jkxr/Projects/Android/jni/OpenJK/codeJK2/game/g_main.cpp
Simon 0f7803e934 Squashed commit of the following:
commit 0c85ac4704
Author: Petr Bartos <petr.bartos@plus4u.net>
Date:   Sat Apr 15 18:47:10 2023 +0200

    Menu updates

commit 35b89acc87
Author: Simon <simonbrown77@googlemail.com>
Date:   Sat Apr 15 12:32:35 2023 +0100

    Support for changeable fresh rate

commit 539bfa8956
Author: Petr Bartos <petr.bartos@plus4u.net>
Date:   Sat Apr 15 00:02:59 2023 +0200

    Ensure proper menu defaults on the very first start

commit 216a225aa6
Author: Petr Bartos <petr.bartos@plus4u.net>
Date:   Fri Apr 14 22:41:24 2023 +0200

    Fix storing of force crosshair option

commit 48c2486aad
Merge: a72ca45 fcb9169
Author: Petr Bartos <petr.bartos@plus4u.net>
Date:   Fri Apr 14 22:23:24 2023 +0200

    Merge remote-tracking branch 'origin/main' into contributions

commit a72ca459c5
Author: Petr Bartos <petr.bartos@plus4u.net>
Date:   Fri Apr 14 22:19:47 2023 +0200

    Intern all default values; Remove no longer needed configuration files

commit 32c4e6eac1
Author: Petr Bartos <petr.bartos@plus4u.net>
Date:   Fri Apr 14 20:59:41 2023 +0200

    Ensure weapon adjustment is applied when resetting to defaults

commit fcb9169955
Author: Grant Bagwell <general@grantbagwell.co.uk>
Date:   Fri Apr 14 18:58:32 2023 +0200

    Increased default force motion trigger

commit 2433ae4110
Author: Grant Bagwell <general@grantbagwell.co.uk>
Date:   Fri Apr 14 18:55:56 2023 +0200

    Change Force Crosshair

commit 4ab6a6024a
Author: Grant Bagwell <general@grantbagwell.co.uk>
Date:   Fri Apr 14 17:01:16 2023 +0200

    Fixed player knockback.... hopefully

commit 7deeee7a6f
Author: Grant Bagwell <general@grantbagwell.co.uk>
Date:   Fri Apr 14 17:01:00 2023 +0200

    Increased Force Power Visibility

commit 1fdcb7c48f
Author: Grant Bagwell <general@grantbagwell.co.uk>
Date:   Fri Apr 14 15:16:34 2023 +0200

    Changed Brightness Range and Default

commit fada09a0bb
Author: Petr Bartos <petr.bartos@plus4u.net>
Date:   Thu Apr 13 20:38:13 2023 +0200

    Fix remote turret on-screen help

commit 82af313786
Merge: 7680cb1 d13176c
Author: Petr Bartos <petr.bartos@plus4u.net>
Date:   Thu Apr 13 18:59:46 2023 +0200

    Merge branch 'main' into contributions

commit d13176c9ba
Author: Grant Bagwell <general@grantbagwell.co.uk>
Date:   Thu Apr 13 13:29:49 2023 +0200

    New Quick Save / Load Video

commit 7680cb1288
Author: Petr Bartos <petr.bartos@plus4u.net>
Date:   Wed Apr 12 22:26:44 2023 +0200

    Fix use action when controlling droid; fix droid view help

commit 4d90595139
Author: Grant Bagwell <general@grantbagwell.co.uk>
Date:   Wed Apr 12 18:21:10 2023 +0200

    Fencing Speed on Pico default

commit 37d5ac4184
Author: Grant Bagwell <general@grantbagwell.co.uk>
Date:   Wed Apr 12 18:12:07 2023 +0200

    Changes to TBDC

    Different menu text + added vanilla mode

commit d6e40ead64
Author: Grant Bagwell <general@grantbagwell.co.uk>
Date:   Wed Apr 12 17:44:29 2023 +0200

    Set Fencing Speed back to Default (by Default)

commit 3f7d116e25
Merge: 7424628 c7c66e4
Author: Grant Bagwell <general@grantbagwell.co.uk>
Date:   Wed Apr 12 14:50:26 2023 +0200

    Merge branch 'main' of https://github.com/DrBeef/JKXR

commit 7424628a5d
Author: Grant Bagwell <general@grantbagwell.co.uk>
Date:   Wed Apr 12 14:50:23 2023 +0200

    Updated Weapons

    + Shader fix for FX mod

commit c7c66e46a7
Author: Simon <simonbrown77@googlemail.com>
Date:   Wed Apr 12 08:37:36 2023 +0100

    Revert "Arrange quick save icons horizontally"

    This reverts commit 20f8fff3fe.

    Also make the icons white

commit fa54045159
Author: Simon <simonbrown77@googlemail.com>
Date:   Tue Apr 11 21:02:29 2023 +0100

    Update .gitignore

commit 20f8fff3fe
Author: Simon <simonbrown77@googlemail.com>
Date:   Tue Apr 11 21:00:09 2023 +0100

    Arrange quick save icons horizontally

commit d3dcac8f9d
Author: Simon <simonbrown77@googlemail.com>
Date:   Tue Apr 11 20:58:56 2023 +0100

    Save game crash fix in JKO

commit 6314561b52
Author: Simon <simonbrown77@googlemail.com>
Date:   Tue Apr 11 08:49:42 2023 +0100

    Switch selector on offhand using offhand thumbstick

commit 3b2ffd7289
Author: Simon <simonbrown77@googlemail.com>
Date:   Tue Apr 11 08:39:32 2023 +0100

    Attempted fix for save game crash

    - Not really a proper fix, but might at least workaround it

commit c135eefa05
Author: Simon <simonbrown77@googlemail.com>
Date:   Tue Apr 11 08:38:54 2023 +0100

    Fixed save/load icon image size

commit f51270a294
Merge: a2ff16b e243d0b
Author: Simon <simonbrown77@googlemail.com>
Date:   Mon Apr 10 19:42:11 2023 +0100

    Merge branch 'main' of https://github.com/DrBeef/JKXR

commit a2ff16b576
Author: Simon <simonbrown77@googlemail.com>
Date:   Mon Apr 10 19:42:06 2023 +0100

    Quick Save/Load in Selector

    Haven't been able to test yet!

commit e243d0bdfa
Author: Grant Bagwell <general@grantbagwell.co.uk>
Date:   Mon Apr 10 18:29:18 2023 +0200

    Update README.md

commit b9d0314a6a
Author: Petr Bartos <petr.bartos@plus4u.net>
Date:   Sun Apr 9 19:38:32 2023 +0200

    Make getting into AT-ST easier

commit d121206f83
Author: Petr Bartos <petr.bartos@plus4u.net>
Date:   Sat Apr 8 22:49:28 2023 +0200

    Tune touch gesture distance; Improve use interaction in 3rd person.

commit 260d501776
Author: Petr Bartos <petr.bartos@plus4u.net>
Date:   Sat Apr 8 22:34:59 2023 +0200

    Fix and improve weapon adjustment helper axes

commit a6318867bb
Author: Grant Bagwell <general@grantbagwell.co.uk>
Date:   Thu Apr 6 20:35:02 2023 +0200

    Update README.md

commit 4a1d90e729
Merge: 425db0f 821a56d
Author: Petr Bartos <petr.bartos@plus4u.net>
Date:   Thu Apr 6 19:12:07 2023 +0200

    Merge branch 'main' into contributions

commit 821a56d4b9
Author: Simon Brown <simonbrown77@googlemail.com>
Date:   Thu Apr 6 17:24:20 2023 +0100

    Update README.md

commit 67b7d26de8
Merge: b407932 ccd63d4
Author: Simon Brown <simonbrown77@googlemail.com>
Date:   Thu Apr 6 16:54:01 2023 +0100

    Merge pull request #4 from DrBeef/TBDC

    TBDC Scales in Code

commit b407932bb2
Author: Grant Bagwell <general@grantbagwell.co.uk>
Date:   Thu Apr 6 17:43:26 2023 +0200

    Update README.md

commit 174c3ce96c
Author: Grant Bagwell <general@grantbagwell.co.uk>
Date:   Thu Apr 6 17:33:33 2023 +0200

    Update README.md

commit e9af8e87c2
Author: Grant Bagwell <general@grantbagwell.co.uk>
Date:   Thu Apr 6 17:23:36 2023 +0200

    Update README.md

commit ccd63d4eec
Author: Grant Bagwell <general@grantbagwell.co.uk>
Date:   Thu Apr 6 16:57:36 2023 +0200

    Forced for Extended Menu

commit d6b3e8eb65
Author: Grant Bagwell <general@grantbagwell.co.uk>
Date:   Thu Apr 6 16:55:02 2023 +0200

    TBDC Scales in Code

commit a5f3adf725
Author: Simon Brown <simonbrown77@googlemail.com>
Date:   Thu Apr 6 15:04:07 2023 +0100

    Update README.md

