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
/DrMem
/Doc private
# Windows local libraries
# Windows and macOS local libraries
/Libs
#CMake generated
@ -298,3 +298,5 @@ _deps
.ninja_deps
.ninja_log
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)
set(CMAKE_CXX_STANDARD 11)
@ -19,6 +19,15 @@ if(MINGW)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -static")
endif()
if(APPLE)
set(MACOSX_RPATH)
set(CMAKE_BUILD_WITH_INSTALL_RPATH true)
set(CMAKE_INSTALL_RPATH "@executable_path/../Frameworks")
set(CMAKE_OSX_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
set(SDL2_BUILDING_LIBRARY ON)
@ -38,6 +47,8 @@ set(SOURCE_FILES
SpaceCadetPinball/control.h
SpaceCadetPinball/EmbeddedData.cpp
SpaceCadetPinball/EmbeddedData.h
SpaceCadetPinball/font_selection.cpp
SpaceCadetPinball/font_selection.h
SpaceCadetPinball/fullscrn.cpp
SpaceCadetPinball/fullscrn.h
SpaceCadetPinball/gdrv.cpp
@ -61,8 +72,6 @@ set(SOURCE_FILES
SpaceCadetPinball/pb.cpp
SpaceCadetPinball/pb.h
SpaceCadetPinball/pch.h
SpaceCadetPinball/pinball.cpp
SpaceCadetPinball/pinball.h
SpaceCadetPinball/proj.cpp
SpaceCadetPinball/proj.h
SpaceCadetPinball/render.cpp
@ -131,6 +140,8 @@ set(SOURCE_FILES
SpaceCadetPinball/TPopupTarget.h
SpaceCadetPinball/TRamp.cpp
SpaceCadetPinball/TRamp.h
SpaceCadetPinball/translations.cpp
SpaceCadetPinball/translations.h
SpaceCadetPinball/TRollover.cpp
SpaceCadetPinball/TRollover.h
SpaceCadetPinball/TSink.cpp
@ -167,15 +178,19 @@ set(SOURCE_FILES
SpaceCadetPinball/imgui_demo.cpp
SpaceCadetPinball/imgui_impl_sdl.cpp
SpaceCadetPinball/imgui_impl_sdl.h
SpaceCadetPinball/imgui_impl_sdlrenderer.h
SpaceCadetPinball/imgui_impl_sdlrenderer.cpp
SpaceCadetPinball/imstb_textedit.h
SpaceCadetPinball/imstb_rectpack.h
SpaceCadetPinball/imstb_truetype.h
SpaceCadetPinball/DebugOverlay.cpp
SpaceCadetPinball/DebugOverlay.h
)
# On Windows, include resource file with the icon
if(WIN32)
set_source_files_properties(SpaceCadetPinball/SpaceCadetPinball.rc LANGUAGE RC)
list(APPEND SOURCE_FILES SpaceCadetPinball/SpaceCadetPinball.rc)
list(APPEND SOURCE_FILES SpaceCadetPinball/SpaceCadetPinball.rc)
endif(WIN32)
add_executable(SpaceCadetPinball ${SOURCE_FILES})
@ -189,6 +204,7 @@ set_source_files_properties(
SpaceCadetPinball/imgui_tables.cpp
SpaceCadetPinball/imgui_demo.cpp
SpaceCadetPinball/imgui_impl_sdl.cpp
SpaceCadetPinball/imgui_impl_sdlrenderer.cpp
PROPERTIES SKIP_PRECOMPILE_HEADERS 1
)
@ -221,10 +237,10 @@ endif()
if(UNIX AND NOT APPLE)
include(GNUInstallDirs)
install(TARGETS "${PROJECT_NAME}" RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}")
install(FILES "/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}.desktop" DESTINATION "share/applications")
install(FILES "${CMAKE_SOURCE_DIR}/Platform/Linux/${PROJECT_NAME}.metainfo.xml" DESTINATION "share/metainfo")
foreach(S 16 32 48 128 192)
install(FILES "${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")
endforeach(S)
endif()

View File

@ -56,6 +56,19 @@
"buildCommandArgs": "",
"ctestCommandArgs": "",
"inheritEnvironments": [ "msvc_x86" ]
},
{
"name": "WSL-Clang-Debug",
"generator": "Ninja",
"configurationType": "Debug",
"buildRoot": "${projectDir}\\out\\build\\${name}",
"installRoot": "${projectDir}\\out\\install\\${name}",
"cmakeExecutable": "cmake",
"cmakeCommandArgs": "",
"buildCommandArgs": "",
"ctestCommandArgs": "",
"inheritEnvironments": [ "linux_clang_x64" ],
"wslPath": "${defaultWSLPath}"
}
]
}

View File

@ -7,5 +7,5 @@ No source ports in main repository.\
I have no way to test and maintain most of them.\
The best I can do is to add a link.
There is no 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.

View File

@ -3,7 +3,7 @@
<id>com.github.k4zmu2a.spacecadetpinball</id>
<name>Space Cadet Pinball</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="bugtracker">https://github.com/k4zmu2a/SpaceCadetPinball/issues</url>
<description>
@ -28,6 +28,35 @@
</screenshot>
</screenshots>
<releases>
<release version="2.1.0" date="2023-10-16">
<description>
<p>Feature release focusing on FT compatibility.</p>
<p>Main highlights:</p>
<ul>
<li>FT: collision system changes, table control changes, multiball, three MIDI tracks.</li>
<li>Work in progress: localization support. Depends on external font selection for most languages.</li>
<li>HW accelerated ImGui renderer.</li>
<li>Experimental stereo sound support.</li>
<li>UX improvements.</li>
<li>Bug fixes.</li>
</ul>
</description>
</release>
<release version="2.0.1" date="2021-12-29">
<description>
<p>First bug fix release of cross-platform port.</p>
<p>Main highlights:</p>
<ul>
<li>Improved Linux support: install target, icons, desktop shortcut, alternative data paths.</li>
<li>Improved game controller support.</li>
<li>High precision sleep mode for ~0 frame time jitter.</li>
<li>Integer image scaling mode.</li>
<li>3DPB &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">
<description>
<p>First release of cross-platform port.</p>
@ -63,4 +92,4 @@
<keyword>cadet</keyword>
</keywords>
<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 3DS | MaikelChan | https://github.com/MaikelChan/SpaceCadetPinball/tree/3ds |
| 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.
@ -71,6 +74,10 @@ This project is available as Flatpak on [Flathub](https://flathub.org/apps/detai
### 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.
* **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.
**Automated compilation:**
Run the `build-mac-app.sh` script from the root of the repository. The app will be available in a DMG file named `SpaceCadetPinball-<version>-mac.dmg`.
Tested with: macOS Ventura (Apple Silicon) with Xcode Command Line Tools 14 & macOS Big Sur on GitHub Runner (Intel) with XCode 13.
## Plans
* ~~Decompile original game~~
* ~~Resizable window, scaled graphics~~
* ~~Loader for high-res sprites from CADET.DAT~~
* ~~Cross-platform port using SDL2, SDL2_mixer, ImGui~~
* Misc features of Full Tilt: 3 music tracks, multiball, centered textboxes, etc.
* Maybe: Text translations
* Maybe: Android port
* Maybe x2: support for other two tables
* Table specific BL (control interactions and missions) is hardcoded, othere parts might be also patched
* Full Tilt Cadet features
* Localization support
* Maybe: Support for the other two tables - Dragon and Pirate
* Maybe: Game data editor
## On 64-bit bug that killed the game

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:
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 "gdrv.h"
#include "pb.h"
#include "pinball.h"
#include "zdrv.h"
@ -47,7 +46,7 @@ void GroupData::AddEntry(EntryData* entry)
if (srcBmp->BitmapType == BitmapTypes::Spliced)
{
// Get rid of spliced bitmap early on, to simplify render pipeline
auto bmp = new gdrv_bitmap8(srcBmp->Width, srcBmp->Height, srcBmp->Width);
auto bmp = new gdrv_bitmap8(srcBmp->Width, srcBmp->Height, true);
auto zMap = new zmap_header_type(srcBmp->Width, srcBmp->Height, srcBmp->Width);
SplitSplicedBitmap(*srcBmp, *bmp, *zMap);
@ -246,7 +245,7 @@ int DatFile::field_size(int groupIndex, FieldTypes targetEntryType)
int DatFile::record_labeled(LPCSTR 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);
if (!groupName)
@ -296,7 +295,7 @@ void DatFile::Finalize()
// PINBALL2.MID is an alternative font provided in 3DPB data
// 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");
fseek(fileHandle, 0, SEEK_END);
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>
</Expand>
</Type>
<Type Name="vector3">
<DisplayString>{{ X={X} Y={Y} Z={Z} }}</DisplayString>
</Type>
</AutoVisualizer>

View File

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

View File

@ -1,20 +1,31 @@
#pragma once
#include "maths.h"
#include "TPinballComponent.h"
struct ChannelInfo
{
int TimeStamp;
vector2 Position;
};
class Sound
{
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 Activate();
static void Deactivate();
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 void FreeSound(Mix_Chunk* wave);
static void SetChannels(int channels);
static void SetVolume(int volume);
private:
static int num_channels;
static bool enabled_flag;
static int* 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 "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[])
{
std::string cmdLine;
for (int i = 1; i < argc; i++)
{
if (i > 1)
cmdLine += " ";
cmdLine += argv[i];
}
return MainActual(cmdLine.c_str());
return winmain::WinMain(cmdLine.c_str());
}
#if _WIN32
@ -32,7 +20,7 @@ int main(int argc, char* argv[])
// Windows subsystem main
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

View File

@ -9,133 +9,189 @@
#include "proj.h"
#include "render.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{};
char ballGroupName[10]{"ball"};
TimeNow = 0.0;
RayMaxDistance = 0.0;
ActiveFlag = 1;
CollisionComp = nullptr;
EdgeCollisionCount = 0;
TimeDelta = 0.0;
FieldFlag = 1;
CollisionFlag = 0;
Speed = 0.0;
Acceleration.Y = 0.0;
Acceleration.X = 0.0;
InvAcceleration.Y = 1000000000.0;
InvAcceleration.X = 1000000000.0;
Position.X = 0.0;
Position.Y = 0.0;
Direction.Y = 0.0;
Direction.X = 0.0;
ListBitmap = new std::vector<SpriteData>();
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*/
auto groupIndex = loader::query_handle(ballGroupName);
groupIndex = loader::query_handle(ballGroupName);
if (groupIndex < 0)
{
ballGroupName[4] = '0' + fullscrn::GetResolution();
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);
for (auto index = 0; index < visualCount; ++index)
{
loader::query_visual(groupIndex, index, &visual);
if (ListBitmap)
ListBitmap->push_back(visual.Bitmap);
auto visVec = reinterpret_cast<vector_type*>(loader::query_float_attribute(groupIndex, index, 501));
auto zDepth = proj::z_distance(visVec);
ListBitmap->push_back(visual.Bitmap);
auto visVec = reinterpret_cast<vector3*>(loader::query_float_attribute(groupIndex, index, 501));
auto zDepth = proj::z_distance(*visVec);
VisualZArray[index] = zDepth;
}
RenderSprite = render::create_sprite(VisualTypes::Ball, nullptr, nullptr, 0, 0, nullptr);
PinballTable->CollisionCompOffset = Offset;
Position.Z = Offset;
RenderSprite = new render_sprite(VisualTypes::Ball, nullptr, nullptr, 0, 0, nullptr);
PinballTable->CollisionCompOffset = Radius;
Position.Z = Radius;
GroupIndex = groupIndex;
}
void TBall::Repaint()
{
int pos2D[2];
if (CollisionFlag)
{
Position.Z =
CollisionOffset.X * Position.X +
CollisionOffset.Y * Position.Y +
Offset + CollisionOffset.Z;
Radius + CollisionOffset.Z;
}
proj::xform_to_2d(&Position, pos2D);
auto zDepth = proj::z_distance(&Position);
auto pos2D = proj::xform_to_2d(Position);
auto zDepth = proj::z_distance(Position);
auto zArrPtr = VisualZArray;
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);
render::ball_set(
RenderSprite,
bmp,
zDepth,
pos2D[0] - bmp->Width / 2,
pos2D[1] - bmp->Height / 2);
SpriteSetBall(index, pos2D, zDepth);
}
void TBall::not_again(TEdgeSegment* edge)
{
if (EdgeCollisionCount < 5)
if (EdgeCollisionCount < 16)
{
Collisions[EdgeCollisionCount] = edge;
++EdgeCollisionCount;
}
else
{
for (int i = 0; i < 8; i++)
Collisions[i] = Collisions[i + 8];
Collisions[8] = edge;
EdgeCollisionCount = 9;
}
EdgeCollisionResetFlag = true;
}
bool TBall::already_hit(TEdgeSegment* edge)
bool TBall::already_hit(const TEdgeSegment& edge) const
{
for (int i = 0; i < EdgeCollisionCount; i++)
{
if (Collisions[i] == edge)
if (Collisions[i] == &edge)
return true;
}
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;
CollisionComp = nullptr;
Position.Y = 0.0;
ActiveFlag = 0;
CollisionFlag = 0;
FieldFlag = 1;
Acceleration.Y = 0.0;
Position.Z = Offset;
Acceleration.X = 0.0;
CollisionMask = 1;
Direction.Y = 0.0;
Position.Z = Radius;
Direction.X = 0.0;
Speed = 0.0;
RayMaxDistance = 0.0;
}
return 0;
}
void TBall::throw_ball(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;
ball->Acceleration = *acceleration;
CollisionComp = nullptr;
Direction = *direction;
float rnd = RandFloat();
float angle = (1.0f - (rnd + rnd)) * angleMult;
maths::RotateVector(&ball->Acceleration, angle);
maths::RotateVector(Direction, angle);
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
#include "maths.h"
#include "TPinballComponent.h"
#include "TCollisionComponent.h"
#include "TEdgeSegment.h"
class TCollisionComponent;
class TEdgeSegment;
class TBall : public TPinballComponent
class TBall : public TCollisionComponent, public TEdgeSegment
{
public :
TBall(TPinballTable* table);
TBall(TPinballTable* table, int groupIndex);
void Repaint();
void not_again(TEdgeSegment* edge);
bool already_hit(TEdgeSegment* edge);
int Message(int code, float value) override;
bool already_hit(const TEdgeSegment& edge) const;
int Message(MessageCode code, float value) override;
vector2 get_coordinates() override;
void Disable();
void throw_ball(vector3* direction, float angleMult, float speedMult1, float speedMult2);
void place_in_grid(RectF* aabb) override {}
void EdgeCollision(TBall* ball, float distance) override;
float FindCollisionDistance(const ray_type& ray) override;
static void throw_ball(TBall* ball, struct vector_type* acceleration, float angleMult, float speedMult1,
float speedMult2);
vector_type Position{};
vector_type Acceleration{};
vector3 Position{};
vector3 PrevPosition{};
vector3 Direction{};
float Speed;
float RayMaxDistance;
float TimeDelta;
float TimeNow;
vector_type InvAcceleration{};
vector_type RampFieldForce{};
vector2 RampFieldForce{};
TCollisionComponent* CollisionComp;
int FieldFlag;
TEdgeSegment* Collisions[5]{};
int CollisionMask;
TEdgeSegment* Collisions[16]{};
int EdgeCollisionCount;
vector_type CollisionOffset{};
bool EdgeCollisionResetFlag{};
vector3 CollisionOffset{};
int CollisionFlag;
float Offset;
float Radius;
bool HasGroupFlag;
int StuckCounter = 0;
int LastActiveTime{};
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);
SoundIndex4 = visual.SoundIndex4;
SoundIndex3 = visual.SoundIndex3;
TurnOnMsgValue = 55;
TurnOffMsgValue = 5;
InitialDuration = 55;
ExtendedDuration = 5;
Threshold = 1000000000.0f;
Timer = 0;
MessageField = 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)
{
case 1011:
case 1020:
case 1024:
case 51:
case MessageCode::SetTiltLock:
case MessageCode::PlayerChanged:
case MessageCode::Reset:
case MessageCode::TBlockerDisable:
if (Timer)
{
timer::kill(Timer);
@ -38,29 +38,29 @@ int TBlocker::Message(int code, float value)
}
MessageField = 0;
ActiveFlag = 0;
render::sprite_set_bitmap(RenderSprite, nullptr);
if (code == 51)
loader::play_sound(SoundIndex3);
return 0;
case 52:
ActiveFlag = 1;
loader::play_sound(SoundIndex4);
render::sprite_set_bitmap(RenderSprite, ListBitmap->at(0));
SpriteSet(-1);
if (code == MessageCode::TBlockerDisable)
loader::play_sound(SoundIndex3, this, "TBlocker1");
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;
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;
}
@ -68,5 +68,5 @@ void TBlocker::TimerExpired(int timerId, void* caller)
{
auto blocker = static_cast<TBlocker*>(caller);
blocker->Timer = 0;
control::handler(60, blocker);
control::handler(MessageCode::ControlTimerExpired, blocker);
}

View File

@ -6,12 +6,12 @@ class TBlocker :
{
public:
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);
int TurnOnMsgValue;
int TurnOffMsgValue;
int InitialDuration;
int ExtendedDuration;
int Timer;
int SoundIndex4;
int SoundIndex3;

View File

@ -21,11 +21,11 @@ TBumper::TBumper(TPinballTable* table, int groupIndex) : TCollisionComponent(tab
OriginalThreshold = Threshold;
}
int TBumper::Message(int code, float value)
int TBumper::Message(MessageCode code, float value)
{
switch (code)
{
case 11:
case MessageCode::TBumperSetBmpIndex:
{
auto nextBmp = static_cast<int>(floor(value));
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)
loader::play_sound(SoundIndex4);
loader::play_sound(SoundIndex4, this, "TBumper1");
if (nextBmp < BmpIndex)
loader::play_sound(SoundIndex3);
loader::play_sound(SoundIndex3, this, "TBumper2");
BmpIndex = nextBmp;
Fire();
control::handler(11, this);
control::handler(MessageCode::TBumperSetBmpIndex, this);
}
break;
}
case 12:
case MessageCode::TBumperIncBmpIndex:
{
auto nextBmp = BmpIndex + 1;
auto maxBmp = static_cast<int>(ListBitmap->size()) - 1;
if (2 * nextBmp > maxBmp)
nextBmp = maxBmp / 2;
TBumper::Message(11, static_cast<float>(nextBmp));
TBumper::Message(MessageCode::TBumperSetBmpIndex, static_cast<float>(nextBmp));
break;
}
case 13:
case MessageCode::TBumperDecBmpIndex:
{
auto nextBmp = BmpIndex - 1;
if (nextBmp < 0)
nextBmp = 0;
TBumper::Message(11, static_cast<float>(nextBmp));
TBumper::Message(MessageCode::TBumperSetBmpIndex, static_cast<float>(nextBmp));
break;
}
case 1020:
case MessageCode::PlayerChanged:
{
auto playerPtr = &PlayerData[PinballTable->CurrentPlayer];
playerPtr->BmpIndex = BmpIndex;
@ -71,10 +71,10 @@ int TBumper::Message(int code, float value)
playerPtr = &PlayerData[static_cast<int>(floor(value))];
BmpIndex = playerPtr->BmpIndex;
MessageField = playerPtr->MessageField;
TBumper::Message(11, static_cast<float>(BmpIndex));
TBumper::Message(MessageCode::TBumperSetBmpIndex, static_cast<float>(BmpIndex));
break;
}
case 1024:
case MessageCode::Reset:
{
if (Timer)
{
@ -83,12 +83,10 @@ int TBumper::Message(int code, float value)
}
BmpIndex = 0;
MessageField = 0;
auto playerPtr = PlayerData;
for (auto index = 0; index < PinballTable->PlayerCount; ++index)
for (auto& playerPtr : PlayerData)
{
playerPtr->BmpIndex = 0;
playerPtr->MessageField = 0;
++playerPtr;
playerPtr.BmpIndex = 0;
playerPtr.MessageField = 0;
}
TimerExpired(0, this);
break;
@ -100,53 +98,26 @@ int TBumper::Message(int code, float value)
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))
{
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)
{
auto bump = static_cast<TBumper*>(caller);
auto bmp = bump->ListBitmap->at(bump->BmpIndex * 2);
auto zMap = bump->ListZMap->at(bump->BmpIndex * 2);
bump->SpriteSet(bump->BmpIndex * 2);
bump->Timer = 0;
render::sprite_set(
bump->RenderSprite,
bmp,
zMap,
bmp->XPosition - bump->PinballTable->XOffset,
bmp->YPosition - bump->PinballTable->YOffset);
bump->Threshold = bump->OriginalThreshold;
}
void TBumper::Fire()
{
int bmpIndex = 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);
SpriteSet(2 * BmpIndex + 1);
Timer = timer::set(TimerTime, this, TimerExpired);
Threshold = 1000000000.0;
}

