Compare commits

...

99 Commits

Author SHA1 Message Date
Muzychenko Andrey 52126feb40 Added SDL game controller DB.
Issue #202.
2024-02-28 08:16:36 +03:00
Muzychenko Andrey 73262ae207 Fixed initial input focus for hidden menu.
Issue #201
2024-01-27 14:23:42 +03:00
Muzychenko Andrey 75d2d98a46 Fixed plunger pullback speed in 3DPB mode.
Issue #193
2023-10-18 08:37:54 +03:00
Muzychenko Andrey 6a30ccbef1 Preparing for release 2.1.0
Added CMake policy version.
2023-10-16 09:14:43 +03:00
Muzychenko Andrey 350651dae6 Fixed MSVC warnings, added Windows build script. 2023-09-05 10:51:19 +03:00
Orazio 6ab7b3e772
Build script for self-contained Mac .app (#185)
* Add CMake settings to build universal binary and find SDL in app bundle

* Source frameworks from extern folder

* Add build script, info plist and big sur style icon

* Add example build workflow (app won't be signed)

* Use macOS 11 runner for better compatibility

* Use better name for the build script, move metadata to Platform/macOS/ folder

* Improve macOS build instructions

* Fix workflow syntax and use a more specific glob pattern too

* Add libs search paths to CMakeLists.txt instead of find files

* Use same /Libs folder as libs search path

* Make build-mac-app.sh not fail on repeated runs

* Make build-mac-app.sh executable
2023-08-01 09:01:56 +03:00
Muzychenko Andrey e2f3ae66f8 Added hide mouse cursor option.
Issue #181.
2023-07-11 15:34:51 +03:00
Muzychenko Andrey 18c80a0ff8 Fixed plunger pullback following FT rules in 3DPB mode.
Issue #179.
2023-07-11 12:25:22 +03:00
Adam 62e20b1cf9
updated Icon_1.ico with 256x256 render for modern Windows using IcoFX (#180)
* updated Icon_1.ico with 256x256 render for modern Windows using IcoFX portable

* new source (using ai to resize) and new shadow (with no edge clash)

* Re-imported 256x256 icon

---------

Co-authored-by: Muzychenko Andrey <33288308+k4zmu2a@users.noreply.github.com>
2023-07-11 11:51:16 +03:00
Muzychenko Andrey 93d8e12782
Update README.md
Updated development plans.
2023-07-03 11:49:55 +03:00
Muzychenko Andrey 8c774722eb
Update SpaceCadetPinball.metainfo.xml
Shortened summary and removed period.
#178
2023-07-03 11:35:37 +03:00
Muzychenko Andrey a937ff5c80 FT control part1: mission and control scores, jackpot handling, hyperspace. 2023-06-25 10:35:04 +03:00
Muzychenko Andrey e466bbacb8 Added credits from FT about dialog. 2023-03-26 14:35:27 +03:00
Muzychenko Andrey 34a1e32843 Added in-game credits from Full Tilt.
This was kind-of requested a long time ago.
2023-03-17 16:38:08 +03:00
Muzychenko Andrey 43e2ab896b FT collision part6: fixes and cleanup. 2023-03-13 10:54:33 +03:00
Muzychenko Andrey e0424bed65 FT collision part5: cleanup. 2023-03-13 08:25:49 +03:00
Muzychenko Andrey c5acdcd524 FT collision part4: ball to ball collision.
TBall uses multiple inheritance, interesting.
2023-03-12 11:12:41 +03:00
Muzychenko Andrey f521a03322 Removed confirmation for exit done from the menu.
Issue #173.
2023-03-10 09:28:28 +03:00
Muzychenko Andrey 4b86fe2aa7 FT collision part3: cleanup. 2023-03-05 14:16:07 +03:00
Muzychenko Andrey ba470e8727 FT collision part2: added most of the FT collision system.
Aka "World's most expensive flippers".
This is an aggregate of collision-related changes made during 3DPB->FT transition.
The most important part is in flipper collision - a shift from monolithic iterative solver in TFlipperEdge::EdgeCollision to a distributed non-iterative solver.
Both 3DPB and FT data sets use FT collision, keeping two collision systems does not make much sense.
From user perspective, FT/3DPB systems should not have any major differences.
2023-03-04 17:31:23 +03:00
Muzychenko Andrey 466c875f8a Added a way to reset all game options.
Fixed some of the fragmented translations.
Improved input rejection in control dialog.
2023-02-17 13:35:18 +03:00
Muzychenko Andrey 1749a2ba09 Future-proof SDL controller enum range check.
Issue #172.
2023-02-12 09:01:20 +03:00
Muzychenko Andrey 831c3f49bf Fixed controller enum use for older SDL versions.
Issue #172.
2023-02-12 08:45:33 +03:00
Muzychenko Andrey 9454e11fd9 Adjusted exit key: added shortcut binding, confirmation dialog. 2023-02-12 08:43:08 +03:00
Muzychenko Andrey 215599684c Options refactor part 3: added key bindings for menu shortcuts.
Issue #168.
2023-02-11 13:18:29 +03:00
Muzychenko Andrey 8df996f452 Options refactor part 2.1: fixed control option labels. 2023-02-10 08:46:09 +03:00
Muzychenko Andrey d99fbb092e Options refactor part 2: input bindings. 2023-01-27 16:25:47 +03:00
Muzychenko Andrey 4192b12c29
Removed dead sourceport link (Web by stech11845)
Ref issue #169
2023-01-26 08:16:18 +03:00
Muzychenko Andrey 10ff1143cc Refactored options: symmetric save/load. 2023-01-03 16:42:34 +03:00
Muzychenko Andrey 2d6f2c14e5 FT collision part1: AABB. 2022-12-28 08:47:44 +03:00
Muzychenko Andrey 17f11bd428 Improved frame timing display. 2022-12-13 15:06:27 +03:00
Muzychenko Andrey 6aa6472667 Added hardware ImGui renderer. 2022-12-11 09:32:40 +03:00
Muzychenko Andrey ab3f3bd12b Updated ImGui to v1.89.2 WIP.
Fixed IdxOffset support in imgui_sdl.
2022-12-11 07:57:49 +03:00
Muzychenko Andrey 3109a8ea75 Improved player name entry in high score dialog.
Issue #165.
2022-12-09 13:01:33 +03:00
Muzychenko Andrey 2162cac977 Moved SDL mixer initialization out of partial restart loop.
This might help with issue #167.
2022-12-02 14:46:22 +03:00
Muzychenko Andrey 8e43d06e84 Improved console output: added version constants and message box texts. 2022-12-02 08:21:08 +03:00
Muzychenko Andrey 31530bef18 Added WSL build configuration for VS. 2022-11-30 08:19:20 +03:00
Muzychenko Andrey 3be26282b3 Fixed lower case .dat file loading.
Ref #164.
2022-11-22 16:31:38 +03:00
Low-power f561cadf63
Allow loading data files with lowercase name (#164)
* Allow loading data files with lowercase name

* Added lower case support for all game data files.

Co-authored-by: Muzychenko Andrey <33288308+k4zmu2a@users.noreply.github.com>
2022-11-22 12:40:50 +03:00
Muzychenko Andrey 1391eeba81 Reordered pch includes in Sound.
This might help with #162.
Fixed switch warning.
2022-11-21 10:02:56 +03:00
Muzychenko Andrey ea32c08c4f Added “easy mode” cheat, aka frustration-free mode.
It involves always on center post and never closing kicker gates.
Issue #161.
2022-11-21 09:49:15 +03:00
Muzychenko Andrey 80947888a0 Optimized table restart, external font loading.
Fixed memory leak related to restart.
Added window to table size adjustment on init, imperfect.
2022-11-17 15:43:59 +03:00
Low-power 6486589c4a
Fix null pointer dereferencing in SDL_GetPrefPath(3) (#163) 2022-11-17 10:10:24 +03:00
Muzychenko Andrey 3c6e1c9d47 Added rate limiter for SDL error messages.
Workaround for issue #158.
2022-10-11 13:39:33 +03:00
Muzychenko Andrey cfaab257ed Added debug overlay for ball sprite size cutoff points. 2022-10-11 12:45:03 +03:00
Muzychenko Andrey 4ec30cf472 Render tweaks part 3: bitmap/zMap pairs. 2022-09-29 14:45:14 +03:00
Natty e7ddebd16c
Add FullTilt lane light behavior (#157)
* Add FullTilt lane light behavior

* Replaced spaces with tabs
2022-09-23 07:42:19 +03:00
Muzychenko Andrey 7003b01e5d Render tweaks part 2: sprite set by index. 2022-09-22 17:46:00 +03:00
Muzychenko Andrey 9f0ae0434e Render tweaks part 1: refactored sprite struct.
Optimized sprite handling in render.
Fixed switch warning in control.
2022-09-21 16:43:45 +03:00
Harmann Gabrielian 1e43bdd5fa
Minor typo fixes in Russian translation (#156)
Self-explanatory.
2022-09-16 07:51:46 +03:00
Muzychenko Andrey 40672845e4 Message code enum part 6: final touches + control light cleanup.
MessageField is often used as int, so it stays unchanged.
2022-09-08 10:51:33 +03:00
Muzychenko Andrey 22603aa126 Message code enum part 5: control codes. 2022-09-07 16:01:38 +03:00
Muzychenko Andrey dfe1665ba1 Message code enum part 4: finalized transition of Message to enum class. 2022-09-06 16:57:56 +03:00
Muzychenko Andrey e80010e3c6 Message code enum part 3: light and light group. 2022-09-06 16:48:09 +03:00
Muzychenko Andrey 803ca14ef2 Message code enum part 2: all components except for lights. 2022-09-06 11:58:35 +03:00
Muzychenko Andrey 44d5fd5097 Message code enum part 1: global messages and some hacks. 2022-09-05 10:17:37 +03:00
Harmann Gabrielian 69fd91f003
Russian translation overhaul (#154)
* Russian translation overhaul

Most of strings were renamed according to the official localisation (bundled with russian WinXP distros, manual only), some of them I had to adapt myself.

* Rollback wormhole translation.

Co-authored-by: Muzychenko Andrey <33288308+k4zmu2a@users.noreply.github.com>
2022-09-02 07:34:54 +03:00
Muzychenko Andrey 42226a14c9 Simplified get_rc_string, merged pinball and pb. 2022-08-31 15:18:22 +03:00
Muzychenko Andrey 88f835d068 Removed unused translated texts.
Fixed translation.h include leak.
Added TTextBox font color support.
2022-08-31 11:11:21 +03:00
Alexis Murzeau 66a868083a
Add translations (#153)
* Add translations from v1

* Add font configuration (to be able to use non-latin languages)

* translations: remove includes that are already in pch.h

* translations: rename enums and avoid macros

* Fix crash when the font file doesn't exist

* translations: avoid u8 to avoid reencoding by MSVC

MSVC will read the file as ASCII and reconvert characters as UTF-8, this will corrupt characters as the file is in fact already in UTF-8.

* translations: remove NUMBER in enums

* translations: handle non existing translations gracefuly (don't crash)

Fallback to english if available, else return empty string

* Testing pull collaboration.

* Rollback: remove NUMBER in enums.

* Get rid of namespace, use header instead.

* Collapsed translated text struct and array.

* Fixed build errors and warnings.

* Simplified language list.

* All new types, locals and globals should use CamelCase.

* Removed unnecessary ImGui patch.

* Rearranged TTextBox immediate mode draw.

* Final touches: removed unused declaration in gdrv.
Removed unused Msg entries and added new check.

* Remove placeholder english texts from missing translations

Co-authored-by: Muzychenko Andrey <33288308+k4zmu2a@users.noreply.github.com>
2022-08-31 07:58:03 +03:00
Muzychenko Andrey c1c74878df Multiball part 1: control and component changes from FT.
The result is 3DPB/FT hybrid, with control closer to 3DPB and components closer to FT.
2022-08-25 17:09:17 +03:00
Muzychenko Andrey 14a8d64b67 TLight: cleanup, code from FT, new test commands. 2022-08-24 13:32:35 +03:00
Muzychenko Andrey acd1ad34b2 Code from FT: simplified TFlipper sprite update.
TFlipperEdge moving geometry stored in object.
2022-08-23 08:14:28 +03:00
Muzychenko Andrey 7feba1e947 Code from FT: simplified score access in TPinballComponent. 2022-08-18 16:23:29 +03:00
stech11 (SoftOrange Tech) e9a4791322
Update README.md (#147) 2022-08-09 10:21:01 +03:00
Alexis Murzeau a2567c1fea
Fix flipper animation and angle calculation (#150)
Checked with a slowed down flipper (reduced retractTime and extendTime)
to ensure the flipper position is correct even when not finished while
pressing the flipper control.
2022-08-09 10:04:51 +03:00
Alexis Murzeau 367f4538a3
fix gui not responding when the game is paused (#151) 2022-08-09 08:26:15 +03:00
Muzychenko Andrey 54a217c27b Fixed build with new SDL_mixer versions.
Issue #145.
2022-07-18 09:45:46 +03:00
sasodoma 6f00b57eb9
Change strings from Commation to Commendation, as it is in the original game (#144) 2022-07-11 10:09:57 +03:00
Federico Matteoni 1a610ba831
Added fexed's Android port (#143)
Forked from iscle's port
2022-07-06 09:47:47 +03:00
Muzychenko Andrey eed3662592 Fixed HardHit detection in DefaultCollision.
Issue #141.
2022-06-15 09:10:24 +03:00
Muzychenko Andrey 5e42f37fba Fixed sound duration for missing sounds.
Issue #140.
2022-06-14 11:46:11 +03:00
Muzychenko Andrey 8017734de4 Switched positional audio to collision coordinate system.
Refactored positional audio.
2022-06-01 16:19:27 +03:00
Muzychenko Andrey c93e11ee6b Added sprite positions to debug overlay. 2022-05-31 11:34:04 +03:00
Muzychenko Andrey 5d7d7c0822 Cleaned up positional sound. 2022-05-30 11:23:47 +03:00
Patrice Levesque a4c6165094
Implement stereo sound. (#138)
* Implement stereo sound.

Original Space Cadet has mono sound.  To achieve stereo, the following
steps were accomplished:

- Add a game option to turn on/off stereo sound.  Default is on.

- TPinballComponent objects were extended with a method called
  get_coordinates() that returns a single 2D point, approximating the
  on-screen position of the object, re-mapped between 0 and 1 vertically
  and horizontally, {0, 0} being at the top-left.

    - For static objects like bumpers and lights, the coordinate refers
      to the geometric center of the corresponding graphic sprite, and
      is precalculated at initialization.

    - For ball objects, the coordinate refers to the geometric center of
      the ball, calculated during play when requested.

- Extend all calls to sound-playing methods so that they include a
  TPinballComponent* argument that refers to the sound source, e.g.
  where the sound comes from.  For instance, when a flipper is
  activated, its method call to emit a sound now includes a reference to
  the flipper object; when a ball goes under a SkillShotGate, its method
  call to emit a sound now includes a reference to the corresponding
  light; and so on.

  For some cases, like light rollovers, the sound source is taken from
  the ball that triggered the light rollover.

  For other cases, like holes, flags and targets, the sound source is
  taken from the object itself.

  For some special cases like ramp activation, sound source is
  taken from the nearest light position that makes sense.

  For all game-progress sounds, like mission completion sounds or ball
  drain sounds, the sound source is undefined (set to nullptr), and the
  Sound::PlaySound() method takes care of positioning them at a default
  location, where speakers on a pinball machine normally are.

- Make the Sound::PlaySound() method accept a new argument, a
  TPinballComponent reference, as described above.

  If the stereo option is turned on, the Sound::PlaySound() method calls
  the get_coordinates() method of the TPinballComponent reference to get
  the sound position.

  This project uses SDL_mixer and there is a function called
  Mix_SetPosition() that allows placing a sound in the stereo field, by
  giving it a distance and an angle.

  We arbitrarily place the player's ears at the bottom of the table; we
  set the ears' height to half a table's length.  Intensity of the
  stereo effect is directly related to this value; the farther the
  player's ears from the table, the narrowest the stereo picture gets,
  and vice-versa.

  From there we have all we need to calculate distance and angle; we do
  just that and position all the sounds.

* Copy-paste typo fix.
2022-05-30 10:35:29 +03:00
Muzychenko Andrey cfe2691892 Optimized SDL_RenderDrawCircle.
Change mouse warping strategy in hidden test cheat.
2022-05-27 13:54:36 +03:00
Muzychenko Andrey 4183e7f0bf Refactored midi multiple track support.
Cleaned up TCollisionComponent.
Issue #129.
2022-05-23 12:45:18 +03:00
Muzychenko Andrey e283a643b3 Added support for multiple music tracks in FT mode.
Note that taba3 is not currently played as it needs multiball support.
Issue #129.
2022-05-20 19:32:09 +03:00
Muzychenko Andrey 97aea20586 Renamed some collision variables. 2022-05-20 11:51:00 +03:00
Muzychenko Andrey 5461483bb5 Added debug overlay v1.
It features various collision info perspective projected and overlayed on the table.
2022-05-19 14:17:31 +03:00
Muzychenko Andrey 0cb75ecf7f Cleaned up Bresenham line in TLine and TEdgeManager. 2022-05-17 12:36:46 +03:00
Muzychenko Andrey 2d2ca0ab2a Cleaning up maths: part 4.
More by ref args, cleaned up distance_to_flipper, ramp init.
2022-05-16 09:28:35 +03:00
Muzychenko Andrey fdf1f6c9f1 Cleaning up maths: part 3.
Demangled methods, vectors args by ref, added comments, more accurate ray_intersect_line.
2022-05-13 11:15:30 +03:00
Muzychenko Andrey 2d0da712e3 Cleaning up maths: part 2.
Renamed vector2.
2022-05-11 16:47:13 +03:00
Muzychenko Andrey d23444b983 Cleaning up maths: part 1.
Vector3 inherited from vector2.
2022-05-11 16:42:45 +03:00
guijan 3f7526ba12
fix X11 include leak (#136)
This fixes the build on OpenBSD.
2022-05-06 07:58:53 +03:00
guijan cdf0216136
cmake: use ${CMAKE_SOURCE_DIR} in install() (#137) 2022-05-04 08:48:42 +03:00
Muzychenko Andrey cc06d35bc7 Fixed high score insertion for multiple players.
Refactored high_score.
Issue #131.
2022-04-11 10:28:20 +03:00
Stefan 0f88e43ba2
AmigaOS 4 port (#132) 2022-03-31 12:44:03 +03:00
Muzychenko Andrey b20e13ee97 control: reduced component indirection.
cheats: hidden test with tab, FT style.
2022-02-10 16:29:31 +03:00
Muzychenko Andrey a626572da3 Fixed wormhole lights reset on mission abort.
Issue #124.
2022-02-07 16:57:04 +03:00
Stefan 29c84c37c8
MorphOS port (#128) 2022-01-26 08:15:41 +03:00
林博仁(Buo-ren, Lin) 6039f843a7
Fix typo in CONTRIBUTING.md (guarantee) (#123)
Signed-off-by: 林博仁(Buo-ren, Lin) <Buo.Ren.Lin@gmail.com>
2022-01-17 09:14:43 +03:00
Muzychenko Andrey 8f34829b1e High score: rank starts from 1, table borders. 2022-01-12 17:26:31 +03:00
Muzychenko Andrey 0a2d6847ba Added sound and music volume control.
Issue #120.
2022-01-12 17:17:38 +03:00
Muzychenko Andrey 43af97127b Simplified game mode, pause. 2022-01-05 11:38:50 +03:00
Nicola Smaniotto d8ee1b9bfe
fix install directories (#115) 2021-12-29 14:28:03 +03:00
Kowalski Dragon 583262d221
Update SpaceCadetPinball.metainfo.xml (#116) 2021-12-29 14:26:49 +03:00
147 changed files with 25223 additions and 10944 deletions

19
.github/workflows/ReleaseBuilds.yml vendored Normal file
View File

@ -0,0 +1,19 @@
name: Make Release Builds
on: [workflow_dispatch]
jobs:
build-macos:
runs-on: macos-11
timeout-minutes: 15
steps:
- uses: actions/checkout@v3
with:
ref: master
- run: bash build-mac-app.sh
- uses: actions/upload-artifact@v3
with:
name: mac-build
path: SpaceCadetPinball-*-mac.dmg

4
.gitignore vendored
View File

@ -266,7 +266,7 @@ __pycache__/
/Export /Export
/DrMem /DrMem
/Doc private /Doc private
# Windows local libraries # Windows and macOS local libraries
/Libs /Libs
#CMake generated #CMake generated
@ -298,3 +298,5 @@ _deps
.ninja_deps .ninja_deps
.ninja_log .ninja_log
build.ninja build.ninja
# Build directory
/build

20
BuildForWindows.ps1 Normal file
View 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

View File

@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 3.0) cmake_minimum_required(VERSION 3.0...3.5)
project(SpaceCadetPinball) project(SpaceCadetPinball)
set(CMAKE_CXX_STANDARD 11) set(CMAKE_CXX_STANDARD 11)
@ -19,6 +19,15 @@ if(MINGW)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -static") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -static")
endif() endif()
if(APPLE)
set(MACOSX_RPATH)
set(CMAKE_BUILD_WITH_INSTALL_RPATH true)
set(CMAKE_INSTALL_RPATH "@executable_path/../Frameworks")
set(CMAKE_OSX_ARCHITECTURES "arm64;x86_64")
list(APPEND SDL2_PATH "${CMAKE_CURRENT_LIST_DIR}/Libs")
list(APPEND SDL2_MIXER_PATH "${CMAKE_CURRENT_LIST_DIR}/Libs")
endif()
# SDL2main is not needed # SDL2main is not needed
set(SDL2_BUILDING_LIBRARY ON) set(SDL2_BUILDING_LIBRARY ON)
@ -38,6 +47,8 @@ set(SOURCE_FILES
SpaceCadetPinball/control.h SpaceCadetPinball/control.h
SpaceCadetPinball/EmbeddedData.cpp SpaceCadetPinball/EmbeddedData.cpp
SpaceCadetPinball/EmbeddedData.h SpaceCadetPinball/EmbeddedData.h
SpaceCadetPinball/font_selection.cpp
SpaceCadetPinball/font_selection.h
SpaceCadetPinball/fullscrn.cpp SpaceCadetPinball/fullscrn.cpp
SpaceCadetPinball/fullscrn.h SpaceCadetPinball/fullscrn.h
SpaceCadetPinball/gdrv.cpp SpaceCadetPinball/gdrv.cpp
@ -61,8 +72,6 @@ set(SOURCE_FILES
SpaceCadetPinball/pb.cpp SpaceCadetPinball/pb.cpp
SpaceCadetPinball/pb.h SpaceCadetPinball/pb.h
SpaceCadetPinball/pch.h SpaceCadetPinball/pch.h
SpaceCadetPinball/pinball.cpp
SpaceCadetPinball/pinball.h
SpaceCadetPinball/proj.cpp SpaceCadetPinball/proj.cpp
SpaceCadetPinball/proj.h SpaceCadetPinball/proj.h
SpaceCadetPinball/render.cpp SpaceCadetPinball/render.cpp
@ -131,6 +140,8 @@ set(SOURCE_FILES
SpaceCadetPinball/TPopupTarget.h SpaceCadetPinball/TPopupTarget.h
SpaceCadetPinball/TRamp.cpp SpaceCadetPinball/TRamp.cpp
SpaceCadetPinball/TRamp.h SpaceCadetPinball/TRamp.h
SpaceCadetPinball/translations.cpp
SpaceCadetPinball/translations.h
SpaceCadetPinball/TRollover.cpp SpaceCadetPinball/TRollover.cpp
SpaceCadetPinball/TRollover.h SpaceCadetPinball/TRollover.h
SpaceCadetPinball/TSink.cpp SpaceCadetPinball/TSink.cpp
@ -167,15 +178,19 @@ set(SOURCE_FILES
SpaceCadetPinball/imgui_demo.cpp SpaceCadetPinball/imgui_demo.cpp
SpaceCadetPinball/imgui_impl_sdl.cpp SpaceCadetPinball/imgui_impl_sdl.cpp
SpaceCadetPinball/imgui_impl_sdl.h SpaceCadetPinball/imgui_impl_sdl.h
SpaceCadetPinball/imgui_impl_sdlrenderer.h
SpaceCadetPinball/imgui_impl_sdlrenderer.cpp
SpaceCadetPinball/imstb_textedit.h SpaceCadetPinball/imstb_textedit.h
SpaceCadetPinball/imstb_rectpack.h SpaceCadetPinball/imstb_rectpack.h
SpaceCadetPinball/imstb_truetype.h SpaceCadetPinball/imstb_truetype.h
SpaceCadetPinball/DebugOverlay.cpp
SpaceCadetPinball/DebugOverlay.h
) )
# On Windows, include resource file with the icon # On Windows, include resource file with the icon
if(WIN32) if(WIN32)
set_source_files_properties(SpaceCadetPinball/SpaceCadetPinball.rc LANGUAGE RC) set_source_files_properties(SpaceCadetPinball/SpaceCadetPinball.rc LANGUAGE RC)
list(APPEND SOURCE_FILES SpaceCadetPinball/SpaceCadetPinball.rc) list(APPEND SOURCE_FILES SpaceCadetPinball/SpaceCadetPinball.rc)
endif(WIN32) endif(WIN32)
add_executable(SpaceCadetPinball ${SOURCE_FILES}) add_executable(SpaceCadetPinball ${SOURCE_FILES})
@ -189,6 +204,7 @@ set_source_files_properties(
SpaceCadetPinball/imgui_tables.cpp SpaceCadetPinball/imgui_tables.cpp
SpaceCadetPinball/imgui_demo.cpp SpaceCadetPinball/imgui_demo.cpp
SpaceCadetPinball/imgui_impl_sdl.cpp SpaceCadetPinball/imgui_impl_sdl.cpp
SpaceCadetPinball/imgui_impl_sdlrenderer.cpp
PROPERTIES SKIP_PRECOMPILE_HEADERS 1 PROPERTIES SKIP_PRECOMPILE_HEADERS 1
) )
@ -221,10 +237,10 @@ endif()
if(UNIX AND NOT APPLE) if(UNIX AND NOT APPLE)
include(GNUInstallDirs) include(GNUInstallDirs)
install(TARGETS "${PROJECT_NAME}" RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}") install(TARGETS "${PROJECT_NAME}" RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}")
install(FILES "/Platform/Linux/${PROJECT_NAME}.desktop" DESTINATION "share/applications") install(FILES "${CMAKE_SOURCE_DIR}/Platform/Linux/${PROJECT_NAME}.desktop" DESTINATION "share/applications")
install(FILES "/Platform/Linux/${PROJECT_NAME}.metainfo.xml" DESTINATION "share/metainfo") install(FILES "${CMAKE_SOURCE_DIR}/Platform/Linux/${PROJECT_NAME}.metainfo.xml" DESTINATION "share/metainfo")
foreach(S 16 32 48 128 192) foreach(S 16 32 48 128 192)
install(FILES "${PROJECT_NAME}/Icon_${S}x${S}.png" DESTINATION install(FILES "${CMAKE_SOURCE_DIR}/${PROJECT_NAME}/Icon_${S}x${S}.png" DESTINATION
"share/icons/hicolor/${S}x${S}/apps" RENAME "${PROJECT_NAME}.png") "share/icons/hicolor/${S}x${S}/apps" RENAME "${PROJECT_NAME}.png")
endforeach(S) endforeach(S)
endif() endif()

View File

@ -56,6 +56,19 @@
"buildCommandArgs": "", "buildCommandArgs": "",
"ctestCommandArgs": "", "ctestCommandArgs": "",
"inheritEnvironments": [ "msvc_x86" ] "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}"
} }
] ]
} }

View File

@ -7,5 +7,5 @@ No source ports in main repository.\
I have no way to test and maintain most of them.\ I have no way to test and maintain most of them.\
The best I can do is to add a link. The best I can do is to add a link.
There is no guaranty that any particular PR will be accepted.\ There is no guarantee that any particular PR will be accepted.\
If you are unsure, ask first, make PR second. If you are unsure, ask first, make PR second.

View File

@ -3,7 +3,7 @@
<id>com.github.k4zmu2a.spacecadetpinball</id> <id>com.github.k4zmu2a.spacecadetpinball</id>
<name>Space Cadet Pinball</name> <name>Space Cadet Pinball</name>
<display_name>Space Cadet Pinball</display_name> <display_name>Space Cadet Pinball</display_name>
<summary>Reverse engineering of 3D Pinball for Windows - Space Cadet, a game bundled with Windows.</summary> <summary>Game engine for Space Cadet Pinball</summary>
<url type="homepage">https://github.com/k4zmu2a/SpaceCadetPinball</url> <url type="homepage">https://github.com/k4zmu2a/SpaceCadetPinball</url>
<url type="bugtracker">https://github.com/k4zmu2a/SpaceCadetPinball/issues</url> <url type="bugtracker">https://github.com/k4zmu2a/SpaceCadetPinball/issues</url>
<description> <description>
@ -28,6 +28,35 @@
</screenshot> </screenshot>
</screenshots> </screenshots>
<releases> <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 &lt;-&gt; 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"> <release version="2.0.0" date="2021-10-30">
<description> <description>
<p>First release of cross-platform port.</p> <p>First release of cross-platform port.</p>
@ -63,4 +92,4 @@
<keyword>cadet</keyword> <keyword>cadet</keyword>
</keywords> </keywords>
<content_rating type="oars-1.1" /> <content_rating type="oars-1.1" />
</component> </component>

34
Platform/macOS/Info.plist Normal file
View File

@ -0,0 +1,34 @@
<?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>NSHighResolutionCapable</key>
<true/>
</dict>
</plist>

Binary file not shown.

View File

@ -23,6 +23,9 @@ Supports data files from Windows and Full Tilt versions of the game.
| Nintendo Wii | MaikelChan | https://github.com/MaikelChan/SpaceCadetPinball | | Nintendo Wii | MaikelChan | https://github.com/MaikelChan/SpaceCadetPinball |
| Nintendo 3DS | MaikelChan | https://github.com/MaikelChan/SpaceCadetPinball/tree/3ds | | Nintendo 3DS | MaikelChan | https://github.com/MaikelChan/SpaceCadetPinball/tree/3ds |
| Nintendo Wii U | IntriguingTiles | https://github.com/IntriguingTiles/SpaceCadetPinball-WiiU | | Nintendo Wii U | IntriguingTiles | https://github.com/IntriguingTiles/SpaceCadetPinball-WiiU |
| 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. Platforms covered by this project: desktop Windows, Linux and macOS.
@ -71,6 +74,10 @@ This project is available as Flatpak on [Flathub](https://flathub.org/apps/detai
### On macOS ### On macOS
Install XCode (or at least Xcode Command Line Tools with `xcode-select --install`) and CMake.
**Manual compilation:**
* **Homebrew**: Install the `SDL2`, `SDL2_mixer` homebrew packages. * **Homebrew**: Install the `SDL2`, `SDL2_mixer` homebrew packages.
* **MacPorts**: Install the `libSDL2`, `libSDL2_mixer` macports packages. * **MacPorts**: Install the `libSDL2`, `libSDL2_mixer` macports packages.
@ -78,17 +85,22 @@ Compile with CMake. Ensure that `CMAKE_OSX_ARCHITECTURES` variable is set for ei
Tested with: macOS Big Sur (Intel) with Xcode 13 & macOS Montery Beta (Apple Silicon) with Xcode 13. 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 ## Plans
* ~~Decompile original game~~ * ~~Decompile original game~~
* ~~Resizable window, scaled graphics~~ * ~~Resizable window, scaled graphics~~
* ~~Loader for high-res sprites from CADET.DAT~~ * ~~Loader for high-res sprites from CADET.DAT~~
* ~~Cross-platform port using SDL2, SDL2_mixer, ImGui~~ * ~~Cross-platform port using SDL2, SDL2_mixer, ImGui~~
* Misc features of Full Tilt: 3 music tracks, multiball, centered textboxes, etc. * Full Tilt Cadet features
* Maybe: Text translations * Localization support
* Maybe: Android port * Maybe: Support for the other two tables - Dragon and Pirate
* Maybe x2: support for other two tables * Maybe: Game data editor
* Table specific BL (control interactions and missions) is hardcoded, othere parts might be also patched
## On 64-bit bug that killed the game ## On 64-bit bug that killed the game

View 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);
}
}

View 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();
};

File diff suppressed because it is too large Load Diff

View File

@ -5,4 +5,6 @@ class EmbeddedData
{ {
public: public:
static const char PB_MSGFT_bin_compressed_data_base85[5380 + 1]; 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;
}; };

View File

@ -6,7 +6,6 @@
#include "fullscrn.h" #include "fullscrn.h"
#include "gdrv.h" #include "gdrv.h"
#include "pb.h" #include "pb.h"
#include "pinball.h"
#include "zdrv.h" #include "zdrv.h"
@ -47,7 +46,7 @@ void GroupData::AddEntry(EntryData* entry)
if (srcBmp->BitmapType == BitmapTypes::Spliced) if (srcBmp->BitmapType == BitmapTypes::Spliced)
{ {
// Get rid of spliced bitmap early on, to simplify render pipeline // Get rid of spliced bitmap early on, to simplify render pipeline
auto bmp = new gdrv_bitmap8(srcBmp->Width, srcBmp->Height, srcBmp->Width); auto bmp = new gdrv_bitmap8(srcBmp->Width, srcBmp->Height, true);
auto zMap = new zmap_header_type(srcBmp->Width, srcBmp->Height, srcBmp->Width); auto zMap = new zmap_header_type(srcBmp->Width, srcBmp->Height, srcBmp->Width);
SplitSplicedBitmap(*srcBmp, *bmp, *zMap); SplitSplicedBitmap(*srcBmp, *bmp, *zMap);
@ -246,7 +245,7 @@ int DatFile::field_size(int groupIndex, FieldTypes targetEntryType)
int DatFile::record_labeled(LPCSTR targetGroupName) int DatFile::record_labeled(LPCSTR targetGroupName)
{ {
auto targetLength = strlen(targetGroupName); auto targetLength = strlen(targetGroupName);
for (int groupIndex = Groups.size() - 1; groupIndex >= 0; --groupIndex) for (int groupIndex = static_cast<int>(Groups.size()) - 1; groupIndex >= 0; --groupIndex)
{ {
auto groupName = field(groupIndex, FieldTypes::GroupName); auto groupName = field(groupIndex, FieldTypes::GroupName);
if (!groupName) if (!groupName)
@ -296,7 +295,7 @@ void DatFile::Finalize()
// PINBALL2.MID is an alternative font provided in 3DPB data // PINBALL2.MID is an alternative font provided in 3DPB data
// Scaled down because it is too large for top text box // Scaled down because it is too large for top text box
/*auto file = pinball::make_path_name("PINBALL2.MID"); /*auto file = pb::make_path_name("PINBALL2.MID");
auto fileHandle = fopenu(file.c_str(), "rb"); auto fileHandle = fopenu(file.c_str(), "rb");
fseek(fileHandle, 0, SEEK_END); fseek(fileHandle, 0, SEEK_END);
auto fileSize = static_cast<uint32_t>(ftell(fileHandle)); auto fileSize = static_cast<uint32_t>(ftell(fileHandle));

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 86 KiB

View File

@ -10,4 +10,7 @@
<Item Name="[Color]" ExcludeView="simple">Color</Item> <Item Name="[Color]" ExcludeView="simple">Color</Item>
</Expand> </Expand>
</Type> </Type>
<Type Name="vector3">
<DisplayString>{{ X={X} Y={Y} Z={Z} }}</DisplayString>
</Type>
</AutoVisualizer> </AutoVisualizer>

View File

@ -1,62 +1,125 @@
#include "pch.h" #include "pch.h"
#include "options.h"
#include "Sound.h" #include "Sound.h"
#include "maths.h"
int Sound::num_channels; int Sound::num_channels;
bool Sound::enabled_flag = false; bool Sound::enabled_flag = false;
int* Sound::TimeStamps = nullptr; std::vector<ChannelInfo> Sound::Channels{};
int Sound::Volume = MIX_MAX_VOLUME;
bool Sound::MixOpen = false;
bool Sound::Init(int channels, bool enableFlag) void Sound::Init(bool mixOpen, int channels, bool enableFlag, int volume)
{ {
Mix_Init(MIX_INIT_MID_Proxy); MixOpen = mixOpen;
auto result = Mix_OpenAudio(MIX_DEFAULT_FREQUENCY, MIX_DEFAULT_FORMAT, 2, 1024); Volume = volume;
SetChannels(channels); SetChannels(channels);
Enable(enableFlag); Enable(enableFlag);
return !result;
} }
void Sound::Enable(bool enableFlag) void Sound::Enable(bool enableFlag)
{ {
enabled_flag = enableFlag; enabled_flag = enableFlag;
if (!enableFlag) if (MixOpen && !enableFlag)
Mix_HaltChannel(-1); Mix_HaltChannel(-1);
} }
void Sound::Activate() void Sound::Activate()
{ {
Mix_Resume(-1); if (MixOpen)
Mix_Resume(-1);
} }
void Sound::Deactivate() void Sound::Deactivate()
{ {
Mix_Pause(-1); if (MixOpen)
Mix_Pause(-1);
} }
void Sound::Close() void Sound::Close()
{ {
delete[] TimeStamps; Enable(false);
TimeStamps = nullptr; Channels.clear();
Mix_CloseAudio();
Mix_Quit();
} }
void Sound::PlaySound(Mix_Chunk* wavePtr, int time) void Sound::PlaySound(Mix_Chunk* wavePtr, int time, TPinballComponent* soundSource, const char* info)
{ {
if (wavePtr && enabled_flag) if (MixOpen && wavePtr && enabled_flag)
{ {
if (Mix_Playing(-1) == num_channels) if (Mix_Playing(-1) == num_channels)
{ {
auto oldestChannel = std::min_element(TimeStamps, TimeStamps + num_channels) - TimeStamps; 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); Mix_HaltChannel(oldestChannel);
} }
auto channel = Mix_PlayChannel(-1, wavePtr, 0); auto channel = Mix_PlayChannel(-1, wavePtr, 0);
if (channel != -1) if (channel != -1)
TimeStamps[channel] = time; {
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) Mix_Chunk* Sound::LoadWaveFile(const std::string& lpName)
{ {
if (!MixOpen)
return nullptr;
auto wavFile = fopenu(lpName.c_str(), "r"); auto wavFile = fopenu(lpName.c_str(), "r");
if (!wavFile) if (!wavFile)
return nullptr; return nullptr;
@ -67,7 +130,7 @@ Mix_Chunk* Sound::LoadWaveFile(const std::string& lpName)
void Sound::FreeSound(Mix_Chunk* wave) void Sound::FreeSound(Mix_Chunk* wave)
{ {
if (wave) if (MixOpen && wave)
Mix_FreeChunk(wave); Mix_FreeChunk(wave);
} }
@ -77,7 +140,15 @@ void Sound::SetChannels(int channels)
channels = 8; channels = 8;
num_channels = channels; num_channels = channels;
delete[] TimeStamps; Channels.resize(num_channels);
TimeStamps = new int[num_channels](); if (MixOpen)
Mix_AllocateChannels(num_channels); Mix_AllocateChannels(num_channels);
SetVolume(Volume);
}
void Sound::SetVolume(int volume)
{
Volume = volume;
if (MixOpen)
Mix_Volume(-1, volume);
} }

View File

@ -1,20 +1,31 @@
#pragma once #pragma once
#include "maths.h"
#include "TPinballComponent.h"
struct ChannelInfo
{
int TimeStamp;
vector2 Position;
};
class Sound class Sound
{ {
public: public:
static bool Init(int channels, bool enableFlag); static std::vector<ChannelInfo> Channels;
static void Init(bool mixOpen, int channels, bool enableFlag, int volume);
static void Enable(bool enableFlag); static void Enable(bool enableFlag);
static void Activate(); static void Activate();
static void Deactivate(); static void Deactivate();
static void Close(); static void Close();
static void PlaySound(Mix_Chunk* wavePtr, int time); static void PlaySound(Mix_Chunk* wavePtr, int time, TPinballComponent *soundSource, const char* info);
static Mix_Chunk* LoadWaveFile(const std::string& lpName); static Mix_Chunk* LoadWaveFile(const std::string& lpName);
static void FreeSound(Mix_Chunk* wave); static void FreeSound(Mix_Chunk* wave);
static void SetChannels(int channels); static void SetChannels(int channels);
static void SetVolume(int volume);
private: private:
static int num_channels; static int num_channels;
static bool enabled_flag; static bool enabled_flag;
static int* TimeStamps; static int Volume;
static bool MixOpen;
}; };

View File

@ -1,29 +1,17 @@
// SpaceCadetPinball.cpp : This file contains the 'main' function. Program execution begins and ends there.
//
#include "pch.h" #include "pch.h"
#include "winmain.h" #include "winmain.h"
int MainActual(LPCSTR lpCmdLine)
{
// Todo: get rid of restart to change resolution.
int returnCode;
do
{
returnCode = winmain::WinMain(lpCmdLine);
}
while (winmain::RestartRequested());
return returnCode;
}
int main(int argc, char* argv[]) int main(int argc, char* argv[])
{ {
std::string cmdLine; std::string cmdLine;
for (int i = 1; i < argc; i++) for (int i = 1; i < argc; i++)
{
if (i > 1)
cmdLine += " ";
cmdLine += argv[i]; cmdLine += argv[i];
}
return MainActual(cmdLine.c_str()); return winmain::WinMain(cmdLine.c_str());
} }
#if _WIN32 #if _WIN32
@ -32,7 +20,7 @@ int main(int argc, char* argv[])
// Windows subsystem main // Windows subsystem main
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd) int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
{ {
return MainActual(lpCmdLine); return winmain::WinMain(lpCmdLine);
} }
// fopen to _wfopen adapter, for UTF-8 paths // fopen to _wfopen adapter, for UTF-8 paths

View File

@ -9,133 +9,189 @@
#include "proj.h" #include "proj.h"
#include "render.h" #include "render.h"
#include "TPinballTable.h" #include "TPinballTable.h"
#include "TTableLayer.h"
TBall::TBall(TPinballTable* table) : TPinballComponent(table, -1, false) TBall::TBall(TPinballTable* table, int groupIndex) : TCollisionComponent(table, groupIndex, false),
TEdgeSegment(this, &ActiveFlag, 0)
{ {
visualStruct visual{}; visualStruct visual{};
char ballGroupName[10]{"ball"}; char ballGroupName[10]{"ball"};
TimeNow = 0.0;
RayMaxDistance = 0.0; RayMaxDistance = 0.0;
ActiveFlag = 1; ActiveFlag = 1;
CollisionComp = nullptr; CollisionComp = nullptr;
EdgeCollisionCount = 0; EdgeCollisionCount = 0;
TimeDelta = 0.0; TimeDelta = 0.0;
FieldFlag = 1;
CollisionFlag = 0; CollisionFlag = 0;
Speed = 0.0; Speed = 0.0;
Acceleration.Y = 0.0; Direction.Y = 0.0;
Acceleration.X = 0.0; Direction.X = 0.0;
InvAcceleration.Y = 1000000000.0; ListBitmap = new std::vector<SpriteData>();
InvAcceleration.X = 1000000000.0;
Position.X = 0.0;
Position.Y = 0.0;
ListBitmap = new std::vector<gdrv_bitmap8*>(); 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*/ /*Full tilt: ball is ballN, where N[0,2] resolution*/
auto groupIndex = loader::query_handle(ballGroupName); groupIndex = loader::query_handle(ballGroupName);
if (groupIndex < 0) if (groupIndex < 0)
{ {
ballGroupName[4] = '0' + fullscrn::GetResolution(); ballGroupName[4] = '0' + fullscrn::GetResolution();
groupIndex = loader::query_handle(ballGroupName); groupIndex = loader::query_handle(ballGroupName);
} }
Offset = *loader::query_float_attribute(groupIndex, 0, 500); Radius = *loader::query_float_attribute(groupIndex, 0, 500);
auto visualCount = loader::query_visual_states(groupIndex); auto visualCount = loader::query_visual_states(groupIndex);
for (auto index = 0; index < visualCount; ++index) for (auto index = 0; index < visualCount; ++index)
{ {
loader::query_visual(groupIndex, index, &visual); loader::query_visual(groupIndex, index, &visual);
if (ListBitmap) ListBitmap->push_back(visual.Bitmap);
ListBitmap->push_back(visual.Bitmap); auto visVec = reinterpret_cast<vector3*>(loader::query_float_attribute(groupIndex, index, 501));
auto visVec = reinterpret_cast<vector_type*>(loader::query_float_attribute(groupIndex, index, 501)); auto zDepth = proj::z_distance(*visVec);
auto zDepth = proj::z_distance(visVec);
VisualZArray[index] = zDepth; VisualZArray[index] = zDepth;
} }
RenderSprite = render::create_sprite(VisualTypes::Ball, nullptr, nullptr, 0, 0, nullptr); RenderSprite = new render_sprite(VisualTypes::Ball, nullptr, nullptr, 0, 0, nullptr);
PinballTable->CollisionCompOffset = Offset; PinballTable->CollisionCompOffset = Radius;
Position.Z = Offset; Position.Z = Radius;
GroupIndex = groupIndex;
} }
void TBall::Repaint() void TBall::Repaint()
{ {
int pos2D[2];
if (CollisionFlag) if (CollisionFlag)
{ {
Position.Z = Position.Z =
CollisionOffset.X * Position.X + CollisionOffset.X * Position.X +
CollisionOffset.Y * Position.Y + CollisionOffset.Y * Position.Y +
Offset + CollisionOffset.Z; Radius + CollisionOffset.Z;
} }
proj::xform_to_2d(&Position, pos2D); auto pos2D = proj::xform_to_2d(Position);
auto zDepth = proj::z_distance(&Position); auto zDepth = proj::z_distance(Position);
auto zArrPtr = VisualZArray;
auto index = 0u; auto index = 0u;
for (; index < ListBitmap->size() - 1; ++index, zArrPtr++) for (; index < ListBitmap->size() - 1; ++index)
{ {
if (*zArrPtr <= zDepth) break; if (VisualZArray[index] <= zDepth) break;
} }
auto bmp = ListBitmap->at(index); SpriteSetBall(index, pos2D, zDepth);
render::ball_set(
RenderSprite,
bmp,
zDepth,
pos2D[0] - bmp->Width / 2,
pos2D[1] - bmp->Height / 2);
} }
void TBall::not_again(TEdgeSegment* edge) void TBall::not_again(TEdgeSegment* edge)
{ {
if (EdgeCollisionCount < 5) if (EdgeCollisionCount < 16)
{ {
Collisions[EdgeCollisionCount] = edge; Collisions[EdgeCollisionCount] = edge;
++EdgeCollisionCount; ++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(TEdgeSegment* edge) bool TBall::already_hit(const TEdgeSegment& edge) const
{ {
for (int i = 0; i < EdgeCollisionCount; i++) for (int i = 0; i < EdgeCollisionCount; i++)
{ {
if (Collisions[i] == edge) if (Collisions[i] == &edge)
return true; return true;
} }
return false; return false;
} }
int TBall::Message(int code, float value) int TBall::Message(MessageCode code, float value)
{ {
if (code == 1024) if (code == MessageCode::Reset)
{ {
render::ball_set(RenderSprite, nullptr, 0.0, 0, 0); SpriteSetBall(-1, {0, 0}, 0.0f);
Position.X = 0.0; Position.X = 0.0;
CollisionComp = nullptr; CollisionComp = nullptr;
Position.Y = 0.0; Position.Y = 0.0;
ActiveFlag = 0; ActiveFlag = 0;
CollisionFlag = 0; CollisionFlag = 0;
FieldFlag = 1; CollisionMask = 1;
Acceleration.Y = 0.0; Direction.Y = 0.0;
Position.Z = Offset; Position.Z = Radius;
Acceleration.X = 0.0; Direction.X = 0.0;
Speed = 0.0; Speed = 0.0;
RayMaxDistance = 0.0; RayMaxDistance = 0.0;
} }
return 0; return 0;
} }
void TBall::throw_ball(TBall* ball, vector_type* acceleration, float angleMult, float speedMult1, float speedMult2) void TBall::throw_ball(vector3* direction, float angleMult, float speedMult1, float speedMult2)
{ {
ball->CollisionComp = nullptr; CollisionComp = nullptr;
ball->Acceleration = *acceleration; Direction = *direction;
float rnd = RandFloat(); float rnd = RandFloat();
float angle = (1.0f - (rnd + rnd)) * angleMult; float angle = (1.0f - (rnd + rnd)) * angleMult;
maths::RotateVector(&ball->Acceleration, angle); maths::RotateVector(Direction, angle);
rnd = RandFloat(); rnd = RandFloat();
ball->Speed = (1.0f - (rnd + rnd)) * (speedMult1 * speedMult2) + speedMult1; 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);
} }

View File

@ -1,36 +1,41 @@
#pragma once #pragma once
#include "maths.h" #include "maths.h"
#include "TPinballComponent.h" #include "TCollisionComponent.h"
#include "TEdgeSegment.h"
class TCollisionComponent; class TBall : public TCollisionComponent, public TEdgeSegment
class TEdgeSegment;
class TBall : public TPinballComponent
{ {
public : public :
TBall(TPinballTable* table); TBall(TPinballTable* table, int groupIndex);
void Repaint(); void Repaint();
void not_again(TEdgeSegment* edge); void not_again(TEdgeSegment* edge);
bool already_hit(TEdgeSegment* edge); bool already_hit(const TEdgeSegment& edge) const;
int Message(int code, float value) override; 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;
static void throw_ball(TBall* ball, struct vector_type* acceleration, float angleMult, float speedMult1, vector3 Position{};
float speedMult2); vector3 PrevPosition{};
vector3 Direction{};
vector_type Position{};
vector_type Acceleration{};
float Speed; float Speed;
float RayMaxDistance; float RayMaxDistance;
float TimeDelta; float TimeDelta;
float TimeNow; vector2 RampFieldForce{};
vector_type InvAcceleration{};
vector_type RampFieldForce{};
TCollisionComponent* CollisionComp; TCollisionComponent* CollisionComp;
int FieldFlag; int CollisionMask;
TEdgeSegment* Collisions[5]{}; TEdgeSegment* Collisions[16]{};
int EdgeCollisionCount; int EdgeCollisionCount;
vector_type CollisionOffset{}; bool EdgeCollisionResetFlag{};
vector3 CollisionOffset{};
int CollisionFlag; int CollisionFlag;
float Offset; float Radius;
bool HasGroupFlag;
int StuckCounter = 0;
int LastActiveTime{};
float VisualZArray[50]{}; float VisualZArray[50]{};
bool CollisionDisabledFlag{};
}; };

View File

@ -14,23 +14,23 @@ TBlocker::TBlocker(TPinballTable* table, int groupIndex) : TCollisionComponent(t
loader::query_visual(groupIndex, 0, &visual); loader::query_visual(groupIndex, 0, &visual);
SoundIndex4 = visual.SoundIndex4; SoundIndex4 = visual.SoundIndex4;
SoundIndex3 = visual.SoundIndex3; SoundIndex3 = visual.SoundIndex3;
TurnOnMsgValue = 55; InitialDuration = 55;
TurnOffMsgValue = 5; ExtendedDuration = 5;
Threshold = 1000000000.0f; Threshold = 1000000000.0f;
Timer = 0; Timer = 0;
MessageField = 0; MessageField = 0;
ActiveFlag = 0; ActiveFlag = 0;
render::sprite_set_bitmap(RenderSprite, nullptr); SpriteSet(-1);
} }
int TBlocker::Message(int code, float value) int TBlocker::Message(MessageCode code, float value)
{ {
switch (code) switch (code)
{ {
case 1011: case MessageCode::SetTiltLock:
case 1020: case MessageCode::PlayerChanged:
case 1024: case MessageCode::Reset:
case 51: case MessageCode::TBlockerDisable:
if (Timer) if (Timer)
{ {
timer::kill(Timer); timer::kill(Timer);
@ -38,29 +38,29 @@ int TBlocker::Message(int code, float value)
} }
MessageField = 0; MessageField = 0;
ActiveFlag = 0; ActiveFlag = 0;
render::sprite_set_bitmap(RenderSprite, nullptr); SpriteSet(-1);
if (code == 51) if (code == MessageCode::TBlockerDisable)
loader::play_sound(SoundIndex3); loader::play_sound(SoundIndex3, this, "TBlocker1");
return 0;
case 52:
ActiveFlag = 1;
loader::play_sound(SoundIndex4);
render::sprite_set_bitmap(RenderSprite, ListBitmap->at(0));
break; break;
case 59: 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; break;
default: default:
return 0; break;
} }
if (Timer)
timer::kill(Timer);
float timerTime;
if (value <= 0.0f)
timerTime = 0.0;
else
timerTime = value;
Timer = timer::set(timerTime, this, TimerExpired);
return 0; return 0;
} }
@ -68,5 +68,5 @@ void TBlocker::TimerExpired(int timerId, void* caller)
{ {
auto blocker = static_cast<TBlocker*>(caller); auto blocker = static_cast<TBlocker*>(caller);
blocker->Timer = 0; blocker->Timer = 0;
control::handler(60, blocker); control::handler(MessageCode::ControlTimerExpired, blocker);
} }

View File

@ -6,12 +6,12 @@ class TBlocker :
{ {
public: public:
TBlocker(TPinballTable* table, int groupIndex); TBlocker(TPinballTable* table, int groupIndex);
int Message(int code, float value) override; int Message(MessageCode code, float value) override;
static void TimerExpired(int timerId, void* caller); static void TimerExpired(int timerId, void* caller);
int TurnOnMsgValue; int InitialDuration;
int TurnOffMsgValue; int ExtendedDuration;
int Timer; int Timer;
int SoundIndex4; int SoundIndex4;
int SoundIndex3; int SoundIndex3;

View File

@ -21,11 +21,11 @@ TBumper::TBumper(TPinballTable* table, int groupIndex) : TCollisionComponent(tab
OriginalThreshold = Threshold; OriginalThreshold = Threshold;
} }
int TBumper::Message(int code, float value) int TBumper::Message(MessageCode code, float value)
{ {
switch (code) switch (code)
{ {
case 11: case MessageCode::TBumperSetBmpIndex:
{ {
auto nextBmp = static_cast<int>(floor(value)); auto nextBmp = static_cast<int>(floor(value));
auto maxBmp = static_cast<int>(ListBitmap->size()) - 1; auto maxBmp = static_cast<int>(ListBitmap->size()) - 1;
@ -36,33 +36,33 @@ int TBumper::Message(int code, float value)
if (nextBmp != BmpIndex) if (nextBmp != BmpIndex)
{ {
if (nextBmp >= BmpIndex) if (nextBmp >= BmpIndex)
loader::play_sound(SoundIndex4); loader::play_sound(SoundIndex4, this, "TBumper1");
if (nextBmp < BmpIndex) if (nextBmp < BmpIndex)
loader::play_sound(SoundIndex3); loader::play_sound(SoundIndex3, this, "TBumper2");
BmpIndex = nextBmp; BmpIndex = nextBmp;
Fire(); Fire();
control::handler(11, this); control::handler(MessageCode::TBumperSetBmpIndex, this);
} }
break; break;
} }
case 12: case MessageCode::TBumperIncBmpIndex:
{ {
auto nextBmp = BmpIndex + 1; auto nextBmp = BmpIndex + 1;
auto maxBmp = static_cast<int>(ListBitmap->size()) - 1; auto maxBmp = static_cast<int>(ListBitmap->size()) - 1;
if (2 * nextBmp > maxBmp) if (2 * nextBmp > maxBmp)
nextBmp = maxBmp / 2; nextBmp = maxBmp / 2;
TBumper::Message(11, static_cast<float>(nextBmp)); TBumper::Message(MessageCode::TBumperSetBmpIndex, static_cast<float>(nextBmp));
break; break;
} }
case 13: case MessageCode::TBumperDecBmpIndex:
{ {
auto nextBmp = BmpIndex - 1; auto nextBmp = BmpIndex - 1;
if (nextBmp < 0) if (nextBmp < 0)
nextBmp = 0; nextBmp = 0;
TBumper::Message(11, static_cast<float>(nextBmp)); TBumper::Message(MessageCode::TBumperSetBmpIndex, static_cast<float>(nextBmp));
break; break;
} }
case 1020: case MessageCode::PlayerChanged:
{ {
auto playerPtr = &PlayerData[PinballTable->CurrentPlayer]; auto playerPtr = &PlayerData[PinballTable->CurrentPlayer];
playerPtr->BmpIndex = BmpIndex; playerPtr->BmpIndex = BmpIndex;
@ -71,10 +71,10 @@ int TBumper::Message(int code, float value)
playerPtr = &PlayerData[static_cast<int>(floor(value))]; playerPtr = &PlayerData[static_cast<int>(floor(value))];
BmpIndex = playerPtr->BmpIndex; BmpIndex = playerPtr->BmpIndex;
MessageField = playerPtr->MessageField; MessageField = playerPtr->MessageField;
TBumper::Message(11, static_cast<float>(BmpIndex)); TBumper::Message(MessageCode::TBumperSetBmpIndex, static_cast<float>(BmpIndex));
break; break;
} }
case 1024: case MessageCode::Reset:
{ {
if (Timer) if (Timer)
{ {
@ -83,12 +83,10 @@ int TBumper::Message(int code, float value)
} }
BmpIndex = 0; BmpIndex = 0;
MessageField = 0; MessageField = 0;
auto playerPtr = PlayerData; for (auto& playerPtr : PlayerData)
for (auto index = 0; index < PinballTable->PlayerCount; ++index)
{ {
playerPtr->BmpIndex = 0; playerPtr.BmpIndex = 0;
playerPtr->MessageField = 0; playerPtr.MessageField = 0;
++playerPtr;
} }
TimerExpired(0, this); TimerExpired(0, this);
break; break;
@ -100,53 +98,26 @@ int TBumper::Message(int code, float value)
return 0; return 0;
} }
void TBumper::Collision(TBall* ball, vector_type* nextPosition, vector_type* direction, float coef, TEdgeSegment* edge) void TBumper::Collision(TBall* ball, vector2* nextPosition, vector2* direction, float distance, TEdgeSegment* edge)
{ {
if (DefaultCollision(ball, nextPosition, direction)) if (DefaultCollision(ball, nextPosition, direction))
{ {
Fire(); Fire();
control::handler(63, this); control::handler(MessageCode::ControlCollision, this);
} }
} }
void TBumper::put_scoring(int index, int score)
{
if (index < 4)
Scores[index] = score;
}
int TBumper::get_scoring(int index)
{
return index < 4 ? Scores[index] : 0;
}
void TBumper::TimerExpired(int timerId, void* caller) void TBumper::TimerExpired(int timerId, void* caller)
{ {
auto bump = static_cast<TBumper*>(caller); auto bump = static_cast<TBumper*>(caller);
auto bmp = bump->ListBitmap->at(bump->BmpIndex * 2); bump->SpriteSet(bump->BmpIndex * 2);
auto zMap = bump->ListZMap->at(bump->BmpIndex * 2);
bump->Timer = 0; bump->Timer = 0;
render::sprite_set(
bump->RenderSprite,
bmp,
zMap,
bmp->XPosition - bump->PinballTable->XOffset,
bmp->YPosition - bump->PinballTable->YOffset);
bump->Threshold = bump->OriginalThreshold; bump->Threshold = bump->OriginalThreshold;
} }
void TBumper::Fire() void TBumper::Fire()
{ {
int bmpIndex = 2 * BmpIndex + 1; SpriteSet(2 * BmpIndex + 1);
auto bmp = ListBitmap->at(bmpIndex);
auto zMap = ListZMap->at(bmpIndex);
render::sprite_set(
RenderSprite,
bmp,
zMap,
bmp->XPosition - PinballTable->XOffset,
bmp->YPosition - PinballTable->YOffset);
Timer = timer::set(TimerTime, this, TimerExpired); Timer = timer::set(TimerTime, this, TimerExpired);
Threshold = 1000000000.0; Threshold = 1000000000.0;
} }

View File

@ -13,11 +13,9 @@ class TBumper :
public: public:
TBumper(TPinballTable* table, int groupIndex); TBumper(TPinballTable* table, int groupIndex);
~TBumper() override = default; ~TBumper() override = default;
int Message(int code, float value) override; int Message(MessageCode code, float value) override;
void Collision(TBall* ball, vector_type* nextPosition, vector_type* direction, float coef, void Collision(TBall* ball, vector2* nextPosition, vector2* direction, float distance,
TEdgeSegment* edge) override; TEdgeSegment* edge) override;
void put_scoring(int index, int score) override;
int get_scoring(int index) override;
void Fire(); void Fire();
static void TimerExpired(int timerId, void* caller); static void TimerExpired(int timerId, void* caller);
@ -28,6 +26,5 @@ public:
float OriginalThreshold; float OriginalThreshold;
int SoundIndex4; int SoundIndex4;
int SoundIndex3; int SoundIndex3;
int Scores[4]{};
TBumper_player_backup PlayerData[4]{}; TBumper_player_backup PlayerData[4]{};
}; };

View File

@ -5,31 +5,40 @@
#include "TCollisionComponent.h" #include "TCollisionComponent.h"
#include "TTableLayer.h" #include "TTableLayer.h"
TCircle::TCircle(TCollisionComponent* collComp, char* activeFlag, unsigned collisionGroup, vector_type* center, TCircle::TCircle(TCollisionComponent* collComp, char* activeFlag, unsigned collisionGroup, vector2* center,
float radius): TEdgeSegment(collComp, activeFlag, collisionGroup) float radius): TEdgeSegment(collComp, activeFlag, collisionGroup)
{ {
Circle.RadiusSq = radius * radius; Circle.RadiusSq = radius * radius;
Circle.Center = *center; Circle.Center = *center;
} }
float TCircle::FindCollisionDistance(ray_type* ray) float TCircle::FindCollisionDistance(const ray_type& ray)
{ {
return maths::ray_intersect_circle(ray, &Circle); return maths::ray_intersect_circle(ray, Circle);
} }
void TCircle::EdgeCollision(TBall* ball, float coef) void TCircle::EdgeCollision(TBall* ball, float distance)
{ {
vector_type direction{}, nextPosition{}; vector2 direction{}, nextPosition{};
nextPosition.X = coef * ball->Acceleration.X + ball->Position.X; nextPosition.X = distance * ball->Direction.X + ball->Position.X;
nextPosition.Y = coef * ball->Acceleration.Y + ball->Position.Y; nextPosition.Y = distance * ball->Direction.Y + ball->Position.Y;
direction.X = nextPosition.X - Circle.Center.X; direction.X = nextPosition.X - Circle.Center.X;
direction.Y = nextPosition.Y - Circle.Center.Y; direction.Y = nextPosition.Y - Circle.Center.Y;
maths::normalize_2d(&direction); maths::normalize_2d(direction);
CollisionComponent->Collision(ball, &nextPosition, &direction, coef, this); CollisionComponent->Collision(ball, &nextPosition, &direction, distance, this);
} }
void TCircle::place_in_grid() 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); TTableLayer::edges_insert_circle(&Circle, this, nullptr);
} }

View File

@ -8,9 +8,9 @@ class TCircle :
public: public:
circle_type Circle{}; circle_type Circle{};
TCircle(TCollisionComponent* collComp, char* activeFlag, unsigned int collisionGroup, vector_type* center, TCircle(TCollisionComponent* collComp, char* activeFlag, unsigned int collisionGroup, vector2* center,
float radius); float radius);
float FindCollisionDistance(ray_type* ray) override; float FindCollisionDistance(const ray_type& ray) override;
void EdgeCollision(TBall* ball, float coef) override; void EdgeCollision(TBall* ball, float distance) override;
void place_in_grid() override; void place_in_grid(RectF* aabb) override;
}; };

View File

@ -4,6 +4,7 @@
#include "maths.h" #include "maths.h"
#include "TEdgeSegment.h" #include "TEdgeSegment.h"
#include "TPinballTable.h" #include "TPinballTable.h"
#include "TBall.h"
TCollisionComponent::TCollisionComponent(TPinballTable* table, int groupIndex, bool createWall) : TCollisionComponent::TCollisionComponent(TPinballTable* table, int groupIndex, bool createWall) :
@ -12,6 +13,7 @@ TCollisionComponent::TCollisionComponent(TPinballTable* table, int groupIndex, b
visualStruct visual{}; visualStruct visual{};
ActiveFlag = 1; ActiveFlag = 1;
AABB = { -10000, -10000, 10000, 10000 };
if (GroupName != nullptr) if (GroupName != nullptr)
UnusedBaseFlag = 1; UnusedBaseFlag = 1;
if (groupIndex <= 0) if (groupIndex <= 0)
@ -51,38 +53,36 @@ void TCollisionComponent::port_draw()
edge->port_draw(); edge->port_draw();
} }
int TCollisionComponent::DefaultCollision(TBall* ball, vector_type* nextPosition, vector_type* direction) bool TCollisionComponent::DefaultCollision(TBall* ball, vector2* nextPosition, vector2* direction)
{ {
if (PinballTable->TiltLockFlag) if (PinballTable->TiltLockFlag)
{ {
maths::basic_collision(ball, nextPosition, direction, Elasticity, Smoothness, 1000000000.0, 0.0); maths::basic_collision(ball, nextPosition, direction, Elasticity, Smoothness, 1000000000.0, 0.0);
return 0; return false;
} }
bool collision = false;
auto projSpeed = maths::basic_collision(ball, nextPosition, direction, Elasticity, Smoothness, Threshold, Boost); auto projSpeed = maths::basic_collision(ball, nextPosition, direction, Elasticity, Smoothness, Threshold, Boost);
if (projSpeed <= Threshold) if (projSpeed > Threshold)
{ {
if (projSpeed > 0.2f) loader::play_sound(HardHitSoundId, ball, "TCollisionComponent1");
{ collision = true;
if (SoftHitSoundId)
loader::play_sound(SoftHitSoundId);
}
return 0;
} }
if (HardHitSoundId) else if (projSpeed > 0.2f)
loader::play_sound(HardHitSoundId); loader::play_sound(SoftHitSoundId, ball, "TCollisionComponent2");
return 1;
return collision;
} }
void TCollisionComponent::Collision(TBall* ball, vector_type* nextPosition, vector_type* direction, void TCollisionComponent::Collision(TBall* ball, vector2* nextPosition, vector2* direction,
float coef, TEdgeSegment* edge) float distance, TEdgeSegment* edge)
{ {
int soundIndex;
if (PinballTable->TiltLockFlag) if (PinballTable->TiltLockFlag)
{ {
maths::basic_collision(ball, nextPosition, direction, Elasticity, Smoothness, 1000000000.0, 0.0); maths::basic_collision(ball, nextPosition, direction, Elasticity, Smoothness, 1000000000.0, 0.0);
return; return;
} }
auto projSpeed = maths::basic_collision( auto projSpeed = maths::basic_collision(
ball, ball,
nextPosition, nextPosition,
@ -91,21 +91,13 @@ void TCollisionComponent::Collision(TBall* ball, vector_type* nextPosition, vect
Smoothness, Smoothness,
Threshold, Threshold,
Boost); Boost);
if (projSpeed <= Threshold) if (projSpeed > Threshold)
{ loader::play_sound(HardHitSoundId, ball, "TCollisionComponent3");
if (projSpeed <= 0.2f) else if (projSpeed > 0.2f)
return; loader::play_sound(SoftHitSoundId, ball, "TCollisionComponent4");
soundIndex = SoftHitSoundId;
}
else
{
soundIndex = HardHitSoundId;
}
if (soundIndex)
loader::play_sound(soundIndex);
} }
int TCollisionComponent::FieldEffect(TBall* ball, vector_type* vecDst) int TCollisionComponent::FieldEffect(TBall* ball, vector2* vecDst)
{ {
return 0; return 0;
} }

View File

@ -1,7 +1,8 @@
#pragma once #pragma once
#include "maths.h"
#include "TPinballComponent.h" #include "TPinballComponent.h"
struct vector_type; struct vector2;
class TEdgeSegment; class TEdgeSegment;
class TBall; class TBall;
@ -15,12 +16,13 @@ public:
float Threshold; float Threshold;
int SoftHitSoundId; int SoftHitSoundId;
int HardHitSoundId; int HardHitSoundId;
RectF AABB;
TCollisionComponent(TPinballTable* table, int groupIndex, bool createWall); TCollisionComponent(TPinballTable* table, int groupIndex, bool createWall);
~TCollisionComponent() override; ~TCollisionComponent() override;
void port_draw() override; void port_draw() override;
virtual void Collision(TBall* ball, vector_type* nextPosition, vector_type* direction, float coef, virtual void Collision(TBall* ball, vector2* nextPosition, vector2* direction, float distance,
TEdgeSegment* edge); TEdgeSegment* edge);
virtual int FieldEffect(TBall* ball, vector_type* vecDst); virtual int FieldEffect(TBall* ball, vector2* vecDst);
int DefaultCollision(TBall* ball, vector_type* nextPosition, vector_type* direction); bool DefaultCollision(TBall* ball, vector2* nextPosition, vector2* direction);
}; };

View File

@ -33,9 +33,9 @@ TComponentGroup::~TComponentGroup()
} }
} }
int TComponentGroup::Message(int code, float value) int TComponentGroup::Message(MessageCode code, float value)
{ {
if (code == 48) if (code == MessageCode::TComponentGroupResetNotifyTimer)
{ {
if (this->Timer) if (this->Timer)
{ {
@ -45,7 +45,8 @@ int TComponentGroup::Message(int code, float value)
if (value > 0.0f) if (value > 0.0f)
this->Timer = timer::set(value, this, NotifyTimerExpired); this->Timer = timer::set(value, this, NotifyTimerExpired);
} }
else if (code <= 1007 || (code > 1011 && code != 1020 && code != 1022)) else if (code < MessageCode::Pause || (code > MessageCode::SetTiltLock &&
code != MessageCode::PlayerChanged && code != MessageCode::GameOver))
{ {
for (auto component : List) for (auto component : List)
{ {
@ -59,5 +60,5 @@ void TComponentGroup::NotifyTimerExpired(int timerId, void* caller)
{ {
auto compGroup = static_cast<TComponentGroup*>(caller); auto compGroup = static_cast<TComponentGroup*>(caller);
compGroup->Timer = 0; compGroup->Timer = 0;
control::handler(61, compGroup); control::handler(MessageCode::ControlNotifyTimerExpired, compGroup);
} }

View File

@ -8,7 +8,7 @@ class TComponentGroup :
public: public:
TComponentGroup(TPinballTable* table, int groupIndex); TComponentGroup(TPinballTable* table, int groupIndex);
~TComponentGroup() override; ~TComponentGroup() override;
int Message(int code, float value) override; int Message(MessageCode code, float value) override;
static void NotifyTimerExpired(int timerId, void* caller); static void NotifyTimerExpired(int timerId, void* caller);
std::vector<TPinballComponent*> List; std::vector<TPinballComponent*> List;

View File

@ -56,23 +56,23 @@ TDemo::TDemo(TPinballTable* table, int groupIndex)
Edge3 = TEdgeSegment::install_wall(v9, this, &ActiveFlag, visual.CollisionGroup, table->CollisionCompOffset, 1404); Edge3 = TEdgeSegment::install_wall(v9, this, &ActiveFlag, visual.CollisionGroup, table->CollisionCompOffset, 1404);
} }
int TDemo::Message(int code, float value) int TDemo::Message(MessageCode code, float value)
{ {
switch (code) switch (code)
{ {
case 1014: case MessageCode::NewGame:
if (RestartGameTimer) if (RestartGameTimer)
timer::kill(RestartGameTimer); timer::kill(RestartGameTimer);
RestartGameTimer = 0; RestartGameTimer = 0;
break; break;
case 1022: case MessageCode::GameOver:
if (RestartGameTimer) if (RestartGameTimer)
timer::kill(RestartGameTimer); timer::kill(RestartGameTimer);
RestartGameTimer = 0; RestartGameTimer = 0;
if (ActiveFlag != 0) if (ActiveFlag != 0)
RestartGameTimer = timer::set(5.0, this, NewGameRestartTimer); RestartGameTimer = timer::set(5.0, this, NewGameRestartTimer);
break; break;
case 1024: case MessageCode::Reset:
if (FlipLeftTimer) if (FlipLeftTimer)
timer::kill(FlipLeftTimer); timer::kill(FlipLeftTimer);
FlipLeftTimer = 0; FlipLeftTimer = 0;
@ -93,12 +93,12 @@ int TDemo::Message(int code, float value)
return 0; return 0;
} }
void TDemo::Collision(TBall* ball, vector_type* nextPosition, vector_type* direction, float coef, TEdgeSegment* edge) void TDemo::Collision(TBall* ball, vector2* nextPosition, vector2* direction, float distance, TEdgeSegment* edge)
{ {
ball->not_again(edge); ball->not_again(edge);
ball->Position.X = nextPosition->X; ball->Position.X = nextPosition->X;
ball->Position.Y = nextPosition->Y; ball->Position.Y = nextPosition->Y;
ball->RayMaxDistance -= coef; ball->RayMaxDistance -= distance;
switch (reinterpret_cast<size_t>(edge->WallValue)) switch (reinterpret_cast<size_t>(edge->WallValue))
{ {
@ -125,7 +125,7 @@ void TDemo::Collision(TBall* ball, vector_type* nextPosition, vector_type* direc
case 1404: case 1404:
if (!PlungerFlag) if (!PlungerFlag)
{ {
PinballTable->Message(1004, ball->TimeNow); PinballTable->Message(MessageCode::PlungerInputPressed, 0);
float time = RandFloat() + 2.0f; float time = RandFloat() + 2.0f;
PlungerFlag = timer::set(time, this, PlungerRelease); PlungerFlag = timer::set(time, this, PlungerRelease);
} }
@ -139,14 +139,14 @@ void TDemo::PlungerRelease(int timerId, void* caller)
{ {
auto demo = static_cast<TDemo*>(caller); auto demo = static_cast<TDemo*>(caller);
demo->PlungerFlag = 0; demo->PlungerFlag = 0;
demo->PinballTable->Message(1005, pb::time_next); demo->PinballTable->Message(MessageCode::PlungerInputReleased, pb::time_next);
} }
void TDemo::UnFlipRight(int timerId, void* caller) void TDemo::UnFlipRight(int timerId, void* caller)
{ {
auto demo = static_cast<TDemo*>(caller); auto demo = static_cast<TDemo*>(caller);
if (demo->FlipRightFlag) if (demo->FlipRightFlag)
demo->PinballTable->Message(1003, pb::time_next); demo->PinballTable->Message(MessageCode::RightFlipperInputReleased, pb::time_next);
demo->FlipRightFlag = 0; demo->FlipRightFlag = 0;
} }
@ -154,7 +154,7 @@ void TDemo::UnFlipLeft(int timerId, void* caller)
{ {
auto demo = static_cast<TDemo*>(caller); auto demo = static_cast<TDemo*>(caller);
if (demo->FlipLeftFlag) if (demo->FlipLeftFlag)
demo->PinballTable->Message(1001, pb::time_next); demo->PinballTable->Message(MessageCode::LeftFlipperInputReleased, pb::time_next);
demo->FlipLeftFlag = 0; demo->FlipLeftFlag = 0;
} }
@ -168,7 +168,7 @@ void TDemo::FlipRight(int timerId, void* caller)
timer::kill(demo->FlipRightTimer); timer::kill(demo->FlipRightTimer);
demo->FlipRightTimer = 0; demo->FlipRightTimer = 0;
} }
demo->PinballTable->Message(1002, pb::time_next); demo->PinballTable->Message(MessageCode::RightFlipperInputPressed, pb::time_next);
demo->FlipRightFlag = 1; demo->FlipRightFlag = 1;
float time = demo->UnFlipTimerTime1 + demo->UnFlipTimerTime2 - RandFloat() * float time = demo->UnFlipTimerTime1 + demo->UnFlipTimerTime2 - RandFloat() *
(demo->UnFlipTimerTime2 + demo->UnFlipTimerTime2); (demo->UnFlipTimerTime2 + demo->UnFlipTimerTime2);
@ -186,7 +186,7 @@ void TDemo::FlipLeft(int timerId, void* caller)
timer::kill(demo->FlipLeftTimer); timer::kill(demo->FlipLeftTimer);
demo->FlipLeftTimer = 0; demo->FlipLeftTimer = 0;
} }
demo->PinballTable->Message(1000, pb::time_next); demo->PinballTable->Message(MessageCode::LeftFlipperInputPressed, pb::time_next);
demo->FlipLeftFlag = 1; demo->FlipLeftFlag = 1;
float time = demo->UnFlipTimerTime1 + demo->UnFlipTimerTime2 - RandFloat() * float time = demo->UnFlipTimerTime1 + demo->UnFlipTimerTime2 - RandFloat() *
(demo->UnFlipTimerTime2 + demo->UnFlipTimerTime2); (demo->UnFlipTimerTime2 + demo->UnFlipTimerTime2);
@ -197,7 +197,7 @@ void TDemo::FlipLeft(int timerId, void* caller)
void TDemo::NewGameRestartTimer(int timerId, void* caller) void TDemo::NewGameRestartTimer(int timerId, void* caller)
{ {
auto demo = static_cast<TDemo*>(caller); auto demo = static_cast<TDemo*>(caller);
pb::replay_level(1); pb::replay_level(true);
demo->PinballTable->Message(1014, static_cast<float>(demo->PinballTable->PlayerCount)); demo->PinballTable->Message(MessageCode::NewGame, static_cast<float>(demo->PinballTable->PlayerCount));
demo->RestartGameTimer = 0; demo->RestartGameTimer = 0;
} }

View File

@ -6,8 +6,8 @@ class TDemo :
{ {
public: public:
TDemo(TPinballTable* table, int groupIndex); TDemo(TPinballTable* table, int groupIndex);
int Message(int code, float value) override; int Message(MessageCode code, float value) override;
void Collision(TBall* ball, vector_type* nextPosition, vector_type* direction, float coef, void Collision(TBall* ball, vector2* nextPosition, vector2* direction, float distance,
TEdgeSegment* edge) override; TEdgeSegment* edge) override;
static void PlungerRelease(int timerId, void* caller); static void PlungerRelease(int timerId, void* caller);

View File

@ -14,30 +14,35 @@ TDrain::TDrain(TPinballTable* table, int groupIndex) : TCollisionComponent(table
TimerTime = *loader::query_float_attribute(groupIndex, 0, 407); TimerTime = *loader::query_float_attribute(groupIndex, 0, 407);
} }
int TDrain::Message(int code, float value) int TDrain::Message(MessageCode code, float value)
{ {
if (code == 1024) if (code == MessageCode::Reset)
{ {
if (Timer) if (Timer)
{ {
timer::kill(Timer); timer::kill(Timer);
Timer = 0; Timer = 0;
} }
PinballTable->BallInSink = 0; PinballTable->BallInDrainFlag = 0;
} }
return 0; return 0;
} }
void TDrain::Collision(TBall* ball, vector_type* nextPosition, vector_type* direction, float coef, TEdgeSegment* edge) void TDrain::Collision(TBall* ball, vector2* nextPosition, vector2* direction, float distance, TEdgeSegment* edge)
{ {
ball->Message(1024, 0.0); ball->Disable();
PinballTable->BallInSink = 1; --PinballTable->MultiballCount;
Timer = timer::set(TimerTime, this, TimerCallback); if (PinballTable->MultiballCount <= 0)
control::handler(63, this); {
PinballTable->MultiballCount = 0;
PinballTable->BallInDrainFlag = 1;
Timer = timer::set(TimerTime, this, TimerCallback);
}
control::handler(MessageCode::ControlCollision, this);
} }
void TDrain::TimerCallback(int timerId, void* caller) void TDrain::TimerCallback(int timerId, void* caller)
{ {
auto drain = static_cast<TDrain*>(caller); auto drain = static_cast<TDrain*>(caller);
control::handler(60, drain); control::handler(MessageCode::ControlTimerExpired, drain);
} }

View File

@ -6,8 +6,8 @@ class TDrain :
{ {
public: public:
TDrain(TPinballTable* table, int groupIndex); TDrain(TPinballTable* table, int groupIndex);
int Message(int code, float value) override; int Message(MessageCode code, float value) override;
void Collision(TBall* ball, vector_type* nextPosition, vector_type* direction, float coef, void Collision(TBall* ball, vector2* nextPosition, vector2* direction, float distance,
TEdgeSegment* edge) override; TEdgeSegment* edge) override;
static void TimerCallback(int timerId, void* caller); static void TimerCallback(int timerId, void* caller);

View File

@ -8,17 +8,19 @@
#include "TEdgeSegment.h" #include "TEdgeSegment.h"
#include "TTableLayer.h" #include "TTableLayer.h"
TEdgeManager::TEdgeManager(float posX, float posY, float width, float height) TEdgeManager::TEdgeManager(float xMin, float yMin, float width, float height)
{ {
X = posX; Width = width;
Y = posY; Height = height;
MinX = xMin;
MinY = yMin;
MaxX = MinX + width;
MaxY = MinY + height;
MaxBoxX = 10; MaxBoxX = 10;
MaxBoxY = 15; MaxBoxY = 15;
AdvanceX = width / 10.0f; AdvanceX = width / static_cast<float>(MaxBoxX);
AdvanceY = height / 15.0f; AdvanceY = height / static_cast<float>(MaxBoxY);
AdvanceXInv = 1.0f / AdvanceX; BoxArray = new TEdgeBox[MaxBoxX * MaxBoxY];
AdvanceYInv = 1.0f / AdvanceY;
BoxArray = new TEdgeBox[150];
} }
TEdgeManager::~TEdgeManager() TEdgeManager::~TEdgeManager()
@ -28,12 +30,12 @@ TEdgeManager::~TEdgeManager()
int TEdgeManager::box_x(float x) int TEdgeManager::box_x(float x)
{ {
return std::max(0, std::min(static_cast<int>(floor((x - X) * AdvanceXInv)), MaxBoxX - 1)); return std::max(0, std::min(static_cast<int>(floor((x - MinX) / AdvanceX)), MaxBoxX - 1));
} }
int TEdgeManager::box_y(float y) int TEdgeManager::box_y(float y)
{ {
return std::max(0, std::min(static_cast<int>(floor((y - Y) * AdvanceYInv)), MaxBoxY - 1)); return std::max(0, std::min(static_cast<int>(floor((y - MinY) / AdvanceY)), MaxBoxY - 1));
} }
int TEdgeManager::increment_box_x(int x) int TEdgeManager::increment_box_x(int x)
@ -48,33 +50,41 @@ int TEdgeManager::increment_box_y(int y)
void TEdgeManager::add_edge_to_box(int x, int y, TEdgeSegment* edge) void TEdgeManager::add_edge_to_box(int x, int y, TEdgeSegment* edge)
{ {
BoxArray[x + y * MaxBoxX].EdgeList.push_back(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) void TEdgeManager::add_field_to_box(int x, int y, field_effect_type* field)
{ {
BoxArray[x + y * MaxBoxX].FieldList.push_back(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 TEdgeManager::TestGridBox(int x, int y, float* distPtr, TEdgeSegment** edgeDst, ray_type* ray, TBall* ball,
int edgeIndex) int edgeIndex)
{ {
if (x >= 0 && x < 10 && y >= 0 && y < 15) if (x >= 0 && x < MaxBoxX && y >= 0 && y < MaxBoxY)
{ {
TEdgeBox* edgeBox = &BoxArray[x + y * MaxBoxX]; TEdgeBox* edgeBox = &BoxArray[x + y * MaxBoxX];
TEdgeSegment** edgePtr = &EdgeArray[edgeIndex]; TEdgeSegment** edgePtr = &EdgeArray[edgeIndex];
for (auto it = edgeBox->EdgeList.rbegin(); it != edgeBox->EdgeList.rend(); ++it) for (auto it = edgeBox->EdgeList.rbegin(); it != edgeBox->EdgeList.rend(); ++it)
{ {
auto edge = *it; auto edge = *it;
if (!edge->ProcessedFlag && *edge->ActiveFlag && (edge->CollisionGroup & ray->FieldFlag)) if (!edge->ProcessedFlag && *edge->ActiveFlagPtr && (edge->CollisionGroup & ray->CollisionMask) != 0)
{ {
if (!ball->already_hit(edge)) if (!ball->already_hit(*edge))
{ {
++edgeIndex; ++edgeIndex;
*edgePtr = edge; *edgePtr = edge;
++edgePtr; ++edgePtr;
edge->ProcessedFlag = 1; edge->ProcessedFlag = 1;
auto dist = edge->FindCollisionDistance(ray); auto dist = edge->FindCollisionDistance(*ray);
if (dist < *distPtr) if (dist < *distPtr)
{ {
*distPtr = dist; *distPtr = dist;
@ -87,20 +97,20 @@ int TEdgeManager::TestGridBox(int x, int y, float* distPtr, TEdgeSegment** edgeD
return edgeIndex; return edgeIndex;
} }
void TEdgeManager::FieldEffects(TBall* ball, vector_type* dstVec) void TEdgeManager::FieldEffects(TBall* ball, vector2* dstVec)
{ {
vector_type vec{}; vector2 vec{};
TEdgeBox* edgeBox = &BoxArray[box_x(ball->Position.X) + box_y(ball->Position.Y) * TEdgeBox* edgeBox = &BoxArray[box_x(ball->Position.X) + box_y(ball->Position.Y) *
MaxBoxX]; MaxBoxX];
for (auto it = edgeBox->FieldList.rbegin(); it != edgeBox->FieldList.rend(); ++it) for (auto it = edgeBox->FieldList.rbegin(); it != edgeBox->FieldList.rend(); ++it)
{ {
auto field = *it; auto field = *it;
if (*field->Flag2Ptr && ball->FieldFlag & field->Mask) if (*field->ActiveFlag && ball->CollisionMask & field->CollisionGroup)
{ {
if (field->CollisionComp->FieldEffect(ball, &vec)) if (field->CollisionComp->FieldEffect(ball, &vec))
{ {
maths::vector_add(dstVec, &vec); maths::vector_add(*dstVec, vec);
} }
} }
} }
@ -111,157 +121,83 @@ float TEdgeManager::FindCollisionDistance(ray_type* ray, TBall* ball, TEdgeSegme
auto distance = 1000000000.0f; auto distance = 1000000000.0f;
auto edgeIndex = 0; auto edgeIndex = 0;
auto rayX = ray->Origin.X; auto x0 = ray->Origin.X;
auto rayY = ray->Origin.Y; auto y0 = ray->Origin.Y;
auto rayBoxX = box_x(rayX); auto x1 = ray->Direction.X * ray->MaxDistance + ray->Origin.X;
auto rayBoxY = box_y(rayY); auto y1 = ray->Direction.Y * ray->MaxDistance + ray->Origin.Y;
auto rayEndX = ray->Direction.X * ray->MaxDistance + ray->Origin.X; auto xBox0 = box_x(x0);
auto rayEndY = ray->Direction.Y * ray->MaxDistance + ray->Origin.Y; auto yBox0 = box_y(y0);
auto rayEndBoxX = box_x(rayEndX); auto xBox1 = box_x(x1);
auto rayEndBoxY = box_y(rayEndY); auto yBox1 = box_y(y1);
auto rayDirX = rayX >= rayEndX ? -1 : 1; auto dirX = x0 >= x1 ? -1 : 1;
auto rayDirY = rayY >= rayEndY ? -1 : 1; auto dirY = y0 >= y1 ? -1 : 1;
if (rayBoxY == rayEndBoxY) if (yBox0 == yBox1)
{ {
if (rayDirX == 1) if (dirX == 1)
{ {
for (auto indexX = rayBoxX; indexX <= rayEndBoxX; indexX++) for (auto indexX = xBox0; indexX <= xBox1; indexX++)
{ {
edgeIndex = TestGridBox(indexX, rayBoxY, &distance, edge, ray, ball, edgeIndex); edgeIndex = TestGridBox(indexX, yBox0, &distance, edge, ray, ball, edgeIndex);
} }
} }
else else
{ {
for (auto indexX = rayBoxX; indexX >= rayEndBoxX; indexX--) for (auto indexX = xBox0; indexX >= xBox1; indexX--)
{ {
edgeIndex = TestGridBox(indexX, rayBoxY, &distance, edge, ray, ball, edgeIndex); edgeIndex = TestGridBox(indexX, yBox0, &distance, edge, ray, ball, edgeIndex);
} }
} }
} }
else if (rayBoxX == rayEndBoxX) else if (xBox0 == xBox1)
{ {
if (rayDirY == 1) if (dirY == 1)
{ {
for (auto indexY = rayBoxY; indexY <= rayEndBoxY; indexY++) for (auto indexY = yBox0; indexY <= yBox1; indexY++)
{ {
edgeIndex = TestGridBox(rayBoxX, indexY, &distance, edge, ray, ball, edgeIndex); edgeIndex = TestGridBox(xBox0, indexY, &distance, edge, ray, ball, edgeIndex);
} }
} }
else else
{ {
for (auto indexY = rayBoxY; indexY >= rayEndBoxY; indexY--) for (auto indexY = yBox0; indexY >= yBox1; indexY--)
{ {
edgeIndex = TestGridBox(rayBoxX, indexY, &distance, edge, ray, ball, edgeIndex); edgeIndex = TestGridBox(xBox0, indexY, &distance, edge, ray, ball, edgeIndex);
} }
} }
} }
else else
{ {
auto rayDyDX = (rayY - rayEndY) / (rayX - rayEndX); edgeIndex = TestGridBox(xBox0, yBox0, &distance, edge, ray, ball, 0);
auto indexX = rayBoxX;
auto indexY = rayBoxY; // Bresenham line formula: y = dYdX * (x - x0) + y0; dYdX = (y0 - y1) / (x0 - x1)
auto bresIndexX = rayBoxX + 1; auto dyDx = (y0 - y1) / (x0 - x1);
auto bresIndexY = rayBoxY + 1; // Precompute constant part: dYdX * (-x0) + y0
auto bresXAdd = rayY - rayDyDX * rayX; auto precomp = -x0 * dyDx + y0;
edgeIndex = TestGridBox(rayBoxX, rayBoxY, &distance, edge, ray, ball, 0); // X and Y indexes are offset by one when going forwards, not sure why
if (rayDirX == 1) auto xBias = dirX == 1 ? 1 : 0, yBias = dirY == 1 ? 1 : 0;
for (auto indexX = xBox0, indexY = yBox0; indexX != xBox1 || indexY != yBox1;)
{ {
if (rayDirY == 1) // 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)
{ {
do // Advance indexY when discrete value is ahead/behind
{ // Advance indexX when discrete value matches linear value
auto yCoord = bresIndexY * AdvanceY + Y; indexY += dirY;
auto xCoord = (bresIndexX * AdvanceX + X) * rayDyDX + bresXAdd; if (ylinear == yDiscrete)
if (xCoord >= yCoord) indexX += dirX;
{
if (xCoord == yCoord)
{
++indexX;
++bresIndexX;
}
++indexY;
++bresIndexY;
}
else
{
++indexX;
++bresIndexX;
}
edgeIndex = TestGridBox(indexX, indexY, &distance, edge, ray, ball, edgeIndex);
}
while (indexX < rayEndBoxX || indexY < rayEndBoxY);
} }
else else
{ {
do // Advance indexX otherwise
{ indexX += dirX;
auto yCoord = indexY * AdvanceY + Y;
auto xCoord = (bresIndexX * AdvanceX + X) * rayDyDX + bresXAdd;
if (xCoord <= yCoord)
{
if (xCoord == yCoord)
{
++indexX;
++bresIndexX;
}
--indexY;
}
else
{
++indexX;
++bresIndexX;
}
edgeIndex = TestGridBox(indexX, indexY, &distance, edge, ray, ball, edgeIndex);
}
while (indexX < rayEndBoxX || indexY > rayEndBoxY);
}
}
else
{
if (rayDirY == 1)
{
do
{
auto yCoord = bresIndexY * AdvanceY + Y;
auto xCoord = (indexX * AdvanceX + X) * rayDyDX + bresXAdd;
if (xCoord >= yCoord)
{
if (xCoord == yCoord)
--indexX;
++indexY;
++bresIndexY;
}
else
{
--indexX;
}
edgeIndex = TestGridBox(indexX, indexY, &distance, edge, ray, ball, edgeIndex);
}
while (indexX > rayEndBoxX || indexY < rayEndBoxY);
}
else
{
do
{
auto yCoord = indexY * AdvanceY + Y;
auto xCoord = (indexX * AdvanceX + X) * rayDyDX + bresXAdd;
if (xCoord <= yCoord)
{
if (xCoord == yCoord)
--indexX;
--indexY;
}
else
{
--indexX;
}
edgeIndex = TestGridBox(indexX, indexY, &distance, edge, ray, ball, edgeIndex);
}
while (indexX > rayEndBoxX || indexY > rayEndBoxY);
} }
edgeIndex = TestGridBox(indexX, indexY, &distance, edge, ray, ball, edgeIndex);
} }
} }
@ -273,3 +209,23 @@ float TEdgeManager::FindCollisionDistance(ray_type* ray, TBall* ball, TEdgeSegme
return distance; 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 };
}

View File

@ -6,17 +6,17 @@ class TEdgeBox;
struct field_effect_type struct field_effect_type
{ {
char* Flag2Ptr; char* ActiveFlag;
int Mask; int CollisionGroup;
TCollisionComponent* CollisionComp; TCollisionComponent* CollisionComp;
}; };
class TEdgeManager class TEdgeManager
{ {
public: public:
TEdgeManager(float posX, float posY, float width, float height); TEdgeManager(float xMin, float yMin, float width, float height);
~TEdgeManager(); ~TEdgeManager();
void FieldEffects(TBall* ball, struct vector_type* dstVec); void FieldEffects(TBall* ball, struct vector2* dstVec);
int box_x(float x); int box_x(float x);
int box_y(float y); int box_y(float y);
int increment_box_x(int x); int increment_box_x(int x);
@ -25,15 +25,19 @@ public:
void add_field_to_box(int x, int y, field_effect_type* field); 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); 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); float FindCollisionDistance(ray_type* ray, TBall* ball, TEdgeSegment** edge);
vector2 NormalizeBox(vector2 pt) const;
vector2 DeNormalizeBox(vector2 pt) const;
float AdvanceX; float AdvanceX;
float AdvanceY; float AdvanceY;
float AdvanceXInv;
float AdvanceYInv;
int MaxBoxX; int MaxBoxX;
int MaxBoxY; int MaxBoxY;
float X; float MinX;
float Y; float MinY;
float MaxX;
float MaxY;
float Width;
float Height;
TEdgeBox* BoxArray; TEdgeBox* BoxArray;
TEdgeSegment* EdgeArray[1000]{}; TEdgeSegment* EdgeArray[1000]{};
}; };

View File

@ -8,7 +8,7 @@
TEdgeSegment::TEdgeSegment(TCollisionComponent* collComp, char* activeFlag, unsigned collisionGroup) TEdgeSegment::TEdgeSegment(TCollisionComponent* collComp, char* activeFlag, unsigned collisionGroup)
{ {
CollisionComponent = collComp; CollisionComponent = collComp;
ActiveFlag = activeFlag; ActiveFlagPtr = activeFlag;
CollisionGroup = collisionGroup; CollisionGroup = collisionGroup;
ProcessedFlag = 0; ProcessedFlag = 0;
} }
@ -20,7 +20,8 @@ void TEdgeSegment::port_draw()
TEdgeSegment* TEdgeSegment::install_wall(float* floatArr, TCollisionComponent* collComp, char* activeFlagPtr, TEdgeSegment* TEdgeSegment::install_wall(float* floatArr, TCollisionComponent* collComp, char* activeFlagPtr,
unsigned int collisionGroup, float offset, size_t wallValue) unsigned int collisionGroup, float offset, size_t wallValue)
{ {
vector_type center{}, start{}, end{}, prevCenter{}, vec1{}, vec2{}, dstVec{}; vector2 center{}, start{}, end{}, prevCenter{};
vector3 vec1{}, vec2{}, dstVec{};
TEdgeSegment* edge = nullptr; TEdgeSegment* edge = nullptr;
wall_type wallType = static_cast<wall_type>(static_cast<int>(floor(*floatArr) - 1.0f)); wall_type wallType = static_cast<wall_type>(static_cast<int>(floor(*floatArr) - 1.0f));
@ -32,15 +33,10 @@ TEdgeSegment* TEdgeSegment::install_wall(float* floatArr, TCollisionComponent* c
center.Y = floatArr[2]; center.Y = floatArr[2];
auto radius = offset + floatArr[3]; auto radius = offset + floatArr[3];
auto circle = new TCircle(collComp, activeFlagPtr, collisionGroup, &center, radius); auto circle = new TCircle(collComp, activeFlagPtr, collisionGroup, &center, radius);
edge = circle; circle->WallValue = reinterpret_cast<void*>(wallValue);
circle->place_in_grid(&collComp->AABB);
if (circle)
{
circle->WallValue = reinterpret_cast<void*>(wallValue);
circle->place_in_grid();
}
collComp->EdgeList.push_back(circle); collComp->EdgeList.push_back(circle);
edge = circle;
break; break;
} }
case wall_type::Line: case wall_type::Line:
@ -49,16 +45,12 @@ TEdgeSegment* TEdgeSegment::install_wall(float* floatArr, TCollisionComponent* c
start.Y = floatArr[2]; start.Y = floatArr[2];
end.X = floatArr[3]; end.X = floatArr[3];
end.Y = floatArr[4]; end.Y = floatArr[4];
auto line = new TLine(collComp, activeFlagPtr, collisionGroup, &start, &end); 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; edge = line;
if (line)
{
line->WallValue = reinterpret_cast<void*>(wallValue);
line->Offset(offset);
line->place_in_grid();
collComp->EdgeList.push_back(line);
}
break; break;
} }
default: default:
@ -92,19 +84,15 @@ TEdgeSegment* TEdgeSegment::install_wall(float* floatArr, TCollisionComponent* c
vec1.Y = center.Y - prevCenter.Y; vec1.Y = center.Y - prevCenter.Y;
vec2.X = centerX2 - centerX1; vec2.X = centerX2 - centerX1;
vec2.Y = centerY2 - center.Y; vec2.Y = centerY2 - center.Y;
maths::cross(&vec1, &vec2, &dstVec); maths::cross(vec1, vec2, dstVec);
if ((dstVec.Z > 0.0f && offset > 0.0f) || if ((dstVec.Z > 0.0f && offset > 0.0f) ||
(dstVec.Z < 0.0f && offset < 0.0f)) (dstVec.Z < 0.0f && offset < 0.0f))
{ {
float radius = offset * 1.001f; float radius = offset * 1.001f;
auto circle = new TCircle(collComp, activeFlagPtr, collisionGroup, &center, radius); auto circle = new TCircle(collComp, activeFlagPtr, collisionGroup, &center, radius);
circle->WallValue = reinterpret_cast<void*>(wallValue);
if (circle) circle->place_in_grid(&collComp->AABB);
{ collComp->EdgeList.push_back(circle);
circle->WallValue = reinterpret_cast<void*>(wallValue);
circle->place_in_grid();
collComp->EdgeList.push_back(circle);
}
} }
} }
@ -112,17 +100,13 @@ TEdgeSegment* TEdgeSegment::install_wall(float* floatArr, TCollisionComponent* c
start.Y = floatArrPtr[1]; start.Y = floatArrPtr[1];
end.X = floatArrPtr[2]; end.X = floatArrPtr[2];
end.Y = floatArrPtr[3]; end.Y = floatArrPtr[3];
auto line = new TLine(collComp, activeFlagPtr, collisionGroup, &start, &end); 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; edge = line;
if (line)
{
line->WallValue = reinterpret_cast<void*>(wallValue);
line->Offset(offset);
line->place_in_grid();
collComp->EdgeList.push_back(line);
}
prevCenter = center; prevCenter = center;
} }
} }

View File

@ -3,6 +3,7 @@
class TBall; class TBall;
class TCollisionComponent; class TCollisionComponent;
struct ray_type; struct ray_type;
struct RectF;
enum class wall_type : int enum class wall_type : int
{ {
@ -14,7 +15,7 @@ class TEdgeSegment
{ {
public: public:
TCollisionComponent* CollisionComponent; TCollisionComponent* CollisionComponent;
char* ActiveFlag; char* ActiveFlagPtr;
char ProcessedFlag; char ProcessedFlag;
void* WallValue{}; void* WallValue{};
unsigned int CollisionGroup; unsigned int CollisionGroup;
@ -22,10 +23,10 @@ public:
TEdgeSegment(TCollisionComponent* collComp, char* activeFlag, unsigned int collisionGroup); TEdgeSegment(TCollisionComponent* collComp, char* activeFlag, unsigned int collisionGroup);
virtual ~TEdgeSegment() = default; virtual ~TEdgeSegment() = default;
virtual void EdgeCollision(TBall* ball, float coef) = 0; virtual void EdgeCollision(TBall* ball, float distance) = 0;
virtual void port_draw(); virtual void port_draw();
virtual void place_in_grid() = 0; virtual void place_in_grid(RectF* aabb) = 0;
virtual float FindCollisionDistance(ray_type* ray) = 0; virtual float FindCollisionDistance(const ray_type& ray) = 0;
static TEdgeSegment* install_wall(float* floatArr, TCollisionComponent* collComp, char* activeFlagPtr, static TEdgeSegment* install_wall(float* floatArr, TCollisionComponent* collComp, char* activeFlagPtr,
unsigned int collisionGroup, float offset, size_t wallValue); unsigned int collisionGroup, float offset, size_t wallValue);

View File

@ -13,7 +13,7 @@
TFlagSpinner::TFlagSpinner(TPinballTable* table, int groupIndex) : TCollisionComponent(table, groupIndex, false) TFlagSpinner::TFlagSpinner(TPinballTable* table, int groupIndex) : TCollisionComponent(table, groupIndex, false)
{ {
visualStruct visual{}; visualStruct visual{};
vector_type end{}, start{}; vector2 end{}, start{};
Timer = 0; Timer = 0;
loader::query_visual(groupIndex, 0, &visual); loader::query_visual(groupIndex, 0, &visual);
@ -21,18 +21,18 @@ TFlagSpinner::TFlagSpinner(TPinballTable* table, int groupIndex) : TCollisionCom
end.Y = visual.FloatArr[1]; end.Y = visual.FloatArr[1];
start.X = visual.FloatArr[2]; start.X = visual.FloatArr[2];
start.Y = visual.FloatArr[3]; start.Y = visual.FloatArr[3];
auto line = new TLine(this, &ActiveFlag, visual.CollisionGroup, &start, &end); auto line = new TLine(this, &ActiveFlag, visual.CollisionGroup, start, end);
if (line) if (line)
{ {
line->place_in_grid(); line->place_in_grid(&AABB);
EdgeList.push_back(line); EdgeList.push_back(line);
} }
line = new TLine(this, &ActiveFlag, visual.CollisionGroup, &end, &start); line = new TLine(this, &ActiveFlag, visual.CollisionGroup, end, start);
PrevCollider = line; PrevCollider = line;
if (line) if (line)
{ {
line->place_in_grid(); line->place_in_grid(&AABB);
EdgeList.push_back(line); EdgeList.push_back(line);
} }
@ -50,9 +50,9 @@ TFlagSpinner::TFlagSpinner(TPinballTable* table, int groupIndex) : TCollisionCom
MinSpeed = *minSpeed; MinSpeed = *minSpeed;
} }
int TFlagSpinner::Message(int code, float value) int TFlagSpinner::Message(MessageCode code, float value)
{ {
if (code == 1024) if (code == MessageCode::Reset)
{ {
if (Timer) if (Timer)
{ {
@ -60,24 +60,17 @@ int TFlagSpinner::Message(int code, float value)
Timer = 0; Timer = 0;
} }
BmpIndex = 0; BmpIndex = 0;
auto bmp = ListBitmap->at(0); SpriteSet(BmpIndex);
auto zMap = ListZMap->at(0);
render::sprite_set(
RenderSprite,
bmp,
zMap,
bmp->XPosition - PinballTable->XOffset,
bmp->YPosition - PinballTable->YOffset);
} }
return 0; return 0;
} }
void TFlagSpinner::Collision(TBall* ball, vector_type* nextPosition, vector_type* direction, float coef, void TFlagSpinner::Collision(TBall* ball, vector2* nextPosition, vector2* direction, float distance,
TEdgeSegment* edge) TEdgeSegment* edge)
{ {
ball->Position.X = nextPosition->X; ball->Position.X = nextPosition->X;
ball->Position.Y = nextPosition->Y; ball->Position.Y = nextPosition->Y;
ball->RayMaxDistance = ball->RayMaxDistance - coef; ball->RayMaxDistance = ball->RayMaxDistance - distance;
ball->not_again(edge); ball->not_again(edge);
SpinDirection = 2 * (PrevCollider != edge) - 1; SpinDirection = 2 * (PrevCollider != edge) - 1;
@ -92,22 +85,11 @@ void TFlagSpinner::Collision(TBall* ball, vector_type* nextPosition, vector_type
NextFrame(); NextFrame();
} }
void TFlagSpinner::put_scoring(int index, int score)
{
if (index < 2)
Scores[index] = score;
}
int TFlagSpinner::get_scoring(int index)
{
return index < 2 ? Scores[index] : 0;
}
void TFlagSpinner::NextFrame() void TFlagSpinner::NextFrame()
{ {
BmpIndex += SpinDirection; BmpIndex += SpinDirection;
int bmpIndex = BmpIndex; int bmpIndex = BmpIndex;
int bmpCount = ListBitmap->size(); int bmpCount = static_cast<int>(ListBitmap->size());
if (bmpIndex >= bmpCount) if (bmpIndex >= bmpCount)
BmpIndex = 0; BmpIndex = 0;
else if (bmpIndex < 0) else if (bmpIndex < 0)
@ -115,22 +97,14 @@ void TFlagSpinner::NextFrame()
if (!PinballTable->TiltLockFlag) if (!PinballTable->TiltLockFlag)
{ {
control::handler(63, this); control::handler(MessageCode::ControlCollision, this);
if (SoftHitSoundId) if (SoftHitSoundId)
loader::play_sound(SoftHitSoundId); loader::play_sound(SoftHitSoundId, this, "TFlagSpinner");
if (!BmpIndex) if (!BmpIndex)
control::handler(62, this); control::handler(MessageCode::ControlSpinnerLoopReset, this);
} }
auto bmp = ListBitmap->at(BmpIndex); SpriteSet(BmpIndex);
auto zMap = ListZMap->at(BmpIndex);
render::sprite_set(
RenderSprite,
bmp,
zMap,
bmp->XPosition - PinballTable->XOffset,
bmp->YPosition - PinballTable->YOffset);
Speed *= SpeedDecrement; Speed *= SpeedDecrement;
if (Speed >= MinSpeed) if (Speed >= MinSpeed)
{ {

View File

@ -6,11 +6,9 @@ class TFlagSpinner :
{ {
public: public:
TFlagSpinner(TPinballTable* table, int groupIndex); TFlagSpinner(TPinballTable* table, int groupIndex);
int Message(int code, float value) override; int Message(MessageCode code, float value) override;
void Collision(TBall* ball, vector_type* nextPosition, vector_type* direction, float coef, void Collision(TBall* ball, vector2* nextPosition, vector2* direction, float distance,
TEdgeSegment* edge) override; TEdgeSegment* edge) override;
void put_scoring(int index, int score) override;
int get_scoring(int index) override;
void NextFrame(); void NextFrame();
static void SpinTimer(int timerId, void* caller); static void SpinTimer(int timerId, void* caller);
@ -23,6 +21,5 @@ public:
int BmpIndex{}; int BmpIndex{};
int Timer; int Timer;
TEdgeSegment* PrevCollider; TEdgeSegment* PrevCollider;
int Scores[2]{};
}; };

View File

@ -6,6 +6,7 @@
#include "loader.h" #include "loader.h"
#include "pb.h" #include "pb.h"
#include "render.h" #include "render.h"
#include "TBall.h"
#include "TFlipperEdge.h" #include "TFlipperEdge.h"
#include "timer.h" #include "timer.h"
#include "TPinballTable.h" #include "TPinballTable.h"
@ -18,22 +19,14 @@ TFlipper::TFlipper(TPinballTable* table, int groupIndex) : TCollisionComponent(t
HardHitSoundId = visual.SoundIndex4; HardHitSoundId = visual.SoundIndex4;
SoftHitSoundId = visual.SoundIndex3; SoftHitSoundId = visual.SoundIndex3;
Elasticity = visual.Elasticity; Elasticity = visual.Elasticity;
Timer = 0;
Smoothness = visual.Smoothness; Smoothness = visual.Smoothness;
auto collMult = *loader::query_float_attribute(groupIndex, 0, 803); auto collMult = *loader::query_float_attribute(groupIndex, 0, 803);
auto retractTime = *loader::query_float_attribute(groupIndex, 0, 805); auto retractTime = *loader::query_float_attribute(groupIndex, 0, 805);
auto extendTime = *loader::query_float_attribute(groupIndex, 0, 804); auto extendTime = *loader::query_float_attribute(groupIndex, 0, 804);
auto vecT2 = reinterpret_cast<vector3*>(loader::query_float_attribute(groupIndex, 0, 802));
/*Full tilt hack: different flipper speed*/ auto vecT1 = reinterpret_cast<vector3*>(loader::query_float_attribute(groupIndex, 0, 801));
if (pb::FullTiltMode) auto origin = reinterpret_cast<vector3*>(loader::query_float_attribute(groupIndex, 0, 800));
{
retractTime = 0.08f;
extendTime = 0.04f;
}
auto vecT2 = reinterpret_cast<vector_type*>(loader::query_float_attribute(groupIndex, 0, 802));
auto vecT1 = reinterpret_cast<vector_type*>(loader::query_float_attribute(groupIndex, 0, 801));
auto origin = reinterpret_cast<vector_type*>(loader::query_float_attribute(groupIndex, 0, 800));
auto flipperEdge = new TFlipperEdge( auto flipperEdge = new TFlipperEdge(
this, this,
&ActiveFlag, &ActiveFlag,
@ -47,79 +40,67 @@ TFlipper::TFlipper(TPinballTable* table, int groupIndex) : TCollisionComponent(t
collMult, collMult,
Elasticity, Elasticity,
Smoothness); Smoothness);
flipperEdge->place_in_grid(&AABB);
FlipperEdge = flipperEdge; FlipperEdge = flipperEdge;
if (flipperEdge)
{
ExtendAnimationFrameTime = flipperEdge->ExtendTime / static_cast<float>(ListBitmap->size() - 1);
RetractAnimationFrameTime = flipperEdge->RetractTime / static_cast<float>(ListBitmap->size() - 1);
}
BmpIndex = 0; BmpIndex = 0;
InputTime = 0.0; if (table)
table->FlipperList.push_back(this);
} }
TFlipper::~TFlipper() TFlipper::~TFlipper()
{ {
delete FlipperEdge; 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(int code, float value) int TFlipper::Message(MessageCode code, float value)
{ {
if (code == 1 || code == 2 || (code > 1008 && code <= 1011) || code == 1022) switch (code)
{ {
float timerTime; case MessageCode::TFlipperExtend:
int command = code; case MessageCode::TFlipperRetract:
if (code == 1) case MessageCode::Resume:
case MessageCode::LooseFocus:
case MessageCode::SetTiltLock:
case MessageCode::GameOver:
if (code == MessageCode::TFlipperExtend)
{ {
control::handler(1, this); control::handler(MessageCode::TFlipperExtend, this);
TimerTime = ExtendAnimationFrameTime; loader::play_sound(HardHitSoundId, this, "TFlipper1");
loader::play_sound(HardHitSoundId);
} }
else if (code == 2) else if (code == MessageCode::TFlipperRetract)
{ {
TimerTime = RetractAnimationFrameTime; loader::play_sound(SoftHitSoundId, this, "TFlipper2");
loader::play_sound(SoftHitSoundId);
} }
else else
{ {
// Retract for all non-input messages // Retract for all non-input messages
command = 2; code = MessageCode::TFlipperRetract;
TimerTime = RetractAnimationFrameTime;
} }
MessageField = FlipperEdge->SetMotion(code);
break;
case MessageCode::PlayerChanged:
case MessageCode::Reset:
if (MessageField) if (MessageField)
{ {
// Message arrived before animation is finished FlipperEdge->CurrentAngle = 0;
auto inputDt = value - FlipperEdge->InputTime; FlipperEdge->set_control_points(0);
timerTime = inputDt - floor(inputDt / TimerTime) * TimerTime; MessageField = 0;
if (timerTime < 0.0f) FlipperEdge->SetMotion(MessageCode::Reset);
timerTime = 0.0; UpdateSprite();
} }
else break;
{ default: break;
timerTime = TimerTime;
}
MessageField = command;
InputTime = value;
if (Timer)
timer::kill(Timer);
Timer = timer::set(timerTime, this, TimerExpired);
FlipperEdge->SetMotion(command, value);
} }
if (code == 1020 || code == 1024)
{
if (MessageField)
{
if (Timer)
timer::kill(Timer);
BmpIndex = -1;
MessageField = 2;
TimerExpired(Timer, this);
FlipperEdge->SetMotion(code, value);
}
}
return 0; return 0;
} }
@ -128,57 +109,95 @@ void TFlipper::port_draw()
FlipperEdge->port_draw(); FlipperEdge->port_draw();
} }
void TFlipper::Collision(TBall* ball, vector_type* nextPosition, vector_type* direction, float coef, TEdgeSegment* edge) void TFlipper::Collision(TBall* ball, vector2* nextPosition, vector2* direction, float distance, TEdgeSegment* edge)
{ {
} }
void TFlipper::TimerExpired(int timerId, void* caller) void TFlipper::UpdateSprite()
{ {
auto flip = static_cast<TFlipper*>(caller); auto bmpCountSub1 = static_cast<int>(ListBitmap->size()) - 1;
int bmpCountSub1 = flip->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;
auto newBmpIndex = static_cast<int>(floor((pb::time_now - flip->InputTime) / flip->TimerTime)); BmpIndex = newBmpIndex;
if (newBmpIndex > bmpCountSub1) SpriteSet(BmpIndex);
newBmpIndex = bmpCountSub1; }
if (newBmpIndex < 0)
newBmpIndex = 0;
bool bmpIndexOutOfBounds = false; int TFlipper::GetFlipperStepAngle(float dt, float* dst) const
if (flip->MessageField == 1) {
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)
{ {
flip->BmpIndex = newBmpIndex; *dst = deltaAngle / step;
if (flip->BmpIndex >= bmpCountSub1) return static_cast<int>(step);
{
flip->BmpIndex = bmpCountSub1;
bmpIndexOutOfBounds = true;
}
} }
if (flip->MessageField == 2)
*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)
{ {
flip->BmpIndex = bmpCountSub1 - newBmpIndex; if ((FlipperEdge->CollisionGroup & ball->CollisionMask) != 0 &&
if (flip->BmpIndex <= 0) FlipperEdge->YMax >= ball->Position.Y && FlipperEdge->YMin <= ball->Position.Y &&
FlipperEdge->XMax >= ball->Position.X && FlipperEdge->XMin <= ball->Position.X)
{ {
flip->BmpIndex = 0; if (FlipperEdge->ControlPointDirtyFlag)
bmpIndexOutOfBounds = true; 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 (bmpIndexOutOfBounds) if (collisionFlag)
{ {
flip->MessageField = 0; auto angleAdvance = deltaAngle / (std::fabs(FlipperEdge->MoveSpeed) * 5.0f);
flip->Timer = 0; FlipperEdge->CurrentAngle -= angleAdvance;
FlipperEdge->AngleRemainder += std::fabs(angleAdvance);
} }
else else
{ {
flip->Timer = timer::set(flip->TimerTime, flip, TimerExpired); FlipperEdge->CurrentAngle += deltaAngle;
FlipperEdge->AngleRemainder -= std::fabs(deltaAngle);
} }
auto bmp = flip->ListBitmap->at(flip->BmpIndex); if (FlipperEdge->AngleRemainder <= 0.0001f)
auto zMap = flip->ListZMap->at(flip->BmpIndex); {
render::sprite_set( FlipperEdge->CurrentAngle = FlipperEdge->AngleDst;
flip->RenderSprite, FlipperEdge->FlipperFlag = MessageCode::TFlipperNull;
bmp, MessageField = 0;
zMap, }
bmp->XPosition - flip->PinballTable->XOffset, FlipperEdge->ControlPointDirtyFlag = true;
bmp->YPosition - flip->PinballTable->YOffset);
} }

View File

@ -9,18 +9,14 @@ class TFlipper :
public: public:
TFlipper(TPinballTable* table, int groupIndex); TFlipper(TPinballTable* table, int groupIndex);
~TFlipper() override; ~TFlipper() override;
int Message(int code, float value) override; int Message(MessageCode code, float value) override;
void port_draw() override; void port_draw() override;
void Collision(TBall* ball, vector_type* nextPosition, vector_type* direction, float coef, void Collision(TBall* ball, vector2* nextPosition, vector2* direction, float distance,
TEdgeSegment* edge) override; TEdgeSegment* edge) override;
void UpdateSprite();
static void TimerExpired(int timerId, void* caller); int GetFlipperStepAngle(float dt, float* dst) const;
void FlipperCollision(float deltaAngle);
int BmpIndex; int BmpIndex;
TFlipperEdge* FlipperEdge; TFlipperEdge* FlipperEdge;
int Timer;
float ExtendAnimationFrameTime{};
float RetractAnimationFrameTime{};
float TimerTime{};
float InputTime;
}; };

View File

@ -2,29 +2,26 @@
#include "TFlipperEdge.h" #include "TFlipperEdge.h"
#include "pb.h"
#include "TLine.h" #include "TLine.h"
#include "TPinballTable.h" #include "TPinballTable.h"
#include "TTableLayer.h" #include "TTableLayer.h"
float TFlipperEdge::flipper_sin_angle, TFlipperEdge::flipper_cos_angle;
vector_type TFlipperEdge::A1, TFlipperEdge::A2, TFlipperEdge::B1, TFlipperEdge::B2, TFlipperEdge::T1;
line_type TFlipperEdge::lineA, TFlipperEdge::lineB;
circle_type TFlipperEdge::circlebase, TFlipperEdge::circleT1;
TFlipperEdge::TFlipperEdge(TCollisionComponent* collComp, char* activeFlag, unsigned int collisionGroup, TPinballTable* table, TFlipperEdge::TFlipperEdge(TCollisionComponent* collComp, char* activeFlag, unsigned int collisionGroup,
vector_type* origin, vector_type* vecT1, vector_type* vecT2, float extendTime, float retractTime, TPinballTable* table,
float collMult, float elasticity, float smoothness): TEdgeSegment(collComp, activeFlag, collisionGroup) vector3* origin, vector3* vecT1, vector3* vecT2, float extendSpeed, float retractSpeed,
float collMult, float elasticity, float smoothness): TEdgeSegment(
collComp, activeFlag, collisionGroup)
{ {
vector_type crossProd{}, vecDir1{}, vecDir2{}; vector3 crossProd{}, vecOriginT1{}, vecOriginT2{};
Elasticity = elasticity; Elasticity = elasticity;
Smoothness = smoothness; Smoothness = smoothness;
ExtendTime = extendTime;
RetractTime = retractTime;
CollisionMult = collMult; CollisionMult = collMult;
T1Src = *vecT1; T1Src = static_cast<vector2>(*vecT1);
T2Src = *vecT2; T2Src = static_cast<vector2>(*vecT2);
RotOrigin.X = origin->X; RotOrigin.X = origin->X;
RotOrigin.Y = origin->Y; RotOrigin.Y = origin->Y;
@ -36,293 +33,146 @@ TFlipperEdge::TFlipperEdge(TCollisionComponent* collComp, char* activeFlag, unsi
CircleT1RadiusMSq = CircleT1Radius * 1.01f * (CircleT1Radius * 1.01f); CircleT1RadiusMSq = CircleT1Radius * 1.01f * (CircleT1Radius * 1.01f);
CircleT1RadiusSq = CircleT1Radius * CircleT1Radius; CircleT1RadiusSq = CircleT1Radius * CircleT1Radius;
vecDir1.X = vecT1->X - origin->X; vecOriginT1.X = vecT1->X - origin->X;
vecDir1.Y = vecT1->Y - origin->Y; vecOriginT1.Y = vecT1->Y - origin->Y;
vecDir1.Z = 0.0; vecOriginT1.Z = 0.0;
maths::normalize_2d(&vecDir1); maths::normalize_2d(vecOriginT1);
vecDir2.X = vecT2->X - origin->X; vecOriginT2.X = vecT2->X - origin->X;
vecDir2.Y = vecT2->Y - origin->Y; vecOriginT2.Y = vecT2->Y - origin->Y;
vecDir2.Z = 0.0; vecOriginT2.Z = 0.0;
maths::normalize_2d(&vecDir2); maths::normalize_2d(vecOriginT2);
AngleMax = acos(maths::DotProduct(&vecDir1, &vecDir2)); AngleMax = acos(maths::DotProduct(vecOriginT1, vecOriginT2));
maths::cross(&vecDir1, &vecDir2, &crossProd); maths::cross(vecOriginT1, vecOriginT2, crossProd);
if (crossProd.Z < 0.0f) if (crossProd.Z < 0.0f)
AngleMax = -AngleMax; AngleMax = -AngleMax;
FlipperFlag = 0;
Angle1 = 0.0;
auto dirX1 = vecDir1.X; FlipperFlag = MessageCode::TFlipperNull;
auto dirY1 = -vecDir1.Y; AngleDst = 0.0;
A2Src.X = dirY1 * CirclebaseRadius + origin->X;
A2Src.Y = dirX1 * CirclebaseRadius + origin->Y;
A1Src.X = dirY1 * CircleT1Radius + vecT1->X;
A1Src.Y = dirX1 * CircleT1Radius + vecT1->Y;
dirX1 = -dirX1; // 3DPB and FT have different formats for flipper speed:
dirY1 = -dirY1; // 3DPB: Time it takes for flipper to go from source to destination, in sec.
B1Src.X = dirY1 * CirclebaseRadius + origin->X; // FT: Flipper movement speed, in radians per sec.
B1Src.Y = dirX1 * CirclebaseRadius + origin->Y; if (!pb::FullTiltMode)
B2Src.X = dirY1 * CircleT1Radius + vecT1->X; {
B2Src.Y = dirX1 * CircleT1Radius + vecT1->Y; 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) if (AngleMax < 0.0f)
{ {
maths::vswap(&A1Src, &B1Src); std::swap(A1Src, B1Src);
maths::vswap(&A2Src, &B2Src); std::swap(A2Src, B2Src);
} }
auto dx = vecT1->X - RotOrigin.X; auto dx = vecT1->X - RotOrigin.X;
auto dy = vecT1->Y - RotOrigin.Y; auto dy = vecT1->Y - RotOrigin.Y;
auto distance1 = sqrt(dy * dy + dx * dx) + table->CollisionCompOffset + vecT1->Z; auto distance1 = sqrt(dy * dy + dx * dx) + table->CollisionCompOffset + vecT1->Z;
DistanceDiv = distance1;
DistanceDivSq = distance1 * distance1; DistanceDivSq = distance1 * distance1;
InvT1Radius = 1.0f / CircleT1Radius * 1.5f;
float minMoveTime = std::min(ExtendTime, RetractTime); if (AngleMax <= 0.0f)
auto distance = maths::Distance(vecT1, vecT2); {
CollisionTimeAdvance = minMoveTime / (distance / CircleT1Radius + distance / CircleT1Radius); ExtendSpeed = -ExtendSpeed;
}
TFlipperEdge::place_in_grid(); else
EdgeCollisionFlag = 0; {
InputTime = 0.0; RetractSpeed = -RetractSpeed;
CollisionFlag1 = 0; }
AngleStopTime = 0.0; set_control_points(CurrentAngle);
AngleMult = 0.0;
} }
void TFlipperEdge::port_draw() void TFlipperEdge::port_draw()
{ {
set_control_points(InputTime); set_control_points(CurrentAngle);
build_edges_in_motion();
} }
float TFlipperEdge::FindCollisionDistance(ray_type* ray) float TFlipperEdge::FindCollisionDistance(const ray_type& ray)
{ {
auto ogRay = ray; ray_type dstRay{};
ray_type dstRay{}, srcRay{}; if (ControlPointDirtyFlag)
set_control_points(CurrentAngle);
auto distance = maths::distance_to_flipper(this, ray, dstRay);
if (distance >= 1e9f)
return 1e9f;
if (ogRay->TimeNow > AngleStopTime) NextBallPosition = dstRay.Origin;
CollisionDirection = dstRay.Direction;
return distance;
}
void TFlipperEdge::EdgeCollision(TBall* ball, float distance)
{
if (FlipperFlag == MessageCode::TFlipperNull)
{ {
FlipperFlag = 0; maths::basic_collision(
ball,
&NextBallPosition,
&CollisionDirection,
Elasticity,
Smoothness,
1e9f,
0);
return;
} }
if (EdgeCollisionFlag == 0)
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 (FlipperFlag == 0) if (AngleMax > 0)
{ frontCollision = true;
CollisionFlag1 = 0; }
CollisionFlag2 = 0; else if (AngleMax <= 0)
set_control_points(ogRay->TimeNow); {
build_edges_in_motion(); frontCollision = true;
auto ballInside = is_ball_inside(ogRay->Origin.X, ogRay->Origin.Y); }
srcRay.MinDistance = ogRay->MinDistance;
if (ballInside == 0)
{
srcRay.Direction = ogRay->Direction;
srcRay.MaxDistance = ogRay->MaxDistance;
srcRay.Origin = ogRay->Origin;
auto distance = maths::distance_to_flipper(&srcRay, &dstRay);
if (distance == 0.0f)
{
NextBallPosition = dstRay.Origin;
NextBallPosition.X -= srcRay.Direction.X * 1e-05f;
NextBallPosition.Y -= srcRay.Direction.Y * 1e-05f;
}
else
{
NextBallPosition = dstRay.Origin;
}
CollisionDirection = dstRay.Direction;
return distance;
}
if (maths::Distance_Squared(ogRay->Origin, RotOrigin) >= CirclebaseRadiusMSq) if (FlipperFlag == MessageCode::TFlipperRetract)
{ {
if (maths::Distance_Squared(ogRay->Origin, T1) >= CircleT1RadiusMSq) frontCollision ^= true;
{ CollisionLinePerp = LineB.PerpendicularC;
srcRay.Direction.Y = lineB.PerpendicularL.Y;
srcRay.Direction.X = lineB.PerpendicularL.X;
if (ballInside == 4)
{
srcRay.Direction.Y = lineA.PerpendicularL.Y;
srcRay.Direction.X = lineA.PerpendicularL.X;
}
srcRay.Direction.X = -srcRay.Direction.X;
srcRay.Direction.Y = -srcRay.Direction.Y;
}
else
{
srcRay.Direction.X = T1.X - ogRay->Origin.X;
srcRay.Direction.Y = T1.Y - ogRay->Origin.Y;
maths::normalize_2d(&srcRay.Direction);
}
}
else
{
srcRay.Direction.X = RotOrigin.X - ogRay->Origin.X;
srcRay.Direction.Y = RotOrigin.Y - ogRay->Origin.Y;
maths::normalize_2d(&srcRay.Direction);
}
srcRay.Origin.X = ogRay->Origin.X - srcRay.Direction.X * 5.0f;
srcRay.Origin.Y = ogRay->Origin.Y - srcRay.Direction.Y * 5.0f;
srcRay.MaxDistance = ogRay->MaxDistance + 10.0f;
if (maths::distance_to_flipper(&srcRay, &dstRay) >= 1e+09f)
{
srcRay.Direction.X = RotOrigin.X - ogRay->Origin.X;
srcRay.Direction.Y = RotOrigin.Y - ogRay->Origin.Y;
maths::normalize_2d(&srcRay.Direction);
srcRay.Origin.X = ogRay->Origin.X - srcRay.Direction.X * 5.0f;
srcRay.Origin.Y = ogRay->Origin.Y - srcRay.Direction.Y * 5.0f;
if (maths::distance_to_flipper(&srcRay, &dstRay) >= 1e+09f)
{
return 1e+09;
}
}
NextBallPosition = dstRay.Origin;
CollisionDirection = dstRay.Direction;
NextBallPosition.X -= srcRay.Direction.X * 1e-05f;
NextBallPosition.Y -= srcRay.Direction.Y * 1e-05f;
return 0.0;
}
auto posX = ogRay->Origin.X;
auto posY = ogRay->Origin.Y;
auto posXAdvance = ogRay->Direction.X * CollisionTimeAdvance;
auto posYAdvance = ogRay->Direction.Y * CollisionTimeAdvance;
auto rayMaxDistance = ogRay->MaxDistance * CollisionTimeAdvance;
auto timeNow = ogRay->TimeNow;
auto stopTime = ogRay->TimeDelta + ogRay->TimeNow;
while (timeNow < stopTime)
{
set_control_points(timeNow);
build_edges_in_motion();
auto ballInside = is_ball_inside(posX, posY);
if (ballInside != 0)
{
vector_type* linePtr;
if (FlipperFlag == 1 && ballInside != 5)
{
linePtr = &lineA.PerpendicularL;
srcRay.Direction.Y = lineA.PerpendicularL.Y;
srcRay.Direction.X = lineA.PerpendicularL.X;
}
else
{
if (FlipperFlag != 2 || ballInside == 4)
{
CollisionFlag1 = 0;
CollisionFlag2 = 1;
srcRay.Direction.X = RotOrigin.X - posX;
srcRay.Direction.Y = RotOrigin.Y - posY;
maths::normalize_2d(&srcRay.Direction);
srcRay.Origin.X = posX - srcRay.Direction.X * 5.0f;
srcRay.Origin.Y = posY - srcRay.Direction.Y * 5.0f;
srcRay.MaxDistance = ogRay->MaxDistance + 10.0f;
if (maths::distance_to_flipper(&srcRay, &dstRay) >= 1e+09f)
{
NextBallPosition.X = posX;
NextBallPosition.Y = posY;
CollisionDirection.X = -srcRay.Direction.X;
CollisionDirection.Y = -srcRay.Direction.Y;
return 0.0;
}
NextBallPosition = dstRay.Origin;
CollisionDirection = dstRay.Direction;
NextBallPosition.X -= srcRay.Direction.X * 1e-05f;
NextBallPosition.Y -= srcRay.Direction.Y * 1e-05f;
return 0.0;
}
linePtr = &lineB.PerpendicularL;
srcRay.Direction.Y = lineB.PerpendicularL.Y;
srcRay.Direction.X = lineB.PerpendicularL.X;
}
CollisionLinePerp = *linePtr;
CollisionFlag2 = 0;
CollisionFlag1 = 1;
srcRay.Direction.X = -srcRay.Direction.X;
srcRay.Direction.Y = -srcRay.Direction.Y;
srcRay.MinDistance = 0.002f;
srcRay.Origin.X = ogRay->Origin.X - srcRay.Direction.X * 5.0f;
srcRay.Origin.Y = ogRay->Origin.Y - srcRay.Direction.Y * 5.0f;
srcRay.MaxDistance = ogRay->MaxDistance + 10.0f;
auto distance = maths::distance_to_flipper(&srcRay, &dstRay);
CollisionDirection = dstRay.Direction;
if (distance >= 1e+09f)
{
return 1e+09;
}
NextBallPosition = dstRay.Origin;
NextBallPosition.X -= srcRay.Direction.X * 1e-05f;
NextBallPosition.Y -= srcRay.Direction.Y * 1e-05f;
return 0.0;
}
srcRay.Direction = ogRay->Direction;
srcRay.MinDistance = ogRay->MinDistance;
srcRay.Origin = ogRay->Origin;
srcRay.MaxDistance = rayMaxDistance;
auto distance = maths::distance_to_flipper(&srcRay, &dstRay);
if (distance < 1e+09f)
{
NextBallPosition = dstRay.Origin;
NextBallPosition.X -= srcRay.Direction.X * 1e-05f;
NextBallPosition.Y -= srcRay.Direction.Y * 1e-05f;
vector_type* linePtr;
if (FlipperFlag == 2)
{
linePtr = &lineB.PerpendicularL;
CollisionFlag1 = AngleMax <= 0.0f;
}
else
{
CollisionFlag1 = AngleMax > 0.0f;
linePtr = &lineA.PerpendicularL;
}
CollisionLinePerp = *linePtr;
CollisionDirection = dstRay.Direction;
return distance;
}
timeNow = timeNow + CollisionTimeAdvance;
posX = posX + posXAdvance;
posY = posY + posYAdvance;
}
} }
else else
{ {
EdgeCollisionFlag = 0; CollisionLinePerp = LineA.PerpendicularC;
} }
return 1e+09;
}
void TFlipperEdge::EdgeCollision(TBall* ball, float coef) auto dx = NextBallPosition.X - RotOrigin.X;
{ auto dy = NextBallPosition.Y - RotOrigin.Y;
EdgeCollisionFlag = 1; auto distanceSq = dy * dy + dx * dx;
if (!FlipperFlag || !CollisionFlag2 || CollisionFlag1) if (frontCollision)
{ {
float boost = 0.0; float boost = 0;
if (CollisionFlag1) if (circlebase.RadiusSq * 1.01f < distanceSq)
{ {
float dx = NextBallPosition.X - RotOrigin.X; auto v21 = std::fabs(MoveSpeed) * std::sqrt(distanceSq / DistanceDivSq);
float dy = NextBallPosition.Y - RotOrigin.Y; auto dot1 = maths::DotProduct(CollisionLinePerp, CollisionDirection);
float distance = dy * dy + dx * dx; if (dot1 >= 0)
if (circlebase.RadiusSq * 1.01f < distance) boost = CollisionMult * dot1 * v21;
{
float v11;
float v20 = sqrt(distance / DistanceDivSq) * (fabs(AngleMax) / AngleMult);
float dot1 = maths::DotProduct(&CollisionLinePerp, &CollisionDirection);
if (dot1 >= 0.0f)
v11 = dot1 * v20;
else
v11 = 0.0;
boost = v11 * CollisionMult;
}
} }
float threshold = boost <= 0.0f ? 1000000000.0f : -1.0f; auto threshold = boost <= 0.0f ? 1e9f : -1.0f;
maths::basic_collision( maths::basic_collision(
ball, ball,
&NextBallPosition, &NextBallPosition,
@ -331,159 +181,93 @@ void TFlipperEdge::EdgeCollision(TBall* ball, float coef)
Smoothness, Smoothness,
threshold, threshold,
boost); boost);
return; }
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});
} }
float elasticity; TTableLayer::edges_insert_square(yMin, xMin, yMax, xMax, this, nullptr);
float dx = NextBallPosition.X - RotOrigin.X;
float dy = NextBallPosition.Y - RotOrigin.Y; auto offset = 1.0f / InvT1Radius + pb::BallHalfRadius;
float distance = dy * dy + dx * dx; XMin = xMin - offset;
if (circlebase.RadiusSq * 1.01f < distance) YMin = yMin - offset;
elasticity = (1.0f - sqrt(distance / DistanceDivSq)) * Elasticity; XMax = xMax + offset;
else YMax = yMax + offset;
elasticity = Elasticity;
maths::basic_collision(ball, &NextBallPosition, &CollisionDirection, elasticity, Smoothness, 1000000000.0, 0.0);
} }
void TFlipperEdge::place_in_grid() void TFlipperEdge::set_control_points(float angle)
{ {
float x0 = RotOrigin.X - CirclebaseRadius; float sin, cos;
float y0 = RotOrigin.Y - CirclebaseRadius; maths::SinCos(angle, sin, cos);
float x1 = RotOrigin.X + CirclebaseRadius;
float y1 = RotOrigin.Y + CirclebaseRadius;
float v2 = T1Src.X - CircleT1Radius;
if (v2 < x0)
x0 = v2;
float v3 = T1Src.Y - CircleT1Radius;
if (v3 < y0)
y0 = v3;
float v4 = T1Src.X + CircleT1Radius;
if (v4 > x1)
x1 = v4;
float v5 = T1Src.Y + CircleT1Radius;
if (v5 > y1)
y1 = v5;
float v6 = T2Src.X - CircleT1Radius;
if (v6 < x0)
x0 = v6;
float v7 = T2Src.Y - CircleT1Radius;
if (v7 < y0)
y0 = v7;
float v8 = T2Src.X + CircleT1Radius;
if (v8 > x1)
x1 = v8;
float v9 = T2Src.Y + CircleT1Radius;
if (v9 > y1)
y1 = v9;
TTableLayer::edges_insert_square(y0, x0, y1, x1, this, nullptr);
}
void TFlipperEdge::set_control_points(float timeNow)
{
maths::SinCos(flipper_angle(timeNow), &flipper_sin_angle, &flipper_cos_angle);
A1 = A1Src; A1 = A1Src;
A2 = A2Src; A2 = A2Src;
B1 = B1Src; B1 = B1Src;
B2 = B2Src; B2 = B2Src;
T1 = T1Src; T1 = T1Src;
maths::RotatePt(&A1, flipper_sin_angle, flipper_cos_angle, &RotOrigin); maths::RotatePt(A1, sin, cos, RotOrigin);
maths::RotatePt(&A2, flipper_sin_angle, flipper_cos_angle, &RotOrigin); maths::RotatePt(A2, sin, cos, RotOrigin);
maths::RotatePt(&T1, flipper_sin_angle, flipper_cos_angle, &RotOrigin); maths::RotatePt(T1, sin, cos, RotOrigin);
maths::RotatePt(&B1, flipper_sin_angle, flipper_cos_angle, &RotOrigin); maths::RotatePt(B1, sin, cos, RotOrigin);
maths::RotatePt(&B2, flipper_sin_angle, flipper_cos_angle, &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;
} }
void TFlipperEdge::build_edges_in_motion() float TFlipperEdge::flipper_angle_delta(float timeDelta)
{ {
maths::line_init(&lineA, A1.X, A1.Y, A2.X, A2.Y); if (FlipperFlag == MessageCode::TFlipperNull)
maths::line_init(&lineB, B1.X, B1.Y, B2.X, B2.Y); return 0.0f;
circlebase.RadiusSq = CirclebaseRadiusSq;
circlebase.Center.X = RotOrigin.X; const auto deltaAngle = MoveSpeed * timeDelta;
circlebase.Center.Y = RotOrigin.Y; if (std::fabs(deltaAngle) > AngleRemainder)
circleT1.RadiusSq = CircleT1RadiusSq; return AngleDst - CurrentAngle;
circleT1.Center.X = T1.X; return deltaAngle;
circleT1.Center.Y = T1.Y;
} }
float TFlipperEdge::flipper_angle(float timeNow) int TFlipperEdge::SetMotion(MessageCode code)
{
if (!FlipperFlag)
return Angle1;
float angle = (Angle1 - Angle2) / AngleMax * AngleMult;
if (angle < 0.0f)
angle = -angle;
if (angle >= 0.0000001f)
angle = (timeNow - InputTime) / angle;
else
angle = 1.0;
angle = std::min(1.0f, std::max(angle, 0.0f));
if (FlipperFlag == 2)
angle = 1.0f - angle;
return angle * AngleMax;
}
int TFlipperEdge::is_ball_inside(float x, float y)
{
vector_type testPoint{};
float dx = RotOrigin.X - x;
float dy = RotOrigin.Y - y;
if (((A2.X - A1.X) * (y - A1.Y) - (A2.Y - A1.Y) * (x - A1.X) >= 0.0f &&
(B1.X - A2.X) * (y - A2.Y) - (B1.Y - A2.Y) * (x - A2.X) >= 0.0f &&
(B2.X - B1.X) * (y - B1.Y) - (B2.Y - B1.Y) * (x - B1.X) >= 0.0f &&
(A1.X - B2.X) * (y - B2.Y) - (A1.Y - B2.Y) * (x - B2.X) >= 0.0f) ||
dy * dy + dx * dx <= CirclebaseRadiusSq ||
(T1.Y - y) * (T1.Y - y) + (T1.X - x) * (T1.X - x) < CircleT1RadiusSq)
{
float flipperLR = AngleMax < 0.0f ? -1.0f : 1.0f;
if (FlipperFlag == 1)
testPoint = AngleMax < 0.0f ? B1 : B2;
else if (FlipperFlag == 2)
testPoint = AngleMax < 0.0f ? A2 : A1;
else
testPoint = T1;
if (((y - testPoint.Y) * (RotOrigin.X - testPoint.X) -
(x - testPoint.X) * (RotOrigin.Y - testPoint.Y)) * flipperLR < 0.0f)
return 4;
return 5;
}
return 0;
}
void TFlipperEdge::SetMotion(int code, float value)
{ {
switch (code) switch (code)
{ {
case 1: case MessageCode::TFlipperExtend:
Angle2 = flipper_angle(value); AngleRemainder = std::fabs(AngleMax - CurrentAngle);
Angle1 = AngleMax; AngleDst = AngleMax;
AngleMult = ExtendTime; MoveSpeed = ExtendSpeed;
break; break;
case 2: case MessageCode::TFlipperRetract:
Angle2 = flipper_angle(value); AngleRemainder = std::fabs(CurrentAngle);
Angle1 = 0.0; AngleDst = 0.0f;
AngleMult = RetractTime; MoveSpeed = RetractSpeed;
break;
case MessageCode::Reset:
AngleRemainder = 0.0f;
AngleDst = 0.0f;
break; break;
case 1024:
FlipperFlag = 0;
Angle1 = 0.0;
return;
default: break; default: break;
} }
if (!FlipperFlag) if (AngleRemainder == 0.0f)
InputTime = value; code = MessageCode::TFlipperNull;
FlipperFlag = code; FlipperFlag = code;
AngleStopTime = AngleMult + InputTime; return static_cast<int>(code);
} }

View File

@ -1,6 +1,7 @@
#pragma once #pragma once
#include "maths.h" #include "maths.h"
#include "TEdgeSegment.h" #include "TEdgeSegment.h"
#include "TPinballComponent.h"
class TPinballTable; class TPinballTable;
@ -8,22 +9,20 @@ class TFlipperEdge : public TEdgeSegment
{ {
public: public:
TFlipperEdge(TCollisionComponent* collComp, char* activeFlag, unsigned int collisionGroup, TPinballTable* table, TFlipperEdge(TCollisionComponent* collComp, char* activeFlag, unsigned int collisionGroup, TPinballTable* table,
vector_type* origin, vector_type* vecT1, vector_type* vecT2, float extendTime, float retractTime, float collMult, vector3* origin, vector3* vecT1, vector3* vecT2, float extendSpeed, float retractSpeed, float collMult,
float elasticity, float smoothness); float elasticity, float smoothness);
void port_draw() override; void port_draw() override;
float FindCollisionDistance(ray_type* ray) override; float FindCollisionDistance(const ray_type& ray) override;
void EdgeCollision(TBall* ball, float coef) override; void EdgeCollision(TBall* ball, float distance) override;
void place_in_grid() override; void place_in_grid(RectF* aabb) override;
void set_control_points(float timeNow); void set_control_points(float angle);
void build_edges_in_motion(); float flipper_angle_delta(float timeDelta);
float flipper_angle(float timeNow); int SetMotion(MessageCode code);
int is_ball_inside(float x, float y);
void SetMotion(int code, float value);
int FlipperFlag; MessageCode FlipperFlag{};
float Elasticity; float Elasticity;
float Smoothness; float Smoothness;
vector_type RotOrigin{}; vector2 RotOrigin{};
float CirclebaseRadius; float CirclebaseRadius;
float CircleT1Radius; float CircleT1Radius;
float CirclebaseRadiusSq; float CirclebaseRadiusSq;
@ -31,31 +30,27 @@ public:
float CirclebaseRadiusMSq; float CirclebaseRadiusMSq;
float CircleT1RadiusMSq; float CircleT1RadiusMSq;
float AngleMax; float AngleMax;
float Angle2{}; float AngleRemainder{};
float Angle1; float AngleDst;
int CollisionFlag1; float CurrentAngle{};
int CollisionFlag2{}; vector2 CollisionLinePerp{};
vector_type CollisionLinePerp{}; vector2 A1Src{};
vector_type A1Src{}; vector2 A2Src{};
vector_type A2Src{}; vector2 B1Src{};
vector_type B1Src{}; vector2 B2Src{};
vector_type B2Src{};
float CollisionMult; float CollisionMult;
vector_type T1Src{}; vector2 T1Src{};
vector_type T2Src{}; vector2 T2Src{};
float DistanceDivSq; float DistanceDiv, DistanceDivSq;
float CollisionTimeAdvance; vector2 CollisionDirection{};
vector_type CollisionDirection{}; float ExtendSpeed;
int EdgeCollisionFlag; float RetractSpeed;
float InputTime; float MoveSpeed;
float AngleStopTime; vector2 NextBallPosition{};
float AngleMult; vector2 A1, A2, B1, B2, T1;
float ExtendTime; line_type LineA, LineB;
float RetractTime; circle_type circlebase, circleT1;
vector_type NextBallPosition{}; float InvT1Radius;
float YMin, YMax, XMin, XMax;
static float flipper_sin_angle, flipper_cos_angle; bool ControlPointDirtyFlag{};
static vector_type A1, A2, B1, B2, T1;
static line_type lineA, lineB;
static circle_type circlebase, circleT1;
}; };

View File

@ -14,28 +14,29 @@ TGate::TGate(TPinballTable* table, int groupIndex) : TCollisionComponent(table,
SoundIndex4 = visual.SoundIndex4; SoundIndex4 = visual.SoundIndex4;
SoundIndex3 = visual.SoundIndex3; SoundIndex3 = visual.SoundIndex3;
ActiveFlag = 1; ActiveFlag = 1;
render::sprite_set_bitmap(RenderSprite, ListBitmap->at(0)); SpriteSet(0);
control::handler(1024, this); control::handler(MessageCode::Reset, this);
} }
int TGate::Message(int code, float value) int TGate::Message(MessageCode code, float value)
{ {
if (code != 1020) switch (code)
{ {
if (code == 53) case MessageCode::TGateDisable:
{ ActiveFlag = 0;
ActiveFlag = 0; SpriteSet(-1);
render::sprite_set_bitmap(RenderSprite, nullptr); loader::play_sound(SoundIndex3, this, "TGate1");
loader::play_sound(SoundIndex3); break;
} case MessageCode::Reset:
else if (code == 54 || code == 1024) case MessageCode::TGateEnable:
{ ActiveFlag = 1;
ActiveFlag = 1; SpriteSet(0);
render::sprite_set_bitmap(RenderSprite, ListBitmap->at(0)); if (code == MessageCode::TGateEnable)
if (code == 54) loader::play_sound(SoundIndex4, this, "TGate2");
loader::play_sound(SoundIndex4); break;
} default: break;
control::handler(code, this);
} }
control::handler(code, this);
return 0; return 0;
} }

View File

@ -6,7 +6,7 @@ class TGate :
{ {
public: public:
TGate(TPinballTable* table, int groupIndex); TGate(TPinballTable* table, int groupIndex);
int Message(int code, float value) override; int Message(MessageCode code, float value) override;
int SoundIndex4; int SoundIndex4;
int SoundIndex3; int SoundIndex3;

View File

@ -18,7 +18,7 @@ THole::THole(TPinballTable* table, int groupIndex) : TCollisionComponent(table,
Unknown4 = 0.050000001f; Unknown4 = 0.050000001f;
MessageField = 0; MessageField = 0;
Timer = 0; Timer = 0;
BallCapturedFlag = 0; BallCapturedFlag = false;
Unknown3 = loader::query_float_attribute(groupIndex, 0, 407, 0.25f); Unknown3 = loader::query_float_attribute(groupIndex, 0, 407, 0.25f);
GravityMult = loader::query_float_attribute(groupIndex, 0, 701, 0.2f); GravityMult = loader::query_float_attribute(groupIndex, 0, 701, 0.2f);
GravityPull = *loader::query_float_attribute(groupIndex, 0, 305); GravityPull = *loader::query_float_attribute(groupIndex, 0, 305);
@ -31,56 +31,53 @@ THole::THole(TPinballTable* table, int groupIndex) : TCollisionComponent(table,
Circle.RadiusSq = 0.001f; Circle.RadiusSq = 0.001f;
auto tCircle = new TCircle(this, &ActiveFlag, visual.CollisionGroup, auto tCircle = new TCircle(this, &ActiveFlag, visual.CollisionGroup,
reinterpret_cast<vector_type*>(visual.FloatArr), reinterpret_cast<vector3*>(visual.FloatArr),
Circle.RadiusSq); Circle.RadiusSq);
if (tCircle) tCircle->place_in_grid(&AABB);
{ EdgeList.push_back(tCircle);
tCircle->place_in_grid();
EdgeList.push_back(tCircle);
}
ZSetValue = loader::query_float_attribute(groupIndex, 0, 408)[2]; ZSetValue = loader::query_float_attribute(groupIndex, 0, 408)[2];
FieldFlag = static_cast<int>(floor(*loader::query_float_attribute(groupIndex, 0, 1304))); CollisionMask = static_cast<int>(floor(*loader::query_float_attribute(groupIndex, 0, 1304)));
/*Full tilt hack - FieldFlag should be on*/ // CollisionMask difference: 3DPB - value given as is, FT: mask = 1 << value
if (pb::FullTiltMode) if (pb::FullTiltMode)
FieldFlag = 1; CollisionMask = 1 << CollisionMask;
Circle.RadiusSq = visual.FloatArr[2] * visual.FloatArr[2]; Circle.RadiusSq = visual.FloatArr[2] * visual.FloatArr[2];
circle.RadiusSq = Circle.RadiusSq; circle.RadiusSq = Circle.RadiusSq;
circle.Center.X = Circle.Center.X; circle.Center.X = Circle.Center.X;
circle.Center.Y = Circle.Center.Y; circle.Center.Y = Circle.Center.Y;
circle.Center.Z = Circle.Center.Z;
Field.Flag2Ptr = &ActiveFlag; Field.ActiveFlag = &ActiveFlag;
Field.CollisionComp = this; Field.CollisionComp = this;
Field.Mask = visual.CollisionGroup; Field.CollisionGroup = visual.CollisionGroup;
TTableLayer::edges_insert_circle(&circle, nullptr, &Field); TTableLayer::edges_insert_circle(&circle, nullptr, &Field);
} }
int THole::Message(int code, float value) int THole::Message(MessageCode code, float value)
{ {
if (code == 1024 && BallCapturedFlag) if (code == MessageCode::Reset && BallCapturedFlag)
{ {
if (Timer) if (Timer)
timer::kill(Timer); timer::kill(Timer);
Timer = 0; Timer = 0;
BallCapturedSecondStage = 1; BallCapturedSecondStage = true;
} }
return 0; return 0;
} }
void THole::Collision(TBall* ball, vector_type* nextPosition, vector_type* direction, float coef, TEdgeSegment* edge) void THole::Collision(TBall* ball, vector2* nextPosition, vector2* direction, float distance, TEdgeSegment* edge)
{ {
if (!BallCapturedFlag) if (!BallCapturedFlag)
{ {
BallCapturedSecondStage = 0; BallCapturedSecondStage = false;
Threshold = 1000000000.0; Threshold = 1e9f;
BallCapturedFlag = 1; BallCapturedFlag = true;
ball->CollisionComp = this; ball->CollisionComp = this;
ball->Position.X = Circle.Center.X; ball->Position.X = Circle.Center.X;
ball->Position.Y = Circle.Center.Y; ball->Position.Y = Circle.Center.Y;
ball->Acceleration.Z = 0.0; ball->Direction.Z = 0.0;
ball->CollisionDisabledFlag = true;
// Ramp hole has no delay in FT. // Ramp hole has no delay in FT.
auto captureTime = pb::FullTiltMode ? 0 : 0.5f; auto captureTime = pb::FullTiltMode ? 0 : 0.5f;
@ -88,36 +85,38 @@ void THole::Collision(TBall* ball, vector_type* nextPosition, vector_type* direc
if (!PinballTable->TiltLockFlag) if (!PinballTable->TiltLockFlag)
{ {
loader::play_sound(HardHitSoundId); loader::play_sound(HardHitSoundId, ball, "THole1");
control::handler(57, this); control::handler(MessageCode::ControlBallCaptured, this);
} }
} }
else
{
DefaultCollision(ball, nextPosition, direction);
}
} }
int THole::FieldEffect(TBall* ball, vector_type* vecDst) int THole::FieldEffect(TBall* ball, vector2* vecDst)
{ {
int result; int result;
vector_type direction{}; vector2 direction{};
if (BallCapturedFlag) if (BallCapturedFlag)
{ {
if (BallCapturedSecondStage) if (BallCapturedSecondStage)
{ {
ball->Acceleration.Z -= PinballTable->GravityDirVectMult * ball->TimeDelta * GravityMult; ball->Direction.Z -= PinballTable->GravityDirVectMult * ball->TimeDelta * GravityMult;
ball->Position.Z += ball->Acceleration.Z; ball->Position.Z += ball->Direction.Z;
if (ball->Position.Z <= ZSetValue) if (ball->Position.Z <= ZSetValue)
{ {
BallCapturedFlag = 0; BallCapturedFlag = false;
BallCapturedSecondStage = 0; BallCapturedSecondStage = false;
ball->Position.Z = ZSetValue; ball->Position.Z = ZSetValue;
ball->Acceleration.Z = 0.0; ball->CollisionMask = CollisionMask;
ball->FieldFlag = FieldFlag;
ball->Acceleration.Y = 0.0;
ball->CollisionComp = nullptr; ball->CollisionComp = nullptr;
ball->Acceleration.X = 0.0; ball->Direction = {};
ball->Speed = 0.0; ball->Speed = 0.0f;
loader::play_sound(SoftHitSoundId); loader::play_sound(SoftHitSoundId, ball, "THole2");
control::handler(58, this); control::handler(MessageCode::ControlBallReleased, this);
} }
} }
result = 0; result = 0;
@ -128,9 +127,9 @@ int THole::FieldEffect(TBall* ball, vector_type* vecDst)
direction.Y = Circle.Center.Y - ball->Position.Y; direction.Y = Circle.Center.Y - ball->Position.Y;
if (direction.X * direction.X + direction.Y * direction.Y <= Circle.RadiusSq) if (direction.X * direction.X + direction.Y * direction.Y <= Circle.RadiusSq)
{ {
maths::normalize_2d(&direction); maths::normalize_2d(direction);
vecDst->X = direction.X * GravityPull - ball->Acceleration.X * ball->Speed; vecDst->X = direction.X * GravityPull - ball->Direction.X * ball->Speed;
vecDst->Y = direction.Y * GravityPull - ball->Acceleration.Y * ball->Speed; vecDst->Y = direction.Y * GravityPull - ball->Direction.Y * ball->Speed;
result = 1; result = 1;
} }
else else
@ -145,5 +144,5 @@ void THole::TimerExpired(int timerId, void* caller)
{ {
auto hole = static_cast<THole*>(caller); auto hole = static_cast<THole*>(caller);
hole->Timer = 0; hole->Timer = 0;
hole->BallCapturedSecondStage = 1; hole->BallCapturedSecondStage = true;
} }

View File

@ -8,21 +8,21 @@ class THole :
{ {
public: public:
THole(TPinballTable* table, int groupIndex); THole(TPinballTable* table, int groupIndex);
int Message(int code, float value) override; int Message(MessageCode code, float value) override;
void Collision(TBall* ball, vector_type* nextPosition, vector_type* direction, float coef, void Collision(TBall* ball, vector2* nextPosition, vector2* direction, float distance,
TEdgeSegment* edge) override; TEdgeSegment* edge) override;
int FieldEffect(TBall* ball, vector_type* vecDst) override; int FieldEffect(TBall* ball, vector2* vecDst) override;
static void TimerExpired(int timerId, void* caller); static void TimerExpired(int timerId, void* caller);
int BallCapturedFlag; bool BallCapturedFlag{};
int BallCapturedSecondStage{}; bool BallCapturedSecondStage{};
int Timer; int Timer;
float Unknown3; float Unknown3;
float Unknown4; float Unknown4;
float GravityMult; float GravityMult;
float ZSetValue; float ZSetValue;
int FieldFlag; int CollisionMask;
float GravityPull; float GravityPull;
circle_type Circle{}; circle_type Circle{};
field_effect_type Field{}; field_effect_type Field{};

View File

@ -19,13 +19,12 @@ TKickback::TKickback(TPinballTable* table, int groupIndex): TCollisionComponent(
Threshold = 1000000000.0f; Threshold = 1000000000.0f;
} }
int TKickback::Message(int code, float value) int TKickback::Message(MessageCode code, float value)
{ {
if ((code == 1011 || code == 1024) && Timer) if ((code == MessageCode::SetTiltLock || code == MessageCode::Reset) && Timer)
{ {
timer::kill(Timer); timer::kill(Timer);
if (ListBitmap) SpriteSet(-1);
render::sprite_set_bitmap(RenderSprite, nullptr);
Timer = 0; Timer = 0;
KickActiveFlag = 0; KickActiveFlag = 0;
Threshold = 1000000000.0; Threshold = 1000000000.0;
@ -33,7 +32,7 @@ int TKickback::Message(int code, float value)
return 0; return 0;
} }
void TKickback::Collision(TBall* ball, vector_type* nextPosition, vector_type* direction, float coef, void TKickback::Collision(TBall* ball, vector2* nextPosition, vector2* direction, float distance,
TEdgeSegment* edge) TEdgeSegment* edge)
{ {
if (PinballTable->TiltLockFlag) if (PinballTable->TiltLockFlag)
@ -62,33 +61,13 @@ void TKickback::TimerExpired(int timerId, void* caller)
{ {
kick->Threshold = 0.0; kick->Threshold = 0.0;
kick->Timer = timer::set(kick->TimerTime2, kick, TimerExpired); kick->Timer = timer::set(kick->TimerTime2, kick, TimerExpired);
loader::play_sound(kick->HardHitSoundId); loader::play_sound(kick->HardHitSoundId, kick, "TKickback");
if (kick->ListBitmap) kick->SpriteSet(1);
{
auto bmp = kick->ListBitmap->at(1);
auto zMap = kick->ListZMap->at(1);
render::sprite_set(
kick->RenderSprite,
bmp,
zMap,
bmp->XPosition - kick->PinballTable->XOffset,
bmp->YPosition - kick->PinballTable->YOffset);
}
} }
else else
{ {
if (kick->ListBitmap) kick->SpriteSet(0);
{
auto bmp = kick->ListBitmap->at(0);
auto zMap = kick->ListZMap->at(0);
render::sprite_set(
kick->RenderSprite,
bmp,
zMap,
bmp->XPosition - kick->PinballTable->XOffset,
bmp->YPosition - kick->PinballTable->YOffset);
}
kick->Timer = 0; kick->Timer = 0;
control::handler(60, kick); control::handler(MessageCode::ControlTimerExpired, kick);
} }
} }

View File

@ -6,8 +6,8 @@ class TKickback :
{ {
public: public:
TKickback(TPinballTable* table, int groupIndex); TKickback(TPinballTable* table, int groupIndex);
int Message(int code, float value) override; int Message(MessageCode code, float value) override;
void Collision(TBall* ball, vector_type* nextPosition, vector_type* direction, float coef, void Collision(TBall* ball, vector2* nextPosition, vector2* direction, float distance,
TEdgeSegment* edge) override; TEdgeSegment* edge) override;
static void TimerExpired(int timerId, void* caller); static void TimerExpired(int timerId, void* caller);

View File

@ -24,7 +24,7 @@ TKickout::TKickout(TPinballTable* table, int groupIndex, bool someFlag): TCollis
TimerTime2 = 0.05f; TimerTime2 = 0.05f;
MessageField = 0; MessageField = 0;
Timer = 0; Timer = 0;
KickFlag1 = 0; BallCaputeredFlag = 0;
FieldMult = *loader::query_float_attribute(groupIndex, 0, 305); FieldMult = *loader::query_float_attribute(groupIndex, 0, 305);
loader::query_visual(groupIndex, 0, &visual); loader::query_visual(groupIndex, 0, &visual);
SoftHitSoundId = visual.SoftHitSoundId; SoftHitSoundId = visual.SoftHitSoundId;
@ -36,10 +36,10 @@ TKickout::TKickout(TPinballTable* table, int groupIndex, bool someFlag): TCollis
if (Circle.RadiusSq == 0.0f) if (Circle.RadiusSq == 0.0f)
Circle.RadiusSq = 0.001f; Circle.RadiusSq = 0.001f;
auto tCircle = new TCircle(this, &ActiveFlag, visual.CollisionGroup, auto tCircle = new TCircle(this, &ActiveFlag, visual.CollisionGroup,
reinterpret_cast<vector_type*>(visual.FloatArr), Circle.RadiusSq); reinterpret_cast<vector3*>(visual.FloatArr), Circle.RadiusSq);
if (tCircle) if (tCircle)
{ {
tCircle->place_in_grid(); tCircle->place_in_grid(&AABB);
EdgeList.push_back(tCircle); EdgeList.push_back(tCircle);
} }
@ -47,38 +47,37 @@ TKickout::TKickout(TPinballTable* table, int groupIndex, bool someFlag): TCollis
auto zAttr = loader::query_float_attribute(groupIndex, 0, 408); auto zAttr = loader::query_float_attribute(groupIndex, 0, 408);
CollisionBallSetZ = pb::FullTiltMode && !pb::FullTiltDemoMode ? zAttr[3] : zAttr[2]; CollisionBallSetZ = pb::FullTiltMode && !pb::FullTiltDemoMode ? zAttr[3] : zAttr[2];
ThrowSpeedMult2 = visual.Kicker.ThrowBallMult * 0.01f; ThrowSpeedMult2 = visual.Kicker.ThrowBallMult * 0.01f;
BallAcceleration = visual.Kicker.ThrowBallAcceleration; BallThrowDirection = visual.Kicker.ThrowBallDirection;
ThrowAngleMult = visual.Kicker.ThrowBallAngleMult; ThrowAngleMult = visual.Kicker.ThrowBallAngleMult;
ThrowSpeedMult1 = visual.Kicker.Boost; ThrowSpeedMult1 = visual.Kicker.Boost;
circle.RadiusSq = Circle.RadiusSq; circle.RadiusSq = Circle.RadiusSq;
circle.Center.X = Circle.Center.X; circle.Center.X = Circle.Center.X;
circle.Center.Y = Circle.Center.Y; circle.Center.Y = Circle.Center.Y;
circle.Center.Z = Circle.Center.Z; Field.ActiveFlag = &ActiveFlag;
Field.Flag2Ptr = &ActiveFlag;
Field.CollisionComp = this; Field.CollisionComp = this;
Field.Mask = visual.CollisionGroup; Field.CollisionGroup = visual.CollisionGroup;
TTableLayer::edges_insert_circle(&circle, nullptr, &Field); TTableLayer::edges_insert_circle(&circle, nullptr, &Field);
} }
int TKickout::Message(int code, float value) int TKickout::Message(MessageCode code, float value)
{ {
switch (code) switch (code)
{ {
case 55: case MessageCode::TKickoutRestartTimer:
if (KickFlag1) if (BallCaputeredFlag)
{ {
if (value < 0.0f) if (value < 0.0f)
value = TimerTime1; value = TimerTime1;
Timer = timer::set(value, this, TimerExpired); Timer = timer::set(value, this, TimerExpired);
} }
break; break;
case 1011: case MessageCode::SetTiltLock:
if (NotSomeFlag) if (NotSomeFlag)
ActiveFlag = 0; ActiveFlag = 0;
break; break;
case 1024: case MessageCode::Reset:
if (KickFlag1) if (BallCaputeredFlag)
{ {
if (Timer) if (Timer)
timer::kill(Timer); timer::kill(Timer);
@ -94,72 +93,69 @@ int TKickout::Message(int code, float value)
return 0; return 0;
} }
void TKickout::put_scoring(int index, int score) void TKickout::Collision(TBall* ball, vector2* nextPosition, vector2* direction, float distance, TEdgeSegment* edge)
{ {
if (index < 5) if (!BallCaputeredFlag)
Scores[index] = score;
}
int TKickout::get_scoring(int index)
{
return index < 5 ? Scores[index] : 0;
}
void TKickout::Collision(TBall* ball, vector_type* nextPosition, vector_type* direction, float coef, TEdgeSegment* edge)
{
if (!KickFlag1)
{ {
Ball = ball; Ball = ball;
Threshold = 1000000000.0; Threshold = 1000000000.0;
KickFlag1 = 1; BallCaputeredFlag = 1;
ball->CollisionComp = this; ball->CollisionComp = this;
ball->Position.X = Circle.Center.X; ball->Position.X = Circle.Center.X;
ball->Position.Y = Circle.Center.Y; ball->Position.Y = Circle.Center.Y;
OriginalBallZ = ball->Position.Z; OriginalBallZ = ball->Position.Z;
ball->Position.Z = CollisionBallSetZ; ball->Position.Z = CollisionBallSetZ;
ball->CollisionDisabledFlag = true;
if (PinballTable->TiltLockFlag) if (PinballTable->TiltLockFlag)
{ {
Message(55, 0.1f); Message(MessageCode::TKickoutRestartTimer, 0.1f);
} }
else else
{ {
loader::play_sound(SoftHitSoundId); loader::play_sound(SoftHitSoundId, ball, "TKickout1");
control::handler(63, this); 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, vector_type* dstVec) int TKickout::FieldEffect(TBall* ball, vector2* dstVec)
{ {
vector_type direction{}; vector2 direction{};
if (KickFlag1) if (BallCaputeredFlag)
return 0; return 0;
direction.X = Circle.Center.X - ball->Position.X; direction.X = Circle.Center.X - ball->Position.X;
direction.Y = Circle.Center.Y - ball->Position.Y; direction.Y = Circle.Center.Y - ball->Position.Y;
if (direction.Y * direction.Y + direction.X * direction.X > Circle.RadiusSq) if (direction.Y * direction.Y + direction.X * direction.X > Circle.RadiusSq)
return 0; return 0;
maths::normalize_2d(&direction); maths::normalize_2d(direction);
dstVec->X = direction.X * FieldMult - ball->Acceleration.X * ball->Speed; dstVec->X = direction.X * FieldMult - ball->Direction.X * ball->Speed;
dstVec->Y = direction.Y * FieldMult - ball->Acceleration.Y * ball->Speed; dstVec->Y = direction.Y * FieldMult - ball->Direction.Y * ball->Speed;
return 1; return 1;
} }
void TKickout::TimerExpired(int timerId, void* caller) void TKickout::TimerExpired(int timerId, void* caller)
{ {
auto kick = static_cast<TKickout*>(caller); auto kick = static_cast<TKickout*>(caller);
if (kick->KickFlag1) if (kick->BallCaputeredFlag)
{ {
kick->KickFlag1 = 0; kick->BallCaputeredFlag = 0;
kick->Timer = timer::set(kick->TimerTime2, kick, ResetTimerExpired); kick->Timer = timer::set(kick->TimerTime2, kick, ResetTimerExpired);
if (kick->Ball) if (kick->Ball)
{ {
loader::play_sound(kick->HardHitSoundId, kick->Ball, "TKickout2");
kick->Ball->Position.Z = kick->OriginalBallZ; kick->Ball->Position.Z = kick->OriginalBallZ;
TBall::throw_ball(kick->Ball, &kick->BallAcceleration, kick->ThrowAngleMult, kick->ThrowSpeedMult1, kick->Ball->throw_ball(&kick->BallThrowDirection, kick->ThrowAngleMult, kick->ThrowSpeedMult1,
kick->ThrowSpeedMult2); kick->ThrowSpeedMult2);
kick->ActiveFlag = 0; kick->ActiveFlag = 0;
kick->Ball = nullptr; kick->Ball = nullptr;
loader::play_sound(kick->HardHitSoundId);
} }
} }
} }

View File

@ -8,17 +8,15 @@ class TKickout :
{ {
public: public:
TKickout(TPinballTable* table, int groupIndex, bool someFlag); TKickout(TPinballTable* table, int groupIndex, bool someFlag);
int Message(int code, float value) override; int Message(MessageCode code, float value) override;
void put_scoring(int index, int score) override; void Collision(TBall* ball, vector2* nextPosition, vector2* direction, float distance,
int get_scoring(int index) override;
void Collision(TBall* ball, vector_type* nextPosition, vector_type* direction, float coef,
TEdgeSegment* edge) override; TEdgeSegment* edge) override;
int FieldEffect(TBall* ball, vector_type* vecDst) override; int FieldEffect(TBall* ball, vector2* vecDst) override;
static void TimerExpired(int timerId, void* caller); static void TimerExpired(int timerId, void* caller);
static void ResetTimerExpired(int timerId, void* caller); static void ResetTimerExpired(int timerId, void* caller);
int KickFlag1; int BallCaputeredFlag;
int NotSomeFlag; int NotSomeFlag;
int Timer; int Timer;
float TimerTime1; float TimerTime1;
@ -28,10 +26,9 @@ public:
float FieldMult; float FieldMult;
circle_type Circle{}; circle_type Circle{};
float OriginalBallZ{}; float OriginalBallZ{};
vector_type BallAcceleration{}; vector3 BallThrowDirection{};
float ThrowAngleMult; float ThrowAngleMult;
float ThrowSpeedMult1; float ThrowSpeedMult1;
float ThrowSpeedMult2; float ThrowSpeedMult2;
field_effect_type Field{}; field_effect_type Field{};
int Scores[5]{};
}; };

View File

@ -10,224 +10,238 @@
TLight::TLight(TPinballTable* table, int groupIndex) : TPinballComponent(table, groupIndex, true) TLight::TLight(TPinballTable* table, int groupIndex) : TPinballComponent(table, groupIndex, true)
{ {
Timer1 = 0; TimeoutTimer = 0;
FlasherActive = 0; FlasherOnFlag = false;
Timer2 = 0; UndoOverrideTimer = 0;
Flasher.Timer = 0; FlashTimer = 0;
Reset(); Reset();
float* floatArr1 = loader::query_float_attribute(groupIndex, 0, 900); float* floatArr1 = loader::query_float_attribute(groupIndex, 0, 900);
Flasher.TimerDelay[0] = *floatArr1; FlashDelay[0] = *floatArr1;
FlasherDelay[0] = *floatArr1; SourceDelay[0] = *floatArr1;
float* floatArr2 = loader::query_float_attribute(groupIndex, 0, 901); float* floatArr2 = loader::query_float_attribute(groupIndex, 0, 901);
Flasher.TimerDelay[1] = *floatArr2; FlashDelay[1] = *floatArr2;
FlasherDelay[1] = *floatArr2; SourceDelay[1] = *floatArr2;
} }
int TLight::Message(int code, float value) int TLight::Message(MessageCode code, float value)
{ {
int bmpIndex; int bmpIndex;
switch (code) switch (code)
{ {
case 1024: case MessageCode::Reset:
Reset(); Reset();
for (auto index = 0; index < PinballTable->PlayerCount; ++index) for (auto index = 0; index < PinballTable->PlayerCount; ++index)
{ {
auto playerPtr = &PlayerData[index]; auto playerPtr = &PlayerData[index];
playerPtr->FlasherActive = FlasherActive; playerPtr->FlasherOnFlag = FlasherOnFlag;
playerPtr->BmpIndex2 = BmpIndex2; playerPtr->LightOnBmpIndex = LightOnBmpIndex;
playerPtr->BmpIndex1 = BmpIndex1; playerPtr->LightOnFlag = LightOnFlag;
playerPtr->MessageField = MessageField; playerPtr->MessageField = MessageField;
} }
break; break;
case 1020: case MessageCode::PlayerChanged:
{ {
auto playerPtr = &PlayerData[PinballTable->CurrentPlayer]; auto playerPtr = &PlayerData[PinballTable->CurrentPlayer];
playerPtr->FlasherActive = FlasherActive; playerPtr->FlasherOnFlag = FlasherOnFlag;
playerPtr->BmpIndex2 = BmpIndex2; playerPtr->LightOnBmpIndex = LightOnBmpIndex;
playerPtr->BmpIndex1 = BmpIndex1; playerPtr->LightOnFlag = LightOnFlag;
playerPtr->MessageField = MessageField; playerPtr->MessageField = MessageField;
Reset(); Reset();
playerPtr = &PlayerData[static_cast<int>(floor(value))]; playerPtr = &PlayerData[static_cast<int>(floor(value))];
FlasherActive = playerPtr->FlasherActive; FlasherOnFlag = playerPtr->FlasherOnFlag;
BmpIndex2 = playerPtr->BmpIndex2; LightOnBmpIndex = playerPtr->LightOnBmpIndex;
BmpIndex1 = playerPtr->BmpIndex1; LightOnFlag = playerPtr->LightOnFlag;
MessageField = playerPtr->MessageField; MessageField = playerPtr->MessageField;
if (BmpIndex2) if (LightOnBmpIndex)
{ {
Message(11, static_cast<float>(BmpIndex2)); Message(MessageCode::TLightSetOnStateBmpIndex, static_cast<float>(LightOnBmpIndex));
} }
if (BmpIndex1) if (LightOnFlag)
Message(1, 0.0); Message(MessageCode::TLightTurnOn, 0.0);
if (FlasherActive) if (FlasherOnFlag)
Message(4, 0.0); Message(MessageCode::TLightFlasherStart, 0.0);
break; break;
} }
case 0: case MessageCode::TLightTurnOff:
BmpIndex1 = 0; LightOnFlag = false;
if (FlasherActive == 0 && !FlasherFlag1 && !FlasherFlag2) if (!FlasherOnFlag && !ToggledOffFlag && !ToggledOnFlag)
render::sprite_set_bitmap(RenderSprite, Flasher.BmpArr[0]); SetSpriteBmp(BmpArr[0]);
break; break;
case 1: case MessageCode::TLightTurnOn:
BmpIndex1 = 1; LightOnFlag = true;
if (FlasherActive == 0 && !FlasherFlag1 && !FlasherFlag2) if (!FlasherOnFlag && !ToggledOffFlag && !ToggledOnFlag)
render::sprite_set_bitmap(RenderSprite, Flasher.BmpArr[1]); SetSpriteBmp(BmpArr[1]);
break; break;
case 2: case MessageCode::TLightGetLightOnFlag:
return BmpIndex1; return LightOnFlag;
case 3: case MessageCode::TLightGetFlasherOnFlag:
return FlasherActive; return FlasherOnFlag;
case 4: case MessageCode::TLightFlasherStart:
schedule_timeout(0.0); schedule_timeout(0.0);
if (!FlasherActive || !Flasher.Timer) if (!FlasherOnFlag || !FlashTimer)
{ {
FlasherActive = 1; FlasherOnFlag = true;
FlasherFlag2 = 0; ToggledOnFlag = false;
FlasherFlag1 = 0; ToggledOffFlag = false;
TurnOffAfterFlashingFg = 0; TurnOffAfterFlashingFg = false;
flasher_start(&Flasher, BmpIndex1); flasher_start(LightOnFlag);
} }
break; break;
case 5: case MessageCode::TLightApplyMultDelay:
Flasher.TimerDelay[0] = value * FlasherDelay[0]; FlashDelay[0] = value * SourceDelay[0];
Flasher.TimerDelay[1] = value * FlasherDelay[1]; FlashDelay[1] = value * SourceDelay[1];
break; break;
case 6: case MessageCode::TLightApplyDelay:
Flasher.TimerDelay[0] = FlasherDelay[0]; FlashDelay[0] = SourceDelay[0];
Flasher.TimerDelay[1] = FlasherDelay[1]; FlashDelay[1] = SourceDelay[1];
break; break;
case 7: case MessageCode::TLightFlasherStartTimed:
if (!FlasherActive) if (!FlasherOnFlag)
flasher_start(&Flasher, BmpIndex1); flasher_start(LightOnFlag);
FlasherActive = 1; FlasherOnFlag = true;
FlasherFlag2 = 0; ToggledOnFlag = false;
TurnOffAfterFlashingFg = 0; TurnOffAfterFlashingFg = false;
FlasherFlag1 = 0; ToggledOffFlag = false;
schedule_timeout(value); schedule_timeout(value);
break; break;
case 8: case MessageCode::TLightTurnOffTimed:
if (!FlasherFlag1) if (!ToggledOffFlag)
{ {
if (FlasherActive) if (FlasherOnFlag)
{ {
flasher_stop(&Flasher, 0); flasher_stop(0);
FlasherActive = 0; FlasherOnFlag = false;
} }
else else
{ {
render::sprite_set_bitmap(RenderSprite, Flasher.BmpArr[0]); SetSpriteBmp(BmpArr[0]);
} }
FlasherFlag1 = 1; ToggledOffFlag = true;
FlasherFlag2 = 0; ToggledOnFlag = false;
} }
schedule_timeout(value); schedule_timeout(value);
break; break;
case 9: case MessageCode::TLightTurnOnTimed:
if (!FlasherFlag2) if (!ToggledOnFlag)
{ {
if (FlasherActive) if (FlasherOnFlag)
{ {
flasher_stop(&Flasher, 1); flasher_stop(1);
FlasherActive = 0; FlasherOnFlag = false;
} }
else else
{ {
render::sprite_set_bitmap(RenderSprite, Flasher.BmpArr[1]); SetSpriteBmp(BmpArr[1]);
} }
FlasherFlag2 = 1; ToggledOnFlag = true;
FlasherFlag1 = 0; ToggledOffFlag = false;
} }
schedule_timeout(value); schedule_timeout(value);
break; break;
case 11: case MessageCode::TLightSetOnStateBmpIndex:
bmpIndex = 0; LightOnBmpIndex = Clamp(static_cast<int>(floor(value)), 0, static_cast<int>(ListBitmap->size()) - 1);
BmpIndex2 = static_cast<int>(floor(value)); BmpArr[0] = -1;
if (BmpIndex2 > static_cast<int>(ListBitmap->size())) BmpArr[1] = LightOnBmpIndex;
BmpIndex2 = ListBitmap->size(); if (!FlasherOnFlag)
if (BmpIndex2 < 0)
BmpIndex2 = 0;
Flasher.BmpArr[0] = nullptr;
Flasher.BmpArr[1] = ListBitmap->at(BmpIndex2);
if (FlasherActive == 0)
{ {
if (!FlasherFlag1) if (ToggledOffFlag)
{ bmpIndex = 0;
if (FlasherFlag2) else if (ToggledOnFlag)
bmpIndex = 1; bmpIndex = 1;
else else
bmpIndex = BmpIndex1; bmpIndex = LightOnFlag;
}
} }
else else
{ {
bmpIndex = Flasher.BmpIndex; bmpIndex = FlashLightOnFlag;
} }
render::sprite_set_bitmap(RenderSprite, Flasher.BmpArr[bmpIndex]); SetSpriteBmp(BmpArr[bmpIndex]);
break; break;
case 12: case MessageCode::TLightIncOnStateBmpIndex:
bmpIndex = BmpIndex2 + 1; bmpIndex = LightOnBmpIndex + 1;
if (bmpIndex > static_cast<int>(ListBitmap->size())) if (bmpIndex >= static_cast<int>(ListBitmap->size()))
bmpIndex = ListBitmap->size(); bmpIndex = static_cast<int>(ListBitmap->size()) - 1;
Message(11, static_cast<float>(bmpIndex)); Message(MessageCode::TLightSetOnStateBmpIndex, static_cast<float>(bmpIndex));
break; break;
case 13: case MessageCode::TLightDecOnStateBmpIndex:
bmpIndex = BmpIndex2 - 1; bmpIndex = LightOnBmpIndex - 1;
if (bmpIndex < 0) if (bmpIndex < 0)
bmpIndex = 0; bmpIndex = 0;
Message(11, static_cast<float>(bmpIndex)); Message(MessageCode::TLightSetOnStateBmpIndex, static_cast<float>(bmpIndex));
break; break;
case 14: case MessageCode::TLightResetTimed:
if (Timer1) if (TimeoutTimer)
timer::kill(Timer1); timer::kill(TimeoutTimer);
Timer1 = 0; TimeoutTimer = 0;
if (FlasherActive != 0) if (FlasherOnFlag)
flasher_stop(&Flasher, -1); flasher_stop(-1);
FlasherActive = 0; FlasherOnFlag = false;
FlasherFlag1 = 0; ToggledOffFlag = false;
FlasherFlag2 = 0; ToggledOnFlag = false;
render::sprite_set_bitmap(RenderSprite, Flasher.BmpArr[BmpIndex1]); SetSpriteBmp(BmpArr[LightOnFlag]);
break; break;
case 15: case MessageCode::TLightFlasherStartTimedThenStayOn:
TurnOffAfterFlashingFg = 0; TurnOffAfterFlashingFg = false;
if (Timer2) if (UndoOverrideTimer)
timer::kill(Timer2); timer::kill(UndoOverrideTimer);
Timer2 = 0; UndoOverrideTimer = 0;
Message(1, 0.0); Message(MessageCode::TLightTurnOn, 0.0);
Message(7, value); Message(MessageCode::TLightFlasherStartTimed, value);
break; break;
case 16: case MessageCode::TLightFlasherStartTimedThenStayOff:
if (Timer2) if (UndoOverrideTimer)
timer::kill(Timer2); timer::kill(UndoOverrideTimer);
Timer2 = 0; UndoOverrideTimer = 0;
Message(7, value); Message(MessageCode::TLightFlasherStartTimed, value);
TurnOffAfterFlashingFg = 1; TurnOffAfterFlashingFg = true;
break; break;
case 17: case MessageCode::TLightToggleValue:
Message(static_cast<int>(floor(value)) != 0, 0.0); Message(static_cast<int>(floor(value)) ? MessageCode::TLightTurnOn : MessageCode::TLightTurnOff, 0.0);
return BmpIndex1; return LightOnFlag;
case 18: case MessageCode::TLightResetAndToggleValue:
Message(17, value); Message(MessageCode::TLightToggleValue, value);
Message(14, 0.0); Message(MessageCode::TLightResetTimed, 0.0);
return BmpIndex1; return LightOnFlag;
case 19: case MessageCode::TLightResetAndTurnOn:
Message(1, 0.0); Message(MessageCode::TLightTurnOn, 0.0);
Message(14, 0.0); Message(MessageCode::TLightResetTimed, 0.0);
break; break;
case 20: case MessageCode::TLightResetAndTurnOff:
Message(0, 0.0); Message(MessageCode::TLightTurnOff, 0.0);
Message(14, 0.0); Message(MessageCode::TLightResetTimed, 0.0);
break; break;
case 21: case MessageCode::TLightToggle:
Message(17, static_cast<float>(BmpIndex1 == 0)); Message(MessageCode::TLightToggleValue, !LightOnFlag);
return BmpIndex1; return LightOnFlag;
case 22: case MessageCode::TLightResetAndToggle:
Message(18, static_cast<float>(BmpIndex1 == 0)); Message(MessageCode::TLightResetAndToggleValue, !LightOnFlag);
return BmpIndex1; return LightOnFlag;
case 23: case MessageCode::TLightSetMessageField:
MessageField = static_cast<int>(floor(value)); MessageField = static_cast<int>(floor(value));
break; 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: default:
break; break;
} }
@ -237,85 +251,99 @@ int TLight::Message(int code, float value)
void TLight::Reset() void TLight::Reset()
{ {
if (Timer1) if (TimeoutTimer)
timer::kill(Timer1); timer::kill(TimeoutTimer);
if (Timer2) if (UndoOverrideTimer)
timer::kill(Timer2); timer::kill(UndoOverrideTimer);
if (FlasherActive) if (FlasherOnFlag)
flasher_stop(&Flasher, -1); flasher_stop(-1);
Unknown20F = 1.0; TimeoutTimer = 0;
Timer1 = 0; UndoOverrideTimer = 0;
Timer2 = 0; LightOnFlag = false;
BmpIndex1 = 0; LightOnBmpIndex = 0;
BmpIndex2 = 0; ToggledOffFlag = false;
FlasherFlag1 = 0; ToggledOnFlag = false;
FlasherFlag2 = 0; FlasherOnFlag = false;
FlasherActive = 0; TemporaryOverrideFlag = false;
TurnOffAfterFlashingFg = 0; TurnOffAfterFlashingFg = false;
render::sprite_set_bitmap(RenderSprite, nullptr); PreviousBitmap = -1;
Flasher.Sprite = RenderSprite; BmpArr[0] = -1;
Flasher.BmpArr[0] = nullptr; BmpArr[1] = 0;
if (ListBitmap) SetSpriteBmp(BmpArr[0]);
Flasher.BmpArr[1] = ListBitmap->at(0);
Flasher.Unknown4 = 0;
Flasher.Unknown3 = 0;
MessageField = 0; MessageField = 0;
} }
void TLight::schedule_timeout(float time) void TLight::schedule_timeout(float time)
{ {
Flasher.TimerDelay[0] = FlasherDelay[0]; FlashDelay[0] = SourceDelay[0];
Flasher.TimerDelay[1] = FlasherDelay[1]; FlashDelay[1] = SourceDelay[1];
if (Timer1) if (TimeoutTimer)
timer::kill(Timer1); timer::kill(TimeoutTimer);
Timer1 = 0; TimeoutTimer = 0;
if (time > 0.0f) if (time > 0.0f)
Timer1 = timer::set(time, this, TimerExpired); TimeoutTimer = timer::set(time, this, TimerExpired);
} }
void TLight::TimerExpired(int timerId, void* caller) void TLight::TimerExpired(int timerId, void* caller)
{ {
auto light = static_cast<TLight*>(caller); auto light = static_cast<TLight*>(caller);
if (light->FlasherActive) if (light->FlasherOnFlag)
flasher_stop(&light->Flasher, -1); light->flasher_stop(-1);
render::sprite_set_bitmap(light->RenderSprite, light->Flasher.BmpArr[light->BmpIndex1]); light->SetSpriteBmp(light->BmpArr[light->LightOnFlag]);
light->FlasherFlag1 = 0; light->ToggledOffFlag = false;
light->FlasherFlag2 = 0; light->ToggledOnFlag = false;
light->FlasherActive = 0; light->FlasherOnFlag = false;
light->Timer1 = 0; light->TimeoutTimer = 0;
if (light->TurnOffAfterFlashingFg != 0) if (light->TurnOffAfterFlashingFg)
{ {
light->TurnOffAfterFlashingFg = 0; light->TurnOffAfterFlashingFg = false;
light->Message(20, 0.0); light->Message(MessageCode::TLightResetAndTurnOff, 0.0);
} }
if (light->Control) if (light->Control)
control::handler(60, light); control::handler(MessageCode::ControlTimerExpired, light);
} }
void TLight::flasher_stop(flasher_type* flash, int bmpIndex) void TLight::flasher_stop(int bmpIndex)
{ {
if (flash->Timer) if (FlashTimer)
timer::kill(flash->Timer); timer::kill(FlashTimer);
flash->Timer = 0; FlashTimer = 0;
if (bmpIndex >= 0) if (bmpIndex >= 0)
{ {
flash->BmpIndex = bmpIndex; FlashLightOnFlag = bmpIndex;
render::sprite_set_bitmap(flash->Sprite, flash->BmpArr[bmpIndex]); SetSpriteBmp(BmpArr[FlashLightOnFlag]);
} }
} }
void TLight::flasher_start(flasher_type* flash, int bmpIndex) void TLight::flasher_start(bool bmpIndex)
{ {
flash->BmpIndex = bmpIndex; FlashLightOnFlag = bmpIndex;
flasher_callback(0, flash); flasher_callback(0, this);
}
void TLight::SetSpriteBmp(int index)
{
PreviousBitmap = index;
if (!TemporaryOverrideFlag)
SpriteSet(index);
} }
void TLight::flasher_callback(int timerId, void* caller) void TLight::flasher_callback(int timerId, void* caller)
{ {
auto flash = static_cast<flasher_type*>(caller); auto light = static_cast<TLight*>(caller);
auto index = 1 - flash->BmpIndex; light->FlashLightOnFlag ^= true;
flash->BmpIndex = index; light->SetSpriteBmp(light->BmpArr[light->FlashLightOnFlag]);
render::sprite_set_bitmap(flash->Sprite, flash->BmpArr[index]); light->FlashTimer = timer::set(light->FlashDelay[light->FlashLightOnFlag], light, flasher_callback);
flash->Timer = timer::set(flash->TimerDelay[flash->BmpIndex], flash, 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;
} }

View File

@ -1,28 +1,12 @@
#pragma once #pragma once
#include "TPinballComponent.h" #include "TPinballComponent.h"
struct gdrv_bitmap8;
struct flasher_type
{
render_sprite_type_struct* Sprite;
gdrv_bitmap8* BmpArr[2];
int Unknown3;
int Unknown4;
float TimerDelay[2];
int Timer;
int BmpIndex;
};
struct TLight_player_backup struct TLight_player_backup
{ {
int MessageField; int MessageField;
int BmpIndex1; bool LightOnFlag;
int FlasherActive; int LightOnBmpIndex;
int Unknown3; bool FlasherOnFlag;
int Unknown4;
int BmpIndex2;
}; };
@ -31,25 +15,32 @@ class TLight :
{ {
public: public:
TLight(TPinballTable* table, int groupIndex); TLight(TPinballTable* table, int groupIndex);
int Message(int code, float value) override; int Message(MessageCode code, float value) override;
void Reset(); void Reset();
void schedule_timeout(float time); 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 TimerExpired(int timerId, void* caller);
static void flasher_stop(flasher_type* flash, int bmpIndex);
static void flasher_start(struct flasher_type* flash, int bmpIndex);
static void flasher_callback(int timerId, void* caller); static void flasher_callback(int timerId, void* caller);
static void UndoTmpOverride(int timerId, void* caller);
flasher_type Flasher{}; int BmpArr[2]{-1};
int BmpIndex1{}; float FlashDelay[2]{};
int FlasherActive; int FlashTimer;
int FlasherFlag1{}; bool FlashLightOnFlag{};
int FlasherFlag2{}; bool LightOnFlag{};
int TurnOffAfterFlashingFg{}; bool FlasherOnFlag;
int BmpIndex2{}; bool ToggledOffFlag{};
float FlasherDelay[2]{}; bool ToggledOnFlag{};
int Timer1; bool TurnOffAfterFlashingFg{};
int Timer2; int LightOnBmpIndex{};
float Unknown20F{}; float SourceDelay[2]{};
int TimeoutTimer;
int UndoOverrideTimer;
bool TemporaryOverrideFlag{};
int PreviousBitmap = -1;
TLight_player_backup PlayerData[4]{}; TLight_player_backup PlayerData[4]{};
}; };

View File

@ -32,13 +32,13 @@ TLightBargraph::~TLightBargraph()
delete[] TimerTimeArray; delete[] TimerTimeArray;
} }
int TLightBargraph::Message(int code, float value) int TLightBargraph::Message(MessageCode code, float value)
{ {
switch (code) switch (code)
{ {
case 37: case MessageCode::TLightGroupGetOnCount:
return TimeIndex; return TimeIndex;
case 45: case MessageCode::TLightGroupToggleSplitIndex:
{ {
if (TimerBargraph) if (TimerBargraph)
{ {
@ -51,24 +51,24 @@ int TLightBargraph::Message(int code, float value)
timeIndex = maxCount - 1; timeIndex = maxCount - 1;
if (timeIndex >= 0) if (timeIndex >= 0)
{ {
TLightGroup::Message(45, static_cast<float>(timeIndex / 2)); TLightGroup::Message(MessageCode::TLightGroupToggleSplitIndex, static_cast<float>(timeIndex / 2));
if (!(timeIndex & 1)) if (!(timeIndex & 1))
TLightGroup::Message(46, 0.0); TLightGroup::Message(MessageCode::TLightGroupStartFlasher, 0.0);
if (TimerTimeArray) if (TimerTimeArray)
TimerBargraph = timer::set(TimerTimeArray[timeIndex], this, BargraphTimerExpired); TimerBargraph = timer::set(TimerTimeArray[timeIndex], this, BargraphTimerExpired);
TimeIndex = timeIndex; TimeIndex = timeIndex;
} }
else else
{ {
TLightGroup::Message(20, 0.0); TLightGroup::Message(MessageCode::TLightResetAndTurnOff, 0.0);
TimeIndex = 0; TimeIndex = 0;
} }
break; break;
} }
case 1011: case MessageCode::SetTiltLock:
Reset(); Reset();
break; break;
case 1020: case MessageCode::PlayerChanged:
if (TimerBargraph) if (TimerBargraph)
{ {
timer::kill(TimerBargraph); timer::kill(TimerBargraph);
@ -79,10 +79,10 @@ int TLightBargraph::Message(int code, float value)
TimeIndex = PlayerTimerIndexBackup[static_cast<int>(floor(value))]; TimeIndex = PlayerTimerIndexBackup[static_cast<int>(floor(value))];
if (TimeIndex) if (TimeIndex)
{ {
TLightBargraph::Message(45, static_cast<float>(TimeIndex)); TLightBargraph::Message(MessageCode::TLightGroupToggleSplitIndex, static_cast<float>(TimeIndex));
} }
break; break;
case 1024: case MessageCode::Reset:
{ {
Reset(); Reset();
int* playerPtr = PlayerTimerIndexBackup; int* playerPtr = PlayerTimerIndexBackup;
@ -92,7 +92,7 @@ int TLightBargraph::Message(int code, float value)
++playerPtr; ++playerPtr;
} }
TLightGroup::Message(1024, value); TLightGroup::Message(MessageCode::Reset, value);
break; break;
} }
default: default:
@ -119,12 +119,12 @@ void TLightBargraph::BargraphTimerExpired(int timerId, void* caller)
bar->TimerBargraph = 0; bar->TimerBargraph = 0;
if (bar->TimeIndex) if (bar->TimeIndex)
{ {
bar->Message(45, static_cast<float>(bar->TimeIndex - 1)); bar->Message(MessageCode::TLightGroupToggleSplitIndex, static_cast<float>(bar->TimeIndex - 1));
control::handler(60, bar); control::handler(MessageCode::ControlTimerExpired, bar);
} }
else else
{ {
bar->Message(20, 0.0); bar->Message(MessageCode::TLightResetAndTurnOff, 0.0);
control::handler(47, bar); control::handler(MessageCode::TLightGroupCountdownEnded, bar);
} }
} }

View File

@ -7,7 +7,7 @@ class TLightBargraph :
public: public:
TLightBargraph(TPinballTable* table, int groupIndex); TLightBargraph(TPinballTable* table, int groupIndex);
~TLightBargraph() override; ~TLightBargraph() override;
int Message(int code, float value) override; int Message(MessageCode code, float value) override;
void Reset() override; void Reset() override;
static void BargraphTimerExpired(int timerId, void* caller); static void BargraphTimerExpired(int timerId, void* caller);

View File

@ -28,15 +28,15 @@ TLightGroup::TLightGroup(TPinballTable* table, int groupIndex) : TPinballCompone
} }
} }
int TLightGroup::Message(int code, float value) int TLightGroup::Message(MessageCode code, float value)
{ {
auto const count = static_cast<int>(List.size()); auto const count = static_cast<int>(List.size());
switch (code) switch (code)
{ {
case 1011: case MessageCode::SetTiltLock:
case 1022: case MessageCode::GameOver:
break; break;
case 1020: case MessageCode::PlayerChanged:
{ {
auto playerPtr = &PlayerData[PinballTable->CurrentPlayer]; auto playerPtr = &PlayerData[PinballTable->CurrentPlayer];
playerPtr->MessageField = MessageField; playerPtr->MessageField = MessageField;
@ -53,7 +53,7 @@ int TLightGroup::Message(int code, float value)
TimerExpired(0, this); TimerExpired(0, this);
break; break;
} }
case 1024: case MessageCode::Reset:
Reset(); Reset();
for (auto index = 0; index < PinballTable->PlayerCount; index++) for (auto index = 0; index < PinballTable->PlayerCount; index++)
{ {
@ -63,99 +63,99 @@ int TLightGroup::Message(int code, float value)
playerPtr->Timer1Time = Timer1Time; playerPtr->Timer1Time = Timer1Time;
} }
break; break;
case 24: case MessageCode::TLightGroupStepBackward:
{ {
auto lastLight = List.at(count - 1); auto lastLight = List.at(count - 1);
if (lastLight->FlasherActive || lastLight->FlasherFlag2 || lastLight->FlasherFlag1) if (lastLight->FlasherOnFlag || lastLight->ToggledOnFlag || lastLight->ToggledOffFlag)
break; break;
if (MessageField2) if (MessageField2 != MessageCode::TLightGroupNull)
{ {
TLightGroup::Message(34, 0.0); TLightGroup::Message(MessageCode::TLightGroupReset, 0.0);
} }
AnimationFlag = 1; AnimationFlag = 1;
MessageField2 = code; MessageField2 = code;
auto lightMessageField = lastLight->MessageField; auto lastMessage = lastLight->MessageField;
auto bmpIndex1 = lastLight->BmpIndex1; auto lastStatus = lastLight->LightOnFlag;
for (auto index = count - 1; index > 0; --index) for (auto index = count - 1; index > 0; --index)
{ {
auto lightCur = List.at(index); auto lightCur = List.at(index);
auto lightPrev = List.at(index - 1); auto lightPrev = List.at(index - 1);
lightCur->Message(lightPrev->BmpIndex1 != 0, 0.0); lightCur->Message(lightPrev->LightOnFlag ? MessageCode::TLightTurnOn : MessageCode::TLightTurnOff, 0.0);
lightCur->MessageField = lightPrev->MessageField; lightCur->MessageField = lightPrev->MessageField;
} }
auto firstLight = List.at(0); auto firstLight = List.at(0);
firstLight->Message(bmpIndex1 != 0, 0.0); firstLight->Message(lastStatus ? MessageCode::TLightTurnOn : MessageCode::TLightTurnOff, 0.0);
firstLight->MessageField = lightMessageField; firstLight->MessageField = lastMessage;
reschedule_animation(value); reschedule_animation(value);
break; break;
} }
case 25: case MessageCode::TLightGroupStepForward:
{ {
auto lastLight = List.at(count - 1); auto lastLight = List.at(count - 1);
if (lastLight->FlasherActive || lastLight->FlasherFlag2 || lastLight->FlasherFlag1) if (lastLight->FlasherOnFlag || lastLight->ToggledOnFlag || lastLight->ToggledOffFlag)
break; break;
if (MessageField2) if (MessageField2 != MessageCode::TLightGroupNull)
{ {
TLightGroup::Message(34, 0.0); TLightGroup::Message(MessageCode::TLightGroupReset, 0.0);
} }
auto firstLight = List.at(0); auto firstLight = List.at(0);
AnimationFlag = 1; AnimationFlag = 1;
MessageField2 = code; MessageField2 = code;
auto lightMessageField = firstLight->MessageField; auto firstMessage = firstLight->MessageField;
auto bmpIndex1 = firstLight->BmpIndex1; auto firstStatus = firstLight->LightOnFlag;
for (auto index = 0; index < count - 1; index++) for (auto index = 0; index < count - 1; index++)
{ {
auto lightCur = List.at(index); auto lightCur = List.at(index);
auto lightNext = List.at(index + 1); auto lightNext = List.at(index + 1);
lightCur->Message(lightNext->BmpIndex1 != 0, 0.0); lightCur->Message(lightNext->LightOnFlag ? MessageCode::TLightTurnOn : MessageCode::TLightTurnOff, 0.0);
lightCur->MessageField = lightNext->MessageField; lightCur->MessageField = lightNext->MessageField;
} }
lastLight->Message(bmpIndex1 != 0, 0.0); lastLight->Message(firstStatus ? MessageCode::TLightTurnOn : MessageCode::TLightTurnOff, 0.0);
lastLight->MessageField = lightMessageField; lastLight->MessageField = firstMessage;
reschedule_animation(value); reschedule_animation(value);
break; break;
} }
case 26: case MessageCode::TLightGroupAnimationBackward:
{ {
if (AnimationFlag || !MessageField2) if (AnimationFlag || MessageField2 == MessageCode::TLightGroupNull)
start_animation(); start_animation();
MessageField2 = code; MessageField2 = code;
AnimationFlag = 0; AnimationFlag = 0;
auto lastLight = List.at(count - 1); auto lastLight = List.at(count - 1);
auto flasherFlag2 = lastLight->FlasherFlag2; auto lastStatus = lastLight->ToggledOnFlag;
for (auto i = count - 1; i > 0; --i) for (auto i = count - 1; i > 0; --i)
{ {
auto lightCur = List.at(i); auto lightCur = List.at(i);
auto lightPrev = List.at(i - 1); auto lightPrev = List.at(i - 1);
lightCur->Message((lightPrev->FlasherFlag2 != 0) + 8, 0.0); lightCur->Message(lightPrev->ToggledOnFlag ? MessageCode::TLightTurnOnTimed : MessageCode::TLightTurnOffTimed, 0.0);
} }
auto firstLight = List.at(0); auto firstLight = List.at(0);
firstLight->Message((flasherFlag2 != 0) + 8, 0); firstLight->Message(lastStatus ? MessageCode::TLightTurnOnTimed : MessageCode::TLightTurnOffTimed, 0);
reschedule_animation(value); reschedule_animation(value);
break; break;
} }
case 27: case MessageCode::TLightGroupAnimationForward:
{ {
if (AnimationFlag || !MessageField2) if (AnimationFlag || MessageField2 == MessageCode::TLightGroupNull)
start_animation(); start_animation();
MessageField2 = code; MessageField2 = code;
AnimationFlag = 0; AnimationFlag = 0;
auto firstLight = List.at(0); auto firstLight = List.at(0);
auto flasherFlag2 = firstLight->FlasherFlag2; auto firstStatus = firstLight->ToggledOnFlag;
for (auto i = 0; i < count - 1; i++) for (auto i = 0; i < count - 1; i++)
{ {
auto lightCur = List.at(i); auto lightCur = List.at(i);
auto lightNext = List.at(i + 1); auto lightNext = List.at(i + 1);
lightCur->Message((lightNext->FlasherFlag2 != 0) + 8, 0.0); lightCur->Message(lightNext->ToggledOnFlag ? MessageCode::TLightTurnOnTimed : MessageCode::TLightTurnOffTimed, 0.0);
} }
auto lastLight = List.at(count - 1); auto lastLight = List.at(count - 1);
lastLight->Message((flasherFlag2 != 0) + 8, 0); lastLight->Message(firstStatus ? MessageCode::TLightTurnOnTimed : MessageCode::TLightTurnOffTimed, 0);
reschedule_animation(value); reschedule_animation(value);
break; break;
} }
case 28: case MessageCode::TLightGroupLightShowAnimation:
{ {
if (AnimationFlag || !MessageField2) if (AnimationFlag || MessageField2 == MessageCode::TLightGroupNull)
start_animation(); start_animation();
MessageField2 = code; MessageField2 = code;
AnimationFlag = 0; AnimationFlag = 0;
@ -164,32 +164,32 @@ int TLightGroup::Message(int code, float value)
if (rand() % 100 > 70) if (rand() % 100 > 70)
{ {
auto randVal = RandFloat() * value * 3.0f + 0.1f; auto randVal = RandFloat() * value * 3.0f + 0.1f;
light->Message(9, randVal); light->Message(MessageCode::TLightTurnOnTimed, randVal);
} }
} }
reschedule_animation(value); reschedule_animation(value);
break; break;
} }
case 29: case MessageCode::TLightGroupGameOverAnimation:
{ {
if (AnimationFlag || !MessageField2) if (AnimationFlag || MessageField2 == MessageCode::TLightGroupNull)
start_animation(); start_animation();
MessageField2 = code; MessageField2 = code;
AnimationFlag = 0; AnimationFlag = 0;
for (auto light : List) for (auto light : List)
{ {
auto randVal = static_cast<float>(rand() % 100 > 70); auto randVal = static_cast<float>(rand() % 100 > 70);
light->Message(18, randVal); light->Message(MessageCode::TLightResetAndToggleValue, randVal);
} }
reschedule_animation(value); reschedule_animation(value);
break; break;
} }
case 30: case MessageCode::TLightGroupRandomAnimationSaturation:
{ {
auto noBmpInd1Count = 0; auto noBmpInd1Count = 0;
for (auto light : List) for (auto light : List)
{ {
if (!light->BmpIndex1) if (!light->LightOnFlag)
++noBmpInd1Count; ++noBmpInd1Count;
} }
if (!noBmpInd1Count) if (!noBmpInd1Count)
@ -199,23 +199,23 @@ int TLightGroup::Message(int code, float value)
for (auto it = List.rbegin(); it != List.rend(); ++it) for (auto it = List.rbegin(); it != List.rend(); ++it)
{ {
auto light = *it; auto light = *it;
if (!light->BmpIndex1 && randModCount-- == 0) if (!light->LightOnFlag && randModCount-- == 0)
{ {
light->Message(1, 0.0); light->Message(MessageCode::TLightTurnOn, 0.0);
break; break;
} }
} }
if (MessageField2) if (MessageField2 != MessageCode::TLightGroupNull)
start_animation(); start_animation();
break; break;
} }
case 31: case MessageCode::TLightGroupRandomAnimationDesaturation:
{ {
auto bmpInd1Count = 0; auto bmpInd1Count = 0;
for (auto light : List) for (auto light : List)
{ {
if (light->BmpIndex1) if (light->LightOnFlag)
++bmpInd1Count; ++bmpInd1Count;
} }
if (!bmpInd1Count) if (!bmpInd1Count)
@ -225,130 +225,131 @@ int TLightGroup::Message(int code, float value)
for (auto it = List.rbegin(); it != List.rend(); ++it) for (auto it = List.rbegin(); it != List.rend(); ++it)
{ {
auto light = *it; auto light = *it;
if (light->BmpIndex1 && randModCount-- == 0) if (light->LightOnFlag && randModCount-- == 0)
{ {
light->Message(0, 0.0); light->Message(MessageCode::TLightTurnOff, 0.0);
break; break;
} }
} }
if (MessageField2) if (MessageField2 != MessageCode::TLightGroupNull)
start_animation(); start_animation();
break; break;
} }
case 32: case MessageCode::TLightGroupOffsetAnimationForward:
{ {
auto index = next_light_up(); auto index = next_light_up();
if (index < 0) if (index < 0)
break; break;
List.at(index)->Message(1, 0.0); List.at(index)->Message(MessageCode::TLightTurnOn, 0.0);
if (MessageField2) if (MessageField2 != MessageCode::TLightGroupNull)
start_animation(); start_animation();
return 1; return 1;
} }
case 33: case MessageCode::TLightGroupOffsetAnimationBackward:
{ {
auto index = next_light_down(); auto index = next_light_down();
if (index < 0) if (index < 0)
break; break;
List.at(index)->Message(0, 0.0); List.at(index)->Message(MessageCode::TLightTurnOff, 0.0);
if (MessageField2) if (MessageField2 != MessageCode::TLightGroupNull)
start_animation(); start_animation();
return 1; return 1;
} }
case 34: case MessageCode::TLightGroupReset:
{ {
if (Timer) if (Timer)
timer::kill(Timer); timer::kill(Timer);
Timer = 0; Timer = 0;
if (MessageField2 == 26 || MessageField2 == 27 || MessageField2 == 28) if (MessageField2 == MessageCode::TLightGroupAnimationBackward ||
TLightGroup::Message(14, 0.0); MessageField2 == MessageCode::TLightGroupAnimationForward || MessageField2 == MessageCode::TLightGroupLightShowAnimation)
MessageField2 = 0; TLightGroup::Message(MessageCode::TLightResetTimed, 0.0);
MessageField2 = MessageCode::TLightGroupNull;
AnimationFlag = 0; AnimationFlag = 0;
break; break;
} }
case 35: case MessageCode::TLightGroupTurnOnAtIndex:
{ {
auto index = static_cast<int>(floor(value)); auto index = static_cast<int>(floor(value));
if (index >= count || index < 0) if (index >= count || index < 0)
break; break;
auto light = List.at(index); auto light = List.at(index);
light->Message(1, 0.0); light->Message(MessageCode::TLightTurnOn, 0.0);
if (MessageField2) if (MessageField2 != MessageCode::TLightGroupNull)
start_animation(); start_animation();
break; break;
} }
case 36: case MessageCode::TLightGroupTurnOffAtIndex:
{ {
auto index = static_cast<int>(floor(value)); auto index = static_cast<int>(floor(value));
if (index >= count || index < 0) if (index >= count || index < 0)
break; break;
auto light = List.at(index); auto light = List.at(index);
light->Message(0, 0.0); light->Message(MessageCode::TLightTurnOff, 0.0);
if (MessageField2) if (MessageField2 != MessageCode::TLightGroupNull)
start_animation(); start_animation();
break; break;
} }
case 37: case MessageCode::TLightGroupGetOnCount:
{ {
auto bmp1Count = 0; auto bmp1Count = 0;
for (auto light : List) for (auto light : List)
{ {
if (light->BmpIndex1) if (light->LightOnFlag)
++bmp1Count; ++bmp1Count;
} }
return bmp1Count; return bmp1Count;
} }
case 38: case MessageCode::TLightGroupGetLightCount:
return count; return count;
case 39: case MessageCode::TLightGroupGetMessage2:
return MessageField2; return static_cast<int>(MessageField2);
case 40: case MessageCode::TLightGroupGetAnimationFlag:
return AnimationFlag; return AnimationFlag;
case 41: case MessageCode::TLightGroupResetAndTurnOn:
{ {
auto index = next_light_up(); auto index = next_light_up();
if (index < 0) if (index < 0)
break; break;
if (MessageField2 || AnimationFlag) if (MessageField2 != MessageCode::TLightGroupNull || AnimationFlag)
TLightGroup::Message(34, 0.0); TLightGroup::Message(MessageCode::TLightGroupReset, 0.0);
List.at(index)->Message(15, value); List.at(index)->Message(MessageCode::TLightFlasherStartTimedThenStayOn, value);
return 1; return 1;
} }
case 42: case MessageCode::TLightGroupResetAndTurnOff:
{ {
auto index = next_light_down(); auto index = next_light_down();
if (index < 0) if (index < 0)
break; break;
if (MessageField2 || AnimationFlag) if (MessageField2 != MessageCode::TLightGroupNull || AnimationFlag)
TLightGroup::Message(34, 0.0); TLightGroup::Message(MessageCode::TLightGroupReset, 0.0);
List.at(index)->Message(16, value); List.at(index)->Message(MessageCode::TLightFlasherStartTimedThenStayOff, value);
return 1; return 1;
} }
case 43: case MessageCode::TLightGroupRestartNotifyTimer:
if (NotifyTimer) if (NotifyTimer)
timer::kill(NotifyTimer); timer::kill(NotifyTimer);
NotifyTimer = 0; NotifyTimer = 0;
if (value > 0.0f) if (value > 0.0f)
NotifyTimer = timer::set(value, this, NotifyTimerExpired); NotifyTimer = timer::set(value, this, NotifyTimerExpired);
break; break;
case 44: case MessageCode::TLightGroupFlashWhenOn:
{ {
for (auto it = List.rbegin(); it != List.rend(); ++it) for (auto it = List.rbegin(); it != List.rend(); ++it)
{ {
auto light = *it; auto light = *it;
if (light->BmpIndex1) if (light->LightOnFlag)
{ {
light->Message(0, 0.0); light->Message(MessageCode::TLightTurnOff, 0.0);
light->Message(16, value); light->Message(MessageCode::TLightFlasherStartTimedThenStayOff, value);
} }
} }
break; break;
} }
case 45: case MessageCode::TLightGroupToggleSplitIndex:
{ {
control::handler(code, this); control::handler(code, this);
auto index = static_cast<int>(floor(value)); auto index = static_cast<int>(floor(value));
@ -357,23 +358,23 @@ int TLightGroup::Message(int code, float value)
// Turn off lights (index, end] // Turn off lights (index, end]
for (auto i = count - 1; i > index; i--) for (auto i = count - 1; i > index; i--)
{ {
List.at(i)->Message(20, 0.0); List.at(i)->Message(MessageCode::TLightResetAndTurnOff, 0.0);
} }
// Turn on lights [begin, index] // Turn on lights [begin, index]
for (auto i = index; i >= 0; i--) for (auto i = index; i >= 0; i--)
{ {
List.at(i)->Message(19, 0.0); List.at(i)->Message(MessageCode::TLightResetAndTurnOn, 0.0);
} }
} }
break; break;
} }
case 46: case MessageCode::TLightGroupStartFlasher:
{ {
auto index = next_light_down(); auto index = next_light_down();
if (index >= 0) if (index >= 0)
{ {
List.at(index)->Message(4, 0.0); List.at(index)->Message(MessageCode::TLightFlasherStart, 0.0);
} }
break; break;
} }
@ -395,7 +396,7 @@ void TLightGroup::Reset()
if (NotifyTimer) if (NotifyTimer)
timer::kill(NotifyTimer); timer::kill(NotifyTimer);
NotifyTimer = 0; NotifyTimer = 0;
MessageField2 = 0; MessageField2 = MessageCode::TLightGroupNull;
AnimationFlag = 0; AnimationFlag = 0;
Timer1Time = Timer1TimeDefault; Timer1Time = Timer1TimeDefault;
} }
@ -407,7 +408,7 @@ void TLightGroup::reschedule_animation(float time)
Timer = 0; Timer = 0;
if (time == 0) if (time == 0)
{ {
MessageField2 = 0; MessageField2 = MessageCode::TLightGroupNull;
AnimationFlag = 0; AnimationFlag = 0;
return; return;
} }
@ -421,10 +422,10 @@ void TLightGroup::start_animation()
for (auto it = List.rbegin(); it != List.rend(); ++it) for (auto it = List.rbegin(); it != List.rend(); ++it)
{ {
auto light = *it; auto light = *it;
if (light->BmpIndex1) if (light->LightOnFlag)
light->Message(9, 0.0); light->Message(MessageCode::TLightTurnOnTimed, 0.0);
else else
light->Message(8, 0.0); light->Message(MessageCode::TLightTurnOffTimed, 0.0);
} }
} }
@ -432,7 +433,7 @@ int TLightGroup::next_light_up()
{ {
for (auto index = 0u; index < List.size(); ++index) for (auto index = 0u; index < List.size(); ++index)
{ {
if (!List[index]->BmpIndex1) if (!List[index]->LightOnFlag)
return static_cast<int>(index); return static_cast<int>(index);
} }
return -1; return -1;
@ -442,7 +443,7 @@ int TLightGroup::next_light_down()
{ {
for (auto index = static_cast<int>(List.size()) - 1; index >= 0; --index) for (auto index = static_cast<int>(List.size()) - 1; index >= 0; --index)
{ {
if (List.at(index)->BmpIndex1) if (List.at(index)->LightOnFlag)
return index; return index;
} }
return -1; return -1;
@ -459,5 +460,5 @@ void TLightGroup::NotifyTimerExpired(int timerId, void* caller)
{ {
auto group = static_cast<TLightGroup*>(caller); auto group = static_cast<TLightGroup*>(caller);
group->NotifyTimer = 0; group->NotifyTimer = 0;
control::handler(61, group); control::handler(MessageCode::ControlNotifyTimerExpired, group);
} }

View File

@ -7,7 +7,7 @@ class TLight;
struct TLightGroup_player_backup struct TLightGroup_player_backup
{ {
int MessageField; int MessageField;
int MessageField2; MessageCode MessageField2;
float Timer1Time; float Timer1Time;
int Unknown3; int Unknown3;
}; };
@ -19,7 +19,7 @@ class TLightGroup :
public: public:
TLightGroup(TPinballTable* table, int groupIndex); TLightGroup(TPinballTable* table, int groupIndex);
~TLightGroup() override = default; ~TLightGroup() override = default;
int Message(int code, float value) override; int Message(MessageCode code, float value) override;
virtual void Reset(); virtual void Reset();
void reschedule_animation(float time); void reschedule_animation(float time);
void start_animation(); void start_animation();
@ -32,7 +32,7 @@ public:
std::vector<TLight*> List; std::vector<TLight*> List;
float Timer1Time{}; float Timer1Time{};
float Timer1TimeDefault; float Timer1TimeDefault;
int MessageField2{}; MessageCode MessageField2{};
int AnimationFlag{}; int AnimationFlag{};
int NotifyTimer; int NotifyTimer;
int Timer; int Timer;

View File

@ -13,33 +13,31 @@ TLightRollover::TLightRollover(TPinballTable* table, int groupIndex) : TRollover
{ {
RolloverFlag = 0; RolloverFlag = 0;
Timer = 0; Timer = 0;
if (ListBitmap != nullptr) SpriteSet(-1);
render::sprite_set_bitmap(RenderSprite, nullptr);
build_walls(groupIndex); build_walls(groupIndex);
FloatArr = *loader::query_float_attribute(groupIndex, 0, 407); FloatArr = *loader::query_float_attribute(groupIndex, 0, 407);
} }
int TLightRollover::Message(int code, float value) int TLightRollover::Message(MessageCode code, float value)
{ {
if (code == 1024) if (code == MessageCode::Reset)
{ {
ActiveFlag = 1; ActiveFlag = 1;
RolloverFlag = 0; RolloverFlag = 0;
if (Timer) if (Timer)
timer::kill(Timer); timer::kill(Timer);
Timer = 0; Timer = 0;
if (ListBitmap) SpriteSet(-1);
render::sprite_set_bitmap(RenderSprite, nullptr);
} }
return 0; return 0;
} }
void TLightRollover::Collision(TBall* ball, vector_type* nextPosition, vector_type* direction, float coef, void TLightRollover::Collision(TBall* ball, vector2* nextPosition, vector2* direction, float distance,
TEdgeSegment* edge) TEdgeSegment* edge)
{ {
ball->Position.X = nextPosition->X; ball->Position.X = nextPosition->X;
ball->Position.Y = nextPosition->Y; ball->Position.Y = nextPosition->Y;
ball->RayMaxDistance -= coef; ball->RayMaxDistance -= distance;
ball->not_again(edge); ball->not_again(edge);
if (!PinballTable->TiltLockFlag) if (!PinballTable->TiltLockFlag)
{ {
@ -53,11 +51,10 @@ void TLightRollover::Collision(TBall* ball, vector_type* nextPosition, vector_ty
} }
else else
{ {
loader::play_sound(SoftHitSoundId); loader::play_sound(SoftHitSoundId, this, "TLightRollover");
control::handler(63, this); control::handler(MessageCode::ControlCollision, this);
RolloverFlag = RolloverFlag == 0; RolloverFlag = RolloverFlag == 0;
if (ListBitmap) SpriteSet(0);
render::sprite_set_bitmap(RenderSprite, ListBitmap->at(0));
} }
} }
} }
@ -65,6 +62,6 @@ void TLightRollover::Collision(TBall* ball, vector_type* nextPosition, vector_ty
void TLightRollover::delay_expired(int timerId, void* caller) void TLightRollover::delay_expired(int timerId, void* caller)
{ {
auto roll = static_cast<TLightRollover*>(caller); auto roll = static_cast<TLightRollover*>(caller);
render::sprite_set_bitmap(roll->RenderSprite, nullptr); roll->SpriteSet(-1);
roll->Timer = 0; roll->Timer = 0;
} }

View File

@ -7,8 +7,8 @@ class TLightRollover :
public: public:
TLightRollover(TPinballTable* table, int groupIndex); TLightRollover(TPinballTable* table, int groupIndex);
~TLightRollover() override = default; ~TLightRollover() override = default;
int Message(int code, float value) override; int Message(MessageCode code, float value) override;
void Collision(TBall* ball, vector_type* nextPosition, vector_type* direction, float coef, void Collision(TBall* ball, vector2* nextPosition, vector2* direction, float distance,
TEdgeSegment* edge) override; TEdgeSegment* edge) override;
static void delay_expired(int timerId, void* caller); static void delay_expired(int timerId, void* caller);

View File

@ -11,48 +11,56 @@ TLine::TLine(TCollisionComponent* collCmp, char* activeFlag, unsigned int collis
Y0 = y0; Y0 = y0;
X1 = x1; X1 = x1;
Y1 = y1; Y1 = y1;
maths::line_init(&Line, x0, y0, x1, y1); maths::line_init(Line, x0, y0, x1, y1);
} }
TLine::TLine(TCollisionComponent* collCmp, char* activeFlag, unsigned int collisionGroup, struct vector_type* start, TLine::TLine(TCollisionComponent* collCmp, char* activeFlag, unsigned int collisionGroup, const vector2& start,
struct vector_type* end) : TEdgeSegment(collCmp, activeFlag, collisionGroup) const vector2& end) : TEdgeSegment(collCmp, activeFlag, collisionGroup)
{ {
X0 = start->X; X0 = start.X;
Y0 = start->Y; Y0 = start.Y;
X1 = end->X; X1 = end.X;
Y1 = end->Y; Y1 = end.Y;
maths::line_init(&Line, X0, Y0, X1, Y1); maths::line_init(Line, X0, Y0, X1, Y1);
} }
void TLine::Offset(float offset) void TLine::Offset(float offset)
{ {
float offX = offset * Line.PerpendicularL.X; float offX = offset * Line.PerpendicularC.X;
float offY = offset * Line.PerpendicularL.Y; float offY = offset * Line.PerpendicularC.Y;
X0 += offX; X0 += offX;
Y0 += offY; Y0 += offY;
X1 += offX; X1 += offX;
Y1 += offY; Y1 += offY;
maths::line_init(&Line, X0, Y0, X1, Y1); maths::line_init(Line, X0, Y0, X1, Y1);
} }
float TLine::FindCollisionDistance(ray_type* ray) float TLine::FindCollisionDistance(const ray_type& ray)
{ {
return maths::ray_intersect_line(ray, &Line); return maths::ray_intersect_line(ray, Line);
} }
void TLine::EdgeCollision(TBall* ball, float coef) void TLine::EdgeCollision(TBall* ball, float distance)
{ {
CollisionComponent->Collision( CollisionComponent->Collision(
ball, ball,
&Line.RayIntersect, &Line.RayIntersect,
&Line.PerpendicularL, &Line.PerpendicularC,
coef, distance,
this); this);
} }
void TLine::place_in_grid() 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 edgeMan = TTableLayer::edge_manager;
auto xBox0 = edgeMan->box_x(X0); auto xBox0 = edgeMan->box_x(X0);
auto yBox0 = edgeMan->box_y(Y0); auto yBox0 = edgeMan->box_y(Y0);
@ -79,123 +87,45 @@ void TLine::place_in_grid()
{ {
if (dirY == 1) if (dirY == 1)
{ {
if (yBox0 <= yBox1) while (yBox0 <= yBox1)
{ edgeMan->add_edge_to_box(xBox0, yBox0++, this);
do
edgeMan->add_edge_to_box(xBox0, yBox0++, this);
while (yBox0 <= yBox1);
}
} }
else if (yBox0 >= yBox1) else
{ {
do while (yBox0 >= yBox1)
edgeMan->add_edge_to_box(xBox0, yBox0--, this); edgeMan->add_edge_to_box(xBox0, yBox0--, this);
while (yBox0 >= yBox1);
} }
} }
else else
{ {
float yCoord, xCoord;
int indexX1 = xBox0, indexY1 = yBox0;
int bresIndexX = xBox0 + 1, bresIndexY = yBox0 + 1;
auto bresDyDx = (Y0 - Y1) / (X0 - X1);
auto bresXAdd = Y0 - bresDyDx * X0;
edgeMan->add_edge_to_box(xBox0, yBox0, this); edgeMan->add_edge_to_box(xBox0, yBox0, this);
if (dirX == 1)
// 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;)
{ {
if (dirY == 1) // 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)
{ {
do // Advance indexY when discrete value is ahead/behind
{ // Advance indexX when discrete value matches linear value
yCoord = bresIndexY * edgeMan->AdvanceY + edgeMan->Y; indexY += dirY;
xCoord = (bresIndexX * edgeMan->AdvanceX + edgeMan->X) * bresDyDx + bresXAdd; if (ylinear == yDiscrete)
if (xCoord >= yCoord) indexX += dirX;
{
if (xCoord == yCoord)
{
++indexX1;
++bresIndexX;
}
++indexY1;
++bresIndexY;
}
else
{
++indexX1;
++bresIndexX;
}
edgeMan->add_edge_to_box(indexX1, indexY1, this);
}
while (indexX1 != xBox1 || indexY1 != yBox1);
} }
else else
{ {
do // Advance indexX otherwise
{ indexX += dirX;
yCoord = indexY1 * edgeMan->AdvanceY + edgeMan->Y;
xCoord = (bresIndexX * edgeMan->AdvanceX + edgeMan->X) * bresDyDx + bresXAdd;
if (xCoord <= yCoord)
{
if (xCoord == yCoord)
{
++indexX1;
++bresIndexX;
}
--indexY1;
}
else
{
++indexX1;
++bresIndexX;
}
edgeMan->add_edge_to_box(indexX1, indexY1, this);
}
while (indexX1 != xBox1 || indexY1 != yBox1);
}
}
else
{
if (dirY == 1)
{
do
{
xCoord = bresIndexY * edgeMan->AdvanceY + edgeMan->Y;
yCoord = (indexX1 * edgeMan->AdvanceX + edgeMan->X) * bresDyDx + bresXAdd;
if (yCoord >= xCoord)
{
if (yCoord == xCoord)
--indexX1;
++indexY1;
++bresIndexY;
}
else
{
--indexX1;
}
edgeMan->add_edge_to_box(indexX1, indexY1, this);
}
while (indexX1 != xBox1 || indexY1 != yBox1);
}
else
{
do
{
yCoord = indexY1 * edgeMan->AdvanceY + edgeMan->Y;
xCoord = (indexX1 * edgeMan->AdvanceX + edgeMan->X) * bresDyDx + bresXAdd;
if (xCoord <= yCoord)
{
if (xCoord == yCoord)
--indexX1;
--indexY1;
}
else
{
--indexX1;
}
edgeMan->add_edge_to_box(indexX1, indexY1, this);
}
while (indexX1 != xBox1 || indexY1 != yBox1);
} }
edgeMan->add_edge_to_box(indexX, indexY, this);
} }
} }
} }

View File

@ -9,9 +9,9 @@ public:
line_type Line{}; line_type Line{};
float X0, Y0, X1, Y1; 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, float x0, float y0, float x1, float y1);
TLine(TCollisionComponent* collCmp, char* activeFlag, unsigned int collisionGroup, vector_type* start, vector_type* end); TLine(TCollisionComponent* collCmp, char* activeFlag, unsigned int collisionGroup, const vector2& start, const vector2& end);
void Offset(float offset); void Offset(float offset);
float FindCollisionDistance(ray_type* ray) override; float FindCollisionDistance(const ray_type& ray) override;
void EdgeCollision(TBall* ball, float coef) override; void EdgeCollision(TBall* ball, float distance) override;
void place_in_grid() override; void place_in_grid(RectF* aabb) override;
}; };

View File

@ -11,7 +11,7 @@
TOneway::TOneway(TPinballTable* table, int groupIndex) : TCollisionComponent(table, groupIndex, false) TOneway::TOneway(TPinballTable* table, int groupIndex) : TCollisionComponent(table, groupIndex, false)
{ {
visualStruct visual{}; visualStruct visual{};
vector_type linePt1{}, linePt2{}; vector2 linePt1{}, linePt2{};
loader::query_visual(groupIndex, 0, &visual); loader::query_visual(groupIndex, 0, &visual);
if (visual.FloatArrCount == 2) if (visual.FloatArrCount == 2)
@ -21,38 +21,38 @@ TOneway::TOneway(TPinballTable* table, int groupIndex) : TCollisionComponent(tab
linePt1.X = visual.FloatArr[2]; linePt1.X = visual.FloatArr[2];
linePt1.Y = visual.FloatArr[3]; linePt1.Y = visual.FloatArr[3];
auto line = new TLine(this, &ActiveFlag, visual.CollisionGroup, &linePt2, &linePt1); auto line = new TLine(this, &ActiveFlag, visual.CollisionGroup, linePt2, linePt1);
if (line) if (line)
{ {
line->Offset(table->CollisionCompOffset); line->Offset(table->CollisionCompOffset);
line->place_in_grid(); line->place_in_grid(&AABB);
EdgeList.push_back(line); EdgeList.push_back(line);
} }
line = new TLine(this, &ActiveFlag, visual.CollisionGroup, &linePt1, &linePt2); line = new TLine(this, &ActiveFlag, visual.CollisionGroup, linePt1, linePt2);
Line = line; Line = line;
if (line) if (line)
{ {
line->Offset(-table->CollisionCompOffset * 0.8f); line->Offset(-table->CollisionCompOffset * 0.8f);
Line->place_in_grid(); Line->place_in_grid(&AABB);
EdgeList.push_back(Line); EdgeList.push_back(Line);
} }
} }
} }
void TOneway::Collision(TBall* ball, vector_type* nextPosition, vector_type* direction, float coef, TEdgeSegment* edge) void TOneway::Collision(TBall* ball, vector2* nextPosition, vector2* direction, float distance, TEdgeSegment* edge)
{ {
if (edge == Line) if (edge == Line)
{ {
ball->not_again(edge); ball->not_again(edge);
ball->Position.X = nextPosition->X; ball->Position.X = nextPosition->X;
ball->Position.Y = nextPosition->Y; ball->Position.Y = nextPosition->Y;
ball->RayMaxDistance -= coef; ball->RayMaxDistance -= distance;
if (!PinballTable->TiltLockFlag) if (!PinballTable->TiltLockFlag)
{ {
if (HardHitSoundId) if (HardHitSoundId)
loader::play_sound(HardHitSoundId); loader::play_sound(HardHitSoundId, ball, "TOneway1");
control::handler(63, this); control::handler(MessageCode::ControlCollision, this);
} }
} }
else if (PinballTable->TiltLockFlag) else if (PinballTable->TiltLockFlag)
@ -69,17 +69,6 @@ void TOneway::Collision(TBall* ball, vector_type* nextPosition, vector_type* dir
Boost) > 0.2f) Boost) > 0.2f)
{ {
if (SoftHitSoundId) if (SoftHitSoundId)
loader::play_sound(SoftHitSoundId); loader::play_sound(SoftHitSoundId, ball, "TOneway2");
} }
} }
void TOneway::put_scoring(int index, int score)
{
if (index < 6)
Scores[index] = score;
}
int TOneway::get_scoring(int index)
{
return index < 6 ? Scores[index] : 0;
}

View File

@ -8,11 +8,8 @@ class TOneway : public TCollisionComponent
public: public:
TOneway(TPinballTable* table, int groupIndex); TOneway(TPinballTable* table, int groupIndex);
~TOneway() override = default; ~TOneway() override = default;
void Collision(TBall* ball, vector_type* nextPosition, vector_type* direction, float coef, void Collision(TBall* ball, vector2* nextPosition, vector2* direction, float distance,
TEdgeSegment* edge) override; TEdgeSegment* edge) override;
void put_scoring(int index, int score) override;
int get_scoring(int index) override;
TLine* Line; TLine* Line;
int Scores[6]{};
}; };

View File

@ -1,8 +1,12 @@
#include "pch.h" #include "pch.h"
#include "TPinballComponent.h" #include "TPinballComponent.h"
#include "control.h"
#include "loader.h" #include "loader.h"
#include "proj.h"
#include "render.h" #include "render.h"
#include "TPinballTable.h" #include "TPinballTable.h"
#include "TTableLayer.h"
TPinballComponent::TPinballComponent(TPinballTable* table, int groupIndex, bool loadVisuals) TPinballComponent::TPinballComponent(TPinballTable* table, int groupIndex, bool loadVisuals)
{ {
@ -14,64 +18,69 @@ TPinballComponent::TPinballComponent(TPinballTable* table, int groupIndex, bool
PinballTable = table; PinballTable = table;
RenderSprite = nullptr; RenderSprite = nullptr;
ListBitmap = nullptr; ListBitmap = nullptr;
ListZMap = nullptr;
GroupName = nullptr; GroupName = nullptr;
Control = nullptr; Control = nullptr;
VisualPosNormX= -1.0f;
VisualPosNormY = -1.0f;
GroupIndex = groupIndex;
if (table) if (table)
table->ComponentList.push_back(this); table->ComponentList.push_back(this);
if (groupIndex >= 0) if (groupIndex >= 0)
GroupName = loader::query_name(groupIndex); GroupName = loader::query_name(groupIndex);
if (loadVisuals && groupIndex >= 0) if (loadVisuals && groupIndex >= 0)
{ {
int visualCount = loader::query_visual_states(groupIndex); int visualCount = loader::query_visual_states(groupIndex);
for (int index = 0; index < visualCount; ++index) for (int index = 0; index < visualCount; ++index)
{ {
loader::query_visual(groupIndex, index, &visual); loader::query_visual(groupIndex, index, &visual);
if (visual.Bitmap) if (visual.Bitmap.Bmp)
{ {
assertm(visual.Bitmap.ZMap, "Bitmap/zMap pairing is mandatory");
if (!ListBitmap) if (!ListBitmap)
ListBitmap = new std::vector<gdrv_bitmap8*>(); ListBitmap = new std::vector<SpriteData>();
if (ListBitmap) ListBitmap->push_back(visual.Bitmap);
ListBitmap->push_back(visual.Bitmap);
}
if (visual.ZMap)
{
if (!ListZMap)
ListZMap = new std::vector<zmap_header_type*>();
if (ListZMap)
ListZMap->push_back(visual.ZMap);
} }
} }
auto zMap = ListZMap ? ListZMap->at(0) : nullptr;
if (ListBitmap) if (ListBitmap)
{ {
rectangle_type bmp1Rect{}, tmpRect{}; rectangle_type bmp1Rect{}, tmpRect{};
auto rootBmp = ListBitmap->at(0); const auto rootSprite = ListBitmap->at(0);
const auto rootBmp = rootSprite.Bmp;
bmp1Rect.XPosition = rootBmp->XPosition - table->XOffset; bmp1Rect.XPosition = rootBmp->XPosition - table->XOffset;
bmp1Rect.YPosition = rootBmp->YPosition - table->YOffset; bmp1Rect.YPosition = rootBmp->YPosition - table->YOffset;
bmp1Rect.Width = rootBmp->Width; bmp1Rect.Width = rootBmp->Width;
bmp1Rect.Height = rootBmp->Height; bmp1Rect.Height = rootBmp->Height;
for (auto index = 1u; index < ListBitmap->size(); index++) for (auto index = 1u; index < ListBitmap->size(); index++)
{ {
auto bmp = ListBitmap->at(index); auto bmp = ListBitmap->at(index).Bmp;
tmpRect.XPosition = bmp->XPosition - table->XOffset; tmpRect.XPosition = bmp->XPosition - table->XOffset;
tmpRect.YPosition = bmp->YPosition - table->YOffset; tmpRect.YPosition = bmp->YPosition - table->YOffset;
tmpRect.Width = bmp->Width; tmpRect.Width = bmp->Width;
tmpRect.Height = bmp->Height; tmpRect.Height = bmp->Height;
maths::enclosing_box(&bmp1Rect, &tmpRect, &bmp1Rect); maths::enclosing_box(bmp1Rect, tmpRect, bmp1Rect);
} }
RenderSprite = render::create_sprite( RenderSprite = new render_sprite(
visualCount > 0 ? VisualTypes::Sprite : VisualTypes::None, VisualTypes::Sprite,
rootBmp, rootBmp,
zMap, rootSprite.ZMap,
rootBmp->XPosition - table->XOffset, rootBmp->XPosition - table->XOffset,
rootBmp->YPosition - table->YOffset, rootBmp->YPosition - table->YOffset,
&bmp1Rect); &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;
} }
} }
GroupIndex = groupIndex;
} }
@ -87,14 +96,13 @@ TPinballComponent::~TPinballComponent()
} }
delete ListBitmap; delete ListBitmap;
delete ListZMap;
} }
int TPinballComponent::Message(int code, float value) int TPinballComponent::Message(MessageCode code, float value)
{ {
MessageField = code; MessageField = static_cast<int>(code);
if (code == 1024) if (code == MessageCode::Reset)
MessageField = 0; MessageField = 0;
return 0; return 0;
} }
@ -103,11 +111,55 @@ void TPinballComponent::port_draw()
{ {
} }
void TPinballComponent::put_scoring(int index, int score) int TPinballComponent::get_scoring(unsigned int index) const
{ {
return Control == nullptr || index >= Control->ScoreCount ? 0 : Control->Scores[index];
} }
int TPinballComponent::get_scoring(int index) vector2 TPinballComponent::get_coordinates()
{ {
return 0; 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);
}
} }

View File

@ -1,17 +1,134 @@
#pragma once #pragma once
struct SpriteData;
struct vector2i;
struct zmap_header_type; struct zmap_header_type;
struct gdrv_bitmap8; struct gdrv_bitmap8;
struct render_sprite_type_struct; struct render_sprite;
struct component_control; struct component_control;
struct vector2;
class TPinballTable; class TPinballTable;
enum class message_code
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, Reset = 1024,
LightActiveCount = 37,
LightTotalCount = 38,
LightSetMessageField = 23,
}; };
class TPinballComponent class TPinballComponent
@ -19,10 +136,12 @@ class TPinballComponent
public: public:
TPinballComponent(TPinballTable* table, int groupIndex, bool loadVisuals); TPinballComponent(TPinballTable* table, int groupIndex, bool loadVisuals);
virtual ~TPinballComponent(); virtual ~TPinballComponent();
virtual int Message(int code, float value); virtual int Message(MessageCode code, float value);
virtual void port_draw(); virtual void port_draw();
virtual void put_scoring(int index, int score); int get_scoring(unsigned int index) const;
virtual int get_scoring(int index); virtual vector2 get_coordinates();
void SpriteSet(int index) const;
void SpriteSetBall(int index, vector2i pos, float depth) const;
char UnusedBaseFlag; char UnusedBaseFlag;
char ActiveFlag; char ActiveFlag;
@ -30,8 +149,10 @@ public:
char* GroupName; char* GroupName;
component_control* Control; component_control* Control;
int GroupIndex; int GroupIndex;
render_sprite_type_struct* RenderSprite; render_sprite* RenderSprite;
TPinballTable* PinballTable; TPinballTable* PinballTable;
std::vector<gdrv_bitmap8*>* ListBitmap; std::vector<SpriteData>* ListBitmap;
std::vector<zmap_header_type*>* ListZMap; private:
float VisualPosNormX;
float VisualPosNormY;
}; };

View File

@ -4,8 +4,8 @@
#include "control.h" #include "control.h"
#include "loader.h" #include "loader.h"
#include "midi.h"
#include "pb.h" #include "pb.h"
#include "pinball.h"
#include "render.h" #include "render.h"
#include "TBall.h" #include "TBall.h"
#include "TBlocker.h" #include "TBlocker.h"
@ -37,6 +37,7 @@
#include "TPlunger.h" #include "TPlunger.h"
#include "TFlipper.h" #include "TFlipper.h"
#include "TDrain.h" #include "TDrain.h"
#include "translations.h"
int TPinballTable::score_multipliers[5] = {1, 2, 3, 5, 10}; int TPinballTable::score_multipliers[5] = {1, 2, 3, 5, 10};
@ -48,20 +49,19 @@ TPinballTable::TPinballTable(): TPinballComponent(nullptr, -1, false)
CurScoreStruct = nullptr; CurScoreStruct = nullptr;
ScoreBallcount = nullptr; ScoreBallcount = nullptr;
ScorePlayerNumber1 = nullptr; ScorePlayerNumber1 = nullptr;
BallInSink = 0; BallInDrainFlag = 0;
ActiveFlag = 1; ActiveFlag = 1;
TiltLockFlag = 0; TiltLockFlag = 0;
EndGameTimeoutTimer = 0; EndGameTimeoutTimer = 0;
LightShowTimer = 0; LightShowTimer = 0;
ReplayTimer = 0; ReplayTimer = 0;
TiltTimeoutTimer = 0; TiltTimeoutTimer = 0;
MultiballFlag = 0; MultiballFlag = false;
PlayerCount = 0; PlayerCount = 0;
auto ballObj = new TBall(this); auto ball = AddBall({0.0f, 0.0f});
BallList.push_back(ballObj); ball->Disable();
if (ballObj)
ballObj->ActiveFlag = 0;
new TTableLayer(this); new TTableLayer(this);
LightGroup = new TLightGroup(this, 0); LightGroup = new TLightGroup(this, 0);
@ -185,8 +185,8 @@ TPinballTable::TPinballTable(): TPinballComponent(nullptr, -1, false)
} }
render::build_occlude_list(); render::build_occlude_list();
pinball::InfoTextBox = dynamic_cast<TTextBox*>(find_component("info_text_box")); pb::InfoTextBox = dynamic_cast<TTextBox*>(find_component("info_text_box"));
pinball::MissTextBox = dynamic_cast<TTextBox*>(find_component("mission_text_box")); pb::MissTextBox = dynamic_cast<TTextBox*>(find_component("mission_text_box"));
control::make_links(this); control::make_links(this);
} }
@ -214,6 +214,7 @@ TPinballTable::~TPinballTable()
delete ComponentList[0]; delete ComponentList[0];
} }
control::ClearLinks(); control::ClearLinks();
pb::InfoTextBox = pb::MissTextBox = nullptr;
} }
TPinballComponent* TPinballTable::find_component(LPCSTR componentName) TPinballComponent* TPinballTable::find_component(LPCSTR componentName)
@ -227,7 +228,7 @@ TPinballComponent* TPinballTable::find_component(LPCSTR componentName)
} }
} }
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_WARNING, "Table cant find:", componentName, nullptr); pb::ShowMessageBox(SDL_MESSAGEBOX_WARNING, "Table cant find:", componentName);
return nullptr; return nullptr;
} }
@ -241,23 +242,24 @@ TPinballComponent* TPinballTable::find_component(int groupIndex)
} }
snprintf(Buffer, sizeof Buffer, "%d", groupIndex); snprintf(Buffer, sizeof Buffer, "%d", groupIndex);
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_WARNING, "Table cant find (lh):", Buffer, nullptr); pb::ShowMessageBox(SDL_MESSAGEBOX_WARNING, "Table cant find (lh):", Buffer);
return nullptr; return nullptr;
} }
int TPinballTable::AddScore(int score) int TPinballTable::AddScore(int score)
{ {
if (ScoreSpecial3Flag) if (JackpotScoreFlag)
{ {
ScoreSpecial3 += score; JackpotScore += score;
if (ScoreSpecial3 > 5000000) const auto jackpotLimit = !pb::FullTiltMode ? 5000000 : 10000000;
ScoreSpecial3 = 5000000; if (JackpotScore > jackpotLimit)
JackpotScore = jackpotLimit;
} }
if (ScoreSpecial2Flag) if (BonusScoreFlag)
{ {
ScoreSpecial2 += score; BonusScore += score;
if (ScoreSpecial2 > 5000000) if (BonusScore > 5000000)
ScoreSpecial2 = 5000000; BonusScore = 5000000;
} }
int addedScore = ScoreAdded + score * score_multipliers[ScoreMultiplier]; int addedScore = ScoreAdded + score * score_multipliers[ScoreMultiplier];
CurScore += addedScore; CurScore += addedScore;
@ -286,21 +288,21 @@ void TPinballTable::ChangeBallCount(int count)
void TPinballTable::tilt(float time) void TPinballTable::tilt(float time)
{ {
if (!TiltLockFlag && !BallInSink) if (!TiltLockFlag && !BallInDrainFlag)
{ {
pinball::InfoTextBox->Clear(); pb::InfoTextBox->Clear();
pinball::MissTextBox->Clear(); pb::MissTextBox->Clear();
pinball::InfoTextBox->Display(pinball::get_rc_string(35, 0), -1.0); pb::InfoTextBox->Display(pb::get_rc_string(Msg::STRING136), -1.0);
loader::play_sound(SoundIndex3); loader::play_sound(SoundIndex3, nullptr, "TPinballTable1");
TiltTimeoutTimer = timer::set(30.0, this, tilt_timeout); TiltTimeoutTimer = timer::set(30.0, this, tilt_timeout);
for (auto component : ComponentList) for (auto component : ComponentList)
{ {
component->Message(1011, time); component->Message(MessageCode::SetTiltLock, time);
} }
LightGroup->Message(8, 0); LightGroup->Message(MessageCode::TLightTurnOffTimed, 0);
TiltLockFlag = 1; TiltLockFlag = 1;
control::table_control_handler(1011); control::table_control_handler(MessageCode::SetTiltLock);
} }
} }
@ -313,49 +315,50 @@ void TPinballTable::port_draw()
} }
} }
int TPinballTable::Message(int code, float value) int TPinballTable::Message(MessageCode code, float value)
{ {
LPSTR rc_text; const char* rc_text;
switch (code) switch (code)
{ {
case 1000: case MessageCode::LeftFlipperInputPressed:
if (!TiltLockFlag) if (!TiltLockFlag)
{ {
FlipperL->Message(1, value); FlipperL->Message(MessageCode::TFlipperExtend, value);
} }
break; break;
case 1001: case MessageCode::LeftFlipperInputReleased:
if (!TiltLockFlag) if (!TiltLockFlag)
{ {
FlipperL->Message(2, value); FlipperL->Message(MessageCode::TFlipperRetract, value);
} }
break; break;
case 1002: case MessageCode::RightFlipperInputPressed:
if (!TiltLockFlag) if (!TiltLockFlag)
{ {
FlipperR->Message(1, value); FlipperR->Message(MessageCode::TFlipperExtend, value);
} }
break; break;
case 1003: case MessageCode::RightFlipperInputReleased:
if (!TiltLockFlag) if (!TiltLockFlag)
{ {
FlipperR->Message(2, value); FlipperR->Message(MessageCode::TFlipperRetract, value);
} }
break; break;
case 1004: case MessageCode::PlungerInputPressed:
case 1005: case MessageCode::PlungerInputReleased:
Plunger->Message(code, value); Plunger->Message(code, value);
break; break;
case 1008: case MessageCode::Pause:
case 1009: case MessageCode::Resume:
case 1010: case MessageCode::LooseFocus:
for (auto component : ComponentList) for (auto component : ComponentList)
{ {
component->Message(code, value); component->Message(code, value);
} }
break; break;
case 1012: case MessageCode::ClearTiltLock:
LightGroup->Message(14, 0.0); LightGroup->Message(MessageCode::TLightResetTimed, 0.0);
if (TiltLockFlag) if (TiltLockFlag)
{ {
TiltLockFlag = 0; TiltLockFlag = 0;
@ -364,35 +367,37 @@ int TPinballTable::Message(int code, float value)
TiltTimeoutTimer = 0; TiltTimeoutTimer = 0;
} }
break; break;
case 1013: case MessageCode::StartGamePlayer1:
LightGroup->Message(34, 0.0); LightGroup->Message(MessageCode::TLightGroupReset, 0.0);
LightGroup->Message(20, 0.0); LightGroup->Message(MessageCode::TLightResetAndTurnOff, 0.0);
Plunger->Message(1016, 0.0); Plunger->Message(MessageCode::PlungerStartFeedTimer, 0.0);
if (Demo && Demo->ActiveFlag) if (Demo && Demo->ActiveFlag)
rc_text = pinball::get_rc_string(30, 0); rc_text = pb::get_rc_string(Msg::STRING131);
else else
rc_text = pinball::get_rc_string(26, 0); rc_text = pb::get_rc_string(Msg::STRING127);
pinball::InfoTextBox->Display(rc_text, -1.0); pb::InfoTextBox->Display(rc_text, -1.0);
if (Demo) if (Demo)
Demo->Message(1014, 0.0); Demo->Message(MessageCode::NewGame, 0.0);
break; break;
case 1014: case MessageCode::NewGame:
if (EndGameTimeoutTimer) if (EndGameTimeoutTimer)
{ {
timer::kill(EndGameTimeoutTimer); timer::kill(EndGameTimeoutTimer);
EndGame_timeout(0, this); EndGame_timeout(0, this);
pb::mode_change(1); pb::mode_change(GameModes::InGame);
} }
if (LightShowTimer) if (LightShowTimer)
{ {
timer::kill(LightShowTimer); timer::kill(LightShowTimer);
LightShowTimer = 0; LightShowTimer = 0;
Message(1013, 0.0); Message(MessageCode::StartGamePlayer1, 0.0);
} }
else else
{ {
// Some of the control cheats persist across games.
// Was this loose anti-cheat by design?
CheatsUsed = 0; CheatsUsed = 0;
Message(1024, 0.0); Message(MessageCode::Reset, 0.0);
auto ball = BallList[0]; auto ball = BallList[0];
ball->Position.Y = 0.0; ball->Position.Y = 0.0;
ball->Position.X = 0.0; ball->Position.X = 0.0;
@ -426,7 +431,7 @@ int TPinballTable::Message(int code, float value)
scorePtr->BallCount = MaxBallCount; scorePtr->BallCount = MaxBallCount;
scorePtr->ExtraBalls = ExtraBalls; scorePtr->ExtraBalls = ExtraBalls;
scorePtr->BallLockedCounter = BallLockedCounter; scorePtr->BallLockedCounter = BallLockedCounter;
scorePtr->Unknown2 = ScoreSpecial3; scorePtr->JackpotScore = JackpotScore;
} }
BallCount = MaxBallCount; BallCount = MaxBallCount;
@ -439,32 +444,42 @@ int TPinballTable::Message(int code, float value)
score::set(PlayerScores[scoreIndex].ScoreStruct, -1); score::set(PlayerScores[scoreIndex].ScoreStruct, -1);
} }
ScoreSpecial3Flag = 0; JackpotScoreFlag = false;
ScoreSpecial2Flag = 0; BonusScoreFlag = false;
UnknownP71 = 0; UnknownP71 = 0;
pinball::InfoTextBox->Clear(); pb::InfoTextBox->Clear();
pinball::MissTextBox->Clear(); pb::MissTextBox->Clear();
LightGroup->Message(28, 0.2f); LightGroup->Message(MessageCode::TLightGroupLightShowAnimation, 0.2f);
auto time = loader::play_sound(SoundIndex1); auto time = loader::play_sound(SoundIndex1, nullptr, "TPinballTable2");
if (time < 0)
time = 5.0f;
LightShowTimer = timer::set(time, this, LightShow_timeout); 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; break;
case 1018: case MessageCode::PlungerRelaunchBall:
if (ReplayTimer) if (ReplayTimer)
timer::kill(ReplayTimer); timer::kill(ReplayTimer);
ReplayTimer = timer::set(floor(value), this, replay_timer_callback); ReplayTimer = timer::set(floor(value), this, replay_timer_callback);
ReplayActiveFlag = 1; ReplayActiveFlag = 1;
break; break;
case 1021: case MessageCode::SwitchToNextPlayer:
{ {
if (PlayerCount <= 1) if (PlayerCount <= 1)
{ {
char* textboxText; const char* textboxText;
if (Demo->ActiveFlag) if (Demo->ActiveFlag)
textboxText = pinball::get_rc_string(30, 0); textboxText = pb::get_rc_string(Msg::STRING131);
else else
textboxText = pinball::get_rc_string(26, 0); textboxText = pb::get_rc_string(Msg::STRING127);
pinball::InfoTextBox->Display(textboxText, -1.0); pb::InfoTextBox->Display(textboxText, -1.0);
break; break;
} }
@ -478,14 +493,14 @@ int TPinballTable::Message(int code, float value)
PlayerScores[CurrentPlayer].BallCount = BallCount; PlayerScores[CurrentPlayer].BallCount = BallCount;
PlayerScores[CurrentPlayer].ExtraBalls = ExtraBalls; PlayerScores[CurrentPlayer].ExtraBalls = ExtraBalls;
PlayerScores[CurrentPlayer].BallLockedCounter = BallLockedCounter; PlayerScores[CurrentPlayer].BallLockedCounter = BallLockedCounter;
PlayerScores[CurrentPlayer].Unknown2 = ScoreSpecial3; PlayerScores[CurrentPlayer].JackpotScore = JackpotScore;
CurScore = nextScorePtr->Score; CurScore = nextScorePtr->Score;
CurScoreE9 = nextScorePtr->ScoreE9Part; CurScoreE9 = nextScorePtr->ScoreE9Part;
BallCount = nextScorePtr->BallCount; BallCount = nextScorePtr->BallCount;
ExtraBalls = nextScorePtr->ExtraBalls; ExtraBalls = nextScorePtr->ExtraBalls;
BallLockedCounter = nextScorePtr->BallLockedCounter; BallLockedCounter = nextScorePtr->BallLockedCounter;
ScoreSpecial3 = nextScorePtr->Unknown2; JackpotScore = nextScorePtr->JackpotScore;
CurScoreStruct = nextScorePtr->ScoreStruct; CurScoreStruct = nextScorePtr->ScoreStruct;
score::set(CurScoreStruct, CurScore); score::set(CurScoreStruct, CurScore);
@ -497,58 +512,58 @@ int TPinballTable::Message(int code, float value)
for (auto component : ComponentList) for (auto component : ComponentList)
{ {
component->Message(1020, static_cast<float>(nextPlayer)); component->Message(MessageCode::PlayerChanged, static_cast<float>(nextPlayer));
} }
char* textboxText = nullptr; const char* textboxText = nullptr;
switch (nextPlayer) switch (nextPlayer)
{ {
case 0: case 0:
if (Demo->ActiveFlag) if (Demo->ActiveFlag)
textboxText = pinball::get_rc_string(30, 0); textboxText = pb::get_rc_string(Msg::STRING131);
else else
textboxText = pinball::get_rc_string(26, 0); textboxText = pb::get_rc_string(Msg::STRING127);
break; break;
case 1: case 1:
if (Demo->ActiveFlag) if (Demo->ActiveFlag)
textboxText = pinball::get_rc_string(31, 0); textboxText = pb::get_rc_string(Msg::STRING132);
else else
textboxText = pinball::get_rc_string(27, 0); textboxText = pb::get_rc_string(Msg::STRING128);
break; break;
case 2: case 2:
if (Demo->ActiveFlag) if (Demo->ActiveFlag)
textboxText = pinball::get_rc_string(32, 0); textboxText = pb::get_rc_string(Msg::STRING133);
else else
textboxText = pinball::get_rc_string(28, 0); textboxText = pb::get_rc_string(Msg::STRING129);
break; break;
case 3: case 3:
if (Demo->ActiveFlag) if (Demo->ActiveFlag)
textboxText = pinball::get_rc_string(33, 0); textboxText = pb::get_rc_string(Msg::STRING134);
else else
textboxText = pinball::get_rc_string(29, 0); textboxText = pb::get_rc_string(Msg::STRING130);
break; break;
default: default:
break; break;
} }
if (textboxText != nullptr) if (textboxText != nullptr)
pinball::InfoTextBox->Display(textboxText, -1); pb::InfoTextBox->Display(textboxText, -1);
ScoreSpecial3Flag = 0; JackpotScoreFlag = false;
ScoreSpecial2Flag = 0; BonusScoreFlag = false;
UnknownP71 = 0; UnknownP71 = 0;
CurrentPlayer = nextPlayer; CurrentPlayer = nextPlayer;
} }
break; break;
case 1022: case MessageCode::GameOver:
loader::play_sound(SoundIndex2); loader::play_sound(SoundIndex2, nullptr, "TPinballTable3");
pinball::MissTextBox->Clear(); pb::MissTextBox->Clear();
pinball::InfoTextBox->Display(pinball::get_rc_string(34, 0), -1.0); pb::InfoTextBox->Display(pb::get_rc_string(Msg::STRING135), -1.0);
EndGameTimeoutTimer = timer::set(3.0, this, EndGame_timeout); EndGameTimeoutTimer = timer::set(3.0, this, EndGame_timeout);
break; break;
case 1024: case MessageCode::Reset:
for (auto component : ComponentList) for (auto component : ComponentList)
{ {
component->Message(1024, 0); component->Message(MessageCode::Reset, 0);
} }
if (ReplayTimer) if (ReplayTimer)
timer::kill(ReplayTimer); timer::kill(ReplayTimer);
@ -556,21 +571,21 @@ int TPinballTable::Message(int code, float value)
if (LightShowTimer) if (LightShowTimer)
{ {
timer::kill(LightShowTimer); timer::kill(LightShowTimer);
LightGroup->Message(34, 0.0); LightGroup->Message(MessageCode::TLightGroupReset, 0.0);
} }
LightShowTimer = 0; LightShowTimer = 0;
ScoreMultiplier = 0; ScoreMultiplier = 0;
ScoreAdded = 0; ScoreAdded = 0;
ScoreSpecial1 = 0; ReflexShotScore = 0;
ScoreSpecial2 = 10000; BonusScore = 10000;
ScoreSpecial2Flag = 0; BonusScoreFlag = false;
ScoreSpecial3 = 20000; JackpotScore = 20000;
ScoreSpecial3Flag = 0; JackpotScoreFlag = false;
UnknownP71 = 0; UnknownP71 = 0;
ExtraBalls = 0; ExtraBalls = 0;
UnknownP75 = 0; MultiballCount = 0;
BallLockedCounter = 0; BallLockedCounter = 0;
MultiballFlag = 0; MultiballFlag = false;
UnknownP78 = 0; UnknownP78 = 0;
ReplayActiveFlag = 0; ReplayActiveFlag = 0;
ReplayTimer = 0; ReplayTimer = 0;
@ -584,6 +599,75 @@ int TPinballTable::Message(int code, float value)
return 0; 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) void TPinballTable::EndGame_timeout(int timerId, void* caller)
{ {
auto table = static_cast<TPinballTable*>(caller); auto table = static_cast<TPinballTable*>(caller);
@ -592,19 +676,19 @@ void TPinballTable::EndGame_timeout(int timerId, void* caller)
for (auto component : table->ComponentList) for (auto component : table->ComponentList)
{ {
component->Message(1022, 0); component->Message(MessageCode::GameOver, 0);
} }
if (table->Demo) if (table->Demo)
table->Demo->Message(1022, 0.0); table->Demo->Message(MessageCode::GameOver, 0.0);
control::handler(67, pinball::MissTextBox); control::handler(MessageCode::ControlMissionStarted, pb::MissTextBox);
pinball::InfoTextBox->Display(pinball::get_rc_string(24, 0), -1.0); pb::InfoTextBox->Display(pb::get_rc_string(Msg::STRING125), -1.0);
} }
void TPinballTable::LightShow_timeout(int timerId, void* caller) void TPinballTable::LightShow_timeout(int timerId, void* caller)
{ {
auto table = static_cast<TPinballTable*>(caller); auto table = static_cast<TPinballTable*>(caller);
table->LightShowTimer = 0; table->LightShowTimer = 0;
table->Message(1013, 0.0); table->Message(MessageCode::StartGamePlayer1, 0.0);
} }
void TPinballTable::replay_timer_callback(int timerId, void* caller) void TPinballTable::replay_timer_callback(int timerId, void* caller)
@ -617,7 +701,7 @@ void TPinballTable::replay_timer_callback(int timerId, void* caller)
void TPinballTable::tilt_timeout(int timerId, void* caller) void TPinballTable::tilt_timeout(int timerId, void* caller)
{ {
auto table = static_cast<TPinballTable*>(caller); auto table = static_cast<TPinballTable*>(caller);
vector_type vec{}; vector2 vec{};
table->TiltTimeoutTimer = 0; table->TiltTimeoutTimer = 0;
if (table->TiltLockFlag) if (table->TiltLockFlag)

View File

@ -1,5 +1,6 @@
#pragma once #pragma once
#include "maths.h"
#include "TPinballComponent.h" #include "TPinballComponent.h"
class TBall; class TBall;
@ -15,7 +16,7 @@ struct score_struct_super
scoreStruct* ScoreStruct; scoreStruct* ScoreStruct;
int Score; int Score;
int ScoreE9Part; int ScoreE9Part;
int Unknown2; int JackpotScore;
int BallCount; int BallCount;
int ExtraBalls; int ExtraBalls;
int BallLockedCounter; int BallLockedCounter;
@ -33,7 +34,10 @@ public:
void ChangeBallCount(int count); void ChangeBallCount(int count);
void tilt(float time); void tilt(float time);
void port_draw() override; void port_draw() override;
int Message(int code, float value) 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 EndGame_timeout(int timerId, void* caller);
static void LightShow_timeout(int timerId, void* caller); static void LightShow_timeout(int timerId, void* caller);
@ -49,7 +53,7 @@ public:
int SoundIndex1{}; int SoundIndex1{};
int SoundIndex2{}; int SoundIndex2{};
int SoundIndex3{}; int SoundIndex3{};
int BallInSink; int BallInDrainFlag;
int CurScore{}; int CurScore{};
int CurScoreE9{}; int CurScoreE9{};
int LightShowTimer; int LightShowTimer;
@ -67,27 +71,27 @@ public:
int Height{}; int Height{};
std::vector<TPinballComponent*> ComponentList; std::vector<TPinballComponent*> ComponentList;
std::vector<TBall*> BallList; std::vector<TBall*> BallList;
std::vector<TFlipper*> FlipperList;
TLightGroup* LightGroup; TLightGroup* LightGroup;
float GravityDirVectMult{}; float GravityDirVectMult{};
float GravityAngleX{}; float GravityAngleX{};
float GravityAnglY{}; float GravityAnglY{};
float CollisionCompOffset{}; float CollisionCompOffset{};
float PlungerPositionX{}; vector2 PlungerPosition{};
float PlungerPositionY{};
int ScoreMultiplier{}; int ScoreMultiplier{};
int ScoreAdded{}; int ScoreAdded{};
int ScoreSpecial1{}; int ReflexShotScore{};
int ScoreSpecial2{}; int BonusScore{};
int ScoreSpecial2Flag{}; bool BonusScoreFlag{};
int ScoreSpecial3{}; int JackpotScore{};
int ScoreSpecial3Flag{}; bool JackpotScoreFlag{};
int UnknownP71{}; int UnknownP71{};
int BallCount{}; int BallCount{};
int MaxBallCount; int MaxBallCount;
int ExtraBalls{}; int ExtraBalls{};
int UnknownP75{}; int MultiballCount{};
int BallLockedCounter{}; int BallLockedCounter{};
int MultiballFlag; bool MultiballFlag;
int UnknownP78{}; int UnknownP78{};
int ReplayActiveFlag{}; int ReplayActiveFlag{};
int ReplayTimer; int ReplayTimer;

View File

@ -23,131 +23,175 @@ TPlunger::TPlunger(TPinballTable* table, int groupIndex) : TCollisionComponent(t
SoundIndexP2 = visual.SoundIndex3; SoundIndexP2 = visual.SoundIndex3;
HardHitSoundId = visual.Kicker.HardHitSoundId; HardHitSoundId = visual.Kicker.HardHitSoundId;
Threshold = 1000000000.0; Threshold = 1000000000.0;
MaxPullback = 100;
// 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; Elasticity = 0.5f;
Smoothness = 0.5f; Smoothness = 0.5f;
PullbackIncrement = static_cast<int>(100.0 / (ListBitmap->size() * 8.0));
Unknown4F = 0.025f; PullbackDelay = 0.025f;
float* floatArr = loader::query_float_attribute(groupIndex, 0, 601); float* floatArr = loader::query_float_attribute(groupIndex, 0, 601);
table->PlungerPositionX = floatArr[0]; table->PlungerPosition = {floatArr[0], floatArr[1]};
table->PlungerPositionY = floatArr[1];
} }
void TPlunger::Collision(TBall* ball, vector_type* nextPosition, vector_type* direction, float coef, TEdgeSegment* edge) void TPlunger::Collision(TBall* ball, vector2* nextPosition, vector2* direction, float distance, TEdgeSegment* edge)
{ {
if (PinballTable->TiltLockFlag) if (PinballTable->TiltLockFlag || SomeCounter > 0)
Message(1017, 0.0); {
coef = RandFloat() * Boost * 0.1f + Boost; // it is intended that the passed in coef is never used! auto boost = RandFloat() * MaxPullback * 0.1f + MaxPullback;
maths::basic_collision(ball, nextPosition, direction, Elasticity, Smoothness, Threshold, coef); 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(int code, float value) int TPlunger::Message(MessageCode code, float value)
{ {
switch (code) switch (code)
{ {
case 1004: case MessageCode::PlungerInputPressed:
if (!PullbackTimer_) if (!PullbackStartedFlag && (!pb::FullTiltMode || PinballTable->MultiballCount > 0 && !PinballTable->
TiltLockFlag))
{ {
PullbackStartedFlag = true;
Boost = 0.0; Boost = 0.0;
Threshold = 1000000000.0; Threshold = 1000000000.0;
loader::play_sound(HardHitSoundId); loader::play_sound(HardHitSoundId, this, "TPlunger1");
PullbackTimer(0, this); PullbackTimer(0, this);
} }
return 0;
case 1015:
{
auto ball = PinballTable->BallList.at(0);
ball->Message(1024, 0.0);
ball->Position.X = PinballTable->PlungerPositionX;
ball->Position.Y = PinballTable->PlungerPositionY;
ball->ActiveFlag = 1;
PinballTable->BallInSink = 0;
pb::tilt_no_more();
control::handler(code, this);
return 0;
}
case 1016:
if (BallFeedTimer_)
timer::kill(BallFeedTimer_);
BallFeedTimer_ = timer::set(0.95999998f, this, BallFeedTimer);
loader::play_sound(SoundIndexP1);
control::handler(code, this);
return 0;
case 1017:
Threshold = 0.0;
Boost = static_cast<float>(MaxPullback);
timer::set(0.2f, this, PlungerReleasedTimer);
break; break;
case 1005: case MessageCode::PlungerFeedBall:
case 1009:
case 1010:
case 1024:
{ {
if (code == 1024) if (PinballTable->BallCountInRect(PinballTable->PlungerPosition, PinballTable->CollisionCompOffset * 1.2f))
{ {
if (BallFeedTimer_) timer::set(1.0f, this, BallFeedTimer);
timer::kill(BallFeedTimer_);
BallFeedTimer_ = 0;
} }
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; Threshold = 0.0;
if (PullbackTimer_) if (PullbackTimer_)
timer::kill(PullbackTimer_); timer::kill(PullbackTimer_);
PullbackTimer_ = 0; PullbackTimer_ = 0;
if (code == 1005) loader::play_sound(SoundIndexP2, this, "TPlunger3");
loader::play_sound(SoundIndexP2); SpriteSet(0);
auto bmp = ListBitmap->at(0); timer::set(PullbackDelay, this, ReleasedTimer);
auto zMap = ListZMap->at(0);
render::sprite_set(
RenderSprite,
bmp,
zMap,
bmp->XPosition - PinballTable->XOffset,
bmp->YPosition - PinballTable->YOffset);
timer::set(Unknown4F, this, PlungerReleasedTimer);
break;
} }
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: default:
break; break;
} }
control::handler(code, this);
return 0; return 0;
} }
void TPlunger::BallFeedTimer(int timerId, void* caller) void TPlunger::BallFeedTimer(int timerId, void* caller)
{ {
auto plunger = static_cast<TPlunger*>(caller); auto plunger = static_cast<TPlunger*>(caller);
plunger->PullbackTimer_ = 0; plunger->Message(MessageCode::PlungerFeedBall, 0.0);
plunger->Message(1015, 0.0);
} }
void TPlunger::PullbackTimer(int timerId, void* caller) void TPlunger::PullbackTimer(int timerId, void* caller)
{ {
auto plunger = static_cast<TPlunger*>(caller); auto plunger = static_cast<TPlunger*>(caller);
plunger->Boost += static_cast<float>(plunger->PullbackIncrement); plunger->Boost += plunger->PullbackIncrement;
if (plunger->Boost <= static_cast<float>(plunger->MaxPullback)) if (plunger->Boost <= plunger->MaxPullback)
{ {
plunger->PullbackTimer_ = timer::set(plunger->Unknown4F, plunger, PullbackTimer); if (plunger->SomeCounter)
{
plunger->PullbackTimer_ = timer::set(plunger->PullbackDelay / 4.0f, plunger, PullbackTimer);
}
else
{
plunger->PullbackTimer_ = timer::set(plunger->PullbackDelay, plunger, PullbackTimer);
}
} }
else else
{ {
plunger->PullbackTimer_ = 0; plunger->PullbackTimer_ = 0;
plunger->Boost = static_cast<float>(plunger->MaxPullback); plunger->Boost = plunger->MaxPullback;
} }
int index = static_cast<int>(floor( int index = static_cast<int>(floor(
static_cast<float>(plunger->ListBitmap->size() - 1) * static_cast<float>(plunger->ListBitmap->size() - 1) *
(plunger->Boost / static_cast<float>(plunger->MaxPullback)))); (plunger->Boost / plunger->MaxPullback)));
auto bmp = plunger->ListBitmap->at(index); plunger->SpriteSet(index);
auto zMap = plunger->ListZMap->at(index);
render::sprite_set(
plunger->RenderSprite,
bmp,
zMap,
bmp->XPosition - plunger->PinballTable->XOffset,
bmp->YPosition - plunger->PinballTable->YOffset);
} }
void TPlunger::PlungerReleasedTimer(int timerId, void* caller) void TPlunger::ReleasedTimer(int timerId, void* caller)
{ {
auto plunger = static_cast<TPlunger*>(caller); auto plunger = static_cast<TPlunger*>(caller);
plunger->Threshold = 1000000000.0; plunger->Threshold = 1000000000.0;

View File

@ -7,19 +7,21 @@ class TPlunger :
public: public:
TPlunger(TPinballTable* table, int groupIndex); TPlunger(TPinballTable* table, int groupIndex);
~TPlunger() override = default; ~TPlunger() override = default;
void Collision(TBall* ball, vector_type* nextPosition, vector_type* direction, float coef, void Collision(TBall* ball, vector2* nextPosition, vector2* direction, float distance,
TEdgeSegment* edge) override; TEdgeSegment* edge) override;
int Message(int code, float value) override; int Message(MessageCode code, float value) override;
static void BallFeedTimer(int timerId, void* caller); static void BallFeedTimer(int timerId, void* caller);
static void PullbackTimer(int timerId, void* caller); static void PullbackTimer(int timerId, void* caller);
static void PlungerReleasedTimer(int timerId, void* caller); static void ReleasedTimer(int timerId, void* caller);
int PullbackTimer_; int PullbackTimer_;
int BallFeedTimer_; int BallFeedTimer_;
int MaxPullback; float MaxPullback;
int PullbackIncrement; float PullbackIncrement;
float Unknown4F; float PullbackDelay;
int SoundIndexP1; int SoundIndexP1;
int SoundIndexP2; int SoundIndexP2;
bool PullbackStartedFlag{};
int SomeCounter{};
}; };

View File

@ -10,38 +10,38 @@
TPopupTarget::TPopupTarget(TPinballTable* table, int groupIndex) : TCollisionComponent(table, groupIndex, true) TPopupTarget::TPopupTarget(TPinballTable* table, int groupIndex) : TCollisionComponent(table, groupIndex, true)
{ {
this->Timer = 0; Timer = 0;
this->TimerTime = *loader::query_float_attribute(groupIndex, 0, 407); TimerTime = *loader::query_float_attribute(groupIndex, 0, 407);
} }
int TPopupTarget::Message(int code, float value) int TPopupTarget::Message(MessageCode code, float value)
{ {
switch (code) switch (code)
{ {
case 49: case MessageCode::TPopupTargetDisable:
this->ActiveFlag = 0; ActiveFlag = 0;
render::sprite_set_bitmap(this->RenderSprite, nullptr); SpriteSet(-1);
break; break;
case 50: case MessageCode::TPopupTargetEnable:
this->Timer = timer::set(this->TimerTime, this, TimerExpired); Timer = timer::set(TimerTime, this, TimerExpired);
break; break;
case 1020: case MessageCode::PlayerChanged:
this->PlayerMessagefieldBackup[this->PinballTable->CurrentPlayer] = this->MessageField; PlayerMessagefieldBackup[PinballTable->CurrentPlayer] = MessageField;
this->MessageField = this->PlayerMessagefieldBackup[static_cast<int>(floor(value))]; MessageField = PlayerMessagefieldBackup[static_cast<int>(floor(value))];
TPopupTarget::Message(50 - (MessageField != 0), 0.0); TPopupTarget::Message(MessageField ? MessageCode::TPopupTargetDisable : MessageCode::TPopupTargetEnable, 0.0);
break; break;
case 1024: case MessageCode::Reset:
{ {
this->MessageField = 0; MessageField = 0;
int* playerPtr = this->PlayerMessagefieldBackup; int* playerPtr = PlayerMessagefieldBackup;
for (auto index = 0; index < this->PinballTable->PlayerCount; ++index) for (auto index = 0; index < PinballTable->PlayerCount; ++index)
{ {
*playerPtr = 0; *playerPtr = 0;
++playerPtr; ++playerPtr;
} }
if (this->Timer) if (Timer)
timer::kill(this->Timer); timer::kill(Timer);
TimerExpired(0, this); TimerExpired(0, this);
break; break;
} }
@ -51,37 +51,26 @@ int TPopupTarget::Message(int code, float value)
return 0; return 0;
} }
void TPopupTarget::put_scoring(int index, int score) void TPopupTarget::Collision(TBall* ball, vector2* nextPosition, vector2* direction, float distance,
{
if (index < 3)
Scores[index] = score;
}
int TPopupTarget::get_scoring(int index)
{
return index < 3 ? Scores[index] : 0;
}
void TPopupTarget::Collision(TBall* ball, vector_type* nextPosition, vector_type* direction, float coef,
TEdgeSegment* edge) TEdgeSegment* edge)
{ {
if (this->PinballTable->TiltLockFlag) if (PinballTable->TiltLockFlag)
{ {
maths::basic_collision(ball, nextPosition, direction, this->Elasticity, this->Smoothness, 1000000000.0, 0.0); maths::basic_collision(ball, nextPosition, direction, Elasticity, Smoothness, 1000000000.0, 0.0);
} }
else if (maths::basic_collision( else if (maths::basic_collision(
ball, ball,
nextPosition, nextPosition,
direction, direction,
this->Elasticity, Elasticity,
this->Smoothness, Smoothness,
this->Threshold, Threshold,
this->Boost) > this->Threshold) Boost) > Threshold)
{ {
if (this->HardHitSoundId) if (HardHitSoundId)
loader::play_sound(this->HardHitSoundId); loader::play_sound(HardHitSoundId, this, "TPopupTarget1");
this->Message(49, 0.0); Message(MessageCode::TPopupTargetDisable, 0.0);
control::handler(63, this); control::handler(MessageCode::ControlCollision, this);
} }
} }
@ -90,10 +79,10 @@ void TPopupTarget::TimerExpired(int timerId, void* caller)
auto target = static_cast<TPopupTarget*>(caller); auto target = static_cast<TPopupTarget*>(caller);
target->Timer = 0; target->Timer = 0;
target->ActiveFlag = 1; target->ActiveFlag = 1;
render::sprite_set_bitmap(target->RenderSprite, target->ListBitmap->at(0)); target->SpriteSet(0);
if (timerId) if (timerId)
{ {
if (target->SoftHitSoundId) if (target->SoftHitSoundId)
loader::play_sound(target->SoftHitSoundId); loader::play_sound(target->SoftHitSoundId, target, "TPopupTarget2");
} }
} }

View File

@ -6,16 +6,13 @@ class TPopupTarget :
{ {
public: public:
TPopupTarget(TPinballTable* table, int groupIndex); TPopupTarget(TPinballTable* table, int groupIndex);
int Message(int code, float value) override; int Message(MessageCode code, float value) override;
void put_scoring(int index, int score) override; void Collision(TBall* ball, vector2* nextPosition, vector2* direction, float distance,
int get_scoring(int index) override;
void Collision(TBall* ball, vector_type* nextPosition, vector_type* direction, float coef,
TEdgeSegment* edge) override; TEdgeSegment* edge) override;
static void TimerExpired(int timerId, void* caller); static void TimerExpired(int timerId, void* caller);
int Timer; int Timer;
float TimerTime; float TimerTime;
int Scores[3]{};
int PlayerMessagefieldBackup[4]{}; int PlayerMessagefieldBackup[4]{};
}; };

View File

@ -14,7 +14,7 @@
TRamp::TRamp(TPinballTable* table, int groupIndex) : TCollisionComponent(table, groupIndex, false) TRamp::TRamp(TPinballTable* table, int groupIndex) : TCollisionComponent(table, groupIndex, false)
{ {
visualStruct visual{}; visualStruct visual{};
vector_type end{}, start{}, *end2, *start2, *start3, *end3; vector2 wall1End{}, wall1Start{}, wall2Start{}, wall2End{};
MessageField = 0; MessageField = 0;
UnusedBaseFlag = 1; UnusedBaseFlag = 1;
@ -22,60 +22,51 @@ TRamp::TRamp(TPinballTable* table, int groupIndex) : TCollisionComponent(table,
CollisionGroup = visual.CollisionGroup; CollisionGroup = visual.CollisionGroup;
BallFieldMult = loader::query_float_attribute(groupIndex, 0, 701, 0.2f); BallFieldMult = loader::query_float_attribute(groupIndex, 0, 701, 0.2f);
RampFlag1 = static_cast<int>(loader::query_float_attribute(groupIndex, 0, 1305, 0)); BallZOffsetFlag = static_cast<int>(loader::query_float_attribute(groupIndex, 0, 1305, 0));
auto floatArr3Plane = loader::query_float_attribute(groupIndex, 0, 1300); auto floatArr3Plane = loader::query_float_attribute(groupIndex, 0, 1300);
RampPlaneCount = static_cast<int>(floor(*floatArr3Plane)); RampPlaneCount = static_cast<int>(floor(*floatArr3Plane));
RampPlane = reinterpret_cast<ramp_plane_type*>(floatArr3Plane + 1); RampPlane = reinterpret_cast<ramp_plane_type*>(floatArr3Plane + 1);
auto floatArr4 = loader::query_float_attribute(groupIndex, 0, 1303); auto wall0Arr = loader::query_float_attribute(groupIndex, 0, 1303);
end.X = floatArr4[2]; auto wall0CollisionGroup = 1 << static_cast<int>(floor(wall0Arr[0]));
end.Y = floatArr4[3]; auto wall0Pts = reinterpret_cast<wall_point_type*>(wall0Arr + 2);
start.X = floatArr4[4]; Line1 = new TLine(this, &ActiveFlag, wall0CollisionGroup, wall0Pts->Pt1, wall0Pts->Pt0);
start.Y = floatArr4[5]; Line1->WallValue = nullptr;
Line1 = new TLine(this, &ActiveFlag, 1 << static_cast<int>(floor(floatArr4[0])), &start, &end); Line1->place_in_grid(&AABB);
EdgeList.push_back(Line1); EdgeList.push_back(Line1);
if (Line1)
{
Line1->WallValue = nullptr;
Line1->place_in_grid();
}
auto floatArr5WallPoint = loader::query_float_attribute(groupIndex, 0, 1301); auto wall1Arr = loader::query_float_attribute(groupIndex, 0, 1301);
Wall1PointFirst = 1 << static_cast<int>(floor(floatArr5WallPoint[0])); Wall1CollisionGroup = 1 << static_cast<int>(floor(wall1Arr[0]));
auto wallPt1_2 = static_cast<int>(floor(floatArr5WallPoint[1])); auto wall1Enabled = static_cast<int>(floor(wall1Arr[1]));
Wall1PointLast = floatArr5WallPoint[7]; Wall1BallOffset = wall1Arr[7];
maths::find_closest_edge( maths::find_closest_edge(
RampPlane, RampPlane,
RampPlaneCount, RampPlaneCount,
reinterpret_cast<wall_point_type*>(floatArr5WallPoint + 3), reinterpret_cast<wall_point_type*>(wall1Arr + 3),
&end2, wall1End,
&start2); wall1Start);
Line2 = new TLine(this, &ActiveFlag, CollisionGroup, start2, end2);
Line2 = new TLine(this, &ActiveFlag, CollisionGroup, wall1Start, wall1End);
Line2->WallValue = nullptr;
Line2->place_in_grid(&AABB);
EdgeList.push_back(Line2); EdgeList.push_back(Line2);
if (Line2)
{
Line2->WallValue = nullptr;
Line2->place_in_grid();
}
auto floatArr6WallPoint = loader::query_float_attribute(groupIndex, 0, 1302); auto wall2Arr = loader::query_float_attribute(groupIndex, 0, 1302);
auto wall2Pt1_2 = static_cast<int>(floor(floatArr6WallPoint[1])); Wall2CollisionGroup = 1 << static_cast<int>(floor(wall2Arr[0]));
Wall2PointFirst = 1 << static_cast<int>(floor(floatArr6WallPoint[0])); auto wall2Enabled = static_cast<int>(floor(wall2Arr[1]));
Wall2PointLast = floatArr6WallPoint[7]; Wall2BallOffset = wall2Arr[7];
maths::find_closest_edge( maths::find_closest_edge(
RampPlane, RampPlane,
RampPlaneCount, RampPlaneCount,
reinterpret_cast<wall_point_type*>(floatArr6WallPoint + 3), reinterpret_cast<wall_point_type*>(wall2Arr + 3),
&end3, wall2End,
&start3); wall2Start);
Line3 = new TLine(this, &ActiveFlag, CollisionGroup, start3, end3);
Line3 = new TLine(this, &ActiveFlag, CollisionGroup, wall2Start, wall2End);
Line3->WallValue = nullptr;
Line3->place_in_grid(&AABB);
EdgeList.push_back(Line3); EdgeList.push_back(Line3);
if (Line3)
{
Line3->WallValue = nullptr;
Line3->place_in_grid();
}
auto xMin = 1000000000.0f; auto xMin = 1000000000.0f;
@ -84,57 +75,50 @@ TRamp::TRamp(TPinballTable* table, int groupIndex) : TCollisionComponent(table,
auto xMax = -1000000000.0f; auto xMax = -1000000000.0f;
for (auto index = 0; index < RampPlaneCount; index++) for (auto index = 0; index < RampPlaneCount; index++)
{ {
auto plane = &RampPlane[index]; auto& plane = RampPlane[index];
auto pVec1 = reinterpret_cast<vector_type*>(&plane->V1); vector2* pointOrder[4] = { &plane.V1, &plane.V2, &plane.V3, &plane.V1 };
auto pVec2 = reinterpret_cast<vector_type*>(&plane->V2);
auto pVec3 = reinterpret_cast<vector_type*>(&plane->V3);
xMin = std::min(std::min(std::min(plane->V3.X, plane->V1.X), plane->V2.X), xMin); 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 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); 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); yMax = std::max(std::max(std::max(plane.V3.Y, plane.V1.Y), plane.V2.Y), xMin);
vector_type* pointOrder[4] = {pVec1, pVec2, pVec3, pVec1};
for (auto pt = 0; pt < 3; pt++) for (auto pt = 0; pt < 3; pt++)
{ {
auto point1 = pointOrder[pt], point2 = pointOrder[pt + 1]; auto& point1 = *pointOrder[pt], point2 = *pointOrder[pt + 1];
auto collisionGroup = 0; auto collisionGroup = 0;
if (point1 != end2 || point2 != start2)
if (point1 == wall1End && point2 == wall1Start)
{ {
if (point1 != end3 || point2 != start3) if (wall1Enabled)
{ collisionGroup = Wall1CollisionGroup;
collisionGroup = visual.CollisionGroup;
}
else if (wall2Pt1_2)
{
collisionGroup = Wall2PointFirst;
}
} }
else if (wallPt1_2) else if (point1 == wall2End && point2 == wall2Start)
{ {
collisionGroup = Wall1PointFirst; if (wall2Enabled)
collisionGroup = Wall2CollisionGroup;
} }
else
collisionGroup = visual.CollisionGroup;
if (collisionGroup) if (collisionGroup)
{ {
auto line = new TLine(this, &ActiveFlag, collisionGroup, point1, point2); auto line = new TLine(this, &ActiveFlag, collisionGroup, point1, point2);
line->WallValue = &plane;
line->place_in_grid(&AABB);
EdgeList.push_back(line); EdgeList.push_back(line);
if (line)
{
line->WallValue = plane;
line->place_in_grid();
}
} }
} }
plane->FieldForce.X = cos(plane->GravityAngle2) * sin(plane->GravityAngle1) * plane.FieldForce.X = cos(plane.GravityAngle2) * sin(plane.GravityAngle1) *
PinballTable->GravityDirVectMult; PinballTable->GravityDirVectMult;
plane->FieldForce.Y = sin(plane->GravityAngle2) * sin(plane->GravityAngle1) * plane.FieldForce.Y = sin(plane.GravityAngle2) * sin(plane.GravityAngle1) *
PinballTable->GravityDirVectMult; PinballTable->GravityDirVectMult;
} }
Field.Flag2Ptr = &ActiveFlag; Field.ActiveFlag = &ActiveFlag;
Field.CollisionComp = this; Field.CollisionComp = this;
Field.Mask = visual.CollisionGroup; Field.CollisionGroup = visual.CollisionGroup;
auto x1 = xMax; auto x1 = xMax;
auto y1 = yMax; auto y1 = yMax;
@ -143,23 +127,12 @@ TRamp::TRamp(TPinballTable* table, int groupIndex) : TCollisionComponent(table,
TTableLayer::edges_insert_square(y0, x0, y1, x1, nullptr, &Field); TTableLayer::edges_insert_square(y0, x0, y1, x1, nullptr, &Field);
} }
void TRamp::put_scoring(int index, int score) void TRamp::Collision(TBall* ball, vector2* nextPosition, vector2* direction, float distance, TEdgeSegment* edge)
{
if (index < 4)
Scores[index] = score;
}
int TRamp::get_scoring(int index)
{
return index < 4 ? Scores[index] : 0;
}
void TRamp::Collision(TBall* ball, vector_type* nextPosition, vector_type* direction, float coef, TEdgeSegment* edge)
{ {
ball->not_again(edge); ball->not_again(edge);
ball->Position.X = nextPosition->X; ball->Position.X = nextPosition->X;
ball->Position.Y = nextPosition->Y; ball->Position.Y = nextPosition->Y;
ball->RayMaxDistance -= coef; ball->RayMaxDistance -= distance;
auto plane = static_cast<ramp_plane_type*>(edge->WallValue); auto plane = static_cast<ramp_plane_type*>(edge->WallValue);
if (plane) if (plane)
@ -171,8 +144,8 @@ void TRamp::Collision(TBall* ball, vector_type* nextPosition, vector_type* direc
ball->RampFieldForce.X = plane->FieldForce.X; ball->RampFieldForce.X = plane->FieldForce.X;
ball->RampFieldForce.Y = plane->FieldForce.Y; ball->RampFieldForce.Y = plane->FieldForce.Y;
ball->Position.Z = ball->Position.X * ball->CollisionOffset.X + ball->Position.Y * ball->CollisionOffset.Y + ball->Position.Z = ball->Position.X * ball->CollisionOffset.X + ball->Position.Y * ball->CollisionOffset.Y +
ball->Offset + ball->CollisionOffset.Z; ball->Radius + ball->CollisionOffset.Z;
ball->FieldFlag = CollisionGroup; ball->CollisionMask = CollisionGroup;
return; return;
} }
@ -180,8 +153,8 @@ void TRamp::Collision(TBall* ball, vector_type* nextPosition, vector_type* direc
{ {
if (!PinballTable->TiltLockFlag) if (!PinballTable->TiltLockFlag)
{ {
loader::play_sound(SoftHitSoundId); loader::play_sound(SoftHitSoundId, ball, "TRamp");
control::handler(63, this); control::handler(MessageCode::ControlCollision, this);
} }
} }
else else
@ -189,25 +162,23 @@ void TRamp::Collision(TBall* ball, vector_type* nextPosition, vector_type* direc
ball->CollisionFlag = 0; ball->CollisionFlag = 0;
if (edge == Line2) if (edge == Line2)
{ {
ball->FieldFlag = Wall1PointFirst; ball->CollisionMask = Wall1CollisionGroup;
if (!RampFlag1) if (BallZOffsetFlag)
return; ball->Position.Z = ball->Radius + Wall1BallOffset;
ball->Position.Z = ball->Offset + Wall1PointLast;
} }
else else
{ {
ball->FieldFlag = Wall2PointFirst; ball->CollisionMask = Wall2CollisionGroup;
if (!RampFlag1) if (BallZOffsetFlag)
return; ball->Position.Z = ball->Radius + Wall2BallOffset;
ball->Position.Z = ball->Offset + Wall2PointLast;
} }
} }
} }
int TRamp::FieldEffect(TBall* ball, vector_type* vecDst) int TRamp::FieldEffect(TBall* ball, vector2* vecDst)
{ {
vecDst->X = ball->RampFieldForce.X - ball->Acceleration.X * ball->Speed * BallFieldMult; vecDst->X = ball->RampFieldForce.X - ball->Direction.X * ball->Speed * BallFieldMult;
vecDst->Y = ball->RampFieldForce.Y - ball->Acceleration.Y * ball->Speed * BallFieldMult; vecDst->Y = ball->RampFieldForce.Y - ball->Direction.Y * ball->Speed * BallFieldMult;
return 1; return 1;
} }

View File

@ -10,25 +10,22 @@ class TRamp :
{ {
public: public:
TRamp(TPinballTable* table, int groupIndex); TRamp(TPinballTable* table, int groupIndex);
void put_scoring(int index, int score) override; void Collision(TBall* ball, vector2* nextPosition, vector2* direction, float distance,
int get_scoring(int index) override;
void Collision(TBall* ball, vector_type* nextPosition, vector_type* direction, float coef,
TEdgeSegment* edge) override; TEdgeSegment* edge) override;
int FieldEffect(TBall* ball, vector_type* vecDst) override; int FieldEffect(TBall* ball, vector2* vecDst) override;
void port_draw() override; void port_draw() override;
int Scores[4]{};
field_effect_type Field{}; field_effect_type Field{};
int CollisionGroup; int CollisionGroup;
int RampFlag1; bool BallZOffsetFlag;
int RampPlaneCount; int RampPlaneCount;
float BallFieldMult; float BallFieldMult;
ramp_plane_type* RampPlane; ramp_plane_type* RampPlane;
TEdgeSegment* Line2; TEdgeSegment* Line2;
TEdgeSegment* Line3; TEdgeSegment* Line3;
TEdgeSegment* Line1; TEdgeSegment* Line1;
int Wall1PointFirst; int Wall1CollisionGroup;
int Wall2PointFirst; int Wall2CollisionGroup;
float Wall1PointLast; float Wall1BallOffset;
float Wall2PointLast; float Wall2BallOffset;
}; };

View File

@ -19,32 +19,30 @@ TRollover::TRollover(TPinballTable* table, int groupIndex, bool createWall) : TC
TRollover::TRollover(TPinballTable* table, int groupIndex) : TCollisionComponent(table, groupIndex, false) TRollover::TRollover(TPinballTable* table, int groupIndex) : TCollisionComponent(table, groupIndex, false)
{ {
if (ListBitmap) SpriteSet(0);
render::sprite_set_bitmap(RenderSprite, ListBitmap->at(0));
build_walls(groupIndex); build_walls(groupIndex);
} }
int TRollover::Message(int code, float value) int TRollover::Message(MessageCode code, float value)
{ {
if (code == 1024) if (code == MessageCode::Reset)
{ {
this->ActiveFlag = 1; ActiveFlag = 1;
this->RolloverFlag = 0; RolloverFlag = 0;
if (this->ListBitmap) SpriteSet(0);
render::sprite_set_bitmap(this->RenderSprite, this->ListBitmap->at(0));
} }
return 0; return 0;
} }
void TRollover::Collision(TBall* ball, vector_type* nextPosition, vector_type* direction, float coef, void TRollover::Collision(TBall* ball, vector2* nextPosition, vector2* direction, float distance,
TEdgeSegment* edge) TEdgeSegment* edge)
{ {
ball->Position.X = nextPosition->X; ball->Position.X = nextPosition->X;
ball->Position.Y = nextPosition->Y; ball->Position.Y = nextPosition->Y;
ball->RayMaxDistance -= coef; ball->RayMaxDistance -= distance;
ball->not_again(edge); ball->not_again(edge);
gdrv_bitmap8* bmp = nullptr;
if (!PinballTable->TiltLockFlag) if (!PinballTable->TiltLockFlag)
{ {
if (RolloverFlag) if (RolloverFlag)
@ -54,31 +52,14 @@ void TRollover::Collision(TBall* ball, vector_type* nextPosition, vector_type* d
} }
else else
{ {
loader::play_sound(SoftHitSoundId); loader::play_sound(SoftHitSoundId, ball, "TRollover");
control::handler(63, this); control::handler(MessageCode::ControlCollision, this);
} }
RolloverFlag = RolloverFlag == 0; RolloverFlag = RolloverFlag == 0;
if (ListBitmap) SpriteSet(RolloverFlag ? -1 : 0);
{
if (!RolloverFlag)
bmp = ListBitmap->at(0);
render::sprite_set_bitmap(RenderSprite, bmp);
}
} }
} }
void TRollover::put_scoring(int index, int score)
{
if (index < 2)
Scores[index] = score;
}
int TRollover::get_scoring(int index)
{
return index < 2 ? Scores[index] : 0;
}
void TRollover::build_walls(int groupIndex) void TRollover::build_walls(int groupIndex)
{ {
visualStruct visual{}; visualStruct visual{};

View File

@ -9,15 +9,12 @@ protected:
public: public:
TRollover(TPinballTable* table, int groupIndex); TRollover(TPinballTable* table, int groupIndex);
~TRollover() override = default; ~TRollover() override = default;
int Message(int code, float value) override; int Message(MessageCode code, float value) override;
void Collision(TBall* ball, vector_type* nextPosition, vector_type* direction, float coef, void Collision(TBall* ball, vector2* nextPosition, vector2* direction, float distance,
TEdgeSegment* edge) override; TEdgeSegment* edge) override;
void put_scoring(int index, int score) override;
int get_scoring(int index) override;
void build_walls(int groupIndex); void build_walls(int groupIndex);
static void TimerExpired(int timerId, void* caller); static void TimerExpired(int timerId, void* caller);
char RolloverFlag{}; char RolloverFlag{};
int Scores[2]{};
}; };

View File

@ -7,6 +7,7 @@
#include "render.h" #include "render.h"
#include "TPinballTable.h" #include "TPinballTable.h"
#include "TBall.h" #include "TBall.h"
#include "TDrain.h"
#include "timer.h" #include "timer.h"
TSink::TSink(TPinballTable* table, int groupIndex) : TCollisionComponent(table, groupIndex, true) TSink::TSink(TPinballTable* table, int groupIndex) : TCollisionComponent(table, groupIndex, true)
@ -14,13 +15,12 @@ TSink::TSink(TPinballTable* table, int groupIndex) : TCollisionComponent(table,
visualStruct visual{}; visualStruct visual{};
MessageField = 0; MessageField = 0;
Timer = 0; loader::query_visual(groupIndex, 0, &visual);
loader::query_visual(groupIndex, 0, &visual); BallThrowDirection = visual.Kicker.ThrowBallDirection;
BallAcceleration = visual.Kicker.ThrowBallAcceleration;
ThrowAngleMult = visual.Kicker.ThrowBallAngleMult; ThrowAngleMult = visual.Kicker.ThrowBallAngleMult;
ThrowSpeedMult1 = visual.Kicker.Boost; ThrowSpeedMult1 = visual.Kicker.Boost;
ThrowSpeedMult2 = visual.Kicker.ThrowBallMult * 0.01f; ThrowSpeedMult2 = visual.Kicker.ThrowBallMult * 0.01f;
SoundIndex4 = visual.SoundIndex4; SoundIndex4 = visual.SoundIndex4;
SoundIndex3 = visual.SoundIndex3; SoundIndex3 = visual.SoundIndex3;
auto floatArr = loader::query_float_attribute(groupIndex, 0, 601); auto floatArr = loader::query_float_attribute(groupIndex, 0, 601);
BallPosition.X = floatArr[0]; BallPosition.X = floatArr[0];
@ -28,34 +28,26 @@ TSink::TSink(TPinballTable* table, int groupIndex) : TCollisionComponent(table,
TimerTime = *loader::query_float_attribute(groupIndex, 0, 407); TimerTime = *loader::query_float_attribute(groupIndex, 0, 407);
} }
int TSink::Message(int code, float value) int TSink::Message(MessageCode code, float value)
{ {
switch (code) switch (code)
{ {
case 56: case MessageCode::TSinkResetTimer:
if (value < 0.0f) if (value < 0.0f)
value = TimerTime; value = TimerTime;
Timer = timer::set(value, this, TimerExpired); timer::set(value, this, TimerExpired);
break; break;
case 1020: case MessageCode::PlayerChanged:
timer::kill(TimerExpired);
PlayerMessagefieldBackup[PinballTable->CurrentPlayer] = MessageField; PlayerMessagefieldBackup[PinballTable->CurrentPlayer] = MessageField;
MessageField = PlayerMessagefieldBackup[static_cast<int>(floor(value))]; MessageField = PlayerMessagefieldBackup[static_cast<int>(floor(value))];
break; break;
case 1024: case MessageCode::Reset:
{ {
if (Timer) timer::kill(TimerExpired);
timer::kill(Timer);
Timer = 0;
MessageField = 0; MessageField = 0;
for (auto &msgBackup : PlayerMessagefieldBackup)
auto playerPtr = PlayerMessagefieldBackup; msgBackup = 0;
for (auto index = 0; index < PinballTable->PlayerCount; ++index)
{
*playerPtr = 0;
++playerPtr;
}
break; break;
} }
default: default:
@ -64,44 +56,36 @@ int TSink::Message(int code, float value)
return 0; return 0;
} }
void TSink::put_scoring(int index, int score) void TSink::Collision(TBall* ball, vector2* nextPosition, vector2* direction, float distance, TEdgeSegment* edge)
{ {
if (index < 3)
Scores[index] = score;
}
int TSink::get_scoring(int index)
{
return index < 3 ? Scores[index] : 0;
}
void TSink::Collision(TBall* ball, vector_type* nextPosition, vector_type* direction, float coef, TEdgeSegment* edge)
{
Timer = 0;
if (PinballTable->TiltLockFlag) if (PinballTable->TiltLockFlag)
{ {
maths::basic_collision(ball, nextPosition, direction, Elasticity, Smoothness, 1000000000.0, 0.0); PinballTable->Drain->Collision(ball, nextPosition, direction, distance, edge);
} }
else else
{ {
ball->ActiveFlag = 0; ball->Disable();
render::sprite_set_bitmap(ball->RenderSprite, nullptr); loader::play_sound(SoundIndex4, ball, "TSink1");
loader::play_sound(SoundIndex4); control::handler(MessageCode::ControlCollision, this);
control::handler(63, this);
} }
} }
void TSink::TimerExpired(int timerId, void* caller) void TSink::TimerExpired(int timerId, void* caller)
{ {
auto sink = static_cast<TSink*>(caller); auto sink = static_cast<TSink*>(caller);
auto ball = sink->PinballTable->BallList.at(0); auto table = sink->PinballTable;
ball->CollisionComp = nullptr; if (table->BallCountInRect(sink->BallPosition, table->CollisionCompOffset * 2.0f))
ball->ActiveFlag = 1; {
ball->Position.X = sink->BallPosition.X; timer::set(0.5f, sink, TimerExpired);
ball->Position.Y = sink->BallPosition.Y; }
TBall::throw_ball(ball, &sink->BallAcceleration, sink->ThrowAngleMult, sink->ThrowSpeedMult1, else
sink->ThrowSpeedMult2); {
if (sink->SoundIndex3) auto ball = table->AddBall(sink->BallPosition);
loader::play_sound(sink->SoundIndex3); assertm(ball, "Failure to create ball in sink");
sink->Timer = 0; ball->CollisionDisabledFlag = true;
ball->throw_ball(&sink->BallThrowDirection, sink->ThrowAngleMult, sink->ThrowSpeedMult1,
sink->ThrowSpeedMult2);
if (sink->SoundIndex3)
loader::play_sound(sink->SoundIndex3, ball, "TSink2");
}
} }

View File

@ -7,23 +7,19 @@ class TSink :
{ {
public: public:
TSink(TPinballTable* table, int groupIndex); TSink(TPinballTable* table, int groupIndex);
int Message(int code, float value) override; int Message(MessageCode code, float value) override;
void put_scoring(int index, int score) override; void Collision(TBall* ball, vector2* nextPosition, vector2* direction, float distance,
int get_scoring(int index) override;
void Collision(TBall* ball, vector_type* nextPosition, vector_type* direction, float coef,
TEdgeSegment* edge) override; TEdgeSegment* edge) override;
static void TimerExpired(int timerId, void* caller); static void TimerExpired(int timerId, void* caller);
int Timer;
float TimerTime; float TimerTime;
vector_type BallPosition{}; vector2 BallPosition{};
vector_type BallAcceleration{}; vector3 BallThrowDirection{};
float ThrowAngleMult; float ThrowAngleMult;
float ThrowSpeedMult1; float ThrowSpeedMult1;
float ThrowSpeedMult2; float ThrowSpeedMult2;
int SoundIndex4; int SoundIndex4;
int SoundIndex3; int SoundIndex3;
int Scores[3]{};
int PlayerMessagefieldBackup[4]{}; int PlayerMessagefieldBackup[4]{};
}; };

View File

@ -16,18 +16,18 @@ TSoloTarget::TSoloTarget(TPinballTable* table, int groupIndex) : TCollisionCompo
TimerTime = 0.1f; TimerTime = 0.1f;
loader::query_visual(groupIndex, 0, &visual); loader::query_visual(groupIndex, 0, &visual);
SoundIndex4 = visual.SoundIndex4; SoundIndex4 = visual.SoundIndex4;
TSoloTarget::Message(50, 0.0); TSoloTarget::Message(MessageCode::TSoloTargetEnable, 0.0);
} }
int TSoloTarget::Message(int code, float value) int TSoloTarget::Message(MessageCode code, float value)
{ {
switch (code) switch (code)
{ {
case 49: case MessageCode::TSoloTargetDisable:
case 50: case MessageCode::TSoloTargetEnable:
ActiveFlag = code == 50; ActiveFlag = code == MessageCode::TSoloTargetEnable;
break; break;
case 1024: case MessageCode::Reset:
if (Timer) if (Timer)
timer::kill(Timer); timer::kill(Timer);
Timer = 0; Timer = 0;
@ -37,47 +37,24 @@ int TSoloTarget::Message(int code, float value)
return 0; return 0;
} }
if (ListBitmap) SpriteSet(1 - ActiveFlag);
{
auto index = 1 - ActiveFlag;
auto bmp = ListBitmap->at(index);
auto zMap = ListZMap->at(index);
render::sprite_set(
RenderSprite,
bmp,
zMap,
bmp->XPosition - PinballTable->XOffset,
bmp->YPosition - PinballTable->YOffset);
}
return 0; return 0;
} }
void TSoloTarget::put_scoring(int index, int score) void TSoloTarget::Collision(TBall* ball, vector2* nextPosition, vector2* direction, float distance,
{
if (index < 1)
Scores[index] = score;
}
int TSoloTarget::get_scoring(int index)
{
return index < 1 ? Scores[index] : 0;
}
void TSoloTarget::Collision(TBall* ball, vector_type* nextPosition, vector_type* direction, float coef,
TEdgeSegment* edge) TEdgeSegment* edge)
{ {
if (DefaultCollision(ball, nextPosition, direction)) if (DefaultCollision(ball, nextPosition, direction))
{ {
Message(49, 0.0); Message(MessageCode::TSoloTargetDisable, 0.0);
Timer = timer::set(TimerTime, this, TimerExpired); Timer = timer::set(TimerTime, this, TimerExpired);
control::handler(63, this); control::handler(MessageCode::ControlCollision, this);
} }
} }
void TSoloTarget::TimerExpired(int timerId, void* caller) void TSoloTarget::TimerExpired(int timerId, void* caller)
{ {
auto target = static_cast<TSoloTarget*>(caller); auto target = static_cast<TSoloTarget*>(caller);
target->Message(50, 0.0); target->Message(MessageCode::TSoloTargetEnable, 0.0);
target->Timer = 0; target->Timer = 0;
} }

View File

@ -6,10 +6,8 @@ class TSoloTarget :
{ {
public: public:
TSoloTarget(TPinballTable* table, int groupIndex); TSoloTarget(TPinballTable* table, int groupIndex);
int Message(int code, float value) override; int Message(MessageCode code, float value) override;
void put_scoring(int index, int score) override; void Collision(TBall* ball, vector2* nextPosition, vector2* direction, float distance,
int get_scoring(int index) override;
void Collision(TBall* ball, vector_type* nextPosition, vector_type* direction, float coef,
TEdgeSegment* edge) override; TEdgeSegment* edge) override;
static void TimerExpired(int timerId, void* caller); static void TimerExpired(int timerId, void* caller);
@ -17,5 +15,4 @@ public:
int Timer; int Timer;
float TimerTime; float TimerTime;
int SoundIndex4; int SoundIndex4;
int Scores[1]{};
}; };

View File

@ -10,7 +10,7 @@ TSound::TSound(TPinballTable* table, int groupIndex) : TPinballComponent(table,
this->SoundIndex = visual.SoundIndex4; this->SoundIndex = visual.SoundIndex4;
} }
float TSound::Play() float TSound::Play(TPinballComponent *soundSource, const char* info)
{ {
return loader::play_sound(this->SoundIndex); return loader::play_sound(this->SoundIndex, soundSource, info);
} }

View File

@ -6,7 +6,7 @@ class TSound :
{ {
public: public:
TSound(TPinballTable* table, int groupIndex); TSound(TPinballTable* table, int groupIndex);
float Play(); float Play(TPinballComponent *soundSource, const char* info);
int SoundIndex; int SoundIndex;
}; };

View File

@ -20,19 +20,20 @@ TTableLayer::TTableLayer(TPinballTable* table): TCollisionComponent(table, -1, f
auto groupIndex = loader::query_handle("table"); auto groupIndex = loader::query_handle("table");
loader::query_visual(groupIndex, 0, &visual); loader::query_visual(groupIndex, 0, &visual);
auto spriteData = visual.Bitmap;
/*Full tilt: proj center first value is offset by resolution*/ /*Full tilt: proj center first value is offset by resolution*/
auto projCenter = loader::query_float_attribute(groupIndex, 0, 700 + fullscrn::GetResolution()); auto projCenter = loader::query_float_attribute(groupIndex, 0, 700 + fullscrn::GetResolution());
proj::recenter(projCenter[0], projCenter[1]); proj::recenter(projCenter[0], projCenter[1]);
render::set_background_zmap(visual.ZMap, 0, 0); render::set_background_zmap(spriteData.ZMap, 0, 0);
auto bmp = visual.Bitmap; auto bmp = spriteData.Bmp;
VisBmp = visual.Bitmap; VisBmp = bmp;
rect.XPosition = 0; rect.XPosition = 0;
rect.YPosition = 0; rect.YPosition = 0;
rect.Width = bmp->Width; rect.Width = bmp->Width;
rect.Height = bmp->Height; rect.Height = bmp->Height;
render::create_sprite(VisualTypes::None, bmp, visual.ZMap, 0, 0, &rect); new render_sprite(VisualTypes::Background, bmp, spriteData.ZMap, 0, 0, &rect);
PinballTable->SoundIndex1 = visual.SoundIndex4; PinballTable->SoundIndex1 = visual.SoundIndex4;
PinballTable->SoundIndex2 = visual.SoundIndex3; PinballTable->SoundIndex2 = visual.SoundIndex3;
@ -71,37 +72,33 @@ TTableLayer::TTableLayer(TPinballTable* table): TCollisionComponent(table, -1, f
Threshold = visual.Kicker.Threshold; Threshold = visual.Kicker.Threshold;
Boost = 15.0f; Boost = 15.0f;
auto visArrPtr = visual.FloatArr; auto edgePoints = reinterpret_cast<vector2*>(visual.FloatArr);
Unknown1F = std::min(visArrPtr[0], std::min(visArrPtr[2], visArrPtr[4])); XMin = std::min(edgePoints[0].X, std::min(edgePoints[1].X, edgePoints[2].X));
Unknown2F = std::min(visArrPtr[1], std::min(visArrPtr[3], visArrPtr[5])); YMin = std::min(edgePoints[0].Y, std::min(edgePoints[1].Y, edgePoints[2].Y));
Unknown3F = std::max(visArrPtr[0], std::max(visArrPtr[2], visArrPtr[4])); XMax = std::max(edgePoints[0].X, std::max(edgePoints[1].X, edgePoints[2].X));
Unknown4F = std::max(visArrPtr[1], std::max(visArrPtr[3], visArrPtr[5])); YMax = std::max(edgePoints[0].Y, std::max(edgePoints[1].Y, edgePoints[2].Y));
auto a2 = Unknown4F - Unknown2F;
auto a1 = Unknown3F - Unknown1F;
edge_manager = new TEdgeManager(Unknown1F, Unknown2F, a1, a2);
for (auto visFloatArrCount = visual.FloatArrCount; visFloatArrCount > 0; visFloatArrCount--) auto height = YMax - YMin;
auto width = XMax - XMin;
edge_manager = new TEdgeManager(XMin, YMin, width, height);
for (auto i = 0; i < visual.FloatArrCount; i++)
{ {
auto line = new TLine(this, auto line = new TLine(this,
&ActiveFlag, &ActiveFlag,
visual.CollisionGroup, visual.CollisionGroup,
visArrPtr[2], edgePoints[i + 1].X,
visArrPtr[3], edgePoints[i + 1].Y,
visArrPtr[0], edgePoints[i].X,
visArrPtr[1]); edgePoints[i].Y);
if (line) line->place_in_grid(&AABB);
{ EdgeList.push_back(line);
line->place_in_grid();
EdgeList.push_back(line);
}
visArrPtr += 2;
} }
Field.Mask = -1; Field.CollisionGroup = -1;
Field.Flag2Ptr = &ActiveFlag; Field.ActiveFlag = &ActiveFlag;
Field.CollisionComp = this; Field.CollisionComp = this;
edges_insert_square(Unknown2F, Unknown1F, Unknown4F, Unknown3F, nullptr, edges_insert_square(YMin, XMin, YMax, XMax, nullptr,
&Field); &Field);
} }
@ -111,11 +108,11 @@ TTableLayer::~TTableLayer()
delete edge_manager; delete edge_manager;
} }
int TTableLayer::FieldEffect(TBall* ball, vector_type* vecDst) int TTableLayer::FieldEffect(TBall* ball, vector2* vecDst)
{ {
vecDst->X = GraityDirX - (0.5f - RandFloat() + ball->Acceleration.X) * vecDst->X = GraityDirX - (0.5f - RandFloat() + ball->Direction.X) *
ball->Speed * GraityMult; ball->Speed * GraityMult;
vecDst->Y = GraityDirY - ball->Acceleration.Y * ball->Speed * GraityMult; vecDst->Y = GraityDirY - ball->Direction.Y * ball->Speed * GraityMult;
return 1; return 1;
} }
@ -134,10 +131,10 @@ void TTableLayer::edges_insert_square(float y0, float x0, float y1, float x1, TE
int xMaxBox = edge_manager->box_x(xMax); int xMaxBox = edge_manager->box_x(xMax);
int yMaxBox = edge_manager->box_y(yMax); int yMaxBox = edge_manager->box_y(yMax);
float boxX = static_cast<float>(xMinBox) * edge_manager->AdvanceX + edge_manager->X; float boxX = static_cast<float>(xMinBox) * edge_manager->AdvanceX + edge_manager->MinX;
for (int indexX = xMinBox; indexX <= xMaxBox; ++indexX) for (int indexX = xMinBox; indexX <= xMaxBox; ++indexX)
{ {
float boxY = static_cast<float>(yMinBox) * edge_manager->AdvanceY + edge_manager->Y; float boxY = static_cast<float>(yMinBox) * edge_manager->AdvanceY + edge_manager->MinY;
for (int indexY = yMinBox; indexY <= yMaxBox; ++indexY) for (int indexY = yMinBox; indexY <= yMaxBox; ++indexY)
{ {
if (xMax >= boxX && xMin <= boxX + edge_manager->AdvanceX && if (xMax >= boxX && xMin <= boxX + edge_manager->AdvanceX &&
@ -161,7 +158,7 @@ void TTableLayer::edges_insert_square(float y0, float x0, float y1, float x1, TE
void TTableLayer::edges_insert_circle(circle_type* circle, TEdgeSegment* edge, field_effect_type* field) void TTableLayer::edges_insert_circle(circle_type* circle, TEdgeSegment* edge, field_effect_type* field)
{ {
ray_type ray{}; ray_type ray{};
vector_type vec1{}; vector2 vec1{};
auto radiusM = sqrt(circle->RadiusSq) + edge_manager->AdvanceX * 0.001f; auto radiusM = sqrt(circle->RadiusSq) + edge_manager->AdvanceX * 0.001f;
auto radiusMSq = radiusM * radiusM; auto radiusMSq = radiusM * radiusM;
@ -182,10 +179,10 @@ void TTableLayer::edges_insert_circle(circle_type* circle, TEdgeSegment* edge, f
xMaxBox = edge_manager->increment_box_x(xMaxBox); xMaxBox = edge_manager->increment_box_x(xMaxBox);
yMaxBox = edge_manager->increment_box_y(yMaxBox); yMaxBox = edge_manager->increment_box_y(yMaxBox);
vec1.X = static_cast<float>(dirX) * edge_manager->AdvanceX + edge_manager->X; vec1.X = static_cast<float>(dirX) * edge_manager->AdvanceX + edge_manager->MinX;
for (auto indexX = dirX; indexX <= xMaxBox; ++indexX) for (auto indexX = dirX; indexX <= xMaxBox; ++indexX)
{ {
vec1.Y = static_cast<float>(dirY) * edge_manager->AdvanceY + edge_manager->Y; vec1.Y = static_cast<float>(dirY) * edge_manager->AdvanceY + edge_manager->MinY;
for (int indexY = dirY; indexY <= yMaxBox; ++indexY) for (int indexY = dirY; indexY <= yMaxBox; ++indexY)
{ {
auto vec1XAdv = vec1.X + edge_manager->AdvanceX; auto vec1XAdv = vec1.X + edge_manager->AdvanceX;
@ -220,45 +217,45 @@ void TTableLayer::edges_insert_circle(circle_type* circle, TEdgeSegment* edge, f
ray.Direction.X = 1.0; ray.Direction.X = 1.0;
ray.Direction.Y = 0.0; ray.Direction.Y = 0.0;
ray.MaxDistance = edge_manager->AdvanceX; ray.MaxDistance = edge_manager->AdvanceX;
if (maths::ray_intersect_circle(&ray, circle) < 1000000000.0f) if (maths::ray_intersect_circle(ray, *circle) < 1000000000.0f)
break; break;
ray.Direction.X = -1.0; ray.Direction.X = -1.0;
ray.Origin.X = ray.Origin.X + edge_manager->AdvanceX; ray.Origin.X = ray.Origin.X + edge_manager->AdvanceX;
if (maths::ray_intersect_circle(&ray, circle) < 1000000000.0f) if (maths::ray_intersect_circle(ray, *circle) < 1000000000.0f)
break; break;
ray.Direction.X = 0.0; ray.Direction.X = 0.0;
ray.Direction.Y = 1.0; ray.Direction.Y = 1.0;
ray.MaxDistance = edge_manager->AdvanceY; ray.MaxDistance = edge_manager->AdvanceY;
if (maths::ray_intersect_circle(&ray, circle) < 1000000000.0f) if (maths::ray_intersect_circle(ray, *circle) < 1000000000.0f)
break; break;
ray.Direction.Y = -1.0; ray.Direction.Y = -1.0;
ray.Origin.Y = ray.Origin.Y + edge_manager->AdvanceY; ray.Origin.Y = ray.Origin.Y + edge_manager->AdvanceY;
if (maths::ray_intersect_circle(&ray, circle) < 1000000000.0f) if (maths::ray_intersect_circle(ray, *circle) < 1000000000.0f)
break; break;
ray.Direction.Y = 0.0; ray.Direction.Y = 0.0;
ray.Direction.X = -1.0; ray.Direction.X = -1.0;
ray.MaxDistance = edge_manager->AdvanceX; ray.MaxDistance = edge_manager->AdvanceX;
if (maths::ray_intersect_circle(&ray, circle) < 1000000000.0f) if (maths::ray_intersect_circle(ray, *circle) < 1000000000.0f)
break; break;
ray.Direction.X = 1.0; ray.Direction.X = 1.0;
ray.Origin.X = ray.Origin.X - edge_manager->AdvanceX; ray.Origin.X = ray.Origin.X - edge_manager->AdvanceX;
if (maths::ray_intersect_circle(&ray, circle) < 1000000000.0f) if (maths::ray_intersect_circle(ray, *circle) < 1000000000.0f)
break; break;
ray.Direction.X = 0.0; ray.Direction.X = 0.0;
ray.Direction.Y = -1.0; ray.Direction.Y = -1.0;
ray.MaxDistance = edge_manager->AdvanceY; ray.MaxDistance = edge_manager->AdvanceY;
if (maths::ray_intersect_circle(&ray, circle) < 1000000000.0f) if (maths::ray_intersect_circle(ray, *circle) < 1000000000.0f)
break; break;
ray.Direction.Y = 1.0; ray.Direction.Y = 1.0;
ray.Origin.Y = ray.Origin.Y - edge_manager->AdvanceY; ray.Origin.Y = ray.Origin.Y - edge_manager->AdvanceY;
if (maths::ray_intersect_circle(&ray, circle) < 1000000000.0f) if (maths::ray_intersect_circle(ray, *circle) < 1000000000.0f)
break; break;
collision = false; collision = false;

View File

@ -14,17 +14,17 @@ class TTableLayer :
public: public:
TTableLayer(TPinballTable* table); TTableLayer(TPinballTable* table);
~TTableLayer() override; ~TTableLayer() override;
int FieldEffect(TBall* ball, vector_type* vecDst) override; int FieldEffect(TBall* ball, vector2* vecDst) override;
static void edges_insert_square(float y0, float x0, float y1, float x1, TEdgeSegment* edge, static void edges_insert_square(float y0, float x0, float y1, float x1, TEdgeSegment* edge,
field_effect_type* field); field_effect_type* field);
static void edges_insert_circle(circle_type* circle, TEdgeSegment* edge, field_effect_type* field); static void edges_insert_circle(circle_type* circle, TEdgeSegment* edge, field_effect_type* field);
gdrv_bitmap8* VisBmp; gdrv_bitmap8* VisBmp;
float Unknown1F; float XMin;
float Unknown2F; float YMin;
float Unknown3F; float XMax;
float Unknown4F; float YMax;
float GraityDirX; float GraityDirX;
float GraityDirY; float GraityDirY;
float GraityMult; float GraityMult;

View File

@ -18,8 +18,8 @@ TTextBox::TTextBox(TPinballTable* table, int groupIndex) : TPinballComponent(tab
Height = 0; Height = 0;
BgBmp = render::background_bitmap; BgBmp = render::background_bitmap;
Font = score::msg_fontp; Font = score::msg_fontp;
Message1 = nullptr; CurrentMessage = nullptr;
Message2 = nullptr; PreviousMessage = nullptr;
Timer = 0; Timer = 0;
if (groupIndex > 0) if (groupIndex > 0)
@ -42,16 +42,16 @@ TTextBox::~TTextBox()
timer::kill(Timer); timer::kill(Timer);
Timer = 0; Timer = 0;
} }
while (Message1) while (CurrentMessage)
{ {
TTextBoxMessage* message = Message1; TTextBoxMessage* message = CurrentMessage;
TTextBoxMessage* nextMessage = message->NextMessage; TTextBoxMessage* nextMessage = message->NextMessage;
delete message; delete message;
Message1 = nextMessage; CurrentMessage = nextMessage;
} }
} }
int TTextBox::Message(int code, float value) int TTextBox::Message(MessageCode code, float value)
{ {
return 0; return 0;
} }
@ -59,19 +59,19 @@ int TTextBox::Message(int code, float value)
void TTextBox::TimerExpired(int timerId, void* caller) void TTextBox::TimerExpired(int timerId, void* caller)
{ {
auto tb = static_cast<TTextBox*>(caller); auto tb = static_cast<TTextBox*>(caller);
TTextBoxMessage* message = tb->Message1; TTextBoxMessage* message = tb->CurrentMessage;
tb->Timer = 0; tb->Timer = 0;
if (message) if (message)
{ {
TTextBoxMessage* nextMessage = message->NextMessage; TTextBoxMessage* nextMessage = message->NextMessage;
delete message; delete message;
tb->Message1 = nextMessage; tb->CurrentMessage = nextMessage;
tb->Draw(); tb->Draw();
control::handler(60, tb); control::handler(MessageCode::ControlTimerExpired, tb);
} }
} }
void TTextBox::Clear() void TTextBox::Clear(bool lowPriorityOnly)
{ {
gdrv_bitmap8* bmp = BgBmp; gdrv_bitmap8* bmp = BgBmp;
if (bmp) if (bmp)
@ -92,24 +92,25 @@ void TTextBox::Clear()
timer::kill(Timer); timer::kill(Timer);
Timer = 0; Timer = 0;
} }
while (Message1) while (CurrentMessage && (!lowPriorityOnly || CurrentMessage->LowPriority))
{ {
TTextBoxMessage* message = Message1; auto message = CurrentMessage;
TTextBoxMessage* nextMessage = message->NextMessage; CurrentMessage = message->NextMessage;
delete message; delete message;
Message1 = nextMessage;
} }
if (CurrentMessage)
Draw();
} }
void TTextBox::Display(const char* text, float time) void TTextBox::Display(const char* text, float time, bool lowPriority)
{ {
if (!text) if (!text)
return; return;
if (Message1 && !strcmp(text, Message2->Text)) if (CurrentMessage && !strcmp(text, PreviousMessage->Text))
{ {
Message2->Refresh(time); PreviousMessage->Refresh(time);
if (Message2 == Message1) if (PreviousMessage == CurrentMessage)
{ {
if (Timer && Timer != -1) if (Timer && Timer != -1)
timer::kill(Timer); timer::kill(Timer);
@ -124,27 +125,63 @@ void TTextBox::Display(const char* text, float time)
if (Timer == -1) if (Timer == -1)
Clear(); Clear();
auto message = new TTextBoxMessage(text, time); auto message = new TTextBoxMessage(text, time, lowPriority);
if (message) if (message->Text)
{ {
if (message->Text) if (CurrentMessage)
{ PreviousMessage->NextMessage = message;
if (Message1)
Message2->NextMessage = message;
else
Message1 = message;
Message2 = message;
if (Timer == 0)
Draw();
}
else else
{ CurrentMessage = message;
delete message; PreviousMessage = message;
} if (Timer == 0)
Draw();
}
else
{
delete message;
} }
} }
} }
void TTextBox::DrawImGui()
{
// Do nothing when using a font (the text will be rendered to VScreen in TTextBox::Draw)
if (Font || !CurrentMessage)
return;
char windowName[64];
SDL_Rect rect;
ImGuiWindowFlags window_flags =
ImGuiWindowFlags_NoBackground |
ImGuiWindowFlags_NoDecoration |
ImGuiWindowFlags_NoSavedSettings |
ImGuiWindowFlags_NoFocusOnAppearing |
ImGuiWindowFlags_NoInputs;
rect.x = OffsetX;
rect.y = OffsetY;
rect.w = Width;
rect.h = Height;
rect = fullscrn::GetScreenRectFromPinballRect(rect);
ImGui::SetNextWindowPos(ImVec2(static_cast<float>(rect.x), static_cast<float>(rect.y)));
ImGui::SetNextWindowSize(ImVec2(static_cast<float>(rect.w), static_cast<float>(rect.h)));
// Use the pointer to generate a window unique name per text box
snprintf(windowName, sizeof(windowName), "TTextBox_%p", static_cast<void*>(this));
if (ImGui::Begin(windowName, nullptr, window_flags))
{
ImGui::SetWindowFontScale(fullscrn::GetScreenToPinballRatio());
// ToDo: centered text in FT
ImGui::PushStyleColor(ImGuiCol_Text, pb::TextBoxColor);
ImGui::TextWrapped("%s", CurrentMessage->Text);
ImGui::PopStyleColor();
}
ImGui::End();
}
void TTextBox::Draw() void TTextBox::Draw()
{ {
auto bmp = BgBmp; auto bmp = BgBmp;
@ -162,26 +199,26 @@ void TTextBox::Draw()
gdrv::fill_bitmap(render::vscreen, Width, Height, OffsetX, OffsetY, 0); gdrv::fill_bitmap(render::vscreen, Width, Height, OffsetX, OffsetY, 0);
bool display = false; bool display = false;
while (Message1) while (CurrentMessage)
{ {
if (Message1->Time == -1.0f) if (CurrentMessage->Time == -1.0f)
{ {
if (!Message1->NextMessage) if (!CurrentMessage->NextMessage)
{ {
Timer = -1; Timer = -1;
display = true; display = true;
break; break;
} }
} }
else if (Message1->TimeLeft() >= -2.0f) else if (CurrentMessage->TimeLeft() >= -2.0f)
{ {
Timer = timer::set(std::max(Message1->TimeLeft(), 0.25f), this, TimerExpired); Timer = timer::set(std::max(CurrentMessage->TimeLeft(), 0.25f), this, TimerExpired);
display = true; display = true;
break; break;
} }
auto tmp = Message1; auto tmp = CurrentMessage;
Message1 = Message1->NextMessage; CurrentMessage = CurrentMessage->NextMessage;
delete tmp; delete tmp;
} }
@ -189,19 +226,13 @@ void TTextBox::Draw()
{ {
if (!Font) if (!Font)
{ {
gdrv::grtext_draw_ttext_in_box( // Immediate mode drawing using system font is handled by TTextBox::DrawImGui
Message1->Text,
render::vscreen->XPosition + OffsetX,
render::vscreen->YPosition + OffsetY,
Width,
Height,
255);
return; return;
} }
std::vector<LayoutResult> lines{}; std::vector<LayoutResult> lines{};
auto textHeight = 0; auto textHeight = 0;
for (auto text = Message1->Text; ; textHeight += Font->Height) for (auto text = CurrentMessage->Text; ; textHeight += Font->Height)
{ {
if (!text[0] || textHeight + Font->Height > Height) if (!text[0] || textHeight + Font->Height > Height)
break; break;

View File

@ -14,14 +14,15 @@ public:
int Timer; int Timer;
gdrv_bitmap8* BgBmp; gdrv_bitmap8* BgBmp;
score_msg_font_type* Font; score_msg_font_type* Font;
TTextBoxMessage* Message1; TTextBoxMessage* CurrentMessage;
TTextBoxMessage* Message2; TTextBoxMessage* PreviousMessage;
TTextBox(TPinballTable* table, int groupIndex); TTextBox(TPinballTable* table, int groupIndex);
~TTextBox() override; ~TTextBox() override;
int Message(int code, float value) override; int Message(MessageCode code, float value) override;
void Clear(); void Clear(bool lowPriorityOnly = false);
void Display(const char* text, float time); void Display(const char* text, float time, bool lowPriority = false);
void DrawImGui();
private: private:
struct LayoutResult struct LayoutResult

View File

@ -2,11 +2,12 @@
#include "TTextBoxMessage.h" #include "TTextBoxMessage.h"
#include "pb.h" #include "pb.h"
TTextBoxMessage::TTextBoxMessage(const char* text, float time) TTextBoxMessage::TTextBoxMessage(const char* text, float time, bool lowPriority)
{ {
NextMessage = nullptr; NextMessage = nullptr;
Time = time; Time = time;
EndTicks = pb::time_ticks + static_cast<int>(time * 1000.0f); EndTicks = pb::time_ticks + static_cast<int>(time * 1000.0f);
LowPriority = lowPriority;
if (text) if (text)
{ {
const auto textLen = strlen(text) + 1; const auto textLen = strlen(text) + 1;

View File

@ -6,8 +6,9 @@ public:
char* Text; char* Text;
float Time; float Time;
int EndTicks; int EndTicks;
bool LowPriority;
TTextBoxMessage(const char* text, float time); TTextBoxMessage(const char* text, float time, bool lowPriority);
~TTextBoxMessage(); ~TTextBoxMessage();
float TimeLeft() const; float TimeLeft() const;
void Refresh(float time); void Refresh(float time);

View File

@ -9,21 +9,25 @@ TTimer::TTimer(TPinballTable* table, int groupIndex) : TPinballComponent(table,
Timer = 0; Timer = 0;
} }
int TTimer::Message(int code, float value) int TTimer::Message(MessageCode code, float value)
{ {
if (code == 59) switch (code)
{ {
case MessageCode::TTimerResetTimer:
if (Timer) if (Timer)
timer::kill(Timer); timer::kill(Timer);
Timer = timer::set(value, this, TimerExpired); Timer = timer::set(value, this, TimerExpired);
} break;
else if (code == 1011 || code == 1022 || code == 1024) case MessageCode::SetTiltLock:
{ case MessageCode::GameOver:
case MessageCode::Reset:
if (Timer) if (Timer)
{ {
timer::kill(Timer); timer::kill(Timer);
Timer = 0; Timer = 0;
} }
break;
default: break;
} }
return 0; return 0;
} }
@ -33,5 +37,5 @@ void TTimer::TimerExpired(int timerId, void* caller)
{ {
auto timer = static_cast<TTimer*>(caller); auto timer = static_cast<TTimer*>(caller);
timer->Timer = 0; timer->Timer = 0;
control::handler(60, timer); control::handler(MessageCode::ControlTimerExpired, timer);
} }

View File

@ -6,7 +6,7 @@ class TTimer :
{ {
public: public:
TTimer(TPinballTable* table, int groupIndex); TTimer(TPinballTable* table, int groupIndex);
int Message(int code, float value) override; int Message(MessageCode code, float value) override;
static void TimerExpired(int timerId, void* caller); static void TimerExpired(int timerId, void* caller);
int Timer; int Timer;

View File

@ -10,16 +10,16 @@ TTripwire::TTripwire(TPinballTable* table, int groupIndex) : TRollover(table, gr
{ {
} }
void TTripwire::Collision(TBall* ball, vector_type* nextPosition, vector_type* direction, float coef, void TTripwire::Collision(TBall* ball, vector2* nextPosition, vector2* direction, float distance,
TEdgeSegment* edge) TEdgeSegment* edge)
{ {
ball->Position.X = nextPosition->X; ball->Position.X = nextPosition->X;
ball->Position.Y = nextPosition->Y; ball->Position.Y = nextPosition->Y;
ball->RayMaxDistance -= coef; ball->RayMaxDistance -= distance;
ball->not_again(edge); ball->not_again(edge);
if (!PinballTable->TiltLockFlag) if (!PinballTable->TiltLockFlag)
{ {
loader::play_sound(SoftHitSoundId); loader::play_sound(SoftHitSoundId, ball, "TTripwire");
control::handler(63, this); control::handler(MessageCode::ControlCollision, this);
} }
} }

View File

@ -7,6 +7,6 @@ class TTripwire :
public: public:
TTripwire(TPinballTable* table, int groupIndex); TTripwire(TPinballTable* table, int groupIndex);
~TTripwire() override = default; ~TTripwire() override = default;
void Collision(TBall* ball, vector_type* nextPosition, vector_type* direction, float coef, void Collision(TBall* ball, vector2* nextPosition, vector2* direction, float distance,
TEdgeSegment* edge) override; TEdgeSegment* edge) override;
}; };

View File

@ -9,14 +9,12 @@
TWall::TWall(TPinballTable* table, int groupIndex) : TCollisionComponent(table, groupIndex, true) TWall::TWall(TPinballTable* table, int groupIndex) : TCollisionComponent(table, groupIndex, true)
{ {
if (RenderSprite) if (RenderSprite)
render::sprite_set_bitmap(RenderSprite, nullptr); SpriteSet(-1);
if (ListBitmap)
BmpPtr = ListBitmap->at(0);
} }
int TWall::Message(int code, float value) int TWall::Message(MessageCode code, float value)
{ {
if (code == 1024 && Timer) if (code == MessageCode::Reset && Timer)
{ {
timer::kill(Timer); timer::kill(Timer);
TimerExpired(Timer, this); TimerExpired(Timer, this);
@ -24,34 +22,23 @@ int TWall::Message(int code, float value)
return 0; return 0;
} }
void TWall::Collision(TBall* ball, vector_type* nextPosition, vector_type* direction, float coef, TEdgeSegment* edge) void TWall::Collision(TBall* ball, vector2* nextPosition, vector2* direction, float distance, TEdgeSegment* edge)
{ {
if (DefaultCollision(ball, nextPosition, direction)) if (DefaultCollision(ball, nextPosition, direction))
{ {
if (BmpPtr) if (ListBitmap)
{ {
render::sprite_set_bitmap(RenderSprite, BmpPtr); SpriteSet(0);
Timer = timer::set(0.1f, this, TimerExpired); Timer = timer::set(0.1f, this, TimerExpired);
} }
control::handler(63, this); control::handler(MessageCode::ControlCollision, this);
} }
} }
void TWall::put_scoring(int index, int score)
{
if (index < 1)
Scores[index] = score;
}
int TWall::get_scoring(int index)
{
return index < 1 ? Scores[index] : 0;
}
void TWall::TimerExpired(int timerId, void* caller) void TWall::TimerExpired(int timerId, void* caller)
{ {
auto wall = static_cast<TWall*>(caller); auto wall = static_cast<TWall*>(caller);
render::sprite_set_bitmap(wall->RenderSprite, nullptr); wall->SpriteSet(-1);
wall->Timer = 0; wall->Timer = 0;
wall->MessageField = 0; wall->MessageField = 0;
} }

View File

@ -2,22 +2,16 @@
#include "TCollisionComponent.h" #include "TCollisionComponent.h"
struct gdrv_bitmap8;
class TWall : class TWall :
public TCollisionComponent public TCollisionComponent
{ {
public: public:
TWall(TPinballTable* table, int groupIndex); TWall(TPinballTable* table, int groupIndex);
int Message(int code, float value) override; int Message(MessageCode code, float value) override;
void Collision(TBall* ball, vector_type* nextPosition, vector_type* direction, float coef, void Collision(TBall* ball, vector2* nextPosition, vector2* direction, float distance,
TEdgeSegment* edge) override; TEdgeSegment* edge) override;
void put_scoring(int index, int score) override;
int get_scoring(int index) override;
static void TimerExpired(int timerId, void* caller); static void TimerExpired(int timerId, void* caller);
int Timer{}; int Timer{};
gdrv_bitmap8* BmpPtr{};
int Scores[1]{};
}; };

File diff suppressed because it is too large Load Diff

View File

@ -1,9 +1,14 @@
#pragma once #pragma once
class TCollisionComponent;
class TBall;
enum class MessageCode;
class TSink;
class TLight; class TLight;
class TSound; class TSound;
class TPinballTable; class TPinballTable;
class TPinballComponent; class TPinballComponent;
enum class Msg : int;
struct component_tag_base struct component_tag_base
{ {
@ -26,9 +31,8 @@ struct component_tag : component_tag_base
static_assert(std::is_base_of<TPinballComponent, T>::value, "T must inherit from TPinballComponent"); static_assert(std::is_base_of<TPinballComponent, T>::value, "T must inherit from TPinballComponent");
T* Component; T* Component;
component_tag(LPCSTR name, TPinballComponent* component): component_tag_base(name), Component(nullptr) component_tag(LPCSTR name) : component_tag_base(name), Component(nullptr)
{ {
component_tag::SetComponent(component);
} }
TPinballComponent* GetComponent() override TPinballComponent* GetComponent() override
@ -47,14 +51,14 @@ struct component_tag : component_tag_base
struct component_control struct component_control
{ {
void (* ControlFunc)(int, TPinballComponent*); void (& ControlFunc)(MessageCode, TPinballComponent*);
int ScoreCount; const unsigned int ScoreCount;
int* Scores; const int* Scores;
}; };
struct component_info struct component_info
{ {
component_tag_base* Tag; component_tag_base& Tag;
component_control Control; component_control Control;
}; };
@ -64,125 +68,128 @@ class control
public: public:
static TPinballTable* TableG; static TPinballTable* TableG;
static component_info score_components[88]; static component_info score_components[88];
static component_tag_base* simple_components[142]; static component_tag_base* simple_components[145];
static int waiting_deployment_flag; static int waiting_deployment_flag;
static bool table_unlimited_balls; static bool table_unlimited_balls, easyMode;
static int RankRcArray[9], MissionRcArray[17], mission_select_scores[17]; static Msg RankRcArray[9], MissionRcArray[17];
static component_tag_base *wormhole_tag_array1[3], *wormhole_tag_array2[3], *wormhole_tag_array3[3]; static int mission_select_scores[17];
static std::reference_wrapper<TSink*> WormholeSinkArray[3];
static std::reference_wrapper<TLight*> WormholeLightArray1[3], WormholeLightArray2[3];
static void make_links(TPinballTable* table); static void make_links(TPinballTable* table);
static void ClearLinks(); static void ClearLinks();
static TPinballComponent* make_component_link(component_tag_base* tag); static TPinballComponent* make_component_link(component_tag_base& tag);
static void handler(int code, TPinballComponent* cmp); static void handler(MessageCode code, TPinballComponent* cmp);
static void pbctrl_bdoor_controller(char key); static void pbctrl_bdoor_controller(char key);
static void table_add_extra_ball(float count); static void table_add_extra_ball(float count);
static void table_set_bonus_hold(); static void table_set_bonus_hold();
static void table_set_bonus(); static void table_set_bonus();
static void table_set_jackpot(); static void table_set_jackpot();
static void table_set_flag_lights(); static void table_set_flag_lights();
static void table_set_multiball(); static void table_set_multiball(float time);
static void table_bump_ball_sink_lock(); static void table_bump_ball_sink_lock();
static void table_set_replay(float value); static void table_set_replay(float value);
static void UnstuckBall(TBall& ball, int dt);
static bool CheckBallInControlBounds(const TBall& ball, const TCollisionComponent& cmp);
static void cheat_bump_rank(); static void cheat_bump_rank();
static bool light_on(component_tag<TLight>* tag); static int SpecialAddScore(int score, bool mission = false);
static int SpecialAddScore(int score);
static int AddRankProgress(int rank); static int AddRankProgress(int rank);
static void AdvanceWormHoleDestination(int flag); static void AdvanceWormHoleDestination(int flag);
static void FlipperRebounderControl1(int code, TPinballComponent* caller); static void FlipperRebounderControl1(MessageCode code, TPinballComponent* caller);
static void FlipperRebounderControl2(int code, TPinballComponent* caller); static void FlipperRebounderControl2(MessageCode code, TPinballComponent* caller);
static void RebounderControl(int code, TPinballComponent* caller); static void RebounderControl(MessageCode code, TPinballComponent* caller);
static void BumperControl(int code, TPinballComponent* caller); static void BumperControl(MessageCode code, TPinballComponent* caller);
static void LeftKickerControl(int code, TPinballComponent* caller); static void LeftKickerControl(MessageCode code, TPinballComponent* caller);
static void RightKickerControl(int code, TPinballComponent* caller); static void RightKickerControl(MessageCode code, TPinballComponent* caller);
static void LeftKickerGateControl(int code, TPinballComponent* caller); static void LeftKickerGateControl(MessageCode code, TPinballComponent* caller);
static void RightKickerGateControl(int code, TPinballComponent* caller); static void RightKickerGateControl(MessageCode code, TPinballComponent* caller);
static void DeploymentChuteToEscapeChuteOneWayControl(int code, TPinballComponent* caller); static void DeploymentChuteToEscapeChuteOneWayControl(MessageCode code, TPinballComponent* caller);
static void DeploymentChuteToTableOneWayControl(int code, TPinballComponent* caller); static void DeploymentChuteToTableOneWayControl(MessageCode code, TPinballComponent* caller);
static void DrainBallBlockerControl(int code, TPinballComponent* caller); static void DrainBallBlockerControl(MessageCode code, TPinballComponent* caller);
static void LaunchRampControl(int code, TPinballComponent* caller); static void LaunchRampControl(MessageCode code, TPinballComponent* caller);
static void LaunchRampHoleControl(int code, TPinballComponent* caller); static void LaunchRampHoleControl(MessageCode code, TPinballComponent* caller);
static void SpaceWarpRolloverControl(int code, TPinballComponent* caller); static void SpaceWarpRolloverControl(MessageCode code, TPinballComponent* caller);
static void ReentryLanesRolloverControl(int code, TPinballComponent* caller); static void ReentryLanesRolloverControl(MessageCode code, TPinballComponent* caller);
static void BumperGroupControl(int code, TPinballComponent* caller); static void BumperGroupControl(MessageCode code, TPinballComponent* caller);
static void LaunchLanesRolloverControl(int code, TPinballComponent* caller); static void LaunchLanesRolloverControl(MessageCode code, TPinballComponent* caller);
static void OutLaneRolloverControl(int code, TPinballComponent* caller); static void OutLaneRolloverControl(MessageCode code, TPinballComponent* caller);
static void ExtraBallLightControl(int code, TPinballComponent* caller); static void ExtraBallLightControl(MessageCode code, TPinballComponent* caller);
static void ReturnLaneRolloverControl(int code, TPinballComponent* caller); static void ReturnLaneRolloverControl(MessageCode code, TPinballComponent* caller);
static void BonusLaneRolloverControl(int code, TPinballComponent* caller); static void BonusLaneRolloverControl(MessageCode code, TPinballComponent* caller);
static void FuelRollover1Control(int code, TPinballComponent* caller); static void FuelRollover1Control(MessageCode code, TPinballComponent* caller);
static void FuelRollover2Control(int code, TPinballComponent* caller); static void FuelRollover2Control(MessageCode code, TPinballComponent* caller);
static void FuelRollover3Control(int code, TPinballComponent* caller); static void FuelRollover3Control(MessageCode code, TPinballComponent* caller);
static void FuelRollover4Control(int code, TPinballComponent* caller); static void FuelRollover4Control(MessageCode code, TPinballComponent* caller);
static void FuelRollover5Control(int code, TPinballComponent* caller); static void FuelRollover5Control(MessageCode code, TPinballComponent* caller);
static void FuelRollover6Control(int code, TPinballComponent* caller); static void FuelRollover6Control(MessageCode code, TPinballComponent* caller);
static void HyperspaceLightGroupControl(int code, TPinballComponent* caller); static void HyperspaceLightGroupControl(MessageCode code, TPinballComponent* caller);
static void WormHoleControl(int code, TPinballComponent* caller); static void WormHoleControl(MessageCode code, TPinballComponent* caller);
static void LeftFlipperControl(int code, TPinballComponent* caller); static void LeftFlipperControl(MessageCode code, TPinballComponent* caller);
static void RightFlipperControl(int code, TPinballComponent* caller); static void RightFlipperControl(MessageCode code, TPinballComponent* caller);
static void JackpotLightControl(int code, TPinballComponent* caller); static void JackpotLightControl(MessageCode code, TPinballComponent* caller);
static void BonusLightControl(int code, TPinballComponent* caller); static void BonusLightControl(MessageCode code, TPinballComponent* caller);
static void BoosterTargetControl(int code, TPinballComponent* caller); static void BoosterTargetControl(MessageCode code, TPinballComponent* caller);
static void MedalLightGroupControl(int code, TPinballComponent* caller); static void MedalLightGroupControl(MessageCode code, TPinballComponent* caller);
static void MultiplierLightGroupControl(int code, TPinballComponent* caller); static void MultiplierLightGroupControl(MessageCode code, TPinballComponent* caller);
static void FuelSpotTargetControl(int code, TPinballComponent* caller); static void FuelSpotTargetControl(MessageCode code, TPinballComponent* caller);
static void MissionSpotTargetControl(int code, TPinballComponent* caller); static void MissionSpotTargetControl(MessageCode code, TPinballComponent* caller);
static void LeftHazardSpotTargetControl(int code, TPinballComponent* caller); static void LeftHazardSpotTargetControl(MessageCode code, TPinballComponent* caller);
static void RightHazardSpotTargetControl(int code, TPinballComponent* caller); static void RightHazardSpotTargetControl(MessageCode code, TPinballComponent* caller);
static void WormHoleDestinationControl(int code, TPinballComponent* caller); static void WormHoleDestinationControl(MessageCode code, TPinballComponent* caller);
static void BlackHoleKickoutControl(int code, TPinballComponent* caller); static void BlackHoleKickoutControl(MessageCode code, TPinballComponent* caller);
static void FlagControl(int code, TPinballComponent* caller); static void FlagControl(MessageCode code, TPinballComponent* caller);
static void GravityWellKickoutControl(int code, TPinballComponent* caller); static void GravityWellKickoutControl(MessageCode code, TPinballComponent* caller);
static void SkillShotGate1Control(int code, TPinballComponent* caller); static void SkillShotGate1Control(MessageCode code, TPinballComponent* caller);
static void SkillShotGate2Control(int code, TPinballComponent* caller); static void SkillShotGate2Control(MessageCode code, TPinballComponent* caller);
static void SkillShotGate3Control(int code, TPinballComponent* caller); static void SkillShotGate3Control(MessageCode code, TPinballComponent* caller);
static void SkillShotGate4Control(int code, TPinballComponent* caller); static void SkillShotGate4Control(MessageCode code, TPinballComponent* caller);
static void SkillShotGate5Control(int code, TPinballComponent* caller); static void SkillShotGate5Control(MessageCode code, TPinballComponent* caller);
static void SkillShotGate6Control(int code, TPinballComponent* caller); static void SkillShotGate6Control(MessageCode code, TPinballComponent* caller);
static void ShootAgainLightControl(int code, TPinballComponent* caller); static void ShootAgainLightControl(MessageCode code, TPinballComponent* caller);
static void EscapeChuteSinkControl(int code, TPinballComponent* caller); static void EscapeChuteSinkControl(MessageCode code, TPinballComponent* caller);
static void MissionControl(int code, TPinballComponent* caller); static void MissionControl(MessageCode code, TPinballComponent* caller);
static void HyperspaceKickOutControl(int code, TPinballComponent* caller); static void HyperspaceKickOutControl(MessageCode code, TPinballComponent* caller);
static void PlungerControl(int code, TPinballComponent* caller); static void PlungerControl(MessageCode code, TPinballComponent* caller);
static void MedalTargetControl(int code, TPinballComponent* caller); static void MedalTargetControl(MessageCode code, TPinballComponent* caller);
static void MultiplierTargetControl(int code, TPinballComponent* caller); static void MultiplierTargetControl(MessageCode code, TPinballComponent* caller);
static void BallDrainControl(int code, TPinballComponent* caller); static void BallDrainControl(MessageCode code, TPinballComponent* caller);
static void table_control_handler(int code); static void table_control_handler(MessageCode code);
static void AlienMenaceController(int code, TPinballComponent* caller); static void AlienMenaceController(MessageCode code, TPinballComponent* caller);
static void AlienMenacePartTwoController(int code, TPinballComponent* caller); static void AlienMenacePartTwoController(MessageCode code, TPinballComponent* caller);
static void BlackHoleThreatController(int code, TPinballComponent* caller); static void BlackHoleThreatController(MessageCode code, TPinballComponent* caller);
static void BugHuntController(int code, TPinballComponent* caller); static void BugHuntController(MessageCode code, TPinballComponent* caller);
static void CosmicPlagueController(int code, TPinballComponent* caller); static void CosmicPlagueController(MessageCode code, TPinballComponent* caller);
static void CosmicPlaguePartTwoController(int code, TPinballComponent* caller); static void CosmicPlaguePartTwoController(MessageCode code, TPinballComponent* caller);
static void DoomsdayMachineController(int code, TPinballComponent* caller); static void DoomsdayMachineController(MessageCode code, TPinballComponent* caller);
static void GameoverController(int code, TPinballComponent* caller); static void GameoverController(MessageCode code, TPinballComponent* caller);
static void LaunchTrainingController(int code, TPinballComponent* caller); static void LaunchTrainingController(MessageCode code, TPinballComponent* caller);
static void MaelstromController(int code, TPinballComponent* caller); static void MaelstromController(MessageCode code, TPinballComponent* caller);
static void MaelstromPartEightController(int code, TPinballComponent* caller); static void MaelstromPartEightController(MessageCode code, TPinballComponent* caller);
static void MaelstromPartFiveController(int code, TPinballComponent* caller); static void MaelstromPartFiveController(MessageCode code, TPinballComponent* caller);
static void MaelstromPartFourController(int code, TPinballComponent* caller); static void MaelstromPartFourController(MessageCode code, TPinballComponent* caller);
static void MaelstromPartSevenController(int code, TPinballComponent* caller); static void MaelstromPartSevenController(MessageCode code, TPinballComponent* caller);
static void MaelstromPartSixController(int code, TPinballComponent* caller); static void MaelstromPartSixController(MessageCode code, TPinballComponent* caller);
static void MaelstromPartThreeController(int code, TPinballComponent* caller); static void MaelstromPartThreeController(MessageCode code, TPinballComponent* caller);
static void MaelstromPartTwoController(int code, TPinballComponent* caller); static void MaelstromPartTwoController(MessageCode code, TPinballComponent* caller);
static void PracticeMissionController(int code, TPinballComponent* caller); static void PracticeMissionController(MessageCode code, TPinballComponent* caller);
static void ReconnaissanceController(int code, TPinballComponent* caller); static void ReconnaissanceController(MessageCode code, TPinballComponent* caller);
static void ReentryTrainingController(int code, TPinballComponent* caller); static void ReentryTrainingController(MessageCode code, TPinballComponent* caller);
static void RescueMissionController(int code, TPinballComponent* caller); static void RescueMissionController(MessageCode code, TPinballComponent* caller);
static void SatelliteController(int code, TPinballComponent* caller); static void SatelliteController(MessageCode code, TPinballComponent* caller);
static void ScienceMissionController(int code, TPinballComponent* caller); static void ScienceMissionController(MessageCode code, TPinballComponent* caller);
static void SecretMissionGreenController(int code, TPinballComponent* caller); static void SecretMissionGreenController(MessageCode code, TPinballComponent* caller);
static void SecretMissionRedController(int code, TPinballComponent* caller); static void SecretMissionRedController(MessageCode code, TPinballComponent* caller);
static void SecretMissionYellowController(int code, TPinballComponent* caller); static void SecretMissionYellowController(MessageCode code, TPinballComponent* caller);
static void SelectMissionController(int code, TPinballComponent* caller); static void SelectMissionController(MessageCode code, TPinballComponent* caller);
static void SpaceRadiationController(int code, TPinballComponent* caller); static void SpaceRadiationController(MessageCode code, TPinballComponent* caller);
static void StrayCometController(int code, TPinballComponent* caller); static void StrayCometController(MessageCode code, TPinballComponent* caller);
static void TimeWarpController(int code, TPinballComponent* caller); static void TimeWarpController(MessageCode code, TPinballComponent* caller);
static void TimeWarpPartTwoController(int code, TPinballComponent* caller); static void TimeWarpPartTwoController(MessageCode code, TPinballComponent* caller);
static void UnselectMissionController(int code, TPinballComponent* caller); static void UnselectMissionController(MessageCode code, TPinballComponent* caller);
static void WaitingDeploymentController(int code, TPinballComponent* caller); static void WaitingDeploymentController(MessageCode code, TPinballComponent* caller);
private: private:
static int extraball_light_flag; static int extraball_light_flag;
}; };

View File

@ -0,0 +1,51 @@
#include "pch.h"
#include "font_selection.h"
#include "options.h"
#include "pb.h"
#include "score.h"
#include "winmain.h"
#include "translations.h"
static const char* popupName = "Font Selection";
bool font_selection::ShowDialogFlag = false;
char font_selection::DialogInputBuffer[512];
void font_selection::ShowDialog()
{
ShowDialogFlag = true;
}
void font_selection::RenderDialog()
{
if (ShowDialogFlag == true)
{
strncpy(DialogInputBuffer, options::Options.FontFileName.V.c_str(), sizeof(DialogInputBuffer));
ShowDialogFlag = false;
if (!ImGui::IsPopupOpen(popupName))
{
ImGui::OpenPopup(popupName);
}
}
bool unused_open = true;
if (ImGui::BeginPopupModal(popupName, &unused_open, ImGuiWindowFlags_AlwaysAutoResize))
{
ImGui::Text("Font file to use: ");
ImGui::SameLine();
ImGui::InputText("##Font", DialogInputBuffer, IM_ARRAYSIZE(DialogInputBuffer));
if (ImGui::Button(pb::get_rc_string(Msg::GenericOk)))
{
options::Options.FontFileName.V = DialogInputBuffer;
ImGui::CloseCurrentPopup();
winmain::Restart();
}
ImGui::SameLine();
if (ImGui::Button(pb::get_rc_string(Msg::GenericCancel)))
ImGui::CloseCurrentPopup();
ImGui::EndPopup();
}
}

View File

@ -0,0 +1,11 @@
#pragma once
class font_selection
{
public:
static void ShowDialog();
static void RenderDialog();
private:
static bool ShowDialogFlag;
static char DialogInputBuffer[512];
};

Some files were not shown because too many files have changed in this diff Show More