commit 316c2a2904
Merge: 387c34b 90b694f
Author: Simon Brown <simonbrown77@googlemail.com>
Date:   Thu Apr 6 10:02:01 2023 +0100

    Merge pull request #2 from DrBeef/TBDC

    Team Beef Directors Cut

commit 387c34b53e
Author: Simon <simonbrown77@googlemail.com>
Date:   Thu Apr 6 09:59:18 2023 +0100

    Update Beef Crawl

commit a271b61ac7
Author: Simon <simonbrown77@googlemail.com>
Date:   Thu Apr 6 09:58:18 2023 +0100

    Switch to use Bummser's NPC file if TBDC is disabled

commit a2f1644d72
Author: Simon <simonbrown77@googlemail.com>
Date:   Thu Apr 6 08:39:02 2023 +0100

    Update version to 1.0.0 for release

commit f785fdc393
Author: Simon <simonbrown77@googlemail.com>
Date:   Thu Apr 6 08:00:11 2023 +0100

    update github banner

    Update README.md

    Update README.md

    Update README.md

    Update README.md

    Update README.md

    Added Team Beef Patreon banner

commit 90b694ff60
Author: Grant Bagwell <general@grantbagwell.co.uk>
Date:   Wed Apr 5 22:21:41 2023 +0200

    Last Cleanup

commit 70468332d6
Author: Grant Bagwell <general@grantbagwell.co.uk>
Date:   Wed Apr 5 22:19:45 2023 +0200

    TBDC Cleanup

commit 6660e8c984
Author: Grant Bagwell <general@grantbagwell.co.uk>
Date:   Wed Apr 5 21:58:00 2023 +0200

    Credits / NPC Scale

commit e1b03fdcc6
Author: Grant Bagwell <general@grantbagwell.co.uk>
Date:   Wed Apr 5 21:57:07 2023 +0200

    Fixing Quick Save

commit 95950f2390
Author: Grant Bagwell <general@grantbagwell.co.uk>
Date:   Wed Apr 5 20:08:10 2023 +0200

    Updated ratios per difficulty

commit d6235ef199
Author: Grant Bagwell <general@grantbagwell.co.uk>
Date:   Wed Apr 5 00:30:35 2023 +0200

    Darkened Menu GFX

commit a3fdb460c4
Author: Simon <simonbrown77@googlemail.com>
Date:   Tue Apr 4 23:03:31 2023 +0100

    Prevent crash when throwing Saber

    seems it is indexing the g2 model surface that doesn't exist, might be something to do with the new hilt and the old hilt being in the save. Not sure, but this stops it crashing.

commit 4c751fcb59
Author: Grant Bagwell <general@grantbagwell.co.uk>
Date:   Tue Apr 4 23:59:12 2023 +0200

    Y Close Datapad

commit a1216665c8
Merge: 1939a26 d841464
Author: Grant Bagwell <general@grantbagwell.co.uk>
Date:   Tue Apr 4 23:48:20 2023 +0200

    Merge pull request #1 from DrBeef/main

    Main -> TDBC

commit 1939a26542
Author: Grant Bagwell <general@grantbagwell.co.uk>
Date:   Tue Apr 4 23:37:41 2023 +0200

    TBDC

    Laser Saber deflections
    Guns balancing.

commit d841464924
Author: Petr Bartos <petr.bartos@plus4u.net>
Date:   Tue Apr 4 21:59:31 2023 +0200

    Update help resources and menu

commit 425db0f108
Author: Petr Bartos <petr.bartos@plus4u.net>
Date:   Tue Apr 4 21:59:31 2023 +0200

    Update help resources and menu

commit 3c895d27de
Merge: fe9e12a c3819fa
Author: Petr Bartos <petr.bartos@plus4u.net>
Date:   Tue Apr 4 21:05:48 2023 +0200

    Merge branch 'main' into contributions

commit c3819fa407
Author: Grant Bagwell <general@grantbagwell.co.uk>
Date:   Mon Apr 3 00:36:57 2023 +0200

    Demo folder assets fix

commit fe9e12a3f9
Merge: d79f57b 2b255ac
Author: Petr Bartos <petr.bartos@plus4u.net>
Date:   Mon Apr 3 22:55:29 2023 +0200

    Merge branch 'main' into contributions

commit 2b255ac3ea
Author: Simon <simonbrown77@googlemail.com>
Date:   Mon Apr 3 21:26:39 2023 +0100

    Improved version string

commit 62284414d0
Author: Simon <simonbrown77@googlemail.com>
Date:   Mon Apr 3 21:26:20 2023 +0100

    Ensure intro vid can be skipped without having to press trigger first

commit 65674abe2a
Author: Grant Bagwell <general@grantbagwell.co.uk>
Date:   Mon Apr 3 21:36:35 2023 +0200

    Fix Controller Location Buzz for Quick Save / Load

commit ba2e726a79
Author: Grant Bagwell <general@grantbagwell.co.uk>
Date:   Mon Apr 3 00:36:57 2023 +0200

    Demo folder assets fix

commit 7175c872a9
Author: Grant Bagwell <general@grantbagwell.co.uk>
Date:   Mon Apr 3 00:36:46 2023 +0200

    Knockback settings TBDC

commit cc7e73d04a
Author: Simon <simonbrown77@googlemail.com>
Date:   Sun Apr 2 22:59:47 2023 +0100

    Update beef_crawl.tga

commit dd71a05e36
Author: Simon <simonbrown77@googlemail.com>
Date:   Sun Apr 2 22:51:49 2023 +0100

    Put version at the bottom of the main menu

commit cb52d310c1
Author: Grant Bagwell <general@grantbagwell.co.uk>
Date:   Sun Apr 2 23:18:28 2023 +0200

    TBDC Weapons

commit ac71f24e78
Author: Petr Bartos <petr.bartos@plus4u.net>
Date:   Sun Apr 2 18:43:10 2023 +0200

    Updated control scheme picture

commit 3f5c767e8d
Author: Simon <simonbrown77@googlemail.com>
Date:   Sun Apr 2 19:42:42 2023 +0100

    Update AndroidManifest.xml

commit 64ab392fc5
Author: Simon <simonbrown77@googlemail.com>
Date:   Sun Apr 2 19:42:39 2023 +0100

    JKA: Some CVAR change to increase performance

commit d43de53e8a
Author: Simon <simonbrown77@googlemail.com>
Date:   Sun Apr 2 19:41:50 2023 +0100

    Added missed saber blocking check

    might explain why some people find JKA a bit easy if it is auto-blocking in 1st person

commit 8b23e255d8
Author: Simon <simonbrown77@googlemail.com>
Date:   Sun Apr 2 19:41:16 2023 +0100

    Update open xr headers

commit d79f57ba2d
Author: Petr Bartos <petr.bartos@plus4u.net>
Date:   Sun Apr 2 18:43:10 2023 +0200

    Updated control scheme picture

commit e3524e8e48
Author: Petr Bartos <petr.bartos@plus4u.net>
Date:   Sat Apr 1 18:48:42 2023 +0200

    Add use haptic feedback; Fix use in 3rd person mode

commit 05fc4d5ab4
Author: Petr Bartos <petr.bartos@plus4u.net>
Date:   Sat Apr 1 12:07:13 2023 +0200

    Fix menu haptics

commit eec46c183d
Author: Petr Bartos <petr.bartos@plus4u.net>
Date:   Sat Apr 1 18:48:42 2023 +0200

    Add use haptic feedback; Fix use in 3rd person mode

commit fe9891f8db
Author: Petr Bartos <petr.bartos@plus4u.net>
Date:   Sat Apr 1 12:07:13 2023 +0200

    Fix menu haptics

commit 21483d0d5a
Author: Petr Bartos <petr.bartos@plus4u.net>
Date:   Sat Apr 1 11:25:11 2023 +0200

    Make weapon adjustment mode independent for each weapon; Optimize loading of weapon adjustments

commit baec8832ab
Merge: 82706df 52fcc8a
Author: Petr Bartos <petr.bartos@plus4u.net>
Date:   Sat Apr 1 11:15:25 2023 +0200

    Merge branch 'main' into contributions

commit 52fcc8a49e
Author: Simon <simonbrown77@googlemail.com>
Date:   Sat Apr 1 09:47:48 2023 +0100

    Revert "Removed the no-backface-culling for weapons as it is no longer needed"

    This reverts commit b899b99178.

commit 5e66ebf6fc
Author: Petr Bartos <petr.bartos@plus4u.net>
Date:   Fri Mar 31 15:40:54 2023 +0200

    Allow to skip cinematics also by triggers

commit 587277fa7f
Author: Petr Bartos <petr.bartos@plus4u.net>
Date:   Fri Mar 31 14:52:40 2023 +0200

    Fix use/crouch buttons on switched control schemes