View File

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

View File

@ -5,31 +5,40 @@
#include "TCollisionComponent.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)
{
Circle.RadiusSq = radius * radius;
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.Y = coef * ball->Acceleration.Y + ball->Position.Y;
nextPosition.X = distance * ball->Direction.X + ball->Position.X;
nextPosition.Y = distance * ball->Direction.Y + ball->Position.Y;
direction.X = nextPosition.X - Circle.Center.X;
direction.Y = nextPosition.Y - Circle.Center.Y;
maths::normalize_2d(&direction);
CollisionComponent->Collision(ball, &nextPosition, &direction, coef, this);
maths::normalize_2d(direction);
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);
}

View File

@ -8,9 +8,9 @@ class TCircle :
public:
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 FindCollisionDistance(ray_type* ray) override;
void EdgeCollision(TBall* ball, float coef) override;
void place_in_grid() override;
float FindCollisionDistance(const ray_type& ray) override;
void EdgeCollision(TBall* ball, float distance) override;
void place_in_grid(RectF* aabb) override;
};

View File

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

View File

@ -1,7 +1,8 @@
#pragma once
#include "maths.h"
#include "TPinballComponent.h"
struct vector_type;
struct vector2;
class TEdgeSegment;
class TBall;
@ -15,12 +16,13 @@ public:
float Threshold;
int SoftHitSoundId;
int HardHitSoundId;
RectF AABB;
TCollisionComponent(TPinballTable* table, int groupIndex, bool createWall);
~TCollisionComponent() override;
void port_draw() override;
virtual void Collision(TBall* ball, vector_type* nextPosition, vector_type* direction, float coef,
virtual void Collision(TBall* ball, vector2* nextPosition, vector2* direction, float distance,
TEdgeSegment* edge);
virtual int FieldEffect(TBall* ball, vector_type* vecDst);
int DefaultCollision(TBall* ball, vector_type* nextPosition, vector_type* direction);
virtual int FieldEffect(TBall* ball, vector2* vecDst);
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)
{
@ -45,7 +45,8 @@ int TComponentGroup::Message(int code, float value)
if (value > 0.0f)
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)
{
@ -59,5 +60,5 @@ void TComponentGroup::NotifyTimerExpired(int timerId, void* caller)
{
auto compGroup = static_cast<TComponentGroup*>(caller);
compGroup->Timer = 0;
control::handler(61, compGroup);
control::handler(MessageCode::ControlNotifyTimerExpired, compGroup);
}

View File

@ -8,7 +8,7 @@ class TComponentGroup :
public:
TComponentGroup(TPinballTable* table, int groupIndex);
~TComponentGroup() override;
int Message(int code, float value) override;
int Message(MessageCode code, float value) override;
static void NotifyTimerExpired(int timerId, void* caller);
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);
}
int TDemo::Message(int code, float value)
int TDemo::Message(MessageCode code, float value)
{
switch (code)
{
case 1014:
case MessageCode::NewGame:
if (RestartGameTimer)
timer::kill(RestartGameTimer);
RestartGameTimer = 0;
break;
case 1022:
case MessageCode::GameOver:
if (RestartGameTimer)
timer::kill(RestartGameTimer);
RestartGameTimer = 0;
if (ActiveFlag != 0)
RestartGameTimer = timer::set(5.0, this, NewGameRestartTimer);
break;
case 1024:
case MessageCode::Reset:
if (FlipLeftTimer)
timer::kill(FlipLeftTimer);
FlipLeftTimer = 0;
@ -93,12 +93,12 @@ int TDemo::Message(int code, float value)
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->Position.X = nextPosition->X;
ball->Position.Y = nextPosition->Y;
ball->RayMaxDistance -= coef;
ball->RayMaxDistance -= distance;
switch (reinterpret_cast<size_t>(edge->WallValue))
{
@ -125,7 +125,7 @@ void TDemo::Collision(TBall* ball, vector_type* nextPosition, vector_type* direc
case 1404:
if (!PlungerFlag)
{
PinballTable->Message(1004, ball->TimeNow);
PinballTable->Message(MessageCode::PlungerInputPressed, 0);
float time = RandFloat() + 2.0f;
PlungerFlag = timer::set(time, this, PlungerRelease);
}
@ -139,14 +139,14 @@ void TDemo::PlungerRelease(int timerId, void* caller)
{
auto demo = static_cast<TDemo*>(caller);
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)
{
auto demo = static_cast<TDemo*>(caller);
if (demo->FlipRightFlag)
demo->PinballTable->Message(1003, pb::time_next);
demo->PinballTable->Message(MessageCode::RightFlipperInputReleased, pb::time_next);
demo->FlipRightFlag = 0;
}
@ -154,7 +154,7 @@ void TDemo::UnFlipLeft(int timerId, void* caller)
{
auto demo = static_cast<TDemo*>(caller);
if (demo->FlipLeftFlag)
demo->PinballTable->Message(1001, pb::time_next);
demo->PinballTable->Message(MessageCode::LeftFlipperInputReleased, pb::time_next);
demo->FlipLeftFlag = 0;
}
@ -168,7 +168,7 @@ void TDemo::FlipRight(int timerId, void* caller)
timer::kill(demo->FlipRightTimer);
demo->FlipRightTimer = 0;
}
demo->PinballTable->Message(1002, pb::time_next);
demo->PinballTable->Message(MessageCode::RightFlipperInputPressed, pb::time_next);
demo->FlipRightFlag = 1;
float time = demo->UnFlipTimerTime1 + demo->UnFlipTimerTime2 - RandFloat() *
(demo->UnFlipTimerTime2 + demo->UnFlipTimerTime2);
@ -186,7 +186,7 @@ void TDemo::FlipLeft(int timerId, void* caller)
timer::kill(demo->FlipLeftTimer);
demo->FlipLeftTimer = 0;
}
demo->PinballTable->Message(1000, pb::time_next);
demo->PinballTable->Message(MessageCode::LeftFlipperInputPressed, pb::time_next);
demo->FlipLeftFlag = 1;
float time = demo->UnFlipTimerTime1 + demo->UnFlipTimerTime2 - RandFloat() *
(demo->UnFlipTimerTime2 + demo->UnFlipTimerTime2);
@ -197,7 +197,7 @@ void TDemo::FlipLeft(int timerId, void* caller)
void TDemo::NewGameRestartTimer(int timerId, void* caller)
{
auto demo = static_cast<TDemo*>(caller);
pb::replay_level(1);
demo->PinballTable->Message(1014, static_cast<float>(demo->PinballTable->PlayerCount));
pb::replay_level(true);
demo->PinballTable->Message(MessageCode::NewGame, static_cast<float>(demo->PinballTable->PlayerCount));
demo->RestartGameTimer = 0;
}

View File

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

View File

@ -14,30 +14,35 @@ TDrain::TDrain(TPinballTable* table, int groupIndex) : TCollisionComponent(table
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)
{
timer::kill(Timer);
Timer = 0;
}
PinballTable->BallInSink = 0;
PinballTable->BallInDrainFlag = 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);
PinballTable->BallInSink = 1;
Timer = timer::set(TimerTime, this, TimerCallback);
control::handler(63, this);
ball->Disable();
--PinballTable->MultiballCount;
if (PinballTable->MultiballCount <= 0)
{
PinballTable->MultiballCount = 0;
PinballTable->BallInDrainFlag = 1;
Timer = timer::set(TimerTime, this, TimerCallback);
}
control::handler(MessageCode::ControlCollision, this);
}
void TDrain::TimerCallback(int timerId, void* caller)
{
auto drain = static_cast<TDrain*>(caller);
control::handler(60, drain);
control::handler(MessageCode::ControlTimerExpired, drain);
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -6,6 +6,7 @@
#include "loader.h"
#include "pb.h"
#include "render.h"
#include "TBall.h"
#include "TFlipperEdge.h"
#include "timer.h"
#include "TPinballTable.h"
@ -18,22 +19,14 @@ TFlipper::TFlipper(TPinballTable* table, int groupIndex) : TCollisionComponent(t
HardHitSoundId = visual.SoundIndex4;
SoftHitSoundId = visual.SoundIndex3;
Elasticity = visual.Elasticity;
Timer = 0;
Smoothness = visual.Smoothness;
auto collMult = *loader::query_float_attribute(groupIndex, 0, 803);
auto retractTime = *loader::query_float_attribute(groupIndex, 0, 805);
auto extendTime = *loader::query_float_attribute(groupIndex, 0, 804);
/*Full tilt hack: different flipper speed*/
if (pb::FullTiltMode)
{
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 vecT2 = reinterpret_cast<vector3*>(loader::query_float_attribute(groupIndex, 0, 802));
auto vecT1 = reinterpret_cast<vector3*>(loader::query_float_attribute(groupIndex, 0, 801));
auto origin = reinterpret_cast<vector3*>(loader::query_float_attribute(groupIndex, 0, 800));
auto flipperEdge = new TFlipperEdge(
this,
&ActiveFlag,
@ -47,79 +40,67 @@ TFlipper::TFlipper(TPinballTable* table, int groupIndex) : TCollisionComponent(t
collMult,
Elasticity,
Smoothness);
flipperEdge->place_in_grid(&AABB);
FlipperEdge = flipperEdge;
if (flipperEdge)
{
ExtendAnimationFrameTime = flipperEdge->ExtendTime / static_cast<float>(ListBitmap->size() - 1);
RetractAnimationFrameTime = flipperEdge->RetractTime / static_cast<float>(ListBitmap->size() - 1);
}
BmpIndex = 0;
InputTime = 0.0;
if (table)
table->FlipperList.push_back(this);
}
TFlipper::~TFlipper()
{
delete FlipperEdge;
if (PinballTable)
{
auto& flippers = PinballTable->FlipperList;
auto position = std::find(flippers.begin(), flippers.end(), this);
if (position != flippers.end())
flippers.erase(position);
}
}
int TFlipper::Message(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;
int command = code;
if (code == 1)
case MessageCode::TFlipperExtend:
case MessageCode::TFlipperRetract:
case MessageCode::Resume:
case MessageCode::LooseFocus:
case MessageCode::SetTiltLock:
case MessageCode::GameOver:
if (code == MessageCode::TFlipperExtend)
{
control::handler(1, this);
TimerTime = ExtendAnimationFrameTime;
loader::play_sound(HardHitSoundId);
control::handler(MessageCode::TFlipperExtend, this);
loader::play_sound(HardHitSoundId, this, "TFlipper1");
}
else if (code == 2)
else if (code == MessageCode::TFlipperRetract)
{
TimerTime = RetractAnimationFrameTime;
loader::play_sound(SoftHitSoundId);
loader::play_sound(SoftHitSoundId, this, "TFlipper2");
}
else
{
// Retract for all non-input messages
command = 2;
TimerTime = RetractAnimationFrameTime;
code = MessageCode::TFlipperRetract;
}
MessageField = FlipperEdge->SetMotion(code);
break;
case MessageCode::PlayerChanged:
case MessageCode::Reset:
if (MessageField)
{
// Message arrived before animation is finished
auto inputDt = value - FlipperEdge->InputTime;
timerTime = inputDt - floor(inputDt / TimerTime) * TimerTime;
if (timerTime < 0.0f)
timerTime = 0.0;
FlipperEdge->CurrentAngle = 0;
FlipperEdge->set_control_points(0);
MessageField = 0;
FlipperEdge->SetMotion(MessageCode::Reset);
UpdateSprite();
}
else
{
timerTime = TimerTime;
}
MessageField = command;
InputTime = value;
if (Timer)
timer::kill(Timer);
Timer = timer::set(timerTime, this, TimerExpired);
FlipperEdge->SetMotion(command, value);
break;
default: break;
}
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;
}
@ -128,57 +109,95 @@ void TFlipper::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);
int bmpCountSub1 = flip->ListBitmap->size() - 1;
auto bmpCountSub1 = static_cast<int>(ListBitmap->size()) - 1;
auto newBmpIndex = static_cast<int>(floor(FlipperEdge->CurrentAngle / FlipperEdge->AngleMax * bmpCountSub1 + 0.5f));
newBmpIndex = Clamp(newBmpIndex, 0, bmpCountSub1);
if (BmpIndex == newBmpIndex)
return;
auto newBmpIndex = static_cast<int>(floor((pb::time_now - flip->InputTime) / flip->TimerTime));
if (newBmpIndex > bmpCountSub1)
newBmpIndex = bmpCountSub1;
if (newBmpIndex < 0)
newBmpIndex = 0;
BmpIndex = newBmpIndex;
SpriteSet(BmpIndex);
}
bool bmpIndexOutOfBounds = false;
if (flip->MessageField == 1)
int TFlipper::GetFlipperStepAngle(float dt, float* dst) const
{
if (!MessageField)
return 0;
auto deltaAngle = FlipperEdge->flipper_angle_delta(dt);
auto step = std::fabs(std::ceil(FlipperEdge->DistanceDiv * deltaAngle * FlipperEdge->InvT1Radius));
if (step > 3.0f)
step = 3.0f;
if (step >= 2.0f)
{
flip->BmpIndex = newBmpIndex;
if (flip->BmpIndex >= bmpCountSub1)
{
flip->BmpIndex = bmpCountSub1;
bmpIndexOutOfBounds = true;
}
*dst = deltaAngle / step;
return static_cast<int>(step);
}
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 (flip->BmpIndex <= 0)
if ((FlipperEdge->CollisionGroup & ball->CollisionMask) != 0 &&
FlipperEdge->YMax >= ball->Position.Y && FlipperEdge->YMin <= ball->Position.Y &&
FlipperEdge->XMax >= ball->Position.X && FlipperEdge->XMin <= ball->Position.X)
{
flip->BmpIndex = 0;
bmpIndexOutOfBounds = true;
if (FlipperEdge->ControlPointDirtyFlag)
FlipperEdge->set_control_points(FlipperEdge->CurrentAngle);
ray.CollisionMask = ball->CollisionMask;
ray.Origin = ball->Position;
float sin, cos;
auto ballPosRot = ray.Origin;
maths::SinCos(-deltaAngle, sin, cos);
maths::RotatePt(ballPosRot, sin, cos, FlipperEdge->RotOrigin);
ray.Direction.X = ballPosRot.X - ray.Origin.X;
ray.Direction.Y = ballPosRot.Y - ray.Origin.Y;
ray.MaxDistance = maths::normalize_2d(ray.Direction);
auto distance = maths::distance_to_flipper(FlipperEdge, ray, rayDst);
if (distance < 1e9f)
{
FlipperEdge->NextBallPosition = ball->Position;
FlipperEdge->CollisionDirection = rayDst.Direction;
FlipperEdge->EdgeCollision(ball, distance);
collisionFlag = true;
}
}
}
if (bmpIndexOutOfBounds)
if (collisionFlag)
{
flip->MessageField = 0;
flip->Timer = 0;
auto angleAdvance = deltaAngle / (std::fabs(FlipperEdge->MoveSpeed) * 5.0f);
FlipperEdge->CurrentAngle -= angleAdvance;
FlipperEdge->AngleRemainder += std::fabs(angleAdvance);
}
else
{
flip->Timer = timer::set(flip->TimerTime, flip, TimerExpired);
FlipperEdge->CurrentAngle += deltaAngle;
FlipperEdge->AngleRemainder -= std::fabs(deltaAngle);
}
auto bmp = flip->ListBitmap->at(flip->BmpIndex);
auto zMap = flip->ListZMap->at(flip->BmpIndex);
render::sprite_set(
flip->RenderSprite,
bmp,
zMap,
bmp->XPosition - flip->PinballTable->XOffset,
bmp->YPosition - flip->PinballTable->YOffset);
if (FlipperEdge->AngleRemainder <= 0.0001f)
{
FlipperEdge->CurrentAngle = FlipperEdge->AngleDst;
FlipperEdge->FlipperFlag = MessageCode::TFlipperNull;
MessageField = 0;
}
FlipperEdge->ControlPointDirtyFlag = true;
}

