mirror of
https://github.com/DrBeef/JKXR.git
synced 2025-04-29 21:11:33 +00:00
commit0c85ac4704
Author: Petr Bartos <petr.bartos@plus4u.net> Date: Sat Apr 15 18:47:10 2023 +0200 Menu updates commit35b89acc87
Author: Simon <simonbrown77@googlemail.com> Date: Sat Apr 15 12:32:35 2023 +0100 Support for changeable fresh rate commit539bfa8956
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 commit216a225aa6
Author: Petr Bartos <petr.bartos@plus4u.net> Date: Fri Apr 14 22:41:24 2023 +0200 Fix storing of force crosshair option commit48c2486aad
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 commita72ca459c5
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 commit32c4e6eac1
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 commitfcb9169955
Author: Grant Bagwell <general@grantbagwell.co.uk> Date: Fri Apr 14 18:58:32 2023 +0200 Increased default force motion trigger commit2433ae4110
Author: Grant Bagwell <general@grantbagwell.co.uk> Date: Fri Apr 14 18:55:56 2023 +0200 Change Force Crosshair commit4ab6a6024a
Author: Grant Bagwell <general@grantbagwell.co.uk> Date: Fri Apr 14 17:01:16 2023 +0200 Fixed player knockback.... hopefully commit7deeee7a6f
Author: Grant Bagwell <general@grantbagwell.co.uk> Date: Fri Apr 14 17:01:00 2023 +0200 Increased Force Power Visibility commit1fdcb7c48f
Author: Grant Bagwell <general@grantbagwell.co.uk> Date: Fri Apr 14 15:16:34 2023 +0200 Changed Brightness Range and Default commitfada09a0bb
Author: Petr Bartos <petr.bartos@plus4u.net> Date: Thu Apr 13 20:38:13 2023 +0200 Fix remote turret on-screen help commit82af313786
Merge:7680cb1
d13176c
Author: Petr Bartos <petr.bartos@plus4u.net> Date: Thu Apr 13 18:59:46 2023 +0200 Merge branch 'main' into contributions commitd13176c9ba
Author: Grant Bagwell <general@grantbagwell.co.uk> Date: Thu Apr 13 13:29:49 2023 +0200 New Quick Save / Load Video commit7680cb1288
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 commit4d90595139
Author: Grant Bagwell <general@grantbagwell.co.uk> Date: Wed Apr 12 18:21:10 2023 +0200 Fencing Speed on Pico default commit37d5ac4184
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 commitd6e40ead64
Author: Grant Bagwell <general@grantbagwell.co.uk> Date: Wed Apr 12 17:44:29 2023 +0200 Set Fencing Speed back to Default (by Default) commit3f7d116e25
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 commit7424628a5d
Author: Grant Bagwell <general@grantbagwell.co.uk> Date: Wed Apr 12 14:50:23 2023 +0200 Updated Weapons + Shader fix for FX mod commitc7c66e46a7
Author: Simon <simonbrown77@googlemail.com> Date: Wed Apr 12 08:37:36 2023 +0100 Revert "Arrange quick save icons horizontally" This reverts commit20f8fff3fe
. Also make the icons white commitfa54045159
Author: Simon <simonbrown77@googlemail.com> Date: Tue Apr 11 21:02:29 2023 +0100 Update .gitignore commit20f8fff3fe
Author: Simon <simonbrown77@googlemail.com> Date: Tue Apr 11 21:00:09 2023 +0100 Arrange quick save icons horizontally commitd3dcac8f9d
Author: Simon <simonbrown77@googlemail.com> Date: Tue Apr 11 20:58:56 2023 +0100 Save game crash fix in JKO commit6314561b52
Author: Simon <simonbrown77@googlemail.com> Date: Tue Apr 11 08:49:42 2023 +0100 Switch selector on offhand using offhand thumbstick commit3b2ffd7289
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 commitc135eefa05
Author: Simon <simonbrown77@googlemail.com> Date: Tue Apr 11 08:38:54 2023 +0100 Fixed save/load icon image size commitf51270a294
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 commita2ff16b576
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! commite243d0bdfa
Author: Grant Bagwell <general@grantbagwell.co.uk> Date: Mon Apr 10 18:29:18 2023 +0200 Update README.md commitb9d0314a6a
Author: Petr Bartos <petr.bartos@plus4u.net> Date: Sun Apr 9 19:38:32 2023 +0200 Make getting into AT-ST easier commitd121206f83
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. commit260d501776
Author: Petr Bartos <petr.bartos@plus4u.net> Date: Sat Apr 8 22:34:59 2023 +0200 Fix and improve weapon adjustment helper axes commita6318867bb
Author: Grant Bagwell <general@grantbagwell.co.uk> Date: Thu Apr 6 20:35:02 2023 +0200 Update README.md commit4a1d90e729
Merge:425db0f
821a56d
Author: Petr Bartos <petr.bartos@plus4u.net> Date: Thu Apr 6 19:12:07 2023 +0200 Merge branch 'main' into contributions commit821a56d4b9
Author: Simon Brown <simonbrown77@googlemail.com> Date: Thu Apr 6 17:24:20 2023 +0100 Update README.md commit67b7d26de8
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 commitb407932bb2
Author: Grant Bagwell <general@grantbagwell.co.uk> Date: Thu Apr 6 17:43:26 2023 +0200 Update README.md commit174c3ce96c
Author: Grant Bagwell <general@grantbagwell.co.uk> Date: Thu Apr 6 17:33:33 2023 +0200 Update README.md commite9af8e87c2
Author: Grant Bagwell <general@grantbagwell.co.uk> Date: Thu Apr 6 17:23:36 2023 +0200 Update README.md commitccd63d4eec
Author: Grant Bagwell <general@grantbagwell.co.uk> Date: Thu Apr 6 16:57:36 2023 +0200 Forced for Extended Menu commitd6b3e8eb65
Author: Grant Bagwell <general@grantbagwell.co.uk> Date: Thu Apr 6 16:55:02 2023 +0200 TBDC Scales in Code commita5f3adf725
Author: Simon Brown <simonbrown77@googlemail.com> Date: Thu Apr 6 15:04:07 2023 +0100 Update README.md commit316c2a2904
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 commit387c34b53e
Author: Simon <simonbrown77@googlemail.com> Date: Thu Apr 6 09:59:18 2023 +0100 Update Beef Crawl commita271b61ac7
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 commita2f1644d72
Author: Simon <simonbrown77@googlemail.com> Date: Thu Apr 6 08:39:02 2023 +0100 Update version to 1.0.0 for release commitf785fdc393
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 commit90b694ff60
Author: Grant Bagwell <general@grantbagwell.co.uk> Date: Wed Apr 5 22:21:41 2023 +0200 Last Cleanup commit70468332d6
Author: Grant Bagwell <general@grantbagwell.co.uk> Date: Wed Apr 5 22:19:45 2023 +0200 TBDC Cleanup commit6660e8c984
Author: Grant Bagwell <general@grantbagwell.co.uk> Date: Wed Apr 5 21:58:00 2023 +0200 Credits / NPC Scale commite1b03fdcc6
Author: Grant Bagwell <general@grantbagwell.co.uk> Date: Wed Apr 5 21:57:07 2023 +0200 Fixing Quick Save commit95950f2390
Author: Grant Bagwell <general@grantbagwell.co.uk> Date: Wed Apr 5 20:08:10 2023 +0200 Updated ratios per difficulty commitd6235ef199
Author: Grant Bagwell <general@grantbagwell.co.uk> Date: Wed Apr 5 00:30:35 2023 +0200 Darkened Menu GFX commita3fdb460c4
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. commit4c751fcb59
Author: Grant Bagwell <general@grantbagwell.co.uk> Date: Tue Apr 4 23:59:12 2023 +0200 Y Close Datapad commita1216665c8
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 commit1939a26542
Author: Grant Bagwell <general@grantbagwell.co.uk> Date: Tue Apr 4 23:37:41 2023 +0200 TBDC Laser Saber deflections Guns balancing. commitd841464924
Author: Petr Bartos <petr.bartos@plus4u.net> Date: Tue Apr 4 21:59:31 2023 +0200 Update help resources and menu commit425db0f108
Author: Petr Bartos <petr.bartos@plus4u.net> Date: Tue Apr 4 21:59:31 2023 +0200 Update help resources and menu commit3c895d27de
Merge:fe9e12a
c3819fa
Author: Petr Bartos <petr.bartos@plus4u.net> Date: Tue Apr 4 21:05:48 2023 +0200 Merge branch 'main' into contributions commitc3819fa407
Author: Grant Bagwell <general@grantbagwell.co.uk> Date: Mon Apr 3 00:36:57 2023 +0200 Demo folder assets fix commitfe9e12a3f9
Merge:d79f57b
2b255ac
Author: Petr Bartos <petr.bartos@plus4u.net> Date: Mon Apr 3 22:55:29 2023 +0200 Merge branch 'main' into contributions commit2b255ac3ea
Author: Simon <simonbrown77@googlemail.com> Date: Mon Apr 3 21:26:39 2023 +0100 Improved version string commit62284414d0
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 commit65674abe2a
Author: Grant Bagwell <general@grantbagwell.co.uk> Date: Mon Apr 3 21:36:35 2023 +0200 Fix Controller Location Buzz for Quick Save / Load commitba2e726a79
Author: Grant Bagwell <general@grantbagwell.co.uk> Date: Mon Apr 3 00:36:57 2023 +0200 Demo folder assets fix commit7175c872a9
Author: Grant Bagwell <general@grantbagwell.co.uk> Date: Mon Apr 3 00:36:46 2023 +0200 Knockback settings TBDC commitcc7e73d04a
Author: Simon <simonbrown77@googlemail.com> Date: Sun Apr 2 22:59:47 2023 +0100 Update beef_crawl.tga commitdd71a05e36
Author: Simon <simonbrown77@googlemail.com> Date: Sun Apr 2 22:51:49 2023 +0100 Put version at the bottom of the main menu commitcb52d310c1
Author: Grant Bagwell <general@grantbagwell.co.uk> Date: Sun Apr 2 23:18:28 2023 +0200 TBDC Weapons commitac71f24e78
Author: Petr Bartos <petr.bartos@plus4u.net> Date: Sun Apr 2 18:43:10 2023 +0200 Updated control scheme picture commit3f5c767e8d
Author: Simon <simonbrown77@googlemail.com> Date: Sun Apr 2 19:42:42 2023 +0100 Update AndroidManifest.xml commit64ab392fc5
Author: Simon <simonbrown77@googlemail.com> Date: Sun Apr 2 19:42:39 2023 +0100 JKA: Some CVAR change to increase performance commitd43de53e8a
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 commit8b23e255d8
Author: Simon <simonbrown77@googlemail.com> Date: Sun Apr 2 19:41:16 2023 +0100 Update open xr headers commitd79f57ba2d
Author: Petr Bartos <petr.bartos@plus4u.net> Date: Sun Apr 2 18:43:10 2023 +0200 Updated control scheme picture commite3524e8e48
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 commit05fc4d5ab4
Author: Petr Bartos <petr.bartos@plus4u.net> Date: Sat Apr 1 12:07:13 2023 +0200 Fix menu haptics commiteec46c183d
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 commitfe9891f8db
Author: Petr Bartos <petr.bartos@plus4u.net> Date: Sat Apr 1 12:07:13 2023 +0200 Fix menu haptics commit21483d0d5a
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 commitbaec8832ab
Merge:82706df
52fcc8a
Author: Petr Bartos <petr.bartos@plus4u.net> Date: Sat Apr 1 11:15:25 2023 +0200 Merge branch 'main' into contributions commit52fcc8a49e
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 commitb899b99178
. commit5e66ebf6fc
Author: Petr Bartos <petr.bartos@plus4u.net> Date: Fri Mar 31 15:40:54 2023 +0200 Allow to skip cinematics also by triggers commit587277fa7f
Author: Petr Bartos <petr.bartos@plus4u.net> Date: Fri Mar 31 14:52:40 2023 +0200 Fix use/crouch buttons on switched control schemes commit82706df34f
Author: Petr Bartos <petr.bartos@plus4u.net> Date: Fri Mar 31 15:40:54 2023 +0200 Allow to skip cinematics also by triggers commit1111766032
Author: Petr Bartos <petr.bartos@plus4u.net> Date: Fri Mar 31 14:52:40 2023 +0200 Fix use/crouch buttons on switched control schemes commit822d1ddbba
Merge:a1a7d54
5f56cf4
Author: Petr Bartos <petr.bartos@plus4u.net> Date: Fri Mar 31 08:43:50 2023 +0200 Merge branch 'main' into contributions commit5f56cf48fc
Author: Simon <simonbrown77@googlemail.com> Date: Thu Mar 30 22:16:37 2023 +0100 Camera shake fix part 2 commit3dd7833cd0
Author: Simon <simonbrown77@googlemail.com> Date: Thu Mar 30 22:06:18 2023 +0100 Disable camera shake when charging a weapon's alt fire commit6202017b6a
Author: Petr Bartos <petr.bartos@plus4u.net> Date: Thu Mar 30 21:52:44 2023 +0200 Fix virtual gun stock commit6d49f87150
Author: Petr Bartos <petr.bartos@plus4u.net> Date: Thu Mar 30 19:26:36 2023 +0200 Do not check angle on non-facing triggers commit926c64c691
Author: Petr Bartos <petr.bartos@plus4u.net> Date: Thu Mar 30 17:54:33 2023 +0200 Add angle check for triggers touched by hand commit78f7d9bcfc
Author: Petr Bartos <petr.bartos@plus4u.net> Date: Wed Mar 29 22:59:27 2023 +0200 JKA mod menu warning commit4219f996e9
Author: Petr Bartos <petr.bartos@plus4u.net> Date: Wed Mar 29 22:42:11 2023 +0200 JKO mod menu warning commitb899b99178
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 commit9877859676
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 commita1a7d541fa
Author: Petr Bartos <petr.bartos@plus4u.net> Date: Thu Mar 30 21:52:44 2023 +0200 Fix virtual gun stock commit15d932c75f
Author: Petr Bartos <petr.bartos@plus4u.net> Date: Thu Mar 30 19:26:36 2023 +0200 Do not check angle on non-facing triggers commit402277e717
Author: Petr Bartos <petr.bartos@plus4u.net> Date: Thu Mar 30 17:54:33 2023 +0200 Add angle check for triggers touched by hand commit9b22378c88
Author: Petr Bartos <petr.bartos@plus4u.net> Date: Thu Mar 30 17:52:26 2023 +0200 Add special delay for re-triggering security cameras commitc4cc218f8b
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 commita4e99c20f9
Author: Petr Bartos <petr.bartos@plus4u.net> Date: Wed Mar 29 22:59:27 2023 +0200 JKA mod menu warning commit442a1fc8e2
Author: Petr Bartos <petr.bartos@plus4u.net> Date: Wed Mar 29 22:42:11 2023 +0200 JKO mod menu warning commit02261c57f0
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 commit3c64fa7e3e
Author: Simon <simonbrown77@googlemail.com> Date: Tue Mar 28 23:19:29 2023 +0100 Lowered Stun Baton trigger velocity commit43192a355d
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 commit94e82c2da3
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 commit40128567be
Author: Petr Bartos <petr.bartos@plus4u.net> Date: Tue Mar 28 18:02:08 2023 +0200 Add help to menu commit4f1b6b5f07
Author: Petr Bartos <petr.bartos@plus4u.net> Date: Tue Mar 28 17:36:13 2023 +0200 Split vr asset packs to avoid duplicates commit51c703a481
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 commit04a539f890
Author: Petr Bartos <petr.bartos@plus4u.net> Date: Tue Mar 28 18:02:08 2023 +0200 Add help to menu commitd9126738d3
Author: Petr Bartos <petr.bartos@plus4u.net> Date: Tue Mar 28 17:36:13 2023 +0200 Split vr asset packs to avoid duplicates commit62b9a0bfab
Author: Simon <simonbrown77@googlemail.com> Date: Tue Mar 28 13:52:45 2023 +0100 Hide force power aura when item selector is shown commit0a9206642e
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 commitf452b9cf06
Author: Simon <simonbrown77@googlemail.com> Date: Tue Mar 28 13:51:49 2023 +0100 Slight tweak to NPC Combat commit20de6ec478
Author: Simon <simonbrown77@googlemail.com> Date: Tue Mar 28 13:50:54 2023 +0100 Added simple readme and license commit77672f612b
Author: Simon <simonbrown77@googlemail.com> Date: Tue Mar 28 07:20:22 2023 +0100 Updated website link to be the patreon commit2a1cd0e6e7
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 commit1b22652e5c
Author: Simon <simonbrown77@googlemail.com> Date: Mon Mar 27 21:38:46 2023 +0100 Fixed aiming of bowcaster and demp alt fire commit895b09041f
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 commit3897531544
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 commit3b5121e349
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 commit50db9039df
Author: Simon <simonbrown77@googlemail.com> Date: Sun Mar 26 16:33:52 2023 +0100 Separate no copy file flag for JK3 commit1968a7d8ba
Author: Simon <simonbrown77@googlemail.com> Date: Sun Mar 26 16:33:22 2023 +0100 Always use right hand as saber home commit74dcd955d2
Author: Simon <simonbrown77@googlemail.com> Date: Sun Mar 26 16:32:49 2023 +0100 Fix crash in JKA commit1e4692d04a
Author: Simon <simonbrown77@googlemail.com> Date: Sun Mar 26 16:32:16 2023 +0100 Update z_Crusty_and_Elin_vr_weapons.pk3 commit134dec8264
Author: Simon <simonbrown77@googlemail.com> Date: Sun Mar 26 16:31:53 2023 +0100 Fix possible crash on JK2 commitf6dc432f6a
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
3215 lines
88 KiB
C++
3215 lines
88 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 "../cgame/cg_local.h"
|
|
#include "Q3_Interface.h"
|
|
#include "wp_saber.h"
|
|
#include "g_icarus.h"
|
|
#include <JKXR/VrClientInfo.h>
|
|
|
|
#ifdef _DEBUG
|
|
#include <float.h>
|
|
#endif //_DEBUG
|
|
|
|
#define SLOWDOWN_DIST 128.0f
|
|
#define MIN_NPC_SPEED 16.0f
|
|
|
|
extern qboolean Q3_TaskIDPending( gentity_t *ent, taskID_t taskType );
|
|
extern void G_MaintainFormations(gentity_t *self);
|
|
extern void BG_CalculateOffsetAngles( gentity_t *ent, usercmd_t *ucmd );//in bg_pangles.cpp
|
|
extern void BG_CalculateVRWeaponPosition( vec3_t origin, vec3_t angles );//in bg_pmisc.cpp
|
|
extern void BG_CalculateVROffHandPosition( vec3_t origin, vec3_t angles );//in bg_pmisc.cpp
|
|
extern void TryUse( gentity_t *ent );
|
|
extern void TryAltUse( gentity_t *ent );
|
|
extern void ChangeWeapon( gentity_t *ent, int newWeapon );
|
|
extern void ScoreBoardReset(void);
|
|
extern void WP_SaberReflectCheck( gentity_t *self, usercmd_t *ucmd );
|
|
extern void WP_SaberUpdate( gentity_t *self, usercmd_t *ucmd );
|
|
extern void WP_SaberStartMissileBlockCheck( gentity_t *self, usercmd_t *ucmd );
|
|
extern void WP_ForcePowersUpdate( gentity_t *self, usercmd_t *ucmd );
|
|
extern void WP_SaberInitBladeData( gentity_t *ent );
|
|
extern gentity_t *SeekerAcquiresTarget ( gentity_t *ent, vec3_t pos );
|
|
extern void FireSeeker( gentity_t *owner, gentity_t *target, vec3_t origin, vec3_t dir );
|
|
extern qboolean InFront( vec3_t spot, vec3_t from, vec3_t fromAngles, float threshHold = 0.0f );
|
|
extern void NPC_SetLookTarget( gentity_t *self, int entNum, int clearTime );
|
|
extern qboolean PM_AdjustAnglesToGripper( gentity_t *gent, usercmd_t *cmd );
|
|
extern qboolean PM_AdjustAngleForWallRun( gentity_t *ent, usercmd_t *ucmd, qboolean doMove );
|
|
extern qboolean PM_AdjustAnglesForSpinningFlip( gentity_t *ent, usercmd_t *ucmd, qboolean anglesOnly );
|
|
extern qboolean PM_AdjustAnglesForBackAttack( gentity_t *ent, usercmd_t *ucmd );
|
|
extern qboolean PM_AdjustAnglesForSaberLock( gentity_t *ent, usercmd_t *ucmd );
|
|
extern qboolean PM_AdjustAnglesForKnockdown( gentity_t *ent, usercmd_t *ucmd, qboolean angleClampOnly );
|
|
extern qboolean PM_HasAnimation( gentity_t *ent, int animation );
|
|
extern qboolean PM_SpinningSaberAnim( int anim );
|
|
extern qboolean PM_SaberInAttack( int move );
|
|
extern int PM_AnimLength( int index, animNumber_t anim );
|
|
extern qboolean PM_InKnockDown( playerState_t *ps );
|
|
extern qboolean PM_InRoll( playerState_t *ps );
|
|
extern void PM_CmdForRoll( int anim, usercmd_t *pCmd );
|
|
extern qboolean PM_CrouchAnim( int anim );
|
|
extern qboolean PM_FlippingAnim( int anim );
|
|
extern qboolean PM_InCartwheel( int anim );
|
|
extern qboolean PM_StandingAnim( int anim );
|
|
extern qboolean PM_InForceGetUp( playerState_t *ps );
|
|
extern void G_CreateG2AttachedWeaponModel( gentity_t *ent, const char *weaponModel );
|
|
extern qboolean FlyingCreature( gentity_t *ent );
|
|
|
|
extern bool in_camera;
|
|
extern qboolean player_locked;
|
|
extern qboolean stop_icarus;
|
|
extern cvar_t *g_spskill;
|
|
extern cvar_t *g_timescale;
|
|
extern cvar_t *g_saberMoveSpeed;
|
|
extern cvar_t *g_saberAutoBlocking;
|
|
extern vmCvar_t cg_thirdPersonAlpha;
|
|
extern vmCvar_t cg_thirdPersonAutoAlpha;
|
|
|
|
void ClientEndPowerUps( gentity_t *ent );
|
|
|
|
int G_FindLookItem( gentity_t *self )
|
|
{
|
|
//FIXME: should be a more intelligent way of doing this, like auto aim?
|
|
//closest, most in front... did damage to... took damage from? How do we know who the player is focusing on?
|
|
gentity_t *ent;
|
|
int bestEntNum = ENTITYNUM_NONE;
|
|
gentity_t *entityList[MAX_GENTITIES];
|
|
int numListedEntities;
|
|
vec3_t center, mins, maxs, fwdangles, forward, dir;
|
|
int i, e;
|
|
float radius = 256;
|
|
float rating, bestRating = 0.0f;
|
|
|
|
//FIXME: no need to do this in 1st person?
|
|
fwdangles[1] = self->client->ps.viewangles[1];
|
|
AngleVectors( fwdangles, forward, NULL, NULL );
|
|
|
|
VectorCopy( self->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 );
|
|
|
|
if ( !numListedEntities )
|
|
{
|
|
return ENTITYNUM_NONE;
|
|
}
|
|
|
|
for ( e = 0 ; e < numListedEntities ; e++ )
|
|
{
|
|
ent = entityList[ e ];
|
|
|
|
if ( !ent->item )
|
|
{
|
|
continue;
|
|
}
|
|
if ( ent->s.eFlags&EF_NODRAW )
|
|
{
|
|
continue;
|
|
}
|
|
if ( (ent->spawnflags&4/*ITMSF_MONSTER*/) )
|
|
{//NPCs only
|
|
continue;
|
|
}
|
|
if ( !BG_CanItemBeGrabbed( &ent->s, &self->client->ps ) )
|
|
{//don't need it
|
|
continue;
|
|
}
|
|
if ( !gi.inPVS( self->currentOrigin, ent->currentOrigin ) )
|
|
{//not even potentially visible
|
|
continue;
|
|
}
|
|
|
|
if ( !G_ClearLOS( self, self->client->renderInfo.eyePoint, ent ) )
|
|
{//can't see him
|
|
continue;
|
|
}
|
|
//rate him based on how close & how in front he is
|
|
VectorSubtract( ent->currentOrigin, center, dir );
|
|
rating = (1.0f-(VectorNormalize( dir )/radius));
|
|
rating *= DotProduct( forward, dir );
|
|
if ( ent->item->giType == IT_HOLDABLE && ent->item->giTag == INV_SECURITY_KEY )
|
|
{//security keys are of the highest importance
|
|
rating *= 2.0f;
|
|
}
|
|
if ( rating > bestRating )
|
|
{
|
|
bestEntNum = ent->s.number;
|
|
bestRating = rating;
|
|
}
|
|
}
|
|
return bestEntNum;
|
|
}
|
|
|
|
extern void CG_SetClientViewAngles( vec3_t angles, qboolean overrideViewEnt );
|
|
qboolean G_ClearViewEntity( gentity_t *ent )
|
|
{
|
|
if ( !ent->client->ps.viewEntity )
|
|
return qfalse;
|
|
|
|
if ( ent->client->ps.viewEntity > 0 && ent->client->ps.viewEntity < ENTITYNUM_NONE )
|
|
{
|
|
if ( &g_entities[ent->client->ps.viewEntity] )
|
|
{
|
|
g_entities[ent->client->ps.viewEntity].svFlags &= ~SVF_BROADCAST;
|
|
if ( g_entities[ent->client->ps.viewEntity].NPC )
|
|
{
|
|
g_entities[ent->client->ps.viewEntity].NPC->controlledTime = 0;
|
|
SetClientViewAngle( &g_entities[ent->client->ps.viewEntity], g_entities[ent->client->ps.viewEntity].currentAngles );
|
|
G_SetAngles( &g_entities[ent->client->ps.viewEntity], g_entities[ent->client->ps.viewEntity].currentAngles );
|
|
VectorCopy( g_entities[ent->client->ps.viewEntity].currentAngles, g_entities[ent->client->ps.viewEntity].NPC->lastPathAngles );
|
|
g_entities[ent->client->ps.viewEntity].NPC->desiredYaw = g_entities[ent->client->ps.viewEntity].currentAngles[YAW];
|
|
}
|
|
}
|
|
CG_SetClientViewAngles( ent->pos4, qtrue );
|
|
SetClientViewAngle( ent, ent->pos4 );
|
|
}
|
|
ent->client->ps.viewEntity = 0;
|
|
return qtrue;
|
|
}
|
|
|
|
void G_SetViewEntity( gentity_t *self, gentity_t *viewEntity )
|
|
{
|
|
if ( !self || !self->client || !viewEntity )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ( self->s.number == 0 && cg.zoomMode )
|
|
{
|
|
// yeah, it should really toggle them so it plays the end sound....
|
|
cg.zoomMode = 0;
|
|
}
|
|
if ( viewEntity->s.number == self->client->ps.viewEntity )
|
|
{
|
|
return;
|
|
}
|
|
//clear old one first
|
|
G_ClearViewEntity( self );
|
|
//set new one
|
|
self->client->ps.viewEntity = viewEntity->s.number;
|
|
viewEntity->svFlags |= SVF_BROADCAST;
|
|
//remember current angles
|
|
VectorCopy( self->client->ps.viewangles, self->pos4 );
|
|
if ( viewEntity->client )
|
|
{
|
|
//vec3_t clear = {0,0,0};
|
|
CG_SetClientViewAngles( viewEntity->client->ps.viewangles, qtrue );
|
|
VectorCopy(viewEntity->client->ps.viewangles, vr->remote_angles);
|
|
vr->remote_snapTurn = vr->snapTurn;
|
|
vr->take_snap = true;
|
|
vr->remote_cooldown = cg.time + 250;
|
|
|
|
//SetClientViewAngle( self, viewEntity->client->ps.viewangles );
|
|
//SetClientViewAngle( viewEntity, clear );
|
|
/*
|
|
VectorCopy( viewEntity->client->ps.viewangles, self->client->ps.viewangles );
|
|
for ( int i = 0; i < 3; i++ )
|
|
{
|
|
self->client->ps.delta_angles[i] = viewEntity->client->ps.delta_angles[i];
|
|
}
|
|
*/
|
|
}
|
|
if ( !self->s.number )
|
|
{
|
|
if (viewEntity->client && viewEntity->client->NPC_class == CLASS_MOUSE) {
|
|
CG_CenterPrint( "@VR_INGAME_EXIT_DROID_VIEW", SCREEN_HEIGHT * 0.95 );
|
|
} else if (Q_stricmp( "misc_panel_turret", viewEntity->classname ) == 0) {
|
|
CG_CenterPrint( "@VR_INGAME_EXIT_TURRET_VIEW", SCREEN_HEIGHT * 0.95 );
|
|
} else {
|
|
CG_CenterPrint( "@VR_INGAME_EXIT_CAMERA_VIEW", SCREEN_HEIGHT * 0.95 );
|
|
}
|
|
}
|
|
}
|
|
|
|
qboolean G_ControlledByPlayer( gentity_t *self )
|
|
{
|
|
if ( self && self->NPC && self->NPC->controlledTime > level.time )
|
|
{//being controlled
|
|
gentity_t *controller = &g_entities[0];
|
|
if ( controller->client && controller->client->ps.viewEntity == self->s.number )
|
|
{//we're the player's viewEntity
|
|
return qtrue;
|
|
}
|
|
}
|
|
return qfalse;
|
|
}
|
|
|
|
qboolean G_ValidateLookEnemy( gentity_t *self, gentity_t *enemy )
|
|
{
|
|
if ( !enemy )
|
|
{
|
|
return qfalse;
|
|
}
|
|
|
|
if ( enemy->flags&FL_NOTARGET )
|
|
{
|
|
return qfalse;
|
|
}
|
|
|
|
if ( !enemy || enemy == self || !enemy->inuse )
|
|
{
|
|
return qfalse;
|
|
}
|
|
if ( !enemy->client || !enemy->NPC )
|
|
{//not valid
|
|
if ( (enemy->svFlags&SVF_NONNPC_ENEMY)
|
|
&& enemy->s.weapon == WP_TURRET
|
|
&& enemy->noDamageTeam != self->client->playerTeam
|
|
&& enemy->health > 0 )
|
|
{//a turret
|
|
//return qtrue;
|
|
}
|
|
else
|
|
{
|
|
return qfalse;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( enemy->client->playerTeam == self->client->playerTeam )
|
|
{//on same team
|
|
return qfalse;
|
|
}
|
|
|
|
if ( enemy->health <= 0 && ((level.time-enemy->s.time) > 3000||!InFront(enemy->currentOrigin,self->currentOrigin,self->client->ps.viewangles,0.2f)||DistanceHorizontal(enemy->currentOrigin,self->currentOrigin)>16384))//>128
|
|
{//corpse, been dead too long or too out of sight to be interesting
|
|
if ( !enemy->message )
|
|
{
|
|
return qfalse;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( (!InFront( enemy->currentOrigin, self->currentOrigin, self->client->ps.viewangles, 0.0f) || !G_ClearLOS( self, self->client->renderInfo.eyePoint, enemy ) )
|
|
&& ( DistanceHorizontalSquared( enemy->currentOrigin, self->currentOrigin ) > 65536 || fabs(enemy->currentOrigin[2]-self->currentOrigin[2]) > 384 ) )
|
|
{//(not in front or not clear LOS) & greater than 256 away
|
|
return qfalse;
|
|
}
|
|
|
|
//LOS?
|
|
|
|
return qtrue;
|
|
}
|
|
|
|
void G_ChooseLookEnemy( gentity_t *self, usercmd_t *ucmd )
|
|
{
|
|
//FIXME: should be a more intelligent way of doing this, like auto aim?
|
|
//closest, most in front... did damage to... took damage from? How do we know who the player is focusing on?
|
|
gentity_t *ent, *bestEnt = NULL;
|
|
gentity_t *entityList[MAX_GENTITIES];
|
|
int numListedEntities;
|
|
vec3_t center, mins, maxs, fwdangles, forward, dir;
|
|
int i, e;
|
|
float radius = 256;
|
|
float rating, bestRating = 0.0f;
|
|
|
|
//FIXME: no need to do this in 1st person?
|
|
fwdangles[0] = 0; //Must initialize data!
|
|
fwdangles[1] = self->client->ps.viewangles[1];
|
|
fwdangles[2] = 0;
|
|
AngleVectors( fwdangles, forward, NULL, NULL );
|
|
|
|
VectorCopy( self->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 );
|
|
|
|
if ( !numListedEntities )
|
|
{//should we clear the enemy?
|
|
return;
|
|
}
|
|
|
|
for ( e = 0 ; e < numListedEntities ; e++ )
|
|
{
|
|
ent = entityList[ e ];
|
|
|
|
if ( !gi.inPVS( self->currentOrigin, ent->currentOrigin ) )
|
|
{//not even potentially visible
|
|
continue;
|
|
}
|
|
|
|
if ( !G_ValidateLookEnemy( self, ent ) )
|
|
{//doesn't meet criteria of valid look enemy (don't check current since we would have done that before this func's call
|
|
continue;
|
|
}
|
|
|
|
if ( !G_ClearLOS( self, self->client->renderInfo.eyePoint, ent ) )
|
|
{//can't see him
|
|
continue;
|
|
}
|
|
//rate him based on how close & how in front he is
|
|
VectorSubtract( ent->currentOrigin, center, dir );
|
|
rating = (1.0f-(VectorNormalize( dir )/radius));
|
|
rating *= DotProduct( forward, dir )+1.0f;
|
|
if ( ent->health <= 0 )
|
|
{
|
|
if ( (ucmd->buttons&BUTTON_ATTACK) || (ucmd->buttons&BUTTON_ALT_ATTACK) )
|
|
{//if attacking, don't consider dead enemies
|
|
continue;
|
|
}
|
|
if ( ent->message )
|
|
{//keyholder
|
|
rating *= 0.5f;
|
|
}
|
|
else
|
|
{
|
|
rating *= 0.1f;
|
|
}
|
|
}
|
|
if ( ent->s.weapon == WP_SABER )
|
|
{
|
|
rating *= 2.0f;
|
|
}
|
|
if ( ent->enemy == self )
|
|
{//he's mad at me, he's more important
|
|
rating *= 2.0f;
|
|
}
|
|
else if ( ent->NPC && ent->NPC->blockedSpeechDebounceTime > level.time - 6000 )
|
|
{//he's detected me, he's more important
|
|
if ( ent->NPC->blockedSpeechDebounceTime > level.time + 4000 )
|
|
{
|
|
rating *= 1.5f;
|
|
}
|
|
else
|
|
{//from 1.0f to 1.5f
|
|
rating += rating * ((float)(ent->NPC->blockedSpeechDebounceTime-level.time) + 6000.0f)/20000.0f;
|
|
}
|
|
}
|
|
/*
|
|
if ( g_crosshairEntNum == ent && !self->enemy )
|
|
{//we don't have an enemy and we are aiming at this guy
|
|
rading *= 2.0f;
|
|
}
|
|
*/
|
|
if ( rating > bestRating )
|
|
{
|
|
bestEnt = ent;
|
|
bestRating = rating;
|
|
}
|
|
}
|
|
if ( bestEnt )
|
|
{
|
|
self->enemy = bestEnt;
|
|
}
|
|
}
|
|
|
|
/*
|
|
===============
|
|
G_DamageFeedback
|
|
|
|
Called just before a snapshot is sent to the given player.
|
|
Totals up all damage and generates both the player_state_t
|
|
damage values to that client for pain blends and kicks, and
|
|
global pain sound events for all clients.
|
|
===============
|
|
*/
|
|
void P_DamageFeedback( gentity_t *player ) {
|
|
gclient_t *client;
|
|
float count;
|
|
vec3_t angles;
|
|
|
|
client = player->client;
|
|
if ( client->ps.pm_type == PM_DEAD ) {
|
|
return;
|
|
}
|
|
|
|
// total points of damage shot at the player this frame
|
|
count = client->damage_blood + client->damage_armor;
|
|
if ( count == 0 ) {
|
|
return; // didn't take any damage
|
|
}
|
|
|
|
if ( count > 255 ) {
|
|
count = 255;
|
|
}
|
|
|
|
// send the information to the client
|
|
|
|
// world damage (falling, slime, etc) uses a special code
|
|
// to make the blend blob centered instead of positional
|
|
if ( client->damage_fromWorld )
|
|
{
|
|
client->ps.damagePitch = 255;
|
|
client->ps.damageYaw = 255;
|
|
|
|
client->damage_fromWorld = qfalse;
|
|
}
|
|
else
|
|
{
|
|
vectoangles( client->damage_from, angles );
|
|
client->ps.damagePitch = angles[PITCH]/360.0 * 256;
|
|
client->ps.damageYaw = angles[YAW]/360.0 * 256;
|
|
}
|
|
|
|
client->ps.damageCount = count;
|
|
|
|
//
|
|
// clear totals
|
|
//
|
|
client->damage_blood = 0;
|
|
client->damage_armor = 0;
|
|
client->damage_knockback = 0;
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
=============
|
|
P_WorldEffects
|
|
|
|
Check for lava / slime contents and drowning
|
|
=============
|
|
*/
|
|
void P_WorldEffects( gentity_t *ent ) {
|
|
int mouthContents = 0;
|
|
|
|
if ( ent->client->noclip )
|
|
{
|
|
ent->client->airOutTime = level.time + 12000; // don't need air
|
|
return;
|
|
}
|
|
|
|
if ( !in_camera )
|
|
{
|
|
mouthContents = gi.pointcontents( ent->client->renderInfo.eyePoint, ent->s.number );
|
|
}
|
|
//
|
|
// check for drowning
|
|
//
|
|
if ( (mouthContents&(CONTENTS_WATER|CONTENTS_SLIME)) )
|
|
{
|
|
if ( ent->client->NPC_class == CLASS_SWAMPTROOPER )
|
|
{//they have air tanks
|
|
ent->client->airOutTime = level.time + 12000; // don't need air
|
|
ent->damage = 2;
|
|
}
|
|
else if ( ent->client->airOutTime < level.time)
|
|
{// if out of air, start drowning
|
|
// drown!
|
|
ent->client->airOutTime += 1000;
|
|
if ( ent->health > 0 ) {
|
|
// take more damage the longer underwater
|
|
ent->damage += 2;
|
|
if (ent->damage > 15)
|
|
ent->damage = 15;
|
|
|
|
// play a gurp sound instead of a normal pain sound
|
|
if (ent->health <= ent->damage)
|
|
{
|
|
G_AddEvent( ent, EV_WATER_DROWN, 0 );
|
|
}
|
|
else
|
|
{
|
|
G_AddEvent( ent, Q_irand(EV_WATER_GURP1, EV_WATER_GURP2), 0 );
|
|
}
|
|
|
|
// don't play a normal pain sound
|
|
ent->painDebounceTime = level.time + 200;
|
|
|
|
G_Damage (ent, NULL, NULL, NULL, NULL,
|
|
ent->damage, DAMAGE_NO_ARMOR, MOD_WATER);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ent->client->airOutTime = level.time + 12000;
|
|
ent->damage = 2;
|
|
}
|
|
|
|
//
|
|
// check for sizzle damage (move to pmove?)
|
|
//
|
|
if (ent->waterlevel &&
|
|
(ent->watertype&(CONTENTS_LAVA|CONTENTS_SLIME)) ) {
|
|
if (ent->health > 0
|
|
&& ent->painDebounceTime < level.time ) {
|
|
|
|
if (ent->watertype & CONTENTS_LAVA) {
|
|
G_Damage (ent, NULL, NULL, NULL, NULL,
|
|
15*ent->waterlevel, 0, MOD_LAVA);
|
|
}
|
|
|
|
if (ent->watertype & CONTENTS_SLIME) {
|
|
G_Damage (ent, NULL, NULL, NULL, NULL,
|
|
1, 0, MOD_SLIME);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Poisoned?
|
|
if ((ent->client->poisonDamage) && (ent->client->poisonTime < level.time))
|
|
{
|
|
ent->client->poisonDamage -= 2;
|
|
ent->client->poisonTime = level.time + 1000;
|
|
G_Damage( ent, NULL, NULL, 0, 0, 2, DAMAGE_NO_KNOCKBACK|DAMAGE_NO_ARMOR, MOD_UNKNOWN );//FIXME: MOD_POISON?
|
|
|
|
if (ent->client->poisonDamage<0)
|
|
{
|
|
ent->client->poisonDamage = 0;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
===============
|
|
G_SetClientSound
|
|
===============
|
|
*/
|
|
void G_SetClientSound( gentity_t *ent ) {
|
|
// if (ent->waterlevel && (ent->watertype&(CONTENTS_LAVA|CONTENTS_SLIME)) )
|
|
// ent->s.loopSound = G_SoundIndex("sound/weapons/stasis/electricloop.wav");
|
|
|
|
// else
|
|
// ent->s.loopSound = 0;
|
|
}
|
|
|
|
|
|
|
|
//==============================================================
|
|
void DoImpact( gentity_t *self, gentity_t *other, qboolean damageSelf )
|
|
{
|
|
float magnitude, my_mass;
|
|
vec3_t velocity;
|
|
|
|
if( self->client )
|
|
{
|
|
VectorCopy( self->client->ps.velocity, velocity );
|
|
my_mass = self->mass;
|
|
}
|
|
else
|
|
{
|
|
VectorCopy( self->s.pos.trDelta, velocity );
|
|
if ( self->s.pos.trType == TR_GRAVITY )
|
|
{
|
|
velocity[2] -= 0.25f * g_gravity->value;
|
|
}
|
|
if( !self->mass )
|
|
{
|
|
my_mass = 1;
|
|
}
|
|
else if ( self->mass <= 10 )
|
|
{
|
|
my_mass = 10;
|
|
}
|
|
else
|
|
{
|
|
my_mass = self->mass;///10;
|
|
}
|
|
}
|
|
|
|
magnitude = VectorLength( velocity ) * my_mass / 50;
|
|
|
|
if ( !self->client || self->client->ps.lastOnGround+300<level.time || ( self->client->ps.lastOnGround+100 < level.time ) )
|
|
{
|
|
vec3_t dir1, dir2;
|
|
float force = 0, dot;
|
|
|
|
if ( other->material == MAT_GLASS || other->material == MAT_GLASS_METAL || other->material == MAT_GRATE1 || ((other->svFlags&SVF_BBRUSH)&&(other->spawnflags&4/*THIN*/)) )//(other->absmax[0]-other->absmin[0]<=32||other->absmax[1]-other->absmin[1]<=32||other->absmax[2]-other->absmin[2]<=32)) )
|
|
{//glass and thin breakable brushes (axially aligned only, unfortunately) take more impact damage
|
|
magnitude *= 2;
|
|
}
|
|
|
|
//damage them
|
|
if ( magnitude >= 100 && other->s.number < ENTITYNUM_WORLD )
|
|
{
|
|
VectorCopy( velocity, dir1 );
|
|
VectorNormalize( dir1 );
|
|
if( VectorCompare( other->currentOrigin, vec3_origin ) )
|
|
{//a brush with no origin
|
|
VectorCopy ( dir1, dir2 );
|
|
}
|
|
else
|
|
{
|
|
VectorSubtract( other->currentOrigin, self->currentOrigin, dir2 );
|
|
VectorNormalize( dir2 );
|
|
}
|
|
|
|
dot = DotProduct( dir1, dir2 );
|
|
|
|
if ( dot >= 0.2 )
|
|
{
|
|
force = dot;
|
|
}
|
|
else
|
|
{
|
|
force = 0;
|
|
}
|
|
|
|
force *= (magnitude/50);
|
|
|
|
int cont = gi.pointcontents( other->absmax, other->s.number );
|
|
if( (cont&CONTENTS_WATER) )//|| (self.classname=="barrel"&&self.aflag))//FIXME: or other watertypes
|
|
{
|
|
force /= 3; //water absorbs 2/3 velocity
|
|
}
|
|
|
|
if ( self->NPC && other->s.number == ENTITYNUM_WORLD )
|
|
{//NPCs take less damage
|
|
force /= 2;
|
|
}
|
|
|
|
/*
|
|
if(self.frozen>0&&force>10)
|
|
force=10;
|
|
*/
|
|
|
|
if( ( force >= 1 && other->s.number != 0 ) || force >= 10)
|
|
{
|
|
/*
|
|
dprint("Damage other (");
|
|
dprint(loser.classname);
|
|
dprint("): ");
|
|
dprint(ftos(force));
|
|
dprint("\n");
|
|
*/
|
|
if ( other->svFlags & SVF_GLASS_BRUSH )
|
|
{
|
|
other->splashRadius = (float)(self->maxs[0] - self->mins[0])/4.0f;
|
|
}
|
|
|
|
if ( self->forcePushTime > level.time - 1000//was force pushed/pulled in the last 1600 milliseconds
|
|
&& self->forcePuller == other->s.number )//hit the person who pushed/pulled me
|
|
{//ignore the impact
|
|
}
|
|
else if ( other->takedamage )
|
|
{
|
|
if ( !self->client || !other->s.number || !other->client )
|
|
{//aw, fuck it, clients no longer take impact damage from other clients, unless you're the player
|
|
G_Damage( other, self, self, velocity, self->currentOrigin, force, DAMAGE_NO_ARMOR, MOD_IMPACT );
|
|
}
|
|
else
|
|
{
|
|
GEntity_PainFunc( other, self, self, self->currentOrigin, force, MOD_IMPACT );
|
|
//Hmm, maybe knockdown?
|
|
G_Throw( other, dir2, force );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//Hmm, maybe knockdown?
|
|
G_Throw( other, dir2, force );
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( damageSelf && self->takedamage && !(self->flags&FL_NO_IMPACT_DMG))
|
|
{
|
|
//Now damage me
|
|
//FIXME: more lenient falling damage, especially for when driving a vehicle
|
|
if ( self->client && self->client->ps.forceJumpZStart )
|
|
{//we were force-jumping
|
|
if ( self->currentOrigin[2] >= self->client->ps.forceJumpZStart )
|
|
{//we landed at same height or higher than we landed
|
|
magnitude = 0;
|
|
}
|
|
else
|
|
{//FIXME: take off some of it, at least?
|
|
magnitude = (self->client->ps.forceJumpZStart-self->currentOrigin[2])/3;
|
|
}
|
|
}
|
|
//if(self.classname!="monster_mezzoman"&&self.netname!="spider")//Cats always land on their feet
|
|
if( ( magnitude >= 100 + self->health && self->s.number != 0 && self->s.weapon != WP_SABER ) || ( magnitude >= 700 ) )//&& self.safe_time < level.time ))//health here is used to simulate structural integrity
|
|
{
|
|
if ( (self->s.weapon == WP_SABER || self->s.number == 0) && self->client && self->client->ps.groundEntityNum < ENTITYNUM_NONE && magnitude < 1000 )
|
|
{//players and jedi take less impact damage
|
|
//allow for some lenience on high falls
|
|
magnitude /= 2;
|
|
/*
|
|
if ( self.absorb_time >= time )//crouching on impact absorbs 1/2 the damage
|
|
{
|
|
magnitude/=2;
|
|
}
|
|
*/
|
|
}
|
|
magnitude /= 40;
|
|
magnitude = magnitude - force/2;//If damage other, subtract half of that damage off of own injury
|
|
if ( magnitude >= 1 )
|
|
{
|
|
//FIXME: Put in a thingtype impact sound function
|
|
/*
|
|
dprint("Damage self (");
|
|
dprint(self.classname);
|
|
dprint("): ");
|
|
dprint(ftos(magnitude));
|
|
dprint("\n");
|
|
*/
|
|
/*
|
|
if ( self.classname=="player_sheep "&& self.flags&FL_ONGROUND && self.velocity_z > -50 )
|
|
return;
|
|
*/
|
|
if ( self->NPC && self->s.weapon == WP_SABER )
|
|
{//FIXME: for now Jedi take no falling damage, but really they should if pushed off?
|
|
magnitude = 0;
|
|
}
|
|
G_Damage( self, NULL, NULL, NULL, self->currentOrigin, magnitude/2, DAMAGE_NO_ARMOR, MOD_FALLING );//FIXME: MOD_IMPACT
|
|
}
|
|
}
|
|
}
|
|
|
|
//FIXME: slow my velocity some?
|
|
|
|
self->lastImpact = level.time;
|
|
|
|
/*
|
|
if(self.flags&FL_ONGROUND)
|
|
self.last_onground=time;
|
|
*/
|
|
}
|
|
}
|
|
|
|
/*
|
|
==============
|
|
ClientImpacts
|
|
==============
|
|
*/
|
|
void ClientImpacts( gentity_t *ent, pmove_t *pm ) {
|
|
int i, j;
|
|
trace_t trace;
|
|
gentity_t *other;
|
|
|
|
memset( &trace, 0, sizeof( trace ) );
|
|
for (i=0 ; i<pm->numtouch ; i++) {
|
|
for (j=0 ; j<i ; j++) {
|
|
if (pm->touchents[j] == pm->touchents[i] ) {
|
|
break;
|
|
}
|
|
}
|
|
if (j != i) {
|
|
continue; // duplicated
|
|
}
|
|
other = &g_entities[ pm->touchents[i] ];
|
|
|
|
if ( ( ent->NPC != NULL ) && ( ent->e_TouchFunc != touchF_NULL ) ) { // last check unneccessary
|
|
GEntity_TouchFunc( ent, other, &trace );
|
|
}
|
|
|
|
if ( other->e_TouchFunc == touchF_NULL ) { // not needed, but I'll leave it I guess (cache-hit issues)
|
|
continue;
|
|
}
|
|
GEntity_TouchFunc( other, ent, &trace );
|
|
}
|
|
|
|
}
|
|
|
|
// Use triggers seems to have bigger "front" boundaries allowing
|
|
// user to trigger them from distance while it is possible to reach
|
|
// through them when standing near. To ballance this, move hand
|
|
// origin a bit back
|
|
const float TOUCH_OFFSET = -10.0f;
|
|
const vec3_t TOUCH_RANGE = { 4, 4, 4 };
|
|
|
|
void G_TouchTriggersWithHand( bool offHand, gentity_t *ent, vec3_t src, vec3_t vf ) {
|
|
vec3_t dest, mins, maxs;
|
|
gentity_t *touch[MAX_GENTITIES], *hit;
|
|
qboolean touched[MAX_GENTITIES];
|
|
int i, num;
|
|
trace_t trace;
|
|
|
|
memset (touched, qfalse, sizeof(touched) );
|
|
|
|
VectorMA( src, TOUCH_OFFSET, vf, dest );
|
|
VectorSubtract( dest, TOUCH_RANGE, mins );
|
|
VectorAdd( dest, TOUCH_RANGE, maxs );
|
|
|
|
num = gi.EntitiesInBox( mins, maxs, touch, MAX_GENTITIES );
|
|
|
|
for ( i=0 ; i<num ; i++ ) {
|
|
hit = touch[i];
|
|
|
|
if ( !( hit->contents & CONTENTS_TRIGGER ) ) {
|
|
// Entity is not trigger
|
|
continue;
|
|
}
|
|
if ( touched[i] == qtrue ) {
|
|
// Already touched this move
|
|
continue;
|
|
}
|
|
if ( !( hit->spawnflags & 4 ) ) {
|
|
// Non-BUTTON entities were already processed
|
|
continue;
|
|
}
|
|
|
|
// In case touching entity requiring to be activated facing to it via use gesture, check
|
|
// activation angle here. Existing check in g_trigger.cpp Touch_Multi is problematic because
|
|
// we do not have there information by which hand was entity triggered.
|
|
if ( (hit->spawnflags & 2) && DotProduct( hit->movedir, vf ) < 0.5 ) {
|
|
//Not Within 45 degrees
|
|
continue;
|
|
}
|
|
|
|
touched[i] = qtrue;
|
|
memset( &trace, 0, sizeof(trace) );
|
|
if ( hit->e_TouchFunc != touchF_NULL ) {
|
|
int channel = vr->right_handed != offHand ? 1 : 2;
|
|
if (level.time > vr->useHapticFeedbackTime[channel - 1]) {
|
|
cgi_HapticEvent("use_button", 0, channel, 60, 0, 0);
|
|
vr->useHapticFeedbackTime[channel - 1] = level.time + USE_HAPTIC_FEEDBACK_DELAY;
|
|
}
|
|
GEntity_TouchFunc(hit, ent, &trace);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
============
|
|
G_TouchTriggersLerped
|
|
|
|
Find all trigger entities that ent's current position touches.
|
|
Spectators will only interact with teleporters.
|
|
|
|
This version checks at 6 unit steps between last and current origins
|
|
============
|
|
*/
|
|
void G_TouchTriggersLerped( gentity_t *ent ) {
|
|
int i, num;
|
|
float dist, curDist = 0;
|
|
gentity_t *touch[MAX_GENTITIES], *hit;
|
|
trace_t trace;
|
|
vec3_t end, mins, maxs, diff;
|
|
const vec3_t range = { 40, 40, 52 };
|
|
qboolean touched[MAX_GENTITIES];
|
|
qboolean done = qfalse;
|
|
|
|
if ( !ent->client ) {
|
|
return;
|
|
}
|
|
|
|
// dead NPCs don't activate triggers!
|
|
if ( ent->client->ps.stats[STAT_HEALTH] <= 0 ) {
|
|
if ( ent->s.number )
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
#ifdef _DEBUG
|
|
for ( int j = 0; j < 3; j++ )
|
|
{
|
|
assert( !Q_isnan(ent->currentOrigin[j]));
|
|
assert( !Q_isnan(ent->lastOrigin[j]));
|
|
}
|
|
#endif// _DEBUG
|
|
VectorSubtract( ent->currentOrigin, ent->lastOrigin, diff );
|
|
dist = VectorNormalize( diff );
|
|
|
|
memset (touched, qfalse, sizeof(touched) );
|
|
|
|
bool thirdPersonActive = gi.cvar("cg_thirdPerson", "0", CVAR_TEMP)->integer;
|
|
bool useGestureEnabled = gi.cvar("vr_gesture_triggered_use", "1", CVAR_ARCHIVE)->integer; // defined in VrCvars.h
|
|
bool useGestureAllowed = useGestureEnabled && !thirdPersonActive && !(vr && vr->remote_droid);
|
|
|
|
for ( curDist = 0; !done && ent->maxs[1]>0; curDist += (float)ent->maxs[1]/2.0f )
|
|
{
|
|
if ( curDist >= dist )
|
|
{
|
|
VectorCopy( ent->currentOrigin, end );
|
|
done = qtrue;
|
|
}
|
|
else
|
|
{
|
|
VectorMA( ent->lastOrigin, curDist, diff, end );
|
|
}
|
|
VectorSubtract( end, range, mins );
|
|
VectorAdd( end, range, maxs );
|
|
|
|
num = gi.EntitiesInBox( mins, maxs, touch, MAX_GENTITIES );
|
|
|
|
// can't use ent->absmin, because that has a one unit pad
|
|
VectorAdd( end, ent->mins, mins );
|
|
VectorAdd( end, ent->maxs, maxs );
|
|
|
|
for ( i=0 ; i<num ; i++ ) {
|
|
hit = touch[i];
|
|
|
|
if ( (hit->e_TouchFunc == touchF_NULL) && (ent->e_TouchFunc == touchF_NULL) ) {
|
|
continue;
|
|
}
|
|
if ( !( hit->contents & CONTENTS_TRIGGER ) ) {
|
|
continue;
|
|
}
|
|
|
|
if ( touched[i] == qtrue ) {
|
|
continue;//already touched this move
|
|
}
|
|
if ( ent->client->ps.stats[STAT_HEALTH] <= 0 )
|
|
{
|
|
if ( Q_stricmp( "trigger_teleport", hit->classname ) || !(hit->spawnflags&16/*TTSF_DEAD_OK*/) )
|
|
{//dead clients can only touch tiogger_teleports that are marked as touchable
|
|
continue;
|
|
}
|
|
}
|
|
// use seperate code for determining if an item is picked up
|
|
// so you don't have to actually contact its bounding box
|
|
/*
|
|
if ( hit->s.eType == ET_ITEM ) {
|
|
if ( !BG_PlayerTouchesItem( &ent->client->ps, &hit->s, level.time ) ) {
|
|
continue;
|
|
}
|
|
} else */
|
|
{
|
|
if ( !gi.EntityContact( mins, maxs, hit ) ) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (ent->client && ent->client->ps.clientNum == 0 && hit->spawnflags & 4 && useGestureAllowed) {
|
|
// Entity is BUTTON touched by player with enabled use gestures. Skip it as we want to touch
|
|
// buttons by hands and not by body in this case
|
|
continue;
|
|
}
|
|
|
|
touched[i] = qtrue;
|
|
|
|
memset( &trace, 0, sizeof(trace) );
|
|
|
|
if ( hit->e_TouchFunc != touchF_NULL ) {
|
|
GEntity_TouchFunc(hit, ent, &trace);
|
|
}
|
|
|
|
//WTF? Why would a trigger ever fire off the NPC's touch func??!!!
|
|
/*
|
|
if ( ( ent->NPC != NULL ) && ( ent->e_TouchFunc != touchF_NULL ) ) {
|
|
GEntity_TouchFunc( ent, hit, &trace );
|
|
}
|
|
*/
|
|
}
|
|
}
|
|
|
|
// In case of player entity with use gesture enabled, trace additional entities for each players hand based on active use action.
|
|
if ( ent->client && ent->client->ps.clientNum == 0 && ent->client->ps.stats[STAT_HEALTH] > 0 && useGestureAllowed) {
|
|
if( ent->client->usercmd.buttons & BUTTON_USE ) {
|
|
vec3_t src, angles, vf;
|
|
BG_CalculateVRWeaponPosition(src, angles);
|
|
AngleVectors( angles, vf, NULL, NULL );
|
|
G_TouchTriggersWithHand(false, ent, src, vf );
|
|
}
|
|
if( ent->client->usercmd.buttons & BUTTON_ALT_USE ) {
|
|
vec3_t src, angles, vf;
|
|
BG_CalculateVROffHandPosition(src, angles);
|
|
AngleVectors( angles, vf, NULL, NULL );
|
|
G_TouchTriggersWithHand(true, ent, src, vf );
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
============
|
|
G_TouchTriggers
|
|
|
|
Find all trigger entities that ent's current position touches.
|
|
Spectators will only interact with teleporters.
|
|
============
|
|
*/
|
|
void G_TouchTriggers( gentity_t *ent ) {
|
|
int i, num;
|
|
gentity_t *touch[MAX_GENTITIES], *hit;
|
|
trace_t trace;
|
|
vec3_t mins, maxs;
|
|
const vec3_t range = { 40, 40, 52 };
|
|
|
|
if ( !ent->client ) {
|
|
return;
|
|
}
|
|
|
|
// dead clients don't activate triggers!
|
|
if ( ent->client->ps.stats[STAT_HEALTH] <= 0 ) {
|
|
return;
|
|
}
|
|
|
|
VectorSubtract( ent->client->ps.origin, range, mins );
|
|
VectorAdd( ent->client->ps.origin, range, maxs );
|
|
|
|
num = gi.EntitiesInBox( mins, maxs, touch, MAX_GENTITIES );
|
|
|
|
// can't use ent->absmin, because that has a one unit pad
|
|
VectorAdd( ent->client->ps.origin, ent->mins, mins );
|
|
VectorAdd( ent->client->ps.origin, ent->maxs, maxs );
|
|
|
|
for ( i=0 ; i<num ; i++ ) {
|
|
hit = touch[i];
|
|
|
|
if ( (hit->e_TouchFunc == touchF_NULL) && (ent->e_TouchFunc == touchF_NULL) ) {
|
|
continue;
|
|
}
|
|
if ( !( hit->contents & CONTENTS_TRIGGER ) ) {
|
|
continue;
|
|
}
|
|
|
|
// use seperate code for determining if an item is picked up
|
|
// so you don't have to actually contact its bounding box
|
|
/*
|
|
if ( hit->s.eType == ET_ITEM ) {
|
|
if ( !BG_PlayerTouchesItem( &ent->client->ps, &hit->s, level.time ) ) {
|
|
continue;
|
|
}
|
|
} else */
|
|
{
|
|
if ( !gi.EntityContact( mins, maxs, hit ) ) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
memset( &trace, 0, sizeof(trace) );
|
|
|
|
if ( hit->e_TouchFunc != touchF_NULL ) {
|
|
GEntity_TouchFunc(hit, ent, &trace);
|
|
}
|
|
|
|
if ( ( ent->NPC != NULL ) && ( ent->e_TouchFunc != touchF_NULL ) ) {
|
|
GEntity_TouchFunc( ent, hit, &trace );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
============
|
|
G_MoverTouchTriggers
|
|
|
|
Find all trigger entities that ent's current position touches.
|
|
Spectators will only interact with teleporters.
|
|
============
|
|
*/
|
|
void G_MoverTouchPushTriggers( gentity_t *ent, vec3_t oldOrg )
|
|
{
|
|
int i, num;
|
|
float step, stepSize, dist;
|
|
gentity_t *touch[MAX_GENTITIES], *hit;
|
|
trace_t trace;
|
|
vec3_t mins, maxs, dir, size, checkSpot;
|
|
const vec3_t range = { 40, 40, 52 };
|
|
|
|
// non-moving movers don't hit triggers!
|
|
if ( !VectorLengthSquared( ent->s.pos.trDelta ) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
VectorSubtract( ent->mins, ent->maxs, size );
|
|
stepSize = VectorLength( size );
|
|
if ( stepSize < 1 )
|
|
{
|
|
stepSize = 1;
|
|
}
|
|
|
|
VectorSubtract( ent->currentOrigin, oldOrg, dir );
|
|
dist = VectorNormalize( dir );
|
|
for ( step = 0; step <= dist; step += stepSize )
|
|
{
|
|
VectorMA( ent->currentOrigin, step, dir, checkSpot );
|
|
VectorSubtract( checkSpot, range, mins );
|
|
VectorAdd( checkSpot, range, maxs );
|
|
|
|
num = gi.EntitiesInBox( mins, maxs, touch, MAX_GENTITIES );
|
|
|
|
// can't use ent->absmin, because that has a one unit pad
|
|
VectorAdd( checkSpot, ent->mins, mins );
|
|
VectorAdd( checkSpot, ent->maxs, maxs );
|
|
|
|
for ( i=0 ; i<num ; i++ )
|
|
{
|
|
hit = touch[i];
|
|
|
|
if ( hit->s.eType != ET_PUSH_TRIGGER )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if ( hit->e_TouchFunc == touchF_NULL )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if ( !( hit->contents & CONTENTS_TRIGGER ) )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
|
|
if ( !gi.EntityContact( mins, maxs, hit ) )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
memset( &trace, 0, sizeof(trace) );
|
|
|
|
if ( hit->e_TouchFunc != touchF_NULL )
|
|
{
|
|
GEntity_TouchFunc(hit, ent, &trace);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void G_MatchPlayerWeapon( gentity_t *ent )
|
|
{
|
|
if ( g_entities[0].inuse && g_entities[0].client )
|
|
{//player is around
|
|
int newWeap;
|
|
if ( g_entities[0].client->ps.weapon > WP_DET_PACK )
|
|
{
|
|
newWeap = WP_BRYAR_PISTOL;
|
|
}
|
|
else
|
|
{
|
|
newWeap = g_entities[0].client->ps.weapon;
|
|
}
|
|
if ( newWeap != WP_NONE && ent->client->ps.weapon != newWeap )
|
|
{
|
|
if ( ent->weaponModel >= 0 )
|
|
{
|
|
gi.G2API_RemoveGhoul2Model(ent->ghoul2, ent->weaponModel);
|
|
}
|
|
ent->client->ps.stats[STAT_WEAPONS] = ( 1 << newWeap );
|
|
ent->client->ps.ammo[weaponData[newWeap].ammoIndex] = 999;
|
|
ChangeWeapon( ent, newWeap );
|
|
ent->client->ps.weapon = newWeap;
|
|
ent->client->ps.weaponstate = WEAPON_READY;
|
|
if ( newWeap == WP_SABER )
|
|
{
|
|
//FIXME: AddSound/Sight Event
|
|
WP_SaberInitBladeData( ent );
|
|
G_CreateG2AttachedWeaponModel( ent, ent->client->ps.saberModel );
|
|
ent->client->ps.saberActive = g_entities[0].client->ps.saberActive;
|
|
ent->client->ps.saberLength = g_entities[0].client->ps.saberLength;
|
|
ent->client->ps.saberAnimLevel = g_entities[0].client->ps.saberAnimLevel;
|
|
}
|
|
else
|
|
{
|
|
G_CreateG2AttachedWeaponModel( ent, weaponData[newWeap].weaponMdl );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void G_NPCMunroMatchPlayerWeapon( gentity_t *ent )
|
|
{
|
|
//special uber hack for cinematic Munro's to match player's weapon
|
|
if ( !in_camera )
|
|
{
|
|
if ( ent && ent->client && ent->NPC && (ent->NPC->aiFlags&NPCAI_MATCHPLAYERWEAPON) )
|
|
{//we're a Kyle NPC
|
|
G_MatchPlayerWeapon( ent );
|
|
}
|
|
}
|
|
}
|
|
/*
|
|
=================
|
|
ClientInactivityTimer
|
|
|
|
Returns qfalse if the client is dropped
|
|
=================
|
|
*/
|
|
qboolean ClientInactivityTimer( gclient_t *client ) {
|
|
if ( ! g_inactivity->integer )
|
|
{
|
|
// give everyone some time, so if the operator sets g_inactivity during
|
|
// gameplay, everyone isn't kicked
|
|
client->inactivityTime = level.time + 60 * 1000;
|
|
client->inactivityWarning = qfalse;
|
|
}
|
|
else if ( client->usercmd.forwardmove ||
|
|
client->usercmd.rightmove ||
|
|
client->usercmd.upmove ||
|
|
(client->usercmd.buttons & BUTTON_ATTACK) ||
|
|
(client->usercmd.buttons & BUTTON_ALT_ATTACK) )
|
|
{
|
|
client->inactivityTime = level.time + g_inactivity->integer * 1000;
|
|
client->inactivityWarning = qfalse;
|
|
}
|
|
else if ( !client->pers.localClient )
|
|
{
|
|
if ( level.time > client->inactivityTime )
|
|
{
|
|
gi.DropClient( client - level.clients, "Dropped due to inactivity" );
|
|
return qfalse;
|
|
}
|
|
if ( level.time > client->inactivityTime - 10000 && !client->inactivityWarning )
|
|
{
|
|
client->inactivityWarning = qtrue;
|
|
gi.SendServerCommand( client - level.clients, "cp \"Ten seconds until inactivity drop!\n\"" );
|
|
}
|
|
}
|
|
else
|
|
{//FIXME: here is where we can decide to play an idle animation
|
|
}
|
|
return qtrue;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
ClientTimerActions
|
|
|
|
Actions that happen once a second
|
|
==================
|
|
*/
|
|
void ClientTimerActions( gentity_t *ent, int msec ) {
|
|
gclient_t *client;
|
|
|
|
client = ent->client;
|
|
client->timeResidual += msec;
|
|
|
|
while ( client->timeResidual >= 1000 )
|
|
{
|
|
client->timeResidual -= 1000;
|
|
|
|
if ( ent->s.weapon != WP_NONE )
|
|
{
|
|
ent->client->sess.missionStats.weaponUsed[ent->s.weapon]++;
|
|
}
|
|
// if we've got the seeker powerup, see if we can shoot it at someone
|
|
/* if ( ent->client->ps.powerups[PW_SEEKER] > level.time )
|
|
{
|
|
vec3_t seekerPos, dir;
|
|
gentity_t *enemy = SeekerAcquiresTarget( ent, seekerPos );
|
|
|
|
if ( enemy != NULL ) // set the client's enemy to a valid target
|
|
{
|
|
FireSeeker( ent, enemy, seekerPos, dir );
|
|
|
|
gentity_t *tent;
|
|
tent = G_TempEntity( seekerPos, EV_POWERUP_SEEKER_FIRE );
|
|
VectorCopy( dir, tent->pos1 );
|
|
tent->s.eventParm = ent->s.number;
|
|
}
|
|
}*/
|
|
}
|
|
}
|
|
|
|
/*
|
|
====================
|
|
ClientIntermissionThink
|
|
====================
|
|
*/
|
|
static qboolean ClientCinematicThink( gclient_t *client ) {
|
|
client->ps.eFlags &= ~EF_FIRING;
|
|
|
|
// swap button actions
|
|
client->oldbuttons = client->buttons;
|
|
client->buttons = client->usercmd.buttons;
|
|
if ( client->buttons & ( BUTTON_USE ) & ( client->oldbuttons ^ client->buttons ) ) {
|
|
return( qtrue );
|
|
}
|
|
return( qfalse );
|
|
}
|
|
|
|
|
|
/*
|
|
================
|
|
ClientEvents
|
|
|
|
Events will be passed on to the clients for presentation,
|
|
but any server game effects are handled here
|
|
================
|
|
*/
|
|
extern void WP_SaberDamageTrace( gentity_t *ent );
|
|
extern void WP_SaberUpdateOldBladeData( gentity_t *ent );
|
|
void ClientEvents( gentity_t *ent, int oldEventSequence ) {
|
|
int i;
|
|
int event;
|
|
gclient_t *client;
|
|
//int damage;
|
|
#ifndef FINAL_BUILD
|
|
qboolean fired = qfalse;
|
|
#endif
|
|
|
|
client = ent->client;
|
|
|
|
for ( i = oldEventSequence ; i < client->ps.eventSequence ; i++ ) {
|
|
event = client->ps.events[ i & (MAX_PS_EVENTS-1) ];
|
|
|
|
switch ( event ) {
|
|
case EV_FALL_MEDIUM:
|
|
case EV_FALL_FAR://these come from bg_pmove, PM_CrashLand
|
|
if ( ent->s.eType != ET_PLAYER ) {
|
|
break; // not in the player model
|
|
}
|
|
/*
|
|
//FIXME: isn't there a more accurate way to calculate damage from falls?
|
|
if ( event == EV_FALL_FAR )
|
|
{
|
|
damage = 50;
|
|
}
|
|
else
|
|
{
|
|
damage = 25;
|
|
}
|
|
ent->painDebounceTime = level.time + 200; // no normal pain sound
|
|
G_Damage (ent, NULL, NULL, NULL, NULL, damage, 0, MOD_FALLING);
|
|
*/
|
|
break;
|
|
|
|
case EV_FIRE_WEAPON:
|
|
#ifndef FINAL_BUILD
|
|
if ( fired ) {
|
|
gi.Printf( "DOUBLE EV_FIRE_WEAPON AND-OR EV_ALT_FIRE!!\n" );
|
|
}
|
|
fired = qtrue;
|
|
#endif
|
|
FireWeapon( ent, qfalse );
|
|
break;
|
|
|
|
case EV_ALT_FIRE:
|
|
#ifndef FINAL_BUILD
|
|
if ( fired ) {
|
|
gi.Printf( "DOUBLE EV_FIRE_WEAPON AND-OR EV_ALT_FIRE!!\n" );
|
|
}
|
|
fired = qtrue;
|
|
#endif
|
|
FireWeapon( ent, qtrue );
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
//by the way, if you have your saber in hand and it's on, do the damage trace
|
|
if ( client->ps.weapon == WP_SABER )
|
|
{
|
|
if ( g_timescale->value >= 1.0f || !(client->ps.forcePowersActive&(1<<FP_SPEED)) )
|
|
{
|
|
int wait = FRAMETIME/2;
|
|
//sanity check
|
|
if ( client->ps.saberDamageDebounceTime - level.time > wait )
|
|
{//when you unpause the game with force speed on, the time gets *really* wiggy...
|
|
client->ps.saberDamageDebounceTime = level.time + wait;
|
|
}
|
|
if ( client->ps.saberDamageDebounceTime <= level.time )
|
|
{
|
|
WP_SaberDamageTrace( ent );
|
|
WP_SaberUpdateOldBladeData( ent );
|
|
/*
|
|
if ( g_timescale->value&&client->ps.clientNum==0&&!player_locked&&!MatrixMode&&client->ps.forcePowersActive&(1<<FP_SPEED) )
|
|
{
|
|
wait = floor( (float)wait*g_timescale->value );
|
|
}
|
|
*/
|
|
client->ps.saberDamageDebounceTime = level.time + wait;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
qboolean G_CheckClampUcmd( gentity_t *ent, usercmd_t *ucmd )
|
|
{
|
|
qboolean overridAngles = qfalse;
|
|
|
|
if ( (!ent->s.number&&ent->aimDebounceTime>level.time)
|
|
|| (ent->client->ps.pm_time && (ent->client->ps.pm_flags&PMF_TIME_KNOCKBACK))
|
|
|| ent->forcePushTime > level.time )
|
|
{//being knocked back, can't do anything!
|
|
ucmd->buttons = 0;
|
|
ucmd->forwardmove = 0;
|
|
ucmd->rightmove = 0;
|
|
ucmd->upmove = 0;
|
|
if ( ent->NPC )
|
|
{
|
|
VectorClear( ent->client->ps.moveDir );
|
|
}
|
|
}
|
|
|
|
overridAngles = (PM_AdjustAnglesForKnockdown( ent, ucmd, qfalse )?qtrue:overridAngles);
|
|
|
|
if ( ent->client->ps.saberLockTime > level.time )
|
|
{
|
|
ucmd->forwardmove = ucmd->rightmove = ucmd->upmove = 0;
|
|
if ( ent->client->ps.saberLockTime - level.time > SABER_LOCK_DELAYED_TIME )
|
|
{//2 second delay before either can push
|
|
//FIXME: base on difficulty
|
|
ucmd->buttons = 0;
|
|
}
|
|
else
|
|
{
|
|
ucmd->buttons &= ~(ucmd->buttons&~BUTTON_ATTACK);
|
|
}
|
|
overridAngles = (PM_AdjustAnglesForSaberLock( ent, ucmd )?qtrue:overridAngles);
|
|
if ( ent->NPC )
|
|
{
|
|
VectorClear( ent->client->ps.moveDir );
|
|
}
|
|
}
|
|
|
|
if ( ent->client->ps.saberMove == LS_A_LUNGE )
|
|
{//can't move during lunge
|
|
ucmd->rightmove = ucmd->upmove = 0;
|
|
if ( ent->client->ps.legsAnimTimer > 500 && (ent->s.number || !player_locked) )
|
|
{
|
|
ucmd->forwardmove = 127;
|
|
}
|
|
else
|
|
{
|
|
ucmd->forwardmove = 0;
|
|
}
|
|
if ( ent->NPC )
|
|
{//invalid now
|
|
VectorClear( ent->client->ps.moveDir );
|
|
}
|
|
}
|
|
|
|
if ( ent->client->ps.saberMove == LS_A_JUMP_T__B_ )
|
|
{//can't move during leap
|
|
if ( ent->client->ps.groundEntityNum != ENTITYNUM_NONE || (!ent->s.number && player_locked) )
|
|
{//hit the ground
|
|
ucmd->forwardmove = 0;
|
|
}
|
|
ucmd->rightmove = ucmd->upmove = 0;
|
|
if ( ent->NPC )
|
|
{//invalid now
|
|
VectorClear( ent->client->ps.moveDir );
|
|
}
|
|
}
|
|
|
|
if ( ent->client->ps.saberMove == LS_A_BACK || ent->client->ps.saberMove == LS_A_BACK_CR
|
|
|| ent->client->ps.saberMove == LS_A_BACKSTAB )
|
|
{//can't move or turn during back attacks
|
|
ucmd->forwardmove = ucmd->rightmove = 0;
|
|
if ( ent->NPC )
|
|
{
|
|
VectorClear( ent->client->ps.moveDir );
|
|
}
|
|
if ( (overridAngles = (PM_AdjustAnglesForBackAttack( ent, ucmd )?qtrue:overridAngles)) == qtrue )
|
|
{
|
|
//pull back the view
|
|
if ( !ent->s.number )
|
|
{
|
|
float animLength = PM_AnimLength( ent->client->clientInfo.animFileIndex, (animNumber_t)ent->client->ps.torsoAnim );
|
|
float elapsedTime = (float)(animLength-ent->client->ps.legsAnimTimer);
|
|
float backDist = 0;
|
|
if ( elapsedTime < animLength/2.0f )
|
|
{//starting anim
|
|
backDist = (elapsedTime/animLength)*120.0f;
|
|
}
|
|
else
|
|
{//ending anim
|
|
backDist = ((animLength-elapsedTime)/animLength)*120.0f;
|
|
}
|
|
cg.overrides.active |= CG_OVERRIDE_3RD_PERSON_RNG;
|
|
cg.overrides.thirdPersonRange = cg_thirdPersonRange.value+backDist;
|
|
}
|
|
}
|
|
}
|
|
else if ( ent->client->ps.torsoAnim == BOTH_WALL_FLIP_BACK1
|
|
|| ent->client->ps.torsoAnim == BOTH_WALL_FLIP_BACK2 )
|
|
{
|
|
//pull back the view
|
|
if ( !ent->s.number )
|
|
{
|
|
float animLength = PM_AnimLength( ent->client->clientInfo.animFileIndex, (animNumber_t)ent->client->ps.torsoAnim );
|
|
float elapsedTime = (float)(animLength-ent->client->ps.legsAnimTimer);
|
|
float backDist = 0;
|
|
if ( elapsedTime < animLength/2.0f )
|
|
{//starting anim
|
|
backDist = (elapsedTime/animLength)*120.0f;
|
|
}
|
|
else
|
|
{//ending anim
|
|
backDist = ((animLength-elapsedTime)/animLength)*120.0f;
|
|
}
|
|
cg.overrides.active |= CG_OVERRIDE_3RD_PERSON_RNG;
|
|
cg.overrides.thirdPersonRange = cg_thirdPersonRange.value+backDist;
|
|
}
|
|
}
|
|
else if ( !ent->s.number )
|
|
{
|
|
if ( ent->client->NPC_class != CLASS_ATST )
|
|
{
|
|
cg.overrides.active &= ~CG_OVERRIDE_3RD_PERSON_RNG;
|
|
cg.overrides.thirdPersonRange = 0;
|
|
}
|
|
}
|
|
|
|
|
|
if ( PM_InRoll( &ent->client->ps ) )
|
|
{
|
|
if ( ent->s.number || !player_locked )
|
|
{
|
|
PM_CmdForRoll( ent->client->ps.legsAnim, ucmd );
|
|
}
|
|
if ( ent->NPC )
|
|
{//invalid now
|
|
VectorClear( ent->client->ps.moveDir );
|
|
}
|
|
ent->client->ps.speed = 400;
|
|
}
|
|
|
|
if ( PM_InCartwheel( ent->client->ps.legsAnim ) )
|
|
{//can't keep moving in cartwheel
|
|
ucmd->forwardmove = ucmd->rightmove = ucmd->upmove = 0;
|
|
if ( ent->NPC )
|
|
{//invalid now
|
|
VectorClear( ent->client->ps.moveDir );
|
|
}
|
|
if ( ent->s.number || !player_locked )
|
|
{
|
|
switch ( ent->client->ps.legsAnim )
|
|
{
|
|
case BOTH_ARIAL_LEFT:
|
|
case BOTH_CARTWHEEL_LEFT:
|
|
ucmd->rightmove = -127;
|
|
break;
|
|
case BOTH_ARIAL_RIGHT:
|
|
case BOTH_CARTWHEEL_RIGHT:
|
|
ucmd->rightmove = 127;
|
|
break;
|
|
case BOTH_ARIAL_F1:
|
|
ucmd->forwardmove = 127;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
overridAngles = (PM_AdjustAngleForWallRun( ent, ucmd, qtrue )?qtrue:overridAngles);
|
|
|
|
return overridAngles;
|
|
}
|
|
|
|
void BG_AddPushVecToUcmd( gentity_t *self, usercmd_t *ucmd )
|
|
{
|
|
vec3_t forward, right, moveDir;
|
|
float pushSpeed, fMove, rMove;
|
|
|
|
if ( !self->client )
|
|
{
|
|
return;
|
|
}
|
|
pushSpeed = VectorLengthSquared(self->client->pushVec);
|
|
if(!pushSpeed)
|
|
{//not being pushed
|
|
return;
|
|
}
|
|
|
|
AngleVectors(self->client->ps.viewangles, forward, right, NULL);
|
|
VectorScale(forward, ucmd->forwardmove/127.0f * self->client->ps.speed, moveDir);
|
|
VectorMA(moveDir, ucmd->rightmove/127.0f * self->client->ps.speed, right, moveDir);
|
|
//moveDir is now our intended move velocity
|
|
|
|
VectorAdd(moveDir, self->client->pushVec, moveDir);
|
|
self->client->ps.speed = VectorNormalize(moveDir);
|
|
//moveDir is now our intended move velocity plus our push Vector
|
|
|
|
fMove = 127.0 * DotProduct(forward, moveDir);
|
|
rMove = 127.0 * DotProduct(right, moveDir);
|
|
ucmd->forwardmove = floor(fMove);//If in the same dir , will be positive
|
|
ucmd->rightmove = floor(rMove);//If in the same dir , will be positive
|
|
|
|
if ( self->client->pushVecTime < level.time )
|
|
{
|
|
VectorClear( self->client->pushVec );
|
|
}
|
|
}
|
|
|
|
void NPC_Accelerate( gentity_t *ent, qboolean fullWalkAcc, qboolean fullRunAcc )
|
|
{
|
|
if ( !ent->client || !ent->NPC )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ( !ent->NPC->stats.acceleration )
|
|
{//No acceleration means just start and stop
|
|
ent->NPC->currentSpeed = ent->NPC->desiredSpeed;
|
|
}
|
|
//FIXME: in cinematics always accel/decel?
|
|
else if ( ent->NPC->desiredSpeed <= ent->NPC->stats.walkSpeed )
|
|
{//Only accelerate if at walkSpeeds
|
|
if ( ent->NPC->desiredSpeed > ent->NPC->currentSpeed + ent->NPC->stats.acceleration )
|
|
{
|
|
//ent->client->ps.friction = 0;
|
|
ent->NPC->currentSpeed += ent->NPC->stats.acceleration;
|
|
}
|
|
else if ( ent->NPC->desiredSpeed > ent->NPC->currentSpeed )
|
|
{
|
|
//ent->client->ps.friction = 0;
|
|
ent->NPC->currentSpeed = ent->NPC->desiredSpeed;
|
|
}
|
|
else if ( fullWalkAcc && ent->NPC->desiredSpeed < ent->NPC->currentSpeed - ent->NPC->stats.acceleration )
|
|
{//decelerate even when walking
|
|
ent->NPC->currentSpeed -= ent->NPC->stats.acceleration;
|
|
}
|
|
else if ( ent->NPC->desiredSpeed < ent->NPC->currentSpeed )
|
|
{//stop on a dime
|
|
ent->NPC->currentSpeed = ent->NPC->desiredSpeed;
|
|
}
|
|
}
|
|
else// if ( ent->NPC->desiredSpeed > ent->NPC->stats.walkSpeed )
|
|
{//Only decelerate if at runSpeeds
|
|
if ( fullRunAcc && ent->NPC->desiredSpeed > ent->NPC->currentSpeed + ent->NPC->stats.acceleration )
|
|
{//Accelerate to runspeed
|
|
//ent->client->ps.friction = 0;
|
|
ent->NPC->currentSpeed += ent->NPC->stats.acceleration;
|
|
}
|
|
else if ( ent->NPC->desiredSpeed > ent->NPC->currentSpeed )
|
|
{//accelerate instantly
|
|
//ent->client->ps.friction = 0;
|
|
ent->NPC->currentSpeed = ent->NPC->desiredSpeed;
|
|
}
|
|
else if ( fullRunAcc && ent->NPC->desiredSpeed < ent->NPC->currentSpeed - ent->NPC->stats.acceleration )
|
|
{
|
|
ent->NPC->currentSpeed -= ent->NPC->stats.acceleration;
|
|
}
|
|
else if ( ent->NPC->desiredSpeed < ent->NPC->currentSpeed )
|
|
{
|
|
ent->NPC->currentSpeed = ent->NPC->desiredSpeed;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
-------------------------
|
|
NPC_GetWalkSpeed
|
|
-------------------------
|
|
*/
|
|
|
|
static int NPC_GetWalkSpeed( gentity_t *ent )
|
|
{
|
|
int walkSpeed = 0;
|
|
|
|
if ( ( ent->client == NULL ) || ( ent->NPC == NULL ) )
|
|
return 0;
|
|
|
|
switch ( ent->client->playerTeam )
|
|
{
|
|
case TEAM_PLAYER: //To shutup compiler, will add entries later (this is stub code)
|
|
default:
|
|
walkSpeed = ent->NPC->stats.walkSpeed;
|
|
break;
|
|
}
|
|
|
|
return walkSpeed;
|
|
}
|
|
|
|
/*
|
|
-------------------------
|
|
NPC_GetRunSpeed
|
|
-------------------------
|
|
*/
|
|
#define BORG_RUN_INCR 25
|
|
#define SPECIES_RUN_INCR 25
|
|
#define STASIS_RUN_INCR 20
|
|
#define WARBOT_RUN_INCR 20
|
|
|
|
static int NPC_GetRunSpeed( gentity_t *ent )
|
|
{
|
|
int runSpeed = 0;
|
|
|
|
if ( ( ent->client == NULL ) || ( ent->NPC == NULL ) )
|
|
return 0;
|
|
|
|
// team no longer indicates species/race. Use NPC_class to adjust speed for specific npc types
|
|
switch( ent->client->NPC_class)
|
|
{
|
|
case CLASS_PROBE: // droid cases here to shut-up compiler
|
|
case CLASS_GONK:
|
|
case CLASS_R2D2:
|
|
case CLASS_R5D2:
|
|
case CLASS_MARK1:
|
|
case CLASS_MARK2:
|
|
case CLASS_PROTOCOL:
|
|
case CLASS_ATST: // hmm, not really your average droid
|
|
case CLASS_MOUSE:
|
|
case CLASS_SEEKER:
|
|
case CLASS_REMOTE:
|
|
runSpeed = ent->NPC->stats.runSpeed;
|
|
break;
|
|
|
|
default:
|
|
runSpeed = ent->NPC->stats.runSpeed;
|
|
break;
|
|
}
|
|
|
|
return runSpeed;
|
|
}
|
|
|
|
void G_UpdateEmplacedWeaponData( gentity_t *ent )
|
|
{
|
|
if ( ent && ent->owner && ent->health > 0 )
|
|
{
|
|
gentity_t *chair = ent->owner;
|
|
//take the emplaced gun's waypoint as your own
|
|
ent->waypoint = chair->waypoint;
|
|
|
|
//update the actual origin of the sitter
|
|
mdxaBone_t boltMatrix;
|
|
vec3_t chairAng = {0, ent->client->ps.viewangles[YAW], 0};
|
|
|
|
// Getting the seat bolt here
|
|
gi.G2API_GetBoltMatrix( chair->ghoul2, chair->playerModel, chair->headBolt,
|
|
&boltMatrix, chairAng, chair->currentOrigin, (cg.time?cg.time:level.time),
|
|
NULL, chair->s.modelScale );
|
|
// Storing ent position, bolt position, and bolt axis
|
|
gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, ent->client->ps.origin );
|
|
gi.linkentity( ent );
|
|
}
|
|
}
|
|
|
|
void ExitEmplacedWeapon( gentity_t *ent )
|
|
{
|
|
// requesting to unlock from the weapon
|
|
int oldWeapon;
|
|
|
|
// Remove this gun from our inventory
|
|
ent->client->ps.stats[STAT_WEAPONS] &= ~( 1 << ent->client->ps.weapon );
|
|
|
|
// when we lock or unlock from the the gun, we just swap weapons with it
|
|
oldWeapon = ent->client->ps.weapon;
|
|
ent->client->ps.weapon = ent->owner->s.weapon;
|
|
ent->owner->s.weapon = oldWeapon;
|
|
|
|
extern void ChangeWeapon( gentity_t *ent, int newWeapon );
|
|
if ( ent->NPC )
|
|
{
|
|
ChangeWeapon( ent, ent->client->ps.weapon );
|
|
}
|
|
else
|
|
{
|
|
extern void CG_ChangeWeapon( int num );
|
|
CG_ChangeWeapon( ent->client->ps.weapon );
|
|
if (weaponData[ent->client->ps.weapon].weaponMdl[0])
|
|
{
|
|
//might be NONE, so check if it has a model
|
|
G_CreateG2AttachedWeaponModel( ent, weaponData[ent->client->ps.weapon].weaponMdl );
|
|
|
|
if ( ent->client->ps.weapon == WP_SABER && cg_saberAutoThird.value )
|
|
{
|
|
gi.cvar_set( "cg_thirdperson", "1" );
|
|
}
|
|
else if ( ent->client->ps.weapon != WP_SABER && cg_gunAutoFirst.value )
|
|
{
|
|
gi.cvar_set( "cg_thirdperson", "0" );
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( ent->client->ps.weapon == WP_SABER )
|
|
{
|
|
ent->client->ps.saberActive = ent->owner->alt_fire;
|
|
}
|
|
|
|
// We'll leave the gun pointed in the direction it was last facing, though we'll cut out the pitch
|
|
if ( ent->client )
|
|
{
|
|
VectorCopy( ent->client->ps.viewangles, ent->owner->s.angles );
|
|
ent->owner->s.angles[PITCH] = 0;
|
|
G_SetAngles( ent->owner, ent->owner->s.angles );
|
|
VectorCopy( ent->owner->s.angles, ent->owner->pos1 );
|
|
|
|
// if we are the player we will have put down a brush that blocks NPCs so that we have a clear spot to get back out.
|
|
//gentity_t *place = G_Find( NULL, FOFS(classname), "emp_placeholder" );
|
|
|
|
if ( ent->health > 0 && ent->owner->nextTrain )
|
|
{//he's still alive, and we have a placeholder, so put him back
|
|
// reset the players position
|
|
VectorCopy( ent->owner->nextTrain->currentOrigin, ent->client->ps.origin );
|
|
//reset ent's size to normal
|
|
VectorCopy( ent->owner->nextTrain->mins, ent->mins );
|
|
VectorCopy( ent->owner->nextTrain->maxs, ent->maxs );
|
|
//free the placeholder
|
|
G_FreeEntity( ent->owner->nextTrain );
|
|
//re-link the ent
|
|
gi.linkentity( ent );
|
|
}
|
|
else if ( ent->health <= 0 )
|
|
{
|
|
// dead, so give 'em a push out of the chair
|
|
vec3_t dir;
|
|
AngleVectors( ent->owner->s.angles, NULL, dir, NULL );
|
|
|
|
if ( rand() & 1 )
|
|
{
|
|
VectorScale( dir, -1, dir );
|
|
}
|
|
|
|
VectorMA( ent->client->ps.velocity, 75, dir, ent->client->ps.velocity );
|
|
}
|
|
}
|
|
|
|
// gi.G2API_DetachG2Model( &ent->ghoul2[ent->playerModel] );
|
|
|
|
ent->s.eFlags &= ~EF_LOCKED_TO_WEAPON;
|
|
ent->client->ps.eFlags &= ~EF_LOCKED_TO_WEAPON;
|
|
|
|
ent->owner->noDamageTeam = TEAM_FREE;
|
|
ent->owner->svFlags &= ~SVF_NONNPC_ENEMY;
|
|
ent->owner->delay = level.time;
|
|
ent->owner->activator = NULL;
|
|
|
|
if ( !ent->NPC )
|
|
{
|
|
// by keeping the owner, a dead npc can be pushed out of the chair without colliding with it
|
|
ent->owner = NULL;
|
|
}
|
|
}
|
|
|
|
void RunEmplacedWeapon( gentity_t *ent, usercmd_t **ucmd )
|
|
{
|
|
if (( (*ucmd)->buttons & BUTTON_USE || /*(*ucmd)->forwardmove < 0 ||*/ (*ucmd)->upmove > 0 ) && ent->owner && ent->owner->delay + 500 < level.time )
|
|
{
|
|
ent->owner->s.loopSound = 0;
|
|
|
|
ExitEmplacedWeapon( ent );
|
|
(*ucmd)->buttons &= ~BUTTON_USE;
|
|
|
|
G_Sound( ent, G_SoundIndex( "sound/weapons/emplaced/emplaced_dismount.mp3" ));
|
|
}
|
|
else
|
|
{
|
|
// this is a crappy way to put sounds on a moving emplaced gun....
|
|
/* if ( ent->owner )
|
|
{
|
|
if ( !VectorCompare( ent->owner->pos3, ent->owner->movedir ))
|
|
{
|
|
ent->owner->s.loopSound = G_SoundIndex( "sound/weapons/emplaced/emplaced_move_lp.wav" );
|
|
ent->owner->fly_sound_debounce_time = level.time;
|
|
}
|
|
else
|
|
{
|
|
if ( ent->owner->fly_sound_debounce_time + 100 <= level.time )
|
|
{
|
|
ent->owner->s.loopSound = 0;
|
|
}
|
|
}
|
|
|
|
VectorCopy( ent->owner->pos3, ent->owner->movedir );
|
|
}
|
|
*/
|
|
// don't allow movement, weapon switching, and most kinds of button presses
|
|
(*ucmd)->forwardmove = 0;
|
|
(*ucmd)->rightmove = 0;
|
|
(*ucmd)->upmove = 0;
|
|
(*ucmd)->buttons &= (BUTTON_ATTACK|BUTTON_ALT_ATTACK);
|
|
|
|
(*ucmd)->weapon = ent->client->ps.weapon; //WP_EMPLACED_GUN;
|
|
|
|
if ( ent->health <= 0 )
|
|
{
|
|
ExitEmplacedWeapon( ent );
|
|
}
|
|
}
|
|
}
|
|
|
|
// yes... so stop skipping...
|
|
void G_StopCinematicSkip( void )
|
|
{
|
|
gi.cvar_set("skippingCinematic", "0");
|
|
gi.cvar_set("timescale", "1");
|
|
}
|
|
|
|
void G_StartCinematicSkip( void )
|
|
{
|
|
|
|
if (cinematicSkipScript[0])
|
|
{
|
|
ICARUS_RunScript( &g_entities[0], va( "%s/%s", Q3_SCRIPT_DIR, cinematicSkipScript ) );
|
|
memset( cinematicSkipScript, 0, sizeof( cinematicSkipScript ) );
|
|
gi.cvar_set("skippingCinematic", "1");
|
|
gi.cvar_set("timescale", "100");
|
|
}
|
|
else
|
|
{
|
|
// no... so start skipping...
|
|
gi.cvar_set("skippingCinematic", "1");
|
|
gi.cvar_set("timescale", "100");
|
|
}
|
|
}
|
|
|
|
void G_CheckClientIdle( gentity_t *ent, usercmd_t *ucmd )
|
|
{
|
|
if ( !ent || !ent->client || ent->health <= 0 )
|
|
{
|
|
return;
|
|
}
|
|
if ( !ent->s.number && ( !cg.renderingThirdPerson || cg.zoomMode ) )
|
|
{
|
|
if ( ent->client->idleTime < level.time )
|
|
{
|
|
ent->client->idleTime = level.time;
|
|
}
|
|
return;
|
|
}
|
|
if ( !VectorCompare( vec3_origin, ent->client->ps.velocity )
|
|
|| ucmd->buttons || ucmd->forwardmove || ucmd->rightmove || ucmd->upmove
|
|
|| !PM_StandingAnim( ent->client->ps.legsAnim )
|
|
|| ent->enemy
|
|
|| ent->client->ps.legsAnimTimer
|
|
|| ent->client->ps.torsoAnimTimer )
|
|
{//FIXME: also check for turning?
|
|
if ( !VectorCompare( vec3_origin, ent->client->ps.velocity )
|
|
|| ucmd->buttons || ucmd->forwardmove || ucmd->rightmove || ucmd->upmove
|
|
|| ent->enemy )
|
|
{
|
|
//if in an idle, break out
|
|
switch ( ent->client->ps.legsAnim )
|
|
{
|
|
case BOTH_STAND1IDLE1:
|
|
case BOTH_STAND2IDLE1:
|
|
case BOTH_STAND2IDLE2:
|
|
case BOTH_STAND3IDLE1:
|
|
case BOTH_STAND4IDLE1:
|
|
ent->client->ps.legsAnimTimer = 0;
|
|
break;
|
|
}
|
|
switch ( ent->client->ps.torsoAnim )
|
|
{
|
|
case BOTH_STAND1IDLE1:
|
|
case BOTH_STAND2IDLE1:
|
|
case BOTH_STAND2IDLE2:
|
|
case BOTH_STAND3IDLE1:
|
|
case BOTH_STAND4IDLE1:
|
|
ent->client->ps.torsoAnimTimer = 0;
|
|
break;
|
|
}
|
|
}
|
|
//
|
|
if ( ent->client->idleTime < level.time )
|
|
{
|
|
ent->client->idleTime = level.time;
|
|
}
|
|
}
|
|
else if ( level.time - ent->client->idleTime > 5000 )
|
|
{//been idle for 5 seconds
|
|
int idleAnim = -1;
|
|
switch ( ent->client->ps.legsAnim )
|
|
{
|
|
case BOTH_STAND1:
|
|
idleAnim = BOTH_STAND1IDLE1;
|
|
break;
|
|
case BOTH_STAND2:
|
|
idleAnim = Q_irand(BOTH_STAND2IDLE1,BOTH_STAND2IDLE2);
|
|
break;
|
|
case BOTH_STAND3:
|
|
idleAnim = BOTH_STAND3IDLE1;
|
|
break;
|
|
case BOTH_STAND4:
|
|
idleAnim = BOTH_STAND4IDLE1;
|
|
break;
|
|
}
|
|
if ( idleAnim != -1 && PM_HasAnimation( ent, idleAnim ) )
|
|
{
|
|
NPC_SetAnim( ent, SETANIM_BOTH, idleAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
|
|
//don't idle again after this anim for a while
|
|
ent->client->idleTime = level.time + PM_AnimLength( ent->client->clientInfo.animFileIndex, (animNumber_t)idleAnim ) + Q_irand( 0, 2000 );
|
|
}
|
|
}
|
|
}
|
|
|
|
void G_CheckMovingLoopingSounds( gentity_t *ent, usercmd_t *ucmd )
|
|
{
|
|
if ( ent->client )
|
|
{
|
|
if ( (ent->NPC&&!VectorCompare( vec3_origin, ent->client->ps.moveDir ))//moving using moveDir
|
|
|| ucmd->forwardmove || ucmd->rightmove//moving using ucmds
|
|
|| (ucmd->upmove&&FlyingCreature( ent ))//flier using ucmds to move
|
|
|| (FlyingCreature( ent )&&!VectorCompare( vec3_origin, ent->client->ps.velocity )&&ent->health>0))//flier using velocity to move
|
|
{
|
|
switch( ent->client->NPC_class )
|
|
{
|
|
case CLASS_R2D2:
|
|
ent->s.loopSound = G_SoundIndex( "sound/chars/r2d2/misc/r2_move_lp.wav" );
|
|
break;
|
|
case CLASS_R5D2:
|
|
ent->s.loopSound = G_SoundIndex( "sound/chars/r2d2/misc/r2_move_lp2.wav" );
|
|
break;
|
|
case CLASS_MARK2:
|
|
ent->s.loopSound = G_SoundIndex( "sound/chars/mark2/misc/mark2_move_lp" );
|
|
break;
|
|
case CLASS_MOUSE:
|
|
ent->s.loopSound = G_SoundIndex( "sound/chars/mouse/misc/mouse_lp" );
|
|
break;
|
|
case CLASS_PROBE:
|
|
ent->s.loopSound = G_SoundIndex( "sound/chars/probe/misc/probedroidloop" );
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{//not moving under your own control, stop loopSound
|
|
if ( ent->client->NPC_class == CLASS_R2D2 || ent->client->NPC_class == CLASS_R5D2
|
|
|| ent->client->NPC_class == CLASS_MARK2 || ent->client->NPC_class == CLASS_MOUSE
|
|
|| ent->client->NPC_class == CLASS_PROBE )
|
|
{
|
|
ent->s.loopSound = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
==============
|
|
ClientThink
|
|
|
|
This will be called once for each client frame, which will
|
|
usually be a couple times for each server frame on fast clients.
|
|
|
|
==============
|
|
*/
|
|
|
|
extern int G_FindLocalInterestPoint( gentity_t *self );
|
|
extern void ForceGrip(gentity_t *ent);
|
|
extern void ForceLightning(gentity_t *ent);
|
|
extern void ForceTelepathy(gentity_t *ent);
|
|
extern void ForceHeal(gentity_t *ent);
|
|
extern void ForceThrowEx( gentity_t *self, qboolean pull, qboolean aimByViewAngles );
|
|
static void ProcessGenericCmd(gentity_t *ent, byte cmd)
|
|
{
|
|
switch(cmd) {
|
|
default:
|
|
break;
|
|
case GENCMD_FORCE_HEAL:
|
|
ForceHeal( ent );
|
|
break;
|
|
case GENCMD_FORCE_SPEED:
|
|
ForceSpeed( ent );
|
|
break;
|
|
case GENCMD_FORCE_THROW:
|
|
ForceThrowEx(ent, qfalse, qtrue);
|
|
break;
|
|
case GENCMD_FORCE_PULL:
|
|
ForceThrowEx(ent, qtrue, qtrue);
|
|
break;
|
|
case GENCMD_FORCE_DISTRACT:
|
|
ForceTelepathy(ent);
|
|
break;
|
|
case GENCMD_FORCE_GRIP:
|
|
ForceGrip(ent);
|
|
break;
|
|
case GENCMD_FORCE_LIGHTNING:
|
|
ForceLightning(ent);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void ClientThink_real( gentity_t *ent, usercmd_t *ucmd )
|
|
{
|
|
gclient_t *client;
|
|
pmove_t pm;
|
|
vec3_t oldOrigin;
|
|
int oldEventSequence;
|
|
int msec;
|
|
qboolean inSpinFlipAttack = PM_AdjustAnglesForSpinningFlip( ent, ucmd, qfalse );
|
|
qboolean controlledByPlayer = qfalse;
|
|
|
|
//Don't let the player do anything if in a camera
|
|
if ( ent->s.number == 0 )
|
|
{
|
|
extern cvar_t *g_skippingcin;
|
|
|
|
if ( ent->s.eFlags & EF_LOCKED_TO_WEAPON )
|
|
{
|
|
G_UpdateEmplacedWeaponData( ent );
|
|
RunEmplacedWeapon( ent, &ucmd );
|
|
}
|
|
if ( ent->client->ps.saberLockTime > level.time && ent->client->ps.saberLockEnemy != ENTITYNUM_NONE )
|
|
{
|
|
NPC_SetLookTarget( ent, ent->client->ps.saberLockEnemy, level.time+1000 );
|
|
}
|
|
if ( ent->client->renderInfo.lookTargetClearTime < level.time //NOTE: here this is used as a debounce, not an actual timer
|
|
&& ent->health > 0 //must be alive
|
|
&& (!ent->enemy || ent->client->ps.saberMove != LS_A_BACKSTAB) )//don't update if in backstab unless don't currently have an enemy
|
|
{//NOTE: doesn't keep updating to nearest enemy once you're dead
|
|
int newLookTarget;
|
|
if ( !G_ValidateLookEnemy( ent, ent->enemy ) )
|
|
{
|
|
ent->enemy = NULL;
|
|
}
|
|
//FIXME: make this a little prescient?
|
|
G_ChooseLookEnemy( ent, ucmd );
|
|
if ( ent->enemy )
|
|
{//target
|
|
newLookTarget = ent->enemy->s.number;
|
|
//so we don't change our minds in the next 1 second
|
|
ent->client->renderInfo.lookTargetClearTime = level.time+1000;
|
|
ent->client->renderInfo.lookMode = LM_ENT;
|
|
}
|
|
else
|
|
{//no target
|
|
//FIXME: what about sightalerts and missiles?
|
|
newLookTarget = ENTITYNUM_NONE;
|
|
newLookTarget = G_FindLocalInterestPoint( ent );
|
|
if ( newLookTarget != ENTITYNUM_NONE )
|
|
{//found something of interest
|
|
ent->client->renderInfo.lookMode = LM_INTEREST;
|
|
}
|
|
else
|
|
{//okay, no interesting things and no enemies, so look for items
|
|
newLookTarget = G_FindLookItem( ent );
|
|
ent->client->renderInfo.lookMode = LM_ENT;
|
|
}
|
|
}
|
|
if ( ent->client->renderInfo.lookTarget != newLookTarget )
|
|
{//transitioning
|
|
NPC_SetLookTarget( ent, newLookTarget, level.time+1000 );
|
|
}
|
|
}
|
|
if ( in_camera )
|
|
{
|
|
// watch the code here, you MUST "return" within this IF(), *unless* you're stopping the cinematic skip.
|
|
//
|
|
if ( ClientCinematicThink(ent->client) )
|
|
{
|
|
if (g_skippingcin->integer) // already doing cinematic skip?
|
|
{// yes... so stop skipping...
|
|
G_StopCinematicSkip();
|
|
}
|
|
else
|
|
{// no... so start skipping...
|
|
G_StartCinematicSkip();
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
else if ( ent->client->ps.vehicleModel != 0 && ent->health > 0 )
|
|
{
|
|
float speed;
|
|
speed = VectorLength( ent->client->ps.velocity );
|
|
if ( speed && ent->client->ps.groundEntityNum == ENTITYNUM_NONE )
|
|
{
|
|
int diff = AngleNormalize180(SHORT2ANGLE(ucmd->angles[YAW]+ent->client->ps.delta_angles[YAW]) - floor(ent->client->ps.viewangles[YAW]));
|
|
int slide = floor(((float)(diff))/120.0f*-127.0f);
|
|
|
|
if ( (slide > 0 && ucmd->rightmove >= 0) || ((slide < 0 && ucmd->rightmove <= 0)) )
|
|
{//note: don't want these to conflict right now because that seems to feel really weird
|
|
//gi.Printf( "slide %i, diff %i, yaw %i\n", slide, diff, ucmd->angles[YAW] );
|
|
ucmd->rightmove += slide;
|
|
}
|
|
|
|
if ( ucmd->rightmove > 64 )
|
|
{
|
|
ucmd->rightmove = 64;
|
|
}
|
|
else if ( ucmd->rightmove < -64 )
|
|
{
|
|
ucmd->rightmove = -64;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ucmd->rightmove = 0;
|
|
ucmd->angles[PITCH] = 0;
|
|
ucmd->angles[YAW] = ANGLE2SHORT( ent->client->ps.viewangles[YAW] ) - ent->client->ps.delta_angles[YAW];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( g_skippingcin->integer )
|
|
{//We're skipping the cinematic and it's over now
|
|
gi.cvar_set("timescale", "1");
|
|
gi.cvar_set("skippingCinematic", "0");
|
|
}
|
|
if ( ent->client->ps.pm_type == PM_DEAD && cg.missionStatusDeadTime < level.time )
|
|
{//mission status screen is up because player is dead, stop all scripts
|
|
stop_icarus = qtrue;
|
|
}
|
|
}
|
|
|
|
// // Don't allow the player to adjust the pitch when they are in third person overhead cam.
|
|
//extern vmCvar_t cg_thirdPerson;
|
|
// if ( cg_thirdPerson.integer == 2 )
|
|
// {
|
|
// ucmd->angles[PITCH] = 0;
|
|
// }
|
|
|
|
if ( cg.zoomMode == 2 )
|
|
{
|
|
// Any kind of movement when the player is NOT ducked when the disruptor gun is zoomed will cause us to auto-magically un-zoom
|
|
if ( ( (ucmd->forwardmove||ucmd->rightmove)
|
|
&& ucmd->upmove >= 0 //crouching-moving is ok
|
|
&& !(ucmd->buttons&BUTTON_USE)/*leaning is ok*/
|
|
)
|
|
|| ucmd->upmove > 0 //jumping not allowed
|
|
)
|
|
{
|
|
// already zooming, so must be wanting to turn it off
|
|
G_Sound( ent, G_SoundIndex( "sound/weapons/disruptor/zoomend.wav" ));
|
|
cg.zoomMode = 0;
|
|
cg.zoomTime = cg.time;
|
|
cg.zoomLocked = qfalse;
|
|
}
|
|
}
|
|
|
|
if ( (player_locked || (ent->client->ps.eFlags&EF_FORCE_GRIPPED)) && ent->client->ps.pm_type < PM_DEAD ) // unless dead
|
|
{//lock out player control
|
|
if ( !player_locked )
|
|
{
|
|
VectorClearM( ucmd->angles );
|
|
}
|
|
ucmd->forwardmove = 0;
|
|
ucmd->rightmove = 0;
|
|
ucmd->buttons = 0;
|
|
ucmd->upmove = 0;
|
|
PM_AdjustAnglesToGripper( ent, ucmd );
|
|
}
|
|
if ( ent->client->ps.leanofs )
|
|
{//no shooting while leaning
|
|
ucmd->buttons &= ~BUTTON_ATTACK;
|
|
if ( ent->client->ps.weapon != WP_DISRUPTOR )
|
|
{//can still zoom around corners
|
|
ucmd->buttons &= ~BUTTON_ALT_ATTACK;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( ent->s.eFlags & EF_LOCKED_TO_WEAPON )
|
|
{
|
|
G_UpdateEmplacedWeaponData( ent );
|
|
}
|
|
if ( player && player->client && player->client->ps.viewEntity == ent->s.number )
|
|
{
|
|
controlledByPlayer = qtrue;
|
|
int sav_weapon = ucmd->weapon;
|
|
memcpy( ucmd, &player->client->usercmd, sizeof( usercmd_t ) );
|
|
ucmd->weapon = sav_weapon;
|
|
ent->client->usercmd = *ucmd;
|
|
}
|
|
G_NPCMunroMatchPlayerWeapon( ent );
|
|
}
|
|
if ( ent->client )
|
|
{
|
|
if ( ent->client->NPC_class == CLASS_GONK ||
|
|
ent->client->NPC_class == CLASS_MOUSE ||
|
|
ent->client->NPC_class == CLASS_R2D2 ||
|
|
ent->client->NPC_class == CLASS_R5D2 )
|
|
{//no jumping or strafing in these guys
|
|
ucmd->upmove = ucmd->rightmove = 0;
|
|
}
|
|
else if ( ent->client->NPC_class == CLASS_ATST )
|
|
{//no jumping in atst
|
|
if (ent->client->ps.pm_type != PM_NOCLIP)
|
|
{
|
|
ucmd->upmove = 0;
|
|
}
|
|
if ( ent->client->ps.groundEntityNum != ENTITYNUM_NONE )
|
|
{//ATST crushed anything underneath it
|
|
gentity_t *under = &g_entities[ent->client->ps.groundEntityNum];
|
|
if ( under && under->health && under->takedamage )
|
|
{
|
|
vec3_t down = {0,0,-1};
|
|
//FIXME: we'll be doing traces down from each foot, so we'll have a real impact origin
|
|
G_Damage( under, ent, ent, down, under->currentOrigin, 100, 0, MOD_CRUSH );
|
|
}
|
|
//so they know to run like hell when I get close
|
|
//FIXME: project this in the direction I'm moving?
|
|
if ( !Q_irand( 0, 10 ) )
|
|
{//not so often...
|
|
AddSoundEvent( ent, ent->currentOrigin, ent->maxs[1]*5, AEL_DANGER );
|
|
AddSightEvent( ent, ent->currentOrigin, ent->maxs[1]*5, AEL_DANGER, 100 );
|
|
}
|
|
}
|
|
}
|
|
else if ( ent->client->ps.groundEntityNum < ENTITYNUM_WORLD && !ent->client->ps.forceJumpCharge )
|
|
{//standing on an entity and not currently force jumping
|
|
gentity_t *groundEnt = &g_entities[ent->client->ps.groundEntityNum];
|
|
if ( groundEnt &&
|
|
groundEnt->client &&
|
|
groundEnt->client->ps.groundEntityNum != ENTITYNUM_NONE &&
|
|
groundEnt->health > 0 &&
|
|
!PM_InRoll( &groundEnt->client->ps )
|
|
&& !(groundEnt->client->ps.eFlags&EF_LOCKED_TO_WEAPON)
|
|
&& !inSpinFlipAttack )
|
|
{//landed on a live client who is on the ground, jump off them and knock them down
|
|
if ( ent->health > 0 )
|
|
{
|
|
if ( !PM_InRoll( &ent->client->ps )
|
|
&& !PM_FlippingAnim( ent->client->ps.legsAnim ) )
|
|
{
|
|
if ( ent->s.number && ent->s.weapon == WP_SABER )
|
|
{
|
|
ent->client->ps.forceJumpCharge = 320;//FIXME: calc this intelligently?
|
|
}
|
|
else if ( !ucmd->upmove )
|
|
{//if not ducking (which should cause a roll), then jump
|
|
ucmd->upmove = 127;
|
|
}
|
|
if ( !ucmd->forwardmove && !ucmd->rightmove )
|
|
{// If not moving, don't want to jump straight up
|
|
//FIXME: trace for clear di?
|
|
if ( !Q_irand( 0, 3 ) )
|
|
{
|
|
ucmd->forwardmove = 127;
|
|
}
|
|
else if ( !Q_irand( 0, 3 ) )
|
|
{
|
|
ucmd->forwardmove = -127;
|
|
}
|
|
else if ( !Q_irand( 0, 1 ) )
|
|
{
|
|
ucmd->rightmove = 127;
|
|
}
|
|
else
|
|
{
|
|
ucmd->rightmove = -127;
|
|
}
|
|
}
|
|
if ( !ent->s.number && ucmd->upmove < 0 )
|
|
{//player who should roll- force it
|
|
int rollAnim = BOTH_ROLL_F;
|
|
if ( ucmd->forwardmove >= 0 )
|
|
{
|
|
rollAnim = BOTH_ROLL_F;
|
|
}
|
|
else if ( ucmd->forwardmove < 0 )
|
|
{
|
|
rollAnim = BOTH_ROLL_B;
|
|
}
|
|
else if ( ucmd->rightmove > 0 )
|
|
{
|
|
rollAnim = BOTH_ROLL_R;
|
|
}
|
|
else if ( ucmd->rightmove < 0 )
|
|
{
|
|
rollAnim = BOTH_ROLL_L;
|
|
}
|
|
NPC_SetAnim(ent,SETANIM_BOTH,rollAnim,SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD);
|
|
G_AddEvent( ent, EV_ROLL, 0 );
|
|
ent->client->ps.saberMove = LS_NONE;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{//a corpse? Shit
|
|
//Hmm, corpses should probably *always* knockdown...
|
|
ent->clipmask &= ~CONTENTS_BODY;
|
|
}
|
|
//FIXME: need impact sound event
|
|
GEntity_PainFunc( groundEnt, ent, ent, groundEnt->currentOrigin, 0, MOD_CRUSH );
|
|
if ( groundEnt->client->NPC_class == CLASS_DESANN && ent->client->NPC_class != CLASS_LUKE )
|
|
{//can't knock down desann unless you're luke
|
|
//FIXME: should he smack you away like Galak Mech?
|
|
}
|
|
else if (
|
|
( ( (groundEnt->s.number&&(groundEnt->s.weapon!=WP_SABER||!groundEnt->NPC||groundEnt->NPC->rank<Q_irand(RANK_CIVILIAN,RANK_CAPTAIN+1))) //an NPC who is either not a saber user or passed the rank-based probability test
|
|
|| ((!ent->s.number||G_ControlledByPlayer(groundEnt)) && !Q_irand( 0, 3 )&&cg.renderingThirdPerson&&!cg.zoomMode) )//or a player in third person, 25% of the time
|
|
&& groundEnt->client->playerTeam != ent->client->playerTeam ) //and not on the same team
|
|
|| ent->client->NPC_class == CLASS_DESANN )//desann always knocks people down
|
|
{
|
|
int knockAnim = BOTH_KNOCKDOWN1;
|
|
if ( PM_CrouchAnim( groundEnt->client->ps.legsAnim ) )
|
|
{//knockdown from crouch
|
|
knockAnim = BOTH_KNOCKDOWN4;
|
|
}
|
|
else
|
|
{
|
|
vec3_t gEFwd, gEAngles = {0,groundEnt->client->ps.viewangles[YAW],0};
|
|
AngleVectors( gEAngles, gEFwd, NULL, NULL );
|
|
if ( DotProduct( ent->client->ps.velocity, gEFwd ) > 50 )
|
|
{//pushing him forward
|
|
knockAnim = BOTH_KNOCKDOWN3;
|
|
}
|
|
}
|
|
NPC_SetAnim( groundEnt, SETANIM_BOTH, knockAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
client = ent->client;
|
|
|
|
// mark the time, so the connection sprite can be removed
|
|
client->lastCmdTime = level.time;
|
|
client->pers.lastCommand = *ucmd;
|
|
|
|
// sanity check the command time to prevent speedup cheating
|
|
if ( ucmd->serverTime > level.time + 200 )
|
|
{
|
|
ucmd->serverTime = level.time + 200;
|
|
}
|
|
if ( ucmd->serverTime < level.time - 1000 )
|
|
{
|
|
ucmd->serverTime = level.time - 1000;
|
|
}
|
|
|
|
msec = ucmd->serverTime - client->ps.commandTime;
|
|
if ( msec < 1 )
|
|
{
|
|
msec = 1;
|
|
}
|
|
if ( msec > 200 )
|
|
{
|
|
msec = 200;
|
|
}
|
|
|
|
// check for inactivity timer, but never drop the local client of a non-dedicated server
|
|
if ( !ClientInactivityTimer( client ) )
|
|
return;
|
|
|
|
if ( client->noclip )
|
|
{
|
|
client->ps.pm_type = PM_NOCLIP;
|
|
}
|
|
else if ( client->ps.stats[STAT_HEALTH] <= 0 )
|
|
{
|
|
client->ps.pm_type = PM_DEAD;
|
|
}
|
|
else
|
|
{
|
|
client->ps.pm_type = PM_NORMAL;
|
|
}
|
|
|
|
//FIXME: if global gravity changes this should update everyone's personal gravity...
|
|
if ( !(ent->svFlags & SVF_CUSTOM_GRAVITY) )
|
|
{
|
|
client->ps.gravity = g_gravity->value;
|
|
}
|
|
|
|
// set speed
|
|
if ( ent->NPC != NULL )
|
|
{//we don't actually scale the ucmd, we use actual speeds
|
|
if ( ent->NPC->combatMove == qfalse )
|
|
{
|
|
if ( !(ucmd->buttons & BUTTON_USE) )
|
|
{//Not leaning
|
|
qboolean Flying = (qboolean)(ucmd->upmove && ent->NPC->stats.moveType == MT_FLYSWIM);
|
|
qboolean Climbing = (qboolean)(ucmd->upmove && (ent->watertype & CONTENTS_LADDER));
|
|
|
|
client->ps.friction = 6;
|
|
|
|
if ( ucmd->forwardmove || ucmd->rightmove || Flying )
|
|
{
|
|
//if ( ent->NPC->behaviorState != BS_FORMATION )
|
|
{//In - Formation NPCs set thier desiredSpeed themselves
|
|
if ( ucmd->buttons & BUTTON_WALKING )
|
|
{
|
|
ent->NPC->desiredSpeed = NPC_GetWalkSpeed( ent );//ent->NPC->stats.walkSpeed;
|
|
}
|
|
else//running
|
|
{
|
|
ent->NPC->desiredSpeed = NPC_GetRunSpeed( ent );//ent->NPC->stats.runSpeed;
|
|
}
|
|
|
|
if ( ent->NPC->currentSpeed >= 80 && !controlledByPlayer )
|
|
{//At higher speeds, need to slow down close to stuff
|
|
//Slow down as you approach your goal
|
|
if ( ent->NPC->distToGoal < SLOWDOWN_DIST && !(ent->NPC->aiFlags&NPCAI_NO_SLOWDOWN) )//128
|
|
{
|
|
if ( ent->NPC->desiredSpeed > MIN_NPC_SPEED )
|
|
{
|
|
float slowdownSpeed = ((float)ent->NPC->desiredSpeed) * ent->NPC->distToGoal / SLOWDOWN_DIST;
|
|
|
|
ent->NPC->desiredSpeed = ceil(slowdownSpeed);
|
|
if ( ent->NPC->desiredSpeed < MIN_NPC_SPEED )
|
|
{//don't slow down too much
|
|
ent->NPC->desiredSpeed = MIN_NPC_SPEED;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if ( Climbing )
|
|
{
|
|
ent->NPC->desiredSpeed = ent->NPC->stats.walkSpeed;
|
|
}
|
|
else
|
|
{//We want to stop
|
|
ent->NPC->desiredSpeed = 0;
|
|
}
|
|
|
|
NPC_Accelerate( ent, qfalse, qfalse );
|
|
|
|
if ( ent->NPC->currentSpeed <= 24 && ent->NPC->desiredSpeed < ent->NPC->currentSpeed )
|
|
{//No-one walks this slow
|
|
client->ps.speed = ent->NPC->currentSpeed = 0;//Full stop
|
|
ucmd->forwardmove = 0;
|
|
ucmd->rightmove = 0;
|
|
}
|
|
else
|
|
{
|
|
if ( ent->NPC->currentSpeed <= ent->NPC->stats.walkSpeed )
|
|
{//Play the walkanim
|
|
ucmd->buttons |= BUTTON_WALKING;
|
|
}
|
|
else
|
|
{
|
|
ucmd->buttons &= ~BUTTON_WALKING;
|
|
}
|
|
|
|
if ( ent->NPC->currentSpeed > 0 )
|
|
{//We should be moving
|
|
if ( Climbing || Flying )
|
|
{
|
|
if ( !ucmd->upmove )
|
|
{//We need to force them to take a couple more steps until stopped
|
|
ucmd->upmove = ent->NPC->last_ucmd.upmove;//was last_upmove;
|
|
}
|
|
}
|
|
else if ( !ucmd->forwardmove && !ucmd->rightmove )
|
|
{//We need to force them to take a couple more steps until stopped
|
|
ucmd->forwardmove = ent->NPC->last_ucmd.forwardmove;//was last_forwardmove;
|
|
ucmd->rightmove = ent->NPC->last_ucmd.rightmove;//was last_rightmove;
|
|
}
|
|
}
|
|
|
|
client->ps.speed = ent->NPC->currentSpeed;
|
|
if ( player && player->client && player->client->ps.viewEntity == ent->s.number )
|
|
{
|
|
}
|
|
else
|
|
{
|
|
//Slow down on turns - don't orbit!!!
|
|
float turndelta = 0;
|
|
// if the NPC is locked into a Yaw, we want to check the lockedDesiredYaw...otherwise the NPC can't walk backwards, because it always thinks it trying to turn according to desiredYaw
|
|
if( client->renderInfo.renderFlags & RF_LOCKEDANGLE ) // yeah I know the RF_ flag is a pretty ugly hack...
|
|
{
|
|
turndelta = (180 - fabs( AngleDelta( ent->currentAngles[YAW], ent->NPC->lockedDesiredYaw ) ))/180;
|
|
}
|
|
else
|
|
{
|
|
turndelta = (180 - fabs( AngleDelta( ent->currentAngles[YAW], ent->NPC->desiredYaw ) ))/180;
|
|
}
|
|
|
|
if ( turndelta < 0.75f )
|
|
{
|
|
client->ps.speed = 0;
|
|
}
|
|
else if ( ent->NPC->distToGoal < 100 && turndelta < 1.0 )
|
|
{//Turn is greater than 45 degrees or closer than 100 to goal
|
|
client->ps.speed = floor(((float)(client->ps.speed))*turndelta);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ent->NPC->desiredSpeed = ( ucmd->buttons & BUTTON_WALKING ) ? NPC_GetWalkSpeed( ent ) : NPC_GetRunSpeed( ent );
|
|
|
|
client->ps.speed = ent->NPC->desiredSpeed;
|
|
}
|
|
}
|
|
else
|
|
{//Client sets ucmds and such for speed alterations
|
|
if ( ent->client->ps.vehicleModel != 0 && ent->health > 0 )
|
|
{
|
|
if ( client->ps.speed || client->ps.groundEntityNum == ENTITYNUM_NONE || ucmd->forwardmove > 0 || ucmd->upmove > 0)
|
|
{
|
|
if ( ucmd->forwardmove > 0 )
|
|
{
|
|
client->ps.speed += 20;
|
|
}
|
|
else if ( ucmd->forwardmove < 0 )
|
|
{
|
|
if ( client->ps.speed > 800 )
|
|
{
|
|
client->ps.speed -= 20;
|
|
}
|
|
else
|
|
{
|
|
client->ps.speed -= 5;
|
|
}
|
|
}
|
|
else
|
|
{//accelerate to cruising speed only, otherwise, just coast
|
|
if ( client->ps.speed < 800 )
|
|
{
|
|
client->ps.speed += 10;
|
|
if ( client->ps.speed > 800 )
|
|
{
|
|
client->ps.speed = 800;
|
|
}
|
|
}
|
|
}
|
|
ucmd->forwardmove = 127;
|
|
}
|
|
else
|
|
{
|
|
if ( ucmd->forwardmove < 0 )
|
|
{
|
|
ucmd->forwardmove = 0;
|
|
}
|
|
if ( ucmd->upmove < 0 )
|
|
{
|
|
ucmd->upmove = 0;
|
|
}
|
|
ucmd->rightmove = 0;
|
|
}
|
|
|
|
if ( client->ps.speed > 3000 )
|
|
{
|
|
client->ps.speed = 3000;
|
|
}
|
|
else if ( client->ps.speed < 0 )
|
|
{
|
|
client->ps.speed = 0;
|
|
}
|
|
|
|
if ( client->ps.speed < 800 )
|
|
{
|
|
client->ps.gravity = (800 - client->ps.speed)/4;
|
|
}
|
|
else
|
|
{
|
|
client->ps.gravity = 0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
client->ps.speed = g_speed->value;//default is 320
|
|
/*if ( !ent->s.number && ent->painDebounceTime>level.time )
|
|
{
|
|
client->ps.speed *= 0.25f;
|
|
}
|
|
else */if ( PM_SaberInAttack( ent->client->ps.saberMove ) && ucmd->forwardmove < 0 )
|
|
{//if running backwards while attacking, don't run as fast.
|
|
switch( client->ps.saberAnimLevel )
|
|
{
|
|
case FORCE_LEVEL_1:
|
|
client->ps.speed *= 0.75f;
|
|
break;
|
|
case FORCE_LEVEL_2:
|
|
client->ps.speed *= 0.60f;
|
|
break;
|
|
case FORCE_LEVEL_3:
|
|
client->ps.speed *= 0.45f;
|
|
break;
|
|
}
|
|
if ( g_saberMoveSpeed->value != 1.0f )
|
|
{
|
|
client->ps.speed *= g_saberMoveSpeed->value;
|
|
}
|
|
}
|
|
else if ( PM_SpinningSaberAnim( client->ps.legsAnim ) )
|
|
{
|
|
client->ps.speed *= 0.5f;
|
|
if ( g_saberMoveSpeed->value != 1.0f )
|
|
{
|
|
client->ps.speed *= g_saberMoveSpeed->value;
|
|
}
|
|
}
|
|
else if ( client->ps.weapon == WP_SABER && ( ucmd->buttons & BUTTON_ATTACK ) )
|
|
{//if attacking with saber while running, drop your speed
|
|
//FIXME: should be weaponTime? Or in certain anims?
|
|
switch( client->ps.saberAnimLevel )
|
|
{
|
|
case FORCE_LEVEL_2:
|
|
client->ps.speed *= 0.85f;
|
|
break;
|
|
case FORCE_LEVEL_3:
|
|
client->ps.speed *= 0.70f;
|
|
break;
|
|
}
|
|
if ( g_saberMoveSpeed->value != 1.0f )
|
|
{
|
|
client->ps.speed *= g_saberMoveSpeed->value;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if ( client->NPC_class == CLASS_ATST && client->ps.legsAnim == BOTH_RUN1START )
|
|
{//HACK: when starting to move as atst, ramp up speed
|
|
//float animLength = PM_AnimLength( client->clientInfo.animFileIndex, (animNumber_t)client->ps.legsAnim);
|
|
//client->ps.speed *= ( animLength - client->ps.legsAnimTimer)/animLength;
|
|
if ( client->ps.legsAnimTimer > 100 )
|
|
{
|
|
client->ps.speed = 0;
|
|
}
|
|
}
|
|
|
|
//Apply forced movement
|
|
if ( client->forced_forwardmove )
|
|
{
|
|
ucmd->forwardmove = client->forced_forwardmove;
|
|
if ( !client->ps.speed )
|
|
{
|
|
if ( ent->NPC != NULL )
|
|
{
|
|
client->ps.speed = ent->NPC->stats.runSpeed;
|
|
}
|
|
else
|
|
{
|
|
client->ps.speed = g_speed->value;//default is 320
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( client->forced_rightmove )
|
|
{
|
|
ucmd->rightmove = client->forced_rightmove;
|
|
if ( !client->ps.speed )
|
|
{
|
|
if ( ent->NPC != NULL )
|
|
{
|
|
client->ps.speed = ent->NPC->stats.runSpeed;
|
|
}
|
|
else
|
|
{
|
|
client->ps.speed = g_speed->value;//default is 320
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( ucmd->forwardmove < 0 && !(ucmd->buttons&BUTTON_WALKING) && client->ps.groundEntityNum != ENTITYNUM_NONE )
|
|
{//running backwards is slower than running forwards
|
|
client->ps.speed *= 0.75;
|
|
}
|
|
|
|
//FIXME: need to do this before check to avoid walls and cliffs (or just cliffs?)
|
|
BG_AddPushVecToUcmd( ent, ucmd );
|
|
|
|
G_CheckClampUcmd( ent, ucmd );
|
|
|
|
if ( (ucmd->buttons&BUTTON_BLOCKING) && !g_saberAutoBlocking->integer )
|
|
{//blocking with saber
|
|
ent->client->ps.saberBlockingTime = level.time + FRAMETIME;
|
|
}
|
|
WP_ForcePowersUpdate( ent, ucmd );
|
|
//if we have the saber in hand, check for starting a block to reflect shots
|
|
WP_SaberStartMissileBlockCheck( ent, ucmd );
|
|
// Update the position of the saber, and check to see if we're throwing it
|
|
|
|
if ( client->ps.saberEntityNum != ENTITYNUM_NONE )
|
|
{
|
|
int updates = 1;
|
|
if ( ent->NPC )
|
|
{
|
|
updates = 3;//simulate player update rate?
|
|
}
|
|
for ( int update = 0; update < updates; update++ )
|
|
{
|
|
WP_SaberUpdate( ent, ucmd );
|
|
}
|
|
}
|
|
|
|
//NEED to do this every frame, since these overrides do not go into the save/load data
|
|
if ( ent->client->ps.vehicleModel != 0 )
|
|
{
|
|
cg.overrides.active |= (CG_OVERRIDE_3RD_PERSON_RNG|CG_OVERRIDE_FOV);
|
|
cg.overrides.thirdPersonRange = 240;
|
|
cg.overrides.fov = 100;
|
|
}
|
|
else if ( client->ps.eFlags&EF_IN_ATST )
|
|
{
|
|
cg.overrides.active |= (CG_OVERRIDE_3RD_PERSON_RNG|CG_OVERRIDE_3RD_PERSON_POF|CG_OVERRIDE_3RD_PERSON_VOF);
|
|
cg.overrides.thirdPersonRange = 240;
|
|
if ( cg_thirdPersonAutoAlpha.integer )
|
|
{
|
|
if ( ent->health > 0 && ent->client->ps.viewangles[PITCH] < 15 && ent->client->ps.viewangles[PITCH] > 0 )
|
|
{
|
|
cg.overrides.active |= CG_OVERRIDE_3RD_PERSON_APH;
|
|
if ( cg.overrides.thirdPersonAlpha > 0.525f )
|
|
{
|
|
cg.overrides.thirdPersonAlpha -= 0.025f;
|
|
}
|
|
else if ( cg.overrides.thirdPersonAlpha > 0.5f )
|
|
{
|
|
cg.overrides.thirdPersonAlpha = 0.5f;
|
|
}
|
|
}
|
|
else if ( cg.overrides.active&CG_OVERRIDE_3RD_PERSON_APH )
|
|
{
|
|
if ( cg.overrides.thirdPersonAlpha > cg_thirdPersonAlpha.value )
|
|
{
|
|
cg.overrides.active &= ~CG_OVERRIDE_3RD_PERSON_APH;
|
|
}
|
|
else if ( cg.overrides.thirdPersonAlpha < cg_thirdPersonAlpha.value-0.1f )
|
|
{
|
|
cg.overrides.thirdPersonAlpha += 0.1f;
|
|
}
|
|
else if ( cg.overrides.thirdPersonAlpha < cg_thirdPersonAlpha.value )
|
|
{
|
|
cg.overrides.thirdPersonAlpha = cg_thirdPersonAlpha.value;
|
|
cg.overrides.active &= ~CG_OVERRIDE_3RD_PERSON_APH;
|
|
}
|
|
}
|
|
}
|
|
if ( ent->client->ps.viewangles[PITCH] > 0 )
|
|
{
|
|
cg.overrides.thirdPersonPitchOffset = ent->client->ps.viewangles[PITCH]*-0.75;
|
|
cg.overrides.thirdPersonVertOffset = 300+ent->client->ps.viewangles[PITCH]*-10;
|
|
if ( cg.overrides.thirdPersonVertOffset < 0 )
|
|
{
|
|
cg.overrides.thirdPersonVertOffset = 0;
|
|
}
|
|
}
|
|
else if ( ent->client->ps.viewangles[PITCH] < 0 )
|
|
{
|
|
cg.overrides.thirdPersonPitchOffset = ent->client->ps.viewangles[PITCH]*-0.75;
|
|
cg.overrides.thirdPersonVertOffset = 300+ent->client->ps.viewangles[PITCH]*-5;
|
|
if ( cg.overrides.thirdPersonVertOffset > 300 )
|
|
{
|
|
cg.overrides.thirdPersonVertOffset = 300;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
cg.overrides.thirdPersonPitchOffset = 0;
|
|
cg.overrides.thirdPersonVertOffset = 200;
|
|
}
|
|
}
|
|
|
|
//play/stop any looping sounds tied to controlled movement
|
|
G_CheckMovingLoopingSounds( ent, ucmd );
|
|
|
|
//remember your last angles
|
|
VectorCopy ( ent->client->ps.viewangles, ent->lastAngles );
|
|
// set up for pmove
|
|
oldEventSequence = client->ps.eventSequence;
|
|
|
|
memset( &pm, 0, sizeof(pm) );
|
|
|
|
pm.gent = ent;
|
|
pm.ps = &client->ps;
|
|
pm.cmd = *ucmd;
|
|
// pm.tracemask = MASK_PLAYERSOLID; // used differently for navgen
|
|
pm.tracemask = ent->clipmask;
|
|
pm.trace = gi.trace;
|
|
pm.pointcontents = gi.pointcontents;
|
|
pm.debugLevel = g_debugMove->integer;
|
|
pm.noFootsteps = qfalse;//( g_dmflags->integer & DF_NO_FOOTSTEPS ) > 0;
|
|
|
|
VectorCopy( client->ps.origin, oldOrigin );
|
|
|
|
// perform a pmove
|
|
Pmove( &pm );
|
|
|
|
ProcessGenericCmd(ent, pm.cmd.generic_cmd);
|
|
|
|
// save results of pmove
|
|
if ( ent->client->ps.eventSequence != oldEventSequence )
|
|
{
|
|
ent->eventTime = level.time;
|
|
{
|
|
int seq;
|
|
|
|
seq = (ent->client->ps.eventSequence-1) & (MAX_PS_EVENTS-1);
|
|
ent->s.event = ent->client->ps.events[ seq ] | ( ( ent->client->ps.eventSequence & 3 ) << 8 );
|
|
ent->s.eventParm = ent->client->ps.eventParms[ seq ];
|
|
}
|
|
}
|
|
PlayerStateToEntityState( &ent->client->ps, &ent->s );
|
|
|
|
VectorCopy ( ent->currentOrigin, ent->lastOrigin );
|
|
#if 1
|
|
// use the precise origin for linking
|
|
VectorCopy( ent->client->ps.origin, ent->currentOrigin );
|
|
#else
|
|
//We don't use prediction anymore, so screw this
|
|
// use the snapped origin for linking so it matches client predicted versions
|
|
VectorCopy( ent->s.pos.trBase, ent->currentOrigin );
|
|
#endif
|
|
|
|
//Had to leave this in, some legacy code must still be using s.angles
|
|
//Shouldn't interfere with interpolation of angles, should it?
|
|
VectorCopy( ent->client->ps.viewangles, ent->currentAngles );
|
|
|
|
VectorCopy( pm.mins, ent->mins );
|
|
VectorCopy( pm.maxs, ent->maxs );
|
|
|
|
ent->waterlevel = pm.waterlevel;
|
|
ent->watertype = pm.watertype;
|
|
|
|
VectorCopyM( ucmd->angles, client->pers.cmd_angles );
|
|
|
|
// execute client events
|
|
ClientEvents( ent, oldEventSequence );
|
|
|
|
//Stun Baton is _always_ firing
|
|
if (ent->s.weapon == WP_STUN_BATON)
|
|
{
|
|
//Use alt-fire to indicate not to make a noise, but do inflict damage
|
|
FireWeapon(ent, qtrue);
|
|
}
|
|
|
|
if ( pm.useEvent )
|
|
{
|
|
//TODO: Use
|
|
TryUse( ent );
|
|
}
|
|
if ( pm.altUseEvent )
|
|
{
|
|
TryAltUse( ent );
|
|
}
|
|
|
|
// link entity now, after any personal teleporters have been used
|
|
gi.linkentity( ent );
|
|
ent->client->hiddenDist = 0;
|
|
if ( !ent->client->noclip )
|
|
{
|
|
G_TouchTriggersLerped( ent );
|
|
}
|
|
|
|
// touch other objects
|
|
ClientImpacts( ent, &pm );
|
|
|
|
// swap and latch button actions
|
|
client->oldbuttons = client->buttons;
|
|
client->buttons = ucmd->buttons;
|
|
client->latched_buttons |= client->buttons & ~client->oldbuttons;
|
|
|
|
// check for respawning
|
|
if ( client->ps.stats[STAT_HEALTH] <= 0 )
|
|
{
|
|
// wait for the attack button to be pressed
|
|
if ( ent->NPC == NULL && level.time > client->respawnTime )
|
|
{
|
|
// don't allow respawn if they are still flying through the
|
|
// air, unless 10 extra seconds have passed, meaning something
|
|
// strange is going on, like the corpse is caught in a wind tunnel
|
|
/*
|
|
if ( level.time < client->respawnTime + 10000 )
|
|
{
|
|
if ( client->ps.groundEntityNum == ENTITYNUM_NONE )
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
*/
|
|
|
|
// pressing attack or use is the normal respawn method
|
|
if ( ucmd->buttons & ( BUTTON_ATTACK ) )
|
|
{
|
|
respawn( ent );
|
|
// gi.SendConsoleCommand( va("disconnect;wait;wait;wait;wait;wait;wait;devmap %s\n",level.mapname) );
|
|
}
|
|
}
|
|
if ( ent
|
|
&& !ent->s.number
|
|
&& ent->enemy
|
|
&& ent->enemy != ent
|
|
&& ent->enemy->s.number < ENTITYNUM_WORLD
|
|
&& ent->enemy->inuse
|
|
&& !(cg.overrides.active&CG_OVERRIDE_3RD_PERSON_ANG) )
|
|
{//keep facing enemy
|
|
vec3_t deadDir;
|
|
float deadYaw;
|
|
VectorSubtract( ent->enemy->currentOrigin, ent->currentOrigin, deadDir );
|
|
deadYaw = AngleNormalize180( vectoyaw ( deadDir ) );
|
|
if ( deadYaw > ent->client->ps.stats[STAT_DEAD_YAW] + 1 )
|
|
{
|
|
ent->client->ps.stats[STAT_DEAD_YAW]++;
|
|
}
|
|
else if ( deadYaw < ent->client->ps.stats[STAT_DEAD_YAW] - 1 )
|
|
{
|
|
ent->client->ps.stats[STAT_DEAD_YAW]--;
|
|
}
|
|
else
|
|
{
|
|
ent->client->ps.stats[STAT_DEAD_YAW] = deadYaw;
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
// perform once-a-second actions
|
|
ClientTimerActions( ent, msec );
|
|
|
|
ClientEndPowerUps( ent );
|
|
//DEBUG INFO
|
|
/*
|
|
if ( client->ps.clientNum < 1 )
|
|
{//Only a player
|
|
if ( ucmd->buttons & BUTTON_USE )
|
|
{
|
|
NAV_PrintLocalWpDebugInfo( ent );
|
|
}
|
|
}
|
|
*/
|
|
//try some idle anims on ent if getting no input and not moving for some time
|
|
G_CheckClientIdle( ent, ucmd );
|
|
}
|
|
|
|
/*
|
|
==================
|
|
ClientThink
|
|
|
|
A new command has arrived from the client
|
|
==================
|
|
*/
|
|
extern void PM_CheckForceUseButton( gentity_t *ent, usercmd_t *ucmd );
|
|
extern qboolean PM_GentCantJump( gentity_t *gent );
|
|
void ClientThink( int clientNum, usercmd_t *ucmd ) {
|
|
gentity_t *ent;
|
|
qboolean restore_ucmd = qfalse;
|
|
usercmd_t sav_ucmd = {0};
|
|
|
|
ent = g_entities + clientNum;
|
|
|
|
if ( !ent->s.number )
|
|
{
|
|
if ( ent->client->ps.viewEntity > 0 && ent->client->ps.viewEntity < ENTITYNUM_WORLD )
|
|
{//you're controlling another NPC
|
|
gentity_t *controlled = &g_entities[ent->client->ps.viewEntity];
|
|
qboolean freed = qfalse;
|
|
if ( controlled->NPC
|
|
&& controlled->NPC->controlledTime
|
|
&& ent->client->ps.forcePowerLevel[FP_TELEPATHY] > FORCE_LEVEL_3 )
|
|
{//An NPC I'm controlling with mind trick
|
|
if ( controlled->NPC->controlledTime < level.time )
|
|
{//time's up!
|
|
G_ClearViewEntity( ent );
|
|
freed = qtrue;
|
|
}
|
|
else if ( ucmd->upmove > 0 )
|
|
{//jumping gets you out of it FIXME: check some other button instead... like ESCAPE... so you could even have total control over an NPC?
|
|
G_ClearViewEntity( ent );
|
|
ucmd->upmove = 0;//ucmd->buttons = 0;
|
|
//stop player from doing anything for a half second after
|
|
ent->aimDebounceTime = level.time + 500;
|
|
freed = qtrue;
|
|
}
|
|
}
|
|
else if ( controlled->NPC //an NPC
|
|
&& PM_GentCantJump( controlled ) //that cannot jump
|
|
&& controlled->NPC->stats.moveType != MT_FLYSWIM ) //and does not use upmove to fly
|
|
{//these types use jump to get out
|
|
if ( ucmd->upmove > 0 )
|
|
{//jumping gets you out of it FIXME: check some other button instead... like ESCAPE... so you could even have total control over an NPC?
|
|
G_ClearViewEntity( ent );
|
|
ucmd->upmove = 0;//ucmd->buttons = 0;
|
|
//stop player from doing anything for a half second after
|
|
ent->aimDebounceTime = level.time + 500;
|
|
freed = qtrue;
|
|
}
|
|
}
|
|
else
|
|
{//others use the blocking key, button3
|
|
if ( (ucmd->buttons&BUTTON_BLOCKING) )
|
|
{//jumping gets you out of it FIXME: check some other button instead... like ESCAPE... so you could even have total control over an NPC?
|
|
G_ClearViewEntity( ent );
|
|
ucmd->buttons = 0;
|
|
freed = qtrue;
|
|
}
|
|
}
|
|
if ( !freed )
|
|
{//still controlling, save off my ucmd and clear it for my actual run through pmove
|
|
restore_ucmd = qtrue;
|
|
memcpy( &sav_ucmd, ucmd, sizeof( usercmd_t ) );
|
|
memset( ucmd, 0, sizeof( usercmd_t ) );
|
|
//to keep pointing in same dir, need to set ucmd->angles
|
|
ucmd->angles[PITCH] = ANGLE2SHORT( ent->client->ps.viewangles[PITCH] ) - ent->client->ps.delta_angles[PITCH];
|
|
ucmd->angles[YAW] = ANGLE2SHORT( ent->client->ps.viewangles[YAW] ) - ent->client->ps.delta_angles[YAW];
|
|
ucmd->angles[ROLL] = 0;
|
|
}
|
|
else
|
|
{
|
|
ucmd->angles[PITCH] = ANGLE2SHORT( ent->client->ps.viewangles[PITCH] ) - ent->client->ps.delta_angles[PITCH];
|
|
ucmd->angles[YAW] = ANGLE2SHORT( ent->client->ps.viewangles[YAW] ) - ent->client->ps.delta_angles[YAW];
|
|
ucmd->angles[ROLL] = 0;
|
|
}
|
|
}
|
|
else if ( ent->client->NPC_class == CLASS_ATST )
|
|
{
|
|
if ( ucmd->upmove > 0 )
|
|
{//get out of ATST
|
|
GEntity_UseFunc( ent->activator, ent, ent );
|
|
ucmd->upmove = 0;//ucmd->buttons = 0;
|
|
}
|
|
}
|
|
|
|
if ( (ucmd->buttons&BUTTON_BLOCKING) && !g_saberAutoBlocking->integer )
|
|
{
|
|
ucmd->buttons &= ~(BUTTON_ATTACK|BUTTON_ALT_ATTACK);
|
|
}
|
|
PM_CheckForceUseButton( ent, ucmd );
|
|
}
|
|
|
|
ent->client->usercmd = *ucmd;
|
|
|
|
// if ( !g_syncronousClients->integer )
|
|
{
|
|
ClientThink_real( ent, ucmd );
|
|
}
|
|
// ClientThink_real can end up freeing this ent, need to check
|
|
if ( restore_ucmd && ent->client )
|
|
{//restore ucmd for later so NPC you're controlling can refer to them
|
|
memcpy( &ent->client->usercmd, &sav_ucmd, sizeof( usercmd_t ) );
|
|
}
|
|
|
|
if ( ent->s.number )
|
|
{//NPCs drown, burn from lava, etc, also
|
|
P_WorldEffects( ent );
|
|
}
|
|
}
|
|
|
|
void ClientEndPowerUps( gentity_t *ent )
|
|
{
|
|
int i;
|
|
|
|
if ( ent == NULL || ent->client == NULL )
|
|
{
|
|
return;
|
|
}
|
|
// turn off any expired powerups
|
|
for ( i = 0 ; i < MAX_POWERUPS ; i++ )
|
|
{
|
|
if ( ent->client->ps.powerups[ i ] < level.time )
|
|
{
|
|
ent->client->ps.powerups[ i ] = 0;
|
|
}
|
|
}
|
|
}
|
|
/*
|
|
==============
|
|
ClientEndFrame
|
|
|
|
Called at the end of each server frame for each connected client
|
|
A fast client will have multiple ClientThink for each ClientEdFrame,
|
|
while a slow client may have multiple ClientEndFrame between ClientThink.
|
|
==============
|
|
*/
|
|
void ClientEndFrame( gentity_t *ent )
|
|
{
|
|
//
|
|
// If the end of unit layout is displayed, don't give
|
|
// the player any normal movement attributes
|
|
//
|
|
|
|
// burn from lava, etc
|
|
P_WorldEffects (ent);
|
|
|
|
// apply all the damage taken this frame
|
|
P_DamageFeedback (ent);
|
|
|
|
// add the EF_CONNECTION flag if we haven't gotten commands recently
|
|
if ( level.time - ent->client->lastCmdTime > 1000 ) {
|
|
ent->s.eFlags |= EF_CONNECTION;
|
|
} else {
|
|
ent->s.eFlags &= ~EF_CONNECTION;
|
|
}
|
|
|
|
ent->client->ps.stats[STAT_HEALTH] = ent->health; // FIXME: get rid of ent->health...
|
|
|
|
// G_SetClientSound (ent);
|
|
}
|
|
|
|
|