commit 82706df34f
Author: Petr Bartos <petr.bartos@plus4u.net>
Date:   Fri Mar 31 15:40:54 2023 +0200

    Allow to skip cinematics also by triggers

commit 1111766032
Author: Petr Bartos <petr.bartos@plus4u.net>
Date:   Fri Mar 31 14:52:40 2023 +0200

    Fix use/crouch buttons on switched control schemes

commit 822d1ddbba
Merge: a1a7d54 5f56cf4
Author: Petr Bartos <petr.bartos@plus4u.net>
Date:   Fri Mar 31 08:43:50 2023 +0200

    Merge branch 'main' into contributions

commit 5f56cf48fc
Author: Simon <simonbrown77@googlemail.com>
Date:   Thu Mar 30 22:16:37 2023 +0100

    Camera shake fix part 2

commit 3dd7833cd0
Author: Simon <simonbrown77@googlemail.com>
Date:   Thu Mar 30 22:06:18 2023 +0100

    Disable camera shake when charging a weapon's alt fire

commit 6202017b6a
Author: Petr Bartos <petr.bartos@plus4u.net>
Date:   Thu Mar 30 21:52:44 2023 +0200

    Fix virtual gun stock

commit 6d49f87150
Author: Petr Bartos <petr.bartos@plus4u.net>
Date:   Thu Mar 30 19:26:36 2023 +0200

    Do not check angle on non-facing triggers

commit 926c64c691
Author: Petr Bartos <petr.bartos@plus4u.net>
Date:   Thu Mar 30 17:54:33 2023 +0200

    Add angle check for triggers touched by hand

commit 78f7d9bcfc
Author: Petr Bartos <petr.bartos@plus4u.net>
Date:   Wed Mar 29 22:59:27 2023 +0200

    JKA mod menu warning

commit 4219f996e9
Author: Petr Bartos <petr.bartos@plus4u.net>
Date:   Wed Mar 29 22:42:11 2023 +0200

    JKO mod menu warning

commit b899b99178
Author: Simon <simonbrown77@googlemail.com>
Date:   Thu Mar 30 21:45:10 2023 +0100

    Removed the no-backface-culling for weapons as it is no longer needed

    used only for hand models now

commit 9877859676
Author: Simon <simonbrown77@googlemail.com>
Date:   Thu Mar 30 21:44:36 2023 +0100

    Update to the VC+Elin weapons pack

    includes a sweet new saber hilt

commit a1a7d541fa
Author: Petr Bartos <petr.bartos@plus4u.net>
Date:   Thu Mar 30 21:52:44 2023 +0200

    Fix virtual gun stock

commit 15d932c75f
Author: Petr Bartos <petr.bartos@plus4u.net>
Date:   Thu Mar 30 19:26:36 2023 +0200

    Do not check angle on non-facing triggers

commit 402277e717
Author: Petr Bartos <petr.bartos@plus4u.net>
Date:   Thu Mar 30 17:54:33 2023 +0200

    Add angle check for triggers touched by hand

commit 9b22378c88
Author: Petr Bartos <petr.bartos@plus4u.net>
Date:   Thu Mar 30 17:52:26 2023 +0200

    Add special delay for re-triggering security cameras

commit c4cc218f8b
Author: Simon <simonbrown77@googlemail.com>
Date:   Wed Mar 29 22:44:50 2023 +0100

    Turn the stun baton into an "always active" weapon

    movement still triggers the sound, but it will always shock an enemy when it makes contact

commit a4e99c20f9
Author: Petr Bartos <petr.bartos@plus4u.net>
Date:   Wed Mar 29 22:59:27 2023 +0200

    JKA mod menu warning

commit 442a1fc8e2
Author: Petr Bartos <petr.bartos@plus4u.net>
Date:   Wed Mar 29 22:42:11 2023 +0200

    JKO mod menu warning

commit 02261c57f0
Merge: 3c64fa7 51c703a
Author: Petr Bartos <petr.bartos@plus4u.net>
Date:   Wed Mar 29 20:51:42 2023 +0200

    Merge branch 'contributions' of github.com:DrBeef/JKXR into contributions

commit 3c64fa7e3e
Author: Simon <simonbrown77@googlemail.com>
Date:   Tue Mar 28 23:19:29 2023 +0100

    Lowered Stun Baton trigger velocity

commit 43192a355d
Author: Simon <simonbrown77@googlemail.com>
Date:   Tue Mar 28 23:03:17 2023 +0100

    Move beef_crawl back into the game specific assets

    as we will be fixing the JKO one on release, but updating JKA

commit 94e82c2da3
Author: Petr Bartos <petr.bartos@plus4u.net>
Date:   Tue Mar 28 23:21:28 2023 +0200

    Fix subtitles rendering; Fix rendering of other centered texts

commit 40128567be
Author: Petr Bartos <petr.bartos@plus4u.net>
Date:   Tue Mar 28 18:02:08 2023 +0200

    Add help to menu

commit 4f1b6b5f07
Author: Petr Bartos <petr.bartos@plus4u.net>
Date:   Tue Mar 28 17:36:13 2023 +0200

    Split vr asset packs to avoid duplicates

commit 51c703a481
Author: Petr Bartos <petr.bartos@plus4u.net>
Date:   Tue Mar 28 23:21:28 2023 +0200

    Fix subtitles rendering; Fix rendering of other centered texts

commit 04a539f890
Author: Petr Bartos <petr.bartos@plus4u.net>
Date:   Tue Mar 28 18:02:08 2023 +0200

    Add help to menu

commit d9126738d3
Author: Petr Bartos <petr.bartos@plus4u.net>
Date:   Tue Mar 28 17:36:13 2023 +0200

    Split vr asset packs to avoid duplicates

commit 62b9a0bfab
Author: Simon <simonbrown77@googlemail.com>
Date:   Tue Mar 28 13:52:45 2023 +0100

    Hide force power aura when item selector is shown

commit 0a9206642e
Author: Simon <simonbrown77@googlemail.com>
Date:   Tue Mar 28 13:52:23 2023 +0100

    Slight adjustment to muzzle position based on whether scoped/two-handed

commit f452b9cf06
Author: Simon <simonbrown77@googlemail.com>
Date:   Tue Mar 28 13:51:49 2023 +0100

    Slight tweak to NPC Combat

commit 20de6ec478
Author: Simon <simonbrown77@googlemail.com>
Date:   Tue Mar 28 13:50:54 2023 +0100

    Added simple readme and license

commit 77672f612b
Author: Simon <simonbrown77@googlemail.com>
Date:   Tue Mar 28 07:20:22 2023 +0100

    Updated website link to be the patreon

commit 2a1cd0e6e7
Author: Simon <simonbrown77@googlemail.com>
Date:   Tue Mar 28 07:20:10 2023 +0100

    Don't send roll to the server, this might be causing gradual roll drift

commit 1b22652e5c
Author: Simon <simonbrown77@googlemail.com>
Date:   Mon Mar 27 21:38:46 2023 +0100

    Fixed aiming of bowcaster and demp alt fire

commit 895b09041f
Author: Simon <simonbrown77@googlemail.com>
Date:   Mon Mar 27 21:38:25 2023 +0100

    JKA - Ensure UseVR Position is only true when in first person

commit 3897531544
Author: Simon <simonbrown77@googlemail.com>
Date:   Mon Mar 27 21:38:17 2023 +0100

    JKO - Ensure UseVR Position is only true when in first person

commit 3b5121e349
Author: Simon <simonbrown77@googlemail.com>
Date:   Mon Mar 27 21:37:49 2023 +0100

    Render Special Effects on hand/weapon when force power is activated for protection etc

commit 50db9039df
Author: Simon <simonbrown77@googlemail.com>
Date:   Sun Mar 26 16:33:52 2023 +0100

    Separate no copy file flag for JK3

commit 1968a7d8ba
Author: Simon <simonbrown77@googlemail.com>
Date:   Sun Mar 26 16:33:22 2023 +0100

    Always use right hand as saber home

commit 74dcd955d2
Author: Simon <simonbrown77@googlemail.com>
Date:   Sun Mar 26 16:32:49 2023 +0100

    Fix crash in JKA

commit 1e4692d04a
Author: Simon <simonbrown77@googlemail.com>
Date:   Sun Mar 26 16:32:16 2023 +0100

    Update z_Crusty_and_Elin_vr_weapons.pk3

commit 134dec8264
Author: Simon <simonbrown77@googlemail.com>
Date:   Sun Mar 26 16:31:53 2023 +0100

    Fix possible crash on JK2

commit f6dc432f6a
Author: Simon <simonbrown77@googlemail.com>
Date:   Sat Mar 25 15:36:01 2023 +0000

    Copy mods to jk2demo if it exists

    to resolve the issue with mods not being picked up from the base folder
2023-04-16 09:38:26 +01:00

1530 lines
37 KiB
C++