View File

@ -9,18 +9,14 @@ class TFlipper :
public:
TFlipper(TPinballTable* table, int groupIndex);
~TFlipper() override;
int Message(int code, float value) override;
int Message(MessageCode code, float value) 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;
static void TimerExpired(int timerId, void* caller);
void UpdateSprite();
int GetFlipperStepAngle(float dt, float* dst) const;
void FlipperCollision(float deltaAngle);
int BmpIndex;
TFlipperEdge* FlipperEdge;
int Timer;
float ExtendAnimationFrameTime{};
float RetractAnimationFrameTime{};
float TimerTime{};
float InputTime;
};

View File

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

View File

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

View File

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

View File

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

View File

@ -18,7 +18,7 @@ THole::THole(TPinballTable* table, int groupIndex) : TCollisionComponent(table,
Unknown4 = 0.050000001f;
MessageField = 0;
Timer = 0;
BallCapturedFlag = 0;
BallCapturedFlag = false;
Unknown3 = loader::query_float_attribute(groupIndex, 0, 407, 0.25f);
GravityMult = loader::query_float_attribute(groupIndex, 0, 701, 0.2f);
GravityPull = *loader::query_float_attribute(groupIndex, 0, 305);
@ -31,56 +31,53 @@ THole::THole(TPinballTable* table, int groupIndex) : TCollisionComponent(table,
Circle.RadiusSq = 0.001f;
auto tCircle = new TCircle(this, &ActiveFlag, visual.CollisionGroup,
reinterpret_cast<vector_type*>(visual.FloatArr),
reinterpret_cast<vector3*>(visual.FloatArr),
Circle.RadiusSq);
if (tCircle)
{
tCircle->place_in_grid();
EdgeList.push_back(tCircle);
}
tCircle->place_in_grid(&AABB);
EdgeList.push_back(tCircle);
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)
FieldFlag = 1;
CollisionMask = 1 << CollisionMask;
Circle.RadiusSq = visual.FloatArr[2] * visual.FloatArr[2];
circle.RadiusSq = Circle.RadiusSq;
circle.Center.X = Circle.Center.X;
circle.Center.Y = Circle.Center.Y;
circle.Center.Z = Circle.Center.Z;
Field.Flag2Ptr = &ActiveFlag;
Field.ActiveFlag = &ActiveFlag;
Field.CollisionComp = this;
Field.Mask = visual.CollisionGroup;
Field.CollisionGroup = visual.CollisionGroup;
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)
timer::kill(Timer);
Timer = 0;
BallCapturedSecondStage = 1;
BallCapturedSecondStage = true;
}
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)
{
BallCapturedSecondStage = 0;
Threshold = 1000000000.0;
BallCapturedFlag = 1;
BallCapturedSecondStage = false;
Threshold = 1e9f;
BallCapturedFlag = true;
ball->CollisionComp = this;
ball->Position.X = Circle.Center.X;
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.
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)
{
loader::play_sound(HardHitSoundId);
control::handler(57, this);
loader::play_sound(HardHitSoundId, ball, "THole1");
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;
vector_type direction{};
vector2 direction{};
if (BallCapturedFlag)
{
if (BallCapturedSecondStage)
{
ball->Acceleration.Z -= PinballTable->GravityDirVectMult * ball->TimeDelta * GravityMult;
ball->Position.Z += ball->Acceleration.Z;
ball->Direction.Z -= PinballTable->GravityDirVectMult * ball->TimeDelta * GravityMult;
ball->Position.Z += ball->Direction.Z;
if (ball->Position.Z <= ZSetValue)
{
BallCapturedFlag = 0;
BallCapturedSecondStage = 0;
BallCapturedFlag = false;
BallCapturedSecondStage = false;
ball->Position.Z = ZSetValue;
ball->Acceleration.Z = 0.0;
ball->FieldFlag = FieldFlag;
ball->Acceleration.Y = 0.0;
ball->CollisionMask = CollisionMask;
ball->CollisionComp = nullptr;
ball->Acceleration.X = 0.0;
ball->Speed = 0.0;
loader::play_sound(SoftHitSoundId);
control::handler(58, this);
ball->Direction = {};
ball->Speed = 0.0f;
loader::play_sound(SoftHitSoundId, ball, "THole2");
control::handler(MessageCode::ControlBallReleased, this);
}
}
result = 0;
@ -128,9 +127,9 @@ int THole::FieldEffect(TBall* ball, vector_type* vecDst)
direction.Y = Circle.Center.Y - ball->Position.Y;
if (direction.X * direction.X + direction.Y * direction.Y <= Circle.RadiusSq)
{
maths::normalize_2d(&direction);
vecDst->X = direction.X * GravityPull - ball->Acceleration.X * ball->Speed;
vecDst->Y = direction.Y * GravityPull - ball->Acceleration.Y * ball->Speed;
maths::normalize_2d(direction);
vecDst->X = direction.X * GravityPull - ball->Direction.X * ball->Speed;
vecDst->Y = direction.Y * GravityPull - ball->Direction.Y * ball->Speed;
result = 1;
}
else
@ -145,5 +144,5 @@ void THole::TimerExpired(int timerId, void* caller)
{
auto hole = static_cast<THole*>(caller);
hole->Timer = 0;
hole->BallCapturedSecondStage = 1;
hole->BallCapturedSecondStage = true;
}

View File

@ -8,21 +8,21 @@ class THole :
{
public:
THole(TPinballTable* table, int groupIndex);
int Message(int code, float value) override;
void Collision(TBall* ball, vector_type* nextPosition, vector_type* direction, float coef,
int Message(MessageCode code, float value) override;
void Collision(TBall* ball, vector2* nextPosition, vector2* direction, float distance,
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);
int BallCapturedFlag;
int BallCapturedSecondStage{};
bool BallCapturedFlag{};
bool BallCapturedSecondStage{};
int Timer;
float Unknown3;
float Unknown4;
float GravityMult;
float ZSetValue;
int FieldFlag;
int CollisionMask;
float GravityPull;
circle_type Circle{};
field_effect_type Field{};

View File

@ -19,13 +19,12 @@ TKickback::TKickback(TPinballTable* table, int groupIndex): TCollisionComponent(
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);
if (ListBitmap)
render::sprite_set_bitmap(RenderSprite, nullptr);
SpriteSet(-1);
Timer = 0;
KickActiveFlag = 0;
Threshold = 1000000000.0;
@ -33,7 +32,7 @@ int TKickback::Message(int code, float value)
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)
{
if (PinballTable->TiltLockFlag)
@ -62,33 +61,13 @@ void TKickback::TimerExpired(int timerId, void* caller)
{
kick->Threshold = 0.0;
kick->Timer = timer::set(kick->TimerTime2, kick, TimerExpired);
loader::play_sound(kick->HardHitSoundId);
if (kick->ListBitmap)
{
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);
}
loader::play_sound(kick->HardHitSoundId, kick, "TKickback");
kick->SpriteSet(1);
}
else
{
if (kick->ListBitmap)
{
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->SpriteSet(0);
kick->Timer = 0;
control::handler(60, kick);
control::handler(MessageCode::ControlTimerExpired, kick);
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,8 +1,12 @@
#include "pch.h"
#include "TPinballComponent.h"
#include "control.h"
#include "loader.h"
#include "proj.h"
#include "render.h"
#include "TPinballTable.h"
#include "TTableLayer.h"
TPinballComponent::TPinballComponent(TPinballTable* table, int groupIndex, bool loadVisuals)
{
@ -14,64 +18,69 @@ TPinballComponent::TPinballComponent(TPinballTable* table, int groupIndex, bool
PinballTable = table;
RenderSprite = nullptr;
ListBitmap = nullptr;
ListZMap = nullptr;
GroupName = nullptr;
Control = nullptr;
VisualPosNormX= -1.0f;
VisualPosNormY = -1.0f;
GroupIndex = groupIndex;
if (table)
table->ComponentList.push_back(this);
if (groupIndex >= 0)
GroupName = loader::query_name(groupIndex);
if (loadVisuals && groupIndex >= 0)
{
int visualCount = loader::query_visual_states(groupIndex);
for (int index = 0; index < visualCount; ++index)
{
loader::query_visual(groupIndex, index, &visual);
if (visual.Bitmap)
if (visual.Bitmap.Bmp)
{
assertm(visual.Bitmap.ZMap, "Bitmap/zMap pairing is mandatory");
if (!ListBitmap)
ListBitmap = new std::vector<gdrv_bitmap8*>();
if (ListBitmap)
ListBitmap->push_back(visual.Bitmap);
}
if (visual.ZMap)
{
if (!ListZMap)
ListZMap = new std::vector<zmap_header_type*>();
if (ListZMap)
ListZMap->push_back(visual.ZMap);
ListBitmap = new std::vector<SpriteData>();
ListBitmap->push_back(visual.Bitmap);
}
}
auto zMap = ListZMap ? ListZMap->at(0) : nullptr;
if (ListBitmap)
{
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.YPosition = rootBmp->YPosition - table->YOffset;
bmp1Rect.Width = rootBmp->Width;
bmp1Rect.Height = rootBmp->Height;
for (auto index = 1u; index < ListBitmap->size(); index++)
{
auto bmp = ListBitmap->at(index);
auto bmp = ListBitmap->at(index).Bmp;
tmpRect.XPosition = bmp->XPosition - table->XOffset;
tmpRect.YPosition = bmp->YPosition - table->YOffset;
tmpRect.Width = bmp->Width;
tmpRect.Height = bmp->Height;
maths::enclosing_box(&bmp1Rect, &tmpRect, &bmp1Rect);
maths::enclosing_box(bmp1Rect, tmpRect, bmp1Rect);
}
RenderSprite = render::create_sprite(
visualCount > 0 ? VisualTypes::Sprite : VisualTypes::None,
RenderSprite = new render_sprite(
VisualTypes::Sprite,
rootBmp,
zMap,
rootSprite.ZMap,
rootBmp->XPosition - table->XOffset,
rootBmp->YPosition - table->YOffset,
&bmp1Rect);
// Sound position = center of root visual, reverse-projected, normalized.
auto& rect = RenderSprite->BmpRect;
vector2i pos2D{ rect.XPosition + rect.Width / 2, rect.YPosition + rect.Height / 2 };
auto pos3D = proj::ReverseXForm(pos2D);
auto posNorm = TTableLayer::edge_manager->NormalizeBox(pos3D);
VisualPosNormX = posNorm.X;
VisualPosNormY = posNorm.Y;
}
}
GroupIndex = groupIndex;
}
@ -87,14 +96,13 @@ TPinballComponent::~TPinballComponent()
}
delete ListBitmap;
delete ListZMap;
}
int TPinballComponent::Message(int code, float value)
int TPinballComponent::Message(MessageCode code, float value)
{
MessageField = code;
if (code == 1024)
MessageField = static_cast<int>(code);
if (code == MessageCode::Reset)
MessageField = 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
struct SpriteData;
struct vector2i;
struct zmap_header_type;
struct gdrv_bitmap8;
struct render_sprite_type_struct;
struct render_sprite;
struct component_control;
struct vector2;
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,
LightActiveCount = 37,
LightTotalCount = 38,
LightSetMessageField = 23,
};
class TPinballComponent
@ -19,10 +136,12 @@ class TPinballComponent
public:
TPinballComponent(TPinballTable* table, int groupIndex, bool loadVisuals);
virtual ~TPinballComponent();
virtual int Message(int code, float value);
virtual int Message(MessageCode code, float value);
virtual void port_draw();
virtual void put_scoring(int index, int score);
virtual int get_scoring(int index);
int get_scoring(unsigned int index) const;
virtual vector2 get_coordinates();
void SpriteSet(int index) const;
void SpriteSetBall(int index, vector2i pos, float depth) const;
char UnusedBaseFlag;
char ActiveFlag;
@ -30,8 +149,10 @@ public:
char* GroupName;
component_control* Control;
int GroupIndex;
render_sprite_type_struct* RenderSprite;
render_sprite* RenderSprite;
TPinballTable* PinballTable;
std::vector<gdrv_bitmap8*>* ListBitmap;
std::vector<zmap_header_type*>* ListZMap;
std::vector<SpriteData>* ListBitmap;
private:
float VisualPosNormX;
float VisualPosNormY;
};

View File

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

View File

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

View File

@ -23,131 +23,175 @@ TPlunger::TPlunger(TPinballTable* table, int groupIndex) : TCollisionComponent(t
SoundIndexP2 = visual.SoundIndex3;
HardHitSoundId = visual.Kicker.HardHitSoundId;
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;
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);
table->PlungerPositionX = floatArr[0];
table->PlungerPositionY = floatArr[1];
table->PlungerPosition = {floatArr[0], 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)
Message(1017, 0.0);
coef = RandFloat() * Boost * 0.1f + Boost; // it is intended that the passed in coef is never used!
maths::basic_collision(ball, nextPosition, direction, Elasticity, Smoothness, Threshold, coef);
if (PinballTable->TiltLockFlag || SomeCounter > 0)
{
auto boost = RandFloat() * MaxPullback * 0.1f + MaxPullback;
maths::basic_collision(ball, nextPosition, direction, Elasticity, Smoothness, 0, boost);
if (SomeCounter)
SomeCounter--;
Message(MessageCode::PlungerInputReleased, 0.0);
}
else
{
auto boost = RandFloat() * Boost * 0.1f + Boost;
maths::basic_collision(ball, nextPosition, direction, Elasticity, Smoothness, Threshold, boost);
}
}
int TPlunger::Message(int code, float value)
int TPlunger::Message(MessageCode code, float value)
{
switch (code)
{
case 1004:
if (!PullbackTimer_)
case MessageCode::PlungerInputPressed:
if (!PullbackStartedFlag && (!pb::FullTiltMode || PinballTable->MultiballCount > 0 && !PinballTable->
TiltLockFlag))
{
PullbackStartedFlag = true;
Boost = 0.0;
Threshold = 1000000000.0;
loader::play_sound(HardHitSoundId);
loader::play_sound(HardHitSoundId, this, "TPlunger1");
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;
case 1005:
case 1009:
case 1010:
case 1024:
case MessageCode::PlungerFeedBall:
{
if (code == 1024)
if (PinballTable->BallCountInRect(PinballTable->PlungerPosition, PinballTable->CollisionCompOffset * 1.2f))
{
if (BallFeedTimer_)
timer::kill(BallFeedTimer_);
BallFeedTimer_ = 0;
timer::set(1.0f, this, BallFeedTimer);
}
else
{
auto ball = PinballTable->AddBall(PinballTable->PlungerPosition);
assertm(ball, "Failure to create ball in plunger");
PinballTable->MultiballCount++;
PinballTable->BallInDrainFlag = 0;
pb::tilt_no_more();
}
break;
}
case MessageCode::PlungerStartFeedTimer:
timer::set(0.95999998f, this, BallFeedTimer);
loader::play_sound(SoundIndexP1, this, "TPlunger2");
break;
case MessageCode::PlungerLaunchBall:
PullbackStartedFlag = true;
Boost = MaxPullback;
Message(MessageCode::PlungerInputReleased, 0.0f);
break;
case MessageCode::PlungerRelaunchBall:
SomeCounter++;
timer::set(value, this, BallFeedTimer);
loader::play_sound(SoundIndexP1, this, "TPlunger2_1");
PullbackStartedFlag = true;
PullbackTimer(0, this);
break;
case MessageCode::PlayerChanged:
PullbackStartedFlag = false;
Boost = 0.0f;
Threshold = 1000000000.0f;
SomeCounter = 0;
timer::kill(BallFeedTimer);
timer::kill(PullbackTimer);
timer::kill(ReleasedTimer);
break;
case MessageCode::SetTiltLock:
SomeCounter = 0;
timer::kill(BallFeedTimer);
break;
case MessageCode::PlungerInputReleased:
case MessageCode::Resume:
case MessageCode::LooseFocus:
if (PullbackStartedFlag && !SomeCounter)
{
PullbackStartedFlag = false;
Threshold = 0.0;
if (PullbackTimer_)
timer::kill(PullbackTimer_);
PullbackTimer_ = 0;
if (code == 1005)
loader::play_sound(SoundIndexP2);
auto bmp = ListBitmap->at(0);
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;
loader::play_sound(SoundIndexP2, this, "TPlunger3");
SpriteSet(0);
timer::set(PullbackDelay, this, ReleasedTimer);
}
break;
case MessageCode::Reset:
{
PullbackStartedFlag = false;
Boost = 0.0f;
Threshold = 1000000000.0f;
SomeCounter = 0;
timer::kill(BallFeedTimer);
timer::kill(PullbackTimer);
timer::kill(ReleasedTimer);
SpriteSet(0);
break;
}
default:
break;
}
control::handler(code, this);
return 0;
}
void TPlunger::BallFeedTimer(int timerId, void* caller)
{
auto plunger = static_cast<TPlunger*>(caller);
plunger->PullbackTimer_ = 0;
plunger->Message(1015, 0.0);
plunger->Message(MessageCode::PlungerFeedBall, 0.0);
}
void TPlunger::PullbackTimer(int timerId, void* caller)
{
auto plunger = static_cast<TPlunger*>(caller);
plunger->Boost += static_cast<float>(plunger->PullbackIncrement);
if (plunger->Boost <= static_cast<float>(plunger->MaxPullback))
plunger->Boost += plunger->PullbackIncrement;
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
{
plunger->PullbackTimer_ = 0;
plunger->Boost = static_cast<float>(plunger->MaxPullback);
plunger->Boost = plunger->MaxPullback;
}
int index = static_cast<int>(floor(
static_cast<float>(plunger->ListBitmap->size() - 1) *
(plunger->Boost / static_cast<float>(plunger->MaxPullback))));
auto bmp = plunger->ListBitmap->at(index);
auto zMap = plunger->ListZMap->at(index);
render::sprite_set(
plunger->RenderSprite,
bmp,
zMap,
bmp->XPosition - plunger->PinballTable->XOffset,
bmp->YPosition - plunger->PinballTable->YOffset);
(plunger->Boost / plunger->MaxPullback)));
plunger->SpriteSet(index);
}
void TPlunger::PlungerReleasedTimer(int timerId, void* caller)
void TPlunger::ReleasedTimer(int timerId, void* caller)
{
auto plunger = static_cast<TPlunger*>(caller);
plunger->Threshold = 1000000000.0;

View File

@ -7,19 +7,21 @@ class TPlunger :
public:
TPlunger(TPinballTable* table, int groupIndex);
~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;
int Message(int code, float value) override;
int Message(MessageCode code, float value) override;
static void BallFeedTimer(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 BallFeedTimer_;
int MaxPullback;
int PullbackIncrement;
float Unknown4F;
float MaxPullback;
float PullbackIncrement;
float PullbackDelay;
int SoundIndexP1;
int SoundIndexP2;
bool PullbackStartedFlag{};
int SomeCounter{};
};

View File

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

View File

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

View File

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

View File

@ -10,25 +10,22 @@ class TRamp :
{
public:
TRamp(TPinballTable* table, int groupIndex);
void put_scoring(int index, int score) override;
int get_scoring(int index) 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;
int FieldEffect(TBall* ball, vector_type* vecDst) override;
int FieldEffect(TBall* ball, vector2* vecDst) override;
void port_draw() override;
int Scores[4]{};
field_effect_type Field{};
int CollisionGroup;
int RampFlag1;
bool BallZOffsetFlag;
int RampPlaneCount;
float BallFieldMult;
ramp_plane_type* RampPlane;
TEdgeSegment* Line2;
TEdgeSegment* Line3;
TEdgeSegment* Line1;
int Wall1PointFirst;
int Wall2PointFirst;
float Wall1PointLast;
float Wall2PointLast;
int Wall1CollisionGroup;
int Wall2CollisionGroup;
float Wall1BallOffset;
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)
{
if (ListBitmap)
render::sprite_set_bitmap(RenderSprite, ListBitmap->at(0));
SpriteSet(0);
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;
this->RolloverFlag = 0;
if (this->ListBitmap)
render::sprite_set_bitmap(this->RenderSprite, this->ListBitmap->at(0));
ActiveFlag = 1;
RolloverFlag = 0;
SpriteSet(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)
{
ball->Position.X = nextPosition->X;
ball->Position.Y = nextPosition->Y;
ball->RayMaxDistance -= coef;
ball->RayMaxDistance -= distance;
ball->not_again(edge);
gdrv_bitmap8* bmp = nullptr;
if (!PinballTable->TiltLockFlag)
{
if (RolloverFlag)
@ -54,31 +52,14 @@ void TRollover::Collision(TBall* ball, vector_type* nextPosition, vector_type* d
}
else
{
loader::play_sound(SoftHitSoundId);
control::handler(63, this);
loader::play_sound(SoftHitSoundId, ball, "TRollover");
control::handler(MessageCode::ControlCollision, this);
}
RolloverFlag = RolloverFlag == 0;
if (ListBitmap)
{
if (!RolloverFlag)
bmp = ListBitmap->at(0);
render::sprite_set_bitmap(RenderSprite, bmp);
}
SpriteSet(RolloverFlag ? -1 : 0);
}
}
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)
{
visualStruct visual{};

View File

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

View File

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

View File

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

View File

@ -16,18 +16,18 @@ TSoloTarget::TSoloTarget(TPinballTable* table, int groupIndex) : TCollisionCompo
TimerTime = 0.1f;
loader::query_visual(groupIndex, 0, &visual);
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)
{
case 49:
case 50:
ActiveFlag = code == 50;
case MessageCode::TSoloTargetDisable:
case MessageCode::TSoloTargetEnable:
ActiveFlag = code == MessageCode::TSoloTargetEnable;
break;
case 1024:
case MessageCode::Reset:
if (Timer)
timer::kill(Timer);
Timer = 0;
@ -37,47 +37,24 @@ int TSoloTarget::Message(int code, float value)
return 0;
}
if (ListBitmap)
{
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);
}
SpriteSet(1 - ActiveFlag);
return 0;
}
void TSoloTarget::put_scoring(int index, int score)
{
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,
void TSoloTarget::Collision(TBall* ball, vector2* nextPosition, vector2* direction, float distance,
TEdgeSegment* edge)
{
if (DefaultCollision(ball, nextPosition, direction))
{
Message(49, 0.0);
Message(MessageCode::TSoloTargetDisable, 0.0);
Timer = timer::set(TimerTime, this, TimerExpired);
control::handler(63, this);
control::handler(MessageCode::ControlCollision, this);
}
}
void TSoloTarget::TimerExpired(int timerId, void* caller)
{
auto target = static_cast<TSoloTarget*>(caller);
target->Message(50, 0.0);
target->Message(MessageCode::TSoloTargetEnable, 0.0);
target->Timer = 0;
}

View File

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

View File

@ -10,7 +10,7 @@ TSound::TSound(TPinballTable* table, int groupIndex) : TPinballComponent(table,
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:
TSound(TPinballTable* table, int groupIndex);
float Play();
float Play(TPinballComponent *soundSource, const char* info);
int SoundIndex;
};

View File

@ -20,19 +20,20 @@ TTableLayer::TTableLayer(TPinballTable* table): TCollisionComponent(table, -1, f
auto groupIndex = loader::query_handle("table");
loader::query_visual(groupIndex, 0, &visual);
auto spriteData = visual.Bitmap;
/*Full tilt: proj center first value is offset by resolution*/
auto projCenter = loader::query_float_attribute(groupIndex, 0, 700 + fullscrn::GetResolution());
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;
VisBmp = visual.Bitmap;
auto bmp = spriteData.Bmp;
VisBmp = bmp;
rect.XPosition = 0;
rect.YPosition = 0;
rect.Width = bmp->Width;
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->SoundIndex2 = visual.SoundIndex3;
@ -71,37 +72,33 @@ TTableLayer::TTableLayer(TPinballTable* table): TCollisionComponent(table, -1, f
Threshold = visual.Kicker.Threshold;
Boost = 15.0f;
auto visArrPtr = visual.FloatArr;
Unknown1F = std::min(visArrPtr[0], std::min(visArrPtr[2], visArrPtr[4]));
Unknown2F = std::min(visArrPtr[1], std::min(visArrPtr[3], visArrPtr[5]));
Unknown3F = std::max(visArrPtr[0], std::max(visArrPtr[2], visArrPtr[4]));
Unknown4F = std::max(visArrPtr[1], std::max(visArrPtr[3], visArrPtr[5]));
auto a2 = Unknown4F - Unknown2F;
auto a1 = Unknown3F - Unknown1F;
edge_manager = new TEdgeManager(Unknown1F, Unknown2F, a1, a2);
auto edgePoints = reinterpret_cast<vector2*>(visual.FloatArr);
XMin = std::min(edgePoints[0].X, std::min(edgePoints[1].X, edgePoints[2].X));
YMin = std::min(edgePoints[0].Y, std::min(edgePoints[1].Y, edgePoints[2].Y));
XMax = std::max(edgePoints[0].X, std::max(edgePoints[1].X, edgePoints[2].X));
YMax = std::max(edgePoints[0].Y, std::max(edgePoints[1].Y, edgePoints[2].Y));
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,
&ActiveFlag,
visual.CollisionGroup,
visArrPtr[2],
visArrPtr[3],
visArrPtr[0],
visArrPtr[1]);
if (line)
{
line->place_in_grid();
EdgeList.push_back(line);
}
visArrPtr += 2;
edgePoints[i + 1].X,
edgePoints[i + 1].Y,
edgePoints[i].X,
edgePoints[i].Y);
line->place_in_grid(&AABB);
EdgeList.push_back(line);
}
Field.Mask = -1;
Field.Flag2Ptr = &ActiveFlag;
Field.CollisionGroup = -1;
Field.ActiveFlag = &ActiveFlag;
Field.CollisionComp = this;
edges_insert_square(Unknown2F, Unknown1F, Unknown4F, Unknown3F, nullptr,
edges_insert_square(YMin, XMin, YMax, XMax, nullptr,
&Field);
}
@ -111,11 +108,11 @@ TTableLayer::~TTableLayer()
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;
vecDst->Y = GraityDirY - ball->Acceleration.Y * ball->Speed * GraityMult;
vecDst->Y = GraityDirY - ball->Direction.Y * ball->Speed * GraityMult;
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 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)
{
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)
{
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)
{
ray_type ray{};
vector_type vec1{};
vector2 vec1{};
auto radiusM = sqrt(circle->RadiusSq) + edge_manager->AdvanceX * 0.001f;
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);
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)
{
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)
{
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.Y = 0.0;
ray.MaxDistance = edge_manager->AdvanceX;
if (maths::ray_intersect_circle(&ray, circle) < 1000000000.0f)
if (maths::ray_intersect_circle(ray, *circle) < 1000000000.0f)
break;
ray.Direction.X = -1.0;
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;
ray.Direction.X = 0.0;
ray.Direction.Y = 1.0;
ray.MaxDistance = edge_manager->AdvanceY;
if (maths::ray_intersect_circle(&ray, circle) < 1000000000.0f)
if (maths::ray_intersect_circle(ray, *circle) < 1000000000.0f)
break;
ray.Direction.Y = -1.0;
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;
ray.Direction.Y = 0.0;
ray.Direction.X = -1.0;
ray.MaxDistance = edge_manager->AdvanceX;
if (maths::ray_intersect_circle(&ray, circle) < 1000000000.0f)
if (maths::ray_intersect_circle(ray, *circle) < 1000000000.0f)
break;
ray.Direction.X = 1.0;
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;
ray.Direction.X = 0.0;
ray.Direction.Y = -1.0;
ray.MaxDistance = edge_manager->AdvanceY;
if (maths::ray_intersect_circle(&ray, circle) < 1000000000.0f)
if (maths::ray_intersect_circle(ray, *circle) < 1000000000.0f)
break;
ray.Direction.Y = 1.0;
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;
collision = false;

View File

@ -14,17 +14,17 @@ class TTableLayer :
public:
TTableLayer(TPinballTable* table);
~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,
field_effect_type* field);
static void edges_insert_circle(circle_type* circle, TEdgeSegment* edge, field_effect_type* field);
gdrv_bitmap8* VisBmp;
float Unknown1F;
float Unknown2F;
float Unknown3F;
float Unknown4F;
float XMin;
float YMin;
float XMax;
float YMax;
float GraityDirX;
float GraityDirY;
float GraityMult;

