222 Commits
Author SHA1 Message Date
Daeho Ro cb9b7b8862 add brew commands (#218) 2024-08-21 09:56:55 +03:00
Orazio 182bd93648 Update macOS GitHub runner, SDL and extend compatibility (#217)
* Update SDL and SDL_mixer on macOS

* Extend (Intel) Mac compatibility all the way to OS X El Capitan

* Update macOS runner due to GitHub deprecation but still use and Xcode version that supports 10.11
2024-08-21 09:31:43 +03:00
Headshotnoby cef716e5cc Add multiple console ports to README (#216)
* Add Nintendo DS port to README

* Add PS2 and Dreamcast ports to README
2024-08-21 09:26:27 +03:00
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
AdamandMuzychenko Andrey 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-powerandMuzychenko Andrey 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 GabrielianandMuzychenko Andrey 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 MurzeauandMuzychenko Andrey 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
Muzychenko Andrey 9acd3dbc9b Bumped version to 2.0.1.
Added Flathub link.
2021-12-29 09:46:09 +03:00
Nicola Smaniotto 5ffbab8208 Simplify icon installation (#114)
Uses a foreach loop to iterate over the possible sizes. Should help in the future if more are added.
2021-12-26 13:43:47 +03:00
Muzychenko Andrey a6e2a433c4 Added secondary clear hack.
Issue #88
2021-12-26 13:25:25 +03:00
Kowalski Dragon ad4a17c2f2 Add assets (#113)
* Add 128x128 icon needed for Flathub submission

* Add screenshot

* Align metainfo to use screenshot in the upstream repository

* Edit CMake to install 128x128 icon in correct folder

* Add jpg screenshots
2021-12-26 13:06:31 +03:00
Kowalski Dragon 4db4e5fbcb Improve linux building (#111)
* Improve Linux platform by adding metadata and improve description

* Improve readme

* Improve metainfo

* Fix wrong desktop file

* Improve readme

* Improve readme

* Appstream doesn't like h1

* Add CMake and Ninja to .gitignore

* Fix issues




3


4

* Revert readme keeping only Linux part

* Change comment on desktop file

* Align metainfo

* Decouple Readme from Linux building instructions
2021-12-24 09:09:40 +03:00
Muzychenko Andrey 0dc6006937 Added explicit bin install directory for old CMake.
Issue #110.
2021-12-20 09:06:32 +03:00
Lucy M e81c6c9d37 spaces in .desktop name (#106) 2021-12-14 12:42:06 +03:00
Nicola Smaniotto b25cf3f0d0 Add Linux installation info (#103)
* Add Linux installation info

Add a repology badge and mention the possibility to install via the package manager.

I believe this is a better approach than explicitly mentioning a repository, because it uses repology's database (and the list will remain updated).

* use tiny badge

The big badge is too big and could overflow in the following section. The tiny one will keep the same size as new packages are added.
2021-12-14 12:40:44 +03:00
Muzychenko Andrey 3400ea4576 Added WD and Linux-specific /usr/* to game data search paths.
Improved data not found error message.
Ref #100.
2021-12-08 15:55:49 +03:00
Martin Ligabue 389122182e typo (#102)
pause/ resume to pause/resume
2021-12-03 15:35:23 +03:00
Nicola Smaniotto de13d4e326 Add linux desktop file and install (#100)
* extract png icons from the ico

* add desktop file

* add linux install option to cmake

* use 32bit icons
2021-12-02 08:07:27 +03:00
Muzychenko Andrey 4e8dbd0b86 TKickout: fixed z reset with demo data. 2021-11-24 17:37:37 +03:00
Muzychenko Andrey 919b537e28 Added FT demo data support.
Tested with .006 ,.020 RC2, there might be more versions out there.
Fixed mds2midi.
Ref #22.
2021-11-24 17:25:23 +03:00
Muzychenko Andrey 95007c9253 partman: improved bad zMap skip. 2021-11-23 15:23:11 +03:00
Muzychenko Andrey 37198f1b99 Added integer scaling option.
Useful for getting exact upscale in combination with nearest neighbor.
Ref issue #97.
2021-11-22 09:32:17 +03:00
Muzychenko Andrey 64c3f2031b Added “Prefer 3DPB game data” option.
Useful for quickly switching between the two datasets.
3DPB and FT data can be stored in one folder without collisions.
2021-11-21 15:40:56 +03:00
Muzychenko Andrey 3b7dc0dae2 Added centered text in textboxes in FT mode. 2021-11-20 19:03:22 +03:00
Muzychenko Andrey 2229f9b70e Added hybrid sleep/spin wait mode. 2021-11-18 17:58:53 +03:00
Muzychenko Andrey 545af17b3b Merge from classic: better frame time tool, negative sleep remainder. 2021-11-18 12:11:25 +03:00
Muzychenko Andrey 200a7dbf79 Enabled ImGui navigation with keyboard and game controller.
Ref issue #92.
2021-11-15 17:32:18 +03:00
Muzychenko Andrey 8ab50ea7b7 ColorRgba: replaced union with bit shifts.
Fixed bad clamping in frame time tool.
2021-11-13 09:00:58 +03:00
Muzychenko Andrey f3e4211226 Improved frame time diagnostic tool.
Split overdraw palette from frame time tool.
2021-11-11 14:30:56 +03:00
Muzychenko Andrey 16b527e3cf Added AddressSanitizer to Windows build config, disabled by default.
VS older that 2019 do not support it.
Game passes ASan checks at the moment of writing.
2021-11-09 16:50:09 +03:00
Muzychenko Andrey 683204519c Added UTF-8 path support on Windows.
Ref issue #82.
2021-11-06 19:22:56 +03:00
Muzychenko Andrey ecdf802d68 Added game data loading from user folder (SDL_GetPrefPath).
Ref issue #80.
2021-11-05 10:16:27 +03:00
Muzychenko Andrey dc00dbde0d Fixed bug with mission accept scores.
Ref issue #81.
2021-11-04 18:46:04 +03:00
Muzychenko Andrey 862fe13dcd Added game controller exit shortcut: back/select when paused.
Ref issue #79.
2021-11-01 09:09:19 +03:00
Muzychenko Andrey 6c299ed103 Updated plans in readme 2021-10-30 12:51:24 +03:00
Muzychenko Andrey fc1975a607 Fixed bug: dialogs not shown when main menu is hidden.
Ref issue #76.
2021-10-30 12:34:17 +03:00
Muzychenko Andrey e61bbd634c Added fallback to SW SDL renderer. 2021-10-30 10:12:30 +03:00
Muzychenko Andrey 917b68d630 Added NN scaling for PINBALL2.MID.
It does not scale well.
Wii port should rather use non-compressed PB_MSGFT_bin.
2021-10-28 13:03:05 +03:00
MaikelChan 46d3ae324c Added 3DS port to README. (#74) 2021-10-28 07:53:57 +03:00
Iscle d27740bd38 Fix compiler warnings (#73) 2021-10-26 17:15:45 +03:00
Muzychenko Andrey cfd30419c2 Added Windows XP build configuration.
Removed unused SDL inits.
2021-10-26 17:11:53 +03:00
Muzychenko Andrey 3ec96b84ad PresentVScreen: fixed sub pixel offset.
SDL<2.0.10 uses crude approximation.
2021-10-25 08:03:30 +03:00
MaikelChan 34cb964ea5 Change texture filtering without restarting. (#67) 2021-10-25 06:42:36 +03:00
MaikelChan 5789492021 Adjusted screen coordinates so menu doesn't overlap (#66)
* Optimized final blit to the screen render target.

When bumping the table, instead of offseting the table pixels by CPU, just memcpy all the pixels to vScreenTex once, and then render two separate quads from that texture: one for the board and the other for the sidebar. Then change the coordinates of the board quad when bumping.

* Main menu bar doesn't cover game area

* Forgot to also take into account changing UI scale.
2021-10-24 18:38:23 +03:00
MaikelChan 38cf08e298 Optimized final blit to the screen render target. (#65)
When bumping the table, instead of offseting the table pixels by CPU, just memcpy all the pixels to vScreenTex once, and then render two separate quads from that texture: one for the board and the other for the sidebar. Then change the coordinates of the board quad when bumping.
2021-10-24 07:13:51 +03:00
Muzychenko Andrey 5cd01807b2 winmain: converted int to bool.
Cleaned up some of the PR changes.
2021-10-23 09:02:51 +03:00
toxie 57af3af800 Increase precision of mode_countdown_ handling (#52)
* fix harmless warnings and properly try/catch allocations via new

otherwise the error handling will never be triggered

* increase precision of mode_countdown_ handling

potentially there could be modes running a bit too long, depending on passed in ms (which were implicitly truncated before when passing in)

also fix some harmless warnings

* document warnings that i cannot handle on my own

* revert changes to have a new cleaner PR after review/cherry picks

* increase precision of mode_countdown_ handling

potentially there could be modes running a bit too long, depending on passed in ms (which were implicitly truncated before when passing in)

also fix some harmless warnings and add comments where original code is 'correct' but weird
2021-10-23 07:33:04 +03:00
Desgging 8e07b7fc3f Full Tilt hack - ball doesn't delay at ramp hole (#63) 2021-10-23 07:31:25 +03:00
IntriguingTiles 0076f8947c Add Wii U port to readme (#60) 2021-10-22 12:34:25 +03:00
Muzychenko Andrey 43ce86571c MinGW: added rc compiler to toolchain.
Ref issue #54.
2021-10-19 08:13:21 +03:00
extrowerk 4188cd3455 Update FindSDL2.cmake (#53)
Fix for Haiku, because nobody can write a cross-platform build tool.
2021-10-19 08:06:37 +03:00
Muzychenko Andrey 685dfe78c2 TPlunger: removed duplicated code.
Ref PR#52.
2021-10-18 10:52:13 +03:00
Muzychenko Andrey 348d79ef38 Bugs and suggestions from PR# 48. 2021-10-18 09:31:47 +03:00
MaikelChan 9088c44b3e Add Wii port to the README (#51) 2021-10-18 08:17:07 +03:00
Muzychenko Andrey 06b760e8dd Player controls: added mouse and game controller remapping.
Fixed duplicate button id.
2021-10-17 18:18:29 +03:00
Muzychenko Andrey d06aa1c736 Changed SDL_mixer version guard.
SDL_MIXER_COMPILEDVERSION is not in Windows release of mixer v2.0.1, so it is back to basics with SDL_MIXER_PATCHLEVEL.
MIX_INIT_FLUIDSYNTH was renamed in 2.0.2, according to headers from Windows releases.
Ref PR #42, #46
2021-10-17 12:00:30 +03:00
Mariotaku 787c623cfe Backward compatibility for mixer version check (#46)
SDL_MIXER_VERSION_ATLEAST isn't available in SDL Mixer 2.0.1, which will cause build errors.
2021-10-17 09:00:49 +03:00
IscleandMuzychenko Andrey b7bf1563e5 Update README.md (#45)
Co-authored-by: Muzychenko Andrey <33288308+k4zmu2a@users.noreply.github.com>
2021-10-17 07:58:00 +03:00
MariotakuandMuzychenko Andrey f56abf0596 Compatibility and game controller patches (#42)
* Compatibility for old CMake versions (3.0)
Compatibility for SDL Mixer 2.0.1
Basic controller support: LB, RB for flippers, A for plunger, DPAD for table bump

* Update SpaceCadetPinball/Sound.cpp

Co-authored-by: Muzychenko Andrey <33288308+k4zmu2a@users.noreply.github.com>
2021-10-17 07:52:05 +03:00
haruna a7e3503e2c fix: lint README to make more linkable (#40)
* fix: lint README to make more linkable

* restore blank lines between `Known source ports` and `Source`
2021-10-17 07:49:17 +03:00
Muzychenko Andrey 2ee40560ee Update CONTRIBUTING.md
Added issue part.
2021-10-16 15:06:44 +03:00
Muzychenko Andrey 5682640e36 Create CONTRIBUTING.md 2021-10-16 14:51:44 +03:00
Mariotaku 7ec2ab9a76 Added webOS source port entry (#43) 2021-10-16 14:26:15 +03:00
Muzychenko Andrey 82d4d8719f Added link to Switch source port.
Mentioned macOS as supported.
2021-10-15 14:07:25 +03:00
Muzychenko Andrey f302687c7f high_score: fixed new score not shifting older scores.
Issue #33.
2021-10-15 13:30:55 +03:00
Tomáš Hübelbauer fd973f049c Fix a typo (#32) 2021-10-15 07:26:16 +03:00
Muzychenko Andrey 5947727f80 Tweaked ball mouse control cheat. 2021-10-10 17:13:43 +03:00
Muzychenko Andrey 69ecce88df Score: inject 3DPB msg font into dat struct.
Fixed double free in sound.
2021-10-10 12:22:21 +03:00
Muzychenko Andrey 43593b168d Sound: added channel recycling.
Added sound channel count (aka voices) user option.
Added 3DPB font to sprite viewer.
Added version number to about dialog.
2021-10-09 17:28:30 +03:00
Muzychenko Andrey d80074b9b6 Added Windows subsystem entry point for release builds. 2021-10-09 12:33:33 +03:00
Muzychenko Andrey 69027eca53 Mingw build: static link, posix g++, SDL2 list order. 2021-10-07 16:47:07 +03:00
Muzychenko Andrey ac289c7f48 Fixed TLightGroup message 45 and 46, used by TLightBargraph.
Issue #25.
2021-10-07 14:17:43 +03:00
87e44b700b Mingw cross compilation (#24)
* Fixed icon filename capitalization

* Created mingw cmake toolchain

* Adjusted CMakeLists.txt for compatibility

* Small mingwcc.cmake cleanup

* Added cross-compilation instructions to readme

* Update README.md

Fixed typo

Co-authored-by: Nicola Orlando <nicolaorlando24@gmail.com>
Co-authored-by: Muzychenko Andrey <33288308+k4zmu2a@users.noreply.github.com>
2021-10-07 12:53:56 +03:00
Muzychenko Andrey 48721e5811 Fixed flipper animation frame skip bug.
It is from original 3DPB, not present in FT.
2021-10-07 08:01:34 +03:00
Muzychenko Andrey 7ee508118c Fixed TKickout temporary Z in FT mode. 2021-10-06 12:42:22 +03:00
Muzychenko Andrey 8c4f38c0af Cheats: flipped literals, made some toggle, added GUI. 2021-10-05 16:48:13 +03:00
Muzychenko Andrey 167a2c2bd1 Demangled and simplified pbctrl_bdoor_controller. 2021-10-04 17:04:09 +03:00
Muzychenko Andrey f1c6c48b36 Enabled icon in Windows build. 2021-10-03 18:57:19 +03:00
Muzychenko Andrey 8d2745fc33 Added support for sub-millisecond frame times. 2021-10-03 18:06:19 +03:00
Muzychenko Andrey 5e03978cd7 cmake: disabled link to SDL2main.
Ref #19.
2021-10-03 15:41:57 +03:00
Muzychenko Andrey 261457a959 TPinballComponent: replaced calloc operator new with member initialization. 2021-10-02 18:58:54 +03:00
oz 93de90b680 Replaced memory with new.
Cleaned up gdrv, zdrv, render.
2021-10-02 17:45:31 +03:00
Muzychenko Andrey dc5915b4f8 Added links to Emscripten source port.
Ref #12
2021-10-02 08:04:54 +03:00
Muzychenko Andrey c3b6daefc9 Added uncapped UPS option.
Issue #18.
2021-10-02 07:42:08 +03:00
Muzychenko Andrey 81c2034a16 Replaced objlist_class with std::vector.
Fixed minor bug in TLightGroup.
Cleaned up some warnings.
2021-10-01 18:55:44 +03:00
Muzychenko Andrey 8a421a2623 Implemented player controls dialog.
This last missing major feature brings v2 into feature parity with the original and closer to release.
Ref issues #16, #17.
2021-10-01 09:05:38 +03:00
Muzychenko Andrey a281000308 midi: added support for FULLTILT16 music. 2021-09-30 08:51:48 +03:00
Muzychenko Andrey 03deda2f9d Added event wait timeout when idle.
Added show menu button to prevent lockout; this somewhat ruins no menu aesthetic.
Cleaned up 3DPB vs. FT methods in midi.
2021-09-29 17:53:49 +03:00
Muzychenko Andrey ba5a0f3044 Compressed and encoded embedded 3DPB font.
Exposed ImGui function for decompressing embedded data.
2021-09-29 10:08:45 +03:00
Muzychenko Andrey b37f5d6d76 Fixed ShowMenu option interrupting ImGui::NewFrame. 2021-09-29 07:46:13 +03:00
Muzychenko Andrey 593b4d161c Merge pull request #14 from prototux/option_nogui
Add an option to show or hide the menu
2021-09-29 07:32:01 +03:00
prototux 7c29c05d64 Fixed the keyboard shortcut for show menu 2021-09-29 06:09:24 +02:00
prototux 89f4b6d535 Fixed the keyboard shortcut for show menu 2021-09-29 06:06:08 +02:00
Muzychenko Andrey 373351c2ba Merge pull request #15 from ajhs-io/updatereadme
Update README.md to include macOS build instructions
2021-09-29 06:11:21 +03:00
Muzychenko Andrey 65037e0e69 Bug-fix: storage for non-inline static constexpr.
Somehow none of the compilers I test with cought this.
2021-09-29 06:07:00 +03:00
Alexander Steffen e2a2037a99 Update README.md to include macOS build instructions
Include instructions for successful build of Space Cadet Pinball on both Intel macOS and Apple Silicon macOS. Small formatting fix.
2021-09-28 21:42:22 -04:00
prototux b843e9a6cb Added an option to show or hide the menu. 2021-09-29 02:21:21 +02:00
Muzychenko Andrey b4cb827d73 winmain: reworked main loop for smoother frame times.
imgui_sdl: added handling for device lost.
midi: load PINBALL.MID in uppercase and using absolute path.
Added UPS/FPS options, by default 120/60.
2021-09-28 08:14:18 +03:00
Muzychenko Andrey 22ce8ac538 gdrv: blit no more, present render:vScreen directly.
Improved split bitmap handling.
2021-09-25 16:52:19 +03:00
Muzychenko Andrey 625a6e7498 Fixed md error in readme.
What is GFM? We just don't know.
2021-09-23 12:21:18 +03:00
Muzychenko Andrey fd9de493e6 Added linear filter option for vScreen texture.
Rollback blit to backing storage – its persistence is not guaranteed.
Added project URL to About dialog.
Added source ports table to readme.
2021-09-23 12:16:58 +03:00
Muzychenko Andrey 4c196a9290 Added table resolution and UI scale options.
gdrv: blit directly to backing store of vScreen texture.
Added missing cheat_bump_rank sub, oops.
Fixed some warnings.
2021-09-22 15:50:07 +03:00
Muzychenko Andrey c63c6701ac gdrv: RGBA buffers, pre-applied palettes, SDL bitmap origin.
Refactored partman.
Added sprite viewer.
2021-09-21 13:14:39 +03:00
Muzychenko Andrey 8bae7a5b05 Added user settings persistence.
Using ImGui .ini writer.
2021-09-16 10:57:46 +03:00
Muzychenko Andrey af5a70785e Enabled pch in CMake.
No need to remove old pch includes.
2021-09-15 12:20:57 +03:00
Muzychenko Andrey c5b7c0ad16 Fixed some of the memory leaks and Clang warnings.
DrMemory and Valgrind work with regular debug builds.
2021-09-14 15:33:18 +03:00
Muzychenko Andrey 28e2417ef9 Made it compile with GCC on Linux.
Fixed GCC warnings and Windows specifics.
Restored C++11, switch to 14 was not supposed to happen.
Not 100% sure about my Find* module section.
2021-09-09 11:40:54 +03:00
Muzychenko Andrey 2fe6d6d33a Removed windows.h dependency.
Added support for music in MDS format.
2021-09-06 16:27:58 +03:00
Muzychenko Andrey e0638c598d Added GUI, some menus dont work yet.
Fixed uniform scaling.
Removed splash screen.
2021-09-05 10:47:05 +03:00
Muzychenko Andrey 9a10d72e1f gdrv: simplified bitmap, fixed blit, improved SDL present.
Removed some Windows dependencies.
2021-09-01 11:02:57 +03:00
Muzychenko Andrey a09ea75d80 SDL port v1, Windows only for now.
Working: graphics, sound, music (3dpb only).
Not working: GUI, user settings.
2021-08-27 13:29:41 +03:00
Muzychenko Andrey 10c83e8bf5 Fixed sound pops introduced by WaveMix upsampler.
This does not fix WINE issues, but at least you don’t have to up sample manually.
2021-08-19 09:29:32 +03:00
Muzychenko Andrey d5b44e44e1 Improved wav duration calculation - now supports sample rates other than 11025.
Bumped build tool version to VS2019.
2021-08-18 12:44:26 +03:00
Muzychenko Andrey dcd488c48c Merge pull request #5 from GeorgeMcMullen/midifix
Fix to the routine that opens the MIDI file.
2021-08-18 10:08:55 +03:00
George McMullen db08631ab9 Fix to the routine that opens the MIDI file.
In the original source code for Space Cadet and its related games, the MIDI sound track is opened with MCI_OPEN_TYPE. According to Microsoft's documentation (https://docs.microsoft.com/en-us/windows/win32/multimedia/mci-open), this is for opening devices and not files. Windows' libraries were obviously robust enough to accommodate the error, but other platforms (i.e. WINE) expects things to be called the right way. The simple fix is to switch out MCI_OPEN_TYPE with MCI_OPEN_ELEMENT and move the info for the filename to the lpstrElementName variable.
2021-08-16 05:39:18 -07:00
Muzychenko Andrey bad55d49cf Updated readme. 2021-02-20 15:58:16 +03:00
Muzychenko Andrey 98f234fce3 Replaced GlobalAlloc with malloc.
WaveMix keeps GlobalAlloc for authenticity.
Fixed float to double casts.
Some cleanup.
2021-02-18 12:53:25 +03:00
Muzychenko Andrey 55984fbb08 Fixed x64 build warnings.
Replaced __intN with intN_t.
Some cleanup.
2021-02-16 19:03:45 +03:00
Muzychenko Andrey 5c3e9fea4c Added FT music loader.
Fixed mouse lock.
Some cleanup.
2021-02-15 18:55:54 +03:00
Muzychenko Andrey debe52c1e0 Added scalable window, mouse controls. 2021-02-09 18:09:44 +03:00
Muzychenko Andrey 1c5256a4c6 Added table resolution support.
Only works with FT .dat file.
FT music does not work - different format.
2021-02-06 16:53:47 +03:00
Muzychenko Andrey d594f5fdb7 Converted memory to direct pointers.
Fixed memory leaks in uninit.
Fixed some of the code analysis warnings.
Enabled /MP build.
Cleaned up the code.
2021-02-02 18:29:54 +03:00
Muzychenko Andrey 49f6132d23 Added loader for Full Tilt .dat files, v1.
Works with some data hacks in lowest resolution.
Seems to work ok, even though BL is still 3DPB.
2021-01-31 17:29:53 +03:00
Muzychenko Andrey 6ff457eb68 Cleaned up objlist_class. 2021-01-30 14:19:25 +03:00
Muzychenko Andrey 5b9a1ff95d Create LICENSE
Some docs might be incompatible with MIT, to be reviewed later.
2021-01-29 19:47:23 +03:00
Muzychenko Andrey 232f24a2a2 Added Readme.md 2021-01-29 19:34:45 +03:00
oz efc56e82d9 Compile releases with Windows subsystem. 2021-01-29 17:49:00 +03:00
187 changed files with 75263 additions and 14911 deletions

No files matched your search

+23
View File
@@ -0,0 +1,23 @@
name: Make Release Builds
on: [workflow_dispatch]
jobs:
build-macos:
runs-on: macos-12
timeout-minutes: 15
steps:
- uses: actions/checkout@v4
with:
ref: master
- uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: 13
- run: bash build-mac-app.sh
- uses: actions/upload-artifact@v4
with:
name: mac-build
path: SpaceCadetPinball-*-mac.dmg
+37
View File
@@ -21,6 +21,9 @@ bld/
[Bb]in/
[Oo]bj/
[Ll]og/
ARM/
ARM64/
win32/
# Visual Studio 2015 cache/options directory
.vs/
@@ -263,3 +266,37 @@ __pycache__/
/Export
/DrMem
/Doc private
# Windows and macOS local libraries
/Libs
#CMake generated
out/
/cmake-build-debug
# Windows XP stuff
DebugWinXp/
ReleaseWinXp/
### CMake ###
CMakeLists.txt.user
CMakeCache.txt
CMakeFiles
CMakeScripts
Testing
Makefile
cmake_install.cmake
install_manifest.txt
compile_commands.json
CTestTestfile.cmake
_deps
### CMake Patch ###
# External projects
*-prefix/
### Ninja ###
.ninja_deps
.ninja_log
build.ninja
# Build directory
/build
+20
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
+247
View File
@@ -0,0 +1,247 @@
cmake_minimum_required(VERSION 3.0...3.5)
project(SpaceCadetPinball)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/bin)
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/CMakeModules")
# On Windows, set paths to SDL-devel packages here
if(WIN32)
list(APPEND SDL2_PATH "${CMAKE_CURRENT_LIST_DIR}/Libs/SDL2")
list(APPEND SDL2_MIXER_PATH "${CMAKE_CURRENT_LIST_DIR}/Libs/SDL2_mixer")
endif()
# Link mingw libs static
if(MINGW)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -static")
endif()
if(APPLE)
set(MACOSX_RPATH)
set(CMAKE_BUILD_WITH_INSTALL_RPATH true)
set(CMAKE_INSTALL_RPATH "@executable_path/../Frameworks")
set(CMAKE_OSX_DEPLOYMENT_TARGET "10.11")
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)
find_package(SDL2 REQUIRED)
FIND_PACKAGE(SDL2_mixer REQUIRED)
include_directories(${SDL2_INCLUDE_DIR} ${SDL2_MIXER_INCLUDE_DIR})
get_property(dirs DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY INCLUDE_DIRECTORIES)
foreach(dir ${dirs})
message(STATUS "Include dir='${dir}'")
endforeach()
set(SOURCE_FILES
SpaceCadetPinball/control.cpp
SpaceCadetPinball/control.h
SpaceCadetPinball/EmbeddedData.cpp
SpaceCadetPinball/EmbeddedData.h
SpaceCadetPinball/font_selection.cpp
SpaceCadetPinball/font_selection.h
SpaceCadetPinball/fullscrn.cpp
SpaceCadetPinball/fullscrn.h
SpaceCadetPinball/gdrv.cpp
SpaceCadetPinball/gdrv.h
SpaceCadetPinball/GroupData.cpp
SpaceCadetPinball/GroupData.h
SpaceCadetPinball/high_score.cpp
SpaceCadetPinball/high_score.h
SpaceCadetPinball/loader.cpp
SpaceCadetPinball/loader.h
SpaceCadetPinball/maths.cpp
SpaceCadetPinball/maths.h
SpaceCadetPinball/midi.cpp
SpaceCadetPinball/midi.h
SpaceCadetPinball/nudge.cpp
SpaceCadetPinball/nudge.h
SpaceCadetPinball/options.cpp
SpaceCadetPinball/options.h
SpaceCadetPinball/partman.cpp
SpaceCadetPinball/partman.h
SpaceCadetPinball/pb.cpp
SpaceCadetPinball/pb.h
SpaceCadetPinball/pch.h
SpaceCadetPinball/proj.cpp
SpaceCadetPinball/proj.h
SpaceCadetPinball/render.cpp
SpaceCadetPinball/render.h
SpaceCadetPinball/score.cpp
SpaceCadetPinball/score.h
SpaceCadetPinball/Sound.cpp
SpaceCadetPinball/Sound.h
SpaceCadetPinball/SpaceCadetPinball.cpp
SpaceCadetPinball/TBall.cpp
SpaceCadetPinball/TBall.h
SpaceCadetPinball/TBlocker.cpp
SpaceCadetPinball/TBlocker.h
SpaceCadetPinball/TBumper.cpp
SpaceCadetPinball/TBumper.h
SpaceCadetPinball/TCircle.cpp
SpaceCadetPinball/TCircle.h
SpaceCadetPinball/TCollisionComponent.cpp
SpaceCadetPinball/TCollisionComponent.h
SpaceCadetPinball/TComponentGroup.cpp
SpaceCadetPinball/TComponentGroup.h
SpaceCadetPinball/TDemo.cpp
SpaceCadetPinball/TDemo.h
SpaceCadetPinball/TDrain.cpp
SpaceCadetPinball/TDrain.h
SpaceCadetPinball/TEdgeBox.h
SpaceCadetPinball/TEdgeManager.cpp
SpaceCadetPinball/TEdgeManager.h
SpaceCadetPinball/TEdgeSegment.cpp
SpaceCadetPinball/TEdgeSegment.h
SpaceCadetPinball/TFlagSpinner.cpp
SpaceCadetPinball/TFlagSpinner.h
SpaceCadetPinball/TFlipper.cpp
SpaceCadetPinball/TFlipper.h
SpaceCadetPinball/TFlipperEdge.cpp
SpaceCadetPinball/TFlipperEdge.h
SpaceCadetPinball/TGate.cpp
SpaceCadetPinball/TGate.h
SpaceCadetPinball/THole.cpp
SpaceCadetPinball/THole.h
SpaceCadetPinball/timer.cpp
SpaceCadetPinball/timer.h
SpaceCadetPinball/TKickback.cpp
SpaceCadetPinball/TKickback.h
SpaceCadetPinball/TKickout.cpp
SpaceCadetPinball/TKickout.h
SpaceCadetPinball/TLight.cpp
SpaceCadetPinball/TLight.h
SpaceCadetPinball/TLightBargraph.cpp
SpaceCadetPinball/TLightBargraph.h
SpaceCadetPinball/TLightGroup.cpp
SpaceCadetPinball/TLightGroup.h
SpaceCadetPinball/TLightRollover.cpp
SpaceCadetPinball/TLightRollover.h
SpaceCadetPinball/TLine.cpp
SpaceCadetPinball/TLine.h
SpaceCadetPinball/TOneway.cpp
SpaceCadetPinball/TOneway.h
SpaceCadetPinball/TPinballComponent.cpp
SpaceCadetPinball/TPinballComponent.h
SpaceCadetPinball/TPinballTable.cpp
SpaceCadetPinball/TPinballTable.h
SpaceCadetPinball/TPlunger.cpp
SpaceCadetPinball/TPlunger.h
SpaceCadetPinball/TPopupTarget.cpp
SpaceCadetPinball/TPopupTarget.h
SpaceCadetPinball/TRamp.cpp
SpaceCadetPinball/TRamp.h
SpaceCadetPinball/translations.cpp
SpaceCadetPinball/translations.h
SpaceCadetPinball/TRollover.cpp
SpaceCadetPinball/TRollover.h
SpaceCadetPinball/TSink.cpp
SpaceCadetPinball/TSink.h
SpaceCadetPinball/TSoloTarget.cpp
SpaceCadetPinball/TSoloTarget.h
SpaceCadetPinball/TSound.cpp
SpaceCadetPinball/TSound.h
SpaceCadetPinball/TTableLayer.cpp
SpaceCadetPinball/TTableLayer.h
SpaceCadetPinball/TTextBox.cpp
SpaceCadetPinball/TTextBox.h
SpaceCadetPinball/TTextBoxMessage.cpp
SpaceCadetPinball/TTextBoxMessage.h
SpaceCadetPinball/TTimer.cpp
SpaceCadetPinball/TTimer.h
SpaceCadetPinball/TTripwire.cpp
SpaceCadetPinball/TTripwire.h
SpaceCadetPinball/TWall.cpp
SpaceCadetPinball/TWall.h
SpaceCadetPinball/winmain.cpp
SpaceCadetPinball/winmain.h
SpaceCadetPinball/zdrv.cpp
SpaceCadetPinball/zdrv.h
SpaceCadetPinball/imconfig.h
SpaceCadetPinball/imgui_internal.h
SpaceCadetPinball/imgui.cpp
SpaceCadetPinball/imgui.h
SpaceCadetPinball/imgui_sdl.cpp
SpaceCadetPinball/imgui_sdl.h
SpaceCadetPinball/imgui_draw.cpp
SpaceCadetPinball/imgui_widgets.cpp
SpaceCadetPinball/imgui_tables.cpp
SpaceCadetPinball/imgui_demo.cpp
SpaceCadetPinball/imgui_impl_sdl.cpp
SpaceCadetPinball/imgui_impl_sdl.h
SpaceCadetPinball/imgui_impl_sdlrenderer.h
SpaceCadetPinball/imgui_impl_sdlrenderer.cpp
SpaceCadetPinball/imstb_textedit.h
SpaceCadetPinball/imstb_rectpack.h
SpaceCadetPinball/imstb_truetype.h
SpaceCadetPinball/DebugOverlay.cpp
SpaceCadetPinball/DebugOverlay.h
)
# On Windows, include resource file with the icon
if(WIN32)
set_source_files_properties(SpaceCadetPinball/SpaceCadetPinball.rc LANGUAGE RC)
list(APPEND SOURCE_FILES SpaceCadetPinball/SpaceCadetPinball.rc)
endif(WIN32)
add_executable(SpaceCadetPinball ${SOURCE_FILES})
# Skip pch on foreign code
set_source_files_properties(
SpaceCadetPinball/imgui.cpp
SpaceCadetPinball/imgui_sdl.cpp
SpaceCadetPinball/imgui_draw.cpp
SpaceCadetPinball/imgui_widgets.cpp
SpaceCadetPinball/imgui_tables.cpp
SpaceCadetPinball/imgui_demo.cpp
SpaceCadetPinball/imgui_impl_sdl.cpp
SpaceCadetPinball/imgui_impl_sdlrenderer.cpp
PROPERTIES SKIP_PRECOMPILE_HEADERS 1
)
if(${CMAKE_VERSION} VERSION_GREATER "3.16.0" OR ${CMAKE_VERSION} VERSION_EQUAL "3.16.0")
target_precompile_headers(SpaceCadetPinball
PUBLIC
SpaceCadetPinball/pch.h
)
endif()
target_link_libraries(SpaceCadetPinball ${SDL2_LIBRARY} ${SDL2_MIXER_LIBRARY})
# On Windows, copy DLL to output
if(WIN32)
list(GET SDL2_LIBRARY -1 SDL2_DLL_PATH)
list(GET SDL2_MIXER_LIBRARY -1 SDL2_MIXER_DLL_PATH)
get_filename_component(SDL2_DLL_PATH ${SDL2_DLL_PATH} DIRECTORY)
get_filename_component(SDL2_MIXER_DLL_PATH ${SDL2_MIXER_DLL_PATH} DIRECTORY)
if(MINGW)
string(REGEX REPLACE "lib$" "bin" SDL2_DLL_PATH ${SDL2_DLL_PATH})
string(REGEX REPLACE "lib$" "bin" SDL2_MIXER_DLL_PATH ${SDL2_MIXER_DLL_PATH})
endif()
message(STATUS "copy paths='${SDL2_DLL_PATH}' '${SDL2_MIXER_DLL_PATH}'")
add_custom_command(TARGET SpaceCadetPinball POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different "${SDL2_DLL_PATH}/SDL2.dll" $<TARGET_FILE_DIR:SpaceCadetPinball>
COMMAND ${CMAKE_COMMAND} -E copy_if_different "${SDL2_MIXER_DLL_PATH}/SDL2_mixer.dll" $<TARGET_FILE_DIR:SpaceCadetPinball>
)
endif()
if(UNIX AND NOT APPLE)
include(GNUInstallDirs)
install(TARGETS "${PROJECT_NAME}" RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}")
install(FILES "${CMAKE_SOURCE_DIR}/Platform/Linux/${PROJECT_NAME}.desktop" DESTINATION "share/applications")
install(FILES "${CMAKE_SOURCE_DIR}/Platform/Linux/${PROJECT_NAME}.metainfo.xml" DESTINATION "share/metainfo")
foreach(S 16 32 48 128 192)
install(FILES "${CMAKE_SOURCE_DIR}/${PROJECT_NAME}/Icon_${S}x${S}.png" DESTINATION
"share/icons/hicolor/${S}x${S}/apps" RENAME "${PROJECT_NAME}.png")
endforeach(S)
endif()
+173
View File
@@ -0,0 +1,173 @@
# This module defines
# SDL2_LIBRARY, the name of the library to link against
# SDL2_FOUND, if false, do not try to link to SDL2
# SDL2_INCLUDE_DIR, where to find SDL.h
#
# This module responds to the the flag:
# SDL2_BUILDING_LIBRARY
# If this is defined, then no SDL2main will be linked in because
# only applications need main().
# Otherwise, it is assumed you are building an application and this
# module will attempt to locate and set the the proper link flags
# as part of the returned SDL2_LIBRARY variable.
#
# Don't forget to include SDLmain.h and SDLmain.m your project for the
# OS X framework based version. (Other versions link to -lSDL2main which
# this module will try to find on your behalf.) Also for OS X, this
# module will automatically add the -framework Cocoa on your behalf.
#
#
# Additional Note: If you see an empty SDL2_LIBRARY_TEMP in your configuration
# and no SDL2_LIBRARY, it means CMake did not find your SDL2 library
# (SDL2.dll, libsdl2.so, SDL2.framework, etc).
# Set SDL2_LIBRARY_TEMP to point to your SDL2 library, and configure again.
# Similarly, if you see an empty SDL2MAIN_LIBRARY, you should set this value
# as appropriate. These values are used to generate the final SDL2_LIBRARY
# variable, but when these values are unset, SDL2_LIBRARY does not get created.
#
#
# $SDL2DIR is an environment variable that would
# correspond to the ./configure --prefix=$SDL2DIR
# used in building SDL2.
# l.e.galup 9-20-02
#
# Modified by Eric Wing.
# Added code to assist with automated building by using environmental variables
# and providing a more controlled/consistent search behavior.
# Added new modifications to recognize OS X frameworks and
# additional Unix paths (FreeBSD, etc).
# Also corrected the header search path to follow "proper" SDL guidelines.
# Added a search for SDL2main which is needed by some platforms.
# Added a search for threads which is needed by some platforms.
# Added needed compile switches for MinGW.
#
# On OSX, this will prefer the Framework version (if found) over others.
# People will have to manually change the cache values of
# SDL2_LIBRARY to override this selection or set the CMake environment
# CMAKE_INCLUDE_PATH to modify the search paths.
#
# Note that the header path has changed from SDL2/SDL.h to just SDL.h
# This needed to change because "proper" SDL convention
# is #include "SDL.h", not <SDL2/SDL.h>. This is done for portability
# reasons because not all systems place things in SDL2/ (see FreeBSD).
#=============================================================================
# Copyright 2003-2009 Kitware, Inc.
#
# Distributed under the OSI-approved BSD License (the "License");
# see accompanying file Copyright.txt for details.
#
# This software is distributed WITHOUT ANY WARRANTY; without even the
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the License for more information.
#=============================================================================
# (To distribute this file outside of CMake, substitute the full
# License text for the above reference.)
# message("<FindSDL2.cmake>")
SET(SDL2_SEARCH_PATHS
~/Library/Frameworks
/Library/Frameworks
/usr/local
/usr
/sw # Fink
/opt/local # DarwinPorts
/opt/csw # Blastwave
/opt
${SDL2_PATH}
)
FIND_PATH(SDL2_INCLUDE_DIR SDL.h
HINTS
$ENV{SDL2DIR}
PATH_SUFFIXES include/SDL2 include SDL2
PATHS ${SDL2_SEARCH_PATHS}
)
if(CMAKE_SIZEOF_VOID_P EQUAL 8)
set(PATH_SUFFIXES lib64 lib/x64 lib)
else()
set(PATH_SUFFIXES lib/x86 lib)
endif()
FIND_LIBRARY(SDL2_LIBRARY_TEMP
NAMES SDL2
HINTS
$ENV{SDL2DIR}
PATH_SUFFIXES ${PATH_SUFFIXES}
PATHS ${SDL2_SEARCH_PATHS}
)
IF(NOT SDL2_BUILDING_LIBRARY)
IF(NOT ${SDL2_INCLUDE_DIR} MATCHES ".framework")
# Non-OS X framework versions expect you to also dynamically link to
# SDL2main. This is mainly for Windows and OS X. Other (Unix) platforms
# seem to provide SDL2main for compatibility even though they don't
# necessarily need it.
FIND_LIBRARY(SDL2MAIN_LIBRARY
NAMES SDL2main
HINTS
$ENV{SDL2DIR}
PATH_SUFFIXES ${PATH_SUFFIXES}
PATHS ${SDL2_SEARCH_PATHS}
)
ENDIF(NOT ${SDL2_INCLUDE_DIR} MATCHES ".framework")
ENDIF(NOT SDL2_BUILDING_LIBRARY)
# SDL2 may require threads on your system.
# The Apple build may not need an explicit flag because one of the
# frameworks may already provide it.
# But for non-OSX systems, I will use the CMake Threads package.
IF(NOT APPLE)
FIND_PACKAGE(Threads)
ENDIF(NOT APPLE)
# MinGW needs an additional link flag, -mwindows
# It's total link flags should look like -lmingw32 -lSDL2main -lSDL2 -mwindows
IF(MINGW)
SET(MINGW32_LIBRARY mingw32 "-mwindows" CACHE STRING "mwindows for MinGW")
ENDIF(MINGW)
IF(SDL2_LIBRARY_TEMP)
# For SDL2main
IF(NOT SDL2_BUILDING_LIBRARY)
IF(SDL2MAIN_LIBRARY)
SET(SDL2_LIBRARY_TEMP ${SDL2MAIN_LIBRARY} ${SDL2_LIBRARY_TEMP})
ENDIF(SDL2MAIN_LIBRARY)
ENDIF(NOT SDL2_BUILDING_LIBRARY)
# For OS X, SDL2 uses Cocoa as a backend so it must link to Cocoa.
# CMake doesn't display the -framework Cocoa string in the UI even
# though it actually is there if I modify a pre-used variable.
# I think it has something to do with the CACHE STRING.
# So I use a temporary variable until the end so I can set the
# "real" variable in one-shot.
IF(APPLE)
SET(SDL2_LIBRARY_TEMP ${SDL2_LIBRARY_TEMP} "-framework Cocoa")
ENDIF(APPLE)
# For threads, as mentioned Apple doesn't need this.
# In fact, there seems to be a problem if I used the Threads package
# and try using this line, so I'm just skipping it entirely for OS X.
IF(NOT APPLE)
SET(SDL2_LIBRARY_TEMP ${CMAKE_THREAD_LIBS_INIT} ${SDL2_LIBRARY_TEMP})
ENDIF(NOT APPLE)
# For MinGW library
IF(MINGW)
SET(SDL2_LIBRARY_TEMP ${MINGW32_LIBRARY} ${SDL2_LIBRARY_TEMP})
ENDIF(MINGW)
# Set the final string here so the GUI reflects the final state.
SET(SDL2_LIBRARY ${SDL2_LIBRARY_TEMP} CACHE STRING "Where the SDL2 Library can be found")
# Set the temp variable to INTERNAL so it is not seen in the CMake GUI
SET(SDL2_LIBRARY_TEMP "${SDL2_LIBRARY_TEMP}" CACHE INTERNAL "")
ENDIF(SDL2_LIBRARY_TEMP)
# message("</FindSDL2.cmake>")
INCLUDE(FindPackageHandleStandardArgs)
FIND_PACKAGE_HANDLE_STANDARD_ARGS(SDL2 REQUIRED_VARS SDL2_LIBRARY SDL2_INCLUDE_DIR)
+100
View File
@@ -0,0 +1,100 @@
# Locate SDL_MIXER library
#
# This module defines:
#
# ::
#
# SDL2_MIXER_LIBRARIES, the name of the library to link against
# SDL2_MIXER_INCLUDE_DIRS, where to find the headers
# SDL2_MIXER_FOUND, if false, do not try to link against
# SDL2_MIXER_VERSION_STRING - human-readable string containing the version of SDL_MIXER
#
#
#
# For backward compatibility the following variables are also set:
#
# ::
#
# SDLMIXER_LIBRARY (same value as SDL2_MIXER_LIBRARIES)
# SDLMIXER_INCLUDE_DIR (same value as SDL2_MIXER_INCLUDE_DIRS)
# SDLMIXER_FOUND (same value as SDL2_MIXER_FOUND)
#
#
#
# $SDLDIR is an environment variable that would correspond to the
# ./configure --prefix=$SDLDIR used in building SDL.
#
# Created by Eric Wing. This was influenced by the FindSDL.cmake
# module, but with modifications to recognize OS X frameworks and
# additional Unix paths (FreeBSD, etc).
#=============================================================================
# Copyright 2005-2009 Kitware, Inc.
# Copyright 2012 Benjamin Eikel
#
# Distributed under the OSI-approved BSD License (the "License");
# see accompanying file Copyright.txt for details.
#
# This software is distributed WITHOUT ANY WARRANTY; without even the
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the License for more information.
#=============================================================================
# (To distribute this file outside of CMake, substitute the full
# License text for the above reference.)
find_path(SDL2_MIXER_INCLUDE_DIR SDL_mixer.h
HINTS
ENV SDL2MIXERDIR
ENV SDL2DIR
PATH_SUFFIXES SDL2
# path suffixes to search inside ENV{SDLDIR}
include/SDL2 include
PATHS ${SDL2_MIXER_PATH}
)
if(CMAKE_SIZEOF_VOID_P EQUAL 8)
set(VC_LIB_PATH_SUFFIX lib/x64)
else()
set(VC_LIB_PATH_SUFFIX lib/x86)
endif()
find_library(SDL2_MIXER_LIBRARY
NAMES SDL2_mixer
HINTS
ENV SDL2MIXERDIR
ENV SDL2DIR
PATH_SUFFIXES lib bin ${VC_LIB_PATH_SUFFIX}
PATHS ${SDL2_MIXER_PATH}
)
if(SDL2_MIXER_INCLUDE_DIR AND EXISTS "${SDL2_MIXER_INCLUDE_DIR}/SDL_mixer.h")
file(STRINGS "${SDL2_MIXER_INCLUDE_DIR}/SDL_mixer.h" SDL2_MIXER_VERSION_MAJOR_LINE REGEX "^#define[ \t]+SDL_MIXER_MAJOR_VERSION[ \t]+[0-9]+$")
file(STRINGS "${SDL2_MIXER_INCLUDE_DIR}/SDL_mixer.h" SDL2_MIXER_VERSION_MINOR_LINE REGEX "^#define[ \t]+SDL_MIXER_MINOR_VERSION[ \t]+[0-9]+$")
file(STRINGS "${SDL2_MIXER_INCLUDE_DIR}/SDL_mixer.h" SDL2_MIXER_VERSION_PATCH_LINE REGEX "^#define[ \t]+SDL_MIXER_PATCHLEVEL[ \t]+[0-9]+$")
string(REGEX REPLACE "^#define[ \t]+SDL_MIXER_MAJOR_VERSION[ \t]+([0-9]+)$" "\\1" SDL2_MIXER_VERSION_MAJOR "${SDL2_MIXER_VERSION_MAJOR_LINE}")
string(REGEX REPLACE "^#define[ \t]+SDL_MIXER_MINOR_VERSION[ \t]+([0-9]+)$" "\\1" SDL2_MIXER_VERSION_MINOR "${SDL2_MIXER_VERSION_MINOR_LINE}")
string(REGEX REPLACE "^#define[ \t]+SDL_MIXER_PATCHLEVEL[ \t]+([0-9]+)$" "\\1" SDL2_MIXER_VERSION_PATCH "${SDL2_MIXER_VERSION_PATCH_LINE}")
set(SDL2_MIXER_VERSION_STRING ${SDL2_MIXER_VERSION_MAJOR}.${SDL2_MIXER_VERSION_MINOR}.${SDL2_MIXER_VERSION_PATCH})
unset(SDL2_MIXER_VERSION_MAJOR_LINE)
unset(SDL2_MIXER_VERSION_MINOR_LINE)
unset(SDL2_MIXER_VERSION_PATCH_LINE)
unset(SDL2_MIXER_VERSION_MAJOR)
unset(SDL2_MIXER_VERSION_MINOR)
unset(SDL2_MIXER_VERSION_PATCH)
endif()
set(SDL2_MIXER_LIBRARIES ${SDL2_MIXER_LIBRARY})
set(SDL2_MIXER_INCLUDE_DIRS ${SDL2_MIXER_INCLUDE_DIR})
include(FindPackageHandleStandardArgs)
FIND_PACKAGE_HANDLE_STANDARD_ARGS(SDL2_mixer
REQUIRED_VARS SDL2_MIXER_LIBRARIES SDL2_MIXER_INCLUDE_DIRS
VERSION_VAR SDL2_MIXER_VERSION_STRING)
# for backward compatibility
set(SDLMIXER_LIBRARY ${SDL2_MIXER_LIBRARIES})
set(SDLMIXER_INCLUDE_DIR ${SDL2_MIXER_INCLUDE_DIRS})
set(SDLMIXER_FOUND ${SDL2_MIXER_FOUND})
mark_as_advanced(SDL2_MIXER_LIBRARY SDL2_MIXER_INCLUDE_DIR)
+74
View File
@@ -0,0 +1,74 @@
{
"configurations": [
{
"name": "x64-Debug",
"generator": "Ninja",
"configurationType": "Debug",
"inheritEnvironments": [ "msvc_x64_x64" ],
"buildRoot": "${projectDir}\\out\\build\\${name}",
"installRoot": "${projectDir}\\out\\install\\${name}",
"cmakeCommandArgs": "",
"buildCommandArgs": "",
"ctestCommandArgs": "",
"addressSanitizerEnabled": false
},
{
"name": "x86-Debug",
"generator": "Ninja",
"configurationType": "Debug",
"buildRoot": "${projectDir}\\out\\build\\${name}",
"installRoot": "${projectDir}\\out\\install\\${name}",
"cmakeCommandArgs": "",
"buildCommandArgs": "",
"ctestCommandArgs": "",
"inheritEnvironments": [ "msvc_x86" ],
"addressSanitizerEnabled": false
},
{
"name": "x86-Release",
"generator": "Ninja",
"configurationType": "Release",
"buildRoot": "${projectDir}\\out\\build\\${name}",
"installRoot": "${projectDir}\\out\\install\\${name}",
"cmakeCommandArgs": "-DCMAKE_WIN32_EXECUTABLE:BOOL=1",
"buildCommandArgs": "",
"ctestCommandArgs": "",
"inheritEnvironments": [ "msvc_x86" ]
},
{
"name": "x64-Release",
"generator": "Ninja",
"configurationType": "Release",
"buildRoot": "${projectDir}\\out\\build\\${name}",
"installRoot": "${projectDir}\\out\\install\\${name}",
"cmakeCommandArgs": "-DCMAKE_WIN32_EXECUTABLE:BOOL=1",
"buildCommandArgs": "",
"ctestCommandArgs": "",
"inheritEnvironments": [ "msvc_x64_x64" ]
},
{
"name": "x86-Release-WinXP",
"generator": "Visual Studio 16 2019",
"configurationType": "Release",
"buildRoot": "${projectDir}\\out\\build\\${name}",
"installRoot": "${projectDir}\\out\\install\\${name}",
"cmakeCommandArgs": "-DCMAKE_WIN32_EXECUTABLE:BOOL=1 -T v141_xp",
"buildCommandArgs": "",
"ctestCommandArgs": "",
"inheritEnvironments": [ "msvc_x86" ]
},
{
"name": "WSL-Clang-Debug",
"generator": "Ninja",
"configurationType": "Debug",
"buildRoot": "${projectDir}\\out\\build\\${name}",
"installRoot": "${projectDir}\\out\\install\\${name}",
"cmakeExecutable": "cmake",
"cmakeCommandArgs": "",
"buildCommandArgs": "",
"ctestCommandArgs": "",
"inheritEnvironments": [ "linux_clang_x64" ],
"wslPath": "${defaultWSLPath}"
}
]
}
+11
View File
@@ -0,0 +1,11 @@
# Issues
Don’t forget to mention game version, which release or branch it came from.\
Source port issues are handled in their respective repositories.
# Pull request
No source ports in main repository.\
I have no way to test and maintain most of them.\
The best I can do is to add a link.
There is no guarantee that any particular PR will be accepted.\
If you are unsure, ask first, make PR second.
-3
View File
@@ -1,3 +0,0 @@
rc /Fo.\DrMem\SpaceCadetPinball.res ".\SpaceCadetPinball\SpaceCadetPinball.rc"
cl /Zi /MT /EHsc /O /Ob0 /cgthreads4 /Fo.\DrMem\ /Fe.\DrMem\myapp.exe ".\SpaceCadetPinball\*.cpp" Comctl32.lib Winmm.lib Htmlhelp.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib ".\DrMem\SpaceCadetPinball.res"
+14 -4
View File
@@ -39,21 +39,21 @@ Type Meaning/comments
9 String (content)
10 Array of 16bits integer values
11 Array of 32bits floating point values (collision box, ...)
12 16 bpp bitmap (Heightmap?)
12 16 bpp bitmap (zMap)
//-- 8bpp bitmap data header --//
+0: Unknown (0) BYTE
+0: Resolution BYTE 0=640x480, 1=800x600, 2=1024x768, -1=Load in all resolutions
+1: Width WORD
+3: Height WORD
+5: X position WORD
+7 Y position WORD
+9: Size of bitmap DWORD
+13: Unknown (1) BYTE
+13: Flags BYTE bit0=Raw bmp align; bit1=DibBitmap, raw when 0; bit2=Spliced bitmap (aka skipline), combines bmp and zMap in RLE-like way
+14: Bitmap data BYTE*(DWORD@+9)
//-- 16bpp bitmap data header --//
//-- 16bpp zMap data header --//
+0: Width WORD
+2: Height WORD
+4: Pitch/2 WORD
@@ -62,6 +62,16 @@ Type Meaning/comments
+12: Unknown (80) WORD
+14: Bitmap data BYTE*(DWORD@+9)
//-- 16bpp zMap data header full tilt --//
+0: Resolution BYTE 0=640x480, 1=800x600, 2=1024x768, -1=Load in all resolutions
+1: Width WORD
+3: Height WORD
+5: Pitch/2 WORD
+7: Unknown (0) DWORD
+11: Unknown (0) WORD
+13: Unknown (80) WORD
+15: Bitmap data BYTE*(DWORD@+9)
//-- Pinball 3D remarkable groups --//
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020-2021 Andrey Muzychenko
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+25
View File
@@ -0,0 +1,25 @@
# Building
Install devel packages for `SDL2` and `SDL2_mixer`.\
Compile with CMake; tested with GCC 10, Clang 11.\
To cross-compile for Windows, install a 64-bit version of mingw and its `SDL2` and `SDL2_mixer` distributions, then use the `mingwcc.cmake` toolchain.
```bash
# On Debian and Ubuntu
sudo apt install cmake build-essential ninja-build libsdl2-dev libsdl2-mixer-dev libsdl2-mixer-2.0-0 libsdl2-2.0-0 fluidsynth
# On Fedora
sudo dnf install cmake ninja-build SDL2 SDL2-devel SDL2_mixer SDL2_mixer-devel fluidsynth fluidsynth-libs mscore-fonts g++
# Build
cmake -GNinja .
ninja
```
**If you can't hear the background music**: You need to export the following variable before launching the game like this:
`export SDL_SOUNDFONT=/path/to/your/soundfont`
Fluidsynth will install a default soundfont in the following path:
On Fedora: `/usr/share/soundfonts/FluidR3_GM.sf2`
On Ubuntu/Debian: `/usr/share/sounds/sf2/FluidR3_GM.sf2`
+10
View File
@@ -0,0 +1,10 @@
[Desktop Entry]
Version=1.0
Name=Space Cadet Pinball
Comment=Decompilation of 3D Pinball for Windows - Space Cadet
Exec=SpaceCadetPinball
StartupNotify=true
Terminal=false
Icon=SpaceCadetPinball
Type=Application
Categories=Game;ArcadeGame;
@@ -0,0 +1,95 @@
<?xml version="1.0" encoding="UTF-8"?>
<component type="desktop-application">
<id>com.github.k4zmu2a.spacecadetpinball</id>
<name>Space Cadet Pinball</name>
<display_name>Space Cadet Pinball</display_name>
<summary>Game engine for Space Cadet Pinball</summary>
<url type="homepage">https://github.com/k4zmu2a/SpaceCadetPinball</url>
<url type="bugtracker">https://github.com/k4zmu2a/SpaceCadetPinball/issues</url>
<description>
<p>
Reverse engineering of '3D Pinball for Windows - Space Cadet', a game bundled with Windows.
</p>
</description>
<categories>
<category>Game</category>
<category>ArcadeGame</category>
</categories>
<launchable type="desktop-id">SpaceCadetPinball.desktop</launchable>
<provides>
<binary>SpaceCadetPinball</binary>
</provides>
<screenshots>
<screenshot type="default">
<image>https://raw.githubusercontent.com/k4zmu2a/SpaceCadetPinball/master/Screenshots/screenshot-1.jpg</image>
</screenshot>
<screenshot>
<image>https://raw.githubusercontent.com/k4zmu2a/SpaceCadetPinball/master/Screenshots/screenshot-2.jpg</image>
</screenshot>
</screenshots>
<releases>
<release version="2.1.0" date="2023-10-16">
<description>
<p>Feature release focusing on FT compatibility.</p>
<p>Main highlights:</p>
<ul>
<li>FT: collision system changes, table control changes, multiball, three MIDI tracks.</li>
<li>Work in progress: localization support. Depends on external font selection for most languages.</li>
<li>HW accelerated ImGui renderer.</li>
<li>Experimental stereo sound support.</li>
<li>UX improvements.</li>
<li>Bug fixes.</li>
</ul>
</description>
</release>
<release version="2.0.1" date="2021-12-29">
<description>
<p>First bug fix release of cross-platform port.</p>
<p>Main highlights:</p>
<ul>
<li>Improved Linux support: install target, icons, desktop shortcut, alternative data paths.</li>
<li>Improved game controller support.</li>
<li>High precision sleep mode for ~0 frame time jitter.</li>
<li>Integer image scaling mode.</li>
<li>3DPB &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>
<p>Main highlights:</p>
<ul>
<li>Windows dependencies removed.</li>
<li>Graphics and sounds rendered with SDL.</li>
<li>Dear ImGui GUI.</li>
<li>CMake build script, compiles on a lot of platforms.</li>
<li>A number of 3rd party source ports.</li>
</ul>
<p>All features of v1.x are present, except for text translations.</p>
<p>
Development and releases of v1.x will continue, don’t confuse the two.
All source ports are technically v2.0RC, not v1.x.
</p>
<p>There are no Linux or macOS binaries attached, compile them yourself.</p>
<p>
This release was made better with the help of contributors.
I thank them for their interest in the project.
</p>
</description>
</release>
</releases>
<metadata_license>CC0-1.0</metadata_license>
<developer_name>Andrey Muzychenko</developer_name>
<project_license>MIT</project_license>
<keywords>
<keyword>pinball</keyword>
<keyword>space</keyword>
<keyword>cadet</keyword>
</keywords>
<content_rating type="oars-1.1" />
</component>
+36
View File
@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>English</string>
<key>CFBundleExecutable</key>
<string>SpaceCadetPinball</string>
<key>CFBundleIconFile</key>
<string>SpaceCadetPinball.icns</string>
<key>CFBundleIdentifier</key>
<string>com.github.k4zmu2a.spacecadetpinball</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleLongVersionString</key>
<string></string>
<key>CFBundleName</key>
<string>SpaceCadetPinball</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>CHANGEME_SW_VERSION</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleSupportedPlatforms</key>
<array>
<string>MacOSX</string>
</array>
<key>CSResourcesFileMapped</key>
<true/>
<key>LSMinimumSystemVersion</key>
<string>10.11</string>
<key>NSHighResolutionCapable</key>
<true/>
</dict>
</plist>
Binary file not shown.
+123
View File
@@ -0,0 +1,123 @@
<!-- markdownlint-disable-file MD033 -->
# SpaceCadetPinball
## Summary
Reverse engineering of `3D Pinball for Windows - Space Cadet`, a game bundled with Windows.
## How to play
Place compiled executable into a folder containing original game resources (not included).\
Supports data files from Windows and Full Tilt versions of the game.
## Known source ports
| Platform | Author | URL |
| ------------------ | --------------- | ---------------------------------------------------------------------------------------------------------- |
| PS Vita | Axiom | <https://github.com/suicvne/SpaceCadetPinball_Vita> |
| Emscripten | alula | <https://github.com/alula/SpaceCadetPinball> <br> Play online: <https://alula.github.io/SpaceCadetPinball> |
| Nintendo Switch | averne | <https://github.com/averne/SpaceCadetPinball-NX> |
| webOS TV | mariotaku | <https://github.com/webosbrew/SpaceCadetPinball> |
| Android (WIP) | Iscle | https://github.com/Iscle/SpaceCadetPinball |
| Nintendo Wii | MaikelChan | https://github.com/MaikelChan/SpaceCadetPinball |
| Nintendo 3DS | MaikelChan | https://github.com/MaikelChan/SpaceCadetPinball/tree/3ds |
| Nintendo DS | Headshotnoby | https://github.com/headshot2017/3dpinball-nds |
| Nintendo Wii U | IntriguingTiles | https://github.com/IntriguingTiles/SpaceCadetPinball-WiiU |
| PlayStation 2 | Headshotnoby | https://github.com/headshot2017/3dpinball-ps2 |
| Sega Dreamcast | Headshotnoby | https://github.com/headshot2017/3dpinball-dc |
| MorphOS | BeWorld | https://www.morphos-storage.net/?id=1688897 |
| AmigaOS 4 | rjd324 | http://aminet.net/package/game/actio/spacecadetpinball-aos4 |
| Android (WIP) | fexed | https://github.com/fexed/Pinball-on-Android |
Platforms covered by this project: desktop Windows, Linux and macOS.
<br>
<br>
<br>
<br>
<br>
<br>
## Source
* `pinball.exe` from `Windows XP` (SHA-1 `2A5B525E0F631BB6107639E2A69DF15986FB0D05`) and its public PDB
* `CADET.EXE` 32bit version from `Full Tilt! Pinball` (SHA-1 `3F7B5699074B83FD713657CD94671F2156DBEDC4`)
## Tools used
`Ghidra`, `Ida`, `Visual Studio`
## What was done
* All structures were populated, globals and locals named.
* All subs were decompiled, C pseudo code was converted to compilable C++. Loose (namespace?) subs were assigned to classes.
## Compiling
Project uses `C++11` and depends on `SDL2` libs.
### On Windows
Download and unpack devel packages for `SDL2` and `SDL2_mixer`.\
Set paths to them in `CMakeLists.txt`, see suggested placement in `/Libs`.\
Compile with Visual Studio; tested with 2019.
### On Linux
Install devel packages for `SDL2` and `SDL2_mixer`.\
Compile with CMake; tested with GCC 10, Clang 11.\
To cross-compile for Windows, install a 64-bit version of mingw and its `SDL2` and `SDL2_mixer` distributions, then use the `mingwcc.cmake` toolchain.
[![Packaging status](https://repology.org/badge/tiny-repos/spacecadetpinball.svg)](https://repology.org/project/spacecadetpinball/versions)
Some distributions provide a package in their repository. You can use those for easier dependency management and updates.
This project is available as Flatpak on [Flathub](https://flathub.org/apps/details/com.github.k4zmu2a.spacecadetpinball).
### On macOS
Install XCode (or at least Xcode Command Line Tools with `xcode-select --install`) and CMake.
**HomeBrew**
You can easily install the build artifact by using `brew`.
```sh
brew tap draftbrew/tap
brew install --no-quarantine space-cadet-pinball
```
Be aware that the flag `--no-quarantime` will disable macOS's Gatekeeper during installation.
**Manual compilation:**
* **Homebrew**: Install the `SDL2`, `SDL2_mixer` homebrew packages.
* **MacPorts**: Install the `libSDL2`, `libSDL2_mixer` macports packages.
Compile with CMake. Ensure that `CMAKE_OSX_ARCHITECTURES` variable is set for either `x86_64` Apple Intel or `arm64` for Apple Silicon.
Tested with: macOS Big Sur (Intel) with Xcode 13 & macOS Montery Beta (Apple Silicon) with Xcode 13.
**Automated compilation:**
Run the `build-mac-app.sh` script from the root of the repository. The app will be available in a DMG file named `SpaceCadetPinball-<version>-mac.dmg`.
Tested with: macOS Ventura (Apple Silicon) with Xcode Command Line Tools 14 & macOS Big Sur on GitHub Runner (Intel) with XCode 13.
## Plans
* ~~Decompile original game~~
* ~~Resizable window, scaled graphics~~
* ~~Loader for high-res sprites from CADET.DAT~~
* ~~Cross-platform port using SDL2, SDL2_mixer, ImGui~~
* Full Tilt Cadet features
* Localization support
* Maybe: Support for the other two tables - Dragon and Pirate
* Maybe: Game data editor
## On 64-bit bug that killed the game
I did not find it, decompiled game worked in x64 mode on the first try.\
It was either lost in decompilation or introduced in x64 port/not present in x86 build.\
Based on public description of the bug (no ball collision), I guess that the bug was in `TEdgeManager::TestGridBox`
Binary file not shown.

After

Width:  |  Height:  |  Size: 612 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 278 KiB

-31
View File
@@ -1,31 +0,0 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.28307.705
MinimumVisualStudioVersion = 10.0.40219.1
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "SpaceCadetPinball", "SpaceCadetPinball\SpaceCadetPinball.vcxproj", "{F7B78CC7-6984-4F79-9486-ABCF87DF9F06}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{F7B78CC7-6984-4F79-9486-ABCF87DF9F06}.Debug|x64.ActiveCfg = Debug|x64
{F7B78CC7-6984-4F79-9486-ABCF87DF9F06}.Debug|x64.Build.0 = Debug|x64
{F7B78CC7-6984-4F79-9486-ABCF87DF9F06}.Debug|x86.ActiveCfg = Debug|Win32
{F7B78CC7-6984-4F79-9486-ABCF87DF9F06}.Debug|x86.Build.0 = Debug|Win32
{F7B78CC7-6984-4F79-9486-ABCF87DF9F06}.Release|x64.ActiveCfg = Release|x64
{F7B78CC7-6984-4F79-9486-ABCF87DF9F06}.Release|x64.Build.0 = Release|x64
{F7B78CC7-6984-4F79-9486-ABCF87DF9F06}.Release|x86.ActiveCfg = Release|Win32
{F7B78CC7-6984-4F79-9486-ABCF87DF9F06}.Release|x86.Build.0 = Release|Win32
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {4B620E0B-FD42-4AEC-BC4A-261C1669A1B5}
EndGlobalSection
EndGlobal
+358
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);
}
}
+26
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
+10
View File
@@ -0,0 +1,10 @@
#pragma once
class EmbeddedData
{
public:
static const char PB_MSGFT_bin_compressed_data_base85[5380 + 1];
static const unsigned int SDL_GameControllerDB_compressed_data[63392 / 4];
static const unsigned int SDL_GameControllerDB_compressed_size;
};
+361
View File
@@ -0,0 +1,361 @@
#include "pch.h"
#include "GroupData.h"
#include "EmbeddedData.h"
#include "fullscrn.h"
#include "gdrv.h"
#include "pb.h"
#include "zdrv.h"
EntryData::~EntryData()
{
if (Buffer)
{
if (EntryType == FieldTypes::Bitmap8bit)
{
delete reinterpret_cast<gdrv_bitmap8*>(Buffer);
}
else if (EntryType == FieldTypes::Bitmap16bit)
{
delete reinterpret_cast<zmap_header_type*>(Buffer);
}
else
delete[] Buffer;
}
}
GroupData::GroupData(int groupId)
{
GroupId = groupId;
}
void GroupData::AddEntry(EntryData* entry)
{
auto addEntry = true;
switch (entry->EntryType)
{
case FieldTypes::GroupName:
GroupName = entry->Buffer;
break;
case FieldTypes::Bitmap8bit:
{
auto srcBmp = reinterpret_cast<gdrv_bitmap8*>(entry->Buffer);
if (srcBmp->BitmapType == BitmapTypes::Spliced)
{
// Get rid of spliced bitmap early on, to simplify render pipeline
auto bmp = new gdrv_bitmap8(srcBmp->Width, srcBmp->Height, true);
auto zMap = new zmap_header_type(srcBmp->Width, srcBmp->Height, srcBmp->Width);
SplitSplicedBitmap(*srcBmp, *bmp, *zMap);
NeedsSort = true;
addEntry = false;
AddEntry(new EntryData(FieldTypes::Bitmap8bit, reinterpret_cast<char*>(bmp)));
AddEntry(new EntryData(FieldTypes::Bitmap16bit, reinterpret_cast<char*>(zMap)));
delete entry;
}
else
{
SetBitmap(srcBmp);
}
break;
}
case FieldTypes::Bitmap16bit:
{
SetZMap(reinterpret_cast<zmap_header_type*>(entry->Buffer));
break;
}
default: break;
}
if (addEntry)
Entries.push_back(entry);
}
void GroupData::FinalizeGroup()
{
if (NeedsSort)
{
// Entries within a group are sorted by EntryType, in ascending order.
// Dat files follow this rule, zMaps inserted in the middle break it.
NeedsSort = false;
std::sort(Entries.begin(), Entries.end(), [](const EntryData* lhs, const EntryData* rhs)
{
return lhs->EntryType < rhs->EntryType;
});
Entries.shrink_to_fit();
}
}
gdrv_bitmap8* GroupData::GetBitmap(int resolution) const
{
return Bitmaps[resolution];
}
zmap_header_type* GroupData::GetZMap(int resolution) const
{
return ZMaps[resolution];
}
void GroupData::SplitSplicedBitmap(const gdrv_bitmap8& srcBmp, gdrv_bitmap8& bmp, zmap_header_type& zMap)
{
assertm(srcBmp.BitmapType == BitmapTypes::Spliced, "GroupData: wrong bitmap type");
std::memset(bmp.IndexedBmpPtr, 0xff, bmp.Stride * bmp.Height);
bmp.XPosition = srcBmp.XPosition;
bmp.YPosition = srcBmp.YPosition;
bmp.Resolution = srcBmp.Resolution;
zdrv::fill(&zMap, zMap.Width, zMap.Height, 0, 0, 0xFFFF);
zMap.Resolution = srcBmp.Resolution;
auto tableWidth = fullscrn::resolution_array[srcBmp.Resolution].TableWidth;
auto src = reinterpret_cast<uint16_t*>(srcBmp.IndexedBmpPtr);
auto srcChar = reinterpret_cast<char**>(&src);
for (int dstInd = 0;;)
{
auto stride = static_cast<int16_t>(*src++);
if (stride < 0)
break;
// Stride is in terms of dst stride, hardcoded to match vScreen width in current resolution
if (stride > bmp.Width)
{
stride += bmp.Width - tableWidth;
assertm(stride >= 0, "Spliced bitmap: negative computed stride");
}
dstInd += stride;
for (auto count = *src++; count; count--)
{
auto depth = *src++;
bmp.IndexedBmpPtr[dstInd] = **srcChar;
zMap.ZPtr1[dstInd] = depth;
(*srcChar)++;
dstInd++;
}
}
}
void GroupData::SetBitmap(gdrv_bitmap8* bmp)
{
assertm(Bitmaps[bmp->Resolution] == nullptr, "GroupData: bitmap override");
Bitmaps[bmp->Resolution] = bmp;
auto zMap = ZMaps[bmp->Resolution];
if (zMap)
{
assertm(bmp->Width == zMap->Width && bmp->Height == zMap->Height,
"GroupData: mismatched bitmap/zMap dimensions");
}
}
void GroupData::SetZMap(zmap_header_type* zMap)
{
// Flip zMap to match with flipped non-indexed bitmaps
zdrv::FlipZMapHorizontally(*zMap);
assertm(ZMaps[zMap->Resolution] == nullptr, "GroupData: zMap override");
ZMaps[zMap->Resolution] = zMap;
auto bmp = Bitmaps[zMap->Resolution];
if (bmp)
{
assertm(bmp->Width == zMap->Width && bmp->Height == zMap->Height,
"GroupData: mismatched bitmap/zMap dimensions");
}
}
DatFile::~DatFile()
{
for (auto group : Groups)
{
if (!group)
continue;
for (const auto entry : group->GetEntries())
{
delete entry;
}
delete group;
}
}
char* DatFile::field(int groupIndex, FieldTypes targetEntryType)
{
assertm(targetEntryType != FieldTypes::Bitmap8bit && targetEntryType != FieldTypes::Bitmap16bit,
"partman: Use specific get for bitmaps");
auto group = Groups[groupIndex];
for (const auto entry : group->GetEntries())
{
if (entry->EntryType == targetEntryType)
{
return entry->Buffer;
}
if (entry->EntryType > targetEntryType)
break;
}
return nullptr;
}
char* DatFile::field_nth(int groupIndex, FieldTypes targetEntryType, int skipFirstN)
{
assertm(targetEntryType != FieldTypes::Bitmap8bit && targetEntryType != FieldTypes::Bitmap16bit,
"partman: Use specific get for bitmaps");
auto group = Groups[groupIndex];
auto skipCount = 0;
for (const auto entry : group->GetEntries())
{
if (entry->EntryType > targetEntryType)
break;
if (entry->EntryType == targetEntryType)
if (skipCount++ == skipFirstN)
return entry->Buffer;
}
return nullptr;
}
int DatFile::field_size_nth(int groupIndex, FieldTypes targetEntryType, int skipFirstN)
{
auto group = Groups[groupIndex];
auto skipCount = 0;
for (const auto entry : group->GetEntries())
{
if (entry->EntryType > targetEntryType)
return 0;
if (entry->EntryType == targetEntryType)
if (skipCount++ == skipFirstN)
return entry->FieldSize;
}
return 0;
}
int DatFile::field_size(int groupIndex, FieldTypes targetEntryType)
{
return field_size_nth(groupIndex, targetEntryType, 0);
}
int DatFile::record_labeled(LPCSTR targetGroupName)
{
auto targetLength = strlen(targetGroupName);
for (int groupIndex = static_cast<int>(Groups.size()) - 1; groupIndex >= 0; --groupIndex)
{
auto groupName = field(groupIndex, FieldTypes::GroupName);
if (!groupName)
continue;
auto index = 0u;
for (; index < targetLength; index++)
if (targetGroupName[index] != groupName[index])
break;
if (index == targetLength && !targetGroupName[index] && !groupName[index])
return groupIndex;
}
return -1;
}
char* DatFile::field_labeled(LPCSTR lpString, FieldTypes fieldType)
{
auto groupIndex = record_labeled(lpString);
return groupIndex < 0 ? nullptr : field(groupIndex, fieldType);
}
gdrv_bitmap8* DatFile::GetBitmap(int groupIndex)
{
auto group = Groups[groupIndex];
return group->GetBitmap(fullscrn::GetResolution());
}
zmap_header_type* DatFile::GetZMap(int groupIndex)
{
auto group = Groups[groupIndex];
return group->GetZMap(fullscrn::GetResolution());
}
void DatFile::Finalize()
{
if (!pb::FullTiltMode)
{
int groupIndex = record_labeled("pbmsg_ft");
assertm(groupIndex < 0, "DatFile: pbmsg_ft is already in .dat");
// Load 3DPB font into dat to simplify pipeline
auto rcData = reinterpret_cast<MsgFont*>(ImFontAtlas::DecompressCompressedBase85Data(
EmbeddedData::PB_MSGFT_bin_compressed_data_base85));
AddMsgFont(rcData, "pbmsg_ft");
IM_FREE(rcData);
// PINBALL2.MID is an alternative font provided in 3DPB data
// Scaled down because it is too large for top text box
/*auto file = pb::make_path_name("PINBALL2.MID");
auto fileHandle = fopenu(file.c_str(), "rb");
fseek(fileHandle, 0, SEEK_END);
auto fileSize = static_cast<uint32_t>(ftell(fileHandle));
auto rcData = reinterpret_cast<MsgFont*>(new uint8_t[fileSize]);
fseek(fileHandle, 0, SEEK_SET);
fread(rcData, 1, fileSize, fileHandle);
fclose(fileHandle);
auto groupId = Groups.back()->GroupId + 1u;
AddMsgFont(rcData, "pbmsg_ft");
delete[] rcData;
for (auto i = groupId; i < Groups.size(); i++)
Groups[i]->GetBitmap(0)->ScaleIndexed(0.84f, 0.84f);*/
}
for (auto group : Groups)
{
group->FinalizeGroup();
}
}
void DatFile::AddMsgFont(MsgFont* font, const std::string& fontName)
{
auto groupId = Groups.back()->GroupId + 1;
auto ptrToData = reinterpret_cast<char*>(font->Data);
for (auto charInd = 32; charInd < 128; charInd++, groupId++)
{
auto curChar = reinterpret_cast<MsgFontChar*>(ptrToData);
assertm(curChar->Width == font->CharWidths[charInd], "Score: mismatched font width");
ptrToData += curChar->Width * font->Height + 1;
auto bmp = new gdrv_bitmap8(curChar->Width, font->Height, true);
auto srcPtr = curChar->Data;
auto dstPtr = &bmp->IndexedBmpPtr[bmp->Stride * (bmp->Height - 1)];
for (auto y = 0; y < font->Height; ++y)
{
memcpy(dstPtr, srcPtr, curChar->Width);
srcPtr += curChar->Width;
dstPtr -= bmp->Stride;
}
auto group = new GroupData(groupId);
group->AddEntry(new EntryData(FieldTypes::Bitmap8bit, reinterpret_cast<char*>(bmp)));
if (charInd == 32)
{
// First font group holds font name and gap width
auto groupName = new char[fontName.length() + 1];
strcpy(groupName, fontName.c_str());
group->AddEntry(new EntryData(FieldTypes::GroupName, groupName));
auto gaps = new char[2];
*reinterpret_cast<int16_t*>(gaps) = font->GapWidth;
group->AddEntry(new EntryData(FieldTypes::ShortArray, gaps));
}
else
{
auto groupName = new char[30];
sprintf(groupName, "char %d='%c'", charInd, charInd);
group->AddEntry(new EntryData(FieldTypes::GroupName, groupName));
}
Groups.push_back(group);
}
}
+128
View File
@@ -0,0 +1,128 @@
#pragma once
struct zmap_header_type;
struct gdrv_bitmap8;
enum class FieldTypes : int16_t
{
// One 16 bit signed integer
ShortValue = 0,
// Sprite bitmap, 8bpp, indexed color
Bitmap8bit = 1,
Unknown2 = 2,
// Group name, char[]. Not all groups have names.
GroupName = 3,
Unknown4 = 4,
// Palette, contains 256 RBGA 4-byte colors.
Palette = 5,
Unknown6 = 6,
Unknown7 = 7,
Unknown8 = 8,
// String, char[]
String = 9,
// Array of 16 bit signed integers
ShortArray = 10,
// Array of 32 bit floats
FloatArray = 11,
// Sprite depth map, 16bpp, unsigned
Bitmap16bit = 12,
};
struct EntryData
{
EntryData() = default;
EntryData(FieldTypes entryType, char* buffer): EntryType(entryType), FieldSize(-1), Buffer(buffer)
{
}
~EntryData();
FieldTypes EntryType{};
int FieldSize{};
char* Buffer{};
};
#pragma pack(push, 1)
struct MsgFontChar
{
uint8_t Width;
char Data[1];
};
struct MsgFont
{
int16_t GapWidth;
int16_t Unknown1;
int16_t Height;
uint8_t CharWidths[128];
MsgFontChar Data[1];
};
#pragma pack(pop)
static_assert(sizeof(MsgFont) == 136, "Wrong size of MsgFont");
class GroupData
{
public:
int GroupId;
std::string GroupName;
GroupData(int groupId);
void AddEntry(EntryData* entry);
void FinalizeGroup();
const std::vector<EntryData*>& GetEntries() const { return Entries; }
const EntryData* GetEntry(size_t index) const { return Entries[index]; }
size_t EntryCount() const { return Entries.size(); }
void ReserveEntries(size_t count) { Entries.reserve(count); }
gdrv_bitmap8* GetBitmap(int resolution) const;
zmap_header_type* GetZMap(int resolution) const;
private:
std::vector<EntryData*> Entries;
gdrv_bitmap8* Bitmaps[3]{};
zmap_header_type* ZMaps[3]{};
bool NeedsSort = false;
static void SplitSplicedBitmap(const gdrv_bitmap8& srcBmp, gdrv_bitmap8& bmp, zmap_header_type& zMap);
void SetBitmap(gdrv_bitmap8* bmp);
void SetZMap(zmap_header_type* zMap);
};
class DatFile
{
public:
std::string AppName;
std::string Description;
std::vector<GroupData*> Groups;
~DatFile();
char* field_nth(int groupIndex, FieldTypes targetEntryType, int skipFirstN);
char* field(int groupIndex, FieldTypes entryType);
int field_size_nth(int groupIndex, FieldTypes targetEntryType, int skipFirstN);
int field_size(int groupIndex, FieldTypes targetEntryType);
int record_labeled(LPCSTR targetGroupName);
char* field_labeled(LPCSTR lpString, FieldTypes fieldType);
gdrv_bitmap8* GetBitmap(int groupIndex);
zmap_header_type* GetZMap(int groupIndex);
void Finalize();
private:
void AddMsgFont(MsgFont* font, const std::string& fontName);
};
Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 972 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

+14 -48
View File
@@ -1,50 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010">
<Type Name="objlist_class">
<DisplayString>{{ size={ListPtr->Count} }}</DisplayString>
<Expand>
<Item Name="[size]" ExcludeView="simple">ListPtr->Count</Item>
<Item Name="[capacity]" ExcludeView="simple">ListPtr->Size</Item>
<ArrayItems>
<Size>ListPtr->Size</Size>
<ValuePointer>ListPtr->Array</ValuePointer>
</ArrayItems>
</Expand>
</Type>
<Type Name="objlist_struct1">
<DisplayString>{{ size={Count} }}</DisplayString>
<Expand>
<Item Name="[size]" ExcludeView="simple">Count</Item>
<Item Name="[capacity]" ExcludeView="simple">Size</Item>
<ArrayItems>
<Size>Size</Size>
<ValuePointer>Array</ValuePointer>
</ArrayItems>
</Expand>
</Type>
<Type Name="datFileStruct">
<DisplayString>{{ NumberOfGroups={NumberOfGroups} }}</DisplayString>
<Expand>
<Item Name="[NumberOfGroups]" ExcludeView="simple">NumberOfGroups</Item>
<Item Name="[Description]" ExcludeView="simple">Description</Item>
<ArrayItems>
<Size>NumberOfGroups</Size>
<ValuePointer>GroupData</ValuePointer>
</ArrayItems>
</Expand>
</Type>
<Type Name="datGroupData">
<DisplayString>{{ EntryCount={EntryCount} }}</DisplayString>
<Expand>
<Item Name="[EntryCount]" ExcludeView="simple">EntryCount</Item>
<ArrayItems>
<Size>EntryCount</Size>
<ValuePointer>Entries</ValuePointer>
</ArrayItems>
</Expand>
</Type>
<Type Name="ColorRgba">
<DisplayString>{{ Color:{Color} }}</DisplayString>
<Expand>
<Item Name="[Alpha]" ExcludeView="simple">Color>>alphaOffset &amp; 255</Item>
<Item Name="[Red]" ExcludeView="simple">Color>>redOffset &amp; 255</Item>
<Item Name="[Green]" ExcludeView="simple">Color>>greenOffset &amp; 255</Item>
<Item Name="[Blue]" ExcludeView="simple">Color>>blueOffset &amp; 255</Item>
<Item Name="[Color]" ExcludeView="simple">Color</Item>
</Expand>
</Type>
<Type Name="vector3">
<DisplayString>{{ X={X} Y={Y} Z={Z} }}</DisplayString>
</Type>
</AutoVisualizer>
+104 -216
View File
@@ -1,266 +1,154 @@
#include "pch.h"
#include "options.h"
#include "Sound.h"
#include "pinball.h"
#include "WaveMix.h"
#include "winmain.h"
#include "maths.h"
int Sound::num_channels;
HWND Sound::wavemix_window;
HANDLE Sound::pMem;
unsigned int Sound::enabled_flag;
int Sound::channel_time[8];
MIXWAVE* Sound::channel_wavePtr[8];
void (*Sound::callback_ptr)(int, MIXWAVE*, int);
HMODULE Sound::HInstance;
bool Sound::enabled_flag = false;
std::vector<ChannelInfo> Sound::Channels{};
int Sound::Volume = MIX_MAX_VOLUME;
bool Sound::MixOpen = false;
int Sound::Init(HINSTANCE hInstance, int voices, void (* someFuncPtr)(int, MIXWAVE*, int))
void Sound::Init(bool mixOpen, int channels, bool enableFlag, int volume)
{
WNDCLASSA WndClass;
char FileName[300];
int channelCount = voices;
if (voices > 8)
channelCount = 8;
num_channels = channelCount;
if (wavemix_window || pMem)
return 0;
enabled_flag = -1;
for (int i = 0; i < channelCount; ++i)
{
channel_time[i] = 0;
channel_wavePtr[i] = nullptr;
}
callback_ptr = someFuncPtr;
if (!someFuncPtr)
callback_ptr = NullCallback;
pinball::make_path_name(FileName, "wavemix.inf", 300);
FILE* wavemixIniFile = nullptr;
fopen_s(&wavemixIniFile, FileName, "r");
if (wavemixIniFile)
{
fclose(wavemixIniFile);
}
else
{
MessageBoxA(winmain::hwnd_frame, pinball::get_rc_string(42, 0), pinball::WindowName, 0x2000u);
}
WndClass.style = 0;
WndClass.lpfnWndProc = SoundCallBackWndProc;
WndClass.cbClsExtra = 0;
WndClass.cbWndExtra = 0;
WndClass.hInstance = hInstance;
WndClass.hIcon = nullptr;
WndClass.hCursor = nullptr;
WndClass.hbrBackground = nullptr;
WndClass.lpszMenuName = nullptr;
WndClass.lpszClassName = "WaveMixSoundGuy";
RegisterClassA(&WndClass);
wavemix_window = CreateWindowExA(
0,
"WaveMixSoundGuy",
nullptr,
0x80000000,
0x80000000,
0,
0x80000000,
0,
nullptr,
nullptr,
hInstance,
nullptr);
if (!wavemix_window)
return 0;
HInstance = hInstance;
HANDLE hMixSession = WaveMix::Init();
pMem = hMixSession;
if (!hMixSession)
return 0;
WaveMix::OpenChannel(hMixSession, num_channels, 2u);
return 1;
MixOpen = mixOpen;
Volume = volume;
SetChannels(channels);
Enable(enableFlag);
}
void Sound::Enable(int channelFrom, int channelTo, int enableFlag)
void Sound::Enable(bool enableFlag)
{
if (pMem)
{
if (channelTo >= num_channels)
channelTo = num_channels - 1;
if (channelFrom >= 0 && channelTo < num_channels)
{
for (int index = channelFrom; index <= channelTo; ++index)
{
int channelFlag = 1 << index;
if (enableFlag)
{
enabled_flag |= channelFlag;
}
else
{
enabled_flag &= ~channelFlag;
Flush(index, index);
}
}
}
}
}
void Sound::Idle()
{
if (pMem)
WaveMix::Pump();
enabled_flag = enableFlag;
if (MixOpen && !enableFlag)
Mix_HaltChannel(-1);
}
void Sound::Activate()
{
if (pMem)
WaveMix::Activate(pMem, true);
if (MixOpen)
Mix_Resume(-1);
}
void Sound::Deactivate()
{
if (pMem)
WaveMix::Activate(pMem, false);
if (MixOpen)
Mix_Pause(-1);
}
void Sound::Close()
{
if (wavemix_window)
{
DestroyWindow(wavemix_window);
wavemix_window = nullptr;
}
if (pMem)
{
WaveMix::CloseChannel(pMem, 0, 1);
WaveMix::CloseSession(pMem);
pMem = nullptr;
}
Enable(false);
Channels.clear();
}
void Sound::PlaySound(MIXWAVE* wavePtr, int minChannel, int maxChannel, unsigned int dwFlags, __int16 loops)
void Sound::PlaySound(Mix_Chunk* wavePtr, int time, TPinballComponent* soundSource, const char* info)
{
MIXPLAYPARAMS mixParams{};
if (!pMem)
return;
if (maxChannel >= num_channels)
maxChannel = num_channels - 1;
if (!wavePtr || minChannel < 0 || maxChannel >= num_channels)
return;
if ((dwFlags & 0x8000) != 0 && num_channels > 0)
if (MixOpen && wavePtr && enabled_flag)
{
int index2 = 0;
bool ok = false;
while (channel_wavePtr[index2] != wavePtr)
if (Mix_Playing(-1) == num_channels)
{
if (++index2 >= num_channels)
auto cmp = [](const ChannelInfo& a, const ChannelInfo& b)
{
ok = true;
break;
}
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);
}
if (!ok)
return;
}
int playChannel = minChannel;
if (minChannel < maxChannel)
{
int curChannel = minChannel;
do
auto channel = Mix_PlayChannel(-1, wavePtr, 0);
if (channel != -1)
{
++curChannel;
if ((1 << curChannel) & enabled_flag &&
channel_time[curChannel] < channel_time[playChannel])
Channels[channel].TimeStamp = time;
if (options::Options.SoundStereo)
{
playChannel = curChannel;
}
}
while (curChannel < maxChannel);
}
// 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.
if ((1 << playChannel) & enabled_flag)
{
mixParams.hMixSession = pMem;
mixParams.hWndNotify = wavemix_window;
mixParams.dwFlags = dwFlags;
mixParams.wSize = 28;
mixParams.wLoops = loops;
mixParams.iChannel = playChannel;
mixParams.lpMixWave = wavePtr;
// 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;
callback_ptr(1, wavePtr, playChannel);
channel_time[playChannel] = timeGetTime();
channel_wavePtr[playChannel] = wavePtr;
WaveMix::Play(&mixParams);
}
}
// 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);
MIXWAVE* Sound::LoadWaveFile(LPCSTR lpName)
{
return pMem ? WaveMix::OpenWave(pMem, lpName, HInstance, 1u) : nullptr;
}
// 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);
void Sound::FreeSound(MIXWAVE* wave)
{
if (wave && pMem)
WaveMix::FreeWave(pMem, wave);
}
// 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));
void Sound::Flush(int channelFrom, int channelTo)
{
if (pMem)
{
if (channelTo >= num_channels)
channelTo = num_channels - 1;
// 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);
if (channelFrom >= 0 && channelTo < num_channels)
{
for (auto index = channelFrom; index <= channelTo; index++)
{
WaveMix::FlushChannel(pMem, index, 0);
channel_time[index] = 0;
channel_wavePtr[index] = nullptr;
// 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
);*/
}
}
}
}
void Sound::NullCallback(int a1, MIXWAVE* a2, int a3)
Mix_Chunk* Sound::LoadWaveFile(const std::string& lpName)
{
if (!MixOpen)
return nullptr;
auto wavFile = fopenu(lpName.c_str(), "r");
if (!wavFile)
return nullptr;
fclose(wavFile);
return Mix_LoadWAV(lpName.c_str());
}
LRESULT Sound::SoundCallBackWndProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam)
void Sound::FreeSound(Mix_Chunk* wave)
{
if (Msg != MM_WOM_DONE)
return DefWindowProcA(hWnd, Msg, wParam, lParam);
if (MixOpen && wave)
Mix_FreeChunk(wave);
}
auto wavePtr = reinterpret_cast<MIXWAVE*>(lParam);
int channel = -1;
for (auto index = 0; index < num_channels; ++index)
{
if (channel_wavePtr[index] == wavePtr &&
(channel < 0 || channel_time[index] < channel_time[channel]))
{
channel = index;
}
}
void Sound::SetChannels(int channels)
{
if (channels <= 0)
channels = 8;
if (channel >= 0)
{
channel_time[channel] = 0;
channel_wavePtr[channel] = nullptr;
}
num_channels = channels;
Channels.resize(num_channels);
if (MixOpen)
Mix_AllocateChannels(num_channels);
SetVolume(Volume);
}
callback_ptr(2, wavePtr, channel);
return 0;
void Sound::SetVolume(int volume)
{
Volume = volume;
if (MixOpen)
Mix_Volume(-1, volume);
}
+20 -17
View File
@@ -1,28 +1,31 @@
#pragma once
#include "WaveMix.h"
#include "maths.h"
#include "TPinballComponent.h"
struct ChannelInfo
{
int TimeStamp;
vector2 Position;
};
class Sound
{
public:
static int Init(HINSTANCE hInstance, int voices, void (* someFuncPtr)(int, MIXWAVE*, int));
static void Enable(int channelFrom, int channelTo, int enableFlag);
static void Idle();
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(MIXWAVE* wavePtr, int minChannel, int maxChannel, unsigned int dwFlags, __int16 loops);
static MIXWAVE* LoadWaveFile(LPCSTR lpName);
static void FreeSound(MIXWAVE* wave);
static void Flush(int channelFrom, int channelTo);
static void NullCallback(int a1, MIXWAVE* a2, int a3);
static LRESULT __stdcall SoundCallBackWndProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam);
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 HWND wavemix_window;
static HANDLE pMem;
static unsigned int enabled_flag;
static int channel_time[8];
static MIXWAVE* channel_wavePtr[8];
static void (* callback_ptr)(int, MIXWAVE*, int);
static HMODULE HInstance;
static bool enabled_flag;
static int Volume;
static bool MixOpen;
};
+37 -72
View File
@@ -1,83 +1,48 @@
// SpaceCadetPinball.cpp : This file contains the 'main' function. Program execution begins and ends there.
//
#include "pch.h"
#include <iostream>
#include "objlist_class.h"
#include "partman.h"
#include "gdrv.h"
#include "loader.h"
#include "pb.h"
#include "pinball.h"
#include "score.h"
#include "TPinballTable.h"
#include "TTextBox.h"
#include "winmain.h"
int main()
{
int main(int argc, char* argv[])
{
std::string cmdLine;
for (int i = 1; i < argc; i++)
{
// Testing with UI
char cmdLine[1]{};
WinMain(GetModuleHandleA(nullptr), 0, cmdLine, 10);
return 0;
if (i > 1)
cmdLine += " ";
cmdLine += argv[i];
}
std::cout << "Hello World!\n";
gdrv::init(0, 0);
auto dib = gdrv::DibCreate(8, 1, 1);
gdrv::DibSetUsage(dib, 0, 1);
objlist_class d = objlist_class(2, 4);
for (int i = 0; i < 100; i++)
{
d.Add((void*)i);
}
d.Delete((void*)3);
auto xx = sizeof(datFileHeader);
lstrcpyA(winmain::DatFileName, "PINBALL.DAT");
pb::init();
auto datFile = pb::record_table;
assert(partman::field_size_nth(datFile, 0, datFieldTypes::String, 0) == 43);
assert(partman::field_size_nth(datFile, 2, datFieldTypes::Palette, 0) == 1024);
assert(partman::field_size_nth(datFile, 101, datFieldTypes::FloatArray, 4) == 32);
assert(strcmp(partman::field(datFile, 0, datFieldTypes::String), "3D-Pinball: Copyright 1994, Cinematronics") == 0);
assert(strcmp(partman::field(datFile, 540, datFieldTypes::GroupName), "table_objects") == 0);
assert(partman::record_labeled(datFile, "background") == 2);
assert(partman::record_labeled(datFile, "a_bump1") == 372);
assert(memcmp(partman::field_labeled(datFile, "table_size", datFieldTypes::ShortArray), new short[2]{ 600, 416 }, 2 * 2) == 0);
//loader::error(25, 26);
loader::get_sound_id(18);
visualStruct visual1{};
loader::material(96, &visual1);
loader::query_visual(283, 0, &visual1);
visualKickerStruct kicker1{};
loader::kicker(509, &kicker1);
auto score1 = score::create("score1", nullptr);
auto pinballTable = pb::MainTable;
//pinballTable->find_component(1);
for (int i = 0; i < 190; i++)
{
auto rsc = pinball::get_rc_string(i, 0);
if (rsc)
printf_s("%d:\t%s\n", i, rsc);
}
//DatParser::Parse(dataFileName);
std::cout << "Goodby World!\n";
return winmain::WinMain(cmdLine.c_str());
}
#if _WIN32
#include <windows.h>
// Windows subsystem main
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
{
return winmain::WinMain(lpCmdLine);
}
// fopen to _wfopen adapter, for UTF-8 paths
FILE* fopenu(const char* path, const char* opt)
{
wchar_t* wideArgs[2]{};
for (auto& arg : wideArgs)
{
auto src = wideArgs[0] ? opt : path;
auto length = MultiByteToWideChar(CP_UTF8, 0, src, -1, nullptr, 0);
arg = new wchar_t[length];
MultiByteToWideChar(CP_UTF8, 0, src, -1, arg, length);
}
auto fileHandle = _wfopen(wideArgs[0], wideArgs[1]);
for (auto arg : wideArgs)
delete[] arg;
return fileHandle;
}
#endif
// Run program: Ctrl + F5 or Debug > Start Without Debugging menu
// Debug program: F5 or Debug > Start Debugging menu
+1 -604
View File
@@ -1,604 +1 @@
// Microsoft Visual C++ generated resource script.
//
#include "resource.h"
#define APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 2 resource.
//
#include "winres.h"
/////////////////////////////////////////////////////////////////////////////
#undef APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
// Russian (Russia) resources
#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_RUS)
LANGUAGE LANG_RUSSIAN, SUBLANG_DEFAULT
#pragma code_page(1251)
#ifdef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// TEXTINCLUDE
//
1 TEXTINCLUDE
BEGIN
"resource.h\0"
END
2 TEXTINCLUDE
BEGIN
"#include ""winres.h""\r\n"
"\0"
END
3 TEXTINCLUDE
BEGIN
"\r\n"
"\0"
END
#endif // APSTUDIO_INVOKED
#endif // Russian (Russia) resources
/////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////
// English (United States) resources
#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
#pragma code_page(1252)
/////////////////////////////////////////////////////////////////////////////
//
// Menu
//
MENU_1 MENU
BEGIN
POPUP "&Game"
BEGIN
MENUITEM "&New Game\tF2", Menu1_New_Game
MENUITEM "&Launch Ball", Menu1_Launch_Ball
MENUITEM "&Pause/Resume Game\tF3", Menu1_Pause_Resume_Game
MENUITEM SEPARATOR
MENUITEM "&High Scores...", Menu1_High_Scores
MENUITEM "&Demo", Menu1_Demo
MENUITEM "E&xit", Menu1_Exit
END
POPUP "&Options"
BEGIN
MENUITEM "&Full Screen\tF4", Menu1_Full_Screen
MENUITEM "Select &Table", Menu1_Select_Table
POPUP "Select &Players"
BEGIN
MENUITEM "&1 Player", Menu1_1Player
MENUITEM "&2 Players", Menu1_2Players
MENUITEM "&3 Players", Menu1_3Players
MENUITEM "&4 Players", Menu1_4Players
END
MENUITEM SEPARATOR
MENUITEM "&Sounds", Menu1_Sounds
MENUITEM "&Music", Menu1_Music
MENUITEM SEPARATOR
MENUITEM "P&layer Controls...\tF8", Menu1_Player_Controls
END
POPUP "&Help"
BEGIN
MENUITEM "&Help Topics\tF1", Menu1_Help_Topics
MENUITEM SEPARATOR
MENUITEM "&About Pinball", Menu1_About_Pinball
END
END
/////////////////////////////////////////////////////////////////////////////
//
// RCDATA
//
PBMSG_FT RCDATA "PB_MSGFT.bin"
/////////////////////////////////////////////////////////////////////////////
//
// Icon
//
// Icon with lowest ID value placed first to ensure application icon
// remains consistent on all systems.
ICON_1 ICON "icon_1.ico"
/////////////////////////////////////////////////////////////////////////////
//
// Dialog
//
DIALOG_1 DIALOG 18, 18, 157, 78
STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION
CAPTION "About"
BEGIN
CTEXT "3D-Pinball for Windows",-1,11,6,135,8
CTEXT "Version 1.0",-1,40,37,76,8
ICON "ICON_1",-1,69,18,16,16
DEFPUSHBUTTON "Ok",1,66,60,24,12
END
DLG_HIGHSCORES DIALOGEX 20, 22, 274, 141
STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | DS_CONTEXTHELP | WS_POPUP | WS_CAPTION | WS_SYSMENU
CAPTION "High Scores"
FONT 8, "MS Shell Dlg", 0, 0, 0x0
BEGIN
DEFPUSHBUTTON "OK",DLG_HIGHSCORES_Ok,109,120,50,14
PUSHBUTTON "Cancel",DLG_HIGHSCORES_Cancel,163,120,50,14
PUSHBUTTON "&Clear",DLG_HIGHSCORES_Clear,217,120,50,14
LTEXT "Name",701,49,7,24,8
LTEXT "Score",702,200,7,20,8
LTEXT "Rank",703,13,7,20,8
RTEXT "1",704,13,31,16,12
RTEXT "2",705,13,47,16,12
RTEXT "3",706,13,63,16,12
RTEXT "4",707,13,79,16,12
RTEXT "5",708,13,95,16,12
LTEXT "",DLG_HIGHSCORES_StaticName1,49,31,120,12
LTEXT "",DLG_HIGHSCORES_StaticName2,49,47,120,12
LTEXT "",DLG_HIGHSCORES_StaticName3,49,63,120,12
LTEXT "",DLG_HIGHSCORES_StaticName4,49,79,120,12
LTEXT "",DLG_HIGHSCORES_StaticName5,49,95,120,12
RTEXT "",DLG_HIGHSCORES_Score1,180,31,58,12
RTEXT "",DLG_HIGHSCORES_Score2,180,47,58,12
RTEXT "",DLG_HIGHSCORES_Score3,180,63,58,12
RTEXT "",DLG_HIGHSCORES_Score4,180,79,58,12
RTEXT "",DLG_HIGHSCORES_Score5,180,95,58,12
EDITTEXT DLG_HIGHSCORES_EditName1,47,29,120,12,WS_DISABLED
EDITTEXT DLG_HIGHSCORES_EditName2,47,45,120,12,WS_DISABLED
EDITTEXT DLG_HIGHSCORES_EditName3,47,61,120,12,WS_DISABLED
EDITTEXT DLG_HIGHSCORES_EditName4,47,77,120,12,WS_DISABLED
EDITTEXT DLG_HIGHSCORES_EditName5,47,93,120,12,WS_DISABLED
CONTROL "",606,"Static",SS_BLACKFRAME,7,21,259,92
END
EDIT_COMPONENTS DIALOG 0, 0, 312, 94
STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CAPTION | WS_SYSMENU
CAPTION "Edit Pinball Components FOR TESTING ONLY"
BEGIN
LISTBOX 100,1,12,68,79,LBS_SORT | WS_VSCROLL | WS_TABSTOP
DEFPUSHBUTTON "OK",1,81,75,41,14
GROUPBOX "",103,136,0,175,93
EDITTEXT 130,141,10,34,12
EDITTEXT 131,141,23,34,12
EDITTEXT 132,141,36,34,12
EDITTEXT 133,141,49,34,12
EDITTEXT 134,141,62,34,12
EDITTEXT 135,141,75,34,12
LTEXT "Text",136,178,12,130,8
LTEXT "Text",137,178,25,130,8
LTEXT "Text",138,178,38,130,8
LTEXT "Text",139,178,51,130,8
LTEXT "Text",140,178,64,130,8
LTEXT "Text",141,178,77,130,8
EDITTEXT 149,142,10,38,12
EDITTEXT 150,142,23,38,12
EDITTEXT 151,142,36,38,12
EDITTEXT 152,142,49,38,12
EDITTEXT 153,142,62,38,12
EDITTEXT 154,142,75,38,12
LTEXT "1",156,184,12,120,8
LTEXT "2",157,184,25,120,8
LTEXT "3",158,184,38,120,8
LTEXT "4",159,184,51,120,8
LTEXT "5",160,184,64,120,8
LTEXT "6",161,184,77,120,8
PUSHBUTTON "Reset",163,81,56,41,12
LTEXT "Text",164,146,26,162,8
LTEXT "Text",165,146,54,162,8
LTEXT "Text",166,146,81,162,8
PUSHBUTTON "Change...",167,245,10,62,14
PUSHBUTTON "Change...",168,245,38,62,14
PUSHBUTTON "Change...",169,245,65,62,14
CONTROL "Sound Effects",170,"Button",BS_AUTORADIOBUTTON | WS_TABSTOP,74,10,58,10
CONTROL "Physics",171,"Button",BS_AUTORADIOBUTTON | WS_TABSTOP,73,24,59,10
CONTROL "Scoring",172,"Button",BS_AUTORADIOBUTTON | WS_TABSTOP,73,38,40,10
LTEXT "Text",173,146,13,97,8
LTEXT "Text",174,146,41,97,8
LTEXT "Text",175,146,68,97,8
LTEXT "Components:",176,2,3,43,8
END
KEYMAPPER DIALOGEX 2, 9, 284, 183
STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | DS_CONTEXTHELP | WS_POPUP | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CAPTION | WS_SYSMENU
CAPTION "3D Pinball: Player Controls"
FONT 8, "MS Shell Dlg", 0, 0, 0x0
BEGIN
PUSHBUTTON "OK",KEYMAPPER_Ok,119,163,50,14
PUSHBUTTON "Cancel",KEYMAPPER_Cancel,173,163,50,14
PUSHBUTTON "&Default",KEYMAPPER_Default,227,163,50,14
LTEXT "&Left Flipper",901,20,87,39,10
COMBOBOX KEYMAPPER_FlipperL,66,85,53,70,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
LTEXT "Right &Flipper",902,146,87,44,10
COMBOBOX KEYMAPPER_FlipperR,192,85,53,70,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
LTEXT "Left &Table Bump",904,20,107,34,17
COMBOBOX KEYMAPPER_BumpLeft,66,108,53,70,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
LTEXT "&Right Table Bump",905,146,107,38,19
COMBOBOX KEYMAPPER_BumpRight,192,108,53,70,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
LTEXT "&BottomTable Bump",906,20,131,41,18
COMBOBOX KEYMAPPER_BumpBottom,66,133,53,70,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
LTEXT "&Plunger",903,146,136,39,8
COMBOBOX KEYMAPPER_Plunger,192,133,53,70,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
LTEXT "To change game controls, choose the control options you want, click the Arrow buttons to change specific keys, and then choose OK.",801,14,17,249,18
LTEXT "To restore 3D Pinball to its original settings, choose Default, and then choose OK. To use the Arrow keys on the numeric keypad, make sure NUMLOCK is off.",802,14,38,256,25
GROUPBOX "Control Options",908,7,71,269,87
GROUPBOX "Instructions",909,7,5,269,61
END
XXDLG_HIGHSCORES DIALOG 20, 22, 211, 116
STYLE DS_MODALFRAME | DS_CONTEXTHELP | WS_POPUP | WS_CAPTION | WS_SYSMENU
CAPTION "High Scores"
BEGIN
DEFPUSHBUTTON "OK",1,32,97,33,14,BS_LEFTTEXT
PUSHBUTTON "Cancel",2,89,97,33,14
PUSHBUTTON "&Clear",112,146,97,33,14
LTEXT "Name",-1,51,8,24,8
LTEXT "Score",-1,160,8,20,8
LTEXT "Rank",-1,15,8,20,8
RTEXT "1",-1,15,28,16,12
RTEXT "2",-1,15,40,16,12
RTEXT "3",-1,15,52,16,12
RTEXT "4",-1,15,64,16,12
RTEXT "5",-1,15,76,16,12
LTEXT "",401,51,28,88,12
LTEXT "",402,51,40,88,12
LTEXT "",403,51,52,88,12
LTEXT "",404,51,64,88,12
LTEXT "",405,51,76,88,12
RTEXT "",501,140,28,58,12
RTEXT "",502,140,40,58,12
RTEXT "",503,140,52,58,12
RTEXT "",504,140,64,58,12
RTEXT "",505,140,76,58,12
EDITTEXT 601,49,26,88,12,WS_DISABLED
EDITTEXT 602,49,38,88,12,WS_DISABLED
EDITTEXT 603,49,50,88,12,WS_DISABLED
EDITTEXT 604,49,62,88,12,WS_DISABLED
EDITTEXT 605,49,74,88,12,WS_DISABLED
CONTROL "",-1,"Static",SS_BLACKFRAME,9,22,195,69
END
XXKEYMAPPER DIALOG 2, 9, 277, 158
STYLE DS_SETFONT | DS_MODALFRAME | DS_CONTEXTHELP | WS_POPUP | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CAPTION | WS_SYSMENU
CAPTION "3D Pinball: Player Controls"
FONT 8, "MS Shell Dlg"
BEGIN
PUSHBUTTON "OK",1,221,84,50,14
PUSHBUTTON "Cancel",2,221,105,50,14
PUSHBUTTON "Default",501,221,127,50,14
COMBOBOX 401,50,82,53,70,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
COMBOBOX 402,157,82,53,70,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
COMBOBOX 404,50,105,53,70,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
COMBOBOX 405,157,105,53,70,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
COMBOBOX 406,50,130,53,70,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
COMBOBOX 403,157,130,53,70,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
RTEXT "Plunger",811,114,133,39,8
RTEXT "Right Flipper",807,107,84,46,10
CTEXT "Left Table Bump",912,12,102,34,19
CTEXT "Right Table Bump",914,115,102,38,19
LTEXT "To change game controls, choose the control options you want, click the Arrow buttons to change specific keys, and then choose OK.",801,12,13,249,18
LTEXT "To restore 3D Pinball to its original settings, choose Default, and then choose OK. To use the Arrow keys on the numeric keypad, make sure NUMLOCK is off.",802,12,32,256,25
GROUPBOX "Control Options",908,5,67,214,87
RTEXT "Left Flipper",803,9,84,39,10
GROUPBOX "Instructions",909,5,1,265,61
CTEXT "BottomTable Bump",916,7,127,41,19
END
/////////////////////////////////////////////////////////////////////////////
//
// DESIGNINFO
//
#ifdef APSTUDIO_INVOKED
GUIDELINES DESIGNINFO
BEGIN
"DIALOG_1", DIALOG
BEGIN
END
"DLG_HIGHSCORES", DIALOG
BEGIN
END
"KEYMAPPER", DIALOG
BEGIN
END
END
#endif // APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// AFX_DIALOG_LAYOUT
//
DLG_HIGHSCORES AFX_DIALOG_LAYOUT
BEGIN
0
END
KEYMAPPER AFX_DIALOG_LAYOUT
BEGIN
0
END
/////////////////////////////////////////////////////////////////////////////
//
// Bitmap
//
splash_bitmap BITMAP "splash_bitmap.bmp"
/////////////////////////////////////////////////////////////////////////////
//
// String Table
//
STRINGTABLE
BEGIN
IDS_STRING101 "Replay Awarded"
IDS_STRING102 "Ball Locked"
IDS_STRING103 "Center Post\n%ld"
IDS_STRING104 "Bonus Awarded\n%ld"
IDS_STRING105 "Bonus Activated"
IDS_STRING106 "Weapons Upgraded"
IDS_STRING107 "Engine Upgraded"
IDS_STRING108 "Bonus up 1 Million"
IDS_STRING109 "Extra Ball Available\n%ld"
IDS_STRING110 "Extra Ball"
IDS_STRING111 "Reflex Shot Award\n%ld"
IDS_STRING112 "Final Battle Won"
IDS_STRING113 "Hyperspace Bonus\n%ld"
IDS_STRING114 "Hyperspace Bonus Available"
IDS_STRING115 "Jackpot Awarded\n%ld"
IDS_STRING116 "Jackpot Activated"
END
STRINGTABLE
BEGIN
IDS_STRING117 "Multiball"
IDS_STRING118 "Ramp Bonus Awarded"
IDS_STRING119 "Light Added"
IDS_STRING120 "Ramp Bonus On"
IDS_STRING121 "Light Reset Off"
IDS_STRING122 "Skill Shot\n%ld"
IDS_STRING123 "Game Paused\nF3 to Resume"
IDS_STRING124 "Continue Play"
IDS_STRING125 "F2 Starts New Game"
IDS_STRING126 "Careful..."
IDS_STRING127 "Player 1"
IDS_STRING128 "Player 2"
IDS_STRING129 "Player 3"
IDS_STRING130 "Player 4"
IDS_STRING131 "Demo\nPlayer 1"
IDS_STRING132 "Demo\nPlayer 2"
END
STRINGTABLE
BEGIN
IDS_STRING133 "Demo\nPlayer 3"
IDS_STRING134 "Demo\nPlayer 4"
IDS_STRING135 "Game Over"
IDS_STRING136 "TILT!"
IDS_STRING137 "This program requires an 80386 or later CPU."
IDS_STRING138 "80386 Required"
IDS_STRING139 "3D Pinball for Windows - Space Cadet"
IDS_STRING140 "One or more of the player controls is set to the same key,\nfor best performance use unique keys for each control."
IDS_STRING141 "Clear High Scores?"
IDS_STRING142 "Confirm"
IDS_STRING143 "WAVEMIX.INF is missing - it must be in the pinball directory!"
IDS_STRING144 "Warning:"
IDS_STRING145 "Ship Re-Fueled"
IDS_STRING146 "Gravity Well"
IDS_STRING147 "Time Warp Forward"
IDS_STRING148 "Time Warp Backward"
END
STRINGTABLE
BEGIN
IDS_STRING149 "Maelstrom!"
IDS_STRING150 "Wormhole"
IDS_STRING151 "Awaiting Deployment"
IDS_STRING152 "Flags Upgraded"
IDS_STRING153 "Bonus Hold"
IDS_STRING154 "Level One Commendation"
IDS_STRING155 "Level Two Commendation"
IDS_STRING156 "Level Three Commendation"
IDS_STRING157 "Field Multiplier 2x"
IDS_STRING158 "Field Multiplier 3x"
IDS_STRING159 "Field Multiplier 5x"
IDS_STRING160 "Field Multiplier 10x"
IDS_STRING161 "Target Practice"
IDS_STRING162 "Launch Training"
IDS_STRING163 "Re-Entry Training"
IDS_STRING164 "Science"
END
STRINGTABLE
BEGIN
IDS_STRING165 "Stray Comet"
IDS_STRING166 "Black Hole"
IDS_STRING167 "Space Radiation"
IDS_STRING168 "Bug Hunt"
IDS_STRING169 "Alien Menace"
IDS_STRING170 "Rescue"
IDS_STRING171 "Satellite Retrieval"
IDS_STRING172 "Recon"
IDS_STRING173 "Doomsday Machine"
IDS_STRING174 "Cosmic Plague"
IDS_STRING175 "Secret"
IDS_STRING176 "Time Warp"
IDS_STRING177 "Maelstrom"
IDS_STRING178 "Mission Accepted\n%ld"
IDS_STRING179 "Mission Completed\n%ld"
IDS_STRING180 "%s Mission Selected"
END
STRINGTABLE
BEGIN
IDS_STRING181 "Black Hole\n%ld"
IDS_STRING182 "Gravity Normalized\n%ld"
IDS_STRING183 "Gravity Well\n%ld"
IDS_STRING184 "Promotion to %s"
IDS_STRING185 "Cadet"
IDS_STRING186 "Ensign"
IDS_STRING187 "Lieutenant"
IDS_STRING188 "Captain"
IDS_STRING189 "Lt Commander"
IDS_STRING190 "Commander"
IDS_STRING191 "Commodore"
IDS_STRING192 "Admiral"
IDS_STRING193 "Fleet Admiral"
IDS_STRING194 "Wormhole Opened"
IDS_STRING195 "Crash Bonus\n%ld"
IDS_STRING196 "Replay Ball"
END
STRINGTABLE
BEGIN
IDS_STRING197 "Re-Deploy"
IDS_STRING198 "Player 1 Shoot Again"
IDS_STRING199 "Player 2 Shoot Again"
IDS_STRING200 "Player 3 Shoot Again"
IDS_STRING201 "Player 4 Shoot Again"
IDS_STRING202 "This 3D Pinball Table was created for Microsoft by Maxis.\nFor more information call (800)-336-2947\n(US and Canadian customers only).\nCopyright (c) 1995 Maxis."
IDS_STRING203 "3D Pinball Table created for Microsoft by Maxis. Copyright (c) 1995 Maxis."
IDS_STRING204 "About 3D Pinball"
IDS_STRING205 "Hit Mission Targets To Select Mission"
IDS_STRING206 "Re-Fuel Ship"
IDS_STRING207 "Launch Ramp To Accept %s Mission"
IDS_STRING208 "Attack Bumpers Hits\nLeft: %d"
IDS_STRING209 "Target Training Passed"
IDS_STRING210 "Mission Aborted"
IDS_STRING211 "Launches Left: %d"
IDS_STRING212 "Launch Training Passed"
END
STRINGTABLE
BEGIN
IDS_STRING213 "Re-Entries Left: %d"
IDS_STRING214 "Re-Entry Training Passed"
IDS_STRING215 "Drop Targets\nLeft: %d"
IDS_STRING216 "Science Mission Completed"
IDS_STRING217 "Warning -- Low Fuel"
IDS_STRING218 "Fill Right Hazard Banks"
IDS_STRING219 "Hyperspace Launch"
IDS_STRING220 "Comet Destroyed"
IDS_STRING221 "Enter Wormhole"
IDS_STRING222 "Radiation Eliminated"
IDS_STRING223 "Upgrade Launch Bumpers"
IDS_STRING224 "Enter Black Hole"
IDS_STRING225 "Black Hole Eliminated"
IDS_STRING226 "Targets\nLeft: %d"
IDS_STRING227 "Xenomorphs Destroyed"
IDS_STRING228 "Upgrade Flags"
END
STRINGTABLE
BEGIN
IDS_STRING229 "Hyperspace Launch"
IDS_STRING230 "Survivors Rescued"
IDS_STRING231 "Aliens Repelled"
IDS_STRING232 "Hit Fuel Targets"
IDS_STRING233 "Remote Attack Bumper Hits\nLeft: %d"
IDS_STRING234 "Satellite Repaired"
IDS_STRING235 "Lane Passes\nLeft: %d"
IDS_STRING236 "Shoot Ball Up Fuel Chute"
IDS_STRING237 "Survey Complete"
IDS_STRING238 "Out Lane Passes\nLeft: %d"
IDS_STRING239 "Doomsday Machine Destroyed"
IDS_STRING240 "Roll Flags: %d"
IDS_STRING241 "Hit Space Warp Rollover"
IDS_STRING242 "Plague Eliminated"
IDS_STRING243 "Hit Yellow Wormhole"
IDS_STRING244 "Hit Red Wormhole"
END
STRINGTABLE
BEGIN
IDS_STRING245 "Hit Green Wormhole"
IDS_STRING246 "Plans Recovered"
IDS_STRING247 "Rebound Hits\nLeft: %d"
IDS_STRING248 "Hit Hyperspace Chute or Launch Ramp"
IDS_STRING249 "Drop Target Hits\nLeft: %d"
IDS_STRING250 "Spot Target Hits\nLeft: %d"
IDS_STRING251 "Lanes Passes\nLeft: %d"
IDS_STRING252 "Shoot Ball Up Fuel Chute"
IDS_STRING253 "Hit Launch Ramp"
IDS_STRING254 "Hit Flags"
IDS_STRING255 "Hit Worm Hole"
IDS_STRING256 "Hyperspace Chute to end Maelstrom"
IDS_STRING257 "pinball.mid"
IDS_STRING258 "1 UseBitmapFont"
IDS_STRING259 "90 Left Flipper Key"
END
STRINGTABLE
BEGIN
IDS_STRING260 "191 Right Flipper Key"
IDS_STRING261 "32 Plunger Key"
IDS_STRING262 "88 Bump Left Key"
IDS_STRING263 "190 Bump Right Key"
IDS_STRING264 "38 Bump Bottom Key"
IDS_STRING265 "Software\\Microsoft\\Plus!\\Pinball"
IDS_STRING266 "SpaceCadet"
IDS_STRING267 "1c7c22a0-9576-11ce-bf80-444553540000"
IDS_STRING268 "PINBALL.DAT"
IDS_STRING269 "Space Cadet"
IDS_STRING270 "Error:"
IDS_STRING271 "Unable to find other tables."
IDS_STRING272 "3D Pinball\nSpace Cadet"
IDS_STRING273 "Promotion to %s"
IDS_STRING274 "Demotion to %s"
IDS_STRING275 "Upgrade Attack Bumpers"
END
STRINGTABLE
BEGIN
IDS_STRING276 "Fill Left Hazard Banks"
IDS_STRING277 "HIGH SCORE"
IDS_STRING278 "pinball.chm"
IDS_STRING279 "Not enough memory to run 3D Pinball."
IDS_STRING280 "Player 1's Score\n%ld"
IDS_STRING281 "Player 2's Score\n%ld"
IDS_STRING282 "Player 3's Score\n%ld"
IDS_STRING283 "Player 4's Score\n%ld"
IDS_STRING284 "High Score 1\n%ld"
IDS_STRING285 "High Score 2\n%ld"
IDS_STRING286 "High Score 3\n%ld"
IDS_STRING287 "High Score 4\n%ld"
IDS_STRING288 "High Score 5\n%ld"
IDS_STRING289 "255 255 255 (R G B default font color)"
END
#endif // English (United States) resources
/////////////////////////////////////////////////////////////////////////////
#ifndef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 3 resource.
//
/////////////////////////////////////////////////////////////////////////////
#endif // not APSTUDIO_INVOKED
ICON_1 ICON "Icon_1.ico"
-314
View File
@@ -1,314 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|Win32">
<Configuration>Debug</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|Win32">
<Configuration>Release</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<VCProjectVersion>15.0</VCProjectVersion>
<ProjectGuid>{F7B78CC7-6984-4F79-9486-ABCF87DF9F06}</ProjectGuid>
<Keyword>Win32Proj</Keyword>
<RootNamespace>SpaceCadetPinball</RootNamespace>
<WindowsTargetPlatformVersion>10.0.17763.0</WindowsTargetPlatformVersion>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v141</PlatformToolset>
<CharacterSet>NotSet</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v141</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>NotSet</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v141</PlatformToolset>
<CharacterSet>NotSet</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v141</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>NotSet</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="Shared">
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<LinkIncremental>true</LinkIncremental>
<RunCodeAnalysis>false</RunCodeAnalysis>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<LinkIncremental>true</LinkIncremental>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<LinkIncremental>false</LinkIncremental>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<LinkIncremental>false</LinkIncremental>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<ClCompile>
<PrecompiledHeader>Use</PrecompiledHeader>
<WarningLevel>Level3</WarningLevel>
<Optimization>Disabled</Optimization>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
<BufferSecurityCheck>true</BufferSecurityCheck>
<PreprocessToFile>false</PreprocessToFile>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<AdditionalDependencies>Comctl32.lib;Winmm.lib;Htmlhelp.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<ClCompile>
<PrecompiledHeader>Use</PrecompiledHeader>
<WarningLevel>Level3</WarningLevel>
<Optimization>Disabled</Optimization>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<AdditionalDependencies>Comctl32.lib;Winmm.lib;Htmlhelp.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<ClCompile>
<PrecompiledHeader>Use</PrecompiledHeader>
<WarningLevel>Level3</WarningLevel>
<Optimization>MaxSpeed</Optimization>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<GenerateDebugInformation>true</GenerateDebugInformation>
<AdditionalDependencies>Comctl32.lib;Winmm.lib;Htmlhelp.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<ClCompile>
<PrecompiledHeader>Use</PrecompiledHeader>
<WarningLevel>Level3</WarningLevel>
<Optimization>MaxSpeed</Optimization>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<GenerateDebugInformation>true</GenerateDebugInformation>
<AdditionalDependencies>Comctl32.lib;Winmm.lib;Htmlhelp.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClInclude Include="control.h" />
<ClInclude Include="fullscrn.h" />
<ClInclude Include="gdrv.h" />
<ClInclude Include="high_score.h" />
<ClInclude Include="loader.h" />
<ClInclude Include="maths.h" />
<ClInclude Include="memory.h" />
<ClInclude Include="midi.h" />
<ClInclude Include="nudge.h" />
<ClInclude Include="objlist_class.h" />
<ClInclude Include="options.h" />
<ClInclude Include="partman.h" />
<ClInclude Include="pb.h" />
<ClInclude Include="pch.h" />
<ClInclude Include="pinball.h" />
<ClInclude Include="proj.h" />
<ClInclude Include="render.h" />
<ClInclude Include="resource.h" />
<ClInclude Include="score.h" />
<ClInclude Include="Sound.h" />
<ClInclude Include="splash.h" />
<ClInclude Include="TBall.h" />
<ClInclude Include="TBlocker.h" />
<ClInclude Include="TBumper.h" />
<ClInclude Include="TCircle.h" />
<ClInclude Include="TCollisionComponent.h" />
<ClInclude Include="TComponentGroup.h" />
<ClInclude Include="TDemo.h" />
<ClInclude Include="TDrain.h" />
<ClInclude Include="TEdgeBox.h" />
<ClInclude Include="TEdgeManager.h" />
<ClInclude Include="TEdgeSegment.h" />
<ClInclude Include="TFlagSpinner.h" />
<ClInclude Include="TFlipper.h" />
<ClInclude Include="TFlipperEdge.h" />
<ClInclude Include="TGate.h" />
<ClInclude Include="THole.h" />
<ClInclude Include="timer.h" />
<ClInclude Include="TKickback.h" />
<ClInclude Include="TKickout.h" />
<ClInclude Include="TLight.h" />
<ClInclude Include="TLightBargraph.h" />
<ClInclude Include="TLightGroup.h" />
<ClInclude Include="TLightRollover.h" />
<ClInclude Include="TLine.h" />
<ClInclude Include="TOneway.h" />
<ClInclude Include="TPinballComponent.h" />
<ClInclude Include="TPinballTable.h" />
<ClInclude Include="TPlunger.h" />
<ClInclude Include="TPopupTarget.h" />
<ClInclude Include="TRamp.h" />
<ClInclude Include="TRollover.h" />
<ClInclude Include="TSink.h" />
<ClInclude Include="TSoloTarget.h" />
<ClInclude Include="TSound.h" />
<ClInclude Include="TTableLayer.h" />
<ClInclude Include="TTextBox.h" />
<ClInclude Include="TTextBoxMessage.h" />
<ClInclude Include="TTimer.h" />
<ClInclude Include="TTripwire.h" />
<ClInclude Include="TWall.h" />
<ClInclude Include="TZmapList.h" />
<ClInclude Include="WaveMix.h" />
<ClInclude Include="winmain.h" />
<ClInclude Include="zdrv.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="control.cpp" />
<ClCompile Include="fullscrn.cpp" />
<ClCompile Include="gdrv.cpp" />
<ClCompile Include="high_score.cpp" />
<ClCompile Include="loader.cpp" />
<ClCompile Include="maths.cpp" />
<ClCompile Include="memory.cpp" />
<ClCompile Include="midi.cpp" />
<ClCompile Include="nudge.cpp" />
<ClCompile Include="objlist_class.cpp" />
<ClCompile Include="options.cpp" />
<ClCompile Include="partman.cpp" />
<ClCompile Include="pb.cpp" />
<ClCompile Include="pch.cpp">
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Create</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Create</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">Create</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Create</PrecompiledHeader>
</ClCompile>
<ClCompile Include="pinball.cpp" />
<ClCompile Include="proj.cpp" />
<ClCompile Include="render.cpp" />
<ClCompile Include="score.cpp" />
<ClCompile Include="Sound.cpp" />
<ClCompile Include="SpaceCadetPinball.cpp" />
<ClCompile Include="splash.cpp" />
<ClCompile Include="TBall.cpp" />
<ClCompile Include="TBlocker.cpp" />
<ClCompile Include="TBumper.cpp" />
<ClCompile Include="TCircle.cpp" />
<ClCompile Include="TCollisionComponent.cpp" />
<ClCompile Include="TComponentGroup.cpp" />
<ClCompile Include="TDemo.cpp" />
<ClCompile Include="TDrain.cpp" />
<ClCompile Include="TEdgeBox.cpp" />
<ClCompile Include="TEdgeManager.cpp" />
<ClCompile Include="TEdgeSegment.cpp" />
<ClCompile Include="TFlagSpinner.cpp" />
<ClCompile Include="TFlipper.cpp" />
<ClCompile Include="TFlipperEdge.cpp" />
<ClCompile Include="TGate.cpp" />
<ClCompile Include="THole.cpp" />
<ClCompile Include="timer.cpp" />
<ClCompile Include="TKickback.cpp" />
<ClCompile Include="TKickout.cpp" />
<ClCompile Include="TLight.cpp" />
<ClCompile Include="TLightBargraph.cpp" />
<ClCompile Include="TLightGroup.cpp" />
<ClCompile Include="TLightRollover.cpp" />
<ClCompile Include="TLine.cpp" />
<ClCompile Include="TOneway.cpp" />
<ClCompile Include="TPinballComponent.cpp" />
<ClCompile Include="TPinballTable.cpp" />
<ClCompile Include="TPlunger.cpp" />
<ClCompile Include="TPopupTarget.cpp" />
<ClCompile Include="TRamp.cpp" />
<ClCompile Include="TRollover.cpp" />
<ClCompile Include="TSink.cpp" />
<ClCompile Include="TSoloTarget.cpp" />
<ClCompile Include="TSound.cpp" />
<ClCompile Include="TTableLayer.cpp" />
<ClCompile Include="TTextBox.cpp" />
<ClCompile Include="TTextBoxMessage.cpp" />
<ClCompile Include="TTimer.cpp" />
<ClCompile Include="TTripwire.cpp" />
<ClCompile Include="TWall.cpp" />
<ClCompile Include="WaveMix.cpp" />
<ClCompile Include="winmain.cpp" />
<ClCompile Include="zdrv.cpp" />
</ItemGroup>
<ItemGroup>
<Natvis Include="NatvisFile.natvis" />
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="SpaceCadetPinball.rc" />
</ItemGroup>
<ItemGroup>
<None Include="PB_MSGFT.bin" />
</ItemGroup>
<ItemGroup>
<Image Include="Icon_1.ico" />
<Image Include="splash_bitmap.bmp" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>
@@ -1,447 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Filter Include="Source Files">
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
<Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
</Filter>
<Filter Include="Header Files">
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
<Extensions>h;hh;hpp;hxx;hm;inl;inc;ipp;xsd</Extensions>
</Filter>
<Filter Include="Resource Files">
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
</Filter>
<Filter Include="Header Files\TEdgeSegment">
<UniqueIdentifier>{0aa40751-a44a-400e-8809-ee817161e8e0}</UniqueIdentifier>
</Filter>
<Filter Include="Source Files\TEdgeSegment">
<UniqueIdentifier>{d70e7fca-2294-41a4-9cf8-78052bdb9aa4}</UniqueIdentifier>
</Filter>
<Filter Include="Source Files\TCollisionComponent">
<UniqueIdentifier>{01aed326-d2ec-457a-b99f-08ef32ed97fa}</UniqueIdentifier>
</Filter>
<Filter Include="Header Files\TCollisionComponent">
<UniqueIdentifier>{7ed2796a-da4b-4edd-8783-53e45d8d1c88}</UniqueIdentifier>
</Filter>
<Filter Include="Header Files\TPinballComponent">
<UniqueIdentifier>{9ee086c2-1a95-48fb-92d8-4b7e7f6682ff}</UniqueIdentifier>
</Filter>
<Filter Include="Source Files\TPinballComponent">
<UniqueIdentifier>{33813da8-81ac-449c-b19a-9756272519b9}</UniqueIdentifier>
</Filter>
</ItemGroup>
<ItemGroup>
<ClInclude Include="pch.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="objlist_class.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="partman.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="loader.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="pinball.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="score.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="TPinballComponent.h">
<Filter>Header Files\TPinballComponent</Filter>
</ClInclude>
<ClInclude Include="TBall.h">
<Filter>Header Files\TPinballComponent</Filter>
</ClInclude>
<ClInclude Include="resource.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="TComponentGroup.h">
<Filter>Header Files\TPinballComponent</Filter>
</ClInclude>
<ClInclude Include="TLight.h">
<Filter>Header Files\TPinballComponent</Filter>
</ClInclude>
<ClInclude Include="TLightBargraph.h">
<Filter>Header Files\TPinballComponent</Filter>
</ClInclude>
<ClInclude Include="TLightGroup.h">
<Filter>Header Files\TPinballComponent</Filter>
</ClInclude>
<ClInclude Include="TSound.h">
<Filter>Header Files\TPinballComponent</Filter>
</ClInclude>
<ClInclude Include="TTextBox.h">
<Filter>Header Files\TPinballComponent</Filter>
</ClInclude>
<ClInclude Include="TTimer.h">
<Filter>Header Files\TPinballComponent</Filter>
</ClInclude>
<ClInclude Include="TZmapList.h">
<Filter>Header Files\TPinballComponent</Filter>
</ClInclude>
<ClInclude Include="memory.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="winmain.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="options.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="Sound.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="pb.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="render.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="fullscrn.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="gdrv.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="maths.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="zdrv.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="TEdgeSegment.h">
<Filter>Header Files\TEdgeSegment</Filter>
</ClInclude>
<ClInclude Include="TCollisionComponent.h">
<Filter>Header Files\TCollisionComponent</Filter>
</ClInclude>
<ClInclude Include="TCircle.h">
<Filter>Header Files\TEdgeSegment</Filter>
</ClInclude>
<ClInclude Include="TLine.h">
<Filter>Header Files\TEdgeSegment</Filter>
</ClInclude>
<ClInclude Include="TWall.h">
<Filter>Header Files\TCollisionComponent</Filter>
</ClInclude>
<ClInclude Include="TBumper.h">
<Filter>Header Files\TCollisionComponent</Filter>
</ClInclude>
<ClInclude Include="TBlocker.h">
<Filter>Header Files\TCollisionComponent</Filter>
</ClInclude>
<ClInclude Include="TDemo.h">
<Filter>Header Files\TCollisionComponent</Filter>
</ClInclude>
<ClInclude Include="TDrain.h">
<Filter>Header Files\TCollisionComponent</Filter>
</ClInclude>
<ClInclude Include="TFlagSpinner.h">
<Filter>Header Files\TCollisionComponent</Filter>
</ClInclude>
<ClInclude Include="TFlipper.h">
<Filter>Header Files\TCollisionComponent</Filter>
</ClInclude>
<ClInclude Include="TGate.h">
<Filter>Header Files\TCollisionComponent</Filter>
</ClInclude>
<ClInclude Include="THole.h">
<Filter>Header Files\TCollisionComponent</Filter>
</ClInclude>
<ClInclude Include="TKickback.h">
<Filter>Header Files\TCollisionComponent</Filter>
</ClInclude>
<ClInclude Include="TKickout.h">
<Filter>Header Files\TCollisionComponent</Filter>
</ClInclude>
<ClInclude Include="TOneway.h">
<Filter>Header Files\TCollisionComponent</Filter>
</ClInclude>
<ClInclude Include="TPlunger.h">
<Filter>Header Files\TCollisionComponent</Filter>
</ClInclude>
<ClInclude Include="TPopupTarget.h">
<Filter>Header Files\TCollisionComponent</Filter>
</ClInclude>
<ClInclude Include="TRamp.h">
<Filter>Header Files\TCollisionComponent</Filter>
</ClInclude>
<ClInclude Include="TRollover.h">
<Filter>Header Files\TCollisionComponent</Filter>
</ClInclude>
<ClInclude Include="TSink.h">
<Filter>Header Files\TCollisionComponent</Filter>
</ClInclude>
<ClInclude Include="TSoloTarget.h">
<Filter>Header Files\TCollisionComponent</Filter>
</ClInclude>
<ClInclude Include="TTableLayer.h">
<Filter>Header Files\TCollisionComponent</Filter>
</ClInclude>
<ClInclude Include="TPinballTable.h">
<Filter>Header Files\TPinballComponent</Filter>
</ClInclude>
<ClInclude Include="TTripwire.h">
<Filter>Header Files\TCollisionComponent</Filter>
</ClInclude>
<ClInclude Include="TLightRollover.h">
<Filter>Header Files\TCollisionComponent</Filter>
</ClInclude>
<ClInclude Include="proj.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="timer.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="midi.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="nudge.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="TTextBoxMessage.h">
<Filter>Header Files\TPinballComponent</Filter>
</ClInclude>
<ClInclude Include="high_score.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="control.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="TEdgeManager.h">
<Filter>Header Files\TPinballComponent</Filter>
</ClInclude>
<ClInclude Include="TEdgeBox.h">
<Filter>Header Files\TPinballComponent</Filter>
</ClInclude>
<ClInclude Include="TFlipperEdge.h">
<Filter>Header Files\TEdgeSegment</Filter>
</ClInclude>
<ClInclude Include="WaveMix.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="splash.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="pch.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="SpaceCadetPinball.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="objlist_class.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="partman.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="loader.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="pinball.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="score.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="TPinballComponent.cpp">
<Filter>Source Files\TPinballComponent</Filter>
</ClCompile>
<ClCompile Include="TPinballTable.cpp">
<Filter>Source Files\TPinballComponent</Filter>
</ClCompile>
<ClCompile Include="TBall.cpp">
<Filter>Source Files\TPinballComponent</Filter>
</ClCompile>
<ClCompile Include="TComponentGroup.cpp">
<Filter>Source Files\TPinballComponent</Filter>
</ClCompile>
<ClCompile Include="TLight.cpp">
<Filter>Source Files\TPinballComponent</Filter>
</ClCompile>
<ClCompile Include="TLightBargraph.cpp">
<Filter>Source Files\TPinballComponent</Filter>
</ClCompile>
<ClCompile Include="TLightGroup.cpp">
<Filter>Source Files\TPinballComponent</Filter>
</ClCompile>
<ClCompile Include="TSound.cpp">
<Filter>Source Files\TPinballComponent</Filter>
</ClCompile>
<ClCompile Include="TTextBox.cpp">
<Filter>Source Files\TPinballComponent</Filter>
</ClCompile>
<ClCompile Include="TTimer.cpp">
<Filter>Source Files\TPinballComponent</Filter>
</ClCompile>
<ClCompile Include="memory.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="winmain.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="options.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="Sound.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="pb.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="render.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="fullscrn.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="gdrv.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="maths.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="zdrv.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="TEdgeSegment.cpp">
<Filter>Source Files\TEdgeSegment</Filter>
</ClCompile>
<ClCompile Include="TCollisionComponent.cpp">
<Filter>Source Files\TCollisionComponent</Filter>
</ClCompile>
<ClCompile Include="TCircle.cpp">
<Filter>Source Files\TEdgeSegment</Filter>
</ClCompile>
<ClCompile Include="TLine.cpp">
<Filter>Source Files\TEdgeSegment</Filter>
</ClCompile>
<ClCompile Include="TWall.cpp">
<Filter>Source Files\TCollisionComponent</Filter>
</ClCompile>
<ClCompile Include="TBlocker.cpp">
<Filter>Source Files\TCollisionComponent</Filter>
</ClCompile>
<ClCompile Include="TBumper.cpp">
<Filter>Source Files\TCollisionComponent</Filter>
</ClCompile>
<ClCompile Include="TDemo.cpp">
<Filter>Source Files\TCollisionComponent</Filter>
</ClCompile>
<ClCompile Include="TDrain.cpp">
<Filter>Source Files\TCollisionComponent</Filter>
</ClCompile>
<ClCompile Include="TFlagSpinner.cpp">
<Filter>Source Files\TCollisionComponent</Filter>
</ClCompile>
<ClCompile Include="TFlipper.cpp">
<Filter>Source Files\TCollisionComponent</Filter>
</ClCompile>
<ClCompile Include="TGate.cpp">
<Filter>Source Files\TCollisionComponent</Filter>
</ClCompile>
<ClCompile Include="THole.cpp">
<Filter>Source Files\TCollisionComponent</Filter>
</ClCompile>
<ClCompile Include="TKickback.cpp">
<Filter>Source Files\TCollisionComponent</Filter>
</ClCompile>
<ClCompile Include="TKickout.cpp">
<Filter>Source Files\TCollisionComponent</Filter>
</ClCompile>
<ClCompile Include="TOneway.cpp">
<Filter>Source Files\TCollisionComponent</Filter>
</ClCompile>
<ClCompile Include="TPlunger.cpp">
<Filter>Source Files\TCollisionComponent</Filter>
</ClCompile>
<ClCompile Include="TPopupTarget.cpp">
<Filter>Source Files\TCollisionComponent</Filter>
</ClCompile>
<ClCompile Include="TRamp.cpp">
<Filter>Source Files\TCollisionComponent</Filter>
</ClCompile>
<ClCompile Include="TRollover.cpp">
<Filter>Source Files\TCollisionComponent</Filter>
</ClCompile>
<ClCompile Include="TSink.cpp">
<Filter>Source Files\TCollisionComponent</Filter>
</ClCompile>
<ClCompile Include="TSoloTarget.cpp">
<Filter>Source Files\TCollisionComponent</Filter>
</ClCompile>
<ClCompile Include="TTableLayer.cpp">
<Filter>Source Files\TCollisionComponent</Filter>
</ClCompile>
<ClCompile Include="TTripwire.cpp">
<Filter>Source Files\TCollisionComponent</Filter>
</ClCompile>
<ClCompile Include="TLightRollover.cpp">
<Filter>Source Files\TCollisionComponent</Filter>
</ClCompile>
<ClCompile Include="proj.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="timer.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="midi.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="nudge.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="TTextBoxMessage.cpp">
<Filter>Source Files\TPinballComponent</Filter>
</ClCompile>
<ClCompile Include="high_score.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="control.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="TEdgeManager.cpp">
<Filter>Source Files\TPinballComponent</Filter>
</ClCompile>
<ClCompile Include="TEdgeBox.cpp">
<Filter>Source Files\TPinballComponent</Filter>
</ClCompile>
<ClCompile Include="TFlipperEdge.cpp">
<Filter>Source Files\TEdgeSegment</Filter>
</ClCompile>
<ClCompile Include="WaveMix.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="splash.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<Natvis Include="NatvisFile.natvis" />
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="SpaceCadetPinball.rc">
<Filter>Resource Files</Filter>
</ResourceCompile>
</ItemGroup>
<ItemGroup>
<None Include="PB_MSGFT.bin">
<Filter>Resource Files</Filter>
</None>
</ItemGroup>
<ItemGroup>
<Image Include="Icon_1.ico">
<Filter>Resource Files</Filter>
</Image>
<Image Include="splash_bitmap.bmp">
<Filter>Resource Files</Filter>
</Image>
</ItemGroup>
</Project>
+125 -68
View File
@@ -2,139 +2,196 @@
#include "TBall.h"
#include "fullscrn.h"
#include "loader.h"
#include "maths.h"
#include "objlist_class.h"
#include "pb.h"
#include "proj.h"
#include "render.h"
#include "TPinballTable.h"
#include "TZmapList.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 TZmapList(0, 4);
auto groupIndex = loader::query_handle("ball");
Offset = *loader::query_float_attribute(groupIndex, 0, 500);
auto visualCount = loader::query_visual_states(groupIndex);
auto index = 0;
if (visualCount > 0)
if (groupIndex == -1)
{
auto visualZPtr = VisualZArray;
do
{
loader::query_visual(groupIndex, index, &visual);
if (ListBitmap)
ListBitmap->Add(visual.Bitmap);
auto visVec = reinterpret_cast<vector_type*>(loader::query_float_attribute(groupIndex, index, 501));
auto zDepth = proj::z_distance(visVec);
++index;
*visualZPtr = zDepth;
++visualZPtr;
}
while (index < visualCount);
HasGroupFlag = false;
Position = {0, 0, 0};
CollisionMask = 1;
}
RenderSprite = render::create_sprite(VisualType::Ball, nullptr, nullptr, 0, 0, nullptr);
PinballTable->CollisionCompOffset = Offset;
Position.Z = Offset;
else
{
HasGroupFlag = true;
loader::query_visual(groupIndex, 0, &visual);
CollisionMask = visual.CollisionGroup;
auto floatArr = loader::query_float_attribute(groupIndex, 0, 408);
Position = {floatArr[0], floatArr[1], floatArr[3]};
}
/*Full tilt: ball is ballN, where N[0,2] resolution*/
groupIndex = loader::query_handle(ballGroupName);
if (groupIndex < 0)
{
ballGroupName[4] = '0' + fullscrn::GetResolution();
groupIndex = loader::query_handle(ballGroupName);
}
Radius = *loader::query_float_attribute(groupIndex, 0, 500);
auto visualCount = loader::query_visual_states(groupIndex);
for (auto index = 0; index < visualCount; ++index)
{
loader::query_visual(groupIndex, index, &visual);
ListBitmap->push_back(visual.Bitmap);
auto visVec = reinterpret_cast<vector3*>(loader::query_float_attribute(groupIndex, index, 501));
auto zDepth = proj::z_distance(*visVec);
VisualZArray[index] = zDepth;
}
RenderSprite = new render_sprite(VisualTypes::Ball, nullptr, nullptr, 0, 0, nullptr);
PinballTable->CollisionCompOffset = Radius;
Position.Z = Radius;
GroupIndex = groupIndex;
}
void TBall::Repaint()
{
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;
int index;
for (index = 0; index < ListBitmap->Count() - 1; ++index, zArrPtr++)
auto index = 0u;
for (; index < ListBitmap->size() - 1; ++index)
{
if (*zArrPtr <= zDepth) break;
if (VisualZArray[index] <= zDepth) break;
}
auto bmp = static_cast<gdrv_bitmap8*>(ListBitmap->Get(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;
float rnd = static_cast<float>(rand());
float angle = (1.0f - (rnd * 0.00003051850947599719f + rnd * 0.00003051850947599719f)) * angleMult;
maths::RotateVector(&ball->Acceleration, angle);
rnd = static_cast<float>(rand());
ball->Speed = (1.0f - (rnd * 0.00003051850947599719f + rnd * 0.00003051850947599719f)) * (speedMult1 *
speedMult2) + speedMult1;
CollisionComp = nullptr;
Direction = *direction;
float rnd = RandFloat();
float angle = (1.0f - (rnd + rnd)) * angleMult;
maths::RotateVector(Direction, angle);
rnd = RandFloat();
Speed = (1.0f - (rnd + rnd)) * (speedMult1 * speedMult2) + speedMult1;
}
void TBall::EdgeCollision(TBall* ball, float distance)
{
ball->CollisionDisabledFlag = true;
ball->Position.X += ball->Direction.X * distance;
ball->Position.Y += ball->Direction.Y * distance;
ball->Direction.X *= ball->Speed;
ball->Direction.Y *= ball->Speed;
Direction.X *= Speed;
Direction.Y *= Speed;
// AB - vector from ball to this, BA - from this to ball; collision direction
vector2 AB{ball->Position.X - Position.X, ball->Position.Y - Position.Y};
maths::normalize_2d(AB);
vector2 BA{-AB.X, -AB.Y};
// Projection = difference between ball directions and collision direction
auto projAB = -maths::DotProduct(ball->Direction, AB);
auto projBA = -maths::DotProduct(Direction, BA);
vector2 delta{AB.X * projAB - BA.X * projBA, AB.Y * projAB - BA.Y * projBA};
ball->Direction.X += delta.X;
ball->Direction.Y += delta.Y;
ball->Speed = maths::normalize_2d(ball->Direction);
Direction.X -= delta.X;
Direction.Y -= delta.Y;
Speed = maths::normalize_2d(Direction);
}
float TBall::FindCollisionDistance(const ray_type& ray)
{
// Original inherits TCircle and aliases position.
const circle_type ballCircle{{Position.X, Position.Y}, Radius * Radius * 4.0f};
return maths::ray_intersect_circle(ray, ballCircle);
}
vector2 TBall::get_coordinates()
{
return TTableLayer::edge_manager->NormalizeBox(Position);
}
void TBall::Disable()
{
ActiveFlag = false;
CollisionDisabledFlag = true;
SpriteSet(-1);
}
+26 -22
View File
@@ -1,37 +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;
int Unknown29;
float VisualZArray[50];
float Radius;
bool HasGroupFlag;
int StuckCounter = 0;
int LastActiveTime{};
float VisualZArray[50]{};
bool CollisionDisabledFlag{};
};
+28 -29
View File
@@ -6,7 +6,6 @@
#include "loader.h"
#include "render.h"
#include "timer.h"
#include "TZmapList.h"
TBlocker::TBlocker(TPinballTable* table, int groupIndex) : TCollisionComponent(table, groupIndex, true)
{
@@ -15,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);
@@ -39,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, static_cast<gdrv_bitmap8*>(ListBitmap->Get(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.0)
timerTime = 0.0;
else
timerTime = value;
Timer = timer::set(timerTime, this, TimerExpired);
return 0;
}
@@ -69,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);
}
+3 -3
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;
+23 -52
View File
@@ -7,7 +7,6 @@
#include "render.h"
#include "timer.h"
#include "TPinballTable.h"
#include "TZmapList.h"
TBumper::TBumper(TPinballTable* table, int groupIndex) : TCollisionComponent(table, groupIndex, true)
{
@@ -22,47 +21,48 @@ 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));
if (2 * nextBmp > ListBitmap->Count() - 1)
nextBmp = (ListBitmap->Count() - 1) / 2;
auto maxBmp = static_cast<int>(ListBitmap->size()) - 1;
if (2 * nextBmp > maxBmp)
nextBmp = maxBmp / 2;
if (nextBmp < 0)
nextBmp = 0;
if (nextBmp != BmpIndex)
{
if (nextBmp >= BmpIndex)
loader::play_sound(SoundIndex4);
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 = ListBitmap->Count() - 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 = static_cast<gdrv_bitmap8*>(bump->ListBitmap->Get(bump->BmpIndex * 2));
auto zMap = static_cast<zmap_header_type*>(bump->ListZMap->Get(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 = static_cast<gdrv_bitmap8*>(ListBitmap->Get(bmpIndex));
auto zMap = static_cast<zmap_header_type*>(ListZMap->Get(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;
}
+3 -6
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];
TBumper_player_backup PlayerData[4]{};
};
+19 -10
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);
}
+5 -5
View File
@@ -6,11 +6,11 @@ class TCircle :
public TEdgeSegment
{
public:
circle_type Circle;
circle_type Circle{};
TCircle(TCollisionComponent* collComp, char* activeFlag, unsigned int collisionGroup, vector_type* center,
TCircle(TCollisionComponent* collComp, char* activeFlag, unsigned int collisionGroup, vector2* center,
float radius);
float 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;
};
+27 -44
View File
@@ -2,18 +2,18 @@
#include "TCollisionComponent.h"
#include "loader.h"
#include "maths.h"
#include "objlist_class.h"
#include "TEdgeSegment.h"
#include "TPinballTable.h"
#include "TBall.h"
TCollisionComponent::TCollisionComponent(TPinballTable* table, int groupIndex, bool createWall) : TPinballComponent(
table, groupIndex, true)
TCollisionComponent::TCollisionComponent(TPinballTable* table, int groupIndex, bool createWall) :
TPinballComponent(table, groupIndex, true)
{
visualStruct visual{};
EdgeList = new objlist_class(4, 4);
ActiveFlag = 1;
AABB = { -10000, -10000, 10000, 10000 };
if (GroupName != nullptr)
UnusedBaseFlag = 1;
if (groupIndex <= 0)
@@ -42,57 +42,48 @@ TCollisionComponent::TCollisionComponent(TPinballTable* table, int groupIndex, b
TCollisionComponent::~TCollisionComponent()
{
for (TEdgeSegment* edge; EdgeList->Count() > 0;)
{
edge = static_cast<TEdgeSegment*>(EdgeList->Get(0));
EdgeList->Delete(edge);
for (auto edge : EdgeList)
delete edge;
}
delete EdgeList;
}
void TCollisionComponent::port_draw()
{
for (int index = EdgeList->Count() - 1; index >= 0; index--)
{
static_cast<TEdgeSegment*>(EdgeList->Get(index))->port_draw();
}
for (auto edge : EdgeList)
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.2)
{
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;
}
double projSpeed = maths::basic_collision(
auto projSpeed = maths::basic_collision(
ball,
nextPosition,
direction,
@@ -100,21 +91,13 @@ void TCollisionComponent::Collision(TBall* ball, vector_type* nextPosition, vect
Smoothness,
Threshold,
Boost);
if (projSpeed <= Threshold)
{
if (projSpeed <= 0.2)
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;
}
+7 -6
View File
@@ -1,27 +1,28 @@
#pragma once
#include "maths.h"
#include "TPinballComponent.h"
class objlist_class;
struct vector_type;
struct vector2;
class TEdgeSegment;
class TBall;
class TCollisionComponent : public TPinballComponent
{
public:
objlist_class* EdgeList;
std::vector<TEdgeSegment*> EdgeList;
float Elasticity;
float Smoothness;
float Boost;
float Threshold;
int SoftHitSoundId;
int HardHitSoundId;
RectF AABB;
TCollisionComponent(TPinballTable* table, int groupIndex, bool createWall);
~TCollisionComponent() override;
void port_draw() override;
virtual void Collision(TBall* ball, 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);
};
+9 -11
View File
@@ -4,13 +4,11 @@
#include "control.h"
#include "loader.h"
#include "objlist_class.h"
#include "timer.h"
#include "TPinballTable.h"
TComponentGroup::TComponentGroup(TPinballTable* table, int groupIndex) : TPinballComponent(table, groupIndex, false)
{
List = new objlist_class(4, 4);
Timer = 0;
if (groupIndex > 0)
{
@@ -21,7 +19,7 @@ TComponentGroup::TComponentGroup(TPinballTable* table, int groupIndex) : TPinbal
{
auto component = table->find_component(*shortArrPtr);
if (component)
List->Add(component);
List.push_back(component);
}
}
}
@@ -33,26 +31,26 @@ TComponentGroup::~TComponentGroup()
timer::kill(Timer);
Timer = 0;
}
delete List;
}
int TComponentGroup::Message(int code, float value)
int TComponentGroup::Message(MessageCode code, float value)
{
if (code == 48)
if (code == MessageCode::TComponentGroupResetNotifyTimer)
{
if (this->Timer)
{
timer::kill(this->Timer);
this->Timer = 0;
}
if (value > 0.0)
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 (int i = 0; i < List->Count(); i++)
for (auto component : List)
{
static_cast<TPinballComponent*>(List->Get(i))->Message(code, value);
component->Message(code, value);
}
}
return 0;
@@ -62,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);
}
+2 -3
View File
@@ -1,7 +1,6 @@
#pragma once
#include "TPinballComponent.h"
class objlist_class;
class TComponentGroup :
public TPinballComponent
@@ -9,9 +8,9 @@ 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);
objlist_class* List;
std::vector<TPinballComponent*> List;
int Timer;
};
+22 -24
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,20 +93,19 @@ 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<int>(edge->WallValue))
switch (reinterpret_cast<size_t>(edge->WallValue))
{
case 1400:
if (!FlipLeftTimer && !FlipLeftFlag)
{
float time = FlipTimerTime1 + FlipTimerTime2 - static_cast<float>(rand()) *
0.00003051850947599719f * (FlipTimerTime2 + FlipTimerTime2);
float time = FlipTimerTime1 + FlipTimerTime2 - RandFloat() * (FlipTimerTime2 + FlipTimerTime2);
FlipLeftTimer = timer::set(time, this, FlipLeft);
}
break;
@@ -116,8 +115,7 @@ void TDemo::Collision(TBall* ball, vector_type* nextPosition, vector_type* direc
case 1402:
if (!FlipRightTimer && !FlipRightFlag)
{
float time = FlipTimerTime1 + FlipTimerTime2 - static_cast<float>(rand()) *
0.00003051850947599719f * (FlipTimerTime2 + FlipTimerTime2);
float time = FlipTimerTime1 + FlipTimerTime2 - RandFloat() * (FlipTimerTime2 + FlipTimerTime2);
FlipRightTimer = timer::set(time, this, FlipRight);
}
break;
@@ -127,8 +125,8 @@ void TDemo::Collision(TBall* ball, vector_type* nextPosition, vector_type* direc
case 1404:
if (!PlungerFlag)
{
PinballTable->Message(1004, ball->TimeNow);
float time = static_cast<float>(rand()) * 0.00003051850947599719f + 2.0f;
PinballTable->Message(MessageCode::PlungerInputPressed, 0);
float time = RandFloat() + 2.0f;
PlungerFlag = timer::set(time, this, PlungerRelease);
}
break;
@@ -141,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;
}
@@ -156,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;
}
@@ -170,10 +168,10 @@ 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 - static_cast<float>(rand()) *
0.00003051850947599719f * (demo->UnFlipTimerTime2 + demo->UnFlipTimerTime2);
float time = demo->UnFlipTimerTime1 + demo->UnFlipTimerTime2 - RandFloat() *
(demo->UnFlipTimerTime2 + demo->UnFlipTimerTime2);
timer::set(time, demo, UnFlipRight);
}
}
@@ -188,10 +186,10 @@ 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 - static_cast<float>(rand()) *
0.00003051850947599719f * (demo->UnFlipTimerTime2 + demo->UnFlipTimerTime2);
float time = demo->UnFlipTimerTime1 + demo->UnFlipTimerTime2 - RandFloat() *
(demo->UnFlipTimerTime2 + demo->UnFlipTimerTime2);
timer::set(time, demo, UnFlipLeft);
}
}
@@ -199,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;
}
+2 -2
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);
+14 -9
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);
}
+2 -2
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);
-16
View File
@@ -1,16 +0,0 @@
#include "pch.h"
#include "TEdgeBox.h"
#include "objlist_class.h"
TEdgeBox::TEdgeBox()
{
EdgeList = new objlist_class(0, 4);
FieldList = new objlist_class(0, 1);
}
TEdgeBox::~TEdgeBox()
{
delete EdgeList;
delete FieldList;
}
+5 -7
View File
@@ -1,13 +1,11 @@
#pragma once
class objlist_class;
struct field_effect_type;
class TEdgeSegment;
class TEdgeBox
{
public:
TEdgeBox();
~TEdgeBox();
objlist_class* EdgeList;
objlist_class* FieldList;
std::vector<TEdgeSegment*> EdgeList{};
std::vector<field_effect_type*> FieldList{};
};
+100 -145
View File
@@ -3,23 +3,24 @@
#include "maths.h"
#include "objlist_class.h"
#include "TBall.h"
#include "TEdgeBox.h"
#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()
@@ -29,53 +30,61 @@ TEdgeManager::~TEdgeManager()
int TEdgeManager::box_x(float x)
{
return static_cast<int>((max(0, min(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 static_cast<int>((max(0, min(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)
{
return min(x + 1, MaxBoxX - 1);
return std::min(x + 1, MaxBoxX - 1);
}
int TEdgeManager::increment_box_y(int y)
{
return min(y + 1, MaxBoxY - 1);
return std::min(y + 1, MaxBoxY - 1);
}
void TEdgeManager::add_edge_to_box(int x, int y, TEdgeSegment* edge)
{
BoxArray[x + y * MaxBoxX].EdgeList->Add(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->Add(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 index = edgeBox->EdgeList->Count() - 1; index >= 0; --index)
for (auto it = edgeBox->EdgeList.rbegin(); it != edgeBox->EdgeList.rend(); ++it)
{
auto edge = static_cast<TEdgeSegment*>(edgeBox->EdgeList->Get(index));
if (!edge->ProcessedFlag && *edge->ActiveFlag && (edge->CollisionGroup & ray->FieldFlag))
auto edge = *it;
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;
@@ -88,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 (int index = edgeBox->FieldList->Count() - 1; index >= 0; --index)
for (auto it = edgeBox->FieldList.rbegin(); it != edgeBox->FieldList.rend(); ++it)
{
auto field = static_cast<field_effect_type*>(edgeBox->FieldList->Get(index));
if (*field->Flag2Ptr && ball->FieldFlag & field->Mask)
auto field = *it;
if (*field->ActiveFlag && ball->CollisionMask & field->CollisionGroup)
{
if (field->CollisionComp->FieldEffect(ball, &vec))
{
maths::vector_add(dstVec, &vec);
maths::vector_add(*dstVec, vec);
}
}
}
@@ -112,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);
}
}
@@ -274,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 };
}
+13 -9
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];
TEdgeSegment* EdgeArray[1000]{};
};
+25 -42
View File
@@ -1,7 +1,6 @@
#include "pch.h"
#include "TEdgeSegment.h"
#include "objlist_class.h"
#include "TCircle.h"
#include "TCollisionComponent.h"
#include "TLine.h"
@@ -9,7 +8,7 @@
TEdgeSegment::TEdgeSegment(TCollisionComponent* collComp, char* activeFlag, unsigned collisionGroup)
{
CollisionComponent = collComp;
ActiveFlag = activeFlag;
ActiveFlagPtr = activeFlag;
CollisionGroup = collisionGroup;
ProcessedFlag = 0;
}
@@ -19,9 +18,10 @@ void TEdgeSegment::port_draw()
}
TEdgeSegment* TEdgeSegment::install_wall(float* floatArr, TCollisionComponent* collComp, char* activeFlagPtr,
unsigned int collisionGroup, float offset, int wallValue)
unsigned int collisionGroup, float offset, size_t wallValue)
{
vector_type center{}, start{}, end{}, prevCenter{}, vec1{}, vec2{}, dstVec{};
vector2 center{}, start{}, end{}, prevCenter{};
vector3 vec1{}, vec2{}, dstVec{};
TEdgeSegment* edge = nullptr;
wall_type wallType = static_cast<wall_type>(static_cast<int>(floor(*floatArr) - 1.0f));
@@ -33,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);
circle->WallValue = reinterpret_cast<void*>(wallValue);
circle->place_in_grid(&collComp->AABB);
collComp->EdgeList.push_back(circle);
edge = circle;
if (circle)
{
circle->WallValue = reinterpret_cast<void*>(wallValue);
circle->place_in_grid();
}
collComp->EdgeList->Add(circle);
break;
}
case wall_type::Line:
@@ -50,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->Add(line);
}
break;
}
default:
@@ -87,25 +78,21 @@ TEdgeSegment* TEdgeSegment::install_wall(float* floatArr, TCollisionComponent* c
center.X = centerX1;
center.Y = centerY1;
if (offset != 0.0)
if (offset != 0.0f)
{
vec1.X = centerX1 - prevCenter.X;
vec1.Y = center.Y - prevCenter.Y;
vec2.X = centerX2 - centerX1;
vec2.Y = centerY2 - center.Y;
maths::cross(&vec1, &vec2, &dstVec);
if (dstVec.Z > 0.0 && offset > 0.0 ||
dstVec.Z < 0.0 && offset < 0.0)
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->Add(circle);
}
circle->WallValue = reinterpret_cast<void*>(wallValue);
circle->place_in_grid(&collComp->AABB);
collComp->EdgeList.push_back(circle);
}
}
@@ -113,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->Add(line);
}
prevCenter = center;
}
}
+7 -6
View File
@@ -3,6 +3,7 @@
class TBall;
class TCollisionComponent;
struct ray_type;
struct RectF;
enum class wall_type : int
{
@@ -14,19 +15,19 @@ class TEdgeSegment
{
public:
TCollisionComponent* CollisionComponent;
char* ActiveFlag;
char* ActiveFlagPtr;
char ProcessedFlag;
void* WallValue;
void* WallValue{};
unsigned int CollisionGroup;
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, int wallValue);
unsigned int collisionGroup, float offset, size_t wallValue);
};
+19 -47
View File
@@ -4,38 +4,36 @@
#include "control.h"
#include "loader.h"
#include "objlist_class.h"
#include "render.h"
#include "TBall.h"
#include "timer.h"
#include "TLine.h"
#include "TPinballTable.h"
#include "TZmapList.h"
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);
end.X = *visual.FloatArr;
end.X = visual.FloatArr[0];
end.Y = visual.FloatArr[1];
start.X = visual.FloatArr[2];
start.Y = visual.FloatArr[3];
auto line = new TLine(this, &ActiveFlag, visual.CollisionGroup, &start, &end);
auto line = new TLine(this, &ActiveFlag, visual.CollisionGroup, start, end);
if (line)
{
line->place_in_grid();
EdgeList->Add(line);
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();
EdgeList->Add(line);
line->place_in_grid(&AABB);
EdgeList.push_back(line);
}
SpeedDecrement = 0.64999998f;
@@ -52,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)
{
@@ -62,28 +60,21 @@ int TFlagSpinner::Message(int code, float value)
Timer = 0;
}
BmpIndex = 0;
auto bmp = static_cast<gdrv_bitmap8*>(ListBitmap->Get(0));
auto zMap = static_cast<zmap_header_type*>(ListZMap->Get(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;
if (ball->Speed == 0.0)
if (ball->Speed == 0.0f)
Speed = MinSpeed;
else
Speed = ball->Speed * 20.0f;
@@ -94,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->Count();
int bmpCount = static_cast<int>(ListBitmap->size());
if (bmpIndex >= bmpCount)
BmpIndex = 0;
else if (bmpIndex < 0)
@@ -117,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 = static_cast<gdrv_bitmap8*>(ListBitmap->Get(BmpIndex));
auto zMap = static_cast<zmap_header_type*>(ListZMap->Get(BmpIndex));
render::sprite_set(
RenderSprite,
bmp,
zMap,
bmp->XPosition - PinballTable->XOffset,
bmp->YPosition - PinballTable->YOffset);
SpriteSet(BmpIndex);
Speed *= SpeedDecrement;
if (Speed >= MinSpeed)
{
+5 -8
View File
@@ -6,23 +6,20 @@ 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);
float Speed;
float Speed{};
float MaxSpeed;
float MinSpeed;
float SpeedDecrement;
int SpinDirection;
int BmpIndex;
int SpinDirection{};
int BmpIndex{};
int Timer;
TEdgeSegment* PrevCollider;
int Scores[2];
};
+122 -107
View File
@@ -6,9 +6,10 @@
#include "loader.h"
#include "pb.h"
#include "render.h"
#include "TBall.h"
#include "TFlipperEdge.h"
#include "timer.h"
#include "TZmapList.h"
#include "TPinballTable.h"
TFlipper::TFlipper(TPinballTable* table, int groupIndex) : TCollisionComponent(table, groupIndex, false)
{
@@ -18,18 +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 floatArr = loader::query_float_attribute(groupIndex, 0, 803);
auto floatArr2 = loader::query_float_attribute(groupIndex, 0, 805);
auto floatArr3 = loader::query_float_attribute(groupIndex, 0, 804);
auto collMult = *floatArr;
auto bmpCoef2 = *floatArr2;
auto bmpCoef1 = *floatArr3;
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 collMult = *loader::query_float_attribute(groupIndex, 0, 803);
auto retractTime = *loader::query_float_attribute(groupIndex, 0, 805);
auto extendTime = *loader::query_float_attribute(groupIndex, 0, 804);
auto vecT2 = reinterpret_cast<vector3*>(loader::query_float_attribute(groupIndex, 0, 802));
auto vecT1 = reinterpret_cast<vector3*>(loader::query_float_attribute(groupIndex, 0, 801));
auto origin = reinterpret_cast<vector3*>(loader::query_float_attribute(groupIndex, 0, 800));
auto flipperEdge = new TFlipperEdge(
this,
&ActiveFlag,
@@ -38,86 +35,72 @@ TFlipper::TFlipper(TPinballTable* table, int groupIndex) : TCollisionComponent(t
origin,
vecT1,
vecT2,
bmpCoef1,
bmpCoef2,
extendTime,
retractTime,
collMult,
Elasticity,
Smoothness);
flipperEdge->place_in_grid(&AABB);
FlipperEdge = flipperEdge;
if (flipperEdge)
{
BmpCoef1 = flipperEdge->BmpCoef1 / static_cast<float>(ListBitmap->Count() - 1);
BmpCoef2 = flipperEdge->BmpCoef2 / static_cast<float>(ListBitmap->Count() - 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 soundIndex = 0, code2 = 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 = BmpCoef1;
soundIndex = HardHitSoundId;
control::handler(MessageCode::TFlipperExtend, this);
loader::play_sound(HardHitSoundId, this, "TFlipper1");
}
else if (code == 2)
else if (code == MessageCode::TFlipperRetract)
{
TimerTime = BmpCoef2;
soundIndex = SoftHitSoundId;
loader::play_sound(SoftHitSoundId, this, "TFlipper2");
}
else
{
code2 = 2;
TimerTime = BmpCoef2;
// Retract for all non-input messages
code = MessageCode::TFlipperRetract;
}
if (soundIndex)
loader::play_sound(soundIndex);
if (Timer)
{
timer::kill(Timer);
Timer = 0;
}
MessageField = FlipperEdge->SetMotion(code);
break;
case MessageCode::PlayerChanged:
case MessageCode::Reset:
if (MessageField)
{
auto v10 = value - FlipperEdge->InputTime;
timerTime = v10 - floor(v10 / TimerTime) * TimerTime;
if (timerTime < 0.0)
timerTime = 0.0;
FlipperEdge->CurrentAngle = 0;
FlipperEdge->set_control_points(0);
MessageField = 0;
FlipperEdge->SetMotion(MessageCode::Reset);
UpdateSprite();
}
else
{
timerTime = TimerTime;
}
MessageField = code2;
InputTime = value;
Timer = timer::set(timerTime, this, TimerExpired);
FlipperEdge->SetMotion(code2, 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;
}
@@ -126,63 +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 timer; // eax
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;
bool bmpIndexOutOfBounds = false;
auto bmpIndexAdvance = static_cast<int>(floor((pb::time_now - flip->InputTime) / flip->TimerTime + 0.5f));
int bmpCount = flip->ListBitmap->Count();
if (bmpIndexAdvance > bmpCount)
bmpIndexAdvance = bmpCount;
if (bmpIndexAdvance < 0)
bmpIndexAdvance = 0;
if (!bmpIndexAdvance)
bmpIndexAdvance = 1;
if (flip->MessageField == 1)
BmpIndex = newBmpIndex;
SpriteSet(BmpIndex);
}
int TFlipper::GetFlipperStepAngle(float dt, float* dst) const
{
if (!MessageField)
return 0;
auto deltaAngle = FlipperEdge->flipper_angle_delta(dt);
auto step = std::fabs(std::ceil(FlipperEdge->DistanceDiv * deltaAngle * FlipperEdge->InvT1Radius));
if (step > 3.0f)
step = 3.0f;
if (step >= 2.0f)
{
*dst = deltaAngle / step;
return static_cast<int>(step);
}
*dst = deltaAngle;
return 1;
}
void TFlipper::FlipperCollision(float deltaAngle)
{
if (!MessageField)
return;
ray_type ray{}, rayDst{};
ray.MinDistance = 0.002f;
bool collisionFlag = false;
for (auto ball : pb::MainTable->BallList)
{
flip->BmpIndex += bmpIndexAdvance;
int countSub1 = flip->ListBitmap->Count() - 1;
if (flip->BmpIndex >= countSub1)
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 = countSub1;
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 (flip->MessageField == 2)
if (collisionFlag)
{
flip->BmpIndex -= bmpIndexAdvance;
timer = 0;
if (flip->BmpIndex <= 0)
{
flip->BmpIndex = 0;
bmpIndexOutOfBounds = true;
}
auto angleAdvance = deltaAngle / (std::fabs(FlipperEdge->MoveSpeed) * 5.0f);
FlipperEdge->CurrentAngle -= angleAdvance;
FlipperEdge->AngleRemainder += std::fabs(angleAdvance);
}
else
{
timer = 0;
FlipperEdge->CurrentAngle += deltaAngle;
FlipperEdge->AngleRemainder -= std::fabs(deltaAngle);
}
if (bmpIndexOutOfBounds)
flip->MessageField = 0;
else
timer = timer::set(flip->TimerTime, flip, TimerExpired);
flip->Timer = timer;
auto bmp = static_cast<gdrv_bitmap8*>(flip->ListBitmap->Get(flip->BmpIndex));
auto zMap = static_cast<zmap_header_type*>(flip->ListZMap->Get(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;
}
+5 -9
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 BmpCoef1;
float BmpCoef2;
float TimerTime;
float InputTime;
};
+180 -397
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 bmpCoef1, float bmpCoef2,
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;
BmpCoef1 = bmpCoef1;
BmpCoef2 = bmpCoef2;
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,294 +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);
if (crossProd.Z < 0.0)
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;
if (AngleMax < 0.0)
// 3DPB and FT have different formats for flipper speed:
// 3DPB: Time it takes for flipper to go from source to destination, in sec.
// FT: Flipper movement speed, in radians per sec.
if (!pb::FullTiltMode)
{
maths::vswap(&A1Src, &B1Src);
maths::vswap(&A2Src, &B2Src);
auto angleMax = std::abs(AngleMax);
retractSpeed = angleMax / retractSpeed;
extendSpeed = angleMax / extendSpeed;
}
ExtendSpeed = extendSpeed;
RetractSpeed = retractSpeed;
const vector2 perpOriginT1Cc = {-vecOriginT1.Y, vecOriginT1.X};
A2Src.X = perpOriginT1Cc.X * CirclebaseRadius + origin->X;
A2Src.Y = perpOriginT1Cc.Y * CirclebaseRadius + origin->Y;
A1Src.X = perpOriginT1Cc.X * CircleT1Radius + vecT1->X;
A1Src.Y = perpOriginT1Cc.Y * CircleT1Radius + vecT1->Y;
const vector2 perpOriginT1C = {vecOriginT1.Y, -vecOriginT1.X};
B1Src.X = perpOriginT1C.X * CirclebaseRadius + origin->X;
B1Src.Y = perpOriginT1C.Y * CirclebaseRadius + origin->Y;
B2Src.X = perpOriginT1C.X * CircleT1Radius + vecT1->X;
B2Src.Y = perpOriginT1C.Y * CircleT1Radius + vecT1->Y;
if (AngleMax < 0.0f)
{
std::swap(A1Src, B1Src);
std::swap(A2Src, B2Src);
}
auto dx = vecT1->X - RotOrigin.X;
auto dy = vecT1->Y - RotOrigin.Y;
auto distance1 = sqrt(dy * dy + dx * dx) + table->CollisionCompOffset + vecT1->Z;
DistanceDiv = distance1;
DistanceDivSq = distance1 * distance1;
InvT1Radius = 1.0f / CircleT1Radius * 1.5f;
float bmpCoef = min(BmpCoef1, BmpCoef2);
auto distance = maths::Distance(vecT1, vecT2);
CollisionTimeAdvance = bmpCoef / (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)
{
EdgeCollisionFlag = 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.0)
{
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+09)
{
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+09)
{
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+09)
{
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+09)
{
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+09)
{
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.0;
}
else
{
CollisionFlag1 = AngleMax > 0.0;
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.01 < distance)
{
float v11;
float v20 = sqrt(distance / DistanceDivSq) * (fabs(AngleMax) / AngleMult);
float dot1 = maths::DotProduct(&CollisionLinePerp, &CollisionDirection);
if (dot1 >= 0.0)
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.0 ? 1000000000.0f : -1.0f;
auto threshold = boost <= 0.0f ? 1e9f : -1.0f;
maths::basic_collision(
ball,
&NextBallPosition,
@@ -332,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.01 < 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.0)
angle = -angle;
if (angle >= 0.0000001)
angle = (timeNow - InputTime) / angle;
else
angle = 1.0;
angle = min(1, max(angle, 0));
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.0 &&
(B1.X - A2.X) * (y - A2.Y) - (B1.Y - A2.Y) * (x - A2.X) >= 0.0 &&
(B2.X - B1.X) * (y - B1.Y) - (B2.Y - B1.Y) * (x - B1.X) >= 0.0 &&
(A1.X - B2.X) * (y - B2.Y) - (A1.Y - B2.Y) * (x - B2.X) >= 0.0 ||
dy * dy + dx * dx <= CirclebaseRadiusSq ||
(T1.Y - y) * (T1.Y - y) + (T1.X - x) * (T1.X - x) < CircleT1RadiusSq)
{
float flipperLR = AngleMax < 0.0 ? -1.0f : 1.0f;
if (FlipperFlag == 1)
testPoint = AngleMax < 0.0 ? B1 : B2;
else if (FlipperFlag == 2)
testPoint = AngleMax < 0.0 ? A2 : A1;
else
testPoint = T1;
if (((y - testPoint.Y) * (RotOrigin.X - testPoint.X) -
(x - testPoint.X) * (RotOrigin.Y - testPoint.Y)) * flipperLR < 0.0)
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 = BmpCoef1;
case MessageCode::TFlipperExtend:
AngleRemainder = std::fabs(AngleMax - CurrentAngle);
AngleDst = AngleMax;
MoveSpeed = ExtendSpeed;
break;
case 2:
Angle2 = flipper_angle(value);
Angle1 = 0.0;
AngleMult = BmpCoef2;
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);
}
+32 -37
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 bmpCoef1, float bmpCoef2, 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 BmpCoef1;
float BmpCoef2;
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{};
};
+19 -19
View File
@@ -5,7 +5,6 @@
#include "control.h"
#include "loader.h"
#include "render.h"
#include "TZmapList.h"
TGate::TGate(TPinballTable* table, int groupIndex) : TCollisionComponent(table, groupIndex, true)
{
@@ -15,28 +14,29 @@ TGate::TGate(TPinballTable* table, int groupIndex) : TCollisionComponent(table,
SoundIndex4 = visual.SoundIndex4;
SoundIndex3 = visual.SoundIndex3;
ActiveFlag = 1;
render::sprite_set_bitmap(RenderSprite, static_cast<gdrv_bitmap8*>(ListBitmap->Get(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, static_cast<gdrv_bitmap8*>(ListBitmap->Get(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;
}
+1 -1
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;
+51 -51
View File
@@ -4,7 +4,7 @@
#include "control.h"
#include "loader.h"
#include "objlist_class.h"
#include "pb.h"
#include "TBall.h"
#include "timer.h"
#include "TPinballTable.h"
@@ -18,105 +18,105 @@ THole::THole(TPinballTable* table, int groupIndex) : TCollisionComponent(table,
Unknown4 = 0.050000001f;
MessageField = 0;
Timer = 0;
BallCapturedFlag = 0;
auto floatArr1 = loader::query_float_attribute(groupIndex, 0, 407);
if (floatArr1)
Unknown3 = *floatArr1;
else
Unknown3 = 0.25;
auto floatArr2 = loader::query_float_attribute(groupIndex, 0, 701);
if (floatArr2)
GravityMult = *floatArr2;
else
GravityMult = 0.5;
BallCapturedFlag = false;
Unknown3 = loader::query_float_attribute(groupIndex, 0, 407, 0.25f);
GravityMult = loader::query_float_attribute(groupIndex, 0, 701, 0.2f);
GravityPull = *loader::query_float_attribute(groupIndex, 0, 305);
loader::query_visual(groupIndex, 0, &visual);
Circle.Center.X = visual.FloatArr[0];
Circle.Center.Y = visual.FloatArr[1];
Circle.RadiusSq = *loader::query_float_attribute(groupIndex, 0, 306) * visual.FloatArr[2];
if (Circle.RadiusSq == 0.0)
if (Circle.RadiusSq == 0.0f)
Circle.RadiusSq = 0.001f;
auto tCircle = new TCircle(this, &ActiveFlag, visual.CollisionGroup, reinterpret_cast<vector_type*>(visual.FloatArr),
auto tCircle = new TCircle(this, &ActiveFlag, visual.CollisionGroup,
reinterpret_cast<vector3*>(visual.FloatArr),
Circle.RadiusSq);
if (tCircle)
{
tCircle->place_in_grid();
EdgeList->Add(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)));
// CollisionMask difference: 3DPB - value given as is, FT: mask = 1 << value
if (pb::FullTiltMode)
CollisionMask = 1 << CollisionMask;
Circle.RadiusSq = visual.FloatArr[2] * visual.FloatArr[2];
circle.RadiusSq = Circle.RadiusSq;
circle.Center.X = Circle.Center.X;
circle.Center.Y = Circle.Center.Y;
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;
Timer = timer::set(0.5f, this, TimerExpired);
ball->Direction.Z = 0.0;
ball->CollisionDisabledFlag = true;
// Ramp hole has no delay in FT.
auto captureTime = pb::FullTiltMode ? 0 : 0.5f;
Timer = timer::set(captureTime, this, TimerExpired);
if (!PinballTable->TiltLockFlag)
{
loader::play_sound(HardHitSoundId);
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;
@@ -127,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
@@ -144,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;
}
+8 -8
View File
@@ -8,22 +8,22 @@ 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;
circle_type Circle{};
field_effect_type Field{};
};
+15 -37
View File
@@ -8,33 +8,31 @@
#include "render.h"
#include "timer.h"
#include "TPinballTable.h"
#include "TZmapList.h"
TKickback::TKickback(TPinballTable* table, int groupIndex): TCollisionComponent(table, groupIndex, true)
{
MessageField = 0;
Timer = 0;
ActiveFlag = 0;
TimerTime = 0.69999999f;
KickActiveFlag = 0;
TimerTime = 0.7f;
TimerTime2 = 0.1f;
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;
ActiveFlag = 0;
KickActiveFlag = 0;
Threshold = 1000000000.0;
}
return 0;
}
void TKickback::Collision(TBall* ball, vector_type* nextPosition, vector_type* direction, float coef,
void TKickback::Collision(TBall* ball, vector2* nextPosition, vector2* direction, float distance,
TEdgeSegment* edge)
{
if (PinballTable->TiltLockFlag)
@@ -44,14 +42,14 @@ void TKickback::Collision(TBall* ball, vector_type* nextPosition, vector_type* d
}
else
{
if (!ActiveFlag)
if (!KickActiveFlag)
{
Threshold = 1000000000.0;
ActiveFlag = 1;
KickActiveFlag = 1;
Timer = timer::set(TimerTime, this, TimerExpired);
}
if (DefaultCollision(ball, nextPosition, direction))
ActiveFlag = 0;
KickActiveFlag = 0;
}
}
@@ -59,37 +57,17 @@ void TKickback::TimerExpired(int timerId, void* caller)
{
auto kick = static_cast<TKickback*>(caller);
if (kick->ActiveFlag)
if (kick->KickActiveFlag)
{
kick->Threshold = 0.0;
kick->Timer = timer::set(kick->TimerTime2, kick, TimerExpired);
loader::play_sound(kick->HardHitSoundId);
if (kick->ListBitmap)
{
auto bmp = static_cast<gdrv_bitmap8*>(kick->ListBitmap->Get(1));
auto zMap = static_cast<zmap_header_type*>(kick->ListZMap->Get(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 = static_cast<gdrv_bitmap8*>(kick->ListBitmap->Get(0));
auto zMap = static_cast<zmap_header_type*>(kick->ListZMap->Get(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);
}
}
+3 -3
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);
@@ -15,5 +15,5 @@ public:
float TimerTime;
float TimerTime2;
int Timer;
int ActiveFlag;
int KickActiveFlag;
};
+44 -47
View File
@@ -4,7 +4,7 @@
#include "control.h"
#include "loader.h"
#include "objlist_class.h"
#include "pb.h"
#include "TBall.h"
#include "TCircle.h"
#include "timer.h"
@@ -24,60 +24,60 @@ 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;
HardHitSoundId = visual.Kicker.HardHitSoundId;
Circle.Center.X = *visual.FloatArr;
Circle.Center.X = visual.FloatArr[0];
Circle.Center.Y = visual.FloatArr[1];
Circle.RadiusSq = *loader::query_float_attribute(groupIndex, 0, 306) * visual.FloatArr[2];
if (Circle.RadiusSq == 0.0)
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();
EdgeList->Add(tCircle);
tCircle->place_in_grid(&AABB);
EdgeList.push_back(tCircle);
}
Circle.RadiusSq = visual.FloatArr[2] * visual.FloatArr[2];
CollisionBallSetZ = loader::query_float_attribute(groupIndex, 0, 408)[2];
auto zAttr = loader::query_float_attribute(groupIndex, 0, 408);
CollisionBallSetZ = pb::FullTiltMode && !pb::FullTiltDemoMode ? zAttr[3] : zAttr[2];
ThrowSpeedMult2 = visual.Kicker.ThrowBallMult * 0.01f;
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.0)
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);
@@ -93,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);
}
}
}
+9 -12
View File
@@ -8,30 +8,27 @@ 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;
float TimerTime2;
float CollisionBallSetZ;
TBall* Ball;
TBall* Ball{};
float FieldMult;
circle_type Circle;
float OriginalBallZ;
vector_type BallAcceleration;
circle_type Circle{};
float OriginalBallZ{};
vector3 BallThrowDirection{};
float ThrowAngleMult;
float ThrowSpeedMult1;
float ThrowSpeedMult2;
field_effect_type Field;
int Scores[5];
field_effect_type Field{};
};
+226 -199
View File
@@ -7,228 +7,241 @@
#include "render.h"
#include "timer.h"
#include "TPinballTable.h"
#include "TZmapList.h"
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:
BmpIndex2 = static_cast<int>(floor(value));
if (BmpIndex2 > ListBitmap->Count())
BmpIndex2 = ListBitmap->Count();
bmpIndex = 0;
if (BmpIndex2 < 0)
BmpIndex2 = 0;
Flasher.BmpArr[0] = nullptr;
Flasher.BmpArr[1] = static_cast<gdrv_bitmap8*>(ListBitmap->Get(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 > ListBitmap->Count())
bmpIndex = ListBitmap->Count();
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;
}
@@ -238,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] = static_cast<gdrv_bitmap8*>(ListBitmap->Get(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;
if (time > 0.0)
Timer1 = timer::set(time, this, TimerExpired);
FlashDelay[0] = SourceDelay[0];
FlashDelay[1] = SourceDelay[1];
if (TimeoutTimer)
timer::kill(TimeoutTimer);
TimeoutTimer = 0;
if (time > 0.0f)
TimeoutTimer = timer::set(time, this, TimerExpired);
}
void TLight::TimerExpired(int timerId, void* caller)
{
auto light = static_cast<TLight*>(caller);
if (light->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;
}
+25 -35
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,26 +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;
int Unknown19;
float Unknown20F;
TLight_player_backup PlayerData[4];
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]{};
};
+22 -26
View File
@@ -4,8 +4,6 @@
#include "control.h"
#include "loader.h"
#include "memory.h"
#include "objlist_class.h"
#include "timer.h"
#include "TPinballTable.h"
@@ -18,11 +16,11 @@ TLightBargraph::TLightBargraph(TPinballTable* table, int groupIndex) : TLightGro
float* floatArr = loader::query_float_attribute(groupIndex, 0, 904);
if (floatArr)
{
int count = 2 * List->Count();
TimerTimeArray = reinterpret_cast<float*>(memory::allocate(count * sizeof(float)));
auto count = 2 * List.size();
TimerTimeArray = new float[count];
if (TimerTimeArray)
{
for (int i = 0; i < count; ++floatArr)
for (auto i = 0u; i < count; ++floatArr)
TimerTimeArray[i++] = *floatArr;
}
}
@@ -31,17 +29,16 @@ TLightBargraph::TLightBargraph(TPinballTable* table, int groupIndex) : TLightGro
TLightBargraph::~TLightBargraph()
{
if (TimerTimeArray)
memory::free(TimerTimeArray);
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)
{
@@ -49,30 +46,29 @@ int TLightBargraph::Message(int code, float value)
TimerBargraph = 0;
}
auto timeIndex = static_cast<int>(floor(value));
auto maxCount = 2 * List->Count();
auto maxCount = static_cast<int>(List.size()) * 2;
if (timeIndex >= maxCount)
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);
float* timeArray = TimerTimeArray;
if (timeArray)
TimerBargraph = timer::set(timeArray[timeIndex], this, BargraphTimerExpired);
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);
@@ -83,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;
@@ -96,7 +92,7 @@ int TLightBargraph::Message(int code, float value)
++playerPtr;
}
TLightGroup::Message(1024, value);
TLightGroup::Message(MessageCode::Reset, value);
break;
}
default:
@@ -123,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);
}
}
+4 -4
View File
@@ -7,13 +7,13 @@ 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);
float* TimerTimeArray;
int TimerBargraph;
int TimeIndex;
int PlayerTimerIndexBackup[4];
int TimerBargraph{};
int TimeIndex{};
int PlayerTimerIndexBackup[4]{};
};
+153 -187
View File
@@ -4,45 +4,39 @@
#include "control.h"
#include "loader.h"
#include "objlist_class.h"
#include "timer.h"
#include "TLight.h"
#include "TPinballTable.h"
TLightGroup::TLightGroup(TPinballTable* table, int groupIndex) : TPinballComponent(table, groupIndex, false)
{
List = new objlist_class(4, 4);
Timer = 0;
NotifyTimer = 0;
Reset();
TLightGroup::Reset();
if (groupIndex > 0)
{
int count;
Timer1TimeDefault = *loader::query_float_attribute(groupIndex, 0, 903);
__int16* groupIndArr = loader::query_iattribute(groupIndex, 1027, &count);
int16_t* groupIndArr = loader::query_iattribute(groupIndex, 1027, &count);
for (int index = 0; index < count; ++groupIndArr)
{
auto comp = table->find_component(*groupIndArr);
auto comp = dynamic_cast<TLight*>(table->find_component(*groupIndArr));
if (comp)
List->Add(comp);
List.push_back(comp);
++index;
}
}
}
TLightGroup::~TLightGroup()
{
delete List;
}
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;
@@ -59,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++)
{
@@ -69,353 +63,325 @@ int TLightGroup::Message(int code, float value)
playerPtr->Timer1Time = Timer1Time;
}
break;
case 24:
case MessageCode::TLightGroupStepBackward:
{
auto count = List->Count();
auto lastLight = static_cast<TLight*>(List->Get(count - 1));
if (lastLight->FlasherActive || lastLight->FlasherFlag2 || lastLight->FlasherFlag1)
auto lastLight = List.at(count - 1);
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 = static_cast<TLight*>(List->Get(index));
auto lightPrev = static_cast<TLight*>(List->Get(index - 1));
lightCur->Message(lightPrev->BmpIndex1 != 0, 0.0);
auto lightCur = List.at(index);
auto lightPrev = List.at(index - 1);
lightCur->Message(lightPrev->LightOnFlag ? MessageCode::TLightTurnOn : MessageCode::TLightTurnOff, 0.0);
lightCur->MessageField = lightPrev->MessageField;
}
auto firstLight = static_cast<TLight*>(List->Get(0));
firstLight->Message(bmpIndex1 != 0, 0.0);
firstLight->MessageField = lightMessageField;
auto firstLight = List.at(0);
firstLight->Message(lastStatus ? MessageCode::TLightTurnOn : MessageCode::TLightTurnOff, 0.0);
firstLight->MessageField = lastMessage;
reschedule_animation(value);
break;
}
case 25:
case MessageCode::TLightGroupStepForward:
{
auto count = List->Count();
auto lastLight = static_cast<TLight*>(List->Get(count - 1));
if (lastLight->FlasherActive || lastLight->FlasherFlag2 || lastLight->FlasherFlag1)
auto lastLight = List.at(count - 1);
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 = static_cast<TLight*>(List->Get(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 = static_cast<TLight*>(List->Get(index));
auto lightNext = static_cast<TLight*>(List->Get(index + 1));
lightCur->Message(lightNext->BmpIndex1 != 0, 0.0);
auto lightCur = List.at(index);
auto lightNext = List.at(index + 1);
lightCur->Message(lightNext->LightOnFlag ? MessageCode::TLightTurnOn : MessageCode::TLightTurnOff, 0.0);
lightCur->MessageField = lightNext->MessageField;
}
lastLight->Message(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 count = List->Count();
auto lastLight = static_cast<TLight*>(List->Get(count - 1));
auto flasherFlag2 = lastLight->FlasherFlag2;
auto lastLight = List.at(count - 1);
auto lastStatus = lastLight->ToggledOnFlag;
for (auto i = count - 1; i > 0; --i)
{
auto lightCur = static_cast<TLight*>(List->Get(i));
auto lightPrev = static_cast<TLight*>(List->Get(i - 1));
lightCur->Message((lightPrev->FlasherFlag2 != 0) + 8, 0.0);
auto lightCur = List.at(i);
auto lightPrev = List.at(i - 1);
lightCur->Message(lightPrev->ToggledOnFlag ? MessageCode::TLightTurnOnTimed : MessageCode::TLightTurnOffTimed, 0.0);
}
auto firstLight = static_cast<TLight*>(List->Get(0));
firstLight->Message((flasherFlag2 != 0) + 8, 0);
auto firstLight = List.at(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 count = List->Count();
auto firstLight = static_cast<TLight*>(List->Get(0));
auto flasherFlag2 = firstLight->FlasherFlag2;
auto firstLight = List.at(0);
auto firstStatus = firstLight->ToggledOnFlag;
for (auto i = 0; i < count - 1; i++)
{
auto lightCur = static_cast<TLight*>(List->Get(i));
auto lightNext = static_cast<TLight*>(List->Get(i + 1));
lightCur->Message((lightNext->FlasherFlag2 != 0) + 8, 0.0);
auto lightCur = List.at(i);
auto lightNext = List.at(i + 1);
lightCur->Message(lightNext->ToggledOnFlag ? MessageCode::TLightTurnOnTimed : MessageCode::TLightTurnOffTimed, 0.0);
}
auto lastLight = static_cast<TLight*>(List->Get(count - 1));
lastLight->Message((flasherFlag2 != 0) + 8, 0);
auto lastLight = List.at(count - 1);
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;
auto count = List->Count();
for (auto i = 0; i < count - 1; i++)
for (auto light : List)
{
if (rand() % 100 > 70)
{
auto light = static_cast<TLight*>(List->Get(i));
auto randVal = static_cast<float>(rand()) * 0.00003051850947599719f * value * 3.0f + 0.1f;
light->Message(9, randVal);
auto randVal = RandFloat() * value * 3.0f + 0.1f;
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;
auto count = List->Count();
for (auto i = 0; i < count - 1; i++)
for (auto light : List)
{
auto light = static_cast<TLight*>(List->Get(i));
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;
auto countSub1 = List->Count() - 1;
if (countSub1 < 0)
break;
for (auto i = countSub1; i >= 0; i--)
for (auto light : List)
{
if (!static_cast<TLight*>(List->Get(i))->BmpIndex1)
if (!light->LightOnFlag)
++noBmpInd1Count;
}
if (!noBmpInd1Count)
break;
auto randModCount = rand() % noBmpInd1Count;
for (auto i = countSub1; i >= 0; i--)
for (auto it = List.rbegin(); it != List.rend(); ++it)
{
auto light = static_cast<TLight*>(List->Get(i));
if (!light->BmpIndex1 && randModCount-- == 0)
auto light = *it;
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;
auto countSub1 = List->Count() - 1;
if (countSub1 < 0)
break;
for (auto i = countSub1; i >= 0; i--)
for (auto light : List)
{
if (static_cast<TLight*>(List->Get(i))->BmpIndex1)
if (light->LightOnFlag)
++bmpInd1Count;
}
if (!bmpInd1Count)
break;
auto randModCount = rand() % bmpInd1Count;
for (auto i = countSub1; i >= 0; i--)
for (auto it = List.rbegin(); it != List.rend(); ++it)
{
auto light = static_cast<TLight*>(List->Get(i));
if (light->BmpIndex1 && randModCount-- == 0)
auto light = *it;
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;
static_cast<TLight*>(List->Get(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;
static_cast<TLight*>(List->Get(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 >= List->Count() || index < 0)
if (index >= count || index < 0)
break;
auto light = static_cast<TLight*>(List->Get(index));
light->Message(1, 0.0);
if (MessageField2)
auto light = List.at(index);
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 >= List->Count() || index < 0)
if (index >= count || index < 0)
break;
auto light = static_cast<TLight*>(List->Get(index));
light->Message(0, 0.0);
if (MessageField2)
auto light = List.at(index);
light->Message(MessageCode::TLightTurnOff, 0.0);
if (MessageField2 != MessageCode::TLightGroupNull)
start_animation();
break;
}
case 37:
case MessageCode::TLightGroupGetOnCount:
{
auto bmp1Count = 0;
auto countSub1 = List->Count() - 1;
for (auto i = countSub1; i >= 0; i--)
for (auto light : List)
{
if (static_cast<TLight*>(List->Get(i))->BmpIndex1)
if (light->LightOnFlag)
++bmp1Count;
}
return bmp1Count;
}
case 38:
return List->Count();
case 39:
return MessageField2;
case 40:
case MessageCode::TLightGroupGetLightCount:
return count;
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);
static_cast<TLight*>(List->Get(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);
static_cast<TLight*>(List->Get(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.0)
if (value > 0.0f)
NotifyTimer = timer::set(value, this, NotifyTimerExpired);
break;
case 44:
case MessageCode::TLightGroupFlashWhenOn:
{
auto countSub1 = List->Count() - 1;
for (auto index = countSub1; index >= 0; index--)
for (auto it = List.rbegin(); it != List.rend(); ++it)
{
auto light = static_cast<TLight*>(List->Get(index));
if (light->BmpIndex1)
auto light = *it;
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));
if (index >= 0)
if (index >= 0 && index < count)
{
auto count = List->Count();
if (index <= count)
// Turn off lights (index, end]
for (auto i = count - 1; i > index; i--)
{
auto countSub1 = count - 1;
if (countSub1 > index)
{
countSub1 = index;
for (auto i = countSub1, k = countSub1 - index; k != 0; i--, k--)
{
auto light = static_cast<TLight*>(List->Get(i));
light->Message(20, 0.0);
}
}
if (countSub1 >= 0)
{
for (auto i = countSub1; i != 0; i--)
{
auto light = static_cast<TLight*>(List->Get(i));
light->Message(19, 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(MessageCode::TLightResetAndTurnOn, 0.0);
}
}
break;
}
case 46:
case MessageCode::TLightGroupStartFlasher:
{
auto index = next_light_down();
if (index >= 0)
{
static_cast<TLight*>(List->Get(index))->Message(4, 0.0);
List.at(index)->Message(MessageCode::TLightFlasherStart, 0.0);
}
break;
}
default:
for (auto index = List->Count() - 1; index >= 0; index--)
for (auto it = List.rbegin(); it != List.rend(); ++it)
{
static_cast<TLight*>(List->Get(index))->Message(code, value);
(*it)->Message(code, value);
}
break;
}
@@ -430,7 +396,7 @@ void TLightGroup::Reset()
if (NotifyTimer)
timer::kill(NotifyTimer);
NotifyTimer = 0;
MessageField2 = 0;
MessageField2 = MessageCode::TLightGroupNull;
AnimationFlag = 0;
Timer1Time = Timer1TimeDefault;
}
@@ -442,42 +408,42 @@ void TLightGroup::reschedule_animation(float time)
Timer = 0;
if (time == 0)
{
MessageField2 = 0;
MessageField2 = MessageCode::TLightGroupNull;
AnimationFlag = 0;
return;
}
Timer1Time = time > 0.0 ? time : Timer1TimeDefault;
Timer1Time = time > 0.0f ? time : Timer1TimeDefault;
Timer = timer::set(Timer1Time, this, TimerExpired);
}
void TLightGroup::start_animation()
{
for (int index = List->Count() - 1; index >= 0; --index)
for (auto it = List.rbegin(); it != List.rend(); ++it)
{
auto light = static_cast<TLight*>(List->Get(index));
if (light->BmpIndex1)
light->Message(9, 0.0);
auto light = *it;
if (light->LightOnFlag)
light->Message(MessageCode::TLightTurnOnTimed, 0.0);
else
light->Message(8, 0.0);
light->Message(MessageCode::TLightTurnOffTimed, 0.0);
}
}
int TLightGroup::next_light_up()
{
for (int index = 0; index < List->Count(); ++index)
for (auto index = 0u; index < List.size(); ++index)
{
if (!static_cast<TLight*>(List->Get(index))->BmpIndex1)
return index;
if (!List[index]->LightOnFlag)
return static_cast<int>(index);
}
return -1;
}
int TLightGroup::next_light_down()
{
for (int index = List->Count() - 1; index >= 0; --index)
for (auto index = static_cast<int>(List.size()) - 1; index >= 0; --index)
{
if (!static_cast<TLight*>(List->Get(index))->BmpIndex1)
if (List.at(index)->LightOnFlag)
return index;
}
return -1;
@@ -494,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);
}
+11 -9
View File
@@ -1,11 +1,13 @@
#pragma once
#include "TPinballComponent.h"
class objlist_class;
class TLight;
struct TLightGroup_player_backup
{
int MessageField;
int MessageField2;
MessageCode MessageField2;
float Timer1Time;
int Unknown3;
};
@@ -16,8 +18,8 @@ class TLightGroup :
{
public:
TLightGroup(TPinballTable* table, int groupIndex);
~TLightGroup() override;
int Message(int code, float value) override;
~TLightGroup() override = default;
int Message(MessageCode code, float value) override;
virtual void Reset();
void reschedule_animation(float time);
void start_animation();
@@ -27,12 +29,12 @@ public:
static void TimerExpired(int timerId, void* caller);
static void NotifyTimerExpired(int timerId, void* caller);
objlist_class* List;
float Timer1Time;
std::vector<TLight*> List;
float Timer1Time{};
float Timer1TimeDefault;
int MessageField2;
int AnimationFlag;
MessageCode MessageField2{};
int AnimationFlag{};
int NotifyTimer;
int Timer;
TLightGroup_player_backup PlayerData[4];
TLightGroup_player_backup PlayerData[4]{};
};
+10 -14
View File
@@ -8,39 +8,36 @@
#include "TBall.h"
#include "timer.h"
#include "TPinballTable.h"
#include "TZmapList.h"
TLightRollover::TLightRollover(TPinballTable* table, int groupIndex) : TRollover(table, groupIndex, false)
{
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)
{
@@ -54,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, static_cast<gdrv_bitmap8*>(ListBitmap->Get(0)));
SpriteSet(0);
}
}
}
@@ -66,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;
}
+2 -2
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);
+50 -120
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);
}
}
}
+5 -5
View File
@@ -6,12 +6,12 @@ class TLine :
public TEdgeSegment
{
public:
line_type Line;
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;
};
+13 -25
View File
@@ -4,7 +4,6 @@
#include "control.h"
#include "loader.h"
#include "objlist_class.h"
#include "TBall.h"
#include "TLine.h"
#include "TPinballTable.h"
@@ -12,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)
@@ -22,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();
EdgeList->Add(line);
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();
EdgeList->Add(Line);
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)
@@ -67,20 +66,9 @@ void TOneway::Collision(TBall* ball, vector_type* nextPosition, vector_type* dir
Elasticity,
Smoothness,
Threshold,
Boost) > 0.2)
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;
}
+1 -4
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];
};
+84 -40
View File
@@ -1,10 +1,12 @@
#include "pch.h"
#include "TPinballComponent.h"
#include "control.h"
#include "loader.h"
#include "objlist_class.h"
#include "proj.h"
#include "render.h"
#include "TZmapList.h"
#include "TPinballTable.h"
#include "TTableLayer.h"
TPinballComponent::TPinballComponent(TPinballTable* table, int groupIndex, bool loadVisuals)
{
@@ -16,83 +18,91 @@ 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->Add(this);
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 TZmapList(visualCount, 4);
if (ListBitmap)
ListBitmap->Add(visual.Bitmap);
}
if (visual.ZMap)
{
if (!ListZMap)
ListZMap = new TZmapList(visualCount, 4);
if (ListZMap)
ListZMap->Add(visual.ZMap);
ListBitmap = new std::vector<SpriteData>();
ListBitmap->push_back(visual.Bitmap);
}
}
zmap_header_type* zMap = nullptr;
if (ListZMap)
zMap = static_cast<zmap_header_type*>(ListZMap->Get(0));
if (ListBitmap)
{
rectangle_type bmp1Rect{}, tmpRect{};
auto rootBmp = static_cast<gdrv_bitmap8*>(ListBitmap->Get(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 (int index = 1; index < ListBitmap->Count(); index++)
for (auto index = 1u; index < ListBitmap->size(); index++)
{
auto bmp = static_cast<gdrv_bitmap8*>(ListBitmap->Get(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 ? VisualType::Sprite : VisualType::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;
}
TPinballComponent::~TPinballComponent()
{
TPinballTable* table = PinballTable;
if (table)
table->ComponentList->Delete(this);
if (PinballTable)
{
// ComponentList contains one reference to each component.
auto& components = PinballTable->ComponentList;
auto position = std::find(components.begin(), components.end(), this);
if (position != components.end())
components.erase(position);
}
delete ListBitmap;
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;
}
@@ -101,21 +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::operator new(size_t Size)
void TPinballComponent::SpriteSet(int index) const
{
return calloc(1u, Size);
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::operator delete(void* p)
void TPinballComponent::SpriteSetBall(int index, vector2i pos, float depth) const
{
free(p); /*Original does not have this*/
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);
}
}
+136 -18
View File
@@ -1,16 +1,134 @@
#pragma once
struct render_sprite_type_struct;
struct SpriteData;
struct vector2i;
struct zmap_header_type;
struct gdrv_bitmap8;
struct render_sprite;
struct component_control;
struct vector2;
class TPinballTable;
class TZmapList;
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
@@ -18,23 +136,23 @@ 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;
void* operator new(size_t Size);
void operator delete(void* p);
__int8 UnusedBaseFlag;
__int8 ActiveFlag;
char UnusedBaseFlag;
char ActiveFlag;
int MessageField;
char* GroupName;
int Unknown4;
component_control* Control;
int GroupIndex;
render_sprite_type_struct* RenderSprite;
render_sprite* RenderSprite;
TPinballTable* PinballTable;
TZmapList* ListBitmap;
TZmapList* ListZMap;
std::vector<SpriteData>* ListBitmap;
private:
float VisualPosNormX;
float VisualPosNormY;
};
+220 -154
View File
@@ -4,12 +4,9 @@
#include "control.h"
#include "loader.h"
#include "memory.h"
#include "objlist_class.h"
#include "midi.h"
#include "pb.h"
#include "pinball.h"
#include "render.h"
#include "Sound.h"
#include "TBall.h"
#include "TBlocker.h"
#include "TBumper.h"
@@ -40,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,25 +46,22 @@ TPinballTable::TPinballTable(): TPinballComponent(nullptr, -1, false)
{
int shortArrLength;
ComponentList = new objlist_class(32, 16);
BallList = new objlist_class(3, 1);
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->Add(ballObj);
if (ballObj)
ballObj->ActiveFlag = 0;
auto ball = AddBall({0.0f, 0.0f});
ball->Disable();
new TTableLayer(this);
LightGroup = new TLightGroup(this, 0);
@@ -103,7 +98,7 @@ TPinballTable::TPinballTable(): TPinballComponent(nullptr, -1, false)
Plunger = new TPlunger(this, groupIndex);
break;
case 1002:
LightGroup->List->Add(new TLight(this, groupIndex));
LightGroup->List.push_back(new TLight(this, groupIndex));
break;
case 1003:
FlipperL = new TFlipper(this, groupIndex);
@@ -190,87 +185,81 @@ 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);
}
TPinballTable::~TPinballTable()
{
for (int scoreIndex = 0; scoreIndex < 4; scoreIndex++)
for (auto& PlayerScore : PlayerScores)
{
memory::free(PlayerScores[scoreIndex].ScoreStruct);
delete PlayerScore.ScoreStruct;
}
if (ScorePlayerNumber1)
{
memory::free(ScorePlayerNumber1);
delete ScorePlayerNumber1;
ScorePlayerNumber1 = nullptr;
}
if (ScoreBallcount)
{
memory::free(ScoreBallcount);
delete ScoreBallcount;
ScoreBallcount = nullptr;
}
delete LightGroup;
while (ComponentList->Count() > 0)
while (!ComponentList.empty())
{
delete static_cast<TPinballComponent*>(ComponentList->Get(0));
// Component destructor removes it from the list.
delete ComponentList[0];
}
delete BallList;
delete ComponentList;
control::ClearLinks();
pb::InfoTextBox = pb::MissTextBox = nullptr;
}
TPinballComponent* TPinballTable::find_component(LPCSTR componentName)
{
int objCount = ComponentList->Count();
if (objCount > 0)
for (auto component : ComponentList)
{
for (int index = 0; index < objCount; ++index)
const char* groupName = component->GroupName;
if (groupName && !strcmp(groupName, componentName))
{
TPinballComponent* obj = static_cast<TPinballComponent*>(ComponentList->Get(index));
const CHAR* groupName = obj->GroupName;
if (groupName && !lstrcmpA(groupName, componentName))
{
return obj;
}
return component;
}
}
MessageBoxA(nullptr, "Table cant find:", componentName, 0x2000u);
pb::ShowMessageBox(SDL_MESSAGEBOX_WARNING, "Table cant find:", componentName);
return nullptr;
}
TPinballComponent* TPinballTable::find_component(int groupIndex)
{
char Buffer[40];
int objCount = ComponentList->Count();
if (objCount > 0)
char Buffer[40]{};
for (auto component : ComponentList)
{
for (int index = 0; index < objCount; ++index)
{
TPinballComponent* obj = static_cast<TPinballComponent*>(ComponentList->Get(index));
if (obj->GroupIndex == groupIndex)
return obj;
}
if (component->GroupIndex == groupIndex)
return component;
}
_itoa_s(groupIndex, Buffer, 10);
MessageBoxA(nullptr, "Table cant find (lh):", Buffer, 0x2000u);
snprintf(Buffer, sizeof Buffer, "%d", groupIndex);
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;
@@ -299,76 +288,77 @@ 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 (int i = 0; i < ComponentList->Count(); i++)
for (auto component : ComponentList)
{
static_cast<TPinballComponent*>(ComponentList->Get(i))->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);
}
}
void TPinballTable::port_draw()
{
for (int index = ComponentList->Count() - 1; index >= 0; index--)
for (auto component : ComponentList)
{
static_cast<TPinballComponent*>(ComponentList->Get(index))->port_draw();
component->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:
for (int i = 0; i < ComponentList->Count(); i++)
case MessageCode::Pause:
case MessageCode::Resume:
case MessageCode::LooseFocus:
for (auto component : ComponentList)
{
static_cast<TPinballComponent*>(ComponentList->Get(i))->Message(code, value);
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;
@@ -377,36 +367,38 @@ 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);
if (Demo->ActiveFlag)
rc_text = pinball::get_rc_string(30, 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 = 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);
auto ball = static_cast<TBall*>(BallList->Get(0));
Message(MessageCode::Reset, 0.0);
auto ball = BallList[0];
ball->Position.Y = 0.0;
ball->Position.X = 0.0;
ball->Position.Z = -0.8f;
@@ -439,50 +431,55 @@ int TPinballTable::Message(int code, float value)
scorePtr->BallCount = MaxBallCount;
scorePtr->ExtraBalls = ExtraBalls;
scorePtr->BallLockedCounter = BallLockedCounter;
scorePtr->Unknown2 = ScoreSpecial3;
scorePtr->JackpotScore = JackpotScore;
}
BallCount = MaxBallCount;
Sound::Idle();
ChangeBallCount(BallCount);
score::set(ScorePlayerNumber1, CurrentPlayer + 1);
score::update(ScorePlayerNumber1);
Sound::Idle();
for (auto scoreIndex = 4 - PlayerCount; scoreIndex > 0; scoreIndex--)
{
score::set(PlayerScores[scoreIndex].ScoreStruct, -1);
}
Sound::Idle();
ScoreSpecial3Flag = 0;
ScoreSpecial2Flag = 0;
JackpotScoreFlag = false;
BonusScoreFlag = false;
UnknownP71 = 0;
pinball::InfoTextBox->Clear();
Sound::Idle();
pinball::MissTextBox->Clear();
Sound::Idle();
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;
}
@@ -496,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);
@@ -513,60 +510,60 @@ int TPinballTable::Message(int code, float value)
score::set(ScorePlayerNumber1, nextPlayer + 1);
score::update(ScorePlayerNumber1);
for (int i = 0; i < ComponentList->Count(); i++)
for (auto component : ComponentList)
{
static_cast<TPinballComponent*>(ComponentList->Get(i))->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:
for (int i = 0; i < ComponentList->Count(); i++)
case MessageCode::Reset:
for (auto component : ComponentList)
{
static_cast<TPinballComponent*>(ComponentList->Get(i))->Message(1024, 0);
component->Message(MessageCode::Reset, 0);
}
if (ReplayTimer)
timer::kill(ReplayTimer);
@@ -574,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;
@@ -602,27 +599,96 @@ 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);
table->EndGameTimeoutTimer = 0;
pb::end_game();
for (int i = 0; i < table->ComponentList->Count(); i++)
for (auto component : table->ComponentList)
{
static_cast<TPinballComponent*>(table->ComponentList->Get(i))->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)
@@ -635,14 +701,14 @@ 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)
{
for (int i = 0; i < table->BallList->Count(); i++)
for (auto ball : table->BallList)
{
table->Drain->Collision(static_cast<TBall*>(table->BallList->Get(i)), &vec, &vec, 0.0, nullptr);
table->Drain->Collision(ball, &vec, &vec, 0.0, nullptr);
}
}
}
+45 -41
View File
@@ -1,13 +1,14 @@
#pragma once
#include "maths.h"
#include "TPinballComponent.h"
class TBall;
struct scoreStruct;
class TFlipper;
class TPlunger;
class TDrain;
class TDemo;
class objlist_class;
class TLightGroup;
struct score_struct_super
@@ -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);
@@ -45,54 +49,54 @@ public:
scoreStruct* CurScoreStruct;
scoreStruct* ScoreBallcount;
scoreStruct* ScorePlayerNumber1;
int CheatsUsed;
int SoundIndex1;
int SoundIndex2;
int SoundIndex3;
int BallInSink;
int CurScore;
int CurScoreE9;
int CheatsUsed{};
int SoundIndex1{};
int SoundIndex2{};
int SoundIndex3{};
int BallInDrainFlag;
int CurScore{};
int CurScoreE9{};
int LightShowTimer;
int EndGameTimeoutTimer;
int TiltTimeoutTimer;
score_struct_super PlayerScores[4];
score_struct_super PlayerScores[4]{};
int PlayerCount;
int CurrentPlayer;
TPlunger* Plunger;
TDrain* Drain;
TDemo* Demo;
int XOffset;
int YOffset;
int Width;
int Height;
objlist_class* ComponentList;
objlist_class* BallList;
TDemo* Demo{};
int XOffset{};
int YOffset{};
int Width{};
int Height{};
std::vector<TPinballComponent*> ComponentList;
std::vector<TBall*> BallList;
std::vector<TFlipper*> FlipperList;
TLightGroup* LightGroup;
float GravityDirVectMult;
float GravityAngleX;
float GravityAnglY;
float CollisionCompOffset;
float PlungerPositionX;
float PlungerPositionY;
int ScoreMultiplier;
int ScoreAdded;
int ScoreSpecial1;
int ScoreSpecial2;
int ScoreSpecial2Flag;
int ScoreSpecial3;
int ScoreSpecial3Flag;
int UnknownP71;
int BallCount;
float GravityDirVectMult{};
float GravityAngleX{};
float GravityAnglY{};
float CollisionCompOffset{};
vector2 PlungerPosition{};
int ScoreMultiplier{};
int ScoreAdded{};
int ReflexShotScore{};
int BonusScore{};
bool BonusScoreFlag{};
int JackpotScore{};
bool JackpotScoreFlag{};
int UnknownP71{};
int BallCount{};
int MaxBallCount;
int ExtraBalls;
int UnknownP75;
int BallLockedCounter;
int MultiballFlag;
int UnknownP78;
int ReplayActiveFlag;
int ExtraBalls{};
int MultiballCount{};
int BallLockedCounter{};
bool MultiballFlag;
int UnknownP78{};
int ReplayActiveFlag{};
int ReplayTimer;
int UnknownP81;
int UnknownP82;
int UnknownP81{};
int UnknownP82{};
int TiltLockFlag;
private:
+123 -95
View File
@@ -10,7 +10,6 @@
#include "TBall.h"
#include "timer.h"
#include "TPinballTable.h"
#include "TZmapList.h"
TPlunger::TPlunger(TPinballTable* table, int groupIndex) : TCollisionComponent(table, groupIndex, true)
{
@@ -24,146 +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->Count() * 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 = static_cast<float>(rand()) * 0.00003051850947599719f * Boost * 0.1f + Boost;
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 1005:
case 1009:
case 1010:
{
Threshold = 0.0;
if (PullbackTimer_)
timer::kill(PullbackTimer_);
PullbackTimer_ = 0;
if (code == 1005)
loader::play_sound(SoundIndexP2);
auto bmp = static_cast<gdrv_bitmap8*>(ListBitmap->Get(0));
auto zMap = static_cast<zmap_header_type*>(ListZMap->Get(0));
render::sprite_set(
RenderSprite,
bmp,
zMap,
bmp->XPosition - PinballTable->XOffset,
bmp->YPosition - PinballTable->YOffset);
timer::set(Unknown4F, this, PlungerReleasedTimer);
break;
}
case 1015:
{
auto ball = static_cast<TBall*>(PinballTable->ComponentList->Get(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 1024:
case MessageCode::PlungerFeedBall:
{
if (BallFeedTimer_)
timer::kill(BallFeedTimer_);
BallFeedTimer_ = 0;
if (PinballTable->BallCountInRect(PinballTable->PlungerPosition, PinballTable->CollisionCompOffset * 1.2f))
{
timer::set(1.0f, this, BallFeedTimer);
}
else
{
auto ball = PinballTable->AddBall(PinballTable->PlungerPosition);
assertm(ball, "Failure to create ball in plunger");
PinballTable->MultiballCount++;
PinballTable->BallInDrainFlag = 0;
pb::tilt_no_more();
}
break;
}
case MessageCode::PlungerStartFeedTimer:
timer::set(0.95999998f, this, BallFeedTimer);
loader::play_sound(SoundIndexP1, this, "TPlunger2");
break;
case MessageCode::PlungerLaunchBall:
PullbackStartedFlag = true;
Boost = MaxPullback;
Message(MessageCode::PlungerInputReleased, 0.0f);
break;
case MessageCode::PlungerRelaunchBall:
SomeCounter++;
timer::set(value, this, BallFeedTimer);
loader::play_sound(SoundIndexP1, this, "TPlunger2_1");
PullbackStartedFlag = true;
PullbackTimer(0, this);
break;
case MessageCode::PlayerChanged:
PullbackStartedFlag = false;
Boost = 0.0f;
Threshold = 1000000000.0f;
SomeCounter = 0;
timer::kill(BallFeedTimer);
timer::kill(PullbackTimer);
timer::kill(ReleasedTimer);
break;
case MessageCode::SetTiltLock:
SomeCounter = 0;
timer::kill(BallFeedTimer);
break;
case MessageCode::PlungerInputReleased:
case MessageCode::Resume:
case MessageCode::LooseFocus:
if (PullbackStartedFlag && !SomeCounter)
{
PullbackStartedFlag = false;
Threshold = 0.0;
if (PullbackTimer_)
timer::kill(PullbackTimer_);
PullbackTimer_ = 0;
if (code == 1005)
loader::play_sound(SoundIndexP2);
auto bmp = static_cast<gdrv_bitmap8*>(ListBitmap->Get(0));
auto zMap = static_cast<zmap_header_type*>(ListZMap->Get(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->Count() - 1) *
(plunger->Boost / static_cast<float>(plunger->MaxPullback))));
auto bmp = static_cast<gdrv_bitmap8*>(plunger->ListBitmap->Get(index));
auto zMap = static_cast<zmap_header_type*>(plunger->ListZMap->Get(index));
render::sprite_set(
plunger->RenderSprite,
bmp,
zMap,
bmp->XPosition - plunger->PinballTable->XOffset,
bmp->YPosition - plunger->PinballTable->YOffset);
static_cast<float>(plunger->ListBitmap->size() - 1) *
(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;
+8 -6
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{};
};
+32 -44
View File
@@ -7,42 +7,41 @@
#include "render.h"
#include "timer.h"
#include "TPinballTable.h"
#include "TZmapList.h"
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;
}
@@ -52,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);
}
}
@@ -91,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, static_cast<gdrv_bitmap8*>(target->ListBitmap->Get(0)));
target->SpriteSet(0);
if (timerId)
{
if (target->SoftHitSoundId)
loader::play_sound(target->SoftHitSoundId);
loader::play_sound(target->SoftHitSoundId, target, "TPopupTarget2");
}
}
+3 -6
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];
int PlayerMessagefieldBackup[4]{};
};
+74 -112
View File
@@ -4,7 +4,6 @@
#include "control.h"
#include "loader.h"
#include "objlist_class.h"
#include "TBall.h"
#include "TEdgeSegment.h"
#include "TLine.h"
@@ -15,76 +14,59 @@
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;
loader::query_visual(groupIndex, 0, &visual);
CollisionGroup = visual.CollisionGroup;
auto floatArr1 = loader::query_float_attribute(groupIndex, 0, 701);
if (floatArr1)
BallFieldMult = *floatArr1;
else
BallFieldMult = 0.2f;
auto floatArr2 = loader::query_float_attribute(groupIndex, 0, 1305);
if (floatArr2)
RampFlag1 = static_cast<int>(floor(*floatArr2));
else
RampFlag1 = 0;
BallFieldMult = loader::query_float_attribute(groupIndex, 0, 701, 0.2f);
BallZOffsetFlag = static_cast<int>(loader::query_float_attribute(groupIndex, 0, 1305, 0));
auto floatArr3Plane = loader::query_float_attribute(groupIndex, 0, 1300);
RampPlaneCount = static_cast<int>(floor(*floatArr3Plane));
RampPlane = reinterpret_cast<ramp_plane_type*>(floatArr3Plane + 1);
auto 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);
EdgeList->Add(Line1);
if (Line1)
{
Line1->WallValue = nullptr;
Line1->place_in_grid();
}
auto wall0Arr = loader::query_float_attribute(groupIndex, 0, 1303);
auto wall0CollisionGroup = 1 << static_cast<int>(floor(wall0Arr[0]));
auto wall0Pts = reinterpret_cast<wall_point_type*>(wall0Arr + 2);
Line1 = new TLine(this, &ActiveFlag, wall0CollisionGroup, wall0Pts->Pt1, wall0Pts->Pt0);
Line1->WallValue = nullptr;
Line1->place_in_grid(&AABB);
EdgeList.push_back(Line1);
auto 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);
EdgeList->Add(Line2);
if (Line2)
{
Line2->WallValue = nullptr;
Line2->place_in_grid();
}
reinterpret_cast<wall_point_type*>(wall1Arr + 3),
wall1End,
wall1Start);
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];
Line2 = new TLine(this, &ActiveFlag, CollisionGroup, wall1Start, wall1End);
Line2->WallValue = nullptr;
Line2->place_in_grid(&AABB);
EdgeList.push_back(Line2);
auto wall2Arr = loader::query_float_attribute(groupIndex, 0, 1302);
Wall2CollisionGroup = 1 << static_cast<int>(floor(wall2Arr[0]));
auto wall2Enabled = static_cast<int>(floor(wall2Arr[1]));
Wall2BallOffset = wall2Arr[7];
maths::find_closest_edge(
RampPlane,
RampPlaneCount,
reinterpret_cast<wall_point_type*>(floatArr6WallPoint + 3),
&end3,
&start3);
Line3 = new TLine(this, &ActiveFlag, CollisionGroup, start3, end3);
EdgeList->Add(Line3);
if (Line3)
{
Line3->WallValue = nullptr;
Line3->place_in_grid();
}
reinterpret_cast<wall_point_type*>(wall2Arr + 3),
wall2End,
wall2Start);
Line3 = new TLine(this, &ActiveFlag, CollisionGroup, wall2Start, wall2End);
Line3->WallValue = nullptr;
Line3->place_in_grid(&AABB);
EdgeList.push_back(Line3);
auto xMin = 1000000000.0f;
@@ -93,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 = min(min(min(plane->V3.X, plane->V1.X), plane->V2.X), xMin);
yMin = min(min(min(plane->V3.Y, plane->V1.Y), plane->V2.Y), xMin); // Sic
xMax = max(max(max(plane->V3.X, plane->V1.X), plane->V2.X), xMin);
yMax = max(max(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);
EdgeList->Add(line);
if (line)
{
line->WallValue = plane;
line->place_in_grid();
}
line->WallValue = &plane;
line->place_in_grid(&AABB);
EdgeList.push_back(line);
}
}
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;
@@ -152,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)
@@ -180,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;
}
@@ -189,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
@@ -198,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;
}
+8 -11
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;
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;
};
+12 -33
View File
@@ -10,7 +10,6 @@
#include "TEdgeSegment.h"
#include "timer.h"
#include "TPinballTable.h"
#include "TZmapList.h"
TRollover::TRollover(TPinballTable* table, int groupIndex, bool createWall) : TCollisionComponent(
table, groupIndex, createWall)
@@ -20,33 +19,30 @@ TRollover::TRollover(TPinballTable* table, int groupIndex, bool createWall) : TC
TRollover::TRollover(TPinballTable* table, int groupIndex) : TCollisionComponent(table, groupIndex, false)
{
RolloverFlag = 0;
if (ListBitmap)
render::sprite_set_bitmap(RenderSprite, static_cast<gdrv_bitmap8*>(ListBitmap->Get(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, static_cast<gdrv_bitmap8*>(this->ListBitmap->Get(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)
@@ -56,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 = static_cast<gdrv_bitmap8*>(ListBitmap->Get(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{};
+3 -6
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];
char RolloverFlag{};
};
+34 -51
View File
@@ -4,10 +4,10 @@
#include "control.h"
#include "loader.h"
#include "objlist_class.h"
#include "render.h"
#include "TPinballTable.h"
#include "TBall.h"
#include "TDrain.h"
#include "timer.h"
TSink::TSink(TPinballTable* table, int groupIndex) : TCollisionComponent(table, groupIndex, true)
@@ -15,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];
@@ -29,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:
if (value < 0.0)
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:
@@ -65,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 = static_cast<TBall*>(sink->PinballTable->BallList->Get(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");
}
}
Loaded 100 of 187 files, more files were not shown because too many files have changed in this diff. Show more