Vendor SpaceCadetPinball, build for Apple Silicon
Reverse-engineered reimplementation of 3D Pinball for Windows - Space Cadet from k4zmu2a/SpaceCadetPinball (MIT), upstream cb9b7b8, vendored rather than submoduled so local patches are just edits. - CMakeLists: stop hard-setting CMAKE_OSX_ARCHITECTURES so the documented -D override works; brew's SDL2 is arm64-only and universal linking failed. - gitignore original .DAT/.MID assets, which are not ours to redistribute. - CLAUDE.md: build line, upstream deviation, and where game data goes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
commit
b671975742
63
.gitattributes
vendored
Normal file
63
.gitattributes
vendored
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
###############################################################################
|
||||||
|
# Set default behavior to automatically normalize line endings.
|
||||||
|
###############################################################################
|
||||||
|
* text=auto
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# Set default behavior for command prompt diff.
|
||||||
|
#
|
||||||
|
# This is need for earlier builds of msysgit that does not have it on by
|
||||||
|
# default for csharp files.
|
||||||
|
# Note: This is only used by command line
|
||||||
|
###############################################################################
|
||||||
|
#*.cs diff=csharp
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# Set the merge driver for project and solution files
|
||||||
|
#
|
||||||
|
# Merging from the command prompt will add diff markers to the files if there
|
||||||
|
# are conflicts (Merging from VS is not affected by the settings below, in VS
|
||||||
|
# the diff markers are never inserted). Diff markers may cause the following
|
||||||
|
# file extensions to fail to load in VS. An alternative would be to treat
|
||||||
|
# these files as binary and thus will always conflict and require user
|
||||||
|
# intervention with every merge. To do so, just uncomment the entries below
|
||||||
|
###############################################################################
|
||||||
|
#*.sln merge=binary
|
||||||
|
#*.csproj merge=binary
|
||||||
|
#*.vbproj merge=binary
|
||||||
|
#*.vcxproj merge=binary
|
||||||
|
#*.vcproj merge=binary
|
||||||
|
#*.dbproj merge=binary
|
||||||
|
#*.fsproj merge=binary
|
||||||
|
#*.lsproj merge=binary
|
||||||
|
#*.wixproj merge=binary
|
||||||
|
#*.modelproj merge=binary
|
||||||
|
#*.sqlproj merge=binary
|
||||||
|
#*.wwaproj merge=binary
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# behavior for image files
|
||||||
|
#
|
||||||
|
# image files are treated as binary by default.
|
||||||
|
###############################################################################
|
||||||
|
#*.jpg binary
|
||||||
|
#*.png binary
|
||||||
|
#*.gif binary
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# diff behavior for common document formats
|
||||||
|
#
|
||||||
|
# Convert binary document formats to text before diffing them. This feature
|
||||||
|
# is only available from the command line. Turn it on by uncommenting the
|
||||||
|
# entries below.
|
||||||
|
###############################################################################
|
||||||
|
#*.doc diff=astextplain
|
||||||
|
#*.DOC diff=astextplain
|
||||||
|
#*.docx diff=astextplain
|
||||||
|
#*.DOCX diff=astextplain
|
||||||
|
#*.dot diff=astextplain
|
||||||
|
#*.DOT diff=astextplain
|
||||||
|
#*.pdf diff=astextplain
|
||||||
|
#*.PDF diff=astextplain
|
||||||
|
#*.rtf diff=astextplain
|
||||||
|
#*.RTF diff=astextplain
|
||||||
23
.github/workflows/ReleaseBuilds.yml
vendored
Normal file
23
.github/workflows/ReleaseBuilds.yml
vendored
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
name: Make Release Builds
|
||||||
|
|
||||||
|
on: [workflow_dispatch]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-macos:
|
||||||
|
runs-on: macos-12
|
||||||
|
timeout-minutes: 15
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
ref: master
|
||||||
|
|
||||||
|
- uses: maxim-lobanov/setup-xcode@v1
|
||||||
|
with:
|
||||||
|
xcode-version: 13
|
||||||
|
|
||||||
|
- run: bash build-mac-app.sh
|
||||||
|
|
||||||
|
- uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: mac-build
|
||||||
|
path: SpaceCadetPinball-*-mac.dmg
|
||||||
308
.gitignore
vendored
Normal file
308
.gitignore
vendored
Normal file
@ -0,0 +1,308 @@
|
|||||||
|
## Ignore Visual Studio temporary files, build results, and
|
||||||
|
## files generated by popular Visual Studio add-ons.
|
||||||
|
|
||||||
|
# User-specific files
|
||||||
|
*.suo
|
||||||
|
*.user
|
||||||
|
*.userosscache
|
||||||
|
*.sln.docstates
|
||||||
|
|
||||||
|
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||||
|
*.userprefs
|
||||||
|
|
||||||
|
# Build results
|
||||||
|
[Dd]ebug/
|
||||||
|
[Dd]ebugPublic/
|
||||||
|
[Rr]elease/
|
||||||
|
[Rr]eleases/
|
||||||
|
x64/
|
||||||
|
x86/
|
||||||
|
bld/
|
||||||
|
[Bb]in/
|
||||||
|
[Oo]bj/
|
||||||
|
[Ll]og/
|
||||||
|
ARM/
|
||||||
|
ARM64/
|
||||||
|
win32/
|
||||||
|
|
||||||
|
# Visual Studio 2015 cache/options directory
|
||||||
|
.vs/
|
||||||
|
# Uncomment if you have tasks that create the project's static files in wwwroot
|
||||||
|
#wwwroot/
|
||||||
|
|
||||||
|
# MSTest test Results
|
||||||
|
[Tt]est[Rr]esult*/
|
||||||
|
[Bb]uild[Ll]og.*
|
||||||
|
|
||||||
|
# NUNIT
|
||||||
|
*.VisualState.xml
|
||||||
|
TestResult.xml
|
||||||
|
|
||||||
|
# Build Results of an ATL Project
|
||||||
|
[Dd]ebugPS/
|
||||||
|
[Rr]eleasePS/
|
||||||
|
dlldata.c
|
||||||
|
|
||||||
|
# DNX
|
||||||
|
project.lock.json
|
||||||
|
project.fragment.lock.json
|
||||||
|
artifacts/
|
||||||
|
|
||||||
|
*_i.c
|
||||||
|
*_p.c
|
||||||
|
*_i.h
|
||||||
|
*.ilk
|
||||||
|
*.meta
|
||||||
|
*.obj
|
||||||
|
*.pch
|
||||||
|
*.pdb
|
||||||
|
*.pgc
|
||||||
|
*.pgd
|
||||||
|
*.rsp
|
||||||
|
*.sbr
|
||||||
|
*.tlb
|
||||||
|
*.tli
|
||||||
|
*.tlh
|
||||||
|
*.tmp
|
||||||
|
*.tmp_proj
|
||||||
|
*.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
|
||||||
|
|
||||||
|
# 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
|
||||||
|
|
||||||
|
# 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
|
||||||
|
# TODO: 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
|
||||||
|
# The packages folder can be ignored because of Package Restore
|
||||||
|
**/packages/*
|
||||||
|
# except build/, which is used as an MSBuild target.
|
||||||
|
!**/packages/build/
|
||||||
|
# Uncomment if necessary however generally it will be regenerated when needed
|
||||||
|
#!**/packages/repositories.config
|
||||||
|
# NuGet v3's project.json files produces more ignoreable 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
|
||||||
|
|
||||||
|
# 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
|
||||||
|
node_modules/
|
||||||
|
orleans.codegen.cs
|
||||||
|
|
||||||
|
# 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
|
||||||
|
|
||||||
|
# SQL Server files
|
||||||
|
*.mdf
|
||||||
|
*.ldf
|
||||||
|
|
||||||
|
# Business Intelligence projects
|
||||||
|
*.rdl.data
|
||||||
|
*.bim.layout
|
||||||
|
*.bim_*.settings
|
||||||
|
|
||||||
|
# Microsoft Fakes
|
||||||
|
FakesAssemblies/
|
||||||
|
|
||||||
|
# GhostDoc plugin setting file
|
||||||
|
*.GhostDoc.xml
|
||||||
|
|
||||||
|
# Node.js Tools for Visual Studio
|
||||||
|
.ntvs_analysis.dat
|
||||||
|
|
||||||
|
# Visual Studio 6 build log
|
||||||
|
*.plg
|
||||||
|
|
||||||
|
# Visual Studio 6 workspace options file
|
||||||
|
*.opt
|
||||||
|
|
||||||
|
# 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/
|
||||||
|
|
||||||
|
# JetBrains Rider
|
||||||
|
.idea/
|
||||||
|
*.sln.iml
|
||||||
|
|
||||||
|
# CodeRush
|
||||||
|
.cr/
|
||||||
|
|
||||||
|
# Python Tools for Visual Studio (PTVS)
|
||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
|
/Ida
|
||||||
|
/Export
|
||||||
|
/DrMem
|
||||||
|
/Doc private
|
||||||
|
# Windows and macOS local libraries
|
||||||
|
/Libs
|
||||||
|
|
||||||
|
#CMake generated
|
||||||
|
out/
|
||||||
|
/cmake-build-debug
|
||||||
|
|
||||||
|
# Windows XP stuff
|
||||||
|
DebugWinXp/
|
||||||
|
ReleaseWinXp/
|
||||||
|
|
||||||
|
### CMake ###
|
||||||
|
CMakeLists.txt.user
|
||||||
|
CMakeCache.txt
|
||||||
|
CMakeFiles
|
||||||
|
CMakeScripts
|
||||||
|
Testing
|
||||||
|
Makefile
|
||||||
|
cmake_install.cmake
|
||||||
|
install_manifest.txt
|
||||||
|
compile_commands.json
|
||||||
|
CTestTestfile.cmake
|
||||||
|
_deps
|
||||||
|
|
||||||
|
### CMake Patch ###
|
||||||
|
# External projects
|
||||||
|
*-prefix/
|
||||||
|
|
||||||
|
### Ninja ###
|
||||||
|
.ninja_deps
|
||||||
|
.ninja_log
|
||||||
|
build.ninja
|
||||||
|
# Build directory
|
||||||
|
/build
|
||||||
|
|
||||||
|
# Original Microsoft/Maxis game assets — never commit these, they are not ours to redistribute
|
||||||
|
*.DAT
|
||||||
|
*.dat
|
||||||
|
*.MID
|
||||||
|
*.mid
|
||||||
20
BuildForWindows.ps1
Normal file
20
BuildForWindows.ps1
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
#Run this script from Developer Command Prompt for VS *
|
||||||
|
$artefacts = ".\bin\Release\SpaceCadetPinball.exe", ".\bin\Release\SDL2.dll", ".\bin\Release\SDL2_mixer.dll"
|
||||||
|
|
||||||
|
#X86 build
|
||||||
|
Remove-Item -Path .\build\CMakeCache.txt -ErrorAction SilentlyContinue
|
||||||
|
cmake -S . -B build -A Win32 -DCMAKE_WIN32_EXECUTABLE:BOOL=1
|
||||||
|
cmake --build build --config Release
|
||||||
|
Compress-Archive -Path $artefacts -DestinationPath ".\bin\SpaceCadetPinballx86Win.zip" -Force
|
||||||
|
|
||||||
|
#X64 build
|
||||||
|
Remove-Item -Path .\build\CMakeCache.txt
|
||||||
|
cmake -S . -B build -A x64 -DCMAKE_WIN32_EXECUTABLE:BOOL=1
|
||||||
|
cmake --build build --config Release
|
||||||
|
Compress-Archive -Path $artefacts -DestinationPath ".\bin\SpaceCadetPinballx64Win.zip" -Force
|
||||||
|
|
||||||
|
#86 XP build, requires special XP MSVC toolset
|
||||||
|
Remove-Item -Path .\build\CMakeCache.txt
|
||||||
|
cmake -S . -B build -A Win32 -DCMAKE_WIN32_EXECUTABLE:BOOL=1 -T v141_xp
|
||||||
|
cmake --build build --config Release
|
||||||
|
Compress-Archive -Path $artefacts -DestinationPath ".\bin\SpaceCadetPinballx86WinXP.zip" -Force
|
||||||
42
CLAUDE.md
Normal file
42
CLAUDE.md
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
# macrosoft3dpinball
|
||||||
|
|
||||||
|
`3D Pinball for Windows – Space Cadet` on macOS. Vendored copy of
|
||||||
|
[k4zmu2a/SpaceCadetPinball](https://github.com/k4zmu2a/SpaceCadetPinball) (MIT) at upstream
|
||||||
|
commit `cb9b7b8`, not a submodule — patch it in place, pull upstream by re-vendoring.
|
||||||
|
|
||||||
|
## Build (Apple Silicon)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cmake -B build -DCMAKE_BUILD_TYPE=Release -DCMAKE_OSX_ARCHITECTURES=arm64 \
|
||||||
|
-DCMAKE_PREFIX_PATH="$(brew --prefix sdl2);$(brew --prefix sdl2_mixer)"
|
||||||
|
cmake --build build -j8
|
||||||
|
```
|
||||||
|
|
||||||
|
Binary lands in `bin/SpaceCadetPinball`. Needs `brew install sdl2 sdl2_mixer`.
|
||||||
|
|
||||||
|
Do **not** use upstream `build-mac-app.sh` — it downloads SDL2 `.framework` DMGs and builds a
|
||||||
|
signed universal `.app`+`.dmg`. Unnecessary here; brew SDL2 is already installed.
|
||||||
|
|
||||||
|
## Gotchas
|
||||||
|
|
||||||
|
- `CMakeLists.txt` line ~27 upstream hard-set `CMAKE_OSX_ARCHITECTURES "arm64;x86_64"`, which
|
||||||
|
silently overrode the `-D` flag the README tells you to pass. Universal linking then fails
|
||||||
|
because brew SDL2 is arm64-only. Patched to only set the default when not given on the
|
||||||
|
command line. Re-apply this when re-vendoring upstream.
|
||||||
|
- Homebrew resolves `sdl2` to `sdl2-compat` (SDL2 API on top of SDL3). Links and runs fine.
|
||||||
|
- Deployment target is 10.11, so the link emits "built for newer version 26.0" warnings against
|
||||||
|
the brew dylibs. Harmless.
|
||||||
|
|
||||||
|
## Game data (not in this repo)
|
||||||
|
|
||||||
|
The engine ships no assets. It needs the original data files, which are Microsoft's and are
|
||||||
|
**not redistributable** — supply them from your own media (Windows XP or earlier install disc
|
||||||
|
for `PINBALL.DAT`; the retail `Full Tilt! Pinball` CD for `CADET.DAT`). `.DAT`/`.MID` are
|
||||||
|
gitignored so they never land in a commit.
|
||||||
|
|
||||||
|
Drop them in any of these; first match wins in the order `CADET.DAT`, `PINBALL.DAT`, `DEMO.DAT`:
|
||||||
|
|
||||||
|
- the working directory, or `bin/` next to the binary
|
||||||
|
- `~/Library/Application Support/SpaceCadetPinball/` ← preferred, keeps the repo clean
|
||||||
|
|
||||||
|
Without them the binary exits with "Could not load game data" and lists these paths.
|
||||||
250
CMakeLists.txt
Normal file
250
CMakeLists.txt
Normal file
@ -0,0 +1,250 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.0...3.5)
|
||||||
|
project(SpaceCadetPinball)
|
||||||
|
|
||||||
|
set(CMAKE_CXX_STANDARD 11)
|
||||||
|
|
||||||
|
|
||||||
|
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/bin)
|
||||||
|
|
||||||
|
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/CMakeModules")
|
||||||
|
|
||||||
|
# On Windows, set paths to SDL-devel packages here
|
||||||
|
if(WIN32)
|
||||||
|
list(APPEND SDL2_PATH "${CMAKE_CURRENT_LIST_DIR}/Libs/SDL2")
|
||||||
|
list(APPEND SDL2_MIXER_PATH "${CMAKE_CURRENT_LIST_DIR}/Libs/SDL2_mixer")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Link mingw libs static
|
||||||
|
if(MINGW)
|
||||||
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -static")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(APPLE)
|
||||||
|
set(MACOSX_RPATH)
|
||||||
|
set(CMAKE_BUILD_WITH_INSTALL_RPATH true)
|
||||||
|
set(CMAKE_INSTALL_RPATH "@executable_path/../Frameworks")
|
||||||
|
set(CMAKE_OSX_DEPLOYMENT_TARGET "10.11")
|
||||||
|
# Universal by default, but let -DCMAKE_OSX_ARCHITECTURES win (brew SDL2 is single-arch).
|
||||||
|
if(NOT DEFINED CACHE{CMAKE_OSX_ARCHITECTURES})
|
||||||
|
set(CMAKE_OSX_ARCHITECTURES "arm64;x86_64")
|
||||||
|
endif()
|
||||||
|
list(APPEND SDL2_PATH "${CMAKE_CURRENT_LIST_DIR}/Libs")
|
||||||
|
list(APPEND SDL2_MIXER_PATH "${CMAKE_CURRENT_LIST_DIR}/Libs")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# SDL2main is not needed
|
||||||
|
set(SDL2_BUILDING_LIBRARY ON)
|
||||||
|
|
||||||
|
find_package(SDL2 REQUIRED)
|
||||||
|
FIND_PACKAGE(SDL2_mixer REQUIRED)
|
||||||
|
|
||||||
|
include_directories(${SDL2_INCLUDE_DIR} ${SDL2_MIXER_INCLUDE_DIR})
|
||||||
|
get_property(dirs DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY INCLUDE_DIRECTORIES)
|
||||||
|
foreach(dir ${dirs})
|
||||||
|
message(STATUS "Include dir='${dir}'")
|
||||||
|
endforeach()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
set(SOURCE_FILES
|
||||||
|
SpaceCadetPinball/control.cpp
|
||||||
|
SpaceCadetPinball/control.h
|
||||||
|
SpaceCadetPinball/EmbeddedData.cpp
|
||||||
|
SpaceCadetPinball/EmbeddedData.h
|
||||||
|
SpaceCadetPinball/font_selection.cpp
|
||||||
|
SpaceCadetPinball/font_selection.h
|
||||||
|
SpaceCadetPinball/fullscrn.cpp
|
||||||
|
SpaceCadetPinball/fullscrn.h
|
||||||
|
SpaceCadetPinball/gdrv.cpp
|
||||||
|
SpaceCadetPinball/gdrv.h
|
||||||
|
SpaceCadetPinball/GroupData.cpp
|
||||||
|
SpaceCadetPinball/GroupData.h
|
||||||
|
SpaceCadetPinball/high_score.cpp
|
||||||
|
SpaceCadetPinball/high_score.h
|
||||||
|
SpaceCadetPinball/loader.cpp
|
||||||
|
SpaceCadetPinball/loader.h
|
||||||
|
SpaceCadetPinball/maths.cpp
|
||||||
|
SpaceCadetPinball/maths.h
|
||||||
|
SpaceCadetPinball/midi.cpp
|
||||||
|
SpaceCadetPinball/midi.h
|
||||||
|
SpaceCadetPinball/nudge.cpp
|
||||||
|
SpaceCadetPinball/nudge.h
|
||||||
|
SpaceCadetPinball/options.cpp
|
||||||
|
SpaceCadetPinball/options.h
|
||||||
|
SpaceCadetPinball/partman.cpp
|
||||||
|
SpaceCadetPinball/partman.h
|
||||||
|
SpaceCadetPinball/pb.cpp
|
||||||
|
SpaceCadetPinball/pb.h
|
||||||
|
SpaceCadetPinball/pch.h
|
||||||
|
SpaceCadetPinball/proj.cpp
|
||||||
|
SpaceCadetPinball/proj.h
|
||||||
|
SpaceCadetPinball/render.cpp
|
||||||
|
SpaceCadetPinball/render.h
|
||||||
|
SpaceCadetPinball/score.cpp
|
||||||
|
SpaceCadetPinball/score.h
|
||||||
|
SpaceCadetPinball/Sound.cpp
|
||||||
|
SpaceCadetPinball/Sound.h
|
||||||
|
SpaceCadetPinball/SpaceCadetPinball.cpp
|
||||||
|
SpaceCadetPinball/TBall.cpp
|
||||||
|
SpaceCadetPinball/TBall.h
|
||||||
|
SpaceCadetPinball/TBlocker.cpp
|
||||||
|
SpaceCadetPinball/TBlocker.h
|
||||||
|
SpaceCadetPinball/TBumper.cpp
|
||||||
|
SpaceCadetPinball/TBumper.h
|
||||||
|
SpaceCadetPinball/TCircle.cpp
|
||||||
|
SpaceCadetPinball/TCircle.h
|
||||||
|
SpaceCadetPinball/TCollisionComponent.cpp
|
||||||
|
SpaceCadetPinball/TCollisionComponent.h
|
||||||
|
SpaceCadetPinball/TComponentGroup.cpp
|
||||||
|
SpaceCadetPinball/TComponentGroup.h
|
||||||
|
SpaceCadetPinball/TDemo.cpp
|
||||||
|
SpaceCadetPinball/TDemo.h
|
||||||
|
SpaceCadetPinball/TDrain.cpp
|
||||||
|
SpaceCadetPinball/TDrain.h
|
||||||
|
SpaceCadetPinball/TEdgeBox.h
|
||||||
|
SpaceCadetPinball/TEdgeManager.cpp
|
||||||
|
SpaceCadetPinball/TEdgeManager.h
|
||||||
|
SpaceCadetPinball/TEdgeSegment.cpp
|
||||||
|
SpaceCadetPinball/TEdgeSegment.h
|
||||||
|
SpaceCadetPinball/TFlagSpinner.cpp
|
||||||
|
SpaceCadetPinball/TFlagSpinner.h
|
||||||
|
SpaceCadetPinball/TFlipper.cpp
|
||||||
|
SpaceCadetPinball/TFlipper.h
|
||||||
|
SpaceCadetPinball/TFlipperEdge.cpp
|
||||||
|
SpaceCadetPinball/TFlipperEdge.h
|
||||||
|
SpaceCadetPinball/TGate.cpp
|
||||||
|
SpaceCadetPinball/TGate.h
|
||||||
|
SpaceCadetPinball/THole.cpp
|
||||||
|
SpaceCadetPinball/THole.h
|
||||||
|
SpaceCadetPinball/timer.cpp
|
||||||
|
SpaceCadetPinball/timer.h
|
||||||
|
SpaceCadetPinball/TKickback.cpp
|
||||||
|
SpaceCadetPinball/TKickback.h
|
||||||
|
SpaceCadetPinball/TKickout.cpp
|
||||||
|
SpaceCadetPinball/TKickout.h
|
||||||
|
SpaceCadetPinball/TLight.cpp
|
||||||
|
SpaceCadetPinball/TLight.h
|
||||||
|
SpaceCadetPinball/TLightBargraph.cpp
|
||||||
|
SpaceCadetPinball/TLightBargraph.h
|
||||||
|
SpaceCadetPinball/TLightGroup.cpp
|
||||||
|
SpaceCadetPinball/TLightGroup.h
|
||||||
|
SpaceCadetPinball/TLightRollover.cpp
|
||||||
|
SpaceCadetPinball/TLightRollover.h
|
||||||
|
SpaceCadetPinball/TLine.cpp
|
||||||
|
SpaceCadetPinball/TLine.h
|
||||||
|
SpaceCadetPinball/TOneway.cpp
|
||||||
|
SpaceCadetPinball/TOneway.h
|
||||||
|
SpaceCadetPinball/TPinballComponent.cpp
|
||||||
|
SpaceCadetPinball/TPinballComponent.h
|
||||||
|
SpaceCadetPinball/TPinballTable.cpp
|
||||||
|
SpaceCadetPinball/TPinballTable.h
|
||||||
|
SpaceCadetPinball/TPlunger.cpp
|
||||||
|
SpaceCadetPinball/TPlunger.h
|
||||||
|
SpaceCadetPinball/TPopupTarget.cpp
|
||||||
|
SpaceCadetPinball/TPopupTarget.h
|
||||||
|
SpaceCadetPinball/TRamp.cpp
|
||||||
|
SpaceCadetPinball/TRamp.h
|
||||||
|
SpaceCadetPinball/translations.cpp
|
||||||
|
SpaceCadetPinball/translations.h
|
||||||
|
SpaceCadetPinball/TRollover.cpp
|
||||||
|
SpaceCadetPinball/TRollover.h
|
||||||
|
SpaceCadetPinball/TSink.cpp
|
||||||
|
SpaceCadetPinball/TSink.h
|
||||||
|
SpaceCadetPinball/TSoloTarget.cpp
|
||||||
|
SpaceCadetPinball/TSoloTarget.h
|
||||||
|
SpaceCadetPinball/TSound.cpp
|
||||||
|
SpaceCadetPinball/TSound.h
|
||||||
|
SpaceCadetPinball/TTableLayer.cpp
|
||||||
|
SpaceCadetPinball/TTableLayer.h
|
||||||
|
SpaceCadetPinball/TTextBox.cpp
|
||||||
|
SpaceCadetPinball/TTextBox.h
|
||||||
|
SpaceCadetPinball/TTextBoxMessage.cpp
|
||||||
|
SpaceCadetPinball/TTextBoxMessage.h
|
||||||
|
SpaceCadetPinball/TTimer.cpp
|
||||||
|
SpaceCadetPinball/TTimer.h
|
||||||
|
SpaceCadetPinball/TTripwire.cpp
|
||||||
|
SpaceCadetPinball/TTripwire.h
|
||||||
|
SpaceCadetPinball/TWall.cpp
|
||||||
|
SpaceCadetPinball/TWall.h
|
||||||
|
SpaceCadetPinball/winmain.cpp
|
||||||
|
SpaceCadetPinball/winmain.h
|
||||||
|
SpaceCadetPinball/zdrv.cpp
|
||||||
|
SpaceCadetPinball/zdrv.h
|
||||||
|
SpaceCadetPinball/imconfig.h
|
||||||
|
SpaceCadetPinball/imgui_internal.h
|
||||||
|
SpaceCadetPinball/imgui.cpp
|
||||||
|
SpaceCadetPinball/imgui.h
|
||||||
|
SpaceCadetPinball/imgui_sdl.cpp
|
||||||
|
SpaceCadetPinball/imgui_sdl.h
|
||||||
|
SpaceCadetPinball/imgui_draw.cpp
|
||||||
|
SpaceCadetPinball/imgui_widgets.cpp
|
||||||
|
SpaceCadetPinball/imgui_tables.cpp
|
||||||
|
SpaceCadetPinball/imgui_demo.cpp
|
||||||
|
SpaceCadetPinball/imgui_impl_sdl.cpp
|
||||||
|
SpaceCadetPinball/imgui_impl_sdl.h
|
||||||
|
SpaceCadetPinball/imgui_impl_sdlrenderer.h
|
||||||
|
SpaceCadetPinball/imgui_impl_sdlrenderer.cpp
|
||||||
|
SpaceCadetPinball/imstb_textedit.h
|
||||||
|
SpaceCadetPinball/imstb_rectpack.h
|
||||||
|
SpaceCadetPinball/imstb_truetype.h
|
||||||
|
SpaceCadetPinball/DebugOverlay.cpp
|
||||||
|
SpaceCadetPinball/DebugOverlay.h
|
||||||
|
)
|
||||||
|
|
||||||
|
# On Windows, include resource file with the icon
|
||||||
|
if(WIN32)
|
||||||
|
set_source_files_properties(SpaceCadetPinball/SpaceCadetPinball.rc LANGUAGE RC)
|
||||||
|
list(APPEND SOURCE_FILES SpaceCadetPinball/SpaceCadetPinball.rc)
|
||||||
|
endif(WIN32)
|
||||||
|
|
||||||
|
add_executable(SpaceCadetPinball ${SOURCE_FILES})
|
||||||
|
|
||||||
|
# Skip pch on foreign code
|
||||||
|
set_source_files_properties(
|
||||||
|
SpaceCadetPinball/imgui.cpp
|
||||||
|
SpaceCadetPinball/imgui_sdl.cpp
|
||||||
|
SpaceCadetPinball/imgui_draw.cpp
|
||||||
|
SpaceCadetPinball/imgui_widgets.cpp
|
||||||
|
SpaceCadetPinball/imgui_tables.cpp
|
||||||
|
SpaceCadetPinball/imgui_demo.cpp
|
||||||
|
SpaceCadetPinball/imgui_impl_sdl.cpp
|
||||||
|
SpaceCadetPinball/imgui_impl_sdlrenderer.cpp
|
||||||
|
PROPERTIES SKIP_PRECOMPILE_HEADERS 1
|
||||||
|
)
|
||||||
|
|
||||||
|
if(${CMAKE_VERSION} VERSION_GREATER "3.16.0" OR ${CMAKE_VERSION} VERSION_EQUAL "3.16.0")
|
||||||
|
target_precompile_headers(SpaceCadetPinball
|
||||||
|
PUBLIC
|
||||||
|
SpaceCadetPinball/pch.h
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
target_link_libraries(SpaceCadetPinball ${SDL2_LIBRARY} ${SDL2_MIXER_LIBRARY})
|
||||||
|
|
||||||
|
# On Windows, copy DLL to output
|
||||||
|
if(WIN32)
|
||||||
|
list(GET SDL2_LIBRARY -1 SDL2_DLL_PATH)
|
||||||
|
list(GET SDL2_MIXER_LIBRARY -1 SDL2_MIXER_DLL_PATH)
|
||||||
|
get_filename_component(SDL2_DLL_PATH ${SDL2_DLL_PATH} DIRECTORY)
|
||||||
|
get_filename_component(SDL2_MIXER_DLL_PATH ${SDL2_MIXER_DLL_PATH} DIRECTORY)
|
||||||
|
if(MINGW)
|
||||||
|
string(REGEX REPLACE "lib$" "bin" SDL2_DLL_PATH ${SDL2_DLL_PATH})
|
||||||
|
string(REGEX REPLACE "lib$" "bin" SDL2_MIXER_DLL_PATH ${SDL2_MIXER_DLL_PATH})
|
||||||
|
endif()
|
||||||
|
message(STATUS "copy paths='${SDL2_DLL_PATH}' '${SDL2_MIXER_DLL_PATH}'")
|
||||||
|
add_custom_command(TARGET SpaceCadetPinball POST_BUILD
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E copy_if_different "${SDL2_DLL_PATH}/SDL2.dll" $<TARGET_FILE_DIR:SpaceCadetPinball>
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E copy_if_different "${SDL2_MIXER_DLL_PATH}/SDL2_mixer.dll" $<TARGET_FILE_DIR:SpaceCadetPinball>
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(UNIX AND NOT APPLE)
|
||||||
|
include(GNUInstallDirs)
|
||||||
|
install(TARGETS "${PROJECT_NAME}" RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}")
|
||||||
|
install(FILES "${CMAKE_SOURCE_DIR}/Platform/Linux/${PROJECT_NAME}.desktop" DESTINATION "share/applications")
|
||||||
|
install(FILES "${CMAKE_SOURCE_DIR}/Platform/Linux/${PROJECT_NAME}.metainfo.xml" DESTINATION "share/metainfo")
|
||||||
|
foreach(S 16 32 48 128 192)
|
||||||
|
install(FILES "${CMAKE_SOURCE_DIR}/${PROJECT_NAME}/Icon_${S}x${S}.png" DESTINATION
|
||||||
|
"share/icons/hicolor/${S}x${S}/apps" RENAME "${PROJECT_NAME}.png")
|
||||||
|
endforeach(S)
|
||||||
|
endif()
|
||||||
173
CMakeModules/FindSDL2.cmake
Normal file
173
CMakeModules/FindSDL2.cmake
Normal file
@ -0,0 +1,173 @@
|
|||||||
|
|
||||||
|
# This module defines
|
||||||
|
# SDL2_LIBRARY, the name of the library to link against
|
||||||
|
# SDL2_FOUND, if false, do not try to link to SDL2
|
||||||
|
# SDL2_INCLUDE_DIR, where to find SDL.h
|
||||||
|
#
|
||||||
|
# This module responds to the the flag:
|
||||||
|
# SDL2_BUILDING_LIBRARY
|
||||||
|
# If this is defined, then no SDL2main will be linked in because
|
||||||
|
# only applications need main().
|
||||||
|
# Otherwise, it is assumed you are building an application and this
|
||||||
|
# module will attempt to locate and set the the proper link flags
|
||||||
|
# as part of the returned SDL2_LIBRARY variable.
|
||||||
|
#
|
||||||
|
# Don't forget to include SDLmain.h and SDLmain.m your project for the
|
||||||
|
# OS X framework based version. (Other versions link to -lSDL2main which
|
||||||
|
# this module will try to find on your behalf.) Also for OS X, this
|
||||||
|
# module will automatically add the -framework Cocoa on your behalf.
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# Additional Note: If you see an empty SDL2_LIBRARY_TEMP in your configuration
|
||||||
|
# and no SDL2_LIBRARY, it means CMake did not find your SDL2 library
|
||||||
|
# (SDL2.dll, libsdl2.so, SDL2.framework, etc).
|
||||||
|
# Set SDL2_LIBRARY_TEMP to point to your SDL2 library, and configure again.
|
||||||
|
# Similarly, if you see an empty SDL2MAIN_LIBRARY, you should set this value
|
||||||
|
# as appropriate. These values are used to generate the final SDL2_LIBRARY
|
||||||
|
# variable, but when these values are unset, SDL2_LIBRARY does not get created.
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# $SDL2DIR is an environment variable that would
|
||||||
|
# correspond to the ./configure --prefix=$SDL2DIR
|
||||||
|
# used in building SDL2.
|
||||||
|
# l.e.galup 9-20-02
|
||||||
|
#
|
||||||
|
# Modified by Eric Wing.
|
||||||
|
# Added code to assist with automated building by using environmental variables
|
||||||
|
# and providing a more controlled/consistent search behavior.
|
||||||
|
# Added new modifications to recognize OS X frameworks and
|
||||||
|
# additional Unix paths (FreeBSD, etc).
|
||||||
|
# Also corrected the header search path to follow "proper" SDL guidelines.
|
||||||
|
# Added a search for SDL2main which is needed by some platforms.
|
||||||
|
# Added a search for threads which is needed by some platforms.
|
||||||
|
# Added needed compile switches for MinGW.
|
||||||
|
#
|
||||||
|
# On OSX, this will prefer the Framework version (if found) over others.
|
||||||
|
# People will have to manually change the cache values of
|
||||||
|
# SDL2_LIBRARY to override this selection or set the CMake environment
|
||||||
|
# CMAKE_INCLUDE_PATH to modify the search paths.
|
||||||
|
#
|
||||||
|
# Note that the header path has changed from SDL2/SDL.h to just SDL.h
|
||||||
|
# This needed to change because "proper" SDL convention
|
||||||
|
# is #include "SDL.h", not <SDL2/SDL.h>. This is done for portability
|
||||||
|
# reasons because not all systems place things in SDL2/ (see FreeBSD).
|
||||||
|
|
||||||
|
#=============================================================================
|
||||||
|
# Copyright 2003-2009 Kitware, Inc.
|
||||||
|
#
|
||||||
|
# Distributed under the OSI-approved BSD License (the "License");
|
||||||
|
# see accompanying file Copyright.txt for details.
|
||||||
|
#
|
||||||
|
# This software is distributed WITHOUT ANY WARRANTY; without even the
|
||||||
|
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||||
|
# See the License for more information.
|
||||||
|
#=============================================================================
|
||||||
|
# (To distribute this file outside of CMake, substitute the full
|
||||||
|
# License text for the above reference.)
|
||||||
|
|
||||||
|
# message("<FindSDL2.cmake>")
|
||||||
|
|
||||||
|
SET(SDL2_SEARCH_PATHS
|
||||||
|
~/Library/Frameworks
|
||||||
|
/Library/Frameworks
|
||||||
|
/usr/local
|
||||||
|
/usr
|
||||||
|
/sw # Fink
|
||||||
|
/opt/local # DarwinPorts
|
||||||
|
/opt/csw # Blastwave
|
||||||
|
/opt
|
||||||
|
${SDL2_PATH}
|
||||||
|
)
|
||||||
|
|
||||||
|
FIND_PATH(SDL2_INCLUDE_DIR SDL.h
|
||||||
|
HINTS
|
||||||
|
$ENV{SDL2DIR}
|
||||||
|
PATH_SUFFIXES include/SDL2 include SDL2
|
||||||
|
PATHS ${SDL2_SEARCH_PATHS}
|
||||||
|
)
|
||||||
|
|
||||||
|
if(CMAKE_SIZEOF_VOID_P EQUAL 8)
|
||||||
|
set(PATH_SUFFIXES lib64 lib/x64 lib)
|
||||||
|
else()
|
||||||
|
set(PATH_SUFFIXES lib/x86 lib)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
FIND_LIBRARY(SDL2_LIBRARY_TEMP
|
||||||
|
NAMES SDL2
|
||||||
|
HINTS
|
||||||
|
$ENV{SDL2DIR}
|
||||||
|
PATH_SUFFIXES ${PATH_SUFFIXES}
|
||||||
|
PATHS ${SDL2_SEARCH_PATHS}
|
||||||
|
)
|
||||||
|
|
||||||
|
IF(NOT SDL2_BUILDING_LIBRARY)
|
||||||
|
IF(NOT ${SDL2_INCLUDE_DIR} MATCHES ".framework")
|
||||||
|
# Non-OS X framework versions expect you to also dynamically link to
|
||||||
|
# SDL2main. This is mainly for Windows and OS X. Other (Unix) platforms
|
||||||
|
# seem to provide SDL2main for compatibility even though they don't
|
||||||
|
# necessarily need it.
|
||||||
|
FIND_LIBRARY(SDL2MAIN_LIBRARY
|
||||||
|
NAMES SDL2main
|
||||||
|
HINTS
|
||||||
|
$ENV{SDL2DIR}
|
||||||
|
PATH_SUFFIXES ${PATH_SUFFIXES}
|
||||||
|
PATHS ${SDL2_SEARCH_PATHS}
|
||||||
|
)
|
||||||
|
ENDIF(NOT ${SDL2_INCLUDE_DIR} MATCHES ".framework")
|
||||||
|
ENDIF(NOT SDL2_BUILDING_LIBRARY)
|
||||||
|
|
||||||
|
# SDL2 may require threads on your system.
|
||||||
|
# The Apple build may not need an explicit flag because one of the
|
||||||
|
# frameworks may already provide it.
|
||||||
|
# But for non-OSX systems, I will use the CMake Threads package.
|
||||||
|
IF(NOT APPLE)
|
||||||
|
FIND_PACKAGE(Threads)
|
||||||
|
ENDIF(NOT APPLE)
|
||||||
|
|
||||||
|
# MinGW needs an additional link flag, -mwindows
|
||||||
|
# It's total link flags should look like -lmingw32 -lSDL2main -lSDL2 -mwindows
|
||||||
|
IF(MINGW)
|
||||||
|
SET(MINGW32_LIBRARY mingw32 "-mwindows" CACHE STRING "mwindows for MinGW")
|
||||||
|
ENDIF(MINGW)
|
||||||
|
|
||||||
|
IF(SDL2_LIBRARY_TEMP)
|
||||||
|
# For SDL2main
|
||||||
|
IF(NOT SDL2_BUILDING_LIBRARY)
|
||||||
|
IF(SDL2MAIN_LIBRARY)
|
||||||
|
SET(SDL2_LIBRARY_TEMP ${SDL2MAIN_LIBRARY} ${SDL2_LIBRARY_TEMP})
|
||||||
|
ENDIF(SDL2MAIN_LIBRARY)
|
||||||
|
ENDIF(NOT SDL2_BUILDING_LIBRARY)
|
||||||
|
|
||||||
|
# For OS X, SDL2 uses Cocoa as a backend so it must link to Cocoa.
|
||||||
|
# CMake doesn't display the -framework Cocoa string in the UI even
|
||||||
|
# though it actually is there if I modify a pre-used variable.
|
||||||
|
# I think it has something to do with the CACHE STRING.
|
||||||
|
# So I use a temporary variable until the end so I can set the
|
||||||
|
# "real" variable in one-shot.
|
||||||
|
IF(APPLE)
|
||||||
|
SET(SDL2_LIBRARY_TEMP ${SDL2_LIBRARY_TEMP} "-framework Cocoa")
|
||||||
|
ENDIF(APPLE)
|
||||||
|
|
||||||
|
# For threads, as mentioned Apple doesn't need this.
|
||||||
|
# In fact, there seems to be a problem if I used the Threads package
|
||||||
|
# and try using this line, so I'm just skipping it entirely for OS X.
|
||||||
|
IF(NOT APPLE)
|
||||||
|
SET(SDL2_LIBRARY_TEMP ${CMAKE_THREAD_LIBS_INIT} ${SDL2_LIBRARY_TEMP})
|
||||||
|
ENDIF(NOT APPLE)
|
||||||
|
|
||||||
|
# For MinGW library
|
||||||
|
IF(MINGW)
|
||||||
|
SET(SDL2_LIBRARY_TEMP ${MINGW32_LIBRARY} ${SDL2_LIBRARY_TEMP})
|
||||||
|
ENDIF(MINGW)
|
||||||
|
|
||||||
|
# Set the final string here so the GUI reflects the final state.
|
||||||
|
SET(SDL2_LIBRARY ${SDL2_LIBRARY_TEMP} CACHE STRING "Where the SDL2 Library can be found")
|
||||||
|
# Set the temp variable to INTERNAL so it is not seen in the CMake GUI
|
||||||
|
SET(SDL2_LIBRARY_TEMP "${SDL2_LIBRARY_TEMP}" CACHE INTERNAL "")
|
||||||
|
ENDIF(SDL2_LIBRARY_TEMP)
|
||||||
|
|
||||||
|
# message("</FindSDL2.cmake>")
|
||||||
|
|
||||||
|
INCLUDE(FindPackageHandleStandardArgs)
|
||||||
|
|
||||||
|
FIND_PACKAGE_HANDLE_STANDARD_ARGS(SDL2 REQUIRED_VARS SDL2_LIBRARY SDL2_INCLUDE_DIR)
|
||||||
100
CMakeModules/FindSDL2_mixer.cmake
Normal file
100
CMakeModules/FindSDL2_mixer.cmake
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
# Locate SDL_MIXER library
|
||||||
|
#
|
||||||
|
# This module defines:
|
||||||
|
#
|
||||||
|
# ::
|
||||||
|
#
|
||||||
|
# SDL2_MIXER_LIBRARIES, the name of the library to link against
|
||||||
|
# SDL2_MIXER_INCLUDE_DIRS, where to find the headers
|
||||||
|
# SDL2_MIXER_FOUND, if false, do not try to link against
|
||||||
|
# SDL2_MIXER_VERSION_STRING - human-readable string containing the version of SDL_MIXER
|
||||||
|
#
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# For backward compatibility the following variables are also set:
|
||||||
|
#
|
||||||
|
# ::
|
||||||
|
#
|
||||||
|
# SDLMIXER_LIBRARY (same value as SDL2_MIXER_LIBRARIES)
|
||||||
|
# SDLMIXER_INCLUDE_DIR (same value as SDL2_MIXER_INCLUDE_DIRS)
|
||||||
|
# SDLMIXER_FOUND (same value as SDL2_MIXER_FOUND)
|
||||||
|
#
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# $SDLDIR is an environment variable that would correspond to the
|
||||||
|
# ./configure --prefix=$SDLDIR used in building SDL.
|
||||||
|
#
|
||||||
|
# Created by Eric Wing. This was influenced by the FindSDL.cmake
|
||||||
|
# module, but with modifications to recognize OS X frameworks and
|
||||||
|
# additional Unix paths (FreeBSD, etc).
|
||||||
|
|
||||||
|
#=============================================================================
|
||||||
|
# Copyright 2005-2009 Kitware, Inc.
|
||||||
|
# Copyright 2012 Benjamin Eikel
|
||||||
|
#
|
||||||
|
# Distributed under the OSI-approved BSD License (the "License");
|
||||||
|
# see accompanying file Copyright.txt for details.
|
||||||
|
#
|
||||||
|
# This software is distributed WITHOUT ANY WARRANTY; without even the
|
||||||
|
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||||
|
# See the License for more information.
|
||||||
|
#=============================================================================
|
||||||
|
# (To distribute this file outside of CMake, substitute the full
|
||||||
|
# License text for the above reference.)
|
||||||
|
|
||||||
|
find_path(SDL2_MIXER_INCLUDE_DIR SDL_mixer.h
|
||||||
|
HINTS
|
||||||
|
ENV SDL2MIXERDIR
|
||||||
|
ENV SDL2DIR
|
||||||
|
PATH_SUFFIXES SDL2
|
||||||
|
# path suffixes to search inside ENV{SDLDIR}
|
||||||
|
include/SDL2 include
|
||||||
|
PATHS ${SDL2_MIXER_PATH}
|
||||||
|
)
|
||||||
|
|
||||||
|
if(CMAKE_SIZEOF_VOID_P EQUAL 8)
|
||||||
|
set(VC_LIB_PATH_SUFFIX lib/x64)
|
||||||
|
else()
|
||||||
|
set(VC_LIB_PATH_SUFFIX lib/x86)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
find_library(SDL2_MIXER_LIBRARY
|
||||||
|
NAMES SDL2_mixer
|
||||||
|
HINTS
|
||||||
|
ENV SDL2MIXERDIR
|
||||||
|
ENV SDL2DIR
|
||||||
|
PATH_SUFFIXES lib bin ${VC_LIB_PATH_SUFFIX}
|
||||||
|
PATHS ${SDL2_MIXER_PATH}
|
||||||
|
)
|
||||||
|
|
||||||
|
if(SDL2_MIXER_INCLUDE_DIR AND EXISTS "${SDL2_MIXER_INCLUDE_DIR}/SDL_mixer.h")
|
||||||
|
file(STRINGS "${SDL2_MIXER_INCLUDE_DIR}/SDL_mixer.h" SDL2_MIXER_VERSION_MAJOR_LINE REGEX "^#define[ \t]+SDL_MIXER_MAJOR_VERSION[ \t]+[0-9]+$")
|
||||||
|
file(STRINGS "${SDL2_MIXER_INCLUDE_DIR}/SDL_mixer.h" SDL2_MIXER_VERSION_MINOR_LINE REGEX "^#define[ \t]+SDL_MIXER_MINOR_VERSION[ \t]+[0-9]+$")
|
||||||
|
file(STRINGS "${SDL2_MIXER_INCLUDE_DIR}/SDL_mixer.h" SDL2_MIXER_VERSION_PATCH_LINE REGEX "^#define[ \t]+SDL_MIXER_PATCHLEVEL[ \t]+[0-9]+$")
|
||||||
|
string(REGEX REPLACE "^#define[ \t]+SDL_MIXER_MAJOR_VERSION[ \t]+([0-9]+)$" "\\1" SDL2_MIXER_VERSION_MAJOR "${SDL2_MIXER_VERSION_MAJOR_LINE}")
|
||||||
|
string(REGEX REPLACE "^#define[ \t]+SDL_MIXER_MINOR_VERSION[ \t]+([0-9]+)$" "\\1" SDL2_MIXER_VERSION_MINOR "${SDL2_MIXER_VERSION_MINOR_LINE}")
|
||||||
|
string(REGEX REPLACE "^#define[ \t]+SDL_MIXER_PATCHLEVEL[ \t]+([0-9]+)$" "\\1" SDL2_MIXER_VERSION_PATCH "${SDL2_MIXER_VERSION_PATCH_LINE}")
|
||||||
|
set(SDL2_MIXER_VERSION_STRING ${SDL2_MIXER_VERSION_MAJOR}.${SDL2_MIXER_VERSION_MINOR}.${SDL2_MIXER_VERSION_PATCH})
|
||||||
|
unset(SDL2_MIXER_VERSION_MAJOR_LINE)
|
||||||
|
unset(SDL2_MIXER_VERSION_MINOR_LINE)
|
||||||
|
unset(SDL2_MIXER_VERSION_PATCH_LINE)
|
||||||
|
unset(SDL2_MIXER_VERSION_MAJOR)
|
||||||
|
unset(SDL2_MIXER_VERSION_MINOR)
|
||||||
|
unset(SDL2_MIXER_VERSION_PATCH)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
set(SDL2_MIXER_LIBRARIES ${SDL2_MIXER_LIBRARY})
|
||||||
|
set(SDL2_MIXER_INCLUDE_DIRS ${SDL2_MIXER_INCLUDE_DIR})
|
||||||
|
|
||||||
|
include(FindPackageHandleStandardArgs)
|
||||||
|
|
||||||
|
FIND_PACKAGE_HANDLE_STANDARD_ARGS(SDL2_mixer
|
||||||
|
REQUIRED_VARS SDL2_MIXER_LIBRARIES SDL2_MIXER_INCLUDE_DIRS
|
||||||
|
VERSION_VAR SDL2_MIXER_VERSION_STRING)
|
||||||
|
|
||||||
|
# for backward compatibility
|
||||||
|
set(SDLMIXER_LIBRARY ${SDL2_MIXER_LIBRARIES})
|
||||||
|
set(SDLMIXER_INCLUDE_DIR ${SDL2_MIXER_INCLUDE_DIRS})
|
||||||
|
set(SDLMIXER_FOUND ${SDL2_MIXER_FOUND})
|
||||||
|
|
||||||
|
mark_as_advanced(SDL2_MIXER_LIBRARY SDL2_MIXER_INCLUDE_DIR)
|
||||||
74
CMakeSettings.json
Normal file
74
CMakeSettings.json
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
{
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "x64-Debug",
|
||||||
|
"generator": "Ninja",
|
||||||
|
"configurationType": "Debug",
|
||||||
|
"inheritEnvironments": [ "msvc_x64_x64" ],
|
||||||
|
"buildRoot": "${projectDir}\\out\\build\\${name}",
|
||||||
|
"installRoot": "${projectDir}\\out\\install\\${name}",
|
||||||
|
"cmakeCommandArgs": "",
|
||||||
|
"buildCommandArgs": "",
|
||||||
|
"ctestCommandArgs": "",
|
||||||
|
"addressSanitizerEnabled": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "x86-Debug",
|
||||||
|
"generator": "Ninja",
|
||||||
|
"configurationType": "Debug",
|
||||||
|
"buildRoot": "${projectDir}\\out\\build\\${name}",
|
||||||
|
"installRoot": "${projectDir}\\out\\install\\${name}",
|
||||||
|
"cmakeCommandArgs": "",
|
||||||
|
"buildCommandArgs": "",
|
||||||
|
"ctestCommandArgs": "",
|
||||||
|
"inheritEnvironments": [ "msvc_x86" ],
|
||||||
|
"addressSanitizerEnabled": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "x86-Release",
|
||||||
|
"generator": "Ninja",
|
||||||
|
"configurationType": "Release",
|
||||||
|
"buildRoot": "${projectDir}\\out\\build\\${name}",
|
||||||
|
"installRoot": "${projectDir}\\out\\install\\${name}",
|
||||||
|
"cmakeCommandArgs": "-DCMAKE_WIN32_EXECUTABLE:BOOL=1",
|
||||||
|
"buildCommandArgs": "",
|
||||||
|
"ctestCommandArgs": "",
|
||||||
|
"inheritEnvironments": [ "msvc_x86" ]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "x64-Release",
|
||||||
|
"generator": "Ninja",
|
||||||
|
"configurationType": "Release",
|
||||||
|
"buildRoot": "${projectDir}\\out\\build\\${name}",
|
||||||
|
"installRoot": "${projectDir}\\out\\install\\${name}",
|
||||||
|
"cmakeCommandArgs": "-DCMAKE_WIN32_EXECUTABLE:BOOL=1",
|
||||||
|
"buildCommandArgs": "",
|
||||||
|
"ctestCommandArgs": "",
|
||||||
|
"inheritEnvironments": [ "msvc_x64_x64" ]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "x86-Release-WinXP",
|
||||||
|
"generator": "Visual Studio 16 2019",
|
||||||
|
"configurationType": "Release",
|
||||||
|
"buildRoot": "${projectDir}\\out\\build\\${name}",
|
||||||
|
"installRoot": "${projectDir}\\out\\install\\${name}",
|
||||||
|
"cmakeCommandArgs": "-DCMAKE_WIN32_EXECUTABLE:BOOL=1 -T v141_xp",
|
||||||
|
"buildCommandArgs": "",
|
||||||
|
"ctestCommandArgs": "",
|
||||||
|
"inheritEnvironments": [ "msvc_x86" ]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "WSL-Clang-Debug",
|
||||||
|
"generator": "Ninja",
|
||||||
|
"configurationType": "Debug",
|
||||||
|
"buildRoot": "${projectDir}\\out\\build\\${name}",
|
||||||
|
"installRoot": "${projectDir}\\out\\install\\${name}",
|
||||||
|
"cmakeExecutable": "cmake",
|
||||||
|
"cmakeCommandArgs": "",
|
||||||
|
"buildCommandArgs": "",
|
||||||
|
"ctestCommandArgs": "",
|
||||||
|
"inheritEnvironments": [ "linux_clang_x64" ],
|
||||||
|
"wslPath": "${defaultWSLPath}"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
11
CONTRIBUTING.md
Normal file
11
CONTRIBUTING.md
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
# Issues
|
||||||
|
Don’t forget to mention game version, which release or branch it came from.\
|
||||||
|
Source port issues are handled in their respective repositories.
|
||||||
|
|
||||||
|
# Pull request
|
||||||
|
No source ports in main repository.\
|
||||||
|
I have no way to test and maintain most of them.\
|
||||||
|
The best I can do is to add a link.
|
||||||
|
|
||||||
|
There is no guarantee that any particular PR will be accepted.\
|
||||||
|
If you are unsure, ask first, make PR second.
|
||||||
5084
Doc/.dat dump.txt
Normal file
5084
Doc/.dat dump.txt
Normal file
File diff suppressed because it is too large
Load Diff
133
Doc/.dat file format.txt
Normal file
133
Doc/.dat file format.txt
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
+-----------------------------------------------------+
|
||||||
|
| 3D Pinball for Windows ("PARTOUT") .DAT file format |
|
||||||
|
| AdrienTD |
|
||||||
|
+-----------------------------------------------------+
|
||||||
|
|
||||||
|
//-- Header --//
|
||||||
|
|
||||||
|
+0x00: File signature BYTE*21
|
||||||
|
+0x15: App name BYTE*50
|
||||||
|
+0x47: Description BYTE*100
|
||||||
|
+0xAB: File size DWORD
|
||||||
|
+0xAF: Number of files/groups WORD
|
||||||
|
+0xB1: Size of body DWORD
|
||||||
|
+0xB5: Unknown (0) WORD
|
||||||
|
|
||||||
|
In 3D Pinball, the file signature is "PARTOUT(4.0)RESOURCE". The rest is
|
||||||
|
filled with 0.
|
||||||
|
|
||||||
|
//-- Body --//
|
||||||
|
|
||||||
|
The body is constitued of groups. Every group begins with a byte that tells
|
||||||
|
how many entries are in the group.
|
||||||
|
|
||||||
|
Every entry begins with a byte that specifies the type of the entry. A DWORD that
|
||||||
|
tells the size of the coming data follows the byte. Then there's some data for the
|
||||||
|
entry. However, if the type is 0, the type byte is followed by a short value,
|
||||||
|
and that's all.
|
||||||
|
|
||||||
|
Existing types:
|
||||||
|
|
||||||
|
Type Meaning/comments
|
||||||
|
---------------------------------------------------
|
||||||
|
0 ?, does not have the 32bits size value, but a 16bits value (see above).
|
||||||
|
1 8 bpp bitmap
|
||||||
|
3 Group name
|
||||||
|
5 Palette (1 color is 1 DWORD, only present 1 time in PINBALL.DAT
|
||||||
|
with a data size of 1024 bytes for 256 colors. Some colors are 0
|
||||||
|
because their indexes are reserved by Windows.)
|
||||||
|
9 String (content)
|
||||||
|
10 Array of 16bits integer values
|
||||||
|
11 Array of 32bits floating point values (collision box, ...)
|
||||||
|
12 16 bpp bitmap (zMap)
|
||||||
|
|
||||||
|
|
||||||
|
//-- 8bpp bitmap data header --//
|
||||||
|
+0: Resolution BYTE 0=640x480, 1=800x600, 2=1024x768, -1=Load in all resolutions
|
||||||
|
+1: Width WORD
|
||||||
|
+3: Height WORD
|
||||||
|
+5: X position WORD
|
||||||
|
+7 Y position WORD
|
||||||
|
+9: Size of bitmap DWORD
|
||||||
|
+13: Flags BYTE bit0=Raw bmp align; bit1=DibBitmap, raw when 0; bit2=Spliced bitmap (aka skipline), combines bmp and zMap in RLE-like way
|
||||||
|
+14: Bitmap data BYTE*(DWORD@+9)
|
||||||
|
|
||||||
|
|
||||||
|
//-- 16bpp zMap data header --//
|
||||||
|
+0: Width WORD
|
||||||
|
+2: Height WORD
|
||||||
|
+4: Pitch/2 WORD
|
||||||
|
+6: Unknown (0) DWORD
|
||||||
|
+10: Unknown (0) WORD
|
||||||
|
+12: Unknown (80) WORD
|
||||||
|
+14: Bitmap data BYTE*(DWORD@+9)
|
||||||
|
|
||||||
|
//-- 16bpp zMap data header full tilt --//
|
||||||
|
+0: Resolution BYTE 0=640x480, 1=800x600, 2=1024x768, -1=Load in all resolutions
|
||||||
|
+1: Width WORD
|
||||||
|
+3: Height WORD
|
||||||
|
+5: Pitch/2 WORD
|
||||||
|
+7: Unknown (0) DWORD
|
||||||
|
+11: Unknown (0) WORD
|
||||||
|
+13: Unknown (80) WORD
|
||||||
|
+15: Bitmap data BYTE*(DWORD@+9)
|
||||||
|
|
||||||
|
|
||||||
|
//-- Pinball 3D remarkable groups --//
|
||||||
|
|
||||||
|
-- table_size
|
||||||
|
Entry type 10 contains 2 16-bit integers: first is width, second is height.
|
||||||
|
|
||||||
|
-- table_objects
|
||||||
|
In entry type 10, the first integer is unknown, but then comes a series of
|
||||||
|
16-bits integer pairs. Every pair represents an object of the table. The
|
||||||
|
first integer is the type of object, and the second is the number of
|
||||||
|
a group/resource in this data file.
|
||||||
|
|
||||||
|
Types of object (3D Pinball Space Cadet for Windows):
|
||||||
|
1000: ?
|
||||||
|
1001: Plunger
|
||||||
|
1002: Light (lite###)
|
||||||
|
1003: Left flipper
|
||||||
|
1004: Right flipper
|
||||||
|
1005: Bumper
|
||||||
|
1006: Yellow target
|
||||||
|
1007: Drain (no bmp)
|
||||||
|
1010: ? (no bmp)
|
||||||
|
1011: Bloc (dot between the flippers)
|
||||||
|
1012: kout (no bmp) (?)
|
||||||
|
1013: Gate
|
||||||
|
1014: Kicker
|
||||||
|
1015: Roll
|
||||||
|
1016: One way (no bmp) (?)
|
||||||
|
1017: Sink (no bmp) (?)
|
||||||
|
1018: Flag
|
||||||
|
1019: Red target
|
||||||
|
1020: Roll (the green circle that makes a weird sound when rolling on it)
|
||||||
|
1021: Ramp (no bmp)
|
||||||
|
1022: Ramp hole (no bmp)
|
||||||
|
1023: Demo (no bmp)
|
||||||
|
1024: Trip (no bmp)
|
||||||
|
1026: Lights (no bmp, list?)
|
||||||
|
1028: Bumpers list (one for attack, one for launch)
|
||||||
|
1029: kout (no bmp) (?, similar to 1012?)
|
||||||
|
1030: Fuel bargraph (list?)
|
||||||
|
1031: Sound
|
||||||
|
1033: Text box (score, ball counter, player number)
|
||||||
|
|
||||||
|
//-- Information --//
|
||||||
|
|
||||||
|
There's an article of the format at http://rewiki.regengedanken.de/.
|
||||||
|
I haven't read it completely, but some informations helped me a bit.
|
||||||
|
|
||||||
|
Another thing: There's nearly nothing about hacking the game on the web (even
|
||||||
|
though the Space Cadet game bundled with Windows is very popular).
|
||||||
|
What I've only seen when doing a (Google) web search is the 'hidden test'
|
||||||
|
cheat code. [as of 2017 or even less]
|
||||||
|
|
||||||
|
|
||||||
|
//-- Contact --//
|
||||||
|
|
||||||
|
Any questions? Contact me at [see my GitHub profile @AdrienTD for contacts] .
|
||||||
|
|
||||||
|
AdrienTD :)
|
||||||
12193
Doc/.pdb dump.txt
Normal file
12193
Doc/.pdb dump.txt
Normal file
File diff suppressed because it is too large
Load Diff
BIN
Doc/FuncStats.xlsx
Normal file
BIN
Doc/FuncStats.xlsx
Normal file
Binary file not shown.
21
LICENSE
Normal file
21
LICENSE
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2020-2021 Andrey Muzychenko
|
||||||
|
|
||||||
|
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.
|
||||||
25
Platform/Linux/BUILD.md
Normal file
25
Platform/Linux/BUILD.md
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
# Building
|
||||||
|
|
||||||
|
Install devel packages for `SDL2` and `SDL2_mixer`.\
|
||||||
|
|
||||||
|
Compile with CMake; tested with GCC 10, Clang 11.\
|
||||||
|
To cross-compile for Windows, install a 64-bit version of mingw and its `SDL2` and `SDL2_mixer` distributions, then use the `mingwcc.cmake` toolchain.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# On Debian and Ubuntu
|
||||||
|
sudo apt install cmake build-essential ninja-build libsdl2-dev libsdl2-mixer-dev libsdl2-mixer-2.0-0 libsdl2-2.0-0 fluidsynth
|
||||||
|
|
||||||
|
# On Fedora
|
||||||
|
sudo dnf install cmake ninja-build SDL2 SDL2-devel SDL2_mixer SDL2_mixer-devel fluidsynth fluidsynth-libs mscore-fonts g++
|
||||||
|
|
||||||
|
# Build
|
||||||
|
cmake -GNinja .
|
||||||
|
ninja
|
||||||
|
```
|
||||||
|
|
||||||
|
**If you can't hear the background music**: You need to export the following variable before launching the game like this:
|
||||||
|
`export SDL_SOUNDFONT=/path/to/your/soundfont`
|
||||||
|
|
||||||
|
Fluidsynth will install a default soundfont in the following path:
|
||||||
|
On Fedora: `/usr/share/soundfonts/FluidR3_GM.sf2`
|
||||||
|
On Ubuntu/Debian: `/usr/share/sounds/sf2/FluidR3_GM.sf2`
|
||||||
10
Platform/Linux/SpaceCadetPinball.desktop
Normal file
10
Platform/Linux/SpaceCadetPinball.desktop
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
[Desktop Entry]
|
||||||
|
Version=1.0
|
||||||
|
Name=Space Cadet Pinball
|
||||||
|
Comment=Decompilation of 3D Pinball for Windows - Space Cadet
|
||||||
|
Exec=SpaceCadetPinball
|
||||||
|
StartupNotify=true
|
||||||
|
Terminal=false
|
||||||
|
Icon=SpaceCadetPinball
|
||||||
|
Type=Application
|
||||||
|
Categories=Game;ArcadeGame;
|
||||||
95
Platform/Linux/SpaceCadetPinball.metainfo.xml
Normal file
95
Platform/Linux/SpaceCadetPinball.metainfo.xml
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<component type="desktop-application">
|
||||||
|
<id>com.github.k4zmu2a.spacecadetpinball</id>
|
||||||
|
<name>Space Cadet Pinball</name>
|
||||||
|
<display_name>Space Cadet Pinball</display_name>
|
||||||
|
<summary>Game engine for Space Cadet Pinball</summary>
|
||||||
|
<url type="homepage">https://github.com/k4zmu2a/SpaceCadetPinball</url>
|
||||||
|
<url type="bugtracker">https://github.com/k4zmu2a/SpaceCadetPinball/issues</url>
|
||||||
|
<description>
|
||||||
|
<p>
|
||||||
|
Reverse engineering of '3D Pinball for Windows - Space Cadet', a game bundled with Windows.
|
||||||
|
</p>
|
||||||
|
</description>
|
||||||
|
<categories>
|
||||||
|
<category>Game</category>
|
||||||
|
<category>ArcadeGame</category>
|
||||||
|
</categories>
|
||||||
|
<launchable type="desktop-id">SpaceCadetPinball.desktop</launchable>
|
||||||
|
<provides>
|
||||||
|
<binary>SpaceCadetPinball</binary>
|
||||||
|
</provides>
|
||||||
|
<screenshots>
|
||||||
|
<screenshot type="default">
|
||||||
|
<image>https://raw.githubusercontent.com/k4zmu2a/SpaceCadetPinball/master/Screenshots/screenshot-1.jpg</image>
|
||||||
|
</screenshot>
|
||||||
|
<screenshot>
|
||||||
|
<image>https://raw.githubusercontent.com/k4zmu2a/SpaceCadetPinball/master/Screenshots/screenshot-2.jpg</image>
|
||||||
|
</screenshot>
|
||||||
|
</screenshots>
|
||||||
|
<releases>
|
||||||
|
<release version="2.1.0" date="2023-10-16">
|
||||||
|
<description>
|
||||||
|
<p>Feature release focusing on FT compatibility.</p>
|
||||||
|
<p>Main highlights:</p>
|
||||||
|
<ul>
|
||||||
|
<li>FT: collision system changes, table control changes, multiball, three MIDI tracks.</li>
|
||||||
|
<li>Work in progress: localization support. Depends on external font selection for most languages.</li>
|
||||||
|
<li>HW accelerated ImGui renderer.</li>
|
||||||
|
<li>Experimental stereo sound support.</li>
|
||||||
|
<li>UX improvements.</li>
|
||||||
|
<li>Bug fixes.</li>
|
||||||
|
</ul>
|
||||||
|
</description>
|
||||||
|
</release>
|
||||||
|
<release version="2.0.1" date="2021-12-29">
|
||||||
|
<description>
|
||||||
|
<p>First bug fix release of cross-platform port.</p>
|
||||||
|
<p>Main highlights:</p>
|
||||||
|
<ul>
|
||||||
|
<li>Improved Linux support: install target, icons, desktop shortcut, alternative data paths.</li>
|
||||||
|
<li>Improved game controller support.</li>
|
||||||
|
<li>High precision sleep mode for ~0 frame time jitter.</li>
|
||||||
|
<li>Integer image scaling mode.</li>
|
||||||
|
<li>3DPB <-> FT data switch option, for easier data-set changes when both are available.</li>
|
||||||
|
<li>FT demo data support.</li>
|
||||||
|
<li>Bug fixes.</li>
|
||||||
|
</ul>
|
||||||
|
</description>
|
||||||
|
</release>
|
||||||
|
<release version="2.0.0" date="2021-10-30">
|
||||||
|
<description>
|
||||||
|
<p>First release of cross-platform port.</p>
|
||||||
|
<p>Main highlights:</p>
|
||||||
|
<ul>
|
||||||
|
<li>Windows dependencies removed.</li>
|
||||||
|
<li>Graphics and sounds rendered with SDL.</li>
|
||||||
|
<li>Dear ImGui GUI.</li>
|
||||||
|
<li>CMake build script, compiles on a lot of platforms.</li>
|
||||||
|
<li>A number of 3rd party source ports.</li>
|
||||||
|
</ul>
|
||||||
|
<p>All features of v1.x are present, except for text translations.</p>
|
||||||
|
<p>
|
||||||
|
Development and releases of v1.x will continue, don’t confuse the two.
|
||||||
|
All source ports are technically v2.0RC, not v1.x.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>There are no Linux or macOS binaries attached, compile them yourself.</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
This release was made better with the help of contributors.
|
||||||
|
I thank them for their interest in the project.
|
||||||
|
</p>
|
||||||
|
</description>
|
||||||
|
</release>
|
||||||
|
</releases>
|
||||||
|
<metadata_license>CC0-1.0</metadata_license>
|
||||||
|
<developer_name>Andrey Muzychenko</developer_name>
|
||||||
|
<project_license>MIT</project_license>
|
||||||
|
<keywords>
|
||||||
|
<keyword>pinball</keyword>
|
||||||
|
<keyword>space</keyword>
|
||||||
|
<keyword>cadet</keyword>
|
||||||
|
</keywords>
|
||||||
|
<content_rating type="oars-1.1" />
|
||||||
|
</component>
|
||||||
36
Platform/macOS/Info.plist
Normal file
36
Platform/macOS/Info.plist
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
|
<string>English</string>
|
||||||
|
<key>CFBundleExecutable</key>
|
||||||
|
<string>SpaceCadetPinball</string>
|
||||||
|
<key>CFBundleIconFile</key>
|
||||||
|
<string>SpaceCadetPinball.icns</string>
|
||||||
|
<key>CFBundleIdentifier</key>
|
||||||
|
<string>com.github.k4zmu2a.spacecadetpinball</string>
|
||||||
|
<key>CFBundleInfoDictionaryVersion</key>
|
||||||
|
<string>6.0</string>
|
||||||
|
<key>CFBundleLongVersionString</key>
|
||||||
|
<string></string>
|
||||||
|
<key>CFBundleName</key>
|
||||||
|
<string>SpaceCadetPinball</string>
|
||||||
|
<key>CFBundlePackageType</key>
|
||||||
|
<string>APPL</string>
|
||||||
|
<key>CFBundleShortVersionString</key>
|
||||||
|
<string>CHANGEME_SW_VERSION</string>
|
||||||
|
<key>CFBundleSignature</key>
|
||||||
|
<string>????</string>
|
||||||
|
<key>CFBundleSupportedPlatforms</key>
|
||||||
|
<array>
|
||||||
|
<string>MacOSX</string>
|
||||||
|
</array>
|
||||||
|
<key>CSResourcesFileMapped</key>
|
||||||
|
<true/>
|
||||||
|
<key>LSMinimumSystemVersion</key>
|
||||||
|
<string>10.11</string>
|
||||||
|
<key>NSHighResolutionCapable</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
BIN
Platform/macOS/SpaceCadetPinball.icns
Normal file
BIN
Platform/macOS/SpaceCadetPinball.icns
Normal file
Binary file not shown.
123
README.md
Normal file
123
README.md
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
<!-- markdownlint-disable-file MD033 -->
|
||||||
|
|
||||||
|
# SpaceCadetPinball
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
Reverse engineering of `3D Pinball for Windows - Space Cadet`, a game bundled with Windows.
|
||||||
|
|
||||||
|
## How to play
|
||||||
|
|
||||||
|
Place compiled executable into a folder containing original game resources (not included).\
|
||||||
|
Supports data files from Windows and Full Tilt versions of the game.
|
||||||
|
|
||||||
|
## Known source ports
|
||||||
|
|
||||||
|
| Platform | Author | URL |
|
||||||
|
| ------------------ | --------------- | ---------------------------------------------------------------------------------------------------------- |
|
||||||
|
| PS Vita | Axiom | <https://github.com/suicvne/SpaceCadetPinball_Vita> |
|
||||||
|
| Emscripten | alula | <https://github.com/alula/SpaceCadetPinball> <br> Play online: <https://alula.github.io/SpaceCadetPinball> |
|
||||||
|
| Nintendo Switch | averne | <https://github.com/averne/SpaceCadetPinball-NX> |
|
||||||
|
| webOS TV | mariotaku | <https://github.com/webosbrew/SpaceCadetPinball> |
|
||||||
|
| Android (WIP) | Iscle | https://github.com/Iscle/SpaceCadetPinball |
|
||||||
|
| Nintendo Wii | MaikelChan | https://github.com/MaikelChan/SpaceCadetPinball |
|
||||||
|
| Nintendo 3DS | MaikelChan | https://github.com/MaikelChan/SpaceCadetPinball/tree/3ds |
|
||||||
|
| Nintendo DS | Headshotnoby | https://github.com/headshot2017/3dpinball-nds |
|
||||||
|
| Nintendo Wii U | IntriguingTiles | https://github.com/IntriguingTiles/SpaceCadetPinball-WiiU |
|
||||||
|
| PlayStation 2 | Headshotnoby | https://github.com/headshot2017/3dpinball-ps2 |
|
||||||
|
| Sega Dreamcast | Headshotnoby | https://github.com/headshot2017/3dpinball-dc |
|
||||||
|
| MorphOS | BeWorld | https://www.morphos-storage.net/?id=1688897 |
|
||||||
|
| AmigaOS 4 | rjd324 | http://aminet.net/package/game/actio/spacecadetpinball-aos4 |
|
||||||
|
| Android (WIP) | fexed | https://github.com/fexed/Pinball-on-Android |
|
||||||
|
|
||||||
|
Platforms covered by this project: desktop Windows, Linux and macOS.
|
||||||
|
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
|
||||||
|
## Source
|
||||||
|
|
||||||
|
* `pinball.exe` from `Windows XP` (SHA-1 `2A5B525E0F631BB6107639E2A69DF15986FB0D05`) and its public PDB
|
||||||
|
* `CADET.EXE` 32bit version from `Full Tilt! Pinball` (SHA-1 `3F7B5699074B83FD713657CD94671F2156DBEDC4`)
|
||||||
|
|
||||||
|
## Tools used
|
||||||
|
|
||||||
|
`Ghidra`, `Ida`, `Visual Studio`
|
||||||
|
|
||||||
|
## What was done
|
||||||
|
|
||||||
|
* All structures were populated, globals and locals named.
|
||||||
|
* All subs were decompiled, C pseudo code was converted to compilable C++. Loose (namespace?) subs were assigned to classes.
|
||||||
|
|
||||||
|
## Compiling
|
||||||
|
|
||||||
|
Project uses `C++11` and depends on `SDL2` libs.
|
||||||
|
|
||||||
|
### On Windows
|
||||||
|
|
||||||
|
Download and unpack devel packages for `SDL2` and `SDL2_mixer`.\
|
||||||
|
Set paths to them in `CMakeLists.txt`, see suggested placement in `/Libs`.\
|
||||||
|
Compile with Visual Studio; tested with 2019.
|
||||||
|
|
||||||
|
### On Linux
|
||||||
|
|
||||||
|
Install devel packages for `SDL2` and `SDL2_mixer`.\
|
||||||
|
Compile with CMake; tested with GCC 10, Clang 11.\
|
||||||
|
To cross-compile for Windows, install a 64-bit version of mingw and its `SDL2` and `SDL2_mixer` distributions, then use the `mingwcc.cmake` toolchain.
|
||||||
|
|
||||||
|
[](https://repology.org/project/spacecadetpinball/versions)
|
||||||
|
|
||||||
|
Some distributions provide a package in their repository. You can use those for easier dependency management and updates.
|
||||||
|
|
||||||
|
This project is available as Flatpak on [Flathub](https://flathub.org/apps/details/com.github.k4zmu2a.spacecadetpinball).
|
||||||
|
|
||||||
|
### On macOS
|
||||||
|
|
||||||
|
Install XCode (or at least Xcode Command Line Tools with `xcode-select --install`) and CMake.
|
||||||
|
|
||||||
|
**HomeBrew**
|
||||||
|
|
||||||
|
You can easily install the build artifact by using `brew`.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
brew tap draftbrew/tap
|
||||||
|
brew install --no-quarantine space-cadet-pinball
|
||||||
|
```
|
||||||
|
|
||||||
|
Be aware that the flag `--no-quarantime` will disable macOS's Gatekeeper during installation.
|
||||||
|
|
||||||
|
**Manual compilation:**
|
||||||
|
|
||||||
|
* **Homebrew**: Install the `SDL2`, `SDL2_mixer` homebrew packages.
|
||||||
|
* **MacPorts**: Install the `libSDL2`, `libSDL2_mixer` macports packages.
|
||||||
|
|
||||||
|
Compile with CMake. Ensure that `CMAKE_OSX_ARCHITECTURES` variable is set for either `x86_64` Apple Intel or `arm64` for Apple Silicon.
|
||||||
|
|
||||||
|
Tested with: macOS Big Sur (Intel) with Xcode 13 & macOS Montery Beta (Apple Silicon) with Xcode 13.
|
||||||
|
|
||||||
|
**Automated compilation:**
|
||||||
|
|
||||||
|
Run the `build-mac-app.sh` script from the root of the repository. The app will be available in a DMG file named `SpaceCadetPinball-<version>-mac.dmg`.
|
||||||
|
|
||||||
|
Tested with: macOS Ventura (Apple Silicon) with Xcode Command Line Tools 14 & macOS Big Sur on GitHub Runner (Intel) with XCode 13.
|
||||||
|
|
||||||
|
## Plans
|
||||||
|
|
||||||
|
* ~~Decompile original game~~
|
||||||
|
* ~~Resizable window, scaled graphics~~
|
||||||
|
* ~~Loader for high-res sprites from CADET.DAT~~
|
||||||
|
* ~~Cross-platform port using SDL2, SDL2_mixer, ImGui~~
|
||||||
|
* Full Tilt Cadet features
|
||||||
|
* Localization support
|
||||||
|
* Maybe: Support for the other two tables - Dragon and Pirate
|
||||||
|
* Maybe: Game data editor
|
||||||
|
|
||||||
|
## On 64-bit bug that killed the game
|
||||||
|
|
||||||
|
I did not find it, decompiled game worked in x64 mode on the first try.\
|
||||||
|
It was either lost in decompilation or introduced in x64 port/not present in x86 build.\
|
||||||
|
Based on public description of the bug (no ball collision), I guess that the bug was in `TEdgeManager::TestGridBox`
|
||||||
358
SpaceCadetPinball/DebugOverlay.cpp
Normal file
358
SpaceCadetPinball/DebugOverlay.cpp
Normal file
@ -0,0 +1,358 @@
|
|||||||
|
#include "pch.h"
|
||||||
|
#include "DebugOverlay.h"
|
||||||
|
|
||||||
|
#include "loader.h"
|
||||||
|
#include "maths.h"
|
||||||
|
#include "proj.h"
|
||||||
|
#include "winmain.h"
|
||||||
|
#include "TFlipperEdge.h"
|
||||||
|
#include "TFlipper.h"
|
||||||
|
#include "pb.h"
|
||||||
|
#include "TLine.h"
|
||||||
|
#include "TCircle.h"
|
||||||
|
#include "TPinballTable.h"
|
||||||
|
#include "TEdgeBox.h"
|
||||||
|
#include "TTableLayer.h"
|
||||||
|
#include "TBall.h"
|
||||||
|
#include "render.h"
|
||||||
|
#include "options.h"
|
||||||
|
#include "Sound.h"
|
||||||
|
|
||||||
|
|
||||||
|
gdrv_bitmap8* DebugOverlay::dbScreen = nullptr;
|
||||||
|
|
||||||
|
static int SDL_RenderDrawCircle(SDL_Renderer* renderer, int x, int y, int radius)
|
||||||
|
{
|
||||||
|
SDL_Point points[256];
|
||||||
|
int pointCount = 0;
|
||||||
|
int offsetx, offsety, d;
|
||||||
|
int status;
|
||||||
|
|
||||||
|
offsetx = 0;
|
||||||
|
offsety = radius;
|
||||||
|
d = radius - 1;
|
||||||
|
status = 0;
|
||||||
|
|
||||||
|
while (offsety >= offsetx)
|
||||||
|
{
|
||||||
|
if (pointCount + 8 > 256)
|
||||||
|
{
|
||||||
|
status = SDL_RenderDrawPoints(renderer, points, pointCount);
|
||||||
|
pointCount = 0;
|
||||||
|
|
||||||
|
if (status < 0) {
|
||||||
|
status = -1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
points[pointCount++] = { x + offsetx, y + offsety };
|
||||||
|
points[pointCount++] = { x + offsety, y + offsetx };
|
||||||
|
points[pointCount++] = { x - offsetx, y + offsety };
|
||||||
|
points[pointCount++] = { x - offsety, y + offsetx };
|
||||||
|
points[pointCount++] = { x + offsetx, y - offsety };
|
||||||
|
points[pointCount++] = { x + offsety, y - offsetx };
|
||||||
|
points[pointCount++] = { x - offsetx, y - offsety };
|
||||||
|
points[pointCount++] = { x - offsety, y - offsetx };
|
||||||
|
|
||||||
|
if (d >= 2 * offsetx) {
|
||||||
|
d -= 2 * offsetx + 1;
|
||||||
|
offsetx += 1;
|
||||||
|
}
|
||||||
|
else if (d < 2 * (radius - offsety)) {
|
||||||
|
d += 2 * offsety - 1;
|
||||||
|
offsety -= 1;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
d += 2 * (offsety - offsetx - 1);
|
||||||
|
offsety -= 1;
|
||||||
|
offsetx += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pointCount > 0)
|
||||||
|
status = SDL_RenderDrawPoints(renderer, points, pointCount);
|
||||||
|
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DebugOverlay::UnInit()
|
||||||
|
{
|
||||||
|
delete dbScreen;
|
||||||
|
dbScreen = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DebugOverlay::DrawOverlay()
|
||||||
|
{
|
||||||
|
if (dbScreen == nullptr)
|
||||||
|
{
|
||||||
|
dbScreen = new gdrv_bitmap8(render::vscreen->Width, render::vscreen->Height, false, false);
|
||||||
|
dbScreen->CreateTexture("nearest", SDL_TEXTUREACCESS_TARGET);
|
||||||
|
SDL_SetTextureBlendMode(dbScreen->Texture, SDL_BLENDMODE_BLEND);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup overlay rendering
|
||||||
|
Uint8 initialR, initialG, initialB, initialA;
|
||||||
|
auto initialRenderTarget = SDL_GetRenderTarget(winmain::Renderer);
|
||||||
|
SDL_GetRenderDrawColor(winmain::Renderer, &initialR, &initialG, &initialB, &initialA);
|
||||||
|
SDL_SetRenderTarget(winmain::Renderer, dbScreen->Texture);
|
||||||
|
SDL_SetRenderDrawColor(winmain::Renderer, 0, 0, 0, 0);
|
||||||
|
SDL_RenderClear(winmain::Renderer);
|
||||||
|
|
||||||
|
// Draw EdgeManager box grid
|
||||||
|
if (options::Options.DebugOverlayGrid)
|
||||||
|
DrawBoxGrid();
|
||||||
|
|
||||||
|
// Draw bounding boxes around sprites
|
||||||
|
if (options::Options.DebugOverlaySprites)
|
||||||
|
DrawAllSprites();
|
||||||
|
|
||||||
|
// Draw all edges registered in TCollisionComponent.EdgeList + flippers
|
||||||
|
if (options::Options.DebugOverlayAllEdges)
|
||||||
|
DrawAllEdges();
|
||||||
|
|
||||||
|
// Draw ball collision info
|
||||||
|
if (options::Options.DebugOverlayBallPosition || options::Options.DebugOverlayBallEdges)
|
||||||
|
DrawBallInfo();
|
||||||
|
|
||||||
|
// Draw positions associated with currently playing sound channels
|
||||||
|
if (options::Options.DebugOverlaySounds)
|
||||||
|
DrawSoundPositions();
|
||||||
|
|
||||||
|
// Draw ball depth cutoff steps that determine sprite size.
|
||||||
|
if (options::Options.DebugOverlayBallDepthGrid)
|
||||||
|
DrawBallDepthSteps();
|
||||||
|
|
||||||
|
// Draw AABB of collision components
|
||||||
|
if (options::Options.DebugOverlayAabb)
|
||||||
|
DrawComponentAabb();
|
||||||
|
|
||||||
|
// Restore render target
|
||||||
|
SDL_SetRenderTarget(winmain::Renderer, initialRenderTarget);
|
||||||
|
SDL_SetRenderDrawColor(winmain::Renderer,
|
||||||
|
initialR, initialG, initialB, initialA);
|
||||||
|
|
||||||
|
// Copy overlay with alpha blending
|
||||||
|
SDL_BlendMode blendMode;
|
||||||
|
SDL_GetRenderDrawBlendMode(winmain::Renderer, &blendMode);
|
||||||
|
SDL_SetRenderDrawBlendMode(winmain::Renderer, SDL_BLENDMODE_BLEND);
|
||||||
|
SDL_RenderCopy(winmain::Renderer, dbScreen->Texture, nullptr, &render::DestinationRect);
|
||||||
|
SDL_SetRenderDrawBlendMode(winmain::Renderer, blendMode);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DebugOverlay::DrawBoxGrid()
|
||||||
|
{
|
||||||
|
auto& edgeMan = *TTableLayer::edge_manager;
|
||||||
|
|
||||||
|
SDL_SetRenderDrawColor(winmain::Renderer, 0, 255, 0, 255);
|
||||||
|
for (int x = 0; x <= edgeMan.MaxBoxX; x++)
|
||||||
|
{
|
||||||
|
vector2 boxPt{ x * edgeMan.AdvanceX + edgeMan.MinX , edgeMan.MinY };
|
||||||
|
auto pt1 = proj::xform_to_2d(boxPt);
|
||||||
|
boxPt.Y = edgeMan.MaxBoxY * edgeMan.AdvanceY + edgeMan.MinY;
|
||||||
|
auto pt2 = proj::xform_to_2d(boxPt);
|
||||||
|
|
||||||
|
SDL_RenderDrawLine(winmain::Renderer, pt1.X, pt1.Y, pt2.X, pt2.Y);
|
||||||
|
}
|
||||||
|
for (int y = 0; y <= edgeMan.MaxBoxY; y++)
|
||||||
|
{
|
||||||
|
vector2 boxPt{ edgeMan.MinX, y * edgeMan.AdvanceY + edgeMan.MinY };
|
||||||
|
auto pt1 = proj::xform_to_2d(boxPt);
|
||||||
|
boxPt.X = edgeMan.MaxBoxX * edgeMan.AdvanceX + edgeMan.MinX;
|
||||||
|
auto pt2 = proj::xform_to_2d(boxPt);
|
||||||
|
|
||||||
|
SDL_RenderDrawLine(winmain::Renderer, pt1.X, pt1.Y, pt2.X, pt2.Y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DebugOverlay::DrawAllEdges()
|
||||||
|
{
|
||||||
|
SDL_SetRenderDrawColor(winmain::Renderer, 0, 200, 200, 255);
|
||||||
|
for (auto cmp : pb::MainTable->ComponentList)
|
||||||
|
{
|
||||||
|
auto collCmp = dynamic_cast<TCollisionComponent*>(cmp);
|
||||||
|
if (collCmp)
|
||||||
|
{
|
||||||
|
for (auto edge : collCmp->EdgeList)
|
||||||
|
{
|
||||||
|
DrawEdge(edge);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
auto flip = dynamic_cast<TFlipper*>(cmp);
|
||||||
|
if (flip)
|
||||||
|
{
|
||||||
|
DrawEdge(flip->FlipperEdge);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DebugOverlay::DrawBallInfo()
|
||||||
|
{
|
||||||
|
auto& edgeMan = *TTableLayer::edge_manager;
|
||||||
|
for (auto ball : pb::MainTable->BallList)
|
||||||
|
{
|
||||||
|
if (ball->ActiveFlag)
|
||||||
|
{
|
||||||
|
vector2 ballPosition = { ball->Position.X, ball->Position.Y };
|
||||||
|
|
||||||
|
if (options::Options.DebugOverlayBallEdges)
|
||||||
|
{
|
||||||
|
SDL_SetRenderDrawColor(winmain::Renderer, 255, 0, 0, 255);
|
||||||
|
auto x = edgeMan.box_x(ballPosition.X), y = edgeMan.box_y(ballPosition.Y);
|
||||||
|
auto& box = edgeMan.BoxArray[x + y * edgeMan.MaxBoxX];
|
||||||
|
for (auto edge : box.EdgeList)
|
||||||
|
{
|
||||||
|
DrawEdge(edge);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options::Options.DebugOverlayBallPosition)
|
||||||
|
{
|
||||||
|
SDL_SetRenderDrawColor(winmain::Renderer, 0, 0, 255, 255);
|
||||||
|
|
||||||
|
auto pt1 = proj::xform_to_2d(ballPosition);
|
||||||
|
vector2 radVec1 = { 0, ballPosition.Y }, radVec2 = { ball->Radius, ballPosition.Y };
|
||||||
|
auto radVec1I = proj::xform_to_2d(radVec1), radVec2I = proj::xform_to_2d(radVec2);
|
||||||
|
auto radI = std::sqrt(maths::magnitudeSq(vector2i{ radVec1I.X - radVec2I.X ,radVec1I.Y - radVec2I.Y }));
|
||||||
|
SDL_RenderDrawCircle(winmain::Renderer, pt1.X, pt1.Y, static_cast<int>(std::round(radI)));
|
||||||
|
|
||||||
|
auto nextPos = ballPosition;
|
||||||
|
maths::vector_add(nextPos, maths::vector_mul(ball->Direction, ball->Speed / 10.0f));
|
||||||
|
auto pt2 = proj::xform_to_2d(nextPos);
|
||||||
|
SDL_RenderDrawLine(winmain::Renderer, pt1.X, pt1.Y, pt2.X, pt2.Y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DebugOverlay::DrawAllSprites()
|
||||||
|
{
|
||||||
|
SDL_SetRenderDrawColor(winmain::Renderer, 200, 200, 0, 255);
|
||||||
|
for (auto cmp : pb::MainTable->ComponentList)
|
||||||
|
{
|
||||||
|
if (cmp->RenderSprite)
|
||||||
|
{
|
||||||
|
auto& bmpR = cmp->RenderSprite->BmpRect;
|
||||||
|
if (bmpR.Width != 0 && bmpR.Height != 0)
|
||||||
|
{
|
||||||
|
SDL_Rect rect{ bmpR.XPosition, bmpR.YPosition, bmpR.Width, bmpR.Height };
|
||||||
|
SDL_RenderDrawRect(winmain::Renderer, &rect);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DebugOverlay::DrawSoundPositions()
|
||||||
|
{
|
||||||
|
auto& edgeMan = *TTableLayer::edge_manager;
|
||||||
|
SDL_SetRenderDrawColor(winmain::Renderer, 200, 0, 200, 255);
|
||||||
|
|
||||||
|
for (auto& posNorm : Sound::Channels)
|
||||||
|
{
|
||||||
|
auto pos3D = edgeMan.DeNormalizeBox(posNorm.Position);
|
||||||
|
auto pos2D = proj::xform_to_2d(pos3D);
|
||||||
|
SDL_RenderDrawCircle(winmain::Renderer, pos2D.X, pos2D.Y, 7);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DebugOverlay::DrawBallDepthSteps()
|
||||||
|
{
|
||||||
|
auto& edgeMan = *TTableLayer::edge_manager;
|
||||||
|
SDL_SetRenderDrawColor(winmain::Renderer, 200, 100, 0, 255);
|
||||||
|
|
||||||
|
for (auto ball : pb::MainTable->BallList)
|
||||||
|
{
|
||||||
|
auto visualCount = loader::query_visual_states(ball->GroupIndex);
|
||||||
|
for (auto index = 0; index < visualCount; ++index)
|
||||||
|
{
|
||||||
|
auto depthPt = reinterpret_cast<vector3*>(loader::query_float_attribute(ball->GroupIndex, index, 501));
|
||||||
|
auto pt = proj::xform_to_2d(*depthPt);
|
||||||
|
|
||||||
|
// Snap X coordinate to edge box sides
|
||||||
|
auto x1 = proj::xform_to_2d(vector2{edgeMan.MinX, depthPt->Y}).X;
|
||||||
|
auto x2 = proj::xform_to_2d(vector2{edgeMan.MaxBoxX * edgeMan.AdvanceX + edgeMan.MinX, depthPt->Y}).X;
|
||||||
|
auto ff = proj::xform_to_2d(vector2{ edgeMan.MaxBoxX * edgeMan.AdvanceX + edgeMan.MinX, depthPt->Y });
|
||||||
|
SDL_RenderDrawLine(winmain::Renderer, x1, pt.Y, x2, pt.Y);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DebugOverlay::DrawComponentAabb()
|
||||||
|
{
|
||||||
|
SDL_SetRenderDrawColor(winmain::Renderer, 0, 50, 200, 255);
|
||||||
|
for (auto cmp : pb::MainTable->ComponentList)
|
||||||
|
{
|
||||||
|
auto collCmp = dynamic_cast<TCollisionComponent*>(cmp);
|
||||||
|
if (collCmp)
|
||||||
|
{
|
||||||
|
const auto& aabb = collCmp->AABB;
|
||||||
|
auto pt1 = proj::xform_to_2d(vector2{ aabb.XMax, aabb.YMax });
|
||||||
|
auto pt2 = proj::xform_to_2d(vector2{ aabb.XMin, aabb.YMin });
|
||||||
|
SDL_Rect rect{ pt2.X,pt2.Y, pt1.X - pt2.X , pt1.Y - pt2.Y };
|
||||||
|
SDL_RenderDrawRect(winmain::Renderer, &rect);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DebugOverlay::DrawCicleType(circle_type& circle)
|
||||||
|
{
|
||||||
|
vector2 linePt{ circle.Center.X + sqrt(circle.RadiusSq), circle.Center.Y };
|
||||||
|
auto pt1 = proj::xform_to_2d(circle.Center);
|
||||||
|
auto pt2 = proj::xform_to_2d(linePt);
|
||||||
|
auto radius = abs(pt2.X - pt1.X);
|
||||||
|
|
||||||
|
SDL_RenderDrawCircle(winmain::Renderer, pt1.X, pt1.Y, radius);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DebugOverlay::DrawLineType(line_type& line)
|
||||||
|
{
|
||||||
|
auto pt1 = proj::xform_to_2d(line.Origin);
|
||||||
|
auto pt2 = proj::xform_to_2d(line.End);
|
||||||
|
|
||||||
|
SDL_RenderDrawLine(winmain::Renderer, pt1.X, pt1.Y, pt2.X, pt2.Y);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DebugOverlay::DrawEdge(TEdgeSegment* edge)
|
||||||
|
{
|
||||||
|
if (options::Options.DebugOverlayCollisionMask)
|
||||||
|
{
|
||||||
|
TBall* refBall = nullptr;
|
||||||
|
for (auto ball : pb::MainTable->BallList)
|
||||||
|
{
|
||||||
|
if (ball->ActiveFlag)
|
||||||
|
{
|
||||||
|
refBall = ball;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (refBall != nullptr && (refBall->CollisionMask & edge->CollisionGroup) == 0)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto line = dynamic_cast<TLine*>(edge);
|
||||||
|
if (line)
|
||||||
|
{
|
||||||
|
DrawLineType(line->Line);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto circle = dynamic_cast<TCircle*>(edge);
|
||||||
|
if (circle)
|
||||||
|
{
|
||||||
|
DrawCicleType(circle->Circle);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto flip = dynamic_cast<TFlipperEdge*>(edge);
|
||||||
|
if (flip)
|
||||||
|
{
|
||||||
|
if (flip->ControlPointDirtyFlag)
|
||||||
|
flip->set_control_points(flip->CurrentAngle);
|
||||||
|
|
||||||
|
DrawLineType(flip->LineA);
|
||||||
|
DrawLineType(flip->LineB);
|
||||||
|
DrawCicleType(flip->circlebase);
|
||||||
|
DrawCicleType(flip->circleT1);
|
||||||
|
}
|
||||||
|
}
|
||||||
26
SpaceCadetPinball/DebugOverlay.h
Normal file
26
SpaceCadetPinball/DebugOverlay.h
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
struct gdrv_bitmap8;
|
||||||
|
struct circle_type;
|
||||||
|
struct line_type;
|
||||||
|
class TEdgeSegment;
|
||||||
|
|
||||||
|
class DebugOverlay
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static void UnInit();
|
||||||
|
static void DrawOverlay();
|
||||||
|
private:
|
||||||
|
static gdrv_bitmap8* dbScreen;
|
||||||
|
|
||||||
|
static void DrawCicleType(circle_type& circle);
|
||||||
|
static void DrawLineType(line_type& line);
|
||||||
|
static void DrawEdge(TEdgeSegment* edge);
|
||||||
|
static void DrawBoxGrid();
|
||||||
|
static void DrawAllEdges();
|
||||||
|
static void DrawBallInfo();
|
||||||
|
static void DrawAllSprites();
|
||||||
|
static void DrawSoundPositions();
|
||||||
|
static void DrawBallDepthSteps();
|
||||||
|
static void DrawComponentAabb();
|
||||||
|
};
|
||||||
1380
SpaceCadetPinball/EmbeddedData.cpp
Normal file
1380
SpaceCadetPinball/EmbeddedData.cpp
Normal file
File diff suppressed because it is too large
Load Diff
10
SpaceCadetPinball/EmbeddedData.h
Normal file
10
SpaceCadetPinball/EmbeddedData.h
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
|
||||||
|
class EmbeddedData
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static const char PB_MSGFT_bin_compressed_data_base85[5380 + 1];
|
||||||
|
static const unsigned int SDL_GameControllerDB_compressed_data[63392 / 4];
|
||||||
|
static const unsigned int SDL_GameControllerDB_compressed_size;
|
||||||
|
};
|
||||||
361
SpaceCadetPinball/GroupData.cpp
Normal file
361
SpaceCadetPinball/GroupData.cpp
Normal file
@ -0,0 +1,361 @@
|
|||||||
|
#include "pch.h"
|
||||||
|
|
||||||
|
#include "GroupData.h"
|
||||||
|
|
||||||
|
#include "EmbeddedData.h"
|
||||||
|
#include "fullscrn.h"
|
||||||
|
#include "gdrv.h"
|
||||||
|
#include "pb.h"
|
||||||
|
#include "zdrv.h"
|
||||||
|
|
||||||
|
|
||||||
|
EntryData::~EntryData()
|
||||||
|
{
|
||||||
|
if (Buffer)
|
||||||
|
{
|
||||||
|
if (EntryType == FieldTypes::Bitmap8bit)
|
||||||
|
{
|
||||||
|
delete reinterpret_cast<gdrv_bitmap8*>(Buffer);
|
||||||
|
}
|
||||||
|
else if (EntryType == FieldTypes::Bitmap16bit)
|
||||||
|
{
|
||||||
|
delete reinterpret_cast<zmap_header_type*>(Buffer);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
delete[] Buffer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
GroupData::GroupData(int groupId)
|
||||||
|
{
|
||||||
|
GroupId = groupId;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GroupData::AddEntry(EntryData* entry)
|
||||||
|
{
|
||||||
|
auto addEntry = true;
|
||||||
|
switch (entry->EntryType)
|
||||||
|
{
|
||||||
|
case FieldTypes::GroupName:
|
||||||
|
GroupName = entry->Buffer;
|
||||||
|
break;
|
||||||
|
case FieldTypes::Bitmap8bit:
|
||||||
|
{
|
||||||
|
auto srcBmp = reinterpret_cast<gdrv_bitmap8*>(entry->Buffer);
|
||||||
|
if (srcBmp->BitmapType == BitmapTypes::Spliced)
|
||||||
|
{
|
||||||
|
// Get rid of spliced bitmap early on, to simplify render pipeline
|
||||||
|
auto bmp = new gdrv_bitmap8(srcBmp->Width, srcBmp->Height, true);
|
||||||
|
auto zMap = new zmap_header_type(srcBmp->Width, srcBmp->Height, srcBmp->Width);
|
||||||
|
SplitSplicedBitmap(*srcBmp, *bmp, *zMap);
|
||||||
|
|
||||||
|
NeedsSort = true;
|
||||||
|
addEntry = false;
|
||||||
|
AddEntry(new EntryData(FieldTypes::Bitmap8bit, reinterpret_cast<char*>(bmp)));
|
||||||
|
AddEntry(new EntryData(FieldTypes::Bitmap16bit, reinterpret_cast<char*>(zMap)));
|
||||||
|
delete entry;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SetBitmap(srcBmp);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case FieldTypes::Bitmap16bit:
|
||||||
|
{
|
||||||
|
SetZMap(reinterpret_cast<zmap_header_type*>(entry->Buffer));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (addEntry)
|
||||||
|
Entries.push_back(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GroupData::FinalizeGroup()
|
||||||
|
{
|
||||||
|
if (NeedsSort)
|
||||||
|
{
|
||||||
|
// Entries within a group are sorted by EntryType, in ascending order.
|
||||||
|
// Dat files follow this rule, zMaps inserted in the middle break it.
|
||||||
|
NeedsSort = false;
|
||||||
|
std::sort(Entries.begin(), Entries.end(), [](const EntryData* lhs, const EntryData* rhs)
|
||||||
|
{
|
||||||
|
return lhs->EntryType < rhs->EntryType;
|
||||||
|
});
|
||||||
|
Entries.shrink_to_fit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
gdrv_bitmap8* GroupData::GetBitmap(int resolution) const
|
||||||
|
{
|
||||||
|
return Bitmaps[resolution];
|
||||||
|
}
|
||||||
|
|
||||||
|
zmap_header_type* GroupData::GetZMap(int resolution) const
|
||||||
|
{
|
||||||
|
return ZMaps[resolution];
|
||||||
|
}
|
||||||
|
|
||||||
|
void GroupData::SplitSplicedBitmap(const gdrv_bitmap8& srcBmp, gdrv_bitmap8& bmp, zmap_header_type& zMap)
|
||||||
|
{
|
||||||
|
assertm(srcBmp.BitmapType == BitmapTypes::Spliced, "GroupData: wrong bitmap type");
|
||||||
|
|
||||||
|
std::memset(bmp.IndexedBmpPtr, 0xff, bmp.Stride * bmp.Height);
|
||||||
|
bmp.XPosition = srcBmp.XPosition;
|
||||||
|
bmp.YPosition = srcBmp.YPosition;
|
||||||
|
bmp.Resolution = srcBmp.Resolution;
|
||||||
|
|
||||||
|
zdrv::fill(&zMap, zMap.Width, zMap.Height, 0, 0, 0xFFFF);
|
||||||
|
zMap.Resolution = srcBmp.Resolution;
|
||||||
|
|
||||||
|
auto tableWidth = fullscrn::resolution_array[srcBmp.Resolution].TableWidth;
|
||||||
|
auto src = reinterpret_cast<uint16_t*>(srcBmp.IndexedBmpPtr);
|
||||||
|
auto srcChar = reinterpret_cast<char**>(&src);
|
||||||
|
for (int dstInd = 0;;)
|
||||||
|
{
|
||||||
|
auto stride = static_cast<int16_t>(*src++);
|
||||||
|
if (stride < 0)
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Stride is in terms of dst stride, hardcoded to match vScreen width in current resolution
|
||||||
|
if (stride > bmp.Width)
|
||||||
|
{
|
||||||
|
stride += bmp.Width - tableWidth;
|
||||||
|
assertm(stride >= 0, "Spliced bitmap: negative computed stride");
|
||||||
|
}
|
||||||
|
|
||||||
|
dstInd += stride;
|
||||||
|
for (auto count = *src++; count; count--)
|
||||||
|
{
|
||||||
|
auto depth = *src++;
|
||||||
|
bmp.IndexedBmpPtr[dstInd] = **srcChar;
|
||||||
|
zMap.ZPtr1[dstInd] = depth;
|
||||||
|
|
||||||
|
(*srcChar)++;
|
||||||
|
dstInd++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GroupData::SetBitmap(gdrv_bitmap8* bmp)
|
||||||
|
{
|
||||||
|
assertm(Bitmaps[bmp->Resolution] == nullptr, "GroupData: bitmap override");
|
||||||
|
Bitmaps[bmp->Resolution] = bmp;
|
||||||
|
|
||||||
|
auto zMap = ZMaps[bmp->Resolution];
|
||||||
|
if (zMap)
|
||||||
|
{
|
||||||
|
assertm(bmp->Width == zMap->Width && bmp->Height == zMap->Height,
|
||||||
|
"GroupData: mismatched bitmap/zMap dimensions");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GroupData::SetZMap(zmap_header_type* zMap)
|
||||||
|
{
|
||||||
|
// Flip zMap to match with flipped non-indexed bitmaps
|
||||||
|
zdrv::FlipZMapHorizontally(*zMap);
|
||||||
|
|
||||||
|
assertm(ZMaps[zMap->Resolution] == nullptr, "GroupData: zMap override");
|
||||||
|
ZMaps[zMap->Resolution] = zMap;
|
||||||
|
|
||||||
|
auto bmp = Bitmaps[zMap->Resolution];
|
||||||
|
if (bmp)
|
||||||
|
{
|
||||||
|
assertm(bmp->Width == zMap->Width && bmp->Height == zMap->Height,
|
||||||
|
"GroupData: mismatched bitmap/zMap dimensions");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
DatFile::~DatFile()
|
||||||
|
{
|
||||||
|
for (auto group : Groups)
|
||||||
|
{
|
||||||
|
if (!group)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
for (const auto entry : group->GetEntries())
|
||||||
|
{
|
||||||
|
delete entry;
|
||||||
|
}
|
||||||
|
delete group;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
char* DatFile::field(int groupIndex, FieldTypes targetEntryType)
|
||||||
|
{
|
||||||
|
assertm(targetEntryType != FieldTypes::Bitmap8bit && targetEntryType != FieldTypes::Bitmap16bit,
|
||||||
|
"partman: Use specific get for bitmaps");
|
||||||
|
|
||||||
|
auto group = Groups[groupIndex];
|
||||||
|
for (const auto entry : group->GetEntries())
|
||||||
|
{
|
||||||
|
if (entry->EntryType == targetEntryType)
|
||||||
|
{
|
||||||
|
return entry->Buffer;
|
||||||
|
}
|
||||||
|
if (entry->EntryType > targetEntryType)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
char* DatFile::field_nth(int groupIndex, FieldTypes targetEntryType, int skipFirstN)
|
||||||
|
{
|
||||||
|
assertm(targetEntryType != FieldTypes::Bitmap8bit && targetEntryType != FieldTypes::Bitmap16bit,
|
||||||
|
"partman: Use specific get for bitmaps");
|
||||||
|
|
||||||
|
auto group = Groups[groupIndex];
|
||||||
|
auto skipCount = 0;
|
||||||
|
for (const auto entry : group->GetEntries())
|
||||||
|
{
|
||||||
|
if (entry->EntryType > targetEntryType)
|
||||||
|
break;
|
||||||
|
if (entry->EntryType == targetEntryType)
|
||||||
|
if (skipCount++ == skipFirstN)
|
||||||
|
return entry->Buffer;
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
int DatFile::field_size_nth(int groupIndex, FieldTypes targetEntryType, int skipFirstN)
|
||||||
|
{
|
||||||
|
auto group = Groups[groupIndex];
|
||||||
|
auto skipCount = 0;
|
||||||
|
for (const auto entry : group->GetEntries())
|
||||||
|
{
|
||||||
|
if (entry->EntryType > targetEntryType)
|
||||||
|
return 0;
|
||||||
|
if (entry->EntryType == targetEntryType)
|
||||||
|
if (skipCount++ == skipFirstN)
|
||||||
|
return entry->FieldSize;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int DatFile::field_size(int groupIndex, FieldTypes targetEntryType)
|
||||||
|
{
|
||||||
|
return field_size_nth(groupIndex, targetEntryType, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
int DatFile::record_labeled(LPCSTR targetGroupName)
|
||||||
|
{
|
||||||
|
auto targetLength = strlen(targetGroupName);
|
||||||
|
for (int groupIndex = static_cast<int>(Groups.size()) - 1; groupIndex >= 0; --groupIndex)
|
||||||
|
{
|
||||||
|
auto groupName = field(groupIndex, FieldTypes::GroupName);
|
||||||
|
if (!groupName)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
auto index = 0u;
|
||||||
|
for (; index < targetLength; index++)
|
||||||
|
if (targetGroupName[index] != groupName[index])
|
||||||
|
break;
|
||||||
|
if (index == targetLength && !targetGroupName[index] && !groupName[index])
|
||||||
|
return groupIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
char* DatFile::field_labeled(LPCSTR lpString, FieldTypes fieldType)
|
||||||
|
{
|
||||||
|
auto groupIndex = record_labeled(lpString);
|
||||||
|
return groupIndex < 0 ? nullptr : field(groupIndex, fieldType);
|
||||||
|
}
|
||||||
|
|
||||||
|
gdrv_bitmap8* DatFile::GetBitmap(int groupIndex)
|
||||||
|
{
|
||||||
|
auto group = Groups[groupIndex];
|
||||||
|
return group->GetBitmap(fullscrn::GetResolution());
|
||||||
|
}
|
||||||
|
|
||||||
|
zmap_header_type* DatFile::GetZMap(int groupIndex)
|
||||||
|
{
|
||||||
|
auto group = Groups[groupIndex];
|
||||||
|
return group->GetZMap(fullscrn::GetResolution());
|
||||||
|
}
|
||||||
|
|
||||||
|
void DatFile::Finalize()
|
||||||
|
{
|
||||||
|
if (!pb::FullTiltMode)
|
||||||
|
{
|
||||||
|
int groupIndex = record_labeled("pbmsg_ft");
|
||||||
|
assertm(groupIndex < 0, "DatFile: pbmsg_ft is already in .dat");
|
||||||
|
|
||||||
|
// Load 3DPB font into dat to simplify pipeline
|
||||||
|
auto rcData = reinterpret_cast<MsgFont*>(ImFontAtlas::DecompressCompressedBase85Data(
|
||||||
|
EmbeddedData::PB_MSGFT_bin_compressed_data_base85));
|
||||||
|
AddMsgFont(rcData, "pbmsg_ft");
|
||||||
|
IM_FREE(rcData);
|
||||||
|
|
||||||
|
// PINBALL2.MID is an alternative font provided in 3DPB data
|
||||||
|
// Scaled down because it is too large for top text box
|
||||||
|
/*auto file = pb::make_path_name("PINBALL2.MID");
|
||||||
|
auto fileHandle = fopenu(file.c_str(), "rb");
|
||||||
|
fseek(fileHandle, 0, SEEK_END);
|
||||||
|
auto fileSize = static_cast<uint32_t>(ftell(fileHandle));
|
||||||
|
auto rcData = reinterpret_cast<MsgFont*>(new uint8_t[fileSize]);
|
||||||
|
fseek(fileHandle, 0, SEEK_SET);
|
||||||
|
fread(rcData, 1, fileSize, fileHandle);
|
||||||
|
fclose(fileHandle);
|
||||||
|
auto groupId = Groups.back()->GroupId + 1u;
|
||||||
|
AddMsgFont(rcData, "pbmsg_ft");
|
||||||
|
delete[] rcData;
|
||||||
|
for (auto i = groupId; i < Groups.size(); i++)
|
||||||
|
Groups[i]->GetBitmap(0)->ScaleIndexed(0.84f, 0.84f);*/
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto group : Groups)
|
||||||
|
{
|
||||||
|
group->FinalizeGroup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DatFile::AddMsgFont(MsgFont* font, const std::string& fontName)
|
||||||
|
{
|
||||||
|
auto groupId = Groups.back()->GroupId + 1;
|
||||||
|
auto ptrToData = reinterpret_cast<char*>(font->Data);
|
||||||
|
for (auto charInd = 32; charInd < 128; charInd++, groupId++)
|
||||||
|
{
|
||||||
|
auto curChar = reinterpret_cast<MsgFontChar*>(ptrToData);
|
||||||
|
assertm(curChar->Width == font->CharWidths[charInd], "Score: mismatched font width");
|
||||||
|
ptrToData += curChar->Width * font->Height + 1;
|
||||||
|
|
||||||
|
auto bmp = new gdrv_bitmap8(curChar->Width, font->Height, true);
|
||||||
|
auto srcPtr = curChar->Data;
|
||||||
|
auto dstPtr = &bmp->IndexedBmpPtr[bmp->Stride * (bmp->Height - 1)];
|
||||||
|
for (auto y = 0; y < font->Height; ++y)
|
||||||
|
{
|
||||||
|
memcpy(dstPtr, srcPtr, curChar->Width);
|
||||||
|
srcPtr += curChar->Width;
|
||||||
|
dstPtr -= bmp->Stride;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto group = new GroupData(groupId);
|
||||||
|
group->AddEntry(new EntryData(FieldTypes::Bitmap8bit, reinterpret_cast<char*>(bmp)));
|
||||||
|
if (charInd == 32)
|
||||||
|
{
|
||||||
|
// First font group holds font name and gap width
|
||||||
|
auto groupName = new char[fontName.length() + 1];
|
||||||
|
strcpy(groupName, fontName.c_str());
|
||||||
|
group->AddEntry(new EntryData(FieldTypes::GroupName, groupName));
|
||||||
|
|
||||||
|
auto gaps = new char[2];
|
||||||
|
*reinterpret_cast<int16_t*>(gaps) = font->GapWidth;
|
||||||
|
group->AddEntry(new EntryData(FieldTypes::ShortArray, gaps));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
auto groupName = new char[30];
|
||||||
|
sprintf(groupName, "char %d='%c'", charInd, charInd);
|
||||||
|
group->AddEntry(new EntryData(FieldTypes::GroupName, groupName));
|
||||||
|
}
|
||||||
|
|
||||||
|
Groups.push_back(group);
|
||||||
|
}
|
||||||
|
}
|
||||||
128
SpaceCadetPinball/GroupData.h
Normal file
128
SpaceCadetPinball/GroupData.h
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
struct zmap_header_type;
|
||||||
|
struct gdrv_bitmap8;
|
||||||
|
|
||||||
|
|
||||||
|
enum class FieldTypes : int16_t
|
||||||
|
{
|
||||||
|
// One 16 bit signed integer
|
||||||
|
ShortValue = 0,
|
||||||
|
|
||||||
|
// Sprite bitmap, 8bpp, indexed color
|
||||||
|
Bitmap8bit = 1,
|
||||||
|
|
||||||
|
Unknown2 = 2,
|
||||||
|
|
||||||
|
// Group name, char[]. Not all groups have names.
|
||||||
|
GroupName = 3,
|
||||||
|
|
||||||
|
Unknown4 = 4,
|
||||||
|
|
||||||
|
// Palette, contains 256 RBGA 4-byte colors.
|
||||||
|
Palette = 5,
|
||||||
|
|
||||||
|
Unknown6 = 6,
|
||||||
|
|
||||||
|
Unknown7 = 7,
|
||||||
|
|
||||||
|
Unknown8 = 8,
|
||||||
|
|
||||||
|
// String, char[]
|
||||||
|
String = 9,
|
||||||
|
|
||||||
|
// Array of 16 bit signed integers
|
||||||
|
ShortArray = 10,
|
||||||
|
|
||||||
|
// Array of 32 bit floats
|
||||||
|
FloatArray = 11,
|
||||||
|
|
||||||
|
// Sprite depth map, 16bpp, unsigned
|
||||||
|
Bitmap16bit = 12,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct EntryData
|
||||||
|
{
|
||||||
|
EntryData() = default;
|
||||||
|
|
||||||
|
EntryData(FieldTypes entryType, char* buffer): EntryType(entryType), FieldSize(-1), Buffer(buffer)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
~EntryData();
|
||||||
|
FieldTypes EntryType{};
|
||||||
|
int FieldSize{};
|
||||||
|
char* Buffer{};
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#pragma pack(push, 1)
|
||||||
|
struct MsgFontChar
|
||||||
|
{
|
||||||
|
uint8_t Width;
|
||||||
|
char Data[1];
|
||||||
|
};
|
||||||
|
|
||||||
|
struct MsgFont
|
||||||
|
{
|
||||||
|
int16_t GapWidth;
|
||||||
|
int16_t Unknown1;
|
||||||
|
int16_t Height;
|
||||||
|
uint8_t CharWidths[128];
|
||||||
|
MsgFontChar Data[1];
|
||||||
|
};
|
||||||
|
#pragma pack(pop)
|
||||||
|
|
||||||
|
static_assert(sizeof(MsgFont) == 136, "Wrong size of MsgFont");
|
||||||
|
|
||||||
|
|
||||||
|
class GroupData
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
int GroupId;
|
||||||
|
std::string GroupName;
|
||||||
|
|
||||||
|
GroupData(int groupId);
|
||||||
|
void AddEntry(EntryData* entry);
|
||||||
|
void FinalizeGroup();
|
||||||
|
const std::vector<EntryData*>& GetEntries() const { return Entries; }
|
||||||
|
const EntryData* GetEntry(size_t index) const { return Entries[index]; }
|
||||||
|
size_t EntryCount() const { return Entries.size(); }
|
||||||
|
void ReserveEntries(size_t count) { Entries.reserve(count); }
|
||||||
|
gdrv_bitmap8* GetBitmap(int resolution) const;
|
||||||
|
zmap_header_type* GetZMap(int resolution) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<EntryData*> Entries;
|
||||||
|
gdrv_bitmap8* Bitmaps[3]{};
|
||||||
|
zmap_header_type* ZMaps[3]{};
|
||||||
|
bool NeedsSort = false;
|
||||||
|
|
||||||
|
static void SplitSplicedBitmap(const gdrv_bitmap8& srcBmp, gdrv_bitmap8& bmp, zmap_header_type& zMap);
|
||||||
|
|
||||||
|
void SetBitmap(gdrv_bitmap8* bmp);
|
||||||
|
void SetZMap(zmap_header_type* zMap);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
class DatFile
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
std::string AppName;
|
||||||
|
std::string Description;
|
||||||
|
std::vector<GroupData*> Groups;
|
||||||
|
|
||||||
|
~DatFile();
|
||||||
|
char* field_nth(int groupIndex, FieldTypes targetEntryType, int skipFirstN);
|
||||||
|
char* field(int groupIndex, FieldTypes entryType);
|
||||||
|
int field_size_nth(int groupIndex, FieldTypes targetEntryType, int skipFirstN);
|
||||||
|
int field_size(int groupIndex, FieldTypes targetEntryType);
|
||||||
|
int record_labeled(LPCSTR targetGroupName);
|
||||||
|
char* field_labeled(LPCSTR lpString, FieldTypes fieldType);
|
||||||
|
gdrv_bitmap8* GetBitmap(int groupIndex);
|
||||||
|
zmap_header_type* GetZMap(int groupIndex);
|
||||||
|
void Finalize();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void AddMsgFont(MsgFont* font, const std::string& fontName);
|
||||||
|
};
|
||||||
BIN
SpaceCadetPinball/Icon_1.ico
Normal file
BIN
SpaceCadetPinball/Icon_1.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 86 KiB |
BIN
SpaceCadetPinball/Icon_128x128.png
Normal file
BIN
SpaceCadetPinball/Icon_128x128.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 24 KiB |
BIN
SpaceCadetPinball/Icon_16x16.png
Normal file
BIN
SpaceCadetPinball/Icon_16x16.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 972 B |
BIN
SpaceCadetPinball/Icon_192x192.png
Normal file
BIN
SpaceCadetPinball/Icon_192x192.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 38 KiB |
BIN
SpaceCadetPinball/Icon_32x32.png
Normal file
BIN
SpaceCadetPinball/Icon_32x32.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.8 KiB |
BIN
SpaceCadetPinball/Icon_48x48.png
Normal file
BIN
SpaceCadetPinball/Icon_48x48.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.1 KiB |
16
SpaceCadetPinball/NatvisFile.natvis
Normal file
16
SpaceCadetPinball/NatvisFile.natvis
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010">
|
||||||
|
<Type Name="ColorRgba">
|
||||||
|
<DisplayString>{{ Color:{Color} }}</DisplayString>
|
||||||
|
<Expand>
|
||||||
|
<Item Name="[Alpha]" ExcludeView="simple">Color>>alphaOffset & 255</Item>
|
||||||
|
<Item Name="[Red]" ExcludeView="simple">Color>>redOffset & 255</Item>
|
||||||
|
<Item Name="[Green]" ExcludeView="simple">Color>>greenOffset & 255</Item>
|
||||||
|
<Item Name="[Blue]" ExcludeView="simple">Color>>blueOffset & 255</Item>
|
||||||
|
<Item Name="[Color]" ExcludeView="simple">Color</Item>
|
||||||
|
</Expand>
|
||||||
|
</Type>
|
||||||
|
<Type Name="vector3">
|
||||||
|
<DisplayString>{{ X={X} Y={Y} Z={Z} }}</DisplayString>
|
||||||
|
</Type>
|
||||||
|
</AutoVisualizer>
|
||||||
BIN
SpaceCadetPinball/PB_MSGFT.bin
Normal file
BIN
SpaceCadetPinball/PB_MSGFT.bin
Normal file
Binary file not shown.
154
SpaceCadetPinball/Sound.cpp
Normal file
154
SpaceCadetPinball/Sound.cpp
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
#include "pch.h"
|
||||||
|
#include "options.h"
|
||||||
|
#include "Sound.h"
|
||||||
|
#include "maths.h"
|
||||||
|
|
||||||
|
int Sound::num_channels;
|
||||||
|
bool Sound::enabled_flag = false;
|
||||||
|
std::vector<ChannelInfo> Sound::Channels{};
|
||||||
|
int Sound::Volume = MIX_MAX_VOLUME;
|
||||||
|
bool Sound::MixOpen = false;
|
||||||
|
|
||||||
|
void Sound::Init(bool mixOpen, int channels, bool enableFlag, int volume)
|
||||||
|
{
|
||||||
|
MixOpen = mixOpen;
|
||||||
|
Volume = volume;
|
||||||
|
SetChannels(channels);
|
||||||
|
Enable(enableFlag);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Sound::Enable(bool enableFlag)
|
||||||
|
{
|
||||||
|
enabled_flag = enableFlag;
|
||||||
|
if (MixOpen && !enableFlag)
|
||||||
|
Mix_HaltChannel(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Sound::Activate()
|
||||||
|
{
|
||||||
|
if (MixOpen)
|
||||||
|
Mix_Resume(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Sound::Deactivate()
|
||||||
|
{
|
||||||
|
if (MixOpen)
|
||||||
|
Mix_Pause(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Sound::Close()
|
||||||
|
{
|
||||||
|
Enable(false);
|
||||||
|
Channels.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Sound::PlaySound(Mix_Chunk* wavePtr, int time, TPinballComponent* soundSource, const char* info)
|
||||||
|
{
|
||||||
|
if (MixOpen && wavePtr && enabled_flag)
|
||||||
|
{
|
||||||
|
if (Mix_Playing(-1) == num_channels)
|
||||||
|
{
|
||||||
|
auto cmp = [](const ChannelInfo& a, const ChannelInfo& b)
|
||||||
|
{
|
||||||
|
return a.TimeStamp < b.TimeStamp;
|
||||||
|
};
|
||||||
|
auto min = std::min_element(Channels.begin(), Channels.end(), cmp);
|
||||||
|
auto oldestChannel = static_cast<int>(std::distance(Channels.begin(), min));
|
||||||
|
Mix_HaltChannel(oldestChannel);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto channel = Mix_PlayChannel(-1, wavePtr, 0);
|
||||||
|
if (channel != -1)
|
||||||
|
{
|
||||||
|
Channels[channel].TimeStamp = time;
|
||||||
|
if (options::Options.SoundStereo)
|
||||||
|
{
|
||||||
|
// Positional audio uses collision grid 2D coordinates normalized to [0, 1]
|
||||||
|
// Point (0, 0) is bottom left table corner; point (1, 1) is top right table corner.
|
||||||
|
// Z is defined as: 0 at table level, positive axis goes up from table surface.
|
||||||
|
|
||||||
|
// Get the source sound position.
|
||||||
|
// Sound without position are assumed to be at the center top of the table.
|
||||||
|
vector3 soundPos{};
|
||||||
|
if (soundSource)
|
||||||
|
{
|
||||||
|
auto soundPos2D = soundSource->get_coordinates();
|
||||||
|
soundPos = {soundPos2D.X, soundPos2D.Y, 0.0f};
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
soundPos = {0.5f, 1.0f, 0.0f};
|
||||||
|
}
|
||||||
|
Channels[channel].Position = soundPos;
|
||||||
|
|
||||||
|
// Listener is positioned at the bottom center of the table,
|
||||||
|
// at 0.5 height, so roughly a table half - length.
|
||||||
|
vector3 playerPos = {0.5f, 0.0f, 0.5f};
|
||||||
|
auto soundDir = maths::vector_sub(soundPos, playerPos);
|
||||||
|
|
||||||
|
// Find sound angle from positive Y axis in clockwise direction with atan2
|
||||||
|
// Remap atan2 output from (-Pi, Pi] to [0, 2 * Pi)
|
||||||
|
auto angle = fmodf(atan2(soundDir.X, soundDir.Y) + Pi * 2, Pi * 2);
|
||||||
|
auto angleDeg = angle * 180.0f / Pi;
|
||||||
|
auto angleSdl = static_cast<Sint16>(angleDeg);
|
||||||
|
|
||||||
|
// Distance from listener to the sound position is roughly in the [0, ~1.22] range.
|
||||||
|
// Remap to [0, 122] by multiplying by 100 and cast to an integer.
|
||||||
|
auto distance = static_cast<Uint8>(100.0f * maths::magnitude(soundDir));
|
||||||
|
|
||||||
|
// Mix_SetPosition expects an angle in (Sint16)degrees, where
|
||||||
|
// angle 0 is due north, and rotates clockwise as the value increases.
|
||||||
|
// Mix_SetPosition expects a (Uint8)distance from 0 (near) to 255 (far).
|
||||||
|
Mix_SetPosition(channel, angleSdl, distance);
|
||||||
|
|
||||||
|
// Output position of each sound emitted so we can verify
|
||||||
|
// the sanity of the implementation.
|
||||||
|
/*printf("X: %3.3f Y: %3.3f Angle: %3.3f Distance: %3d, Object: %s\n",
|
||||||
|
soundPos.X,
|
||||||
|
soundPos.Y,
|
||||||
|
angleDeg,
|
||||||
|
distance,
|
||||||
|
info
|
||||||
|
);*/
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Mix_Chunk* Sound::LoadWaveFile(const std::string& lpName)
|
||||||
|
{
|
||||||
|
if (!MixOpen)
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
auto wavFile = fopenu(lpName.c_str(), "r");
|
||||||
|
if (!wavFile)
|
||||||
|
return nullptr;
|
||||||
|
fclose(wavFile);
|
||||||
|
|
||||||
|
return Mix_LoadWAV(lpName.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
void Sound::FreeSound(Mix_Chunk* wave)
|
||||||
|
{
|
||||||
|
if (MixOpen && wave)
|
||||||
|
Mix_FreeChunk(wave);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Sound::SetChannels(int channels)
|
||||||
|
{
|
||||||
|
if (channels <= 0)
|
||||||
|
channels = 8;
|
||||||
|
|
||||||
|
num_channels = channels;
|
||||||
|
Channels.resize(num_channels);
|
||||||
|
if (MixOpen)
|
||||||
|
Mix_AllocateChannels(num_channels);
|
||||||
|
SetVolume(Volume);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Sound::SetVolume(int volume)
|
||||||
|
{
|
||||||
|
Volume = volume;
|
||||||
|
if (MixOpen)
|
||||||
|
Mix_Volume(-1, volume);
|
||||||
|
}
|
||||||
31
SpaceCadetPinball/Sound.h
Normal file
31
SpaceCadetPinball/Sound.h
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "maths.h"
|
||||||
|
#include "TPinballComponent.h"
|
||||||
|
|
||||||
|
struct ChannelInfo
|
||||||
|
{
|
||||||
|
int TimeStamp;
|
||||||
|
vector2 Position;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Sound
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static std::vector<ChannelInfo> Channels;
|
||||||
|
|
||||||
|
static void Init(bool mixOpen, int channels, bool enableFlag, int volume);
|
||||||
|
static void Enable(bool enableFlag);
|
||||||
|
static void Activate();
|
||||||
|
static void Deactivate();
|
||||||
|
static void Close();
|
||||||
|
static void PlaySound(Mix_Chunk* wavePtr, int time, TPinballComponent *soundSource, const char* info);
|
||||||
|
static Mix_Chunk* LoadWaveFile(const std::string& lpName);
|
||||||
|
static void FreeSound(Mix_Chunk* wave);
|
||||||
|
static void SetChannels(int channels);
|
||||||
|
static void SetVolume(int volume);
|
||||||
|
private:
|
||||||
|
static int num_channels;
|
||||||
|
static bool enabled_flag;
|
||||||
|
static int Volume;
|
||||||
|
static bool MixOpen;
|
||||||
|
};
|
||||||
55
SpaceCadetPinball/SpaceCadetPinball.cpp
Normal file
55
SpaceCadetPinball/SpaceCadetPinball.cpp
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
#include "pch.h"
|
||||||
|
#include "winmain.h"
|
||||||
|
|
||||||
|
int main(int argc, char* argv[])
|
||||||
|
{
|
||||||
|
std::string cmdLine;
|
||||||
|
for (int i = 1; i < argc; i++)
|
||||||
|
{
|
||||||
|
if (i > 1)
|
||||||
|
cmdLine += " ";
|
||||||
|
cmdLine += argv[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
return winmain::WinMain(cmdLine.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
#if _WIN32
|
||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
// Windows subsystem main
|
||||||
|
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
|
||||||
|
{
|
||||||
|
return winmain::WinMain(lpCmdLine);
|
||||||
|
}
|
||||||
|
|
||||||
|
// fopen to _wfopen adapter, for UTF-8 paths
|
||||||
|
FILE* fopenu(const char* path, const char* opt)
|
||||||
|
{
|
||||||
|
wchar_t* wideArgs[2]{};
|
||||||
|
for (auto& arg : wideArgs)
|
||||||
|
{
|
||||||
|
auto src = wideArgs[0] ? opt : path;
|
||||||
|
auto length = MultiByteToWideChar(CP_UTF8, 0, src, -1, nullptr, 0);
|
||||||
|
arg = new wchar_t[length];
|
||||||
|
MultiByteToWideChar(CP_UTF8, 0, src, -1, arg, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto fileHandle = _wfopen(wideArgs[0], wideArgs[1]);
|
||||||
|
for (auto arg : wideArgs)
|
||||||
|
delete[] arg;
|
||||||
|
|
||||||
|
return fileHandle;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Run program: Ctrl + F5 or Debug > Start Without Debugging menu
|
||||||
|
// Debug program: F5 or Debug > Start Debugging menu
|
||||||
|
|
||||||
|
// Tips for Getting Started:
|
||||||
|
// 1. Use the Solution Explorer window to add/manage files
|
||||||
|
// 2. Use the Team Explorer window to connect to source control
|
||||||
|
// 3. Use the Output window to see build output and other messages
|
||||||
|
// 4. Use the Error List window to view errors
|
||||||
|
// 5. Go to Project > Add New Item to create new code files, or Project > Add Existing Item to add existing code files to the project
|
||||||
|
// 6. In the future, to open this project again, go to File > Open > Project and select the .sln file
|
||||||
1
SpaceCadetPinball/SpaceCadetPinball.rc
Normal file
1
SpaceCadetPinball/SpaceCadetPinball.rc
Normal file
@ -0,0 +1 @@
|
|||||||
|
ICON_1 ICON "Icon_1.ico"
|
||||||
197
SpaceCadetPinball/TBall.cpp
Normal file
197
SpaceCadetPinball/TBall.cpp
Normal file
@ -0,0 +1,197 @@
|
|||||||
|
#include "pch.h"
|
||||||
|
#include "TBall.h"
|
||||||
|
|
||||||
|
|
||||||
|
#include "fullscrn.h"
|
||||||
|
#include "loader.h"
|
||||||
|
#include "maths.h"
|
||||||
|
#include "pb.h"
|
||||||
|
#include "proj.h"
|
||||||
|
#include "render.h"
|
||||||
|
#include "TPinballTable.h"
|
||||||
|
#include "TTableLayer.h"
|
||||||
|
|
||||||
|
TBall::TBall(TPinballTable* table, int groupIndex) : TCollisionComponent(table, groupIndex, false),
|
||||||
|
TEdgeSegment(this, &ActiveFlag, 0)
|
||||||
|
{
|
||||||
|
visualStruct visual{};
|
||||||
|
char ballGroupName[10]{"ball"};
|
||||||
|
|
||||||
|
RayMaxDistance = 0.0;
|
||||||
|
ActiveFlag = 1;
|
||||||
|
CollisionComp = nullptr;
|
||||||
|
EdgeCollisionCount = 0;
|
||||||
|
TimeDelta = 0.0;
|
||||||
|
CollisionFlag = 0;
|
||||||
|
Speed = 0.0;
|
||||||
|
Direction.Y = 0.0;
|
||||||
|
Direction.X = 0.0;
|
||||||
|
ListBitmap = new std::vector<SpriteData>();
|
||||||
|
|
||||||
|
if (groupIndex == -1)
|
||||||
|
{
|
||||||
|
HasGroupFlag = false;
|
||||||
|
Position = {0, 0, 0};
|
||||||
|
CollisionMask = 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
HasGroupFlag = true;
|
||||||
|
loader::query_visual(groupIndex, 0, &visual);
|
||||||
|
CollisionMask = visual.CollisionGroup;
|
||||||
|
auto floatArr = loader::query_float_attribute(groupIndex, 0, 408);
|
||||||
|
Position = {floatArr[0], floatArr[1], floatArr[3]};
|
||||||
|
}
|
||||||
|
|
||||||
|
/*Full tilt: ball is ballN, where N[0,2] resolution*/
|
||||||
|
groupIndex = loader::query_handle(ballGroupName);
|
||||||
|
if (groupIndex < 0)
|
||||||
|
{
|
||||||
|
ballGroupName[4] = '0' + fullscrn::GetResolution();
|
||||||
|
groupIndex = loader::query_handle(ballGroupName);
|
||||||
|
}
|
||||||
|
|
||||||
|
Radius = *loader::query_float_attribute(groupIndex, 0, 500);
|
||||||
|
|
||||||
|
auto visualCount = loader::query_visual_states(groupIndex);
|
||||||
|
for (auto index = 0; index < visualCount; ++index)
|
||||||
|
{
|
||||||
|
loader::query_visual(groupIndex, index, &visual);
|
||||||
|
ListBitmap->push_back(visual.Bitmap);
|
||||||
|
auto visVec = reinterpret_cast<vector3*>(loader::query_float_attribute(groupIndex, index, 501));
|
||||||
|
auto zDepth = proj::z_distance(*visVec);
|
||||||
|
VisualZArray[index] = zDepth;
|
||||||
|
}
|
||||||
|
RenderSprite = new render_sprite(VisualTypes::Ball, nullptr, nullptr, 0, 0, nullptr);
|
||||||
|
PinballTable->CollisionCompOffset = Radius;
|
||||||
|
Position.Z = Radius;
|
||||||
|
GroupIndex = groupIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TBall::Repaint()
|
||||||
|
{
|
||||||
|
if (CollisionFlag)
|
||||||
|
{
|
||||||
|
Position.Z =
|
||||||
|
CollisionOffset.X * Position.X +
|
||||||
|
CollisionOffset.Y * Position.Y +
|
||||||
|
Radius + CollisionOffset.Z;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto pos2D = proj::xform_to_2d(Position);
|
||||||
|
auto zDepth = proj::z_distance(Position);
|
||||||
|
|
||||||
|
auto index = 0u;
|
||||||
|
for (; index < ListBitmap->size() - 1; ++index)
|
||||||
|
{
|
||||||
|
if (VisualZArray[index] <= zDepth) break;
|
||||||
|
}
|
||||||
|
|
||||||
|
SpriteSetBall(index, pos2D, zDepth);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TBall::not_again(TEdgeSegment* edge)
|
||||||
|
{
|
||||||
|
if (EdgeCollisionCount < 16)
|
||||||
|
{
|
||||||
|
Collisions[EdgeCollisionCount] = edge;
|
||||||
|
++EdgeCollisionCount;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for (int i = 0; i < 8; i++)
|
||||||
|
Collisions[i] = Collisions[i + 8];
|
||||||
|
Collisions[8] = edge;
|
||||||
|
EdgeCollisionCount = 9;
|
||||||
|
}
|
||||||
|
EdgeCollisionResetFlag = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TBall::already_hit(const TEdgeSegment& edge) const
|
||||||
|
{
|
||||||
|
for (int i = 0; i < EdgeCollisionCount; i++)
|
||||||
|
{
|
||||||
|
if (Collisions[i] == &edge)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int TBall::Message(MessageCode code, float value)
|
||||||
|
{
|
||||||
|
if (code == MessageCode::Reset)
|
||||||
|
{
|
||||||
|
SpriteSetBall(-1, {0, 0}, 0.0f);
|
||||||
|
Position.X = 0.0;
|
||||||
|
CollisionComp = nullptr;
|
||||||
|
Position.Y = 0.0;
|
||||||
|
ActiveFlag = 0;
|
||||||
|
CollisionFlag = 0;
|
||||||
|
CollisionMask = 1;
|
||||||
|
Direction.Y = 0.0;
|
||||||
|
Position.Z = Radius;
|
||||||
|
Direction.X = 0.0;
|
||||||
|
Speed = 0.0;
|
||||||
|
RayMaxDistance = 0.0;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TBall::throw_ball(vector3* direction, float angleMult, float speedMult1, float speedMult2)
|
||||||
|
{
|
||||||
|
CollisionComp = nullptr;
|
||||||
|
Direction = *direction;
|
||||||
|
float rnd = RandFloat();
|
||||||
|
float angle = (1.0f - (rnd + rnd)) * angleMult;
|
||||||
|
maths::RotateVector(Direction, angle);
|
||||||
|
rnd = RandFloat();
|
||||||
|
Speed = (1.0f - (rnd + rnd)) * (speedMult1 * speedMult2) + speedMult1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TBall::EdgeCollision(TBall* ball, float distance)
|
||||||
|
{
|
||||||
|
ball->CollisionDisabledFlag = true;
|
||||||
|
ball->Position.X += ball->Direction.X * distance;
|
||||||
|
ball->Position.Y += ball->Direction.Y * distance;
|
||||||
|
ball->Direction.X *= ball->Speed;
|
||||||
|
ball->Direction.Y *= ball->Speed;
|
||||||
|
Direction.X *= Speed;
|
||||||
|
Direction.Y *= Speed;
|
||||||
|
|
||||||
|
// AB - vector from ball to this, BA - from this to ball; collision direction
|
||||||
|
vector2 AB{ball->Position.X - Position.X, ball->Position.Y - Position.Y};
|
||||||
|
maths::normalize_2d(AB);
|
||||||
|
vector2 BA{-AB.X, -AB.Y};
|
||||||
|
|
||||||
|
// Projection = difference between ball directions and collision direction
|
||||||
|
auto projAB = -maths::DotProduct(ball->Direction, AB);
|
||||||
|
auto projBA = -maths::DotProduct(Direction, BA);
|
||||||
|
vector2 delta{AB.X * projAB - BA.X * projBA, AB.Y * projAB - BA.Y * projBA};
|
||||||
|
|
||||||
|
ball->Direction.X += delta.X;
|
||||||
|
ball->Direction.Y += delta.Y;
|
||||||
|
ball->Speed = maths::normalize_2d(ball->Direction);
|
||||||
|
Direction.X -= delta.X;
|
||||||
|
Direction.Y -= delta.Y;
|
||||||
|
Speed = maths::normalize_2d(Direction);
|
||||||
|
}
|
||||||
|
|
||||||
|
float TBall::FindCollisionDistance(const ray_type& ray)
|
||||||
|
{
|
||||||
|
// Original inherits TCircle and aliases position.
|
||||||
|
const circle_type ballCircle{{Position.X, Position.Y}, Radius * Radius * 4.0f};
|
||||||
|
return maths::ray_intersect_circle(ray, ballCircle);
|
||||||
|
}
|
||||||
|
|
||||||
|
vector2 TBall::get_coordinates()
|
||||||
|
{
|
||||||
|
return TTableLayer::edge_manager->NormalizeBox(Position);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TBall::Disable()
|
||||||
|
{
|
||||||
|
ActiveFlag = false;
|
||||||
|
CollisionDisabledFlag = true;
|
||||||
|
SpriteSet(-1);
|
||||||
|
}
|
||||||
41
SpaceCadetPinball/TBall.h
Normal file
41
SpaceCadetPinball/TBall.h
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "maths.h"
|
||||||
|
#include "TCollisionComponent.h"
|
||||||
|
#include "TEdgeSegment.h"
|
||||||
|
|
||||||
|
class TBall : public TCollisionComponent, public TEdgeSegment
|
||||||
|
{
|
||||||
|
public :
|
||||||
|
TBall(TPinballTable* table, int groupIndex);
|
||||||
|
void Repaint();
|
||||||
|
void not_again(TEdgeSegment* edge);
|
||||||
|
bool already_hit(const TEdgeSegment& edge) const;
|
||||||
|
int Message(MessageCode code, float value) override;
|
||||||
|
vector2 get_coordinates() override;
|
||||||
|
void Disable();
|
||||||
|
void throw_ball(vector3* direction, float angleMult, float speedMult1, float speedMult2);
|
||||||
|
void place_in_grid(RectF* aabb) override {}
|
||||||
|
void EdgeCollision(TBall* ball, float distance) override;
|
||||||
|
float FindCollisionDistance(const ray_type& ray) override;
|
||||||
|
|
||||||
|
vector3 Position{};
|
||||||
|
vector3 PrevPosition{};
|
||||||
|
vector3 Direction{};
|
||||||
|
float Speed;
|
||||||
|
float RayMaxDistance;
|
||||||
|
float TimeDelta;
|
||||||
|
vector2 RampFieldForce{};
|
||||||
|
TCollisionComponent* CollisionComp;
|
||||||
|
int CollisionMask;
|
||||||
|
TEdgeSegment* Collisions[16]{};
|
||||||
|
int EdgeCollisionCount;
|
||||||
|
bool EdgeCollisionResetFlag{};
|
||||||
|
vector3 CollisionOffset{};
|
||||||
|
int CollisionFlag;
|
||||||
|
float Radius;
|
||||||
|
bool HasGroupFlag;
|
||||||
|
int StuckCounter = 0;
|
||||||
|
int LastActiveTime{};
|
||||||
|
float VisualZArray[50]{};
|
||||||
|
bool CollisionDisabledFlag{};
|
||||||
|
};
|
||||||
72
SpaceCadetPinball/TBlocker.cpp
Normal file
72
SpaceCadetPinball/TBlocker.cpp
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
#include "pch.h"
|
||||||
|
#include "TBlocker.h"
|
||||||
|
|
||||||
|
|
||||||
|
#include "control.h"
|
||||||
|
#include "loader.h"
|
||||||
|
#include "render.h"
|
||||||
|
#include "timer.h"
|
||||||
|
|
||||||
|
TBlocker::TBlocker(TPinballTable* table, int groupIndex) : TCollisionComponent(table, groupIndex, true)
|
||||||
|
{
|
||||||
|
visualStruct visual{};
|
||||||
|
|
||||||
|
loader::query_visual(groupIndex, 0, &visual);
|
||||||
|
SoundIndex4 = visual.SoundIndex4;
|
||||||
|
SoundIndex3 = visual.SoundIndex3;
|
||||||
|
InitialDuration = 55;
|
||||||
|
ExtendedDuration = 5;
|
||||||
|
Threshold = 1000000000.0f;
|
||||||
|
Timer = 0;
|
||||||
|
MessageField = 0;
|
||||||
|
ActiveFlag = 0;
|
||||||
|
SpriteSet(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
int TBlocker::Message(MessageCode code, float value)
|
||||||
|
{
|
||||||
|
switch (code)
|
||||||
|
{
|
||||||
|
case MessageCode::SetTiltLock:
|
||||||
|
case MessageCode::PlayerChanged:
|
||||||
|
case MessageCode::Reset:
|
||||||
|
case MessageCode::TBlockerDisable:
|
||||||
|
if (Timer)
|
||||||
|
{
|
||||||
|
timer::kill(Timer);
|
||||||
|
Timer = 0;
|
||||||
|
}
|
||||||
|
MessageField = 0;
|
||||||
|
ActiveFlag = 0;
|
||||||
|
SpriteSet(-1);
|
||||||
|
if (code == MessageCode::TBlockerDisable)
|
||||||
|
loader::play_sound(SoundIndex3, this, "TBlocker1");
|
||||||
|
break;
|
||||||
|
case MessageCode::TBlockerEnable:
|
||||||
|
ActiveFlag = 1;
|
||||||
|
loader::play_sound(SoundIndex4, this, "TBlocker2");
|
||||||
|
SpriteSet(0);
|
||||||
|
if (Timer)
|
||||||
|
timer::kill(Timer);
|
||||||
|
Timer = 0;
|
||||||
|
if (value >= 0)
|
||||||
|
Timer = timer::set(value, this, TimerExpired);
|
||||||
|
break;
|
||||||
|
case MessageCode::TBlockerRestartTimeout:
|
||||||
|
if (Timer)
|
||||||
|
timer::kill(Timer);
|
||||||
|
Timer = timer::set(std::max(value, 0.0f), this, TimerExpired);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TBlocker::TimerExpired(int timerId, void* caller)
|
||||||
|
{
|
||||||
|
auto blocker = static_cast<TBlocker*>(caller);
|
||||||
|
blocker->Timer = 0;
|
||||||
|
control::handler(MessageCode::ControlTimerExpired, blocker);
|
||||||
|
}
|
||||||
18
SpaceCadetPinball/TBlocker.h
Normal file
18
SpaceCadetPinball/TBlocker.h
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "TCollisionComponent.h"
|
||||||
|
|
||||||
|
class TBlocker :
|
||||||
|
public TCollisionComponent
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
TBlocker(TPinballTable* table, int groupIndex);
|
||||||
|
int Message(MessageCode code, float value) override;
|
||||||
|
|
||||||
|
static void TimerExpired(int timerId, void* caller);
|
||||||
|
|
||||||
|
int InitialDuration;
|
||||||
|
int ExtendedDuration;
|
||||||
|
int Timer;
|
||||||
|
int SoundIndex4;
|
||||||
|
int SoundIndex3;
|
||||||
|
};
|
||||||
123
SpaceCadetPinball/TBumper.cpp
Normal file
123
SpaceCadetPinball/TBumper.cpp
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
#include "pch.h"
|
||||||
|
#include "TBumper.h"
|
||||||
|
|
||||||
|
|
||||||
|
#include "control.h"
|
||||||
|
#include "loader.h"
|
||||||
|
#include "render.h"
|
||||||
|
#include "timer.h"
|
||||||
|
#include "TPinballTable.h"
|
||||||
|
|
||||||
|
TBumper::TBumper(TPinballTable* table, int groupIndex) : TCollisionComponent(table, groupIndex, true)
|
||||||
|
{
|
||||||
|
visualStruct visual{};
|
||||||
|
|
||||||
|
BmpIndex = 0;
|
||||||
|
Timer = 0;
|
||||||
|
TimerTime = *loader::query_float_attribute(groupIndex, 0, 407);
|
||||||
|
loader::query_visual(groupIndex, 0, &visual);
|
||||||
|
SoundIndex4 = visual.SoundIndex4;
|
||||||
|
SoundIndex3 = visual.SoundIndex3;
|
||||||
|
OriginalThreshold = Threshold;
|
||||||
|
}
|
||||||
|
|
||||||
|
int TBumper::Message(MessageCode code, float value)
|
||||||
|
{
|
||||||
|
switch (code)
|
||||||
|
{
|
||||||
|
case MessageCode::TBumperSetBmpIndex:
|
||||||
|
{
|
||||||
|
auto nextBmp = static_cast<int>(floor(value));
|
||||||
|
auto maxBmp = static_cast<int>(ListBitmap->size()) - 1;
|
||||||
|
if (2 * nextBmp > maxBmp)
|
||||||
|
nextBmp = maxBmp / 2;
|
||||||
|
if (nextBmp < 0)
|
||||||
|
nextBmp = 0;
|
||||||
|
if (nextBmp != BmpIndex)
|
||||||
|
{
|
||||||
|
if (nextBmp >= BmpIndex)
|
||||||
|
loader::play_sound(SoundIndex4, this, "TBumper1");
|
||||||
|
if (nextBmp < BmpIndex)
|
||||||
|
loader::play_sound(SoundIndex3, this, "TBumper2");
|
||||||
|
BmpIndex = nextBmp;
|
||||||
|
Fire();
|
||||||
|
control::handler(MessageCode::TBumperSetBmpIndex, this);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case MessageCode::TBumperIncBmpIndex:
|
||||||
|
{
|
||||||
|
auto nextBmp = BmpIndex + 1;
|
||||||
|
auto maxBmp = static_cast<int>(ListBitmap->size()) - 1;
|
||||||
|
if (2 * nextBmp > maxBmp)
|
||||||
|
nextBmp = maxBmp / 2;
|
||||||
|
TBumper::Message(MessageCode::TBumperSetBmpIndex, static_cast<float>(nextBmp));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case MessageCode::TBumperDecBmpIndex:
|
||||||
|
{
|
||||||
|
auto nextBmp = BmpIndex - 1;
|
||||||
|
if (nextBmp < 0)
|
||||||
|
nextBmp = 0;
|
||||||
|
TBumper::Message(MessageCode::TBumperSetBmpIndex, static_cast<float>(nextBmp));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case MessageCode::PlayerChanged:
|
||||||
|
{
|
||||||
|
auto playerPtr = &PlayerData[PinballTable->CurrentPlayer];
|
||||||
|
playerPtr->BmpIndex = BmpIndex;
|
||||||
|
playerPtr->MessageField = MessageField;
|
||||||
|
|
||||||
|
playerPtr = &PlayerData[static_cast<int>(floor(value))];
|
||||||
|
BmpIndex = playerPtr->BmpIndex;
|
||||||
|
MessageField = playerPtr->MessageField;
|
||||||
|
TBumper::Message(MessageCode::TBumperSetBmpIndex, static_cast<float>(BmpIndex));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case MessageCode::Reset:
|
||||||
|
{
|
||||||
|
if (Timer)
|
||||||
|
{
|
||||||
|
timer::kill(Timer);
|
||||||
|
TimerExpired(Timer, this);
|
||||||
|
}
|
||||||
|
BmpIndex = 0;
|
||||||
|
MessageField = 0;
|
||||||
|
for (auto& playerPtr : PlayerData)
|
||||||
|
{
|
||||||
|
playerPtr.BmpIndex = 0;
|
||||||
|
playerPtr.MessageField = 0;
|
||||||
|
}
|
||||||
|
TimerExpired(0, this);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TBumper::Collision(TBall* ball, vector2* nextPosition, vector2* direction, float distance, TEdgeSegment* edge)
|
||||||
|
{
|
||||||
|
if (DefaultCollision(ball, nextPosition, direction))
|
||||||
|
{
|
||||||
|
Fire();
|
||||||
|
control::handler(MessageCode::ControlCollision, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TBumper::TimerExpired(int timerId, void* caller)
|
||||||
|
{
|
||||||
|
auto bump = static_cast<TBumper*>(caller);
|
||||||
|
bump->SpriteSet(bump->BmpIndex * 2);
|
||||||
|
bump->Timer = 0;
|
||||||
|
bump->Threshold = bump->OriginalThreshold;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TBumper::Fire()
|
||||||
|
{
|
||||||
|
SpriteSet(2 * BmpIndex + 1);
|
||||||
|
Timer = timer::set(TimerTime, this, TimerExpired);
|
||||||
|
Threshold = 1000000000.0;
|
||||||
|
}
|
||||||
30
SpaceCadetPinball/TBumper.h
Normal file
30
SpaceCadetPinball/TBumper.h
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "TCollisionComponent.h"
|
||||||
|
|
||||||
|
struct TBumper_player_backup
|
||||||
|
{
|
||||||
|
int MessageField;
|
||||||
|
int BmpIndex;
|
||||||
|
};
|
||||||
|
|
||||||
|
class TBumper :
|
||||||
|
public TCollisionComponent
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
TBumper(TPinballTable* table, int groupIndex);
|
||||||
|
~TBumper() override = default;
|
||||||
|
int Message(MessageCode code, float value) override;
|
||||||
|
void Collision(TBall* ball, vector2* nextPosition, vector2* direction, float distance,
|
||||||
|
TEdgeSegment* edge) override;
|
||||||
|
void Fire();
|
||||||
|
|
||||||
|
static void TimerExpired(int timerId, void* caller);
|
||||||
|
|
||||||
|
int BmpIndex;
|
||||||
|
int Timer;
|
||||||
|
float TimerTime;
|
||||||
|
float OriginalThreshold;
|
||||||
|
int SoundIndex4;
|
||||||
|
int SoundIndex3;
|
||||||
|
TBumper_player_backup PlayerData[4]{};
|
||||||
|
};
|
||||||
44
SpaceCadetPinball/TCircle.cpp
Normal file
44
SpaceCadetPinball/TCircle.cpp
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
#include "pch.h"
|
||||||
|
#include "TCircle.h"
|
||||||
|
|
||||||
|
#include "TBall.h"
|
||||||
|
#include "TCollisionComponent.h"
|
||||||
|
#include "TTableLayer.h"
|
||||||
|
|
||||||
|
TCircle::TCircle(TCollisionComponent* collComp, char* activeFlag, unsigned collisionGroup, vector2* center,
|
||||||
|
float radius): TEdgeSegment(collComp, activeFlag, collisionGroup)
|
||||||
|
{
|
||||||
|
Circle.RadiusSq = radius * radius;
|
||||||
|
Circle.Center = *center;
|
||||||
|
}
|
||||||
|
|
||||||
|
float TCircle::FindCollisionDistance(const ray_type& ray)
|
||||||
|
{
|
||||||
|
return maths::ray_intersect_circle(ray, Circle);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TCircle::EdgeCollision(TBall* ball, float distance)
|
||||||
|
{
|
||||||
|
vector2 direction{}, nextPosition{};
|
||||||
|
|
||||||
|
nextPosition.X = distance * ball->Direction.X + ball->Position.X;
|
||||||
|
nextPosition.Y = distance * ball->Direction.Y + ball->Position.Y;
|
||||||
|
direction.X = nextPosition.X - Circle.Center.X;
|
||||||
|
direction.Y = nextPosition.Y - Circle.Center.Y;
|
||||||
|
maths::normalize_2d(direction);
|
||||||
|
CollisionComponent->Collision(ball, &nextPosition, &direction, distance, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TCircle::place_in_grid(RectF* aabb)
|
||||||
|
{
|
||||||
|
if(aabb)
|
||||||
|
{
|
||||||
|
const auto radius = sqrt(Circle.RadiusSq);
|
||||||
|
aabb->Merge({
|
||||||
|
Circle.Center.X + radius, Circle.Center.Y + radius,
|
||||||
|
Circle.Center.X - radius, Circle.Center.Y - radius
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
TTableLayer::edges_insert_circle(&Circle, this, nullptr);
|
||||||
|
}
|
||||||
16
SpaceCadetPinball/TCircle.h
Normal file
16
SpaceCadetPinball/TCircle.h
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "maths.h"
|
||||||
|
#include "TEdgeSegment.h"
|
||||||
|
|
||||||
|
class TCircle :
|
||||||
|
public TEdgeSegment
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
circle_type Circle{};
|
||||||
|
|
||||||
|
TCircle(TCollisionComponent* collComp, char* activeFlag, unsigned int collisionGroup, vector2* center,
|
||||||
|
float radius);
|
||||||
|
float FindCollisionDistance(const ray_type& ray) override;
|
||||||
|
void EdgeCollision(TBall* ball, float distance) override;
|
||||||
|
void place_in_grid(RectF* aabb) override;
|
||||||
|
};
|
||||||
103
SpaceCadetPinball/TCollisionComponent.cpp
Normal file
103
SpaceCadetPinball/TCollisionComponent.cpp
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
#include "pch.h"
|
||||||
|
#include "TCollisionComponent.h"
|
||||||
|
#include "loader.h"
|
||||||
|
#include "maths.h"
|
||||||
|
#include "TEdgeSegment.h"
|
||||||
|
#include "TPinballTable.h"
|
||||||
|
#include "TBall.h"
|
||||||
|
|
||||||
|
|
||||||
|
TCollisionComponent::TCollisionComponent(TPinballTable* table, int groupIndex, bool createWall) :
|
||||||
|
TPinballComponent(table, groupIndex, true)
|
||||||
|
{
|
||||||
|
visualStruct visual{};
|
||||||
|
|
||||||
|
ActiveFlag = 1;
|
||||||
|
AABB = { -10000, -10000, 10000, 10000 };
|
||||||
|
if (GroupName != nullptr)
|
||||||
|
UnusedBaseFlag = 1;
|
||||||
|
if (groupIndex <= 0)
|
||||||
|
{
|
||||||
|
loader::default_vsi(&visual);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
loader::query_visual(groupIndex, 0, &visual);
|
||||||
|
if (createWall)
|
||||||
|
{
|
||||||
|
float offset = table->CollisionCompOffset;
|
||||||
|
float* floatArr = loader::query_float_attribute(groupIndex, 0, 600);
|
||||||
|
TEdgeSegment::install_wall(floatArr, this, &ActiveFlag, visual.CollisionGroup, offset, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Threshold = visual.Kicker.Threshold;
|
||||||
|
Elasticity = visual.Elasticity;
|
||||||
|
Smoothness = visual.Smoothness;
|
||||||
|
Boost = visual.Kicker.Boost;
|
||||||
|
HardHitSoundId = visual.Kicker.HardHitSoundId;
|
||||||
|
SoftHitSoundId = visual.SoftHitSoundId;
|
||||||
|
GroupIndex = groupIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
TCollisionComponent::~TCollisionComponent()
|
||||||
|
{
|
||||||
|
for (auto edge : EdgeList)
|
||||||
|
delete edge;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void TCollisionComponent::port_draw()
|
||||||
|
{
|
||||||
|
for (auto edge : EdgeList)
|
||||||
|
edge->port_draw();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TCollisionComponent::DefaultCollision(TBall* ball, vector2* nextPosition, vector2* direction)
|
||||||
|
{
|
||||||
|
if (PinballTable->TiltLockFlag)
|
||||||
|
{
|
||||||
|
maths::basic_collision(ball, nextPosition, direction, Elasticity, Smoothness, 1000000000.0, 0.0);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool collision = false;
|
||||||
|
auto projSpeed = maths::basic_collision(ball, nextPosition, direction, Elasticity, Smoothness, Threshold, Boost);
|
||||||
|
if (projSpeed > Threshold)
|
||||||
|
{
|
||||||
|
loader::play_sound(HardHitSoundId, ball, "TCollisionComponent1");
|
||||||
|
collision = true;
|
||||||
|
}
|
||||||
|
else if (projSpeed > 0.2f)
|
||||||
|
loader::play_sound(SoftHitSoundId, ball, "TCollisionComponent2");
|
||||||
|
|
||||||
|
return collision;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TCollisionComponent::Collision(TBall* ball, vector2* nextPosition, vector2* direction,
|
||||||
|
float distance, TEdgeSegment* edge)
|
||||||
|
{
|
||||||
|
if (PinballTable->TiltLockFlag)
|
||||||
|
{
|
||||||
|
maths::basic_collision(ball, nextPosition, direction, Elasticity, Smoothness, 1000000000.0, 0.0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto projSpeed = maths::basic_collision(
|
||||||
|
ball,
|
||||||
|
nextPosition,
|
||||||
|
direction,
|
||||||
|
Elasticity,
|
||||||
|
Smoothness,
|
||||||
|
Threshold,
|
||||||
|
Boost);
|
||||||
|
if (projSpeed > Threshold)
|
||||||
|
loader::play_sound(HardHitSoundId, ball, "TCollisionComponent3");
|
||||||
|
else if (projSpeed > 0.2f)
|
||||||
|
loader::play_sound(SoftHitSoundId, ball, "TCollisionComponent4");
|
||||||
|
}
|
||||||
|
|
||||||
|
int TCollisionComponent::FieldEffect(TBall* ball, vector2* vecDst)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
28
SpaceCadetPinball/TCollisionComponent.h
Normal file
28
SpaceCadetPinball/TCollisionComponent.h
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "maths.h"
|
||||||
|
#include "TPinballComponent.h"
|
||||||
|
|
||||||
|
struct vector2;
|
||||||
|
class TEdgeSegment;
|
||||||
|
class TBall;
|
||||||
|
|
||||||
|
class TCollisionComponent : public TPinballComponent
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
std::vector<TEdgeSegment*> EdgeList;
|
||||||
|
float Elasticity;
|
||||||
|
float Smoothness;
|
||||||
|
float Boost;
|
||||||
|
float Threshold;
|
||||||
|
int SoftHitSoundId;
|
||||||
|
int HardHitSoundId;
|
||||||
|
RectF AABB;
|
||||||
|
|
||||||
|
TCollisionComponent(TPinballTable* table, int groupIndex, bool createWall);
|
||||||
|
~TCollisionComponent() override;
|
||||||
|
void port_draw() override;
|
||||||
|
virtual void Collision(TBall* ball, vector2* nextPosition, vector2* direction, float distance,
|
||||||
|
TEdgeSegment* edge);
|
||||||
|
virtual int FieldEffect(TBall* ball, vector2* vecDst);
|
||||||
|
bool DefaultCollision(TBall* ball, vector2* nextPosition, vector2* direction);
|
||||||
|
};
|
||||||
64
SpaceCadetPinball/TComponentGroup.cpp
Normal file
64
SpaceCadetPinball/TComponentGroup.cpp
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
#include "pch.h"
|
||||||
|
#include "TComponentGroup.h"
|
||||||
|
|
||||||
|
|
||||||
|
#include "control.h"
|
||||||
|
#include "loader.h"
|
||||||
|
#include "timer.h"
|
||||||
|
#include "TPinballTable.h"
|
||||||
|
|
||||||
|
TComponentGroup::TComponentGroup(TPinballTable* table, int groupIndex) : TPinballComponent(table, groupIndex, false)
|
||||||
|
{
|
||||||
|
Timer = 0;
|
||||||
|
if (groupIndex > 0)
|
||||||
|
{
|
||||||
|
int attrCount;
|
||||||
|
auto shortArr = loader::query_iattribute(groupIndex, 1027, &attrCount);
|
||||||
|
auto shortArrPtr = shortArr;
|
||||||
|
for (auto index = 0; index < attrCount; ++index, ++shortArrPtr)
|
||||||
|
{
|
||||||
|
auto component = table->find_component(*shortArrPtr);
|
||||||
|
if (component)
|
||||||
|
List.push_back(component);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TComponentGroup::~TComponentGroup()
|
||||||
|
{
|
||||||
|
if (Timer)
|
||||||
|
{
|
||||||
|
timer::kill(Timer);
|
||||||
|
Timer = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int TComponentGroup::Message(MessageCode code, float value)
|
||||||
|
{
|
||||||
|
if (code == MessageCode::TComponentGroupResetNotifyTimer)
|
||||||
|
{
|
||||||
|
if (this->Timer)
|
||||||
|
{
|
||||||
|
timer::kill(this->Timer);
|
||||||
|
this->Timer = 0;
|
||||||
|
}
|
||||||
|
if (value > 0.0f)
|
||||||
|
this->Timer = timer::set(value, this, NotifyTimerExpired);
|
||||||
|
}
|
||||||
|
else if (code < MessageCode::Pause || (code > MessageCode::SetTiltLock &&
|
||||||
|
code != MessageCode::PlayerChanged && code != MessageCode::GameOver))
|
||||||
|
{
|
||||||
|
for (auto component : List)
|
||||||
|
{
|
||||||
|
component->Message(code, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TComponentGroup::NotifyTimerExpired(int timerId, void* caller)
|
||||||
|
{
|
||||||
|
auto compGroup = static_cast<TComponentGroup*>(caller);
|
||||||
|
compGroup->Timer = 0;
|
||||||
|
control::handler(MessageCode::ControlNotifyTimerExpired, compGroup);
|
||||||
|
}
|
||||||
16
SpaceCadetPinball/TComponentGroup.h
Normal file
16
SpaceCadetPinball/TComponentGroup.h
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "TPinballComponent.h"
|
||||||
|
|
||||||
|
|
||||||
|
class TComponentGroup :
|
||||||
|
public TPinballComponent
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
TComponentGroup(TPinballTable* table, int groupIndex);
|
||||||
|
~TComponentGroup() override;
|
||||||
|
int Message(MessageCode code, float value) override;
|
||||||
|
static void NotifyTimerExpired(int timerId, void* caller);
|
||||||
|
|
||||||
|
std::vector<TPinballComponent*> List;
|
||||||
|
int Timer;
|
||||||
|
};
|
||||||
203
SpaceCadetPinball/TDemo.cpp
Normal file
203
SpaceCadetPinball/TDemo.cpp
Normal file
@ -0,0 +1,203 @@
|
|||||||
|
#include "pch.h"
|
||||||
|
#include "TDemo.h"
|
||||||
|
|
||||||
|
|
||||||
|
#include "loader.h"
|
||||||
|
#include "pb.h"
|
||||||
|
#include "TEdgeSegment.h"
|
||||||
|
#include "timer.h"
|
||||||
|
#include "TPinballTable.h"
|
||||||
|
#include "TBall.h"
|
||||||
|
|
||||||
|
TDemo::TDemo(TPinballTable* table, int groupIndex)
|
||||||
|
: TCollisionComponent(table, groupIndex, false)
|
||||||
|
{
|
||||||
|
visualStruct visual{};
|
||||||
|
|
||||||
|
RestartGameTimer = 0;
|
||||||
|
PlungerFlag = 0;
|
||||||
|
FlipLeftTimer = 0;
|
||||||
|
FlipRightTimer = 0;
|
||||||
|
MessageField = 0;
|
||||||
|
UnusedBaseFlag = 0;
|
||||||
|
ActiveFlag = 0;
|
||||||
|
FlipRightFlag = 0;
|
||||||
|
FlipLeftFlag = 0;
|
||||||
|
table->Demo = this;
|
||||||
|
auto floatArr1 = loader::query_float_attribute(groupIndex, 0, 407);
|
||||||
|
if (floatArr1)
|
||||||
|
{
|
||||||
|
FlipTimerTime1 = floatArr1[0];
|
||||||
|
FlipTimerTime2 = floatArr1[1];
|
||||||
|
UnFlipTimerTime1 = floatArr1[2];
|
||||||
|
UnFlipTimerTime2 = floatArr1[3];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
FlipTimerTime1 = 0.2f;
|
||||||
|
FlipTimerTime2 = 0.1f;
|
||||||
|
UnFlipTimerTime1 = 0.2f;
|
||||||
|
UnFlipTimerTime2 = 0.1f;
|
||||||
|
}
|
||||||
|
loader::query_visual(groupIndex, 0, &visual);
|
||||||
|
auto v5 = loader::query_float_attribute(groupIndex, 0, 1400);
|
||||||
|
Edge1 = TEdgeSegment::install_wall(v5, this, &ActiveFlag, visual.CollisionGroup, 0.0, 1400);
|
||||||
|
|
||||||
|
auto v6 = loader::query_float_attribute(groupIndex, 0, 1401);
|
||||||
|
TEdgeSegment::install_wall(v6, this, &ActiveFlag, visual.CollisionGroup, 0.0, 1401);
|
||||||
|
|
||||||
|
auto v7 = loader::query_float_attribute(groupIndex, 0, 1402);
|
||||||
|
Edge2 = TEdgeSegment::install_wall(v7, this, &ActiveFlag, visual.CollisionGroup, 0.0, 1402);
|
||||||
|
|
||||||
|
auto v8 = loader::query_float_attribute(groupIndex, 0, 1403);
|
||||||
|
TEdgeSegment::install_wall(v8, this, &ActiveFlag, visual.CollisionGroup, 0.0, 1403);
|
||||||
|
|
||||||
|
auto v9 = loader::query_float_attribute(groupIndex, 0, 1404);
|
||||||
|
Edge3 = TEdgeSegment::install_wall(v9, this, &ActiveFlag, visual.CollisionGroup, table->CollisionCompOffset, 1404);
|
||||||
|
}
|
||||||
|
|
||||||
|
int TDemo::Message(MessageCode code, float value)
|
||||||
|
{
|
||||||
|
switch (code)
|
||||||
|
{
|
||||||
|
case MessageCode::NewGame:
|
||||||
|
if (RestartGameTimer)
|
||||||
|
timer::kill(RestartGameTimer);
|
||||||
|
RestartGameTimer = 0;
|
||||||
|
break;
|
||||||
|
case MessageCode::GameOver:
|
||||||
|
if (RestartGameTimer)
|
||||||
|
timer::kill(RestartGameTimer);
|
||||||
|
RestartGameTimer = 0;
|
||||||
|
if (ActiveFlag != 0)
|
||||||
|
RestartGameTimer = timer::set(5.0, this, NewGameRestartTimer);
|
||||||
|
break;
|
||||||
|
case MessageCode::Reset:
|
||||||
|
if (FlipLeftTimer)
|
||||||
|
timer::kill(FlipLeftTimer);
|
||||||
|
FlipLeftTimer = 0;
|
||||||
|
if (FlipRightTimer)
|
||||||
|
timer::kill(FlipRightTimer);
|
||||||
|
FlipRightTimer = 0;
|
||||||
|
|
||||||
|
if (FlipLeftFlag != 0)
|
||||||
|
UnFlipLeft(0, this);
|
||||||
|
if (FlipRightFlag)
|
||||||
|
UnFlipRight(0, this);
|
||||||
|
if (PlungerFlag)
|
||||||
|
PlungerRelease(0, this);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TDemo::Collision(TBall* ball, vector2* nextPosition, vector2* direction, float distance, TEdgeSegment* edge)
|
||||||
|
{
|
||||||
|
ball->not_again(edge);
|
||||||
|
ball->Position.X = nextPosition->X;
|
||||||
|
ball->Position.Y = nextPosition->Y;
|
||||||
|
ball->RayMaxDistance -= distance;
|
||||||
|
|
||||||
|
switch (reinterpret_cast<size_t>(edge->WallValue))
|
||||||
|
{
|
||||||
|
case 1400:
|
||||||
|
if (!FlipLeftTimer && !FlipLeftFlag)
|
||||||
|
{
|
||||||
|
float time = FlipTimerTime1 + FlipTimerTime2 - RandFloat() * (FlipTimerTime2 + FlipTimerTime2);
|
||||||
|
FlipLeftTimer = timer::set(time, this, FlipLeft);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 1401:
|
||||||
|
FlipLeft(0, this);
|
||||||
|
break;
|
||||||
|
case 1402:
|
||||||
|
if (!FlipRightTimer && !FlipRightFlag)
|
||||||
|
{
|
||||||
|
float time = FlipTimerTime1 + FlipTimerTime2 - RandFloat() * (FlipTimerTime2 + FlipTimerTime2);
|
||||||
|
FlipRightTimer = timer::set(time, this, FlipRight);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 1403:
|
||||||
|
FlipRight(0, this);
|
||||||
|
break;
|
||||||
|
case 1404:
|
||||||
|
if (!PlungerFlag)
|
||||||
|
{
|
||||||
|
PinballTable->Message(MessageCode::PlungerInputPressed, 0);
|
||||||
|
float time = RandFloat() + 2.0f;
|
||||||
|
PlungerFlag = timer::set(time, this, PlungerRelease);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TDemo::PlungerRelease(int timerId, void* caller)
|
||||||
|
{
|
||||||
|
auto demo = static_cast<TDemo*>(caller);
|
||||||
|
demo->PlungerFlag = 0;
|
||||||
|
demo->PinballTable->Message(MessageCode::PlungerInputReleased, pb::time_next);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TDemo::UnFlipRight(int timerId, void* caller)
|
||||||
|
{
|
||||||
|
auto demo = static_cast<TDemo*>(caller);
|
||||||
|
if (demo->FlipRightFlag)
|
||||||
|
demo->PinballTable->Message(MessageCode::RightFlipperInputReleased, pb::time_next);
|
||||||
|
demo->FlipRightFlag = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TDemo::UnFlipLeft(int timerId, void* caller)
|
||||||
|
{
|
||||||
|
auto demo = static_cast<TDemo*>(caller);
|
||||||
|
if (demo->FlipLeftFlag)
|
||||||
|
demo->PinballTable->Message(MessageCode::LeftFlipperInputReleased, pb::time_next);
|
||||||
|
demo->FlipLeftFlag = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TDemo::FlipRight(int timerId, void* caller)
|
||||||
|
{
|
||||||
|
auto demo = static_cast<TDemo*>(caller);
|
||||||
|
if (!demo->FlipRightFlag)
|
||||||
|
{
|
||||||
|
if (demo->FlipRightTimer)
|
||||||
|
{
|
||||||
|
timer::kill(demo->FlipRightTimer);
|
||||||
|
demo->FlipRightTimer = 0;
|
||||||
|
}
|
||||||
|
demo->PinballTable->Message(MessageCode::RightFlipperInputPressed, pb::time_next);
|
||||||
|
demo->FlipRightFlag = 1;
|
||||||
|
float time = demo->UnFlipTimerTime1 + demo->UnFlipTimerTime2 - RandFloat() *
|
||||||
|
(demo->UnFlipTimerTime2 + demo->UnFlipTimerTime2);
|
||||||
|
timer::set(time, demo, UnFlipRight);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TDemo::FlipLeft(int timerId, void* caller)
|
||||||
|
{
|
||||||
|
auto demo = static_cast<TDemo*>(caller);
|
||||||
|
if (!demo->FlipLeftFlag)
|
||||||
|
{
|
||||||
|
if (demo->FlipLeftTimer)
|
||||||
|
{
|
||||||
|
timer::kill(demo->FlipLeftTimer);
|
||||||
|
demo->FlipLeftTimer = 0;
|
||||||
|
}
|
||||||
|
demo->PinballTable->Message(MessageCode::LeftFlipperInputPressed, pb::time_next);
|
||||||
|
demo->FlipLeftFlag = 1;
|
||||||
|
float time = demo->UnFlipTimerTime1 + demo->UnFlipTimerTime2 - RandFloat() *
|
||||||
|
(demo->UnFlipTimerTime2 + demo->UnFlipTimerTime2);
|
||||||
|
timer::set(time, demo, UnFlipLeft);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TDemo::NewGameRestartTimer(int timerId, void* caller)
|
||||||
|
{
|
||||||
|
auto demo = static_cast<TDemo*>(caller);
|
||||||
|
pb::replay_level(true);
|
||||||
|
demo->PinballTable->Message(MessageCode::NewGame, static_cast<float>(demo->PinballTable->PlayerCount));
|
||||||
|
demo->RestartGameTimer = 0;
|
||||||
|
}
|
||||||
33
SpaceCadetPinball/TDemo.h
Normal file
33
SpaceCadetPinball/TDemo.h
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "TCollisionComponent.h"
|
||||||
|
|
||||||
|
class TDemo :
|
||||||
|
public TCollisionComponent
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
TDemo(TPinballTable* table, int groupIndex);
|
||||||
|
int Message(MessageCode code, float value) override;
|
||||||
|
void Collision(TBall* ball, vector2* nextPosition, vector2* direction, float distance,
|
||||||
|
TEdgeSegment* edge) override;
|
||||||
|
|
||||||
|
static void PlungerRelease(int timerId, void* caller);
|
||||||
|
static void UnFlipRight(int timerId, void* caller);
|
||||||
|
static void UnFlipLeft(int timerId, void* caller);
|
||||||
|
static void FlipRight(int timerId, void* caller);
|
||||||
|
static void FlipLeft(int timerId, void* caller);
|
||||||
|
static void NewGameRestartTimer(int timerId, void* caller);
|
||||||
|
|
||||||
|
float FlipTimerTime1;
|
||||||
|
float FlipTimerTime2;
|
||||||
|
float UnFlipTimerTime1;
|
||||||
|
float UnFlipTimerTime2;
|
||||||
|
int FlipLeftFlag;
|
||||||
|
int FlipRightFlag;
|
||||||
|
int FlipLeftTimer;
|
||||||
|
int FlipRightTimer;
|
||||||
|
int PlungerFlag;
|
||||||
|
int RestartGameTimer;
|
||||||
|
TEdgeSegment* Edge1;
|
||||||
|
TEdgeSegment* Edge2;
|
||||||
|
TEdgeSegment* Edge3;
|
||||||
|
};
|
||||||
48
SpaceCadetPinball/TDrain.cpp
Normal file
48
SpaceCadetPinball/TDrain.cpp
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
#include "pch.h"
|
||||||
|
#include "TDrain.h"
|
||||||
|
|
||||||
|
|
||||||
|
#include "control.h"
|
||||||
|
#include "loader.h"
|
||||||
|
#include "TBall.h"
|
||||||
|
#include "timer.h"
|
||||||
|
#include "TPinballTable.h"
|
||||||
|
|
||||||
|
TDrain::TDrain(TPinballTable* table, int groupIndex) : TCollisionComponent(table, groupIndex, true)
|
||||||
|
{
|
||||||
|
Timer = 0;
|
||||||
|
TimerTime = *loader::query_float_attribute(groupIndex, 0, 407);
|
||||||
|
}
|
||||||
|
|
||||||
|
int TDrain::Message(MessageCode code, float value)
|
||||||
|
{
|
||||||
|
if (code == MessageCode::Reset)
|
||||||
|
{
|
||||||
|
if (Timer)
|
||||||
|
{
|
||||||
|
timer::kill(Timer);
|
||||||
|
Timer = 0;
|
||||||
|
}
|
||||||
|
PinballTable->BallInDrainFlag = 0;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TDrain::Collision(TBall* ball, vector2* nextPosition, vector2* direction, float distance, TEdgeSegment* edge)
|
||||||
|
{
|
||||||
|
ball->Disable();
|
||||||
|
--PinballTable->MultiballCount;
|
||||||
|
if (PinballTable->MultiballCount <= 0)
|
||||||
|
{
|
||||||
|
PinballTable->MultiballCount = 0;
|
||||||
|
PinballTable->BallInDrainFlag = 1;
|
||||||
|
Timer = timer::set(TimerTime, this, TimerCallback);
|
||||||
|
}
|
||||||
|
control::handler(MessageCode::ControlCollision, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TDrain::TimerCallback(int timerId, void* caller)
|
||||||
|
{
|
||||||
|
auto drain = static_cast<TDrain*>(caller);
|
||||||
|
control::handler(MessageCode::ControlTimerExpired, drain);
|
||||||
|
}
|
||||||
17
SpaceCadetPinball/TDrain.h
Normal file
17
SpaceCadetPinball/TDrain.h
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "TCollisionComponent.h"
|
||||||
|
|
||||||
|
class TDrain :
|
||||||
|
public TCollisionComponent
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
TDrain(TPinballTable* table, int groupIndex);
|
||||||
|
int Message(MessageCode code, float value) override;
|
||||||
|
void Collision(TBall* ball, vector2* nextPosition, vector2* direction, float distance,
|
||||||
|
TEdgeSegment* edge) override;
|
||||||
|
|
||||||
|
static void TimerCallback(int timerId, void* caller);
|
||||||
|
|
||||||
|
float TimerTime;
|
||||||
|
int Timer;
|
||||||
|
};
|
||||||
11
SpaceCadetPinball/TEdgeBox.h
Normal file
11
SpaceCadetPinball/TEdgeBox.h
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
struct field_effect_type;
|
||||||
|
class TEdgeSegment;
|
||||||
|
|
||||||
|
class TEdgeBox
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
std::vector<TEdgeSegment*> EdgeList{};
|
||||||
|
std::vector<field_effect_type*> FieldList{};
|
||||||
|
};
|
||||||
231
SpaceCadetPinball/TEdgeManager.cpp
Normal file
231
SpaceCadetPinball/TEdgeManager.cpp
Normal file
@ -0,0 +1,231 @@
|
|||||||
|
#include "pch.h"
|
||||||
|
#include "TEdgeManager.h"
|
||||||
|
|
||||||
|
|
||||||
|
#include "maths.h"
|
||||||
|
#include "TBall.h"
|
||||||
|
#include "TEdgeBox.h"
|
||||||
|
#include "TEdgeSegment.h"
|
||||||
|
#include "TTableLayer.h"
|
||||||
|
|
||||||
|
TEdgeManager::TEdgeManager(float xMin, float yMin, float width, float height)
|
||||||
|
{
|
||||||
|
Width = width;
|
||||||
|
Height = height;
|
||||||
|
MinX = xMin;
|
||||||
|
MinY = yMin;
|
||||||
|
MaxX = MinX + width;
|
||||||
|
MaxY = MinY + height;
|
||||||
|
MaxBoxX = 10;
|
||||||
|
MaxBoxY = 15;
|
||||||
|
AdvanceX = width / static_cast<float>(MaxBoxX);
|
||||||
|
AdvanceY = height / static_cast<float>(MaxBoxY);
|
||||||
|
BoxArray = new TEdgeBox[MaxBoxX * MaxBoxY];
|
||||||
|
}
|
||||||
|
|
||||||
|
TEdgeManager::~TEdgeManager()
|
||||||
|
{
|
||||||
|
delete[] BoxArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
int TEdgeManager::box_x(float x)
|
||||||
|
{
|
||||||
|
return std::max(0, std::min(static_cast<int>(floor((x - MinX) / AdvanceX)), MaxBoxX - 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
int TEdgeManager::box_y(float y)
|
||||||
|
{
|
||||||
|
return std::max(0, std::min(static_cast<int>(floor((y - MinY) / AdvanceY)), MaxBoxY - 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
int TEdgeManager::increment_box_x(int x)
|
||||||
|
{
|
||||||
|
return std::min(x + 1, MaxBoxX - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
int TEdgeManager::increment_box_y(int y)
|
||||||
|
{
|
||||||
|
return std::min(y + 1, MaxBoxY - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TEdgeManager::add_edge_to_box(int x, int y, TEdgeSegment* edge)
|
||||||
|
{
|
||||||
|
assertm((unsigned)x < (unsigned)MaxBoxX && (unsigned)y < (unsigned)MaxBoxY, "Box coordinates out of range");
|
||||||
|
|
||||||
|
auto& list = BoxArray[x + y * MaxBoxX].EdgeList;
|
||||||
|
assertm(std::find(list.begin(), list.end(), edge) == list.end(), "Duplicate inserted into box");
|
||||||
|
list.push_back(edge);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TEdgeManager::add_field_to_box(int x, int y, field_effect_type* field)
|
||||||
|
{
|
||||||
|
assertm((unsigned)x < (unsigned)MaxBoxX && (unsigned)y < (unsigned)MaxBoxY, "Box coordinates out of range");
|
||||||
|
|
||||||
|
auto& list = BoxArray[x + y * MaxBoxX].FieldList;
|
||||||
|
assertm(std::find(list.begin(), list.end(), field) == list.end(), "Duplicate inserted into box");
|
||||||
|
list.push_back(field);
|
||||||
|
}
|
||||||
|
|
||||||
|
int TEdgeManager::TestGridBox(int x, int y, float* distPtr, TEdgeSegment** edgeDst, ray_type* ray, TBall* ball,
|
||||||
|
int edgeIndex)
|
||||||
|
{
|
||||||
|
if (x >= 0 && x < MaxBoxX && y >= 0 && y < MaxBoxY)
|
||||||
|
{
|
||||||
|
TEdgeBox* edgeBox = &BoxArray[x + y * MaxBoxX];
|
||||||
|
TEdgeSegment** edgePtr = &EdgeArray[edgeIndex];
|
||||||
|
for (auto it = edgeBox->EdgeList.rbegin(); it != edgeBox->EdgeList.rend(); ++it)
|
||||||
|
{
|
||||||
|
auto edge = *it;
|
||||||
|
if (!edge->ProcessedFlag && *edge->ActiveFlagPtr && (edge->CollisionGroup & ray->CollisionMask) != 0)
|
||||||
|
{
|
||||||
|
if (!ball->already_hit(*edge))
|
||||||
|
{
|
||||||
|
++edgeIndex;
|
||||||
|
*edgePtr = edge;
|
||||||
|
++edgePtr;
|
||||||
|
edge->ProcessedFlag = 1;
|
||||||
|
auto dist = edge->FindCollisionDistance(*ray);
|
||||||
|
if (dist < *distPtr)
|
||||||
|
{
|
||||||
|
*distPtr = dist;
|
||||||
|
*edgeDst = edge;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return edgeIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TEdgeManager::FieldEffects(TBall* ball, vector2* dstVec)
|
||||||
|
{
|
||||||
|
vector2 vec{};
|
||||||
|
TEdgeBox* edgeBox = &BoxArray[box_x(ball->Position.X) + box_y(ball->Position.Y) *
|
||||||
|
MaxBoxX];
|
||||||
|
|
||||||
|
for (auto it = edgeBox->FieldList.rbegin(); it != edgeBox->FieldList.rend(); ++it)
|
||||||
|
{
|
||||||
|
auto field = *it;
|
||||||
|
if (*field->ActiveFlag && ball->CollisionMask & field->CollisionGroup)
|
||||||
|
{
|
||||||
|
if (field->CollisionComp->FieldEffect(ball, &vec))
|
||||||
|
{
|
||||||
|
maths::vector_add(*dstVec, vec);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
float TEdgeManager::FindCollisionDistance(ray_type* ray, TBall* ball, TEdgeSegment** edge)
|
||||||
|
{
|
||||||
|
auto distance = 1000000000.0f;
|
||||||
|
auto edgeIndex = 0;
|
||||||
|
|
||||||
|
auto x0 = ray->Origin.X;
|
||||||
|
auto y0 = ray->Origin.Y;
|
||||||
|
auto x1 = ray->Direction.X * ray->MaxDistance + ray->Origin.X;
|
||||||
|
auto y1 = ray->Direction.Y * ray->MaxDistance + ray->Origin.Y;
|
||||||
|
|
||||||
|
auto xBox0 = box_x(x0);
|
||||||
|
auto yBox0 = box_y(y0);
|
||||||
|
auto xBox1 = box_x(x1);
|
||||||
|
auto yBox1 = box_y(y1);
|
||||||
|
|
||||||
|
auto dirX = x0 >= x1 ? -1 : 1;
|
||||||
|
auto dirY = y0 >= y1 ? -1 : 1;
|
||||||
|
|
||||||
|
if (yBox0 == yBox1)
|
||||||
|
{
|
||||||
|
if (dirX == 1)
|
||||||
|
{
|
||||||
|
for (auto indexX = xBox0; indexX <= xBox1; indexX++)
|
||||||
|
{
|
||||||
|
edgeIndex = TestGridBox(indexX, yBox0, &distance, edge, ray, ball, edgeIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for (auto indexX = xBox0; indexX >= xBox1; indexX--)
|
||||||
|
{
|
||||||
|
edgeIndex = TestGridBox(indexX, yBox0, &distance, edge, ray, ball, edgeIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (xBox0 == xBox1)
|
||||||
|
{
|
||||||
|
if (dirY == 1)
|
||||||
|
{
|
||||||
|
for (auto indexY = yBox0; indexY <= yBox1; indexY++)
|
||||||
|
{
|
||||||
|
edgeIndex = TestGridBox(xBox0, indexY, &distance, edge, ray, ball, edgeIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for (auto indexY = yBox0; indexY >= yBox1; indexY--)
|
||||||
|
{
|
||||||
|
edgeIndex = TestGridBox(xBox0, indexY, &distance, edge, ray, ball, edgeIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
edgeIndex = TestGridBox(xBox0, yBox0, &distance, edge, ray, ball, 0);
|
||||||
|
|
||||||
|
// Bresenham line formula: y = dYdX * (x - x0) + y0; dYdX = (y0 - y1) / (x0 - x1)
|
||||||
|
auto dyDx = (y0 - y1) / (x0 - x1);
|
||||||
|
// Precompute constant part: dYdX * (-x0) + y0
|
||||||
|
auto precomp = -x0 * dyDx + y0;
|
||||||
|
// X and Y indexes are offset by one when going forwards, not sure why
|
||||||
|
auto xBias = dirX == 1 ? 1 : 0, yBias = dirY == 1 ? 1 : 0;
|
||||||
|
|
||||||
|
for (auto indexX = xBox0, indexY = yBox0; indexX != xBox1 || indexY != yBox1;)
|
||||||
|
{
|
||||||
|
// Calculate y from indexY and from line formula
|
||||||
|
auto yDiscrete = (indexY + yBias) * AdvanceY + MinY;
|
||||||
|
auto ylinear = ((indexX + xBias) * AdvanceX + MinX) * dyDx + precomp;
|
||||||
|
if (dirY == 1 ? ylinear >= yDiscrete : ylinear <= yDiscrete)
|
||||||
|
{
|
||||||
|
// Advance indexY when discrete value is ahead/behind
|
||||||
|
// Advance indexX when discrete value matches linear value
|
||||||
|
indexY += dirY;
|
||||||
|
if (ylinear == yDiscrete)
|
||||||
|
indexX += dirX;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Advance indexX otherwise
|
||||||
|
indexX += dirX;
|
||||||
|
}
|
||||||
|
edgeIndex = TestGridBox(indexX, indexY, &distance, edge, ray, ball, edgeIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
for (auto edgePtr = EdgeArray; edgeIndex > 0; --edgeIndex, ++edgePtr)
|
||||||
|
{
|
||||||
|
(*edgePtr)->ProcessedFlag = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return distance;
|
||||||
|
}
|
||||||
|
|
||||||
|
vector2 TEdgeManager::NormalizeBox(vector2 pt) const
|
||||||
|
{
|
||||||
|
// Standard PB Box ranges: X [-8, 8]; Y [-14, 15]; Top right corner: (-8, -14)
|
||||||
|
// Bring them to: X [0, 16]; Y [0, 29]; Top right corner: (0, 0)
|
||||||
|
auto x = Clamp(pt.X, MinX, MaxX) + abs(MinX);
|
||||||
|
auto y = Clamp(pt.Y, MinY, MaxY) + abs(MinY);
|
||||||
|
|
||||||
|
// Normalize and invert to: X [0, 1]; Y [0, 1]; Top right corner: (1, 1)
|
||||||
|
x /= Width; y /= Height;
|
||||||
|
return vector2{ 1 - x, 1 - y };
|
||||||
|
}
|
||||||
|
|
||||||
|
vector2 TEdgeManager::DeNormalizeBox(vector2 pt) const
|
||||||
|
{
|
||||||
|
// Undo normalization by applying steps in reverse
|
||||||
|
auto x = (1 - pt.X) * Width - abs(MinX);
|
||||||
|
auto y = (1 - pt.Y) * Height - abs(MinY);
|
||||||
|
return vector2{ x, y };
|
||||||
|
}
|
||||||
43
SpaceCadetPinball/TEdgeManager.h
Normal file
43
SpaceCadetPinball/TEdgeManager.h
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "TCollisionComponent.h"
|
||||||
|
|
||||||
|
struct ray_type;
|
||||||
|
class TEdgeBox;
|
||||||
|
|
||||||
|
struct field_effect_type
|
||||||
|
{
|
||||||
|
char* ActiveFlag;
|
||||||
|
int CollisionGroup;
|
||||||
|
TCollisionComponent* CollisionComp;
|
||||||
|
};
|
||||||
|
|
||||||
|
class TEdgeManager
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
TEdgeManager(float xMin, float yMin, float width, float height);
|
||||||
|
~TEdgeManager();
|
||||||
|
void FieldEffects(TBall* ball, struct vector2* dstVec);
|
||||||
|
int box_x(float x);
|
||||||
|
int box_y(float y);
|
||||||
|
int increment_box_x(int x);
|
||||||
|
int increment_box_y(int y);
|
||||||
|
void add_edge_to_box(int x, int y, TEdgeSegment* edge);
|
||||||
|
void add_field_to_box(int x, int y, field_effect_type* field);
|
||||||
|
int TestGridBox(int x, int y, float* distPtr, TEdgeSegment** edgeDst, ray_type* ray, TBall* ball, int edgeIndex);
|
||||||
|
float FindCollisionDistance(ray_type* ray, TBall* ball, TEdgeSegment** edge);
|
||||||
|
vector2 NormalizeBox(vector2 pt) const;
|
||||||
|
vector2 DeNormalizeBox(vector2 pt) const;
|
||||||
|
|
||||||
|
float AdvanceX;
|
||||||
|
float AdvanceY;
|
||||||
|
int MaxBoxX;
|
||||||
|
int MaxBoxY;
|
||||||
|
float MinX;
|
||||||
|
float MinY;
|
||||||
|
float MaxX;
|
||||||
|
float MaxY;
|
||||||
|
float Width;
|
||||||
|
float Height;
|
||||||
|
TEdgeBox* BoxArray;
|
||||||
|
TEdgeSegment* EdgeArray[1000]{};
|
||||||
|
};
|
||||||
116
SpaceCadetPinball/TEdgeSegment.cpp
Normal file
116
SpaceCadetPinball/TEdgeSegment.cpp
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
#include "pch.h"
|
||||||
|
#include "TEdgeSegment.h"
|
||||||
|
|
||||||
|
#include "TCircle.h"
|
||||||
|
#include "TCollisionComponent.h"
|
||||||
|
#include "TLine.h"
|
||||||
|
|
||||||
|
TEdgeSegment::TEdgeSegment(TCollisionComponent* collComp, char* activeFlag, unsigned collisionGroup)
|
||||||
|
{
|
||||||
|
CollisionComponent = collComp;
|
||||||
|
ActiveFlagPtr = activeFlag;
|
||||||
|
CollisionGroup = collisionGroup;
|
||||||
|
ProcessedFlag = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TEdgeSegment::port_draw()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
TEdgeSegment* TEdgeSegment::install_wall(float* floatArr, TCollisionComponent* collComp, char* activeFlagPtr,
|
||||||
|
unsigned int collisionGroup, float offset, size_t wallValue)
|
||||||
|
{
|
||||||
|
vector2 center{}, start{}, end{}, prevCenter{};
|
||||||
|
vector3 vec1{}, vec2{}, dstVec{};
|
||||||
|
TEdgeSegment* edge = nullptr;
|
||||||
|
|
||||||
|
wall_type wallType = static_cast<wall_type>(static_cast<int>(floor(*floatArr) - 1.0f));
|
||||||
|
switch (wallType)
|
||||||
|
{
|
||||||
|
case wall_type::Circle:
|
||||||
|
{
|
||||||
|
center.X = floatArr[1];
|
||||||
|
center.Y = floatArr[2];
|
||||||
|
auto radius = offset + floatArr[3];
|
||||||
|
auto circle = new TCircle(collComp, activeFlagPtr, collisionGroup, ¢er, radius);
|
||||||
|
circle->WallValue = reinterpret_cast<void*>(wallValue);
|
||||||
|
circle->place_in_grid(&collComp->AABB);
|
||||||
|
collComp->EdgeList.push_back(circle);
|
||||||
|
edge = circle;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case wall_type::Line:
|
||||||
|
{
|
||||||
|
start.X = floatArr[1];
|
||||||
|
start.Y = floatArr[2];
|
||||||
|
end.X = floatArr[3];
|
||||||
|
end.Y = floatArr[4];
|
||||||
|
auto line = new TLine(collComp, activeFlagPtr, collisionGroup, start, end);
|
||||||
|
line->WallValue = reinterpret_cast<void*>(wallValue);
|
||||||
|
line->Offset(offset);
|
||||||
|
line->place_in_grid(&collComp->AABB);
|
||||||
|
collComp->EdgeList.push_back(line);
|
||||||
|
edge = line;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
int wallTypeI = static_cast<int>(wallType);
|
||||||
|
auto floatArrPtr = floatArr + 1;
|
||||||
|
prevCenter.X = floatArr[2 * wallTypeI - 1];
|
||||||
|
prevCenter.Y = floatArr[2 * wallTypeI];
|
||||||
|
|
||||||
|
for (int index = 0; index < wallTypeI; index++, floatArrPtr += 2)
|
||||||
|
{
|
||||||
|
float centerX2, centerY2;
|
||||||
|
if (index >= wallTypeI - 1)
|
||||||
|
{
|
||||||
|
centerX2 = floatArr[1];
|
||||||
|
centerY2 = floatArr[2];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
centerX2 = floatArrPtr[2];
|
||||||
|
centerY2 = floatArrPtr[3];
|
||||||
|
}
|
||||||
|
auto centerX1 = floatArrPtr[0];
|
||||||
|
auto centerY1 = floatArrPtr[1];
|
||||||
|
|
||||||
|
center.X = centerX1;
|
||||||
|
center.Y = centerY1;
|
||||||
|
if (offset != 0.0f)
|
||||||
|
{
|
||||||
|
vec1.X = centerX1 - prevCenter.X;
|
||||||
|
vec1.Y = center.Y - prevCenter.Y;
|
||||||
|
vec2.X = centerX2 - centerX1;
|
||||||
|
vec2.Y = centerY2 - center.Y;
|
||||||
|
maths::cross(vec1, vec2, dstVec);
|
||||||
|
if ((dstVec.Z > 0.0f && offset > 0.0f) ||
|
||||||
|
(dstVec.Z < 0.0f && offset < 0.0f))
|
||||||
|
{
|
||||||
|
float radius = offset * 1.001f;
|
||||||
|
auto circle = new TCircle(collComp, activeFlagPtr, collisionGroup, ¢er, radius);
|
||||||
|
circle->WallValue = reinterpret_cast<void*>(wallValue);
|
||||||
|
circle->place_in_grid(&collComp->AABB);
|
||||||
|
collComp->EdgeList.push_back(circle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
start.X = floatArrPtr[0];
|
||||||
|
start.Y = floatArrPtr[1];
|
||||||
|
end.X = floatArrPtr[2];
|
||||||
|
end.Y = floatArrPtr[3];
|
||||||
|
auto line = new TLine(collComp, activeFlagPtr, collisionGroup, start, end);
|
||||||
|
line->WallValue = reinterpret_cast<void*>(wallValue);
|
||||||
|
line->Offset(offset);
|
||||||
|
line->place_in_grid(&collComp->AABB);
|
||||||
|
collComp->EdgeList.push_back(line);
|
||||||
|
|
||||||
|
edge = line;
|
||||||
|
prevCenter = center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return edge;
|
||||||
|
}
|
||||||
33
SpaceCadetPinball/TEdgeSegment.h
Normal file
33
SpaceCadetPinball/TEdgeSegment.h
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
class TBall;
|
||||||
|
class TCollisionComponent;
|
||||||
|
struct ray_type;
|
||||||
|
struct RectF;
|
||||||
|
|
||||||
|
enum class wall_type : int
|
||||||
|
{
|
||||||
|
Circle = 0,
|
||||||
|
Line = 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
class TEdgeSegment
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
TCollisionComponent* CollisionComponent;
|
||||||
|
char* ActiveFlagPtr;
|
||||||
|
char ProcessedFlag;
|
||||||
|
void* WallValue{};
|
||||||
|
unsigned int CollisionGroup;
|
||||||
|
|
||||||
|
TEdgeSegment(TCollisionComponent* collComp, char* activeFlag, unsigned int collisionGroup);
|
||||||
|
virtual ~TEdgeSegment() = default;
|
||||||
|
|
||||||
|
virtual void EdgeCollision(TBall* ball, float distance) = 0;
|
||||||
|
virtual void port_draw();
|
||||||
|
virtual void place_in_grid(RectF* aabb) = 0;
|
||||||
|
virtual float FindCollisionDistance(const ray_type& ray) = 0;
|
||||||
|
|
||||||
|
static TEdgeSegment* install_wall(float* floatArr, TCollisionComponent* collComp, char* activeFlagPtr,
|
||||||
|
unsigned int collisionGroup, float offset, size_t wallValue);
|
||||||
|
};
|
||||||
120
SpaceCadetPinball/TFlagSpinner.cpp
Normal file
120
SpaceCadetPinball/TFlagSpinner.cpp
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
#include "pch.h"
|
||||||
|
#include "TFlagSpinner.h"
|
||||||
|
|
||||||
|
|
||||||
|
#include "control.h"
|
||||||
|
#include "loader.h"
|
||||||
|
#include "render.h"
|
||||||
|
#include "TBall.h"
|
||||||
|
#include "timer.h"
|
||||||
|
#include "TLine.h"
|
||||||
|
#include "TPinballTable.h"
|
||||||
|
|
||||||
|
TFlagSpinner::TFlagSpinner(TPinballTable* table, int groupIndex) : TCollisionComponent(table, groupIndex, false)
|
||||||
|
{
|
||||||
|
visualStruct visual{};
|
||||||
|
vector2 end{}, start{};
|
||||||
|
|
||||||
|
Timer = 0;
|
||||||
|
loader::query_visual(groupIndex, 0, &visual);
|
||||||
|
end.X = visual.FloatArr[0];
|
||||||
|
end.Y = visual.FloatArr[1];
|
||||||
|
start.X = visual.FloatArr[2];
|
||||||
|
start.Y = visual.FloatArr[3];
|
||||||
|
auto line = new TLine(this, &ActiveFlag, visual.CollisionGroup, start, end);
|
||||||
|
if (line)
|
||||||
|
{
|
||||||
|
line->place_in_grid(&AABB);
|
||||||
|
EdgeList.push_back(line);
|
||||||
|
}
|
||||||
|
|
||||||
|
line = new TLine(this, &ActiveFlag, visual.CollisionGroup, end, start);
|
||||||
|
PrevCollider = line;
|
||||||
|
if (line)
|
||||||
|
{
|
||||||
|
line->place_in_grid(&AABB);
|
||||||
|
EdgeList.push_back(line);
|
||||||
|
}
|
||||||
|
|
||||||
|
SpeedDecrement = 0.64999998f;
|
||||||
|
MaxSpeed = 50000.0f;
|
||||||
|
MinSpeed = 5.0f;
|
||||||
|
auto speedDec = loader::query_float_attribute(groupIndex, 0, 1202);
|
||||||
|
if (speedDec)
|
||||||
|
SpeedDecrement = *speedDec;
|
||||||
|
auto maxSpeed = loader::query_float_attribute(groupIndex, 0, 1200);
|
||||||
|
if (maxSpeed)
|
||||||
|
MaxSpeed = *maxSpeed;
|
||||||
|
auto minSpeed = loader::query_float_attribute(groupIndex, 0, 1201);
|
||||||
|
if (minSpeed)
|
||||||
|
MinSpeed = *minSpeed;
|
||||||
|
}
|
||||||
|
|
||||||
|
int TFlagSpinner::Message(MessageCode code, float value)
|
||||||
|
{
|
||||||
|
if (code == MessageCode::Reset)
|
||||||
|
{
|
||||||
|
if (Timer)
|
||||||
|
{
|
||||||
|
timer::kill(Timer);
|
||||||
|
Timer = 0;
|
||||||
|
}
|
||||||
|
BmpIndex = 0;
|
||||||
|
SpriteSet(BmpIndex);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TFlagSpinner::Collision(TBall* ball, vector2* nextPosition, vector2* direction, float distance,
|
||||||
|
TEdgeSegment* edge)
|
||||||
|
{
|
||||||
|
ball->Position.X = nextPosition->X;
|
||||||
|
ball->Position.Y = nextPosition->Y;
|
||||||
|
ball->RayMaxDistance = ball->RayMaxDistance - distance;
|
||||||
|
ball->not_again(edge);
|
||||||
|
|
||||||
|
SpinDirection = 2 * (PrevCollider != edge) - 1;
|
||||||
|
if (ball->Speed == 0.0f)
|
||||||
|
Speed = MinSpeed;
|
||||||
|
else
|
||||||
|
Speed = ball->Speed * 20.0f;
|
||||||
|
if (Speed < MinSpeed)
|
||||||
|
Speed = MinSpeed;
|
||||||
|
if (Speed > MaxSpeed)
|
||||||
|
Speed = MaxSpeed;
|
||||||
|
NextFrame();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TFlagSpinner::NextFrame()
|
||||||
|
{
|
||||||
|
BmpIndex += SpinDirection;
|
||||||
|
int bmpIndex = BmpIndex;
|
||||||
|
int bmpCount = static_cast<int>(ListBitmap->size());
|
||||||
|
if (bmpIndex >= bmpCount)
|
||||||
|
BmpIndex = 0;
|
||||||
|
else if (bmpIndex < 0)
|
||||||
|
BmpIndex = bmpCount - 1;
|
||||||
|
|
||||||
|
if (!PinballTable->TiltLockFlag)
|
||||||
|
{
|
||||||
|
control::handler(MessageCode::ControlCollision, this);
|
||||||
|
if (SoftHitSoundId)
|
||||||
|
loader::play_sound(SoftHitSoundId, this, "TFlagSpinner");
|
||||||
|
if (!BmpIndex)
|
||||||
|
control::handler(MessageCode::ControlSpinnerLoopReset, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
SpriteSet(BmpIndex);
|
||||||
|
Speed *= SpeedDecrement;
|
||||||
|
if (Speed >= MinSpeed)
|
||||||
|
{
|
||||||
|
timer::set(1.0f / Speed, this, SpinTimer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TFlagSpinner::SpinTimer(int timerId, void* caller)
|
||||||
|
{
|
||||||
|
auto spinner = static_cast<TFlagSpinner*>(caller);
|
||||||
|
spinner->Timer = 0;
|
||||||
|
spinner->NextFrame();
|
||||||
|
}
|
||||||
25
SpaceCadetPinball/TFlagSpinner.h
Normal file
25
SpaceCadetPinball/TFlagSpinner.h
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "TCollisionComponent.h"
|
||||||
|
|
||||||
|
class TFlagSpinner :
|
||||||
|
public TCollisionComponent
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
TFlagSpinner(TPinballTable* table, int groupIndex);
|
||||||
|
int Message(MessageCode code, float value) override;
|
||||||
|
void Collision(TBall* ball, vector2* nextPosition, vector2* direction, float distance,
|
||||||
|
TEdgeSegment* edge) override;
|
||||||
|
void NextFrame();
|
||||||
|
|
||||||
|
static void SpinTimer(int timerId, void* caller);
|
||||||
|
|
||||||
|
float Speed{};
|
||||||
|
float MaxSpeed;
|
||||||
|
float MinSpeed;
|
||||||
|
float SpeedDecrement;
|
||||||
|
int SpinDirection{};
|
||||||
|
int BmpIndex{};
|
||||||
|
int Timer;
|
||||||
|
TEdgeSegment* PrevCollider;
|
||||||
|
};
|
||||||
|
|
||||||
203
SpaceCadetPinball/TFlipper.cpp
Normal file
203
SpaceCadetPinball/TFlipper.cpp
Normal file
@ -0,0 +1,203 @@
|
|||||||
|
#include "pch.h"
|
||||||
|
#include "TFlipper.h"
|
||||||
|
|
||||||
|
|
||||||
|
#include "control.h"
|
||||||
|
#include "loader.h"
|
||||||
|
#include "pb.h"
|
||||||
|
#include "render.h"
|
||||||
|
#include "TBall.h"
|
||||||
|
#include "TFlipperEdge.h"
|
||||||
|
#include "timer.h"
|
||||||
|
#include "TPinballTable.h"
|
||||||
|
|
||||||
|
TFlipper::TFlipper(TPinballTable* table, int groupIndex) : TCollisionComponent(table, groupIndex, false)
|
||||||
|
{
|
||||||
|
visualStruct visual{};
|
||||||
|
|
||||||
|
loader::query_visual(groupIndex, 0, &visual);
|
||||||
|
HardHitSoundId = visual.SoundIndex4;
|
||||||
|
SoftHitSoundId = visual.SoundIndex3;
|
||||||
|
Elasticity = visual.Elasticity;
|
||||||
|
Smoothness = visual.Smoothness;
|
||||||
|
|
||||||
|
auto collMult = *loader::query_float_attribute(groupIndex, 0, 803);
|
||||||
|
auto retractTime = *loader::query_float_attribute(groupIndex, 0, 805);
|
||||||
|
auto extendTime = *loader::query_float_attribute(groupIndex, 0, 804);
|
||||||
|
auto vecT2 = reinterpret_cast<vector3*>(loader::query_float_attribute(groupIndex, 0, 802));
|
||||||
|
auto vecT1 = reinterpret_cast<vector3*>(loader::query_float_attribute(groupIndex, 0, 801));
|
||||||
|
auto origin = reinterpret_cast<vector3*>(loader::query_float_attribute(groupIndex, 0, 800));
|
||||||
|
auto flipperEdge = new TFlipperEdge(
|
||||||
|
this,
|
||||||
|
&ActiveFlag,
|
||||||
|
visual.CollisionGroup,
|
||||||
|
table,
|
||||||
|
origin,
|
||||||
|
vecT1,
|
||||||
|
vecT2,
|
||||||
|
extendTime,
|
||||||
|
retractTime,
|
||||||
|
collMult,
|
||||||
|
Elasticity,
|
||||||
|
Smoothness);
|
||||||
|
flipperEdge->place_in_grid(&AABB);
|
||||||
|
|
||||||
|
FlipperEdge = flipperEdge;
|
||||||
|
BmpIndex = 0;
|
||||||
|
if (table)
|
||||||
|
table->FlipperList.push_back(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
TFlipper::~TFlipper()
|
||||||
|
{
|
||||||
|
delete FlipperEdge;
|
||||||
|
if (PinballTable)
|
||||||
|
{
|
||||||
|
auto& flippers = PinballTable->FlipperList;
|
||||||
|
auto position = std::find(flippers.begin(), flippers.end(), this);
|
||||||
|
if (position != flippers.end())
|
||||||
|
flippers.erase(position);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int TFlipper::Message(MessageCode code, float value)
|
||||||
|
{
|
||||||
|
switch (code)
|
||||||
|
{
|
||||||
|
case MessageCode::TFlipperExtend:
|
||||||
|
case MessageCode::TFlipperRetract:
|
||||||
|
case MessageCode::Resume:
|
||||||
|
case MessageCode::LooseFocus:
|
||||||
|
case MessageCode::SetTiltLock:
|
||||||
|
case MessageCode::GameOver:
|
||||||
|
if (code == MessageCode::TFlipperExtend)
|
||||||
|
{
|
||||||
|
control::handler(MessageCode::TFlipperExtend, this);
|
||||||
|
loader::play_sound(HardHitSoundId, this, "TFlipper1");
|
||||||
|
}
|
||||||
|
else if (code == MessageCode::TFlipperRetract)
|
||||||
|
{
|
||||||
|
loader::play_sound(SoftHitSoundId, this, "TFlipper2");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Retract for all non-input messages
|
||||||
|
code = MessageCode::TFlipperRetract;
|
||||||
|
}
|
||||||
|
|
||||||
|
MessageField = FlipperEdge->SetMotion(code);
|
||||||
|
break;
|
||||||
|
case MessageCode::PlayerChanged:
|
||||||
|
case MessageCode::Reset:
|
||||||
|
if (MessageField)
|
||||||
|
{
|
||||||
|
FlipperEdge->CurrentAngle = 0;
|
||||||
|
FlipperEdge->set_control_points(0);
|
||||||
|
MessageField = 0;
|
||||||
|
FlipperEdge->SetMotion(MessageCode::Reset);
|
||||||
|
UpdateSprite();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TFlipper::port_draw()
|
||||||
|
{
|
||||||
|
FlipperEdge->port_draw();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TFlipper::Collision(TBall* ball, vector2* nextPosition, vector2* direction, float distance, TEdgeSegment* edge)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void TFlipper::UpdateSprite()
|
||||||
|
{
|
||||||
|
auto bmpCountSub1 = static_cast<int>(ListBitmap->size()) - 1;
|
||||||
|
auto newBmpIndex = static_cast<int>(floor(FlipperEdge->CurrentAngle / FlipperEdge->AngleMax * bmpCountSub1 + 0.5f));
|
||||||
|
newBmpIndex = Clamp(newBmpIndex, 0, bmpCountSub1);
|
||||||
|
if (BmpIndex == newBmpIndex)
|
||||||
|
return;
|
||||||
|
|
||||||
|
BmpIndex = newBmpIndex;
|
||||||
|
SpriteSet(BmpIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
int TFlipper::GetFlipperStepAngle(float dt, float* dst) const
|
||||||
|
{
|
||||||
|
if (!MessageField)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
auto deltaAngle = FlipperEdge->flipper_angle_delta(dt);
|
||||||
|
auto step = std::fabs(std::ceil(FlipperEdge->DistanceDiv * deltaAngle * FlipperEdge->InvT1Radius));
|
||||||
|
if (step > 3.0f)
|
||||||
|
step = 3.0f;
|
||||||
|
if (step >= 2.0f)
|
||||||
|
{
|
||||||
|
*dst = deltaAngle / step;
|
||||||
|
return static_cast<int>(step);
|
||||||
|
}
|
||||||
|
|
||||||
|
*dst = deltaAngle;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TFlipper::FlipperCollision(float deltaAngle)
|
||||||
|
{
|
||||||
|
if (!MessageField)
|
||||||
|
return;
|
||||||
|
|
||||||
|
ray_type ray{}, rayDst{};
|
||||||
|
ray.MinDistance = 0.002f;
|
||||||
|
bool collisionFlag = false;
|
||||||
|
for (auto ball : pb::MainTable->BallList)
|
||||||
|
{
|
||||||
|
if ((FlipperEdge->CollisionGroup & ball->CollisionMask) != 0 &&
|
||||||
|
FlipperEdge->YMax >= ball->Position.Y && FlipperEdge->YMin <= ball->Position.Y &&
|
||||||
|
FlipperEdge->XMax >= ball->Position.X && FlipperEdge->XMin <= ball->Position.X)
|
||||||
|
{
|
||||||
|
if (FlipperEdge->ControlPointDirtyFlag)
|
||||||
|
FlipperEdge->set_control_points(FlipperEdge->CurrentAngle);
|
||||||
|
ray.CollisionMask = ball->CollisionMask;
|
||||||
|
ray.Origin = ball->Position;
|
||||||
|
|
||||||
|
float sin, cos;
|
||||||
|
auto ballPosRot = ray.Origin;
|
||||||
|
maths::SinCos(-deltaAngle, sin, cos);
|
||||||
|
maths::RotatePt(ballPosRot, sin, cos, FlipperEdge->RotOrigin);
|
||||||
|
ray.Direction.X = ballPosRot.X - ray.Origin.X;
|
||||||
|
ray.Direction.Y = ballPosRot.Y - ray.Origin.Y;
|
||||||
|
ray.MaxDistance = maths::normalize_2d(ray.Direction);
|
||||||
|
auto distance = maths::distance_to_flipper(FlipperEdge, ray, rayDst);
|
||||||
|
if (distance < 1e9f)
|
||||||
|
{
|
||||||
|
FlipperEdge->NextBallPosition = ball->Position;
|
||||||
|
FlipperEdge->CollisionDirection = rayDst.Direction;
|
||||||
|
FlipperEdge->EdgeCollision(ball, distance);
|
||||||
|
collisionFlag = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (collisionFlag)
|
||||||
|
{
|
||||||
|
auto angleAdvance = deltaAngle / (std::fabs(FlipperEdge->MoveSpeed) * 5.0f);
|
||||||
|
FlipperEdge->CurrentAngle -= angleAdvance;
|
||||||
|
FlipperEdge->AngleRemainder += std::fabs(angleAdvance);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
FlipperEdge->CurrentAngle += deltaAngle;
|
||||||
|
FlipperEdge->AngleRemainder -= std::fabs(deltaAngle);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (FlipperEdge->AngleRemainder <= 0.0001f)
|
||||||
|
{
|
||||||
|
FlipperEdge->CurrentAngle = FlipperEdge->AngleDst;
|
||||||
|
FlipperEdge->FlipperFlag = MessageCode::TFlipperNull;
|
||||||
|
MessageField = 0;
|
||||||
|
}
|
||||||
|
FlipperEdge->ControlPointDirtyFlag = true;
|
||||||
|
}
|
||||||
22
SpaceCadetPinball/TFlipper.h
Normal file
22
SpaceCadetPinball/TFlipper.h
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "TCollisionComponent.h"
|
||||||
|
|
||||||
|
class TFlipperEdge;
|
||||||
|
|
||||||
|
class TFlipper :
|
||||||
|
public TCollisionComponent
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
TFlipper(TPinballTable* table, int groupIndex);
|
||||||
|
~TFlipper() override;
|
||||||
|
int Message(MessageCode code, float value) override;
|
||||||
|
void port_draw() override;
|
||||||
|
void Collision(TBall* ball, vector2* nextPosition, vector2* direction, float distance,
|
||||||
|
TEdgeSegment* edge) override;
|
||||||
|
void UpdateSprite();
|
||||||
|
int GetFlipperStepAngle(float dt, float* dst) const;
|
||||||
|
void FlipperCollision(float deltaAngle);
|
||||||
|
|
||||||
|
int BmpIndex;
|
||||||
|
TFlipperEdge* FlipperEdge;
|
||||||
|
};
|
||||||
273
SpaceCadetPinball/TFlipperEdge.cpp
Normal file
273
SpaceCadetPinball/TFlipperEdge.cpp
Normal file
@ -0,0 +1,273 @@
|
|||||||
|
#include "pch.h"
|
||||||
|
#include "TFlipperEdge.h"
|
||||||
|
|
||||||
|
|
||||||
|
#include "pb.h"
|
||||||
|
#include "TLine.h"
|
||||||
|
#include "TPinballTable.h"
|
||||||
|
#include "TTableLayer.h"
|
||||||
|
|
||||||
|
|
||||||
|
TFlipperEdge::TFlipperEdge(TCollisionComponent* collComp, char* activeFlag, unsigned int collisionGroup,
|
||||||
|
TPinballTable* table,
|
||||||
|
vector3* origin, vector3* vecT1, vector3* vecT2, float extendSpeed, float retractSpeed,
|
||||||
|
float collMult, float elasticity, float smoothness): TEdgeSegment(
|
||||||
|
collComp, activeFlag, collisionGroup)
|
||||||
|
{
|
||||||
|
vector3 crossProd{}, vecOriginT1{}, vecOriginT2{};
|
||||||
|
|
||||||
|
Elasticity = elasticity;
|
||||||
|
Smoothness = smoothness;
|
||||||
|
CollisionMult = collMult;
|
||||||
|
|
||||||
|
T1Src = static_cast<vector2>(*vecT1);
|
||||||
|
T2Src = static_cast<vector2>(*vecT2);
|
||||||
|
RotOrigin.X = origin->X;
|
||||||
|
RotOrigin.Y = origin->Y;
|
||||||
|
|
||||||
|
CirclebaseRadius = origin->Z + table->CollisionCompOffset;
|
||||||
|
CirclebaseRadiusMSq = CirclebaseRadius * 1.01f * (CirclebaseRadius * 1.01f);
|
||||||
|
CirclebaseRadiusSq = CirclebaseRadius * CirclebaseRadius;
|
||||||
|
|
||||||
|
CircleT1Radius = vecT1->Z + table->CollisionCompOffset;
|
||||||
|
CircleT1RadiusMSq = CircleT1Radius * 1.01f * (CircleT1Radius * 1.01f);
|
||||||
|
CircleT1RadiusSq = CircleT1Radius * CircleT1Radius;
|
||||||
|
|
||||||
|
vecOriginT1.X = vecT1->X - origin->X;
|
||||||
|
vecOriginT1.Y = vecT1->Y - origin->Y;
|
||||||
|
vecOriginT1.Z = 0.0;
|
||||||
|
maths::normalize_2d(vecOriginT1);
|
||||||
|
|
||||||
|
vecOriginT2.X = vecT2->X - origin->X;
|
||||||
|
vecOriginT2.Y = vecT2->Y - origin->Y;
|
||||||
|
vecOriginT2.Z = 0.0;
|
||||||
|
maths::normalize_2d(vecOriginT2);
|
||||||
|
|
||||||
|
AngleMax = acos(maths::DotProduct(vecOriginT1, vecOriginT2));
|
||||||
|
maths::cross(vecOriginT1, vecOriginT2, crossProd);
|
||||||
|
if (crossProd.Z < 0.0f)
|
||||||
|
AngleMax = -AngleMax;
|
||||||
|
|
||||||
|
FlipperFlag = MessageCode::TFlipperNull;
|
||||||
|
AngleDst = 0.0;
|
||||||
|
|
||||||
|
// 3DPB and FT have different formats for flipper speed:
|
||||||
|
// 3DPB: Time it takes for flipper to go from source to destination, in sec.
|
||||||
|
// FT: Flipper movement speed, in radians per sec.
|
||||||
|
if (!pb::FullTiltMode)
|
||||||
|
{
|
||||||
|
auto angleMax = std::abs(AngleMax);
|
||||||
|
retractSpeed = angleMax / retractSpeed;
|
||||||
|
extendSpeed = angleMax / extendSpeed;
|
||||||
|
}
|
||||||
|
ExtendSpeed = extendSpeed;
|
||||||
|
RetractSpeed = retractSpeed;
|
||||||
|
|
||||||
|
const vector2 perpOriginT1Cc = {-vecOriginT1.Y, vecOriginT1.X};
|
||||||
|
A2Src.X = perpOriginT1Cc.X * CirclebaseRadius + origin->X;
|
||||||
|
A2Src.Y = perpOriginT1Cc.Y * CirclebaseRadius + origin->Y;
|
||||||
|
A1Src.X = perpOriginT1Cc.X * CircleT1Radius + vecT1->X;
|
||||||
|
A1Src.Y = perpOriginT1Cc.Y * CircleT1Radius + vecT1->Y;
|
||||||
|
|
||||||
|
const vector2 perpOriginT1C = {vecOriginT1.Y, -vecOriginT1.X};
|
||||||
|
B1Src.X = perpOriginT1C.X * CirclebaseRadius + origin->X;
|
||||||
|
B1Src.Y = perpOriginT1C.Y * CirclebaseRadius + origin->Y;
|
||||||
|
B2Src.X = perpOriginT1C.X * CircleT1Radius + vecT1->X;
|
||||||
|
B2Src.Y = perpOriginT1C.Y * CircleT1Radius + vecT1->Y;
|
||||||
|
|
||||||
|
if (AngleMax < 0.0f)
|
||||||
|
{
|
||||||
|
std::swap(A1Src, B1Src);
|
||||||
|
std::swap(A2Src, B2Src);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto dx = vecT1->X - RotOrigin.X;
|
||||||
|
auto dy = vecT1->Y - RotOrigin.Y;
|
||||||
|
auto distance1 = sqrt(dy * dy + dx * dx) + table->CollisionCompOffset + vecT1->Z;
|
||||||
|
DistanceDiv = distance1;
|
||||||
|
DistanceDivSq = distance1 * distance1;
|
||||||
|
InvT1Radius = 1.0f / CircleT1Radius * 1.5f;
|
||||||
|
|
||||||
|
if (AngleMax <= 0.0f)
|
||||||
|
{
|
||||||
|
ExtendSpeed = -ExtendSpeed;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
RetractSpeed = -RetractSpeed;
|
||||||
|
}
|
||||||
|
set_control_points(CurrentAngle);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TFlipperEdge::port_draw()
|
||||||
|
{
|
||||||
|
set_control_points(CurrentAngle);
|
||||||
|
}
|
||||||
|
|
||||||
|
float TFlipperEdge::FindCollisionDistance(const ray_type& ray)
|
||||||
|
{
|
||||||
|
ray_type dstRay{};
|
||||||
|
if (ControlPointDirtyFlag)
|
||||||
|
set_control_points(CurrentAngle);
|
||||||
|
auto distance = maths::distance_to_flipper(this, ray, dstRay);
|
||||||
|
if (distance >= 1e9f)
|
||||||
|
return 1e9f;
|
||||||
|
|
||||||
|
NextBallPosition = dstRay.Origin;
|
||||||
|
CollisionDirection = dstRay.Direction;
|
||||||
|
return distance;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TFlipperEdge::EdgeCollision(TBall* ball, float distance)
|
||||||
|
{
|
||||||
|
if (FlipperFlag == MessageCode::TFlipperNull)
|
||||||
|
{
|
||||||
|
maths::basic_collision(
|
||||||
|
ball,
|
||||||
|
&NextBallPosition,
|
||||||
|
&CollisionDirection,
|
||||||
|
Elasticity,
|
||||||
|
Smoothness,
|
||||||
|
1e9f,
|
||||||
|
0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
vector2 t1NextPos{NextBallPosition.X - T1.X, NextBallPosition.Y - T1.Y},
|
||||||
|
t1RotOrigin{RotOrigin.X - T1.X, RotOrigin.Y - T1.Y};
|
||||||
|
auto crossProduct = maths::cross(t1RotOrigin, t1NextPos);
|
||||||
|
|
||||||
|
bool frontCollision = false;
|
||||||
|
if (crossProduct <= 0)
|
||||||
|
{
|
||||||
|
if (AngleMax > 0)
|
||||||
|
frontCollision = true;
|
||||||
|
}
|
||||||
|
else if (AngleMax <= 0)
|
||||||
|
{
|
||||||
|
frontCollision = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (FlipperFlag == MessageCode::TFlipperRetract)
|
||||||
|
{
|
||||||
|
frontCollision ^= true;
|
||||||
|
CollisionLinePerp = LineB.PerpendicularC;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
CollisionLinePerp = LineA.PerpendicularC;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto dx = NextBallPosition.X - RotOrigin.X;
|
||||||
|
auto dy = NextBallPosition.Y - RotOrigin.Y;
|
||||||
|
auto distanceSq = dy * dy + dx * dx;
|
||||||
|
if (frontCollision)
|
||||||
|
{
|
||||||
|
float boost = 0;
|
||||||
|
if (circlebase.RadiusSq * 1.01f < distanceSq)
|
||||||
|
{
|
||||||
|
auto v21 = std::fabs(MoveSpeed) * std::sqrt(distanceSq / DistanceDivSq);
|
||||||
|
auto dot1 = maths::DotProduct(CollisionLinePerp, CollisionDirection);
|
||||||
|
if (dot1 >= 0)
|
||||||
|
boost = CollisionMult * dot1 * v21;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto threshold = boost <= 0.0f ? 1e9f : -1.0f;
|
||||||
|
maths::basic_collision(
|
||||||
|
ball,
|
||||||
|
&NextBallPosition,
|
||||||
|
&CollisionDirection,
|
||||||
|
Elasticity,
|
||||||
|
Smoothness,
|
||||||
|
threshold,
|
||||||
|
boost);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
auto elasticity = Elasticity;
|
||||||
|
if (circlebase.RadiusSq * 1.01f < distanceSq)
|
||||||
|
elasticity = (1.0f - std::sqrt(distanceSq / DistanceDivSq)) * Elasticity;
|
||||||
|
maths::basic_collision(ball, &NextBallPosition, &CollisionDirection, elasticity, Smoothness, 1e9f, 0.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TFlipperEdge::place_in_grid(RectF* aabb)
|
||||||
|
{
|
||||||
|
auto xMax = std::max(std::max(T2Src.X + CircleT1Radius, T1Src.X + CircleT1Radius), RotOrigin.X + CirclebaseRadius);
|
||||||
|
auto yMax = std::max(std::max(T2Src.Y + CircleT1Radius, T1Src.Y + CircleT1Radius), RotOrigin.Y + CirclebaseRadius);
|
||||||
|
auto xMin = std::min(std::min(T2Src.X - CircleT1Radius, T1Src.X - CircleT1Radius), RotOrigin.X - CirclebaseRadius);
|
||||||
|
auto yMin = std::min(std::min(T2Src.Y - CircleT1Radius, T1Src.Y - CircleT1Radius), RotOrigin.Y - CirclebaseRadius);
|
||||||
|
|
||||||
|
if (aabb)
|
||||||
|
{
|
||||||
|
aabb->Merge({xMax, yMax, xMin, yMin});
|
||||||
|
}
|
||||||
|
|
||||||
|
TTableLayer::edges_insert_square(yMin, xMin, yMax, xMax, this, nullptr);
|
||||||
|
|
||||||
|
auto offset = 1.0f / InvT1Radius + pb::BallHalfRadius;
|
||||||
|
XMin = xMin - offset;
|
||||||
|
YMin = yMin - offset;
|
||||||
|
XMax = xMax + offset;
|
||||||
|
YMax = yMax + offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TFlipperEdge::set_control_points(float angle)
|
||||||
|
{
|
||||||
|
float sin, cos;
|
||||||
|
maths::SinCos(angle, sin, cos);
|
||||||
|
A1 = A1Src;
|
||||||
|
A2 = A2Src;
|
||||||
|
B1 = B1Src;
|
||||||
|
B2 = B2Src;
|
||||||
|
T1 = T1Src;
|
||||||
|
maths::RotatePt(A1, sin, cos, RotOrigin);
|
||||||
|
maths::RotatePt(A2, sin, cos, RotOrigin);
|
||||||
|
maths::RotatePt(T1, sin, cos, RotOrigin);
|
||||||
|
maths::RotatePt(B1, sin, cos, RotOrigin);
|
||||||
|
maths::RotatePt(B2, sin, cos, RotOrigin);
|
||||||
|
maths::line_init(LineA, A1.X, A1.Y, A2.X, A2.Y);
|
||||||
|
maths::line_init(LineB, B1.X, B1.Y, B2.X, B2.Y);
|
||||||
|
circlebase = {RotOrigin, CirclebaseRadiusSq};
|
||||||
|
circleT1 = {T1, CircleT1RadiusSq};
|
||||||
|
ControlPointDirtyFlag = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
float TFlipperEdge::flipper_angle_delta(float timeDelta)
|
||||||
|
{
|
||||||
|
if (FlipperFlag == MessageCode::TFlipperNull)
|
||||||
|
return 0.0f;
|
||||||
|
|
||||||
|
const auto deltaAngle = MoveSpeed * timeDelta;
|
||||||
|
if (std::fabs(deltaAngle) > AngleRemainder)
|
||||||
|
return AngleDst - CurrentAngle;
|
||||||
|
return deltaAngle;
|
||||||
|
}
|
||||||
|
|
||||||
|
int TFlipperEdge::SetMotion(MessageCode code)
|
||||||
|
{
|
||||||
|
switch (code)
|
||||||
|
{
|
||||||
|
case MessageCode::TFlipperExtend:
|
||||||
|
AngleRemainder = std::fabs(AngleMax - CurrentAngle);
|
||||||
|
AngleDst = AngleMax;
|
||||||
|
MoveSpeed = ExtendSpeed;
|
||||||
|
break;
|
||||||
|
case MessageCode::TFlipperRetract:
|
||||||
|
AngleRemainder = std::fabs(CurrentAngle);
|
||||||
|
AngleDst = 0.0f;
|
||||||
|
MoveSpeed = RetractSpeed;
|
||||||
|
break;
|
||||||
|
case MessageCode::Reset:
|
||||||
|
AngleRemainder = 0.0f;
|
||||||
|
AngleDst = 0.0f;
|
||||||
|
break;
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (AngleRemainder == 0.0f)
|
||||||
|
code = MessageCode::TFlipperNull;
|
||||||
|
|
||||||
|
FlipperFlag = code;
|
||||||
|
return static_cast<int>(code);
|
||||||
|
}
|
||||||
56
SpaceCadetPinball/TFlipperEdge.h
Normal file
56
SpaceCadetPinball/TFlipperEdge.h
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "maths.h"
|
||||||
|
#include "TEdgeSegment.h"
|
||||||
|
#include "TPinballComponent.h"
|
||||||
|
|
||||||
|
class TPinballTable;
|
||||||
|
|
||||||
|
class TFlipperEdge : public TEdgeSegment
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
TFlipperEdge(TCollisionComponent* collComp, char* activeFlag, unsigned int collisionGroup, TPinballTable* table,
|
||||||
|
vector3* origin, vector3* vecT1, vector3* vecT2, float extendSpeed, float retractSpeed, float collMult,
|
||||||
|
float elasticity, float smoothness);
|
||||||
|
void port_draw() override;
|
||||||
|
float FindCollisionDistance(const ray_type& ray) override;
|
||||||
|
void EdgeCollision(TBall* ball, float distance) override;
|
||||||
|
void place_in_grid(RectF* aabb) override;
|
||||||
|
void set_control_points(float angle);
|
||||||
|
float flipper_angle_delta(float timeDelta);
|
||||||
|
int SetMotion(MessageCode code);
|
||||||
|
|
||||||
|
MessageCode FlipperFlag{};
|
||||||
|
float Elasticity;
|
||||||
|
float Smoothness;
|
||||||
|
vector2 RotOrigin{};
|
||||||
|
float CirclebaseRadius;
|
||||||
|
float CircleT1Radius;
|
||||||
|
float CirclebaseRadiusSq;
|
||||||
|
float CircleT1RadiusSq;
|
||||||
|
float CirclebaseRadiusMSq;
|
||||||
|
float CircleT1RadiusMSq;
|
||||||
|
float AngleMax;
|
||||||
|
float AngleRemainder{};
|
||||||
|
float AngleDst;
|
||||||
|
float CurrentAngle{};
|
||||||
|
vector2 CollisionLinePerp{};
|
||||||
|
vector2 A1Src{};
|
||||||
|
vector2 A2Src{};
|
||||||
|
vector2 B1Src{};
|
||||||
|
vector2 B2Src{};
|
||||||
|
float CollisionMult;
|
||||||
|
vector2 T1Src{};
|
||||||
|
vector2 T2Src{};
|
||||||
|
float DistanceDiv, DistanceDivSq;
|
||||||
|
vector2 CollisionDirection{};
|
||||||
|
float ExtendSpeed;
|
||||||
|
float RetractSpeed;
|
||||||
|
float MoveSpeed;
|
||||||
|
vector2 NextBallPosition{};
|
||||||
|
vector2 A1, A2, B1, B2, T1;
|
||||||
|
line_type LineA, LineB;
|
||||||
|
circle_type circlebase, circleT1;
|
||||||
|
float InvT1Radius;
|
||||||
|
float YMin, YMax, XMin, XMax;
|
||||||
|
bool ControlPointDirtyFlag{};
|
||||||
|
};
|
||||||
42
SpaceCadetPinball/TGate.cpp
Normal file
42
SpaceCadetPinball/TGate.cpp
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
#include "pch.h"
|
||||||
|
#include "TGate.h"
|
||||||
|
|
||||||
|
|
||||||
|
#include "control.h"
|
||||||
|
#include "loader.h"
|
||||||
|
#include "render.h"
|
||||||
|
|
||||||
|
TGate::TGate(TPinballTable* table, int groupIndex) : TCollisionComponent(table, groupIndex, true)
|
||||||
|
{
|
||||||
|
visualStruct visual{};
|
||||||
|
|
||||||
|
loader::query_visual(groupIndex, 0, &visual);
|
||||||
|
SoundIndex4 = visual.SoundIndex4;
|
||||||
|
SoundIndex3 = visual.SoundIndex3;
|
||||||
|
ActiveFlag = 1;
|
||||||
|
SpriteSet(0);
|
||||||
|
control::handler(MessageCode::Reset, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
int TGate::Message(MessageCode code, float value)
|
||||||
|
{
|
||||||
|
switch (code)
|
||||||
|
{
|
||||||
|
case MessageCode::TGateDisable:
|
||||||
|
ActiveFlag = 0;
|
||||||
|
SpriteSet(-1);
|
||||||
|
loader::play_sound(SoundIndex3, this, "TGate1");
|
||||||
|
break;
|
||||||
|
case MessageCode::Reset:
|
||||||
|
case MessageCode::TGateEnable:
|
||||||
|
ActiveFlag = 1;
|
||||||
|
SpriteSet(0);
|
||||||
|
if (code == MessageCode::TGateEnable)
|
||||||
|
loader::play_sound(SoundIndex4, this, "TGate2");
|
||||||
|
break;
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
|
||||||
|
control::handler(code, this);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
13
SpaceCadetPinball/TGate.h
Normal file
13
SpaceCadetPinball/TGate.h
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "TCollisionComponent.h"
|
||||||
|
|
||||||
|
class TGate :
|
||||||
|
public TCollisionComponent
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
TGate(TPinballTable* table, int groupIndex);
|
||||||
|
int Message(MessageCode code, float value) override;
|
||||||
|
|
||||||
|
int SoundIndex4;
|
||||||
|
int SoundIndex3;
|
||||||
|
};
|
||||||
148
SpaceCadetPinball/THole.cpp
Normal file
148
SpaceCadetPinball/THole.cpp
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
#include "pch.h"
|
||||||
|
#include "THole.h"
|
||||||
|
|
||||||
|
|
||||||
|
#include "control.h"
|
||||||
|
#include "loader.h"
|
||||||
|
#include "pb.h"
|
||||||
|
#include "TBall.h"
|
||||||
|
#include "timer.h"
|
||||||
|
#include "TPinballTable.h"
|
||||||
|
#include "TTableLayer.h"
|
||||||
|
|
||||||
|
THole::THole(TPinballTable* table, int groupIndex) : TCollisionComponent(table, groupIndex, false)
|
||||||
|
{
|
||||||
|
visualStruct visual{};
|
||||||
|
circle_type circle{};
|
||||||
|
|
||||||
|
Unknown4 = 0.050000001f;
|
||||||
|
MessageField = 0;
|
||||||
|
Timer = 0;
|
||||||
|
BallCapturedFlag = false;
|
||||||
|
Unknown3 = loader::query_float_attribute(groupIndex, 0, 407, 0.25f);
|
||||||
|
GravityMult = loader::query_float_attribute(groupIndex, 0, 701, 0.2f);
|
||||||
|
GravityPull = *loader::query_float_attribute(groupIndex, 0, 305);
|
||||||
|
|
||||||
|
loader::query_visual(groupIndex, 0, &visual);
|
||||||
|
Circle.Center.X = visual.FloatArr[0];
|
||||||
|
Circle.Center.Y = visual.FloatArr[1];
|
||||||
|
Circle.RadiusSq = *loader::query_float_attribute(groupIndex, 0, 306) * visual.FloatArr[2];
|
||||||
|
if (Circle.RadiusSq == 0.0f)
|
||||||
|
Circle.RadiusSq = 0.001f;
|
||||||
|
|
||||||
|
auto tCircle = new TCircle(this, &ActiveFlag, visual.CollisionGroup,
|
||||||
|
reinterpret_cast<vector3*>(visual.FloatArr),
|
||||||
|
Circle.RadiusSq);
|
||||||
|
tCircle->place_in_grid(&AABB);
|
||||||
|
EdgeList.push_back(tCircle);
|
||||||
|
|
||||||
|
ZSetValue = loader::query_float_attribute(groupIndex, 0, 408)[2];
|
||||||
|
CollisionMask = static_cast<int>(floor(*loader::query_float_attribute(groupIndex, 0, 1304)));
|
||||||
|
|
||||||
|
// CollisionMask difference: 3DPB - value given as is, FT: mask = 1 << value
|
||||||
|
if (pb::FullTiltMode)
|
||||||
|
CollisionMask = 1 << CollisionMask;
|
||||||
|
|
||||||
|
Circle.RadiusSq = visual.FloatArr[2] * visual.FloatArr[2];
|
||||||
|
circle.RadiusSq = Circle.RadiusSq;
|
||||||
|
circle.Center.X = Circle.Center.X;
|
||||||
|
circle.Center.Y = Circle.Center.Y;
|
||||||
|
|
||||||
|
Field.ActiveFlag = &ActiveFlag;
|
||||||
|
Field.CollisionComp = this;
|
||||||
|
Field.CollisionGroup = visual.CollisionGroup;
|
||||||
|
TTableLayer::edges_insert_circle(&circle, nullptr, &Field);
|
||||||
|
}
|
||||||
|
|
||||||
|
int THole::Message(MessageCode code, float value)
|
||||||
|
{
|
||||||
|
if (code == MessageCode::Reset && BallCapturedFlag)
|
||||||
|
{
|
||||||
|
if (Timer)
|
||||||
|
timer::kill(Timer);
|
||||||
|
Timer = 0;
|
||||||
|
BallCapturedSecondStage = true;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void THole::Collision(TBall* ball, vector2* nextPosition, vector2* direction, float distance, TEdgeSegment* edge)
|
||||||
|
{
|
||||||
|
if (!BallCapturedFlag)
|
||||||
|
{
|
||||||
|
BallCapturedSecondStage = false;
|
||||||
|
Threshold = 1e9f;
|
||||||
|
BallCapturedFlag = true;
|
||||||
|
ball->CollisionComp = this;
|
||||||
|
ball->Position.X = Circle.Center.X;
|
||||||
|
ball->Position.Y = Circle.Center.Y;
|
||||||
|
ball->Direction.Z = 0.0;
|
||||||
|
ball->CollisionDisabledFlag = true;
|
||||||
|
|
||||||
|
// Ramp hole has no delay in FT.
|
||||||
|
auto captureTime = pb::FullTiltMode ? 0 : 0.5f;
|
||||||
|
Timer = timer::set(captureTime, this, TimerExpired);
|
||||||
|
|
||||||
|
if (!PinballTable->TiltLockFlag)
|
||||||
|
{
|
||||||
|
loader::play_sound(HardHitSoundId, ball, "THole1");
|
||||||
|
control::handler(MessageCode::ControlBallCaptured, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
DefaultCollision(ball, nextPosition, direction);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int THole::FieldEffect(TBall* ball, vector2* vecDst)
|
||||||
|
{
|
||||||
|
int result;
|
||||||
|
vector2 direction{};
|
||||||
|
|
||||||
|
if (BallCapturedFlag)
|
||||||
|
{
|
||||||
|
if (BallCapturedSecondStage)
|
||||||
|
{
|
||||||
|
ball->Direction.Z -= PinballTable->GravityDirVectMult * ball->TimeDelta * GravityMult;
|
||||||
|
ball->Position.Z += ball->Direction.Z;
|
||||||
|
if (ball->Position.Z <= ZSetValue)
|
||||||
|
{
|
||||||
|
BallCapturedFlag = false;
|
||||||
|
BallCapturedSecondStage = false;
|
||||||
|
ball->Position.Z = ZSetValue;
|
||||||
|
ball->CollisionMask = CollisionMask;
|
||||||
|
ball->CollisionComp = nullptr;
|
||||||
|
ball->Direction = {};
|
||||||
|
ball->Speed = 0.0f;
|
||||||
|
loader::play_sound(SoftHitSoundId, ball, "THole2");
|
||||||
|
control::handler(MessageCode::ControlBallReleased, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result = 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
direction.X = Circle.Center.X - ball->Position.X;
|
||||||
|
direction.Y = Circle.Center.Y - ball->Position.Y;
|
||||||
|
if (direction.X * direction.X + direction.Y * direction.Y <= Circle.RadiusSq)
|
||||||
|
{
|
||||||
|
maths::normalize_2d(direction);
|
||||||
|
vecDst->X = direction.X * GravityPull - ball->Direction.X * ball->Speed;
|
||||||
|
vecDst->Y = direction.Y * GravityPull - ball->Direction.Y * ball->Speed;
|
||||||
|
result = 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
result = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void THole::TimerExpired(int timerId, void* caller)
|
||||||
|
{
|
||||||
|
auto hole = static_cast<THole*>(caller);
|
||||||
|
hole->Timer = 0;
|
||||||
|
hole->BallCapturedSecondStage = true;
|
||||||
|
}
|
||||||
29
SpaceCadetPinball/THole.h
Normal file
29
SpaceCadetPinball/THole.h
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "TCircle.h"
|
||||||
|
#include "TCollisionComponent.h"
|
||||||
|
#include "TEdgeManager.h"
|
||||||
|
|
||||||
|
class THole :
|
||||||
|
public TCollisionComponent
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
THole(TPinballTable* table, int groupIndex);
|
||||||
|
int Message(MessageCode code, float value) override;
|
||||||
|
void Collision(TBall* ball, vector2* nextPosition, vector2* direction, float distance,
|
||||||
|
TEdgeSegment* edge) override;
|
||||||
|
int FieldEffect(TBall* ball, vector2* vecDst) override;
|
||||||
|
|
||||||
|
static void TimerExpired(int timerId, void* caller);
|
||||||
|
|
||||||
|
bool BallCapturedFlag{};
|
||||||
|
bool BallCapturedSecondStage{};
|
||||||
|
int Timer;
|
||||||
|
float Unknown3;
|
||||||
|
float Unknown4;
|
||||||
|
float GravityMult;
|
||||||
|
float ZSetValue;
|
||||||
|
int CollisionMask;
|
||||||
|
float GravityPull;
|
||||||
|
circle_type Circle{};
|
||||||
|
field_effect_type Field{};
|
||||||
|
};
|
||||||
73
SpaceCadetPinball/TKickback.cpp
Normal file
73
SpaceCadetPinball/TKickback.cpp
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
#include "pch.h"
|
||||||
|
#include "TKickback.h"
|
||||||
|
|
||||||
|
|
||||||
|
#include "control.h"
|
||||||
|
#include "loader.h"
|
||||||
|
#include "maths.h"
|
||||||
|
#include "render.h"
|
||||||
|
#include "timer.h"
|
||||||
|
#include "TPinballTable.h"
|
||||||
|
|
||||||
|
TKickback::TKickback(TPinballTable* table, int groupIndex): TCollisionComponent(table, groupIndex, true)
|
||||||
|
{
|
||||||
|
MessageField = 0;
|
||||||
|
Timer = 0;
|
||||||
|
KickActiveFlag = 0;
|
||||||
|
TimerTime = 0.7f;
|
||||||
|
TimerTime2 = 0.1f;
|
||||||
|
Threshold = 1000000000.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
int TKickback::Message(MessageCode code, float value)
|
||||||
|
{
|
||||||
|
if ((code == MessageCode::SetTiltLock || code == MessageCode::Reset) && Timer)
|
||||||
|
{
|
||||||
|
timer::kill(Timer);
|
||||||
|
SpriteSet(-1);
|
||||||
|
Timer = 0;
|
||||||
|
KickActiveFlag = 0;
|
||||||
|
Threshold = 1000000000.0;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TKickback::Collision(TBall* ball, vector2* nextPosition, vector2* direction, float distance,
|
||||||
|
TEdgeSegment* edge)
|
||||||
|
{
|
||||||
|
if (PinballTable->TiltLockFlag)
|
||||||
|
{
|
||||||
|
maths::basic_collision(ball, nextPosition, direction, Elasticity, Smoothness, 0.0,
|
||||||
|
Boost);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (!KickActiveFlag)
|
||||||
|
{
|
||||||
|
Threshold = 1000000000.0;
|
||||||
|
KickActiveFlag = 1;
|
||||||
|
Timer = timer::set(TimerTime, this, TimerExpired);
|
||||||
|
}
|
||||||
|
if (DefaultCollision(ball, nextPosition, direction))
|
||||||
|
KickActiveFlag = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TKickback::TimerExpired(int timerId, void* caller)
|
||||||
|
{
|
||||||
|
auto kick = static_cast<TKickback*>(caller);
|
||||||
|
|
||||||
|
if (kick->KickActiveFlag)
|
||||||
|
{
|
||||||
|
kick->Threshold = 0.0;
|
||||||
|
kick->Timer = timer::set(kick->TimerTime2, kick, TimerExpired);
|
||||||
|
loader::play_sound(kick->HardHitSoundId, kick, "TKickback");
|
||||||
|
kick->SpriteSet(1);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
kick->SpriteSet(0);
|
||||||
|
kick->Timer = 0;
|
||||||
|
control::handler(MessageCode::ControlTimerExpired, kick);
|
||||||
|
}
|
||||||
|
}
|
||||||
19
SpaceCadetPinball/TKickback.h
Normal file
19
SpaceCadetPinball/TKickback.h
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "TCollisionComponent.h"
|
||||||
|
|
||||||
|
class TKickback :
|
||||||
|
public TCollisionComponent
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
TKickback(TPinballTable* table, int groupIndex);
|
||||||
|
int Message(MessageCode code, float value) override;
|
||||||
|
void Collision(TBall* ball, vector2* nextPosition, vector2* direction, float distance,
|
||||||
|
TEdgeSegment* edge) override;
|
||||||
|
|
||||||
|
static void TimerExpired(int timerId, void* caller);
|
||||||
|
|
||||||
|
float TimerTime;
|
||||||
|
float TimerTime2;
|
||||||
|
int Timer;
|
||||||
|
int KickActiveFlag;
|
||||||
|
};
|
||||||
169
SpaceCadetPinball/TKickout.cpp
Normal file
169
SpaceCadetPinball/TKickout.cpp
Normal file
@ -0,0 +1,169 @@
|
|||||||
|
#include "pch.h"
|
||||||
|
#include "TKickout.h"
|
||||||
|
|
||||||
|
|
||||||
|
#include "control.h"
|
||||||
|
#include "loader.h"
|
||||||
|
#include "pb.h"
|
||||||
|
#include "TBall.h"
|
||||||
|
#include "TCircle.h"
|
||||||
|
#include "timer.h"
|
||||||
|
#include "TPinballTable.h"
|
||||||
|
#include "TTableLayer.h"
|
||||||
|
|
||||||
|
TKickout::TKickout(TPinballTable* table, int groupIndex, bool someFlag): TCollisionComponent(
|
||||||
|
table, groupIndex, false)
|
||||||
|
{
|
||||||
|
visualStruct visual{};
|
||||||
|
circle_type circle{};
|
||||||
|
|
||||||
|
NotSomeFlag = !someFlag;
|
||||||
|
if (!someFlag)
|
||||||
|
ActiveFlag = 0;
|
||||||
|
TimerTime1 = 1.5;
|
||||||
|
TimerTime2 = 0.05f;
|
||||||
|
MessageField = 0;
|
||||||
|
Timer = 0;
|
||||||
|
BallCaputeredFlag = 0;
|
||||||
|
FieldMult = *loader::query_float_attribute(groupIndex, 0, 305);
|
||||||
|
loader::query_visual(groupIndex, 0, &visual);
|
||||||
|
SoftHitSoundId = visual.SoftHitSoundId;
|
||||||
|
HardHitSoundId = visual.Kicker.HardHitSoundId;
|
||||||
|
|
||||||
|
Circle.Center.X = visual.FloatArr[0];
|
||||||
|
Circle.Center.Y = visual.FloatArr[1];
|
||||||
|
Circle.RadiusSq = *loader::query_float_attribute(groupIndex, 0, 306) * visual.FloatArr[2];
|
||||||
|
if (Circle.RadiusSq == 0.0f)
|
||||||
|
Circle.RadiusSq = 0.001f;
|
||||||
|
auto tCircle = new TCircle(this, &ActiveFlag, visual.CollisionGroup,
|
||||||
|
reinterpret_cast<vector3*>(visual.FloatArr), Circle.RadiusSq);
|
||||||
|
if (tCircle)
|
||||||
|
{
|
||||||
|
tCircle->place_in_grid(&AABB);
|
||||||
|
EdgeList.push_back(tCircle);
|
||||||
|
}
|
||||||
|
|
||||||
|
Circle.RadiusSq = visual.FloatArr[2] * visual.FloatArr[2];
|
||||||
|
auto zAttr = loader::query_float_attribute(groupIndex, 0, 408);
|
||||||
|
CollisionBallSetZ = pb::FullTiltMode && !pb::FullTiltDemoMode ? zAttr[3] : zAttr[2];
|
||||||
|
ThrowSpeedMult2 = visual.Kicker.ThrowBallMult * 0.01f;
|
||||||
|
BallThrowDirection = visual.Kicker.ThrowBallDirection;
|
||||||
|
ThrowAngleMult = visual.Kicker.ThrowBallAngleMult;
|
||||||
|
ThrowSpeedMult1 = visual.Kicker.Boost;
|
||||||
|
|
||||||
|
circle.RadiusSq = Circle.RadiusSq;
|
||||||
|
circle.Center.X = Circle.Center.X;
|
||||||
|
circle.Center.Y = Circle.Center.Y;
|
||||||
|
Field.ActiveFlag = &ActiveFlag;
|
||||||
|
Field.CollisionComp = this;
|
||||||
|
Field.CollisionGroup = visual.CollisionGroup;
|
||||||
|
TTableLayer::edges_insert_circle(&circle, nullptr, &Field);
|
||||||
|
}
|
||||||
|
|
||||||
|
int TKickout::Message(MessageCode code, float value)
|
||||||
|
{
|
||||||
|
switch (code)
|
||||||
|
{
|
||||||
|
case MessageCode::TKickoutRestartTimer:
|
||||||
|
if (BallCaputeredFlag)
|
||||||
|
{
|
||||||
|
if (value < 0.0f)
|
||||||
|
value = TimerTime1;
|
||||||
|
Timer = timer::set(value, this, TimerExpired);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case MessageCode::SetTiltLock:
|
||||||
|
if (NotSomeFlag)
|
||||||
|
ActiveFlag = 0;
|
||||||
|
break;
|
||||||
|
case MessageCode::Reset:
|
||||||
|
if (BallCaputeredFlag)
|
||||||
|
{
|
||||||
|
if (Timer)
|
||||||
|
timer::kill(Timer);
|
||||||
|
TimerExpired(0, this);
|
||||||
|
}
|
||||||
|
if (NotSomeFlag)
|
||||||
|
ActiveFlag = 0;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TKickout::Collision(TBall* ball, vector2* nextPosition, vector2* direction, float distance, TEdgeSegment* edge)
|
||||||
|
{
|
||||||
|
if (!BallCaputeredFlag)
|
||||||
|
{
|
||||||
|
Ball = ball;
|
||||||
|
Threshold = 1000000000.0;
|
||||||
|
BallCaputeredFlag = 1;
|
||||||
|
ball->CollisionComp = this;
|
||||||
|
ball->Position.X = Circle.Center.X;
|
||||||
|
ball->Position.Y = Circle.Center.Y;
|
||||||
|
OriginalBallZ = ball->Position.Z;
|
||||||
|
ball->Position.Z = CollisionBallSetZ;
|
||||||
|
ball->CollisionDisabledFlag = true;
|
||||||
|
if (PinballTable->TiltLockFlag)
|
||||||
|
{
|
||||||
|
Message(MessageCode::TKickoutRestartTimer, 0.1f);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
loader::play_sound(SoftHitSoundId, ball, "TKickout1");
|
||||||
|
control::handler(MessageCode::ControlCollision, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ball->Position.X = nextPosition->X;
|
||||||
|
ball->Position.Y = nextPosition->Y;
|
||||||
|
ball->RayMaxDistance -= distance;
|
||||||
|
ball->not_again(edge);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int TKickout::FieldEffect(TBall* ball, vector2* dstVec)
|
||||||
|
{
|
||||||
|
vector2 direction{};
|
||||||
|
|
||||||
|
if (BallCaputeredFlag)
|
||||||
|
return 0;
|
||||||
|
direction.X = Circle.Center.X - ball->Position.X;
|
||||||
|
direction.Y = Circle.Center.Y - ball->Position.Y;
|
||||||
|
if (direction.Y * direction.Y + direction.X * direction.X > Circle.RadiusSq)
|
||||||
|
return 0;
|
||||||
|
maths::normalize_2d(direction);
|
||||||
|
dstVec->X = direction.X * FieldMult - ball->Direction.X * ball->Speed;
|
||||||
|
dstVec->Y = direction.Y * FieldMult - ball->Direction.Y * ball->Speed;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TKickout::TimerExpired(int timerId, void* caller)
|
||||||
|
{
|
||||||
|
auto kick = static_cast<TKickout*>(caller);
|
||||||
|
if (kick->BallCaputeredFlag)
|
||||||
|
{
|
||||||
|
kick->BallCaputeredFlag = 0;
|
||||||
|
kick->Timer = timer::set(kick->TimerTime2, kick, ResetTimerExpired);
|
||||||
|
if (kick->Ball)
|
||||||
|
{
|
||||||
|
loader::play_sound(kick->HardHitSoundId, kick->Ball, "TKickout2");
|
||||||
|
kick->Ball->Position.Z = kick->OriginalBallZ;
|
||||||
|
kick->Ball->throw_ball(&kick->BallThrowDirection, kick->ThrowAngleMult, kick->ThrowSpeedMult1,
|
||||||
|
kick->ThrowSpeedMult2);
|
||||||
|
kick->ActiveFlag = 0;
|
||||||
|
kick->Ball = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TKickout::ResetTimerExpired(int timerId, void* caller)
|
||||||
|
{
|
||||||
|
auto kick = static_cast<TKickout*>(caller);
|
||||||
|
if (!kick->NotSomeFlag)
|
||||||
|
kick->ActiveFlag = 1;
|
||||||
|
kick->Timer = 0;
|
||||||
|
}
|
||||||
34
SpaceCadetPinball/TKickout.h
Normal file
34
SpaceCadetPinball/TKickout.h
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "maths.h"
|
||||||
|
#include "TCollisionComponent.h"
|
||||||
|
#include "TEdgeManager.h"
|
||||||
|
|
||||||
|
class TKickout :
|
||||||
|
public TCollisionComponent
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
TKickout(TPinballTable* table, int groupIndex, bool someFlag);
|
||||||
|
int Message(MessageCode code, float value) override;
|
||||||
|
void Collision(TBall* ball, vector2* nextPosition, vector2* direction, float distance,
|
||||||
|
TEdgeSegment* edge) override;
|
||||||
|
int FieldEffect(TBall* ball, vector2* vecDst) override;
|
||||||
|
|
||||||
|
static void TimerExpired(int timerId, void* caller);
|
||||||
|
static void ResetTimerExpired(int timerId, void* caller);
|
||||||
|
|
||||||
|
int BallCaputeredFlag;
|
||||||
|
int NotSomeFlag;
|
||||||
|
int Timer;
|
||||||
|
float TimerTime1;
|
||||||
|
float TimerTime2;
|
||||||
|
float CollisionBallSetZ;
|
||||||
|
TBall* Ball{};
|
||||||
|
float FieldMult;
|
||||||
|
circle_type Circle{};
|
||||||
|
float OriginalBallZ{};
|
||||||
|
vector3 BallThrowDirection{};
|
||||||
|
float ThrowAngleMult;
|
||||||
|
float ThrowSpeedMult1;
|
||||||
|
float ThrowSpeedMult2;
|
||||||
|
field_effect_type Field{};
|
||||||
|
};
|
||||||
349
SpaceCadetPinball/TLight.cpp
Normal file
349
SpaceCadetPinball/TLight.cpp
Normal file
@ -0,0 +1,349 @@
|
|||||||
|
#include "pch.h"
|
||||||
|
#include "TLight.h"
|
||||||
|
|
||||||
|
|
||||||
|
#include "control.h"
|
||||||
|
#include "loader.h"
|
||||||
|
#include "render.h"
|
||||||
|
#include "timer.h"
|
||||||
|
#include "TPinballTable.h"
|
||||||
|
|
||||||
|
TLight::TLight(TPinballTable* table, int groupIndex) : TPinballComponent(table, groupIndex, true)
|
||||||
|
{
|
||||||
|
TimeoutTimer = 0;
|
||||||
|
FlasherOnFlag = false;
|
||||||
|
UndoOverrideTimer = 0;
|
||||||
|
FlashTimer = 0;
|
||||||
|
Reset();
|
||||||
|
float* floatArr1 = loader::query_float_attribute(groupIndex, 0, 900);
|
||||||
|
FlashDelay[0] = *floatArr1;
|
||||||
|
SourceDelay[0] = *floatArr1;
|
||||||
|
float* floatArr2 = loader::query_float_attribute(groupIndex, 0, 901);
|
||||||
|
FlashDelay[1] = *floatArr2;
|
||||||
|
SourceDelay[1] = *floatArr2;
|
||||||
|
}
|
||||||
|
|
||||||
|
int TLight::Message(MessageCode code, float value)
|
||||||
|
{
|
||||||
|
int bmpIndex;
|
||||||
|
|
||||||
|
switch (code)
|
||||||
|
{
|
||||||
|
case MessageCode::Reset:
|
||||||
|
Reset();
|
||||||
|
for (auto index = 0; index < PinballTable->PlayerCount; ++index)
|
||||||
|
{
|
||||||
|
auto playerPtr = &PlayerData[index];
|
||||||
|
playerPtr->FlasherOnFlag = FlasherOnFlag;
|
||||||
|
playerPtr->LightOnBmpIndex = LightOnBmpIndex;
|
||||||
|
playerPtr->LightOnFlag = LightOnFlag;
|
||||||
|
playerPtr->MessageField = MessageField;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case MessageCode::PlayerChanged:
|
||||||
|
{
|
||||||
|
auto playerPtr = &PlayerData[PinballTable->CurrentPlayer];
|
||||||
|
playerPtr->FlasherOnFlag = FlasherOnFlag;
|
||||||
|
playerPtr->LightOnBmpIndex = LightOnBmpIndex;
|
||||||
|
playerPtr->LightOnFlag = LightOnFlag;
|
||||||
|
playerPtr->MessageField = MessageField;
|
||||||
|
|
||||||
|
Reset();
|
||||||
|
|
||||||
|
playerPtr = &PlayerData[static_cast<int>(floor(value))];
|
||||||
|
FlasherOnFlag = playerPtr->FlasherOnFlag;
|
||||||
|
LightOnBmpIndex = playerPtr->LightOnBmpIndex;
|
||||||
|
LightOnFlag = playerPtr->LightOnFlag;
|
||||||
|
MessageField = playerPtr->MessageField;
|
||||||
|
if (LightOnBmpIndex)
|
||||||
|
{
|
||||||
|
Message(MessageCode::TLightSetOnStateBmpIndex, static_cast<float>(LightOnBmpIndex));
|
||||||
|
}
|
||||||
|
if (LightOnFlag)
|
||||||
|
Message(MessageCode::TLightTurnOn, 0.0);
|
||||||
|
if (FlasherOnFlag)
|
||||||
|
Message(MessageCode::TLightFlasherStart, 0.0);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case MessageCode::TLightTurnOff:
|
||||||
|
LightOnFlag = false;
|
||||||
|
if (!FlasherOnFlag && !ToggledOffFlag && !ToggledOnFlag)
|
||||||
|
SetSpriteBmp(BmpArr[0]);
|
||||||
|
break;
|
||||||
|
case MessageCode::TLightTurnOn:
|
||||||
|
LightOnFlag = true;
|
||||||
|
if (!FlasherOnFlag && !ToggledOffFlag && !ToggledOnFlag)
|
||||||
|
SetSpriteBmp(BmpArr[1]);
|
||||||
|
break;
|
||||||
|
case MessageCode::TLightGetLightOnFlag:
|
||||||
|
return LightOnFlag;
|
||||||
|
case MessageCode::TLightGetFlasherOnFlag:
|
||||||
|
return FlasherOnFlag;
|
||||||
|
case MessageCode::TLightFlasherStart:
|
||||||
|
schedule_timeout(0.0);
|
||||||
|
if (!FlasherOnFlag || !FlashTimer)
|
||||||
|
{
|
||||||
|
FlasherOnFlag = true;
|
||||||
|
ToggledOnFlag = false;
|
||||||
|
ToggledOffFlag = false;
|
||||||
|
TurnOffAfterFlashingFg = false;
|
||||||
|
flasher_start(LightOnFlag);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case MessageCode::TLightApplyMultDelay:
|
||||||
|
FlashDelay[0] = value * SourceDelay[0];
|
||||||
|
FlashDelay[1] = value * SourceDelay[1];
|
||||||
|
break;
|
||||||
|
case MessageCode::TLightApplyDelay:
|
||||||
|
FlashDelay[0] = SourceDelay[0];
|
||||||
|
FlashDelay[1] = SourceDelay[1];
|
||||||
|
break;
|
||||||
|
case MessageCode::TLightFlasherStartTimed:
|
||||||
|
if (!FlasherOnFlag)
|
||||||
|
flasher_start(LightOnFlag);
|
||||||
|
FlasherOnFlag = true;
|
||||||
|
ToggledOnFlag = false;
|
||||||
|
TurnOffAfterFlashingFg = false;
|
||||||
|
ToggledOffFlag = false;
|
||||||
|
schedule_timeout(value);
|
||||||
|
break;
|
||||||
|
case MessageCode::TLightTurnOffTimed:
|
||||||
|
if (!ToggledOffFlag)
|
||||||
|
{
|
||||||
|
if (FlasherOnFlag)
|
||||||
|
{
|
||||||
|
flasher_stop(0);
|
||||||
|
FlasherOnFlag = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SetSpriteBmp(BmpArr[0]);
|
||||||
|
}
|
||||||
|
ToggledOffFlag = true;
|
||||||
|
ToggledOnFlag = false;
|
||||||
|
}
|
||||||
|
schedule_timeout(value);
|
||||||
|
break;
|
||||||
|
case MessageCode::TLightTurnOnTimed:
|
||||||
|
if (!ToggledOnFlag)
|
||||||
|
{
|
||||||
|
if (FlasherOnFlag)
|
||||||
|
{
|
||||||
|
flasher_stop(1);
|
||||||
|
FlasherOnFlag = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SetSpriteBmp(BmpArr[1]);
|
||||||
|
}
|
||||||
|
ToggledOnFlag = true;
|
||||||
|
ToggledOffFlag = false;
|
||||||
|
}
|
||||||
|
schedule_timeout(value);
|
||||||
|
break;
|
||||||
|
case MessageCode::TLightSetOnStateBmpIndex:
|
||||||
|
LightOnBmpIndex = Clamp(static_cast<int>(floor(value)), 0, static_cast<int>(ListBitmap->size()) - 1);
|
||||||
|
BmpArr[0] = -1;
|
||||||
|
BmpArr[1] = LightOnBmpIndex;
|
||||||
|
if (!FlasherOnFlag)
|
||||||
|
{
|
||||||
|
if (ToggledOffFlag)
|
||||||
|
bmpIndex = 0;
|
||||||
|
else if (ToggledOnFlag)
|
||||||
|
bmpIndex = 1;
|
||||||
|
else
|
||||||
|
bmpIndex = LightOnFlag;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
bmpIndex = FlashLightOnFlag;
|
||||||
|
}
|
||||||
|
SetSpriteBmp(BmpArr[bmpIndex]);
|
||||||
|
break;
|
||||||
|
case MessageCode::TLightIncOnStateBmpIndex:
|
||||||
|
bmpIndex = LightOnBmpIndex + 1;
|
||||||
|
if (bmpIndex >= static_cast<int>(ListBitmap->size()))
|
||||||
|
bmpIndex = static_cast<int>(ListBitmap->size()) - 1;
|
||||||
|
Message(MessageCode::TLightSetOnStateBmpIndex, static_cast<float>(bmpIndex));
|
||||||
|
break;
|
||||||
|
case MessageCode::TLightDecOnStateBmpIndex:
|
||||||
|
bmpIndex = LightOnBmpIndex - 1;
|
||||||
|
if (bmpIndex < 0)
|
||||||
|
bmpIndex = 0;
|
||||||
|
Message(MessageCode::TLightSetOnStateBmpIndex, static_cast<float>(bmpIndex));
|
||||||
|
break;
|
||||||
|
case MessageCode::TLightResetTimed:
|
||||||
|
if (TimeoutTimer)
|
||||||
|
timer::kill(TimeoutTimer);
|
||||||
|
TimeoutTimer = 0;
|
||||||
|
if (FlasherOnFlag)
|
||||||
|
flasher_stop(-1);
|
||||||
|
FlasherOnFlag = false;
|
||||||
|
ToggledOffFlag = false;
|
||||||
|
ToggledOnFlag = false;
|
||||||
|
SetSpriteBmp(BmpArr[LightOnFlag]);
|
||||||
|
break;
|
||||||
|
case MessageCode::TLightFlasherStartTimedThenStayOn:
|
||||||
|
TurnOffAfterFlashingFg = false;
|
||||||
|
if (UndoOverrideTimer)
|
||||||
|
timer::kill(UndoOverrideTimer);
|
||||||
|
UndoOverrideTimer = 0;
|
||||||
|
Message(MessageCode::TLightTurnOn, 0.0);
|
||||||
|
Message(MessageCode::TLightFlasherStartTimed, value);
|
||||||
|
break;
|
||||||
|
case MessageCode::TLightFlasherStartTimedThenStayOff:
|
||||||
|
if (UndoOverrideTimer)
|
||||||
|
timer::kill(UndoOverrideTimer);
|
||||||
|
UndoOverrideTimer = 0;
|
||||||
|
Message(MessageCode::TLightFlasherStartTimed, value);
|
||||||
|
TurnOffAfterFlashingFg = true;
|
||||||
|
break;
|
||||||
|
case MessageCode::TLightToggleValue:
|
||||||
|
Message(static_cast<int>(floor(value)) ? MessageCode::TLightTurnOn : MessageCode::TLightTurnOff, 0.0);
|
||||||
|
return LightOnFlag;
|
||||||
|
case MessageCode::TLightResetAndToggleValue:
|
||||||
|
Message(MessageCode::TLightToggleValue, value);
|
||||||
|
Message(MessageCode::TLightResetTimed, 0.0);
|
||||||
|
return LightOnFlag;
|
||||||
|
case MessageCode::TLightResetAndTurnOn:
|
||||||
|
Message(MessageCode::TLightTurnOn, 0.0);
|
||||||
|
Message(MessageCode::TLightResetTimed, 0.0);
|
||||||
|
break;
|
||||||
|
case MessageCode::TLightResetAndTurnOff:
|
||||||
|
Message(MessageCode::TLightTurnOff, 0.0);
|
||||||
|
Message(MessageCode::TLightResetTimed, 0.0);
|
||||||
|
break;
|
||||||
|
case MessageCode::TLightToggle:
|
||||||
|
Message(MessageCode::TLightToggleValue, !LightOnFlag);
|
||||||
|
return LightOnFlag;
|
||||||
|
case MessageCode::TLightResetAndToggle:
|
||||||
|
Message(MessageCode::TLightResetAndToggleValue, !LightOnFlag);
|
||||||
|
return LightOnFlag;
|
||||||
|
case MessageCode::TLightSetMessageField:
|
||||||
|
MessageField = static_cast<int>(floor(value));
|
||||||
|
break;
|
||||||
|
case MessageCode::TLightFtTmpOverrideOn:
|
||||||
|
case MessageCode::TLightFtTmpOverrideOff:
|
||||||
|
// FT codes in negative to avoid overlap with 3DPB TLightGroup codes
|
||||||
|
SpriteSet(BmpArr[code == MessageCode::TLightFtTmpOverrideOn]);
|
||||||
|
if (UndoOverrideTimer)
|
||||||
|
timer::kill(UndoOverrideTimer);
|
||||||
|
UndoOverrideTimer = 0;
|
||||||
|
if (value > 0)
|
||||||
|
{
|
||||||
|
TemporaryOverrideFlag = true;
|
||||||
|
UndoOverrideTimer = timer::set(value, this, UndoTmpOverride);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case MessageCode::TLightFtResetOverride:
|
||||||
|
if (UndoOverrideTimer)
|
||||||
|
timer::kill(UndoOverrideTimer);
|
||||||
|
UndoOverrideTimer = 0;
|
||||||
|
TemporaryOverrideFlag = false;
|
||||||
|
SpriteSet(PreviousBitmap);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TLight::Reset()
|
||||||
|
{
|
||||||
|
if (TimeoutTimer)
|
||||||
|
timer::kill(TimeoutTimer);
|
||||||
|
if (UndoOverrideTimer)
|
||||||
|
timer::kill(UndoOverrideTimer);
|
||||||
|
if (FlasherOnFlag)
|
||||||
|
flasher_stop(-1);
|
||||||
|
TimeoutTimer = 0;
|
||||||
|
UndoOverrideTimer = 0;
|
||||||
|
LightOnFlag = false;
|
||||||
|
LightOnBmpIndex = 0;
|
||||||
|
ToggledOffFlag = false;
|
||||||
|
ToggledOnFlag = false;
|
||||||
|
FlasherOnFlag = false;
|
||||||
|
TemporaryOverrideFlag = false;
|
||||||
|
TurnOffAfterFlashingFg = false;
|
||||||
|
PreviousBitmap = -1;
|
||||||
|
BmpArr[0] = -1;
|
||||||
|
BmpArr[1] = 0;
|
||||||
|
SetSpriteBmp(BmpArr[0]);
|
||||||
|
MessageField = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TLight::schedule_timeout(float time)
|
||||||
|
{
|
||||||
|
FlashDelay[0] = SourceDelay[0];
|
||||||
|
FlashDelay[1] = SourceDelay[1];
|
||||||
|
if (TimeoutTimer)
|
||||||
|
timer::kill(TimeoutTimer);
|
||||||
|
TimeoutTimer = 0;
|
||||||
|
if (time > 0.0f)
|
||||||
|
TimeoutTimer = timer::set(time, this, TimerExpired);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TLight::TimerExpired(int timerId, void* caller)
|
||||||
|
{
|
||||||
|
auto light = static_cast<TLight*>(caller);
|
||||||
|
if (light->FlasherOnFlag)
|
||||||
|
light->flasher_stop(-1);
|
||||||
|
light->SetSpriteBmp(light->BmpArr[light->LightOnFlag]);
|
||||||
|
light->ToggledOffFlag = false;
|
||||||
|
light->ToggledOnFlag = false;
|
||||||
|
light->FlasherOnFlag = false;
|
||||||
|
light->TimeoutTimer = 0;
|
||||||
|
if (light->TurnOffAfterFlashingFg)
|
||||||
|
{
|
||||||
|
light->TurnOffAfterFlashingFg = false;
|
||||||
|
light->Message(MessageCode::TLightResetAndTurnOff, 0.0);
|
||||||
|
}
|
||||||
|
if (light->Control)
|
||||||
|
control::handler(MessageCode::ControlTimerExpired, light);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void TLight::flasher_stop(int bmpIndex)
|
||||||
|
{
|
||||||
|
if (FlashTimer)
|
||||||
|
timer::kill(FlashTimer);
|
||||||
|
FlashTimer = 0;
|
||||||
|
if (bmpIndex >= 0)
|
||||||
|
{
|
||||||
|
FlashLightOnFlag = bmpIndex;
|
||||||
|
SetSpriteBmp(BmpArr[FlashLightOnFlag]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TLight::flasher_start(bool bmpIndex)
|
||||||
|
{
|
||||||
|
FlashLightOnFlag = bmpIndex;
|
||||||
|
flasher_callback(0, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TLight::SetSpriteBmp(int index)
|
||||||
|
{
|
||||||
|
PreviousBitmap = index;
|
||||||
|
if (!TemporaryOverrideFlag)
|
||||||
|
SpriteSet(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TLight::flasher_callback(int timerId, void* caller)
|
||||||
|
{
|
||||||
|
auto light = static_cast<TLight*>(caller);
|
||||||
|
light->FlashLightOnFlag ^= true;
|
||||||
|
light->SetSpriteBmp(light->BmpArr[light->FlashLightOnFlag]);
|
||||||
|
light->FlashTimer = timer::set(light->FlashDelay[light->FlashLightOnFlag], light, flasher_callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TLight::UndoTmpOverride(int timerId, void* caller)
|
||||||
|
{
|
||||||
|
auto light = static_cast<TLight*>(caller);
|
||||||
|
light->Message(MessageCode::TLightFtResetOverride, 0.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TLight::light_on() const
|
||||||
|
{
|
||||||
|
return LightOnFlag || ToggledOnFlag || FlasherOnFlag;
|
||||||
|
}
|
||||||
46
SpaceCadetPinball/TLight.h
Normal file
46
SpaceCadetPinball/TLight.h
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "TPinballComponent.h"
|
||||||
|
|
||||||
|
struct TLight_player_backup
|
||||||
|
{
|
||||||
|
int MessageField;
|
||||||
|
bool LightOnFlag;
|
||||||
|
int LightOnBmpIndex;
|
||||||
|
bool FlasherOnFlag;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
class TLight :
|
||||||
|
public TPinballComponent
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
TLight(TPinballTable* table, int groupIndex);
|
||||||
|
int Message(MessageCode code, float value) override;
|
||||||
|
void Reset();
|
||||||
|
void schedule_timeout(float time);
|
||||||
|
void flasher_stop(int bmpIndex);
|
||||||
|
void flasher_start(bool bmpIndex);
|
||||||
|
void SetSpriteBmp(int index);
|
||||||
|
bool light_on() const;
|
||||||
|
|
||||||
|
static void TimerExpired(int timerId, void* caller);
|
||||||
|
static void flasher_callback(int timerId, void* caller);
|
||||||
|
static void UndoTmpOverride(int timerId, void* caller);
|
||||||
|
|
||||||
|
int BmpArr[2]{-1};
|
||||||
|
float FlashDelay[2]{};
|
||||||
|
int FlashTimer;
|
||||||
|
bool FlashLightOnFlag{};
|
||||||
|
bool LightOnFlag{};
|
||||||
|
bool FlasherOnFlag;
|
||||||
|
bool ToggledOffFlag{};
|
||||||
|
bool ToggledOnFlag{};
|
||||||
|
bool TurnOffAfterFlashingFg{};
|
||||||
|
int LightOnBmpIndex{};
|
||||||
|
float SourceDelay[2]{};
|
||||||
|
int TimeoutTimer;
|
||||||
|
int UndoOverrideTimer;
|
||||||
|
bool TemporaryOverrideFlag{};
|
||||||
|
int PreviousBitmap = -1;
|
||||||
|
TLight_player_backup PlayerData[4]{};
|
||||||
|
};
|
||||||
130
SpaceCadetPinball/TLightBargraph.cpp
Normal file
130
SpaceCadetPinball/TLightBargraph.cpp
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
#include "pch.h"
|
||||||
|
#include "TLightBargraph.h"
|
||||||
|
|
||||||
|
|
||||||
|
#include "control.h"
|
||||||
|
#include "loader.h"
|
||||||
|
#include "timer.h"
|
||||||
|
#include "TPinballTable.h"
|
||||||
|
|
||||||
|
TLightBargraph::TLightBargraph(TPinballTable* table, int groupIndex) : TLightGroup(table, groupIndex)
|
||||||
|
{
|
||||||
|
TimerTimeArray = nullptr;
|
||||||
|
TLightBargraph::Reset();
|
||||||
|
if (groupIndex > 0)
|
||||||
|
{
|
||||||
|
float* floatArr = loader::query_float_attribute(groupIndex, 0, 904);
|
||||||
|
if (floatArr)
|
||||||
|
{
|
||||||
|
auto count = 2 * List.size();
|
||||||
|
TimerTimeArray = new float[count];
|
||||||
|
if (TimerTimeArray)
|
||||||
|
{
|
||||||
|
for (auto i = 0u; i < count; ++floatArr)
|
||||||
|
TimerTimeArray[i++] = *floatArr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TLightBargraph::~TLightBargraph()
|
||||||
|
{
|
||||||
|
delete[] TimerTimeArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
int TLightBargraph::Message(MessageCode code, float value)
|
||||||
|
{
|
||||||
|
switch (code)
|
||||||
|
{
|
||||||
|
case MessageCode::TLightGroupGetOnCount:
|
||||||
|
return TimeIndex;
|
||||||
|
case MessageCode::TLightGroupToggleSplitIndex:
|
||||||
|
{
|
||||||
|
if (TimerBargraph)
|
||||||
|
{
|
||||||
|
timer::kill(TimerBargraph);
|
||||||
|
TimerBargraph = 0;
|
||||||
|
}
|
||||||
|
auto timeIndex = static_cast<int>(floor(value));
|
||||||
|
auto maxCount = static_cast<int>(List.size()) * 2;
|
||||||
|
if (timeIndex >= maxCount)
|
||||||
|
timeIndex = maxCount - 1;
|
||||||
|
if (timeIndex >= 0)
|
||||||
|
{
|
||||||
|
TLightGroup::Message(MessageCode::TLightGroupToggleSplitIndex, static_cast<float>(timeIndex / 2));
|
||||||
|
if (!(timeIndex & 1))
|
||||||
|
TLightGroup::Message(MessageCode::TLightGroupStartFlasher, 0.0);
|
||||||
|
if (TimerTimeArray)
|
||||||
|
TimerBargraph = timer::set(TimerTimeArray[timeIndex], this, BargraphTimerExpired);
|
||||||
|
TimeIndex = timeIndex;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
TLightGroup::Message(MessageCode::TLightResetAndTurnOff, 0.0);
|
||||||
|
TimeIndex = 0;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case MessageCode::SetTiltLock:
|
||||||
|
Reset();
|
||||||
|
break;
|
||||||
|
case MessageCode::PlayerChanged:
|
||||||
|
if (TimerBargraph)
|
||||||
|
{
|
||||||
|
timer::kill(TimerBargraph);
|
||||||
|
TimerBargraph = 0;
|
||||||
|
}
|
||||||
|
PlayerTimerIndexBackup[PinballTable->CurrentPlayer] = TimeIndex;
|
||||||
|
Reset();
|
||||||
|
TimeIndex = PlayerTimerIndexBackup[static_cast<int>(floor(value))];
|
||||||
|
if (TimeIndex)
|
||||||
|
{
|
||||||
|
TLightBargraph::Message(MessageCode::TLightGroupToggleSplitIndex, static_cast<float>(TimeIndex));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case MessageCode::Reset:
|
||||||
|
{
|
||||||
|
Reset();
|
||||||
|
int* playerPtr = PlayerTimerIndexBackup;
|
||||||
|
for (auto index = 0; index < PinballTable->PlayerCount; ++index)
|
||||||
|
{
|
||||||
|
*playerPtr = TimeIndex;
|
||||||
|
|
||||||
|
++playerPtr;
|
||||||
|
}
|
||||||
|
TLightGroup::Message(MessageCode::Reset, value);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
TLightGroup::Message(code, value);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TLightBargraph::Reset()
|
||||||
|
{
|
||||||
|
if (TimerBargraph)
|
||||||
|
{
|
||||||
|
timer::kill(TimerBargraph);
|
||||||
|
TimerBargraph = 0;
|
||||||
|
}
|
||||||
|
TimeIndex = 0;
|
||||||
|
TLightGroup::Reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TLightBargraph::BargraphTimerExpired(int timerId, void* caller)
|
||||||
|
{
|
||||||
|
auto bar = static_cast<TLightBargraph*>(caller);
|
||||||
|
bar->TimerBargraph = 0;
|
||||||
|
if (bar->TimeIndex)
|
||||||
|
{
|
||||||
|
bar->Message(MessageCode::TLightGroupToggleSplitIndex, static_cast<float>(bar->TimeIndex - 1));
|
||||||
|
control::handler(MessageCode::ControlTimerExpired, bar);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
bar->Message(MessageCode::TLightResetAndTurnOff, 0.0);
|
||||||
|
control::handler(MessageCode::TLightGroupCountdownEnded, bar);
|
||||||
|
}
|
||||||
|
}
|
||||||
19
SpaceCadetPinball/TLightBargraph.h
Normal file
19
SpaceCadetPinball/TLightBargraph.h
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "TLightGroup.h"
|
||||||
|
|
||||||
|
class TLightBargraph :
|
||||||
|
public TLightGroup
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
TLightBargraph(TPinballTable* table, int groupIndex);
|
||||||
|
~TLightBargraph() override;
|
||||||
|
int Message(MessageCode code, float value) override;
|
||||||
|
void Reset() override;
|
||||||
|
|
||||||
|
static void BargraphTimerExpired(int timerId, void* caller);
|
||||||
|
|
||||||
|
float* TimerTimeArray;
|
||||||
|
int TimerBargraph{};
|
||||||
|
int TimeIndex{};
|
||||||
|
int PlayerTimerIndexBackup[4]{};
|
||||||
|
};
|
||||||
464
SpaceCadetPinball/TLightGroup.cpp
Normal file
464
SpaceCadetPinball/TLightGroup.cpp
Normal file
@ -0,0 +1,464 @@
|
|||||||
|
#include "pch.h"
|
||||||
|
#include "TLightGroup.h"
|
||||||
|
|
||||||
|
|
||||||
|
#include "control.h"
|
||||||
|
#include "loader.h"
|
||||||
|
#include "timer.h"
|
||||||
|
#include "TLight.h"
|
||||||
|
#include "TPinballTable.h"
|
||||||
|
|
||||||
|
TLightGroup::TLightGroup(TPinballTable* table, int groupIndex) : TPinballComponent(table, groupIndex, false)
|
||||||
|
{
|
||||||
|
Timer = 0;
|
||||||
|
NotifyTimer = 0;
|
||||||
|
TLightGroup::Reset();
|
||||||
|
if (groupIndex > 0)
|
||||||
|
{
|
||||||
|
int count;
|
||||||
|
Timer1TimeDefault = *loader::query_float_attribute(groupIndex, 0, 903);
|
||||||
|
int16_t* groupIndArr = loader::query_iattribute(groupIndex, 1027, &count);
|
||||||
|
for (int index = 0; index < count; ++groupIndArr)
|
||||||
|
{
|
||||||
|
auto comp = dynamic_cast<TLight*>(table->find_component(*groupIndArr));
|
||||||
|
if (comp)
|
||||||
|
List.push_back(comp);
|
||||||
|
++index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int TLightGroup::Message(MessageCode code, float value)
|
||||||
|
{
|
||||||
|
auto const count = static_cast<int>(List.size());
|
||||||
|
switch (code)
|
||||||
|
{
|
||||||
|
case MessageCode::SetTiltLock:
|
||||||
|
case MessageCode::GameOver:
|
||||||
|
break;
|
||||||
|
case MessageCode::PlayerChanged:
|
||||||
|
{
|
||||||
|
auto playerPtr = &PlayerData[PinballTable->CurrentPlayer];
|
||||||
|
playerPtr->MessageField = MessageField;
|
||||||
|
playerPtr->MessageField2 = MessageField2;
|
||||||
|
playerPtr->Timer1Time = Timer1Time;
|
||||||
|
|
||||||
|
Reset();
|
||||||
|
|
||||||
|
playerPtr = &PlayerData[static_cast<int>(floor(value))];
|
||||||
|
MessageField = playerPtr->MessageField;
|
||||||
|
MessageField2 = playerPtr->MessageField2;
|
||||||
|
Timer1Time = playerPtr->Timer1Time;
|
||||||
|
if (!(MessageField == 0))
|
||||||
|
TimerExpired(0, this);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case MessageCode::Reset:
|
||||||
|
Reset();
|
||||||
|
for (auto index = 0; index < PinballTable->PlayerCount; index++)
|
||||||
|
{
|
||||||
|
auto playerPtr = &PlayerData[index];
|
||||||
|
playerPtr->MessageField = MessageField;
|
||||||
|
playerPtr->MessageField2 = MessageField2;
|
||||||
|
playerPtr->Timer1Time = Timer1Time;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case MessageCode::TLightGroupStepBackward:
|
||||||
|
{
|
||||||
|
auto lastLight = List.at(count - 1);
|
||||||
|
if (lastLight->FlasherOnFlag || lastLight->ToggledOnFlag || lastLight->ToggledOffFlag)
|
||||||
|
break;
|
||||||
|
if (MessageField2 != MessageCode::TLightGroupNull)
|
||||||
|
{
|
||||||
|
TLightGroup::Message(MessageCode::TLightGroupReset, 0.0);
|
||||||
|
}
|
||||||
|
AnimationFlag = 1;
|
||||||
|
MessageField2 = code;
|
||||||
|
auto lastMessage = lastLight->MessageField;
|
||||||
|
auto lastStatus = lastLight->LightOnFlag;
|
||||||
|
for (auto index = count - 1; index > 0; --index)
|
||||||
|
{
|
||||||
|
auto lightCur = List.at(index);
|
||||||
|
auto lightPrev = List.at(index - 1);
|
||||||
|
lightCur->Message(lightPrev->LightOnFlag ? MessageCode::TLightTurnOn : MessageCode::TLightTurnOff, 0.0);
|
||||||
|
lightCur->MessageField = lightPrev->MessageField;
|
||||||
|
}
|
||||||
|
auto firstLight = List.at(0);
|
||||||
|
firstLight->Message(lastStatus ? MessageCode::TLightTurnOn : MessageCode::TLightTurnOff, 0.0);
|
||||||
|
firstLight->MessageField = lastMessage;
|
||||||
|
reschedule_animation(value);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case MessageCode::TLightGroupStepForward:
|
||||||
|
{
|
||||||
|
auto lastLight = List.at(count - 1);
|
||||||
|
if (lastLight->FlasherOnFlag || lastLight->ToggledOnFlag || lastLight->ToggledOffFlag)
|
||||||
|
break;
|
||||||
|
if (MessageField2 != MessageCode::TLightGroupNull)
|
||||||
|
{
|
||||||
|
TLightGroup::Message(MessageCode::TLightGroupReset, 0.0);
|
||||||
|
}
|
||||||
|
auto firstLight = List.at(0);
|
||||||
|
AnimationFlag = 1;
|
||||||
|
MessageField2 = code;
|
||||||
|
auto firstMessage = firstLight->MessageField;
|
||||||
|
auto firstStatus = firstLight->LightOnFlag;
|
||||||
|
for (auto index = 0; index < count - 1; index++)
|
||||||
|
{
|
||||||
|
auto lightCur = List.at(index);
|
||||||
|
auto lightNext = List.at(index + 1);
|
||||||
|
lightCur->Message(lightNext->LightOnFlag ? MessageCode::TLightTurnOn : MessageCode::TLightTurnOff, 0.0);
|
||||||
|
lightCur->MessageField = lightNext->MessageField;
|
||||||
|
}
|
||||||
|
lastLight->Message(firstStatus ? MessageCode::TLightTurnOn : MessageCode::TLightTurnOff, 0.0);
|
||||||
|
lastLight->MessageField = firstMessage;
|
||||||
|
reschedule_animation(value);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case MessageCode::TLightGroupAnimationBackward:
|
||||||
|
{
|
||||||
|
if (AnimationFlag || MessageField2 == MessageCode::TLightGroupNull)
|
||||||
|
start_animation();
|
||||||
|
MessageField2 = code;
|
||||||
|
AnimationFlag = 0;
|
||||||
|
auto lastLight = List.at(count - 1);
|
||||||
|
auto lastStatus = lastLight->ToggledOnFlag;
|
||||||
|
for (auto i = count - 1; i > 0; --i)
|
||||||
|
{
|
||||||
|
auto lightCur = List.at(i);
|
||||||
|
auto lightPrev = List.at(i - 1);
|
||||||
|
lightCur->Message(lightPrev->ToggledOnFlag ? MessageCode::TLightTurnOnTimed : MessageCode::TLightTurnOffTimed, 0.0);
|
||||||
|
}
|
||||||
|
auto firstLight = List.at(0);
|
||||||
|
firstLight->Message(lastStatus ? MessageCode::TLightTurnOnTimed : MessageCode::TLightTurnOffTimed, 0);
|
||||||
|
reschedule_animation(value);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case MessageCode::TLightGroupAnimationForward:
|
||||||
|
{
|
||||||
|
if (AnimationFlag || MessageField2 == MessageCode::TLightGroupNull)
|
||||||
|
start_animation();
|
||||||
|
MessageField2 = code;
|
||||||
|
AnimationFlag = 0;
|
||||||
|
auto firstLight = List.at(0);
|
||||||
|
auto firstStatus = firstLight->ToggledOnFlag;
|
||||||
|
for (auto i = 0; i < count - 1; i++)
|
||||||
|
{
|
||||||
|
auto lightCur = List.at(i);
|
||||||
|
auto lightNext = List.at(i + 1);
|
||||||
|
lightCur->Message(lightNext->ToggledOnFlag ? MessageCode::TLightTurnOnTimed : MessageCode::TLightTurnOffTimed, 0.0);
|
||||||
|
}
|
||||||
|
auto lastLight = List.at(count - 1);
|
||||||
|
lastLight->Message(firstStatus ? MessageCode::TLightTurnOnTimed : MessageCode::TLightTurnOffTimed, 0);
|
||||||
|
reschedule_animation(value);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case MessageCode::TLightGroupLightShowAnimation:
|
||||||
|
{
|
||||||
|
if (AnimationFlag || MessageField2 == MessageCode::TLightGroupNull)
|
||||||
|
start_animation();
|
||||||
|
MessageField2 = code;
|
||||||
|
AnimationFlag = 0;
|
||||||
|
for (auto light : List)
|
||||||
|
{
|
||||||
|
if (rand() % 100 > 70)
|
||||||
|
{
|
||||||
|
auto randVal = RandFloat() * value * 3.0f + 0.1f;
|
||||||
|
light->Message(MessageCode::TLightTurnOnTimed, randVal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
reschedule_animation(value);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case MessageCode::TLightGroupGameOverAnimation:
|
||||||
|
{
|
||||||
|
if (AnimationFlag || MessageField2 == MessageCode::TLightGroupNull)
|
||||||
|
start_animation();
|
||||||
|
MessageField2 = code;
|
||||||
|
AnimationFlag = 0;
|
||||||
|
for (auto light : List)
|
||||||
|
{
|
||||||
|
auto randVal = static_cast<float>(rand() % 100 > 70);
|
||||||
|
light->Message(MessageCode::TLightResetAndToggleValue, randVal);
|
||||||
|
}
|
||||||
|
reschedule_animation(value);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case MessageCode::TLightGroupRandomAnimationSaturation:
|
||||||
|
{
|
||||||
|
auto noBmpInd1Count = 0;
|
||||||
|
for (auto light : List)
|
||||||
|
{
|
||||||
|
if (!light->LightOnFlag)
|
||||||
|
++noBmpInd1Count;
|
||||||
|
}
|
||||||
|
if (!noBmpInd1Count)
|
||||||
|
break;
|
||||||
|
|
||||||
|
auto randModCount = rand() % noBmpInd1Count;
|
||||||
|
for (auto it = List.rbegin(); it != List.rend(); ++it)
|
||||||
|
{
|
||||||
|
auto light = *it;
|
||||||
|
if (!light->LightOnFlag && randModCount-- == 0)
|
||||||
|
{
|
||||||
|
light->Message(MessageCode::TLightTurnOn, 0.0);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (MessageField2 != MessageCode::TLightGroupNull)
|
||||||
|
start_animation();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case MessageCode::TLightGroupRandomAnimationDesaturation:
|
||||||
|
{
|
||||||
|
auto bmpInd1Count = 0;
|
||||||
|
for (auto light : List)
|
||||||
|
{
|
||||||
|
if (light->LightOnFlag)
|
||||||
|
++bmpInd1Count;
|
||||||
|
}
|
||||||
|
if (!bmpInd1Count)
|
||||||
|
break;
|
||||||
|
|
||||||
|
auto randModCount = rand() % bmpInd1Count;
|
||||||
|
for (auto it = List.rbegin(); it != List.rend(); ++it)
|
||||||
|
{
|
||||||
|
auto light = *it;
|
||||||
|
if (light->LightOnFlag && randModCount-- == 0)
|
||||||
|
{
|
||||||
|
light->Message(MessageCode::TLightTurnOff, 0.0);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (MessageField2 != MessageCode::TLightGroupNull)
|
||||||
|
start_animation();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case MessageCode::TLightGroupOffsetAnimationForward:
|
||||||
|
{
|
||||||
|
auto index = next_light_up();
|
||||||
|
if (index < 0)
|
||||||
|
break;
|
||||||
|
List.at(index)->Message(MessageCode::TLightTurnOn, 0.0);
|
||||||
|
if (MessageField2 != MessageCode::TLightGroupNull)
|
||||||
|
start_animation();
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
case MessageCode::TLightGroupOffsetAnimationBackward:
|
||||||
|
{
|
||||||
|
auto index = next_light_down();
|
||||||
|
if (index < 0)
|
||||||
|
break;
|
||||||
|
List.at(index)->Message(MessageCode::TLightTurnOff, 0.0);
|
||||||
|
if (MessageField2 != MessageCode::TLightGroupNull)
|
||||||
|
start_animation();
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
case MessageCode::TLightGroupReset:
|
||||||
|
{
|
||||||
|
if (Timer)
|
||||||
|
timer::kill(Timer);
|
||||||
|
Timer = 0;
|
||||||
|
if (MessageField2 == MessageCode::TLightGroupAnimationBackward ||
|
||||||
|
MessageField2 == MessageCode::TLightGroupAnimationForward || MessageField2 == MessageCode::TLightGroupLightShowAnimation)
|
||||||
|
TLightGroup::Message(MessageCode::TLightResetTimed, 0.0);
|
||||||
|
MessageField2 = MessageCode::TLightGroupNull;
|
||||||
|
AnimationFlag = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case MessageCode::TLightGroupTurnOnAtIndex:
|
||||||
|
{
|
||||||
|
auto index = static_cast<int>(floor(value));
|
||||||
|
if (index >= count || index < 0)
|
||||||
|
break;
|
||||||
|
|
||||||
|
auto light = List.at(index);
|
||||||
|
light->Message(MessageCode::TLightTurnOn, 0.0);
|
||||||
|
if (MessageField2 != MessageCode::TLightGroupNull)
|
||||||
|
start_animation();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case MessageCode::TLightGroupTurnOffAtIndex:
|
||||||
|
{
|
||||||
|
auto index = static_cast<int>(floor(value));
|
||||||
|
if (index >= count || index < 0)
|
||||||
|
break;
|
||||||
|
|
||||||
|
auto light = List.at(index);
|
||||||
|
light->Message(MessageCode::TLightTurnOff, 0.0);
|
||||||
|
if (MessageField2 != MessageCode::TLightGroupNull)
|
||||||
|
start_animation();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case MessageCode::TLightGroupGetOnCount:
|
||||||
|
{
|
||||||
|
auto bmp1Count = 0;
|
||||||
|
for (auto light : List)
|
||||||
|
{
|
||||||
|
if (light->LightOnFlag)
|
||||||
|
++bmp1Count;
|
||||||
|
}
|
||||||
|
return bmp1Count;
|
||||||
|
}
|
||||||
|
case MessageCode::TLightGroupGetLightCount:
|
||||||
|
return count;
|
||||||
|
case MessageCode::TLightGroupGetMessage2:
|
||||||
|
return static_cast<int>(MessageField2);
|
||||||
|
case MessageCode::TLightGroupGetAnimationFlag:
|
||||||
|
return AnimationFlag;
|
||||||
|
case MessageCode::TLightGroupResetAndTurnOn:
|
||||||
|
{
|
||||||
|
auto index = next_light_up();
|
||||||
|
if (index < 0)
|
||||||
|
break;
|
||||||
|
if (MessageField2 != MessageCode::TLightGroupNull || AnimationFlag)
|
||||||
|
TLightGroup::Message(MessageCode::TLightGroupReset, 0.0);
|
||||||
|
List.at(index)->Message(MessageCode::TLightFlasherStartTimedThenStayOn, value);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
case MessageCode::TLightGroupResetAndTurnOff:
|
||||||
|
{
|
||||||
|
auto index = next_light_down();
|
||||||
|
if (index < 0)
|
||||||
|
break;
|
||||||
|
if (MessageField2 != MessageCode::TLightGroupNull || AnimationFlag)
|
||||||
|
TLightGroup::Message(MessageCode::TLightGroupReset, 0.0);
|
||||||
|
List.at(index)->Message(MessageCode::TLightFlasherStartTimedThenStayOff, value);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
case MessageCode::TLightGroupRestartNotifyTimer:
|
||||||
|
if (NotifyTimer)
|
||||||
|
timer::kill(NotifyTimer);
|
||||||
|
NotifyTimer = 0;
|
||||||
|
if (value > 0.0f)
|
||||||
|
NotifyTimer = timer::set(value, this, NotifyTimerExpired);
|
||||||
|
break;
|
||||||
|
case MessageCode::TLightGroupFlashWhenOn:
|
||||||
|
{
|
||||||
|
for (auto it = List.rbegin(); it != List.rend(); ++it)
|
||||||
|
{
|
||||||
|
auto light = *it;
|
||||||
|
if (light->LightOnFlag)
|
||||||
|
{
|
||||||
|
light->Message(MessageCode::TLightTurnOff, 0.0);
|
||||||
|
light->Message(MessageCode::TLightFlasherStartTimedThenStayOff, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case MessageCode::TLightGroupToggleSplitIndex:
|
||||||
|
{
|
||||||
|
control::handler(code, this);
|
||||||
|
auto index = static_cast<int>(floor(value));
|
||||||
|
if (index >= 0 && index < count)
|
||||||
|
{
|
||||||
|
// Turn off lights (index, end]
|
||||||
|
for (auto i = count - 1; i > index; i--)
|
||||||
|
{
|
||||||
|
List.at(i)->Message(MessageCode::TLightResetAndTurnOff, 0.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Turn on lights [begin, index]
|
||||||
|
for (auto i = index; i >= 0; i--)
|
||||||
|
{
|
||||||
|
List.at(i)->Message(MessageCode::TLightResetAndTurnOn, 0.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case MessageCode::TLightGroupStartFlasher:
|
||||||
|
{
|
||||||
|
auto index = next_light_down();
|
||||||
|
if (index >= 0)
|
||||||
|
{
|
||||||
|
List.at(index)->Message(MessageCode::TLightFlasherStart, 0.0);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
for (auto it = List.rbegin(); it != List.rend(); ++it)
|
||||||
|
{
|
||||||
|
(*it)->Message(code, value);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TLightGroup::Reset()
|
||||||
|
{
|
||||||
|
if (Timer)
|
||||||
|
timer::kill(Timer);
|
||||||
|
Timer = 0;
|
||||||
|
if (NotifyTimer)
|
||||||
|
timer::kill(NotifyTimer);
|
||||||
|
NotifyTimer = 0;
|
||||||
|
MessageField2 = MessageCode::TLightGroupNull;
|
||||||
|
AnimationFlag = 0;
|
||||||
|
Timer1Time = Timer1TimeDefault;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TLightGroup::reschedule_animation(float time)
|
||||||
|
{
|
||||||
|
if (Timer)
|
||||||
|
timer::kill(Timer);
|
||||||
|
Timer = 0;
|
||||||
|
if (time == 0)
|
||||||
|
{
|
||||||
|
MessageField2 = MessageCode::TLightGroupNull;
|
||||||
|
AnimationFlag = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Timer1Time = time > 0.0f ? time : Timer1TimeDefault;
|
||||||
|
Timer = timer::set(Timer1Time, this, TimerExpired);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TLightGroup::start_animation()
|
||||||
|
{
|
||||||
|
for (auto it = List.rbegin(); it != List.rend(); ++it)
|
||||||
|
{
|
||||||
|
auto light = *it;
|
||||||
|
if (light->LightOnFlag)
|
||||||
|
light->Message(MessageCode::TLightTurnOnTimed, 0.0);
|
||||||
|
else
|
||||||
|
light->Message(MessageCode::TLightTurnOffTimed, 0.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int TLightGroup::next_light_up()
|
||||||
|
{
|
||||||
|
for (auto index = 0u; index < List.size(); ++index)
|
||||||
|
{
|
||||||
|
if (!List[index]->LightOnFlag)
|
||||||
|
return static_cast<int>(index);
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int TLightGroup::next_light_down()
|
||||||
|
{
|
||||||
|
for (auto index = static_cast<int>(List.size()) - 1; index >= 0; --index)
|
||||||
|
{
|
||||||
|
if (List.at(index)->LightOnFlag)
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TLightGroup::TimerExpired(int timerId, void* caller)
|
||||||
|
{
|
||||||
|
auto group = static_cast<TLightGroup*>(caller);
|
||||||
|
group->Timer = 0;
|
||||||
|
group->Message(group->MessageField2, group->Timer1Time);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TLightGroup::NotifyTimerExpired(int timerId, void* caller)
|
||||||
|
{
|
||||||
|
auto group = static_cast<TLightGroup*>(caller);
|
||||||
|
group->NotifyTimer = 0;
|
||||||
|
control::handler(MessageCode::ControlNotifyTimerExpired, group);
|
||||||
|
}
|
||||||
40
SpaceCadetPinball/TLightGroup.h
Normal file
40
SpaceCadetPinball/TLightGroup.h
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "TPinballComponent.h"
|
||||||
|
|
||||||
|
|
||||||
|
class TLight;
|
||||||
|
|
||||||
|
struct TLightGroup_player_backup
|
||||||
|
{
|
||||||
|
int MessageField;
|
||||||
|
MessageCode MessageField2;
|
||||||
|
float Timer1Time;
|
||||||
|
int Unknown3;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
class TLightGroup :
|
||||||
|
public TPinballComponent
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
TLightGroup(TPinballTable* table, int groupIndex);
|
||||||
|
~TLightGroup() override = default;
|
||||||
|
int Message(MessageCode code, float value) override;
|
||||||
|
virtual void Reset();
|
||||||
|
void reschedule_animation(float time);
|
||||||
|
void start_animation();
|
||||||
|
int next_light_up();
|
||||||
|
int next_light_down();
|
||||||
|
|
||||||
|
static void TimerExpired(int timerId, void* caller);
|
||||||
|
static void NotifyTimerExpired(int timerId, void* caller);
|
||||||
|
|
||||||
|
std::vector<TLight*> List;
|
||||||
|
float Timer1Time{};
|
||||||
|
float Timer1TimeDefault;
|
||||||
|
MessageCode MessageField2{};
|
||||||
|
int AnimationFlag{};
|
||||||
|
int NotifyTimer;
|
||||||
|
int Timer;
|
||||||
|
TLightGroup_player_backup PlayerData[4]{};
|
||||||
|
};
|
||||||
67
SpaceCadetPinball/TLightRollover.cpp
Normal file
67
SpaceCadetPinball/TLightRollover.cpp
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
#include "pch.h"
|
||||||
|
#include "TLightRollover.h"
|
||||||
|
|
||||||
|
|
||||||
|
#include "control.h"
|
||||||
|
#include "loader.h"
|
||||||
|
#include "render.h"
|
||||||
|
#include "TBall.h"
|
||||||
|
#include "timer.h"
|
||||||
|
#include "TPinballTable.h"
|
||||||
|
|
||||||
|
TLightRollover::TLightRollover(TPinballTable* table, int groupIndex) : TRollover(table, groupIndex, false)
|
||||||
|
{
|
||||||
|
RolloverFlag = 0;
|
||||||
|
Timer = 0;
|
||||||
|
SpriteSet(-1);
|
||||||
|
build_walls(groupIndex);
|
||||||
|
FloatArr = *loader::query_float_attribute(groupIndex, 0, 407);
|
||||||
|
}
|
||||||
|
|
||||||
|
int TLightRollover::Message(MessageCode code, float value)
|
||||||
|
{
|
||||||
|
if (code == MessageCode::Reset)
|
||||||
|
{
|
||||||
|
ActiveFlag = 1;
|
||||||
|
RolloverFlag = 0;
|
||||||
|
if (Timer)
|
||||||
|
timer::kill(Timer);
|
||||||
|
Timer = 0;
|
||||||
|
SpriteSet(-1);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TLightRollover::Collision(TBall* ball, vector2* nextPosition, vector2* direction, float distance,
|
||||||
|
TEdgeSegment* edge)
|
||||||
|
{
|
||||||
|
ball->Position.X = nextPosition->X;
|
||||||
|
ball->Position.Y = nextPosition->Y;
|
||||||
|
ball->RayMaxDistance -= distance;
|
||||||
|
ball->not_again(edge);
|
||||||
|
if (!PinballTable->TiltLockFlag)
|
||||||
|
{
|
||||||
|
if (RolloverFlag)
|
||||||
|
{
|
||||||
|
timer::set(0.1f, this, TimerExpired);
|
||||||
|
ActiveFlag = 0;
|
||||||
|
RolloverFlag = RolloverFlag == 0;
|
||||||
|
if (Timer == 0)
|
||||||
|
Timer = timer::set(FloatArr, this, delay_expired);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
loader::play_sound(SoftHitSoundId, this, "TLightRollover");
|
||||||
|
control::handler(MessageCode::ControlCollision, this);
|
||||||
|
RolloverFlag = RolloverFlag == 0;
|
||||||
|
SpriteSet(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TLightRollover::delay_expired(int timerId, void* caller)
|
||||||
|
{
|
||||||
|
auto roll = static_cast<TLightRollover*>(caller);
|
||||||
|
roll->SpriteSet(-1);
|
||||||
|
roll->Timer = 0;
|
||||||
|
}
|
||||||
18
SpaceCadetPinball/TLightRollover.h
Normal file
18
SpaceCadetPinball/TLightRollover.h
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "TRollover.h"
|
||||||
|
|
||||||
|
class TLightRollover :
|
||||||
|
public TRollover
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
TLightRollover(TPinballTable* table, int groupIndex);
|
||||||
|
~TLightRollover() override = default;
|
||||||
|
int Message(MessageCode code, float value) override;
|
||||||
|
void Collision(TBall* ball, vector2* nextPosition, vector2* direction, float distance,
|
||||||
|
TEdgeSegment* edge) override;
|
||||||
|
|
||||||
|
static void delay_expired(int timerId, void* caller);
|
||||||
|
|
||||||
|
float FloatArr;
|
||||||
|
int Timer;
|
||||||
|
};
|
||||||
131
SpaceCadetPinball/TLine.cpp
Normal file
131
SpaceCadetPinball/TLine.cpp
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
#include "pch.h"
|
||||||
|
#include "TLine.h"
|
||||||
|
|
||||||
|
#include "TTableLayer.h"
|
||||||
|
|
||||||
|
|
||||||
|
TLine::TLine(TCollisionComponent* collCmp, char* activeFlag, unsigned int collisionGroup, float x0, float y0, float x1,
|
||||||
|
float y1): TEdgeSegment(collCmp, activeFlag, collisionGroup)
|
||||||
|
{
|
||||||
|
X0 = x0;
|
||||||
|
Y0 = y0;
|
||||||
|
X1 = x1;
|
||||||
|
Y1 = y1;
|
||||||
|
maths::line_init(Line, x0, y0, x1, y1);
|
||||||
|
}
|
||||||
|
|
||||||
|
TLine::TLine(TCollisionComponent* collCmp, char* activeFlag, unsigned int collisionGroup, const vector2& start,
|
||||||
|
const vector2& end) : TEdgeSegment(collCmp, activeFlag, collisionGroup)
|
||||||
|
{
|
||||||
|
X0 = start.X;
|
||||||
|
Y0 = start.Y;
|
||||||
|
X1 = end.X;
|
||||||
|
Y1 = end.Y;
|
||||||
|
maths::line_init(Line, X0, Y0, X1, Y1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TLine::Offset(float offset)
|
||||||
|
{
|
||||||
|
float offX = offset * Line.PerpendicularC.X;
|
||||||
|
float offY = offset * Line.PerpendicularC.Y;
|
||||||
|
|
||||||
|
X0 += offX;
|
||||||
|
Y0 += offY;
|
||||||
|
X1 += offX;
|
||||||
|
Y1 += offY;
|
||||||
|
maths::line_init(Line, X0, Y0, X1, Y1);
|
||||||
|
}
|
||||||
|
|
||||||
|
float TLine::FindCollisionDistance(const ray_type& ray)
|
||||||
|
{
|
||||||
|
return maths::ray_intersect_line(ray, Line);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TLine::EdgeCollision(TBall* ball, float distance)
|
||||||
|
{
|
||||||
|
CollisionComponent->Collision(
|
||||||
|
ball,
|
||||||
|
&Line.RayIntersect,
|
||||||
|
&Line.PerpendicularC,
|
||||||
|
distance,
|
||||||
|
this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TLine::place_in_grid(RectF* aabb)
|
||||||
|
{
|
||||||
|
if (aabb)
|
||||||
|
{
|
||||||
|
aabb->Merge({
|
||||||
|
std::max(X0, X1), std::max(Y0, Y1),
|
||||||
|
std::min(X0, X1), std::min(Y0, Y1)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
auto edgeMan = TTableLayer::edge_manager;
|
||||||
|
auto xBox0 = edgeMan->box_x(X0);
|
||||||
|
auto yBox0 = edgeMan->box_y(Y0);
|
||||||
|
auto xBox1 = edgeMan->box_x(X1);
|
||||||
|
auto yBox1 = edgeMan->box_y(Y1);
|
||||||
|
|
||||||
|
int dirX = X0 >= X1 ? -1 : 1;
|
||||||
|
int dirY = Y0 >= Y1 ? -1 : 1;
|
||||||
|
|
||||||
|
if (yBox0 == yBox1)
|
||||||
|
{
|
||||||
|
if (dirX == 1)
|
||||||
|
{
|
||||||
|
while (xBox0 <= xBox1)
|
||||||
|
edgeMan->add_edge_to_box(xBox0++, yBox0, this);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
while (xBox0 >= xBox1)
|
||||||
|
edgeMan->add_edge_to_box(xBox0--, yBox0, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (xBox0 == xBox1)
|
||||||
|
{
|
||||||
|
if (dirY == 1)
|
||||||
|
{
|
||||||
|
while (yBox0 <= yBox1)
|
||||||
|
edgeMan->add_edge_to_box(xBox0, yBox0++, this);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
while (yBox0 >= yBox1)
|
||||||
|
edgeMan->add_edge_to_box(xBox0, yBox0--, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
edgeMan->add_edge_to_box(xBox0, yBox0, this);
|
||||||
|
|
||||||
|
// Bresenham line formula: y = dYdX * (x - x0) + y0; dYdX = (y0 - y1) / (x0 - x1)
|
||||||
|
auto dyDx = (Y0 - Y1) / (X0 - X1);
|
||||||
|
// Precompute constant part: dYdX * (-x0) + y0
|
||||||
|
auto precomp = -X0 * dyDx + Y0;
|
||||||
|
// X and Y indexes are offset by one when going forwards, not sure why
|
||||||
|
auto xBias = dirX == 1 ? 1 : 0, yBias = dirY == 1 ? 1 : 0;
|
||||||
|
|
||||||
|
for (auto indexX = xBox0, indexY = yBox0; indexX != xBox1 || indexY != yBox1;)
|
||||||
|
{
|
||||||
|
// Calculate y from indexY and from line formula
|
||||||
|
auto yDiscrete = (indexY + yBias) * edgeMan->AdvanceY + edgeMan->MinY;
|
||||||
|
auto ylinear = ((indexX + xBias) * edgeMan->AdvanceX + edgeMan->MinX) * dyDx + precomp;
|
||||||
|
if (dirY == 1 ? ylinear >= yDiscrete : ylinear <= yDiscrete)
|
||||||
|
{
|
||||||
|
// Advance indexY when discrete value is ahead/behind
|
||||||
|
// Advance indexX when discrete value matches linear value
|
||||||
|
indexY += dirY;
|
||||||
|
if (ylinear == yDiscrete)
|
||||||
|
indexX += dirX;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Advance indexX otherwise
|
||||||
|
indexX += dirX;
|
||||||
|
}
|
||||||
|
edgeMan->add_edge_to_box(indexX, indexY, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
17
SpaceCadetPinball/TLine.h
Normal file
17
SpaceCadetPinball/TLine.h
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "maths.h"
|
||||||
|
#include "TEdgeSegment.h"
|
||||||
|
|
||||||
|
class TLine :
|
||||||
|
public TEdgeSegment
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
line_type Line{};
|
||||||
|
float X0, Y0, X1, Y1;
|
||||||
|
TLine(TCollisionComponent* collCmp, char* activeFlag, unsigned int collisionGroup, float x0, float y0, float x1, float y1);
|
||||||
|
TLine(TCollisionComponent* collCmp, char* activeFlag, unsigned int collisionGroup, const vector2& start, const vector2& end);
|
||||||
|
void Offset(float offset);
|
||||||
|
float FindCollisionDistance(const ray_type& ray) override;
|
||||||
|
void EdgeCollision(TBall* ball, float distance) override;
|
||||||
|
void place_in_grid(RectF* aabb) override;
|
||||||
|
};
|
||||||
74
SpaceCadetPinball/TOneway.cpp
Normal file
74
SpaceCadetPinball/TOneway.cpp
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
#include "pch.h"
|
||||||
|
#include "TOneway.h"
|
||||||
|
|
||||||
|
|
||||||
|
#include "control.h"
|
||||||
|
#include "loader.h"
|
||||||
|
#include "TBall.h"
|
||||||
|
#include "TLine.h"
|
||||||
|
#include "TPinballTable.h"
|
||||||
|
|
||||||
|
TOneway::TOneway(TPinballTable* table, int groupIndex) : TCollisionComponent(table, groupIndex, false)
|
||||||
|
{
|
||||||
|
visualStruct visual{};
|
||||||
|
vector2 linePt1{}, linePt2{};
|
||||||
|
|
||||||
|
loader::query_visual(groupIndex, 0, &visual);
|
||||||
|
if (visual.FloatArrCount == 2)
|
||||||
|
{
|
||||||
|
linePt2.X = visual.FloatArr[0];
|
||||||
|
linePt2.Y = visual.FloatArr[1];
|
||||||
|
linePt1.X = visual.FloatArr[2];
|
||||||
|
linePt1.Y = visual.FloatArr[3];
|
||||||
|
|
||||||
|
auto line = new TLine(this, &ActiveFlag, visual.CollisionGroup, linePt2, linePt1);
|
||||||
|
if (line)
|
||||||
|
{
|
||||||
|
line->Offset(table->CollisionCompOffset);
|
||||||
|
line->place_in_grid(&AABB);
|
||||||
|
EdgeList.push_back(line);
|
||||||
|
}
|
||||||
|
|
||||||
|
line = new TLine(this, &ActiveFlag, visual.CollisionGroup, linePt1, linePt2);
|
||||||
|
Line = line;
|
||||||
|
if (line)
|
||||||
|
{
|
||||||
|
line->Offset(-table->CollisionCompOffset * 0.8f);
|
||||||
|
Line->place_in_grid(&AABB);
|
||||||
|
EdgeList.push_back(Line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TOneway::Collision(TBall* ball, vector2* nextPosition, vector2* direction, float distance, TEdgeSegment* edge)
|
||||||
|
{
|
||||||
|
if (edge == Line)
|
||||||
|
{
|
||||||
|
ball->not_again(edge);
|
||||||
|
ball->Position.X = nextPosition->X;
|
||||||
|
ball->Position.Y = nextPosition->Y;
|
||||||
|
ball->RayMaxDistance -= distance;
|
||||||
|
if (!PinballTable->TiltLockFlag)
|
||||||
|
{
|
||||||
|
if (HardHitSoundId)
|
||||||
|
loader::play_sound(HardHitSoundId, ball, "TOneway1");
|
||||||
|
control::handler(MessageCode::ControlCollision, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (PinballTable->TiltLockFlag)
|
||||||
|
{
|
||||||
|
maths::basic_collision(ball, nextPosition, direction, Elasticity, Smoothness, 1000000000.0, 0.0);
|
||||||
|
}
|
||||||
|
else if (maths::basic_collision(
|
||||||
|
ball,
|
||||||
|
nextPosition,
|
||||||
|
direction,
|
||||||
|
Elasticity,
|
||||||
|
Smoothness,
|
||||||
|
Threshold,
|
||||||
|
Boost) > 0.2f)
|
||||||
|
{
|
||||||
|
if (SoftHitSoundId)
|
||||||
|
loader::play_sound(SoftHitSoundId, ball, "TOneway2");
|
||||||
|
}
|
||||||
|
}
|
||||||
15
SpaceCadetPinball/TOneway.h
Normal file
15
SpaceCadetPinball/TOneway.h
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "TCollisionComponent.h"
|
||||||
|
|
||||||
|
class TLine;
|
||||||
|
|
||||||
|
class TOneway : public TCollisionComponent
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
TOneway(TPinballTable* table, int groupIndex);
|
||||||
|
~TOneway() override = default;
|
||||||
|
void Collision(TBall* ball, vector2* nextPosition, vector2* direction, float distance,
|
||||||
|
TEdgeSegment* edge) override;
|
||||||
|
|
||||||
|
TLine* Line;
|
||||||
|
};
|
||||||
165
SpaceCadetPinball/TPinballComponent.cpp
Normal file
165
SpaceCadetPinball/TPinballComponent.cpp
Normal file
@ -0,0 +1,165 @@
|
|||||||
|
#include "pch.h"
|
||||||
|
#include "TPinballComponent.h"
|
||||||
|
|
||||||
|
#include "control.h"
|
||||||
|
#include "loader.h"
|
||||||
|
#include "proj.h"
|
||||||
|
#include "render.h"
|
||||||
|
#include "TPinballTable.h"
|
||||||
|
#include "TTableLayer.h"
|
||||||
|
|
||||||
|
TPinballComponent::TPinballComponent(TPinballTable* table, int groupIndex, bool loadVisuals)
|
||||||
|
{
|
||||||
|
visualStruct visual{};
|
||||||
|
|
||||||
|
MessageField = 0;
|
||||||
|
UnusedBaseFlag = 0;
|
||||||
|
ActiveFlag = 0;
|
||||||
|
PinballTable = table;
|
||||||
|
RenderSprite = nullptr;
|
||||||
|
ListBitmap = nullptr;
|
||||||
|
GroupName = nullptr;
|
||||||
|
Control = nullptr;
|
||||||
|
VisualPosNormX= -1.0f;
|
||||||
|
VisualPosNormY = -1.0f;
|
||||||
|
GroupIndex = groupIndex;
|
||||||
|
if (table)
|
||||||
|
table->ComponentList.push_back(this);
|
||||||
|
if (groupIndex >= 0)
|
||||||
|
GroupName = loader::query_name(groupIndex);
|
||||||
|
|
||||||
|
if (loadVisuals && groupIndex >= 0)
|
||||||
|
{
|
||||||
|
int visualCount = loader::query_visual_states(groupIndex);
|
||||||
|
for (int index = 0; index < visualCount; ++index)
|
||||||
|
{
|
||||||
|
loader::query_visual(groupIndex, index, &visual);
|
||||||
|
if (visual.Bitmap.Bmp)
|
||||||
|
{
|
||||||
|
assertm(visual.Bitmap.ZMap, "Bitmap/zMap pairing is mandatory");
|
||||||
|
if (!ListBitmap)
|
||||||
|
ListBitmap = new std::vector<SpriteData>();
|
||||||
|
ListBitmap->push_back(visual.Bitmap);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ListBitmap)
|
||||||
|
{
|
||||||
|
rectangle_type bmp1Rect{}, tmpRect{};
|
||||||
|
const auto rootSprite = ListBitmap->at(0);
|
||||||
|
const auto rootBmp = rootSprite.Bmp;
|
||||||
|
|
||||||
|
bmp1Rect.XPosition = rootBmp->XPosition - table->XOffset;
|
||||||
|
bmp1Rect.YPosition = rootBmp->YPosition - table->YOffset;
|
||||||
|
bmp1Rect.Width = rootBmp->Width;
|
||||||
|
bmp1Rect.Height = rootBmp->Height;
|
||||||
|
|
||||||
|
for (auto index = 1u; index < ListBitmap->size(); index++)
|
||||||
|
{
|
||||||
|
auto bmp = ListBitmap->at(index).Bmp;
|
||||||
|
tmpRect.XPosition = bmp->XPosition - table->XOffset;
|
||||||
|
tmpRect.YPosition = bmp->YPosition - table->YOffset;
|
||||||
|
tmpRect.Width = bmp->Width;
|
||||||
|
tmpRect.Height = bmp->Height;
|
||||||
|
maths::enclosing_box(bmp1Rect, tmpRect, bmp1Rect);
|
||||||
|
}
|
||||||
|
|
||||||
|
RenderSprite = new render_sprite(
|
||||||
|
VisualTypes::Sprite,
|
||||||
|
rootBmp,
|
||||||
|
rootSprite.ZMap,
|
||||||
|
rootBmp->XPosition - table->XOffset,
|
||||||
|
rootBmp->YPosition - table->YOffset,
|
||||||
|
&bmp1Rect);
|
||||||
|
|
||||||
|
// Sound position = center of root visual, reverse-projected, normalized.
|
||||||
|
auto& rect = RenderSprite->BmpRect;
|
||||||
|
vector2i pos2D{ rect.XPosition + rect.Width / 2, rect.YPosition + rect.Height / 2 };
|
||||||
|
auto pos3D = proj::ReverseXForm(pos2D);
|
||||||
|
auto posNorm = TTableLayer::edge_manager->NormalizeBox(pos3D);
|
||||||
|
VisualPosNormX = posNorm.X;
|
||||||
|
VisualPosNormY = posNorm.Y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
TPinballComponent::~TPinballComponent()
|
||||||
|
{
|
||||||
|
if (PinballTable)
|
||||||
|
{
|
||||||
|
// ComponentList contains one reference to each component.
|
||||||
|
auto& components = PinballTable->ComponentList;
|
||||||
|
auto position = std::find(components.begin(), components.end(), this);
|
||||||
|
if (position != components.end())
|
||||||
|
components.erase(position);
|
||||||
|
}
|
||||||
|
|
||||||
|
delete ListBitmap;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int TPinballComponent::Message(MessageCode code, float value)
|
||||||
|
{
|
||||||
|
MessageField = static_cast<int>(code);
|
||||||
|
if (code == MessageCode::Reset)
|
||||||
|
MessageField = 0;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TPinballComponent::port_draw()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
int TPinballComponent::get_scoring(unsigned int index) const
|
||||||
|
{
|
||||||
|
return Control == nullptr || index >= Control->ScoreCount ? 0 : Control->Scores[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
vector2 TPinballComponent::get_coordinates()
|
||||||
|
{
|
||||||
|
return {VisualPosNormX, VisualPosNormY};
|
||||||
|
}
|
||||||
|
|
||||||
|
void TPinballComponent::SpriteSet(int index) const
|
||||||
|
{
|
||||||
|
if (!ListBitmap)
|
||||||
|
return;
|
||||||
|
|
||||||
|
int xPos, yPos;
|
||||||
|
gdrv_bitmap8* bmp;
|
||||||
|
zmap_header_type* zMap;
|
||||||
|
if (index >= 0)
|
||||||
|
{
|
||||||
|
auto& spriteData = ListBitmap->at(index);
|
||||||
|
bmp = spriteData.Bmp;
|
||||||
|
zMap = spriteData.ZMap;
|
||||||
|
xPos = bmp->XPosition - PinballTable->XOffset;
|
||||||
|
yPos = bmp->YPosition - PinballTable->YOffset;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
bmp = nullptr;
|
||||||
|
zMap = nullptr;
|
||||||
|
xPos = RenderSprite->BmpRect.XPosition;
|
||||||
|
yPos = RenderSprite->BmpRect.YPosition;
|
||||||
|
}
|
||||||
|
|
||||||
|
RenderSprite->set(bmp, zMap, xPos, yPos);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TPinballComponent::SpriteSetBall(int index, vector2i pos, float depth) const
|
||||||
|
{
|
||||||
|
if (ListBitmap)
|
||||||
|
{
|
||||||
|
gdrv_bitmap8* bmp = nullptr;
|
||||||
|
if (index >= 0)
|
||||||
|
{
|
||||||
|
bmp = ListBitmap->at(index).Bmp;
|
||||||
|
pos.X -= bmp->Width / 2;
|
||||||
|
pos.Y -= bmp->Height / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
RenderSprite->ball_set(bmp, depth, pos.X, pos.Y);
|
||||||
|
}
|
||||||
|
}
|
||||||
158
SpaceCadetPinball/TPinballComponent.h
Normal file
158
SpaceCadetPinball/TPinballComponent.h
Normal file
@ -0,0 +1,158 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
struct SpriteData;
|
||||||
|
struct vector2i;
|
||||||
|
struct zmap_header_type;
|
||||||
|
struct gdrv_bitmap8;
|
||||||
|
struct render_sprite;
|
||||||
|
struct component_control;
|
||||||
|
struct vector2;
|
||||||
|
class TPinballTable;
|
||||||
|
|
||||||
|
|
||||||
|
enum class MessageCode
|
||||||
|
{
|
||||||
|
// Private codes <1000, different meaning for each component
|
||||||
|
TFlipperNull = 0,
|
||||||
|
TFlipperExtend = 1,
|
||||||
|
TFlipperRetract = 2,
|
||||||
|
|
||||||
|
TLightTurnOff = 0,
|
||||||
|
TLightTurnOn = 1,
|
||||||
|
TLightGetLightOnFlag = 2,
|
||||||
|
TLightGetFlasherOnFlag = 3,
|
||||||
|
TLightFlasherStart = 4,
|
||||||
|
TLightApplyMultDelay = 5,
|
||||||
|
TLightApplyDelay = 6,
|
||||||
|
TLightFlasherStartTimed = 7,
|
||||||
|
TLightTurnOffTimed = 8,
|
||||||
|
TLightTurnOnTimed = 9,
|
||||||
|
TLightSetOnStateBmpIndex = 11,
|
||||||
|
TLightIncOnStateBmpIndex = 12,
|
||||||
|
TLightDecOnStateBmpIndex = 13,
|
||||||
|
TLightResetTimed = 14,
|
||||||
|
TLightFlasherStartTimedThenStayOn = 15,
|
||||||
|
TLightFlasherStartTimedThenStayOff = 16,
|
||||||
|
TLightToggleValue = 17,
|
||||||
|
TLightResetAndToggleValue = 18,
|
||||||
|
TLightResetAndTurnOn = 19,
|
||||||
|
TLightResetAndTurnOff = 20,
|
||||||
|
TLightToggle = 21,
|
||||||
|
TLightResetAndToggle = 22,
|
||||||
|
TLightSetMessageField = 23,
|
||||||
|
TLightFtTmpOverrideOn = -24,
|
||||||
|
TLightFtTmpOverrideOff = -25,
|
||||||
|
TLightFtResetOverride = -26,
|
||||||
|
|
||||||
|
TLightGroupNull = 0,
|
||||||
|
TLightGroupStepBackward = 24,
|
||||||
|
TLightGroupStepForward = 25,
|
||||||
|
TLightGroupAnimationBackward = 26,
|
||||||
|
TLightGroupAnimationForward = 27,
|
||||||
|
TLightGroupLightShowAnimation = 28,
|
||||||
|
TLightGroupGameOverAnimation = 29,
|
||||||
|
TLightGroupRandomAnimationSaturation = 30,
|
||||||
|
TLightGroupRandomAnimationDesaturation = 31,
|
||||||
|
TLightGroupOffsetAnimationForward = 32,
|
||||||
|
TLightGroupOffsetAnimationBackward = 33,
|
||||||
|
TLightGroupReset = 34,
|
||||||
|
TLightGroupTurnOnAtIndex = 35,
|
||||||
|
TLightGroupTurnOffAtIndex = 36,
|
||||||
|
TLightGroupGetOnCount = 37,
|
||||||
|
TLightGroupGetLightCount = 38,
|
||||||
|
TLightGroupGetMessage2 = 39,
|
||||||
|
TLightGroupGetAnimationFlag = 40,
|
||||||
|
TLightGroupResetAndTurnOn = 41,
|
||||||
|
TLightGroupResetAndTurnOff = 42,
|
||||||
|
TLightGroupRestartNotifyTimer = 43,
|
||||||
|
TLightGroupFlashWhenOn = 44,
|
||||||
|
TLightGroupToggleSplitIndex = 45,
|
||||||
|
TLightGroupStartFlasher = 46,
|
||||||
|
TLightGroupCountdownEnded = 47,
|
||||||
|
|
||||||
|
TBumperSetBmpIndex = 11,
|
||||||
|
TBumperIncBmpIndex = 12,
|
||||||
|
TBumperDecBmpIndex = 13,
|
||||||
|
|
||||||
|
TComponentGroupResetNotifyTimer = 48,
|
||||||
|
|
||||||
|
TPopupTargetDisable = 49,
|
||||||
|
TPopupTargetEnable = 50,
|
||||||
|
|
||||||
|
TBlockerDisable = 51,
|
||||||
|
TBlockerEnable = 52,
|
||||||
|
TBlockerRestartTimeout = 59,
|
||||||
|
|
||||||
|
TGateDisable = 53,
|
||||||
|
TGateEnable = 54,
|
||||||
|
|
||||||
|
TKickoutRestartTimer = 55,
|
||||||
|
|
||||||
|
TSinkUnknown7 = 7,
|
||||||
|
TSinkResetTimer = 56,
|
||||||
|
|
||||||
|
TSoloTargetDisable = 49,
|
||||||
|
TSoloTargetEnable = 50,
|
||||||
|
|
||||||
|
TTimerResetTimer = 59,
|
||||||
|
|
||||||
|
ControlBallCaptured = 57,
|
||||||
|
ControlBallReleased = 58,
|
||||||
|
ControlTimerExpired = 60,
|
||||||
|
ControlNotifyTimerExpired = 61,
|
||||||
|
ControlSpinnerLoopReset = 62,
|
||||||
|
ControlCollision = 63,
|
||||||
|
ControlEnableMultiplier = 64,
|
||||||
|
ControlDisableMultiplier = 65,
|
||||||
|
ControlMissionComplete = 66,
|
||||||
|
ControlMissionStarted = 67,
|
||||||
|
|
||||||
|
// Public codes 1000+, apply to all components
|
||||||
|
LeftFlipperInputPressed = 1000,
|
||||||
|
LeftFlipperInputReleased = 1001,
|
||||||
|
RightFlipperInputPressed = 1002,
|
||||||
|
RightFlipperInputReleased = 1003,
|
||||||
|
PlungerInputPressed = 1004,
|
||||||
|
PlungerInputReleased = 1005,
|
||||||
|
Pause = 1008,
|
||||||
|
Resume = 1009,
|
||||||
|
LooseFocus = 1010,
|
||||||
|
SetTiltLock = 1011,
|
||||||
|
ClearTiltLock = 1012,
|
||||||
|
StartGamePlayer1 = 1013,
|
||||||
|
NewGame = 1014,
|
||||||
|
PlungerFeedBall = 1015,
|
||||||
|
PlungerStartFeedTimer = 1016,
|
||||||
|
PlungerLaunchBall = 1017,
|
||||||
|
PlungerRelaunchBall = 1018,
|
||||||
|
PlayerChanged = 1020,
|
||||||
|
SwitchToNextPlayer = 1021,
|
||||||
|
GameOver = 1022,
|
||||||
|
Reset = 1024,
|
||||||
|
};
|
||||||
|
|
||||||
|
class TPinballComponent
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
TPinballComponent(TPinballTable* table, int groupIndex, bool loadVisuals);
|
||||||
|
virtual ~TPinballComponent();
|
||||||
|
virtual int Message(MessageCode code, float value);
|
||||||
|
virtual void port_draw();
|
||||||
|
int get_scoring(unsigned int index) const;
|
||||||
|
virtual vector2 get_coordinates();
|
||||||
|
void SpriteSet(int index) const;
|
||||||
|
void SpriteSetBall(int index, vector2i pos, float depth) const;
|
||||||
|
|
||||||
|
char UnusedBaseFlag;
|
||||||
|
char ActiveFlag;
|
||||||
|
int MessageField;
|
||||||
|
char* GroupName;
|
||||||
|
component_control* Control;
|
||||||
|
int GroupIndex;
|
||||||
|
render_sprite* RenderSprite;
|
||||||
|
TPinballTable* PinballTable;
|
||||||
|
std::vector<SpriteData>* ListBitmap;
|
||||||
|
private:
|
||||||
|
float VisualPosNormX;
|
||||||
|
float VisualPosNormY;
|
||||||
|
};
|
||||||
714
SpaceCadetPinball/TPinballTable.cpp
Normal file
714
SpaceCadetPinball/TPinballTable.cpp
Normal file
@ -0,0 +1,714 @@
|
|||||||
|
#include "pch.h"
|
||||||
|
#include "TPinballTable.h"
|
||||||
|
|
||||||
|
|
||||||
|
#include "control.h"
|
||||||
|
#include "loader.h"
|
||||||
|
#include "midi.h"
|
||||||
|
#include "pb.h"
|
||||||
|
#include "render.h"
|
||||||
|
#include "TBall.h"
|
||||||
|
#include "TBlocker.h"
|
||||||
|
#include "TBumper.h"
|
||||||
|
#include "TComponentGroup.h"
|
||||||
|
#include "TDemo.h"
|
||||||
|
#include "TFlagSpinner.h"
|
||||||
|
#include "TGate.h"
|
||||||
|
#include "THole.h"
|
||||||
|
#include "timer.h"
|
||||||
|
#include "TKickback.h"
|
||||||
|
#include "TKickout.h"
|
||||||
|
#include "TLight.h"
|
||||||
|
#include "TLightBargraph.h"
|
||||||
|
#include "TLightGroup.h"
|
||||||
|
#include "TLightRollover.h"
|
||||||
|
#include "TOneway.h"
|
||||||
|
#include "TPopupTarget.h"
|
||||||
|
#include "TRamp.h"
|
||||||
|
#include "TRollover.h"
|
||||||
|
#include "TSink.h"
|
||||||
|
#include "TSoloTarget.h"
|
||||||
|
#include "TSound.h"
|
||||||
|
#include "TTableLayer.h"
|
||||||
|
#include "TTextBox.h"
|
||||||
|
#include "TTimer.h"
|
||||||
|
#include "TTripwire.h"
|
||||||
|
#include "TWall.h"
|
||||||
|
#include "TPlunger.h"
|
||||||
|
#include "TFlipper.h"
|
||||||
|
#include "TDrain.h"
|
||||||
|
#include "translations.h"
|
||||||
|
|
||||||
|
int TPinballTable::score_multipliers[5] = {1, 2, 3, 5, 10};
|
||||||
|
|
||||||
|
|
||||||
|
TPinballTable::TPinballTable(): TPinballComponent(nullptr, -1, false)
|
||||||
|
{
|
||||||
|
int shortArrLength;
|
||||||
|
|
||||||
|
CurScoreStruct = nullptr;
|
||||||
|
ScoreBallcount = nullptr;
|
||||||
|
ScorePlayerNumber1 = nullptr;
|
||||||
|
BallInDrainFlag = 0;
|
||||||
|
ActiveFlag = 1;
|
||||||
|
TiltLockFlag = 0;
|
||||||
|
EndGameTimeoutTimer = 0;
|
||||||
|
LightShowTimer = 0;
|
||||||
|
ReplayTimer = 0;
|
||||||
|
TiltTimeoutTimer = 0;
|
||||||
|
MultiballFlag = false;
|
||||||
|
PlayerCount = 0;
|
||||||
|
|
||||||
|
auto ball = AddBall({0.0f, 0.0f});
|
||||||
|
ball->Disable();
|
||||||
|
|
||||||
|
new TTableLayer(this);
|
||||||
|
LightGroup = new TLightGroup(this, 0);
|
||||||
|
|
||||||
|
auto score1 = score::create("score1", render::background_bitmap);
|
||||||
|
CurScoreStruct = score1;
|
||||||
|
PlayerScores[0].ScoreStruct = score1;
|
||||||
|
for (int scoreIndex = 1; scoreIndex < 4; scoreIndex++)
|
||||||
|
{
|
||||||
|
PlayerScores[scoreIndex].ScoreStruct = score::dup(CurScoreStruct, scoreIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
CurrentPlayer = 0;
|
||||||
|
MaxBallCount = 3;
|
||||||
|
ScoreBallcount = score::create("ballcount1", render::background_bitmap);
|
||||||
|
ScorePlayerNumber1 = score::create("player_number1", render::background_bitmap);
|
||||||
|
int groupIndexObjects = loader::query_handle("table_objects");
|
||||||
|
|
||||||
|
short* shortArr = loader::query_iattribute(groupIndexObjects, 1025, &shortArrLength);
|
||||||
|
if (shortArrLength > 0)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < shortArrLength / 2; ++i)
|
||||||
|
{
|
||||||
|
int objectType = *shortArr;
|
||||||
|
short* shortArrPlus1 = shortArr + 1;
|
||||||
|
int groupIndex = *shortArrPlus1;
|
||||||
|
shortArr = shortArrPlus1 + 1;
|
||||||
|
switch (objectType)
|
||||||
|
{
|
||||||
|
case 1000:
|
||||||
|
case 1010:
|
||||||
|
new TWall(this, groupIndex);
|
||||||
|
break;
|
||||||
|
case 1001:
|
||||||
|
Plunger = new TPlunger(this, groupIndex);
|
||||||
|
break;
|
||||||
|
case 1002:
|
||||||
|
LightGroup->List.push_back(new TLight(this, groupIndex));
|
||||||
|
break;
|
||||||
|
case 1003:
|
||||||
|
FlipperL = new TFlipper(this, groupIndex);
|
||||||
|
break;
|
||||||
|
case 1004:
|
||||||
|
FlipperR = new TFlipper(this, groupIndex);
|
||||||
|
break;
|
||||||
|
case 1005:
|
||||||
|
new TBumper(this, groupIndex);
|
||||||
|
break;
|
||||||
|
case 1006:
|
||||||
|
new TPopupTarget(this, groupIndex);
|
||||||
|
break;
|
||||||
|
case 1007:
|
||||||
|
Drain = new TDrain(this, groupIndex);
|
||||||
|
break;
|
||||||
|
case 1011:
|
||||||
|
new TBlocker(this, groupIndex);
|
||||||
|
break;
|
||||||
|
case 1012:
|
||||||
|
new TKickout(this, groupIndex, true);
|
||||||
|
break;
|
||||||
|
case 1013:
|
||||||
|
new TGate(this, groupIndex);
|
||||||
|
break;
|
||||||
|
case 1014:
|
||||||
|
new TKickback(this, groupIndex);
|
||||||
|
break;
|
||||||
|
case 1015:
|
||||||
|
new TRollover(this, groupIndex);
|
||||||
|
break;
|
||||||
|
case 1016:
|
||||||
|
new TOneway(this, groupIndex);
|
||||||
|
break;
|
||||||
|
case 1017:
|
||||||
|
new TSink(this, groupIndex);
|
||||||
|
break;
|
||||||
|
case 1018:
|
||||||
|
new TFlagSpinner(this, groupIndex);
|
||||||
|
break;
|
||||||
|
case 1019:
|
||||||
|
new TSoloTarget(this, groupIndex);
|
||||||
|
break;
|
||||||
|
case 1020:
|
||||||
|
new TLightRollover(this, groupIndex);
|
||||||
|
break;
|
||||||
|
case 1021:
|
||||||
|
new TRamp(this, groupIndex);
|
||||||
|
break;
|
||||||
|
case 1022:
|
||||||
|
new THole(this, groupIndex);
|
||||||
|
break;
|
||||||
|
case 1023:
|
||||||
|
new TDemo(this, groupIndex);
|
||||||
|
break;
|
||||||
|
case 1024:
|
||||||
|
new TTripwire(this, groupIndex);
|
||||||
|
break;
|
||||||
|
case 1026:
|
||||||
|
new TLightGroup(this, groupIndex);
|
||||||
|
break;
|
||||||
|
case 1028:
|
||||||
|
new TComponentGroup(this, groupIndex);
|
||||||
|
break;
|
||||||
|
case 1029:
|
||||||
|
new TKickout(this, groupIndex, false);
|
||||||
|
break;
|
||||||
|
case 1030:
|
||||||
|
new TLightBargraph(this, groupIndex);
|
||||||
|
break;
|
||||||
|
case 1031:
|
||||||
|
new TSound(this, groupIndex);
|
||||||
|
break;
|
||||||
|
case 1032:
|
||||||
|
new TTimer(this, groupIndex);
|
||||||
|
break;
|
||||||
|
case 1033:
|
||||||
|
new TTextBox(this, groupIndex);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render::build_occlude_list();
|
||||||
|
pb::InfoTextBox = dynamic_cast<TTextBox*>(find_component("info_text_box"));
|
||||||
|
pb::MissTextBox = dynamic_cast<TTextBox*>(find_component("mission_text_box"));
|
||||||
|
control::make_links(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
TPinballTable::~TPinballTable()
|
||||||
|
{
|
||||||
|
for (auto& PlayerScore : PlayerScores)
|
||||||
|
{
|
||||||
|
delete PlayerScore.ScoreStruct;
|
||||||
|
}
|
||||||
|
if (ScorePlayerNumber1)
|
||||||
|
{
|
||||||
|
delete ScorePlayerNumber1;
|
||||||
|
ScorePlayerNumber1 = nullptr;
|
||||||
|
}
|
||||||
|
if (ScoreBallcount)
|
||||||
|
{
|
||||||
|
delete ScoreBallcount;
|
||||||
|
ScoreBallcount = nullptr;
|
||||||
|
}
|
||||||
|
delete LightGroup;
|
||||||
|
while (!ComponentList.empty())
|
||||||
|
{
|
||||||
|
// Component destructor removes it from the list.
|
||||||
|
delete ComponentList[0];
|
||||||
|
}
|
||||||
|
control::ClearLinks();
|
||||||
|
pb::InfoTextBox = pb::MissTextBox = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
TPinballComponent* TPinballTable::find_component(LPCSTR componentName)
|
||||||
|
{
|
||||||
|
for (auto component : ComponentList)
|
||||||
|
{
|
||||||
|
const char* groupName = component->GroupName;
|
||||||
|
if (groupName && !strcmp(groupName, componentName))
|
||||||
|
{
|
||||||
|
return component;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pb::ShowMessageBox(SDL_MESSAGEBOX_WARNING, "Table cant find:", componentName);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
TPinballComponent* TPinballTable::find_component(int groupIndex)
|
||||||
|
{
|
||||||
|
char Buffer[40]{};
|
||||||
|
for (auto component : ComponentList)
|
||||||
|
{
|
||||||
|
if (component->GroupIndex == groupIndex)
|
||||||
|
return component;
|
||||||
|
}
|
||||||
|
|
||||||
|
snprintf(Buffer, sizeof Buffer, "%d", groupIndex);
|
||||||
|
pb::ShowMessageBox(SDL_MESSAGEBOX_WARNING, "Table cant find (lh):", Buffer);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
int TPinballTable::AddScore(int score)
|
||||||
|
{
|
||||||
|
if (JackpotScoreFlag)
|
||||||
|
{
|
||||||
|
JackpotScore += score;
|
||||||
|
const auto jackpotLimit = !pb::FullTiltMode ? 5000000 : 10000000;
|
||||||
|
if (JackpotScore > jackpotLimit)
|
||||||
|
JackpotScore = jackpotLimit;
|
||||||
|
}
|
||||||
|
if (BonusScoreFlag)
|
||||||
|
{
|
||||||
|
BonusScore += score;
|
||||||
|
if (BonusScore > 5000000)
|
||||||
|
BonusScore = 5000000;
|
||||||
|
}
|
||||||
|
int addedScore = ScoreAdded + score * score_multipliers[ScoreMultiplier];
|
||||||
|
CurScore += addedScore;
|
||||||
|
if (CurScore > 1000000000)
|
||||||
|
{
|
||||||
|
++CurScoreE9;
|
||||||
|
CurScore = CurScore - 1000000000;
|
||||||
|
}
|
||||||
|
score::set(CurScoreStruct, CurScore);
|
||||||
|
return addedScore;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TPinballTable::ChangeBallCount(int count)
|
||||||
|
{
|
||||||
|
BallCount = count;
|
||||||
|
if (count <= 0)
|
||||||
|
{
|
||||||
|
score::erase(ScoreBallcount, 1);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
score::set(ScoreBallcount, MaxBallCount - count + 1);
|
||||||
|
score::update(ScoreBallcount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TPinballTable::tilt(float time)
|
||||||
|
{
|
||||||
|
if (!TiltLockFlag && !BallInDrainFlag)
|
||||||
|
{
|
||||||
|
pb::InfoTextBox->Clear();
|
||||||
|
pb::MissTextBox->Clear();
|
||||||
|
pb::InfoTextBox->Display(pb::get_rc_string(Msg::STRING136), -1.0);
|
||||||
|
loader::play_sound(SoundIndex3, nullptr, "TPinballTable1");
|
||||||
|
TiltTimeoutTimer = timer::set(30.0, this, tilt_timeout);
|
||||||
|
|
||||||
|
for (auto component : ComponentList)
|
||||||
|
{
|
||||||
|
component->Message(MessageCode::SetTiltLock, time);
|
||||||
|
}
|
||||||
|
LightGroup->Message(MessageCode::TLightTurnOffTimed, 0);
|
||||||
|
TiltLockFlag = 1;
|
||||||
|
control::table_control_handler(MessageCode::SetTiltLock);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void TPinballTable::port_draw()
|
||||||
|
{
|
||||||
|
for (auto component : ComponentList)
|
||||||
|
{
|
||||||
|
component->port_draw();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int TPinballTable::Message(MessageCode code, float value)
|
||||||
|
{
|
||||||
|
const char* rc_text;
|
||||||
|
|
||||||
|
switch (code)
|
||||||
|
{
|
||||||
|
case MessageCode::LeftFlipperInputPressed:
|
||||||
|
if (!TiltLockFlag)
|
||||||
|
{
|
||||||
|
FlipperL->Message(MessageCode::TFlipperExtend, value);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case MessageCode::LeftFlipperInputReleased:
|
||||||
|
if (!TiltLockFlag)
|
||||||
|
{
|
||||||
|
FlipperL->Message(MessageCode::TFlipperRetract, value);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case MessageCode::RightFlipperInputPressed:
|
||||||
|
if (!TiltLockFlag)
|
||||||
|
{
|
||||||
|
FlipperR->Message(MessageCode::TFlipperExtend, value);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case MessageCode::RightFlipperInputReleased:
|
||||||
|
if (!TiltLockFlag)
|
||||||
|
{
|
||||||
|
FlipperR->Message(MessageCode::TFlipperRetract, value);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case MessageCode::PlungerInputPressed:
|
||||||
|
case MessageCode::PlungerInputReleased:
|
||||||
|
Plunger->Message(code, value);
|
||||||
|
break;
|
||||||
|
case MessageCode::Pause:
|
||||||
|
case MessageCode::Resume:
|
||||||
|
case MessageCode::LooseFocus:
|
||||||
|
for (auto component : ComponentList)
|
||||||
|
{
|
||||||
|
component->Message(code, value);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case MessageCode::ClearTiltLock:
|
||||||
|
LightGroup->Message(MessageCode::TLightResetTimed, 0.0);
|
||||||
|
if (TiltLockFlag)
|
||||||
|
{
|
||||||
|
TiltLockFlag = 0;
|
||||||
|
if (TiltTimeoutTimer)
|
||||||
|
timer::kill(TiltTimeoutTimer);
|
||||||
|
TiltTimeoutTimer = 0;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case MessageCode::StartGamePlayer1:
|
||||||
|
LightGroup->Message(MessageCode::TLightGroupReset, 0.0);
|
||||||
|
LightGroup->Message(MessageCode::TLightResetAndTurnOff, 0.0);
|
||||||
|
Plunger->Message(MessageCode::PlungerStartFeedTimer, 0.0);
|
||||||
|
if (Demo && Demo->ActiveFlag)
|
||||||
|
rc_text = pb::get_rc_string(Msg::STRING131);
|
||||||
|
else
|
||||||
|
rc_text = pb::get_rc_string(Msg::STRING127);
|
||||||
|
pb::InfoTextBox->Display(rc_text, -1.0);
|
||||||
|
if (Demo)
|
||||||
|
Demo->Message(MessageCode::NewGame, 0.0);
|
||||||
|
break;
|
||||||
|
case MessageCode::NewGame:
|
||||||
|
if (EndGameTimeoutTimer)
|
||||||
|
{
|
||||||
|
timer::kill(EndGameTimeoutTimer);
|
||||||
|
EndGame_timeout(0, this);
|
||||||
|
pb::mode_change(GameModes::InGame);
|
||||||
|
}
|
||||||
|
if (LightShowTimer)
|
||||||
|
{
|
||||||
|
timer::kill(LightShowTimer);
|
||||||
|
LightShowTimer = 0;
|
||||||
|
Message(MessageCode::StartGamePlayer1, 0.0);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Some of the control cheats persist across games.
|
||||||
|
// Was this loose anti-cheat by design?
|
||||||
|
CheatsUsed = 0;
|
||||||
|
Message(MessageCode::Reset, 0.0);
|
||||||
|
auto ball = BallList[0];
|
||||||
|
ball->Position.Y = 0.0;
|
||||||
|
ball->Position.X = 0.0;
|
||||||
|
ball->Position.Z = -0.8f;
|
||||||
|
|
||||||
|
auto playerCount = static_cast<int>(floor(value));
|
||||||
|
PlayerCount = playerCount;
|
||||||
|
if (playerCount >= 1)
|
||||||
|
{
|
||||||
|
if (playerCount > 4)
|
||||||
|
PlayerCount = 4;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
PlayerCount = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto plr1Score = PlayerScores[0].ScoreStruct;
|
||||||
|
CurrentPlayer = 0;
|
||||||
|
CurScoreStruct = plr1Score;
|
||||||
|
CurScore = 0;
|
||||||
|
score::set(plr1Score, 0);
|
||||||
|
ScoreMultiplier = 0;
|
||||||
|
|
||||||
|
for (int plrIndex = 1; plrIndex < PlayerCount; ++plrIndex)
|
||||||
|
{
|
||||||
|
auto scorePtr = &PlayerScores[plrIndex];
|
||||||
|
score::set(scorePtr->ScoreStruct, 0);
|
||||||
|
scorePtr->Score = 0;
|
||||||
|
scorePtr->ScoreE9Part = 0;
|
||||||
|
scorePtr->BallCount = MaxBallCount;
|
||||||
|
scorePtr->ExtraBalls = ExtraBalls;
|
||||||
|
scorePtr->BallLockedCounter = BallLockedCounter;
|
||||||
|
scorePtr->JackpotScore = JackpotScore;
|
||||||
|
}
|
||||||
|
|
||||||
|
BallCount = MaxBallCount;
|
||||||
|
ChangeBallCount(BallCount);
|
||||||
|
score::set(ScorePlayerNumber1, CurrentPlayer + 1);
|
||||||
|
score::update(ScorePlayerNumber1);
|
||||||
|
|
||||||
|
for (auto scoreIndex = 4 - PlayerCount; scoreIndex > 0; scoreIndex--)
|
||||||
|
{
|
||||||
|
score::set(PlayerScores[scoreIndex].ScoreStruct, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
JackpotScoreFlag = false;
|
||||||
|
BonusScoreFlag = false;
|
||||||
|
UnknownP71 = 0;
|
||||||
|
pb::InfoTextBox->Clear();
|
||||||
|
pb::MissTextBox->Clear();
|
||||||
|
LightGroup->Message(MessageCode::TLightGroupLightShowAnimation, 0.2f);
|
||||||
|
auto time = loader::play_sound(SoundIndex1, nullptr, "TPinballTable2");
|
||||||
|
if (time < 0)
|
||||||
|
time = 5.0f;
|
||||||
|
LightShowTimer = timer::set(time, this, LightShow_timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pb::FullTiltMode)
|
||||||
|
{
|
||||||
|
// Multi-ball is FT exclusive feature, at least for now.
|
||||||
|
MultiballFlag = true;
|
||||||
|
JackpotScore = 500000;
|
||||||
|
}
|
||||||
|
midi::play_track(MidiTracks::Track1, true);
|
||||||
|
break;
|
||||||
|
case MessageCode::PlungerRelaunchBall:
|
||||||
|
if (ReplayTimer)
|
||||||
|
timer::kill(ReplayTimer);
|
||||||
|
ReplayTimer = timer::set(floor(value), this, replay_timer_callback);
|
||||||
|
ReplayActiveFlag = 1;
|
||||||
|
break;
|
||||||
|
case MessageCode::SwitchToNextPlayer:
|
||||||
|
{
|
||||||
|
if (PlayerCount <= 1)
|
||||||
|
{
|
||||||
|
const char* textboxText;
|
||||||
|
if (Demo->ActiveFlag)
|
||||||
|
textboxText = pb::get_rc_string(Msg::STRING131);
|
||||||
|
else
|
||||||
|
textboxText = pb::get_rc_string(Msg::STRING127);
|
||||||
|
pb::InfoTextBox->Display(textboxText, -1.0);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto nextPlayer = (CurrentPlayer + 1) % PlayerCount;
|
||||||
|
auto nextScorePtr = &PlayerScores[nextPlayer];
|
||||||
|
if (nextScorePtr->BallCount <= 0)
|
||||||
|
break;
|
||||||
|
|
||||||
|
PlayerScores[CurrentPlayer].Score = CurScore;
|
||||||
|
PlayerScores[CurrentPlayer].ScoreE9Part = CurScoreE9;
|
||||||
|
PlayerScores[CurrentPlayer].BallCount = BallCount;
|
||||||
|
PlayerScores[CurrentPlayer].ExtraBalls = ExtraBalls;
|
||||||
|
PlayerScores[CurrentPlayer].BallLockedCounter = BallLockedCounter;
|
||||||
|
PlayerScores[CurrentPlayer].JackpotScore = JackpotScore;
|
||||||
|
|
||||||
|
CurScore = nextScorePtr->Score;
|
||||||
|
CurScoreE9 = nextScorePtr->ScoreE9Part;
|
||||||
|
BallCount = nextScorePtr->BallCount;
|
||||||
|
ExtraBalls = nextScorePtr->ExtraBalls;
|
||||||
|
BallLockedCounter = nextScorePtr->BallLockedCounter;
|
||||||
|
JackpotScore = nextScorePtr->JackpotScore;
|
||||||
|
|
||||||
|
CurScoreStruct = nextScorePtr->ScoreStruct;
|
||||||
|
score::set(CurScoreStruct, CurScore);
|
||||||
|
CurScoreStruct->DirtyFlag = true;
|
||||||
|
|
||||||
|
ChangeBallCount(BallCount);
|
||||||
|
score::set(ScorePlayerNumber1, nextPlayer + 1);
|
||||||
|
score::update(ScorePlayerNumber1);
|
||||||
|
|
||||||
|
for (auto component : ComponentList)
|
||||||
|
{
|
||||||
|
component->Message(MessageCode::PlayerChanged, static_cast<float>(nextPlayer));
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* textboxText = nullptr;
|
||||||
|
switch (nextPlayer)
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
if (Demo->ActiveFlag)
|
||||||
|
textboxText = pb::get_rc_string(Msg::STRING131);
|
||||||
|
else
|
||||||
|
textboxText = pb::get_rc_string(Msg::STRING127);
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
if (Demo->ActiveFlag)
|
||||||
|
textboxText = pb::get_rc_string(Msg::STRING132);
|
||||||
|
else
|
||||||
|
textboxText = pb::get_rc_string(Msg::STRING128);
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
if (Demo->ActiveFlag)
|
||||||
|
textboxText = pb::get_rc_string(Msg::STRING133);
|
||||||
|
else
|
||||||
|
textboxText = pb::get_rc_string(Msg::STRING129);
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
if (Demo->ActiveFlag)
|
||||||
|
textboxText = pb::get_rc_string(Msg::STRING134);
|
||||||
|
else
|
||||||
|
textboxText = pb::get_rc_string(Msg::STRING130);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (textboxText != nullptr)
|
||||||
|
pb::InfoTextBox->Display(textboxText, -1);
|
||||||
|
JackpotScoreFlag = false;
|
||||||
|
BonusScoreFlag = false;
|
||||||
|
UnknownP71 = 0;
|
||||||
|
CurrentPlayer = nextPlayer;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case MessageCode::GameOver:
|
||||||
|
loader::play_sound(SoundIndex2, nullptr, "TPinballTable3");
|
||||||
|
pb::MissTextBox->Clear();
|
||||||
|
pb::InfoTextBox->Display(pb::get_rc_string(Msg::STRING135), -1.0);
|
||||||
|
EndGameTimeoutTimer = timer::set(3.0, this, EndGame_timeout);
|
||||||
|
break;
|
||||||
|
case MessageCode::Reset:
|
||||||
|
for (auto component : ComponentList)
|
||||||
|
{
|
||||||
|
component->Message(MessageCode::Reset, 0);
|
||||||
|
}
|
||||||
|
if (ReplayTimer)
|
||||||
|
timer::kill(ReplayTimer);
|
||||||
|
ReplayTimer = 0;
|
||||||
|
if (LightShowTimer)
|
||||||
|
{
|
||||||
|
timer::kill(LightShowTimer);
|
||||||
|
LightGroup->Message(MessageCode::TLightGroupReset, 0.0);
|
||||||
|
}
|
||||||
|
LightShowTimer = 0;
|
||||||
|
ScoreMultiplier = 0;
|
||||||
|
ScoreAdded = 0;
|
||||||
|
ReflexShotScore = 0;
|
||||||
|
BonusScore = 10000;
|
||||||
|
BonusScoreFlag = false;
|
||||||
|
JackpotScore = 20000;
|
||||||
|
JackpotScoreFlag = false;
|
||||||
|
UnknownP71 = 0;
|
||||||
|
ExtraBalls = 0;
|
||||||
|
MultiballCount = 0;
|
||||||
|
BallLockedCounter = 0;
|
||||||
|
MultiballFlag = false;
|
||||||
|
UnknownP78 = 0;
|
||||||
|
ReplayActiveFlag = 0;
|
||||||
|
ReplayTimer = 0;
|
||||||
|
TiltLockFlag = 0;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
|
||||||
|
control::table_control_handler(code);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
TBall* TPinballTable::AddBall(vector2 position)
|
||||||
|
{
|
||||||
|
TBall* ball = nullptr;
|
||||||
|
|
||||||
|
for (auto curBall : BallList)
|
||||||
|
{
|
||||||
|
if (!curBall->ActiveFlag)
|
||||||
|
{
|
||||||
|
ball = curBall;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ball != nullptr)
|
||||||
|
{
|
||||||
|
ball->ActiveFlag = 1;
|
||||||
|
ball->Position.Z = ball->Radius;
|
||||||
|
ball->Direction = {};
|
||||||
|
ball->Speed = 0;
|
||||||
|
ball->TimeDelta = 0;
|
||||||
|
ball->EdgeCollisionCount = 0;
|
||||||
|
ball->CollisionFlag = 0;
|
||||||
|
ball->CollisionMask = 1;
|
||||||
|
ball->CollisionComp = nullptr;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (BallList.size() >= 20)
|
||||||
|
return nullptr;
|
||||||
|
ball = new TBall(this, -1);
|
||||||
|
BallList.push_back(ball);
|
||||||
|
}
|
||||||
|
|
||||||
|
ball->Position.X = position.X;
|
||||||
|
ball->Position.Y = position.Y;
|
||||||
|
ball->PrevPosition = ball->Position;
|
||||||
|
ball->StuckCounter = 0;
|
||||||
|
ball->LastActiveTime = pb::time_ticks;
|
||||||
|
|
||||||
|
return ball;
|
||||||
|
}
|
||||||
|
|
||||||
|
int TPinballTable::BallCountInRect(const RectF& rect)
|
||||||
|
{
|
||||||
|
int count = 0;
|
||||||
|
for (const auto ball : BallList)
|
||||||
|
{
|
||||||
|
if (ball->ActiveFlag &&
|
||||||
|
ball->Position.X >= rect.XMin &&
|
||||||
|
ball->Position.Y >= rect.YMin &&
|
||||||
|
ball->Position.X <= rect.XMax &&
|
||||||
|
ball->Position.Y <= rect.YMax)
|
||||||
|
{
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
int TPinballTable::BallCountInRect(const vector2& pos, float margin)
|
||||||
|
{
|
||||||
|
RectF rect{};
|
||||||
|
rect.XMin = pos.X - margin;
|
||||||
|
rect.XMax = pos.X + margin;
|
||||||
|
rect.YMin = pos.Y - margin;
|
||||||
|
rect.YMax = pos.Y + margin;
|
||||||
|
return BallCountInRect(rect);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TPinballTable::EndGame_timeout(int timerId, void* caller)
|
||||||
|
{
|
||||||
|
auto table = static_cast<TPinballTable*>(caller);
|
||||||
|
table->EndGameTimeoutTimer = 0;
|
||||||
|
pb::end_game();
|
||||||
|
|
||||||
|
for (auto component : table->ComponentList)
|
||||||
|
{
|
||||||
|
component->Message(MessageCode::GameOver, 0);
|
||||||
|
}
|
||||||
|
if (table->Demo)
|
||||||
|
table->Demo->Message(MessageCode::GameOver, 0.0);
|
||||||
|
control::handler(MessageCode::ControlMissionStarted, pb::MissTextBox);
|
||||||
|
pb::InfoTextBox->Display(pb::get_rc_string(Msg::STRING125), -1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TPinballTable::LightShow_timeout(int timerId, void* caller)
|
||||||
|
{
|
||||||
|
auto table = static_cast<TPinballTable*>(caller);
|
||||||
|
table->LightShowTimer = 0;
|
||||||
|
table->Message(MessageCode::StartGamePlayer1, 0.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TPinballTable::replay_timer_callback(int timerId, void* caller)
|
||||||
|
{
|
||||||
|
auto table = static_cast<TPinballTable*>(caller);
|
||||||
|
table->ReplayActiveFlag = 0;
|
||||||
|
table->ReplayTimer = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TPinballTable::tilt_timeout(int timerId, void* caller)
|
||||||
|
{
|
||||||
|
auto table = static_cast<TPinballTable*>(caller);
|
||||||
|
vector2 vec{};
|
||||||
|
|
||||||
|
table->TiltTimeoutTimer = 0;
|
||||||
|
if (table->TiltLockFlag)
|
||||||
|
{
|
||||||
|
for (auto ball : table->BallList)
|
||||||
|
{
|
||||||
|
table->Drain->Collision(ball, &vec, &vec, 0.0, nullptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
104
SpaceCadetPinball/TPinballTable.h
Normal file
104
SpaceCadetPinball/TPinballTable.h
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "maths.h"
|
||||||
|
#include "TPinballComponent.h"
|
||||||
|
|
||||||
|
class TBall;
|
||||||
|
struct scoreStruct;
|
||||||
|
class TFlipper;
|
||||||
|
class TPlunger;
|
||||||
|
class TDrain;
|
||||||
|
class TDemo;
|
||||||
|
class TLightGroup;
|
||||||
|
|
||||||
|
struct score_struct_super
|
||||||
|
{
|
||||||
|
scoreStruct* ScoreStruct;
|
||||||
|
int Score;
|
||||||
|
int ScoreE9Part;
|
||||||
|
int JackpotScore;
|
||||||
|
int BallCount;
|
||||||
|
int ExtraBalls;
|
||||||
|
int BallLockedCounter;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
class TPinballTable : public TPinballComponent
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
TPinballTable();
|
||||||
|
~TPinballTable() override;
|
||||||
|
TPinballComponent* find_component(LPCSTR componentName);
|
||||||
|
TPinballComponent* find_component(int groupIndex);
|
||||||
|
int AddScore(int score);
|
||||||
|
void ChangeBallCount(int count);
|
||||||
|
void tilt(float time);
|
||||||
|
void port_draw() override;
|
||||||
|
int Message(MessageCode code, float value) override;
|
||||||
|
TBall* AddBall(vector2 position);
|
||||||
|
int BallCountInRect(const RectF& rect);
|
||||||
|
int BallCountInRect(const vector2& pos, float margin);
|
||||||
|
|
||||||
|
static void EndGame_timeout(int timerId, void* caller);
|
||||||
|
static void LightShow_timeout(int timerId, void* caller);
|
||||||
|
static void replay_timer_callback(int timerId, void* caller);
|
||||||
|
static void tilt_timeout(int timerId, void* caller);
|
||||||
|
|
||||||
|
TFlipper* FlipperL;
|
||||||
|
TFlipper* FlipperR;
|
||||||
|
scoreStruct* CurScoreStruct;
|
||||||
|
scoreStruct* ScoreBallcount;
|
||||||
|
scoreStruct* ScorePlayerNumber1;
|
||||||
|
int CheatsUsed{};
|
||||||
|
int SoundIndex1{};
|
||||||
|
int SoundIndex2{};
|
||||||
|
int SoundIndex3{};
|
||||||
|
int BallInDrainFlag;
|
||||||
|
int CurScore{};
|
||||||
|
int CurScoreE9{};
|
||||||
|
int LightShowTimer;
|
||||||
|
int EndGameTimeoutTimer;
|
||||||
|
int TiltTimeoutTimer;
|
||||||
|
score_struct_super PlayerScores[4]{};
|
||||||
|
int PlayerCount;
|
||||||
|
int CurrentPlayer;
|
||||||
|
TPlunger* Plunger;
|
||||||
|
TDrain* Drain;
|
||||||
|
TDemo* Demo{};
|
||||||
|
int XOffset{};
|
||||||
|
int YOffset{};
|
||||||
|
int Width{};
|
||||||
|
int Height{};
|
||||||
|
std::vector<TPinballComponent*> ComponentList;
|
||||||
|
std::vector<TBall*> BallList;
|
||||||
|
std::vector<TFlipper*> FlipperList;
|
||||||
|
TLightGroup* LightGroup;
|
||||||
|
float GravityDirVectMult{};
|
||||||
|
float GravityAngleX{};
|
||||||
|
float GravityAnglY{};
|
||||||
|
float CollisionCompOffset{};
|
||||||
|
vector2 PlungerPosition{};
|
||||||
|
int ScoreMultiplier{};
|
||||||
|
int ScoreAdded{};
|
||||||
|
int ReflexShotScore{};
|
||||||
|
int BonusScore{};
|
||||||
|
bool BonusScoreFlag{};
|
||||||
|
int JackpotScore{};
|
||||||
|
bool JackpotScoreFlag{};
|
||||||
|
int UnknownP71{};
|
||||||
|
int BallCount{};
|
||||||
|
int MaxBallCount;
|
||||||
|
int ExtraBalls{};
|
||||||
|
int MultiballCount{};
|
||||||
|
int BallLockedCounter{};
|
||||||
|
bool MultiballFlag;
|
||||||
|
int UnknownP78{};
|
||||||
|
int ReplayActiveFlag{};
|
||||||
|
int ReplayTimer;
|
||||||
|
int UnknownP81{};
|
||||||
|
int UnknownP82{};
|
||||||
|
int TiltLockFlag;
|
||||||
|
|
||||||
|
private:
|
||||||
|
static int score_multipliers[5];
|
||||||
|
};
|
||||||
199
SpaceCadetPinball/TPlunger.cpp
Normal file
199
SpaceCadetPinball/TPlunger.cpp
Normal file
@ -0,0 +1,199 @@
|
|||||||
|
#include "pch.h"
|
||||||
|
#include "TPlunger.h"
|
||||||
|
|
||||||
|
|
||||||
|
#include "control.h"
|
||||||
|
#include "loader.h"
|
||||||
|
#include "maths.h"
|
||||||
|
#include "pb.h"
|
||||||
|
#include "render.h"
|
||||||
|
#include "TBall.h"
|
||||||
|
#include "timer.h"
|
||||||
|
#include "TPinballTable.h"
|
||||||
|
|
||||||
|
TPlunger::TPlunger(TPinballTable* table, int groupIndex) : TCollisionComponent(table, groupIndex, true)
|
||||||
|
{
|
||||||
|
visualStruct visual{};
|
||||||
|
|
||||||
|
loader::query_visual(groupIndex, 0, &visual);
|
||||||
|
Boost = 0.0;
|
||||||
|
BallFeedTimer_ = 0;
|
||||||
|
PullbackTimer_ = 0;
|
||||||
|
SoundIndexP1 = visual.SoundIndex4;
|
||||||
|
SoundIndexP2 = visual.SoundIndex3;
|
||||||
|
HardHitSoundId = visual.Kicker.HardHitSoundId;
|
||||||
|
Threshold = 1000000000.0;
|
||||||
|
|
||||||
|
// FT:MaxPullback = 50; 3DPB: MaxPullback = 100, PullbackIncrement is floored.
|
||||||
|
if (pb::FullTiltMode)
|
||||||
|
{
|
||||||
|
MaxPullback = 50;
|
||||||
|
PullbackIncrement = MaxPullback / (ListBitmap->size() * 8.0f);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
MaxPullback = 100;
|
||||||
|
PullbackIncrement = std::floor(MaxPullback / (ListBitmap->size() * 8.0f));
|
||||||
|
}
|
||||||
|
|
||||||
|
Elasticity = 0.5f;
|
||||||
|
Smoothness = 0.5f;
|
||||||
|
|
||||||
|
PullbackDelay = 0.025f;
|
||||||
|
float* floatArr = loader::query_float_attribute(groupIndex, 0, 601);
|
||||||
|
table->PlungerPosition = {floatArr[0], floatArr[1]};
|
||||||
|
}
|
||||||
|
|
||||||
|
void TPlunger::Collision(TBall* ball, vector2* nextPosition, vector2* direction, float distance, TEdgeSegment* edge)
|
||||||
|
{
|
||||||
|
if (PinballTable->TiltLockFlag || SomeCounter > 0)
|
||||||
|
{
|
||||||
|
auto boost = RandFloat() * MaxPullback * 0.1f + MaxPullback;
|
||||||
|
maths::basic_collision(ball, nextPosition, direction, Elasticity, Smoothness, 0, boost);
|
||||||
|
if (SomeCounter)
|
||||||
|
SomeCounter--;
|
||||||
|
Message(MessageCode::PlungerInputReleased, 0.0);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
auto boost = RandFloat() * Boost * 0.1f + Boost;
|
||||||
|
maths::basic_collision(ball, nextPosition, direction, Elasticity, Smoothness, Threshold, boost);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int TPlunger::Message(MessageCode code, float value)
|
||||||
|
{
|
||||||
|
switch (code)
|
||||||
|
{
|
||||||
|
case MessageCode::PlungerInputPressed:
|
||||||
|
if (!PullbackStartedFlag && (!pb::FullTiltMode || PinballTable->MultiballCount > 0 && !PinballTable->
|
||||||
|
TiltLockFlag))
|
||||||
|
{
|
||||||
|
PullbackStartedFlag = true;
|
||||||
|
Boost = 0.0;
|
||||||
|
Threshold = 1000000000.0;
|
||||||
|
loader::play_sound(HardHitSoundId, this, "TPlunger1");
|
||||||
|
PullbackTimer(0, this);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case MessageCode::PlungerFeedBall:
|
||||||
|
{
|
||||||
|
if (PinballTable->BallCountInRect(PinballTable->PlungerPosition, PinballTable->CollisionCompOffset * 1.2f))
|
||||||
|
{
|
||||||
|
timer::set(1.0f, this, BallFeedTimer);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
auto ball = PinballTable->AddBall(PinballTable->PlungerPosition);
|
||||||
|
assertm(ball, "Failure to create ball in plunger");
|
||||||
|
PinballTable->MultiballCount++;
|
||||||
|
PinballTable->BallInDrainFlag = 0;
|
||||||
|
pb::tilt_no_more();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case MessageCode::PlungerStartFeedTimer:
|
||||||
|
timer::set(0.95999998f, this, BallFeedTimer);
|
||||||
|
loader::play_sound(SoundIndexP1, this, "TPlunger2");
|
||||||
|
break;
|
||||||
|
case MessageCode::PlungerLaunchBall:
|
||||||
|
PullbackStartedFlag = true;
|
||||||
|
Boost = MaxPullback;
|
||||||
|
Message(MessageCode::PlungerInputReleased, 0.0f);
|
||||||
|
break;
|
||||||
|
case MessageCode::PlungerRelaunchBall:
|
||||||
|
SomeCounter++;
|
||||||
|
timer::set(value, this, BallFeedTimer);
|
||||||
|
loader::play_sound(SoundIndexP1, this, "TPlunger2_1");
|
||||||
|
PullbackStartedFlag = true;
|
||||||
|
PullbackTimer(0, this);
|
||||||
|
break;
|
||||||
|
case MessageCode::PlayerChanged:
|
||||||
|
PullbackStartedFlag = false;
|
||||||
|
Boost = 0.0f;
|
||||||
|
Threshold = 1000000000.0f;
|
||||||
|
SomeCounter = 0;
|
||||||
|
timer::kill(BallFeedTimer);
|
||||||
|
timer::kill(PullbackTimer);
|
||||||
|
timer::kill(ReleasedTimer);
|
||||||
|
break;
|
||||||
|
case MessageCode::SetTiltLock:
|
||||||
|
SomeCounter = 0;
|
||||||
|
timer::kill(BallFeedTimer);
|
||||||
|
break;
|
||||||
|
case MessageCode::PlungerInputReleased:
|
||||||
|
case MessageCode::Resume:
|
||||||
|
case MessageCode::LooseFocus:
|
||||||
|
if (PullbackStartedFlag && !SomeCounter)
|
||||||
|
{
|
||||||
|
PullbackStartedFlag = false;
|
||||||
|
Threshold = 0.0;
|
||||||
|
if (PullbackTimer_)
|
||||||
|
timer::kill(PullbackTimer_);
|
||||||
|
PullbackTimer_ = 0;
|
||||||
|
loader::play_sound(SoundIndexP2, this, "TPlunger3");
|
||||||
|
SpriteSet(0);
|
||||||
|
timer::set(PullbackDelay, this, ReleasedTimer);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case MessageCode::Reset:
|
||||||
|
{
|
||||||
|
PullbackStartedFlag = false;
|
||||||
|
Boost = 0.0f;
|
||||||
|
Threshold = 1000000000.0f;
|
||||||
|
SomeCounter = 0;
|
||||||
|
|
||||||
|
timer::kill(BallFeedTimer);
|
||||||
|
timer::kill(PullbackTimer);
|
||||||
|
timer::kill(ReleasedTimer);
|
||||||
|
|
||||||
|
SpriteSet(0);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
control::handler(code, this);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TPlunger::BallFeedTimer(int timerId, void* caller)
|
||||||
|
{
|
||||||
|
auto plunger = static_cast<TPlunger*>(caller);
|
||||||
|
plunger->Message(MessageCode::PlungerFeedBall, 0.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TPlunger::PullbackTimer(int timerId, void* caller)
|
||||||
|
{
|
||||||
|
auto plunger = static_cast<TPlunger*>(caller);
|
||||||
|
plunger->Boost += plunger->PullbackIncrement;
|
||||||
|
if (plunger->Boost <= plunger->MaxPullback)
|
||||||
|
{
|
||||||
|
if (plunger->SomeCounter)
|
||||||
|
{
|
||||||
|
plunger->PullbackTimer_ = timer::set(plunger->PullbackDelay / 4.0f, plunger, PullbackTimer);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
plunger->PullbackTimer_ = timer::set(plunger->PullbackDelay, plunger, PullbackTimer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
plunger->PullbackTimer_ = 0;
|
||||||
|
plunger->Boost = plunger->MaxPullback;
|
||||||
|
}
|
||||||
|
|
||||||
|
int index = static_cast<int>(floor(
|
||||||
|
static_cast<float>(plunger->ListBitmap->size() - 1) *
|
||||||
|
(plunger->Boost / plunger->MaxPullback)));
|
||||||
|
plunger->SpriteSet(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TPlunger::ReleasedTimer(int timerId, void* caller)
|
||||||
|
{
|
||||||
|
auto plunger = static_cast<TPlunger*>(caller);
|
||||||
|
plunger->Threshold = 1000000000.0;
|
||||||
|
plunger->Boost = 0.0;
|
||||||
|
}
|
||||||
27
SpaceCadetPinball/TPlunger.h
Normal file
27
SpaceCadetPinball/TPlunger.h
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "TCollisionComponent.h"
|
||||||
|
|
||||||
|
class TPlunger :
|
||||||
|
public TCollisionComponent
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
TPlunger(TPinballTable* table, int groupIndex);
|
||||||
|
~TPlunger() override = default;
|
||||||
|
void Collision(TBall* ball, vector2* nextPosition, vector2* direction, float distance,
|
||||||
|
TEdgeSegment* edge) override;
|
||||||
|
int Message(MessageCode code, float value) override;
|
||||||
|
|
||||||
|
static void BallFeedTimer(int timerId, void* caller);
|
||||||
|
static void PullbackTimer(int timerId, void* caller);
|
||||||
|
static void ReleasedTimer(int timerId, void* caller);
|
||||||
|
|
||||||
|
int PullbackTimer_;
|
||||||
|
int BallFeedTimer_;
|
||||||
|
float MaxPullback;
|
||||||
|
float PullbackIncrement;
|
||||||
|
float PullbackDelay;
|
||||||
|
int SoundIndexP1;
|
||||||
|
int SoundIndexP2;
|
||||||
|
bool PullbackStartedFlag{};
|
||||||
|
int SomeCounter{};
|
||||||
|
};
|
||||||
88
SpaceCadetPinball/TPopupTarget.cpp
Normal file
88
SpaceCadetPinball/TPopupTarget.cpp
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
#include "pch.h"
|
||||||
|
#include "TPopupTarget.h"
|
||||||
|
|
||||||
|
|
||||||
|
#include "control.h"
|
||||||
|
#include "loader.h"
|
||||||
|
#include "render.h"
|
||||||
|
#include "timer.h"
|
||||||
|
#include "TPinballTable.h"
|
||||||
|
|
||||||
|
TPopupTarget::TPopupTarget(TPinballTable* table, int groupIndex) : TCollisionComponent(table, groupIndex, true)
|
||||||
|
{
|
||||||
|
Timer = 0;
|
||||||
|
TimerTime = *loader::query_float_attribute(groupIndex, 0, 407);
|
||||||
|
}
|
||||||
|
|
||||||
|
int TPopupTarget::Message(MessageCode code, float value)
|
||||||
|
{
|
||||||
|
switch (code)
|
||||||
|
{
|
||||||
|
case MessageCode::TPopupTargetDisable:
|
||||||
|
ActiveFlag = 0;
|
||||||
|
SpriteSet(-1);
|
||||||
|
break;
|
||||||
|
case MessageCode::TPopupTargetEnable:
|
||||||
|
Timer = timer::set(TimerTime, this, TimerExpired);
|
||||||
|
break;
|
||||||
|
case MessageCode::PlayerChanged:
|
||||||
|
PlayerMessagefieldBackup[PinballTable->CurrentPlayer] = MessageField;
|
||||||
|
MessageField = PlayerMessagefieldBackup[static_cast<int>(floor(value))];
|
||||||
|
TPopupTarget::Message(MessageField ? MessageCode::TPopupTargetDisable : MessageCode::TPopupTargetEnable, 0.0);
|
||||||
|
break;
|
||||||
|
case MessageCode::Reset:
|
||||||
|
{
|
||||||
|
MessageField = 0;
|
||||||
|
int* playerPtr = PlayerMessagefieldBackup;
|
||||||
|
for (auto index = 0; index < PinballTable->PlayerCount; ++index)
|
||||||
|
{
|
||||||
|
*playerPtr = 0;
|
||||||
|
++playerPtr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Timer)
|
||||||
|
timer::kill(Timer);
|
||||||
|
TimerExpired(0, this);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TPopupTarget::Collision(TBall* ball, vector2* nextPosition, vector2* direction, float distance,
|
||||||
|
TEdgeSegment* edge)
|
||||||
|
{
|
||||||
|
if (PinballTable->TiltLockFlag)
|
||||||
|
{
|
||||||
|
maths::basic_collision(ball, nextPosition, direction, Elasticity, Smoothness, 1000000000.0, 0.0);
|
||||||
|
}
|
||||||
|
else if (maths::basic_collision(
|
||||||
|
ball,
|
||||||
|
nextPosition,
|
||||||
|
direction,
|
||||||
|
Elasticity,
|
||||||
|
Smoothness,
|
||||||
|
Threshold,
|
||||||
|
Boost) > Threshold)
|
||||||
|
{
|
||||||
|
if (HardHitSoundId)
|
||||||
|
loader::play_sound(HardHitSoundId, this, "TPopupTarget1");
|
||||||
|
Message(MessageCode::TPopupTargetDisable, 0.0);
|
||||||
|
control::handler(MessageCode::ControlCollision, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TPopupTarget::TimerExpired(int timerId, void* caller)
|
||||||
|
{
|
||||||
|
auto target = static_cast<TPopupTarget*>(caller);
|
||||||
|
target->Timer = 0;
|
||||||
|
target->ActiveFlag = 1;
|
||||||
|
target->SpriteSet(0);
|
||||||
|
if (timerId)
|
||||||
|
{
|
||||||
|
if (target->SoftHitSoundId)
|
||||||
|
loader::play_sound(target->SoftHitSoundId, target, "TPopupTarget2");
|
||||||
|
}
|
||||||
|
}
|
||||||
18
SpaceCadetPinball/TPopupTarget.h
Normal file
18
SpaceCadetPinball/TPopupTarget.h
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "TCollisionComponent.h"
|
||||||
|
|
||||||
|
class TPopupTarget :
|
||||||
|
public TCollisionComponent
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
TPopupTarget(TPinballTable* table, int groupIndex);
|
||||||
|
int Message(MessageCode code, float value) override;
|
||||||
|
void Collision(TBall* ball, vector2* nextPosition, vector2* direction, float distance,
|
||||||
|
TEdgeSegment* edge) override;
|
||||||
|
|
||||||
|
static void TimerExpired(int timerId, void* caller);
|
||||||
|
|
||||||
|
int Timer;
|
||||||
|
float TimerTime;
|
||||||
|
int PlayerMessagefieldBackup[4]{};
|
||||||
|
};
|
||||||
188
SpaceCadetPinball/TRamp.cpp
Normal file
188
SpaceCadetPinball/TRamp.cpp
Normal file
@ -0,0 +1,188 @@
|
|||||||
|
#include "pch.h"
|
||||||
|
#include "TRamp.h"
|
||||||
|
|
||||||
|
|
||||||
|
#include "control.h"
|
||||||
|
#include "loader.h"
|
||||||
|
#include "TBall.h"
|
||||||
|
#include "TEdgeSegment.h"
|
||||||
|
#include "TLine.h"
|
||||||
|
#include "TPinballTable.h"
|
||||||
|
#include "TTableLayer.h"
|
||||||
|
|
||||||
|
|
||||||
|
TRamp::TRamp(TPinballTable* table, int groupIndex) : TCollisionComponent(table, groupIndex, false)
|
||||||
|
{
|
||||||
|
visualStruct visual{};
|
||||||
|
vector2 wall1End{}, wall1Start{}, wall2Start{}, wall2End{};
|
||||||
|
|
||||||
|
MessageField = 0;
|
||||||
|
UnusedBaseFlag = 1;
|
||||||
|
loader::query_visual(groupIndex, 0, &visual);
|
||||||
|
CollisionGroup = visual.CollisionGroup;
|
||||||
|
|
||||||
|
BallFieldMult = loader::query_float_attribute(groupIndex, 0, 701, 0.2f);
|
||||||
|
BallZOffsetFlag = static_cast<int>(loader::query_float_attribute(groupIndex, 0, 1305, 0));
|
||||||
|
|
||||||
|
auto floatArr3Plane = loader::query_float_attribute(groupIndex, 0, 1300);
|
||||||
|
RampPlaneCount = static_cast<int>(floor(*floatArr3Plane));
|
||||||
|
RampPlane = reinterpret_cast<ramp_plane_type*>(floatArr3Plane + 1);
|
||||||
|
|
||||||
|
auto wall0Arr = loader::query_float_attribute(groupIndex, 0, 1303);
|
||||||
|
auto wall0CollisionGroup = 1 << static_cast<int>(floor(wall0Arr[0]));
|
||||||
|
auto wall0Pts = reinterpret_cast<wall_point_type*>(wall0Arr + 2);
|
||||||
|
Line1 = new TLine(this, &ActiveFlag, wall0CollisionGroup, wall0Pts->Pt1, wall0Pts->Pt0);
|
||||||
|
Line1->WallValue = nullptr;
|
||||||
|
Line1->place_in_grid(&AABB);
|
||||||
|
EdgeList.push_back(Line1);
|
||||||
|
|
||||||
|
auto wall1Arr = loader::query_float_attribute(groupIndex, 0, 1301);
|
||||||
|
Wall1CollisionGroup = 1 << static_cast<int>(floor(wall1Arr[0]));
|
||||||
|
auto wall1Enabled = static_cast<int>(floor(wall1Arr[1]));
|
||||||
|
Wall1BallOffset = wall1Arr[7];
|
||||||
|
maths::find_closest_edge(
|
||||||
|
RampPlane,
|
||||||
|
RampPlaneCount,
|
||||||
|
reinterpret_cast<wall_point_type*>(wall1Arr + 3),
|
||||||
|
wall1End,
|
||||||
|
wall1Start);
|
||||||
|
|
||||||
|
Line2 = new TLine(this, &ActiveFlag, CollisionGroup, wall1Start, wall1End);
|
||||||
|
Line2->WallValue = nullptr;
|
||||||
|
Line2->place_in_grid(&AABB);
|
||||||
|
EdgeList.push_back(Line2);
|
||||||
|
|
||||||
|
auto wall2Arr = loader::query_float_attribute(groupIndex, 0, 1302);
|
||||||
|
Wall2CollisionGroup = 1 << static_cast<int>(floor(wall2Arr[0]));
|
||||||
|
auto wall2Enabled = static_cast<int>(floor(wall2Arr[1]));
|
||||||
|
Wall2BallOffset = wall2Arr[7];
|
||||||
|
maths::find_closest_edge(
|
||||||
|
RampPlane,
|
||||||
|
RampPlaneCount,
|
||||||
|
reinterpret_cast<wall_point_type*>(wall2Arr + 3),
|
||||||
|
wall2End,
|
||||||
|
wall2Start);
|
||||||
|
|
||||||
|
Line3 = new TLine(this, &ActiveFlag, CollisionGroup, wall2Start, wall2End);
|
||||||
|
Line3->WallValue = nullptr;
|
||||||
|
Line3->place_in_grid(&AABB);
|
||||||
|
EdgeList.push_back(Line3);
|
||||||
|
|
||||||
|
|
||||||
|
auto xMin = 1000000000.0f;
|
||||||
|
auto yMin = 1000000000.0f;
|
||||||
|
auto yMax = -1000000000.0f;
|
||||||
|
auto xMax = -1000000000.0f;
|
||||||
|
for (auto index = 0; index < RampPlaneCount; index++)
|
||||||
|
{
|
||||||
|
auto& plane = RampPlane[index];
|
||||||
|
vector2* pointOrder[4] = { &plane.V1, &plane.V2, &plane.V3, &plane.V1 };
|
||||||
|
|
||||||
|
xMin = std::min(std::min(std::min(plane.V3.X, plane.V1.X), plane.V2.X), xMin);
|
||||||
|
yMin = std::min(std::min(std::min(plane.V3.Y, plane.V1.Y), plane.V2.Y), xMin); // Sic
|
||||||
|
xMax = std::max(std::max(std::max(plane.V3.X, plane.V1.X), plane.V2.X), xMin);
|
||||||
|
yMax = std::max(std::max(std::max(plane.V3.Y, plane.V1.Y), plane.V2.Y), xMin);
|
||||||
|
|
||||||
|
for (auto pt = 0; pt < 3; pt++)
|
||||||
|
{
|
||||||
|
auto& point1 = *pointOrder[pt], point2 = *pointOrder[pt + 1];
|
||||||
|
auto collisionGroup = 0;
|
||||||
|
|
||||||
|
if (point1 == wall1End && point2 == wall1Start)
|
||||||
|
{
|
||||||
|
if (wall1Enabled)
|
||||||
|
collisionGroup = Wall1CollisionGroup;
|
||||||
|
}
|
||||||
|
else if (point1 == wall2End && point2 == wall2Start)
|
||||||
|
{
|
||||||
|
if (wall2Enabled)
|
||||||
|
collisionGroup = Wall2CollisionGroup;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
collisionGroup = visual.CollisionGroup;
|
||||||
|
|
||||||
|
if (collisionGroup)
|
||||||
|
{
|
||||||
|
auto line = new TLine(this, &ActiveFlag, collisionGroup, point1, point2);
|
||||||
|
line->WallValue = &plane;
|
||||||
|
line->place_in_grid(&AABB);
|
||||||
|
EdgeList.push_back(line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
plane.FieldForce.X = cos(plane.GravityAngle2) * sin(plane.GravityAngle1) *
|
||||||
|
PinballTable->GravityDirVectMult;
|
||||||
|
plane.FieldForce.Y = sin(plane.GravityAngle2) * sin(plane.GravityAngle1) *
|
||||||
|
PinballTable->GravityDirVectMult;
|
||||||
|
}
|
||||||
|
|
||||||
|
Field.ActiveFlag = &ActiveFlag;
|
||||||
|
Field.CollisionComp = this;
|
||||||
|
Field.CollisionGroup = visual.CollisionGroup;
|
||||||
|
|
||||||
|
auto x1 = xMax;
|
||||||
|
auto y1 = yMax;
|
||||||
|
auto x0 = xMin;
|
||||||
|
auto y0 = yMin;
|
||||||
|
TTableLayer::edges_insert_square(y0, x0, y1, x1, nullptr, &Field);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TRamp::Collision(TBall* ball, vector2* nextPosition, vector2* direction, float distance, TEdgeSegment* edge)
|
||||||
|
{
|
||||||
|
ball->not_again(edge);
|
||||||
|
ball->Position.X = nextPosition->X;
|
||||||
|
ball->Position.Y = nextPosition->Y;
|
||||||
|
ball->RayMaxDistance -= distance;
|
||||||
|
|
||||||
|
auto plane = static_cast<ramp_plane_type*>(edge->WallValue);
|
||||||
|
if (plane)
|
||||||
|
{
|
||||||
|
ball->CollisionFlag = 1;
|
||||||
|
ball->CollisionOffset.X = plane->BallCollisionOffset.X;
|
||||||
|
ball->CollisionOffset.Y = plane->BallCollisionOffset.Y;
|
||||||
|
ball->CollisionOffset.Z = plane->BallCollisionOffset.Z;
|
||||||
|
ball->RampFieldForce.X = plane->FieldForce.X;
|
||||||
|
ball->RampFieldForce.Y = plane->FieldForce.Y;
|
||||||
|
ball->Position.Z = ball->Position.X * ball->CollisionOffset.X + ball->Position.Y * ball->CollisionOffset.Y +
|
||||||
|
ball->Radius + ball->CollisionOffset.Z;
|
||||||
|
ball->CollisionMask = CollisionGroup;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (edge == Line1)
|
||||||
|
{
|
||||||
|
if (!PinballTable->TiltLockFlag)
|
||||||
|
{
|
||||||
|
loader::play_sound(SoftHitSoundId, ball, "TRamp");
|
||||||
|
control::handler(MessageCode::ControlCollision, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ball->CollisionFlag = 0;
|
||||||
|
if (edge == Line2)
|
||||||
|
{
|
||||||
|
ball->CollisionMask = Wall1CollisionGroup;
|
||||||
|
if (BallZOffsetFlag)
|
||||||
|
ball->Position.Z = ball->Radius + Wall1BallOffset;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ball->CollisionMask = Wall2CollisionGroup;
|
||||||
|
if (BallZOffsetFlag)
|
||||||
|
ball->Position.Z = ball->Radius + Wall2BallOffset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int TRamp::FieldEffect(TBall* ball, vector2* vecDst)
|
||||||
|
{
|
||||||
|
vecDst->X = ball->RampFieldForce.X - ball->Direction.X * ball->Speed * BallFieldMult;
|
||||||
|
vecDst->Y = ball->RampFieldForce.Y - ball->Direction.Y * ball->Speed * BallFieldMult;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TRamp::port_draw()
|
||||||
|
{
|
||||||
|
TCollisionComponent::port_draw();
|
||||||
|
}
|
||||||
31
SpaceCadetPinball/TRamp.h
Normal file
31
SpaceCadetPinball/TRamp.h
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "TCollisionComponent.h"
|
||||||
|
#include "TEdgeManager.h"
|
||||||
|
|
||||||
|
struct ramp_plane_type;
|
||||||
|
|
||||||
|
class TRamp :
|
||||||
|
public TCollisionComponent
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
TRamp(TPinballTable* table, int groupIndex);
|
||||||
|
void Collision(TBall* ball, vector2* nextPosition, vector2* direction, float distance,
|
||||||
|
TEdgeSegment* edge) override;
|
||||||
|
int FieldEffect(TBall* ball, vector2* vecDst) override;
|
||||||
|
void port_draw() override;
|
||||||
|
|
||||||
|
field_effect_type Field{};
|
||||||
|
int CollisionGroup;
|
||||||
|
bool BallZOffsetFlag;
|
||||||
|
int RampPlaneCount;
|
||||||
|
float BallFieldMult;
|
||||||
|
ramp_plane_type* RampPlane;
|
||||||
|
TEdgeSegment* Line2;
|
||||||
|
TEdgeSegment* Line3;
|
||||||
|
TEdgeSegment* Line1;
|
||||||
|
int Wall1CollisionGroup;
|
||||||
|
int Wall2CollisionGroup;
|
||||||
|
float Wall1BallOffset;
|
||||||
|
float Wall2BallOffset;
|
||||||
|
};
|
||||||
78
SpaceCadetPinball/TRollover.cpp
Normal file
78
SpaceCadetPinball/TRollover.cpp
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
#include "pch.h"
|
||||||
|
#include "TRollover.h"
|
||||||
|
|
||||||
|
|
||||||
|
#include "control.h"
|
||||||
|
#include "gdrv.h"
|
||||||
|
#include "loader.h"
|
||||||
|
#include "render.h"
|
||||||
|
#include "TBall.h"
|
||||||
|
#include "TEdgeSegment.h"
|
||||||
|
#include "timer.h"
|
||||||
|
#include "TPinballTable.h"
|
||||||
|
|
||||||
|
TRollover::TRollover(TPinballTable* table, int groupIndex, bool createWall) : TCollisionComponent(
|
||||||
|
table, groupIndex, createWall)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
TRollover::TRollover(TPinballTable* table, int groupIndex) : TCollisionComponent(table, groupIndex, false)
|
||||||
|
{
|
||||||
|
SpriteSet(0);
|
||||||
|
build_walls(groupIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int TRollover::Message(MessageCode code, float value)
|
||||||
|
{
|
||||||
|
if (code == MessageCode::Reset)
|
||||||
|
{
|
||||||
|
ActiveFlag = 1;
|
||||||
|
RolloverFlag = 0;
|
||||||
|
SpriteSet(0);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TRollover::Collision(TBall* ball, vector2* nextPosition, vector2* direction, float distance,
|
||||||
|
TEdgeSegment* edge)
|
||||||
|
{
|
||||||
|
ball->Position.X = nextPosition->X;
|
||||||
|
ball->Position.Y = nextPosition->Y;
|
||||||
|
ball->RayMaxDistance -= distance;
|
||||||
|
ball->not_again(edge);
|
||||||
|
|
||||||
|
if (!PinballTable->TiltLockFlag)
|
||||||
|
{
|
||||||
|
if (RolloverFlag)
|
||||||
|
{
|
||||||
|
timer::set(0.1f, this, TimerExpired);
|
||||||
|
ActiveFlag = 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
loader::play_sound(SoftHitSoundId, ball, "TRollover");
|
||||||
|
control::handler(MessageCode::ControlCollision, this);
|
||||||
|
}
|
||||||
|
RolloverFlag = RolloverFlag == 0;
|
||||||
|
SpriteSet(RolloverFlag ? -1 : 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TRollover::build_walls(int groupIndex)
|
||||||
|
{
|
||||||
|
visualStruct visual{};
|
||||||
|
|
||||||
|
loader::query_visual(groupIndex, 0, &visual);
|
||||||
|
float* arr1 = loader::query_float_attribute(groupIndex, 0, 600);
|
||||||
|
TEdgeSegment::install_wall(arr1, this, &ActiveFlag, visual.CollisionGroup, 0.0, 600);
|
||||||
|
float* arr2 = loader::query_float_attribute(groupIndex, 0, 603);
|
||||||
|
TEdgeSegment::install_wall(arr2, this, &RolloverFlag, visual.CollisionGroup, 0.0, 603);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TRollover::TimerExpired(int timerId, void* caller)
|
||||||
|
{
|
||||||
|
auto roll = static_cast<TRollover*>(caller);
|
||||||
|
roll->ActiveFlag = 1;
|
||||||
|
}
|
||||||
20
SpaceCadetPinball/TRollover.h
Normal file
20
SpaceCadetPinball/TRollover.h
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "TCollisionComponent.h"
|
||||||
|
|
||||||
|
class TRollover :
|
||||||
|
public TCollisionComponent
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
TRollover(TPinballTable* table, int groupIndex, bool createWall);
|
||||||
|
public:
|
||||||
|
TRollover(TPinballTable* table, int groupIndex);
|
||||||
|
~TRollover() override = default;
|
||||||
|
int Message(MessageCode code, float value) override;
|
||||||
|
void Collision(TBall* ball, vector2* nextPosition, vector2* direction, float distance,
|
||||||
|
TEdgeSegment* edge) override;
|
||||||
|
void build_walls(int groupIndex);
|
||||||
|
|
||||||
|
static void TimerExpired(int timerId, void* caller);
|
||||||
|
|
||||||
|
char RolloverFlag{};
|
||||||
|
};
|
||||||
91
SpaceCadetPinball/TSink.cpp
Normal file
91
SpaceCadetPinball/TSink.cpp
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
#include "pch.h"
|
||||||
|
#include "TSink.h"
|
||||||
|
|
||||||
|
|
||||||
|
#include "control.h"
|
||||||
|
#include "loader.h"
|
||||||
|
#include "render.h"
|
||||||
|
#include "TPinballTable.h"
|
||||||
|
#include "TBall.h"
|
||||||
|
#include "TDrain.h"
|
||||||
|
#include "timer.h"
|
||||||
|
|
||||||
|
TSink::TSink(TPinballTable* table, int groupIndex) : TCollisionComponent(table, groupIndex, true)
|
||||||
|
{
|
||||||
|
visualStruct visual{};
|
||||||
|
|
||||||
|
MessageField = 0;
|
||||||
|
loader::query_visual(groupIndex, 0, &visual);
|
||||||
|
BallThrowDirection = visual.Kicker.ThrowBallDirection;
|
||||||
|
ThrowAngleMult = visual.Kicker.ThrowBallAngleMult;
|
||||||
|
ThrowSpeedMult1 = visual.Kicker.Boost;
|
||||||
|
ThrowSpeedMult2 = visual.Kicker.ThrowBallMult * 0.01f;
|
||||||
|
SoundIndex4 = visual.SoundIndex4;
|
||||||
|
SoundIndex3 = visual.SoundIndex3;
|
||||||
|
auto floatArr = loader::query_float_attribute(groupIndex, 0, 601);
|
||||||
|
BallPosition.X = floatArr[0];
|
||||||
|
BallPosition.Y = floatArr[1];
|
||||||
|
TimerTime = *loader::query_float_attribute(groupIndex, 0, 407);
|
||||||
|
}
|
||||||
|
|
||||||
|
int TSink::Message(MessageCode code, float value)
|
||||||
|
{
|
||||||
|
switch (code)
|
||||||
|
{
|
||||||
|
case MessageCode::TSinkResetTimer:
|
||||||
|
if (value < 0.0f)
|
||||||
|
value = TimerTime;
|
||||||
|
timer::set(value, this, TimerExpired);
|
||||||
|
break;
|
||||||
|
case MessageCode::PlayerChanged:
|
||||||
|
timer::kill(TimerExpired);
|
||||||
|
PlayerMessagefieldBackup[PinballTable->CurrentPlayer] = MessageField;
|
||||||
|
MessageField = PlayerMessagefieldBackup[static_cast<int>(floor(value))];
|
||||||
|
break;
|
||||||
|
case MessageCode::Reset:
|
||||||
|
{
|
||||||
|
timer::kill(TimerExpired);
|
||||||
|
MessageField = 0;
|
||||||
|
for (auto &msgBackup : PlayerMessagefieldBackup)
|
||||||
|
msgBackup = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TSink::Collision(TBall* ball, vector2* nextPosition, vector2* direction, float distance, TEdgeSegment* edge)
|
||||||
|
{
|
||||||
|
if (PinballTable->TiltLockFlag)
|
||||||
|
{
|
||||||
|
PinballTable->Drain->Collision(ball, nextPosition, direction, distance, edge);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ball->Disable();
|
||||||
|
loader::play_sound(SoundIndex4, ball, "TSink1");
|
||||||
|
control::handler(MessageCode::ControlCollision, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TSink::TimerExpired(int timerId, void* caller)
|
||||||
|
{
|
||||||
|
auto sink = static_cast<TSink*>(caller);
|
||||||
|
auto table = sink->PinballTable;
|
||||||
|
if (table->BallCountInRect(sink->BallPosition, table->CollisionCompOffset * 2.0f))
|
||||||
|
{
|
||||||
|
timer::set(0.5f, sink, TimerExpired);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
auto ball = table->AddBall(sink->BallPosition);
|
||||||
|
assertm(ball, "Failure to create ball in sink");
|
||||||
|
ball->CollisionDisabledFlag = true;
|
||||||
|
ball->throw_ball(&sink->BallThrowDirection, sink->ThrowAngleMult, sink->ThrowSpeedMult1,
|
||||||
|
sink->ThrowSpeedMult2);
|
||||||
|
if (sink->SoundIndex3)
|
||||||
|
loader::play_sound(sink->SoundIndex3, ball, "TSink2");
|
||||||
|
}
|
||||||
|
}
|
||||||
25
SpaceCadetPinball/TSink.h
Normal file
25
SpaceCadetPinball/TSink.h
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "maths.h"
|
||||||
|
#include "TCollisionComponent.h"
|
||||||
|
|
||||||
|
class TSink :
|
||||||
|
public TCollisionComponent
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
TSink(TPinballTable* table, int groupIndex);
|
||||||
|
int Message(MessageCode code, float value) override;
|
||||||
|
void Collision(TBall* ball, vector2* nextPosition, vector2* direction, float distance,
|
||||||
|
TEdgeSegment* edge) override;
|
||||||
|
|
||||||
|
static void TimerExpired(int timerId, void* caller);
|
||||||
|
|
||||||
|
float TimerTime;
|
||||||
|
vector2 BallPosition{};
|
||||||
|
vector3 BallThrowDirection{};
|
||||||
|
float ThrowAngleMult;
|
||||||
|
float ThrowSpeedMult1;
|
||||||
|
float ThrowSpeedMult2;
|
||||||
|
int SoundIndex4;
|
||||||
|
int SoundIndex3;
|
||||||
|
int PlayerMessagefieldBackup[4]{};
|
||||||
|
};
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user