View File

@ -18,8 +18,8 @@ TTextBox::TTextBox(TPinballTable* table, int groupIndex) : TPinballComponent(tab
Height = 0;
BgBmp = render::background_bitmap;
Font = score::msg_fontp;
Message1 = nullptr;
Message2 = nullptr;
CurrentMessage = nullptr;
PreviousMessage = nullptr;
Timer = 0;
if (groupIndex > 0)
@ -42,16 +42,16 @@ TTextBox::~TTextBox()
timer::kill(Timer);
Timer = 0;
}
while (Message1)
while (CurrentMessage)
{
TTextBoxMessage* message = Message1;
TTextBoxMessage* message = CurrentMessage;
TTextBoxMessage* nextMessage = message->NextMessage;
delete message;
Message1 = nextMessage;
CurrentMessage = nextMessage;
}
}
int TTextBox::Message(int code, float value)
int TTextBox::Message(MessageCode code, float value)
{
return 0;
}
@ -59,19 +59,19 @@ int TTextBox::Message(int code, float value)
void TTextBox::TimerExpired(int timerId, void* caller)
{
auto tb = static_cast<TTextBox*>(caller);
TTextBoxMessage* message = tb->Message1;
TTextBoxMessage* message = tb->CurrentMessage;
tb->Timer = 0;
if (message)
{
TTextBoxMessage* nextMessage = message->NextMessage;
delete message;
tb->Message1 = nextMessage;
tb->CurrentMessage = nextMessage;
tb->Draw();
control::handler(60, tb);
control::handler(MessageCode::ControlTimerExpired, tb);
}
}
void TTextBox::Clear()
void TTextBox::Clear(bool lowPriorityOnly)
{
gdrv_bitmap8* bmp = BgBmp;
if (bmp)
@ -92,24 +92,25 @@ void TTextBox::Clear()
timer::kill(Timer);
Timer = 0;
}
while (Message1)
while (CurrentMessage && (!lowPriorityOnly || CurrentMessage->LowPriority))
{
TTextBoxMessage* message = Message1;
TTextBoxMessage* nextMessage = message->NextMessage;
auto message = CurrentMessage;
CurrentMessage = message->NextMessage;
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)
return;
if (Message1 && !strcmp(text, Message2->Text))
if (CurrentMessage && !strcmp(text, PreviousMessage->Text))
{
Message2->Refresh(time);
if (Message2 == Message1)
PreviousMessage->Refresh(time);
if (PreviousMessage == CurrentMessage)
{
if (Timer && Timer != -1)
timer::kill(Timer);
@ -124,27 +125,63 @@ void TTextBox::Display(const char* text, float time)
if (Timer == -1)
Clear();
auto message = new TTextBoxMessage(text, time);
if (message)
auto message = new TTextBoxMessage(text, time, lowPriority);
if (message->Text)
{
if (message->Text)
{
if (Message1)
Message2->NextMessage = message;
else
Message1 = message;
Message2 = message;
if (Timer == 0)
Draw();
}
if (CurrentMessage)
PreviousMessage->NextMessage = message;
else
{
delete message;
}
CurrentMessage = 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()
{
auto bmp = BgBmp;
@ -162,26 +199,26 @@ void TTextBox::Draw()
gdrv::fill_bitmap(render::vscreen, Width, Height, OffsetX, OffsetY, 0);
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;
display = true;
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;
break;
}
auto tmp = Message1;
Message1 = Message1->NextMessage;
auto tmp = CurrentMessage;
CurrentMessage = CurrentMessage->NextMessage;
delete tmp;
}
@ -189,19 +226,13 @@ void TTextBox::Draw()
{
if (!Font)
{
gdrv::grtext_draw_ttext_in_box(
Message1->Text,
render::vscreen->XPosition + OffsetX,
render::vscreen->YPosition + OffsetY,
Width,
Height,
255);
// Immediate mode drawing using system font is handled by TTextBox::DrawImGui
return;
}
std::vector<LayoutResult> lines{};
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)
break;

View File

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

View File

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

View File

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

View File

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

View File

@ -6,7 +6,7 @@ class TTimer :
{
public:
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);
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)
{
ball->Position.X = nextPosition->X;
ball->Position.Y = nextPosition->Y;
ball->RayMaxDistance -= coef;
ball->RayMaxDistance -= distance;
ball->not_again(edge);
if (!PinballTable->TiltLockFlag)
{
loader::play_sound(SoftHitSoundId);
control::handler(63, this);
loader::play_sound(SoftHitSoundId, ball, "TTripwire");
control::handler(MessageCode::ControlCollision, this);
}
}

View File

@ -7,6 +7,6 @@ class TTripwire :
public:
TTripwire(TPinballTable* table, int groupIndex);
~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;
};

