diff --git a/CMakeLists.txt b/CMakeLists.txt index f95fc93f..acb13d2a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,10 +2,10 @@ cmake_minimum_required(VERSION 2.8...3.22 FATAL_ERROR) project(dhewm3sdk) option(BASE "Build the base (game/) game code" ON) -set(BASE_NAME "base" CACHE STRING "Name of the mod built from game/ (will result in \${BASE_NAME}.dll)") +set(BASE_NAME "eoc" CACHE STRING "Name of the mod built from game/ (will result in \${BASE_NAME}.dll)") set(BASE_DEFS "GAME_DLL" CACHE STRING "Compiler definitions for the mod built from game/") -option(D3XP "Build the d3xp/ game code" ON) +option(D3XP "Build the d3xp/ game code" OFF) set(D3XP_NAME "d3xp" CACHE STRING "Name of the mod built from d3xp/ (will result in \${D3XP_NAME}.dll)") set(D3XP_DEFS "GAME_DLL;_D3XP;CTF" CACHE STRING "Compiler definitions for the mod built from d3xp/") @@ -20,6 +20,17 @@ set(src_game_mod # add additional .cpp files of your mod in game/ # (that you added to the ones already existant in the SDK/in dhewm3) # like "game/MyFile.cpp" (without quotes, one file per line) + game/PlayerArtifacts.cpp + game/PlayerScriptEvents.cpp + game/ai/AI_Golem.cpp + game/ai/AI_Shadowspawn.cpp + game/ai/AI_Veloxite.cpp + game/objects/Leaf.cpp + game/objects/LeafEmitter.cpp + game/objects/Tree.cpp + game/projectiles/FireStorm.cpp + game/projectiles/Soul.cpp + game/projectiles/Wraithverge.cpp ) set(src_d3xp_mod @@ -190,12 +201,19 @@ if(D3_COMPILER_IS_GCC_OR_CLANG) endif () endif () - set(CMAKE_C_FLAGS_DEBUG "-g -D_DEBUG -O0") + if(cpu STREQUAL "e2k" AND CMAKE_COMPILER_IS_GNUCC) + # O3 on E2K mcst-lcc approximately equal to O2 at X86/ARM gcc + set(OPT_LEVEL "-O3") + else() + set(OPT_LEVEL "-O2") + endif() + + set(CMAKE_C_FLAGS_DEBUG "-g -ggdb -D_DEBUG -O0") set(CMAKE_C_FLAGS_DEBUGALL "-g -ggdb -D_DEBUG") set(CMAKE_C_FLAGS_PROFILE "-g -ggdb -D_DEBUG -O1 -fno-omit-frame-pointer") - set(CMAKE_C_FLAGS_RELEASE "-O2 -fno-math-errno -fno-trapping-math -fomit-frame-pointer") - set(CMAKE_C_FLAGS_RELWITHDEBINFO "-O2 -g -ggdb -fno-math-errno -fno-trapping-math -fno-omit-frame-pointer") - set(CMAKE_C_FLAGS_MINSIZEREL "-Os -fno-math-errno -fno-trapping-math -fomit-frame-pointer") + set(CMAKE_C_FLAGS_RELEASE "${OPT_LEVEL} -fno-math-errno -fno-trapping-math -ffinite-math-only -fomit-frame-pointer") + set(CMAKE_C_FLAGS_RELWITHDEBINFO "-g -ggdb ${OPT_LEVEL} -fno-math-errno -fno-trapping-math -ffinite-math-only -fno-omit-frame-pointer") + set(CMAKE_C_FLAGS_MINSIZEREL "-Os -fno-math-errno -fno-trapping-math -ffinite-math-only -fomit-frame-pointer") set(CMAKE_CXX_FLAGS_DEBUGALL ${CMAKE_C_FLAGS_DEBUGALL}) set(CMAKE_CXX_FLAGS_PROFILE ${CMAKE_C_FLAGS_PROFILE}) @@ -203,7 +221,10 @@ if(D3_COMPILER_IS_GCC_OR_CLANG) add_compile_options(-fno-strict-aliasing) # dear idiot compilers, don't fuck up math code with useless FMA "optimizations" # (https://gcc.gnu.org/bugzilla/show_bug.cgi?id=100839) - add_compile_options(-ffp-contract=off) + CHECK_CXX_COMPILER_FLAG("-ffp-contract=off" cxx_has_fp-contract) + if(cxx_has_fp-contract) + add_compile_options(-ffp-contract=off) + endif() if(ASAN) # if this doesn't work, ASan might not be available on your platform, don't set ASAN then.. @@ -212,7 +233,7 @@ if(D3_COMPILER_IS_GCC_OR_CLANG) # set(ldflags ${ldflags} -fsanitize=address) endif() - if(NOT AROS) + if(NOT AROS AND NOT WIN32) CHECK_CXX_COMPILER_FLAG("-fvisibility=hidden" cxx_has_fvisibility) if(NOT cxx_has_fvisibility) message(FATAL_ERROR "Compiler does not support -fvisibility") @@ -220,6 +241,8 @@ if(D3_COMPILER_IS_GCC_OR_CLANG) add_compile_options(-fvisibility=hidden) endif() + add_compile_options(-Werror=format) + # TODO fix these warnings add_compile_options(-Wno-sign-compare) add_compile_options(-Wno-switch) @@ -233,9 +256,21 @@ if(D3_COMPILER_IS_GCC_OR_CLANG) add_compile_options(-Wno-class-memaccess) endif() + CHECK_CXX_COMPILER_FLAG("-Wdangling-reference" cxx_has_Wdangling_reference) + if(cxx_has_Wdangling_reference) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Werror=dangling-reference") + endif() + CHECK_CXX_COMPILER_FLAG("-Woverloaded-virtual" cxx_has_Woverload_virtual) if(cxx_has_Woverload_virtual) - add_compile_options(-Woverloaded-virtual) + add_compile_options(-Wno-overloaded-virtual) + endif() + + # ignore warnings about variables named "requires" for now (in C++20 it's a keyword, + # but currently we don't even use C++11 features) + CHECK_CXX_COMPILER_FLAG("-Wno-c++20-compat" cxx_has_Wno-cpp20-compat) + if(cxx_has_Wno-cpp20-compat) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-c++20-compat") endif() if(AROS) @@ -245,11 +280,11 @@ if(D3_COMPILER_IS_GCC_OR_CLANG) add_definitions(-DMACOS_X=1) if(cpu STREQUAL "x86_64") - add_compile_options(-arch x86_64 -mmacosx-version-min=10.6) - set(ldflags "${ldflags} -arch x86_64 -mmacosx-version-min=10.6") + add_compile_options(-arch x86_64 -mmacosx-version-min=10.7) + set(ldflags "${ldflags} -arch x86_64 -mmacosx-version-min=10.7") elseif(cpu STREQUAL "arm64") - add_compile_options(-arch arm64 -mmacosx-version-min=11.0) - set(ldflags "${ldflags} -arch arm64 -mmacosx-version-min=11.0") + add_compile_options(-arch arm64 -mmacosx-version-min=10.7) + set(ldflags "${ldflags} -arch arm64 -mmacosx-version-min=10.7") elseif(cpu STREQUAL "x86") CHECK_CXX_COMPILER_FLAG("-arch i386" cxx_has_arch_i386) if(cxx_has_arch_i386) diff --git a/README.md b/README.md index 63ff5ed7..c9cfb72d 100644 --- a/README.md +++ b/README.md @@ -1,37 +1,25 @@ -# dhewm3 Mod SDK - -This repository contains an SDK that can be used to create modifications ("mods") -for (or port Doom3 mods to) [dhewm3](https://dhewm3.org). - -It contains (mostly) the same source files as the original Doom3 SDK, but these -are taken from dhewm3 and **are licensed under GPLv3, not the SDK license**. -Another small difference is that this is built using [CMake](https://cmake.org/) -instead of SCons + VS Project files. -This means that you need CMake to build it, but don't worry, on Windows it can -create a Visual Studio Solution for you so you can program and compile with -Visual Studio like you might be used to. - -## Some ports of existing Mods - -This repository also contains ports of existing mods whichs authors released -the source under GPL; you can find these in their own branches: -[Blood Mod](https://github.com/dhewm/dhewm3-sdk/tree/bloodmod), -[Classic Doom 3](https://github.com/dhewm/dhewm3-sdk/tree/cdoom), -[Denton's Enhanced Doom3](https://github.com/dhewm/dhewm3-sdk/tree/dentonmod), -[Fitz Packerton](https://github.com/dhewm/dhewm3-sdk/tree/fitz), -[Hard Corps](https://github.com/dhewm/dhewm3-sdk/tree/hardcorps), -[Perfected Doom 3](https://github.com/dhewm/dhewm3-sdk/tree/perfected), -[Scarlet **Rivensin**: The Ruiner](https://github.com/dhewm/dhewm3-sdk/tree/rivensin), -[Doom3: The Lost Mission](https://github.com/dhewm/dhewm3-sdk/tree/d3le) and -[Sikkmod](https://github.com/dhewm/dhewm3-sdk/tree/sikkmod) - -In addition to this repository, there is also the LibreCoop mod that implements Coop -gameplay for dhewm3: [LibreCoop Github for the source code](https://github.com/Stradex/librecoop) -and [LibreCoop on ModDB which has the game data](https://www.moddb.com/mods/librecoop-dhewm3-coop). - -You can find Win32 DLLs and Linux x86_64 (amd64) `.so` libraries of those mods that work with -dhewm3 1.5.x at [the dhewm3 Github release page](https://github.com/dhewm/dhewm3/releases/latest) -(the dhewm3-mods-1.5.* archives). +# HeXen: Edge Of Chaos + +This is the source of HeXen: Edge Of Chaos (HEOC) game by HEOC Team. + +You can **download** the required mod **game data** at https://www.moddb.com/games/doom-iii/downloads/hexen-edge-of-chaos-dhewm3-edition + +The original source can be downloaded at https://www.moddb.com/mods/hexen-edge-of-chaos/downloads/hexen-edge-of-chaos-demo-source-patch +or https://github.com/LegendaryGuard/HeXen_Edge_Of_Chaos (Special thanks: LegendGuard). + +Reference (for more info): https://web.archive.org/web/20190803175542/http://hexenmod.net/about/ + +HeXen:Edge of Chaos is a free fan game, based on the original Hexen franchise that was developed by id Software and Raven Software. The game utilizes the id Tech 4 GPL engine that id Software released on November 22, 2011. Previously the project was a total conversion for Doom 3. + +Edge of Chaos was developed by a team of fans who had always loved the original Hexen since it came out. The team spent countless hours with the entire series. When id Software released Doom3, they saw immense potential in the engine itself; the 'Hell' maps gave them the inspiration to make a hack-and-slash game based, primarily, on the original Hexen. Their story was designed to tie into the expansion packs and also touch upon the Heretic narrative, aiming to capture the incredible atmosphere of those rich worlds and their darkling journeys. + +The project was planned for release in three episodes, each based on one character class. The only release, a demo excerpt, was based on the Cleric class. The second and third episodes, which remained unreleased, were intended to be based on the warrior and sorceress class respectively. The team had chosen to use an episodic approach in order to aim for a manageable release schedule. + +This was the grand vision for HEOC. It seems like the project is dead, but they released this demo in 2010 which can now be played with dhewm3. + +After a long time... + +**A monumental thank you to Zeroth for releasing this mod under GPL and to _LegendGuard_ for organizing this and doing the initial dhewm3 port!** ## How to build @@ -40,8 +28,9 @@ dhewm3 1.5.x at [the dhewm3 Github release page](https://github.com/dhewm/dhewm3 You need [CMake](https://cmake.org/) either Visual Studio (2010 and newer have been tested) or [MinGW-w64](https://mingw-w64.org/) 1. Clone the dhewm3-sdk git repo -2. (optional: switch to an existing mods branch: `git checkout dentonmod`) -3. create a **build directory** in your `dhewm3-sdk/` directory (`build/` or `build-dentonmod/` or whatever) +2. switch to your git clone's directory: `cd dhewm3-sdk` +2. (optional: switch to an existing mods branch: `git checkout eoc`) +3. create a **build directory** in your `dhewm3-sdk/` directory (`build/` or `build-eoc/` or whatever) 4. Start the CMake GUI 5. Select your `dhewm3-sdk/` folder for *"Where is the source code"* and your **build directory** from step 3 for *"Where to build the binaries"*. @@ -53,10 +42,10 @@ You need [CMake](https://cmake.org/) either Visual Studio (2010 and newer have b 8. Building: * If you're using **Visual Studio**, you should be able to just click `[Open Project]` to open the generated Project in Visual Studio. You can now compile the SDK in Visual Studio (and of course make your changes to the code). * (Untested:) For **MinGW**, open your MinGW or MSys shell, switch to your **build directory** and execute `make -j4` to build the game DLL -9. Now it's time to copy the DLL (e.g. `dentonmod.dll`) to your dhewm3 install, where base.dll and d3xp.dll are +9. Now it's time to copy the DLL (`eoc.dll`) to your dhewm3 install, where eoc.dll and d3xp.dll are - For **Visual Studio** the DLL should be in a subdirectory of your **build directory**, depending on the build type you selected in `build/Debug/` or `build/Release/` or similar - For **MinGW** the DLL should be directly in your **build directory**. -10. start the game with that mod, like `dhewm3 +set fs_game dentonmod` +10. start the game with that mod, like `dhewm3 +set fs_game eoc` (Make sure to actually have the mods game data in the right directory as well; the directory name should be the same as the game lib name, but without .dll, @@ -67,351 +56,111 @@ for example dentonmod/ for dentonmod.dll) On Linux and similar the following should work (if you have cmake, make and GCC/g++ installed): 1. Clone the dhewm3-sdk git repo 2. switch to your git clone's directory: `cd dhewm3-sdk` -3. (optional: switch to an existing mods branch: `git checkout dentonmod`) +3. (optional: switch to an existing mods branch: `git checkout eoc`) 4. create a build directory: `mkdir build` 5. switch to build directory: `cd build` 6. create Makefile with CMake: `cmake ..` - by default, this will create an *RelWithDebInfo* build, which is optimized but still has debug info, so it's somewhat debuggable. You can select another kind of build with `cmake -DCMAKE_BUILD_TYPE=Debug ..` for a Debug build with less optimization, which will make it easier to debug (but possibly slower). You could also replace "Debug" with "Release" for a proper optimized Release build without any Debug info. 7. compile the mod .so: `make -j4` -8. it (e.g. `dentonmod.so`) should now be in the build/ directory, - copy it to your dhewm3 install, where base.so and d3xp.so are -9. start the game with that mod, like `dhewm3 +set fs_game dentonmod` +8. it (e.g. `eoc.so`) should now be in the build/ directory, + copy it to your dhewm3 install, where eoc.so and d3xp.so are +9. start the game with that mod, like `dhewm3 +set fs_game eoc` (Make sure to actually have the mods game data in the right directory as well; the directory name should be the same as the game lib name, but without .so/.dylib, for example dentonmod/ for dentonmod.so) -## How to port a Mod to dhewm3 - -Please note that currently I only accept mods that are released under the -GPL license - the one used by Open Source Doom3 (i.e. *not* only the Doom3 SDK license) - -because neither the GPL nor the SDK license allow merging code from both licenses. -So please get permission from the mod authors first. - -The usual (easiest) way to port a mod is to make a diff between the mod's source -and the Doom3 SDK and apply the resulting patch to the vanilla game source (from the master branch). -Afterwards usually some manual work must be done to resolve patching conflicts and get the mod to compile. -Also, the CMakeLists.txt file must be adjusted (see the dentonmod branch for examples). - -Here is the approximate steps I use to port a mod. -Note that you'll need basic C++ (or at least C) programming skills, so you can resolve the little -(and sometimes not-so-little) issues that (almost) always occur when porting a mod, like merge -conflicts and compiler errors due to missing `#include`s. - -#### Getting the difference between the original Doom3 SDK and the Mod's source code - -IMHO, the easiest way is to use a git repo of the the origin Doom3 SDK source code, copy the modified -source from the mod on top and then let git create the diff. - -For your convenience, I created such a git repo: https://github.com/DanielGibson/Doom3-SDK - -So clone it and switch into its directory, by running the following commands in a terminal -(on Windows, use the "Git Bash"): -* `cd doom3dev/` (change into a directory you want to put Doom3 projects in, adjust this to your needs) -* `git clone https://github.com/DanielGibson/Doom3-SDK.git` -* `cd Doom3-SDK` - -Now copy the mod's source code to the correct place in the repo, usually `src/` or `src/game`, -replacing the existing files. -`git status` -shows which files have been changed, -`git diff` -will show the actual differences *(but only for files that already exist in the repo!)*; or use -`git gui` -for a GUI-based overview. - -It's possible that the copied files use different line endings than the git repo, in which case git -will show lots of changes that are really none. - -You can fix the line endings by running the following commands: -* `find -iname "*.cpp" -exec dos2unix {} \;` -* `find -iname "*.h" -exec dos2unix {} \;` - - *If you're on **Windows**, use `unix2dos` instead of `dos2unix`*. - -Now **create a branch** for the mod in your local Doom3-SDK repo and stage all the changes, -including added files: -* `git checkout -b mymodname` (adjust the name...) -* `git add --all` - -I recommend using `git gui` to check if any files have been added that are irrelevant, i.e. files -that are not source files but from build directories or in Visual Studio project files or similar -(those won't be needed for dhewm3-sdk, it uses CMake to handle the build). -You can *unstage* files or changes by clicking the file icon left of the filename -in the "Staged Changes" list to remove them from the commit (the files/changes are *not* deleted, -they will just not be committed). - -> If you insist on using the commandline, -> `git status` -> will also show the staged files, and you can unstage files with -> `git rm --cached path/to/file.name` -> or unstage whole directories with -> `git rm --cached -r path/to/` - -Now commit the changes, either on the commandline with -`git commit -m "imported mymodname"` -or by just typing a commit message in `git gui` and clicking "Commit" there. - -Finally, to get a diff patch you can apply to the dhewm3 SDK, run the following command: -`git diff main > ../mymodname.diff` - -#### Applying the mod diff to the dhewm3 SDK - -First, clone the dhewm3 SDK and create a branch for the new mod, with the following terminal commands -(again, on Windows, use the "Git Bash"): -* `cd doom3dev/` (same directory as used in the other step) -* `git clone https://github.com/dhewm/dhewm3-sdk.git` -* `cd dhewm3-sdk` -* `git checkout -b mymodname` - -> If you've already cloned the repo earlier, make sure to check out the `master` branch before -> creating the new branch for your mod, so the new branch is based on the unmodified gamecode, -> and make sure there are no uncommitted changes: -> * `git checkout master` -> * `git reset --hard HEAD` (undo all uncommitted changes) -> * `git pull` (get the latest changes from the dhewm3-sdk repo) - -Now apply the patch with the mod's code with: - -`patch -p2 -l --merge --no-backup-if-mismatch < ../mymodname.diff` - -Explanation: -* `-p2` skips an additional directory layer: In the Doom3 SDK the source `src/game/`, for example, - in the dhewm3 SDK it's directly in `game/`, so `src/` must be skipped -* `-l` ignore whitespace changes, in case the mod has replaced tabs with spaces or something -* `--merge` when there are merge conflicts (patch isn't sure how to apply a change to the dhewm3 SDK), - they are marked in the corresponding source files with sections containing the new code and the old - code, marked with `<<<<<<<`, `=======` and `>>>>>>>` (see below) -* `--no-backup-if-mismatch` if this is not set, patch will create `bla.cpp.orig` (containing the - original unpatched file) for every file that's patched, we don't want that - -Now look very carefully at the output the `patch` command printed to the terminal! -It often happens that some changes can't be merged automatically, and `patch will tell you about that` -like this: - -``` -patching file game/Misc.cpp -Hunk #1 NOT MERGED at 143-148. -``` - -This means that one change in game/Misc.cpp could not be merged automatically, so it must be merged manually. -`patch` then inserts something like this in that file: - - ``` - ... - switch( event ) { - case EVENT_TELEPORTPLAYER: { - entityNumber = msg.ReadBits( GENTITYNUM_BITS ); - idPlayer *player = static_cast( gameLocal.entities[entityNumber] ); - if ( player != NULL && player->IsType( idPlayer::Type ) ) { - Event_TeleportPlayer( player ); - } - return true; - } - default: - break; - } - <<<<<<< - - return idEntity::ClientReceiveEvent( event, time, msg ); - ======= - // return false; // sikk - warning C4702: unreachable code - >>>>>>> - } - ``` - -So open the file and search for "<<<<". -The first section (between `<<<<<<<` and `=======`) is the existing code of dhewm3-sdk, the second -section (between `=======` and `>>>>>>>`) is what that code looked like in the mod you're trying to -merge (in this example Sikkmod). - -> In this case, both Sikkmod and dhewm3 fixed a compiler warning: -> `return idEntity::ClientReceiveEvent( event, time, msg );` used to be in the `default:` case of switch, -> and after the switch was `return false;`, which was unreachable because the function would always -> return at `default: return idEntity::ClientReceiveEvent( event, time, msg );` (if it didn't already -> return before), and the compiler warned about that unreachable code. -> *sikk* got rid of that warning by commenting out `return false;`, in dhewm3 we moved the -> `return idEntity::ClientReceiveEvent( event, time, msg );` behind the switch-case - both valid -> (and equivalent) solutions. - -You need to remove the lines with `<<<<<<<` and `=======` and `>>>>>>>`, and make sure that the code that -was in this sections is merged completely, i.e. in a state that works correctly like it did in the mod. - -> In this example it's simple: Just keep dhewm3's code and remove sikk's change: -> ``` -> ... -> switch( event ) { -> case EVENT_TELEPORTPLAYER: { -> entityNumber = msg.ReadBits( GENTITYNUM_BITS ); -> idPlayer *player = static_cast( gameLocal.entities[entityNumber] ); -> if ( player != NULL && player->IsType( idPlayer::Type ) ) { -> Event_TeleportPlayer( player ); -> } -> return true; -> } -> default: -> break; -> } -> -> return idEntity::ClientReceiveEvent( event, time, msg ); -> } -> ``` - -Sometimes it makes sense to open the file from Doom3 SDK and the patched one from dhewm3 SDK -side-by-side to compare functions with merge conflicts to see more context from the original file. - -If you're *really* unlucky, `patch` will show you messages like -``` -patching file d3xp/Item.cpp -Hunk #1 merged at 653. -misordered hunks! output would be garbled -Hunk #2 FAILED at 77. -misordered hunks! output would be garbled -Hunk #3 FAILED at 109. -2 out of 4 hunks FAILED -- saving rejects to file d3xp/Item.cpp.rej - -``` -This means that it has no idea whatsoever where that code from the patch belongs, and it will *not* -create such a merge-conflict section in the file as shown above. In that case you'll have to check -the `.rej` files for what changes have been omitted and try to merge them manually. -In this case, even the first hunk (which was supported to be around line 50) was, for reasons unclear -to me, merged at the totally wrong location so it must be fixed as well. - -When merging the changes for a file fails completely, it can help to use a graphical diff and merge -tool like [meld](https://meldmerge.org/) or [kdiff3](https://kdiff3.sourceforge.net/) or -[Beyond Compare](https://www.scootersoftware.com/) to compare the file from the Doom3 SDK and the one -from the dhewm3 SDK and merge the changes in there. -However note that it will also show you differences that are unrelated to the mod, like fixes made -in dhewm3 - and the first lines of the file are always different, because in the dhewm3 SDK they -contain the [GPL license note from the Doom3 GPL release](https://github.com/dhewm/dhewm3-sdk/blob/master/d3xp/Item.cpp#L1-L27), -while in the original Doom3 SDK there usually is only a very short comment like -``` -// Copyright (C) 2004 Id Software, Inc. -// -``` -This is also the reason why it's easiest to create a diff in the Doom3 SDK and apply that diff -in the dhewm3 SDK (instead of using a merge tool on all files, for example): The diff only contains -the changes made in the SDK, so those copyright notices are not in the diff (unless the Mod author -changed those lines), and don't create merge conflicts in dhewm3 code. - -In my experience, most of the changes from the patch are merged without any conflict, and then you'll -have a handful of `Hunk #X NOT MERGED` errors that at least can be resolved within the file. -I've only ever seen the `Hunk #X FAILED` error in one project.. - -
-Anyway, when you think you've resolved all merge conflicts, you can make double-sure like this: - -`grep -r "<<<<" ` -and -`grep -r ">>>>"` - -both shouldn't find anything, at least not in .cpp or .h files. - -Once all merge conflicts are resolved, remove any `.rej` files and commit the changes, like described -above (or just use `git gui` for that). -Don't forget to also commit added source files, if any - in fact, remember which (source) files were -added, because they're needed in the next step! - -#### Build the Mod for dhewm3 - -Now edit `CMakeLists.txt`. - -If the mod only uses the code in `game/`, you can set the `ON` in `option(D3XP "Build the d3xp/ game code" ON)` -to `OFF`, if it only uses the code in `d3xp/`, you can do the same for `option(BASE ...` (if both are used, -i.e. the mod builds two DLLs, one for the base game and one for Resurrection of Evil, leave those options as they are). - -Make sure to adjust `BASE_NAME` and/or `D3XP_NAME` according to the mod directory name, for example, -the *Classic Doom 3* mod directory is called `cdoom` and it uses the source code in `game/`, so -the line is adjusted like `set(BASE_NAME "cdoom" CACHE STRING "Name of the mod...")`. - -If the mod requires definitions passed to the compiler (like `-DMY_OPTION` for `#ifdef MY_OPTION`), -adjust `BASE_DEFS` and/or `D3XP_DEFS` accordingly. - -Last but not least, if the mod adds any source files to the SDK (instead of just modifying the -existing ones), add them to `src_game_mod` or `src_d3xp_mod`. - -Look at the -[CMakeLists.txt of the Rivensen Mod](https://github.com/dhewm/dhewm3-sdk/blob/rivensin/CMakeLists.txt) -for an example that does several of the things mentioned above (disable D3XP DLL, set custom compiler -definitions, add custom source files). - -Once that's done, you can finally try to build the mod, as described in the [How to build section](#how-to-build). - -You'll likely get compiler errors because of missing includes, or maybe there's -`#include "../idlib/precompiled.h"` or similar somewhere which is an error because dhewm3 doesn't -have `precompiled.h` (so remove that). It's usually best to scroll up to the first compiler error -and fix it (for example, if it complains that unknown type is used, that type is likely defined in -a header that must be included) and retry building, because often further errors are caused by the -first one, and fixing it fixes several others as well, so by building again after fixing the first -you'll see which errors remain. - -### Getting in touch - -If you are a mod author and want to release your mod's sourcecode under GPL, -but don't want to port it yourself (or don't have time or are unsure how) -please contact me, I can probably help you :-) - -The easiest way to contact me is by creating an issue in this Github repository, -or by sending a DM to *caedes* in the [id Tech Forums](http://idtechforums.fuzzylogicinc.com/) -or by pinging *caedes* in the #iodoom3 IRC channel on FreeNode. -If you prefer E-Mail, you can find my address in the [git commits](https://github.com/dhewm/dhewm3-sdk/commit/b7d77c468a42892fa3c03a9ce0683916a110e8db.patch). - -## New features that mods can use - -dhewm3 has some features that the original Doom3 didn't have that are interesting for Mods. - -### The Script Debugger - -dhewm3 1.5.2 and newer contain the Script Debugger (that you may remember from Quake4). -While most of the code for it is in the engine, the game code also needs some small changes for it to work. - -The [debugger branch](https://github.com/dhewm/dhewm3-sdk/tree/debugger) -has the necessary changes in its last commit. - -### Injecting all supported resolutions into the video menu - -Mods that have their own video settings menu can tell dhewm3 to replace -the "choices" and "values" entries in their choiceDef with the -resolutions supported by dhewm3 (and corresponding modes). -So if we add new video modes to dhewm3, they'll automatically appear in -the menu without changing the .gui -To enable this, you only need to add a `injectResolutions 1` -entry to the resolution choiceDef. By default, the first entry will -be "r_custom*" for r_mode -1, which means "custom resolution, use -r_customWidth and r_customHeight". -If the "r_custom*" entry should be disabled for your mod, just add another entry: -`injectCustomResolutionMode 0` - -### Scale GUIs to 4:3 - -Fullscreen menus (like the main menu and the PDA) are scaled to 4:3 by default, adding black bars on the left/right when using widescreen resolutions (users can disable this with `r_scaleMenusTo43 0`). -By default, this is not done for other GUIs, mainly because the HUD is a fullscreen GUI and also handles damage effects (coloring the whole screen red), which would look shitty if scaled to 4:3 with black/empty bars on the left/right. -However, you can still enable this for your WIN_DESKTOP GUIs, either in the .gui itself or via C++ code when loading the GUI (I found this especially useful for the crosshairs/cursor GUI). - -WIN_DESKTOP means that this can currently only be set for the top-level window in a .gui (all its subwindows/widgets will be scaled implicitly). +## License + +The code changes of HEOC have been released under GPLv2 or later (by Brandon Captain (AKA Zeroth)). As the Doom 3 source code (that the dhewm3 SDK is based on) was released under GPLv3 or later, that's the effective license of this code. + +## Credits + +### The Team + +**Andreas HÃ¥ndlykken (AKA Slash)** +Lead Concept Artist, Texture Artist, Modeler, Pirate Captain + +**Bas de Zwart (AKA BloodRayne)** +Project Founder. Models, Textures, Weapons, Scripting + +**Brandon Captain (AKA Zeroth)** +SDK Programmer, Scripting, SVN/Server host and Admin + +**Caffeine Freak** +Lead Level Designer. All of HUB 1 - H1M1(detailing/scripting), H1M2(All), H1M3(All) + +**Calavera_Jo** +Coder + +**Deadite4** +Sound FX/VO Engineer, Handyman, Coordinator, PR + +**Justin Lassen** +Composer, All Music + +**Mystic Realmz** +Composer + +**Parsonsbear** +Pickup and Map Models/Textures + +**Shadow** +Level Designer + +**Solarsplace** +Coder + +**Spookt** +Writer. Story Arc, Journals + +**TomJ** +Level Designer, Hub 2 (H2M1(future release)) + +
+ +### Ocassional Contributors + +**BHenderson** +Animations + +
+ +### Past Members + +**CodenniumRed** +Level Designer, H1M1 Layout + +**Gazz** +Level Designer, H1M3(1st initial block out) + +**Gman** +Static Models + +**RazorBladder** +Lead Texture Artist. Models, Textures + +**Ruckus** +Map Models + +**Slyrr** +Lead Creature Models, Lead Animations + +**Zurra** +Models, Textures + +
+ +### Testers -There are two ways to make a GUI use this: -1. in the **.gui** add a window variable `scaleto43 1`, like - ``` - windowDef Desktop { - rect 0 ,0 ,640 ,480 - nocursor 1 - float talk 0 +**Indoril Lichenvale** +**JC316** +**RaVeN** +**Redeemer812** - scaleto43 1 +
- // .. etc rest of windowDef - ``` +### Special Thanks -2. When creating the GUI from **C++ code**, you can afterwards make the - UserInterface scale to 4:3 like this: - ```c++ - idUserInterface* ui = Whatever(); // create it - ui->SetStateBool("scaleto43", true); - ui->StateChanged(gameLocal.time); - ``` - Both lines are important! - - Keep in mind that if the GUI is saved to the savegame, you need to call this after restoring the GUI from the savegame, - see https://github.com/dhewm/dhewm3-sdk/commit/5070b8c7ec6f3a8ba1cb4123de37732f9cd9437f for an example. - - Also note that you can *not* generally inject variables into GUIs like that from C++, dhewm3 has special code to make the `"scaleto43"` case work. +**LegendGuard** +Who ported to dhewm3 diff --git a/d3xp/Camera.cpp b/d3xp/Camera.cpp index dedba94f..0e8d410a 100644 --- a/d3xp/Camera.cpp +++ b/d3xp/Camera.cpp @@ -516,7 +516,7 @@ void idCameraAnim::Think( void ) { if ( frameRate == USERCMD_HZ ) { frameTime = gameLocal.time - starttime; - frame = frameTime / gameLocal.msec; + frame = frameTime / gameLocal.msecPrecise; } else { frameTime = ( gameLocal.time - starttime ) * frameRate; frame = frameTime / 1000; @@ -570,7 +570,7 @@ void idCameraAnim::GetViewParms( renderView_t *view ) { if ( frameRate == USERCMD_HZ ) { frameTime = gameLocal.time - starttime; - frame = frameTime / gameLocal.msec; + frame = frameTime / gameLocal.msecPrecise; lerp = 0.0f; } else { frameTime = ( gameLocal.time - starttime ) * frameRate; diff --git a/d3xp/Entity.h b/d3xp/Entity.h index 4413c303..950bbace 100644 --- a/d3xp/Entity.h +++ b/d3xp/Entity.h @@ -605,10 +605,10 @@ ID_INLINE void SetTimeState::PushState( int timeGroup ) { // set correct time if ( fast ) { - gameLocal.fast.Get( gameLocal.time, gameLocal.previousTime, gameLocal.msec, gameLocal.framenum, gameLocal.realClientTime ); + gameLocal.fast.Get( gameLocal.time, gameLocal.previousTime, gameLocal.msec, gameLocal.framenum, gameLocal.realClientTime, gameLocal.msecPrecise ); } else { - gameLocal.slow.Get( gameLocal.time, gameLocal.previousTime, gameLocal.msec, gameLocal.framenum, gameLocal.realClientTime ); + gameLocal.slow.Get( gameLocal.time, gameLocal.previousTime, gameLocal.msec, gameLocal.framenum, gameLocal.realClientTime, gameLocal.msecPrecise ); } } } @@ -617,10 +617,10 @@ ID_INLINE SetTimeState::~SetTimeState() { if ( activated && !gameLocal.isMultiplayer ) { // set previous correct time if ( previousFast ) { - gameLocal.fast.Get( gameLocal.time, gameLocal.previousTime, gameLocal.msec, gameLocal.framenum, gameLocal.realClientTime ); + gameLocal.fast.Get( gameLocal.time, gameLocal.previousTime, gameLocal.msec, gameLocal.framenum, gameLocal.realClientTime, gameLocal.msecPrecise ); } else { - gameLocal.slow.Get( gameLocal.time, gameLocal.previousTime, gameLocal.msec, gameLocal.framenum, gameLocal.realClientTime ); + gameLocal.slow.Get( gameLocal.time, gameLocal.previousTime, gameLocal.msec, gameLocal.framenum, gameLocal.realClientTime, gameLocal.msecPrecise ); } } } diff --git a/d3xp/Game_local.cpp b/d3xp/Game_local.cpp index 85c4bb72..13c550bd 100644 --- a/d3xp/Game_local.cpp +++ b/d3xp/Game_local.cpp @@ -58,6 +58,8 @@ const idVec3 DEFAULT_GRAVITY_VEC3( 0, 0, -DEFAULT_GRAVITY ); const int CINEMATIC_SKIP_DELAY = SEC2MS( 2.0f ); +const float USERCMD_MSEC_PRECISE = 1000.0f/60.0f; + #ifdef GAME_DLL idSys * sys = NULL; @@ -1554,14 +1556,20 @@ bool idGameLocal::InitFromSaveGame( const char *mapName, idRenderWorld *renderWo if ( gameSoundWorld ) { gameSoundWorld->SetSlowmo( false ); } + // DG: not saving/restoring msecPrecise to avoid breaking savegames + // if slowmo is off, this is the value msec(Precise) should have + msecPrecise = USERCMD_MSEC_PRECISE; } else { if ( gameSoundWorld ) { gameSoundWorld->SetSlowmo( true ); } + // DG: msec(Precise) should be set frequently anyway, so using the approximate msec + // in the slowmo-case should be good enough (I know, famous last words etc) + msecPrecise = msec; } if ( gameSoundWorld ) { - gameSoundWorld->SetSlowmoSpeed( slowmoMsec / (float)USERCMD_MSEC ); + gameSoundWorld->SetSlowmoSpeed( slowmoMsec / USERCMD_MSEC_PRECISE ); // DG: slowmoMsec now is precise } #endif @@ -2414,7 +2422,7 @@ void idGameLocal::RunTimeGroup2( int msec_fast ) { // dezo2/DG: added argument f int num = 0; fast.Increment( msec_fast ); - fast.Get( time, previousTime, msec, framenum, realClientTime ); + fast.Get( time, previousTime, msec, framenum, realClientTime, msecPrecise ); for( ent = activeEntities.Next(); ent != NULL; ent = ent->activeNode.Next() ) { if ( ent->timeGroup != TIME_GROUP2 ) { @@ -2425,7 +2433,7 @@ void idGameLocal::RunTimeGroup2( int msec_fast ) { // dezo2/DG: added argument f num++; } - slow.Get( time, previousTime, msec, framenum, realClientTime ); + slow.Get( time, previousTime, msec, framenum, realClientTime, msecPrecise ); } #endif @@ -2464,8 +2472,9 @@ gameReturn_t idGameLocal::RunFrame( const usercmd_t *clientCmds ) { #ifdef _D3XP ComputeSlowMsec(); - slow.Get( time, previousTime, msec, framenum, realClientTime ); + slow.Get( time, previousTime, msec, framenum, realClientTime, msecPrecise ); msec = slowmoMsec; + msecPrecise = slowmoMsec; #endif if ( !isMultiplayer && g_stopTime.GetBool() ) { @@ -2485,14 +2494,16 @@ gameReturn_t idGameLocal::RunFrame( const usercmd_t *clientCmds ) { // dezo2/DG: recalculate each frame, see comment at CalcMSec() int msec_fast = CalcMSec( framenum ); - if ( slowmoState == SLOWMO_STATE_OFF ) + if ( slowmoState == SLOWMO_STATE_OFF ) { msec = msec_fast; + msecPrecise = USERCMD_MSEC_PRECISE; + } time += msec; realClientTime = time; #ifdef _D3XP - slow.Set( time, previousTime, msec, framenum, realClientTime ); + slow.Set( time, previousTime, msec, framenum, realClientTime, msecPrecise ); #endif #ifdef GAME_DLL @@ -2612,9 +2623,9 @@ gameReturn_t idGameLocal::RunFrame( const usercmd_t *clientCmds ) { #ifdef _D3XP // service pending fast events - fast.Get( time, previousTime, msec, framenum, realClientTime ); + fast.Get( time, previousTime, msec, framenum, realClientTime, msecPrecise ); idEvent::ServiceFastEvents(); - slow.Get( time, previousTime, msec, framenum, realClientTime ); + slow.Get( time, previousTime, msec, framenum, realClientTime, msecPrecise ); #endif timer_events.Stop(); @@ -4826,9 +4837,9 @@ idGameLocal::SelectTimeGroup */ void idGameLocal::SelectTimeGroup( int timeGroup ) { if ( timeGroup ) { - fast.Get( time, previousTime, msec, framenum, realClientTime ); + fast.Get( time, previousTime, msec, framenum, realClientTime, msecPrecise ); } else { - slow.Get( time, previousTime, msec, framenum, realClientTime ); + slow.Get( time, previousTime, msec, framenum, realClientTime, msecPrecise ); } } @@ -4878,7 +4889,7 @@ void idGameLocal::ComputeSlowMsec() { // stop the state slowmoState = SLOWMO_STATE_OFF; - slowmoMsec = USERCMD_MSEC; + slowmoMsec = USERCMD_MSEC_PRECISE; // DG: slowmoMsec now is precise } // check the player state @@ -4896,10 +4907,10 @@ void idGameLocal::ComputeSlowMsec() { if ( powerupOn && slowmoState == SLOWMO_STATE_OFF ) { slowmoState = SLOWMO_STATE_RAMPUP; - slowmoMsec = msec; + slowmoMsec = msecPrecise; if ( gameSoundWorld ) { gameSoundWorld->SetSlowmo( true ); - gameSoundWorld->SetSlowmoSpeed( slowmoMsec / (float)USERCMD_MSEC ); + gameSoundWorld->SetSlowmoSpeed( slowmoMsec / USERCMD_MSEC_PRECISE ); // DG: slowmoMsec now is precise } } else if ( !powerupOn && slowmoState == SLOWMO_STATE_ON ) { @@ -4911,12 +4922,15 @@ void idGameLocal::ComputeSlowMsec() { } } + // DG: for more precision in slowmo timing + static const float quarterFrameTime = USERCMD_MSEC_PRECISE * 0.25; + // do any necessary ramping if ( slowmoState == SLOWMO_STATE_RAMPUP ) { - delta = 4 - slowmoMsec; + delta = quarterFrameTime - slowmoMsec; // DG: slowmoMsec now is precise if ( fabs( delta ) < g_slowmoStepRate.GetFloat() ) { - slowmoMsec = 4; + slowmoMsec = quarterFrameTime; // DG: slowmoMsec now is precise slowmoState = SLOWMO_STATE_ON; } else { @@ -4924,14 +4938,14 @@ void idGameLocal::ComputeSlowMsec() { } if ( gameSoundWorld ) { - gameSoundWorld->SetSlowmoSpeed( slowmoMsec / (float)USERCMD_MSEC ); + gameSoundWorld->SetSlowmoSpeed( slowmoMsec / USERCMD_MSEC_PRECISE ); // DG: slowmoMsec now is precise } } else if ( slowmoState == SLOWMO_STATE_RAMPDOWN ) { - delta = 16 - slowmoMsec; + delta = USERCMD_MSEC_PRECISE - slowmoMsec; // DG: slowmoMsec now is precise if ( fabs( delta ) < g_slowmoStepRate.GetFloat() ) { - slowmoMsec = 16; + slowmoMsec = USERCMD_MSEC_PRECISE; // DG: slowmoMsec now is precise slowmoState = SLOWMO_STATE_OFF; if ( gameSoundWorld ) { gameSoundWorld->SetSlowmo( false ); @@ -4942,7 +4956,7 @@ void idGameLocal::ComputeSlowMsec() { } if ( gameSoundWorld ) { - gameSoundWorld->SetSlowmoSpeed( slowmoMsec / (float)USERCMD_MSEC ); + gameSoundWorld->SetSlowmoSpeed( slowmoMsec / USERCMD_MSEC_PRECISE ); // DG: slowmoMsec now is precise } } } @@ -4954,18 +4968,21 @@ idGameLocal::ResetSlowTimeVars */ void idGameLocal::ResetSlowTimeVars() { msec = USERCMD_MSEC; - slowmoMsec = USERCMD_MSEC; + msecPrecise = USERCMD_MSEC_PRECISE; + slowmoMsec = USERCMD_MSEC_PRECISE; // DG: slowmoMsec now is precise slowmoState = SLOWMO_STATE_OFF; fast.framenum = 0; fast.previousTime = 0; fast.time = 0; fast.msec = USERCMD_MSEC; + fast.msecPrecise = USERCMD_MSEC_PRECISE; slow.framenum = 0; slow.previousTime = 0; slow.time = 0; slow.msec = USERCMD_MSEC; + fast.msecPrecise = USERCMD_MSEC_PRECISE; } /* diff --git a/d3xp/Game_local.h b/d3xp/Game_local.h index 9031dda4..663d959e 100644 --- a/d3xp/Game_local.h +++ b/d3xp/Game_local.h @@ -89,6 +89,9 @@ extern idRenderWorld * gameRenderWorld; extern idSoundWorld * gameSoundWorld; extern const int NUM_RENDER_PORTAL_BITS; + +// DG: USERCMD_MSEC is 16, USERCMD_MSEC_PRECISE is a float and 16.6666.. +extern const float USERCMD_MSEC_PRECISE; /* =============================================================================== @@ -226,13 +229,25 @@ struct timeState_t { int time; int previousTime; int msec; + float msecPrecise; // DG: added for smoother timing int framenum; int realClientTime; - void Set( int t, int pt, int ms, int f, int rct ) { time = t; previousTime = pt; msec = ms; framenum = f; realClientTime = rct; }; - void Get( int& t, int& pt, int& ms, int& f, int& rct ) { t = time; pt = previousTime; ms = msec; f = framenum; rct = realClientTime; }; + void Set( int t, int pt, int ms, int f, int rct, float msp ) { time = t; previousTime = pt; msec = ms; framenum = f; realClientTime = rct; msecPrecise = msp; }; + void Get( int& t, int& pt, int& ms, int& f, int& rct, float& msp ) { t = time; pt = previousTime; ms = msec; f = framenum; rct = realClientTime; msp = msecPrecise; }; void Save( idSaveGame *savefile ) const { savefile->WriteInt( time ); savefile->WriteInt( previousTime ); savefile->WriteInt( msec ); savefile->WriteInt( framenum ); savefile->WriteInt( realClientTime ); } - void Restore( idRestoreGame *savefile ) { savefile->ReadInt( time ); savefile->ReadInt( previousTime ); savefile->ReadInt( msec ); savefile->ReadInt( framenum ); savefile->ReadInt( realClientTime ); } + void Restore( idRestoreGame *savefile ) { // DG: only functional change to Restore(): setting msecPrecise + savefile->ReadInt( time ); savefile->ReadInt( previousTime ); + savefile->ReadInt( msec ); savefile->ReadInt( framenum ); + savefile->ReadInt( realClientTime ); + if ( msec == 16 || msec == 17 ) { + msecPrecise = USERCMD_MSEC_PRECISE; + } else { + // if we're in some slowmo state, the approximate msec has to suffice, + // it'll be set to the proper value again soon anyway. + msecPrecise = msec; + } + } void Increment(int _msec) { // dezo2/DG: update msec (sometimes it's 16, sometimes 17) framenum++; previousTime = time; @@ -306,6 +321,12 @@ class idGameLocal : public idGame { int time; // in msec int msec; // time since last update in milliseconds + // DG: unlike msec (which is varies by +/-1 each frame, see CalcMSec()), + // msecPrecise remains constant (unless scaled for slowmo) and is not rounded down to an integer, + // so it can be used when the correct time for multiple frames must be calculated, + // or when setting an int-timer for next frame (where it rounds down which is safe for that case) + float msecPrecise; + int vacuumAreaNum; // -1 if level doesn't have any outside areas gameType_t gameType; diff --git a/d3xp/Game_network.cpp b/d3xp/Game_network.cpp index 1fd70234..b195cad6 100644 --- a/d3xp/Game_network.cpp +++ b/d3xp/Game_network.cpp @@ -1543,8 +1543,8 @@ gameReturn_t idGameLocal::ClientPrediction( int clientNum, const usercmd_t *clie } #ifdef _D3XP - slow.Set( time, previousTime, msec, framenum, realClientTime ); - fast.Set( time, previousTime, msec, framenum, realClientTime ); + slow.Set( time, previousTime, msec, framenum, realClientTime, msecPrecise ); + fast.Set( time, previousTime, msec, framenum, realClientTime, msecPrecise ); #endif // set the user commands for this frame diff --git a/d3xp/Player.cpp b/d3xp/Player.cpp index 88048dda..b5619be1 100644 --- a/d3xp/Player.cpp +++ b/d3xp/Player.cpp @@ -5757,7 +5757,7 @@ void idPlayer::BobCycle( const idVec3 &pushVelocity ) { // check for footstep / splash sounds old = bobCycle; - bobCycle = (int)( old + bobmove * gameLocal.msec ) & 255; + bobCycle = (int)( old + bobmove * gameLocal.msecPrecise ) & 255; bobFoot = ( bobCycle & 128 ) >> 7; bobfracsin = idMath::Fabs( sin( ( bobCycle & 127 ) / 127.0 * idMath::PI ) ); } diff --git a/d3xp/PlayerView.cpp b/d3xp/PlayerView.cpp index b82f6235..c963a206 100644 --- a/d3xp/PlayerView.cpp +++ b/d3xp/PlayerView.cpp @@ -307,7 +307,8 @@ void idPlayerView::DamageImpulse( idVec3 localKickDir, const idDict *damageDef ) if ( blobTime ) { screenBlob_t *blob = GetScreenBlob(); blob->startFadeTime = gameLocal.slow.time; - blob->finishTime = gameLocal.slow.time + blobTime * g_blobTime.GetFloat() * ((float)gameLocal.msec / USERCMD_MSEC); + // DG: use precise timing because gameLocal.msec varies by +/- 1 each frame + blob->finishTime = gameLocal.slow.time + blobTime * g_blobTime.GetFloat() * ( gameLocal.msecPrecise / USERCMD_MSEC_PRECISE ); const char *materialName = damageDef->GetString( "mtr_blob" ); blob->material = declManager->FindMaterial( materialName ); diff --git a/d3xp/ai/AI_pathing.cpp b/d3xp/ai/AI_pathing.cpp index a57a24ef..0fdc63ee 100644 --- a/d3xp/ai/AI_pathing.cpp +++ b/d3xp/ai/AI_pathing.cpp @@ -904,7 +904,11 @@ bool FindOptimalPath( const pathNode_t *root, const obstacle_t *obstacles, int n } if ( !pathToGoalExists ) { - seekPos.ToVec2() = root->children[0]->pos; + if ( root->children[0] != NULL ) { + seekPos.ToVec2() = root->children[0]->pos; + } else { + seekPos.ToVec2() = root->pos; + } } else if ( !optimizedPathCalculated ) { OptimizePath( root, bestNode, obstacles, numObstacles, optimizedPath ); seekPos.ToVec2() = optimizedPath[1]; diff --git a/d3xp/physics/Clip.cpp b/d3xp/physics/Clip.cpp index f1fb1236..1d3b0f73 100644 --- a/d3xp/physics/Clip.cpp +++ b/d3xp/physics/Clip.cpp @@ -965,6 +965,14 @@ idClip::TestHugeTranslation */ ID_INLINE bool TestHugeTranslation( trace_t &results, const idClipModel *mdl, const idVec3 &start, const idVec3 &end, const idMat3 &trmAxis ) { if ( mdl != NULL && ( end - start ).LengthSqr() > Square( CM_MAX_TRACE_DIST ) ) { +#ifndef CTF + // May be important: This occurs in CTF when a player connects and spawns + // in the PVS of a player that has a flag that is spawning the idMoveableItem + // "nuggets". The error seems benign and the assert was getting in the way + // of testing. + // assert( 0 ); DG: this was annoying and not really necessary, a Warning should suffice. +#endif + results.fraction = 0.0f; results.endpos = start; results.endAxis = trmAxis; @@ -972,19 +980,12 @@ ID_INLINE bool TestHugeTranslation( trace_t &results, const idClipModel *mdl, co results.c.point = start; if ( mdl->GetEntity() ) { - gameLocal.Printf( "huge translation for clip model %d on entity %d '%s'\n", mdl->GetId(), mdl->GetEntity()->entityNumber, mdl->GetEntity()->GetName() ); + gameLocal.Warning( "huge translation for clip model %d on entity %d '%s'\n", mdl->GetId(), mdl->GetEntity()->entityNumber, mdl->GetEntity()->GetName() ); } else { - gameLocal.Printf( "huge translation for clip model %d\n", mdl->GetId() ); + gameLocal.Warning( "huge translation for clip model %d\n", mdl->GetId() ); } - gameLocal.Printf( " from (%.2f %.2f %.2f) to (%.2f %.2f %.2f)\n", start.x, start.y, start.z, end.x, end.y, end.z); + gameLocal.Warning( " from (%.2f %.2f %.2f) to (%.2f %.2f %.2f)\n", start.x, start.y, start.z, end.x, end.y, end.z); -#ifndef CTF - // May be important: This occurs in CTF when a player connects and spawns - // in the PVS of a player that has a flag that is spawning the idMoveableItem - // "nuggets". The error seems benign and the assert was getting in the way - // of testing. - assert( 0 ); -#endif return true; } return false; diff --git a/framework/UsercmdGen.h b/framework/UsercmdGen.h index 17e88fe8..ecaa5a46 100644 --- a/framework/UsercmdGen.h +++ b/framework/UsercmdGen.h @@ -46,6 +46,7 @@ const int BUTTON_RUN = BIT(1); const int BUTTON_ZOOM = BIT(2); const int BUTTON_SCORES = BIT(3); const int BUTTON_MLOOK = BIT(4); +const int BUTTON_ATTACK2 = BIT(5); // Zeroth404 const int BUTTON_5 = BIT(5); const int BUTTON_6 = BIT(6); const int BUTTON_7 = BIT(7); @@ -82,6 +83,12 @@ const int IMPULSE_27 = 27; // const int IMPULSE_28 = 28; // vote yes const int IMPULSE_29 = 29; // vote no const int IMPULSE_40 = 40; // use vehicle +// Zeroth +const int IMPULSE_41 = 41; // hec Inventory Scroll Right +const int IMPULSE_42 = 42; // hec Inventory Scroll Left +const int IMPULSE_43 = 43; // hec use selected item +const int IMPULSE_44 = 44; // hec drop selected item +const int IMPULSE_45 = 45; // toggle automap // usercmd_t->flags const int UCF_IMPULSE_SEQUENCE = 0x0001; // toggled every time an impulse command is sent diff --git a/game/AFEntity.cpp b/game/AFEntity.cpp index 686263fc..22a4e711 100644 --- a/game/AFEntity.cpp +++ b/game/AFEntity.cpp @@ -377,10 +377,10 @@ Pass damage to body at the bindjoint ============ */ void idAFAttachment::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, - const char *damageDefName, const float damageScale, const int location ) { + const char *damageDefName, const float damageScale, const int location, const idVec3 &iPoint ) { if ( body ) { - body->Damage( inflictor, attacker, dir, damageDefName, damageScale, attachJoint ); + body->Damage( inflictor, attacker, dir, damageDefName, damageScale, attachJoint, iPoint ); } } @@ -1087,11 +1087,11 @@ void idAFEntity_Gibbable::Present( void ) { idAFEntity_Gibbable::Damage ================ */ -void idAFEntity_Gibbable::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ) { +void idAFEntity_Gibbable::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location, idVec3 &iPoint ) { if ( !fl.takedamage ) { return; } - idAFEntity_Base::Damage( inflictor, attacker, dir, damageDefName, damageScale, location ); + idAFEntity_Base::Damage( inflictor, attacker, dir, damageDefName, damageScale, location, iPoint ); if ( health < -20 && spawnArgs.GetBool( "gib" ) ) { Gib( dir, damageDefName ); } diff --git a/game/AFEntity.h b/game/AFEntity.h index bc7dee49..1132e058 100644 --- a/game/AFEntity.h +++ b/game/AFEntity.h @@ -123,7 +123,7 @@ class idAFAttachment : public idAnimatedEntity { virtual void ApplyImpulse( idEntity *ent, int id, const idVec3 &point, const idVec3 &impulse ); virtual void AddForce( idEntity *ent, int id, const idVec3 &point, const idVec3 &force ); - virtual void Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ); + virtual void Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location, const idVec3 &iPoint ); virtual void AddDamageEffect( const trace_t &collision, const idVec3 &velocity, const char *damageDefName ); void SetCombatModel( void ); @@ -225,7 +225,7 @@ class idAFEntity_Gibbable : public idAFEntity_Base { void Save( idSaveGame *savefile ) const; void Restore( idRestoreGame *savefile ); virtual void Present( void ); - virtual void Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ); + virtual void Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location, idVec3 &iPoint ); virtual void SpawnGibs( const idVec3 &dir, const char *damageDefName ); protected: diff --git a/game/Actor.cpp b/game/Actor.cpp index 15061a29..c512d815 100644 --- a/game/Actor.cpp +++ b/game/Actor.cpp @@ -35,7 +35,7 @@ If you have questions concerning this license or the applicable additional terms #include "WorldSpawn.h" #include "Actor.h" - +#include "Trigger.h" /*********************************************************************** @@ -1825,6 +1825,22 @@ idActor *idActor::ClosestEnemyToPoint( const idVec3 &pos ) { return bestEnt; } +// HEXEN : Zeroth +bool idActor::IsEnemy( idEntity *test ) { + idActor *ent; + for( ent = enemyList.Next(); ent != NULL; ent = ent->enemyNode.Next() ) { + if ( ent->fl.hidden ) { + continue; + } + gameLocal.Printf(" [%s] - [%s]", ent->name.c_str(), test->name.c_str()); + if ( ent->name.c_str() == test->name.c_str() ) { + return true; + } + } + + return false; +} + /* ================ idActor::EnemyWithMostHealth @@ -2158,7 +2174,7 @@ calls Damage() ============ */ void idActor::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, - const char *damageDefName, const float damageScale, const int location ) { + const char *damageDefName, const float damageScale, const int location, const idVec3 &iPoint ) { if ( !fl.takedamage ) { return; } @@ -2258,10 +2274,26 @@ bool idActor::Pain( idEntity *inflictor, idEntity *attacker, int damage, const i return false; } - // set the pain anim idStr damageGroup = GetDamageGroup( location ); painAnim = ""; + + if ( g_debugDamage.GetBool() ) { + gameLocal.Printf( "Damage: joint: '%s', zone '%s', anim '%s'\n", animator.GetJointName( ( jointHandle_t )location ), + damageGroup.c_str(), painAnim.c_str() ); + } + + // HEXEN : Zeroth: don't do pain anims for trigger types + if ( inflictor ) { + if ( inflictor->IsType( idTrigger_Hurt::Type ) ) { + return false; + } + if ( inflictor->IsType( idTrigger_Touch::Type ) ) { + return false; + } + } + + // set the pain anim if ( animPrefix.Length() ) { if ( damageGroup.Length() && ( damageGroup != "legs" ) ) { sprintf( painAnim, "%s_pain_%s", animPrefix.c_str(), damageGroup.c_str() ); @@ -2293,11 +2325,6 @@ bool idActor::Pain( idEntity *inflictor, idEntity *attacker, int damage, const i painAnim = "pain"; } - if ( g_debugDamage.GetBool() ) { - gameLocal.Printf( "Damage: joint: '%s', zone '%s', anim '%s'\n", animator.GetJointName( ( jointHandle_t )location ), - damageGroup.c_str(), painAnim.c_str() ); - } - return true; } @@ -2403,11 +2430,49 @@ idActor::Event_EnableEyeFocus void idActor::PlayFootStepSound( void ) { const char *sound = NULL; const idMaterial *material; + idPlayer *player; if ( !GetPhysics()->HasGroundContacts() ) { return; } + // HEXEN : Zeroth + if ( this->IsType( idPlayer::Type ) ) { + player = static_cast< idPlayer* >( this ); + + // no footstep sounds when flying (wings of wrath) + if ( player->FreeMove ) { + return; + } + + if ( player->inWater ) { + waterLevel_t level = static_cast< idPhysics_Player* >( player->GetPlayerPhysics() )->GetWaterLevel(); + + if ( level == WATERLEVEL_FEET ) { + sound = spawnArgs.GetString( "snd_footstep_water_feet" ); + } else if ( level == WATERLEVEL_WAIST ) { + sound = spawnArgs.GetString( "snd_footstep_water_waist" ); + } else if ( level == WATERLEVEL_HEAD ) { + sound = spawnArgs.GetString( "snd_footstep_water_head" ); + } else{ + sound = spawnArgs.GetString( "snd_footstep_water_feet" ); + } + + if ( *sound != '\0' ) { + StartSoundShader( declManager->FindSound( sound ), SND_CHANNEL_BODY, SSF_NO_DUPS, false, NULL ); + } + + return; + } else if ( player->leftWater != 0 && gameLocal.time < player->leftWater + 3000 ) { + sound = spawnArgs.GetString( "snd_footstep_wet" ); + + if ( *sound != '\0' ) { + StartSoundShader( declManager->FindSound( sound ), SND_CHANNEL_BODY, SSF_NO_DUPS, false, NULL ); + } + return; + } + } + // start footstep sound based on material type material = GetPhysics()->GetContact( 0 ).material; if ( material != NULL ) { @@ -2417,7 +2482,7 @@ void idActor::PlayFootStepSound( void ) { sound = spawnArgs.GetString( "snd_footstep" ); } if ( *sound != '\0' ) { - StartSoundShader( declManager->FindSound( sound ), SND_CHANNEL_BODY, 0, false, NULL ); + StartSoundShader( declManager->FindSound( sound ), SND_CHANNEL_BODY, SSF_NO_DUPS, false, NULL ); } } @@ -3120,22 +3185,24 @@ idActor::Event_AnimLength ================ */ void idActor::Event_AnimLength( int channel, const char *animname ) { + idThread::ReturnFloat( GetAnimLength(channel, animname) ); +} + +float idActor::GetAnimLength( int channel, const char *animname ) { int anim; anim = GetAnim( channel, animname ); if ( anim ) { if ( channel == ANIMCHANNEL_HEAD ) { if ( head.GetEntity() ) { - idThread::ReturnFloat( MS2SEC( head.GetEntity()->GetAnimator()->AnimLength( anim ) ) ); - return; + return MS2SEC( head.GetEntity()->GetAnimator()->AnimLength( anim ) ); } } else { - idThread::ReturnFloat( MS2SEC( animator.AnimLength( anim ) ) ); - return; + return MS2SEC( animator.AnimLength( anim ) ); } } - idThread::ReturnFloat( 0.0f ); + return 0.0f; } /* diff --git a/game/Actor.h b/game/Actor.h index c52fb27f..c3c0cd83 100644 --- a/game/Actor.h +++ b/game/Actor.h @@ -165,7 +165,7 @@ class idActor : public idAFEntity_Gibbable { // damage void SetupDamageGroups( void ); - virtual void Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ); + virtual void Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location, const idVec3 &iPoint ); int GetDamageForLocation( int damage, int location ); const char * GetDamageGroup( int location ); void ClearPain( void ); @@ -186,6 +186,7 @@ class idActor : public idAFEntity_Gibbable { bool HasEnemies( void ) const; idActor * ClosestEnemyToPoint( const idVec3 &pos ); + idActor * EnemyWithMostHealth(); virtual bool OnLadder( void ) const; @@ -318,6 +319,15 @@ class idActor : public idAFEntity_Gibbable { void Event_SetState( const char *name ); void Event_GetState( void ); void Event_GetHead( void ); + +// HEXEN : Zeroth +protected: + void Event_VecForward( float spread ); // return a forward vector for the direction we're facing, with some random spread (like projectiles) + +// HEXEN : Zeroth +public: + bool IsEnemy( idEntity *test ); + float GetAnimLength( int channel, const char *animname ); }; #endif /* !__GAME_ACTOR_H__ */ diff --git a/game/BrittleFracture.cpp b/game/BrittleFracture.cpp index 58557a0f..a54791e8 100644 --- a/game/BrittleFracture.cpp +++ b/game/BrittleFracture.cpp @@ -39,8 +39,8 @@ CLASS_DECLARATION( idEntity, idBrittleFracture ) EVENT( EV_Touch, idBrittleFracture::Event_Touch ) END_CLASS -const int SHARD_ALIVE_TIME = 5000; -const int SHARD_FADE_START = 2000; +const int SHARD_ALIVE_TIME = 5000; // HEXEN : Zeroth - chagned from 5000 +const int SHARD_FADE_START = 3500; // HEXEN : Zeroth - chagned from 2000 static const char *brittleFracture_SnapshotName = "_BrittleFracture_Snapshot_"; @@ -772,13 +772,39 @@ void idBrittleFracture::ProjectDecal( const idVec3 &point, const idVec3 &dir, co idBrittleFracture::DropShard ================ */ -void idBrittleFracture::DropShard( shard_t *shard, const idVec3 &point, const idVec3 &dir, const float impulse, const int time ) { +void idBrittleFracture::DropShard( shard_t *shard, const idVec3 &point, const idVec3 &odir, const float oimpulse, const int time ) { int i, j, clipModelId; float dist, f; idVec3 dir2, origin; idMat3 axis; shard_t *neighbour; + idVec3 dir = odir; // HEXEN : Zeroth + float impulse = oimpulse; // HEXEN : Zeroth + + // HEXEN : Zeroth + if ( dir.Length() < 5 ) { // 5 is just an (whats that word?) + dir = idVec3(0,0,-1) * spawnArgs.GetFloat( "forceIfZero", "0" ); + impulse = dir.Normalize(); + } + + // HEXEN : Zeroth + if ( spawnArgs.GetBool( "blastBothWays", "0" ) ) { + dir = -dir; + //m = dir.Normalize(); + } + + // HEXEN : Zeroth + if ( spawnArgs.GetBool( "randomDir", "0" ) ) { + float len = dir.Length(); + dir.x = gameLocal.random.RandomFloat() - 0.5; + dir.y = gameLocal.random.RandomFloat() - 0.5; + dir.z = gameLocal.random.RandomFloat() - 0.5; + dir.Normalize(); + dir *= len; + //m = dir.Normalize(); + } + // don't display decals on dropped shards shard->decals.DeleteContents( true ); @@ -818,10 +844,26 @@ void idBrittleFracture::DropShard( shard_t *shard, const idVec3 &point, const id shard->physicsObj.SetAxis( axis ); shard->physicsObj.SetBouncyness( bouncyness ); shard->physicsObj.SetFriction( 0.6f, 0.6f, friction ); - shard->physicsObj.SetGravity( gameLocal.GetGravity() ); + + // HEXEN : Zeroth + float grv = spawnArgs.GetFloat( "shardGravity", "0" ); + if ( grv ) { + shard->physicsObj.SetGravity( idVec3(0,0,-1) * grv ); + } else { + shard->physicsObj.SetGravity( gameLocal.GetGravity() ); + } + + // HEXEN : Zeroth + float velScale = 1; + if ( spawnArgs.GetBool( "shardRandomVelocity", "0" ) ) { + velScale = gameLocal.random.RandomFloat(); + } else { + velScale = 1; + } + shard->physicsObj.SetContents( CONTENTS_RENDERMODEL ); shard->physicsObj.SetClipMask( MASK_SOLID | CONTENTS_MOVEABLECLIP ); - shard->physicsObj.ApplyImpulse( 0, origin, impulse * linearVelocityScale * dir ); + shard->physicsObj.ApplyImpulse( 0, origin, impulse * linearVelocityScale * dir * velScale ); // HEXEN : Zeroth: added velScale shard->physicsObj.SetAngularVelocity( dir.Cross( dir2 ) * ( f * angularVelocityScale ) ); shard->clipModel->SetId( clipModelId ); @@ -884,7 +926,10 @@ void idBrittleFracture::Shatter( const idVec3 &point, const idVec3 &impulse, con DropShard( shard, point, dir, m, time ); } - DropFloatingIslands( point, impulse, time ); + // HEXEN : Zeroth + if ( spawnArgs.GetBool( "dropFloatingIsland", "1" ) ) { + DropFloatingIslands( point, dir, time ); // impulse changed to dir + } } /* @@ -895,12 +940,13 @@ idBrittleFracture::DropFloatingIslands void idBrittleFracture::DropFloatingIslands( const idVec3 &point, const idVec3 &impulse, const int time ) { int i, j, numIslands; int queueStart, queueEnd; + float m; // HEXEN : Zeroth shard_t *curShard, *nextShard, **queue; bool touchesEdge; idVec3 dir; dir = impulse; - dir.Normalize(); + m = dir.Normalize(); // HEXEN : Zeroth - added 'm = ' numIslands = 0; queue = (shard_t **) _alloca16( shards.Num() * sizeof(shard_t **) ); @@ -955,7 +1001,7 @@ void idBrittleFracture::DropFloatingIslands( const idVec3 &point, const idVec3 & // if the island is not connected to the world at any edges if ( !touchesEdge ) { for ( j = 0; j < queueEnd; j++ ) { - DropShard( queue[j], point, dir, 0.0f, time ); + DropShard( queue[j], point, dir, m, time ); // HEXEN : Zeroth - 0.0f to m } } } @@ -969,6 +1015,7 @@ idBrittleFracture::Break void idBrittleFracture::Break( void ) { fl.takedamage = false; physicsObj.SetContents( CONTENTS_RENDERMODEL | CONTENTS_TRIGGER ); + gameLocal.SetPersistentRemove(name.c_str()); } /* diff --git a/game/Camera.cpp b/game/Camera.cpp index eddef373..d4ebed53 100644 --- a/game/Camera.cpp +++ b/game/Camera.cpp @@ -516,7 +516,7 @@ void idCameraAnim::Think( void ) { if ( frameRate == USERCMD_HZ ) { frameTime = gameLocal.time - starttime; - frame = frameTime / gameLocal.msec; + frame = frameTime / gameLocal.msecPrecise; } else { frameTime = ( gameLocal.time - starttime ) * frameRate; frame = frameTime / 1000; @@ -566,7 +566,7 @@ void idCameraAnim::GetViewParms( renderView_t *view ) { if ( frameRate == USERCMD_HZ ) { frameTime = gameLocal.time - starttime; - frame = frameTime / gameLocal.msec; + frame = frameTime / gameLocal.msecPrecise; lerp = 0.0f; } else { frameTime = ( gameLocal.time - starttime ) * frameRate; diff --git a/game/Entity.cpp b/game/Entity.cpp index 1983a4ce..126b453b 100644 --- a/game/Entity.cpp +++ b/game/Entity.cpp @@ -26,6 +26,7 @@ If you have questions concerning this license or the applicable additional terms =========================================================================== */ +#include "Game_local.h" #include "sys/platform.h" #include "idlib/geometry/JointTransform.h" #include "idlib/LangDict.h" @@ -44,6 +45,7 @@ If you have questions concerning this license or the applicable additional terms #include "WorldSpawn.h" #include "SmokeParticles.h" +#include "ai/AI.h" #include "Entity.h" /* @@ -120,6 +122,21 @@ const idEventDef EV_StartFx( "startFx", "s" ); const idEventDef EV_HasFunction( "hasFunction", "s", 'd' ); const idEventDef EV_CallFunction( "callFunction", "s" ); const idEventDef EV_SetNeverDormant( "setNeverDormant", "d" ); +// HEXEN : Zeroth +const idEventDef EV_SetGravity( "SetGravity", "v" ); +const idEventDef EV_GetGravity( "GetGravity", NULL, 'v' ); +const idEventDef EV_GetGravityNormal( "GetGravityNormal", NULL, 'v' ); +const idEventDef EV_GetSelfEntity( "GetSelfEntity", NULL, 'e' ); +const idEventDef EV_SpawnProjectiles( "EntityLaunchProjectiles", "dffff" ); +const idEventDef EV_CreateProjectile( "EntityCreateProjectile", NULL, 'e' ); +const idEventDef EV_GetMaster( "GetMaster", NULL, 'e' ); +const idEventDef EV_GetModelDims( "GetModelDims", NULL, 'v' ); +const idEventDef EV_ReplaceMaterial( "ReplaceMaterial", "ss" ); +const idEventDef EV_ResetGravity( "ResetGravity" ); +const idEventDef EV_GetHealth( "GetHealth", NULL, 'f' ); +const idEventDef EV_SetHealth( "SetHealth", "f" ); +const idEventDef EV_HudMessage( "HudMessage", "s" ); +const idEventDef EV_GetType( "GetType", NULL, 'f' ); ABSTRACT_DECLARATION( idClass, idEntity ) EVENT( EV_GetName, idEntity::Event_GetName ) @@ -185,6 +202,21 @@ ABSTRACT_DECLARATION( idClass, idEntity ) EVENT( EV_HasFunction, idEntity::Event_HasFunction ) EVENT( EV_CallFunction, idEntity::Event_CallFunction ) EVENT( EV_SetNeverDormant, idEntity::Event_SetNeverDormant ) +// HEXEN : Zeroth + EVENT( EV_SetGravity, idEntity::Event_SetGravity ) + EVENT( EV_GetGravity, idEntity::Event_GetGravity ) + EVENT( EV_GetGravityNormal, idEntity::Event_GetGravityNormal ) + EVENT( EV_GetSelfEntity, idEntity::Event_GetSelfEntity ) + EVENT( EV_SpawnProjectiles, idEntity::Event_SpawnProjectiles ) + EVENT( EV_CreateProjectile, idEntity::Event_CreateProjectile ) + EVENT( EV_GetMaster, idEntity::Event_GetMaster ) + EVENT( EV_GetModelDims, idEntity::Event_GetModelDims ) + EVENT( EV_ReplaceMaterial, idEntity::Event_ReplaceMaterial ) + EVENT( EV_ResetGravity, idEntity::Event_ResetGravity ) + EVENT( EV_GetHealth, idEntity::Event_GetHealth ) + EVENT( EV_SetHealth, idEntity::Event_SetHealth ) + EVENT( EV_HudMessage, idEntity::Event_HudMessage ) + EVENT( EV_GetType, idEntity::Event_GetType ) END_CLASS /* @@ -435,6 +467,14 @@ idEntity::idEntity() { memset( &refSound, 0, sizeof( refSound ) ); mpGUIState = -1; + +// HEXEN : Zeroth + scriptThread = NULL; // initialized by ConstructScriptObject, which is called by idEntity::Spawn + state = NULL; + idealState = NULL; + waitState = ""; + gravityMod = false; + inWater = false; } /* @@ -465,6 +505,11 @@ void idEntity::Spawn( void ) { const char *classname; const char *scriptObjectName; + // HEXEN : Zeroth + onFire = 0; + nextFlame = 0; + fireJoint = 0; + gameLocal.RegisterEntity( this ); spawnArgs.GetString( "classname", NULL, &classname ); @@ -589,6 +634,7 @@ idEntity::~idEntity( void ) { networkSystem->ServerSendReliableMessage( -1, msg ); } + ShutdownThreads(); DeconstructScriptObject(); scriptObject.Free(); @@ -692,6 +738,44 @@ void idEntity::Save( idSaveGame *savefile ) const { } savefile->WriteInt( mpGUIState ); + + // HEXEN : Zeroth + savefile->WriteObject( scriptThread ); + savefile->WriteString( waitState ); + + //FIXME: this is unneccesary + idToken token; + // HEXEN : Zeroth + if ( state ) { + idLexer src( state->Name(), idStr::Length( state->Name() ), "idEntity::Save" ); + + src.ReadTokenOnLine( &token ); + src.ExpectTokenString( "::" ); + src.ReadTokenOnLine( &token ); + + savefile->WriteString( token ); + } else { + savefile->WriteString( "" ); + } + + // HEXEN : Zeroth + if ( idealState ) { + idLexer src( idealState->Name(), idStr::Length( idealState->Name() ), "idEntity::Save" ); + + src.ReadTokenOnLine( &token ); + src.ExpectTokenString( "::" ); + src.ReadTokenOnLine( &token ); + + savefile->WriteString( token ); + } else { + savefile->WriteString( "" ); + } + + // HEXEN : Zeroth + savefile->WriteObject( projectileEnt ); + savefile->WriteInt( nextFlame ); + savefile->WriteInt( fireJoint ); + savefile->WriteInt( onFire ); } /* @@ -778,6 +862,37 @@ void idEntity::Restore( idRestoreGame *savefile ) { if ( modelDefHandle != -1 ) { modelDefHandle = gameRenderWorld->AddEntityDef( &renderEntity ); } + + // HEXEN : Zeroth + savefile->ReadObject( reinterpret_cast( scriptThread ) ); + savefile->ReadString( waitState ); + idStr statename; + + // HEXEN : Zeroth + savefile->ReadString( statename ); + if ( statename.Length() > 0 ) { + state = GetScriptFunction( statename ); + } + + // HEXEN : Zeroth + savefile->ReadString( statename ); + if ( statename.Length() > 0 ) { + idealState = GetScriptFunction( statename ); + } + + // HEXEN : Zeroth + const idDeclEntityDef *projectileDef = gameLocal.FindEntityDef( spawnArgs.GetString( "def_projectile" ), false ); + if ( projectileDef ) { + projectileDict = projectileDef->dict; + } else { + projectileDict.Clear(); + } + + // HEXEN : Zeroth + savefile->ReadObject( reinterpret_cast( projectileEnt ) ); + savefile->ReadInt( nextFlame ); + savefile->ReadInt( fireJoint ); + savefile->ReadInt( onFire ); } /* @@ -1551,7 +1666,10 @@ bool idEntity::StartSound( const char *soundName, const s_channelType channel, i // we should ALWAYS be playing sounds from the def. // hardcoded sounds MUST be avoided at all times because they won't get precached. - assert( idStr::Icmpn( soundName, "snd_", 4 ) == 0 ); + //assert( idStr::Icmpn( soundName, "snd_", 4 ) == 0 ); - DG: unfortunately EOC's game data does this anyway + if ( idStr::Icmpn( soundName, "snd_", 4 ) != 0 ) { + gameLocal.Warning( "Using hardcoded non precached sound %s, should use ones that start with snd_!", soundName ); + } if ( !spawnArgs.GetString( soundName, "", &sound ) ) { return false; @@ -2527,7 +2645,7 @@ idEntity::GetPhysics ================ */ idPhysics *idEntity::GetPhysics( void ) const { - return physics; + return physics; } /* @@ -2859,7 +2977,7 @@ void idEntity::AddContactEntity( idEntity *ent ) { idEntity::RemoveContactEntity ================ */ -void idEntity::RemoveContactEntity( idEntity *ent ) { +void idEntity::RemoveContactEntity( idEntity *ent ) { GetPhysics()->RemoveContactEntity( ent ); } @@ -2980,7 +3098,7 @@ inflictor, attacker, dir, and point can be NULL for environmental effects ============ */ void idEntity::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, - const char *damageDefName, const float damageScale, const int location ) { + const char *damageDefName, const float damageScale, const int location, const idVec3 &iPoint ) { if ( !fl.takedamage ) { return; } @@ -3108,7 +3226,6 @@ Can be overridden by subclasses when a thread doesn't need to be allocated. ================ */ idThread *idEntity::ConstructScriptObject( void ) { - idThread *thread; const function_t *constructor; // init the script object's data @@ -3118,18 +3235,22 @@ idThread *idEntity::ConstructScriptObject( void ) { constructor = scriptObject.GetConstructor(); if ( constructor ) { // start a thread that will initialize after Spawn is done being called - thread = new idThread(); - thread->SetThreadName( name.c_str() ); - thread->CallFunction( this, constructor, true ); - thread->DelayedStart( 0 ); + scriptThread = new idThread(); + scriptThread->SetThreadName( name.c_str() ); + scriptThread->CallFunction( this, constructor, true ); + scriptThread->DelayedStart( 0 ); + + // HEXEN : Zeroth + scriptThread->ManualDelete(); + //scriptThread->ManualControl(); } else { - thread = NULL; + scriptThread = NULL; } // clear out the object's memory scriptObject.ClearObject(); - return thread; + return scriptThread; } /* @@ -3576,6 +3697,55 @@ void idEntity::ActivateTargets( idEntity *activator ) const { } } +// HEXEN : Zeroth +void idAnimatedEntity::EmitFlames( void ) { + if ( gameLocal.time < nextFlame ) { + return; + } + + particleEmitter_t pe; + idStr particleName = "firestorm_incinerator"; + idVec3 origin; + idMat3 axis; + + idAnimator *anim=GetAnimator(); + if ( anim == NULL ) { + return; + } + + int numjoints=anim->NumJoints(); + if ( numjoints <= 0 ) { + return; + } + + nextFlame += gameLocal.random.RandomFloat() * ( ( 50 / numjoints ) ); + + fireJoint++; + + if ( fireJoint >= numjoints ) { + fireJoint = 0; + } + + pe.joint = (jointHandle_t) fireJoint; + //idAnimatedEntity *aent=static_cast< idAnimatedEntity * >( ent ); + + anim->GetJointTransform( pe.joint, gameLocal.time, origin, axis ); + origin = renderEntity.origin + origin * renderEntity.axis; + //origin = GetJointPos( (jointHandle_t)fireJoint ); + + BecomeActive( TH_UPDATEPARTICLES ); + if ( !gameLocal.time ) { + // particles with time of 0 don't show, so set the time differently on the first frame + pe.time = 1; + } else { + pe.time = gameLocal.time; + } + pe.particle = static_cast( declManager->FindType( DECL_PARTICLE, particleName ) ); + gameLocal.smokeParticles->EmitSmoke( pe.particle, pe.time, gameLocal.random.CRandomFloat(), origin, axis ); + + //particles.Append( pe ); +} + /*********************************************************************** Misc. @@ -3625,8 +3795,10 @@ bool idEntity::TouchTriggers( void ) const { ent = cm->GetEntity(); - if ( !ent->RespondsTo( EV_Touch ) && !ent->HasSignal( SIG_TOUCH ) ) { - continue; + if ( !ent->RespondsTo( EV_Touch ) ) { + if ( !ent->HasSignal( SIG_TOUCH ) ) { + continue; + } } if ( !GetPhysics()->ClipContents( cm ) ) { @@ -4160,10 +4332,15 @@ idEntity::Event_GetAngles ================ */ void idEntity::Event_GetAngles( void ) { - idAngles ang = GetPhysics()->GetAxis().ToAngles(); - idThread::ReturnVector( idVec3( ang[0], ang[1], ang[2] ) ); + idAngles ang = GetAngles(); + idThread::ReturnVector( idVec3(ang.yaw, ang.pitch, ang.roll) ); +} + +idAngles idEntity::GetAngles( void ) { + return( GetPhysics()->GetAxis().ToAngles() ); } + /* ================ idEntity::Event_SetLinearVelocity @@ -4384,9 +4561,10 @@ void idEntity::Event_GetEntityKey( const char *key ) { } ent = gameLocal.FindEntity( entname ); - if ( !ent ) { - gameLocal.Warning( "Couldn't find entity '%s' specified in '%s' key in entity '%s'", entname, key, name.c_str() ); - } + // HEXEN : Zeroth - this gets annoying, not necessary. commenting out. + //if ( !ent ) { + //gameLocal.Warning( "Couldn't find entity '%s' specified in '%s' key in entity '%s'", entname, key, name.c_str() ); + //} idThread::ReturnEntity( ent ); } @@ -4884,6 +5062,9 @@ const idEventDef EV_SetJointAngle( "setJointAngle", "ddv" ); const idEventDef EV_GetJointPos( "getJointPos", "d", 'v' ); const idEventDef EV_GetJointAngle( "getJointAngle", "d", 'v' ); +// HEXEN : Zeroth +const idEventDef EV_TransitionJointAngle( "transitionJointAngle", "ddvvff" ); + CLASS_DECLARATION( idEntity, idAnimatedEntity ) EVENT( EV_GetJointHandle, idAnimatedEntity::Event_GetJointHandle ) EVENT( EV_ClearAllJoints, idAnimatedEntity::Event_ClearAllJoints ) @@ -4892,6 +5073,9 @@ CLASS_DECLARATION( idEntity, idAnimatedEntity ) EVENT( EV_SetJointAngle, idAnimatedEntity::Event_SetJointAngle ) EVENT( EV_GetJointPos, idAnimatedEntity::Event_GetJointPos ) EVENT( EV_GetJointAngle, idAnimatedEntity::Event_GetJointAngle ) + +// HEXEN : Zeroth + EVENT( EV_TransitionJointAngle, idAnimatedEntity::Event_TransitionJointAngle ) END_CLASS /* @@ -4975,6 +5159,13 @@ void idAnimatedEntity::Think( void ) { UpdateAnimation(); Present(); UpdateDamageEffects(); + + // HEXEN : Zeroth + if ( gameLocal.time < onFire ) { + if ( gameLocal.time >= nextFlame ) { + EmitFlames(); + } + } } /* @@ -5372,6 +5563,25 @@ void idAnimatedEntity::Event_SetJointAngle( jointHandle_t jointnum, jointModTran animator.SetJointAxis( jointnum, transform_type, mat ); } +// HEXEN : Zeroth +void idAnimatedEntity::Event_TransitionJointAngle( jointHandle_t jointnum, jointModTransform_t transform_type, const idAngles &to, const idAngles &from, float seconds, float transitions ) { + TransitionJointAngle( jointnum, transform_type, to, from, seconds, transitions ); +} + +void idAnimatedEntity::TransitionJointAngle( jointHandle_t jointnum, jointModTransform_t transform_type, const idAngles &to, const idAngles &from, float seconds, float transitions ) { +// idVec3 offset; +// idMat3 axis; + idAngles aTo = to; + idAngles aFrom = from; + +// if ( !GetJointWorldTransform( jointnum, gameLocal.time, offset, axis ) ) { +// gameLocal.Warning( "Joint # %d out of range on entity '%s'", jointnum, name.c_str() ); +// } + + animator.eoc_TransitionJointAngle( jointnum, transform_type, aTo, aFrom, seconds, transitions ); +} + + /* ================ idAnimatedEntity::Event_GetJointPos @@ -5380,6 +5590,11 @@ returns the position of the joint in worldspace ================ */ void idAnimatedEntity::Event_GetJointPos( jointHandle_t jointnum ) { + idThread::ReturnVector( GetJointPos( jointnum ) ); +} + +// HEXEN : Zeroth +idVec3 idAnimatedEntity::GetJointPos( jointHandle_t jointnum ) { idVec3 offset; idMat3 axis; @@ -5387,7 +5602,7 @@ void idAnimatedEntity::Event_GetJointPos( jointHandle_t jointnum ) { gameLocal.Warning( "Joint # %d out of range on entity '%s'", jointnum, name.c_str() ); } - idThread::ReturnVector( offset ); + return offset; } /* @@ -5409,3 +5624,454 @@ void idAnimatedEntity::Event_GetJointAngle( jointHandle_t jointnum ) { idVec3 vec( ang[ 0 ], ang[ 1 ], ang[ 2 ] ); idThread::ReturnVector( vec ); } + +// HEXEN : Zeroth +idAngles idAnimatedEntity::GetJointAngle( jointHandle_t jointnum ) { + idVec3 offset; + idMat3 axis; + + if ( !GetJointWorldTransform( jointnum, gameLocal.time, offset, axis ) ) { + gameLocal.Warning( "Joint # %d out of range on entity '%s'", jointnum, name.c_str() ); + } + + return( axis.ToAngles() ); +} + +// HEXEN : Zeroth +void idAnimatedEntity::SetJointAngle( jointHandle_t jointnum, jointModTransform_t transform_type, const idAngles &angles ) { + idMat3 mat; + + mat = angles.ToMat3(); + animator.SetJointAxis( jointnum, transform_type, mat ); +} + +void idEntity::Event_SetGravity( const idVec3 &grav ) { + GetPhysics()->SetGravity(grav); +} + +void idEntity::Event_GetGravity( void ) { + idVec3 cur=GetPhysics()->GetGravity(); + + idThread::ReturnVector(cur); +} + +void idEntity::Event_GetGravityNormal( void ) { + idVec3 cur=GetPhysics()->GetGravityNormal(); + + idThread::ReturnVector(cur); +} + +void idEntity::Event_GetSelfEntity( void ) { + idThread::ReturnEntity(this); +} + +void idEntity::Event_GetHealth( void ) { + idThread::ReturnInt(health); +} + +void idEntity::Event_SetHealth( float newHealth ) { + health = newHealth; +} + +// HEXEN : Zeroth +void idEntity::Event_HudMessage( const char *message ) { + gameLocal.SendLocalUserHudMessage( message ); +} + +/* +===================== +Zeroth +idItem::GetScriptFunction +===================== +*/ +const function_t *idEntity::GetScriptFunction( const char *funcname ) { + const function_t *func; + + func = scriptObject.GetFunction( funcname ); + if ( !func ) { + scriptThread->Error( "Unknown function '%s' in '%s'", funcname, scriptObject.GetTypeName() ); + } + + return func; +} + +/* +===================== +Zeroth +idItem::SetState +===================== +*/ +void idEntity::SetState( const function_t *newState ) { + if ( !newState ) { + gameLocal.Printf("idEntity::SetState(): %s: Null State.\n", name.c_str()); + } + + state = newState; + idealState = state; + scriptThread->CallFunction( this, state, true ); +} + +/* +===================== +Zeroth +idItem::SetState +===================== +*/ +void idEntity::SetState( const char *statename ) { + const function_t *newState; + + newState = GetScriptFunction( statename ); + SetState( newState ); +} + +/* +===================== +idItem::UpdateScript +===================== +*/ +void idEntity::UpdateScript( void ) { + int i; + + // a series of state changes can happen in a single frame. + // this loop limits them in case we've entered an infinite loop. + for( i = 0; i < 20; i++ ) { + if ( idealState != state ) { + SetState( idealState ); + } + + // don't call script until it's done waiting + if ( scriptThread->IsWaiting() ) { + break; + } + + scriptThread->Execute(); + if ( idealState == state ) { + break; + } + } + + if ( i == 20 ) { + scriptThread->Warning( "idEntity::UpdateScript: exited loop to prevent lockup" ); + } +} + +/* +================ +Zeroth +idItem::ShutdownThreads +================ +*/ +void idEntity::ShutdownThreads( void ) { + if ( scriptThread ) { + scriptThread->EndThread(); + scriptThread->PostEventMS( &EV_Remove, 0 ); + delete scriptThread; + scriptThread = NULL; + } +} + +/* +===================== +Zeroth +idItem::Event_SetNextState +===================== +*/ +void idEntity::Event_SetNextState( const char *name ) { + idealState = GetScriptFunction( name ); + if ( idealState == state ) { + state = NULL; + } +} + + +/* +===================== +Zeroth +idItem::Event_SetState +===================== +*/ +void idEntity::Event_SetState( const char *name ) { + idealState = GetScriptFunction( name ); + if ( idealState == state ) { + state = NULL; + } + scriptThread->DoneProcessing(); +} + +/* +===================== +Zeroth +idAItem::Event_GetState +===================== +*/ +void idEntity::Event_GetState( void ) { + if ( state ) { + idThread::ReturnString( state->Name() ); + } else { + idThread::ReturnString( "" ); + } +} + + +/* +===================== +Zeroth +idItem::WaitState +===================== +*/ +const char *idEntity::WaitState( void ) const { + if ( waitState.Length() ) { + return waitState; + } else { + return NULL; + } +} + +/* +===================== +Zeroth +idAItem::SetWaitState +===================== +*/ +void idEntity::SetWaitState( const char *_waitstate ) { + waitState = _waitstate; +} + +/* +================ +Zeroth +idEntity::FinishSetup +================ +*/ +/*void idEntity::FinishSetup( void ) { + const char *scriptObjectName; + + // setup script object + if ( spawnArgs.GetString( "scriptobject", NULL, &scriptObjectName ) ) { + if ( !scriptObject.SetType( scriptObjectName ) ) { + gameLocal.Error( "Script object '%s' not found on entity '%s'.", scriptObjectName, name.c_str() ); + } + + ConstructScriptObject(); + } +} +*/ + +// /* +// ================ +// HEXEN : Zeroth +// idEntity::IsInUse +// ================ +// */ +// bool IsInUse( void ) { +// if (scriptThread != NULL) { +// return (scriptThread->IsBusy()); +// } +// +// return false; +// } + +void idEntity::Event_GetType( void ) { + idThread::ReturnFloat( (float)GetType()->typeNum ); +} + +// HEXEN : Zeroth +void idEntity::Event_SpawnProjectiles( int num_projectiles, float spread, float fuseOffset, float launchPower, float dmgPower ) { + idProjectile *proj; + idEntity *ent; + int i; + idVec3 dir; + float ang; + float spin; + float distance; + trace_t tr; + idVec3 start; + idVec3 muzzle_pos; + idBounds ownerBounds, projBounds; + + idVec3 playerViewOrigin; + idMat3 playerViewAxis; + idVec3 zzero; + idEntity *owner = this; + + zzero.Zero(); + + playerViewOrigin.Zero(); + playerViewAxis.Zero(); + + playerViewOrigin = physics->GetOrigin(); + playerViewAxis = physics->GetAxis(); + + if ( !projectileDict.GetNumKeyVals() ) { + const char *classname = this->spawnArgs.GetString("inv_name"); + gameLocal.Warning( "No projectile defined on '%s'", classname ); + return; + } + + if ( gameLocal.isClient ) { + // predict instant hit projectiles + if ( projectileDict.GetBool( "net_instanthit" ) ) { + float spreadRad = DEG2RAD( spread ); + muzzle_pos = playerViewOrigin + playerViewAxis[ 0 ] * 2.0f; + for( i = 0; i < num_projectiles; i++ ) { + ang = idMath::Sin( spreadRad * gameLocal.random.RandomFloat() ); + spin = (float)DEG2RAD( 360.0f ) * gameLocal.random.RandomFloat(); + dir = playerViewAxis[ 0 ] + playerViewAxis[ 2 ] * ( ang * idMath::Sin( spin ) ) - playerViewAxis[ 1 ] * ( ang * idMath::Cos( spin ) ); + dir.Normalize(); + gameLocal.clip.Translation( tr, muzzle_pos, muzzle_pos + dir * 4096.0f, NULL, mat3_identity, MASK_SHOT_RENDERMODEL, owner ); + if ( tr.fraction < 1.0f ) { + idProjectile::ClientPredictionCollide( this, projectileDict, tr, vec3_origin, true ); + } + } + } + + } else { + + ownerBounds = owner->GetPhysics()->GetAbsBounds(); + +// owner->AddProjectilesFired( num_projectiles ); + + float spreadRad = DEG2RAD( spread ); + for( i = 0; i < num_projectiles; i++ ) { + ang = idMath::Sin( spreadRad * gameLocal.random.RandomFloat() ); + spin = (float)DEG2RAD( 360.0f ) * gameLocal.random.RandomFloat(); + dir = playerViewAxis[ 0 ] + playerViewAxis[ 2 ] * ( ang * idMath::Sin( spin ) ) - playerViewAxis[ 1 ] * ( ang * idMath::Cos( spin ) ); + dir.Normalize(); + + if ( projectileEnt ) { + ent = projectileEnt; + ent->Show(); + ent->Unbind(); + projectileEnt = NULL; + } else { + gameLocal.SpawnEntityDef( projectileDict, &ent, false ); + } + + if ( !ent || !ent->IsType( idProjectile::Type ) ) { + const char *projectileName = this->spawnArgs.GetString( "def_projectile" ); + gameLocal.Error( "'%s' is not an idProjectile", projectileName ); + } + + if ( projectileDict.GetBool( "net_instanthit" ) ) { + // don't synchronize this on top of the already predicted effect + ent->fl.networkSync = false; + } + + proj = static_cast(ent); + proj->Create( owner, playerViewOrigin, dir ); + + projBounds = proj->GetPhysics()->GetBounds().Rotate( proj->GetPhysics()->GetAxis() ); + + // make sure the projectile starts inside the bounding box of the owner + if ( i == 0 ) { + muzzle_pos = playerViewOrigin + playerViewAxis[ 0 ] * 2.0f; + if ( ( ownerBounds - projBounds).RayIntersection( muzzle_pos, playerViewAxis[0], distance ) ) { + start = muzzle_pos + distance * playerViewAxis[0]; + } else { + start = ownerBounds.GetCenter(); + } + gameLocal.clip.Translation( tr, start, muzzle_pos, proj->GetPhysics()->GetClipModel(), proj->GetPhysics()->GetClipModel()->GetAxis(), MASK_SHOT_RENDERMODEL, owner ); + muzzle_pos = tr.endpos; + } + + proj->Launch( muzzle_pos, dir, zzero, fuseOffset, launchPower, dmgPower ); + } + + } +} + +// HEXEN : Zeroth +void idEntity::Event_CreateProjectile( void ) { + idEntity *owner = this; + + if ( !gameLocal.isClient ) { + projectileEnt = NULL; + gameLocal.SpawnEntityDef( projectileDict, &projectileEnt, false ); + if ( projectileEnt ) { + projectileEnt->SetOrigin( GetPhysics()->GetOrigin() ); + projectileEnt->Bind( owner, false ); + projectileEnt->Hide(); + } + idThread::ReturnEntity( projectileEnt ); + } else { + idThread::ReturnEntity( NULL ); + } +} + +// HEXEN : Zeroth +void idEntity::Event_GetMaster( void ) { + idThread::ReturnEntity(bindMaster); +} + +// HEXEN : Zeroth +void idEntity::Event_GetModelDims( void ) { + idThread::ReturnVector(GetModelDims()); +} + +// HEXEN : Zeroth +idVec3 idEntity::GetModelDims( void ) { + idVec3 a; + if ( renderEntity.hModel ) { + a.x = abs(renderEntity.bounds.GetMaxs().x) + abs(renderEntity.bounds.GetMins().x); + a.y = abs(renderEntity.bounds.GetMaxs().y) + abs(renderEntity.bounds.GetMins().y); + a.z = abs(renderEntity.bounds.GetMaxs().z) + abs(renderEntity.bounds.GetMins().z); + } else { + a.Zero(); + } + return a; +} + +void idEntity::Event_ResetGravity( void ) { + physics->SetGravity(gameLocal.GetGravity()); +} + +// HEXEN : Zeroth - broken +void idEntity::Event_ReplaceMaterial( const char * replacee, const char * replacer ) { + +// idStr str = replacer; +// +// if ( !str.Length() ) { +// return; +// } +// int i, num = renderEntity.hModel->NumSurfaces(); + +// for (i=0; ima +// this->UpdateRenderEntity( + //renderEntity.hModel->Surface(i)->shader->FreeData(); + +// gameLocal.Printf("repmat: %s\n", replacer); +// gameLocal.Printf("curmat: %s\n", renderEntity.hModel->Surface(i)->shader->GetName()); + +// const idMaterial * shd; +// shd = renderEntity.hModel->Surface(i)->shader; + +// shd = declManager->FindMaterial( replacer ); +// renderEntity.hModel->Surface(i)->shader = declManager->FindMaterial( replacer ); + +// gameLocal.Printf("newmat: %s\n", renderEntity.hModel->Surface(i)->shader->GetName()); +// this->renderEntity.hModel->FinishSurfaces(); +// } +} + +// HEXEN : Zeroth +// ****** thanks SnoopJeDi ( http://www.doom3world.org/phpbb2/viewtopic.php?f=56&t=12469&p=214427#p214427 ) +/* +================ +idEntity::FadeMusic +================ +*/ +void idEntity::FadeMusic( int channel, float to, float over ) { + if ( spawnArgs.GetBool( "s_music" ) ) { + Event_FadeSound( channel, to, over ); + } +} +// ****** + +idVec3 idEntity::curNorm() { + return physics->GetGravityNormal(); +} + +idVec3 idEntity::curGrav() { + return physics->GetGravity(); +} diff --git a/game/Entity.h b/game/Entity.h index 709543b1..ebccff6f 100644 --- a/game/Entity.h +++ b/game/Entity.h @@ -141,7 +141,14 @@ class idEntity : public idClass { idList< idEntityPtr > targets; // when this entity is activated these entities entity are activated - int health; // FIXME: do all objects really need health? +// HEXEN : Zeroth +protected: + int nextFlame; + int fireJoint; +public: + int health; + int onFire; + struct entityFlags_s { bool notarget :1; // if true never attack or target this entity @@ -158,6 +165,16 @@ class idEntity : public idClass { bool networkSync :1; // if true the entity is synchronized over the network } fl; +// HEXEN : Zeroth +public: + bool gravityMod; + bool inWater; + +// HEXEN : Zeroth +public: + idVec3 curGrav( void ); + idVec3 curNorm( void ); + public: ABSTRACT_PROTOTYPE( idEntity ); @@ -176,7 +193,18 @@ class idEntity : public idClass { // clients generate views based on all the player specific options, // cameras have custom code, and everything else just uses the axis orientation + virtual renderView_t * GetRenderView(); + void ShutdownThreads( void ); + void UpdateScript( void ); + void SetState( const char *statename ); + void SetState( const function_t *newState ); + const function_t *GetScriptFunction( const char *funcname ); + const char *WaitState( void ) const; + void SetWaitState( const char *_waitstate ); + //idThread *ConstructScriptObject( void ); + //void idEntity::FinishSetup( void ); + //bool IsInUse( void ); // HEXEN : Zeroth // thinking virtual void Think( void ); @@ -302,7 +330,7 @@ class idEntity : public idClass { // returns true if this entity can be damaged from the given origin virtual bool CanDamage( const idVec3 &origin, idVec3 &damagePoint ) const; // applies damage to this entity - virtual void Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ); + virtual void Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location, const idVec3 &iPoint ); // adds a damage effect like overlays, blood, sparks, debris etc. virtual void AddDamageEffect( const trace_t &collision, const idVec3 &velocity, const char *damageDefName ); // callback function for when another entity received damage from this entity. damage can be adjusted and returned to the caller. @@ -361,11 +389,24 @@ class idEntity : public idClass { void ServerSendEvent( int eventId, const idBitMsg *msg, bool saveEvent, int excludeClient ) const; void ClientSendEvent( int eventId, const idBitMsg *msg ) const; +// HEXEN : Zeroth +// ****** thanks SnoopJeDi ( http://www.doom3world.org/phpbb2/viewtopic.php?f=56&t=12469&p=214427#p214427 ) + void FadeMusic( int channel, float to, float over ); +// ****** + protected: renderEntity_t renderEntity; // used to present a model to the renderer int modelDefHandle; // handle to static renderer model refSound_t refSound; // used to present sound to the audio engine +// HEXEN : Zeroth + // state variables + const function_t *state; + const function_t *idealState; + // script variables + idThread * scriptThread; + idStr waitState; + private: idPhysics_Static defaultPhysicsObj; // default physics object idPhysics * physics; // physics used for this entity @@ -381,7 +422,7 @@ class idEntity : public idClass { signalList_t * signals; int mpGUIState; // local cache to avoid systematic SetStateInt - + idThread * thread; private: void FixupLocalizedStrings(); @@ -465,6 +506,32 @@ class idEntity : public idClass { void Event_HasFunction( const char *name ); void Event_CallFunction( const char *name ); void Event_SetNeverDormant( int enable ); + +// HEXEN : Zeroth +private: + idDict projectileDict; + idEntity *projectileEnt; + void Event_GetState( void ); + void Event_SetState( const char *name ); + void Event_SetNextState( const char *name ); + + void Event_SetGravity( const idVec3 &grav ); + void Event_GetGravity( void ); + void Event_GetGravityNormal( void ); + void Event_GetSelfEntity( void ); + void Event_SetHealth( float newHealth ); + void Event_GetHealth( void ); + void Event_GetType( void ); + void Event_SpawnProjectiles( int num_projectiles, float spread, float fuseOffset, float launchPower, float dmgPower ); + void Event_CreateProjectile( void ); + void Event_GetMaster( void ); + void Event_GetModelDims( void ); + void Event_ReplaceMaterial( const char * replacee, const char * replacer ); + void Event_ResetGravity( void ); + void Event_HudMessage( const char *message ); +public: + idAngles GetAngles( void ); + idVec3 GetModelDims( void ); }; /* @@ -509,6 +576,7 @@ class idAnimatedEntity : public idEntity { virtual void AddDamageEffect( const trace_t &collision, const idVec3 &velocity, const char *damageDefName ); void AddLocalDamageEffect( jointHandle_t jointNum, const idVec3 &localPoint, const idVec3 &localNormal, const idVec3 &localDir, const idDeclEntityDef *def, const idMaterial *collisionMaterial ); void UpdateDamageEffects( void ); + void EmitFlames( void ); virtual bool ClientReceiveEvent( int event, int time, const idBitMsg &msg ); @@ -529,6 +597,15 @@ class idAnimatedEntity : public idEntity { void Event_SetJointAngle( jointHandle_t jointnum, jointModTransform_t transform_type, const idAngles &angles ); void Event_GetJointPos( jointHandle_t jointnum ); void Event_GetJointAngle( jointHandle_t jointnum ); + +// HEXEN : Zeroth +private: + void Event_TransitionJointAngle( jointHandle_t jointnum, jointModTransform_t transform_type, const idAngles &to, const idAngles &from, float seconds, float transitions ); +public: + idVec3 GetJointPos( jointHandle_t jointnum ); + void TransitionJointAngle( jointHandle_t jointnum, jointModTransform_t transform_type, const idAngles &to, const idAngles &from, float seconds, float transitions ); + void SetJointAngle( jointHandle_t jointnum, jointModTransform_t transform_type, const idAngles &angles ); + idAngles GetJointAngle( jointHandle_t jointnum ); }; #endif /* !__GAME_ENTITY_H__ */ diff --git a/game/GameBase.h b/game/GameBase.h index 5269b673..2a621822 100644 --- a/game/GameBase.h +++ b/game/GameBase.h @@ -50,7 +50,7 @@ If you have questions concerning this license or the applicable additional terms #define GAME_VERSION "baseDOOM-1" #define MAX_CLIENTS 32 -#define GENTITYNUM_BITS 12 +#define GENTITYNUM_BITS 13 // HEXEN : Zeroth - increased from 12 to 13 #define MAX_GENTITIES (1<RegisterDeclType( "model", DECL_MODELDEF, idDeclAllocator ); + declManager->RegisterDeclType( "export", DECL_MODELEXPORT, idDeclAllocator ); + // register game specific decl folders - declManager->RegisterDeclFolder( "def", ".def", DECL_ENTITYDEF ); - declManager->RegisterDeclFolder( "fx", ".fx", DECL_FX ); - declManager->RegisterDeclFolder( "particles", ".prt", DECL_PARTICLE ); - declManager->RegisterDeclFolder( "af", ".af", DECL_AF ); - declManager->RegisterDeclFolder( "newpdas", ".pda", DECL_PDA ); + declManager->RegisterDeclFolder( "def", ".def", DECL_ENTITYDEF ); + declManager->RegisterDeclFolder( "fx", ".fx", DECL_FX ); + declManager->RegisterDeclFolder( "particles", ".prt", DECL_PARTICLE ); + declManager->RegisterDeclFolder( "af", ".af", DECL_AF ); + declManager->RegisterDeclFolder( "newpdas", ".pda", DECL_PDA ); cmdSystem->AddCommand( "listModelDefs", idListDecls_f, CMD_FL_SYSTEM|CMD_FL_GAME, "lists model defs" ); - cmdSystem->AddCommand( "printModelDefs", idPrintDecls_f, CMD_FL_SYSTEM|CMD_FL_GAME, "prints a model def", idCmdSystem::ArgCompletion_Decl ); + cmdSystem->AddCommand( "printModelDefs", idPrintDecls_f, CMD_FL_SYSTEM|CMD_FL_GAME, "prints a model def", idCmdSystem::ArgCompletion_Decl ); Clear(); - idEvent::Init(); - idClass::Init(); + idEvent::Init(); + idClass::Init(); InitConsoleCommands(); @@ -315,6 +349,7 @@ void idGameLocal::Init( void ) { smokeParticles = new idSmokeParticles; + // set up the aas dict = FindEntityDefDict( "aas_types" ); if ( !dict ) { @@ -324,6 +359,8 @@ void idGameLocal::Init( void ) { // allocate space for the aas const idKeyValue *kv = dict->MatchPrefix( "type" ); while( kv != NULL ) { + + aas = idAAS::Alloc(); aasList.Append( aas ); aasNames.Append( kv->GetValue() ); @@ -332,6 +369,67 @@ void idGameLocal::Init( void ) { gamestate = GAMESTATE_NOMAP; + // HEXEN : Zeroth - setup r_vmodes + r_vmodes[0].ratio=0; + r_vmodes[0].width=800; + r_vmodes[0].height=600; + + r_vmodes[1].ratio=0; + r_vmodes[1].width=1024; + r_vmodes[1].height=768; + + r_vmodes[2].ratio=0; + r_vmodes[2].width=1280; + r_vmodes[2].height=960; + + r_vmodes[3].ratio=0; + r_vmodes[3].width=1400; + r_vmodes[3].height=1050; + + r_vmodes[4].ratio=1; + r_vmodes[4].width=856; + r_vmodes[4].height=480; + + r_vmodes[5].ratio=1; + r_vmodes[5].width=1024; + r_vmodes[5].height=576; + + r_vmodes[6].ratio=1; + r_vmodes[6].width=1280; + r_vmodes[6].height=720; + + r_vmodes[7].ratio=1; + r_vmodes[7].width=1366; + r_vmodes[7].height=768; + + r_vmodes[8].ratio=1; + r_vmodes[8].width=1600; + r_vmodes[8].height=900; + + r_vmodes[9].ratio=1; + r_vmodes[9].width=1920; + r_vmodes[9].height=1080; + + r_vmodes[10].ratio=2; + r_vmodes[10].width=1280; + r_vmodes[10].height=800; + + r_vmodes[11].ratio=2; + r_vmodes[11].width=1440; + r_vmodes[11].height=900; + + r_vmodes[12].ratio=2; + r_vmodes[12].width=1680; + r_vmodes[12].height=1050; + + r_vmodes[13].ratio=2; + r_vmodes[13].width=1920; + r_vmodes[13].height=1200; + + r_vmodes[14].ratio=2; + r_vmodes[14].width=2560; + r_vmodes[14].height=1600; + Printf( "...%d aas types\n", aasList.Num() ); } @@ -439,6 +537,10 @@ void idGameLocal::SaveGame( idFile *f ) { savegame.WriteShort( SDL_BYTEORDER ) ; // SDL_LIL_ENDIAN or SDL_BIG_ENDIAN // DG end + // a number to signify which release the save file belons to. this + // way we can make save games compatible with newer releases of EOC + savegame.WriteInt( EOC_RELEASE ); + // go through all entities and threads and add them to the object list for( i = 0; i < MAX_GENTITIES; i++ ) { ent = entities[i]; @@ -548,6 +650,11 @@ void idGameLocal::SaveGame( idFile *f ) { savegame.WriteBool( isNewFrame ); savegame.WriteFloat( clientSmoothing ); + //zeorth404 + portalSkyEnt.Save( &savegame ); + savegame.WriteBool( portalSkyActive ); + + savegame.WriteBool( mapCycleLoaded ); savegame.WriteInt( spawnCount ); @@ -874,6 +981,7 @@ void idGameLocal::LoadMap( const char *mapName, int randseed ) { // load the collision map collisionModelManager->LoadMap( mapFile ); + numClients = 0; @@ -883,6 +991,12 @@ void idGameLocal::LoadMap( const char *mapName, int randseed ) { memset( spawnIds, -1, sizeof( spawnIds ) ); spawnCount = INITIAL_SPAWN_COUNT; + // HEXEN : Zeroth + // ****** thanks SnoopJeDi ( http://www.doom3world.org/phpbb2/viewtopic.php?f=56&t=12469&p=214427#p214427 ) + s_music_vol.ClearModified(); + s_music_vol.SetModified(); // SnoopJeDi: we want to fade on level start + // ****** + spawnedEntities.Clear(); activeEntities.Clear(); numEntitiesToDeactivate = 0; @@ -918,6 +1032,9 @@ void idGameLocal::LoadMap( const char *mapName, int randseed ) { sessionCommand = ""; nextGibTime = 0; + // HEXEN : Zeroth + portalSkyEnt = NULL; + portalSkyActive = false; vacuumAreaNum = -1; // if an info_vacuum is spawned, it will set this if ( !editEntities ) { @@ -941,6 +1058,8 @@ void idGameLocal::LoadMap( const char *mapName, int randseed ) { // load navigation system for all the different monster sizes for( i = 0; i < aasNames.Num(); i++ ) { + + aasList[ i ]->Init( idStr( mapFileName ).SetFileExtension( aasNames[ i ] ).c_str(), mapFile->GetGeometryCRC() ); } @@ -1150,12 +1269,15 @@ void idGameLocal::MapPopulate( void ) { } // parse the key/value pairs and spawn entities SpawnMapEntities(); + // mark location entities in all connected areas SpreadLocations(); + // prepare the list of randomized initial spawn spots RandomizeInitialSpawns(); + // spawnCount - 1 is the number of entities spawned into the map, their indexes started at MAX_CLIENTS (included) // mapSpawnCount is used as the max index of map entities, it's the first index of non-map entities @@ -1166,6 +1288,7 @@ void idGameLocal::MapPopulate( void ) { // before the physics are run so entities can bind correctly Printf( "==== Processing events ====\n" ); idEvent::ServiceEvents(); + } /* @@ -1175,6 +1298,11 @@ idGameLocal::InitFromNewMap */ void idGameLocal::InitFromNewMap( const char *mapName, idRenderWorld *renderWorld, idSoundWorld *soundWorld, bool isServer, bool isClient, int randseed ) { + // ** thanks snoopjedi + musicSpeakers.Clear(); // SnoopJeDi: new map, not reload, so clear the list + cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "heartbeat\n" ); // SnoopJeDi - iddevnet fix to keep server from dropping off master list + // ** + this->isServer = isServer; this->isClient = isClient; this->isMultiplayer = isServer || isClient; @@ -1183,27 +1311,38 @@ void idGameLocal::InitFromNewMap( const char *mapName, idRenderWorld *renderWorl MapShutdown(); } - Printf( "----- Game Map Init -----\n" ); + Printf( "----------- Game Map Init ------------\n" ); gamestate = GAMESTATE_STARTUP; gameRenderWorld = renderWorld; gameSoundWorld = soundWorld; + eoc_MapPath.Clear(); LoadMap( mapName, randseed ); + InitScriptForMap(); + MapPopulate(); + mpGame.Reset(); - mpGame.Precache(); + // free up any unused animations animationLib.FlushUnusedAnims(); gamestate = GAMESTATE_ACTIVE; + + // HEXEN : Zeroth + + InitHub(); + UpdateFog(); + + Printf( "--------------------------------------\n" ); } /* @@ -1221,7 +1360,8 @@ bool idGameLocal::InitFromSaveGame( const char *mapName, idRenderWorld *renderWo MapShutdown(); } - Printf( "----- Game Map Init SaveGame -----\n" ); + Printf( "------- Game Map Init SaveGame -------\n" ); + eoc_MapPath.Clear(); gamestate = GAMESTATE_STARTUP; @@ -1262,8 +1402,15 @@ bool idGameLocal::InitFromSaveGame( const char *mapName, idRenderWorld *renderWo } // DG end + + // a number to signify which release the save file belons to. this + // way we can make save games compatible with newer releases of + int eocnum; + savegame.ReadInt( eocnum ); + // Create the list of all objects in the game savegame.CreateObjects(); + // Load the idProgram, also checking to make sure scripting hasn't changed since the savegame if ( program.Restore( &savegame ) == false ) { @@ -1276,8 +1423,10 @@ bool idGameLocal::InitFromSaveGame( const char *mapName, idRenderWorld *renderWo return false; } + // load the map needed for this savegame LoadMap( mapName, 0 ); + savegame.ReadInt( i ); g_skill.SetInteger( i ); @@ -1287,6 +1436,7 @@ bool idGameLocal::InitFromSaveGame( const char *mapName, idRenderWorld *renderWo // precache any media specified in the map for ( i = 0; i < mapFile->GetNumEntities(); i++ ) { + idMapEntity *mapEnt = mapFile->GetEntity( i ); if ( !InhibitEntitySpawn( mapEnt->epairs ) ) { @@ -1303,12 +1453,16 @@ bool idGameLocal::InitFromSaveGame( const char *mapName, idRenderWorld *renderWo savegame.ReadInt( numClients ); for( i = 0; i < numClients; i++ ) { + + savegame.ReadDict( &userInfo[ i ] ); savegame.ReadUsercmd( usercmds[ i ] ); savegame.ReadDict( &persistentPlayerInfo[ i ] ); } for( i = 0; i < MAX_GENTITIES; i++ ) { + + savegame.ReadObject( reinterpret_cast( entities[ i ] ) ); savegame.ReadInt( spawnIds[ i ] ); @@ -1318,6 +1472,7 @@ bool idGameLocal::InitFromSaveGame( const char *mapName, idRenderWorld *renderWo } } + savegame.ReadInt( firstFreeIndex ); savegame.ReadInt( num_entities ); @@ -1327,6 +1482,8 @@ bool idGameLocal::InitFromSaveGame( const char *mapName, idRenderWorld *renderWo savegame.ReadInt( num ); for( i = 0; i < num; i++ ) { + + savegame.ReadObject( reinterpret_cast( ent ) ); assert( ent ); if ( ent ) { @@ -1334,8 +1491,12 @@ bool idGameLocal::InitFromSaveGame( const char *mapName, idRenderWorld *renderWo } } + + savegame.ReadInt( num ); for( i = 0; i < num; i++ ) { + + savegame.ReadObject( reinterpret_cast( ent ) ); assert( ent ); if ( ent ) { @@ -1343,12 +1504,15 @@ bool idGameLocal::InitFromSaveGame( const char *mapName, idRenderWorld *renderWo } } + + savegame.ReadInt( numEntitiesToDeactivate ); savegame.ReadBool( sortPushers ); savegame.ReadBool( sortTeamMasters ); savegame.ReadDict( &persistentLevelInfo ); for( i = 0; i < MAX_GLOBAL_SHADER_PARMS; i++ ) { + savegame.ReadFloat( globalShaderParms[ i ] ); } @@ -1395,9 +1559,15 @@ bool idGameLocal::InitFromSaveGame( const char *mapName, idRenderWorld *renderWo savegame.ReadBool( isNewFrame ); savegame.ReadFloat( clientSmoothing ); + //zeorth404 + portalSkyEnt.Restore( &savegame ); + savegame.ReadBool( portalSkyActive ); + savegame.ReadBool( mapCycleLoaded ); savegame.ReadInt( spawnCount ); + + savegame.ReadInt( num ); if ( num ) { if ( num != gameRenderWorld->NumAreas() ) { @@ -1406,6 +1576,8 @@ bool idGameLocal::InitFromSaveGame( const char *mapName, idRenderWorld *renderWo locationEntities = new idLocationEntity *[ num ]; for( i = 0; i < num; i++ ) { + + savegame.ReadObject( reinterpret_cast( locationEntities[ i ] ) ); } } @@ -1438,15 +1610,25 @@ bool idGameLocal::InitFromSaveGame( const char *mapName, idRenderWorld *renderWo // makingBuild // shakeSounds + + // Read out pending events idEvent::Restore( &savegame ); + savegame.RestoreObjects(); + + // HEXEN : Zeroth + + InitHub(); + mpGame.Reset(); + mpGame.Precache(); + // free up any unused animations animationLib.FlushUnusedAnims(); @@ -1463,6 +1645,8 @@ idGameLocal::MapClear void idGameLocal::MapClear( bool clearClients ) { int i; + SavePersistentMoveables(); + for( i = ( clearClients ? 0 : MAX_CLIENTS ); i < MAX_GENTITIES; i++ ) { delete entities[ i ]; // ~idEntity is in charge of setting the pointer to NULL @@ -1472,6 +1656,7 @@ void idGameLocal::MapClear( bool clearClients ) { } entityHash.Clear( 1024, MAX_GENTITIES ); + entypeHash.Clear( 1024, MAX_GENTITIES ); if ( !clearClients ) { // add back the hashes of the clients @@ -1480,6 +1665,7 @@ void idGameLocal::MapClear( bool clearClients ) { continue; } entityHash.Add( entityHash.GenerateKey( entities[ i ]->name.c_str(), true ), i ); + entypeHash.Add( entypeHash.GenerateKey( entities[ i ]->GetClassname(), true ), i ); } } @@ -1493,6 +1679,8 @@ void idGameLocal::MapClear( bool clearClients ) { delete[] locationEntities; locationEntities = NULL; + + BanishLocationList.DeleteContents( true ); } /* @@ -1534,6 +1722,7 @@ void idGameLocal::MapShutdown( void ) { ShutdownAsyncNetwork(); mapFileName.Clear(); + BanishLocationList.Clear(); gameRenderWorld = NULL; gameSoundWorld = NULL; @@ -2041,6 +2230,25 @@ void idGameLocal::SetupPlayerPVS( void ) { pvs.FreeCurrentPVS( otherPVS ); playerConnectedAreas = newPVS; } + + // HEXEN : Zeroth + // if portalSky is preset, then merge into pvs so we get rotating brushes, etc + if ( portalSkyEnt.GetEntity() ) { + idEntity *skyEnt = portalSkyEnt.GetEntity(); + + otherPVS = pvs.SetupCurrentPVS( skyEnt->GetPVSAreas(), skyEnt->GetNumPVSAreas() ); + newPVS = pvs.MergeCurrentPVS( playerPVS, otherPVS ); + pvs.FreeCurrentPVS( playerPVS ); + pvs.FreeCurrentPVS( otherPVS ); + playerPVS = newPVS; + + otherPVS = pvs.SetupCurrentPVS( skyEnt->GetPVSAreas(), skyEnt->GetNumPVSAreas() ); + newPVS = pvs.MergeCurrentPVS( playerConnectedAreas, otherPVS ); + pvs.FreeCurrentPVS( playerConnectedAreas ); + pvs.FreeCurrentPVS( otherPVS ); + playerConnectedAreas = newPVS; + } + } } @@ -2215,6 +2423,14 @@ gameReturn_t idGameLocal::RunFrame( const usercmd_t *clientCmds ) { idPlayer *player; const renderView_t *view; +// HEXEN : Zeroth - for foliage rendering +/* + idEntity *ent; + float isFoliage=0; + idVec3 dist; + float maxFoliageDist=2000; +*/ + #ifdef _DEBUG if ( isMultiplayer ) { assert( !isClient ); @@ -2223,6 +2439,27 @@ gameReturn_t idGameLocal::RunFrame( const usercmd_t *clientCmds ) { player = GetLocalPlayer(); +// HEXEN : Zeroth - foliage rendering +/* + for (int i = 0; i < MAX_GENTITIES; i++ ) { + ent = entities[i]; + + if (ent) { + ent = entities[i]; + ent->spawnArgs.GetFloat( "foliage", "0", isFoliage ); + if ( isFoliage ) { + dist = ent->GetPhysics()->GetOrigin() - player->GetPhysics()->GetOrigin(); + + if (sqrt(dist.x * dist.x + dist.y * dist.y + dist.z * dist.z) > maxFoliageDist) { + ent->Hide(); + }else{ + ent->Show(); + } + } + } + } +*/ + if ( !isMultiplayer && g_stopTime.GetBool() ) { // clear any debug lines from a previous frame gameRenderWorld->DebugClearLines( time + 1 ); @@ -2234,12 +2471,15 @@ gameReturn_t idGameLocal::RunFrame( const usercmd_t *clientCmds ) { player->Think(); } } else do { - // update the game time - framenum++; - previousTime = time; - msec = CalcMSec( framenum ); // dezo2/DG: recalculate each frame, see comment at CalcMSec() - time += msec; - realClientTime = time; + + if ( !paused ) { + // update the game time + framenum++; + previousTime = time; + msec = CalcMSec( framenum ); // dezo2/DG: recalculate each frame, see comment at CalcMSec() + time += msec; + realClientTime = time; + } #ifdef GAME_DLL // allow changing SIMD usage on the fly @@ -2370,8 +2610,6 @@ gameReturn_t idGameLocal::RunFrame( const usercmd_t *clientCmds ) { if ( !isMultiplayer && player ) { ret.health = player->health; - ret.heartRate = player->heartRate; - ret.stamina = idMath::FtoiFast( player->stamina ); // combat is a 0-100 value based on lastHitTime and lastDmgTime // each make up 50% of the time spread over 10 seconds ret.combat = 0; @@ -2410,7 +2648,6 @@ gameReturn_t idGameLocal::RunFrame( const usercmd_t *clientCmds ) { return ret; } - /* ====================================================================== @@ -2500,6 +2737,16 @@ makes rendering and sound system calls ================ */ bool idGameLocal::Draw( int clientNum ) { + + if ( s_music_vol.IsModified() ) { //SnoopJeDi, fade that sound! + for ( int i = 0; i < musicSpeakers.Num(); i++ ) { + idSound* ent = static_cast(entities[ musicSpeakers[ i ] ]); + if (ent) + ent->FadeMusic( 0, s_music_vol.GetFloat(), 0 ); + } + s_music_vol.ClearModified(); + } + if ( isMultiplayer ) { return mpGame.Draw( clientNum ); } @@ -2562,12 +2809,100 @@ const char* idGameLocal::HandleGuiCommands( const char *menuCommand ) { return mpGame.HandleGuiCommands( menuCommand ); } +/* +#ifdef EOC_WIN32 +void idGameLocal::GetMainWindowHandle(void) { + extern HWND eoc_hwnd; + + if (eoc_hwnd != 0) + return; + + GUITHREADINFO winfo; + winfo.cbSize = sizeof(GUITHREADINFO); + GetGUIThreadInfo(GetCurrentThreadId(), &winfo); + eoc_hwnd = winfo.hwndActive; +} +#endif + +void idGameLocal::GetDesktopResolution(int *X, int *Y) { + gameLocal.Printf("Entered"); + #ifdef EOC_WIN32 + RECT rect; + + GetWindowRect(GetDesktopWindow(), &rect); + *X = (int) (rect.right - rect.left); + *Y = (int) (rect.bottom - rect.top); + #endif +} + +void idGameLocal::GetMouseCoord(int *X, int *Y) { + #ifdef EOC_WIN32 + int width, height; + float posX, posY; + POINT pt; + RECT rect; + extern HWND eoc_hwnd; + + GetDesktopResolution(&width, &height); + + GetMainWindowHandle(); + GetWindowRect(eoc_hwnd, &rect); + + GetCursorPos(&pt); + + gameLocal.Printf("\n\n------------------------------"); + gameLocal.Printf("Doom Res: %d, %d\n", (rect.right - rect.left), (rect.bottom - rect.top) ); + gameLocal.Printf("GetCursorPos: %d, %d\n", pt.x, pt.y); + + posX = (float) pt.x; // convert to float so we don't lose precision + posY = (float) pt.y; + + gameLocal.Printf("Subracting Left/Top: %d, %d\n", rect.left, rect.top); + posX -= rect.left; + posY -= rect.top; + if (posX < 0) posX=0; + if (posY < 0) posY=0; + if (posX > rect.right) posX=rect.right; + if (posY > rect.bottom) posY=rect.bottom; + gameLocal.Printf("Subracted: %f, %f\n", posX, posY); + *X = (int) posX; + *Y = (int) posY; + + gameLocal.Printf("Converted to Int: %d, %d\n", *X, *Y ); + gameLocal.Printf("------------------------------\n\n"); + // scale down to dooms actual resolution +#endif +} +*/ /* ================ idGameLocal::HandleMainMenuCommands ================ */ -void idGameLocal::HandleMainMenuCommands( const char *menuCommand, idUserInterface *gui ) { } +void idGameLocal::HandleMainMenuCommands( const char *menuCommand, idUserInterface *gui ) { + idUserInterface *mainMenuGui = uiManager->FindGui( "guis/mainmenu.gui", true, false, true ); + if ( mainMenuGui ) { + mainMenuGui->SetStateFloat("inGame", gamestate == GAMESTATE_ACTIVE); + } +// HEXEN : Zeroth +/* + #ifdef EOC_WIN32 + int posX, posY; + + GetMouseCoord(&posX, &posY); + + idUserInterface *mainMenuGui = uiManager->FindGui( "guis/mainmenu.gui", true, false, true ); + + mainMenuGui->SetStateFloat( "eoc_MouseX", posX ); + mainMenuGui->SetStateFloat( "eoc_MouseY", posY ); + #endif + */ + +// HEXEN : Zeroth +// idUserInterface *mainMenuGui = uiManager->FindGui( "guis/mainmenu.gui", true, false, true ); +// if (mainMenuGui) mainMenuGui->SetStateFloat( "eoc_MouseX", mainMenuGui->CursorX() ); +// if (mainMenuGui) mainMenuGui->SetStateFloat( "eoc_MouseY", mainMenuGui->CursorY() ); + } /* ================ @@ -3079,7 +3414,7 @@ bool idGameLocal::SpawnEntityDef( const idDict &args, idEntity **ent, bool setDe } spawnArgs = args; - + if ( spawnArgs.GetString( "name", "", &name ) ) { sprintf( error, " on '%s'", name); } @@ -3245,8 +3580,6 @@ void idGameLocal::SpawnMapEntities( void ) { int numEntities; idDict args; - Printf( "Spawning entities\n" ); - if ( mapFile == NULL ) { Printf("No mapfile present\n"); return; @@ -3271,7 +3604,13 @@ void idGameLocal::SpawnMapEntities( void ) { num = 1; inhibit = 0; + //if ( loadGui != NULL ) { + // tickShader = loadGui->GetStateString( "tickshader" ); + //} + for ( i = 1 ; i < numEntities ; i++ ) { + + mapEnt = mapFile->GetEntity( i ); args = mapEnt->epairs; @@ -3299,6 +3638,7 @@ void idGameLocal::AddEntityToHash( const char *name, idEntity *ent ) { Error( "Multiple entities named '%s'", name ); } entityHash.Add( entityHash.GenerateKey( name, true ), ent->entityNumber ); + entypeHash.Add( entypeHash.GenerateKey( ent->spawnArgs.GetString("spawnclass"), true ), ent->entityNumber ); } /* @@ -3309,6 +3649,15 @@ idGameLocal::RemoveEntityFromHash bool idGameLocal::RemoveEntityFromHash( const char *name, idEntity *ent ) { int hash, i; + // HEXEN : Zeroth + hash = entypeHash.GenerateKey( ent->spawnArgs.GetString("spawnclass"), true ); + for ( i = entypeHash.First( hash ); i != -1; i = entypeHash.Next( i ) ) { + if ( entities[i] && entities[i] == ent && entities[i]->name.Icmp( name ) == 0 && !strcmp(entities[i]->GetClassname(), ent->GetClassname() ) ) { + entypeHash.Remove( hash, i ); + break; + } + } + hash = entityHash.GenerateKey( name, true ); for ( i = entityHash.First( hash ); i != -1; i = entityHash.Next( i ) ) { if ( entities[i] && entities[i] == ent && entities[i]->name.Icmp( name ) == 0 ) { @@ -3316,6 +3665,7 @@ bool idGameLocal::RemoveEntityFromHash( const char *name, idEntity *ent ) { return true; } } + return false; } @@ -3406,6 +3756,21 @@ idEntity *idGameLocal::FindEntity( const char *name ) const { return NULL; } +// HEXEN : Zeroth +idEntity *idGameLocal::FindEntityType( const idTypeInfo &type ) const { + int hash, i; + + // HEXEN : Zeroth + hash = entypeHash.GenerateKey( type.classname, true ); + for ( i = entypeHash.First( hash ); i != -1; i = entypeHash.Next( i ) ) { + if ( entities[i] && !strcmp(entities[i]->GetClassname(), type.classname ) ) { + return entities[i]; + } + } + + return NULL; +} + /* ============= idGameLocal::FindEntityUsingDef @@ -3531,7 +3896,7 @@ void idGameLocal::KillBox( idEntity *ent, bool catch_teleport ) { if ( hit->IsType( idPlayer::Type ) && static_cast< idPlayer * >( hit )->IsInTeleport() ) { static_cast< idPlayer * >( hit )->TeleportDeath( ent->entityNumber ); } else if ( !catch_teleport ) { - hit->Damage( ent, ent, vec3_origin, "damage_telefrag", 1.0f, INVALID_JOINT ); + hit->Damage( ent, ent, vec3_origin, "damage_telefrag", 1.0f, INVALID_JOINT, idVec3(0,0,0) ); } if ( !gameLocal.isMultiplayer ) { @@ -3573,7 +3938,7 @@ idGameLocal::AlertAI void idGameLocal::AlertAI( idEntity *ent ) { if ( ent && ent->IsType( idActor::Type ) ) { // alert them for the next frame - lastAIAlertTime = time + msec; + lastAIAlertTime = time + msecPrecise; lastAIAlertEntity = static_cast( ent ); } } @@ -3604,6 +3969,7 @@ void idGameLocal::RadiusDamage( const idVec3 &origin, idEntity *inflictor, idEnt idBounds bounds; idVec3 v, damagePoint, dir; int i, e, damage, radius, push; + bool notlocalplayer; const idDict *damageDef = FindEntityDefDict( damageDefName, false ); if ( !damageDef ) { @@ -3616,6 +3982,7 @@ void idGameLocal::RadiusDamage( const idVec3 &origin, idEntity *inflictor, idEnt damageDef->GetInt( "push", va( "%d", damage * 100 ), push ); damageDef->GetFloat( "attackerDamageScale", "0.5", attackerDamageScale ); damageDef->GetFloat( "attackerPushScale", "0", attackerPushScale ); + damageDef->GetBool( "notlocalplayer", "1", notlocalplayer ); if ( radius < 1 ) { radius = 1; @@ -3641,6 +4008,10 @@ void idGameLocal::RadiusDamage( const idVec3 &origin, idEntity *inflictor, idEnt ent = entityList[ e ]; assert( ent ); + if ( notlocalplayer && ent == GetLocalPlayer() ) { + continue; + } + if ( !ent->fl.takedamage ) { continue; } @@ -3686,13 +4057,13 @@ void idGameLocal::RadiusDamage( const idVec3 &origin, idEntity *inflictor, idEnt damageScale *= attackerDamageScale; } - ent->Damage( inflictor, attacker, dir, damageDefName, damageScale, INVALID_JOINT ); + ent->Damage( inflictor, attacker, dir, damageDefName, damageScale, INVALID_JOINT, idVec3(0,0,0) ); } } // push physics objects if ( push ) { - RadiusPush( origin, radius, push * dmgPower, attacker, ignorePush, attackerPushScale, false ); + RadiusPush( origin, radius, push * dmgPower, attacker, ignorePush, attackerPushScale, false, notlocalplayer, damageDef->GetBool( "notprojectiles", "1" ) ); } } @@ -3701,7 +4072,7 @@ void idGameLocal::RadiusDamage( const idVec3 &origin, idEntity *inflictor, idEnt idGameLocal::RadiusPush ============== */ -void idGameLocal::RadiusPush( const idVec3 &origin, const float radius, const float push, const idEntity *inflictor, const idEntity *ignore, float inflictorScale, const bool quake ) { +void idGameLocal::RadiusPush( const idVec3 &origin, const float radius, const float push, const idEntity *inflictor, const idEntity *ignore, float inflictorScale, const bool quake, const bool notlocalplayer, const bool notprojectiles ) { int i, numListedClipModels; idClipModel *clipModel; idClipModel *clipModelList[ MAX_GENTITIES ]; @@ -3737,8 +4108,12 @@ void idGameLocal::RadiusPush( const idVec3 &origin, const float radius, const fl ent = clipModel->GetEntity(); + if ( notlocalplayer && ent == GetLocalPlayer() ) { + continue; + } + // never push projectiles - if ( ent->IsType( idProjectile::Type ) ) { + if ( notprojectiles && ent->IsType( idProjectile::Type ) ) { continue; } @@ -3978,7 +4353,7 @@ void idGameLocal::SetCamera( idCamera *cam ) { } else { inCinematic = false; - cinematicStopTime = time + msec; + cinematicStopTime = time + msecPrecise; // restore r_znear cvarSystem->SetCVarFloat( "r_znear", 3.0f ); @@ -3991,6 +4366,16 @@ void idGameLocal::SetCamera( idCamera *cam ) { } } } +// HEXEN : Zeroth +// if (camera) { +// renderView_t *rv = camera->GetRenderView(); +// Printf("= camera\n"); +// if (rv) { +// Printf("==============[ %f, %f ]==============", rv->fov_x, rv->fov_y); +// } +// }else{ +// Printf("= no camera\n"); +// } } /* @@ -4050,6 +4435,8 @@ void idGameLocal::SpreadLocations() { // for each location entity, make pointers from every area it touches for( ent = spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + + if ( !ent->IsType( idLocationEntity::Type ) ) { continue; } @@ -4165,6 +4552,8 @@ void idGameLocal::RandomizeInitialSpawns( void ) { spot.dist = 0; spot.ent = FindEntityUsingDef( NULL, "info_player_deathmatch" ); while( spot.ent ) { + + spawnSpots.Append( spot ); if ( spot.ent->spawnArgs.GetBool( "initial" ) ) { initialSpots.Append( spot.ent ); @@ -4184,6 +4573,8 @@ void idGameLocal::RandomizeInitialSpawns( void ) { } } for ( i = 0; i < initialSpots.Num(); i++ ) { + + j = random.RandomInt( initialSpots.Num() ); ent = initialSpots[ i ]; initialSpots[ i ] = initialSpots[ j ]; @@ -4193,6 +4584,11 @@ void idGameLocal::RandomizeInitialSpawns( void ) { currentInitialSpot = 0; } +// HEXEN : Zeroth +void idGameLocal::SetLocalPlayerSpawnPoint(idStr point) { + eoc_LocalPlayerSpawnPoint = point; +} + /* =========== idGameLocal::SelectInitialSpawnPoint @@ -4210,10 +4606,19 @@ idEntity *idGameLocal::SelectInitialSpawnPoint( idPlayer *player ) { bool alone; if ( !isMultiplayer || !spawnSpots.Num() ) { - spot.ent = FindEntityUsingDef( NULL, "info_player_start" ); +// HEXEN : Zeroth + idStr point = "info_player_start_"; + if (eoc_LocalPlayerSpawnPoint == "") eoc_LocalPlayerSpawnPoint = "1"; + point += eoc_LocalPlayerSpawnPoint; + + spot.ent = FindEntity( point ); + if ( !spot.ent ) { - Error( "No info_player_start on map.\n" ); + Error( "Did not find Player Spawn Point in map: %s", point.c_str() ); } + + eoc_LocalPlayerSpawnPoint = "1"; // should always be 1 by default + return spot.ent; } if ( player->spectating ) { @@ -4328,6 +4733,29 @@ void idGameLocal::ThrottleUserInfo( void ) { mpGame.ThrottleUserInfo(); } + +/* +================= +Zeroth +idPlayer::SetPortalSkyEnt +================= +*/ +void idGameLocal::SetPortalSkyEnt( idEntity *ent ) { + portalSkyEnt = ent; +} + +/* +================= +Zeroth +idPlayer::IsPortalSkyAcive +================= +*/ +bool idGameLocal::IsPortalSkyAcive() { + return portalSkyActive; +} + + + /* =========== idGameLocal::SelectTimeGroup @@ -4420,4 +4848,343 @@ void idGameLocal::SwitchTeam( int clientNum, int team ) { idGameLocal::GetMapLoadingGUI =============== */ + void idGameLocal::GetMapLoadingGUI( char gui[ MAX_STRING_CHARS ] ) { } + + +void idGameLocal::InitHub(void) { + idEntity* ent; + idStr name,tmp,name_str,st; + int nt=0; + idVec3 vc; + idAngles ag; + idMat3 ax; + + for (int i = 0; i < MAX_GENTITIES; i++ ) { + ent = gameLocal.entities[i]; + if (ent) { + + name = ent->GetEntityDefName(); + + // find banish locations + if (name == "speaker" || name == "light" || name.Left(8) == "trigger_" || + name.Left(5) == "ammo_" || name.Left(5) == "path_" ) { + BanishLocationList.AddUnique( new idVec3( ent->GetPhysics()->GetOrigin() ) ); + } + + // remove leaves //z.todo: this is a temporary fix for a clipping issue that + // causes the game to freeze after a savegame load (not crash) + if ( ent->IsType( idEntity_Leaf::Type ) ) { + delete ent; + continue; + } + + // entities which need to be removed + tmp=GetMapName(); + tmp+="_"; + tmp+=ent->GetName(); + name_str=tmp; + name_str+="_remove"; + + if ( persistentLevelInfo.GetInt( name_str ) ) { + delete ent; + gameLocal.entities[i]=NULL; + continue; + } + + // idTrigger enable/disable + name_str=tmp; + name_str+="_trig"; + st=persistentLevelInfo.GetString( name_str ); + + if ( st == "on" ) { + static_cast(gameLocal.entities[i])->Enable(); + continue; + } else if ( st == "off" ) { + static_cast(gameLocal.entities[i])->Disable(); + continue; + } + + // idTrigger_Timer enable/disable + name_str=tmp; + name_str+="_timer"; + st=persistentLevelInfo.GetString( name_str ); + + if ( st == "on" ) { + static_cast(gameLocal.entities[i])->Enable(); + continue; + } else if ( st == "off" ) { + static_cast(gameLocal.entities[i])->Disable(); + continue; + } + + // idTrigger_Touch enable/disable + name_str=tmp; + name_str+="_touch"; + st=persistentLevelInfo.GetString( name_str ); + + if ( st == "on" ) { + static_cast(gameLocal.entities[i])->Enable(); + continue; + } else if ( st == "off" ) { + static_cast(gameLocal.entities[i])->Disable(); + continue; + } + + // idTrigger_Count 'count' variable + name_str=tmp; + name_str+="_count_count"; + st=persistentLevelInfo.GetString( name_str ); + + if ( st != "" ) { + nt=persistentLevelInfo.GetInt( name_str ); + static_cast(gameLocal.entities[i])->SetCount(nt); + continue; + } + + // idTrigger_Count 'goal' variable + name_str=tmp; + name_str+="_count_goal"; + st=persistentLevelInfo.GetString( name_str ); + + if ( st != "" ) { + nt=persistentLevelInfo.GetInt( name_str ); + static_cast(gameLocal.entities[i])->SetGoal(nt); + continue; + } + + // light broken + name_str=tmp; + name_str+="_light_broken"; + st=persistentLevelInfo.GetString( name_str ); + + if ( st == "1" ) { + static_cast(gameLocal.entities[i])->BecomeBroken(NULL); + continue; + } + + // light on + name_str=tmp; + name_str+="_light_on"; + st=persistentLevelInfo.GetString( name_str ); + + if ( st == "on" ) { + static_cast(gameLocal.entities[i])->On(); + continue; + } else if ( st == "off" ) { + static_cast(gameLocal.entities[i])->Off(); + continue; + } + + // mover pos + name_str=tmp; + name_str+="_mover_pos"; + st=persistentLevelInfo.GetString( name_str ); + + if ( st != "" ) { + vc=persistentLevelInfo.GetVector( name_str ); + static_cast(gameLocal.entities[i])->GetPhysics()->SetOrigin( vc ); + static_cast(gameLocal.entities[i])->SetDestPos( vc ); + continue; + } + + // mover ang + name_str=tmp; + name_str+="_mover_pos"; + st=persistentLevelInfo.GetString( name_str ); + + if ( st != "" ) { + ag=persistentLevelInfo.GetAngles( name_str ); + ag.Normalize360(); + static_cast(static_cast(gameLocal.entities[i])->GetPhysics())->SetAngularExtrapolation( EXTRAPOLATION_NONE, 0, 0, ag, ang_zero, ang_zero ); + continue; + } + + // moveable pos/ang/vel + name_str=tmp; + name_str+="_moveable_pos"; + st=persistentLevelInfo.GetString( name_str ); + + if ( st != "" ) { + static_cast(static_cast(gameLocal.entities[i])->GetPhysics())->SetOrigin( persistentLevelInfo.GetVector( name_str ) ); + name_str=tmp; + name_str+="_moveable_ang"; + static_cast(gameLocal.entities[i])->SetAngles( persistentLevelInfo.GetAngles( name_str ) ); + name_str=tmp; + name_str+="_moveable_vel"; + static_cast(static_cast(gameLocal.entities[i])->GetPhysics())->SetLinearVelocity( persistentLevelInfo.GetVector( name_str ) ); + continue; + } + } + } + + if ( BanishLocationList.Num() > 0 ) { + BanishLocationList.Shuffle(); + } +} + +void idGameLocal::SendLocalUserHudMessage( const char *message ) { + GetLocalPlayer()->ShowHudMessage( message ); +} + +void idGameLocal::SendLocalUserHudMessage( idStr message ) { + GetLocalPlayer()->ShowHudMessage( message.c_str() ); +} + +// HEXEN : Zeroth - shows or hides fog based on r_fog +void idGameLocal::UpdateFog( void ) { + idStr nam; + idStr fogprefix="fog_"; + bool eoc_fog = r_fog.GetBool(); + + int hash = gameLocal.entypeHash.GenerateKey( idLight::Type.classname, true ); + idLight *light; + + for ( int i = gameLocal.entypeHash.First( hash ); i != -1; i = gameLocal.entypeHash.Next( i ) ) { + if ( gameLocal.entities[i] && !strcmp(gameLocal.entities[i]->GetClassname(), idLight::Type.classname ) ) { + if ( !gameLocal.entities[i]->IsType( idLight::Type ) ) { + continue; + } + + light = static_cast< idLight* >( gameLocal.entities[i] ); + + if ( !light ) { + continue; + } + + nam = light->GetName(); + + if ( fogprefix == nam.Left(4) ) { + if ( eoc_fog ) { + light->On(); + } else { + light->Off(); + } + } + } + } +} + +void idGameLocal::SetPersistentRemove( const char *name ) { + // for removing items/creatures/etc after returning to a level (hubs) + idStr name_str; + + name_str=gameLocal.GetMapName(); + name_str+="_"; + name_str+=name; + name_str+="_remove"; + + persistentLevelInfo.Set( name_str, "1" ); +} + +void idGameLocal::SetPersistentLightBroken( const char *name ) { + // for removing items/creatures/etc after returning to a level (hubs) + idStr name_str; + + name_str=gameLocal.GetMapName(); + name_str+="_"; + name_str+=name; + name_str+="_light_broken"; + + persistentLevelInfo.Set( name_str, "1" ); +} + +void idGameLocal::SetPersistentLightOn( const char *name, bool state ) { + // for removing items/creatures/etc after returning to a level (hubs) + idStr name_str, st; + + name_str=gameLocal.GetMapName(); + name_str+="_"; + name_str+=name; + name_str+="_light_on"; + + if ( state ) { + st="on"; + } else { + st="off"; + } + + persistentLevelInfo.Set( name_str, st ); +} + +void idGameLocal::SetPersistentTrigger( const char *type, const char *name, const bool state ) { + // for removing items/creatures/etc after returning to a level (hubs) + idStr name_str, st; + + name_str=gameLocal.GetMapName(); + name_str+="_"; + name_str+=name; + name_str+="_"; + name_str+=type; + + if ( state ) { + st="on"; + } else { + st="off"; + } + + persistentLevelInfo.Set( name_str, st ); +} + +void idGameLocal::SetPersistentTriggerInt( const char *type, const char *var, const char *name, int val ) { + // for removing items/creatures/etc after returning to a level (hubs) + idStr name_str, st; + + name_str=gameLocal.GetMapName(); + name_str+="_"; + name_str+=name; + name_str+="_"; + name_str+=type; + name_str+="_"; + name_str+=var; + + persistentLevelInfo.SetInt( name_str, val ); +} + +void idGameLocal::SavePersistentMoveables(void) { + idEntity* ent; + idStr tmp,name_str,st,name; + int nt=0; +// idVec3 vc; + idAngles ag; + + for (int i = 0; i < MAX_GENTITIES; i++ ) { + ent = gameLocal.entities[i]; + if (ent) { + + name = ent->GetEntityDefName(); + + // find moveables locations + if ( name.Left(9) == "moveable_" ) { + /* + vc = ent->GetPhysics()->GetOrigin(); + if ( vc == ent->spawnArgs.GetVector("origin") ) { + continue; + } + */ + + if ( static_cast( ent )->savePersistentInfo == false ) { + continue; + } + + tmp=GetMapName(); + tmp+="_"; + tmp+=ent->GetName(); + name_str=tmp; + name_str+="_moveable_pos"; + + persistentLevelInfo.SetVector( name_str, ent->GetPhysics()->GetOrigin() ); + + name_str=tmp; + name_str+="_moveable_ang"; + + persistentLevelInfo.SetAngles( name_str, ent->GetAngles() ); + + name_str=tmp; + name_str+="_moveable_vel"; + + persistentLevelInfo.SetVector( name_str, ent->GetPhysics()->GetLinearVelocity() ); + } + } + } +} diff --git a/game/Game_local.h b/game/Game_local.h index 1b47fcf0..8a7cc326 100644 --- a/game/Game_local.h +++ b/game/Game_local.h @@ -51,6 +51,15 @@ If you have questions concerning this license or the applicable additional terms #define protected public #endif +// HEXEN : Zeroth +#define NUM_UNIQUE_ARTIFACTS (16) +#define EOC_NUM_VMODES (15) +#define EOC_RELEASE (1) + +#define LAGO_IMG_WIDTH 64 +#define LAGO_IMG_HEIGHT 64 +#define LAGO_WIDTH 64 + /* =============================================================================== @@ -96,6 +105,7 @@ extern const int NUM_RENDER_PORTAL_BITS; =============================================================================== */ + typedef struct entityState_s { int entityNumber; idBitMsg state; @@ -220,6 +230,13 @@ class idEntityPtr { //============================================================================ +// HEXEN : Zeroth +struct r_vmodes_type { + int width; + int height; + int ratio; +}; + class idGameLocal : public idGame { public: idDict serverInfo; // all the tunable parameters, like numclients, etc @@ -232,6 +249,7 @@ class idGameLocal : public idGame { int firstFreeIndex; // first free index in the entities array int num_entities; // current number <= MAX_GENTITIES idHashIndex entityHash; // hash table to quickly find entities by name + idHashIndex entypeHash; // hash table to quickly find entities by type (works in paralel with entityHash) idWorldspawn * world; // world entity idLinkList spawnedEntities; // all spawned entities idLinkList activeEntities; // all thinking entities (idEntity::thinkFlags != 0) @@ -277,6 +295,12 @@ class idGameLocal : public idGame { // so it can be set to 16 or 17 in different frames // so 60 frames add up to 1000ms int msec; // time since last update in milliseconds + // DG: unlike msec, msecPrecise remains constant (in the base game, in d3xp it might be scaled for slowmo) + // so it can be used when the correct time for multiple frames must be calculated, + // or when setting an int-timer for next frame (where it rounds down which is safe for that case) + static const float msecPrecise; // 16.66666 + + bool paused; // // HEXEN : Zeroth int vacuumAreaNum; // -1 if level doesn't have any outside areas @@ -298,6 +322,15 @@ class idGameLocal : public idGame { idEntityPtr lastGUIEnt; // last entity with a GUI, used by Cmd_NextGUI_f int lastGUI; // last GUI on the lastGUIEnt +// HEXEN : Zeroth +public: + idEntityPtr portalSkyEnt; + bool portalSkyActive; + void SetPortalSkyEnt( idEntity *ent ); + bool IsPortalSkyAcive(); + r_vmodes_type r_vmodes[EOC_NUM_VMODES]; + int r_vmode; + // ---------------------- Public idGame Interface ------------------- idGameLocal(); @@ -391,6 +424,11 @@ class idGameLocal : public idGame { bool InPlayerPVS( idEntity *ent ) const; bool InPlayerConnectedArea( idEntity *ent ) const; +public: + pvsHandle_t GetPlayerPVS() { return playerPVS; }; + + + void SetCamera( idCamera *cam ); idCamera * GetCamera( void ) const; bool SkipCinematic( void ); @@ -406,12 +444,16 @@ class idGameLocal : public idGame { static void ArgCompletion_EntityName( const idCmdArgs &args, void(*callback)( const char *s ) ); idEntity * FindTraceEntity( idVec3 start, idVec3 end, const idTypeInfo &c, const idEntity *skip ) const; idEntity * FindEntity( const char *name ) const; + +// HEXEN : Zeroth + idEntity * FindEntityType( const idTypeInfo &type ) const; + idEntity * FindEntityUsingDef( idEntity *from, const char *match ) const; int EntitiesWithinRadius( const idVec3 org, float radius, idEntity **entityList, int maxCount ) const; void KillBox( idEntity *ent, bool catch_teleport = false ); void RadiusDamage( const idVec3 &origin, idEntity *inflictor, idEntity *attacker, idEntity *ignoreDamage, idEntity *ignorePush, const char *damageDefName, float dmgPower = 1.0f ); - void RadiusPush( const idVec3 &origin, const float radius, const float push, const idEntity *inflictor, const idEntity *ignore, float inflictorScale, const bool quake ); + void RadiusPush( const idVec3 &origin, const float radius, const float push, const idEntity *inflictor, const idEntity *ignore, float inflictorScale, const bool quake, const bool notlocalplayer=false, const bool notprojectiles=true ); void RadiusPushClipModel( const idVec3 &origin, const float push, const idClipModel *clipModel ); void ProjectDecal( const idVec3 &origin, const idVec3 &dir, float depth, bool parallel, float size, const char *material, float angle = 0 ); @@ -452,6 +494,33 @@ class idGameLocal : public idGame { bool NeedRestart(); + +// HEXEN : Zeroth +// HEXEN : Zeroth +// ****** thanks SnoopJeDi ( http://www.doom3world.org/phpbb2/viewtopic.php?f=56&t=12469&p=214427#p214427 ) + idList musicSpeakers; //SnoopJeDi - holds entitynum values for speakers with s_music set +// ****** + void SetLocalPlayerSpawnPoint(idStr point); +// void FoliageRendering( void ); + idStr eoc_MapPath; + void InitHub(void); + void SendLocalUserHudMessage( const char *message ); + void SendLocalUserHudMessage( idStr message ); + void UpdateFog( void ); + void SetPersistentRemove( const char *name ); + void SetPersistentLightOn( const char *name, bool state ); + void SetPersistentLightBroken( const char *name ); + void SetPersistentTrigger( const char *type, const char *name, const bool state ); + void SetPersistentTriggerInt( const char *type, const char *var, const char *name, int val ); + void SavePersistentMoveables(void); +// HEXEN : Zeroth +public: + idStr eoc_LocalPlayerSpawnPoint; + float eoc_MapLoading; + float eoc_MapLoadingPrev; + idStr mapNameForCheat; + idList BanishLocationList; + private: const static int INITIAL_SPAWN_COUNT = 1; const static int INTERNAL_SAVEGAME_VERSION = 1; // DG: added this for >= 1305 savegames @@ -497,6 +566,7 @@ class idGameLocal : public idGame { idStaticList spawnSpots; idStaticList initialSpots; + int currentInitialSpot; idDict newInfo; @@ -658,6 +728,24 @@ typedef enum { extern const float DEFAULT_GRAVITY; extern const idVec3 DEFAULT_GRAVITY_VEC3; +extern const idVec3 DEFAULT_GRAVITY_NORMAL; // for HeXen: EOC extern const int CINEMATIC_SKIP_DELAY; +#if 0 +// DG: Note: all the following includes were added by h:eoc, not sure if they're *really* needed here +#include "script/Script_Interpreter.h" +#include "script/Script_Thread.h" + +// HEXEN : Zeroth +#include "projectiles/Wraithverge.h" +#include "projectiles/FireStorm.h" +#include "projectiles/Soul.h" +#include "ai/AI_Veloxite.h" +#include "ai/AI_Golem.h" +#include "ai/AI_Shadowspawn.h" +#include "objects/Tree.h" +#include "objects/Leaf.h" +#include "objects/LeafEmitter.h" +#endif // 0 + #endif /* !__GAME_LOCAL_H__ */ diff --git a/game/Game_network.cpp b/game/Game_network.cpp index 5e24df70..b3e20c0f 100644 --- a/game/Game_network.cpp +++ b/game/Game_network.cpp @@ -70,6 +70,8 @@ void idGameLocal::InitAsyncNetwork( void ) { for ( i = 0; i < MAX_CLIENTS; i++ ) { for ( type = 0; type < declManager->GetNumDeclTypes(); type++ ) { + + clientDeclRemap[i][type].Clear(); } } @@ -589,6 +591,20 @@ void idGameLocal::ServerWriteSnapshot( int clientNum, int sequence, idBitMsg &ms numSourceAreas = gameRenderWorld->BoundsInAreas( spectated->GetPlayerPhysics()->GetAbsBounds(), sourceAreas, idEntity::MAX_PVS_AREAS ); pvsHandle = gameLocal.pvs.SetupCurrentPVS( sourceAreas, numSourceAreas, PVS_NORMAL ); + // HEXEN : Zeroth + // Add portalSky areas to PVS + if ( portalSkyEnt.GetEntity() ) { + pvsHandle_t otherPVS, newPVS; + idEntity *skyEnt = portalSkyEnt.GetEntity(); + + otherPVS = gameLocal.pvs.SetupCurrentPVS( skyEnt->GetPVSAreas(), skyEnt->GetNumPVSAreas() ); + newPVS = gameLocal.pvs.MergeCurrentPVS( pvsHandle, otherPVS ); + pvs.FreeCurrentPVS( pvsHandle ); + pvs.FreeCurrentPVS( otherPVS ); + pvsHandle = newPVS; + } + + #if ASYNC_WRITE_TAGS idRandom tagRandom; tagRandom.SetSeed( random.RandomInt() ); @@ -1120,6 +1136,19 @@ void idGameLocal::ClientReadSnapshot( int clientNum, int sequence, const int gam numSourceAreas = gameRenderWorld->BoundsInAreas( spectated->GetPlayerPhysics()->GetAbsBounds(), sourceAreas, idEntity::MAX_PVS_AREAS ); pvsHandle = gameLocal.pvs.SetupCurrentPVS( sourceAreas, numSourceAreas, PVS_NORMAL ); + // HEXEN : Zeroth + // Add portalSky areas to PVS + if ( portalSkyEnt.GetEntity() ) { + pvsHandle_t otherPVS, newPVS; + idEntity *skyEnt = portalSkyEnt.GetEntity(); + + otherPVS = gameLocal.pvs.SetupCurrentPVS( skyEnt->GetPVSAreas(), skyEnt->GetNumPVSAreas() ); + newPVS = gameLocal.pvs.MergeCurrentPVS( pvsHandle, otherPVS ); + pvs.FreeCurrentPVS( pvsHandle ); + pvs.FreeCurrentPVS( otherPVS ); + pvsHandle = newPVS; + } + // read the PVS from the snapshot #if ASYNC_WRITE_PVS int serverPVS[idEntity::MAX_PVS_AREAS]; diff --git a/game/Item.cpp b/game/Item.cpp index 97ae9523..1cf692f4 100644 --- a/game/Item.cpp +++ b/game/Item.cpp @@ -29,6 +29,7 @@ If you have questions concerning this license or the applicable additional terms #include "sys/platform.h" #include "renderer/RenderSystem.h" +#include "framework/DeclEntityDef.h" #include "gamesys/SysCvar.h" #include "Player.h" #include "Fx.h" @@ -50,6 +51,18 @@ const idEventDef EV_RespawnFx( "" ); const idEventDef EV_GetPlayerPos( "" ); const idEventDef EV_HideObjective( "", "e" ); const idEventDef EV_CamShot( "" ); +const idEventDef EV_SetNextState( "setNextState", "s" ); +const idEventDef EV_SetState( "setState", "s" ); +const idEventDef EV_GetState( "getState", NULL, 's' ); +// HEXEN : Zeroth +const idEventDef EV_ArtifactStart( "ArtifactStart" ); +const idEventDef EV_ArtifactCoolDown( "ArtifactCoolDown" ); +const idEventDef EV_ArtifactDone( "ArtifactDone" ); +const idEventDef EV_SetArtifactActive( "ArtifactActive", "f" ); +const idEventDef EV_OwnerLaunchProjectiles( "OwnerLaunchProjectiles", "dffff" ); +const idEventDef EV_OwnerCreateProjectile( "OwnerCreateProjectile", NULL, 'e' ); +const idEventDef EV_GetOwner( "GetOwner", NULL, 'e' ); +const idEventDef EV_HideMultiModel( "HideMultiModel" ); CLASS_DECLARATION( idEntity, idItem ) EVENT( EV_DropToFloor, idItem::Event_DropToFloor ) @@ -57,6 +70,18 @@ CLASS_DECLARATION( idEntity, idItem ) EVENT( EV_Activate, idItem::Event_Trigger ) EVENT( EV_RespawnItem, idItem::Event_Respawn ) EVENT( EV_RespawnFx, idItem::Event_RespawnFx ) +// EVENT( EV_SetNextState, idItem::Event_SetNextState ) +// EVENT( EV_SetState, idItem::Event_SetState ) +// EVENT( EV_GetState, idItem::Event_GetState ) +// HEXEN : Zeroth + EVENT( EV_ArtifactStart, idItem::Event_ArtifactStart ) + EVENT( EV_ArtifactDone, idItem::Event_ArtifactDone ) + EVENT( EV_ArtifactCoolDown, idItem::Event_ArtifactCoolDown ) + EVENT( EV_SetArtifactActive, idItem::Event_SetArtifactActive ) + EVENT( EV_OwnerLaunchProjectiles, idItem::Event_OwnerLaunchProjectiles ) + EVENT( EV_OwnerCreateProjectile, idItem::Event_OwnerCreateProjectile ) + EVENT( EV_GetOwner, idItem::Event_GetOwner ) + EVENT( EV_HideMultiModel, idItem::Event_HideMultiModel ) END_CLASS @@ -76,6 +101,12 @@ idItem::idItem() { orgOrigin.Zero(); canPickUp = true; fl.networkSync = true; + + owner=NULL; + DeleteMe = false; + Cooling = false; + ArtifactActive = false; + Processing = false; } /* @@ -88,6 +119,63 @@ idItem::~idItem() { if ( itemShellHandle != -1 ) { gameRenderWorld->FreeEntityDef( itemShellHandle ); } + + if ( multimodel ) { + delete multimodel; + } + + //z.todo: necessary??? +// delete scriptThread; +// DeconstructScriptObject(); +// scriptObject.Free(); +} + +void idItem::Hide( void ) { + if ( !idEntity::IsHidden() ) { + idEntity::Hide(); + if ( multimodel ) { + multimodel->Hide(); + } + } +} + +void idItem::Event_ArtifactStart( void ) { + if (owner != NULL) { + Processing = true; + + owner->RemoveInventoryItem(spawnArgs.GetString("inv_name")); + owner->UpdateHudArtifacts(); + } +} + +void idItem::Event_ArtifactDone( void ) { + if (owner != NULL) { + Cooling=false; + DeleteMe = true; + } +} + +void idItem::Event_ArtifactCoolDown( void ) { + if (owner != NULL) { + Cooling=true; + owner->UpdateHudActiveArtifacts(); + } +} + +void idItem::Event_SetArtifactActive( const float yesorno ) { + if (owner != NULL) { + if (yesorno) { + ArtifactActive=true; + owner->UpdateHudActiveArtifacts(); + }else{ + ArtifactActive=false; + owner->UpdateHudActiveArtifacts(); + } + } +} + +void idItem::Event_GetOwner( void ) { + idThread::ReturnEntity( owner ); } /* @@ -108,6 +196,42 @@ void idItem::Save( idSaveGame *savefile ) const { savefile->WriteInt( inViewTime ); savefile->WriteInt( lastCycle ); savefile->WriteInt( lastRenderViewTime ); + + // HEXEN : Zeroth + savefile->WriteObject( scriptThread ); + savefile->WriteString( waitState ); + + //FIXME: this is unneccesary + idToken token; + // HEXEN : Zeroth + if ( state ) { + idLexer src( state->Name(), idStr::Length( state->Name() ), "idItem::Save" ); + + src.ReadTokenOnLine( &token ); + src.ExpectTokenString( "::" ); + src.ReadTokenOnLine( &token ); + + savefile->WriteString( token ); + } else { + savefile->WriteString( "" ); + } + + // HEXEN : Zeroth + if ( idealState ) { + idLexer src( idealState->Name(), idStr::Length( idealState->Name() ), "idItem::Save" ); + + src.ReadTokenOnLine( &token ); + src.ExpectTokenString( "::" ); + src.ReadTokenOnLine( &token ); + + savefile->WriteString( token ); + } else { + savefile->WriteString( "" ); + } + + // HEXEN : Zeroth + savefile->WriteObject( projectileEnt ); + savefile->WriteObject( multimodel ); } /* @@ -130,6 +254,35 @@ void idItem::Restore( idRestoreGame *savefile ) { savefile->ReadInt( lastRenderViewTime ); itemShellHandle = -1; + + // HEXEN : Zeroth + savefile->ReadObject( reinterpret_cast( scriptThread ) ); + savefile->ReadString( waitState ); + idStr statename; + + // HEXEN : Zeroth + savefile->ReadString( statename ); + if ( statename.Length() > 0 ) { + state = GetScriptFunction( statename ); + } + + // HEXEN : Zeroth + savefile->ReadString( statename ); + if ( statename.Length() > 0 ) { + idealState = GetScriptFunction( statename ); + } + + // HEXEN : Zeroth + const idDeclEntityDef *projectileDef = gameLocal.FindEntityDef( spawnArgs.GetString( "def_projectile" ), false ); + if ( projectileDef ) { + projectileDict = projectileDef->dict; + } else { + projectileDict.Clear(); + } + + // HEXEN : Zeroth + savefile->ReadObject( reinterpret_cast( projectileEnt ) ); + savefile->ReadObject( reinterpret_cast( multimodel ) ); } /* @@ -227,7 +380,7 @@ void idItem::Think( void ) { ang.yaw = ( gameLocal.time & 4095 ) * 360.0f / -4096.0f; SetAngles( ang ); - float scale = 0.005f + entityNumber * 0.00001f; + float scale = 0.005f; org = orgOrigin; org.z += 4.0f + cos( ( gameLocal.time + 2000 ) * scale ) * 4.0f; @@ -236,6 +389,11 @@ void idItem::Think( void ) { } Present(); + + if (!IsHidden() && multimodel) { + multimodel->GetPhysics()->SetOrigin( GetPhysics()->GetOrigin() ); // bob up and down in unison + multimodel->SetAngles(-GetAngles()); // make it spin the opposite direction + } } /* @@ -275,6 +433,30 @@ void idItem::Spawn( void ) { idStr giveTo; idEntity * ent; float tsize; + const char *projectileName; + + state = NULL; + idealState = NULL; + + // HEXEN : Zeroth + // get the projectile + projectileDict.Clear(); + + projectileName = spawnArgs.GetString( "def_projectile" ); + if ( projectileName[0] != '\0' ) { + const idDeclEntityDef *projectileDef = gameLocal.FindEntityDef( projectileName, false ); + if ( !projectileDef ) { + gameLocal.Warning( "Unknown projectile '%s' in item '%s'", projectileName, spawnArgs.GetString("inv_name") ); + } else { + const char *spawnclass = projectileDef->dict.GetString( "spawnclass" ); + idTypeInfo *cls = idClass::GetClass( spawnclass ); + if ( !cls || !cls->IsType( idProjectile::Type ) ) { + gameLocal.Warning( "Invalid spawnclass '%s' on projectile '%s' (used by item '%s')", spawnclass, projectileName, spawnArgs.GetString("inv_name") ); + } else { + projectileDict = projectileDef->dict; + } + } + } if ( spawnArgs.GetBool( "dropToFloor" ) ) { PostEventMS( &EV_DropToFloor, 0 ); @@ -317,6 +499,18 @@ void idItem::Spawn( void ) { lastCycle = -1; itemShellHandle = -1; shellMaterial = declManager->FindMaterial( "itemHighlightShell" ); + + multimodel=NULL; + idStr mstr=spawnArgs.GetString("multimodel"); + + if ( mstr != "" ) { + const idDict *multimodeldef = gameLocal.FindEntityDefDict( mstr.c_str() ); + if (gameLocal.SpawnEntityDef( *multimodeldef, &multimodel ) && multimodel) { + multimodel->GetPhysics()->SetOrigin( GetPhysics()->GetOrigin() ); // bob up and down in unison + multimodel->SetAngles(-GetAngles()); // make it spin the opposite direction + spawnArgs.Set("multimodel_name", multimodel->GetName()); + } + } } /* @@ -346,11 +540,34 @@ bool idItem::GiveToPlayer( idPlayer *player ) { return false; } + bool val=false; + if ( spawnArgs.GetBool( "inv_carry" ) ) { - return player->GiveInventoryItem( &spawnArgs ); + val = player->GiveInventoryItem( this ); + } else { + val = player->GiveItem( this ); + } + + if ( val ) { + if ( spawnArgs.GetString( "scriptobject" ) != "" ) { + // dhewm3 hates leaving that unchecked at runtime. Without these checks, game crashes + const function_t *pickup_message_func = scriptObject.GetFunction( "pickup_message" ); + const function_t *pickup_effect_func = scriptObject.GetFunction( "pickup_effect" ); + + if ( !g_noPickupNotification.GetBool() && !spawnArgs.GetBool( "dontNotifyOnPickup" ) + && pickup_message_func != NULL ) { + CallFunc( "pickup_message" ); + } + + if ( pickup_effect_func != NULL ) { + CallFunc( "pickup_effect" ); + } + } + + gameLocal.SetPersistentRemove(name.c_str()); } - return player->GiveItem( this ); + return val; } /* @@ -359,7 +576,6 @@ idItem::Pickup ================ */ bool idItem::Pickup( idPlayer *player ) { - if ( !GiveToPlayer( player ) ) { return false; } @@ -369,7 +585,9 @@ bool idItem::Pickup( idPlayer *player ) { } // play pickup sound - StartSound( "snd_acquire", SND_CHANNEL_ITEM, 0, false, NULL ); + if ( !g_noPickupNotification.GetBool() ) { + StartSound( "snd_acquire", SND_CHANNEL_ITEM, 0, false, NULL ); + } // trigger our targets ActivateTargets( player ); @@ -508,14 +726,44 @@ idItem::Event_Touch ================ */ void idItem::Event_Touch( idEntity *other, trace_t *trace ) { + idPlayer *player; + if ( !other->IsType( idPlayer::Type ) ) { return; } + player=static_cast(other); + if ( !canPickUp ) { return; } + player->CleanupArtifactItems(); + + //pickup delay for this player, to prevent instant pickup after drop. + + if (player == lastOwner && spawnArgs.GetBool( "eoc_dropped" )) { + if (PickupDelayTime < MS2SEC( gameLocal.realClientTime )) { + return; + } + } + + //I don't know why this happens, but it does. seems the player can hit an item multiple times and trigger this pickupevent before he has a chance to disappear after picking it up the first time. + if (player == owner) { + return; + } + + if (spawnArgs.GetBool( "instantEffect" ) && player->ActiveArtifact(spawnArgs.GetString( "inv_name" ))) { + return; + } + + //don't pickup if we're full on 'em + if (spawnArgs.FindKey("artifact") ) { + if (spawnArgs.GetInt( "max_inventory" ) > 0 && player->InventoryItemQty(spawnArgs.GetString( "inv_name" )) >= spawnArgs.GetInt( "max_inventory" )) { + return; + } + } + Pickup( static_cast(other) ); } @@ -637,9 +885,138 @@ bool idItemPowerup::GiveToPlayer( idPlayer *player ) { return false; } player->GivePowerUp( type, time * 1000 ); + gameLocal.SetPersistentRemove(name.c_str()); return true; } +/* +================ +idItem::Event_OwnerLaunchProjectiles +================ +*/ +void idItem::Event_OwnerLaunchProjectiles( int num_projectiles, float spread, float fuseOffset, float launchPower, float dmgPower ) { + idProjectile *proj; + idEntity *ent; + int i; + idVec3 dir; + float ang; + float spin; + float distance; + trace_t tr; + idVec3 start; + idVec3 muzzle_pos; + idBounds ownerBounds, projBounds; + + idVec3 playerViewOrigin; + idMat3 playerViewAxis; + idVec3 zzero; + + zzero.Zero(); + + playerViewOrigin.Zero(); + playerViewAxis.Zero(); + + playerViewOrigin = owner->firstPersonViewOrigin; + playerViewAxis = owner->firstPersonViewAxis; + + if ( !projectileDict.GetNumKeyVals() ) { + const char *classname = this->spawnArgs.GetString("inv_name"); + gameLocal.Warning( "No projectile defined on '%s'", classname ); + return; + } + + if ( gameLocal.isClient ) { + // predict instant hit projectiles + if ( projectileDict.GetBool( "net_instanthit" ) ) { + float spreadRad = DEG2RAD( spread ); + muzzle_pos = playerViewOrigin + playerViewAxis[ 0 ] * 2.0f; + for( i = 0; i < num_projectiles; i++ ) { + ang = idMath::Sin( spreadRad * gameLocal.random.RandomFloat() ); + spin = (float)DEG2RAD( 360.0f ) * gameLocal.random.RandomFloat(); + dir = playerViewAxis[ 0 ] + playerViewAxis[ 2 ] * ( ang * idMath::Sin( spin ) ) - playerViewAxis[ 1 ] * ( ang * idMath::Cos( spin ) ); + dir.Normalize(); + gameLocal.clip.Translation( tr, muzzle_pos, muzzle_pos + dir * 4096.0f, NULL, mat3_identity, MASK_SHOT_RENDERMODEL, owner ); + if ( tr.fraction < 1.0f ) { + idProjectile::ClientPredictionCollide( this, projectileDict, tr, vec3_origin, true ); + } + } + } + + } else { + + ownerBounds = owner->GetPhysics()->GetAbsBounds(); + + owner->AddProjectilesFired( num_projectiles ); + + float spreadRad = DEG2RAD( spread ); + for( i = 0; i < num_projectiles; i++ ) { + ang = idMath::Sin( spreadRad * gameLocal.random.RandomFloat() ); + spin = (float)DEG2RAD( 360.0f ) * gameLocal.random.RandomFloat(); + dir = playerViewAxis[ 0 ] + playerViewAxis[ 2 ] * ( ang * idMath::Sin( spin ) ) - playerViewAxis[ 1 ] * ( ang * idMath::Cos( spin ) ); + dir.Normalize(); + + if ( projectileEnt ) { + ent = projectileEnt; + ent->Show(); + ent->Unbind(); + projectileEnt = NULL; + } else { + gameLocal.SpawnEntityDef( projectileDict, &ent, false ); + } + + if ( !ent || !ent->IsType( idProjectile::Type ) ) { + const char *projectileName = this->spawnArgs.GetString( "def_projectile" ); + gameLocal.Error( "'%s' is not an idProjectile", projectileName ); + } + + if ( projectileDict.GetBool( "net_instanthit" ) ) { + // don't synchronize this on top of the already predicted effect + ent->fl.networkSync = false; + } + + proj = static_cast(ent); + proj->Create( owner, playerViewOrigin, dir ); + + projBounds = proj->GetPhysics()->GetBounds().Rotate( proj->GetPhysics()->GetAxis() ); + + // make sure the projectile starts inside the bounding box of the owner + if ( i == 0 ) { + muzzle_pos = playerViewOrigin + playerViewAxis[ 0 ] * 2.0f; + if ( ( ownerBounds - projBounds).RayIntersection( muzzle_pos, playerViewAxis[0], distance ) ) { + start = muzzle_pos + distance * playerViewAxis[0]; + } else { + start = ownerBounds.GetCenter(); + } + gameLocal.clip.Translation( tr, start, muzzle_pos, proj->GetPhysics()->GetClipModel(), proj->GetPhysics()->GetClipModel()->GetAxis(), MASK_SHOT_RENDERMODEL, owner ); + muzzle_pos = tr.endpos; + } + + proj->Launch( muzzle_pos, dir, zzero, fuseOffset, launchPower, dmgPower ); + } + + } +} + +/* +================ +idItem::Event_OwnerCreateProjectile +================ +*/ +void idItem::Event_OwnerCreateProjectile( void ) { + if ( !gameLocal.isClient ) { + projectileEnt = NULL; + gameLocal.SpawnEntityDef( projectileDict, &projectileEnt, false ); + if ( projectileEnt ) { + projectileEnt->SetOrigin( GetPhysics()->GetOrigin() ); + projectileEnt->Bind( owner, false ); + projectileEnt->Hide(); + } + idThread::ReturnEntity( projectileEnt ); + } else { + idThread::ReturnEntity( NULL ); + } +} + /* =============================================================================== @@ -712,6 +1089,43 @@ void idObjective::Event_CamShot( ) { renderView_t fullView = *view; fullView.width = SCREEN_WIDTH; fullView.height = SCREEN_HEIGHT; + +// HEXEN : Zeroth + // HACK : always draw sky-portal view if there is one in the map, this isn't real-time + if ( gameLocal.portalSkyEnt.GetEntity() && g_enablePortalSky.GetBool() ) { + renderView_t portalView = fullView; + portalView.vieworg = gameLocal.portalSkyEnt.GetEntity()->GetPhysics()->GetOrigin(); + + // setup global fixup projection vars + if ( 1 ) { + int vidWidth, vidHeight; + idVec2 shiftScale; + + renderSystem->GetGLSettings( vidWidth, vidHeight ); + + float pot; + int temp; + + int w = vidWidth; + for (temp = 1 ; temp < w ; temp<<=1) { + } + pot = (float)temp; + shiftScale.x = (float)w / pot; + + int h = vidHeight; + for (temp = 1 ; temp < h ; temp<<=1) { + } + pot = (float)temp; + shiftScale.y = (float)h / pot; + + fullView.shaderParms[4] = shiftScale.x; + fullView.shaderParms[5] = shiftScale.y; + } + + gameRenderWorld->RenderScene( &portalView ); + renderSystem->CaptureRenderToImage( "_currentRender" ); + } + // draw a view to a texture renderSystem->CropRenderSize( 256, 256, true ); gameRenderWorld->RenderScene( &fullView ); @@ -821,6 +1235,7 @@ bool idVideoCDItem::GiveToPlayer( idPlayer *player ) { if ( player && str.Length() ) { player->GiveVideo( str, &spawnArgs ); } + gameLocal.SetPersistentRemove(name.c_str()); return true; } @@ -845,6 +1260,7 @@ bool idPDAItem::GiveToPlayer(idPlayer *player) { if ( player ) { player->GivePDA( str, &spawnArgs ); } + gameLocal.SetPersistentRemove(name.c_str()); return true; } @@ -1203,6 +1619,7 @@ bool idMoveablePDAItem::GiveToPlayer(idPlayer *player) { if ( player ) { player->GivePDA( str, &spawnArgs ); } + gameLocal.SetPersistentRemove(name.c_str()); return true; } @@ -1356,3 +1773,33 @@ void idObjectiveComplete::Event_HideObjective( idEntity *e ) { } } } + +void idItem::SetOwner( idPlayer *_owner ) { + assert( !owner ); + owner = _owner; + lastOwner = _owner; + //SetName( va( "%s_weapon", owner->name.c_str() ) ); + + //if ( worldModel.GetEntity() ) { + // worldModel.GetEntity()->SetName( va( "%s_weapon_worldmodel", owner->name.c_str() ) ); + //} +} + +bool idItem::CallFunc(const char *funcName) { + const function_t *func = GetScriptFunction( (const char*) funcName ); + if ( !func ) { + assert( 0 ); + gameLocal.Error( "Can't find function use' in object '%s'", scriptObject.GetTypeName() ); + return false; + } + + SetState( func ); + UpdateScript(); + return true; +} + +void idItem::Event_HideMultiModel() { + if ( multimodel ) { + multimodel->Hide(); + } +} diff --git a/game/Item.h b/game/Item.h index acf7c0c9..8d1ff906 100644 --- a/game/Item.h +++ b/game/Item.h @@ -56,6 +56,14 @@ class idItem : public idEntity { virtual bool Pickup( idPlayer *player ); virtual void Think( void ); virtual void Present(); + void Hide(); + +// HEXEN : Zeroth +public: + void SetOwner( idPlayer *owner ); + idPlayer* GetOwner( void ); + idPlayer* GetLastOwner( void ); + bool CallFunc(const char *funcName); enum { EVENT_PICKUP = idEntity::EVENT_MAXEVENTS, @@ -71,6 +79,19 @@ class idItem : public idEntity { virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); +// HEXEN : Zeroth +public: + bool DeleteMe; // whether this artifact should be deleted in the next artifact cleanup (in player.cpp) + bool ArtifactActive; // whether this artifact is active (valid for time-based effects) + bool Processing; // whether this artifacts script is busy + bool Cooling; // whether artifact is in cooldown mode + int PickupDelayTime; // time in seconds for how long we should wait before letting a player pick up the item he dropped (necessary to prevent instant pickup afte drop) + +// HEXEN : Zeroth +private: + idPlayer* owner; + idPlayer* lastOwner; + private: idVec3 orgOrigin; bool spin; @@ -95,6 +116,27 @@ class idItem : public idEntity { void Event_Trigger( idEntity *activator ); void Event_Respawn( void ); void Event_RespawnFx( void ); + +// HEXEN : Zeroth +private: + idDict projectileDict; + idEntity *projectileEnt; +// void Event_GetState( void ); +// void Event_SetState( const char *name ); +// void Event_SetNextState( const char *name ); + void Event_ArtifactStart( void ); + void Event_Artifact( void ); + void Event_ArtifactDone( void ); + void Event_ArtifactCoolDown( void ); + void Event_SetArtifactActive( const float yesorno ); + void Event_OwnerLaunchProjectiles( int num_projectiles, float spread, float fuseOffset, float launchPower, float dmgPower ); + void Event_OwnerCreateProjectile( void ); + void Event_GetOwner( void ); + +//MultiModel + void Event_HideMultiModel( void ); +public: + idEntity *multimodel; }; class idItemPowerup : public idItem { @@ -226,4 +268,12 @@ class idObjectiveComplete : public idItemRemover { void Event_GetPlayerPos(); }; +ID_INLINE idPlayer* idItem::GetOwner( void ) { + return owner; +} + +ID_INLINE idPlayer* idItem::GetLastOwner( void ) { + return lastOwner; +} + #endif /* !__GAME_ITEM_H__ */ diff --git a/game/Light.cpp b/game/Light.cpp index cc24a42d..88e1069a 100644 --- a/game/Light.cpp +++ b/game/Light.cpp @@ -567,6 +567,7 @@ idLight::On ================ */ void idLight::On( void ) { + gameLocal.SetPersistentLightOn(name.c_str(), true); currentLevel = levels; // offset the start time of the shader to sync it to the game time renderLight.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( gameLocal.time ); @@ -584,6 +585,7 @@ idLight::Off ================ */ void idLight::Off( void ) { + gameLocal.SetPersistentLightOn(name.c_str(), false); currentLevel = 0; // kill any sound it was making if ( refSound.referenceSound && refSound.referenceSound->CurrentlyPlaying() ) { @@ -646,6 +648,7 @@ idLight::BecomeBroken ================ */ void idLight::BecomeBroken( idEntity *activator ) { + gameLocal.SetPersistentLightBroken(name.c_str()); const char *damageDefName; fl.takedamage = false; diff --git a/game/Misc.cpp b/game/Misc.cpp index e0c0e89c..14d32eb5 100644 --- a/game/Misc.cpp +++ b/game/Misc.cpp @@ -2393,13 +2393,60 @@ CLASS_DECLARATION( idEntity, idLiquid ) EVENT( EV_Touch, idLiquid::Event_Touch ) END_CLASS +// HEXEN : Zeroth +void idLiquid::Event_Touch( idEntity *other, trace_t *trace ) { +} + +// HEXEN : Zeroth +idLiquid::idLiquid( void ) { + clipModel = NULL; + touchingEntities.Clear(); + flagEntities.Clear(); + //resistTimeEntities.Clear(); + +// HEXEN : Zeroth - for limit entity types + dontTripby_LocalPlayer=false; + limitEntityType=false; + tripby_idPlayer=false; + tripby_idAI=false; + tripby_idMoveable=false; + tripby_idItem=false; + tripby_idActor=false; + tripby_idProjectile=false; + + particleOnExit=false; // whether to spawn a particle when something leaves the water + moveResistance=0; + moveDir.Zero(); + moveAmt=0; +} + /* ================ idLiquid::Save ================ */ void idLiquid::Save( idSaveGame *savefile ) const { - // Nothing to save +// HEXEN : Zeroth + savefile->WriteClipModel( clipModel ); + savefile->WriteBool(limitEntityType); + savefile->WriteBool(dontTripby_LocalPlayer); + savefile->WriteBool(tripby_idPlayer); + savefile->WriteBool(tripby_idAI); + savefile->WriteBool(tripby_idMoveable); + savefile->WriteBool(tripby_idItem); + savefile->WriteBool(tripby_idActor); + savefile->WriteBool(tripby_idProjectile); + savefile->WriteBool(particleOnExit); + savefile->WriteVec3(moveDir); + savefile->WriteFloat(moveAmt); + savefile->WriteFloat(moveResistance); + + savefile->WriteInt(touchingEntities.Num()); + for (int i=0; iWriteString(touchingEntities[i]); + savefile->WriteBool(flagEntities[i]); + } + } /* @@ -2410,6 +2457,32 @@ idLiquid::Restore void idLiquid::Restore( idRestoreGame *savefile ) { //FIXME: NO! Spawn(); + +// HEXEN : Zeroth + savefile->ReadClipModel( clipModel ); + savefile->ReadBool(limitEntityType); + savefile->ReadBool(dontTripby_LocalPlayer); + savefile->ReadBool(tripby_idPlayer); + savefile->ReadBool(tripby_idAI); + savefile->ReadBool(tripby_idMoveable); + savefile->ReadBool(tripby_idItem); + savefile->ReadBool(tripby_idActor); + savefile->ReadBool(tripby_idProjectile); + savefile->ReadBool(particleOnExit); + savefile->ReadVec3(moveDir); + savefile->ReadFloat(moveAmt); + savefile->ReadFloat(moveResistance); + + bool bol; + int num; + idStr str; + savefile->ReadInt(num); + for (int i=0; iReadString(str); + touchingEntities.Append(str); + savefile->ReadBool(bol); + flagEntities.Append(bol); + } } /* @@ -2426,23 +2499,244 @@ void idLiquid::Spawn() { model->Reset(); GetPhysics()->SetContents( CONTENTS_TRIGGER ); */ + +// HEXEN : Zeroth - create clip model. copied from idTrigger_Touch + clipModel = new idClipModel( GetPhysics()->GetClipModel() ); // get the clip model + //GetPhysics()->SetClipModel( NULL, 1.0f ); // remove the collision model from the physics object + BecomeActive( TH_THINK ); + +// HEXEN : Zeroth - for limit entity types + spawnArgs.GetBool( "dontTripby_LocalPlayer", "0", dontTripby_LocalPlayer); + spawnArgs.GetBool( "limitEntityType", "0", limitEntityType); + spawnArgs.GetBool( "tripby_idPlayer", "0", tripby_idPlayer); + spawnArgs.GetBool( "tripby_idAI", "0", tripby_idAI); + spawnArgs.GetBool( "tripby_idMoveable", "0", tripby_idMoveable); + spawnArgs.GetBool( "tripby_idItem", "0", tripby_idItem); + spawnArgs.GetBool( "tripby_idActor", "0", tripby_idActor); + spawnArgs.GetBool( "tripby_idProjectile", "0", tripby_idProjectile); + spawnArgs.GetBool( "doParticleOnExit", "0", particleOnExit); + + splashParticleRipple = NULL; + splashParticleTiny = NULL; + splashParticleSmall = NULL; + splashParticleBig = NULL; + splashParticleHuge = NULL; + + const char *splashName = spawnArgs.GetString( "smoke_ripple" ); + if ( *splashName != '\0' ) { + splashParticleRipple = static_cast( declManager->FindType( DECL_PARTICLE, splashName ) ); + } + + splashName = spawnArgs.GetString( "smoke_splashTiny" ); + if ( *splashName != '\0' ) { + splashParticleTiny = static_cast( declManager->FindType( DECL_PARTICLE, splashName ) ); + } + + splashName = spawnArgs.GetString( "smoke_splashSmall" ); + if ( *splashName != '\0' ) { + splashParticleSmall = static_cast( declManager->FindType( DECL_PARTICLE, splashName ) ); + } + + splashName = spawnArgs.GetString( "smoke_splashBig" ); + if ( *splashName != '\0' ) { + splashParticleBig = static_cast( declManager->FindType( DECL_PARTICLE, splashName ) ); + } + + splashName = spawnArgs.GetString( "smoke_splashHuge" ); + if ( *splashName != '\0' ) { + splashParticleHuge = static_cast( declManager->FindType( DECL_PARTICLE, splashName ) ); + } + + spawnArgs.GetVector( "moveDir", "0 0 0", moveDir); + spawnArgs.GetFloat( "moveAmt", "0", moveAmt); + spawnArgs.GetFloat( "moveResistance", "0", moveResistance); } -/* -================ -idLiquid::Event_Touch -================ -*/ -void idLiquid::Event_Touch( idEntity *other, trace_t *trace ) { - // FIXME: for QuakeCon -/* - idVec3 pos; +// HEXEN : Zeroth +void idLiquid::TouchEntities( void ) { //Z.TODO: this is getting messy, maybe split it into separate entities? + int numClipModels, i, c; + idBounds bounds; + idClipModel *cm, *clipModelList[ MAX_GENTITIES ]; - pos = other->GetPhysics()->GetOrigin() - GetPhysics()->GetOrigin(); - model->IntersectBounds( other->GetPhysics()->GetBounds().Translate( pos ), -10.0f ); -*/ +// HEXEN : Zeroth - for limit entity type + idEntity *entity=NULL; + bool dontSplash; + idVec3 cmOrigin; + idMat3 cmAxis; + cmHandle_t cmHandle; + idVec3 myOrigin; + idMat3 myAxis; + + if ( clipModel == NULL ) { + return; + } + + for ( c=0; cStartSound( "snd_splashexit", SND_CHANNEL_ANY, 0, false, NULL ); + const idSoundShader *shader = declManager->FindSound( spawnArgs.GetString( "snd_splashexit" ) ); + entity->StartSoundShader( shader, SND_CHANNEL_ANY, SSF_GLOBAL, false, NULL ); + + + // spawn the exit splash + if ( particleOnExit ) { +// gameLocal.smokeParticles->EmitSmoke( splashParticle, gameLocal.time, gameLocal.random.CRandomFloat(), entity->GetPhysics()->GetOrigin(), idAngles( 0, gameLocal.random.CRandomFloat() * 360, 0 ).ToMat3() ); + } + + entity->inWater = false; + if ( entity->IsType( idPlayer::Type ) ) { + static_cast< idPlayer* >( entity )->leftWater = gameLocal.time; + } + } + } + + touchingEntities.RemoveIndex(c); + touchingEntities.Condense(); + flagEntities.RemoveIndex(c); + flagEntities.Condense(); + c--; + } else { +// flag all entities as NOT touching, test if they are during the clip tests + flagEntities[c] = false; + } + } + + bounds.FromTransformedBounds( clipModel->GetBounds(), clipModel->GetOrigin(), clipModel->GetAxis() ); + numClipModels = gameLocal.clip.ClipModelsTouchingBounds( bounds, -1, clipModelList, MAX_GENTITIES ); + + for ( i = 0; i < numClipModels; i++ ) { + cm = clipModelList[ i ]; + if ( !cm->IsTraceModel() ) { + continue; + } + + entity = clipModelList[ i ]->GetEntity(); + if ( !entity ) { + continue; + } + + if ( entity->spawnArgs.GetBool("nosplash") ) { + continue; + } + + cmOrigin = cm->GetOrigin(); + cmAxis = cm->GetAxis(); + cmHandle = clipModel->Handle(); + myOrigin = clipModel->GetOrigin(); + myAxis = clipModel->GetAxis(); + + if ( !gameLocal.clip.ContentsModel( cmOrigin, cm, cmAxis, -1, cmHandle, myOrigin, myAxis ) ) { + continue; + } + + // get the intersection point so we know where to spawn the effect + // gameLocal.clip.Translation( trace, cmOrigin, cmOrigin, cm, cmAxis, -1, this ); + + if (limitEntityType) { + if ( ( !tripby_idPlayer && entity->IsType( idPlayer::Type ) ) || + ( !tripby_idAI && entity->IsType( idAI::Type ) ) || + ( !tripby_idActor && entity->IsType( idActor::Type ) ) || + ( !tripby_idProjectile && entity->IsType( idProjectile::Type ) ) || + ( !tripby_idItem && entity->IsType( idItem::Type ) ) || + ( !tripby_idMoveable && entity->IsType( idMoveable::Type ) ) ) { + continue; + } + } + + if ( dontTripby_LocalPlayer && entity->IsType( idPlayer::Type ) && gameLocal.GetLocalPlayer() == ( static_cast( entity ) ) ) { + continue; + } + +// if the entity is still touching, dont splash + dontSplash=false; + for ( c=0; cGetName() ) { + flagEntities[c] = true; // flag it as touching + dontSplash = true; + entity->inWater = true; // if the entity leaves another body of water, it will be set to false. it's still in this one, so keep it true. + break; + } + } + +// if the entity was not in our list, add it. else don't trigger. + if ( !dontSplash ) { + touchingEntities.Append(idStr(entity->GetName())); + flagEntities.Append(true); + } else { + continue; + } + +//if the entity is already in water, dont splash + if ( entity->inWater ) { + continue; + } + + entity->inWater = true; + + if ( !gameLocal.isClient ) { + const idDeclParticle * prt = NULL; + idVec3 v = entity->GetPhysics()->GetLinearVelocity(); + float vel = v.Length(); + + int whichPrt, maxPrt; + float m = entity->GetPhysics()->GetMass(); + // static const unsigned long double const * foo = NULL; // hahaha! + float a = spawnArgs.GetFloat( "rippleObjectMass" ); + float b = spawnArgs.GetFloat( "tinyObjectMass" ); + float c = spawnArgs.GetFloat( "smallObjectMass" ); + float d = spawnArgs.GetFloat( "bigObjectMass" ); + float e = spawnArgs.GetFloat( "largeObjectMass" ); + + if (m <= a ) { + maxPrt = 0; + } else if (m <= b ) { + maxPrt = 1; + } else if (m <= c ) { + maxPrt = 2; + } else if (m <= d ) { + maxPrt = 3; + } else { + maxPrt = 4; + } + + whichPrt = ( vel / spawnArgs.GetFloat( "velocityForMaxiumSplash" ) ) * maxPrt; + + if ( maxPrt == 0) { + prt = splashParticleRipple; + } else if ( whichPrt == 1 || maxPrt == 1) { + prt = splashParticleTiny; + } else if ( whichPrt == 2 || maxPrt == 2 ) { + prt = splashParticleSmall; + } else if ( whichPrt == 3 || maxPrt == 3 ) { + prt = splashParticleBig; + } else if ( whichPrt == 4 || maxPrt == 4 ) { + prt = splashParticleHuge; + } + + if ( maxPrt != 0 ) { + //entity->StartSound( "snd_splash", SND_CHANNEL_ANY, 0, false, NULL ); + const idSoundShader *shader = declManager->FindSound( spawnArgs.GetString( "snd_splash" ) ); + entity->StartSoundShader( shader, SND_CHANNEL_ANY, SSF_GLOBAL, false, NULL ); + } + + if ( prt ) { + gameLocal.smokeParticles->EmitSmoke( prt, gameLocal.time, gameLocal.random.CRandomFloat(), entity->GetPhysics()->GetOrigin(), idAngles( 0, gameLocal.random.CRandomFloat() * 360, 0 ).ToMat3() ); + } + } + } } +// HEXEN : Zeroth +void idLiquid::Think( void ) { + if ( thinkFlags & TH_THINK ) { + TouchEntities(); + } + idEntity::Think(); +} /* =============================================================================== @@ -3244,3 +3538,66 @@ void idPhantomObjects::Think( void ) { BecomeInactive( TH_THINK ); } } + + + +/* +=============================================================================== + +Zeroth +idPortalSky + +=============================================================================== +*/ + +CLASS_DECLARATION( idEntity, idPortalSky ) + EVENT( EV_PostSpawn, idPortalSky::Event_PostSpawn ) + EVENT( EV_Activate, idPortalSky::Event_Activate ) +END_CLASS + +/* +=============== +idPortalSky::idPortalSky +=============== +*/ +idPortalSky::idPortalSky( void ) { + +} + +/* +=============== +idPortalSky::~idPortalSky +=============== +*/ +idPortalSky::~idPortalSky( void ) { + +} + +/* +=============== +idPortalSky::Spawn +=============== +*/ +void idPortalSky::Spawn( void ) { + if ( !spawnArgs.GetBool( "triggered" ) ) { + PostEventMS( &EV_PostSpawn, 1 ); + } +} + +/* +================ +idPortalSky::Event_PostSpawn +================ +*/ +void idPortalSky::Event_PostSpawn() { + gameLocal.SetPortalSkyEnt( this ); +} + +/* +================ +idPortalSky::Event_Activate +================ +*/ +void idPortalSky::Event_Activate( idEntity *activator ) { + gameLocal.SetPortalSkyEnt( this ); +} diff --git a/game/Misc.h b/game/Misc.h index 695e0903..1b1047bf 100644 --- a/game/Misc.h +++ b/game/Misc.h @@ -559,13 +559,44 @@ class idLiquid : public idEntity { public: CLASS_PROTOTYPE( idLiquid ); + idLiquid( void ); // HEXEN : Zeroth + void Spawn( void ); + void Think( void ); // HEXEN : Zeroth void Save( idSaveGame *savefile ) const; void Restore( idRestoreGame *savefile ); private: - void Event_Touch( idEntity *other, trace_t *trace ); + void Event_Touch( idEntity *other, trace_t *trace ); // HEXEN : Zeroth - was commented out + +//zerorh + idClipModel * clipModel; + void TouchEntities( void ); + bool particleOnExit; // whether to spawn a particle when something leaves the water + const idDeclParticle * splashParticleRipple; + const idDeclParticle * splashParticleTiny; + const idDeclParticle * splashParticleSmall; + const idDeclParticle * splashParticleBig; + const idDeclParticle * splashParticleHuge; + float moveResistance; + idVec3 moveDir; + float moveAmt; + idList touchingEntities; // list of entities touching the trigger + idList flagEntities; // used to test if touchingEntities[x] is still touching, if not to be removed from list + //idList resistTimeEntities; + + +// HEXEN : Zeroth - for limit entity types +private: + bool dontTripby_LocalPlayer; + bool limitEntityType; + bool tripby_idPlayer; + bool tripby_idAI; + bool tripby_idMoveable; + bool tripby_idItem; + bool tripby_idActor; + bool tripby_idProjectile; idRenderModelLiquid *model; @@ -776,4 +807,25 @@ class idPhantomObjects : public idEntity { idList lastTargetPos; }; + +/* +=============================================================================== + +Zeroth +idPortalSky + +=============================================================================== +*/ +class idPortalSky : public idEntity { +public: + CLASS_PROTOTYPE( idPortalSky ); + + idPortalSky(); + ~idPortalSky(); + + void Spawn( void ); + void Event_PostSpawn(); + void Event_Activate( idEntity *activator ); +}; + #endif /* !__GAME_MISC_H__ */ diff --git a/game/Moveable.cpp b/game/Moveable.cpp index e3458b34..42d96082 100644 --- a/game/Moveable.cpp +++ b/game/Moveable.cpp @@ -32,6 +32,8 @@ If you have questions concerning this license or the applicable additional terms #include "Fx.h" #include "Moveable.h" +#include "Projectile.h" +#include "SmokeParticles.h" /* =============================================================================== @@ -45,13 +47,19 @@ const idEventDef EV_BecomeNonSolid( "becomeNonSolid" ); const idEventDef EV_SetOwnerFromSpawnArgs( "" ); const idEventDef EV_IsAtRest( "isAtRest", NULL, 'd' ); const idEventDef EV_EnableDamage( "enableDamage", "f" ); +// HEXEN : Zeroth +const idEventDef EV_BecomeSolid( "MovableBecomeSolid" ); +const idEventDef EV_DealDirectDamage( "directDamage", "es" ); CLASS_DECLARATION( idEntity, idMoveable ) EVENT( EV_Activate, idMoveable::Event_Activate ) - EVENT( EV_BecomeNonSolid, idMoveable::Event_BecomeNonSolid ) + EVENT( EV_BecomeSolid, idMoveable::Event_BecomeSolid ) EVENT( EV_SetOwnerFromSpawnArgs, idMoveable::Event_SetOwnerFromSpawnArgs ) EVENT( EV_IsAtRest, idMoveable::Event_IsAtRest ) EVENT( EV_EnableDamage, idMoveable::Event_EnableDamage ) +// HEXEN : Zeroth + EVENT( EV_BecomeNonSolid, idMoveable::Event_BecomeNonSolid ) + EVENT( EV_DealDirectDamage, idMoveable::Event_DirectDamage ) END_CLASS @@ -97,6 +105,7 @@ void idMoveable::Spawn( void ) { float density, friction, bouncyness, mass; int clipShrink; idStr clipModelName; + savePersistentInfo = false; // check if a clip model is set spawnArgs.GetString( "clipmodel", "", clipModelName ); @@ -104,6 +113,11 @@ void idMoveable::Spawn( void ) { clipModelName = spawnArgs.GetString( "model" ); // use the visual model } + // HEXEN : Zeroth +/** if ( !clipModelName[0] && IsType( idWood::Type ) ) { + gameLocal.Error("idWood '%s': Please open map editor and re-save map.", name.c_str() ); + } +**/ if ( !collisionModelManager->TrmFromModel( clipModelName, trm ) ) { gameLocal.Error( "idMoveable '%s': cannot load collision model %s", name.c_str(), clipModelName.c_str() ); return; @@ -138,6 +152,8 @@ void idMoveable::Spawn( void ) { health = spawnArgs.GetInt( "health", "0" ); spawnArgs.GetString( "broken", "", brokenModel ); + spawnArgs.GetBool( "removeWhenBroken", "", removeWhenBroken ); + spawnArgs.GetBool( "call_script_on_broken", "", brokenScript ); // HEXEN : Zeroth if ( health ) { if ( brokenModel != "" && !renderModelManager->CheckModel( brokenModel ) ) { @@ -162,13 +178,13 @@ void idMoveable::Spawn( void ) { physicsObj.SetMass( mass ); } - if ( spawnArgs.GetBool( "nodrop" ) ) { + if ( spawnArgs.GetBool( "nodrop" ) /**|| IsType( idWood::Type ) **/) { // HEXEN : Zeroth, added idWood physicsObj.PutToRest(); } else { physicsObj.DropToFloor(); } - if ( spawnArgs.GetBool( "noimpact" ) || spawnArgs.GetBool( "notPushable" ) ) { + if ( spawnArgs.GetBool( "noimpact" ) || spawnArgs.GetBool( "notPushable" ) /**|| IsType( idWood::Type )**/ ) { // HEXEN : Zeroth, added idWood physicsObj.DisableImpact(); } @@ -187,8 +203,9 @@ idMoveable::Save ================ */ void idMoveable::Save( idSaveGame *savefile ) const { - savefile->WriteString( brokenModel ); + savefile->WriteBool( brokenScript ); + savefile->WriteBool( removeWhenBroken ); savefile->WriteString( damage ); savefile->WriteString( fxCollide ); savefile->WriteInt( nextCollideFxTime ); @@ -215,6 +232,8 @@ void idMoveable::Restore( idRestoreGame *savefile ) { int initialSplineTime; savefile->ReadString( brokenModel ); + savefile->ReadBool( brokenScript ); + savefile->ReadBool( removeWhenBroken ); savefile->ReadString( damage ); savefile->ReadString( fxCollide ); savefile->ReadInt( nextCollideFxTime ); @@ -237,6 +256,7 @@ void idMoveable::Restore( idRestoreGame *savefile ) { savefile->ReadStaticObject( physicsObj ); RestorePhysics( &physicsObj ); + gameLocal.Printf("restore %s\n", name.c_str()); } /* @@ -270,6 +290,8 @@ bool idMoveable::Collide( const trace_t &collision, const idVec3 &velocity ) { float v, f; idVec3 dir; idEntity *ent; + + savePersistentInfo = true; v = -( velocity * collision.c.normal ); if ( v > BOUNCE_SOUND_MIN_VELOCITY && gameLocal.time > nextSoundTime ) { @@ -288,7 +310,7 @@ bool idMoveable::Collide( const trace_t &collision, const idVec3 &velocity ) { f = v > maxDamageVelocity ? 1.0f : idMath::Sqrt( v - minDamageVelocity ) * ( 1.0f / idMath::Sqrt( maxDamageVelocity - minDamageVelocity ) ); dir = velocity; dir.NormalizeFast(); - ent->Damage( this, GetPhysics()->GetClipModel()->GetOwner(), dir, damage, f, INVALID_JOINT ); + ent->Damage( this, GetPhysics()->GetClipModel()->GetOwner(), dir, damage, f, INVALID_JOINT, idVec3( collision.c.point ) ); nextDamageTime = gameLocal.time + 1000; } } @@ -321,6 +343,91 @@ void idMoveable::Killed( idEntity *inflictor, idEntity *attacker, int damage, co } } + // HEXEN : Zeroth + const idSoundShader *shader = declManager->FindSound( spawnArgs.GetString( "snd_break" ) ); + this->StartSoundShader( shader, SND_CHANNEL_ANY, SSF_GLOBAL, false, NULL ); + + // HEXEN : Zeroth + const idDeclParticle * smokeBreak = NULL; + int smokeBreakTime = 0; + const char *smokeName = spawnArgs.GetString( "smoke_break" ); + if ( *smokeName != '\0' ) { + smokeBreak = static_cast( declManager->FindType( DECL_PARTICLE, smokeName ) ); + smokeBreakTime = gameLocal.time; + + // get center of bounds on moveable + idVec3 newDir; + newDir = GetPhysics()->GetBounds()[1] + GetPhysics()->GetBounds()[0]; + newDir *= GetPhysics()->GetClipModel()->GetAxis(); + newDir += GetPhysics()->GetOrigin(); + + gameLocal.smokeParticles->EmitSmoke( smokeBreak, smokeBreakTime, gameLocal.random.CRandomFloat(), newDir, GetPhysics()->GetAxis() ); + } + + // HEXEN : Zeroth + if ( removeWhenBroken ) { + Hide(); + gameLocal.SetPersistentRemove(name.c_str()); + + physicsObj.PutToRest(); + CancelEvents( &EV_Explode ); + CancelEvents( &EV_Activate ); + + if ( spawnArgs.GetBool( "triggerTargets" ) ) { + ActivateTargets( this ); + } + + PostEventMS( &EV_Remove, spawnArgs.GetFloat("fuse") ); + } + + // HEXEN : Zeroth + if ( scriptThread != NULL && brokenScript ) { + const function_t *func = GetScriptFunction( "broken" ); + if ( !func ) { + assert( 0 ); + gameLocal.Error( "Can't find function use' in object '%s'", scriptObject.GetTypeName() ); + return; + } + + SetState( func ); + UpdateScript(); + } + + const idKeyValue *kv = spawnArgs.MatchPrefix( "def_debris" ); + // bool first = true; + while ( kv ) { + const idDict *debris_args = gameLocal.FindEntityDefDict( kv->GetValue(), false ); + if ( debris_args ) { + idEntity *ent; + idVec3 dir; + idDebris *debris; + //if ( first ) { + dir = physicsObj.GetAxis()[1]; + // first = false; + //} else { + dir.x += gameLocal.random.CRandomFloat() * 4.0f; + dir.y += gameLocal.random.CRandomFloat() * 4.0f; + //dir.z = gameLocal.random.RandomFloat() * 8.0f; + //} + dir.Normalize(); + + gameLocal.SpawnEntityDef( *debris_args, &ent, false ); + if ( !ent || !ent->IsType( idDebris::Type ) ) { + gameLocal.Error( "'projectile_debris' is not an idDebris" ); + } + + debris = static_cast(ent); + debris->randomPosInBounds = true; + debris->randomPosEnt = this; + debris->Create( this, physicsObj.GetOrigin(), dir.ToMat3() ); + debris->Launch(); + debris->GetRenderEntity()->shaderParms[ SHADERPARM_TIME_OF_DEATH ] = ( gameLocal.time + 1500 ) * 0.001f; + debris->UpdateVisuals(); + + } + kv = spawnArgs.MatchPrefix( "def_debris", kv ); + } + if ( renderEntity.gui[ 0 ] ) { renderEntity.gui[ 0 ] = NULL; } @@ -350,6 +457,14 @@ void idMoveable::BecomeNonSolid( void ) { physicsObj.SetClipMask( MASK_SOLID | CONTENTS_CORPSE | CONTENTS_MOVEABLECLIP ); } +// HEXEN : Zeroth +void idMoveable::BecomeSolid( void ) { + // set CONTENTS_RENDERMODEL so bullets still collide with the moveable + physicsObj.SetContents( CONTENTS_SOLID ); + physicsObj.SetClipMask( MASK_SOLID | CONTENTS_BODY | CONTENTS_CORPSE | CONTENTS_MOVEABLECLIP ); +} + + /* ================ idMoveable::EnableDamage @@ -469,6 +584,11 @@ void idMoveable::Event_BecomeNonSolid( void ) { BecomeNonSolid(); } +// HEXEN : Zeroth +void idMoveable::Event_BecomeSolid( void ) { + BecomeSolid(); +} + /* ================ idMoveable::Event_Activate @@ -537,6 +657,43 @@ void idMoveable::Event_EnableDamage( float enable ) { canDamage = ( enable != 0.0f ); } +void idMoveable::Event_DirectDamage( idEntity *damageTarget, const char *damageDefName ) { + DirectDamage( damageDefName, damageTarget ); +} + +void idMoveable::DirectDamage( const char *meleeDefName, idEntity *ent ) { + const idDict *meleeDef; + const char *p; + const idSoundShader *shader; + + meleeDef = gameLocal.FindEntityDefDict( meleeDefName, false ); + if ( !meleeDef ) { + gameLocal.Error( "Unknown damage def '%s' on '%s'", meleeDefName, name.c_str() ); + } + + if ( !ent->fl.takedamage ) { + const idSoundShader *shader = declManager->FindSound(meleeDef->GetString( "snd_miss" )); + StartSoundShader( shader, SND_CHANNEL_DAMAGE, 0, false, NULL ); + return; + } + + // + // do the damage + // + p = meleeDef->GetString( "snd_hit" ); + if ( p && *p ) { + shader = declManager->FindSound( p ); + StartSoundShader( shader, SND_CHANNEL_DAMAGE, 0, false, NULL ); + } + + idVec3 kickDir; + meleeDef->GetVector( "kickDir", "0 0 0", kickDir ); + + idVec3 globalKickDir; + globalKickDir = kickDir; // z.todo: not proper + + ent->Damage( this, this, globalKickDir, meleeDefName, 1.0f, INVALID_JOINT, idVec3(0,0,0) ); +} /* =============================================================================== @@ -1074,7 +1231,7 @@ idExplodingBarrel::Damage ================ */ void idExplodingBarrel::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, - const char *damageDefName, const float damageScale, const int location ) { + const char *damageDefName, const float damageScale, const int location, const idVec3 &iPoint ) { const idDict *damageDef = gameLocal.FindEntityDefDict( damageDefName ); if ( !damageDef ) { @@ -1083,7 +1240,7 @@ void idExplodingBarrel::Damage( idEntity *inflictor, idEntity *attacker, const i if ( damageDef->FindKey( "radius" ) && GetPhysics()->GetContents() != 0 && GetBindMaster() == NULL ) { PostEventMS( &EV_Explode, 400 ); } else { - idEntity::Damage( inflictor, attacker, dir, damageDefName, damageScale, location ); + idEntity::Damage( inflictor, attacker, dir, damageDefName, damageScale, location, iPoint ); } } @@ -1201,3 +1358,1263 @@ bool idExplodingBarrel::ClientReceiveEvent( int event, int time, const idBitMsg return idBarrel::ClientReceiveEvent( event, time, msg ); } + + +#if 0 +/********************************************************* + +Zeroth's idWood. + +for breaking boards, rocks, etc. + +*********************************************************/ +/** +CLASS_DECLARATION( idMoveable, idWood ) +// EVENT( EV_Touch, idLiquid::Event_Touch ) +END_CLASS + + +// HEXEN : Zeroth +idWood::idWood( void ) { + geoConstructed = false; +} + +// HEXEN : Zeroth +void idWood::Spawn( void ) { + if (!geoConstructed) { + ConstructGeo(); + } + + BecomeActive( TH_THINK ); +} + +// HEXEN : Zeroth +// SDK does not contain information that allows us to access faces of models +// like (*faces[face])[vertex], so we'll have to build that information here. +void idWood::ConstructGeo( void ) { + int i, v, f, v2, f2; + srfTriangles_t *surf = renderEntity.hModel->Surface(0)->geometry; + + // assign vertices to geo.faces + for (i=0; i < (surf->numIndexes / 3); i++) { + f = geo.faces.New(); + geo.faces[f].plane = &surf->facePlanes[i]; //z.todo: delete geta/b/c/d funcs. + for ( v=0; v < surf->numVerts; v++ ) { + if ( surf->facePlanes[i].Side(surf->verts[v].xyz, ON_EPSILON ) == PLANESIDE_ON ) { + geo.faces[f].verts.Append( surf->verts[v].xyz ); //origin + winding[0].ToVec3() * axis; + } + } + } + geo.faces.Condense(); + +//************ +//merge perpendicular faces that share 2+ verts +//************ + + int same; + for (f=0; f < geo.faces.Num(); f++) { + + int verts = geo.faces[f].verts.Num(); + for ( f2=0; f2 < geo.faces.Num(); f2++ ) { + if ( f == f2 ) { + continue; + } + // perpendicular test + if ( geo.faces[f].plane->Normal() != geo.faces[f2].plane->Normal() ) { + continue; + } + + // test if all vertices on plane + for ( v2=0; v2Side( geo.faces[f2].verts[v2], ON_EPSILON ) != PLANESIDE_ON ) { + continue; + } + } + + // test if share 2 vertices + same=0; + for ( v2=0; v2 < geo.faces[f2].verts.Num() && same < 2; v2++ ) { + if ( geo.faces[f].verts.Find( geo.faces[f2].verts[v2] ) ) { + same++; + } + } + +// same=0; +// for ( v=0; v < verts && same < 2; v++ ) { +// for ( v2=0; v2 < geo.faces[f2].verts.Num() && same < 2; v2++ ) { +// if ( geo.faces[f].verts.Find( geo.faces[f2].verts[v2] ) ) { +// same++; +// } +// } +// } +// + if ( same >= 2 ){ + + for ( v2=0; v2 < geo.faces[f2].verts.Num(); v2++ ) { + geo.faces[f].verts.Append( geo.faces[f2].verts[v2] ); + } + geo.faces[f2].verts.Clear(); + geo.faces.RemoveIndex(f2); + geo.faces.Condense(); + f2--; + } + } + } + +//************ +//delete duplicate vertices +//************ + + for (f=0; f < geo.faces.Num(); f++) { + for ( v=0; v < geo.faces[f].verts.Num(); v++ ) { + for ( i=v+1; i < geo.faces[f].verts.Num(); i++ ) { + if ( idVec3( geo.faces[f].verts[v] - geo.faces[f].verts[i] ).Length() < 0.00001 ) { + geo.faces[f].verts.RemoveIndex(i); + geo.faces[f].verts.Condense(); + i--; + } + } + } + } + + // debuging +// for ( f=0; f < geo.faces.Num(); f++ ) { +// gameLocal.Printf("face %i:\n", f ); +// for ( v=0; v < geo.faces[f].verts.Num(); v++ ) { +// gameLocal.Printf("%i:%i : %f,%f,%f\n", f, v, geo.faces[f].verts[v].x, geo.faces[f].verts[v].y, geo.faces[f].verts[v].z ); +// +// } +// } + + +//************ +//sort vertices clockwise +//************ + + for ( f=0; f < geo.faces.Num(); f++ ) { + bool DoneSorting; + idVec3 h,j; + + for (i=0; i < geo.faces[f].verts.Num(); i++) + { + DoneSorting = true; + + for (v=0; v < geo.faces[f].verts.Num()-2; v++) { + h = geo.faces[f].verts[v+1] - geo.faces[f].verts[0]; + j = geo.faces[f].verts[v+2] - geo.faces[f].verts[0]; + + j = h.Cross(j); + j.Normalize(); + + if ( j * geo.faces[f].plane->Normal() < 0.0f ) { + DoneSorting = false; + + j = geo.faces[f].verts[v+2]; + + geo.faces[f].verts[v+2] = geo.faces[f].verts[v+1]; + geo.faces[f].verts[v+1] = j; + } + } + + if (DoneSorting) { + break; + } + } + } + +//************ +//get center of model +//*********** + + geo.center.Zero(); + int verts=0; + for ( f=0; f < geo.faces.Num(); f++ ) { + verts += geo.faces[f].verts.Num(); + for ( v=0; v < geo.faces[f].verts.Num(); v++ ) { + geo.center += geo.faces[f].verts[v]; + } + } + geo.center /= verts; + +//************ +//get average surface area per side +//************ + + geo.area = 0; + float a,b,c,p; + + for ( f=0; f < geo.faces.Num(); f++ ) { + // get center of the face + geo.faces[f].center.Zero(); + for ( v=0; v < geo.faces[f].verts.Num(); v++ ) { + geo.faces[f].center += geo.faces[f].verts[v]; + } + geo.faces[f].center /= geo.faces[f].verts.Num(); + + // get area of each portion of face + geo.faces[f].area = 0; + for ( v=0; v < geo.faces[f].verts.Num() - 1; v++ ) { + a = ( geo.faces[f].center - geo.faces[f].verts[v] ).Length(); + b = ( geo.faces[f].verts[v] - geo.faces[f].verts[v+1] ).Length(); + c = ( geo.faces[f].verts[v+1] - geo.faces[f].center ).Length(); + p = (a+b+c)/2; + geo.faces[f].area += idMath::Sqrt( p * ( p - a ) * ( p - b ) * ( p - c ) ); + } + gameLocal.Printf("face %i area %f\n", f, geo.faces[f].area); + geo.area += geo.faces[f].area; + } + geo.avgArea = geo.area / geo.faces.Num(); + gameLocal.Printf("tot area %f\n", geo.area); + gameLocal.Printf("areaavg %f\n", geo.avgArea); + +//*********** +//store adjacent sides +//*********** + + // faces sharing at least one vertice with one another are adjacent. + for ( f=0; fGetInt( "damage" ); + + // inform the attacker that they hit someone + attacker->DamageFeedback( this, inflictor, damage ); + if ( damage ) { + // do the damage + health -= damage; + if ( health <= 0 ) { + if ( health < -999 ) { + health = -999; + } + + Killed( inflictor, attacker, damage, dir, location ); + } else { + Pain( inflictor, attacker, damage, dir, location ); + } + } +} + + +bool idWood::CheckFloating( idWood *caller, idWood *chain[], int c ) { + // make sure we aren't caught in a recursive loop with the other touching entities + int numClipModels, t, i; + idBounds bounds; + idClipModel *cm, *clipModelList[ MAX_GENTITIES ]; + idEntity* entity; + + for ( i=0; iGetPhysics()->GetClipModel() ) { + return false; + } + + // add self to chain + chain[c] = this; + c++; + + bounds.FromTransformedBounds( this->GetPhysics()->GetClipModel()->GetBounds(), this->GetPhysics()->GetClipModel()->GetOrigin(), this->GetPhysics()->GetClipModel()->GetAxis() ); + numClipModels = gameLocal.clip.ClipModelsTouchingBounds( bounds, -1, clipModelList, MAX_GENTITIES ); + + // check all touchers to see if they're NOT floating + for ( i = 0; i < numClipModels; i++ ) { + cm = clipModelList[ i ]; + if ( !cm->IsTraceModel() ) { + continue; + } + + entity = clipModelList[ i ]->GetEntity(); + if ( !entity ) { + continue; + } + + if ( !gameLocal.clip.ContentsModel( cm->GetOrigin(), cm, cm->GetAxis(), -1, GetPhysics()->GetClipModel()->Handle(), GetPhysics()->GetClipModel()->GetOrigin(), GetPhysics()->GetClipModel()->GetAxis() ) ) { + continue; + } + + if ( !entity->IsType( idWood::Type ) ) { + return false; + } + + if ( static_cast< idWood* >( entity ) == caller ) { + continue; + } + + if ( static_cast< idWood* >( entity )->spawnArgs.GetBool("stopper") ) { + return false; + } + + if ( static_cast< idWood* >( entity )->health <= 0 ) { + return true; + } + + if ( ! static_cast< idWood* >( entity )->CheckFloating( this, chain, c ) ) { + return false; + } + } + + // if we haven't found a non-floater, we're floating + physicsObj.EnableImpact(); + physicsObj.SetLinearVelocity( idVec3(0,0,-20) ); + BecomeNonSolid(); + return true; +} +**/ + +/* +bool idWood::CheckFloating( idWood &caller, idWood *chain, int c ) { + // make sure we aren't caught in a recursive loop with the other touching entities + for ( i=0; ibounds, -1, entityList, MAX_GENTITIES ); + + // if theres only one toucher, and it's the caller, we're floating + if ( n == 1 && entityList[0] == caller ) { + physicsObj.EnableImpact(); + physicsObj.SetLinearVelocity( idVec3(0,0,1) ); + return true; + } + + // if no touchers.. we're floating + if ( n == 0 ) { + physicsObj.EnableImpact(); + physicsObj.SetLinearVelocity( idVec3(0,0,1) ); + return true; + } + + // add self to chain + chain[n] = this; + c++; + + // check all touchers to see if they're NOT floating + for ( i=0; i < n; i++ ) { + if ( !entityList[i]->IsType( idWood::Type ) ) { + return false; + } + + if ( entityList[i] == caller ) { + continue; + } + + if ( ! static_cast< idWood* >( entityList[i] )->CheckFloating( this, chain, c ) ) { + return false; + } + } + + // if we haven't found a non-floater, we're floating + physicsObj.EnableImpact(); + physicsObj.SetLinearVelocity( idVec3(0,0,1) ); + return true; +} +*/ +// HEXEN : Zeroth +/** +void idWood::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { + idEntity *impactor = NULL; + int f, v, r, end, op; + idVec3 ray; + float dist; + float oldDist = 0; + + physicsObj.EnableImpact(); + physicsObj.SetLinearVelocity( idVec3(0,0,-20) ); + BecomeNonSolid(); + + idWood * chain[ MAX_GENTITIES ]; + CheckFloating( this, chain, 0 ); + + if ( unbindOnDeath ) { + Unbind(); + } + + if ( renderEntity.gui[ 0 ] ) { + renderEntity.gui[ 0 ] = NULL; + } + + ActivateTargets( this ); + fl.takedamage = false; + + +**/ + // spawn new log (for one half) + +/**************************************************** + +for trying to create a new one on teh fly, or using the default model + +***************************************************** + + idDict newDebris; + //newDebris.Copy(spawnArgs); + +// newDebris.Set("material", spawnArgs.GetString("material") ); +// idClipModel *nclip=new idClipModel( GetPhysics()->GetClipModel() ); +// newDebris.Set( "clipmodel", nclip->GetEntity()->GetName() ); +// renderEntity_t *nrend=new renderEntity_t( renderEntity ); +// newDebris.Set( "model", nrend->hModel->Name() ); + + //newDebris.Delete("name"); + //newDebris.Delete("clipmodel"); + idWood *deb = static_cast< idWood * >( gameLocal.SpawnEntityType(idMoveable::Type, &newDebris) ); + idVec3 org( physicsObj.GetOrigin() ); + deb->GetPhysics()->SetOrigin( org ); + deb->GetPhysics()->SetAxis( physicsObj.GetAxis() ); + deb->GetPhysics()->SetClipModel( new idClipModel( GetPhysics()->GetClipModel() ), spawnArgs.GetFloat( "density", "0.5" ) ); + //renderEntity_t nrend( renderEntity ); + //deb->SetModel( nrend.hModel->Name() ); + + //physicsObj.GetClipModel()->LoadModel( modelDefHandle ); + // err: TraceModelForClipModel: clip model 0 on wood_2 is not a trace model + +// idRenderModel *mod; +// mod = renderModelManager->AllocModel(); +// mod->AllocSurfaceTriangles( renderEntity.hModel->Surface(0)->geometry->numVerts, renderEntity.hModel->Surface(0)->geometry->numIndexes ); +// mod->AddSurface( *renderEntity.hModel->Surface(0) ); +// mod->FinishSurfaces(); +// mod->FreeVertexCache(); + + + idRenderModel *mod; + mod = renderModelManager->AllocModel(); + mod->LoadModel(); + + deb->SetModel( mod->Name() ); + deb->UpdateModel(); + deb->UpdateModelTransform(); + deb->UpdateVisuals(); + + srfTriangles_t *debsgeo = deb->renderEntity.hModel->Surface(0)->geometry; + srfTriangles_t *sgeo = renderEntity.hModel->Surface(0)->geometry; + +/**************************************************** + + using woodbreak entity + +**************************************************** + + idWood *deb = static_cast< idWood* >( gameLocal.FindEntity("woodbreak") ); + if ( !deb ) { + return; + } + + //srfTriangles_t *debsgeo = deb->renderEntity.hModel->Surface(0)->geometry; + srfTriangles_t *sgeo = renderEntity.hModel->Surface(0)->geometry; + +/********************************************** + + add splinters to wood + +*********************************************** + + // find the smallest face (short end of the wood) + for ( end=0, f=1; fNormal() * 10 ; + idFixedWinding w; + w.Clear(); + vv = sgeo->verts[ sgeo->indexes[ 0 ] ]; + w.AddPoint( newFace[0] ); + w[0].s = vv.st[0]; + w[0].t = vv.st[1]; + w.AddPoint( newFace[1] ); + w[1].s = vv.st[0]; + w[1].t = vv.st[1]; + w.AddPoint( newFace[2] ); + w[2].s = vv.st[0]; + w[2].t = vv.st[1]; +*/ + /** this DEFINITELY doesnt owrk + sgeo->numVerts += 3; + realloc( sgeo->verts, sgeo->numVerts * sizeof( sgeo->verts ) ); + realloc( sgeo->facePlanes, ( sgeo->numVerts / 3 ) * sizeof( sgeo->facePlanes ) ); + sgeo->verts[ sgeo->numVerts - 3 ].xyz = newFace[0]; + sgeo->verts[ sgeo->numVerts - 2 ].xyz = newFace[1]; + sgeo->verts[ sgeo->numVerts - 1 ].xyz = newFace[2]; + sgeo->facePlanes[ ( sgeo->numVerts / 3 ) - 1 ] = sgeo->facePlanes[ ( sgeo->numVerts / 3 ) - 2 ]; + **/ +/** // using shards... need to figure out how to use them properly. + shard_t *shard = new shard_t; + shard->clipModel = GetPhysics()->GetClipModel(); + shard->droppedTime = -1; + shard->winding = w; + shard->decals.Clear(); + shard->edgeHasNeighbour.AssureSize( w.GetNumPoints(), false ); + shard->neighbours.Clear(); + shard->atEdge = false; + + // setup the physics + shard->physicsObj.SetSelf( this ); + shard->physicsObj.SetClipModel( shard->clipModel, 1 ); + shard->physicsObj.SetMass( GetPhysics()->GetMass() ); + shard->physicsObj.SetOrigin( GetPhysics()->GetOrigin() ); + shard->physicsObj.SetAxis( GetPhysics()->GetAxis() ); + shard->physicsObj.SetBouncyness( 1 ); + shard->physicsObj.SetFriction( 0.6f, 0.6f, 1 ); + shard->droppedTime = gameLocal.time; + shard->physicsObj.SetContents( CONTENTS_RENDERMODEL ); + shard->physicsObj.SetClipMask( MASK_SOLID | CONTENTS_MOVEABLECLIP ); + + //shard->clipModel->SetId( shard->clipModel->GetId(); ); + + BecomeActive( TH_PHYSICS ); +*/ +/********************************************** + + match up geo + +*********************************************** + + idList oneClock; + idList twoClock; + idVec3 h,j; + + + // store vertices in the same direction. doesnt seem to accomplish anything + for ( f=0, r=0; r < sgeo->numVerts; r+=3, f++ ) { + oneClock.Append(true); + // check if side is stored clockwise + h = sgeo->verts[r+1].xyz - sgeo->verts[r].xyz; + j = sgeo->verts[r+2].xyz - sgeo->verts[r].xyz; + + j = h.Cross(j); + j.Normalize(); + + if ( j * sgeo->facePlanes[f].Normal() < 0.0f ) { + oneClock[f] = false; + } + } + + for ( f=0, r=0; r < debsgeo->numVerts; r+=3, f++ ) { + twoClock.Append(true); + // check if side is stored clockwise + h = debsgeo->verts[r+1].xyz - debsgeo->verts[r].xyz; + j = debsgeo->verts[r+2].xyz - debsgeo->verts[r].xyz; + + j = h.Cross(j); + j.Normalize(); + + if ( j * debsgeo->facePlanes[f].Normal() < 0.0f ) { + twoClock[f] = false; + } + } + + for ( f=0, r=0; r < sgeo->numVerts; r+=3, f++ ) { + if ( oneClock[f] == twoClock[f] ) { + debsgeo->verts[r].xyz = sgeo->verts[r].xyz; + debsgeo->verts[r+1].xyz = sgeo->verts[r+1].xyz; + debsgeo->verts[r+2].xyz = sgeo->verts[r+2].xyz; + } else { + debsgeo->verts[r].xyz = sgeo->verts[r+2].xyz; + debsgeo->verts[r+1].xyz = sgeo->verts[r+1].xyz; + debsgeo->verts[r+2].xyz = sgeo->verts[r].xyz; + } + } + + // match vertices + for ( r=0; r < sgeo->numVerts; r++ ) { + debsgeo->verts[r].xyz = sgeo->verts[r].xyz; + } + + // also seems to not help + // match faces + for ( r=0; r < sgeo->numVerts; r+=3 ) { + debsgeo->facePlanes[r].SetDist( sgeo->facePlanes[r].Dist() ); + debsgeo->facePlanes[r].SetNormal( sgeo->facePlanes[r].Normal() ); + } + + Hide(); + + // match indexed + for ( r=0; r numIndexes; r+=3 ) { + for ( v = 0; v < 3; v++ ) { + debsgeo->verts[ sgeo->indexes[ r + 2 - v ] ].st[0] = sgeo->verts[ sgeo->indexes[ r + 2 - v ] ].st[0]; + debsgeo->verts[ sgeo->indexes[ r + 2 - v ] ].st[1] = sgeo->verts[ sgeo->indexes[ r + 2 - v ] ].st[1]; + } + } + + // match tangents and normal + for ( r=0; r numIndexes; r+=3 ) { + debsgeo->verts[r].tangents[0] = sgeo->verts[r].tangents[0]; + debsgeo->verts[r].tangents[1] = sgeo->verts[r].tangents[1]; + debsgeo->verts[r].normal = sgeo->verts[r].normal; + } + + // deb->renderEntity.customShader + //deb->renderEntity.hModel->Surface(0)->shader + deb->renderEntity.customShader = renderEntity.hModel->Surface(0)->shader; + deb->GetPhysics()->SetClipModel( new idClipModel( GetPhysics()->GetClipModel() ), spawnArgs.GetFloat( "density", "0.5" ), 0, true ); + deb->GetPhysics()->SetOrigin( physicsObj.GetOrigin() ); + deb->GetPhysics()->SetAxis( physicsObj.GetAxis() ); + + deb->renderEntity.hModel->FreeVertexCache(); + deb->UpdateModel(); + deb->UpdateModelTransform(); + deb->UpdateVisuals(); + + deb->Show(); + +/**/ + +/********************************************** + + Split in half + +*********************************************** + + // find the smallest face (short end of the wood) + for ( end=0, f=1; f oldDist ) { + op = f; + } + } + + // move shortest face half way to opposing face + for ( v=0; vnumVerts; r++ ) { + //sgeo->verts[r].xyz /= 2; + if ( sgeo->verts[r].xyz != geo.faces[end].verts[v] ) { + continue; + } + sgeo->verts[r].xyz += ( geo.faces[op].center - geo.faces[end].center ) / 2; + } + geo.faces[end].verts[v] += ( geo.faces[op].center - geo.faces[end].center ) / 2; + } + +// // other half: move shortest face half way to opposing face +// for ( v=0; vgeo.faces[op].verts.Num(); v++ ) { +// // update verts in the render entity +// for ( r=0; r < debsgeo->numVerts; r++ ) { +// if ( debsgeo->verts[r].xyz == deb->geo.faces[op].verts[v] ) { +// debsgeo->verts[r].xyz += ( deb->geo.faces[end].center - deb->geo.faces[op].center ) / 2; +// } +// } +// deb->geo.faces[op].verts[v] += ( deb->geo.faces[end].center - deb->geo.faces[op].center ) / 2; +// } + + // store distance + dist = idVec3(geo.faces[end].center - geo.faces[op].center).Length(); + + // update center now + geo.faces[end].center += ( geo.faces[op].center - geo.faces[end].center ) / 2; + deb->geo.faces[op].center += ( deb->geo.faces[end].center - deb->geo.faces[op].center ) / 2; +// currently not functional +// // resize short face and update area +// float newArea = ( ( geo.faces[op].area - geo.faces[end].area ) / dist ) * ( dist / 2 ) + geo.faces[end].area; +// float vertMoveDist = ( newArea / geo.faces[end].area ) / 4; // 4 sides +// geo.faces[end].area = newArea; +// for ( v=0; v < geo.faces[end].verts.Num(); v++ ) { +// idVec3 vertAdd = ( geo.faces[end].verts[v] - geo.faces[end].center ); +// +// vertAdd.Normalize(); +// vertAdd *= vertMoveDist; +// +// // update verts in the render entity +// for ( int r=0; r < sgeo->numVerts; r++ ) { +// if ( sgeo->verts[r].xyz == geo.faces[end].verts[v] ) { +// sgeo->verts[r].xyz += vertAdd; +// } +// } +// +// geo.faces[end].verts[v] += vertAdd; +// } +// +// // other half: resize short face and update area +// newArea = ( ( deb->geo.faces[end].area - deb->geo.faces[op].area ) / dist ) * ( dist / 2 ) + deb->geo.faces[op].area; +// vertMoveDist = ( newArea / deb->geo.faces[op].area ) / 4; // 4 sides +// deb->geo.faces[op].area = newArea; +// for ( v=0; v < deb->geo.faces[op].verts.Num(); v++ ) { +// idVec3 vertAdd = ( deb->geo.faces[op].verts[v] - deb->geo.faces[op].center ); +// +// vertAdd.Normalize(); +// vertAdd *= vertMoveDist; +// +// // update verts in the render entity +// for ( int r=0; r < debsgeo->numVerts; r++ ) { +// if ( debsgeo->verts[r].xyz == deb->geo.faces[op].verts[v] ) { +// debsgeo->verts[r].xyz += vertAdd; +// } +// } +// +// deb->geo.faces[op].verts[v] += vertAdd; +// } + + renderEntity.hModel->FreeVertexCache(); + UpdateModel(); + UpdateModelTransform(); + UpdateVisuals(); + + deb->renderEntity.hModel->FreeVertexCache(); + deb->UpdateModel(); + deb->UpdateModelTransform(); + deb->UpdateVisuals(); + + // other side: update render model's bounds + srfTriangles_t *suu = sgeo; + + int mins=0, maxs=0; + idVec3 min, max; + max.x = min.x = suu->verts[0].xyz.x; + max.y = min.y = suu->verts[0].xyz.y; + max.z = min.z = suu->verts[0].xyz.z; + + for ( v=1; v < suu->numVerts; v++ ) { + if ( suu->verts[v].xyz.x < min.x ) { min.x = suu->verts[v].xyz.x; } + if ( suu->verts[v].xyz.y < min.y ) { min.y = suu->verts[v].xyz.y; } + if ( suu->verts[v].xyz.z < min.z ) { min.z = suu->verts[v].xyz.z; } + if ( suu->verts[v].xyz.x > max.x ) { max.x = suu->verts[v].xyz.x; } + if ( suu->verts[v].xyz.y > max.y ) { max.y = suu->verts[v].xyz.y; } + if ( suu->verts[v].xyz.z > max.z ) { max.z = suu->verts[v].xyz.z; } + } + suu->bounds = idBounds(min, max); + + // create a new clip model based on the new bounds + float density = spawnArgs.GetFloat( "density", "0.5" ); + physicsObj.SetClipModel( new idClipModel( idTraceModel( suu->bounds ) ), density ); + + UpdateModelTransform(); + UpdateVisuals(); + +// // other side: update render model's bounds +// suu = debsgeo; +// +// mins=0, maxs=0; +// max.x = min.x = suu->verts[0].xyz.x; +// max.y = min.y = suu->verts[0].xyz.y; +// max.z = min.z = suu->verts[0].xyz.z; +// +// for ( v=1; v < suu->numVerts; v++ ) { +// if ( suu->verts[v].xyz.x < min.x ) { min.x = suu->verts[v].xyz.x; } +// if ( suu->verts[v].xyz.y < min.y ) { min.y = suu->verts[v].xyz.y; } +// if ( suu->verts[v].xyz.z < min.z ) { min.z = suu->verts[v].xyz.z; } +// if ( suu->verts[v].xyz.x > max.x ) { max.x = suu->verts[v].xyz.x; } +// if ( suu->verts[v].xyz.y > max.y ) { max.y = suu->verts[v].xyz.y; } +// if ( suu->verts[v].xyz.z > max.z ) { max.z = suu->verts[v].xyz.z; } +// } +// suu->bounds = idBounds(min, max); +// +// // other side: create a new clip model based on the new bounds +// density = spawnArgs.GetFloat( "density", "0.5" ); +// deb->physicsObj.SetClipModel( new idClipModel( idTraceModel( suu->bounds ) ), density ); +// +// deb->UpdateModelTransform(); +// deb->UpdateVisuals(); + +/**/ +/** +} +**/ +/************************************************************** + +test code for trying to create new logs + +*/////////////////////////////////////////////////////////////// + + + /******************************** + + remove old model and spawn two halves + + ******************************** + + int f,f2,v,v2,num,lst,end; + + // find the smallest face (short end of the wood) + for ( end=0, f=1; f adjs; + idList< idVec3 > ver; + idList< idVec3 > ver2; + idList< idVec3 > ver3; + + // find all adjacent sides to end + for ( f=0; f( gameLocal.SpawnEntityType(idWood::Type, &newDebris) ); + idVec3 org( physicsObj.GetOrigin() ); + org.z += 50; + deb->GetPhysics()->SetOrigin( org ); + deb->GetPhysics()->SetAxis( physicsObj.GetAxis() ); + + srfTriangles_t *surf = deb->GetRenderEntity()->hModel->Surface(0)->geometry; + + int idx; + idVec3 nw; + + // shorten the new log + // z.todo: we're only ASSUMING that ver and ver2 indexes aren't in mismatched order + for ( v=0; v < surf->numVerts; v++ ) { + idx = ver.FindIndex( surf->verts[v].xyz ); + if ( idx != -1 ) { +/* + // get direction fro upper ring vert to lower ring vert + nw = ver2[idx] - ver[idx]; + + // make the vector about half the length of the log + nw.Normalize(); + nw *= idMath::Sqrt( geo.area / 2 ); // sqrt( area / 2 ) is close enough to half the length of the log + + surf->verts[v].xyz += nw; +/** + } + } + + // remove the old log + // ... + + //gameLocal.Printf("adjs: %i+ 1 = %i\n", adjs.Num(),adjs.Num()+1); + //gameLocal.Printf("faces: %i / 2 = %i\n", geo.faces.Num(), geo.faces.Num() / 2); + +/**/ + + /**************************************** + + CRAP + + *****************************************/ +/* + idList< int > hits; + int bestHit; + + // find surface which is closest to impact point + // z.todo: fuck it. deal with this later. +/* + gameLocal.Printf("math : %f,%f,%f - ", lastiPoint.x,lastiPoint.y,lastiPoint.z); + gameLocal.Printf("%f,%f,%f * ", physicsObj.GetOrigin().x,physicsObj.GetOrigin().y,physicsObj.GetOrigin().z); + gameLocal.Printf("%f,%f,%f\n", -physicsObj.GetAxis().ToAngles().yaw,-physicsObj.GetAxis().ToAngles().pitch,-physicsObj.GetAxis().ToAngles().roll); + gameLocal.Printf("before: %f,%f,%f\n", lastiPoint.x,lastiPoint.y,lastiPoint.z); + lastiPoint = lastiPoint - physicsObj.GetOrigin() * -physicsObj.GetAxis(); + gameLocal.Printf(" after: %f,%f,%f\n", lastiPoint.x,lastiPoint.y,lastiPoint.z); +/**/ + /* + for ( i=0; iNormal() * physicsObj.GetAxis(); +// rightNorm.Normalize(); +// ray = lastiPoint - rightCenter; +// ray.Normalize(); +/* + gameLocal.Printf("\nTesting Face: %i\n", i ); +// gameLocal.Printf(" lastiPoint: %f,%f,%f\n", lastiPoint.x,lastiPoint.y,lastiPoint.z); +// gameLocal.Printf(" geo.center: %f,%f,%f\n", geo.center.x,geo.center.y,geo.center.z ); +// gameLocal.Printf(" WoodCenter: %f,%f,%f\n", rightCenter.x,rightCenter.y,rightCenter.z ); // v->xyz = origin + winding[0].ToVec3() * axis; +// gameLocal.Printf(" face i norm: %f,%f,%f\n", geo.faces[i].plane->Normal().z,geo.faces[i].plane->Normal().y,geo.faces[i].plane->Normal().z ); +// gameLocal.Printf("WFace i Norm: %f,%f,%f\n", rightNorm.z,rightNorm.y,rightNorm.z ); +// gameLocal.Printf(" Ray: %f,%f,%f\n",ray.x,ray.y,ray.z ); +// gameLocal.Printf(" Angle: %f\n", RAD2DEG( idMath::ACos( rightNorm * ray ) ) ); +// gameLocal.Printf(" physaxis: %f,%f,%f\n",physicsObj.GetAxis().ToAngles().yaw,physicsObj.GetAxis().ToAngles().pitch,physicsObj.GetAxis().ToAngles().roll ); +// gameLocal.Printf(" Origin: %f,%f,%f\n",physicsObj.GetOrigin().x,physicsObj.GetOrigin().y,physicsObj.GetOrigin().z ); + + // plane(normal, distance) + /* + float dist = -(geo.faces[i].plane->Normal() * geo.faces[i].verts[0]); + dist = dist + physicsObj.GetOrigin() * physicsObj.GetAxis(); + idPlane plane( geo.faces[i].plane->Normal(), dist) +/**/ + + //pa = physicsObj.GetOrigin() * physicsObj.GetAxis() + + + // make a plane that is where the face is in the world + +/* + gameLocal.Printf("origin: %f,%f,%f\n", physicsObj.GetOrigin().x, physicsObj.GetOrigin().y, physicsObj.GetOrigin().z); + idVec3 or = physicsObj.GetOrigin() * physicsObj.GetAxis(); + gameLocal.Printf("toaxis: %f,%f,%f\n", or.x, or.y, or.z); + gameLocal.Printf("apoint: %f,%f,%f\n", geo.faces[i].verts[0].x, geo.faces[i].verts[0].y, geo.faces[i].verts[0].z); + idVec3 pa = geo.faces[i].verts[0] * physicsObj.GetAxis(); + gameLocal.Printf("toaxis: %f,%f,%f\n", pa.x, pa.y, pa.z); + pa = (physicsObj.GetOrigin() + geo.faces[i].verts[0]); + gameLocal.Printf("added: %f,%f,%f\n\n", pa.x, pa.y, pa.z); + idVec3 no = geo.faces[i].plane->Normal(); + gameLocal.Printf("normal: %f,%f,%f\n\n", geo.faces[i].plane->Normal().x, geo.faces[i].plane->Normal().y, geo.faces[i].plane->Normal().z); + no *= physicsObj.GetAxis(); + gameLocal.Printf("toaxis: %f,%f,%f\n\n", no.x, no.y, no.z); + float dist = -( geo.faces[i].plane->Normal() * pa ); + gameLocal.Printf("dist: %f", dist); + + idPlane plane( no, dist); + + if (plane.Side( lastiPoint, ON_EPSILON ) == PLANESIDE_FRONT ) { +// hits.Append(i); // mark it as a hit +// } + + if (plane.Side( lastiPoint, ON_EPSILON ) == PLANESIDE_ON ) { +// hits.Append(i); // mark it as a hit + } + +/* + if (idMath::ACos( rightNorm * ray ) < DEG2RAD(90) && // if angle between face normal and angle of impact < 90 + geo.faces[i].plane->RayIntersection( lastiPoint, rightCenter - lastiPoint, tmpf ) // and it passes through the face + ) { + + gameLocal.Printf("hit\n"); + hits.Append(i); // mark it as a hit + } */ + //} + +// if ( hits.Num() < 1 ) { +// return; // this should never happen +// } + +// gameLocal.Printf("hits: %i\n", hits.Num() ); + +/* + // find the closest face-center to impact + bestHit = hits[0]; + + for ( i=1; i geo.avgArea * (2/3) ) { +// gameLocal.Printf("Break, side %i, area %f\n", i, geo.faces[i].area); + +// const idDeclParticle *splashParticleTiny = static_cast( declManager->FindType( DECL_PARTICLE, "water_body_splash_tiny" ) ); +// gameLocal.smokeParticles->EmitSmoke( splashParticleTiny, gameLocal.time, gameLocal.random.CRandomFloat(), lastiPoint, idAngles( 0, gameLocal.random.CRandomFloat() * 360, 0 ).ToMat3() ); + + /// spawn two halves, touching where hit occured. same texture. do some math to figure out shape. + /// spawn some shards with somewhat random vertices so they're shaped differently + /// spawn some shards attached to board halves where hit occured (for splints) + /// spawn some brittle fracture + /// remove board +/* } else { + // just spawn some chipped debris + gameLocal.Printf("Cip, side %i, area %f\n", i, geo.faces[i].area); + /// if hit on the short side less than half the length of an adjacent side) + /// spawn largest portion. same texture. do some math to figure out shape. + /// spawn some brittle fracture + } + */ + + + // * amount of debris dependant upon size + // * health dependant upon size + + + +/************************************************************************* + +Test code for trying to create clip models on the fly + +*************************************************************************/ + +/**/ // remove clip model +//physicsObj.DisableClip(); +//delete physicsObj.clipModel; +//physicsObj.clipModel = NULL; +//physicsObj.DisableImpact(); + + +/** // create a new clip model + //FreeModelDef(); + //UpdateVisuals(); + physicsObj.UnlinkClip(); + + idTraceModel trm; + float density, friction, bouncyness, mass; + int clipShrink; + idStr renderModelName = spawnArgs.GetString( "model" ); + + // create clipModel from renderModel + if ( !collisionModelManager->TrmFromModel( renderModelName, trm ) ) { + gameLocal.Error( "idMoveable '%s': cannot load collision model %s", name.c_str(), renderModelName.c_str() ); + return; + } + //trm.bounds = suu->bounds = idBounds(min, max); + + // get rigid body properties + spawnArgs.GetFloat( "density", "0.5", density ); + density = idMath::ClampFloat( 0.001f, 1000.0f, density ); +// spawnArgs.GetFloat( "friction", "0.05", friction ); +// friction = idMath::ClampFloat( 0.0f, 1.0f, friction ); +// spawnArgs.GetFloat( "bouncyness", "0.6", bouncyness ); +// bouncyness = idMath::ClampFloat( 0.0f, 1.0f, bouncyness ); + +// damage = spawnArgs.GetString( "def_damage", "" ); +// canDamage +// minDamageVelocity = spawnArgs.GetFloat( "minDamageVelocity", "300" ); // _D3XP +// maxDamageVelocity = spawnArgs.GetFloat( "maxDamageVelocity", "700" ); // _D3XP + + // setup the physics + //physicsObj.clipModel +// physicsObj.SetSelf( this ); + + // ??? physicsObj.GetClipModel()->CheckModel( const char *name ); + //physicsObj.GetClipModel()->ClearTraceModelCache(); + //physicsObj.GetClipModel()->TraceModelCacheSize(); + //nooope physicsObj.SetClipModel( new idClipModel( trm ), density, 0, true ); + //physicsObj.SetClipModel( new idClipModel( modelDefHandle ), density, 0, true ); //nooope + // noooope + //int hnd = physicsObj.GetClipModel()->GetRenderModelHandle(); + // physicsObj.SetClipModel( new idClipModel( hnd ), density, 0, true ); + + /* nope + idRenderModel *ass( renderEntity.hModel ); + renderEntity_t re(renderEntity); + re.hModel = ass; + + physicsObj.SetClipModel( new idClipModel( gameRenderWorld->AddEntityDef( &re ) ), density, 0, true ); + */ +/* + physicsObj.SetClipModel( new idClipModel( gameRenderWorld->AddEntityDef( &re ) ), density, 0, true ); + physicsObj.GetClipModel()->SetMaterial( GetRenderModelMaterial() ); + + physicsObj.LinkClip(); +*/ + +// physicsObj.SetOrigin( GetPhysics()->GetOrigin() ); +// physicsObj.SetAxis( GetPhysics()->GetAxis() ); +// physicsObj.SetBouncyness( bouncyness ); +// physicsObj.SetFriction( 0.6f, 0.6f, friction ); +// physicsObj.SetGravity( gameLocal.GetGravity() ); +// physicsObj.SetContents( CONTENTS_SOLID ); +// physicsObj.SetClipMask( MASK_SOLID | CONTENTS_BODY | CONTENTS_CORPSE | CONTENTS_MOVEABLECLIP ); +// SetPhysics( &physicsObj ); + +// physicsObj.SetMass( mass ); +// physicsObj.DropToFloor(); +// physicsObj.DisableImpact(); +// BecomeNonSolid(); +// allowStep = spawnArgs.GetBool( "allowStep", "1" ); + +/** // update things again + //UpdateModel(); + UpdateModelTransform(); + UpdateVisuals(); + //renderEntity.forceUpdate; // ? +/**/ + + +/*/ // method 2: remove surfaces, add new ones, + asds + // allocate new surface ( contains all faces of model, init with previous model ) + modelSurface_t sur( *renderEntity.hModel->Surface(0) ); + + // update the bounds + // ... + + // resize the new surfaces + for ( v=0; v < sur.geometry->numVerts; v++ ) { + sur.geometry->verts[v].xyz /= 2; + } + + // disappear the render model +// renderEntity.hModel->PurgeModel(); +// srfTriangles_t tri; +// for ( int i=0, vrt=0; i < sur.geometry->numVerts / 3; i++, vrt++ ) { + //tri.vert + // sur.geometry->nums + // sur.geometry->facePlanes[ + // renderEntity.hModel->FreeSurfaceTriangles( &tri ); +///// renderEntity.hModel->FreeSurfaceTriangles( renderEntity.hModel->Surface(0)->geometry ); +// } + +// assign new surfaces +/// AllocSurfaceTriangles +// renderEntity.hModel->AddSurface( sur ); +/// FinishSurfaces + +// renderEntity.hModel->AddSurface( sur ); // crashed the game... + //renderEntity.hModel->FinishSurfaces(); + + // create the new clip model + //idTraceModel nt( sur.geometry->bounds ); + physicsObj.GetClipModel()->LoadModel( sur.geometry->bounds; +/**/ + + +/*************/ +// this is how to create a renderEntity/renderModel +// cant create renderModel with static models +/* + idRenderModel *newmodel; + renderEntity_t ent; + memset( &ent, 0, sizeof( ent ) ); + ent.bounds.Clear(); + ent.suppressSurfaceInViewID = 0; + ent.numJoints = renderEntity.hModel->NumJoints(); + ent.joints = ( idJointMat * )Mem_Alloc16( ent.numJoints * sizeof( *ent.joints ) ); + newmodel = renderEntity.hModel->InstantiateDynamicModel( &ent, NULL, NULL ); + Mem_Free16( ent.joints ); + ent.joints = NULL; + return; + // newmodel; +/**************/ + + +/* + modelSurface_t sur( *renderEntity.hModel->Surface(0) ); + + for ( v=0; v < sur.geometry->numVerts; v++ ) { + sur.geometry->verts[v].xyz /= 2; + } + + renderEntity.hModel->FreeSurfaceTriangles(); + //PurgeModel(); + renderEntity.hModel->AddSurface( sur ); + //renderEntity.hModel->FinishSurfaces(); + + idTraceModel nt( renderEntity.hModel->Bounds() ); + physicsObj.GetClipModel()->LoadModel( nt ); + + //renderEntity.hModel->InitEmpty( "something" ); + UpdateModel(); + UpdateModelTransform(); + UpdateVisuals(); + //renderEntity.forceUpdate; +/**/ + + //physicsObj.GetClipModel()->LoadModel( modelDefHandle ); + // err: TraceModelForClipModel: clip model 0 on wood_2 is not a trace model + + //renderEntity.hModel = renderModelManager->AllocModel(); + //renderEntity.hModel->InitEmpty( brittleFracture_SnapshotName ); +#endif + diff --git a/game/Moveable.h b/game/Moveable.h index 21f0092a..524b59f2 100644 --- a/game/Moveable.h +++ b/game/Moveable.h @@ -88,8 +88,13 @@ class idMoveable : public idEntity { int nextDamageTime; // next time the movable can hurt the player int nextSoundTime; // next time the moveable can make a sound +// HEXEN : Zeroth +protected: + bool brokenScript; // call ::Broken on killed. + bool removeWhenBroken; // removes entity after it's "fuse" delay + const idMaterial * GetRenderModelMaterial( void ) const; - void BecomeNonSolid( void ); + void InitInitialSpline( int startTime ); bool FollowInitialSplinePath( void ); @@ -98,6 +103,19 @@ class idMoveable : public idEntity { void Event_SetOwnerFromSpawnArgs( void ); void Event_IsAtRest( void ); void Event_EnableDamage( float enable ); + void Event_DirectDamage( idEntity *damageTarget, const char *damageDefName ); + + void DirectDamage( const char *meleeDefName, idEntity *ent ); +// HEXEN : Zeroth +protected: + void Event_BecomeSolid( void ); + +// HEXEN : Zeroth +public: + void BecomeNonSolid( void ); + void BecomeSolid( void ); +public: + bool savePersistentInfo; }; @@ -160,7 +178,7 @@ class idExplodingBarrel : public idBarrel { virtual void Think( void ); virtual void Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, - const char *damageDefName, const float damageScale, const int location ); + const char *damageDefName, const float damageScale, const int location, const idVec3 &iPoint ); virtual void Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; @@ -199,6 +217,66 @@ class idExplodingBarrel : public idBarrel { void Event_Respawn(); void Event_Explode(); void Event_TriggerTargets(); +}; + +#if 0 +// HEXEN : Zeroth +// for idWood and other entities with renderModels that need more vertex/face info +class Destructible_FaceData_t { +public: + // this could be a bit more effiecient with idVec3 pointers that point to surface geometry... + idList< idVec3 > verts; + idVec3 center; // center of face + float area; + idPlane * plane; + idList< int > adjacent; // stores indexes of adjacent faces +// idVec3 worldCenter(void) const; +// idVec3 worldNotm(void) const; +}; + +//ID_INLINE idVec3 faceData_t::worldCenter(void) const { +// return center + physicsObj.GetOrigin() * physicsObj.GetAxis(); +//} + +//ID_INLINE idVec3 faceData_t::worldNorm(void) const { +// return norm + physicsObj.GetOrigin() * physicsObj.GetAxis(); +//} + +class Destructible_ModelData_t { +public: + idList faces; + idVec3 center; + float area; + float avgArea; +// idVec3 worldCenter(void) const; +}; + +//ID_INLINE idVec3 geoData_t::worldCenter(void) const { +// return geo.center + physicsObj.GetOrigin() * physicsObj.GetAxis(); +//} + +// HEXEN : Zeroth +class idWood : public idMoveable { +public: + CLASS_PROTOTYPE( idWood ); + + idWood::idWood( void ); + + void Spawn( void ); + virtual void Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ); + void Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, + const char *damageDefName, const float damageScale, const int location, idVec3 &iPoint ); + bool CheckFloating( idWood *caller, idWood *chain[], int c ); + + bool geoConstructed; + Destructible_ModelData_t geo; + + idVec3 lastiPoint; // last damage position + +private: + void ConstructGeo( void ); + }; +#endif #endif /* !__GAME_MOVEABLE_H__ */ diff --git a/game/Mover.cpp b/game/Mover.cpp index 9f1a8199..9a95373c 100644 --- a/game/Mover.cpp +++ b/game/Mover.cpp @@ -90,6 +90,11 @@ const idEventDef EV_StartSpline( "startSpline", "e" ); const idEventDef EV_StopSpline( "stopSpline", NULL ); const idEventDef EV_IsMoving( "isMoving", NULL, 'd' ); const idEventDef EV_IsRotating( "isRotating", NULL, 'd' ); +const idEventDef EV_EnableClip( "enableClip" ); +const idEventDef EV_DisableClip( "disableClip" ); +const idEventDef EV_CanBecomeSolid( "canBecomeSolid", NULL, 'f' ); +const idEventDef EV_MoverBecomeSolid( "becomeSolid" ); +const idEventDef EV_MoverBecomeNonSolid( "becomeNonSolid" ); CLASS_DECLARATION( idEntity, idMover ) EVENT( EV_FindGuiTargets, idMover::Event_FindGuiTargets ) @@ -131,6 +136,11 @@ CLASS_DECLARATION( idEntity, idMover ) EVENT( EV_Activate, idMover::Event_Activate ) EVENT( EV_IsMoving, idMover::Event_IsMoving ) EVENT( EV_IsRotating, idMover::Event_IsRotating ) + EVENT( EV_EnableClip, idMover::Event_EnableClip ) + EVENT( EV_DisableClip, idMover::Event_DisableClip ) + EVENT( EV_CanBecomeSolid, idMover::Event_CanBecomeSolid ) + EVENT( EV_MoverBecomeSolid, idMover::Event_BecomeSolid ) + EVENT( EV_MoverBecomeNonSolid, idMover::Event_BecomeNonSolid ) END_CLASS /* @@ -345,7 +355,9 @@ void idMover::Spawn( void ) { spawnArgs.GetFloat( "damage" , "0", damage ); dest_position = GetPhysics()->GetOrigin(); + SetPersistentPos(dest_position); dest_angles = GetPhysics()->GetAxis().ToAngles(); + SetPersistentAng(dest_angles); physicsObj.SetSelf( this ); physicsObj.SetClipModel( new idClipModel( GetPhysics()->GetClipModel() ), 1.0f ); @@ -999,7 +1011,7 @@ idMover::Event_PartBlocked */ void idMover::Event_PartBlocked( idEntity *blockingEntity ) { if ( damage > 0.0f ) { - blockingEntity->Damage( this, this, vec3_origin, "damage_moverCrush", damage, INVALID_JOINT ); + blockingEntity->Damage( this, this, vec3_origin, "damage_moverCrush", damage, INVALID_JOINT, idVec3(0,0,0) ); } if ( g_debugMover.GetBool() ) { gameLocal.Printf( "%d: '%s' blocked by '%s'\n", gameLocal.time, name.c_str(), blockingEntity->name.c_str() ); @@ -1034,6 +1046,13 @@ void idMover::Event_SetMoveTime( float time ) { move_time = SEC2MS( time ); } +// HEXEN : Zeroth +/* +void idMover::eoc_SetMoveTime( float time ) { + Event_SetMoveTime( time ); +} +/* + /* ================ idMover::Event_SetAccellerationTime @@ -1071,9 +1090,35 @@ void idMover::Event_MoveTo( idEntity *ent ) { } dest_position = GetLocalCoordinates( ent->GetPhysics()->GetOrigin() ); + SetPersistentPos(dest_position); BeginMove( idThread::CurrentThread() ); } +void idMover::SetPersistentPos( idVec3 &pos ) { + // for removing items/creatures/etc after returning to a level (hubs) + idStr name_str, st; + + name_str=gameLocal.GetMapName(); + name_str+="_mover_pos"; + + gameLocal.persistentLevelInfo.SetVector(name_str, pos); +} + +void idMover::SetPersistentAng( idAngles &ang ) { + // for removing items/creatures/etc after returning to a level (hubs) + idStr name_str, st; + + name_str=gameLocal.GetMapName(); + name_str+="_mover_ang"; + + gameLocal.persistentLevelInfo.SetAngles(name_str, ang); +} + +// HEXEN : Zeroth +/*void idMover::eoc_MoveToPos( const idVec3 &pos ) { + MoveToPos( pos ); +}*/ + /* ================ idMover::MoveToPos @@ -1081,6 +1126,7 @@ idMover::MoveToPos */ void idMover::MoveToPos( const idVec3 &pos ) { dest_position = GetLocalCoordinates( pos ); + SetPersistentPos(dest_position); BeginMove( NULL ); } @@ -1105,6 +1151,7 @@ void idMover::Event_MoveDir( float angle, float distance ) { physicsObj.GetLocalOrigin( org ); VectorForDir( angle, dir ); dest_position = org + dir * distance; + SetPersistentPos(dest_position); BeginMove( idThread::CurrentThread() ); } @@ -1456,6 +1503,10 @@ idMover::Event_Activate ================ */ void idMover::Event_Activate( idEntity *activator ) { + if ( spawnArgs.GetBool( "NoActivateWhenTriggered", "0" ) ) { + return; + } + Show(); Event_StartSpline( this ); } @@ -1535,6 +1586,63 @@ void idMover::SetPortalState( bool open ) { gameLocal.SetPortalState( areaPortal, open ? PS_BLOCK_NONE : PS_BLOCK_ALL ); } +void idMover::Event_EnableClip( void ) { + physicsObj.SetClipMask( MASK_SOLID ); +} + +void idMover::Event_DisableClip( void ) { + physicsObj.SetClipMask( 0 ); +} + +void idMover::Event_CanBecomeSolid( void ) { + idThread::ReturnFloat( CanBecomeSolid() ); +} + +bool idMover::CanBecomeSolid( void ) { + int i; + int num; + idEntity * hit; + idClipModel *cm; + idClipModel *clipModels[ MAX_GENTITIES ]; + + num = gameLocal.clip.ClipModelsTouchingBounds( physicsObj.GetAbsBounds(), MASK_MONSTERSOLID, clipModels, MAX_GENTITIES ); + for ( i = 0; i < num; i++ ) { + cm = clipModels[ i ]; + + // don't check render entities + if ( cm->IsRenderModel() ) { + continue; + } + + hit = cm->GetEntity(); + if ( ( hit == this ) || !hit->fl.takedamage ) { + continue; + } + + if ( physicsObj.ClipContents( cm ) ) { + return false; + } + } + + return true; +} + +void idMover::Event_BecomeSolid( void ) { + BecomeSolid( ); +} + +void idMover::BecomeSolid( void ) { + physicsObj.SetContents( MASK_SOLID ); +} + +void idMover::Event_BecomeNonSolid( void ) { + BecomeNonSolid(); +} + +void idMover::BecomeNonSolid( void ) { + physicsObj.SetContents( 0 ); +} + /* =============================================================================== @@ -3788,7 +3896,7 @@ idDoor::Event_PartBlocked */ void idDoor::Event_PartBlocked( idEntity *blockingEntity ) { if ( damage > 0.0f ) { - blockingEntity->Damage( this, this, vec3_origin, "damage_moverCrush", damage, INVALID_JOINT ); + blockingEntity->Damage( this, this, vec3_origin, "damage_moverCrush", damage, INVALID_JOINT, idVec3(0,0,0) ); } } @@ -4225,7 +4333,7 @@ idPlat::Event_PartBlocked */ void idPlat::Event_PartBlocked( idEntity *blockingEntity ) { if ( damage > 0.0f ) { - blockingEntity->Damage( this, this, vec3_origin, "damage_moverCrush", damage, INVALID_JOINT ); + blockingEntity->Damage( this, this, vec3_origin, "damage_moverCrush", damage, INVALID_JOINT, idVec3(0,0,0) ); } } @@ -4316,7 +4424,7 @@ idMover_Periodic::Event_PartBlocked */ void idMover_Periodic::Event_PartBlocked( idEntity *blockingEntity ) { if ( damage > 0.0f ) { - blockingEntity->Damage( this, this, vec3_origin, "damage_moverCrush", damage, INVALID_JOINT ); + blockingEntity->Damage( this, this, vec3_origin, "damage_moverCrush", damage, INVALID_JOINT, idVec3(0,0,0) ); } } diff --git a/game/Mover.h b/game/Mover.h index 7b955987..c3d6ee4d 100644 --- a/game/Mover.h +++ b/game/Mover.h @@ -66,6 +66,26 @@ class idMover : public idEntity { void SetPortalState( bool open ); + void Event_EnableClip( void ); + void Event_DisableClip( void ); + void Event_BecomeSolid( void ); + void Event_CanBecomeSolid( void ); + void Event_BecomeNonSolid( void ); + bool CanBecomeSolid( void ); + void BecomeSolid( void ); + void BecomeNonSolid( void ); + + // only for use with hub-style map initialization + void SetDestPos( const idVec3 &pos ) { dest_position=pos; } + void SetDestAng( const idAngles &ang ) { dest_angles=ang; } + +// HEXEN : Zeroth +// void eoc_SetMoveTime( float time ); +// void eoc_MoveToPos( const idVec3 &pos ); + + void SetPersistentPos( idVec3 &pos ); + void SetPersistentAng( idAngles &ang ); + protected: typedef enum { ACCELERATION_STAGE, diff --git a/game/Player.cpp b/game/Player.cpp index 8308c961..bf682c61 100644 --- a/game/Player.cpp +++ b/game/Player.cpp @@ -53,6 +53,17 @@ const int ASYNC_PLAYER_INV_CLIP_BITS = -7; // -7 bits to cover the range =============================================================================== */ +// HEXEN : Zeroth +// if the player doesn't move outside of HELLION_DANGER_DIST_MAX distance every so often, spawn hellions near him +idVec3 HELLION_MINS; +idVec3 HELLION_MAXS; +idBounds HELLION_BOUNDS; +int HELLION_DANGER_DIST_MAX; +int HELLION_DANGER_TIME_MIN; +int HELLION_DANGER_TIME_MAX; +int HELLION_DANGER_RECOUP_TIME; +int HELLION_SPAWN_DIST_FROM_PLAYER; + // distance between ladder rungs (actually is half that distance, but this sounds better) const int LADDER_RUNG_DISTANCE = 32; @@ -91,6 +102,35 @@ const idEventDef EV_Player_HideTip( "hideTip" ); const idEventDef EV_Player_LevelTrigger( "levelTrigger" ); const idEventDef EV_SpectatorTouch( "spectatorTouch", "et" ); const idEventDef EV_Player_GetIdealWeapon( "getIdealWeapon", NULL, 's' ); +// ************** Defined in PlayerScriptEvents.cpp: +const idEventDef EV_GiveHealth( "GiveHealth", "f" ); +const idEventDef EV_GiveSpeed( "GiveSpeed", "f" ); +const idEventDef EV_GiveArmor( "GiveArmor", "f" ); +const idEventDef EV_SetHealth( "SetHealth", "f" ); +const idEventDef EV_SetArmor( "SetArmor", "f" ); +const idEventDef EV_GetHealth( "GetHealth", NULL, 'f' ); +const idEventDef EV_GetArmor( "GetArmor", NULL, 'f' ); +const idEventDef EV_GetFullHealth( "GetFullHealth", NULL, 'f' ); +const idEventDef EV_GetFullArmor( "GetFullArmor", NULL, 'f' ); +const idEventDef EV_SetAmmo( "SetAmmo", "sf" ); +const idEventDef EV_GetAmmo( "GetAmmo", "s", 'f' ); +const idEventDef EV_GetFullAmmo( "GetFullAmmo", "s", 'f' ); +const idEventDef EV_GetClass( "GetClass", NULL, 'f' ); +const idEventDef EV_SetInvincible( "SetInvincible", "f" ); +const idEventDef EV_GetInvincible( "GetInvincible", NULL, 'f' ); +const idEventDef EV_ChaosDevice( "ChaosDevice" ); +const idEventDef EV_SetFreeMove( "SetFreeMove", "f" ); +const idEventDef EV_SetViewAngles( "SetViewAngles", "v" ); //does nothing? +const idEventDef EV_GetEyePos( "GetEyePos", NULL, 'v' ); +const idEventDef EV_StickToSurface( "StickToSurface", "v" ); +const idEventDef EV_UnstickToSurface( "UnstickToSurface" ); +const idEventDef EV_StuckToSurface( "StuckToSurface", NULL, 'f'); +const idEventDef EV_SetPowerTome( "SetPowerTome", "f"); +const idEventDef EV_GetPowerTome( "GetPowerTome", NULL, 'f'); +const idEventDef EV_VecForwardP( "VecForwardP", NULL, 'v' ); +const idEventDef EV_VecFacingP( "VecFacingP", NULL, 'v' ); +const idEventDef EV_HudMessage( "HudMessage", "s" ); +const idEventDef EV_ArtifactUseFlash( "ArtifactUseFlash" ); CLASS_DECLARATION( idActor, idPlayer ) EVENT( EV_Player_GetButtons, idPlayer::Event_GetButtons ) @@ -111,6 +151,36 @@ CLASS_DECLARATION( idActor, idPlayer ) EVENT( EV_Player_LevelTrigger, idPlayer::Event_LevelTrigger ) EVENT( EV_Gibbed, idPlayer::Event_Gibbed ) EVENT( EV_Player_GetIdealWeapon, idPlayer::Event_GetIdealWeapon ) +// ************** Defined in PlayerScriptEvents.cpp: + EVENT( EV_GiveHealth, idPlayer::Event_GiveHealth ) + EVENT( EV_GiveSpeed, idPlayer::Event_GiveSpeed ) + EVENT( EV_GiveArmor, idPlayer::Event_GiveArmor ) + EVENT( EV_GetHealth, idPlayer::Event_GetHealth ) + EVENT( EV_GetFullHealth, idPlayer::Event_GetFullHealth ) + EVENT( EV_GetArmor, idPlayer::Event_GetArmor ) + EVENT( EV_GetFullArmor, idPlayer::Event_GetFullArmor ) + EVENT( EV_SetHealth, idPlayer::Event_SetHealth ) + EVENT( EV_SetArmor, idPlayer::Event_SetArmor ) + EVENT( EV_SetAmmo, idPlayer::Event_SetAmmo ) + EVENT( EV_GetAmmo, idPlayer::Event_GetAmmo ) + EVENT( EV_GetFullAmmo, idPlayer::Event_GetFullAmmo ) + EVENT( EV_GetClass, idPlayer::Event_GetClass ) + EVENT( EV_SetInvincible, idPlayer::Event_SetInvincible ) + EVENT( EV_GetInvincible, idPlayer::Event_GetInvincible ) + EVENT( EV_ChaosDevice, idPlayer::Event_ChaosDevice ) + EVENT( EV_SetFreeMove, idPlayer::Event_SetFreeMove ) + EVENT( EV_SetViewAngles, idPlayer::Event_SetViewAngles ) + EVENT( EV_GetEyePos, idPlayer::Event_GetEyePos ) + EVENT( EV_StickToSurface, idPlayer::Event_StickToSurface ) + EVENT( EV_UnstickToSurface, idPlayer::Event_UnstickToSurface ) + EVENT( EV_StuckToSurface, idPlayer::Event_StuckToSurface ) + EVENT( EV_GetPowerTome, idPlayer::Event_GetPowerTome ) + EVENT( EV_SetPowerTome, idPlayer::Event_SetPowerTome ) + EVENT( EV_VecForwardP, idPlayer::Event_VecForwardP ) + EVENT( EV_VecFacingP, idPlayer::Event_VecFacingP ) + EVENT( EV_HudMessage, idPlayer::Event_HudMessage ) + EVENT( EV_ArtifactUseFlash, idPlayer::Event_ArtifactUseFlash ) +// ************** END_CLASS const int MAX_RESPAWN_TIME = 10000; @@ -149,7 +219,11 @@ void idInventory::Clear( void ) { ClearPowerUps(); // set to -1 so that the gun knows to have a full clip the first time we get it and at the start of the level - memset( clip, -1, sizeof( clip ) ); +// HEXEN : Zeroth. You can't memset floats as -1, else you get -1.#QNAN0 values. + //memset( clip, -1, sizeof( clip ) ); + for (int i=0; i< MAX_WEAPONS; i++) { + clip[i]=-1; + } items.DeleteContents( true ); memset(pdasViewed, 0, 4 * sizeof( pdasViewed[0] ) ); @@ -232,20 +306,39 @@ void idInventory::GetPersistantData( idDict &dict ) { int i; int num; idDict *item; - idStr key; + idStr key,str,clas; const idKeyValue *kv; const char *name; - // armor - dict.SetInt( "armor", armor ); + dict.SetInt( "eoc_Class", Class ); - // don't bother with powerups, maxhealth, maxarmor, or the clip + switch( Class ) { + case MAGE: { + clas="mage"; + break; + } + case FIGHTER: { + clas="fighter"; + break; + } + default: { + clas="cleric"; + break; + } + } + + dict.SetInt( idStr("maxhealth_")+clas, maxHealth ); + dict.SetInt( idStr("armor_")+clas, armor ); + dict.SetInt( idStr("maxarmor_")+clas, maxarmor ); + dict.SetInt( idStr("deplete_armor_")+clas, deplete_armor ); + dict.SetFloat( idStr("deplete_rate_")+clas, deplete_rate ); + dict.SetInt( idStr("deplete_ammount_")+clas, deplete_ammount ); // ammo for( i = 0; i < AMMO_NUMTYPES; i++ ) { name = idWeapon::GetAmmoNameForNum( ( ammo_t )i ); if ( name ) { - dict.SetInt( name, ammo[ i ] ); + dict.SetFloat( name, ammo[ i ] ); } } @@ -254,6 +347,12 @@ void idInventory::GetPersistantData( idDict &dict ) { for( i = 0; i < items.Num(); i++ ) { item = items[ i ]; + // artifacts are dealt with elsewhere + str=item->GetString("artifact"); + if ( idStr::Icmp( str, "" ) ) { + continue; + } + // copy all keys with "inv_" kv = item->MatchPrefix( "inv_" ); if ( kv ) { @@ -322,19 +421,34 @@ void idInventory::RestoreInventory( idPlayer *owner, const idDict &dict ) { int num; idDict *item; idStr key; - idStr itemname; + idStr itemname,clas; const idKeyValue *kv; const char *name; Clear(); + Class = dict.GetInt( "eoc_Class" ); + + switch( Class ) { + case MAGE: { + clas="mage"; + break; + } + case FIGHTER: { + clas="fighter"; + break; + } + default: { + clas="cleric"; + break; + } + } - // health/armor - maxHealth = dict.GetInt( "maxhealth", "100" ); - armor = dict.GetInt( "armor", "50" ); - maxarmor = dict.GetInt( "maxarmor", "100" ); - deplete_armor = dict.GetInt( "deplete_armor", "0" ); - deplete_rate = dict.GetFloat( "deplete_rate", "2.0" ); - deplete_ammount = dict.GetInt( "deplete_ammount", "1" ); + maxHealth = dict.GetInt( idStr("maxhealth_")+clas, "44" ); + armor = dict.GetInt( idStr("armor_")+clas, "44" ); + maxarmor = dict.GetInt( idStr("maxarmor_")+clas, "44" ); + deplete_armor = dict.GetInt( idStr("deplete_armor_")+clas, "0" ); + deplete_rate = dict.GetFloat( idStr("deplete_rate_")+clas, "0" ); + deplete_ammount = dict.GetInt( idStr("deplete_ammount_")+clas, "0" ); // the clip and powerups aren't restored @@ -342,7 +456,7 @@ void idInventory::RestoreInventory( idPlayer *owner, const idDict &dict ) { for( i = 0; i < AMMO_NUMTYPES; i++ ) { name = idWeapon::GetAmmoNameForNum( ( ammo_t )i ); if ( name ) { - ammo[ i ] = dict.GetInt( name ); + ammo[ i ] = dict.GetFloat( name ); } } @@ -426,7 +540,9 @@ idInventory::Save */ void idInventory::Save( idSaveGame *savefile ) const { int i; - + + // HEXEN : Zeroth + savefile->WriteInt( Class ); savefile->WriteInt( maxHealth ); savefile->WriteInt( weapons ); savefile->WriteInt( powerups ); @@ -439,10 +555,10 @@ void idInventory::Save( idSaveGame *savefile ) const { savefile->WriteInt( nextArmorDepleteTime ); for( i = 0; i < AMMO_NUMTYPES; i++ ) { - savefile->WriteInt( ammo[ i ] ); + savefile->WriteFloat( ammo[ i ] ); } for( i = 0; i < MAX_WEAPONS; i++ ) { - savefile->WriteInt( clip[ i ] ); + savefile->WriteFloat( clip[ i ] ); } for( i = 0; i < MAX_POWERUPS; i++ ) { savefile->WriteInt( powerupEndTime[ i ] ); @@ -523,6 +639,8 @@ idInventory::Restore void idInventory::Restore( idRestoreGame *savefile ) { int i, num; + // HEXEN : Zeroth + savefile->ReadInt( Class ); savefile->ReadInt( maxHealth ); savefile->ReadInt( weapons ); savefile->ReadInt( powerups ); @@ -535,20 +653,28 @@ void idInventory::Restore( idRestoreGame *savefile ) { savefile->ReadInt( nextArmorDepleteTime ); for( i = 0; i < AMMO_NUMTYPES; i++ ) { - savefile->ReadInt( ammo[ i ] ); + savefile->ReadFloat( ammo[ i ] ); } for( i = 0; i < MAX_WEAPONS; i++ ) { - savefile->ReadInt( clip[ i ] ); + savefile->ReadFloat( clip[ i ] ); } for( i = 0; i < MAX_POWERUPS; i++ ) { savefile->ReadInt( powerupEndTime[ i ] ); } savefile->ReadInt( num ); + idStr str; for( i = 0; i < num; i++ ) { idDict *itemdict = new idDict; savefile->ReadDict( itemdict ); + + // artifacts are dealt with elsewhere + str=itemdict->GetString("artifact"); + if ( idStr::Icmp( str, "" ) ) { + continue; + } + items.Append( itemdict ); } @@ -650,7 +776,7 @@ idInventory::AmmoIndexForAmmoClass ============== */ int idInventory::MaxAmmoForAmmoClass( idPlayer *owner, const char *ammo_classname ) const { - return owner->spawnArgs.GetInt( va( "max_%s", ammo_classname ), "0" ); + return owner->spawnArgs.GetFloat( va( "max_%s", ammo_classname ), "0" ); } /* @@ -692,13 +818,13 @@ int idInventory::WeaponIndexForAmmoClass( const idDict & spawnArgs, const char * idInventory::AmmoIndexForWeaponClass ============== */ -ammo_t idInventory::AmmoIndexForWeaponClass( const char *weapon_classname, int *ammoRequired ) { +ammo_t idInventory::AmmoIndexForWeaponClass( const char *weapon_classname, float *ammoRequired ) { const idDeclEntityDef *decl = gameLocal.FindEntityDef( weapon_classname, false ); if ( !decl ) { gameLocal.Error( "Unknown weapon in decl '%s'", weapon_classname ); } if ( ammoRequired ) { - *ammoRequired = decl->dict.GetInt( "ammoRequired" ); + *ammoRequired = decl->dict.GetFloat( "ammoRequired" ); } ammo_t ammo_i = AmmoIndexForAmmoClass( decl->dict.GetString( "ammoType" ) ); return ammo_i; @@ -736,25 +862,74 @@ bool idInventory::Give( idPlayer *owner, const idDict &spawnArgs, const char *st const char *end; int len; idStr weaponString; - int max; + float fmax; const idDeclEntityDef *weaponDecl; bool tookWeapon; int amount; + float famount; idItemInfo info; const char *name; + // HEXEN : Zeroth + int green, blue, combined; if ( !idStr::Icmpn( statname, "ammo_", 5 ) ) { i = AmmoIndexForAmmoClass( statname ); - max = MaxAmmoForAmmoClass( owner, statname ); - if ( ammo[ i ] >= max ) { - return false; - } - amount = atoi( value ); - if ( amount ) { - ammo[ i ] += amount; - if ( ( max > 0 ) && ( ammo[ i ] > max ) ) { - ammo[ i ] = max; + famount = atof( value ); + + if ( famount ) { + bool rtrn = true; + + // if its combined mana, add the number to blue, green, and combined. + combined = idWeapon::GetAmmoNumForName("ammo_combinedmana"); + blue = idWeapon::GetAmmoNumForName("ammo_bluemana"); + green = idWeapon::GetAmmoNumForName("ammo_greenmana"); + + if (i == combined) { + fmax = MaxAmmoForAmmoClass( owner, "ammo_bluemana" ); + if ( ammo[ blue ] >= fmax ) { + rtrn = false; + } else { + ammo[ blue ] += famount; + + if (ammo[ blue ] > fmax) { + ammo[ blue ] = fmax; + } + } + + fmax = MaxAmmoForAmmoClass( owner, "ammo_greenmana" ); + if ( ammo[ green ] >= fmax ) { + rtrn = false; + } else { + rtrn = true; + ammo[ green ] += famount; + + if (ammo[ green ] > fmax) { + ammo[ green ] = fmax; + } + } + }else{ + fmax = MaxAmmoForAmmoClass( owner, statname ); + + if ( ammo[ i ] >= fmax ) { + return false; + } + + ammo[ i ] += famount; + if ( ( fmax > 0 ) && ( ammo[ i ] > fmax ) ) { + ammo[ i ] = fmax; + } + } + + if (ammo[ green ] < ammo[ blue ]) { + ammo[ combined ] = ammo[ green ]; + }else{ + ammo[ combined ] = ammo[ blue ]; + } + + if ( rtrn == false ) { + return false; } + ammoPulse = true; name = AmmoPickupNameForIndex( i ); @@ -762,18 +937,65 @@ bool idInventory::Give( idPlayer *owner, const idDict &spawnArgs, const char *st AddPickupName( name, "" ); } } - } else if ( !idStr::Icmp( statname, "armor" ) ) { - if ( armor >= maxarmor ) { - return false; // can't hold any more, so leave the item - } - amount = atoi( value ); - if ( amount ) { - armor += amount; - if ( armor > maxarmor ) { - armor = maxarmor; + } else if ( !idStr::Icmpn( statname, "armor", 5 ) ) { + if ( !idStr::Icmp( statname, "armor_cleric" ) &&Class == CLERIC ) { + if ( armor >= maxarmor ) { + return false; // can't hold any more, so leave the item + } + + amount = atoi( value ); + if ( amount ) { + armor += amount; + if ( armor > maxarmor ) { + armor = maxarmor; + } + nextArmorDepleteTime = 0; + armorPulse = true; + } + } else if ( !idStr::Icmp( statname, "armor_fighter" ) &&Class == FIGHTER ) { + if ( armor >= maxarmor ) { + return false; // can't hold any more, so leave the item + } + + amount = atoi( value ); + if ( amount ) { + armor += amount; + if ( armor > maxarmor ) { + armor = maxarmor; + } + nextArmorDepleteTime = 0; + armorPulse = true; + } + } else if ( !idStr::Icmp( statname, "armor_mage" ) &&Class == MAGE ) { + if ( armor >= maxarmor ) { + return false; // can't hold any more, so leave the item + } + + amount = atoi( value ); + if ( amount ) { + armor += amount; + if ( armor > maxarmor ) { + armor = maxarmor; + } + nextArmorDepleteTime = 0; + armorPulse = true; + } + } else if ( !idStr::Icmp( statname, "armor" ) ) { + if ( armor >= maxarmor ) { + return false; // can't hold any more, so leave the item + } + + amount = atoi( value ); + if ( amount ) { + armor += amount; + if ( armor > maxarmor ) { + armor = maxarmor; + } + nextArmorDepleteTime = 0; + armorPulse = true; } - nextArmorDepleteTime = 0; - armorPulse = true; + } else { + return false; } } else if ( idStr::FindText( statname, "inclip_" ) == 0 ) { i = WeaponIndexForAmmoClass( spawnArgs, statname + 7 ); @@ -815,7 +1037,8 @@ bool idInventory::Give( idPlayer *owner, const idDict &spawnArgs, const char *st // don't pickup "no ammo" weapon types twice // not for D3 SP .. there is only one case in the game where you can get a no ammo // weapon when you might already have it, in that case it is more conistent to pick it up - if ( gameLocal.isMultiplayer && weaponDecl && ( weapons & ( 1 << i ) ) && !weaponDecl->dict.GetInt( "ammoRequired" ) ) { + + if ( gameLocal.isMultiplayer && weaponDecl && ( weapons & ( 1 << i ) ) && !weaponDecl->dict.GetFloat( "ammoRequired" ) ) { continue; } @@ -875,6 +1098,11 @@ void idInventory::Drop( const idDict &spawnArgs, const char *weapon_classname, i if ( ammo_i ) { clip[ weapon_index ] = -1; ammo[ ammo_i ] = 0; + // HEXEN : Zeroth + if ( ammo_i == idWeapon::GetAmmoNumForName("ammo_combinedmana") ) { + ammo[ idWeapon::GetAmmoNumForName("ammo_bluemana") ] = 0; + ammo[ idWeapon::GetAmmoNumForName("ammo_greenmana") ] = 0; + } } } @@ -883,7 +1111,7 @@ void idInventory::Drop( const idDict &spawnArgs, const char *weapon_classname, i idInventory::HasAmmo =============== */ -int idInventory::HasAmmo( ammo_t type, int amount ) { +float idInventory::HasAmmo( ammo_t type, float amount ) { if ( ( type == 0 ) || !amount ) { // always allow weapons that don't use ammo to fire return -1; @@ -894,8 +1122,10 @@ int idInventory::HasAmmo( ammo_t type, int amount ) { return -1; } + DoCombinedMana(); + // return how many shots we can fire - return ammo[ type ] / amount; + return (int) ( ammo[ type ] / amount ); } /* @@ -903,28 +1133,60 @@ int idInventory::HasAmmo( ammo_t type, int amount ) { idInventory::HasAmmo =============== */ -int idInventory::HasAmmo( const char *weapon_classname ) { - int ammoRequired; +float idInventory::HasAmmo( const char *weapon_classname ) { + float ammoRequired; ammo_t ammo_i = AmmoIndexForWeaponClass( weapon_classname, &ammoRequired ); return HasAmmo( ammo_i, ammoRequired ); } +// HEXEN : Zeroth +void idInventory::DoCombinedMana( void ) { + int blue, green, combined; + + combined = idWeapon::GetAmmoNumForName("ammo_combinedmana"); + green = idWeapon::GetAmmoNumForName("ammo_greenmana"); + blue = idWeapon::GetAmmoNumForName("ammo_bluemana"); + + // ammo_combinedmana is a special ammo type. + if (ammo[ green ] < ammo[ blue ]) { + ammo[ combined ] = ammo[ green ]; + }else{ + ammo[ combined ] = ammo[ blue ]; + } +} + /* =============== idInventory::UseAmmo =============== */ -bool idInventory::UseAmmo( ammo_t type, int amount ) { - if ( !HasAmmo( type, amount ) ) { +bool idInventory::UseAmmo( ammo_t type, float amount ) { + int blue, green, combined; + + if ( HasAmmo( type, amount ) < 1 ) { return false; } // take an ammo away if not infinite + + // HEXEN : Zeroth + combined = idWeapon::GetAmmoNumForName("ammo_combinedmana"); + green = idWeapon::GetAmmoNumForName("ammo_greenmana"); + blue = idWeapon::GetAmmoNumForName("ammo_bluemana"); + if ( ammo[ type ] >= 0 ) { - ammo[ type ] -= amount; - ammoPredictTime = gameLocal.time; // mp client: we predict this. mark time so we're not confused by snapshots + if (type == combined) { + ammo[ blue ] -= amount; + ammo[ green ] -= amount; + ammo[ combined ] -= amount; + }else{ + ammo[ type ] -= amount; +// ammoPredictTime = gameLocal.time; // mp client: we predict this. mark time so we're not confused by snapshots + } } + ammoPredictTime = gameLocal.time; // mp client: we predict this. mark time so we're not confused by snapshots + return true; } @@ -958,6 +1220,13 @@ idPlayer::idPlayer() { noclip = false; godmode = false; + // HEXEN : Zeroth + invincible = false; + speedMod = 0; + FreeMove = false; + BeltSelection = -1; + BeltPosition = -1; + spawnAnglesSet = false; spawnAngles = ang_zero; viewAngles = ang_zero; @@ -976,15 +1245,11 @@ idPlayer::idPlayer() { hud = NULL; objectiveSystem = NULL; objectiveSystemOpen = false; - - heartRate = BASE_HEARTRATE; - heartInfo.Init( 0, 0, 0, 0 ); - lastHeartAdjust = 0; - lastHeartBeat = 0; + customGuiOpen = false; + customGui = NULL; lastDmgTime = 0; deathClearContentsTime = 0; lastArmorPulse = -10000; - stamina = 0.0f; healthPool = 0.0f; nextHealthPulse = 0; healthPulse = false; @@ -1148,6 +1413,7 @@ void idPlayer::LinkScriptVariables( void ) { AI_STRAFE_LEFT.LinkTo( scriptObject, "AI_STRAFE_LEFT" ); AI_STRAFE_RIGHT.LinkTo( scriptObject, "AI_STRAFE_RIGHT" ); AI_ATTACK_HELD.LinkTo( scriptObject, "AI_ATTACK_HELD" ); + //AI_ATTACK2_HELD.LinkTo( scriptObject, "AI_ATTACK2_HELD" ); // DG: for HEXEN, in theory, but seems unused AI_WEAPON_FIRED.LinkTo( scriptObject, "AI_WEAPON_FIRED" ); AI_JUMP.LinkTo( scriptObject, "AI_JUMP" ); AI_DEAD.LinkTo( scriptObject, "AI_DEAD" ); @@ -1197,11 +1463,36 @@ idPlayer::Init ============== */ void idPlayer::Init( void ) { + const char *value; const idKeyValue *kv; noclip = false; godmode = false; + + // HEXEN : Zeroth + beaten = false; + invincible = false; // this is used for temporary godmode ( icon of the defender ) + hellion_danger_time = 0; + + const idDict *HELLPIT=gameLocal.FindEntityDefDict("object_hellionpit"); + HELLION_MINS=HELLPIT->GetVector("mins", "48 48 68"); + HELLION_MAXS=HELLPIT->GetVector("maxs", "48 48 68"); + HELLION_BOUNDS = idBounds(HELLION_MINS, HELLION_MAXS); + HELLION_DANGER_DIST_MAX = HELLPIT->GetInt("dist_max"); + HELLION_DANGER_TIME_MIN = HELLPIT->GetInt("time_min"); + HELLION_DANGER_TIME_MAX = HELLPIT->GetInt("time_max"); + HELLION_DANGER_RECOUP_TIME = HELLPIT->GetInt("time_recoup"); + HELLION_SPAWN_DIST_FROM_PLAYER = HELLPIT->GetInt("dist_spawn"); + + hud->HandleNamedEvent( "eoc_AutoMapOff" ); + AutoMapOff = true; + hud->SetStateString( "eoc_AutoMapImageTop", gameLocal.world->spawnArgs.GetString("floor_top") ); + hud->SetStateString( "eoc_AutoMapImageMid", gameLocal.world->spawnArgs.GetString("floor_mid") ); + hud->SetStateString( "eoc_AutoMapImageBot", gameLocal.world->spawnArgs.GetString("floor_bot") ); + + leftWater = 0; + PowerTome = false; oldButtons = 0; oldFlags = 0; @@ -1219,9 +1510,6 @@ void idPlayer::Init( void ) { lastDmgTime = 0; lastArmorPulse = -10000; - lastHeartAdjust = 0; - lastHeartBeat = 0; - heartInfo.Init( 0, 0, 0, 0 ); bobCycle = 0; bobFrac = 0.0f; @@ -1254,11 +1542,12 @@ void idPlayer::Init( void ) { fl.takedamage = true; ClearPain(); + inventory.Class= spawnArgs.GetInt( "eoc_class", "0" ); + // restore persistent data RestorePersistantInfo(); bobCycle = 0; - stamina = 0.0f; healthPool = 0.0f; nextHealthPulse = 0; healthPulse = false; @@ -1269,9 +1558,6 @@ void idPlayer::Init( void ) { currentWeapon = -1; previousWeapon = -1; - heartRate = BASE_HEARTRATE; - AdjustHeartRate( BASE_HEARTRATE, 0.0f, 0.0f, true ); - idealLegsYaw = 0.0f; legsYaw = 0.0f; legsForward = true; @@ -1286,14 +1572,6 @@ void idPlayer::Init( void ) { } } - // disable stamina on hell levels - if ( gameLocal.world && gameLocal.world->spawnArgs.GetBool( "no_stamina" ) ) { - pm_stamina.SetFloat( 0.0f ); - } - - // stamina always initialized to maximum - stamina = pm_stamina.GetFloat(); - // air always initialized to maximum too airTics = pm_airTics.GetFloat(); airless = false; @@ -1358,6 +1636,7 @@ void idPlayer::Init( void ) { AI_STRAFE_LEFT = false; AI_STRAFE_RIGHT = false; AI_ATTACK_HELD = false; + // AI_ATTACK2_HELD = false; // HEXEN : Zeroth - DG: seems unused AI_WEAPON_FIRED = false; AI_JUMP = false; AI_DEAD = false; @@ -1426,6 +1705,11 @@ void idPlayer::Spawn( void ) { gameLocal.Error( "entityNum > MAX_CLIENTS for player. Player may only be spawned with a client." ); } + // Zeroth + // *** Thanks SnoopJedi + s_music_vol.SetModified(); // SnoopJeDi - To make sure we get music faded properly + // *** + // allow thinking during cinematics cinematic = true; @@ -1469,6 +1753,8 @@ void idPlayer::Spawn( void ) { objectiveSystem = uiManager->FindGui( "guis/pda.gui", true, false, true ); objectiveSystemOpen = false; + customGui = NULL; + customGuiOpen = false; } SetLastHitTime( 0 ); @@ -1539,6 +1825,8 @@ void idPlayer::Spawn( void ) { } } hud->HandleNamedEvent( "itemPickup" ); + hud->SetStateString( "eoc_message", "" ); + hud->HandleNamedEvent( "eoc_MessageHide" ); } if ( GetPDA() ) { @@ -1584,7 +1872,8 @@ void idPlayer::Spawn( void ) { } } else { g_damageScale.SetFloat( 1.0f ); - g_armorProtection.SetFloat( ( g_skill.GetInteger() < 2 ) ? 0.4f : 0.2f ); + + //g_armorProtection.SetFloat( ( g_skill.GetInteger() < 2 ) ? 0.4f : 0.2f ); // HEXEN : Zeroth if ( g_skill.GetInteger() == 3 ) { healthTake = true; @@ -1592,6 +1881,12 @@ void idPlayer::Spawn( void ) { } } } + + SpawnPos = GetPhysics()->GetOrigin(); + SpawnViewAngles = viewAngles; + + UpdateHudActiveArtifacts(); + } /* @@ -1604,6 +1899,8 @@ Release any resources used by the player. idPlayer::~idPlayer() { delete weapon.GetEntity(); weapon = NULL; + + Artifact.DeleteContents( true ); } /* @@ -1619,7 +1916,7 @@ void idPlayer::Save( idSaveGame *savefile ) const { savefile->WriteBool( noclip ); savefile->WriteBool( godmode ); - + // don't save spawnAnglesSet, since we'll have to reset them after loading the savegame savefile->WriteAngles( spawnAngles ); savefile->WriteAngles( viewAngles ); @@ -1641,25 +1938,17 @@ void idPlayer::Save( idSaveGame *savefile ) const { savefile->WriteUserInterface( hud, false ); savefile->WriteUserInterface( objectiveSystem, false ); savefile->WriteBool( objectiveSystemOpen ); + savefile->WriteBool( customGuiOpen ); + savefile->WriteUserInterface( customGui, false ); savefile->WriteInt( weapon_soulcube ); savefile->WriteInt( weapon_pda ); savefile->WriteInt( weapon_fists ); - savefile->WriteInt( heartRate ); - - savefile->WriteFloat( heartInfo.GetStartTime() ); - savefile->WriteFloat( heartInfo.GetDuration() ); - savefile->WriteFloat( heartInfo.GetStartValue() ); - savefile->WriteFloat( heartInfo.GetEndValue() ); - - savefile->WriteInt( lastHeartAdjust ); - savefile->WriteInt( lastHeartBeat ); savefile->WriteInt( lastDmgTime ); savefile->WriteInt( deathClearContentsTime ); savefile->WriteBool( doingDeathSkin ); savefile->WriteInt( lastArmorPulse ); - savefile->WriteFloat( stamina ); savefile->WriteFloat( healthPool ); savefile->WriteInt( nextHealthPulse ); savefile->WriteBool( healthPulse ); @@ -1809,12 +2098,38 @@ void idPlayer::Save( idSaveGame *savefile ) const { savefile->WriteInt( lastSpectateChange ); savefile->WriteInt( lastTeleFX ); - savefile->WriteFloat( pm_stamina.GetFloat() ); - if ( hud ) { hud->SetStateString( "message", common->GetLanguageDict()->GetString( "#str_02916" ) ); hud->HandleNamedEvent( "Message" ); } + +// **************************** +// +// HEXEN +// +// **************************** + + // whether or not player has beaten this release (for importing player data in future releases) + savefile->WriteBool( beaten ); + + savefile->WriteInt(Artifact.Num()); + for ( int i=0; i WriteString(Artifact[i]->GetString("defname")); + savefile->WriteInt(InventoryItemQty( Artifact[i]->GetString("name") )); + } + + savefile->WriteBool(PowerTome ); + savefile->WriteBool(AutoMapOff ); + savefile->WriteAngles(SpawnViewAngles ); + savefile->WriteBool( invincible ); + savefile->WriteVec3(SpawnPos ); + savefile->WriteInt(BeltSelection ); + savefile->WriteInt(BeltPosition ); + savefile->WriteInt(speedMod ); + savefile->WriteBool(FreeMove ); + savefile->WriteVec3( hellion_danger_origin ); + savefile->WriteInt( hellion_danger_time ); + savefile->WriteInt( leftWater ); } /* @@ -1823,16 +2138,17 @@ idPlayer::Restore =========== */ void idPlayer::Restore( idRestoreGame *savefile ) { - int i; - int num; + int i,j; + int num,num2; float set; + idStr str; savefile->ReadUsercmd( usercmd ); playerView.Restore( savefile ); savefile->ReadBool( noclip ); savefile->ReadBool( godmode ); - + savefile->ReadAngles( spawnAngles ); savefile->ReadAngles( viewAngles ); savefile->ReadAngles( cmdAngles ); @@ -1865,29 +2181,17 @@ void idPlayer::Restore( idRestoreGame *savefile ) { savefile->ReadUserInterface( hud ); savefile->ReadUserInterface( objectiveSystem ); savefile->ReadBool( objectiveSystemOpen ); + savefile->ReadBool( customGuiOpen ); + savefile->ReadUserInterface( customGui ); savefile->ReadInt( weapon_soulcube ); savefile->ReadInt( weapon_pda ); savefile->ReadInt( weapon_fists ); - savefile->ReadInt( heartRate ); - - savefile->ReadFloat( set ); - heartInfo.SetStartTime( set ); - savefile->ReadFloat( set ); - heartInfo.SetDuration( set ); - savefile->ReadFloat( set ); - heartInfo.SetStartValue( set ); - savefile->ReadFloat( set ); - heartInfo.SetEndValue( set ); - - savefile->ReadInt( lastHeartAdjust ); - savefile->ReadInt( lastHeartBeat ); savefile->ReadInt( lastDmgTime ); savefile->ReadInt( deathClearContentsTime ); savefile->ReadBool( doingDeathSkin ); savefile->ReadInt( lastArmorPulse ); - savefile->ReadFloat( stamina ); savefile->ReadFloat( healthPool ); savefile->ReadInt( nextHealthPulse ); savefile->ReadBool( healthPulse ); @@ -2058,12 +2362,52 @@ void idPlayer::Restore( idRestoreGame *savefile ) { kv = spawnArgs.MatchPrefix( "pm_", kv ); } - savefile->ReadFloat( set ); - pm_stamina.SetFloat( set ); - // create combat collision hull for exact collision detection SetCombatModel(); + +// **************************** +// +// HEXEN +// +// **************************** + + savefile->ReadBool( beaten ); // release beaten or not + + //** artifacts ** + Artifact.DeleteContents(true); + savefile->ReadInt( num ); + idDict args; + + for ( int i=0; i < num; i++ ) { + savefile->ReadString(str); + savefile->ReadInt(num2); + + for ( j=0; jReadBool(PowerTome ); + savefile->ReadBool(AutoMapOff ); + savefile->ReadAngles(SpawnViewAngles ); + savefile->ReadBool( invincible ); + savefile->ReadVec3(SpawnPos ); + savefile->ReadInt(BeltSelection ); + savefile->ReadInt(BeltPosition ); + savefile->ReadInt(speedMod ); + savefile->ReadBool(FreeMove ); + savefile->ReadVec3( hellion_danger_origin ); + savefile->ReadInt( hellion_danger_time ); + savefile->ReadInt( leftWater ); + // DG: workaround for lingering messages that are shown forever after loading a savegame // (one way to get them is saving again, while the message from first save is still // shown, and then load) @@ -2294,12 +2638,46 @@ Saves any inventory and player stats when changing levels. =============== */ void idPlayer::SavePersistantInfo( void ) { + int i; + idStr key,str,clas; idDict &playerInfo = gameLocal.persistentPlayerInfo[entityNumber]; playerInfo.Clear(); inventory.GetPersistantData( playerInfo ); + + switch( inventory.Class ) { + case MAGE: { + clas="mage"; + break; + } + case FIGHTER: { + clas="fighter"; + break; + } + default: { + clas="cleric"; + break; + } + } + + playerInfo.SetInt( idStr("health_")+clas, health ); playerInfo.SetInt( "health", health ); playerInfo.SetInt( "current_weapon", currentWeapon ); + + //** artifacts ** + + playerInfo.SetInt("artnum", Artifact.Num()); + for ( i=0; i GetString("defname") ); + + sprintf(key, "artifact%iqty", i); + playerInfo.SetInt(key, InventoryItemQty( Artifact[i]->GetString("name") ) ); + } + playerInfo.SetInt( "eoc_BeltSelection",BeltSelection ); + playerInfo.SetInt( "eoc_BeltPosition",BeltPosition ); + + //** artifacts ** } /* @@ -2313,14 +2691,72 @@ void idPlayer::RestorePersistantInfo( void ) { if ( gameLocal.isMultiplayer ) { gameLocal.persistentPlayerInfo[entityNumber].Clear(); } + int i,j,artnum,qty; + idStr key,str,clas; spawnArgs.Copy( gameLocal.persistentPlayerInfo[entityNumber] ); inventory.RestoreInventory( this, spawnArgs ); - health = spawnArgs.GetInt( "health", "100" ); + + switch( inventory.Class ) { + case MAGE: { + clas="mage"; + break; + } + case FIGHTER: { + clas="fighter"; + break; + } + default: { + clas="cleric"; + break; + } + } + + health = spawnArgs.GetInt( idStr("health_")+clas, "44" ); + if ( !gameLocal.isClient ) { idealWeapon = spawnArgs.GetInt( "current_weapon", "1" ); } + + //** artifacts ** + Artifact.DeleteContents(true); + artnum = spawnArgs.GetInt("artnum", "0"); + idDict args; + + for ( i=0; i AmmoInClip(); ammoamount = weapon.GetEntity()->AmmoAvailable(); + +// HEXEN : Zeroth + combinedmana = inventory.ammo[idWeapon::GetAmmoNumForName("ammo_combinedmana")]; + bluemana = inventory.ammo[idWeapon::GetAmmoNumForName("ammo_bluemana")]; + greenmana = inventory.ammo[idWeapon::GetAmmoNumForName("ammo_greenmana")]; + +// greenmana = weapon.GetEntity()->GreenManaAvailable(); +// bluemana = weapon.GetEntity()->BlueManaAvailable(); +// combinedmana = weapon.GetEntity()->CombinedManaAvailable(); + + //change these to floats if you'd like to see #.0000 numbers + _hud->SetStateInt( "player_totalgreenmana", (int) greenmana); + _hud->SetStateInt( "player_totalbluemana", (int) bluemana); + _hud->SetStateInt( "player_totalcombinedmana", (int) combinedmana); + if ( ammoamount < 0 || !weapon.GetEntity()->IsReady() ) { // show infinite ammo _hud->SetStateString( "player_ammo", "" ); _hud->SetStateString( "player_totalammo", "" ); } else { // show remaining ammo - _hud->SetStateString( "player_totalammo", va( "%i", ammoamount - inclip ) ); - _hud->SetStateString( "player_ammo", weapon.GetEntity()->ClipSize() ? va( "%i", inclip ) : "--" ); // how much in the current clip - _hud->SetStateString( "player_clips", weapon.GetEntity()->ClipSize() ? va( "%i", ammoamount / weapon.GetEntity()->ClipSize() ) : "--" ); - _hud->SetStateString( "player_allammo", va( "%i/%i", inclip, ammoamount - inclip ) ); + _hud->SetStateString( "player_totalammo", va( "%f", ammoamount - inclip ) ); + _hud->SetStateString( "player_ammo", weapon.GetEntity()->ClipSize() ? va( "%f", inclip ) : "--" ); // how much in the current clip + _hud->SetStateString( "player_clips", weapon.GetEntity()->ClipSize() ? va( "%f", ammoamount / weapon.GetEntity()->ClipSize() ) : "--" ); + _hud->SetStateString( "player_allammo", va( "%f/%f", inclip, ammoamount - inclip ) ); } _hud->SetStateBool( "player_ammo_empty", ( ammoamount == 0 ) ); @@ -2514,25 +2968,14 @@ idPlayer::UpdateHudStats =============== */ void idPlayer::UpdateHudStats( idUserInterface *_hud ) { - int staminapercentage; - float max_stamina; - assert( _hud ); - max_stamina = pm_stamina.GetFloat(); - if ( !max_stamina ) { - // stamina disabled, so show full stamina bar - staminapercentage = 100.0f; - } else { - staminapercentage = idMath::FtoiFast( 100.0f * stamina / max_stamina ); - } - _hud->SetStateInt( "player_health", health ); - _hud->SetStateInt( "player_stamina", staminapercentage ); _hud->SetStateInt( "player_armor", inventory.armor ); - _hud->SetStateInt( "player_hr", heartRate ); - _hud->SetStateInt( "player_nostamina", ( max_stamina == 0 ) ? 1 : 0 ); + _hud->SetStateInt( "player_maxhealth", inventory.maxHealth ); + _hud->SetStateInt( "player_maxarmor", inventory.maxarmor ); + _hud->HandleNamedEvent( "updateArmorHealthAir" ); if ( healthPulse ) { @@ -2569,12 +3012,11 @@ void idPlayer::UpdateHudStats( idUserInterface *_hud ) { /* =============== +Zeroth idPlayer::UpdateHudWeapon =============== */ void idPlayer::UpdateHudWeapon( bool flashWeapon ) { - idUserInterface *hud = idPlayer::hud; - // if updating the hud of a followed client if ( gameLocal.localClientNum >= 0 && gameLocal.entities[ gameLocal.localClientNum ] && gameLocal.entities[ gameLocal.localClientNum ]->IsType( idPlayer::Type ) ) { idPlayer *p = static_cast< idPlayer * >( gameLocal.entities[ gameLocal.localClientNum ] ); @@ -2634,7 +3076,7 @@ void idPlayer::DrawHUD( idUserInterface *_hud ) { // weapon targeting crosshair if ( !GuiActive() ) { - if ( cursor && weapon.GetEntity()->ShowCrosshair() ) { + if ( cursor && weapon.GetEntity()->ShowCrosshair() && !customGuiOpen ) { cursor->Redraw( gameLocal.realClientTime ); } } @@ -2668,6 +3110,7 @@ void idPlayer::EnterCinematic( void ) { AI_STRAFE_RIGHT = false; AI_RUN = false; AI_ATTACK_HELD = false; + // AI_ATTACK2_HELD = false; // HEXEN : Zeroth - DG: seems unused AI_WEAPON_FIRED = false; AI_JUMP = false; AI_CROUCH = false; @@ -2737,7 +3180,6 @@ void idPlayer::UpdateConditions( void ) { AI_STRAFE_RIGHT = false; } - AI_RUN = ( usercmd.buttons & BUTTON_RUN ) && ( ( !pm_stamina.GetFloat() ) || ( stamina > pm_staminathreshold.GetFloat() ) ); AI_DEAD = ( health <= 0 ); } @@ -2766,10 +3208,12 @@ idPlayer::StopFiring */ void idPlayer::StopFiring( void ) { AI_ATTACK_HELD = false; + //AI_ATTACK2_HELD = false; // HEXEN : Zeroth - DG: seems unused AI_WEAPON_FIRED = false; AI_RELOAD = false; if ( weapon.GetEntity() ) { weapon.GetEntity()->EndAttack(); + weapon.GetEntity()->EndAttack2(); } } @@ -2794,9 +3238,59 @@ void idPlayer::FireWeapon( void ) { } if ( !hiddenWeapon && weapon.GetEntity()->IsReady() ) { - if ( weapon.GetEntity()->AmmoInClip() || weapon.GetEntity()->AmmoAvailable() ) { + if ( weapon.GetEntity()->AmmoInClip() > weapon.GetEntity()->AmmoRequired() || weapon.GetEntity()->AmmoAvailable() >= 1 || weapon.GetEntity()->AmmoAvailable() == -1 ) { AI_ATTACK_HELD = true; weapon.GetEntity()->BeginAttack(); + inventory.DoCombinedMana(); // HEXEN : Zeroth + if ( ( weapon_soulcube >= 0 ) && ( currentWeapon == weapon_soulcube ) ) { + if ( hud ) { + hud->HandleNamedEvent( "soulCubeNotReady" ); + } + SelectWeapon( previousWeapon, false ); + } + } else { + NextBestWeapon(); + } + } + + if ( hud ) { + if ( tipUp ) { + HideTip(); + } + // may want to track with with a bool as well + // keep from looking up named events so often + if ( objectiveUp ) { + HideObjective(); + } + } +} + +/* +=============== +// HEXEN : Zeroth +idPlayer::FireWeaponAlt +=============== +*/ +void idPlayer::FireWeaponAlt( void ) { + idMat3 axis; + idVec3 muzzle; + + if ( privateCameraView ) { + return; + } + + if ( g_editEntityMode.GetInteger() ) { + GetViewPos( muzzle, axis ); + if ( gameLocal.editEntities->SelectEntity( muzzle, axis[0], this ) ) { + return; + } + } + + if ( !hiddenWeapon && weapon.GetEntity()->IsReady() ) { + if ( weapon.GetEntity()->AmmoInClip() || weapon.GetEntity()->AmmoAvailable() ) { + //AI_ATTACK2_HELD = true; - DG: seems unused + weapon.GetEntity()->BeginAttack2(); + inventory.DoCombinedMana(); // HEXEN : Zeroth if ( ( weapon_soulcube >= 0 ) && ( currentWeapon == weapon_soulcube ) ) { if ( hud ) { hud->HandleNamedEvent( "soulCubeNotReady" ); @@ -2858,36 +3352,74 @@ bool idPlayer::Give( const char *statname, const char *value ) { return false; } - if ( !idStr::Icmp( statname, "health" ) ) { - if ( health >= inventory.maxHealth ) { - return false; - } - amount = atoi( value ); - if ( amount ) { - health += amount; - if ( health > inventory.maxHealth ) { - health = inventory.maxHealth; + if ( !idStr::Icmpn( statname, "health", 6 ) ) { + if ( !idStr::Icmp( statname, "health" ) ) { + if ( health >= inventory.maxHealth ) { + return false; } - if ( hud ) { - hud->HandleNamedEvent( "healthPulse" ); + + amount = atoi( value ); + + if ( amount ) { + health += amount; + if ( health > inventory.maxHealth ) { + health = inventory.maxHealth; + } + if ( hud ) { + hud->HandleNamedEvent( "healthPulse" ); + } + } + } else if ( !idStr::Icmp( statname, "health_mage" ) ) { + if ( health >= inventory.maxHealth ) { + return false; } - } - } else if ( !idStr::Icmp( statname, "stamina" ) ) { - if ( stamina >= 100 ) { - return false; - } - stamina += atof( value ); - if ( stamina > 100 ) { - stamina = 100; - } + amount = atoi( value ); + + if ( amount ) { + health += amount; + if ( health > inventory.maxHealth ) { + health = inventory.maxHealth; + } + if ( hud ) { + hud->HandleNamedEvent( "healthPulse" ); + } + } + } else if ( !idStr::Icmp( statname, "health_cleric" ) ) { + if ( health >= inventory.maxHealth ) { + return false; + } - } else if ( !idStr::Icmp( statname, "heartRate" ) ) { - heartRate += atoi( value ); - if ( heartRate > MAX_HEARTRATE ) { - heartRate = MAX_HEARTRATE; - } + amount = atoi( value ); + + if ( amount ) { + health += amount; + if ( health > inventory.maxHealth ) { + health = inventory.maxHealth; + } + if ( hud ) { + hud->HandleNamedEvent( "healthPulse" ); + } + } + } else if ( !idStr::Icmp( statname, "health_fighter" ) ) { + if ( health >= inventory.maxHealth ) { + return false; + } + amount = atoi( value ); + + if ( amount ) { + health += amount; + if ( health > inventory.maxHealth ) { + health = inventory.maxHealth; + } + if ( hud ) { + hud->HandleNamedEvent( "healthPulse" ); + } + } + } else { + return false; + } } else if ( !idStr::Icmp( statname, "air" ) ) { if ( airTics >= pm_airTics.GetInteger() ) { return false; @@ -2902,7 +3434,6 @@ bool idPlayer::Give( const char *statname, const char *value ) { return true; } - /* =============== idPlayer::GiveHealthPool @@ -3028,8 +3559,8 @@ idPlayer::GivePowerUp =============== */ bool idPlayer::GivePowerUp( int powerup, int time ) { - const char *sound; - const char *skin; +// const char *sound; +// const char *skin; if ( powerup >= 0 && powerup < MAX_POWERUPS ) { @@ -3042,57 +3573,10 @@ bool idPlayer::GivePowerUp( int powerup, int time ) { msg.WriteBits( 1, 1 ); ServerSendEvent( EVENT_POWERUP, &msg, false, -1 ); } - - if ( powerup != MEGAHEALTH ) { - inventory.GivePowerUp( this, powerup, time ); - } + inventory.GivePowerUp( this, powerup, time ); const idDeclEntityDef *def = NULL; - switch( powerup ) { - case BERSERK: { - if ( spawnArgs.GetString( "snd_berserk_third", "", &sound ) ) { - StartSoundShader( declManager->FindSound( sound ), SND_CHANNEL_DEMONIC, 0, false, NULL ); - } - if ( baseSkinName.Length() ) { - powerUpSkin = declManager->FindSkin( baseSkinName + "_berserk" ); - } - if ( !gameLocal.isClient ) { - idealWeapon = 0; - } - break; - } - case INVISIBILITY: { - spawnArgs.GetString( "skin_invisibility", "", &skin ); - powerUpSkin = declManager->FindSkin( skin ); - // remove any decals from the model - if ( modelDefHandle != -1 ) { - gameRenderWorld->RemoveDecals( modelDefHandle ); - } - if ( weapon.GetEntity() ) { - weapon.GetEntity()->UpdateSkin(); - } - if ( spawnArgs.GetString( "snd_invisibility", "", &sound ) ) { - StartSoundShader( declManager->FindSound( sound ), SND_CHANNEL_ANY, 0, false, NULL ); - } - break; - } - case ADRENALINE: { - stamina = 100.0f; - break; - } - case MEGAHEALTH: { - if ( spawnArgs.GetString( "snd_megahealth", "", &sound ) ) { - StartSoundShader( declManager->FindSound( sound ), SND_CHANNEL_ANY, 0, false, NULL ); - } - def = gameLocal.FindEntityDef( "powerup_megahealth", false ); - if ( def ) { - health = def->dict.GetInt( "inv_health" ); - } - break; - } - } - if ( hud ) { hud->HandleNamedEvent( "itemPickup" ); } @@ -3207,24 +3691,56 @@ void idPlayer::ClearPowerUps( void ) { idPlayer::GiveInventoryItem =============== */ -bool idPlayer::GiveInventoryItem( idDict *item ) { +bool idPlayer::GiveInventoryItem( idItem *item ) { + idDict artifact; + if ( gameLocal.isMultiplayer && spectating ) { return false; } - inventory.items.Append( new idDict( *item ) ); + + // HEXEN : Zeroth + if (item->spawnArgs.GetInt( "max_inventory" ) > 0 &&InventoryItemQty(item->spawnArgs.GetString( "inv_name" )) >= item->spawnArgs.GetInt( "max_inventory" )) { + return false; + } + + const char* itemName = item->spawnArgs.GetString( "inv_name" ); + + // HEXEN : Zeroth + if (item->spawnArgs.FindKey("artifact") && !item->spawnArgs.GetBool("instantEffect") ) { + //only append an artifact if it isn't already in the list + if (FindArtifact(item->spawnArgs.GetInt( "artifact" )) == -1) { + item->ArtifactActive=0; // whether or not the artifact is active - can be changed later to represent number of active artifacts of this type if necessary + artifact.SetInt("artifact", item->spawnArgs.GetInt("artifact")); //slot in artifact belt + artifact.Set("name", itemName); + artifact.Set("defname", item->GetEntityDefName()); + Artifact.Append( new idDict( artifact ) ); + + // artifacts must be listed in ascending order in order to space them correctly in + // the GUI, as well as know which ones are off screen and as such should be hidden. + // Artifacts only need to be sorted when an artifact is added. (thats here!) + SortArtifacts(); + } + } + + inventory.items.Append( new idDict( item->spawnArgs ) ); idItemInfo info; - const char* itemName = item->GetString( "inv_name" ); + if ( idStr::Cmpn( itemName, STRTABLE_ID, STRTABLE_ID_LENGTH ) == 0 ) { info.name = common->GetLanguageDict()->GetString( itemName ); } else { info.name = itemName; } - info.icon = item->GetString( "inv_icon" ); + info.icon = item->spawnArgs.GetString( "inv_icon" ); inventory.pickupItemNames.Append( info ); if ( hud ) { hud->SetStateString( "itemicon", info.icon ); hud->HandleNamedEvent( "invPickup" ); } + + // HEXEN : Zeroth + item->SetOwner(this); //item->owner = this; +UpdateHudArtifacts(); + return true; } @@ -3425,6 +3941,7 @@ idPlayer::RemoveInventoryItem */ void idPlayer::RemoveInventoryItem( const char *name ) { idDict *item = FindInventoryItem(name); + if ( item ) { RemoveInventoryItem( item ); } @@ -3436,8 +3953,28 @@ idPlayer::RemoveInventoryItem =============== */ void idPlayer::RemoveInventoryItem( idDict *item ) { + bool foundkey=false; + int key; + //int iteration=rand(); + + if ( !item ) { + return; + } + + if (item->FindKey("artifact") ) { + foundkey=true; + key=item->GetInt("artifact"); + } + inventory.items.Remove( item ); delete item; + + if (foundkey) { + ArtifactVerify(FindArtifact(key)); + UpdateHudArtifacts(); + } + + } /* @@ -3466,7 +4003,7 @@ int idPlayer::SlotForWeapon( const char *weaponName ) { for( i = 0; i < MAX_WEAPONS; i++ ) { const char *weap = spawnArgs.GetString( va( "def_weapon%d", i ) ); - if ( !idStr::Cmp( weap, weaponName ) ) { + if ( !idStr::Icmp( weap, weaponName ) ) { return i; } } @@ -3532,7 +4069,7 @@ void idPlayer::NextWeapon( void ) { const char *weap; int w; - if ( !weaponEnabled || spectating || hiddenWeapon || gameLocal.inCinematic || gameLocal.world->spawnArgs.GetBool( "no_Weapons" ) || health < 0 ) { + if ( !weaponEnabled || spectating || hiddenWeapon || gameLocal.inCinematic || gameLocal.world->spawnArgs.GetBool( "no_Weapons" ) || health < 0 || customGuiOpen ) { return; } @@ -3582,7 +4119,7 @@ void idPlayer::PrevWeapon( void ) { const char *weap; int w; - if ( !weaponEnabled || spectating || hiddenWeapon || gameLocal.inCinematic || gameLocal.world->spawnArgs.GetBool( "no_Weapons" ) || health < 0 ) { + if ( !weaponEnabled || spectating || hiddenWeapon || gameLocal.inCinematic || gameLocal.world->spawnArgs.GetBool( "no_Weapons" ) || health < 0 || customGuiOpen ) { return; } @@ -3631,7 +4168,7 @@ idPlayer::SelectWeapon void idPlayer::SelectWeapon( int num, bool force ) { const char *weap; - if ( !weaponEnabled || spectating || gameLocal.inCinematic || health < 0 ) { + if ( !weaponEnabled || spectating || gameLocal.inCinematic || health < 0 || customGuiOpen ) { return; } @@ -3686,7 +4223,7 @@ idPlayer::DropWeapon */ void idPlayer::DropWeapon( bool died ) { idVec3 forward, up; - int inclip, ammoavailable; + float inclip, ammoavailable; assert( !gameLocal.isClient ); @@ -3729,10 +4266,10 @@ void idPlayer::DropWeapon( bool died ) { // set the appropriate ammo in the dropped object const idKeyValue * keyval = item->spawnArgs.MatchPrefix( "inv_ammo_" ); if ( keyval ) { - item->spawnArgs.SetInt( keyval->GetKey(), ammoavailable ); + item->spawnArgs.SetFloat( keyval->GetKey(), ammoavailable ); idStr inclipKey = keyval->GetKey(); inclipKey.Insert( "inclip_", 4 ); - item->spawnArgs.SetInt( inclipKey, inclip ); + item->spawnArgs.SetFloat( inclipKey, inclip ); } if ( !died ) { // remove from our local inventory completely @@ -3769,8 +4306,8 @@ void idPlayer::StealWeapon( idPlayer *player ) { } const char *weapon_classname = spawnArgs.GetString( va( "def_weapon%d", newweap ) ); assert( weapon_classname ); - int ammoavailable = player->weapon.GetEntity()->AmmoAvailable(); - int inclip = player->weapon.GetEntity()->AmmoInClip(); + float ammoavailable = player->weapon.GetEntity()->AmmoAvailable(); + float inclip = player->weapon.GetEntity()->AmmoInClip(); if ( ( ammoavailable != -1 ) && ( ammoavailable - inclip < 0 ) ) { // see DropWeapon common->DPrintf( "idPlayer::StealWeapon: bad ammo setup\n" ); @@ -3780,7 +4317,7 @@ void idPlayer::StealWeapon( idPlayer *player ) { assert( decl ); const idKeyValue *keypair = decl->dict.MatchPrefix( "inv_ammo_" ); assert( keypair ); - ammoavailable = atoi( keypair->GetValue() ); + ammoavailable = atof( keypair->GetValue() ); } player->weapon.GetEntity()->WeaponStolen(); @@ -3808,6 +4345,10 @@ idUserInterface *idPlayer::ActiveGui( void ) { return objectiveSystem; } + if ( customGuiOpen ) { + return customGui; + } + return focusUI; } @@ -3868,6 +4409,7 @@ void idPlayer::Weapon_Combat( void ) { currentWeapon = idealWeapon; weaponGone = false; animPrefix = spawnArgs.GetString( va( "def_weapon%d", currentWeapon ) ); + weapon.GetEntity()->GetWeaponDef( animPrefix, inventory.clip[ currentWeapon ] ); animPrefix.Strip( "weapon_" ); @@ -3898,6 +4440,13 @@ void idPlayer::Weapon_Combat( void ) { } else if ( oldButtons & BUTTON_ATTACK ) { AI_ATTACK_HELD = false; weapon.GetEntity()->EndAttack(); + } else + // HEXEN : Zeroth + if ( ( usercmd.buttons & BUTTON_ATTACK2 ) && !weaponGone ) { + FireWeaponAlt(); + } else if ( oldButtons & BUTTON_ATTACK2 ) { + //AI_ATTACK2_HELD = false; - DG: seems unused + weapon.GetEntity()->EndAttack2(); } } @@ -3925,6 +4474,11 @@ void idPlayer::Weapon_NPC( void ) { if ( ( usercmd.buttons & BUTTON_ATTACK ) && !( oldButtons & BUTTON_ATTACK ) ) { buttonMask |= BUTTON_ATTACK; focusCharacter->TalkTo( this ); + } else + // HEXEN : Zeroth + if ( ( usercmd.buttons & BUTTON_ATTACK2 ) && !( oldButtons & BUTTON_ATTACK2 ) ) { + buttonMask |= BUTTON_ATTACK2; + focusCharacter->TalkTo( this ); } } @@ -3977,7 +4531,7 @@ idPlayer::Weapon_GUI */ void idPlayer::Weapon_GUI( void ) { - if ( !objectiveSystemOpen ) { + if ( !objectiveSystemOpen && !customGuiOpen ) { if ( idealWeapon != currentWeapon ) { Weapon_Combat(); } @@ -4012,6 +4566,30 @@ void idPlayer::Weapon_GUI( void ) { } else { HandleGuiCommands( this, command ); } + } else + // HEXEN : Zeroth + if ( ( oldButtons ^ usercmd.buttons ) & BUTTON_ATTACK2 ) { + sysEvent_t ev; + const char *command = NULL; + bool updateVisuals = false; + + idUserInterface *ui = ActiveGui(); + if ( ui ) { + ev = sys->GenerateMouseButtonEvent( 1, ( usercmd.buttons & BUTTON_ATTACK2 ) != 0 ); + command = ui->HandleEvent( &ev, gameLocal.time, &updateVisuals ); + if ( updateVisuals && focusGUIent && ui == focusUI ) { + focusGUIent->UpdateVisuals(); + } + } + if ( gameLocal.isClient ) { + // we predict enough, but don't want to execute commands + return; + } + if ( focusGUIent ) { + HandleGuiCommands( focusGUIent, command ); + } else { + HandleGuiCommands( this, command ); + } } } @@ -4048,6 +4626,10 @@ void idPlayer::UpdateWeapon( void ) { if ( hiddenWeapon && tipUp && usercmd.buttons & BUTTON_ATTACK ) { HideTip(); + } else + // HEXEN : Zeroth + if ( hiddenWeapon && tipUp && usercmd.buttons & BUTTON_ATTACK2 ) { + HideTip(); } if ( g_dragEntity.GetBool() ) { @@ -4157,6 +4739,8 @@ void idPlayer::UpdateSpectating( void ) { SpectateFreeFly( false ); } else if ( usercmd.buttons & BUTTON_ATTACK ) { SpectateCycle(); + } /*Zeroth*/ else if ( usercmd.buttons & BUTTON_ATTACK2 ) { + SpectateCycle(); } } @@ -4176,6 +4760,22 @@ bool idPlayer::HandleSingleGuiCommand( idEntity *entityGui, idLexer *src ) { return false; } + if ( token == "mainmenu" ) { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "clearPersistentLevelInfo" ); + gameLocal.sessionCommand = "disconnect"; + return true; + } + + if ( token == "quit" ) { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "quit" ); + return true; + } + + if ( token == "save" ) { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "savegame" ); + return true; + } + if ( token.Icmp( "addhealth" ) == 0 ) { if ( entityGui && health < 100 ) { int _health = entityGui->spawnArgs.GetInt( "gui_parm1" ); @@ -4219,6 +4819,9 @@ bool idPlayer::HandleSingleGuiCommand( idEntity *entityGui, idLexer *src ) { if ( objectiveSystem && objectiveSystemOpen ) { TogglePDA(); } + if ( customGui && customGuiOpen ) { + customGuiOpen = false; + } } if ( token.Icmp( "playpdavideo" ) == 0 ) { @@ -4362,6 +4965,8 @@ void idPlayer::UpdateFocus( void ) { // can still chainsaw NPC's if ( gameLocal.isMultiplayer || ( !focusCharacter && ( usercmd.buttons & BUTTON_ATTACK ) ) ) { allowFocus = false; + } /* Zeroth */ else if ( gameLocal.isMultiplayer || ( !focusCharacter && ( usercmd.buttons & BUTTON_ATTACK2 ) ) ) { + allowFocus = false; } else { allowFocus = true; } @@ -4516,10 +5121,7 @@ void idPlayer::UpdateFocus( void ) { focusUI->SetStateInt( p, 1 ); } } - - int staminapercentage = ( int )( 100.0f * stamina / pm_stamina.GetFloat() ); focusUI->SetStateString( "player_health", va("%i", health ) ); - focusUI->SetStateString( "player_stamina", va( "%i%%", staminapercentage ) ); focusUI->SetStateString( "player_armor", va( "%i%%", inventory.armor ) ); kv = focusGUIent->spawnArgs.MatchPrefix( "gui_parm", NULL ); @@ -4664,14 +5266,8 @@ void idPlayer::CrashLand( const idVec3 &oldOrigin, const idVec3 &oldVelocity ) { return; } - // allow falling a bit further for multiplayer - if ( gameLocal.isMultiplayer ) { - fatalDelta = 75.0f; - hardDelta = 50.0f; - } else { - fatalDelta = 65.0f; - hardDelta = 45.0f; - } + fatalDelta = 90.0f; // ~ 320 units + hardDelta = 50.0f; // ~ 256 units if ( delta > fatalDelta ) { AI_HARDLANDING = true; @@ -4679,7 +5275,7 @@ void idPlayer::CrashLand( const idVec3 &oldOrigin, const idVec3 &oldVelocity ) { landTime = gameLocal.time; if ( !noDamage ) { pain_debounce_time = gameLocal.time + pain_delay + 1; // ignore pain since we'll play our landing anim - Damage( NULL, NULL, idVec3( 0, 0, -1 ), "damage_fatalfall", 1.0f, 0 ); + Damage( NULL, NULL, idVec3( 0, 0, -1 ), "damage_fatalfall", 1.0f, 0, idVec3(0,0,0) ); } } else if ( delta > hardDelta ) { AI_HARDLANDING = true; @@ -4687,7 +5283,7 @@ void idPlayer::CrashLand( const idVec3 &oldOrigin, const idVec3 &oldVelocity ) { landTime = gameLocal.time; if ( !noDamage ) { pain_debounce_time = gameLocal.time + pain_delay + 1; // ignore pain since we'll play our landing anim - Damage( NULL, NULL, idVec3( 0, 0, -1 ), "damage_hardfall", 1.0f, 0 ); + Damage( NULL, NULL, idVec3( 0, 0, -1 ), "damage_hardfall", 1.0f, 0, idVec3(0,0,0) ); } } else if ( delta > 30 ) { AI_HARDLANDING = true; @@ -4695,7 +5291,7 @@ void idPlayer::CrashLand( const idVec3 &oldOrigin, const idVec3 &oldVelocity ) { landTime = gameLocal.time; if ( !noDamage ) { pain_debounce_time = gameLocal.time + pain_delay + 1; // ignore pain since we'll play our landing anim - Damage( NULL, NULL, idVec3( 0, 0, -1 ), "damage_softfall", 1.0f, 0 ); + Damage( NULL, NULL, idVec3( 0, 0, -1 ), "damage_softfall", 1.0f, 0, idVec3(0,0,0) ); } } else if ( delta > 7 ) { AI_SOFTLANDING = true; @@ -4750,7 +5346,11 @@ void idPlayer::BobCycle( const idVec3 &pushVelocity ) { bobFoot = 0; bobfracsin = 0; } else { - if ( physicsObj.IsCrouching() ) { + // HEXEN : Zeroth + if ( FreeMove ) { + // bob very little and no footsteps when player is on Wings of Wrath + bobmove = pm_flybob.GetFloat(); + } else if ( physicsObj.IsCrouching() ) { bobmove = pm_crouchbob.GetFloat(); // ducked characters never play footsteps } else { @@ -4760,7 +5360,7 @@ void idPlayer::BobCycle( const idVec3 &pushVelocity ) { // check for footstep / splash sounds old = bobCycle; - bobCycle = (int)( old + bobmove * gameLocal.msec ) & 255; + bobCycle = (int)( old + bobmove * gameLocal.msecPrecise ) & 255; bobFoot = ( bobCycle & 128 ) >> 7; bobfracsin = idMath::Fabs( sin( ( bobCycle & 127 ) / 127.0 * idMath::PI ) ); } @@ -4873,7 +5473,7 @@ void idPlayer::UpdateViewAngles( void ) { int i; idAngles delta; - if ( !noclip && ( gameLocal.inCinematic || privateCameraView || gameLocal.GetCamera() || influenceActive == INFLUENCE_LEVEL2 || objectiveSystemOpen ) ) { + if ( !noclip && ( gameLocal.inCinematic || privateCameraView || gameLocal.GetCamera() || influenceActive == INFLUENCE_LEVEL2 || objectiveSystemOpen || customGuiOpen ) ) { // no view changes at all, but we still want to update the deltas or else when // we get out of this mode, our view will snap to a kind of random angle UpdateDeltaViewAngles( viewAngles ); @@ -4933,116 +5533,6 @@ void idPlayer::UpdateViewAngles( void ) { loggedViewAngles[ gameLocal.framenum & (NUM_LOGGED_VIEW_ANGLES-1) ] = viewAngles; } -/* -============== -idPlayer::AdjustHeartRate - -Player heartrate works as follows - -DEF_HEARTRATE is resting heartrate - -Taking damage when health is above 75 adjusts heart rate by 1 beat per second -Taking damage when health is below 75 adjusts heart rate by 5 beats per second -Maximum heartrate from damage is MAX_HEARTRATE - -Firing a weapon adds 1 beat per second up to a maximum of COMBAT_HEARTRATE - -Being at less than 25% stamina adds 5 beats per second up to ZEROSTAMINA_HEARTRATE - -All heartrates are target rates.. the heart rate will start falling as soon as there have been no adjustments for 5 seconds -Once it starts falling it always tries to get to DEF_HEARTRATE - -The exception to the above rule is upon death at which point the rate is set to DYING_HEARTRATE and starts falling -immediately to zero - -Heart rate volumes go from zero ( -40 db for DEF_HEARTRATE to 5 db for MAX_HEARTRATE ) the volume is -scaled linearly based on the actual rate - -Exception to the above rule is once the player is dead, the dying heart rate starts at either the current volume if -it is audible or -10db and scales to 8db on the last few beats -============== -*/ -void idPlayer::AdjustHeartRate( int target, float timeInSecs, float delay, bool force ) { - - if ( heartInfo.GetEndValue() == target ) { - return; - } - - if ( AI_DEAD && !force ) { - return; - } - - lastHeartAdjust = gameLocal.time; - - heartInfo.Init( gameLocal.time + delay * 1000, timeInSecs * 1000, heartRate, target ); -} - -/* -============== -idPlayer::GetBaseHeartRate -============== -*/ -int idPlayer::GetBaseHeartRate( void ) { - int base = idMath::FtoiFast( ( BASE_HEARTRATE + LOWHEALTH_HEARTRATE_ADJ ) - ( (float)health / 100.0f ) * LOWHEALTH_HEARTRATE_ADJ ); - int rate = idMath::FtoiFast( base + ( ZEROSTAMINA_HEARTRATE - base ) * ( 1.0f - stamina / pm_stamina.GetFloat() ) ); - int diff = ( lastDmgTime ) ? gameLocal.time - lastDmgTime : 99999; - rate += ( diff < 5000 ) ? ( diff < 2500 ) ? ( diff < 1000 ) ? 15 : 10 : 5 : 0; - return rate; -} - -/* -============== -idPlayer::SetCurrentHeartRate -============== -*/ -void idPlayer::SetCurrentHeartRate( void ) { - - int base = idMath::FtoiFast( ( BASE_HEARTRATE + LOWHEALTH_HEARTRATE_ADJ ) - ( (float) health / 100.0f ) * LOWHEALTH_HEARTRATE_ADJ ); - - if ( PowerUpActive( ADRENALINE )) { - heartRate = 135; - } else { - heartRate = idMath::FtoiFast( heartInfo.GetCurrentValue( gameLocal.time ) ); - int currentRate = GetBaseHeartRate(); - if ( health >= 0 && gameLocal.time > lastHeartAdjust + 2500 ) { - AdjustHeartRate( currentRate, 2.5f, 0.0f, false ); - } - } - - int bps = idMath::FtoiFast( 60.0f / heartRate * 1000.0f ); - if ( gameLocal.time - lastHeartBeat > bps ) { - int dmgVol = DMG_VOLUME; - int deathVol = DEATH_VOLUME; - int zeroVol = ZERO_VOLUME; - float pct = 0.0; - if ( heartRate > BASE_HEARTRATE && health > 0 ) { - pct = (float)(heartRate - base) / (MAX_HEARTRATE - base); - pct *= ((float)dmgVol - (float)zeroVol); - } else if ( health <= 0 ) { - pct = (float)(heartRate - DYING_HEARTRATE) / (BASE_HEARTRATE - DYING_HEARTRATE); - if ( pct > 1.0f ) { - pct = 1.0f; - } else if (pct < 0.0f) { - pct = 0.0f; - } - pct *= ((float)deathVol - (float)zeroVol); - } - - pct += (float)zeroVol; - - if ( pct != zeroVol ) { - StartSound( "snd_heartbeat", SND_CHANNEL_HEART, SSF_PRIVATE_SOUND, false, NULL ); - // modify just this channel to a custom volume - soundShaderParms_t parms; - memset( &parms, 0, sizeof( parms ) ); - parms.volume = pct; - refSound.referenceSound->ModifySound( SND_CHANNEL_HEART, &parms ); - } - - lastHeartBeat = gameLocal.time; - } -} - /* ============== idPlayer::UpdateAir @@ -5088,7 +5578,7 @@ void idPlayer::UpdateAir( void ) { const idDict *damageDef = gameLocal.FindEntityDefDict( "damage_noair", false ); int dmgTiming = 1000 * ((damageDef) ? damageDef->GetFloat( "delay", "3.0" ) : 3.0f ); if ( gameLocal.time > lastAirDamage + dmgTiming ) { - Damage( NULL, NULL, vec3_origin, "damage_noair", 1.0f, 0 ); + Damage( NULL, NULL, vec3_origin, "damage_noair", 1.0f, 0, idVec3(0,0,0) ); lastAirDamage = gameLocal.time; } } @@ -5256,7 +5746,7 @@ void idPlayer::UpdatePDAInfo( bool updatePDASel ) { if ( j != currentPDA && j < 128 && inventory.pdasViewed[j >> 5] & (1 << (j & 31)) ) { // This pda has been read already, mark in gray - objectiveSystem->SetStateString( va( "listPDA_item_%i", index), va(S_COLOR_GRAY "%s", pda->GetPdaName()) ); + objectiveSystem->SetStateString( va( "listPDA_item_%i", index), va(S_COLOR_BLACK "%s", pda->GetPdaName()) ); } else { // This pda has not been read yet objectiveSystem->SetStateString( va( "listPDA_item_%i", index), pda->GetPdaName() ); @@ -5374,7 +5864,7 @@ void idPlayer::TogglePDA( void ) { assert( hud ); - if ( !objectiveSystemOpen ) { + if ( !objectiveSystemOpen && !customGuiOpen ) { int j, c = inventory.items.Num(); objectiveSystem->SetStateInt( "inv_count", c ); for ( j = 0; j < MAX_INVENTORY_ITEMS; j++ ) { @@ -5549,7 +6039,6 @@ idPlayer::PerformImpulse ============== */ void idPlayer::PerformImpulse( int impulse ) { - if ( gameLocal.isClient ) { idBitMsg msg; byte msgBuf[MAX_EVENT_PARAM_SIZE]; @@ -5587,7 +6076,6 @@ void idPlayer::PerformImpulse( int impulse ) { } case IMPULSE_18: { centerView.Init(gameLocal.time, 200, viewAngles.pitch, 0); - break; } case IMPULSE_19: { // when we're not in single player, IMPULSE_19 is used for showScores @@ -5629,6 +6117,45 @@ void idPlayer::PerformImpulse( int impulse ) { UseVehicle(); break; } +// HEXEN : Zeroth + case IMPULSE_41: { + ArtifactScrollLeft(1); //start scrolling for an item from 1 to the left + ShowArtifactHud(); + break; + } + case IMPULSE_42: { + ArtifactScrollRight(1); //start scrolling for an item from 1 to the right + ShowArtifactHud(); + break; + } + case IMPULSE_43: { + + ArtifactExec(BeltSelection, "use", true ); + break; + }/* dropping not working properly, and isn't even necessary + case IMPULSE_44: { + while (1) { + int arse =InventoryItemQty(0); + + if ( arse == 0 ) { + break; + } + + for (int c=0; cHandleNamedEvent( "eoc_AutoMapOn" ); + AutoMapOff = false; + } else { + hud->HandleNamedEvent( "eoc_AutoMapOff" ); + AutoMapOff = true; + } + } } } @@ -5692,7 +6219,7 @@ idPlayer::AdjustSpeed */ void idPlayer::AdjustSpeed( void ) { float speed; - float rate; +// float rate; if ( spectating ) { speed = pm_spectatespeed.GetFloat(); @@ -5700,33 +6227,10 @@ void idPlayer::AdjustSpeed( void ) { } else if ( noclip ) { speed = pm_noclipspeed.GetFloat(); bobFrac = 0.0f; - } else if ( !physicsObj.OnLadder() && ( usercmd.buttons & BUTTON_RUN ) && ( usercmd.forwardmove || usercmd.rightmove ) && ( usercmd.upmove >= 0 ) ) { - if ( !gameLocal.isMultiplayer && !physicsObj.IsCrouching() && !PowerUpActive( ADRENALINE ) ) { - stamina -= MS2SEC( gameLocal.msec ); - } - if ( stamina < 0 ) { - stamina = 0; - } - if ( ( !pm_stamina.GetFloat() ) || ( stamina > pm_staminathreshold.GetFloat() ) ) { - bobFrac = 1.0f; - } else if ( pm_staminathreshold.GetFloat() <= 0.0001f ) { - bobFrac = 0.0f; - } else { - bobFrac = stamina / pm_staminathreshold.GetFloat(); - } + } else if ( !physicsObj.OnLadder() &&speedMod == 0 && ( usercmd.buttons & BUTTON_RUN ) && ( usercmd.forwardmove || usercmd.rightmove ) && ( usercmd.upmove >= 0 ) ) { + bobFrac = 1; // HEXEN : Zeroth speed = pm_walkspeed.GetFloat() * ( 1.0f - bobFrac ) + pm_runspeed.GetFloat() * bobFrac; } else { - rate = pm_staminarate.GetFloat(); - - // increase 25% faster when not moving - if ( ( usercmd.forwardmove == 0 ) && ( usercmd.rightmove == 0 ) && ( !physicsObj.OnLadder() || ( usercmd.upmove == 0 ) ) ) { - rate *= 1.25f; - } - - stamina += rate * MS2SEC( gameLocal.msec ); - if ( stamina > pm_stamina.GetFloat() ) { - stamina = pm_stamina.GetFloat(); - } speed = pm_walkspeed.GetFloat(); bobFrac = 0.0f; } @@ -5737,6 +6241,9 @@ void idPlayer::AdjustSpeed( void ) { speed *= 0.33f; } + // HEXEN : Zeroth + speed +=speedMod; + physicsObj.SetSpeed( speed, pm_crouchspeed.GetFloat() ); } @@ -5957,6 +6464,9 @@ void idPlayer::Move( void ) { } else if ( gameLocal.inCinematic || gameLocal.GetCamera() || privateCameraView || ( influenceActive == INFLUENCE_LEVEL2 ) ) { physicsObj.SetContents( CONTENTS_BODY ); physicsObj.SetMovementType( PM_FREEZE ); + } else if (FreeMove) { + physicsObj.SetContents( CONTENTS_BODY ); + physicsObj.SetMovementType( PM_FLY ); } else { physicsObj.SetContents( CONTENTS_BODY ); physicsObj.SetMovementType( PM_NORMAL ); @@ -6125,6 +6635,15 @@ void idPlayer::UpdateHud( void ) { } else { hud->SetStateString( "hudLag", "0" ); } + + // HEXEN : Zeroth + if ( g_noHudAutoHide.GetBool() ) { + hud->SetStateFloat( "dontHideHud", 1.0f); + } else { + hud->SetStateFloat( "dontHideHud", 0.0f); + } + + hud->SetStateBool( "crosshairVis", cvarSystem->GetCVarBool( "g_crosshairVis" ) ); } /* @@ -6221,7 +6740,7 @@ void idPlayer::Think( void ) { oldFlags = usercmd.flags; } - if ( objectiveSystemOpen || gameLocal.inCinematic || influenceActive ) { + if ( objectiveSystemOpen || customGuiOpen || gameLocal.inCinematic || influenceActive ) { if ( objectiveSystemOpen && AI_PAIN ) { TogglePDA(); } @@ -6253,7 +6772,7 @@ void idPlayer::Think( void ) { } // zooming - if ( ( usercmd.buttons ^ oldCmd.buttons ) & BUTTON_ZOOM ) { + if ( ( usercmd.buttons ^ oldCmd.buttons ) & BUTTON_ZOOM && !customGuiOpen ) { if ( ( usercmd.buttons & BUTTON_ZOOM ) && weapon.GetEntity() ) { zoomFov.Init( gameLocal.time, 200.0f, CalcFov( false ), weapon.GetEntity()->GetZoomFov() ); } else { @@ -6261,6 +6780,7 @@ void idPlayer::Think( void ) { } } + // if we have an active gui, we will unrotate the view angles as // we turn the mouse movements into gui events idUserInterface *gui = ActiveGui(); @@ -6290,7 +6810,6 @@ void idPlayer::Think( void ) { // not done on clients for various reasons. don't do it on server and save the sound channel for other things if ( !gameLocal.isMultiplayer ) { - SetCurrentHeartRate(); float scale = g_damageScale.GetFloat(); if ( g_useDynamicProtection.GetBool() && scale < 1.0f && gameLocal.time - lastDmgTime > 500 ) { if ( scale < 1.0f ) { @@ -6313,7 +6832,7 @@ void idPlayer::Think( void ) { // service animations if ( !spectating && !af.IsActive() && !gameLocal.inCinematic ) { - UpdateConditions(); + UpdateConditions(); UpdateAnimState(); CheckBlink(); } @@ -6340,6 +6859,7 @@ void idPlayer::Think( void ) { UpdateAir(); UpdateHud(); + UpdateHudAutoMap(); // HEXEN : Zeroth UpdatePowerUps(); @@ -6383,7 +6903,7 @@ void idPlayer::Think( void ) { if ( !g_stopTime.GetBool() ) { UpdateAnimation(); - Present(); + Present(); UpdateDamageEffects(); @@ -6406,6 +6926,120 @@ void idPlayer::Think( void ) { } gameLocal.Printf( "%d: enemies\n", num ); } + + // determine if portal sky is in pvs + gameLocal.portalSkyActive = gameLocal.pvs.CheckAreasForPortalSky( gameLocal.GetPlayerPVS(), GetPhysics()->GetOrigin() ); // HEXEN : Zeroth + + // spawn hellions if the player is camping + SpawnHellions(); +} + +// spawn hellions if the player is camping +void idPlayer::SpawnHellions( void ) { + if ( !cvarSystem->GetCVarBool( "g_hellions" ) ) { + return; + } + + if ( hellion_danger_time == 0 ) { + hellion_danger_time = gameLocal.time + HELLION_DANGER_TIME_MIN + ( gameLocal.random.RandomFloat() * ( HELLION_DANGER_TIME_MAX - HELLION_DANGER_TIME_MIN ) ); + hellion_danger_origin = gameLocal.GetLocalPlayer()->GetPhysics()->GetOrigin(); + return; + } + + if ( gameLocal.time < hellion_danger_time ) { + return; + } + + idVec3 A = gameLocal.GetLocalPlayer()->GetPhysics()->GetOrigin() - hellion_danger_origin; + float dist = idMath::Sqrt(A.x * A.x + A.y * A.y + A.z * A.z); + + if ( dist > HELLION_DANGER_DIST_MAX ) { + hellion_danger_origin = gameLocal.GetLocalPlayer()->GetPhysics()->GetOrigin(); + return; + } + + int i; + trace_t trace; + idEntity *floor; + const idBounds smallBounds = idBounds( idVec3( -1, -1, -1 ), idVec3( 1, 1, 1 ) ); + const idVec3 traceOrigin=GetEyePosition()-idVec3(0,0,50); + const idVec3 downward=idVec3(0,0,-100); + idVec3 forw=VecForwardP()*HELLION_SPAWN_DIST_FROM_PLAYER; + idVec3 perpVec, traceEnd; + + // bunch of traces from players midsection to all around him. + // the first trace is to make sure we dont spawn hellions on the other side of a wall. + for (i=0; i<4; i++ ) { + // get perpendicular vector to forw, counterclockwise + perpVec.x = -forw.y; + perpVec.y = forw.x; + perpVec.z = 0; + forw=perpVec; + traceEnd=traceOrigin+forw; + + // we want to find *nothing* + gameLocal.clip.TraceBounds( trace, traceOrigin, traceEnd, HELLION_BOUNDS, MASK_SOLID, this ); + if ( trace.fraction != 1 ) { + continue; + } + + // subtract width of hellionPit for next trace position so we don't spawn inside a wall. + idVec3 vec=forw; + vec.Normalize(); + vec *= (HELLION_MINS.x+HELLION_MAXS.x)/2; + traceEnd -= vec; + + // from the end of that tracepoint, trace directly downward + gameLocal.clip.TraceBounds( trace, traceEnd, traceEnd+downward, smallBounds, MASK_SOLID, this ); + if ( trace.fraction == 1 ) { + continue; + } + + floor = gameLocal.GetTraceEntity( trace ); + if ( !floor || ( floor->name != "world" && floor->name.Mid(0, 11) != "func_static" ) ) { + continue; + } + + // Z.ToDo: if worldspawn's normal is not pointing fairly upward, continue + + // spawn em + idDict hellionpit; + idEntity *pit; + hellionpit.Set("classname", "object_hellionpit"); + hellionpit.SetVector("origin", traceEnd+(downward*trace.fraction)); + if ( gameLocal.SpawnEntityDef( hellionpit, &pit) ) { + + // reset danger time & origin + hellion_danger_time = gameLocal.time + HELLION_DANGER_TIME_MIN + ( gameLocal.random.RandomFloat() * ( HELLION_DANGER_TIME_MAX - HELLION_DANGER_TIME_MIN ) ) + HELLION_DANGER_RECOUP_TIME; + hellion_danger_origin = gameLocal.GetLocalPlayer()->GetPhysics()->GetOrigin(); + return; + } + } + + // if we were supposed to spawn hellions but could not, just try again in a few seconds + hellion_danger_time = gameLocal.time + 1000; +} + +void idPlayer::UpdateHudAutoMap( void ) { + hud->SetStateFloat( "eoc_PlayerPosX", GetPhysics()->GetOrigin().x ); + hud->SetStateFloat( "eoc_PlayerPosY", GetPhysics()->GetOrigin().y ); +} + +void idPlayer::AutoMapChange( idStr mapFloor ) { + if (!strcmp(mapFloor, "top")) { + hud->SetStateBool( "eoc_AutoMapShowTop", true ); + hud->SetStateBool( "eoc_AutoMapShowMid", false ); + hud->SetStateBool( "eoc_AutoMapShowBot", false ); + } else if (!strcmp(mapFloor, "mid")) { + hud->SetStateBool( "eoc_AutoMapShowTop", false ); + hud->SetStateBool( "eoc_AutoMapShowMid", true ); + hud->SetStateBool( "eoc_AutoMapShowBot", false ); + } else if (!strcmp(mapFloor, "bot")) { + hud->SetStateBool( "eoc_AutoMapShowTop", false ); + hud->SetStateBool( "eoc_AutoMapShowMid", false ); + hud->SetStateBool( "eoc_AutoMapShowBot", true ); + } + } /* @@ -6454,11 +7088,12 @@ void idPlayer::Kill( bool delayRespawn, bool nodamage ) { SpectateFreeFly( false ); } else if ( health > 0 ) { godmode = false; + invincible = false; // HEXEN : Zeroth if ( nodamage ) { ServerSpectate( true ); forceRespawn = true; } else { - Damage( this, this, vec3_origin, "damage_suicide", 1.0f, INVALID_JOINT ); + Damage( this, this, vec3_origin, "damage_suicide", 1.0f, INVALID_JOINT, idVec3(0,0,0) ); if ( delayRespawn ) { forceRespawn = false; int delay = spawnArgs.GetFloat( "respawn_delay" ); @@ -6490,9 +7125,6 @@ void idPlayer::Killed( idEntity *inflictor, idEntity *attacker, int damage, cons return; } - heartInfo.Init( 0, 0, 0, BASE_HEARTRATE ); - AdjustHeartRate( DEAD_HEARTRATE, 10.0f, 0.0f, true ); - if ( !g_testDeath.GetBool() ) { playerView.Fade( colorBlack, 12000 ); } @@ -6605,7 +7237,11 @@ void idPlayer::CalcDamagePoints( idEntity *inflictor, idEntity *attacker, const int damage; int armorSave; - damageDef->GetInt( "damage", "20", damage ); + damageDef->GetInt( "playerDamage", "-1", damage ); + if ( damage == -1 ) { + damageDef->GetInt( "damage", "20", damage ); + } + damage = GetDamageForLocation( damage, location ); idPlayer *player = attacker->IsType( idPlayer::Type ) ? static_cast(attacker) : NULL; @@ -6645,7 +7281,7 @@ void idPlayer::CalcDamagePoints( idEntity *inflictor, idEntity *attacker, const // check for completely getting out of the damage if ( !damageDef->GetBool( "noGod" ) ) { // check for godmode - if ( godmode ) { + if ( godmode || invincible /*Zeroth*/) { damage = 0; } } @@ -6667,8 +7303,8 @@ void idPlayer::CalcDamagePoints( idEntity *inflictor, idEntity *attacker, const if ( !damage ) { armorSave = 0; } else if ( armorSave >= damage ) { - armorSave = damage - 1; - damage = 1; + armorSave = damage; // HEXEN : Zeroth: armorSave = damage - 1; + damage = 0; // HEXEN : Zeroth: damage = 1; } else { damage -= armorSave; } @@ -6707,7 +7343,7 @@ inflictor, attacker, dir, and point can be NULL for environmental effects ============ */ void idPlayer::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, - const char *damageDefName, const float damageScale, const int location ) { + const char *damageDefName, const float damageScale, const int location, const idVec3 &iPoint ) { idVec3 kick; int damage; int armorSave; @@ -7686,7 +8322,7 @@ void idPlayer::Event_SelectWeapon( const char *weaponName ) { for( i = 0; i < MAX_WEAPONS; i++ ) { if ( inventory.weapons & ( 1 << i ) ) { const char *weap = spawnArgs.GetString( va( "def_weapon%d", i ) ); - if ( !idStr::Cmp( weap, weaponName ) ) { + if ( !idStr::Icmp( weap, weaponName ) ) { weaponNum = i; break; } @@ -7782,7 +8418,7 @@ void idPlayer::Event_ExitTeleporter( void ) { if ( teleportKiller != -1 ) { // we got killed while being teleported - Damage( gameLocal.entities[ teleportKiller ], gameLocal.entities[ teleportKiller ], vec3_origin, "damage_telefrag", 1.0f, INVALID_JOINT ); + Damage( gameLocal.entities[ teleportKiller ], gameLocal.entities[ teleportKiller ], vec3_origin, "damage_telefrag", 1.0f, INVALID_JOINT, idVec3(0,0,0) ); teleportKiller = -1; } else { // kill anything that would have waited at teleport exit @@ -7807,12 +8443,13 @@ void idPlayer::ClientPredictionThink( void ) { if ( entityNumber != gameLocal.localClientNum ) { // ignore attack button of other clients. that's no good for predictions usercmd.buttons &= ~BUTTON_ATTACK; + usercmd.buttons &= ~BUTTON_ATTACK2; } buttonMask &= usercmd.buttons; usercmd.buttons &= ~buttonMask; - if ( objectiveSystemOpen ) { + if ( objectiveSystemOpen || customGuiOpen ) { usercmd.forwardmove = 0; usercmd.rightmove = 0; usercmd.upmove = 0; @@ -7860,7 +8497,7 @@ void idPlayer::ClientPredictionThink( void ) { // service animations if ( !spectating && !af.IsActive() ) { - UpdateConditions(); + UpdateConditions(); UpdateAnimState(); CheckBlink(); } @@ -7937,6 +8574,13 @@ void idPlayer::ClientPredictionThink( void ) { } } +#ifdef _D3XP + // determine if portal sky is in pvs + pvsHandle_t clientPVS = gameLocal.pvs.SetupCurrentPVS( GetPVSAreas(), GetNumPVSAreas() ); + gameLocal.portalSkyActive = gameLocal.pvs.CheckAreasForPortalSky( clientPVS, GetPhysics()->GetOrigin() ); + gameLocal.pvs.FreeCurrentPVS( clientPVS ); +#endif + /* ================ idPlayer::GetPhysicsToVisualTransform @@ -8532,3 +9176,105 @@ bool idPlayer::NeedsIcon( void ) { // local clients don't render their own icons... they're only info for other clients return entityNumber != gameLocal.localClientNum && ( isLagged || isChatting ); } + + + +// HEXEN : Zeroth +void idPlayer::ShowHudMessage( const char *message ) { + if ( hud ) { + hud->SetStateString( "eoc_message", message ); + hud->HandleNamedEvent( "eoc_MessageShow" ); + } + gameLocal.Printf("%s\n", message); +} + +// HEXEN : Zeroth +void idPlayer::ShowHudMessage( idStr message ) { +ShowHudMessage( message.c_str() ); +} + +// HEXEN : Zeroth +int idPlayer::InventoryItemQty( const char *name ) const { + int qty=0,i; + + for (i=0; i < inventory.items.Num(); i++ ) { + const char *iname = inventory.items[i]->GetString( "inv_name" ); + if ( iname && *iname ) { + if ( idStr::Icmp( name, iname ) == 0 ) { + qty++; + } + } + } + + return qty; +} + +// HEXEN : Zeroth +int idPlayer::InventoryItemQty( int belt ) { + if (ArtifactVerify(belt)) { + return InventoryItemQty(Artifact[belt]->GetString("name")); + }else{ + return 0; + } +} + +void idPlayer::SetClass( int classnum ) { + idStr clas; + + inventory.Class=classnum; + + switch( classnum ) { + case MAGE: { + clas="mage"; + break; + } + case FIGHTER: { + clas="fighter"; + break; + } + default: { + clas="cleric"; + break; + } + } + + health = spawnArgs.GetInt( idStr("health_")+clas, "44" ); + inventory.maxHealth = spawnArgs.GetInt( idStr("maxhealth_")+clas, "44" ); + if ( health > inventory.maxHealth ) { + health = inventory.maxHealth; + } + + inventory.armor = spawnArgs.GetInt( idStr("armor_")+clas, "44" ); + inventory.maxarmor = spawnArgs.GetInt( idStr("maxarmor_")+clas, "44" ); + if ( inventory.armor > inventory.maxarmor ) { + inventory.armor = inventory.maxarmor; + } + + inventory.deplete_armor = spawnArgs.GetInt( idStr("deplete_armor_")+clas, "0" ); + inventory.deplete_rate = spawnArgs.GetFloat( idStr("deplete_rate_")+clas, "0" ); + inventory.deplete_ammount = spawnArgs.GetInt( idStr("deplete_ammount_")+clas, "0" ); + + if ( hud ) { + UpdateHudStats( hud ); + } +} + +void idPlayer::OpenCustomGui( idStr file ) { + if ( file == "" ) { + return; + } + + customGui = uiManager->FindGui( file, true, false, true ); + + if ( !customGui ) { + return; + } + + if ( objectiveSystemOpen ) { + TogglePDA(); + } + + customGuiOpen = true; + customGui->Activate( true, gameLocal.time ); + StopFiring(); +} diff --git a/game/Player.h b/game/Player.h index b411a0c0..c48d3b59 100644 --- a/game/Player.h +++ b/game/Player.h @@ -39,7 +39,10 @@ If you have questions concerning this license or the applicable additional terms #include "PlayerIcon.h" #include "GameEdit.h" +#include "Mover.h" + class idAI; +class idProjectile; /* =============================================================================== @@ -66,12 +69,6 @@ const int FOCUS_GUI_TIME = 500; const int MAX_WEAPONS = 16; -const int DEAD_HEARTRATE = 0; // fall to as you die -const int LOWHEALTH_HEARTRATE_ADJ = 20; // -const int DYING_HEARTRATE = 30; // used for volumen calc when dying/dead -const int BASE_HEARTRATE = 70; // default -const int ZEROSTAMINA_HEARTRATE = 115; // no stamina -const int MAX_HEARTRATE = 130; // maximum const int ZERO_VOLUME = -40; // volume at zero const int DMG_VOLUME = 5; // volume when taking damage const int DEATH_VOLUME = 15; // volume at death @@ -81,6 +78,10 @@ const int SAVING_THROW_TIME = 5000; // maximum one "saving throw" every five se extern const int ASYNC_PLAYER_INV_AMMO_BITS; extern const int ASYNC_PLAYER_INV_CLIP_BITS; +const int CLERIC = 0; +const int MAGE = 1; +const int FIGHTER = 2; + struct idItemInfo { idStr name; idStr icon; @@ -123,14 +124,18 @@ enum { }; class idInventory { +// HEXEN : Zeroth +public: + int Class; + public: int maxHealth; int weapons; int powerups; int armor; int maxarmor; - int ammo[ AMMO_NUMTYPES ]; - int clip[ MAX_WEAPONS ]; + float ammo[ AMMO_NUMTYPES ]; + float clip[ MAX_WEAPONS ]; int powerupEndTime[ MAX_POWERUPS ]; // mp @@ -179,13 +184,17 @@ class idInventory { ammo_t AmmoIndexForAmmoClass( const char *ammo_classname ) const; int MaxAmmoForAmmoClass( idPlayer *owner, const char *ammo_classname ) const; int WeaponIndexForAmmoClass( const idDict & spawnArgs, const char *ammo_classname ) const; - ammo_t AmmoIndexForWeaponClass( const char *weapon_classname, int *ammoRequired ); + ammo_t AmmoIndexForWeaponClass( const char *weapon_classname, float *ammoRequired ); const char * AmmoPickupNameForIndex( ammo_t ammonum ) const; void AddPickupName( const char *name, const char *icon ); - int HasAmmo( ammo_t type, int amount ); - bool UseAmmo( ammo_t type, int amount ); - int HasAmmo( const char *weapon_classname ); // looks up the ammo information for the weapon class first +// HEXEN : Zeroth +public: + void DoCombinedMana( void ); + + float HasAmmo( ammo_t type, float amount ); + bool UseAmmo( ammo_t type, float amount ); + float HasAmmo( const char *weapon_classname ); // looks up the ammo information for the weapon class first void UpdateArmor( void ); @@ -194,6 +203,7 @@ class idInventory { int onePickupTime; idList pickupItemNames; idList objectiveNames; + }; typedef struct { @@ -223,7 +233,7 @@ class idPlayer : public idActor { bool noclip; bool godmode; - + bool spawnAnglesSet; // on first usercmd, we must set deltaAngles idAngles spawnAngles; idAngles viewAngles; // player view angles @@ -242,6 +252,11 @@ class idPlayer : public idActor { idScriptBool AI_STRAFE_LEFT; idScriptBool AI_STRAFE_RIGHT; idScriptBool AI_ATTACK_HELD; + // DG: AI_ATTACK2_HELD seems unused. it used to cause an assertion when loading a level + // because it wasn't linked, when linking it starting the game fails with + // "ERROR: Missing 'AI_ATTACK2_HELD' field in script object 'player'" + // which indicates that the scripts don't use this + //idScriptBool AI_ATTACK2_HELD; // HEXEN : Zeroth idScriptBool AI_WEAPON_FIRED; idScriptBool AI_JUMP; idScriptBool AI_CROUCH; @@ -256,7 +271,7 @@ class idPlayer : public idActor { idScriptBool AI_TELEPORT; idScriptBool AI_TURN_LEFT; idScriptBool AI_TURN_RIGHT; - + // inventory idInventory inventory; @@ -264,20 +279,34 @@ class idPlayer : public idActor { idUserInterface * hud; // MP: is NULL if not local player idUserInterface * objectiveSystem; bool objectiveSystemOpen; + idUserInterface * customGui; + bool customGuiOpen; int weapon_soulcube; int weapon_pda; int weapon_fists; - int heartRate; - idInterpolate heartInfo; - int lastHeartAdjust; - int lastHeartBeat; int lastDmgTime; + +// HEXEN : Zeroth +public: + bool PowerTome; + bool AutoMapOff; + idAngles SpawnViewAngles; // player view angles + bool invincible; + idVec3 SpawnPos; + int BeltSelection; + int BeltPosition; + int speedMod; + bool FreeMove; + int leftWater; + idList Artifact; // list of artifacts & quantities, and other artifact info + void SetClass( const int classnum ); + bool beaten; // whether this eoc release has been beaten + int deathClearContentsTime; bool doingDeathSkin; int lastArmorPulse; // lastDmgTime if we had armor at time of hit - float stamina; float healthPool; // amount of health to give over time int nextHealthPulse; bool healthPulse; @@ -381,7 +410,7 @@ class idPlayer : public idActor { virtual void DamageFeedback( idEntity *victim, idEntity *inflictor, int &damage ); void CalcDamagePoints( idEntity *inflictor, idEntity *attacker, const idDict *damageDef, const float damageScale, const int location, int *health, int *armor ); - virtual void Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ); + virtual void Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location, const idVec3 &iPoint ); // use exitEntityNum to specify a teleport with private camera view and delayed exit virtual void Teleport( const idVec3 &origin, const idAngles &angles, idEntity *destination ); @@ -410,12 +439,41 @@ class idPlayer : public idActor { void GiveItem( const char *name ); void GiveHealthPool( float amt ); - bool GiveInventoryItem( idDict *item ); + bool GiveInventoryItem( idItem *item ); void RemoveInventoryItem( idDict *item ); bool GiveInventoryItem( const char *name ); void RemoveInventoryItem( const char *name ); idDict * FindInventoryItem( const char *name ); +// HEXEN : Zeroth +public: + void UpdateHudArtifacts( void ); + void UpdateHudActiveArtifacts(); + bool ArtifactVerify( int art ); // return whetherArtifact[art] is safe to use. Or if the artifact is Qty 0, delete it and return false. + void CleanupArtifactItems(); // deletes allAartifactItem[] elements marked as to be. + int InventoryItemQty( const char *name ) const; + int InventoryItemQty( int belt ); // specifically for artifacts + void SortArtifacts( void ); // sort belt items in ascending order of their "artifact" keys + void ArtifactExec( int belt, const char *func, bool remove ); + void ArtifactDrop( int belt, bool randomPos ); + bool ArtifactRemove( int belt ); + void ShowArtifactHud( void ); + void UpdateArtifactHudDescription( void ); + int FindArtifact( int art ); // returns the index inArtifact[] matching "artifact" spawnArg + bool ActiveArtifact( int art ); + bool ActiveArtifact( const char *art ); + void ArtifactValidateSelection( void ); + void ArtifactScrollRight( int startfrom ); + void ArtifactScrollLeft( int startfrom ); + int Belt2Index(int belt_index); // translate an artitfacts position on the belt to it'sArtifactItem[] index. + // HEXEN : Zeroth + /*virtual*/ bool StuckToSurface( void ); + void SpawnHellions( void ); + void UpdateHudAutoMap( void ); + void AutoMapChange( idStr mapFloor ); + void ShowHudMessage( const char *message ); + void ShowHudMessage( idStr message ); + bool GetPowerTome( void ) { return PowerTome; }; void GivePDA( const char *pdaName, idDict *item ); void GiveVideo( const char *videoName, idDict *item ); void GiveEmail( const char *emailName ); @@ -449,9 +507,6 @@ class idPlayer : public idActor { void AddAIKill( void ); void SetSoulCubeProjectile( idProjectile *projectile ); - void AdjustHeartRate( int target, float timeInSecs, float delay, bool force ); - void SetCurrentHeartRate( void ); - int GetBaseHeartRate( void ); void UpdateAir( void ); virtual bool HandleSingleGuiCommand( idEntity *entityGui, idLexer *src ); @@ -474,6 +529,7 @@ class idPlayer : public idActor { void StartFxFov( float duration ); void UpdateHudWeapon( bool flashWeapon = true ); void UpdateHudStats( idUserInterface *hud ); + void UpdateHudAmmo( idUserInterface *hud ); void Event_StopAudioLog( void ); void StartAudioLog( void ); @@ -637,10 +693,17 @@ class idPlayer : public idActor { bool selfSmooth; +// HEXEN : Zeroth +private: + idVec3 hellion_danger_origin; + int hellion_danger_time; + + void LookAtKiller( idEntity *inflictor, idEntity *attacker ); void StopFiring( void ); void FireWeapon( void ); + void FireWeaponAlt( void ); void Weapon_Combat( void ); void Weapon_NPC( void ); void Weapon_GUI( void ); @@ -695,6 +758,44 @@ class idPlayer : public idActor { void Event_LevelTrigger( void ); void Event_Gibbed( void ); void Event_GetIdealWeapon( void ); + +// HEXEN : Zeroth +private: + void Event_GiveHealth( const float amount ); + void Event_GiveSpeed( const float amount ); + void Event_GiveArmor( const float amount ); + void Event_GetHealth( void ); + void Event_GetFullHealth( void ); + void Event_GetArmor( void ); + void Event_GetFullArmor( void ); + void Event_SetHealth( const float amount ); + void Event_SetArmor( const float amount ); + void Event_GetClass( void ); + void Event_SetAmmo( const char *ammo_classname, const float amount ); + void Event_GetAmmo( const char *ammo_classname ); + void Event_GetFullAmmo( const char *ammo_classname ); + void Event_SetInvincible( const float number ); + void Event_GetInvincible( void ); + void Event_ChaosDevice( void ); + void Event_SetFreeMove( const float yesorno ); + void Event_SetViewAngles( idVec3 &vec ); + void Event_GetEyePos( void ); + void Event_SetPowerTome( float val ); + void Event_GetPowerTome( void ); + void Event_VecForwardP( void ); + void Event_VecFacingP( void ); + void Event_HudMessage( const char *message ); + void Event_ArtifactUseFlash( void ); + + //the following are no longer used - perhaps they will be useful for something else, like sticky bombs of some kind + void Event_StickToSurface( const idVec3 &surfaceNormal ); + void Event_UnstickToSurface( void ); + void Event_StuckToSurface( void ); + +public: + idVec3 VecForwardP( void ) const; + idVec3 VecFacingP( void ) const; + void OpenCustomGui( idStr file ); }; ID_INLINE bool idPlayer::IsReady( void ) { diff --git a/game/PlayerArtifacts.cpp b/game/PlayerArtifacts.cpp new file mode 100644 index 00000000..2ee9afd2 --- /dev/null +++ b/game/PlayerArtifacts.cpp @@ -0,0 +1,575 @@ +// HEXEN : Zeroth + +#include "Game_local.h" +#include "Item.h" +#include "Player.h" +#include "gamesys/SysCvar.h" + +int idPlayer::FindArtifact( int art ) { + for (int belt=0; beltGetInt("artifact") == art) { + return belt; + } + } + return -1; +} + +bool idPlayer::ActiveArtifact( int art ) { + idItem *item; + int hash, i; + + hash = gameLocal.entypeHash.GenerateKey( idItem::Type.classname, true ); + + for ( i = gameLocal.entypeHash.First( hash ); i != -1; i = gameLocal.entypeHash.Next( i ) ) { + if ( gameLocal.entities[i] && gameLocal.entities[i]->IsType( idItem::Type ) ) { + item = static_cast< idItem* >( gameLocal.entities[i] ); + + if ( item->GetOwner() != this ) { + item = NULL; + continue; + } + + if ( !item->ArtifactActive ) { + item = NULL; + continue; + } + + if ( item->spawnArgs.GetInt("artifact") != art) { + item = NULL; + continue; + } + + return true; + } + } + + return false; +} + +bool idPlayer::ActiveArtifact( const char *art ) { + idDict tmp; + tmp.Set("art", art); + return ActiveArtifact(tmp.GetInt("art")); +} + +void idPlayer::ArtifactExec( int belt, const char * funcName, bool remove ) { + CleanupArtifactItems(); + + if (!ArtifactVerify(belt)) { + UpdateHudArtifacts(); + return; + } + + if (funcName == NULL) { + return; + } + + const char *name=Artifact[belt]->GetString("name"); + + if (ActiveArtifact(Artifact[belt]->GetInt("artifact"))) { + return; + } + + idItem *item; + int hash, i; + + hash = gameLocal.entypeHash.GenerateKey( idItem::Type.classname, true ); + + for ( i = gameLocal.entypeHash.First( hash ); i != -1; i = gameLocal.entypeHash.Next( i ) ) { + if ( gameLocal.entities[i] && gameLocal.entities[i]->IsType( idItem::Type ) ) { + item = static_cast< idItem* >( gameLocal.entities[i] ); + + if ( item->GetOwner() != this ) { + continue; + } + + if ( idStr::Icmp(name, item->spawnArgs.GetString("inv_name")) ) { + continue; + } + + if ( item->Processing ) { + continue; + } + + if ( item->DeleteMe ) { + continue; + } + + item->CallFunc(funcName); + return; + } + } +} + +void idPlayer::SortArtifacts( void ) { + int a,b; + idDict *temp; + + for (a=0; aGetInt("artifact") < Artifact[b]->GetInt("artifact")) { + continue; + } + + temp=Artifact[a]; + Artifact[a]=Artifact[b]; + Artifact[b]=temp; + temp=NULL; + + if ( BeltSelection == a) { + BeltSelection = b; + } else if ( BeltSelection == b) { + BeltSelection = a; + } + + // we don't callUpdateHudArtifacts(); because SortArtifacts is only called by GiveInventoryItem(), and that takes care of it. + } + } +} + +void idPlayer::UpdateHudArtifacts( void ) { + int belt,art; + int pos; // belt item position marker + int onScreen=((7-1)/2); //number of on-screen artifacts to the left of the center. (belt_width - 1) / 2; belt_width must be odd (the middle number is center of belt) + int tileWidth=60; //width in pixels of an artifact on the belt + char itemStr[22]; + + ArtifactValidateSelection(); + + // disappear all artifacts + for ( art=0; art < NUM_UNIQUE_ARTIFACTS; art++) { + sprintf(itemStr, "eoc_Artifact%iVis", art); + hud->SetStateBool( itemStr, false ); + + sprintf(itemStr, "eoc_Artifact%iRotate", art); + hud->SetStateFloat( itemStr, 0.0f ); + } + + hud->SetStateInt( "eoc_Artifacts",Artifact.Num() ); + hud->SetStateInt( "eoc_BeltSelection",BeltSelection ); + + // determine tile position and visibility + pos=0; + for (belt=0; belt < Artifact.Num(); belt++) { + art=Artifact[belt]->GetInt("artifact"); + if (InventoryItemQty(belt) > 0) { + sprintf(itemStr, "eoc_Artifact%iPos", art); + hud->SetStateInt( itemStr, pos ); + + sprintf(itemStr, "eoc_Artifact%iQty", art); + hud->SetStateInt( itemStr,InventoryItemQty(belt) ); + + sprintf(itemStr, "eoc_Artifact%iVis", art); + if ((beltBeltSelection+onScreen)) { + hud->SetStateBool( itemStr, false ); // Hide artifacts that are off-screen + }else{ + hud->SetStateBool( itemStr, true ); + } + + sprintf(itemStr, "eoc_Artifact%iRotate", art); + if (BeltSelection == belt) //ArtifactValidateSelection(); sould be called once before this in the same function + { + BeltPosition=pos; + hud->SetStateFloat( itemStr, 0.05f ); + }else{ + hud->SetStateFloat( itemStr, 0.0f ); + } + + pos+=tileWidth; //up the position for the next visible item + }else{ + //the artifact is no longer in the inventory, but is in use + sprintf(itemStr, "eoc_Artifact%iVis", art); + hud->SetStateBool( itemStr, false ); + + sprintf(itemStr, "eoc_Artifact%iRotate", art); + hud->SetStateFloat( itemStr, 0.0f ); + } + } + + BeltPosition= -(BeltPosition)+(tileWidth*onScreen); // add onScreen*tielWidth to get to center of belt, negate to scroll in opposite (correct) direction + hud->SetStateInt( "eoc_BeltPosition",BeltPosition ); // set scrolled position of inventory + + // update selected artifact + if (BeltSelection < 0 ) { + hud->SetStateString( "eoc_BeltSelectionQty", "" ); + } else { + hud->SetStateInt( "eoc_BeltSelectionQty", InventoryItemQty(BeltSelection) ); + } + UpdateArtifactHudDescription(); +} + +void idPlayer::UpdateHudActiveArtifacts() { + int i; + int numActive=0; + int pos=0; // active artifact position marker + int tileWidth=60; //width in pixels of an artifact on the belt + char itemStr[30]; + idItem* item; + bool active[NUM_UNIQUE_ARTIFACTS]; + bool cooling[NUM_UNIQUE_ARTIFACTS]; + int totArtifacts=NUM_UNIQUE_ARTIFACTS; //Z.TODO this should be read from a def file or something + + // initialize local variables + for (i=0; i < totArtifacts; i++) { + active[i]=false; + cooling[i]=false; + } + + // figure out which artifacts are active or cooling + int hash; + + hash = gameLocal.entypeHash.GenerateKey( idItem::Type.classname, true ); + + for ( i = gameLocal.entypeHash.First( hash ); i != -1; i = gameLocal.entypeHash.Next( i ) ) { + if ( gameLocal.entities[i] && gameLocal.entities[i]->IsType( idItem::Type ) ) { + item = static_cast< idItem* >( gameLocal.entities[i] ); + + if ( item->GetOwner() != this ) { + continue; + } + + if ( !item->ArtifactActive ) { + continue; + } + + active[item->spawnArgs.GetInt("artifact")] = true; + + if ( item->Cooling ) { + cooling[item->spawnArgs.GetInt("artifact")] = true; + } + } + } + + + for (i=0; i < totArtifacts; i++ ) { + if ( active[i] ) { + sprintf(itemStr, "eoc_Artifact%iPosA", i); + hud->SetStateInt( itemStr, pos ); + + sprintf(itemStr, "eoc_Artifact%iVisA", i); + hud->SetStateBool( itemStr, true ); + + // artifact must be active for cooldown, but effects should be turned off at this point via script + sprintf(itemStr, "eoc_Artifact%iCoolDownVisA", i); + hud->SetStateBool( itemStr, cooling[i] ); + + pos+=tileWidth; //up the position for the next visible item + numActive++; + }else{ + //no need to change pos + + sprintf(itemStr, "eoc_Artifact%iVisA", i); + hud->SetStateBool( itemStr, false ); + + sprintf(itemStr, "eoc_Artifact%iCoolDownVisA", i); + hud->SetStateBool( itemStr, false ); + } + } + + hud->SetStateInt("eoc_ActiveArtifacts", numActive); + +} + +bool idPlayer::ArtifactVerify( int belt ) { + int art; + char itemStr[22]; + //int iteration=rand(); + + if ( belt == -1 ) { + return false; + } + + if ( belt >= Artifact.Num() ) { + return false; + } + + art=Artifact[belt]->GetInt("artifact"); + + idStr::Copynz(itemStr, Artifact[belt]->GetString("name"), sizeof(itemStr)); + + if ( art == -1 ) { + return true; //? + } + + if ( InventoryItemQty(itemStr) > 0 ) { + return true; + } + + // remove the artifact, we have zero items of it's type + sprintf(itemStr, "eoc_Artifact%iPos", art); + hud->SetStateInt( itemStr, 0 ); + sprintf(itemStr, "eoc_Artifact%iVis", art); + hud->SetStateBool( itemStr, false ); + sprintf(itemStr, "eoc_Artifact%iQty", art); + hud->SetStateInt( itemStr, 0 ); + sprintf(itemStr, "eoc_Artifact%iRotate", art); + hud->SetStateFloat( itemStr, 0.0f ); + + if (BeltSelection > belt) { + BeltSelection--; + } + + delete Artifact[belt]; + Artifact.RemoveIndex(belt); + Artifact.Condense(); + + return false; +} + +void idPlayer::ArtifactValidateSelection( void ) { + int belt; + + if ( gameLocal.inCinematic || health < 0 ) { + return; + } + + if ( gameLocal.isClient ) { + return; + } + + if (BeltSelection == -1) { + ArtifactScrollRight(1); + return; + } + + if ( ArtifactVerify(BeltSelection) ) { + return; + } + + belt=BeltSelection; + ArtifactScrollRight(0); // we start scrolling right from where we are because idList removes gaps between list items. + + if ( belt == BeltSelection ) { + ArtifactScrollLeft(0); + } + + if ( belt == BeltSelection ) { + BeltSelection = -1; + } +} + +void idPlayer::ArtifactScrollRight( int startfrom ) { + int belt; + + if ( gameLocal.inCinematic || health < 0 ) { + return; + } + + if ( gameLocal.isClient ) { + return; + } + + for (belt=BeltSelection+startfrom; belt 0 ) { + BeltSelection = belt; + UpdateHudArtifacts(); + return; + } + } +} + +void idPlayer::ArtifactScrollLeft( int startfrom ) { + int belt; + + if ( gameLocal.inCinematic || health < 0 ) { + return; + } + + if ( gameLocal.isClient ) { + return; + } + + for (belt=BeltSelection-startfrom; belt > -1; belt--) { + if (InventoryItemQty(belt) > 0 ) { + BeltSelection = belt; + UpdateHudArtifacts(); + return; + } + } +} + +void idPlayer::ArtifactDrop( int belt, bool randomPos ) { + + CleanupArtifactItems(); + + if ( !ArtifactVerify(belt) ) { + UpdateHudArtifacts(); + return; + } + + const char *name=Artifact[belt]->GetString("name"); + + idEntity* ent; + idItem* item; + + for (int i = 0; i < MAX_GENTITIES; i++ ) { + ent = gameLocal.entities[i]; + if ( !ent ) { + continue; + } + + if ( !ent->IsType( idItem::Type ) ) { + continue; + } + + item = static_cast< idItem* >( ent ); + + if ( item->GetOwner() != this ) { + continue; + } + + if ( idStr::Icmp(name, item->spawnArgs.GetString("inv_name") ) ) { + continue; + } + + if ( item->Processing ) { + continue; + } + + idAngles use=viewAngles; + float dist=80; + if ( randomPos ) { + use.yaw = gameLocal.random.CRandomFloat() * 360.0f; + dist = 40 + gameLocal.random.CRandomFloat() * 40; + } + + idVec3 org = GetPhysics()->GetOrigin() + idAngles( 0, use.yaw, 0 ).ToForward() * dist + idVec3( 0, 0, 1 ); + + idDict dict; + dict.Set( "classname", item->spawnArgs.GetString("classname") ); + dict.SetVector( "origin", org ); + //dict.SetAngles( "angles", viewAngles.yaw + 180 ); + + idEntity *newItem; + gameLocal.SpawnEntityDef( dict, &newItem ); + static_cast< idItem* >(newItem)->PickupDelayTime = gameLocal.time + 3000; + + RemoveInventoryItem(name); + item->DeleteMe = true; + item->SetOwner(NULL); + UpdateHudArtifacts(); + + break; + } + + return; +} + +bool idPlayer::ArtifactRemove( int belt ) { + + CleanupArtifactItems(); + + if ( !ArtifactVerify(belt) ) { + UpdateHudArtifacts(); + return false; + } + + const char *name=Artifact[belt]->GetString("name"); + + idItem *item; + int hash, i; + + hash = gameLocal.entypeHash.GenerateKey( idItem::Type.classname, true ); + + for ( i = gameLocal.entypeHash.First( hash ); i != -1; i = gameLocal.entypeHash.Next( i ) ) { + if ( gameLocal.entities[i] && gameLocal.entities[i]->IsType( idItem::Type ) ) { + item = static_cast< idItem* >( gameLocal.entities[i] ); + + if ( item->GetOwner() != this ) { + continue; + } + + if ( idStr::Icmp(name, item->spawnArgs.GetString("inv_name")) ) { + continue; + } + + if ( item->Processing ) { + continue; + } + + RemoveInventoryItem(name); + item->DeleteMe = true; + item->SetOwner(NULL); + UpdateHudArtifacts(); + return true; + } + } + return false; +} + +void idPlayer::ShowArtifactHud( void ) { + hud->HandleNamedEvent( "eoc_BeltShow" ); +} + +void idPlayer::UpdateArtifactHudDescription( void ) { + if ( g_noArtifactDescriptions.GetBool() ) { + hud->SetStateBool( "artifactDescShow", 0.0f ); + hud->SetStateString( "artifactDesc", "" ); + return; + } + + CleanupArtifactItems(); + + if (!ArtifactVerify(BeltSelection)) { + hud->SetStateString( "artifactDesc", "" ); + return; + } + + hud->SetStateBool( "artifactDescShow", 1.0f ); + + idStr desc=""; + + const idDict *item = gameLocal.FindEntityDefDict( Artifact[BeltSelection]->GetString("defname") ); + + if ( !item ) { + desc = ""; + } else { + desc = item->GetString("eoc_description"); + + if ( desc == "" ) { + switch( inventory.Class ) { + case CLERIC: desc = "eoc_description_cleric"; break; + case MAGE: desc = "eoc_description_mage"; break; + case FIGHTER: desc = "eoc_description_fighter"; break; + } + desc = item->GetString( desc.c_str() ); + } + + desc = "\n" + desc; + desc = Artifact[BeltSelection]->GetString("name") + desc; + } + + hud->SetStateString( "artifactDesc", desc.c_str() ); + +} + +// z.todo: this COULD be done with a post event ... +void idPlayer::CleanupArtifactItems() { + idItem *item; + int hash, i; + bool upd=false; + + hash = gameLocal.entypeHash.GenerateKey( idItem::Type.classname, true ); + + for ( i = gameLocal.entypeHash.First( hash ); i != -1; i = gameLocal.entypeHash.Next( i ) ) { + if ( gameLocal.entities[i] && gameLocal.entities[i]->IsType( idItem::Type ) ) { + item = static_cast< idItem* >( gameLocal.entities[i] ); + + if ( item->GetOwner() != this ) { + continue; + } + + if ( !item->DeleteMe ) { + continue; + } + + delete item; + item=NULL; + upd=true; + } + } + + if ( upd ) { + UpdateHudArtifacts(); + } +} diff --git a/game/PlayerScriptEvents.cpp b/game/PlayerScriptEvents.cpp new file mode 100644 index 00000000..1e6bfcba --- /dev/null +++ b/game/PlayerScriptEvents.cpp @@ -0,0 +1,154 @@ +// HEXEN : Zeroth + +#include "Game_local.h" +#include "Player.h" + +void idPlayer::Event_SetViewAngles( idVec3 &vec ) { + viewAngles[0] = vec.x; + viewAngles[1] = vec.y; + viewAngles[2] = vec.z; +} + +void idPlayer::Event_GetEyePos( void ) { + idThread::ReturnVector( GetEyePosition() ); +} + +void idPlayer::Event_SetFreeMove( const float yesorno ) { + //spawnArgs.SetInt("freemovement", yesorno); + if (yesorno) { + FreeMove=true; + }else{ + FreeMove=false; + } +} + +void idPlayer::Event_ChaosDevice( void ) { + SetOrigin(SpawnPos); + SetViewAngles(SpawnViewAngles); +} + +void idPlayer::Event_GiveHealth( const float amount ) { + health+=amount; +} + +void idPlayer::Event_GiveSpeed( const float amount ) { +speedMod+=amount; +} + +void idPlayer::Event_GiveArmor( const float amount ) { + inventory.armor+=amount; +} + +void idPlayer::Event_SetHealth( const float amount ) { + health=amount; +} + +void idPlayer::Event_SetArmor( const float amount ) { + inventory.armor=amount; +} + +void idPlayer::Event_SetInvincible( const float number ) { + if (number != 0) { + invincible = true; + }else{ + invincible = false; + } +} + +void idPlayer::Event_GetInvincible( void ) { + if (godmode || invincible) { + idThread::ReturnFloat(1); + }else{ + idThread::ReturnFloat(0); + } +} + +void idPlayer::Event_GetClass( void ) { + idThread::ReturnFloat( inventory.Class ); +} + +void idPlayer::Event_GetHealth( void ) { + idThread::ReturnFloat( health ); +} + +void idPlayer::Event_GetFullHealth( void ) { + idThread::ReturnFloat( inventory.maxHealth ); +} + +void idPlayer::Event_GetFullArmor( void ) { + idThread::ReturnFloat( inventory.maxarmor ); +} + +void idPlayer::Event_GetFullAmmo( const char* ammo_classname ) { + idThread::ReturnFloat( spawnArgs.GetInt( va( "max_%s", ammo_classname ), "0" ) ); +} + +void idPlayer::Event_GetAmmo( const char* ammo_classname ) { + idThread::ReturnFloat( inventory.ammo[ idWeapon::GetAmmoNumForName( ammo_classname ) ] ); +} + +void idPlayer::Event_SetAmmo( const char* ammo_classname, float amount ) { + if ( ammo_classname ) { + inventory.ammo[ idWeapon::GetAmmoNumForName( ammo_classname ) ] = amount; + } +} + +void idPlayer::Event_GetArmor( void ) { + idThread::ReturnFloat( inventory.armor ); +} + +void idPlayer::Event_StickToSurface( const idVec3 &surfaceNormal ) { + //idPhysics_Actor *x=GetPhysics(); + physicsObj.SetSurfaceNormal(surfaceNormal); + physicsObj.SetStuckToSurface(true); +} + +void idPlayer::Event_UnstickToSurface( void ) { + physicsObj.SetStuckToSurface(false); +} + +bool idPlayer::StuckToSurface( void ) { + return physicsObj.StuckToSurface(); +} + +void idPlayer::Event_StuckToSurface( void ) { + idThread::ReturnFloat((float)StuckToSurface()); +} + +void idPlayer::Event_SetPowerTome( float val ) { + PowerTome = !(!val); +} + +void idPlayer::Event_GetPowerTome( void ) { + idThread::ReturnFloat((float)PowerTome); +} + +void idPlayer::Event_VecFacingP( void ) { + // return proper facing direction (yaw only) + idVec3 dir=viewAxis[ 0 ] * physicsObj.GetGravityAxis(); + dir.Normalize(); + idThread::ReturnVector(dir); + +} + +void idPlayer::Event_HudMessage( const char *message ) { +ShowHudMessage( message ); +} + +void idPlayer::Event_VecForwardP( void ) { + idThread::ReturnVector(VecForwardP()); +} + +idVec3 idPlayer::VecForwardP( void ) const { + // return proper forward vector for whatever way the player is looking + idVec3 dir=viewAngles.ToForward() * physicsObj.GetGravityAxis(); + dir.Normalize(); + return dir; +} + +// HEXEN : Zeroth +void idPlayer::Event_ArtifactUseFlash( void ) { + if ( hud ) { + hud->HandleNamedEvent( "eoc_artifactUse" ); + } +} diff --git a/game/PlayerView.cpp b/game/PlayerView.cpp index 52631906..9d1e85ac 100644 --- a/game/PlayerView.cpp +++ b/game/PlayerView.cpp @@ -36,6 +36,14 @@ If you have questions concerning this license or the applicable additional terms #include "PlayerView.h" +// HEXEN : Zeroth +static int MakePowerOfTwo( int num ) { + int pot; + for (pot = 1 ; pot < num ; pot<<=1) { + } + return pot; +} + const int IMPULSE_DELAY = 150; /* ============== @@ -448,16 +456,61 @@ void idPlayerView::SingleView( idUserInterface *hud, const renderView_t *view ) return; } + // endgame dialog + if ( player->customGuiOpen && player->customGui != NULL ) { + player->customGui->Redraw( gameLocal.time ); + return; + } + // hack the shake in at the very last moment, so it can't cause any consistency problems renderView_t hackedView = *view; hackedView.viewaxis = hackedView.viewaxis * ShakeAxis(); - gameRenderWorld->RenderScene( &hackedView ); +// HEXEN : Zeroth + if ( gameLocal.portalSkyEnt.GetEntity() && gameLocal.IsPortalSkyAcive() && g_enablePortalSky.GetBool() ) { + renderView_t portalView = hackedView; + portalView.vieworg = gameLocal.portalSkyEnt.GetEntity()->GetPhysics()->GetOrigin(); + + // setup global fixup projection vars + if ( 1 ) { + int vidWidth, vidHeight; + idVec2 shiftScale; + + renderSystem->GetGLSettings( vidWidth, vidHeight ); + + float pot; + int w = vidWidth; + pot = MakePowerOfTwo( w ); + shiftScale.x = (float)w / pot; + + int h = vidHeight; + pot = MakePowerOfTwo( h ); + shiftScale.y = (float)h / pot; + + hackedView.shaderParms[4] = shiftScale.x; + hackedView.shaderParms[5] = shiftScale.y; + } + + gameRenderWorld->RenderScene( &portalView ); + renderSystem->CaptureRenderToImage( "_currentRender" ); + + hackedView.forceUpdate = true; // FIX: for smoke particles not drawing when portalSky present + }//else{ + gameRenderWorld->RenderScene( &hackedView ); + //} + + // process the frame +// HEXEN : Zeroth - d3xp, not needed for skybox? fxManager->Process( &hackedView ); + if ( player->spectating ) { return; } + //if ( !hud ) { + //return; + //} + // draw screen blobs if ( !pm_thirdPerson.GetBool() && !g_skipViewEffects.GetBool() ) { for ( int i = 0 ; i < MAX_SCREEN_BLOBS ; i++ ) { diff --git a/game/Projectile.cpp b/game/Projectile.cpp index 6460ae04..b5e1a4a3 100644 --- a/game/Projectile.cpp +++ b/game/Projectile.cpp @@ -55,6 +55,8 @@ const idEventDef EV_Explode( "", NULL ); const idEventDef EV_Fizzle( "", NULL ); const idEventDef EV_RadiusDamage( "", "e" ); const idEventDef EV_GetProjectileState( "getProjectileState", NULL, 'd' ); +// HEXEN : Zeroth +const idEventDef EV_Launch( "Launch", "vvvfff" ); CLASS_DECLARATION( idEntity, idProjectile ) EVENT( EV_Explode, idProjectile::Event_Explode ) @@ -62,6 +64,8 @@ CLASS_DECLARATION( idEntity, idProjectile ) EVENT( EV_Touch, idProjectile::Event_Touch ) EVENT( EV_RadiusDamage, idProjectile::Event_RadiusDamage ) EVENT( EV_GetProjectileState, idProjectile::Event_GetProjectileState ) +// HEXEN : Zeroth + EVENT( EV_Launch, idProjectile::Event_Launch ) END_CLASS /* @@ -302,6 +306,13 @@ void idProjectile::Launch( const idVec3 &start, const idVec3 &dir, const idVec3 int contents; int clipMask; +// HEXEN : Zeroth + bool randomDirection; + idVec3 newDir; + bool randomVelocity; + float randomAngular; + idVec2 fuse_random; + // allow characters to throw projectiles during cinematics, but not the player if ( owner.GetEntity() && !owner.GetEntity()->IsType( idPlayer::Type ) ) { cinematic = owner.GetEntity()->cinematic; @@ -314,12 +325,47 @@ void idProjectile::Launch( const idVec3 &start, const idVec3 &dir, const idVec3 spawnArgs.GetVector( "velocity", "0 0 0", velocity ); + // HEXEN : Zeroth + randomDirection = spawnArgs.GetBool( "random_direction" ); + randomVelocity = spawnArgs.GetBool( "random_velocity" ); + + // HEXEN : Zeroth + if ( randomVelocity ) { + velocity.x *= gameLocal.random.RandomFloat() + 0.5f; + velocity.y *= gameLocal.random.RandomFloat() + 0.5f; + velocity.z *= gameLocal.random.RandomFloat() + 0.5f; + } + // HEXEN : Zeroth + if ( randomDirection ) { + newDir.x = gameLocal.random.RandomInt(360) - 180; + newDir.y = gameLocal.random.RandomInt(360) - 180; + newDir.z = gameLocal.random.RandomInt(360) - 180; + newDir.Normalize(); + + velocity = velocity.Length() * newDir; + } + speed = velocity.Length() * launchPower; damagePower = dmgPower; spawnArgs.GetAngles( "angular_velocity", "0 0 0", angular_velocity ); + // HEXEN : Zeroth + randomAngular = spawnArgs.GetFloat( "random_angular_velocity" ); + + // HEXEN : Zeroth + if ( randomAngular != 0.0f ) { + angular_velocity.yaw += gameLocal.random.RandomInt( randomAngular ); + angular_velocity.pitch += gameLocal.random.RandomInt( randomAngular ); + angular_velocity.roll += gameLocal.random.RandomInt( randomAngular ); + } +// +// // HEXEN : Zeroth +// SetAngles( idAngles( gameLocal.random.RandomInt( 360 * 2 ) - 360, +// gameLocal.random.RandomInt( 360 * 2 ) - 360, +// gameLocal.random.RandomInt( 360 * 2 ) - 360 ) ); + linear_friction = spawnArgs.GetFloat( "linear_friction" ); angular_friction = spawnArgs.GetFloat( "angular_friction" ); contact_friction = spawnArgs.GetFloat( "contact_friction" ); @@ -328,6 +374,12 @@ void idProjectile::Launch( const idVec3 &start, const idVec3 &dir, const idVec3 gravity = spawnArgs.GetFloat( "gravity" ); fuse = spawnArgs.GetFloat( "fuse" ); + // HEXEN : Zeroth + fuse_random = spawnArgs.GetVec2( "fuse_random" ); + if ( fuse_random.y > 0.0f ) { + fuse = gameLocal.random.CRandomFloat() * fuse_random.y + fuse_random.x; + } + projectileFlags.detonate_on_world = spawnArgs.GetBool( "detonate_on_world" ); projectileFlags.detonate_on_actor = spawnArgs.GetBool( "detonate_on_actor" ); projectileFlags.randomShaderSpin = spawnArgs.GetBool( "random_shader_spin" ); @@ -382,8 +434,15 @@ void idProjectile::Launch( const idVec3 &start, const idVec3 &dir, const idVec3 physicsObj.SetGravity( gravVec * gravity ); physicsObj.SetContents( contents ); physicsObj.SetClipMask( clipMask ); - physicsObj.SetLinearVelocity( axis[ 2 ] * speed + pushVelocity ); - physicsObj.SetAngularVelocity( angular_velocity.ToAngularVelocity() * axis ); + + if ( spawnArgs.GetBool( "override_launch" ) ) { + physicsObj.SetLinearVelocity( velocity ); + physicsObj.SetAngularVelocity( angular_velocity.ToAngularVelocity() ); + } else { + physicsObj.SetLinearVelocity( axis[ 2 ] * speed + pushVelocity ); + physicsObj.SetAngularVelocity( angular_velocity.ToAngularVelocity() * axis ); + } + physicsObj.SetOrigin( start ); physicsObj.SetAxis( axis ); @@ -440,7 +499,6 @@ idProjectile::Think ================ */ void idProjectile::Think( void ) { - if ( thinkFlags & TH_THINK ) { if ( thrust && ( gameLocal.time < thrust_end ) ) { // evaluate force @@ -591,7 +649,7 @@ bool idProjectile::Collide( const trace_t &collision, const idVec3 &velocity ) { } if ( damageDefName[0] != '\0' ) { - ent->Damage( this, owner.GetEntity(), dir, damageDefName, damageScale, CLIPMODEL_ID_TO_JOINT_HANDLE( collision.c.id ) ); + ent->Damage( this, owner.GetEntity(), dir, damageDefName, damageScale, CLIPMODEL_ID_TO_JOINT_HANDLE( collision.c.id ), idVec3( collision.c.point ) ); ignore = ent; } } @@ -1544,7 +1602,7 @@ void idSoulCubeMissile::KillTarget( const idVec3 &dir ) { if ( ( act->health > 0 ) && ownerEnt && ownerEnt->IsType( idPlayer::Type ) && ( ownerEnt->health > 0 ) && !act->spawnArgs.GetBool( "boss" ) ) { static_cast( ownerEnt )->GiveHealthPool( act->health ); } - act->Damage( this, owner.GetEntity(), dir, spawnArgs.GetString( "def_damage" ), 1.0f, INVALID_JOINT ); + act->Damage( this, owner.GetEntity(), dir, spawnArgs.GetString( "def_damage" ), 1.0f, INVALID_JOINT, idVec3(0,0,0) ); act->GetAFPhysics()->SetTimeScale( 0.25 ); StartSound( "snd_explode", SND_CHANNEL_BODY, 0, false, NULL ); } @@ -1826,7 +1884,7 @@ void idBFGProjectile::Think( void ) { if ( damageFreq && *(const char *)damageFreq && beamTargets[i].target.GetEntity() && beamTargets[i].target.GetEntity()->CanDamage( GetPhysics()->GetOrigin(), org ) ) { org = beamTargets[i].target.GetEntity()->GetPhysics()->GetOrigin() - GetPhysics()->GetOrigin(); org.Normalize(); - beamTargets[i].target.GetEntity()->Damage( this, owner.GetEntity(), org, damageFreq, ( damagePower ) ? damagePower : 1.0f, INVALID_JOINT ); + beamTargets[i].target.GetEntity()->Damage( this, owner.GetEntity(), org, damageFreq, ( damagePower ) ? damagePower : 1.0f, INVALID_JOINT, idVec3(0,0,0) ); } else { beamTargets[i].renderEntity.shaderParms[ SHADERPARM_RED ] = beamTargets[i].renderEntity.shaderParms[ SHADERPARM_GREEN ] = @@ -2025,7 +2083,7 @@ void idBFGProjectile::Explode( const trace_t &collision, idEntity *ignore ) { if ( damage[0] && ( beamTargets[i].target.GetEntity()->entityNumber > gameLocal.numClients - 1 ) ) { dir = beamTargets[i].target.GetEntity()->GetPhysics()->GetOrigin() - GetPhysics()->GetOrigin(); dir.Normalize(); - beamTargets[i].target.GetEntity()->Damage( this, ownerEnt, dir, damage, damageScale, ( collision.c.id < 0 ) ? CLIPMODEL_ID_TO_JOINT_HANDLE( collision.c.id ) : INVALID_JOINT ); + beamTargets[i].target.GetEntity()->Damage( this, ownerEnt, dir, damage, damageScale, ( collision.c.id < 0 ) ? CLIPMODEL_ID_TO_JOINT_HANDLE( collision.c.id ) : INVALID_JOINT, idVec3( collision.c.point ) ); } } @@ -2072,6 +2130,8 @@ void idDebris::Spawn( void ) { owner = NULL; smokeFly = NULL; smokeFlyTime = 0; +// HEXEN : Zeroth + randomPosInBounds=false; } /* @@ -2161,6 +2221,17 @@ void idDebris::Launch( void ) { bool randomVelocity; idMat3 axis; +// HEXEN : Zeroth + bool randomDirection; + bool randomPosition; + float direction_from_spawner; + idVec3 newDir; + // DG: FIXME: originBeforeAdjustis never set but used below, + // I initialize it to (0, 0, 0) so it at least has a deterministic and valid value + idVec3 originBeforeAdjust(0, 0, 0); + float randomAngular; + idVec2 fuse_random; + renderEntity.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( gameLocal.time ); spawnArgs.GetVector( "velocity", "0 0 0", velocity ); @@ -2172,8 +2243,19 @@ void idDebris::Launch( void ) { bounce = spawnArgs.GetFloat( "bounce" ); mass = spawnArgs.GetFloat( "mass" ); gravity = spawnArgs.GetFloat( "gravity" ); - fuse = spawnArgs.GetFloat( "fuse" ); randomVelocity = spawnArgs.GetBool ( "random_velocity" ); + fuse = spawnArgs.GetFloat( "fuse" ); +// HEXEN : Zeroth + randomPosition = spawnArgs.GetBool( "random_pos_in_spawner" ); + randomDirection = spawnArgs.GetBool( "random_direction" ); + direction_from_spawner = spawnArgs.GetFloat( "direction_from_spawner" ); + randomAngular = spawnArgs.GetFloat( "random_angular_velocity" ); + fuse_random = spawnArgs.GetVec2( "fuse_random" ); + + // HEXEN : Zeroth + if ( fuse_random.y > 0.0f ) { + fuse = gameLocal.random.CRandomFloat() * fuse_random.y + fuse_random.x; + } if ( mass <= 0 ) { gameLocal.Error( "Invalid mass on '%s'\n", GetEntityDefName() ); @@ -2185,6 +2267,63 @@ void idDebris::Launch( void ) { velocity.z *= gameLocal.random.RandomFloat() + 0.5f; } + // HEXEN : Zeroth + if ( randomDirection ) { + newDir.x = gameLocal.random.RandomInt(360) - 180; + newDir.y = gameLocal.random.RandomInt(360) - 180; + newDir.z = gameLocal.random.RandomInt(360) - 180; + newDir.Normalize(); + + velocity = velocity.Length() * newDir; + } + + if ( randomPosition && randomPosInBounds ) { + + // set origin to random position inside moveable's bounds + newDir.x = gameLocal.random.RandomFloat() * (randomPosEnt->GetPhysics()->GetBounds()[1].x - randomPosEnt->GetPhysics()->GetBounds()[0].x); + newDir.x += randomPosEnt->GetPhysics()->GetBounds()[0].x; + newDir.y = gameLocal.random.RandomFloat() * (randomPosEnt->GetPhysics()->GetBounds()[1].y - randomPosEnt->GetPhysics()->GetBounds()[0].y); + newDir.y += randomPosEnt->GetPhysics()->GetBounds()[0].y; + newDir.z = gameLocal.random.RandomFloat() * (randomPosEnt->GetPhysics()->GetBounds()[1].z - randomPosEnt->GetPhysics()->GetBounds()[0].z); + newDir.z += randomPosEnt->GetPhysics()->GetBounds()[0].z; + newDir *= randomPosEnt->GetPhysics()->GetClipModel()->GetAxis(); + newDir += randomPosEnt->GetPhysics()->GetOrigin(); + GetPhysics()->SetOrigin( newDir ); + + // overrides "randomDirection" + if ( direction_from_spawner != 0 ) { + // DG: FIXME: originBeforeAdjust is never set + newDir = GetPhysics()->GetOrigin() - originBeforeAdjust; + newDir.Normalize(); + // DG: FIXME: what is this supposed to do? direction_from_spawner + // doesn't change the direction of newdir, it only scales it + velocity += direction_from_spawner * newDir; + + // get center of bounds on moveable + newDir = randomPosEnt->GetPhysics()->GetBounds()[1] + randomPosEnt->GetPhysics()->GetBounds()[0]; + newDir *= randomPosEnt->GetPhysics()->GetClipModel()->GetAxis(); + + // get vector from moveable's center to debris' origin + newDir = randomPosEnt->GetPhysics()->GetOrigin() - newDir; + newDir.Normalize(); + + // set velocity in that direction + velocity = velocity.Length() * newDir; + } + } + + // HEXEN : Zeroth + if ( randomAngular != 0.0f ) { + angular_velocity.yaw += gameLocal.random.RandomInt( randomAngular ); + angular_velocity.pitch += gameLocal.random.RandomInt( randomAngular ); + angular_velocity.roll += gameLocal.random.RandomInt( randomAngular ); + } + + // HEXEN : Zeroth - why shoulnt the angles ever be not random ? + SetAngles( idAngles( gameLocal.random.RandomInt( 360 * 2 ) - 360, + gameLocal.random.RandomInt( 360 * 2 ) - 360, + gameLocal.random.RandomInt( 360 * 2 ) - 360 ) ); + if ( health ) { fl.takedamage = true; } @@ -2221,8 +2360,27 @@ void idDebris::Launch( void ) { } physicsObj.SetBouncyness( bounce ); physicsObj.SetGravity( gravVec * gravity ); - physicsObj.SetContents( 0 ); - physicsObj.SetClipMask( MASK_SOLID | CONTENTS_MOVEABLECLIP ); + if ( spawnArgs.GetBool ( "hascontents" ) ) { + if ( spawnArgs.GetFloat( "mass", "10", mass ) ) { + physicsObj.SetMass( mass ); + } + + if ( spawnArgs.GetBool( "noimpact" ) || spawnArgs.GetBool( "notPushable" )/** || IsType( idWood::Type )**/ ) { // HEXEN : Zeroth, added idWood + physicsObj.DisableImpact(); + } + + if ( spawnArgs.GetBool( "nonsolid" ) ) { + physicsObj.SetContents( CONTENTS_CORPSE | CONTENTS_RENDERMODEL ); + physicsObj.SetClipMask( MASK_SOLID | CONTENTS_CORPSE | CONTENTS_MOVEABLECLIP ); + } else { + physicsObj.SetContents( CONTENTS_SOLID ); + physicsObj.SetClipMask( MASK_SOLID | CONTENTS_BODY | CONTENTS_CORPSE | CONTENTS_MOVEABLECLIP ); + } + } else { + physicsObj.SetContents( 0 ); + physicsObj.SetClipMask( MASK_SOLID | CONTENTS_MOVEABLECLIP ); + } + physicsObj.SetLinearVelocity( axis[ 0 ] * velocity[ 0 ] + axis[ 1 ] * velocity[ 1 ] + axis[ 2 ] * velocity[ 2 ] ); physicsObj.SetAngularVelocity( angular_velocity.ToAngularVelocity() * axis ); physicsObj.SetOrigin( GetPhysics()->GetOrigin() ); @@ -2399,3 +2557,8 @@ idDebris::Event_Fizzle void idDebris::Event_Fizzle( void ) { Fizzle(); } + +// HEXEN : Zeroth +void idProjectile::Event_Launch( const idVec3 &start, const idVec3 &dir, const idVec3 &pushVelocity, const float timeSinceFire, const float launchPower, const float dmgPower ) { + Launch( start, dir, pushVelocity, timeSinceFire, launchPower, dmgPower ); +} diff --git a/game/Projectile.h b/game/Projectile.h index 3ada8a3d..c9eba638 100644 --- a/game/Projectile.h +++ b/game/Projectile.h @@ -33,6 +33,8 @@ If you have questions concerning this license or the applicable additional terms #include "physics/Force_Constant.h" #include "Entity.h" +#include "Moveable.h" + /* =============================================================================== @@ -43,6 +45,8 @@ If you have questions concerning this license or the applicable additional terms extern const idEventDef EV_Explode; +class idMoveable; + class idProjectile : public idEntity { public : CLASS_PROTOTYPE( idProjectile ); @@ -123,14 +127,21 @@ public : private: bool netSyncPhysics; - +// HEXEN : Zeroth: changed to protected +protected: void AddDefaultDamageEffect( const trace_t &collision, const idVec3 &velocity ); +private: + void Event_Explode( void ); void Event_Fizzle( void ); void Event_RadiusDamage( idEntity *ignore ); void Event_Touch( idEntity *other, trace_t *trace ); void Event_GetProjectileState( void ); + +// HEXEN : Zeroth +private: + void Event_Launch( const idVec3 &start, const idVec3 &dir, const idVec3 &pushVelocity, const float timeSinceFire, const float launchPower, const float dmgPower ); }; class idGuidedProjectile : public idProjectile { @@ -268,6 +279,10 @@ public : void Event_Explode( void ); void Event_Fizzle( void ); + +public: + bool randomPosInBounds; + idMoveable *randomPosEnt; }; #endif /* !__GAME_PROJECTILE_H__ */ diff --git a/game/Pvs.cpp b/game/Pvs.cpp index 4536710e..8003e85e 100644 --- a/game/Pvs.cpp +++ b/game/Pvs.cpp @@ -1421,3 +1421,36 @@ void idPVS::ReadPVS( const pvsHandle_t handle, const idBitMsg &msg ) { } #endif + +/* +================ +Zeroth +idPVS::CheckAreasForPortalSky +================ +*/ +bool idPVS::CheckAreasForPortalSky( const pvsHandle_t handle, const idVec3 &origin ) { + int j, sourceArea; + + if ( handle.i < 0 || handle.i >= MAX_CURRENT_PVS || handle.h != currentPVS[handle.i].handle.h ) { + return false; + } + + sourceArea = gameRenderWorld->PointInArea( origin ); + + if ( sourceArea == -1 ) { + return false; + } + + for ( j = 0; j < numAreas; j++ ) { + + if ( !( currentPVS[handle.i].pvs[j>>3] & (1 << (j&7)) ) ) { + continue; + } + + if ( gameRenderWorld->CheckAreaForPortalSky( j ) ) { + return true; + } + } + + return false; +} diff --git a/game/Pvs.h b/game/Pvs.h index 963b1786..8202d2fe 100644 --- a/game/Pvs.h +++ b/game/Pvs.h @@ -97,6 +97,8 @@ class idPVS { void ReadPVS( const pvsHandle_t handle, const idBitMsg &msg ); #endif + bool CheckAreasForPortalSky( const pvsHandle_t handle, const idVec3 &origin ); // HEXEN : Zeroth + private: int numAreas; int numPortals; diff --git a/game/Sound.cpp b/game/Sound.cpp index d5a1f3e5..53a730d1 100644 --- a/game/Sound.cpp +++ b/game/Sound.cpp @@ -26,6 +26,7 @@ If you have questions concerning this license or the applicable additional terms =========================================================================== */ +#include "gamesys/SysCvar.h" #include "sys/platform.h" #include "Entity.h" @@ -128,6 +129,14 @@ void idSound::Spawn( void ) { } else { timerOn = false; } + + // HEXEN : Zeroth + // ****** thanks SnoopJeDi ( http://www.doom3world.org/phpbb2/viewtopic.php?f=56&t=12469&p=214427#p214427 ) + + if ( spawnArgs.GetBool( "s_music" ) ) { //SnoopJeDi + gameLocal.musicSpeakers.Append( entityNumber ); + } + // ****** } /* @@ -260,6 +269,7 @@ idSound::DoSound */ void idSound::DoSound( bool play ) { if ( play ) { + s_music_vol.SetModified(); //zeroth: sometimes triggered speakers like to ignore that they're supposed to be faded StartSoundShader( refSound.shader, SND_CHANNEL_ANY, refSound.parms.soundShaderFlags, true, &playingUntilTime ); playingUntilTime += gameLocal.time; } else { diff --git a/game/Target.cpp b/game/Target.cpp index 1912a788..94ab0861 100644 --- a/game/Target.cpp +++ b/game/Target.cpp @@ -147,7 +147,7 @@ void idTarget_Damage::Event_Activate( idEntity *activator ) { for( i = 0; i < targets.Num(); i++ ) { ent = targets[ i ].GetEntity(); if ( ent ) { - ent->Damage( this, this, vec3_origin, damage, 1.0f, INVALID_JOINT ); + ent->Damage( this, this, vec3_origin, damage, 1.0f, INVALID_JOINT, idVec3(0,0,0) ); } } } @@ -195,6 +195,8 @@ idTarget_EndLevel::Event_Activate */ void idTarget_EndLevel::Event_Activate( idEntity *activator ) { idStr nextMap; + idStr spawnPoint; + idStr endEoCRelease; if ( spawnArgs.GetBool( "endOfGame" ) ) { cvarSystem->SetCVarBool( "g_nightmare", true ); @@ -207,6 +209,10 @@ void idTarget_EndLevel::Event_Activate( idEntity *activator ) { return; } +// HEXEN : Zeroth + spawnArgs.GetString( "spawnPoint", "", spawnPoint ); + gameLocal.SetLocalPlayerSpawnPoint(spawnPoint); + if ( spawnArgs.GetInt( "devmap", "0" ) ) { gameLocal.sessionCommand = "devmap "; // only for special demos } else { diff --git a/game/Trigger.cpp b/game/Trigger.cpp index 212b2b07..a91b769a 100644 --- a/game/Trigger.cpp +++ b/game/Trigger.cpp @@ -29,6 +29,7 @@ If you have questions concerning this license or the applicable additional terms #include "sys/platform.h" #include "script/Script_Thread.h" #include "Player.h" +#include "ai/AI.h" #include "Trigger.h" @@ -121,6 +122,7 @@ idTrigger::Enable void idTrigger::Enable( void ) { GetPhysics()->SetContents( CONTENTS_TRIGGER ); GetPhysics()->EnableClip(); + gameLocal.SetPersistentTrigger( "trig", name.c_str(), true ); } /* @@ -132,6 +134,7 @@ void idTrigger::Disable( void ) { // we may be relinked if we're bound to another object, so clear the contents as well GetPhysics()->SetContents( 0 ); GetPhysics()->DisableClip(); + gameLocal.SetPersistentTrigger( "trig", name.c_str(), false ); } /* @@ -235,6 +238,16 @@ void idTrigger::Spawn( void ) { } +// HEXEN : Zeroth +void idTrigger::eoc_Check_Automap( idEntity *activator ) { + idStr mapFloor = spawnArgs.GetString( "floor" ); + if ( mapFloor != "" && activator->IsType(idPlayer::Type)) { + idPlayer *p1 = static_cast< idPlayer * > ( activator ); + + p1->AutoMapChange(mapFloor); + } +} + /* =============================================================================== @@ -391,12 +404,66 @@ idTrigger_Multi::TriggerAction ================ */ void idTrigger_Multi::TriggerAction( idEntity *activator ) { + ActivateTargets( triggerWithSelf ? this : activator ); CallScript(); +// HEXEN : Zeroth + eoc_Check_Automap( activator ); + //eoc_Check_Gravity(); +/* + if ( spawnArgs.GetBool("pauseGame") ) { + gameLocal.paused = true; + } +*/ + if ( spawnArgs.GetBool("killAllMonsters") ) { + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "massacre" ); + cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "massacre" ); + } + + if ( spawnArgs.GetBool("stopAllSounds") ) { + gameSoundWorld->StopAllSounds(); + } + + idPlayer *player = gameLocal.GetLocalPlayer(); + if ( player != NULL ) { + // show custom gui, if any - MUST COME BEFORE PLAYER SPAWN ARGS STUFF BELOW + idStr guiname; + guiname = spawnArgs.GetString("showGui"); + if ( guiname != "" ) { + player->OpenCustomGui( guiname ); + } + + // set player spawn args, if any + const idKeyValue *kv; + idStr key; + + kv = spawnArgs.MatchPrefix( "setPlayerArg_", NULL ); + while( kv ) { + key = kv->GetKey(); + key = key.Mid(13,key.Length()); + + player->spawnArgs.Set( key.c_str(), kv->GetValue() ); + kv = spawnArgs.MatchPrefix( "setPlayerArg_", kv ); + } + } +/* + // HEXEN : Zeroth - set local player's variables to let the class know to save + // some important info in his end-of-game save screen. that info will let us + // be able to use save games from old releases/versions in new releases. + spawnArgs.GetString( "endEoCRelease", "", endEoCRelease ); + if ( endEoCRelease != "" ) { + idPlayer *player = gameLocal.GetLocalPlayer(); + if ( player != NULL ) { + player->beaten=true; + } + } +*/ if ( wait >= 0 ) { nextTriggerTime = gameLocal.time + SEC2MS( wait + random * gameLocal.random.CRandomFloat() ); } else { + gameLocal.SetPersistentRemove( name.c_str() ); + // we can't just remove (this) here, because this is a touch function // called while looping through area links... nextTriggerTime = gameLocal.time + 1; @@ -610,6 +677,8 @@ void idTrigger_EntityName::TriggerAction( idEntity *activator ) { if ( wait >= 0 ) { nextTriggerTime = gameLocal.time + SEC2MS( wait + random * gameLocal.random.CRandomFloat() ); } else { + gameLocal.SetPersistentRemove( name.c_str() ); + // we can't just remove (this) here, because this is a touch function // called while looping through area links... nextTriggerTime = gameLocal.time + 1; @@ -783,6 +852,7 @@ void idTrigger_Timer::Enable( void ) { if ( !on ) { on = true; PostEventSec( &EV_Timer, delay ); + gameLocal.SetPersistentTrigger( "timer", name.c_str(), true ); } } @@ -796,6 +866,7 @@ void idTrigger_Timer::Disable( void ) { if ( on ) { on = false; CancelEvents( &EV_Timer ); + gameLocal.SetPersistentTrigger( "timer", name.c_str(), false ); } } @@ -908,6 +979,8 @@ void idTrigger_Count::Event_Trigger( idEntity *activator ) { } else { goal = -1; } + gameLocal.SetPersistentTriggerInt( "count", "count", name.c_str(), count ); + gameLocal.SetPersistentTriggerInt( "count", "goal", name.c_str(), goal ); PostEventSec( &EV_TriggerAction, delay, activator ); } } @@ -922,6 +995,8 @@ void idTrigger_Count::Event_TriggerAction( idEntity *activator ) { ActivateTargets( activator ); CallScript(); if ( goal == -1 ) { + gameLocal.SetPersistentRemove( name.c_str() ); + PostEventMS( &EV_Remove, 0 ); } } @@ -948,7 +1023,8 @@ idTrigger_Hurt::idTrigger_Hurt idTrigger_Hurt::idTrigger_Hurt( void ) { on = false; delay = 0.0f; - nextTime = 0; +// HEXEN : Zeroth + nextTime.Clear(); } /* @@ -959,7 +1035,20 @@ idTrigger_Hurt::Save void idTrigger_Hurt::Save( idSaveGame *savefile ) const { savefile->WriteBool( on ); savefile->WriteFloat( delay ); - savefile->WriteInt( nextTime ); +// HEXEN : Zeroth + savefile->WriteInt( nextTime.Num() ); + for (int i=0; iWriteString( nextTime[i].GetString("name") ); + savefile->WriteFloat( nextTime[i].GetFloat("time") ); + } + savefile->WriteBool(limitEntityType); + savefile->WriteBool(dontTripby_LocalPlayer); + savefile->WriteBool(tripby_idPlayer); + savefile->WriteBool(tripby_idAI); + savefile->WriteBool(tripby_idMoveable); + savefile->WriteBool(tripby_idItem); + savefile->WriteBool(tripby_idActor); + savefile->WriteBool(tripby_idProjectile); } /* @@ -970,7 +1059,26 @@ idTrigger_Hurt::Restore void idTrigger_Hurt::Restore( idRestoreGame *savefile ) { savefile->ReadBool( on ); savefile->ReadFloat( delay ); - savefile->ReadInt( nextTime ); +// HEXEN : Zeroth + int c; + float f; + idStr s; + savefile->ReadInt( c ); + for (int i=0; iReadString( s ); + savefile->ReadFloat( f ); + nextTime.Append( idDict() ); + nextTime[i].Set("name", s); + nextTime[i].SetFloat("time", f); + } + savefile->ReadBool(limitEntityType); + savefile->ReadBool(dontTripby_LocalPlayer); + savefile->ReadBool(tripby_idPlayer); + savefile->ReadBool(tripby_idAI); + savefile->ReadBool(tripby_idMoveable); + savefile->ReadBool(tripby_idItem); + savefile->ReadBool(tripby_idActor); + savefile->ReadBool(tripby_idProjectile); } /* @@ -984,7 +1092,21 @@ idTrigger_Hurt::Spawn void idTrigger_Hurt::Spawn( void ) { spawnArgs.GetBool( "on", "1", on ); spawnArgs.GetFloat( "delay", "1.0", delay ); - nextTime = gameLocal.time; +// HEXEN : Zeroth + for (int i=0; iIsType( idPlayer::Type ) ) || + ( !tripby_idAI && other->IsType( idAI::Type ) ) || + ( !tripby_idActor && other->IsType( idActor::Type ) ) || + ( !tripby_idProjectile && other->IsType( idProjectile::Type ) ) || + ( !tripby_idItem && other->IsType( idItem::Type ) ) || + ( !tripby_idMoveable && other->IsType( idMoveable::Type ) ) ) { + return; + } + } + + if ( dontTripby_LocalPlayer && other->IsType( idPlayer::Type ) && gameLocal.GetLocalPlayer() == ( static_cast( other ) ) ) { + return; + } + + // delay time based on individual entites + if ( delay > 0 ) { + name = other->GetName(); + located=false; + for (i=0; i < nextTime.Num(); i++) { + if ( nextTime[i].GetString( "name" ) == name ) { + if ( nextTime[i].GetFloat( "time" ) > gameLocal.time ) { + return; + } else { + nextTime[i].SetFloat( "time", gameLocal.time + SEC2MS( delay )); + located = true; + break; + } + } + } + + if ( !located ) { + nextTime.Append( idDict() ); + nextTime[nextTime.Num()-1].Set("name", name); + nextTime[nextTime.Num()-1].SetFloat("time", gameLocal.time); + } + } - if ( on && other && gameLocal.time >= nextTime ) { damage = spawnArgs.GetString( "def_damage", "damage_painTrigger" ); - other->Damage( NULL, NULL, vec3_origin, damage, 1.0f, INVALID_JOINT ); + // HEXEN : Zeroth: added the static casts + other->Damage( static_cast< idEntity* >( this ), static_cast< idEntity* >( this ), vec3_origin, damage, 1.0f, INVALID_JOINT, trace->c.point ); ActivateTargets( other ); CallScript(); - - nextTime = gameLocal.time + SEC2MS( delay ); } } @@ -1014,6 +1177,7 @@ idTrigger_Hurt::Event_Toggle */ void idTrigger_Hurt::Event_Toggle( idEntity *activator ) { on = !on; + gameLocal.SetPersistentTrigger( "hurt_toggle", name.c_str(), on ); } @@ -1068,6 +1232,28 @@ idTrigger_Touch::idTrigger_Touch */ idTrigger_Touch::idTrigger_Touch( void ) { clipModel = NULL; +// HEXEN : Zeroth + nextTime.Clear(); + onEntrance=false; + touchingEntities.Clear(); + flagEntities.Clear(); + resistTimeEntities.Clear(); + +// HEXEN : Zeroth - for limit entity types + dontTripby_LocalPlayer=false; + limitEntityType=false; + tripby_idPlayer=false; + tripby_idAI=false; + tripby_idMoveable=false; + tripby_idItem=false; + tripby_idActor=false; + tripby_idProjectile=false; + +// HEXEN : Zeroth - for trigger_hurtmulti + hurtmulti=false; + +// HEXEN : Zeroth - primarily for trigger_hurtmulti + delay=0; } /* @@ -1085,6 +1271,30 @@ void idTrigger_Touch::Spawn( void ) { if ( spawnArgs.GetBool( "start_on" ) ) { BecomeActive( TH_THINK ); } + +// HEXEN : Zeroth - for delay + spawnArgs.GetFloat( "delay", "0.0", delay ); + for (int i=0; iWriteClipModel( clipModel ); + +// HEXEN : Zeroth + savefile->WriteBool(onEntrance); + savefile->WriteBool(limitEntityType); + savefile->WriteBool(dontTripby_LocalPlayer); + savefile->WriteBool(tripby_idPlayer); + savefile->WriteBool(tripby_idAI); + savefile->WriteBool(tripby_idMoveable); + savefile->WriteBool(tripby_idItem); + savefile->WriteBool(tripby_idActor); + savefile->WriteBool(tripby_idProjectile); + savefile->WriteBool( hurtmulti ); + savefile->WriteFloat( delay ); + + savefile->WriteInt(touchingEntities.Num()); + for (int i=0; iWriteString(touchingEntities[i]); + savefile->WriteBool(flagEntities[i]); + savefile->WriteInt(resistTimeEntities[i]); + } + + savefile->WriteInt(nextTime.Num()); + for (int i=0; iWriteString( nextTime[i].GetString("name") ); + savefile->WriteFloat( nextTime[i].GetFloat("time") ); + } } /* @@ -1103,6 +1339,45 @@ idTrigger_Touch::Restore */ void idTrigger_Touch::Restore( idRestoreGame *savefile ) { savefile->ReadClipModel( clipModel ); + +// HEXEN : Zeroth + savefile->ReadBool(onEntrance); + savefile->ReadBool(limitEntityType); + savefile->ReadBool(dontTripby_LocalPlayer); + savefile->ReadBool(tripby_idPlayer); + savefile->ReadBool(tripby_idAI); + savefile->ReadBool(tripby_idMoveable); + savefile->ReadBool(tripby_idItem); + savefile->ReadBool(tripby_idActor); + savefile->ReadBool(tripby_idProjectile); + savefile->ReadBool( hurtmulti ); + savefile->ReadFloat( delay ); + + bool bol; + int num; + idStr str; + int tim; + savefile->ReadInt(num); + for (int i=0; iReadString(str); + touchingEntities.Append(str); + savefile->ReadBool(bol); + flagEntities.Append(bol); + savefile->ReadInt(tim); + resistTimeEntities.Append(tim); + } + int c; + float f; + idStr s; + savefile->ReadInt( c ); + for (int i=0; iReadString( s ); + savefile->ReadFloat( f ); + nextTime.Append( idDict() ); + nextTime[i].Set("name", s); + nextTime[i].SetFloat("time", f); + } + idStr g; } /* @@ -1110,41 +1385,146 @@ void idTrigger_Touch::Restore( idRestoreGame *savefile ) { idTrigger_Touch::TouchEntities ================ */ -void idTrigger_Touch::TouchEntities( void ) { - int numClipModels, i; +void idTrigger_Touch::TouchEntities( void ) { //Z.TODO: this is getting messy, maybe split it into separate entities? + int numClipModels, t, i, c; idBounds bounds; idClipModel *cm, *clipModelList[ MAX_GENTITIES ]; - if ( clipModel == NULL || scriptFunction == NULL ) { +// HEXEN : Zeroth - for trigger_hurtmulti + const char *damage; + idStr name; + bool dontTrigger = true; // DG: initializing this so ensure it has a deterministic value + +// HEXEN : Zeroth - for limit entity type + idEntity *entity=NULL; + idVec3 cmOrigin; + idMat3 cmAxis; + cmHandle_t cmHandle; + idVec3 myOrigin; + idMat3 myAxis; + + if ( clipModel == NULL ) { return; } +// HEXEN : Zeroth - for trigger_enter + if ( onEntrance ) { + for ( c=0; cGetBounds(), clipModel->GetOrigin(), clipModel->GetAxis() ); numClipModels = gameLocal.clip.ClipModelsTouchingBounds( bounds, -1, clipModelList, MAX_GENTITIES ); for ( i = 0; i < numClipModels; i++ ) { cm = clipModelList[ i ]; - if ( !cm->IsTraceModel() ) { continue; } - idEntity *entity = cm->GetEntity(); - + entity = clipModelList[ i ]->GetEntity(); if ( !entity ) { continue; } - if ( !gameLocal.clip.ContentsModel( cm->GetOrigin(), cm, cm->GetAxis(), -1, - clipModel->Handle(), clipModel->GetOrigin(), clipModel->GetAxis() ) ) { + cmOrigin = cm->GetOrigin(); + cmAxis = cm->GetAxis(); + cmHandle = clipModel->Handle(); + myOrigin = clipModel->GetOrigin(); + myAxis = clipModel->GetAxis(); + + if ( !gameLocal.clip.ContentsModel( cmOrigin, cm, cmAxis, -1, cmHandle, myOrigin, myAxis ) ) { continue; } + +// HEXEN : Zeroth - limit entity types + if (limitEntityType) { + if ( ( !tripby_idPlayer && entity->IsType( idPlayer::Type ) ) || + ( !tripby_idAI && entity->IsType( idAI::Type ) ) || + ( !tripby_idActor && entity->IsType( idActor::Type ) ) || + ( !tripby_idProjectile && entity->IsType( idProjectile::Type ) ) || + ( !tripby_idItem && entity->IsType( idItem::Type ) ) || + ( !tripby_idMoveable && entity->IsType( idMoveable::Type ) ) ) { + continue; + } + } + + if ( dontTripby_LocalPlayer && entity->IsType( idPlayer::Type ) && gameLocal.GetLocalPlayer() == ( static_cast( entity ) ) ) { + return; + } + +// HEXEN : Zeroth - for trigger_enter: if the entity is still touching, dont trigger + if ( onEntrance ) { + dontTrigger=false; + for ( c=0; cGetName() ) { + flagEntities[c] = true; // flag it as touching + dontTrigger = true; + break; + } + } + +// HEXEN : Zeroth - for trigger_enter: if the entity was not in our list, add it. else don't trigger. + if ( !dontTrigger ) { + touchingEntities.Append(idStr(entity->GetName())); + flagEntities.Append(true); + } else { + continue; + } + } + +// HEXEN : Zeroth - delay time based on individual entites + if ( delay > 0 ) { + name = entity->GetName(); + for (t=0; t < nextTime.Num(); t++) { + if ( nextTime[t].GetString( "name" ) == name ) { + if ( gameLocal.time < nextTime[t].GetFloat( "time" ) ) { + dontTrigger = true; + break; + } else { + dontTrigger = false; + nextTime[t].SetFloat( "time", gameLocal.time + SEC2MS( delay )); + break; + } + } + } + + // if entity wasn't found in the list, add it + if ( t == nextTime.Num() ) { + nextTime.Append( idDict() ); + nextTime[t].Set("name", entity->GetName() ); + nextTime[t].SetFloat("time", gameLocal.time ); + } else if ( dontTrigger ) { + continue; + } + } + +// HEXEN : Zeroth - for trigger_hurtmulti + if ( hurtmulti ) { + damage = spawnArgs.GetString( "def_damage", "damage_painTrigger" ); + entity->Damage( static_cast< idEntity* >( this ), static_cast< idEntity* >( this ), vec3_origin, damage, 1.0f, INVALID_JOINT, idVec3(0,0,0) ); + } + ActivateTargets( entity ); - idThread *thread = new idThread(); - thread->CallFunction( entity, scriptFunction, false ); - thread->DelayedStart( 0 ); + if (scriptFunction != NULL) { + idThread *thread = new idThread(); + thread->CallFunction( entity, scriptFunction, false ); + thread->DelayedStart( 0 ); + } } } @@ -1180,6 +1560,7 @@ idTrigger_Touch::Enable */ void idTrigger_Touch::Enable( void ) { BecomeActive( TH_THINK ); + gameLocal.SetPersistentTrigger( "touch", name.c_str(), true ); } /* @@ -1189,4 +1570,5 @@ idTrigger_Touch::Disable */ void idTrigger_Touch::Disable( void ) { BecomeInactive( TH_THINK ); + gameLocal.SetPersistentTrigger( "touch", name.c_str(), false ); } diff --git a/game/Trigger.h b/game/Trigger.h index 74d503e3..fc2ccead 100644 --- a/game/Trigger.h +++ b/game/Trigger.h @@ -66,6 +66,9 @@ class idTrigger : public idEntity { void Event_Enable( void ); void Event_Disable( void ); + //void check_gravity(); + void eoc_Check_Automap( idEntity *activator ); // HEXEN : Zeroth + const function_t * scriptFunction; }; @@ -195,6 +198,8 @@ class idTrigger_Count : public idTrigger { void Save( idSaveGame *savefile ) const; void Restore( idRestoreGame *savefile ); + void SetCount( int c ) { count = c; }; + void SetGoal( int c ) { goal = c; }; void Spawn( void ); @@ -230,10 +235,22 @@ class idTrigger_Hurt : public idTrigger { private: bool on; float delay; - int nextTime; + idList nextTime; void Event_Touch( idEntity *other, trace_t *trace ); void Event_Toggle( idEntity *activator ); + +// HEXEN : Zeroth +private: + bool dontTripby_LocalPlayer; + bool limitEntityType; + bool tripby_idPlayer; + bool tripby_idAI; + bool tripby_idMoveable; + bool tripby_idItem; + bool tripby_idActor; + bool tripby_idProjectile; + bool tripby_LocalPlayer; }; @@ -285,6 +302,36 @@ class idTrigger_Touch : public idTrigger { idClipModel * clipModel; void Event_Trigger( idEntity *activator ); + +// HEXEN : Zeroth - for trigger_enter +private: + bool onEntrance; + idList touchingEntities; + idList flagEntities; + idList resistTimeEntities; + +// HEXEN : Zeroth - for limit entity types +private: + bool dontTripby_LocalPlayer; + bool limitEntityType; + bool tripby_idPlayer; + bool tripby_idAI; + bool tripby_idMoveable; + bool tripby_idItem; + bool tripby_idActor; + bool tripby_idProjectile; + +// HEXEN : Zeroth - for trigger_hurtmulti +private: + bool hurtmulti; + +// HEXEN : Zeroth - primarily for trigger_hurtmulti +private: + float delay; + +// HEXEN : Zeroth - for trigger_hurtmulti +private: + idList nextTime; // HEXEN : Zeroth. changed to idlist. not really useful anymore. }; #endif /* !__GAME_TRIGGER_H__ */ diff --git a/game/Weapon.cpp b/game/Weapon.cpp index 727e061b..e4273498 100644 --- a/game/Weapon.cpp +++ b/game/Weapon.cpp @@ -52,8 +52,8 @@ const idEventDef EV_Weapon_Clear( "" ); const idEventDef EV_Weapon_GetOwner( "getOwner", NULL, 'e' ); const idEventDef EV_Weapon_Next( "nextWeapon" ); const idEventDef EV_Weapon_State( "weaponState", "sd" ); -const idEventDef EV_Weapon_UseAmmo( "useAmmo", "d" ); -const idEventDef EV_Weapon_AddToClip( "addToClip", "d" ); +const idEventDef EV_Weapon_UseAmmo( "useAmmo", "f" ); +const idEventDef EV_Weapon_AddToClip( "addToClip", "f" ); const idEventDef EV_Weapon_AmmoInClip( "ammoInClip", NULL, 'f' ); const idEventDef EV_Weapon_AmmoAvailable( "ammoAvailable", NULL, 'f' ); const idEventDef EV_Weapon_TotalAmmoCount( "totalAmmoCount", NULL, 'f' ); @@ -321,10 +321,10 @@ void idWeapon::Save( idSaveGame *savefile ) const { savefile->WriteVec3( muzzle_kick_offset ); savefile->WriteInt( ammoType ); - savefile->WriteInt( ammoRequired ); - savefile->WriteInt( clipSize ); - savefile->WriteInt( ammoClip ); - savefile->WriteInt( lowAmmo ); + savefile->WriteFloat( ammoRequired ); + savefile->WriteFloat( clipSize ); + savefile->WriteFloat( ammoClip ); + savefile->WriteFloat( lowAmmo ); savefile->WriteBool( powerAmmo ); // savegames <= 17 @@ -374,8 +374,12 @@ void idWeapon::Save( idSaveGame *savefile ) const { savefile->WriteFloat( weaponOffsetScale ); savefile->WriteBool( allowDrop ); - savefile->WriteObject( projectileEnt ); +// HEXEN : Zeroth + savefile->WriteInt( projectileEnts.Num() ); + for (int i=0; iWriteObject( projectileEnts[i] ); + } } /* @@ -395,6 +399,7 @@ void idWeapon::Restore( idRestoreGame *savefile ) { // Re-link script fields WEAPON_ATTACK.LinkTo( scriptObject, "WEAPON_ATTACK" ); + WEAPON_ATTACK2.LinkTo( scriptObject, "WEAPON_ATTACK2" ); // HEXEN : Zeroth WEAPON_RELOAD.LinkTo( scriptObject, "WEAPON_RELOAD" ); WEAPON_NETRELOAD.LinkTo( scriptObject, "WEAPON_NETRELOAD" ); WEAPON_NETENDRELOAD.LinkTo( scriptObject, "WEAPON_NETENDRELOAD" ); @@ -483,10 +488,10 @@ void idWeapon::Restore( idRestoreGame *savefile ) { savefile->ReadVec3( muzzle_kick_offset ); savefile->ReadInt( (int &)ammoType ); - savefile->ReadInt( ammoRequired ); - savefile->ReadInt( clipSize ); - savefile->ReadInt( ammoClip ); - savefile->ReadInt( lowAmmo ); + savefile->ReadFloat( ammoRequired ); + savefile->ReadFloat( clipSize ); + savefile->ReadFloat( ammoClip ); + savefile->ReadFloat( lowAmmo ); savefile->ReadBool( powerAmmo ); // savegame versions <= 17 @@ -540,7 +545,16 @@ void idWeapon::Restore( idRestoreGame *savefile ) { savefile->ReadFloat( weaponOffsetScale ); savefile->ReadBool( allowDrop ); - savefile->ReadObject( reinterpret_cast( projectileEnt ) ); + + // HEXEN : Zeroth + int projs; + idEntity *proj; + savefile->ReadInt( projs ); + for (int i=0; iReadObject( reinterpret_cast( proj ) ); + projectileEnts.Append( proj ); + } + } /*********************************************************************** @@ -561,6 +575,7 @@ void idWeapon::Clear( void ) { scriptObject.Free(); WEAPON_ATTACK.Unlink(); + WEAPON_ATTACK2.Unlink(); // HEXEN : Zeroth WEAPON_RELOAD.Unlink(); WEAPON_NETRELOAD.Unlink(); WEAPON_NETENDRELOAD.Unlink(); @@ -718,7 +733,10 @@ void idWeapon::Clear( void ) { sndHum = NULL; isLinked = false; - projectileEnt = NULL; + +// HEXEN : Zeroth + projectileEnts.Clear(); + //projectileEnt = NULL; isFiring = false; } @@ -774,7 +792,7 @@ void idWeapon::InitWorldModel( const idDeclEntityDef *def ) { idWeapon::GetWeaponDef ================ */ -void idWeapon::GetWeaponDef( const char *objectname, int ammoinclip ) { +void idWeapon::GetWeaponDef( const char *objectname, float ammoinclip ) { const char *shader; const char *objectType; const char *vmodel; @@ -782,7 +800,7 @@ void idWeapon::GetWeaponDef( const char *objectname, int ammoinclip ) { const char *projectileName; const char *brassDefName; const char *smokeName; - int ammoAvail; + float ammoAvail; Clear(); @@ -795,9 +813,10 @@ void idWeapon::GetWeaponDef( const char *objectname, int ammoinclip ) { weaponDef = gameLocal.FindEntityDef( objectname ); ammoType = GetAmmoNumForName( weaponDef->dict.GetString( "ammoType" ) ); - ammoRequired = weaponDef->dict.GetInt( "ammoRequired" ); - clipSize = weaponDef->dict.GetInt( "clipSize" ); - lowAmmo = weaponDef->dict.GetInt( "lowAmmo" ); + ammoRequired = weaponDef->dict.GetFloat( "ammoRequired" ); + + clipSize = weaponDef->dict.GetFloat( "clipSize" ); + lowAmmo = weaponDef->dict.GetFloat( "lowAmmo" ); icon = weaponDef->dict.GetString( "icon" ); silent_fire = weaponDef->dict.GetBool( "silent_fire" ); @@ -856,7 +875,13 @@ void idWeapon::GetWeaponDef( const char *objectname, int ammoinclip ) { } // find some joints in the model for locating effects - barrelJointView = animator.GetJointHandle( "barrel" ); +// HEXEN : Zeroth + if ( !weaponDef->dict.FindKey( "barrelJointName" ) ) { + barrelJointView = animator.GetJointHandle( "barrel" ); + } else { + barrelJointView = animator.GetJointHandle( weaponDef->dict.GetString( "barrelJointName" ) ); + } + flashJointView = animator.GetJointHandle( "flash" ); ejectJointView = animator.GetJointHandle( "eject" ); guiLightJointView = animator.GetJointHandle( "guiLight" ); @@ -1004,6 +1029,7 @@ void idWeapon::GetWeaponDef( const char *objectname, int ammoinclip ) { } WEAPON_ATTACK.LinkTo( scriptObject, "WEAPON_ATTACK" ); + WEAPON_ATTACK2.LinkTo( scriptObject, "WEAPON_ATTACK2" ); // HEXEN : Zeroth WEAPON_RELOAD.LinkTo( scriptObject, "WEAPON_RELOAD" ); WEAPON_NETRELOAD.LinkTo( scriptObject, "WEAPON_NETRELOAD" ); WEAPON_NETENDRELOAD.LinkTo( scriptObject, "WEAPON_NETENDRELOAD" ); @@ -1074,18 +1100,18 @@ void idWeapon::UpdateGUI( void ) { } } - int inclip = AmmoInClip(); - int ammoamount = AmmoAvailable(); + float inclip = AmmoInClip(); + float ammoamount = AmmoAvailable(); if ( ammoamount < 0 ) { // show infinite ammo renderEntity.gui[ 0 ]->SetStateString( "player_ammo", "" ); } else { // show remaining ammo - renderEntity.gui[ 0 ]->SetStateString( "player_totalammo", va( "%i", ammoamount - inclip) ); - renderEntity.gui[ 0 ]->SetStateString( "player_ammo", ClipSize() ? va( "%i", inclip ) : "--" ); - renderEntity.gui[ 0 ]->SetStateString( "player_clips", ClipSize() ? va("%i", ammoamount / ClipSize()) : "--" ); - renderEntity.gui[ 0 ]->SetStateString( "player_allammo", va( "%i/%i", inclip, ammoamount - inclip ) ); + renderEntity.gui[ 0 ]->SetStateString( "player_totalammo", va( "%i", int(ammoamount - inclip)) ); // TODO: or use %f ? + renderEntity.gui[ 0 ]->SetStateString( "player_ammo", ClipSize() ? va( "%f", inclip ) : "--" ); + renderEntity.gui[ 0 ]->SetStateString( "player_clips", ClipSize() ? va("%f", ammoamount / ClipSize()) : "--" ); + renderEntity.gui[ 0 ]->SetStateString( "player_allammo", va( "%f/%f", inclip, ammoamount - inclip ) ); } renderEntity.gui[ 0 ]->SetStateBool( "player_ammo_empty", ( ammoamount == 0 ) ); renderEntity.gui[ 0 ]->SetStateBool( "player_clip_empty", ( inclip == 0 ) ); @@ -1426,6 +1452,29 @@ void idWeapon::BeginAttack( void ) { WEAPON_ATTACK = true; } +/* +================ +Zeroth +idWeapon::BeginAttack2 +================ +*/ +void idWeapon::BeginAttack2( void ) { + if ( status != WP_OUTOFAMMO ) { + lastAttack = gameLocal.time; + } + + if ( !isLinked ) { + return; + } + + if ( !WEAPON_ATTACK2 ) { + if ( sndHum ) { + StopSound( SND_CHANNEL_BODY, false ); + } + } + WEAPON_ATTACK2 = true; +} + /* ================ idWeapon::EndAttack @@ -1443,6 +1492,24 @@ void idWeapon::EndAttack( void ) { } } +/* +================ +Zeroth +idWeapon::EndAttack2 +================ +*/ +void idWeapon::EndAttack2( void ) { + if ( !WEAPON_ATTACK2.IsLinked() ) { + return; + } + if ( WEAPON_ATTACK2 ) { + WEAPON_ATTACK2 = false; + if ( sndHum ) { + StartSoundShader( sndHum, SND_CHANNEL_BODY, 0, false, NULL ); + } + } +} + /* ================ idWeapon::isReady @@ -1502,12 +1569,15 @@ idWeapon::WeaponStolen */ void idWeapon::WeaponStolen( void ) { assert( !gameLocal.isClient ); - if ( projectileEnt ) { + if ( projectileEnts.Num() ) { if ( isLinked ) { SetState( "WeaponStolen", 0 ); thread->Execute(); } - projectileEnt = NULL; +// HEXEN : Zeroth + for (int i=0; iExecute(); WEAPON_ATTACK = false; + WEAPON_ATTACK2 = false; // HEXEN : Zeroth WEAPON_RELOAD = false; WEAPON_NETRELOAD = false; WEAPON_NETENDRELOAD = false; @@ -2041,6 +2112,21 @@ void idWeapon::EnterCinematic( void ) { LowerWeapon(); } +// HEXEN : Zeroth +void idWeapon::eoc_UnstickAllButtons( void ) { + if ( isLinked ) { + WEAPON_ATTACK = false; + WEAPON_ATTACK2 = false; + WEAPON_RELOAD = false; + WEAPON_NETRELOAD = false; + WEAPON_NETENDRELOAD = false; + WEAPON_NETFIRING = false; + WEAPON_RAISEWEAPON = false; + WEAPON_LOWERWEAPON = false; + } +} + + /* ================ idWeapon::ExitCinematic @@ -2203,20 +2289,63 @@ const char *idWeapon::GetAmmoPickupNameForNum( ammo_t ammonum ) { idWeapon::AmmoAvailable ================ */ -int idWeapon::AmmoAvailable( void ) const { +float idWeapon::AmmoAvailable( void ) const { if ( owner ) { + owner->inventory.DoCombinedMana(); // HEXEN : Zeroth return owner->inventory.HasAmmo( ammoType, ammoRequired ); } else { return 0; } } +/* +================ +Zeroth +idWeapon::BlueManaAvailable +================ +*/ +//int idWeapon::BlueManaAvailable( void ) const { +// if ( owner ) { +// return owner->inventory.HasAmmo( GetAmmoNumForName("ammo_bluemana"), 1 ); +// } else { +// return 0; +// } +//} + +/* +================ +Zeroth +idWeapon::GreenManaAvailable +================ +*/ +//int idWeapon::GreenManaAvailable( void ) const { +// if ( owner ) { +// return owner->inventory.HasAmmo( GetAmmoNumForName("ammo_greenmana"), 1 ); +/// } else { +// return 0; +// } +//} + +/* +================ +Zeroth +idWeapon::CombinedManaAvailable +================ +*/ +//int idWeapon::CombinedManaAvailable( void ) const { +// if ( owner ) { +// return owner->inventory.HasAmmo( GetAmmoNumForName("ammo_combinedmana"), 1 ); +// } else { +// return 0; +// } +//} + /* ================ idWeapon::AmmoInClip ================ */ -int idWeapon::AmmoInClip( void ) const { +float idWeapon::AmmoInClip( void ) const { return ammoClip; } @@ -2243,7 +2372,7 @@ ammo_t idWeapon::GetAmmoType( void ) const { idWeapon::ClipSize ================ */ -int idWeapon::ClipSize( void ) const { +float idWeapon::ClipSize( void ) const { return clipSize; } @@ -2252,7 +2381,7 @@ int idWeapon::ClipSize( void ) const { idWeapon::LowAmmo ================ */ -int idWeapon::LowAmmo() const { +float idWeapon::LowAmmo() const { return lowAmmo; } @@ -2261,7 +2390,7 @@ int idWeapon::LowAmmo() const { idWeapon::AmmoRequired ================ */ -int idWeapon::AmmoRequired( void ) const { +float idWeapon::AmmoRequired( void ) const { return ammoRequired; } @@ -2478,7 +2607,7 @@ void idWeapon::Event_WeaponLowering( void ) { idWeapon::Event_UseAmmo =============== */ -void idWeapon::Event_UseAmmo( int amount ) { +void idWeapon::Event_UseAmmo( float amount ) { if ( gameLocal.isClient ) { return; } @@ -2497,8 +2626,8 @@ void idWeapon::Event_UseAmmo( int amount ) { idWeapon::Event_AddToClip =============== */ -void idWeapon::Event_AddToClip( int amount ) { - int ammoAvail; +void idWeapon::Event_AddToClip( float amount ) { + float ammoAvail; if ( gameLocal.isClient ) { return; @@ -2521,7 +2650,7 @@ idWeapon::Event_AmmoInClip =============== */ void idWeapon::Event_AmmoInClip( void ) { - int ammo = AmmoInClip(); + float ammo = AmmoInClip(); idThread::ReturnFloat( ammo ); } @@ -2531,7 +2660,7 @@ idWeapon::Event_AmmoAvailable =============== */ void idWeapon::Event_AmmoAvailable( void ) { - int ammoAvail = owner->inventory.HasAmmo( ammoType, ammoRequired ); + float ammoAvail = owner->inventory.HasAmmo( ammoType, ammoRequired ); idThread::ReturnFloat( ammoAvail ); } @@ -2541,7 +2670,7 @@ idWeapon::Event_TotalAmmoCount =============== */ void idWeapon::Event_TotalAmmoCount( void ) { - int ammoAvail = owner->inventory.HasAmmo( ammoType, 1 ); + float ammoAvail = owner->inventory.HasAmmo( ammoType, 1 ); idThread::ReturnFloat( ammoAvail ); } @@ -2791,14 +2920,17 @@ idWeapon::Event_CreateProjectile */ void idWeapon::Event_CreateProjectile( void ) { if ( !gameLocal.isClient ) { - projectileEnt = NULL; - gameLocal.SpawnEntityDef( projectileDict, &projectileEnt, false ); - if ( projectileEnt ) { - projectileEnt->SetOrigin( GetPhysics()->GetOrigin() ); - projectileEnt->Bind( owner, false ); - projectileEnt->Hide(); +// HEXEN : Zeroth + idEntity * newProjectile = new idEntity(); + projectileEnts.Append( newProjectile ); + int pnum=projectileEnts.Num()-1; + gameLocal.SpawnEntityDef( projectileDict, &(projectileEnts[pnum]), false ); + if ( projectileEnts[pnum] ) { + projectileEnts[pnum]->SetOrigin( GetPhysics()->GetOrigin() ); + projectileEnts[pnum]->Bind( owner, false ); + projectileEnts[pnum]->Hide(); } - idThread::ReturnEntity( projectileEnt ); + idThread::ReturnEntity( projectileEnts[pnum] ); } else { idThread::ReturnEntity( NULL ); } @@ -2812,7 +2944,7 @@ idWeapon::Event_LaunchProjectiles void idWeapon::Event_LaunchProjectiles( int num_projectiles, float spread, float fuseOffset, float launchPower, float dmgPower ) { idProjectile *proj; idEntity *ent; - int i; + int i,c; idVec3 dir; float ang; float spin; @@ -2836,18 +2968,18 @@ void idWeapon::Event_LaunchProjectiles( int num_projectiles, float spread, float if ( !gameLocal.isClient ) { // check if we're out of ammo or the clip is empty - int ammoAvail = owner->inventory.HasAmmo( ammoType, ammoRequired ); + float ammoAvail = owner->inventory.HasAmmo( ammoType, ammoRequired ); if ( !ammoAvail || ( ( clipSize != 0 ) && ( ammoClip <= 0 ) ) ) { return; } - // if this is a power ammo weapon ( currently only the bfg ) then make sure + // if this is a power ammo weapon ( currently only the bfg ) then make sure // we only fire as much power as available in each clip if ( powerAmmo ) { // power comes in as a float from zero to max // if we use this on more than the bfg will need to define the max // in the .def as opposed to just in the script so proper calcs - // can be done here. + // can be done here. dmgPower = ( int )dmgPower + 1; if ( dmgPower > ammoClip ) { dmgPower = ammoClip; @@ -2926,49 +3058,92 @@ void idWeapon::Event_LaunchProjectiles( int num_projectiles, float spread, float dir = playerViewAxis[ 0 ] + playerViewAxis[ 2 ] * ( ang * idMath::Sin( spin ) ) - playerViewAxis[ 1 ] * ( ang * idMath::Cos( spin ) ); dir.Normalize(); - if ( projectileEnt ) { - ent = projectileEnt; - ent->Show(); - ent->Unbind(); - projectileEnt = NULL; +// HEXEN : Zeroth +//Z.TODO: this could be simplified, but for now it works. + c = projectileEnts.Num(); + if ( c ) { + for (; c>0; c--) { + ent = projectileEnts[c-1]; + ent->Show(); + ent->Unbind(); + + if ( !ent || !ent->IsType( idProjectile::Type ) ) { + const char *projectileName = weaponDef->dict.GetString( "def_projectile" ); + gameLocal.Error( "'%s' is not an idProjectile", projectileName ); + } + + if ( projectileDict.GetBool( "net_instanthit" ) ) { + // don't synchronize this on top of the already predicted effect + ent->fl.networkSync = false; + } + + proj = static_cast(ent); + proj->Create( owner, muzzleOrigin, dir ); + + projBounds = proj->GetPhysics()->GetBounds().Rotate( proj->GetPhysics()->GetAxis() ); + + // make sure the projectile starts inside the bounding box of the owner + if ( i == 0 ) { + muzzle_pos = muzzleOrigin + playerViewAxis[ 0 ] * 2.0f; + + // DG: sometimes the assertion in idBounds::operator-(const idBounds&) triggers + // (would get bounding box with negative volume) + // => check that before doing ownerBounds - projBounds (equivalent to the check in the assertion) + idVec3 obDiff = ownerBounds[1] - ownerBounds[0]; + idVec3 pbDiff = projBounds[1] - projBounds[0]; + bool boundsSubLegal = obDiff.x > pbDiff.x && obDiff.y > pbDiff.y && obDiff.z > pbDiff.z; + if ( boundsSubLegal && ( ownerBounds - projBounds ).RayIntersection( muzzle_pos, playerViewAxis[0], distance ) ) { + start = muzzle_pos + distance * playerViewAxis[0]; + } else { + start = ownerBounds.GetCenter(); + } + gameLocal.clip.Translation( tr, start, muzzle_pos, proj->GetPhysics()->GetClipModel(), proj->GetPhysics()->GetClipModel()->GetAxis(), MASK_SHOT_RENDERMODEL, owner ); + muzzle_pos = tr.endpos; + } + + proj->Launch( muzzle_pos, dir, pushVelocity, fuseOffset, launchPower, dmgPower ); + + projectileEnts.RemoveIndex(c-1); + } } else { gameLocal.SpawnEntityDef( projectileDict, &ent, false ); - } - if ( !ent || !ent->IsType( idProjectile::Type ) ) { - const char *projectileName = weaponDef->dict.GetString( "def_projectile" ); - gameLocal.Error( "'%s' is not an idProjectile", projectileName ); - } + if ( !ent || !ent->IsType( idProjectile::Type ) ) { + const char *projectileName = weaponDef->dict.GetString( "def_projectile" ); + gameLocal.Error( "'%s' is not an idProjectile", projectileName ); + } - if ( projectileDict.GetBool( "net_instanthit" ) ) { - // don't synchronize this on top of the already predicted effect - ent->fl.networkSync = false; - } + if ( projectileDict.GetBool( "net_instanthit" ) ) { + // don't synchronize this on top of the already predicted effect + ent->fl.networkSync = false; + } - proj = static_cast(ent); - proj->Create( owner, muzzleOrigin, dir ); - - projBounds = proj->GetPhysics()->GetBounds().Rotate( proj->GetPhysics()->GetAxis() ); - - // make sure the projectile starts inside the bounding box of the owner - if ( i == 0 ) { - muzzle_pos = muzzleOrigin + playerViewAxis[ 0 ] * 2.0f; - // DG: sometimes the assertion in idBounds::operator-(const idBounds&) triggers - // (would get bounding box with negative volume) - // => check that before doing ownerBounds - projBounds (equivalent to the check in the assertion) - idVec3 obDiff = ownerBounds[1] - ownerBounds[0]; - idVec3 pbDiff = projBounds[1] - projBounds[0]; - bool boundsSubLegal = obDiff.x > pbDiff.x && obDiff.y > pbDiff.y && obDiff.z > pbDiff.z; - if ( boundsSubLegal && ( ownerBounds - projBounds ).RayIntersection( muzzle_pos, playerViewAxis[0], distance ) ) { - start = muzzle_pos + distance * playerViewAxis[0]; - } else { - start = ownerBounds.GetCenter(); + proj = static_cast(ent); + proj->Create( owner, muzzleOrigin, dir ); + + projBounds = proj->GetPhysics()->GetBounds().Rotate( proj->GetPhysics()->GetAxis() ); + + // make sure the projectile starts inside the bounding box of the owner + if ( i == 0 ) { + muzzle_pos = muzzleOrigin + playerViewAxis[ 0 ] * 2.0f; + + // DG: sometimes the assertion in idBounds::operator-(const idBounds&) triggers + // (would get bounding box with negative volume) + // => check that before doing ownerBounds - projBounds (equivalent to the check in the assertion) + idVec3 obDiff = ownerBounds[1] - ownerBounds[0]; + idVec3 pbDiff = projBounds[1] - projBounds[0]; + bool boundsSubLegal = obDiff.x > pbDiff.x && obDiff.y > pbDiff.y && obDiff.z > pbDiff.z; + if ( boundsSubLegal && ( ownerBounds - projBounds ).RayIntersection( muzzle_pos, playerViewAxis[0], distance ) ) { + start = muzzle_pos + distance * playerViewAxis[0]; + } else { + start = ownerBounds.GetCenter(); + } + gameLocal.clip.Translation( tr, start, muzzle_pos, proj->GetPhysics()->GetClipModel(), proj->GetPhysics()->GetClipModel()->GetAxis(), MASK_SHOT_RENDERMODEL, owner ); + muzzle_pos = tr.endpos; } - gameLocal.clip.Translation( tr, start, muzzle_pos, proj->GetPhysics()->GetClipModel(), proj->GetPhysics()->GetClipModel()->GetAxis(), MASK_SHOT_RENDERMODEL, owner ); - muzzle_pos = tr.endpos; - } - proj->Launch( muzzle_pos, dir, pushVelocity, fuseOffset, launchPower, dmgPower ); + proj->Launch( muzzle_pos, dir, pushVelocity, fuseOffset, launchPower, dmgPower ); + } } // toss the brass @@ -3045,7 +3220,7 @@ void idWeapon::Event_Melee( void ) { idVec3 kickDir, globalKickDir; meleeDef->dict.GetVector( "kickDir", "0 0 0", kickDir ); globalKickDir = muzzleAxis * kickDir; - ent->Damage( owner, owner, globalKickDir, meleeDefName, owner->PowerUpModifier( MELEE_DAMAGE ), tr.c.id ); + ent->Damage( owner, owner, globalKickDir, meleeDefName, owner->PowerUpModifier( MELEE_DAMAGE ), tr.c.id, tr.c.point ); hit = true; } @@ -3069,7 +3244,7 @@ void idWeapon::Event_Melee( void ) { // start impact sound based on material type hitSound = meleeDef->dict.GetString( va( "snd_%s", materialType ) ); if ( *hitSound == '\0' ) { - hitSound = meleeDef->dict.GetString( "snd_metal" ); + hitSound = meleeDef->dict.GetString( "snd_hit" ); } if ( gameLocal.time > nextStrikeFx ) { diff --git a/game/Weapon.h b/game/Weapon.h index 4f8b3744..31903726 100644 --- a/game/Weapon.h +++ b/game/Weapon.h @@ -82,7 +82,7 @@ class idWeapon : public idAnimatedEntity { // Weapon definition management void Clear( void ); - void GetWeaponDef( const char *objectname, int ammoinclip ); + void GetWeaponDef( const char *objectname, float ammoinclip ); bool IsLinked( void ); bool IsWorldModelReady( void ); @@ -109,6 +109,8 @@ class idWeapon : public idAnimatedEntity { void OwnerDied( void ); void BeginAttack( void ); void EndAttack( void ); + void BeginAttack2( void ); // HEXEN : Zeroth + void EndAttack2( void ); // HEXEN : Zeroth bool IsReady( void ) const; bool IsReloading( void ) const; bool IsHolstered( void ) const; @@ -123,6 +125,7 @@ class idWeapon : public idAnimatedEntity { void SetState( const char *statename, int blendFrames ); void UpdateScript( void ); void EnterCinematic( void ); + void eoc_UnstickAllButtons( void ); // HEXEN : Zeroth void ExitCinematic( void ); void NetCatchup( void ); @@ -138,12 +141,12 @@ class idWeapon : public idAnimatedEntity { static const char *GetAmmoNameForNum( ammo_t ammonum ); static const char *GetAmmoPickupNameForNum( ammo_t ammonum ); ammo_t GetAmmoType( void ) const; - int AmmoAvailable( void ) const; - int AmmoInClip( void ) const; + float AmmoAvailable( void ) const; + float AmmoInClip( void ) const; void ResetAmmoClip( void ); - int ClipSize( void ) const; - int LowAmmo( void ) const; - int AmmoRequired( void ) const; + float ClipSize( void ) const; + float LowAmmo( void ) const; + float AmmoRequired( void ) const; virtual void WriteToSnapshot( idBitMsgDelta &msg ) const; virtual void ReadFromSnapshot( const idBitMsgDelta &msg ); @@ -161,6 +164,7 @@ class idWeapon : public idAnimatedEntity { private: // script control idScriptBool WEAPON_ATTACK; + idScriptBool WEAPON_ATTACK2; // HEXEN : Zeroth idScriptBool WEAPON_RELOAD; idScriptBool WEAPON_NETRELOAD; idScriptBool WEAPON_NETENDRELOAD; @@ -176,7 +180,7 @@ class idWeapon : public idAnimatedEntity { bool isLinked; // precreated projectile - idEntity *projectileEnt; + idList projectileEnts; // HEXEN : Zeroth. turned into an idList. idPlayer * owner; idEntityPtr worldModel; @@ -250,10 +254,10 @@ class idWeapon : public idAnimatedEntity { // ammo management ammo_t ammoType; - int ammoRequired; // amount of ammo to use each shot. 0 means weapon doesn't need ammo. - int clipSize; // 0 means no reload - int ammoClip; - int lowAmmo; // if ammo in clip hits this threshold, snd_ + float ammoRequired; // amount of ammo to use each shot. 0 means weapon doesn't need ammo. + float clipSize; // 0 means no reload + float ammoClip; + float lowAmmo; // if ammo in clip hits this threshold, snd_ bool powerAmmo; // true if the clip reduction is a factor of the power setting when // a projectile is launched // mp client @@ -326,8 +330,8 @@ class idWeapon : public idAnimatedEntity { void Event_WeaponHolstered( void ); void Event_WeaponRising( void ); void Event_WeaponLowering( void ); - void Event_UseAmmo( int amount ); - void Event_AddToClip( int amount ); + void Event_UseAmmo( float amount ); + void Event_AddToClip( float amount ); void Event_AmmoInClip( void ); void Event_AmmoAvailable( void ); void Event_TotalAmmoCount( void ); diff --git a/game/ai/AI.cpp b/game/ai/AI.cpp index 2ac99486..c87b8a08 100644 --- a/game/ai/AI.cpp +++ b/game/ai/AI.cpp @@ -384,6 +384,9 @@ idAI::idAI() { eyeFocusRate = 0.0f; headFocusRate = 0.0f; focusAlignTime = 0; + + // HEXEN : Zeroth + ignoreObstacles = false; } /* @@ -531,6 +534,9 @@ void idAI::Save( idSaveGame *savefile ) const { savefile->WriteJoint( flyTiltJoint ); savefile->WriteBool( GetPhysics() == static_cast(&physicsObj) ); + + // HEXEN : Zeroth + savefile->WriteBool( ignoreObstacles ); } /* @@ -698,6 +704,14 @@ void idAI::Restore( idRestoreGame *savefile ) { if ( restorePhysics ) { RestorePhysics( &physicsObj ); } + + savefile->ReadBool( ignoreObstacles ); +} + +// Hexen : Zeroth +void idAI::Kill( void ) { + health = 0; + DirectDamage( "damage_generic", this ); } /* @@ -1049,6 +1063,11 @@ idAI::Think ===================== */ void idAI::Think( void ) { + // HEXEN : Zeroth + if ( !AI_DEAD && gameLocal.time < onFire || onFire == -1 ) { + EmitFlames(); + } + // if we are completely closed off from the player, don't do anything at all if ( CheckDormant() ) { return; @@ -1687,6 +1706,10 @@ bool idAI::MoveToEnemy( void ) { move.startTime = gameLocal.time; } + if ( spawnArgs.GetBool( "wall_walker" ) ) { + move.moveDest.z = lastVisibleEnemyPos.z; + } + move.moveDest = pos; move.goalEntity = enemyEnt; move.speed = fly_speed; @@ -1926,6 +1949,85 @@ bool idAI::MoveToPosition( const idVec3 &pos ) { return true; } +// HEXEN : Zeroth +void idAI::MoveForward( void ) { + idVec3 pos; + //idVec3 org; + //int areaNum; + //aasPath_t path; + idVec3 dir; + + //ideal_yaw = current_yaw; + //idVec3 dir=angles.ToForward() * physicsObj.GetGravityAxis(); + idAngles ang; + ang.yaw = ideal_yaw; + ang.ToVectors( &dir ); + dir.Normalize(); + pos = physicsObj.GetOrigin() + (dir * 64); + + //idVec3 dir; + //idVec3 local_dir; + //float lengthSqr; + +// dir = pos - physicsObj.GetOrigin(); + +// physicsObj.GetGravityAxis().ProjectVector( dir, local_dir ); +// local_dir.z = 0.0f; +// lengthSqr = local_dir.LengthSqr(); +// if ( lengthSqr > Square( 2.0f ) || ( lengthSqr > Square( 0.1f ) && enemy.GetEntity() == NULL ) ) { +// ideal_yaw = idMath::AngleNormalize180( local_dir.ToYaw() ); +// } + + DirectMoveToPosition(pos); + TurnToward( pos ); +} + +/* +===================== +idAI::Event_GetJumpVelocity +===================== +*/ +idVec3 idAI::GetJumpVelocity( const idVec3 &pos, float speed, float max_height ) { + idVec3 start; + idVec3 end; + idVec3 dir; + float dist; + bool result; + idEntity *enemyEnt = enemy.GetEntity(); + + if ( !enemyEnt ) { + return vec3_zero; + } + + if ( speed <= 0.0f ) { + gameLocal.Error( "Invalid speed. speed must be > 0." ); + } + + start = physicsObj.GetOrigin(); + end = pos; + dir = end - start; + dist = dir.Normalize(); + if ( dist > 16.0f ) { + dist -= 16.0f; + end -= dir * 16.0f; + } + + result = PredictTrajectory( start, end, speed, physicsObj.GetGravity(), physicsObj.GetClipModel(), MASK_MONSTERSOLID, max_height, this, enemyEnt, ai_debugMove.GetBool() ? 4000 : 0, dir ); + if ( result ) { + return dir * speed ; + } else { + return vec3_zero ; + } +} + +// HEXEN : Zeroth +idVec3 idAI::FacingNormal( void ) { + idVec3 dir=viewAxis[ 0 ] * physicsObj.GetGravityAxis(); + dir.Normalize(); + return dir; +} + + /* ===================== idAI::MoveToCover @@ -2147,6 +2249,11 @@ bool idAI::NewWanderDir( const idVec3 &dest ) { } } + // HEXEN : Zeroth - always move direcly toward the player when ignoring obstacles. + if ( ignoreObstacles ) { + return true; + } + // try other directions if ( ( gameLocal.random.RandomInt() & 1 ) || idMath::Fabs( deltay ) > idMath::Fabs( deltax ) ) { tdir = d[ 1 ]; @@ -2682,10 +2789,16 @@ void idAI::AnimMove( void ) { if ( gameLocal.time < move.startTime + move.duration ) { goalPos = move.moveDest - move.moveDir * MS2SEC( move.startTime + move.duration - gameLocal.time ); delta = goalPos - oldorigin; - delta.z = 0.0f; + if ( !spawnArgs.GetBool( "wall_walker" ) ) { + gameLocal.Printf("lolz\n"); + delta.z = 0.0f; + } } else { delta = move.moveDest - oldorigin; - delta.z = 0.0f; + if ( !spawnArgs.GetBool( "wall_walker" ) ) { + gameLocal.Printf("lolz mang\n"); + delta.z = 0.0f; + } StopMove( MOVE_STATUS_DONE ); } } else if ( allowMove ) { @@ -2711,7 +2824,8 @@ void idAI::AnimMove( void ) { gameRenderWorld->DebugLine( colorCyan, oldorigin, physicsObj.GetOrigin(), 5000 ); } - if ( !af_push_moveables && attack.Length() && TestMelee() ) { + //moveResult = physicsObj.GetMoveResult(); + if ( !af_push_moveables && attack.Length() && TestMelee( idVec3() ) ) { DirectDamage( attack, enemy.GetEntity() ); } else { idEntity *blockEnt = physicsObj.GetSlideMoveEntity(); @@ -2837,7 +2951,8 @@ void idAI::SlideMove( void ) { gameRenderWorld->DebugLine( colorCyan, oldorigin, physicsObj.GetOrigin(), 5000 ); } - if ( !af_push_moveables && attack.Length() && TestMelee() ) { + //moveResult = physicsObj.GetMoveResult(); + if ( !af_push_moveables && attack.Length() && TestMelee( idVec3() ) ) { DirectDamage( attack, enemy.GetEntity() ); } else { idEntity *blockEnt = physicsObj.GetSlideMoveEntity(); @@ -3085,7 +3200,7 @@ void idAI::FlyMove( void ) { RunPhysics(); monsterMoveResult_t moveResult = physicsObj.GetMoveResult(); - if ( !af_push_moveables && attack.Length() && TestMelee() ) { + if ( !af_push_moveables && attack.Length() && TestMelee( idVec3() ) ) { DirectDamage( attack, enemy.GetEntity() ); } else { idEntity *blockEnt = physicsObj.GetSlideMoveEntity(); @@ -3139,7 +3254,7 @@ void idAI::StaticMove( void ) { AI_ONGROUND = false; - if ( !af_push_moveables && attack.Length() && TestMelee() ) { + if ( !af_push_moveables && attack.Length() && TestMelee( idVec3() ) ) { DirectDamage( attack, enemyEnt ); } @@ -3398,6 +3513,7 @@ void idAI::Killed( idEntity *inflictor, idEntity *attacker, int damage, const id if ( ( attacker && attacker->IsType( idPlayer::Type ) ) && ( inflictor && !inflictor->IsType( idSoulCubeMissile::Type ) ) ) { static_cast< idPlayer* >( attacker )->AddAIKill(); } + gameLocal.SetPersistentRemove(name.c_str()); } /*********************************************************************** @@ -4256,7 +4372,7 @@ void idAI::DirectDamage( const char *meleeDefName, idEntity *ent ) { idVec3 globalKickDir; globalKickDir = ( viewAxis * physicsObj.GetGravityAxis() ) * kickDir; - ent->Damage( this, this, globalKickDir, meleeDefName, 1.0f, INVALID_JOINT ); + ent->Damage( this, this, globalKickDir, meleeDefName, 1.0f, INVALID_JOINT, idVec3(0,0,0) ); // end the attack if we're a multiframe attack EndAttack(); @@ -4267,7 +4383,7 @@ void idAI::DirectDamage( const char *meleeDefName, idEntity *ent ) { idAI::TestMelee ===================== */ -bool idAI::TestMelee( void ) const { +bool idAI::TestMelee( const idVec3 &iPoint ) const { trace_t trace; idActor *enemyEnt = enemy.GetEntity(); @@ -4306,6 +4422,7 @@ bool idAI::TestMelee( void ) const { gameLocal.clip.TracePoint( trace, start, end, MASK_SHOT_BOUNDINGBOX, this ); if ( ( trace.fraction == 1.0f ) || ( gameLocal.GetTraceEntity( trace ) == enemyEnt ) ) { +// iPoint = trace.c.point; return true; } @@ -4366,7 +4483,8 @@ bool idAI::AttackMelee( const char *meleeDefName ) { } // make sure the trace can actually hit the enemy - if ( forceMiss || !TestMelee() ) { + idVec3 iPoint; + if ( forceMiss || !TestMelee( iPoint ) ) { // missed p = meleeDef->GetString( "snd_miss" ); if ( p && *p ) { @@ -4391,7 +4509,7 @@ bool idAI::AttackMelee( const char *meleeDefName ) { idVec3 globalKickDir; globalKickDir = ( viewAxis * physicsObj.GetGravityAxis() ) * kickDir; - enemyEnt->Damage( this, this, globalKickDir, meleeDefName, 1.0f, INVALID_JOINT ); + enemyEnt->Damage( this, this, globalKickDir, meleeDefName, 1.0f, INVALID_JOINT, iPoint ); lastAttackTime = gameLocal.time; @@ -4432,7 +4550,7 @@ void idAI::PushWithAF( void ) { vel = ent->GetPhysics()->GetAbsBounds().GetCenter() - touchList[ i ].touchedByBody->GetWorldOrigin(); vel.Normalize(); if ( attack.Length() && ent->IsType( idActor::Type ) ) { - ent->Damage( this, this, vel, attack, 1.0f, INVALID_JOINT ); + ent->Damage( this, this, vel, attack, 1.0f, INVALID_JOINT, idVec3(0,0,0) ); } else { ent->GetPhysics()->SetLinearVelocity( 100.0f * vel, touchList[ i ].touchedClipModel->GetId() ); } diff --git a/game/ai/AI.h b/game/ai/AI.h index b038ba41..4a859f3a 100644 --- a/game/ai/AI.h +++ b/game/ai/AI.h @@ -252,6 +252,7 @@ class idAI : public idActor { void Save( idSaveGame *savefile ) const; void Restore( idRestoreGame *savefile ); + void Kill( void); void Spawn( void ); void HeardSound( idEntity *ent, const char *action ); @@ -514,7 +515,7 @@ class idAI : public idActor { idProjectile *LaunchProjectile( const char *jointname, idEntity *target, bool clampToAttackCone ); virtual void DamageFeedback( idEntity *victim, idEntity *inflictor, int &damage ); void DirectDamage( const char *meleeDefName, idEntity *ent ); - bool TestMelee( void ) const; + bool TestMelee( const idVec3 &iPoint ) const; bool AttackMelee( const char *meleeDefName ); void BeginAttack( const char *name ); void EndAttack( void ); @@ -664,6 +665,38 @@ class idAI : public idActor { void Event_CanReachEntity( idEntity *ent ); void Event_CanReachEnemy( void ); void Event_GetReachableEntityPosition( idEntity *ent ); + +// HEXEN : Zeroth +protected: + void Event_IsEnemy( const idEntity *test ); + void Event_NumGroundContacts( void ); + void Event_VecForward( void ); + void Event_VecFacing( void ); + void Event_SetFacingDir( idVec3 dir); + void Event_IgnoreObstacles( void ); + void Event_GetRealEnemyPos( void ); + void Event_DirectMoveToPosition( const idVec3 &pos ); + void Event_MoveForward( void ); + +// HEXEN : Zeroth +public: + bool ignoreObstacles; + +// HEXEN : Zeroth +public: + idVec3 FacingNormal( void ); + idVec3 PredictEnemyPos( float time ); + bool CanHitEnemy( void ); + void BecomeNonSolid( void ); + bool CanBecomeSolid( void ); + void BecomeSolid( void ); + +// HEXEN : Zeroth +protected: +// bool AI_DirectMoveToPosition( const idVec3 &pos ); +// bool AI_MoveForward( void ); + void MoveForward( void ); + idVec3 GetJumpVelocity( const idVec3 &pos, float speed, float max_height ); }; class idCombatNode : public idEntity { diff --git a/game/ai/AI_Golem.cpp b/game/ai/AI_Golem.cpp new file mode 100644 index 00000000..21f3ea0f --- /dev/null +++ b/game/ai/AI_Golem.cpp @@ -0,0 +1,309 @@ + + +#include "Entity.h" +#include "Game_local.h" +#include "Moveable.h" +#include "AI.h" +#include "AI_Golem.h" +#include "WorldSpawn.h" + +const idVec3 GOLEM_ROCKS_NO_GRAVITY = idVec3(0,0,0); +const int GOLEM_ROCKS_MAX = 13; +const int GOLEM_ROCKS_MIN = 8; +const int GOLEM_ROCKS_MIN_CHECK_DELAY = 2000; +const int GOLEM_ROCKS_MAX_DIST = 300; +const int GOLEM_ROCKS_ATTRACT_SPEED = 240; +const int GOLEM_RICKS_MAX_MOVE_TIME_BEFORE_BIND = 5000; +const int GOLEM_ROCKS_BIND_DISTANCE = 16; + +const idEventDef AI_Golem_SawEnemy( "SawEnemy" ); + +CLASS_DECLARATION( idAI, idAI_Golem ) + EVENT( AI_Golem_SawEnemy, idAI_Golem::Event_SawEnemy ) +END_CLASS + +void idAI_Golem::Spawn() { + alive = false; + nextFindRocks = 0; + totRocks = 0; + nextBoneI = 0; + nextBone(); // start er off + golemType = spawnArgs.GetString("golemType"); + onEnt = 0; + onFire = -1; +} + +void idAI_Golem::Save( idSaveGame *savefile ) const { + savefile->WriteFloat( totRocks ); + savefile->WriteFloat( nextFindRocks ); + savefile->WriteInt( nextBoneI ); + savefile->WriteString( curBone ); + savefile->WriteString( golemType ); + savefile->WriteBool( alive ); + savefile->WriteInt( rocks.Num() ); + for ( int i=0; iWriteObject( rocks[i] ); + } +} + +void idAI_Golem::Restore( idRestoreGame *savefile ) { + savefile->ReadFloat( totRocks ); + savefile->ReadFloat( nextFindRocks ); + savefile->ReadInt( nextBoneI ); + savefile->ReadString( curBone ); + savefile->ReadString( golemType ); + savefile->ReadBool( alive ); + int i; + savefile->ReadInt( i ); + idClass *obj; + for ( ; i>0; i-- ) { + savefile->ReadObject( obj ); + rocks.Append( static_cast< idMoveable* >( obj ) ); + } +} + +void idAI_Golem::findNewRocks() { + idMoveable *rock = FindGolemRock(); + if ( rock ) { + addRock( rock ); + } +} + + +void idAI_Golem::addRock( idMoveable *newRock ) { + newRock->BecomeNonSolid(); + newRock->spawnArgs.SetBool("golem_owned", 1); + newRock->spawnArgs.SetFloat("golem_bindTime", gameLocal.time + GOLEM_RICKS_MAX_MOVE_TIME_BEFORE_BIND ); + newRock->spawnArgs.SetBool("golem_bound", 0); + newRock->spawnArgs.Set("old_bounce", newRock->spawnArgs.GetString("snd_bounce") ); + newRock->spawnArgs.Set("snd_bounce", ""); // no bounce sounds while on the golem, gets REAL annoying. + newRock->spawnArgs.Set("bone", curBone); + newRock->health = 1000; + newRock->GetPhysics()->DisableClip(); + rocks.Append(newRock); + + totRocks++; + nextBone(); +} + +void idAI_Golem::Think() { + idAI::Think(); + + if ( alive ) { + evalRocks(); + } +} + +void idAI_Golem::evalRocks() { + int i; + float length; + idVec3 dist; + idStr bone; + + if ( gameLocal.time > nextFindRocks ) { + nextFindRocks = gameLocal.time + 2000; // find new rocks every 2 seconds + findNewRocks(); + } + + for (i=0; i < rocks.Num(); i++) { + if ( !rocks[i] ) { + continue; + } + + bone = rocks[i]->spawnArgs.GetString("bone"); + + if ( !rocks[i]->spawnArgs.GetBool("golem_bound") ) { + dist = GetJointPos(animator.GetJointHandle(bone)) - rocks[i]->GetPhysics()->GetOrigin(); + length = dist.Length(); + + // if rocks are close enough or max time movement has passed, bind them. + if ( length < GOLEM_ROCKS_BIND_DISTANCE || gameLocal.time > rocks[i]->spawnArgs.GetFloat("golem_bindTime") ) { + rocks[i]->GetPhysics()->SetGravity( GOLEM_ROCKS_NO_GRAVITY ); + //rocks[i]->SetOrigin( GetLocalCoordinates( GetJointPos(animator.GetJointHandle(bone)) ) ); + rocks[i]->SetOrigin( GetJointPos(animator.GetJointHandle(bone)) ); + rocks[i]->spawnArgs.Set("golem_bound", "1"); + rocks[i]->SetAngles( idAngles(gameLocal.random.RandomInt() % 200 - 100, gameLocal.random.RandomInt() % 200 - 100, gameLocal.random.RandomInt() % 200 - 100 ) ); + rocks[i]->BindToJoint(this, bone, 1); + rocks[i]->GetPhysics()->EnableClip(); + // else just move them + } else { + dist.Normalize(); + //looks shitty: rocks[i]->GetPhysics()->SetAngularVelocity( idVec3(gameLocal.random.RandomInt() % 200 - 100, gameLocal.random.RandomInt() % 200 - 100, gameLocal.random.RandomInt() % 200 - 100 ) ); + rocks[i]->GetPhysics()->SetLinearVelocity( dist * GOLEM_ROCKS_ATTRACT_SPEED ); + } + + continue; + } + + // if a rock takes damage, the golem takes damage + if ( rocks[i]->health < 1000 ) { + Damage( NULL, NULL, idVec3(0,0,0), "damage_golem1", 1000 - rocks[i]->health, 0 ); // Z.TODO: this needs to be the damage def of the weapon used...somehow... + rocks[i]->health = 1000; + } + } +} + +void idAI_Golem::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location, idVec3 &iPoint ) { + if ( !fl.takedamage ) { + return; + } + + if ( !inflictor ) { + inflictor = gameLocal.world; + } + if ( !attacker ) { + attacker = gameLocal.world; + } + + const idDict *damageDef = gameLocal.FindEntityDefDict( damageDefName ); + if ( !damageDef ) { + gameLocal.Error( "Unknown damageDef '%s'", damageDefName ); + } + + int damage = damageDef->GetInt( "damage" ) * damageScale; + damage = GetDamageForLocation( damage, location ); + + // inform the attacker that they hit someone + attacker->DamageFeedback( this, inflictor, damage ); + if ( damage > 0 ) { + health -= damage; + if ( health <= 0 ) { + if ( health < -999 ) { + health = -999; + } + gameLocal.Printf("BLOWUP!\n"); + BlowUp(); + Killed( inflictor, attacker, damage, dir, location ); + } else { + Pain( inflictor, attacker, damage, dir, location ); + } + } else { + // don't accumulate knockback + if ( af.IsLoaded() ) { + // clear impacts + af.Rest(); + + // physics is turned off by calling af.Rest() + BecomeActive( TH_PHYSICS ); + } + } +} + +void idAI_Golem::BlowUp( void ) { + //spawnArgs.SetVector( "origin", GetPhysics()->GetOrigin() ); + //spawnArgs.Set( "classname", "proj_golemsoul" ); + //gameLocal.SpawnEntityDef( spawnArgs ); + //soul->GetPhysics()->SetOrigin( GetPhysics()->GetOrigin() ); + + // rocks get removed from head to toe. + for (int i=rocks.Num()-1; i >= 0; i--) { + // DG: this code should start at rocks.Num()-1 (last valid index) + // and the check was "!rocks[i]" but the opposite is needed + if ( rocks[i] != NULL ) { + rocks[i]->GetPhysics()->SetGravity(gameLocal.GetGravity()); + rocks[i]->spawnArgs.Set("golem_owned", 0); + rocks[i]->spawnArgs.Set("golem_bound", 0); + rocks[i]->spawnArgs.Set("golem_bindTime", 0); + rocks[i]->spawnArgs.Set("snd_bounce", rocks[i]->spawnArgs.GetString("old_bounce") ); //Z.TODO + rocks[i]->Unbind(); + rocks[i]->BecomeSolid(); + idVec3 dx; + dx.x = gameLocal.random.RandomInt() % 200 - 100; + dx.y = gameLocal.random.RandomInt() % 200 - 100; + dx.z = ( gameLocal.random.RandomInt() % 100 ) * 3; + dx.Normalize(); + rocks[i]->GetPhysics()->SetLinearVelocity( dx * 600 ); + dx.x = gameLocal.random.RandomInt() % 200 - 100; + dx.y = gameLocal.random.RandomInt() % 200 - 100; + dx.z = gameLocal.random.RandomInt() % 200 - 100; + dx.Normalize(); + rocks[i]->GetPhysics()->EnableClip(); + rocks[i]->GetPhysics()->SetAngularVelocity( dx * 100 ); + rocks.RemoveIndex(i); + } + } +} + +void idAI_Golem::nextBone() { + + // one rock per joint, from toe to head. + switch ( nextBoneI ) { + case 0: curBone = "lfoot"; break; + case 1: curBone = "rfoot"; break; + case 2: curBone = "lknee"; break; + case 3: curBone = "rknee"; break; + case 4: curBone = "pelvis"; break; + case 5: curBone = "spineup"; break; + case 6: curBone = "lhand"; break; + case 7: curBone = "rhand"; break; + case 8: curBone = "lloarm"; break; + case 9: curBone = "rloarm"; break; + case 10: curBone = "luparm"; break; + case 11: curBone = "ruparm"; break; + case 12: curBone = "head"; break; + default: curBone = ""; break; + } + + nextBoneI++; +} + +void idAI_Golem::Event_SawEnemy() { + alive=true; +} + +const char * idAI_Golem::GetGolemType( void ) const { + return golemType.c_str(); +} + +// HEXEN : Zeroth +idMoveable *idAI_Golem::FindGolemRock( void ) { + int hash, i; + idMoveable *target=NULL; + + hash = gameLocal.entypeHash.GenerateKey( idMoveable::Type.classname, true ); + + for ( i = gameLocal.entypeHash.First( hash ); i != -1; i = gameLocal.entypeHash.Next( i ) ) { + if ( gameLocal.entities[i] && gameLocal.entities[i]->IsType( idMoveable::Type ) ) { + target = static_cast< idMoveable* >( gameLocal.entities[i] ); + + if ( target->IsHidden() ) { + continue; + } + + if ( target->spawnArgs.GetInt("golem_owned") == 1 ) { + continue; + } +/* smaller golemd haven't been implemented yet + if ( !target->spawnArgs.GetInt("forLargeGolem") ) { + continue; + } +*/ + if ( GetGolemType() != target->spawnArgs.GetString("rockType") ) { + continue; + } + + // trace from golem to rock + trace_t trace; + idEntity *traceEnt = NULL; + gameLocal.clip.TraceBounds( trace, + GetJointPos( animator.GetJointHandle( curBone ) ), + target->GetPhysics()->GetOrigin(), + idBounds( GOLEM_TRACE_MINS, GOLEM_TRACE_MAXS ), + MASK_MONSTERSOLID, + this ); + + if ( trace.fraction < 1.0f ) { + traceEnt = gameLocal.entities[ trace.c.entityNum ]; + } + + // make sure rock isn't through a wall or something. + if ( !traceEnt || traceEnt != target ) { + continue; + } + + return target; + } + } + + return NULL; +} diff --git a/game/ai/AI_Golem.h b/game/ai/AI_Golem.h new file mode 100644 index 00000000..06412e86 --- /dev/null +++ b/game/ai/AI_Golem.h @@ -0,0 +1,44 @@ +#ifndef __AI_GOLEM_ +#define __AI_GOLEM_ + +#include "Game_local.h" +#include "AI.h" + +const idVec3 GOLEM_TRACE_MINS = idVec3(-1,-1,-1); +const idVec3 GOLEM_TRACE_MAXS = idVec3(-1,-1,-1); + +class idAI_Golem : public idAI { +public: + CLASS_PROTOTYPE( idAI_Golem ); + void Spawn(); + void Think(); + void Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location, idVec3 &iPoint ); + void Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ) { idVec3 zero; Damage(inflictor, attacker, dir, damageDefName, damageScale, location, zero); } + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + +public: + const char * GetGolemType( void ) const; + +private: + float totRocks; // this IS the total number of rocks. + float nextFindRocks; // time delay for finding new rocks + int nextBoneI; + idStr curBone; + idStr golemType; + idList rocks; + bool alive; + int onEnt; + +private: + void findNewRocks( void ); // look for new rocks + void addRock( idMoveable *newRock ); + void evalRocks( void ); // call this every frame to do all rock related junk + void nextBone( void ); + void BlowUp( void ); + void Event_SawEnemy( void ); + idMoveable *FindGolemRock( void ); +}; + + +#endif // __AI_GOLEM_ diff --git a/game/ai/AI_Shadowspawn.cpp b/game/ai/AI_Shadowspawn.cpp new file mode 100644 index 00000000..f00ebfeb --- /dev/null +++ b/game/ai/AI_Shadowspawn.cpp @@ -0,0 +1,115 @@ + +#include "Game_local.h" + +const int SHADOWSPAWN_RANGE_ATTACK_RATE = 3; +const int SHADOWSPAWN_DRAIN_RATE = 10; + +/* +CLASS_DECLARATION( idAI, idAI_Shadowspawn ) + +END_CLASS + +void idAI_Shadowspawn::regenerate() { + float drainTime=0; + idVec3 dir; + float giveUp=DelayTime(5); + +// // regenerate 2 health every second +// if ( regenTime > ( gameLocal.time / 1000 ) && getHealth() < getFloat("health") ) { +// setHealth(getHealth() + 1); +// regenTime = delaytime(1); +// } + + //vec.Length(GetOrigin() - enemy.GetEyePos()) < 80 + + if ( !enemy ) { + return; + } + + SetAnimState( ANIMCHANNEL_TORSO, "Torso_TelePull", 4 ); + stayPut=getWorldOrigin(); // shadowspawn should not be moved by projectiles, etc, might put player outside of level + while ( 1 ) { + setWorldOrigin(stayPut); + + if ( vec.Length(stayPut - enemy->GetPhysics()->GetOrigin()()) < 90 ) { + break; + } else if (( gameLocal.time / 1000 ) > giveUp) { + SetAnimState( ANIMCHANNEL_TORSO, "Torso_Idle", 4 ); + return; + } + + enemy->GetPhysics()->SetLinearVelocity( sys.vecNormalize(stayPut - enemy->GetPhysics()->GetOrigin()()) * 80); + waitFrame(); + } + + + enemy.bind(me); + //enemyStayPut = enemy.getWorldOrigin(); + float tim=DelayTime(2); + + //vector turny = getAngles(); + + SetAnimState( ANIMCHANNEL_TORSO, "Torso_AttackDrain", 4 ); + while ( 1 ) { + setWorldOrigin(stayPut); // shadowspawn should not be moved by projectiles, etc, might put player outside of level + //enemy.setWorldOrigin(enemyStayPut); + //turnTo(turny.y + 180); + if (drainTime < ( gameLocal.time / 1000 )) { + drainTime = DelayTime(1); + directDamage(enemy, "damage_shadowspawn_drain"); + setHealth(getHealth()+100); + } + + if ( tim < ( gameLocal.time / 1000 ) ) { + SetAnimState( ANIMCHANNEL_TORSO, "Torso_Repulse", 4 ); + waitAction( "repulse" ); + enemy.unbind(); + dir = sys.vecNormalize(enemy->GetPhysics()->GetOrigin()() - stayPut) * 1180; + dir.z = 0; + enemy->GetPhysics()->SetLinearVelocity( dir ); + break; + } + waitFrame(); + } +} + +void idAI_Shadowspawn::telleport() { + float tDelay; + idVec3 pV; + float dist; + float ang; + idVec3 enemyOrigin = enemy->GetPhysics()->GetOrigin()(); + + nextTelleport = ( gameLocal.time / 1000 ) + sys.random(4)+7; + + // lunge at player and disappear + pV = sys.vecNormalize(enemy.GetEyePos() - GetOrigin()); + turnToEntity(enemy); + tDelay = 1 + ( gameLocal.time / 1000 ); + GetPhysics()->SetLinearVelocity(pV * 1066); + + while (( gameLocal.time / 1000 ) < tDelay && vec.Length(GetOrigin() - enemy.GetEyePos()) > 20) { + waitFrame(); + } + + hide(); + + // delay + tDelay = sys.random(3) + ( gameLocal.time / 1000 ); + while (( gameLocal.time / 1000 ) < tDelay) { + waitFrame(); + } + + // reappear + ang = sys.random(360); + dist = sys.random(350)+150; + pV = enemy.GetEyePos(); + pV.x = pV.x + dist * sys.sin(ang); + pV.y = pV.y + dist * sys.cos(ang); + //pV.z = enemyOrigin.z; + SetOrigin(pV); + show(); + + doTelleport = false; +} +*/ diff --git a/game/ai/AI_Shadowspawn.h b/game/ai/AI_Shadowspawn.h new file mode 100644 index 00000000..63a25734 --- /dev/null +++ b/game/ai/AI_Shadowspawn.h @@ -0,0 +1,16 @@ + +/* +class idAI_Shadowspawn : public idAI { +public: + CLASS_PROTOTYPE( idAI_Shadowspawn ); + void telleport(); + void Spawn(); + void regenerate(); + +private: + idEntity enemy; + idVec3 stayPut; + //vector enemyStayPut; +}; + +*/ \ No newline at end of file diff --git a/game/ai/AI_Veloxite.cpp b/game/ai/AI_Veloxite.cpp new file mode 100644 index 00000000..781f359a --- /dev/null +++ b/game/ai/AI_Veloxite.cpp @@ -0,0 +1,623 @@ + +#include "Entity.h" +#include "AI.h" +#include "AI_Veloxite.h" + +const float VELOX_MAX_WALL_DIST = 75.0f; // get on walls this far away +const float VELOX_SURFACECHECK_RATE = 0.25f; // four times per second should be enough +const float VELOX_WALLCHECK_RATE = 1.0f; +const float VELOX_LEAP_RANGE_MIN = 64.0f; +const float VELOX_LEAP_RANGE_MAX = 275.0f; // this only pertains to AI_LEAPING from walls. +const float VELOX_LEAP_SPEED = 650.0f; +const float VELOX_LEAP_MAXHEIGHT = 48.0f; + +// for all angles defined below, the angle is between floor and wall/slop angles. in other words, the angle is the opposite of what you'd think (180 - value). +const float VELOX_MAX_WALL_ANGLE = 100.0f; // more than 100 is probably asking for clipping problems +const float VELOX_MIN_WALL_ANGLE = 46.0f; // 46 degrees is the very nearly the max slope walkable for aas48 entites +const float VELOX_MIN_SLOPE_ANGLE = 0.0f; // change gravity only on slopes with this angle or greater +const float VELOX_MIN_TRANS_FRAMES = 10; +const float VELOX_MAX_TRANS_FRAMES = 25; +const float VELOX_WALLLEAP_TRANSITIONS = 4; + +// *********************************************************** + +// Script Stuff Continued + +// *********************************************************** + +const idEventDef AI_Veloxite_doSurfaceChecks( "velox_doSurfaceChecks", NULL, 'f' ); +const idEventDef AI_Veloxite_getJumpVelocity( "velox_getJumpVelocity", "fffs", 'v' ); +const idEventDef AI_Veloxite_startLeaping( "velox_startLeaping" ); +const idEventDef AI_Veloxite_doneLeaping( "velox_doneLeaping" ); +const idEventDef AI_Veloxite_getOffWall( "velox_getOffWall", "f" ); + +CLASS_DECLARATION( idAI, idAI_Veloxite ) + EVENT( AI_Veloxite_doSurfaceChecks, idAI_Veloxite::Event_doSurfaceChecks ) + EVENT( AI_Veloxite_getJumpVelocity, idAI_Veloxite::Event_getVeloxJumpVelocity ) + EVENT( AI_Veloxite_doneLeaping, idAI_Veloxite::Event_doneLeaping ) + EVENT( AI_Veloxite_startLeaping, idAI_Veloxite::Event_startLeaping ) + EVENT( AI_Veloxite_getOffWall, idAI_Veloxite::Event_getOffWall ) +END_CLASS + +void idAI_Veloxite::LinkScriptVariables( void ) { + AI_ALIGNING.LinkTo( scriptObject, "AI_ALIGNING" ); + AI_WALLING.LinkTo( scriptObject, "AI_WALLING" ); + AI_LEDGING.LinkTo( scriptObject, "AI_LEDGING" ); + AI_SLOPING.LinkTo( scriptObject, "AI_SLOPING" ); + AI_LEAPING.LinkTo( scriptObject, "AI_LEAPING" ); + AI_FALLING.LinkTo( scriptObject, "AI_FALLING" ); + AI_DROPATTACK.LinkTo( scriptObject, "AI_DROPATTACK" ); + AI_ONWALL.LinkTo( scriptObject, "AI_ONWALL" ); + idAI::LinkScriptVariables(); +} + +void idAI_Veloxite::Save( idSaveGame *savefile ) const { + idAI::Save( savefile ); + + savefile->WriteBool( doPostTrans ); + savefile->WriteBool( doTrans ); + savefile->WriteInt( curTrans ); + savefile->WriteInt( numTrans ); + savefile->WriteVec3( transGrav ); + savefile->WriteVec3( destGrav ); + savefile->WriteVec3( upVec ); + savefile->WriteTrace( trace ); + savefile->WriteFloat( nextWallCheck ); + savefile->WriteFloat( maxGraceTime ); + savefile->WriteFloat( debuglevel ); + savefile->WriteVec3( veloxMins ); + savefile->WriteVec3( veloxMaxs ); + savefile->WriteVec3( traceMins ); + savefile->WriteVec3( traceMaxs ); + savefile->WriteBool( AI_ALIGNING ? true : false ); + savefile->WriteBool( AI_WALLING ? true : false ); + savefile->WriteBool( AI_LEDGING ? true : false ); + savefile->WriteBool( AI_SLOPING ? true : false ); + savefile->WriteBool( AI_LEAPING ? true : false ); + savefile->WriteBool( AI_FALLING ? true : false ); + savefile->WriteBool( AI_DROPATTACK ? true : false ); + savefile->WriteBool( AI_ONWALL ? true : false ); +} + +void idAI_Veloxite::Restore( idRestoreGame *savefile ) { + idAI::Restore( savefile ); + + savefile->ReadBool( doPostTrans ); + savefile->ReadBool( doTrans ); + savefile->ReadInt( curTrans ); + savefile->ReadInt( numTrans ); + savefile->ReadVec3( transGrav ); + savefile->ReadVec3( destGrav ); + savefile->ReadVec3( upVec ); + savefile->ReadTrace( trace ); + savefile->ReadFloat( nextWallCheck ); + savefile->ReadFloat( maxGraceTime ); + savefile->ReadFloat( debuglevel ); + savefile->ReadVec3( veloxMins ); + savefile->ReadVec3( veloxMaxs ); + savefile->ReadVec3( traceMins ); + savefile->ReadVec3( traceMaxs ); + // Link the script variables back to the scriptobject - DG: and do this *before* using them + LinkScriptVariables(); + bool b; + savefile->ReadBool( b ); b ? AI_ALIGNING = true : AI_ALIGNING = false; + savefile->ReadBool( b ); b ? AI_WALLING = true : AI_WALLING = false; + savefile->ReadBool( b ); b ? AI_LEDGING = true : AI_LEDGING = false; + savefile->ReadBool( b ); b ? AI_SLOPING = true : AI_SLOPING = false; + savefile->ReadBool( b ); b ? AI_LEAPING = true : AI_LEAPING = false; + savefile->ReadBool( b ); b ? AI_FALLING = true : AI_FALLING = false; + savefile->ReadBool( b ); b ? AI_DROPATTACK = true : AI_DROPATTACK = false; + savefile->ReadBool( b ); b ? AI_ONWALL = true : AI_ONWALL = false; + +} + +// *********************************************************** + +// Class Method Definitions + +// *********************************************************** + +void idAI_Veloxite::Spawn( void ) { + idVec3 veloxSize = spawnArgs.GetVector( "size", "15 15 15" ); + veloxMins = -veloxSize / 2; + veloxMaxs = veloxSize / 2; + traceMins = veloxMins; + traceMaxs = traceMaxs; + transGrav.Zero(); + doTrans = false; + doPostTrans = false; + curTrans = 0; + debuglevel = 1; + next=0; + LinkScriptVariables(); + // DG: script variables must be linked before using them + AI_ONWALL = onWall(); + // just to make sure 'trace' is safe + idVec3 addHeight = physicsObj.GetGravity(); + addHeight.Normalize(); + addHeight *= 24; + + idVec3 A = physicsObj.GetOrigin() + addHeight; + idVec3 B = A - addHeight * 2; + gameLocal.clip.TraceBounds( trace, idVec3(), B, idBounds(traceMins, traceMaxs), MASK_MONSTERSOLID, this); +} + +void idAI_Veloxite::Think( void ) { + next=0; + if ( ( (float) gameLocal.time / 1000 ) > next ) { + next = ( (float) gameLocal.time / 1000 ) + 1; + } + + if ( AI_FALLING && !AI_ALIGNING && AI_ONGROUND ) { + AI_FALLING = false; + } + + if ( AI_DROPATTACK && !AI_ALIGNING && AI_ONGROUND ) { + AI_DROPATTACK = false; + } + + if ( AI_PAIN && onWall() ) { + getOffSurface( true ); + } + + if ( doTrans ) { + doSurfaceTransition(); + } + + if ( doPostTrans ) { + postSurfaceTransition(); + } + + idAI::Think(); +} + +float idAI_Veloxite::checkSurfaces() { + if ( checkFallingSideways() ) { + return VELOX_SURFACECHECK_RATE ; // we're in the air, so we might as well issue a delay + } + + if (!AI_ONGROUND && disableGravity == true && physicsObj.GetLinearVelocity() == idVec3(0,0,0) ) { + disableGravity = false; + getOffSurface(true); + } + + if (AI_ONGROUND && !AI_FALLING && !AI_ALIGNING && !AI_LEAPING && !AI_DROPATTACK ) + { + // checkStuckInWall(); + if ( checkSlope() ) { return VELOX_SURFACECHECK_RATE ; } + if ( checkLedge() ) { return VELOX_SURFACECHECK_RATE ; } + if ( checkWall() ) { return VELOX_SURFACECHECK_RATE ; } + if ( AI_ONWALL && checkDropAttack() ) { return VELOX_SURFACECHECK_RATE ; } + } + + if ( !AI_ALIGNING ) checkHovering(); // todo: maybe check hovering when all bools are false? + + return VELOX_SURFACECHECK_RATE; +} + +bool idAI_Veloxite::checkWall() { + int asd=0; + + if (( (float) gameLocal.time / 1000 ) < nextWallCheck) { + return false; + } + + idVec3 addHeight = physicsObj.GetGravity(); + addHeight.Normalize(); + addHeight = -addHeight*24; + + idVec3 A = physicsObj.GetOrigin() + addHeight; + idVec3 B = A + ( FacingNormal() * VELOX_MAX_WALL_DIST ); + idVec3 normal = gameLocal.clip.TraceSurfaceNormal(trace, A, B, MASK_MONSTERSOLID, this); + + if ( surfaceType( normal ) == v_wall ) { + gameLocal.clip.TraceBounds( trace, A, B, idBounds(traceMins, traceMaxs), MASK_MONSTERSOLID, this); // trace point just wouldn't do the trick. dunno why (didn't return an idEntity) + idEntity *wall = gameLocal.entities[ trace.c.entityNum ]; + + if ( wall && wallIsWalkable(wall) ) { + nextWallCheck = ( (float) gameLocal.time / 1000 ) + VELOX_WALLCHECK_RATE; // to prevent rapid new surface AI_ALIGNING, wait 5 seconds for old surface. + AI_WALLING=true; + AI_ALIGNING = true; + getOnSurface( -normal ); + return true; + } + } + return false; +} + + +bool idAI_Veloxite::checkDropAttack() { + idVec3 A = physicsObj.GetOrigin(); + idVec3 B = A + DEFAULT_GRAVITY_NORMAL * 1024; + gameLocal.clip.TraceBounds( trace, A, B, idBounds(veloxMins, veloxMaxs), MASK_MONSTERSOLID, this); + idEntity *nmy = gameLocal.entities[ trace.c.entityNum ]; + + if ( nmy && nmy->GetName() == enemy.GetEntity()->GetName() ) { + AI_DROPATTACK = true; + BeginAttack( "melee_veloxite_fall" ); + SetAnimState( ANIMCHANNEL_TORSO, "Torso_LeapAttack", 4 ); + getOffSurface(true); + } + + return false; +} + +bool idAI_Veloxite::checkLedge() { + if (AI_FORWARD) { + idVec3 addHeight = physicsObj.GetGravity(); + addHeight.Normalize(); + addHeight = -addHeight*24; //24 seems to be just enough to penetrate max slopes for ground - at least for velox + + idVec3 myOrigin=physicsObj.GetOrigin(); + idVec3 addForward = FacingNormal() * 24; + idVec3 A = myOrigin + addHeight; + idVec3 B = A + addForward; + + idEntity *wall; + + //make sure there isn't a wall right in our way + idVec3 normal = gameLocal.clip.TraceSurfaceNormal(trace, A, B, MASK_MONSTERSOLID, this); + if ( normal != idVec3(0,0,0) ) { + return false; + } + + // do a trace straight through the floor, a bit of infront of the velox + A = myOrigin + addHeight + addForward; + B = myOrigin - addHeight + addForward; + gameLocal.clip.TraceBounds( trace, A, B, idBounds(traceMins, traceMaxs), MASK_MONSTERSOLID, this); + wall = gameLocal.entities[ trace.c.entityNum ]; + + // if the trace found nothing, then we have a ledge, look for a surface to get on + if ( !wall ) { + A = myOrigin - addHeight + addForward; + B = myOrigin - addHeight - addForward; + normal = gameLocal.clip.TraceSurfaceNormal(trace, A, B, MASK_MONSTERSOLID, this); + gameLocal.clip.TraceBounds( trace, A, B, idBounds(traceMins, traceMaxs), MASK_MONSTERSOLID, this); + wall = gameLocal.entities[ trace.c.entityNum ]; + if ( !wall ) return false; + if ( wallIsWalkable(wall) ) { + AI_LEDGING = true; + AI_ALIGNING = true; + getOnSurface( -normal ); + + return true; + + } + return false; + } + + } + return false; +} + +bool idAI_Veloxite::checkFallingSideways( void ) { + idVec3 vec = physicsObj.GetLinearVelocity(); + float mySpeed = vec.Length(); + + if ( !AI_ONGROUND && !AI_ALIGNING && !AI_WALLING && !AI_FALLING && AI_ONWALL && !AI_DROPATTACK && !AI_SLOPING && !AI_LEDGING && !AI_LEAPING && mySpeed > 125.0f) { + getOffSurface(true); + return true; + } + return false; +} + +bool idAI_Veloxite::checkSlope( void ) { + idVec3 addHeight = physicsObj.GetGravity(); + addHeight.Normalize(); + addHeight = -addHeight* 24; //24 seems to be just enough to penetrate max slopes for ground - at least for velox + + // do a trace straight through the floor + idVec3 A = physicsObj.GetOrigin() + addHeight; + idVec3 B = A - addHeight * 2; + idVec3 normal=gameLocal.clip.TraceSurfaceNormal(trace, A, B, MASK_MONSTERSOLID, this); + + if ( normal != -curNorm() && surfaceType(normal) == v_slope ) { // gravity normal is the opposite of the normal of the plane velox is on + gameLocal.clip.TraceBounds( trace, A, B, idBounds(traceMins, traceMaxs), MASK_MONSTERSOLID, this); // trace point just wouldn't do the trick. dunno why (didn't return an idEntity) + idEntity *wall = gameLocal.entities[ trace.c.entityNum ]; + + if ( wall && wallIsWalkable( wall ) ) { // make sure they don't climb on stupid things + AI_SLOPING = true; + AI_ALIGNING = true; + getOnSurface( -normal ); + return true; + } + } + return false; +} + +bool idAI_Veloxite::checkHovering( void ) { + if ( AI_ONGROUND ) { + idVec3 addHeight = physicsObj.GetGravity(); + addHeight.Normalize(); + addHeight *= 24; //24 seems to be just enough to penetrate max slopes for ground - at least for velox + + // do a trace straight through the floor + idVec3 A = physicsObj.GetOrigin() + addHeight; + idVec3 B = A - addHeight * 2; + gameLocal.clip.TraceBounds( trace, A, B, idBounds(traceMins, traceMaxs), MASK_MONSTERSOLID, this); // trace point just wouldn't do the trick. dunno why (didn't return an idEntity) + idEntity *wall = gameLocal.entities[ trace.c.entityNum ]; + + if ( wall && !wallIsWalkable( wall ) ) { + idVec3 vecUp = physicsObj.GetGravity(); vecUp.Normalize(); + idVec3 dir = FacingNormal() + vecUp * 0.5; dir.Normalize(); + + physicsObj.SetLinearVelocity( dir * 150); + return true; + } + } + return false; +} + +void idAI_Veloxite::getOffSurface( bool fall ) { + if ( AI_ONWALL || AI_LEAPING ) { + // sometimes we might want to keep pre-existing velocity, such as for leapattacks. in such cases, fall is false. + if (fall) { + AI_FALLING = true; // prevent checkSurfaces while AI_FALLING. clipping issues otherwise. + idVec3 thisNorm = physicsObj.GetGravity(); + thisNorm.Normalize(); + thisNorm = -thisNorm; + + // set origin a bit away from the wall to prevent velox from getting on the other side during a drop attack, or getting stuck when falling off a wall. + physicsObj.SetOrigin( physicsObj.GetOrigin() + thisNorm * 10 ); + + physicsObj.SetGravity(thisNorm*100); // reduce gravity to reduse "wall friction" + physicsObj.SetLinearVelocity(DEFAULT_GRAVITY * DEFAULT_GRAVITY_NORMAL); // pull to floor + } + + getOnSurface(DEFAULT_GRAVITY_NORMAL); + } +} + +void idAI_Veloxite::getOnSurface( const idVec3 &newNorm, int numt ) { + AI_ALIGNING = true; + float upMax; // max height we will lift the veloxite to in the transition (avoid clipping probs) + destGrav = newNorm * DEFAULT_GRAVITY; + + // PREPARING TO TRANSITION + // we need to move the velox up, so it doesn't get stuck in a wall when the gravity changes. + if (!AI_LEAPING) { // if we're AI_LEAPING, don't screw with the origin + + // if AI_LEDGING, we need to go away from the new surface instead + // if not AI_LEDGING, upVec will point mostly straight up (relative to the velox) + if (AI_LEDGING) { + upVec = destGrav; upVec.Normalize(); upVec = -upVec; + } else { + upVec = -curNorm(); + } + + // the more perpendicular the wall is to the ground, the more height. from 0 to 90 degrees, increase height. after 90 degrees, decrease height from max. this works for AI_LEDGING, AI_WALLING, and AI_SLOPING + float ang = curNorm().toAngle(newNorm); + if (ang > 90) { + ang = ang - 90; + ang = 90 - ang; + } + + float proportion; + if (AI_SLOPING) { + proportion = ang / 180; + }else{ + proportion = ang / 90; + } + + // now define the maximum height we will lift the velox + if (AI_LEDGING) { upMax = 2.0f; } + else if (AI_SLOPING) { upMax = 0.66f; } + else { upMax = 1.66f; } // AI_FALLING, AI_WALLING + + upMax = upMax; + + // now proportion is a decimal value from 0 to 1. do some magic to get the right height: + upVec = upVec * (((upMax-1) * proportion) + 1); // multiplying by less than 1 causes upVec to get shorter (bad) + + if ( numt < 1 ) { + // we transition the gravity change because when you change gravity, the veolx's model aligns instantly. if done gradually, it looks like a natural rotation. + // the more perpendicular the wall is to the ground, the longer the transition. from 0 to 90 degrees, increase ti after 90 degrees, decrease time from max. + numTrans = ( ( VELOX_MAX_TRANS_FRAMES - VELOX_MIN_TRANS_FRAMES ) * proportion ) + VELOX_MIN_TRANS_FRAMES; + } else { + numTrans = numt; + } + } + + if (AI_LEDGING || AI_WALLING) { disableGravity = true; } + doTrans = true; + curTrans = 0; + transGrav = physicsObj.GetGravity(); + doSurfaceTransition(); +} + +void idAI_Veloxite::doSurfaceTransition( void ) { + idVec3 midGrav; + + if ( curTrans >= numTrans || AI_LEAPING ) { // no transition for AI_LEAPING, causes too many problems. + doTrans = false; + curTrans = 0; + physicsObj.SetGravity(destGrav); // set the final gravity just incase we missed some in the rounding + disableGravity = false; + RunPhysics(); //physicsObj.Evaluate(); // particularly for velocity + maxGraceTime=( (float) gameLocal.time / 1000 )+0.5; + doPostTrans = true; + return; + } + + // TRANSITION + curTrans++; + // move the Veloxite up: + physicsObj.SetOrigin(physicsObj.GetOrigin() + upVec); // Z.TODO: clip test with velox model + + // change velox gravity to the new normal: + midGrav = transGrav * ( 1.0f / curTrans) + + destGrav * ( 1.0f / (numTrans+1-curTrans)); + physicsObj.SetGravity(midGrav); + + return; +} + +void idAI_Veloxite::postSurfaceTransition( void ) { + if (!physicsObj.OnGround() + && !AI_PAIN + && !AI_LEAPING + && !(( (float) gameLocal.time / 1000 ) > maxGraceTime) + && physicsObj.GetLinearVelocity() != idVec3(0,0,0) ) { + return; + } + + doPostTrans = false; + AI_ALIGNING = false; + AI_WALLING = false; + AI_LEDGING = false; + AI_SLOPING = false; + AI_DROPATTACK = false; + + idVec3 worldNorm = gameLocal.GetGravity(); + worldNorm.Normalize(); + + AI_ONWALL = onWall(); + +} + +/****************************************************************** + +Script Events + +******************************************************************/ + +void idAI_Veloxite::Event_getOffWall( float fall ) { + getOffSurface( !(!fall) ); +} + +void idAI_Veloxite::Event_doSurfaceChecks( void ) { + idThread::ReturnFloat( checkSurfaces() ); +} + +void idAI_Veloxite::Event_doneLeaping( void ) { + AI_LEAPING = false; +} + +void idAI_Veloxite::Event_startLeaping( void ) { + // leaping from wall handled here +// if ( AI_ONWALL ) { +// StopMove( MOVE_STATUS_DONE ); +// getOffSurface(false); + +// //get jumpVelocity +// idVec3 jumpTo = lastVisibleEnemyPos + lastVisibleEnemyEyeOffset; +// idVec3 jumpVel = jumpTo - physicsObj.GetOrigin(); +// jumpVel *= ; + +// TurnToward( jumpTo ); +// SetAnimState( ANIMCHANNEL_TORSO, "Torso_LeapAttackFromWall", 4 ); +// } + + // leaping from ground handled in script + + AI_LEAPING = true; +} + +void idAI_Veloxite::Event_getVeloxJumpVelocity( float speed, float max_height, float channel, const char *animname ) { + float range; + idVec3 jumpVelocity; + + if ( AI_ENEMY_IN_FOV ) { //todo: no? + if ( AI_LEAPING ) { + goto returnZero; + } + + if ( AI_ONWALL ) { + if ( !enemy.GetEntity() ) { + goto returnZero; + } + + // don't leap if player is much higher than veloxc because he cant be reached + idVec3 veloxO = physicsObj.GetOrigin(); + idVec3 enemyO = enemy.GetEntity()->GetPhysics()->GetOrigin(); + float zDist = enemyO.z - veloxO.z; + if ( zDist > VELOX_LEAP_MAXHEIGHT / 2 ) { + goto returnZero; + } + + // now check horizontal range (no height considered) + range = ( idVec3(veloxO.x,veloxO.y,0) - idVec3(enemyO.x,enemyO.y,0) ).Length(); + if ( VELOX_LEAP_RANGE_MIN > range && range > VELOX_LEAP_RANGE_MAX) { + goto returnZero; + } + + // trace to the player + gameLocal.clip.TraceBounds( trace, veloxO, enemyO, idBounds(veloxMins, veloxMaxs), MASK_MONSTERSOLID, this); + idEntity *traceEnt = gameLocal.entities[ trace.c.entityNum ]; + if ( !traceEnt) { + goto returnZero; + } + if ( traceEnt->spawnArgs.GetString("name") != enemy.GetEntity()->spawnArgs.GetString("name") ) { + goto returnZero; + } + jumpVelocity = enemyO - veloxO; + // if player is higher, add height to make sure velox will reach him + if ( zDist > 0 ) { jumpVelocity.z += zDist * 2; } + jumpVelocity.Normalize(); + jumpVelocity *= VELOX_LEAP_SPEED; + idThread::ReturnVector( jumpVelocity ); return; + } else { + if ( !idAI::CanHitEnemy() ) { + goto returnZero; + } + range = ( enemy.GetEntity()->GetPhysics()->GetOrigin() - physicsObj.GetOrigin() ).Length(); + if ( range < VELOX_LEAP_RANGE_MIN ) { + goto returnZero; + } + // dont check range max, not necessary + + idThread::ReturnVector( GetJumpVelocity( PredictEnemyPos( GetAnimLength( (int)channel, animname ) ), VELOX_LEAP_SPEED, VELOX_LEAP_MAXHEIGHT ) ); return; + } + } + +returnZero: + + idThread::ReturnVector( idVec3(0,0,0) ); return; +} + + +/****************************************************************** + +Helper Functions + +******************************************************************/ + +ID_INLINE bool idAI_Veloxite::wallIsWalkable( idEntity *wall ) { + if ( !wall ) { return false; } + if (wall->name == "world" || wall->name.Mid(0, 11) == "func_static" || wall->spawnArgs.GetInt("veloxite_walkable") == 1) { + if (wall->name.Right(4) != "_sky") { // not a skybox + return true; + } + } + return false; +} + +ID_INLINE bool idAI_Veloxite::onWall() { + float ang = curNorm().toAngle(DEFAULT_GRAVITY_NORMAL); + + if ( ang > VELOX_MIN_WALL_ANGLE ) { + return true; + } else { + return false; + } +} + +ID_INLINE v_stype idAI_Veloxite::surfaceType( idVec3 normal ) { + v_stype t = v_none; + + if ( normal == idVec3(0,0,0) ) { + return t; + } + + float ang = normal.toAngle(-curNorm()); + + if ( VELOX_MIN_SLOPE_ANGLE <= ang && ang < VELOX_MIN_WALL_ANGLE ) { + t = v_slope; + } + + if ( VELOX_MIN_WALL_ANGLE <= ang && ang < VELOX_MAX_WALL_ANGLE ) { + t = v_wall; + } + + return t; +} diff --git a/game/ai/AI_Veloxite.h b/game/ai/AI_Veloxite.h new file mode 100644 index 00000000..ec195147 --- /dev/null +++ b/game/ai/AI_Veloxite.h @@ -0,0 +1,108 @@ +#ifndef __AI_VELOXITE_H__ +#define __AI_VELOXITE_H__ + +enum v_stype { + v_none = 0, + v_slope = 1, + v_wall = 2 +}; + +class idAI_Veloxite : public idAI { + +public: + CLASS_PROTOTYPE( idAI_Veloxite ); + + void Spawn( void ); + void Think( void ); + void LinkScriptVariables( void ); + void Save( idSaveGame *savefile ) const; + void Restore( idRestoreGame *savefile ); + +// *********************************************************** + +// Helper Functions + +// *********************************************************** +private: + ID_INLINE bool onWall( void ); + ID_INLINE bool wallIsWalkable( idEntity *wall ); + ID_INLINE v_stype surfaceType( idVec3 normal ); + + +// *********************************************************** + +// Class Method Declarations + +// *********************************************************** +private: +// surface checks etc + float checkSurfaces( void ); + bool checkSlope( void ); + bool checkLedge( void ); + bool checkWall( void ); + void getOnSurface( const idVec3 &norm, int numt = 0 ); + void getOffSurface( bool fall ); + void doSurfaceTransition( void ); + void postSurfaceTransition( void ); +// player chasing / attacks + bool checkDropAttack( void ); +// bool checkParallel( void ); // if enemy is above velox (relative to velox's gravity) and there there is floor to drop on, do it. +// fix oops's + bool checkFallingSideways( void ); // if we're going faster than 300, we must be falling, make sure it's truly straight down! + bool checkHovering( void ); // if we're AI_ONGROUND but origin is not above a surface, we're hovering over a ledge. do something... + +// *********************************************************** + +// Variables + +// *********************************************************** +private: +// transitions + bool doPostTrans; + bool doTrans; + int curTrans; + int numTrans; // in frames + idVec3 transGrav; + idVec3 destGrav; + idVec3 upVec; +// other stuff + trace_t trace; + float nextWallCheck; + float maxGraceTime; + float debuglevel; + idVec3 veloxMins; + idVec3 veloxMaxs; + idVec3 traceMins;float next; + idVec3 traceMaxs; + +/* Debug Level: + 1 - useful functions that dont happen every frame + 5 - useful functions that happen every frame + 10 - unuseful functions that happen every frame + 15 - unuseful functions that may happen more than every frame + 20 - things you'll functions never want to see +*/ + +// *********************************************************** + +// Script Stuff + +// *********************************************************** +protected: + idScriptBool AI_ALIGNING; + idScriptBool AI_WALLING; + idScriptBool AI_LEDGING; + idScriptBool AI_SLOPING; + idScriptBool AI_LEAPING; + idScriptBool AI_FALLING; + idScriptBool AI_DROPATTACK; + idScriptBool AI_ONWALL; +private: + void Event_doSurfaceChecks( void ); + void Event_doneLeaping( void ); + void Event_startLeaping( void ); + void Event_getVeloxJumpVelocity( float speed, float max_height, float channel, const char *animname ); + void Event_getOffWall( float fall ); +}; + +#endif // __AI_VELOXITE_H__ diff --git a/game/ai/AI_events.cpp b/game/ai/AI_events.cpp index 04d66870..0fb667fe 100644 --- a/game/ai/AI_events.cpp +++ b/game/ai/AI_events.cpp @@ -60,6 +60,7 @@ const idEventDef AI_MeleeAttackToJoint( "meleeAttackToJoint", "ss", 'd' ); const idEventDef AI_RandomPath( "randomPath", NULL, 'e' ); const idEventDef AI_CanBecomeSolid( "canBecomeSolid", NULL, 'f' ); const idEventDef AI_BecomeSolid( "becomeSolid" ); +const idEventDef AI_BecomeNonSolid( "becomeNonSolid" ); const idEventDef AI_BecomeRagdoll( "becomeRagdoll", NULL, 'd' ); const idEventDef AI_StopRagdoll( "stopRagdoll" ); const idEventDef AI_SetHealth( "setHealth", "f" ); @@ -162,6 +163,16 @@ const idEventDef AI_CanReachPosition( "canReachPosition", "v", 'd' ); const idEventDef AI_CanReachEntity( "canReachEntity", "E", 'd' ); const idEventDef AI_CanReachEnemy( "canReachEnemy", NULL, 'd' ); const idEventDef AI_GetReachableEntityPosition( "getReachableEntityPosition", "e", 'v' ); +// HEXEN : Zeroth +const idEventDef AI_IsEnemy( "IsEnemy", "e", 'f' ); +const idEventDef AI_NumGroundContacts( "NumGroundContacts", NULL, 'f' ); +const idEventDef AI_VecForward( "VecForward", NULL, 'v' ); +const idEventDef AI_VecFacing( "VecFacing", NULL, 'v' ); +//const idEventDef AI_SetFacingDir( "SetFacingDir", "v" ); +const idEventDef AI_IgnoreObstacles( "IgnoreObstacles" ); +const idEventDef AI_DirectMoveToPosition( "DirectMoveToPosition", "v" ); +const idEventDef AI_MoveForward( "MoveForward" ); +const idEventDef AI_GetRealEnemyPos( "getRealEnemyPos", NULL, 'v' ); CLASS_DECLARATION( idActor, idAI ) EVENT( EV_Activate, idAI::Event_Activate ) @@ -292,6 +303,17 @@ CLASS_DECLARATION( idActor, idAI ) EVENT( AI_CanReachEntity, idAI::Event_CanReachEntity ) EVENT( AI_CanReachEnemy, idAI::Event_CanReachEnemy ) EVENT( AI_GetReachableEntityPosition, idAI::Event_GetReachableEntityPosition ) +// HEXEN : Zeroth + EVENT( AI_IsEnemy, idAI::Event_IsEnemy ) + EVENT( AI_NumGroundContacts, idAI::Event_NumGroundContacts ) + EVENT( AI_VecForward, idAI::Event_VecForward ) + EVENT( AI_VecFacing, idAI::Event_VecFacing ) + //EVENT( AI_SetFacingDir, idAI::Event_SetFacingDir ) + EVENT( AI_IgnoreObstacles, idAI::Event_IgnoreObstacles ) + EVENT( AI_IgnoreObstacles, idAI::Event_IgnoreObstacles ) + EVENT( AI_MoveForward, idAI::MoveForward ) + EVENT( AI_DirectMoveToPosition, idAI::DirectMoveToPosition ) + EVENT( AI_GetRealEnemyPos, idAI::Event_GetRealEnemyPos ) END_CLASS /* @@ -772,6 +794,10 @@ idAI::Event_CanBecomeSolid ===================== */ void idAI::Event_CanBecomeSolid( void ) { + idThread::ReturnFloat( CanBecomeSolid() ); +} + +bool idAI::CanBecomeSolid( void ) { int i; int num; idEntity * hit; @@ -793,12 +819,11 @@ void idAI::Event_CanBecomeSolid( void ) { } if ( physicsObj.ClipContents( cm ) ) { - idThread::ReturnFloat( false ); - return; + return false; } } - idThread::ReturnFloat( true ); + return true; } /* @@ -807,6 +832,10 @@ idAI::Event_BecomeSolid ===================== */ void idAI::Event_BecomeSolid( void ) { + BecomeSolid( ); +} + +void idAI::BecomeSolid( void ) { physicsObj.EnableClip(); if ( spawnArgs.GetBool( "big_monster" ) ) { physicsObj.SetContents( 0 ); @@ -825,11 +854,16 @@ idAI::Event_BecomeNonSolid ===================== */ void idAI::Event_BecomeNonSolid( void ) { + BecomeNonSolid(); +} + +void idAI::BecomeNonSolid( void ) { fl.takedamage = false; physicsObj.SetContents( 0 ); physicsObj.GetClipModel()->Unlink(); } + /* ===================== idAI::Event_BecomeRagdoll @@ -1207,37 +1241,7 @@ idAI::Event_GetJumpVelocity ===================== */ void idAI::Event_GetJumpVelocity( const idVec3 &pos, float speed, float max_height ) { - idVec3 start; - idVec3 end; - idVec3 dir; - float dist; - bool result; - idEntity *enemyEnt = enemy.GetEntity(); - - if ( !enemyEnt ) { - idThread::ReturnVector( vec3_zero ); - return; - } - - if ( speed <= 0.0f ) { - gameLocal.Error( "Invalid speed. speed must be > 0." ); - } - - start = physicsObj.GetOrigin(); - end = pos; - dir = end - start; - dist = dir.Normalize(); - if ( dist > 16.0f ) { - dist -= 16.0f; - end -= dir * 16.0f; - } - - result = PredictTrajectory( start, end, speed, physicsObj.GetGravity(), physicsObj.GetClipModel(), MASK_MONSTERSOLID, max_height, this, enemyEnt, ai_debugMove.GetBool() ? 4000 : 0, dir ); - if ( result ) { - idThread::ReturnVector( dir * speed ); - } else { - idThread::ReturnVector( vec3_zero ); - } + idThread::ReturnVector( GetJumpVelocity( pos, speed, max_height ) ) ; } /* @@ -1386,6 +1390,10 @@ void idAI::Event_GetEnemyPos( void ) { idThread::ReturnVector( lastVisibleEnemyPos ); } +void idAI::Event_GetRealEnemyPos( void ) { + idThread::ReturnVector( enemy.GetEntity()->GetPhysics()->GetOrigin() ); +} + /* ===================== idAI::Event_GetEnemyEyePos @@ -1401,19 +1409,22 @@ idAI::Event_PredictEnemyPos ===================== */ void idAI::Event_PredictEnemyPos( float time ) { + idThread::ReturnVector( PredictEnemyPos(time) ); +} + +idVec3 idAI::PredictEnemyPos( float time ) { predictedPath_t path; idActor *enemyEnt = enemy.GetEntity(); // if no enemy set if ( !enemyEnt ) { - idThread::ReturnVector( physicsObj.GetOrigin() ); - return; + return physicsObj.GetOrigin(); } // predict the enemy movement idAI::PredictPath( enemyEnt, aas, lastVisibleEnemyPos, enemyEnt->GetPhysics()->GetLinearVelocity(), SEC2MS( time ), SEC2MS( time ), ( move.moveType == MOVETYPE_FLY ) ? SE_BLOCKED : ( SE_BLOCKED | SE_ENTER_LEDGE_AREA ), path ); - idThread::ReturnVector( path.endPos ); + return path.endPos; } /* @@ -1422,19 +1433,21 @@ idAI::Event_CanHitEnemy ===================== */ void idAI::Event_CanHitEnemy( void ) { + idThread::ReturnInt( CanHitEnemy() ); +} + +bool idAI::CanHitEnemy( void ) { trace_t tr; idEntity *hit; idActor *enemyEnt = enemy.GetEntity(); if ( !AI_ENEMY_VISIBLE || !enemyEnt ) { - idThread::ReturnInt( false ); - return; + return false; } // don't check twice per frame if ( gameLocal.time == lastHitCheckTime ) { - idThread::ReturnInt( lastHitCheckResult ); - return; + return lastHitCheckResult; } lastHitCheckTime = gameLocal.time; @@ -1458,7 +1471,7 @@ void idAI::Event_CanHitEnemy( void ) { lastHitCheckResult = false; } - idThread::ReturnInt( lastHitCheckResult ); + return lastHitCheckResult; } /* @@ -1773,7 +1786,7 @@ idAI::Event_TestMeleeAttack ===================== */ void idAI::Event_TestMeleeAttack( void ) { - bool result = TestMelee(); + bool result = TestMelee( idVec3(0, 0, 0) ); // DG: making sure the idVec3 has a deterministic value idThread::ReturnInt( result ); } @@ -2706,3 +2719,41 @@ void idAI::Event_GetReachableEntityPosition( idEntity *ent ) { idThread::ReturnVector( pos ); } + +// HEXEN : Zeroth +void idAI::Event_IsEnemy( const idEntity *test ) { + idThread::ReturnFloat(IsEnemy( (idEntity *) test)); +} + +// HEXEN : Zeroth +void idAI::Event_NumGroundContacts( void ) { + idThread::ReturnFloat( physicsObj.NumGroundContacts() ); +} + +// HEXEN : Zeroth +void idAI::Event_VecFacing( void ) { + // return proper facing direction (yaw only) + idVec3 dir=viewAxis[ 0 ] * physicsObj.GetGravityAxis(); + dir.Normalize(); + idThread::ReturnVector(dir); +} + +// HEXEN : Zeroth - no workie +/*void idAI::Event_SetFacingDir( idVec3 dir ) { + // return proper facing direction (yaw only) + dir.Normalize(); + viewAxis[ 0 ] = dir;// * physicsObj.GetGravityAxis(); +}*/ + +// HEXEN : Zeroth +void idAI::Event_VecForward( void ) { + // return proper forward vector for whatever way the AI is looking + idVec3 dir=deltaViewAngles.ToForward() * physicsObj.GetGravityAxis(); + dir.Normalize(); + idThread::ReturnVector(dir); +} + +// HEXEN : Zeroth +void idAI::Event_IgnoreObstacles( void ) { + ignoreObstacles = true; +} diff --git a/game/ai/AI_pathing.cpp b/game/ai/AI_pathing.cpp index 57f38eba..819f6b74 100644 --- a/game/ai/AI_pathing.cpp +++ b/game/ai/AI_pathing.cpp @@ -907,7 +907,11 @@ bool FindOptimalPath( const pathNode_t *root, const obstacle_t *obstacles, int n } if ( !pathToGoalExists ) { - seekPos.ToVec2() = root->children[0]->pos; + if ( root->children[0] != NULL ) { + seekPos.ToVec2() = root->children[0]->pos; + } else { + seekPos.ToVec2() = root->pos; + } } else if ( !optimizedPathCalculated ) { OptimizePath( root, bestNode, obstacles, numObstacles, optimizedPath ); seekPos.ToVec2() = optimizedPath[1]; diff --git a/game/anim/Anim.cpp b/game/anim/Anim.cpp index a9f6252d..04136b36 100644 --- a/game/anim/Anim.cpp +++ b/game/anim/Anim.cpp @@ -1075,6 +1075,8 @@ void idAnimManager::FlushUnusedAnims( void ) { idList removeAnims; for( i = 0; i < animations.Num(); i++ ) { + + animptr = animations.GetIndex( i ); if ( animptr && *animptr ) { if ( ( *animptr )->NumRefs() <= 0 ) { diff --git a/game/anim/Anim.h b/game/anim/Anim.h index dcba8531..d91d2dc2 100644 --- a/game/anim/Anim.h +++ b/game/anim/Anim.h @@ -540,6 +540,7 @@ class idAnimator { void SetJointPos( jointHandle_t jointnum, jointModTransform_t transform_type, const idVec3 &pos ); void SetJointAxis( jointHandle_t jointnum, jointModTransform_t transform_type, const idMat3 &mat ); + void ClearJoint( jointHandle_t jointnum ); void ClearAllJoints( void ); @@ -592,6 +593,15 @@ class idAnimator { idList AFPoseJointFrame; idBounds AFPoseBounds; int AFPoseTime; + +// HEXEN : Zeroth +private: + idList< idDict > jointTransitions; + void transitionJoints( void ); + +// HEXEN : Zeroth +public: + void eoc_TransitionJointAngle( jointHandle_t jointnum, jointModTransform_t transform_type, idAngles &to, idAngles &from, float seconds, float transitions ); }; /* diff --git a/game/anim/Anim_Blend.cpp b/game/anim/Anim_Blend.cpp index e3409ba5..ab909e1d 100644 --- a/game/anim/Anim_Blend.cpp +++ b/game/anim/Anim_Blend.cpp @@ -3031,6 +3031,9 @@ idAnimator::idAnimator() { channels[ i ][ j ].Reset( NULL ); } } + +// HEXEN : Zeroth + jointTransitions.Clear(); } /* @@ -3606,6 +3609,45 @@ void idAnimator::SetJointPos( jointHandle_t jointnum, jointModTransform_t transf ForceUpdate(); } +void idAnimator::eoc_TransitionJointAngle( jointHandle_t jointnum, jointModTransform_t transform_type, idAngles &to, idAngles &from, float seconds, float transitions ) { + int i, c; + + // find the joint if it's already got a transition going on + for (i=0; i ); if ( gameLocal.inCinematic && gameLocal.skipCinematic ) { @@ -5030,3 +5074,48 @@ idRenderModel *idGameEdit::ANIM_CreateMeshForAnim( idRenderModel *model, const c return newmodel; } + +// HEXEN : Zeroth +void idAnimator::transitionJoints( void ) { + for (int i=0; i < jointTransitions.Num(); i++) { + int t; + int transitions = jointTransitions[i].GetInt("transitions"); + int nextTran = jointTransitions[i].GetInt("next_transition", "0"); + float curTime = gameLocal.time; + float time = jointTransitions[i].GetFloat( va( "time_%i", nextTran ) ); + + // make sure we aren't lagging behind the time. + for ( t=nextTran; t < transitions; t++ ) { + if ( curTime < time ) { + // t is the transition we're on for the current time. + break; + } else { + // get the next time and up the transition counter + time = jointTransitions[i].GetFloat( va( "time_%i", t ) ); + } + } + + // if we've cycled all the transitions and all of them are in the past, just set this to the last transition + if ( t == transitions ) { + t = transitions - 1; + } + + // get the angle we need to set to + idAngles ang = jointTransitions[i].GetAngles( va( "angle_%i", t ) ); + + SetJointAxis( (jointHandle_t) jointTransitions[i].GetInt("jointnum"), + (jointModTransform_t) jointTransitions[i].GetInt("transform_type"), + ang.ToMat3() ); + + // only up the transition if the current time is greater than the time to apply the current transition + if ( t < transitions && curTime > jointTransitions[i].GetFloat( va( "time_%i", t-1 ) ) ) { + t++; + } + + if ( t == transitions ) { + jointTransitions.RemoveIndex(i); + } else { + jointTransitions[i].SetInt("next_transition", t); + } + } +} \ No newline at end of file diff --git a/game/gamesys/Event.cpp b/game/gamesys/Event.cpp index 013b13dd..ad853903 100644 --- a/game/gamesys/Event.cpp +++ b/game/gamesys/Event.cpp @@ -686,6 +686,8 @@ void idEvent::Restore( idRestoreGame *savefile ) { savefile->ReadInt( num ); for ( i = 0; i < num; i++ ) { + + if ( FreeEvents.IsListEmpty() ) { gameLocal.Error( "idEvent::Restore : No more free events" ); } @@ -722,6 +724,7 @@ void idEvent::Restore( idRestoreGame *savefile ) { format = event->eventdef->GetArgFormat(); assert( format ); for ( j = 0, size = 0; j < event->eventdef->GetNumArgs(); ++j) { + dataPtr = &event->data[ event->eventdef->GetArgOffset( j ) ]; switch( format[ j ] ) { case D_EVENT_FLOAT : diff --git a/game/gamesys/SaveGame.cpp b/game/gamesys/SaveGame.cpp index 158f5abe..14c27833 100644 --- a/game/gamesys/SaveGame.cpp +++ b/game/gamesys/SaveGame.cpp @@ -809,6 +809,7 @@ void idRestoreGame::CreateObjects( void ) { memset( objects.Ptr(), 0, sizeof( objects[ 0 ] ) * objects.Num() ); for( i = 1; i < objects.Num(); i++ ) { + ReadString( classname ); type = idClass::GetClass( classname ); if ( !type ) { @@ -837,11 +838,16 @@ void idRestoreGame::RestoreObjects( void ) { // restore all the objects for( i = 1; i < objects.Num(); i++ ) { + + //MessageBox( NULL, objects[i]->GetClassname(), objects[i]->GetClassname(), 1); + //MessageBox( NULL, static_cast(objects[ i ])->GetName(), "ah", MB_OK ); CallRestore_r( objects[ i ]->GetType(), objects[ i ] ); } // regenerate render entities and render lights because are not saved for( i = 1; i < objects.Num(); i++ ) { + + if ( objects[ i ]->IsType( idEntity::Type ) ) { idEntity *ent = static_cast( objects[ i ] ); ent->UpdateVisuals(); diff --git a/game/gamesys/SysCmds.cpp b/game/gamesys/SysCmds.cpp index 105ff577..c362e7c0 100644 --- a/game/gamesys/SysCmds.cpp +++ b/game/gamesys/SysCmds.cpp @@ -233,6 +233,439 @@ void KillEntities( const idCmdArgs &args, const idTypeInfo &superClass ) { } } +// HEXEN : Zeroth +void Cmd_MapRestart_f( const idCmdArgs &args ) { + if ( !gameLocal.GetLocalPlayer() || !gameLocal.CheatsOk( false ) ) { + return; + } + + idStr map = gameLocal.GetMapName(); + idStr cmd; + + map = map.Right(map.Length() - 4); // get right of the leading "/map" because the 'map' session command tacks it on. + + gameLocal.sessionCommand = "map "; +// gameLocal.sessionCommand += map.c_str(); + //gameLocal.sessionCommand += gameLocal.eoc_MapPath.c_str(); + gameLocal.sessionCommand += gameLocal.mapNameForCheat; +} + +// HEXEN : Zeroth +// determine which parts of the settings GUI should be visible and display the proper settings on it +void Cmd_guiUpdateSettings_f( const idCmdArgs &args ) { + int i; + int ratio=cvarSystem->GetCVarInteger( "r_aspectRatio" ); + + idUserInterface *mainMenuGui = uiManager->FindGui( "guis/mainmenu.gui", true, false, true ); + + // HEXEN : Zeroth. determine what mode we're in + for (i=0; iGetCVarInteger( "r_customWidth" ) && + gameLocal.r_vmodes[i].height == cvarSystem->GetCVarInteger( "r_customHeight" ) && + gameLocal.r_vmodes[i].ratio == cvarSystem->GetCVarInteger( "r_aspectRatio" ) ) + { + r_vmode.SetInteger(i); + break; + } + } + + if ( i == EOC_NUM_VMODES ) { + r_vmode.SetInteger( -1 ); + mainMenuGui->SetStateBool( "modeCustom", true ); + mainMenuGui->SetStateBool( "modeSetCustom", true); + } else { + mainMenuGui->SetStateBool( "modeCustom", false); + mainMenuGui->SetStateBool( "modeSetCustom", false); + if ( ratio == 0 ) { + mainMenuGui->SetStateBool( "modeSet0", true ); + mainMenuGui->SetStateBool( "modeChoices0", true ); + } else { + mainMenuGui->SetStateBool( "modeSet0", false ); + mainMenuGui->SetStateBool( "modeChoices0", false ); + } + + if ( ratio == 1 ) { + mainMenuGui->SetStateBool( "modeSet1", true ); + mainMenuGui->SetStateBool( "modeChoices1", true ); + } else { + mainMenuGui->SetStateBool( "modeSet1", false ); + mainMenuGui->SetStateBool( "modeChoices1", false ); + } + + if ( ratio == 2 ) { + mainMenuGui->SetStateBool( "modeSet2", true ); + mainMenuGui->SetStateBool( "modeChoices2", true ); + } else { + mainMenuGui->SetStateBool( "modeSet2", false ); + mainMenuGui->SetStateBool( "modeChoices2", false ); + } + } + + mainMenuGui->SetStateInt( "r_aspectRatio", ratio ); + mainMenuGui->SetStateInt( "r_customWidth", cvarSystem->GetCVarInteger( "r_customWidth" ) ); + mainMenuGui->SetStateInt( "r_customHeight", cvarSystem->GetCVarInteger( "r_customHeight" ) ); + + switch ( ratio ) { + case 0: mainMenuGui->SetStateString( "r_aspectRatioString", "3:4" ); break; + case 1: mainMenuGui->SetStateString( "r_aspectRatioString", "16:9" ); break; + case 2: mainMenuGui->SetStateString( "r_aspectRatioString", "16:10" ); break; + } + + // set up font color for video graphics options on settings menu (so much easier than doing it in gui) + idStr str; + char key[50]; + for ( int n=0; n<4; n++ ) { + if ( cvarSystem->GetCVarInteger( "com_machineSpec" ) == n ) { + str="1,.66,.15,1"; + } else { + str="1, 0.8, 0.5, 1"; + } + sprintf(key, "video_%i_fcolor", n); + mainMenuGui->SetStateString( key, str ); + } +} + +void Cmd_UpdateFog_f( const idCmdArgs &args ) { + gameLocal.UpdateFog(); +} + +void Cmd_SetClass_f( const idCmdArgs &args ) { + idStr clas = args.Argv( 1 ); + gameLocal.GetLocalPlayer()->SetClass( atoi(clas.c_str()) ); +} + +void Cmd_ClearPLI_f( const idCmdArgs &args ) { + gameLocal.persistentLevelInfo.Clear(); + idEntity* ent; + idStr name; + + for (int i = 0; i < MAX_GENTITIES; i++ ) { + ent = gameLocal.entities[i]; + if (ent) { + + name = ent->GetEntityDefName(); + + // find moveables locations + if ( name.Left(9) == "moveable_" ) { + // z.todo: crash on moveables which inherit from "moveable_item_default" via def. + // (SVN 2889, check moveable_book_1 in moveable.def) + + static_cast( ent )->savePersistentInfo = false; + } + } + } +} + +// HEXEN : Zeroth - set up video mode +void Cmd_VidRestart_f( const idCmdArgs &args ) { + bool doit=false; + + // DG: initialized these variables to appease the compiler + int ratio = 0; + int width = 0; + int height = 0; + + int eoc_cvarmode = r_vmode.GetInteger(); + + idPlayer *localplayer = gameLocal.GetLocalPlayer(); + + /// if you click too fast, doom3 doesnt register that you've unclicked when it resets video. let's try to find the best way to go about this... + //float wait=gameLocal.time+500; + //while ( gameLocal.time < wait ) { + // sys.wait( + //} + //if ( localplayer != NULL ) { + // localplayer->usercmd.buttons = 1; + // localplayer->weapon.GetEntity()->eoc_UnstickAllButtons(); + //} + + // figure out what resolution to use + if ( eoc_cvarmode >= 0 && eoc_cvarmode < EOC_NUM_VMODES ) { // preset resolution/ratio + height = gameLocal.r_vmodes[eoc_cvarmode].height; + width = gameLocal.r_vmodes[eoc_cvarmode].width; + ratio = gameLocal.r_vmodes[eoc_cvarmode].ratio; + } else if ( eoc_cvarmode == -1 ) { // custom resoultion/ratio + height = cvarSystem->GetCVarInteger( "r_customHeight"); + width = cvarSystem->GetCVarInteger( "r_customWidth"); + ratio = cvarSystem->GetCVarInteger( "r_aspectRatio"); + } + + cvarSystem->SetCVarInteger( "r_aspectRatio", ratio ); + + cvarSystem->SetCVarInteger( "r_mode", -1 ); + cvarSystem->SetCVarInteger( "r_customHeight", height ); + cvarSystem->SetCVarInteger( "r_customWidth", width ); + + cmdSystem->BufferCommandText( CMD_EXEC_NOW, "vid_restart" ); + const idCmdArgs nil; + + Cmd_guiUpdateSettings_f( nil ); + + gameLocal.UpdateFog(); +} + +// HEXEN : Zeroth - clear persistent info and start newgame +void Cmd_NewGame_f( const idCmdArgs &args ) { + const idCmdArgs nil; + + if ( args.Argc() == 0 ) { + gameLocal.Printf("no map specified"); + return; + } + + Cmd_ClearPLI_f( nil ); + + idStr cmd; + cmd="map "; + cmd+=args.Argv(1); + + cmdSystem->BufferCommandText( CMD_EXEC_APPEND, cmd.c_str() ); +} + +// HEXEN : Zeroth +void Cmd_SpawnAt_f( const idCmdArgs &args ) { + idStr point = "info_player_start_"; + point.Append(args.Args()); + gameLocal.SetLocalPlayerSpawnPoint(point); + gameLocal.Printf("Spawn point set to %s", point.c_str()); +} + +// HEXEN : Zeroth +void Cmd_Visit_f( const idCmdArgs &args ) { + idStr cmd = args.Args(); + + if ( cmd.Length() != 6 ) { + gameLocal.Printf("Usage: visit e#h#m#"); + return; + } + + idStr episode; + idStr map; + idStr path = "map hexen/ep"; + + cmd.Mid(1,1,episode); + path.Append(episode); + path.Append("/"); + cmd.Right(4,map); + path.Append(map); + + gameLocal.Printf("Changing map to: %s", path.c_str()); + cmdSystem->BufferCommandText( CMD_EXEC_NOW, path.c_str() ); +} + +// HEXEN : Zeroth +void Cmd_Where_f( const idCmdArgs &args ) { + if ( !gameLocal.GetLocalPlayer() || !gameLocal.CheatsOk( false ) ) { + return; + } + idVec3 origin = gameLocal.GetLocalPlayer()->GetPhysics()->GetOrigin(); + gameLocal.Printf("You are here -> %f, %f, %f", origin.x, origin.y, origin.z); +} + +// HEXEN : Zeroth +void Cmd_Ticker_f( const idCmdArgs &args ) { + if ( !gameLocal.GetLocalPlayer() || !gameLocal.CheatsOk( false ) ) { + return; + } + if (cvarSystem->GetCVarBool( "com_showFPS" ) ) { + cvarSystem->SetCVarBool( "com_showFPS", false ); + } else { + cvarSystem->SetCVarBool( "com_showFPS", true ); + } +} + +// HEXEN : Zeroth +void Cmd_iddqd_f( const idCmdArgs &args ) { + if ( !gameLocal.GetLocalPlayer() || !gameLocal.CheatsOk( false ) ) { + return; + } + + idPlayer *player; + + if ( gameLocal.isMultiplayer ) { + if ( gameLocal.isClient ) { + idBitMsg outMsg; + byte msgBuf[ MAX_GAME_MESSAGE_SIZE ]; + outMsg.Init( msgBuf, sizeof( msgBuf ) ); + outMsg.WriteByte( GAME_RELIABLE_MESSAGE_KILL ); + networkSystem->ClientSendReliableMessage( outMsg ); + } else { + player = gameLocal.GetClientByCmdArgs( args ); + if ( !player ) { + common->Printf( "kill or kill \n" ); + return; + } + player->Kill( false, false ); + cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "say killed client %d '%s^0'\n", player->entityNumber, gameLocal.userInfo[ player->entityNumber ].GetString( "ui_name" ) ) ); + } + } else { + player = gameLocal.GetLocalPlayer(); + if ( !player ) { + return; + } + gameLocal.Printf("Trying to cheat, eh? Now you die!"); + player->hud->SetStateString("eoc_message", "Trying to cheat, eh? Now you die!"); + player->hud->HandleNamedEvent( "eoc_MessageShow" ); + player->Kill( false, false ); + } +} + +// HEXEN : Zeroth +void Cmd_Conan_f( const idCmdArgs &args ) { + if ( !gameLocal.GetLocalPlayer() || !gameLocal.CheatsOk( false ) ) { + return; + } + + idPlayer *player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk() ) { + return; + } + + player->inventory.weapons = 1; + player->PerformImpulse(0); + + gameLocal.Printf("Conan-Mode!"); +} + +void Cmd_FullHealth_f( const idCmdArgs &args ) { + idPlayer *player; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk() ) { + return; + } + + player->health = player->inventory.maxHealth; + gameLocal.Printf( "Health Restored\n" ); +} + +// HEXEN : Zeroth +void Cmd_idkfa_f( const idCmdArgs &args ) { + if ( !gameLocal.GetLocalPlayer() || !gameLocal.CheatsOk( false ) ) { + return; + } + + idPlayer *player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk() ) { + return; + } + + gameLocal.Printf("Trying to cheat, eh? You don't deserve items!"); + player->hud->SetStateString("eoc_message", "Trying to cheat, eh? You don't deserve items!"); + player->hud->HandleNamedEvent( "eoc_MessageShow" ); + + int i; + for ( i=NUM_UNIQUE_ARTIFACTS-1; i>=0; i-- ) { + player->ArtifactRemove( i ); + } + + player->UpdateHudArtifacts(); + player->UpdateArtifactHudDescription(); + player->ShowArtifactHud(); + + player->inventory.weapons = 1; + player->PerformImpulse(0); + player->inventory.powerups = 0; + player->inventory.armor = 0; + + player->inventory.items.Clear(); + + for (i=0; i < AMMO_NUMTYPES; i++) + player->inventory.ammo[ i ] = 0; + for (i=0; i < MAX_WEAPONS; i++) + player->inventory.clip[ i ] = 0; +} + +// HEXEN : Zeroth +void Cmd_WallWalk_f( const idCmdArgs &args ) { + const char *msg; + idPlayer *player; + idPhysics *phys; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk() ) { + return; + } + + if ( player->gravityMod ) { + player->gravityMod = false; + //player->GetPhysics()->SetGravity(idVec3(0,0,-1066)); + //phys = static_cast( player->GetPlayerPhysics() ); + phys = player->GetPhysics(); + phys->TransitionToGravity = idVec3(0,0,-1); + phys->TransitionFromGravity = phys->GetGravityNormal(); + phys->curTransition = 0; + msg = "OFF"; + } else { + player->gravityMod = true; + msg = "ON - try walking up a slope somewhere"; + } + + gameLocal.Printf( "wall walking %s", msg ); +} + +void Cmd_Beaten_f( const idCmdArgs &args ) { + idPlayer *player=gameLocal.GetLocalPlayer(); + + if ( player ) { + if ( player->beaten ) { + player->beaten = false; + gameLocal.Printf("set: beaten."); + } else { + player->beaten = true; + gameLocal.Printf("set: not beaten."); + } + } +} + +void Cmd_Hexinfo_f( const idCmdArgs &args ) { + int i; + char str[1024]; + FILE *fsvn=NULL; + fsvn=fopen("eoc/.svn/entries","r"); + if ( !fsvn ) { + fsvn=fopen("trunk/.svn/entries","r"); + } + if ( !fsvn ) { + fsvn=fopen("hexen/.svn/entries","r"); + } + + // print time + time_t rawtime; + time(&rawtime); + gameLocal.Printf("Date: %s", ctime(&rawtime) ); + idPlayer *player=gameLocal.GetLocalPlayer(); + gameLocal.Printf("one: %s", player->spawnArgs.GetString("one") ); + gameLocal.Printf("two: %s", player->spawnArgs.GetString("two") ); + gameLocal.Printf("beaten: %s", player->spawnArgs.GetString("beaten") ); + + + // print svn revision + if ( fsvn ) { + // skip 3 lines (lines in this svn file are pretty short) + for (i=0; i<3; i++) { + fgets(str, sizeof(str), fsvn); + } + fgets(str, sizeof(str), fsvn); + + gameLocal.Printf("SVN: %s", str); + + fclose(fsvn); + } + + gameLocal.Printf("Release: %i", EOC_RELEASE); +} + +void Cmd_ToggleFog_f( const idCmdArgs &args ) { + if ( r_fog.GetBool() ) { + r_fog.SetBool(false); + } else { + r_fog.SetBool(true); + } + + gameLocal.UpdateFog(); +} + /* ================== Cmd_KillMonsters_f @@ -241,10 +674,22 @@ Kills all the monsters in a level. ================== */ void Cmd_KillMonsters_f( const idCmdArgs &args ) { - KillEntities( args, idAI::Type ); + idEntity *ent; + + if ( !gameLocal.GetLocalPlayer() || !gameLocal.CheatsOk( false ) ) { + return; + } + + for( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) { + if ( ent->IsType( idAI::Type ) ) { + static_cast< idAI* >( ent )->Kill(); + } + } // kill any projectiles as well since they have pointers to the monster that created them KillEntities( args, idProjectile::Type ); + + gameLocal.Printf( "Butchered All Monsters\n" ); } /* @@ -276,6 +721,47 @@ void Cmd_KillRagdolls_f( const idCmdArgs &args ) { KillEntities( args, idAFEntity_WithAttachedHead::Type ); } +// Hexen : Zeroth +void Cmd_Indiana_f( const idCmdArgs &args ) { + idStr art[15]; + int i=0; + idDict iargs; + + idPlayer * player = gameLocal.GetLocalPlayer(); + if ( !player ) { + return; + } + + art[i++] = "artifact_banish" ; + art[i++] = "artifact_boots" ; + art[i++] = "artifact_bracers" ; + art[i++] = "artifact_chaos" ; + art[i++] = "artifact_defender" ; + art[i++] = "artifact_disc" ; + art[i++] = "artifact_flask" ; + art[i++] = "artifact_flechette" ; + art[i++] = "artifact_krater" ; +//notineocalpha art[i++] = "artifact_pork" ; +//notineocalpha art[i++] = "artifact_servant" ; + art[i++] = "artifact_tome" ; + art[i++] = "artifact_torch" ; + art[i++] = "artifact_urn" ; + + for ( int a=0; a < 15; a++ ) { + idDict artifact( *gameLocal.FindEntityDefDict( art[a] ) ); + int max = gameLocal.FindEntityDefDict( art[a] )->GetInt( "max_inventory" ); + max -= gameLocal.GetLocalPlayer()->InventoryItemQty( artifact.GetString("inv_name") ); + + for ( i=0; iGetName() ); + iargs.Set( "snd_acquire", "" ); + iargs.Set( "dontNotifyOnPickup", "1" ); + gameLocal.SpawnEntityDef( iargs ); + } + } +} + /* ================== Cmd_Give_f @@ -313,11 +799,18 @@ void Cmd_Give_f( const idCmdArgs &args ) { } } - if ( ( idStr::Cmpn( name, "weapon_", 7 ) == 0 ) || ( idStr::Cmpn( name, "item_", 5 ) == 0 ) || ( idStr::Cmpn( name, "ammo_", 5 ) == 0 ) ) { + // HEXEN : Zeroth + if ( ( idStr::Cmpn( name, "weapon_", 7 ) == 0 ) || idStr::Cmpn( name, "artifact_", 9 ) == 0 || ( idStr::Cmpn( name, "item_", 5 ) == 0 ) || ( idStr::Cmpn( name, "ammo_", 5 ) == 0 ) ) { player->GiveItem( name ); return; } + // HEXEN : Zeroth + if ( give_all ) { + const idCmdArgs blah; + Cmd_Indiana_f( blah ); + } + if ( give_all || idStr::Icmp( name, "health" ) == 0 ) { player->health = player->inventory.maxHealth; if ( !give_all ) { @@ -375,6 +868,49 @@ void Cmd_Give_f( const idCmdArgs &args ) { } } +// Hexen : Zeroth +void Cmd_Shazam_f( const idCmdArgs &args ) { + if ( gameLocal.GetLocalPlayer()->PowerTome ) { + gameLocal.GetLocalPlayer()->PowerTome = false; + } else { + gameLocal.GetLocalPlayer()->PowerTome = true; + } +} + +void Cmd_Rambo_f( const idCmdArgs &args ) { + idPlayer *player; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk() ) { + return; + } + + player->inventory.weapons = BIT( MAX_WEAPONS ) - 1; + player->CacheWeapons(); + + for ( int i = 0 ; i < AMMO_NUMTYPES; i++ ) { + player->inventory.ammo[ i ] = player->inventory.MaxAmmoForAmmoClass( player, idWeapon::GetAmmoNameForNum( ( ammo_t )i ) ); + } +} + +void Cmd_NRA_f( const idCmdArgs &args ) { + idPlayer *player; + + player = gameLocal.GetLocalPlayer(); + if ( !player || !gameLocal.CheatsOk() ) { + return; + } + + player->inventory.weapons = BIT( MAX_WEAPONS ) - 1; + player->CacheWeapons(); + + for ( int i = 0 ; i < AMMO_NUMTYPES; i++ ) { + player->inventory.ammo[ i ] = player->inventory.MaxAmmoForAmmoClass( player, idWeapon::GetAmmoNameForNum( ( ammo_t )i ) ); + } + + player->inventory.armor = player->inventory.maxarmor; +} + /* ================== Cmd_CenterView_f @@ -416,13 +952,13 @@ void Cmd_God_f( const idCmdArgs &args ) { if ( player->godmode ) { player->godmode = false; - msg = "godmode OFF\n"; + msg = "OFF\n"; } else { player->godmode = true; - msg = "godmode ON\n"; + msg = "ON\n"; } - gameLocal.Printf( "%s", msg ); + gameLocal.Printf( "Satan-Mode %s", msg ); } /* @@ -487,6 +1023,7 @@ Cmd_Kill_f */ void Cmd_Kill_f( const idCmdArgs &args ) { idPlayer *player; + static int times=0; if ( gameLocal.isMultiplayer ) { if ( gameLocal.isClient ) { @@ -505,11 +1042,16 @@ void Cmd_Kill_f( const idCmdArgs &args ) { cmdSystem->BufferCommandText( CMD_EXEC_NOW, va( "say killed client %d '%s^0'\n", player->entityNumber, gameLocal.userInfo[ player->entityNumber ].GetString( "ui_name" ) ) ); } } else { + times++; + if ( times != 3 ) { + return; + } player = gameLocal.GetLocalPlayer(); if ( !player ) { return; } player->Kill( false, false ); + gameLocal.Printf("Butchered Self."); } } @@ -841,7 +1383,7 @@ void Cmd_Damage_f( const idCmdArgs &args ) { return; } - ent->Damage( gameLocal.world, gameLocal.world, idVec3( 0, 0, 1 ), "damage_moverCrush", atoi( args.Argv( 2 ) ), INVALID_JOINT ); + ent->Damage( gameLocal.world, gameLocal.world, idVec3( 0, 0, 1 ), "damage_moverCrush", atoi( args.Argv( 2 ) ), INVALID_JOINT, idVec3(0,0,0) ); } @@ -1509,7 +2051,7 @@ static void Cmd_TestDamage_f( const idCmdArgs &args ) { // give the player full health before and after // running the damage player->health = player->inventory.maxHealth; - player->Damage( NULL, NULL, dir, damageDefName, 1.0f, INVALID_JOINT ); + player->Damage( NULL, NULL, dir, damageDefName, 1.0f, INVALID_JOINT, idVec3(0,0,0) ); player->health = player->inventory.maxHealth; } @@ -1556,7 +2098,7 @@ static void Cmd_TestDeath_f( const idCmdArgs &args ) { dir[2] = 0; g_testDeath.SetBool( 1 ); - player->Damage( NULL, NULL, dir, "damage_triggerhurt_1000", 1.0f, INVALID_JOINT ); + player->Damage( NULL, NULL, dir, "damage_triggerhurt_1000", 1.0f, INVALID_JOINT, idVec3(0,0,0) ); if ( args.Argc() >= 2) { player->SpawnGibs( dir, "damage_triggerhurt_1000" ); } @@ -2304,6 +2846,66 @@ so it can perform tab completion ================= */ void idGameLocal::InitConsoleCommands( void ) { +// HEXEN : Zeroth - hexen / heretic / modified cheats + cmdSystem->AddCommand( "satan", Cmd_God_f, CMD_FL_GAME|CMD_FL_CHEAT, "enables god mode" ); + cmdSystem->AddCommand( "quicken", Cmd_God_f, CMD_FL_GAME|CMD_FL_CHEAT, "enables god mode" ); + cmdSystem->AddCommand( "indiana", Cmd_Indiana_f, CMD_FL_GAME|CMD_FL_CHEAT, "give max of each artifact" ); + cmdSystem->AddCommand( "gimme", Cmd_Give_f, CMD_FL_GAME|CMD_FL_CHEAT, "gives one or more items" ); + cmdSystem->AddCommand( "rambo", Cmd_Rambo_f, CMD_FL_GAME|CMD_FL_CHEAT, "give all weapons, full mana" ); + cmdSystem->AddCommand( "nra", Cmd_NRA_f, CMD_FL_GAME|CMD_FL_CHEAT, "give all weapons, full mana, full armor" ); + cmdSystem->AddCommand( "clubmed", Cmd_FullHealth_f, CMD_FL_GAME|CMD_FL_CHEAT, "gives full health" ); + cmdSystem->AddCommand( "ponce", Cmd_FullHealth_f, CMD_FL_GAME|CMD_FL_CHEAT, "gives full health" ); + cmdSystem->AddCommand( "conan", Cmd_Conan_f, CMD_FL_GAME|CMD_FL_CHEAT, "remove all but main weapon" ); + cmdSystem->AddCommand( "casper", Cmd_Noclip_f, CMD_FL_GAME|CMD_FL_CHEAT, "disables collision detection for the player" ); + cmdSystem->AddCommand( "kitty", Cmd_Noclip_f, CMD_FL_GAME|CMD_FL_CHEAT, "disables collision detection for the player" ); + cmdSystem->AddCommand( "butcher", Cmd_KillMonsters_f, CMD_FL_GAME|CMD_FL_CHEAT, "kills all monsters" ); + cmdSystem->AddCommand( "massacre", Cmd_KillMonsters_f, CMD_FL_GAME|CMD_FL_CHEAT, "kills all monsters" ); +//notineocalpha cmdSystem->AddCommand( "init", Cmd_MapRestart_f, CMD_FL_GAME|CMD_FL_CHEAT, "restart local map (MIGHT CRASH!)" ); + cmdSystem->AddCommand( "visit", Cmd_Visit_f, CMD_FL_GAME|CMD_FL_CHEAT, "change map e#h#m#" ); + cmdSystem->AddCommand( "engage", Cmd_Visit_f, CMD_FL_GAME|CMD_FL_CHEAT, "change map e#h#m#" ); + cmdSystem->AddCommand( "shazam", Cmd_Shazam_f, CMD_FL_GAME|CMD_FL_CHEAT, "toggle power mode" ); + cmdSystem->AddCommand( "spawnat", Cmd_SpawnAt_f, CMD_FL_GAME|CMD_FL_CHEAT, "set spawn point" ); + cmdSystem->AddCommand( "where", Cmd_Where_f, CMD_FL_GAME, "Display coordinates" ); +//notineocalpha cmdSystem->AddCommand( "ticker", Cmd_Ticker_f, CMD_FL_GAME, "Display frame rate" ); + + // naughty things + cmdSystem->AddCommand( "martek", Cmd_Kill_f, CMD_FL_GAME|CMD_FL_CHEAT, "kills you" ); + cmdSystem->AddCommand( "iddqd", Cmd_iddqd_f, CMD_FL_GAME|CMD_FL_CHEAT, "kills you" ); + cmdSystem->AddCommand( "god", Cmd_iddqd_f, CMD_FL_GAME|CMD_FL_CHEAT, "kills you" ); + cmdSystem->AddCommand( "idkfa", Cmd_idkfa_f, CMD_FL_GAME|CMD_FL_CHEAT, "removes all weapons and items" ); + cmdSystem->AddCommand( "give", Cmd_idkfa_f, CMD_FL_GAME|CMD_FL_CHEAT, "removes all weapons and items" ); + + // what? + //cmdSystem->AddCommand( "albouy", Cmd_Albouy_f, CMD_FL_GAME, "Answer" ); + //cmdSystem->AddCommand( "arnold", Cmd_Arnold_f, CMD_FL_GAME, "Proposal" ); + + // do later + //cmdSystem->AddCommand( "shadowcaster", Cmd_Class_f, CMD_FL_GAME|CMD_FL_CHEAT, "Change character: shadowcaster #" ); + //cmdSystem->AddCommand( "deliverance", Cmd_Deliverance_f, CMD_FL_GAME|CMD_FL_CHEAT, "pig mode" ); + //cmdSystem->AddCommand( "locksmith", Cmd_Locksmith_f, CMD_FL_GAME|CMD_FL_CHEAT, "All keys" ); + //cmdSystem->AddCommand( "mapsco", Cmd_Mapsco_f, CMD_FL_GAME|CMD_FL_CHEAT, "full map" ); + //cmdSystem->AddCommand( "ravskel", Cmd_RavSkel_f, CMD_FL_GAME|CMD_FL_CHEAT, "obtain all skeleton keys" ); + //cmdSystem->AddCommand( "cockadoodledoo", Cmd_Chicken_f, CMD_FL_GAME|CMD_FL_CHEAT, "chicken mode" ); + //cmdSystem->AddCommand( "engage41", Cmd_Engage41_f, CMD_FL_GAME|CMD_FL_CHEAT, "deathmatch level" ); + //cmdSystem->AddCommand( "idmus", Cmd_idMus_f, CMD_FL_GAME, "Select music track: idmus<01-31>" ); + //cmdSystem->AddCommand( "sherlock", Cmd_Sherlock_f, CMD_FL_GAME|CMD_FL_CHEAT, "All puzzle pieces" ); + +// ORBIT : Zeroth + cmdSystem->AddCommand( "tommy", Cmd_WallWalk_f, CMD_FL_GAME|CMD_FL_CHEAT, "is it easter?" ); + +// HEXEN : Zeroth: EoC Settings Menu + cmdSystem->AddCommand( "eoc_vid_restart", Cmd_VidRestart_f, CMD_FL_RENDERER, "" ); + cmdSystem->AddCommand( "eoc_guiUpdateSettings", Cmd_guiUpdateSettings_f, CMD_FL_SYSTEM, "used by the mainmenu GUI. determine which parts of the settings GUI should be visible and display the proper settings on it." ); + +// HEXEN : Zeroth + cmdSystem->AddCommand( "eoc_beaten", Cmd_Beaten_f, CMD_FL_GAME|CMD_FL_CHEAT, "used by the game to signify that you have completed this game release." ); + cmdSystem->AddCommand( "hexinfo", Cmd_Hexinfo_f, CMD_FL_SYSTEM, "print information about this release." ); + cmdSystem->AddCommand( "toggleFog", Cmd_ToggleFog_f, CMD_FL_GAME, "toggles fog." ); + cmdSystem->AddCommand( "updateFog", Cmd_UpdateFog_f, CMD_FL_GAME, "toggles fog." ); +//notineocalpha cmdSystem->AddCommand( "setClass", Cmd_SetClass_f, CMD_FL_GAME|CMD_FL_CHEAT, "set class, 0=cleric, 1=mage, 2=fighter." ); + cmdSystem->AddCommand( "clearPersistentLevelInfo", Cmd_ClearPLI_f, CMD_FL_GAME|CMD_FL_CHEAT, "clear info about levels already visited." ); + cmdSystem->AddCommand( "newGame", Cmd_NewGame_f, CMD_FL_GAME, "clear info about levels already visited." ); + cmdSystem->AddCommand( "listTypeInfo", ListTypeInfo_f, CMD_FL_GAME, "list type info" ); cmdSystem->AddCommand( "writeGameState", WriteGameState_f, CMD_FL_GAME, "write game state" ); cmdSystem->AddCommand( "testSaveGame", TestSaveGame_f, CMD_FL_GAME|CMD_FL_CHEAT, "test a save game for a level" ); @@ -2318,12 +2920,8 @@ void idGameLocal::InitConsoleCommands( void ) { cmdSystem->AddCommand( "sayTeam", Cmd_SayTeam_f, CMD_FL_GAME, "team text chat" ); cmdSystem->AddCommand( "addChatLine", Cmd_AddChatLine_f, CMD_FL_GAME, "internal use - core to game chat lines" ); cmdSystem->AddCommand( "gameKick", Cmd_Kick_f, CMD_FL_GAME, "same as kick, but recognizes player names" ); - cmdSystem->AddCommand( "give", Cmd_Give_f, CMD_FL_GAME|CMD_FL_CHEAT, "gives one or more items" ); cmdSystem->AddCommand( "centerview", Cmd_CenterView_f, CMD_FL_GAME, "centers the view" ); - cmdSystem->AddCommand( "god", Cmd_God_f, CMD_FL_GAME|CMD_FL_CHEAT, "enables god mode" ); cmdSystem->AddCommand( "notarget", Cmd_Notarget_f, CMD_FL_GAME|CMD_FL_CHEAT, "disables the player as a target" ); - cmdSystem->AddCommand( "noclip", Cmd_Noclip_f, CMD_FL_GAME|CMD_FL_CHEAT, "disables collision detection for the player" ); - cmdSystem->AddCommand( "kill", Cmd_Kill_f, CMD_FL_GAME, "kills the player" ); cmdSystem->AddCommand( "where", Cmd_GetViewpos_f, CMD_FL_GAME|CMD_FL_CHEAT, "prints the current view position" ); cmdSystem->AddCommand( "getviewpos", Cmd_GetViewpos_f, CMD_FL_GAME|CMD_FL_CHEAT, "prints the current view position" ); cmdSystem->AddCommand( "setviewpos", Cmd_SetViewpos_f, CMD_FL_GAME|CMD_FL_CHEAT, "sets the current view position" ); @@ -2332,7 +2930,6 @@ void idGameLocal::InitConsoleCommands( void ) { cmdSystem->AddCommand( "spawn", Cmd_Spawn_f, CMD_FL_GAME|CMD_FL_CHEAT, "spawns a game entity", idCmdSystem::ArgCompletion_Decl ); cmdSystem->AddCommand( "damage", Cmd_Damage_f, CMD_FL_GAME|CMD_FL_CHEAT, "apply damage to an entity", idGameLocal::ArgCompletion_EntityName ); cmdSystem->AddCommand( "remove", Cmd_Remove_f, CMD_FL_GAME|CMD_FL_CHEAT, "removes an entity", idGameLocal::ArgCompletion_EntityName ); - cmdSystem->AddCommand( "killMonsters", Cmd_KillMonsters_f, CMD_FL_GAME|CMD_FL_CHEAT, "removes all monsters" ); cmdSystem->AddCommand( "killMoveables", Cmd_KillMovables_f, CMD_FL_GAME|CMD_FL_CHEAT, "removes all moveables" ); cmdSystem->AddCommand( "killRagdolls", Cmd_KillRagdolls_f, CMD_FL_GAME|CMD_FL_CHEAT, "removes all ragdolls" ); cmdSystem->AddCommand( "addline", Cmd_AddDebugLine_f, CMD_FL_GAME|CMD_FL_CHEAT, "adds a debug line" ); diff --git a/game/gamesys/SysCvar.cpp b/game/gamesys/SysCvar.cpp index b19fba1d..6a311813 100644 --- a/game/gamesys/SysCvar.cpp +++ b/game/gamesys/SysCvar.cpp @@ -123,15 +123,12 @@ idCVar g_debugTriggers( "g_debugTriggers", "0", CVAR_GAME | CVAR_BOOL, "" idCVar g_debugCinematic( "g_debugCinematic", "0", CVAR_GAME | CVAR_BOOL, "" ); idCVar g_stopTime( "g_stopTime", "0", CVAR_GAME | CVAR_BOOL, "" ); idCVar g_damageScale( "g_damageScale", "1", CVAR_GAME | CVAR_FLOAT | CVAR_ARCHIVE, "scale final damage on player by this factor" ); -idCVar g_armorProtection( "g_armorProtection", "0.3", CVAR_GAME | CVAR_FLOAT | CVAR_ARCHIVE, "armor takes this percentage of damage" ); -idCVar g_armorProtectionMP( "g_armorProtectionMP", "0.6", CVAR_GAME | CVAR_FLOAT | CVAR_ARCHIVE, "armor takes this percentage of damage in mp" ); +idCVar g_armorProtection( "g_armorProtection", "1", CVAR_GAME | CVAR_FLOAT | CVAR_ARCHIVE, "armor takes this percentage of damage" ); // HEXEN : Zeroth - 0.3 to 1 +idCVar g_armorProtectionMP( "g_armorProtectionMP", "1", CVAR_GAME | CVAR_FLOAT | CVAR_ARCHIVE, "armor takes this percentage of damage in mp" ); // HEXEN : Zeroth - 0.6 to 1 idCVar g_useDynamicProtection( "g_useDynamicProtection", "1", CVAR_GAME | CVAR_BOOL | CVAR_ARCHIVE, "scale damage and armor dynamically to keep the player alive more often" ); idCVar g_healthTakeTime( "g_healthTakeTime", "5", CVAR_GAME | CVAR_INTEGER | CVAR_ARCHIVE, "how often to take health in nightmare mode" ); idCVar g_healthTakeAmt( "g_healthTakeAmt", "5", CVAR_GAME | CVAR_INTEGER | CVAR_ARCHIVE, "how much health to take in nightmare mode" ); idCVar g_healthTakeLimit( "g_healthTakeLimit", "25", CVAR_GAME | CVAR_INTEGER | CVAR_ARCHIVE, "how low can health get taken in nightmare mode" ); - - - idCVar g_showPVS( "g_showPVS", "0", CVAR_GAME | CVAR_INTEGER, "", 0, 2 ); idCVar g_showTargets( "g_showTargets", "0", CVAR_GAME | CVAR_BOOL, "draws entities and their targets. hidden entities are drawn grey." ); idCVar g_showTriggers( "g_showTriggers", "0", CVAR_GAME | CVAR_BOOL, "draws trigger entities (orange) and their targets (green). disabled triggers are drawn grey." ); @@ -149,6 +146,7 @@ idCVar g_showEnemies( "g_showEnemies", "0", CVAR_GAME | CVAR_BOOL, "draws idCVar g_frametime( "g_frametime", "0", CVAR_GAME | CVAR_BOOL, "displays timing information for each game frame" ); idCVar g_timeentities( "g_timeEntities", "0", CVAR_GAME | CVAR_FLOAT, "when non-zero, shows entities whose think functions exceeded the # of milliseconds specified" ); +idCVar g_enablePortalSky( "g_enablePortalSky", "1", CVAR_GAME | CVAR_BOOL, "enables the portal sky" ); // HEXEN : Zeroth idCVar ai_debugScript( "ai_debugScript", "-1", CVAR_GAME | CVAR_INTEGER, "displays script calls for the specified monster entity number" ); idCVar ai_debugMove( "ai_debugMove", "0", CVAR_GAME | CVAR_BOOL, "draws movement information for monsters" ); idCVar ai_debugTrajectory( "ai_debugTrajectory", "0", CVAR_GAME | CVAR_BOOL, "draws trajectory tests for monsters" ); @@ -279,7 +277,7 @@ idCVar g_gun_y( "g_gunY", "0", CVAR_GAME | CVAR_FLOAT, "" ); idCVar g_gun_z( "g_gunZ", "0", CVAR_GAME | CVAR_FLOAT, "" ); idCVar g_viewNodalX( "g_viewNodalX", "0", CVAR_GAME | CVAR_FLOAT, "" ); idCVar g_viewNodalZ( "g_viewNodalZ", "0", CVAR_GAME | CVAR_FLOAT, "" ); -idCVar g_fov( "g_fov", "90", CVAR_GAME | CVAR_INTEGER | CVAR_NOCHEAT, "" ); +idCVar g_fov( "g_fov", "90", CVAR_GAME | CVAR_ARCHIVE | CVAR_INTEGER | CVAR_NOCHEAT, "" ); idCVar g_skipViewEffects( "g_skipViewEffects", "0", CVAR_GAME | CVAR_BOOL, "skip damage and other view effects" ); idCVar g_mpWeaponAngleScale( "g_mpWeaponAngleScale", "0", CVAR_GAME | CVAR_FLOAT, "Control the weapon sway in MP" ); @@ -335,3 +333,21 @@ idCVar mod_validSkins( "mod_validSkins", "skins/characters/player/marine_mp idCVar net_serverDownload( "net_serverDownload", "0", CVAR_GAME | CVAR_INTEGER | CVAR_ARCHIVE, "enable server download redirects. 0: off 1: redirect to si_serverURL 2: use builtin download. see net_serverDl cvars for configuration" ); idCVar net_serverDlBaseURL( "net_serverDlBaseURL", "", CVAR_GAME | CVAR_ARCHIVE, "base URL for the download redirection" ); idCVar net_serverDlTable( "net_serverDlTable", "", CVAR_GAME | CVAR_ARCHIVE, "pak names for which download is provided, separated by ;" ); + +// HEXEN : Zeroth +// ****** thanks SnoopJeDi ( http://www.doom3world.org/phpbb2/viewtopic.php?f=56&t=12469&p=214427#p214427 ) +idCVar s_music_vol( "s_music_vol", "0", CVAR_GAME | CVAR_FLOAT | CVAR_ARCHIVE , "the volume in dB of all speakers with the s_music key set" ); +// ****** + +// HEXEN : Zeroth +idCVar pm_flybob( "pm_flybob", "0.1", CVAR_GAME | CVAR_NETWORKSYNC | CVAR_FLOAT| CVAR_NOCHEAT, "bob much slower when flying" ); +idCVar g_noHudAutoHide( "g_noHudAutohide", "0", CVAR_GAME | CVAR_BOOL | CVAR_ARCHIVE| CVAR_NOCHEAT, "don't auto-hide hud" ); +idCVar g_noArtifactDescriptions( "g_noArtifactDescriptions", "0", CVAR_GAME | CVAR_BOOL | CVAR_ARCHIVE| CVAR_NOCHEAT, "when set, shows breif descriptions on hud of artifact when selected." ); +idCVar r_vmode( "r_vmode", "0", CVAR_GAME | CVAR_FLOAT | CVAR_ARCHIVE| CVAR_NOCHEAT, "resolution and fov presets" ); +idCVar r_dofDelay( "r_dofDelay", "0.1", CVAR_GAME | CVAR_FLOAT | CVAR_ARCHIVE| CVAR_NOCHEAT, "How long it takes before stuff comes into focus. The higher the number, the quicker dof responds, 0 = instantaneous, 5 = very slow." ); +idCVar r_dofRange( "r_dofRange", "10000", CVAR_GAME | CVAR_FLOAT | CVAR_ARCHIVE| CVAR_NOCHEAT, "How far the scan range is for dof." ); +idCVar r_dof( "r_dof", "0", CVAR_GAME | CVAR_FLOAT | CVAR_ARCHIVE| CVAR_NOCHEAT, "whether Depth of Field is on or not." ); +idCVar r_fog( "r_fog", "0", CVAR_GAME | CVAR_BOOL | CVAR_ARCHIVE| CVAR_NOCHEAT, "whether Fog is on or not." ); +idCVar g_hellions( "g_hellions", "0", CVAR_GAME | CVAR_BOOL | CVAR_ARCHIVE, "whether hellions spawn if player camps." ); +idCVar g_crosshairVis( "g_crosshairVis", "1", CVAR_ARCHIVE | CVAR_GAME | CVAR_BOOL | CVAR_NOCHEAT, "whether crosshair is visible." ); +idCVar g_noPickupNotification( "g_noPickupNotification", "0", CVAR_GAME | CVAR_BOOL | CVAR_ARCHIVE, "Set to 1 to disable artifact pickup messages and sounds." ); diff --git a/game/gamesys/SysCvar.h b/game/gamesys/SysCvar.h index da8e26ee..6b7ac103 100644 --- a/game/gamesys/SysCvar.h +++ b/game/gamesys/SysCvar.h @@ -118,6 +118,7 @@ extern idCVar g_vehicleSuspensionDown; extern idCVar g_vehicleSuspensionKCompress; extern idCVar g_vehicleSuspensionDamping; extern idCVar g_vehicleTireFriction; +extern idCVar g_enablePortalSky; //zeorth404 extern idCVar ik_enable; extern idCVar ik_debug; @@ -250,8 +251,24 @@ extern idCVar si_spectators; extern idCVar net_clientSelfSmoothing; extern idCVar net_clientLagOMeter; +// HEXEN : Zeroth +extern idCVar g_noHudAutoHide; +extern idCVar g_noArtifactDescriptions; +extern idCVar pm_flybob; +extern idCVar g_noPickupNotification; +extern idCVar r_vmode; +extern idCVar r_dofDelay; +extern idCVar r_dofRange; +extern idCVar r_dof; +extern idCVar r_fog; + extern const char *si_gameTypeArgs[]; extern const char *ui_skinArgs[]; +// HEXEN : Zeroth +// ****** thanks SnoopJeDi ( http://www.doom3world.org/phpbb2/viewtopic.php?f=56&t=12469&p=214427#p214427 ) +extern idCVar s_music_vol; +// ****** + #endif /* !__SYS_CVAR_H__ */ diff --git a/game/objects/ChaosDeviceModel.cpp b/game/objects/ChaosDeviceModel.cpp new file mode 100644 index 00000000..12931efa --- /dev/null +++ b/game/objects/ChaosDeviceModel.cpp @@ -0,0 +1,15 @@ + +#include "Game_local.h" + +CLASS_DECLARATION( idEntity, idEntity_MultiModel ) + +END_CLASS + +void idEntity_MultiModel::Spawn() { + +} + +void idEntity_MultiModel::Think() { + idEntity::Think(); + +} diff --git a/game/objects/ChaosDeviceModel.h b/game/objects/ChaosDeviceModel.h new file mode 100644 index 00000000..94d0d33d --- /dev/null +++ b/game/objects/ChaosDeviceModel.h @@ -0,0 +1,14 @@ +#ifndef __CHAOSDEVICEMODEL_H__ +#define __CHAOSDEVICEMODEL_H__ + +class idEntity_ChaosDeviceModel : idEntity { +public: + CLASS_PROTOTYPE( idEntity_ChaosDeviceModel ); + void Spawn(); + void Think(); + +private: + +}; + +#endif // __CHAOSDEVICEMODEL_H__ \ No newline at end of file diff --git a/game/objects/Leaf.cpp b/game/objects/Leaf.cpp new file mode 100644 index 00000000..e0c18346 --- /dev/null +++ b/game/objects/Leaf.cpp @@ -0,0 +1,85 @@ + +#include "Entity.h" +#include "Moveable.h" +#include "Leaf.h" + +CLASS_DECLARATION( idMoveable, idEntity_Leaf ) + +END_CLASS + +void idEntity_Leaf::Spawn() { + liveTime = spawnArgs.GetFloat("leaf_liveTime"); // in seconds. Keep this low to prevent too many leaves in the map at a time. each leaf is an entity! Try to make each leaf live at least until it touches the ground. Some leaves will slide along the ground if they live long enough. default: 8 + moveSpeed = spawnArgs.GetFloat("leaf_moveSpeed"); // from 1-10. How fast the leaves move regardless of wind. Keep this high. default: 10 + spread = spawnArgs.GetFloat("leaf_spread"); // from 1-10. Affects how far apart leafs speread apart when falling. default: 10 + windPower = spawnArgs.GetFloat("leaf_windPower"); // from 1-10. How strong the wind is on the leaves. default: 1 + windDir = spawnArgs.GetVector("leaf_windDir"); // Wind direction. default: '0 1 0' + gravity = spawnArgs.GetVector("leaf_gravity"); // Gravity of the leaf. Keep this pretty low, or accept the default. default: '0 0 -20' + origin = spawnArgs.GetVector("leaf_origin"); + ang = GetAngles(); + dieTime = gameLocal.time + liveTime * 1000; + + if ( moveSpeed == 0 ) { + moveSpeed = 1; + } + + if ( origin != idVec3(0,0,0) ) { + SetOrigin(origin); + } + + spread = spread / 20; //max spread is 0.5 + + dir = gameLocal.random.RandomInt(3); + if (dir < 1) { spreadX = spread; } + else if (dir < 2 && dir > 1 ) { spreadX = 0; } + else { spreadX = -spread; } + + dir = gameLocal.random.RandomInt(3); + if (dir < 1) { spreadY = spread; } + else if (dir < 2 && dir > 1 ) { spreadY = 0; } + else { spreadY = -spread; } + + SetAngles( idAngles(gameLocal.random.CRandomFloat()*360, gameLocal.random.CRandomFloat()*360, gameLocal.random.CRandomFloat()*360) ); + GetPhysics()->SetLinearVelocity( idVec3(0,0,0) ); + GetPhysics()->SetGravity( idVec3(0,0,0) ); //gravity - if we set zero gravity, the leaves stop when they hit the ground. dont know why, but it's cool! + + curVel = GetPhysics()->GetLinearVelocity(); + + nextAngles = 0; +} + +void idEntity_Leaf::Think() { + idEntity::Think(); + + if ( gameLocal.time >= dieTime ) { + PostEventMS( &EV_Remove, 0.0 ); + } + + if ( gameLocal.random.RandomInt(100) > 33) { + return; + } + + curDir = GetPhysics()->GetLinearVelocity(); + curDir.Normalize(); + + curDir.x = curDir.x + gameLocal.random.RandomFloat()-0.5 + windDir.x*(windPower/14); + curDir.y = curDir.y + gameLocal.random.RandomFloat()-0.5 + windDir.y*(windPower/14); + curDir.z = curDir.z + gameLocal.random.RandomFloat()-0.5 + windDir.z*(windPower/14); + + // more spread + curDir.x = curDir.x + spreadX; + curDir.y = curDir.y + spreadY; + + curVel = curDir * moveSpeed * (windPower+1) + gravity; + GetPhysics()->SetLinearVelocity( curVel ); + + if ( gameLocal.time > nextAngles ) { + angles.x = gameLocal.random.CRandomFloat(); + angles.y = gameLocal.random.CRandomFloat(); + angles.z = gameLocal.random.CRandomFloat(); + angles.Normalize(); + angles *= 180 * (windPower / 10); + GetPhysics()->SetAngularVelocity( angles ); + nextAngles = gameLocal.time + 500 + gameLocal.random.RandomFloat() * 3000; + } +} + diff --git a/game/objects/Leaf.h b/game/objects/Leaf.h new file mode 100644 index 00000000..74157384 --- /dev/null +++ b/game/objects/Leaf.h @@ -0,0 +1,29 @@ +#ifndef __LEAF_H__ +#define __LEAF_H__ + +class idEntity_Leaf : idMoveable { +public: + CLASS_PROTOTYPE( idEntity_Leaf ); + void Spawn(); + void Think(); + +private: + float liveTime; + float moveSpeed; + float spread; + float windPower; + idVec3 windDir; + idVec3 gravity; + idVec3 origin; + idVec3 curDir; + idVec3 angles; + idVec3 curVel; + idAngles ang; + float spreadX; + float spreadY; + float dir; + float dieTime; + float nextAngles; +}; + +#endif // __LEAF_H__ \ No newline at end of file diff --git a/game/objects/LeafEmitter.cpp b/game/objects/LeafEmitter.cpp new file mode 100644 index 00000000..e2098d16 --- /dev/null +++ b/game/objects/LeafEmitter.cpp @@ -0,0 +1,41 @@ + + +#include "Entity.h" +#include "Game_local.h" +#include "LeafEmitter.h" + +CLASS_DECLARATION( idEntity, idEntity_LeafEmitter ) + +END_CLASS + +void idEntity_LeafEmitter::Spawn() { + nextLeaf=0; + interval=spawnArgs.GetFloat("emitter_leaf_interval"); + maxLeaf=spawnArgs.GetFloat("emitter_leaf_max"); + + leaf.SetFloat("leaf_liveTime", spawnArgs.GetFloat("leaf_liveTime")); + leaf.SetFloat("leaf_spread", spawnArgs.GetFloat("leaf_spread")); + leaf.SetFloat("leaf_windPower", spawnArgs.GetFloat("leaf_windPower")); + leaf.SetVector("leaf_windDir", spawnArgs.GetVector("leaf_windDir")); + leaf.SetFloat("leaf_moveSpeed", spawnArgs.GetFloat("leaf_moveSpeed")); + leaf.SetVector("leaf_gravity", spawnArgs.GetVector("leaf_gravity")); + leaf.SetFloat("leaf_maxSpinSpeed", spawnArgs.GetFloat("leaf_maxSpinSpeed")); + leaf.SetVector("leaf_origin", GetPhysics()->GetOrigin()); +} + +void idEntity_LeafEmitter::Think() { + if ( ( gameLocal.time / 1000 ) > nextLeaf) { + + float i = gameLocal.random.RandomFloat() * 4; + + if (gameLocal.random.RandomInt(1) > 1) { + leaf.Set("classname", "object_leaf_lg"); + } else { + leaf.Set("classname", "object_leaf_sm"); + } + + gameLocal.SpawnEntityDef( leaf ); + + nextLeaf = ( gameLocal.time / 1000 ) + interval; + } +} diff --git a/game/objects/LeafEmitter.h b/game/objects/LeafEmitter.h new file mode 100644 index 00000000..7513b3e5 --- /dev/null +++ b/game/objects/LeafEmitter.h @@ -0,0 +1,17 @@ +#ifndef __LEAFEMITTER_H__ +#define __LEAFEMITTER_H__ + +class idEntity_LeafEmitter : idEntity { +public: + CLASS_PROTOTYPE( idEntity_LeafEmitter ); + void Spawn(); + void Think(); + +private: + float nextLeaf; + float interval; + float maxLeaf; + idDict leaf; +}; + +#endif // __LEAFEMITTER_H__ \ No newline at end of file diff --git a/game/objects/Tree.cpp b/game/objects/Tree.cpp new file mode 100644 index 00000000..766ca311 --- /dev/null +++ b/game/objects/Tree.cpp @@ -0,0 +1,76 @@ + +#include "Entity.h" +#include "Misc.h" +#include "Tree.h" + +CLASS_DECLARATION( idAnimated, idAnimated_Tree ) + +END_CLASS + +void idAnimated_Tree::Spawn() { + // randomize tree branch positions + RandomizeJoints(); + + // set up swaying + idVec3 treeDim = GetModelDims(); + normAngle = idAngles(0,0,0); + windSpeed = spawnArgs.GetFloat( "wind_speed" ); + maxAngle = RAD2DEG( idMath::ATan( OBJECT_TREE_MAX_SWAY_DIST / treeDim.z ) ); + float useAngle = maxAngle * ( windSpeed / OBJECT_TREE_MAX_WIND_SPEED ); + swayAngle = spawnArgs.GetAngles( "wind_dir" ); swayAngle *= useAngle; + randSwayRange = useAngle / 8; // an eighth of swayAngle + swayTime = 3000 - ( ( windSpeed / OBJECT_TREE_MAX_WIND_SPEED ) * 2000 ) + 1000; // 1 < swatTime < 3 + transitions = ( swayTime / 1000 ) * OBJECT_TREE_JOINT_ANGLE_TRANSITIONS_PER_SEC; + swayDir = 1; +} + +void idAnimated_Tree::Think( void ) { + idAngles to; + float doSpeed; + float rndTime; + + idAnimated::Think(); + + if ( ( gameLocal.time ) < nextSway ) { + return; + } + + if ( swayDir == 1 ) { + swayDir = -1; + to = swayAngle; + to.yaw = to.yaw + gameLocal.random.RandomInt(randSwayRange*2) - randSwayRange; + to.pitch = to.pitch + gameLocal.random.RandomInt(randSwayRange*2) - randSwayRange; + + // only a forward sway should have decreased time per transition (wind gusts) + rndTime = gameLocal.random.RandomFloat() - 0.5; + } else { + swayDir = 1; + float rnd = ( gameLocal.random.RandomFloat() * windSpeed / 2 ) / OBJECT_TREE_MAX_WIND_SPEED ; // the faster the wind, the less likely to make a full backsway (wind gusts) + to = swayAngle * rnd; + + rndTime = gameLocal.random.RandomFloat() / 32; + } + + doSpeed = swayTime + ( rndTime * 1000 ); + + for (int i=0; iGetJointAngle( (jointHandle_t) i ); + SetJointAngle( (jointHandle_t) i , (jointModTransform_t) 1, rndAng + curAng ); + } +} diff --git a/game/objects/Tree.h b/game/objects/Tree.h new file mode 100644 index 00000000..c87315a2 --- /dev/null +++ b/game/objects/Tree.h @@ -0,0 +1,31 @@ +#ifndef __TREE_H__ +#define __TREE_H__ + +const int OBJECT_TREE_PAUSE_TIME = 0; // in ms, pause after each transition // update: this seems to make movement much less life-like, so I Set it to zero. +const int OBJECT_TREE_NUM_JOINTS = 5; +const int OBJECT_TREE_MAX_SWAY_DIST = 32; // in units, the maximum distance trees will sway when winds are strongest +const int OBJECT_TREE_MAX_WIND_SPEED = 10; +const int OBJECT_TREE_JOINT_ANGLE_TRANSITIONS_PER_SEC = 12; // this is about all we're gunna get. the joints don't update quick enough (at least on my system, but I assume it's time-based so it shouldnt matter). regardless, each transition is another calculation. +const int OBJECT_TREE_MAX_RANDOM_JOINT_ANGLE = 2; + +class idAnimated_Tree : idAnimated { +public: + CLASS_PROTOTYPE( idAnimated_Tree ); + void Spawn(); + void Think(); + void RandomizeJoints( void ); + +private: + idAngles normAngle; + idAngles curAngle; + float maxAngle; + idAngles swayAngle; + float swayDir; + float swayTime; + float randSwayRange; + float nextSway; + float transitions; + float windSpeed; +}; + +#endif // __TREE_H__ \ No newline at end of file diff --git a/game/physics/Clip.cpp b/game/physics/Clip.cpp index b2a237be..1b44f80f 100644 --- a/game/physics/Clip.cpp +++ b/game/physics/Clip.cpp @@ -965,7 +965,7 @@ idClip::TestHugeTranslation */ ID_INLINE bool TestHugeTranslation( trace_t &results, const idClipModel *mdl, const idVec3 &start, const idVec3 &end, const idMat3 &trmAxis ) { if ( mdl != NULL && ( end - start ).LengthSqr() > Square( CM_MAX_TRACE_DIST ) ) { - + // assert( 0 ); DG: this was annoying and not really necessary, a Warning should suffice. results.fraction = 0.0f; results.endpos = start; @@ -975,14 +975,13 @@ ID_INLINE bool TestHugeTranslation( trace_t &results, const idClipModel *mdl, co results.c.entityNum = ENTITYNUM_WORLD; if ( mdl->GetEntity() ) { - gameLocal.Printf( "huge translation for clip model %d on entity %d '%s'\n", mdl->GetId(), mdl->GetEntity()->entityNumber, mdl->GetEntity()->GetName() ); + gameLocal.Warning( "huge translation for clip model %d on entity %d '%s'\n", mdl->GetId(), mdl->GetEntity()->entityNumber, mdl->GetEntity()->GetName() ); } else { - gameLocal.Printf( "huge translation for clip model %d\n", mdl->GetId() ); + gameLocal.Warning( "huge translation for clip model %d\n", mdl->GetId() ); } - gameLocal.Printf( " from (%.2f %.2f %.2f) to (%.2f %.2f %.2f)\n", start.x, start.y, start.z, end.x, end.y, end.z); + gameLocal.Warning( " from (%.2f %.2f %.2f) to (%.2f %.2f %.2f)\n", start.x, start.y, start.z, end.x, end.y, end.z); - assert( 0 ); return true; } return false; @@ -1670,3 +1669,9 @@ bool idClip::DrawModelContactFeature( const contactInfo_t &contact, const idClip return true; } + + +// HEXEN : Zeroth +int idClipModel::GetRenderModelHandle( void ) { + return renderModelHandle; +} diff --git a/game/physics/Clip.h b/game/physics/Clip.h index e6f0c205..32d2cd32 100644 --- a/game/physics/Clip.h +++ b/game/physics/Clip.h @@ -113,6 +113,9 @@ class idClipModel { static void SaveTraceModels( idSaveGame *savefile ); static void RestoreTraceModels( idRestoreGame *savefile ); +// HEXEN : Zeroth + int GetRenderModelHandle( void ); + private: bool enabled; // true if this clip model is used for clipping idEntity * entity; // entity using this clip model @@ -277,6 +280,8 @@ class idClip { int contentMask, const idEntity *passEntity ); bool TraceBounds( trace_t &results, const idVec3 &start, const idVec3 &end, const idBounds &bounds, int contentMask, const idEntity *passEntity ); + idVec3 TraceSurfaceNormal( trace_t &results, const idVec3 &A, const idVec3 &B, const float clipMask, const idEntity *pass); + // clip versus a specific model void TranslationModel( trace_t &results, const idVec3 &start, const idVec3 &end, @@ -340,6 +345,19 @@ ID_INLINE bool idClip::TracePoint( trace_t &results, const idVec3 &start, const return ( results.fraction < 1.0f ); } +ID_INLINE idVec3 idClip::TraceSurfaceNormal( trace_t &trace, const idVec3 &A, const idVec3 &B, const float clipMask, const idEntity *pass ) { + TracePoint( trace, A, B, clipMask, pass ); + + // if near a surface + if ( trace.fraction < 1.0f ) { + //idVec3 bub=trace.c.normal; + + return trace.c.normal; + }else{ + return idVec3(0, 0, 0); + } +} + ID_INLINE bool idClip::TraceBounds( trace_t &results, const idVec3 &start, const idVec3 &end, const idBounds &bounds, int contentMask, const idEntity *passEntity ) { temporaryClipModel.LoadModel( idTraceModel( bounds ) ); Translation( results, start, end, &temporaryClipModel, mat3_identity, contentMask, passEntity ); diff --git a/game/physics/Physics.h b/game/physics/Physics.h index 545c6ee2..5b771830 100644 --- a/game/physics/Physics.h +++ b/game/physics/Physics.h @@ -91,6 +91,13 @@ class idPhysics : public idClass { void Save( idSaveGame *savefile ) const; void Restore( idRestoreGame *savefile ); +// HEXEN : Zeroth +public: + idVec3 TransitionFromGravity; + idVec3 TransitionToGravity; + int curTransition; + float nextTransition; + public: // common physics interface // set pointer to entity using physics virtual void SetSelf( idEntity *e ) = 0; diff --git a/game/physics/Physics_AF.cpp b/game/physics/Physics_AF.cpp index 37304807..411aeb21 100644 --- a/game/physics/Physics_AF.cpp +++ b/game/physics/Physics_AF.cpp @@ -6371,11 +6371,12 @@ bool idPhysics_AF::Evaluate( int timeStepMSec, int endTimeMSec ) { AddPushVelocity( current.pushVelocity ); current.pushVelocity.Zero(); - if ( IsOutsideWorld() ) { - gameLocal.Warning( "articulated figure moved outside world bounds for entity '%s' type '%s' at (%s)", - self->name.c_str(), self->GetType()->classname, bodies[0]->current->worldOrigin.ToString(0) ); - Rest(); - } +// HEXEN : Zeroth - for some entities, it is okay to move outside world bounds. would be nice to have a "canMoveOutsideWorld" spawnArg +// if ( IsOutsideWorld() ) { +// gameLocal.Warning( "articulated figure moved outside world bounds for entity '%s' type '%s' at (%s)", +// self->name.c_str(), self->GetType()->classname, bodies[0]->current->worldOrigin.ToString(0) ); +// Rest(); +// } #ifdef AF_TIMINGS timer_total.Stop(); diff --git a/game/physics/Physics_Actor.cpp b/game/physics/Physics_Actor.cpp index 85a45a7f..873f295a 100644 --- a/game/physics/Physics_Actor.cpp +++ b/game/physics/Physics_Actor.cpp @@ -28,6 +28,7 @@ If you have questions concerning this license or the applicable additional terms #include "sys/platform.h" #include "Entity.h" +#include "Player.h" #include "physics/Physics_Actor.h" @@ -275,9 +276,44 @@ idPhysics_Actor::SetGravity ================ */ void idPhysics_Actor::SetGravity( const idVec3 &newGravity ) { - if ( newGravity != gravityVector ) { - idPhysics_Base::SetGravity( newGravity ); - SetClipModelAxis(); + if ( newGravity == gravityVector ) { + return; + } + + idPlayer *player=NULL; + idAngles newVA; + idAngles oldang; + + if ( self->IsType( idPlayer::Type ) ) { + player=static_cast(self); + oldang=player->GetAngles(); + } + + idPhysics_Base::SetGravity( newGravity ); + SetClipModelAxis(); + + if ( player ) { + idAngles mang=player->GetAngles(); + // fix view angles - not perfect, but best I can figure. todo: check out + oldang=mang-oldang; + newVA = player->viewAngles; + + if ( mang.roll > 90 || mang.roll < -90 ) { + newVA.yaw += oldang.yaw; + // newVA.yaw += oldang.yaw * ( idMath::Fabs( mang.roll ) / 180.0f ); + // gameLocal.Printf("up: %f\n", ( idMath::Fabs( mang.roll ) / 180.0f )); + } else { + newVA.yaw -= oldang.yaw; + // newVA.yaw -= oldang.yaw * ( ( 180.0f - idMath::Fabs( mang.roll ) ) / 180.0f ); + // gameLocal.Printf("down %f\n", ( ( 180.0f - idMath::Fabs( mang.roll ) ) / 180.0f ) ); + } + + /* sorta works, but has a jerk now and again + newVA.yaw += oldang.yaw * ( idMath::Fabs( mang.roll ) / 180.0f ); + newVA.yaw -= oldang.yaw * ( ( 180.0f - idMath::Fabs( mang.roll ) ) / 180.0f ); + */ + + player->SetViewAngles(newVA); } } diff --git a/game/physics/Physics_Actor.h b/game/physics/Physics_Actor.h index e50ae17e..4b014aa0 100644 --- a/game/physics/Physics_Actor.h +++ b/game/physics/Physics_Actor.h @@ -94,7 +94,7 @@ class idPhysics_Actor : public idPhysics_Base { void LinkClip( void ); bool EvaluateContacts( void ); - + protected: idClipModel * clipModel; // clip model used for collision detection idMat3 clipModelAxis; // axis of clip model aligned with gravity direction @@ -110,6 +110,12 @@ class idPhysics_Actor : public idPhysics_Base { // results of last evaluate idEntityPtr groundEntityPtr; + +// HEXEN : Zeroth +protected: + bool IsStuckToSurface; + idVec3 SurfaceNormal; + }; #endif /* !__PHYSICS_ACTOR_H__ */ diff --git a/game/physics/Physics_Base.cpp b/game/physics/Physics_Base.cpp index 41b455b5..678b3643 100644 --- a/game/physics/Physics_Base.cpp +++ b/game/physics/Physics_Base.cpp @@ -439,6 +439,11 @@ const idVec3 &idPhysics_Base::GetGravityNormal( void ) const { return gravityNormal; } +// HEXEN : Zeroth +void idPhysics_Base::SetGravityNormal( idVec3 newNormal ) { + gravityNormal = newNormal; +} + /* ================ idPhysics_Base::ClipTranslation @@ -605,6 +610,17 @@ bool idPhysics_Base::HasGroundContacts( void ) const { return false; } +int idPhysics_Base::NumGroundContacts( void ) const { + int i, cnt=0; + + for ( i = 0; i < contacts.Num(); i++ ) { + if ( contacts[i].normal * -gravityNormal > 0.0f ) { + cnt++; + } + } + return cnt; +} + /* ================ idPhysics_Base::IsGroundEntity diff --git a/game/physics/Physics_Base.h b/game/physics/Physics_Base.h index 0d739342..500becd4 100644 --- a/game/physics/Physics_Base.h +++ b/game/physics/Physics_Base.h @@ -108,6 +108,10 @@ class idPhysics_Base : public idPhysics { const idVec3 & GetGravity( void ) const; const idVec3 & GetGravityNormal( void ) const; +// HEXEN : Zeroth +public: + void SetGravityNormal( idVec3 newNormal ); + void ClipTranslation( trace_t &results, const idVec3 &translation, const idClipModel *model ) const; void ClipRotation( trace_t &results, const idRotation &rotation, const idClipModel *model ) const; int ClipContents( const idClipModel *model ) const; @@ -144,6 +148,10 @@ class idPhysics_Base : public idPhysics { void WriteToSnapshot( idBitMsgDelta &msg ) const; void ReadFromSnapshot( const idBitMsgDelta &msg ); +// HEXEN : Zeroth +public: + int NumGroundContacts( void ) const; + protected: idEntity * self; // entity using this physics object int clipMask; // contents the physics object collides with diff --git a/game/physics/Physics_Monster.cpp b/game/physics/Physics_Monster.cpp index 8b74d3a7..4201aaca 100644 --- a/game/physics/Physics_Monster.cpp +++ b/game/physics/Physics_Monster.cpp @@ -509,6 +509,9 @@ bool idPhysics_Monster::Evaluate( int timeStepMSec, int endTimeMSec ) { current.velocity += gravityVector * timeStep; } } else { +// // HEXEN : Zeroth +// eoc_DoStuckOnSurface(); + if ( useVelocityMove ) { delta = current.velocity * timeStep; } else { @@ -535,10 +538,11 @@ bool idPhysics_Monster::Evaluate( int timeStepMSec, int endTimeMSec ) { current.velocity += current.pushVelocity; current.pushVelocity.Zero(); - if ( IsOutsideWorld() ) { - gameLocal.Warning( "clip model outside world bounds for entity '%s' at (%s)", self->name.c_str(), current.origin.ToString(0) ); - Rest(); - } + // HEXEN : Zeroth - sometimes this is okay + //if ( IsOutsideWorld() ) { + // gameLocal.Warning( "clip model outside world bounds for entity '%s' at (%s)", self->name.c_str(), current.origin.ToString(0) ); + // Rest(); + //} return ( current.origin != oldOrigin ); } diff --git a/game/physics/Physics_Player.cpp b/game/physics/Physics_Player.cpp index 82ac8121..c9daf411 100644 --- a/game/physics/Physics_Player.cpp +++ b/game/physics/Physics_Player.cpp @@ -28,6 +28,7 @@ If you have questions concerning this license or the applicable additional terms #include "sys/platform.h" #include "gamesys/SysCvar.h" +#include "Actor.h" #include "Entity.h" #include "physics/Physics_Player.h" @@ -464,7 +465,7 @@ void idPhysics_Player::Friction( void ) { drop = 0; // spectator friction - if ( current.movementType == PM_SPECTATOR ) { + if ( current.movementType == PM_SPECTATOR || current.movementType == PM_FLY ) { drop += speed * PM_FLYFRICTION * frametime; } // apply ground friction @@ -599,6 +600,33 @@ void idPhysics_Player::FlyMove( void ) { idPhysics_Player::SlideMove( false, false, false, false ); } + +void idPhysics_Player::FlyMoveWithCollision( void ) { + idVec3 wishvel; + float wishspeed; + idVec3 wishdir; + float scale; + + // normal slowdown + idPhysics_Player::Friction(); + + scale = idPhysics_Player::CmdScale( command ); + + if ( !scale ) { + wishvel = vec3_origin; + } else { + wishvel = scale * (viewForward * command.forwardmove + viewRight * command.rightmove); + wishvel -= scale * gravityNormal * command.upmove; + } + + wishdir = wishvel; + wishspeed = wishdir.Normalize(); + + idPhysics_Player::Accelerate( wishdir, wishspeed, PM_FLYACCELERATE ); + + idPhysics_Player::SlideMove( true, true, true, true ); +} + /* =================== idPhysics_Player::AirMove @@ -925,6 +953,77 @@ void idPhysics_Player::LadderMove( void ) { idPhysics_Player::SlideMove( false, ( command.forwardmove > 0 ), false, false ); } +void idPhysics_Player::StuckToSurfaceMove( void ) { + idVec3 wishdir, wishvel, right; + float wishspeed, scale; + float upscale; + + // stick to the ladder + wishvel = -100.0f * SurfaceNormal; + current.velocity = (gravityNormal * current.velocity) * gravityNormal + wishvel; + + upscale = (-gravityNormal * viewForward + 0.5f) * 2.5f; + if ( upscale > 1.0f ) { + upscale = 1.0f; + } + else if ( upscale < -1.0f ) { + upscale = -1.0f; + } + + scale = idPhysics_Player::CmdScale( command ); + wishvel = -0.9f * gravityNormal * upscale * scale * (float)command.forwardmove; + + // strafe + if ( command.rightmove ) { + // right vector orthogonal to gravity + right = viewRight - (gravityNormal * viewRight) * gravityNormal; + // project right vector into ladder plane + right = right - (SurfaceNormal * right) * SurfaceNormal; + right.Normalize(); + + // if we are looking away from the ladder, reverse the right vector + if ( SurfaceNormal * viewForward > 0.0f ) { + right = -right; + } + wishvel += 2.0f * right * scale * (float) command.rightmove; + } + + // up down movement + if ( command.upmove ) { + wishvel += -0.5f * gravityNormal * scale * (float) command.upmove; + } + + // accelerate + wishspeed = wishvel.Normalize(); + idPhysics_Player::Accelerate( wishvel, wishspeed, PM_ACCELERATE ); + + // cap the vertical velocity + upscale = current.velocity * -gravityNormal; + if ( upscale < -PM_LADDERSPEED ) { + current.velocity += gravityNormal * (upscale + PM_LADDERSPEED); + } + else if ( upscale > PM_LADDERSPEED ) { + current.velocity += gravityNormal * (upscale - PM_LADDERSPEED); + } + + if ( (wishvel * gravityNormal) == 0.0f ) { + if ( current.velocity * gravityNormal < 0.0f ) { + current.velocity += gravityVector * frametime; + if ( current.velocity * gravityNormal > 0.0f ) { + current.velocity -= (gravityNormal * current.velocity) * gravityNormal; + } + } + else { + current.velocity -= gravityVector * frametime; + if ( current.velocity * gravityNormal < 0.0f ) { + current.velocity -= (gravityNormal * current.velocity) * gravityNormal; + } + } + } + + idPhysics_Player::SlideMove( false, ( command.forwardmove > 0 ), false, false ); +} + /* ============= idPhysics_Player::CorrectAllSolid @@ -1311,12 +1410,47 @@ void idPhysics_Player::DropTimers( void ) { } } +// HEXEN : Zeroth +void idPhysics_Player::EvalGravity( void ) { + const int transitions = 8; + const float transitionTime = 0.10f; + idVec3 curOrigin = clipModel->GetOrigin(); + idVec3 curGrav = gravityVector; + + float curTime = MS2SEC( gameLocal.realClientTime ); + if (TransitionToGravity != idVec3(0,0,0) && GetGravityNormal() != TransitionToGravity && nextTransition < curTime) { + double gravAmount = sqrt(curGrav.x * curGrav.x + curGrav.y * curGrav.y + curGrav.z * curGrav.z); + idVec3 gravDir = TransitionToGravity * curTransition + + TransitionFromGravity * (transitions-curTransition); + gravDir.Normalize(); + idActor * owner=static_cast< idActor * >(masterEntity); + + if (curTransition == transitions) { + SetGravity(TransitionToGravity * gravAmount); + TransitionToGravity.Zero(); + nextTransition=0; + } else { + SetGravity(gravDir * gravAmount); + curTransition++; + nextTransition = MS2SEC( gameLocal.realClientTime ) + (transitionTime / transitions); + } + + // flip viewAngles cuz ... things get screwey + // avoid getting stuck in wall + while (groundPlane && !HasGroundContacts()) { // we must have ground contacts at all times that we have a groundPlane, else we risk falling through floor + current.origin = current.origin - gravityNormal; + CheckGround(); + } + } +} + /* ================ idPhysics_Player::MovePlayer ================ */ void idPhysics_Player::MovePlayer( int msec ) { + EvalGravity(); //seems the best place to put this. MovePlayer seems to get called every single frame regardless of if player is actually moving. // this counter lets us debug movement problems with a journal // by setting a conditional breakpoint for the previous frame @@ -1363,6 +1497,13 @@ void idPhysics_Player::MovePlayer( int msec ) { return; } + // freemove + if ( current.movementType == PM_FLY ) { + FlyMoveWithCollision(); + idPhysics_Player::DropTimers(); // 404 + return; + } + // special no clip mode if ( current.movementType == PM_NOCLIP ) { idPhysics_Player::NoclipMove(); @@ -1397,6 +1538,11 @@ void idPhysics_Player::MovePlayer( int msec ) { // dead idPhysics_Player::DeadMove(); } +// HEXEN : Zeroth + else if ( StuckToSurface() ) { + DoStuckToSurface(); + idPhysics_Player::StuckToSurfaceMove(); + } else if ( ladder ) { // going up or down a ladder idPhysics_Player::LadderMove(); @@ -1422,6 +1568,23 @@ void idPhysics_Player::MovePlayer( int msec ) { idPhysics_Player::SetWaterLevel(); idPhysics_Player::CheckGround(); + // HEXEN : Zeroth - test if we should modify players gravity + //idPlayer *player = static_cast( masterEntity ); + if ( self->gravityMod ) { + + // grnd is not the ground the players feet are on, it's the surface directly below the player's origin + idVec3 myOrigin = GetOrigin(); + float grndDist = 16; + trace_t grnd; + gameLocal.clip.TracePoint( grnd, myOrigin, myOrigin + GetGravityNormal() * grndDist, MASK_PLAYERSOLID, self ); + + if ( grnd.fraction < 1.0f && GetGravityNormal() != -grnd.c.normal && TransitionToGravity != -grnd.c.normal ) { + TransitionToGravity = -grnd.c.normal; + TransitionFromGravity = GetGravityNormal(); + curTransition = 0; + } + } + // move the player velocity back into the world frame current.velocity += current.pushVelocity; current.pushVelocity.Zero(); @@ -1520,6 +1683,9 @@ idPhysics_Player::idPhysics_Player( void ) { ladderNormal.Zero(); waterLevel = WATERLEVEL_NONE; waterType = 0; + IsStuckToSurface = false; + SurfaceNormal.Zero(); + TransitionToGravity.Zero(); } /* @@ -1753,9 +1919,10 @@ bool idPhysics_Player::Evaluate( int timeStepMSec, int endTimeMSec ) { clipModel->Link( gameLocal.clip, self, 0, current.origin, clipModel->GetAxis() ); - if ( IsOutsideWorld() ) { - gameLocal.Warning( "clip model outside world bounds for entity '%s' at (%s)", self->name.c_str(), current.origin.ToString(0) ); - } + // HEXEN : Zeroth - sometimes this is okay + //if ( IsOutsideWorld() ) { + // gameLocal.Warning( "clip model outside world bounds for entity '%s' at (%s)", self->name.c_str(), current.origin.ToString(0) ); + //} return true; //( current.origin != oldOrigin ); } @@ -2056,3 +2223,28 @@ void idPhysics_Player::ReadFromSnapshot( const idBitMsgDelta &msg ) { clipModel->Link( gameLocal.clip, self, 0, current.origin, clipModel->GetAxis() ); } } + +// HEXEN : Zeroth +bool idPhysics_Player::StuckToSurface( void ) { + return IsStuckToSurface; +} + +idVec3 idPhysics_Player::GetSurfaceNormal( void ) { + return SurfaceNormal; +} + +void idPhysics_Player::SetSurfaceNormal( idVec3 newNormal ) { + SurfaceNormal = newNormal; +} + +void idPhysics_Player::SetStuckToSurface( bool yesOrNo ) { + IsStuckToSurface = yesOrNo; +} + +void idPhysics_Player::DoStuckToSurface( void ) { + // do all the math and junk to stick to a wall. this function should be called prior to moving an actor. + if (StuckToSurface()) { + idVec3 wishvel = -100.0f * SurfaceNormal; + current.velocity = (gravityNormal * current.velocity) * gravityNormal + wishvel; + } +} diff --git a/game/physics/Physics_Player.h b/game/physics/Physics_Player.h index 40d52e7e..12bcc084 100644 --- a/game/physics/Physics_Player.h +++ b/game/physics/Physics_Player.h @@ -48,7 +48,8 @@ typedef enum { PM_DEAD, // no acceleration or turning, but free falling PM_SPECTATOR, // flying without gravity but with collision detection PM_FREEZE, // stuck in place without control - PM_NOCLIP // flying without collision detection nor gravity + PM_NOCLIP, // flying without collision detection nor gravity + PM_FLY // flying without gravity but with collision detection } pmtype_t; typedef enum { @@ -100,6 +101,14 @@ class idPhysics_Player : public idPhysics_Actor { bool OnLadder( void ) const; const idVec3 & PlayerGetOrigin( void ) const; // != GetOrigin +// HEXEN : Zeroth +public: + bool StuckToSurface( void ); + void SetStuckToSurface( bool yesOrNo ); + idVec3 GetSurfaceNormal( void ); + void SetSurfaceNormal( idVec3 newNormal ); + void DoStuckToSurface( void ); + public: // common physics interface bool Evaluate( int timeStepMSec, int endTimeMSec ); void UpdateTime( int endTimeMSec ); @@ -177,6 +186,7 @@ class idPhysics_Player : public idPhysics_Actor { void WaterJumpMove( void ); void WaterMove( void ); void FlyMove( void ); + void FlyMoveWithCollision( void ); void AirMove( void ); void WalkMove( void ); void DeadMove( void ); @@ -192,6 +202,11 @@ class idPhysics_Player : public idPhysics_Actor { void SetWaterLevel( void ); void DropTimers( void ); void MovePlayer( int msec ); + +// HEXEN : Zeroth +private: + void EvalGravity( void ); + void StuckToSurfaceMove( void ); }; #endif /* !__PHYSICS_PLAYER_H__ */ diff --git a/game/physics/Physics_RigidBody.cpp b/game/physics/Physics_RigidBody.cpp index 83806569..5343b05a 100644 --- a/game/physics/Physics_RigidBody.cpp +++ b/game/physics/Physics_RigidBody.cpp @@ -966,12 +966,12 @@ bool idPhysics_RigidBody::Evaluate( int timeStepMSec, int endTimeMSec ) { current.lastTimeStep = timeStep; current.externalForce.Zero(); current.externalTorque.Zero(); - - if ( IsOutsideWorld() ) { - gameLocal.Warning( "rigid body moved outside world bounds for entity '%s' type '%s' at (%s)", - self->name.c_str(), self->GetType()->classname, current.i.position.ToString(0) ); - Rest(); - } +// HEXEN : Zeroth - for some entities, it is okay to move outside world bounds. would be nice to have a "canMoveOutsideWorld" spawnArg +// if ( IsOutsideWorld() ) { +// gameLocal.Warning( "rigid body moved outside world bounds for entity '%s' type '%s' at (%s)", +// self->name.c_str(), self->GetType()->classname, current.i.position.ToString(0) ); +// Rest(); +// } #ifdef RB_TIMINGS timer_total.Stop(); diff --git a/game/physics/Push.cpp b/game/physics/Push.cpp index 0b986130..5649528b 100644 --- a/game/physics/Push.cpp +++ b/game/physics/Push.cpp @@ -1174,7 +1174,7 @@ float idPush::ClipTranslationalPush( trace_t &results, idEntity *pusher, const i // if blocking entities should be crushed if ( flags & PUSHFL_CRUSH ) { - check->Damage( clipModel->GetEntity(), clipModel->GetEntity(), vec3_origin, "damage_crush", 1.0f, CLIPMODEL_ID_TO_JOINT_HANDLE( pushResults.c.id ) ); + check->Damage( clipModel->GetEntity(), clipModel->GetEntity(), vec3_origin, "damage_crush", 1.0f, CLIPMODEL_ID_TO_JOINT_HANDLE( pushResults.c.id ), pushResults.c.point ); continue; } @@ -1344,7 +1344,7 @@ float idPush::ClipRotationalPush( trace_t &results, idEntity *pusher, const int // if blocking entities should be crushed if ( flags & PUSHFL_CRUSH ) { - check->Damage( clipModel->GetEntity(), clipModel->GetEntity(), vec3_origin, "damage_crush", 1.0f, CLIPMODEL_ID_TO_JOINT_HANDLE( pushResults.c.id ) ); + check->Damage( clipModel->GetEntity(), clipModel->GetEntity(), vec3_origin, "damage_crush", 1.0f, CLIPMODEL_ID_TO_JOINT_HANDLE( pushResults.c.id ), pushResults.c.point ); continue; } diff --git a/game/projectiles/FireStorm.cpp b/game/projectiles/FireStorm.cpp new file mode 100644 index 00000000..05a8ea57 --- /dev/null +++ b/game/projectiles/FireStorm.cpp @@ -0,0 +1,268 @@ + +#include "Game_local.h" +#include "Player.h" +#include "Projectile.h" +#include "ai/AI.h" + +#include "FireStorm.h" + +const int FIREBEAM_METEOR_DELAY = 300; // in msec +const int FIREBEAM_METEOR_NUM = 8; +const int FIREBEAM_METEOR_SPEED = 400; +const int FIREBEAM_METOER_DISTT = 200; // distance between pairs in sky +const int FIREBEAM_METOER_DISTB = 75; // distance between pairs when reach firebeam +const int FIREBEAM_METEOR_HEIGHT = 200; + +static const int BFG_DAMAGE_FREQUENCY = 333; +static const float BOUNCE_SOUND_MIN_VELOCITY = 200.0f; +static const float BOUNCE_SOUND_MAX_VELOCITY = 400.0f; + +const idEventDef EV_LaunchFireBeam( "LaunchFireBeam", "vvvfff" ); + +/************************************* + +zeroth +idProj_FireBeam + +*************************************/ + +CLASS_DECLARATION( idProjectile, idProj_FireBeam ) + EVENT( EV_LaunchFireBeam, idProj_FireBeam::Event_Launch ) +END_CLASS + +void idProj_FireBeam::Spawn( void ) { + +} + +void idProj_FireBeam::Think( void ) { + if ( state == 2 && meteorNum < FIREBEAM_METEOR_NUM ) { + idVec3 dir; + const idDict *boltDef; + idEntity *bolt; + + idVec3 forw = GetPhysics()->GetLinearVelocity(); + forw.Normalize(); + + dir.z = forw.z; + + for ( int i=0; i<2; i++ ) { + if ( i == 0 ) { + // counter clockwise + dir.x = -forw.y; + dir.y = forw.x; + } else { + // clockwise + dir.x = forw.y; + dir.y = -forw.x; + } + + boltDef = gameLocal.FindEntityDefDict( "projectile_firestorm_meteor" ); + gameLocal.SpawnEntityDef( *boltDef, &bolt ); + + if ( !bolt ) { + return; + } + + idProjectile *blt = static_cast< idProjectile * >(bolt); + + idVec3 vec1 = GetPhysics()->GetOrigin() + dir * FIREBEAM_METOER_DISTT; + + vec1.z += FIREBEAM_METEOR_HEIGHT; + blt->GetPhysics()->SetOrigin( vec1 ); + + idVec3 vec2 = GetPhysics()->GetOrigin() + dir * FIREBEAM_METOER_DISTB; + + vec2 -= vec1; + vec2.Normalize(); + + //blt->SetAngles( vec2.ToAngles() ); + + //blt->GetPhysics()->SetLinearVelocity( vec2 * FIREBEAM_METEOR_SPEED ); + + blt->Launch( vec1, vec2, vec2 * FIREBEAM_METEOR_SPEED ); + + } + meteorNum++; + } + + idProjectile::Think(); +} + +void idProj_FireBeam::Launch( const idVec3 &start, const idVec3 &dir, const idVec3 &pushVelocity, const float timeSinceFire, const float launchPower, const float dmgPower ) { + idProjectile::Launch( start, dir, pushVelocity, timeSinceFire, launchPower, dmgPower); +} + +void idProj_FireBeam::Event_Launch( const idVec3 &start, const idVec3 &dir, const idVec3 &pushVelocity, const float timeSinceFire, const float launchPower, const float dmgPower ) { + idProjectile::Launch( start, dir, pushVelocity, timeSinceFire, launchPower, dmgPower ); +} + +/************************************* + +zeroth +idProj_Incinerator + +*************************************/ + +CLASS_DECLARATION( idProjectile, idProj_Incinerator ) + +END_CLASS + +void idProj_Incinerator::Spawn( void ) { + ent = NULL; +} + +void idProj_Incinerator::Incinerate( void ) { + particleEmitter_t pe; + //idStr particleName = "firestorm_incinerator"; + idVec3 origin; + idMat3 axis; + + // if there's no animator, just bind one flame to the entity where we impacted it + if ( !ent->GetAnimator() ) {/* + idEntity *flame; + const idDict *flameDef = gameLocal.FindEntityDefDict( "firestorm_incinerator_flame" ); + gameLocal.SpawnEntityDef( *flameDef, &flame ); + flame->GetPhysics()->SetOrigin( GetPhysics()->GetOrigin() ); + flame->Bind(ent, 0);*/ + + return; + } + + ent->onFire = gameLocal.time + 5000; +} + +bool idProj_Incinerator::Collide( const trace_t &collision, const idVec3 &velocity ) { + idEntity *ignore; + const char *damageDefName; + idVec3 dir; + float push; + float damageScale; + + if ( state == EXPLODED || state == FIZZLED ) { + return true; + } + + // predict the explosion + if ( gameLocal.isClient ) { + if ( ClientPredictionCollide( this, spawnArgs, collision, velocity, !spawnArgs.GetBool( "net_instanthit" ) ) ) { + Explode( collision, NULL ); + return true; + } + return false; + } + + // remove projectile when a 'noimpact' surface is hit + if ( ( collision.c.material != NULL ) && ( collision.c.material->GetSurfaceFlags() & SURF_NOIMPACT ) ) { + PostEventMS( &EV_Remove, 0 ); + common->DPrintf( "Projectile collision no impact\n" ); + return true; + } + + // get the entity the projectile collided with + ent = gameLocal.entities[ collision.c.entityNum ]; + if ( ent == owner.GetEntity() ) { + assert( 0 ); + return true; + } + + // just get rid of the projectile when it hits a player in noclip + if ( ent->IsType( idPlayer::Type ) && static_cast( ent )->noclip ) { + PostEventMS( &EV_Remove, 0 ); + return true; + } + + // direction of projectile + dir = velocity; + dir.Normalize(); + + // projectiles can apply an additional impulse next to the rigid body physics impulse + if ( spawnArgs.GetFloat( "push", "0", push ) && push > 0.0f ) { + ent->ApplyImpulse( this, collision.c.id, collision.c.point, push * dir ); + } + + // MP: projectiles open doors + if ( gameLocal.isMultiplayer && ent->IsType( idDoor::Type ) && !static_cast< idDoor * >(ent)->IsOpen() && !ent->spawnArgs.GetBool( "no_touch" ) ) { + ent->ProcessEvent( &EV_Activate , this ); + } + + if ( ent->IsType( idActor::Type ) || ( ent->IsType( idAFAttachment::Type ) && static_cast(ent)->GetBody()->IsType( idActor::Type ) ) ) { + if ( !projectileFlags.detonate_on_actor ) { + return false; + } + } else { + if ( !projectileFlags.detonate_on_world ) { + if ( !StartSound( "snd_ricochet", SND_CHANNEL_ITEM, 0, true, NULL ) ) { + float len = velocity.Length(); + if ( len > BOUNCE_SOUND_MIN_VELOCITY ) { + SetSoundVolume( len > BOUNCE_SOUND_MAX_VELOCITY ? 1.0f : idMath::Sqrt( len - BOUNCE_SOUND_MIN_VELOCITY ) * ( 1.0f / idMath::Sqrt( BOUNCE_SOUND_MAX_VELOCITY - BOUNCE_SOUND_MIN_VELOCITY ) ) ); + StartSound( "snd_bounce", SND_CHANNEL_ANY, 0, true, NULL ); + } + } + return false; + } + } + + SetOrigin( collision.endpos ); + SetAxis( collision.endAxis ); + + // unlink the clip model because we no longer need it + GetPhysics()->UnlinkClip(); + + damageDefName = spawnArgs.GetString( "def_damage" ); + + ignore = NULL; + + // if the hit entity takes damage + if ( ent->fl.takedamage ) { + if ( damagePower ) { + damageScale = damagePower; + } else { + damageScale = 1.0f; + } + + // if the projectile owner is a player + if ( owner.GetEntity() && owner.GetEntity()->IsType( idPlayer::Type ) ) { + // if the projectile hit an actor + if ( ent->IsType( idActor::Type ) ) { + idPlayer *player = static_cast( owner.GetEntity() ); + player->AddProjectileHits( 1 ); + damageScale *= player->PowerUpModifier( PROJECTILE_DAMAGE ); + } + } + + if ( damageDefName[0] != '\0' ) { + ent->Damage( this, owner.GetEntity(), dir, damageDefName, damageScale, CLIPMODEL_ID_TO_JOINT_HANDLE( collision.c.id ), idVec3( collision.c.point ) ); + ignore = ent; + } + } + + // if the projectile causes a damage effect + if ( spawnArgs.GetBool( "impact_damage_effect" ) ) { + // if the hit entity has a special damage effect + if ( ent->spawnArgs.GetBool( "bleed" ) ) { + ent->AddDamageEffect( collision, velocity, damageDefName ); + } else { + AddDefaultDamageEffect( collision, velocity ); + } + } + + Explode( collision, ignore ); + Incinerate(); + + return true; +} + +/************************************* + +zeroth +idProj_IncineratorFlame + +*************************************/ + +CLASS_DECLARATION( idEntity, idProj_IncineratorFlame ) + +END_CLASS + +void idProj_IncineratorFlame::Spawn() { + PostEventMS( &EV_Remove, gameLocal.random.RandomFloat() * 1000 + 4000 ); +} diff --git a/game/projectiles/FireStorm.h b/game/projectiles/FireStorm.h new file mode 100644 index 00000000..270dee8c --- /dev/null +++ b/game/projectiles/FireStorm.h @@ -0,0 +1,41 @@ +#ifndef __AI_FIRESTORM_H__ +#define __AI_FIRESTORM_H__ + +// one flame to go on one joint. idProj_Incinerator creates these entities. +class idProj_IncineratorFlame : public idEntity { +public: + CLASS_PROTOTYPE( idProj_IncineratorFlame ); + void Spawn( void ); +}; + +// base class for projetiles that incinerate things +class idProj_Incinerator : public idProjectile { +public: + CLASS_PROTOTYPE( idProj_Incinerator ); + void Spawn( void ); + void Incinerate( void ); + bool Collide( const trace_t &collision, const idVec3 &velocity ); + +private: + bool incinerate; + idEntity * ent; +}; + +class idProj_FireBeam : public idProj_Incinerator { +public: + CLASS_PROTOTYPE( idProj_FireBeam ); + void Think( void ); + void Spawn( void ); + void Launch( const idVec3 &start, const idVec3 &dir, const idVec3 &pushVelocity, const float timeSinceFire = 0.0f, const float launchPower = 1.0f, const float dmgPower = 1.0f ); + +private: + float gravTime; + +private: + int meteorNum; + +private: + void Event_Launch( const idVec3 &start, const idVec3 &dir, const idVec3 &pushVelocity, const float timeSinceFire, const float launchPower, const float dmgPower ); +}; + +#endif // __AI_FIRESTORM_H__ \ No newline at end of file diff --git a/game/projectiles/Soul.cpp b/game/projectiles/Soul.cpp new file mode 100644 index 00000000..98c314d6 --- /dev/null +++ b/game/projectiles/Soul.cpp @@ -0,0 +1,44 @@ + +#include "Game_local.h" +#include "Projectile.h" + +#include "Soul.h" + +const float SOUL_SPEED = 1000.0f; + +CLASS_DECLARATION( idProjectile, idProj_Soul ) + +END_CLASS + +void idProj_Soul::init() { + dieTime = 2000 + gameLocal.time; + + // spawn the effect around the wraith model + idDict effectArgs; + effectArgs.Set( "classname", "blank_item" ); + effectArgs.Set("model", spawnArgs.GetString("def_projectile")); + effectArgs.SetVector("origin", GetPhysics()->GetOrigin()); + effectArgs.SetBool("nonsolid", true); + effectArgs.SetBool("nocollide", true); + gameLocal.SpawnEntityDef(effectArgs, &effect, true); + effect->Bind(this, true); + GetPhysics()->SetGravity( idVec3(0,0,0.000001f) ); + + rnd = gameLocal.random.RandomInt(360); +} + + +void idProj_Soul::Think() { + idProjectile::Think(); + + if ( gameLocal.time >= dieTime ) { + PostEventMS( &EV_Remove, 0.0 ); + } + + ang.yaw = (( gameLocal.time / 1000 ) * 360) + rnd; + dir = ang.ToForward(); dir.Normalize(); + dir = dir + idVec3(0,0,0.66f); dir.Normalize(); + SetAngles( ang ); + + GetPhysics()->SetLinearVelocity(SOUL_SPEED * dir); +} diff --git a/game/projectiles/Soul.h b/game/projectiles/Soul.h new file mode 100644 index 00000000..afc55096 --- /dev/null +++ b/game/projectiles/Soul.h @@ -0,0 +1,18 @@ +#ifndef __AI_SOUL_H__ +#define __AI_SOUL_H__ + +class idProj_Soul : public idProjectile { +public: + CLASS_PROTOTYPE( idProj_Soul ); + void init( void ); + void Think( void ); + +private: + idEntity *effect; + idVec3 dir; + float dieTime; + idAngles ang; + float rnd; +}; + +#endif // __AI_SOUL_H__ \ No newline at end of file diff --git a/game/projectiles/Wraithverge.cpp b/game/projectiles/Wraithverge.cpp new file mode 100644 index 00000000..9f4ff79b --- /dev/null +++ b/game/projectiles/Wraithverge.cpp @@ -0,0 +1,368 @@ + +#include "Game_local.h" +#include "Player.h" +#include "Projectile.h" +#include "ai/AI.h" +#include "ai/AI_Golem.h" +#include "ai/AI_Veloxite.h" + +#include "Wraithverge.h" + +const int PROJ_WRAITH_CHASE_RANGE = 768; +const int PROJ_WRAITH_SPAWN_DELAY = 400; +const int PROJ_WRAITH_NUM = 2; +const int PROJ_WRAITH_NUM_TOME = 2; +const int PROJ_WRAITH_CHECK_DELAY = 300; + +CLASS_DECLARATION( idProjectile, idProj_Wraith ) + +END_CLASS + +void idProj_Wraith::Spawn( void ) { + tome = false; + + if ( spawnArgs.GetBool("tome") ) { // if ( owner.GetEntity() && owner.GetEntity()->IsType( idPlayer::Type ) && ( static_cast< idPlayer * >( owner.GetEntity() )->GetPowerTome() ) ) { + tome = true; + spawnArgs.Set( "def_damage", "damage_wraithverge_tome" ); + } +} + +void idProj_Wraith::Think( void ) { + if ( wraithSpun == true ) { + //gameLocal.entities[this->entityNumber] = NULL; + PostEventSec( &EV_Explode, 0.0f ); + return; + } + + idProjectile::Think(); + + /* current projectile doesnt need spinning, turning this off + // do spinning + ang = forw.ToAngles(); + ang.roll = gameLocal.time * 360; + SetAngles( ang ); + */ + + // delay until spawn new wraiths + if ( gameLocal.time >= spinTime ) { + if ( tome ) { + wraithSpun = true; + SpawnSubWraiths( PROJ_WRAITH_NUM_TOME ); + } else { + wraithSpun = true; + SpawnSubWraiths( PROJ_WRAITH_NUM ); + } + } +} + +void idProj_Wraith::Launch( const idVec3 &start, const idVec3 &dir, const idVec3 &pushVelocity, const float timeSinceFire, const float launchPower, const float dmgPower ) { + forw = dir; + wraithInitialized=false; + wraithSpun = false; + + spinTime = gameLocal.time + PROJ_WRAITH_SPAWN_DELAY; + +/* this was used when we had an actual shadowspawn model for the projectile + // spawn the effect around the wraith model + spawnArgs.Set("model", spawnArgs.GetString("def_projectile")); + spawnArgs.SetVector("origin", GetPhysics()->GetOrigin()); + spawnArgs.Set("nonsolid", "1"); + spawnArgs.Set("nocollide", "1"); + const idDict *blank = gameLocal.FindEntityDefDict("blank_item"); + effect = gameLocal.SpawnEntityType( idEntity::Type, blank ); + effect->Bind( this, 1 ); +*/ + + idProjectile::Launch( start, dir, pushVelocity, timeSinceFire, launchPower, dmgPower ); +} + +void idProj_Wraith::SpawnSubWraiths( int num ) { + idProj_HomingWraith *newWraith = NULL; + idEntity *ent = NULL; + idVec3 rnd; + idVec3 dir2; + float rndf=0; + idDict newWraithDict; + + if ( tome ) { + newWraithDict.Set( "classname", "projectile_HomingWraith_Tome" ); + } else { + newWraithDict.Set( "classname", "projectile_HomingWraith" ); + } + + rnd.z = 0; + + // spawn some wraiths + for (int i=0; i < num; i++) { + + // alternate left & right + if ( rndf > 0 ) { + rndf = -1; + // perpendicular vector counterclockwise + rnd.x = -forw.y; + rnd.y = forw.x; + } else { + // perpendicular vector clockwise + rndf = 1; + rnd.x = forw.y; + rnd.y = -forw.x; + } + + dir2.Set(forw.x, forw.y, 0); + dir2.Normalize(); + float cval = gameLocal.random.RandomFloat(); + dir2 = ( dir2 * cval ) + ( rnd * ( 1 - cval ) ); + dir2.Normalize(); + + newWraithDict.SetAngles( "wraithAngles", dir2.ToAngles() ); + newWraithDict.SetVector( "wraithVelocity", defVel * dir2 ); + newWraithDict.SetVector( "wraithOrigin", GetPhysics()->GetOrigin() ); + newWraithDict.SetVector( "wraithDir", dir2 ); + newWraithDict.SetBool( "wraithTome", tome ); + + gameLocal.SpawnEntityDef( newWraithDict, &ent ); + if ( !ent ) { + return; + } + newWraith = static_cast< idProj_HomingWraith* >( ent ); + } +} + +/************************************************************************* + +Homing Wraith + +*************************************************************************/ + +CLASS_DECLARATION( idProjectile, idProj_HomingWraith ) + +END_CLASS + +void idProj_HomingWraith::Spawn( void ) { + GetPhysics()->SetLinearVelocity( spawnArgs.GetVector( "wraithVelocity" ) ); + GetPhysics()->SetGravity( idVec3(0, 0, 0.000001f ) ); // grav can't be zero. //z.todo: try this in def + GetPhysics()->SetOrigin( spawnArgs.GetVector( "wraithOrigin" ) ); + + SetAngles( spawnArgs.GetAngles( "wraithAngles" ) ); + + target = NULL; + tome = spawnArgs.GetBool( "wraithTome" ); + dir = spawnArgs.GetVector( "wraithDir" ); + defVel = spawnArgs.GetFloat("velocity"); + dieTime = SEC2MS( spawnArgs.GetFloat("fuse") ) + gameLocal.time; // not working??? + + nextSearch = 0; + hits = 0; +} + +void idProj_HomingWraith::Think() { + + if ( state == EXPLODED ) { + return; + } + + if ( target ) { + // move to the head of the enemy, not the origin (feet) + targOrigin = target->GetPhysics()->GetOrigin(); + targOrigin.z = targOrigin.z + ( targSize.z ); + + dir = targOrigin -GetPhysics()->GetOrigin(); + + if ( dir.Length() < 10 ) { // target distance + //z.todo startSound("snd_explode", SND_CHANNEL_ANY, false ); +// target->GetPhysics()->SetLinearVelocity(target->GetPhysics()->GetLinearVelocity() + dir * 150 ); // push + + //radiusDamage(target->GetPhysics()->GetOrigin()(), this, this, this, "damage_wraithverge", 1); // damage //Z.TODO: make player the attacker + DirectDamage( spawnArgs.GetString("def_damage"), target ); + target->spawnArgs.Set("wraithChased", "0"); + + //get off this target, keep flying for a bit, and find a new one + target = NULL; + if ( tome ) { + nextSearch = gameLocal.time + ( PROJ_WRAITH_CHECK_DELAY / 3 ); + } else { + nextSearch = gameLocal.time + ( PROJ_WRAITH_CHECK_DELAY ); + } + } + + dir.Normalize(); + } else { + if ( gameLocal.time > dieTime ) { + //gameLocal.entities[this->entityNumber] = NULL; + PostEventSec( &EV_Explode, 0.0f ); + } + + if ( gameLocal.time > nextSearch ) { + findTarget(); + nextSearch = gameLocal.time + 500 + 250 * gameLocal.random.RandomFloat(); // we don't want to search every frame, that will get real expensive. + + if ( target ) { + targSize = target->spawnArgs.GetVector("size"); + } + } + } + + GetPhysics()->SetLinearVelocity(defVel * dir); // chase target, apply velocity here, this way we maintain speed each frameall the time (it likes to slow down) + + SetAngles( dir.ToAngles() ); + + idProjectile::Think(); + +} + +void idProj_HomingWraith::DirectDamage( const char *meleeDefName, idEntity *ent ) { + const idDict *meleeDef; + const char *p; + const idSoundShader *shader; + + meleeDef = gameLocal.FindEntityDefDict( meleeDefName, false ); + if ( !meleeDef ) { + gameLocal.Error( "Unknown damage def '%s' on '%s'", meleeDefName, name.c_str() ); + } + + if ( !ent->fl.takedamage ) { + const idSoundShader *shader = declManager->FindSound(meleeDef->GetString( "snd_miss" )); + StartSoundShader( shader, SND_CHANNEL_DAMAGE, 0, false, NULL ); + return; + } + + // + // do the damage + // + p = meleeDef->GetString( "snd_hit" ); + if ( p && *p ) { + shader = declManager->FindSound( p ); + StartSoundShader( shader, SND_CHANNEL_DAMAGE, 0, false, NULL ); + } + + ent->Damage( this, this, idVec3(0,0,0), meleeDefName, 1.0f, INVALID_JOINT, idVec3(0,0,0) ); + + hits++; + + if ( tome && hits >= 4) { + target->spawnArgs.Set("wraithChased", "0"); + Explode( owner.GetEntity() ); + } +} + +void idProj_HomingWraith::Explode( idEntity *ignore ) { + trace_t collision; + + memset( &collision, 0, sizeof( collision ) ); + collision.endAxis = GetPhysics()->GetAxis(); + collision.endpos = GetPhysics()->GetOrigin(); + collision.c.point = GetPhysics()->GetOrigin(); + collision.c.normal.Set( 0, 0, 1 ); + AddDefaultDamageEffect( collision, collision.c.normal ); + idProjectile::Explode( collision, ignore ); +} + +void idProj_HomingWraith::findTarget() { + int hash, i, j; + const char *cname=NULL; + + for ( j=0; j<3; j++ ) { + switch (j) { + case 0: + cname = idAI::Type.classname; + break; + case 1: + cname = idAI_Golem::Type.classname; + break; + case 2: + cname = idAI_Veloxite::Type.classname; + break;/* + case 3: + cname = idAI_Shadowspawn::Type.classname; + break;*/ + default: + cname = idAI::Type.classname; + break; + } + + hash = gameLocal.entypeHash.GenerateKey( cname, true ); + + for ( i = gameLocal.entypeHash.First( hash ); i != -1; i = gameLocal.entypeHash.Next( i ) ) { + if ( gameLocal.entities[i] && !strcmp(gameLocal.entities[i]->GetClassname(), cname ) ) { + target = static_cast< idAI* >( gameLocal.entities[i] ); + + if ( target->IsHidden() ) { + continue; + } + + if ( target->health <= 0 ) { + continue; + } + + if ( target->spawnArgs.GetFloat("wraithChased") ) { + continue; + } + + // check distance + if ( idVec3( gameLocal.entities[i]->GetPhysics()->GetOrigin() - GetPhysics()->GetOrigin() ).Length() > 768 /* wraith chase range */ ) { + continue; + } + + target->spawnArgs.Set("wraithChased", "1"); + return; + } + } + } + target = NULL; +} + +/************************************************************************* + +Pulling Wraith + +*************************************************************************/ + +/* pulling wraith was a neat idea, but wraiths come out of the wraithverge. let's stick to the story here... +void proj_PullingWraith::Think() { + startSound("snd_split", SND_CHANNEL_ANY, false ); + + findTarget(); + + if (! (!target)) { + targSize = target->spawnArgs.GetVector("size"); + + SetOrigin(target->GetPhysics()->GetOrigin() - idVec3(0, 0, 125); + + while (1) { + // move to the head of the enemy, not the origin (feet) + targOrigin = target->GetPhysics()->GetOrigin(); + targOrigin.z = targOrigin.z + ( targSize.z ); + + dir = targOrigin -GetPhysics()->GetOrigin(); + targetDist = dir.Length(); + + if (targetDist < 10) { // drag enemy down + startSound("snd_explode", SND_CHANNEL_ANY, false ); + + //target->bind(this); + + dieTime = gameLocal.time + 2; + defVel = defVel * 0.5; + + target->becomeRagdoll(); + while (gameLocal.time < dieTime) { + target->GetPhysics()->SetOrigin()(GetPhysics()->GetOrigin()); + GetPhysics()->SetLinearVelocity(defVel * idVec3(0, 0, -1) ); + waitFrame(); + } + + target->Set("wraithChased", "0"); + target->remove(); + + break; + } + + dir = sys.vecNormalize(dir); + GetPhysics()->SetLinearVelocity(defVel * dir); // chase target + GetPhysics()->SetAngles(sys.VecToAngles(dir)); + + waitFrame(); + } + } +} +*/ diff --git a/game/projectiles/Wraithverge.h b/game/projectiles/Wraithverge.h new file mode 100644 index 00000000..927a201b --- /dev/null +++ b/game/projectiles/Wraithverge.h @@ -0,0 +1,66 @@ +#ifndef __AI_WRAITHVERGE_H__ +#define __AI_WRAITHVERGE_H__ + +#include "Projectile.h" + +/************************************************************************* + +WraithVerge Projectile (spawns other wraith types) + +*************************************************************************/ + +class idProj_Wraith : public idProjectile { +public: + CLASS_PROTOTYPE( idProj_Wraith ); + void Think( void ); + void Spawn( void ); + void Launch( const idVec3 &start, const idVec3 &dir, const idVec3 &pushVelocity, const float timeSinceFire = 0.0f, const float launchPower = 1.0f, const float dmgPower = 1.0f ); + +private: + void SpawnSubWraiths( int num ); + +private: + bool tome; + bool wraithSpun; + idVec3 forw; + float defVel; + float dieTime; + bool wraithInitialized; + float spinTime; + idAngles ang; +}; + + +class idProj_HomingWraith : public idProjectile { +public: + CLASS_PROTOTYPE( idProj_HomingWraith ); + +private: + void Think( void ); + void Explode( idEntity *ignore ); + void Spawn( void ); + void DirectDamage( const char *meleeDefName, idEntity *ent ); + +public: + bool ready; + +private: + void findTarget(); + void homingWraith(); + void pullingWraith(); + +private: + float gravTime; + idVec3 dir; + float defVel; + int dieTime; + bool tome; + idAI *target; + idVec3 targOrigin; + idVec3 targSize; + float nextSearch; + float nextWallCollideCheck; + int hits; +}; + +#endif // __AI_WRAITHVERGE_H__ diff --git a/game/script/Script_Interpreter.cpp b/game/script/Script_Interpreter.cpp index f2b91688..a8ae5777 100644 --- a/game/script/Script_Interpreter.cpp +++ b/game/script/Script_Interpreter.cpp @@ -673,6 +673,7 @@ void idInterpreter::LeaveFunction( idVarDef *returnDef ) { // all done doneProcessing = true; threadDying = true; + //stackEmpty = true; // HEXEN : Zeroth currentFunction = 0; } } diff --git a/game/script/Script_Thread.cpp b/game/script/Script_Thread.cpp index 3aa8054b..54b904a5 100644 --- a/game/script/Script_Thread.cpp +++ b/game/script/Script_Thread.cpp @@ -28,9 +28,11 @@ If you have questions concerning this license or the applicable additional terms #include "sys/platform.h" +#include "Game_local.h" #include "gamesys/SysCvar.h" #include "Player.h" #include "Camera.h" +#include "SmokeParticles.h" #include "script/Script_Thread.h" @@ -64,6 +66,8 @@ const idEventDef EV_Thread_SpawnFloat( "SpawnFloat", "sf", 'f' ); const idEventDef EV_Thread_SpawnVector( "SpawnVector", "sv", 'v' ); const idEventDef EV_Thread_ClearPersistantArgs( "clearPersistantArgs" ); const idEventDef EV_Thread_SetPersistantArg( "setPersistantArg", "ss" ); +const idEventDef EV_Thread_SetPersistantMapArg( "setPersistantMapArg", "ss" ); +const idEventDef EV_Thread_GetPersistantMapFloat( "getPersistantMapFloat", "ss", 'f' ); const idEventDef EV_Thread_GetPersistantString( "getPersistantString", "s", 's' ); const idEventDef EV_Thread_GetPersistantFloat( "getPersistantFloat", "s", 'f' ); const idEventDef EV_Thread_GetPersistantVector( "getPersistantVector", "s", 'v' ); @@ -113,6 +117,17 @@ const idEventDef EV_Thread_DebugCircle( "debugCircle", "vvvfdf" ); const idEventDef EV_Thread_DebugBounds( "debugBounds", "vvvf" ); const idEventDef EV_Thread_DrawText( "drawText", "svfvdf" ); const idEventDef EV_Thread_InfluenceActive( "influenceActive", NULL, 'd' ); +// HEXEN : Zeroth +//const idEventDef EV_Thread_SoundMute( "SoundMute", "f" ); +const idEventDef EV_Thread_ASin( "ASin", "f", 'f' ); +const idEventDef EV_Thread_ACos( "ACos", "f", 'f' ); +const idEventDef EV_Thread_ATan( "ATan", "f", 'f' ); +const idEventDef EV_Thread_GetRandomBanishLocation( "GetRandomBanishLocation", NULL, 'v' ); +const idEventDef EV_Thread_GetEntityNum( "GetEntityNum", "e", 'f' ); +const idEventDef EV_Thread_GetEntityNumFromName( "GetEntityNumFromName", "s", 'f' ); +const idEventDef EV_Thread_TraceSurfaceNormal( "TraceSurfaceNormal", "vvfe", 'v' ); +const idEventDef EV_Thread_GetWorldGravity( "GetWorldGravity", NULL, 'v' ); +const idEventDef EV_Thread_SpawnParticle( "spawnParticle", "sv" ); CLASS_DECLARATION( idClass, idThread ) EVENT( EV_Thread_Execute, idThread::Event_Execute ) @@ -142,6 +157,8 @@ CLASS_DECLARATION( idClass, idThread ) EVENT( EV_Thread_SpawnVector, idThread::Event_SpawnVector ) EVENT( EV_Thread_ClearPersistantArgs, idThread::Event_ClearPersistantArgs ) EVENT( EV_Thread_SetPersistantArg, idThread::Event_SetPersistantArg ) + EVENT( EV_Thread_SetPersistantMapArg, idThread::Event_SetPersistantMapArg ) + EVENT( EV_Thread_GetPersistantMapFloat, idThread::Event_GetPersistantMapFloat ) EVENT( EV_Thread_GetPersistantString, idThread::Event_GetPersistantString ) EVENT( EV_Thread_GetPersistantFloat, idThread::Event_GetPersistantFloat ) EVENT( EV_Thread_GetPersistantVector, idThread::Event_GetPersistantVector ) @@ -193,6 +210,17 @@ CLASS_DECLARATION( idClass, idThread ) EVENT( EV_Thread_DebugBounds, idThread::Event_DebugBounds ) EVENT( EV_Thread_DrawText, idThread::Event_DrawText ) EVENT( EV_Thread_InfluenceActive, idThread::Event_InfluenceActive ) +// HEXEN : Zeroth +// EVENT( EV_Thread_SoundMute, idThread::Event_SoundMute ) + EVENT( EV_Thread_ASin, idThread::Event_ASin ) + EVENT( EV_Thread_ACos, idThread::Event_ACos ) + EVENT( EV_Thread_ATan, idThread::Event_ATan ) + EVENT( EV_Thread_GetRandomBanishLocation, idThread::Event_GetRandomBanishLocation ) + EVENT( EV_Thread_GetEntityNum, idThread::Event_GetEntityNum ) + EVENT( EV_Thread_GetEntityNumFromName, idThread::Event_GetEntityNumFromName ) + EVENT( EV_Thread_TraceSurfaceNormal, idThread::Event_TraceSurfaceNormal ) + EVENT( EV_Thread_GetWorldGravity, idThread::Event_GetWorldGravity ) + EVENT( EV_Thread_SpawnParticle, idThread::Event_SpawnParticle ) END_CLASS idThread *idThread::currentThread = NULL; @@ -773,6 +801,20 @@ void idThread::ThreadCallback( idThread *thread ) { } } +// /* +// ================ +// idThread::Event_SoundMute +// ================ +// */ +// void idThread::Event_SoundMute( float yesorno ) { +// if (yesorno) { +// soundSystem->SetMute( true ); +// } else { +// soundSystem->SetMute( false ); +// } +// } + + /* ================ idThread::Event_SetThreadName @@ -910,7 +952,7 @@ void idThread::WaitFrame( void ) { // manual control threads don't set waitingUntil so that they can be run again // that frame if necessary. if ( !manualControl ) { - waitingUntil = gameLocal.time + gameLocal.msec; + waitingUntil = gameLocal.time + gameLocal.msecPrecise; } } @@ -1125,6 +1167,18 @@ void idThread::Event_Spawn( const char *classname ) { spawnArgs.Clear(); } +// HEXEN : Zeroth +void idThread::Event_SpawnParticle( const char *particleName, const idVec3 &origin ) { + const idDecl *dec; + dec = declManager->FindType( DECL_PARTICLE, particleName ); + if ( !dec ) { + return; + } + + //gameLocal.smokeParticles->EmitSmoke( static_cast( dec ), gameLocal.time+1, gameLocal.random.CRandomFloat(), origin, idMat3() ); + gameLocal.smokeParticles->EmitSmoke( static_cast( dec ), gameLocal.time, gameLocal.random.RandomFloat(), origin, idMat3( 1,0,0,0,0,0,0,0,0 ) ); +} + /* ================ idThread::Event_CopySpawnArgs @@ -1198,6 +1252,27 @@ void idThread::Event_SetPersistantArg( const char *key, const char *value ) { gameLocal.persistentLevelInfo.Set( key, value ); } +void idThread::Event_SetPersistantMapArg( const char *key, const char *value ) { + idStr name_str; + + name_str=gameLocal.GetMapName(); + name_str+="_"; + name_str+=key; + + gameLocal.persistentLevelInfo.Set( name_str, value ); +} + +void idThread::Event_GetPersistantMapFloat( const char *key, const char *defaultvalue ) { + float result; + idStr name_str; + + name_str=gameLocal.GetMapName(); + name_str+="_"; + name_str+=key; + gameLocal.persistentLevelInfo.GetFloat( name_str, defaultvalue, result ); + ReturnFloat( result ); +} + /* ================ idThread::Event_GetPersistantString @@ -1841,3 +1916,71 @@ void idThread::Event_InfluenceActive( void ) { idThread::ReturnInt( false ); } } + + +// HEXEN : Zeroth +void idThread::Event_ASin( const float angle ) { + ReturnFloat( idMath::ASin( angle ) ); +} + +void idThread::Event_ACos( const float angle ) { + ReturnFloat( idMath::ACos( angle ) ); +} + +void idThread::Event_ATan( const float angle ) { + gameLocal.Printf("RETURNING: %f from %f\n", atan( angle ), angle); + ReturnFloat( atan( angle ) ); +} + +void idThread::Event_GetRandomBanishLocation( void ) { + int i; + + if ( gameLocal.BanishLocationList.Num() < 1 ) { + gameLocal.Error("There are no Banish Locations\n."); + idThread::ReturnVector( idVec3(0, 0, 0) ); // DG: making sure the vector is initialized + return; + } + + i = gameLocal.random.RandomInt(gameLocal.BanishLocationList.Num()); + + idThread::ReturnVector( *gameLocal.BanishLocationList[i] ); +} + +void idThread::Event_GetEntityNum( const idEntity *ent1 ) { + if ( ent1 ) { + idThread::ReturnFloat(ent1->entityNumber); + } else { + idThread::ReturnFloat(-1); + } +} + +void idThread::Event_GetEntityNumFromName( const idStr entName ) { + for (int i = 0; i < MAX_GENTITIES; i++ ) { + idEntity *ent = gameLocal.entities[i]; + if (ent && !idStr::Cmp( ent->name, entName ) ) { + idThread::ReturnFloat(i); return; + } + } + idThread::ReturnFloat(-1); return; +} + +void idThread::Event_TraceSurfaceNormal( const idVec3 &A, const idVec3 &B, const float clipMask, const idEntity *pass ) { + trace_t trace; + idEntity *ent; + + gameLocal.clip.TracePoint( trace, A, B, MASK_PLAYERSOLID, pass ); + + // if near a surface + if ( trace.fraction < 1.0f ) { + idVec3 bub=trace.c.normal; + ent = gameLocal.GetTraceEntity(trace); + + idThread::ReturnVector( trace.c.normal ); + }else{ + idThread::ReturnVector( idVec3(0, 0, 0) ); + } +} + +void idThread::Event_GetWorldGravity( void ) { + idThread::ReturnVector( gameLocal.GetGravity() ); +} diff --git a/game/script/Script_Thread.h b/game/script/Script_Thread.h index fe24943f..de4fd75b 100644 --- a/game/script/Script_Thread.h +++ b/game/script/Script_Thread.h @@ -136,6 +136,8 @@ class idThread : public idClass { void Event_SpawnVector( const char *key, idVec3 &defaultvalue ); void Event_ClearPersistantArgs( void ); void Event_SetPersistantArg( const char *key, const char *value ); + void Event_SetPersistantMapArg( const char *key, const char *value ); + void Event_GetPersistantMapFloat( const char *key, const char *defaultvalue ); void Event_GetPersistantString( const char *key ); void Event_GetPersistantFloat( const char *key ); void Event_GetPersistantVector( const char *key ); @@ -188,6 +190,19 @@ class idThread : public idClass { void Event_DrawText( const char *text, const idVec3 &origin, float scale, const idVec3 &color, const int align, const float lifetime ); void Event_InfluenceActive( void ); +// HEXEN : Zeroth +private: +// void Event_SoundMute( float yesorno ); + void Event_ASin( const float angle ); + void Event_ACos( const float angle ); + void Event_ATan( const float angle ); + void Event_GetRandomBanishLocation( void ); + void Event_GetEntityNum( const idEntity *ent ); + void Event_GetEntityNumFromName( const idStr entName ); + void Event_TraceSurfaceNormal( const idVec3 &A, const idVec3 &B, const float clipMask, const idEntity *pass ); + void Event_GetWorldGravity( void ); + void Event_SpawnParticle( const char *particleName, const idVec3 &origin ); + public: CLASS_PROTOTYPE( idThread ); @@ -228,6 +243,7 @@ class idThread : public idClass { static idList& GetThreads ( void ); bool IsDoneProcessing ( void ); + //bool IsBusy( void ); // HEXEN : Zeroth - this will be true if the script is in a function - whether paused or not. bool IsDying ( void ); void End( void ); @@ -321,6 +337,20 @@ ID_INLINE bool idThread::IsDoneProcessing ( void ) { return interpreter.doneProcessing; } +// /* +// ================ +// HEXEN : Zeroth +// idThread::IsBusy +// ================ +// */ +// ID_INLINE bool idThread::IsBusy ( void ) { +// if (interpreter.GetCallstackDepth() == 0) { +// return false; +// }else{ +// return true; +// } +// } + /* ================ idThread::IsDying diff --git a/idlib/bv/Bounds.cpp b/idlib/bv/Bounds.cpp index c28b6a1e..128dcad9 100644 --- a/idlib/bv/Bounds.cpp +++ b/idlib/bv/Bounds.cpp @@ -54,6 +54,16 @@ float idBounds::GetRadius( void ) const { return idMath::Sqrt( total ); } +// HEXEN : Zeroth +idVec3 idBounds::GetMaxs( void ) const { + return idVec3(b[1]); +} + +// HEXEN : Zeroth +idVec3 idBounds::GetMins( void ) const { + return idVec3(b[0]); +} + /* ============ idBounds::GetRadius diff --git a/idlib/bv/Bounds.h b/idlib/bv/Bounds.h index 7e79b8ad..b4fb24d3 100644 --- a/idlib/bv/Bounds.h +++ b/idlib/bv/Bounds.h @@ -68,6 +68,12 @@ class idBounds { idVec3 GetCenter( void ) const; // returns center of bounds float GetRadius( void ) const; // returns the radius relative to the bounds origin + +// HEXEN : Zeroth +public: + idVec3 GetMaxs( void ) const; + idVec3 GetMins( void ) const; + float GetRadius( const idVec3 ¢er ) const; // returns the radius relative to the given center float GetVolume( void ) const; // returns the volume of the bounds bool IsCleared( void ) const; // returns true if bounds are inside out diff --git a/idlib/containers/List.h b/idlib/containers/List.h index 9817d7f2..7c4b8b48 100644 --- a/idlib/containers/List.h +++ b/idlib/containers/List.h @@ -117,6 +117,9 @@ class idList { type * Ptr( void ); // returns a pointer to the list const type * Ptr( void ) const; // returns a pointer to the list type & Alloc( void ); // returns reference to a new data element at the end of the list +// HEXEN : Zeroth + int New( void ); // append element + int Append( const type & obj ); // append element int Append( const idList &other ); // append list int AddUnique( const type & obj ); // add unique element @@ -128,6 +131,7 @@ class idList { bool RemoveIndex( int index ); // remove the element at the given index bool Remove( const type & obj ); // remove the element void Sort( cmp_t *compare = ( cmp_t * )&idListSortCompare ); + void Shuffle( void ); void SortSubSection( int startIndex, int endIndex, cmp_t *compare = ( cmp_t * )&idListSortCompare ); void Swap( idList &other ); // swap the contents of the lists void DeleteContents( bool clear ); // delete the contents of the list @@ -695,6 +699,41 @@ ID_INLINE int idList::Append( type const & obj ) { return num - 1; } +/* +================ +Zeroth + +idList::New + +Increases the size of the list by one element. +if it is a pointer, user is responsible for NULL'ing it. + +Returns the index of the new element. +================ +*/ +template< class type > +ID_INLINE int idList::New( void ) { + if ( !list ) { + Resize( granularity ); + } + + if ( num == size ) { + int newsize; + + if ( granularity == 0 ) { // this is a hack to fix our memset classes + granularity = 16; + } + newsize = size + granularity; + Resize( newsize - newsize % granularity ); + } + + list[ num ] = *(new type); + num++; + + return num - 1; +} + + /* ================ @@ -936,6 +975,23 @@ ID_INLINE void idList::Sort( cmp_t *compare ) { qsort( ( void * )list, ( size_t )num, sizeof( type ), vCompare ); } +template< class type > +ID_INLINE void idList::Shuffle( void ) { + if ( !list ) { + return; + } + + int i,rnd; + type tmp; + + for ( i=0; i::SortSubSection diff --git a/idlib/math/Math.h b/idlib/math/Math.h index 2fd0fd0a..ac677ad7 100644 --- a/idlib/math/Math.h +++ b/idlib/math/Math.h @@ -200,7 +200,11 @@ class idMath { static float BitsToFloat( int i, int exponentBits, int mantissaBits ); static int FloatHash( const float *array, const int numFloats ); - +/* +// HEXEN : Zeroth + static idVec3 Vec45CtrClock( idVec3 vec ); + static idVec3 Vec45Cloc( idVec3 vec ); +*/ static const float PI; // pi static const float TWO_PI; // pi * 2 static const float HALF_PI; // pi / 2 @@ -928,5 +932,29 @@ ID_INLINE int idMath::FloatHash( const float *array, const int numFloats ) { } return hash; } +/* +// HEXEN : Zeroth +ID_INLINE idVec3 idMath::Vec45CtrClock( idVec3 vec ) { + idVec3 perpVec; + + // perpendicular vector to forward, counterclockwise + perpVec.x = -vec.y; + perpVec.y = vec.x; + perpVec.z = 0; + return perpVec; +} + +// HEXEN : Zeroth +ID_INLINE idVec3 idMath::Vec45Clock( idVec3 vec ) { + idVec3 perpVec; + + // perpendicular vector to forward, clockwise + perpVec_x = vec.y; + perpVec_y = -vec.x; + perpVec_z = 0; + + return perpVec; +} +*/ #endif /* !__MATH_MATH_H__ */ diff --git a/idlib/math/Vector.cpp b/idlib/math/Vector.cpp index a863ec73..dd15d661 100644 --- a/idlib/math/Vector.cpp +++ b/idlib/math/Vector.cpp @@ -294,7 +294,13 @@ void idVec3::ProjectSelfOntoSphere( const float radius ) { } } - +// HEXEN : Zeroth +float idVec3::toAngle( idVec3 B) { +// return the angle in degrees between two idVec3s + idVec3 Bn = B; Bn.Normalize(); + idVec3 An = *this; An.Normalize(); + return RAD2DEG( idMath::ACos( An * Bn ) ); +} //=============================================================== // diff --git a/idlib/math/Vector.h b/idlib/math/Vector.h index d00949f6..14218ada 100644 --- a/idlib/math/Vector.h +++ b/idlib/math/Vector.h @@ -389,6 +389,10 @@ class idVec3 { void Lerp( const idVec3 &v1, const idVec3 &v2, const float l ); void SLerp( const idVec3 &v1, const idVec3 &v2, const float l ); + +// HEXEN : Zeroth +public: + float toAngle( idVec3 B); // return the angle in degrees between two idVec3s }; extern idVec3 vec3_origin;