/*
===========================================================================
Copyright (C) 1999 - 2005, Id Software, Inc.
Copyright (C) 2000 - 2013, Raven Software, Inc.
Copyright (C) 2001 - 2013, Activision, Inc.
Copyright (C) 2013 - 2015, OpenJK contributors
This file is part of the OpenJK source code.
OpenJK is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License version 2 as
published by the Free Software Foundation.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, see <http://www.gnu.org/licenses/>.
===========================================================================
*/
#include "g_headers.h"
#include "g_local.h"
#include "g_functions.h"
#include "Q3_Interface.h"
#include "g_nav.h"
#include "g_roff.h"
#include "g_navigator.h"
#include "b_local.h"
#include "anims.h"
#include "g_icarus.h"
#include "objectives.h"
#include "../cgame/cg_local.h" // yeah I know this is naughty, but we're shipping soon...
#include "time.h"
#include "../code/qcommon/ojk_saved_game_helper.h"
#include "qcommon/q_version.h"
extern CNavigator navigator;
static int navCalcPathTime = 0;
int eventClearTime = 0;
#define STEPSIZE 18
level_locals_t level;
game_import_t gi;
game_export_t globals;
gentity_t g_entities[MAX_GENTITIES];
unsigned int g_entityInUseBits[MAX_GENTITIES/32];
void G_ASPreCacheFree(void);
void ClearAllInUse(void)
{
memset(g_entityInUseBits,0,sizeof(g_entityInUseBits));
}
void SetInUse(gentity_t *ent)
{
assert(((uintptr_t)ent)>=(uintptr_t)g_entities);
assert(((uintptr_t)ent)<=(uintptr_t)(g_entities+MAX_GENTITIES-1));
unsigned int entNum=ent-g_entities;
g_entityInUseBits[entNum/32]|=((unsigned int)1)<<(entNum&0x1f);
}
void ClearInUse(gentity_t *ent)
{
assert(((uintptr_t)ent)>=(uintptr_t)g_entities);
assert(((uintptr_t)ent)<=(uintptr_t)(g_entities+MAX_GENTITIES-1));
unsigned int entNum=ent-g_entities;
g_entityInUseBits[entNum/32]&=~(((unsigned int)1)<<(entNum&0x1f));
}
qboolean PInUse(unsigned int entNum)
{
assert(entNum>=0);
assert(entNum<MAX_GENTITIES);
return (qboolean)((g_entityInUseBits[entNum / 32] & (1u << (entNum & 0x1f))) != 0);
}
qboolean PInUse2(gentity_t *ent)
{
assert(((uintptr_t)ent)>=(uintptr_t)g_entities);
assert(((uintptr_t)ent)<=(uintptr_t)(g_entities+MAX_GENTITIES-1));
unsigned int entNum=ent-g_entities;
return (qboolean)((g_entityInUseBits[entNum / 32] & (1u << (entNum & 0x1f))) != 0);
}
void WriteInUseBits()
{
ojk::SavedGameHelper saved_game(
::gi.saved_game);
saved_game.write_chunk<uint32_t>(
INT_ID('I', 'N', 'U', 'S'),
::g_entityInUseBits);
}
void ReadInUseBits()
{
ojk::SavedGameHelper saved_game(
::gi.saved_game);
saved_game.read_chunk<uint32_t>(
INT_ID('I', 'N', 'U', 'S'),
::g_entityInUseBits);
// This is only temporary. Once I have converted all the ent->inuse refs,
// it won;t be needed -MW.
for(int i=0;i<MAX_GENTITIES;i++)
{
g_entities[i].inuse=PInUse(i);
}
}
void ValidateInUseBits(void)
{
for(int i=0;i<MAX_GENTITIES;i++)
{
assert(g_entities[i].inuse==PInUse(i));
}
}
class CGEntCleaner
{
public:
~CGEntCleaner()
{
for (int i=0; i<MAX_GENTITIES; i++)
{
gi.G2API_CleanGhoul2Models(g_entities[i].ghoul2);
}
}
};
// CGEntCleaner TheGEntCleaner; I don't think we want this
gentity_t *player;
cvar_t *g_speed;
cvar_t *g_gravity;
cvar_t *g_sex;
cvar_t *g_spskill;
cvar_t *g_cheats;
cvar_t *g_developer;
cvar_t *g_timescale;
cvar_t *g_knockback;
cvar_t *g_dismemberment;
cvar_t *g_dismemberProbabilities;
cvar_t *g_synchSplitAnims;
cvar_t *g_inactivity;
cvar_t *g_debugMove;
cvar_t *g_debugDamage;
cvar_t *g_weaponRespawn;
cvar_t *g_subtitles;
cvar_t *g_ICARUSDebug;
cvar_t *com_buildScript;
cvar_t *g_skippingcin;
cvar_t *g_AIsurrender;
cvar_t *g_numEntities;
cvar_t *g_iscensored;
cvar_t *g_TeamBeefDirectorsCut;
cvar_t *g_saberAutoDeflect1stPerson;
cvar_t *g_saberAutoBlocking;
cvar_t *g_saberRealisticCombat;
cvar_t *g_saberMoveSpeed;
cvar_t *g_saberAnimSpeed;
cvar_t *g_saberAutoAim;
qboolean stop_icarus = qfalse;
extern char *G_GetLocationForEnt( gentity_t *ent );
extern void pitch_roll_for_slope( gentity_t *forwhom, vec3_t pass_slope );
extern void CP_FindCombatPointWaypoints( void );
extern qboolean InFront( vec3_t spot, vec3_t from, vec3_t fromAngles, float threshHold = 0.0f );
void G_RunFrame (int levelTime);
void PrintEntClassname( int gentNum );
void CG_LoadInterface (void);
void ClearNPCGlobals( void );
extern void AI_UpdateGroups( void );
void ClearPlayerAlertEvents( void );
extern void NPC_ShowDebugInfo (void);
extern int killPlayerTimer;
extern cvar_t *d_altRoutes;
/*
static void G_DynamicMusicUpdate( usercmd_t *ucmd )
FIXME: can we merge any of this with the G_ChooseLookEnemy stuff?
*/
static void G_DynamicMusicUpdate( void )
{
gentity_t *ent;
gentity_t *entityList[MAX_GENTITIES];
int numListedEntities;
vec3_t mins, maxs;
int i, e;
int distSq, radius = 2048;
vec3_t center;
int danger = 0;
int battle = 0;
int entTeam;
qboolean LOScalced = qfalse, clearLOS = qfalse;
//FIXME: intro and/or other cues? (one-shot music sounds)
//loops
//player-based
if ( !player )
{//WTF?
player = &g_entities[0];
return;
}
if ( !player->client
|| player->client->pers.teamState.state != TEAM_ACTIVE
|| level.time - player->client->pers.enterTime < 100 )
{//player hasn't spawned yet
return;
}
if ( player->health <= 0 && player->max_health > 0 )
{//defeat music
if ( level.dmState != DM_DEATH )
{
level.dmState = DM_DEATH;
}
}
if ( level.dmState == DM_DEATH )
{
gi.SetConfigstring( CS_DYNAMIC_MUSIC_STATE, "death" );
return;
}
if ( level.dmState == DM_BOSS )
{
gi.SetConfigstring( CS_DYNAMIC_MUSIC_STATE, "boss" );
return;
}
if ( level.dmState == DM_SILENCE )
{
gi.SetConfigstring( CS_DYNAMIC_MUSIC_STATE, "silence" );
return;
}
if ( level.dmBeatTime > level.time )
{//not on a beat
return;
}
level.dmBeatTime = level.time + 1000;//1 second beats
if ( player->health <= 20 )
{
danger = 1;
}
//enemy-based
VectorCopy( player->currentOrigin, center );
for ( i = 0 ; i < 3 ; i++ )
{
mins[i] = center[i] - radius;
maxs[i] = center[i] + radius;
}
numListedEntities = gi.EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );
for ( e = 0 ; e < numListedEntities ; e++ )
{
ent = entityList[ e ];
if ( !ent || !ent->inuse )
{
continue;
}
if ( !ent->client || !ent->NPC )
{
if ( ent->classname && (!Q_stricmp( "PAS", ent->classname )||!Q_stricmp( "misc_turret", ent->classname )) )
{//a turret
entTeam = ent->noDamageTeam;
}
else
{
continue;
}
}
else
{//an NPC
entTeam = ent->client->playerTeam;
}
if ( entTeam == player->client->playerTeam )
{//ally
continue;
}
if ( entTeam == TEAM_NEUTRAL && (!ent->enemy || !ent->enemy->client || ent->enemy->client->playerTeam != player->client->playerTeam) )
{//a droid that is not mad at me or my allies
continue;
}
if ( !gi.inPVS( player->currentOrigin, ent->currentOrigin ) )
{//not potentially visible
continue;
}
if ( ent->client && ent->s.weapon == WP_NONE )
{//they don't have a weapon... FIXME: only do this for droids?
continue;
}
LOScalced = clearLOS = qfalse;
if ( (ent->enemy==player&&(!ent->NPC||ent->NPC->confusionTime<level.time)) || (ent->client&&ent->client->ps.weaponTime) || (!ent->client&&ent->attackDebounceTime>level.time))
{//mad
if ( ent->health > 0 )
{//alive
//FIXME: do I really need this check?
if ( ent->s.weapon == WP_SABER && ent->client && !ent->client->ps.saberActive && ent->enemy != player )
{//a Jedi who has not yet gotten made at me
continue;
}
if ( ent->NPC && ent->NPC->behaviorState == BS_CINEMATIC )
{//they're not actually going to do anything about being mad at me...
continue;
}
//okay, they're in my PVS, but how close are they? Are they actively attacking me?
if ( !ent->client && ent->s.weapon == WP_TURRET && ent->fly_sound_debounce_time && ent->fly_sound_debounce_time - level.time < 10000 )
{//a turret that shot at me less than ten seconds ago
}
else if ( ent->client && ent->client->ps.lastShotTime && ent->client->ps.lastShotTime - level.time < 10000 )
{//an NPC that shot at me less than ten seconds ago
}
else
{//not actively attacking me lately, see how far away they are
distSq = DistanceSquared( ent->currentOrigin, player->currentOrigin );
if ( distSq > 4194304/*2048*2048*/ )
{//> 2048 away
continue;
}
else if ( distSq > 1048576/*1024*1024*/ )
{//> 1024 away
clearLOS = G_ClearLOS( player, player->client->renderInfo.eyePoint, ent );
LOScalced = qtrue;
if ( clearLOS == qfalse )
{//No LOS
continue;
}
}
}
battle++;
}
}
if ( level.dmState == DM_EXPLORE )
{//only do these visibility checks if you're still in exploration mode
if ( !InFront( ent->currentOrigin, player->currentOrigin, player->client->ps.viewangles, 0.0f) )
{//not in front
continue;
}
if ( !LOScalced )
{
clearLOS = G_ClearLOS( player, player->client->renderInfo.eyePoint, ent );
}
if ( !clearLOS )
{//can't see them directly
continue;
}
}
if ( ent->health <= 0 )
{//dead
if ( !ent->client || level.time - ent->s.time > 10000 )
{//corpse has been dead for more than 10 seconds
//FIXME: coming across corpses should cause danger sounds too?
continue;
}
}
//we see enemies and/or corpses
danger++;
}
if ( !battle )
{//no active enemies, but look for missiles, shot impacts, etc...
int alert = G_CheckAlertEvents( player, qtrue, qtrue, 1024, 1024, -1, qfalse, AEL_SUSPICIOUS );
if ( alert != -1 )
{//FIXME: maybe tripwires and other FIXED things need their own sound, some kind of danger/caution theme
if ( G_CheckForDanger( player, alert ) )
{//found danger near by
danger++;
battle = 1;
}
else if ( level.alertEvents[alert].owner && (level.alertEvents[alert].owner == player->enemy || (level.alertEvents[alert].owner->client && level.alertEvents[alert].owner->client->playerTeam == player->client->enemyTeam) ) )
{//NPC on enemy team of player made some noise
switch ( level.alertEvents[alert].level )
{
case AEL_DISCOVERED:
//dangerNear = qtrue;
break;
case AEL_SUSPICIOUS:
//suspicious = qtrue;
break;
case AEL_MINOR:
//distraction = qtrue;
break;
default:
break;
}
}
}
}
if ( battle )
{//battle - this can interrupt level.dmDebounceTime of lower intensity levels
//play battle
if ( level.dmState != DM_ACTION )
{
gi.SetConfigstring( CS_DYNAMIC_MUSIC_STATE, "action" );
}
level.dmState = DM_ACTION;
if ( battle > 5 )
{
//level.dmDebounceTime = level.time + 8000;//don't change again for 5 seconds
}
else
{
//level.dmDebounceTime = level.time + 3000 + 1000*battle;
}
}
else
{
if ( level.dmDebounceTime > level.time )
{//not ready to switch yet
return;
}
else
{//at least 1 second (for beats)
//level.dmDebounceTime = level.time + 1000;//FIXME: define beat time?
}
/*
if ( danger || dangerNear )
{//danger
//stay on whatever we were on, action or exploration
if ( !danger )
{//minimum
danger = 1;
}
if ( danger > 3 )
{
level.dmDebounceTime = level.time + 5000;
}
else
{
level.dmDebounceTime = level.time + 2000 + 1000*danger;
}
}
else
*/
{//still nothing dangerous going on
if ( level.dmState != DM_EXPLORE )
{//just went to explore, hold it for a couple seconds at least
//level.dmDebounceTime = level.time + 2000;
gi.SetConfigstring( CS_DYNAMIC_MUSIC_STATE, "explore" );
}
level.dmState = DM_EXPLORE;
//FIXME: look for interest points and play "mysterious" music instead of exploration?
//FIXME: suspicious and distraction sounds should play some cue or change music in a subtle way?
//play exploration
}
//FIXME: when do we go to silence?
}
}
/*
================
G_FindTeams
Chain together all entities with a matching team field.
Entity teams are used for item groups and multi-entity mover groups.
All but the first will have the FL_TEAMSLAVE flag set and teammaster field set
All but the last will have the teamchain field set to the next one
================
*/
void G_FindTeams( void ) {
gentity_t *e, *e2;
int i, j;
int c, c2;
c = 0;
c2 = 0;
// for ( i=1, e=g_entities,i ; i < globals.num_entities ; i++,e++ )
for ( i=1 ; i < globals.num_entities ; i++ )
{
// if (!e->inuse)
// continue;
if(!PInUse(i))
continue;
e=&g_entities[i];
if (!e->team)
continue;
if (e->flags & FL_TEAMSLAVE)
continue;
e->teammaster = e;
c++;
c2++;
// for (j=i+1, e2=e+1 ; j < globals.num_entities ; j++,e2++)
for (j=i+1; j < globals.num_entities ; j++)
{
// if (!e2->inuse)
// continue;
if(!PInUse(j))
continue;
e2=&g_entities[j];
if (!e2->team)
continue;
if (e2->flags & FL_TEAMSLAVE)
continue;
if (!strcmp(e->team, e2->team))
{
c2++;
e2->teamchain = e->teamchain;
e->teamchain = e2;
e2->teammaster = e;
e2->flags |= FL_TEAMSLAVE;
// make sure that targets only point at the master
if ( e2->targetname ) {
e->targetname = e2->targetname;
e2->targetname = NULL;
}
}
}
}
gi.Printf ("%i teams with %i entities\n", c, c2);
}
/*
============
G_InitCvars
============
*/
void G_InitCvars( void ) {
// don't override the cheat state set by the system
g_cheats = gi.cvar ("helpUsObi", "", 0);
g_developer = gi.cvar ("developer", "", 0);
// noset vars
gi.cvar( "gamename", GAMEVERSION , CVAR_SERVERINFO | CVAR_ROM );
gi.cvar( "gamedate", SOURCE_DATE , CVAR_ROM );
g_skippingcin = gi.cvar ("skippingCinematic", "0", CVAR_ROM);
// latched vars
// change anytime vars
g_speed = gi.cvar( "g_speed", "250", CVAR_CHEAT );
g_gravity = gi.cvar( "g_gravity", "800", CVAR_SAVEGAME|CVAR_ROM );
g_sex = gi.cvar ("sex", "male", CVAR_USERINFO | CVAR_ARCHIVE|CVAR_SAVEGAME|CVAR_NORESTART );
g_spskill = gi.cvar ("g_spskill", "0", CVAR_ARCHIVE | CVAR_SAVEGAME|CVAR_NORESTART);
g_knockback = gi.cvar( "g_knockback", "1000", CVAR_CHEAT );
g_dismemberment = gi.cvar ( "g_dismemberment", "3", CVAR_ARCHIVE );//0 = none, 1 = arms and hands, 2 = legs, 3 = waist and head, 4 = mega dismemberment
g_dismemberProbabilities = gi.cvar ( "g_dismemberProbabilities", "100", CVAR_ARCHIVE );//0 = ignore probabilities, 1 = use probabilities
g_synchSplitAnims = gi.cvar ( "g_synchSplitAnims", "1", 0 );
g_inactivity = gi.cvar ("g_inactivity", "0", 0);
g_debugMove = gi.cvar ("g_debugMove", "0", CVAR_CHEAT );
g_debugDamage = gi.cvar ("g_debugDamage", "0", CVAR_CHEAT );
g_ICARUSDebug = gi.cvar( "g_ICARUSDebug", "0", CVAR_CHEAT );
g_timescale = gi.cvar( "timescale", "1", 0 );
g_subtitles = gi.cvar( "g_subtitles", "0", CVAR_ARCHIVE );
com_buildScript = gi.cvar ("com_buildscript", "0", 0);
g_TeamBeefDirectorsCut = gi.cvar( "g_TeamBeefDirectorsCut", "1", CVAR_ARCHIVE ); // Team Beef Director's Cut tweaks
g_saberAutoDeflect1stPerson = gi.cvar( "g_saberAutoDeflect1stPerson", "0", CVAR_ARCHIVE );//Whether the saber will auto deflect missiles in first person
g_saberAutoBlocking = gi.cvar( "g_saberAutoBlocking", "1", CVAR_ARCHIVE|CVAR_CHEAT );//must press +block button to do any blocking
g_saberRealisticCombat = gi.cvar( "g_saberRealisticCombat", "1", CVAR_ARCHIVE );//makes collision more precise, increases damage
g_saberMoveSpeed = gi.cvar( "g_saberMoveSpeed", "1", CVAR_ARCHIVE|CVAR_CHEAT );//how fast you run while attacking with a saber
g_saberAnimSpeed = gi.cvar( "g_saberAnimSpeed", "1.2", CVAR_ARCHIVE|CVAR_CHEAT );//how fast saber animations run
g_saberAutoAim = gi.cvar( "g_saberAutoAim", "1", CVAR_ARCHIVE|CVAR_CHEAT );//auto-aims at enemies when not moving or when just running forward
g_AIsurrender = gi.cvar( "g_AIsurrender", "0", CVAR_CHEAT );
g_numEntities = gi.cvar( "g_numEntities", "0", CVAR_CHEAT );
gi.cvar( "newTotalSecrets", "0", CVAR_ROM );
gi.cvar_set("newTotalSecrets", "0");//used to carry over the count from SP_target_secret to ClientBegin
g_iscensored = gi.cvar( "ui_iscensored", "0", CVAR_ARCHIVE|CVAR_ROM|CVAR_INIT|CVAR_CHEAT|CVAR_NORESTART );
gi.cvar( "g_JKXRHelpEnabled", "1", CVAR_ARCHIVE ); // Help menu item
}
/*
============
InitGame
============
*/
// I'm just declaring a global here which I need to get at in NAV_GenerateSquadPaths for deciding if pre-calc'd
// data is valid, and this saves changing the proto of G_SpawnEntitiesFromString() to include a checksum param which
// may get changed anyway if a new nav system is ever used. This way saves messing with g_local.h each time -slc
int giMapChecksum;
SavedGameJustLoaded_e g_eSavedGameJustLoaded;
qboolean g_qbLoadTransition = qfalse;
#ifndef FINAL_BUILD
extern int fatalErrors;
#endif
void InitGame( const char *mapname, const char *spawntarget, int checkSum, const char *entities, int levelTime, int randomSeed, int globalTime, SavedGameJustLoaded_e eSavedGameJustLoaded, qboolean qbLoadTransition )
{
giMapChecksum = checkSum;
g_eSavedGameJustLoaded = eSavedGameJustLoaded;
g_qbLoadTransition = qbLoadTransition;
gi.Printf ("------- Game Initialization -------\n");
gi.Printf ("gamename: %s\n", GAMEVERSION);
gi.Printf ("gamedate: %s\n", SOURCE_DATE);
srand( randomSeed );
G_InitCvars();
G_InitMemory();
// set some level globals
memset( &level, 0, sizeof( level ) );
level.time = levelTime;
level.globalTime = globalTime;
Q_strncpyz( level.mapname, mapname, sizeof(level.mapname) );
if ( spawntarget != NULL && spawntarget[0] )
{
Q_strncpyz( level.spawntarget, spawntarget, sizeof(level.spawntarget) );
}
else
{
level.spawntarget[0] = 0;
}
G_InitWorldSession();
// initialize all entities for this game
memset( g_entities, 0, MAX_GENTITIES * sizeof(g_entities[0]) );
globals.gentities = g_entities;
ClearAllInUse();
// initialize all clients for this game
level.maxclients = 1;
level.clients = (gclient_t *) G_Alloc( level.maxclients * sizeof(level.clients[0]) );
memset(level.clients, 0, level.maxclients * sizeof(level.clients[0]));
// set client fields on player
g_entities[0].client = level.clients;
// always leave room for the max number of clients,
// even if they aren't all used, so numbers inside that
// range are NEVER anything but clients
globals.num_entities = MAX_CLIENTS;
//Set up NPC init data
NPC_InitGame();
TIMER_Clear();
//
//ICARUS INIT START
gi.Printf("------ ICARUS Initialization ------\n");
gi.Printf("ICARUS version : %1.2f\n", ICARUS_VERSION);
Interface_Init( &interface_export );
ICARUS_Init();
gi.Printf ("-----------------------------------\n");
//ICARUS INIT END
//
IT_LoadItemParms ();
ClearRegisteredItems();
//FIXME: if this is from a loadgame, it needs to be sure to write this out whenever you do a savegame since the edges and routes are dynamic...
navCalculatePaths = ( navigator.Load( mapname, checkSum ) == qfalse );
// parse the key/value pairs and spawn gentities
G_SpawnEntitiesFromString( entities );
// general initialization
G_FindTeams();
// SaveRegisteredItems();
gi.Printf ("-----------------------------------\n");
if ( navCalculatePaths )
{//not loaded - need to calc paths
navCalcPathTime = level.time + START_TIME_NAV_CALC;//make sure all ents are in and linked
}
else
{//loaded
//FIXME: if this is from a loadgame, it needs to be sure to write this
//out whenever you do a savegame since the edges and routes are dynamic...
//OR: always do a navigator.CheckBlockedEdges() on map startup after nav-load/calc-paths
navigator.pathsCalculated = qtrue;//just to be safe? Does this get saved out? No... assumed
//need to do this, because combatpoint waypoints aren't saved out...?
CP_FindCombatPointWaypoints();
navCalcPathTime = 0;
if ( g_eSavedGameJustLoaded == eNO )
{//clear all the failed edges unless we just loaded the game (which would include failed edges)
navigator.ClearAllFailedEdges();
}
}
player = &g_entities[0];
//Init dynamic music
level.dmState = DM_EXPLORE;
level.dmDebounceTime = 0;
level.dmBeatTime = 0;
level.curAlertID = 1;//0 is default for lastAlertEvent, so...
eventClearTime = 0;
}
/*
=================
ShutdownGame
=================
*/
void ShutdownGame( void ) {
gi.Printf ("==== ShutdownGame ====\n");
gi.Printf ("... ICARUS_Shutdown\n");
ICARUS_Shutdown (); //Shut ICARUS down
gi.Printf ("... Reference Tags Cleared\n");
TAG_Init(); //Clear the reference tags
gi.Printf ("... Navigation Data Cleared\n");
NAV_Shutdown();
// write all the client session data so we can get it back
G_WriteSessionData();
/*
Ghoul2 Insert Start
*/
gi.Printf ("... Ghoul2 Models Shutdown\n");
for (int i=0; i<MAX_GENTITIES; i++)
{
gi.G2API_CleanGhoul2Models(g_entities[i].ghoul2);
}
/*
Ghoul2 Insert End
*/
G_ASPreCacheFree();
}
//===================================================================
static void G_Cvar_Create( const char *var_name, const char *var_value, int flags ) {
gi.cvar( var_name, var_value, flags );
}
/*
=================
GetGameAPI
Returns a pointer to the structure with all entry points
and global variables
=================
*/
extern int PM_ValidateAnimRange( int startFrame, int endFrame, float animSpeed );
extern "C" Q_EXPORT game_export_t* QDECL GetGameAPI( game_import_t *import ) {
gameinfo_import_t gameinfo_import;
gi = *import;
globals.apiversion = GAME_API_VERSION;
globals.Init = InitGame;
globals.Shutdown = ShutdownGame;
globals.WriteLevel = WriteLevel;
globals.ReadLevel = ReadLevel;
globals.GameAllowedToSaveHere = GameAllowedToSaveHere;
globals.ClientThink = ClientThink;
globals.ClientConnect = ClientConnect;
globals.ClientUserinfoChanged = ClientUserinfoChanged;
globals.ClientDisconnect = ClientDisconnect;
globals.ClientBegin = ClientBegin;
globals.ClientCommand = ClientCommand;
globals.RunFrame = G_RunFrame;
globals.ConsoleCommand = ConsoleCommand;
// globals.PrintEntClassname = PrintEntClassname;
// globals.ValidateAnimRange = PM_ValidateAnimRange;
globals.gentitySize = sizeof(gentity_t);
gameinfo_import.FS_FOpenFile = gi.FS_FOpenFile;
gameinfo_import.FS_Read = gi.FS_Read;
gameinfo_import.FS_FCloseFile = gi.FS_FCloseFile;
gameinfo_import.Cvar_Set = gi.cvar_set;
gameinfo_import.Cvar_VariableStringBuffer = gi.Cvar_VariableStringBuffer;
gameinfo_import.Cvar_Create = G_Cvar_Create;
GI_Init( &gameinfo_import );
return &globals;
}
void QDECL G_Error( const char *fmt, ... ) {
va_list argptr;
char text[1024];
va_start (argptr, fmt);
Q_vsnprintf (text, sizeof(text), fmt, argptr);
va_end (argptr);
gi.Error( ERR_DROP, "%s", text);
}
/*
-------------------------
Com_Error
-------------------------
*/
void Com_Error ( int level, const char *error, ... ) {
va_list argptr;
char text[1024];
va_start (argptr, error);
Q_vsnprintf (text, sizeof(text), error, argptr);
va_end (argptr);
gi.Error( level, "%s", text);
}
/*
-------------------------
Com_Printf
-------------------------
*/
void Com_Printf( const char *msg, ... ) {
va_list argptr;
char text[1024];
va_start (argptr, msg);
Q_vsnprintf (text, sizeof(text), msg, argptr);
va_end (argptr);
gi.Printf ("%s", text);
}
/*
========================================================================
MAP CHANGING
========================================================================
*/
/*
========================================================================
FUNCTIONS CALLED EVERY FRAME
========================================================================
*/
static void G_CheckTasksCompleted (gentity_t *ent)
{
if ( Q3_TaskIDPending( ent, TID_CHAN_VOICE ) )
{
if ( !gi.VoiceVolume[ent->s.number] )
{//not playing a voice sound
//return task_complete
Q3_TaskIDComplete( ent, TID_CHAN_VOICE );
}
}
if ( Q3_TaskIDPending( ent, TID_LOCATION ) )
{
char *currentLoc = G_GetLocationForEnt( ent );
if ( currentLoc && currentLoc[0] && Q_stricmp( ent->message, currentLoc ) == 0 )
{//we're in the desired location
Q3_TaskIDComplete( ent, TID_LOCATION );
}
//FIXME: else see if were in other trigger_locations?
}
}
static void G_CheckSpecialPersistentEvents( gentity_t *ent )
{//special-case alerts that would be a pain in the ass to have the ent's think funcs generate
if ( ent == NULL )
{
return;
}
if ( ent->s.eType == ET_MISSILE && ent->s.weapon == WP_THERMAL && ent->s.pos.trType == TR_STATIONARY )
{
if ( eventClearTime == level.time + ALERT_CLEAR_TIME )
{//events were just cleared out so add me again
AddSoundEvent( ent->owner, ent->currentOrigin, ent->splashRadius*2, AEL_DANGER );
AddSightEvent( ent->owner, ent->currentOrigin, ent->splashRadius*2, AEL_DANGER );
}
}
if ( ent->forcePushTime >= level.time )
{//being pushed
if ( eventClearTime == level.time + ALERT_CLEAR_TIME )
{//events were just cleared out so add me again
//NOTE: presumes the player did the pushing, this is not always true, but shouldn't really matter?
if ( ent->item && ent->item->giTag == INV_SECURITY_KEY )
{
AddSightEvent( player, ent->currentOrigin, 128, AEL_DISCOVERED );//security keys are more important
}
else
{
AddSightEvent( player, ent->currentOrigin, 128, AEL_SUSPICIOUS );//hmm... or should this always be discovered?
}
}
}
if ( ent->contents == CONTENTS_LIGHTSABER && !Q_stricmp( "lightsaber", ent->classname ) )
{//lightsaber
if( ent->owner && ent->owner->client )
{
if ( ent->owner->client->ps.saberLength > 0 )
{//it's on
//sight event
AddSightEvent( ent->owner, ent->currentOrigin, 512, AEL_DISCOVERED );
}
}
}
}
/*
=============
G_RunThink
Runs thinking code for this frame if necessary
=============
*/
void G_RunThink (gentity_t *ent)
{
float thinktime;
/*
if ( ent->NPC == NULL )
{
if ( ent->taskManager && !stop_icarus )
{
ent->taskManager->Update( );
}
}
*/
thinktime = ent->nextthink;
if ( thinktime <= 0 )
{
goto runicarus;
}
if ( thinktime > level.time )
{
goto runicarus;
}
ent->nextthink = 0;
if ( ent->e_ThinkFunc == thinkF_NULL ) // actually you don't need this if I check for it in the next function -slc
{
//gi.Error ( "NULL ent->think");
goto runicarus;
}
GEntity_ThinkFunc( ent ); // ent->think (ent);
runicarus:
if ( ent->inuse ) // GEntity_ThinkFunc( ent ) can have freed up this ent if it was a type flier_child (stasis1 crash)
{
if ( ent->NPC == NULL )
{
if ( ent->taskManager && !stop_icarus )
{
ent->taskManager->Update( );
}
}
}
}
/*
-------------------------
G_Animate
-------------------------
*/
void G_Animate ( gentity_t *self )
{
if ( self->s.eFlags & EF_SHADER_ANIM )
{
return;
}
if ( self->s.frame == self->endFrame )
{
if ( self->svFlags & SVF_ANIMATING )
{
// ghoul2 requires some extra checks to see if the animation is done since it doesn't set the current frame directly
if ( self->ghoul2.size() )
{
float frame, junk2;
int junk;
// I guess query ghoul2 to find out what the current frame is and see if we are done.
gi.G2API_GetBoneAnimIndex( &self->ghoul2[self->playerModel], self->rootBone,
(cg.time?cg.time:level.time), &frame, &junk, &junk, &junk, &junk2, NULL );
// It NEVER seems to get to what you'd think the last frame would be, so I'm doing this to try and catch when the animation has stopped
if ( frame + 1 >= self->endFrame )
{
self->svFlags &= ~SVF_ANIMATING;
Q3_TaskIDComplete( self, TID_ANIM_BOTH );
}
}
else // not ghoul2
{
if ( self->loopAnim )
{
self->s.frame = self->startFrame;
}
else
{
self->svFlags &= ~SVF_ANIMATING;
}
//Finished sequence - FIXME: only do this once even on looping anims?
Q3_TaskIDComplete( self, TID_ANIM_BOTH );
}
}
return;
}
self->svFlags |= SVF_ANIMATING;
// With ghoul2, we'll just set the desired start and end frame and let it do it's thing.
if ( self->ghoul2.size())
{
self->s.frame = self->endFrame;
gi.G2API_SetBoneAnimIndex( &self->ghoul2[self->playerModel], self->rootBone,
self->startFrame, self->endFrame, BONE_ANIM_OVERRIDE_FREEZE, 1.0f, cg.time, -1, -1 );
return;
}
if ( self->startFrame < self->endFrame )
{
if ( self->s.frame < self->startFrame || self->s.frame > self->endFrame )
{
self->s.frame = self->startFrame;
}
else
{
self->s.frame++;
}
}
else if ( self->startFrame > self->endFrame )
{
if ( self->s.frame > self->startFrame || self->s.frame < self->endFrame )
{
self->s.frame = self->startFrame;
}
else
{
self->s.frame--;
}
}
else
{
self->s.frame = self->endFrame;
}
}
/*
-------------------------
ResetTeamCounters
-------------------------
*/
/*
void ResetTeamCounters( void )
{
//clear team enemy counters
for ( int team = TEAM_FREE; team < TEAM_NUM_TEAMS; team++ )
{
teamEnemyCount[team] = 0;
teamCount[team] = 0;
}
}
*/
/*
-------------------------
UpdateTeamCounters
-------------------------
*/
/*
void UpdateTeamCounters( gentity_t *ent )
{
if ( !ent->NPC )
{
return;
}
if ( !ent->client )
{
return;
}
if ( ent->health <= 0 )
{
return;
}
if ( (ent->s.eFlags&EF_NODRAW) )
{
return;
}
if ( ent->client->playerTeam == TEAM_FREE )
{
return;
}
//this is an NPC who is alive and visible and is on a specific team
teamCount[ent->client->playerTeam]++;
if ( !ent->enemy )
{
return;
}
//ent has an enemy
if ( !ent->enemy->client )
{//enemy is a normal ent
if ( ent->noDamageTeam == ent->client->playerTeam )
{//it's on my team, don't count it as an enemy
return;
}
}
else
{//enemy is another NPC/player
if ( ent->enemy->client->playerTeam == ent->client->playerTeam)
{//enemy is on the same team, don't count it as an enemy
return;
}
}
//ent's enemy is not on the same team
teamLastEnemyTime[ent->client->playerTeam] = level.time;
teamEnemyCount[ent->client->playerTeam]++;
}
*/
extern void G_SoundOnEnt( gentity_t *ent, soundChannel_t channel, const char *soundPath );
void G_PlayerGuiltDeath( void )
{
if ( player && player->client )
{//simulate death
player->client->ps.stats[STAT_HEALTH] = 0;
//turn off saber
if ( player->client->ps.weapon == WP_SABER && player->client->ps.saberActive )
{
G_SoundOnEnt( player, CHAN_WEAPON, "sound/weapons/saber/saberoff.wav" );
player->client->ps.saberActive = qfalse;
}
//play the "what have I done?!" anim
NPC_SetAnim( player, SETANIM_BOTH, BOTH_FORCEHEAL_START, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
player->client->ps.legsAnimTimer = player->client->ps.torsoAnimTimer = -1;
//look at yourself
player->client->ps.stats[STAT_DEAD_YAW] = player->client->ps.viewangles[YAW]+180;
}
}
extern void NPC_SetAnim(gentity_t *ent,int type,int anim,int priority);
extern void G_MakeTeamVulnerable( void );
int killPlayerTimer = 0;
void G_CheckEndLevelTimers( gentity_t *ent )
{
if ( killPlayerTimer && level.time > killPlayerTimer )
{
killPlayerTimer = 0;
ent->health = 0;
if ( ent->client && ent->client->ps.stats[STAT_HEALTH] > 0 )
{
G_PlayerGuiltDeath();
//cg.missionStatusShow = qtrue;
statusTextIndex = MAX_MISSIONFAILED;
//debounce respawn time
ent->client->respawnTime = level.time + 2000;
//stop all scripts
stop_icarus = qtrue;
//make the team killable
G_MakeTeamVulnerable();
}
}
}
void NAV_CheckCalcPaths( void )
{
if ( navCalcPathTime && navCalcPathTime < level.time )
{//first time we've ever loaded this map...
//clear all the failed edges
navigator.ClearAllFailedEdges();
//Calculate all paths
NAV_CalculatePaths( level.mapname, giMapChecksum );
navigator.CalculatePaths();
#ifndef FINAL_BUILD
if ( fatalErrors )
{
gi.Printf( S_COLOR_RED"Not saving .nav file due to fatal nav errors\n" );
}
else
#endif
if ( navigator.Save( level.mapname, giMapChecksum ) == qfalse )
{
gi.Printf("Unable to save navigations data for map \"%s\" (checksum:%d)\n", level.mapname, giMapChecksum );
}
navCalcPathTime = 0;
}
}
/*
================
G_RunFrame
Advances the non-player objects in the world
================
*/
#if AI_TIMERS
int AITime = 0;
int navTime = 0;
#endif// AI_TIMERS
#ifndef FINAL_BUILD
extern int delayedShutDown;
#endif
void G_RunFrame( int levelTime ) {
int i;
gentity_t *ent;
int ents_inuse=0; // someone's gonna be pissed I put this here...
#if AI_TIMERS
AITime = 0;
navTime = 0;
#endif// AI_TIMERS
level.framenum++;
level.previousTime = level.time;
level.time = levelTime;
//msec = level.time - level.previousTime;
NAV_CheckCalcPaths();
//ResetTeamCounters();
AI_UpdateGroups();
if ( d_altRoutes->integer )
{
navigator.CheckAllFailedEdges();
}
navigator.ClearCheckedNodes();
//remember last waypoint, clear current one
// for ( i = 0, ent = &g_entities[0]; i < globals.num_entities ; i++, ent++)
for ( i = 0; i < globals.num_entities ; i++)
{
// if ( !ent->inuse )
// continue;
if(!PInUse(i))
continue;
ent = &g_entities[i];
if ( ent->waypoint != WAYPOINT_NONE
&& ent->noWaypointTime < level.time )
{
ent->lastWaypoint = ent->waypoint;
ent->waypoint = WAYPOINT_NONE;
}
if ( d_altRoutes->integer )
{
navigator.CheckFailedNodes( ent );
}
}
//Look to clear out old events
ClearPlayerAlertEvents();
//Run the frame for all entities
// for ( i = 0, ent = &g_entities[0]; i < globals.num_entities ; i++, ent++)
for ( i = 0; i < globals.num_entities ; i++)
{
// if ( !ent->inuse )
// continue;
if(!PInUse(i))
continue;
ents_inuse++;
ent = &g_entities[i];
// clear events that are too old
if ( level.time - ent->eventTime > EVENT_VALID_MSEC ) {
if ( ent->s.event ) {
ent->s.event = 0; // &= EV_EVENT_BITS;
if ( ent->client ) {
ent->client->ps.externalEvent = 0;
}
}
if ( ent->freeAfterEvent ) {
// tempEntities or dropped items completely go away after their event
G_FreeEntity( ent );
continue;
} else if ( ent->unlinkAfterEvent ) {
// items that will respawn will hide themselves after their pickup event
ent->unlinkAfterEvent = qfalse;
gi.unlinkentity( ent );
}
}
// temporary entities don't think
if ( ent->freeAfterEvent )
continue;
G_CheckTasksCompleted(ent);
G_Roff( ent );
if( !ent->client )
{
if ( !(ent->svFlags & SVF_SELF_ANIMATING) )
{//FIXME: make sure this is done only for models with frames?
//Or just flag as animating?
if ( ent->s.eFlags & EF_ANIM_ONCE )
{
ent->s.frame++;
}
else if ( !(ent->s.eFlags & EF_ANIM_ALLFAST) )
{
G_Animate( ent );
}
}
}
G_CheckSpecialPersistentEvents( ent );
if ( ent->s.eType == ET_MISSILE )
{
G_RunMissile( ent );
continue;
}
if ( ent->s.eType == ET_ITEM )
{
G_RunItem( ent );
continue;
}
if ( ent->s.eType == ET_MOVER )
{
if ( ent->model && Q_stricmp( "models/test/mikeg/tie_fighter.md3", ent->model ) == 0 )
{
TieFighterThink( ent );
}
G_RunMover( ent );
continue;
}
//The player
if ( i == 0 )
{
// decay batteries if the goggles are active
if ( cg.zoomMode == 1 && ent->client->ps.batteryCharge > 0 )
{
ent->client->ps.batteryCharge--;
}
else if ( cg.zoomMode == 3 && ent->client->ps.batteryCharge > 0 )
{
ent->client->ps.batteryCharge -= 2;
if ( ent->client->ps.batteryCharge < 0 )
{
ent->client->ps.batteryCharge = 0;
}
}
G_CheckEndLevelTimers( ent );
//Recalculate the nearest waypoint for the coming NPC updates
NAV_FindPlayerWaypoint();
if( ent->taskManager && !stop_icarus )
{
ent->taskManager->Update();
}
//dead
if ( ent->health <= 0 )
{
if ( ent->client->ps.groundEntityNum != ENTITYNUM_NONE )
{//on the ground
pitch_roll_for_slope( ent, NULL );
}
}
continue; // players are ucmd driven
}
G_RunThink( ent ); // be aware that ent may be free after returning from here, at least one func frees them
ClearNPCGlobals(); // but these 2 funcs are ok
//UpdateTeamCounters( ent ); // to call anyway on a freed ent.
}
// perform final fixups on the player
ent = &g_entities[0];
if ( ent->inuse )
{
ClientEndFrame( ent );
}
if( g_numEntities->integer )
{
gi.Printf( S_COLOR_WHITE"Number of Entities in use : %d\n", ents_inuse );
}
//DEBUG STUFF
NAV_ShowDebugInfo();
NPC_ShowDebugInfo();
G_DynamicMusicUpdate();
#if AI_TIMERS
AITime -= navTime;
if ( AITime > 20 )
{
gi.Printf( S_COLOR_RED"ERROR: total AI time: %d\n", AITime );
}
else if ( AITime > 10 )
{
gi.Printf( S_COLOR_YELLOW"WARNING: total AI time: %d\n", AITime );
}
else if ( AITime > 2 )
{
gi.Printf( S_COLOR_GREEN"total AI time: %d\n", AITime );
}
if ( navTime > 20 )
{
gi.Printf( S_COLOR_RED"ERROR: total nav time: %d\n", navTime );
}
else if ( navTime > 10 )
{
gi.Printf( S_COLOR_YELLOW"WARNING: total nav time: %d\n", navTime );
}
else if ( navTime > 2 )
{
gi.Printf( S_COLOR_GREEN"total nav time: %d\n", navTime );
}
#endif// AI_TIMERS
#ifndef FINAL_BUILD
if ( delayedShutDown != 0 && delayedShutDown < level.time )
{
G_Error( "Game Errors. Scroll up the console to read them." );
}
#endif
#ifdef _DEBUG
if(!(level.framenum&0xff))
{
ValidateInUseBits();
}
#endif
}
extern qboolean player_locked;
void G_LoadSave_WriteMiscData(void)
{
ojk::SavedGameHelper saved_game(
::gi.saved_game);
saved_game.write_chunk<int32_t>(
INT_ID('L', 'C', 'K', 'D'),
::player_locked);
}
void G_LoadSave_ReadMiscData(void)
{
ojk::SavedGameHelper saved_game(
::gi.saved_game);
saved_game.read_chunk<int32_t>(
INT_ID('L', 'C', 'K', 'D'),
::player_locked);
}
void PrintEntClassname( int gentNum )
{
Com_Printf( "%d: %s in snapshot\n", gentNum, g_entities[gentNum].classname );
}
IGhoul2InfoArray &TheGameGhoul2InfoArray()
{
return gi.TheGhoul2InfoArray();
}