View File

@ -9,14 +9,12 @@
TWall::TWall(TPinballTable* table, int groupIndex) : TCollisionComponent(table, groupIndex, true)
{
if (RenderSprite)
render::sprite_set_bitmap(RenderSprite, nullptr);
if (ListBitmap)
BmpPtr = ListBitmap->at(0);
SpriteSet(-1);
}
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);
TimerExpired(Timer, this);
@ -24,34 +22,23 @@ int TWall::Message(int code, float value)
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 (BmpPtr)
if (ListBitmap)
{
render::sprite_set_bitmap(RenderSprite, BmpPtr);
SpriteSet(0);
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)
{
auto wall = static_cast<TWall*>(caller);
render::sprite_set_bitmap(wall->RenderSprite, nullptr);
wall->SpriteSet(-1);
wall->Timer = 0;
wall->MessageField = 0;
}

View File

@ -2,22 +2,16 @@
#include "TCollisionComponent.h"
struct gdrv_bitmap8;
class TWall :
public TCollisionComponent
{
public:
TWall(TPinballTable* table, int groupIndex);
int Message(int code, float value) override;
void Collision(TBall* ball, vector_type* nextPosition, vector_type* direction, float coef,
int Message(MessageCode code, float value) override;
void Collision(TBall* ball, vector2* nextPosition, vector2* direction, float distance,
TEdgeSegment* edge) override;
void put_scoring(int index, int score) override;
int get_scoring(int index) override;
static void TimerExpired(int timerId, void* caller);
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
class TCollisionComponent;
class TBall;
enum class MessageCode;
class TSink;
class TLight;
class TSound;
class TPinballTable;
class TPinballComponent;
enum class Msg : int;
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");
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
@ -47,14 +51,14 @@ struct component_tag : component_tag_base
struct component_control
{
void (* ControlFunc)(int, TPinballComponent*);
int ScoreCount;
int* Scores;
void (& ControlFunc)(MessageCode, TPinballComponent*);
const unsigned int ScoreCount;
const int* Scores;
};
struct component_info
{
component_tag_base* Tag;
component_tag_base& Tag;
component_control Control;
};
@ -64,125 +68,128 @@ class control
public:
static TPinballTable* TableG;
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 bool table_unlimited_balls;
static int RankRcArray[9], MissionRcArray[17], mission_select_scores[17];
static component_tag_base *wormhole_tag_array1[3], *wormhole_tag_array2[3], *wormhole_tag_array3[3];
static bool table_unlimited_balls, easyMode;
static Msg RankRcArray[9], MissionRcArray[17];
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 ClearLinks();
static TPinballComponent* make_component_link(component_tag_base* tag);
static void handler(int code, TPinballComponent* cmp);
static TPinballComponent* make_component_link(component_tag_base& tag);
static void handler(MessageCode code, TPinballComponent* cmp);
static void pbctrl_bdoor_controller(char key);
static void table_add_extra_ball(float count);
static void table_set_bonus_hold();
static void table_set_bonus();
static void table_set_jackpot();
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_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 bool light_on(component_tag<TLight>* tag);
static int SpecialAddScore(int score);
static int SpecialAddScore(int score, bool mission = false);
static int AddRankProgress(int rank);
static void AdvanceWormHoleDestination(int flag);
static void FlipperRebounderControl1(int code, TPinballComponent* caller);
static void FlipperRebounderControl2(int code, TPinballComponent* caller);
static void RebounderControl(int code, TPinballComponent* caller);
static void BumperControl(int code, TPinballComponent* caller);
static void LeftKickerControl(int code, TPinballComponent* caller);
static void RightKickerControl(int code, TPinballComponent* caller);
static void LeftKickerGateControl(int code, TPinballComponent* caller);
static void RightKickerGateControl(int code, TPinballComponent* caller);
static void DeploymentChuteToEscapeChuteOneWayControl(int code, TPinballComponent* caller);
static void DeploymentChuteToTableOneWayControl(int code, TPinballComponent* caller);
static void DrainBallBlockerControl(int code, TPinballComponent* caller);
static void LaunchRampControl(int code, TPinballComponent* caller);
static void LaunchRampHoleControl(int code, TPinballComponent* caller);
static void SpaceWarpRolloverControl(int code, TPinballComponent* caller);
static void ReentryLanesRolloverControl(int code, TPinballComponent* caller);
static void BumperGroupControl(int code, TPinballComponent* caller);
static void LaunchLanesRolloverControl(int code, TPinballComponent* caller);
static void OutLaneRolloverControl(int code, TPinballComponent* caller);
static void ExtraBallLightControl(int code, TPinballComponent* caller);
static void ReturnLaneRolloverControl(int code, TPinballComponent* caller);
static void BonusLaneRolloverControl(int code, TPinballComponent* caller);
static void FuelRollover1Control(int code, TPinballComponent* caller);
static void FuelRollover2Control(int code, TPinballComponent* caller);
static void FuelRollover3Control(int code, TPinballComponent* caller);
static void FuelRollover4Control(int code, TPinballComponent* caller);
static void FuelRollover5Control(int code, TPinballComponent* caller);
static void FuelRollover6Control(int code, TPinballComponent* caller);
static void HyperspaceLightGroupControl(int code, TPinballComponent* caller);
static void WormHoleControl(int code, TPinballComponent* caller);
static void LeftFlipperControl(int code, TPinballComponent* caller);
static void RightFlipperControl(int code, TPinballComponent* caller);
static void JackpotLightControl(int code, TPinballComponent* caller);
static void BonusLightControl(int code, TPinballComponent* caller);
static void BoosterTargetControl(int code, TPinballComponent* caller);
static void MedalLightGroupControl(int code, TPinballComponent* caller);
static void MultiplierLightGroupControl(int code, TPinballComponent* caller);
static void FuelSpotTargetControl(int code, TPinballComponent* caller);
static void MissionSpotTargetControl(int code, TPinballComponent* caller);
static void LeftHazardSpotTargetControl(int code, TPinballComponent* caller);
static void RightHazardSpotTargetControl(int code, TPinballComponent* caller);
static void WormHoleDestinationControl(int code, TPinballComponent* caller);
static void BlackHoleKickoutControl(int code, TPinballComponent* caller);
static void FlagControl(int code, TPinballComponent* caller);
static void GravityWellKickoutControl(int code, TPinballComponent* caller);
static void SkillShotGate1Control(int code, TPinballComponent* caller);
static void SkillShotGate2Control(int code, TPinballComponent* caller);
static void SkillShotGate3Control(int code, TPinballComponent* caller);
static void SkillShotGate4Control(int code, TPinballComponent* caller);
static void SkillShotGate5Control(int code, TPinballComponent* caller);
static void SkillShotGate6Control(int code, TPinballComponent* caller);
static void ShootAgainLightControl(int code, TPinballComponent* caller);
static void EscapeChuteSinkControl(int code, TPinballComponent* caller);
static void MissionControl(int code, TPinballComponent* caller);
static void HyperspaceKickOutControl(int code, TPinballComponent* caller);
static void PlungerControl(int code, TPinballComponent* caller);
static void MedalTargetControl(int code, TPinballComponent* caller);
static void MultiplierTargetControl(int code, TPinballComponent* caller);
static void BallDrainControl(int code, TPinballComponent* caller);
static void FlipperRebounderControl1(MessageCode code, TPinballComponent* caller);
static void FlipperRebounderControl2(MessageCode code, TPinballComponent* caller);
static void RebounderControl(MessageCode code, TPinballComponent* caller);
static void BumperControl(MessageCode code, TPinballComponent* caller);
static void LeftKickerControl(MessageCode code, TPinballComponent* caller);
static void RightKickerControl(MessageCode code, TPinballComponent* caller);
static void LeftKickerGateControl(MessageCode code, TPinballComponent* caller);
static void RightKickerGateControl(MessageCode code, TPinballComponent* caller);
static void DeploymentChuteToEscapeChuteOneWayControl(MessageCode code, TPinballComponent* caller);
static void DeploymentChuteToTableOneWayControl(MessageCode code, TPinballComponent* caller);
static void DrainBallBlockerControl(MessageCode code, TPinballComponent* caller);
static void LaunchRampControl(MessageCode code, TPinballComponent* caller);
static void LaunchRampHoleControl(MessageCode code, TPinballComponent* caller);
static void SpaceWarpRolloverControl(MessageCode code, TPinballComponent* caller);
static void ReentryLanesRolloverControl(MessageCode code, TPinballComponent* caller);
static void BumperGroupControl(MessageCode code, TPinballComponent* caller);
static void LaunchLanesRolloverControl(MessageCode code, TPinballComponent* caller);
static void OutLaneRolloverControl(MessageCode code, TPinballComponent* caller);
static void ExtraBallLightControl(MessageCode code, TPinballComponent* caller);
static void ReturnLaneRolloverControl(MessageCode code, TPinballComponent* caller);
static void BonusLaneRolloverControl(MessageCode code, TPinballComponent* caller);
static void FuelRollover1Control(MessageCode code, TPinballComponent* caller);
static void FuelRollover2Control(MessageCode code, TPinballComponent* caller);
static void FuelRollover3Control(MessageCode code, TPinballComponent* caller);
static void FuelRollover4Control(MessageCode code, TPinballComponent* caller);
static void FuelRollover5Control(MessageCode code, TPinballComponent* caller);
static void FuelRollover6Control(MessageCode code, TPinballComponent* caller);
static void HyperspaceLightGroupControl(MessageCode code, TPinballComponent* caller);
static void WormHoleControl(MessageCode code, TPinballComponent* caller);
static void LeftFlipperControl(MessageCode code, TPinballComponent* caller);
static void RightFlipperControl(MessageCode code, TPinballComponent* caller);
static void JackpotLightControl(MessageCode code, TPinballComponent* caller);
static void BonusLightControl(MessageCode code, TPinballComponent* caller);
static void BoosterTargetControl(MessageCode code, TPinballComponent* caller);
static void MedalLightGroupControl(MessageCode code, TPinballComponent* caller);
static void MultiplierLightGroupControl(MessageCode code, TPinballComponent* caller);
static void FuelSpotTargetControl(MessageCode code, TPinballComponent* caller);
static void MissionSpotTargetControl(MessageCode code, TPinballComponent* caller);
static void LeftHazardSpotTargetControl(MessageCode code, TPinballComponent* caller);
static void RightHazardSpotTargetControl(MessageCode code, TPinballComponent* caller);
static void WormHoleDestinationControl(MessageCode code, TPinballComponent* caller);
static void BlackHoleKickoutControl(MessageCode code, TPinballComponent* caller);
static void FlagControl(MessageCode code, TPinballComponent* caller);
static void GravityWellKickoutControl(MessageCode code, TPinballComponent* caller);
static void SkillShotGate1Control(MessageCode code, TPinballComponent* caller);
static void SkillShotGate2Control(MessageCode code, TPinballComponent* caller);
static void SkillShotGate3Control(MessageCode code, TPinballComponent* caller);
static void SkillShotGate4Control(MessageCode code, TPinballComponent* caller);
static void SkillShotGate5Control(MessageCode code, TPinballComponent* caller);
static void SkillShotGate6Control(MessageCode code, TPinballComponent* caller);
static void ShootAgainLightControl(MessageCode code, TPinballComponent* caller);
static void EscapeChuteSinkControl(MessageCode code, TPinballComponent* caller);
static void MissionControl(MessageCode code, TPinballComponent* caller);
static void HyperspaceKickOutControl(MessageCode code, TPinballComponent* caller);
static void PlungerControl(MessageCode code, TPinballComponent* caller);
static void MedalTargetControl(MessageCode code, TPinballComponent* caller);
static void MultiplierTargetControl(MessageCode 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 AlienMenacePartTwoController(int code, TPinballComponent* caller);
static void BlackHoleThreatController(int code, TPinballComponent* caller);
static void BugHuntController(int code, TPinballComponent* caller);
static void CosmicPlagueController(int code, TPinballComponent* caller);
static void CosmicPlaguePartTwoController(int code, TPinballComponent* caller);
static void DoomsdayMachineController(int code, TPinballComponent* caller);
static void GameoverController(int code, TPinballComponent* caller);
static void LaunchTrainingController(int code, TPinballComponent* caller);
static void MaelstromController(int code, TPinballComponent* caller);
static void MaelstromPartEightController(int code, TPinballComponent* caller);
static void MaelstromPartFiveController(int code, TPinballComponent* caller);
static void MaelstromPartFourController(int code, TPinballComponent* caller);
static void MaelstromPartSevenController(int code, TPinballComponent* caller);
static void MaelstromPartSixController(int code, TPinballComponent* caller);
static void MaelstromPartThreeController(int code, TPinballComponent* caller);
static void MaelstromPartTwoController(int code, TPinballComponent* caller);
static void PracticeMissionController(int code, TPinballComponent* caller);
static void ReconnaissanceController(int code, TPinballComponent* caller);
static void ReentryTrainingController(int code, TPinballComponent* caller);
static void RescueMissionController(int code, TPinballComponent* caller);
static void SatelliteController(int code, TPinballComponent* caller);
static void ScienceMissionController(int code, TPinballComponent* caller);
static void SecretMissionGreenController(int code, TPinballComponent* caller);
static void SecretMissionRedController(int code, TPinballComponent* caller);
static void SecretMissionYellowController(int code, TPinballComponent* caller);
static void SelectMissionController(int code, TPinballComponent* caller);
static void SpaceRadiationController(int code, TPinballComponent* caller);
static void StrayCometController(int code, TPinballComponent* caller);
static void TimeWarpController(int code, TPinballComponent* caller);
static void TimeWarpPartTwoController(int code, TPinballComponent* caller);
static void UnselectMissionController(int code, TPinballComponent* caller);
static void WaitingDeploymentController(int code, TPinballComponent* caller);
static void AlienMenaceController(MessageCode code, TPinballComponent* caller);
static void AlienMenacePartTwoController(MessageCode code, TPinballComponent* caller);
static void BlackHoleThreatController(MessageCode code, TPinballComponent* caller);
static void BugHuntController(MessageCode code, TPinballComponent* caller);
static void CosmicPlagueController(MessageCode code, TPinballComponent* caller);
static void CosmicPlaguePartTwoController(MessageCode code, TPinballComponent* caller);
static void DoomsdayMachineController(MessageCode code, TPinballComponent* caller);
static void GameoverController(MessageCode code, TPinballComponent* caller);
static void LaunchTrainingController(MessageCode code, TPinballComponent* caller);
static void MaelstromController(MessageCode code, TPinballComponent* caller);
static void MaelstromPartEightController(MessageCode code, TPinballComponent* caller);
static void MaelstromPartFiveController(MessageCode code, TPinballComponent* caller);
static void MaelstromPartFourController(MessageCode code, TPinballComponent* caller);
static void MaelstromPartSevenController(MessageCode code, TPinballComponent* caller);
static void MaelstromPartSixController(MessageCode code, TPinballComponent* caller);
static void MaelstromPartThreeController(MessageCode code, TPinballComponent* caller);
static void MaelstromPartTwoController(MessageCode code, TPinballComponent* caller);
static void PracticeMissionController(MessageCode code, TPinballComponent* caller);
static void ReconnaissanceController(MessageCode code, TPinballComponent* caller);
static void ReentryTrainingController(MessageCode code, TPinballComponent* caller);
static void RescueMissionController(MessageCode code, TPinballComponent* caller);
static void SatelliteController(MessageCode code, TPinballComponent* caller);
static void ScienceMissionController(MessageCode code, TPinballComponent* caller);
static void SecretMissionGreenController(MessageCode code, TPinballComponent* caller);
static void SecretMissionRedController(MessageCode code, TPinballComponent* caller);
static void SecretMissionYellowController(MessageCode code, TPinballComponent* caller);
static void SelectMissionController(MessageCode code, TPinballComponent* caller);
static void SpaceRadiationController(MessageCode code, TPinballComponent* caller);
static void StrayCometController(MessageCode code, TPinballComponent* caller);
static void TimeWarpController(MessageCode code, TPinballComponent* caller);
static void TimeWarpPartTwoController(MessageCode code, TPinballComponent* caller);
static void UnselectMissionController(MessageCode code, TPinballComponent* caller);
static void WaitingDeploymentController(MessageCode code, TPinballComponent* caller);
private:
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