diff --git a/.gitattributes b/.gitattributes index 437f140a..2214f67b 100644 --- a/.gitattributes +++ b/.gitattributes @@ -32,9 +32,9 @@ vpc binary *.inl text *.asm text +.github/CONTRIBUTING.md text .gitignore text README.md text -CONTRIBUTING text LICENSE text *.exe binary diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md new file mode 100644 index 00000000..4c9b35dc --- /dev/null +++ b/.github/CONTRIBUTING.md @@ -0,0 +1,66 @@ +This file details how to contribute to the Mapbase project on GitHub: + https://github.com/mapbase-source/source-sdk-2013 + +For the original Source SDK 2013 contribution guidelines, click here: + https://github.com/ValveSoftware/source-sdk-2013/blob/master/CONTRIBUTING + +--- + +Mapbase is a project which many Source modders draw from, so it has its own unique standards +for contributions which differ from other projects, but it is still an open-source repository +that is always open to contributions. + +Whenever you contribute to the Mapbase repository, you must keep in mind that any contributions +made could be deployed to all mods utilizing Mapbase, which can include everything from high-profile +Steam mods to amateur HL2 maps. Many contributions can also end up being available in both SP and MP +if the contributions are not obviously exclusive to one of the two. + +All contributions must follow the following rules: + + * A contribution must be aligned with Mapbase's goals and priorities and should not be "subjective" + or related to a specific mod or type of mod. For example, fixing an existing issue or adding a + new tool for mappers to use is usually fine, but adding a new custom weapon with its own assets + is usually not fit for Mapbase. + + * All content in a contribution must be either already legally open-source or done with the + full permission of the content's original creator(s). If a license is involved, the contributor + should ensure Mapbase conforms to its terms. + * **NOTE:** Due to concerns with mods which do not wish to be open-source, content using GPL licenses (or any + license with similar open-source requirements) are currently not allowed to be distributed with Mapbase. + Contributions which can draw from them without actually distributing the licensed content may be excepted + from this rule. + + * Contributions must not break existing maps/content or interfere with them in a negative or non-objective way. + + * Code contributions are not obliged to follow Mapbase's preprocessor conventions (e.g. #ifdef MAPBASE), + although following them is usually acceptable. + + * Code contributions which modify or add onto existing code should generally match its syntax and shouldn't + change the spacing unless necessary. + + * If you are contributing a file you created yourself specifically for Mapbase, you are required to + use the custom "Mapbase - Source 2013" header used in other Mapbase files as of Mapbase v5.0. + You are encouraged to append an "Author(s)" part to that header in your file in order to clarify who wrote it. + + * Do not modify the README to add attribution for your contribution. That is handled by Mapbase's maintainers. + +Contributions which do not follow these guidelines cannot be accepted into Mapbase. Attempting to contribute content +which seriously violates the rules above can lead to being blocked from contributing, especially if done repeatedly. + +--- + +Mapbase uses GitHub Actions to help manage issues and pull requests. Some of these workflows build the code of incoming +contributions to make sure they compile properly. The code is compiled separately for Visual Studio 2022 and GCC/G++ 9 (Linux) +and on both Debug and Release configurations. + +If these workflows fail, don't freak out! Accidents can happen frequently due to compiler syntax differences and conflicts +from other contributions. You can look at a failed workflow's log by clicking "Details", which will include the build's output +in the "Build" step(s). Any errors must be resolved by you and/or by code reviewers before a pull request can be merged. + +If your contribution is accepted, you may be listed in Mapbase's credits and the README's external content list: + https://github.com/mapbase-source/source-sdk-2013/wiki/Mapbase-Credits#Contributors + https://github.com/mapbase-source/source-sdk-2013/blob/master/README + +You may also receive the "Contributor" or "Major Contributor" role on Mapbase's Discord server if you are +a member of it. + diff --git a/.github/ISSUE_TEMPLATE/bug-report---code.md b/.github/ISSUE_TEMPLATE/bug-report---code.md new file mode 100644 index 00000000..49724747 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug-report---code.md @@ -0,0 +1,25 @@ +--- +name: Bug report - Code +about: Create a bug report related to the source code itself. (e.g. a compile error) +title: "[CODE] " +labels: Bug +assignees: '' + +--- + +### Describe the bug +A clear and concise description of what the bug is. + +### Steps to reproduce +Steps to reproduce the behavior: +1. Have '...' set to something +2. ??? +3. See error + +### Expected behavior +A clear and concise description of what you expected to happen. + +--- + +### Additional context +(Optional) Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/bug-report---game.md b/.github/ISSUE_TEMPLATE/bug-report---game.md new file mode 100644 index 00000000..bdce4b18 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug-report---game.md @@ -0,0 +1,25 @@ +--- +name: Bug report - Game +about: Create a bug report related to game behavior. +title: "[GAME] " +labels: Bug +assignees: '' + +--- + +### Describe the bug +A clear and concise description of what the bug is. + +### Steps to reproduce +Steps to reproduce the behavior: +1. Have '...' in the map +2. ??? +3. See error + +### Expected behavior +A clear and concise description of what you expected to happen. + +--- + +### Additional context +(Optional) Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/bug-report---misc.md b/.github/ISSUE_TEMPLATE/bug-report---misc.md new file mode 100644 index 00000000..ca74bb35 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug-report---misc.md @@ -0,0 +1,25 @@ +--- +name: Bug report - Miscellaneous +about: Create a bug report not related to any of the other subjects. +title: "[MISC] " +labels: Bug +assignees: '' + +--- + +### Describe the bug +A clear and concise description of what the bug is. + +### Steps to reproduce +Steps to reproduce the behavior: +1. Do '...' +2. ??? +3. See error + +### Expected behavior +A clear and concise description of what you expected to happen. + +--- + +### Additional context +(Optional) Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/bug-report---npc.md b/.github/ISSUE_TEMPLATE/bug-report---npc.md new file mode 100644 index 00000000..a526c312 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug-report---npc.md @@ -0,0 +1,25 @@ +--- +name: Bug report - NPCs +about: Create a bug report related to NPCs. +title: "[NPCs] " +labels: Bug +assignees: '' + +--- + +### Describe the bug +A clear and concise description of what the bug is. + +### Steps to reproduce +Steps to reproduce the behavior: +1. Have '...' do something +2. ??? +3. See error + +### Expected behavior +A clear and concise description of what you expected to happen. + +--- + +### Additional context +(Optional) Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/bug-report---tools.md b/.github/ISSUE_TEMPLATE/bug-report---tools.md new file mode 100644 index 00000000..cdbe3beb --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug-report---tools.md @@ -0,0 +1,25 @@ +--- +name: Bug report - Tools +about: Create a bug report related to compile tools, editor stuff, etc. +title: "[TOOLS] " +labels: Bug, Tools +assignees: '' + +--- + +### Describe the bug +A clear and concise description of what the bug is. + +### Steps to reproduce +Steps to reproduce the behavior: +1. Have '...' in the map +2. ??? +3. See error + +### Expected behavior +A clear and concise description of what you expected to happen. + +--- + +### Additional context +(Optional) Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/bug-report---visuals-graphics.md b/.github/ISSUE_TEMPLATE/bug-report---visuals-graphics.md new file mode 100644 index 00000000..8e4c66f8 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug-report---visuals-graphics.md @@ -0,0 +1,29 @@ +--- +name: Bug report - Visuals/Graphics +about: Create a bug report related to visual issues. (e.g. shaders, projected textures, + etc.) +title: "[VISUAL] " +labels: Bug +assignees: '' + +--- + +### Describe the bug +A clear and concise description of what the bug is. + +### Steps to reproduce +Steps to reproduce the behavior: +1. Have '...' in the map +2. ??? +3. See error + +### Expected behavior +A clear and concise description of what you expected to happen. + +### Media +Any related screenshots, videos, etc. which show the bug. + +--- + +### Additional context +(Optional) Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/bug-report---vscript.md b/.github/ISSUE_TEMPLATE/bug-report---vscript.md new file mode 100644 index 00000000..244fbc21 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug-report---vscript.md @@ -0,0 +1,25 @@ +--- +name: Bug report - VScript +about: Create a bug report related to VScript. +title: "[VSCRIPT] " +labels: Bug, VScript +assignees: '' + +--- + +### Describe the bug +A clear and concise description of what the bug is. + +### Steps to reproduce +Steps to reproduce the behavior: +1. Have '...' in the script +2. ??? +3. See error + +### Expected behavior +A clear and concise description of what you expected to happen. + +--- + +### Additional context +(Optional) Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature-request---code.md b/.github/ISSUE_TEMPLATE/feature-request---code.md new file mode 100644 index 00000000..c1a7cc5e --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature-request---code.md @@ -0,0 +1,22 @@ +--- +name: Feature request - Code +about: Suggest an idea related to the source code itself. (e.g. a new utility function) +title: "[CODE] " +labels: Enhancement +assignees: '' + +--- + +### Is your feature request related to a problem? Please describe. +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +### Describe the solution you'd like +A clear and concise description of what you want to happen. + +### Describe alternatives you've considered +A clear and concise description of any alternative solutions or features you've considered. + +--- + +### Additional context +(Optional) Add any other context or screenshots about the feature request here. diff --git a/.github/ISSUE_TEMPLATE/feature-request---game.md b/.github/ISSUE_TEMPLATE/feature-request---game.md new file mode 100644 index 00000000..b943b87d --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature-request---game.md @@ -0,0 +1,22 @@ +--- +name: Feature request - Game +about: Suggest an idea related to game behavior. +title: "[GAME] " +labels: Enhancement +assignees: '' + +--- + +### Is your feature request related to a problem? Please describe. +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +### Describe the solution you'd like +A clear and concise description of what you want to happen. + +### Describe alternatives you've considered +A clear and concise description of any alternative solutions or features you've considered. + +--- + +### Additional context +(Optional) Add any other context or screenshots about the feature request here. diff --git a/.github/ISSUE_TEMPLATE/feature-request---misc.md b/.github/ISSUE_TEMPLATE/feature-request---misc.md new file mode 100644 index 00000000..3f5ef86c --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature-request---misc.md @@ -0,0 +1,22 @@ +--- +name: Feature request - Miscellaneous +about: Suggest an idea not related to any of the other subjects. +title: "[MISC] " +labels: Enhancement +assignees: '' + +--- + +### Is your feature request related to a problem? Please describe. +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +### Describe the solution you'd like +A clear and concise description of what you want to happen. + +### Describe alternatives you've considered +A clear and concise description of any alternative solutions or features you've considered. + +--- + +### Additional context +(Optional) Add any other context or screenshots about the feature request here. diff --git a/.github/ISSUE_TEMPLATE/feature-request---npc.md b/.github/ISSUE_TEMPLATE/feature-request---npc.md new file mode 100644 index 00000000..a8ae910d --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature-request---npc.md @@ -0,0 +1,22 @@ +--- +name: Feature request - NPCs +about: Suggest an idea related to NPCs. +title: "[NPCs] " +labels: Enhancement +assignees: '' + +--- + +### Is your feature request related to a problem? Please describe. +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +### Describe the solution you'd like +A clear and concise description of what you want to happen. + +### Describe alternatives you've considered +A clear and concise description of any alternative solutions or features you've considered. + +--- + +### Additional context +(Optional) Add any other context or screenshots about the feature request here. diff --git a/.github/ISSUE_TEMPLATE/feature-request---tools.md b/.github/ISSUE_TEMPLATE/feature-request---tools.md new file mode 100644 index 00000000..3043db79 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature-request---tools.md @@ -0,0 +1,22 @@ +--- +name: Feature request - Tools +about: Suggest an idea related to compile tools, editor stuff, etc. +title: "[TOOLS] " +labels: Enhancement, Tools +assignees: '' + +--- + +### Is your feature request related to a problem? Please describe. +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +### Describe the solution you'd like +A clear and concise description of what you want to happen. + +### Describe alternatives you've considered +A clear and concise description of any alternative solutions or features you've considered. + +--- + +### Additional context +(Optional) Add any other context or screenshots about the feature request here. diff --git a/.github/ISSUE_TEMPLATE/feature-request---visuals-graphics.md b/.github/ISSUE_TEMPLATE/feature-request---visuals-graphics.md new file mode 100644 index 00000000..5067b45b --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature-request---visuals-graphics.md @@ -0,0 +1,23 @@ +--- +name: Feature request - Visuals/Graphics +about: Suggest an idea related to visuals or graphics. (e.g. shaders, projected textures, + etc.) +title: "[VISUAL] " +labels: Enhancement +assignees: '' + +--- + +### Is your feature request related to a problem? Please describe. +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +### Describe the solution you'd like +A clear and concise description of what you want to happen. + +### Describe alternatives you've considered +A clear and concise description of any alternative solutions or features you've considered. + +--- + +### Additional context +(Optional) Add any other context or screenshots about the feature request here. diff --git a/.github/ISSUE_TEMPLATE/feature-request---vscript.md b/.github/ISSUE_TEMPLATE/feature-request---vscript.md new file mode 100644 index 00000000..dde39768 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature-request---vscript.md @@ -0,0 +1,22 @@ +--- +name: Feature request - VScript +about: Suggest an idea related to VScript. +title: "[VSCRIPT] " +labels: Enhancement, VScript +assignees: '' + +--- + +### Is your feature request related to a problem? Please describe. +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +### Describe the solution you'd like +A clear and concise description of what you want to happen. + +### Describe alternatives you've considered +A clear and concise description of any alternative solutions or features you've considered. + +--- + +### Additional context +(Optional) Add any other context or screenshots about the feature request here. diff --git a/.github/labeler.yml b/.github/labeler.yml new file mode 100644 index 00000000..1166136f --- /dev/null +++ b/.github/labeler.yml @@ -0,0 +1,35 @@ +# +# MAPBASE REPO AUTOMATION +# +# Automatically labels pull requests according to changed file paths. +# See mapbase_pr.yml for more information. +# +Repo: + - '*' + - '.github/**' + +Project Generation: + - '**/src/vpc_scripts/**' + - '**/src/devtools/**' + - '**/src/create*' + +Entities: + - '**/src/game/**/**logic**' + - '**/src/game/**/**point**' + +Shaders: + - '**/src/materialsystem/**' + +VScript: + - '**vscript**' + +Tools: + - '**utils**' + +NPCs: + - '**npc_**' + - '**ai_**' + +VGUI: + - '**hud_**' + - '**vgui_**' \ No newline at end of file diff --git a/.github/pull-request-template.md b/.github/pull-request-template.md new file mode 100644 index 00000000..53db0cc8 --- /dev/null +++ b/.github/pull-request-template.md @@ -0,0 +1,11 @@ +(Describe your PR here and then fill out the checklist at the bottom) + +--- + +#### Does this PR close any issues? +* (Optional) Insert issue number(s) and any related info here + + +#### PR Checklist +- [ ] **My PR follows all guidelines in the CONTRIBUTING.md file** +- [ ] My PR targets a `develop` branch OR targets another branch with a specific goal in mind diff --git a/.github/workflows/mapbase_build-base-dispatch.yml b/.github/workflows/mapbase_build-base-dispatch.yml new file mode 100644 index 00000000..d89d151e --- /dev/null +++ b/.github/workflows/mapbase_build-base-dispatch.yml @@ -0,0 +1,70 @@ +# +# MAPBASE SOURCE 2013 CI +# +# This can be used to manually build the codebase. +# +# See mapbase_build-base.yml for more information on how this works. + +name: Build Projects (Manual) + +on: + workflow_dispatch: + inputs: + configuration: + description: 'Which configuration to build with' + default: 'Release' + required: true + type: choice + options: + - Release + - Debug + branch: + description: 'Which Source 2013 engine branch to compile for' + default: 'sp' + required: true + type: choice + options: + - sp + - mp + game: + description: 'Name of the game to build (if relevant)' + default: 'episodic' + required: false + type: choice + options: + - episodic + - hl2 + project-group: + description: 'Which group of projects to compile' + required: true + type: choice + options: + - all + - game + - shaders + - maptools + solution-name: + description: 'Name of the solution/makefile' + required: true + type: choice + options: + - everything + - games + - shaders + - maptools + build-on-linux: + description: 'Build on Ubuntu/Linux?' + default: true + required: false + type: boolean + +jobs: + build_manual: + uses: ./.github/workflows/mapbase_build-base.yml + with: + configuration: '${{ github.event.inputs.configuration }}' + branch: '${{ github.event.inputs.branch }}' + game: '${{ github.event.inputs.game }}' + project-group: '${{ github.event.inputs.project-group }}' + solution-name: '${{ github.event.inputs.solution-name }}' + build-on-linux: "${{ github.event.inputs.build-on-linux == 'true' }}" diff --git a/.github/workflows/mapbase_build-base.yml b/.github/workflows/mapbase_build-base.yml new file mode 100644 index 00000000..45602a72 --- /dev/null +++ b/.github/workflows/mapbase_build-base.yml @@ -0,0 +1,195 @@ +# +# MAPBASE SOURCE 2013 CI +# +# This workflow script automatically builds the Source SDK 2013 codebase on Windows and Linux using GitHub Actions. +# +# This is useful in a number of ways: +# +# 1. It ensures pull requests compile correctly on multiple platforms and provides binaries that can be used to test them. +# 2. It can be used to compile code for releases without having to pull and prepare a local development environment. +# 3. It opens potential for scripts that can employ more principles of CI/CD. (e.g. automatically publishing a release) +# +# This is based on a workflow originally created by z33ky. + +name: Build Projects + +on: + workflow_call: + inputs: + configuration: + description: 'Which configuration to build with' + default: 'Release' + required: true + type: string + branch: + description: 'Which Source 2013 engine branch to compile for' + default: 'sp' + required: true + type: string + game: + description: 'The name of the game to build (if relevant)' + default: 'episodic' + required: false + type: string + project-group: + description: 'Which group of projects to compile' + required: true + type: string + solution-name: + description: 'The name of the solution/makefile' + required: true + type: string + build-on-linux: + description: 'Build on Ubuntu/Linux?' + default: true + required: false + type: boolean + +jobs: + build_windows: + name: Windows (VS2022) + runs-on: windows-latest + + steps: + - uses: actions/checkout@v4 + + - name: Add MSBuild to PATH + uses: compnerd/gha-setup-vsdevenv@v6 + + - name: Enable VS2022 + working-directory: '${{inputs.branch}}/src/vpc_scripts' + shell: bash + run: sed -i 's/^\($Conditional[ ]\+VS2022[ ]\+\).*/\1"1"/' newer_vs_toolsets.vpc + + - name: Pick game + if: inputs.project-group == 'game' || inputs.project-group == 'shaders' + working-directory: '${{inputs.branch}}/src' + shell: bash + run: sed -i 's/\/hl2 \/episodic/\/${{inputs.game}}/' create${{inputs.project-group}}projects.bat + + - name: Create project files + working-directory: '${{inputs.branch}}/src' + shell: cmd + # https://github.com/ValveSoftware/source-sdk-2013/issues/72 + run: | + reg add "HKLM\SOFTWARE\WOW6432Node\Microsoft\VisualStudio\10.0\Projects\{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}" /v DefaultProjectExtension /t REG_SZ /d vcproj /f + create${{inputs.project-group}}projects.bat + + # -------------------------------------------------------------------- + + - name: Build + #if: steps.filter.outputs.game == 'true' + working-directory: '${{inputs.branch}}/src' + shell: cmd + run: | + devenv ${{inputs.solution-name}}.sln /upgrade + msbuild -m -t:Rebuild -p:Configuration=${{inputs.configuration}};Platform=x86 ${{inputs.solution-name}}.sln + + # -------------------------------------------------------------------- + + - name: Publish game binaries + if: inputs.project-group == 'game' || inputs.project-group == 'shaders' + uses: actions/upload-artifact@v4 + with: + name: '${{inputs.project-group}}_${{inputs.game}}_win32_${{ inputs.configuration }}' + path: | + ${{inputs.branch}}/game/mod_${{inputs.game}}/bin/*.dll + if-no-files-found: error + + - name: Publish map tools + if: inputs.project-group == 'maptools' + uses: actions/upload-artifact@v4 + with: + name: '${{inputs.project-group}}_win32_${{ inputs.configuration }}' + path: | + ${{inputs.branch}}/game/bin/*.exe + ${{inputs.branch}}/game/bin/*.dll + if-no-files-found: error + + - name: Publish everything + if: inputs.project-group == 'all' + uses: actions/upload-artifact@v4 + with: + name: 'everything_win32_${{ inputs.configuration }}' + path: | + ${{inputs.branch}}/game/bin + ${{inputs.branch}}/game/mod_*/bin + if-no-files-found: error + + build_ubuntu: + if: inputs.build-on-linux == true && inputs.project-group != 'maptools' # No Linux map tools for now + name: Ubuntu (GCC/G++) + runs-on: ubuntu-latest + env: + config: ${{ inputs.configuration }} + + steps: + - uses: actions/checkout@v3 + + - name: Install GCC/G++ multilib + run: | + sudo apt-get update + sudo apt-get install gcc-multilib g++-multilib + + - name: Pick game + if: inputs.project-group == 'game' || inputs.project-group == 'shaders' + working-directory: '${{inputs.branch}}/src' + shell: bash + run: sed -i 's/\/hl2 \/episodic/\/${{inputs.game}}/' create${{inputs.project-group}}projects + + - name: Set configuration + working-directory: '${{inputs.branch}}/src' + shell: bash + run: | + config=${{inputs.configuration}} + export CFG=${config,,} + echo "config=${CFG}" >> $GITHUB_ENV + + - name: Create project files + working-directory: '${{inputs.branch}}/src' + run: ./create${{inputs.project-group}}projects + + # -------------------------------------------------------------------- + + - name: Build + working-directory: '${{inputs.branch}}/src' + run: make CFG=${{env.config}} -f ${{inputs.solution-name}}.mak + + # -------------------------------------------------------------------- + + - name: Publish game binaries + if: inputs.project-group == 'game' || inputs.project-group == 'shaders' + uses: actions/upload-artifact@v4 + with: + name: '${{inputs.project-group}}_${{inputs.game}}_linux32_${{ inputs.configuration }}' + path: | + ${{inputs.branch}}/game/mod_${{inputs.game}}/bin/*.so + !${{inputs.branch}}/game/mod_${{inputs.game}}/bin/*_srv.so + if-no-files-found: error + + #- name: Publish map tools + # if: inputs.project-group == 'maptools' + # uses: actions/upload-artifact@v4 + # with: + # name: '${{inputs.project-group}}_linux32_${{ inputs.configuration }}' + # path: | + # ${{inputs.branch}}/game/bin/vbsp + # ${{inputs.branch}}/game/bin/vvis + # ${{inputs.branch}}/game/bin/vvis_dll.so + # ${{inputs.branch}}/game/bin/vrad + # ${{inputs.branch}}/game/bin/vrad_dll.so + # if-no-files-found: error + + # For now, don't publish the .dbg files even though we publish .pdb files on Windows + # (they're too big) + - name: Publish everything + if: inputs.project-group == 'all' + uses: actions/upload-artifact@v4 + with: + name: 'everything_linux32_${{ inputs.configuration }}' + path: | + ${{inputs.branch}}/game/bin/*.so + !${{inputs.branch}}/game/bin/*_srv.so + ${{inputs.branch}}/game/mod_*/bin/*.so + !${{inputs.branch}}/game/mod_*/bin/*_srv.so + if-no-files-found: error diff --git a/.github/workflows/mapbase_build-master.yml b/.github/workflows/mapbase_build-master.yml new file mode 100644 index 00000000..9f70451b --- /dev/null +++ b/.github/workflows/mapbase_build-master.yml @@ -0,0 +1,31 @@ +# +# MAPBASE SOURCE 2013 CI +# +# Builds all projects when a pull request to the master branch is opened. +# If you're using a fork of Mapbase, feel free to configure this to meet your repository's needs. +# +# The "mapbase_build-sp" set of workflows can build specific projects depending on what files are changed. +# They are designed around a "develop" branch, but can be configured to target "master" and replace this +# instead (or target a similar branch with a different name) +# +# See mapbase_build-base.yml for more information on how this works. + +name: Build All Projects #(SP Release) + +on: + pull_request: + branches: + - master + +jobs: + everything: + strategy: + matrix: + configuration: [Release, Debug] + uses: ./.github/workflows/mapbase_build-base.yml + with: + configuration: ${{ matrix.configuration }} + branch: 'sp' + project-group: 'all' + solution-name: 'everything' + build-on-linux: true # Disable this if you don't want to compile for Linux diff --git a/.github/workflows/mapbase_build-sp-games.yml b/.github/workflows/mapbase_build-sp-games.yml new file mode 100644 index 00000000..dc5511ae --- /dev/null +++ b/.github/workflows/mapbase_build-sp-games.yml @@ -0,0 +1,38 @@ +# +# MAPBASE SOURCE 2013 CI +# +# Builds game projects every time a pull request which modifies the game code is opened. +# If you're using a fork of Mapbase, feel free to configure this to meet your repository's needs. +# +# See mapbase_build-base.yml for more information on how this works. + +name: Build Game Projects #(SP Release) + +on: + pull_request: + branches: + - develop + paths: + - '.github/workflows/mapbase_build-base.yml' + - '.github/workflows/mapbase_build-sp-rel-games.yml' + - 'sp/src/vpc_scripts/**' + - 'sp/src/game/**' + - 'sp/src/mathlib/**' + - 'sp/src/responserules/runtime/**' + - 'sp/src/tier1/**' + - 'sp/src/vgui2/vgui_controls/**' + - 'sp/src/vscript/**' + +jobs: + games: + strategy: + matrix: + configuration: [Release, Debug] + uses: ./.github/workflows/mapbase_build-base.yml + with: + configuration: ${{ matrix.configuration }} + branch: 'sp' + game: 'episodic' # Change this if your mod is not using HL2/Episodic game projects + project-group: 'game' + solution-name: 'games' + build-on-linux: true # Disable this if you don't want to compile for Linux diff --git a/.github/workflows/mapbase_build-sp-maptools.yml b/.github/workflows/mapbase_build-sp-maptools.yml new file mode 100644 index 00000000..0ae631b3 --- /dev/null +++ b/.github/workflows/mapbase_build-sp-maptools.yml @@ -0,0 +1,38 @@ +# +# MAPBASE SOURCE 2013 CI +# +# Builds map tool projects every time a pull request which modifies the map tool code is opened. +# If you're using a fork of Mapbase, feel free to configure this to meet your repository's needs. +# +# See mapbase_build-base.yml for more information on how this works. + +name: Build Map Tool Projects #(SP Release) + +on: + pull_request: + branches: + - develop + paths: + - '.github/workflows/mapbase_build-sp-rel-maptools.yml' + - 'sp/src/vpc_scripts/**' + - 'sp/src/utils/vbsp/**' + - 'sp/src/utils/vvis/**' + - 'sp/src/utils/vvis_launcher/**' + - 'sp/src/utils/vrad/**' + - 'sp/src/utils/vrad_launcher/**' + - 'sp/src/mathlib/**' + - 'sp/src/tier1/**' + - 'sp/src/vgui2/vgui_controls/**' + - 'sp/src/vscript/**' + +jobs: + maptools: + strategy: + matrix: + configuration: [Release, Debug] + uses: ./.github/workflows/mapbase_build-base.yml + with: + configuration: ${{ matrix.configuration }} + branch: 'sp' + project-group: 'maptools' + solution-name: 'maptools' diff --git a/.github/workflows/mapbase_build-sp-shaders.yml b/.github/workflows/mapbase_build-sp-shaders.yml new file mode 100644 index 00000000..73036e10 --- /dev/null +++ b/.github/workflows/mapbase_build-sp-shaders.yml @@ -0,0 +1,33 @@ +# +# MAPBASE SOURCE 2013 CI +# +# Builds shader projects every time a pull request which modifies the shader code is opened. +# If you're using a fork of Mapbase, feel free to configure this to meet your repository's needs. +# +# See mapbase_build-base.yml for more information on how this works. + +name: Build Shader Projects #(SP Release) + +on: + pull_request: + branches: + - develop + paths: + - '.github/workflows/mapbase_build-sp-rel-shaders.yml' + - 'sp/src/vpc_scripts/**' + - 'sp/src/materialsystem/**' + - 'sp/src/mathlib/**' + +jobs: + shaders: + strategy: + matrix: + configuration: [Release, Debug] + uses: ./.github/workflows/mapbase_build-base.yml + with: + configuration: ${{ matrix.configuration }} + branch: 'sp' + game: 'episodic' # Change this if your mod is not using HL2/Episodic game projects + project-group: 'shaders' + solution-name: 'shaders' + build-on-linux: true # Disable this if you don't want to compile for Linux diff --git a/.github/workflows/mapbase_pr.yml b/.github/workflows/mapbase_pr.yml new file mode 100644 index 00000000..b71506ea --- /dev/null +++ b/.github/workflows/mapbase_pr.yml @@ -0,0 +1,23 @@ +# +# MAPBASE REPO AUTOMATION +# +# Automatically labels pull requests according to changed file paths. +# +# https://github.com/actions/labeler + +name: Pull Request Automation +on: [pull_request] # pull_request_target + +jobs: + label: + + if: github.repository_owner == 'mapbase-source' # ${{ vars.MAPBASE_LABELS == 'true' }} + runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: write + + steps: + - uses: actions/labeler@v4 + with: + repo-token: "${{ secrets.GITHUB_TOKEN }}" diff --git a/.gitignore b/.gitignore index 47340f78..5daaf173 100644 --- a/.gitignore +++ b/.gitignore @@ -26,6 +26,7 @@ ipch *.xcodeproj/ obj*/ !devtools/*.mak +sp/src/devtools/makefile_base_posix.mak.link !utils/smdlexp/smdlexp.mak # Specific Source build products @@ -53,6 +54,13 @@ config.cfg # shader files *.tmp +*.vcs + +# Unnecessary files +*.lib +*.filters +*.vpc_crc +*.sentinel # GCH *.h.gch diff --git a/CONTRIBUTING b/CONTRIBUTING deleted file mode 100644 index 809ecd58..00000000 --- a/CONTRIBUTING +++ /dev/null @@ -1,38 +0,0 @@ -Thanks for your interest in the Source SDK 2013 project. When you make a -contribution to the project (e.g. create an Issue or submit a Pull Request) -(a "Contribution"), Valve wants to be able to use your Contribution to improve -the SDK and other Valve products. - -As a condition of providing a Contribution, you agree that: -- You grant Valve a non-exclusive, irrevocable, royalty-free, worldwide license -to make, use, sell, reproduce, modify, distribute (directly and indirectly), -and publicly display and perform the Contribution, and any derivative works -that Valve may make from the Contribution, under any intellectual property you -own or have the right to license. -- You warrant and represent that the Contribution is your original creation, -that you have the authority to grant this license to Valve, and that this -license does not require the permission of any third party. Otherwise, you -provide your Contribution "as is" without warranties. - -Should you wish to submit a suggestion or work that is not your original -creation, you may submit it to Valve separate from any Contribution, -explicitly identifying it as sourced from a third party, stating the details -of its origin, and informing Valve of any license or other restriction of -which you are personally aware. - - -Valve is happy to accept pull requests and issues in the source-sdk-2013 -repository in these cases: - * Changes that fix bugs in the SDK deployment process itself. The repository - should build out of the box, and anything that prevents that is a pull - request we want. - * High priority bugs in HL2, the Episodes, or HL2MP that can be fixed in - client.dll or server.dll. - -For other changes we suggest that you issue a pull request to one of these -fine community-maintained repositories instead: - https://developer.valvesoftware.com/wiki/Source-sdk-2013-community-repos - -If you are going to make a pull request, please keep them as granular as -possible. Pull requests with 3-4 unrelated changes in them aren't going to -be accepted. diff --git a/README.md b/README.md index 4344b6d8..804f200a 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,226 @@ +//========================================================================================================================= + + Mapbase v7.3 - Source 2013 + https://github.com/mapbase-source/source-sdk-2013 + https://www.moddb.com/mods/mapbase + +//========================================================================================================================= + +This repository contains code from Mapbase, a modification of the Source 2013 SDK which serves as a combined package +of general-purpose improvements, fixes, and utility features for mods. + +Mapbase's main content in this repository may include: + +- 80+ custom entities (new logic entities, filters, etc.) +- Hundreds of Inputs/Outputs/KeyValues additions and modifications +- Custom SDK_ shaders with improvements adapted from Alien Swarm SDK code, including projected texture fixes and radial fog +- Custom VScript implementation based on public Squirrel API and Alien Swarm SDK interfaces/descriptions +- Additional gameplay control for Half-Life 2 mods, including grenade features for more NPCs and singleplayer respawning +- More map-specific capabilities for maps unable to branch into their own mods, e.g. MP mods or map compilations +- View rendering changes for drawing 3D skyboxes and RT-based entities +- Countless other fixes and improvements + +For more information, view this page: +https://github.com/mapbase-source/source-sdk-2013/wiki/Introduction-to-Mapbase + +//========================================================================================================================= + +Mapbase is an open-source project and its contents can be distributed and used at the discretion of its users. However, this project contains content from +a vast number of different sources which have their own licensing or attribution requirements. We try to handle most of that ourselves, but users who plan on +distributing Mapbase content are expected to comply with certain rules. + + Up-to-date information about Mapbase content usage and credit are addressed in this article on Mapbase's wiki: + https://github.com/mapbase-source/source-sdk-2013/wiki/Using-Mapbase-Content + +//========================================================================================================================= + +>>>>>>>> EXTERNAL CONTENT USED IN MAPBASE <<<<<<<< + +The Mapbase project is a combination of original code from its creators, code contributed by other Source modders, and code borrowed from open-source articles +and repositories (especially ones which are specifically published as free source code). One of Mapbase's goals is to make the most popular fixes and the most obscure +or complicated code changes accessible and easy to use for level designers and other kinds of Source modders who would otherwise have no idea how to implement them. + +*** DISCLAIMER: Mapbase has a strict no-leak-content policy and only allows content created directly by contributors or content originating from open-source repositories. +If you believe any content in Mapbase originates from any leak or unauthorized source (Valve or otherwise), please contact Blixibon immediately. +Mapbase is intended to be usable by everyone, including licensed Source projects and Steam mods. *** + +Mapbase uses content from the following non-Source SDK 2013 Valve games or SDKs: + +-- Alien Swarm SDK (Used to backport features and code from newer branches of Source into a Source 2013/Half-Life 2 environment) +-- Source SDK 2007 Code (Used to implement some of Tony Sergi's code changes) + +-- Alien Swarm (Used to port assets from the aforementioned SDK code features, e.g. game instructor icons) +-- Left 4 Dead (Used to port certain animations as well as assets from the aforementioned SDK code features, e.g. particle rain) +-- Half-Life: Source (Used to port friction tool textures) + +Valve allows assets from these titles to be distributed for modding purposes. Note that ported assets are only used in the release build, not the code repository. + +Mapbase may also contain new third-party software distributed under specific licensing. Please see the bottom of thirdpartylegalnotices.txt for more information. + +Here's a list of Mapbase's other external code sources: + +- https://github.com/95Navigator/insolence-2013 (Initial custom shader code and projected texture improvements; also used to implement ASW SDK particle precipitation code) +-- https://github.com/Biohazard90/g-string_2013 (Custom shadow filters, included indirectly via Insolence repo) +-- https://github.com/KyleGospo/City-17-Episode-One-Source (Brush phong and projected texture changes, included indirectly via Insolence repo) +- https://github.com/DownFall-Team/DownFall (Multiple skybox code and fix for ent_fire delay not using floats; Also used as a guide to port certain Alien Swarm SDK changes to Source 2013, +including radial fog, rope code, and treesway) +- https://github.com/momentum-mod/game (Used as a guide to port postprocess_controller and env_dof_controller to Source 2013 from the Alien Swarm SDK) +- https://github.com/DeathByNukes/source-sdk-2013 (VBSP manifest fixes) +- https://github.com/entropy-zero/source-sdk-2013 (skill_changed game event) +- https://github.com/Nbc66/source-sdk-2013-ce/tree/v142 (Base for VS2019 toolset support) + +//------------------------------------------------------------------------------------------------------------------------- + +Valve Developer Community (VDC) sources: + +- https://developer.valvesoftware.com/wiki/Dynamic_RTT_shadow_angles_in_Source_2007 (Dynamic RTT shadow angles by Saul Rennison) +- https://developer.valvesoftware.com/wiki/Parallax_Corrected_Cubemaps (Parallax corrected cubemaps implementation from Brian Charles) +- https://developer.valvesoftware.com/wiki/Adding_the_Game_Instructor (ASW SDK game instructor adapted to Source 2013 by Kolesias123; was implemented based on a translated article) +- https://developer.valvesoftware.com/wiki/Brush_ladders (Functional func_ladders in Source 2013) +- https://developer.valvesoftware.com/wiki/CAreaPortalOneWay (func_areaportal_oneway) +- https://developer.valvesoftware.com/wiki/Implementing_Discord_RPC (Discord RPC implementation; Mapbase has its own framework originally based on this article) +- https://developer.valvesoftware.com/wiki/Rain_splashes (NOTE: This code is not completely used in Mapbase, but may still exist in its codebase) +- https://developer.valvesoftware.com/wiki/Hand_Viewmodels (NOTE: This code is not completely supported by default because Mapbase does not yet have weapon viewmodels which support +interchangeable arms; this may change in the future) + +- https://developer.valvesoftware.com/wiki/General_SDK_Snippets_%26_Fixes (Various snippets on the article, including the dropship gun fix) +- https://developer.valvesoftware.com/wiki/Memory_Leak_Fixes (Most of these snippets were applied in later SDK updates, but some were not and have been added to Mapbase) +- https://developer.valvesoftware.com/wiki/Env_projectedtexture/fixes (Misc. env_projectedtexture fixes; Some of these are superceded by Alien Swarm-based changes and not used) +- https://developer.valvesoftware.com/wiki/Scenes.image (Original raw VCD file support; Code was improved for Mapbase and the article was later updated with it) +- https://developer.valvesoftware.com/wiki/Extending_Prop_Sphere (prop_sphere customization) +- https://developer.valvesoftware.com/wiki/TF2_Glow_Effect_(2013_SDK) (Glow effect) +- https://developer.valvesoftware.com/wiki/CFuncMoveLinear_ParentingFix (func_movelinear parenting fix; Code was improved for Mapbase and the article was later updated with it) +- https://developer.valvesoftware.com/wiki/Viewmodel_Prediction_Fix (Viewmodel prediction fix) +- https://developer.valvesoftware.com/wiki/Owner#Collisions_with_owner (FSOLID_COLLIDE_WITH_OWNER flag) +- https://developer.valvesoftware.com/wiki/Npc_clawscanner#Strider_Scout_Issue (npc_clawscanner strider scout fix) +- https://developer.valvesoftware.com/wiki/Ambient_generic:_stop_and_toggle_fix (Fixes for stopping/toggling ambient_generic) +- https://developer.valvesoftware.com/wiki/Func_clip_vphysics ("Start Disabled" keyvalue fix) +- https://developer.valvesoftware.com/wiki/Importing_CSS_Weapons_Into_HL2 (CS:S viewmodel chirality) + +//------------------------------------------------------------------------------------------------------------------------- + +Direct contributions: + +- https://github.com/mapbase-source/source-sdk-2013/pull/3 ("playvideo" command playback fix from Avanate) +- https://github.com/mapbase-source/source-sdk-2013/pull/5 (Custom VScript implementation by ReDucTor; was placed into feature branch before being merged in a subsequent PR) +- https://github.com/mapbase-source/source-sdk-2013/pull/60 (Adjustment by RoyaleNoir to one of Saul's VDC changes) +- https://github.com/mapbase-source/source-sdk-2013/pull/84 (CS:S viewmodel chirality from 1upD) +- https://github.com/mapbase-source/source-sdk-2013/pull/116 (vgui_movie_display mute keyvalue from Alivebyte/rzkid) +- https://github.com/mapbase-source/source-sdk-2013/pull/140 (logic_substring entity and icon created by moofemp) +- https://github.com/mapbase-source/source-sdk-2013/pull/143 (Propper features for VBSP from Matty-64) +- https://github.com/mapbase-source/source-sdk-2013/pull/174 (Fix for multiply defined symbols in later toolsets from und) +- https://github.com/mapbase-source/source-sdk-2013/issues/201 (env_projectedtexture shadow filter keyvalue from celisej567) +- https://github.com/mapbase-source/source-sdk-2013/pull/193 (RTB:R info_particle_system_coordinate by arbabf and Iridium77) +- https://github.com/mapbase-source/source-sdk-2013/pull/193 (Infinite prop_interactable cooldown by arbabf) +- https://github.com/mapbase-source/source-sdk-2013/pull/229 (Extended point_bugbait functionality by arbabf) +- https://github.com/mapbase-source/source-sdk-2013/pull/236 (Toggleable prop sprinting by Crimson-X1) +- https://github.com/mapbase-source/source-sdk-2013/pull/237 (Commander goal trace fix by Agrimar) +- https://github.com/mapbase-source/source-sdk-2013/pull/245 (ViewPunch random fix by Mr0maks) +- https://github.com/mapbase-source/source-sdk-2013/pull/248 (soundlevel_t conversation warning fix by Mechami) +- https://github.com/mapbase-source/source-sdk-2013/pull/266 ("OnPhysGunPull" output in CPhysicsProp by rlenhub) +- https://github.com/mapbase-source/source-sdk-2013/pull/292 (env_headcrabcanister random spawn type by arbabf) +- https://github.com/mapbase-source/source-sdk-2013/pull/294 (SDK_LightmappedGeneric editor blend swap fix by azzyr) +- https://github.com/mapbase-source/source-sdk-2013/pull/308 (BlurFilterY fix by Wikot235) +- https://github.com/mapbase-source/source-sdk-2013/pull/312 (Zombie improvements by Wikot235) +- https://github.com/mapbase-source/source-sdk-2013/pull/315 (env_flare crash fix by Wikot235) +- https://github.com/mapbase-source/source-sdk-2013/pull/324 (server-only info/func_null by SirYodaJedi) +- https://github.com/mapbase-source/source-sdk-2013/pull/333 (Cvar to transition levels while in MOVETYPE_NOCLIP by Wikot235) +- https://github.com/mapbase-source/source-sdk-2013/pull/342 (NaN particle cull radius fix by celisej567) +- https://github.com/mapbase-source/mapbase-game-src/pull/1 (Advanced video options duplicate field name fix by arbabf; This is asset-based and not reflected in the code) +- https://github.com/mapbase-source/mapbase-game-src/pull/2 (gameinfo.txt typo fix by CarePackage17; This is asset-based and not reflected in the code) +- https://github.com/mapbase-source/mapbase-game-src/pull/3 (HudMessage cutoff fix by arbabf; This is asset-based and not reflected in the code) +- Demo autorecord code provided by Klems +- cc_emit crash fix provided by 1upD +- Custom HL2 ammo crate models created by Rykah (Textures created by Blixibon; This is asset-based and, aside from the SLAM crate, not reflected in the code) +- Combine lock hardware on door01_left.mdl created by Kralich (This is asset-based and not reflected in the code) +- npc_vehicledriver fixes provided by CrAzY +- npc_combine cover behavior patches provided by iohnnyboy +- logic_playmovie icon created by URAKOLOUY5 (This is asset-based and not reflected in the code) +- Dropship APC save/load fix provided by Cvoxulary + +== Contributions from samisalreadytaken: +=-- https://github.com/mapbase-source/source-sdk-2013/pull/47 (VScript utility/consistency changes) +=-- https://github.com/mapbase-source/source-sdk-2013/pull/59 (New VScript functions and singletons based on API documentation in later Source/Source 2 games) +=-- https://github.com/mapbase-source/source-sdk-2013/pull/80 (More VScript changes, including support for extremely flexible client/server messaging) +=-- https://github.com/mapbase-source/source-sdk-2013/pull/105 (VScript fixes and optimizations, Vector class extensions, custom convars/commands) +=-- https://github.com/mapbase-source/source-sdk-2013/pull/114 (VScript fixes and extensions) +=-- https://github.com/mapbase-source/source-sdk-2013/pull/122 (Minor VScript-related adjustments) +=-- https://github.com/mapbase-source/source-sdk-2013/pull/148 (Minor fixup) +=-- https://github.com/mapbase-source/source-sdk-2013/pull/167 (Security fixes) +=-- https://github.com/mapbase-source/source-sdk-2013/pull/168 (Squirrel update) +=-- https://github.com/mapbase-source/source-sdk-2013/pull/169 (VScript VGUI) +=-- https://github.com/mapbase-source/source-sdk-2013/pull/171 (VScript documentation sorting) +=-- https://github.com/mapbase-source/source-sdk-2013/pull/173 (VScript fixes and optimizations) +=-- https://github.com/mapbase-source/source-sdk-2013/pull/192 (VScript hook manager and fixes) +=-- https://github.com/mapbase-source/source-sdk-2013/pull/206 (Fix CScriptNetMsgHelper::WriteEntity()) +=-- https://github.com/mapbase-source/source-sdk-2013/pull/213 (VScript HUD visibility control, optimizations for 3D skybox angles/fake worldportals) +=-- https://github.com/mapbase-source/source-sdk-2013/pull/229 (VScript VGUI HUD viewport parenting, game_text and vgui_text_display VScript font fallback) +=-- https://github.com/mapbase-source/source-sdk-2013/pull/260 (CScriptNetPropManager rewrite) +=-- https://github.com/mapbase-source/source-sdk-2013/pull/261 (Misc VScript additions) +=-- https://github.com/mapbase-source/source-sdk-2013/pull/279 (weapon_custom_scripted fixes) +=-- https://github.com/mapbase-source/source-sdk-2013/pull/332 (Fix OOB access) + +== Contributions from z33ky: +=-- https://github.com/mapbase-source/source-sdk-2013/pull/21 (Various GCC/Linux compilation fixes) +=-- https://github.com/mapbase-source/source-sdk-2013/pull/95 (Additional GCC/Linux compilation fixes) +=-- https://github.com/mapbase-source/source-sdk-2013/pull/117 (Additional GCC/Linux compilation fixes) +=-- https://github.com/mapbase-source/source-sdk-2013/pull/124 (Memory error fixes) +=-- https://github.com/mapbase-source/source-sdk-2013/pull/130 (Memory error fixes) +=-- https://github.com/mapbase-source/source-sdk-2013/pull/131 (env_projectedtexture target shadows fix) +=-- https://github.com/mapbase-source/source-sdk-2013/pull/132 (Console error fix) +=-- https://github.com/mapbase-source/source-sdk-2013/pull/152 (Additional GCC/Linux compilation fixes) +=-- https://github.com/mapbase-source/source-sdk-2013/pull/159 (Additional GCC/Linux compilation fixes) +=-- https://github.com/mapbase-source/source-sdk-2013/pull/162 (VS2019 exception specification fix) +=-- https://github.com/mapbase-source/source-sdk-2013/pull/170 (HL2 non-Episodic build fix) +=-- https://github.com/mapbase-source/source-sdk-2013/pull/322 (Small Mapbase fixes) + +== Contributions from Petercov: +=-- https://github.com/mapbase-source/source-sdk-2013/pull/182 (NPCs load dynamic interactions from all animation MDLs) +=-- https://github.com/mapbase-source/source-sdk-2013/pull/184 (Projected texture horizontal FOV shadow fix) +=-- https://github.com/mapbase-source/source-sdk-2013/pull/185 (Fix enemyfinders becoming visible when they wake) +=-- https://github.com/mapbase-source/source-sdk-2013/pull/186 (Fix for brightly glowing teeth) +=-- https://github.com/mapbase-source/source-sdk-2013/pull/183 (Enhanced custom weapons support) +=-- https://github.com/mapbase-source/source-sdk-2013/pull/230 (Caption fixes) +=-- https://github.com/mapbase-source/source-sdk-2013/pull/231 (Sentence source bug fix) +=-- https://github.com/mapbase-source/source-sdk-2013/pull/264 (Outputs for vgui_screen) + +//------------------------------------------------------------------------------------------------------------------------- + +Other sources: + +- Various code from Source SDK 2013 pull requests on the GitHub repository (https://github.com/ValveSoftware/source-sdk-2013/pulls): +-- https://github.com/ValveSoftware/source-sdk-2013/pull/441 (Smooth scrape sound oversight fix) +-- https://github.com/ValveSoftware/source-sdk-2013/pull/436 (VRAD debug counter fix + thread bump) +-- https://github.com/ValveSoftware/source-sdk-2013/pull/416 (Ragdoll null pointer dereference fix) +-- https://github.com/ValveSoftware/source-sdk-2013/pull/401 (func_rot_button "Starts locked" flag fix) +-- https://github.com/ValveSoftware/source-sdk-2013/pull/391 (VBSP func_detail smoothing group fix) +-- https://github.com/ValveSoftware/source-sdk-2013/pull/362 (npc_manhack npc_maker fix; Adjusted for formatting and save/restore in Mapbase) +-- https://github.com/Petercov/Source-PlusPlus/commit/ecdf50c48cd31dec4dbdb7fea2d0780e7f0dd8ec (used as a guide for porting the Alien Swarm SDK response system) +- https://github.com/momentum-mod/game/blob/1d066180b3bf74830c51e6914d46c40b0bea1fc2/mp/src/game/server/player.cpp#L6543 (spec_goto fix) +- Poison zombie barnacle crash fix implemented based on a snippet from HL2: Plus posted by Agent Agrimar on Discord (Mapbase makes the barnacle recognize it as poison just like poison headcrabs) +- https://gamebanana.com/skins/172192 (Airboat handling fix; This is asset-based and not reflected in the code) +- Vortigaunt LOS fix originally created by dky.tehkingd.u for HL2:CE +- https://combineoverwiki.net/wiki/File:Combine_main_symbol.svg ("icon_combine" instructor icon in "materials/vgui/hud/gameinstructor_hl2_1"; This is asset-based and not reflected in the code) + +//------------------------------------------------------------------------------------------------------------------------- + +If there is anything missing from this list, please contact Blixibon. + +//========================================================================================================================= + +Aside from the content list above, Mapbase has more descriptive and up-to-date credits on this wiki article: +https://github.com/mapbase-source/source-sdk-2013/wiki/Mapbase-Credits + +Other relevant articles: +* https://github.com/mapbase-source/source-sdk-2013/wiki/Mapbase-Disclaimers +* https://github.com/mapbase-source/source-sdk-2013/wiki/Frequently-Asked-Questions-(FAQ) + +//------------------------------------------------------------------------------------------------------------------------- + +In memory of Holly Liberatore (moofemp) + +//========================================================================================================================= + # Source SDK 2013 Source code for Source SDK 2013. diff --git a/src/createmaptoolsprojects b/src/createmaptoolsprojects new file mode 100755 index 00000000..9cc950cb --- /dev/null +++ b/src/createmaptoolsprojects @@ -0,0 +1,5 @@ +#!/bin/bash + +pushd `dirname $0` +devtools/bin/vpc /hl2 /episodic +maptools /mksln maptools +popd diff --git a/src/createmaptoolsprojects.bat b/src/createmaptoolsprojects.bat new file mode 100644 index 00000000..e21a53ff --- /dev/null +++ b/src/createmaptoolsprojects.bat @@ -0,0 +1 @@ +devtools\bin\vpc.exe +maptools /mksln maptools.sln diff --git a/src/createshadersprojects b/src/createshadersprojects new file mode 100755 index 00000000..ff4823e0 --- /dev/null +++ b/src/createshadersprojects @@ -0,0 +1,5 @@ +#!/bin/bash + +pushd `dirname $0` +devtools/bin/vpc /hl2 /episodic +shaders /mksln shaders +popd diff --git a/src/createshadersprojects.bat b/src/createshadersprojects.bat new file mode 100644 index 00000000..43569ae5 --- /dev/null +++ b/src/createshadersprojects.bat @@ -0,0 +1 @@ +devtools\bin\vpc.exe /hl2 /episodic +shaders /mksln shaders.sln diff --git a/src/fgdlib/gamedata.cpp b/src/fgdlib/gamedata.cpp index f3689ee9..3d1d75e6 100644 --- a/src/fgdlib/gamedata.cpp +++ b/src/fgdlib/gamedata.cpp @@ -12,6 +12,9 @@ #include "filesystem_tools.h" #include "tier1/strtools.h" #include "utlmap.h" +#ifdef MAPBASE +#include "fmtstr.h" +#endif // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" @@ -579,6 +582,34 @@ GDclass *GameData::BeginInstanceRemap( const char *pszClassName, const char *psz return m_InstanceClass; } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: Sets up for additional instance remap fixes from Mapbase +//----------------------------------------------------------------------------- +void GameData::SetupInstanceRemapParams( int iStartNodes, int iStartBrushSide, bool bRemapVecLines ) +{ + // Set the numer of nodes in the level + m_InstanceStartAINodes = iStartNodes; + + // If we have a "nodeid" key, set it to ivNodeDest so it's properly recognized + // during AI node remapping + GDinputvariable *var = m_InstanceClass->VarForName( "nodeid" ); + if ( var ) + { + var->ForceSetType( ivNodeDest ); + } + + //--------------------------------------------- + + // Set the number of brush sides in the level + m_InstanceStartSide = iStartBrushSide; + + //--------------------------------------------- + + m_bRemapVecLines = bRemapVecLines; +} +#endif + enum tRemapOperation { @@ -586,6 +617,13 @@ enum tRemapOperation REMAP_POSITION, REMAP_ANGLE, REMAP_ANGLE_NEGATIVE_PITCH, +#ifdef MAPBASE + // Remaps the node ID for instance/manifest AI node support + REMAP_NODE_ID, + + // Remaps brush sides and sidelists + REMAP_SIDES, +#endif }; @@ -624,6 +662,12 @@ bool GameData::RemapKeyValue( const char *pszKey, const char *pszInValue, char * RemapOperation.Insert( ivOrigin, REMAP_POSITION ); RemapOperation.Insert( ivAxis, REMAP_ANGLE ); RemapOperation.Insert( ivAngleNegativePitch, REMAP_ANGLE_NEGATIVE_PITCH ); +#ifdef MAPBASE + RemapOperation.Insert( ivNodeDest, REMAP_NODE_ID ); + RemapOperation.Insert( ivSide, REMAP_SIDES ); + RemapOperation.Insert( ivSideList, REMAP_SIDES ); + RemapOperation.Insert( ivVecLine, REMAP_POSITION ); +#endif } if ( !m_InstanceClass ) @@ -657,6 +701,12 @@ bool GameData::RemapKeyValue( const char *pszKey, const char *pszInValue, char * case REMAP_POSITION: { +#ifdef MAPBASE + // Only remap ivVecLine if the keyvalue is enabled + if (KVType == ivVecLine && !m_bRemapVecLines) + break; +#endif + Vector inPoint( 0.0f, 0.0f, 0.0f ), outPoint; sscanf ( pszInValue, "%f %f %f", &inPoint.x, &inPoint.y, &inPoint.z ); @@ -697,6 +747,54 @@ bool GameData::RemapKeyValue( const char *pszKey, const char *pszInValue, char * sprintf( pszOutValue, "%g", -outAngles.x ); // just the pitch } break; + +#ifdef MAPBASE + case REMAP_NODE_ID: + { + int value = atoi( pszInValue ); + if (value == -1) + break; + + //Warning( " %s %s: Remapped %i to %i", m_InstanceClass->GetName(), KVVar->GetName(), value, value + m_InstanceStartAINodes ); + + value += m_InstanceStartAINodes; + + sprintf( pszOutValue, "%i", value ); + } + break; + + case REMAP_SIDES: + { + CUtlStringList sideList; + V_SplitString( pszInValue, " ", sideList ); + + // Convert sides + CUtlStringList newSideList; + for (int i = 0; i < sideList.Count(); i++) + { + int iSide = atoi( sideList[i] ); + + //Warning( " %s %s: Remapped %i to %i", m_InstanceClass->GetName(), KVVar->GetName(), iSide, iSide + m_InstanceStartSide ); + + iSide += m_InstanceStartSide; + + newSideList.AddToTail( const_cast( CNumStr( iSide ).String() ) ); + } + + // Initial side + strcpy( pszOutValue, newSideList[0] ); + + // Start at 1 for subsequent sides + for (int i = 1; i < newSideList.Count(); i++) + { + // Any subsequent sides are spaced + sprintf( pszOutValue, "%s %s", pszOutValue, newSideList[i] ); + } + + //Warning("Old side list: \"%s\", new side list: \"%s\"\n", pszInValue, pszOutValue); + } + break; +#endif } return ( strcmpi( pszInValue, pszOutValue ) != 0 ); @@ -715,7 +813,11 @@ bool GameData::RemapNameField( const char *pszInValue, char *pszOutValue, TNameF { strcpy( pszOutValue, pszInValue ); +#ifdef MAPBASE + if ( pszInValue[ 0 ] && pszInValue[ 0 ] != '@' && pszInValue[ 0 ] != '!' ) +#else if ( pszInValue[ 0 ] && pszInValue[ 0 ] != '@' ) +#endif { // ! at the start of a value means it is global and should not be remaped switch( NameFixup ) { diff --git a/src/game/client/C_Env_Projected_Texture.h b/src/game/client/C_Env_Projected_Texture.h index b15ea6ef..cb626814 100644 --- a/src/game/client/C_Env_Projected_Texture.h +++ b/src/game/client/C_Env_Projected_Texture.h @@ -14,6 +14,105 @@ #include "c_baseentity.h" #include "basetypes.h" +#ifdef ASW_PROJECTED_TEXTURES + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class C_EnvProjectedTexture : public C_BaseEntity +{ + DECLARE_CLASS( C_EnvProjectedTexture, C_BaseEntity ); +public: + DECLARE_CLIENTCLASS(); + + void SetMaterial( IMaterial *pMaterial ); + void SetLightColor( byte r, byte g, byte b, byte a ); + void SetSize( float flSize ); + void SetRotation( float flRotation ); + + virtual void OnDataChanged( DataUpdateType_t updateType ); + void ShutDownLightHandle( void ); + +#ifdef MAPBASE + virtual void Simulate(); +#else + virtual bool Simulate(); +#endif + + void UpdateLight( void ); + + C_EnvProjectedTexture(); + ~C_EnvProjectedTexture(); + + static void SetVisibleBBoxMinHeight( float flVisibleBBoxMinHeight ) { m_flVisibleBBoxMinHeight = flVisibleBBoxMinHeight; } + static float GetVisibleBBoxMinHeight( void ) { return m_flVisibleBBoxMinHeight; } + static C_EnvProjectedTexture *Create( ); + +private: + + inline bool IsBBoxVisible( void ); + bool IsBBoxVisible( Vector vecExtentsMin, + Vector vecExtentsMax ); + + ClientShadowHandle_t m_LightHandle; + bool m_bForceUpdate; + + EHANDLE m_hTargetEntity; +#ifdef MAPBASE + bool m_bDontFollowTarget; +#endif + + bool m_bState; + bool m_bAlwaysUpdate; + float m_flLightFOV; +#ifdef MAPBASE + float m_flLightHorFOV; +#endif + bool m_bEnableShadows; + bool m_bLightOnlyTarget; + bool m_bLightWorld; + bool m_bCameraSpace; + float m_flBrightnessScale; + color32 m_LightColor; + Vector m_CurrentLinearFloatLightColor; + float m_flCurrentLinearFloatLightAlpha; +#ifdef MAPBASE + float m_flCurrentBrightnessScale; +#endif + float m_flColorTransitionTime; + float m_flAmbient; + float m_flNearZ; + float m_flFarZ; + char m_SpotlightTextureName[ MAX_PATH ]; + CTextureReference m_SpotlightTexture; + int m_nSpotlightTextureFrame; + int m_nShadowQuality; +#ifdef MAPBASE + float m_flConstantAtten; + float m_flLinearAtten; + float m_flQuadraticAtten; + float m_flShadowAtten; + float m_flShadowFilter; + + bool m_bAlwaysDraw; + //bool m_bProjectedTextureVersion; +#endif + + Vector m_vecExtentsMin; + Vector m_vecExtentsMax; + + static float m_flVisibleBBoxMinHeight; +}; + + + +bool C_EnvProjectedTexture::IsBBoxVisible( void ) +{ + return IsBBoxVisible( GetAbsOrigin() + m_vecExtentsMin, GetAbsOrigin() + m_vecExtentsMax ); +} + +#else + //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- @@ -62,4 +161,6 @@ public: C_EnvProjectedTexture* GetEnvProjectedTextureList(); +#endif + #endif // C_ENVPROJECTEDTEXTURE_H diff --git a/src/game/client/c_baseanimating.cpp b/src/game/client/c_baseanimating.cpp index d365dc14..ee992b60 100644 --- a/src/game/client/c_baseanimating.cpp +++ b/src/game/client/c_baseanimating.cpp @@ -54,6 +54,9 @@ #include "replay/replay_ragdoll.h" #include "studio_stats.h" #include "tier1/callqueue.h" +#ifdef MAPBASE +#include "viewrender.h" +#endif #ifdef TF_CLIENT_DLL #include "c_tf_player.h" @@ -258,6 +261,9 @@ LINK_ENTITY_TO_CLASS( client_ragdoll, C_ClientRagdoll ); BEGIN_DATADESC( C_ClientRagdoll ) DEFINE_FIELD( m_bFadeOut, FIELD_BOOLEAN ), DEFINE_FIELD( m_bImportant, FIELD_BOOLEAN ), +#ifdef MAPBASE + DEFINE_FIELD( m_flForcedRetireTime, FIELD_FLOAT ), +#endif DEFINE_FIELD( m_iCurrentFriction, FIELD_INTEGER ), DEFINE_FIELD( m_iMinFriction, FIELD_INTEGER ), DEFINE_FIELD( m_iMaxFriction, FIELD_INTEGER ), @@ -280,6 +286,94 @@ BEGIN_DATADESC( C_ClientRagdoll ) END_DATADESC() +#ifdef MAPBASE_VSCRIPT +BEGIN_ENT_SCRIPTDESC( C_ClientRagdoll, C_BaseAnimating, "Client-side ragdolls" ) + + DEFINE_SCRIPTFUNC_NAMED( SUB_Remove, "FadeOut", "Fades out the ragdoll and removes it from the client." ) + + // TODO: Proper shared ragdoll funcs? + DEFINE_SCRIPTFUNC_NAMED( ScriptGetRagdollObject, "GetRagdollObject", "Gets the ragdoll object of the specified index." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptGetRagdollObjectCount, "GetRagdollObjectCount", "Gets the number of ragdoll objects on this ragdoll." ) + +END_SCRIPTDESC(); + +ScriptHook_t C_BaseAnimating::g_Hook_OnClientRagdoll; +ScriptHook_t C_BaseAnimating::g_Hook_FireEvent; +//ScriptHook_t C_BaseAnimating::g_Hook_BuildTransformations; +#endif + +BEGIN_ENT_SCRIPTDESC( C_BaseAnimating, C_BaseEntity, "Animating models client-side" ) +#ifdef MAPBASE_VSCRIPT + DEFINE_SCRIPTFUNC_NAMED( ScriptGetPoseParameter, "GetPoseParameter", "Get the specified pose parameter's value" ) +#endif + DEFINE_SCRIPTFUNC_NAMED( ScriptSetPoseParameter, "SetPoseParameter", "Set the specified pose parameter to the specified value" ) + DEFINE_SCRIPTFUNC( IsSequenceFinished, "Ask whether the main sequence is done playing" ) +#ifdef MAPBASE_VSCRIPT + DEFINE_SCRIPTFUNC_NAMED( ScriptLookupAttachment, "LookupAttachment", "Get the named attachement id" ) + DEFINE_SCRIPTFUNC_NAMED( ScriptGetAttachmentOrigin, "GetAttachmentOrigin", "Get the attachement id's origin vector" ) + DEFINE_SCRIPTFUNC_NAMED( ScriptGetAttachmentAngles, "GetAttachmentAngles", "Get the attachement id's angles as a p,y,r vector" ) + DEFINE_SCRIPTFUNC_NAMED( ScriptGetAttachmentMatrix, "GetAttachmentMatrix", "Get the attachement id's matrix transform" ) + + DEFINE_SCRIPTFUNC( LookupBone, "Get the named bone id" ) + DEFINE_SCRIPTFUNC_NAMED( ScriptGetBoneTransform, "GetBoneTransform", "Get the transform for the specified bone" ) + DEFINE_SCRIPTFUNC_NAMED( ScriptSetBoneTransform, "SetBoneTransform", "Set the transform for the specified bone" ) + + DEFINE_SCRIPTFUNC_NAMED( ScriptAttachEntityToBone, "AttachEntityToBone", "Attaches this entity to the specified target and bone. Also allows for optional local position offset" ) + DEFINE_SCRIPTFUNC_NAMED( ScriptRemoveBoneAttachment, "RemoveBoneAttachment", "Removes the specified bone attachment" ) + //DEFINE_SCRIPTFUNC( RemoveBoneAttachments, "Removes all bone attachments" ) + DEFINE_SCRIPTFUNC( DestroyBoneAttachments, "Destroys all bone attachments" ) + DEFINE_SCRIPTFUNC( GetNumBoneAttachments, "Gets the number of bone attachments" ) + DEFINE_SCRIPTFUNC_NAMED( ScriptGetBoneAttachment, "GetBoneAttachment", "Gets the specified bone attachment" ) + + DEFINE_SCRIPTFUNC( SetBodygroup, "Sets a bodygroup") + DEFINE_SCRIPTFUNC( GetBodygroup, "Gets a bodygroup" ) + DEFINE_SCRIPTFUNC( GetBodygroupName, "Gets a bodygroup name" ) + DEFINE_SCRIPTFUNC( FindBodygroupByName, "Finds a bodygroup by name" ) + DEFINE_SCRIPTFUNC( GetBodygroupCount, "Gets the number of models in a bodygroup" ) + DEFINE_SCRIPTFUNC( GetNumBodyGroups, "Gets the number of bodygroups" ) + + DEFINE_SCRIPTFUNC( GetSequence, "Gets the current sequence" ) + DEFINE_SCRIPTFUNC( SetSequence, "Sets the current sequence" ) + DEFINE_SCRIPTFUNC( SequenceLoops, "Does the current sequence loop?" ) + DEFINE_SCRIPTFUNC( LookupSequence, "Gets the index of the specified sequence name" ) + DEFINE_SCRIPTFUNC( LookupActivity, "Gets the ID of the specified activity name" ) + DEFINE_SCRIPTFUNC( GetSequenceName, "Gets the name of the specified sequence index" ) + DEFINE_SCRIPTFUNC( GetSequenceActivityName, "Gets the activity name of the specified sequence index" ) + DEFINE_SCRIPTFUNC_NAMED( ScriptGetSequenceMoveDist, "GetSequenceMoveDist", "Gets the move distance of the specified sequence" ) + DEFINE_SCRIPTFUNC_NAMED( ScriptGetSequenceActivity, "GetSequenceActivity", "Gets the activity ID of the specified sequence index" ) + DEFINE_SCRIPTFUNC_NAMED( ScriptSelectWeightedSequence, "SelectWeightedSequence", "Selects a sequence for the specified activity ID" ) + + DEFINE_SCRIPTFUNC( GetPlaybackRate, "" ) + DEFINE_SCRIPTFUNC( SetPlaybackRate, "" ) + DEFINE_SCRIPTFUNC( GetCycle, "" ) + DEFINE_SCRIPTFUNC( SetCycle, "" ) + DEFINE_SCRIPTFUNC_NAMED( ScriptGetSkin, "GetSkin", "Gets the model's skin" ) + DEFINE_SCRIPTFUNC( SetSkin, "Sets the model's skin" ) + + DEFINE_SCRIPTFUNC( GetForceBone, "Gets the entity's force bone, which is used to determine which bone a ragdoll should apply its force to." ) + DEFINE_SCRIPTFUNC( SetForceBone, "Sets the entity's force bone, which is used to determine which bone a ragdoll should apply its force to." ) + DEFINE_SCRIPTFUNC( GetRagdollForce, "Gets the entity's ragdoll force, which is used to apply velocity to a ragdoll." ) + DEFINE_SCRIPTFUNC( SetRagdollForce, "Sets the entity's ragdoll force, which is used to apply velocity to a ragdoll." ) + + DEFINE_SCRIPTFUNC_NAMED( ScriptBecomeRagdollOnClient, "BecomeRagdollOnClient", "" ) + DEFINE_SCRIPTFUNC( IsRagdoll, "" ) + + BEGIN_SCRIPTHOOK( C_BaseAnimating::g_Hook_OnClientRagdoll, "OnClientRagdoll", FIELD_VOID, "Called when this entity turns into a client-side ragdoll." ) + DEFINE_SCRIPTHOOK_PARAM( "ragdoll", FIELD_HSCRIPT ) + END_SCRIPTHOOK() + + BEGIN_SCRIPTHOOK( C_BaseAnimating::g_Hook_FireEvent, "FireEvent", FIELD_BOOLEAN, "Called when handling animation events. Return false to cancel base handling." ) + DEFINE_SCRIPTHOOK_PARAM( "origin", FIELD_VECTOR ) + DEFINE_SCRIPTHOOK_PARAM( "angles", FIELD_VECTOR ) + DEFINE_SCRIPTHOOK_PARAM( "event", FIELD_INTEGER ) + DEFINE_SCRIPTHOOK_PARAM( "options", FIELD_CSTRING ) + END_SCRIPTHOOK() + + //BEGIN_SCRIPTHOOK( C_BaseAnimating::g_Hook_BuildTransformations, "BuildTransformations", FIELD_VOID, "Called when building bone transformations. Allows VScript to read/write any bone with Get/SetBoneTransform." ) + //END_SCRIPTHOOK() +#endif +END_SCRIPTDESC(); + C_ClientRagdoll::C_ClientRagdoll( bool bRestoring ) { m_iCurrentFriction = 0; @@ -288,6 +382,9 @@ C_ClientRagdoll::C_ClientRagdoll( bool bRestoring ) m_bFadeOut = false; m_bFadingOut = false; m_bImportant = false; +#ifdef MAPBASE + m_flForcedRetireTime = 0.0f; +#endif m_bNoModelParticles = false; SetClassname("client_ragdoll"); @@ -368,7 +465,11 @@ void C_ClientRagdoll::OnRestore( void ) if ( m_bFadeOut == true ) { +#ifdef MAPBASE + s_RagdollLRU.MoveToTopOfLRU( this, m_bImportant, m_flForcedRetireTime ); +#else s_RagdollLRU.MoveToTopOfLRU( this, m_bImportant ); +#endif } NoteRagdollCreationTick( this ); @@ -646,6 +747,24 @@ void C_ClientRagdoll::Release( void ) BaseClass::Release(); } +#ifdef MAPBASE_VSCRIPT +HSCRIPT C_ClientRagdoll::ScriptGetRagdollObject( int iIndex ) +{ + if (iIndex < 0 || iIndex > m_pRagdoll->RagdollBoneCount()) + { + Warning("%s GetRagdollObject: Index %i not valid (%i objects)\n", GetDebugName(), iIndex, m_pRagdoll->RagdollBoneCount()); + return NULL; + } + + return g_pScriptVM->RegisterInstance( m_pRagdoll->GetElement(iIndex) ); +} + +int C_ClientRagdoll::ScriptGetRagdollObjectCount() +{ + return m_pRagdoll->RagdollBoneCount(); +} +#endif + //----------------------------------------------------------------------------- // Incremented each frame in InvalidateModelBones. Models compare this value to what it // was last time they setup their bones to determine if they need to re-setup their bones. @@ -683,6 +802,10 @@ C_BaseAnimating::C_BaseAnimating() : m_nPrevSequence = -1; m_nRestoreSequence = -1; m_pRagdoll = NULL; + m_pClientsideRagdoll = NULL; +#ifdef MAPBASE + m_pServerRagdoll = NULL; +#endif m_builtRagdoll = false; m_hitboxBoneCacheHandle = 0; int i; @@ -1446,6 +1569,115 @@ float C_BaseAnimating::ClampCycle( float flCycle, bool isLooping ) return flCycle; } +#ifdef MAPBASE_VSCRIPT +//----------------------------------------------------------------------------- +// Purpose: Returns the world location and world angles of an attachment to vscript caller +// Input : attachment name +// Output : location and angles +//----------------------------------------------------------------------------- +const Vector& C_BaseAnimating::ScriptGetAttachmentOrigin( int iAttachment ) +{ + + static Vector absOrigin; + static QAngle qa; + + C_BaseAnimating::GetAttachment( iAttachment, absOrigin, qa ); + + return absOrigin; +} + +const Vector& C_BaseAnimating::ScriptGetAttachmentAngles( int iAttachment ) +{ + + static Vector absOrigin; + static Vector absAngles; + static QAngle qa; + + C_BaseAnimating::GetAttachment( iAttachment, absOrigin, qa ); + absAngles.x = qa.x; + absAngles.y = qa.y; + absAngles.z = qa.z; + return absAngles; +} + +HSCRIPT C_BaseAnimating::ScriptGetAttachmentMatrix( int iAttachment ) +{ + static matrix3x4_t matrix; + + C_BaseAnimating::GetAttachment( iAttachment, matrix ); + return g_pScriptVM->RegisterInstance( &matrix ); +} + +void C_BaseAnimating::ScriptGetBoneTransform( int iBone, HSCRIPT hTransform ) +{ + matrix3x4_t *matTransform = HScriptToClass( hTransform ); + if (matTransform == NULL) + return; + + GetBoneTransform( iBone, *matTransform ); +} + +void C_BaseAnimating::ScriptSetBoneTransform( int iBone, HSCRIPT hTransform ) +{ + matrix3x4_t *matTransform = HScriptToClass( hTransform ); + if (matTransform == NULL) + return; + + MatrixCopy( *matTransform, GetBoneForWrite( iBone ) ); +} + +void C_BaseAnimating::ScriptAttachEntityToBone( HSCRIPT attachTarget, int boneIndexAttached, const Vector &bonePosition, const QAngle &boneAngles ) +{ + C_BaseEntity *pTarget = ToEnt( attachTarget ); + if (pTarget == NULL) + return; + + AttachEntityToBone( pTarget->GetBaseAnimating(), boneIndexAttached, bonePosition, boneAngles ); +} + +void C_BaseAnimating::ScriptRemoveBoneAttachment( HSCRIPT boneAttachment ) +{ + C_BaseEntity *pTarget = ToEnt( boneAttachment ); + if (pTarget == NULL) + return; + + RemoveBoneAttachment( pTarget->GetBaseAnimating() ); +} + +HSCRIPT C_BaseAnimating::ScriptGetBoneAttachment( int i ) +{ + return ToHScript( GetBoneAttachment( i ) ); +} + +HSCRIPT C_BaseAnimating::ScriptBecomeRagdollOnClient() +{ + C_BaseAnimating *pRagdoll = BecomeRagdollOnClient(); + if (!pRagdoll) + return NULL; + + return pRagdoll->GetScriptInstance(); +} + +float C_BaseAnimating::ScriptGetPoseParameter( const char* szName ) +{ + CStudioHdr* pHdr = GetModelPtr(); + if (pHdr == NULL) + return 0.0f; + + int iPoseParam = LookupPoseParameter( pHdr, szName ); + return GetPoseParameter( iPoseParam ); +} +#endif + +void C_BaseAnimating::ScriptSetPoseParameter(const char* szName, float fValue) +{ + CStudioHdr* pHdr = GetModelPtr(); + if (pHdr == NULL) + return; + + int iPoseParam = LookupPoseParameter(pHdr, szName); + SetPoseParameter(pHdr, iPoseParam, fValue); +} void C_BaseAnimating::GetCachedBoneMatrix( int boneIndex, matrix3x4_t &out ) { @@ -1603,7 +1835,23 @@ void C_BaseAnimating::BuildTransformations( CStudioHdr *hdr, Vector *pos, Quater } } - +#ifdef MAPBASE_VSCRIPT + //if (m_ScriptScope.IsInitialized() && g_Hook_BuildTransformations.CanRunInScope(m_ScriptScope)) + //{ + // int oldWritableBones = m_BoneAccessor.GetWritableBones(); + // int oldReadableBones = m_BoneAccessor.GetReadableBones(); + // m_BoneAccessor.SetWritableBones( BONE_USED_BY_ANYTHING ); + // m_BoneAccessor.SetReadableBones( BONE_USED_BY_ANYTHING ); + // + // // No parameters + // //ScriptVariant_t args[] = {}; + // //ScriptVariant_t returnValue; + // g_Hook_BuildTransformations.Call( m_ScriptScope, NULL, NULL /*&returnValue, args*/ ); + // + // m_BoneAccessor.SetWritableBones( oldWritableBones ); + // m_BoneAccessor.SetReadableBones( oldReadableBones ); + //} +#endif } //----------------------------------------------------------------------------- @@ -1809,6 +2057,10 @@ CollideType_t C_BaseAnimating::GetCollideType( void ) return BaseClass::GetCollideType(); } +#ifdef MAPBASE +ConVar ai_death_pose_enabled( "ai_death_pose_enabled", "1", FCVAR_NONE, "Toggles the death pose fix code, which cancels sequence transitions while a NPC is ragdolling." ); +#endif + //----------------------------------------------------------------------------- // Purpose: if the active sequence changes, keep track of the previous ones and decay them based on their decay rate //----------------------------------------------------------------------------- @@ -1825,6 +2077,14 @@ void C_BaseAnimating::MaintainSequenceTransitions( IBoneSetup &boneSetup, float return; } +#ifdef MAPBASE + if ( IsAboutToRagdoll() && ai_death_pose_enabled.GetBool() ) + { + m_nPrevNewSequenceParity = m_nNewSequenceParity; + return; + } +#endif + m_SequenceTransitioner.CheckForSequenceChange( boneSetup.GetStudioHdr(), GetSequence(), @@ -2636,14 +2896,29 @@ void C_BaseAnimating::CalculateIKLocks( float currentTime ) // debugoverlay->AddBoxOverlay( origin, Vector( -1, -1, -1 ), Vector( 1, 1, 1 ), QAngle( 0, 0, 0 ), 255, 0, 0, 0, 0 ); - float d = (pTarget->est.pos - origin).Length(); + Vector vecDelta = (origin - pTarget->est.pos); + float d = vecDelta.Length(); if ( d >= flDist) continue; flDist = d; - pTarget->SetPos( origin ); - pTarget->SetAngles( angles ); +#ifdef MAPBASE + // For blending purposes, IK attachments should obey weight + if ( pTarget->est.flWeight < 1.0f ) + { + Quaternion qTarget; + AngleQuaternion( angles, qTarget ); + + QuaternionSlerp( pTarget->est.q, qTarget, pTarget->est.flWeight, pTarget->est.q ); + pTarget->SetPos( pTarget->est.pos + (vecDelta * pTarget->est.flWeight) ); + } + else +#endif + { + pTarget->SetPos( origin ); + pTarget->SetAngles( angles ); + } // debugoverlay->AddBoxOverlay( pTarget->est.pos, Vector( -pTarget->est.radius, -pTarget->est.radius, -pTarget->est.radius ), Vector( pTarget->est.radius, pTarget->est.radius, pTarget->est.radius), QAngle( 0, 0, 0 ), 0, 255, 0, 0, 0 ); } @@ -3191,6 +3466,17 @@ int C_BaseAnimating::DrawModel( int flags ) int drawn = 0; +#ifdef MAPBASE + if (m_iViewHideFlags > 0) + { + // Hide this entity if it's not supposed to be drawn in this view. + if (m_iViewHideFlags & (1 << CurrentViewID())) + { + return 0; + } + } +#endif + #ifdef TF_CLIENT_DLL ValidateModelIndex(); #endif @@ -3694,7 +3980,11 @@ void C_BaseAnimating::DoAnimationEvents( CStudioHdr *pStudioHdr ) flEventCycle, gpGlobals->curtime ); } - + +#ifdef MAPBASE_VSCRIPT + if (ScriptHookFireEvent( GetAbsOrigin(), GetAbsAngles(), pevent[ i ].event, pevent[ i ].pszOptions() ) == false) + continue; +#endif FireEvent( GetAbsOrigin(), GetAbsAngles(), pevent[ i ].event, pevent[ i ].pszOptions() ); } @@ -3727,6 +4017,11 @@ void C_BaseAnimating::DoAnimationEvents( CStudioHdr *pStudioHdr ) gpGlobals->curtime ); } +#ifdef MAPBASE_VSCRIPT + if (ScriptHookFireEvent( GetAbsOrigin(), GetAbsAngles(), pevent[ i ].event, pevent[ i ].pszOptions() ) == false) + continue; +#endif + FireEvent( GetAbsOrigin(), GetAbsAngles(), pevent[ i ].event, pevent[ i ].pszOptions() ); } } @@ -3734,6 +4029,26 @@ void C_BaseAnimating::DoAnimationEvents( CStudioHdr *pStudioHdr ) m_flPrevEventCycle = flEventCycle; } +#ifdef MAPBASE_VSCRIPT +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool C_BaseAnimating::ScriptHookFireEvent( const Vector& origin, const QAngle& angles, int event, const char *options ) +{ + if (m_ScriptScope.IsInitialized() && g_Hook_FireEvent.CanRunInScope(m_ScriptScope)) + { + // origin, angles, event, options + ScriptVariant_t args[] = { origin, angles, event, options }; + ScriptVariant_t returnValue = true; + g_Hook_FireEvent.Call( m_ScriptScope, &returnValue, args ); + + return returnValue.Get(); + } + + return true; +} +#endif + //----------------------------------------------------------------------------- // Purpose: Parses a muzzle effect event and sends it out for drawing // Input : *options - event parameters in text format @@ -3747,7 +4062,7 @@ bool C_BaseAnimating::DispatchMuzzleEffect( const char *options, bool isFirstPer int weaponType = 0; // Get the first parameter - p = nexttoken( token, p, ' ' ); + p = nexttoken( token, sizeof( token ), p, ' ' ); // Find the weapon type if ( token[0] ) @@ -3791,7 +4106,7 @@ bool C_BaseAnimating::DispatchMuzzleEffect( const char *options, bool isFirstPer } // Get the second parameter - p = nexttoken( token, p, ' ' ); + p = nexttoken( token, sizeof( token ), p, ' ' ); int attachmentIndex = -1; @@ -4085,18 +4400,33 @@ void C_BaseAnimating::FireEvent( const Vector& origin, const QAngle& angles, int // Eject brass case CL_EVENT_EJECTBRASS1: - if ( m_Attachments.Count() > 0 ) { - if ( MainViewOrigin().DistToSqr( GetAbsOrigin() ) < (256 * 256) ) + // Check if we're a weapon, if we belong to the local player, and if the local player is in third person - if all are true, don't do a muzzleflash in this instance, because + // we're using the view models dispatch for smoothness. + if ( dynamic_cast< C_BaseCombatWeapon *>(this) != NULL ) { - Vector attachOrigin; - QAngle attachAngles; - - if( GetAttachment( 2, attachOrigin, attachAngles ) ) + C_BaseCombatWeapon *pWeapon = dynamic_cast< C_BaseCombatWeapon *>(this); + if ( pWeapon && pWeapon->GetOwner() == C_BasePlayer::GetLocalPlayer() && ::input->CAM_IsThirdPerson() ) + break; + } + + if ( ( prediction->InPrediction() && !prediction->IsFirstTimePredicted() ) ) + break; + + if ( m_Attachments.Count() > 0 ) + { + if ( MainViewOrigin().DistToSqr( GetAbsOrigin() ) < (256 * 256) ) { - tempents->EjectBrass( attachOrigin, attachAngles, GetAbsAngles(), atoi( options ) ); + Vector attachOrigin; + QAngle attachAngles; + + if( GetAttachment( 2, attachOrigin, attachAngles ) ) + { + tempents->EjectBrass( attachOrigin, attachAngles, GetAbsAngles(), atoi( options ) ); + } } } + break; } break; @@ -4110,6 +4440,36 @@ void C_BaseAnimating::FireEvent( const Vector& origin, const QAngle& angles, int case AE_NPC_MUZZLEFLASH: { // Send out the effect for an NPC +#if defined ( HL2MP ) || defined ( SDK_DLL ) // works for the modified CSS weapons included in the new template sdk. + // HL2MP - Make third person muzzleflashes as reliable as the first person ones + // while in third person the view model dispatches the muzzleflash event - note: the weapon models dispatch them too, but not frequently. + if ( IsViewModel() ) + { + C_BasePlayer *pPlayer = ToBasePlayer( dynamic_cast(this)->GetOwner() ); + if ( pPlayer && pPlayer == C_BasePlayer::GetLocalPlayer()) + { + if ( ::input->CAM_IsThirdPerson() ) + { + // Dispatch on the weapon - the player model doesn't have the attachments in hl2mp. + C_BaseCombatWeapon *pWeapon = pPlayer->GetActiveWeapon(); + if ( !pWeapon ) + break; + pWeapon->DispatchMuzzleEffect( options, false ); + break; + } + } + } + + // Check if we're a weapon, if we belong to the local player, and if the local player is in third person - if all are true, don't do a muzzleflash in this instance, because + // we're using the view models dispatch for smoothness. + if ( dynamic_cast< C_BaseCombatWeapon *>(this) != NULL ) + { + C_BaseCombatWeapon *pWeapon = dynamic_cast< C_BaseCombatWeapon *>(this); + if ( pWeapon && pWeapon->GetOwner() == C_BasePlayer::GetLocalPlayer() && ::input->CAM_IsThirdPerson() ) + break; + } + +#endif DispatchMuzzleEffect( options, false ); break; } @@ -4169,11 +4529,11 @@ void C_BaseAnimating::FireEvent( const Vector& origin, const QAngle& angles, int const char *p = options; // Bodygroup Name - p = nexttoken(token, p, ' '); + p = nexttoken( token, sizeof( token ), p, ' ' ); Q_strncpy( szBodygroupName, token, sizeof(szBodygroupName) ); // Get the desired value - p = nexttoken(token, p, ' '); + p = nexttoken( token, sizeof( token ), p, ' ' ); value = token[0] ? atoi( token ) : 0; int index_ = FindBodygroupByName( szBodygroupName ); @@ -4184,6 +4544,22 @@ void C_BaseAnimating::FireEvent( const Vector& origin, const QAngle& angles, int } break; +#ifdef MAPBASE + case AE_VSCRIPT_RUN: + { + if (!RunScript( options )) + Warning( "%s failed to run AE_VSCRIPT_RUN on client with \"%s\"\n", GetDebugName(), options ); + } + break; + + case AE_VSCRIPT_RUN_FILE: + { + if (!RunScriptFile( options )) + Warning( "%s failed to run AE_VSCRIPT_RUN_FILE on client with \"%s\"\n", GetDebugName(), options ); + } + break; +#endif + default: break; } @@ -4210,13 +4586,13 @@ void C_BaseAnimating::FireObsoleteEvent( const Vector& origin, const QAngle& ang const char *p = options; - p = nexttoken(token, p, ' '); + p = nexttoken( token, sizeof( token ), p, ' ' ); Q_strncpy( effectFunc, token, sizeof(effectFunc) ); - - p = nexttoken(token, p, ' '); + + p = nexttoken( token, sizeof( token ), p, ' ' ); iAttachment = token[0] ? atoi(token) : -1; - - p = nexttoken(token, p, ' '); + + p = nexttoken( token, sizeof( token ), p, ' ' ); iParam = token[0] ? atoi(token) : 0; if ( iAttachment != -1 && m_Attachments.Count() >= iAttachment ) @@ -4799,12 +5175,18 @@ bool C_BaseAnimating::GetRagdollInitBoneArrays( matrix3x4_t *pDeltaBones0, matri return bSuccess; } +C_ClientRagdoll *C_BaseAnimating::CreateClientRagdoll( bool bRestoring ) +{ + //DevMsg( "Creating ragdoll at tick %d\n", gpGlobals->tickcount ); + return new C_ClientRagdoll( bRestoring ); +} + C_BaseAnimating *C_BaseAnimating::CreateRagdollCopy() { //Adrian: We now create a separate entity that becomes this entity's ragdoll. //That way the server side version of this entity can go away. //Plus we can hook save/restore code to these ragdolls so they don't fall on restore anymore. - C_ClientRagdoll *pRagdoll = new C_ClientRagdoll( false ); + C_ClientRagdoll *pRagdoll = CreateClientRagdoll( false ); if ( pRagdoll == NULL ) return NULL; @@ -4855,6 +5237,14 @@ C_BaseAnimating *C_BaseAnimating::CreateRagdollCopy() pRagdoll->m_nForceBone = m_nForceBone; pRagdoll->SetNextClientThink( CLIENT_THINK_ALWAYS ); +#ifdef MAPBASE + pRagdoll->m_iViewHideFlags = m_iViewHideFlags; + + pRagdoll->m_fadeMinDist = m_fadeMinDist; + pRagdoll->m_fadeMaxDist = m_fadeMaxDist; + pRagdoll->m_flFadeScale = m_flFadeScale; +#endif + pRagdoll->SetModelName( AllocPooledString(pModelName) ); pRagdoll->SetModelScale( GetModelScale() ); return pRagdoll; @@ -4865,8 +5255,8 @@ C_BaseAnimating *C_BaseAnimating::BecomeRagdollOnClient() MoveToLastReceivedPosition( true ); GetAbsOrigin(); - C_BaseAnimating *pRagdoll = CreateRagdollCopy(); - if ( pRagdoll ) + m_pClientsideRagdoll = CreateRagdollCopy(); + if ( m_pClientsideRagdoll ) { matrix3x4_t boneDelta0[MAXSTUDIOBONES]; matrix3x4_t boneDelta1[MAXSTUDIOBONES]; @@ -4878,19 +5268,29 @@ C_BaseAnimating *C_BaseAnimating::BecomeRagdollOnClient() if ( bInitBoneArrays ) { - bInitAsClient = pRagdoll->InitAsClientRagdoll( boneDelta0, boneDelta1, currentBones, boneDt ); + bInitAsClient = m_pClientsideRagdoll->InitAsClientRagdoll( boneDelta0, boneDelta1, currentBones, boneDt ); } if ( !bInitAsClient || !bInitBoneArrays ) { Warning( "C_BaseAnimating::BecomeRagdollOnClient failed. pRagdoll:%p bInitBoneArrays:%d bInitAsClient:%d\n", - pRagdoll, bInitBoneArrays, bInitAsClient ); - pRagdoll->Release(); + m_pClientsideRagdoll, bInitBoneArrays, bInitAsClient ); + m_pClientsideRagdoll->Release(); return NULL; } + +#ifdef MAPBASE_VSCRIPT + // Hook for ragdolling + if (m_ScriptScope.IsInitialized() && g_Hook_OnClientRagdoll.CanRunInScope( m_ScriptScope )) + { + // ragdoll + ScriptVariant_t args[] = { ScriptVariant_t( m_pClientsideRagdoll->GetScriptInstance() ) }; + g_Hook_OnClientRagdoll.Call( m_ScriptScope, NULL, args ); + } +#endif } - return pRagdoll; + return m_pClientsideRagdoll; } bool C_BaseAnimating::InitAsClientRagdoll( const matrix3x4_t *pDeltaBones0, const matrix3x4_t *pDeltaBones1, const matrix3x4_t *pCurrentBonePosition, float boneDt, bool bFixedConstraints ) @@ -5359,6 +5759,11 @@ void C_BaseAnimating::StudioFrameAdvance() if ( flNewCycle < 0.0f || flNewCycle >= 1.0f ) { + if (flNewCycle >= 1.0f) + { + ReachedEndOfSequence(); + } + if ( IsSequenceLooping( hdr, GetSequence() ) ) { flNewCycle -= (int)(flNewCycle); diff --git a/src/game/client/c_baseanimating.h b/src/game/client/c_baseanimating.h index 987213cf..6de2ee59 100644 --- a/src/game/client/c_baseanimating.h +++ b/src/game/client/c_baseanimating.h @@ -38,6 +38,7 @@ class C_BaseClientShader */ class IRagdoll; +class C_ClientRagdoll; class CIKContext; class CIKState; class ConVar; @@ -79,7 +80,7 @@ public: QAngle m_angRotation; Vector m_vOriginVelocity; int m_nLastFramecount : 31; - int m_bAnglesComputed : 1; + bool m_bAnglesComputed : 1; }; @@ -95,6 +96,7 @@ public: DECLARE_CLIENTCLASS(); DECLARE_PREDICTABLE(); DECLARE_INTERPOLATION(); + DECLARE_ENT_SCRIPTDESC(); enum { @@ -166,6 +168,17 @@ public: virtual void FireObsoleteEvent( const Vector& origin, const QAngle& angles, int event, const char *options ); virtual const char* ModifyEventParticles( const char* token ) { return token; } +#ifdef MAPBASE_VSCRIPT + bool ScriptHookFireEvent( const Vector& origin, const QAngle& angles, int event, const char *options ); +#endif + +#if defined ( SDK_DLL ) || defined ( HL2MP ) + virtual void ResetEventsParity() { m_nPrevResetEventsParity = -1; } // used to force animation events to function on players so the muzzleflashes and other events occur + // so new functions don't have to be made to parse the models like CSS does in ProcessMuzzleFlashEvent + // allows the multiplayer world weapon models to declare the muzzleflashes, and other effects like sp + // without the need to script it and add extra parsing code. +#endif + // Parses and distributes muzzle flash events virtual bool DispatchMuzzleEffect( const char *options, bool isFirstPerson ); @@ -292,6 +305,7 @@ public: bool IsRagdoll() const; bool IsAboutToRagdoll() const; virtual C_BaseAnimating *BecomeRagdollOnClient(); + virtual C_ClientRagdoll *CreateClientRagdoll( bool bRestoring = false ); C_BaseAnimating *CreateRagdollCopy(); bool InitAsClientRagdoll( const matrix3x4_t *pDeltaBones0, const matrix3x4_t *pDeltaBones1, const matrix3x4_t *pCurrentBonePosition, float boneDt, bool bFixedConstraints=false ); void IgniteRagdoll( C_BaseAnimating *pSource ); @@ -345,6 +359,8 @@ public: void ClientSideAnimationChanged(); virtual unsigned int ComputeClientSideAnimationFlags(); + virtual void ReachedEndOfSequence() { return; } + virtual void ResetClientsideFrame( void ) { SetCycle( 0 ); } void SetCycle( float flCycle ); @@ -450,6 +466,41 @@ public: virtual bool IsViewModel() const; virtual void UpdateOnRemove( void ); +#ifdef MAPBASE_VSCRIPT + int ScriptLookupAttachment( const char *pAttachmentName ) { return LookupAttachment( pAttachmentName ); } + const Vector& ScriptGetAttachmentOrigin(int iAttachment); + const Vector& ScriptGetAttachmentAngles(int iAttachment); + HSCRIPT ScriptGetAttachmentMatrix(int iAttachment); + + void ScriptGetBoneTransform( int iBone, HSCRIPT hTransform ); + void ScriptSetBoneTransform( int iBone, HSCRIPT hTransform ); + + void ScriptAttachEntityToBone( HSCRIPT attachTarget, int boneIndexAttached, const Vector &bonePosition, const QAngle &boneAngles ); + void ScriptRemoveBoneAttachment( HSCRIPT boneAttachment ); + HSCRIPT ScriptGetBoneAttachment( int i ); + + int ScriptGetSequenceActivity( int iSequence ) { return GetSequenceActivity( iSequence ); } + float ScriptGetSequenceMoveDist( int iSequence ) { return GetSequenceMoveDist( GetModelPtr(), iSequence ); } + int ScriptSelectWeightedSequence( int activity ) { return SelectWeightedSequence( (Activity)activity ); } + + // For VScript + int ScriptGetSkin() { return GetSkin(); } + void SetSkin( int iSkin ) { m_nSkin = iSkin; } + + int GetForceBone() { return m_nForceBone; } + void SetForceBone( int iBone ) { m_nForceBone = iBone; } + const Vector& GetRagdollForce() { return m_vecForce; } + void SetRagdollForce( const Vector &vecForce ) { m_vecForce = vecForce; } + + HSCRIPT ScriptBecomeRagdollOnClient(); + + static ScriptHook_t g_Hook_OnClientRagdoll; + static ScriptHook_t g_Hook_FireEvent; + //static ScriptHook_t g_Hook_BuildTransformations; // UNDONE: Thread access issues + + float ScriptGetPoseParameter(const char* szName); +#endif + void ScriptSetPoseParameter(const char* szName, float fValue); protected: // View models scale their attachment positions to account for FOV. To get the unmodified // attachment position (like if you're rendering something else during the view model's DrawModel call), @@ -487,6 +538,10 @@ private: public: CRagdoll *m_pRagdoll; + C_BaseAnimating *m_pClientsideRagdoll; // From Alien Swarm SDK +#ifdef MAPBASE + C_BaseAnimating *m_pServerRagdoll; // Not from Alien Swarm SDK (note that this can exist without the entity having died) +#endif // Texture group to use int m_nSkin; @@ -660,6 +715,9 @@ public: C_ClientRagdoll( bool bRestoring = true ); DECLARE_CLASS( C_ClientRagdoll, C_BaseAnimating ); DECLARE_DATADESC(); +#ifdef MAPBASE_VSCRIPT + DECLARE_ENT_SCRIPTDESC(); +#endif // inherited from IPVSNotify virtual void OnPVSStatusChanged( bool bInPVS ); @@ -681,8 +739,17 @@ public: void FadeOut( void ); virtual float LastBoneChangedTime(); +#ifdef MAPBASE_VSCRIPT + HSCRIPT ScriptGetRagdollObject( int iIndex ); + int ScriptGetRagdollObjectCount(); +#endif + bool m_bFadeOut; bool m_bImportant; +#ifdef MAPBASE + // Required to save/restore Alien Swarm SDK ragdoll LRU forced fade + float m_flForcedRetireTime; +#endif float m_flEffectTime; private: diff --git a/src/game/client/c_basecombatcharacter.cpp b/src/game/client/c_basecombatcharacter.cpp index 846901a5..821ff8b9 100644 --- a/src/game/client/c_basecombatcharacter.cpp +++ b/src/game/client/c_basecombatcharacter.cpp @@ -35,6 +35,10 @@ C_BaseCombatCharacter::C_BaseCombatCharacter() m_bGlowEnabled = false; m_bOldGlowEnabled = false; m_bClientSideGlowEnabled = false; + m_GlowColor.Init( 0.76f, 0.76f, 0.76f ); + m_OldGlowColor = m_GlowColor; + m_GlowAlpha = 1.0f; + m_OldGlowAlpha = 1.0f; #endif // GLOWS_ENABLE } @@ -67,6 +71,8 @@ void C_BaseCombatCharacter::OnPreDataChanged( DataUpdateType_t updateType ) #ifdef GLOWS_ENABLE m_bOldGlowEnabled = m_bGlowEnabled; + m_OldGlowColor = m_GlowColor; + m_OldGlowAlpha = m_GlowAlpha; #endif // GLOWS_ENABLE } @@ -78,7 +84,7 @@ void C_BaseCombatCharacter::OnDataChanged( DataUpdateType_t updateType ) BaseClass::OnDataChanged( updateType ); #ifdef GLOWS_ENABLE - if ( m_bOldGlowEnabled != m_bGlowEnabled ) + if ( m_bOldGlowEnabled != m_bGlowEnabled || m_OldGlowColor != m_GlowColor || m_OldGlowAlpha != m_GlowAlpha ) { UpdateGlowEffect(); } @@ -107,11 +113,13 @@ void C_BaseCombatCharacter::DoMuzzleFlash() //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- -void C_BaseCombatCharacter::GetGlowEffectColor( float *r, float *g, float *b ) +void C_BaseCombatCharacter::GetGlowEffectColor( float *r, float *g, float *b, float *a ) { - *r = 0.76f; - *g = 0.76f; - *b = 0.76f; + *r = m_GlowColor.x; + *g = m_GlowColor.y; + *b = m_GlowColor.z; + if (a) + *a = m_GlowAlpha; } //----------------------------------------------------------------------------- @@ -144,10 +152,10 @@ void C_BaseCombatCharacter::UpdateGlowEffect( void ) // create a new effect if ( m_bGlowEnabled || m_bClientSideGlowEnabled ) { - float r, g, b; - GetGlowEffectColor( &r, &g, &b ); + float r, g, b, a; + GetGlowEffectColor( &r, &g, &b, &a ); - m_pGlowEffect = new CGlowObject( this, Vector( r, g, b ), 1.0, true ); + m_pGlowEffect = new CGlowObject( this, Vector( r, g, b ), a, true ); } } @@ -178,6 +186,8 @@ BEGIN_RECV_TABLE(C_BaseCombatCharacter, DT_BaseCombatCharacter) RecvPropArray3( RECVINFO_ARRAY(m_hMyWeapons), RecvPropEHandle( RECVINFO( m_hMyWeapons[0] ) ) ), #ifdef GLOWS_ENABLE RecvPropBool( RECVINFO( m_bGlowEnabled ) ), + RecvPropVector( RECVINFO( m_GlowColor ) ), + RecvPropFloat( RECVINFO( m_GlowAlpha ) ), #endif // GLOWS_ENABLE #ifdef INVASION_CLIENT_DLL @@ -195,3 +205,39 @@ BEGIN_PREDICTION_DATA( C_BaseCombatCharacter ) DEFINE_PRED_ARRAY( m_hMyWeapons, FIELD_EHANDLE, MAX_WEAPONS, FTYPEDESC_INSENDTABLE ), END_PREDICTION_DATA() + +#ifdef MAPBASE_VSCRIPT + +BEGIN_ENT_SCRIPTDESC( C_BaseCombatCharacter, C_BaseAnimating, "" ) + DEFINE_SCRIPTFUNC_NAMED( ScriptGetAmmoCount, "GetAmmoCount", "" ) + DEFINE_SCRIPTFUNC_NAMED( ScriptGetActiveWeapon, "GetActiveWeapon", "" ) + DEFINE_SCRIPTFUNC_NAMED( ScriptGetWeapon, "GetWeapon", "" ) +END_SCRIPTDESC(); + + +int C_BaseCombatCharacter::ScriptGetAmmoCount( int i ) +{ + Assert( i == -1 || i < MAX_AMMO_SLOTS ); + + if ( i < 0 || i >= MAX_AMMO_SLOTS ) + return NULL; + + return GetAmmoCount( i ); +} + +HSCRIPT C_BaseCombatCharacter::ScriptGetActiveWeapon() +{ + return ToHScript( GetActiveWeapon() ); +} + +HSCRIPT C_BaseCombatCharacter::ScriptGetWeapon( int i ) +{ + Assert( i >= 0 && i < MAX_WEAPONS ); + + if ( i < 0 || i >= MAX_WEAPONS ) + return NULL; + + return ToHScript( GetWeapon(i) ); +} + +#endif diff --git a/src/game/client/c_basecombatcharacter.h b/src/game/client/c_basecombatcharacter.h index 0a135b05..c5784c63 100644 --- a/src/game/client/c_basecombatcharacter.h +++ b/src/game/client/c_basecombatcharacter.h @@ -29,6 +29,9 @@ class C_BaseCombatCharacter : public C_BaseFlex public: DECLARE_CLIENTCLASS(); DECLARE_PREDICTABLE(); +#ifdef MAPBASE_VSCRIPT + DECLARE_ENT_SCRIPTDESC(); +#endif C_BaseCombatCharacter( void ); virtual ~C_BaseCombatCharacter( void ); @@ -96,13 +99,19 @@ public: #ifdef GLOWS_ENABLE CGlowObject *GetGlowObject( void ){ return m_pGlowEffect; } - virtual void GetGlowEffectColor( float *r, float *g, float *b ); + virtual void GetGlowEffectColor( float *r, float *g, float *b, float *a = NULL ); // void EnableGlowEffect( float r, float g, float b ); void SetClientSideGlowEnabled( bool bEnabled ){ m_bClientSideGlowEnabled = bEnabled; UpdateGlowEffect(); } bool IsClientSideGlowEnabled( void ){ return m_bClientSideGlowEnabled; } #endif // GLOWS_ENABLE +#ifdef MAPBASE_VSCRIPT + int ScriptGetAmmoCount( int i ); + HSCRIPT ScriptGetActiveWeapon(); + HSCRIPT ScriptGetWeapon( int i ); +#endif + public: float m_flNextAttack; @@ -129,6 +138,10 @@ private: bool m_bGlowEnabled; // networked value bool m_bOldGlowEnabled; CGlowObject *m_pGlowEffect; + Vector m_GlowColor; + Vector m_OldGlowColor; + float m_GlowAlpha; + int m_OldGlowAlpha; #endif // GLOWS_ENABLE private: diff --git a/src/game/client/c_basecombatweapon.cpp b/src/game/client/c_basecombatweapon.cpp index 6a0cedff..9b48b861 100644 --- a/src/game/client/c_basecombatweapon.cpp +++ b/src/game/client/c_basecombatweapon.cpp @@ -16,6 +16,9 @@ #include "tier1/KeyValues.h" #include "toolframework/itoolframework.h" #include "toolframework_client.h" +#ifdef MAPBASE +#include "viewrender.h" +#endif // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" @@ -82,6 +85,24 @@ static inline bool ShouldDrawLocalPlayerViewModel( void ) { #if defined( PORTAL ) return false; +#elif MAPBASE + // We shouldn't draw the viewmodel externally. + C_BasePlayer *localplayer = C_BasePlayer::GetLocalPlayer(); + if (localplayer) + { + if (localplayer->m_bDrawPlayerModelExternally) + { + // If this isn't the main view, draw the weapon. + view_id_t viewID = CurrentViewID(); + if (viewID != VIEW_MAIN && viewID != VIEW_INTRO_CAMERA) + return false; + } + + // Since we already have the local player, check its own ShouldDrawThisPlayer() to avoid extra checks + return !localplayer->ShouldDrawThisPlayer(); + } + else + return false; #else return !C_BasePlayer::ShouldDrawLocalPlayer(); #endif @@ -104,9 +125,15 @@ void C_BaseCombatWeapon::OnRestore() int C_BaseCombatWeapon::GetWorldModelIndex( void ) { +#ifdef MAPBASE + int iIndex = GetOwner() ? m_iWorldModelIndex.Get() : m_iDroppedModelIndex.Get(); +#else + int iIndex = m_iWorldModelIndex.Get(); +#endif + if ( GameRules() ) { - const char *pBaseName = modelinfo->GetModelName( modelinfo->GetModel( m_iWorldModelIndex ) ); + const char *pBaseName = modelinfo->GetModelName( modelinfo->GetModel( iIndex ) ); const char *pTranslatedName = GameRules()->TranslateEffectForVisionFilter( "weapons", pBaseName ); if ( pTranslatedName != pBaseName ) @@ -115,7 +142,7 @@ int C_BaseCombatWeapon::GetWorldModelIndex( void ) } } - return m_iWorldModelIndex; + return iIndex; } //----------------------------------------------------------------------------- @@ -156,11 +183,8 @@ void C_BaseCombatWeapon::OnDataChanged( DataUpdateType_t updateType ) } else // weapon carried by other player or not at all { - int overrideModelIndex = CalcOverrideModelIndex(); - if( overrideModelIndex != -1 && overrideModelIndex != GetModelIndex() ) - { - SetModelIndex( overrideModelIndex ); - } + // See comment below + EnsureCorrectRenderingModel(); } if ( updateType == DATA_UPDATE_CREATED ) @@ -435,6 +459,12 @@ bool C_BaseCombatWeapon::ShouldDraw( void ) if ( !ShouldDrawLocalPlayerViewModel() ) return true; +#ifdef MAPBASE + // We're drawing this in non-main views, handle it in DrawModel() + if ( pLocalPlayer->m_bDrawPlayerModelExternally ) + return true; +#endif + // don't draw active weapon if not in some kind of 3rd person mode, the viewmodel will do that return false; } @@ -481,15 +511,44 @@ int C_BaseCombatWeapon::DrawModel( int flags ) // check if local player chases owner of this weapon in first person C_BasePlayer *localplayer = C_BasePlayer::GetLocalPlayer(); - if ( localplayer && localplayer->IsObserver() && GetOwner() ) + if ( localplayer ) { - // don't draw weapon if chasing this guy as spectator - // we don't check that in ShouldDraw() since this may change - // without notification +#ifdef MAPBASE + if (localplayer->m_bDrawPlayerModelExternally) + { + // If this isn't the main view, draw the weapon. + view_id_t viewID = CurrentViewID(); + if ( (!localplayer->InFirstPersonView() || (viewID != VIEW_MAIN && viewID != VIEW_INTRO_CAMERA)) && (viewID != VIEW_SHADOW_DEPTH_TEXTURE || !localplayer->IsEffectActive(EF_DIMLIGHT)) ) + { + // TODO: Is this inefficient? + int nModelIndex = GetModelIndex(); + int nWorldModelIndex = GetWorldModelIndex(); + if (nModelIndex != nWorldModelIndex) + { + SetModelIndex(nWorldModelIndex); + } + + int iDraw = BaseClass::DrawModel(flags); + + if (nModelIndex != nWorldModelIndex) + { + SetModelIndex(nModelIndex); + } + + return iDraw; + } + } +#endif + if ( localplayer->IsObserver() && GetOwner() ) + { + // don't draw weapon if chasing this guy as spectator + // we don't check that in ShouldDraw() since this may change + // without notification - if ( localplayer->GetObserverMode() == OBS_MODE_IN_EYE && - localplayer->GetObserverTarget() == GetOwner() ) - return false; + if ( localplayer->GetObserverMode() == OBS_MODE_IN_EYE && + localplayer->GetObserverTarget() == GetOwner() ) + return false; + } } return BaseClass::DrawModel( flags ); @@ -534,6 +593,36 @@ bool C_BaseCombatWeapon::PredictionErrorShouldResetLatchedForAllPredictables( vo return BaseClass::PredictionErrorShouldResetLatchedForAllPredictables(); } +//----------------------------------------------------------------------------- +// If the local player is visible (thirdperson mode, tf2 taunts, etc., then make sure that we are using the +// w_ (world) model not the v_ (view) model or else the model can flicker, etc. +// Otherwise, if we're not the local player, always use the world model +//----------------------------------------------------------------------------- +void C_BaseCombatWeapon::EnsureCorrectRenderingModel() +{ + C_BasePlayer *localplayer = C_BasePlayer::GetLocalPlayer(); + if ( localplayer && + localplayer == GetOwner() && + !ShouldDrawUsingViewModel() ) + { + return; + } + + // BRJ 10/14/02 + // FIXME: Remove when Yahn's client-side prediction is done + // It's a hacky workaround for the model indices fighting + // (GetRenderBounds uses the model index, which is for the view model) + SetModelIndex( GetWorldModelIndex() ); + + // Validate our current sequence just in case ( in theory the view and weapon models should have the same sequences for sequences that overlap at least ) + CStudioHdr *pStudioHdr = GetModelPtr(); + if ( pStudioHdr && + GetSequence() >= pStudioHdr->GetNumSeq() ) + { + SetSequence( 0 ); + } +} + //----------------------------------------------------------------------------- // tool recording //----------------------------------------------------------------------------- diff --git a/src/game/client/c_baseentity.cpp b/src/game/client/c_baseentity.cpp index 14dc689c..4427305a 100644 --- a/src/game/client/c_baseentity.cpp +++ b/src/game/client/c_baseentity.cpp @@ -40,6 +40,14 @@ #include "cdll_bounded_cvars.h" #include "inetchannelinfo.h" #include "proto_version.h" +#ifdef MAPBASE +#include "viewrender.h" +#endif +#ifdef MAPBASE_VSCRIPT +#include "vscript_client.h" +#endif + +#include "gamestringpool.h" #ifdef TF_CLIENT_DLL #include "c_tf_player.h" @@ -424,6 +432,148 @@ BEGIN_RECV_TABLE_NOBASE( C_BaseEntity, DT_AnimTimeMustBeFirst ) RecvPropInt( RECVINFO(m_flAnimTime), 0, RecvProxy_AnimTime ), END_RECV_TABLE() +#ifdef MAPBASE_VSCRIPT +ScriptHook_t C_BaseEntity::g_Hook_UpdateOnRemove; +ScriptHook_t C_BaseEntity::g_Hook_ModifyEmitSoundParams; +#endif + +BEGIN_ENT_SCRIPTDESC_ROOT( C_BaseEntity, "Root class of all client-side entities" ) + DEFINE_SCRIPT_INSTANCE_HELPER( &g_BaseEntityScriptInstanceHelper ) + DEFINE_SCRIPTFUNC_NAMED( GetAbsOrigin, "GetOrigin", "" ) + DEFINE_SCRIPTFUNC_NAMED( ScriptGetForward, "GetForwardVector", "Get the forward vector of the entity" ) +#ifdef MAPBASE_VSCRIPT + DEFINE_SCRIPTFUNC_NAMED( ScriptGetRight, "GetRightVector", "Get the right vector of the entity" ) + DEFINE_SCRIPTFUNC_NAMED( GetTeamNumber, "GetTeam", "Gets this entity's team" ) +#endif + DEFINE_SCRIPTFUNC_NAMED( ScriptGetLeft, "GetLeftVector", SCRIPT_HIDE ) + DEFINE_SCRIPTFUNC_NAMED( GetTeamNumber, "GetTeamNumber", SCRIPT_HIDE ) + + DEFINE_SCRIPTFUNC_NAMED( ScriptGetUp, "GetUpVector", "Get the up vector of the entity" ) + +#ifdef MAPBASE_VSCRIPT + DEFINE_SCRIPTFUNC( ValidateScriptScope, "Ensure that an entity's script scope has been created" ) + DEFINE_SCRIPTFUNC( GetOrCreatePrivateScriptScope, "Create and retrieve the script-side data associated with an entity" ) + DEFINE_SCRIPTFUNC( GetScriptScope, "Retrieve the script-side data associated with an entity" ) + + DEFINE_SCRIPTFUNC( GetHealth, "" ) + DEFINE_SCRIPTFUNC( GetMaxHealth, "" ) + + DEFINE_SCRIPTFUNC_NAMED( ScriptGetModelName, "GetModelName", "Returns the name of the model" ) + + DEFINE_SCRIPTFUNC_NAMED( ScriptStopSound, "StopSound", "Stops a sound from this entity." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptEmitSound, "EmitSound", "Plays a sound from this entity." ) + DEFINE_SCRIPTFUNC_NAMED( VScriptPrecacheScriptSound, "PrecacheSoundScript", "Precache a sound for later playing." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptSoundDuration, "GetSoundDuration", "Returns float duration of the sound. Takes soundname and optional actormodelname." ) + + DEFINE_SCRIPTFUNC( GetClassname, "" ) + DEFINE_SCRIPTFUNC_NAMED( GetEntityName, "GetName", "" ) + + DEFINE_SCRIPTFUNC_NAMED( SetAbsOrigin, "SetOrigin", "" ) + DEFINE_SCRIPTFUNC_NAMED( ScriptSetForward, "SetForwardVector", "Set the orientation of the entity to have this forward vector" ) + + DEFINE_SCRIPTFUNC( GetLocalOrigin, "GetLocalOrigin" ) + DEFINE_SCRIPTFUNC( SetLocalOrigin, "SetLocalOrigin" ) + DEFINE_SCRIPTFUNC( GetLocalAngles, "GetLocalAngles" ) + DEFINE_SCRIPTFUNC( SetLocalAngles, "SetLocalAngles" ) + + DEFINE_SCRIPTFUNC_NAMED( WorldSpaceCenter, "GetCenter", "Get vector to center of object - absolute coords" ) + DEFINE_SCRIPTFUNC_NAMED( ScriptEyePosition, "EyePosition", "Get vector to eye position - absolute coords" ) + DEFINE_SCRIPTFUNC_NAMED( ScriptEyeAngles, "EyeAngles", "Get eye pitch, yaw, roll as a vector" ) + DEFINE_SCRIPTFUNC_NAMED( GetAbsAngles, "GetAngles", "Get entity pitch, yaw, roll as a vector" ) + DEFINE_SCRIPTFUNC_NAMED( SetAbsAngles, "SetAngles", "Set entity pitch, yaw, roll" ) + + DEFINE_SCRIPTFUNC( SetSize, "" ) + DEFINE_SCRIPTFUNC_NAMED( ScriptGetBoundingMins, "GetBoundingMins", "Get a vector containing min bounds, centered on object" ) + DEFINE_SCRIPTFUNC_NAMED( ScriptGetBoundingMaxs, "GetBoundingMaxs", "Get a vector containing max bounds, centered on object" ) + + DEFINE_SCRIPTFUNC_NAMED( ScriptEntityToWorldTransform, "EntityToWorldTransform", "Get the entity's transform" ) + + DEFINE_SCRIPTFUNC_NAMED( ScriptGetPhysicsObject, "GetPhysicsObject", "Get the entity's physics object if it has one" ) + DEFINE_SCRIPTFUNC_NAMED( ScriptPhysicsInitNormal, "PhysicsInitNormal", "Initializes the entity's physics object with the specified solid type, solid flags, and whether to start asleep" ) + DEFINE_SCRIPTFUNC_NAMED( ScriptPhysicsDestroyObject, "PhysicsDestroyObject", "Destroys the entity's physics object" ) + + DEFINE_SCRIPTFUNC( GetWaterLevel, "Get current level of water submergence" ) + + DEFINE_SCRIPTFUNC_NAMED( ScriptSetParent, "SetParent", "" ) + DEFINE_SCRIPTFUNC_NAMED( ScriptGetMoveParent, "GetMoveParent", "If in hierarchy, retrieves the entity's parent" ) + DEFINE_SCRIPTFUNC_NAMED( ScriptGetRootMoveParent, "GetRootMoveParent", "If in hierarchy, walks up the hierarchy to find the root parent" ) + DEFINE_SCRIPTFUNC_NAMED( ScriptFirstMoveChild, "FirstMoveChild", "" ) + DEFINE_SCRIPTFUNC_NAMED( ScriptNextMovePeer, "NextMovePeer", "" ) + + DEFINE_SCRIPTFUNC_NAMED( ScriptFollowEntity, "FollowEntity", "Begin following the specified entity. This makes this entity non-solid, parents it to the target entity, and teleports it to the specified entity's origin. The second parameter is whether or not to use bonemerging while following." ) + DEFINE_SCRIPTFUNC( StopFollowingEntity, "Stops following an entity if we're following one." ) + DEFINE_SCRIPTFUNC( IsFollowingEntity, "Returns true if this entity is following another entity." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptGetFollowedEntity, "GetFollowedEntity", "Get the entity we're following." ) + + DEFINE_SCRIPTFUNC_NAMED( GetScriptOwnerEntity, "GetOwner", "Gets this entity's owner" ) + DEFINE_SCRIPTFUNC_NAMED( SetScriptOwnerEntity, "SetOwner", "Sets this entity's owner" ) + + DEFINE_SCRIPTFUNC_NAMED( ScriptGetGroundEntity, "GetGroundEntity", "Get the entity we're standing on." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptSetGroundEntity, "SetGroundEntity", "Set the entity we're standing on." ) + + DEFINE_SCRIPTFUNC_NAMED( ScriptGetColorVector, "GetRenderColorVector", "Get the render color as a vector" ) + DEFINE_SCRIPTFUNC_NAMED( ScriptGetColorR, "GetRenderColorR", "Get the render color's R value" ) + DEFINE_SCRIPTFUNC_NAMED( ScriptGetColorG, "GetRenderColorG", "Get the render color's G value" ) + DEFINE_SCRIPTFUNC_NAMED( ScriptGetColorB, "GetRenderColorB", "Get the render color's B value" ) + DEFINE_SCRIPTFUNC_NAMED( ScriptGetAlpha, "GetRenderAlpha", "Get the render color's alpha value" ) + DEFINE_SCRIPTFUNC_NAMED( ScriptSetColorVector, "SetRenderColorVector", "Set the render color as a vector" ) + DEFINE_SCRIPTFUNC_NAMED( ScriptSetColor, "SetRenderColor", "Set the render color" ) + DEFINE_SCRIPTFUNC_NAMED( ScriptSetColorR, "SetRenderColorR", "Set the render color's R value" ) + DEFINE_SCRIPTFUNC_NAMED( ScriptSetColorG, "SetRenderColorG", "Set the render color's G value" ) + DEFINE_SCRIPTFUNC_NAMED( ScriptSetColorB, "SetRenderColorB", "Set the render color's B value" ) + DEFINE_SCRIPTFUNC_NAMED( ScriptSetAlpha, "SetRenderAlpha", "Set the render color's alpha value" ) + + DEFINE_SCRIPTFUNC_NAMED( ScriptGetRenderMode, "GetRenderMode", "Get render mode" ) + DEFINE_SCRIPTFUNC_NAMED( ScriptSetRenderMode, "SetRenderMode", "Set render mode" ) + + DEFINE_SCRIPTFUNC( GetEffects, "Get effects" ) + DEFINE_SCRIPTFUNC( AddEffects, "Add effect(s)" ) + DEFINE_SCRIPTFUNC( RemoveEffects, "Remove effect(s)" ) + DEFINE_SCRIPTFUNC( ClearEffects, "Clear effect(s)" ) + DEFINE_SCRIPTFUNC( SetEffects, "Set effect(s)" ) + DEFINE_SCRIPTFUNC( IsEffectActive, "Check if an effect is active" ) + + DEFINE_SCRIPTFUNC( GetFlags, "Get flags" ) + DEFINE_SCRIPTFUNC( AddFlag, "Add flag" ) + DEFINE_SCRIPTFUNC( RemoveFlag, "Remove flag" ) + + DEFINE_SCRIPTFUNC( GetEFlags, "Get Eflags" ) + DEFINE_SCRIPTFUNC( AddEFlags, "Add Eflags" ) + DEFINE_SCRIPTFUNC( RemoveEFlags, "Remove Eflags" ) + + DEFINE_SCRIPTFUNC_NAMED( ScriptGetMoveType, "GetMoveType", "Get the move type" ) + DEFINE_SCRIPTFUNC_NAMED( ScriptSetMoveType, "SetMoveType", "Set the move type" ) + + DEFINE_SCRIPTFUNC( GetCollisionGroup, "Get the collision group" ) + DEFINE_SCRIPTFUNC( SetCollisionGroup, "Set the collision group" ) + + DEFINE_SCRIPTFUNC( GetSolidFlags, "Get solid flags" ) + DEFINE_SCRIPTFUNC( AddSolidFlags, "Add solid flags" ) + DEFINE_SCRIPTFUNC( RemoveSolidFlags, "Remove solid flags" ) + + DEFINE_SCRIPTFUNC( IsPlayer, "Returns true if this entity is a player." ) + DEFINE_SCRIPTFUNC( IsNPC, "Returns true if this entity is a NPC." ) + //DEFINE_SCRIPTFUNC( IsCombatCharacter, "Returns true if this entity is a combat character (player or NPC)." ) + DEFINE_SCRIPTFUNC_NAMED( IsBaseCombatWeapon, "IsWeapon", "Returns true if this entity is a weapon." ) + DEFINE_SCRIPTFUNC( IsWorld, "Returns true if this entity is the world." ) + + DEFINE_SCRIPTFUNC( SetModel, "Set client-only entity model" ) + //DEFINE_SCRIPTFUNC_NAMED( ScriptInitializeAsClientEntity, "InitializeAsClientEntity", "" ) + DEFINE_SCRIPTFUNC_NAMED( Remove, "Destroy", "Remove clientside entity" ) + DEFINE_SCRIPTFUNC_NAMED( GetEntityIndex, "entindex", "" ) + + DEFINE_SCRIPTFUNC_NAMED( ScriptSetContextThink, "SetContextThink", "Set a think function on this entity." ) + + + DEFINE_SIMPLE_SCRIPTHOOK( C_BaseEntity::g_Hook_UpdateOnRemove, "UpdateOnRemove", FIELD_VOID, "Called when the entity is being removed." ) + + BEGIN_SCRIPTHOOK( C_BaseEntity::g_Hook_ModifyEmitSoundParams, "ModifyEmitSoundParams", FIELD_VOID, "Called every time a sound is emitted on this entity, allowing for its parameters to be modified." ) + DEFINE_SCRIPTHOOK_PARAM( "params", FIELD_HSCRIPT ) + END_SCRIPTHOOK() + +#endif // MAPBASE_VSCRIPT + +END_SCRIPTDESC(); #ifndef NO_ENTITY_PREDICTION BEGIN_RECV_TABLE_NOBASE( C_BaseEntity, DT_PredictableId ) @@ -455,6 +605,10 @@ BEGIN_RECV_TABLE_NOBASE(C_BaseEntity, DT_BaseEntity) RecvPropInt(RECVINFO(m_nRenderMode)), RecvPropInt(RECVINFO(m_nRenderFX)), RecvPropInt(RECVINFO(m_clrRender)), +#ifdef MAPBASE + RecvPropInt(RECVINFO(m_iViewHideFlags)), + RecvPropBool(RECVINFO(m_bDisableFlashlight)), +#endif RecvPropInt(RECVINFO(m_iTeamNum)), RecvPropInt(RECVINFO(m_CollisionGroup)), RecvPropFloat(RECVINFO(m_flElasticity)), @@ -464,6 +618,8 @@ BEGIN_RECV_TABLE_NOBASE(C_BaseEntity, DT_BaseEntity) RecvPropInt( RECVINFO_NAME(m_hNetworkMoveParent, moveparent), 0, RecvProxy_IntToMoveParent ), RecvPropInt( RECVINFO( m_iParentAttachment ) ), + RecvPropString(RECVINFO(m_iName)), + RecvPropInt( "movetype", 0, SIZEOF_IGNORE, 0, RecvProxy_MoveType ), RecvPropInt( "movecollide", 0, SIZEOF_IGNORE, 0, RecvProxy_MoveCollide ), RecvPropDataTable( RECVINFO_DT( m_Collision ), 0, &REFERENCE_RECV_TABLE(DT_CollisionProperty) ), @@ -1120,6 +1276,8 @@ bool C_BaseEntity::Init( int entnum, int iSerialNum ) m_nCreationTick = gpGlobals->tickcount; + m_hScriptInstance = NULL; + return true; } @@ -1197,6 +1355,7 @@ void C_BaseEntity::Term() g_Predictables.RemoveFromPredictablesList( GetClientHandle() ); } + // If it's play simulated, remove from simulation list if the player still exists... if ( IsPlayerSimulated() && C_BasePlayer::GetLocalPlayer() ) { @@ -1233,6 +1392,27 @@ void C_BaseEntity::Term() RemoveFromLeafSystem(); RemoveFromAimEntsList(); + + if ( m_hScriptInstance ) + { +#ifdef MAPBASE_VSCRIPT + if ( m_ScriptScope.IsInitialized() && g_Hook_UpdateOnRemove.CanRunInScope( m_ScriptScope ) ) + { + g_Hook_UpdateOnRemove.Call( m_ScriptScope, NULL, NULL ); + } +#endif + g_pScriptVM->RemoveInstance( m_hScriptInstance ); + m_hScriptInstance = NULL; + +#ifdef MAPBASE_VSCRIPT + FOR_EACH_VEC( m_ScriptThinkFuncs, i ) + { + HSCRIPT h = m_ScriptThinkFuncs[i]->m_hfnThink; + if ( h ) g_pScriptVM->ReleaseScript( h ); + } + m_ScriptThinkFuncs.PurgeAndDeleteElements(); +#endif + } } @@ -1569,6 +1749,11 @@ bool C_BaseEntity::ShouldReceiveProjectedTextures( int flags ) if ( IsEffectActive( EF_NODRAW ) ) return false; +#ifdef MAPBASE + if ( m_bDisableFlashlight ) + return false; +#endif + if( flags & SHADOW_FLAGS_FLASHLIGHT ) { if ( GetRenderMode() > kRenderNormal && GetRenderColor().a == 0 ) @@ -2003,6 +2188,17 @@ int C_BaseEntity::DrawModel( int flags ) return drawn; } +#ifdef MAPBASE + if (m_iViewHideFlags > 0) + { + // Hide this entity if it's not supposed to be drawn in this view. + if (m_iViewHideFlags & (1 << CurrentViewID())) + { + return 0; + } + } +#endif + int modelType = modelinfo->GetModelType( model ); switch ( modelType ) { @@ -4782,9 +4978,15 @@ C_BaseEntity *C_BaseEntity::Instance( int iEnt ) } #if defined( WIN32 ) && _MSC_VER <= 1920 + +#if _MSC_VER < 1900 #pragma warning( push ) #include #pragma warning( pop ) +#else +#include +#endif + #endif //----------------------------------------------------------------------------- @@ -6017,6 +6219,9 @@ BEGIN_DATADESC_NO_BASE( C_BaseEntity ) DEFINE_FIELD( m_angAbsRotation, FIELD_VECTOR ), DEFINE_ARRAY( m_rgflCoordinateFrame, FIELD_FLOAT, 12 ), // NOTE: MUST BE IN LOCAL SPACE, NOT POSITION_VECTOR!!! (see CBaseEntity::Restore) DEFINE_FIELD( m_fFlags, FIELD_INTEGER ), +#ifdef MAPBASE_VSCRIPT + DEFINE_FIELD( m_iszScriptId, FIELD_STRING ), +#endif END_DATADESC() //----------------------------------------------------------------------------- @@ -6475,6 +6680,187 @@ int C_BaseEntity::GetCreationTick() const return m_nCreationTick; } +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +HSCRIPT C_BaseEntity::GetScriptInstance() +{ + if (!m_hScriptInstance) + { + if (m_iszScriptId == NULL_STRING) + { + char* szName = (char*)stackalloc(1024); + g_pScriptVM->GenerateUniqueKey((m_iName != NULL_STRING) ? STRING(GetEntityName()) : GetClassname(), szName, 1024); + m_iszScriptId = AllocPooledString(szName); + } + + m_hScriptInstance = g_pScriptVM->RegisterInstance(GetScriptDesc(), this); + g_pScriptVM->SetInstanceUniqeId(m_hScriptInstance, STRING(m_iszScriptId)); + } + return m_hScriptInstance; +} + +#ifdef MAPBASE_VSCRIPT +//----------------------------------------------------------------------------- +// Using my edict, cook up a unique VScript scope that's private to me, and +// persistent. +//----------------------------------------------------------------------------- +bool C_BaseEntity::ValidateScriptScope() +{ + if (!m_ScriptScope.IsInitialized()) + { + if (scriptmanager == NULL) + { + ExecuteOnce(DevMsg("Cannot execute script because scripting is disabled (-scripting)\n")); + return false; + } + + if (g_pScriptVM == NULL) + { + ExecuteOnce(DevMsg(" Cannot execute script because there is no available VM\n")); + return false; + } + + // Force instance creation + GetScriptInstance(); + + EHANDLE hThis; + hThis.Set(this); + + bool bResult = m_ScriptScope.Init(STRING(m_iszScriptId)); + + if (!bResult) + { + DevMsg("%s couldn't create ScriptScope!\n", GetDebugName()); + return false; + } + g_pScriptVM->SetValue(m_ScriptScope, "self", GetScriptInstance()); + } + return true; +} + +//----------------------------------------------------------------------------- +// Returns true if the function was located and called. false otherwise. +// NOTE: Assumes the function takes no parameters at the moment. +//----------------------------------------------------------------------------- +bool C_BaseEntity::CallScriptFunction( const char* pFunctionName, ScriptVariant_t* pFunctionReturn ) +{ + if (!ValidateScriptScope()) + { + DevMsg("\n***\nFAILED to create private ScriptScope. ABORTING script\n***\n"); + return false; + } + + + HSCRIPT hFunc = m_ScriptScope.LookupFunction(pFunctionName); + + if (hFunc) + { + m_ScriptScope.Call(hFunc, pFunctionReturn); + m_ScriptScope.ReleaseFunction(hFunc); + + return true; + } + + return false; +} + +//----------------------------------------------------------------------------- +// Gets a function handle +//----------------------------------------------------------------------------- +HSCRIPT C_BaseEntity::LookupScriptFunction( const char* pFunctionName ) +{ + if (!m_ScriptScope.IsInitialized()) + { + return NULL; + } + + return m_ScriptScope.LookupFunction(pFunctionName); +} + +//----------------------------------------------------------------------------- +// Calls and releases a function handle (ASSUMES SCRIPT SCOPE AND FUNCTION ARE VALID!) +//----------------------------------------------------------------------------- +bool C_BaseEntity::CallScriptFunctionHandle( HSCRIPT hFunc, ScriptVariant_t* pFunctionReturn ) +{ + m_ScriptScope.Call(hFunc, pFunctionReturn); + m_ScriptScope.ReleaseFunction(hFunc); + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Load, compile, and run a script file from disk. +// Input : *pScriptFile - The filename of the script file. +// bUseRootScope - If true, runs this script in the root scope, not +// in this entity's private scope. +//----------------------------------------------------------------------------- +bool C_BaseEntity::RunScriptFile( const char* pScriptFile, bool bUseRootScope ) +{ + if (!ValidateScriptScope()) + { + DevMsg("\n***\nFAILED to create private ScriptScope. ABORTING script\n***\n"); + return false; + } + + if (bUseRootScope) + { + return VScriptRunScript(pScriptFile); + } + else + { + return VScriptRunScript(pScriptFile, m_ScriptScope, true); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Compile and execute a discrete string of script source code +// Input : *pScriptText - A string containing script code to compile and run +//----------------------------------------------------------------------------- +bool C_BaseEntity::RunScript( const char* pScriptText, const char* pDebugFilename ) +{ + if (!ValidateScriptScope()) + { + DevMsg("\n***\nFAILED to create private ScriptScope. ABORTING script\n***\n"); + return false; + } + + if (m_ScriptScope.Run(pScriptText, pDebugFilename) == SCRIPT_ERROR) + { + DevWarning(" Entity %s encountered an error in RunScript()\n", GetDebugName()); + } + + return true; +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +HSCRIPT C_BaseEntity::ScriptGetMoveParent( void ) +{ + return ToHScript( GetMoveParent() ); +} +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +HSCRIPT C_BaseEntity::ScriptGetRootMoveParent() +{ + return ToHScript( GetRootMoveParent() ); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +HSCRIPT C_BaseEntity::ScriptFirstMoveChild( void ) +{ + return ToHScript( FirstMoveChild() ); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +HSCRIPT C_BaseEntity::ScriptNextMovePeer( void ) +{ + return ToHScript( NextMovePeer() ); +} +#endif + //------------------------------------------------------------------------------ void CC_CL_Find_Ent( const CCommand& args ) { diff --git a/src/game/client/c_baseentity.h b/src/game/client/c_baseentity.h index 8ca3e375..39ce6f79 100644 --- a/src/game/client/c_baseentity.h +++ b/src/game/client/c_baseentity.h @@ -36,6 +36,9 @@ #include "toolframework/itoolentity.h" #include "tier0/threadtools.h" +#include "vscript/ivscript.h" +#include "vscript_shared.h" + class C_Team; class IPhysicsObject; class IClientVehicle; @@ -159,6 +162,15 @@ struct thinkfunc_t int m_nLastThinkTick; }; +#ifdef MAPBASE_VSCRIPT +struct scriptthinkfunc_t +{ + float m_flNextThink; + HSCRIPT m_hfnThink; + unsigned m_iContextHash; +}; +#endif + #define CREATE_PREDICTED_ENTITY( className ) \ C_BaseEntity::CreatePredictedEntityByName( className, __FILE__, __LINE__ ); @@ -184,6 +196,8 @@ public: DECLARE_DATADESC(); DECLARE_CLIENTCLASS(); DECLARE_PREDICTABLE(); + // script description + DECLARE_ENT_SCRIPTDESC(); C_BaseEntity(); virtual ~C_BaseEntity(); @@ -257,6 +271,40 @@ public: string_t m_iClassname; +#ifdef MAPBASE_VSCRIPT + // VSCRIPT + bool ValidateScriptScope(); + bool CallScriptFunction( const char* pFunctionName, ScriptVariant_t* pFunctionReturn ); + + HSCRIPT GetOrCreatePrivateScriptScope(); + HSCRIPT GetScriptScope() { return m_ScriptScope; } + + HSCRIPT LookupScriptFunction(const char* pFunctionName); + bool CallScriptFunctionHandle(HSCRIPT hFunc, ScriptVariant_t* pFunctionReturn); + + bool RunScriptFile( const char* pScriptFile, bool bUseRootScope = false ); + bool RunScript( const char* pScriptText, const char* pDebugFilename = "C_BaseEntity::RunScript" ); +#endif + + HSCRIPT GetScriptOwnerEntity(); + virtual void SetScriptOwnerEntity(HSCRIPT pOwner); + +#ifdef MAPBASE_VSCRIPT + HSCRIPT ScriptGetGroundEntity(); + void ScriptSetGroundEntity( HSCRIPT hGroundEnt ); +#endif + + HSCRIPT GetScriptInstance(); + + HSCRIPT m_hScriptInstance; + string_t m_iszScriptId; +#ifdef MAPBASE_VSCRIPT + CScriptScope m_ScriptScope; + + static ScriptHook_t g_Hook_UpdateOnRemove; + static ScriptHook_t g_Hook_ModifyEmitSoundParams; +#endif + // IClientUnknown overrides. public: @@ -358,6 +406,11 @@ public: bool IsMarkedForDeletion( void ); virtual int entindex( void ) const; + +#ifdef MAPBASE_VSCRIPT + // "I don't know why but wrapping entindex() works, while calling it directly crashes." + inline int GetEntityIndex() const { return entindex(); } +#endif // This works for client-only entities and returns the GetEntryIndex() of the entity's handle, // so the sound system can get an IClientEntity from it. @@ -863,6 +916,7 @@ public: void SetSize( const Vector &vecMin, const Vector &vecMax ); // UTIL_SetSize( pev, mins, maxs ); char const *GetClassname( void ); char const *GetDebugName( void ); + virtual const char *GetPlayerName() const { return NULL; } static int PrecacheModel( const char *name ); static bool PrecacheSound( const char *name ); static void PrefetchSound( const char *name ); @@ -1005,6 +1059,7 @@ public: ///////////////// virtual bool IsPlayer( void ) const { return false; }; + virtual bool IsBot( void ) const { return ((GetFlags() & FL_FAKECLIENT) == FL_FAKECLIENT) ? true : false; } virtual bool IsBaseCombatCharacter( void ) { return false; }; virtual C_BaseCombatCharacter *MyCombatCharacterPointer( void ) { return NULL; } virtual bool IsNPC( void ) { return false; } @@ -1022,6 +1077,11 @@ public: virtual Vector EyePosition( void ); virtual const QAngle& EyeAngles( void ); // Direction of eyes virtual const QAngle& LocalEyeAngles( void ); // Direction of eyes in local space (pl.v_angle) + +#ifdef MAPBASE + // Created for script_intro and info_player_view_proxy + virtual void GetEyePosition( Vector &vecOrigin, QAngle &angAngles ) { vecOrigin = EyePosition(); angAngles = EyeAngles(); } +#endif // position of ears virtual Vector EarPosition( void ); @@ -1115,10 +1175,70 @@ public: bool IsFollowingEntity(); CBaseEntity *GetFollowedEntity(); +#ifdef MAPBASE_VSCRIPT + void ScriptFollowEntity( HSCRIPT hBaseEntity, bool bBoneMerge ); + HSCRIPT ScriptGetFollowedEntity(); +#endif + // For shadows rendering the correct body + sequence... virtual int GetBody() { return 0; } virtual int GetSkin() { return 0; } + const Vector& ScriptGetForward(void) { static Vector vecForward; GetVectors(&vecForward, NULL, NULL); return vecForward; } +#ifdef MAPBASE_VSCRIPT + const Vector& ScriptGetRight(void) { static Vector vecRight; GetVectors(NULL, &vecRight, NULL); return vecRight; } +#endif + const Vector& ScriptGetLeft(void) { static Vector vecRight; GetVectors(NULL, &vecRight, NULL); return vecRight; } + + const Vector& ScriptGetUp(void) { static Vector vecUp; GetVectors(NULL, NULL, &vecUp); return vecUp; } + +#ifdef MAPBASE_VSCRIPT + const char* ScriptGetModelName( void ) const { return STRING(GetModelName()); } + + void ScriptStopSound(const char* soundname); + void ScriptEmitSound(const char* soundname); + float ScriptSoundDuration(const char* soundname, const char* actormodel); + + void VScriptPrecacheScriptSound(const char* soundname); + + const Vector& ScriptEyePosition(void) { static Vector vec; vec = EyePosition(); return vec; } + const QAngle& ScriptEyeAngles(void) { static QAngle ang; ang = EyeAngles(); return ang; } + void ScriptSetForward( const Vector& v ) { QAngle angles; VectorAngles( v, angles ); SetAbsAngles( angles ); } + + const Vector& ScriptGetBoundingMins( void ) { return m_Collision.OBBMins(); } + const Vector& ScriptGetBoundingMaxs( void ) { return m_Collision.OBBMaxs(); } + + HSCRIPT ScriptEntityToWorldTransform( void ); + + HSCRIPT ScriptGetPhysicsObject( void ); + void ScriptPhysicsInitNormal( int nSolidType, int nSolidFlags, bool createAsleep ); + void ScriptPhysicsDestroyObject() { VPhysicsDestroyObject(); } + + void ScriptSetParent( HSCRIPT hParent, const char *szAttachment ); + HSCRIPT ScriptGetMoveParent( void ); + HSCRIPT ScriptGetRootMoveParent(); + HSCRIPT ScriptFirstMoveChild( void ); + HSCRIPT ScriptNextMovePeer( void ); + + const Vector& ScriptGetColorVector(); + int ScriptGetColorR() { return m_clrRender.GetR(); } + int ScriptGetColorG() { return m_clrRender.GetG(); } + int ScriptGetColorB() { return m_clrRender.GetB(); } + int ScriptGetAlpha() { return m_clrRender.GetA(); } + void ScriptSetColorVector( const Vector& vecColor ); + void ScriptSetColor( int r, int g, int b ); + void ScriptSetColorR( int iVal ) { SetRenderColorR( iVal ); } + void ScriptSetColorG( int iVal ) { SetRenderColorG( iVal ); } + void ScriptSetColorB( int iVal ) { SetRenderColorB( iVal ); } + void ScriptSetAlpha( int iVal ) { SetRenderColorA( iVal ); } + + int ScriptGetRenderMode() { return GetRenderMode(); } + void ScriptSetRenderMode( int nRenderMode ) { SetRenderMode( (RenderMode_t)nRenderMode ); } + + int ScriptGetMoveType() { return GetMoveType(); } + void ScriptSetMoveType( int iMoveType ) { SetMoveType( (MoveType_t)iMoveType ); } +#endif + // Stubs on client void NetworkStateManualMode( bool activate ) { } void NetworkStateChanged() { } @@ -1159,7 +1279,7 @@ public: #ifdef _DEBUG void FunctionCheck( void *pFunction, const char *name ); - ENTITYFUNCPTR TouchSet( ENTITYFUNCPTR func, char *name ) + ENTITYFUNCPTR TouchSet( ENTITYFUNCPTR func, const char *name ) { //COMPILE_TIME_ASSERT( sizeof(func) == 4 ); m_pfnTouch = func; @@ -1279,6 +1399,7 @@ public: void SetRenderMode( RenderMode_t nRenderMode, bool bForceUpdate = false ); RenderMode_t GetRenderMode() const; + const char* GetEntityName(); public: // Determine what entity this corresponds to @@ -1293,6 +1414,11 @@ public: CNetworkColor32( m_clrRender ); +#ifdef MAPBASE + int m_iViewHideFlags; + bool m_bDisableFlashlight; +#endif + private: // Model for rendering @@ -1432,6 +1558,15 @@ protected: CUtlVector< thinkfunc_t > m_aThinkFunctions; int m_iCurrentThinkContext; +#ifdef MAPBASE_VSCRIPT +public: + void ScriptSetContextThink( const char* szContext, HSCRIPT hFunc, float time ); + void ScriptContextThink(); +private: + CUtlVector< scriptthinkfunc_t* > m_ScriptThinkFuncs; +public: +#endif + // Object eye position Vector m_vecViewOffset; @@ -1660,6 +1795,8 @@ private: // The owner! EHANDLE m_hOwnerEntity; EHANDLE m_hEffectEntity; + + char m_iName[MAX_PATH]; // This is a random seed used by the networking code to allow client - side prediction code // randon number generators to spit out the same random numbers on both sides for a particular @@ -2218,6 +2355,12 @@ inline bool C_BaseEntity::ShouldRecordInTools() const #endif } +inline const char *C_BaseEntity::GetEntityName() +{ + return m_iName; +} + + C_BaseEntity *CreateEntityByName( const char *className ); #endif // C_BASEENTITY_H diff --git a/src/game/client/c_baseflex.cpp b/src/game/client/c_baseflex.cpp index 6723a1d8..b63e43a1 100644 --- a/src/game/client/c_baseflex.cpp +++ b/src/game/client/c_baseflex.cpp @@ -1476,7 +1476,7 @@ bool C_BaseFlex::ClearSceneEvent( CSceneEventInfo *info, bool fastKill, bool can // expression - // duration - //----------------------------------------------------------------------------- -void C_BaseFlex::AddSceneEvent( CChoreoScene *scene, CChoreoEvent *event, CBaseEntity *pTarget, bool bClientSide ) +void C_BaseFlex::AddSceneEvent( CChoreoScene *scene, CChoreoEvent *event, CBaseEntity *pTarget, bool bClientSide, C_SceneEntity* pSceneEntity) { if ( !scene || !event ) { @@ -1501,6 +1501,7 @@ void C_BaseFlex::AddSceneEvent( CChoreoScene *scene, CChoreoEvent *event, CBaseE info.m_hTarget = pTarget; info.m_bStarted = false; info.m_bClientSide = bClientSide; + info.m_hSceneEntity = pSceneEntity; if (StartSceneEvent( &info, scene, event, actor, pTarget )) { diff --git a/src/game/client/c_baseflex.h b/src/game/client/c_baseflex.h index f0c96635..a558e3d9 100644 --- a/src/game/client/c_baseflex.h +++ b/src/game/client/c_baseflex.h @@ -213,7 +213,7 @@ public: virtual bool ClearSceneEvent( CSceneEventInfo *info, bool fastKill, bool canceled ); // Add the event to the queue for this actor - void AddSceneEvent( CChoreoScene *scene, CChoreoEvent *event, C_BaseEntity *pTarget = NULL, bool bClientSide = false ); + void AddSceneEvent( CChoreoScene *scene, CChoreoEvent *event, C_BaseEntity *pTarget = NULL, bool bClientSide = false, C_SceneEntity* pSceneEntity = NULL); // Remove the event from the queue for this actor void RemoveSceneEvent( CChoreoScene *scene, CChoreoEvent *event, bool fastKill ); diff --git a/src/game/client/c_baselesson.cpp b/src/game/client/c_baselesson.cpp new file mode 100644 index 00000000..ec01575b --- /dev/null +++ b/src/game/client/c_baselesson.cpp @@ -0,0 +1,3895 @@ +//========= Copyright © 1996-2008, Valve Corporation, All rights reserved. ============// +// +// Purpose: Client handler implementations for instruction players how to play +// +//=============================================================================// + +#include "cbase.h" + +#include "c_baselesson.h" +#include "c_gameinstructor.h" + +#include "hud_locator_target.h" +#include "c_world.h" +#include "iinput.h" +#include "ammodef.h" +#include "vprof.h" +#include "view.h" +#include "vstdlib/IKeyValuesSystem.h" +#ifdef MAPBASE +#include "usermessages.h" +#endif + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//========================================================= +// Configuración +//========================================================= + +#define LESSON_PRIORITY_MAX 1000 +#define LESSON_PRIORITY_NONE 0 +#define LESSON_MIN_TIME_ON_SCREEN_TO_MARK_DISPLAYED 1.5f +#define LESSON_MIN_TIME_BEFORE_LOCK_ALLOWED 0.1f +#define LESSON_DISTANCE_UPDATE_RATE 0.25f + +// See comments in UtlSymbol on why this is useful and how it works +IMPLEMENT_PRIVATE_SYMBOLTYPE( CGameInstructorSymbol ); + +extern ConVar gameinstructor_verbose; +extern ConVar gameinstructor_verbose_lesson; +extern ConVar gameinstructor_find_errors; + +#ifdef MAPBASE +// Mapbase was originally going to use a HL2-style default color (245,232,179). +// This is no longer the case, but mods are free to change this cvar in their config files. +ConVar gameinstructor_default_captioncolor( "gameinstructor_default_captioncolor", "255,255,255", FCVAR_NONE ); +ConVar gameinstructor_default_bindingcolor( "gameinstructor_default_bindingcolor", "0,0,0", FCVAR_NONE ); +#endif + +// +// CGameInstructorLesson +// + +Color CBaseLesson::m_rgbaVerboseHeader = Color( 255, 128, 64, 255 ); +Color CBaseLesson::m_rgbaVerbosePlain = Color( 64, 128, 255, 255 ); +Color CBaseLesson::m_rgbaVerboseName = Color( 255, 255, 255, 255 ); +Color CBaseLesson::m_rgbaVerboseOpen = Color( 0, 255, 0, 255 ); +Color CBaseLesson::m_rgbaVerboseClose = Color( 255, 0, 0, 255 ); +Color CBaseLesson::m_rgbaVerboseSuccess = Color( 255, 255, 0, 255 ); +Color CBaseLesson::m_rgbaVerboseUpdate = Color( 255, 0, 255, 255 ); + + +//========================================================= +// Constructor +//========================================================= +CBaseLesson::CBaseLesson( const char *pchName, bool bIsDefaultHolder, bool bIsOpenOpportunity ) +{ + COMPILE_TIME_ASSERT( sizeof( CGameInstructorSymbol ) == sizeof( CUtlSymbol ) ); + + m_stringName = pchName; + m_stringReplaceKey = ""; + m_bIsDefaultHolder = bIsDefaultHolder; + m_bIsOpenOpportunity = bIsOpenOpportunity; + + Init(); +} + +//========================================================= +// Destructor +//========================================================= +CBaseLesson::~CBaseLesson() +{ + // Remove from root's children list + if ( m_pRoot ) + m_pRoot->m_OpenOpportunities.FindAndRemove(this); + + else + { + for ( int i = 0; i < m_OpenOpportunities.Count(); ++i ) + { + // Remove from children if they are still around + CBaseLesson *pLesson = m_OpenOpportunities[ i ]; + pLesson->m_pRoot = NULL; + } + } +} + +//========================================================= +//========================================================= +void CBaseLesson::AddPrerequisite( const char *pchLessonName ) +{ + if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() ) + { + ConColorMsg( CBaseLesson::m_rgbaVerboseHeader, "\t%s: ", GetName() ); + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "Adding prereq " ); + ConColorMsg( CBaseLesson::m_rgbaVerboseOpen, "\"%s\"", pchLessonName ); + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ".\n" ); + } + + const CBaseLesson *pPrerequisite = GetGameInstructor().GetLesson( pchLessonName ); + + if ( !pPrerequisite ) + { + DevWarning( "Prerequisite %s added by lesson %s doesn't exist!\n", pchLessonName, GetName() ); + return; + } + + m_Prerequisites.AddToTail(pPrerequisite); +} + +//========================================================= +//========================================================= +void CBaseLesson::SetRoot( CBaseLesson *pRoot ) +{ + m_pRoot = pRoot; + + if ( m_pRoot->m_OpenOpportunities.Find( this ) == -1 ) + m_pRoot->m_OpenOpportunities.AddToTail( this ); +} + +//========================================================= +//========================================================= +bool CBaseLesson::ShouldShowSpew() +{ + // @DEBUG + return true; + + if ( gameinstructor_verbose_lesson.GetString()[ 0 ] == '\0' ) + return false; + + return ( Q_stristr( GetName(), gameinstructor_verbose_lesson.GetString() ) != NULL ); +} + +//========================================================= +//========================================================= +bool CBaseLesson::NoPriority() const +{ + return ( m_iPriority == LESSON_PRIORITY_NONE ); +} + +//========================================================= +//========================================================= +bool CBaseLesson::IsLocked() const +{ + if ( m_fLockDuration == 0.0f ) + return false; + + if ( !IsInstructing() || !IsVisible() ) + return false; + + float fLockTime = m_fLockTime; + + if ( fLockTime == 0.0f ) + fLockTime = m_fStartTime; + + return ( gpGlobals->curtime > m_fStartTime + LESSON_MIN_TIME_BEFORE_LOCK_ALLOWED && gpGlobals->curtime < fLockTime + m_fLockDuration ); +} + +//========================================================= +//========================================================= +bool CBaseLesson::IsLearned() const +{ + if ( m_iDisplayLimit > 0 && m_iDisplayCount >= m_iDisplayLimit ) + return true; + + if ( m_iSuccessLimit > 0 && m_iSuccessCount >= m_iSuccessLimit ) + return true; + + return false; +} + +//========================================================= +//========================================================= +bool CBaseLesson::PrerequisitesHaveBeenMet() const +{ + for ( int i = 0; i < m_Prerequisites.Count(); ++i ) + { + if ( !m_Prerequisites[ i ]->IsLearned() ) + { + // Failed a prereq + return false; + } + } + + // All prereqs passed + return true; +} + +//========================================================= +//========================================================= +bool CBaseLesson::IsTimedOut() +{ + VPROF_BUDGET( "CBaseLesson::IsTimedOut", "GameInstructor" ); + + // Check for no timeout + if ( m_fTimeout == 0.0f ) + return false; + + float fStartTime = m_fStartTime; + + if ( GetRoot()->IsLearned() ) + { + if ( !m_bBumpWithTimeoutWhenLearned ) + { + // Time out instantly if we've learned this and don't want to keep it open for priority bumping + return true; + } + else + { + // It'll never be active, so lets use timeout based on when it was initialized + fStartTime = m_fInitTime; + } + } + + if ( !fStartTime ) + { + if ( !m_bCanTimeoutWhileInactive ) + { + return false; + } + + // Not active, so lets use timeout based on when it was initialized + fStartTime = m_fInitTime; + } + + bool bTimedOut = ( fStartTime + m_fTimeout < gpGlobals->curtime ); + + if ( bTimedOut ) + SetCloseReason( "Timed out." ); + + return bTimedOut; +} + +//========================================================= +//========================================================= +void CBaseLesson::ResetDisplaysAndSuccesses() +{ + m_iDisplayCount = 0; + m_bSuccessCounted = false; + m_iSuccessCount = 0; +} + +//========================================================= +//========================================================= +bool CBaseLesson::IncDisplayCount() +{ + if ( m_iDisplayCount < m_iDisplayLimit ) + { + m_iDisplayCount++; + return true; + } + + return false; +} + +//========================================================= +//========================================================= +bool CBaseLesson::IncSuccessCount() +{ + if ( m_iSuccessCount < m_iSuccessLimit ) + { + m_iSuccessCount++; + return true; + } + + return false; +} + +//========================================================= +//========================================================= +void CBaseLesson::Init() +{ + m_pRoot = NULL; + m_bSuccessCounted = false; + + SetCloseReason( "None given." ); + + m_iPriority = LESSON_PRIORITY_MAX; // Set to invalid value to ensure that it is actually set later on + m_iInstanceType = LESSON_INSTANCE_MULTIPLE; + m_iFixedInstancesMax = 1; + m_bReplaceOnlyWhenStopped = false; + m_iTeam = TEAM_ANY; + m_bOnlyKeyboard = false; + m_bOnlyGamepad = false; + + m_iDisplayLimit = 0; + m_iDisplayCount = 0; + m_bWasDisplayed = false; + + m_iSuccessLimit = 0; + m_iSuccessCount = 0; + + m_fLockDuration = 0.0f; + m_bCanOpenWhenDead = false; + m_bBumpWithTimeoutWhenLearned = false; + m_bCanTimeoutWhileInactive = false; + m_fTimeout = 0.0f; + + m_fInitTime = gpGlobals->curtime; + m_fStartTime = 0.0f; + m_fLockTime = 0.0f; + + m_fUpdateInterval = 0.5; + m_bHasPlayedSound = false; + + m_szStartSound = "Instructor.LessonStart"; + m_szLessonGroup = ""; + + m_iNumDelayedPlayerSwaps = 0; +} + +//========================================================= +//========================================================= +void CBaseLesson::TakePlaceOf( CBaseLesson *pLesson ) +{ + // Transfer over marked as displayed so a replaced lesson won't count as an extra display + m_bWasDisplayed = pLesson->m_bWasDisplayed; + pLesson->m_bWasDisplayed = false; +} + +//========================================================= +//========================================================= +void CBaseLesson::MarkSucceeded() +{ + if ( !m_bSuccessCounted ) + { + GetGameInstructor().MarkSucceeded( GetName() ); + m_bSuccessCounted = true; + } +} + +//========================================================= +//========================================================= +void CBaseLesson::CloseOpportunity( const char *pchReason ) +{ + SetCloseReason(pchReason); + m_bIsOpenOpportunity = false; +} + +//========================================================= +//========================================================= +bool CBaseLesson::DoDelayedPlayerSwaps() const +{ + // A bot has swapped places with a player or player with a bot... + // At the time of the actual swap there was no client representation for the new player... + // So that swap was queued up and now we're going to make things right! + while ( m_iNumDelayedPlayerSwaps ) + { + C_BasePlayer *pNewPlayer = UTIL_PlayerByUserId( m_pDelayedPlayerSwap[ m_iNumDelayedPlayerSwaps - 1 ].iNewUserID ); + + if ( !pNewPlayer ) + { + // There is still no client representation of the new player, we'll have to try again later + if ( gameinstructor_verbose.GetInt() > 1 ) + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\tFailed delayed player swap!" ); + + return false; + } + + if ( gameinstructor_verbose.GetInt() > 1 ) + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\tSuccessful delayed player swap!" ); + + m_pDelayedPlayerSwap[ m_iNumDelayedPlayerSwaps - 1 ].phHandleToChange->Set( pNewPlayer ); + m_iNumDelayedPlayerSwaps--; + } + + return true; +} + + +// +// CTextLesson +// + +//========================================================= +//========================================================= +void CTextLesson::Init() +{ + m_szDisplayText = ""; + m_szDisplayParamText = ""; + m_szBinding = ""; + m_szGamepadBinding = ""; +} + +//========================================================= +//========================================================= +void CTextLesson::Start() +{ + // TODO: Display some text + //m_szDisplayText +} + +//========================================================= +//========================================================= +void CTextLesson::Stop() +{ + // TODO: Clean up text +} + +// +// CIconLesson +// + +void CIconLesson::Init() +{ + m_hIconTarget = NULL; + m_szVguiTargetName = ""; + m_szVguiTargetLookup = ""; + m_nVguiTargetEdge = 0; + + m_hLocatorTarget = -1; + m_bFixedPosition = false; + m_bNoIconTarget = false; + m_bAllowNodrawTarget = false; + + m_bVisible = true; + m_bShowWhenOccluded = true; + m_bNoOffscreen = false; + m_bForceCaption = false; + + m_szOnscreenIcon = ""; + m_szOffscreenIcon = ""; + + m_flUpOffset = 0.0f; + m_flRelativeUpOffset = 0.0f; + m_fFixedPositionX = 0.0f; + m_fFixedPositionY = 0.0f; + + m_fRange = 0.0f; + m_fCurrentDistance = 0.0f; + + m_fOnScreenStartTime = 0.0f; + m_fUpdateDistanceTime = 0.0f; + + m_iFlags = LOCATOR_ICON_FX_NONE; +#ifdef MAPBASE + m_szCaptionColor = gameinstructor_default_captioncolor.GetString(); + + m_iIconTargetPos = ICON_TARGET_EYE_POSITION; + m_szHudHint = ""; +#else + m_szCaptionColor = "255,255,255";// Default to white +#endif +} + +//========================================================= +//========================================================= +void CIconLesson::Start() +{ + if ( !DoDelayedPlayerSwaps() ) + return; + + // Display some text + C_BaseEntity *pIconTarget = m_hIconTarget.Get(); + + if ( !pIconTarget ) + { + if ( !m_bNoIconTarget ) + { + // Wanted one, but couldn't get it + CloseOpportunity( "Icon Target handle went invalid before the lesson started!" ); + } + + return; + } + else + { + if ( ( pIconTarget->IsEffectActive( EF_NODRAW ) || pIconTarget->IsDormant() ) && !m_bAllowNodrawTarget ) + { + // We don't allow no draw entities + CloseOpportunity( "Icon Target is using effect NODRAW and allow_nodraw_target is false!" ); + return; + } + } + + CLocatorTarget *pLocatorTarget = NULL; + + if ( m_hLocatorTarget != -1 ) + { + // Lets try the handle that we've held on to + pLocatorTarget = Locator_GetTargetFromHandle( m_hLocatorTarget ); + + if ( !pLocatorTarget ) + { + // It's gone stale, get a new target + m_hLocatorTarget = Locator_AddTarget(); + pLocatorTarget = Locator_GetTargetFromHandle( m_hLocatorTarget ); + } + } + else + { + // Get a new target + m_hLocatorTarget = Locator_AddTarget(); + pLocatorTarget = Locator_GetTargetFromHandle( m_hLocatorTarget ); + } + + if( m_hLocatorTarget == -1 || !pLocatorTarget ) + { + CloseOpportunity( "Could not get a handle for new locator target. Too many targets in use!" ); + return; + } + + pLocatorTarget->AddIconEffects( m_iFlags ); + pLocatorTarget->SetCaptionColor( GetCaptionColorString() ); + UpdateLocatorTarget( pLocatorTarget, pIconTarget ); + + // Update occlusion data + Locator_ComputeTargetIconPositionFromHandle( m_hLocatorTarget ); +} + +//========================================================= +//========================================================= +void CIconLesson::Stop() +{ + if ( !DoDelayedPlayerSwaps() ) + return; + + if ( m_hLocatorTarget != -1 ) + Locator_RemoveTarget( m_hLocatorTarget ); + + m_fOnScreenStartTime = 0.0f; +} + +//========================================================= +//========================================================= +void CIconLesson::Update() +{ + if ( !DoDelayedPlayerSwaps() ) + return; + + C_BaseEntity *pIconTarget = m_hIconTarget.Get(); + + if ( !pIconTarget ) + { + if ( !m_bNoIconTarget ) + { + CloseOpportunity( "Lost our icon target handle returned NULL." ); + } + + return; + } + else + { + if ( ( pIconTarget->IsEffectActive( EF_NODRAW ) || pIconTarget->IsDormant() ) && !m_bAllowNodrawTarget ) + { + // We don't allow no draw entities + CloseOpportunity( "Icon Target is using effect NODRAW and allow_nodraw_target is false!" ); + return; + } + } + + CLocatorTarget *pLocatorTarget = Locator_GetTargetFromHandle( m_hLocatorTarget ); + if ( !pLocatorTarget ) + { + // Temp instrumentation to catch a bug - possibly calling Update without having called Start? + Warning( "Problem in lesson %s: Locator_GetTargetFromHandle returned null for handle %d.\n IsInstanceActive: %s. IsInstructing: %s. IsLearned: %s\n", + GetName(), m_hLocatorTarget, + (IsInstanceActive() ? "yes" : "no"), + (IsInstructing() ? "yes" : "no"), + (IsLearned() ? "yes" : "no") ); + CloseOpportunity( "Lost locator target handle." ); + return; + } + + UpdateLocatorTarget( pLocatorTarget, pIconTarget ); + C_BasePlayer *pLocalPlayer = GetGameInstructor().GetLocalPlayer(); + + // Check if it has been onscreen long enough to count as being displayed + if ( m_fOnScreenStartTime == 0.0f ) + { + if ( pLocatorTarget->IsOnScreen() && ( IsPresentComplete() || ( pLocatorTarget->GetIconEffectsFlags() & LOCATOR_ICON_FX_STATIC ) ) ) + { + // Is either static or has finished presenting and is on screen + m_fOnScreenStartTime = gpGlobals->curtime; + } + } + else + { + if ( !pLocatorTarget->IsOnScreen() ) + { + // Was visible before, but it isn't now + m_fOnScreenStartTime = 0.0f; + } + else if ( gpGlobals->curtime - m_fOnScreenStartTime >= LESSON_MIN_TIME_ON_SCREEN_TO_MARK_DISPLAYED ) + { + // Lesson on screen long enough to be counted as displayed + m_bWasDisplayed = true; + } + } + + if ( m_fUpdateDistanceTime < gpGlobals->curtime ) + { + // Update it's distance from the local player + C_BaseEntity *pTarget = m_hIconTarget.Get(); + + if ( !pLocalPlayer || !pTarget || pLocalPlayer == pTarget ) + { + m_fCurrentDistance = 0.0f; + } + else + { + m_fCurrentDistance = pLocalPlayer->EyePosition().DistTo( pTarget->WorldSpaceCenter() ); + } + + m_fUpdateDistanceTime = gpGlobals->curtime + LESSON_DISTANCE_UPDATE_RATE; + } +} + +void CIconLesson::UpdateInactive() +{ + if ( m_fUpdateDistanceTime < gpGlobals->curtime ) + { + if ( !DoDelayedPlayerSwaps() ) + { + return; + } + + C_BaseEntity *pIconTarget = m_hIconTarget.Get(); + + if ( !pIconTarget ) + { + if ( !m_bNoIconTarget ) + { + CloseOpportunity( "Lost our icon target handle returned NULL." ); + } + + m_fCurrentDistance = 0.0f; + return; + } + else + { + if ( ( pIconTarget->IsEffectActive( EF_NODRAW ) || pIconTarget->IsDormant() ) && !m_bAllowNodrawTarget ) + { + // We don't allow no draw entities + CloseOpportunity( "Icon Target is using effect NODRAW and allow_nodraw_target is false!" ); + return; + } + } + + // Update it's distance from the local player + C_BasePlayer *pLocalPlayer = GetGameInstructor().GetLocalPlayer(); + + if ( !pLocalPlayer || pLocalPlayer == pIconTarget ) + { + m_fCurrentDistance = 0.0f; + } + else + { + m_fCurrentDistance = pLocalPlayer->EyePosition().DistTo( pIconTarget->WorldSpaceCenter() ); + } + +#ifdef MAPBASE + if (m_szHudHint.String()[0] != '\0' && GetRoot()->IsLearned()) + { + DevMsg("Showing hint\n"); + CUtlBuffer msg_data; + msg_data.PutChar( 1 ); + msg_data.PutString( m_szHudHint.String() ); + bf_read msg( msg_data.Base(), msg_data.TellPut() ); + usermessages->DispatchUserMessage( usermessages->LookupUserMessage( "KeyHintText" ), msg ); + } +#endif + + m_fUpdateDistanceTime = gpGlobals->curtime + LESSON_DISTANCE_UPDATE_RATE; + } +} + +bool CIconLesson::ShouldDisplay() const +{ + VPROF_BUDGET( "CIconLesson::ShouldDisplay", "GameInstructor" ); + + if ( !DoDelayedPlayerSwaps() ) + { + return false; + } + + if ( m_fRange > 0.0f && m_fCurrentDistance > m_fRange ) + { + // Distance to target is more than the max range + return false; + } + + if ( !m_bShowWhenOccluded && m_hLocatorTarget >= 0 ) + { + CLocatorTarget *pLocatorTarget = Locator_GetTargetFromHandle( m_hLocatorTarget ); + + if ( pLocatorTarget && pLocatorTarget->IsOccluded() ) + { + // Target is occluded and doesn't want to be shown when occluded + return false; + } + } + + // Ok to display + return true; +} + +bool CIconLesson::IsVisible() const +{ + VPROF_BUDGET( "CIconLesson::IsVisible", "GameInstructor" ); + + if( m_hLocatorTarget == -1 ) + { + // If it doesn't want a target, it's "visible" otherwise we'll have to call it invisible + return m_bNoIconTarget; + } + + CLocatorTarget *pLocatorTarget = Locator_GetTargetFromHandle( m_hLocatorTarget ); + if ( !pLocatorTarget ) + { + return false; + } + + return pLocatorTarget->IsVisible(); +} + +void CIconLesson::SwapOutPlayers( int iOldUserID, int iNewUserID ) +{ + BaseClass::SwapOutPlayers( iOldUserID, iNewUserID ); + + if ( m_bNoIconTarget ) + return; + + // Get the player pointers from the user IDs + C_BasePlayer *pOldPlayer = UTIL_PlayerByUserId( iOldUserID ); + C_BasePlayer *pNewPlayer = UTIL_PlayerByUserId( iNewUserID ); + + if ( pOldPlayer == m_hIconTarget.Get() ) + { + if ( pNewPlayer ) + { + m_hIconTarget = pNewPlayer; + } + else + { + if ( m_iNumDelayedPlayerSwaps < MAX_DELAYED_PLAYER_SWAPS ) + { + m_pDelayedPlayerSwap[ m_iNumDelayedPlayerSwaps ].phHandleToChange = &m_hIconTarget; + m_pDelayedPlayerSwap[ m_iNumDelayedPlayerSwaps ].iNewUserID = iNewUserID; + ++m_iNumDelayedPlayerSwaps; + } + } + } +} + +void CIconLesson::TakePlaceOf( CBaseLesson *pLesson ) +{ + BaseClass::TakePlaceOf( pLesson ); + + const CIconLesson *pIconLesson = dynamic_cast( pLesson ); + + if ( pIconLesson ) + { + if ( pIconLesson->m_hLocatorTarget != -1 ) + { + CLocatorTarget *pLocatorTarget = Locator_GetTargetFromHandle( pIconLesson->m_hLocatorTarget ); + + if ( pLocatorTarget ) + { + // This one draw right to the hud... use it's icon target handle + m_hLocatorTarget = pIconLesson->m_hLocatorTarget; + } + } + + m_fOnScreenStartTime = pIconLesson->m_fOnScreenStartTime; + } +} + +void CIconLesson::SetLocatorBinding( CLocatorTarget * pLocatorTarget ) +{ + if ( IsX360() /*|| input->ControllerModeActive()*/ ) + { + // Try to use gamepad bindings first + if ( m_szGamepadBinding.String()[ 0 ] != '\0' ) + { + // Found gamepad binds! + pLocatorTarget->SetBinding( m_szGamepadBinding.String() ); + } + else + { + // No gamepad binding, so fallback to the regular binding + pLocatorTarget->SetBinding( m_szBinding.String() ); + } + } + else + { + // Always use the regular binding when the gamepad is disabled + pLocatorTarget->SetBinding( m_szBinding.String() ); + } +} + +bool CIconLesson::IsPresentComplete() +{ + if ( m_hLocatorTarget == -1 ) + return false; + + CLocatorTarget *pLocatorTarget = Locator_GetTargetFromHandle( m_hLocatorTarget ); + + if ( !pLocatorTarget ) + return false; + + return !pLocatorTarget->IsPresenting(); +} + +void CIconLesson::PresentStart() +{ + if ( m_hLocatorTarget == -1 ) + return; + + CLocatorTarget *pLocatorTarget = Locator_GetTargetFromHandle( m_hLocatorTarget ); + + if ( !pLocatorTarget ) + return; + + pLocatorTarget->StartPresent(); +} + +void CIconLesson::PresentEnd() +{ + if ( m_hLocatorTarget == -1 ) + return; + + CLocatorTarget *pLocatorTarget = Locator_GetTargetFromHandle( m_hLocatorTarget ); + + if ( !pLocatorTarget ) + return; + + pLocatorTarget->EndPresent(); +} + +void CIconLesson::UpdateLocatorTarget( CLocatorTarget *pLocatorTarget, C_BaseEntity *pIconTarget ) +{ + if ( m_bFixedPosition ) + { + pLocatorTarget->m_bOriginInScreenspace = true; + pLocatorTarget->m_vecOrigin.x = m_fFixedPositionX; + pLocatorTarget->m_vecOrigin.y = m_fFixedPositionY; + pLocatorTarget->SetVguiTargetName( m_szVguiTargetName.String() ); + pLocatorTarget->SetVguiTargetLookup( m_szVguiTargetLookup.String() ); + pLocatorTarget->SetVguiTargetEdge( m_nVguiTargetEdge ); + } + else + { + pLocatorTarget->m_bOriginInScreenspace = false; +#ifdef MAPBASE + pLocatorTarget->m_vecOrigin = GetIconTargetPosition( pIconTarget ) + MainViewUp() * m_flRelativeUpOffset + Vector( 0.0f, 0.0f, m_flUpOffset ); +#else + pLocatorTarget->m_vecOrigin = pIconTarget->EyePosition() + MainViewUp() * m_flRelativeUpOffset + Vector( 0.0f, 0.0f, m_flUpOffset ); +#endif + pLocatorTarget->SetVguiTargetName( "" ); + } + + const char *pchDisplayParamText = m_szDisplayParamText.String(); +#ifdef INFESTED_DLL + char szCustomName[ 256 ]; +#endif + + // Check if the parameter is the be the player display name + if ( Q_stricmp( pchDisplayParamText, "use_name" ) == 0 ) + { + // Fix up the player display name + C_BasePlayer *pPlayer = ToBasePlayer( pIconTarget ); + if ( pPlayer ) + { + pchDisplayParamText = pPlayer->GetPlayerName(); + } + else + { + bool bNoName = true; + +#ifdef INFESTED_DLL + C_ASW_Marine *pMarine = dynamic_cast< C_ASW_Marine* >( pIconTarget ); + if ( pMarine ) + { + C_ASW_Marine_Resource *pMR = pMarine->GetMarineResource(); + if ( pMR ) + { + pMR->GetDisplayName( szCustomName, sizeof( szCustomName ) ); + pchDisplayParamText = szCustomName; + bNoName = false; + } + } +#endif + + if ( bNoName ) + { + // It's not a player! + pchDisplayParamText = ""; + } + } + } + + pLocatorTarget->SetCaptionText( m_szDisplayText.String(), pchDisplayParamText ); + SetLocatorBinding( pLocatorTarget ); + pLocatorTarget->SetOnscreenIconTextureName( m_szOnscreenIcon.String() ); + pLocatorTarget->SetOffscreenIconTextureName( m_szOffscreenIcon.String() ); + pLocatorTarget->SetVisible( m_bVisible ); + + C_BasePlayer *pLocalPlayer = GetGameInstructor().GetLocalPlayer(); + + if( !m_bFixedPosition && + ( ( pLocalPlayer != NULL && pLocalPlayer == m_hIconTarget ) || + GetClientWorldEntity() == m_hIconTarget ) ) + { + // Mark this icon as a static icon that draws in a fixed + // location on the hud rather than tracking an object + // in 3D space. + pLocatorTarget->AddIconEffects( LOCATOR_ICON_FX_STATIC ); + } + else + { + pLocatorTarget->AddIconEffects( LOCATOR_ICON_FX_NONE ); + } + + if ( m_bNoOffscreen ) + { + pLocatorTarget->AddIconEffects( LOCATOR_ICON_FX_NO_OFFSCREEN ); + } + else + { + pLocatorTarget->RemoveIconEffects( LOCATOR_ICON_FX_NO_OFFSCREEN ); + } + + if( m_bForceCaption || IsLocked() ) + { + pLocatorTarget->AddIconEffects( LOCATOR_ICON_FX_FORCE_CAPTION ); + } + else + { + pLocatorTarget->RemoveIconEffects( LOCATOR_ICON_FX_FORCE_CAPTION ); + } + + pLocatorTarget->Update(); + + if ( pLocatorTarget->m_bIsDrawing ) + { + if ( !m_bHasPlayedSound ) + { + GetGameInstructor().PlaySound( m_szStartSound.String() ); + m_bHasPlayedSound = true; + } + } +} + +#ifdef MAPBASE +Vector CIconLesson::GetIconTargetPosition( C_BaseEntity *pIconTarget ) +{ + switch (m_iIconTargetPos) + { + default: + case ICON_TARGET_EYE_POSITION: + return pIconTarget->EyePosition(); + + case ICON_TARGET_ORIGIN: + return pIconTarget->GetAbsOrigin(); + + case ICON_TARGET_CENTER: + return pIconTarget->WorldSpaceCenter(); + } +} +#endif + +// +// CScriptedIconLesson +// + +// Linking variables to scriptable entries is done here! +// The first parameter correlates to the case insensitive string name read from scripts. +// This macro generates code that passes this consistent variable data in to other macros +#define LESSON_VARIABLE_FACTORY \ + LESSON_VARIABLE_MACRO_EHANDLE( VOID, m_hLocalPlayer, EHANDLE ) \ + \ + LESSON_VARIABLE_MACRO_EHANDLE( LOCAL_PLAYER, m_hLocalPlayer, EHANDLE ) \ + LESSON_VARIABLE_MACRO( OUTPUT, m_fOutput, float ) \ + \ + LESSON_VARIABLE_MACRO_EHANDLE( ENTITY1, m_hEntity1, EHANDLE ) \ + LESSON_VARIABLE_MACRO_EHANDLE( ENTITY2, m_hEntity2, EHANDLE ) \ + LESSON_VARIABLE_MACRO_STRING( STRING1, m_szString1, CGameInstructorSymbol ) \ + LESSON_VARIABLE_MACRO_STRING( STRING2, m_szString2, CGameInstructorSymbol ) \ + LESSON_VARIABLE_MACRO( INTEGER1, m_iInteger1, int ) \ + LESSON_VARIABLE_MACRO( INTEGER2, m_iInteger2, int ) \ + LESSON_VARIABLE_MACRO( FLOAT1, m_fFloat1, float ) \ + LESSON_VARIABLE_MACRO( FLOAT2, m_fFloat2, float ) \ + \ + LESSON_VARIABLE_MACRO_EHANDLE( ICON_TARGET, m_hIconTarget, EHANDLE ) \ + LESSON_VARIABLE_MACRO_STRING( VGUI_TARGET_NAME, m_szVguiTargetName, CGameInstructorSymbol ) \ + LESSON_VARIABLE_MACRO_STRING( VGUI_TARGET_LOOKUP, m_szVguiTargetLookup, CGameInstructorSymbol ) \ + LESSON_VARIABLE_MACRO( VGUI_TARGET_EDGE, m_nVguiTargetEdge, int ) \ + LESSON_VARIABLE_MACRO( FIXED_POSITION_X, m_fFixedPositionX, float ) \ + LESSON_VARIABLE_MACRO( FIXED_POSITION_Y, m_fFixedPositionY, float ) \ + LESSON_VARIABLE_MACRO_BOOL( FIXED_POSITION, m_bFixedPosition, bool ) \ + LESSON_VARIABLE_MACRO_BOOL( NO_ICON_TARGET, m_bNoIconTarget, bool ) \ + LESSON_VARIABLE_MACRO_BOOL( ALLOW_NODRAW_TARGET, m_bAllowNodrawTarget, bool ) \ + LESSON_VARIABLE_MACRO_BOOL( VISIBLE, m_bVisible, bool ) \ + LESSON_VARIABLE_MACRO_BOOL( SHOW_WHEN_OCCLUDED, m_bShowWhenOccluded, bool ) \ + LESSON_VARIABLE_MACRO_BOOL( NO_OFFSCREEN, m_bNoOffscreen, bool ) \ + LESSON_VARIABLE_MACRO_BOOL( FORCE_CAPTION, m_bForceCaption, bool ) \ + LESSON_VARIABLE_MACRO_STRING( ONSCREEN_ICON, m_szOnscreenIcon, CGameInstructorSymbol ) \ + LESSON_VARIABLE_MACRO_STRING( OFFSCREEN_ICON, m_szOffscreenIcon, CGameInstructorSymbol ) \ + LESSON_VARIABLE_MACRO( ICON_OFFSET, m_flUpOffset, float ) \ + LESSON_VARIABLE_MACRO( ICON_RELATIVE_OFFSET, m_flRelativeUpOffset, float ) \ + LESSON_VARIABLE_MACRO( RANGE, m_fRange, float ) \ + \ + LESSON_VARIABLE_MACRO( FLAGS, m_iFlags, int ) \ + LESSON_VARIABLE_MACRO_STRING( CAPTION_COLOR, m_szCaptionColor, CGameInstructorSymbol ) \ + LESSON_VARIABLE_MACRO_STRING( GROUP, m_szLessonGroup, CGameInstructorSymbol ) \ + \ + LESSON_VARIABLE_MACRO_STRING( CAPTION, m_szDisplayText, CGameInstructorSymbol ) \ + LESSON_VARIABLE_MACRO_STRING( CAPTION_PARAM, m_szDisplayParamText, CGameInstructorSymbol ) \ + LESSON_VARIABLE_MACRO_STRING( BINDING, m_szBinding, CGameInstructorSymbol ) \ + LESSON_VARIABLE_MACRO_STRING( GAMEPAD_BINDING, m_szGamepadBinding, CGameInstructorSymbol ) \ + \ + LESSON_VARIABLE_MACRO( PRIORITY, m_iPriority, int ) \ + LESSON_VARIABLE_MACRO_STRING( REPLACE_KEY, m_stringReplaceKey, CGameInstructorSymbol ) \ + \ + LESSON_VARIABLE_MACRO( LOCK_DURATION, m_fLockDuration, float ) \ + LESSON_VARIABLE_MACRO_BOOL( CAN_OPEN_WHEN_DEAD, m_bCanOpenWhenDead, bool ) \ + LESSON_VARIABLE_MACRO_BOOL( BUMP_WITH_TIMEOUT_WHEN_LEARNED, m_bBumpWithTimeoutWhenLearned, bool ) \ + LESSON_VARIABLE_MACRO_BOOL( CAN_TIMEOUT_WHILE_INACTIVE, m_bCanTimeoutWhileInactive, bool ) \ + LESSON_VARIABLE_MACRO( TIMEOUT, m_fTimeout, float ) \ + LESSON_VARIABLE_MACRO( UPDATE_INTERVAL, m_fUpdateInterval, float ) \ + LESSON_VARIABLE_MACRO_STRING( START_SOUND, m_szStartSound, CGameInstructorSymbol ) \ + \ + LESSON_VARIABLE_MACRO( ICON_TARGET_POS, m_iIconTargetPos, int ) \ + LESSON_VARIABLE_MACRO_STRING( HUD_HINT_AFTER_LEARNED, m_szHudHint, CGameInstructorSymbol ) \ + + +// Create keyvalues name symbol +#define LESSON_VARIABLE_SYMBOL( _varEnum, _varName, _varType ) static int g_n##_varEnum##Symbol; + +#define LESSON_VARIABLE_INIT_SYMBOL( _varEnum, _varName, _varType ) g_n##_varEnum##Symbol = KeyValuesSystem()->GetSymbolForString( #_varEnum ); + +#define LESSON_SCRIPT_STRING_ADD_TO_MAP( _varEnum, _varName, _varType ) g_NameToTypeMap.Insert( #_varEnum, LESSON_VARIABLE_##_varEnum ); + +// Create enum value +#define LESSON_VARIABLE_ENUM( _varEnum, _varName, _varType ) LESSON_VARIABLE_##_varEnum, + +// Init info call +#define LESSON_VARIABLE_INIT_INFO_CALL( _varEnum, _varName, _varType ) g_pLessonVariableInfo[ LESSON_VARIABLE_##_varEnum ].Init_##_varEnum(); + +// Init info +#define LESSON_VARIABLE_INIT_INFO( _varEnum, _varName, _varType ) \ + void Init_##_varEnum() \ + { \ + iOffset = offsetof( CScriptedIconLesson, CScriptedIconLesson::_varName ); \ + varType = LessonParamTypeFromString( #_varType ); \ + } + +#define LESSON_VARIABLE_INIT_INFO_BOOL( _varEnum, _varName, _varType ) \ + void Init_##_varEnum() \ + { \ + iOffset = offsetof( CScriptedIconLesson, CScriptedIconLesson::_varName ); \ + varType = FIELD_BOOLEAN; \ + } + +#define LESSON_VARIABLE_INIT_INFO_EHANDLE( _varEnum, _varName, _varType ) \ + void Init_##_varEnum() \ + { \ + iOffset = offsetof( CScriptedIconLesson, CScriptedIconLesson::_varName ); \ + varType = FIELD_EHANDLE; \ + } + +#define LESSON_VARIABLE_INIT_INFO_STRING( _varEnum, _varName, _varType ) \ + void Init_##_varEnum() \ + { \ + iOffset = offsetof( CScriptedIconLesson, CScriptedIconLesson::_varName ); \ + varType = FIELD_STRING; \ + } + +// Copy defaults into this scripted lesson into a new one +#define LESSON_VARIABLE_DEFAULT( _varEnum, _varName, _varType ) ( _varName = m_pDefaultHolder->_varName ); + +// Copy a variable from this scripted lesson into a new one +#define LESSON_VARIABLE_COPY( _varEnum, _varName, _varType ) ( pOpenLesson->_varName = _varName ); + +// Return the first param if pchName is the same as the second param +#define LESSON_SCRIPT_STRING( _type, _string ) \ + if ( Q_stricmp( pchName, _string ) == 0 )\ + {\ + return _type;\ + } + +// Wrapper for using this macro in the factory +#define LESSON_SCRIPT_STRING_GENERAL( _varEnum, _varName, _varType ) LESSON_SCRIPT_STRING( LESSON_VARIABLE_##_varEnum##, #_varEnum ) + +// Process the element action on this variable +#define PROCESS_LESSON_ACTION( _varEnum, _varName, _varType ) \ + case LESSON_VARIABLE_##_varEnum:\ + return ProcessElementAction( pLessonElement->iAction, pLessonElement->bNot, #_varName, _varName, &pLessonElement->szParam, eventParam_float ); + +#define PROCESS_LESSON_ACTION_EHANDLE( _varEnum, _varName, _varType ) \ + case LESSON_VARIABLE_##_varEnum:\ + return ProcessElementAction( pLessonElement->iAction, pLessonElement->bNot, #_varName, _varName, &pLessonElement->szParam, eventParam_float, eventParam_BaseEntity, eventParam_string ); + +#define PROCESS_LESSON_ACTION_STRING( _varEnum, _varName, _varType ) \ + case LESSON_VARIABLE_##_varEnum:\ + return ProcessElementAction( pLessonElement->iAction, pLessonElement->bNot, #_varName, &_varName, &pLessonElement->szParam, eventParam_string ); + +// Init the variable from the script (or a convar) +#define LESSON_VARIABLE_INIT( _varEnum, _varName, _varType ) \ + else if ( g_n##_varEnum##Symbol == pSubKey->GetNameSymbol() ) \ + { \ + const char *pchParam = pSubKey->GetString(); \ + if ( pchParam && StringHasPrefix( pchParam, "convar " ) ) \ + { \ + ConVarRef tempCVar( pchParam + Q_strlen( "convar " ) ); \ + if ( tempCVar.IsValid() ) \ + { \ + _varName = static_cast<_varType>( tempCVar.GetFloat() ); \ + } \ + else \ + { \ + _varName = static_cast<_varType>( 0.0f ); \ + } \ + } \ + else \ + { \ + _varName = static_cast<_varType>( pSubKey->GetFloat() ); \ + } \ + } + +#define LESSON_VARIABLE_INIT_BOOL( _varEnum, _varName, _varType ) \ + else if ( Q_stricmp( #_varEnum, pSubKey->GetName() ) == 0 ) \ + { \ + _varName = pSubKey->GetBool(); \ + } + +#define LESSON_VARIABLE_INIT_EHANDLE( _varEnum, _varName, _varType ) \ + else if ( g_n##_varEnum##Symbol == pSubKey->GetNameSymbol() ) \ + { \ + DevWarning( "Can't initialize an EHANDLE from the instructor lesson script." ); \ + } + +#define LESSON_VARIABLE_INIT_STRING( _varEnum, _varName, _varType ) \ + else if ( g_n##_varEnum##Symbol == pSubKey->GetNameSymbol() ) \ + { \ + const char *pchParam = pSubKey->GetString(); \ + if ( pchParam && StringHasPrefix( pchParam, "convar " ) ) \ + { \ + ConVarRef tempCVar( pchParam + Q_strlen( "convar " ) ); \ + if ( tempCVar.IsValid() ) \ + { \ + _varName = tempCVar.GetString(); \ + } \ + else \ + { \ + _varName = ""; \ + } \ + } \ + else \ + { \ + _varName = pSubKey->GetString(); \ + } \ + } + +// Gets a scripted variable by offset and casts it to the proper type +#define LESSON_VARIABLE_GET_FROM_OFFSET( _type, _offset ) *static_cast<_type*>( static_cast( static_cast( static_cast( this ) ) + _offset ) ) + + +// Enum of scripted variables +enum LessonVariable +{ + // Run enum macros on all scriptable variables (see: LESSON_VARIABLE_FACTORY definition) +#define LESSON_VARIABLE_MACRO LESSON_VARIABLE_ENUM +#define LESSON_VARIABLE_MACRO_BOOL LESSON_VARIABLE_ENUM +#define LESSON_VARIABLE_MACRO_EHANDLE LESSON_VARIABLE_ENUM +#define LESSON_VARIABLE_MACRO_STRING LESSON_VARIABLE_ENUM + LESSON_VARIABLE_FACTORY +#undef LESSON_VARIABLE_MACRO +#undef LESSON_VARIABLE_MACRO_BOOL +#undef LESSON_VARIABLE_MACRO_EHANDLE +#undef LESSON_VARIABLE_MACRO_STRING + + LESSON_VARIABLE_TOTAL +}; + +// Declare the keyvalues symbols for the keynames +#define LESSON_VARIABLE_MACRO LESSON_VARIABLE_SYMBOL +#define LESSON_VARIABLE_MACRO_BOOL LESSON_VARIABLE_SYMBOL +#define LESSON_VARIABLE_MACRO_EHANDLE LESSON_VARIABLE_SYMBOL +#define LESSON_VARIABLE_MACRO_STRING LESSON_VARIABLE_SYMBOL + LESSON_VARIABLE_FACTORY +#undef LESSON_VARIABLE_MACRO +#undef LESSON_VARIABLE_MACRO_BOOL +#undef LESSON_VARIABLE_MACRO_EHANDLE +#undef LESSON_VARIABLE_MACRO_STRING + +// String lookup prototypes +LessonVariable LessonVariableFromString( const char *pchName, bool bWarnOnInvalidNames = true ); +_fieldtypes LessonParamTypeFromString( const char *pchName ); +int LessonActionFromString( const char *pchName ); + + +// This is used to get type info an variable offsets from the variable enumerated value +class LessonVariableInfo +{ +public: + + LessonVariableInfo() + : iOffset( 0 ), varType( FIELD_VOID ) + { + } + + // Run init info macros on all scriptable variables (see: LESSON_VARIABLE_FACTORY definition) +#define LESSON_VARIABLE_MACRO LESSON_VARIABLE_INIT_INFO +#define LESSON_VARIABLE_MACRO_BOOL LESSON_VARIABLE_INIT_INFO_BOOL +#define LESSON_VARIABLE_MACRO_EHANDLE LESSON_VARIABLE_INIT_INFO_EHANDLE +#define LESSON_VARIABLE_MACRO_STRING LESSON_VARIABLE_INIT_INFO_STRING + LESSON_VARIABLE_FACTORY +#undef LESSON_VARIABLE_MACRO +#undef LESSON_VARIABLE_MACRO_BOOL +#undef LESSON_VARIABLE_MACRO_EHANDLE +#undef LESSON_VARIABLE_MACRO_STRING + +public: + + int iOffset; + _fieldtypes varType; +}; + +LessonVariableInfo g_pLessonVariableInfo[ LESSON_VARIABLE_TOTAL ]; + + +const LessonVariableInfo *GetLessonVariableInfo( int iLessonVariable ) +{ + Assert( iLessonVariable >= 0 && iLessonVariable < LESSON_VARIABLE_TOTAL ); + + if ( g_pLessonVariableInfo[ 0 ].varType == FIELD_VOID ) + { + // Run init info call macros on all scriptable variables (see: LESSON_VARIABLE_FACTORY definition) +#define LESSON_VARIABLE_MACRO LESSON_VARIABLE_INIT_INFO_CALL +#define LESSON_VARIABLE_MACRO_BOOL LESSON_VARIABLE_INIT_INFO_CALL +#define LESSON_VARIABLE_MACRO_EHANDLE LESSON_VARIABLE_INIT_INFO_CALL +#define LESSON_VARIABLE_MACRO_STRING LESSON_VARIABLE_INIT_INFO_CALL + LESSON_VARIABLE_FACTORY +#undef LESSON_VARIABLE_MACRO +#undef LESSON_VARIABLE_MACRO_BOOL +#undef LESSON_VARIABLE_MACRO_EHANDLE +#undef LESSON_VARIABLE_MACRO_STRING + } + + return &(g_pLessonVariableInfo[ iLessonVariable ]); +} + +static CUtlDict< LessonVariable, int > g_NameToTypeMap; +static CUtlDict< fieldtype_t, int > g_TypeToParamTypeMap; +CUtlDict< int, int > CScriptedIconLesson::LessonActionMap; + +CScriptedIconLesson::~CScriptedIconLesson() +{ + if ( m_pDefaultHolder ) + { + delete m_pDefaultHolder; + m_pDefaultHolder = NULL; + } +} + + +void CScriptedIconLesson::Init() +{ + m_hLocalPlayer.Set( NULL ); + m_fOutput = 0.0f; + m_hEntity1.Set( NULL ); + m_hEntity2.Set( NULL ); + m_szString1 = ""; + m_szString2 = ""; + m_iInteger1 = 0; + m_iInteger2 = 0; + m_fFloat1 = 0.0f; + m_fFloat2 = 0.0f; + + m_fUpdateEventTime = 0.0f; + m_pDefaultHolder = NULL; + m_iScopeDepth = 0; + + if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() ) + { + ConColorMsg( CBaseLesson::m_rgbaVerboseHeader, "GAME INSTRUCTOR: " ); + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "Initializing scripted lesson " ); + ConColorMsg( CBaseLesson::m_rgbaVerboseName, "\"%s\"", GetName() ); + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "...\n" ); + } + + if ( !IsDefaultHolder() ) + { + if ( !IsOpenOpportunity() ) + { + // Initialize from the key value file + InitFromKeys( GetGameInstructor().GetScriptKeys() ); + + if ( m_iPriority >= LESSON_PRIORITY_MAX ) + { + DevWarning( "Priority level not set for lesson: %s\n", GetName() ); + } + + // We use this to remember variable defaults to be reset before each open attempt + m_pDefaultHolder = new CScriptedIconLesson( GetName(), true, false ); + CScriptedIconLesson *pOpenLesson = m_pDefaultHolder; + + // Run copy macros on all default scriptable variables (see: LESSON_VARIABLE_FACTORY definition) +#define LESSON_VARIABLE_MACRO LESSON_VARIABLE_COPY +#define LESSON_VARIABLE_MACRO_BOOL LESSON_VARIABLE_COPY +#define LESSON_VARIABLE_MACRO_EHANDLE LESSON_VARIABLE_COPY +#define LESSON_VARIABLE_MACRO_STRING LESSON_VARIABLE_COPY + LESSON_VARIABLE_FACTORY +#undef LESSON_VARIABLE_MACRO +#undef LESSON_VARIABLE_MACRO_BOOL +#undef LESSON_VARIABLE_MACRO_EHANDLE +#undef LESSON_VARIABLE_MACRO_STRING + + // Listen for open events + for ( int iLessonEvent = 0; iLessonEvent < m_OpenEvents.Count(); ++iLessonEvent ) + { + const LessonEvent_t *pLessonEvent = &(m_OpenEvents[ iLessonEvent ]); + ListenForGameEvent( pLessonEvent->szEventName.String() ); + + if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() ) + { + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\tListen for open event " ); + ConColorMsg( CBaseLesson::m_rgbaVerboseOpen, "\"%s\"", pLessonEvent->szEventName.String()); + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ".\n" ); + } + } + + // Listen for close events + for ( int iLessonEvent = 0; iLessonEvent < m_CloseEvents.Count(); ++iLessonEvent ) + { + const LessonEvent_t *pLessonEvent = &(m_CloseEvents[ iLessonEvent ]); + ListenForGameEvent( pLessonEvent->szEventName.String() ); + + if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() ) + { + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\tListen for close event " ); + ConColorMsg( CBaseLesson::m_rgbaVerboseClose, "\"%s\"", pLessonEvent->szEventName.String()); + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ".\n" ); + } + } + + // Listen for success events + for ( int iLessonEvent = 0; iLessonEvent < m_SuccessEvents.Count(); ++iLessonEvent ) + { + const LessonEvent_t *pLessonEvent = &(m_SuccessEvents[ iLessonEvent ]); + ListenForGameEvent( pLessonEvent->szEventName.String()); + + if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() ) + { + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\tListen for success event " ); + ConColorMsg( CBaseLesson::m_rgbaVerboseSuccess, "\"%s\"", pLessonEvent->szEventName.String()); + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ".\n" ); + } + } + } + else + { + // This is an open lesson! Get the root for reference + const CScriptedIconLesson *pLesson = static_cast( GetGameInstructor().GetLesson( GetName() ) ); + SetRoot( const_cast( pLesson ) ); + } + } +} + +void CScriptedIconLesson::InitPrerequisites() +{ + if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() ) + { + ConColorMsg( CBaseLesson::m_rgbaVerboseHeader, "GAME INSTRUCTOR: " ); + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "Initializing prereqs for scripted lesson " ); + ConColorMsg( CBaseLesson::m_rgbaVerboseOpen, "\"%s\"", GetName() ); + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "...\n" ); + } + + for ( int iPrerequisit = 0; iPrerequisit < m_PrerequisiteNames.Count(); ++iPrerequisit ) + { + const char *pPrerequisiteName = m_PrerequisiteNames[ iPrerequisit ].String(); + AddPrerequisite( pPrerequisiteName ); + } +} + +void CScriptedIconLesson::OnOpen() +{ + VPROF_BUDGET( "CScriptedIconLesson::OnOpen", "GameInstructor" ); + + if ( !DoDelayedPlayerSwaps() ) + { + return; + } + + const CScriptedIconLesson *pLesson = static_cast( GetRoot() ); + + // Process all update events + for ( int iLessonEvent = 0; iLessonEvent < pLesson->m_OnOpenEvents.Count(); ++iLessonEvent ) + { + const LessonEvent_t *pLessonEvent = &(pLesson->m_OnOpenEvents[ iLessonEvent ]); + + if ( gameinstructor_verbose.GetInt() > 1 && ShouldShowSpew() ) + { + ConColorMsg( Color( 255, 128, 64, 255 ), "GAME INSTRUCTOR: " ); + ConColorMsg( Color( 64, 128, 255, 255 ), "OnOpen event " ); + ConColorMsg( Color( 0, 255, 0, 255 ), "\"%s\"", pLessonEvent->szEventName.String()); + ConColorMsg( Color( 64, 128, 255, 255 ), "received for lesson \"%s\"...\n", GetName() ); + } + + ProcessElements( NULL, &(pLessonEvent->elements) ); + } + + BaseClass::OnOpen(); +} + +void CScriptedIconLesson::Update() +{ + VPROF_BUDGET( "CScriptedIconLesson::Update", "GameInstructor" ); + + if ( !DoDelayedPlayerSwaps() ) + { + return; + } + + const CScriptedIconLesson *pLesson = static_cast( GetRoot() ); + + if ( gpGlobals->curtime >= m_fUpdateEventTime ) + { + bool bShowSpew = ( gameinstructor_verbose.GetInt() > 1 && ShouldShowSpew() ); + + int iVerbose = gameinstructor_verbose.GetInt(); + if ( gameinstructor_verbose.GetInt() == 1 ) + { + // Force the verbose level from 1 to 0 for update events + gameinstructor_verbose.SetValue( 0 ); + } + + // Process all update events + for ( int iLessonEvent = 0; iLessonEvent < pLesson->m_UpdateEvents.Count(); ++iLessonEvent ) + { + const LessonEvent_t *pLessonEvent = &(pLesson->m_UpdateEvents[ iLessonEvent ]); + + if ( bShowSpew ) + { + ConColorMsg( CBaseLesson::m_rgbaVerboseHeader, "GAME INSTRUCTOR: " ); + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "Update event " ); + ConColorMsg( CBaseLesson::m_rgbaVerboseUpdate, "\"%s\"", pLessonEvent->szEventName.String()); + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "received for lesson \"%s\"...\n", GetName() ); + } + + ProcessElements( NULL, &(pLessonEvent->elements) ); + } + + gameinstructor_verbose.SetValue( iVerbose ); + + // Wait before doing update events again + m_fUpdateEventTime = gpGlobals->curtime + m_fUpdateInterval; + } + + BaseClass::Update(); +} + +void CScriptedIconLesson::SwapOutPlayers( int iOldUserID, int iNewUserID ) +{ + BaseClass::SwapOutPlayers( iOldUserID, iNewUserID ); + + // Get the player pointers from the user IDs + C_BasePlayer *pOldPlayer = UTIL_PlayerByUserId( iOldUserID ); + C_BasePlayer *pNewPlayer = UTIL_PlayerByUserId( iNewUserID ); + + if ( pOldPlayer == m_hEntity1.Get() ) + { + if ( pNewPlayer ) + { + m_hEntity1 = pNewPlayer; + } + else + { + if ( m_iNumDelayedPlayerSwaps < MAX_DELAYED_PLAYER_SWAPS ) + { + m_pDelayedPlayerSwap[ m_iNumDelayedPlayerSwaps ].phHandleToChange = &m_hEntity1; + m_pDelayedPlayerSwap[ m_iNumDelayedPlayerSwaps ].iNewUserID = iNewUserID; + ++m_iNumDelayedPlayerSwaps; + } + } + } + + if ( pOldPlayer == m_hEntity2.Get() ) + { + if ( pNewPlayer ) + { + m_hEntity2 = pNewPlayer; + } + else + { + + if ( m_iNumDelayedPlayerSwaps < MAX_DELAYED_PLAYER_SWAPS ) + { + m_pDelayedPlayerSwap[ m_iNumDelayedPlayerSwaps ].phHandleToChange = &m_hEntity2; + m_pDelayedPlayerSwap[ m_iNumDelayedPlayerSwaps ].iNewUserID = iNewUserID; + ++m_iNumDelayedPlayerSwaps; + } + } + } +} + +void CScriptedIconLesson::FireGameEvent( IGameEvent *event ) +{ + VPROF_BUDGET( "CScriptedIconLesson::FireGameEvent", "GameInstructor" ); + + if ( m_bDisabled ) + return; + + if ( !DoDelayedPlayerSwaps() ) + { + return; + } + + if ( !C_BasePlayer::GetLocalPlayer() ) + return; + + // Check that this lesson is allowed for the current input device + if( m_bOnlyKeyboard /*&& input->ControllerModeActive()*/ ) + return; + + if( m_bOnlyGamepad /*&& !input->ControllerModeActive()*/ ) + return; + + // Check that this lesson is for the proper team + CBasePlayer *pLocalPlayer = GetGameInstructor().GetLocalPlayer(); + + if ( m_iTeam != TEAM_ANY && pLocalPlayer && pLocalPlayer->GetTeamNumber() != m_iTeam ) + { + // This lesson is intended for a different team + return; + } + + const char *name = event->GetName(); + + // Open events run on the root + ProcessOpenGameEvents( this, name, event ); + + // Close and success events run on the children + const CUtlVector < CBaseLesson * > *pChildren = GetChildren(); + for ( int iChild = 0; iChild < pChildren->Count(); ++iChild ) + { + CScriptedIconLesson *pScriptedChild = dynamic_cast( (*pChildren)[ iChild ] ); + + pScriptedChild->ProcessCloseGameEvents( this, name, event ); + pScriptedChild->ProcessSuccessGameEvents( this, name, event ); + } +} + +void CScriptedIconLesson::ProcessOpenGameEvents( const CScriptedIconLesson *pRootLesson, const char *name, IGameEvent *event ) +{ + if ( pRootLesson->InstanceType() == LESSON_INSTANCE_SINGLE_OPEN && GetGameInstructor().IsLessonOfSameTypeOpen( this ) ) + { + // We don't want more than one of this type, and there is already one open + if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() ) + { + ConColorMsg( CBaseLesson::m_rgbaVerboseHeader, "GAME INSTRUCTOR: " ); + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "Opportunity " ); + ConColorMsg( CBaseLesson::m_rgbaVerboseClose, "\"%s\" ", pRootLesson->GetName() ); + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "open events NOT processed (there is already an open lesson of this type).\n" ); + } + + return; + } + + for ( int iLessonEvent = 0; iLessonEvent < pRootLesson->m_OpenEvents.Count(); ++iLessonEvent ) + { + const LessonEvent_t *pLessonEvent = &(pRootLesson->m_OpenEvents[ iLessonEvent ]); + + if ( Q_strcmp( name, pLessonEvent->szEventName.String()) == 0 ) + { + if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() ) + { + ConColorMsg( CBaseLesson::m_rgbaVerboseHeader, "GAME INSTRUCTOR: " ); + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "Open event " ); + ConColorMsg( CBaseLesson::m_rgbaVerboseOpen, "\"%s\"", pLessonEvent->szEventName.String()); + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "received for lesson \"%s\"...\n", GetName() ); + } + + if ( m_pDefaultHolder ) + { + // Run copy from default macros on all scriptable variables (see: LESSON_VARIABLE_FACTORY definition) +#define LESSON_VARIABLE_MACRO LESSON_VARIABLE_DEFAULT +#define LESSON_VARIABLE_MACRO_BOOL LESSON_VARIABLE_DEFAULT +#define LESSON_VARIABLE_MACRO_EHANDLE LESSON_VARIABLE_DEFAULT +#define LESSON_VARIABLE_MACRO_STRING LESSON_VARIABLE_DEFAULT + LESSON_VARIABLE_FACTORY +#undef LESSON_VARIABLE_MACRO +#undef LESSON_VARIABLE_MACRO_BOOL +#undef LESSON_VARIABLE_MACRO_EHANDLE +#undef LESSON_VARIABLE_MACRO_STRING + } + + if ( ProcessElements( event, &(pLessonEvent->elements) ) ) + { + if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() ) + { + ConColorMsg( CBaseLesson::m_rgbaVerboseOpen, "\tAll elements returned true. Opening!\n" ); + } + + MEM_ALLOC_CREDIT(); + CScriptedIconLesson *pOpenLesson = new CScriptedIconLesson( GetName(), false, true ); + + // Run copy macros on all scriptable variables (see: LESSON_VARIABLE_FACTORY definition) +#define LESSON_VARIABLE_MACRO LESSON_VARIABLE_COPY +#define LESSON_VARIABLE_MACRO_BOOL LESSON_VARIABLE_COPY +#define LESSON_VARIABLE_MACRO_EHANDLE LESSON_VARIABLE_COPY +#define LESSON_VARIABLE_MACRO_STRING LESSON_VARIABLE_COPY + LESSON_VARIABLE_FACTORY +#undef LESSON_VARIABLE_MACRO +#undef LESSON_VARIABLE_MACRO_BOOL +#undef LESSON_VARIABLE_MACRO_EHANDLE +#undef LESSON_VARIABLE_MACRO_STRING + + if ( GetGameInstructor().OpenOpportunity( pOpenLesson ) ) + { + pOpenLesson->OnOpen(); + + if ( pRootLesson->InstanceType() == LESSON_INSTANCE_SINGLE_OPEN ) + { + // This one is open and we only want one! So, we're done. + // Other open events may be listening for the same events... skip them! + return; + } + } + } + } + } +} + +void CScriptedIconLesson::ProcessCloseGameEvents( const CScriptedIconLesson *pRootLesson, const char *name, IGameEvent *event ) +{ + for ( int iLessonEvent = 0; iLessonEvent < pRootLesson->m_CloseEvents.Count() && IsOpenOpportunity(); ++iLessonEvent ) + { + const LessonEvent_t *pLessonEvent = &(pRootLesson->m_CloseEvents[ iLessonEvent ]); + + if ( Q_strcmp( name, pLessonEvent->szEventName.String()) == 0 ) + { + if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() ) + { + ConColorMsg( CBaseLesson::m_rgbaVerboseHeader, "GAME INSTRUCTOR: " ); + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "Close event " ); + ConColorMsg( CBaseLesson::m_rgbaVerboseClose, "\"%s\"", pLessonEvent->szEventName.String()); + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "received for lesson \"%s\"...\n", GetName() ); + } + + if ( ProcessElements( event, &(pLessonEvent->elements) ) ) + { + if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() ) + { + ConColorMsg( CBaseLesson::m_rgbaVerboseClose, "\tAll elements returned true. Closing!\n" ); + } + + CloseOpportunity( "Close event elements completed." ); + } + } + } +} + +void CScriptedIconLesson::ProcessSuccessGameEvents( const CScriptedIconLesson *pRootLesson, const char *name, IGameEvent *event ) +{ + for ( int iLessonEvent = 0; iLessonEvent < pRootLesson->m_SuccessEvents.Count(); ++iLessonEvent ) + { + const LessonEvent_t *pLessonEvent = &(pRootLesson->m_SuccessEvents[ iLessonEvent ]); + + if ( Q_strcmp( name, pLessonEvent->szEventName.String()) == 0 ) + { + if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() ) + { + ConColorMsg( CBaseLesson::m_rgbaVerboseHeader, "GAME INSTRUCTOR: " ); + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "Success event " ); + ConColorMsg( CBaseLesson::m_rgbaVerboseSuccess, "\"%s\"", pLessonEvent->szEventName.String()); + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "received for lesson \"%s\"...\n", GetName() ); + } + + if ( ProcessElements( event, &(pLessonEvent->elements) ) ) + { + if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() ) + { + ConColorMsg( CBaseLesson::m_rgbaVerboseSuccess, "\tAll elements returned true. Succeeding!\n" ); + } + + MarkSucceeded(); + } + } + } +} + +LessonVariable LessonVariableFromString( const char *pchName, bool bWarnOnInvalidNames ) +{ + int slot = g_NameToTypeMap.Find( pchName ); + if ( slot != g_NameToTypeMap.InvalidIndex() ) + return g_NameToTypeMap[ slot ]; + + if ( bWarnOnInvalidNames ) + { + AssertMsg( 0, "Invalid scripted lesson variable!" ); + DevWarning( "Invalid scripted lesson variable: %s\n", pchName ); + } + + return LESSON_VARIABLE_TOTAL; +} + +_fieldtypes LessonParamTypeFromString( const char *pchName ) +{ + int slot = g_TypeToParamTypeMap.Find( pchName ); + if ( slot != g_TypeToParamTypeMap.InvalidIndex() ) + return g_TypeToParamTypeMap[ slot ]; + + DevWarning( "Invalid scripted lesson variable/param type: %s\n", pchName ); + return FIELD_VOID; +} + +int LessonActionFromString( const char *pchName ) +{ + int slot = CScriptedIconLesson::LessonActionMap.Find( pchName ); + if ( slot != CScriptedIconLesson::LessonActionMap.InvalidIndex() ) + return CScriptedIconLesson::LessonActionMap[ slot ]; + + DevWarning( "Invalid scripted lesson action: %s\n", pchName ); + return LESSON_ACTION_NONE; +} + +void CScriptedIconLesson::InitElementsFromKeys( CUtlVector< LessonElement_t > *pLessonElements, KeyValues *pKey ) +{ + KeyValues *pSubKey = NULL; + for ( pSubKey = pKey->GetFirstSubKey(); pSubKey; pSubKey = pSubKey->GetNextKey() ) + { + char szSubKeyName[ 256 ]; + Q_strcpy( szSubKeyName, pSubKey->GetName() ); + + char *pchToken = strtok( szSubKeyName, " " ); + LessonVariable iVariable = LessonVariableFromString( pchToken ); + + pchToken = strtok ( NULL, "" ); + int iAction = LESSON_ACTION_NONE; + bool bNot = false; + bool bOptionalParam = false; + + if ( !pchToken || pchToken[ 0 ] == '\0' ) + { + DevWarning( "No action specified for variable: \"%s\"\n", pSubKey->GetName() ); + } + else + { + if ( pchToken[ 0 ] == '?' ) + { + pchToken++; + bOptionalParam = true; + } + + if ( pchToken[ 0 ] == '!' ) + { + pchToken++; + bNot = true; + } + + iAction = LessonActionFromString( pchToken ); + } + + Q_strcpy( szSubKeyName, pSubKey->GetString() ); + + pchToken = strtok( szSubKeyName, " " ); + _fieldtypes paramType = LessonParamTypeFromString( pchToken ); + + char *pchParam = ""; + + if ( paramType != FIELD_VOID ) + { + pchToken = strtok ( NULL, "" ); + pchParam = pchToken; + } + + if ( !pchParam ) + { + DevWarning( "No parameter specified for action: \"%s\"\n", pSubKey->GetName() ); + } + else + { + if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() ) + { + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\t\tElement \"%s %s\" added.\n", pSubKey->GetName(), pSubKey->GetString() ); + } + + // See if our param is a scripted var + LessonVariable iParamVarIndex = LessonVariableFromString( pchParam, false ); + + pLessonElements->AddToTail( LessonElement_t( iVariable, iAction, bNot, bOptionalParam, pchParam, iParamVarIndex, paramType ) ); + } + } +} + +void CScriptedIconLesson::InitElementsFromElements( CUtlVector< LessonElement_t > *pLessonElements, const CUtlVector< LessonElement_t > *pLessonElements2 ) +{ + for ( int i = 0; i < pLessonElements2->Count(); ++i ) + { + pLessonElements->AddToTail( LessonElement_t( (*pLessonElements2)[ i ] ) ); + } +} + +void CScriptedIconLesson::InitFromKeys( KeyValues *pKey ) +{ + if ( !pKey ) + return; + + static int s_nInstanceTypeSymbol = KeyValuesSystem()->GetSymbolForString( "instance_type" ); + static int s_nReplaceKeySymbol = KeyValuesSystem()->GetSymbolForString( "replace_key" ); + static int s_nFixedInstancesMaxSymbol = KeyValuesSystem()->GetSymbolForString( "fixed_instances_max" ); + static int s_nReplaceOnlyWhenStopped = KeyValuesSystem()->GetSymbolForString( "replace_only_when_stopped" ); + static int s_nTeamSymbol = KeyValuesSystem()->GetSymbolForString( "team" ); + static int s_nOnlyKeyboardSymbol = KeyValuesSystem()->GetSymbolForString( "only_keyboard" ); + static int s_nOnlyGamepadSymbol = KeyValuesSystem()->GetSymbolForString( "only_gamepad" ); + static int s_nDisplayLimitSymbol = KeyValuesSystem()->GetSymbolForString( "display_limit" ); + static int s_nSuccessLimitSymbol = KeyValuesSystem()->GetSymbolForString( "success_limit" ); + static int s_nPreReqSymbol = KeyValuesSystem()->GetSymbolForString( "prereq" ); + static int s_nOpenSymbol = KeyValuesSystem()->GetSymbolForString( "open" ); + static int s_nCloseSymbol = KeyValuesSystem()->GetSymbolForString( "close" ); + static int s_nSuccessSymbol = KeyValuesSystem()->GetSymbolForString( "success" ); + static int s_nOnOpenSymbol = KeyValuesSystem()->GetSymbolForString( "onopen" ); + static int s_nUpdateSymbol = KeyValuesSystem()->GetSymbolForString( "update" ); + + KeyValues *pSubKey = NULL; + for ( pSubKey = pKey->GetFirstSubKey(); pSubKey; pSubKey = pSubKey->GetNextKey() ) + { + if ( pSubKey->GetNameSymbol() == s_nInstanceTypeSymbol ) + { + m_iInstanceType = LessonInstanceType( pSubKey->GetInt() ); + } + else if ( pSubKey->GetNameSymbol() == s_nReplaceKeySymbol ) + { + m_stringReplaceKey = pSubKey->GetString(); + } + else if ( pSubKey->GetNameSymbol() == s_nFixedInstancesMaxSymbol ) + { + m_iFixedInstancesMax = pSubKey->GetInt(); + } + else if ( pSubKey->GetNameSymbol() == s_nReplaceOnlyWhenStopped ) + { + m_bReplaceOnlyWhenStopped = pSubKey->GetBool(); + } + else if ( pSubKey->GetNameSymbol() == s_nTeamSymbol ) + { + m_iTeam = pSubKey->GetInt(); + } + else if ( pSubKey->GetNameSymbol() == s_nOnlyKeyboardSymbol ) + { + m_bOnlyKeyboard = pSubKey->GetBool(); + } + else if ( pSubKey->GetNameSymbol() == s_nOnlyGamepadSymbol ) + { + m_bOnlyGamepad = pSubKey->GetBool(); + } + else if ( pSubKey->GetNameSymbol() == s_nDisplayLimitSymbol ) + { + m_iDisplayLimit = pSubKey->GetInt(); + } + else if ( pSubKey->GetNameSymbol() == s_nSuccessLimitSymbol ) + { + m_iSuccessLimit = pSubKey->GetInt(); + } + else if ( pSubKey->GetNameSymbol() == s_nPreReqSymbol ) + { + CGameInstructorSymbol pName; + pName = pSubKey->GetString(); + m_PrerequisiteNames.AddToTail( pName ); + } + else if ( pSubKey->GetNameSymbol() == s_nOpenSymbol ) + { + KeyValues *pEventKey = NULL; + for ( pEventKey = pSubKey->GetFirstTrueSubKey(); pEventKey; pEventKey = pEventKey->GetNextTrueSubKey() ) + { + LessonEvent_t *pLessonEvent = AddOpenEvent(); + pLessonEvent->szEventName = pEventKey->GetName(); + + if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() ) + { + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\tAdding open event " ); + ConColorMsg( CBaseLesson::m_rgbaVerboseOpen, "\"%s\" ", pLessonEvent->szEventName.String()); + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "...\n" ); + } + + InitElementsFromKeys( &(pLessonEvent->elements), pEventKey ); + } + } + else if ( pSubKey->GetNameSymbol() == s_nCloseSymbol ) + { + KeyValues *pEventKey = NULL; + for ( pEventKey = pSubKey->GetFirstTrueSubKey(); pEventKey; pEventKey = pEventKey->GetNextTrueSubKey() ) + { + LessonEvent_t *pLessonEvent = AddCloseEvent(); + pLessonEvent->szEventName = pEventKey->GetName(); + + if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() ) + { + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\tAdding close event " ); + ConColorMsg( CBaseLesson::m_rgbaVerboseClose, "\"%s\" ", pLessonEvent->szEventName.String()); + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "...\n" ); + } + + InitElementsFromKeys( &(pLessonEvent->elements), pEventKey ); + } + } + else if ( pSubKey->GetNameSymbol() == s_nSuccessSymbol ) + { + KeyValues *pEventKey = NULL; + for ( pEventKey = pSubKey->GetFirstTrueSubKey(); pEventKey; pEventKey = pEventKey->GetNextTrueSubKey() ) + { + LessonEvent_t *pLessonEvent = AddSuccessEvent(); + pLessonEvent->szEventName = pEventKey->GetName(); + + if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() ) + { + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\tAdding success event " ); + ConColorMsg( CBaseLesson::m_rgbaVerboseSuccess, "\"%s\" ", pLessonEvent->szEventName.String()); + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "...\n" ); + } + + InitElementsFromKeys( &(pLessonEvent->elements), pEventKey ); + } + } + else if ( pSubKey->GetNameSymbol() == s_nOnOpenSymbol ) + { + KeyValues *pEventKey = NULL; + for ( pEventKey = pSubKey->GetFirstTrueSubKey(); pEventKey; pEventKey = pEventKey->GetNextTrueSubKey() ) + { + LessonEvent_t *pLessonEvent = AddOnOpenEvent(); + pLessonEvent->szEventName = pEventKey->GetName(); + + if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() ) + { + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\tAdding onopen event " ); + ConColorMsg( CBaseLesson::m_rgbaVerboseOpen, "\"%s\" ", pLessonEvent->szEventName.String()); + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "...\n" ); + } + + InitElementsFromKeys( &(pLessonEvent->elements), pEventKey ); + } + } + else if ( pSubKey->GetNameSymbol() == s_nUpdateSymbol ) + { + KeyValues *pEventKey = NULL; + for ( pEventKey = pSubKey->GetFirstTrueSubKey(); pEventKey; pEventKey = pEventKey->GetNextTrueSubKey() ) + { + LessonEvent_t *pLessonEvent = AddUpdateEvent(); + pLessonEvent->szEventName = pEventKey->GetName(); + + if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() ) + { + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\tAdding update event " ); + ConColorMsg( CBaseLesson::m_rgbaVerboseUpdate, "\"%s\" ", pLessonEvent->szEventName.String()); + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "...\n" ); + } + + InitElementsFromKeys( &(pLessonEvent->elements), pEventKey ); + } + } + + // Run intialize from key macros on all scriptable variables (see: LESSON_VARIABLE_FACTORY definition) +#define LESSON_VARIABLE_MACRO LESSON_VARIABLE_INIT +#define LESSON_VARIABLE_MACRO_BOOL LESSON_VARIABLE_INIT_BOOL +#define LESSON_VARIABLE_MACRO_EHANDLE LESSON_VARIABLE_INIT_EHANDLE +#define LESSON_VARIABLE_MACRO_STRING LESSON_VARIABLE_INIT_STRING + LESSON_VARIABLE_FACTORY +#undef LESSON_VARIABLE_MACRO +#undef LESSON_VARIABLE_MACRO_BOOL +#undef LESSON_VARIABLE_MACRO_EHANDLE +#undef LESSON_VARIABLE_MACRO_STRING + } +} + +bool CScriptedIconLesson::ProcessElements( IGameEvent *event, const CUtlVector< LessonElement_t > *pElements ) +{ + VPROF_BUDGET( "CScriptedIconLesson::ProcessElements", "GameInstructor" ); + + m_hLocalPlayer = GetGameInstructor().GetLocalPlayer(); + + bool bSuccess = true; + int nContinueScope = -1; + m_iScopeDepth = 0; + + if ( gameinstructor_find_errors.GetBool() ) + { + // Just run them all to check for errors! + for ( int iElement = 0; iElement < pElements->Count(); ++iElement ) + { + ProcessElement( event, &((*pElements)[ iElement ] ), false ); + } + + return false; + } + + // Process each element until a step fails + for ( int iElement = 0; iElement < pElements->Count(); ++iElement ) + { + if ( nContinueScope == m_iScopeDepth ) + { + nContinueScope = -1; + } + + if ( !ProcessElement( event, &((*pElements)[ iElement ]), nContinueScope != -1 ) ) + { + // This element failed + if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() ) + { + ConColorMsg( CBaseLesson::m_rgbaVerboseClose, "\tPrevious element returned false.\n" ); + } + + nContinueScope = m_iScopeDepth - 1; + + if ( nContinueScope < 0 ) + { + // No outer scope to worry about, we're done + bSuccess = false; + break; + } + } + } + + return bSuccess; +} + +bool CScriptedIconLesson::ProcessElement( IGameEvent *event, const LessonElement_t *pLessonElement, bool bInFailedScope ) +{ + VPROF_BUDGET( "CScriptedIconLesson::ProcessElement", "GameInstructor" ); + + if ( pLessonElement->iAction == LESSON_ACTION_SCOPE_IN ) + { + // Special case for closing (we don't need variables for this) + if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() ) + { + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\tScopeIn()\n" ); + } + + m_iScopeDepth++; + return true; + } + else if ( pLessonElement->iAction == LESSON_ACTION_SCOPE_OUT ) + { + // Special case for closing (we don't need variables for this) + if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() ) + { + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\tScopeOut()\n" ); + } + + m_iScopeDepth--; + return true; + } + + if ( bInFailedScope ) + { + // Only scope upkeep is done when we're in a failing scope... bail! + return true; + } + + if ( pLessonElement->iAction == LESSON_ACTION_CLOSE ) + { + // Special case for closing (we don't need variables for this) + if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() ) + { + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\tCloseOpportunity()\n" ); + } + + CloseOpportunity( "Close action." ); + return true; + } + else if ( pLessonElement->iAction == LESSON_ACTION_SUCCESS ) + { + // Special case for succeeding (we don't need variables for this) + if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() ) + { + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\tMarkSucceeded()\n" ); + } + + MarkSucceeded(); + return true; + } + else if ( pLessonElement->iAction == LESSON_ACTION_LOCK ) + { + // Special case for setting the starting point for the lesson to stay locked from (we don't need variables for this) + if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() ) + { + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\tm_fLockTime = gpGlobals->curtime\n" ); + } + + m_fLockTime = gpGlobals->curtime; + return true; + } + else if ( pLessonElement->iAction == LESSON_ACTION_PRESENT_COMPLETE ) + { + // Special case for checking presentation status (we don't need variables for this) + bool bPresentComplete = IsPresentComplete(); + + if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() ) + { + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\tIsPresentComplete() " ); + ConColorMsg( CBaseLesson::m_rgbaVerboseName, "%s ", ( bPresentComplete ) ? ( "true" ) : ( "false" ) ); + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ( pLessonElement->bNot ) ? ( "!= true\n" ) : ( "== true\n" ) ); + } + + return ( pLessonElement->bNot ) ? ( !bPresentComplete ) : ( bPresentComplete ); + } + else if ( pLessonElement->iAction == LESSON_ACTION_PRESENT_START ) + { + // Special case for setting presentation status (we don't need variables for this) + if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() ) + { + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\tPresentStart()\n" ); + } + + PresentStart(); + return true; + } + else if ( pLessonElement->iAction == LESSON_ACTION_PRESENT_END ) + { + // Special case for setting presentation status (we don't need variables for this) + if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() ) + { + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\tPresentEnd()\n" ); + } + + PresentEnd(); + return true; + } + + // These values temporarily hold the parameter's value + const char *pParamName = pLessonElement->szParam.String(); + float eventParam_float = 0.0f; + char eventParam_string[ 256 ]; + eventParam_string[ 0 ] = '\0'; + C_BaseEntity *eventParam_BaseEntity = NULL; + + // Get the value from the event parameter based on its type + switch ( pLessonElement->paramType ) + { + case FIELD_FLOAT: + if ( pLessonElement->iParamVarIndex < LESSON_VARIABLE_TOTAL ) + { + // The parameter is a scripted var + const LessonVariableInfo *pInfo = GetLessonVariableInfo( pLessonElement->iParamVarIndex ); + + switch ( pInfo->varType ) + { + case FIELD_FLOAT: + eventParam_float = LESSON_VARIABLE_GET_FROM_OFFSET( float, pInfo->iOffset ); + break; + case FIELD_INTEGER: + eventParam_float = static_cast( LESSON_VARIABLE_GET_FROM_OFFSET( int, pInfo->iOffset ) ); + break; + case FIELD_BOOLEAN: + eventParam_float = static_cast( LESSON_VARIABLE_GET_FROM_OFFSET( bool, pInfo->iOffset ) ); + break; + case FIELD_STRING: + eventParam_float = static_cast( atoi( &LESSON_VARIABLE_GET_FROM_OFFSET( CGameInstructorSymbol, pInfo->iOffset )->String() ) ); + break; + case FIELD_EHANDLE: + case FIELD_FUNCTION: + DevWarning( "Can't use this variable type with this parameter type in lesson script.\n" ); + break; + } + } + else if ( event && !(event->IsEmpty( pParamName )) ) + { + eventParam_float = event->GetFloat( pParamName ); + } + else if ( pLessonElement->bOptionalParam ) + { + // We don't want to interpret this and not finding the param is still ok + return true; + } + else if ( ( pParamName[ 0 ] >= '0' && pParamName[ 0 ] <= '9' ) || pParamName[ 0 ] == '-' || pParamName[ 0 ] == '.' ) + { + // This param doesn't exist, try parsing the string + eventParam_float = Q_atof( pParamName ); + } + else + { + DevWarning( "Invalid event field name and not a float \"%s\".\n", pParamName ); + return false; + } + break; + + case FIELD_INTEGER: + if ( pLessonElement->iParamVarIndex < LESSON_VARIABLE_TOTAL ) + { + // The parameter is a scripted var + const LessonVariableInfo *pInfo = GetLessonVariableInfo( pLessonElement->iParamVarIndex ); + + switch ( pInfo->varType ) + { + case FIELD_FLOAT: + eventParam_float = static_cast( LESSON_VARIABLE_GET_FROM_OFFSET( float, pInfo->iOffset ) ); + break; + case FIELD_INTEGER: + eventParam_float = LESSON_VARIABLE_GET_FROM_OFFSET( int, pInfo->iOffset ); + break; + case FIELD_BOOLEAN: + eventParam_float = static_cast( LESSON_VARIABLE_GET_FROM_OFFSET( bool, pInfo->iOffset ) ); + break; + case FIELD_STRING: + eventParam_float = atof( &LESSON_VARIABLE_GET_FROM_OFFSET( CGameInstructorSymbol, pInfo->iOffset )->String() ); + break; + case FIELD_EHANDLE: + case FIELD_FUNCTION: + DevWarning( "Can't use this variable type with this parameter type in lesson script.\n" ); + break; + } + } + else if ( event && !(event->IsEmpty( pParamName )) ) + { + eventParam_float = static_cast( event->GetInt( pParamName ) ); + } + else if ( pLessonElement->bOptionalParam ) + { + // We don't want to interpret this and not finding the param is still ok + return true; + } + else if ( ( pParamName[ 0 ] >= '0' && pParamName[ 0 ] <= '9' ) || pParamName[ 0 ] == '-' ) + { + // This param doesn't exist, try parsing the string + eventParam_float = static_cast( Q_atoi( pParamName ) ); + } + else + { + DevWarning( "Invalid event field name and not an integer \"%s\".\n", pParamName ); + return false; + } + break; + + case FIELD_STRING: + if ( pLessonElement->iParamVarIndex < LESSON_VARIABLE_TOTAL ) + { + // The parameter is a scripted var + const LessonVariableInfo *pInfo = GetLessonVariableInfo( pLessonElement->iParamVarIndex ); + + switch ( pInfo->varType ) + { + case FIELD_STRING: + Q_strncpy( eventParam_string, &LESSON_VARIABLE_GET_FROM_OFFSET( CGameInstructorSymbol, pInfo->iOffset )->String(), sizeof( eventParam_string ) ); + break; + case FIELD_FLOAT: + Q_snprintf( eventParam_string, sizeof( eventParam_string ), "%f", LESSON_VARIABLE_GET_FROM_OFFSET( float, pInfo->iOffset ) ); + break; + case FIELD_INTEGER: + Q_snprintf( eventParam_string, sizeof( eventParam_string ), "%i", LESSON_VARIABLE_GET_FROM_OFFSET( int, pInfo->iOffset ) ); + break; + case FIELD_BOOLEAN: + case FIELD_EHANDLE: + case FIELD_FUNCTION: + DevWarning( "Can't use this variable type with this parameter type in lesson script.\n" ); + break; + } + } + else + { + const char *pchEventString = NULL; + + if ( event && !(event->IsEmpty( pParamName )) ) + { + pchEventString = event->GetString( pParamName ); + } + + if ( pchEventString && pchEventString[0] ) + { + Q_strcpy( eventParam_string, pchEventString ); + } + else if ( pLessonElement->bOptionalParam ) + { + // We don't want to interpret this and not finding the param is still ok + return true; + } + else + { + // This param doesn't exist, try parsing the string + Q_strncpy( eventParam_string, pParamName, sizeof( eventParam_string ) ); + } + } + break; + + case FIELD_BOOLEAN: + if ( pLessonElement->iParamVarIndex < LESSON_VARIABLE_TOTAL ) + { + // The parameter is a scripted var + const LessonVariableInfo *pInfo = GetLessonVariableInfo( pLessonElement->iParamVarIndex ); + + switch ( pInfo->varType ) + { + case FIELD_FLOAT: + eventParam_float = ( ( LESSON_VARIABLE_GET_FROM_OFFSET( float, pInfo->iOffset ) ) ? ( 1.0f ) : ( 0.0f ) ); + break; + case FIELD_INTEGER: + eventParam_float = ( ( LESSON_VARIABLE_GET_FROM_OFFSET( int, pInfo->iOffset ) ) ? ( 1.0f ) : ( 0.0f ) ); + break; + case FIELD_BOOLEAN: + eventParam_float = ( ( LESSON_VARIABLE_GET_FROM_OFFSET( bool, pInfo->iOffset ) ) ? ( 1.0f ) : ( 0.0f ) ); + break; + case FIELD_EHANDLE: + case FIELD_STRING: + case FIELD_FUNCTION: + DevWarning( "Can't use this variable type with this parameter type in lesson script.\n" ); + break; + } + } + else if ( event && !(event->IsEmpty( pParamName )) ) + { + eventParam_float = ( event->GetBool( pParamName ) ) ? ( 1.0f ) : ( 0.0f ); + } + else if ( pLessonElement->bOptionalParam ) + { + // We don't want to interpret this and not finding the param is still ok + return true; + } + else if ( pParamName[ 0 ] == '0' || pParamName[ 0 ] == '1' ) + { + // This param doesn't exist, try parsing the string + eventParam_float = Q_atof( pParamName ) != 0.0f; + } + else + { + DevWarning( "Invalid event field name and not an boolean \"%s\".\n", pParamName ); + return false; + } + break; + + case FIELD_CUSTOM: + if ( pLessonElement->iParamVarIndex < LESSON_VARIABLE_TOTAL ) + { + // The parameter is a scripted var + const LessonVariableInfo *pInfo = GetLessonVariableInfo( pLessonElement->iParamVarIndex ); + + switch ( pInfo->varType ) + { + case FIELD_EHANDLE: + eventParam_BaseEntity = ( LESSON_VARIABLE_GET_FROM_OFFSET( EHANDLE, pInfo->iOffset ) ).Get(); + if ( !eventParam_BaseEntity ) + { + if ( pLessonElement->bOptionalParam ) + { + // Not having an entity is fine + return true; + } + + if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() ) + { + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\tPlayer param \"%s\" returned NULL.\n", pParamName ); + } + return false; + } + break; + case FIELD_FLOAT: + case FIELD_INTEGER: + case FIELD_BOOLEAN: + case FIELD_STRING: + case FIELD_FUNCTION: + DevWarning( "Can't use this variable type with this parameter type in lesson script.\n" ); + break; + } + } + else if ( event && !(event->IsEmpty( pParamName )) ) + { + eventParam_BaseEntity = UTIL_PlayerByUserId( event->GetInt( pParamName ) ); + if ( !eventParam_BaseEntity ) + { + if ( pLessonElement->bOptionalParam ) + { + // Not having an entity is fine + return true; + } + + if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() ) + { + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\tPlayer param \"%s\" returned NULL.\n", pParamName ); + } + return false; + } + } + else if ( pLessonElement->bOptionalParam ) + { + // We don't want to interpret this and not finding the param is still ok + return true; + } + else if ( Q_stricmp( pParamName, "null" ) == 0 ) + { + // They explicitly want a null pointer + eventParam_BaseEntity = NULL; + } + else + { + DevWarning( "Invalid event field name \"%s\".\n", pParamName ); + return false; + } + break; + + case FIELD_EHANDLE: + if ( pLessonElement->iParamVarIndex < LESSON_VARIABLE_TOTAL ) + { + // The parameter is a scripted var + const LessonVariableInfo *pInfo = GetLessonVariableInfo( pLessonElement->iParamVarIndex ); + + switch ( pInfo->varType ) + { + case FIELD_EHANDLE: + eventParam_BaseEntity = ( LESSON_VARIABLE_GET_FROM_OFFSET( EHANDLE, pInfo->iOffset ) ).Get(); + if ( !eventParam_BaseEntity ) + { + if ( pLessonElement->bOptionalParam ) + { + // Not having an entity is fine + return true; + } + + if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() ) + { + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\tEntity param \"%s\" returned NULL.\n", pParamName ); + } + return false; + } + break; + case FIELD_FLOAT: + case FIELD_INTEGER: + case FIELD_BOOLEAN: + case FIELD_STRING: + case FIELD_FUNCTION: + DevWarning( "Can't use this variable type with this parameter type in lesson script.\n" ); + break; + } + } + else if ( event && !(event->IsEmpty( pParamName )) ) + { + int iEntID = event->GetInt( pParamName ); + if ( iEntID >= NUM_ENT_ENTRIES ) + { + AssertMsg( 0, "Invalid entity ID used in game event field!" ); + DevWarning( "Invalid entity ID used in game event (%s) for param (%s).", event->GetName(), pParamName ); + return false; + } + + eventParam_BaseEntity = C_BaseEntity::Instance( iEntID ); + if ( !eventParam_BaseEntity ) + { + if ( pLessonElement->bOptionalParam ) + { + // Not having an entity is fine + return true; + } + + if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() ) + { + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\tEntity param \"%s\" returned NULL.\n", pParamName ); + } + return false; + } + } + else if ( pLessonElement->bOptionalParam ) + { + // We don't want to interpret this and not finding the param is still ok + return true; + } + else if ( Q_stricmp( pParamName, "null" ) == 0 ) + { + // They explicitly want a null pointer + eventParam_BaseEntity = NULL; + } + else if ( Q_stricmp( pParamName, "world" ) == 0 ) + { + // They explicitly want the world + eventParam_BaseEntity = GetClientWorldEntity(); + } + else + { + DevWarning( "Invalid event field name \"%s\".\n", pParamName ); + return false; + } + break; + + case FIELD_EMBEDDED: + { + // The parameter is a convar + ConVarRef tempCVar( pParamName ); + if ( tempCVar.IsValid() ) + { + eventParam_float = tempCVar.GetFloat(); + Q_strncpy( eventParam_string, tempCVar.GetString(), sizeof( eventParam_string ) ); + } + else + { + DevWarning( "Invalid convar name \"%s\".\n", pParamName ); + return false; + } + } + break; + } + + // Do the action to the specified variable + switch ( pLessonElement->iVariable ) + { + // Run process action macros on all scriptable variables (see: LESSON_VARIABLE_FACTORY definition) +#define LESSON_VARIABLE_MACRO PROCESS_LESSON_ACTION +#define LESSON_VARIABLE_MACRO_BOOL PROCESS_LESSON_ACTION +#define LESSON_VARIABLE_MACRO_EHANDLE PROCESS_LESSON_ACTION_EHANDLE +#define LESSON_VARIABLE_MACRO_STRING PROCESS_LESSON_ACTION_STRING + LESSON_VARIABLE_FACTORY; +#undef LESSON_VARIABLE_MACRO +#undef LESSON_VARIABLE_MACRO_BOOL +#undef LESSON_VARIABLE_MACRO_EHANDLE +#undef LESSON_VARIABLE_MACRO_STRING + } + + return true; +} + +bool CScriptedIconLesson::ProcessElementAction( int iAction, bool bNot, const char *pchVarName, float &fVar, const CGameInstructorSymbol *pchParamName, float fParam ) +{ + switch ( iAction ) + { + case LESSON_ACTION_SET: + if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() ) + { + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\t[%s] = [%s] ", pchVarName, pchParamName->String() ); + ConColorMsg( CBaseLesson::m_rgbaVerboseName, "%f\n", fParam ); + } + + fVar = fParam; + return true; + + case LESSON_ACTION_ADD: + if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() ) + { + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\t[%s] += [%s] ", pchVarName, pchParamName->String() ); + ConColorMsg( CBaseLesson::m_rgbaVerboseName, "%f\n", fParam ); + } + + fVar += fParam; + return true; + + case LESSON_ACTION_SUBTRACT: + if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() ) + { + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\t[%s] -= [%s] ", pchVarName, pchParamName->String() ); + ConColorMsg( CBaseLesson::m_rgbaVerboseName, "%f\n", fParam ); + } + + fVar -= fParam; + return true; + + case LESSON_ACTION_MULTIPLY: + if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() ) + { + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\t[%s] *= [%s] ", pchVarName, pchParamName->String() ); + ConColorMsg( CBaseLesson::m_rgbaVerboseName, "%f\n", fParam ); + } + + fVar *= fParam; + return true; + + case LESSON_ACTION_IS: + if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() ) + { + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\t[%s] ", pchVarName ); + ConColorMsg( CBaseLesson::m_rgbaVerboseName, "%f ", fVar ); + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ( bNot ) ? ( "!= [%s] " ) : ( "== [%s] " ), pchParamName->String() ); + ConColorMsg( CBaseLesson::m_rgbaVerboseName, "%f\n", fParam ); + } + + return ( bNot ) ? ( fVar != fParam ) : ( fVar == fParam ); + + case LESSON_ACTION_LESS_THAN: + if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() ) + { + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\t[%s] ", pchVarName ); + ConColorMsg( CBaseLesson::m_rgbaVerboseName, "%f ", fVar ); + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ( bNot ) ? ( ">= [%s] " ) : ( "< [%s] " ), pchParamName->String() ); + ConColorMsg( CBaseLesson::m_rgbaVerboseName, "%f\n", fParam ); + } + + return ( bNot ) ? ( fVar >= fParam ) : ( fVar < fParam ); + + case LESSON_ACTION_HAS_BIT: + { + int iTemp1 = static_cast( fVar ); + int iTemp2 = ( 1 << static_cast( fParam ) ); + + if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() ) + { + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\t([%s] ", pchVarName ); + ConColorMsg( CBaseLesson::m_rgbaVerboseName, "0x%X ", iTemp1 ); + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "& [%s] ", pchParamName->String() ); + ConColorMsg( CBaseLesson::m_rgbaVerboseName, "0x%X", iTemp2 ); + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ( bNot ) ? ( ") == 0\n" ) : ( ") != 0\n" ) ); + } + + return ( bNot ) ? ( ( iTemp1 & iTemp2 ) == 0 ) : ( ( iTemp1 & iTemp2 ) != 0 ); + } + + case LESSON_ACTION_BIT_COUNT_IS: + { + int iTemp1 = UTIL_CountNumBitsSet( static_cast( fVar ) ); + int iTemp2 = static_cast( fParam ); + + if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() ) + { + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\tUTIL_CountNumBitsSet([%s]) ", pchVarName ); + ConColorMsg( CBaseLesson::m_rgbaVerboseName, "%i ", iTemp1 ); + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ( bNot ) ? ( " != [%s] " ) : ( " == [%s] " ), pchParamName->String() ); + ConColorMsg( CBaseLesson::m_rgbaVerboseName, "%i\n", iTemp2 ); + } + + return ( bNot ) ? ( iTemp1 != iTemp2 ) : ( iTemp1 == iTemp2 ); + } + + case LESSON_ACTION_BIT_COUNT_LESS_THAN: + { + int iTemp1 = UTIL_CountNumBitsSet( static_cast( fVar ) ); + int iTemp2 = static_cast( fParam ); + + if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() ) + { + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\tUTIL_CountNumBitsSet([%s]) ", pchVarName ); + ConColorMsg( CBaseLesson::m_rgbaVerboseName, "%i ", iTemp1 ); + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ( bNot ) ? ( " >= [%s] " ) : ( " < [%s] " ), pchParamName->String() ); + ConColorMsg( CBaseLesson::m_rgbaVerboseName, "%i\n", iTemp2 ); + } + + return ( bNot ) ? ( iTemp1 >= iTemp2 ) : ( iTemp1 < iTemp2 ); + } + } + + DevWarning( "Invalid lesson action type used with \"%s\" variable type.\n", pchVarName ); + + return false; +} + +bool CScriptedIconLesson::ProcessElementAction( int iAction, bool bNot, const char *pchVarName, int &iVar, const CGameInstructorSymbol *pchParamName, float fParam ) +{ + float fTemp = static_cast( iVar ); + bool bRetVal = ProcessElementAction( iAction, bNot, pchVarName, fTemp, pchParamName, fParam ); + + iVar = static_cast( fTemp ); + return bRetVal; +} + +bool CScriptedIconLesson::ProcessElementAction( int iAction, bool bNot, const char *pchVarName, bool &bVar, const CGameInstructorSymbol *pchParamName, float fParam ) +{ + float fTemp = ( bVar ) ? ( 1.0f ) : ( 0.0f ); + bool bRetVal = ProcessElementAction( iAction, bNot, pchVarName, fTemp, pchParamName, fParam ); + + bVar = ( fTemp != 0.0f ); + return bRetVal; +} + +bool CScriptedIconLesson::ProcessElementAction( int iAction, bool bNot, const char *pchVarName, EHANDLE &hVar, const CGameInstructorSymbol *pchParamName, float fParam, C_BaseEntity *pParam, const char *pchParam ) +{ + // First try to let the mod act on the action + /*bool bModHandled = false; + bool bModReturn = Mod_ProcessElementAction( iAction, bNot, pchVarName, hVar, pchParamName, fParam, pParam, pchParam, bModHandled ); + + if ( bModHandled ) + { + return bModReturn; + }*/ + + C_BaseEntity *pVar = hVar.Get(); + + switch ( iAction ) + { + case LESSON_ACTION_SET: + { + if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() ) + { + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\t[%s] = [%s]\n", pchVarName, pchParamName->String() ); + } + + hVar = pParam; + return true; + } + + case LESSON_ACTION_IS: + if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() ) + { + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ( bNot ) ? ( "\t[%s] != [%s]\n" ) : ( "\t[%s] == [%s]\n" ), pchVarName, pchParamName->String() ); + } + + return ( bNot ) ? ( pVar != pParam ) : ( pVar == pParam ); + + case LESSON_ACTION_GET_DISTANCE: + { + if ( !pVar || !pParam ) + { + if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() ) + { + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\t[output] = [%s]->DistTo( [%s] )", pchVarName, pchParamName->String() ); + ConColorMsg( CBaseLesson::m_rgbaVerboseName, "...\n" ); + ConColorMsg( CBaseLesson::m_rgbaVerboseClose, "\tVar handle or Param handle returned NULL!\n" ); + } + + return false; + } + + C_BasePlayer *pVarPlayer = ( pVar->IsPlayer() ? static_cast< C_BasePlayer* >( pVar ) : NULL ); + C_BasePlayer *pParamPlayer = ( pParam->IsPlayer() ? static_cast< C_BasePlayer* >( pParam ) : NULL ); + + Vector vVarPos = ( pVarPlayer ? pVarPlayer->EyePosition() : pVar->WorldSpaceCenter() ); + Vector vParamPos = ( pParamPlayer ? pParamPlayer->EyePosition() : pParam->WorldSpaceCenter() ); + + m_fOutput = vVarPos.DistTo( vParamPos ); + + if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() ) + { + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\t[output] = [%s]->DistTo( [%s] ) ", pchVarName, pchParamName->String() ); + ConColorMsg( CBaseLesson::m_rgbaVerboseName, "%f\n", m_fOutput ); + } + + return true; + } + + case LESSON_ACTION_GET_ANGULAR_DISTANCE: + { + if ( !pVar || !pParam ) + { + if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() ) + { + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\t[output] = [%s]->AngularDistTo( [%s] )", pchVarName, pchParamName->String() ); + ConColorMsg( CBaseLesson::m_rgbaVerboseName, "...\n" ); + ConColorMsg( CBaseLesson::m_rgbaVerboseClose, "\tVar handle or Param handle returned NULL!\n" ); + } + + return false; + } + + C_BasePlayer *pVarPlayer = ( pVar->IsPlayer() ? static_cast< C_BasePlayer* >( pVar ) : NULL ); + C_BasePlayer *pParamPlayer = ( pParam->IsPlayer() ? static_cast< C_BasePlayer* >( pParam ) : NULL ); + + Vector vVarPos = ( pVarPlayer ? pVarPlayer->EyePosition() : pVar->WorldSpaceCenter() ); + Vector vParamPos = ( pParamPlayer ? pParamPlayer->EyePosition() : pParam->WorldSpaceCenter() ); + + Vector vVarToParam = vParamPos - vVarPos; + VectorNormalize( vVarToParam ); + + Vector vVarForward; + + if ( pVar->IsPlayer() ) + { + AngleVectors( static_cast< C_BasePlayer* >( pVar )->EyeAngles(), &vVarForward, NULL, NULL ); + } + else + { + pVar->GetVectors( &vVarForward, NULL, NULL ); + } + + // Set the distance in degrees + m_fOutput = ( vVarToParam.Dot( vVarForward ) - 1.0f ) * -90.0f; + + if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() ) + { + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\t[output] = [%s]->AngularDistTo( [%s] ) ", pchVarName, pchParamName->String() ); + ConColorMsg( CBaseLesson::m_rgbaVerboseName, "%f\n", m_fOutput ); + } + + return true; + } + + case LESSON_ACTION_GET_PLAYER_DISPLAY_NAME: + { + int iTemp = static_cast( fParam ); + + if ( iTemp <= 0 || iTemp > 2 ) + { + if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() ) + { + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\tQ_strcpy( [stringINVALID], [%s]->GetPlayerName() ", pchVarName ); + ConColorMsg( CBaseLesson::m_rgbaVerboseName, "... " ); + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ")\n" ); + ConColorMsg( CBaseLesson::m_rgbaVerboseClose, "\tParam selecting string is out of range!\n" ); + } + + return false; + } + + // Use string2 if it was specified, otherwise, use string1 + CGameInstructorSymbol *pString; + char const *pchParamNameTemp = NULL; + + if ( iTemp == 2 ) + { + pString = &m_szString2; + pchParamNameTemp = "string2"; + } + else + { + pString = &m_szString1; + pchParamNameTemp = "string1"; + } + + if ( !pVar ) + { + if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() ) + { + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\tQ_strcpy( [%s], [%s]->GetPlayerName() ", pchParamNameTemp, pchVarName ); + ConColorMsg( CBaseLesson::m_rgbaVerboseName, "... " ); + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ")\n" ); + ConColorMsg( CBaseLesson::m_rgbaVerboseClose, "\tVar handle returned NULL!\n" ); + } + + return false; + } + + *pString = pVar->GetPlayerName(); + + if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() ) + { + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\tQ_strcpy( [%s], [%s]->GetPlayerName() ", pchParamNameTemp, pchVarName ); + ConColorMsg( CBaseLesson::m_rgbaVerboseName, "\"%s\" ", pString->String() ); + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ")\n" ); + } + + return true; + } + + case LESSON_ACTION_CLASSNAME_IS: + { + if ( !pVar ) + { + if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() ) + { + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ( bNot ) ? ( "\t!FClassnameIs( [%s] " ) : ( "\tFClassnameIs( [%s] " ), pchVarName ); + ConColorMsg( CBaseLesson::m_rgbaVerboseName, "..." ); + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ", [%s] ", pchParamName->String() ); + ConColorMsg( CBaseLesson::m_rgbaVerboseName, "\"%s\" ", pchParam ); + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ")\n" ); + + ConColorMsg( CBaseLesson::m_rgbaVerboseClose, "\tVar handle returned NULL!\n" ); + } + + return false; + } + + if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() ) + { + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ( bNot ) ? ( "\t!FClassnameIs( [%s] " ) : ( "\tFClassnameIs( [%s] " ), pchVarName ); + ConColorMsg( CBaseLesson::m_rgbaVerboseName, "%s", pVar->GetClassname() ); + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ", [%s] ", pchParamName->String() ); + ConColorMsg( CBaseLesson::m_rgbaVerboseName, "\"%s\" ", pchParam ); + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ")\n" ); + } + + return ( bNot ) ? ( !FClassnameIs( pVar, pchParam ) ) : ( FClassnameIs( pVar, pchParam ) ); + } + + case LESSON_ACTION_TEAM_IS: + { + int iTemp = static_cast( fParam ); + + if ( !pVar ) + { + if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() ) + { + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\t[%s]->GetTeamNumber() ", pchVarName ); + ConColorMsg( CBaseLesson::m_rgbaVerboseName, "... " ); + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ( bNot ) ? ( "!= [%s] " ) : ( "== [%s] " ), pchParamName->String() ); + ConColorMsg( CBaseLesson::m_rgbaVerboseName, "%i\n", iTemp ); + ConColorMsg( CBaseLesson::m_rgbaVerboseClose, "\tVar handle returned NULL!\n" ); + } + + return false; + } + + if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() ) + { + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\t[%s]->GetTeamNumber() ", pchVarName ); + ConColorMsg( CBaseLesson::m_rgbaVerboseName, "%i ", pVar->GetTeamNumber() ); + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ( bNot ) ? ( "!= [%s] " ) : ( "== [%s] " ), pchParamName->String() ); + ConColorMsg( CBaseLesson::m_rgbaVerboseName, "%i\n", iTemp ); + } + + return ( bNot ) ? ( pVar->GetTeamNumber() != iTemp ) : ( pVar->GetTeamNumber() == iTemp ); + } + + case LESSON_ACTION_MODELNAME_IS: + { + C_BaseAnimating *pBaseAnimating = dynamic_cast( pVar ); + + if ( !pBaseAnimating ) + { + if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() ) + { + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\tQ_stricmp( [%s]->ModelName() ", pchVarName ); + ConColorMsg( CBaseLesson::m_rgbaVerboseName, "..." ); + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ", [%s] ", pchParamName->String() ); + ConColorMsg( CBaseLesson::m_rgbaVerboseName, "\"%s\" ", pchParam ); + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ( bNot ) ? ( ") != 0\n" ) : ( ") == 0\n" ) ); + ConColorMsg( CBaseLesson::m_rgbaVerboseClose, "\tVar handle as BaseAnimating returned NULL!\n" ); + } + + return false; + } + + const char *pchModelName = "-no model-"; + CStudioHdr *pModel = pBaseAnimating->GetModelPtr(); + if ( pModel ) + { + const studiohdr_t *pRenderHDR = pModel->GetRenderHdr(); + if ( pRenderHDR ) + { + pchModelName = pRenderHDR->name; + } + } + + if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() ) + { + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\tQ_stricmp( [%s]->ModelName() ", pchVarName ); + ConColorMsg( CBaseLesson::m_rgbaVerboseName, "%s", pchModelName ); + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ", [%s] ", pchParamName->String() ); + ConColorMsg( CBaseLesson::m_rgbaVerboseName, "\"%s\" ", pchParam ); + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ( bNot ) ? ( ") != 0\n" ) : ( ") == 0\n" ) ); + } + + return ( bNot ) ? ( Q_stricmp( pchModelName, pchParam ) != 0 ) : ( Q_stricmp( pchModelName, pchParam ) == 0 ); + } + + case LESSON_ACTION_HEALTH_LESS_THAN: + { + int iTemp = static_cast( fParam ); + + if ( !pVar ) + { + if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() ) + { + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\t[%s]->GetHealth() ", pchVarName ); + ConColorMsg( CBaseLesson::m_rgbaVerboseName, "... " ); + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ( bNot ) ? ( ">= [%s] " ) : ( "< [%s] " ), pchParamName->String() ); + ConColorMsg( CBaseLesson::m_rgbaVerboseName, "%i\n", iTemp ); + ConColorMsg( CBaseLesson::m_rgbaVerboseClose, "\tVar handle returned NULL!\n" ); + } + + return false; + } + + if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() ) + { + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\t[%s]->GetHealth() ", pchVarName ); + ConColorMsg( CBaseLesson::m_rgbaVerboseName, "%i ", pVar->GetHealth() ); + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ( bNot ) ? ( ">= [%s] " ) : ( "< [%s] " ), pchParamName->String() ); + ConColorMsg( CBaseLesson::m_rgbaVerboseName, "%i\n", iTemp ); + } + + return ( bNot ) ? ( pVar->GetHealth() >= iTemp ) : ( pVar->GetHealth() < iTemp ); + } + + case LESSON_ACTION_HEALTH_PERCENTAGE_LESS_THAN: + { + if ( !pVar ) + { + if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() ) + { + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\t[%s]->HealthFraction() ", pchVarName ); + ConColorMsg( CBaseLesson::m_rgbaVerboseName, "... " ); + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ( bNot ) ? ( ">= [%s] " ) : ( "< [%s] " ), pchParamName->String() ); + ConColorMsg( CBaseLesson::m_rgbaVerboseName, "%f\n", fParam ); + ConColorMsg( CBaseLesson::m_rgbaVerboseClose, "\tVar handle returned NULL!\n" ); + } + + return false; + } + + if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() ) + { + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\t[%s]->HealthFraction() ", pchVarName ); + ConColorMsg( CBaseLesson::m_rgbaVerboseName, "%f ", pVar->HealthFraction() ); + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ( bNot ) ? ( ">= [%s] " ) : ( "< [%s] " ), pchParamName->String() ); + ConColorMsg( CBaseLesson::m_rgbaVerboseName, "%f\n", fParam ); + } + + float fHealthPercentage = 1.0f; + + if ( pVar->GetMaxHealth() != 0.0f ) + { + fHealthPercentage = pVar->HealthFraction(); + } + + return ( bNot ) ? ( fHealthPercentage >= fParam ) : ( fHealthPercentage < fParam ); + } + + case LESSON_ACTION_GET_ACTIVE_WEAPON: + { + int iTemp = static_cast( fParam ); + + if ( iTemp <= 0 || iTemp > 2 ) + { + if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() ) + { + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\t[entityINVALID] = [%s]->GetActiveWeapon()\n", pchVarName ); + ConColorMsg( CBaseLesson::m_rgbaVerboseClose, "\tParam selecting string is out of range!\n" ); + } + + return false; + } + + // Use entity2 if it was specified, otherwise, use entity1 + CHandle *pHandle; + + char const *pchParamNameTemp = NULL; + + if ( iTemp == 2 ) + { + pHandle = &m_hEntity2; + pchParamNameTemp = "entity2"; + } + else + { + pHandle = &m_hEntity1; + pchParamNameTemp = "entity1"; + } + + C_BaseCombatCharacter *pBaseCombatCharacter = NULL; + + if ( pVar ) + { + pBaseCombatCharacter = pVar->MyCombatCharacterPointer(); + } + + if ( !pBaseCombatCharacter ) + { + if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() ) + { + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\t[%s] = [%s]->GetActiveWeapon()", pchParamNameTemp, pchVarName ); + ConColorMsg( CBaseLesson::m_rgbaVerboseClose, "\tVar handle as BaseCombatCharacter returned NULL!\n" ); + } + + return false; + } + + pHandle->Set( pBaseCombatCharacter->GetActiveWeapon() ); + + if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() ) + { + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\t[%s] = [%s]->GetActiveWeapon()", pchParamNameTemp, pchVarName ); + ConColorMsg( CBaseLesson::m_rgbaVerboseName, "\"%s\"\n", pchParam ); + } + + return true; + } + + case LESSON_ACTION_WEAPON_IS: + { + C_BaseCombatCharacter *pBaseCombatCharacter = NULL; + + if ( pVar ) + { + pBaseCombatCharacter = pVar->MyCombatCharacterPointer(); + } + + if ( !pBaseCombatCharacter ) + { + if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() ) + { + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\t[%s]->GetActiveWeapon()->GetName() ", pchVarName ); + ConColorMsg( CBaseLesson::m_rgbaVerboseName, "... " ); + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ( bNot ) ? ( "!= [%s] " ) : ( "== [%s] " ), pchParamName->String() ); + ConColorMsg( CBaseLesson::m_rgbaVerboseName, "\"%s\"\n", pchParam ); + ConColorMsg( CBaseLesson::m_rgbaVerboseClose, "\tVar handle as BaseCombatCharacter returned NULL!\n" ); + } + + return false; + } + + CBaseCombatWeapon *pBaseCombatWeapon = pBaseCombatCharacter->GetActiveWeapon(); + + if ( !pBaseCombatWeapon ) + { + if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() ) + { + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\t[%s]->GetActiveWeapon()->GetName() ", pchVarName ); + ConColorMsg( CBaseLesson::m_rgbaVerboseName, "... " ); + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ( bNot ) ? ( "!= [%s] " ) : ( "== [%s] " ), pchParamName->String() ); + ConColorMsg( CBaseLesson::m_rgbaVerboseName, "\"%s\"\n", pchParam ); + ConColorMsg( CBaseLesson::m_rgbaVerboseClose, "\tVar GetActiveWeapon returned NULL!\n" ); + } + + return false; + } + + if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() ) + { + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\t[%s]->GetActiveWeapon()->GetName() ", pchVarName ); + ConColorMsg( CBaseLesson::m_rgbaVerboseName, "\"%s\" ", pBaseCombatWeapon->GetName() ); + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ( bNot ) ? ( "!= [%s] " ) : ( "== [%s] " ), pchParamName->String() ); + ConColorMsg( CBaseLesson::m_rgbaVerboseName, "\"%s\"\n", pchParam ); + } + + return ( bNot ) ? ( Q_stricmp( pBaseCombatWeapon->GetName(), pchParam ) != 0 ) : ( Q_stricmp( pBaseCombatWeapon->GetName(), pchParam ) == 0 ); + } + + case LESSON_ACTION_WEAPON_HAS: + { + C_BaseCombatCharacter *pBaseCombatCharacter = NULL; + + if ( pVar ) + { + pBaseCombatCharacter = pVar->MyCombatCharacterPointer(); + } + + if ( !pBaseCombatCharacter ) + { + if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() ) + { + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ( bNot ) ? ( "\t![%s]->Weapon_OwnsThisType([%s] " ) : ( "\t[%s]->Weapon_OwnsThisType([%s] " ), pchVarName, pchParamName->String() ); + ConColorMsg( CBaseLesson::m_rgbaVerboseName, "\"%s\"", pchParam ); + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ")\n" ); + ConColorMsg( CBaseLesson::m_rgbaVerboseClose, "\tVar handle as BaseCombatCharacter returned NULL!\n" ); + } + + return false; + } + + if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() ) + { + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ( bNot ) ? ( "\t![%s]->Weapon_OwnsThisType([%s] " ) : ( "\t[%s]->Weapon_OwnsThisType([%s] " ), pchVarName, pchParamName->String() ); + ConColorMsg( CBaseLesson::m_rgbaVerboseName, "\"%s\"", pchParam ); + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ")\n" ); + } + + return ( bNot ) ? ( pBaseCombatCharacter->Weapon_OwnsThisType( pchParam ) == NULL ) : ( pBaseCombatCharacter->Weapon_OwnsThisType( pchParam ) != NULL ); + } + + case LESSON_ACTION_GET_ACTIVE_WEAPON_SLOT: + { + C_BaseCombatCharacter *pBaseCombatCharacter = NULL; + + if ( pVar ) + { + pBaseCombatCharacter = pVar->MyCombatCharacterPointer(); + } + + if ( !pBaseCombatCharacter ) + { + if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() ) + { + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\t[output] = [%s]->Weapon_GetActiveSlot() ...\n", pchVarName ); + ConColorMsg( CBaseLesson::m_rgbaVerboseClose, "\tVar handle as BaseCombatCharacter returned NULL!\n" ); + } + + return false; + } + + C_BaseCombatWeapon *pWeapon = pBaseCombatCharacter->GetActiveWeapon(); + + if ( !pWeapon ) + { + if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() ) + { + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\t[output] = [%s]->Weapon_GetActiveSlot() ...\n", pchVarName ); + ConColorMsg( CBaseLesson::m_rgbaVerboseClose, "\tVar GetActiveWeapon returned NULL!\n" ); + } + + return false; + } + + // @TODO + /*m_fOutput = pBaseCombatCharacter->Weapon_GetSlot( pWeapon->GetWpnData().szClassName ); + + if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() ) + { + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\t[output] = [%s]->Weapon_GetSlot() ", pchVarName ); + ConColorMsg( CBaseLesson::m_rgbaVerboseName, "%f\n", m_fOutput ); + }*/ + + return true; + } + + case LESSON_ACTION_GET_WEAPON_SLOT: + { + C_BaseCombatCharacter *pBaseCombatCharacter = NULL; + + if ( pVar ) + { + pBaseCombatCharacter = pVar->MyCombatCharacterPointer(); + } + + if ( !pBaseCombatCharacter ) + { + if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() ) + { + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\t[output] = [%s]->Weapon_GetSlot([%s] ", pchVarName, pchParamName->String() ); + ConColorMsg( CBaseLesson::m_rgbaVerboseName, "\"%s\"", pchParam ); + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ") ...\n" ); + ConColorMsg( CBaseLesson::m_rgbaVerboseClose, "\tVar handle as BaseCombatCharacter returned NULL!\n" ); + } + + return false; + } + + // @TODO + /*m_fOutput = pBaseCombatCharacter->Weapon_GetSlot( pchParam ); + + if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() ) + { + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\t[output] = [%s]->Weapon_GetSlot([%s] ", pchVarName, pchParamName->String() ); + ConColorMsg( CBaseLesson::m_rgbaVerboseName, "\"%s\"", pchParam ); + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ") " ); + ConColorMsg( CBaseLesson::m_rgbaVerboseName, "%f\n", m_fOutput ); + }*/ + + return true; + } + + case LESSON_ACTION_GET_WEAPON_IN_SLOT: + { + int nTemp = static_cast( fParam ); + + C_BaseCombatCharacter *pBaseCombatCharacter = NULL; + + if ( pVar ) + { + pBaseCombatCharacter = pVar->MyCombatCharacterPointer(); + } + + if ( !pBaseCombatCharacter ) + { + if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() ) + { + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\t[entity1] = [%s]->GetWeapon([%s] ", pchVarName, pchParamName->String() ); + ConColorMsg( CBaseLesson::m_rgbaVerboseName, "\"%i\"", nTemp ); + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ")\n" ); + ConColorMsg( CBaseLesson::m_rgbaVerboseClose, "\tVar handle as BaseCombatCharacter returned NULL!\n" ); + } + + return false; + } + + m_hEntity1 = pBaseCombatCharacter->GetWeapon( nTemp ); + + if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() ) + { + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\t[entity1] = [%s]->GetWeapon([%s] ", pchVarName, pchParamName->String() ); + ConColorMsg( CBaseLesson::m_rgbaVerboseName, "\"%i\"", nTemp ); + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ")\n" ); + } + + return true; + } + + case LESSON_ACTION_CLIP_PERCENTAGE_LESS_THAN: + { + C_BaseCombatCharacter *pBaseCombatCharacter = NULL; + + if ( pVar ) + { + pBaseCombatCharacter = pVar->MyCombatCharacterPointer(); + } + + if ( !pBaseCombatCharacter ) + { + if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() ) + { + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\t[%s]->GetActiveWeapon()->Clip1Percentage() ", pchVarName ); + ConColorMsg( CBaseLesson::m_rgbaVerboseName, "... " ); + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ( bNot ) ? ( ">= [%s] " ) : ( "< [%s] " ), pchParamName->String() ); + ConColorMsg( CBaseLesson::m_rgbaVerboseName, "%.1f\n", fParam ); + ConColorMsg( CBaseLesson::m_rgbaVerboseClose, "\tVar handle as BaseCombatCharacter returned NULL!\n" ); + } + + return false; + } + + CBaseCombatWeapon *pBaseCombatWeapon = pBaseCombatCharacter->GetActiveWeapon(); + + if ( !pBaseCombatWeapon ) + { + if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() ) + { + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\t[%s]->GetActiveWeapon()->Clip1Percentage() ", pchVarName ); + ConColorMsg( CBaseLesson::m_rgbaVerboseName, "... " ); + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ( bNot ) ? ( ">= [%s] " ) : ( "< [%s] " ), pchParamName->String() ); + ConColorMsg( CBaseLesson::m_rgbaVerboseName, "%.1f\n", fParam ); + ConColorMsg( CBaseLesson::m_rgbaVerboseClose, "\tVar GetActiveWeapon returned NULL!\n" ); + } + + return false; + } + + float fClip1Percentage = 100.0f; + + if ( pBaseCombatWeapon->UsesClipsForAmmo1() ) + { + fClip1Percentage = 100.0f * ( static_cast( pBaseCombatWeapon->Clip1() ) / static_cast( pBaseCombatWeapon->GetMaxClip1() ) ); + } + + if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() ) + { + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\t[%s]->GetActiveWeapon()->Clip1Percentage() ", pchVarName ); + ConColorMsg( CBaseLesson::m_rgbaVerboseName, "%.1f ", fClip1Percentage ); + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ( bNot ) ? ( ">= [%s] " ) : ( "< [%s] " ), pchParamName->String() ); + ConColorMsg( CBaseLesson::m_rgbaVerboseName, "%.1f\n", fParam ); + } + + return ( bNot ) ? ( fClip1Percentage >= fParam ) : ( fClip1Percentage < fParam ); + } + + case LESSON_ACTION_WEAPON_AMMO_LOW: + { + int iTemp = static_cast( fParam ); + + C_BasePlayer *pBasePlayer = ToBasePlayer( pVar ); + + if ( !pBasePlayer ) + { + if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() ) + { + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\t[%s]->GetWeaponInSlot( ", pchVarName ); + ConColorMsg( CBaseLesson::m_rgbaVerboseName, "%i ", iTemp ); + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ( bNot ) ? ( ")->AmmoPercentage() >= 30\n" ) : ( ")->AmmoPercentage() < 30\n" ) ); + ConColorMsg( CBaseLesson::m_rgbaVerboseClose, "\tVar handle as BasePlayer returned NULL!\n" ); + } + + return false; + } + + CBaseCombatWeapon *pBaseCombatWeapon = NULL; + + // Get the weapon in variable slot + for ( int iWeapon = 0; iWeapon < MAX_WEAPONS; iWeapon++ ) + { + CBaseCombatWeapon *pBaseCombatWeaponTemp = pBasePlayer->GetWeapon( iWeapon ); + if ( pBaseCombatWeaponTemp ) + { + if ( pBaseCombatWeaponTemp->GetSlot() == iTemp ) + { + pBaseCombatWeapon = pBaseCombatWeaponTemp; + break; + } + } + } + + if ( !pBaseCombatWeapon ) + { + if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() ) + { + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\t[%s]->GetWeaponInSlot( ", pchVarName ); + ConColorMsg( CBaseLesson::m_rgbaVerboseName, "%i ", iTemp ); + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ( bNot ) ? ( ")->AmmoPercentage() >= 30\n" ) : ( ")->AmmoPercentage() < 30\n" ) ); + ConColorMsg( CBaseLesson::m_rgbaVerboseClose, "\tVar GetActiveWeapon returned NULL!\n" ); + } + + return false; + } + + // Check if the ammo is full + int iAmmoType = pBaseCombatWeapon->GetPrimaryAmmoType(); + int iMaxAmmo = GetAmmoDef()->MaxCarry( iAmmoType/*, pBasePlayer*/ ); + int iPlayerAmmo = pBasePlayer->GetAmmoCount( iAmmoType ); + + bool bAmmoLow = ( iPlayerAmmo < ( iMaxAmmo / 3 ) ); + + if ( bNot ) + { + bAmmoLow = !bAmmoLow; + } + + if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() ) + { + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\t[%s]->GetWeaponInSlot( ", pchVarName ); + ConColorMsg( CBaseLesson::m_rgbaVerboseName, "%i ", iTemp ); + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ( bNot ) ? ( ")->AmmoPercentage() >= 30 " ) : ( ")->AmmoPercentage() < 30 " ) ); + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ( bAmmoLow ) ? ( "true\n" ) : ( "false\n" ) ); + } + + return bAmmoLow; + } + + case LESSON_ACTION_WEAPON_AMMO_FULL: + { + int iTemp = static_cast( fParam ); + + C_BasePlayer *pBasePlayer = ToBasePlayer( pVar ); + + if ( !pBasePlayer ) + { + if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() ) + { + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ( bNot ) ? ( "\t![%s]->GetWeaponInSlot( " ) : ( "\t[%s]->GetWeaponInSlot( " ), pchVarName ); + ConColorMsg( CBaseLesson::m_rgbaVerboseSuccess, "%i ", iTemp ); + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ")->AmmoFull()\n" ); + ConColorMsg( CBaseLesson::m_rgbaVerboseClose, "\tVar handle as BasePlayer returned NULL!\n" ); + } + + return false; + } + + CBaseCombatWeapon *pBaseCombatWeapon = NULL; + + // Get the weapon in variable slot + for ( int iWeapon = 0; iWeapon < MAX_WEAPONS; iWeapon++ ) + { + CBaseCombatWeapon *pBaseCombatWeaponTemp = pBasePlayer->GetWeapon( iWeapon ); + if ( pBaseCombatWeaponTemp ) + { + if ( pBaseCombatWeaponTemp->GetSlot() == iTemp ) + { + pBaseCombatWeapon = pBaseCombatWeaponTemp; + break; + } + } + } + + if ( !pBaseCombatWeapon ) + { + if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() ) + { + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ( bNot ) ? ( "\t![%s]->GetWeaponInSlot( " ) : ( "\t[%s]->GetWeaponInSlot( " ), pchVarName ); + ConColorMsg( CBaseLesson::m_rgbaVerboseSuccess, "%i ", iTemp ); + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ")->AmmoFull()\n" ); + ConColorMsg( CBaseLesson::m_rgbaVerboseClose, "\tVar GetWeaponInSlot returned NULL!\n" ); + } + + return false; + } + + // Check if the ammo is full + int iAmmoType = pBaseCombatWeapon->GetPrimaryAmmoType(); + int iMaxAmmo = GetAmmoDef()->MaxCarry( iAmmoType/*, pBasePlayer*/ ); + int iPlayerAmmo = pBasePlayer->GetAmmoCount( iAmmoType ); + + bool bAmmoFull = ( iPlayerAmmo >= iMaxAmmo ); + + if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() ) + { + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ( bNot ) ? ( "\t![%s]->GetWeaponInSlot( " ) : ( "\t[%s]->GetWeaponInSlot( " ), pchVarName ); + ConColorMsg( CBaseLesson::m_rgbaVerboseSuccess, "%i ", iTemp ); + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ")->AmmoFull() " ); + ConColorMsg( CBaseLesson::m_rgbaVerboseName, ( bAmmoFull ) ? ( "true\n" ) : ( "false\n" ) ); + } + + return ( bNot ) ? ( !bAmmoFull ) : ( bAmmoFull ); + } + + case LESSON_ACTION_WEAPON_AMMO_EMPTY: + { + int iTemp = static_cast( fParam ); + + C_BasePlayer *pBasePlayer = ToBasePlayer( pVar ); + + if ( !pBasePlayer ) + { + if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() ) + { + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ( bNot ) ? ( "\t![%s]->GetWeaponInSlot( " ) : ( "\t[%s]->GetWeaponInSlot( " ), pchVarName ); + ConColorMsg( CBaseLesson::m_rgbaVerboseSuccess, "%i ", iTemp ); + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ")->AmmoEmpty()\n" ); + ConColorMsg( CBaseLesson::m_rgbaVerboseClose, "\tVar handle as BasePlayer returned NULL!\n" ); + } + + return false; + } + + CBaseCombatWeapon *pBaseCombatWeapon = NULL; + + // Get the weapon in variable slot + for ( int iWeapon = 0; iWeapon < MAX_WEAPONS; iWeapon++ ) + { + CBaseCombatWeapon *pBaseCombatWeaponTemp = pBasePlayer->GetWeapon( iWeapon ); + if ( pBaseCombatWeaponTemp ) + { + if ( pBaseCombatWeaponTemp->GetSlot() == iTemp ) + { + pBaseCombatWeapon = pBaseCombatWeaponTemp; + break; + } + } + } + + if ( !pBaseCombatWeapon ) + { + if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() ) + { + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ( bNot ) ? ( "\t![%s]->GetWeaponInSlot( " ) : ( "\t[%s]->GetWeaponInSlot( " ), pchVarName ); + ConColorMsg( CBaseLesson::m_rgbaVerboseSuccess, "%i ", iTemp ); + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ")->AmmoEmpty()\n" ); + ConColorMsg( CBaseLesson::m_rgbaVerboseClose, "\tVar GetWeaponInSlot returned NULL!\n" ); + } + + return false; + } + + // Check if the ammo is empty + int iAmmoType = pBaseCombatWeapon->GetPrimaryAmmoType(); + int iPlayerAmmo = pBasePlayer->GetAmmoCount( iAmmoType ); + + bool bAmmoEmpty = ( iPlayerAmmo <= 0 ); + + if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() ) + { + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ( bNot ) ? ( "\t![%s]->GetWeaponInSlot( " ) : ( "\t[%s]->GetWeaponInSlot( " ), pchVarName ); + ConColorMsg( CBaseLesson::m_rgbaVerboseSuccess, "%i ", iTemp ); + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ")->AmmoEmpty() " ); + ConColorMsg( CBaseLesson::m_rgbaVerboseName, ( bAmmoEmpty ) ? ( "true" ) : ( "false" ) ); + ConColorMsg(CBaseLesson::m_rgbaVerbosePlain, " )\n" ); + } + + return ( bNot ) ? ( !bAmmoEmpty ) : ( bAmmoEmpty ); + } + + /*case LESSON_ACTION_WEAPON_CAN_USE: + { + C_BaseCombatWeapon *pBaseCombatWeapon = dynamic_cast( pParam ); + C_BasePlayer *pBasePlayer = ToBasePlayer( pVar ); + + if ( !pBasePlayer ) + { + if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() ) + { + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ( bNot ) ? ( "\t![%s]->Weapon_CanUse([%s])\n" ) : ( "\t[%s]->Weapon_CanUse([%s])\n" ), pchVarName, pchParamName->String() ); + ConColorMsg( CBaseLesson::m_rgbaVerboseClose, "\tVar handle as BasePlayer returned NULL!\n" ); + } + + return false; + } + + if ( !pBaseCombatWeapon ) + { + if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() ) + { + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ( bNot ) ? ( "\t![%s]->Weapon_CanUse([%s])\n" ) : ( "\t[%s]->Weapon_CanUse([%s])\n" ), pchVarName, pchParamName->String() ); + ConColorMsg( CBaseLesson::m_rgbaVerboseClose, "\tParam BaseCombatWeapon returned NULL!\n" ); + } + + return false; + } + + bool bCanEquip = pBasePlayer->Weapon_CanUse( pBaseCombatWeapon ); + + if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() ) + { + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ( bNot ) ? ( "\t![%s]->Weapon_CanUse([%s]) " ) : ( "\t[%s]->Weapon_CanUse([%s]) " ), pchVarName, pchParamName->String() ); + ConColorMsg( CBaseLesson::m_rgbaVerboseName, ( bCanEquip ) ? ( "true\n" ) : ( "false\n" ) ); + } + + return ( bNot ) ? ( !bCanEquip ) : ( bCanEquip ); + }*/ + + case LESSON_ACTION_USE_TARGET_IS: + { + C_BasePlayer *pBasePlayer = ToBasePlayer( pVar ); + + if ( !pBasePlayer ) + { + if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() ) + { + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ( bNot ) ? ( "\tC_BaseEntity::Instance([%s]->GetUseEntity()) != [%s]\n" ) : ( "\tC_BaseEntity::Instance([%s]->GetUseEntity()) == [%s]\n" ), pchVarName, pchParamName->String() ); + ConColorMsg( CBaseLesson::m_rgbaVerboseClose, "\tVar handle as Player returned NULL!\n" ); + } + + return false; + } + + if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() ) + { + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ( bNot ) ? ( "\tC_BaseEntity::Instance([%s]->GetUseEntity()) != [%s]\n" ) : ( "\tC_BaseEntity::Instance([%s]->GetUseEntity()) == [%s]\n" ), pchVarName, pchParamName->String() ); + } + + return ( bNot ) ? ( C_BaseEntity::Instance( pBasePlayer->GetUseEntity() ) != pParam ) : ( C_BaseEntity::Instance( pBasePlayer->GetUseEntity() ) == pParam ); + } + + case LESSON_ACTION_GET_USE_TARGET: + { + int iTemp = static_cast( fParam ); + + if ( iTemp <= 0 || iTemp > 2 ) + { + if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() ) + { + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\t[entityINVALID] = C_BaseEntity::Instance([%s]->GetUseEntity())\n", pchVarName ); + ConColorMsg( CBaseLesson::m_rgbaVerboseClose, "\tParam selecting string is out of range!\n" ); + } + + return false; + } + + // Use entity2 if it was specified, otherwise, use entity1 + CHandle *pHandle; + char const *pchParamNameTemp = NULL; + + if ( iTemp == 2 ) + { + pHandle = &m_hEntity2; + pchParamNameTemp = "entity2"; + } + else + { + pHandle = &m_hEntity1; + pchParamNameTemp = "entity1"; + } + + C_BasePlayer *pBasePlayer = ToBasePlayer( pVar ); + + if ( !pBasePlayer ) + { + if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() ) + { + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\t[%s] = C_BaseEntity::Instance([%s]->GetUseEntity())\n", pchParamNameTemp, pchVarName ); + ConColorMsg( CBaseLesson::m_rgbaVerboseClose, "\tVar handle as Player returned NULL!\n" ); + } + + return false; + } + + pHandle->Set( C_BaseEntity::Instance( pBasePlayer->GetUseEntity() ) ); + + if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() ) + { + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\t[%s] = C_BaseEntity::Instance([%s]->GetUseEntity())\n", pchParamNameTemp, pchVarName ); + } + + return true; + } + + /*case LESSON_ACTION_GET_POTENTIAL_USE_TARGET: + { + int iTemp = static_cast( fParam ); + + if ( iTemp <= 0 || iTemp > 2 ) + { + if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() ) + { + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\t[entityINVALID] = C_BaseEntity::Instance([%s]->GetPotentialUseEntity())\n", pchVarName ); + ConColorMsg( CBaseLesson::m_rgbaVerboseClose, "\tParam selecting string is out of range!\n" ); + } + + return false; + } + + // Use entity2 if it was specified, otherwise, use entity1 + CHandle *pHandle; + char const *pchParamNameTemp = NULL; + + if ( iTemp == 2 ) + { + pHandle = &m_hEntity2; + pchParamNameTemp = "entity2"; + } + else + { + pHandle = &m_hEntity1; + pchParamNameTemp = "entity1"; + } + + C_BasePlayer *pBasePlayer = ToBasePlayer( pVar ); + + if ( !pBasePlayer ) + { + if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() ) + { + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\t[%s] = C_BaseEntity::Instance([%s]->GetPotentialUseEntity())\n", pchParamNameTemp, pchVarName ); + ConColorMsg( CBaseLesson::m_rgbaVerboseClose, "\tVar handle as Player returned NULL!\n" ); + } + + return false; + } + + pHandle->Set( C_BaseEntity::Instance( pBasePlayer->GetPotentialUseEntity() ) ); + + if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() ) + { + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\t[%s] = C_BaseEntity::Instance([%s]->GetPotentialUseEntity())\n", pchParamNameTemp, pchVarName ); + } + + return true; + }*/ + } + + DevWarning( "Invalid lesson action type used with \"%s\" variable type.\n", pchVarName ); + + return false; +} + +bool CScriptedIconLesson::ProcessElementAction( int iAction, bool bNot, const char *pchVarName, CGameInstructorSymbol *pchVar, const CGameInstructorSymbol *pchParamName, const char *pchParam ) +{ + switch ( iAction ) + { + case LESSON_ACTION_REFERENCE_OPEN: + { + const CBaseLesson *pLesson = GetGameInstructor().GetLesson( pchParamName->String() ); + if ( !pLesson ) + { + DevWarning( "Invalid lesson specified: \"%s\".", pchParamName->String() ); + return false; + } + + if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() ) + { + ConColorMsg( Color( 64, 128, 255, 255 ), ( bNot ) ? ( "\t!( [\"%s\"]->IsInstanceActive() " ) : ( "\t( [\"%s\"]->IsInstanceActive() " ), pchParamName->String() ); + ConColorMsg( Color( 255, 255, 255, 255 ), "\"%s\"", (pLesson->IsInstanceActive() ? "true" : "false") ); + ConColorMsg( Color( 64, 128, 255, 255 ), " )\n" ); + } + + return ( bNot ) ? ( !pLesson->IsInstanceActive() ) : ( pLesson->IsInstanceActive() ); + } + + case LESSON_ACTION_SET: + if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() ) + { + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\tQ_strcpy([%s], [%s] ", pchVarName, pchParamName->String() ); + ConColorMsg( CBaseLesson::m_rgbaVerboseName, "\"%s\"", pchParam ); + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ")\n" ); + } + + *pchVar = pchParam; + return true; + + case LESSON_ACTION_ADD: + if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() ) + { + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\tQ_strcat([%s], [%s] ", pchVarName, pchParamName->String() ); + ConColorMsg( CBaseLesson::m_rgbaVerboseName, "\"%s\"", pchParam ); + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ")\n" ); + } + + char szTemp[ 256 ]; + Q_strncpy( szTemp, pchVar->String(), sizeof( szTemp ) ); + Q_strncat( szTemp, pchParam, sizeof( szTemp ) ); + + *pchVar = szTemp; + return true; + + case LESSON_ACTION_IS: + if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() ) + { + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\tQ_strcmp([%s] ", pchVarName ); + ConColorMsg( CBaseLesson::m_rgbaVerboseName, "\"%s\"", pchVar->String() ); + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ", [%s] ", pchParamName->String() ); + ConColorMsg( CBaseLesson::m_rgbaVerboseName, "\"%s\"", pchParam ); + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ( bNot ) ? ( ") != 0\n" ) : ( ") == 0\n" ) ); + } + + return ( bNot ) ? ( Q_strcmp( pchVar->String(), pchParam ) != 0 ) : ( Q_strcmp( pchVar->String(), pchParam ) == 0 ); + + case LESSON_ACTION_HAS_PREFIX: + if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() ) + { + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\tStringHasPrefix([%s] ", pchVarName ); + ConColorMsg( CBaseLesson::m_rgbaVerboseName, "\"%s\"", pchVar->String() ); + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ", [%s] ", pchParamName->String() ); + ConColorMsg( CBaseLesson::m_rgbaVerboseName, "\"%s\"", pchParam ); + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ( bNot ) ? ( ") == false\n" ) : ( ") == true\n" ) ); + } + + return ( bNot ) ? ( !StringHasPrefix( pchVar->String(), pchParam ) ) : ( StringHasPrefix( pchVar->String(), pchParam ) ); + + case LESSON_ACTION_LESS_THAN: + if ( gameinstructor_verbose.GetInt() > 0 && ShouldShowSpew() ) + { + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\tQ_strcmp([%s] ", pchVarName ); + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\"%s\"", pchVar->String() ); + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ", [%s] ", pchParamName->String() ); + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "\"%s\"", pchParam ); + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ( bNot ) ? ( ") >= 0\n" ) : ( ") < 0\n" ) ); + } + + return ( bNot ) ? ( Q_strcmp( pchVar->String(), pchParam ) >= 0 ) : ( Q_strcmp( pchVar->String(), pchParam ) < 0 ); + } + + DevWarning( "Invalid lesson action type used with \"%s\" variable type.\n", pchVarName ); + + return false; +} + +LessonEvent_t * CScriptedIconLesson::AddOpenEvent() +{ + int iNewLessonEvent = m_OpenEvents.AddToTail(); + return &(m_OpenEvents[ iNewLessonEvent ]); +} + +LessonEvent_t * CScriptedIconLesson::AddCloseEvent() +{ + int iNewLessonEvent = m_CloseEvents.AddToTail(); + return &(m_CloseEvents[ iNewLessonEvent ]); +} + +LessonEvent_t * CScriptedIconLesson::AddSuccessEvent() +{ + int iNewLessonEvent = m_SuccessEvents.AddToTail(); + return &(m_SuccessEvents[ iNewLessonEvent ]); +} + +LessonEvent_t * CScriptedIconLesson::AddOnOpenEvent() +{ + int iNewLessonEvent = m_OnOpenEvents.AddToTail(); + return &(m_OnOpenEvents[ iNewLessonEvent ]); +} + +LessonEvent_t * CScriptedIconLesson::AddUpdateEvent() +{ + int iNewLessonEvent = m_UpdateEvents.AddToTail(); + return &(m_UpdateEvents[ iNewLessonEvent ]); +} + +// Static method to init the keyvalues symbols used for comparisons +void CScriptedIconLesson::PreReadLessonsFromFile() +{ + static bool bFirstTime = true; + if ( !bFirstTime ) + return; + bFirstTime = false; + + // Run init info call macros on all scriptable variables (see: LESSON_VARIABLE_FACTORY definition) +#define LESSON_VARIABLE_MACRO LESSON_VARIABLE_INIT_SYMBOL +#define LESSON_VARIABLE_MACRO_BOOL LESSON_VARIABLE_INIT_SYMBOL +#define LESSON_VARIABLE_MACRO_EHANDLE LESSON_VARIABLE_INIT_SYMBOL +#define LESSON_VARIABLE_MACRO_STRING LESSON_VARIABLE_INIT_SYMBOL + LESSON_VARIABLE_FACTORY +#undef LESSON_VARIABLE_MACRO +#undef LESSON_VARIABLE_MACRO_BOOL +#undef LESSON_VARIABLE_MACRO_EHANDLE +#undef LESSON_VARIABLE_MACRO_STRING + + // And build the map of variable name to enum + // Run string to int macros on all scriptable variables (see: LESSON_VARIABLE_FACTORY definition) +#define LESSON_VARIABLE_MACRO LESSON_SCRIPT_STRING_ADD_TO_MAP +#define LESSON_VARIABLE_MACRO_BOOL LESSON_SCRIPT_STRING_ADD_TO_MAP +#define LESSON_VARIABLE_MACRO_EHANDLE LESSON_SCRIPT_STRING_ADD_TO_MAP +#define LESSON_VARIABLE_MACRO_STRING LESSON_SCRIPT_STRING_ADD_TO_MAP + LESSON_VARIABLE_FACTORY +#undef LESSON_VARIABLE_MACRO +#undef LESSON_VARIABLE_MACRO_BOOL +#undef LESSON_VARIABLE_MACRO_EHANDLE +#undef LESSON_VARIABLE_MACRO_STRING + + // Set up mapping of field types + g_TypeToParamTypeMap.Insert( "float", FIELD_FLOAT ); + g_TypeToParamTypeMap.Insert( "string", FIELD_STRING ); + g_TypeToParamTypeMap.Insert( "int", FIELD_INTEGER ); + g_TypeToParamTypeMap.Insert( "integer", FIELD_INTEGER ); + g_TypeToParamTypeMap.Insert( "short", FIELD_INTEGER ); + g_TypeToParamTypeMap.Insert( "long", FIELD_INTEGER ); + g_TypeToParamTypeMap.Insert( "bool", FIELD_BOOLEAN ); + g_TypeToParamTypeMap.Insert( "player", FIELD_CUSTOM ); + g_TypeToParamTypeMap.Insert( "entity", FIELD_EHANDLE ); + g_TypeToParamTypeMap.Insert( "convar", FIELD_EMBEDDED ); + g_TypeToParamTypeMap.Insert( "void", FIELD_VOID ); + + // Set up the lesson action map + + CScriptedIconLesson::LessonActionMap.Insert( "scope in", LESSON_ACTION_SCOPE_IN ); + CScriptedIconLesson::LessonActionMap.Insert( "scope out", LESSON_ACTION_SCOPE_OUT ); + CScriptedIconLesson::LessonActionMap.Insert( "close", LESSON_ACTION_CLOSE ); + CScriptedIconLesson::LessonActionMap.Insert( "success", LESSON_ACTION_SUCCESS ); + CScriptedIconLesson::LessonActionMap.Insert( "lock", LESSON_ACTION_LOCK ); + CScriptedIconLesson::LessonActionMap.Insert( "present complete", LESSON_ACTION_PRESENT_COMPLETE ); + CScriptedIconLesson::LessonActionMap.Insert( "present start", LESSON_ACTION_PRESENT_START ); + CScriptedIconLesson::LessonActionMap.Insert( "present end", LESSON_ACTION_PRESENT_END ); + + CScriptedIconLesson::LessonActionMap.Insert( "reference open", LESSON_ACTION_REFERENCE_OPEN ); + + CScriptedIconLesson::LessonActionMap.Insert( "set", LESSON_ACTION_SET ); + CScriptedIconLesson::LessonActionMap.Insert( "add", LESSON_ACTION_ADD ); + CScriptedIconLesson::LessonActionMap.Insert( "subtract", LESSON_ACTION_SUBTRACT ); + CScriptedIconLesson::LessonActionMap.Insert( "multiply", LESSON_ACTION_MULTIPLY ); + CScriptedIconLesson::LessonActionMap.Insert( "is", LESSON_ACTION_IS ); + CScriptedIconLesson::LessonActionMap.Insert( "less than", LESSON_ACTION_LESS_THAN ); + CScriptedIconLesson::LessonActionMap.Insert( "has prefix", LESSON_ACTION_HAS_PREFIX ); + CScriptedIconLesson::LessonActionMap.Insert( "has bit", LESSON_ACTION_HAS_BIT ); + CScriptedIconLesson::LessonActionMap.Insert( "bit count is", LESSON_ACTION_BIT_COUNT_IS ); + CScriptedIconLesson::LessonActionMap.Insert( "bit count less than", LESSON_ACTION_BIT_COUNT_LESS_THAN ); + + CScriptedIconLesson::LessonActionMap.Insert( "get distance", LESSON_ACTION_GET_DISTANCE ); + CScriptedIconLesson::LessonActionMap.Insert( "get angular distance", LESSON_ACTION_GET_ANGULAR_DISTANCE ); + CScriptedIconLesson::LessonActionMap.Insert( "get player display name", LESSON_ACTION_GET_PLAYER_DISPLAY_NAME ); + CScriptedIconLesson::LessonActionMap.Insert( "classname is", LESSON_ACTION_CLASSNAME_IS ); + CScriptedIconLesson::LessonActionMap.Insert( "modelname is", LESSON_ACTION_MODELNAME_IS ); + CScriptedIconLesson::LessonActionMap.Insert( "team is", LESSON_ACTION_TEAM_IS ); + CScriptedIconLesson::LessonActionMap.Insert( "health less than", LESSON_ACTION_HEALTH_LESS_THAN ); + CScriptedIconLesson::LessonActionMap.Insert( "health percentage less than", LESSON_ACTION_HEALTH_PERCENTAGE_LESS_THAN ); + CScriptedIconLesson::LessonActionMap.Insert( "get active weapon", LESSON_ACTION_GET_ACTIVE_WEAPON ); + CScriptedIconLesson::LessonActionMap.Insert( "weapon is", LESSON_ACTION_WEAPON_IS ); + CScriptedIconLesson::LessonActionMap.Insert( "weapon has", LESSON_ACTION_WEAPON_HAS ); + CScriptedIconLesson::LessonActionMap.Insert( "get active weapon slot", LESSON_ACTION_GET_ACTIVE_WEAPON_SLOT ); + CScriptedIconLesson::LessonActionMap.Insert( "get weapon slot", LESSON_ACTION_GET_WEAPON_SLOT ); + CScriptedIconLesson::LessonActionMap.Insert( "get weapon in slot", LESSON_ACTION_GET_WEAPON_IN_SLOT ); + CScriptedIconLesson::LessonActionMap.Insert( "clip percentage less than", LESSON_ACTION_CLIP_PERCENTAGE_LESS_THAN); + CScriptedIconLesson::LessonActionMap.Insert( "weapon ammo low", LESSON_ACTION_WEAPON_AMMO_LOW ); + CScriptedIconLesson::LessonActionMap.Insert( "weapon ammo full", LESSON_ACTION_WEAPON_AMMO_FULL ); + CScriptedIconLesson::LessonActionMap.Insert( "weapon ammo empty", LESSON_ACTION_WEAPON_AMMO_EMPTY ); + CScriptedIconLesson::LessonActionMap.Insert( "weapon can use", LESSON_ACTION_WEAPON_CAN_USE ); + CScriptedIconLesson::LessonActionMap.Insert( "use target is", LESSON_ACTION_USE_TARGET_IS ); + CScriptedIconLesson::LessonActionMap.Insert( "get use target", LESSON_ACTION_GET_USE_TARGET ); + CScriptedIconLesson::LessonActionMap.Insert( "get potential use target", LESSON_ACTION_GET_POTENTIAL_USE_TARGET ); + + // Add mod actions to the map + //Mod_PreReadLessonsFromFile(); +} diff --git a/src/game/client/c_baselesson.h b/src/game/client/c_baselesson.h new file mode 100644 index 00000000..b413d282 --- /dev/null +++ b/src/game/client/c_baselesson.h @@ -0,0 +1,460 @@ +//========= Copyright © 1996-2008, Valve Corporation, All rights reserved. ============// +// +// Purpose: Client handler for instruction players how to play +// +//=============================================================================// + +#ifndef _C_BASELESSON_H_ +#define _C_BASELESSON_H_ + + +#include "GameEventListener.h" +#include "hud_locator_target.h" + + +#define DECLARE_LESSON( _lessonClassName, _baseLessonClassName ) \ + typedef _baseLessonClassName BaseClass;\ + typedef _lessonClassName ThisClass;\ + _lessonClassName( const char *pchName, bool bIsDefaultHolder, bool bIsOpenOpportunity )\ + : _baseLessonClassName( pchName, bIsDefaultHolder, bIsOpenOpportunity )\ + {\ + Init();\ + } + + +enum LessonInstanceType +{ + LESSON_INSTANCE_MULTIPLE, + LESSON_INSTANCE_SINGLE_OPEN, + LESSON_INSTANCE_FIXED_REPLACE, + LESSON_INSTANCE_SINGLE_ACTIVE, + + LESSON_INSTANCE_TYPE_TOTAL +}; + + +// This is used to solve a problem where bots can take the place of a player, where on or the other don't have valid entities on the client at the same time +#define MAX_DELAYED_PLAYER_SWAPS 8 + +struct delayed_player_swap_t +{ + CHandle *phHandleToChange; + int iNewUserID; + + delayed_player_swap_t( void ) + { + phHandleToChange = NULL; + iNewUserID = -1; + } +}; + + +abstract_class CBaseLesson : public CGameEventListener +{ +public: + CBaseLesson( const char *pchName, bool bIsDefaultHolder, bool bIsOpenOpportunity ); + virtual ~CBaseLesson( void ); + + void AddPrerequisite( const char *pchLessonName ); + + const CGameInstructorSymbol& GetNameSymbol( void ) const { return m_stringName; } + const char * GetName( void ) const { return m_stringName.String(); } + int GetPriority( void ) const { return m_iPriority; } + const char * GetCloseReason( void ) const { return m_stringCloseReason.String(); } + void SetCloseReason( const char *pchReason ) { m_stringCloseReason = pchReason; } + + CBaseLesson* GetRoot( void ) const { return m_pRoot; } + void SetRoot( CBaseLesson *pRoot ); + const CUtlVector < CBaseLesson * >* GetChildren( void ) const { return &m_OpenOpportunities; } + + float GetInitTime( void ) { return m_fInitTime; } + void SetStartTime( void ) { m_fStartTime = gpGlobals->curtime; } + void ResetStartTime( void ) { m_fStartTime = 0.0f; m_bHasPlayedSound = false; } + + bool ShouldShowSpew( void ); + bool NoPriority( void ) const; + bool IsDefaultHolder( void ) const { return m_bIsDefaultHolder; } + bool IsOpenOpportunity( void ) const { return m_bIsOpenOpportunity; } + bool IsLocked( void ) const; + bool CanOpenWhenDead( void ) const { return m_bCanOpenWhenDead; } + bool IsInstructing( void ) const { return ( m_fStartTime > 0.0f ); } + bool IsLearned( void ) const; + bool PrerequisitesHaveBeenMet( void ) const; + bool IsTimedOut( void ); + + int InstanceType( void ) const { return m_iInstanceType; } + const CGameInstructorSymbol& GetReplaceKeySymbol( void ) const { return m_stringReplaceKey; } + const char* GetReplaceKey( void ) const { return m_stringReplaceKey.String(); } + int GetFixedInstancesMax( void ) const { return m_iFixedInstancesMax; } + bool ShouldReplaceOnlyWhenStopped( void ) const { return m_bReplaceOnlyWhenStopped; } + void SetInstanceActive( bool bInstanceActive ) { m_bInstanceActive = bInstanceActive; } + bool IsInstanceActive( void ) const { return m_bInstanceActive; } + + void ResetDisplaysAndSuccesses( void ); + bool IncDisplayCount( void ); + bool IncSuccessCount( void ); + void SetDisplayCount( int iDisplayCount ) { m_iDisplayCount = iDisplayCount; } + void SetSuccessCount( int iSuccessCount ) { m_iSuccessCount = iSuccessCount; } + int GetDisplayCount( void ) const { return m_iDisplayCount; } + int GetSuccessCount( void ) const { return m_iSuccessCount; } + int GetDisplayLimit( void ) const { return m_iDisplayLimit; } + int GetSuccessLimit( void ) const { return m_iSuccessLimit; } + + void Init( void ); // NOT virtual, each constructor calls their own + virtual void InitPrerequisites( void ) {}; + virtual void Start( void ) = 0; + virtual void Stop( void ) = 0; + virtual void OnOpen( void ) {}; + virtual void Update( void ) {}; + virtual void UpdateInactive( void ) {}; + + virtual bool ShouldDisplay( void ) const { return true; } + virtual bool IsVisible( void ) const { return true; } + virtual bool WasDisplayed( void ) const { return m_bWasDisplayed ? true : false; } + virtual void SwapOutPlayers( int iOldUserID, int iNewUserID ) {} + virtual void TakePlaceOf( CBaseLesson *pLesson ); + + const char *GetGroup() { return m_szLessonGroup.String(); } + void SetEnabled( bool bEnabled ) { m_bDisabled = !bEnabled; } + +protected: + void MarkSucceeded( void ); + void CloseOpportunity( const char *pchReason ); + bool DoDelayedPlayerSwaps( void ) const; + +private: + + CBaseLesson *m_pRoot; + CUtlVector < CBaseLesson * > m_OpenOpportunities; + CUtlVector < const CBaseLesson * > m_Prerequisites; + + CGameInstructorSymbol m_stringCloseReason; + CGameInstructorSymbol m_stringName; + + bool m_bInstanceActive : 1; + bool m_bSuccessCounted : 1; + bool m_bIsDefaultHolder : 1; + bool m_bIsOpenOpportunity : 1; + +protected: + LessonInstanceType m_iInstanceType; + + int m_iPriority; + CGameInstructorSymbol m_stringReplaceKey; + int m_iFixedInstancesMax; + bool m_bReplaceOnlyWhenStopped; + int m_iTeam; + bool m_bOnlyKeyboard; + bool m_bOnlyGamepad; + + int m_iDisplayLimit; + int m_iDisplayCount; + bool m_bWasDisplayed; + int m_iSuccessLimit; + int m_iSuccessCount; + + float m_fLockDuration; + float m_fTimeout; + float m_fInitTime; + float m_fStartTime; + float m_fLockTime; + float m_fUpdateInterval; + bool m_bHasPlayedSound; + + CGameInstructorSymbol m_szStartSound; + CGameInstructorSymbol m_szLessonGroup; + + bool m_bCanOpenWhenDead; + bool m_bBumpWithTimeoutWhenLearned; + bool m_bCanTimeoutWhileInactive; + bool m_bDisabled; + + // Right now we can only queue up 4 swaps... + // this number can be increased if more entity handle scripted variables are added + mutable delayed_player_swap_t m_pDelayedPlayerSwap[ MAX_DELAYED_PLAYER_SWAPS ]; + mutable int m_iNumDelayedPlayerSwaps; + +public: + + // Colors for console spew in verbose mode + static Color m_rgbaVerboseHeader; + static Color m_rgbaVerbosePlain; + static Color m_rgbaVerboseName; + static Color m_rgbaVerboseOpen; + static Color m_rgbaVerboseClose; + static Color m_rgbaVerboseSuccess; + static Color m_rgbaVerboseUpdate; +}; + + +class CTextLesson : public CBaseLesson +{ +public: + DECLARE_LESSON( CTextLesson, CBaseLesson ); + + void Init( void ); // NOT virtual, each constructor calls their own + virtual void Start( void ); + virtual void Stop( void ); + +protected: + + CGameInstructorSymbol m_szDisplayText; + CGameInstructorSymbol m_szDisplayParamText; + CGameInstructorSymbol m_szBinding; + CGameInstructorSymbol m_szGamepadBinding; +}; + + +class CIconLesson : public CTextLesson +{ +public: + DECLARE_LESSON( CIconLesson, CTextLesson ); + + void Init( void ); // NOT virtual, each constructor calls their own + virtual void Start( void ); + virtual void Stop( void ); + virtual void Update( void ); + virtual void UpdateInactive( void ); + + virtual bool ShouldDisplay( void ) const; + virtual bool IsVisible( void ) const; + virtual void SwapOutPlayers( int iOldUserID, int iNewUserID ); + virtual void TakePlaceOf( CBaseLesson *pLesson ); + + void SetLocatorBinding( CLocatorTarget * pLocatorTarget ); + + const char *GetCaptionColorString() { return m_szCaptionColor.String(); } + + bool IsPresentComplete( void ); + void PresentStart( void ); + void PresentEnd( void ); + +private: + virtual void UpdateLocatorTarget( CLocatorTarget *pLocatorTarget, C_BaseEntity *pIconTarget ); + +#ifdef MAPBASE + Vector GetIconTargetPosition( C_BaseEntity *pIconTarget ); +#endif + +protected: + CHandle m_hIconTarget; + CGameInstructorSymbol m_szVguiTargetName; + CGameInstructorSymbol m_szVguiTargetLookup; + int m_nVguiTargetEdge; + float m_flUpOffset; + float m_flRelativeUpOffset; + float m_fFixedPositionX; + float m_fFixedPositionY; + + int m_hLocatorTarget; + int m_iFlags; + + float m_fRange; + float m_fCurrentDistance; + float m_fOnScreenStartTime; + float m_fUpdateDistanceTime; + + CGameInstructorSymbol m_szOnscreenIcon; + CGameInstructorSymbol m_szOffscreenIcon; + CGameInstructorSymbol m_szCaptionColor; + + bool m_bFixedPosition; + bool m_bNoIconTarget; + bool m_bAllowNodrawTarget; + bool m_bVisible; + bool m_bShowWhenOccluded; + bool m_bNoOffscreen; + bool m_bForceCaption; + +#ifdef MAPBASE + int m_iIconTargetPos; + + enum + { + ICON_TARGET_EYE_POSITION, + ICON_TARGET_ORIGIN, + ICON_TARGET_CENTER, + }; + + CGameInstructorSymbol m_szHudHint; +#endif +}; + +enum LessonAction +{ + LESSON_ACTION_NONE, + + LESSON_ACTION_SCOPE_IN, + LESSON_ACTION_SCOPE_OUT, + LESSON_ACTION_CLOSE, + LESSON_ACTION_SUCCESS, + LESSON_ACTION_LOCK, + LESSON_ACTION_PRESENT_COMPLETE, + LESSON_ACTION_PRESENT_START, + LESSON_ACTION_PRESENT_END, + + LESSON_ACTION_REFERENCE_OPEN, + + LESSON_ACTION_SET, + LESSON_ACTION_ADD, + LESSON_ACTION_SUBTRACT, + LESSON_ACTION_MULTIPLY, + LESSON_ACTION_IS, + LESSON_ACTION_LESS_THAN, + LESSON_ACTION_HAS_PREFIX, + LESSON_ACTION_HAS_BIT, + LESSON_ACTION_BIT_COUNT_IS, + LESSON_ACTION_BIT_COUNT_LESS_THAN, + + LESSON_ACTION_GET_DISTANCE, + LESSON_ACTION_GET_ANGULAR_DISTANCE, + LESSON_ACTION_GET_PLAYER_DISPLAY_NAME, + LESSON_ACTION_CLASSNAME_IS, + LESSON_ACTION_MODELNAME_IS, + LESSON_ACTION_TEAM_IS, + LESSON_ACTION_HEALTH_LESS_THAN, + LESSON_ACTION_HEALTH_PERCENTAGE_LESS_THAN, + LESSON_ACTION_GET_ACTIVE_WEAPON, + LESSON_ACTION_WEAPON_IS, + LESSON_ACTION_WEAPON_HAS, + LESSON_ACTION_GET_ACTIVE_WEAPON_SLOT, + LESSON_ACTION_GET_WEAPON_SLOT, + LESSON_ACTION_GET_WEAPON_IN_SLOT, + LESSON_ACTION_CLIP_PERCENTAGE_LESS_THAN, + LESSON_ACTION_WEAPON_AMMO_LOW, + LESSON_ACTION_WEAPON_AMMO_FULL, + LESSON_ACTION_WEAPON_AMMO_EMPTY, + LESSON_ACTION_WEAPON_CAN_USE, + LESSON_ACTION_USE_TARGET_IS, + LESSON_ACTION_GET_USE_TARGET, + LESSON_ACTION_GET_POTENTIAL_USE_TARGET, + + // Enum continued in Mod_LessonAction + LESSON_ACTION_MOD_START, +}; + +struct LessonElement_t +{ + int iVariable; + int iParamVarIndex; + int iAction; + _fieldtypes paramType; + CGameInstructorSymbol szParam; + bool bNot : 1; + bool bOptionalParam : 1; + + LessonElement_t( int p_iVariable, int p_iAction, bool p_bNot, bool p_bOptionalParam, const char *pchParam, int p_iParamVarIndex, _fieldtypes p_paramType ) + { + iVariable = p_iVariable; + iAction = p_iAction; + bNot = p_bNot; + bOptionalParam = p_bOptionalParam; + szParam = pchParam; + iParamVarIndex = p_iParamVarIndex; + paramType = p_paramType; + } + + LessonElement_t( const LessonElement_t &p_LessonElement ) + { + iVariable = p_LessonElement.iVariable; + iAction = p_LessonElement.iAction; + bNot = p_LessonElement.bNot; + bOptionalParam = p_LessonElement.bOptionalParam; + szParam = p_LessonElement.szParam; + iParamVarIndex = p_LessonElement.iParamVarIndex; + paramType = p_LessonElement.paramType; + } +}; + +struct LessonEvent_t +{ + CUtlVector< LessonElement_t > elements; + CGameInstructorSymbol szEventName; +}; + +class CScriptedIconLesson : public CIconLesson +{ +public: + DECLARE_LESSON( CScriptedIconLesson, CIconLesson ) + + virtual ~CScriptedIconLesson( void ); + + static void PreReadLessonsFromFile( void ); + static void Mod_PreReadLessonsFromFile( void ); + + void Init( void ); // NOT virtual, each constructor calls their own + virtual void InitPrerequisites( void ); + virtual void OnOpen( void ); + virtual void Update( void ); + + virtual void SwapOutPlayers( int iOldUserID, int iNewUserID ); + + virtual void FireGameEvent( IGameEvent *event ); + virtual void ProcessOpenGameEvents( const CScriptedIconLesson *pRootLesson, const char *name, IGameEvent *event ); + virtual void ProcessCloseGameEvents( const CScriptedIconLesson *pRootLesson, const char *name, IGameEvent *event ); + virtual void ProcessSuccessGameEvents( const CScriptedIconLesson *pRootLesson, const char *name, IGameEvent *event ); + + CUtlVector< LessonEvent_t >& GetOpenEvents( void ) { return m_OpenEvents; } + CUtlVector< LessonEvent_t >& GetCloseEvents( void ) { return m_CloseEvents; } + CUtlVector< LessonEvent_t >& GetSuccessEvents( void ) { return m_SuccessEvents; } + CUtlVector< LessonEvent_t >& GetOnOpenEvents( void ) { return m_OnOpenEvents; } + CUtlVector< LessonEvent_t >& GetUpdateEvents( void ) { return m_UpdateEvents; } + + bool ProcessElements( IGameEvent *event, const CUtlVector< LessonElement_t > *pElements ); + +private: + void InitElementsFromKeys( CUtlVector< LessonElement_t > *pLessonElements, KeyValues *pKey ); + void InitElementsFromElements( CUtlVector< LessonElement_t > *pLessonElements, const CUtlVector< LessonElement_t > *pLessonElements2 ); + + void InitFromKeys( KeyValues *pKey ); + + bool ProcessElement( IGameEvent *event, const LessonElement_t *pLessonElement, bool bInFailedScope ); + + bool ProcessElementAction( int iAction, bool bNot, const char *pchVarName, float &bVar, const CGameInstructorSymbol *pchParamName, float fParam ); + bool ProcessElementAction( int iAction, bool bNot, const char *pchVarName, int &bVar, const CGameInstructorSymbol *pchParamName, float fParam ); + bool ProcessElementAction( int iAction, bool bNot, const char *pchVarName, bool &bVar, const CGameInstructorSymbol *pchParamName, float fParam ); + bool ProcessElementAction( int iAction, bool bNot, const char *pchVarName, EHANDLE &hVar, const CGameInstructorSymbol *pchParamName, float fParam, C_BaseEntity *pParam, const char *pchParam ); + bool ProcessElementAction( int iAction, bool bNot, const char *pchVarName, CGameInstructorSymbol *pchVar, const CGameInstructorSymbol *pchParamName, const char *pchParam ); + + // Implemented per mod so they can have custom actions + bool Mod_ProcessElementAction( int iAction, bool bNot, const char *pchVarName, EHANDLE &hVar, const CGameInstructorSymbol *pchParamName, float fParam, C_BaseEntity *pParam, const char *pchParam, bool &bModHandled ); + + LessonEvent_t * AddOpenEvent( void ); + LessonEvent_t * AddCloseEvent( void ); + LessonEvent_t * AddSuccessEvent( void ); + LessonEvent_t * AddOnOpenEvent( void ); + LessonEvent_t * AddUpdateEvent( void ); + +private: + static CUtlDict< int, int > LessonActionMap; + + EHANDLE m_hLocalPlayer; + float m_fOutput; + CHandle m_hEntity1; + CHandle m_hEntity2; + CGameInstructorSymbol m_szString1; + CGameInstructorSymbol m_szString2; + int m_iInteger1; + int m_iInteger2; + float m_fFloat1; + float m_fFloat2; + + CUtlVector< CGameInstructorSymbol > m_PrerequisiteNames; + CUtlVector< LessonEvent_t > m_OpenEvents; + CUtlVector< LessonEvent_t > m_CloseEvents; + CUtlVector< LessonEvent_t > m_SuccessEvents; + CUtlVector< LessonEvent_t > m_OnOpenEvents; + CUtlVector< LessonEvent_t > m_UpdateEvents; + + float m_fUpdateEventTime; + CScriptedIconLesson *m_pDefaultHolder; + + int m_iScopeDepth; + + // Need this to get offsets to scripted variables + friend class LessonVariableInfo; + friend int LessonActionFromString( const char *pchName ); +}; + + +#endif // _C_BASELESSON_H_ diff --git a/src/game/client/c_baseplayer.cpp b/src/game/client/c_baseplayer.cpp index b5ce6728..a28c7437 100644 --- a/src/game/client/c_baseplayer.cpp +++ b/src/game/client/c_baseplayer.cpp @@ -58,6 +58,10 @@ #include "econ_wearable.h" #endif +#ifdef MAPBASE +#include "viewrender.h" +#endif + // NVNT haptics system interface #include "haptics/ihaptics.h" @@ -136,6 +140,16 @@ void RecvProxy_LocalVelocityZ( const CRecvProxyData *pData, void *pStruct, void void RecvProxy_ObserverTarget( const CRecvProxyData *pData, void *pStruct, void *pOut ); void RecvProxy_ObserverMode ( const CRecvProxyData *pData, void *pStruct, void *pOut ); +#ifdef MAPBASE +// Needs to shift bits back +void RecvProxy_ShiftPlayerSpawnflags( const CRecvProxyData *pData, void *pStruct, void *pOut ) +{ + C_BasePlayer *pPlayer = (C_BasePlayer *)pStruct; + + pPlayer->m_spawnflags = (pData->m_Value.m_Int) << 16; +} +#endif + // -------------------------------------------------------------------------------- // // RecvTable for CPlayerState. // -------------------------------------------------------------------------------- // @@ -184,6 +198,11 @@ BEGIN_RECV_TABLE_NOBASE( CPlayerLocalData, DT_Local ) // 3d skybox data RecvPropInt(RECVINFO(m_skybox3d.scale)), RecvPropVector(RECVINFO(m_skybox3d.origin)), +#ifdef MAPBASE + RecvPropVector(RECVINFO(m_skybox3d.angles)), + RecvPropEHandle(RECVINFO(m_skybox3d.skycamera)), + RecvPropInt( RECVINFO( m_skybox3d.skycolor ), 0, RecvProxy_IntToColor32 ), +#endif RecvPropInt(RECVINFO(m_skybox3d.area)), // 3d skybox fog data @@ -196,6 +215,9 @@ BEGIN_RECV_TABLE_NOBASE( CPlayerLocalData, DT_Local ) RecvPropFloat( RECVINFO( m_skybox3d.fog.start ) ), RecvPropFloat( RECVINFO( m_skybox3d.fog.end ) ), RecvPropFloat( RECVINFO( m_skybox3d.fog.maxdensity ) ), +#ifdef MAPBASE + RecvPropFloat( RECVINFO( m_skybox3d.fog.farz ) ), +#endif // fog data RecvPropEHandle( RECVINFO( m_PlayerFog.m_hCtrl ) ), @@ -214,6 +236,14 @@ BEGIN_RECV_TABLE_NOBASE( CPlayerLocalData, DT_Local ) RecvPropInt( RECVINFO( m_audio.entIndex ) ), RecvPropString( RECVINFO( m_szScriptOverlayMaterial ) ), + + //Tony; tonemap stuff! -- TODO! Optimize this with bit sizes from env_tonemap_controller. + RecvPropFloat ( RECVINFO( m_TonemapParams.m_flTonemapScale ) ), + RecvPropFloat ( RECVINFO( m_TonemapParams.m_flTonemapRate ) ), + RecvPropFloat ( RECVINFO( m_TonemapParams.m_flBloomScale ) ), + + RecvPropFloat ( RECVINFO( m_TonemapParams.m_flAutoExposureMin ) ), + RecvPropFloat ( RECVINFO( m_TonemapParams.m_flAutoExposureMax ) ), END_RECV_TABLE() // -------------------------------------------------------------------------------- // @@ -256,6 +286,15 @@ END_RECV_TABLE() RecvPropInt ( RECVINFO( m_nWaterLevel ) ), RecvPropFloat ( RECVINFO( m_flLaggedMovementValue )), +#ifdef MAPBASE + // Transmitted from the server for internal player spawnflags. + // See baseplayer_shared.h for more details. + RecvPropInt ( RECVINFO( m_spawnflags ), 0, RecvProxy_ShiftPlayerSpawnflags ), + + RecvPropBool ( RECVINFO( m_bDrawPlayerModelExternally ) ), + RecvPropBool ( RECVINFO( m_bInTriggerFall ) ), +#endif + END_RECV_TABLE() @@ -304,6 +343,11 @@ END_RECV_TABLE() RecvPropString( RECVINFO(m_szLastPlaceName) ), +#ifdef MAPBASE // From Alien Swarm SDK + RecvPropEHandle( RECVINFO( m_hPostProcessCtrl ) ), // Send to everybody - for spectating + RecvPropEHandle( RECVINFO( m_hColorCorrectionCtrl ) ), // Send to everybody - for spectating +#endif + #if defined USES_ECON_ITEMS RecvPropUtlVector( RECVINFO_UTLVECTOR( m_hMyWearables ), MAX_WEARABLES_SENT_FROM_SERVER, RecvPropEHandle(NULL, 0, 0) ), #endif @@ -410,7 +454,10 @@ BEGIN_PREDICTION_DATA( C_BasePlayer ) END_PREDICTION_DATA() +// link this in each derived player class, like the server!! +#if 0 LINK_ENTITY_TO_CLASS( player, C_BasePlayer ); +#endif // -------------------------------------------------------------------------------- // // Functions. @@ -464,6 +511,13 @@ C_BasePlayer::~C_BasePlayer() if ( this == s_pLocalPlayer ) { s_pLocalPlayer = NULL; + +#ifdef MAPBASE_VSCRIPT + if ( g_pScriptVM ) + { + g_pScriptVM->SetValue( "player", SCRIPT_VARIANT_NULL ); + } +#endif } delete m_pFlashlight; @@ -834,6 +888,14 @@ void C_BasePlayer::PostDataUpdate( DataUpdateType_t updateType ) // changed level, which would cause the snd_soundmixer to be left modified. ConVar *pVar = (ConVar *)cvar->FindVar( "snd_soundmixer" ); pVar->Revert(); + +#ifdef MAPBASE_VSCRIPT + // Moved here from LevelInitPostEntity, which is executed before local player is spawned. + if ( g_pScriptVM ) + { + g_pScriptVM->SetValue( "player", GetScriptInstance() ); + } +#endif } } @@ -996,6 +1058,16 @@ void C_BasePlayer::OnRestore() input->ClearInputButton( IN_ATTACK | IN_ATTACK2 ); // GetButtonBits() has to be called for the above to take effect input->GetButtonBits( 0 ); + +#ifdef MAPBASE_VSCRIPT + // HACK: (03/25/09) Then the player goes across a transition it doesn't spawn and register + // it's instance. We're hacking around this for now, but this will go away when we get around to + // having entities cross transitions and keep their script state. + if ( g_pScriptVM ) + { + g_pScriptVM->SetValue( "player", GetScriptInstance() ); + } +#endif } // For ammo history icons to current value so they don't flash on level transtions @@ -1105,14 +1177,32 @@ void C_BasePlayer::DetermineVguiInputMode( CUserCmd *pCmd ) // If we're in vgui mode *and* we're holding down mouse buttons, // stay in vgui mode even if we're outside the screen bounds +#ifdef VGUI_SCREEN_FIX + if (m_pCurrentVguiScreen.Get() && (pCmd->buttons & (IN_ATTACK | IN_ATTACK2 | IN_VALIDVGUIINPUT))) + { + SetVGuiScreenButtonState( m_pCurrentVguiScreen.Get(), pCmd->buttons ); + + // Kill all attack inputs if we're in vgui screen mode + pCmd->buttons &= ~(IN_ATTACK | IN_ATTACK2 | IN_VALIDVGUIINPUT); +#ifdef MAPBASE + pCmd->buttons |= IN_VGUIMODE; +#endif // MAPBASE + return; + } +#else if (m_pCurrentVguiScreen.Get() && (pCmd->buttons & (IN_ATTACK | IN_ATTACK2)) ) { SetVGuiScreenButtonState( m_pCurrentVguiScreen.Get(), pCmd->buttons ); // Kill all attack inputs if we're in vgui screen mode pCmd->buttons &= ~(IN_ATTACK | IN_ATTACK2); +#ifdef MAPBASE + pCmd->buttons &= ~(IN_USE | IN_ATTACK3); + pCmd->buttons |= IN_VGUIMODE; +#endif // MAPBASE return; } +#endif // We're not in vgui input mode if we're moving, or have hit a key // that will make us move... @@ -1174,6 +1264,10 @@ void C_BasePlayer::DetermineVguiInputMode( CUserCmd *pCmd ) // Kill all attack inputs if we're in vgui screen mode pCmd->buttons &= ~(IN_ATTACK | IN_ATTACK2); +#ifdef MAPBASE + pCmd->buttons &= ~(IN_USE | IN_ATTACK3); + pCmd->buttons |= IN_VGUIMODE; +#endif // MAPBASE } } @@ -1234,7 +1328,12 @@ bool C_BasePlayer::CreateMove( float flInputSampleTime, CUserCmd *pCmd ) m_vecOldViewAngles = pCmd->viewangles; // Check to see if we're in vgui input mode... +#ifdef VGUI_SCREEN_FIX + if(pCmd->buttons & IN_VALIDVGUIINPUT) + DetermineVguiInputMode( pCmd ); +#else DetermineVguiInputMode( pCmd ); +#endif return true; } @@ -1330,6 +1429,10 @@ void C_BasePlayer::AddEntity( void ) // Add in lighting effects CreateLightEffects(); + +#ifdef MAPBASE + SetLocalAnglesDim( X_INDEX, 0 ); +#endif } extern float UTIL_WaterLevel( const Vector &position, float minz, float maxz ); @@ -1436,11 +1539,35 @@ bool C_BasePlayer::ShouldInterpolate() bool C_BasePlayer::ShouldDraw() { +#ifdef MAPBASE + // We have to "always draw" a player with m_bDrawPlayerModelExternally in order to show up in whatever rendering list all of the views use, + // but we can't put this in ShouldDrawThisPlayer() because we would have no way of knowing if it stomps the other checks that draw the player model anyway. + // As a result, we have to put it here in the central ShouldDraw() function. DrawModel() makes sure we only draw in non-main views and nothing's drawing the model anyway. + return (ShouldDrawThisPlayer() || m_bDrawPlayerModelExternally) && BaseClass::ShouldDraw(); +#else return ShouldDrawThisPlayer() && BaseClass::ShouldDraw(); +#endif } int C_BasePlayer::DrawModel( int flags ) { +#ifdef MAPBASE + if (m_bDrawPlayerModelExternally) + { + // Draw the player in any view except the main or "intro" view, both of which are default first-person views. + // HACKHACK: Also don't draw in shadow depth textures if the player's flashlight is on, as that causes the playermodel to block it. + view_id_t viewID = CurrentViewID(); + if (viewID == VIEW_MAIN || viewID == VIEW_INTRO_CAMERA || (viewID == VIEW_SHADOW_DEPTH_TEXTURE && IsEffectActive(EF_DIMLIGHT))) + { + // Make sure the player model wouldn't draw anyway... + if (!ShouldDrawThisPlayer()) + return 0; + } + + return BaseClass::DrawModel( flags ); + } +#endif + #ifndef PORTAL // In Portal this check is already performed as part of // C_Portal_Player::DrawModel() @@ -1449,9 +1576,42 @@ int C_BasePlayer::DrawModel( int flags ) return 0; } #endif + return BaseClass::DrawModel( flags ); } +#ifdef MAPBASE +ConVar cl_player_allow_thirdperson_projtex( "cl_player_allow_thirdperson_projtex", "1", FCVAR_NONE, "Allows players to receive projected textures if they're non-local or in third person." ); +ConVar cl_player_allow_thirdperson_rttshadows( "cl_player_allow_thirdperson_rttshadows", "0", FCVAR_NONE, "Allows players to cast RTT shadows if they're non-local or in third person." ); +ConVar cl_player_allow_firstperson_projtex( "cl_player_allow_firstperson_projtex", "1", FCVAR_NONE, "Allows players to receive projected textures even if they're in first person." ); +ConVar cl_player_allow_firstperson_rttshadows( "cl_player_allow_firstperson_rttshadows", "0", FCVAR_NONE, "Allows players to cast RTT shadows even if they're in first person." ); + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +ShadowType_t C_BasePlayer::ShadowCastType() +{ + if ( (!IsLocalPlayer() || ShouldDraw()) ? !cl_player_allow_thirdperson_rttshadows.GetBool() : !cl_player_allow_firstperson_rttshadows.GetBool() ) + return SHADOWS_NONE; + + if ( !IsVisible() ) + return SHADOWS_NONE; + + return SHADOWS_RENDER_TO_TEXTURE_DYNAMIC; +} + +//----------------------------------------------------------------------------- +// Should this object receive shadows? +//----------------------------------------------------------------------------- +bool C_BasePlayer::ShouldReceiveProjectedTextures( int flags ) +{ + if ( (!IsLocalPlayer() || ShouldDraw()) ? !cl_player_allow_thirdperson_projtex.GetBool() : !cl_player_allow_firstperson_projtex.GetBool() ) + return false; + + return BaseClass::ShouldReceiveProjectedTextures( flags ); +} +#endif + //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- @@ -1573,7 +1733,14 @@ void C_BasePlayer::CalcChaseCamView(Vector& eyeOrigin, QAngle& eyeAngles, float& } } - if ( target && !target->IsPlayer() && target->IsNextBot() ) + // SDK TODO + if ( target && target->IsBaseTrain() ) + { + // if this is a train, we want to be back a little further so we can see more of it + flMaxDistance *= 2.5f; + m_flObserverChaseDistance = flMaxDistance; + } + else if ( target && !target->IsPlayer() && target->IsNextBot() ) { // if this is a boss, we want to be back a little further so we can see more of it flMaxDistance *= 2.5f; @@ -1906,6 +2073,12 @@ void C_BasePlayer::ThirdPersonSwitch( bool bThirdperson ) } } } + else + { + CBaseCombatWeapon *pWeapon = GetActiveWeapon(); + if ( pWeapon ) + pWeapon->ThirdPersonSwitch( bThirdperson ); + } } @@ -2869,6 +3042,24 @@ void C_BasePlayer::UpdateFogBlend( void ) } } +#ifdef MAPBASE // From Alien Swarm SDK +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +C_PostProcessController* C_BasePlayer::GetActivePostProcessController() const +{ + return m_hPostProcessCtrl.Get(); +} + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +C_ColorCorrection* C_BasePlayer::GetActiveColorCorrection() const +{ + return m_hColorCorrectionCtrl.Get(); +} +#endif + //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- diff --git a/src/game/client/c_baseplayer.h b/src/game/client/c_baseplayer.h index 11a7fabc..6515265c 100644 --- a/src/game/client/c_baseplayer.h +++ b/src/game/client/c_baseplayer.h @@ -23,6 +23,10 @@ #include "hintsystem.h" #include "SoundEmitterSystem/isoundemittersystembase.h" #include "c_env_fog_controller.h" +#ifdef MAPBASE // From Alien Swarm SDK +#include "c_postprocesscontroller.h" +#include "c_colorcorrection.h" +#endif #include "igameevents.h" #include "GameEventListener.h" @@ -37,6 +41,7 @@ class C_BaseViewModel; class C_FuncLadder; class CFlashlightEffect; class C_EconWearable; +class C_PostProcessController; extern int g_nKillCamMode; extern int g_nKillCamTarget1; @@ -183,7 +188,7 @@ public: // Flashlight void Flashlight( void ); - void UpdateFlashlight( void ); + virtual void UpdateFlashlight( void ); // Weapon selection code virtual bool IsAllowedToSwitchWeapons( void ) { return !IsObserver(); } @@ -202,6 +207,11 @@ public: void SetMaxSpeed( float flMaxSpeed ) { m_flMaxspeed = flMaxSpeed; } float MaxSpeed() const { return m_flMaxspeed; } +#ifdef MAPBASE + // See c_baseplayer.cpp + virtual ShadowType_t ShadowCastType(); + virtual bool ShouldReceiveProjectedTextures( int flags ); +#else // Should this object cast shadows? virtual ShadowType_t ShadowCastType() { return SHADOWS_NONE; } @@ -209,6 +219,7 @@ public: { return false; } +#endif bool IsLocalPlayer( void ) const; @@ -380,6 +391,11 @@ public: void UpdateFogController( void ); void UpdateFogBlend( void ); +#ifdef MAPBASE // From Alien Swarm SDK + C_PostProcessController* GetActivePostProcessController() const; + C_ColorCorrection* GetActiveColorCorrection() const; +#endif + float GetFOVTime( void ){ return m_flFOVTime; } virtual void OnAchievementAchieved( int iAchievement ) {} @@ -447,20 +463,35 @@ public: float m_flConstraintWidth; float m_flConstraintSpeedFactor; +#ifdef MAPBASE + // Transmitted from the server for internal player spawnflags. + // See baseplayer_shared.h for more details. + int m_spawnflags; + + inline bool HasSpawnFlags( int flags ) { return (m_spawnflags & flags) != 0; } + inline void RemoveSpawnFlags( int flags ) { m_spawnflags &= ~flags; } + inline void AddSpawnFlags( int flags ) { m_spawnflags |= flags; } + + // Allows the player's model to draw on non-main views, like monitors or mirrors. + bool m_bDrawPlayerModelExternally; + + bool m_bInTriggerFall; +#endif + protected: - void CalcPlayerView( Vector& eyeOrigin, QAngle& eyeAngles, float& fov ); - void CalcVehicleView(IClientVehicle *pVehicle, Vector& eyeOrigin, QAngle& eyeAngles, - float& zNear, float& zFar, float& fov ); + //Tony; made all of these virtual so mods can override. + virtual void CalcPlayerView( Vector& eyeOrigin, QAngle& eyeAngles, float& fov ); + virtual void CalcVehicleView(IClientVehicle *pVehicle, Vector& eyeOrigin, QAngle& eyeAngles, float& zNear, float& zFar, float& fov ); virtual void CalcObserverView( Vector& eyeOrigin, QAngle& eyeAngles, float& fov ); virtual Vector GetChaseCamViewOffset( CBaseEntity *target ); - void CalcChaseCamView( Vector& eyeOrigin, QAngle& eyeAngles, float& fov ); + virtual void CalcChaseCamView( Vector& eyeOrigin, QAngle& eyeAngles, float& fov ); virtual void CalcInEyeCamView( Vector& eyeOrigin, QAngle& eyeAngles, float& fov ); virtual float GetDeathCamInterpolationTime(); virtual void CalcDeathCamView( Vector& eyeOrigin, QAngle& eyeAngles, float& fov ); - void CalcRoamingView(Vector& eyeOrigin, QAngle& eyeAngles, float& fov); + virtual void CalcRoamingView(Vector& eyeOrigin, QAngle& eyeAngles, float& fov); virtual void CalcFreezeCamView( Vector& eyeOrigin, QAngle& eyeAngles, float& fov ); // Check to see if we're in vgui input mode... @@ -633,6 +664,11 @@ private: // One for left and one for right side of step StepSoundCache_t m_StepSoundCache[ 2 ]; +#ifdef MAPBASE // From Alien Swarm SDK + CNetworkHandle( C_PostProcessController, m_hPostProcessCtrl ); // active postprocessing controller + CNetworkHandle( C_ColorCorrection, m_hColorCorrectionCtrl ); // active FXVolume color correction +#endif + public: const char *GetLastKnownPlaceName( void ) const { return m_szLastPlaceName; } // return the last nav place name the player occupied diff --git a/src/game/client/c_baseviewmodel.cpp b/src/game/client/c_baseviewmodel.cpp index 07a1e71c..4b3c22bb 100644 --- a/src/game/client/c_baseviewmodel.cpp +++ b/src/game/client/c_baseviewmodel.cpp @@ -35,7 +35,7 @@ // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" -#ifdef CSTRIKE_DLL +#if defined(CSTRIKE_DLL) || defined (MAPBASE) ConVar cl_righthand( "cl_righthand", "1", FCVAR_ARCHIVE, "Use right-handed view models." ); #endif @@ -197,7 +197,7 @@ bool C_BaseViewModel::Interpolate( float currentTime ) bool C_BaseViewModel::ShouldFlipViewModel() { -#ifdef CSTRIKE_DLL +#if defined(CSTRIKE_DLL) || defined (MAPBASE) // If cl_righthand is set, then we want them all right-handed. CBaseCombatWeapon *pWeapon = m_hWeapon.Get(); if ( pWeapon ) diff --git a/src/game/client/c_colorcorrection.cpp b/src/game/client/c_colorcorrection.cpp index 6960031d..12e29768 100644 --- a/src/game/client/c_colorcorrection.cpp +++ b/src/game/client/c_colorcorrection.cpp @@ -6,6 +6,7 @@ //===========================================================================// #include "cbase.h" +#include "c_colorcorrection.h" #include "filesystem.h" #include "cdll_client_int.h" #include "colorcorrectionmgr.h" @@ -17,45 +18,27 @@ static ConVar mat_colcorrection_disableentities( "mat_colcorrection_disableentities", "0", FCVAR_NONE, "Disable map color-correction entities" ); - -//------------------------------------------------------------------------------ -// Purpose : Color correction entity with radial falloff -//------------------------------------------------------------------------------ -class C_ColorCorrection : public C_BaseEntity -{ -public: - DECLARE_CLASS( C_ColorCorrection, C_BaseEntity ); - - DECLARE_CLIENTCLASS(); - - C_ColorCorrection(); - virtual ~C_ColorCorrection(); - - void OnDataChanged(DataUpdateType_t updateType); - bool ShouldDraw(); - - void ClientThink(); - -private: - Vector m_vecOrigin; - - float m_minFalloff; - float m_maxFalloff; - float m_flCurWeight; - char m_netLookupFilename[MAX_PATH]; - - bool m_bEnabled; - - ClientCCHandle_t m_CCHandle; -}; +#ifdef MAPBASE // From Alien Swarm SDK +static ConVar mat_colcorrection_forceentitiesclientside( "mat_colcorrection_forceentitiesclientside", "0", FCVAR_CHEAT, "Forces color correction entities to be updated on the client" ); +#endif IMPLEMENT_CLIENTCLASS_DT(C_ColorCorrection, DT_ColorCorrection, CColorCorrection) RecvPropVector( RECVINFO(m_vecOrigin) ), RecvPropFloat( RECVINFO(m_minFalloff) ), RecvPropFloat( RECVINFO(m_maxFalloff) ), RecvPropFloat( RECVINFO(m_flCurWeight) ), +#ifdef MAPBASE // From Alien Swarm SDK + RecvPropFloat( RECVINFO(m_flMaxWeight) ), + RecvPropFloat( RECVINFO(m_flFadeInDuration) ), + RecvPropFloat( RECVINFO(m_flFadeOutDuration) ), +#endif RecvPropString( RECVINFO(m_netLookupFilename) ), RecvPropBool( RECVINFO(m_bEnabled) ), +#ifdef MAPBASE // From Alien Swarm SDK + RecvPropBool( RECVINFO(m_bMaster) ), + RecvPropBool( RECVINFO(m_bClientSide) ), + RecvPropBool( RECVINFO(m_bExclusive) ) +#endif END_RECV_TABLE() @@ -65,14 +48,43 @@ END_RECV_TABLE() //------------------------------------------------------------------------------ C_ColorCorrection::C_ColorCorrection() { +#ifdef MAPBASE // From Alien Swarm SDK + m_minFalloff = -1.0f; + m_maxFalloff = -1.0f; + m_flFadeInDuration = 0.0f; + m_flFadeOutDuration = 0.0f; + m_flCurWeight = 0.0f; + m_flMaxWeight = 1.0f; + m_netLookupFilename[0] = '\0'; + m_bEnabled = false; + m_bMaster = false; + m_bExclusive = false; +#endif m_CCHandle = INVALID_CLIENT_CCHANDLE; + +#ifdef MAPBASE // From Alien Swarm SDK + m_bFadingIn = false; + m_flFadeStartWeight = 0.0f; + m_flFadeStartTime = 0.0f; + m_flFadeDuration = 0.0f; +#endif } C_ColorCorrection::~C_ColorCorrection() { +#ifdef MAPBASE // From Alien Swarm SDK + g_pColorCorrectionMgr->RemoveColorCorrectionEntity( this, m_CCHandle ); +#else g_pColorCorrectionMgr->RemoveColorCorrection( m_CCHandle ); +#endif } +#ifdef MAPBASE // From Alien Swarm SDK +bool C_ColorCorrection::IsClientSide() const +{ + return m_bClientSide || mat_colcorrection_forceentitiesclientside.GetBool(); +} +#endif //------------------------------------------------------------------------------ // Purpose : @@ -87,11 +99,21 @@ void C_ColorCorrection::OnDataChanged(DataUpdateType_t updateType) { if ( m_CCHandle == INVALID_CLIENT_CCHANDLE ) { +#ifdef MAPBASE // From Alien Swarm SDK + // forming a unique name without extension + char cleanName[MAX_PATH]; + V_StripExtension( m_netLookupFilename, cleanName, sizeof( cleanName ) ); + char name[MAX_PATH]; + Q_snprintf( name, MAX_PATH, "%s_%d", cleanName, entindex() ); + + m_CCHandle = g_pColorCorrectionMgr->AddColorCorrectionEntity( this, name, m_netLookupFilename ); +#else char filename[MAX_PATH]; Q_strncpy( filename, m_netLookupFilename, MAX_PATH ); m_CCHandle = g_pColorCorrectionMgr->AddColorCorrection( filename ); SetNextClientThink( ( m_CCHandle != INVALID_CLIENT_CCHANDLE ) ? CLIENT_THINK_ALWAYS : CLIENT_THINK_NEVER ); +#endif } } } @@ -104,6 +126,129 @@ bool C_ColorCorrection::ShouldDraw() return false; } +#ifdef MAPBASE // From Alien Swarm SDK +void C_ColorCorrection::Update( C_BasePlayer *pPlayer, float ccScale ) +{ + Assert( m_CCHandle != INVALID_CLIENT_CCHANDLE ); + + if ( mat_colcorrection_disableentities.GetInt() ) + { + // Allow the colorcorrectionui panel (or user) to turn off color-correction entities + g_pColorCorrectionMgr->SetColorCorrectionWeight( m_CCHandle, 0.0f, m_bExclusive ); + return; + } + + // fade weight on client + if ( IsClientSide() ) + { + m_flCurWeight = Lerp( GetFadeRatio(), m_flFadeStartWeight, m_bFadingIn ? m_flMaxWeight : 0.0f ); + } + + if( !m_bEnabled && m_flCurWeight == 0.0f ) + { + g_pColorCorrectionMgr->SetColorCorrectionWeight( m_CCHandle, 0.0f, m_bExclusive ); + return; + } + + Vector playerOrigin = pPlayer->GetAbsOrigin(); + + float weight = 0; + if ( ( m_minFalloff != -1 ) && ( m_maxFalloff != -1 ) && m_minFalloff != m_maxFalloff ) + { + float dist = (playerOrigin - m_vecOrigin).Length(); + weight = (dist-m_minFalloff) / (m_maxFalloff-m_minFalloff); + if ( weight<0.0f ) weight = 0.0f; + if ( weight>1.0f ) weight = 1.0f; + } + + g_pColorCorrectionMgr->SetColorCorrectionWeight( m_CCHandle, m_flCurWeight * ( 1.0 - weight ) * ccScale, m_bExclusive ); +} + +void C_ColorCorrection::EnableOnClient( bool bEnable, bool bSkipFade ) +{ + if ( !IsClientSide() ) + { + return; + } + + m_bFadingIn = bEnable; + + // initialize countdown timer + m_flFadeStartWeight = m_flCurWeight; + float flFadeTimeScale = 1.0f; + if ( m_flMaxWeight != 0.0f ) + { + flFadeTimeScale = m_flCurWeight / m_flMaxWeight; + } + + if ( m_bFadingIn ) + { + flFadeTimeScale = 1.0f - flFadeTimeScale; + } + + if ( bSkipFade ) + { + flFadeTimeScale = 0.0f; + } + + StartFade( flFadeTimeScale * ( m_bFadingIn ? m_flFadeInDuration : m_flFadeOutDuration ) ); + + // update the clientside weight once here, in case the fade duration is 0 + m_flCurWeight = Lerp( GetFadeRatio(), m_flFadeStartWeight, m_bFadingIn ? m_flMaxWeight : 0.0f ); +} + +Vector C_ColorCorrection::GetOrigin() +{ + return m_vecOrigin; +} + +float C_ColorCorrection::GetMinFalloff() +{ + return m_minFalloff; +} + +float C_ColorCorrection::GetMaxFalloff() +{ + return m_maxFalloff; +} + +void C_ColorCorrection::SetWeight( float fWeight ) +{ + g_pColorCorrectionMgr->SetColorCorrectionWeight( m_CCHandle, fWeight, false ); +} + +void C_ColorCorrection::StartFade( float flDuration ) +{ + m_flFadeStartTime = gpGlobals->curtime; + m_flFadeDuration = MAX( flDuration, 0.0f ); +} + +float C_ColorCorrection::GetFadeRatio() const +{ + float flRatio = 1.0f; + + if ( m_flFadeDuration != 0.0f ) + { + flRatio = ( gpGlobals->curtime - m_flFadeStartTime ) / m_flFadeDuration; + flRatio = clamp( flRatio, 0.0f, 1.0f ); + } + return flRatio; +} + +bool C_ColorCorrection::IsFadeTimeElapsed() const +{ + return ( ( gpGlobals->curtime - m_flFadeStartTime ) > m_flFadeDuration ) || + ( ( gpGlobals->curtime - m_flFadeStartTime ) < 0.0f ); +} + +void UpdateColorCorrectionEntities( C_BasePlayer *pPlayer, float ccScale, C_ColorCorrection **pList, int listCount ) +{ + for ( int i = 0; i < listCount; i++ ) + { + pList[i]->Update(pPlayer, ccScale); + } +} +#else void C_ColorCorrection::ClientThink() { if ( m_CCHandle == INVALID_CLIENT_CCHANDLE ) @@ -141,6 +286,7 @@ void C_ColorCorrection::ClientThink() BaseClass::ClientThink(); } +#endif diff --git a/src/game/client/c_colorcorrection.h b/src/game/client/c_colorcorrection.h new file mode 100644 index 00000000..63149a0a --- /dev/null +++ b/src/game/client/c_colorcorrection.h @@ -0,0 +1,88 @@ +//========= Mapbase - https://github.com/mapbase-source/source-sdk-2013 ============// +// +// Note that this header exists in the Alien Swarm SDK, but not in stock Source SDK 2013. +// Although technically a new Mapbase file, it only serves to move otherwise identical code, +// so most code and repo conventions will pretend it was always there. +// +// -------------------------------------------------------------------- +// +// Purpose: Color correction entity with simple radial falloff +// +//=============================================================================// + +#ifndef C_COLORCORRECTION_H +#define C_COLORCORRECTION_H +#ifdef _WIN32 +#pragma once +#endif + +#include "colorcorrectionmgr.h" + +//------------------------------------------------------------------------------ +// Purpose : Color correction entity with radial falloff +//------------------------------------------------------------------------------ +class C_ColorCorrection : public C_BaseEntity +{ +public: + DECLARE_CLASS( C_ColorCorrection, C_BaseEntity ); + + DECLARE_CLIENTCLASS(); + + C_ColorCorrection(); + virtual ~C_ColorCorrection(); + + void OnDataChanged(DataUpdateType_t updateType); + bool ShouldDraw(); + +#ifdef MAPBASE // From Alien Swarm SDK + virtual void Update(C_BasePlayer *pPlayer, float ccScale); + + bool IsMaster() const { return m_bMaster; } + bool IsClientSide() const; + bool IsExclusive() const { return m_bExclusive; } + + void EnableOnClient( bool bEnable, bool bSkipFade = false ); + + Vector GetOrigin(); + float GetMinFalloff(); + float GetMaxFalloff(); + + void SetWeight( float fWeight ); + +protected: + void StartFade( float flDuration ); + float GetFadeRatio() const; + bool IsFadeTimeElapsed() const; +#else + void ClientThink(); + +private: +#endif + Vector m_vecOrigin; + + float m_minFalloff; + float m_maxFalloff; + float m_flCurWeight; + char m_netLookupFilename[MAX_PATH]; + + bool m_bEnabled; + +#ifdef MAPBASE // From Alien Swarm SDK + float m_flFadeInDuration; + float m_flFadeOutDuration; + float m_flMaxWeight; + + bool m_bMaster; + bool m_bClientSide; + bool m_bExclusive; + + bool m_bFadingIn; + float m_flFadeStartWeight; + float m_flFadeStartTime; + float m_flFadeDuration; +#endif + + ClientCCHandle_t m_CCHandle; +}; + +#endif diff --git a/src/game/client/c_colorcorrectionvolume.cpp b/src/game/client/c_colorcorrectionvolume.cpp index 4bbcea94..f7e33708 100644 --- a/src/game/client/c_colorcorrectionvolume.cpp +++ b/src/game/client/c_colorcorrectionvolume.cpp @@ -36,9 +36,26 @@ public: void OnDataChanged(DataUpdateType_t updateType); bool ShouldDraw(); +#ifdef MAPBASE // From Alien Swarm SDK + void Update( C_BasePlayer *pPlayer, float ccScale ); + + void StartTouch( C_BaseEntity *pOther ); + void EndTouch( C_BaseEntity *pOther ); +#else void ClientThink(); +#endif private: +#ifdef MAPBASE // From Alien Swarm SDK + float m_LastEnterWeight; + float m_LastEnterTime; + + float m_LastExitWeight; + float m_LastExitTime; + bool m_bEnabled; + float m_MaxWeight; + float m_FadeDuration; +#endif float m_Weight; char m_lookupFilename[MAX_PATH]; @@ -46,6 +63,11 @@ private: }; IMPLEMENT_CLIENTCLASS_DT(C_ColorCorrectionVolume, DT_ColorCorrectionVolume, CColorCorrectionVolume) +#ifdef MAPBASE // From Alien Swarm SDK + RecvPropBool( RECVINFO( m_bEnabled ) ), + RecvPropFloat( RECVINFO( m_MaxWeight ) ), + RecvPropFloat( RECVINFO( m_FadeDuration ) ), +#endif RecvPropFloat( RECVINFO(m_Weight) ), RecvPropString( RECVINFO(m_lookupFilename) ), END_RECV_TABLE() @@ -82,11 +104,21 @@ void C_ColorCorrectionVolume::OnDataChanged(DataUpdateType_t updateType) { if ( m_CCHandle == INVALID_CLIENT_CCHANDLE ) { +#ifdef MAPBASE // From Alien Swarm SDK + // forming a unique name without extension + char cleanName[MAX_PATH]; + V_StripExtension( m_lookupFilename, cleanName, sizeof( cleanName ) ); + char name[MAX_PATH]; + Q_snprintf( name, MAX_PATH, "%s_%d", cleanName, entindex() ); + + m_CCHandle = g_pColorCorrectionMgr->AddColorCorrectionVolume( this, name, m_lookupFilename ); +#else char filename[MAX_PATH]; Q_strncpy( filename, m_lookupFilename, MAX_PATH ); m_CCHandle = g_pColorCorrectionMgr->AddColorCorrection( filename ); SetNextClientThink( ( m_CCHandle != INVALID_CLIENT_CCHANDLE ) ? CLIENT_THINK_ALWAYS : CLIENT_THINK_NEVER ); +#endif } } } @@ -99,11 +131,95 @@ bool C_ColorCorrectionVolume::ShouldDraw() return false; } +#ifdef MAPBASE // From Alien Swarm SDK +//-------------------------------------------------------------------------------------------------------- +void C_ColorCorrectionVolume::StartTouch( CBaseEntity *pEntity ) +{ + m_LastEnterTime = gpGlobals->curtime; + m_LastEnterWeight = m_Weight; +} + + +//-------------------------------------------------------------------------------------------------------- +void C_ColorCorrectionVolume::EndTouch( CBaseEntity *pEntity ) +{ + m_LastExitTime = gpGlobals->curtime; + m_LastExitWeight = m_Weight; +} + + +void C_ColorCorrectionVolume::Update( C_BasePlayer *pPlayer, float ccScale ) +{ + if ( pPlayer ) + { + bool isTouching = CollisionProp()->IsPointInBounds( pPlayer->EyePosition() ); + bool wasTouching = m_LastEnterTime > m_LastExitTime; + + if ( isTouching && !wasTouching ) + { + StartTouch( pPlayer ); + } + else if ( !isTouching && wasTouching ) + { + EndTouch( pPlayer ); + } + } + + if( !m_bEnabled ) + { + m_Weight = 0.0f; + } + else + { + if( m_LastEnterTime > m_LastExitTime ) + { + // we most recently entered the volume + + if( m_Weight < 1.0f ) + { + float dt = gpGlobals->curtime - m_LastEnterTime; + float weight = m_LastEnterWeight + dt / ((1.0f-m_LastEnterWeight)*m_FadeDuration); + if( weight>1.0f ) + weight = 1.0f; + + m_Weight = weight; + } + } + else + { + // we most recently exitted the volume + + if( m_Weight > 0.0f ) + { + float dt = gpGlobals->curtime - m_LastExitTime; + float weight = (1.0f-m_LastExitWeight) + dt / (m_LastExitWeight*m_FadeDuration); + if( weight>1.0f ) + weight = 1.0f; + + m_Weight = 1.0f - weight; + } + } + } + + // Vector entityPosition = GetAbsOrigin(); + g_pColorCorrectionMgr->SetColorCorrectionWeight( m_CCHandle, m_Weight * ccScale ); +} + + +void UpdateColorCorrectionVolumes( C_BasePlayer *pPlayer, float ccScale, C_ColorCorrectionVolume **pList, int listCount ) +{ + for ( int i = 0; i < listCount; i++ ) + { + pList[i]->Update(pPlayer, ccScale); + } +} +#else void C_ColorCorrectionVolume::ClientThink() { Vector entityPosition = GetAbsOrigin(); g_pColorCorrectionMgr->SetColorCorrectionWeight( m_CCHandle, m_Weight ); } +#endif diff --git a/src/game/client/c_effects.cpp b/src/game/client/c_effects.cpp index 3fc18490..7e22e75d 100644 --- a/src/game/client/c_effects.cpp +++ b/src/game/client/c_effects.cpp @@ -6,6 +6,7 @@ // //=============================================================================// #include "cbase.h" +#include "c_effects.h" #include "c_tracer.h" #include "view.h" #include "initializer.h" @@ -22,6 +23,7 @@ #include "collisionutils.h" #include "tier0/vprof.h" #include "viewrender.h" +#include "raytrace.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" @@ -35,6 +37,15 @@ float g_flSplashLifetime = 0.5f; float g_flSplashAlpha = 0.3f; ConVar r_RainSplashPercentage( "r_RainSplashPercentage", "20", FCVAR_CHEAT ); // N% chance of a rain particle making a splash. +ConVar r_RainParticleDensity( "r_RainParticleDensity", "1", FCVAR_NONE, "Density of Particle Rain 0-1" ); + +#ifdef MAPBASE +ConVar r_RainParticleClampOffset_Rain( "r_RainParticleClampOffset_Rain", "120", FCVAR_NONE, "How far inward or outward to extrude clamped precipitation particle systems using the 'Particle Rain' type." ); +ConVar r_RainParticleClampOffset_Ash( "r_RainParticleClampOffset_Ash", "300", FCVAR_NONE, "How far inward or outward to extrude clamped precipitation particle systems using the 'Particle Ash' type." ); +ConVar r_RainParticleClampOffset_RainStorm( "r_RainParticleClampOffset_RainStorm", "112", FCVAR_NONE, "How far inward or outward to extrude clamped precipitation particle systems using the 'Particle Rain Storm' type." ); +ConVar r_RainParticleClampOffset_Snow( "r_RainParticleClampOffset_Snow", "300", FCVAR_NONE, "How far inward or outward to extrude clamped precipitation particle systems using the 'Particle Snow' type." ); +ConVar r_RainParticleClampDebug( "r_RainParticleClampDebug", "0", FCVAR_NONE, "Enables debug code for precipitation particle system clamping" ); +#endif float GUST_INTERVAL_MIN = 1; float GUST_INTERVAL_MAX = 2; @@ -60,151 +71,14 @@ CLIENTEFFECT_MATERIAL( "particle/rain" ) CLIENTEFFECT_MATERIAL( "particle/snow" ) CLIENTEFFECT_REGISTER_END() -//----------------------------------------------------------------------------- -// Precipitation particle type -//----------------------------------------------------------------------------- - -class CPrecipitationParticle -{ -public: - Vector m_Pos; - Vector m_Velocity; - float m_SpawnTime; // Note: Tweak with this to change lifetime - float m_Mass; - float m_Ramp; - - float m_flCurLifetime; - float m_flMaxLifetime; -}; - - -class CClient_Precipitation; -static CUtlVector g_Precipitations; - -//=========== -// Snow fall -//=========== -class CSnowFallManager; -static CSnowFallManager *s_pSnowFallMgr = NULL; -bool SnowFallManagerCreate( CClient_Precipitation *pSnowEntity ); -void SnowFallManagerDestroy( void ); - -class AshDebrisEffect : public CSimpleEmitter -{ -public: - AshDebrisEffect( const char *pDebugName ) : CSimpleEmitter( pDebugName ) {} - - static AshDebrisEffect* Create( const char *pDebugName ); - - virtual float UpdateAlpha( const SimpleParticle *pParticle ); - virtual float UpdateRoll( SimpleParticle *pParticle, float timeDelta ); - -private: - AshDebrisEffect( const AshDebrisEffect & ); -}; - -//----------------------------------------------------------------------------- -// Precipitation base entity -//----------------------------------------------------------------------------- - -class CClient_Precipitation : public C_BaseEntity -{ -class CPrecipitationEffect; -friend class CClient_Precipitation::CPrecipitationEffect; - -public: - DECLARE_CLASS( CClient_Precipitation, C_BaseEntity ); - DECLARE_CLIENTCLASS(); - - CClient_Precipitation(); - virtual ~CClient_Precipitation(); - - // Inherited from C_BaseEntity - virtual void Precache( ); - - void Render(); - -private: - - // Creates a single particle - CPrecipitationParticle* CreateParticle(); - - virtual void OnDataChanged( DataUpdateType_t updateType ); - virtual void ClientThink(); - - void Simulate( float dt ); - - // Renders the particle - void RenderParticle( CPrecipitationParticle* pParticle, CMeshBuilder &mb ); - - void CreateWaterSplashes(); - - // Emits the actual particles - void EmitParticles( float fTimeDelta ); - - // Computes where we're gonna emit - bool ComputeEmissionArea( Vector& origin, Vector2D& size ); - - // Gets the tracer width and speed - float GetWidth() const; - float GetLength() const; - float GetSpeed() const; - - // Gets the remaining lifetime of the particle - float GetRemainingLifetime( CPrecipitationParticle* pParticle ) const; - - // Computes the wind vector - static void ComputeWindVector( ); - - // simulation methods - bool SimulateRain( CPrecipitationParticle* pParticle, float dt ); - bool SimulateSnow( CPrecipitationParticle* pParticle, float dt ); - - void CreateAshParticle( void ); - void CreateRainOrSnowParticle( Vector vSpawnPosition, Vector vVelocity ); - - // Information helpful in creating and rendering particles - IMaterial *m_MatHandle; // material used - - float m_Color[4]; // precip color - float m_Lifetime; // Precip lifetime - float m_InitialRamp; // Initial ramp value - float m_Speed; // Precip speed - float m_Width; // Tracer width - float m_Remainder; // particles we should render next time - PrecipitationType_t m_nPrecipType; // Precip type - float m_flHalfScreenWidth; // Precalculated each frame. - - float m_flDensity; - - // Some state used in rendering and simulation - // Used to modify the rain density and wind from the console - static ConVar s_raindensity; - static ConVar s_rainwidth; - static ConVar s_rainlength; - static ConVar s_rainspeed; - - static Vector s_WindVector; // Stores the wind speed vector - - CUtlLinkedList m_Particles; - CUtlVector m_Splashes; - - CSmartPtr m_pAshEmitter; - TimedEvent m_tAshParticleTimer; - TimedEvent m_tAshParticleTraceTimer; - bool m_bActiveAshEmitter; - Vector m_vAshSpawnOrigin; - - int m_iAshCount; - -private: - CClient_Precipitation( const CClient_Precipitation & ); // not defined, not accessible -}; - +CUtlVector< RayTracingEnvironment* > g_RayTraceEnvironments; // Just receive the normal data table stuff IMPLEMENT_CLIENTCLASS_DT(CClient_Precipitation, DT_Precipitation, CPrecipitation) - RecvPropInt( RECVINFO( m_nPrecipType ) ) + RecvPropInt( RECVINFO( m_nPrecipType ) ), +#ifdef MAPBASE + RecvPropInt( RECVINFO( m_spawnflags ) ), +#endif END_RECV_TABLE() static ConVar r_SnowEnable( "r_SnowEnable", "1", FCVAR_CHEAT, "Snow Enable" ); @@ -396,6 +270,12 @@ inline bool CClient_Precipitation::SimulateSnow( CPrecipitationParticle* pPartic void CClient_Precipitation::Simulate( float dt ) { + if ( IsParticleRainType(m_nPrecipType) ) + { + CreateParticlePrecip(); + return; + } + // NOTE: When client-side prechaching works, we need to remove this Precache(); @@ -472,6 +352,9 @@ inline void CClient_Precipitation::RenderParticle( CPrecipitationParticle* pPart float scale; Vector start, delta; + if ( IsParticleRainType(m_nPrecipType) ) + return; + if ( m_nPrecipType == PRECIPITATION_TYPE_ASH ) return; @@ -562,6 +445,9 @@ void CClient_Precipitation::Render() if ( !r_DrawRain.GetInt() ) return; + if ( IsParticleRainType(m_nPrecipType) ) + return; + // Don't render in monitors or in reflections or refractions. if ( CurrentViewID() == VIEW_MONITOR ) return; @@ -632,6 +518,11 @@ CClient_Precipitation::CClient_Precipitation() : m_Remainder(0.0f) m_nPrecipType = PRECIPITATION_TYPE_RAIN; m_MatHandle = INVALID_MATERIAL_HANDLE; m_flHalfScreenWidth = 1; + + m_pParticlePrecipInnerNear = NULL; + m_pParticlePrecipInnerFar = NULL; + m_pParticlePrecipOuter = NULL; + m_bActiveParticlePrecipEmitter = false; g_Precipitations.AddToTail( this ); } @@ -1011,6 +902,397 @@ void CClient_Precipitation::CreateAshParticle( void ) } } +void CClient_Precipitation::PrecacheParticlePrecip( void ) +{ + if ( m_nPrecipType == PRECIPITATION_TYPE_PARTICLEASH ) + { + PrecacheParticleSystem( "ash" ); + PrecacheParticleSystem( "ash_outer" ); + } + else if ( m_nPrecipType == PRECIPITATION_TYPE_PARTICLESNOW ) + { + PrecacheParticleSystem( "snow" ); + PrecacheParticleSystem( "snow_outer" ); + } + else if ( m_nPrecipType == PRECIPITATION_TYPE_PARTICLERAINSTORM ) + { + PrecacheParticleSystem( "rain_storm" ); + PrecacheParticleSystem( "rain_storm_screen" ); + PrecacheParticleSystem( "rain_storm_outer" ); + } + else //default to rain + { + PrecacheParticleSystem( "rain" ); + PrecacheParticleSystem( "rain_outer" ); + } +} + +void CClient_Precipitation::CreateParticlePrecip( void ) +{ + if ( !m_bParticlePrecipInitialized ) + { + PrecacheParticlePrecip(); + InitializeParticlePrecip(); + } + + + C_BasePlayer *pPlayer = C_BasePlayer::GetLocalPlayer(); + + if ( pPlayer == NULL ) + return; + + // Make sure the emitter is setup + if ( !m_bActiveParticlePrecipEmitter ) + { + //Update 8 times per second. + m_tParticlePrecipTraceTimer.Init( 8 ); + DestroyInnerParticlePrecip(); + DestroyOuterParticlePrecip(); + m_bActiveParticlePrecipEmitter = true; + } + + UpdateParticlePrecip( pPlayer ); +} + +#ifdef MAPBASE +void CClient_Precipitation::ClampParticlePosition( Vector &vPlayerPos, Vector &vOffsetPos, Vector &vOffsetPosNear, Vector &vOffsetPosFar ) +{ + Vector mins, maxs; + modelinfo->GetModelBounds( GetModel(), mins, maxs ); + + // Account for precipitation height + maxs.z += 180; + + Vector vecOrigin; //= WorldSpaceCenter(); + VectorLerp( mins, maxs, 0.5f, vecOrigin ); + + maxs -= vecOrigin; + mins -= vecOrigin; + + //float flMax = r_RainParticleClampOffset.GetFloat(); + float flMax = 0; + switch (m_nPrecipType) + { + case PRECIPITATION_TYPE_PARTICLERAIN: + flMax = r_RainParticleClampOffset_Rain.GetFloat(); + break; + + case PRECIPITATION_TYPE_PARTICLEASH: + flMax = r_RainParticleClampOffset_Ash.GetFloat(); + break; + + case PRECIPITATION_TYPE_PARTICLERAINSTORM: + flMax = r_RainParticleClampOffset_RainStorm.GetFloat(); + break; + + case PRECIPITATION_TYPE_PARTICLESNOW: + flMax = r_RainParticleClampOffset_Snow.GetFloat(); + break; + } + + Vector addend( flMax, flMax, 0 ); + mins += addend; + maxs -= addend; + + if (flMax > 0) + { + // Unless this is extruding outwards, make sure the offset isn't inverting the bounds. + // This means precipitation triggers with bounds less than offset*2 will turn into a thin line + // and the involved precipitation will pretty much be spatial at all times, which is okay. + mins.x = clamp( mins.x, -FLT_MAX, -1 ); + mins.y = clamp( mins.y, -FLT_MAX, -1 ); + maxs.x = clamp( maxs.x, 1, FLT_MAX ); + maxs.y = clamp( maxs.y, 1, FLT_MAX ); + } + + if (r_RainParticleClampDebug.GetBool()) + debugoverlay->AddBoxOverlay( vecOrigin, mins, maxs, vec3_angle, 255, 0, 0, 128, 0.15f ); + + maxs += vecOrigin; + mins += vecOrigin; + + CalcClosestPointOnAABB( mins, maxs, vPlayerPos, vPlayerPos ); + CalcClosestPointOnAABB( mins, maxs, vOffsetPos, vOffsetPos ); + CalcClosestPointOnAABB( mins, maxs, vOffsetPosNear, vOffsetPosNear ); + CalcClosestPointOnAABB( mins, maxs, vOffsetPosFar, vOffsetPosFar ); +} +#endif + +void CClient_Precipitation::UpdateParticlePrecip( C_BasePlayer *pPlayer ) +{ + if ( !pPlayer ) + return; + + Vector vForward; + Vector vRight; + + pPlayer->GetVectors( &vForward, &vRight, NULL ); + vForward.z = 0.0f; + vForward.NormalizeInPlace(); + Vector vForward45Right = vForward + vRight; + Vector vForward45Left = vForward - vRight; + vForward45Right.NormalizeInPlace(); + vForward45Left.NormalizeInPlace(); + fltx4 TMax = ReplicateX4( 320.0f ); + SubFloat( TMax, 3 ) = FLT_MAX; + float curTime = gpGlobals->frametime; + + while ( m_tParticlePrecipTraceTimer.NextEvent( curTime ) ) + { + Vector vPlayerPos = pPlayer->EyePosition(); + Vector vOffsetPos = vPlayerPos + Vector ( 0, 0, 180 ); + Vector vOffsetPosNear = vPlayerPos + Vector ( 0, 0, 180 ) + ( vForward * 32 ); + Vector vOffsetPosFar = vPlayerPos + Vector ( 0, 0, 180 ) + ( vForward * 100 ); + +#ifdef MAPBASE + if (m_spawnflags & SF_PRECIP_PARTICLE_CLAMP) + { + ClampParticlePosition( vPlayerPos, vOffsetPos, vOffsetPosNear, vOffsetPosFar ); + } +#endif + + Vector vDensity = Vector( r_RainParticleDensity.GetFloat(), 0, 0 ) * m_flDensity; + + // Get the rain volume Ray Tracing Environment. Currently hard coded to 0, should have this lookup + RayTracingEnvironment *RtEnv = g_RayTraceEnvironments.Element( 0 ); + + // Our 4 Rays are forward, off to the left and right, and directly up. + // Use the first three to determine if there's generally visible rain where we're looking. + // The forth, straight up, tells us if we're standing inside a rain volume + // (based on the normal that we hit or if we miss entirely) + FourRays frRays; + FourVectors fvDirection; + fvDirection = FourVectors( vForward, vForward45Left, vForward45Right, Vector( 0, 0, 1 ) ); + frRays.direction = fvDirection; + frRays.origin.DuplicateVector( vPlayerPos ); + RayTracingResult Result; + + RtEnv->Trace4Rays( frRays, Four_Zeros, TMax, &Result ); + + i32x4 in4HitIds = LoadAlignedIntSIMD( Result.HitIds ); + fltx4 fl4HitIds = SignedIntConvertToFltSIMD ( in4HitIds ); + + fltx4 fl4Tolerance = ReplicateX4( 300.0f ); + // ignore upwards test for tolerance, as we may be below an area which is raining, but with it not visible in front of us + //SubFloat( fl4Tolerance, 3 ) = 0.0f; + + bool bInside = ( Result.HitIds[3] != -1 && Result.surface_normal.Vec( 3 ).z < 0.0f ); + bool bNearby = ( IsAnyNegative ( CmpGeSIMD ( fl4HitIds, Four_Zeros ) ) && IsAnyNegative( CmpGeSIMD( fl4Tolerance, Result.HitDistance ) ) ); + + if ( bInside || bNearby ) + { + //We can see a rain volume, but it's farther than 180 units away, only use far effect. + if ( !bInside && SubFloat( FindLowestSIMD3( Result.HitDistance ), 0 ) >= m_flParticleInnerDist ) + { + // Kill the inner rain if it's previously been in use + if ( m_pParticlePrecipInnerNear != NULL ) + { + DestroyInnerParticlePrecip(); + } + // Update if we've already got systems, otherwise, create them. + if ( m_pParticlePrecipOuter != NULL ) + { + m_pParticlePrecipOuter->SetControlPoint( 1, vOffsetPos ); + m_pParticlePrecipOuter->SetControlPoint( 3, vDensity ); + } + else + { + DispatchOuterParticlePrecip( pPlayer, vForward ); + } + } + else //We're close enough to use the near effect. + { + // Update if we've already got systems, otherwise, create them. +#ifdef MAPBASE + // The outer can now be suppressed without interfering with other functionality + if ( m_pParticlePrecipOuter != NULL ) + { + m_pParticlePrecipOuter->SetControlPoint( 1, vOffsetPos ); + m_pParticlePrecipOuter->SetControlPoint( 3, vDensity ); + } + if ( m_pParticlePrecipInnerNear != NULL && m_pParticlePrecipInnerFar != NULL ) + { + m_pParticlePrecipInnerNear->SetControlPoint( 1, vOffsetPosNear ); + m_pParticlePrecipInnerFar->SetControlPoint( 1, vOffsetPosFar ); + m_pParticlePrecipInnerNear->SetControlPoint( 3, vDensity ); + m_pParticlePrecipInnerFar->SetControlPoint( 3, vDensity ); + } +#else + if ( m_pParticlePrecipInnerNear != NULL && m_pParticlePrecipInnerFar != NULL && m_pParticlePrecipOuter != NULL ) + { + m_pParticlePrecipOuter->SetControlPoint( 1, vOffsetPos ); + m_pParticlePrecipInnerNear->SetControlPoint( 1, vOffsetPosNear ); + m_pParticlePrecipInnerFar->SetControlPoint( 1, vOffsetPosFar ); + m_pParticlePrecipInnerNear->SetControlPoint( 3, vDensity ); + m_pParticlePrecipInnerFar->SetControlPoint( 3, vDensity ); + m_pParticlePrecipOuter->SetControlPoint( 3, vDensity ); + } +#endif + else + { + DispatchInnerParticlePrecip( pPlayer, vForward ); + } + } + } + else // No rain in the area, kill any leftover systems. + { + DestroyInnerParticlePrecip(); + DestroyOuterParticlePrecip(); + } + } +} + +void CClient_Precipitation::InitializeParticlePrecip( void ) +{ + //Set up which type of precipitation particle we'll use + if ( m_nPrecipType == PRECIPITATION_TYPE_PARTICLEASH ) + { + m_pParticleInnerNearDef = "ash"; + m_pParticleInnerFarDef = "ash"; + m_pParticleOuterDef = "ash_outer"; + m_flParticleInnerDist = 280.0; + } + else if ( m_nPrecipType == PRECIPITATION_TYPE_PARTICLESNOW ) + { + m_pParticleInnerNearDef = "snow"; + m_pParticleInnerFarDef = "snow"; + m_pParticleOuterDef = "snow_outer"; + m_flParticleInnerDist = 280.0; + } + else if ( m_nPrecipType == PRECIPITATION_TYPE_PARTICLERAINSTORM ) + { + m_pParticleInnerNearDef = "rain_storm"; + m_pParticleInnerFarDef = "rain_storm_screen"; + m_pParticleOuterDef = "rain_storm_outer"; + m_flParticleInnerDist = 0.0; + } + else //default to rain + { + m_pParticleInnerNearDef = "rain"; + m_pParticleInnerFarDef = "rain"; + m_pParticleOuterDef = "rain_outer"; + m_flParticleInnerDist = 180.0; + } + + Assert( m_pParticleInnerFarDef != NULL ); + + //We'll want to change this if/when we add more raytrace environments. + g_RayTraceEnvironments.PurgeAndDeleteElements(); + + // Sets up ray tracing environments for all func_precipitations and func_precipitation_blockers + RayTracingEnvironment *rtEnvRainEmission = new RayTracingEnvironment(); + g_RayTraceEnvironments.AddToTail( rtEnvRainEmission ); + RayTracingEnvironment *rtEnvRainBlocker = new RayTracingEnvironment(); + g_RayTraceEnvironments.AddToTail( rtEnvRainBlocker ); + + rtEnvRainEmission->Flags |= RTE_FLAGS_DONT_STORE_TRIANGLE_COLORS; // save some ram + rtEnvRainBlocker->Flags |= RTE_FLAGS_DONT_STORE_TRIANGLE_COLORS; // save some ram + + int nTriCount = 1; + for ( int i=0; iGetVCollide( volume->GetModelIndex() ); + + if ( !pCollide || pCollide->solidCount <= 0 ) + continue; + + Vector *outVerts; + int vertCount = physcollision->CreateDebugMesh( pCollide->solids[0], &outVerts ); + + if ( vertCount ) + { + for ( int j = 0; j < vertCount; j += 3 ) + { + rtEnvRainEmission->AddTriangle( nTriCount++, outVerts[j], outVerts[j + 1], outVerts[j + 2], Vector( 1, 1, 1 ) ); + } + } + physcollision->DestroyDebugMesh( vertCount, outVerts ); + } + rtEnvRainEmission->SetupAccelerationStructure(); + + m_bParticlePrecipInitialized = true; +} + +void CClient_Precipitation::DestroyInnerParticlePrecip( void ) +{ + if ( m_pParticlePrecipInnerFar != NULL ) + { + m_pParticlePrecipInnerFar->StopEmission(); + m_pParticlePrecipInnerFar = NULL; + } + if ( m_pParticlePrecipInnerNear != NULL ) + { + m_pParticlePrecipInnerNear->StopEmission(); + m_pParticlePrecipInnerNear = NULL; + } +} + +void CClient_Precipitation::DestroyOuterParticlePrecip( void ) +{ + if ( m_pParticlePrecipOuter != NULL ) + { + m_pParticlePrecipOuter->StopEmission(); + m_pParticlePrecipOuter = NULL; + } +} + +void CClient_Precipitation::DispatchOuterParticlePrecip( C_BasePlayer *pPlayer, Vector vForward ) +{ + DestroyOuterParticlePrecip(); + +#ifdef MAPBASE + if (m_spawnflags & SF_PRECIP_PARTICLE_NO_OUTER) + return; +#endif + + Vector vDensity = Vector( r_RainParticleDensity.GetFloat(), 0, 0 ) * m_flDensity; + Vector vPlayerPos = pPlayer->EyePosition(); + + m_pParticlePrecipOuter = ParticleProp()->Create( m_pParticleOuterDef, PATTACH_ABSORIGIN_FOLLOW ); + m_pParticlePrecipOuter->SetControlPointEntity( 2, pPlayer ); + m_pParticlePrecipOuter->SetControlPoint( 1, vPlayerPos + Vector (0, 0, 180 ) ); + m_pParticlePrecipOuter->SetControlPoint( 3, vDensity ); +} + +void CClient_Precipitation::DispatchInnerParticlePrecip( C_BasePlayer *pPlayer, Vector vForward ) +{ + DestroyInnerParticlePrecip(); + DestroyOuterParticlePrecip(); + Vector vPlayerPos = pPlayer->EyePosition(); + Vector vOffsetPos = vPlayerPos + Vector ( 0, 0, 180 ); + Vector vOffsetPosNear = vPlayerPos + Vector ( 0, 0, 180 ) + ( vForward * 32 ); + Vector vOffsetPosFar = vPlayerPos + Vector ( 0, 0, 180 ) + ( vForward * m_flParticleInnerDist ); // 100.0 + Vector vDensity = Vector( r_RainParticleDensity.GetFloat(), 0, 0 ) * m_flDensity; + +#ifdef MAPBASE + if (m_spawnflags & SF_PRECIP_PARTICLE_CLAMP) + { + ClampParticlePosition( vPlayerPos, vOffsetPos, vOffsetPosNear, vOffsetPosFar ); + } +#endif + +#ifdef MAPBASE + if (!(m_spawnflags & SF_PRECIP_PARTICLE_NO_OUTER)) +#endif + { + m_pParticlePrecipOuter = ParticleProp()->Create( m_pParticleOuterDef, PATTACH_ABSORIGIN_FOLLOW ); + m_pParticlePrecipOuter->SetControlPointEntity( 2, pPlayer ); + m_pParticlePrecipOuter->SetControlPoint( 1, vOffsetPos ); + m_pParticlePrecipOuter->SetControlPoint( 3, vDensity ); + } + + m_pParticlePrecipInnerNear = ParticleProp()->Create( m_pParticleInnerNearDef, PATTACH_ABSORIGIN_FOLLOW ); + m_pParticlePrecipInnerFar = ParticleProp()->Create( m_pParticleInnerFarDef, PATTACH_ABSORIGIN_FOLLOW ); + m_pParticlePrecipInnerNear->SetControlPointEntity( 2, pPlayer ); + m_pParticlePrecipInnerFar->SetControlPointEntity( 2, pPlayer ); + m_pParticlePrecipInnerNear->SetControlPoint( 1, vOffsetPosNear ); + m_pParticlePrecipInnerFar->SetControlPoint( 1, vOffsetPosFar ); + m_pParticlePrecipInnerNear->SetControlPoint( 3, vDensity ); + m_pParticlePrecipInnerFar->SetControlPoint( 3, vDensity ); +} + void CClient_Precipitation::CreateRainOrSnowParticle( Vector vSpawnPosition, Vector vVelocity ) { // Create the particle @@ -1206,6 +1488,12 @@ BEGIN_RECV_TABLE_NOBASE(CEnvWindShared, DT_EnvWindShared) RecvPropFloat (RECVINFO(m_flStartTime)), RecvPropFloat (RECVINFO(m_flGustDuration)), // RecvPropInt (RECVINFO(m_iszGustSound)), +#ifdef MAPBASE + RecvPropFloat (RECVINFO(m_windRadius)), + RecvPropFloat (RECVINFO(m_windRadiusInner)), + RecvPropVector (RECVINFO(m_location)), + RecvPropFloat (RECVINFO(m_flTreeSwayScale)), +#endif END_RECV_TABLE() IMPLEMENT_CLIENTCLASS_DT( C_EnvWind, DT_EnvWind, CEnvWind ) @@ -1540,8 +1828,12 @@ public: pParticle->m_vecVelocity *= flSpeed; +#ifdef MAPBASE + Vector vecWindVelocity = GetWindspeedAtLocation( pParticle->m_Pos ); +#else Vector vecWindVelocity; GetWindspeedAtTime( gpGlobals->curtime, vecWindVelocity ); +#endif pParticle->m_vecVelocity += ( vecWindVelocity * r_SnowWindScale.GetFloat() ); } diff --git a/src/game/client/c_effects.h b/src/game/client/c_effects.h index 27bb501e..5f7e02a5 100644 --- a/src/game/client/c_effects.h +++ b/src/game/client/c_effects.h @@ -10,9 +10,182 @@ #pragma once #endif +#include "cbase.h" +#include "precipitation_shared.h" // Draw rain effects. void DrawPrecipitation(); +//----------------------------------------------------------------------------- +// Precipitation particle type +//----------------------------------------------------------------------------- + +class CPrecipitationParticle +{ +public: + Vector m_Pos; + Vector m_Velocity; + float m_SpawnTime; // Note: Tweak with this to change lifetime + float m_Mass; + float m_Ramp; + + float m_flCurLifetime; + float m_flMaxLifetime; +}; + + +class CClient_Precipitation; +static CUtlVector g_Precipitations; + +//=========== +// Snow fall +//=========== +class CSnowFallManager; +static CSnowFallManager *s_pSnowFallMgr = NULL; +bool SnowFallManagerCreate( CClient_Precipitation *pSnowEntity ); +void SnowFallManagerDestroy( void ); + +class AshDebrisEffect : public CSimpleEmitter +{ +public: + AshDebrisEffect( const char *pDebugName ) : CSimpleEmitter( pDebugName ) {} + + static AshDebrisEffect* Create( const char *pDebugName ); + + virtual float UpdateAlpha( const SimpleParticle *pParticle ); + virtual float UpdateRoll( SimpleParticle *pParticle, float timeDelta ); + +private: + AshDebrisEffect( const AshDebrisEffect & ); +}; + +//----------------------------------------------------------------------------- +// Precipitation base entity +//----------------------------------------------------------------------------- + +class CClient_Precipitation : public C_BaseEntity +{ +class CPrecipitationEffect; +friend class CClient_Precipitation::CPrecipitationEffect; + +public: + DECLARE_CLASS( CClient_Precipitation, C_BaseEntity ); + DECLARE_CLIENTCLASS(); + + CClient_Precipitation(); + virtual ~CClient_Precipitation(); + + // Inherited from C_BaseEntity + virtual void Precache( ); + + void Render(); + +private: + + // Creates a single particle + CPrecipitationParticle* CreateParticle(); + + virtual void OnDataChanged( DataUpdateType_t updateType ); + virtual void ClientThink(); + + void Simulate( float dt ); + + // Renders the particle + void RenderParticle( CPrecipitationParticle* pParticle, CMeshBuilder &mb ); + + void CreateWaterSplashes(); + + // Emits the actual particles + void EmitParticles( float fTimeDelta ); + + // Computes where we're gonna emit + bool ComputeEmissionArea( Vector& origin, Vector2D& size ); + + // Gets the tracer width and speed + float GetWidth() const; + float GetLength() const; + float GetSpeed() const; + + // Gets the remaining lifetime of the particle + float GetRemainingLifetime( CPrecipitationParticle* pParticle ) const; + + // Computes the wind vector + static void ComputeWindVector( ); + + // simulation methods + bool SimulateRain( CPrecipitationParticle* pParticle, float dt ); + bool SimulateSnow( CPrecipitationParticle* pParticle, float dt ); + + void PrecacheParticlePrecip( void ); + void CreateParticlePrecip( void ); + void InitializeParticlePrecip( void ); + void DispatchOuterParticlePrecip( C_BasePlayer *pPlayer, Vector vForward ); + void DispatchInnerParticlePrecip( C_BasePlayer *pPlayer, Vector vForward ); + void DestroyOuterParticlePrecip( void ); + void DestroyInnerParticlePrecip( void ); + + void UpdateParticlePrecip( C_BasePlayer *pPlayer ); + +private: + void CreateAshParticle( void ); + void CreateRainOrSnowParticle( Vector vSpawnPosition, Vector vVelocity ); + +#ifdef MAPBASE + void ClampParticlePosition( Vector &vPlayerPos, Vector &vOffsetPos, Vector &vOffsetPosNear, Vector &vOffsetPosFar ); +#endif + + // Information helpful in creating and rendering particles + IMaterial *m_MatHandle; // material used + + float m_Color[4]; // precip color + float m_Lifetime; // Precip lifetime + float m_InitialRamp; // Initial ramp value + float m_Speed; // Precip speed + float m_Width; // Tracer width + float m_Remainder; // particles we should render next time + PrecipitationType_t m_nPrecipType; // Precip type + float m_flHalfScreenWidth; // Precalculated each frame. + + float m_flDensity; + +#ifdef MAPBASE + int m_spawnflags; +#endif + + // Some state used in rendering and simulation + // Used to modify the rain density and wind from the console + static ConVar s_raindensity; + static ConVar s_rainwidth; + static ConVar s_rainlength; + static ConVar s_rainspeed; + + static Vector s_WindVector; // Stores the wind speed vector + + CUtlLinkedList m_Particles; + CUtlVector m_Splashes; + + CSmartPtr m_pAshEmitter; + TimedEvent m_tAshParticleTimer; + TimedEvent m_tAshParticleTraceTimer; + bool m_bActiveAshEmitter; + Vector m_vAshSpawnOrigin; + + int m_iAshCount; + +protected: + float m_flParticleInnerDist; //The distance at which to start drawing the inner system + char *m_pParticleInnerNearDef; //Name of the first inner system + char *m_pParticleInnerFarDef; //Name of the second inner system + char *m_pParticleOuterDef; //Name of the outer system + HPARTICLEFFECT m_pParticlePrecipInnerNear; + HPARTICLEFFECT m_pParticlePrecipInnerFar; + HPARTICLEFFECT m_pParticlePrecipOuter; + TimedEvent m_tParticlePrecipTraceTimer; + bool m_bActiveParticlePrecipEmitter; + bool m_bParticlePrecipInitialized; + +private: + CClient_Precipitation( const CClient_Precipitation & ); // not defined, not accessible +}; #endif // C_EFFECTS_H diff --git a/src/game/client/c_env_dof_controller.cpp b/src/game/client/c_env_dof_controller.cpp new file mode 100644 index 00000000..274820e7 --- /dev/null +++ b/src/game/client/c_env_dof_controller.cpp @@ -0,0 +1,88 @@ +//====== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======= +// +// Purpose: Depth of field controller entity +// +//============================================================================= + +#include "cbase.h" + +// NOTE: This has to be the last file included! +#include "tier0/memdbgon.h" + + +extern bool g_bDOFEnabled; +extern float g_flDOFNearBlurDepth; +extern float g_flDOFNearFocusDepth; +extern float g_flDOFFarFocusDepth; +extern float g_flDOFFarBlurDepth; +extern float g_flDOFNearBlurRadius; +extern float g_flDOFFarBlurRadius; + +EHANDLE g_hDOFControllerInUse = NULL; + +class C_EnvDOFController : public C_BaseEntity +{ + DECLARE_CLASS( C_EnvDOFController, C_BaseEntity ); +public: + DECLARE_CLIENTCLASS(); + + C_EnvDOFController(); + ~C_EnvDOFController(); + virtual void OnDataChanged( DataUpdateType_t updateType ); + +private: + bool m_bDOFEnabled; + float m_flNearBlurDepth; + float m_flNearFocusDepth; + float m_flFarFocusDepth; + float m_flFarBlurDepth; + float m_flNearBlurRadius; + float m_flFarBlurRadius; + +private: + C_EnvDOFController( const C_EnvDOFController & ); +}; + +IMPLEMENT_CLIENTCLASS_DT( C_EnvDOFController, DT_EnvDOFController, CEnvDOFController ) + RecvPropInt( RECVINFO(m_bDOFEnabled) ), + RecvPropFloat( RECVINFO(m_flNearBlurDepth) ), + RecvPropFloat( RECVINFO(m_flNearFocusDepth) ), + RecvPropFloat( RECVINFO(m_flFarFocusDepth) ), + RecvPropFloat( RECVINFO(m_flFarBlurDepth) ), + RecvPropFloat( RECVINFO(m_flNearBlurRadius) ), + RecvPropFloat( RECVINFO(m_flFarBlurRadius) ) +END_RECV_TABLE() + +C_EnvDOFController::C_EnvDOFController() +: m_bDOFEnabled( true ), + m_flNearBlurDepth( 20.0f ), + m_flNearFocusDepth( 100.0f ), + m_flFarFocusDepth( 250.0f ), + m_flFarBlurDepth( 1000.0f ), + m_flNearBlurRadius( 0.0f ), // no near blur by default + m_flFarBlurRadius( 5.0f ) +{ +} + +C_EnvDOFController::~C_EnvDOFController() +{ + if ( g_hDOFControllerInUse == this ) + { + g_bDOFEnabled = false; + } +} + +void C_EnvDOFController::OnDataChanged( DataUpdateType_t updateType ) +{ + BaseClass::OnDataChanged( updateType ); + + g_bDOFEnabled = m_bDOFEnabled && ( ( m_flNearBlurRadius > 0.0f ) || ( m_flFarBlurRadius > 0.0f ) ); + g_flDOFNearBlurDepth = m_flNearBlurDepth; + g_flDOFNearFocusDepth = m_flNearFocusDepth; + g_flDOFFarFocusDepth = m_flFarFocusDepth; + g_flDOFFarBlurDepth = m_flFarBlurDepth; + g_flDOFNearBlurRadius = m_flNearBlurRadius; + g_flDOFFarBlurRadius = m_flFarBlurRadius; + + g_hDOFControllerInUse = this; +} diff --git a/src/game/client/c_env_global_light.cpp b/src/game/client/c_env_global_light.cpp new file mode 100644 index 00000000..b143a79d --- /dev/null +++ b/src/game/client/c_env_global_light.cpp @@ -0,0 +1,348 @@ +//========= Copyright 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: Sunlight shadow control entity. +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" + +#include "c_baseplayer.h" +#include "tier0/vprof.h" +#ifdef MAPBASE +#include "materialsystem/itexture.h" +#endif + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + + +extern ConVar cl_sunlight_ortho_size; +extern ConVar cl_sunlight_depthbias; + +ConVar cl_globallight_freeze( "cl_globallight_freeze", "0" ); +#ifdef MAPBASE +// I imagine these values might've been designed for the ASW view. +// You can set these as KV anyway. +ConVar cl_globallight_xoffset( "cl_globallight_xoffset", "0" ); +ConVar cl_globallight_yoffset( "cl_globallight_yoffset", "0" ); + +static ConVar cl_globallight_slopescaledepthbias_shadowmap( "cl_globallight_slopescaledepthbias_shadowmap", "16", FCVAR_CHEAT ); +static ConVar cl_globallight_shadowfiltersize( "cl_globallight_shadowfiltersize", "0.1", FCVAR_CHEAT ); +static ConVar cl_globallight_depthbias_shadowmap( "cl_globallight_depthbias_shadowmap", "0.00001", FCVAR_CHEAT ); +static ConVar cl_globallight_depthres( "cl_globallight_depthres", "8192", FCVAR_CHEAT ); +#else +ConVar cl_globallight_xoffset( "cl_globallight_xoffset", "-800" ); +ConVar cl_globallight_yoffset( "cl_globallight_yoffset", "1600" ); +#endif + +//------------------------------------------------------------------------------ +// Purpose : Sunlights shadow control entity +//------------------------------------------------------------------------------ +class C_GlobalLight : public C_BaseEntity +{ +public: + DECLARE_CLASS( C_GlobalLight, C_BaseEntity ); + + DECLARE_CLIENTCLASS(); + + virtual ~C_GlobalLight(); + + void OnDataChanged( DataUpdateType_t updateType ); + void Spawn(); + bool ShouldDraw(); + + void ClientThink(); + +private: + Vector m_shadowDirection; + bool m_bEnabled; + char m_TextureName[ MAX_PATH ]; +#ifdef MAPBASE + int m_nSpotlightTextureFrame; +#endif + CTextureReference m_SpotlightTexture; + color32 m_LightColor; +#ifdef MAPBASE + float m_flBrightnessScale; + float m_flCurrentBrightnessScale; +#endif + Vector m_CurrentLinearFloatLightColor; + float m_flCurrentLinearFloatLightAlpha; + float m_flColorTransitionTime; + float m_flSunDistance; + float m_flFOV; + float m_flNearZ; + float m_flNorthOffset; +#ifdef MAPBASE + float m_flEastOffset; + float m_flForwardOffset; + float m_flOrthoSize; +#endif + bool m_bEnableShadows; + bool m_bOldEnableShadows; + + static ClientShadowHandle_t m_LocalFlashlightHandle; +}; + + +ClientShadowHandle_t C_GlobalLight::m_LocalFlashlightHandle = CLIENTSHADOW_INVALID_HANDLE; + + +IMPLEMENT_CLIENTCLASS_DT(C_GlobalLight, DT_GlobalLight, CGlobalLight) + RecvPropVector(RECVINFO(m_shadowDirection)), + RecvPropBool(RECVINFO(m_bEnabled)), + RecvPropString(RECVINFO(m_TextureName)), +#ifdef MAPBASE + RecvPropInt(RECVINFO(m_nSpotlightTextureFrame)), +#endif + /*RecvPropInt(RECVINFO(m_LightColor), 0, RecvProxy_Int32ToColor32),*/ + RecvPropInt(RECVINFO(m_LightColor), 0, RecvProxy_IntToColor32), +#ifdef MAPBASE + RecvPropFloat(RECVINFO(m_flBrightnessScale)), +#endif + RecvPropFloat(RECVINFO(m_flColorTransitionTime)), + RecvPropFloat(RECVINFO(m_flSunDistance)), + RecvPropFloat(RECVINFO(m_flFOV)), + RecvPropFloat(RECVINFO(m_flNearZ)), + RecvPropFloat(RECVINFO(m_flNorthOffset)), +#ifdef MAPBASE + RecvPropFloat(RECVINFO(m_flEastOffset)), + RecvPropFloat(RECVINFO(m_flForwardOffset)), + RecvPropFloat(RECVINFO(m_flOrthoSize)), +#endif + RecvPropBool(RECVINFO(m_bEnableShadows)), +END_RECV_TABLE() + + +C_GlobalLight::~C_GlobalLight() +{ + if ( m_LocalFlashlightHandle != CLIENTSHADOW_INVALID_HANDLE ) + { + g_pClientShadowMgr->DestroyFlashlight( m_LocalFlashlightHandle ); + m_LocalFlashlightHandle = CLIENTSHADOW_INVALID_HANDLE; + } +} + +void C_GlobalLight::OnDataChanged( DataUpdateType_t updateType ) +{ + if ( updateType == DATA_UPDATE_CREATED ) + { + m_SpotlightTexture.Init( m_TextureName, TEXTURE_GROUP_OTHER, true ); + } +#ifdef MAPBASE + else //if ( updateType == DATA_UPDATE_DATATABLE_CHANGED ) + { + // It could've been changed via input + if( !FStrEq(m_SpotlightTexture->GetName(), m_TextureName) ) + { + m_SpotlightTexture.Init( m_TextureName, TEXTURE_GROUP_OTHER, true ); + } + } +#endif + + BaseClass::OnDataChanged( updateType ); +} + +void C_GlobalLight::Spawn() +{ + BaseClass::Spawn(); + + m_bOldEnableShadows = m_bEnableShadows; + + SetNextClientThink( CLIENT_THINK_ALWAYS ); +} + +//------------------------------------------------------------------------------ +// We don't draw... +//------------------------------------------------------------------------------ +bool C_GlobalLight::ShouldDraw() +{ + return false; +} + +void C_GlobalLight::ClientThink() +{ + VPROF("C_GlobalLight::ClientThink"); + + bool bSupressWorldLights = false; + + if ( cl_globallight_freeze.GetBool() == true ) + { + return; + } + + if ( m_bEnabled ) + { + Vector vLinearFloatLightColor( m_LightColor.r, m_LightColor.g, m_LightColor.b ); + float flLinearFloatLightAlpha = m_LightColor.a; + +#ifdef MAPBASE + if ( m_CurrentLinearFloatLightColor != vLinearFloatLightColor || m_flCurrentLinearFloatLightAlpha != flLinearFloatLightAlpha || m_flCurrentBrightnessScale != m_flBrightnessScale ) + { + if (m_flColorTransitionTime != 0.0f) + { + float flColorTransitionSpeed = gpGlobals->frametime * m_flColorTransitionTime * 255.0f; + + m_CurrentLinearFloatLightColor.x = Approach( vLinearFloatLightColor.x, m_CurrentLinearFloatLightColor.x, flColorTransitionSpeed ); + m_CurrentLinearFloatLightColor.y = Approach( vLinearFloatLightColor.y, m_CurrentLinearFloatLightColor.y, flColorTransitionSpeed ); + m_CurrentLinearFloatLightColor.z = Approach( vLinearFloatLightColor.z, m_CurrentLinearFloatLightColor.z, flColorTransitionSpeed ); + m_flCurrentLinearFloatLightAlpha = Approach( flLinearFloatLightAlpha, m_flCurrentLinearFloatLightAlpha, flColorTransitionSpeed ); + m_flCurrentBrightnessScale = Approach( m_flBrightnessScale, m_flCurrentBrightnessScale, flColorTransitionSpeed ); + } + else + { + // Just do it instantly + m_CurrentLinearFloatLightColor.x = vLinearFloatLightColor.x; + m_CurrentLinearFloatLightColor.y = vLinearFloatLightColor.y; + m_CurrentLinearFloatLightColor.z = vLinearFloatLightColor.z; + m_flCurrentLinearFloatLightAlpha = flLinearFloatLightAlpha; + m_flCurrentBrightnessScale = m_flBrightnessScale; + } + } +#else + if ( m_CurrentLinearFloatLightColor != vLinearFloatLightColor || m_flCurrentLinearFloatLightAlpha != flLinearFloatLightAlpha ) + { + float flColorTransitionSpeed = gpGlobals->frametime * m_flColorTransitionTime * 255.0f; + + m_CurrentLinearFloatLightColor.x = Approach( vLinearFloatLightColor.x, m_CurrentLinearFloatLightColor.x, flColorTransitionSpeed ); + m_CurrentLinearFloatLightColor.y = Approach( vLinearFloatLightColor.y, m_CurrentLinearFloatLightColor.y, flColorTransitionSpeed ); + m_CurrentLinearFloatLightColor.z = Approach( vLinearFloatLightColor.z, m_CurrentLinearFloatLightColor.z, flColorTransitionSpeed ); + m_flCurrentLinearFloatLightAlpha = Approach( flLinearFloatLightAlpha, m_flCurrentLinearFloatLightAlpha, flColorTransitionSpeed ); + } +#endif + + FlashlightState_t state; + + Vector vDirection = m_shadowDirection; + VectorNormalize( vDirection ); + + //Vector vViewUp = Vector( 0.0f, 1.0f, 0.0f ); + Vector vSunDirection2D = vDirection; + vSunDirection2D.z = 0.0f; + + /*HACK_GETLOCALPLAYER_GUARD( "C_GlobalLight::ClientThink" );*/ + + if ( !C_BasePlayer::GetLocalPlayer() ) + return; + + Vector vPos; + QAngle EyeAngles; + float flZNear, flZFar, flFov; + + C_BasePlayer::GetLocalPlayer()->CalcView( vPos, EyeAngles, flZNear, flZFar, flFov ); +// Vector vPos = C_BasePlayer::GetLocalPlayer()->GetAbsOrigin(); + +// vPos = Vector( 0.0f, 0.0f, 500.0f ); + vPos = ( vPos + vSunDirection2D * m_flNorthOffset ) - vDirection * m_flSunDistance; +#ifdef MAPBASE + vPos += Vector( m_flEastOffset + cl_globallight_xoffset.GetFloat(), m_flForwardOffset + cl_globallight_yoffset.GetFloat(), 0.0f ); +#else + vPos += Vector( cl_globallight_xoffset.GetFloat(), cl_globallight_yoffset.GetFloat(), 0.0f ); +#endif + + QAngle angAngles; + VectorAngles( vDirection, angAngles ); + + Vector vForward, vRight, vUp; + AngleVectors( angAngles, &vForward, &vRight, &vUp ); + + state.m_fHorizontalFOVDegrees = m_flFOV; + state.m_fVerticalFOVDegrees = m_flFOV; + + state.m_vecLightOrigin = vPos; + BasisToQuaternion( vForward, vRight, vUp, state.m_quatOrientation ); + + state.m_fQuadraticAtten = 0.0f; + state.m_fLinearAtten = m_flSunDistance * 2.0f; + state.m_fConstantAtten = 0.0f; + state.m_FarZAtten = m_flSunDistance * 2.0f; +#ifdef MAPBASE + float flAlpha = m_flCurrentLinearFloatLightAlpha * (1.0f / 255.0f); + state.m_Color[0] = (m_CurrentLinearFloatLightColor.x * ( 1.0f / 255.0f ) * flAlpha) * m_flCurrentBrightnessScale; + state.m_Color[1] = (m_CurrentLinearFloatLightColor.y * ( 1.0f / 255.0f ) * flAlpha) * m_flCurrentBrightnessScale; + state.m_Color[2] = (m_CurrentLinearFloatLightColor.z * ( 1.0f / 255.0f ) * flAlpha) * m_flCurrentBrightnessScale; +#else + state.m_Color[0] = m_CurrentLinearFloatLightColor.x * ( 1.0f / 255.0f ) * m_flCurrentLinearFloatLightAlpha; + state.m_Color[1] = m_CurrentLinearFloatLightColor.y * ( 1.0f / 255.0f ) * m_flCurrentLinearFloatLightAlpha; + state.m_Color[2] = m_CurrentLinearFloatLightColor.z * ( 1.0f / 255.0f ) * m_flCurrentLinearFloatLightAlpha; +#endif + state.m_Color[3] = 0.0f; // fixme: need to make ambient work m_flAmbient; + state.m_NearZ = 4.0f; + state.m_FarZ = m_flSunDistance * 2.0f; + state.m_fBrightnessScale = 2.0f; + state.m_bGlobalLight = true; + +#ifdef MAPBASE + float flOrthoSize = m_flOrthoSize; +#else + float flOrthoSize = 1000.0f; +#endif + + if ( flOrthoSize > 0 ) + { + state.m_bOrtho = true; + state.m_fOrthoLeft = -flOrthoSize; + state.m_fOrthoTop = -flOrthoSize; + state.m_fOrthoRight = flOrthoSize; + state.m_fOrthoBottom = flOrthoSize; + } + else + { + state.m_bOrtho = false; + } + +#ifdef MAPBASE + //state.m_bDrawShadowFrustum = true; // Don't draw that huge debug thing + state.m_flShadowMapResolution = cl_globallight_depthres.GetFloat(); + state.m_flShadowFilterSize = cl_globallight_shadowfiltersize.GetFloat(); + state.m_flShadowSlopeScaleDepthBias = cl_globallight_slopescaledepthbias_shadowmap.GetFloat(); + state.m_flShadowDepthBias = cl_globallight_depthbias_shadowmap.GetFloat(); + state.m_bEnableShadows = m_bEnableShadows; + state.m_pSpotlightTexture = m_SpotlightTexture; + state.m_nSpotlightTextureFrame = m_nSpotlightTextureFrame; +#else + state.m_bDrawShadowFrustum = true; + /*state.m_flShadowSlopeScaleDepthBias = g_pMaterialSystemHardwareConfig->GetShadowSlopeScaleDepthBias();; + state.m_flShadowDepthBias = g_pMaterialSystemHardwareConfig->GetShadowDepthBias();*/ + state.m_bEnableShadows = m_bEnableShadows; + state.m_pSpotlightTexture = m_SpotlightTexture; + state.m_nSpotlightTextureFrame = 0; +#endif + + state.m_nShadowQuality = 1; // Allow entity to affect shadow quality +// state.m_bShadowHighRes = true; + + if ( m_bOldEnableShadows != m_bEnableShadows ) + { + // If they change the shadow enable/disable, we need to make a new handle + if ( m_LocalFlashlightHandle != CLIENTSHADOW_INVALID_HANDLE ) + { + g_pClientShadowMgr->DestroyFlashlight( m_LocalFlashlightHandle ); + m_LocalFlashlightHandle = CLIENTSHADOW_INVALID_HANDLE; + } + + m_bOldEnableShadows = m_bEnableShadows; + } + + if( m_LocalFlashlightHandle == CLIENTSHADOW_INVALID_HANDLE ) + { + m_LocalFlashlightHandle = g_pClientShadowMgr->CreateFlashlight( state ); + } + else + { + g_pClientShadowMgr->UpdateFlashlightState( m_LocalFlashlightHandle, state ); + g_pClientShadowMgr->UpdateProjectedTexture( m_LocalFlashlightHandle, true ); + } + + bSupressWorldLights = m_bEnableShadows; + } + else if ( m_LocalFlashlightHandle != CLIENTSHADOW_INVALID_HANDLE ) + { + g_pClientShadowMgr->DestroyFlashlight( m_LocalFlashlightHandle ); + m_LocalFlashlightHandle = CLIENTSHADOW_INVALID_HANDLE; + } + + g_pClientShadowMgr->SetShadowFromWorldLightsEnabled( !bSupressWorldLights ); + + BaseClass::ClientThink(); +} \ No newline at end of file diff --git a/src/game/client/c_env_projectedtexture.cpp b/src/game/client/c_env_projectedtexture.cpp index 310c3a27..66449652 100644 --- a/src/game/client/c_env_projectedtexture.cpp +++ b/src/game/client/c_env_projectedtexture.cpp @@ -5,6 +5,13 @@ //============================================================================= #include "cbase.h" +#ifdef ASW_PROJECTED_TEXTURES +#include "C_Env_Projected_Texture.h" +#include "vprof.h" +#endif +#ifdef MAPBASE +#include "materialsystem/itexture.h" +#endif #include "shareddefs.h" #include "materialsystem/imesh.h" #include "materialsystem/imaterial.h" @@ -17,8 +24,480 @@ // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" +#ifdef ASW_PROJECTED_TEXTURES +extern ConVarRef mat_slopescaledepthbias_shadowmap; +extern ConVarRef mat_depthbias_shadowmap; + +float C_EnvProjectedTexture::m_flVisibleBBoxMinHeight = -FLT_MAX; + + +IMPLEMENT_CLIENTCLASS_DT( C_EnvProjectedTexture, DT_EnvProjectedTexture, CEnvProjectedTexture ) + RecvPropEHandle( RECVINFO( m_hTargetEntity ) ), +#ifdef MAPBASE + RecvPropBool( RECVINFO( m_bDontFollowTarget )), +#endif + RecvPropBool( RECVINFO( m_bState ) ), + RecvPropBool( RECVINFO( m_bAlwaysUpdate ) ), + RecvPropFloat( RECVINFO( m_flLightFOV ) ), +#ifdef MAPBASE + RecvPropFloat( RECVINFO( m_flLightHorFOV ) ), +#endif + RecvPropBool( RECVINFO( m_bEnableShadows ) ), + RecvPropBool( RECVINFO( m_bLightOnlyTarget ) ), + RecvPropBool( RECVINFO( m_bLightWorld ) ), + RecvPropBool( RECVINFO( m_bCameraSpace ) ), + RecvPropFloat( RECVINFO( m_flBrightnessScale ) ), + RecvPropInt( RECVINFO( m_LightColor ), 0, RecvProxy_IntToColor32 ), + RecvPropFloat( RECVINFO( m_flColorTransitionTime ) ), + RecvPropFloat( RECVINFO( m_flAmbient ) ), + RecvPropString( RECVINFO( m_SpotlightTextureName ) ), + RecvPropInt( RECVINFO( m_nSpotlightTextureFrame ) ), + RecvPropFloat( RECVINFO( m_flNearZ ) ), + RecvPropFloat( RECVINFO( m_flFarZ ) ), + RecvPropInt( RECVINFO( m_nShadowQuality ) ), +#ifdef MAPBASE + RecvPropFloat( RECVINFO( m_flConstantAtten ) ), + RecvPropFloat( RECVINFO( m_flLinearAtten ) ), + RecvPropFloat( RECVINFO( m_flQuadraticAtten ) ), + RecvPropFloat( RECVINFO( m_flShadowAtten ) ), + RecvPropFloat( RECVINFO( m_flShadowFilter ) ), + RecvPropBool( RECVINFO( m_bAlwaysDraw ) ), + + // Not needed on the client right now, change when it actually is needed + //RecvPropBool( RECVINFO( m_bProjectedTextureVersion ) ), +#endif +END_RECV_TABLE() + +C_EnvProjectedTexture *C_EnvProjectedTexture::Create( ) +{ + C_EnvProjectedTexture *pEnt = new C_EnvProjectedTexture(); + + pEnt->m_flNearZ = 4.0f; + pEnt->m_flFarZ = 2000.0f; +// strcpy( pEnt->m_SpotlightTextureName, "particle/rj" ); + pEnt->m_bLightWorld = true; + pEnt->m_bLightOnlyTarget = false; + pEnt->m_nShadowQuality = 1; + pEnt->m_flLightFOV = 10.0f; +#ifdef MAPBASE + pEnt->m_flLightHorFOV = 10.0f; +#endif + pEnt->m_LightColor.r = 255; + pEnt->m_LightColor.g = 255; + pEnt->m_LightColor.b = 255; + pEnt->m_LightColor.a = 255; + pEnt->m_bEnableShadows = false; + pEnt->m_flColorTransitionTime = 1.0f; + pEnt->m_bCameraSpace = false; + pEnt->SetAbsAngles( QAngle( 90, 0, 0 ) ); + pEnt->m_bAlwaysUpdate = true; + pEnt->m_bState = true; +#ifdef MAPBASE + pEnt->m_bAlwaysDraw = false; + pEnt->m_flConstantAtten = 0.0f; + pEnt->m_flLinearAtten = 100.0f; + pEnt->m_flQuadraticAtten = 0.0f; + pEnt->m_flShadowAtten = 0.0f; + pEnt->m_flShadowFilter = 0.5f; + //pEnt->m_bProjectedTextureVersion = 1; +#endif + + return pEnt; +} + +C_EnvProjectedTexture::C_EnvProjectedTexture( void ) +{ + m_LightHandle = CLIENTSHADOW_INVALID_HANDLE; + m_bForceUpdate = true; +#ifndef MAPBASE + AddToEntityList( ENTITY_LIST_SIMULATE ); +#endif +} + +C_EnvProjectedTexture::~C_EnvProjectedTexture( void ) +{ + ShutDownLightHandle(); +} + +void C_EnvProjectedTexture::ShutDownLightHandle( void ) +{ + // Clear out the light + if( m_LightHandle != CLIENTSHADOW_INVALID_HANDLE ) + { + g_pClientShadowMgr->DestroyFlashlight( m_LightHandle ); + m_LightHandle = CLIENTSHADOW_INVALID_HANDLE; + } +} + + +void C_EnvProjectedTexture::SetLightColor( byte r, byte g, byte b, byte a ) +{ + m_LightColor.r = r; + m_LightColor.g = g; + m_LightColor.b = b; + m_LightColor.a = a; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : updateType - +//----------------------------------------------------------------------------- +void C_EnvProjectedTexture::OnDataChanged( DataUpdateType_t updateType ) +{ + if ( updateType == DATA_UPDATE_CREATED ) + { + m_SpotlightTexture.Init( m_SpotlightTextureName, TEXTURE_GROUP_OTHER, true ); + } +#ifdef MAPBASE + else //if ( updateType == DATA_UPDATE_DATATABLE_CHANGED ) + { + // It could've been changed via input + if( !FStrEq(m_SpotlightTexture->GetName(), m_SpotlightTextureName) ) + { + m_SpotlightTexture.Init( m_SpotlightTextureName, TEXTURE_GROUP_OTHER, true ); + } + } +#endif + + m_bForceUpdate = true; + UpdateLight(); + BaseClass::OnDataChanged( updateType ); +} + +static ConVar asw_perf_wtf("asw_perf_wtf", "0", FCVAR_DEVELOPMENTONLY, "Disable updating of projected shadow textures from UpdateLight" ); +void C_EnvProjectedTexture::UpdateLight( void ) +{ + VPROF("C_EnvProjectedTexture::UpdateLight"); + bool bVisible = true; + + Vector vLinearFloatLightColor( m_LightColor.r, m_LightColor.g, m_LightColor.b ); + float flLinearFloatLightAlpha = m_LightColor.a; + + if ( m_bAlwaysUpdate ) + { + m_bForceUpdate = true; + } + +#ifdef MAPBASE + if ( m_CurrentLinearFloatLightColor != vLinearFloatLightColor || m_flCurrentLinearFloatLightAlpha != flLinearFloatLightAlpha || m_flCurrentBrightnessScale != m_flBrightnessScale ) + { + if (m_flColorTransitionTime != 0.0f) + { + float flColorTransitionSpeed = gpGlobals->frametime * m_flColorTransitionTime * 255.0f; + + m_CurrentLinearFloatLightColor.x = Approach( vLinearFloatLightColor.x, m_CurrentLinearFloatLightColor.x, flColorTransitionSpeed ); + m_CurrentLinearFloatLightColor.y = Approach( vLinearFloatLightColor.y, m_CurrentLinearFloatLightColor.y, flColorTransitionSpeed ); + m_CurrentLinearFloatLightColor.z = Approach( vLinearFloatLightColor.z, m_CurrentLinearFloatLightColor.z, flColorTransitionSpeed ); + m_flCurrentLinearFloatLightAlpha = Approach( flLinearFloatLightAlpha, m_flCurrentLinearFloatLightAlpha, flColorTransitionSpeed ); + m_flCurrentBrightnessScale = Approach( m_flBrightnessScale, m_flCurrentBrightnessScale, flColorTransitionSpeed ); + } + else + { + // Just do it instantly + m_CurrentLinearFloatLightColor.x = vLinearFloatLightColor.x; + m_CurrentLinearFloatLightColor.y = vLinearFloatLightColor.y; + m_CurrentLinearFloatLightColor.z = vLinearFloatLightColor.z; + m_flCurrentLinearFloatLightAlpha = flLinearFloatLightAlpha; + m_flCurrentBrightnessScale = m_flBrightnessScale; + } + + m_bForceUpdate = true; + } +#else + if ( m_CurrentLinearFloatLightColor != vLinearFloatLightColor || m_flCurrentLinearFloatLightAlpha != flLinearFloatLightAlpha ) + { + float flColorTransitionSpeed = gpGlobals->frametime * m_flColorTransitionTime * 255.0f; + + m_CurrentLinearFloatLightColor.x = Approach( vLinearFloatLightColor.x, m_CurrentLinearFloatLightColor.x, flColorTransitionSpeed ); + m_CurrentLinearFloatLightColor.y = Approach( vLinearFloatLightColor.y, m_CurrentLinearFloatLightColor.y, flColorTransitionSpeed ); + m_CurrentLinearFloatLightColor.z = Approach( vLinearFloatLightColor.z, m_CurrentLinearFloatLightColor.z, flColorTransitionSpeed ); + m_flCurrentLinearFloatLightAlpha = Approach( flLinearFloatLightAlpha, m_flCurrentLinearFloatLightAlpha, flColorTransitionSpeed ); + + m_bForceUpdate = true; + } +#endif + + if ( !m_bForceUpdate ) + { + bVisible = IsBBoxVisible(); + } + + if ( m_bState == false || !bVisible ) + { + // Spotlight's extents aren't in view + ShutDownLightHandle(); + + return; + } + + if ( m_LightHandle == CLIENTSHADOW_INVALID_HANDLE || m_hTargetEntity != NULL || m_bForceUpdate ) + { + Vector vForward, vRight, vUp, vPos = GetAbsOrigin(); + FlashlightState_t state; + +#ifdef MAPBASE + if ( m_hTargetEntity != NULL && !m_bDontFollowTarget ) +#else + if ( m_hTargetEntity != NULL ) +#endif + { + if ( m_bCameraSpace ) + { + const QAngle &angles = GetLocalAngles(); + + C_BasePlayer *pPlayer = C_BasePlayer::GetLocalPlayer(); + if( pPlayer ) + { + const QAngle playerAngles = pPlayer->GetAbsAngles(); + + Vector vPlayerForward, vPlayerRight, vPlayerUp; + AngleVectors( playerAngles, &vPlayerForward, &vPlayerRight, &vPlayerUp ); + + matrix3x4_t mRotMatrix; + AngleMatrix( angles, mRotMatrix ); + + VectorITransform( vPlayerForward, mRotMatrix, vForward ); + VectorITransform( vPlayerRight, mRotMatrix, vRight ); + VectorITransform( vPlayerUp, mRotMatrix, vUp ); + + float dist = (m_hTargetEntity->GetAbsOrigin() - GetAbsOrigin()).Length(); + vPos = m_hTargetEntity->GetAbsOrigin() - vForward*dist; + + VectorNormalize( vForward ); + VectorNormalize( vRight ); + VectorNormalize( vUp ); + } + } + else + { + vForward = m_hTargetEntity->GetAbsOrigin() - GetAbsOrigin(); + VectorNormalize( vForward ); + + // JasonM - unimplemented + Assert (0); + + //Quaternion q = DirectionToOrientation( dir ); + + + // + // JasonM - set up vRight, vUp + // + + // VectorNormalize( vRight ); + // VectorNormalize( vUp ); + + VectorVectors( vForward, vRight, vUp ); + } + } + else + { + AngleVectors( GetAbsAngles(), &vForward, &vRight, &vUp ); + } + +#ifdef MAPBASE + float fHighFOV; + if( m_flLightFOV > m_flLightHorFOV ) + fHighFOV = m_flLightFOV; + else + fHighFOV = m_flLightHorFOV; + + state.m_fHorizontalFOVDegrees = m_flLightHorFOV; +#else + state.m_fHorizontalFOVDegrees = m_flLightFOV; +#endif + state.m_fVerticalFOVDegrees = m_flLightFOV; + + state.m_vecLightOrigin = vPos; + BasisToQuaternion( vForward, vRight, vUp, state.m_quatOrientation ); + state.m_NearZ = m_flNearZ; + state.m_FarZ = m_flFarZ; + + // quickly check the proposed light's bbox against the view frustum to determine whether we + // should bother to create it, if it doesn't exist, or cull it, if it does. + // get the half-widths of the near and far planes, + // based on the FOV which is in degrees. Remember that + // on planet Valve, x is forward, y left, and z up. +#ifdef MAPBASE + const float tanHalfAngle = tan( fHighFOV * ( M_PI/180.0f ) * 0.5f ); +#else + const float tanHalfAngle = tan( m_flLightFOV * ( M_PI/180.0f ) * 0.5f ); +#endif + const float halfWidthNear = tanHalfAngle * m_flNearZ; + const float halfWidthFar = tanHalfAngle * m_flFarZ; + // now we can build coordinates in local space: the near rectangle is eg + // (0, -halfWidthNear, -halfWidthNear), (0, halfWidthNear, -halfWidthNear), + // (0, halfWidthNear, halfWidthNear), (0, -halfWidthNear, halfWidthNear) + + VectorAligned vNearRect[4] = { + VectorAligned( m_flNearZ, -halfWidthNear, -halfWidthNear), VectorAligned( m_flNearZ, halfWidthNear, -halfWidthNear), + VectorAligned( m_flNearZ, halfWidthNear, halfWidthNear), VectorAligned( m_flNearZ, -halfWidthNear, halfWidthNear) + }; + + VectorAligned vFarRect[4] = { + VectorAligned( m_flFarZ, -halfWidthFar, -halfWidthFar), VectorAligned( m_flFarZ, halfWidthFar, -halfWidthFar), + VectorAligned( m_flFarZ, halfWidthFar, halfWidthFar), VectorAligned( m_flFarZ, -halfWidthFar, halfWidthFar) + }; + + matrix3x4_t matOrientation( vForward, -vRight, vUp, vPos ); + + enum + { + kNEAR = 0, + kFAR = 1, + }; + VectorAligned vOutRects[2][4]; + + for ( int i = 0 ; i < 4 ; ++i ) + { + VectorTransform( vNearRect[i].Base(), matOrientation, vOutRects[0][i].Base() ); + } + for ( int i = 0 ; i < 4 ; ++i ) + { + VectorTransform( vFarRect[i].Base(), matOrientation, vOutRects[1][i].Base() ); + } + + // now take the min and max extents for the bbox, and see if it is visible. + Vector mins = **vOutRects; + Vector maxs = **vOutRects; + for ( int i = 1; i < 8 ; ++i ) + { + VectorMin( mins, *(*vOutRects+i), mins ); + VectorMax( maxs, *(*vOutRects+i), maxs ); + } + +#if 0 //for debugging the visibility frustum we just calculated + NDebugOverlay::Triangle( vOutRects[0][0], vOutRects[0][1], vOutRects[0][2], 255, 0, 0, 100, true, 0.0f ); //first tri + NDebugOverlay::Triangle( vOutRects[0][2], vOutRects[0][1], vOutRects[0][0], 255, 0, 0, 100, true, 0.0f ); //make it double sided + NDebugOverlay::Triangle( vOutRects[0][2], vOutRects[0][3], vOutRects[0][0], 255, 0, 0, 100, true, 0.0f ); //second tri + NDebugOverlay::Triangle( vOutRects[0][0], vOutRects[0][3], vOutRects[0][2], 255, 0, 0, 100, true, 0.0f ); //make it double sided + + NDebugOverlay::Triangle( vOutRects[1][0], vOutRects[1][1], vOutRects[1][2], 0, 0, 255, 100, true, 0.0f ); //first tri + NDebugOverlay::Triangle( vOutRects[1][2], vOutRects[1][1], vOutRects[1][0], 0, 0, 255, 100, true, 0.0f ); //make it double sided + NDebugOverlay::Triangle( vOutRects[1][2], vOutRects[1][3], vOutRects[1][0], 0, 0, 255, 100, true, 0.0f ); //second tri + NDebugOverlay::Triangle( vOutRects[1][0], vOutRects[1][3], vOutRects[1][2], 0, 0, 255, 100, true, 0.0f ); //make it double sided + + NDebugOverlay::Box( vec3_origin, mins, maxs, 0, 255, 0, 100, 0.0f ); +#endif + + bool bVisible = IsBBoxVisible( mins, maxs ); + if (!bVisible) + { + // Spotlight's extents aren't in view + if ( m_LightHandle != CLIENTSHADOW_INVALID_HANDLE ) + { + ShutDownLightHandle(); + } + + return; + } + + float flAlpha = m_flCurrentLinearFloatLightAlpha * ( 1.0f / 255.0f ); + +#ifdef MAPBASE + state.m_fConstantAtten = m_flConstantAtten; + state.m_fLinearAtten = m_flLinearAtten; + state.m_fQuadraticAtten = m_flQuadraticAtten; + state.m_FarZAtten = m_flFarZ; + state.m_Color[0] = (m_CurrentLinearFloatLightColor.x * ( 1.0f / 255.0f ) * flAlpha) * m_flCurrentBrightnessScale; + state.m_Color[1] = (m_CurrentLinearFloatLightColor.y * ( 1.0f / 255.0f ) * flAlpha) * m_flCurrentBrightnessScale; + state.m_Color[2] = (m_CurrentLinearFloatLightColor.z * ( 1.0f / 255.0f ) * flAlpha) * m_flCurrentBrightnessScale; + state.m_Color[3] = 0.0f; // fixme: need to make ambient work m_flAmbient; + state.m_flShadowSlopeScaleDepthBias = mat_slopescaledepthbias_shadowmap.GetFloat(); + state.m_flShadowDepthBias = mat_depthbias_shadowmap.GetFloat(); + state.m_flShadowAtten = m_flShadowAtten; + state.m_flShadowFilterSize = m_flShadowFilter; +#else + state.m_fQuadraticAtten = 0.0; + state.m_fLinearAtten = 100; + state.m_fConstantAtten = 0.0f; + state.m_FarZAtten = m_flFarZ; + state.m_fBrightnessScale = m_flBrightnessScale; + state.m_Color[0] = m_CurrentLinearFloatLightColor.x * ( 1.0f / 255.0f ) * flAlpha; + state.m_Color[1] = m_CurrentLinearFloatLightColor.y * ( 1.0f / 255.0f ) * flAlpha; + state.m_Color[2] = m_CurrentLinearFloatLightColor.z * ( 1.0f / 255.0f ) * flAlpha; + state.m_Color[3] = 0.0f; // fixme: need to make ambient work m_flAmbient; + state.m_flShadowSlopeScaleDepthBias = g_pMaterialSystemHardwareConfig->GetShadowSlopeScaleDepthBias(); + state.m_flShadowDepthBias = g_pMaterialSystemHardwareConfig->GetShadowDepthBias(); +#endif + state.m_bEnableShadows = m_bEnableShadows; + state.m_pSpotlightTexture = m_SpotlightTexture; + state.m_nSpotlightTextureFrame = m_nSpotlightTextureFrame; + + state.m_nShadowQuality = m_nShadowQuality; // Allow entity to affect shadow quality + +#ifdef MAPBASE + state.m_bAlwaysDraw = m_bAlwaysDraw; +#endif + + if( m_LightHandle == CLIENTSHADOW_INVALID_HANDLE ) + { + m_LightHandle = g_pClientShadowMgr->CreateFlashlight( state ); + + if ( m_LightHandle != CLIENTSHADOW_INVALID_HANDLE ) + { + m_bForceUpdate = false; + } + } + else + { + g_pClientShadowMgr->UpdateFlashlightState( m_LightHandle, state ); + m_bForceUpdate = false; + } + + g_pClientShadowMgr->GetFrustumExtents( m_LightHandle, m_vecExtentsMin, m_vecExtentsMax ); + + m_vecExtentsMin = m_vecExtentsMin - GetAbsOrigin(); + m_vecExtentsMax = m_vecExtentsMax - GetAbsOrigin(); + } + + if( m_bLightOnlyTarget ) + { + g_pClientShadowMgr->SetFlashlightTarget( m_LightHandle, m_hTargetEntity ); + } + else + { + g_pClientShadowMgr->SetFlashlightTarget( m_LightHandle, NULL ); + } + + g_pClientShadowMgr->SetFlashlightLightWorld( m_LightHandle, m_bLightWorld ); + + if ( !asw_perf_wtf.GetBool() && !m_bForceUpdate ) + { + g_pClientShadowMgr->UpdateProjectedTexture( m_LightHandle, true ); + } +} + +void C_EnvProjectedTexture::Simulate( void ) +{ + UpdateLight(); + + BaseClass::Simulate(); +} + +bool C_EnvProjectedTexture::IsBBoxVisible( Vector vecExtentsMin, Vector vecExtentsMax ) +{ +#ifdef MAPBASE + if (m_bAlwaysDraw) + return true; +#endif + + // Z position clamped to the min height (but must be less than the max) + float flVisibleBBoxMinHeight = MIN( vecExtentsMax.z - 1.0f, m_flVisibleBBoxMinHeight ); + vecExtentsMin.z = MAX( vecExtentsMin.z, flVisibleBBoxMinHeight ); + + // Check if the bbox is in the view + return !engine->CullBox( vecExtentsMin, vecExtentsMax ); +} + +#else + +#ifndef MAPBASE static ConVar mat_slopescaledepthbias_shadowmap( "mat_slopescaledepthbias_shadowmap", "16", FCVAR_CHEAT ); static ConVar mat_depthbias_shadowmap( "mat_depthbias_shadowmap", "0.0005", FCVAR_CHEAT ); +#else +static ConVar mat_slopescaledepthbias_shadowmap( "mat_slopescaledepthbias_shadowmap", "4", FCVAR_CHEAT ); +static ConVar mat_depthbias_shadowmap( "mat_depthbias_shadowmap", "0.00001", FCVAR_CHEAT ); +#endif //----------------------------------------------------------------------------- // Purpose: @@ -34,7 +513,11 @@ public: virtual void Simulate(); +#ifdef MAPBASE + void UpdateLight(); +#else void UpdateLight( bool bForceUpdate ); +#endif C_EnvProjectedTexture(); ~C_EnvProjectedTexture(); @@ -42,10 +525,16 @@ public: private: ClientShadowHandle_t m_LightHandle; +#ifdef MAPBASE + bool m_bForceUpdate; +#endif EHANDLE m_hTargetEntity; bool m_bState; +#ifdef MAPBASE + bool m_bAlwaysUpdate; +#endif float m_flLightFOV; bool m_bEnableShadows; bool m_bLightOnlyTarget; @@ -63,6 +552,9 @@ private: IMPLEMENT_CLIENTCLASS_DT( C_EnvProjectedTexture, DT_EnvProjectedTexture, CEnvProjectedTexture ) RecvPropEHandle( RECVINFO( m_hTargetEntity ) ), RecvPropBool( RECVINFO( m_bState ) ), +#ifdef MAPBASE + RecvPropBool( RECVINFO( m_bAlwaysUpdate ) ), +#endif RecvPropFloat( RECVINFO( m_flLightFOV ) ), RecvPropBool( RECVINFO( m_bEnableShadows ) ), RecvPropBool( RECVINFO( m_bLightOnlyTarget ) ), @@ -103,12 +595,22 @@ void C_EnvProjectedTexture::ShutDownLightHandle( void ) //----------------------------------------------------------------------------- void C_EnvProjectedTexture::OnDataChanged( DataUpdateType_t updateType ) { +#ifdef MAPBASE + m_bForceUpdate = true; + UpdateLight(); +#else UpdateLight( true ); +#endif BaseClass::OnDataChanged( updateType ); } +#ifndef MAPBASE void C_EnvProjectedTexture::UpdateLight( bool bForceUpdate ) +#else +void C_EnvProjectedTexture::UpdateLight() +#endif { +#ifndef MAPBASE if ( m_bState == false ) { if ( m_LightHandle != CLIENTSHADOW_INVALID_HANDLE ) @@ -118,7 +620,25 @@ void C_EnvProjectedTexture::UpdateLight( bool bForceUpdate ) return; } +#else + if ( m_bAlwaysUpdate ) + { + m_bForceUpdate = true; + } + if ( m_bState == false ) + { + // Spotlight's extents aren't in view + ShutDownLightHandle(); + + return; + } +#endif + +#ifdef MAPBASE + if ( m_LightHandle == CLIENTSHADOW_INVALID_HANDLE || m_hTargetEntity != NULL || m_bForceUpdate ) + { +#endif Vector vForward, vRight, vUp, vPos = GetAbsOrigin(); FlashlightState_t state; @@ -153,8 +673,24 @@ void C_EnvProjectedTexture::UpdateLight( bool bForceUpdate ) } else { +#ifndef MAPBASE vForward = m_hTargetEntity->GetAbsOrigin() - GetAbsOrigin(); VectorNormalize( vForward ); +#else + // VXP: Fixing targeting + Vector vecToTarget; + QAngle vecAngles; + if (m_hTargetEntity == NULL) + { + vecAngles = GetAbsAngles(); + } + else + { + vecToTarget = m_hTargetEntity->GetAbsOrigin() - GetAbsOrigin(); + VectorAngles(vecToTarget, vecAngles); + } + AngleVectors(vecAngles, &vForward, &vRight, &vUp); +#endif // JasonM - unimplemented Assert (0); @@ -204,11 +740,19 @@ void C_EnvProjectedTexture::UpdateLight( bool bForceUpdate ) } else { +#ifndef MAPBASE if ( m_hTargetEntity != NULL || bForceUpdate == true ) { g_pClientShadowMgr->UpdateFlashlightState( m_LightHandle, state ); } +#else + g_pClientShadowMgr->UpdateFlashlightState( m_LightHandle, state ); + m_bForceUpdate = false; +#endif } +#ifdef MAPBASE + } +#endif if( m_bLightOnlyTarget ) { @@ -221,16 +765,29 @@ void C_EnvProjectedTexture::UpdateLight( bool bForceUpdate ) g_pClientShadowMgr->SetFlashlightLightWorld( m_LightHandle, m_bLightWorld ); +#ifndef MAPBASE if ( bForceUpdate == false ) { g_pClientShadowMgr->UpdateProjectedTexture( m_LightHandle, true ); } +#else + if ( !m_bForceUpdate ) + { + g_pClientShadowMgr->UpdateProjectedTexture( m_LightHandle, true ); + } +#endif } void C_EnvProjectedTexture::Simulate( void ) { +#ifndef MAPBASE UpdateLight( false ); +#else + UpdateLight(); +#endif BaseClass::Simulate(); } +#endif + diff --git a/src/game/client/c_env_screenoverlay.cpp b/src/game/client/c_env_screenoverlay.cpp index 42fb7303..22ff8e74 100644 --- a/src/game/client/c_env_screenoverlay.cpp +++ b/src/game/client/c_env_screenoverlay.cpp @@ -48,6 +48,9 @@ protected: int m_iCachedDesiredOverlay; int m_iCurrentOverlay; float m_flCurrentOverlayTime; +#ifdef MAPBASE + int m_iOverlayIndex; +#endif }; IMPLEMENT_CLIENTCLASS_DT( C_EnvScreenOverlay, DT_EnvScreenOverlay, CEnvScreenOverlay ) @@ -56,6 +59,9 @@ IMPLEMENT_CLIENTCLASS_DT( C_EnvScreenOverlay, DT_EnvScreenOverlay, CEnvScreenOve RecvPropFloat( RECVINFO( m_flStartTime ) ), RecvPropInt( RECVINFO( m_iDesiredOverlay ) ), RecvPropBool( RECVINFO( m_bIsActive ) ), +#ifdef MAPBASE + RecvPropInt( RECVINFO( m_iOverlayIndex ) ), +#endif END_RECV_TABLE() //----------------------------------------------------------------------------- @@ -77,7 +83,11 @@ void C_EnvScreenOverlay::PostDataUpdate( DataUpdateType_t updateType ) BaseClass::PostDataUpdate( updateType ); // If we have a start time now, start the overlays going +#ifdef MAPBASE + if ( m_bIsActive && m_flStartTime > 0 && (view->GetScreenOverlayMaterial() == NULL || (m_iOverlayIndex != -1 && view->GetIndexedScreenOverlayMaterial(m_iOverlayIndex) == NULL)) ) +#else if ( m_bIsActive && m_flStartTime > 0 && view->GetScreenOverlayMaterial() == NULL ) +#endif { StartOverlays(); } @@ -111,7 +121,16 @@ void C_EnvScreenOverlay::StopOverlays( void ) if ( m_bWasActive && !m_bIsActive ) { - view->SetScreenOverlayMaterial( NULL ); +#ifdef MAPBASE + if (m_iOverlayIndex != -1) + { + view->SetIndexedScreenOverlayMaterial( m_iOverlayIndex, NULL ); + } + else +#endif + { + view->SetScreenOverlayMaterial( NULL ); + } } } @@ -163,7 +182,16 @@ void C_EnvScreenOverlay::StartCurrentOverlay( void ) IMaterial *pMaterial = materials->FindMaterial( m_iszOverlayNames[m_iCurrentOverlay], TEXTURE_GROUP_CLIENT_EFFECTS, false ); if ( !IsErrorMaterial( pMaterial ) ) { - view->SetScreenOverlayMaterial( pMaterial ); +#ifdef MAPBASE + if (m_iOverlayIndex != -1) + { + view->SetIndexedScreenOverlayMaterial( m_iOverlayIndex, pMaterial ); + } + else +#endif + { + view->SetScreenOverlayMaterial( pMaterial ); + } } else { @@ -191,6 +219,11 @@ enum SCREENEFFECT_EP2_ADVISOR_STUN, SCREENEFFECT_EP1_INTRO, SCREENEFFECT_EP2_GROGGY, + +#ifdef MAPBASE + SCREENEFFECT_MAPBASE_CHROMATIC_BLUR = 100, // Overlays 3 different frames of red, green, and blue tints respectively with different offsets + SCREENEFFECT_MAPBASE_CHROMATIC_ABERRATION, // Similar to above, except it stretches frames in addition to offsetting them +#endif }; // ============================================================================ @@ -275,6 +308,21 @@ void C_EnvScreenEffect::ReceiveMessage( int classID, bf_read &msg ) g_pScreenSpaceEffects->SetScreenSpaceEffectParams( "ep2_groggy", pKeys ); g_pScreenSpaceEffects->EnableScreenSpaceEffect( "ep2_groggy" ); } +#ifdef MAPBASE + else if ( m_nType == SCREENEFFECT_MAPBASE_CHROMATIC_BLUR || m_nType == SCREENEFFECT_MAPBASE_CHROMATIC_ABERRATION ) + { + if( g_pMaterialSystemHardwareConfig->GetDXSupportLevel() < 80 ) + return; + + // Set our keys + pKeys->SetFloat( "duration", m_flDuration ); + pKeys->SetInt( "fadeout", 0 ); + pKeys->SetInt( "stretch", m_nType == SCREENEFFECT_MAPBASE_CHROMATIC_ABERRATION ); + + g_pScreenSpaceEffects->SetScreenSpaceEffectParams( "mapbase_chromatic_aberration", pKeys ); + g_pScreenSpaceEffects->EnableScreenSpaceEffect( "mapbase_chromatic_aberration" ); + } +#endif pKeys->deleteThis(); } @@ -321,6 +369,25 @@ void C_EnvScreenEffect::ReceiveMessage( int classID, bf_read &msg ) g_pScreenSpaceEffects->SetScreenSpaceEffectParams( "ep2_groggy", pKeys ); } +#ifdef MAPBASE + else if ( m_nType == SCREENEFFECT_MAPBASE_CHROMATIC_BLUR || m_nType == SCREENEFFECT_MAPBASE_CHROMATIC_ABERRATION ) + { + if( g_pMaterialSystemHardwareConfig->GetDXSupportLevel() < 80 ) + return; + + // Create a keyvalue block to set these params + KeyValues *pKeys = new KeyValues( "keys" ); + if ( pKeys == NULL ) + return; + + // Set our keys + pKeys->SetFloat( "duration", m_flDuration ); + pKeys->SetInt( "fadeout", 1 ); + pKeys->SetInt( "stretch", m_nType == SCREENEFFECT_MAPBASE_CHROMATIC_ABERRATION ); + + g_pScreenSpaceEffects->SetScreenSpaceEffectParams( "mapbase_chromatic_aberration", pKeys ); + } +#endif break; } diff --git a/src/game/client/c_func_dust.cpp b/src/game/client/c_func_dust.cpp index 94f114e4..c3c3d79d 100644 --- a/src/game/client/c_func_dust.cpp +++ b/src/game/client/c_func_dust.cpp @@ -88,8 +88,9 @@ void CDustEffect::RenderParticles( CParticleRenderIterator *pIterator ) void CDustEffect::SimulateParticles( CParticleSimulateIterator *pIterator ) { Vector vecWind; +#ifndef MAPBASE GetWindspeedAtTime( gpGlobals->curtime, vecWind ); - +#endif CFuncDustParticle *pParticle = (CFuncDustParticle*)pIterator->GetFirst(); while ( pParticle ) @@ -105,6 +106,9 @@ void CDustEffect::SimulateParticles( CParticleSimulateIterator *pIterator ) } else { +#ifdef MAPBASE + vecWind = GetWindspeedAtLocation( pParticle->m_Pos ); +#endif for ( int i = 0 ; i < 2 ; i++ ) { if ( pParticle->m_vVelocity[i] < vecWind[i] ) diff --git a/src/game/client/c_func_lod.cpp b/src/game/client/c_func_lod.cpp index 766cced3..89998824 100644 --- a/src/game/client/c_func_lod.cpp +++ b/src/game/client/c_func_lod.cpp @@ -30,6 +30,9 @@ public: // These are documented in the server-side entity. public: float m_fDisappearDist; +#ifdef MAPBASE + float m_fDisappearMaxDist; +#endif }; @@ -43,6 +46,9 @@ ConVar lod_TransitionDist("lod_TransitionDist", "800"); // Datatable.. IMPLEMENT_CLIENTCLASS_DT(C_Func_LOD, DT_Func_LOD, CFunc_LOD) RecvPropFloat(RECVINFO(m_fDisappearDist)), +#ifdef MAPBASE + RecvPropFloat(RECVINFO(m_fDisappearMaxDist)), +#endif END_RECV_TABLE() @@ -54,6 +60,9 @@ END_RECV_TABLE() C_Func_LOD::C_Func_LOD() { m_fDisappearDist = 5000.0f; +#ifdef MAPBASE + m_fDisappearMaxDist = 0.0f; +#endif } //----------------------------------------------------------------------------- @@ -61,7 +70,11 @@ C_Func_LOD::C_Func_LOD() //----------------------------------------------------------------------------- unsigned char C_Func_LOD::GetClientSideFade() { +#ifdef MAPBASE + return UTIL_ComputeEntityFade( this, m_fDisappearDist, m_fDisappearDist + (m_fDisappearMaxDist != 0 ? m_fDisappearMaxDist : lod_TransitionDist.GetFloat()), 1.0f ); +#else return UTIL_ComputeEntityFade( this, m_fDisappearDist, m_fDisappearDist + lod_TransitionDist.GetFloat(), 1.0f ); +#endif } diff --git a/src/game/client/c_func_reflective_glass.cpp b/src/game/client/c_func_reflective_glass.cpp index 994d62ba..4e3040d3 100644 --- a/src/game/client/c_func_reflective_glass.cpp +++ b/src/game/client/c_func_reflective_glass.cpp @@ -6,6 +6,9 @@ //===========================================================================// #include "cbase.h" #include "view_shared.h" +#ifdef MAPBASE +#include "viewrender.h" +#endif // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" @@ -23,10 +26,27 @@ public: virtual bool ShouldDraw(); +#ifdef MAPBASE + virtual void OnDataChanged( DataUpdateType_t type ); + ITexture *ReflectionRenderTarget(); + ITexture *RefractionRenderTarget(); + + char m_iszReflectRenderTarget[64]; + char m_iszRefractRenderTarget[64]; + ITexture *m_pReflectRenderTarget; + ITexture *m_pRefractRenderTarget; +#endif + C_FuncReflectiveGlass *m_pNext; }; IMPLEMENT_CLIENTCLASS_DT( C_FuncReflectiveGlass, DT_FuncReflectiveGlass, CFuncReflectiveGlass ) + +#ifdef MAPBASE + RecvPropString( RECVINFO( m_iszReflectRenderTarget ) ), + RecvPropString( RECVINFO( m_iszRefractRenderTarget ) ), +#endif + END_RECV_TABLE() @@ -47,6 +67,11 @@ C_FuncReflectiveGlass* GetReflectiveGlassList() //----------------------------------------------------------------------------- C_FuncReflectiveGlass::C_FuncReflectiveGlass() { +#ifdef MAPBASE + m_iszReflectRenderTarget[0] = '\0'; + m_iszRefractRenderTarget[0] = '\0'; +#endif + g_ReflectiveGlassList.Insert( this ); } @@ -114,5 +139,111 @@ bool IsReflectiveGlassInView( const CViewSetup& view, cplane_t &plane ) return false; } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Iterates through reflective glass instead of just picking one +//----------------------------------------------------------------------------- +C_BaseEntity *NextReflectiveGlass( C_BaseEntity *pStart, const CViewSetup& view, cplane_t &plane, + const Frustum_t &frustum, ITexture **pRenderTargets ) +{ + // Early out if no cameras + C_FuncReflectiveGlass *pReflectiveGlass = NULL; + if (!pStart) + pReflectiveGlass = GetReflectiveGlassList(); + else + pReflectiveGlass = ((C_FuncReflectiveGlass*)pStart)->m_pNext; + + cplane_t localPlane; + Vector vecOrigin, vecWorld, vecDelta; + for ( ; pReflectiveGlass != NULL; pReflectiveGlass = pReflectiveGlass->m_pNext ) + { + if ( pReflectiveGlass->IsDormant() ) + continue; + + if ( pReflectiveGlass->m_iViewHideFlags & (1 << CurrentViewID()) ) + continue; + + Vector vecMins, vecMaxs; + pReflectiveGlass->GetRenderBoundsWorldspace( vecMins, vecMaxs ); + if ( R_CullBox( vecMins, vecMaxs, frustum ) ) + continue; + + const model_t *pModel = pReflectiveGlass->GetModel(); + const matrix3x4_t& mat = pReflectiveGlass->EntityToWorldTransform(); + + int nCount = modelinfo->GetBrushModelPlaneCount( pModel ); + for ( int i = 0; i < nCount; ++i ) + { + modelinfo->GetBrushModelPlane( pModel, i, localPlane, &vecOrigin ); + + MatrixTransformPlane( mat, localPlane, plane ); // Transform to world space + VectorTransform( vecOrigin, mat, vecWorld ); + + if ( view.origin.Dot( plane.normal ) <= plane.dist ) // Check for view behind plane + continue; + + VectorSubtract( vecWorld, view.origin, vecDelta ); // Backface cull + if ( vecDelta.Dot( plane.normal ) >= 0 ) + continue; + + if (pRenderTargets != NULL) + { + pRenderTargets[0] = pReflectiveGlass->ReflectionRenderTarget(); + pRenderTargets[1] = pReflectiveGlass->RefractionRenderTarget(); + } + + return pReflectiveGlass; + } + } + + return NULL; +} + +void C_FuncReflectiveGlass::OnDataChanged( DataUpdateType_t type ) +{ + // Reset render textures + m_pReflectRenderTarget = NULL; + m_pRefractRenderTarget = NULL; + + return BaseClass::OnDataChanged( type ); +} + +ITexture *C_FuncReflectiveGlass::ReflectionRenderTarget() +{ + if (m_iszReflectRenderTarget[0] != '\0') + { + if (!m_pReflectRenderTarget) + { + // We don't use a CTextureReference for this because we don't want to shut down the texture on removal/change + m_pReflectRenderTarget = materials->FindTexture( m_iszReflectRenderTarget, TEXTURE_GROUP_RENDER_TARGET ); + } + + if (m_pReflectRenderTarget) + return m_pReflectRenderTarget; + } + + return NULL; + //return GetWaterReflectionTexture(); +} + +ITexture *C_FuncReflectiveGlass::RefractionRenderTarget() +{ + if (m_iszRefractRenderTarget[0] != '\0') + { + if (!m_pRefractRenderTarget) + { + // We don't use a CTextureReference for this because we don't want to shut down the texture on removal/change + m_pRefractRenderTarget = materials->FindTexture( m_iszRefractRenderTarget, TEXTURE_GROUP_RENDER_TARGET ); + } + + if (m_pRefractRenderTarget) + return m_pRefractRenderTarget; + } + + return NULL; + //return GetWaterRefractionTexture(); +} +#endif + diff --git a/src/game/client/c_func_reflective_glass.h b/src/game/client/c_func_reflective_glass.h index 48e1491e..ab52ab88 100644 --- a/src/game/client/c_func_reflective_glass.h +++ b/src/game/client/c_func_reflective_glass.h @@ -21,6 +21,11 @@ class CViewSetup; //----------------------------------------------------------------------------- bool IsReflectiveGlassInView( const CViewSetup& view, cplane_t &plane ); +#ifdef MAPBASE +C_BaseEntity *NextReflectiveGlass( C_BaseEntity *pStart, const CViewSetup& view, cplane_t &plane, + const Frustum_t &frustum, ITexture **pRenderTargets = NULL ); +#endif + #endif // C_FUNC_REFLECTIVE_GLASS diff --git a/src/game/client/c_gameinstructor.cpp b/src/game/client/c_gameinstructor.cpp new file mode 100644 index 00000000..ff5f0e53 --- /dev/null +++ b/src/game/client/c_gameinstructor.cpp @@ -0,0 +1,1312 @@ +//========= Copyright © 1996-2008, Valve Corporation, All rights reserved. ============// +// +// Purpose: Client handler implementations for instruction players how to play +// +//=============================================================================// + +#include "cbase.h" + +#include "c_gameinstructor.h" +#include "c_baselesson.h" +#include "filesystem.h" +#include "vprof.h" +#include "ixboxsystem.h" +#include "tier0/icommandline.h" +#include "iclientmode.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//========================================================= +// Configuration +//========================================================= + +#define MOD_DIR "MOD" +#define GAMEINSTRUCTOR_SCRIPT_FILE "scripts/instructor_lessons.txt" +#define GAMEINSTRUCTOR_MOD_SCRIPT_FILE "scripts/mod_lessons.txt" + +// Game instructor auto game system instantiation +C_GameInstructor g_GameInstructor; +C_GameInstructor &GetGameInstructor() +{ + return g_GameInstructor; +} + +void GameInstructorEnable_ChangeCallback( IConVar *var, const char *pOldValue, float flOldValue ); +void SVGameInstructorDisable_ChangeCallback( IConVar *var, const char *pOldValue, float flOldValue ); + +extern ConVar sv_gameinstructor_disable; + +//========================================================= +// Comandos de consola +//========================================================= + +ConVar gameinstructor_verbose("gameinstructor_verbose", "0", FCVAR_CHEAT, "Set to 1 for standard debugging or 2 (in combo with gameinstructor_verbose_lesson) to show update actions."); +ConVar gameinstructor_verbose_lesson("gameinstructor_verbose_lesson", "", FCVAR_CHEAT, "Display more verbose information for lessons have this name." ); +ConVar gameinstructor_find_errors("gameinstructor_find_errors", "1", FCVAR_CHEAT, "Set to 1 and the game instructor will run EVERY scripted command to uncover errors." ); + +ConVar gameinstructor_enable( "gameinstructor_enable", "1", FCVAR_CLIENTDLL | FCVAR_ARCHIVE, "Display in game lessons that teach new players.", GameInstructorEnable_ChangeCallback ); +ConVar gameinstructor_start_sound_cooldown( "gameinstructor_start_sound_cooldown", "4.0", FCVAR_NONE, "Number of seconds forced between similar lesson start sounds." ); + +ConVar sv_gameinstructor_disable( "sv_gameinstructor_disable", "0", FCVAR_REPLICATED, "Force all clients to disable their game instructors.", SVGameInstructorDisable_ChangeCallback ); + +//========================================================= +// Activa o Desactiva el Instructor del lado del cliente. +//========================================================= +void EnableDisableInstructor() +{ + bool bEnabled = ( !sv_gameinstructor_disable.GetBool() && gameinstructor_enable.GetBool() ); + + // Game instructor has been enabled, so init it! + if ( bEnabled ) + GetGameInstructor().Init(); + + // Game instructor has been disabled, so shut it down! + else + GetGameInstructor().Shutdown(); +} + +//========================================================= +//========================================================= +void GameInstructorEnable_ChangeCallback( IConVar *var, const char *pOldValue, float flOldValue ) +{ + if ( ( flOldValue != 0.0f ) != gameinstructor_enable.GetBool() ) + EnableDisableInstructor(); +} + +//========================================================= +//========================================================= +void SVGameInstructorDisable_ChangeCallback( IConVar *var, const char *pOldValue, float flOldValue ) +{ + if ( !engine ) + return; + + EnableDisableInstructor(); +} + + +//========================================================= +// Initialize the Instructor +//========================================================= +bool C_GameInstructor::Init() +{ +// if ( &GetGameInstructor() == this ) + // return true; + + // Instructor deactivated, don't initialize. + if ( !gameinstructor_enable.GetBool() || sv_gameinstructor_disable.GetBool() ) + return true; + + if ( gameinstructor_verbose.GetInt() > 0 ) + { + ConColorMsg( CBaseLesson::m_rgbaVerboseHeader, "[INSTRUCTOR]: " ); + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "Initializing...\n" ); + } + + m_bNoDraw = false; + m_bHiddenDueToOtherElements = false; + + m_iCurrentPriority = 0; + m_hLastSpectatedPlayer = NULL; + m_bSpectatedPlayerChanged = false; + + m_szPreviousStartSound[0] = '\0'; + m_fNextStartSoundTime = 0; + + ReadLessonsFromFile( GAMEINSTRUCTOR_MOD_SCRIPT_FILE ); + ReadLessonsFromFile( GAMEINSTRUCTOR_SCRIPT_FILE ); + + InitLessonPrerequisites(); + ReadSaveData(); + + ListenForGameEvent("gameinstructor_draw"); + ListenForGameEvent("gameinstructor_nodraw"); + + ListenForGameEvent("round_end"); + ListenForGameEvent("round_start"); + ListenForGameEvent("player_death"); + ListenForGameEvent("player_team"); + ListenForGameEvent("player_disconnect"); + ListenForGameEvent("map_transition"); + ListenForGameEvent("game_newmap"); + ListenForGameEvent("set_instructor_group_enabled"); + + EvaluateLessonsForGameRules(); + return true; +} + +//========================================================= +// Shut down the instructor +//========================================================= +void C_GameInstructor::Shutdown() +{ + if ( gameinstructor_verbose.GetInt() > 0 ) + { + ConColorMsg( CBaseLesson::m_rgbaVerboseHeader, "[INSTRUCTOR]: " ); + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "Shutting down...\n" ); + } + + CloseAllOpenOpportunities(); + WriteSaveData(); + + // Removemos todas las lecciones. + for ( int i = 0; i < m_Lessons.Count(); ++i ) + { + if ( m_Lessons[ i ] ) + { + m_Lessons[ i ]->StopListeningForAllEvents(); + delete m_Lessons[ i ]; + m_Lessons[ i ] = NULL; + } + } + + m_Lessons.RemoveAll(); + m_LessonGroupConVarToggles.RemoveAll(); + + // Paramos de escuchar eventos. + StopListeningForAllEvents(); +} + +//========================================================= +//========================================================= +void C_GameInstructor::UpdateHiddenByOtherElements() +{ + //bool bHidden = Mod_HiddenByOtherElements(); + bool bHidden = false; + + if ( bHidden && !m_bHiddenDueToOtherElements ) + StopAllLessons(); + + m_bHiddenDueToOtherElements = bHidden; +} + +//========================================================= +//========================================================= +void C_GameInstructor::Update( float frametime ) +{ + VPROF_BUDGET( "C_GameInstructor::Update", "GameInstructor" ); + + UpdateHiddenByOtherElements(); + + // Instructor deactivated. + if ( !gameinstructor_enable.GetBool() || m_bNoDraw || m_bHiddenDueToOtherElements ) + return; + + if ( gameinstructor_find_errors.GetBool() ) + { + FindErrors(); + gameinstructor_find_errors.SetValue(0); + } + + if ( IsConsole() ) + { + // On X360 we want to save when they're not connected + // They aren't in game + if ( !engine->IsInGame() ) + WriteSaveData(); + else + { + const char *levelName = engine->GetLevelName(); + + // The are in game, but it's a background map + if ( levelName && levelName[0] && engine->IsLevelMainMenuBackground() ) + WriteSaveData(); + } + } + + if ( m_bSpectatedPlayerChanged ) + { + // Safe spot to clean out stale lessons if spectator changed + if ( gameinstructor_verbose.GetInt() > 0 ) + { + ConColorMsg( CBaseLesson::m_rgbaVerboseHeader, "[INSTRUCTOR]: " ); + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "Spectated player changed...\n" ); + } + + CloseAllOpenOpportunities(); + m_bSpectatedPlayerChanged = false; + } + + // Loop through all the lesson roots and reset their active status + for ( int i = m_OpenOpportunities.Count() - 1; i >= 0; --i ) + { + CBaseLesson *pLesson = m_OpenOpportunities[ i ]; + CBaseLesson *pRootLesson = pLesson->GetRoot(); + + if ( pRootLesson->InstanceType() == LESSON_INSTANCE_SINGLE_ACTIVE ) + pRootLesson->SetInstanceActive(false); + } + + int iCurrentPriority = 0; + + // Loop through all the open lessons + for ( int i = m_OpenOpportunities.Count() - 1; i >= 0; --i ) + { + CBaseLesson *pLesson = m_OpenOpportunities[ i ]; + + // This opportunity has closed + if ( !pLesson->IsOpenOpportunity() || pLesson->IsTimedOut() ) + { + CloseOpportunity( pLesson ); + continue; + } + + // Lesson should be displayed, so it can affect priority + CBaseLesson *pRootLesson = pLesson->GetRoot(); + bool bShouldDisplay = pLesson->ShouldDisplay(); + bool bIsLocked = pLesson->IsLocked(); + + if ( ( bShouldDisplay || bIsLocked ) && + ( pLesson->GetPriority() >= m_iCurrentPriority || pLesson->NoPriority() || bIsLocked ) && + ( pRootLesson && ( pRootLesson->InstanceType() != LESSON_INSTANCE_SINGLE_ACTIVE || !pRootLesson->IsInstanceActive() ) ) ) + { + // Lesson is at the highest priority level, isn't violating instance rules, and has met all the prerequisites + if ( UpdateActiveLesson( pLesson, pRootLesson ) || pRootLesson->IsLearned() ) + { + // Lesson is active + if ( pLesson->IsVisible() || pRootLesson->IsLearned() ) + { + pRootLesson->SetInstanceActive( true ); + + // This active or learned lesson has the highest priority so far + if ( iCurrentPriority < pLesson->GetPriority() && !pLesson->NoPriority() ) + iCurrentPriority = pLesson->GetPriority(); + } + } + else + { + // On second thought, this shouldn't have been displayed + bShouldDisplay = false; + } + } + else + { + // Lesson shouldn't be displayed right now + UpdateInactiveLesson( pLesson ); + } + } + + // Set the priority for next frame + if ( gameinstructor_verbose.GetInt() > 1 && m_iCurrentPriority != iCurrentPriority ) + { + ConColorMsg( CBaseLesson::m_rgbaVerboseHeader, "[INSTRUCTOR]: " ); + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "Priority changed from " ); + ConColorMsg( CBaseLesson::m_rgbaVerboseClose, "%i ", m_iCurrentPriority ); + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "to " ); + ConColorMsg( CBaseLesson::m_rgbaVerboseOpen, "%i", iCurrentPriority ); + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ".\n" ); + } + + m_iCurrentPriority = iCurrentPriority; +} + +//========================================================= +//========================================================= +void C_GameInstructor::FireGameEvent( IGameEvent *event ) +{ + VPROF_BUDGET( "C_GameInstructor::FireGameEvent", "GameInstructor" ); + const char *name = event->GetName(); + + if ( Q_strcmp( name, "gameinstructor_draw" ) == 0 ) + { + if ( m_bNoDraw ) + { + if ( gameinstructor_verbose.GetInt() > 0 ) + { + ConColorMsg( CBaseLesson::m_rgbaVerboseHeader, "[INSTRUCTOR]: " ); + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "Set to draw...\n" ); + } + + m_bNoDraw = false; + } + } + else if ( Q_strcmp( name, "gameinstructor_nodraw" ) == 0 ) + { + if ( !m_bNoDraw ) + { + if ( gameinstructor_verbose.GetInt() > 0 ) + { + ConColorMsg( CBaseLesson::m_rgbaVerboseHeader, "[INSTRUCTOR]: " ); + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "Set to not draw...\n" ); + } + + m_bNoDraw = true; + StopAllLessons(); + } + } + else if ( Q_strcmp( name, "round_end" ) == 0 ) + { + if ( gameinstructor_verbose.GetInt() > 0 ) + { + ConColorMsg( CBaseLesson::m_rgbaVerboseHeader, "[INSTRUCTOR]: " ); + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "Round ended...\n" ); + } + + CloseAllOpenOpportunities(); + + if ( IsPC() ) + { + // Good place to backup our counts + WriteSaveData(); + } + } + else if ( Q_strcmp( name, "round_start" ) == 0 ) + { + if ( gameinstructor_verbose.GetInt() > 0 ) + { + ConColorMsg( CBaseLesson::m_rgbaVerboseHeader, "[INSTRUCTOR]: " ); + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "Round started...\n" ); + } + + CloseAllOpenOpportunities(); + + EvaluateLessonsForGameRules(); + } + else if ( Q_strcmp( name, "player_death" ) == 0 ) + { + #if !defined(NO_STEAM) && defined(USE_CEG) + Steamworks_TestSecret(); + Steamworks_SelfCheck(); + #endif + + C_BasePlayer *pLocalPlayer = GetLocalPlayer(); + + if ( pLocalPlayer && pLocalPlayer == UTIL_PlayerByUserId( event->GetInt( "userid" ) ) ) + { + if ( gameinstructor_verbose.GetInt() > 0 ) + { + ConColorMsg( CBaseLesson::m_rgbaVerboseHeader, "[INSTRUCTOR]: " ); + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "Local player died...\n" ); + } + + for ( int i = m_OpenOpportunities.Count() - 1; i >= 0; --i ) + { + CBaseLesson *pLesson = m_OpenOpportunities[ i ]; + CBaseLesson *pRootLesson = pLesson->GetRoot(); + + if ( !pRootLesson->CanOpenWhenDead() ) + CloseOpportunity( pLesson ); + } + } + } + else if ( Q_strcmp( name, "player_team" ) == 0 ) + { + C_BasePlayer *pLocalPlayer = GetLocalPlayer(); + + if ( pLocalPlayer && pLocalPlayer == UTIL_PlayerByUserId( event->GetInt( "userid" ) ) && + ( event->GetInt( "team" ) != event->GetInt( "oldteam" ) || event->GetBool( "disconnect" ) ) ) + { + if ( gameinstructor_verbose.GetInt() > 0 ) + { + ConColorMsg( CBaseLesson::m_rgbaVerboseHeader, "[INSTRUCTOR]: " ); + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "Local player changed team (or disconnected)...\n" ); + } + + CloseAllOpenOpportunities(); + } + + EvaluateLessonsForGameRules(); + } + else if ( Q_strcmp( name, "player_disconnect" ) == 0 ) + { + C_BasePlayer *pLocalPlayer = GetLocalPlayer(); + if ( pLocalPlayer && pLocalPlayer == UTIL_PlayerByUserId( event->GetInt( "userid" ) ) ) + { + if ( gameinstructor_verbose.GetInt() > 0 ) + { + ConColorMsg( CBaseLesson::m_rgbaVerboseHeader, "[INSTRUCTOR]: " ); + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "Local player disconnected...\n" ); + } + + CloseAllOpenOpportunities(); + } + } + else if ( Q_strcmp( name, "map_transition" ) == 0 ) + { + if ( gameinstructor_verbose.GetInt() > 0 ) + { + ConColorMsg( CBaseLesson::m_rgbaVerboseHeader, "[INSTRUCTOR]: " ); + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "Map transition...\n" ); + } + + CloseAllOpenOpportunities(); + + if ( m_bNoDraw ) + { + if ( gameinstructor_verbose.GetInt() > 0 ) + { + ConColorMsg( Color( 255, 128, 64, 255 ), "[INSTRUCTOR]: " ); + ConColorMsg( Color( 64, 128, 255, 255 ), "Set to draw...\n" ); + } + + m_bNoDraw = false; + } + + if ( IsPC() ) + { + // Good place to backup our counts + WriteSaveData(); + } + } + else if ( Q_strcmp( name, "game_newmap" ) == 0 ) + { + if ( gameinstructor_verbose.GetInt() > 0 ) + { + ConColorMsg( CBaseLesson::m_rgbaVerboseHeader, "[INSTRUCTOR]: " ); + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "New map...\n" ); + } + + CloseAllOpenOpportunities(); + + if ( m_bNoDraw ) + { + if ( gameinstructor_verbose.GetInt() > 0 ) + { + ConColorMsg( Color( 255, 128, 64, 255 ), "[INSTRUCTOR]: " ); + ConColorMsg( Color( 64, 128, 255, 255 ), "Set to draw...\n" ); + } + + m_bNoDraw = false; + } + + if ( IsPC() ) + { + // Good place to backup our counts + WriteSaveData(); + } + } + + else if ( Q_strcmp( name, "set_instructor_group_enabled" ) == 0 ) + { + const char *pszGroup = event->GetString( "group" ); + bool bEnabled = event->GetInt( "enabled" ) != 0; + + if ( pszGroup && pszGroup[0] ) + SetLessonGroupEnabled(pszGroup, bEnabled); + } +} + +//========================================================= +//========================================================= +void C_GameInstructor::DefineLesson( CBaseLesson *pLesson ) +{ + if ( gameinstructor_verbose.GetInt() > 0 ) + { + ConColorMsg( CBaseLesson::m_rgbaVerboseHeader, "[INSTRUCTOR]: " ); + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "Lesson " ); + ConColorMsg( CBaseLesson::m_rgbaVerboseName, "\"%s\" ", pLesson->GetName() ); + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "defined.\n" ); + } + + m_Lessons.AddToTail( pLesson ); +} + +//========================================================= +//========================================================= +const CBaseLesson * C_GameInstructor::GetLesson( const char *pchLessonName ) +{ + return GetLesson_Internal( pchLessonName ); +} + +//========================================================= +//========================================================= +bool C_GameInstructor::IsLessonOfSameTypeOpen( const CBaseLesson *pLesson ) const +{ + for ( int i = 0; i < m_OpenOpportunities.Count(); ++i ) + { + CBaseLesson *pOpenOpportunity = m_OpenOpportunities[ i ]; + + if ( pOpenOpportunity->GetNameSymbol() == pLesson->GetNameSymbol() ) + return true; + } + + return false; +} + +//========================================================= +//========================================================= +bool C_GameInstructor::ReadSaveData() +{ + // for external playtests, don't ever read in persisted instructor state, always start fresh + if ( CommandLine()->FindParm( "-playtest" ) ) + return true; + + if ( m_bHasLoadedSaveData ) + return true; + + // Always reset state first in case storage device + // was declined or ends up in faulty state + ResetDisplaysAndSuccesses(); + + m_bHasLoadedSaveData = true; + +#ifdef _X360 + DevMsg( "Read Game Instructor for splitscreen slot %d\n", m_nSplitScreenSlot ); + + if ( m_nSplitScreenSlot < 0 ) + return false; + + if ( m_nSplitScreenSlot >= (int) XBX_GetNumGameUsers() ) + return false; + + int iController = XBX_GetUserId( m_nSplitScreenSlot ); + + if ( iController < 0 || XBX_GetUserIsGuest( iController ) ) + { + // Can't read data for guests + return false; + } + + DWORD nStorageDevice = XBX_GetStorageDeviceId( iController ); + if ( !XBX_DescribeStorageDevice( nStorageDevice ) ) + return false; +#endif + + char szFilename[_MAX_PATH]; + +#ifdef _X360 + if ( IsX360() ) + { + XBX_MakeStorageContainerRoot( iController, XBX_USER_SETTINGS_CONTAINER_DRIVE, szFilename, sizeof( szFilename ) ); + int nLen = strlen( szFilename ); + Q_snprintf( szFilename + nLen, sizeof( szFilename ) - nLen, ":\\game_instructor_counts.txt" ); + } + else +#endif + { + Q_snprintf( szFilename, sizeof( szFilename ), "save/game_instructor_counts.txt" ); + } + + KeyValues *data = new KeyValues( "Game Instructor Counts" ); + KeyValues::AutoDelete autoDelete(data); + + if ( data->LoadFromFile( g_pFullFileSystem, szFilename, NULL ) ) + { + int nVersion = 0; + + for ( KeyValues *pKey = data->GetFirstSubKey(); pKey; pKey = pKey->GetNextTrueSubKey() ) + { + CBaseLesson *pLesson = GetLesson_Internal( pKey->GetName() ); + + if ( pLesson ) + { + pLesson->SetDisplayCount( pKey->GetInt( "display", 0 ) ); + pLesson->SetSuccessCount( pKey->GetInt( "success", 0 ) ); + + if ( Q_strcmp( pKey->GetName(), "version number" ) == 0 ) + { + nVersion = pLesson->GetSuccessCount(); + } + } + } + + CBaseLesson *pLessonVersionNumber = GetLesson_Internal( "version number" ); + if ( pLessonVersionNumber && !pLessonVersionNumber->IsLearned() ) + { + ResetDisplaysAndSuccesses(); + pLessonVersionNumber->SetSuccessCount( pLessonVersionNumber->GetSuccessLimit() ); + m_bDirtySaveData = true; + } + + + return true; + } + + // Couldn't read from the file + return false; +} + +//========================================================= +//========================================================= +bool C_GameInstructor::WriteSaveData() +{ + if ( engine->IsPlayingDemo() ) + return false; + + if ( !m_bDirtySaveData ) + return true; + +#ifdef _X360 + float flPlatTime = Plat_FloatTime(); + + static ConVarRef host_write_last_time( "host_write_last_time" ); + if ( host_write_last_time.IsValid() ) + { + float flTimeSinceLastWrite = flPlatTime - host_write_last_time.GetFloat(); + if ( flTimeSinceLastWrite < 3.5f ) + { + // Prevent writing to the same storage device twice in less than 3 second succession for TCR success! + // This happens after leaving a game in splitscreen. + //DevMsg( "Waiting to write Game Instructor for splitscreen slot %d... (%.1f seconds remain)\n", m_nSplitScreenSlot, 3.5f - flTimeSinceLastWrite ); + return false; + } + } +#endif + + // Always mark as clean state to avoid re-entry on + // subsequent frames when storage device might be + // in a yet-unmounted state. + m_bDirtySaveData = false; + +#ifdef _X360 + DevMsg( "Write Game Instructor for splitscreen slot %d at time: %.1f\n", m_nSplitScreenSlot, flPlatTime ); + + if ( m_nSplitScreenSlot < 0 ) + return false; + + if ( m_nSplitScreenSlot >= (int) XBX_GetNumGameUsers() ) + return false; + + int iController = XBX_GetUserId( m_nSplitScreenSlot ); + + if ( iController < 0 || XBX_GetUserIsGuest( iController ) ) + { + // Can't save data for guests + return false; + } + + DWORD nStorageDevice = XBX_GetStorageDeviceId( iController ); + if ( !XBX_DescribeStorageDevice( nStorageDevice ) ) + return false; +#endif + + // Build key value data to save + KeyValues *data = new KeyValues( "Game Instructor Counts" ); + KeyValues::AutoDelete autoDelete(data); + + for ( int i = 0; i < m_Lessons.Count(); ++i ) + { + CBaseLesson *pLesson = m_Lessons[i]; + + int iDisplayCount = pLesson->GetDisplayCount(); + int iSuccessCount = pLesson->GetSuccessCount(); + + if ( iDisplayCount || iSuccessCount ) + { + // We've got some data worth saving + KeyValues *pKVData = new KeyValues( pLesson->GetName() ); + + if ( iDisplayCount ) + pKVData->SetInt( "display", iDisplayCount ); + + if ( iSuccessCount ) + pKVData->SetInt( "success", iSuccessCount ); + + data->AddSubKey( pKVData ); + } + } + + // Save it! + CUtlBuffer buf( 0, 0, CUtlBuffer::TEXT_BUFFER ); + + data->RecursiveSaveToFile( buf, 0 ); + + char szFilename[_MAX_PATH]; + +#ifdef _X360 + if ( IsX360() ) + { + XBX_MakeStorageContainerRoot( iController, XBX_USER_SETTINGS_CONTAINER_DRIVE, szFilename, sizeof( szFilename ) ); + int nLen = strlen( szFilename ); + Q_snprintf( szFilename + nLen, sizeof( szFilename ) - nLen, ":\\game_instructor_counts.txt" ); + } + else +#endif + { + Q_snprintf( szFilename, sizeof( szFilename ), "save/game_instructor_counts.txt" ); + filesystem->CreateDirHierarchy( "save", "MOD" ); + } + + bool bWriteSuccess = filesystem->WriteFile( szFilename, MOD_DIR, buf ); + +#ifdef _X360 + if ( xboxsystem ) + { + xboxsystem->FinishContainerWrites( iController ); + } +#endif + + return bWriteSuccess; +} + +//========================================================= +//========================================================= +void C_GameInstructor::RefreshDisplaysAndSuccesses() +{ + m_bHasLoadedSaveData = false; + ReadSaveData(); +} + +//========================================================= +//========================================================= +void C_GameInstructor::ResetDisplaysAndSuccesses() +{ + if ( gameinstructor_verbose.GetInt() > 0 ) + { + ConColorMsg( CBaseLesson::m_rgbaVerboseHeader, "[INSTRUCTOR]: " ); + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "Reset all lesson display and success counts.\n" ); + } + + for ( int i = 0; i < m_Lessons.Count(); ++i ) + { + m_Lessons[ i ]->ResetDisplaysAndSuccesses(); + } + + m_bDirtySaveData = false; +} + +//========================================================= +//========================================================= +void C_GameInstructor::MarkDisplayed( const char *pchLessonName ) +{ + CBaseLesson *pLesson = GetLesson_Internal(pchLessonName); + + if ( !pLesson ) + return; + + if ( gameinstructor_verbose.GetInt() > 0 ) + { + ConColorMsg( CBaseLesson::m_rgbaVerboseHeader, "[INSTRUCTOR]: " ); + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "Lesson " ); + ConColorMsg( CBaseLesson::m_rgbaVerboseOpen, "\"%s\" ", pLesson->GetName() ); + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "marked as displayed.\n" ); + } + + if ( pLesson->IncDisplayCount() ) + m_bDirtySaveData = true; +} + +//========================================================= +//========================================================= +void C_GameInstructor::MarkSucceeded(const char *pchLessonName) +{ + CBaseLesson *pLesson = GetLesson_Internal(pchLessonName); + + if ( !pLesson ) + return; + + if ( gameinstructor_verbose.GetInt() > 0 ) + { + ConColorMsg( CBaseLesson::m_rgbaVerboseHeader, "[INSTRUCTOR]: " ); + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "Lesson " ); + ConColorMsg( CBaseLesson::m_rgbaVerboseSuccess, "\"%s\" ", pLesson->GetName() ); + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "marked as succeeded.\n" ); + } + + if ( pLesson->IncSuccessCount() ) + m_bDirtySaveData = true; +} + +//========================================================= +//========================================================= +void C_GameInstructor::PlaySound( const char *pchSoundName ) +{ + // emit alert sound + C_BasePlayer *pLocalPlayer = C_BasePlayer::GetLocalPlayer(); + + if ( pLocalPlayer ) + { + // Local player exists + if ( pchSoundName[ 0 ] != '\0' && Q_strcmp( m_szPreviousStartSound, pchSoundName ) != 0 ) + { + Q_strcpy( m_szPreviousStartSound, pchSoundName ); + m_fNextStartSoundTime = 0.0f; + } + + if ( gpGlobals->curtime >= m_fNextStartSoundTime && pchSoundName[ 0 ] != '\0' ) + { + // A sound was specified, so play it! + pLocalPlayer->EmitSound( pchSoundName ); + m_fNextStartSoundTime = gpGlobals->curtime + gameinstructor_start_sound_cooldown.GetFloat(); + } + } +} + +//========================================================= +//========================================================= +bool C_GameInstructor::OpenOpportunity( CBaseLesson *pLesson ) +{ + // Get the root lesson + CBaseLesson *pRootLesson = pLesson->GetRoot(); + + if ( !pRootLesson ) + { + if ( gameinstructor_verbose.GetInt() > 0 ) + { + ConColorMsg( CBaseLesson::m_rgbaVerboseHeader, "[INSTRUCTOR]: " ); + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "Opportunity " ); + ConColorMsg( CBaseLesson::m_rgbaVerboseClose, "\"%s\" ", pLesson->GetName() ); + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "NOT opened (because root lesson could not be found).\n" ); + } + + delete pLesson; + return false; + } + + C_BasePlayer *pLocalPlayer = GetLocalPlayer(); + + if ( !pRootLesson->CanOpenWhenDead() && ( !pLocalPlayer || !pLocalPlayer->IsAlive() ) ) + { + // If the player is dead don't allow lessons that can't be opened when dead + if ( gameinstructor_verbose.GetInt() > 0 ) + { + ConColorMsg( CBaseLesson::m_rgbaVerboseHeader, "[INSTRUCTOR]: " ); + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "Opportunity " ); + ConColorMsg( CBaseLesson::m_rgbaVerboseClose, "\"%s\" ", pLesson->GetName() ); + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "NOT opened (because player is dead and can_open_when_dead not set).\n" ); + } + + delete pLesson; + return false; + } + + if ( !pRootLesson->PrerequisitesHaveBeenMet() ) + { + // If the prereqs haven't been met, don't open it + if ( gameinstructor_verbose.GetInt() > 0 ) + { + ConColorMsg( CBaseLesson::m_rgbaVerboseHeader, "[INSTRUCTOR]: " ); + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "Opportunity " ); + ConColorMsg( CBaseLesson::m_rgbaVerboseClose, "\"%s\" ", pLesson->GetName() ); + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "NOT opened (because prereqs haven't been met).\n" ); + } + + delete pLesson; + return false; + } + + if ( pRootLesson->InstanceType() == LESSON_INSTANCE_FIXED_REPLACE ) + { + CBaseLesson *pLessonToReplace = NULL; + CBaseLesson *pLastReplacableLesson = NULL; + + int iInstanceCount = 0; + + // Check how many are already open + for ( int i = m_OpenOpportunities.Count() - 1; i >= 0; --i ) + { + CBaseLesson *pOpenOpportunity = m_OpenOpportunities[ i ]; + + if ( pOpenOpportunity->GetNameSymbol() == pLesson->GetNameSymbol() && + pOpenOpportunity->GetReplaceKeySymbol() == pLesson->GetReplaceKeySymbol() ) + { + iInstanceCount++; + + if ( pRootLesson->ShouldReplaceOnlyWhenStopped() ) + { + if ( !pOpenOpportunity->IsInstructing() ) + { + pLastReplacableLesson = pOpenOpportunity; + } + } + else + { + pLastReplacableLesson = pOpenOpportunity; + } + + if ( iInstanceCount >= pRootLesson->GetFixedInstancesMax() ) + { + pLessonToReplace = pLastReplacableLesson; + break; + } + } + } + + if ( pLessonToReplace ) + { + // Take the place of the previous instance + if ( gameinstructor_verbose.GetInt() > 0 ) + { + ConColorMsg( CBaseLesson::m_rgbaVerboseHeader, "GAME INSTRUCTOR: " ); + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "Opportunity " ); + ConColorMsg( CBaseLesson::m_rgbaVerboseOpen, "\"%s\" ", pLesson->GetName() ); + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "replacing open lesson of same type.\n" ); + } + + pLesson->TakePlaceOf( pLessonToReplace ); + CloseOpportunity( pLessonToReplace ); + } + else if ( iInstanceCount >= pRootLesson->GetFixedInstancesMax() ) + { + // Don't add another lesson of this type + if ( gameinstructor_verbose.GetInt() > 0 ) + { + ConColorMsg( CBaseLesson::m_rgbaVerboseHeader, "GAME INSTRUCTOR: " ); + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "Opportunity " ); + ConColorMsg( CBaseLesson::m_rgbaVerboseClose, "\"%s\" ", pLesson->GetName() ); + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "NOT opened (there is too many started lessons of this type).\n" ); + } + + delete pLesson; + return false; + } + } + + if ( gameinstructor_verbose.GetInt() > 0 ) + { + ConColorMsg( CBaseLesson::m_rgbaVerboseHeader, "GAME INSTRUCTOR: " ); + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "Opportunity " ); + ConColorMsg( CBaseLesson::m_rgbaVerboseOpen, "\"%s\" ", pLesson->GetName() ); + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "opened.\n" ); + } + + m_OpenOpportunities.AddToTail( pLesson ); + + return true; +} + +//========================================================= +//========================================================= +void C_GameInstructor::DumpOpenOpportunities() +{ + ConColorMsg( CBaseLesson::m_rgbaVerboseHeader, "GAME INSTRUCTOR: " ); + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "Open lessons...\n" ); + + for ( int i = m_OpenOpportunities.Count() - 1; i >= 0; --i ) + { + CBaseLesson *pLesson = m_OpenOpportunities[ i ]; + CBaseLesson *pRootLesson = pLesson->GetRoot(); + + Color color; + + if ( pLesson->IsInstructing() ) + { + // Green + color = CBaseLesson::m_rgbaVerboseOpen; + } + else if ( pRootLesson->IsLearned() && pLesson->GetPriority() >= m_iCurrentPriority ) + { + // Yellow + color = CBaseLesson::m_rgbaVerboseSuccess; + } + else + { + // Red + color = CBaseLesson::m_rgbaVerboseClose; + } + + ConColorMsg( color, "\t%s\n", pLesson->GetName() ); + } +} + +//========================================================= +//========================================================= +KeyValues * C_GameInstructor::GetScriptKeys() +{ + return m_pScriptKeys; +} + +//========================================================= +//========================================================= +C_BasePlayer * C_GameInstructor::GetLocalPlayer() +{ + C_BasePlayer *pLocalPlayer = C_BasePlayer::GetLocalPlayer(); + + // If we're not a developer, don't do the special spectator hook ups + if ( !developer.GetBool() ) + return pLocalPlayer; + + // If there is no local player and we're not spectating, just return that + if ( !pLocalPlayer || pLocalPlayer->GetTeamNumber() != TEAM_SPECTATOR ) + return pLocalPlayer; + + // We're purely a spectator let's get lessons of the person we're spectating + C_BasePlayer *pSpectatedPlayer = NULL; + + if ( pLocalPlayer->GetObserverMode() == OBS_MODE_IN_EYE || pLocalPlayer->GetObserverMode() == OBS_MODE_CHASE ) + pSpectatedPlayer = ToBasePlayer( pLocalPlayer->GetObserverTarget() ); + + if ( m_hLastSpectatedPlayer != pSpectatedPlayer ) + { + // We're spectating someone new! Close all the stale lessons! + m_bSpectatedPlayerChanged = true; + m_hLastSpectatedPlayer = pSpectatedPlayer; + } + + return pSpectatedPlayer; +} + +//========================================================= +//========================================================= +void C_GameInstructor::EvaluateLessonsForGameRules() +{ + // Enable everything by default + for ( int i = 0; i < m_Lessons.Count(); ++i ) + m_Lessons[ i ]->SetEnabled(true); + + // Then see if we should disable anything + for ( int nConVar = 0; nConVar < m_LessonGroupConVarToggles.Count(); ++nConVar ) + { + LessonGroupConVarToggle_t *pLessonGroupConVarToggle = &(m_LessonGroupConVarToggles[ nConVar ]); + + if ( pLessonGroupConVarToggle->var.IsValid() ) + { + if ( pLessonGroupConVarToggle->var.GetBool() ) + SetLessonGroupEnabled( pLessonGroupConVarToggle->szLessonGroupName, false ); + } + } +} + +//========================================================= +//========================================================= +void C_GameInstructor::SetLessonGroupEnabled( const char *pszGroup, bool bEnabled ) +{ + for ( int i = 0; i < m_Lessons.Count(); ++i ) + { + if ( !Q_stricmp(pszGroup, m_Lessons[i]->GetGroup()) ) + m_Lessons[i]->SetEnabled( bEnabled ); + } +} + +//========================================================= +//========================================================= +void C_GameInstructor::FindErrors() +{ + // Loop through all the lesson and run all their scripted actions + for ( int i = 0; i < m_Lessons.Count(); ++i ) + { + CScriptedIconLesson *pLesson = dynamic_cast( m_Lessons[ i ] ); + if ( pLesson ) + { + // Process all open events + for ( int iLessonEvent = 0; iLessonEvent < pLesson->GetOpenEvents().Count(); ++iLessonEvent ) + { + const LessonEvent_t *pLessonEvent = &(pLesson->GetOpenEvents()[ iLessonEvent ]); + pLesson->ProcessElements( NULL, &(pLessonEvent->elements) ); + } + + // Process all close events + for ( int iLessonEvent = 0; iLessonEvent < pLesson->GetCloseEvents().Count(); ++iLessonEvent ) + { + const LessonEvent_t *pLessonEvent = &(pLesson->GetCloseEvents()[ iLessonEvent ]); + pLesson->ProcessElements( NULL, &(pLessonEvent->elements) ); + } + + // Process all success events + for ( int iLessonEvent = 0; iLessonEvent < pLesson->GetSuccessEvents().Count(); ++iLessonEvent ) + { + const LessonEvent_t *pLessonEvent = &(pLesson->GetSuccessEvents()[ iLessonEvent ]); + pLesson->ProcessElements( NULL, &(pLessonEvent->elements) ); + } + + // Process all on open events + for ( int iLessonEvent = 0; iLessonEvent < pLesson->GetOnOpenEvents().Count(); ++iLessonEvent ) + { + const LessonEvent_t *pLessonEvent = &(pLesson->GetOnOpenEvents()[ iLessonEvent ]); + pLesson->ProcessElements( NULL, &(pLessonEvent->elements) ); + } + + // Process all update events + for ( int iLessonEvent = 0; iLessonEvent < pLesson->GetUpdateEvents().Count(); ++iLessonEvent ) + { + const LessonEvent_t *pLessonEvent = &(pLesson->GetUpdateEvents()[ iLessonEvent ]); + pLesson->ProcessElements( NULL, &(pLessonEvent->elements) ); + } + } + } +} + +//========================================================= +//========================================================= +bool C_GameInstructor::UpdateActiveLesson( CBaseLesson *pLesson, const CBaseLesson *pRootLesson ) +{ + VPROF_BUDGET( "C_GameInstructor::UpdateActiveLesson", "GameInstructor" ); + + bool bIsOpen = pLesson->IsInstructing(); + + if ( !bIsOpen && !pRootLesson->IsLearned() ) + { + pLesson->SetStartTime(); + pLesson->Start(); + + // Check to see if it successfully started + bIsOpen = ( pLesson->IsOpenOpportunity() && pLesson->ShouldDisplay() ); + + if ( bIsOpen ) + { + // Lesson hasn't been started and hasn't been learned + if ( gameinstructor_verbose.GetInt() > 0 ) + { + ConColorMsg( CBaseLesson::m_rgbaVerboseHeader, "GAME INSTRUCTOR: " ); + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "Started lesson " ); + ConColorMsg( CBaseLesson::m_rgbaVerboseOpen, "\"%s\"", pLesson->GetName() ); + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ".\n" ); + } + } + else + { + pLesson->Stop(); + pLesson->ResetStartTime(); + } + } + + if ( bIsOpen ) + { + // Update the running lesson + pLesson->Update(); + return true; + } + else + { + pLesson->UpdateInactive(); + return false; + } +} + +//========================================================= +//========================================================= +void C_GameInstructor::UpdateInactiveLesson( CBaseLesson *pLesson ) +{ + VPROF_BUDGET( "C_GameInstructor::UpdateInactiveLesson", "GameInstructor" ); + + if ( pLesson->IsInstructing() ) + { + // Lesson hasn't been stopped + if ( gameinstructor_verbose.GetInt() > 0 ) + { + ConColorMsg( CBaseLesson::m_rgbaVerboseHeader, "GAME INSTRUCTOR: " ); + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "Stopped lesson " ); + ConColorMsg( CBaseLesson::m_rgbaVerboseClose, "\"%s\"", pLesson->GetName() ); + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, ".\n" ); + } + + pLesson->Stop(); + pLesson->ResetStartTime(); + } + + pLesson->UpdateInactive(); +} + +//========================================================= +//========================================================= +CBaseLesson * C_GameInstructor::GetLesson_Internal( const char *pchLessonName ) +{ + for ( int i = 0; i < m_Lessons.Count(); ++i ) + { + CBaseLesson *pLesson = m_Lessons[ i ]; + + if ( Q_strcmp( pLesson->GetName(), pchLessonName ) == 0 ) + { + return pLesson; + } + } + + return NULL; +} + +//========================================================= +//========================================================= +void C_GameInstructor::StopAllLessons() +{ + // Stop all the current lessons + for ( int i = m_OpenOpportunities.Count() - 1; i >= 0; --i ) + { + CBaseLesson *pLesson = m_OpenOpportunities[ i ]; + UpdateInactiveLesson( pLesson ); + } +} + +//========================================================= +//========================================================= +void C_GameInstructor::CloseAllOpenOpportunities() +{ + // Clear out all the open opportunities + for ( int i = m_OpenOpportunities.Count() - 1; i >= 0; --i ) + { + CBaseLesson *pLesson = m_OpenOpportunities[ i ]; + CloseOpportunity( pLesson ); + } + + Assert( m_OpenOpportunities.Count() == 0 ); +} + +//========================================================= +//========================================================= +void C_GameInstructor::CloseOpportunity( CBaseLesson *pLesson ) +{ + UpdateInactiveLesson( pLesson ); + + if ( pLesson->WasDisplayed() ) + MarkDisplayed( pLesson->GetName() ); + + if ( gameinstructor_verbose.GetInt() > 0 ) + { + ConColorMsg( CBaseLesson::m_rgbaVerboseHeader, "GAME INSTRUCTOR: " ); + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "Opportunity " ); + ConColorMsg( CBaseLesson::m_rgbaVerboseClose, "\"%s\" ", pLesson->GetName() ); + ConColorMsg( CBaseLesson::m_rgbaVerbosePlain, "closed for reason: " ); + ConColorMsg( CBaseLesson::m_rgbaVerboseClose, "%s\n", pLesson->GetCloseReason() ); + } + + pLesson->StopListeningForAllEvents(); + + m_OpenOpportunities.FindAndRemove( pLesson ); + delete pLesson; +} + +//========================================================= +//========================================================= +void C_GameInstructor::ReadLessonsFromFile( const char *pchFileName ) +{ + // Static init function + CScriptedIconLesson::PreReadLessonsFromFile(); + MEM_ALLOC_CREDIT(); + + KeyValues *pLessonKeys = new KeyValues("instructor_lessons"); + KeyValues::AutoDelete autoDelete(pLessonKeys); + + pLessonKeys->LoadFromFile(g_pFullFileSystem, pchFileName, NULL); + + for ( m_pScriptKeys = pLessonKeys->GetFirstTrueSubKey(); m_pScriptKeys; m_pScriptKeys = m_pScriptKeys->GetNextTrueSubKey() ) + { + if ( Q_stricmp(m_pScriptKeys->GetName(), "GroupConVarToggle") == 0 ) + { + // Add convar group toggler to the list + int nLessonGroupConVarToggle = m_LessonGroupConVarToggles.AddToTail( LessonGroupConVarToggle_t( m_pScriptKeys->GetString( "convar" ) ) ); + LessonGroupConVarToggle_t *pLessonGroupConVarToggle = &(m_LessonGroupConVarToggles[nLessonGroupConVarToggle]); + + Q_strcpy( pLessonGroupConVarToggle->szLessonGroupName, m_pScriptKeys->GetString("group") ); + continue; + } + + // Ensure that lessons aren't added twice + if ( GetLesson_Internal(m_pScriptKeys->GetName()) ) + { + DevWarning("Lesson \"%s\" defined twice!\n", m_pScriptKeys->GetName()); + continue; + } + + CScriptedIconLesson *pNewLesson = new CScriptedIconLesson(m_pScriptKeys->GetName(), false, false); + GetGameInstructor().DefineLesson(pNewLesson); + } + + m_pScriptKeys = NULL; +} + +//========================================================= +//========================================================= +void C_GameInstructor::InitLessonPrerequisites() +{ + for ( int i = 0; i < m_Lessons.Count(); ++i ) + m_Lessons[ i ]->InitPrerequisites(); +} + +//========================================================= +// Commands +//========================================================= + +CON_COMMAND_F( gameinstructor_reload_lessons, "Shuts down all open lessons and reloads them from the script file.", FCVAR_CHEAT ) +{ + GetGameInstructor().Shutdown(); + GetGameInstructor().Init(); +} + +CON_COMMAND_F( gameinstructor_reset_counts, "Resets all display and success counts to zero.", FCVAR_NONE ) +{ + GetGameInstructor().ResetDisplaysAndSuccesses(); +} + +CON_COMMAND_F( gameinstructor_dump_open_lessons, "Gives a list of all currently open lessons.", FCVAR_CHEAT ) +{ + GetGameInstructor().DumpOpenOpportunities(); +} diff --git a/src/game/client/c_gameinstructor.h b/src/game/client/c_gameinstructor.h new file mode 100644 index 00000000..14ae908c --- /dev/null +++ b/src/game/client/c_gameinstructor.h @@ -0,0 +1,118 @@ +//========= Copyright © 1996-2008, Valve Corporation, All rights reserved. ============// +// +// Purpose: Client handler for instruction players how to play +// +//=============================================================================// + +#ifndef _C_GAMEINSTRUCTOR_H_ +#define _C_GAMEINSTRUCTOR_H_ + + +#include "GameEventListener.h" +#include "vgui_controls/PHandle.h" + +class CBaseLesson; + + +struct LessonGroupConVarToggle_t +{ + ConVarRef var; + char szLessonGroupName[ 64 ]; + + LessonGroupConVarToggle_t( const char *pchConVarName ) : + var( pchConVarName ) + { + } +}; + + +class C_GameInstructor : public CAutoGameSystemPerFrame, public CGameEventListener +{ +public: + C_GameInstructor() : CAutoGameSystemPerFrame( "C_GameInstructor" ) + { + m_bHasLoadedSaveData = false; + m_bDirtySaveData = false; + } + + // Methods of IGameSystem + virtual bool Init( void ); + virtual void Shutdown( void ); + virtual void Update( float frametime ); + + void UpdateHiddenByOtherElements( void ); + bool Mod_HiddenByOtherElements( void ); + + virtual void FireGameEvent( IGameEvent *event ); + + void DefineLesson( CBaseLesson *pLesson ); + + const CBaseLesson * GetLesson( const char *pchLessonName ); + bool IsLessonOfSameTypeOpen( const CBaseLesson *pLesson ) const; + + bool ReadSaveData( void ); + bool WriteSaveData( void ); + void RefreshDisplaysAndSuccesses( void ); + void ResetDisplaysAndSuccesses( void ); + void MarkDisplayed( const char *pchLessonName ); + void MarkSucceeded( const char *pchLessonName ); + + void PlaySound( const char *pchSoundName ); + + bool OpenOpportunity( CBaseLesson *pLesson ); + + void DumpOpenOpportunities( void ); + + KeyValues * GetScriptKeys( void ); + C_BasePlayer * GetLocalPlayer( void ); + + void EvaluateLessonsForGameRules( void ); + void SetLessonGroupEnabled( const char *pszGroup, bool bEnabled ); + + // Mapbase needs this to be public for map-specific file system + void ReadLessonsFromFile( const char *pchFileName ); + +private: + void FindErrors( void ); + + bool UpdateActiveLesson( CBaseLesson *pLesson, const CBaseLesson *pRootLesson ); + void UpdateInactiveLesson( CBaseLesson *pLesson ); + + CBaseLesson * GetLesson_Internal( const char *pchLessonName ); + + void StopAllLessons( void ); + + void CloseAllOpenOpportunities( void ); + void CloseOpportunity( CBaseLesson *pLesson ); + + void InitLessonPrerequisites( void ); + +private: + CUtlVector < CBaseLesson* > m_Lessons; + CUtlVector < CBaseLesson* > m_OpenOpportunities; + + CUtlVector < LessonGroupConVarToggle_t > m_LessonGroupConVarToggles; + + KeyValues *m_pScriptKeys; + + bool m_bNoDraw; + bool m_bHiddenDueToOtherElements; + + int m_iCurrentPriority; + EHANDLE m_hLastSpectatedPlayer; + bool m_bSpectatedPlayerChanged; + + char m_szPreviousStartSound[ 128 ]; + float m_fNextStartSoundTime; + + bool m_bHasLoadedSaveData; + bool m_bDirtySaveData; +}; + +C_GameInstructor &GetGameInstructor(); + +void GameInstructor_Init(); +void GameInstructor_Shutdown(); + + +#endif // _C_GAMEINSTRUCTOR_H_ diff --git a/src/game/client/c_impact_effects.cpp b/src/game/client/c_impact_effects.cpp index 5b350c18..64a51bf3 100644 --- a/src/game/client/c_impact_effects.cpp +++ b/src/game/client/c_impact_effects.cpp @@ -95,18 +95,23 @@ extern PMaterialHandle g_Material_Spark; //----------------------------------------------------------------------------- void GetColorForSurface( trace_t *trace, Vector *color ) { - Vector baseColor, diffuseColor; - Vector end = trace->startpos + ( ( Vector )trace->endpos - ( Vector )trace->startpos ) * 1.1f; - + Vector baseColor = vec3_invalid, diffuseColor; + const char *kind; + if ( trace->DidHitWorld() ) { if ( trace->hitbox == 0 ) { + kind = "World"; + Vector end = trace->startpos + ( trace->endpos - trace->startpos ) * 1.1f; // If we hit the world, then ask the world for the fleck color - engine->TraceLineMaterialAndLighting( trace->startpos, end, diffuseColor, baseColor ); + if ( !engine->TraceLineMaterialAndLighting( trace->startpos, end, diffuseColor, baseColor ) ) { + baseColor = vec3_invalid; // Make sure this wasn't modified + } } else { + kind = "Static Prop"; // In this case we hit a static prop. staticpropmgr->GetStaticPropMaterialColorAndLighting( trace, trace->hitbox - 1, diffuseColor, baseColor ); } @@ -117,20 +122,24 @@ void GetColorForSurface( trace_t *trace, Vector *color ) C_BaseEntity *pEnt = trace->m_pEnt; if ( !pEnt ) { - Msg("Couldn't find surface in GetColorForSurface()\n"); - color->x = 255; - color->y = 255; - color->z = 255; - return; + kind = "Null-Entity"; + } else { + kind = "Entity"; + ICollideable *pCollide = pEnt->GetCollideable(); + int modelIndex = pCollide->GetCollisionModelIndex(); + model_t* pModel = const_cast(modelinfo->GetModel( modelIndex )); + + // Ask the model info about what we need to know + modelinfo->GetModelMaterialColorAndLighting( pModel, pCollide->GetCollisionOrigin(), + pCollide->GetCollisionAngles(), trace, diffuseColor, baseColor ); } + } - ICollideable *pCollide = pEnt->GetCollideable(); - int modelIndex = pCollide->GetCollisionModelIndex(); - model_t* pModel = const_cast(modelinfo->GetModel( modelIndex )); - - // Ask the model info about what we need to know - modelinfo->GetModelMaterialColorAndLighting( pModel, pCollide->GetCollisionOrigin(), - pCollide->GetCollisionAngles(), trace, diffuseColor, baseColor ); + if ( baseColor == vec3_invalid ) + { + Warning( "Couldn't find surface color of %s\n", kind ); + baseColor = Vector( .5f, .5f, .5f ); + diffuseColor = engine->GetLightForPoint( trace->endpos, true ); } //Get final light value diff --git a/src/game/client/c_lightglow.cpp b/src/game/client/c_lightglow.cpp index 06f20a6c..501a2612 100644 --- a/src/game/client/c_lightglow.cpp +++ b/src/game/client/c_lightglow.cpp @@ -102,6 +102,10 @@ public: C_LightGlowOverlay m_Glow; float m_flGlowProxySize; + +#ifdef MAPBASE + bool m_bDisabled; +#endif }; static void RecvProxy_HDRColorScale( const CRecvProxyData *pData, void *pStruct, void *pOut ) @@ -123,6 +127,9 @@ IMPLEMENT_CLIENTCLASS_DT_NOBASE( C_LightGlow, DT_LightGlow, CLightGlow ) RecvPropQAngles( RECVINFO_NAME( m_angNetworkAngles, m_angRotation ) ), RecvPropInt( RECVINFO_NAME(m_hNetworkMoveParent, moveparent), 0, RecvProxy_IntToMoveParent ), RecvPropFloat(RECVINFO(m_flGlowProxySize)), +#ifdef MAPBASE + RecvPropBool( RECVINFO( m_bDisabled ) ), +#endif RecvPropFloat("HDRColorScale", 0, SIZEOF_IGNORE, 0, RecvProxy_HDRColorScale), END_RECV_TABLE() @@ -202,7 +209,11 @@ void C_LightGlow::OnDataChanged( DataUpdateType_t updateType ) void C_LightGlow::ClientThink( void ) { Vector mins = GetAbsOrigin(); +#ifdef MAPBASE + if ( engine->IsBoxVisible( mins, mins ) && !m_bDisabled ) +#else if ( engine->IsBoxVisible( mins, mins ) ) +#endif { m_Glow.Activate(); } diff --git a/src/game/client/c_movie_display.cpp b/src/game/client/c_movie_display.cpp new file mode 100644 index 00000000..27327403 --- /dev/null +++ b/src/game/client/c_movie_display.cpp @@ -0,0 +1,27 @@ +//========= Copyright © 1996-2009, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// +//=====================================================================================// +#include "cbase.h" +#include "c_movie_display.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +IMPLEMENT_CLIENTCLASS_DT( C_MovieDisplay, DT_MovieDisplay, CMovieDisplay ) + RecvPropBool( RECVINFO( m_bEnabled ) ), + RecvPropBool( RECVINFO( m_bLooping ) ), + RecvPropBool( RECVINFO( m_bMuted ) ), + RecvPropString( RECVINFO( m_szMovieFilename ) ), + RecvPropString( RECVINFO( m_szGroupName ) ), +END_RECV_TABLE() + +C_MovieDisplay::C_MovieDisplay() +{ +} + +C_MovieDisplay::~C_MovieDisplay() +{ +} diff --git a/src/game/client/c_movie_display.h b/src/game/client/c_movie_display.h new file mode 100644 index 00000000..55d0211f --- /dev/null +++ b/src/game/client/c_movie_display.h @@ -0,0 +1,36 @@ +//========= Copyright © 1996-2009, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=====================================================================================// + +#ifndef C_MOVIE_DISPLAY_H +#define C_MOVIE_DISPLAY_H + +#include "cbase.h" + +class C_MovieDisplay : public C_BaseEntity +{ +public: + DECLARE_CLASS( C_MovieDisplay, C_BaseEntity ); + DECLARE_CLIENTCLASS(); + + C_MovieDisplay(); + ~C_MovieDisplay(); + + bool IsEnabled( void ) const { return m_bEnabled; } + bool IsLooping( void ) const { return m_bLooping; } + bool IsMuted(void) const { return m_bMuted; } + + const char *GetMovieFilename( void ) const { return m_szMovieFilename; } + const char *GetGroupName( void ) const { return m_szGroupName; } + +private: + bool m_bEnabled; + bool m_bLooping; + bool m_bMuted; + char m_szMovieFilename[128]; + char m_szGroupName[128]; +}; + +#endif //C_MOVIE_DISPLAY_H \ No newline at end of file diff --git a/src/game/client/c_particle_system.cpp b/src/game/client/c_particle_system.cpp index e0aee808..74ac224f 100644 --- a/src/game/client/c_particle_system.cpp +++ b/src/game/client/c_particle_system.cpp @@ -32,6 +32,9 @@ public: protected: int m_iEffectIndex; bool m_bActive; +#ifdef MAPBASE + bool m_bDestroyImmediately; +#endif bool m_bOldActive; float m_flStartTime; // Time at which the effect started @@ -39,6 +42,7 @@ protected: EHANDLE m_hControlPointEnts[kMAXCONTROLPOINTS]; + Vector m_vControlPointVecs[kMAXCONTROLPOINTS]; // SendPropArray3( SENDINFO_ARRAY3(m_iControlPointParents), SendPropInt( SENDINFO_ARRAY(m_iControlPointParents), 3, SPROP_UNSIGNED ) ), unsigned char m_iControlPointParents[kMAXCONTROLPOINTS]; @@ -56,9 +60,13 @@ BEGIN_RECV_TABLE_NOBASE( C_ParticleSystem, DT_ParticleSystem ) RecvPropInt( RECVINFO( m_iEffectIndex ) ), RecvPropBool( RECVINFO( m_bActive ) ), +#ifdef MAPBASE + RecvPropBool( RECVINFO( m_bDestroyImmediately ) ), +#endif RecvPropFloat( RECVINFO( m_flStartTime ) ), RecvPropArray3( RECVINFO_ARRAY(m_hControlPointEnts), RecvPropEHandle( RECVINFO( m_hControlPointEnts[0] ) ) ), + RecvPropArray3( RECVINFO_ARRAY(m_vControlPointVecs), RecvPropVector( RECVINFO( m_vControlPointVecs[0] ) ) ), RecvPropArray3( RECVINFO_ARRAY(m_iControlPointParents), RecvPropInt( RECVINFO(m_iControlPointParents[0]))), RecvPropBool( RECVINFO( m_bWeatherEffect ) ), END_RECV_TABLE(); @@ -108,9 +116,18 @@ void C_ParticleSystem::PostDataUpdate( DataUpdateType_t updateType ) SetNextClientThink( gpGlobals->curtime ); } else +#ifdef MAPBASE + { + if (!m_bDestroyImmediately) + ParticleProp()->StopEmission(); + else + ParticleProp()->StopEmissionAndDestroyImmediately(); + } +#else { ParticleProp()->StopEmission(); } +#endif } } } @@ -135,21 +152,41 @@ void C_ParticleSystem::ClientThink( void ) AssertMsg1( pEffect, "Particle system couldn't make %s", pszName ); if (pEffect) { - for ( int i = 0 ; i < kMAXCONTROLPOINTS ; ++i ) + if (m_vControlPointVecs[0] != GetAbsOrigin() && m_hControlPointEnts[0] == NULL) { - CBaseEntity *pOnEntity = m_hControlPointEnts[i].Get(); - if ( pOnEntity ) + // we are using info_particle_system_coordinate + for (int i = 0; i < kMAXCONTROLPOINTS; ++i) { - ParticleProp()->AddControlPoint( pEffect, i + 1, pOnEntity, PATTACH_ABSORIGIN_FOLLOW ); + ParticleProp()->AddControlPoint(pEffect, i + 1, this, PATTACH_WORLDORIGIN, 0, m_vControlPointVecs[i] - GetAbsOrigin()); + + AssertMsg2(m_iControlPointParents[i] >= 0 && m_iControlPointParents[i] <= kMAXCONTROLPOINTS, + "Particle system specified bogus control point parent (%d) for point %d.", + m_iControlPointParents[i], i); + + if (m_iControlPointParents[i] != 0) + { + pEffect->SetControlPointParent(i + 1, m_iControlPointParents[i]); + } } - - AssertMsg2( m_iControlPointParents[i] >= 0 && m_iControlPointParents[i] <= kMAXCONTROLPOINTS , - "Particle system specified bogus control point parent (%d) for point %d.", - m_iControlPointParents[i], i ); - - if (m_iControlPointParents[i] != 0) + } + else + { + for ( int i = 0 ; i < kMAXCONTROLPOINTS ; ++i ) { - pEffect->SetControlPointParent(i+1, m_iControlPointParents[i]); + CBaseEntity *pOnEntity = m_hControlPointEnts[i].Get(); + if ( pOnEntity ) + { + ParticleProp()->AddControlPoint( pEffect, i + 1, pOnEntity, PATTACH_ABSORIGIN_FOLLOW ); + } + + AssertMsg2( m_iControlPointParents[i] >= 0 && m_iControlPointParents[i] <= kMAXCONTROLPOINTS , + "Particle system specified bogus control point parent (%d) for point %d.", + m_iControlPointParents[i], i ); + + if (m_iControlPointParents[i] != 0) + { + pEffect->SetControlPointParent(i+1, m_iControlPointParents[i]); + } } } diff --git a/src/game/client/c_pixel_visibility.cpp b/src/game/client/c_pixel_visibility.cpp index 85434892..4fa5ce7e 100644 --- a/src/game/client/c_pixel_visibility.cpp +++ b/src/game/client/c_pixel_visibility.cpp @@ -429,8 +429,10 @@ void CPixelVisibilityQuery::IssueQuery( IMatRenderContext *pRenderContext, float return; } } +#ifndef MAPBASE // Mapbase can also query visibility several times via multiple point_cameras, etc. #ifndef PORTAL // FIXME: In portal we query visibility multiple times per frame because of portal renders! Assert ( ( m_frameIssued != gpGlobals->framecount ) || UseVR() ); +#endif #endif m_frameIssued = gpGlobals->framecount; diff --git a/src/game/client/c_playerlocaldata.h b/src/game/client/c_playerlocaldata.h index 9994f79f..3c6f2724 100644 --- a/src/game/client/c_playerlocaldata.h +++ b/src/game/client/c_playerlocaldata.h @@ -83,6 +83,10 @@ public: bool m_bSlowMovement; CNetworkString( m_szScriptOverlayMaterial, MAX_PATH ); + + //Tony; added so tonemap controller can work in multiplayer with inputs. + tonemap_params_t m_TonemapParams; + }; #endif // C_PLAYERLOCALDATA_H diff --git a/src/game/client/c_point_camera.cpp b/src/game/client/c_point_camera.cpp index b234b694..ad3b8d4d 100644 --- a/src/game/client/c_point_camera.cpp +++ b/src/game/client/c_point_camera.cpp @@ -26,6 +26,10 @@ IMPLEMENT_CLIENTCLASS_DT( C_PointCamera, DT_PointCamera, CPointCamera ) RecvPropInt( RECVINFO( m_bFogRadial ) ), RecvPropInt( RECVINFO( m_bActive ) ), RecvPropInt( RECVINFO( m_bUseScreenAspectRatio ) ), +#ifdef MAPBASE + RecvPropInt( RECVINFO( m_iSkyMode ) ), + RecvPropString( RECVINFO( m_iszRenderTarget ) ), +#endif END_RECV_TABLE() C_EntityClassList g_PointCameraList; @@ -42,6 +46,10 @@ C_PointCamera::C_PointCamera() m_bFogEnable = false; m_bFogRadial = false; +#ifdef MAPBASE + m_iszRenderTarget[0] = '\0'; +#endif + g_PointCameraList.Insert( this ); } @@ -55,6 +63,16 @@ bool C_PointCamera::ShouldDraw() return false; } +void C_PointCamera::OnDataChanged( DataUpdateType_t type ) +{ +#ifdef MAPBASE + // Reset render texture + m_pRenderTarget = NULL; +#endif + + return BaseClass::OnDataChanged( type ); +} + float C_PointCamera::GetFOV() { return m_FOV; @@ -119,4 +137,31 @@ void C_PointCamera::GetToolRecordingState( KeyValues *msg ) msg->SetPtr( "monitor", &state ); } +#ifdef MAPBASE +extern ITexture *GetCameraTexture( void ); +extern void AddReleaseFunc( void ); + +ITexture *C_PointCamera::RenderTarget() +{ + if (m_iszRenderTarget[0] != '\0') + { + if (!m_pRenderTarget) + { + // We don't use a CTextureReference for this because we don't want to shut down the texture on removal/change + m_pRenderTarget = materials->FindTexture( m_iszRenderTarget, TEXTURE_GROUP_RENDER_TARGET ); + } + + if (m_pRenderTarget) + return m_pRenderTarget; + } + + return GetCameraTexture(); +} + +IMPLEMENT_CLIENTCLASS_DT( C_PointCameraOrtho, DT_PointCameraOrtho, CPointCameraOrtho ) + RecvPropInt( RECVINFO( m_bOrtho ) ), + RecvPropArray( RecvPropFloat( RECVINFO( m_OrthoDimensions[0] ) ), m_OrthoDimensions ), +END_RECV_TABLE() +#endif + diff --git a/src/game/client/c_point_camera.h b/src/game/client/c_point_camera.h index 3dd70416..8d73075a 100644 --- a/src/game/client/c_point_camera.h +++ b/src/game/client/c_point_camera.h @@ -29,6 +29,9 @@ public: // C_BaseEntity. virtual bool ShouldDraw(); + // Mapbase uses this for m_iszRenderTarget + virtual void OnDataChanged( DataUpdateType_t type ); + float GetFOV(); float GetResolution(); bool IsFogEnabled(); @@ -38,6 +41,14 @@ public: float GetFogEnd(); bool GetFogRadial(); bool UseScreenAspectRatio() const { return m_bUseScreenAspectRatio; } +#ifdef MAPBASE + virtual bool IsOrtho() const { return false; } + virtual void GetOrthoDimensions(float &up, float &dn, float &lf, float &rt) const {} + + SkyboxVisibility_t SkyMode() { return m_iSkyMode; } + + ITexture *RenderTarget(); +#endif virtual void GetToolRecordingState( KeyValues *msg ); @@ -52,11 +63,37 @@ private: bool m_bFogRadial; bool m_bActive; bool m_bUseScreenAspectRatio; +#ifdef MAPBASE + SkyboxVisibility_t m_iSkyMode; + ITexture *m_pRenderTarget; + char m_iszRenderTarget[64]; +#endif public: C_PointCamera *m_pNext; }; +#ifdef MAPBASE +class C_PointCameraOrtho : public C_PointCamera +{ +public: + DECLARE_CLASS( C_PointCameraOrtho, C_PointCamera ); + DECLARE_CLIENTCLASS(); + +public: + bool IsOrtho() const { return m_bOrtho; } + void GetOrthoDimensions( float &up, float &dn, float &lf, float &rt ) const + { + up = m_OrthoDimensions[0], dn = m_OrthoDimensions[1]; + lf = m_OrthoDimensions[2], rt = m_OrthoDimensions[3]; + } + +private: + bool m_bOrtho; + float m_OrthoDimensions[4]; +}; +#endif + C_PointCamera *GetPointCameraList(); #endif // C_POINTCAMERA_H diff --git a/src/game/client/c_point_commentary_node.cpp b/src/game/client/c_point_commentary_node.cpp index 527d5ca0..0982348a 100644 --- a/src/game/client/c_point_commentary_node.cpp +++ b/src/game/client/c_point_commentary_node.cpp @@ -18,6 +18,16 @@ #include "convar.h" #include "hud_closecaption.h" #include "in_buttons.h" +#ifdef MAPBASE +#include "vgui_controls/Label.h" +#include "vgui_controls/TextImage.h" +#include "vgui_controls/ImagePanel.h" +#include "vgui_controls/AnimationController.h" +#include "filesystem.h" +#include "scenefilecache/ISceneFileCache.h" +#include "choreoscene.h" +#include "c_sceneentity.h" +#endif // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" @@ -37,6 +47,17 @@ bool IsInCommentaryMode( void ) static bool g_bTracingVsCommentaryNodes = false; +#ifdef MAPBASE +ConVar commentary_type_force( "commentary_type_force", "-1", FCVAR_NONE, "Forces all commentary nodes to use the specified type." ); +ConVar commentary_type_text_endtime( "commentary_type_text_endtime", "120" ); +ConVar commentary_type_image_endtime( "commentary_type_image_endtime", "120" ); +ConVar commentary_audio_element_below_cc( "commentary_audio_element_below_cc", "1", FCVAR_NONE, "Allows commentary audio elements to display even when CC is enabled (although this is done by inverting their Y axis)" ); +ConVar commentary_audio_element_below_cc_margin( "commentary_audio_element_below_cc_margin", "4" ); +ConVar commentary_combine_speaker_and_printname( "commentary_combine_speaker_and_printname", "1" ); +ConVar commentary_footnote_offset_x( "commentary_footnote_offset_x", "16" ); +ConVar commentary_footnote_offset_y( "commentary_footnote_offset_y", "8" ); +#endif + //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- @@ -52,12 +73,28 @@ public: virtual void ApplySchemeSettings( vgui::IScheme *pScheme ); void StartCommentary( C_PointCommentaryNode *pNode, char *pszSpeakers, int iNode, int iNodeMax, float flStartTime, float flEndTime ); +#ifdef MAPBASE + void StartTextCommentary( C_PointCommentaryNode *pNode, const char *pszText, char *pszSpeakers, int iNode, int iNodeMax, float flStartTime, float flEndTime ); + void StartImageCommentary( C_PointCommentaryNode *pNode, const char *pszImage, char *pszSpeakers, int iNode, int iNodeMax, float flStartTime, float flEndTime ); + void StartSceneCommentary( C_PointCommentaryNode *pNode, char *pszSpeakers, int iNode, int iNodeMax, float flStartTime, float flEndTime ); +#endif void StopCommentary( void ); bool IsTheActiveNode( C_PointCommentaryNode *pNode ) { return (pNode == m_hActiveNode); } +#ifdef MAPBASE + void FixupCommentaryLabels( const char *pszPrintName, const char *pszSpeakers, const char *pszFootnote ); + void RepositionAndFollowCloseCaption( int yOffset = 0 ); +#endif + // vgui overrides virtual void Paint( void ); virtual bool ShouldDraw( void ); +#ifdef MAPBASE + virtual void PerformLayout(); + void ResolveBounds( int width, int height ); + + virtual void LevelShutdown(); +#endif private: CHandle m_hActiveNode; @@ -68,6 +105,25 @@ private: wchar_t m_szCount[MAX_COUNT_STRING]; CMaterialReference m_matIcon; bool m_bHiding; +#ifdef MAPBASE + int m_iCommentaryType; + float m_flPanelScale; + float m_flOverrideX; + float m_flOverrideY; + + vgui::Label *m_pLabel; + vgui::ImagePanel *m_pImage; + vgui::HFont m_hFont; + + vgui::Label *m_pFootnoteLabel; + vgui::HFont m_hSmallFont; + + // HACKHACK: Needed as a failsafe to prevent desync + int m_iCCDefaultY; + float m_flCCAnimTime; + + bool m_bShouldRepositionSubtitles; +#endif // Painting CPanelAnimationVarAliasType( int, m_iBarX, "bar_xpos", "8", "proportional_int" ); @@ -84,24 +140,65 @@ private: CPanelAnimationVarAliasType( int, m_iIconTall, "icon_height", "8", "proportional_int" ); CPanelAnimationVarAliasType( int, m_nIconTextureId, "icon_texture", "vgui/hud/icon_commentary", "textureid" ); +#ifdef MAPBASE + CPanelAnimationVarAliasType( int, m_iTypeAudioX, "type_audio_xpos", "190", "proportional_int" ); + CPanelAnimationVarAliasType( int, m_iTypeAudioY, "type_audio_ypos", "350", "proportional_int" ); + CPanelAnimationVarAliasType( int, m_iTypeAudioW, "type_audio_wide", "380", "proportional_int" ); + CPanelAnimationVarAliasType( int, m_iTypeAudioT, "type_audio_tall", "40", "proportional_int" ); + CPanelAnimationVarAliasType( int, m_iTypeTextX, "type_text_xpos", "180", "proportional_int" ); + CPanelAnimationVarAliasType( int, m_iTypeTextY, "type_text_ypos", "150", "proportional_int" ); + CPanelAnimationVarAliasType( int, m_iTypeTextW, "type_text_wide", "400", "proportional_int" ); + CPanelAnimationVarAliasType( int, m_iTypeTextT, "type_text_tall", "200", "proportional_int" ); + CPanelAnimationVarAliasType( int, m_iTypeTextCountXFR, "type_text_count_xpos_from_right", "10", "proportional_int" ); + CPanelAnimationVarAliasType( int, m_iTypeTextCountYFB, "type_text_count_ypos_from_bottom", "10", "proportional_int" ); + CPanelAnimationVar( Color, m_TextBackgroundColor, "BackgroundColorTextContent", "0 0 0 224" ); + CPanelAnimationVar( Color, m_TypeTextContentColor, "TextContentColor", "255 230 180 255" ); + CPanelAnimationVar( int, m_iTextBorderSpace, "type_text_border_space", "8" ); +#endif + CPanelAnimationVar( bool, m_bUseScriptBGColor, "use_script_bgcolor", "0" ); +#ifdef MAPBASE + CPanelAnimationVar( Color, m_BackgroundColor, "BackgroundColor", "Panel.BgColor" ); + CPanelAnimationVar( Color, m_ForegroundColor, "ForegroundColor", "255 170 0 255" ); +#else CPanelAnimationVar( Color, m_BackgroundColor, "BackgroundColor", "0 0 0 0" ); +#endif CPanelAnimationVar( Color, m_BGOverrideColor, "BackgroundOverrideColor", "Panel.BgColor" ); }; //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- -class C_PointCommentaryNode : public C_BaseAnimating +class C_PointCommentaryNode : public C_BaseAnimating, public IChoreoEventCallback { DECLARE_CLASS( C_PointCommentaryNode, C_BaseAnimating ); public: DECLARE_CLIENTCLASS(); DECLARE_DATADESC(); +#ifdef MAPBASE_VSCRIPT + DECLARE_ENT_SCRIPTDESC(); +#endif virtual void OnPreDataChanged( DataUpdateType_t type ); virtual void OnDataChanged( DataUpdateType_t type ); + void StartAudioCommentary( const char *pszCommentaryFile, C_BasePlayer *pPlayer ); +#ifdef MAPBASE + void StartTextCommentary( const char *pszCommentaryFile, C_BasePlayer *pPlayer ); + void StartImageCommentary( const char *pszCommentaryFile, C_BasePlayer *pPlayer ); + void StartSceneCommentary( const char *pszCommentaryFile, C_BasePlayer *pPlayer ); + + // From IChoreoEventCallback + virtual void StartEvent( float currenttime, CChoreoScene *scene, CChoreoEvent *event ); +#else + virtual void StartEvent( float currenttime, CChoreoScene *scene, CChoreoEvent *event ) {} +#endif + virtual void EndEvent( float currenttime, CChoreoScene *scene, CChoreoEvent *event ) {} + virtual void ProcessEvent( float currenttime, CChoreoScene *scene, CChoreoEvent *event ) {} + virtual bool CheckEvent( float currenttime, CChoreoScene *scene, CChoreoEvent *event ) { return true; } + + void ClientThink(); + void OnRestore( void ) { BaseClass::OnRestore(); @@ -149,6 +246,11 @@ public: { int iRenderGroup = gHUD.LookupRenderGroupIndexByName( "commentary" ); gHUD.LockRenderGroup( iRenderGroup ); + +#ifdef MAPBASE + // Special commentary localization file (useful for things like text nodes or print names) + g_pVGuiLocalize->AddFile( "resource/commentary_%language%.txt", "MOD", true ); +#endif } if ( g_CommentaryNodes.Find(this) == g_CommentaryNodes.InvalidIndex() ) @@ -168,6 +270,22 @@ public: } } +#ifdef MAPBASE_VSCRIPT // VScript funcs + bool IsActive() { return m_bActive; } + + int GetCommentaryType() { return m_iCommentaryType; } + void SetCommentaryType( int iType ) { m_iCommentaryType = iType; } + + const char *GetCommentaryFile() { return m_iszCommentaryFile; } + void SetCommentaryFile( const char *pszNewFile ) { Q_strncpy( m_iszCommentaryFile, pszNewFile, sizeof( m_iszCommentaryFile ) ); } + const char *GetSpeakers() { return m_iszSpeakers; } + void SetSpeakers( const char *pszSpeakers ) { Q_strncpy( m_iszSpeakers, pszSpeakers, sizeof( m_iszSpeakers ) ); } + const char *GetPrintName() { return m_iszPrintName; } + void SetPrintName( const char *pszPrintName ) { Q_strncpy( m_iszPrintName, pszPrintName, sizeof( m_iszPrintName ) ); } + const char *GetFootnote() { return m_iszFootnote; } + void SetFootnote( const char *pszFootnote ) { Q_strncpy( m_iszFootnote, pszFootnote, sizeof( m_iszFootnote ) ); } +#endif + public: // Data received from the server bool m_bActive; @@ -181,6 +299,22 @@ public: CSoundPatch *m_sndCommentary; EHANDLE m_hViewPosition; bool m_bRestartAfterRestore; +#ifdef MAPBASE + char m_iszPrintName[MAX_SPEAKER_NAME]; + char m_iszFootnote[MAX_SPEAKER_NAME]; + int m_iCommentaryType; + float m_flPanelScale; + float m_flPanelX; + float m_flPanelY; + + CChoreoScene *m_pScene; + //CHandle m_hScene; + EHANDLE m_hSceneOrigin; +#endif + +#ifdef MAPBASE_VSCRIPT + static ScriptHook_t g_Hook_PreStartCommentaryClient; +#endif }; IMPLEMENT_CLIENTCLASS_DT(C_PointCommentaryNode, DT_PointCommentaryNode, CPointCommentaryNode) @@ -192,6 +326,14 @@ IMPLEMENT_CLIENTCLASS_DT(C_PointCommentaryNode, DT_PointCommentaryNode, CPointCo RecvPropInt( RECVINFO( m_iNodeNumber ) ), RecvPropInt( RECVINFO( m_iNodeNumberMax ) ), RecvPropEHandle( RECVINFO(m_hViewPosition) ), +#ifdef MAPBASE + RecvPropString( RECVINFO( m_iszPrintName ) ), + RecvPropString( RECVINFO( m_iszFootnote ) ), + RecvPropInt( RECVINFO( m_iCommentaryType ) ), + RecvPropFloat( RECVINFO( m_flPanelScale ) ), + RecvPropFloat( RECVINFO( m_flPanelX ) ), + RecvPropFloat( RECVINFO( m_flPanelY ) ), +#endif END_RECV_TABLE() BEGIN_DATADESC( C_PointCommentaryNode ) @@ -200,6 +342,28 @@ BEGIN_DATADESC( C_PointCommentaryNode ) DEFINE_SOUNDPATCH( m_sndCommentary ), END_DATADESC() +#ifdef MAPBASE_VSCRIPT +ScriptHook_t C_PointCommentaryNode::g_Hook_PreStartCommentaryClient; + +BEGIN_ENT_SCRIPTDESC( C_PointCommentaryNode, C_BaseAnimating, "Commentary nodes which play commentary in commentary mode." ) + + DEFINE_SCRIPTFUNC( IsActive, "" ) + DEFINE_SCRIPTFUNC( GetCommentaryFile, "" ) + DEFINE_SCRIPTFUNC( SetCommentaryFile, "" ) + DEFINE_SCRIPTFUNC( GetSpeakers, "" ) + DEFINE_SCRIPTFUNC( SetSpeakers, "" ) + DEFINE_SCRIPTFUNC( GetPrintName, "" ) + DEFINE_SCRIPTFUNC( SetPrintName, "" ) + DEFINE_SCRIPTFUNC( GetFootnote, "" ) + DEFINE_SCRIPTFUNC( SetFootnote, "" ) + DEFINE_SCRIPTFUNC( GetCommentaryType, "" ) + DEFINE_SCRIPTFUNC( SetCommentaryType, "" ) + + DEFINE_SIMPLE_SCRIPTHOOK( C_PointCommentaryNode::g_Hook_PreStartCommentaryClient, "PreStartCommentaryClient", FIELD_BOOLEAN, "Called just before commentary begins on the client. Use this to modify variables or commentary behavior before it begins. Returning false will prevent the commentary from starting." ) + +END_SCRIPTDESC(); +#endif + //----------------------------------------------------------------------------- // Purpose: @@ -229,6 +393,22 @@ void C_PointCommentaryNode::OnDataChanged( DataUpdateType_t updateType ) C_BasePlayer *pPlayer = C_BasePlayer::GetLocalPlayer(); if ( m_bActive && pPlayer ) { +#ifdef MAPBASE_VSCRIPT + if (m_ScriptScope.IsInitialized() && g_Hook_PreStartCommentaryClient.CanRunInScope( m_ScriptScope )) + { + ScriptVariant_t functionReturn; + if (g_Hook_PreStartCommentaryClient.Call( m_ScriptScope, &functionReturn, NULL ) && functionReturn.m_type == FIELD_BOOLEAN) + { + // Don't play the commentary if it returned false + if (functionReturn.m_bool == false) + { + engine->ServerCmd( "commentary_finishnode\n" ); + return; + } + } + } +#endif + // Use the HDR / Non-HDR version based on whether we're running HDR or not char *pszCommentaryFile; if ( g_pMaterialSystemHardwareConfig->GetHDRType() == HDR_TYPE_NONE && m_iszCommentaryFileNoHDR[0] ) @@ -245,58 +425,33 @@ void C_PointCommentaryNode::OnDataChanged( DataUpdateType_t updateType ) return; } - EmitSound_t es; - es.m_nChannel = CHAN_STATIC; - es.m_pSoundName = pszCommentaryFile; - es.m_SoundLevel = SNDLVL_GUNFIRE; - es.m_nFlags = SND_SHOULDPAUSE; +#ifdef MAPBASE + int iCommentaryType = m_iCommentaryType; + if (commentary_type_force.GetInt() != -1) + iCommentaryType = commentary_type_force.GetInt(); - CBaseEntity *pSoundEntity; - if ( m_hViewPosition ) + switch (iCommentaryType) { - pSoundEntity = m_hViewPosition; - } - else if ( render->GetViewEntity() ) - { - pSoundEntity = cl_entitylist->GetEnt( render->GetViewEntity() ); - es.m_SoundLevel = SNDLVL_NONE; - } - else - { - pSoundEntity = pPlayer; - } - CSingleUserRecipientFilter filter( pPlayer ); - m_sndCommentary = (CSoundEnvelopeController::GetController()).SoundCreate( filter, pSoundEntity->entindex(), es ); - if ( m_sndCommentary ) - { - (CSoundEnvelopeController::GetController()).SoundSetCloseCaptionDuration( m_sndCommentary, -1 ); - (CSoundEnvelopeController::GetController()).Play( m_sndCommentary, 1.0f, 100, m_flStartTime ); - } + case COMMENTARY_TYPE_TEXT: + StartTextCommentary( pszCommentaryFile, pPlayer ); + break; - // Get the duration so we know when it finishes - float flDuration = enginesound->GetSoundDuration( STRING( CSoundEnvelopeController::GetController().SoundGetName( m_sndCommentary ) ) ) ; + case COMMENTARY_TYPE_IMAGE: + StartImageCommentary( pszCommentaryFile, pPlayer ); + break; - CHudCloseCaption *pHudCloseCaption = (CHudCloseCaption *)GET_HUDELEMENT( CHudCloseCaption ); - if ( pHudCloseCaption ) - { - // This is where we play the commentary close caption (and lock the other captions out). - // Also, if close captions are off we force a caption in non-English - if ( closecaption.GetBool() || ( !closecaption.GetBool() && !english.GetBool() ) ) - { - // Clear the close caption element in preparation - pHudCloseCaption->Reset(); + case COMMENTARY_TYPE_SCENE: + StartSceneCommentary( pszCommentaryFile, pPlayer ); + break; - // Process the commentary caption - pHudCloseCaption->ProcessCaptionDirect( pszCommentaryFile, flDuration ); - - // Find the close caption hud element & lock it - pHudCloseCaption->Lock(); - } + default: + case COMMENTARY_TYPE_AUDIO: + StartAudioCommentary( pszCommentaryFile, pPlayer ); + break; } - - // Tell the HUD element - CHudCommentary *pHudCommentary = (CHudCommentary *)GET_HUDELEMENT( CHudCommentary ); - pHudCommentary->StartCommentary( this, m_iszSpeakers, m_iNodeNumber, m_iNodeNumberMax, m_flStartTime, m_flStartTime + flDuration ); +#else + StartAudioCommentary( pszCommentaryFile, pPlayer ); +#endif } else if ( m_bWasActive ) { @@ -312,6 +467,356 @@ void C_PointCommentaryNode::OnDataChanged( DataUpdateType_t updateType ) m_bRestartAfterRestore = false; } +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_PointCommentaryNode::StartAudioCommentary( const char *pszCommentaryFile, C_BasePlayer *pPlayer ) +{ + EmitSound_t es; + es.m_nChannel = CHAN_STATIC; + es.m_pSoundName = pszCommentaryFile; + es.m_SoundLevel = SNDLVL_GUNFIRE; + es.m_nFlags = SND_SHOULDPAUSE; + + CBaseEntity *pSoundEntity; + if ( m_hViewPosition ) + { + pSoundEntity = m_hViewPosition; + } + else if ( render->GetViewEntity() ) + { + pSoundEntity = cl_entitylist->GetEnt( render->GetViewEntity() ); + es.m_SoundLevel = SNDLVL_NONE; + } + else + { + pSoundEntity = pPlayer; + } + CSingleUserRecipientFilter filter( pPlayer ); + m_sndCommentary = (CSoundEnvelopeController::GetController()).SoundCreate( filter, pSoundEntity->entindex(), es ); + if ( m_sndCommentary ) + { + (CSoundEnvelopeController::GetController()).SoundSetCloseCaptionDuration( m_sndCommentary, -1 ); + (CSoundEnvelopeController::GetController()).Play( m_sndCommentary, 1.0f, 100, m_flStartTime ); + } + + // Get the duration so we know when it finishes + float flDuration = enginesound->GetSoundDuration( STRING( CSoundEnvelopeController::GetController().SoundGetName( m_sndCommentary ) ) ) ; + bool bSubtitlesEnabled = false; + + CHudCloseCaption *pHudCloseCaption = (CHudCloseCaption *)GET_HUDELEMENT( CHudCloseCaption ); + if ( pHudCloseCaption ) + { + // This is where we play the commentary close caption (and lock the other captions out). + // Also, if close captions are off we force a caption in non-English + if ( closecaption.GetBool() || ( !closecaption.GetBool() && !english.GetBool() ) ) + { + // Clear the close caption element in preparation + pHudCloseCaption->Reset(); + + // Process the commentary caption + pHudCloseCaption->ProcessCaptionDirect( pszCommentaryFile, flDuration ); + + // Find the close caption hud element & lock it + pHudCloseCaption->Lock(); + + bSubtitlesEnabled = true; + } + } + + char *pszSpeakers = m_iszSpeakers; + + // Tell the HUD element + CHudCommentary *pHudCommentary = (CHudCommentary *)GET_HUDELEMENT( CHudCommentary ); + pHudCommentary->StartCommentary( this, pszSpeakers, m_iNodeNumber, m_iNodeNumberMax, m_flStartTime, m_flStartTime + flDuration ); +} + +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_PointCommentaryNode::StartTextCommentary( const char *pszCommentaryFile, C_BasePlayer *pPlayer ) +{ + // Get the duration so we know when it finishes + //float flDuration = enginesound->GetSoundDuration( STRING( CSoundEnvelopeController::GetController().SoundGetName( m_sndCommentary ) ) ) ; + + // TODO: Determine from text length? + float flDuration = commentary_type_text_endtime.GetFloat(); + + // Tell the HUD element + CHudCommentary *pHudCommentary = (CHudCommentary *)GET_HUDELEMENT( CHudCommentary ); + pHudCommentary->StartTextCommentary( this, pszCommentaryFile, m_iszSpeakers, m_iNodeNumber, m_iNodeNumberMax, m_flStartTime, m_flStartTime + flDuration ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_PointCommentaryNode::StartImageCommentary( const char *pszCommentaryFile, C_BasePlayer *pPlayer ) +{ + // Get the duration so we know when it finishes + //float flDuration = enginesound->GetSoundDuration( STRING( CSoundEnvelopeController::GetController().SoundGetName( m_sndCommentary ) ) ) ; + + float flDuration = commentary_type_image_endtime.GetFloat(); + + // Tell the HUD element + CHudCommentary *pHudCommentary = (CHudCommentary *)GET_HUDELEMENT( CHudCommentary ); + pHudCommentary->StartImageCommentary( this, pszCommentaryFile, m_iszSpeakers, m_iNodeNumber, m_iNodeNumberMax, m_flStartTime, m_flStartTime + flDuration ); +} + +extern CChoreoStringPool g_ChoreoStringPool; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_PointCommentaryNode::StartSceneCommentary( const char *pszCommentaryFile, C_BasePlayer *pPlayer ) +{ + char loadfile[MAX_PATH]; + Q_strncpy( loadfile, pszCommentaryFile, sizeof( loadfile ) ); + Q_SetExtension( loadfile, ".vcd", sizeof( loadfile ) ); + Q_FixSlashes( loadfile ); + + // + // Raw scene file support + // + void *pBuffer = 0; + size_t bufsize = scenefilecache->GetSceneBufferSize( loadfile ); + if ( bufsize > 0 ) + { + // Definitely in scenes.image + pBuffer = malloc( bufsize ); + if ( !scenefilecache->GetSceneData( pszCommentaryFile, (byte *)pBuffer, bufsize ) ) + { + free( pBuffer ); + } + + + if ( IsBufferBinaryVCD( (char*)pBuffer, bufsize ) ) + { + m_pScene = new CChoreoScene( NULL ); + CUtlBuffer buf( pBuffer, bufsize, CUtlBuffer::READ_ONLY ); + if ( !m_pScene->RestoreFromBinaryBuffer( buf, loadfile, &g_ChoreoStringPool ) ) + { + Warning( "Unable to restore scene '%s'\n", loadfile ); + delete m_pScene; + m_pScene = NULL; + } + } + } + else if (filesystem->ReadFileEx( loadfile, "MOD", &pBuffer, true )) + { + // Not in scenes.image, but it's a raw file + g_TokenProcessor.SetBuffer((char*)pBuffer); + m_pScene = ChoreoLoadScene( loadfile, this, &g_TokenProcessor, Scene_Printf ); + } + + free( pBuffer ); + + if( m_pScene ) + { + m_pScene->SetPrintFunc( Scene_Printf ); + m_pScene->SetEventCallbackInterface( this ); + } + else + { + // Cancel commentary (TODO: clean up?) + return; + } + + int types[ 2 ]; + types[ 0 ] = CChoreoEvent::SPEAK; + //types[ 1 ] = CChoreoEvent::GENERIC; // TODO: Support for the game_text event? + m_pScene->RemoveEventsExceptTypes( types, 1 ); + + // Iterate events and precache necessary resources + for ( int i = 0; i < m_pScene->GetNumEvents(); i++ ) + { + CChoreoEvent *event = m_pScene->GetEvent( i ); + if ( !event ) + continue; + + // load any necessary data + switch (event->GetType() ) + { + default: + break; + case CChoreoEvent::SPEAK: + { + // Defined in SoundEmitterSystem.cpp + // NOTE: The script entries associated with .vcds are forced to preload to avoid + // loading hitches during triggering + CBaseEntity::PrecacheScriptSound( event->GetParameters() ); + + if ( event->GetCloseCaptionType() == CChoreoEvent::CC_MASTER && + event->GetNumSlaves() > 0 ) + { + char tok[ CChoreoEvent::MAX_CCTOKEN_STRING ]; + if ( event->GetPlaybackCloseCaptionToken( tok, sizeof( tok ) ) ) + { + CBaseEntity::PrecacheScriptSound( tok ); + } + } + } + break; + } + } + + PrecacheScriptSound( "AI_BaseNPC.SentenceStop" ); + + if ( m_hViewPosition ) + { + m_hSceneOrigin = m_hViewPosition; + } + else if ( render->GetViewEntity() ) + { + m_hSceneOrigin = cl_entitylist->GetEnt( render->GetViewEntity() ); + } + else + { + m_hSceneOrigin = pPlayer; + } + + // Get the duration so we know when it finishes + float flDuration = m_pScene->GetDuration(); + + // Add a tiny amount of time at the end to ensure audio doesn't get cut off + flDuration += 0.5f; + + CHudCloseCaption *pHudCloseCaption = (CHudCloseCaption *)GET_HUDELEMENT( CHudCloseCaption ); + if ( pHudCloseCaption ) + { + // This is where we play the commentary close caption (and lock the other captions out). + // Also, if close captions are off we force a caption in non-English + if ( closecaption.GetBool() || ( !closecaption.GetBool() && !english.GetBool() ) ) + { + // Clear the close caption element in preparation + pHudCloseCaption->Reset(); + + // Find the close caption hud element & lock it + pHudCloseCaption->Lock(); + } + } + + // Tell the HUD element + CHudCommentary *pHudCommentary = (CHudCommentary *)GET_HUDELEMENT( CHudCommentary ); + pHudCommentary->StartSceneCommentary( this, m_iszSpeakers, m_iNodeNumber, m_iNodeNumberMax, m_flStartTime, m_flStartTime + flDuration ); + + // Start thinking for the scene + SetNextClientThink( CLIENT_THINK_ALWAYS ); +} + +//----------------------------------------------------------------------------- +// Purpose: All events are leading edge triggered +// Input : currenttime - +// *event - +//----------------------------------------------------------------------------- +void C_PointCommentaryNode::StartEvent( float currenttime, CChoreoScene *scene, CChoreoEvent *event ) +{ + Assert( event ); + + if ( !Q_stricmp( event->GetName(), "NULL" ) ) + { + return; + } + + //Msg("Starting event \"%s\" (%s)\n", event->GetName(), event->GetParameters()); + + // load any necessary data + switch (event->GetType() ) + { + default: + break; + case CChoreoEvent::SPEAK: + { + CSingleUserRecipientFilter filter( C_BasePlayer::GetLocalPlayer() ); + + CSoundParameters soundParams; + bool bSoundscript = (g_pSoundEmitterSystem->GetParametersForSound( event->GetParameters(), soundParams, GENDER_NONE, false )); + EmitSound_t es( soundParams ); + if (bSoundscript) + { + } + else + { + es.m_pSoundName = event->GetParameters(); + es.m_flVolume = 1; + } + + // TODO: This is supposed to make sure actors don't interrupt each other, but it doesn't seem to work + es.m_nChannel = CHAN_USER_BASE + scene->FindActorIndex( event->GetActor() ); + es.m_SoundLevel = SNDLVL_GUNFIRE; + es.m_nFlags = SND_SHOULDPAUSE; + + es.m_bEmitCloseCaption = false; + + // Just in case + if (!m_hSceneOrigin) + m_hSceneOrigin = C_BasePlayer::GetLocalPlayer(); + + EmitSound( filter, m_hSceneOrigin->entindex(), es ); + + // Close captioning only on master token no matter what... + // Also, if close captions are off we force a caption in non-English + if ( event->GetCloseCaptionType() == CChoreoEvent::CC_MASTER && closecaption.GetBool() || ( !closecaption.GetBool() && !english.GetBool() ) ) + { + char tok[ CChoreoEvent::MAX_CCTOKEN_STRING ]; + bool validtoken = event->GetPlaybackCloseCaptionToken( tok, sizeof( tok ) ); + if ( validtoken ) + { + CRC32_t tokenCRC; + CRC32_Init( &tokenCRC ); + + char lowercase[ 256 ]; + Q_strncpy( lowercase, tok, sizeof( lowercase ) ); + Q_strlower( lowercase ); + + CRC32_ProcessBuffer( &tokenCRC, lowercase, Q_strlen( lowercase ) ); + CRC32_Final( &tokenCRC ); + + float endtime = event->GetLastSlaveEndTime(); + float durationShort = event->GetDuration(); + float durationLong = endtime - event->GetStartTime(); + float duration = MAX( durationShort, durationLong ); + + CHudCloseCaption *hudCloseCaption = GET_HUDELEMENT( CHudCloseCaption ); + if ( hudCloseCaption ) + { + hudCloseCaption->ProcessCaptionDirect( lowercase, duration ); + } + } + + } + } + break; + // TODO: Support for the game_text event? + /* + case CChoreoEvent::GENERIC: + { + + } + break; + */ + } + + event->m_flPrevTime = currenttime; +} +#endif + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_PointCommentaryNode::ClientThink() +{ + BaseClass::ClientThink(); + +#ifdef MAPBASE + if (m_iCommentaryType == COMMENTARY_TYPE_SCENE && m_pScene) + { + m_pScene->Think( gpGlobals->curtime - m_flStartTime ); + SetNextClientThink( CLIENT_THINK_ALWAYS ); + } +#endif +} + //----------------------------------------------------------------------------- // Purpose: Shut down the commentary //----------------------------------------------------------------------------- @@ -322,6 +827,29 @@ void C_PointCommentaryNode::StopLoopingSounds( void ) (CSoundEnvelopeController::GetController()).SoundDestroy( m_sndCommentary ); m_sndCommentary = NULL; } + +#ifdef MAPBASE + if ( m_pScene ) + { + // Must do this to terminate audio + if (m_hSceneOrigin) + { + CSingleUserRecipientFilter filter( C_BasePlayer::GetLocalPlayer() ); + + for (int i = 0; i < m_pScene->GetNumActors(); i++) + { + EmitSound_t es; + es.m_nChannel = CHAN_USER_BASE + i; + es.m_pSoundName = "common/null.wav"; + + EmitSound( filter, m_hSceneOrigin->entindex(), es ); + } + } + + delete m_pScene; + m_pScene = NULL; + } +#endif } //----------------------------------------------------------------------------- @@ -374,6 +902,17 @@ CHudCommentary::CHudCommentary( const char *name ) : vgui::Panel( NULL, "HudComm m_hActiveNode = NULL; m_bShouldPaint = true; + +#ifdef MAPBASE + m_pLabel = new vgui::Label( this, "HudCommentaryTextLabel", L"Textual commentary" ); + m_pImage = new vgui::ImagePanel( this, "HudCommentaryImagePanel" ); + m_pImage->SetShouldScaleImage( true ); + + m_pFootnoteLabel = new vgui::Label( this, "HudCommentaryFootnoteLabel", L"Commentary footnote" ); + + m_iCCDefaultY = 0; + m_flCCAnimTime = 0.0f; +#endif } void CHudCommentary::ApplySchemeSettings( vgui::IScheme *pScheme ) @@ -384,6 +923,14 @@ void CHudCommentary::ApplySchemeSettings( vgui::IScheme *pScheme ) { SetBgColor( m_BGOverrideColor ); } + +#ifdef MAPBASE + m_pLabel->SetPaintBackgroundType( 2 ); + m_pLabel->SetSize( 0, GetTall() ); + + m_pFootnoteLabel->SetPaintBackgroundType( 2 ); + m_pFootnoteLabel->SetSize( 0, GetTall() ); +#endif } //----------------------------------------------------------------------------- @@ -405,6 +952,17 @@ void CHudCommentary::Paint() if ( pHudCloseCaption ) { pHudCloseCaption->Reset(); + +#ifdef MAPBASE + // Reset close caption element if needed + if (pHudCloseCaption->IsUsingCommentaryDimensions()) + { + // Run this animation command instead of setting the position directly + g_pClientMode->GetViewportAnimationController()->RunAnimationCommand( pHudCloseCaption, "YPos", m_iCCDefaultY, 0.0f, 0.4f, vgui::AnimationController::INTERPOLATOR_ACCEL ); + + pHudCloseCaption->SetUsingCommentaryDimensions( false ); + } +#endif } } } @@ -413,6 +971,22 @@ void CHudCommentary::Paint() // Detect the end of the commentary if ( flPercentage >= 1 && m_hActiveNode ) { +#ifdef MAPBASE + // Ensure that the scene is terminated + if (m_iCommentaryType == COMMENTARY_TYPE_SCENE) + m_hActiveNode->StopLoopingSounds(); + + // Reset close caption element if needed + CHudCloseCaption *pHudCloseCaption = (CHudCloseCaption *)GET_HUDELEMENT( CHudCloseCaption ); + if (pHudCloseCaption && pHudCloseCaption->IsUsingCommentaryDimensions()) + { + // Run this animation command instead of setting the position directly + g_pClientMode->GetViewportAnimationController()->RunAnimationCommand( pHudCloseCaption, "YPos", m_iCCDefaultY, 0.0f, 0.4f, vgui::AnimationController::INTERPOLATOR_ACCEL ); + + pHudCloseCaption->SetUsingCommentaryDimensions( false ); + } +#endif + m_hActiveNode = NULL; g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( "HideCommentary" ); @@ -426,27 +1000,62 @@ void CHudCommentary::Paint() int x, y, wide, tall; GetBounds( x, y, wide, tall ); - int xOffset = m_iBarX; + int xOffset = m_iBarX; int yOffset = m_iBarY; // Find our fade based on our time shown - Color clr = Color( 255, 170, 0, GetAlpha() ); + Color clr = m_ForegroundColor; +#ifdef MAPBASE + switch (m_iCommentaryType) + { + case COMMENTARY_TYPE_TEXT: + { + // Figure out the size before setting bounds + int lW, lT; + m_pLabel->GetContentSize( lW, lT ); + + lT += (m_iTextBorderSpace * 2); + + vgui::surface()->DrawSetColor( clr ); + vgui::surface()->DrawOutlinedRect( xOffset, yOffset, xOffset + (m_iBarWide * m_flPanelScale), yOffset + (lT /** m_flPanelScale*/) ); //m_iTypeTextT - (yOffset /*+ m_iBarTall*/) ); + } break; + + case COMMENTARY_TYPE_IMAGE: + { + // Figure out the size before setting bounds + int iW, iT; + m_pImage->GetSize( iW, iT ); + //vgui::surface()->DrawGetTextureSize( m_pImage->GetImage()->GetID(), iW, iT ); + + iW += (m_iTextBorderSpace * 2); + iT += (m_iTextBorderSpace * 2); + + vgui::surface()->DrawSetColor( clr ); + vgui::surface()->DrawOutlinedRect( xOffset, yOffset, xOffset + iW, yOffset + iT ); //m_iTypeTextT - (yOffset /*+ m_iBarTall*/) ); + } break; + + default: + case COMMENTARY_TYPE_SCENE: + case COMMENTARY_TYPE_AUDIO: + { + // Draw the progress bar + vgui::surface()->DrawSetColor( clr ); + vgui::surface()->DrawOutlinedRect( xOffset, yOffset, xOffset+(m_iBarWide*m_flPanelScale), yOffset+m_iBarTall ); + vgui::surface()->DrawSetColor( clr ); + vgui::surface()->DrawFilledRect( xOffset+2, yOffset+2, xOffset+(int)(flPercentage*(m_iBarWide*m_flPanelScale))-2, yOffset+m_iBarTall-2 ); + } break; + } +#else // Draw the progress bar vgui::surface()->DrawSetColor( clr ); vgui::surface()->DrawOutlinedRect( xOffset, yOffset, xOffset+m_iBarWide, yOffset+m_iBarTall ); vgui::surface()->DrawSetColor( clr ); vgui::surface()->DrawFilledRect( xOffset+2, yOffset+2, xOffset+(int)(flPercentage*m_iBarWide)-2, yOffset+m_iBarTall-2 ); +#endif // Draw the speaker names - // Get our scheme and font information - vgui::HScheme scheme = vgui::scheme()->GetScheme( "ClientScheme" ); - vgui::HFont hFont = vgui::scheme()->GetIScheme(scheme)->GetFont( "CommentaryDefault" ); - if ( !hFont ) - { - hFont = vgui::scheme()->GetIScheme(scheme)->GetFont( "Default" ); - } - vgui::surface()->DrawSetTextFont( hFont ); + vgui::surface()->DrawSetTextFont( m_hFont ); vgui::surface()->DrawSetTextColor( clr ); vgui::surface()->DrawSetTextPos( m_iSpeakersX, m_iSpeakersY ); vgui::surface()->DrawPrintText( m_szSpeakers, wcslen(m_szSpeakers) ); @@ -469,7 +1078,7 @@ void CHudCommentary::Paint() { int w, h; UTIL_ReplaceKeyBindings( pszText, 0, wzFinal, sizeof( wzFinal ) ); - vgui::surface()->GetTextSize( hFont, wzFinal, w, h ); + vgui::surface()->GetTextSize( m_hFont, wzFinal, w, h ); vgui::surface()->DrawSetTextPos( m_iBarX + m_iBarWide - w, iY ); vgui::surface()->DrawPrintText( wzFinal, wcslen(wzFinal) ); } @@ -478,9 +1087,45 @@ void CHudCommentary::Paint() // Draw the commentary count // Determine our text size, and move that far in from the right hand size (plus the offset) int iCountWide, iCountTall; - vgui::surface()->GetTextSize( hFont, m_szCount, iCountWide, iCountTall ); + vgui::surface()->GetTextSize( m_hFont, m_szCount, iCountWide, iCountTall ); + +#ifdef MAPBASE + if (m_pFootnoteLabel->IsEnabled()) + { + // Raise the count's position so that it doesn't get in the way + //iCountTall *= 2; + + int x, y; + m_pFootnoteLabel->GetPos(x, y); + + // + // Draw a bullet next to each footnote + // + CUtlVector pLineCoords; + pLineCoords.AddToTail( 0 ); // First line + + m_pFootnoteLabel->GetTextImage()->GetNewlinePositions( &pLineCoords, true ); + + int iBulletX = x - commentary_footnote_offset_x.GetInt(); + int iBulletY = y; + + vgui::surface()->DrawSetTextFont( m_hFont ); + vgui::surface()->DrawSetTextColor( clr ); + + for (int i = 0; i < pLineCoords.Count(); i++) + { + vgui::surface()->DrawSetTextPos( iBulletX, iBulletY + pLineCoords[i] ); + vgui::surface()->DrawUnicodeChar( L'\u2022' ); + } + } + + if (m_iCommentaryType != COMMENTARY_TYPE_AUDIO && m_iCommentaryType != COMMENTARY_TYPE_SCENE) + vgui::surface()->DrawSetTextPos( wide - m_iTypeTextCountXFR - iCountWide, tall - m_iTypeTextCountYFB - iCountTall ); + else +#endif vgui::surface()->DrawSetTextPos( wide - m_iCountXFR - iCountWide, m_iCountY ); - vgui::surface()->DrawPrintText( m_szCount, wcslen(m_szCount) ); + + vgui::surface()->DrawPrintText( m_szCount, wcslen( m_szCount ) ); // Draw the icon vgui::surface()->DrawSetColor( Color(255,170,0,GetAlpha()) ); @@ -496,12 +1141,207 @@ bool CHudCommentary::ShouldDraw() return ( m_hActiveNode || GetAlpha() > 0 ); } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CHudCommentary::PerformLayout() +{ + BaseClass::PerformLayout(); + + // Don't do anything if we shouldn't draw + if (!m_hActiveNode) // !ShouldDraw() + return; + + int extraWidth = 0, extraHeight = 0; + + // The dimensions of a progress bar, text card, etc. + int contentWidth = 0, contentHeight = 0; + + int xOffset = m_iBarX; + int yOffset = m_iBarY; + + // Footnotes can add more space to the bottom if they have newlines. + if (m_pFootnoteLabel->IsEnabled()) + { + m_pFootnoteLabel->SetBounds( xOffset, yOffset, (float)(m_iBarWide * m_flPanelScale), GetTall() ); + + int iNoteWide, iNoteTall; + m_pFootnoteLabel->GetContentSize( iNoteWide, iNoteTall ); + + m_pFootnoteLabel->SetTall( iNoteTall ); + + extraHeight += iNoteTall; + } + + switch (m_iCommentaryType) + { + case COMMENTARY_TYPE_TEXT: + { + m_pLabel->SetBounds( + xOffset + m_iTextBorderSpace, yOffset + m_iTextBorderSpace, + (float)(m_iBarWide * m_flPanelScale) - m_iTextBorderSpace, GetTall() ); + + // Figure out the size before setting bounds + int lW, lT; + m_pLabel->GetContentSize( lW, lT ); + + //lT = (float)lT * m_flPanelScale; // Don't affect height when scaling + + m_pLabel->SetTall( lT ); + + lW += (m_iTextBorderSpace * 2); + lT += (m_iTextBorderSpace * 2); + + contentWidth = lW, contentHeight = lT; + + lW += (xOffset * 2); + lT += (yOffset * 2); + + ResolveBounds( lW + extraWidth, lT + extraHeight ); + } break; + + case COMMENTARY_TYPE_IMAGE: + { + // Figure out the size before setting bounds + int iW, iT; + //m_pImage->GetImage()->GetSize( iW, iT ); + vgui::surface()->DrawGetTextureSize( m_pImage->GetImage()->GetID(), iW, iT ); + if (iW <= 0) + iW = 1; + + int iTargetSize = (m_iBarWide - m_iTextBorderSpace); + iT *= (iTargetSize / iW); + iW = iTargetSize; + + iW = (float)iW * m_flPanelScale; + iT = (float)iT * m_flPanelScale; + + m_pImage->SetBounds( + xOffset + m_iTextBorderSpace, + yOffset + m_iTextBorderSpace, + iW, iT ); + + iW += (m_iTextBorderSpace * 2); + iT += (m_iTextBorderSpace * 2); + + contentWidth = iW, contentHeight = iT; + + iW += (xOffset * 2); + iT += (yOffset * 2); + + ResolveBounds( iW + extraWidth, iT + extraHeight ); + } break; + + default: + case COMMENTARY_TYPE_SCENE: + case COMMENTARY_TYPE_AUDIO: + + // Keep the box centered + SetBounds( m_iTypeAudioX, m_iTypeAudioY - extraHeight, m_iTypeAudioW + extraWidth, m_iTypeAudioT + extraHeight ); + + // Reposition the subtitles to be above the commentary dialog + if (m_bShouldRepositionSubtitles) + { + RepositionAndFollowCloseCaption( extraHeight ); + } + + contentWidth = (m_iBarWide * m_flPanelScale), contentHeight = m_iBarTall; + + break; + } + + // Move the footnote to be at the bottom + if (m_pFootnoteLabel->IsEnabled()) + { + m_pFootnoteLabel->SetPos( m_iSpeakersX + commentary_footnote_offset_x.GetInt(), yOffset+contentHeight+ commentary_footnote_offset_y.GetInt() ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Resolves position on screen; Heavily borrows from CHudMessage::XPosition/YPosition +//----------------------------------------------------------------------------- +void CHudCommentary::ResolveBounds( int width, int height ) +{ + int xPos; + int yPos; + + // ====== X ====== + if ( m_flOverrideX == -1 ) + { + xPos = (ScreenWidth() - width) * 0.5f; + } + else + { + if ( m_flOverrideX < 0 ) + xPos = (1.0 + m_flOverrideX) * ScreenWidth() - width; // Align to right + else + xPos = m_flOverrideX * (ScreenWidth() - width); + } + + // Clamp to edge of screen + if ( xPos + width > ScreenWidth() ) + xPos = ScreenWidth() - width; + else if ( xPos < 0 ) + xPos = 0; + + // ====== Y ====== + if ( m_flOverrideY == -1 ) + { + yPos = (ScreenHeight() - height) * 0.5f; + } + else + { + if ( m_flOverrideY < 0 ) + yPos = (1.0 + m_flOverrideY) * ScreenHeight() - height; // Align to bottom + else + yPos = m_flOverrideY * (ScreenHeight() - height); + } + + // Clamp to edge of screen + if ( yPos + height > ScreenHeight() ) + yPos = ScreenHeight() - height; + else if ( yPos < 0 ) + yPos = 0; + + SetBounds( xPos, yPos, width, height ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CHudCommentary::LevelShutdown( void ) +{ + if (m_iCCDefaultY != 0) + { + CHudCloseCaption *pHudCloseCaption = (CHudCloseCaption *)GET_HUDELEMENT( CHudCloseCaption ); + if (pHudCloseCaption && pHudCloseCaption->IsUsingCommentaryDimensions()) + { + int ccX, ccY; + pHudCloseCaption->GetPos( ccX, ccY ); + + if (m_iCCDefaultY != ccY) + { + DevMsg( "CHudCommentary had to reset misaligned CC element Y (%i) to default Y (%i)\n", ccY, m_iCCDefaultY ); + pHudCloseCaption->SetPos( ccX, m_iCCDefaultY ); + } + + pHudCloseCaption->SetUsingCommentaryDimensions( false ); + } + } +} +#endif + //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CHudCommentary::Init( void ) { m_matIcon.Init( "vgui/hud/icon_commentary", TEXTURE_GROUP_VGUI ); + +#ifdef MAPBASE + SetProportional( true ); +#endif } //----------------------------------------------------------------------------- @@ -511,6 +1351,9 @@ void CHudCommentary::VidInit( void ) { SetAlpha(0); StopCommentary(); +#ifdef MAPBASE + m_iCCDefaultY = 0; +#endif } //----------------------------------------------------------------------------- @@ -525,7 +1368,38 @@ void CHudCommentary::StartCommentary( C_PointCommentaryNode *pNode, char *pszSpe m_flStartTime = flStartTime; m_flEndTime = flEndTime; m_bHiding = false; - g_pVGuiLocalize->ConvertANSIToUnicode( pszSpeakers, m_szSpeakers, sizeof(m_szSpeakers) ); +#ifdef MAPBASE + m_iCommentaryType = COMMENTARY_TYPE_AUDIO; + m_flPanelScale = pNode->m_flPanelScale; + m_flOverrideX = pNode->m_flPanelX; + m_flOverrideY = pNode->m_flPanelY; +#endif + g_pVGuiLocalize->ConvertANSIToUnicode( pszSpeakers, m_szSpeakers, sizeof( m_szSpeakers ) ); + +#ifdef MAPBASE + SetBounds( m_iTypeAudioX, m_iTypeAudioY, m_iTypeAudioW, m_iTypeAudioT ); + SetBgColor( m_bUseScriptBGColor ? m_BGOverrideColor : m_BackgroundColor ); + + m_pLabel->SetPaintEnabled( false ); + m_pImage->SetPaintEnabled( false ); + m_pImage->EvictImage(); + + m_pFootnoteLabel->SetEnabled( false ); + + // Get our scheme and font information + vgui::HScheme scheme = GetScheme(); + m_hFont = vgui::scheme()->GetIScheme(scheme)->GetFont( "CommentaryDefault" ); + if ( !m_hFont ) + { + m_hFont = vgui::scheme()->GetIScheme(scheme)->GetFont( "Default" ); + } + + m_hSmallFont = vgui::scheme()->GetIScheme(scheme)->GetFont( "CommentarySmall" ); + if ( !m_hSmallFont) + { + m_hSmallFont = m_hFont; + } +#endif // Don't draw the element itself if closecaptions are on (and captions are always on in non-english mode) ConVarRef pCVar( "closecaption" ); @@ -537,6 +1411,95 @@ void CHudCommentary::StartCommentary( C_PointCommentaryNode *pNode, char *pszSpe { m_bShouldPaint = true; } + +#ifdef MAPBASE + if (!m_bShouldPaint && commentary_audio_element_below_cc.GetBool()) + { + m_bShouldPaint = true; + m_bShouldRepositionSubtitles = true; + + // Ensure we perform layout later + InvalidateLayout(); + } + else + m_bShouldRepositionSubtitles = false; + + FixupCommentaryLabels( pNode->m_iszPrintName, pNode->m_iszSpeakers, pNode->m_iszFootnote ); +#endif + + SetPaintBackgroundEnabled( m_bShouldPaint ); + + char sz[MAX_COUNT_STRING]; + Q_snprintf( sz, sizeof(sz), "%d \\ %d", iNode, iNodeMax ); + g_pVGuiLocalize->ConvertANSIToUnicode( sz, m_szCount, sizeof(m_szCount) ); + + // If the commentary just started, play the commentary fade in. + if ( fabs(flStartTime - gpGlobals->curtime) < 1.0 ) + { + g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( "ShowCommentary" ); + } + else + { + // We're reloading a savegame that has an active commentary going in it. Don't fade in. + SetAlpha( 255 ); + } +} + +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CHudCommentary::StartTextCommentary( C_PointCommentaryNode *pNode, const char *pszText, char *pszSpeakers, int iNode, int iNodeMax, float flStartTime, float flEndTime ) +{ + if ( (flEndTime - flStartTime) <= 0 ) + return; + + m_hActiveNode = pNode; + m_flStartTime = flStartTime; + m_flEndTime = flEndTime; + m_bHiding = false; + m_iCommentaryType = COMMENTARY_TYPE_TEXT; + m_flPanelScale = pNode->m_flPanelScale; + m_flOverrideX = pNode->m_flPanelX; + m_flOverrideY = pNode->m_flPanelY; + g_pVGuiLocalize->ConvertANSIToUnicode( pszSpeakers, m_szSpeakers, sizeof( m_szSpeakers ) ); + + SetBounds( m_iTypeTextX, m_iTypeTextY, m_iTypeTextW, m_iTypeTextT ); + SetBgColor( m_bUseScriptBGColor ? m_BGOverrideColor : m_TextBackgroundColor ); + + // Get our scheme and font information + vgui::HScheme scheme = GetScheme(); + m_hFont = vgui::scheme()->GetIScheme(scheme)->GetFont( "CommentaryDefault" ); + if ( !m_hFont ) + { + m_hFont = vgui::scheme()->GetIScheme(scheme)->GetFont( "Default" ); + } + + m_hSmallFont = vgui::scheme()->GetIScheme(scheme)->GetFont( "CommentarySmall" ); + if ( !m_hSmallFont) + { + m_hSmallFont = m_hFont; + } + + m_pLabel->SetText( pszText ); + m_pLabel->SetFont( m_hFont ); + m_pLabel->SetWrap( true ); + m_pLabel->SetPaintEnabled( true ); + m_pLabel->SetPaintBackgroundEnabled( false ); + m_pLabel->SetPaintBorderEnabled( false ); + //m_pLabel->SizeToContents(); + m_pLabel->SetContentAlignment( vgui::Label::a_northwest ); + m_pLabel->SetFgColor( m_TypeTextContentColor ); + + m_pImage->SetPaintEnabled( false ); + m_pImage->EvictImage(); + + m_pFootnoteLabel->SetEnabled( false ); + + m_bShouldPaint = true; + + FixupCommentaryLabels( pNode->m_iszPrintName, pNode->m_iszSpeakers, pNode->m_iszFootnote ); + SetPaintBackgroundEnabled( m_bShouldPaint ); char sz[MAX_COUNT_STRING]; @@ -555,14 +1518,300 @@ void CHudCommentary::StartCommentary( C_PointCommentaryNode *pNode, char *pszSpe } } +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CHudCommentary::StartImageCommentary( C_PointCommentaryNode *pNode, const char *pszImage, char *pszSpeakers, int iNode, int iNodeMax, float flStartTime, float flEndTime ) +{ + if ( (flEndTime - flStartTime) <= 0 ) + return; + + m_hActiveNode = pNode; + m_flStartTime = flStartTime; + m_flEndTime = flEndTime; + m_bHiding = false; + m_iCommentaryType = COMMENTARY_TYPE_IMAGE; + m_flPanelScale = pNode->m_flPanelScale; + m_flOverrideX = pNode->m_flPanelX; + m_flOverrideY = pNode->m_flPanelY; + g_pVGuiLocalize->ConvertANSIToUnicode( pszSpeakers, m_szSpeakers, sizeof( m_szSpeakers ) ); + + SetBounds( m_iTypeTextX, m_iTypeTextY, m_iTypeTextW, m_iTypeTextT ); + SetBgColor( m_bUseScriptBGColor ? m_BGOverrideColor : m_TextBackgroundColor ); + + m_pLabel->SetPaintEnabled( false ); + + m_pImage->SetPaintEnabled( true ); + m_pImage->SetImage( pszImage ); + m_pImage->SetWide( m_iBarWide - m_iTextBorderSpace ); + + m_pFootnoteLabel->SetEnabled( false ); + + // Get our scheme and font information + vgui::HScheme scheme = GetScheme(); + m_hFont = vgui::scheme()->GetIScheme(scheme)->GetFont( "CommentaryDefault" ); + if ( !m_hFont ) + { + m_hFont = vgui::scheme()->GetIScheme(scheme)->GetFont( "Default" ); + } + + m_hSmallFont = vgui::scheme()->GetIScheme(scheme)->GetFont( "CommentarySmall" ); + if ( !m_hSmallFont) + { + m_hSmallFont = m_hFont; + } + + m_bShouldPaint = true; + + FixupCommentaryLabels( pNode->m_iszPrintName, pNode->m_iszSpeakers, pNode->m_iszFootnote ); + + SetPaintBackgroundEnabled( m_bShouldPaint ); + + char sz[MAX_COUNT_STRING]; + Q_snprintf( sz, sizeof(sz), "%d \\ %d", iNode, iNodeMax ); + g_pVGuiLocalize->ConvertANSIToUnicode( sz, m_szCount, sizeof(m_szCount) ); + + // If the commentary just started, play the commentary fade in. + if ( fabs(flStartTime - gpGlobals->curtime) < 1.0 ) + { + g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( "ShowCommentary" ); + } + else + { + // We're reloading a savegame that has an active commentary going in it. Don't fade in. + SetAlpha( 255 ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CHudCommentary::StartSceneCommentary( C_PointCommentaryNode *pNode, char *pszSpeakers, int iNode, int iNodeMax, float flStartTime, float flEndTime ) +{ + if ( (flEndTime - flStartTime) <= 0 ) + return; + + m_hActiveNode = pNode; + m_flStartTime = flStartTime; + m_flEndTime = flEndTime; + m_bHiding = false; + m_iCommentaryType = COMMENTARY_TYPE_SCENE; + m_flPanelScale = pNode->m_flPanelScale; + m_flOverrideX = pNode->m_flPanelX; + m_flOverrideY = pNode->m_flPanelY; + g_pVGuiLocalize->ConvertANSIToUnicode( pszSpeakers, m_szSpeakers, sizeof( m_szSpeakers ) ); + + SetBounds( m_iTypeAudioX, m_iTypeAudioY, m_iTypeAudioW, m_iTypeAudioT ); + SetBgColor( m_bUseScriptBGColor ? m_BGOverrideColor : m_BackgroundColor ); + + m_pLabel->SetPaintEnabled( false ); + m_pImage->SetPaintEnabled( false ); + m_pImage->EvictImage(); + + m_pFootnoteLabel->SetEnabled( false ); + + // Get our scheme and font information + vgui::HScheme scheme = GetScheme(); + m_hFont = vgui::scheme()->GetIScheme(scheme)->GetFont( "CommentaryDefault" ); + if ( !m_hFont ) + { + m_hFont = vgui::scheme()->GetIScheme(scheme)->GetFont( "Default" ); + } + + m_hSmallFont = vgui::scheme()->GetIScheme(scheme)->GetFont( "CommentarySmall" ); + if ( !m_hSmallFont) + { + m_hSmallFont = m_hFont; + } + + // Don't draw the element itself if closecaptions are on (and captions are always on in non-english mode) + ConVarRef pCVar( "closecaption" ); + if ( pCVar.IsValid() ) + { + m_bShouldPaint = ( !pCVar.GetBool() && english.GetBool() ); + } + else + { + m_bShouldPaint = true; + } + + if (!m_bShouldPaint && commentary_audio_element_below_cc.GetBool()) + { + m_bShouldPaint = true; + m_bShouldRepositionSubtitles = true; + + // Ensure we perform layout later + InvalidateLayout(); + } + else + m_bShouldRepositionSubtitles = false; + + FixupCommentaryLabels( pNode->m_iszPrintName, pNode->m_iszSpeakers, pNode->m_iszFootnote ); + + SetPaintBackgroundEnabled( m_bShouldPaint ); + + char sz[MAX_COUNT_STRING]; + Q_snprintf( sz, sizeof(sz), "%d \\ %d", iNode, iNodeMax ); + g_pVGuiLocalize->ConvertANSIToUnicode( sz, m_szCount, sizeof(m_szCount) ); + + // If the commentary just started, play the commentary fade in. + if ( fabs(flStartTime - gpGlobals->curtime) < 1.0 ) + { + g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( "ShowCommentary" ); + } + else + { + // We're reloading a savegame that has an active commentary going in it. Don't fade in. + SetAlpha( 255 ); + } +} +#endif + //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CHudCommentary::StopCommentary( void ) { m_hActiveNode = NULL; + +#ifdef MAPBASE + // Reset close caption element if needed + CHudCloseCaption *pHudCloseCaption = (CHudCloseCaption *)GET_HUDELEMENT( CHudCloseCaption ); + if (pHudCloseCaption && pHudCloseCaption->IsUsingCommentaryDimensions()) + { + // Run this animation command instead of setting the position directly + g_pClientMode->GetViewportAnimationController()->RunAnimationCommand( pHudCloseCaption, "YPos", m_iCCDefaultY, 0.0f, 0.4f, vgui::AnimationController::INTERPOLATOR_ACCEL ); + + pHudCloseCaption->SetUsingCommentaryDimensions( false ); + } +#endif } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CHudCommentary::FixupCommentaryLabels( const char *pszPrintName, const char *pszSpeakers, const char *pszFootnote ) +{ + if (commentary_combine_speaker_and_printname.GetBool() && pszPrintName[0] != '\0') + { + wchar_t *pszLocal = g_pVGuiLocalize->Find( pszPrintName ); + if (m_szSpeakers[0] == '\0' || !m_bShouldPaint) // Use m_bShouldPaint as an indicator of whether or not we use subtitles + { + if (pszPrintName[0] == '#' && pszLocal) + wcsncpy( m_szSpeakers, pszLocal, sizeof( m_szSpeakers ) / sizeof( wchar_t ) ); + else + g_pVGuiLocalize->ConvertANSIToUnicode( pszPrintName, m_szSpeakers, sizeof( m_szSpeakers ) ); + } + else + { + static wchar_t iszSpeakersLocalized[MAX_SPEAKER_NAME] = { 0 }; + static wchar_t iszPrintNameLocalized[MAX_SPEAKER_NAME] = { 0 }; + + wcsncpy( iszSpeakersLocalized, m_szSpeakers, sizeof( iszSpeakersLocalized ) / sizeof( wchar_t ) ); + + if (m_szSpeakers[0] == '#') + { + wchar_t *pwszSpeakers = g_pVGuiLocalize->Find( pszSpeakers ); + if (pwszSpeakers) + wcsncpy( iszSpeakersLocalized, pwszSpeakers, sizeof( iszSpeakersLocalized ) / sizeof( wchar_t ) ); + } + + if (pszPrintName[0] == '#' && pszLocal) + wcsncpy( iszPrintNameLocalized, pszLocal, sizeof( iszPrintNameLocalized ) / sizeof( wchar_t ) ); + else + g_pVGuiLocalize->ConvertANSIToUnicode( pszPrintName, iszPrintNameLocalized, sizeof( iszPrintNameLocalized ) ); + + V_snwprintf( m_szSpeakers, sizeof( m_szSpeakers ), L"%ls ~ %ls", iszSpeakersLocalized, iszPrintNameLocalized ); + } + } + + if (pszFootnote[0] != '\0' && m_bShouldPaint) + { + m_pFootnoteLabel->SetText( pszFootnote ); + m_pFootnoteLabel->SetFont( m_hSmallFont ); + m_pFootnoteLabel->SetWrap( true ); + m_pFootnoteLabel->SetEnabled( true ); + m_pFootnoteLabel->SetPaintEnabled( true ); + m_pFootnoteLabel->SetPaintBackgroundEnabled( false ); + m_pFootnoteLabel->SetPaintBorderEnabled( false ); + //m_pFootnoteLabel->SizeToContents(); + m_pFootnoteLabel->SetContentAlignment( vgui::Label::a_northwest ); + m_pFootnoteLabel->SetFgColor( m_ForegroundColor ); + } + else + { + m_pFootnoteLabel->SetPaintEnabled( false ); + m_pFootnoteLabel->SetEnabled( false ); + } + + // Reset close caption element if it's still using commentary dimensions + // (fixes problems with switching from node to node) + CHudCloseCaption *pHudCloseCaption = (CHudCloseCaption *)GET_HUDELEMENT( CHudCloseCaption ); + if (pHudCloseCaption && pHudCloseCaption->IsUsingCommentaryDimensions()) + { + // Run this animation command instead of setting the position directly + g_pClientMode->GetViewportAnimationController()->RunAnimationCommand( pHudCloseCaption, "YPos", m_iCCDefaultY, 0.0f, 0.4f, vgui::AnimationController::INTERPOLATOR_ACCEL ); + + pHudCloseCaption->SetUsingCommentaryDimensions( false ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CHudCommentary::RepositionAndFollowCloseCaption( int yOffset ) +{ + // Invert the Y axis + //SetPos( m_iTypeAudioX, ScreenHeight() - m_iTypeAudioY ); + + // Place underneath the close caption element + CHudCloseCaption *pHudCloseCaption = (CHudCloseCaption *)GET_HUDELEMENT( CHudCloseCaption ); + if (pHudCloseCaption /*&& !pHudCloseCaption->IsUsingCommentaryDimensions()*/) + { + int ccX, ccY; + pHudCloseCaption->GetPos( ccX, ccY ); + + // Save the default position in case we need to do a hard reset + // (this usually happens when players begin commentary before the CC element's return animation command is finished) + if (m_iCCDefaultY == 0) + { + m_iCCDefaultY = ccY; + } + + if (!pHudCloseCaption->IsUsingCommentaryDimensions()) + { + if (m_iCCDefaultY != ccY /*&& !pHudCloseCaption->IsUsingCommentaryDimensions()*/) + { + DevMsg( "CHudCommentary had to reset misaligned CC element Y (%i) to default Y (%i)\n", ccY, m_iCCDefaultY ); + ccY = m_iCCDefaultY; + } + + ccY -= m_iTypeAudioT; + + // Run this animation command instead of setting the position directly + g_pClientMode->GetViewportAnimationController()->RunAnimationCommand( pHudCloseCaption, "YPos", ccY - yOffset, 0.0f, 0.2f, vgui::AnimationController::INTERPOLATOR_DEACCEL ); + //pHudCloseCaption->SetPos( ccX, ccY ); + m_flCCAnimTime = gpGlobals->curtime + 0.2f; + + pHudCloseCaption->SetUsingCommentaryDimensions( true ); + } + else if (gpGlobals->curtime > m_flCCAnimTime && ccY != m_iCCDefaultY - m_iTypeAudioT - yOffset) + { + DevMsg( "CHudCommentary had to correct misaligned CC element offset (%i != %i)\n", m_iCCDefaultY - ccY, yOffset ); + + g_pClientMode->GetViewportAnimationController()->RunAnimationCommand( pHudCloseCaption, "YPos", m_iCCDefaultY - m_iTypeAudioT - yOffset, 0.0f, 0.2f, vgui::AnimationController::INTERPOLATOR_DEACCEL ); + m_flCCAnimTime = gpGlobals->curtime + 0.2f; + } + + SetPos( ccX, ccY + pHudCloseCaption->GetTall() + commentary_audio_element_below_cc_margin.GetInt() ); + + m_flPanelScale = (float)pHudCloseCaption->GetWide() / (float)GetWide(); + SetWide( pHudCloseCaption->GetWide() ); + } +} +#endif + //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- diff --git a/src/game/client/c_postprocesscontroller.cpp b/src/game/client/c_postprocesscontroller.cpp new file mode 100644 index 00000000..91a2d1df --- /dev/null +++ b/src/game/client/c_postprocesscontroller.cpp @@ -0,0 +1,63 @@ +//====== Copyright © 1996-2008, Valve Corporation, All rights reserved. ======= +// +// Purpose: stores map postprocess params +// +//============================================================================= +#include "cbase.h" +#include "c_postprocesscontroller.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +IMPLEMENT_CLIENTCLASS_DT( C_PostProcessController, DT_PostProcessController, CPostProcessController ) + RecvPropArray3( RECVINFO_NAME( m_PostProcessParameters.m_flParameters[0], m_flPostProcessParameters ), POST_PROCESS_PARAMETER_COUNT, RecvPropFloat( RECVINFO_NAME( m_PostProcessParameters.m_flParameters[0], m_flPostProcessParameters[0] ) ) ), + RecvPropBool( RECVINFO(m_bMaster) ) +END_RECV_TABLE() + +C_PostProcessController* C_PostProcessController::ms_pMasterController = nullptr; + +//----------------------------------------------------------------------------- +C_PostProcessController::C_PostProcessController() +: m_bMaster( false ) +{ + if ( ms_pMasterController == nullptr) + { + ms_pMasterController = this; + } +} + +//----------------------------------------------------------------------------- +C_PostProcessController::~C_PostProcessController() +{ + if ( ms_pMasterController == this ) + { + ms_pMasterController = nullptr; + } +} + +void C_PostProcessController::PostDataUpdate( DataUpdateType_t updateType ) +{ + BaseClass::PostDataUpdate( updateType ); + + if ( m_bMaster ) + { + ms_pMasterController = this; + } +} + +#ifdef MAPBASE +// Prevents parameters from fading after a save/restore +bool g_bPostProcessNeedsRestore = false; + +void C_PostProcessController::OnRestore() +{ + BaseClass::OnRestore(); + + C_BasePlayer *pPlayer = C_BasePlayer::GetLocalPlayer(); + if ( pPlayer && pPlayer->GetActivePostProcessController() == this ) + { + // Tell clientmode this is part of a save/restore + g_bPostProcessNeedsRestore = true; + } +} +#endif diff --git a/src/game/client/c_postprocesscontroller.h b/src/game/client/c_postprocesscontroller.h new file mode 100644 index 00000000..c07673f7 --- /dev/null +++ b/src/game/client/c_postprocesscontroller.h @@ -0,0 +1,33 @@ +#pragma once + +#include "postprocess_shared.h" + +//============================================================================= +// +// Class Postprocess Controller: +// +class C_PostProcessController : public C_BaseEntity +{ + DECLARE_CLASS( C_PostProcessController, C_BaseEntity ); +public: + DECLARE_CLIENTCLASS(); + + C_PostProcessController(); + virtual ~C_PostProcessController(); + + virtual void PostDataUpdate( DataUpdateType_t updateType ); + + static C_PostProcessController* GetMasterController() { return ms_pMasterController; } + + PostProcessParameters_t m_PostProcessParameters; + +#ifdef MAPBASE + // Prevents fade time from being used in save/restore + virtual void OnRestore(); +#endif + +private: + bool m_bMaster; + + static C_PostProcessController* ms_pMasterController; +}; diff --git a/src/game/client/c_props.cpp b/src/game/client/c_props.cpp index 07f5710d..7981fe2c 100644 --- a/src/game/client/c_props.cpp +++ b/src/game/client/c_props.cpp @@ -24,6 +24,11 @@ BEGIN_NETWORK_TABLE( CDynamicProp, DT_DynamicProp ) RecvPropBool(RECVINFO(m_bUseHitboxesForRenderBox)), END_NETWORK_TABLE() +#ifdef MAPBASE_VSCRIPT +// Allows client-side VScript to create dynamic props via CreateProp() +LINK_ENTITY_TO_CLASS( prop_dynamic, C_DynamicProp ); +#endif + C_DynamicProp::C_DynamicProp( void ) { m_iCachedFrameCount = -1; diff --git a/src/game/client/c_rope.cpp b/src/game/client/c_rope.cpp index cbc724e7..e63c08dd 100644 --- a/src/game/client/c_rope.cpp +++ b/src/game/client/c_rope.cpp @@ -29,6 +29,14 @@ // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" +#ifdef MAPBASE +static void PrecacheCable( void* ) +{ + PrecacheMaterial( "cable/rope_shadowdepth" ); +} +PRECACHE_REGISTER_FN( PrecacheCable ); +#endif + void RecvProxy_RecomputeSprings( const CRecvProxyData *pData, void *pStruct, void *pOut ) { // Have the regular proxy store the data. @@ -40,6 +48,9 @@ void RecvProxy_RecomputeSprings( const CRecvProxyData *pData, void *pStruct, voi IMPLEMENT_CLIENTCLASS_DT_NOBASE( C_RopeKeyframe, DT_RopeKeyframe, CRopeKeyframe ) +#ifdef MAPBASE + RecvPropInt( RECVINFO( m_nChangeCount ) ), +#endif RecvPropInt( RECVINFO(m_iRopeMaterialModelIndex) ), RecvPropEHandle( RECVINFO(m_hStartPoint) ), RecvPropEHandle( RECVINFO(m_hEndPoint) ), @@ -63,6 +74,27 @@ IMPLEMENT_CLIENTCLASS_DT_NOBASE( C_RopeKeyframe, DT_RopeKeyframe, CRopeKeyframe RecvPropInt( RECVINFO( m_iParentAttachment ) ), END_RECV_TABLE() +#ifdef MAPBASE_VSCRIPT +BEGIN_ENT_SCRIPTDESC( C_RopeKeyframe, C_BaseEntity, "The clientside class of move_rope and keyframe_rope" ) + DEFINE_SCRIPTFUNC( GetNodePosition, "Gets the position of the specified node index" ) + DEFINE_SCRIPTFUNC( GetNumNodes, "Gets the number of nodes available" ) + + DEFINE_SCRIPTFUNC_NAMED( ScriptGetStartEntity, "GetStartEntity", "Gets the rope's start entity" ) + DEFINE_SCRIPTFUNC_NAMED( ScriptGetEndEntity, "GetEndEntity", "Gets the rope's end entity" ) + + DEFINE_SCRIPTFUNC( SetupHangDistance, "Sets the rope's hang distance" ) + DEFINE_SCRIPTFUNC( SetSlack, "Sets the rope's slack value (extra length)" ) + DEFINE_SCRIPTFUNC( GetRopeFlags, "Gets the rope's flags" ) + DEFINE_SCRIPTFUNC( SetRopeFlags, "Sets the rope's flags" ) + + DEFINE_SCRIPTFUNC( SetColorMod, "Sets the rope's color mod value" ) + + DEFINE_SCRIPTFUNC( ShakeRope, "Shakes the rope with the specified center, radius, and magnitude" ) + + DEFINE_SCRIPTFUNC( AnyPointsMoved, "Returns true if any points have moved recently" ) +END_SCRIPTDESC(); +#endif + #define ROPE_IMPULSE_SCALE 20 #define ROPE_IMPULSE_DECAY 0.95 @@ -81,7 +113,9 @@ static ConVar rope_smooth_maxalpha( "rope_smooth_maxalpha", "0.5", 0, "Alpha for static ConVar mat_fullbright( "mat_fullbright", "0", FCVAR_CHEAT ); // get it from the engine static ConVar r_drawropes( "r_drawropes", "1", FCVAR_CHEAT ); +#ifndef MAPBASE static ConVar r_queued_ropes( "r_queued_ropes", "1" ); +#endif static ConVar r_ropetranslucent( "r_ropetranslucent", "1"); static ConVar r_rope_holiday_light_scale( "r_rope_holiday_light_scale", "0.055", FCVAR_DEVELOPMENTONLY ); static ConVar r_ropes_holiday_lights_allowed( "r_ropes_holiday_lights_allowed", "1", FCVAR_DEVELOPMENTONLY ); @@ -99,11 +133,17 @@ static ConVar rope_solid_minalpha( "rope_solid_minalpha", "0.0" ); static ConVar rope_solid_maxalpha( "rope_solid_maxalpha", "1" ); +#ifndef MAPBASE static CCycleCount g_RopeCollideTicks; static CCycleCount g_RopeDrawTicks; static CCycleCount g_RopeSimulateTicks; +#endif static int g_nRopePointsSimulated; +#ifdef MAPBASE +static IMaterial *g_pSplineCableShadowdepth = NULL; +#endif + // Active ropes. CUtlLinkedList g_Ropes; @@ -119,6 +159,7 @@ public: } } g_FullBrightLightValuesInit; +#ifndef MAPBASE // Precalculated info for rope subdivision. static Vector g_RopeSubdivs[MAX_ROPE_SUBDIVS][MAX_ROPE_SUBDIVS]; class CSubdivInit @@ -142,6 +183,7 @@ static int g_nBarbedSubdivs = 3; static Vector g_BarbedSubdivs[MAX_ROPE_SUBDIVS] = { Vector(1.5, 1.5*1.5, 1.5*1.5*1.5), Vector(-0.5, -0.5 * -0.5, -0.5*-0.5*-0.5), Vector(0.5, 0.5*0.5, 0.5*0.5*0.5) }; +#endif // This can be exposed through the entity if we ever care. static float g_flLockAmount = 0.1; @@ -149,7 +191,7 @@ static float g_flLockFalloff = 0.3; - +#ifndef MAPBASE class CQueuedRopeMemoryManager { public: @@ -219,6 +261,7 @@ struct RopeSegData_t // If this is less than rope_solid_minwidth and rope_solid_minalpha is 0, then we can avoid drawing.. float m_flMaxBackWidth; }; +#endif class CRopeManager : public IRopeManager { @@ -230,15 +273,18 @@ public: void ResetRenderCache( void ); void AddToRenderCache( C_RopeKeyframe *pRope ); void DrawRenderCache( bool bShadowDepth ); +#ifndef MAPBASE void OnRenderStart( void ) { m_QueuedModeMemory.SwitchStack(); } +#endif void SetHolidayLightMode( bool bHoliday ) { m_bDrawHolidayLights = bHoliday; } bool IsHolidayLightMode( void ); int GetHolidayLightStyle( void ); +#ifndef MAPBASE private: struct RopeRenderData_t; public: @@ -246,26 +292,32 @@ public: void ResetSegmentCache( int nMaxSegments ); RopeSegData_t *GetNextSegmentFromCache( void ); +#endif enum { MAX_ROPE_RENDERCACHE = 128 }; void RemoveRopeFromQueuedRenderCaches( C_RopeKeyframe *pRope ); +#ifndef MAPBASE private: void RenderNonSolidRopes( IMatRenderContext *pRenderContext, IMaterial *pMaterial, int nVertCount, int nIndexCount ); void RenderSolidRopes( IMatRenderContext *pRenderContext, IMaterial *pMaterial, int nVertCount, int nIndexCount, bool bRenderNonSolid ); +#endif private: struct RopeRenderData_t { IMaterial *m_pSolidMaterial; +#ifndef MAPBASE IMaterial *m_pBackMaterial; +#endif int m_nCacheCount; C_RopeKeyframe *m_aCache[MAX_ROPE_RENDERCACHE]; }; +#ifndef MAPBASE CUtlVector m_aRenderCache; int m_nSegmentCacheCount; CUtlVector m_aSegmentCache; @@ -275,20 +327,37 @@ private: CQueuedRopeMemoryManager m_QueuedModeMemory; IMaterial* m_pDepthWriteMaterial; +#endif struct RopeQueuedRenderCache_t { RopeRenderData_t *pCaches; int iCacheCount; +#ifdef MAPBASE + CThreadFastMutex *m_pRopeDataMutex; +#endif RopeQueuedRenderCache_t( void ) : pCaches(NULL), iCacheCount(0) { }; }; +#ifdef MAPBASE + void DrawRenderCache_NonQueued( bool bShadowDepth, RopeRenderData_t *pRenderCache, int nRenderCacheCount, const Vector &vCurrentViewForward, const Vector &vCurrentViewOrigin, C_RopeKeyframe::BuildRopeQueuedData_t *pBuildRopeQueuedData, CThreadFastMutex *pRopeDataMutex ); +#else CUtlLinkedList m_RopeQueuedRenderCaches; +#endif bool m_bDrawHolidayLights; bool m_bHolidayInitialized; int m_nHolidayLightsStyle; + +#ifdef MAPBASE + CUtlVector m_aRenderCache; + + //in queued material system mode we need to store off data for later use. + IMaterial* m_pDepthWriteMaterial; + CUtlLinkedList m_RopeQueuedRenderCaches; + CThreadFastMutex m_RopeQueuedRenderCaches_Mutex; //mutex just for changing m_RopeQueuedRenderCaches +#endif }; static CRopeManager s_RopeManager; @@ -298,11 +367,12 @@ IRopeManager *RopeManager() return &s_RopeManager; } - +#ifndef MAPBASE inline bool ShouldUseFakeAA( IMaterial *pBackMaterial ) { return pBackMaterial && rope_smooth.GetInt() && engine->GetDXSupportLevel() > 70 && !g_pMaterialSystemHardwareConfig->IsAAEnabled(); } +#endif //----------------------------------------------------------------------------- @@ -311,8 +381,10 @@ inline bool ShouldUseFakeAA( IMaterial *pBackMaterial ) CRopeManager::CRopeManager() { m_aRenderCache.Purge(); +#ifndef MAPBASE m_aSegmentCache.Purge(); m_nSegmentCacheCount = 0; +#endif m_pDepthWriteMaterial = NULL; m_bDrawHolidayLights = false; m_bHolidayInitialized = false; @@ -324,6 +396,9 @@ CRopeManager::CRopeManager() //----------------------------------------------------------------------------- CRopeManager::~CRopeManager() { +#ifdef MAPBASE + m_aRenderCache.Purge(); +#else int nRenderCacheCount = m_aRenderCache.Count(); for ( int iRenderCache = 0; iRenderCache < nRenderCacheCount; ++iRenderCache ) { @@ -339,6 +414,7 @@ CRopeManager::~CRopeManager() m_aRenderCache.Purge(); m_aSegmentCache.Purge(); +#endif } //----------------------------------------------------------------------------- @@ -368,8 +444,12 @@ void CRopeManager::AddToRenderCache( C_RopeKeyframe *pRope ) int nRenderCacheCount = m_aRenderCache.Count(); for ( ; iRenderCache < nRenderCacheCount; ++iRenderCache ) { +#ifdef MAPBASE + if ( pRope->GetSolidMaterial() == m_aRenderCache[iRenderCache].m_pSolidMaterial ) +#else if ( ( pRope->GetSolidMaterial() == m_aRenderCache[iRenderCache].m_pSolidMaterial ) && ( pRope->GetBackMaterial() == m_aRenderCache[iRenderCache].m_pBackMaterial ) ) +#endif break; } @@ -379,6 +459,7 @@ void CRopeManager::AddToRenderCache( C_RopeKeyframe *pRope ) { iRenderCache = m_aRenderCache.AddToTail(); m_aRenderCache[iRenderCache].m_pSolidMaterial = pRope->GetSolidMaterial(); +#ifndef MAPBASE if ( m_aRenderCache[iRenderCache].m_pSolidMaterial ) { m_aRenderCache[iRenderCache].m_pSolidMaterial->IncrementReferenceCount(); @@ -388,6 +469,7 @@ void CRopeManager::AddToRenderCache( C_RopeKeyframe *pRope ) { m_aRenderCache[iRenderCache].m_pBackMaterial->IncrementReferenceCount(); } +#endif m_aRenderCache[iRenderCache].m_nCacheCount = 0; } @@ -401,6 +483,257 @@ void CRopeManager::AddToRenderCache( C_RopeKeyframe *pRope ) ++m_aRenderCache[iRenderCache].m_nCacheCount; } +#ifdef MAPBASE +#define OUTPUT_2SPLINE_VERTS( t, u ) \ + meshBuilder.Color4ub( nRed, nGreen, nBlue, nAlpha ); \ + meshBuilder.Position3f( (t), u, 0 ); \ + meshBuilder.TexCoord4fv( 0, vecP0.Base() ); \ + meshBuilder.TexCoord4fv( 1, vecP1.Base() ); \ + meshBuilder.TexCoord4fv( 2, vecP2.Base() ); \ + meshBuilder.TexCoord4fv( 3, vecP3.Base() ); \ + meshBuilder.AdvanceVertexF(); \ + meshBuilder.Color4ub( nRed, nGreen, nBlue, nAlpha ); \ + meshBuilder.Position3f( (t), u, 1 ); \ + meshBuilder.TexCoord4fv( 0, vecP0.Base() ); \ + meshBuilder.TexCoord4fv( 1, vecP1.Base() ); \ + meshBuilder.TexCoord4fv( 2, vecP2.Base() ); \ + meshBuilder.TexCoord4fv( 3, vecP3.Base() ); \ + meshBuilder.AdvanceVertexF(); + + +void CRopeManager::DrawRenderCache_NonQueued( bool bShadowDepth, RopeRenderData_t *pRenderCache, int nRenderCacheCount, const Vector &vCurrentViewForward, const Vector &vCurrentViewOrigin, C_RopeKeyframe::BuildRopeQueuedData_t *pBuildRopeQueuedData, CThreadFastMutex *pRopeDataMutex ) +{ + VPROF_BUDGET( "CRopeManager::DrawRenderCache", VPROF_BUDGETGROUP_ROPES ); + + CThreadFastMutex dummyMutex; + if( pRopeDataMutex == NULL ) + pRopeDataMutex = &dummyMutex; + + if ( bShadowDepth && !m_pDepthWriteMaterial && g_pMaterialSystem ) + { + KeyValues *pVMTKeyValues = new KeyValues( "SDK_DepthWrite" ); + pVMTKeyValues->SetInt( "$no_fullbright", 1 ); + pVMTKeyValues->SetInt( "$alphatest", 0 ); + pVMTKeyValues->SetInt( "$nocull", 1 ); + m_pDepthWriteMaterial = g_pMaterialSystem->FindProceduralMaterial( "__DepthWrite01", TEXTURE_GROUP_OTHER, pVMTKeyValues ); + } + CMatRenderContextPtr pRenderContext( materials ); + + // UNDONE: needs to use the queued data + { + AUTO_LOCK( *pRopeDataMutex ); + int defaultSubdiv = rope_subdiv.GetInt(); + for ( int iRenderCache = 0; iRenderCache < nRenderCacheCount; ++iRenderCache ) + { + int nCacheCount = pRenderCache[iRenderCache].m_nCacheCount; + + int nTotalVerts = 0; + int nTotalIndices = 0; + for ( int iCache = 0; iCache < nCacheCount; ++iCache ) + { + C_RopeKeyframe *pRope = pRenderCache[iRenderCache].m_aCache[iCache]; + if ( pRope ) + { + int segs = pRope->m_RopePhysics.NumNodes()-1; + int nSubdivCount = (pRope->m_Subdiv != 255 ? pRope->m_Subdiv : defaultSubdiv) + 1; + nTotalVerts += ((2 * nSubdivCount) * segs) + 2; + nTotalIndices += (6 * nSubdivCount) * segs; + } + } + if ( nTotalVerts == 0 ) + continue; + + IMaterial *pMaterial = bShadowDepth ? g_pSplineCableShadowdepth : pRenderCache[iRenderCache].m_pSolidMaterial; + + // Need to make sure that all rope materials use the splinerope shader since there are a lot of assumptions about how the shader interfaces with this code. + AssertOnce( V_strstr( pMaterial->GetShaderName(), "SDK_Cable" ) != NULL ); // splinerope + + pRenderContext->Bind( pMaterial ); + + int nMaxVertices = pRenderContext->GetMaxVerticesToRender( pMaterial ); + int nMaxIndices = pRenderContext->GetMaxIndicesToRender(); + + IMesh* pMesh = pRenderContext->GetDynamicMesh( true ); + CMeshBuilder meshBuilder; + int meshVertCount = MIN(nTotalVerts, nMaxVertices); + int meshIndexCount = MIN(nTotalIndices, nMaxIndices); + meshBuilder.Begin( pMesh, MATERIAL_TRIANGLES, meshVertCount, meshIndexCount ); + int nCurIDX = 0; + + int availableVerts = meshVertCount; + int availableIndices = meshIndexCount; + float flLastU = 1.0f; + + for ( int iCache = 0; iCache < nCacheCount; ++iCache ) + { + C_RopeKeyframe *pRope = pRenderCache[iRenderCache].m_aCache[iCache]; + if ( pRope ) + { + CSimplePhysics::CNode *pNode = pRope->m_RopePhysics.GetFirstNode(); + int nSegmentsToRender = pRope->m_RopePhysics.NumNodes()-1; + if ( !nSegmentsToRender ) + continue; + + int nParticles = pRope->m_RopePhysics.NumNodes(); + int nSubdivCount = (pRope->m_Subdiv != 255 ? pRope->m_Subdiv : defaultSubdiv) + 1; + + int nNumIndicesPerSegment = 6 * nSubdivCount; + int nNumVerticesPerSegment = 2 * nSubdivCount; + + int nSegmentsAvailableInBuffer = MIN( ( availableVerts - 2 ) / nNumVerticesPerSegment, + ( availableIndices ) / nNumIndicesPerSegment ); + + int segmentsInBuffer = MIN(nSegmentsAvailableInBuffer,nSegmentsToRender); + availableIndices -= nNumIndicesPerSegment * segmentsInBuffer; + availableVerts -= 2 + (nNumVerticesPerSegment * segmentsInBuffer); + + float width = pRope->m_Width; + Vector vModColor = pRope->m_vColorMod; + Vector *pColors = pRope->m_LightValues; + + // Figure out texture scale. + float flPixelsPerInch = 4.0f / pRope->m_TextureScale; + // This is the total number of texels for the length of the whole rope. + float flTotalTexCoord = flPixelsPerInch * ( pRope->m_RopeLength + pRope->m_Slack + ROPESLACK_FUDGEFACTOR ); + int nTotalPoints = (nSegmentsToRender * (nSubdivCount-1)) + 1; + float flDU = ( flTotalTexCoord / nTotalPoints ) / ( float )pRope->m_TextureHeight; + float flU = pRope->m_flCurScroll; + float m_flTStep = 1.0f / float(nSubdivCount); + + bool bFirstPoint = true; + + // initialize first spline segment + Vector4D vecP1; + Vector4D vecP2; + vecP1.Init( pNode[0].m_vPredicted, pRope->m_Width ); + vecP2.Init( pNode[1].m_vPredicted, pRope->m_Width ); + Vector4D vecP0 = vecP1; + + uint8 nRed = 0; + uint8 nGreen = 0; + uint8 nBlue = 0; + uint8 nAlpha = 255; + + Vector4D vecDelta = vecP2; + vecDelta -= vecP1; + vecP0 -= vecDelta; + + Vector4D vecP3; + + if ( nParticles < 3 ) + { + vecP3 = vecP2; + vecP3 += vecDelta; + } + else + { + vecP3.Init( pNode[2].m_vPredicted, width ); + } + int nPnt = 3; + int nColor = 1; + Vector vColor0( pColors[0].x * vModColor.x, pColors[0].y * vModColor.y, pColors[0].z * vModColor.z ); + Vector vColor1( pColors[1].x * vModColor.x, pColors[1].y * vModColor.y, pColors[1].z * vModColor.z ); + + float flT = 0; + do + { + if ( ! nSegmentsAvailableInBuffer ) + { + meshBuilder.End(); + pMesh->Draw(); + nTotalVerts -= (meshVertCount - availableVerts); + nTotalIndices -= (meshIndexCount - availableIndices); + meshVertCount = MIN(nTotalVerts, nMaxVertices); + meshIndexCount = MIN(nTotalIndices, nMaxIndices); + meshBuilder.Begin( pMesh, MATERIAL_TRIANGLES, meshVertCount, meshIndexCount ); + availableVerts = meshVertCount; + availableIndices = meshIndexCount; + // copy the last emitted points + OUTPUT_2SPLINE_VERTS( flT, flLastU ); + + nSegmentsAvailableInBuffer = MIN( ( availableVerts - 2 ) / nNumVerticesPerSegment, + availableIndices / nNumIndicesPerSegment ); + + nCurIDX = 0; + } + nSegmentsAvailableInBuffer--; + flT = 0.; + for( int nSlice = 0 ; nSlice < nSubdivCount; nSlice++ ) + { + float omt = 1.0f - flT; + nRed = FastFToC( (vColor0.x * omt) + (vColor1.x*flT) ); + nGreen = FastFToC( (vColor0.y * omt) + (vColor1.y*flT) ); + nBlue = FastFToC( (vColor0.z * omt) + (vColor1.z*flT) ); + OUTPUT_2SPLINE_VERTS( flT, flU ); + flT += m_flTStep; + flU += flDU; + if ( ! bFirstPoint ) + { + meshBuilder.FastIndex( nCurIDX ); + meshBuilder.FastIndex( nCurIDX+1 ); + meshBuilder.FastIndex( nCurIDX+2 ); + meshBuilder.FastIndex( nCurIDX+1 ); + meshBuilder.FastIndex( nCurIDX+3 ); + meshBuilder.FastIndex( nCurIDX+2 ); + nCurIDX += 2; + } + bFirstPoint = false; + } + // next segment + vColor0 = vColor1; + if ( nColor < nParticles-1 ) + { + nColor++; + vColor1.Init( pColors[nColor].x * vModColor.x, pColors[nColor].y * vModColor.y, pColors[nColor].z * vModColor.z ); + } + if ( nSegmentsToRender > 1 ) + { + vecP0 = vecP1; + vecP1 = vecP2; + vecP2 = vecP3; + + if ( nPnt < nParticles ) + { + vecP3.AsVector3D() = pNode[nPnt].m_vPredicted; + nPnt++; + } + else + { + // fake last point by extrapolating + vecP3 += vecP2; + vecP3 -= vecP1; + } + } + } while( --nSegmentsToRender ); + + // output last piece + OUTPUT_2SPLINE_VERTS( 1.0, flU ); + meshBuilder.FastIndex( nCurIDX ); + meshBuilder.FastIndex( nCurIDX+1 ); + meshBuilder.FastIndex( nCurIDX+2 ); + meshBuilder.FastIndex( nCurIDX+1 ); + meshBuilder.FastIndex( nCurIDX+3 ); + meshBuilder.FastIndex( nCurIDX+2 ); + nCurIDX += 4; + flLastU = flU; + } + } + + meshBuilder.End(); + pMesh->Draw(); + } + } + + m_RopeQueuedRenderCaches_Mutex.Lock(); + if( pBuildRopeQueuedData && (m_RopeQueuedRenderCaches.Count() != 0) ) + { + unsigned short iHeadIndex = m_RopeQueuedRenderCaches.Head(); + delete m_RopeQueuedRenderCaches[iHeadIndex].m_pRopeDataMutex; + m_RopeQueuedRenderCaches.Remove( iHeadIndex ); + } + m_RopeQueuedRenderCaches_Mutex.Unlock(); +} +#else void CRopeManager::DrawRenderCache_NonQueued( bool bShadowDepth, RopeRenderData_t *pRenderCache, int nRenderCacheCount, const Vector &vCurrentViewForward, const Vector &vCurrentViewOrigin, C_RopeKeyframe::BuildRopeQueuedData_t *pBuildRopeQueuedData ) { VPROF_BUDGET( "CRopeManager::DrawRenderCache", VPROF_BUDGETGROUP_ROPES ); @@ -519,6 +852,11 @@ void CRopeManager::DrawRenderCache_NonQueued( bool bShadowDepth, RopeRenderData_ m_RopeQueuedRenderCaches.Remove( m_RopeQueuedRenderCaches.Head() ); } } +#endif + +#ifdef MAPBASE +ConVar r_queued_ropes( "r_queued_ropes", "1" ); +#endif //----------------------------------------------------------------------------- // Purpose: @@ -530,15 +868,30 @@ void CRopeManager::DrawRenderCache( bool bShadowDepth ) if( iRenderCacheCount == 0 ) return; +#ifdef MAPBASE + // Check to see if we want to render the ropes. + if( !r_drawropes.GetBool() ) + return; +#endif + Vector vForward = CurrentViewForward(); Vector vOrigin = CurrentViewOrigin(); +#ifdef MAPBASE + CMatRenderContextPtr pRenderContext(materials); +#endif ICallQueue *pCallQueue; +#ifdef MAPBASE + if( r_queued_ropes.GetBool() && (pCallQueue = pRenderContext->GetCallQueue()) != NULL ) +#else if( r_queued_ropes.GetBool() && (pCallQueue = materials->GetRenderContext()->GetCallQueue()) != NULL ) +#endif { //material queue available and desired CRopeManager::RopeRenderData_t *pRenderCache = m_aRenderCache.Base(); +#ifndef MAPBASE AUTO_LOCK( m_RenderCacheMutex ); +#endif int iRopeCount = 0; int iNodeCount = 0; @@ -564,7 +917,12 @@ void CRopeManager::DrawRenderCache( bool bShadowDepth ) (iRopeCount * sizeof(C_RopeKeyframe::BuildRopeQueuedData_t)) + (iNodeCount * (sizeof(Vector) * 2)); +#ifdef MAPBASE + CMatRenderData< byte > rd(pRenderContext, iMemoryNeeded); + void *pMemory = rd.Base(); +#else void *pMemory = m_QueuedModeMemory.Alloc( iMemoryNeeded ); +#endif CRopeManager::RopeRenderData_t *pRenderCachesStart = (CRopeManager::RopeRenderData_t *)pMemory; C_RopeKeyframe::BuildRopeQueuedData_t *pBuildRopeQueuedDataStart = (C_RopeKeyframe::BuildRopeQueuedData_t *)(pRenderCachesStart + iRenderCacheCount); @@ -575,7 +933,11 @@ void CRopeManager::DrawRenderCache( bool bShadowDepth ) RopeQueuedRenderCache_t cache; cache.pCaches = pRenderCachesStart; cache.iCacheCount = iRenderCacheCount; +#ifdef MAPBASE + cache.m_pRopeDataMutex = new CThreadFastMutex; +#else m_RopeQueuedRenderCaches.AddToTail( cache ); +#endif C_RopeKeyframe::BuildRopeQueuedData_t *pWriteRopeQueuedData = pBuildRopeQueuedDataStart; Vector *pVectorWrite = (Vector *)pVectorDataStart; @@ -588,7 +950,9 @@ void CRopeManager::DrawRenderCache( bool bShadowDepth ) int iCacheCount = pReadCache->m_nCacheCount; pWriteCache->m_nCacheCount = 0; pWriteCache->m_pSolidMaterial = pReadCache->m_pSolidMaterial; +#ifndef MAPBASE pWriteCache->m_pBackMaterial = pReadCache->m_pBackMaterial; +#endif for( int j = 0; j != iCacheCount; ++j ) { C_RopeKeyframe *pRope = pReadCache->m_aCache[j]; @@ -619,6 +983,21 @@ void CRopeManager::DrawRenderCache( bool bShadowDepth ) pVectorWrite += iNodes; //so we don't overwrite the light values with the next rope's predicted positions } } +#ifdef MAPBASE + m_RopeQueuedRenderCaches_Mutex.Lock(); + unsigned short iLLIndex = m_RopeQueuedRenderCaches.AddToTail( cache ); + CThreadFastMutex *pRopeDataMutex = m_RopeQueuedRenderCaches[iLLIndex].m_pRopeDataMutex; + m_RopeQueuedRenderCaches_Mutex.Unlock(); + + Assert( ((void *)pVectorWrite == (void *)(((uint8 *)pMemory) + iMemoryNeeded)) && ((void *)pWriteRopeQueuedData == (void *)pVectorDataStart)); + pCallQueue->QueueCall( this, &CRopeManager::DrawRenderCache_NonQueued, bShadowDepth, pRenderCachesStart, iRenderCacheCount, vForward, vOrigin, pBuildRopeQueuedDataStart, pRopeDataMutex ); + + if ( IsHolidayLightMode() ) + { + // With holiday lights we need to also build the ropes non-queued without rendering them + DrawRenderCache_NonQueued( bShadowDepth, m_aRenderCache.Base(), iRenderCacheCount, vForward, vOrigin, NULL, NULL ); + } +#else Assert( ((void *)pVectorWrite == (void *)(((uint8 *)pMemory) + iMemoryNeeded)) && ((void *)pWriteRopeQueuedData == (void *)pVectorDataStart)); pCallQueue->QueueCall( this, &CRopeManager::DrawRenderCache_NonQueued, bShadowDepth, pRenderCachesStart, iRenderCacheCount, vForward, vOrigin, pBuildRopeQueuedDataStart ); @@ -627,10 +1006,15 @@ void CRopeManager::DrawRenderCache( bool bShadowDepth ) // With holiday lights we need to also build the ropes non-queued without rendering them DrawRenderCache_NonQueued( bShadowDepth, m_aRenderCache.Base(), iRenderCacheCount, vForward, vOrigin, NULL ); } +#endif } else { +#ifdef MAPBASE + DrawRenderCache_NonQueued( bShadowDepth, m_aRenderCache.Base(), iRenderCacheCount, vForward, vOrigin, NULL, NULL ); +#else DrawRenderCache_NonQueued( bShadowDepth, m_aRenderCache.Base(), iRenderCacheCount, vForward, vOrigin, NULL ); +#endif } } @@ -684,6 +1068,7 @@ int CRopeManager::GetHolidayLightStyle( void ) return m_nHolidayLightsStyle; } +#ifndef MAPBASE //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- @@ -807,13 +1192,14 @@ RopeSegData_t *CRopeManager::GetNextSegmentFromCache( void ) ++m_nSegmentCacheCount; return &m_aSegmentCache[m_nSegmentCacheCount-1]; } +#endif void CRopeManager::RemoveRopeFromQueuedRenderCaches( C_RopeKeyframe *pRope ) { //remove this rope from queued render caches - AUTO_LOCK( m_RenderCacheMutex ); + AUTO_LOCK( m_RopeQueuedRenderCaches_Mutex ); int index = m_RopeQueuedRenderCaches.Head(); while( m_RopeQueuedRenderCaches.IsValidIndex( index ) ) { @@ -825,7 +1211,13 @@ void CRopeManager::RemoveRopeFromQueuedRenderCaches( C_RopeKeyframe *pRope ) { if( pCache->m_aCache[j] == pRope ) { +#ifdef MAPBASE + RenderCacheData.m_pRopeDataMutex->Lock(); pCache->m_aCache[j] = NULL; + RenderCacheData.m_pRopeDataMutex->Unlock(); +#else + pCache->m_aCache[j] = NULL; +#endif } } } @@ -842,9 +1234,11 @@ void CRopeManager::RemoveRopeFromQueuedRenderCaches( C_RopeKeyframe *pRope ) void Rope_ResetCounters() { +#ifndef MAPBASE g_RopeCollideTicks.Init(); g_RopeDrawTicks.Init(); g_RopeSimulateTicks.Init(); +#endif g_nRopePointsSimulated = 0; } @@ -888,8 +1282,12 @@ void C_RopeKeyframe::CPhysicsDelegate::GetNodeForces( CSimplePhysics::CNode *pNo if( !m_pKeyframe->m_LinksTouchingSomething[iNode] && m_pKeyframe->m_bApplyWind) { +#ifdef MAPBASE + Vector vecWindVel = GetWindspeedAtLocation( m_pKeyframe->m_RopePhysics.GetNode( iNode )->m_vPos ); +#else Vector vecWindVel; GetWindspeedAtTime(gpGlobals->curtime, vecWindVel); +#endif if ( vecWindVel.LengthSqr() > 0 ) { Vector vecWindAccel; @@ -897,7 +1295,11 @@ void C_RopeKeyframe::CPhysicsDelegate::GetNodeForces( CSimplePhysics::CNode *pNo } else { +#ifdef MAPBASE + if ( ( m_pKeyframe->m_flCurrentGustLifetime != 0.0f ) && ( m_pKeyframe->m_flCurrentGustTimer < m_pKeyframe->m_flCurrentGustLifetime ) ) +#else if (m_pKeyframe->m_flCurrentGustTimer < m_pKeyframe->m_flCurrentGustLifetime ) +#endif { float div = m_pKeyframe->m_flCurrentGustTimer / m_pKeyframe->m_flCurrentGustLifetime; float scale = 1 - cos( div * M_PI ); @@ -915,8 +1317,17 @@ void C_RopeKeyframe::CPhysicsDelegate::GetNodeForces( CSimplePhysics::CNode *pNo } // Apply any instananeous forces and reset +#ifdef MAPBASE + *pAccel += ROPE_IMPULSE_SCALE * m_pKeyframe->m_vecImpulse; + m_pKeyframe->m_vecImpulse *= ROPE_IMPULSE_DECAY; + if ( m_pKeyframe->m_vecImpulse.LengthSqr() < 0.1f ) + { + m_pKeyframe->m_vecImpulse = vec3_origin; + } +#else *pAccel += ROPE_IMPULSE_SCALE * m_pKeyframe->m_flImpulse; m_pKeyframe->m_flImpulse *= ROPE_IMPULSE_DECAY; +#endif } @@ -953,21 +1364,31 @@ void C_RopeKeyframe::CPhysicsDelegate::ApplyConstraints( CSimplePhysics::CNode * { VPROF( "CPhysicsDelegate::ApplyConstraints" ); +#ifndef MAPBASE CTraceFilterWorldOnly traceFilter; +#endif // Collide with the world. if( ((m_pKeyframe->m_RopeFlags & ROPE_COLLIDE) && rope_collide.GetInt()) || (rope_collide.GetInt() == 2) ) { +#ifdef MAPBASE + CTraceFilterWorldOnly traceFilter; +#else CTimeAdder adder( &g_RopeCollideTicks ); +#endif for( int i=0; i < nNodes; i++ ) { CSimplePhysics::CNode *pNode = &pNodes[i]; int iIteration; +#ifdef MAPBASE + const int nIterations = 10; +#else int nIterations = 10; +#endif for( iIteration=0; iIteration < nIterations; iIteration++ ) { trace_t trace; @@ -985,7 +1406,11 @@ void C_RopeKeyframe::CPhysicsDelegate::ApplyConstraints( CSimplePhysics::CNode * } // Apply some friction. +#ifdef MAPBASE + const float flSlowFactor = 0.3f; +#else static float flSlowFactor = 0.3f; +#endif pNode->m_vPos -= (pNode->m_vPos - pNode->m_vPrevPos) * flSlowFactor; // Move it out along the face normal. @@ -1051,6 +1476,10 @@ C_RopeKeyframe::C_RopeKeyframe() m_vColorMod.Init( 1, 1, 1 ); m_nLinksTouchingSomething = 0; m_Subdiv = 255; // default to using the cvar +#ifdef MAPBASE + m_flCurrentGustLifetime = 0.0f; + m_flCurrentGustTimer = 0.0f; +#endif m_fLockedPoints = 0; m_fPrevLockedPoints = 0; @@ -1058,7 +1487,11 @@ C_RopeKeyframe::C_RopeKeyframe() m_iForcePointMoveCounter = 0; m_flCurScroll = m_flScrollSpeed = 0; m_TextureScale = 4; // 4:1 +#ifdef MAPBASE + m_vecImpulse.Init(); +#else m_flImpulse.Init(); +#endif m_nRopeIndex = s_nLastRopeIndex++; @@ -1071,11 +1504,13 @@ C_RopeKeyframe::~C_RopeKeyframe() s_RopeManager.RemoveRopeFromQueuedRenderCaches( this ); g_Ropes.FindAndRemove( this ); +#ifndef MAPBASE if ( m_pBackMaterial ) { m_pBackMaterial->DecrementReferenceCount(); m_pBackMaterial = NULL; } +#endif } @@ -1136,6 +1571,11 @@ C_RopeKeyframe* C_RopeKeyframe::CreateFromKeyValues( C_BaseAnimating *pEnt, KeyV pRope->m_RopeFlags |= ROPE_NO_GRAVITY; } +#ifdef MAPBASE + // Model ropes need wind to move + pRope->m_RopeFlags |= ROPE_USE_WIND; +#endif + pRope->m_RopeLength = pValues->GetInt( "Length" ); pRope->m_TextureScale = pValues->GetFloat( "TextureScale", pRope->m_TextureScale ); pRope->m_Slack = 0; @@ -1265,6 +1705,9 @@ void C_RopeKeyframe::RecomputeSprings() void C_RopeKeyframe::ShakeRope( const Vector &vCenter, float flRadius, float flMagnitude ) { // Sum up whatever it would apply to all of our points. +#ifdef MAPBASE + bool bWantsThink = false; +#endif for ( int i=0; i < m_nSegments; i++ ) { CSimplePhysics::CNode *pNode = m_RopePhysics.GetNode( i ); @@ -1274,9 +1717,21 @@ void C_RopeKeyframe::ShakeRope( const Vector &vCenter, float flRadius, float flM float flShakeAmount = 1.0f - flDist / flRadius; if ( flShakeAmount >= 0 ) { +#ifdef MAPBASE + m_vecImpulse.z += flShakeAmount * flMagnitude; + bWantsThink = true; +#else m_flImpulse.z += flShakeAmount * flMagnitude; +#endif } } + +#ifdef MAPBASE + if ( bWantsThink ) + { + SetNextClientThink( CLIENT_THINK_ALWAYS ); + } +#endif } @@ -1285,6 +1740,9 @@ void C_RopeKeyframe::OnDataChanged( DataUpdateType_t updateType ) BaseClass::OnDataChanged( updateType ); m_bNewDataThisFrame = true; +#ifdef MAPBASE + SetNextClientThink( CLIENT_THINK_ALWAYS ); +#endif if( updateType != DATA_UPDATE_CREATED ) return; @@ -1303,7 +1761,11 @@ void C_RopeKeyframe::OnDataChanged( DataUpdateType_t updateType ) } else { +#ifdef MAPBASE + Q_strncpy( str, "missing_rope_material", sizeof( str ) ); +#else Q_strncpy( str, "asdf", sizeof( str ) ); +#endif } FinishInit( str ); @@ -1314,11 +1776,21 @@ void C_RopeKeyframe::FinishInit( const char *pMaterialName ) { // Get the material from the material system. m_pMaterial = materials->FindMaterial( pMaterialName, TEXTURE_GROUP_OTHER ); + +#ifdef MAPBASE + if ( !g_pSplineCableShadowdepth ) + { + g_pSplineCableShadowdepth = g_pMaterialSystem->FindMaterial( "cable/rope_shadowdepth", TEXTURE_GROUP_OTHER ); + g_pSplineCableShadowdepth->IncrementReferenceCount(); + } +#endif + if( m_pMaterial ) m_TextureHeight = m_pMaterial->GetMappingHeight(); else m_TextureHeight = 1; +#ifndef MAPBASE char backName[512]; Q_snprintf( backName, sizeof( backName ), "%s_back", pMaterialName ); @@ -1331,6 +1803,7 @@ void C_RopeKeyframe::FinishInit( const char *pMaterialName ) m_pBackMaterial->IncrementReferenceCount(); m_pBackMaterial->GetMappingWidth(); } +#endif // Init rope physics. m_nSegments = clamp( m_nSegments, 2, ROPE_MAX_SEGMENTS ); @@ -1409,10 +1882,24 @@ void C_RopeKeyframe::ClientThink() if( !InitRopePhysics() ) // init if not already return; +#ifdef MAPBASE + if( DetectRestingState( m_bApplyWind ) ) +#else if( !DetectRestingState( m_bApplyWind ) ) +#endif { +#ifdef MAPBASE + if ( ( m_RopeFlags & ROPE_USE_WIND ) == 0 ) + { + SetNextClientThink( CLIENT_THINK_NEVER ); + } + return; + } +#endif // Update the simulation. +#ifndef MAPBASE CTimeAdder adder( &g_RopeSimulateTicks ); +#endif RunRopeSimulation( gpGlobals->frametime ); @@ -1421,6 +1908,10 @@ void C_RopeKeyframe::ClientThink() m_bNewDataThisFrame = false; // Setup a new wind gust? +#ifdef MAPBASE + if ( m_bApplyWind ) + { +#endif m_flCurrentGustTimer += gpGlobals->frametime; m_flTimeToNextGust -= gpGlobals->frametime; if( m_flTimeToNextGust <= 0 ) @@ -1438,8 +1929,13 @@ void C_RopeKeyframe::ClientThink() m_flTimeToNextGust = RandomFloat( 3.0f, 4.0f ); } +#ifdef MAPBASE + } + UpdateBBox(); +#else UpdateBBox(); } +#endif } @@ -1560,18 +2056,62 @@ bool C_RopeKeyframe::GetAttachment( int number, Vector &origin, QAngle &angles ) return false; } +#ifdef MAPBASE +const Vector &C_RopeKeyframe::GetNodePosition( int index ) +{ + int nNodes = m_RopePhysics.NumNodes(); + if ( index >= nNodes || nNodes < 2 ) + { + Warning( "C_RopeKeyframe::GetNodePosition(): Invalid node index %i (number of nodes is %i)\n", index, nNodes ); + return vec3_origin; + } + + return m_RopePhysics.GetNode( index )->m_vPredicted; +} + +int C_RopeKeyframe::GetNumNodes() +{ + return m_RopePhysics.NumNodes(); +} +#endif + bool C_RopeKeyframe::AnyPointsMoved() { +#ifdef MAPBASE + int nNodeCount = m_RopePhysics.NumNodes(); + for( int i=0; i < nNodeCount; i++ ) +#else for( int i=0; i < m_RopePhysics.NumNodes(); i++ ) +#endif { CSimplePhysics::CNode *pNode = m_RopePhysics.GetNode( i ); +#ifdef MAPBASE + float flMoveDistSqr = pNode->m_vPos.DistToSqr( pNode->m_vPrevPos ); + if( flMoveDistSqr > 0.25f ) + { + if ( m_iForcePointMoveCounter < 5 ) + { + m_iForcePointMoveCounter = 5; + } + return true; + } +#else float flMoveDistSqr = (pNode->m_vPos - pNode->m_vPrevPos).LengthSqr(); if( flMoveDistSqr > 0.03f ) return true; +#endif } +#ifdef MAPBASE + if( m_iForcePointMoveCounter >= 0 ) + { + --m_iForcePointMoveCounter; + return true; + } +#else if( --m_iForcePointMoveCounter > 0 ) return true; +#endif return false; } @@ -1628,6 +2168,22 @@ bool C_RopeKeyframe::DetectRestingState( bool &bApplyWind ) Vector &vEnd1 = m_RopePhysics.GetFirstNode()->m_vPos; Vector &vEnd2 = m_RopePhysics.GetLastNode()->m_vPos; +#ifdef MAPBASE + if ( m_RopeFlags & ROPE_USE_WIND ) + { + // Don't apply wind if more than half of the nodes are touching something. + if( m_nLinksTouchingSomething < (m_RopePhysics.NumNodes() >> 1) ) + { + bApplyWind = CalcDistanceToLineSegment( MainViewOrigin(), vEnd1, vEnd2 ) < rope_wind_dist.GetFloat(); + } + } + + if ( m_vecPreviousImpulse != m_vecImpulse ) + { + m_vecPreviousImpulse = m_vecImpulse; + return false; + } +#else if ( !( m_RopeFlags & ROPE_NO_WIND ) ) { // Don't apply wind if more than half of the nodes are touching something. @@ -1641,6 +2197,7 @@ bool C_RopeKeyframe::DetectRestingState( bool &bApplyWind ) m_flPreviousImpulse = m_flImpulse; return false; } +#endif return !AnyPointsMoved() && !bApplyWind && !rope_shake.GetInt(); } @@ -1673,7 +2230,7 @@ inline void Catmull_Rom_Eval( const catmull_t &spline, const Vector &t, Vector & output = spline.c + (t.x * spline.t) + (t.y*spline.t2) + (t.z * spline.t3); } - +#ifndef MAPBASE //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- @@ -1842,6 +2399,7 @@ void C_RopeKeyframe::BuildRope( RopeSegData_t *pSegmentData, const Vector &vCurr } } } +#endif void C_RopeKeyframe::UpdateBBox() { @@ -1929,7 +2487,11 @@ bool C_RopeKeyframe::CalculateEndPointAttachment( C_BaseEntity *pEnt, int iAttac if ( m_RopeFlags & ROPE_PLAYER_WPN_ATTACH ) { +#ifdef MAPBASE + C_BasePlayer *pPlayer = ToBasePlayer( pEnt ); +#else C_BasePlayer *pPlayer = dynamic_cast< C_BasePlayer* >( pEnt ); +#endif if ( pPlayer ) { C_BaseAnimating *pModel = pPlayer->GetRenderedWeaponModel(); @@ -1995,10 +2557,12 @@ IMaterial* C_RopeKeyframe::GetSolidMaterial( void ) return m_pMaterial; } +#ifndef MAPBASE IMaterial* C_RopeKeyframe::GetBackMaterial( void ) { return m_pBackMaterial; } +#endif bool C_RopeKeyframe::GetEndPointAttachment( int iPt, Vector &vPos, QAngle &angle ) { @@ -2017,7 +2581,7 @@ bool C_RopeKeyframe::GetEndPointAttachment( int iPt, Vector &vPos, QAngle &angle return true; } - +#ifndef MAPBASE // Look at the global cvar and recalculate rope subdivision data if necessary. Vector *C_RopeKeyframe::GetRopeSubdivVectors( int *nSubdivs ) { @@ -2041,6 +2605,7 @@ Vector *C_RopeKeyframe::GetRopeSubdivVectors( int *nSubdivs ) return g_RopeSubdivs[subdiv]; } } +#endif void C_RopeKeyframe::CalcLightValues() @@ -2089,7 +2654,13 @@ void C_RopeKeyframe::ReceiveMessage( int classID, bf_read &msg ) } // Read instantaneous fore data +#ifdef MAPBASE + m_vecImpulse.x = msg.ReadFloat(); + m_vecImpulse.y = msg.ReadFloat(); + m_vecImpulse.z = msg.ReadFloat(); +#else m_flImpulse.x = msg.ReadFloat(); m_flImpulse.y = msg.ReadFloat(); m_flImpulse.z = msg.ReadFloat(); +#endif } diff --git a/src/game/client/c_rope.h b/src/game/client/c_rope.h index b9bfb2c8..aa3bdaae 100644 --- a/src/game/client/c_rope.h +++ b/src/game/client/c_rope.h @@ -33,6 +33,9 @@ public: DECLARE_CLASS( C_RopeKeyframe, C_BaseEntity ); DECLARE_CLIENTCLASS(); +#ifdef MAPBASE_VSCRIPT + DECLARE_ENT_SCRIPTDESC(); +#endif private: @@ -107,7 +110,9 @@ public: // Get the rope material data. IMaterial *GetSolidMaterial( void ); +#ifndef MAPBASE IMaterial *GetBackMaterial( void ); +#endif struct BuildRopeQueuedData_t { @@ -119,7 +124,11 @@ public: float m_Slack; }; +#ifdef MAPBASE + void BuildRope( RopeSegData_t *pRopeSegment, const Vector &vCurrentViewForward, const Vector &vCurrentViewOrigin, BuildRopeQueuedData_t *pQueuedData ); +#else void BuildRope( RopeSegData_t *pRopeSegment, const Vector &vCurrentViewForward, const Vector &vCurrentViewOrigin, BuildRopeQueuedData_t *pQueuedData, bool bQueued ); +#endif // C_BaseEntity overrides. public: @@ -136,6 +145,11 @@ public: virtual bool GetAttachment( int number, Vector &origin ); virtual bool GetAttachmentVelocity( int number, Vector &originVel, Quaternion &angleVel ); +#ifdef MAPBASE + const Vector &GetNodePosition( int index ); + int GetNumNodes(); +#endif + private: void FinishInit( const char *pMaterialName ); @@ -160,6 +174,11 @@ private: void ReceiveMessage( int classID, bf_read &msg ); bool CalculateEndPointAttachment( C_BaseEntity *pEnt, int iAttachment, Vector &vPos, QAngle *pAngles ); +#ifdef MAPBASE_VSCRIPT + HSCRIPT ScriptGetStartEntity() { return ToHScript( GetStartEntity() ); } + HSCRIPT ScriptGetEndEntity() { return ToHScript( GetEndEntity() ); } +#endif + private: // Track which links touched something last frame. Used to prevent wind from gusting on them. @@ -196,19 +215,29 @@ private: float m_TextureScale; // pixels per inch int m_fLockedPoints; // Which points are locked down. +#ifdef MAPBASE + int m_nChangeCount; +#endif float m_Width; CPhysicsDelegate m_PhysicsDelegate; IMaterial *m_pMaterial; +#ifndef MAPBASE IMaterial *m_pBackMaterial; // Optional translucent background material for the rope to help reduce aliasing. +#endif int m_TextureHeight; // Texture height, for texture scale calculations. // Instantaneous force +#ifdef MAPBASE + Vector m_vecImpulse; + Vector m_vecPreviousImpulse; +#else Vector m_flImpulse; Vector m_flPreviousImpulse; +#endif // Simulated wind gusts. float m_flCurrentGustTimer; @@ -253,7 +282,9 @@ public: virtual void ResetRenderCache( void ) = 0; virtual void AddToRenderCache( C_RopeKeyframe *pRope ) = 0; virtual void DrawRenderCache( bool bShadowDepth ) = 0; +#ifndef MAPBASE virtual void OnRenderStart( void ) = 0; +#endif virtual void SetHolidayLightMode( bool bHoliday ) = 0; virtual bool IsHolidayLightMode( void ) = 0; virtual int GetHolidayLightStyle( void ) = 0; diff --git a/src/game/client/c_sceneentity.cpp b/src/game/client/c_sceneentity.cpp index 1ee99399..8c1adade 100644 --- a/src/game/client/c_sceneentity.cpp +++ b/src/game/client/c_sceneentity.cpp @@ -622,7 +622,7 @@ void C_SceneEntity::DispatchStartSpeak( CChoreoScene *scene, C_BaseFlex *actor, es.m_pSoundName = event->GetParameters(); EmitSound( filter, actor->entindex(), es ); - actor->AddSceneEvent( scene, event, NULL, IsClientOnly() ); + actor->AddSceneEvent( scene, event, NULL, IsClientOnly(), this ); // Close captioning only on master token no matter what... if ( event->GetCloseCaptionType() == CChoreoEvent::CC_MASTER ) @@ -752,20 +752,72 @@ CChoreoStringPool g_ChoreoStringPool; CChoreoScene *C_SceneEntity::LoadScene( const char *filename ) { +#ifdef MAPBASE + char loadfile[MAX_PATH]; +#else char loadfile[ 512 ]; +#endif Q_strncpy( loadfile, filename, sizeof( loadfile ) ); Q_SetExtension( loadfile, ".vcd", sizeof( loadfile ) ); Q_FixSlashes( loadfile ); +#ifdef MAPBASE + // + // Raw scene file support + // + void *pBuffer = 0; + size_t bufsize = scenefilecache->GetSceneBufferSize( loadfile ); + CChoreoScene *pScene = NULL; + if ( bufsize > 0 ) + { + // Definitely in scenes.image + pBuffer = malloc( bufsize ); + if ( !scenefilecache->GetSceneData( filename, (byte *)pBuffer, bufsize ) ) + { + free( pBuffer ); + return NULL; + } + + + if ( IsBufferBinaryVCD( (char*)pBuffer, bufsize ) ) + { + pScene = new CChoreoScene( this ); + CUtlBuffer buf( pBuffer, bufsize, CUtlBuffer::READ_ONLY ); + if ( !pScene->RestoreFromBinaryBuffer( buf, loadfile, &g_ChoreoStringPool ) ) + { + Warning( "Unable to restore scene '%s'\n", loadfile ); + delete pScene; + pScene = NULL; + } + } + } + else if (filesystem->ReadFileEx( loadfile, "MOD", &pBuffer, true )) + { + // Not in scenes.image, but it's a raw file + g_TokenProcessor.SetBuffer((char*)pBuffer); + pScene = ChoreoLoadScene( loadfile, this, &g_TokenProcessor, Scene_Printf ); + } + else + { + // Abandon ship + return NULL; + } + + if(pScene) + { + pScene->SetPrintFunc( Scene_Printf ); + pScene->SetEventCallbackInterface( this ); + } +#else char *pBuffer = NULL; size_t bufsize = scenefilecache->GetSceneBufferSize( loadfile ); if ( bufsize <= 0 ) return NULL; - pBuffer = new char[ bufsize ]; + pBuffer = malloc( bufsize ); if ( !scenefilecache->GetSceneData( filename, (byte *)pBuffer, bufsize ) ) { - delete[] pBuffer; + free( pBuffer ); return NULL; } @@ -791,8 +843,9 @@ CChoreoScene *C_SceneEntity::LoadScene( const char *filename ) g_TokenProcessor.SetBuffer( pBuffer ); pScene = ChoreoLoadScene( loadfile, this, &g_TokenProcessor, Scene_Printf ); } +#endif - delete[] pBuffer; + free( pBuffer ); return pScene; } @@ -859,7 +912,7 @@ void C_SceneEntity::UnloadScene( void ) //----------------------------------------------------------------------------- void C_SceneEntity::DispatchStartFlexAnimation( CChoreoScene *scene, C_BaseFlex *actor, CChoreoEvent *event ) { - actor->AddSceneEvent( scene, event, NULL, IsClientOnly() ); + actor->AddSceneEvent( scene, event, NULL, IsClientOnly(), this ); } //----------------------------------------------------------------------------- @@ -879,7 +932,7 @@ void C_SceneEntity::DispatchEndFlexAnimation( CChoreoScene *scene, C_BaseFlex *a //----------------------------------------------------------------------------- void C_SceneEntity::DispatchStartExpression( CChoreoScene *scene, C_BaseFlex *actor, CChoreoEvent *event ) { - actor->AddSceneEvent( scene, event, NULL, IsClientOnly() ); + actor->AddSceneEvent( scene, event, NULL, IsClientOnly(), this ); } //----------------------------------------------------------------------------- @@ -903,7 +956,7 @@ void C_SceneEntity::DispatchStartGesture( CChoreoScene *scene, C_BaseFlex *actor if ( !Q_stricmp( event->GetName(), "NULL" ) ) return; - actor->AddSceneEvent( scene, event, NULL, IsClientOnly() ); + actor->AddSceneEvent( scene, event, NULL, IsClientOnly(), this ); } //----------------------------------------------------------------------------- @@ -918,7 +971,7 @@ void C_SceneEntity::DispatchProcessGesture( CChoreoScene *scene, C_BaseFlex *act return; actor->RemoveSceneEvent( scene, event, false ); - actor->AddSceneEvent( scene, event, NULL, IsClientOnly() ); + actor->AddSceneEvent( scene, event, NULL, IsClientOnly(), this ); } //----------------------------------------------------------------------------- @@ -941,7 +994,7 @@ void C_SceneEntity::DispatchEndGesture( CChoreoScene *scene, C_BaseFlex *actor, //----------------------------------------------------------------------------- void C_SceneEntity::DispatchStartSequence( CChoreoScene *scene, CBaseFlex *actor, CChoreoEvent *event ) { - actor->AddSceneEvent( scene, event, NULL, IsClientOnly() ); + actor->AddSceneEvent( scene, event, NULL, IsClientOnly(), this ); } //----------------------------------------------------------------------------- @@ -951,7 +1004,7 @@ void C_SceneEntity::DispatchStartSequence( CChoreoScene *scene, CBaseFlex *actor void C_SceneEntity::DispatchProcessSequence( CChoreoScene *scene, CBaseFlex *actor, CChoreoEvent *event ) { actor->RemoveSceneEvent( scene, event, false ); - actor->AddSceneEvent( scene, event, NULL, IsClientOnly() ); + actor->AddSceneEvent( scene, event, NULL, IsClientOnly(), this ); } //----------------------------------------------------------------------------- @@ -1156,4 +1209,4 @@ void C_SceneEntity::PrefetchAnimBlocks( CChoreoScene *pScene ) return; Msg( "%d of %d animations resident\n", nResident, nChecked ); -} \ No newline at end of file +} diff --git a/src/game/client/c_shadowcontrol.cpp b/src/game/client/c_shadowcontrol.cpp index 80169241..c725a2ba 100644 --- a/src/game/client/c_shadowcontrol.cpp +++ b/src/game/client/c_shadowcontrol.cpp @@ -32,13 +32,24 @@ private: color32 m_shadowColor; float m_flShadowMaxDist; bool m_bDisableShadows; +#ifdef MAPBASE + bool m_bEnableLocalLightShadows; +#endif }; IMPLEMENT_CLIENTCLASS_DT(C_ShadowControl, DT_ShadowControl, CShadowControl) RecvPropVector(RECVINFO(m_shadowDirection)), +#ifdef MAPBASE + /*RecvPropInt(RECVINFO(m_shadowColor), 0, RecvProxy_Int32ToColor32),*/ + RecvPropInt(RECVINFO(m_shadowColor), 0, RecvProxy_IntToColor32), +#else RecvPropInt(RECVINFO(m_shadowColor)), +#endif RecvPropFloat(RECVINFO(m_flShadowMaxDist)), RecvPropBool(RECVINFO(m_bDisableShadows)), +#ifdef MAPBASE + RecvPropBool(RECVINFO(m_bEnableLocalLightShadows)), +#endif END_RECV_TABLE() @@ -54,6 +65,9 @@ void C_ShadowControl::OnDataChanged(DataUpdateType_t updateType) g_pClientShadowMgr->SetShadowColor( m_shadowColor.r, m_shadowColor.g, m_shadowColor.b ); g_pClientShadowMgr->SetShadowDistance( m_flShadowMaxDist ); g_pClientShadowMgr->SetShadowsDisabled( m_bDisableShadows ); +#ifdef DYNAMIC_RTT_SHADOWS + g_pClientShadowMgr->SetShadowFromWorldLightsEnabled( m_bEnableLocalLightShadows ); +#endif } //------------------------------------------------------------------------------ diff --git a/src/game/client/c_soundscape.cpp b/src/game/client/c_soundscape.cpp index 6321ce8d..0fae8ea8 100644 --- a/src/game/client/c_soundscape.cpp +++ b/src/game/client/c_soundscape.cpp @@ -207,6 +207,9 @@ public: // "dsp_volume" void ProcessDSPVolume( KeyValues *pKey, subsoundscapeparams_t ¶ms ); +#ifdef MAPBASE // Moved to public space + void AddSoundScapeFile( const char *filename ); +#endif private: @@ -215,7 +218,9 @@ private: return gpGlobals->framecount == m_nRestoreFrame ? true : false; } +#ifndef MAPBASE // Moved to public space void AddSoundScapeFile( const char *filename ); +#endif void TouchPlayLooping( KeyValues *pAmbient ); void TouchPlayRandom( KeyValues *pPlayRandom ); @@ -265,6 +270,13 @@ void Soundscape_Update( audioparams_t &audio ) g_SoundscapeSystem.UpdateAudioParams( audio ); } +#ifdef MAPBASE +void Soundscape_AddFile( const char *szFile ) +{ + g_SoundscapeSystem.AddSoundScapeFile(szFile); +} +#endif + #define SOUNDSCAPE_MANIFEST_FILE "scripts/soundscapes_manifest.txt" void C_SoundscapeSystem::AddSoundScapeFile( const char *filename ) @@ -310,6 +322,16 @@ bool C_SoundscapeSystem::Init() mapSoundscapeFilename = VarArgs( "scripts/soundscapes_%s.txt", mapname ); } +#ifdef MAPBASE + if (filesystem->FileExists(VarArgs("maps/%s_soundscapes.txt", mapname))) + { + // A Mapbase-specific file exists. Load that instead. + // Any additional soundscape files, like the original scripts/soundscapes version, + // could be loaded through #include and/or #base. + mapSoundscapeFilename = VarArgs("maps/%s_soundscapes.txt", mapname); + } +#endif + KeyValues *manifest = new KeyValues( SOUNDSCAPE_MANIFEST_FILE ); if ( filesystem->LoadKeyValues( *manifest, IFileSystem::TYPE_SOUNDSCAPE, SOUNDSCAPE_MANIFEST_FILE, "GAME" ) ) { diff --git a/src/game/client/c_soundscape.h b/src/game/client/c_soundscape.h index 8c5f45e9..b42d4b42 100644 --- a/src/game/client/c_soundscape.h +++ b/src/game/client/c_soundscape.h @@ -24,4 +24,8 @@ extern void Soundscape_Update( audioparams_t &audio ); // sounds are still playing when they're not. void Soundscape_OnStopAllSounds(); +#ifdef MAPBASE +void Soundscape_AddFile( const char *szFile ); +#endif + #endif // C_SOUNDSCAPE_H diff --git a/src/game/client/c_te_largefunnel.cpp b/src/game/client/c_te_largefunnel.cpp index 25430db0..f8db20c4 100644 --- a/src/game/client/c_te_largefunnel.cpp +++ b/src/game/client/c_te_largefunnel.cpp @@ -64,7 +64,12 @@ void C_TELargeFunnel::CreateFunnel( void ) float ratio = 0.25; float invratio = 1 / ratio; +#ifdef MAPBASE + // Uh...figure out how to fix this PMaterialHandle hMaterial = pSimple->GetPMaterial( "sprites/flare6" ); +#else + PMaterialHandle hMaterial = pSimple->GetPMaterial( "sprites/flare6" ); +#endif for ( i = -256 ; i <= 256 ; i += 24 ) //24 from 32.. little more dense { diff --git a/src/game/client/c_te_legacytempents.cpp b/src/game/client/c_te_legacytempents.cpp index a0b04734..af727666 100644 --- a/src/game/client/c_te_legacytempents.cpp +++ b/src/game/client/c_te_legacytempents.cpp @@ -597,8 +597,12 @@ bool C_LocalTempEntity::Frame( float frametime, int framenumber ) if ( flags & FTENT_WINDBLOWN ) { +#ifdef MAPBASE + Vector vecWind = GetWindspeedAtLocation( GetAbsOrigin() ); +#else Vector vecWind; GetWindspeedAtTime( gpGlobals->curtime, vecWind ); +#endif for ( int i = 0 ; i < 2 ; i++ ) { @@ -1860,6 +1864,9 @@ void CTempEnts::MuzzleFlash( const Vector& pos1, const QAngle& angles, int type, // UNDONE: These need their own effects/sprites. For now use the pistol // SMG1 +#if defined ( HL2MP ) // HACK for hl2mp, make the default muzzleflash the smg muzzleflash for weapons like the RPG that are using 'type 0' + default: +#endif // HL2MP case MUZZLEFLASH_SMG1: if ( firstPerson ) { @@ -1897,10 +1904,12 @@ void CTempEnts::MuzzleFlash( const Vector& pos1, const QAngle& angles, int type, } break; +#if !defined ( HL2MP ) // HACK for hl2mp, make the default muzzleflash the smg muzzleflash for weapons like the RPG that are using 'type 0' default: // There's no supported muzzle flash for the type specified! Assert(0); break; +#endif // HL2MP } #endif diff --git a/src/game/client/c_vehicle_choreo_generic.cpp b/src/game/client/c_vehicle_choreo_generic.cpp index 52eba09e..82abc224 100644 --- a/src/game/client/c_vehicle_choreo_generic.cpp +++ b/src/game/client/c_vehicle_choreo_generic.cpp @@ -54,7 +54,12 @@ public: flFOV = m_flFOV; } virtual void DrawHudElements(); +#ifdef MAPBASE + virtual bool IsPassengerUsingStandardWeapons( int nRole = VEHICLE_ROLE_DRIVER ) { return m_bAllowStandardWeapons; } + bool m_bAllowStandardWeapons; +#else virtual bool IsPassengerUsingStandardWeapons( int nRole = VEHICLE_ROLE_DRIVER ) { return false; } +#endif virtual void UpdateViewAngles( C_BasePlayer *pLocalPlayer, CUserCmd *pCmd ); virtual C_BaseCombatCharacter *GetPassenger( int nRole ); virtual int GetPassengerRole( C_BaseCombatCharacter *pPassenger ); @@ -107,6 +112,9 @@ IMPLEMENT_CLIENTCLASS_DT(C_PropVehicleChoreoGeneric, DT_PropVehicleChoreoGeneric RecvPropFloat( RECVINFO( m_vehicleView.flYawMax ) ), RecvPropFloat( RECVINFO( m_vehicleView.flPitchMin ) ), RecvPropFloat( RECVINFO( m_vehicleView.flPitchMax ) ), +#ifdef MAPBASE + RecvPropBool( RECVINFO( m_bAllowStandardWeapons ) ), +#endif END_RECV_TABLE() diff --git a/src/game/client/c_vguiscreen.cpp b/src/game/client/c_vguiscreen.cpp index 1be47024..ee0945a2 100644 --- a/src/game/client/c_vguiscreen.cpp +++ b/src/game/client/c_vguiscreen.cpp @@ -38,10 +38,14 @@ extern vgui::IInputInternal *g_InputInternal; #define VGUI_SCREEN_MODE_RADIUS 80 //Precache the materials -CLIENTEFFECT_REGISTER_BEGIN( PrecacheEffectVGuiScreen ) -CLIENTEFFECT_MATERIAL( "engine/writez" ) +CLIENTEFFECT_REGISTER_BEGIN(PrecacheEffectVGuiScreen) +CLIENTEFFECT_MATERIAL("engine/writez") CLIENTEFFECT_REGISTER_END() +#ifdef MAPBASE +C_EntityClassList g_VGUIScreenList; +template <> C_VGuiScreen* C_EntityClassList::m_pClassList = NULL; +#endif // MAPBASE IMPLEMENT_CLIENTCLASS_DT(C_VGuiScreen, DT_VGuiScreen, CVGuiScreen) RecvPropFloat( RECVINFO(m_flWidth) ), RecvPropFloat( RECVINFO(m_flHeight) ), @@ -67,11 +71,19 @@ C_VGuiScreen::C_VGuiScreen() m_WriteZMaterial.Init( "engine/writez", TEXTURE_GROUP_VGUI ); m_OverlayMaterial.Init( m_WriteZMaterial ); + +#ifdef MAPBASE + g_VGUIScreenList.Insert(this); +#endif // MAPBASE } C_VGuiScreen::~C_VGuiScreen() { DestroyVguiScreen(); + +#ifdef MAPBASE + g_VGUIScreenList.Remove(this); +#endif // MAPBASE } //----------------------------------------------------------------------------- @@ -381,34 +393,69 @@ void C_VGuiScreen::ClientThink( void ) int px = (int)(u * m_nPixelWidth + 0.5f); int py = (int)(v * m_nPixelHeight + 0.5f); +#ifndef MAPBASE // Generate mouse input commands if ((px != m_nOldPx) || (py != m_nOldPy)) { - g_InputInternal->InternalCursorMoved( px, py ); + g_InputInternal->InternalCursorMoved(px, py); + m_nOldPx = px; m_nOldPy = py; } if (m_nButtonPressed & IN_ATTACK) { - g_InputInternal->SetMouseCodeState( MOUSE_LEFT, vgui::BUTTON_PRESSED ); + g_InputInternal->SetMouseCodeState(MOUSE_LEFT, vgui::BUTTON_PRESSED); g_InputInternal->InternalMousePressed(MOUSE_LEFT); } if (m_nButtonPressed & IN_ATTACK2) { - g_InputInternal->SetMouseCodeState( MOUSE_RIGHT, vgui::BUTTON_PRESSED ); - g_InputInternal->InternalMousePressed( MOUSE_RIGHT ); + g_InputInternal->SetMouseCodeState(MOUSE_RIGHT, vgui::BUTTON_PRESSED); + g_InputInternal->InternalMousePressed(MOUSE_RIGHT); } - if ( (m_nButtonReleased & IN_ATTACK) || m_bLoseThinkNextFrame) // for a button release on loosing focus + if ((m_nButtonReleased & IN_ATTACK) || m_bLoseThinkNextFrame) // for a button release on loosing focus { - g_InputInternal->SetMouseCodeState( MOUSE_LEFT, vgui::BUTTON_RELEASED ); - g_InputInternal->InternalMouseReleased( MOUSE_LEFT ); + g_InputInternal->SetMouseCodeState(MOUSE_LEFT, vgui::BUTTON_RELEASED); + g_InputInternal->InternalMouseReleased(MOUSE_LEFT); } if (m_nButtonReleased & IN_ATTACK2) { - g_InputInternal->SetMouseCodeState( MOUSE_RIGHT, vgui::BUTTON_RELEASED ); - g_InputInternal->InternalMouseReleased( MOUSE_RIGHT ); + g_InputInternal->SetMouseCodeState(MOUSE_RIGHT, vgui::BUTTON_RELEASED); + g_InputInternal->InternalMouseReleased(MOUSE_RIGHT); } +#else + vgui::VPANEL focus = g_InputInternal->GetMouseOver(); + // Generate mouse input commands + if ((px != m_nOldPx) || (py != m_nOldPy)) + { + g_InputInternal->UpdateCursorPosInternal(px, py); + + m_nOldPx = px; + m_nOldPy = py; + + focus = pPanel->IsWithinTraverse(px, py, true); + g_InputInternal->SetMouseFocus(focus); + vgui::ivgui()->PostMessage(focus, new KeyValues("CursorMoved", "xpos", px, "ypos", py), NULL); + } + + for (int i = 0; i < 2; i++) + { + const int nBit = i ? IN_ATTACK2 : (IN_ATTACK | IN_USE); + const vgui::MouseCode nButton = i ? MOUSE_RIGHT : MOUSE_LEFT; + + if ((m_nButtonReleased & nBit) || ((m_nButtonState & nBit) && m_bLoseThinkNextFrame)) // for a button release on loosing focus + { + g_InputInternal->SetMouseCodeState(nButton, vgui::BUTTON_RELEASED); + vgui::ivgui()->PostMessage(focus, new KeyValues("MouseReleased", "code", nButton), NULL); + } + else if (m_nButtonPressed & nBit) + { + g_InputInternal->SetMouseCodeState(nButton, vgui::BUTTON_PRESSED); + vgui::ivgui()->PostMessage(focus, new KeyValues("MousePressed", "code", nButton), NULL); + } + } +#endif // !MAPBASE + if ( m_bLoseThinkNextFrame == true ) { @@ -592,6 +639,7 @@ bool C_VGuiScreen::IsInputOnlyToOwner( void ) return (m_fScreenFlags & VGUI_SCREEN_ONLY_USABLE_BY_OWNER) != 0; } +#ifndef MAPBASE //----------------------------------------------------------------------------- // // Enumator class for finding vgui screens close to the local player @@ -599,29 +647,29 @@ bool C_VGuiScreen::IsInputOnlyToOwner( void ) //----------------------------------------------------------------------------- class CVGuiScreenEnumerator : public IPartitionEnumerator { - DECLARE_CLASS_GAMEROOT( CVGuiScreenEnumerator, IPartitionEnumerator ); + DECLARE_CLASS_GAMEROOT(CVGuiScreenEnumerator, IPartitionEnumerator); public: - virtual IterationRetval_t EnumElement( IHandleEntity *pHandleEntity ); + virtual IterationRetval_t EnumElement(IHandleEntity* pHandleEntity); int GetScreenCount(); - C_VGuiScreen *GetVGuiScreen( int index ); + C_VGuiScreen* GetVGuiScreen(int index); private: CUtlVector< CHandle< C_VGuiScreen > > m_VguiScreens; }; -IterationRetval_t CVGuiScreenEnumerator::EnumElement( IHandleEntity *pHandleEntity ) +IterationRetval_t CVGuiScreenEnumerator::EnumElement(IHandleEntity* pHandleEntity) { - C_BaseEntity *pEnt = ClientEntityList().GetBaseEntityFromHandle( pHandleEntity->GetRefEHandle() ); - if ( pEnt == NULL ) + C_BaseEntity* pEnt = ClientEntityList().GetBaseEntityFromHandle(pHandleEntity->GetRefEHandle()); + if (pEnt == NULL) return ITERATION_CONTINUE; // FIXME.. pretty expensive... - C_VGuiScreen *pScreen = dynamic_cast(pEnt); - if ( pScreen ) + C_VGuiScreen* pScreen = dynamic_cast(pEnt); + if (pScreen) { - int i = m_VguiScreens.AddToTail( ); - m_VguiScreens[i].Set( pScreen ); + int i = m_VguiScreens.AddToTail(); + m_VguiScreens[i].Set(pScreen); } return ITERATION_CONTINUE; @@ -632,10 +680,12 @@ int CVGuiScreenEnumerator::GetScreenCount() return m_VguiScreens.Count(); } -C_VGuiScreen *CVGuiScreenEnumerator::GetVGuiScreen( int index ) +C_VGuiScreen* CVGuiScreenEnumerator::GetVGuiScreen(int index) { return m_VguiScreens[index].Get(); -} +} +#endif // !MAPBASE + //----------------------------------------------------------------------------- @@ -669,18 +719,29 @@ C_BaseEntity *FindNearbyVguiScreen( const Vector &viewPosition, const QAngle &vi Ray_t lookRay; lookRay.Init( viewPosition, lookEnd ); +#ifndef MAPBASE // Look for vgui screens that are close to the player CVGuiScreenEnumerator localScreens; ::partition->EnumerateElementsInSphere( PARTITION_CLIENT_NON_STATIC_EDICTS, viewPosition, VGUI_SCREEN_MODE_RADIUS, false, &localScreens ); +#endif // !MAPBASE Vector vecOut, vecViewDelta; float flBestDist = 2.0f; C_VGuiScreen *pBestScreen = NULL; +#ifndef MAPBASE for (int i = localScreens.GetScreenCount(); --i >= 0; ) +#else + for (C_VGuiScreen* pScreen = g_VGUIScreenList.m_pClassList; pScreen != NULL; pScreen = pScreen->m_pNext) +#endif // !MAPBASE { - C_VGuiScreen *pScreen = localScreens.GetVGuiScreen(i); - +#ifndef MAPBASE + C_VGuiScreen* pScreen = localScreens.GetVGuiScreen(i); +#else + // Skip if out of PVS + if (pScreen->IsDormant()) + continue; +#endif if ( pScreen->IsAttachedToViewModel() ) continue; @@ -829,11 +890,21 @@ vgui::Panel *CVGuiScreenPanel::CreateControlByName(const char *controlName) //----------------------------------------------------------------------------- // Purpose: Called when the user presses a button //----------------------------------------------------------------------------- -void CVGuiScreenPanel::OnCommand( const char *command) +void CVGuiScreenPanel::OnCommand(const char* command) { - if ( Q_stricmp( command, "vguicancel" ) ) + if (Q_stricmp(command, "vguicancel")) { - engine->ClientCmd( const_cast( command ) ); +#ifdef MAPBASE + if (m_hEntity && m_hEntity->IsServerEntity()) + { + KeyValues* pCommand = new KeyValues("EntityCommand"); + pCommand->SetInt("entindex", m_hEntity->index); + pCommand->SetString("command_data", command); + engine->ServerCmdKeyValues(pCommand); + } + else +#endif + engine->ClientCmd(const_cast(command)); } BaseClass::OnCommand(command); diff --git a/src/game/client/c_vguiscreen.h b/src/game/client/c_vguiscreen.h index e62cba1b..cb690e65 100644 --- a/src/game/client/c_vguiscreen.h +++ b/src/game/client/c_vguiscreen.h @@ -66,6 +66,10 @@ class C_VGuiScreen : public C_BaseEntity public: DECLARE_CLIENTCLASS(); +#ifdef MAPBASE + C_VGuiScreen* m_pNext; +#endif // MAPBASE + C_VGuiScreen(); ~C_VGuiScreen(); @@ -112,6 +116,15 @@ public: C_BasePlayer *GetPlayerOwner( void ); bool IsInputOnlyToOwner( void ); +#ifdef MAPBASE + void GetSize( float &width, float &height ) const { width = m_flWidth; height = m_flHeight; } + void GetPixelSize( int &width, int &height ) const { width = m_nPixelWidth; height = m_nPixelHeight; } + void SetWidth( float flWidth ) { m_flWidth = flWidth; } + void SetHeight( float flHeight ) { m_flHeight = flHeight; } + void SetPixelWidth( int nWidth ) { m_nPixelWidth = nWidth; } + void SetPixelHeight( int nHeight ) { m_nPixelHeight = nHeight; } +#endif + private: // Vgui screen management void CreateVguiScreen( const char *pTypeName ); diff --git a/src/game/client/c_world.cpp b/src/game/client/c_world.cpp index 3ae6bdcc..938db241 100644 --- a/src/game/client/c_world.cpp +++ b/src/game/client/c_world.cpp @@ -13,6 +13,9 @@ #include "ivieweffects.h" #include "shake.h" #include "eventlist.h" +#ifdef MAPBASE +#include "mapentities_shared.h" +#endif // NVNT haptic include for notification of world precache #include "haptics/haptic_utils.h" // memdbgon must be the last include file in a .cpp file!!! @@ -59,8 +62,15 @@ BEGIN_RECV_TABLE( C_World, DT_World ) RecvPropFloat(RECVINFO(m_flMinPropScreenSpaceWidth)), RecvPropString(RECVINFO(m_iszDetailSpriteMaterial)), RecvPropInt(RECVINFO(m_bColdWorld)), +#ifdef MAPBASE + RecvPropString(RECVINFO(m_iszChapterTitle)), +#endif END_RECV_TABLE() +#ifdef MAPBASE_VSCRIPT +extern bool VScriptClientInit(); +#endif + C_World::C_World( void ) { @@ -76,6 +86,11 @@ bool C_World::Init( int entnum, int iSerialNum ) ActivityList_Init(); EventList_Init(); +#ifdef MAPBASE_VSCRIPT + m_iScriptLanguageServer = SL_NONE; + m_iScriptLanguageClient = SL_NONE; +#endif + return BaseClass::Init( entnum, iSerialNum ); } @@ -184,6 +199,72 @@ void C_World::Spawn( void ) Precache(); } +//----------------------------------------------------------------------------- +// Parse data from a map file +//----------------------------------------------------------------------------- +bool C_World::KeyValue( const char *szKeyName, const char *szValue ) +{ +#ifdef MAPBASE_VSCRIPT + if ( FStrEq( szKeyName, "vscriptlanguage" ) ) + { + m_iScriptLanguageServer = atoi( szValue ); + } + else if ( FStrEq( szKeyName, "vscriptlanguage_client" ) ) + { + m_iScriptLanguageClient = atoi( szValue ); + } + else +#endif + return BaseClass::KeyValue( szKeyName, szValue ); + + return true; +} + +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Parses worldspawn data from BSP on the client +//----------------------------------------------------------------------------- +void C_World::ParseWorldMapData( const char *pMapData ) +{ + char szTokenBuffer[MAPKEY_MAXLENGTH]; + for ( ; true; pMapData = MapEntity_SkipToNextEntity(pMapData, szTokenBuffer) ) + { + // + // Parse the opening brace. + // + char token[MAPKEY_MAXLENGTH]; + pMapData = MapEntity_ParseToken( pMapData, token ); + + // + // Check to see if we've finished or not. + // + if (!pMapData) + break; + + if (token[0] != '{') + { + Error( "MapEntity_ParseAllEntities: found %s when expecting {", token); + continue; + } + + CEntityMapData entData( (char*)pMapData ); + char className[MAPKEY_MAXLENGTH]; + + if (!entData.ExtractValue( "classname", className )) + { + Error( "classname missing from entity!\n" ); + } + + if ( !Q_strcmp( className, "worldspawn" ) ) + { + // Set up keyvalues. + ParseMapData( &entData ); + return; + } + } +} +#endif + C_World *GetClientWorldEntity() diff --git a/src/game/client/c_world.h b/src/game/client/c_world.h index e0951afe..b3ae8d6e 100644 --- a/src/game/client/c_world.h +++ b/src/game/client/c_world.h @@ -31,6 +31,7 @@ public: virtual void Precache(); virtual void Spawn(); + virtual bool KeyValue( const char *szKeyName, const char *szValue ); // Don't worry about adding the world to the collision list; it's already there virtual CollideType_t GetCollideType( void ) { return ENTITY_SHOULD_NOT_COLLIDE; } @@ -41,6 +42,19 @@ public: float GetWaveHeight() const; const char *GetDetailSpriteMaterial() const; +#ifdef MAPBASE + // A special function which parses map data for the client world entity before LevelInitPreEntity(). + // This can be used to access keyvalues early and without transmitting from the server. + void ParseWorldMapData( const char *pMapData ); +#endif + +#ifdef MAPBASE_VSCRIPT + void ClientThink() { ScriptContextThink(); } + + // -2 = Use server language + ScriptLanguage_t GetScriptLanguage() { return (ScriptLanguage_t)(m_iScriptLanguageClient != -2 ? m_iScriptLanguageClient : m_iScriptLanguageServer); } +#endif + public: enum { @@ -56,6 +70,13 @@ public: float m_flMinPropScreenSpaceWidth; float m_flMaxPropScreenSpaceWidth; bool m_bColdWorld; +#ifdef MAPBASE + char m_iszChapterTitle[64]; +#endif +#ifdef MAPBASE_VSCRIPT + int m_iScriptLanguageServer; + int m_iScriptLanguageClient; +#endif private: void RegisterSharedActivities( void ); diff --git a/src/game/client/cbase.h b/src/game/client/cbase.h index 57e3260b..53322ca4 100644 --- a/src/game/client/cbase.h +++ b/src/game/client/cbase.h @@ -37,6 +37,10 @@ struct studiohdr_t; #include #include +#ifdef MAPBASE +#include "tier1/mapbase_con_groups.h" +#endif + // This is a precompiled header. Include a bunch of common stuff. // This is kind of ugly in that it adds a bunch of dependency where it isn't needed. diff --git a/src/game/client/cdll_client_int.cpp b/src/game/client/cdll_client_int.cpp index 7b47d820..fa2b9262 100644 --- a/src/game/client/cdll_client_int.cpp +++ b/src/game/client/cdll_client_int.cpp @@ -151,6 +151,10 @@ #endif +#ifdef MAPBASE_VSCRIPT +#include "vscript_client.h" +#endif + extern vgui::IInputInternal *g_InputInternal; //============================================================================= @@ -218,6 +222,11 @@ IEngineReplay *g_pEngineReplay = NULL; IEngineClientReplay *g_pEngineClientReplay = NULL; IReplaySystem *g_pReplay = NULL; #endif +#ifdef MAPBASE +IVEngineServer *serverengine = NULL; +#endif + +IScriptManager *scriptmanager = NULL; IHaptics* haptics = NULL;// NVNT haptics system interface singleton @@ -269,6 +278,8 @@ void ProcessCacheUsedMaterials() } } +void VGui_ClearVideoPanels(); + // String tables INetworkStringTable *g_pStringTableParticleEffectNames = NULL; INetworkStringTable *g_StringTableEffectDispatch = NULL; @@ -341,6 +352,14 @@ static ConVar s_cl_load_hl1_content("cl_load_hl1_content", "0", FCVAR_ARCHIVE, " ConVar r_lightmap_bicubic_set( "r_lightmap_bicubic_set", "0", FCVAR_ARCHIVE | FCVAR_HIDDEN, "Hack to get this convar to be re-set on first launch." ); +#ifdef MAPBASE_RPC +// Mapbase stuff +extern void MapbaseRPC_Init(); +extern void MapbaseRPC_Shutdown(); +extern void MapbaseRPC_Update( int iType, const char *pMapName ); +#endif + + // Physics system bool g_bLevelInitialized; bool g_bTextMode = false; @@ -860,6 +879,7 @@ CHLClient::CHLClient() } + extern IGameSystem *ViewportClientSystem(); @@ -960,9 +980,30 @@ int CHLClient::Init( CreateInterfaceFn appSystemFactory, CreateInterfaceFn physi return false; #endif +#ifdef MAPBASE + // Implements the server engine interface on the client. + // I'm extremely confused as to how this is even possible, but Saul Rennison's worldlight did it. + // If it's really this possible, why wasn't it available before? + // Hopefully there's no SP-only magic going on here, because I want to use this for RPC. + if ( (serverengine = (IVEngineServer*)appSystemFactory(INTERFACEVERSION_VENGINESERVER, NULL )) == NULL ) + return false; +#endif + if (!g_pMatSystemSurface) return false; + if ( !CommandLine()->CheckParm( "-noscripting") ) + { +#ifndef MAPBASE_VSCRIPT // Mapbase VScript uses .lib + scriptmanager = (IScriptManager *)appSystemFactory( VSCRIPT_INTERFACE_VERSION, NULL ); +#endif + + if (scriptmanager == nullptr) + { + scriptmanager = (IScriptManager*)Sys_GetFactoryThis()(VSCRIPT_INTERFACE_VERSION, NULL); + } + } + // it's ok if this is NULL. That just means the sourcevr.dll wasn't found if ( CommandLine()->CheckParm( "-vr" ) ) @@ -1091,6 +1132,9 @@ int CHLClient::Init( CreateInterfaceFn appSystemFactory, CreateInterfaceFn physi g_pGameSaveRestoreBlockSet->AddBlockHandler( GetEntitySaveRestoreBlockHandler() ); g_pGameSaveRestoreBlockSet->AddBlockHandler( GetPhysSaveRestoreBlockHandler() ); g_pGameSaveRestoreBlockSet->AddBlockHandler( GetViewEffectsRestoreBlockHandler() ); +#ifdef MAPBASE_VSCRIPT + g_pGameSaveRestoreBlockSet->AddBlockHandler( GetVScriptSaveRestoreBlockHandler() ); +#endif ClientWorldFactoryInit(); @@ -1104,6 +1148,14 @@ int CHLClient::Init( CreateInterfaceFn appSystemFactory, CreateInterfaceFn physi HookHapticMessages(); // Always hook the messages #endif +#ifdef MAPBASE_RPC + MapbaseRPC_Init(); +#endif + +#ifdef MAPBASE + CommandLine()->AppendParm( "+r_hunkalloclightmaps", "0" ); +#endif + FnUnsafeCmdLineProcessor *pfnUnsafeCmdLineProcessor = #ifndef TF_CLIENT_DLL &UnsafeCmdLineProcessor; @@ -1211,12 +1263,17 @@ void CHLClient::Shutdown( void ) g_pSixenseInput = NULL; #endif + VGui_ClearVideoPanels(); + C_BaseAnimating::ShutdownBoneSetupThreadPool(); ClientWorldFactoryShutdown(); g_pGameSaveRestoreBlockSet->RemoveBlockHandler( GetViewEffectsRestoreBlockHandler() ); g_pGameSaveRestoreBlockSet->RemoveBlockHandler( GetPhysSaveRestoreBlockHandler() ); g_pGameSaveRestoreBlockSet->RemoveBlockHandler( GetEntitySaveRestoreBlockHandler() ); +#ifdef MAPBASE_VSCRIPT + g_pGameSaveRestoreBlockSet->RemoveBlockHandler( GetVScriptSaveRestoreBlockHandler() ); +#endif ClientVoiceMgr_Shutdown(); @@ -1245,6 +1302,10 @@ void CHLClient::Shutdown( void ) ClientSteamContext().Shutdown(); #endif + +#ifdef MAPBASE_RPC + MapbaseRPC_Shutdown(); +#endif // This call disconnects the VGui libraries which we rely on later in the shutdown path, so don't do it // DisconnectTier3Libraries( ); @@ -1630,6 +1691,10 @@ void CHLClient::LevelInitPreEntity( char const* pMapName ) tempents->LevelInit(); ResetToneMapping(1.0); +#ifdef MAPBASE + GetClientWorldEntity()->ParseWorldMapData( engine->GetMapEntitiesString() ); +#endif + IGameSystem::LevelInitPreEntityAllSystems(pMapName); #ifdef USES_ECON_ITEMS @@ -1661,6 +1726,13 @@ void CHLClient::LevelInitPreEntity( char const* pMapName ) } #endif +#ifdef MAPBASE_RPC + if (!g_bTextMode) + { + MapbaseRPC_Update(RPCSTATE_LEVEL_INIT, pMapName); + } +#endif + // Check low violence settings for this map g_RagdollLVManager.SetLowViolence( pMapName ); @@ -1752,6 +1824,13 @@ void CHLClient::LevelShutdown( void ) gHUD.LevelShutdown(); +#ifdef MAPBASE_RPC + if (!g_bTextMode) + { + MapbaseRPC_Update(RPCSTATE_LEVEL_SHUTDOWN, NULL); + } +#endif + internalCenterPrint->Clear(); messagechars->Clear(); @@ -2183,7 +2262,9 @@ void OnRenderStart() // are at the correct location view->OnRenderStart(); +#ifndef MAPBASE RopeManager()->OnRenderStart(); +#endif // This will place all entities in the correct position in world space and in the KD-tree C_BaseAnimating::UpdateClientSideAnimations(); diff --git a/src/game/client/cdll_client_int.h b/src/game/client/cdll_client_int.h index ce5da343..375684f6 100644 --- a/src/game/client/cdll_client_int.h +++ b/src/game/client/cdll_client_int.h @@ -110,6 +110,9 @@ extern IReplayManager *g_pReplayManager; extern IReplayScreenshotManager *g_pReplayScreenshotManager; extern IEngineReplay *g_pEngineReplay; extern IEngineClientReplay *g_pEngineClientReplay; +#ifdef MAPBASE +extern IVEngineServer *serverengine; +#endif //============================================================================= // HPE_BEGIN @@ -171,4 +174,16 @@ extern CSteamID GetSteamIDForPlayerIndex( int iPlayerIndex ); #endif +#ifdef MAPBASE +// Mapbase RPC stuff +enum +{ + RPCSTATE_INIT, + RPCSTATE_LEVEL_INIT, + RPCSTATE_LEVEL_SHUTDOWN, + + RPCSTATE_UPDATE, +}; +#endif + #endif // CDLL_CLIENT_INT_H diff --git a/src/game/client/cdll_util.cpp b/src/game/client/cdll_util.cpp index c003acd4..f5c24587 100644 --- a/src/game/client/cdll_util.cpp +++ b/src/game/client/cdll_util.cpp @@ -714,6 +714,24 @@ int UTIL_EntitiesAlongRay( C_BaseEntity **pList, int listMax, const Ray_t &ray, return rayEnum.GetCount(); } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: Pass in an array of pointers and an array size, it fills the array and returns the number inserted +// Input : **pList - +// listMax - +// &point - +// flagMask - +// Output : int +//----------------------------------------------------------------------------- +int UTIL_EntitiesAtPoint( C_BaseEntity **pList, int listMax, const Vector &point, int flagMask, int partitionMask ) +{ + CFlaggedEntitiesEnum rayEnum( pList, listMax, flagMask ); + partition->EnumerateElementsAtPoint( partitionMask, point, false, &rayEnum ); + + return rayEnum.GetCount(); +} +#endif + CEntitySphereQuery::CEntitySphereQuery( const Vector ¢er, float radius, int flagMask, int partitionMask ) { m_listIndex = 0; diff --git a/src/game/client/cdll_util.h b/src/game/client/cdll_util.h index f0d7736c..1950cea9 100644 --- a/src/game/client/cdll_util.h +++ b/src/game/client/cdll_util.h @@ -119,6 +119,9 @@ void ClientPrint( C_BasePlayer *player, int msg_dest, const char *msg_name, cons int UTIL_EntitiesInBox( C_BaseEntity **pList, int listMax, const Vector &mins, const Vector &maxs, int flagMask, int partitionMask = PARTITION_CLIENT_NON_STATIC_EDICTS ); int UTIL_EntitiesInSphere( C_BaseEntity **pList, int listMax, const Vector ¢er, float radius, int flagMask, int partitionMask = PARTITION_CLIENT_NON_STATIC_EDICTS ); int UTIL_EntitiesAlongRay( C_BaseEntity **pList, int listMax, const Ray_t &ray, int flagMask, int partitionMask = PARTITION_CLIENT_NON_STATIC_EDICTS ); +#ifdef MAPBASE +int UTIL_EntitiesAtPoint( C_BaseEntity **pList, int listMax, const Vector &point, int flagMask, int partitionMask = PARTITION_CLIENT_NON_STATIC_EDICTS ); +#endif // make this a fixed size so it just sits on the stack #define MAX_SPHERE_QUERY 256 @@ -161,7 +164,12 @@ T *_CreateEntity( T *newClass, const char *className ) // Misc useful inline bool FStrEq(const char *sz1, const char *sz2) { - return (sz1 == sz2 || V_stricmp(sz1, sz2) == 0); +#ifdef MAPBASE + // V_stricmp() already checks if the pointers are equal, so having a comparison here is pointless. + return ( V_stricmp(sz1, sz2) == 0 ); +#else + return ( sz1 == sz2 || V_stricmp(sz1, sz2) == 0 ); +#endif } // Given a vector, clamps the scalar axes to MAX_COORD_FLOAT ranges from worldsize.h diff --git a/src/game/client/client_base.vpc b/src/game/client/client_base.vpc index 08744c56..1ee0d6b8 100644 --- a/src/game/client/client_base.vpc +++ b/src/game/client/client_base.vpc @@ -18,6 +18,9 @@ $include "$SRCDIR\vpc_scripts\protobuf_builder.vpc" $Include "$SRCDIR\vpc_scripts\source_replay.vpc" [$TF] $Include "$SRCDIR\game\protobuf_include.vpc" +// Mapbase stuff +$Include "$SRCDIR\game\client\client_mapbase.vpc" [$MAPBASE] + $Configuration "Debug" { $General @@ -499,6 +502,11 @@ $Project $File "viewrender.cpp" $File "$SRCDIR\game\shared\voice_banmgr.cpp" $File "$SRCDIR\game\shared\voice_status.cpp" + $File "vscript_client.cpp" + $File "vscript_client.h" + $File "vscript_client.nut" + $File "$SRCDIR\game\shared\vscript_shared.cpp" + $File "$SRCDIR\game\shared\vscript_shared.h" $File "warp_overlay.cpp" $File "WaterLODMaterialProxy.cpp" $File "$SRCDIR\game\shared\weapon_parse.cpp" @@ -540,7 +548,6 @@ $Project "$SRCDIR\public\dt_utlvector_recv.cpp" \ "$SRCDIR\public\filesystem_helpers.cpp" \ "$SRCDIR\public\interpolatortypes.cpp" \ - "$SRCDIR\game\shared\interval.cpp" \ "$SRCDIR\common\language.cpp" \ "$SRCDIR\public\networkvar.cpp" \ "$SRCDIR\common\randoverride.cpp" \ @@ -1114,6 +1121,7 @@ $Project $File "$SRCDIR\public\vgui_controls\WizardSubPanel.h" $File "$SRCDIR\public\worldsize.h" $File "$SRCDIR\public\zip_uncompressed.h" + $File "$SRCDIR\public\tier1\interval.h" //Haptics $File "$SRCDIR\public\haptics\ihaptics.h" [$WIN32] $File "$SRCDIR\public\haptics\haptic_utils.h" [$WIN32] @@ -1170,7 +1178,6 @@ $Project $File "$SRCDIR\game\shared\igamesystem.h" $File "$SRCDIR\game\shared\imovehelper.h" $File "$SRCDIR\game\shared\in_buttons.h" - $File "$SRCDIR\game\shared\interval.h" $File "$SRCDIR\game\shared\iplayeranimstate.h" $File "$SRCDIR\game\shared\ipredictionsystem.h" $File "$SRCDIR\game\shared\itempents.h" @@ -1266,6 +1273,9 @@ $Project $Lib tier3 $Lib vgui_controls $Lib vtf + + // Discord integration + $Lib "$LIBPUBLIC\discord-rpc" [$MAPBASE_RPC&&!$LINUXALL] $ImpLibexternal steam_api [!$WIN64] $ImpLibexternal steam_api64 [$WIN64] diff --git a/src/game/client/client_episodic.vpc b/src/game/client/client_episodic.vpc index 5c89d940..3c6f20dd 100644 --- a/src/game/client/client_episodic.vpc +++ b/src/game/client/client_episodic.vpc @@ -79,7 +79,7 @@ $Project "Client (Episodic)" $File "hl2\c_weapon_crossbow.cpp" $File "episodic\c_weapon_hopwire.cpp" $File "hl2\c_weapon_physcannon.cpp" - $File "hl2\c_weapon_stunstick.cpp" + $File "hl2\c_weapon_stunstick.cpp" [!$MAPBASE] // See client_mapbase.vpc $File "$SRCDIR\game\shared\hl2\citadel_effects_shared.h" $File "hl2\clientmode_hlnormal.cpp" $File "hl2\clientmode_hlnormal.h" diff --git a/src/game/client/client_hl2.vpc b/src/game/client/client_hl2.vpc index db44981b..b8f6f0ef 100644 --- a/src/game/client/client_hl2.vpc +++ b/src/game/client/client_hl2.vpc @@ -74,7 +74,7 @@ $Project "Client (HL2)" $File "hl2\c_weapon__stubs_hl2.cpp" $File "hl2\c_weapon_crossbow.cpp" $File "hl2\c_weapon_physcannon.cpp" - $File "hl2\c_weapon_stunstick.cpp" + $File "hl2\c_weapon_stunstick.cpp" [!$MAPBASE] // See client_mapbase.vpc $File "$SRCDIR\game\shared\hl2\citadel_effects_shared.h" $File "hl2\clientmode_hlnormal.cpp" $File "hl2\clientmode_hlnormal.h" diff --git a/src/game/client/client_mapbase.vpc b/src/game/client/client_mapbase.vpc new file mode 100644 index 00000000..7b3b28f3 --- /dev/null +++ b/src/game/client/client_mapbase.vpc @@ -0,0 +1,98 @@ +//----------------------------------------------------------------------------- +// CLIENT_MAPBASE.VPC +// +// Project Script +//----------------------------------------------------------------------------- + +$Configuration +{ + $Compiler + { + $PreprocessorDefinitions "$BASE;ASW_PROJECTED_TEXTURES;DYNAMIC_RTT_SHADOWS;GLOWS_ENABLE" + + $PreprocessorDefinitions "$BASE;MAPBASE_RPC;DISCORD_RPC;STEAM_RPC" [$MAPBASE_RPC&&!$LINUXALL] + $PreprocessorDefinitions "$BASE;MAPBASE_VSCRIPT" [$MAPBASE_VSCRIPT] + $PreprocessorDefinitions "$BASE;NEW_RESPONSE_SYSTEM" [$NEW_RESPONSE_SYSTEM] + } +} + +$Project +{ + $Folder "Source Files" + { + $File "c_env_global_light.cpp" + $File "worldlight.cpp" + $File "worldlight.h" + $File "c_baselesson.cpp" + $File "c_baselesson.h" + $File "c_gameinstructor.cpp" + $File "c_gameinstructor.h" + $File "hud_locator_target.cpp" + $File "hud_locator_target.h" + $File "c_postprocesscontroller.cpp" + $File "c_postprocesscontroller.h" + $File "c_env_dof_controller.cpp" + $File "c_movie_display.cpp" + $File "c_movie_display.h" + $File "vgui_movie_display.cpp" + $File "convarproxy.cpp" + + $Folder "Mapbase" + { + $File "$SRCDIR\game\shared\mapbase\mapbase_shared.cpp" + $File "$SRCDIR\game\shared\mapbase\mapbase_usermessages.cpp" + $File "$SRCDIR\game\shared\mapbase\mapbase_rpc.cpp" + $File "$SRCDIR\game\shared\mapbase\mapbase_game_log.cpp" + $File "$SRCDIR\game\shared\mapbase\MapEdit.cpp" + $File "$SRCDIR\game\shared\mapbase\MapEdit.h" + $File "$SRCDIR\game\shared\mapbase\matchers.cpp" + $File "$SRCDIR\game\shared\mapbase\matchers.h" + $File "$SRCDIR\game\shared\mapbase\singleplayer_animstate.cpp" + $File "$SRCDIR\game\shared\mapbase\singleplayer_animstate.h" + $File "$SRCDIR\game\shared\mapbase\vscript_funcs_shared.cpp" [$MAPBASE_VSCRIPT] + $File "$SRCDIR\game\shared\mapbase\vscript_funcs_shared.h" [$MAPBASE_VSCRIPT] + $File "$SRCDIR\game\shared\mapbase\vscript_singletons.cpp" [$MAPBASE_VSCRIPT] + $File "$SRCDIR\game\shared\mapbase\vscript_singletons.h" [$MAPBASE_VSCRIPT] + $File "$SRCDIR\game\shared\mapbase\vscript_funcs_hl2.cpp" [$MAPBASE_VSCRIPT] + $File "$SRCDIR\game\shared\mapbase\vscript_consts_shared.cpp" [$MAPBASE_VSCRIPT] + $File "$SRCDIR\game\shared\mapbase\vscript_consts_weapons.cpp" [$MAPBASE_VSCRIPT] + $File "$SRCDIR\game\shared\mapbase\weapon_custom_scripted.cpp" [$MAPBASE_VSCRIPT] + $File "$SRCDIR\game\shared\mapbase\weapon_custom_scripted.h" [$MAPBASE_VSCRIPT] + $File "$SRCDIR\game\shared\mapbase\logic_script_client.cpp" [$MAPBASE_VSCRIPT] + + $File "mapbase\vscript_vgui.cpp" [$MAPBASE_VSCRIPT] + $File "mapbase\vscript_vgui.h" [$MAPBASE_VSCRIPT] + $File "mapbase\vscript_vgui.nut" [$MAPBASE_VSCRIPT] + + $File "mapbase\c_func_clientclip.cpp" + $File "mapbase\c_func_fake_worldportal.cpp" + $File "mapbase\c_func_fake_worldportal.h" + $File "mapbase\c_point_glow.cpp" + $File "mapbase\c_vgui_text_display.cpp" + $File "mapbase\c_weapon_custom_hl2.cpp" + $File "mapbase\mapbase_autocubemap.cpp" + } + + $Folder "HL2 DLL" + { + // Original stunstick files are conditional'd out in the HL2 VPCs + $File "$SRCDIR\game\shared\hl2mp\weapon_stunstick.cpp" + $File "$SRCDIR\game\shared\hl2mp\weapon_stunstick.h" + } + + $Folder "HL2MP" + { + $Folder "Weapons" + { + $File "$SRCDIR\game\shared\hl2mp\weapon_slam.cpp" + $File "$SRCDIR\game\shared\hl2mp\weapon_slam.h" + } + } + } + + $Folder "Link Libraries" + { + $Lib "vscript" [$MAPBASE_VSCRIPT] + $Lib "raytrace" + } +} diff --git a/src/game/client/clientleafsystem.cpp b/src/game/client/clientleafsystem.cpp index 57bb1872..af86c316 100644 --- a/src/game/client/clientleafsystem.cpp +++ b/src/game/client/clientleafsystem.cpp @@ -1501,10 +1501,12 @@ inline void AddRenderableToRenderList( CClientRenderablesList &renderList, IClie pEntry->m_RenderHandle = renderHandle; curCount++; } +#ifndef MAPBASE // According to ficool2, this message can cause significant lag else { engine->Con_NPrintf( 10, "Warning: overflowed CClientRenderablesList group %d", group ); } +#endif } diff --git a/src/game/client/clientleafsystem.h b/src/game/client/clientleafsystem.h index dd48ec0b..5caa8914 100644 --- a/src/game/client/clientleafsystem.h +++ b/src/game/client/clientleafsystem.h @@ -52,7 +52,11 @@ class CClientRenderablesList : public CRefCounted<> public: enum { +#ifdef MAPBASE + MAX_GROUP_ENTITIES = 16834 // According to ficool2, this limit is bogus/not enforced by the engine and can be "safely" raised. +#else MAX_GROUP_ENTITIES = 4096 +#endif }; struct CEntry diff --git a/src/game/client/clientmode_shared.cpp b/src/game/client/clientmode_shared.cpp index 7fd78629..5f169e92 100644 --- a/src/game/client/clientmode_shared.cpp +++ b/src/game/client/clientmode_shared.cpp @@ -36,6 +36,7 @@ #include #include "hud_vote.h" #include "ienginevgui.h" +#include "viewpostprocess.h" #include "sourcevr/isourcevirtualreality.h" #if defined( _X360 ) #include "xbox/xbox_console.h" @@ -67,6 +68,10 @@ extern ConVar replay_rendersetting_renderglow; #include "c_tf_team.h" #endif +#ifdef GLOWS_ENABLE +#include "clienteffectprecachesystem.h" +#endif + // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" @@ -79,6 +84,9 @@ class CHudVote; static vgui::HContext s_hVGuiContext = DEFAULT_VGUI_CONTEXT; ConVar cl_drawhud( "cl_drawhud", "1", FCVAR_CHEAT, "Enable the rendering of the hud" ); +#ifdef DEMO_AUTORECORD +ConVar cl_autorecord("cl_autorecord", "0", FCVAR_CLIENTDLL | FCVAR_ARCHIVE, "Start recording demos automatically with an incremental name based on this value."); +#endif ConVar hud_takesshots( "hud_takesshots", "0", FCVAR_CLIENTDLL | FCVAR_ARCHIVE, "Auto-save a scoreboard screenshot at the end of a map." ); ConVar hud_freezecamhide( "hud_freezecamhide", "0", FCVAR_CLIENTDLL | FCVAR_ARCHIVE, "Hide the HUD during freeze-cam" ); ConVar cl_show_num_particle_systems( "cl_show_num_particle_systems", "0", FCVAR_CLIENTDLL, "Display the number of active particle systems." ); @@ -95,6 +103,13 @@ CON_COMMAND( cl_reload_localization_files, "Reloads all localization files" ) g_pVGuiLocalize->ReloadLocalizationFiles(); } +#ifdef GLOWS_ENABLE +CLIENTEFFECT_REGISTER_BEGIN( PrecachePostProcessingEffectsGlow ) +CLIENTEFFECT_MATERIAL( "dev/glow_color" ) +CLIENTEFFECT_MATERIAL( "dev/halo_add_to_screen" ) +CLIENTEFFECT_REGISTER_END_CONDITIONAL( engine->GetDXSupportLevel() >= 90 ) +#endif + #ifdef VOICE_VOX_ENABLE void VoxCallback( IConVar *var, const char *oldString, float oldFloat ) { @@ -293,6 +308,12 @@ ClientModeShared::ClientModeShared() m_pWeaponSelection = NULL; m_nRootSize[ 0 ] = m_nRootSize[ 1 ] = -1; +#ifdef MAPBASE // From Alien Swarm SDK + m_pCurrentPostProcessController = NULL; + m_PostProcessLerpTimer.Invalidate(); + m_pCurrentColorCorrection = NULL; +#endif + #if defined( REPLAY_ENABLED ) m_pReplayReminderPanel = NULL; m_flReplayStartRecordTime = 0.0f; @@ -622,6 +643,8 @@ void ClientModeShared::Update() m_pViewport->SetVisible( cl_drawhud.GetBool() ); } + UpdatePostProcessingEffects(); + UpdateRumbleEffects(); if ( cl_show_num_particle_systems.GetBool() ) @@ -647,6 +670,43 @@ void ClientModeShared::Update() } } +#ifdef MAPBASE // From Alien Swarm SDK +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ClientModeShared::OnColorCorrectionWeightsReset( void ) +{ + C_ColorCorrection *pNewColorCorrection = NULL; + C_ColorCorrection *pOldColorCorrection = m_pCurrentColorCorrection; + C_BasePlayer* pPlayer = C_BasePlayer::GetLocalPlayer(); + if ( pPlayer ) + { + pNewColorCorrection = pPlayer->GetActiveColorCorrection(); + } + + if ( pNewColorCorrection != pOldColorCorrection ) + { + if ( pOldColorCorrection ) + { + pOldColorCorrection->EnableOnClient( false ); + } + if ( pNewColorCorrection ) + { + pNewColorCorrection->EnableOnClient( true, pOldColorCorrection == NULL ); + } + m_pCurrentColorCorrection = pNewColorCorrection; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +float ClientModeShared::GetColorCorrectionScale( void ) const +{ + return 1.0f; +} +#endif + //----------------------------------------------------------------------------- // This processes all input before SV Move messages are sent //----------------------------------------------------------------------------- @@ -803,6 +863,10 @@ int ClientModeShared::HudElementKeyInput( int down, ButtonCode_t keynum, const c //----------------------------------------------------------------------------- bool ClientModeShared::DoPostScreenSpaceEffects( const CViewSetup *pSetup ) { +#ifdef GLOWS_ENABLE + g_GlowObjectManager.RenderGlowEffects( pSetup, 0 ); +#endif + #if defined( REPLAY_ENABLED ) if ( engine->IsPlayingDemo() ) { @@ -894,8 +958,54 @@ void ClientModeShared::LevelInit( const char *newmap ) // Reset any player explosion/shock effects CLocalPlayerFilter filter; enginesound->SetPlayerDSP( filter, 0, true ); + +#ifdef DEMO_AUTORECORD + AutoRecord(newmap); +#endif } +#ifdef DEMO_AUTORECORD +void ClientModeShared::AutoRecord(const char *map) +{ + if (!cl_autorecord.GetBool()) { + return; + } + + if (map == nullptr) { + Warning("null map in ClientModeShared::AutoRecord"); + return; + } + + // stop any demo to make sure they're saved + engine->ClientCmd("stop"); + + // KLEMS: sanitize space in client name because having to type "" while playing back lots of demos is annoying + ConVarRef name("name"); + char nameStr[128]; + memset(nameStr, 0, sizeof(nameStr)); + Q_snprintf(nameStr, sizeof(nameStr), "%s", name.GetString()); + int i = 0; + while (nameStr[i]) { + char c = nameStr[i]; + if (!( (c >= '0' && c <= '9') || + (c >= 'a' && c <= 'z') || + (c >= 'A' && c <= 'Z'))) { + nameStr[i] = '_'; + } + i++; + } + nameStr[127] = 0; + + char cmd[256]; + Q_snprintf(cmd, sizeof(cmd), "record \"%s_%04d_%s\"", nameStr, cl_autorecord.GetInt(), map); + cl_autorecord.SetValue(cl_autorecord.GetInt() + 1); + engine->ClientCmd(cmd); + + // write to config to make sure the cvar is recorded + engine->ClientCmd("host_writeconfig"); +} +#endif + //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- @@ -914,6 +1024,17 @@ void ClientModeShared::LevelShutdown( void ) s_hVGuiContext = DEFAULT_VGUI_CONTEXT; } +#ifdef MAPBASE + // Always reset post-processing on level unload + //if (m_pCurrentPostProcessController) + { + m_CurrentPostProcessParameters = PostProcessParameters_t(); + m_LerpEndPostProcessParameters = PostProcessParameters_t(); + m_pCurrentPostProcessController = NULL; + SetPostProcessParams( &m_CurrentPostProcessParameters ); + } +#endif + // Reset any player explosion/shock effects CLocalPlayerFilter filter; enginesound->SetPlayerDSP( filter, 0, true ); @@ -988,6 +1109,69 @@ float ClientModeShared::GetViewModelFOV( void ) return v_viewmodel_fov.GetFloat(); } +#ifdef MAPBASE +extern bool g_bPostProcessNeedsRestore; +#endif + +void ClientModeShared::UpdatePostProcessingEffects() +{ + C_PostProcessController* pNewPostProcessController = NULL; + C_BasePlayer* pPlayer = C_BasePlayer::GetLocalPlayer(); + + if (pPlayer) + pNewPostProcessController = pPlayer->GetActivePostProcessController(); + + if (!pNewPostProcessController) + { + m_CurrentPostProcessParameters = PostProcessParameters_t(); + m_pCurrentPostProcessController = NULL; + SetPostProcessParams( &m_CurrentPostProcessParameters ); + return; + } + + if (pNewPostProcessController != m_pCurrentPostProcessController) + m_pCurrentPostProcessController = pNewPostProcessController; + + // Start a lerp timer if the parameters changed, regardless of whether the controller changed + if (m_LerpEndPostProcessParameters != pNewPostProcessController->m_PostProcessParameters) + { + m_LerpStartPostProcessParameters = m_CurrentPostProcessParameters; + m_LerpEndPostProcessParameters = pNewPostProcessController ? pNewPostProcessController->m_PostProcessParameters : m_CurrentPostProcessParameters; + + float flFadeTime = pNewPostProcessController ? pNewPostProcessController->m_PostProcessParameters.m_flParameters[PPPN_FADE_TIME] : 0.0f; + if (flFadeTime <= 0.0f) + { + flFadeTime = 0.001f; + } + + m_PostProcessLerpTimer.Start( flFadeTime ); + } +#ifdef MAPBASE + // HACKHACK: Needs to be checked here because OnRestore() doesn't seem to run before a lerp begins + else if (g_bPostProcessNeedsRestore) + { + // The player just loaded a saved game. + // Don't fade parameters from 0; instead, take what's already there and assume they were already active. + // (we have no way of knowing if they were in the middle of a lerp) + m_PostProcessLerpTimer.Invalidate(); + g_bPostProcessNeedsRestore = false; + } +#endif + + // Lerp between old and new parameters + float flLerpFactor = 1.0f - m_PostProcessLerpTimer.GetRemainingRatio(); + for (int nParameter = 0; nParameter < POST_PROCESS_PARAMETER_COUNT; ++nParameter) + { + m_CurrentPostProcessParameters.m_flParameters[nParameter] = + Lerp( + flLerpFactor, + m_LerpStartPostProcessParameters.m_flParameters[nParameter], + m_LerpEndPostProcessParameters.m_flParameters[nParameter] ); + } + + SetPostProcessParams( &m_CurrentPostProcessParameters ); +} + class CHudChat; bool PlayerNameNotSetYet( const char *pszName ) diff --git a/src/game/client/clientmode_shared.h b/src/game/client/clientmode_shared.h index 4d82c719..a1e31554 100644 --- a/src/game/client/clientmode_shared.h +++ b/src/game/client/clientmode_shared.h @@ -42,6 +42,10 @@ class CReplayReminderPanel; #define USERID2PLAYER(i) ToBasePlayer( ClientEntityList().GetEnt( engine->GetPlayerForUserID( i ) ) ) +#ifdef MAPBASE +#define DEMO_AUTORECORD 1 +#endif + extern IClientMode *GetClientModeNormal(); // must be implemented // This class implements client mode functionality common to HL2 and TF2. @@ -62,6 +66,10 @@ public: virtual void LevelInit( const char *newmap ); virtual void LevelShutdown( void ); +#ifdef DEMO_AUTORECORD + virtual void AutoRecord( const char *map ); +#endif + virtual void Enable(); virtual void Disable(); virtual void Layout(); @@ -83,6 +91,11 @@ public: virtual void ProcessInput(bool bActive); virtual bool CreateMove( float flInputSampleTime, CUserCmd *cmd ); virtual void Update(); +#ifdef MAPBASE // From Alien Swarm SDK + virtual void OnColorCorrectionWeightsReset( void ); + virtual float GetColorCorrectionScale( void ) const; + virtual void ClearCurrentColorCorrection() { m_pCurrentColorCorrection = NULL; } +#endif // Input virtual int KeyInput( int down, ButtonCode_t keynum, const char *pszCurrentBinding ); @@ -163,6 +176,17 @@ private: vgui::HCursor m_CursorNone; CBaseHudWeaponSelection *m_pWeaponSelection; int m_nRootSize[2]; + +#ifdef MAPBASE // From Alien Swarm SDK + void UpdatePostProcessingEffects(); + + const C_PostProcessController* m_pCurrentPostProcessController; + PostProcessParameters_t m_CurrentPostProcessParameters; + PostProcessParameters_t m_LerpStartPostProcessParameters, m_LerpEndPostProcessParameters; + CountdownTimer m_PostProcessLerpTimer; + + CHandle m_pCurrentColorCorrection; +#endif }; #endif // CLIENTMODE_NORMAL_H diff --git a/src/game/client/clientshadowmgr.cpp b/src/game/client/clientshadowmgr.cpp index 8daa9f65..d95ade3a 100644 --- a/src/game/client/clientshadowmgr.cpp +++ b/src/game/client/clientshadowmgr.cpp @@ -81,6 +81,16 @@ #include "toolframework_client.h" #include "bonetoworldarray.h" #include "cmodel.h" +#ifdef MAPBASE +#include "renderparm.h" +#endif +#ifdef ASW_PROJECTED_TEXTURES +#include "flashlighteffect.h" +#endif +#ifdef DYNAMIC_RTT_SHADOWS +#include "debugoverlay_shared.h" +#include "worldlight.h" +#endif // memdbgon must be the last include file in a .cpp file!!! @@ -89,6 +99,12 @@ static ConVar r_flashlightdrawfrustum( "r_flashlightdrawfrustum", "0" ); static ConVar r_flashlightmodels( "r_flashlightmodels", "1" ); static ConVar r_shadowrendertotexture( "r_shadowrendertotexture", "0" ); +#ifdef ASW_PROJECTED_TEXTURES +static ConVar r_shadow_lightpos_lerptime( "r_shadow_lightpos_lerptime", "0.5" ); +static ConVar r_shadowfromworldlights_debug( "r_shadowfromworldlights_debug", "0", FCVAR_CHEAT ); +static ConVar r_shadow_shortenfactor( "r_shadow_shortenfactor", "2" , 0, "Makes shadows cast from local lights shorter" ); +static ConVar r_shadow_mincastintensity( "r_shadow_mincastintensity", "0.3", FCVAR_CHEAT, "Minimum brightness of a light to be classed as shadow casting", true, 0, false, 0 ); +#endif static ConVar r_flashlight_version2( "r_flashlight_version2", "0", FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY ); ConVar r_flashlightdepthtexture( "r_flashlightdepthtexture", "1", FCVAR_ALLOWED_IN_COMPETITIVE ); @@ -96,10 +112,23 @@ ConVar r_flashlightdepthtexture( "r_flashlightdepthtexture", "1", FCVAR_ALLOWED_ #if defined( _X360 ) ConVar r_flashlightdepthres( "r_flashlightdepthres", "512" ); #else +#ifdef MAPBASE +ConVar r_flashlightdepthres( "r_flashlightdepthres", "2048" ); +#else ConVar r_flashlightdepthres( "r_flashlightdepthres", "1024" ); #endif +#endif +#ifdef ASW_PROJECTED_TEXTURES +ConVar r_threaded_client_shadow_manager( "r_threaded_client_shadow_manager", "1" ); +#else ConVar r_threaded_client_shadow_manager( "r_threaded_client_shadow_manager", "0" ); +#endif + +#ifdef MAPBASE +ConVarRef mat_slopescaledepthbias_shadowmap( "mat_slopescaledepthbias_shadowmap" ); +ConVarRef mat_depthbias_shadowmap( "mat_depthbias_shadowmap" ); +#endif #ifdef _WIN32 #pragma warning( disable: 4701 ) @@ -782,7 +811,11 @@ public: void RestoreRenderState(); // Computes a rough bounding box encompassing the volume of the shadow +#ifdef DYNAMIC_RTT_SHADOWS + void ComputeShadowBBox( IClientRenderable *pRenderable, ClientShadowHandle_t shadowHandle, const Vector &vecAbsCenter, float flRadius, Vector *pAbsMins, Vector *pAbsMaxs ); +#else void ComputeShadowBBox( IClientRenderable *pRenderable, const Vector &vecAbsCenter, float flRadius, Vector *pAbsMins, Vector *pAbsMaxs ); +#endif bool WillParentRenderBlobbyShadow( IClientRenderable *pRenderable ); @@ -794,6 +827,13 @@ public: r_shadows_gamecontrol.SetValue( bDisabled != 1 ); } +#ifdef DYNAMIC_RTT_SHADOWS + // Toggle shadow casting from world light sources + virtual void SetShadowFromWorldLightsEnabled( bool bEnable ); + void SuppressShadowFromWorldLights( bool bSuppress ); + bool IsShadowingFromWorldLights() const { return m_bShadowFromWorldLights && !m_bSuppressShadowFromWorldLights; } +#endif + private: enum { @@ -811,8 +851,16 @@ private: unsigned short m_Flags; VMatrix m_WorldToShadow; Vector2D m_WorldSize; +#ifdef DYNAMIC_RTT_SHADOWS + Vector m_ShadowDir; +#endif Vector m_LastOrigin; QAngle m_LastAngles; +#ifdef DYNAMIC_RTT_SHADOWS + Vector m_CurrentLightPos; // When shadowing from local lights, stores the position of the currently shadowing light + Vector m_TargetLightPos; // When shadowing from local lights, stores the position of the new shadowing light + float m_LightPosLerp; // Lerp progress when going from current to target light +#endif TextureHandle_t m_ShadowTexture; CTextureReference m_ShadowDepthTexture; int m_nRenderFrame; @@ -825,6 +873,11 @@ private: void UpdateBrushShadow( IClientRenderable *pRenderable, ClientShadowHandle_t handle ); void UpdateShadow( ClientShadowHandle_t handle, bool force ); +#ifdef DYNAMIC_RTT_SHADOWS + // Updates shadow cast direction when shadowing from world lights + void UpdateShadowDirectionFromLocalLightSource( ClientShadowHandle_t shadowHandle ); +#endif + // Gets the entity whose shadow this shadow will render into IClientRenderable *GetParentShadowEntity( ClientShadowHandle_t handle ); @@ -842,6 +895,10 @@ private: void BuildPerspectiveWorldToFlashlightMatrix( VMatrix& matWorldToShadow, const FlashlightState_t &flashlightState ); +#ifdef ASW_PROJECTED_TEXTURES + void BuildOrthoWorldToFlashlightMatrix( VMatrix& matWorldToShadow, const FlashlightState_t &flashlightState ); +#endif + // Update a shadow void UpdateProjectedTextureInternal( ClientShadowHandle_t handle, bool force ); @@ -910,6 +967,9 @@ private: // Returns renderable-specific shadow info float GetShadowDistance( IClientRenderable *pRenderable ) const; const Vector &GetShadowDirection( IClientRenderable *pRenderable ) const; +#ifdef DYNAMIC_RTT_SHADOWS + const Vector &GetShadowDirection( ClientShadowHandle_t shadowHandle ) const; +#endif // Initialize, shutdown render-to-texture shadows void InitDepthTextureShadows(); @@ -933,6 +993,11 @@ private: // Set and clear flashlight target renderable void SetFlashlightTarget( ClientShadowHandle_t shadowHandle, EHANDLE targetEntity ); +#ifdef ASW_PROJECTED_TEXTURES + // Get current frustum extents + void GetFrustumExtents( ClientShadowHandle_t handle, Vector &vecMin, Vector &vecMax ); +#endif + // Set flashlight light world flag void SetFlashlightLightWorld( ClientShadowHandle_t shadowHandle, bool bLightWorld ); @@ -941,9 +1006,18 @@ private: // Builds a list of active shadows requiring shadow depth renders int BuildActiveShadowDepthList( const CViewSetup &viewSetup, int nMaxDepthShadows, ClientShadowHandle_t *pActiveDepthShadows ); +#ifdef ASW_PROJECTED_TEXTURES + // Builds a list of active flashlights + int BuildActiveFlashlightList( const CViewSetup &viewSetup, int nMaxFlashlights, ClientShadowHandle_t *pActiveFlashlights ); +#endif + // Sets the view's active flashlight render state void SetViewFlashlightState( int nActiveFlashlightCount, ClientShadowHandle_t* pActiveFlashlights ); +#ifdef DYNAMIC_RTT_SHADOWS + void UpdateDirtyShadow( ClientShadowHandle_t handle ); +#endif + private: Vector m_SimpleShadowDir; color32 m_AmbientLightColor; @@ -963,6 +1037,10 @@ private: CUtlRBTree< ClientShadowHandle_t, unsigned short > m_DirtyShadows; CUtlVector< ClientShadowHandle_t > m_TransparentShadows; +#ifdef ASW_PROJECTED_TEXTURES + int m_nPrevFrameCount; +#endif + // These members maintain current state of depth texturing (size and global active state) // If either changes in a frame, PreRender() will catch it and do the appropriate allocation, deallocation or reallocation bool m_bDepthTextureActive; @@ -972,6 +1050,11 @@ private: CUtlVector< bool > m_DepthTextureCacheLocks; int m_nMaxDepthTextureShadows; +#ifdef DYNAMIC_RTT_SHADOWS + bool m_bShadowFromWorldLights; + bool m_bSuppressShadowFromWorldLights; +#endif + friend class CVisibleShadowList; friend class CVisibleShadowFrustumList; }; @@ -1064,6 +1147,12 @@ void CVisibleShadowList::EnumShadow( unsigned short clientShadowHandle ) if ( shadow.m_nRenderFrame == gpGlobals->framecount ) return; +#ifdef ASW_PROJECTED_TEXTURES + // Don't bother with flashlights + if ( ( shadow.m_Flags & SHADOW_FLAGS_FLASHLIGHT ) != 0 ) + return; +#endif + // We don't need to bother with it if it's not render-to-texture if ( s_ClientShadowMgr.GetActualShadowCastType( clientShadowHandle ) != SHADOWS_RENDER_TO_TEXTURE ) return; @@ -1088,7 +1177,11 @@ void CVisibleShadowList::EnumShadow( unsigned short clientShadowHandle ) // Compute a box surrounding the shadow Vector vecAbsMins, vecAbsMaxs; +#ifdef DYNAMIC_RTT_SHADOWS + s_ClientShadowMgr.ComputeShadowBBox( pRenderable, shadow.m_ShadowHandle, vecAbsCenter, flRadius, &vecAbsMins, &vecAbsMaxs ); +#else s_ClientShadowMgr.ComputeShadowBBox( pRenderable, vecAbsCenter, flRadius, &vecAbsMins, &vecAbsMaxs ); +#endif // FIXME: Add distance check here? @@ -1167,7 +1260,14 @@ int CVisibleShadowList::FindShadows( const CViewSetup *pView, int nLeafCount, Le //----------------------------------------------------------------------------- CClientShadowMgr::CClientShadowMgr() : m_DirtyShadows( 0, 0, ShadowHandleCompareFunc ), +#ifdef ASW_PROJECTED_TEXTURES + m_nPrevFrameCount( -1 ), +#endif m_RenderToTextureActive( false ), +#ifdef DYNAMIC_RTT_SHADOWS + m_bShadowFromWorldLights( false ), + m_bSuppressShadowFromWorldLights( false ), +#endif m_bDepthTextureActive( false ) { m_nDepthTextureResolution = r_flashlightdepthres.GetInt(); @@ -1271,6 +1371,15 @@ CON_COMMAND_F( r_shadowblobbycutoff, "some shadow stuff", FCVAR_CHEAT ) } } +#ifdef DYNAMIC_RTT_SHADOWS +void OnShadowFromWorldLights( IConVar *var, const char *pOldValue, float flOldValue ); +static ConVar r_shadowfromworldlights( "r_shadowfromworldlights", "1", FCVAR_NONE, "Enable shadowing from world lights", OnShadowFromWorldLights ); +void OnShadowFromWorldLights( IConVar *var, const char *pOldValue, float flOldValue ) +{ + s_ClientShadowMgr.SuppressShadowFromWorldLights( !r_shadowfromworldlights.GetBool() ); +} +#endif + static void ShadowRestoreFunc( int nChangeFlags ) { s_ClientShadowMgr.RestoreRenderState(); @@ -1290,8 +1399,14 @@ bool CClientShadowMgr::Init() SetShadowBlobbyCutoffArea( 0.005 ); +#ifndef MAPBASE bool bTools = CommandLine()->CheckParm( "-tools" ) != NULL; m_nMaxDepthTextureShadows = bTools ? 4 : 1; // Just one shadow depth texture in games, more in tools +#else + // 5 lets mappers use up to 4 shadow-casting projected textures, which is better than 3. + int iNumShadows = CommandLine()->ParmValue( "-numshadowtextures", 5 ); + m_nMaxDepthTextureShadows = iNumShadows; +#endif bool bLowEnd = ( g_pMaterialSystemHardwareConfig->GetDXSupportLevel() < 80 ); @@ -1314,6 +1429,15 @@ bool CClientShadowMgr::Init() materials->AddRestoreFunc( ShadowRestoreFunc ); +#ifdef MAPBASE + // These need to be referenced here since the cvars don't exist in the initial declaration + mat_slopescaledepthbias_shadowmap = ConVarRef( "mat_slopescaledepthbias_shadowmap" ); + mat_depthbias_shadowmap = ConVarRef( "mat_depthbias_shadowmap" ); + + mat_slopescaledepthbias_shadowmap.SetValue( "16" ); // Would do something like 2 here, but it causes citizens to look weird under flashlights + mat_depthbias_shadowmap.SetValue( "0.00005" ); +#endif + return true; } @@ -1336,6 +1460,11 @@ void CClientShadowMgr::InitDepthTextureShadows() { VPROF_BUDGET( "CClientShadowMgr::InitDepthTextureShadows", VPROF_BUDGETGROUP_SHADOW_DEPTH_TEXTURING ); +#if defined(MAPBASE) //&& !defined(ASW_PROJECTED_TEXTURES) + // SAUL: set m_nDepthTextureResolution to the depth resolution we want + m_nDepthTextureResolution = r_flashlightdepthres.GetInt(); +#endif + if( !m_bDepthTextureActive ) { m_bDepthTextureActive = true; @@ -1351,8 +1480,13 @@ void CClientShadowMgr::InitDepthTextureShadows() // only need the dummy surface, don't care about color results m_DummyColorTexture.InitRenderTargetTexture( r_flashlightdepthres.GetInt(), r_flashlightdepthres.GetInt(), RT_SIZE_OFFSCREEN, IMAGE_FORMAT_BGR565, MATERIAL_RT_DEPTH_SHARED, false, "_rt_ShadowDummy" ); m_DummyColorTexture.InitRenderTargetSurface( r_flashlightdepthres.GetInt(), r_flashlightdepthres.GetInt(), IMAGE_FORMAT_BGR565, true ); +#else +#if defined(MAPBASE) //&& !defined(ASW_PROJECTED_TEXTURES) + // SAUL: we want to create a render target of specific size, so use RT_SIZE_NO_CHANGE + m_DummyColorTexture.InitRenderTarget( m_nDepthTextureResolution, m_nDepthTextureResolution, RT_SIZE_NO_CHANGE, nullFormat, MATERIAL_RT_DEPTH_NONE, false, "_rt_ShadowDummy" ); #else m_DummyColorTexture.InitRenderTarget( r_flashlightdepthres.GetInt(), r_flashlightdepthres.GetInt(), RT_SIZE_OFFSCREEN, nullFormat, MATERIAL_RT_DEPTH_NONE, false, "_rt_ShadowDummy" ); +#endif #endif // Create some number of depth-stencil textures @@ -1371,9 +1505,20 @@ void CClientShadowMgr::InitDepthTextureShadows() // surface is effectively never used depthTex.InitRenderTargetTexture( m_nDepthTextureResolution, m_nDepthTextureResolution, RT_SIZE_OFFSCREEN, dstFormat, MATERIAL_RT_DEPTH_NONE, false, strRTName ); depthTex.InitRenderTargetSurface( 1, 1, dstFormat, false ); +#else +#if defined(MAPBASE) //&& !defined(ASW_PROJECTED_TEXTURES) + // SAUL: we want to create a *DEPTH TEXTURE* of specific size, so use RT_SIZE_NO_CHANGE and MATERIAL_RT_DEPTH_ONLY + // However, MATERIAL_RT_DEPTH_ONLY forces point filtering to be enabled which negatively affect PCF, so the standard MATERIAL_RT_DEPTH_NONE works better. + depthTex.InitRenderTarget( m_nDepthTextureResolution, m_nDepthTextureResolution, RT_SIZE_NO_CHANGE, dstFormat, MATERIAL_RT_DEPTH_NONE, false, strRTName ); #else depthTex.InitRenderTarget( m_nDepthTextureResolution, m_nDepthTextureResolution, RT_SIZE_OFFSCREEN, dstFormat, MATERIAL_RT_DEPTH_NONE, false, strRTName ); #endif +#endif + +#if defined(MAPBASE) //&& !defined(ASW_PROJECTED_TEXTURES) + // SAUL: ensure the depth texture size wasn't changed + Assert(depthTex->GetActualWidth() == m_nDepthTextureResolution); +#endif if ( i == 0 ) { @@ -1514,6 +1659,15 @@ void CClientShadowMgr::LevelInitPreEntity() { m_bUpdatingDirtyShadows = false; +#ifdef DYNAMIC_RTT_SHADOWS + // Default setting for this, can be overridden by shadow control entities +#ifdef MAPBASE + SetShadowFromWorldLightsEnabled( false ); +#else + SetShadowFromWorldLightsEnabled( true ); +#endif +#endif + Vector ambientColor; engine->GetAmbientLightColor( ambientColor ); ambientColor *= 3; @@ -1698,6 +1852,158 @@ const Vector &CClientShadowMgr::GetShadowDirection( IClientRenderable *pRenderab return vecResult; } +#ifdef DYNAMIC_RTT_SHADOWS +const Vector &CClientShadowMgr::GetShadowDirection( ClientShadowHandle_t shadowHandle ) const +{ + Assert( shadowHandle != CLIENTSHADOW_INVALID_HANDLE ); + + IClientRenderable* pRenderable = ClientEntityList().GetClientRenderableFromHandle( m_Shadows[shadowHandle].m_Entity ); + Assert( pRenderable ); + + if ( !IsShadowingFromWorldLights() ) + { + return GetShadowDirection( pRenderable ); + } + + Vector &vecResult = AllocTempVector(); + vecResult = m_Shadows[shadowHandle].m_ShadowDir; + + // Allow the renderable to override the default + pRenderable->GetShadowCastDirection( &vecResult, GetActualShadowCastType( pRenderable ) ); + + return vecResult; +} + +void CClientShadowMgr::UpdateShadowDirectionFromLocalLightSource( ClientShadowHandle_t shadowHandle ) +{ + Assert( shadowHandle != CLIENTSHADOW_INVALID_HANDLE ); + + ClientShadow_t& shadow = m_Shadows[shadowHandle]; + + IClientRenderable* pRenderable = ClientEntityList().GetClientRenderableFromHandle( shadow.m_Entity ); + + // TODO: Figure out why this still gets hit + Assert( pRenderable ); + if ( !pRenderable ) + { + DevWarning( "%s(): Skipping shadow with invalid client renderable (shadow handle %d)\n", __FUNCTION__, shadowHandle ); + return; + } + + Vector bbMin, bbMax; + pRenderable->GetRenderBoundsWorldspace( bbMin, bbMax ); + Vector origin( 0.5f * ( bbMin + bbMax ) ); + origin.z = bbMin.z; // Putting origin at the bottom of the bounding box makes the shadows a little shorter + + Vector lightPos; + Vector lightBrightness; + + if ( shadow.m_LightPosLerp >= 1.0f ) // skip finding new light source if we're in the middle of a lerp + { + // Calculate minimum brightness squared + float flMinBrightnessSqr = r_shadow_mincastintensity.GetFloat(); + flMinBrightnessSqr *= flMinBrightnessSqr; + + if(g_pWorldLights->GetBrightestLightSource(pRenderable->GetRenderOrigin(), lightPos, lightBrightness) == false || + lightBrightness.LengthSqr() < flMinBrightnessSqr ) + { + // didn't find a light source at all, use default shadow direction + // TODO: Could switch to using blobby shadow in this case + lightPos.Init( FLT_MAX, FLT_MAX, FLT_MAX ); + } + } + + if ( shadow.m_LightPosLerp == FLT_MAX ) // first light pos ever, just init + { + shadow.m_CurrentLightPos = lightPos; + shadow.m_TargetLightPos = lightPos; + shadow.m_LightPosLerp = 1.0f; + } + else if ( shadow.m_LightPosLerp < 1.0f ) + { + // We're in the middle of a lerp from current to target light. Finish it. + shadow.m_LightPosLerp += gpGlobals->frametime * 1.0f/r_shadow_lightpos_lerptime.GetFloat(); + shadow.m_LightPosLerp = clamp( shadow.m_LightPosLerp, 0.0f, 1.0f ); + + Vector currLightPos( shadow.m_CurrentLightPos ); + Vector targetLightPos( shadow.m_TargetLightPos ); + if ( currLightPos.x == FLT_MAX ) + { + currLightPos = origin - 200.0f * GetShadowDirection(); + } + if ( targetLightPos.x == FLT_MAX ) + { + targetLightPos = origin - 200.0f * GetShadowDirection(); + } + + // lerp light pos + Vector v1 = origin - shadow.m_CurrentLightPos; + v1.NormalizeInPlace(); + + Vector v2 = origin - shadow.m_TargetLightPos; + v2.NormalizeInPlace(); + + // SAULUNDONE: caused over top sweeping far too often +#if 0 + if ( v1.Dot( v2 ) < 0.0f ) + { + // if change in shadow angle is more than 90 degrees, lerp over the renderable's top to avoid long sweeping shadows + Vector fakeOverheadLightPos( origin.x, origin.y, origin.z + 200.0f ); + if( shadow.m_LightPosLerp < 0.5f ) + { + lightPos = Lerp( 2.0f * shadow.m_LightPosLerp, currLightPos, fakeOverheadLightPos ); + } + else + { + lightPos = Lerp( 2.0f * shadow.m_LightPosLerp - 1.0f, fakeOverheadLightPos, targetLightPos ); + } + } + else +#endif + { + lightPos = Lerp( shadow.m_LightPosLerp, currLightPos, targetLightPos ); + } + + if ( shadow.m_LightPosLerp >= 1.0f ) + { + shadow.m_CurrentLightPos = shadow.m_TargetLightPos; + } + } + else if ( shadow.m_LightPosLerp >= 1.0f ) + { + // check if we have a new closest light position and start a new lerp + float flDistSq = ( lightPos - shadow.m_CurrentLightPos ).LengthSqr(); + + if ( flDistSq > 1.0f ) + { + // light position has changed, which means we got a new light source. Initiate a lerp + shadow.m_TargetLightPos = lightPos; + shadow.m_LightPosLerp = 0.0f; + } + + lightPos = shadow.m_CurrentLightPos; + } + + if ( lightPos.x == FLT_MAX ) + { + lightPos = origin - 200.0f * GetShadowDirection(); + } + + Vector vecResult( origin - lightPos ); + vecResult.NormalizeInPlace(); + + vecResult.z *= r_shadow_shortenfactor.GetFloat(); + vecResult.NormalizeInPlace(); + + shadow.m_ShadowDir = vecResult; + + if ( r_shadowfromworldlights_debug.GetBool() ) + { + NDebugOverlay::Line( lightPos, origin, 255, 255, 0, false, 0.0f ); + } +} +#endif + //----------------------------------------------------------------------------- // Sets the shadow distance @@ -1818,6 +2124,12 @@ ClientShadowHandle_t CClientShadowMgr::CreateProjectedTexture( ClientEntityHandl shadow.m_ClientLeafShadowHandle = ClientLeafSystem()->AddShadow( h, flags ); shadow.m_Flags = flags; shadow.m_nRenderFrame = -1; +#ifdef DYNAMIC_RTT_SHADOWS + shadow.m_ShadowDir = GetShadowDirection(); + shadow.m_CurrentLightPos.Init( FLT_MAX, FLT_MAX, FLT_MAX ); + shadow.m_TargetLightPos.Init( FLT_MAX, FLT_MAX, FLT_MAX ); + shadow.m_LightPosLerp = FLT_MAX; +#endif shadow.m_LastOrigin.Init( FLT_MAX, FLT_MAX, FLT_MAX ); shadow.m_LastAngles.Init( FLT_MAX, FLT_MAX, FLT_MAX ); Assert( ( ( shadow.m_Flags & SHADOW_FLAGS_FLASHLIGHT ) == 0 ) != @@ -1837,12 +2149,21 @@ ClientShadowHandle_t CClientShadowMgr::CreateProjectedTexture( ClientEntityHandl pShadowProxyData = (void*)(uintp)h; } +#ifdef ASW_PROJECTED_TEXTURES + if( ( flags & SHADOW_FLAGS_USE_DEPTH_TEXTURE ) || ( flags & ( SHADOW_FLAGS_FLASHLIGHT ) ) ) + { + pShadowMaterial = NULL; // these materials aren't used for shadow depth texture shadows. + pShadowModelMaterial = NULL; + pShadowProxyData = (void*)(uintp)h; + } +#else if( flags & SHADOW_FLAGS_USE_DEPTH_TEXTURE ) { pShadowMaterial = m_RenderShadow; pShadowModelMaterial = m_RenderModelShadow; pShadowProxyData = (void*)(uintp)h; } +#endif int createShadowFlags; if( flags & SHADOW_FLAGS_FLASHLIGHT ) @@ -1905,7 +2226,27 @@ void CClientShadowMgr::UpdateFlashlightState( ClientShadowHandle_t shadowHandle, { VPROF_BUDGET( "CClientShadowMgr::UpdateFlashlightState", VPROF_BUDGETGROUP_SHADOW_DEPTH_TEXTURING ); +#ifdef ASW_PROJECTED_TEXTURES + if( flashlightState.m_bEnableShadows && r_flashlightdepthtexture.GetBool() ) + { + m_Shadows[shadowHandle].m_Flags |= SHADOW_FLAGS_USE_DEPTH_TEXTURE; + } + else + { + m_Shadows[shadowHandle].m_Flags &= ~SHADOW_FLAGS_USE_DEPTH_TEXTURE; + } + + if ( flashlightState.m_bOrtho ) + { + BuildOrthoWorldToFlashlightMatrix( m_Shadows[shadowHandle].m_WorldToShadow, flashlightState ); + } + else + { + BuildPerspectiveWorldToFlashlightMatrix( m_Shadows[shadowHandle].m_WorldToShadow, flashlightState ); + } +#else BuildPerspectiveWorldToFlashlightMatrix( m_Shadows[shadowHandle].m_WorldToShadow, flashlightState ); +#endif shadowmgr->UpdateFlashlightState( m_Shadows[shadowHandle].m_ShadowHandle, flashlightState ); } @@ -2016,6 +2357,40 @@ void CClientShadowMgr::BuildPerspectiveWorldToFlashlightMatrix( VMatrix& matWorl MatrixMultiply( matPerspective, matWorldToShadowView, matWorldToShadow ); } +#ifdef ASW_PROJECTED_TEXTURES +void CClientShadowMgr::BuildOrthoWorldToFlashlightMatrix( VMatrix& matWorldToShadow, const FlashlightState_t &flashlightState ) +{ + VPROF_BUDGET( "CClientShadowMgr::BuildPerspectiveWorldToFlashlightMatrix", VPROF_BUDGETGROUP_SHADOW_DEPTH_TEXTURING ); + + // Buildworld to shadow matrix, then perspective projection and concatenate + VMatrix matWorldToShadowView, matPerspective; + BuildWorldToShadowMatrix( matWorldToShadowView, flashlightState.m_vecLightOrigin, + flashlightState.m_quatOrientation ); + + MatrixBuildOrtho( matPerspective, + flashlightState.m_fOrthoLeft, flashlightState.m_fOrthoTop, flashlightState.m_fOrthoRight, flashlightState.m_fOrthoBottom, + flashlightState.m_NearZ, flashlightState.m_FarZ ); + + // Shift it z/y to 0 to -2 space + VMatrix addW; + addW.Identity(); + addW[0][3] = -1.0f; + addW[1][3] = -1.0f; + addW[2][3] = 0.0f; + MatrixMultiply( addW, matPerspective, matPerspective ); + + // Flip x/y to positive 0 to 1... flip z to negative + VMatrix scaleHalf; + scaleHalf.Identity(); + scaleHalf[0][0] = -0.5f; + scaleHalf[1][1] = -0.5f; + scaleHalf[2][2] = -1.0f; + MatrixMultiply( scaleHalf, matPerspective, matPerspective ); + + MatrixMultiply( matPerspective, matWorldToShadowView, matWorldToShadow ); +} +#endif + //----------------------------------------------------------------------------- // Compute the shadow origin and attenuation start distance //----------------------------------------------------------------------------- @@ -2289,7 +2664,11 @@ void CClientShadowMgr::BuildOrthoShadow( IClientRenderable* pRenderable, AngleVectors( pRenderable->GetRenderAngles(), &vec[0], &vec[1], &vec[2] ); vec[1] *= -1.0f; +#ifdef DYNAMIC_RTT_SHADOWS + Vector vecShadowDir = GetShadowDirection( handle ); +#else Vector vecShadowDir = GetShadowDirection( pRenderable ); +#endif // Project the shadow casting direction into the space of the object Vector localShadowDir; @@ -2375,7 +2754,11 @@ void CClientShadowMgr::BuildOrthoShadow( IClientRenderable* pRenderable, // Compute extra clip planes to prevent poke-thru // FIXME!!!!!!!!!!!!!! Removing this for now since it seems to mess up the blobby shadows. +#ifdef ASW_PROJECTED_TEXTURES + ComputeExtraClipPlanes( pRenderable, handle, vec, mins, maxs, localShadowDir ); +#else // ComputeExtraClipPlanes( pEnt, handle, vec, mins, maxs, localShadowDir ); +#endif // Add the shadow to the client leaf system so it correctly marks // leafs as being affected by a particular shadow @@ -2475,7 +2858,11 @@ void CClientShadowMgr::BuildRenderToTextureShadow( IClientRenderable* pRenderabl AngleVectors( pRenderable->GetRenderAngles(), &vec[0], &vec[1], &vec[2] ); vec[1] *= -1.0f; +#ifdef DYNAMIC_RTT_SHADOWS + Vector vecShadowDir = GetShadowDirection( handle ); +#else Vector vecShadowDir = GetShadowDirection( pRenderable ); +#endif // Debugging aid // const model_t *pModel = pRenderable->GetModel(); @@ -2870,6 +3257,13 @@ bool CClientShadowMgr::ShouldUseParentShadow( IClientRenderable *pRenderable ) //----------------------------------------------------------------------------- void CClientShadowMgr::PreRender() { +#ifdef ASW_PROJECTED_TEXTURES + // only update shadows once per frame + if( gpGlobals->framecount == m_nPrevFrameCount ) + return; + m_nPrevFrameCount = gpGlobals->framecount; +#endif + VPROF_BUDGET( "CClientShadowMgr::PreRender", VPROF_BUDGETGROUP_SHADOW_RENDERING ); MDLCACHE_CRITICAL_SECTION(); @@ -2941,8 +3335,12 @@ void CClientShadowMgr::PreRender() while ( i != m_DirtyShadows.InvalidIndex() ) { ClientShadowHandle_t& handle = m_DirtyShadows[ i ]; +#ifdef DYNAMIC_RTT_SHADOWS + UpdateDirtyShadow(handle); +#else Assert( m_Shadows.IsValidIndex( handle ) ); UpdateProjectedTextureInternal( handle, false ); +#endif i = m_DirtyShadows.NextInorder(i); } m_DirtyShadows.RemoveAll(); @@ -2958,6 +3356,20 @@ void CClientShadowMgr::PreRender() m_bUpdatingDirtyShadows = false; } +#ifdef DYNAMIC_RTT_SHADOWS +//----------------------------------------------------------------------------- +// Updates a single dirty shadow +//----------------------------------------------------------------------------- +void CClientShadowMgr::UpdateDirtyShadow( ClientShadowHandle_t handle ) +{ + Assert( m_Shadows.IsValidIndex( handle ) ); + if ( IsShadowingFromWorldLights() ) + { + UpdateShadowDirectionFromLocalLightSource( handle ); + } + UpdateProjectedTextureInternal( handle, false ); +} +#endif //----------------------------------------------------------------------------- // Gets the entity whose shadow this shadow will render into @@ -3132,7 +3544,11 @@ void CClientShadowMgr::UpdateShadow( ClientShadowHandle_t handle, bool force ) const Vector& origin = pRenderable->GetRenderOrigin(); const QAngle& angles = pRenderable->GetRenderAngles(); +#ifdef DYNAMIC_RTT_SHADOWS + if (force || (origin != shadow.m_LastOrigin) || (angles != shadow.m_LastAngles) || shadow.m_LightPosLerp < 1.0f) +#else if (force || (origin != shadow.m_LastOrigin) || (angles != shadow.m_LastAngles)) +#endif { // Store off the new pos/orientation VectorCopy( origin, shadow.m_LastOrigin ); @@ -3251,12 +3667,20 @@ void CClientShadowMgr::ComputeBoundingSphere( IClientRenderable* pRenderable, Ve //----------------------------------------------------------------------------- // Computes a rough AABB encompassing the volume of the shadow //----------------------------------------------------------------------------- +#ifdef DYNAMIC_RTT_SHADOWS +void CClientShadowMgr::ComputeShadowBBox( IClientRenderable *pRenderable, ClientShadowHandle_t shadowHandle, const Vector &vecAbsCenter, float flRadius, Vector *pAbsMins, Vector *pAbsMaxs ) +#else void CClientShadowMgr::ComputeShadowBBox( IClientRenderable *pRenderable, const Vector &vecAbsCenter, float flRadius, Vector *pAbsMins, Vector *pAbsMaxs ) +#endif { // This is *really* rough. Basically we simply determine the // maximum shadow casting length and extrude the box by that distance +#ifdef DYNAMIC_RTT_SHADOWS + Vector vecShadowDir = GetShadowDirection( shadowHandle ); +#else Vector vecShadowDir = GetShadowDirection( pRenderable ); +#endif for (int i = 0; i < 3; ++i) { float flShadowCastDistance = GetShadowDistance( pRenderable ); @@ -3354,7 +3778,11 @@ bool CClientShadowMgr::CullReceiver( ClientShadowHandle_t handle, IClientRendera if (foundSeparatingPlane) { // Compute which side of the plane the renderable is on.. +#ifdef DYNAMIC_RTT_SHADOWS + Vector vecShadowDir = GetShadowDirection( handle ); +#else Vector vecShadowDir = GetShadowDirection( pSourceRenderable ); +#endif float shadowDot = DotProduct( vecShadowDir, plane.normal ); float receiverDot = DotProduct( plane.normal, origin ); float sourceDot = DotProduct( plane.normal, originSource ); @@ -3841,6 +4269,11 @@ void CClientShadowMgr::AdvanceFrame() //----------------------------------------------------------------------------- int CClientShadowMgr::BuildActiveShadowDepthList( const CViewSetup &viewSetup, int nMaxDepthShadows, ClientShadowHandle_t *pActiveDepthShadows ) { +#ifdef ASW_PROJECTED_TEXTURES + Frustum_t viewFrustum; + GeneratePerspectiveFrustum( viewSetup.origin, viewSetup.angles, viewSetup.zNear, viewSetup.zFar, viewSetup.fov, viewSetup.m_flAspectRatio, viewFrustum ); +#endif + int nActiveDepthShadowCount = 0; for ( ClientShadowHandle_t i = m_Shadows.Head(); i != m_Shadows.InvalidIndex(); i = m_Shadows.Next(i) ) { @@ -3865,7 +4298,13 @@ int CClientShadowMgr::BuildActiveShadowDepthList( const CViewSetup &viewSetup, i // FIXME: Could do other sorts of culling here, such as frustum-frustum test, distance etc. // If it's not in the view frustum, move on +#ifdef MAPBASE + if ( !flashlightState.m_bAlwaysDraw && !flashlightState.m_bOrtho && R_CullBox( vecAbsMins, vecAbsMaxs, viewFrustum ) ) +#elif ASW_PROJECTED_TEXTURES + if ( !flashlightState.m_bOrtho && R_CullBox( vecAbsMins, vecAbsMaxs, viewFrustum ) ) +#else if ( R_CullBox( vecAbsMins, vecAbsMaxs, viewFrustum ) ) +#endif { shadowmgr->SetFlashlightDepthTexture( shadow.m_ShadowHandle, NULL, 0 ); continue; @@ -3890,6 +4329,53 @@ int CClientShadowMgr::BuildActiveShadowDepthList( const CViewSetup &viewSetup, i } +#ifdef ASW_PROJECTED_TEXTURES +//----------------------------------------------------------------------------- +// Re-render shadow depth textures that lie in the leaf list +//----------------------------------------------------------------------------- +int CClientShadowMgr::BuildActiveFlashlightList( const CViewSetup &viewSetup, int nMaxFlashlights, ClientShadowHandle_t *pActiveFlashlights ) +{ + int nActiveFlashlightCount = 0; + for ( ClientShadowHandle_t i = m_Shadows.Head(); i != m_Shadows.InvalidIndex(); i = m_Shadows.Next(i) ) + { + ClientShadow_t& shadow = m_Shadows[i]; + + if ( ( shadow.m_Flags & SHADOW_FLAGS_FLASHLIGHT ) == 0 ) + continue; + + // Calculate an AABB around the shadow frustum + Vector vecAbsMins, vecAbsMaxs; + CalculateAABBFromProjectionMatrix( shadow.m_WorldToShadow, &vecAbsMins, &vecAbsMaxs ); + + Frustum_t viewFrustum; + GeneratePerspectiveFrustum( viewSetup.origin, viewSetup.angles, viewSetup.zNear, viewSetup.zFar, viewSetup.fov, viewSetup.m_flAspectRatio, viewFrustum ); + + // FIXME: Could do other sorts of culling here, such as frustum-frustum test, distance etc. + // If it's not in the view frustum, move on + if ( R_CullBox( vecAbsMins, vecAbsMaxs, viewFrustum ) ) + { + continue; + } + + if ( nActiveFlashlightCount >= nMaxFlashlights ) + { + static bool s_bOverflowWarning = false; + if ( !s_bOverflowWarning ) + { + Warning( "Too many flashlights rendered in a single view!\n" ); + Assert( 0 ); + s_bOverflowWarning = true; + } + //shadowmgr->SetFlashlightDepthTexture( shadow.m_ShadowHandle, NULL, 0 ); + continue; + } + + pActiveFlashlights[nActiveFlashlightCount++] = i; + } + return nActiveFlashlightCount; +} +#endif + //----------------------------------------------------------------------------- // Sets the view's active flashlight render state //----------------------------------------------------------------------------- @@ -3913,12 +4399,56 @@ void CClientShadowMgr::SetViewFlashlightState( int nActiveFlashlightCount, Clien } } +#ifdef ASW_PROJECTED_TEXTURES +void AddPointToExtentsHelper( const VMatrix &flashlightToWorld, const Vector &vecPos, Vector &vecMin, Vector &vecMax ) +{ + Vector worldSpacePos; + + Vector3DMultiplyPositionProjective( flashlightToWorld, vecPos, worldSpacePos ); + VectorMin( vecMin, worldSpacePos, vecMin ); + VectorMax( vecMax, worldSpacePos, vecMax ); +} + + +void CClientShadowMgr::GetFrustumExtents( ClientShadowHandle_t handle, Vector &vecMin, Vector &vecMax ) +{ + Assert( m_Shadows.IsValidIndex( handle ) ); + + CClientShadowMgr::ClientShadow_t &shadow = m_Shadows[ handle ]; + + VMatrix flashlightToWorld; + MatrixInverseGeneral( shadow.m_WorldToShadow, flashlightToWorld ); + + vecMin = Vector( FLT_MAX, FLT_MAX, FLT_MAX ); + vecMax = Vector( -FLT_MAX, -FLT_MAX, -FLT_MAX ); + + AddPointToExtentsHelper( flashlightToWorld, Vector( 0.0f, 0.0f, 0.0f ), vecMin, vecMax ); + AddPointToExtentsHelper( flashlightToWorld, Vector( 0.0f, 0.0f, 1.0f ), vecMin, vecMax ); + AddPointToExtentsHelper( flashlightToWorld, Vector( 0.0f, 1.0f, 0.0f ), vecMin, vecMax ); + AddPointToExtentsHelper( flashlightToWorld, Vector( 1.0f, 0.0f, 0.0f ), vecMin, vecMax ); + AddPointToExtentsHelper( flashlightToWorld, Vector( 0.0f, 1.0f, 1.0f ), vecMin, vecMax ); + AddPointToExtentsHelper( flashlightToWorld, Vector( 1.0f, 0.0f, 1.0f ), vecMin, vecMax ); + AddPointToExtentsHelper( flashlightToWorld, Vector( 1.0f, 1.0f, 0.0f ), vecMin, vecMax ); + AddPointToExtentsHelper( flashlightToWorld, Vector( 1.0f, 1.0f, 1.0f ), vecMin, vecMax ); +} +#endif //----------------------------------------------------------------------------- // Re-render shadow depth textures that lie in the leaf list //----------------------------------------------------------------------------- void CClientShadowMgr::ComputeShadowDepthTextures( const CViewSetup &viewSetup ) { +#ifdef ASW_PROJECTED_TEXTURES + if ( !r_flashlightdepthtexture.GetBool() ) + { + // Build list of active flashlights + ClientShadowHandle_t pActiveFlashlights[16]; + int nActiveFlashlights = BuildActiveFlashlightList( viewSetup, ARRAYSIZE( pActiveFlashlights ), pActiveFlashlights ); + SetViewFlashlightState( nActiveFlashlights, pActiveFlashlights ); + return; + } +#endif + VPROF_BUDGET( "CClientShadowMgr::ComputeShadowDepthTextures", VPROF_BUDGETGROUP_SHADOW_DEPTH_TEXTURING ); CMatRenderContextPtr pRenderContext( materials ); @@ -3934,6 +4464,10 @@ void CClientShadowMgr::ComputeShadowDepthTextures( const CViewSetup &viewSetup ) { ClientShadow_t& shadow = m_Shadows[ pActiveDepthShadows[j] ]; +#ifdef ASW_PROJECTED_TEXTURES + FlashlightState_t& flashlightState = const_cast( shadowmgr->GetFlashlightState( shadow.m_ShadowHandle ) ); +#endif + CTextureReference shadowDepthTexture; bool bGotShadowDepthTexture = LockShadowDepthTexture( &shadowDepthTexture ); if ( !bGotShadowDepthTexture ) @@ -3948,19 +4482,57 @@ void CClientShadowMgr::ComputeShadowDepthTextures( const CViewSetup &viewSetup ) Assert(0); shadowmgr->SetFlashlightDepthTexture( shadow.m_ShadowHandle, NULL, 0 ); +#ifdef MAPBASE + if ( j <= ( INT_FLASHLIGHT_DEPTHTEXTURE_FALLBACK_LAST - INT_FLASHLIGHT_DEPTHTEXTURE_FALLBACK_FIRST ) ) + { + pRenderContext->SetIntRenderingParameter( INT_FLASHLIGHT_DEPTHTEXTURE_FALLBACK_FIRST + j, 0 ); + } +#endif continue; } CViewSetup shadowView; +#ifndef MAPBASE shadowView.m_flAspectRatio = 1.0f; +#endif shadowView.x = shadowView.y = 0; shadowView.width = shadowDepthTexture->GetActualWidth(); shadowView.height = shadowDepthTexture->GetActualHeight(); +#ifndef ASW_PROJECTED_TEXTURES shadowView.m_bOrtho = false; shadowView.m_bDoBloomAndToneMapping = false; +#ifdef MAPBASE + shadowView.m_flAspectRatio = (flashlightState.m_fHorizontalFOVDegrees / flashlightState.m_fVerticalFOVDegrees); +#endif // MAPBASE +#endif // Copy flashlight parameters +#ifdef ASW_PROJECTED_TEXTURES + if ( !flashlightState.m_bOrtho ) + { + shadowView.m_bOrtho = false; + +#ifdef MAPBASE + shadowView.m_flAspectRatio = (flashlightState.m_fHorizontalFOVDegrees / flashlightState.m_fVerticalFOVDegrees); +#endif // MAPBASE + } + else + { + shadowView.m_bOrtho = true; + shadowView.m_OrthoLeft = flashlightState.m_fOrthoLeft; + shadowView.m_OrthoTop = flashlightState.m_fOrthoTop; + shadowView.m_OrthoRight = flashlightState.m_fOrthoRight; + shadowView.m_OrthoBottom = flashlightState.m_fOrthoBottom; + +#ifdef MAPBASE + shadowView.m_flAspectRatio = 1.0f; +#endif + } + + shadowView.m_bDoBloomAndToneMapping = false; +#else const FlashlightState_t& flashlightState = shadowmgr->GetFlashlightState( shadow.m_ShadowHandle ); +#endif shadowView.fov = shadowView.fovViewmodel = flashlightState.m_fHorizontalFOVDegrees; shadowView.origin = flashlightState.m_vecLightOrigin; QuaternionAngles( flashlightState.m_quatOrientation, shadowView.angles ); // Convert from Quaternion to QAngle @@ -3981,6 +4553,19 @@ void CClientShadowMgr::ComputeShadowDepthTextures( const CViewSetup &viewSetup ) // Render to the shadow depth texture with appropriate view view->UpdateShadowDepthTexture( m_DummyColorTexture, shadowDepthTexture, shadowView ); +#ifdef MAPBASE + if ( j <= ( INT_FLASHLIGHT_DEPTHTEXTURE_FALLBACK_LAST - INT_FLASHLIGHT_DEPTHTEXTURE_FALLBACK_FIRST ) ) + { + pRenderContext->SetIntRenderingParameter( INT_FLASHLIGHT_DEPTHTEXTURE_FALLBACK_FIRST + j, int((ITexture*)shadowDepthTexture) ); + + FlashlightState_t state = shadowmgr->GetFlashlightState( shadow.m_ShadowHandle ); + + state.m_nShadowQuality = state.m_nShadowQuality | ( ( j + 1 ) << 16 ); + + shadowmgr->UpdateFlashlightState( shadow.m_ShadowHandle, state ); + } +#endif + // Associate the shadow depth texture and stencil bit with the flashlight for use during scene rendering shadowmgr->SetFlashlightDepthTexture( shadow.m_ShadowHandle, shadowDepthTexture, 0 ); } @@ -4005,7 +4590,11 @@ void CClientShadowMgr::ComputeShadowTextures( const CViewSetup &viewShadow, int if ( !m_RenderToTextureActive || (r_shadows.GetInt() == 0) || r_shadows_gamecontrol.GetInt() == 0 ) return; +#ifdef ASW_PROJECTED_TEXTURES + m_bThreaded = ( r_threaded_client_shadow_manager.GetBool() && g_pThreadPool->NumIdleThreads() ); +#else m_bThreaded = false;//( r_threaded_client_shadow_manager.GetBool() && g_pThreadPool->NumIdleThreads() ); +#endif MDLCACHE_CRITICAL_SECTION(); // First grab all shadow textures we may want to render @@ -4189,6 +4778,28 @@ bool CClientShadowMgr::IsFlashlightTarget( ClientShadowHandle_t shadowHandle, IC return false; } +#ifdef DYNAMIC_RTT_SHADOWS +void CClientShadowMgr::SetShadowFromWorldLightsEnabled( bool bEnable ) +{ + bool bIsShadowingFromWorldLights = IsShadowingFromWorldLights(); + m_bShadowFromWorldLights = bEnable; + if ( bIsShadowingFromWorldLights != IsShadowingFromWorldLights() ) + { + UpdateAllShadows(); + } +} + +void CClientShadowMgr::SuppressShadowFromWorldLights( bool bSuppress ) +{ + bool bIsShadowingFromWorldLights = IsShadowingFromWorldLights(); + m_bSuppressShadowFromWorldLights = bSuppress; + if ( bIsShadowingFromWorldLights != IsShadowingFromWorldLights() ) + { + UpdateAllShadows(); + } +} +#endif + //----------------------------------------------------------------------------- // A material proxy that resets the base texture to use the rendered shadow //----------------------------------------------------------------------------- diff --git a/src/game/client/colorcorrectionmgr.cpp b/src/game/client/colorcorrectionmgr.cpp index 770354b0..cf1210ac 100644 --- a/src/game/client/colorcorrectionmgr.cpp +++ b/src/game/client/colorcorrectionmgr.cpp @@ -8,6 +8,12 @@ #include "cbase.h" #include "tier0/vprof.h" #include "colorcorrectionmgr.h" +#ifdef MAPBASE // From Alien Swarm SDK +#include "clientmode_shared.h" //"clientmode.h" + +// NOTE: This has to be the last file included! +#include "tier0/memdbgon.h" +#endif //------------------------------------------------------------------------------ @@ -16,6 +22,13 @@ static CColorCorrectionMgr s_ColorCorrectionMgr; CColorCorrectionMgr *g_pColorCorrectionMgr = &s_ColorCorrectionMgr; +#ifdef MAPBASE // From Alien Swarm SDK +static ConVar mat_colcorrection_editor( "mat_colcorrection_editor", "0" ); + +static CUtlVector g_ColorCorrectionList; +static CUtlVector g_ColorCorrectionVolumeList; +#endif + //------------------------------------------------------------------------------ // Constructor @@ -62,10 +75,89 @@ void CColorCorrectionMgr::RemoveColorCorrection( ClientCCHandle_t h ) } } +#ifdef MAPBASE // From Alien Swarm SDK +ClientCCHandle_t CColorCorrectionMgr::AddColorCorrectionEntity( C_ColorCorrection *pEntity, const char *pName, const char *pFileName ) +{ + ClientCCHandle_t h = AddColorCorrection(pName, pFileName); + if ( h != INVALID_CLIENT_CCHANDLE ) + { + Assert(g_ColorCorrectionList.Find(pEntity) == -1); + g_ColorCorrectionList.AddToTail(pEntity); + } + return h; +} + +void CColorCorrectionMgr::RemoveColorCorrectionEntity( C_ColorCorrection *pEntity, ClientCCHandle_t h) +{ + RemoveColorCorrection(h); + g_ColorCorrectionList.FindAndFastRemove(pEntity); +} + +ClientCCHandle_t CColorCorrectionMgr::AddColorCorrectionVolume( C_ColorCorrectionVolume *pVolume, const char *pName, const char *pFileName ) +{ + ClientCCHandle_t h = AddColorCorrection(pName, pFileName); + if ( h != INVALID_CLIENT_CCHANDLE ) + { + Assert(g_ColorCorrectionVolumeList.Find(pVolume) == -1); + g_ColorCorrectionVolumeList.AddToTail(pVolume); + } + return h; +} + +void CColorCorrectionMgr::RemoveColorCorrectionVolume( C_ColorCorrectionVolume *pVolume, ClientCCHandle_t h) +{ + RemoveColorCorrection(h); + g_ColorCorrectionVolumeList.FindAndFastRemove(pVolume); +} +#endif //------------------------------------------------------------------------------ // Modify color correction weights //------------------------------------------------------------------------------ +#ifdef MAPBASE // From Alien Swarm SDK +void CColorCorrectionMgr::SetColorCorrectionWeight( ClientCCHandle_t h, float flWeight, bool bExclusive ) +{ + if ( h != INVALID_CLIENT_CCHANDLE ) + { + SetWeightParams_t params = { h, flWeight, bExclusive }; + m_colorCorrectionWeights.AddToTail( params ); + if( bExclusive && m_bHaveExclusiveWeight && ( flWeight != 0.0f ) ) + { + DevWarning( "Found multiple active color_correction entities with exclusive setting enabled. This is invalid.\n" ); + } + if ( bExclusive ) + { + m_bHaveExclusiveWeight = true; + m_flExclusiveWeight = flWeight; + } + } +} + +void CColorCorrectionMgr::CommitColorCorrectionWeights() +{ + CMatRenderContextPtr pRenderContext( g_pMaterialSystem ); + + for ( int i = 0; i < m_colorCorrectionWeights.Count(); i++ ) + { + ColorCorrectionHandle_t ccHandle = reinterpret_cast( m_colorCorrectionWeights[i].handle ); + float flWeight = m_colorCorrectionWeights[i].flWeight; + if ( !m_colorCorrectionWeights[i].bExclusive ) + { + flWeight = (1.0f - m_flExclusiveWeight ) * m_colorCorrectionWeights[i].flWeight; + } + pRenderContext->SetLookupWeight( ccHandle, flWeight ); + + // FIXME: NOTE! This doesn't work if the same handle has + // its weight set twice with no intervening calls to ResetColorCorrectionWeights + // which, at the moment, is true + if ( flWeight != 0.0f ) + { + ++m_nActiveWeightCount; + } + } + m_colorCorrectionWeights.RemoveAll(); +} +#else void CColorCorrectionMgr::SetColorCorrectionWeight( ClientCCHandle_t h, float flWeight ) { if ( h != INVALID_CLIENT_CCHANDLE ) @@ -83,6 +175,7 @@ void CColorCorrectionMgr::SetColorCorrectionWeight( ClientCCHandle_t h, float fl } } } +#endif void CColorCorrectionMgr::ResetColorCorrectionWeights() { @@ -93,6 +186,11 @@ void CColorCorrectionMgr::ResetColorCorrectionWeights() CMatRenderContextPtr pRenderContext( g_pMaterialSystem ); pRenderContext->ResetLookupWeights(); m_nActiveWeightCount = 0; +#ifdef MAPBASE // From Alien Swarm SDK + m_bHaveExclusiveWeight = false; + m_flExclusiveWeight = 0.0f; + m_colorCorrectionWeights.RemoveAll(); +#endif } void CColorCorrectionMgr::SetResetable( ClientCCHandle_t h, bool bResetable ) @@ -113,7 +211,34 @@ void CColorCorrectionMgr::SetResetable( ClientCCHandle_t h, bool bResetable ) //------------------------------------------------------------------------------ // Is color correction active? //------------------------------------------------------------------------------ +#ifdef MAPBASE // From Alien Swarm SDK +bool CColorCorrectionMgr::HasNonZeroColorCorrectionWeights() const +{ + return ( m_nActiveWeightCount != 0 ) || mat_colcorrection_editor.GetBool(); +} + +void CColorCorrectionMgr::UpdateColorCorrection() +{ + ResetColorCorrectionWeights(); + C_BasePlayer *pPlayer = C_BasePlayer::GetLocalPlayer(); + IClientMode *pClientMode = GetClientModeNormal(); //GetClientMode(); + + Assert( pClientMode ); + if ( !pPlayer || !pClientMode ) + { + return; + } + + pClientMode->OnColorCorrectionWeightsReset(); + float ccScale = pClientMode->GetColorCorrectionScale(); + + UpdateColorCorrectionEntities( pPlayer, ccScale, g_ColorCorrectionList.Base(), g_ColorCorrectionList.Count() ); + UpdateColorCorrectionVolumes( pPlayer, ccScale, g_ColorCorrectionVolumeList.Base(), g_ColorCorrectionVolumeList.Count() ); + CommitColorCorrectionWeights(); +} +#else bool CColorCorrectionMgr::HasNonZeroColorCorrectionWeights() const { return ( m_nActiveWeightCount != 0 ); } +#endif diff --git a/src/game/client/colorcorrectionmgr.h b/src/game/client/colorcorrectionmgr.h index 3d5271db..3eba0f8c 100644 --- a/src/game/client/colorcorrectionmgr.h +++ b/src/game/client/colorcorrectionmgr.h @@ -14,6 +14,10 @@ #include "igamesystem.h" +#ifdef MAPBASE // From Alien Swarm SDK +class C_ColorCorrection; +class C_ColorCorrectionVolume; +#endif //------------------------------------------------------------------------------ // Purpose : Singleton manager for color correction on the client @@ -35,8 +39,21 @@ public: ClientCCHandle_t AddColorCorrection( const char *pName, const char *pFileName = NULL ); void RemoveColorCorrection( ClientCCHandle_t ); +#ifdef MAPBASE // From Alien Swarm SDK + ClientCCHandle_t AddColorCorrectionEntity( C_ColorCorrection *pEntity, const char *pName, const char *pFileName = NULL ); + void RemoveColorCorrectionEntity( C_ColorCorrection *pEntity, ClientCCHandle_t ); + + ClientCCHandle_t AddColorCorrectionVolume( C_ColorCorrectionVolume *pVolume, const char *pName, const char *pFileName = NULL ); + void RemoveColorCorrectionVolume( C_ColorCorrectionVolume *pVolume, ClientCCHandle_t ); +#endif + // Modify color correction weights +#ifdef MAPBASE // From Alien Swarm SDK + void SetColorCorrectionWeight( ClientCCHandle_t h, float flWeight, bool bExclusive = false ); + void UpdateColorCorrection(); +#else void SetColorCorrectionWeight( ClientCCHandle_t h, float flWeight ); +#endif void ResetColorCorrectionWeights(); void SetResetable( ClientCCHandle_t h, bool bResetable ); @@ -45,8 +62,27 @@ public: private: int m_nActiveWeightCount; +#ifdef MAPBASE // From Alien Swarm SDK + bool m_bHaveExclusiveWeight; + float m_flExclusiveWeight; + + struct SetWeightParams_t + { + ClientCCHandle_t handle; + float flWeight; + bool bExclusive; + }; + + CUtlVector< SetWeightParams_t > m_colorCorrectionWeights; + + void CommitColorCorrectionWeights(); +#endif }; +#ifdef MAPBASE // From Alien Swarm SDK +void UpdateColorCorrectionEntities( C_BasePlayer *pPlayer, float ccScale, C_ColorCorrection **pList, int listCount ); +void UpdateColorCorrectionVolumes( C_BasePlayer *pPlayer, float ccScale, C_ColorCorrectionVolume **pList, int listCount ); +#endif //------------------------------------------------------------------------------ // Singleton access diff --git a/src/game/client/convarproxy.cpp b/src/game/client/convarproxy.cpp new file mode 100644 index 00000000..6b08bafc --- /dev/null +++ b/src/game/client/convarproxy.cpp @@ -0,0 +1,113 @@ +//========= Copyright © 1996-2008, Valve Corporation, All rights reserved. ============// +// +// Material proxy to stuff a convar into a material var. +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" +// identifier was truncated to '255' characters in the debug information +//#pragma warning(disable: 4786) + +#include "convar.h" +#include "materialsystem/imaterialproxy.h" +#include "materialsystem/imaterialvar.h" +//#include "imaterialproxydict.h" + +// NOTE: This has to be the last file included! +#include "tier0/memdbgon.h" + + +class CConVarMaterialProxy: public IMaterialProxy +{ +public: + CConVarMaterialProxy() + : m_pResult( NULL ), + m_conVarRef( "", true ) + { + } + + virtual ~CConVarMaterialProxy() + { + } + + virtual bool Init( IMaterial *pMaterial, KeyValues *pKeyValues ) + { + const char *pResult = pKeyValues->GetString( "resultVar" ); + if ( !pResult ) + return false; + + bool found; + m_pResult = pMaterial->FindVar( pResult, &found ); + if ( !found ) + { + m_pResult = NULL; + return false; + } + + /* + if ( !Q_stricmp( pResult, "$alpha" ) ) + { + pMaterial->SetMaterialVarFlag( MATERIAL_VAR_ALPHA_MODIFIED_BY_PROXY, true ); + } + */ + + pResult = pKeyValues->GetString( "convar" ); + if( !pResult ) + { + return false; + } + + m_conVarRef.Init( pResult, false ); + if ( !m_conVarRef.IsValid() ) + { + return false; + } + + return true; + } + + virtual void OnBind( void* ) + { + switch( m_pResult->GetType() ) + { + case MATERIAL_VAR_TYPE_VECTOR: + { + float f = m_conVarRef.GetFloat(); + Vector4D vec( f, f, f, f ); + m_pResult->SetVecValue( vec.Base(), m_pResult->VectorSize() ); + } + break; + +#ifdef MAPBASE + case MATERIAL_VAR_TYPE_STRING: + m_pResult->SetStringValue( m_conVarRef.GetString() ); + break; +#endif + + case MATERIAL_VAR_TYPE_INT: + m_pResult->SetIntValue( m_conVarRef.GetInt() ); + break; + + case MATERIAL_VAR_TYPE_FLOAT: + default: + m_pResult->SetFloatValue( m_conVarRef.GetFloat() ); + break; + } + } + + virtual IMaterial *GetMaterial() + { + return m_pResult->GetOwningMaterial(); + } + + virtual void Release() + { + } + +protected: + IMaterialVar *m_pResult; + ConVarRef m_conVarRef; +}; + +EXPOSE_INTERFACE( CConVarMaterialProxy, IMaterialProxy, "ConVar" IMATERIAL_PROXY_INTERFACE_VERSION ); diff --git a/src/game/client/detailobjectsystem.cpp b/src/game/client/detailobjectsystem.cpp index 3518fa90..fe158845 100644 --- a/src/game/client/detailobjectsystem.cpp +++ b/src/game/client/detailobjectsystem.cpp @@ -1472,6 +1472,7 @@ void CDetailObjectSystem::LevelInitPreEntity() } } +#ifndef MAPBASE if ( m_DetailObjects.Count() || m_DetailSpriteDict.Count() ) { // There are detail objects in the level, so precache the material @@ -1490,6 +1491,7 @@ void CDetailObjectSystem::LevelInitPreEntity() } } } +#endif int detailPropLightingLump; if( g_pMaterialSystemHardwareConfig->GetHDRType() != HDR_TYPE_NONE ) @@ -1513,6 +1515,32 @@ void CDetailObjectSystem::LevelInitPreEntity() void CDetailObjectSystem::LevelInitPostEntity() { +#ifdef MAPBASE + if ( m_DetailObjects.Count() || m_DetailSpriteDict.Count() ) + { + const char *pDetailSpriteMaterial = DETAIL_SPRITE_MATERIAL; + C_World *pWorld = GetClientWorldEntity(); + if ( pWorld && pWorld->GetDetailSpriteMaterial() && *(pWorld->GetDetailSpriteMaterial()) ) + pDetailSpriteMaterial = pWorld->GetDetailSpriteMaterial(); + + m_DetailSpriteMaterial.Init( pDetailSpriteMaterial, TEXTURE_GROUP_OTHER ); + PrecacheMaterial( pDetailSpriteMaterial ); + IMaterial *pMat = m_DetailSpriteMaterial; + + // adjust for non-square textures (cropped) + float flRatio = pMat->GetMappingWidth() / pMat->GetMappingHeight(); + if ( flRatio > 1.0 ) + { + for( int i = 0; iGetDetailSpriteMaterial() && *(pWorld->GetDetailSpriteMaterial()) ) @@ -1520,6 +1548,7 @@ void CDetailObjectSystem::LevelInitPostEntity() pDetailSpriteMaterial = pWorld->GetDetailSpriteMaterial(); } m_DetailSpriteMaterial.Init( pDetailSpriteMaterial, TEXTURE_GROUP_OTHER ); +#endif if ( GetDetailController() ) { @@ -1596,12 +1625,14 @@ void CDetailObjectSystem::UnserializeModelDict( CUtlBuffer& buf ) DetailModelDict_t dict; dict.m_pModel = (model_t *)engine->LoadModel( lump.m_Name, true ); +#ifndef MAPBASE // Don't allow vertex-lit models if (modelinfo->IsModelVertexLit(dict.m_pModel)) { Warning("Detail prop model %s is using vertex-lit materials!\nIt must use unlit materials!\n", lump.m_Name ); dict.m_pModel = (model_t *)engine->LoadModel( "models/error.mdl" ); } +#endif m_DetailObjectDict.AddToTail( dict ); } diff --git a/src/game/client/episodic/c_prop_scalable.cpp b/src/game/client/episodic/c_prop_scalable.cpp index d3902db3..b3134460 100644 --- a/src/game/client/episodic/c_prop_scalable.cpp +++ b/src/game/client/episodic/c_prop_scalable.cpp @@ -5,6 +5,10 @@ //============================================================================= #include "cbase.h" +#ifdef MAPBASE +#include "proxyentity.h" +#include "materialsystem/imaterialvar.h" +#endif class C_PropScalable : public C_BaseAnimating { @@ -194,3 +198,56 @@ void C_PropScalable::GetRenderBounds( Vector &theMins, Vector &theMaxs ) Assert( theMins.IsValid() && theMaxs.IsValid() ); } + +#ifdef MAPBASE +ConVar r_coreball_update_sphere_center( "r_coreball_update_sphere_center", "1", FCVAR_NONE, "Allows prop_coreball to update its center to the entity's origin" ); + +class CCoreBallUpdateMaterialProxy : public CEntityMaterialProxy +{ +public: + CCoreBallUpdateMaterialProxy() + { + m_pMaterial = NULL; + m_pSphereCenter = NULL; + } + virtual ~CCoreBallUpdateMaterialProxy() + { + } + virtual bool Init( IMaterial *pMaterial, KeyValues *pKeyValues ) + { + m_pMaterial = pMaterial; + bool found; + m_pSphereCenter = m_pMaterial->FindVar( "$spherecenter", &found ); + if( !found ) + { + m_pSphereCenter = NULL; + return false; + } + return true; + } + virtual void OnBind( C_BaseEntity *pC_BaseEntity ) + { + if (r_coreball_update_sphere_center.GetBool()) + { + const Vector &origin = pC_BaseEntity->GetAbsOrigin(); + m_pSphereCenter->SetVecValue( origin.x, origin.y, origin.z ); + } + else + { + // Just continuously bind the old hacked value (TODO: Optimize so it's not just assigning the same value constantly?) + m_pSphereCenter->SetVecValue( 2688.0, 12139.0, 5170.0 ); + } + } + + virtual IMaterial *GetMaterial() + { + return m_pMaterial; + } + +protected: + IMaterial *m_pMaterial; + IMaterialVar *m_pSphereCenter; +}; + +EXPOSE_INTERFACE( CCoreBallUpdateMaterialProxy, IMaterialProxy, "CoreBallUpdate" IMATERIAL_PROXY_INTERFACE_VERSION ); +#endif diff --git a/src/game/client/episodic/episodic_screenspaceeffects.cpp b/src/game/client/episodic/episodic_screenspaceeffects.cpp index a2b59bbc..fd8cafb2 100644 --- a/src/game/client/episodic/episodic_screenspaceeffects.cpp +++ b/src/game/client/episodic/episodic_screenspaceeffects.cpp @@ -462,3 +462,225 @@ void CEP2StunEffect::Render( int x, int y, int w, int h ) pRenderContext->MatrixMode( MATERIAL_PROJECTION ); pRenderContext->PopMatrix(); } + +// ================================================================================================================ +// +// Chromatic Aberration +// +// ================================================================================================================ + +#ifdef MAPBASE +ConVar r_chromatic_aberration_offset( "r_chromatic_aberration_offset", "8.0" ); +ConVar r_chromatic_aberration_intensity( "r_chromatic_aberration_intensity", "0.2" ); +ConVar r_chromatic_aberration_noise( "r_chromatic_aberration_noise", "4.0" ); + +ConVar r_chromatic_aberration_frame1_clr( "r_chromatic_aberration_frame1_clr", "1.0 0.0 0.0 1.0" ); +ConVar r_chromatic_aberration_frame1_offset_x( "r_chromatic_aberration_frame1_offset_x", "1.0" ); +ConVar r_chromatic_aberration_frame1_offset_y( "r_chromatic_aberration_frame1_offset_y", "4.0" ); + +ConVar r_chromatic_aberration_frame2_clr( "r_chromatic_aberration_frame2_clr", "0.0 1.0 0.0 1.0" ); +ConVar r_chromatic_aberration_frame2_offset_x( "r_chromatic_aberration_frame2_offset_x", "-5.0" ); +ConVar r_chromatic_aberration_frame2_offset_y( "r_chromatic_aberration_frame2_offset_y", "-1.0" ); + +ConVar r_chromatic_aberration_frame3_clr( "r_chromatic_aberration_frame3_clr", "0.0 0.0 1.0 1.0" ); +ConVar r_chromatic_aberration_frame3_offset_x( "r_chromatic_aberration_frame3_offset_x", "3.0" ); +ConVar r_chromatic_aberration_frame3_offset_y( "r_chromatic_aberration_frame3_offset_y", "-3.0" ); + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CChromaticAberrationEffect::Init( void ) +{ + m_flDuration = 0.0f; + m_flFinishTime = 0.0f; + m_bUpdateView = true; + + KeyValues *pVMTKeyValues = new KeyValues( "UnlitGeneric" ); + pVMTKeyValues->SetString( "$basetexture", STUN_TEXTURE ); + m_EffectMaterial.Init( "__stuneffect", TEXTURE_GROUP_CLIENT_EFFECTS, pVMTKeyValues ); + m_StunTexture.Init( STUN_TEXTURE, TEXTURE_GROUP_CLIENT_EFFECTS ); +} + +void CChromaticAberrationEffect::Shutdown( void ) +{ + m_EffectMaterial.Shutdown(); + m_StunTexture.Shutdown(); +} + +//------------------------------------------------------------------------------ +// Purpose: Pick up changes in our parameters +//------------------------------------------------------------------------------ +void CChromaticAberrationEffect::SetParameters( KeyValues *params ) +{ + if( params->FindKey( "duration" ) ) + { + m_flDuration = params->GetFloat( "duration" ); + m_flFinishTime = gpGlobals->curtime + m_flDuration; + m_bUpdateView = true; + } + + if( params->FindKey( "fadeout" ) ) + { + m_bFadeOut = ( params->GetInt( "fadeout" ) == 1 ); + } + + if( params->FindKey( "stretch" ) ) + { + m_bStretch = ( params->GetInt( "stretch" ) == 1 ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Render the effect +//----------------------------------------------------------------------------- +void CChromaticAberrationEffect::RenderColorFrame( CMatRenderContextPtr &pRenderContext, float flEffectPerc, int nColorMode, int x, int y, int w, int h ) +{ + // Change color + float flColor[4] = { 1.0f, 1.0f, 1.0f, 1.0f }; + + float viewOffsX = flEffectPerc; + if (m_bStretch) + viewOffsX *= r_chromatic_aberration_offset.GetFloat() * 2; + else + viewOffsX *= r_chromatic_aberration_offset.GetFloat(); + + float viewOffsY = viewOffsX; + + { + char szColor[16] = { 0 }; + float flNoise = sin( gpGlobals->curtime * r_chromatic_aberration_noise.GetFloat() ) * flEffectPerc; + + switch (nColorMode) + { + // Red + case 0: + Q_strncpy( szColor, r_chromatic_aberration_frame1_clr.GetString(), sizeof( szColor ) ); + + viewOffsX *= r_chromatic_aberration_frame1_offset_x.GetFloat(); + viewOffsY *= r_chromatic_aberration_frame1_offset_y.GetFloat(); + + viewOffsX += flNoise; + viewOffsY += flNoise; + break; + + // Green + case 1: + Q_strncpy( szColor, r_chromatic_aberration_frame2_clr.GetString(), sizeof( szColor ) ); + + viewOffsX *= r_chromatic_aberration_frame2_offset_x.GetFloat(); + viewOffsY *= r_chromatic_aberration_frame2_offset_y.GetFloat(); + + viewOffsX += flNoise; + viewOffsY += flNoise; + break; + + // Blue + case 2: + Q_strncpy( szColor, r_chromatic_aberration_frame3_clr.GetString(), sizeof( szColor ) ); + + viewOffsX *= r_chromatic_aberration_frame3_offset_x.GetFloat(); + viewOffsY *= r_chromatic_aberration_frame3_offset_y.GetFloat(); + + viewOffsX += flNoise; + viewOffsY += flNoise; + break; + } + + char *c = strtok( szColor, " " ); + for (int i = 0; i < 4 && c != NULL; i++, c = strtok( NULL, " " )) + { + flColor[i] = atof( c ); + } + } + + if (flColor[3] == 0.0f || g_pMaterialSystemHardwareConfig->GetDXSupportLevel() < 80) + return; + + m_EffectMaterial->ColorModulate( flColor[0], flColor[1], flColor[2] ); + + // Set alpha blend value + float flOverlayAlpha = clamp( r_chromatic_aberration_intensity.GetFloat() * flEffectPerc * flColor[3], 0.0f, 1.0f); + m_EffectMaterial->AlphaModulate( flOverlayAlpha ); + + // Draw full screen alpha-blended quad + if (m_bStretch) + { + float vX = x - (viewOffsX * 0.5f); + float vY = y - (viewOffsY * 0.5f); + + pRenderContext->DrawScreenSpaceRectangle( m_EffectMaterial, vX, vY, w + viewOffsX, h + viewOffsY, + 0, 0, (m_StunTexture->GetActualWidth()-1), (m_StunTexture->GetActualHeight()-1), + m_StunTexture->GetActualWidth(), m_StunTexture->GetActualHeight() ); + } + else + { + float vX = x + viewOffsX; + float vY = y + viewOffsY; + + pRenderContext->DrawScreenSpaceRectangle( m_EffectMaterial, 0, 0, w, h, + vX, vY, (m_StunTexture->GetActualWidth()-1)+vX, (m_StunTexture->GetActualHeight()-1)+vY, + m_StunTexture->GetActualWidth(), m_StunTexture->GetActualHeight() ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Render the effect +//----------------------------------------------------------------------------- +void CChromaticAberrationEffect::Render( int x, int y, int w, int h ) +{ + // Make sure we're ready to play this effect + if ( !IsEnabled() ) + return; + + if ( m_bFadeOut && m_flFinishTime < gpGlobals->curtime ) + { + g_pScreenSpaceEffects->DisableScreenSpaceEffect( "mapbase_chromatic_aberration" ); + return; + } + + CMatRenderContextPtr pRenderContext( materials ); + + // Set ourselves to the proper rendermode + pRenderContext->MatrixMode( MATERIAL_VIEW ); + pRenderContext->PushMatrix(); + pRenderContext->LoadIdentity(); + pRenderContext->MatrixMode( MATERIAL_PROJECTION ); + pRenderContext->PushMatrix(); + pRenderContext->LoadIdentity(); + + // Draw the texture if we're using it + if ( m_bUpdateView ) + { + // Save off this pass + Rect_t srcRect; + srcRect.x = x; + srcRect.y = y; + srcRect.width = w; + srcRect.height = h; + pRenderContext->CopyRenderTargetToTextureEx( m_StunTexture, 0, &srcRect, NULL ); + m_bUpdateView = false; + } + + float flEffectPerc = SmoothCurve( clamp( ( m_flFinishTime - gpGlobals->curtime ) / m_flDuration, 0.0f, 1.0f ) ); + if (!m_bFadeOut) + flEffectPerc = 1.0f - flEffectPerc; + + RenderColorFrame( pRenderContext, flEffectPerc, 0, x, y, w, h ); + RenderColorFrame( pRenderContext, flEffectPerc, 1, x, y, w, h ); + RenderColorFrame( pRenderContext, flEffectPerc, 2, x, y, w, h ); + + // Save off this pass + Rect_t srcRect; + srcRect.x = x; + srcRect.y = y; + srcRect.width = w; + srcRect.height = h; + pRenderContext->CopyRenderTargetToTextureEx( m_StunTexture, 0, &srcRect, NULL ); + + // Restore our state + pRenderContext->MatrixMode( MATERIAL_VIEW ); + pRenderContext->PopMatrix(); + pRenderContext->MatrixMode( MATERIAL_PROJECTION ); + pRenderContext->PopMatrix(); +} +#endif diff --git a/src/game/client/episodic/episodic_screenspaceeffects.h b/src/game/client/episodic/episodic_screenspaceeffects.h index ff5bc716..661aa403 100644 --- a/src/game/client/episodic/episodic_screenspaceeffects.h +++ b/src/game/client/episodic/episodic_screenspaceeffects.h @@ -116,4 +116,38 @@ private: ADD_SCREENSPACE_EFFECT( CEP2StunEffect, ep2_groggy ); +#ifdef MAPBASE +class CChromaticAberrationEffect : public IScreenSpaceEffect +{ +public: + CChromaticAberrationEffect( void ) : + m_flDuration( 0.0f ), + m_flFinishTime( 0.0f ), + m_bUpdateView( true ), + m_bEnabled( false ), + m_bFadeOut( false ) {} + + virtual void Init( void ); + virtual void Shutdown( void ); + virtual void SetParameters( KeyValues *params ); + virtual void Enable( bool bEnable ) { m_bEnabled = bEnable; }; + virtual bool IsEnabled( ) { return m_bEnabled; } + + virtual void RenderColorFrame( CMatRenderContextPtr &pRenderContext, float flEffectPerc, int nColorMode, int x, int y, int w, int h ); + virtual void Render( int x, int y, int w, int h ); + +private: + CTextureReference m_StunTexture; + CMaterialReference m_EffectMaterial; + float m_flDuration; + float m_flFinishTime; + bool m_bUpdateView; + bool m_bStretch; + bool m_bFadeOut; + bool m_bEnabled; +}; + +ADD_SCREENSPACE_EFFECT( CChromaticAberrationEffect, mapbase_chromatic_aberration ); +#endif + #endif // EPISODIC_SCREENSPACEEFFECTS_H diff --git a/src/game/client/flashlighteffect.cpp b/src/game/client/flashlighteffect.cpp index ebff8655..e1e7fb16 100644 --- a/src/game/client/flashlighteffect.cpp +++ b/src/game/client/flashlighteffect.cpp @@ -48,8 +48,16 @@ static ConVar r_flashlightvisualizetrace( "r_flashlightvisualizetrace", "0", FCV static ConVar r_flashlightambient( "r_flashlightambient", "0.0", FCVAR_CHEAT ); static ConVar r_flashlightshadowatten( "r_flashlightshadowatten", "0.35", FCVAR_CHEAT ); static ConVar r_flashlightladderdist( "r_flashlightladderdist", "40.0", FCVAR_CHEAT ); +#ifndef MAPBASE static ConVar mat_slopescaledepthbias_shadowmap( "mat_slopescaledepthbias_shadowmap", "16", FCVAR_CHEAT ); static ConVar mat_depthbias_shadowmap( "mat_depthbias_shadowmap", "0.0005", FCVAR_CHEAT ); +#else +extern ConVarRef mat_slopescaledepthbias_shadowmap; +extern ConVarRef mat_depthbias_shadowmap; +#endif +#ifdef MAPBASE +static ConVar r_flashlighttextureoverride( "r_flashlighttextureoverride", "", FCVAR_CHEAT ); +#endif void r_newflashlightCallback_f( IConVar *pConVar, const char *pOldString, float flOldValue ) @@ -78,6 +86,13 @@ CFlashlightEffect::CFlashlightEffect(int nEntIndex) r_newflashlight.SetValue( 0 ); } +#ifdef MAPBASE + if ( r_flashlighttextureoverride.GetString()[0] != '\0' ) + { + m_FlashlightTexture.Init( r_flashlighttextureoverride.GetString(), TEXTURE_GROUP_OTHER, true ); + } + else +#endif if ( g_pMaterialSystemHardwareConfig->SupportsBorderColor() ) { m_FlashlightTexture.Init( "effects/flashlight_border", TEXTURE_GROUP_OTHER, true ); diff --git a/src/game/client/fx.cpp b/src/game/client/fx.cpp index a342837f..a53aeac1 100644 --- a/src/game/client/fx.cpp +++ b/src/game/client/fx.cpp @@ -1255,6 +1255,13 @@ void FX_BuildTeslaHitbox( const CEffectData &data ) { Vector vColor( 1, 1, 1 ); +#ifdef MAPBASE + if ( data.m_bCustomColors ) + { + vColor = data.m_CustomColors.m_vecColor1; + } +#endif + C_BaseEntity *pEntity = ClientEntityList().GetEnt( data.entindex() ); C_BaseAnimating *pAnimating = pEntity ? pEntity->GetBaseAnimating() : NULL; if (!pAnimating) diff --git a/src/game/client/fx_impact.cpp b/src/game/client/fx_impact.cpp index c1aba01b..e662bff9 100644 --- a/src/game/client/fx_impact.cpp +++ b/src/game/client/fx_impact.cpp @@ -26,6 +26,13 @@ extern ConVar r_drawmodeldecals; ImpactSoundRouteFn g_pImpactSoundRouteFn = NULL; +#ifdef MAPBASE +ConVar g_ragdoll_steal_impacts_client( "g_ragdoll_steal_impacts_client", "1", FCVAR_NONE, "Allows clientside death ragdolls to \"steal\" impacts from their source entities. This fixes issues with NPCs dying before decals are applied." ); +ConVar g_ragdoll_steal_impacts_server( "g_ragdoll_steal_impacts_server", "1", FCVAR_NONE, "Allows serverside death ragdolls to \"steal\" impacts from their source entities. This fixes issues with NPCs dying before decals are applied." ); + +ConVar g_ragdoll_client_impact_decals( "g_ragdoll_client_impact_decals", "1", FCVAR_NONE, "Applies decals to clientside ragdolls when they are hit." ); +#endif + //========================================================================================================================== // RAGDOLL ENUMERATOR //========================================================================================================================== @@ -33,7 +40,11 @@ CRagdollEnumerator::CRagdollEnumerator( Ray_t& shot, int iDamageType ) { m_rayShot = shot; m_iDamageType = iDamageType; +#ifdef MAPBASE + m_pHitEnt = NULL; +#else m_bHit = false; +#endif } IterationRetval_t CRagdollEnumerator::EnumElement( IHandleEntity *pHandleEntity ) @@ -58,7 +69,11 @@ IterationRetval_t CRagdollEnumerator::EnumElement( IHandleEntity *pHandleEntity if ( tr.fraction < 1.0 ) { pModel->ImpactTrace( &tr, m_iDamageType, NULL ); +#ifdef MAPBASE + m_pHitEnt = pModel; +#else m_bHit = true; +#endif //FIXME: Yes? No? return ITERATION_STOP; @@ -85,6 +100,22 @@ bool FX_AffectRagdolls( Vector vecOrigin, Vector vecStart, int iDamageType ) return ragdollEnum.Hit(); } +#ifdef MAPBASE +C_BaseAnimating *FX_AffectRagdolls_GetHit( Vector vecOrigin, Vector vecStart, int iDamageType ) +{ + // don't do this when lots of ragdolls are simulating + if ( s_RagdollLRU.CountRagdolls(true) > 1 ) + return NULL; + Ray_t shotRay; + shotRay.Init( vecStart, vecOrigin ); + + CRagdollEnumerator ragdollEnum( shotRay, iDamageType ); + partition->EnumerateElementsAlongRay( PARTITION_CLIENT_RESPONSIVE_EDICTS, shotRay, false, &ragdollEnum ); + + return ragdollEnum.GetHit(); +} +#endif + //----------------------------------------------------------------------------- // Purpose: // Input : &data - @@ -105,6 +136,22 @@ bool Impact( Vector &vecOrigin, Vector &vecStart, int iMaterial, int iDamageType Assert ( pEntity ); +#ifdef MAPBASE + // If the entity already has a ragdoll that was created on the current tick, use that ragdoll instead. + // This allows the killing damage's decals to show up on the ragdoll. + if (C_BaseAnimating *pAnimating = pEntity->GetBaseAnimating()) + { + if (pAnimating->m_pClientsideRagdoll && WasRagdollCreatedOnCurrentTick( pAnimating->m_pClientsideRagdoll ) && g_ragdoll_steal_impacts_client.GetBool()) + { + pEntity = pAnimating->m_pClientsideRagdoll; + } + else if (pAnimating->m_pServerRagdoll && WasRagdollCreatedOnCurrentTick( pAnimating->m_pServerRagdoll ) && g_ragdoll_steal_impacts_server.GetBool()) + { + pEntity = pAnimating->m_pServerRagdoll; + } + } +#endif + // Clear out the trace memset( &tr, 0, sizeof(trace_t)); tr.fraction = 1.0f; @@ -116,13 +163,52 @@ bool Impact( Vector &vecOrigin, Vector &vecStart, int iMaterial, int iDamageType VectorMA( vecStart, flLength + 8.0f, shotDir, traceExt ); // Attempt to hit ragdolls - + bool bHitRagdoll = false; - + +#ifdef MAPBASE + if ( !pEntity->IsClientCreated() ) + { + C_BaseAnimating *pRagdoll = FX_AffectRagdolls_GetHit( vecOrigin, vecStart, iDamageType ); + if (pRagdoll) + { + bHitRagdoll = true; + + if (g_ragdoll_client_impact_decals.GetBool() && pRagdoll->IsRagdoll()) + { + pEntity = pRagdoll; + + // HACKHACK: Get the ragdoll's nearest bone for its material + int iNearestMaterial = 0; + float flNearestDistSqr = FLT_MAX; + + IPhysicsObject *pList[VPHYSICS_MAX_OBJECT_LIST_COUNT]; + int count = pEntity->VPhysicsGetObjectList( pList, ARRAYSIZE(pList) ); + for ( int i = 0; i < count; i++ ) + { + Vector vecPosition; + QAngle angAngles; + pList[i]->GetPosition( &vecPosition, &angAngles ); + float flDistSqr = (vecStart - vecPosition).LengthSqr(); + if (flDistSqr < flNearestDistSqr) + { + iNearestMaterial = pList[i]->GetMaterialIndex(); + flNearestDistSqr = flDistSqr; + } + } + + // Get the material from the surfaceprop + surfacedata_t *psurfaceData = physprops->GetSurfaceData( iNearestMaterial ); + iMaterial = psurfaceData->game.material; + } + } + } +#else if ( !pEntity->IsClientCreated() ) { bHitRagdoll = FX_AffectRagdolls( vecOrigin, vecStart, iDamageType ); } +#endif if ( (nFlags & IMPACT_NODECAL) == 0 ) { diff --git a/src/game/client/fx_impact.h b/src/game/client/fx_impact.h index ad57f7e7..9c6cb875 100644 --- a/src/game/client/fx_impact.h +++ b/src/game/client/fx_impact.h @@ -58,12 +58,21 @@ public: // Actual work code virtual IterationRetval_t EnumElement( IHandleEntity *pHandleEntity ); +#ifdef MAPBASE + bool Hit( void ) const { return m_pHitEnt != NULL; } + C_BaseAnimating *GetHit( void ) { return m_pHitEnt; } +#else bool Hit( void ) const { return m_bHit; } +#endif private: Ray_t m_rayShot; int m_iDamageType; +#ifdef MAPBASE + C_BaseAnimating *m_pHitEnt; +#else bool m_bHit; +#endif }; #endif // FX_IMPACT_H diff --git a/src/game/client/fx_tracer.cpp b/src/game/client/fx_tracer.cpp index cec013a6..79bc513e 100644 --- a/src/game/client/fx_tracer.cpp +++ b/src/game/client/fx_tracer.cpp @@ -40,11 +40,13 @@ Vector GetTracerOrigin( const CEffectData &data ) C_BaseEntity *pEnt = data.GetEntity(); -// This check should probably be for all multiplayer games, investigate later -#if defined( HL2MP ) || defined( TF_CLIENT_DLL ) - if ( pEnt && pEnt->IsDormant() ) - return vecStart; -#endif + // This check should probably be for all multiplayer games, investigate later + // 10/09/2008: It should. + if ( gpGlobals->maxClients > 1 ) + { + if ( pEnt && pEnt->IsDormant() ) + return vecStart; + } C_BaseCombatWeapon *pWpn = dynamic_cast( pEnt ); if ( pWpn && pWpn->ShouldDrawUsingViewModel() ) diff --git a/src/game/client/game_controls/baseviewport.cpp b/src/game/client/game_controls/baseviewport.cpp index 86c8dce8..56a650cf 100644 --- a/src/game/client/game_controls/baseviewport.cpp +++ b/src/game/client/game_controls/baseviewport.cpp @@ -156,6 +156,32 @@ bool CBaseViewport::LoadHudAnimations( void ) return true; } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: Reloads HUD animations after loading a map-specific HUD animations file. +//----------------------------------------------------------------------------- +void CBaseViewport::ReloadHudAnimations( void ) +{ + // Force a reload + if ( LoadHudAnimations() == false ) + { + // Fall back to just the main + if ( m_pAnimController->SetScriptFile( GetVPanel(), "scripts/HudAnimations.txt", true ) == false ) + { + Assert(0); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Loads a map-specific HUD animations file. +//----------------------------------------------------------------------------- +bool CBaseViewport::LoadCustomHudAnimations( const char *pszFile ) +{ + return m_pAnimController->SetScriptFile( GetVPanel(), pszFile, true ); +} +#endif + //================================================================ CBaseViewport::CBaseViewport() : vgui::EditablePanel( NULL, "CBaseViewport") { diff --git a/src/game/client/game_controls/baseviewport.h b/src/game/client/game_controls/baseviewport.h index fa8055d2..27e60d22 100644 --- a/src/game/client/game_controls/baseviewport.h +++ b/src/game/client/game_controls/baseviewport.h @@ -88,6 +88,10 @@ public: public: // IGameEventListener: virtual void FireGameEvent( IGameEvent * event); +#ifdef MAPBASE + bool LoadCustomHudAnimations( const char *pszFile ); + void ReloadHudAnimations( void ); +#endif protected: diff --git a/src/game/client/glow_outline_effect.h b/src/game/client/glow_outline_effect.h index a534fd75..8da7c114 100644 --- a/src/game/client/glow_outline_effect.h +++ b/src/game/client/glow_outline_effect.h @@ -150,6 +150,10 @@ private: static const int ENTRY_IN_USE = -2; }; +#ifdef MAPBASE_VSCRIPT + // For unregistration boundary check +public: +#endif CUtlVector< GlowObjectDefinition_t > m_GlowObjectDefinitions; int m_nFirstFreeSlot; }; diff --git a/src/game/client/hl2/c_basehlplayer.cpp b/src/game/client/hl2/c_basehlplayer.cpp index 779ba954..7ccd693d 100644 --- a/src/game/client/hl2/c_basehlplayer.cpp +++ b/src/game/client/hl2/c_basehlplayer.cpp @@ -31,6 +31,9 @@ ConVar cl_npc_speedmod_outtime( "cl_npc_speedmod_outtime", "1.5", FCVAR_CLIENTDL IMPLEMENT_CLIENTCLASS_DT(C_BaseHLPlayer, DT_HL2_Player, CHL2_Player) RecvPropDataTable( RECVINFO_DT(m_HL2Local),0, &REFERENCE_RECV_TABLE(DT_HL2Local) ), RecvPropBool( RECVINFO( m_fIsSprinting ) ), +#ifdef SP_ANIM_STATE + RecvPropFloat( RECVINFO( m_flAnimRenderYaw ) ), +#endif END_RECV_TABLE() BEGIN_PREDICTION_DATA( C_BaseHLPlayer ) @@ -38,6 +41,11 @@ BEGIN_PREDICTION_DATA( C_BaseHLPlayer ) DEFINE_PRED_FIELD( m_fIsSprinting, FIELD_BOOLEAN, FTYPEDESC_INSENDTABLE ), END_PREDICTION_DATA() +// link to the correct class. +#if !defined ( HL2MP ) && !defined ( PORTAL ) +LINK_ENTITY_TO_CLASS( player, C_BaseHLPlayer ); +#endif + BEGIN_RECV_TABLE_NOBASE( LadderMove_t, DT_LadderMove ) RecvPropBool( RECVINFO( m_bForceLadderMove ) ), RecvPropBool( RECVINFO( m_bForceMount ) ), @@ -84,6 +92,11 @@ C_BaseHLPlayer::C_BaseHLPlayer() m_flZoomRate = 0.0f; m_flZoomStartTime = 0.0f; m_flSpeedMod = cl_forwardspeed.GetFloat(); + +#ifdef MAPBASE + ConVarRef scissor("r_flashlightscissor"); + scissor.SetValue("0"); +#endif } //----------------------------------------------------------------------------- @@ -98,6 +111,13 @@ void C_BaseHLPlayer::OnDataChanged( DataUpdateType_t updateType ) SetNextClientThink( CLIENT_THINK_ALWAYS ); } +#ifdef SP_ANIM_STATE + if (m_flAnimRenderYaw != FLT_MAX) + { + m_angAnimRender = QAngle( 0, m_flAnimRenderYaw, 0 ); + } +#endif + BaseClass::OnDataChanged( updateType ); } @@ -665,3 +685,21 @@ void C_BaseHLPlayer::BuildTransformations( CStudioHdr *hdr, Vector *pos, Quatern BuildFirstPersonMeathookTransformations( hdr, pos, q, cameraTransform, boneMask, boneComputed, "ValveBiped.Bip01_Head1" ); } + +#ifdef SP_ANIM_STATE +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +const QAngle& C_BaseHLPlayer::GetRenderAngles( void ) +{ + if ( m_flAnimRenderYaw != FLT_MAX ) + { + return m_angAnimRender; + } + else + { + return BaseClass::GetRenderAngles(); + } +} +#endif + diff --git a/src/game/client/hl2/c_basehlplayer.h b/src/game/client/hl2/c_basehlplayer.h index 2b033076..652c19e8 100644 --- a/src/game/client/hl2/c_basehlplayer.h +++ b/src/game/client/hl2/c_basehlplayer.h @@ -5,7 +5,6 @@ // $Workfile: $ // $NoKeywords: $ //=============================================================================// - #if !defined( C_BASEHLPLAYER_H ) #define C_BASEHLPLAYER_H #ifdef _WIN32 @@ -16,6 +15,10 @@ #include "c_baseplayer.h" #include "c_hl2_playerlocaldata.h" +#if !defined( HL2MP ) && defined ( MAPBASE ) +#include "mapbase/singleplayer_animstate.h" +#endif + class C_BaseHLPlayer : public C_BasePlayer { public: @@ -34,10 +37,17 @@ public: float GetZoom( void ); bool IsZoomed( void ) { return m_HL2Local.m_bZooming; } - bool IsSprinting( void ) { return m_HL2Local.m_bitsActiveDevices & bits_SUIT_DEVICE_SPRINT; } + //Tony; minor cosmetic really, fix confusion by simply renaming this one; everything calls IsSprinting(), and this isn't really even used. + bool IsSprintActive( void ) { return m_HL2Local.m_bitsActiveDevices & bits_SUIT_DEVICE_SPRINT; } bool IsFlashlightActive( void ) { return m_HL2Local.m_bitsActiveDevices & bits_SUIT_DEVICE_FLASHLIGHT; } bool IsBreatherActive( void ) { return m_HL2Local.m_bitsActiveDevices & bits_SUIT_DEVICE_BREATHER; } +#ifdef MAPBASE + bool IsCustomDevice0Active( void ) { return m_HL2Local.m_bitsActiveDevices & bits_SUIT_DEVICE_CUSTOM0; } + bool IsCustomDevice1Active( void ) { return m_HL2Local.m_bitsActiveDevices & bits_SUIT_DEVICE_CUSTOM1; } + bool IsCustomDevice2Active( void ) { return m_HL2Local.m_bitsActiveDevices & bits_SUIT_DEVICE_CUSTOM2; } +#endif + virtual int DrawModel( int flags ); virtual void BuildTransformations( CStudioHdr *hdr, Vector *pos, Quaternion q[], const matrix3x4_t& cameraTransform, int boneMask, CBoneBitList &boneComputed ); @@ -55,6 +65,10 @@ public: virtual void HandleSpeedChanges( CMoveData *mv ){} virtual void ReduceTimers( CMoveData *mv ){} +#ifdef SP_ANIM_STATE + virtual const QAngle& GetRenderAngles( void ); +#endif + public: C_HL2PlayerLocalData m_HL2Local; @@ -75,7 +89,13 @@ private: bool m_bPlayUseDenySound; // Signaled by PlayerUse, but can be unset by HL2 ladder code... float m_flSpeedMod; float m_flExitSpeedMod; - + +#ifdef SP_ANIM_STATE + // At the moment, we network the render angles since almost none of the player anim stuff is done on the client in SP. + // If any of this is ever adapted for MP, this method should be replaced with replicating/moving the anim state to the client. + float m_flAnimRenderYaw; + QAngle m_angAnimRender; +#endif friend class CHL2GameMovement; }; diff --git a/src/game/client/hl2/c_env_starfield.cpp b/src/game/client/hl2/c_env_starfield.cpp index 632c6f35..123dc0c1 100644 --- a/src/game/client/hl2/c_env_starfield.cpp +++ b/src/game/client/hl2/c_env_starfield.cpp @@ -90,6 +90,11 @@ void C_EnvStarfield::ClientThink( void ) if ( !m_bOn || !m_flDensity ) return; +#ifdef MAPBASE + if ( engine->IsPaused() ) + return; +#endif + PMaterialHandle hParticleMaterial = m_pEmitter->GetPMaterial( "effects/spark_noz" ); // Find a start & end point for the particle diff --git a/src/game/client/hl2/c_script_intro.cpp b/src/game/client/hl2/c_script_intro.cpp index eb97cb4f..49052f03 100644 --- a/src/game/client/hl2/c_script_intro.cpp +++ b/src/game/client/hl2/c_script_intro.cpp @@ -10,6 +10,9 @@ #include "iviewrender.h" #include "view_shared.h" #include "viewrender.h" +#ifdef MAPBASE +#include "c_point_camera.h" +#endif // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" @@ -53,6 +56,11 @@ private: float m_flBlendStartTime; bool m_bActive; EHANDLE m_hCameraEntity; +#ifdef MAPBASE + bool m_bDrawSky; + bool m_bDrawSky2; + bool m_bUseEyePosition; +#endif // Fades float m_flFadeColor[3]; // Server's desired fade color @@ -71,6 +79,11 @@ IMPLEMENT_CLIENTCLASS_DT( C_ScriptIntro, DT_ScriptIntro, CScriptIntro ) RecvPropFloat( RECVINFO( m_flNextBlendTime ) ), RecvPropFloat( RECVINFO( m_flBlendStartTime ) ), RecvPropBool( RECVINFO( m_bActive ) ), +#ifdef MAPBASE + RecvPropBool( RECVINFO( m_bDrawSky ) ), + RecvPropBool( RECVINFO( m_bDrawSky2 ) ), + RecvPropBool( RECVINFO( m_bUseEyePosition ) ), +#endif // Fov & fov blends RecvPropInt( RECVINFO( m_iFOV ) ), @@ -140,6 +153,10 @@ void C_ScriptIntro::PostDataUpdate( DataUpdateType_t updateType ) m_IntroData.m_vecCameraViewAngles = m_vecCameraViewAngles; m_IntroData.m_Passes.SetCount( 0 ); +#ifdef MAPBASE + m_IntroData.m_bDrawSky = m_bDrawSky; +#endif + // Find/Create our first pass IntroDataBlendPass_t *pass1; if ( m_IntroData.m_Passes.Count() == 0 ) @@ -161,6 +178,20 @@ void C_ScriptIntro::PostDataUpdate( DataUpdateType_t updateType ) else { m_IntroData.m_bDrawPrimary = true; +#ifdef MAPBASE + m_IntroData.m_bDrawSky2 = m_bDrawSky2; + + // If it's a point_camera and it's ortho, send it to the intro data + // Change this code if the purpose of m_hCameraEntity in intro data ever goes beyond ortho + if ( m_hCameraEntity && Q_strncmp(m_hCameraEntity->GetClassname(), "point_camera", 12) == 0 ) + { + C_PointCamera *pCamera = dynamic_cast(m_hCameraEntity.Get()); + if (pCamera && pCamera->IsOrtho()) + { + m_IntroData.m_hCameraEntity = pCamera; + } + } +#endif } // If we're currently blending to a new mode, set the second pass @@ -239,8 +270,20 @@ void C_ScriptIntro::ClientThink( void ) if ( m_hCameraEntity ) { +#ifdef MAPBASE + if ( m_bUseEyePosition ) + { + m_hCameraEntity->GetEyePosition( m_IntroData.m_vecCameraView, m_IntroData.m_vecCameraViewAngles ); + } + else + { + m_IntroData.m_vecCameraView = m_hCameraEntity->GetAbsOrigin(); + m_IntroData.m_vecCameraViewAngles = m_hCameraEntity->GetAbsAngles(); + } +#else m_IntroData.m_vecCameraView = m_hCameraEntity->GetAbsOrigin(); m_IntroData.m_vecCameraViewAngles = m_hCameraEntity->GetAbsAngles(); +#endif } CalculateFOV(); @@ -325,3 +368,135 @@ void C_ScriptIntro::CalculateAlpha( void ) m_IntroData.m_flCurrentFadeColor[3] = flNewAlpha; } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class C_PlayerViewProxy : public C_BaseEntity +{ + DECLARE_CLASS( C_PlayerViewProxy, C_BaseEntity ); +public: + DECLARE_CLIENTCLASS(); + + Vector EyePosition( void ); // position of eyes + const QAngle &EyeAngles( void ); // Direction of eyes in world space + void GetEyePosition( Vector &vecOrigin, QAngle &angAngles ); + const QAngle &LocalEyeAngles( void ); // Direction of eyes + Vector EarPosition( void ); // position of ears + +#ifdef MAPBASE_MP + C_BasePlayer *GetPlayer() { return m_bEnabled ? (m_hPlayer.Get() ? m_hPlayer.Get() : C_BasePlayer::GetLocalPlayer()) : NULL; } +#else + C_BasePlayer *GetPlayer() { return m_bEnabled ? C_BasePlayer::GetLocalPlayer() : NULL; } +#endif + +public: +#ifdef MAPBASE_MP + CHandle m_hPlayer; +#endif + + bool m_bEnabled; +}; + +IMPLEMENT_CLIENTCLASS_DT( C_PlayerViewProxy, DT_PlayerViewProxy, CPlayerViewProxy ) +#ifdef MAPBASE_MP + RecvPropEHandle( RECVINFO( m_hPlayer ) ), +#endif + RecvPropBool( RECVINFO( m_bEnabled ) ), +END_RECV_TABLE() + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +Vector C_PlayerViewProxy::EyePosition( void ) +{ + C_BasePlayer *pPlayer = GetPlayer(); + if (pPlayer) + { + //Vector vecPlayerOffset = m_hPlayer.Get()->EyePosition() - m_hPlayer.Get()->GetAbsOrigin(); + //return GetAbsOrigin() + vecPlayerOffset; + + Vector vecOrigin; + QAngle angAngles; + float fldummy; + pPlayer->CalcView( vecOrigin, angAngles, fldummy, fldummy, fldummy ); + + return GetAbsOrigin() + (vecOrigin - pPlayer->GetAbsOrigin()); + } + else + return BaseClass::EyePosition(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +const QAngle &C_PlayerViewProxy::EyeAngles( void ) +{ + C_BasePlayer *pPlayer = GetPlayer(); + if (pPlayer) + { + Vector vecOrigin; + static QAngle angAngles; + float fldummy; + pPlayer->CalcView( vecOrigin, angAngles, fldummy, fldummy, fldummy ); + + angAngles = GetAbsAngles() + (angAngles - pPlayer->GetAbsAngles()); + return angAngles; + + //return m_hPlayer.Get()->EyeAngles(); + } + else + return BaseClass::EyeAngles(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_PlayerViewProxy::GetEyePosition( Vector &vecOrigin, QAngle &angAngles ) +{ + C_BasePlayer *pPlayer = GetPlayer(); + if (pPlayer) + { + float fldummy; + pPlayer->CalcView( vecOrigin, angAngles, fldummy, fldummy, fldummy ); + + vecOrigin = GetAbsOrigin() + (vecOrigin - pPlayer->GetAbsOrigin()); + angAngles = GetAbsAngles() + (angAngles - pPlayer->GetAbsAngles()); + } + else + { + BaseClass::GetEyePosition( vecOrigin, angAngles ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +const QAngle &C_PlayerViewProxy::LocalEyeAngles( void ) +{ + C_BasePlayer *pPlayer = GetPlayer(); + if (pPlayer) { + static QAngle angAngles; + angAngles = GetAbsAngles() + (pPlayer->LocalEyeAngles() - pPlayer->GetAbsAngles()); + return angAngles; + } + else + return BaseClass::LocalEyeAngles(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +Vector C_PlayerViewProxy::EarPosition( void ) +{ + C_BasePlayer *pPlayer = GetPlayer(); + if (pPlayer) + { + Vector vecPlayerOffset = pPlayer->GetAbsOrigin() - pPlayer->EarPosition(); + return GetAbsOrigin() + vecPlayerOffset; + } + else + return BaseClass::EarPosition(); +} +#endif + diff --git a/src/game/client/hl2/c_weapon__stubs_hl2.cpp b/src/game/client/hl2/c_weapon__stubs_hl2.cpp index 68693bfc..1bb922ee 100644 --- a/src/game/client/hl2/c_weapon__stubs_hl2.cpp +++ b/src/game/client/hl2/c_weapon__stubs_hl2.cpp @@ -33,7 +33,9 @@ STUB_WEAPON_CLASS( weapon_shotgun, WeaponShotgun, C_BaseHLCombatWeapon ); STUB_WEAPON_CLASS( weapon_smg1, WeaponSMG1, C_HLSelectFireMachineGun ); STUB_WEAPON_CLASS( weapon_357, Weapon357, C_BaseHLCombatWeapon ); STUB_WEAPON_CLASS( weapon_crossbow, WeaponCrossbow, C_BaseHLCombatWeapon ); +#ifndef MAPBASE STUB_WEAPON_CLASS( weapon_slam, Weapon_SLAM, C_BaseHLCombatWeapon ); +#endif STUB_WEAPON_CLASS( weapon_crowbar, WeaponCrowbar, C_BaseHLBludgeonWeapon ); #ifdef HL2_EPISODIC STUB_WEAPON_CLASS( weapon_hopwire, WeaponHopwire, C_BaseHLCombatWeapon ); diff --git a/src/game/client/hl2/hud_credits.cpp b/src/game/client/hl2/hud_credits.cpp index 6ad1488b..bf0e58fd 100644 --- a/src/game/client/hl2/hud_credits.cpp +++ b/src/game/client/hl2/hud_credits.cpp @@ -32,6 +32,15 @@ struct creditname_t float flTimeAdd; float flTimeStart; int iSlot; + +#ifdef MAPBASE + // New credits stuff + CCopyableUtlVector cColorOverride; + + // Images + int iImageID = -1; + float flImageScale = 1.0f; +#endif }; #define CREDITS_FILE "scripts/credits.txt" @@ -47,6 +56,9 @@ enum #define CREDITS_LOGO 1 #define CREDITS_INTRO 2 #define CREDITS_OUTRO 3 +#ifdef MAPBASE +#define CREDITS_PRECACHE 4 +#endif bool g_bRollingCredits = false; @@ -93,15 +105,27 @@ private: void DrawOutroCreditsName( void ); void DrawIntroCreditsName( void ); void DrawLogo( void ); +#ifdef MAPBASE + void DrawOutroCreditFont( const char *pCreditName, float flYPos, vgui::HFont hTFont, const Color &cColor, int iScreenWidth, int iDivisor = 2 ); + void DrawOutroCreditTexture( int iImageID, float flYPos, float flImageScale, const Color &cColor, int iScreenWidth, int iDivisor = 2 ); +#endif void PrepareLogo( float flTime ); void PrepareOutroCredits( void ); void PrepareIntroCredits( void ); +#ifdef MAPBASE + void PrecacheCredits(); +#endif + float FadeBlend( float fadein, float fadeout, float hold, float localTime ); void PrepareLine( vgui::HFont hFont, char const *pchLine ); +#ifdef MAPBASE + int GetOrAllocateImageID( const char *szFileName ); +#endif + CPanelAnimationVar( vgui::HFont, m_hTextFont, "TextFont", "Default" ); CPanelAnimationVar( Color, m_TextColor, "TextColor", "FgColor" ); @@ -109,6 +133,12 @@ private: float m_flScrollTime; float m_flSeparation; +#ifdef MAPBASE + int m_iEndLines; + float m_flEndLinesFadeHoldTime; + bool m_bAllowColumns; + CUtlDict m_ImageDict; +#endif float m_flFadeTime; bool m_bLastOneInPlace; int m_Alpha; @@ -133,6 +163,15 @@ private: char m_szLogo2[256]; Color m_cColor; + +#ifdef MAPBASE + char m_szCreditsFile[MAX_PATH]; + + char m_szLogoFont[64]; + char m_szLogo2Font[64]; + Color m_cLogoColor; + Color m_cLogo2Color; +#endif }; @@ -141,7 +180,11 @@ void CHudCredits::PrepareCredits( const char *pKeyName ) Clear(); KeyValues *pKV= new KeyValues( "CreditsFile" ); +#ifdef MAPBASE + if ( !pKV->LoadFromFile( filesystem, m_szCreditsFile, "MOD" ) ) +#else if ( !pKV->LoadFromFile( filesystem, CREDITS_FILE, "MOD" ) ) +#endif { pKV->deleteThis(); @@ -189,6 +232,20 @@ void CHudCredits::Clear( void ) m_bLastOneInPlace = false; m_Alpha = m_TextColor[3]; m_iLogoState = LOGO_FADEOFF; + +#ifdef MAPBASE + if ( surface() ) + { + for (int i = m_ImageDict.Count()-1; i >= 0; i--) + { + if (m_ImageDict[i] != -1) + { + surface()->DestroyTextureID( m_ImageDict[i] ); + m_ImageDict.RemoveAt( i ); + } + } + } +#endif } //----------------------------------------------------------------------------- @@ -216,7 +273,11 @@ void CHudCredits::ReadNames( KeyValues *pKeyValue ) { creditname_t Credits; V_strcpy_safe( Credits.szCreditName, pKVNames->GetName() ); +#ifdef MAPBASE + V_strcpy_safe( Credits.szFontName, pKVNames->GetString( (const char *)NULL, "Default" ) ); +#else V_strcpy_safe( Credits.szFontName, pKeyValue->GetString( Credits.szCreditName, "Default" ) ); +#endif m_CreditsList.AddToTail( Credits ); pKVNames = pKVNames->GetNextKey(); @@ -233,6 +294,11 @@ void CHudCredits::ReadParams( KeyValues *pKeyValue ) m_flScrollTime = pKeyValue->GetFloat( "scrolltime", 57 ); m_flSeparation = pKeyValue->GetFloat( "separation", 5 ); +#ifdef MAPBASE + m_iEndLines = pKeyValue->GetInt( "endlines", 1 ); + m_flEndLinesFadeHoldTime = pKeyValue->GetFloat( "endlines_fadeholdtime", ( IsConsole() ? 2.0f : 10.0f ) ); // "360 certification requires that we not hold a static image too long." + m_bAllowColumns = pKeyValue->GetBool( "allow_columns", false ); +#endif m_flFadeInTime = pKeyValue->GetFloat( "fadeintime", 1 ); m_flFadeHoldTime = pKeyValue->GetFloat( "fadeholdtime", 3 ); @@ -249,6 +315,14 @@ void CHudCredits::ReadParams( KeyValues *pKeyValue ) Q_strncpy( m_szLogo, pKeyValue->GetString( "logo", "HALF-LIFE'" ), sizeof( m_szLogo ) ); Q_strncpy( m_szLogo2, pKeyValue->GetString( "logo2", "" ), sizeof( m_szLogo2 ) ); + +#ifdef MAPBASE + Q_strncpy( m_szLogoFont, pKeyValue->GetString( "logofont", "" ), sizeof( m_szLogoFont ) ); + Q_strncpy( m_szLogo2Font, pKeyValue->GetString( "logo2font", "" ), sizeof( m_szLogo2Font ) ); + + m_cLogoColor = pKeyValue->GetColor( "logocolor" ); + m_cLogo2Color = pKeyValue->GetColor( "logo2color" ); +#endif } int CHudCredits::GetStringPixelWidth( wchar_t *pString, vgui::HFont hFont ) @@ -280,10 +354,46 @@ void CHudCredits::DrawOutroCreditsName( void ) if ( pCredit == NULL ) continue; +#ifdef MAPBASE + vgui::HScheme scheme = GetScheme(); +#else vgui::HScheme scheme = vgui::scheme()->GetScheme( "ClientScheme" ); - vgui::HFont m_hTFont = vgui::scheme()->GetIScheme(scheme)->GetFont( pCredit->szFontName, true ); +#endif + vgui::HFont m_hTFont = INVALID_FONT; - int iFontTall = surface()->GetFontTall ( m_hTFont ); + int iFontTall = 1; + +#ifdef MAPBASE + if (pCredit->iImageID != -1) + { + // Get the size of the tallest image if there's multiple + int iFontWide; + if (m_bAllowColumns && V_strstr( pCredit->szCreditName, "\t" )) + { + CUtlStringList outStrings; + V_SplitString( pCredit->szCreditName, "\t", outStrings ); + FOR_EACH_VEC( outStrings, i ) + { + int iTempTall; + surface()->DrawGetTextureSize( GetOrAllocateImageID( outStrings[i] ), iFontWide, iTempTall ); + if (iTempTall > iFontTall) + iFontTall = iTempTall; + } + outStrings.PurgeAndDeleteElements(); + } + else + { + surface()->DrawGetTextureSize( GetOrAllocateImageID( pCredit->szCreditName ), iFontWide, iFontTall ); + } + + iFontTall = ((float)iFontTall * pCredit->flImageScale); + } + else +#endif + { + m_hTFont = vgui::scheme()->GetIScheme( scheme )->GetFont( pCredit->szFontName, true ); + iFontTall = surface()->GetFontTall( m_hTFont ); + } if ( pCredit->flYPos < -iFontTall || pCredit->flYPos > iTall ) { @@ -296,9 +406,17 @@ void CHudCredits::DrawOutroCreditsName( void ) Color cColor = m_TextColor; +#ifdef MAPBASE + if (pCredit->cColorOverride.Count() > 0) + cColor.SetRawColor( pCredit->cColorOverride[0] ); + + // Some lines should stick around and fade out + if ( i >= m_CreditsList.Count()-m_iEndLines ) +#else //HACKHACK //Last one stays on screen and fades out if ( i == m_CreditsList.Count()-1 ) +#endif { if ( m_bLastOneInPlace == false ) { @@ -308,8 +426,12 @@ void CHudCredits::DrawOutroCreditsName( void ) { m_bLastOneInPlace = true; +#ifdef MAPBASE + m_flFadeTime = gpGlobals->curtime + m_flEndLinesFadeHoldTime; +#else // 360 certification requires that we not hold a static image too long. m_flFadeTime = gpGlobals->curtime + ( IsConsole() ? 2.0f : 10.0f ); +#endif } } else @@ -339,6 +461,62 @@ void CHudCredits::DrawOutroCreditsName( void ) if ( pCredit->bActive == false ) continue; +#ifdef MAPBASE + // Credits separated by tabs should appear divided + if (m_bAllowColumns && V_strstr( pCredit->szCreditName, "\t" )) + { + CUtlStringList outStrings; + V_SplitString( pCredit->szCreditName, "\t", outStrings ); + int iDivisor = 1 + outStrings.Count(); + if (pCredit->iImageID != -1) + { + FOR_EACH_VEC( outStrings, i ) + { + if (i < pCredit->cColorOverride.Count()) + { + // Change color to this particular column's color + cColor.SetRawColor( pCredit->cColorOverride[i] ); + } + + int iImageID = GetOrAllocateImageID( outStrings[i] ); + + // Center the image if needed + int iImageWide, iImageTall = 1; + surface()->DrawGetTextureSize( iImageID, iImageWide, iImageTall ); + if (iImageTall < iFontTall) + { + DrawOutroCreditTexture( iImageID, pCredit->flYPos + ((iFontTall * 0.5f) - (iImageTall * 0.5f)), pCredit->flImageScale, cColor, iWidth*(i + 1), iDivisor ); + } + else + { + DrawOutroCreditTexture( iImageID, pCredit->flYPos, pCredit->flImageScale, cColor, iWidth*(i + 1), iDivisor ); + } + } + } + else + { + FOR_EACH_VEC( outStrings, i ) + { + if (i < pCredit->cColorOverride.Count()) + { + // Change color to this particular column's color + cColor.SetRawColor( pCredit->cColorOverride[i] ); + } + + DrawOutroCreditFont( outStrings[i], pCredit->flYPos, m_hTFont, cColor, iWidth*(i + 1), iDivisor ); + } + } + outStrings.PurgeAndDeleteElements(); + } + else if (pCredit->iImageID != -1) + { + DrawOutroCreditTexture( pCredit->iImageID, pCredit->flYPos, pCredit->flImageScale, cColor, iWidth, 2 ); + } + else + { + DrawOutroCreditFont( pCredit->szCreditName, pCredit->flYPos, m_hTFont, cColor, iWidth, 2 ); + } +#else surface()->DrawSetTextFont( m_hTFont ); surface()->DrawSetTextColor( cColor[0], cColor[1], cColor[2], cColor[3] ); @@ -357,9 +535,56 @@ void CHudCredits::DrawOutroCreditsName( void ) surface()->DrawSetTextPos( ( iWidth / 2 ) - ( iStringWidth / 2 ), pCredit->flYPos ); surface()->DrawUnicodeString( unicode ); +#endif } } +#ifdef MAPBASE +void CHudCredits::DrawOutroCreditFont( const char *pCreditName, float flYPos, vgui::HFont hTFont, const Color &cColor, int iScreenWidth, int iDivisor ) +{ + surface()->DrawSetTextFont( hTFont ); + surface()->DrawSetTextColor( cColor[0], cColor[1], cColor[2], cColor[3] ); + + wchar_t unicode[256]; + + if ( pCreditName[0] == '#' ) + { + g_pVGuiLocalize->ConstructString( unicode, sizeof(unicode), g_pVGuiLocalize->Find(pCreditName), 0 ); + } + else + { + g_pVGuiLocalize->ConvertANSIToUnicode( pCreditName, unicode, sizeof( unicode ) ); + } + + int iStringWidth = GetStringPixelWidth( unicode, hTFont ); + + // ((iScreenWidth*iMultiplier) / iDivisor) + // When needed, just multiply iScreenWidth before sending to the function + surface()->DrawSetTextPos( (iScreenWidth / iDivisor) - (iStringWidth / 2), flYPos ); + surface()->DrawUnicodeString( unicode ); +} + +void CHudCredits::DrawOutroCreditTexture( int iImageID, float flYPos, float flImageScale, const Color &cColor, int iScreenWidth, int iDivisor ) +{ + int iImageWide, iImageTall; + surface()->DrawGetTextureSize( iImageID, iImageWide, iImageTall ); + + // Scale for resolution + flImageScale *= ((float)GetTall() / 900.0f); + + iImageWide = ((float)(iImageWide) * flImageScale); + iImageTall = ((float)(iImageTall) * flImageScale); + + iImageWide /= 2; + //iImageTall /= 2; + iScreenWidth /= iDivisor; + + surface()->DrawSetColor( cColor ); + surface()->DrawSetTexture( iImageID ); + surface()->DrawTexturedRect( iScreenWidth - iImageWide, flYPos, iScreenWidth + iImageWide, flYPos + iImageTall ); +} +#endif + void CHudCredits::DrawLogo( void ) { if( m_iLogoState == LOGO_FADEOFF ) @@ -418,6 +643,14 @@ void CHudCredits::DrawLogo( void ) char szLogoFont[64]; +#ifdef MAPBASE + if (m_szLogoFont[0] != '\0') + { + // Custom logo font + Q_strncpy( szLogoFont, m_szLogoFont, sizeof( szLogoFont ) ); + } + else +#endif if ( IsXbox() ) { Q_snprintf( szLogoFont, sizeof( szLogoFont ), "WeaponIcons_Small" ); @@ -431,13 +664,22 @@ void CHudCredits::DrawLogo( void ) Q_snprintf( szLogoFont, sizeof( szLogoFont ), "WeaponIcons" ); } +#ifdef MAPBASE + vgui::HScheme scheme = GetScheme(); +#else vgui::HScheme scheme = vgui::scheme()->GetScheme( "ClientScheme" ); +#endif vgui::HFont m_hTFont = vgui::scheme()->GetIScheme(scheme)->GetFont( szLogoFont ); int iFontTall = surface()->GetFontTall ( m_hTFont ); Color cColor = m_TextColor; cColor[3] = m_Alpha; + +#ifdef MAPBASE + if (m_cLogoColor.a() > 0) + cColor = m_cLogoColor; +#endif surface()->DrawSetTextFont( m_hTFont ); surface()->DrawSetTextColor( cColor[0], cColor[1], cColor[2], cColor[3] ); @@ -452,6 +694,19 @@ void CHudCredits::DrawLogo( void ) if ( Q_strlen( m_szLogo2 ) > 0 ) { +#ifdef MAPBASE + if (m_szLogo2Font[0] != '\0') + { + m_hTFont = vgui::scheme()->GetIScheme( scheme )->GetFont( m_szLogo2Font ); + iFontTall = surface()->GetFontTall( m_hTFont ); + surface()->DrawSetTextFont( m_hTFont ); + } + if (m_cLogo2Color.a() > 0) + { + surface()->DrawSetTextColor( m_cLogo2Color[0], m_cLogo2Color[1], m_cLogo2Color[2], m_cLogo2Color[3] ); + } +#endif + g_pVGuiLocalize->ConvertANSIToUnicode( m_szLogo2, unicode, sizeof( unicode ) ); iStringWidth = GetStringPixelWidth( unicode, m_hTFont ); @@ -511,14 +766,26 @@ void CHudCredits::DrawIntroCreditsName( void ) if ( pCredit->bActive == false ) continue; - + +#ifdef MAPBASE + vgui::HScheme scheme = GetScheme(); +#else vgui::HScheme scheme = vgui::scheme()->GetScheme( "ClientScheme" ); +#endif vgui::HFont m_hTFont = vgui::scheme()->GetIScheme(scheme)->GetFont( pCredit->szFontName ); float localTime = gpGlobals->curtime - pCredit->flTimeStart; surface()->DrawSetTextFont( m_hTFont ); +#ifdef MAPBASE + Color cColor = m_cColor; + if (pCredit->cColorOverride.Count() > 0) + cColor.SetRawColor( pCredit->cColorOverride[0] ); + + surface()->DrawSetTextColor( cColor[0], cColor[1], cColor[2], FadeBlend( m_flFadeInTime, m_flFadeOutTime, m_flFadeHoldTime + pCredit->flTimeAdd, localTime ) * cColor[3] ); +#else surface()->DrawSetTextColor( m_cColor[0], m_cColor[1], m_cColor[2], FadeBlend( m_flFadeInTime, m_flFadeOutTime, m_flFadeHoldTime + pCredit->flTimeAdd, localTime ) * m_cColor[3] ); +#endif wchar_t unicode[256]; g_pVGuiLocalize->ConvertANSIToUnicode( pCredit->szCreditName, unicode, sizeof( unicode ) ); @@ -638,6 +905,20 @@ void CHudCredits::PrepareOutroCredits( void ) int iHeight = iTall; +#ifdef MAPBASE + if (m_iEndLines <= 0) + { + // We need a credit to fade out at the end so we know when the credits are done. + // Add a dummy credit to act as the "end line". + creditname_t DummyCredit; + V_strcpy_safe( DummyCredit.szCreditName, ""); + V_strcpy_safe( DummyCredit.szFontName, "Default" ); + + m_CreditsList.AddToTail(DummyCredit); + m_iEndLines = 1; + } +#endif + for ( int i = 0; i < m_CreditsList.Count(); i++ ) { creditname_t *pCredit = &m_CreditsList[i]; @@ -645,17 +926,155 @@ void CHudCredits::PrepareOutroCredits( void ) if ( pCredit == NULL ) continue; +#ifdef MAPBASE + vgui::HScheme scheme = GetScheme(); +#else vgui::HScheme scheme = vgui::scheme()->GetScheme( "ClientScheme" ); - vgui::HFont m_hTFont = vgui::scheme()->GetIScheme(scheme)->GetFont( pCredit->szFontName, true ); +#endif - pCredit->flYPos = iHeight; - pCredit->bActive = false; +#ifdef MAPBASE + if (pCredit->szFontName[0] == '$') + { + if (V_strncmp( pCredit->szFontName + 1, "Image", 5 ) == 0) + { + if (pCredit->szFontName[6] == ';') + { + CUtlStringList outStrings; + V_SplitString( pCredit->szFontName, ";", outStrings ); + FOR_EACH_VEC( outStrings, i ) + { + switch (i) + { + // Get scale + case 1: + pCredit->flImageScale = atof( outStrings[i] ); + break; - iHeight += surface()->GetFontTall ( m_hTFont ) + m_flSeparation; + // Get color + case 2: + char *pToken = strtok( outStrings[i], "," ); + if (pToken) + { + // Multiple colors for multiple columns + while (pToken != NULL) + { + int tmp[4]; + UTIL_StringToIntArray( tmp, 4, pToken ); + pCredit->cColorOverride.AddToTail( Color( tmp[0], tmp[1], tmp[2], tmp[3] ).GetRawColor() ); - PrepareLine( m_hTFont, pCredit->szCreditName ); + pToken = strtok( NULL, "," ); + } + } + else + { + int tmp[4]; + UTIL_StringToIntArray( tmp, 4, outStrings[i] ); + pCredit->cColorOverride.AddToTail( Color( tmp[0], tmp[1], tmp[2], tmp[3] ).GetRawColor() ); + } + break; + } + } + outStrings.PurgeAndDeleteElements(); + } + + // Get the size of the tallest image if there's multiple + int iFontWide, iFontTall = 1; + if (m_bAllowColumns && V_strstr( pCredit->szCreditName, "\t" )) + { + CUtlStringList outStrings; + V_SplitString( pCredit->szCreditName, "\t", outStrings ); + FOR_EACH_VEC( outStrings, i ) + { + pCredit->iImageID = GetOrAllocateImageID( outStrings[i] ); + + int iTempTall; + surface()->DrawGetTextureSize( pCredit->iImageID, iFontWide, iTempTall ); + if (iTempTall > iFontTall) + iFontTall = iTempTall; + } + outStrings.PurgeAndDeleteElements(); + } + else + { + pCredit->iImageID = GetOrAllocateImageID( pCredit->szCreditName ); + surface()->DrawGetTextureSize( pCredit->iImageID, iFontWide, iFontTall ); + } + + pCredit->flYPos = iHeight; + pCredit->bActive = false; + + iHeight += ((float)iFontTall * pCredit->flImageScale * ((float)GetTall() / 900.0f)) + m_flSeparation; + + //Msg( "'%s' is image type (image scale is %f)\n", pCredit->szCreditName, pCredit->flImageScale ); + } + else + { + //Msg( "'%s' is not an image type\n", pCredit->szFontName + 1 ); + } + } + else +#endif + { +#ifdef MAPBASE + if (V_strstr( pCredit->szFontName, ";" )) + { + CUtlStringList outStrings; + V_SplitString( pCredit->szFontName, ";", outStrings ); + FOR_EACH_VEC( outStrings, i ) + { + switch (i) + { + // Get color + case 1: + char *pToken = strtok( outStrings[i], "," ); + if (pToken) + { + // Multiple colors for multiple columns + while (pToken != NULL) + { + int tmp[4]; + UTIL_StringToIntArray( tmp, 4, pToken ); + pCredit->cColorOverride.AddToTail( Color( tmp[0], tmp[1], tmp[2], tmp[3] ).GetRawColor() ); + + pToken = strtok( NULL, "," ); + } + } + else + { + int tmp[4]; + UTIL_StringToIntArray( tmp, 4, outStrings[i] ); + pCredit->cColorOverride.AddToTail( Color( tmp[0], tmp[1], tmp[2], tmp[3] ).GetRawColor() ); + } + break; + } + } + + Q_strncpy( pCredit->szFontName, outStrings[0], sizeof( pCredit->szFontName ) ); + outStrings.PurgeAndDeleteElements(); + } +#endif + + vgui::HFont m_hTFont = vgui::scheme()->GetIScheme( scheme )->GetFont( pCredit->szFontName, true ); + + pCredit->flYPos = iHeight; + pCredit->bActive = false; + + iHeight += surface()->GetFontTall ( m_hTFont ) + m_flSeparation; + + PrepareLine( m_hTFont, pCredit->szCreditName ); + } } +#ifdef MAPBASE + // Check if the last line has a color override. If it does, use that as the alpha for the fadeout + if (m_CreditsList.Tail().cColorOverride.Count() > 0) + { + Color clr; + clr.SetRawColor( m_CreditsList.Tail().cColorOverride[0] ); + m_Alpha = clr.a(); + } +#endif + SetActive( true ); g_iCreditsPixelHeight = iHeight; @@ -674,7 +1093,35 @@ void CHudCredits::PrepareIntroCredits( void ) if ( pCredit == NULL ) continue; +#ifdef MAPBASE + if (V_strstr( pCredit->szFontName, ";" )) + { + CUtlStringList outStrings; + V_SplitString( pCredit->szFontName, ";", outStrings ); + FOR_EACH_VEC( outStrings, i ) + { + switch (i) + { + // Get color + case 1: + // TODO: Columns? + int tmp[4]; + UTIL_StringToIntArray( tmp, 4, outStrings[i] ); + pCredit->cColorOverride.AddToTail( Color( tmp[0], tmp[1], tmp[2], tmp[3] ).GetRawColor() ); + break; + } + } + + Q_strncpy( pCredit->szFontName, outStrings[0], sizeof( pCredit->szFontName ) ); + outStrings.PurgeAndDeleteElements(); + } +#endif + +#ifdef MAPBASE + vgui::HScheme scheme = GetScheme(); +#else vgui::HScheme scheme = vgui::scheme()->GetScheme( "ClientScheme" ); +#endif vgui::HFont m_hTFont = vgui::scheme()->GetIScheme(scheme)->GetFont( pCredit->szFontName ); pCredit->flYPos = m_flY + ( iSlot * surface()->GetFontTall ( m_hTFont ) ); @@ -702,10 +1149,75 @@ void CHudCredits::PrepareIntroCredits( void ) SetActive( true ); } +#ifdef MAPBASE +void CHudCredits::PrecacheCredits() +{ + PrepareCredits( "OutroCreditsNames" ); + + if ( m_CreditsList.Count() == 0 ) + return; + + for ( int i = 0; i < m_CreditsList.Count(); i++ ) + { + creditname_t *pCredit = &m_CreditsList[i]; + + if ( pCredit == NULL ) + continue; + + if (pCredit->szFontName[0] == '$') + { + if (V_strncmp( pCredit->szFontName + 1, "Image", 5 ) == 0) + { + if (m_bAllowColumns && V_strstr( pCredit->szCreditName, "\t" )) + { + CUtlStringList outStrings; + V_SplitString( pCredit->szCreditName, "\t", outStrings ); + FOR_EACH_VEC( outStrings, i ) + { + GetOrAllocateImageID( outStrings[i] ); + } + outStrings.PurgeAndDeleteElements(); + } + else + { + GetOrAllocateImageID( pCredit->szCreditName ); + } + } + else + { + //Msg( "'%s' is not an image type\n", pCredit->szFontName + 1 ); + } + } + } + + m_CreditsList.RemoveAll(); +} + +int CHudCredits::GetOrAllocateImageID( const char *szFileName ) +{ + int iIndex = m_ImageDict.Find( szFileName ); + if (iIndex == m_ImageDict.InvalidIndex()) + { + iIndex = surface()->CreateNewTextureID(); + m_ImageDict.Insert( szFileName, iIndex ); + surface()->DrawSetTextureFile( iIndex, szFileName, true, false ); + return iIndex; + } + return m_ImageDict[iIndex]; +} +#endif + void CHudCredits::MsgFunc_CreditsMsg( bf_read &msg ) { m_iCreditsType = msg.ReadByte(); +#ifdef MAPBASE + msg.ReadString(m_szCreditsFile, sizeof(m_szCreditsFile)); + + if (m_szCreditsFile[0] == '\0') + Q_strncpy(m_szCreditsFile, CREDITS_FILE, sizeof(m_szCreditsFile)); +#endif + switch ( m_iCreditsType ) { case CREDITS_LOGO: @@ -723,13 +1235,30 @@ void CHudCredits::MsgFunc_CreditsMsg( bf_read &msg ) PrepareOutroCredits(); break; } +#ifdef MAPBASE + case CREDITS_PRECACHE: + { + PrecacheCredits(); + break; + } +#endif } } void CHudCredits::MsgFunc_LogoTimeMsg( bf_read &msg ) { m_iCreditsType = CREDITS_LOGO; +#ifdef MAPBASE + float flLogoTime = msg.ReadFloat(); + msg.ReadString(m_szCreditsFile, sizeof(m_szCreditsFile)); + + if (m_szCreditsFile[0] == '\0') + Q_strncpy(m_szCreditsFile, CREDITS_FILE, sizeof(m_szCreditsFile)); + + PrepareLogo(flLogoTime); +#else PrepareLogo( msg.ReadFloat() ); +#endif } diff --git a/src/game/client/hl2/hud_suitpower.cpp b/src/game/client/hl2/hud_suitpower.cpp index 416c9300..f1af00ac 100644 --- a/src/game/client/hl2/hud_suitpower.cpp +++ b/src/game/client/hl2/hud_suitpower.cpp @@ -104,12 +104,27 @@ void CHudSuitPower::OnThink( void ) bool breatherActive = pPlayer->IsBreatherActive(); int activeDevices = (int)flashlightActive + (int)sprintActive + (int)breatherActive; +#ifdef MAPBASE + activeDevices += (int)pPlayer->IsCustomDevice0Active() + (int)pPlayer->IsCustomDevice1Active() + (int)pPlayer->IsCustomDevice2Active(); +#endif + if (activeDevices != m_iActiveSuitDevices) { m_iActiveSuitDevices = activeDevices; switch ( m_iActiveSuitDevices ) { +#ifdef MAPBASE + case 6: + g_pClientMode->GetViewportAnimationController()->StartAnimationSequence("SuitAuxPowerSixItemsActive"); + break; + case 5: + g_pClientMode->GetViewportAnimationController()->StartAnimationSequence("SuitAuxPowerFiveItemsActive"); + break; + case 4: + g_pClientMode->GetViewportAnimationController()->StartAnimationSequence("SuitAuxPowerFourItemsActive"); + break; +#endif default: case 3: g_pClientMode->GetViewportAnimationController()->StartAnimationSequence("SuitAuxPowerThreeItemsActive"); @@ -251,6 +266,59 @@ void CHudSuitPower::Paint() } ypos += text2_gap; } + +#ifdef MAPBASE + if (pPlayer->IsCustomDevice0Active()) + { + tempString = g_pVGuiLocalize->Find("#Mapbase_Hud_DEVICE0"); + + surface()->DrawSetTextPos(text2_xpos, ypos); + + if (tempString) + { + surface()->DrawPrintText(tempString, wcslen(tempString)); + } + else + { + surface()->DrawPrintText(L"CUSTOM 0", wcslen(L"CUSTOM 0")); + } + ypos += text2_gap; + } + + if (pPlayer->IsCustomDevice1Active()) + { + tempString = g_pVGuiLocalize->Find("#Mapbase_Hud_DEVICE1"); + + surface()->DrawSetTextPos(text2_xpos, ypos); + + if (tempString) + { + surface()->DrawPrintText(tempString, wcslen(tempString)); + } + else + { + surface()->DrawPrintText(L"CUSTOM 1", wcslen(L"CUSTOM 1")); + } + ypos += text2_gap; + } + + if (pPlayer->IsCustomDevice2Active()) + { + tempString = g_pVGuiLocalize->Find("#Mapbase_Hud_DEVICE2"); + + surface()->DrawSetTextPos(text2_xpos, ypos); + + if (tempString) + { + surface()->DrawPrintText(tempString, wcslen(tempString)); + } + else + { + surface()->DrawPrintText(L"CUSTOM 2", wcslen(L"CUSTOM 2")); + } + ypos += text2_gap; + } +#endif } } diff --git a/src/game/client/hl2/hud_weaponselection.cpp b/src/game/client/hl2/hud_weaponselection.cpp index f5600705..b83d6429 100644 --- a/src/game/client/hl2/hud_weaponselection.cpp +++ b/src/game/client/hl2/hud_weaponselection.cpp @@ -644,6 +644,15 @@ void CHudWeaponSelection::Paint() // This is a bit of a misnomer... we really are asking "Is this the selected slot"? selectedWeapon = true; } +#ifdef MAPBASE + else if (!hud_showemptyweaponslots.GetBool() && !pWeapon) + { + // Revert the offset + xPos -= ( m_flMediumBoxWide + 5 ) * xModifiers[ i ]; + yPos -= ( m_flMediumBoxTall + 5 ) * yModifiers[ i ]; + continue; + } +#endif // Draw the box with the appropriate icon DrawLargeWeaponBox( pWeapon, @@ -1375,6 +1384,23 @@ void CHudWeaponSelection::PlusTypeFastWeaponSwitch( int iWeaponSlot ) // Changing vertical/horizontal direction. Reset the selected box position to zero. m_iSelectedBoxPosition = 0; m_iSelectedSlot = iWeaponSlot; + +#ifdef MAPBASE + if (!hud_showemptyweaponslots.GetBool()) + { + // Skip empty slots + int i = 0; + while ( i < MAX_WEAPON_POSITIONS ) + { + C_BaseCombatWeapon *pWeapon = GetWeaponInSlot( iWeaponSlot, i ); + if ( pWeapon ) + break; + i++; + } + + m_iSelectedBoxPosition = i; + } +#endif } else { @@ -1385,6 +1411,27 @@ void CHudWeaponSelection::PlusTypeFastWeaponSwitch( int iWeaponSlot ) // Decrementing within the slot. If we're at the zero position in this slot, // jump to the zero position of the opposite slot. This also counts as our increment. increment = -1; +#ifdef MAPBASE + if (!hud_showemptyweaponslots.GetBool()) + { + // Skip empty slots + int iZeroPos = 0; + while ( iZeroPos < MAX_WEAPON_POSITIONS ) + { + C_BaseCombatWeapon *pWeapon = GetWeaponInSlot( m_iSelectedSlot, iZeroPos ); + if ( pWeapon ) + break; + iZeroPos++; + } + + if ( iZeroPos == m_iSelectedBoxPosition ) + { + newSlot = ( m_iSelectedSlot + 2 ) % 4; + m_iSelectedBoxPosition = increment = 0; + } + } + else +#endif if ( 0 == m_iSelectedBoxPosition ) { newSlot = ( m_iSelectedSlot + 2 ) % 4; @@ -1402,6 +1449,35 @@ void CHudWeaponSelection::PlusTypeFastWeaponSwitch( int iWeaponSlot ) lastSlotPos = slotPos; } } + +#ifdef MAPBASE + if (!hud_showemptyweaponslots.GetBool()) + { + // Skip empty slots + int i = m_iSelectedBoxPosition + increment; + while ( i >= 0 && i < lastSlotPos ) + { + C_BaseCombatWeapon *pWeapon = GetWeaponInSlot( newSlot, i ); + if ( !pWeapon ) + { + if (increment < 0) + { + increment--; + i--; + } + else + { + increment++; + i++; + } + } + else + { + break; + } + } + } +#endif // Increment/Decrement the selected box position if ( m_iSelectedBoxPosition + increment <= lastSlotPos ) diff --git a/src/game/client/hud.cpp b/src/game/client/hud.cpp index e00129fb..4afd7044 100644 --- a/src/game/client/hud.cpp +++ b/src/game/client/hud.cpp @@ -134,10 +134,13 @@ void LoadHudTextures( CUtlDict< CHudTexture *, int >& list, const char *szFilena pTemp = pTemp->GetNextKey(); } } - } - // Failed for some reason. Delete the Key data and abort. - pKeyValuesData->deleteThis(); + pKeyValuesData->deleteThis(); + } + else + { + Warning( "Unable to read script %s.\n", szFilenameWithoutExtension ); + } } //----------------------------------------------------------------------------- @@ -472,6 +475,8 @@ void CHud::Init( void ) } FreeHudTextureList( textureList ); + + HudIcons().Init(); } //----------------------------------------------------------------------------- @@ -1200,3 +1205,232 @@ CON_COMMAND_F( testhudanim, "Test a hud element animation.\n\tArguments: GetViewportAnimationController()->StartAnimationSequence( args[1] ); } +CHudIcons::CHudIcons() : + m_bHudTexturesLoaded( false ) +{ +} + +CHudIcons::~CHudIcons() +{ + int c = m_Icons.Count(); + for ( int i = c - 1; i >= 0; i-- ) + { + CHudTexture *tex = m_Icons[ i ]; + g_HudTextureMemoryPool.Free( tex ); + } + m_Icons.Purge(); +} + +void CHudIcons::Init() +{ + if ( m_bHudTexturesLoaded ) + return; + + m_bHudTexturesLoaded = true; + CUtlDict< CHudTexture *, int > textureList; + + // check to see if we have sprites for this res; if not, step down + LoadHudTextures( textureList, "scripts/hud_textures", NULL ); + LoadHudTextures( textureList, "scripts/mod_textures", NULL ); + + LoadHudTextures( textureList, "scripts/instructor_textures", NULL ); +#ifdef HL2_CLIENT_DLL + LoadHudTextures( textureList, "scripts/instructor_textures_hl2", NULL ); +#endif + LoadHudTextures( textureList, "scripts/instructor_modtextures", NULL ); + + int c = textureList.Count(); + for ( int index = 0; index < c; index++ ) + { + CHudTexture* tex = textureList[ index ]; + AddSearchableHudIconToList( *tex ); + } + + FreeHudTextureList( textureList ); +} + +void CHudIcons::Shutdown() +{ + m_bHudTexturesLoaded = false; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CHudTexture *CHudIcons::AddUnsearchableHudIconToList( CHudTexture& texture ) +{ + // These names are composed based on the texture file name + char composedName[ 512 ]; + + if ( texture.bRenderUsingFont ) + { + Q_snprintf( composedName, sizeof( composedName ), "%s_c%i", + texture.szTextureFile, texture.cCharacterInFont ); + } + else + { + Q_snprintf( composedName, sizeof( composedName ), "%s_%i_%i_%i_%i", + texture.szTextureFile, texture.rc.left, texture.rc.top, texture.rc.right, texture.rc.bottom ); + } + + CHudTexture *icon = GetIcon( composedName ); + if ( icon ) + { + return icon; + } + + CHudTexture *newTexture = ( CHudTexture * )g_HudTextureMemoryPool.Alloc(); + *newTexture = texture; + + SetupNewHudTexture( newTexture ); + + int idx = m_Icons.Insert( composedName, newTexture ); + return m_Icons[ idx ]; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CHudTexture *CHudIcons::AddSearchableHudIconToList( CHudTexture& texture ) +{ + CHudTexture *icon = GetIcon( texture.szShortName ); + if ( icon ) + { + return icon; + } + + CHudTexture *newTexture = ( CHudTexture * )g_HudTextureMemoryPool.Alloc(); + *newTexture = texture; + + SetupNewHudTexture( newTexture ); + + int idx = m_Icons.Insert( texture.szShortName, newTexture ); + return m_Icons[ idx ]; +} + +//----------------------------------------------------------------------------- +// Purpose: returns a pointer to an icon in the list +//----------------------------------------------------------------------------- +CHudTexture *CHudIcons::GetIcon( const char *szIcon ) +{ + int i = m_Icons.Find( szIcon ); + if ( i == m_Icons.InvalidIndex() ) + return NULL; + + return m_Icons[ i ]; +} + +//----------------------------------------------------------------------------- +// Purpose: Gets texture handles for the hud icon +//----------------------------------------------------------------------------- +void CHudIcons::SetupNewHudTexture( CHudTexture *t ) +{ + if ( t->bRenderUsingFont ) + { + vgui::HScheme scheme = vgui::scheme()->GetScheme( "ClientScheme" ); + t->hFont = vgui::scheme()->GetIScheme(scheme)->GetFont( t->szTextureFile, true ); + t->rc.top = 0; + t->rc.left = 0; + t->rc.right = vgui::surface()->GetCharacterWidth( t->hFont, t->cCharacterInFont ); + t->rc.bottom = vgui::surface()->GetFontTall( t->hFont ); + } + else + { + // Set up texture id and texture coordinates + t->textureId = vgui::surface()->CreateNewTextureID(); + vgui::surface()->DrawSetTextureFile( t->textureId, t->szTextureFile, false, false ); + + int wide, tall; + vgui::surface()->DrawGetTextureSize( t->textureId, wide, tall ); + + t->texCoords[ 0 ] = (float)(t->rc.left + 0.5f) / (float)wide; + t->texCoords[ 1 ] = (float)(t->rc.top + 0.5f) / (float)tall; + t->texCoords[ 2 ] = (float)(t->rc.right - 0.5f) / (float)wide; + t->texCoords[ 3 ] = (float)(t->rc.bottom - 0.5f) / (float)tall; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CHudIcons::RefreshHudTextures() +{ + if ( !m_bHudTexturesLoaded ) + { + Assert( 0 ); + return; + } + + CUtlDict< CHudTexture *, int > textureList; + + // check to see if we have sprites for this res; if not, step down + LoadHudTextures( textureList, "scripts/hud_textures", NULL ); + LoadHudTextures( textureList, "scripts/mod_textures", NULL ); + + LoadHudTextures( textureList, "scripts/instructor_textures", NULL ); + + + // fix up all the texture icons first + int c = textureList.Count(); + for ( int index = 0; index < c; index++ ) + { + CHudTexture *tex = textureList[ index ]; + Assert( tex ); + + CHudTexture *icon = GetIcon( tex->szShortName ); + if ( !icon ) + continue; + + // Update file + Q_strncpy( icon->szTextureFile, tex->szTextureFile, sizeof( icon->szTextureFile ) ); + + if ( !icon->bRenderUsingFont ) + { + // Update subrect + icon->rc = tex->rc; + + // Keep existing texture id, but now update texture file and texture coordinates + vgui::surface()->DrawSetTextureFile( icon->textureId, icon->szTextureFile, false, false ); + + // Get new texture dimensions in case it changed + int wide, tall; + vgui::surface()->DrawGetTextureSize( icon->textureId, wide, tall ); + + // Assign coords + icon->texCoords[ 0 ] = (float)(icon->rc.left + 0.5f) / (float)wide; + icon->texCoords[ 1 ] = (float)(icon->rc.top + 0.5f) / (float)tall; + icon->texCoords[ 2 ] = (float)(icon->rc.right - 0.5f) / (float)wide; + icon->texCoords[ 3 ] = (float)(icon->rc.bottom - 0.5f) / (float)tall; + } + } + + FreeHudTextureList( textureList ); + + // fixup all the font icons + vgui::HScheme scheme = vgui::scheme()->GetScheme( "ClientScheme" ); + for (int i = m_Icons.First(); m_Icons.IsValidIndex(i); i = m_Icons.Next(i)) + { + CHudTexture *icon = m_Icons[i]; + if ( !icon ) + continue; + + // Update file + if ( icon->bRenderUsingFont ) + { + icon->hFont = vgui::scheme()->GetIScheme(scheme)->GetFont( icon->szTextureFile, true ); + icon->rc.top = 0; + icon->rc.left = 0; + icon->rc.right = vgui::surface()->GetCharacterWidth( icon->hFont, icon->cCharacterInFont ); + icon->rc.bottom = vgui::surface()->GetFontTall( icon->hFont ); + } + } +} + + +static CHudIcons g_HudIcons; + +CHudIcons &HudIcons() +{ + return g_HudIcons; +} + diff --git a/src/game/client/hud.h b/src/game/client/hud.h index 3005f67a..d836cbf1 100644 --- a/src/game/client/hud.h +++ b/src/game/client/hud.h @@ -203,6 +203,37 @@ private: extern CHud gHUD; +//----------------------------------------------------------------------------- +// Purpose: CHudIcons +//----------------------------------------------------------------------------- +class CHudIcons +{ +public: + CHudIcons(); + ~CHudIcons(); + + void Init(); + void Shutdown(); + + CHudTexture *GetIcon( const char *szIcon ); + + // loads a new icon into the list, without duplicates + CHudTexture *AddUnsearchableHudIconToList( CHudTexture& texture ); + CHudTexture *AddSearchableHudIconToList( CHudTexture& texture ); + + void RefreshHudTextures(); + +private: + + void SetupNewHudTexture( CHudTexture *t ); + bool m_bHudTexturesLoaded; + // Global list of known icons + CUtlDict< CHudTexture *, int > m_Icons; + +}; + +CHudIcons &HudIcons(); + //----------------------------------------------------------------------------- // Global fonts used in the client DLL //----------------------------------------------------------------------------- diff --git a/src/game/client/hud_closecaption.cpp b/src/game/client/hud_closecaption.cpp index 2d9ca9da..b033f833 100644 --- a/src/game/client/hud_closecaption.cpp +++ b/src/game/client/hud_closecaption.cpp @@ -31,12 +31,20 @@ extern ISoundEmitterSystemBase *soundemitterbase; // Marked as FCVAR_USERINFO so that the server can cull CC messages before networking them down to us!!! +#ifdef MAPBASE +ConVar closecaption( "closecaption", "1", FCVAR_ARCHIVE | FCVAR_ARCHIVE_XBOX | FCVAR_USERINFO, "Enable close captioning." ); +#else ConVar closecaption( "closecaption", "0", FCVAR_ARCHIVE | FCVAR_ARCHIVE_XBOX | FCVAR_USERINFO, "Enable close captioning." ); +#endif extern ConVar cc_lang; static ConVar cc_linger_time( "cc_linger_time", "1.0", FCVAR_ARCHIVE, "Close caption linger time." ); static ConVar cc_predisplay_time( "cc_predisplay_time", "0.25", FCVAR_ARCHIVE, "Close caption delay before showing caption." ); static ConVar cc_captiontrace( "cc_captiontrace", "1", 0, "Show missing closecaptions (0 = no, 1 = devconsole, 2 = show in hud)" ); +#ifdef MAPBASE +static ConVar cc_subtitles( "cc_subtitles", "1", FCVAR_ARCHIVE | FCVAR_ARCHIVE_XBOX, "If set, don't show sound effect captions, just voice overs (i.e., won't help hearing impaired players)." ); +#else static ConVar cc_subtitles( "cc_subtitles", "0", FCVAR_ARCHIVE | FCVAR_ARCHIVE_XBOX, "If set, don't show sound effect captions, just voice overs (i.e., won't help hearing impaired players)." ); +#endif ConVar english( "english", "1", FCVAR_USERINFO, "If set to 1, running the english language set of assets." ); static ConVar cc_smallfontlength( "cc_smallfontlength", "300", 0, "If text stream is this long, force usage of small font size." ); @@ -1313,7 +1321,7 @@ bool CHudCloseCaption::SplitCommand( wchar_t const **ppIn, wchar_t *cmd, int nCm cmd[ 0 ] = 0; wchar_t *out = cmd; in++; - while ( *in != L'\0' && *in != L':' && *in != L'>' && !isspace( *in ) ) + while ( *in != L'\0' && *in != L':' && *in != L'>' && !V_isspace( *in ) ) { // If there won't be enough room to null terminate, then we need to fail this. if ( ( 1 + out - cmd ) == nCmdSize ) @@ -1503,9 +1511,23 @@ void CHudCloseCaption::Process( const wchar_t *stream, float duration, const cha if ( m_Items.Count() > 0 ) { +#ifndef MAPBASE // Get the remaining life span of the last item - CCloseCaptionItem *final = m_Items[ m_Items.Count() - 1 ]; + CCloseCaptionItem* final = m_Items[m_Items.Count() - 1]; float prevlife = final->GetTimeToLive(); +#else + float prevlife = 0.f; + // Get the remaining life span of the last displayed item + for (int i = m_Items.Count() - 1; i >= 0; i--) + { + if (m_Items[i]->GetPreDisplayTime() > cc_predisplay_time.GetFloat()) + continue; + + prevlife = m_Items[i]->GetTimeToLive(); + break; + } +#endif // !MAPBASE + if ( prevlife > lifespan ) { @@ -1539,7 +1561,31 @@ void CHudCloseCaption::Process( const wchar_t *stream, float duration, const cha if ( wcslen( phrase ) > 0 ) { CCloseCaptionItem *item = new CCloseCaptionItem( phrase, lifespan, addedlife, delay, valid, fromplayer ); - m_Items.AddToTail( item ); +#ifdef MAPBASE + if (m_Items.Count()) + { + // Add it where it will appear + for (int i = m_Items.Count() - 1; i >= 0; i--) + { + if (m_Items[i]->GetPreDisplayTime() > delay + cc_predisplay_time.GetFloat()) + { + if (i == 0) + { + m_Items.AddToHead(item); + break; + } + else + continue; + } + + m_Items.InsertAfter(i, item); + break; + } + } + else +#endif // MAPBASE + m_Items.AddToTail(item); + if ( StreamHasCommand( phrase, L"sfx" ) ) { // SFX show up instantly. @@ -1548,6 +1594,9 @@ void CHudCloseCaption::Process( const wchar_t *stream, float duration, const cha if ( GetFloatCommandValue( phrase, L"len", override_duration ) ) { +#ifdef MAPBASE + override_duration += cc_linger_time.GetFloat(); +#endif // MAPBASE item->SetTimeToLive( override_duration ); } } @@ -1576,7 +1625,30 @@ void CHudCloseCaption::Process( const wchar_t *stream, float duration, const cha if ( wcslen( phrase ) > 0 ) { CCloseCaptionItem *item = new CCloseCaptionItem( phrase, lifespan, addedlife, delay, valid, fromplayer ); - m_Items.AddToTail( item ); +#ifdef MAPBASE + if (m_Items.Count()) + { + // Add it where it will appear + for (int i = m_Items.Count() - 1; i >= 0; i--) + { + if (m_Items[i]->GetPreDisplayTime() > delay + cc_predisplay_time.GetFloat()) + { + if (i == 0) + { + m_Items.AddToHead(item); + break; + } + else + continue; + } + + m_Items.InsertAfter(i, item); + break; + } + } + else +#endif // MAPBASE + m_Items.AddToTail(item); if ( StreamHasCommand( phrase, L"sfx" ) ) { @@ -1586,6 +1658,10 @@ void CHudCloseCaption::Process( const wchar_t *stream, float duration, const cha if ( GetFloatCommandValue( phrase, L"len", override_duration ) ) { +#ifdef MAPBASE + override_duration += cc_linger_time.GetFloat(); +#endif // MAPBASE + item->SetTimeToLive( override_duration ); item->SetInitialLifeSpan( override_duration ); } @@ -1625,6 +1701,9 @@ struct WorkUnitParams clr = Color( 255, 255, 255, 255 ); newline = false; font = 0; +#ifdef MAPBASE + customFont = false; +#endif } ~WorkUnitParams() @@ -1670,6 +1749,9 @@ struct WorkUnitParams Color clr; bool newline; vgui::HFont font; +#ifdef MAPBASE + bool customFont; +#endif }; void CHudCloseCaption::AddWorkUnit( CCloseCaptionItem *item, @@ -1784,27 +1866,58 @@ void CHudCloseCaption::ComputeStreamWork( int available_width, CCloseCaptionItem { AddWorkUnit( item, params ); params.italic = !params.italic; +#ifdef MAPBASE + params.customFont = false; +#endif } else if ( !wcscmp( cmd, L"B" ) ) { AddWorkUnit( item, params ); params.bold = !params.bold; +#ifdef MAPBASE + params.customFont = false; +#endif } +#ifdef MAPBASE + else if ( !wcscmp( cmd, L"font" ) ) + { + AddWorkUnit( item, params ); + vgui::IScheme *pScheme = vgui::scheme()->GetIScheme( GetScheme() ); + + if ( args[0] != 0 ) + { + char font[64]; + g_pVGuiLocalize->ConvertUnicodeToANSI( args, font, sizeof( font ) ); + params.font = pScheme->GetFont( font ); + params.customFont = true; + } + else + { + params.customFont = false; + } + } +#endif continue; } - int font; - if ( IsPC() ) + vgui::HFont useF = params.font; +#ifdef MAPBASE + if (params.customFont == false) +#endif { - font = params.GetFontNumber(); + int font; + if ( IsPC() ) + { + font = params.GetFontNumber(); + } + else + { + font = streamlen >= cc_smallfontlength.GetInt() ? CCFONT_SMALL : CCFONT_NORMAL; + } + useF = m_hFonts[font]; + params.font = useF; } - else - { - font = streamlen >= cc_smallfontlength.GetInt() ? CCFONT_SMALL : CCFONT_NORMAL; - } - vgui::HFont useF = m_hFonts[font]; - params.font = useF; int w, h; @@ -2584,8 +2697,14 @@ void CHudCloseCaption::InitCaptionDictionary( const char *dbfile ) g_AsyncCaptionResourceManager.Clear(); +#ifdef MAPBASE + int iBufferSize = filesystem->GetSearchPath("GAME", true, nullptr, 0); + char* searchPaths = (char*)stackalloc(iBufferSize); + filesystem->GetSearchPath("GAME", true, searchPaths, iBufferSize); +#else char searchPaths[4096]; filesystem->GetSearchPath( "GAME", true, searchPaths, sizeof( searchPaths ) ); +#endif for ( char *path = strtok( searchPaths, ";" ); path; path = strtok( NULL, ";" ) ) { @@ -2596,8 +2715,13 @@ void CHudCloseCaption::InitCaptionDictionary( const char *dbfile ) } char fullpath[MAX_PATH]; - Q_snprintf( fullpath, sizeof( fullpath ), "%s%s", path, dbfile ); - Q_FixSlashes( fullpath ); +#ifndef MAPBASE + Q_snprintf(fullpath, sizeof(fullpath), "%s%s", path, dbfile); + Q_FixSlashes(fullpath); +#else + V_ComposeFileName(path, dbfile, fullpath, sizeof(fullpath)); +#endif // !MAPBASE + if ( IsX360() ) { @@ -2643,6 +2767,123 @@ void CHudCloseCaption::InitCaptionDictionary( const char *dbfile ) g_AsyncCaptionResourceManager.SetDbInfo( m_AsyncCaptions ); } +#ifdef MAPBASE +void CHudCloseCaption::AddAdditionalCaptionDictionary( const char *dbfile, CUtlVector &outPathSymbols ) +{ + CGMsg( 1, CON_GROUP_MAPBASE_MISC, "Adding additional caption dictionary \"%s\"\n", dbfile ); + + g_AsyncCaptionResourceManager.Clear(); + + char searchPaths[4096]; + filesystem->GetSearchPath( "MOD", true, searchPaths, sizeof( searchPaths ) ); + + for ( char *path = strtok( searchPaths, ";" ); path; path = strtok( NULL, ";" ) ) + { + if ( IsX360() && ( filesystem->GetDVDMode() == DVDMODE_STRICT ) && !V_stristr( path, ".zip" ) ) + { + // only want zip paths + continue; + } + + char fullpath[MAX_PATH]; + V_ComposeFileName(path, dbfile, fullpath, sizeof(fullpath)); + + if ( IsX360() ) + { + char fullpath360[MAX_PATH]; + UpdateOrCreateCaptionFile( fullpath, fullpath360, sizeof( fullpath360 ) ); + Q_strncpy( fullpath, fullpath360, sizeof( fullpath ) ); + } + + // Seach for this dictionary. If it already exists, remove it. + for (int i = 0; i < m_AsyncCaptions.Count(); ++i) + { + if (FStrEq( m_AsyncCaptions[i].m_DataBaseFile.String(), fullpath )) + { + m_AsyncCaptions.Remove( i ); + break; + } + } + + FileHandle_t fh = filesystem->Open( fullpath, "rb" ); + if ( FILESYSTEM_INVALID_HANDLE != fh ) + { + MEM_ALLOC_CREDIT(); + + CUtlBuffer dirbuffer; + + AsyncCaption_t& entry = m_AsyncCaptions[ m_AsyncCaptions.AddToTail() ]; + + // Read the header + filesystem->Read( &entry.m_Header, sizeof( entry.m_Header ), fh ); + if ( entry.m_Header.magic != COMPILED_CAPTION_FILEID ) + Error( "Invalid file id for %s\n", fullpath ); + if ( entry.m_Header.version != COMPILED_CAPTION_VERSION ) + Error( "Invalid file version for %s\n", fullpath ); + if ( entry.m_Header.directorysize < 0 || entry.m_Header.directorysize > 64 * 1024 ) + Error( "Invalid directory size %d for %s\n", entry.m_Header.directorysize, fullpath ); + //if ( entry.m_Header.blocksize != MAX_BLOCK_SIZE ) + // Error( "Invalid block size %d, expecting %d for %s\n", entry.m_Header.blocksize, MAX_BLOCK_SIZE, fullpath ); + + int directoryBytes = entry.m_Header.directorysize * sizeof( CaptionLookup_t ); + entry.m_CaptionDirectory.EnsureCapacity( entry.m_Header.directorysize ); + dirbuffer.EnsureCapacity( directoryBytes ); + + filesystem->Read( dirbuffer.Base(), directoryBytes, fh ); + filesystem->Close( fh ); + + entry.m_CaptionDirectory.CopyArray( (const CaptionLookup_t *)dirbuffer.PeekGet(), entry.m_Header.directorysize ); + entry.m_CaptionDirectory.RedoSort( true ); + + entry.m_DataBaseFile = fullpath; + outPathSymbols.AddToTail( entry.m_DataBaseFile ); + } + } + + g_AsyncCaptionResourceManager.SetDbInfo( m_AsyncCaptions ); +} + +void CHudCloseCaption::AddCustomCaptionFile( char const *file, CUtlVector &outPathSymbols ) +{ + // + // 'file' should be something like "maps/mapbase_demo01_closecaption_%language%" + // + + CGMsg( 1, CON_GROUP_MAPBASE_MISC, "Adding custom caption file \"%s\"\n", file ); + + if (!IsX360()) + { + g_pVGuiLocalize->AddFile( file, "MOD", true ); + } + + char uilanguage[64]; + engine->GetUILanguage( uilanguage, sizeof( uilanguage ) ); + + char dbfile[512]; + V_StrSubst( file, "%language%", uilanguage, dbfile, sizeof( dbfile ) ); + V_SetExtension( dbfile, ".dat", sizeof( dbfile ) ); + AddAdditionalCaptionDictionary( dbfile, outPathSymbols ); +} + +void CHudCloseCaption::RemoveCaptionDictionary( const CUtlSymbol &dbFileSymbol ) +{ + // + // 'file' should be something like "maps/mapbase_demo01_closecaption_%language%" + // + + CGMsg( 1, CON_GROUP_MAPBASE_MISC, "Removing custom caption file \"%s\"\n", dbFileSymbol.String() ); + + for (int i = 0; i < m_AsyncCaptions.Count(); ++i) + { + if ( m_AsyncCaptions[i].m_DataBaseFile == dbFileSymbol ) + { + m_AsyncCaptions.Remove( i ); + break; + } + } +} +#endif + void CHudCloseCaption::OnFinishAsyncLoad( int nFileIndex, int nBlockNum, AsyncCaptionData_t *pData ) { // Fill in data for all users of pData->m_nBlockNum @@ -2713,6 +2954,11 @@ CON_COMMAND_F_COMPLETION( cc_emit, "Emits a closed caption", 0, EmitCaptionCompl return; } +#ifdef MAPBASE // 1upD + if (!closecaption.GetBool()) + return; +#endif + CHudCloseCaption *hudCloseCaption = GET_HUDELEMENT( CHudCloseCaption ); if ( hudCloseCaption ) { diff --git a/src/game/client/hud_closecaption.h b/src/game/client/hud_closecaption.h index 2a14de3b..2c8c9713 100644 --- a/src/game/client/hud_closecaption.h +++ b/src/game/client/hud_closecaption.h @@ -111,6 +111,11 @@ public: void PlayRandomCaption(); void InitCaptionDictionary( char const *dbfile ); +#ifdef MAPBASE + void AddAdditionalCaptionDictionary( char const *dbfile, CUtlVector &outPathSymbols ); + void AddCustomCaptionFile( char const *file, CUtlVector &outPathSymbols ); + void RemoveCaptionDictionary( const CUtlSymbol &dbFileSymbol ); +#endif void OnFinishAsyncLoad( int nFileIndex, int nBlockNum, AsyncCaptionData_t *pData ); void Flush(); @@ -133,6 +138,11 @@ public: void FindSound( char const *pchANSI ); +#ifdef MAPBASE + inline bool IsUsingCommentaryDimensions() const { return m_bUsingCommentaryDimensions; } + inline void SetUsingCommentaryDimensions( bool bToggle ) { m_bUsingCommentaryDimensions = bToggle; } +#endif + public: struct CaptionRepeat @@ -213,6 +223,10 @@ private: bool m_bVisibleDueToDirect; bool m_bPaintDebugInfo; CUtlSymbol m_CurrentLanguage; + +#ifdef MAPBASE + bool m_bUsingCommentaryDimensions; +#endif }; #endif // HUD_CLOSECAPTION_H diff --git a/src/game/client/hud_hintdisplay.cpp b/src/game/client/hud_hintdisplay.cpp index 040ce5bc..d371a548 100644 --- a/src/game/client/hud_hintdisplay.cpp +++ b/src/game/client/hud_hintdisplay.cpp @@ -599,10 +599,39 @@ bool CHudHintKeyDisplay::SetHintText( const char *text ) else { const char *key = engine->Key_LookupBinding( *binding == '+' ? binding + 1 : binding ); +#ifdef MAPBASE + if ( !key ) + { + const char *pszNotBound = VarArgs("< %s, not bound >", *binding == '+' ? binding + 1 : binding); + if (strchr(binding, '&')) + { + // "%walk&use%" >> "ALT + E" + char *token = strtok(binding, "&"); + while (token) + { + const char *tokenkey = engine->Key_LookupBinding( *token == '+' ? token + 1 : token ); + + key = VarArgs("%s%s%s", key ? key : "", key ? " + " : "", tokenkey ? tokenkey : pszNotBound); + + token = strtok(NULL, "&"); + } + } + else if (binding[0] == '$') + { + // "%$COOL STRING DUDE%" >> "COOL STRING DUDE" + key = binding + 1; + } + else + { + key = pszNotBound; + } + } +#else if ( !key ) { key = "< not bound >"; } +#endif Q_snprintf( friendlyName, sizeof(friendlyName), "#%s", key ); Q_strupr( friendlyName ); diff --git a/src/game/client/hud_lcd.cpp b/src/game/client/hud_lcd.cpp index e66f9060..68e26284 100644 --- a/src/game/client/hud_lcd.cpp +++ b/src/game/client/hud_lcd.cpp @@ -1188,16 +1188,16 @@ void CLCD::DumpPlayer() C_Team *team = player->GetTeam(); if ( team ) { - CDescribeData helper( team ); - helper.DumpDescription( team->GetPredDescMap() ); + CDescribeData helperLocl( team ); + helperLocl.DumpDescription( team->GetPredDescMap() ); } Msg( "(playerresource)\n\n" ); if ( g_PR ) { - CDescribeData helper( g_PR ); - helper.DumpDescription( g_PR->GetPredDescMap() ); + CDescribeData helperLocl( g_PR ); + helperLocl.DumpDescription( g_PR->GetPredDescMap() ); } Msg( "(localplayerweapon)\n\n" ); @@ -1205,8 +1205,8 @@ void CLCD::DumpPlayer() C_BaseCombatWeapon *active = player->GetActiveWeapon(); if ( active ) { - CDescribeData helper( active ); - helper.DumpDescription( active->GetPredDescMap() ); + CDescribeData helperLocl( active ); + helperLocl.DumpDescription( active->GetPredDescMap() ); } Msg( "Other replacements:\n\n" ); diff --git a/src/game/client/hud_locator_target.cpp b/src/game/client/hud_locator_target.cpp new file mode 100644 index 00000000..2a8af109 --- /dev/null +++ b/src/game/client/hud_locator_target.cpp @@ -0,0 +1,2207 @@ +//========= Copyright © 1996-2008, Valve Corporation, All rights reserved. ============// +// +// Purpose: See header file +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" +#include "hud_locator_target.h" +#include "iclientmode.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include "iinput.h" +#include "view.h" +#include "hud.h" +#include "hudelement.h" +#include "vgui_int.h" + +#include "hud_macros.h" +#include "iclientmode.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + + +#define ICON_SIZE 0.04f // Icons are ScreenWidth() * ICON_SIZE wide. +#define ICON_GAP 5 // Number of pixels between the icon and the text + +#define OFFSCREEN_ICON_POSITION_RADIUS 100 +#define BUTTON_FONT_HANDLE m_hCaptionFont + +#define ICON_DIST_TOO_FAR (60.0f * 12.0f) + +#define MIN_ICON_ALPHA 0.5 +#define MAX_ICON_ALPHA 1 + +ConVar locator_icon_min_size_non_ss( "locator_icon_min_size_non_ss", "1.0", FCVAR_NONE, "Minimum scale of the icon on the screen" ); +ConVar locator_icon_max_size_non_ss( "locator_icon_max_size_non_ss", "1.5", FCVAR_NONE, "Maximum scale of the icon on the screen" ); + +#define MIN_ICON_SCALE locator_icon_min_size_non_ss.GetFloat() +#define MAX_ICON_SCALE locator_icon_max_size_non_ss.GetFloat() + +#define LOCATOR_OCCLUSION_TEST_RATE 0.25f + +enum +{ + DRAW_ARROW_NO = 0, + DRAW_ARROW_UP, + DRAW_ARROW_DOWN, + DRAW_ARROW_LEFT, + DRAW_ARROW_RIGHT +}; + +ConVar locator_fade_time( "locator_fade_time", "0.3", FCVAR_NONE, "Number of seconds it takes for a lesson to fully fade in/out." ); +ConVar locator_lerp_speed( "locator_lerp_speed", "5.0f", FCVAR_NONE, "Speed that static lessons move along the Y axis." ); +ConVar locator_lerp_rest( "locator_lerp_rest", "2.25f", FCVAR_NONE, "Number of seconds before moving from the center." ); +ConVar locator_lerp_time( "locator_lerp_time", "1.75f", FCVAR_NONE, "Number of seconds to lerp before reaching final destination" ); +ConVar locator_pulse_time( "locator_pulse_time", "1.0f", FCVAR_NONE, "Number of seconds to pulse after changing icon or position" ); +ConVar locator_start_at_crosshair( "locator_start_at_crosshair", "0", FCVAR_NONE, "Start position at the crosshair instead of the top middle of the screen." ); + +ConVar locator_topdown_style( "locator_topdown_style", "0", FCVAR_NONE, "Topdown games set this to handle distance and offscreen location differently." ); + +ConVar locator_background_style( "locator_background_style", "0", FCVAR_NONE, "Setting this to 1 will show rectangle backgrounds behind the items word-bubble pointers." ); +ConVar locator_background_color( "locator_background_color", "255 255 255 5", FCVAR_NONE, "The default color for the background." ); +ConVar locator_background_border_color( "locator_background_border_color", "255 255 255 15", FCVAR_NONE, "The default color for the border." ); +ConVar locator_background_thickness_x( "locator_background_thickness_x", "8", FCVAR_NONE, "How many pixels the background borders the left and right." ); +ConVar locator_background_thickness_y( "locator_background_thickness_y", "0", FCVAR_NONE, "How many pixels the background borders the top and bottom." ); +ConVar locator_background_shift_x( "locator_background_shift_x", "3", FCVAR_NONE, "How many pixels the background is shifted right." ); +ConVar locator_background_shift_y( "locator_background_shift_y", "1", FCVAR_NONE, "How many pixels the background is shifted down." ); +ConVar locator_background_border_thickness( "locator_background_border_thickness", "3", FCVAR_NONE, "How many pixels the background borders the left and right." ); + +ConVar locator_target_offset_x( "locator_target_offset_x", "0", FCVAR_NONE, "How many pixels to offset the locator from the target position." ); +ConVar locator_target_offset_y( "locator_target_offset_y", "0", FCVAR_NONE, "How many pixels to offset the locator from the target position." ); + +ConVar locator_text_drop_shadow( "locator_text_drop_shadow", "1", FCVAR_NONE, "If enabled, a drop shadow is drawn behind caption text. PC only." ); +ConVar locator_text_glow( "locator_text_glow", "0", FCVAR_NONE, "If enabled, a glow is drawn behind caption text" ); +ConVar locator_text_glow_color( "locator_text_glow_color", "255 255 255 255", FCVAR_NONE, "Color of text glow" ); + +ConVar locator_split_maxwide_percent( "locator_split_maxwide_percent", "0.80f", FCVAR_CHEAT ); +ConVar locator_split_len( "locator_split_len", "0.5f", FCVAR_CHEAT ); + +#ifdef MAPBASE +extern ConVar gameinstructor_default_bindingcolor; +#endif + + +//------------------------------------ +CLocatorTarget::CLocatorTarget( void ) +{ + Deactivate( true ); + + PrecacheMaterial("vgui/hud/icon_arrow_left"); + PrecacheMaterial("vgui/hud/icon_arrow_right"); + PrecacheMaterial("vgui/hud/icon_arrow_up"); + PrecacheMaterial("vgui/hud/icon_arrow_down"); + PrecacheMaterial("vgui/hud/icon_arrow_plain"); +} + +//------------------------------------ +void CLocatorTarget::Activate( int serialNumber ) +{ + m_serialNumber = serialNumber; + m_frameLastUpdated = gpGlobals->framecount; + m_isActive = true; + + m_bVisible = true; + m_bOnscreen = true; + m_alpha = 0; + m_fadeStart = gpGlobals->curtime; + + m_offsetX = m_offsetY = 0; + + int iStartX = ScreenWidth() / 2; + int iStartY = ScreenHeight() / 4; + + // We want to start lessons at the players crosshair, cause that's where they're looking! + if ( locator_start_at_crosshair.GetBool() ) + vgui::input()->GetCursorPos( iStartX, iStartY ); + + m_lastXPos = iStartX; + m_lastYPos = iStartY; + + m_drawArrowDirection = DRAW_ARROW_NO; + m_lerpStart = gpGlobals->curtime; + m_pulseStart = gpGlobals->curtime; + m_declutterIndex = 0; + m_lastDeclutterIndex = 0; + + AddIconEffects(LOCATOR_ICON_FX_FADE_IN); + +#ifdef MAPBASE + // Mods are capable of using a custom binding color + CSplitString colorValues( gameinstructor_default_bindingcolor.GetString(), "," ); + + int r,g,b; + r = g = b = 0; + + if (colorValues.Count() == 3) + { + r = atoi( colorValues[0] ); + g = atoi( colorValues[1] ); + b = atoi( colorValues[2] ); + } + + m_bindingColor.SetColor( r, g, b, 255 ); +#endif +} + +//------------------------------------ +void CLocatorTarget::Deactivate( bool bNoFade ) +{ + if ( bNoFade || m_alpha == 0 || + ( m_bOccluded && !( m_iEffectsFlags & LOCATOR_ICON_FX_FORCE_CAPTION ) ) || + ( !m_bOnscreen && ( m_iEffectsFlags & LOCATOR_ICON_FX_NO_OFFSCREEN ) ) ) + { + m_bOriginInScreenspace = false; + + m_serialNumber = -1; + m_isActive = false; + m_frameLastUpdated = 0; + m_pIcon_onscreen = NULL; + m_pIcon_offscreen = NULL; + m_bDrawControllerButton = false; + m_bDrawControllerButtonOffscreen = false; + m_iEffectsFlags = LOCATOR_ICON_FX_NONE; + m_captionWide = 0; + + m_pchDrawBindingName = NULL; + m_pchDrawBindingNameOffscreen = NULL; + m_widthScale_onscreen = 1.0f; + m_bOccluded = false; + m_alpha = 0; + m_bIsDrawing = false; + m_bVisible = false; + + m_szVguiTargetName = ""; + m_szVguiTargetLookup = ""; + m_hVguiTarget = NULL; + m_nVguiTargetEdge = vgui::Label::a_northwest; + + m_szBinding = ""; + m_iBindingTick = 0; + m_flNextBindingTick = 0.0f; + m_flNextOcclusionTest = 0.0f; + m_iBindingChoicesCount = 0; + + m_wszCaption.RemoveAll(); + m_wszCaption.AddToTail( (wchar_t)0 ); + } + else if ( !( m_iEffectsFlags & LOCATOR_ICON_FX_FADE_OUT ) ) + { + // Determine home much time it would have spent fading to reach the current alpha + float flAssumedFadeTime; + flAssumedFadeTime = ( 1.0f - static_cast( m_alpha ) / 255.0f ) * locator_fade_time.GetFloat(); + + // Set the fade + m_fadeStart = gpGlobals->curtime - flAssumedFadeTime; + AddIconEffects( LOCATOR_ICON_FX_FADE_OUT ); + RemoveIconEffects( LOCATOR_ICON_FX_FADE_IN ); + } +} + +//------------------------------------ +void CLocatorTarget::Update() +{ + m_frameLastUpdated = gpGlobals->framecount; + + if ( m_bVisible && ( m_iEffectsFlags & LOCATOR_ICON_FX_FADE_OUT ) ) + { + // Determine home much time it would have spent fading to reach the current alpha + float flAssumedFadeTime; + flAssumedFadeTime = ( 1.0f - static_cast( m_alpha ) / 255.0f ) * locator_fade_time.GetFloat(); + + // Set the fade + m_fadeStart = gpGlobals->curtime - flAssumedFadeTime; + AddIconEffects( LOCATOR_ICON_FX_FADE_OUT ); + RemoveIconEffects( LOCATOR_ICON_FX_FADE_OUT ); + } +} + +int CLocatorTarget::GetIconX( void ) +{ + return m_iconX + ( IsOnScreen() ? locator_target_offset_x.GetInt()+m_offsetX : 0 ); +} + +int CLocatorTarget::GetIconY( void ) +{ + return m_iconY + ( IsOnScreen() ? locator_target_offset_y.GetInt()+m_offsetY : 0 ); +} + +int CLocatorTarget::GetIconCenterX( void ) +{ + return m_centerX + locator_target_offset_x.GetInt() + m_offsetX; +} + +int CLocatorTarget::GetIconCenterY( void ) +{ + return m_centerY + locator_target_offset_y.GetInt() + m_offsetY; +} + +void CLocatorTarget::SetVisible( bool bVisible ) +{ + // They are already the same + if ( m_bVisible == bVisible ) + return; + + m_bVisible = bVisible; + + if ( bVisible ) + { + // Determine home much time it would have spent fading to reach the current alpha + float flAssumedFadeTime; + flAssumedFadeTime = ( static_cast( m_alpha ) / 255.0f ) * locator_fade_time.GetFloat(); + + // Set the fade + m_fadeStart = gpGlobals->curtime - flAssumedFadeTime; + AddIconEffects( LOCATOR_ICON_FX_FADE_IN ); + RemoveIconEffects( LOCATOR_ICON_FX_FADE_OUT ); + } + else + { + // Determine home much time it would have spent fading to reach the current alpha + float flAssumedFadeTime; + flAssumedFadeTime = ( 1.0f - static_cast( m_alpha ) / 255.0f ) * locator_fade_time.GetFloat(); + + // Set the fade + m_fadeStart = gpGlobals->curtime - flAssumedFadeTime; + AddIconEffects( LOCATOR_ICON_FX_FADE_OUT ); + RemoveIconEffects( LOCATOR_ICON_FX_FADE_IN ); + } +} + +bool CLocatorTarget::IsVisible( void ) +{ + return m_bVisible; +} + +void CLocatorTarget::SetCaptionText( const char *pszText, const char *pszParam ) +{ + wchar_t outbuf[ 256 ]; + outbuf[ 0 ] = L'\0'; + + if ( pszParam && pszParam[ 0 ] != '\0' ) + { + wchar_t wszParamBuff[ 128 ]; + wchar_t *pLocalizedParam = NULL; + + if ( pszParam[ 0 ] == '#' ) + { + pLocalizedParam = g_pVGuiLocalize->Find( pszParam ); + } + + if ( !pLocalizedParam ) + { + g_pVGuiLocalize->ConvertANSIToUnicode( pszParam, wszParamBuff, sizeof( wszParamBuff ) ); + pLocalizedParam = wszParamBuff; + } + + wchar_t wszTextBuff[ 128 ]; + wchar_t *pLocalizedText = NULL; + + if ( pszText[ 0 ] == '#' ) + pLocalizedText = g_pVGuiLocalize->Find( pszText ); + + if ( !pLocalizedText ) + { + g_pVGuiLocalize->ConvertANSIToUnicode( pszText, wszTextBuff, sizeof( wszTextBuff ) ); + pLocalizedText = wszTextBuff; + } + + wchar_t buf[ 256 ]; + g_pVGuiLocalize->ConstructString( buf, sizeof(buf), pLocalizedText, 1, pLocalizedParam ); + + UTIL_ReplaceKeyBindings( buf, sizeof( buf ), outbuf, sizeof( outbuf ) ); + } + else + { + wchar_t wszTextBuff[ 128 ]; + wchar_t *pLocalizedText = NULL; + + if ( pszText[ 0 ] == '#' ) + { + pLocalizedText = g_pVGuiLocalize->Find( pszText ); + } + + if ( !pLocalizedText ) + { + g_pVGuiLocalize->ConvertANSIToUnicode( pszText, wszTextBuff, sizeof( wszTextBuff ) ); + pLocalizedText = wszTextBuff; + } + + wchar_t buf[ 256 ]; + Q_wcsncpy( buf, pLocalizedText, sizeof( buf ) ); + + UTIL_ReplaceKeyBindings( buf, sizeof(buf), outbuf, sizeof( outbuf ) ); + } + + int len = wcslen( outbuf ) + 1; + m_wszCaption.RemoveAll(); + m_wszCaption.EnsureCount( len ); + Q_wcsncpy( m_wszCaption.Base(), outbuf, len * sizeof( wchar_t ) ); +} + +void CLocatorTarget::SetCaptionColor( const char *pszCaptionColor ) +{ + int r,g,b; + r = g = b = 0; + + CSplitString colorValues( pszCaptionColor, "," ); + + if( colorValues.Count() == 3 ) + { + r = atoi( colorValues[0] ); + g = atoi( colorValues[1] ); + b = atoi( colorValues[2] ); + + m_captionColor.SetColor( r,g,b, 255 ); + } + else + { + DevWarning( "caption_color format incorrect. RRR,GGG,BBB expected.\n"); + } +} + +bool CLocatorTarget::IsStatic() +{ + return ( ( m_iEffectsFlags & LOCATOR_ICON_FX_STATIC ) || IsPresenting() ); +} + +bool CLocatorTarget::IsPresenting() +{ + return ( gpGlobals->curtime - m_lerpStart < locator_lerp_rest.GetFloat() ); +} + +void CLocatorTarget::StartTimedLerp() +{ + if ( gpGlobals->curtime - m_lerpStart > locator_lerp_rest.GetFloat() ) + { + m_lerpStart = gpGlobals->curtime - locator_lerp_rest.GetFloat(); + } +} + +void CLocatorTarget::StartPresent() +{ + m_lerpStart = gpGlobals->curtime; +} + + +void CLocatorTarget::EndPresent() +{ + if ( gpGlobals->curtime - m_lerpStart < locator_lerp_rest.GetFloat() ) + { + m_lerpStart = gpGlobals->curtime - locator_lerp_rest.GetFloat(); + } +} + +void CLocatorTarget::UpdateVguiTarget( void ) +{ + const char *pchVguiTargetName = m_szVguiTargetName.String(); + + if ( !pchVguiTargetName || pchVguiTargetName[ 0 ] == '\0' ) + { + m_hVguiTarget = NULL; + return; + } + + // Get the appropriate token based on the binding + if ( m_iBindingChoicesCount > 0 ) + { + int nTagetToken = m_iBindChoicesOriginalToken[ m_iBindingTick % m_iBindingChoicesCount ]; + + for ( int nToken = 0; nToken < nTagetToken && pchVguiTargetName; ++nToken ) + { + pchVguiTargetName = strchr( pchVguiTargetName, ';' ); + + if ( pchVguiTargetName ) + { + pchVguiTargetName++; + } + } + + if ( !pchVguiTargetName || pchVguiTargetName[ 0 ] == '\0' ) + { + // There wasn't enough tokens, just use the first + pchVguiTargetName = m_szVguiTargetName.String(); + } + } + + m_hVguiTarget = g_pClientMode->GetViewport(); +} + +void CLocatorTarget::SetVguiTargetName( const char *pchVguiTargetName ) +{ + if ( Q_strcmp( m_szVguiTargetName.String(), pchVguiTargetName ) == 0 ) + return; + + m_szVguiTargetName = pchVguiTargetName; + + UpdateVguiTarget(); +} + +void CLocatorTarget::SetVguiTargetLookup( const char *pchVguiTargetLookup ) +{ + m_szVguiTargetLookup = pchVguiTargetLookup; +} + +void CLocatorTarget::SetVguiTargetEdge( int nVguiEdge ) +{ + m_nVguiTargetEdge = nVguiEdge; +} + +vgui::Panel *CLocatorTarget::GetVguiTarget( void ) +{ + return (vgui::Panel *)m_hVguiTarget.Get(); +} + +//------------------------------------ +void CLocatorTarget::SetOnscreenIconTextureName( const char *pszTexture ) +{ + if ( Q_strcmp( m_szOnscreenTexture.String(), pszTexture ) == 0 ) + return; + + m_szOnscreenTexture = pszTexture; + m_pIcon_onscreen = NULL; // Dirty the onscreen icon so that the Locator will look up the new icon by name. + m_pulseStart = gpGlobals->curtime; +} + +//------------------------------------ +void CLocatorTarget::SetOffscreenIconTextureName( const char *pszTexture ) +{ + if ( Q_strcmp( m_szOffscreenTexture.String(), pszTexture ) == 0 ) + return; + + m_szOffscreenTexture = pszTexture; + m_pIcon_offscreen = NULL; // Ditto + m_pulseStart = gpGlobals->curtime; +} + +//------------------------------------ +void CLocatorTarget::SetBinding( const char *pszBinding ) +{ + int iAllowJoystick = -1; + + /*if ( !IsX360() ) + { + // Only show joystick binds if it's enabled and non-joystick if it's disabled + iAllowJoystick = input->ControllerModeActive(); + }*/ + + bool bIsControllerNow = ( iAllowJoystick != 0 ); + + if ( m_bWasControllerLast == bIsControllerNow ) + { + // We haven't toggled joystick enabled recently, so if it's the same bind, bail + if ( Q_strcmp( m_szBinding.String(), pszBinding ) == 0 ) + return; + } + + m_bWasControllerLast = bIsControllerNow; + + m_szBinding = pszBinding; + m_pIcon_onscreen = NULL; // Dirty the onscreen icon so that the Locator will look up the new icon by name. + m_pIcon_offscreen = NULL; // ditto. + m_flNextBindingTick = gpGlobals->curtime + 0.75f; + + // Get a list of all the keys bound to these actions + m_iBindingChoicesCount = 0; + + // Tokenize the binding name (could be more than one binding) + int nOriginalToken = 0; + const char *pchToken = m_szBinding.String(); + char szToken[ 128 ]; + + pchToken = nexttoken( szToken, pchToken, ';' ); + + while ( pchToken ) + { + // Get the first parameter + int iTokenBindingCount = 0; + const char *pchBinding = engine->Key_LookupBindingExact( szToken ); + + while ( m_iBindingChoicesCount < MAX_LOCATOR_BINDINGS_SHOWN && pchBinding ) + { + m_pchBindingChoices[ m_iBindingChoicesCount ] = pchBinding; + m_iBindChoicesOriginalToken[ m_iBindingChoicesCount ] = nOriginalToken; + ++m_iBindingChoicesCount; + ++iTokenBindingCount; + + pchBinding = engine->Key_LookupBindingExact( szToken ); + } + + nOriginalToken++; + pchToken = nexttoken( szToken, pchToken, ';' ); + } + + m_pulseStart = gpGlobals->curtime; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +const char *CLocatorTarget::UseBindingImage( char *pchIconTextureName, size_t bufSize ) +{ + if ( m_iBindingChoicesCount <= 0 ) + { + if ( IsX360() ) + { + Q_strncpy( pchIconTextureName, "icon_blank", bufSize ); + } + else + { + Q_strncpy( pchIconTextureName, "icon_key_wide", bufSize ); + return "#GameUI_Icons_NONE"; + } + + return NULL; + } + + // Cycle through the list of binds at a rate of 2 per second + const char *pchBinding = m_pchBindingChoices[ m_iBindingTick % m_iBindingChoicesCount ]; + + // We counted at least one binding... this should not be NULL! + Assert( pchBinding ); + + if ( IsX360() ) + { + // Use a blank background for the button icons + Q_strncpy( pchIconTextureName, "icon_blank", bufSize ); + return pchBinding; + } + + /*if ( input->ControllerModeActive() && + ( Q_strcmp( pchBinding, "A_BUTTON" ) == 0 || + Q_strcmp( pchBinding, "B_BUTTON" ) == 0 || + Q_strcmp( pchBinding, "X_BUTTON" ) == 0 || + Q_strcmp( pchBinding, "Y_BUTTON" ) == 0 || + Q_strcmp( pchBinding, "L_SHOULDER" ) == 0 || + Q_strcmp( pchBinding, "R_SHOULDER" ) == 0 || + Q_strcmp( pchBinding, "L_TRIGGER" ) == 0 || + Q_strcmp( pchBinding, "R_TRIGGER" ) == 0 || + Q_strcmp( pchBinding, "BACK" ) == 0 || + Q_strcmp( pchBinding, "START" ) == 0 || + Q_strcmp( pchBinding, "STICK1" ) == 0 || + Q_strcmp( pchBinding, "STICK2" ) == 0 || + Q_strcmp( pchBinding, "UP" ) == 0 || + Q_strcmp( pchBinding, "DOWN" ) == 0 || + Q_strcmp( pchBinding, "LEFT" ) == 0 || + Q_strcmp( pchBinding, "RIGHT" ) == 0 ) ) + { + // Use a blank background for the button icons + Q_strncpy( pchIconTextureName, "icon_blank", bufSize ); + return pchBinding; + }*/ + + if ( Q_strcmp( pchBinding, "MOUSE1" ) == 0 ) + { + Q_strncpy( pchIconTextureName, "icon_mouseLeft", bufSize ); + return NULL; + } + else if ( Q_strcmp( pchBinding, "MOUSE2" ) == 0 ) + { + Q_strncpy( pchIconTextureName, "icon_mouseRight", bufSize ); + return NULL; + } + else if ( Q_strcmp( pchBinding, "MOUSE3" ) == 0 ) + { + Q_strncpy( pchIconTextureName, "icon_mouseThree", bufSize ); + return NULL; + } + else if ( Q_strcmp( pchBinding, "MWHEELUP" ) == 0 ) + { + Q_strncpy( pchIconTextureName, "icon_mouseWheel_up", bufSize ); + return NULL; + } + else if ( Q_strcmp( pchBinding, "MWHEELDOWN" ) == 0 ) + { + Q_strncpy( pchIconTextureName, "icon_mouseWheel_down", bufSize ); + return NULL; + } + else if ( Q_strcmp( pchBinding, "UPARROW" ) == 0 ) + { + Q_strncpy( pchIconTextureName, "icon_key_up", bufSize ); + return NULL; + } + else if ( Q_strcmp( pchBinding, "LEFTARROW" ) == 0 ) + { + Q_strncpy( pchIconTextureName, "icon_key_left", bufSize ); + return NULL; + } + else if ( Q_strcmp( pchBinding, "DOWNARROW" ) == 0 ) + { + Q_strncpy( pchIconTextureName, "icon_key_down", bufSize ); + return NULL; + } + else if ( Q_strcmp( pchBinding, "RIGHTARROW" ) == 0 ) + { + Q_strncpy( pchIconTextureName, "icon_key_right", bufSize ); + return NULL; + } + else if ( Q_strcmp( pchBinding, "SEMICOLON" ) == 0 || + Q_strcmp( pchBinding, "INS" ) == 0 || + Q_strcmp( pchBinding, "DEL" ) == 0 || + Q_strcmp( pchBinding, "HOME" ) == 0 || + Q_strcmp( pchBinding, "END" ) == 0 || + Q_strcmp( pchBinding, "PGUP" ) == 0 || + Q_strcmp( pchBinding, "PGDN" ) == 0 || + Q_strcmp( pchBinding, "PAUSE" ) == 0 || + Q_strcmp( pchBinding, "F10" ) == 0 || + Q_strcmp( pchBinding, "F11" ) == 0 || + Q_strcmp( pchBinding, "F12" ) == 0 ) + { + Q_strncpy( pchIconTextureName, "icon_key_generic", bufSize ); + return pchBinding; + } + else if ( Q_strlen( pchBinding ) <= 2 ) + { + Q_strncpy( pchIconTextureName, "icon_key_generic", bufSize ); + return pchBinding; + } + else if ( Q_strlen( pchBinding ) <= 6 ) + { + Q_strncpy( pchIconTextureName, "icon_key_wide", bufSize ); + return pchBinding; + } + else + { + Q_strncpy( pchIconTextureName, "icon_key_wide", bufSize ); + return pchBinding; + } + + return pchBinding; +} + +//----------------------------------------------------------------------------- +int CLocatorTarget::GetIconWidth( void ) +{ + return m_wide; +} + +//----------------------------------------------------------------------------- +int CLocatorTarget::GetIconHeight( void ) +{ + return m_tall; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class CLocatorPanel : public vgui::EditablePanel +{ + DECLARE_CLASS_SIMPLE( CLocatorPanel, vgui::EditablePanel ); +public: + CLocatorPanel( vgui::Panel *parent, const char *name ); + ~CLocatorPanel( void ); + + virtual void ApplySchemeSettings( vgui::IScheme *pScheme ); + virtual void PerformLayout( void ); + virtual void OnTick( void ); + virtual void PaintBackground( void ); + virtual void Paint( void ); + void ValidateTexture( int *pTextureID, const char *pszTextureName ); + bool ValidateTargetTextures( CLocatorTarget *pTarget ); + bool IconsAreIntersecting( CLocatorTarget &first, CLocatorTarget &second, int iTolerance ); + virtual void PaintTarget( CLocatorTarget *pTarget ); + + void DrawPointerBackground( CLocatorTarget *pTarget, int nPointerX, int nPointerY, int nWide, int nTall, bool bPointer ); + void DrawStaticIcon( CLocatorTarget *pTarget ); + void DrawDynamicIcon( CLocatorTarget *pTarget, bool bDrawCaption, bool bDrawSimpleArrow ); + void DrawIndicatorArrow( int x, int y, int iconWide, int iconTall, int textWidth, int direction ); + void DrawTargetCaption( CLocatorTarget *pTarget, int x, int y, bool bDrawMultiline ); + int GetScreenWidthForCaption( const wchar_t *pString, vgui::HFont hFont ); + void DrawBindingName( CLocatorTarget *pTarget, const char *pchBindingName, int x, int y, bool bController ); + void ComputeTargetIconPosition( CLocatorTarget *pTarget, bool bSetPosition ); + void CalculateOcclusion( CLocatorTarget *pTarget ); + + void DrawSimpleArrow( int x, int y, int iconWide, int iconTall ); + void GetIconPositionForOffscreenTarget( const Vector &vecDelta, float flDist, int *pXPos, int *pYPos ); + + CLocatorTarget *GetPointerForHandle( int hTarget ); + int AddTarget(); + void RemoveTarget( int hTarget ); + + void GetTargetPosition( const Vector &vecDelta, float flRadius, float *xpos, float *ypos, float *flRotation ); + + void DeactivateAllTargets(); + void CollectGarbage(); + + // Animation + void AnimateIconSize( int flags, int *wide, int *tall, float fPulseStart ); + void AnimateIconPosition( int flags, int *x, int *y ); + void AnimateIconAlpha( int flags, int *alpha, float fadeStart ); + +private: + + CPanelAnimationVar( vgui::HFont, m_hCaptionFont, "font", "InstructorTitle" ); + CPanelAnimationVar( vgui::HFont, m_hCaptionFont_ss, "font", "InstructorTitle_ss" ); + CPanelAnimationVar( vgui::HFont, m_hCaptionGlowFont, "font", "InstructorTitleGlow" ); + CPanelAnimationVar( vgui::HFont, m_hCaptionGlowFont_ss, "font", "InstructorTitleGlow_ss" ); + + CPanelAnimationVar( vgui::HFont, m_hButtonFont, "font", "InstructorButtons" ); + + CPanelAnimationVar( vgui::HFont, m_hButtonFont_ss, "font", "InstructorButtons_ss" ); + CPanelAnimationVar( vgui::HFont, m_hKeysFont, "font", "InstructorKeyBindings" ); + + + CPanelAnimationVar( int, m_iShouldWrapStaticLocators, "WrapStaticLocators", "0" ); + + static int m_serializer; // Used to issue unique serial numbers to targets, for use as handles + int m_textureID_ArrowRight; + int m_textureID_ArrowLeft; + int m_textureID_ArrowUp; + int m_textureID_ArrowDown; + int m_textureID_SimpleArrow; + + int m_staticIconPosition;// Helps us stack static icons + + CLocatorTarget m_targets[MAX_LOCATOR_TARGETS]; +}; + +//----------------------------------------------------------------------------- +// Local variables +//----------------------------------------------------------------------------- +static CLocatorPanel *s_pLocatorPanel; + +inline CLocatorPanel * GetPlayerLocatorPanel() +{ + //if ( !engine->IsLocalPlayerResolvable() ) + //return NULL; + + Assert( s_pLocatorPanel ); + return s_pLocatorPanel; +} + + +//----------------------------------------------------------------------------- +// Static variable initialization +//----------------------------------------------------------------------------- +int CLocatorPanel::m_serializer = 1000; // Serial numbers start at 1000 + +//----------------------------------------------------------------------------- +// This is the interface function that other systems use to send us targets +//----------------------------------------------------------------------------- +int Locator_AddTarget() +{ + if( s_pLocatorPanel == NULL ) + { + // Locator has not been used yet. Construct it. + CLocatorPanel *pLocator = new CLocatorPanel( g_pClientMode->GetViewport(), "LocatorPanel" ); + vgui::SETUP_PANEL(pLocator); + pLocator->SetBounds( 0, 0, ScreenWidth(), ScreenHeight() ); + pLocator->SetPos( 0, 0 ); + pLocator->SetVisible( true ); + vgui::ivgui()->AddTickSignal( pLocator->GetVPanel() ); + } + + Assert( s_pLocatorPanel != NULL ); + return s_pLocatorPanel ? s_pLocatorPanel->AddTarget() : -1; +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void Locator_RemoveTarget( int hTarget ) +{ + if ( CLocatorPanel *pPanel = GetPlayerLocatorPanel() ) + pPanel->RemoveTarget( hTarget ); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +CLocatorTarget *Locator_GetTargetFromHandle( int hTarget ) +{ + if ( CLocatorPanel *pPanel = GetPlayerLocatorPanel() ) + return pPanel->GetPointerForHandle( hTarget ); + else + return NULL; +} + +void Locator_ComputeTargetIconPositionFromHandle( int hTarget ) +{ + if ( CLocatorPanel *pPanel = GetPlayerLocatorPanel() ) + { + if ( CLocatorTarget *pTarget = pPanel->GetPointerForHandle( hTarget ) ) + { + if( !( pTarget->GetIconEffectsFlags() & LOCATOR_ICON_FX_STATIC ) ) + { + // It's not presenting in the middle of the screen, so figure out it's position + pPanel->ComputeTargetIconPosition( pTarget, !pTarget->IsPresenting() ); + pPanel->CalculateOcclusion( pTarget ); + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CLocatorPanel::CLocatorPanel( Panel *parent, const char *name ) : EditablePanel(parent,name) +{ + Assert( s_pLocatorPanel == NULL ); + DeactivateAllTargets(); + + s_pLocatorPanel = this; + m_textureID_ArrowRight = -1; + m_textureID_ArrowLeft = -1; + m_textureID_ArrowUp = -1; + m_textureID_ArrowDown = -1; + m_textureID_SimpleArrow = -1; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CLocatorPanel::~CLocatorPanel( void ) +{ + Assert( s_pLocatorPanel == this ); + s_pLocatorPanel = NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: Applies scheme settings +//----------------------------------------------------------------------------- +void CLocatorPanel::ApplySchemeSettings( vgui::IScheme *pScheme ) +{ + BaseClass::ApplySchemeSettings( pScheme ); + LoadControlSettings("resource/UI/Locator.res"); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CLocatorPanel::PerformLayout( void ) +{ + BaseClass::PerformLayout(); + + vgui::Panel *pPanel = FindChildByName( "LocatorBG" ); + + if ( pPanel ) + pPanel->SetPos( (GetWide() - pPanel->GetWide()) * 0.5, (GetTall() - pPanel->GetTall()) * 0.5 ); +} + +//----------------------------------------------------------------------------- +// Purpose: Given an offscreen target position, compute the 'compass position' +// so that we can draw an icon on the imaginary circle around the crosshair +// that indicates which way the player should turn to bring the target into view. +//----------------------------------------------------------------------------- +void CLocatorPanel::GetTargetPosition( const Vector &vecDelta, float flRadius, float *xpos, float *ypos, float *flRotation ) +{ + // Player Data + Vector playerPosition = MainViewOrigin(); + QAngle playerAngles = MainViewAngles(); + + Vector forward, right, up(0,0,1); + AngleVectors (playerAngles, &forward, NULL, NULL ); + forward.z = 0; + VectorNormalize(forward); + CrossProduct( up, forward, right ); + float front = DotProduct(vecDelta, forward); + float side = DotProduct(vecDelta, right); + *xpos = flRadius * -side; + *ypos = flRadius * -front; + + // Get the rotation (yaw) + *flRotation = atan2(*xpos,*ypos) + M_PI; + *flRotation *= 180 / M_PI; + + float yawRadians = -(*flRotation) * M_PI / 180.0f; + float ca = cos( yawRadians ); + float sa = sin( yawRadians ); + + // Rotate it around the circle, squash Y to make an oval rather than a circle + *xpos = (int)((ScreenWidth() / 2) + (flRadius * sa)); + *ypos = (int)((ScreenHeight() / 2) - (flRadius * 0.6f * ca)); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CLocatorPanel::DeactivateAllTargets() +{ + for( int i = 0 ; i < MAX_LOCATOR_TARGETS ; i++ ) + m_targets[ i ].Deactivate( true ); +} + +//----------------------------------------------------------------------------- +// Purpose: Deactivate any target that has not been updated within several frames +//----------------------------------------------------------------------------- +void CLocatorPanel::CollectGarbage() +{ + for( int i = 0 ; i < MAX_LOCATOR_TARGETS ; i++ ) + { + if( m_targets[ i ].m_isActive ) + { + if( gpGlobals->framecount - m_targets[ i ].m_frameLastUpdated > 20 ) + m_targets[ i ].Deactivate(); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Provide simple animation by modifying the width and height of the +// icon before it is drawn. +//----------------------------------------------------------------------------- +void CLocatorPanel::AnimateIconSize( int flags, int *wide, int *tall, float fPulseStart ) +{ + float flScale = MIN_ICON_SCALE; + float scaleDelta = MAX_ICON_SCALE - MIN_ICON_SCALE; + + float newWide = *wide; + float newTall = *tall; + + if( flags & LOCATOR_ICON_FX_PULSE_SLOW || gpGlobals->curtime - fPulseStart < locator_pulse_time.GetFloat() ) + { + flScale += scaleDelta * fabs( sin( ( gpGlobals->curtime - fPulseStart ) * M_PI ) ); + } + else if( flags & LOCATOR_ICON_FX_PULSE_FAST ) + { + flScale += scaleDelta * fabs( sin( gpGlobals->curtime * 2 * M_PI ) ); + } + else if( flags & LOCATOR_ICON_FX_PULSE_URGENT ) + { + flScale += scaleDelta * fabs( sin( gpGlobals->curtime * 4 * M_PI ) ); + } + + if ( newWide > newTall ) + { + // Get scale to make width change by only the standard height amount of pixels + int iHeightDelta = (int)(newTall * flScale - newTall); + flScale = ( newWide + iHeightDelta ) / newWide; + } + + newWide = newWide * flScale; + newTall = newTall * flScale; + + *wide = newWide; + *tall = newTall; +} + +//----------------------------------------------------------------------------- +// Purpose: Modify the alpha of the icon before it is drawn. +//----------------------------------------------------------------------------- +void CLocatorPanel::AnimateIconAlpha( int flags, int *alpha, float fadeStart ) +{ + float flScale = MIN_ICON_ALPHA; + float scaleDelta = MAX_ICON_ALPHA - MIN_ICON_ALPHA; + + if( flags & LOCATOR_ICON_FX_ALPHA_SLOW ) + { + flScale += scaleDelta * fabs( sin( gpGlobals->curtime * 3 ) ); + } + else if( flags & LOCATOR_ICON_FX_ALPHA_FAST ) + { + flScale += scaleDelta * fabs( sin( gpGlobals->curtime * 7 ) ); + } + else if( flags & LOCATOR_ICON_FX_ALPHA_URGENT ) + { + flScale += scaleDelta * fabs( sin( gpGlobals->curtime * 10 ) ); + } + else + { + flScale = MAX_ICON_ALPHA; + } + + if ( flags & LOCATOR_ICON_FX_FADE_OUT ) + { + flScale *= MAX( 0.0f, ( locator_fade_time.GetFloat() - ( gpGlobals->curtime - fadeStart ) ) / locator_fade_time.GetFloat() ); + } + else if ( flags & LOCATOR_ICON_FX_FADE_IN ) + { + flScale *= MAX_ICON_ALPHA - MAX( 0.0f, ( locator_fade_time.GetFloat() - ( gpGlobals->curtime - fadeStart ) ) / locator_fade_time.GetFloat() ); + } + + *alpha = static_cast( 255.0f * flScale ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CLocatorPanel::AnimateIconPosition( int flags, int *x, int *y ) +{ + int newX = *x; + int newY = *y; + + if( flags & LOCATOR_ICON_FX_SHAKE_NARROW ) + { + newX += RandomInt( -2, 2 ); + newY += RandomInt( -2, 2 ); + } + else if( flags & LOCATOR_ICON_FX_SHAKE_WIDE ) + { + newX += RandomInt( -5, 5 ); + newY += RandomInt( -5, 5 ); + } + + *x = newX; + *y = newY; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CLocatorPanel::OnTick( void ) +{ + +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CLocatorPanel::PaintBackground( void ) +{ + return; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CLocatorPanel::Paint( void ) +{ + ValidateTexture( &m_textureID_ArrowLeft, "vgui/hud/icon_arrow_left" ); + ValidateTexture( &m_textureID_ArrowRight, "vgui/hud/icon_arrow_right" ); + ValidateTexture( &m_textureID_ArrowUp, "vgui/hud/icon_arrow_up" ); + ValidateTexture( &m_textureID_ArrowDown, "vgui/hud/icon_arrow_down" ); + ValidateTexture( &m_textureID_SimpleArrow, "vgui/hud/icon_arrow_plain" ); + + // reset the static icon position. This is the y position at which the first + // static icon will be drawn. This value will be incremented by the height of + // each static icon drawn, which forces the next static icon to be drawn below + // the previous one, creating a little fixed, vertical stack below the crosshair. + m_staticIconPosition = ScreenHeight() / 6; + + // Time now to draw the 'dynamic' icons, the icons which help players locate things + // in actual world space. + + //---------- + // Batch 1 + // Go through all of the active locator targets and compute where to draw the icons + // that represent each of them. This builds a poor man's draw list by updating the + // m_iconX, m_iconY members of each locator target. + CUtlVectorFixed< CLocatorTarget *, MAX_LOCATOR_TARGETS > vecValid; + + for( int i = 0 ; i < MAX_LOCATOR_TARGETS ; i++ ) + { + CLocatorTarget *pLocatorTarget = &(m_targets[ i ]); + + // Reset drawing state for this frame... set back to true when it's finally draws + pLocatorTarget->m_bIsDrawing = false; + + if ( ( !pLocatorTarget->m_bVisible && !pLocatorTarget->m_alpha ) || !pLocatorTarget->m_isActive ) + { + // Don't want to be visible and have finished fading + continue; + } + + vecValid.AddToTail( pLocatorTarget ); + + // This prevents an error that if a locator was fading as the map transitioned + pLocatorTarget->m_fadeStart = fpmin( pLocatorTarget->m_fadeStart, gpGlobals->curtime ); + + if( !( pLocatorTarget->GetIconEffectsFlags() & LOCATOR_ICON_FX_STATIC ) ) + { + // It's not presenting in the middle of the screen, so figure out it's position + ComputeTargetIconPosition( pLocatorTarget, !pLocatorTarget->IsPresenting() ); + CalculateOcclusion( pLocatorTarget ); + + pLocatorTarget->m_lastDeclutterIndex = pLocatorTarget->m_declutterIndex; + pLocatorTarget->m_declutterIndex = 0; + } + } + + //---------- + // Batch 2 + // Now that we know where each icon _wants_ to be drawn, we grovel through them and + // push apart any icons that are too close to one another. This helps to unclutter + // the display and ensure the maximum number of legible icons and captions. Obviously + // this process changes where some icons will be drawn. Bubble sort, but tiny data set. + int iTolerance = 1.25 * (ScreenWidth() * ICON_SIZE); + int iterations = 0;// Count iterations, don't go infinite in the event of some weird case. + bool bStillUncluttering = true; + static int MAX_UNCLUTTER_ITERATIONS = 10; + while( iterations < MAX_UNCLUTTER_ITERATIONS && bStillUncluttering ) + { + iterations++; + bStillUncluttering = false; + + for( int i = 0 ; i < vecValid.Count() ; ++i ) + { + CLocatorTarget *pLocatorTarget1 = vecValid[ i ]; + + for( int j = i + 1 ; j < vecValid.Count() ; ++j ) + { + CLocatorTarget *pLocatorTarget2 = vecValid[ j ]; + + // Don't attempt to declutter icons if one or both is attempting to fade out + bool bLocatorsFullyActive = !((pLocatorTarget1->GetIconEffectsFlags()|pLocatorTarget2->GetIconEffectsFlags()) & LOCATOR_ICON_FX_FADE_OUT); + + if ( bLocatorsFullyActive && IconsAreIntersecting( *pLocatorTarget1, *pLocatorTarget2, iTolerance ) ) + { + // Unclutter. Lift whichever icon is highest a bit higher + if( pLocatorTarget1->m_iconY < pLocatorTarget2->m_iconY ) + { + pLocatorTarget1->m_iconY = pLocatorTarget2->m_iconY - iTolerance; + pLocatorTarget1->m_centerY = pLocatorTarget2->m_centerY - iTolerance; + pLocatorTarget1->m_declutterIndex -= 1; + } + else + { + pLocatorTarget2->m_iconY = pLocatorTarget1->m_iconY - iTolerance; + pLocatorTarget2->m_centerY = pLocatorTarget1->m_centerY - iTolerance; + pLocatorTarget2->m_declutterIndex -= 1; + } + + bStillUncluttering = true; + } + } + } + } + + if( iterations == MAX_UNCLUTTER_ITERATIONS ) + { + DevWarning( "Game instructor hit MAX_UNCLUTTER_ITERATIONS!\n"); + } + + float flLocatorLerpRest = locator_lerp_rest.GetFloat(); + float flLocatorLerpTime = locator_lerp_time.GetFloat(); + + //---------- + // Batch 3 + // Draw each of the icons. + for( int i = 0 ; i < vecValid.Count() ; i++ ) + { + CLocatorTarget *pLocatorTarget = vecValid[ i ]; + // Back to lerping for these guys + if ( pLocatorTarget->m_lastDeclutterIndex != pLocatorTarget->m_declutterIndex ) + { + // It wants to be popped to another position... do it smoothly + pLocatorTarget->StartTimedLerp(); + } + + // Lerp to the desired position + float flLerpTime = gpGlobals->curtime - pLocatorTarget->m_lerpStart; + + if ( flLerpTime >= flLocatorLerpRest && flLerpTime < flLocatorLerpRest + flLocatorLerpTime ) + { + // Lerp slow to fast + float fInterp = 1.0f - ( ( flLocatorLerpTime - ( flLerpTime - flLocatorLerpRest ) ) / flLocatorLerpTime ); + + // Get our desired position + float iconX = pLocatorTarget->m_iconX; + float iconY = pLocatorTarget->m_iconY; + + // Get the distance we need to go to reach it + float diffX = fabsf( pLocatorTarget->m_iconX - pLocatorTarget->m_lastXPos ); + float diffY = fabsf( pLocatorTarget->m_iconY - pLocatorTarget->m_lastYPos ); + + // Go from our current position toward the desired position as quick as the interp allows + pLocatorTarget->m_iconX = static_cast( Approach( iconX, pLocatorTarget->m_lastXPos, diffX * fInterp ) ); + pLocatorTarget->m_iconY = static_cast( Approach( iconY, pLocatorTarget->m_lastYPos, diffY * fInterp ) ); + + // Get how much our position changed and apply it to the center values + int iOffsetX = pLocatorTarget->m_iconX - iconX; + int iOffsetY = pLocatorTarget->m_iconY - iconY; + + pLocatorTarget->m_centerX += iOffsetX; + pLocatorTarget->m_centerY += iOffsetY; + + if ( iOffsetX < 3 && iOffsetY < 3 ) + { + // Near our target! Stop lerping! + flLerpTime = flLocatorLerpRest + flLocatorLerpTime; + } + } + + PaintTarget( pLocatorTarget ); + } + + CollectGarbage(); +} + +//----------------------------------------------------------------------------- +// Purpose: A helper function to save on typing. Make sure our texture ID's +// stay valid. +//----------------------------------------------------------------------------- +void CLocatorPanel::ValidateTexture( int *pTextureID, const char *pszTextureName ) +{ + if( *pTextureID == -1 ) + { + *pTextureID = vgui::surface()->CreateNewTextureID(); + vgui::surface()->DrawSetTextureFile( *pTextureID, pszTextureName, true, false ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Called every frame before painting the targets. Ensures that the +// target's textures are properly cached. +//----------------------------------------------------------------------------- +bool CLocatorPanel::ValidateTargetTextures( CLocatorTarget *pTarget ) +{ + bool bBindingTick = false; + + if ( gpGlobals->curtime >= pTarget->m_flNextBindingTick ) + { + if ( pTarget->m_iBindingChoicesCount > 1 ) + { + bBindingTick = true; + pTarget->m_iBindingTick++; + } + + pTarget->m_flNextBindingTick = gpGlobals->curtime + 0.75f; + + pTarget->UpdateVguiTarget(); + } + + bool bUsesBinding = ( Q_stricmp( pTarget->GetOnscreenIconTextureName(), "use_binding" ) == 0 ); + + if( !pTarget->m_pIcon_onscreen || !pTarget->m_pIcon_offscreen || ( bUsesBinding && bBindingTick ) ) + { + char szIconTextureName[ 256 ]; + if ( bUsesBinding ) + { + const char *pchDrawBindingName = pTarget->UseBindingImage( szIconTextureName, sizeof( szIconTextureName ) ); + pTarget->m_bDrawControllerButton = ( Q_strcmp( szIconTextureName, "icon_blank" ) == 0 ); + + pTarget->DrawBindingName( pchDrawBindingName ); + } + else + { + pTarget->m_bDrawControllerButton = false; + Q_strcpy( szIconTextureName, pTarget->GetOnscreenIconTextureName() ); + pTarget->DrawBindingName( NULL ); + } + + // This target's texture ID is dirty, meaning the target is about to be drawn + // for the first time, or about to be drawn for the first time since a texture + // was changed. + if ( Q_strlen(szIconTextureName) == 0 ) + { + DevWarning("Locator Target has no onscreen texture name!\n"); + return false; + } + else + { + pTarget->m_pIcon_onscreen = HudIcons().GetIcon( szIconTextureName ); + if ( pTarget->m_pIcon_onscreen ) + { + pTarget->m_widthScale_onscreen = static_cast< float >( pTarget->m_pIcon_onscreen->Width() ) / pTarget->m_pIcon_onscreen->Height(); + } + else + { + pTarget->m_widthScale_onscreen = 1.0f; + } + } + + if ( Q_stricmp( pTarget->GetOffscreenIconTextureName() , "use_binding" ) == 0 ) + { + const char *pchDrawBindingName = pTarget->UseBindingImage( szIconTextureName, sizeof( szIconTextureName ) ); + pTarget->m_bDrawControllerButtonOffscreen = ( Q_strcmp( szIconTextureName, "icon_blank" ) == 0 ); + + pTarget->DrawBindingNameOffscreen( pchDrawBindingName ); + } + else + { + pTarget->m_bDrawControllerButtonOffscreen = false; + Q_strcpy( szIconTextureName, pTarget->GetOffscreenIconTextureName() ); + pTarget->DrawBindingNameOffscreen( NULL ); + } + + if( Q_strlen(szIconTextureName) == 0 ) + { + if( !pTarget->m_pIcon_onscreen ) + { + DevWarning("Locator Target has no offscreen texture name and can't fall back!\n"); + } + else + { + // The onscreen texture is valid, so default behavior is to use that. + pTarget->m_pIcon_offscreen = pTarget->m_pIcon_onscreen; + const char *pchDrawBindingName = pTarget->DrawBindingName(); + pTarget->DrawBindingNameOffscreen( pchDrawBindingName ); + } + } + else + { + pTarget->m_pIcon_offscreen = HudIcons().GetIcon( szIconTextureName ); + } + + return true; + } + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: Compute where on the screen to draw the icon for this target. +//----------------------------------------------------------------------------- +void CLocatorPanel::ComputeTargetIconPosition( CLocatorTarget *pTarget, bool bSetPosition ) +{ + int iconX; + int iconY; + + // Measure the delta and the dist from this player to this target. + Vector vecTarget = pTarget->m_vecOrigin; + Vector vecDelta = vecTarget - MainViewOrigin(); + + if ( pTarget->m_bOriginInScreenspace ) + { + // Coordinates are already in screenspace + pTarget->m_distFromPlayer = 0.0f; + + iconX = vecTarget.x * ScreenWidth(); + iconY = vecTarget.y * ScreenHeight(); + pTarget->m_targetX = iconX; + pTarget->m_targetY = iconY; + } + else + { + pTarget->m_distFromPlayer = VectorNormalize( vecDelta ); + + if ( GetVectorInScreenSpace( vecTarget, iconX, iconY ) ) + { + // NOTE: GetVectorInScreenSpace returns false in an edge case where the + // target is very far off screen... just us the old values + pTarget->m_targetX = iconX; + pTarget->m_targetY = iconY; + } + } + + pTarget->m_drawArrowDirection = DRAW_ARROW_NO; + + float fTitleSafeInset = ScreenWidth() * 0.075f; + + if( iconX < fTitleSafeInset || iconX > ScreenWidth() - fTitleSafeInset ) + { + // It's off the screen left or right. + if ( pTarget->m_bOnscreen && !( pTarget->GetIconEffectsFlags() & LOCATOR_ICON_FX_NO_OFFSCREEN ) ) + { + // Back to lerping + pTarget->StartTimedLerp(); + pTarget->m_pulseStart = gpGlobals->curtime; + } + + if ( bSetPosition ) + { + pTarget->m_bOnscreen = false; + } + + GetIconPositionForOffscreenTarget( vecDelta, pTarget->m_distFromPlayer, &iconX, &iconY ); + + Vector vCenter = pTarget->m_vecOrigin; + if( MainViewRight().Dot( vCenter - MainViewOrigin() ) > 0 ) + pTarget->m_drawArrowDirection = DRAW_ARROW_RIGHT; + else + pTarget->m_drawArrowDirection = DRAW_ARROW_LEFT; + } + else if( iconY < fTitleSafeInset || iconY > ScreenHeight() - fTitleSafeInset ) + { + // It's off the screen up or down. + if ( pTarget->m_bOnscreen && !( pTarget->GetIconEffectsFlags() & LOCATOR_ICON_FX_NO_OFFSCREEN ) ) + { + // Back to lerping + pTarget->StartTimedLerp(); + pTarget->m_pulseStart = gpGlobals->curtime; + } + + if ( bSetPosition ) + { + pTarget->m_bOnscreen = false; + } + + GetIconPositionForOffscreenTarget( vecDelta, pTarget->m_distFromPlayer, &iconX, &iconY ); + + Vector vCenter = pTarget->m_vecOrigin; + if( MainViewUp().Dot( vCenter - MainViewOrigin() ) > 0 ) + pTarget->m_drawArrowDirection = DRAW_ARROW_UP; + else + pTarget->m_drawArrowDirection = DRAW_ARROW_DOWN; + } + else + { + if ( !pTarget->m_bOnscreen && !( pTarget->GetIconEffectsFlags() & LOCATOR_ICON_FX_NO_OFFSCREEN ) ) + { + // Back to lerping + pTarget->StartTimedLerp(); + pTarget->m_pulseStart = gpGlobals->curtime; + } + + pTarget->m_bOnscreen = true; + } + + if ( bSetPosition ) + { + int tall = ScreenWidth() * ICON_SIZE; + int wide = tall * pTarget->m_widthScale_onscreen; + + // Animate the icon + AnimateIconSize( pTarget->GetIconEffectsFlags(), &wide, &tall, pTarget->m_pulseStart ); + AnimateIconPosition( pTarget->GetIconEffectsFlags(), &iconX, &iconY ); + AnimateIconAlpha( pTarget->GetIconEffectsFlags(), &pTarget->m_alpha, pTarget->m_fadeStart ); + + if( pTarget->m_distFromPlayer > ICON_DIST_TOO_FAR && !locator_topdown_style.GetBool() ) + { + // Make the icon smaller + wide = wide >> 1; + tall = tall >> 1; + } + + pTarget->m_centerX = iconX; + pTarget->m_centerY = iconY; + + pTarget->m_iconX = pTarget->m_centerX - ( wide >> 1 ); + pTarget->m_iconY = pTarget->m_centerY - ( tall >> 1 ); + pTarget->m_wide = wide; + pTarget->m_tall = tall; + } +} + +void CLocatorPanel::CalculateOcclusion( CLocatorTarget *pTarget ) +{ + if ( gpGlobals->curtime >= pTarget->m_flNextOcclusionTest ) + { + pTarget->m_flNextOcclusionTest = gpGlobals->curtime + LOCATOR_OCCLUSION_TEST_RATE; + + // Assume the target is not occluded. + pTarget->m_bOccluded = false; + + if ( pTarget->m_bOriginInScreenspace ) + return; + + trace_t tr; + UTIL_TraceLine( pTarget->m_vecOrigin, MainViewOrigin(), (CONTENTS_SOLID|CONTENTS_MOVEABLE), NULL, COLLISION_GROUP_NONE, &tr ); + if ( tr.fraction < 1.0f ) + { + pTarget->m_bOccluded = true; + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: This is not valid until after you have computed the onscreen +// icon position for each target! +//----------------------------------------------------------------------------- +bool CLocatorPanel::IconsAreIntersecting( CLocatorTarget &first, CLocatorTarget &second, int iTolerance ) +{ + if( first.m_bOnscreen != second.m_bOnscreen ) + { + // We only declutter onscreen icons against other onscreen icons and vice-versa. + return false; + } + + if( first.IsStatic() || second.IsStatic() ) + { + // Static icons don't count. + return false; + } + + if( abs(first.GetIconY() - second.GetIconY()) < iTolerance ) + { + // OK, we need the Y-check first. Now we have to see if these icons and their captions overlap. + int firstWide = iTolerance + first.m_captionWide; + int secondWide = iTolerance + second.m_captionWide; + + if( abs(first.GetIconX() - second.GetIconX()) < (firstWide + secondWide) / 2 ) + { + return true; + } + } + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: Draw this target on the locator. +// +// IF onscreen and visible, draw no icon, draw no arrows +// IF onscreen and occluded, draw icon transparently, draw no arrows +// IF offscreen, draw icon, draw an arrow indicating the direction to the target +//----------------------------------------------------------------------------- +void CLocatorPanel::PaintTarget( CLocatorTarget *pTarget ) +{ + bool bNewTexture = ValidateTargetTextures( pTarget ); + + if ( bNewTexture ) + { + // Refigure the width/height for the new texture + int tall = ScreenWidth() * ICON_SIZE; + int wide = tall * pTarget->m_widthScale_onscreen; + + AnimateIconSize( pTarget->GetIconEffectsFlags(), &wide, &tall, pTarget->m_pulseStart ); + + pTarget->m_wide = wide; + pTarget->m_tall = tall; + } + + // A static icon just draws with other static icons in a stack under the crosshair. + // Once displayed, they do not move. The are often used for notifiers. + if( pTarget->IsStatic() ) + { + DrawStaticIcon( pTarget ); + return; + } + + if ( !pTarget->m_bOnscreen && ( pTarget->GetIconEffectsFlags() & LOCATOR_ICON_FX_NO_OFFSCREEN ) ) + { + // Doesn't draw when offscreen... reset it's alpha so it has to fade in again + pTarget->m_fadeStart = gpGlobals->curtime; + pTarget->m_alpha = 0; + } + else + { + // Save these coordinates for later lerping + pTarget->m_lastXPos = pTarget->m_iconX; + pTarget->m_lastYPos = pTarget->m_iconY; + + // Draw when it's on screen or allowed to draw offscreen + DrawDynamicIcon( pTarget, pTarget->HasCaptionText(), pTarget->m_bOnscreen ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Draws the caption-like background with word-bubble style pointer +//----------------------------------------------------------------------------- +void CLocatorPanel::DrawPointerBackground( CLocatorTarget *pTarget, int nPointerX, int nPointerY, int nWide, int nTall, bool bPointer ) +{ + if ( locator_background_style.GetInt() == 0 || pTarget->m_alpha == 0 ) + return; + + /* + int nPosX = pTarget->GetIconX() + locator_background_shift_x.GetInt() - locator_background_thickness_x.GetInt() / 2; + int nPosY = pTarget->GetIconY() + locator_background_shift_y.GetInt() - locator_background_thickness_y.GetInt() / 2; + int nBackgroundWide = nWide + locator_background_thickness_x.GetInt(); + int nBackgroundTall = nTall + locator_background_thickness_y.GetInt(); + + nPointerX = clamp( nPointerX, -0.5f * ScreenWidth(), ScreenWidth() * 1.5f ); + nPointerY = clamp( nPointerY, -0.5f * ScreenHeight(), ScreenHeight() * 1.5f ); + + float fAlpha = static_cast( pTarget->m_alpha ) / 255.0f; + + Color rgbaBackground = locator_background_color.GetColor(); + rgbaBackground[ 3 ] *= fAlpha; + + Color rgbaBorder = locator_background_border_color.GetColor(); + rgbaBorder[ 3 ] *= fAlpha; + */ + + DevMsg("[TODO] vgui::surface()->DrawWordBubble \n"); + + //vgui::surface()->DrawWordBubble( nPosX, nPosY, nPosX + nBackgroundWide, nPosY + nBackgroundTall, locator_background_border_thickness.GetInt(), rgbaBackground, rgbaBorder, bPointer, nPointerX, nPointerY, ScreenWidth() * ICON_SIZE ); +} + +//----------------------------------------------------------------------------- +// Purpose: Draw an icon with the group of static icons. +//----------------------------------------------------------------------------- +void CLocatorPanel::DrawStaticIcon( CLocatorTarget *pTarget ) +{ + int centerX = ScreenWidth() / 2; + int centerY = ScreenHeight() / 2; + centerY += m_staticIconPosition; + + int iconTall = ScreenWidth() * ICON_SIZE; + int iconWide = iconTall * pTarget->m_widthScale_onscreen; + + pTarget->m_centerX = centerX; + pTarget->m_centerY = centerY; + + // Animate the icon + AnimateIconSize( pTarget->GetIconEffectsFlags(), &iconWide, &iconTall, pTarget->m_pulseStart ); + AnimateIconPosition( pTarget->GetIconEffectsFlags(), ¢erX, ¢erY ); + AnimateIconAlpha( pTarget->GetIconEffectsFlags(), &pTarget->m_alpha, pTarget->m_fadeStart ); + + // Figure out the caption width + pTarget->m_captionWide = GetScreenWidthForCaption( pTarget->GetCaptionText(), m_hCaptionFont ); + + bool bDrawMultilineCaption = false; + + if ( m_iShouldWrapStaticLocators > 0 ) // conditionalized in locator.res + { + if ( pTarget->m_captionWide > ( ScreenWidth() * locator_split_maxwide_percent.GetFloat() ) ) + { + // we will double-line this + pTarget->m_captionWide = pTarget->m_captionWide * locator_split_len.GetFloat(); + bDrawMultilineCaption = true; + } + } + int totalWide = iconWide + ICON_GAP + pTarget->m_captionWide; + pTarget->m_iconX = centerX - totalWide * 0.5f; + pTarget->m_iconY = centerY - ( iconTall >> 1 ); + + // Lerp by speed on the Y axis + float iconY = pTarget->m_iconY; + + float diffY = fabsf( pTarget->m_iconY - pTarget->m_lastYPos ); + + float flLerpSpeed = gpGlobals->frametime * locator_lerp_speed.GetFloat(); + pTarget->m_iconY = static_cast( Approach( iconY, pTarget->m_lastYPos, MAX( 3.0f, flLerpSpeed * diffY ) ) ); + pTarget->m_centerY += ( pTarget->m_iconY - iconY ); + + pTarget->m_lastXPos = pTarget->m_iconX; + pTarget->m_lastYPos = pTarget->m_iconY; + + pTarget->m_bIsDrawing = true; + + vgui::Panel *pVguiTarget = pTarget->GetVguiTarget(); + + if ( pVguiTarget ) + { + int nPanelX, nPanelY; + nPanelX = 0; + nPanelY = 0; + + vgui::Label::Alignment nVguiTargetEdge = (vgui::Label::Alignment)pTarget->GetVguiTargetEdge(); + + int nWide = pVguiTarget->GetWide(); + int nTall = pVguiTarget->GetTall(); + + /* + const char *pchLookup = pTarget->GetVguiTargetLookup(); + if ( pchLookup[ 0 ] != '\0' ) + { + bool bLookupSuccess = false; + bLookupSuccess = pVguiTarget->LookupElementBounds( pchLookup, nPanelX, nPanelY, nWide, nTall ); + + Assert( bLookupSuccess ); + } + */ + + if ( nVguiTargetEdge == vgui::Label::a_north || + nVguiTargetEdge == vgui::Label::a_center || + nVguiTargetEdge == vgui::Label::a_south ) + { + nPanelX += nWide / 2; + } + else if ( nVguiTargetEdge == vgui::Label::a_northeast || + nVguiTargetEdge == vgui::Label::a_east || + nVguiTargetEdge == vgui::Label::a_southeast ) + { + nPanelX += nWide; + } + + if ( nVguiTargetEdge == vgui::Label::a_west || + nVguiTargetEdge == vgui::Label::a_center || + nVguiTargetEdge == vgui::Label::a_east ) + { + nPanelY += nTall / 2; + } + else if ( nVguiTargetEdge == vgui::Label::a_southwest || + nVguiTargetEdge == vgui::Label::a_south || + nVguiTargetEdge == vgui::Label::a_southeast ) + { + nPanelY += nTall; + } + + pVguiTarget->LocalToScreen( nPanelX, nPanelY ); + + DrawPointerBackground( pTarget, nPanelX, nPanelY, totalWide, iconTall, true ); + } + else + { + DrawPointerBackground( pTarget, pTarget->m_centerX, pTarget->m_centerY, totalWide, iconTall, false ); + } + + if ( pTarget->m_pIcon_onscreen ) + { + if ( !pTarget->m_bDrawControllerButton ) + { + // Don't draw the icon if we're on 360 and have a binding to draw + pTarget->m_pIcon_onscreen->DrawSelf( pTarget->GetIconX(), pTarget->GetIconY(), iconWide, iconTall, Color( 255, 255, 255, pTarget->m_alpha ) ); + } + } + + DrawTargetCaption( pTarget, pTarget->GetIconX() + iconWide + ICON_GAP, pTarget->GetIconCenterY(), bDrawMultilineCaption ); + if ( pTarget->DrawBindingName() ) + { + DrawBindingName( pTarget, pTarget->DrawBindingName(), pTarget->GetIconX() + (iconWide>>1), pTarget->GetIconY() + (iconTall>>1), pTarget->m_bDrawControllerButton ); + } + + // Draw the arrow. + int iArrowSize = ScreenWidth() * ICON_SIZE; // Always square width + DrawIndicatorArrow( pTarget->GetIconX(), pTarget->GetIconY(), iArrowSize, iArrowSize, pTarget->m_captionWide + ICON_GAP, pTarget->m_drawArrowDirection ); + + pTarget->m_bOnscreen = true; + + // Move the static icon position so the next static icon drawn this frame below this one. + m_staticIconPosition += iconTall + (iconTall>>2); + // Move down a little more if this one was multi-line + if ( bDrawMultilineCaption ) + { + m_staticIconPosition += (iconTall>>2); + } + return; +} + +//----------------------------------------------------------------------------- +// Purpose: Position and animate this target's icon on the screen. Based on +// options, draw the indicator arrows (arrows that point to the +// direction the player should turn to see the icon), text caption, +// and the 'simple' arrow which just points down to indicate the +// item the icon represents. +//----------------------------------------------------------------------------- +void CLocatorPanel::DrawDynamicIcon( CLocatorTarget *pTarget, bool bDrawCaption, bool bDrawSimpleArrow ) +{ + int alpha = pTarget->m_alpha; + + if( pTarget->m_bOccluded && !( (pTarget->GetIconEffectsFlags() & LOCATOR_ICON_FX_FORCE_CAPTION) || locator_topdown_style.GetBool() ) ) + { + return; + } + + // Draw the icon! + vgui::surface()->DrawSetColor( 255, 255, 255, alpha ); + + int iWide = pTarget->m_wide; + + if ( !pTarget->m_bOnscreen ) + { + // Width is always square for offscreen icons + iWide /= pTarget->m_widthScale_onscreen; + } + + // Figure out the caption width + pTarget->m_captionWide = GetScreenWidthForCaption( pTarget->GetCaptionText(), m_hCaptionFont ); + + bool bDrawMultilineCaption = false; + + if ( m_iShouldWrapStaticLocators > 0 ) // conditionalized in locator.res + { + if ( pTarget->m_captionWide > ( ScreenWidth() * locator_split_maxwide_percent.GetFloat() ) ) + { + // we will double-line this + pTarget->m_captionWide = pTarget->m_captionWide * locator_split_len.GetFloat(); + bDrawMultilineCaption = true; + } + } + + int totalWide = iWide; + + bool bShouldDrawCaption = ( (pTarget->GetIconEffectsFlags() & LOCATOR_ICON_FX_FORCE_CAPTION) || (!pTarget->m_bOccluded && pTarget->m_distFromPlayer <= ICON_DIST_TOO_FAR) || locator_topdown_style.GetBool() ); + + if( pTarget->m_bOnscreen && bDrawCaption && bShouldDrawCaption ) + { + totalWide += ( ICON_GAP + pTarget->m_captionWide ); + } + + pTarget->m_bIsDrawing = true; + + int nTargetX, nTargetY; + + vgui::Panel *pVguiTarget = pTarget->GetVguiTarget(); + + if ( pVguiTarget ) + { + nTargetX = 0; + nTargetY = 0; + + vgui::Label::Alignment nVguiTargetEdge = (vgui::Label::Alignment)pTarget->GetVguiTargetEdge(); + + int nWide = pVguiTarget->GetWide(); + int nTall = pVguiTarget->GetTall(); + + /* + const char *pchLookup = pTarget->GetVguiTargetLookup(); + + if ( pchLookup[ 0 ] != '\0' ) + { + bool bLookupSuccess = false; + bLookupSuccess = pVguiTarget->LookupElementBounds( pchLookup, nTargetX, nTargetY, nWide, nTall ); + Assert( bLookupSuccess ); + } + */ + + if ( nVguiTargetEdge == vgui::Label::a_north || + nVguiTargetEdge == vgui::Label::a_center || + nVguiTargetEdge == vgui::Label::a_south ) + { + nTargetX += nWide / 2; + } + else if ( nVguiTargetEdge== vgui::Label::a_northeast || + nVguiTargetEdge == vgui::Label::a_east || + nVguiTargetEdge == vgui::Label::a_southeast ) + { + nTargetX += nWide; + } + + if ( nVguiTargetEdge == vgui::Label::a_west || + nVguiTargetEdge == vgui::Label::a_center || + nVguiTargetEdge == vgui::Label::a_east ) + { + nTargetY += nTall / 2; + } + else if ( nVguiTargetEdge == vgui::Label::a_southwest || + nVguiTargetEdge == vgui::Label::a_south || + nVguiTargetEdge == vgui::Label::a_southeast ) + { + nTargetY += nTall; + } + + pVguiTarget->LocalToScreen( nTargetX, nTargetY ); + } + else if ( !pTarget->m_bOnscreen ) + { + nTargetX = pTarget->m_targetX; + nTargetY = pTarget->m_targetY; + } + else + { + nTargetX = pTarget->m_centerX; + nTargetY = pTarget->m_centerY; + } + + if ( pTarget->m_bOnscreen ) + { + DrawPointerBackground( pTarget, nTargetX, nTargetY, totalWide, pTarget->m_tall, true ); + } + else + { + // Offscreen we need to point the pointer toward out offscreen target + DrawPointerBackground( pTarget, nTargetX, nTargetY, totalWide, pTarget->m_tall, true ); + } + + if( pTarget->m_bOnscreen && pTarget->m_pIcon_onscreen ) + { + if ( !pTarget->m_bDrawControllerButton ) + { + pTarget->m_pIcon_onscreen->DrawSelf( pTarget->GetIconX(), pTarget->GetIconY(), iWide, pTarget->m_tall, Color( 255, 255, 255, alpha ) ); + } + } + else if ( pTarget->m_pIcon_offscreen ) + { + if ( !pTarget->m_bDrawControllerButtonOffscreen ) + { + pTarget->m_pIcon_offscreen->DrawSelf( pTarget->GetIconX(), pTarget->GetIconY(), iWide, pTarget->m_tall, Color( 255, 255, 255, alpha ) ); + } + } + + if( !pTarget->m_bOnscreen ) + { + if ( pTarget->DrawBindingNameOffscreen() ) + { + DrawBindingName( pTarget, pTarget->DrawBindingName(), pTarget->GetIconX() + (iWide>>1), pTarget->GetIconY() + (pTarget->m_tall>>1), pTarget->m_bDrawControllerButtonOffscreen ); + } + + if ( locator_background_style.GetInt() == 0 ) + { + // Draw the arrow. + DrawIndicatorArrow( pTarget->GetIconX(), pTarget->GetIconY(), iWide, pTarget->m_tall, 0, pTarget->m_drawArrowDirection ); + } + } + else if( bShouldDrawCaption ) + { + if( bDrawCaption ) + { + //ScreenWidth() * * pTarget->m_widthScale_onscreen + DrawTargetCaption( pTarget, pTarget->GetIconCenterX() + ICON_GAP + pTarget->GetIconWidth() * 0.5, pTarget->GetIconCenterY(), bDrawMultilineCaption ); + } + if ( pTarget->DrawBindingName() ) + { + DrawBindingName( pTarget, pTarget->DrawBindingName(), pTarget->GetIconX() + (iWide>>1), pTarget->GetIconY() + (pTarget->m_tall>>1), pTarget->m_bDrawControllerButton ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Some targets have text captions. Draw the text. +//----------------------------------------------------------------------------- +void CLocatorPanel::DrawTargetCaption( CLocatorTarget *pTarget, int x, int y, bool bDrawMultiline ) +{ + // Draw the caption + vgui::surface()->DrawSetTextFont( m_hCaptionFont ); + int fontTall = vgui::surface()->GetFontTall( m_hCaptionFont ); + int iCaptionWidth = GetScreenWidthForCaption( pTarget->GetCaptionText(), m_hCaptionFont ); + + if ( bDrawMultiline ) + { + iCaptionWidth *= locator_split_len.GetFloat(); + } + + // Mapbase fixes glow and shadow not working in multiline + bool bDrawGlow = locator_text_glow.GetBool(); + bool bDrawShadow = !IsConsole() && locator_text_drop_shadow.GetBool(); // Only draw drop shadow on PC because it looks crappy on a TV + + if ( !bDrawMultiline ) + { + if ( bDrawGlow ) + { + vgui::surface()->DrawSetTextFont( m_hCaptionGlowFont ); + Color glowColor = locator_text_glow_color.GetColor(); + vgui::surface()->DrawSetTextColor( glowColor.r(), glowColor.g(), glowColor.b(), ( glowColor.a() / 255.0f ) * pTarget->m_alpha ); + vgui::surface()->DrawSetTextPos( x - 1, y - (fontTall >>1) - 1 ); + vgui::surface()->DrawUnicodeString( pTarget->GetCaptionText() ); + vgui::surface()->DrawSetTextFont( m_hCaptionFont ); + } + + if ( bDrawShadow ) + { + // Draw black text (drop shadow) + vgui::surface()->DrawSetTextColor( 0,0,0, pTarget->m_alpha ); + vgui::surface()->DrawSetTextPos( x, y - (fontTall >>1) ); + vgui::surface()->DrawUnicodeString( pTarget->GetCaptionText() ); + } + + // Draw text + vgui::surface()->DrawSetTextColor( pTarget->m_captionColor.r(),pTarget->m_captionColor.g(),pTarget->m_captionColor.b(), pTarget->m_alpha ); + vgui::surface()->DrawSetTextPos( x - 1, y - (fontTall >>1) - 1 ); + vgui::surface()->DrawUnicodeString( pTarget->GetCaptionText() ); + } + else + { + int charX = x-1; + int charY = y - ( fontTall >> 1 ) - 1; + + int iWidth = 0; + + const wchar_t *pString = pTarget->GetCaptionText(); + int len = Q_wcslen( pString ); + + Color glowColor = locator_text_glow_color.GetColor(); + for ( int iChar = 0; iChar < len; ++ iChar ) + { + int charW = vgui::surface()->GetCharacterWidth( m_hCaptionFont, pString[ iChar ] ); + iWidth += charW; + + if ( iWidth > pTarget->m_captionWide && pString[iChar] == L' ' ) + { + charY += fontTall; + charX = x-1; + iWidth = 0; + } + + if ( bDrawGlow ) + { + vgui::surface()->DrawSetTextFont( m_hCaptionGlowFont ); + vgui::surface()->DrawSetTextColor( glowColor.r(), glowColor.g(), glowColor.b(), ( glowColor.a() / 255.0f ) * pTarget->m_alpha ); + vgui::surface()->DrawSetTextPos( charX - 1, charY ); + vgui::surface()->DrawUnicodeChar( pString[iChar] ); + vgui::surface()->DrawSetTextFont( m_hCaptionFont ); + } + + if ( bDrawShadow ) + { + // Draw black text (drop shadow) + vgui::surface()->DrawSetTextColor( 0,0,0, pTarget->m_alpha ); + vgui::surface()->DrawSetTextPos( charX, charY + 1 ); + vgui::surface()->DrawUnicodeChar( pString[iChar] ); + } + + // Draw text + vgui::surface()->DrawSetTextColor( pTarget->m_captionColor.r(),pTarget->m_captionColor.g(),pTarget->m_captionColor.b(), pTarget->m_alpha ); + vgui::surface()->DrawSetTextPos( charX, charY ); + vgui::surface()->DrawUnicodeChar( pString[iChar] ); + charX += charW; + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Figure out how wide (pixels) a string will be if rendered with this font +// +//----------------------------------------------------------------------------- +int CLocatorPanel::GetScreenWidthForCaption( const wchar_t *pString, vgui::HFont hFont ) +{ + int iWidth = 0; + + for ( int iChar = 0; iChar < Q_wcslen( pString ); ++ iChar ) + { + iWidth += vgui::surface()->GetCharacterWidth( hFont, pString[ iChar ] ); + } + + return iWidth; +} + +//----------------------------------------------------------------------------- +// Purpose: Some targets' captions contain information about key bindings that +// should be displayed to the player. Do so. +//----------------------------------------------------------------------------- +void CLocatorPanel::DrawBindingName( CLocatorTarget *pTarget, const char *pchBindingName, int x, int y, bool bController ) +{ + if ( !bController && !IsConsole() ) + { + // Draw the caption + vgui::surface()->DrawSetTextFont( m_hKeysFont ); + int fontTall = vgui::surface()->GetFontTall( m_hKeysFont ); + + char szBinding[ 256 ]; + Q_strcpy( szBinding, pchBindingName ? pchBindingName : "" ); + + if ( Q_strcmp( szBinding, "SEMICOLON" ) == 0 ) + { + Q_strcpy( szBinding, ";" ); + } + else if ( Q_strlen( szBinding ) == 1 && szBinding[ 0 ] >= 'a' && szBinding[ 0 ] <= 'z' ) + { + // Make single letters uppercase + szBinding[ 0 ] += ( 'A' - 'a' ); + } + + wchar_t wszCaption[ 64 ]; + g_pVGuiLocalize->ConstructString( wszCaption, sizeof(wchar_t)*64, szBinding, NULL ); + + int iWidth = GetScreenWidthForCaption( wszCaption, m_hKeysFont ); + +#ifdef MAPBASE + // Mods are capable of choosing a custom color + vgui::surface()->DrawSetTextColor( pTarget->m_bindingColor.r(), pTarget->m_bindingColor.g(), pTarget->m_bindingColor.b(), pTarget->m_alpha ); +#else + // Draw black text + vgui::surface()->DrawSetTextColor( 0,0,0, pTarget->m_alpha ); +#endif + vgui::surface()->DrawSetTextPos( x - (iWidth>>1) - 1, y - (fontTall >>1) - 1 ); + vgui::surface()->DrawUnicodeString( wszCaption ); + } + else + { + // Draw the caption + wchar_t wszCaption[ 64 ]; + + vgui::surface()->DrawSetTextFont( BUTTON_FONT_HANDLE ); + int fontTall = vgui::surface()->GetFontTall( BUTTON_FONT_HANDLE ); + + char szBinding[ 256 ]; + + // Turn localized string into icon character + Q_snprintf( szBinding, sizeof( szBinding ), "#GameUI_Icons_%s", pchBindingName ); + g_pVGuiLocalize->ConstructString( wszCaption, sizeof( wszCaption ), g_pVGuiLocalize->Find( szBinding ), 0 ); + g_pVGuiLocalize->ConvertUnicodeToANSI( wszCaption, szBinding, sizeof( szBinding ) ); + + int iWidth = GetScreenWidthForCaption( wszCaption, BUTTON_FONT_HANDLE ); + + int iLargeIconShift = MAX( 0, iWidth - ( ScreenWidth() * ICON_SIZE + ICON_GAP + ICON_GAP ) ); + + // Draw the button + vgui::surface()->DrawSetTextColor( 255,255,255, pTarget->m_alpha ); + vgui::surface()->DrawSetTextPos( x - (iWidth>>1) - iLargeIconShift, y - (fontTall >>1) ); + vgui::surface()->DrawUnicodeString( wszCaption ); + + } +} + +//----------------------------------------------------------------------------- +// Purpose: Draw an arrow to indicate that a target is offscreen +// +// iconWide is sent to this function so that the arrow knows how to straddle +// the icon that it is being drawn near. +//----------------------------------------------------------------------------- +void CLocatorPanel::DrawIndicatorArrow( int x, int y, int iconWide, int iconTall, int textWidth, int direction ) +{ + int wide = iconWide; + int tall = iconTall; + + //tall = wide = ScreenWidth() * ICON_SIZE; + + switch( direction ) + { + case DRAW_ARROW_LEFT: + vgui::surface()->DrawSetTexture( m_textureID_ArrowLeft ); + x -= wide; + y += iconTall / 2 - tall / 2; + vgui::surface()->DrawTexturedRect( x, y, x + wide, y + tall ); + break; + + case DRAW_ARROW_RIGHT: + vgui::surface()->DrawSetTexture( m_textureID_ArrowRight ); + x += iconWide + textWidth; + y += iconTall / 2 - tall / 2; + vgui::surface()->DrawTexturedRect( x, y, x + wide, y + tall ); + break; + + case DRAW_ARROW_UP: + vgui::surface()->DrawSetTexture( m_textureID_ArrowUp ); + x += iconWide / 2 - wide / 2; + y -= tall; + vgui::surface()->DrawTexturedRect( x, y, x + wide, y + tall ); + break; + + case DRAW_ARROW_DOWN: + vgui::surface()->DrawSetTexture( m_textureID_ArrowDown ); + x += iconWide / 2 - wide / 2; + y += iconTall; + vgui::surface()->DrawTexturedRect( x, y, x + wide, y + tall ); + break; + + default: + // Do not draw. + break; + } +} + +//----------------------------------------------------------------------------- +// Purpose: Draws a very simple arrow that points down. +//----------------------------------------------------------------------------- +void CLocatorPanel::DrawSimpleArrow( int x, int y, int iconWide, int iconTall ) +{ + vgui::surface()->DrawSetTexture( m_textureID_SimpleArrow ); + + y += iconTall; + + vgui::surface()->DrawTexturedRect( x, y, x + iconWide, y + iconTall ); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CLocatorPanel::GetIconPositionForOffscreenTarget( const Vector &vecDelta, float flDist, int *pXPos, int *pYPos ) +{ + float xpos, ypos; + float flRotation; + float flRadius = YRES(OFFSCREEN_ICON_POSITION_RADIUS); + + if ( locator_topdown_style.GetBool() ) + { + flRadius *= clamp( flDist / 600.0f, 1.75f, 3.0f ); + } + + GetTargetPosition( vecDelta, flRadius, &xpos, &ypos, &flRotation ); + + *pXPos = xpos; + *pYPos = ypos; +} + +//----------------------------------------------------------------------------- +// Purpose: Given a handle, return the pointer to the proper locator target. +//----------------------------------------------------------------------------- +CLocatorTarget *CLocatorPanel::GetPointerForHandle( int hTarget ) +{ + for( int i = 0 ; i < MAX_LOCATOR_TARGETS ; i++ ) + { + if( m_targets[ i ].m_isActive && m_targets[ i ].m_serialNumber == hTarget ) + { + return &m_targets[ i ]; + } + } + + return NULL; +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +int CLocatorPanel::AddTarget() +{ + for( int i = 0 ; i < MAX_LOCATOR_TARGETS ; i++ ) + { + if( !m_targets[ i ].m_isActive ) + { + m_targets[ i ].Activate( m_serializer ); + m_serializer++; + + return m_targets[ i ].m_serialNumber; + } + } + + DevWarning( "Locator Panel has no free targets!\n" ); + return -1; +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CLocatorPanel::RemoveTarget( int hTarget ) +{ + CLocatorTarget *pTarget = GetPointerForHandle( hTarget ); + + if( pTarget ) + { + pTarget->Deactivate(); + } +} + diff --git a/src/game/client/hud_locator_target.h b/src/game/client/hud_locator_target.h new file mode 100644 index 00000000..69939dbb --- /dev/null +++ b/src/game/client/hud_locator_target.h @@ -0,0 +1,184 @@ +//====== Copyright © 1996-2008, Valve Corporation, All rights reserved. ======= +// +// Purpose: Add entities to this system, and the Locator will maintain an arrow +// on the HUD that points to the entities when they are offscreen. +// +//============================================================================= + +#ifndef L4D_HUD_LOCATOR_H +#define L4D_HUD_LOCATOR_H +#ifdef _WIN32 +#pragma once +#endif + + +#include "vgui_controls/PHandle.h" + + +#define MAX_LOCATOR_BINDINGS_SHOWN 8 +#define MAX_LOCATOR_TARGETS 10 +#define LOCATOR_FLAGS_NONE 0x00000000 + +#define LOCATOR_ICON_FX_NONE 0x00000000 +#define LOCATOR_ICON_FX_PULSE_SLOW 0x00000001 +#define LOCATOR_ICON_FX_PULSE_FAST 0x00000002 +#define LOCATOR_ICON_FX_PULSE_URGENT 0x00000004 +#define LOCATOR_ICON_FX_ALPHA_SLOW 0x00000008 +#define LOCATOR_ICON_FX_ALPHA_FAST 0x00000010 +#define LOCATOR_ICON_FX_ALPHA_URGENT 0x00000020 +#define LOCATOR_ICON_FX_SHAKE_NARROW 0x00000040 +#define LOCATOR_ICON_FX_SHAKE_WIDE 0x00000080 +#define LOCATOR_ICON_FX_STATIC 0x00000100 // This icon draws at a fixed location on the HUD. +#define LOCATOR_ICON_FX_NO_OFFSCREEN 0x00000200 +#define LOCATOR_ICON_FX_FORCE_CAPTION 0x00000400 // Always draw the caption, even when the icon is occluded. +#define LOCATOR_ICON_FX_FADE_OUT 0x00000800 // Set when deactivated so it can smoothly vanish +#define LOCATOR_ICON_FX_FADE_IN 0x00001000 // Set when activated so it can smoothly appear + +#include "tier1/utlsymbol.h" + +// See comments in UtlSymbol on why this is useful +DECLARE_PRIVATE_SYMBOLTYPE( CGameInstructorSymbol ); + +//----------------------------------------------------------------------------- +// This class represents a single target to be tracked by the locator +//----------------------------------------------------------------------------- +class CLocatorTarget +{ +public: + bool m_bOriginInScreenspace; + Vector m_vecOrigin; // The location in the world to draw on the locator + + // ONLY the locator panel should fiddle with these fields. + bool m_isActive; + int m_serialNumber; + int m_frameLastUpdated; + bool m_bOnscreen; + bool m_bOccluded; + bool m_bVisible; + bool m_bIsDrawing; + float m_distFromPlayer; + CHudTexture *m_pIcon_onscreen; + CHudTexture *m_pIcon_offscreen; + int m_iBindingTick; + float m_flNextBindingTick; + float m_flNextOcclusionTest; + int m_iBindingChoicesCount; + const char *(m_pchBindingChoices[ MAX_LOCATOR_BINDINGS_SHOWN ]); + int m_iBindChoicesOriginalToken[ MAX_LOCATOR_BINDINGS_SHOWN ]; + + // Fields for drawing + int m_targetX; // screen X position of the actual target + int m_targetY; // screen Y position of the actual target + int m_iconX; // screen X position (top) + int m_iconY; // screen Y position (left) + int m_centerX; // screen X position (center) + int m_centerY; // screen Y position (center) + int m_wide; // draw width of icon (may be different from frame to frame as the icon's size animates, for instance) + int m_tall; // draw height of icon '' '' + float m_widthScale_onscreen; // for icons that are wider than standard + int m_alpha; // + float m_fadeStart; // time stamp when fade out started + float m_lerpStart; // time stamp when lerping started + float m_pulseStart; // time stamp when pulsing started + int m_declutterIndex; // sort order from the declutterer + int m_lastDeclutterIndex; // last sort order from the declutterer + int m_drawArrowDirection; // Whether to draw an arrow indicating this target is off-screen, also tells us which arrow to draw (left, up, etc.) + int m_captionWide; // How wide (pixels) my caption is. + bool m_bDrawControllerButton; + bool m_bDrawControllerButtonOffscreen; + int m_offsetX; // User-specified X offset which is applied in screenspace + int m_offsetY; // User-specified Y offset which is applied in screenspace + + // Fields for interpolating icon position + float m_flTimeLerpDone; // How much time left before this icon arrives where it is supposed to be. + int m_lastXPos; // screen X position last frame + int m_lastYPos; // '' Y + + CLocatorTarget( void ); + void Activate( int serialNumber ); + void Deactivate( bool bNoFade = false ); + void Update(); + + int GetIconX( void ); + int GetIconY( void ); + int GetIconCenterX( void ); + int GetIconCenterY( void ); + int GetIconWidth( void ); + int GetIconHeight( void ); + + void AddIconEffects( int add ) { m_iEffectsFlags |= add; } + void RemoveIconEffects( int remove ) { m_iEffectsFlags &= ~remove; } + int GetIconEffectsFlags() { return m_iEffectsFlags; } + void SetCaptionColor( Color col ) { m_captionColor = col; } + void SetCaptionColor( const char *pszCaptionColor ); + bool IsStatic(); + bool IsPresenting(); + void StartTimedLerp(); + void StartPresent(); + void EndPresent(); + + void UpdateVguiTarget( void ); + vgui::Panel *GetVguiTarget( void ); + void SetVguiTargetName( const char *pchVguiTargetName ); + const char *GetVguiTargetName( void ) { return m_szVguiTargetName.String(); } + void SetVguiTargetLookup( const char *pchVguiTargetLookup ); + const char *GetVguiTargetLookup( void ) { return m_szVguiTargetLookup.String(); } + void SetVguiTargetEdge( int nVguiEdge ); + int GetVguiTargetEdge( void ) const { return m_nVguiTargetEdge; } + + void SetOnscreenIconTextureName( const char *pszTexture ); + void SetOffscreenIconTextureName( const char *pszTexture ); + void SetBinding( const char *pszBinding ); + const char *UseBindingImage( char *pchIconTextureName, size_t bufSize ); + + const char *GetOnscreenIconTextureName() { return m_szOnscreenTexture.String(); } + const char *GetOffscreenIconTextureName() { return m_szOffscreenTexture.String(); } + const char *GetBinding() { return m_szBinding.String(); } + + void SetVisible( bool bVisible ); + bool IsVisible( void ); + + void SetCaptionText( const char *pszText, const char *pszParam ); + const wchar_t *GetCaptionText( void ) { return (const wchar_t *)m_wszCaption.Base(); } + bool HasCaptionText( void ) { return m_wszCaption.Count() > 1; } + + void DrawBindingName( const char *pchDrawName ) { m_pchDrawBindingName = pchDrawName; } + void DrawBindingNameOffscreen( const char *pchDrawName ) { m_pchDrawBindingNameOffscreen = pchDrawName; } + + const char *DrawBindingName( void ) { return m_pchDrawBindingName; } + const char *DrawBindingNameOffscreen( void ) { return m_pchDrawBindingNameOffscreen; } + + bool IsOnScreen() { return m_bOnscreen; } + bool IsOccluded() { return m_bOccluded; } + + +private: + CGameInstructorSymbol m_szVguiTargetName; + CGameInstructorSymbol m_szVguiTargetLookup; + vgui::DHANDLE m_hVguiTarget; + int m_nVguiTargetEdge; + + CGameInstructorSymbol m_szOnscreenTexture; + CGameInstructorSymbol m_szOffscreenTexture; + CGameInstructorSymbol m_szBinding; + + bool m_bWasControllerLast; + const char *m_pchDrawBindingName; + const char *m_pchDrawBindingNameOffscreen; + int m_iEffectsFlags; + CUtlVector< wchar_t > m_wszCaption; + +public: + Color m_captionColor; +#ifdef MAPBASE + Color m_bindingColor; +#endif +}; + +extern int Locator_AddTarget(); +extern void Locator_RemoveTarget( int hTarget ); +CLocatorTarget *Locator_GetTargetFromHandle( int hTarget ); +void Locator_ComputeTargetIconPositionFromHandle( int hTarget ); + + +#endif // L4D_HUD_LOCATOR_H \ No newline at end of file diff --git a/src/game/client/hud_pdump.cpp b/src/game/client/hud_pdump.cpp index de6124f7..acf06c8f 100644 --- a/src/game/client/hud_pdump.cpp +++ b/src/game/client/hud_pdump.cpp @@ -21,9 +21,15 @@ static CPDumpPanel *g_pPDumpPanel = NULL; // we pragma'd away in platform.h, so this little compiler specific hack will eliminate those warnings while // retaining our own warning setup...ywb #if defined( WIN32 ) && _MSC_VER <= 1920 + +#if _MSC_VER < 1900 #pragma warning( push ) #include #pragma warning( pop ) +#else +#include +#endif + #endif using namespace vgui; diff --git a/src/game/client/hudelement.h b/src/game/client/hudelement.h index 6954fe2a..af20e46c 100644 --- a/src/game/client/hudelement.h +++ b/src/game/client/hudelement.h @@ -60,6 +60,9 @@ public: // Hidden bits. // HIDEHUD_ flags that note when this element should be hidden in the HUD virtual void SetHiddenBits( int iBits ); +#ifdef MAPBASE_VSCRIPT + int GetHiddenBits() const { return m_iHiddenBits; } +#endif bool IsParentedToClientDLLRootPanel() const; void SetParentedToClientDLLRootPanel( bool parented ); diff --git a/src/game/client/iclientmode.h b/src/game/client/iclientmode.h index a313d8ff..48dcfa2a 100644 --- a/src/game/client/iclientmode.h +++ b/src/game/client/iclientmode.h @@ -113,6 +113,11 @@ public: virtual bool CanRecordDemo( char *errorMsg, int length ) const = 0; +#ifdef MAPBASE // From Alien Swarm SDK + virtual void OnColorCorrectionWeightsReset( void ) = 0; + virtual float GetColorCorrectionScale( void ) const = 0; +#endif + virtual void ComputeVguiResConditions( KeyValues *pkvConditions ) = 0; //============================================================================= diff --git a/src/game/client/iclientshadowmgr.h b/src/game/client/iclientshadowmgr.h index 174f57a8..df8f9352 100644 --- a/src/game/client/iclientshadowmgr.h +++ b/src/game/client/iclientshadowmgr.h @@ -97,6 +97,15 @@ public: // Set flashlight light world flag virtual void SetFlashlightLightWorld( ClientShadowHandle_t shadowHandle, bool bLightWorld ) = 0; +#ifdef ASW_PROJECTED_TEXTURES + virtual void GetFrustumExtents( ClientShadowHandle_t handle, Vector &vecMin, Vector &vecMax ) = 0; +#endif + +#ifdef DYNAMIC_RTT_SHADOWS + // Toggle shadow casting from world light sources + virtual void SetShadowFromWorldLightsEnabled( bool bEnable ) = 0; +#endif + virtual void SetShadowsDisabled( bool bDisabled ) = 0; virtual void ComputeShadowDepthTextures( const CViewSetup &pView ) = 0; diff --git a/src/game/client/in_joystick.cpp b/src/game/client/in_joystick.cpp index 20dd6770..9daf1506 100644 --- a/src/game/client/in_joystick.cpp +++ b/src/game/client/in_joystick.cpp @@ -25,7 +25,9 @@ #include "tier0/icommandline.h" #include "inputsystem/iinputsystem.h" #include "inputsystem/ButtonCode.h" +#if _MSC_VER < 1900 #include "math.h" +#endif #include "tier1/convar_serverbounded.h" #include "cam_thirdperson.h" diff --git a/src/game/client/in_main.cpp b/src/game/client/in_main.cpp index 5d2c6b0e..e94463c5 100644 --- a/src/game/client/in_main.cpp +++ b/src/game/client/in_main.cpp @@ -1225,6 +1225,9 @@ void CInput::CreateMove ( int sequence_number, float input_sample_frametime, boo } // Let the move manager override anything it wants to. +#ifdef VGUI_SCREEN_FIX + cmd->buttons |= IN_VALIDVGUIINPUT; +#endif if ( g_pClientMode->CreateMove( input_sample_frametime, cmd ) ) { // Get current view angles after the client mode tweaks with it diff --git a/src/game/client/iviewrender.h b/src/game/client/iviewrender.h index c66061ae..8d797dea 100644 --- a/src/game/client/iviewrender.h +++ b/src/game/client/iviewrender.h @@ -115,6 +115,13 @@ public: virtual void SetScreenOverlayMaterial( IMaterial *pMaterial ) = 0; virtual IMaterial *GetScreenOverlayMaterial( ) = 0; +#ifdef MAPBASE + virtual void SetIndexedScreenOverlayMaterial( int i, IMaterial *pMaterial ) = 0; + virtual IMaterial *GetIndexedScreenOverlayMaterial( int i ) = 0; + virtual void ResetIndexedScreenOverlays() = 0; + virtual int GetMaxIndexedScreenOverlays() const = 0; +#endif + virtual void WriteSaveGameScreenshot( const char *pFilename ) = 0; virtual void WriteSaveGameScreenshotOfSize( const char *pFilename, int width, int height, bool bCreatePowerOf2Padded = false, bool bWriteVTF = false ) = 0; diff --git a/src/game/client/mapbase/c_func_clientclip.cpp b/src/game/client/mapbase/c_func_clientclip.cpp new file mode 100644 index 00000000..f6595949 --- /dev/null +++ b/src/game/client/mapbase/c_func_clientclip.cpp @@ -0,0 +1,93 @@ +//========= Mapbase - https://github.com/mapbase-source/source-sdk-2013 ============// +// +// Purpose: +// +//===========================================================================// + +#include "cbase.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + + +class C_FuncClientClip : public C_BaseEntity +{ +public: + DECLARE_CLASS( C_FuncClientClip, C_BaseEntity ); + DECLARE_CLIENTCLASS(); + + void OnDataChanged( DataUpdateType_t type ); + void ClientThink(); + bool TestCollision( const Ray_t &ray, unsigned int mask, trace_t& trace ); + + bool m_bDisabled; +}; + +IMPLEMENT_CLIENTCLASS_DT( C_FuncClientClip, DT_FuncClientClip, CFuncClientClip ) + RecvPropBool( RECVINFO( m_bDisabled ) ), +END_RECV_TABLE() + +void C_FuncClientClip::OnDataChanged( DataUpdateType_t type ) +{ + BaseClass::OnDataChanged( type ); + + //if ( type == DATA_UPDATE_CREATED ) + //{ + SetSolid(GetMoveParent() ? SOLID_VPHYSICS : SOLID_BSP); // SOLID_VPHYSICS + //} + + if ( !m_bDisabled ) + { + VPhysicsDestroyObject(); + VPhysicsInitShadow( true, true ); + + // Think constantly updates the shadow + if (GetMoveParent()) + SetNextClientThink( CLIENT_THINK_ALWAYS ); + } + else + { + // Disabling + VPhysicsDestroyObject(); + SetNextClientThink( CLIENT_THINK_NEVER ); + } +} + +void C_FuncClientClip::ClientThink() +{ + // We shouldn't be thinking if we're disabled + Assert(!m_bDisabled); + + if (VPhysicsGetObject()) + { + // Constantly updates the shadow. + // This think function should really only be active when we're parented. + VPhysicsGetObject()->UpdateShadow( GetAbsOrigin(), GetAbsAngles(), false, TICK_INTERVAL ); + } + else + { + // This should never happen... + VPhysicsInitShadow( true, true ); + } + + BaseClass::ClientThink(); +} + +bool C_FuncClientClip::TestCollision( const Ray_t &ray, unsigned int mask, trace_t& trace ) +{ + if ( m_bDisabled ) + return false; + + if ( !VPhysicsGetObject() ) + return false; + + physcollision->TraceBox( ray, VPhysicsGetObject()->GetCollide(), GetAbsOrigin(), GetAbsAngles(), &trace ); + + if ( trace.DidHit() ) + { + trace.surface.surfaceProps = VPhysicsGetObject()->GetMaterialIndex(); + return true; + } + + return false; +} diff --git a/src/game/client/mapbase/c_func_fake_worldportal.cpp b/src/game/client/mapbase/c_func_fake_worldportal.cpp new file mode 100644 index 00000000..703d5a88 --- /dev/null +++ b/src/game/client/mapbase/c_func_fake_worldportal.cpp @@ -0,0 +1,171 @@ +//========= Mapbase - https://github.com/mapbase-source/source-sdk-2013 ============// +// +// Purpose: Recreates Portal 2 linked_portal_door visual functionality using SDK code only. +// (basically a combination of point_camera and func_reflective_glass) +// +// $NoKeywords: $ +//===========================================================================// +#include "cbase.h" +#include "view_shared.h" +#include "viewrender.h" +#include "c_func_fake_worldportal.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +IMPLEMENT_CLIENTCLASS_DT( C_FuncFakeWorldPortal, DT_FuncFakeWorldPortal, CFuncFakeWorldPortal ) + + RecvPropEHandle( RECVINFO( m_hTargetPlane ) ), + RecvPropVector( RECVINFO( m_PlaneAngles ) ), + RecvPropInt( RECVINFO( m_iSkyMode ) ), + RecvPropFloat( RECVINFO( m_flScale ) ), + RecvPropString( RECVINFO( m_iszRenderTarget ) ), + RecvPropEHandle( RECVINFO( m_hFogController ) ), + +END_RECV_TABLE() + +//----------------------------------------------------------------------------- +// Globals +//----------------------------------------------------------------------------- +C_EntityClassList g_FakeWorldPortalList; +template<> C_FuncFakeWorldPortal *C_EntityClassList::m_pClassList = NULL; + +C_FuncFakeWorldPortal* GetFakeWorldPortalList() +{ + return g_FakeWorldPortalList.m_pClassList; +} + + +//----------------------------------------------------------------------------- +// Constructor, destructor +//----------------------------------------------------------------------------- +C_FuncFakeWorldPortal::C_FuncFakeWorldPortal() +{ + m_iszRenderTarget[0] = '\0'; + + g_FakeWorldPortalList.Insert( this ); +} + +C_FuncFakeWorldPortal::~C_FuncFakeWorldPortal() +{ + g_FakeWorldPortalList.Remove( this ); +} + + +bool C_FuncFakeWorldPortal::ShouldDraw() +{ + return true; +} + + +//----------------------------------------------------------------------------- +// Iterates through fake world portals instead of just picking one +//----------------------------------------------------------------------------- +C_FuncFakeWorldPortal *NextFakeWorldPortal( C_FuncFakeWorldPortal *pStart, const CViewSetup& view, + Vector &vecAbsPlaneNormal, float &flLocalPlaneDist, const Frustum_t &frustum ) +{ + // Early out if no cameras + C_FuncFakeWorldPortal *pReflectiveGlass = NULL; + if (!pStart) + pReflectiveGlass = GetFakeWorldPortalList(); + else + pReflectiveGlass = pStart->m_pNext; + + cplane_t localPlane, worldPlane; + Vector vecMins, vecMaxs, vecLocalOrigin, vecAbsOrigin, vecDelta; + + for ( ; pReflectiveGlass != NULL; pReflectiveGlass = pReflectiveGlass->m_pNext ) + { + if ( pReflectiveGlass->IsDormant() ) + continue; + + if ( pReflectiveGlass->m_iViewHideFlags & (1 << CurrentViewID()) ) + continue; + + // Must have valid plane + if ( !pReflectiveGlass->m_hTargetPlane ) + continue; + + pReflectiveGlass->GetRenderBoundsWorldspace( vecMins, vecMaxs ); + if ( R_CullBox( vecMins, vecMaxs, frustum ) ) + continue; + + const model_t *pModel = pReflectiveGlass->GetModel(); + const matrix3x4_t& mat = pReflectiveGlass->EntityToWorldTransform(); + + int nCount = modelinfo->GetBrushModelPlaneCount( pModel ); + for ( int i = 0; i < nCount; ++i ) + { + modelinfo->GetBrushModelPlane( pModel, i, localPlane, &vecLocalOrigin ); + + MatrixTransformPlane( mat, localPlane, worldPlane ); // Transform to world space + + if ( view.origin.Dot( worldPlane.normal ) <= worldPlane.dist ) // Check for view behind plane + continue; + + VectorTransform( vecLocalOrigin, mat, vecAbsOrigin ); + VectorSubtract( vecAbsOrigin, view.origin, vecDelta ); + + if ( vecDelta.Dot( worldPlane.normal ) >= 0 ) // Backface cull + continue; + + flLocalPlaneDist = localPlane.dist; + vecAbsPlaneNormal = worldPlane.normal; + + return pReflectiveGlass; + } + } + + return NULL; +} + +void C_FuncFakeWorldPortal::OnDataChanged( DataUpdateType_t type ) +{ + // Reset render texture + m_pRenderTarget = NULL; + + // Reset fog + m_pFog = NULL; + + return BaseClass::OnDataChanged( type ); +} + +extern ITexture *GetWaterReflectionTexture( void ); + +ITexture *C_FuncFakeWorldPortal::RenderTarget() +{ + if (m_iszRenderTarget[0] != '\0') + { + if (!m_pRenderTarget) + { + // We don't use a CTextureReference for this because we don't want to shut down the texture on removal/change + m_pRenderTarget = materials->FindTexture( m_iszRenderTarget, TEXTURE_GROUP_RENDER_TARGET ); + } + + if (m_pRenderTarget) + return m_pRenderTarget; + } + + return GetWaterReflectionTexture(); +} + +fogparams_t *C_FuncFakeWorldPortal::GetFog() +{ + if (m_pFog) + return m_pFog; + + if (m_hFogController) + { + C_FogController *pFogController = dynamic_cast(m_hFogController.Get()); + if (pFogController) + { + m_pFog = &pFogController->m_fog; + } + else + { + Warning("%s is not an env_fog_controller\n", m_hFogController->GetEntityName()); + } + } + + return NULL; +} diff --git a/src/game/client/mapbase/c_func_fake_worldportal.h b/src/game/client/mapbase/c_func_fake_worldportal.h new file mode 100644 index 00000000..bc1d9e61 --- /dev/null +++ b/src/game/client/mapbase/c_func_fake_worldportal.h @@ -0,0 +1,62 @@ +//========= Mapbase - https://github.com/mapbase-source/source-sdk-2013 ============// +// +// Purpose: Recreates Portal 2 linked_portal_door functionality using SDK code only. +// (basically a combination of point_camera and func_reflective_glass) +// +// $NoKeywords: $ +//===========================================================================// + +#ifndef C_FUNC_FAKE_WORLDPORTAL +#define C_FUNC_FAKE_WORLDPORTAL + +#ifdef _WIN32 +#pragma once +#endif + +struct cplane_t; +class CViewSetup; + +class C_FuncFakeWorldPortal : public C_BaseEntity +{ +public: + DECLARE_CLASS( C_FuncFakeWorldPortal, C_BaseEntity ); + DECLARE_CLIENTCLASS(); + + C_FuncFakeWorldPortal(); + virtual ~C_FuncFakeWorldPortal(); + + virtual bool ShouldDraw(); + virtual void OnDataChanged( DataUpdateType_t type ); + + SkyboxVisibility_t SkyMode() { return m_iSkyMode; } + + ITexture *RenderTarget(); + + fogparams_t *GetFog(); + +public: + + EHANDLE m_hTargetPlane; + QAngle m_PlaneAngles; + SkyboxVisibility_t m_iSkyMode; + float m_flScale; + + EHANDLE m_hFogController; + fogparams_t *m_pFog; + + char m_iszRenderTarget[64]; + ITexture *m_pRenderTarget; + + C_FuncFakeWorldPortal *m_pNext; +}; + +//----------------------------------------------------------------------------- +// Do we have reflective glass in view? If so, what's the reflection plane? +//----------------------------------------------------------------------------- +C_FuncFakeWorldPortal *NextFakeWorldPortal( C_FuncFakeWorldPortal *pStart, const CViewSetup& view, + Vector &vecAbsPlaneNormal, float &flLocalPlaneDist, const Frustum_t &frustum ); + + +#endif // C_FUNC_FAKE_WORLDPORTAL + + diff --git a/src/game/client/mapbase/c_point_glow.cpp b/src/game/client/mapbase/c_point_glow.cpp new file mode 100644 index 00000000..590e0f3e --- /dev/null +++ b/src/game/client/mapbase/c_point_glow.cpp @@ -0,0 +1,103 @@ +//========= Mapbase - https://github.com/mapbase-source/source-sdk-2013 ============// +// +// Purpose: Mapbase off-shoot of tf_glow (created using SDK code only) +// +//===========================================================================// + +#include "cbase.h" +#include "glow_outline_effect.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + + +class C_PointGlow : public C_BaseEntity +{ +public: + DECLARE_CLASS( C_PointGlow, C_BaseEntity ); + DECLARE_CLIENTCLASS(); + + ~C_PointGlow(); + + enum + { + GLOW_VIS_ALWAYS, + GLOW_VIS_NOT_WHEN_VISIBLE, + GLOW_VIS_ONLY_WHEN_VISIBLE, + }; + + void OnDataChanged( DataUpdateType_t type ); + + CGlowObject *GetGlowObject( void ){ return m_pGlowEffect; } + void UpdateGlowEffect( void ); + void DestroyGlowEffect( void ); + + EHANDLE m_hGlowTarget; + color32 m_GlowColor; + + bool m_bGlowDisabled; + CGlowObject *m_pGlowEffect; +}; + +IMPLEMENT_CLIENTCLASS_DT( C_PointGlow, DT_PointGlow, CPointGlow ) + RecvPropEHandle( RECVINFO( m_hGlowTarget ) ), + RecvPropInt( RECVINFO( m_GlowColor ), 0, RecvProxy_IntToColor32 ), + RecvPropBool( RECVINFO( m_bGlowDisabled ) ), +END_RECV_TABLE() + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +C_PointGlow::~C_PointGlow() +{ + DestroyGlowEffect(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_PointGlow::OnDataChanged( DataUpdateType_t updateType ) +{ + BaseClass::OnDataChanged( updateType ); + + UpdateGlowEffect(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_PointGlow::UpdateGlowEffect( void ) +{ + // destroy the existing effect + if ( m_pGlowEffect ) + { + DestroyGlowEffect(); + } + + // create a new effect + if ( !m_bGlowDisabled ) + { + Vector4D vecColor( m_GlowColor.r, m_GlowColor.g, m_GlowColor.b, m_GlowColor.a ); + for (int i = 0; i < 4; i++) + { + if (vecColor[i] == 0.0f) + continue; + + vecColor[i] /= 255.0f; + } + + m_pGlowEffect = new CGlowObject( m_hGlowTarget, vecColor.AsVector3D(), vecColor.w, true ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_PointGlow::DestroyGlowEffect( void ) +{ + if ( m_pGlowEffect ) + { + delete m_pGlowEffect; + m_pGlowEffect = NULL; + } +} diff --git a/src/game/client/mapbase/c_vgui_text_display.cpp b/src/game/client/mapbase/c_vgui_text_display.cpp new file mode 100644 index 00000000..de8e8450 --- /dev/null +++ b/src/game/client/mapbase/c_vgui_text_display.cpp @@ -0,0 +1,289 @@ +//========= Mapbase - https://github.com/mapbase-source/source-sdk-2013 ============// +// +// Purpose: Displays easy, flexible VGui text. Mapbase equivalent of point_worldtext. +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" +#include "panelmetaclassmgr.h" +#include "VGuiMatSurface/IMatSystemSurface.h" +#include +#include +#include +#include "ienginevgui.h" +#include "c_vguiscreen.h" +#include "vgui_bitmapbutton.h" +#include "vgui_bitmappanel.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +using namespace vgui; + +//----------------------------------------------------------------------------- +// vgui_text_display +//----------------------------------------------------------------------------- +class C_VGuiTextDisplay : public C_BaseEntity +{ +public: + DECLARE_CLASS( C_VGuiTextDisplay, C_BaseEntity ); + DECLARE_CLIENTCLASS(); + + C_VGuiTextDisplay(); + ~C_VGuiTextDisplay(); + + virtual void PostDataUpdate( DataUpdateType_t updateType ); + + bool IsEnabled( void ) const { return m_bEnabled; } + + const char *GetDisplayText( void ) const { return m_szDisplayText; } + const char *GetFontName( void ) const { return m_szFont; } + int GetResolution( void ) const { return m_iResolution; } + vgui::Label::Alignment GetContentAlignment() const { return m_iContentAlignment; } + + bool NeedsTextUpdate() { return m_bTextNeedsUpdate; } + void UpdatedText() { m_bTextNeedsUpdate = false; } + +private: + bool m_bEnabled; + char m_szDisplayText[256]; + vgui::Label::Alignment m_iContentAlignment; + char m_szFont[64]; + int m_iResolution; + + bool m_bTextNeedsUpdate; +}; + +IMPLEMENT_CLIENTCLASS_DT( C_VGuiTextDisplay, DT_VGuiTextDisplay, CVGuiTextDisplay ) + RecvPropBool( RECVINFO( m_bEnabled ) ), + RecvPropString( RECVINFO( m_szDisplayText ) ), + RecvPropInt( RECVINFO( m_iContentAlignment ) ), + RecvPropString( RECVINFO( m_szFont ) ), + RecvPropInt( RECVINFO( m_iResolution ) ), +END_RECV_TABLE() + +C_VGuiTextDisplay::C_VGuiTextDisplay() +{ +} + +C_VGuiTextDisplay::~C_VGuiTextDisplay() +{ +} + +void C_VGuiTextDisplay::PostDataUpdate( DataUpdateType_t updateType ) +{ + BaseClass::PostDataUpdate( updateType ); + + // For now, always update + m_bTextNeedsUpdate = true; +} + +using namespace vgui; + +//----------------------------------------------------------------------------- +// Control screen +//----------------------------------------------------------------------------- +class C_TextDisplayPanel : public CVGuiScreenPanel +{ + DECLARE_CLASS( C_TextDisplayPanel, CVGuiScreenPanel ); + +public: + C_TextDisplayPanel( vgui::Panel *parent, const char *panelName ); + ~C_TextDisplayPanel( void ); + + virtual void ApplySchemeSettings( IScheme *pScheme ); + + void UpdateText(); + + virtual bool Init( KeyValues* pKeyValues, VGuiScreenInitData_t* pInitData ); + virtual void OnTick( void ); + virtual void Paint( void ); + +private: + + CHandle m_hVGUIScreen; + CHandle m_hScreenEntity; + + // VGUI specifics + Label *m_pDisplayTextLabel; +}; + +DECLARE_VGUI_SCREEN_FACTORY( C_TextDisplayPanel, "text_display_panel" ); + +CUtlVector g_TextDisplays; + +//----------------------------------------------------------------------------- +// Constructor: +//----------------------------------------------------------------------------- +C_TextDisplayPanel::C_TextDisplayPanel( vgui::Panel *parent, const char *panelName ) +: BaseClass( parent, "C_TextDisplayPanel"/*, vgui::scheme()->LoadSchemeFromFileEx( enginevgui->GetPanel( PANEL_CLIENTDLL ), "resource/WorldTextPanel.res", "WorldTextPanel" )*/ ) +{ + // Add ourselves to the global list of movie displays + g_TextDisplays.AddToTail( this ); +} + +//----------------------------------------------------------------------------- +// Purpose: Clean up the movie +//----------------------------------------------------------------------------- +C_TextDisplayPanel::~C_TextDisplayPanel( void ) +{ + // Remove ourselves from the global list of movie displays + g_TextDisplays.FindAndRemove( this ); +} + +//----------------------------------------------------------------------------- +// Purpose: Setup our scheme +//----------------------------------------------------------------------------- +void C_TextDisplayPanel::ApplySchemeSettings( IScheme *pScheme ) +{ + BaseClass::ApplySchemeSettings( pScheme ); + + /* + m_pDisplayTextLabel->SetFgColor( Color( 255, 255, 255, 255 ) ); + m_pDisplayTextLabel->SetText( "" ); + m_pDisplayTextLabel->SetVisible( false ); + */ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void C_TextDisplayPanel::UpdateText() +{ + color32 clr = m_hScreenEntity->GetRenderColor(); + + m_pDisplayTextLabel->SetFgColor( Color( clr.r, clr.g, clr.b, clr.a ) ); + m_pDisplayTextLabel->SetText( m_hScreenEntity->GetDisplayText() ); + + //SetSize( m_hScreenEntity->GetTextSize(), m_hScreenEntity->GetTextSize() ); + //m_pDisplayTextLabel->SetSize( m_hScreenEntity->GetTextSize(), m_hScreenEntity->GetTextSize() ); + + Label::Alignment iAlignment = m_hScreenEntity->GetContentAlignment(); + + switch (iAlignment) + { + // Use a special scaling method when using a south alignment + case Label::Alignment::a_southwest: + case Label::Alignment::a_south: + case Label::Alignment::a_southeast: + int lW, lT; + m_pDisplayTextLabel->GetContentSize( lW, lT ); + SetSize( m_hScreenEntity->GetResolution(), lT ); + m_pDisplayTextLabel->SetSize( m_hScreenEntity->GetResolution(), lT ); + + float sW, sT; + m_hVGUIScreen->GetSize( sW, sT ); + //Msg( "Screen width: %f, new height: %f\n", sW, sW * (lT / m_hScreenEntity->GetResolution()) ); + m_hVGUIScreen->SetHeight( sW * ((float)lT / (float)m_hScreenEntity->GetResolution()) ); + m_hVGUIScreen->SetPixelHeight( lT ); + break; + + default: + SetSize( m_hScreenEntity->GetResolution(), m_hScreenEntity->GetResolution() ); + m_pDisplayTextLabel->SetSize( m_hScreenEntity->GetResolution(), m_hScreenEntity->GetResolution() ); + break; + } + + m_pDisplayTextLabel->SetContentAlignment( iAlignment ); + + bool bWrap = true; + bool bCenterWrap = false; + switch (iAlignment) + { + // Center wrap if centered + case Label::Alignment::a_north: + case Label::Alignment::a_center: + case Label::Alignment::a_south: + bCenterWrap = true; + break; + + // HACKHACK: Don't wrap if using an east alignment + case Label::Alignment::a_northeast: + case Label::Alignment::a_east: + case Label::Alignment::a_southeast: + bWrap = false; + break; + } + + m_pDisplayTextLabel->SetWrap( bWrap ); + m_pDisplayTextLabel->SetCenterWrap( bCenterWrap ); + + //Msg( "Resolution is %i\n", m_hScreenEntity->GetResolution() ); + + const char *pszFontName = m_hScreenEntity->GetFontName(); + if (pszFontName && pszFontName[0] != '\0') + { + HFont font = scheme()->GetIScheme( GetScheme() )->GetFont( pszFontName ); + + if ( !font ) + { + extern HFont GetScriptFont( const char *, bool ); + font = GetScriptFont( pszFontName, false ); + } + + m_pDisplayTextLabel->SetFont( font ); + } + + m_pDisplayTextLabel->SetVisible( true ); +} + +//----------------------------------------------------------------------------- +// Initialization +//----------------------------------------------------------------------------- +bool C_TextDisplayPanel::Init( KeyValues* pKeyValues, VGuiScreenInitData_t* pInitData ) +{ + if ( !BaseClass::Init( pKeyValues, pInitData ) ) + return false; + + // Make sure we get ticked... + vgui::ivgui()->AddTickSignal( GetVPanel() ); + + m_pDisplayTextLabel = dynamic_cast(FindChildByName( "TextDisplay" )); + + // Save this for simplicity later on + m_hVGUIScreen = dynamic_cast( GetEntity() ); + if ( m_hVGUIScreen != NULL ) + { + // Also get the associated entity + m_hScreenEntity = dynamic_cast(m_hVGUIScreen->GetOwnerEntity()); + UpdateText(); + } + + return true; +} + +//----------------------------------------------------------------------------- +// Update the display string +//----------------------------------------------------------------------------- +void C_TextDisplayPanel::OnTick() +{ + if (m_hScreenEntity->NeedsTextUpdate()) + { + UpdateText(); + m_hScreenEntity->UpdatedText(); + } + + BaseClass::OnTick(); +} + +ConVar r_vguitext_bg( "r_vguitext_bg", "0" ); + +//----------------------------------------------------------------------------- +// Purpose: Update and draw the frame +//----------------------------------------------------------------------------- +void C_TextDisplayPanel::Paint( void ) +{ + // Black out the background (we could omit drawing under the video surface, but this is straight-forward) + if ( r_vguitext_bg.GetBool() ) + { + surface()->DrawSetColor( 0, 0, 0, 255 ); + surface()->DrawFilledRect( 0, 0, GetWide(), GetTall() ); + + //surface()->DrawSetColor( 64, 64, 64, 255 ); + //surface()->DrawFilledRect( 0, 0, m_pDisplayTextLabel->GetWide(), m_pDisplayTextLabel->GetTall() ); + } + + // Parent's turn + BaseClass::Paint(); +} diff --git a/src/game/client/mapbase/c_weapon_custom_hl2.cpp b/src/game/client/mapbase/c_weapon_custom_hl2.cpp new file mode 100644 index 00000000..784ba9ea --- /dev/null +++ b/src/game/client/mapbase/c_weapon_custom_hl2.cpp @@ -0,0 +1,91 @@ +//========= Mapbase - https://github.com/mapbase-source/source-sdk-2013 ============// +// +// Purpose: Client classes for Half-Life 2 based custom weapons. +// +// Author: Peter Covington (petercov@outlook.com) +// +//==================================================================================// + +#include "cbase.h" +#include "c_weapon__stubs.h" +#include "basehlcombatweapon_shared.h" +#include "c_basehlcombatweapon.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +class C_HLCustomWeaponMelee : public C_BaseHLBludgeonWeapon +{ +public: + DECLARE_CLASS(C_HLCustomWeaponMelee, C_BaseHLBludgeonWeapon); + DECLARE_CLIENTCLASS(); + DECLARE_PREDICTABLE(); + + C_HLCustomWeaponMelee(); + + void OnDataChanged( DataUpdateType_t updateType ); + + virtual const char* GetWeaponScriptName() { return m_iszWeaponScriptName; } +private: + char m_iszWeaponScriptName[128]; +}; + +STUB_WEAPON_CLASS_IMPLEMENT(weapon_hlcustommelee, C_HLCustomWeaponMelee); + +IMPLEMENT_CLIENTCLASS_DT(C_HLCustomWeaponMelee, DT_HLCustomWeaponMelee, CHLCustomWeaponMelee) +RecvPropString(RECVINFO(m_iszWeaponScriptName)), +END_RECV_TABLE(); + +C_HLCustomWeaponMelee::C_HLCustomWeaponMelee() +{ + m_iszWeaponScriptName[0] = '\0'; +} + +void C_HLCustomWeaponMelee::OnDataChanged( DataUpdateType_t updateType ) +{ + if (updateType == DATA_UPDATE_CREATED) + { + Precache(); + } + + BaseClass::OnDataChanged( updateType ); +} + + + +class C_HLCustomWeaponGun : public C_BaseHLCombatWeapon +{ +public: + DECLARE_CLASS(C_HLCustomWeaponGun, C_BaseHLCombatWeapon); + DECLARE_CLIENTCLASS(); + DECLARE_PREDICTABLE(); + + C_HLCustomWeaponGun(); + + void OnDataChanged( DataUpdateType_t updateType ); + + virtual const char* GetWeaponScriptName() { return m_iszWeaponScriptName; } +private: + char m_iszWeaponScriptName[128]; +}; + +STUB_WEAPON_CLASS_IMPLEMENT(weapon_hlcustomgun, C_HLCustomWeaponGun); + +IMPLEMENT_CLIENTCLASS_DT(C_HLCustomWeaponGun, DT_HLCustomWeaponGun, CHLCustomWeaponGun) +RecvPropString(RECVINFO(m_iszWeaponScriptName)), +END_RECV_TABLE(); + +C_HLCustomWeaponGun::C_HLCustomWeaponGun() +{ + m_iszWeaponScriptName[0] = '\0'; +} + +void C_HLCustomWeaponGun::OnDataChanged( DataUpdateType_t updateType ) +{ + if (updateType == DATA_UPDATE_CREATED) + { + Precache(); + } + + BaseClass::OnDataChanged( updateType ); +} diff --git a/src/game/client/mapbase/mapbase_autocubemap.cpp b/src/game/client/mapbase/mapbase_autocubemap.cpp new file mode 100644 index 00000000..79f2497d --- /dev/null +++ b/src/game/client/mapbase/mapbase_autocubemap.cpp @@ -0,0 +1,286 @@ +//========= Mapbase - https://github.com/mapbase-source/source-sdk-2013 ============// +// +// Purpose: A utility which automatically generates HDR and LDR cubemaps. +// This has the following purposes: +// +// 1. Allow both HDR and LDR cubemaps to be generated automatically after a map is compiled +// 2. Have a way to batch build cubemaps for several levels at once +// +// Author: Blixibon +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" + +#include "tier0/icommandline.h" +#include "igamesystem.h" +#include "filesystem.h" +#include "utlbuffer.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +extern const char *g_MapName; + +ConVar autocubemap_hdr_do_both( "autocubemap_hdr_do_both", "1" ); +ConVar autocubemap_hdr_value( "autocubemap_hdr_value", "2" ); + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class CAutoCubemapSystem : public CAutoGameSystem +{ +public: + CAutoCubemapSystem() : CAutoGameSystem( "CAutoCubemapSystem" ) + { + } + + virtual bool Init() + { + const char *pszFile = NULL; + if (CommandLine()->CheckParm( "-autocubemap", &pszFile )) + { + if (!pszFile || pszFile[0] == '\0') + { + // Assume that we just want to autocubemap the first map we load + // (no code here for now) + } + else + { + LoadFile( pszFile ); + } + + // Begin autocubemap with the first level we load + m_bAutoCubemapOnFirstLevel = true; + } + + return true; + } + + virtual void LevelInitPostEntity() + { + if (m_bAutoCubemapActive) + { + if (m_bAutoCubemapBuildingCubemaps) + { + // Check if we need to do the other HDR level + if (autocubemap_hdr_do_both.GetBool() && !m_bAutoCubemapDoingBoth) + { + m_bAutoCubemapBuildingCubemaps = false; + m_bAutoCubemapDoingBoth = true; + + // Change the HDR level and restart the map + //ConVarRef mat_hdr_level( "mat_hdr_level" ); + engine->ClientCmd_Unrestricted( VarArgs( "toggle mat_hdr_level 0 %i; restart", autocubemap_hdr_value.GetInt() ) ); + } + else + { + // Go to the next map + m_bAutoCubemapBuildingCubemaps = false; + m_bAutoCubemapDoingBoth = false; + + m_AutoCubemapMapsIndex++; + if (m_AutoCubemapMapsIndex < m_AutoCubemapMaps.Count()) + { + engine->ClientCmd_Unrestricted( VarArgs( "map %s", m_AutoCubemapMaps[m_AutoCubemapMapsIndex] ) ); + } + else + { + // CUBEMAPPER FINISHED + m_AutoCubemapMaps.PurgeAndDeleteElements(); + m_AutoCubemapMapsIndex = 0; + m_bAutoCubemapActive = false; + + Msg( "CUBEMAPPER FINISHED\n" ); + + if (autocubemap_hdr_do_both.GetBool()) + { + engine->ClientCmd_Unrestricted( VarArgs( "mat_hdr_level %i", m_iAutoCubemapUserHDRLevel ) ); + } + } + } + } + else + { + // Build cubemaps for this map + m_bAutoCubemapBuildingCubemaps = true; + engine->ClientCmd_Unrestricted( "exec buildcubemaps_prep; buildcubemaps" ); + } + } + else if (m_bAutoCubemapOnFirstLevel) + { + // Start autocubemap now + StartAutoCubemap(); + m_bAutoCubemapOnFirstLevel = false; + } + } + + //------------------------------------------------------------------------------------- + + void StartAutoCubemap() + { + if (m_AutoCubemapMaps.Count() <= 0) + { + //Msg("No maps to cubemap with!\n"); + //return; + + if (C_BasePlayer::GetLocalPlayer() == NULL) + { + Msg( "Must be in a level (or have a loaded map list) to begin autocubemap\n" ); + return; + } + + // Just do this map + m_AutoCubemapMaps.AddToTail( strdup( g_MapName ) ); + } + + if (autocubemap_hdr_do_both.GetBool()) + { + // Save the user's HDR level + ConVarRef mat_hdr_level( "mat_hdr_level" ); + m_iAutoCubemapUserHDRLevel = mat_hdr_level.GetInt(); + } + + m_bAutoCubemapActive = true; + m_AutoCubemapMapsIndex = 0; + + if (FStrEq( m_AutoCubemapMaps[m_AutoCubemapMapsIndex], g_MapName )) + { + // Build cubemaps right here, right now + m_bAutoCubemapBuildingCubemaps = true; + engine->ClientCmd_Unrestricted( "exec buildcubemaps_prep; buildcubemaps" ); + } + else + { + // Go to that map + engine->ClientCmd_Unrestricted( VarArgs( "map %s", m_AutoCubemapMaps[m_AutoCubemapMapsIndex] ) ); + } + } + + void LoadFile( const char *pszFile ) + { + KeyValues *pKV = new KeyValues( "AutoCubemap" ); + + if ( pKV->LoadFromFile( filesystem, pszFile, NULL ) ) + { + KeyValues *pSubKey = pKV->GetFirstSubKey(); + + while ( pSubKey ) + { + m_AutoCubemapMaps.AddToTail( strdup(pSubKey->GetName()) ); + pSubKey = pSubKey->GetNextKey(); + } + + Msg( "Initted autocubemap\n" ); + } + else + { + Warning( "Unable to load autocubemap file \"%s\"\n", pszFile ); + } + + pKV->deleteThis(); + } + + void Clear() + { + m_bAutoCubemapActive = false; + m_bAutoCubemapBuildingCubemaps = false; + m_bAutoCubemapDoingBoth = false; + + m_AutoCubemapMaps.PurgeAndDeleteElements(); + m_AutoCubemapMapsIndex = 0; + } + + void PrintState() + { + char szCmd[1024] = { 0 }; + + if (m_AutoCubemapMaps.Count() > 0) + { + Q_strncpy( szCmd, "=== CUBEMAPPER MAP LIST ===\n", sizeof( szCmd ) ); + + FOR_EACH_VEC( m_AutoCubemapMaps, i ) + { + Q_snprintf( szCmd, sizeof( szCmd ), "%s%s\n", szCmd, m_AutoCubemapMaps[i] ); + } + + Q_strncat( szCmd, "========================", sizeof( szCmd ), COPY_ALL_CHARACTERS ); + + Q_snprintf( szCmd, sizeof( szCmd ), "%s\nNumber of maps: %i (starting at %i)\n", szCmd, m_AutoCubemapMaps.Count(), m_AutoCubemapMapsIndex ); + } + else + { + Q_strncat( szCmd, "========================\n", sizeof( szCmd ), COPY_ALL_CHARACTERS ); + Q_strncat( szCmd, "There are no maps selected. Use 'autocubemap_init' to load a map list.\nIf 'autocubemap_start' is executed while no maps are selected, only the current map will have cubemaps generated.\n", sizeof( szCmd ), COPY_ALL_CHARACTERS ); + Q_strncat( szCmd, "========================\n", sizeof( szCmd ), COPY_ALL_CHARACTERS ); + } + + Msg( "%s", szCmd ); + } + + //------------------------------------------------------------------------------------- + + bool m_bAutoCubemapActive = false; + bool m_bAutoCubemapBuildingCubemaps = false; + bool m_bAutoCubemapDoingBoth = false; + int m_iAutoCubemapUserHDRLevel; // For setting the user back to the right HDR level when we're finished + + // Start autocubemap with the first level we load (used for launch parameter) + bool m_bAutoCubemapOnFirstLevel = false; + + CUtlVector m_AutoCubemapMaps; + int m_AutoCubemapMapsIndex; +}; + +CAutoCubemapSystem g_AutoCubemapSystem; + +CON_COMMAND( autocubemap_init, "Inits autocubemap" ) +{ + if (gpGlobals->maxClients > 1) + { + Msg( "Can't run autocubemap in multiplayer\n" ); + return; + } + + if (args.ArgC() <= 1) + { + Msg("Format: autocubemap_init \n"); + return; + } + + g_AutoCubemapSystem.LoadFile( args.Arg( 1 ) ); +} + +CON_COMMAND( autocubemap_print, "Prints current autocubemap information" ) +{ + if (gpGlobals->maxClients > 1) + { + Msg("Can't run autocubemap in multiplayer\n"); + return; + } + + g_AutoCubemapSystem.PrintState(); +} + +CON_COMMAND( autocubemap_clear, "Clears autocubemap stuff" ) +{ + if (gpGlobals->maxClients > 1) + { + Msg("Can't run autocubemap in multiplayer\n"); + return; + } + + g_AutoCubemapSystem.Clear(); +} + +CON_COMMAND( autocubemap_start, "Begins the autocubemap (it's recommended to check 'autocubemap_print' before running this command)" ) +{ + if (gpGlobals->maxClients > 1) + { + Msg("Can't run autocubemap in multiplayer\n"); + return; + } + + g_AutoCubemapSystem.StartAutoCubemap(); +} diff --git a/src/game/client/mapbase/vscript_vgui.cpp b/src/game/client/mapbase/vscript_vgui.cpp new file mode 100644 index 00000000..805d3305 --- /dev/null +++ b/src/game/client/mapbase/vscript_vgui.cpp @@ -0,0 +1,4066 @@ +//========= Mapbase - https://github.com/mapbase-source/source-sdk-2013 ============// +// +// Purpose: +// +// $NoKeywords: $ +// +// Author: samisalreadytaken +// +//=============================================================================// + + +#include "cbase.h" +#include "tier1/utlcommon.h" + +#include "inputsystem/iinputsystem.h" +#include "iinput.h" + +#include +#include +#include +#include +#include +#include + +#include + +#include "matsys_controls/matsyscontrols.h" +#include "VGuiMatSurface/IMatSystemSurface.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +//#include + +#if VGUI_TGA_IMAGE_PANEL +#include "bitmap/tgaloader.h" +#endif + +#if !defined(NO_STEAM) +#include "steam/steam_api.h" +#include "vgui_avatarimage.h" +#endif + +#include "view.h" +#include "hudelement.h" +#include "iclientmode.h" // g_pClientMode->GetViewport() + +#include "vscript_vgui.h" +#include "vscript_vgui.nut" + + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + + +//============================================================================= +// +// Exposing a new panel class (e.g. vgui::FileOpenDialog): +// +// 1. Create C++ bindings using 'CLASS_HELPER_INTERFACE( FileOpenDialog, Frame ){};' +// 2. Define script bindings using '#define DEFINE_VGUI_SCRIPTFUNC_FileOpenDialog()' +// 3. Create 'class CScript_FileOpenDialog : FileOpenDialog' with vgui message callbacks and overrides if needed +// 4. Create script helper using 'BEGIN_VGUI_HELPER( FileOpenDialog )', 'END_VGUI_HELPER()'. This determines the script class name. +// 5. Register script bindings with 'BEGIN_SCRIPTDESC_VGUI( FileOpenDialog )', 'END_SCRIPTDESC()' +// 6. Add new condition in CScriptVGUI::CreatePanel() +// +// +// +// CScript_FileOpenDialog_Helper +// ^^ +// IScript_FileOpenDialog << CScript_FileOpenDialog +// ^^ ^^ +// IScript_Frame FileOpenDialog +// ^^ ^^ +// IScript_Panel Frame +// ^^ ^^ +// CScriptVGUIObject Panel +// +//============================================================================= + + +// When enabled, script panels will be parented to custom root panels. +// When disabled, script panels will be parented to engine root panels, and allow Z values for script panels to be interplaced amongst non-script panels. +// Changing this is not backwards compatible, as existing top level script panel depth would then change relative to non-script panels. +#define SCRIPT_ENGINE_ROOT_PANELS 1 + +// +// Options to restrict where script panels can be parented to. +// The safest options any game can have are HUD viewport and clientdll. +// + +#define ALLOW_ROOT_PANEL_PARENT 1 + +#define ALLOW_HUD_VIEWPORT_ROOT_PARENT 1 + +#define ALLOW_CLIENTDLL_ROOT_PARENT 1 + +#define ALLOW_GAMEUI_ROOT_PARENT 0 + +// On level transitions Restore is called up to 4 times in a row (due to .hl? client state files), each time +// trying to restore script panels from pre and post transitions, failing every time because script panels are +// destroyed on level shutdown but after client state files are written. +// +// Script variables are also reset between each OnRestore callback, causing duplicate panels if user scripts create panels +// by checking restored script variables. +// +// The workaround hack is to queue OnRestore callbacks with a think function. +// +// This code is left here for testing. +#define SCRIPT_VGUI_SAVERESTORE 0 + +#define SCRIPT_VGUI_SIGNAL_INTERFACE 0 + + + +#ifdef _DEBUG +#define DebugMsg(...) ConColorMsg( Color(196, 196, 156, 255), __VA_ARGS__ ) +#define DebugWarning(...) Warning( __VA_ARGS__ ) +#define DebugDevMsg(...) DevMsg( __VA_ARGS__ ) + +#define DBG_PARAM(...) __VA_ARGS__ +#else +#define DebugMsg(...) (void)(0) +#define DebugWarning(...) (void)(0) +#define DebugDevMsg(...) (void)(0) + +#define DBG_PARAM(...) +#endif + + + +template< typename T > +class CCopyableUtlVectorConservative : public CUtlVectorConservative< T > +{ + typedef CUtlVectorConservative< T > BaseClass; +public: + explicit CCopyableUtlVectorConservative( int growSize = 0, int initSize = 0 ) : BaseClass( growSize, initSize ) {} + explicit CCopyableUtlVectorConservative( T* pMemory, int numElements ) : BaseClass( pMemory, numElements ) {} + CCopyableUtlVectorConservative( CCopyableUtlVectorConservative const& vec ) { this->CopyArray( vec.Base(), vec.Count() ); } +}; + + +using namespace vgui; +class IScriptVGUIObject; +struct FontData_t; + +// Aliases contain only one font definition unless 'yres' was defined +typedef CCopyableUtlVectorConservative< FontData_t > fontalias_t; +typedef CUtlDict< fontalias_t > CFontDict; + + +CFontDict g_ScriptFonts( k_eDictCompareTypeCaseSensitive ); +CUtlVector< int > g_ScriptTextureIDs; +CUtlLinkedList< IScriptVGUIObject*, unsigned short > g_ScriptPanels; + + +// Boundary is not checked in Surface, keep count manually to sanitise user input. +static int g_nFontCount = 0; + +static inline HFont IntToFontHandle( int i ) +{ + if ( i < 0 || i > g_nFontCount ) + return INVALID_FONT; + return static_cast< unsigned int >(i); +} + +// vscript does not support unsigned int, +// but the representation of the handle does not matter, +// and these handles are CUtlVector indices +static inline int HandleToInt( unsigned int i ) +{ + return static_cast< int >(i); +} + + +struct FontData_t +{ + HFont font; + char *name; + int tall; + int weight; + int blur; + int scanlines; + int flags; + //int range_min; + //int range_max; + int yres_min; + int yres_max; + bool proportional; +}; + +static const char *GetFixedFontName( const char *name, bool proportional ) +{ + static char fontName[64]; + V_snprintf( fontName, sizeof(fontName), "%s-%s", name, proportional ? "p" : "no" ); + return fontName; +} + +CON_COMMAND( vgui_spew_fonts_script, "" ) +{ + char fontName[64]; + + FOR_EACH_DICT_FAST( g_ScriptFonts, i ) + { + const FontData_t &data = g_ScriptFonts[i].Head(); + const char *name = surface()->GetFontName( data.font ); + const char *alias = g_ScriptFonts.GetElementName(i); + + // Strip off the appendix "-p" / "-no" + V_StrLeft( alias, V_strlen(alias) - (data.proportional ? 2 : 3), fontName, sizeof(fontName) ); + + Msg( " %2d: HFont:0x%8.8lx, %s, %s, font:%s, tall:%d(%d) {%d}\n", + i, + data.font, + fontName, + alias, + name ? name : "??", + surface()->GetFontTall( data.font ), + surface()->GetFontTallRequested( data.font ), + g_ScriptFonts[i].Count() ); + } +} + +bool LoadFont( const FontData_t &font DBG_PARAM(, const char *fontAlias) ) +{ + if ( font.yres_min ) + { + int nScreenWide, nScreenTall; + surface()->GetScreenSize( nScreenWide, nScreenTall ); + + if ( nScreenTall < font.yres_min ) + return false; + + if ( font.yres_max && nScreenTall > font.yres_max ) + return false; + } + + int tall = font.tall; + int blur = font.blur; + int scanlines = font.scanlines; + + if ( font.proportional && !font.yres_min ) + { + tall = scheme()->GetProportionalScaledValue( tall ); + blur = scheme()->GetProportionalScaledValue( blur ); + scanlines = scheme()->GetProportionalScaledValue( scanlines ); + } + + bool bSuccess = surface()->SetFontGlyphSet( + font.font, + font.name, + tall, + font.weight, + blur, + scanlines, + font.flags ); + + NOTE_UNUSED( bSuccess ); + if ( bSuccess ) + { + if ( font.yres_min ) + DebugMsg( "Load font [%li]%s [%d %d]\n", font.font, fontAlias, font.yres_min, font.yres_max ); + else + DebugMsg( "Load font [%li]%s\n", font.font, fontAlias ); + } + else + { + DebugWarning( "Failed to load font [%li]%s\n", font.font, fontAlias ); + } + + return true; +} + +void ReloadScriptFontGlyphs() +{ + // Invalidate cached values + if ( g_pScriptVM ) + g_pScriptVM->Run( "ISurface.__OnScreenSizeChanged()" ); + + FOR_EACH_DICT_FAST( g_ScriptFonts, i ) + { + const fontalias_t &alias = g_ScriptFonts[i]; + for ( int j = 0; j < alias.Count(); ++j ) + { + if ( LoadFont( alias.Element(j) DBG_PARAM(, g_ScriptFonts.GetElementName(i)) ) ) + break; + } + } +} + + +static inline void InitRootPanel( Panel *p, VGuiPanel_t parent, const char *name ) +{ + int w, h; + surface()->GetScreenSize( w, h ); + p->Init( 0, 0, w, h ); + p->SetName( name ); + p->SetVisible( true ); + p->SetPaintEnabled( false ); + p->SetPaintBackgroundEnabled( false ); + p->SetPaintBorderEnabled( false ); + p->SetPostChildPaintEnabled( false ); + p->SetParent( enginevgui->GetPanel( parent ) ); +} + +class CScriptRootPanel : public Panel +{ +public: + CScriptRootPanel() + { + InitRootPanel( this, PANEL_ROOT, "VScriptRoot" ); + } + + void OnTick() + { + if ( m_nLastFrame == gpGlobals->framecount ) + return; + + ReloadScriptFontGlyphs(); + ivgui()->RemoveTickSignal( GetVPanel() ); + } + + // Used as a callback to font invalidation. + // Ideally script fonts would be loaded along with others in engine. + // In that case CScriptRootPanel would be removed, and + // g_pScriptRootPanel would be CScriptRootDLLPanel inside #if SCRIPT_ENGINE_ROOT_PANELS + void OnScreenSizeChanged( int w, int t ) + { + // Reload fonts in the next vgui frame + ivgui()->AddTickSignal( GetVPanel() ); + m_nLastFrame = gpGlobals->framecount; + + // Invalidate cached values + if ( g_pScriptVM ) + g_pScriptVM->Run( "ISurface.__OnScreenSizeChanged()" ); + + Panel::OnScreenSizeChanged( w, t ); + } + +private: + int m_nLastFrame; +}; + +CScriptRootPanel *g_pScriptRootPanel = NULL; + +#if SCRIPT_ENGINE_ROOT_PANELS +class CScriptRootDLLPanel : public Panel +{ +public: + CScriptRootDLLPanel( VGuiPanel_t parent, const char *name ) + { + InitRootPanel( this, parent, name ); + } +}; + +#if ALLOW_CLIENTDLL_ROOT_PARENT +CScriptRootDLLPanel *g_pScriptClientDLLPanel = NULL; +#endif +#if ALLOW_GAMEUI_ROOT_PARENT +CScriptRootDLLPanel *g_pScriptGameUIDLLPanel = NULL; +#endif +#endif + +void VGUI_DestroyScriptRootPanels() +{ + if ( g_pScriptRootPanel ) + { + delete g_pScriptRootPanel; + g_pScriptRootPanel = NULL; + } +#if SCRIPT_ENGINE_ROOT_PANELS +#if ALLOW_CLIENTDLL_ROOT_PARENT + if ( g_pScriptClientDLLPanel ) + { + delete g_pScriptClientDLLPanel; + g_pScriptClientDLLPanel = NULL; + } +#endif +#if ALLOW_GAMEUI_ROOT_PARENT + if ( g_pScriptGameUIDLLPanel ) + { + delete g_pScriptGameUIDLLPanel; + g_pScriptGameUIDLLPanel = NULL; + } +#endif +#endif +} + +VPANEL VGUI_GetScriptRootPanel( VGuiPanel_t type ) +{ +#if SCRIPT_ENGINE_ROOT_PANELS + switch ( type ) + { + case PANEL_ROOT: +#if ALLOW_ROOT_PANEL_PARENT + { + if ( !g_pScriptRootPanel ) + g_pScriptRootPanel = new CScriptRootPanel(); + + return g_pScriptRootPanel->GetVPanel(); + } +#endif + case PANEL_CLIENTDLL: +#if ALLOW_CLIENTDLL_ROOT_PARENT + { + if ( !g_pScriptClientDLLPanel ) + g_pScriptClientDLLPanel = new CScriptRootDLLPanel( PANEL_CLIENTDLL, "VScriptClient" ); + + return g_pScriptClientDLLPanel->GetVPanel(); + } +#endif + case PANEL_GAMEUIDLL: +#if ALLOW_GAMEUI_ROOT_PARENT + { + if ( !g_pScriptGameUIDLLPanel ) + g_pScriptGameUIDLLPanel = new CScriptRootDLLPanel( PANEL_GAMEUIDLL, "VScriptGameUI" ); + + return g_pScriptGameUIDLLPanel->GetVPanel(); + } +#endif + default: return NULL; + } +#else + return enginevgui->GetPanel(type); +#endif +} + + +// +// Escapes "vgui/" prepended to the file name in CSchemeManager::GetImage(). +// +IImage *vgui_GetImage( const char *imageName, bool hardwareFilter ) +{ + char fileName[MAX_PATH]; + V_snprintf( fileName, sizeof( fileName ), "../%s", imageName ); + + return scheme()->GetImage( fileName, hardwareFilter ); +} + + +//-------------------------------------------------------------- +// +//-------------------------------------------------------------- +class CScriptSurface +{ +public: + void PlaySound( const char* sound ); + void SetColor( int r, int g, int b, int a ); + void DrawFilledRect( int x0, int y0, int width, int height ); + void DrawFilledRectFade( int x0, int y0, int width, int height, int a0, int a1, bool bHorz ); + void DrawOutlinedRect( int x0, int y0, int width, int height, int thickness ); + void DrawLine( int x0, int y0, int x1, int y1 ); + void DrawOutlinedCircle( int x, int y, int radius, int segments ); + + void SetTextColor( int r, int g, int b, int a ); + void SetTextPos( int x, int y ); + void SetTextFont( int font ); + void DrawText( const char *text, int drawType/* = FONT_DRAW_DEFAULT*/ ); + void DrawUnicodeChar( int ch, int drawType/* = FONT_DRAW_DEFAULT*/ ); + + int GetFont( const char* name, bool proportional, const char* schema ); + int GetTextWidth( int font, const char* text ); + int GetFontTall( int font ); + int GetCharacterWidth( int font, int ch ); + + void CreateFont( const char *customName, const char *windowsFontName, int tall, int weight, int blur, int scanlines, int flags, int yresMin, int yresMax, bool proportional ); + bool AddCustomFontFile( const char *fontFileName ); + + int GetTextureID( char const *filename ); + int ValidateTexture( const char *filename, bool hardwareFilter, bool forceReload, bool procedural ); + void SetTextureFile( int id, const char *filename, bool hardwareFilter ); + int GetTextureWide( int id ); + int GetTextureTall( int id ); + void SetTexture( int id ); + + void DrawTexturedRect( int x0, int y0, int width, int height ); + void DrawTexturedSubRect( int x0, int y0, int x1, int y1, float texs0, float text0, float texs1, float text1 ); + + // ------------------------------------------------------------ + // Utility functions + // ------------------------------------------------------------ + + void DrawTexturedBox( int texture, int x, int y, int wide, int tall, int r, int g, int b, int a ); + void DrawColoredText( int font, int x, int y, int r, int g, int b, int a, const char *text ); + void DrawColoredTextRect( int font, int x, int y, int w, int h, int r, int g, int b, int a, const char *text ); + void DrawTexturedRectRotated( int x, int y, int w, int t, float yaw ); + +} script_surface; + +BEGIN_SCRIPTDESC_ROOT_NAMED( CScriptSurface, "ISurface", SCRIPT_SINGLETON ) + DEFINE_SCRIPTFUNC( PlaySound, "" ) + + DEFINE_SCRIPTFUNC( SetColor, "" ) + DEFINE_SCRIPTFUNC( DrawFilledRect, "" ) + DEFINE_SCRIPTFUNC( DrawFilledRectFade, "" ) + DEFINE_SCRIPTFUNC( DrawOutlinedRect, "" ) + DEFINE_SCRIPTFUNC( DrawLine, "" ) + DEFINE_SCRIPTFUNC( DrawOutlinedCircle, "" ) + + DEFINE_SCRIPTFUNC( SetTextColor, "" ) + DEFINE_SCRIPTFUNC( SetTextPos, "" ) + DEFINE_SCRIPTFUNC( SetTextFont, "" ) + DEFINE_SCRIPTFUNC( DrawText, "" ) + DEFINE_SCRIPTFUNC( DrawUnicodeChar, "" ) + + DEFINE_SCRIPTFUNC( GetFont, "" ) + DEFINE_SCRIPTFUNC( GetTextWidth, "" ) + DEFINE_SCRIPTFUNC( GetFontTall, "" ) + DEFINE_SCRIPTFUNC( GetCharacterWidth, "" ) + + DEFINE_SCRIPTFUNC( CreateFont, SCRIPT_HIDE ) + DEFINE_SCRIPTFUNC( AddCustomFontFile, "" ) + + DEFINE_SCRIPTFUNC( GetTextureID, "" ) + DEFINE_SCRIPTFUNC( ValidateTexture, "" ) + DEFINE_SCRIPTFUNC( SetTextureFile, "" ) + DEFINE_SCRIPTFUNC( GetTextureWide, "" ) + DEFINE_SCRIPTFUNC( GetTextureTall, "" ) + DEFINE_SCRIPTFUNC( SetTexture, "" ) + + DEFINE_SCRIPTFUNC( DrawTexturedRect, "" ) + DEFINE_SCRIPTFUNC( DrawTexturedSubRect, "" ) + + DEFINE_SCRIPTFUNC( DrawTexturedBox, "" ) + DEFINE_SCRIPTFUNC( DrawColoredText, "" ) + DEFINE_SCRIPTFUNC( DrawColoredTextRect, "" ) + DEFINE_SCRIPTFUNC( DrawTexturedRectRotated, "" ) +END_SCRIPTDESC() + + +void CScriptSurface::PlaySound( const char* sound ) +{ + surface()->PlaySound(sound); +} + +void CScriptSurface::SetColor( int r, int g, int b, int a ) +{ + surface()->DrawSetColor( r, g, b, a ); +} + +void CScriptSurface::DrawFilledRect( int x0, int y0, int width, int height ) +{ + surface()->DrawFilledRect( x0, y0, x0 + width, y0 + height ); +} + +void CScriptSurface::DrawFilledRectFade( int x0, int y0, int width, int height, int a0, int a1, bool bHorz ) +{ + surface()->DrawFilledRectFade( x0, y0, x0 + width, y0 + height, a0, a1, bHorz ); +} + +void CScriptSurface::DrawOutlinedRect( int x0, int y0, int width, int height, int thickness ) +{ + int x1 = x0 + width; + int y1 = y0 + height - thickness; + y0 += thickness; + + surface()->DrawFilledRect( x0, y0 - thickness, x1, y0 ); // top + surface()->DrawFilledRect( x1 - thickness, y0, x1, y1 ); // right + surface()->DrawFilledRect( x0, y1, x1, y1 + thickness ); // bottom + surface()->DrawFilledRect( x0, y0, x0 + thickness, y1 ); // left +} + +void CScriptSurface::DrawLine( int x0, int y0, int x1, int y1 ) +{ + surface()->DrawLine( x0, y0, x1, y1 ); +} +#if 0 +void CScriptSurface::DrawPolyLine( HSCRIPT ax, HSCRIPT ay, int count ) +{ + if (count < 1) + return; + + if (count > 4096) + count = 4096; + + int *px = (int*)stackalloc( count * sizeof(int) ); + int *py = (int*)stackalloc( count * sizeof(int) ); + ScriptVariant_t vx, vy; + + int i = count; + while ( i-- ) + { + g_pScriptVM->GetValue( ax, i, &vx ); + g_pScriptVM->GetValue( ay, i, &vy ); + + px[i] = vx.m_int; + py[i] = vy.m_int; + } + + surface()->DrawPolyLine( px, py, count ); +} +#endif +void CScriptSurface::DrawOutlinedCircle( int x, int y, int radius, int segments ) +{ + surface()->DrawOutlinedCircle( x, y, radius, segments ); +} + +void CScriptSurface::SetTextColor( int r, int g, int b, int a ) +{ + surface()->DrawSetTextColor( r, g, b, a ); +} + +void CScriptSurface::SetTextPos( int x, int y ) +{ + surface()->DrawSetTextPos( x, y ); +} + +void CScriptSurface::SetTextFont( int font ) +{ + surface()->DrawSetTextFont( IntToFontHandle(font) ); +} + +void CScriptSurface::DrawText( const char *text, int drawType ) +{ + wchar_t wcs[512]; + g_pVGuiLocalize->ConvertANSIToUnicode( text, wcs, sizeof(wcs) ); + surface()->DrawPrintText( wcs, wcslen(wcs), (FontDrawType_t)drawType ); +} + +void CScriptSurface::DrawUnicodeChar( int ch, int drawType ) +{ + surface()->DrawUnicodeChar( (wchar_t)ch, (FontDrawType_t)drawType ); +} + +int CScriptSurface::GetFont( const char* name, bool proportional, const char* schema ) +{ + HFont font = INVALID_FONT; + + if ( !schema || !schema[0] ) + { + int idx = g_ScriptFonts.Find( GetFixedFontName( name, proportional ) ); + if ( idx != g_ScriptFonts.InvalidIndex() ) + { + font = g_ScriptFonts[idx].Head().font; + } + } + else + { + HScheme sch = scheme()->GetScheme( schema ); + font = scheme()->GetIScheme(sch)->GetFont( name, proportional ); + + // Update known count + if ( font > (unsigned int)g_nFontCount ) + g_nFontCount = font; + } + + return HandleToInt( font ); +} + +int CScriptSurface::GetTextWidth( int font, const char* text ) +{ + int w, t; + wchar_t wcs[512]; + g_pVGuiLocalize->ConvertANSIToUnicode( text, wcs, sizeof(wcs) ); + surface()->GetTextSize( IntToFontHandle(font), wcs, w, t ); + return w; +} + +int CScriptSurface::GetFontTall( int font ) +{ + return surface()->GetFontTall( IntToFontHandle(font) ); +} + +int CScriptSurface::GetCharacterWidth( int font, int ch ) +{ + return surface()->GetCharacterWidth( IntToFontHandle(font), ch ); +} + +void CScriptSurface::CreateFont( const char *customName, const char *windowsFontName, int tall, int weight, int blur, int scanlines, int flags, int yresMin, int yresMax, bool proportional ) +{ + // Make sure font invalidation callback is established. + // Not necessary if script fonts are reloaded in engine. + if ( !g_pScriptRootPanel ) + g_pScriptRootPanel = new CScriptRootPanel(); + + if ( flags & ISurface::FONTFLAG_BITMAP ) + { + AssertMsg( 0, "Bitmap fonts are not supported!" ); + return; + } + + if ( proportional && yresMin ) + { + AssertMsg( 0, "Resolution cannot be defined on a proportional font!" ); + return; + } + + if ( (yresMin < 0 || yresMax < 0) || (!!yresMin != !!yresMax) ) + { + AssertMsg( 0, "Invalid resolution!" ); + return; + } + +#if 0 + bool bProportionalFallbackFont = false; + if ( proportional ) + { + // Find if this is a resolution filtered font alias + const char *fontAlias = GetFixedFontName( customName, false ); + int idx = g_ScriptFonts.Find( fontAlias ); + if ( idx != g_ScriptFonts.InvalidIndex() ) + { + fontalias_t &alias = g_ScriptFonts[idx]; + for ( int i = 0; i < alias.Count(); ++i ) + { + FontData_t &data = alias.Element(i); + if ( data.yres_min && data.yres_max ) + { + bProportionalFallbackFont = true; + + // Save this proportional font in non-proportional alias + proportional = false; + break; + } + } + } + } +#endif + + const char *fontAlias = GetFixedFontName( customName, proportional ); + + int idx = g_ScriptFonts.Find( fontAlias ); + if ( idx != g_ScriptFonts.InvalidIndex() ) + { + fontalias_t &alias = g_ScriptFonts[idx]; + +#ifdef _DEBUG + if ( !yresMin && !yresMax /*&& !bProportionalFallbackFont*/ ) + { + // There must be only one font registered. + Assert( alias.Count() == 1 ); + + HFont font = alias.Head().font; + int oldTall = surface()->GetFontTallRequested( font ); + int newTall = proportional ? scheme()->GetProportionalScaledValue( tall ) : tall; + const char *oldName = surface()->GetFontName( font ); + + // Font changes will not be applied. + Assert( oldTall == newTall ); + if ( oldName ) // can be null + AssertMsg( !V_stricmp( oldName, windowsFontName ), "'%s' != '%s'", oldName, windowsFontName ); + } +#endif + + // if input resolutions match any of the existing fonts, + // then this must be a duplicate call. + for ( int i = 0; i < alias.Count(); ++i ) + { + FontData_t &data = alias.Element(i); + + if ( yresMin == data.yres_min && yresMax == data.yres_max ) + return; + } +#if 0 + if ( bProportionalFallbackFont ) + proportional = true; +#endif + DebugMsg( "Create font add '%s' [%d %d]\n", fontAlias, yresMin, yresMax ); + + FontData_t &newFont = alias.Element( alias.AddToTail() ); + newFont.font = alias.Head().font; + newFont.name = strdup( windowsFontName ); + newFont.tall = tall; + newFont.weight = weight; + newFont.blur = blur; + newFont.scanlines = scanlines; + newFont.flags = flags; + newFont.yres_min = yresMin; + newFont.yres_max = yresMax; + newFont.proportional = proportional; + +#if 0 + // Put the proportional font in the very end so that it is loaded only when no resolution is matched + struct L + { + static int __cdecl F( const FontData_t* a, const FontData_t* b ) + { + if ( !a->proportional && b->proportional ) + return -1; + if ( a->proportional && !b->proportional ) + return 1; + return 0; + } + }; + alias.Sort( L::F ); +#endif + + LoadFont( newFont DBG_PARAM(, fontAlias) ); + } + else + { + HFont font = surface()->CreateFont(); + + // Sanity check + Assert( font > (unsigned int)g_nFontCount && font < INT_MAX ); + + // Update known count + if ( font > (unsigned int)g_nFontCount ) + g_nFontCount = font; + + if ( yresMax && yresMin > yresMax ) + { + int t = yresMin; + yresMin = yresMax; + yresMax = t; + } + + if ( yresMin ) + DebugMsg( "Create font new '%s' [%d %d]\n", fontAlias, yresMin, yresMax ); + else + DebugMsg( "Create font new '%s'\n", fontAlias ); + + fontalias_t &alias = g_ScriptFonts.Element( g_ScriptFonts.Insert( fontAlias ) ); + FontData_t &newFont = alias.Element( alias.AddToTail() ); + newFont.font = font; + newFont.name = strdup( windowsFontName ); + newFont.tall = tall; + newFont.weight = weight; + newFont.blur = blur; + newFont.scanlines = scanlines; + newFont.flags = flags; + newFont.yres_min = yresMin; + newFont.yres_max = yresMax; + newFont.proportional = proportional; + + LoadFont( newFont DBG_PARAM(, fontAlias) ); + } +} + +bool CScriptSurface::AddCustomFontFile( const char *fontFileName ) +{ + return surface()->AddCustomFontFile( NULL, fontFileName ); +} + +int CScriptSurface::GetTextureID( char const *filename ) +{ + return surface()->DrawGetTextureId( filename ); +} + +// Create texture if it does not already exist +int CScriptSurface::ValidateTexture( const char *filename, bool hardwareFilter, bool forceReload, bool procedural ) +{ + int id = surface()->DrawGetTextureId( filename ); + if ( id <= 0 ) + { + id = surface()->CreateNewTextureID( procedural ); + g_ScriptTextureIDs.AddToTail( id ); + + surface()->DrawSetTextureFile( id, filename, hardwareFilter, forceReload ); + +#ifdef _DEBUG + char tex[MAX_PATH]; + surface()->DrawGetTextureFile( id, tex, sizeof(tex)-1 ); + if ( !V_stricmp( filename, tex ) ) + { + DebugMsg( "Create texture [%i]%s\n", id, filename ); + } + else + { + DebugWarning( "Create texture [%i]%s(%s)\n", id, tex, filename ); + } +#endif + } + else if ( forceReload && g_ScriptTextureIDs.HasElement( id ) ) + { + surface()->DrawSetTextureFile( id, filename, hardwareFilter, forceReload ); + } + else + { + surface()->DrawSetTexture( id ); + } + + return id; +} + +// Replace existing texture +void CScriptSurface::SetTextureFile( int id, const char *filename, bool hardwareFilter ) +{ + if ( g_ScriptTextureIDs.HasElement(id) ) + { + Assert( surface()->IsTextureIDValid(id) ); + surface()->DrawSetTextureFile( id, filename, hardwareFilter, true ); + +#ifdef _DEBUG + char tex[MAX_PATH]; + surface()->DrawGetTextureFile( id, tex, sizeof(tex)-1 ); + if ( !V_stricmp( filename, tex ) ) + { + DebugMsg( "Set texture [%i]%s\n", id, filename ); + } + else + { + DebugWarning( "Set texture [%i]%s(%s)\n", id, tex, filename ); + } +#endif + } + +#ifdef _DEBUG + if ( !g_ScriptTextureIDs.HasElement(id) && surface()->IsTextureIDValid(id) ) + { + DebugWarning( "Tried to set non-script created texture! [%i]%s\n", id, filename ); + } + + if ( !surface()->IsTextureIDValid(id) ) + { + DebugWarning( "Tried to set invalid texture id! [%i]%s\n", id, filename ); + } +#endif +} +#if 0 +void CScriptSurface::SetTextureMaterial( int id, HSCRIPT hMaterial ) +{ + IMaterial *pMaterial = (IMaterial*)HScriptToClass< IScriptMaterial >( hMaterial ); + if ( !IsValid( pMaterial ) ) + return; + + if ( g_ScriptTextureIDs.HasElement(id) ) + { + Assert( surface()->IsTextureIDValid(id) ); + MatSystemSurface()->DrawSetTextureMaterial( id, pMaterial ); + + DebugMsg( "Set texture [%i]%s\n", id, pMaterial->GetName() ); + } + +#ifdef _DEBUG + if ( !g_ScriptTextureIDs.HasElement(id) && surface()->IsTextureIDValid(id) ) + { + DebugWarning( "Tried to set non-script created texture! [%i]\n", id ); + } + + if ( !surface()->IsTextureIDValid(id) ) + { + DebugWarning( "Tried to set invalid texture id! [%i]\n", id ); + } +#endif +} +#endif +int CScriptSurface::GetTextureWide( int id ) +{ + int w, t; + surface()->DrawGetTextureSize( id, w, t ); + return w; +} + +int CScriptSurface::GetTextureTall( int id ) +{ + int w, t; + surface()->DrawGetTextureSize( id, w, t ); + return t; +} + +void CScriptSurface::SetTexture( int id ) +{ + surface()->DrawSetTexture( id ); +} + +void CScriptSurface::DrawTexturedRect( int x0, int y0, int width, int height ) +{ + surface()->DrawTexturedRect( x0, y0, x0 + width, y0 + height ); +} + +void CScriptSurface::DrawTexturedSubRect( int x0, int y0, int x1, int y1, float texs0, float text0, float texs1, float text1 ) +{ + surface()->DrawTexturedSubRect( x0, y0, x1, y1, texs0, text0, texs1, text1 ); +} + +void CScriptSurface::DrawTexturedRectRotated( int x, int y, int w, int t, float yaw ) +{ + Vertex_t verts[4]; + Vector2D axis[2]; + + float sy, cy; + SinCos( DEG2RAD( -yaw ), &sy, &cy ); + + axis[0].x = cy; + axis[0].y = sy; + axis[1].x = -axis[0].y; + axis[1].y = axis[0].x; + + verts[0].m_TexCoord.Init( 0, 0 ); + Vector2DMA( Vector2D( x + w * 0.5f, y + t * 0.5f ), w * -0.5f, axis[0], verts[0].m_Position ); + Vector2DMA( verts[0].m_Position, t * -0.5f, axis[1], verts[0].m_Position ); + + verts[1].m_TexCoord.Init( 1, 0 ); + Vector2DMA( verts[0].m_Position, w, axis[0], verts[1].m_Position ); + + verts[2].m_TexCoord.Init( 1, 1 ); + Vector2DMA( verts[1].m_Position, t, axis[1], verts[2].m_Position ); + + verts[3].m_TexCoord.Init( 0, 1 ); + Vector2DMA( verts[0].m_Position, t, axis[1], verts[3].m_Position ); + + surface()->DrawTexturedPolygon( 4, verts ); +} + +void CScriptSurface::DrawTexturedBox( int texture, int x, int y, int wide, int tall, int r, int g, int b, int a ) +{ + surface()->DrawSetColor( r, g, b, a ); + surface()->DrawSetTexture( texture ); + surface()->DrawTexturedRect( x, y, x + wide, y + tall ); +} + +void CScriptSurface::DrawColoredText( int font, int x, int y, int r, int g, int b, int a, const char *text ) +{ + wchar_t wcs[512]; + g_pVGuiLocalize->ConvertANSIToUnicode( text, wcs, sizeof(wcs) ); + + surface()->DrawSetTextFont( IntToFontHandle(font) ); + surface()->DrawSetTextColor( r, g, b, a ); + surface()->DrawSetTextPos( x, y ); + surface()->DrawPrintText( wcs, wcslen(wcs) ); +} + +void CScriptSurface::DrawColoredTextRect( int font, int x, int y, int w, int h, int r, int g, int b, int a, const char *text ) +{ + MatSystemSurface()->DrawColoredTextRect( IntToFontHandle(font), x, y, w, h, r, g, b, a, text ); +} + + +//============================================================== +//============================================================== + +#define __base() this->_base + +#define BEGIN_SCRIPTDESC_VGUI( panelClass )\ + BEGIN_SCRIPTDESC_NAMED( CScript_##panelClass##_Helper, IScriptVGUIObject, #panelClass, "" )\ + DEFINE_VGUI_SCRIPTFUNC_##panelClass() + +// +// Script helpers are wrappers that only redirect to VGUI panels (such as CScript_Panel : Panel), +// these macros help to simplify definitions. +// + +// +// BEGIN_VGUI_HELPER() assumes the VGUI panel class has the prefix 'CScript_' +// Use BEGIN_VGUI_HELPER_EX() to manually define VGUI panel class name. +// +#define BEGIN_VGUI_HELPER( panelClass )\ + BEGIN_VGUI_HELPER_EX( panelClass, CScript_##panelClass ) + +#define BEGIN_VGUI_HELPER_DEFAULT_TEXT( panelClass )\ + BEGIN_VGUI_HELPER_DEFAULT_TEXT_EX( panelClass, CScript_##panelClass ) + +#define BEGIN_VGUI_HELPER_EX( panelClass, baseClass )\ + class CScript_##panelClass##_Helper : public IScript_##panelClass< baseClass >\ + {\ + void Create( const char *panelName ) override\ + {\ + Assert( !_base && !_vpanel );\ + _base = new baseClass( NULL, panelName );\ + }\ +\ + public: + +#define BEGIN_VGUI_HELPER_DEFAULT_TEXT_EX( panelClass, baseClass )\ + class CScript_##panelClass##_Helper : public IScript_##panelClass< baseClass >\ + {\ + void Create( const char *panelName ) override\ + {\ + Assert( !_base && !_vpanel );\ + _base = new baseClass( NULL, panelName, (const char*)NULL );\ + }\ +\ + public: +#define END_VGUI_HELPER()\ + }; + + +#define CLASS_HELPER_INTERFACE_ROOT( panelClass )\ + template \ + class IScript_##panelClass : public CScriptVGUIObject + +#define CLASS_HELPER_INTERFACE( panelClass, baseClass )\ + template \ + class IScript_##panelClass : public IScript_##baseClass + + +#ifdef _DEBUG +#define DEBUG_DESTRUCTOR( panelClass, baseClass )\ + panelClass()\ + {\ + DebugDestructor( baseClass )\ + } + +#define DebugDestructor( panelClass )\ + {\ + DebugDevMsg( " ~" #panelClass "() '%s'\n", GetName() );\ + } +#else +#define DEBUG_DESTRUCTOR( panelClass, baseClass ) +#define DebugDestructor( panelClass ) +#endif + +#define DECLARE_SCRIPTVGUI_CLASS( baseClass )\ + DECLARE_SCRIPTVGUI_CLASS_EX( CScript_##baseClass, baseClass )\ + DEBUG_DESTRUCTOR( ~CScript_##baseClass, baseClass ) + +#define DECLARE_SCRIPTVGUI_CLASS_EX( panelClass, baseClass )\ + typedef baseClass BaseClass;\ + typedef panelClass ThisClass;\ +public:\ + void OnDelete()\ + {\ + DebugMsg( #baseClass "::OnDelete() '%s'\n", GetName() );\ + int i;\ + IScriptVGUIObject *obj = FindInScriptPanels( GetVPanel(), i );\ + if ( obj )\ + {\ + obj->Destroy( i );\ + }\ + BaseClass::OnDelete();\ + } + +// +// Definitions for 'empty' vgui objects that do not have any script specific implementation - overrides or callbacks. +// These are required to shutdown script objects on panel death +// (on save restore where panel destructor is called after the VM is restarted while HSCRIPT members are invalid but not nullified, +// and on C++ deletion where IScriptVGUIObject::Destroy() is not automatically called). +// +#define DEFINE_VGUI_CLASS_EMPTY( panelClass )\ + class CScript_##panelClass : public panelClass\ + {\ + DECLARE_SCRIPTVGUI_CLASS( panelClass )\ + void ScriptShutdown() {}\ +\ + public:\ + CScript_##panelClass( Panel *parent, const char *name )\ + : BaseClass( parent, name )\ + {}\ + };\ +\ + BEGIN_VGUI_HELPER( panelClass )\ + END_VGUI_HELPER()\ +\ + BEGIN_SCRIPTDESC_VGUI( panelClass )\ + END_SCRIPTDESC() + +#define DEFINE_VGUI_CLASS_EMPTY_DEFAULT_TEXT( panelClass )\ + class CScript_##panelClass : public panelClass\ + {\ + DECLARE_SCRIPTVGUI_CLASS( panelClass )\ + void ScriptShutdown() {}\ +\ + public:\ + CScript_##panelClass( Panel *parent, const char *name, const char *text )\ + : BaseClass( parent, name, text )\ + {}\ + };\ +\ + BEGIN_VGUI_HELPER_DEFAULT_TEXT( panelClass )\ + END_VGUI_HELPER()\ +\ + BEGIN_SCRIPTDESC_VGUI( panelClass )\ + END_SCRIPTDESC() + +class IScriptVGUIObject +{ +public: + virtual ~IScriptVGUIObject() {} + +#ifdef _DEBUG + virtual const char *GetName() = 0; +#endif + //----------------------------------------------------- + // Free the VGUI panel and script instance. + //----------------------------------------------------- + virtual void Destroy( int ) = 0; + + //----------------------------------------------------- + // Create new panel + //----------------------------------------------------- + virtual void Create( const char *panelName ) = 0; + +public: + VPANEL GetVPanel() { return _vpanel; } + HSCRIPT GetScriptInstance() { return m_hScriptInstance; } + +protected: + VPANEL _vpanel; + HSCRIPT m_hScriptInstance; + + // Called on deletion + static void ResolveChildren_r( VPANEL panel DBG_PARAM(, int level) ); + +public: +#if SCRIPT_VGUI_SAVERESTORE + IScriptVGUIObject() {} + void SetScriptInstance( HSCRIPT h ) { m_hScriptInstance = h; } + char m_pszScriptId[16]; +#endif + +#ifdef _DEBUG + #if SCRIPT_VGUI_SAVERESTORE + const char *GetDebugName() { return m_pszScriptId; } + #else + const char *GetDebugName() { return ""; } + #endif +#endif +}; + +BEGIN_SCRIPTDESC_ROOT( IScriptVGUIObject, SCRIPT_HIDE ) +END_SCRIPTDESC() + + +#if SCRIPT_VGUI_SAVERESTORE +class CScriptVGUIScriptInstanceHelper : public IScriptInstanceHelper +{ + void *BindOnRead( HSCRIPT hInstance, void *pOld, const char *pszId ) + { + DebugMsg( "BindOnRead (0x%p) (%s) (count %d)\n", (uint)hInstance, pszId, g_ScriptPanels.Count() ); + + FOR_EACH_LL( g_ScriptPanels, i ) + { + IScriptVGUIObject *pPanel = g_ScriptPanels[i]; + // DebugMsg( " cmp (%s)\n", pPanel->m_pszScriptId ); + if ( !V_stricmp( pPanel->m_pszScriptId, pszId ) ) + { + pPanel->SetScriptInstance( hInstance ); + DebugMsg( " ret (%s)\n", pPanel->m_pszScriptId ); + return pPanel; + } + } + DebugMsg( " ret (null)\n" ); + return NULL; + } +}; + +static CScriptVGUIScriptInstanceHelper g_ScriptVGUIScriptInstanceHelper; + +#define DEFINE_VGUI_SCRIPT_INSTANCE_HELPER() DEFINE_SCRIPT_INSTANCE_HELPER( &g_ScriptVGUIScriptInstanceHelper ) +#else +#define DEFINE_VGUI_SCRIPT_INSTANCE_HELPER() +#endif + + +IScriptVGUIObject *ToScriptVGUIObj( HSCRIPT inst ) +{ + return (IScriptVGUIObject *)g_pScriptVM->GetInstanceValue( inst, ::GetScriptDesc( (IScriptVGUIObject *)0 ) ); +} + +template < typename T > inline T* AllocScriptPanel() +{ + return new T; +} + +inline IScriptVGUIObject *FindInScriptPanels( VPANEL panel, int &I ) +{ + for ( int i = g_ScriptPanels.Head(); i != g_ScriptPanels.InvalidIndex(); i = g_ScriptPanels.Next(i) ) + { + IScriptVGUIObject *obj = g_ScriptPanels[i]; + if ( obj->GetVPanel() == panel ) + { + I = i; + return obj; + } + } + return NULL; +} + +void IScriptVGUIObject::ResolveChildren_r( VPANEL panel DBG_PARAM(, int level = 0) ) +{ +#ifdef _DEBUG + char indent[32]; + + int l = level, c = 0; + if ( l > 15 ) + l = 15; + + while ( l-- ) + { + indent[c++] = ' '; + indent[c++] = ' '; + } + indent[c] = 0; + + if ( level > 15 ) + { + indent[c-1] = '.'; + indent[c-2] = '.'; + } +#endif + + CUtlVector< VPANEL > &children = ipanel()->GetChildren( panel ); + FOR_EACH_VEC_BACK( children, i ) + { + VPANEL child = children[i]; + int j; + IScriptVGUIObject *obj = FindInScriptPanels( child, j ); + if ( obj ) + { + if ( ipanel()->IsAutoDeleteSet(child) ) + { + DebugMsg( " %sResolveChildren: '%s' (autodelete)\n", indent, obj->GetName() ); + + if ( g_pScriptVM ) + g_pScriptVM->RemoveInstance( obj->m_hScriptInstance ); + g_ScriptPanels.Remove( j ); + delete obj; + + ResolveChildren_r( child DBG_PARAM(, level+1) ); + } + else + { + DebugMsg( " %sResolveChildren: '%s'\n", indent, obj->GetName() ); + + // Panel::SetAutoDelete should not be added until + // what to do on their parent death is finalised. + // + // This assert will be hit if a deleted panel has + // C++ created and autodelete disabled children who are + // also registered to script. + Assert(0); + } + } + } +} + +template +class CScriptVGUIObject : public IScriptVGUIObject +{ +public: + T *_base; + + CScriptVGUIObject() : _base(0) + { + _vpanel = 0; + m_hScriptInstance = 0; + } + + void Destroy( int i = -1 ) + { + if ( i != -1 ) + { + Assert( g_ScriptPanels.IsValidIndex(i) ); + Assert( g_ScriptPanels[i] == this ); + + g_ScriptPanels.Remove( i ); + } + else + { + Assert( g_ScriptPanels.Find( this ) != g_ScriptPanels.InvalidIndex() ); + + g_ScriptPanels.FindAndRemove( this ); + } + + if ( GetVPanel() ) + { + DebugMsg( " Destroy panel '%s' %s\n", _base->GetName(), GetDebugName() ); + _base->ScriptShutdown(); + ResolveChildren_r( _vpanel ); + _base->MarkForDeletion(); + } + + if ( m_hScriptInstance ) + { + if ( g_pScriptVM ) + g_pScriptVM->RemoveInstance( m_hScriptInstance ); + } + + delete this; + } + + template + void CreateFromScript( HSCRIPT parent, const char *panelName, int root ) + { + Assert( !_vpanel && !m_hScriptInstance && !g_ScriptPanels.IsValidIndex( g_ScriptPanels.Find( this ) ) ); + + Create( panelName && *panelName ? panelName : NULL ); + _vpanel = _base->GetVPanel(); + m_hScriptInstance = g_pScriptVM->RegisterInstance< CHelper >( static_cast< CHelper* >( this ) ); + +#if SCRIPT_VGUI_SAVERESTORE + g_pScriptVM->GenerateUniqueKey( "", m_pszScriptId, sizeof(m_pszScriptId) ); + g_pScriptVM->SetInstanceUniqeId( m_hScriptInstance, m_pszScriptId ); +#endif + + if ( parent ) + { + IScriptVGUIObject *obj = ToScriptVGUIObj( parent ); + if ( obj ) + { + // Insert this after the parent to make sure children come after their parents, + // and their removal is done inside ResolveChildren_r(), not by individual Destroy() calls from LevelShutdown. + unsigned short parentIdx = g_ScriptPanels.Find( obj ); + + // My parent can't not be in the list. + Assert( parentIdx != g_ScriptPanels.InvalidIndex() && g_ScriptPanels.IsInList( parentIdx ) ); + + g_ScriptPanels.InsertAfter( parentIdx, this ); + + _base->SetParent( obj->GetVPanel() ); + return; + } + + AssertMsg( 0, "invalid parent" ); + + g_ScriptPanels.AddToTail( this ); + + // leave me parentless + return; + } + + g_ScriptPanels.AddToTail( this ); + + // Script specified root panel - a cheap alternative to registering uneditable panel instances. + // Match the values to vscript_vgui.nut. + // + // This parameter is hidden in script, and is defined by the return value of dummy functions. + VPANEL vparent = 0; + + switch ( root ) + { + #if ALLOW_ROOT_PANEL_PARENT + case 0: + vparent = VGUI_GetScriptRootPanel( PANEL_ROOT ); + break; + #endif + #if ALLOW_GAMEUI_ROOT_PARENT + case 1: + vparent = VGUI_GetScriptRootPanel( PANEL_GAMEUIDLL ); + break; + #endif + #if ALLOW_CLIENTDLL_ROOT_PARENT + case 2: + vparent = VGUI_GetScriptRootPanel( PANEL_CLIENTDLL ); + break; + #endif + #if ALLOW_HUD_VIEWPORT_ROOT_PARENT + case 10: // Hud viewport + Assert( g_pClientMode && g_pClientMode->GetViewport() ); + vparent = g_pClientMode->GetViewport()->GetVPanel(); + break; + #endif + default: + #if SCRIPT_ENGINE_ROOT_PANELS + UNREACHABLE(); // Invalid parent panel + #else + // Allow everything defined in vscript_vgui.nut + vparent = VGUI_GetScriptRootPanel( (VGuiPanel_t)root ); + #endif + } + + _base->SetParent( vparent ); + } +}; + +//-------------------------------------------------------------- +//-------------------------------------------------------------- + +CLASS_HELPER_INTERFACE_ROOT( Panel ) +{ +public: + void Destroy() + { + CScriptVGUIObject::Destroy(); + } + + void MakeReadyForUse() + { + __base()->MakeReadyForUse(); + } + + const char *GetName() + { + return __base()->GetName(); + } + + void AddTickSignal( int i ) + { + ivgui()->AddTickSignal( this->GetVPanel(), i ); + } + + void RemoveTickSignal() + { + ivgui()->RemoveTickSignal( this->GetVPanel() ); + } +#if SCRIPT_VGUI_SIGNAL_INTERFACE + void AddActionSignalTarget( HSCRIPT messageTarget ) + { + IScriptVGUIObject *obj = ToScriptVGUIObj( messageTarget ); + if ( obj ) + { + __base()->AddActionSignalTarget( obj->GetVPanel() ); + } + } +#endif + //----------------------------------------------------- + // Get script created parent + //----------------------------------------------------- + HSCRIPT GetParent() + { + VPANEL parent = ipanel()->GetParent( this->GetVPanel() ); + if ( !parent ) + return NULL; + + int i; + IScriptVGUIObject* obj = FindInScriptPanels( parent, i ); + if ( obj ) + { + // My parent can't be invalid. + Assert( ToScriptVGUIObj( obj->GetScriptInstance() ) ); + + return obj->GetScriptInstance(); + } + +#ifdef _DEBUG + // Is my parent one of the root panels? + bool bRootParent = false; +#if SCRIPT_ENGINE_ROOT_PANELS + if ( ( parent == g_pScriptRootPanel->GetVPanel() ) + #if ALLOW_GAMEUI_ROOT_PARENT + || ( g_pScriptGameUIDLLPanel && parent == g_pScriptGameUIDLLPanel->GetVPanel() ) + #endif + #if ALLOW_CLIENTDLL_ROOT_PARENT + || ( g_pScriptClientDLLPanel && parent == g_pScriptClientDLLPanel->GetVPanel() ) + #endif + ) + { + bRootParent = true; + } + else +#endif + for ( int i = PANEL_ROOT; i <= PANEL_CLIENTDLL_TOOLS; ++i ) + { + if ( parent == enginevgui->GetPanel( (VGuiPanel_t)i ) ) + { + bRootParent = true; + break; + } + } +#if ALLOW_HUD_VIEWPORT_ROOT_PARENT + if ( g_pClientMode && g_pClientMode->GetViewport() && ( parent == g_pClientMode->GetViewport()->GetVPanel() ) ) + bRootParent = true; +#endif + // My parent wasn't registered. + AssertMsg1( bRootParent, "'%s'", ipanel()->GetName(parent) ); +#endif + + return NULL; + } + + //----------------------------------------------------- + // Set script created parent + //----------------------------------------------------- + void SetParent( HSCRIPT parent ) + { + if ( !parent ) + { + __base()->SetParent( (VPANEL)NULL ); + return; + } + + IScriptVGUIObject *obj = ToScriptVGUIObj( parent ); + if ( obj ) + { + __base()->SetParent( obj->GetVPanel() ); + return; + } + + AssertMsg( 0, "invalid parent" ); + } + + void GetChildren( HSCRIPT arr ) + { + CUtlVector< VPANEL > &children = ipanel()->GetChildren( this->GetVPanel() ); + FOR_EACH_VEC( children, i ) + { + VPANEL child = children[i]; + int j; + IScriptVGUIObject* obj = FindInScriptPanels( child, j ); + if ( obj ) + { + g_pScriptVM->ArrayAppend( arr, obj->GetScriptInstance() ); + } + // Beware of dangling pointers if C++ created children are to be registered + } + } + + int GetXPos() + { + int x, y; + ipanel()->GetPos( this->GetVPanel(), x, y ); + return x; + } + + int GetYPos() + { + int x, y; + ipanel()->GetPos( this->GetVPanel(), x, y ); + return y; + } + + void SetPos( int x, int y ) + { + ipanel()->SetPos( this->GetVPanel(), x, y ); + } + + void SetZPos( int i ) + { + ipanel()->SetZPos( this->GetVPanel(), i ); + } + + int GetZPos() + { + return ipanel()->GetZPos( this->GetVPanel() ); + } + + void SetSize( int w, int t ) + { + ipanel()->SetSize( this->GetVPanel(), w, t ); + } + + void SetWide( int w ) + { + ipanel()->SetSize( this->GetVPanel(), w, GetTall() ); + } + + int GetWide() + { + int w, t; + ipanel()->GetSize( this->GetVPanel(), w, t ); + return w; + } + + void SetTall( int t ) + { + ipanel()->SetSize( this->GetVPanel(), GetWide(), t ); + } + + int GetTall() + { + int w, t; + ipanel()->GetSize( this->GetVPanel(), w, t ); + return t; + } + + int GetAlpha() + { + return __base()->GetAlpha(); + } + + void SetAlpha( int i ) + { + __base()->SetAlpha( i ); + } + + void SetVisible( bool i ) + { + ipanel()->SetVisible( this->GetVPanel(), i ); + } + + bool IsVisible() + { + return ipanel()->IsVisible( this->GetVPanel() ); + } +#if BUILD_GROUPS_ENABLED + void SetProportional( bool i ) + { + __base()->SetProportional(i); + } +#endif +#if 0 + void LocalToScreen( HSCRIPT out ) + { + int px, py; + ipanel()->GetAbsPos( this->GetVPanel(), px, py ); + + ScriptVariant_t x, y; + g_pScriptVM->GetValue( out, (ScriptVariant_t)0, &x ); + g_pScriptVM->GetValue( out, 1, &y ); + + g_pScriptVM->SetValue( out, (ScriptVariant_t)0, x.m_int + px ); + g_pScriptVM->SetValue( out, 1, y.m_int + py ); + } + + void ScreenToLocal( HSCRIPT out ) + { + int px, py; + ipanel()->GetAbsPos( this->GetVPanel(), px, py ); + + ScriptVariant_t x, y; + g_pScriptVM->GetValue( out, (ScriptVariant_t)0, &x ); + g_pScriptVM->GetValue( out, 1, &y ); + + g_pScriptVM->SetValue( out, (ScriptVariant_t)0, x.m_int - px ); + g_pScriptVM->SetValue( out, 1, y.m_int - py ); + } +#endif + bool IsWithin( int x, int y ) + { + return __base()->IsWithin( x, y ); + } + + void SetEnabled( bool i ) + { + __base()->SetEnabled(i); + } + + bool IsEnabled() + { + return __base()->IsEnabled(); + } + + void SetPaintEnabled( bool i ) + { + __base()->SetPaintEnabled(i); + } + + void SetPaintBackgroundEnabled( bool i ) + { + __base()->SetPaintBackgroundEnabled(i); + } + + void SetPaintBorderEnabled( bool i ) + { + __base()->SetPaintBorderEnabled(i); + } + + void SetPostChildPaintEnabled( bool i ) + { + __base()->SetPostChildPaintEnabled(i); + } + + // 0 for normal(opaque), 1 for single texture from Texture1, and 2 for rounded box w/ four corner textures + void SetPaintBackgroundType( int i ) + { + __base()->SetPaintBackgroundType(i); + } + + void SetFgColor( int r, int g, int b, int a ) + { + __base()->SetFgColor( Color( r, g, b, a ) ); + } + + void SetBgColor( int r, int g, int b, int a ) + { + __base()->SetBgColor( Color( r, g, b, a ) ); + } +#if 0 + void SetScheme( const char *tag ) + { + return __base()->SetScheme( tag ); + } +#endif + void SetCursor( int cursor ) + { + AssertMsg( cursor >= 0 && cursor < dc_last, "invalid cursor" ); + + // do nothing + if ( cursor < 0 || cursor >= dc_last ) + return; + + return __base()->SetCursor( (HCursor)cursor ); + } + + bool IsCursorOver() + { + return __base()->IsCursorOver(); + } + + bool HasFocus() + { + return __base()->HasFocus(); + } + + void RequestFocus() + { + __base()->RequestFocus(); + } + + void MakePopup() + { + __base()->MakePopup(); + } + + void MoveToFront() + { + __base()->MoveToFront(); + } + + void SetMouseInputEnabled( bool i ) + { + __base()->SetMouseInputEnabled(i); + } + + void SetKeyBoardInputEnabled( bool i ) + { + __base()->SetKeyBoardInputEnabled(i); + } + + // ----------------------- + // Drawing utility + // ----------------------- + //void SetRoundedCorners( int cornerFlags ) + //{ + // __base()->SetRoundedCorners( cornerFlags & 0xff ); + //} + + void DrawBox( int x, int y, int wide, int tall, int r, int g, int b, int a, bool hollow ) + { + __base()->DrawBox( x, y, wide, tall, Color(r, g, b, a), 1.0f, hollow ); + } + + void DrawBoxFade( int x, int y, int wide, int tall, int r, int g, int b, int a, int alpha0, int alpha1, bool bHorizontal, bool hollow ) + { + __base()->DrawBoxFade( x, y, wide, tall, Color(r, g, b, a), 1.0f, alpha0, alpha1, bHorizontal, hollow ); + } +#if 0 + // ----------------------- + // drag drop + // ----------------------- + void SetDragEnabled( bool i ) + { + __base()->SetDragEnabled(i); + } + + bool IsDragEnabled() + { + return __base()->IsDragEnabled(); + } + + void SetDropEnabled( bool i ) + { + __base()->SetDropEnabled( i, 0.0f ); + } + + bool IsDropEnabled() + { + return __base()->IsDropEnabled(); + } + + void SetShowDragHelper( int i ) + { + __base()->SetShowDragHelper(i); + } + + int GetDragStartTolerance() + { + return __base()->GetDragStartTolerance(); + } + + void SetDragStartTolerance( int i ) + { + __base()->SetDragSTartTolerance(i); + } +#endif +#if 0 + void SetTooltip( const char *text ) + { + __base()->GetTooltip()->SetText( text ); + } + + void SetTooltipDelay( int delay ) + { + __base()->GetTooltip()->SetTooltipDelay( delay ); + } +#endif +}; + +#define DEFINE_VGUI_SCRIPTFUNC_Panel()\ + DEFINE_VGUI_SCRIPT_INSTANCE_HELPER()\ +\ + DEFINE_SCRIPTFUNC( Destroy, "" )\ + DEFINE_SCRIPTFUNC( MakeReadyForUse, "" )\ + DEFINE_SCRIPTFUNC( GetName, "" )\ + DEFINE_SCRIPTFUNC( AddTickSignal, "" )\ + DEFINE_SCRIPTFUNC( RemoveTickSignal, "" )\ +\ + DEFINE_SCRIPTFUNC( GetParent, "" )\ + DEFINE_SCRIPTFUNC( SetParent, "" )\ + DEFINE_SCRIPTFUNC( GetChildren, "" )\ +\ + DEFINE_SCRIPTFUNC( GetXPos, "" )\ + DEFINE_SCRIPTFUNC( GetYPos, "" )\ + DEFINE_SCRIPTFUNC( SetPos, "" )\ +\ + DEFINE_SCRIPTFUNC( GetZPos, "" )\ + DEFINE_SCRIPTFUNC( SetZPos, "" )\ +\ + DEFINE_SCRIPTFUNC( SetSize, "" )\ + DEFINE_SCRIPTFUNC( GetWide, "" )\ + DEFINE_SCRIPTFUNC( SetWide, "" )\ +\ + DEFINE_SCRIPTFUNC( GetTall, "" )\ + DEFINE_SCRIPTFUNC( SetTall, "" )\ +\ + DEFINE_SCRIPTFUNC( GetAlpha, "" )\ + DEFINE_SCRIPTFUNC( SetAlpha, "" )\ +\ + DEFINE_SCRIPTFUNC( SetVisible, "" )\ + DEFINE_SCRIPTFUNC( IsVisible, "" )\ +\ + DEFINE_SCRIPTFUNC( IsWithin, "" )\ +\ + DEFINE_SCRIPTFUNC( SetEnabled, "" )\ + DEFINE_SCRIPTFUNC( IsEnabled, "" )\ +\ + DEFINE_SCRIPTFUNC( SetPaintEnabled, "" )\ + DEFINE_SCRIPTFUNC( SetPaintBackgroundEnabled, "" )\ + DEFINE_SCRIPTFUNC( SetPaintBorderEnabled, "" )\ + DEFINE_SCRIPTFUNC( SetPostChildPaintEnabled, "" )\ + DEFINE_SCRIPTFUNC( SetPaintBackgroundType, "" )\ +\ + DEFINE_SCRIPTFUNC( SetFgColor, "" )\ + DEFINE_SCRIPTFUNC( SetBgColor, "" )\ +\ + DEFINE_SCRIPTFUNC( SetCursor, "" )\ + DEFINE_SCRIPTFUNC( IsCursorOver, "" )\ +\ + DEFINE_SCRIPTFUNC( HasFocus, "" )\ + DEFINE_SCRIPTFUNC( RequestFocus, "" )\ + DEFINE_SCRIPTFUNC( MakePopup, "" )\ + DEFINE_SCRIPTFUNC( MoveToFront, "" )\ +\ + DEFINE_SCRIPTFUNC( SetMouseInputEnabled, "" )\ + DEFINE_SCRIPTFUNC( SetKeyBoardInputEnabled, "" )\ +\ + DEFINE_SCRIPTFUNC( DrawBox, "" )\ + DEFINE_SCRIPTFUNC( DrawBoxFade, "" )\ + +//-------------------------------------------------------------- +//-------------------------------------------------------------- +// These need more testing. +// TODO: DECLARE_BUILD_FACTORY_SCRIPT() to create overridable script panels from controls file +#if BUILD_GROUPS_ENABLED +CLASS_HELPER_INTERFACE( EditablePanel, Panel ) +{ +public: + // Call on creation or on ApplySchemeSettings() + void LoadControlSettings( const char *resName ) + { + __base()->LoadControlSettings( resName ); + } + + HSCRIPT FindChildByName( const char *childName ) + { + Panel *pPanel = __base()->FindChildByName( childName, false ); + if ( pPanel ) + { + int i; + IScriptVGUIObject* obj = FindInScriptPanels( child, i ); + if ( obj ) + { + return obj->GetScriptInstance(); + } + } + return NULL; + } +}; + +#define DEFINE_VGUI_SCRIPTFUNC_EditablePanel()\ + DEFINE_VGUI_SCRIPTFUNC_Panel()\ + DEFINE_SCRIPTFUNC( LoadControlSettings, "" )\ + DEFINE_SCRIPTFUNC( FindChildByName, "" ) +#endif +//-------------------------------------------------------------- +//-------------------------------------------------------------- + +CLASS_HELPER_INTERFACE( Label, Panel ) +{ +public: + void SetText( const char *text ) + { + wchar_t wcs[512]; + g_pVGuiLocalize->ConvertANSIToUnicode( text, wcs, sizeof(wcs) ); + __base()->SetText( wcs ); + } + + void SetFont( int i ) + { + __base()->SetFont( IntToFontHandle(i) ); + } + + void SetAllCaps( bool i ) + { + __base()->SetAllCaps(i); + } + + void SetWrap( bool i ) + { + __base()->SetWrap(i); + } + + void SetCenterWrap( bool i ) + { + __base()->SetCenterWrap(i); + } + + void SetContentAlignment( int i ) + { + __base()->SetContentAlignment( (Label::Alignment)i ); + } + + void SetTextInset( int x, int y ) + { + __base()->SetTextInset( x, y ); + } + + void SizeToContents() + { + __base()->SizeToContents(); + } + + void SetAssociatedControl( HSCRIPT control ) + { + IScriptVGUIObject *obj = ToScriptVGUIObj( control ); + if ( obj ) + { + __base()->SetAssociatedControl( ipanel()->GetPanel( obj->GetVPanel(), GetControlsModuleName() ) ); + } + } + + void AddColorChange( int r, int g, int b, int a, int iTextStreamIndex ) + { + __base()->GetTextImage()->AddColorChange( Color( r, g, b, a ), iTextStreamIndex ); + } + + void ClearColorChangeStream() + { + __base()->GetTextImage()->ClearColorChangeStream(); + } +#if 0 + void SetTextImageIndex( int index ) + { + __base()->SetTextImageIndex( index ); + } + + void SetImageAtIndex( int index, const char *imageName, bool hardwareFilter, int offset ) + { + return __base()->SetImageAtIndex( index, vgui_GetImage( imageName, hardwareFilter ), offset ); + } + + int AddImage( const char *imageName, bool hardwareFilter, int offset ) + { + return __base()->AddImage( vgui_GetImage( imageName, hardwareFilter ), offset ); + } +#endif +}; + +#define DEFINE_VGUI_SCRIPTFUNC_Label()\ + DEFINE_VGUI_SCRIPTFUNC_Panel()\ + DEFINE_SCRIPTFUNC( SetText, "" )\ + DEFINE_SCRIPTFUNC( SetFont, "" )\ + DEFINE_SCRIPTFUNC( SetAllCaps, "" )\ + DEFINE_SCRIPTFUNC( SetWrap, "" )\ + DEFINE_SCRIPTFUNC( SetCenterWrap, "" )\ + DEFINE_SCRIPTFUNC( SetContentAlignment, "" )\ + DEFINE_SCRIPTFUNC( SetTextInset, "" )\ + DEFINE_SCRIPTFUNC( SizeToContents, "" )\ + DEFINE_SCRIPTFUNC( SetAssociatedControl, "" )\ + DEFINE_SCRIPTFUNC( AddColorChange, "" )\ + DEFINE_SCRIPTFUNC( ClearColorChangeStream, "" )\ + +//-------------------------------------------------------------- +//-------------------------------------------------------------- + +CLASS_HELPER_INTERFACE( Button, Label ) +{ +public: +#if SCRIPT_VGUI_SIGNAL_INTERFACE + // Sets the command message to send to the action signal target when the button is pressed + void SetCommand( const char *command ) + { + if ( !V_strnicmp( command, "url ", 4 ) ) + { + __base()->SetCommand( (KeyValues*)NULL ); + + g_pScriptVM->RaiseException("invalid button command"); + return; + } + + __base()->SetCommand( command ); + } +#endif + void SetButtonActivationType( int activationType ) + { + __base()->SetButtonActivationType( (Button::ActivationType_t)activationType ); + } + + bool IsArmed() + { + return __base()->IsArmed(); + } + + void SetArmed( bool state ) + { + __base()->SetArmed(state); + } + + bool IsSelected() + { + return __base()->IsSelected(); + } + + void SetSelected( bool state ) + { + __base()->SetSelected(state); + } + + bool IsDepressed() + { + return __base()->IsDepressed(); + } + + void ForceDepressed( bool state ) + { + __base()->ForceDepressed(state); + } + + void SetMouseClickEnabled( int code, bool state ) + { + __base()->SetMouseClickEnabled( (MouseCode)code, state ); + } + + bool IsMouseClickEnabled( int code ) + { + return __base()->IsMouseClickEnabled( (MouseCode)code ); + } + + void SetDefaultColor( int fr, int fg, int fb, int fa, int br, int bg, int bb, int ba ) + { + __base()->SetDefaultColor( Color(fr, fg, fb, fa), Color(br, bg, bb, ba) ); + } + + void SetArmedColor( int fr, int fg, int fb, int fa, int br, int bg, int bb, int ba ) + { + __base()->SetArmedColor( Color(fr, fg, fb, fa), Color(br, bg, bb, ba) ); + } + + void SetSelectedColor( int fr, int fg, int fb, int fa, int br, int bg, int bb, int ba ) + { + __base()->SetSelectedColor( Color(fr, fg, fb, fa), Color(br, bg, bb, ba) ); + } + + void SetDepressedColor( int fr, int fg, int fb, int fa, int br, int bg, int bb, int ba ) + { + __base()->SetDepressedColor( Color(fr, fg, fb, fa), Color(br, bg, bb, ba) ); + } + + void SetArmedSound( const char *sound ) + { + __base()->SetArmedSound( sound ); + } + + void SetDepressedSound( const char *sound ) + { + __base()->SetDepressedSound( sound ); + } + + void SetReleasedSound( const char *sound ) + { + __base()->SetReleasedSound( sound ); + } +}; + +#define DEFINE_VGUI_SCRIPTFUNC_Button()\ + DEFINE_VGUI_SCRIPTFUNC_Label()\ + DEFINE_SCRIPTFUNC( SetButtonActivationType, "" )\ + DEFINE_SCRIPTFUNC( IsArmed, "" )\ + DEFINE_SCRIPTFUNC( SetArmed, "" )\ + DEFINE_SCRIPTFUNC( IsSelected, "" )\ + DEFINE_SCRIPTFUNC( SetSelected, "" )\ + DEFINE_SCRIPTFUNC( IsDepressed, "" )\ + DEFINE_SCRIPTFUNC( ForceDepressed, "" )\ + DEFINE_SCRIPTFUNC( SetMouseClickEnabled, "" )\ + DEFINE_SCRIPTFUNC( IsMouseClickEnabled, "" )\ + DEFINE_SCRIPTFUNC( SetDefaultColor, "" )\ + DEFINE_SCRIPTFUNC( SetArmedColor, "" )\ + DEFINE_SCRIPTFUNC( SetSelectedColor, "" )\ + DEFINE_SCRIPTFUNC( SetDepressedColor, "" )\ + DEFINE_SCRIPTFUNC( SetArmedSound, "" )\ + DEFINE_SCRIPTFUNC( SetDepressedSound, "" )\ + DEFINE_SCRIPTFUNC( SetReleasedSound, "" )\ + +//-------------------------------------------------------------- +//-------------------------------------------------------------- + +CLASS_HELPER_INTERFACE( ImagePanel, Panel ) +{ +public: + void SetImage( const char *imageName, bool hardwareFilter ) + { + __base()->EvictImage(); + __base()->SetImage( vgui_GetImage( imageName, hardwareFilter ) ); + } + + void SetDrawColor( int r, int g, int b, int a ) + { + __base()->SetDrawColor( Color( r, g, b, a ) ); + } + + void SetTileImage( bool bTile ) + { + __base()->SetTileImage( bTile ); + } + + void SetShouldScaleImage( bool state ) + { + __base()->SetShouldScaleImage( state ); + } + + void SetRotation( int rotation ) + { + Assert( rotation == ROTATED_UNROTATED || + rotation == ROTATED_CLOCKWISE_90 || + rotation == ROTATED_ANTICLOCKWISE_90 || + rotation == ROTATED_FLIPPED ); + + __base()->SetRotation( rotation ); + } +#if 0 + void SetFrame( int nFrame ) + { + __base()->SetFrame( nFrame ); + } +#endif +}; + +#define DEFINE_VGUI_SCRIPTFUNC_ImagePanel()\ + DEFINE_VGUI_SCRIPTFUNC_Panel()\ + DEFINE_SCRIPTFUNC( SetImage, "" )\ + DEFINE_SCRIPTFUNC( SetDrawColor, "" )\ + DEFINE_SCRIPTFUNC( SetTileImage, "" )\ + DEFINE_SCRIPTFUNC( SetShouldScaleImage, "" )\ + DEFINE_SCRIPTFUNC( SetRotation, "" )\ + +//-------------------------------------------------------------- +//-------------------------------------------------------------- + +CLASS_HELPER_INTERFACE( Frame, Panel ) +{ +public: + void SetMinimumSize( int wide, int tall ) + { + __base()->SetMinimumSize( wide, tall ); + } + + void SetTitle( const char* titel ) + { + __base()->SetTitle( titel, false ); + } + + void Close() + { + __base()->Close(); + } + + void SetDeleteSelfOnClose( bool state ) + { + __base()->SetDeleteSelfOnClose( state ); + } + + void SetMoveable( bool state ) + { + __base()->SetMoveable( state ); + } + + void SetSizeable( bool state ) + { + __base()->SetSizeable( state ); + } + + void SetCloseButtonVisible( bool state ) + { + __base()->SetCloseButtonVisible( state ); + } + + void SetTitleBarVisible( bool state ) + { + __base()->SetTitleBarVisible( state ); + } +}; + +#define DEFINE_VGUI_SCRIPTFUNC_Frame()\ + DEFINE_VGUI_SCRIPTFUNC_Panel()\ + DEFINE_SCRIPTFUNC( SetMinimumSize, "" )\ + DEFINE_SCRIPTFUNC( SetTitle, "" )\ + DEFINE_SCRIPTFUNC( Close, "" )\ + DEFINE_SCRIPTFUNC( SetDeleteSelfOnClose, "" )\ + DEFINE_SCRIPTFUNC( SetMoveable, "" )\ + DEFINE_SCRIPTFUNC( SetSizeable, "" )\ + DEFINE_SCRIPTFUNC( SetCloseButtonVisible, "" )\ + DEFINE_SCRIPTFUNC( SetTitleBarVisible, "" )\ + +//-------------------------------------------------------------- +//-------------------------------------------------------------- + +CLASS_HELPER_INTERFACE( RichText, Panel ) +{ +public: + void SetText( const char* text ) + { + __base()->SetText( text ); + } + + void SetFont( int font ) + { + __base()->SetFont( IntToFontHandle(font) ); + } + + void InsertString( const char* text ) + { + __base()->InsertString( text ); + } + + void SetPanelInteractive( bool bInteractive ) + { + __base()->SetPanelInteractive( bInteractive ); + } + + void SetUnusedScrollbarInvisible( bool bInvis ) + { + __base()->SetUnusedScrollbarInvisible( bInvis ); + } + + void GotoTextStart() + { + __base()->GotoTextStart(); + } + + void GotoTextEnd() + { + __base()->GotoTextEnd(); + } + + void SetMaximumCharCount( int maxChars ) + { + __base()->SetMaximumCharCount( maxChars ); + } + + void InsertColorChange( int r, int g, int b, int a ) + { + __base()->InsertColorChange( Color( r, g, b, a ) ); + } + + int GetNumLines() + { + return __base()->GetNumLines(); + } + + void SetDrawTextOnly() + { + __base()->SetDrawTextOnly(); + } +}; + +#define DEFINE_VGUI_SCRIPTFUNC_RichText()\ + DEFINE_VGUI_SCRIPTFUNC_Panel()\ + DEFINE_SCRIPTFUNC( SetText, "" )\ + DEFINE_SCRIPTFUNC( SetFont, "" )\ + DEFINE_SCRIPTFUNC( InsertString, "" )\ + DEFINE_SCRIPTFUNC( SetPanelInteractive, "" )\ + DEFINE_SCRIPTFUNC( SetUnusedScrollbarInvisible, "" )\ + DEFINE_SCRIPTFUNC( GotoTextStart, "" )\ + DEFINE_SCRIPTFUNC( GotoTextEnd, "" )\ + DEFINE_SCRIPTFUNC( SetMaximumCharCount, "" )\ + DEFINE_SCRIPTFUNC( InsertColorChange, "" )\ + DEFINE_SCRIPTFUNC( GetNumLines, "" )\ + DEFINE_SCRIPTFUNC( SetDrawTextOnly, "" )\ + +//-------------------------------------------------------------- +//-------------------------------------------------------------- + +CLASS_HELPER_INTERFACE( TextEntry, Panel ) +{ +public: + void SetText( const char* text ) + { + wchar_t wcs[512]; + g_pVGuiLocalize->ConvertANSIToUnicode( text, wcs, sizeof(wcs) ); + __base()->SetText( wcs ); + } + + const char *GetText() + { + static char sz[512]; + __base()->GetText( sz, sizeof(sz) ); + return sz; + } + + void SetFont( int font ) + { + __base()->SetFont( IntToFontHandle(font) ); + } + + void SetEditable( bool state ) + { + __base()->SetEditable( state ); + } + + void GotoTextStart() + { + __base()->GotoTextStart(); + } + + void GotoTextEnd() + { + __base()->GotoTextEnd(); + } + + void InsertString( const char* text ) + { + __base()->InsertString( text ); + } + + void SelectNone() + { + __base()->SelectNone(); + } + + void SetMultiline( bool state ) + { + __base()->SetMultiline( state ); + } + + void SetVerticalScrollbar( bool state ) + { + __base()->SetVerticalScrollbar( state ); + } +#if 0 + void SetHorizontalScrolling( bool status ) + { + __base()->SetHorizontalScrolling( status ); + } +#endif + void SetCatchEnterKey( bool state ) + { + __base()->SetCatchEnterKey( state ); + } + + void SetMaximumCharCount( int maxChars ) + { + __base()->SetMaximumCharCount( maxChars ); + } +#if 0 + void SetWrap( bool wrap ) + { + __base()->SetWrap( wrap ); + } +#endif + void SetAllowNumericInputOnly( bool state ) + { + __base()->SetAllowNumericInputOnly( state ); + } +#if 0 + void SetDisabledBgColor( int r, int g, int b, int a ) + { + __base()->SetDisabledBgColor( Color( r, g, b, a ) ); + } + + void SetSelectionTextColor( int r, int g, int b, int a ) + { + __base()->SetSelectionTextColor( Color( r, g, b, a ) ); + } + + void SetSelectionBgColor( int r, int g, int b, int a ) + { + __base()->SetSelectionBgColor( Color( r, g, b, a ) ); + } + + void SetSelectionUnfocusedBgColor( int r, int g, int b, int a ) + { + __base()->SetSelectionUnfocusedBgColor( Color( r, g, b, a ) ); + } +#endif +}; + +#define DEFINE_VGUI_SCRIPTFUNC_TextEntry()\ + DEFINE_VGUI_SCRIPTFUNC_Panel()\ + DEFINE_SCRIPTFUNC( SetText, "" )\ + DEFINE_SCRIPTFUNC( GetText, "" )\ + DEFINE_SCRIPTFUNC( SetFont, "" )\ + DEFINE_SCRIPTFUNC( SetEditable, "" )\ + DEFINE_SCRIPTFUNC( GotoTextStart, "" )\ + DEFINE_SCRIPTFUNC( GotoTextEnd, "" )\ + DEFINE_SCRIPTFUNC( InsertString, "" )\ + DEFINE_SCRIPTFUNC( SelectNone, "" )\ + DEFINE_SCRIPTFUNC( SetMultiline, "" )\ + DEFINE_SCRIPTFUNC( SetVerticalScrollbar, "" )\ + DEFINE_SCRIPTFUNC( SetCatchEnterKey, "" )\ + DEFINE_SCRIPTFUNC( SetMaximumCharCount, "" )\ + DEFINE_SCRIPTFUNC( SetAllowNumericInputOnly, "" )\ + +//-------------------------------------------------------------- +//-------------------------------------------------------------- +#if !defined(NO_STEAM) +CLASS_HELPER_INTERFACE( AvatarImage, Panel ) +{ +public: + void SetPlayer( const char *steam2id, int eAvatarSize ) + { + uint32 __SteamInstanceID; + uint32 __SteamLocalUserID_Low32Bits; + uint32 __SteamLocalUserID_High32Bits; + + int c = sscanf( steam2id, "STEAM_%u:%u:%u", + &__SteamInstanceID, &__SteamLocalUserID_High32Bits, &__SteamLocalUserID_Low32Bits ); + + if ( c < 3 ) + return; + + CSteamID id( __SteamLocalUserID_Low32Bits * 2 + __SteamLocalUserID_High32Bits, + k_EUniversePublic, + k_EAccountTypeIndividual ); + + __base()->SetPlayer( id, (EAvatarSize)eAvatarSize ); + } + + void SetPlayerByIndex( int entindex, int eAvatarSize ) + { + if ( !entindex ) + { + __base()->ClearAvatar(); + return; + } + + __base()->SetPlayer( entindex, (EAvatarSize)eAvatarSize ); + } + + void SetDefaultAvatar( const char *imageName ) + { + __base()->SetDefaultAvatar( vgui_GetImage( imageName, false ) ); + } + + void SetShouldScaleImage( bool state ) + { + __base()->SetShouldScaleImage( state ); + } +}; + +#define DEFINE_VGUI_SCRIPTFUNC_AvatarImage()\ + DEFINE_VGUI_SCRIPTFUNC_Panel()\ + DEFINE_SCRIPTFUNC( SetPlayer, "" )\ + DEFINE_SCRIPTFUNC( SetPlayerByIndex, "" )\ + DEFINE_SCRIPTFUNC( SetDefaultAvatar, "" )\ + DEFINE_SCRIPTFUNC( SetShouldScaleImage, "" ) +#endif +//-------------------------------------------------------------- +//-------------------------------------------------------------- +#if VGUI_TGA_IMAGE_PANEL +CLASS_HELPER_INTERFACE( TGAImage, Panel ) +{ +public: + void SetImage( const char *p ) + { + __base()->SetTGAImage( p ); + } + + void SetDrawColor( int r, int g, int b, int a ) + { + __base()->SetDrawColor( r, g, b, a ); + } + + void SetShouldScaleImage( bool i ) + { + __base()->SetShouldScaleImage( i ); + } +}; + +#define DEFINE_VGUI_SCRIPTFUNC_TGAImage()\ + DEFINE_VGUI_SCRIPTFUNC_Panel()\ + DEFINE_SCRIPTFUNC( SetImage, "" )\ + DEFINE_SCRIPTFUNC( SetDrawColor, "" )\ + DEFINE_SCRIPTFUNC( SetShouldScaleImage, "" ) +#endif +//-------------------------------------------------------------- +//-------------------------------------------------------------- +#if 0 +CLASS_HELPER_INTERFACE( PNGImage, Panel ) +{ +public: + void SetImage( const char *p ) + { + __base()->SetPNGImage( p ); + } + + void SetDrawColor( int r, int g, int b, int a ) + { + __base()->SetDrawColor( r, g, b, a ); + } + + void SetShouldScaleImage( bool i ) + { + __base()->SetShouldScaleImage( i ); + } +}; + +#define DEFINE_VGUI_SCRIPTFUNC_PNGImage()\ + DEFINE_VGUI_SCRIPTFUNC_Panel()\ + DEFINE_SCRIPTFUNC( SetImage, "" )\ + DEFINE_SCRIPTFUNC( SetDrawColor, "" )\ + DEFINE_SCRIPTFUNC( SetShouldScaleImage, "" ) +#endif +//-------------------------------------------------------------- +//-------------------------------------------------------------- + +//-------------------------------------------------------------- +//-------------------------------------------------------------- + + +//============================================================== +//============================================================== + + +static inline void SetHScript( HSCRIPT &var, HSCRIPT val ) +{ + if ( var && g_pScriptVM ) + g_pScriptVM->ReleaseScript( var ); + var = val; +} + +#define CheckCallback(s)\ + if ( !V_strcmp( cb, #s ) )\ + {\ + SetHScript( m_hfn##s, fn );\ + return;\ + } + +//-------------------------------------------------------- +// C++ objects for vgui overrides and messages. +//-------------------------------------------------------- + + +class CScript_Panel : public Panel +{ + DECLARE_SCRIPTVGUI_CLASS( Panel ); + +private: + HSCRIPT m_hfnPaint; + HSCRIPT m_hfnPaintBackground; + HSCRIPT m_hfnPostChildPaint; + + HSCRIPT m_hfnPerformLayout; + HSCRIPT m_hfnOnTick; + HSCRIPT m_hfnOnScreenSizeChanged; + + HSCRIPT m_hfnOnCursorEntered; + HSCRIPT m_hfnOnCursorExited; + HSCRIPT m_hfnOnCursorMoved; + + HSCRIPT m_hfnOnMousePressed; + HSCRIPT m_hfnOnMouseDoublePressed; + HSCRIPT m_hfnOnMouseReleased; + HSCRIPT m_hfnOnMouseWheeled; + + HSCRIPT m_hfnOnKeyCodePressed; + HSCRIPT m_hfnOnKeyCodeReleased; + HSCRIPT m_hfnOnKeyCodeTyped; + +#if SCRIPT_VGUI_SIGNAL_INTERFACE + HSCRIPT m_hfnOnCommand; +#endif + +public: + CScript_Panel( Panel *parent, const char *name ) : + BaseClass( parent, name ), + + m_hfnPaint(NULL), + m_hfnPaintBackground(NULL), + m_hfnPostChildPaint(NULL), + + m_hfnPerformLayout(NULL), + m_hfnOnTick(NULL), + m_hfnOnScreenSizeChanged(NULL), +#if SCRIPT_VGUI_SIGNAL_INTERFACE + m_hfnOnCommand(NULL), +#endif + m_hfnOnCursorEntered(NULL), + m_hfnOnCursorExited(NULL), + m_hfnOnCursorMoved(NULL), + + m_hfnOnMousePressed(NULL), + m_hfnOnMouseDoublePressed(NULL), + m_hfnOnMouseReleased(NULL), + m_hfnOnMouseWheeled(NULL), + + m_hfnOnKeyCodePressed(NULL), + m_hfnOnKeyCodeReleased(NULL), + m_hfnOnKeyCodeTyped(NULL) + {} + + void ScriptShutdown() + { + ivgui()->RemoveTickSignal( GetVPanel() ); + + SetHScript( m_hfnPaint, NULL ); + SetHScript( m_hfnPaintBackground, NULL ); + SetHScript( m_hfnPostChildPaint, NULL ); + + SetHScript( m_hfnPerformLayout, NULL ); + SetHScript( m_hfnOnTick, NULL ); + SetHScript( m_hfnOnScreenSizeChanged, NULL ); + + SetHScript( m_hfnOnCursorEntered, NULL ); + SetHScript( m_hfnOnCursorExited, NULL ); + SetHScript( m_hfnOnCursorMoved, NULL ); + + SetHScript( m_hfnOnMousePressed, NULL ); + SetHScript( m_hfnOnMouseDoublePressed, NULL ); + SetHScript( m_hfnOnMouseReleased, NULL ); + SetHScript( m_hfnOnMouseWheeled, NULL ); + + SetHScript( m_hfnOnKeyCodePressed, NULL ); + SetHScript( m_hfnOnKeyCodeReleased, NULL ); + SetHScript( m_hfnOnKeyCodeTyped, NULL ); + +#if SCRIPT_VGUI_SIGNAL_INTERFACE + SetHScript( m_hfnOnCommand, NULL ); +#endif + } + +public: + void Paint() + { + g_pScriptVM->ExecuteFunction( m_hfnPaint, NULL, 0, NULL, NULL, true ); + } + + void PaintBackground() + { + if ( m_hfnPaintBackground ) + { + g_pScriptVM->ExecuteFunction( m_hfnPaintBackground, NULL, 0, NULL, NULL, true ); + } + else + { + BaseClass::PaintBackground(); + } + } + + void PostChildPaint() + { + g_pScriptVM->ExecuteFunction( m_hfnPostChildPaint, NULL, 0, NULL, NULL, true ); + } + + void PerformLayout() + { + BaseClass::PerformLayout(); + + if ( m_hfnPerformLayout ) + { + g_pScriptVM->ExecuteFunction( m_hfnPerformLayout, NULL, 0, NULL, NULL, true ); + } + } + + void OnTick() + { + g_pScriptVM->ExecuteFunction( m_hfnOnTick, NULL, 0, NULL, NULL, true ); + } + + void OnScreenSizeChanged( int oldwide, int oldtall ) + { + BaseClass::OnScreenSizeChanged( oldwide, oldtall ); + + if ( m_hfnOnScreenSizeChanged ) + { + ScriptVariant_t args[2] = { oldwide, oldtall }; + g_pScriptVM->ExecuteFunction( m_hfnOnScreenSizeChanged, args, 2, NULL, NULL, true ); + } + } +#if SCRIPT_VGUI_SIGNAL_INTERFACE + void OnCommand( const char *command ) + { + if ( m_hfnOnCommand ) + { + ScriptVariant_t ret, arg = command; + g_pScriptVM->ExecuteFunction( m_hfnOnCommand, &arg, 1, &ret, NULL, true ); + + // Return true to swallow + if ( ret.m_type == FIELD_BOOLEAN && ret.m_bool ) + return; + } + + BaseClass::OnCommand( command ); + } +#endif + void OnCursorEntered() + { + if ( m_hfnOnCursorEntered ) + { + g_pScriptVM->ExecuteFunction( m_hfnOnCursorEntered, NULL, 0, NULL, NULL, true ); + } + } + + void OnCursorExited() + { + if ( m_hfnOnCursorExited ) + { + g_pScriptVM->ExecuteFunction( m_hfnOnCursorExited, NULL, 0, NULL, NULL, true ); + } + } + + void OnCursorMoved( int x, int y ) + { + if ( m_hfnOnCursorMoved ) + { + ScriptVariant_t args[2] = { x, y }; + g_pScriptVM->ExecuteFunction( m_hfnOnCursorMoved, args, 2, NULL, NULL, true ); + } + else + { + Assert( !ParentNeedsCursorMoveEvents() ); + } + } + + void OnMousePressed( MouseCode code ) + { + if ( m_hfnOnMousePressed ) + { + ScriptVariant_t arg = (int)code; + g_pScriptVM->ExecuteFunction( m_hfnOnMousePressed, &arg, 1, NULL, NULL, true ); + } + } + + void OnMouseDoublePressed( MouseCode code ) + { + if ( m_hfnOnMouseDoublePressed ) + { + ScriptVariant_t arg = (int)code; + g_pScriptVM->ExecuteFunction( m_hfnOnMouseDoublePressed, &arg, 1, NULL, NULL, true ); + } + } + + void OnMouseReleased( MouseCode code ) + { + if ( m_hfnOnMouseReleased ) + { + ScriptVariant_t arg = (int)code; + g_pScriptVM->ExecuteFunction( m_hfnOnMouseReleased, &arg, 1, NULL, NULL, true ); + } + } + + void OnMouseWheeled( int delta ) + { + if ( m_hfnOnMouseWheeled ) + { + ScriptVariant_t arg = (int)delta; + g_pScriptVM->ExecuteFunction( m_hfnOnMouseWheeled, &arg, 1, NULL, NULL, true ); + } + } + + void OnKeyCodePressed( KeyCode code ) + { + if ( m_hfnOnKeyCodePressed ) + { + ScriptVariant_t ret, arg = (int)code; + g_pScriptVM->ExecuteFunction( m_hfnOnKeyCodePressed, &arg, 1, &ret, NULL, true ); + + // Return true to swallow + if ( ret.m_type == FIELD_BOOLEAN && ret.m_bool ) + return; + } + + BaseClass::OnKeyCodePressed( code ); + } + + void OnKeyCodeReleased( KeyCode code ) + { + if ( m_hfnOnKeyCodeReleased ) + { + ScriptVariant_t ret, arg = (int)code; + g_pScriptVM->ExecuteFunction( m_hfnOnKeyCodeReleased, &arg, 1, &ret, NULL, true ); + + // Return true to swallow + if ( ret.m_type == FIELD_BOOLEAN && ret.m_bool ) + return; + } + + BaseClass::OnKeyCodeReleased( code ); + } + + void OnKeyCodeTyped( KeyCode code ) + { + if ( m_hfnOnKeyCodeTyped ) + { + ScriptVariant_t ret, arg = (int)code; + g_pScriptVM->ExecuteFunction( m_hfnOnKeyCodeTyped, &arg, 1, &ret, NULL, true ); + + // Return true to swallow + if ( ret.m_type == FIELD_BOOLEAN && ret.m_bool ) + return; + } + + BaseClass::OnKeyCodeTyped( code ); + } + +public: + void SetCallback( const char* cb, HSCRIPT fn ) + { + CheckCallback( Paint ); + CheckCallback( PaintBackground ); + CheckCallback( PostChildPaint ); + + CheckCallback( PerformLayout ); + CheckCallback( OnTick ); + CheckCallback( OnScreenSizeChanged ); + + CheckCallback( OnCursorEntered ); + CheckCallback( OnCursorExited ); + CheckCallback( OnCursorMoved ); + + CheckCallback( OnMousePressed ); + CheckCallback( OnMouseDoublePressed ); + CheckCallback( OnMouseReleased ); + CheckCallback( OnMouseWheeled ); + + CheckCallback( OnKeyCodePressed ); + CheckCallback( OnKeyCodeReleased ); + CheckCallback( OnKeyCodeTyped ); + +#if SCRIPT_VGUI_SIGNAL_INTERFACE + CheckCallback( OnCommand ); +#endif + + g_pScriptVM->RaiseException("invalid callback"); + } +}; + +//-------------------------------------------------------------- +//-------------------------------------------------------------- + +class CScript_Frame : public Frame +{ + DECLARE_SCRIPTVGUI_CLASS( Frame ); + +private: + HSCRIPT m_hfnPaint; + HSCRIPT m_hfnPaintBackground; + + HSCRIPT m_hfnPerformLayout; + HSCRIPT m_hfnOnTick; + HSCRIPT m_hfnOnScreenSizeChanged; + + HSCRIPT m_hfnOnCursorEntered; + HSCRIPT m_hfnOnCursorExited; + HSCRIPT m_hfnOnCursorMoved; + + HSCRIPT m_hfnOnMousePressed; + HSCRIPT m_hfnOnMouseDoublePressed; + HSCRIPT m_hfnOnMouseReleased; + HSCRIPT m_hfnOnMouseWheeled; + + HSCRIPT m_hfnOnKeyCodePressed; + HSCRIPT m_hfnOnKeyCodeReleased; + HSCRIPT m_hfnOnKeyCodeTyped; + +#if SCRIPT_VGUI_SIGNAL_INTERFACE + HSCRIPT m_hfnOnCommand; +#endif + +public: + CScript_Frame( Panel *parent, const char *name ) : + + // Start without popup + BaseClass( parent, name, false, false ), + + m_hfnPaint(NULL), + m_hfnPaintBackground(NULL), + + m_hfnPerformLayout(NULL), + m_hfnOnTick(NULL), + m_hfnOnScreenSizeChanged(NULL), +#if SCRIPT_VGUI_SIGNAL_INTERFACE + m_hfnOnCommand(NULL), +#endif + + m_hfnOnCursorEntered(NULL), + m_hfnOnCursorExited(NULL), + m_hfnOnCursorMoved(NULL), + + m_hfnOnMousePressed(NULL), + m_hfnOnMouseDoublePressed(NULL), + m_hfnOnMouseReleased(NULL), + m_hfnOnMouseWheeled(NULL), + + m_hfnOnKeyCodePressed(NULL), + m_hfnOnKeyCodeReleased(NULL), + m_hfnOnKeyCodeTyped(NULL) + { + SetFadeEffectDisableOverride( true ); + } + + void ScriptShutdown() + { + ivgui()->RemoveTickSignal( GetVPanel() ); + + SetHScript( m_hfnPaint, NULL ); + SetHScript( m_hfnPaintBackground, NULL ); + + SetHScript( m_hfnPerformLayout, NULL ); + SetHScript( m_hfnOnTick, NULL ); + SetHScript( m_hfnOnScreenSizeChanged, NULL ); + + SetHScript( m_hfnOnMousePressed, NULL ); + SetHScript( m_hfnOnMouseDoublePressed, NULL ); + SetHScript( m_hfnOnMouseReleased, NULL ); + SetHScript( m_hfnOnMouseWheeled, NULL ); + + SetHScript( m_hfnOnKeyCodePressed, NULL ); + SetHScript( m_hfnOnKeyCodeReleased, NULL ); + SetHScript( m_hfnOnKeyCodeTyped, NULL ); + +#if SCRIPT_VGUI_SIGNAL_INTERFACE + SetHScript( m_hfnOnCommand, NULL ); +#endif + } + +public: + void Paint() + { + g_pScriptVM->ExecuteFunction( m_hfnPaint, NULL, 0, NULL, NULL, true ); + } + + void PaintBackground() + { + if ( m_hfnPaintBackground ) + { + g_pScriptVM->ExecuteFunction( m_hfnPaintBackground, NULL, 0, NULL, NULL, true ); + } + else + { + BaseClass::PaintBackground(); + } + } + + void PerformLayout() + { + BaseClass::PerformLayout(); + + if ( m_hfnPerformLayout ) + { + g_pScriptVM->ExecuteFunction( m_hfnPerformLayout, NULL, 0, NULL, NULL, true ); + } + } +#if 0 + void ApplySchemeSettings( IScheme *pScheme ) + { + BaseClass::ApplySchemeSettings( pScheme ); + + if ( m_hfnApplySchemeSettings ) + { + ScriptVariant_t arg; + g_pScriptVM->ExecuteFunction( m_hfnApplySchemeSettings, &arg, 1, NULL, NULL, true ); + } + } +#endif + void OnTick() + { + g_pScriptVM->ExecuteFunction( m_hfnOnTick, NULL, 0, NULL, NULL, true ); + } + + void OnScreenSizeChanged( int oldwide, int oldtall ) + { + BaseClass::OnScreenSizeChanged( oldwide, oldtall ); + + if ( m_hfnOnScreenSizeChanged ) + { + ScriptVariant_t args[2] = { oldwide, oldtall }; + g_pScriptVM->ExecuteFunction( m_hfnOnScreenSizeChanged, args, 2, NULL, NULL, true ); + } + } +#if SCRIPT_VGUI_SIGNAL_INTERFACE + void OnCommand( const char *command ) + { + if ( m_hfnOnCommand ) + { + ScriptVariant_t ret, arg = command; + g_pScriptVM->ExecuteFunction( m_hfnOnCommand, &arg, 1, &ret, NULL, true ); + + // Return true to swallow + if ( ret.m_type == FIELD_BOOLEAN && ret.m_bool ) + return; + } + + BaseClass::OnCommand( command ); + } +#endif + void OnCursorEntered() + { + if ( m_hfnOnCursorEntered ) + { + g_pScriptVM->ExecuteFunction( m_hfnOnCursorEntered, NULL, 0, NULL, NULL, true ); + } + } + + void OnCursorExited() + { + if ( m_hfnOnCursorExited ) + { + g_pScriptVM->ExecuteFunction( m_hfnOnCursorExited, NULL, 0, NULL, NULL, true ); + } + } + + void OnCursorMoved( int x, int y ) + { + if ( m_hfnOnCursorMoved ) + { + ScriptVariant_t args[2] = { x, y }; + g_pScriptVM->ExecuteFunction( m_hfnOnCursorMoved, args, 2, NULL, NULL, true ); + } + else + { + Assert( !ParentNeedsCursorMoveEvents() ); + } + } + + void OnMousePressed( MouseCode code ) + { + BaseClass::OnMousePressed( code ); + + if ( m_hfnOnMousePressed ) + { + ScriptVariant_t arg = (int)code; + g_pScriptVM->ExecuteFunction( m_hfnOnMousePressed, &arg, 1, NULL, NULL, true ); + } + } + + void OnMouseDoublePressed( MouseCode code ) + { + if ( m_hfnOnMouseDoublePressed ) + { + ScriptVariant_t arg = (int)code; + g_pScriptVM->ExecuteFunction( m_hfnOnMouseDoublePressed, &arg, 1, NULL, NULL, true ); + } + } + + void OnMouseReleased( MouseCode code ) + { + if ( m_hfnOnMouseReleased ) + { + ScriptVariant_t arg = (int)code; + g_pScriptVM->ExecuteFunction( m_hfnOnMouseReleased, &arg, 1, NULL, NULL, true ); + } + } + + void OnMouseWheeled( int delta ) + { + if ( m_hfnOnMouseWheeled ) + { + ScriptVariant_t arg = (int)delta; + g_pScriptVM->ExecuteFunction( m_hfnOnMouseWheeled, &arg, 1, NULL, NULL, true ); + } + } + + void OnKeyCodePressed( KeyCode code ) + { + if ( m_hfnOnKeyCodePressed ) + { + ScriptVariant_t ret, arg = (int)code; + g_pScriptVM->ExecuteFunction( m_hfnOnKeyCodePressed, &arg, 1, &ret, NULL, true ); + + // Return true to swallow + if ( ret.m_type == FIELD_BOOLEAN && ret.m_bool ) + return; + } + + BaseClass::OnKeyCodePressed( code ); + } + + void OnKeyCodeReleased( KeyCode code ) + { + if ( m_hfnOnKeyCodeReleased ) + { + ScriptVariant_t ret, arg = (int)code; + g_pScriptVM->ExecuteFunction( m_hfnOnKeyCodeReleased, &arg, 1, &ret, NULL, true ); + + // Return true to swallow + if ( ret.m_type == FIELD_BOOLEAN && ret.m_bool ) + return; + } + + BaseClass::OnKeyCodeReleased( code ); + } + + void OnKeyCodeTyped( KeyCode code ) + { + if ( m_hfnOnKeyCodeTyped ) + { + ScriptVariant_t ret, arg = (int)code; + g_pScriptVM->ExecuteFunction( m_hfnOnKeyCodeTyped, &arg, 1, &ret, NULL, true ); + + // Return true to swallow the CanChainKeysToParent() override check and fallback, + // which by default swallows the input. + if ( ret.m_type == FIELD_BOOLEAN && ret.m_bool ) + return; + + if ( CanChainKeysToParent() ) + { + BaseClass::OnKeyCodeTyped( code ); + } + } + else + { + BaseClass::OnKeyCodeTyped( code ); + } + } + +public: + void SetCallback( const char* cb, HSCRIPT fn ) + { + CheckCallback( Paint ); + CheckCallback( PaintBackground ); + + CheckCallback( PerformLayout ); + CheckCallback( OnTick ); + CheckCallback( OnScreenSizeChanged ); + + CheckCallback( OnCursorEntered ); + CheckCallback( OnCursorExited ); + CheckCallback( OnCursorMoved ); + + CheckCallback( OnMousePressed ); + CheckCallback( OnMouseDoublePressed ); + CheckCallback( OnMouseReleased ); + CheckCallback( OnMouseWheeled ); + + CheckCallback( OnKeyCodePressed ); + CheckCallback( OnKeyCodeReleased ); + CheckCallback( OnKeyCodeTyped ); + +#if SCRIPT_VGUI_SIGNAL_INTERFACE + CheckCallback( OnCommand ); +#endif + + g_pScriptVM->RaiseException("invalid callback"); + } +}; + +//-------------------------------------------------------------- +//-------------------------------------------------------------- + +class CScript_Button : public Button +{ + DECLARE_SCRIPTVGUI_CLASS( Button ); + +private: + HSCRIPT m_hfnPaint; + HSCRIPT m_hfnPaintBackground; + HSCRIPT m_hfnDoClick; + +public: + CScript_Button( Panel *parent, const char *name, const char *text ) : + BaseClass( parent, name, text ), + + m_hfnPaint(NULL), + m_hfnPaintBackground(NULL), + + m_hfnDoClick(NULL) + {} + + void ScriptShutdown() + { + SetHScript( m_hfnPaint, NULL ); + SetHScript( m_hfnPaintBackground, NULL ); + + SetHScript( m_hfnDoClick, NULL ); + } + +public: + void Paint() + { + if ( m_hfnPaint ) + { + g_pScriptVM->ExecuteFunction( m_hfnPaint, NULL, 0, NULL, NULL, true ); + } + else + { + BaseClass::Paint(); + } + } + + void PaintBackground() + { + if ( m_hfnPaintBackground ) + { + g_pScriptVM->ExecuteFunction( m_hfnPaintBackground, NULL, 0, NULL, NULL, true ); + } + else + { + BaseClass::PaintBackground(); + } + } + + void DoClick() + { + BaseClass::DoClick(); + + if ( m_hfnDoClick ) + { + g_pScriptVM->ExecuteFunction( m_hfnDoClick, NULL, 0, NULL, NULL, true ); + } + } + +public: + void SetCallback( const char* cb, HSCRIPT fn ) + { + CheckCallback( Paint ); + CheckCallback( PaintBackground ); + CheckCallback( DoClick ); + + g_pScriptVM->RaiseException("invalid callback"); + } +}; + +//-------------------------------------------------------------- +//-------------------------------------------------------------- + +class CScript_TextEntry : public TextEntry +{ + DECLARE_SCRIPTVGUI_CLASS( TextEntry ); + +private: + HSCRIPT m_hfnTextChanged; + +public: + CScript_TextEntry( Panel *parent, const char *name ) : + BaseClass( parent, name ), + + m_hfnTextChanged(NULL) + {} + + void ScriptShutdown() + { + SetHScript( m_hfnTextChanged, NULL ); + } + +public: + //--------------------------------------------- + // On "TextMessage" message. + // Used for responding to user input as it is typed. + //--------------------------------------------- + void FireActionSignal() + { + BaseClass::FireActionSignal(); + + if ( m_hfnTextChanged ) + { + g_pScriptVM->ExecuteFunction( m_hfnTextChanged, NULL, 0, NULL, NULL, true ); + } + } + +public: + void SetCallback( const char* cb, HSCRIPT fn ) + { + CheckCallback( TextChanged ); + + g_pScriptVM->RaiseException("invalid callback"); + } +}; + +//-------------------------------------------------------------- +//-------------------------------------------------------------- +#if !defined(NO_STEAM) +class CScript_AvatarImage : public CAvatarImagePanel +{ + DECLARE_SCRIPTVGUI_CLASS_EX( CScript_AvatarImage, CAvatarImagePanel ); + +public: + CScript_AvatarImage( Panel *parent, const char *name ) : + BaseClass( parent, name ) + { + SetShouldDrawFriendIcon( false ); + } + + ~CScript_AvatarImage() + { + DebugDestructor( CAvatarImagePanel ); + } + + void ScriptShutdown() {} +}; +#endif +//-------------------------------------------------------------- +//-------------------------------------------------------------- +#if VGUI_TGA_IMAGE_PANEL +class CTGAImagePanel : public Panel +{ + DECLARE_SCRIPTVGUI_CLASS_EX( CTGAImagePanel, Panel ); + +private: + int m_iTexture; + int m_nWidth; + int m_nHeight; + Color m_ImageColor; + bool m_bScaleImage; + +public: + CTGAImagePanel( Panel *parent, const char *name ) : + BaseClass( parent, name ), + m_iTexture(-1), + m_bScaleImage(0), + m_ImageColor( 255, 255, 255, 255 ) + { + SetPaintBackgroundEnabled( false ); + } + + ~CTGAImagePanel() + { + DebugDestructor( CTGAImagePanel ); + + if ( m_iTexture != -1 ) + { + surface()->DestroyTextureID( m_iTexture ); + } + } + + void ScriptShutdown() {} + +public: + void Paint() + { + if ( m_iTexture != -1 ) + { + surface()->DrawSetColor( m_ImageColor ); + surface()->DrawSetTexture( m_iTexture ); + + if ( m_bScaleImage ) + { + int w, t; + GetSize( w, t ); + surface()->DrawTexturedRect( 0, 0, w, t ); + } + else + { + surface()->DrawTexturedRect( 0, 0, m_nWidth, m_nHeight ); + } + } + else + { + int w, t; + GetSize( w, t ); + surface()->DrawSetColor( 200, 50, 150, 255 ); + surface()->DrawFilledRect( 0, 0, w, t ); + } + } + +public: + void SetTGAImage( const char *fileName ) + { + const char *ext = V_GetFileExtension( fileName ); + + if ( ext && V_stricmp( ext, "tga" ) != 0 ) + return; + + CUtlMemory< unsigned char > tga; + + if ( TGALoader::LoadRGBA8888( fileName, tga, m_nWidth, m_nHeight ) ) + { + if ( m_iTexture == -1 ) + { + m_iTexture = surface()->CreateNewTextureID( true ); + } + + surface()->DrawSetTextureRGBA( m_iTexture, tga.Base(), m_nWidth, m_nHeight, false, false ); + } + else + { + Warning( "Failed to load TGA image: '%s'\n", fileName ); + } + } + + void SetDrawColor( int r, int g, int b, int a ) + { + m_ImageColor.SetColor( r, g, b, a ); + } + + void SetShouldScaleImage( bool state ) + { + m_bScaleImage = state; + } +}; +#endif +//-------------------------------------------------------------- +//-------------------------------------------------------------- + +//-------------------------------------------------------------- +//-------------------------------------------------------------- + + +//============================================================== +//============================================================== + +//-------------------------------------------------------- +// Script objects +//-------------------------------------------------------- + +DEFINE_VGUI_CLASS_EMPTY_DEFAULT_TEXT( Label ) +DEFINE_VGUI_CLASS_EMPTY( ImagePanel ) +DEFINE_VGUI_CLASS_EMPTY( RichText ) + +//-------------------------------------------------------------- +//-------------------------------------------------------------- + +BEGIN_VGUI_HELPER( Panel ) + void SetCallback( const char *a, HSCRIPT b ) { __base()->SetCallback( a, b ); } +END_VGUI_HELPER() + +BEGIN_SCRIPTDESC_VGUI( Panel ) + DEFINE_SCRIPTFUNC( SetCallback, "" ) +END_SCRIPTDESC() + +//-------------------------------------------------------------- +//-------------------------------------------------------------- + +BEGIN_VGUI_HELPER( Frame ) + void SetCallback( const char *a, HSCRIPT b ) { __base()->SetCallback( a, b ); } +END_VGUI_HELPER() + +BEGIN_SCRIPTDESC_VGUI( Frame ) + DEFINE_SCRIPTFUNC( SetCallback, "" ) +END_SCRIPTDESC() + +//-------------------------------------------------------------- +//-------------------------------------------------------------- + +BEGIN_VGUI_HELPER_DEFAULT_TEXT( Button ) + void SetCallback( const char *a, HSCRIPT b ) { __base()->SetCallback( a, b ); } +END_VGUI_HELPER() + +BEGIN_SCRIPTDESC_VGUI( Button ) + DEFINE_SCRIPTFUNC( SetCallback, "" ) +END_SCRIPTDESC() + +//-------------------------------------------------------------- +//-------------------------------------------------------------- + +BEGIN_VGUI_HELPER( TextEntry ) + void SetCallback( const char *a, HSCRIPT b ) { __base()->SetCallback( a, b ); } +END_VGUI_HELPER() + +BEGIN_SCRIPTDESC_VGUI( TextEntry ) + DEFINE_SCRIPTFUNC( SetCallback, "" ) +END_SCRIPTDESC() + +//-------------------------------------------------------------- +//-------------------------------------------------------------- +#if !defined(NO_STEAM) +BEGIN_VGUI_HELPER( AvatarImage ) +END_VGUI_HELPER() + +BEGIN_SCRIPTDESC_VGUI( AvatarImage ) +END_SCRIPTDESC() +#endif +//-------------------------------------------------------------- +//-------------------------------------------------------------- +#if VGUI_TGA_IMAGE_PANEL +BEGIN_VGUI_HELPER_EX( TGAImage, CTGAImagePanel ) +END_VGUI_HELPER() + +BEGIN_SCRIPTDESC_VGUI( TGAImage ) +END_SCRIPTDESC() +#endif +//-------------------------------------------------------------- +//-------------------------------------------------------------- +#if 0 +BEGIN_VGUI_HELPER_EX( PNGImage, CPNGImagePanel ) +END_VGUI_HELPER() + +BEGIN_SCRIPTDESC_VGUI( PNGImage ) +END_SCRIPTDESC() +#endif +//-------------------------------------------------------------- +//-------------------------------------------------------------- + +//-------------------------------------------------------------- +//-------------------------------------------------------------- + + +//============================================================== +//============================================================== + + +struct hudelementcache_t +{ + CUtlConstString name; + int bits; +}; +CUtlVector< hudelementcache_t > m_HudElementCache; + +// Check if hud elements were changed in this level to shortcut on level shutdown +bool m_bHudVisiblityChangedThisLevel = false; + + + +class CScriptVGUI : public CAutoGameSystem +{ +public: + void LevelShutdownPostEntity(); + void Shutdown(); + +public: + HSCRIPT CreatePanel( const char* panelClass, HSCRIPT parent, const char* panelName, int root ); + //void LoadSchemeFromFile( const char *filename, const char *tag ); + +} script_vgui; + +BEGIN_SCRIPTDESC_ROOT_NAMED( CScriptVGUI, "IVGui", SCRIPT_SINGLETON ) + DEFINE_SCRIPTFUNC( CreatePanel, SCRIPT_HIDE ) +END_SCRIPTDESC() + + +HSCRIPT CScriptVGUI::CreatePanel( const char* panelClass, HSCRIPT parent, const char* panelName, int root ) +{ + if ( (unsigned)g_ScriptPanels.Count() >= (unsigned)g_ScriptPanels.InvalidIndex()-1 ) + { + Warning( "CScriptVGUI::CreatePanel() exhausted vgui panel storage!\n" ); + return NULL; + } + +#define Check( _name )\ + if ( !V_strcmp( panelClass, #_name ) )\ + {\ + CScript_##_name##_Helper *helper = AllocScriptPanel< CScript_##_name##_Helper >();\ + helper->CreateFromScript< CScript_##_name##_Helper >( (HSCRIPT)parent, panelName, root );\ + DebugDevMsg( "%3d | Create vgui %s '%s' %s\n", g_ScriptPanels.Count(), panelClass, panelName, helper->GetDebugName() );\ + return helper->GetScriptInstance();\ + } + + Check( Panel ); + Check( Label ); + Check( Button ); + Check( ImagePanel ); + Check( Frame ); + Check( RichText ); + Check( TextEntry ); +#if !defined(NO_STEAM) + Check( AvatarImage ); +#endif +#if VGUI_TGA_IMAGE_PANEL + Check( TGAImage ); +#endif + + g_pScriptVM->RaiseException("invalid vgui class"); + return NULL; + +#undef Check +} + +void CScriptVGUI::LevelShutdownPostEntity() +{ + DebugMsg( "LevelShutdownPostEntity()\n" ); + + if ( g_ScriptPanels.Count() ) + { + while ( g_ScriptPanels.Count() ) + { + Assert( g_ScriptPanels.Head() != g_ScriptPanels.InvalidIndex() ); + + int head = g_ScriptPanels.Head(); + g_ScriptPanels[ head ]->Destroy( head ); + } + + g_ScriptPanels.Purge(); + } + + if ( int i = g_ScriptTextureIDs.Count() ) + { + while ( i-- ) + { +#ifdef _DEBUG + char tex[MAX_PATH]; + surface()->DrawGetTextureFile( g_ScriptTextureIDs[i], tex, sizeof(tex)-1 ); + DebugMsg( "Destroy texture [%i]%s\n", g_ScriptTextureIDs[i], tex ); +#endif + surface()->DestroyTextureID( g_ScriptTextureIDs[i] ); + } + + g_ScriptTextureIDs.Purge(); + } + + // + // Reset hud element visibility + // + if ( m_bHudVisiblityChangedThisLevel ) + { + m_bHudVisiblityChangedThisLevel = false; + + FOR_EACH_VEC( m_HudElementCache, i ) + { + const hudelementcache_t &cache = m_HudElementCache[i]; + Assert( !cache.name.IsEmpty() ); + CHudElement *elem = gHUD.FindElement( cache.name ); + Assert( elem ); + if ( elem ) + { + elem->SetHiddenBits( cache.bits ); + } + } + } +} + +void CScriptVGUI::Shutdown() +{ + VGUI_DestroyScriptRootPanels(); + + FOR_EACH_DICT_FAST( g_ScriptFonts, i ) + { + fontalias_t &alias = g_ScriptFonts[i]; + for ( int j = 0; j < alias.Count(); ++j ) + { + char *pName = alias.Element(j).name; + if ( pName ) + { + free( pName ); + alias.Element(j).name = NULL; + } + } + + alias.Purge(); + } + + g_ScriptFonts.Purge(); + + m_HudElementCache.Purge(); +} + + +void SetHudElementVisible( const char *name, bool state ) +{ + CHudElement *elem = gHUD.FindElement( name ); + if ( !elem ) + return; + + int iOldBits = -2; + + FOR_EACH_VEC( m_HudElementCache, i ) + { + const hudelementcache_t &cache = m_HudElementCache[i]; + if ( !V_stricmp( cache.name, name ) ) + { + iOldBits = cache.bits; + break; + } + } + + if ( iOldBits == -2 ) + { + if ( state ) // no change + return; + + // First time setting the visibility of this element, save the original bits + hudelementcache_t &cache = m_HudElementCache.Element( m_HudElementCache.AddToTail() ); + cache.name.Set( name ); + cache.bits = elem->GetHiddenBits(); + } + + elem->SetHiddenBits( state ? iOldBits : -1 ); + + m_bHudVisiblityChangedThisLevel = true; +} + +#ifdef _DEBUG +CON_COMMAND( dump_hud_elements, "" ) +{ + int size = gHUD.m_HudList.Size(); + + CUtlVector< const char* > list( 0, size ); + + for ( int i = 0; i < size; i++ ) + { + list.AddToTail( gHUD.m_HudList[i]->GetName() ); + } + + struct _cmp + { + static int __cdecl fn( const char * const *a, const char * const *b ) { return strcmp( *a, *b ); } + }; + + list.Sort( _cmp::fn ); + + for ( int i = 0; i < size; i++ ) + { + Msg( "%s\n", list[i] ); + } +} +#endif + + +class CScriptIInput +{ +public: + void MakeWeaponSelection( HSCRIPT weapon ) + { + ::input->MakeWeaponSelection( HScriptToClass< C_BaseCombatWeapon >( weapon ) ); + } +#if 0 + int GetButtonBits() + { + return ::input->GetButtonBits(0); + } + + void ClearInputButton( int i ) + { + return ::input->ClearInputButton(i); + } +#endif + void SetCursorPos( int x, int y ) + { + vgui::input()->SetCursorPos( x, y ); + } + + int GetAnalogValue( int code ) + { + Assert( code >= 0 && code < ANALOG_CODE_LAST ); + + if ( code < 0 || code >= ANALOG_CODE_LAST ) + return 0; + + return inputsystem->GetAnalogValue( (AnalogCode_t)code ); + } + + int GetAnalogDelta( int code ) + { + Assert( code >= 0 && code < ANALOG_CODE_LAST ); + + if ( code < 0 || code >= ANALOG_CODE_LAST ) + return 0; + + return inputsystem->GetAnalogDelta( (AnalogCode_t)code ); + } + + bool IsButtonDown( int code ) + { + Assert( code >= BUTTON_CODE_NONE && code < BUTTON_CODE_LAST ); + + if ( code < BUTTON_CODE_NONE || code >= BUTTON_CODE_LAST ) + return 0; + + return inputsystem->IsButtonDown( (ButtonCode_t)code ); + } + + // key -> button + int StringToButtonCode( const char *key ) + { + return inputsystem->StringToButtonCode( key ); + } + + // button -> key + const char *ButtonCodeToString( int code ) + { + Assert( code >= BUTTON_CODE_NONE && code < BUTTON_CODE_LAST ); + + if ( code < BUTTON_CODE_NONE || code >= BUTTON_CODE_LAST ) + return 0; + + return inputsystem->ButtonCodeToString( (ButtonCode_t)code ); + } + + // bind -> key + const char *LookupBinding( const char *bind ) + { + return engine->Key_LookupBinding( bind ); + } + + // button -> bind + const char *BindingForKey( int code ) + { + return engine->Key_BindingForKey( (ButtonCode_t)code ); + } +#if 0 + const char *GetIMELanguageShortCode() + { + static char ret[5]; + wchar_t get[5]; + get[0] = L'\0'; + vgui::input()->GetIMELanguageShortCode( get, wcslen(get) ); + g_pVGuiLocalize->ConvertUnicodeToANSI( get, ret, sizeof(ret) ); + return ret; + } +#endif +} script_input; + +BEGIN_SCRIPTDESC_ROOT_NAMED( CScriptIInput, "IInput", SCRIPT_SINGLETON ) + DEFINE_SCRIPTFUNC( MakeWeaponSelection, "" ) + + DEFINE_SCRIPTFUNC( SetCursorPos, "" ) + + DEFINE_SCRIPTFUNC( GetAnalogValue, "" ) + DEFINE_SCRIPTFUNC( GetAnalogDelta, "" ) + DEFINE_SCRIPTFUNC( IsButtonDown, "" ) + + DEFINE_SCRIPTFUNC( StringToButtonCode, "" ) + DEFINE_SCRIPTFUNC( ButtonCodeToString, "" ) + DEFINE_SCRIPTFUNC( LookupBinding, "" ) + DEFINE_SCRIPTFUNC( BindingForKey, "" ) +END_SCRIPTDESC() + + +void SetClipboardText( const char *text ) +{ + system()->SetClipboardText( text, V_strlen(text) ); +} + +//============================================================== +//============================================================== + +#if 0 +//----------------------------------------------------------------------------- +// Get world position in screen space [0,1]. Return true if on screen. +//----------------------------------------------------------------------------- +inline bool WorldToScreen( const Vector &pos, int &ix, int &iy ) +{ + int scrw, scrh; + surface()->GetScreenSize( scrw, scrh ); + + const VMatrix &worldToScreen = engine->WorldToScreenMatrix(); + bool bOnScreen; + + // VMatrix * Vector (position projective) + vec_t w = worldToScreen[3][0] * pos[0] + worldToScreen[3][1] * pos[1] + worldToScreen[3][2] * pos[2] + worldToScreen[3][3]; + vec_t fx = worldToScreen[0][0] * pos[0] + worldToScreen[0][1] * pos[1] + worldToScreen[0][2] * pos[2] + worldToScreen[0][3]; + vec_t fy = worldToScreen[1][0] * pos[0] + worldToScreen[1][1] * pos[1] + worldToScreen[1][2] * pos[2] + worldToScreen[1][3]; + + if ( w < 0.001f ) + { + fx *= 1e5f; + fy *= 1e5f; + bOnScreen = false; + } + else + { + w = 1.0f / w; + fx *= w; + fy *= w; + bOnScreen = true; + } + + ix = (int)( scrw * 0.5f * ( 1.0f + fx ) + 0.5f ); + iy = (int)( scrh * 0.5f * ( 1.0f - fy ) + 0.5f ); + + return bOnScreen; +} +#endif +//----------------------------------------------------------------------------- +// Get screen pixel position [0,1] in world space. +//----------------------------------------------------------------------------- +inline void ScreenToWorld( int x, int y, Vector &out ) +{ + int scrw, scrh; + surface()->GetScreenSize( scrw, scrh ); + float scrx = (float)x / (float)scrw; + float scry = (float)y / (float)scrh; + + vec_t tmp[2]; + tmp[0] = 2.0f * scrx - 1.0f; + tmp[1] = 1.0f - 2.0f * scry; + //tmp[2] = 1.0f; + //tmp[3] = 1.0f; + + VMatrix screenToWorld; + MatrixInverseGeneral( engine->WorldToScreenMatrix(), screenToWorld ); + + // VMatrix * Vector (position projective) + vec_t iw = 1.0f / ( screenToWorld[3][0] * tmp[0] + screenToWorld[3][1] * tmp[1] + screenToWorld[3][2] + screenToWorld[3][3] ); + out[0] = iw * ( screenToWorld[0][0] * tmp[0] + screenToWorld[0][1] * tmp[1] + screenToWorld[0][2] + screenToWorld[0][3] ); + out[1] = iw * ( screenToWorld[1][0] * tmp[0] + screenToWorld[1][1] * tmp[1] + screenToWorld[1][2] + screenToWorld[1][3] ); + out[2] = iw * ( screenToWorld[2][0] * tmp[0] + screenToWorld[2][1] * tmp[1] + screenToWorld[2][2] + screenToWorld[2][3] ); +} + +#if 0 +static bool ScriptWorldToScreen( const Vector &pos, HSCRIPT out ) +{ + int ix, iy; + bool r = WorldToScreen( pos, ix, iy ); + + g_pScriptVM->SetValue( out, (ScriptVariant_t)0, ix ); + g_pScriptVM->SetValue( out, 1, iy ); + return r; +} +#endif +static const Vector& ScriptScreenToWorld( int x, int y ) +{ + static Vector out; + ScreenToWorld( x, y, out ); + return out; +} + +static const Vector& ScreenToRay( int x, int y ) +{ + static Vector out; + ScreenToWorld( x, y, out ); + VectorSubtract( out, CurrentViewOrigin(), out ); + VectorNormalize( out ); + return out; +} + +//----------------------------------------------------------------------------- +// Get world position normalised in screen space. Return true if on screen. +//----------------------------------------------------------------------------- +int ScreenTransform( const Vector& point, Vector& screen ); +static bool ScriptScreenTransform( const Vector &pos, HSCRIPT out ) +{ + Vector v; + bool r = ScreenTransform( pos, v ); + float x = 0.5f * ( 1.0f + v[0] ); + float y = 0.5f * ( 1.0f - v[1] ); + + g_pScriptVM->SetValue( out, (ScriptVariant_t)0, x ); + g_pScriptVM->SetValue( out, 1, y ); + return !r; +} + +int ScriptScreenWidth() +{ + int w, h; + surface()->GetScreenSize( w, h ); + return w; +} + +int ScriptScreenHeight() +{ + int w, h; + surface()->GetScreenSize( w, h ); + return h; +} + +// +// Saving the static (ScreenWidth/640) ratio in a script closure +// messes up on save/restore at differing resolutions - +// the closure and the user script funcs retain the ratio at the time of the save. +// It is not possible to update restored script closure outer variables without writing language specific functions. +// +// NOTE: Returns int! int usage is more common than float operations. +// +static int ScriptXRES( float x ) +{ + return x * ( (float)ScriptScreenWidth() / 640.0f ); +} + +static int ScriptYRES( float y ) +{ + return y * ( (float)ScriptScreenHeight() / 480.0f ); +} + +vgui::HFont GetScriptFont( const char *name, bool proportional ) +{ + return script_surface.GetFont( name, proportional, NULL ); +} + + +void RegisterScriptVGUI() +{ + ScriptRegisterFunction( g_pScriptVM, SetHudElementVisible, "" ); + + ScriptRegisterFunctionNamed( g_pScriptVM, ScriptXRES, "XRES", "" ); + ScriptRegisterFunctionNamed( g_pScriptVM, ScriptYRES, "YRES", "" ); + + ScriptRegisterFunction( g_pScriptVM, SetClipboardText, "" ); + //ScriptRegisterFunctionNamed( g_pScriptVM, ScriptWorldToScreen, "WorldToScreen", "Get world position in screen space [0,1]. Return true if on screen." ); + ScriptRegisterFunctionNamed( g_pScriptVM, ScriptScreenToWorld, "ScreenToWorld", "Get screen pixel position [0,1] in world space." ); + ScriptRegisterFunction( g_pScriptVM, ScreenToRay, "Get a ray from screen pixel position to world space." ); + ScriptRegisterFunctionNamed( g_pScriptVM, ScriptScreenTransform, "ScreenTransform", "Get world position normalised in screen space. Return true if on screen." ); + + g_pScriptVM->Run( g_Script_vgui_init ); + + g_pScriptVM->RegisterInstance( &script_surface, "surface" ); + g_pScriptVM->RegisterInstance( &script_input, "input" ); + g_pScriptVM->RegisterInstance( &script_vgui, "vgui" ); +} diff --git a/src/game/client/mapbase/vscript_vgui.h b/src/game/client/mapbase/vscript_vgui.h new file mode 100644 index 00000000..d5214073 --- /dev/null +++ b/src/game/client/mapbase/vscript_vgui.h @@ -0,0 +1,16 @@ +//========= Mapbase - https://github.com/mapbase-source/source-sdk-2013 ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef VSCRIPT_VGUI_H +#define VSCRIPT_VGUI_H +#ifdef _WIN32 +#pragma once +#endif + +void RegisterScriptVGUI(); + +#endif diff --git a/src/game/client/mapbase/vscript_vgui.nut b/src/game/client/mapbase/vscript_vgui.nut new file mode 100644 index 00000000..c4727eeb --- /dev/null +++ b/src/game/client/mapbase/vscript_vgui.nut @@ -0,0 +1,390 @@ +static const char* g_Script_vgui_init = R"script( +local DoCreateFont = ISurface.CreateFont; +function ISurface::CreateFont( name, props ) +{ + if ( !("name" in props) || typeof props.name != "string" ) + throw "invalid parameter 'name'"; + + if ( !("tall" in props) || typeof props.tall != "integer" || !props.tall ) + throw "invalid parameter 'tall'"; + + if ( !("weight" in props) || typeof props.weight != "integer" ) + throw "invalid parameter 'weight'"; + + local yres_min = 0, yres_max = 0; + + if ( "yres" in props && typeof props.yres == "string" ) + { + local ss = ::split( props.yres, " " ); + try + { + yres_min = ss[0].tointeger(); + yres_max = ss[1].tointeger(); + } + catch(x) + { + throw "invalid parameter 'yres'"; + } + } + + if ( ( (!("proportional" in props) || typeof props.proportional != "bool") ) && !yres_min ) + { + throw "parameter 'proportional' or 'yres' not found"; + } + else if ( "proportional" in props && props.proportional && yres_min ) + { + throw "resolution definition on a proportional font" + } + + local blur = 0, scanlines = 0, proportional = false, flags = 0; + + if ( "blur" in props && typeof props.blur == "integer" ) + blur = props.blur; + + if ( "scanlines" in props && typeof props.scanlines == "integer" ) + scanlines = props.scanlines; + + if ( "proportional" in props && typeof props.proportional == "bool" ) + proportional = props.proportional; + + if ( "italic" in props && props.italic == true ) + flags = flags | 0x001; + + if ( "underline" in props && props.underline == true ) + flags = flags | 0x002; + + if ( "strikeout" in props && props.strikeout == true ) + flags = flags | 0x004; + + if ( "symbol" in props && props.symbol == true ) + flags = flags | 0x008; + + if ( "antialias" in props && props.antialias == true ) + flags = flags | 0x010; + + if ( "gaussianblur" in props && props.gaussianblur == true ) + flags = flags | 0x020; + + if ( "rotary" in props && props.rotary == true ) + flags = flags | 0x040; + + if ( "dropshadow" in props && props.dropshadow == true ) + flags = flags | 0x080; + + if ( "additive" in props && props.additive == true ) + flags = flags | 0x100; + + if ( "outline" in props && props.outline == true ) + flags = flags | 0x200; + + if ( "custom" in props && props.custom == true ) + flags = flags | 0x400; + + if ( "bitmap" in props && props.bitmap == true ) + flags = flags | 0x800; + + return DoCreateFont( name, props.name, props.tall, props.weight, blur, scanlines, flags, yres_min, yres_max, proportional ); +} + +local _FontTall = {} +local _Schemes = {} +local DoGetFont = ISurface.DoGetFont <- ISurface.GetFont; +local DoGetFontTall = ISurface.GetFontTall; + +function ISurface::GetFont( name, proportional, sch = "" ) +{ + if ( sch in _Schemes ) + { + local fonts = _Schemes[sch][proportional.tointeger()]; + if ( name in fonts ) + return fonts[name]; + } + else + { + if ( typeof sch != "string" ) + throw "invalid parameter 'scheme'"; + _Schemes[sch] <- [{}, {}]; + } + + local id = DoGetFont( name, proportional, sch ); + if ( id > 0 ) + _Schemes[sch][proportional.tointeger()][name] <- id; + + return id; +} + +ISurface.GetFontTall <- function( id ) +{ + if ( id in _FontTall ) + return _FontTall[id]; + return _FontTall[id] <- DoGetFontTall( id ); +} + +local _Textures = {} +local DoGetTextureID = ISurface.GetTextureID; +local DoValidateTexture = ISurface.ValidateTexture; +local DoSetTextureFile = ISurface.SetTextureFile; + +ISurface.ValidateTexture <- function( filename, hardwareFilter, forceReload = false, procedural = false ) +{ + return DoValidateTexture( filename, hardwareFilter, forceReload, procedural ); +} + +ISurface.SetTextureFile <- function( id, filename, hardwareFilter ) +{ + if ( filename in _Textures ) + delete _Textures[filename]; + + return DoSetTextureFile( id, filename, hardwareFilter ); +} + +ISurface.GetTextureID <- function( name ) +{ + if ( name in _Textures ) + return _Textures[name]; + + local id = DoGetTextureID( name ); + if ( id > 0 ) + _Textures[name] <- id; + + return id; +} + +// Forward compatibility +IVGui.GetRootPanel <- function() { return 0x8888 } +//IVGui.GetGameUIRootPanel <- function() { return 0x8888+1 } +IVGui.GetClientDLLRootPanel <- function() { return 0x8888+2 } +IVGui.GetHudViewport <- function() { return 0x8888+10 } + +local CreatePanel = IVGui.CreatePanel; +function IVGui::CreatePanel( type, parent, name ) +{ + if ( !parent ) + throw "invalid parent"; + + local root = -1; + if ( typeof parent == "integer" ) + { + root = parent-0x8888; + switch ( root ) + { + case 0: + case 2: + case 10: + break; + default: throw "invalid parent"; + } + parent = null; + } + return CreatePanel( type, parent, name, root ); +} + +ISurface.__OnScreenSizeChanged <- function() +{ + _FontTall.clear(); +} + +// MAX_JOYSTICKS = 1 // ( 1 << MAX_SPLITSCREEN_CLIENT_BITS ) +// MAX_JOYSTICK_AXES = 6 // X,Y,Z,R,U,V +// JOYSTICK_MAX_BUTTON_COUNT = 32 +// JOYSTICK_POV_BUTTON_COUNT = 4 +// JOYSTICK_AXIS_BUTTON_COUNT = MAX_JOYSTICK_AXES * 2 + +enum ButtonCode +{ + KEY_FIRST = 0 + KEY_0 = 1 + KEY_1 = 2 + KEY_2 = 3 + KEY_3 = 4 + KEY_4 = 5 + KEY_5 = 6 + KEY_6 = 7 + KEY_7 = 8 + KEY_8 = 9 + KEY_9 = 10 + KEY_A = 11 + KEY_B = 12 + KEY_C = 13 + KEY_D = 14 + KEY_E = 15 + KEY_F = 16 + KEY_G = 17 + KEY_H = 18 + KEY_I = 19 + KEY_J = 20 + KEY_K = 21 + KEY_L = 22 + KEY_M = 23 + KEY_N = 24 + KEY_O = 25 + KEY_P = 26 + KEY_Q = 27 + KEY_R = 28 + KEY_S = 29 + KEY_T = 30 + KEY_U = 31 + KEY_V = 32 + KEY_W = 33 + KEY_X = 34 + KEY_Y = 35 + KEY_Z = 36 + KEY_PAD_0 = 37 + KEY_PAD_1 = 38 + KEY_PAD_2 = 39 + KEY_PAD_3 = 40 + KEY_PAD_4 = 41 + KEY_PAD_5 = 42 + KEY_PAD_6 = 43 + KEY_PAD_7 = 44 + KEY_PAD_8 = 45 + KEY_PAD_9 = 46 + KEY_PAD_DIVIDE = 47 + KEY_PAD_MULTIPLY = 48 + KEY_PAD_MINUS = 49 + KEY_PAD_PLUS = 50 + KEY_PAD_ENTER = 51 + KEY_PAD_DECIMAL = 52 + KEY_LBRACKET = 53 + KEY_RBRACKET = 54 + KEY_SEMICOLON = 55 + KEY_APOSTROPHE = 56 + KEY_BACKQUOTE = 57 + KEY_COMMA = 58 + KEY_PERIOD = 59 + KEY_SLASH = 60 + KEY_BACKSLASH = 61 + KEY_MINUS = 62 + KEY_EQUAL = 63 + KEY_ENTER = 64 + KEY_SPACE = 65 + KEY_BACKSPACE = 66 + KEY_TAB = 67 + KEY_CAPSLOCK = 68 + KEY_NUMLOCK = 69 + KEY_ESCAPE = 70 + KEY_SCROLLLOCK = 71 + KEY_INSERT = 72 + KEY_DELETE = 73 + KEY_HOME = 74 + KEY_END = 75 + KEY_PAGEUP = 76 + KEY_PAGEDOWN = 77 + KEY_BREAK = 78 + KEY_LSHIFT = 79 + KEY_RSHIFT = 80 + KEY_LALT = 81 + KEY_RALT = 82 + KEY_LCONTROL = 83 + KEY_RCONTROL = 84 + KEY_LWIN = 85 + KEY_RWIN = 86 + KEY_APP = 87 + KEY_UP = 88 + KEY_LEFT = 89 + KEY_DOWN = 90 + KEY_RIGHT = 91 + KEY_F1 = 92 + KEY_F2 = 93 + KEY_F3 = 94 + KEY_F4 = 95 + KEY_F5 = 96 + KEY_F6 = 97 + KEY_F7 = 98 + KEY_F8 = 99 + KEY_F9 = 100 + KEY_F10 = 101 + KEY_F11 = 102 + KEY_F12 = 103 + KEY_CAPSLOCKTOGGLE = 104 + KEY_NUMLOCKTOGGLE = 105 + KEY_SCROLLLOCKTOGGLE = 106 + KEY_LAST = 106 + + MOUSE_FIRST = 107 + MOUSE_LEFT = 107 + MOUSE_RIGHT = 108 + MOUSE_MIDDLE = 109 + MOUSE_4 = 110 + MOUSE_5 = 111 + MOUSE_WHEEL_UP = 112 + MOUSE_WHEEL_DOWN = 113 + MOUSE_LAST = 113 + + JOYSTICK_FIRST = 114 + JOYSTICK_FIRST_BUTTON = 114 + JOYSTICK_LAST_BUTTON = 145 + JOYSTICK_FIRST_POV_BUTTON = 146 + JOYSTICK_LAST_POV_BUTTON = 149 + JOYSTICK_FIRST_AXIS_BUTTON = 150 + JOYSTICK_LAST_AXIS_BUTTON = 161 + JOYSTICK_LAST = 161 +} + +enum AnalogCode +{ + MOUSE_X = 0 + MOUSE_Y = 1 + MOUSE_XY = 2 + MOUSE_WHEEL = 3 + + JOYSTICK_FIRST_AXIS = 4 + JOYSTICK_LAST_AXIS = 9 +} + +enum CursorCode +{ + dc_none = 1 + dc_arrow = 2 + dc_ibeam = 3 + dc_hourglass = 4 + dc_waitarrow = 5 + dc_crosshair = 6 + dc_up = 7 + dc_sizenwse = 8 + dc_sizenesw = 9 + dc_sizewe = 10 + dc_sizens = 11 + dc_sizeall = 12 + dc_no = 13 + dc_hand = 14 + dc_blank = 15 +} + +enum Alignment +{ + northwest = 0 + north = 1 + northeast = 2 + west = 3 + center = 4 + east = 5 + southwest = 6 + south = 7 + southeast = 8 +} + +if ( __Documentation.RegisterHelp != dummy ) +{ + local RegEnum = function( e ) + { + local K = getconsttable()[e]; + __Documentation.RegisterEnumHelp( e, K.len(), "" ); + e += "."; + foreach ( s, v in K ) + { + __Documentation.RegisterConstHelp( e+s, v, "" ); + } + } + RegEnum( "ButtonCode" ); + RegEnum( "AnalogCode" ); + RegEnum( "CursorCode" ); + RegEnum( "Alignment" ); + + __Documentation.RegisterHelp( "ISurface::CreateFont", "void ISurface::CreateFont(string, handle)", "" ); + __Documentation.RegisterHelp( "IVGui::CreatePanel", "handle IVGui::CreatePanel(string, handle, string)", "" ); + __Documentation.RegisterHelp( "IVGui::GetRootPanel", "handle IVGui::GetRootPanel()", "" ); + __Documentation.RegisterHelp( "IVGui::GetClientDLLRootPanel", "handle IVGui::GetClientDLLRootPanel()", "" ); + __Documentation.RegisterHelp( "IVGui::GetHudViewport", "handle IVGui::GetHudViewport()", "" ); +} +)script"; diff --git a/src/game/client/message.cpp b/src/game/client/message.cpp index 63d60ff5..9517cc2d 100644 --- a/src/game/client/message.cpp +++ b/src/game/client/message.cpp @@ -447,6 +447,8 @@ void CHudMessage::MessageScanStart( void ) break; } + // Font was just set in MessageDrawScan() +#ifndef MAPBASE m_parms.font = g_hFontTrebuchet24; if ( m_parms.vguiFontName != NULL && @@ -455,6 +457,7 @@ void CHudMessage::MessageScanStart( void ) SetFont( vgui::scheme()->GetDefaultScheme(), m_parms.vguiFontName ); } +#endif } //----------------------------------------------------------------------------- @@ -497,7 +500,30 @@ void CHudMessage::MessageDrawScan( client_textmessage_t *pMessage, float time ) m_parms.totalWidth = 0; m_parms.vguiFontName = pMessage->pVGuiSchemeFontName; +#ifdef MAPBASE + if ( m_parms.vguiFontName != NULL && + m_parms.vguiFontName[ 0 ] ) + { + SetFont( vgui::scheme()->GetDefaultScheme(), m_parms.vguiFontName ); + + #ifdef MAPBASE_VSCRIPT + if ( m_parms.font == vgui::INVALID_FONT ) + { + extern vgui::HFont GetScriptFont( const char *, bool ); + + vgui::HFont font = GetScriptFont( m_parms.vguiFontName, IsProportional() ); + textmessage->SetFont( font ); + m_parms.font = font; + } + #endif + } + else + { + m_parms.font = g_hFontTrebuchet24; + } +#else m_parms.font = g_hFontTrebuchet24; +#endif while ( *pText ) { @@ -862,6 +888,85 @@ void CHudMessage::MsgFunc_HudMsg(bf_read &msg) // see tmessage.cpp why 512 msg.ReadString( (char*)pNetMessage->pMessage, 512 ); +#ifdef MAPBASE + // + // Mapbase adds a new data entry for custom fonts on entities like game_text. + // Some existing instances of this user message may not have this, so we have to make sure we have any bits left first. + // + if (msg.GetNumBitsLeft() > 0) + { + // Try to have VGui font names for each channel + static char szVGuiFontNames[MAX_NETMESSAGE][512]; + msg.ReadString( szVGuiFontNames[channel], 512 ); + if (szVGuiFontNames[channel][0] != '\0') + { + pNetMessage->pVGuiSchemeFontName = szVGuiFontNames[channel]; + } + } + + // + // Mapbase adds a new data entry for breaking game_text into newline when it goes past the user's screen. + // Some existing instances of this user message may not have this, so we have to make sure we have any bits left first. + // + if (msg.GetNumBitsLeft() > 0) + { + int len = msg.ReadByte(); + + // This is supposed to work around a bug where certain aspect ratios cut off lengthy texts. + //int lineMax = 64 * ((float)ScreenWidth() / 1440.0f); + int lineMax = 100 / engine->GetScreenAspectRatio(); + + int lineMinBreak = lineMax * 0.9; + + CGMsg( 2, CON_GROUP_CHOREO, "Line max is %i from an aspect ratio of %.3f (strlen %i)\n", lineMax, engine->GetScreenAspectRatio(), len ); + + char *curMessage = (char*)pNetMessage->pMessage; + char newMessage[512]; + + int cur = 0; // Current time on this line + int i = 0; // curMessage + int i2 = 0; // newMessage + for (i = 0; i < len; i++) + { + cur++; + newMessage[i2] = curMessage[i]; + + // Check if we're past the point in which we should break the line + if (cur >= lineMinBreak) + { + // Line break at the next space + if (curMessage[i] == ' ') + { + newMessage[i2] = '\n'; + cur = 0; + } + else if (curMessage[i] == '\n') + { + // Already a newline here + cur = 0; + } + else if (cur >= lineMax) + { + // We're at the max and there's no space. Force a newline with a hyphen + newMessage[i2] = '-'; + i2++; + newMessage[i2] = '\n'; + i2++; + newMessage[i2] = curMessage[i]; + cur = 0; + } + } + + i2++; + } + + // Null terminate + newMessage[i2] = '\0'; + + Q_strncpy( (char*)pNetMessage->pMessage, newMessage, 512 ); + } +#endif + MessageAdd( pNetMessage->pName ); } diff --git a/src/game/client/particlemgr.cpp b/src/game/client/particlemgr.cpp index bf2b394b..e006a5e2 100644 --- a/src/game/client/particlemgr.cpp +++ b/src/game/client/particlemgr.cpp @@ -146,6 +146,8 @@ CParticleEffectBinding::CParticleEffectBinding() m_LastMin = m_Min; m_LastMax = m_Max; + m_flParticleCullRadius = 0.0f; + SetParticleCullRadius( 0.0f ); m_nActiveParticles = 0; diff --git a/src/game/client/particles_simple.cpp b/src/game/client/particles_simple.cpp index b2971dbb..20500b32 100644 --- a/src/game/client/particles_simple.cpp +++ b/src/game/client/particles_simple.cpp @@ -308,8 +308,12 @@ void CSimpleEmitter::UpdateVelocity( SimpleParticle *pParticle, float timeDelta { if (pParticle->m_iFlags & SIMPLE_PARTICLE_FLAG_WINDBLOWN) { +#ifdef MAPBASE + Vector vecWind = GetWindspeedAtLocation( pParticle->m_Pos ); +#else Vector vecWind; GetWindspeedAtTime( gpGlobals->curtime, vecWind ); +#endif for ( int i = 0 ; i < 2 ; i++ ) { diff --git a/src/game/client/physpropclientside.cpp b/src/game/client/physpropclientside.cpp index 2f5c5f3b..83fc111c 100644 --- a/src/game/client/physpropclientside.cpp +++ b/src/game/client/physpropclientside.cpp @@ -704,9 +704,157 @@ void C_PhysPropClientside::ParseAllEntities(const char *pMapData) } } +#ifdef MAPBASE +CBaseAnimating *BreakModelCreate_Ragdoll( CBaseEntity *pOwnerEnt, breakmodel_t *pModel, const Vector &position, const QAngle &angles, const Vector &velocity, const AngularImpulse &angVelocity ) +{ + C_BaseAnimating *pOwner = dynamic_cast( pOwnerEnt ); + if ( !pOwner ) + return NULL; + + C_ClientRagdoll *pRagdoll = new C_ClientRagdoll( false ); + if ( pRagdoll == NULL ) + return NULL; + + const char *pModelName = pModel->modelName; + if ( pRagdoll->InitializeAsClientEntity( pModelName, RENDER_GROUP_OPAQUE_ENTITY ) == false ) + { + pRagdoll->Release(); + return NULL; + } + + pRagdoll->SetAbsOrigin( position ); + pRagdoll->SetAbsAngles( angles ); + + matrix3x4_t boneDelta0[MAXSTUDIOBONES]; + matrix3x4_t boneDelta1[MAXSTUDIOBONES]; + matrix3x4_t currentBones[MAXSTUDIOBONES]; + const float boneDt = 0.1f; + + pRagdoll->SetParent( pOwner ); + pRagdoll->ForceSetupBonesAtTime( boneDelta0, gpGlobals->curtime - boneDt ); + pRagdoll->ForceSetupBonesAtTime( boneDelta1, gpGlobals->curtime ); + pRagdoll->ForceSetupBonesAtTime( currentBones, gpGlobals->curtime ); + pRagdoll->SetParent( NULL ); + + // We need to take these from the entity + //pRagdoll->SetAbsOrigin( position ); + //pRagdoll->SetAbsAngles( angles ); + + pRagdoll->IgniteRagdoll( pOwner ); + pRagdoll->TransferDissolveFrom( pOwner ); + pRagdoll->InitModelEffects(); + + if ( pOwner->IsEffectActive( EF_NOSHADOW ) ) + { + pRagdoll->AddEffects( EF_NOSHADOW ); + } + + pRagdoll->m_nRenderFX = kRenderFxRagdoll; + pRagdoll->SetRenderMode( pOwner->GetRenderMode() ); + pRagdoll->SetRenderColor( pOwner->GetRenderColor().r, pOwner->GetRenderColor().g, pOwner->GetRenderColor().b, pOwner->GetRenderColor().a ); + //pRagdoll->SetGlobalFadeScale( pOwner->GetGlobalFadeScale() ); + + pRagdoll->SetSkin( pOwner->GetSkin() ); + //pRagdoll->m_vecForce = pOwner->m_vecForce; + //pRagdoll->m_nForceBone = 0; //pOwner->m_nForceBone; + pRagdoll->SetNextClientThink( CLIENT_THINK_ALWAYS ); + + pRagdoll->SetModelName( AllocPooledString( pModelName ) ); + pRagdoll->ResetSequence( 0 ); + pRagdoll->SetModelScale( pOwner->GetModelScale() ); + pRagdoll->SetCollisionGroup( COLLISION_GROUP_DEBRIS ); + //pRagdoll->m_builtRagdoll = true; + + CStudioHdr *hdr = pRagdoll->GetModelPtr(); + if ( !hdr ) + { + pRagdoll->Release(); + Warning( "Couldn't create ragdoll gib for %s (no model pointer)\n", pModel->modelName ); + return NULL; + } + + pRagdoll->m_pRagdoll = CreateRagdoll( + pRagdoll, + hdr, + vec3_origin, + 0, + boneDelta0, + boneDelta1, + currentBones, + boneDt ); + + if ( !pRagdoll->m_pRagdoll ) + { + pRagdoll->Release(); + Warning( "Couldn't create ragdoll gib for %s\n", pModel->modelName ); + return NULL; + } + + IPhysicsObject *pPhysicsObject = pRagdoll->VPhysicsGetObject(); + if ( pPhysicsObject ) + { + // randomize velocity by 5% + float rndf = RandomFloat( -0.025, 0.025 ); + Vector rndVel = velocity + rndf*velocity; + + pPhysicsObject->AddVelocity( &rndVel, &angVelocity ); + } + pRagdoll->ApplyLocalAngularVelocityImpulse( angVelocity ); + + if ( pRagdoll->m_pRagdoll ) + { + pRagdoll->m_bImportant = false; + pRagdoll->m_flForcedRetireTime = pModel->fadeTime > 0.0f ? gpGlobals->curtime + pModel->fadeTime : 0.0f; + s_RagdollLRU.MoveToTopOfLRU( pRagdoll, pRagdoll->m_bImportant, pRagdoll->m_flForcedRetireTime ); + pRagdoll->m_bFadeOut = true; + } + + // Cause the entity to recompute its shadow type and make a + // version which only updates when physics state changes + // NOTE: We have to do this after m_pRagdoll is assigned above + // because that's what ShadowCastType uses to figure out which type of shadow to use. + pRagdoll->DestroyShadow(); + pRagdoll->CreateShadow(); + + pRagdoll->SetAbsOrigin( position ); + pRagdoll->SetAbsAngles( angles ); + + pRagdoll->SetPlaybackRate( 0 ); + pRagdoll->SetCycle( 0 ); + + // put into ACT_DIERAGDOLL if it exists, otherwise use sequence 0 + int nSequence = pRagdoll->SelectWeightedSequence( ACT_DIERAGDOLL ); + if ( nSequence < 0 ) + { + pRagdoll->ResetSequence( 0 ); + } + else + { + pRagdoll->ResetSequence( nSequence ); + } + + pRagdoll->UpdatePartitionListEntry(); + pRagdoll->MarkRenderHandleDirty(); + + NoteRagdollCreationTick( pRagdoll ); + + //pRagdoll->InitAsClientRagdoll( boneDelta0, boneDelta1, currentBones, boneDt ); + + return pRagdoll; +} +#endif + CBaseEntity *BreakModelCreateSingle( CBaseEntity *pOwner, breakmodel_t *pModel, const Vector &position, const QAngle &angles, const Vector &velocity, const AngularImpulse &angVelocity, int nSkin, const breakablepropparams_t ¶ms ) { +#ifdef MAPBASE + if ( pModel->isRagdoll ) + { + CBaseEntity *pEntity = BreakModelCreate_Ragdoll( pOwner, pModel, position, angles, velocity, angVelocity ); + return pEntity; + } +#endif + C_PhysPropClientside *pEntity = C_PhysPropClientside::CreateNew(); if ( !pEntity ) @@ -778,10 +926,12 @@ CBaseEntity *BreakModelCreateSingle( CBaseEntity *pOwner, breakmodel_t *pModel, pEntity->SetFadeMinMax( pModel->fadeMinDist, pModel->fadeMaxDist ); } +#ifndef MAPBASE if ( pModel->isRagdoll ) { DevMsg( "BreakModelCreateSingle: clientside doesn't support ragdoll breakmodels.\n" ); } +#endif IPhysicsObject *pPhysicsObject = pEntity->VPhysicsGetObject(); diff --git a/src/game/client/prediction.cpp b/src/game/client/prediction.cpp index 420fb37d..9e6830e8 100644 --- a/src/game/client/prediction.cpp +++ b/src/game/client/prediction.cpp @@ -30,6 +30,13 @@ // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" +#ifdef MAPBASE +// This turned out to be causing major issues with VPhysics collision. +// It's deactivated until a fix is found. +// See player_command.cpp as well. +//#define PLAYER_COMMAND_FIX 1 +#endif + IPredictionSystem *IPredictionSystem::g_pPredictionSystems = NULL; #if !defined( NO_ENTITY_PREDICTION ) @@ -905,7 +912,7 @@ void CPrediction::RunCommand( C_BasePlayer *player, CUserCmd *ucmd, IMoveHelper C_BaseCombatWeapon *weapon = dynamic_cast< C_BaseCombatWeapon * >( CBaseEntity::Instance( ucmd->weaponselect ) ); if ( weapon ) { - player->SelectItem( weapon->GetName(), ucmd->weaponsubtype ); + player->SelectItem( weapon->GetClassname(), ucmd->weaponsubtype ); } } @@ -960,6 +967,11 @@ void CPrediction::RunCommand( C_BasePlayer *player, CUserCmd *ucmd, IMoveHelper pVehicle->ProcessMovement( player, g_pMoveData ); } +#ifdef PLAYER_COMMAND_FIX + RunPostThink( player ); + + FinishMove( player, ucmd, g_pMoveData ); +#else FinishMove( player, ucmd, g_pMoveData ); VPROF_SCOPE_BEGIN( "moveHelper->ProcessImpacts(cl)" ); @@ -967,6 +979,7 @@ void CPrediction::RunCommand( C_BasePlayer *player, CUserCmd *ucmd, IMoveHelper VPROF_SCOPE_END(); RunPostThink( player ); +#endif g_pGameMovement->FinishTrackPredictionErrors( player ); diff --git a/src/game/client/proxyplayer.cpp b/src/game/client/proxyplayer.cpp index a1b55572..8b5e56d1 100644 --- a/src/game/client/proxyplayer.cpp +++ b/src/game/client/proxyplayer.cpp @@ -12,6 +12,9 @@ #include "materialsystem/imaterialsystem.h" #include "functionproxy.h" #include "toolframework_client.h" +#ifdef MAPBASE +#include "view.h" +#endif // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" @@ -521,4 +524,100 @@ void CPlayerLogoOnModelProxy::OnBind( void *pC_BaseEntity ) } EXPOSE_INTERFACE( CPlayerLogoOnModelProxy, IMaterialProxy, "PlayerLogoOnModel" IMATERIAL_PROXY_INTERFACE_VERSION ); -*/ \ No newline at end of file +*/ + + +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Returns the proximity of the current view to the entity +//----------------------------------------------------------------------------- +class CViewProximityProxy : public CResultProxy +{ +public: + bool Init( IMaterial *pMaterial, KeyValues *pKeyValues ); + void OnBind( void *pC_BaseEntity ); + +private: + float m_Factor; +}; + +bool CViewProximityProxy::Init( IMaterial *pMaterial, KeyValues *pKeyValues ) +{ + if (!CResultProxy::Init( pMaterial, pKeyValues )) + return false; + + m_Factor = pKeyValues->GetFloat( "scale", 0.002 ); + return true; +} + +void CViewProximityProxy::OnBind( void *pC_BaseEntity ) +{ + if (!pC_BaseEntity) + return; + + // Find the distance between the player and this entity.... + C_BaseEntity *pEntity = BindArgToEntity( pC_BaseEntity ); + + Vector delta; + VectorSubtract( pEntity->WorldSpaceCenter(), CurrentViewOrigin(), delta ); + + Assert( m_pResult ); + SetFloatResult( delta.Length() * m_Factor ); + + if ( ToolsEnabled() ) + { + ToolFramework_RecordMaterialParams( GetMaterial() ); + } +} + +EXPOSE_INTERFACE( CViewProximityProxy, IMaterialProxy, "ViewProximity" IMATERIAL_PROXY_INTERFACE_VERSION ); + +//----------------------------------------------------------------------------- +// Returns the current view direction +//----------------------------------------------------------------------------- +class CViewDirectionProxy : public CResultProxy +{ +public: + bool Init( IMaterial *pMaterial, KeyValues *pKeyValues ); + void OnBind( void *pC_BaseEntity ); + +private: + float m_Factor; +}; + +bool CViewDirectionProxy::Init( IMaterial *pMaterial, KeyValues *pKeyValues ) +{ + if (!CResultProxy::Init( pMaterial, pKeyValues )) + return false; + + m_Factor = pKeyValues->GetFloat( "scale", 2 ); + return true; +} + +void CViewDirectionProxy::OnBind( void *pC_BaseEntity ) +{ + if (!pC_BaseEntity) + return; + + // Find the view angle between the player and this entity.... + C_BaseEntity *pEntity = BindArgToEntity( pC_BaseEntity ); + + Vector delta; + Vector forward; + + VectorSubtract( pEntity->WorldSpaceCenter(), CurrentViewOrigin(), delta ); + VectorNormalize( delta ); + + forward = CurrentViewForward(); + + Assert( m_pResult ); + SetFloatResult( DotProduct( forward, delta ) * m_Factor ); + + if ( ToolsEnabled() ) + { + ToolFramework_RecordMaterialParams( GetMaterial() ); + } +} + +EXPOSE_INTERFACE( CViewDirectionProxy, IMaterialProxy, "ViewDirection" IMATERIAL_PROXY_INTERFACE_VERSION ); +#endif diff --git a/src/game/client/ragdoll.cpp b/src/game/client/ragdoll.cpp index ed423a72..91fa866e 100644 --- a/src/game/client/ragdoll.cpp +++ b/src/game/client/ragdoll.cpp @@ -69,6 +69,16 @@ BEGIN_SIMPLE_DATADESC( CRagdoll ) DEFINE_RAGDOLL_ELEMENT( 21 ), DEFINE_RAGDOLL_ELEMENT( 22 ), DEFINE_RAGDOLL_ELEMENT( 23 ), +#ifdef MAPBASE + DEFINE_RAGDOLL_ELEMENT( 24 ), + DEFINE_RAGDOLL_ELEMENT( 25 ), + DEFINE_RAGDOLL_ELEMENT( 26 ), + DEFINE_RAGDOLL_ELEMENT( 27 ), + DEFINE_RAGDOLL_ELEMENT( 28 ), + DEFINE_RAGDOLL_ELEMENT( 29 ), + DEFINE_RAGDOLL_ELEMENT( 30 ), + DEFINE_RAGDOLL_ELEMENT( 31 ), +#endif END_DATADESC() @@ -478,6 +488,9 @@ int C_ServerRagdoll::InternalDrawModel( int flags ) return ret; } +#ifdef MAPBASE +static ConVar g_ragdoll_server_snatch_instance( "g_ragdoll_server_snatch_instance", "1", FCVAR_NONE, "Allows serverside ragdolls to snatch their source entities' model instances in the same way clientside ragdolls do, thereby retaining decals." ); +#endif CStudioHdr *C_ServerRagdoll::OnNewModel( void ) { @@ -500,6 +513,26 @@ CStudioHdr *C_ServerRagdoll::OnNewModel( void ) m_iv_ragAngles.SetMaxCount( m_elementCount ); } +#ifdef MAPBASE + if ( GetOwnerEntity() ) + { + if (GetOwnerEntity()->GetModelName() == GetModelName()) + { + // TODO: Is there a better place for this? + if (GetOwnerEntity()->GetBaseAnimating()) + GetOwnerEntity()->GetBaseAnimating()->m_pServerRagdoll = this; + + if (g_ragdoll_server_snatch_instance.GetBool()) + { + GetOwnerEntity()->SnatchModelInstance( this ); + } + } + } + + // Add server ragdolls to the creation tick list + NoteRagdollCreationTick( this ); +#endif + return hdr; } diff --git a/src/game/client/text_message.cpp b/src/game/client/text_message.cpp index 2251556b..33bef81f 100644 --- a/src/game/client/text_message.cpp +++ b/src/game/client/text_message.cpp @@ -121,7 +121,7 @@ char *CHudTextMessage::BufferedLocaliseTextString( const char *msg ) char *CHudTextMessage::LookupString( const char *msg, int *msg_dest ) { if ( !msg ) - return ""; + return (char*)""; // '#' character indicates this is a reference to a string in titles.txt, and not the string itself if ( msg[0] == '#' ) diff --git a/src/game/client/vgui_movie_display.cpp b/src/game/client/vgui_movie_display.cpp new file mode 100644 index 00000000..7bc5bb62 --- /dev/null +++ b/src/game/client/vgui_movie_display.cpp @@ -0,0 +1,442 @@ +//========= Copyright © 1996-2009, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=====================================================================================// + +#include "cbase.h" +#include "c_vguiscreen.h" +#include "vgui_controls/Label.h" +#include "vgui_bitmappanel.h" +#include +#include "c_slideshow_display.h" +#include "ienginevgui.h" +#include "fmtstr.h" +#include "vgui_controls/ImagePanel.h" +#include +#include "video/ivideoservices.h" +#include "engine/IEngineSound.h" +#include "VGuiMatSurface/IMatSystemSurface.h" +#include "c_movie_display.h" + +// NOTE: This has to be the last file included! +#include "tier0/memdbgon.h" + +using namespace vgui; + +struct VideoPlaybackInfo_t +{ + VideoPlaybackInfo_t( void ) : + m_pMaterial ( NULL ), + m_nSourceHeight(0), m_nSourceWidth(0), + m_flU(0.0f),m_flV(0.0f) {} + + IMaterial *m_pMaterial; + int m_nSourceHeight, m_nSourceWidth; // Source movie's dimensions + float m_flU, m_flV; // U,V ranges for video on its sheet +}; + +//----------------------------------------------------------------------------- +// Control screen +//----------------------------------------------------------------------------- +class CMovieDisplayScreen : public CVGuiScreenPanel +{ + DECLARE_CLASS( CMovieDisplayScreen, CVGuiScreenPanel ); + +public: + CMovieDisplayScreen( vgui::Panel *parent, const char *panelName ); + ~CMovieDisplayScreen( void ); + + virtual void ApplySchemeSettings( IScheme *pScheme ); + + virtual bool Init( KeyValues* pKeyValues, VGuiScreenInitData_t* pInitData ); + virtual void OnTick( void ); + virtual void Paint( void ); + +private: + bool IsActive( void ); + + void SetupMovie( void ); + void UpdateMovie( void ); + bool BeginPlayback( const char *pFilename ); + void CalculatePlaybackDimensions( int nSrcWidth, int nSrcHeight ); + + inline void GetPanelPos( int &xpos, int &ypos ) + { + xpos = ( (float) ( GetWide() - m_nPlaybackWidth ) / 2 ); + ypos = ( (float) ( GetTall() - m_nPlaybackHeight ) / 2 ); + } + +private: + + // BINK playback info + IVideoMaterial *m_VideoMaterial; + VideoPlaybackInfo_t m_playbackInfo; + CHandle m_hVGUIScreen; + CHandle m_hScreenEntity; + + int m_nTextureId; + int m_nPlaybackHeight; // Playback dimensions (proper ration adjustments) + int m_nPlaybackWidth; + bool m_bBlackBackground; + bool m_bSlaved; + bool m_bInitialized; + bool m_bLastActiveState; // HACK: I'd rather get a real callback... + + // VGUI specifics + Label *m_pDisplayTextLabel; + + Color m_cDefault; + Color m_cInvisible; + + bool bIsAlreadyVisible; +}; + +DECLARE_VGUI_SCREEN_FACTORY( CMovieDisplayScreen, "movie_display_screen" ); + +CUtlVector g_MovieDisplays; + +//----------------------------------------------------------------------------- +// Constructor: +//----------------------------------------------------------------------------- +CMovieDisplayScreen::CMovieDisplayScreen( vgui::Panel *parent, const char *panelName ) +: BaseClass( parent, "CMovieDisplayScreen", vgui::scheme()->LoadSchemeFromFileEx( enginevgui->GetPanel( PANEL_CLIENTDLL ), "resource/MovieDisplayScreen.res", "MovieDisplayScreen" ) ) +{ + m_pDisplayTextLabel = new vgui::Label( this, "NumberDisplay", "testing!"); + + m_VideoMaterial = NULL; + m_nTextureId = -1; + m_bBlackBackground = true; + m_bSlaved = false; + m_bInitialized = false; + // Add ourselves to the global list of movie displays + g_MovieDisplays.AddToTail( this ); + //m_VideoMaterial->SetMuted(true); + m_bLastActiveState = IsActive(); +} + +//----------------------------------------------------------------------------- +// Purpose: Clean up the movie +//----------------------------------------------------------------------------- +CMovieDisplayScreen::~CMovieDisplayScreen( void ) +{ + if ( g_pVideo != NULL && m_VideoMaterial != NULL ) + { + g_pVideo->DestroyVideoMaterial( m_VideoMaterial ); + m_VideoMaterial = NULL; + } + + // Clean up our texture reference + g_pMatSystemSurface->DestroyTextureID( m_nTextureId ); + + // Remove ourselves from the global list of movie displays + g_MovieDisplays.FindAndRemove( this ); +} + +//----------------------------------------------------------------------------- +// Purpose: Setup our scheme +//----------------------------------------------------------------------------- +void CMovieDisplayScreen::ApplySchemeSettings( IScheme *pScheme ) +{ + assert( pScheme ); + + m_cDefault = Color( 255, 255, 255, 255 ); + m_cInvisible = Color( 0, 0, 0, 0 ); + + m_pDisplayTextLabel->SetFgColor( m_cDefault ); + m_pDisplayTextLabel->SetText( "" ); + m_pDisplayTextLabel->SetVisible( false ); +} + +//----------------------------------------------------------------------------- +// Initialization +//----------------------------------------------------------------------------- +bool CMovieDisplayScreen::Init( KeyValues* pKeyValues, VGuiScreenInitData_t* pInitData ) +{ + // Make sure we get ticked... + vgui::ivgui()->AddTickSignal( GetVPanel() ); + + if ( !BaseClass::Init( pKeyValues, pInitData ) ) + return false; + + // Save this for simplicity later on + m_hVGUIScreen = dynamic_cast( GetEntity() ); + if ( m_hVGUIScreen != NULL ) + { + // Also get the associated entity + m_hScreenEntity = dynamic_cast(m_hVGUIScreen->GetOwnerEntity()); + } + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Helper function to check our active state +//----------------------------------------------------------------------------- +bool CMovieDisplayScreen::IsActive( void ) +{ + bool bScreenActive = false; + if ( m_hVGUIScreen != NULL ) + { + bScreenActive = m_hVGUIScreen->IsActive(); + } + + return bScreenActive; +} + +//----------------------------------------------------------------------------- +// Purpose: Either become the master of a group of screens, or become a slave to another +//----------------------------------------------------------------------------- +void CMovieDisplayScreen::SetupMovie( void ) +{ + // Only bother if we haven't been setup yet + if ( m_bInitialized ) + return; + + const char *szGroupName = m_hScreenEntity->GetGroupName(); + + CMovieDisplayScreen *pMasterScreen = NULL; + for ( int i = 0; i < g_MovieDisplays.Count(); i++ ) + { + // Must be valid and not us + if ( g_MovieDisplays[i] == NULL || g_MovieDisplays[i] == this ) + continue; + + // Must have an associated movie entity + if ( g_MovieDisplays[i]->m_hScreenEntity == NULL ) + continue; + + // Must have a group name to care + if ( szGroupName[0] == NULL ) + continue; + + // Group names must match! + // FIXME: Use an ID instead? + const char *szTestGroupName = g_MovieDisplays[i]->m_hScreenEntity->GetGroupName(); + if ( Q_strnicmp( szTestGroupName, szGroupName, 128 ) ) + continue; + + // See if we've found a master display + if ( g_MovieDisplays[i]->m_bInitialized && g_MovieDisplays[i]->m_bSlaved == false ) + { + m_bSlaved = true; + + // Share the info from the master + m_playbackInfo = g_MovieDisplays[i]->m_playbackInfo; + + // We need to calculate our own playback dimensions as we may be a different size than our parent + CalculatePlaybackDimensions( m_playbackInfo.m_nSourceWidth, m_playbackInfo.m_nSourceHeight ); + + // Bind our texture + m_nTextureId = surface()->CreateNewTextureID( true ); + g_pMatSystemSurface->DrawSetTextureMaterial( m_nTextureId, m_playbackInfo.m_pMaterial ); + + // Hold this as the master screen + pMasterScreen = g_MovieDisplays[i]; + break; + } + } + + // We need to try again, we have no screen entity! + if ( m_hScreenEntity == NULL ) + return; + + // No master found, become one + if ( pMasterScreen == NULL ) + { + const char *szFilename = m_hScreenEntity->GetMovieFilename(); + BeginPlayback( szFilename ); + m_bSlaved = false; + } + + // Done + m_bInitialized = true; +} + +//----------------------------------------------------------------------------- +// Purpose: Deal with the details of the video playback +//----------------------------------------------------------------------------- +void CMovieDisplayScreen::UpdateMovie( void ) +{ + // Only the master in a group updates the bink file + if ( m_bSlaved ) + return; + + if ( m_VideoMaterial == NULL ) + return; + + // Get the current activity state of the screen + bool bScreenActive = IsActive(); + + // Pause if the game has paused + if ( engine->IsPaused() || engine->Con_IsVisible() ) + { + bScreenActive = false; + } + + // See if we've changed our activity state + if ( bScreenActive != m_bLastActiveState ) + { + m_VideoMaterial->SetPaused( !bScreenActive ); + } + + // Updated + m_bLastActiveState = bScreenActive; + + // Update the frame if we're currently enabled + if ( bScreenActive ) + { + // Update our frame + if ( m_VideoMaterial->Update() == false ) + { + // Issue a close command + // OnVideoOver(); + // StopPlayback(); + } + + if (!m_hScreenEntity->IsMuted()) + { + m_VideoMaterial->SetMuted(false); + } + } +} + +//----------------------------------------------------------------------------- +// Update the display string +//----------------------------------------------------------------------------- +void CMovieDisplayScreen::OnTick() +{ + BaseClass::OnTick(); + + // Create our playback or slave to another screen already playing + SetupMovie(); + + // Now update the movie + UpdateMovie(); +} + +//----------------------------------------------------------------------------- +// Purpose: Adjust the playback dimensions to properly account for our screen dimensions +//----------------------------------------------------------------------------- +void CMovieDisplayScreen::CalculatePlaybackDimensions( int nSrcWidth, int nSrcHeight ) +{ + float flFrameRatio = ( (float) GetWide() / (float) GetTall() ); + float flVideoRatio = ( (float) nSrcWidth / (float) nSrcHeight ); + + if ( flVideoRatio > flFrameRatio ) + { + m_nPlaybackWidth = GetWide(); + m_nPlaybackHeight = ( GetWide() / flVideoRatio ); + } + else if ( flVideoRatio < flFrameRatio ) + { + m_nPlaybackWidth = ( GetTall() * flVideoRatio ); + m_nPlaybackHeight = GetTall(); + } + else + { + m_nPlaybackWidth = GetWide(); + m_nPlaybackHeight = GetTall(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Begins playback of a movie +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CMovieDisplayScreen::BeginPlayback( const char *pFilename ) +{ + // need working video services + if ( g_pVideo == NULL ) + return false; + + // Create a new video material + if ( m_VideoMaterial != NULL ) + { + g_pVideo->DestroyVideoMaterial( m_VideoMaterial ); + m_VideoMaterial = NULL; + } + + // Create a globally unique name for this material + char szMaterialName[256]; + + // Append our group name if we have one + const char *szGroupName = m_hScreenEntity->GetGroupName(); + if ( szGroupName[0] != NULL ) + { + Q_snprintf( szMaterialName, sizeof(szMaterialName), "%s_%s", pFilename, szGroupName ); + } + else + { + Q_snprintf( szMaterialName, sizeof(szMaterialName), "%s_%s", pFilename, m_hScreenEntity->GetEntityName() ); + } + + m_VideoMaterial = g_pVideo->CreateVideoMaterial( szMaterialName, pFilename, "GAME", + VideoPlaybackFlags::DEFAULT_MATERIAL_OPTIONS, + VideoSystem::DETERMINE_FROM_FILE_EXTENSION/*, m_bAllowAlternateMedia*/ ); + + if ( m_VideoMaterial == NULL ) + return false; + + + + m_VideoMaterial->SetMuted(true); // FIXME: Allow? + + + if ( m_hScreenEntity->IsLooping() ) + { + m_VideoMaterial->SetLooping( true ); + } + + if ( m_VideoMaterial->HasAudio()) + { + // We want to be the sole audio source + enginesound->NotifyBeginMoviePlayback(); + } + + // Get our basic info from the movie + m_VideoMaterial->GetVideoImageSize( &m_playbackInfo.m_nSourceWidth, &m_playbackInfo.m_nSourceHeight ); + m_VideoMaterial->GetVideoTexCoordRange( &m_playbackInfo.m_flU, &m_playbackInfo.m_flV ); + m_playbackInfo.m_pMaterial = m_VideoMaterial->GetMaterial(); + + // Get our playback dimensions + CalculatePlaybackDimensions( m_playbackInfo.m_nSourceWidth, m_playbackInfo.m_nSourceHeight ); + + // Bind our texture + m_nTextureId = surface()->CreateNewTextureID( true ); + g_pMatSystemSurface->DrawSetTextureMaterial( m_nTextureId, m_playbackInfo.m_pMaterial ); + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Update and draw the frame +//----------------------------------------------------------------------------- +void CMovieDisplayScreen::Paint( void ) +{ + // Masters must keep the video updated + if ( m_bSlaved == false && m_VideoMaterial == NULL ) + { + BaseClass::Paint(); + return; + } + + // Sit in the "center" + int xpos, ypos; + GetPanelPos( xpos, ypos ); + + // Black out the background (we could omit drawing under the video surface, but this is straight-forward) + if ( m_bBlackBackground ) + { + surface()->DrawSetColor( 0, 0, 0, 255 ); + surface()->DrawFilledRect( 0, 0, GetWide(), GetTall() ); + } + + // Draw it + surface()->DrawSetTexture( m_nTextureId ); + surface()->DrawSetColor( 255, 255, 255, 255 ); + surface()->DrawTexturedSubRect( xpos, ypos, xpos+m_nPlaybackWidth, ypos+m_nPlaybackHeight, 0.0f, 0.0f, m_playbackInfo.m_flU, m_playbackInfo.m_flV ); + + // Parent's turn + BaseClass::Paint(); +} diff --git a/src/game/client/vgui_video.cpp b/src/game/client/vgui_video.cpp index 75041b8a..13b16bd9 100644 --- a/src/game/client/vgui_video.cpp +++ b/src/game/client/vgui_video.cpp @@ -16,15 +16,59 @@ // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" +using namespace vgui; + +static CUtlVector< VideoPanel * > g_vecVideoPanels; + +// Thiis is a hack due to the fact that the user can type quit with the video panel up, but it's parented to the GameUI dll root panel, which is already gone so +// we would crash in the destructor +void VGui_ClearVideoPanels() +{ + for ( int i = g_vecVideoPanels.Count() - 1; i >= 0; --i ) + { + if ( g_vecVideoPanels[ i ] ) + { + delete g_vecVideoPanels[ i ]; + } + } + g_vecVideoPanels.RemoveAll(); +} + +struct VideoPanelParms_t +{ + VideoPanelParms_t( bool _interrupt = true, bool _loop = false, bool _mute = false ) + { + bAllowInterrupt = _interrupt; + bLoop = _loop; + bMute = _mute; + } + + bool bAllowInterrupt; + bool bLoop; + bool bMute; + + //float flFadeIn; + //float flFadeOut; +}; VideoPanel::VideoPanel( unsigned int nXPos, unsigned int nYPos, unsigned int nHeight, unsigned int nWidth, bool allowAlternateMedia ) : BaseClass( NULL, "VideoPanel" ), m_VideoMaterial( NULL ), m_nPlaybackWidth( 0 ), m_nPlaybackHeight( 0 ), - m_bAllowAlternateMedia( allowAlternateMedia ) + m_nShutdownCount( 0 ), + m_bLooping( false ), + m_bStopAllSounds( true ), + m_bAllowInterruption( true ), + m_bAllowAlternateMedia( allowAlternateMedia ), + m_bStarted( false ) { +#ifdef MAPBASE + vgui::VPANEL pParent = enginevgui->GetPanel( PANEL_ROOT ); +#else vgui::VPANEL pParent = enginevgui->GetPanel( PANEL_GAMEUIDLL ); +#endif + SetParent( pParent ); SetVisible( false ); @@ -48,6 +92,11 @@ VideoPanel::VideoPanel( unsigned int nXPos, unsigned int nYPos, unsigned int nHe SetScheme(vgui::scheme()->LoadSchemeFromFile( "resource/VideoPanelScheme.res", "VideoPanelScheme")); LoadControlSettings("resource/UI/VideoPanel.res"); + + // Let us update + vgui::ivgui()->AddTickSignal( GetVPanel() ); + + g_vecVideoPanels.AddToTail( this ); } //----------------------------------------------------------------------------- @@ -55,6 +104,8 @@ VideoPanel::VideoPanel( unsigned int nXPos, unsigned int nYPos, unsigned int nHe //----------------------------------------------------------------------------- VideoPanel::~VideoPanel( void ) { + g_vecVideoPanels.FindAndRemove( this ); + SetParent( (vgui::Panel *) NULL ); // Shut down this video, destroy the video material @@ -65,13 +116,39 @@ VideoPanel::~VideoPanel( void ) } } +//----------------------------------------------------------------------------- +// Purpose: Keeps a tab on when the movie is ending and allows a frame to pass to prevent threading issues +//----------------------------------------------------------------------------- +void VideoPanel::OnTick( void ) +{ + if ( m_nShutdownCount > 0 ) + { + m_nShutdownCount++; + + if ( m_nShutdownCount > 10 ) + { + OnClose(); + m_nShutdownCount = 0; + } + } + + BaseClass::OnTick(); +} + +void VideoPanel::OnVideoOver() +{ + StopPlayback(); +} + //----------------------------------------------------------------------------- // Purpose: Begins playback of a movie // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- bool VideoPanel::BeginPlayback( const char *pFilename ) { - // Who the heck hacked this in? + if ( !pFilename || pFilename[ 0 ] == '\0' ) + return false; + #ifdef _X360 XVIDEO_MODE videoMode; XGetVideoMode( &videoMode ); @@ -101,9 +178,25 @@ bool VideoPanel::BeginPlayback( const char *pFilename ) if ( m_VideoMaterial == NULL ) return false; + if ( m_bLooping ) + { + m_VideoMaterial->SetLooping( true ); + } + +#ifdef MAPBASE + if ( m_bMuted ) + { + m_VideoMaterial->SetMuted( true ); + } +#endif + + m_bStarted = true; + // We want to be the sole audio source - // FIXME: This may not always be true! - enginesound->NotifyBeginMoviePlayback(); + if ( m_bStopAllSounds ) + { + enginesound->NotifyBeginMoviePlayback(); + } int nWidth, nHeight; m_VideoMaterial->GetVideoImageSize( &nWidth, &nHeight ); @@ -163,9 +256,10 @@ void VideoPanel::DoModal( void ) //----------------------------------------------------------------------------- void VideoPanel::OnKeyCodeTyped( vgui::KeyCode code ) { - if ( code == KEY_ESCAPE ) + bool bInterruptKeyPressed = ( code == KEY_ESCAPE ); + if ( m_bAllowInterruption && bInterruptKeyPressed ) { - OnClose(); + StopPlayback(); } else { @@ -176,36 +270,56 @@ void VideoPanel::OnKeyCodeTyped( vgui::KeyCode code ) //----------------------------------------------------------------------------- // Purpose: Handle keys that should cause us to close //----------------------------------------------------------------------------- -void VideoPanel::OnKeyCodePressed( vgui::KeyCode code ) +void VideoPanel::OnKeyCodePressed( vgui::KeyCode keycode ) { + vgui::KeyCode code = GetBaseButtonCode( keycode ); + + // All these keys will interrupt playback + bool bInterruptKeyPressed = ( code == KEY_ESCAPE || + code == KEY_BACKQUOTE || + code == KEY_SPACE || + code == KEY_ENTER || + code == KEY_XBUTTON_A || + code == KEY_XBUTTON_B || + code == KEY_XBUTTON_X || + code == KEY_XBUTTON_Y || + code == KEY_XBUTTON_START || + code == KEY_XBUTTON_BACK || + code == STEAMCONTROLLER_A || + code == STEAMCONTROLLER_B ); + // These keys cause the panel to shutdown - if ( code == KEY_ESCAPE || - code == KEY_BACKQUOTE || - code == KEY_SPACE || - code == KEY_ENTER || - code == KEY_XBUTTON_A || - code == KEY_XBUTTON_B || - code == KEY_XBUTTON_X || - code == KEY_XBUTTON_Y || - code == KEY_XBUTTON_START || - code == KEY_XBUTTON_BACK || - code == STEAMCONTROLLER_A || - code == STEAMCONTROLLER_B ) + if ( m_bAllowInterruption && bInterruptKeyPressed ) { OnClose(); } else { - BaseClass::OnKeyCodePressed( code ); + BaseClass::OnKeyCodePressed( keycode ); } } +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void VideoPanel::StopPlayback( void ) +{ + SetVisible( false ); + + // Start the deferred shutdown process + m_nShutdownCount = 1; +} + //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void VideoPanel::OnClose( void ) { - enginesound->NotifyEndMoviePlayback(); + if ( m_bStopAllSounds ) + { + enginesound->NotifyEndMoviePlayback(); + } + BaseClass::OnClose(); if ( vgui::input()->GetAppModalSurface() == GetVPanel() ) @@ -221,7 +335,6 @@ void VideoPanel::OnClose( void ) engine->ClientCmd( m_szExitCommand ); } - SetVisible( false ); MarkForDeletion(); } @@ -244,26 +357,52 @@ void VideoPanel::Paint( void ) if ( m_VideoMaterial == NULL ) return; + float alpha = ((float)GetFgColor()[3]/255.0f); +#ifdef MAPBASE + if (m_flFadeIn != 0.0f || m_flFadeOut != 0.0f) + { + // GetCurrentVideoTime() and GetVideoDuration() are borked + float flFrameCount = m_VideoMaterial->GetFrameCount(); + float flEnd = flFrameCount / m_VideoMaterial->GetVideoFrameRate().GetFPS(); + float flTime = ((float)(m_VideoMaterial->GetCurrentFrame()) / flFrameCount) * flEnd; + float flFadeOutDelta = (flEnd - m_flFadeOut); + + if (flTime <= m_flFadeIn) + { + alpha = (flTime / m_flFadeIn); + } + else if (flTime >= flFadeOutDelta) + { + alpha = (1.0f - ((flTime - flFadeOutDelta) / m_flFadeOut)); + } + } +#endif + if ( m_VideoMaterial->Update() == false ) { // Issue a close command OnVideoOver(); - OnClose(); + //OnClose(); } // Sit in the "center" int xpos, ypos; GetPanelPos( xpos, ypos ); + LocalToScreen( xpos, ypos ); // Black out the background (we could omit drawing under the video surface, but this is straight-forward) if ( m_bBlackBackground ) { - vgui::surface()->DrawSetColor( 0, 0, 0, 255 ); + vgui::surface()->DrawSetColor( 0, 0, 0, alpha * 255.0f ); vgui::surface()->DrawFilledRect( 0, 0, GetWide(), GetTall() ); } // Draw the polys to draw this out CMatRenderContextPtr pRenderContext( materials ); + +#ifdef MAPBASE + pRenderContext->ClearColor4ub( 255, 255, 255, alpha * 255.0f ); +#endif pRenderContext->MatrixMode( MATERIAL_VIEW ); pRenderContext->PushMatrix(); @@ -303,8 +442,6 @@ void VideoPanel::Paint( void ) flTopY = FLerp( 1, -1, 0, vh ,flTopY ); flBottomY = FLerp( 1, -1, 0, vh, flBottomY ); - float alpha = ((float)GetFgColor()[3]/255.0f); - for ( int corner=0; corner<4; corner++ ) { bool bLeft = (corner==0) || (corner==3); @@ -337,16 +474,37 @@ void VideoPanel::Paint( void ) bool VideoPanel_Create( unsigned int nXPos, unsigned int nYPos, unsigned int nWidth, unsigned int nHeight, const char *pVideoFilename, - const char *pExitCommand /*= NULL*/) + const char *pExitCommand /*= NULL*/, + const VideoPanelParms_t &parms ) { // Create the base video panel - VideoPanel *pVideoPanel = new VideoPanel( nXPos, nYPos, nHeight, nWidth, false ); + VideoPanel *pVideoPanel = new VideoPanel( nXPos, nYPos, nHeight, nWidth ); if ( pVideoPanel == NULL ) return false; + // Toggle if we want the panel to allow interruption + pVideoPanel->SetAllowInterrupt( parms.bAllowInterrupt ); + // Set the command we'll call (if any) when the video is interrupted or completes pVideoPanel->SetExitCommand( pExitCommand ); +#ifdef MAPBASE + // Toggle if we want the panel to loop (inspired by Portal 2) + pVideoPanel->SetLooping( parms.bLoop ); + + // Toggle if we want the panel to be muted + pVideoPanel->SetMuted( parms.bMute ); + + // TODO: Unique "Stop All Sounds" parameter + if (parms.bMute) + { + pVideoPanel->SetStopAllSounds( false ); + } + + // Fade parameters (unfinished) + //pVideoPanel->SetFade( parms.flFadeIn, parms.flFadeOut ); +#endif + // Start it going if ( pVideoPanel->BeginPlayback( pVideoFilename ) == false ) { @@ -362,8 +520,29 @@ bool VideoPanel_Create( unsigned int nXPos, unsigned int nYPos, } //----------------------------------------------------------------------------- -// Purpose: Used to launch a video playback (Debug) - -// user must include file extension +// Purpose: Create a video panel with the supplied commands +//----------------------------------------------------------------------------- +void CreateVideoPanel( const char *lpszFilename, const char *lpszExitCommand, int nWidth, int nHeight, VideoPanelParms_t &parms ) +{ + char strFullpath[MAX_PATH]; + Q_strncpy( strFullpath, "media/", MAX_PATH ); // Assume we must play out of the media directory + char strFilename[MAX_PATH]; + Q_StripExtension( lpszFilename, strFilename, MAX_PATH ); + Q_strncat( strFullpath, lpszFilename, MAX_PATH ); + + // Use the full screen size if they haven't specified an override + unsigned int nScreenWidth = ( nWidth != 0 ) ? nWidth : ScreenWidth(); + unsigned int nScreenHeight = ( nHeight != 0 ) ? nHeight : ScreenHeight(); + + // Create the panel and go! + if ( VideoPanel_Create( 0, 0, nScreenWidth, nScreenHeight, strFullpath, lpszExitCommand, parms ) == false ) + { + Warning( "Unable to play video: %s\n", strFullpath ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Used to launch a video playback //----------------------------------------------------------------------------- CON_COMMAND( playvideo, "Plays a video: [width height]" ) @@ -373,30 +552,32 @@ CON_COMMAND( playvideo, "Plays a video: [width height]" ) unsigned int nScreenWidth = Q_atoi( args[2] ); unsigned int nScreenHeight = Q_atoi( args[3] ); - - char strFullpath[MAX_PATH]; - Q_strncpy( strFullpath, "media/", MAX_PATH ); // Assume we must play out of the media directory - char strFilename[MAX_PATH]; - Q_StripExtension( args[1], strFilename, MAX_PATH ); - Q_strncat( strFullpath, args[1], MAX_PATH ); - - if ( nScreenWidth == 0 ) - { - nScreenWidth = ScreenWidth(); - } - - if ( nScreenHeight == 0 ) - { - nScreenHeight = ScreenHeight(); - } - // Create the panel and go! - if ( VideoPanel_Create( 0, 0, nScreenWidth, nScreenHeight, strFullpath ) == false ) - { - Warning( "Unable to play video: %s\n", strFullpath ); - } + // New struct; functionally identical + VideoPanelParms_t parms; + + CreateVideoPanel( args[1], NULL, nScreenWidth, nScreenHeight, parms ); } +//----------------------------------------------------------------------------- +// Purpose: Used to launch a video playback +//----------------------------------------------------------------------------- + +CON_COMMAND( playvideo_nointerrupt, "Plays a video without ability to skip: [width height]" ) +{ + if ( args.ArgC() < 2 ) + return; + + unsigned int nScreenWidth = Q_atoi( args[2] ); + unsigned int nScreenHeight = Q_atoi( args[3] ); + + // New struct; functionally identical + VideoPanelParms_t parms( false ); + + CreateVideoPanel( args[1], NULL, nScreenWidth, nScreenHeight, parms ); +} + + //----------------------------------------------------------------------------- // Purpose: Used to launch a video playback and fire a command on completion //----------------------------------------------------------------------------- @@ -406,21 +587,78 @@ CON_COMMAND( playvideo_exitcommand, "Plays a video and fires and exit command wh if ( args.ArgC() < 2 ) return; - unsigned int nScreenWidth = ScreenWidth(); - unsigned int nScreenHeight = ScreenHeight(); - - char strFullpath[MAX_PATH]; - Q_strncpy( strFullpath, "media/", MAX_PATH ); // Assume we must play out of the media directory - char strFilename[MAX_PATH]; - Q_StripExtension( args[1], strFilename, MAX_PATH ); - Q_strncat( strFullpath, args[1], MAX_PATH ); - + // Pull out the exit command we want to use char *pExitCommand = Q_strstr( args.GetCommandString(), args[2] ); - // Create the panel and go! - if ( VideoPanel_Create( 0, 0, nScreenWidth, nScreenHeight, strFullpath, pExitCommand ) == false ) + // New struct; functionally identical + VideoPanelParms_t parms; + + CreateVideoPanel( args[1], pExitCommand, 0, 0, parms ); +} + +//----------------------------------------------------------------------------- +// Purpose: Used to launch a video playback and fire a command on completion +//----------------------------------------------------------------------------- + +CON_COMMAND( playvideo_exitcommand_nointerrupt, "Plays a video (without interruption) and fires and exit command when it is stopped or finishes: " ) +{ + if ( args.ArgC() < 2 ) + return; + + // Pull out the exit command we want to use + char *pExitCommand = Q_strstr( args.GetCommandString(), args[2] ); + + // New struct; functionally identical + VideoPanelParms_t parms( false ); + + CreateVideoPanel( args[1], pExitCommand, 0, 0, parms ); +} + +//----------------------------------------------------------------------------- +// Purpose: Cause all playback to stop +//----------------------------------------------------------------------------- + +CON_COMMAND( stopvideos, "Stops all videos playing to the screen" ) +{ + FOR_EACH_VEC( g_vecVideoPanels, itr ) { - Warning( "Unable to play video: %s\n", strFullpath ); - engine->ClientCmd( pExitCommand ); + g_vecVideoPanels[itr]->StopPlayback(); } -} \ No newline at end of file +} + +//----------------------------------------------------------------------------- +// Purpose: Used to launch a video playback and fire a command on completion +//----------------------------------------------------------------------------- + +CON_COMMAND( playvideo_complex, "Plays a video with various parameters to simplify logic_playmovie: " ) +{ + if ( args.ArgC() < 2 ) + return; + + // Pull out the exit command we want to use + char *pExitCommand = Q_strstr( args.GetCommandString(), args[2] ); + + // Parameters + VideoPanelParms_t parms; + + if (args.ArgC() >= 3) + parms.bAllowInterrupt = atoi( args[3] ) != 0; + if (args.ArgC() >= 4) + parms.bLoop = atoi( args[4] ) != 0; + if (args.ArgC() >= 5) + parms.bMute = atoi( args[5] ) != 0; + + //if (args.ArgC() >= 5) + // parms.flFadeIn = atof( args[5] ); + //if (args.ArgC() >= 6) + // parms.flFadeOut = atof( args[6] ); + + // Stop a softlock + if (parms.bAllowInterrupt == false && parms.bLoop) + { + Warning( "WARNING: Tried to play video set to be uninterruptible and looping. This would cause a softlock because the video loops forever and there's no way to stop it.\n" ); + return; + } + + CreateVideoPanel( args[1], pExitCommand, 0, 0, parms ); +} diff --git a/src/game/client/vgui_video.h b/src/game/client/vgui_video.h index 34f93afa..5e8a46f7 100644 --- a/src/game/client/vgui_video.h +++ b/src/game/client/vgui_video.h @@ -45,14 +45,22 @@ public: } virtual bool BeginPlayback( const char *pFilename ); + void StopPlayback( void ); void SetBlackBackground( bool bBlack ){ m_bBlackBackground = bBlack; } + void SetAllowInterrupt( bool bAllowInterrupt ) { m_bAllowInterruption = bAllowInterrupt; } + void SetStopAllSounds( bool bStopAllSounds ) { m_bStopAllSounds = bStopAllSounds; } +#ifdef MAPBASE + void SetLooping( bool bLooping ) { m_bLooping = bLooping; } + void SetMuted( bool bMuted ) { m_bMuted = bMuted; } + void SetFade( float flStartFade, float flEndFade ) { m_flFadeIn = flStartFade; m_flFadeOut = flEndFade; } +#endif protected: - virtual void OnTick( void ) { BaseClass::OnTick(); } + virtual void OnTick( void ); virtual void OnCommand( const char *pcCommand ) { BaseClass::OnCommand( pcCommand ); } - virtual void OnVideoOver(){} + virtual void OnVideoOver(); protected: IVideoMaterial *m_VideoMaterial; @@ -65,8 +73,19 @@ protected: float m_flU; // U,V ranges for video on its sheet float m_flV; + bool m_bLooping; +#ifdef MAPBASE + float m_flFadeIn; + float m_flFadeOut; + bool m_bMuted; +#endif + bool m_bStopAllSounds; + bool m_bAllowInterruption; bool m_bBlackBackground; bool m_bAllowAlternateMedia; + int m_nShutdownCount; + + bool m_bStarted; }; diff --git a/src/game/client/view.cpp b/src/game/client/view.cpp index de0c0b07..61dd611a 100644 --- a/src/game/client/view.cpp +++ b/src/game/client/view.cpp @@ -105,10 +105,11 @@ extern ConVar cl_forwardspeed; static ConVar v_centermove( "v_centermove", "0.15"); static ConVar v_centerspeed( "v_centerspeed","500" ); -#ifdef TF_CLIENT_DLL +#if defined(TF_CLIENT_DLL) || defined(MAPBASE) // 54 degrees approximates a 35mm camera - we determined that this makes the viewmodels // and motions look the most natural. ConVar v_viewmodel_fov( "viewmodel_fov", "54", FCVAR_ARCHIVE, "Sets the field-of-view for the viewmodel.", true, 0.1, true, 179.9, true, 54, true, 70, NULL ); +ConVar v_viewmodel_fov_script_override( "viewmodel_fov_script_override", "0", FCVAR_NONE, "If nonzero, overrides the viewmodel FOV of weapon scripts which override the viewmodel FOV." ); #else ConVar v_viewmodel_fov( "viewmodel_fov", "54", FCVAR_CHEAT, "Sets the field-of-view for the viewmodel.", true, 0.1, true, 179.9 ); #endif @@ -124,6 +125,9 @@ ConVar gl_clear( "gl_clear", "0"); ConVar gl_clear_randomcolor( "gl_clear_randomcolor", "0", FCVAR_CHEAT, "Clear the back buffer to random colors every frame. Helps spot open seams in geometry." ); static ConVar r_farz( "r_farz", "-1", FCVAR_CHEAT, "Override the far clipping plane. -1 means to use the value in env_fog_controller." ); +#ifdef MAPBASE +static ConVar r_nearz( "r_nearz", "-1", FCVAR_CHEAT, "Override the near clipping plane. -1 means to use the default value (usually 7)." ); +#endif static ConVar cl_demoviewoverride( "cl_demoviewoverride", "0", 0, "Override view during demo playback" ); @@ -592,6 +596,11 @@ static QAngle s_DbgSetupAngles; //----------------------------------------------------------------------------- float CViewRender::GetZNear() { +#ifdef MAPBASE + if (r_nearz.GetFloat() > 0) + return r_nearz.GetFloat(); +#endif + return VIEW_NEARZ; } @@ -657,6 +666,10 @@ void CViewRender::SetUpViews() Vector ViewModelOrigin; QAngle ViewModelAngles; +#ifdef MAPBASE + viewEye.fovViewmodel = g_pClientMode->GetViewModelFOV(); +#endif + if ( engine->IsHLTV() ) { HLTVCamera()->CalcView( viewEye.origin, viewEye.angles, viewEye.fov ); @@ -692,6 +705,18 @@ void CViewRender::SetUpViews() bCalcViewModelView = true; ViewModelOrigin = viewEye.origin; ViewModelAngles = viewEye.angles; + +#ifdef MAPBASE + // Allow weapons to override viewmodel FOV + C_BaseCombatWeapon *pWeapon = pPlayer->GetActiveWeapon(); + if (pWeapon && pWeapon->GetViewmodelFOVOverride() != 0.0f) + { + if (v_viewmodel_fov_script_override.GetFloat() > 0.0f) + viewEye.fovViewmodel = v_viewmodel_fov_script_override.GetFloat(); + else + viewEye.fovViewmodel = pWeapon->GetViewmodelFOVOverride(); + } +#endif } else { @@ -726,7 +751,11 @@ void CViewRender::SetUpViews() float flFOVOffset = fDefaultFov - viewEye.fov; //Adjust the viewmodel's FOV to move with any FOV offsets on the viewer's end +#ifdef MAPBASE + viewEye.fovViewmodel = max(0.001f, viewEye.fovViewmodel - flFOVOffset); +#else viewEye.fovViewmodel = g_pClientMode->GetViewModelFOV() - flFOVOffset; +#endif if ( UseVR() ) { diff --git a/src/game/client/viewdebug.cpp b/src/game/client/viewdebug.cpp index 7b67c8f7..ba251135 100644 --- a/src/game/client/viewdebug.cpp +++ b/src/game/client/viewdebug.cpp @@ -29,6 +29,9 @@ static ConVar mat_wateroverlaysize( "mat_wateroverlaysize", "256" ); static ConVar mat_showframebuffertexture( "mat_showframebuffertexture", "0", FCVAR_CHEAT ); static ConVar mat_framebuffercopyoverlaysize( "mat_framebuffercopyoverlaysize", "256" ); static ConVar mat_showcamerarendertarget( "mat_showcamerarendertarget", "0", FCVAR_CHEAT ); +#ifdef MAPBASE +static ConVar mat_showcamerarendertarget_all( "mat_showcamerarendertarget_all", "0", FCVAR_CHEAT ); +#endif static ConVar mat_camerarendertargetoverlaysize( "mat_camerarendertargetoverlaysize", "256", FCVAR_CHEAT ); static ConVar mat_hsv( "mat_hsv", "0", FCVAR_CHEAT ); static ConVar mat_yuv( "mat_yuv", "0", FCVAR_CHEAT ); @@ -178,6 +181,11 @@ void OverlayCameraRenderTarget( const char *pszMaterialName, float flX, float fl pMaterial = materials->FindMaterial( pszMaterialName, TEXTURE_GROUP_OTHER, true ); if( !IsErrorMaterial( pMaterial ) ) { +#ifdef MAPBASE + // HACKHACK + pMaterial->IncrementReferenceCount(); +#endif + CMatRenderContextPtr pRenderContext( materials ); pRenderContext->Bind( pMaterial ); IMesh* pMesh = pRenderContext->GetDynamicMesh( true ); @@ -203,6 +211,11 @@ void OverlayCameraRenderTarget( const char *pszMaterialName, float flX, float fl meshBuilder.End(); pMesh->Draw(); + +#ifdef MAPBASE + // HACKHACK + pMaterial->DecrementReferenceCount(); +#endif } } @@ -214,7 +227,11 @@ static void OverlayFrameBufferTexture( int nFrameBufferIndex ) IMaterial *pMaterial; char buf[MAX_PATH]; Q_snprintf( buf, MAX_PATH, "debug/debugfbtexture%d", nFrameBufferIndex ); +#ifdef MAPBASE + pMaterial = materials->FindMaterial( buf, NULL, true ); +#else pMaterial = materials->FindMaterial( buf, TEXTURE_GROUP_OTHER, true ); +#endif if( !IsErrorMaterial( pMaterial ) ) { CMatRenderContextPtr pRenderContext( materials ); @@ -586,12 +603,52 @@ void CDebugViewRender::Draw2DDebuggingInfo( const CViewSetup &viewDebug ) if ( mat_showcamerarendertarget.GetBool() ) { +#ifdef MAPBASE + float w = mat_camerarendertargetoverlaysize.GetFloat(); + float h = mat_camerarendertargetoverlaysize.GetFloat(); +#else float w = mat_wateroverlaysize.GetFloat(); float h = mat_wateroverlaysize.GetFloat(); +#endif #ifdef PORTAL g_pPortalRender->OverlayPortalRenderTargets( w, h ); #else + +#ifdef MAPBASE + int iCameraNum = mat_showcamerarendertarget.GetInt(); + + if (iCameraNum == 1) // Display the default camera + { + OverlayCameraRenderTarget( "debug/debugcamerarendertarget", 0, 0, w, h ); + } + else if (mat_showcamerarendertarget_all.GetBool()) // Display all cameras + { + OverlayCameraRenderTarget( "debug/debugcamerarendertarget", 0, 0, w, h ); + + // Already showed one camera + iCameraNum--; + + // Show Mapbase's cameras + char szTextureName[48]; + for (int i = 0; i < iCameraNum; i++) + { + V_snprintf( szTextureName, sizeof( szTextureName ), "debug/debugcamerarendertarget_camera%i", i ); + + // Show them vertically if the cvar is set to 2 + if (mat_showcamerarendertarget_all.GetInt() == 2) + OverlayCameraRenderTarget( szTextureName, 0, h * (i + 1), w, h ); + else + OverlayCameraRenderTarget( szTextureName, w * (i + 1), 0, w, h ); + } + } + else // Display one of the new cameras + { + OverlayCameraRenderTarget( VarArgs( "debug/debugcamerarendertarget_camera%i", iCameraNum-2 ), 0, 0, w, h ); + } +#else OverlayCameraRenderTarget( "debug/debugcamerarendertarget", 0, 0, w, h ); +#endif + #endif } @@ -656,6 +713,57 @@ CON_COMMAND_F( r_screenoverlay, "Draw specified material as an overlay", FCVAR_C } } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// The same as above, but using the new indexed overlays +//----------------------------------------------------------------------------- +CON_COMMAND_F( r_screenoverlay_indexed, "Draw specified material as an overlay in the specified index", FCVAR_CHEAT|FCVAR_SERVER_CAN_EXECUTE ) +{ + if( args.ArgC() == 3 ) + { + int index = atoi( args[1] ); + if (index < 0 || index >= MAX_SCREEN_OVERLAYS) + { + Warning( "r_screenoverlay_indexed: '%i' is out of range (should be 0-9)\n", index ); + return; + } + + if ( !Q_stricmp( "off", args[2] ) ) + { + view->SetIndexedScreenOverlayMaterial( index, NULL ); + } + else + { + IMaterial *pMaterial = materials->FindMaterial( args[2], TEXTURE_GROUP_OTHER, false ); + if ( !IsErrorMaterial( pMaterial ) ) + { + view->SetIndexedScreenOverlayMaterial( index, pMaterial ); + } + else + { + view->SetIndexedScreenOverlayMaterial( index, NULL ); + } + } + } + else if ( args.ArgC() == 2 ) + { + int index = atoi( args[1] ); + if (index < 0 || index >= MAX_SCREEN_OVERLAYS) + { + Warning( "r_screenoverlay_indexed: '%i' is out of range (should be 0-9)\n", index ); + return; + } + + IMaterial *pMaterial = view->GetIndexedScreenOverlayMaterial( index ); + Warning( "r_screenoverlay_indexed %i: %s\n", index, pMaterial ? pMaterial->GetName() : "off" ); + } + else + { + Warning( "Format: r_screenoverlay_indexed []\n" ); + } +} +#endif + // Used to verify frame syncing. void CDebugViewRender::GenerateOverdrawForTesting() { diff --git a/src/game/client/viewpostprocess.cpp b/src/game/client/viewpostprocess.cpp index ac85ad86..fb614707 100644 --- a/src/game/client/viewpostprocess.cpp +++ b/src/game/client/viewpostprocess.cpp @@ -13,8 +13,13 @@ #include "materialsystem/materialsystem_config.h" #include "tier1/callqueue.h" #include "colorcorrectionmgr.h" +#include "postprocess_shared.h" #include "view_scene.h" #include "c_world.h" + +//Tony; new +#include "c_baseplayer.h" + #include "bitmap/tgawriter.h" #include "filesystem.h" #include "tier0/vprof.h" @@ -34,6 +39,15 @@ float g_flCustomAutoExposureMax = 0; float g_flCustomBloomScale = 0.0f; float g_flCustomBloomScaleMinimum = 0.0f; +// mapmaker controlled depth of field +bool g_bDOFEnabled = false; +float g_flDOFNearBlurDepth = 20.0f; +float g_flDOFNearFocusDepth = 100.0f; +float g_flDOFFarFocusDepth = 250.0f; +float g_flDOFFarBlurDepth = 1000.0f; +float g_flDOFNearBlurRadius = 0.0f; +float g_flDOFFarBlurRadius = 10.0f; + bool g_bFlashlightIsOn = false; // hdr parameters @@ -331,7 +345,7 @@ PostProcessingPass HDRSimulate_NonHDR[] = PPP_END }; -static void SetRenderTargetAndViewPort(ITexture *rt) +void SetRenderTargetAndViewPort(ITexture *rt) { tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ ); @@ -764,7 +778,19 @@ static float GetCurrentBloomScale( void ) { // Use the appropriate bloom scale settings. Mapmakers's overrides the convar settings. float flCurrentBloomScale = 1.0f; - if ( g_bUseCustomBloomScale ) + + //Tony; get the local player first.. + C_BasePlayer *pLocalPlayer = NULL; + + if ( ( gpGlobals->maxClients > 1 ) ) + pLocalPlayer = (C_BasePlayer*)C_BasePlayer::GetLocalPlayer(); + + //Tony; in multiplayer, get the local player etc. + if ( (pLocalPlayer != NULL && pLocalPlayer->m_Local.m_TonemapParams.m_flAutoExposureMin > 0.0f) ) + { + flCurrentBloomScale = pLocalPlayer->m_Local.m_TonemapParams.m_flAutoExposureMin; + } + else if ( g_bUseCustomBloomScale ) { flCurrentBloomScale = g_flCustomBloomScale; } @@ -777,8 +803,19 @@ static float GetCurrentBloomScale( void ) static void GetExposureRange( float *flAutoExposureMin, float *flAutoExposureMax ) { + //Tony; get the local player first.. + C_BasePlayer *pLocalPlayer = NULL; + + if ( ( gpGlobals->maxClients > 1 ) ) + pLocalPlayer = (C_BasePlayer*)C_BasePlayer::GetLocalPlayer(); + + //Tony; in multiplayer, get the local player etc. + if ( (pLocalPlayer != NULL && pLocalPlayer->m_Local.m_TonemapParams.m_flAutoExposureMin > 0.0f) ) + { + *flAutoExposureMin = pLocalPlayer->m_Local.m_TonemapParams.m_flAutoExposureMin; + } // Get min - if ( ( g_bUseCustomAutoExposureMin ) && ( g_flCustomAutoExposureMin > 0.0f ) ) + else if ( ( g_bUseCustomAutoExposureMin ) && ( g_flCustomAutoExposureMin > 0.0f ) ) { *flAutoExposureMin = g_flCustomAutoExposureMin; } @@ -787,8 +824,13 @@ static void GetExposureRange( float *flAutoExposureMin, float *flAutoExposureMax *flAutoExposureMin = mat_autoexposure_min.GetFloat(); } + //Tony; in multiplayer, get the value from the local player, if it's set. + if ( (pLocalPlayer != NULL && pLocalPlayer->m_Local.m_TonemapParams.m_flAutoExposureMax > 0.0f) ) + { + *flAutoExposureMax = pLocalPlayer->m_Local.m_TonemapParams.m_flAutoExposureMax; + } // Get max - if ( ( g_bUseCustomAutoExposureMax ) && ( g_flCustomAutoExposureMax > 0.0f ) ) + else if ( ( g_bUseCustomAutoExposureMax ) && ( g_flCustomAutoExposureMax > 0.0f ) ) { *flAutoExposureMax = g_flCustomAutoExposureMax; } @@ -1112,6 +1154,32 @@ void CLuminanceHistogramSystem::DisplayHistogram( void ) pRenderContext->PopRenderTargetAndViewport(); } +// Local contrast setting +PostProcessParameters_t s_LocalPostProcessParameters; + +// view fade param settings +static Vector4D s_viewFadeColor; +static bool s_bViewFadeModulate; + +static bool s_bOverridePostProcessParams = false; + +void SetPostProcessParams( const PostProcessParameters_t* pPostProcessParameters ) +{ + if (!s_bOverridePostProcessParams) + s_LocalPostProcessParameters = *pPostProcessParameters; +} + +void SetPostProcessParams( const PostProcessParameters_t* pPostProcessParameters, bool bOverride ) +{ + s_bOverridePostProcessParams = bOverride; + s_LocalPostProcessParameters = *pPostProcessParameters; +} + +void SetViewFadeParams( byte r, byte g, byte b, byte a, bool bModulate ) +{ + s_viewFadeColor.Init( float( r ) / 255.0f, float( g ) / 255.0f, float( b ) / 255.0f, float( a ) / 255.0f ); + s_bViewFadeModulate = bModulate; +} static CLuminanceHistogramSystem g_HDR_HistogramSystem; @@ -1207,15 +1275,32 @@ private: IMaterialVar *m_pMaterialParam_AAValues; IMaterialVar *m_pMaterialParam_AAValues2; IMaterialVar *m_pMaterialParam_BloomEnable; + IMaterialVar *m_pMaterialParam_BloomAmount; IMaterialVar *m_pMaterialParam_BloomUVTransform; IMaterialVar *m_pMaterialParam_ColCorrectEnable; IMaterialVar *m_pMaterialParam_ColCorrectNumLookups; IMaterialVar *m_pMaterialParam_ColCorrectDefaultWeight; IMaterialVar *m_pMaterialParam_ColCorrectLookupWeights; + IMaterialVar *m_pMaterialParam_LocalContrastStrength; + IMaterialVar *m_pMaterialParam_LocalContrastEdgeStrength; + IMaterialVar *m_pMaterialParam_VignetteStart; + IMaterialVar *m_pMaterialParam_VignetteEnd; + IMaterialVar *m_pMaterialParam_VignetteBlurEnable; + IMaterialVar *m_pMaterialParam_VignetteBlurStrength; + IMaterialVar *m_pMaterialParam_FadeToBlackStrength; + IMaterialVar *m_pMaterialParam_DepthBlurFocalDistance; + IMaterialVar *m_pMaterialParam_DepthBlurStrength; + IMaterialVar *m_pMaterialParam_ScreenBlurStrength; + IMaterialVar *m_pMaterialParam_FilmGrainStrength; + IMaterialVar *m_pMaterialParam_VomitEnable; + IMaterialVar *m_pMaterialParam_VomitColor1; + IMaterialVar *m_pMaterialParam_VomitColor2; + IMaterialVar *m_pMaterialParam_FadeColor; + IMaterialVar *m_pMaterialParam_FadeType; public: static IMaterial * SetupEnginePostMaterial( const Vector4D & fullViewportBloomUVs, const Vector4D & fullViewportFBUVs, const Vector2D & destTexSize, - bool bPerformSoftwareAA, bool bPerformBloom, bool bPerformColCorrect, float flAAStrength ); + bool bPerformSoftwareAA, bool bPerformBloom, bool bPerformColCorrect, float flAAStrength, float flBloomAmount ); static void SetupEnginePostMaterialAA( bool bPerformSoftwareAA, float flAAStrength ); static void SetupEnginePostMaterialTextureTransform( const Vector4D & fullViewportBloomUVs, const Vector4D & fullViewportFBUVs, Vector2D destTexSize ); @@ -1224,12 +1309,14 @@ private: static float s_vBloomAAValues2[4]; static float s_vBloomUVTransform[4]; static int s_PostBloomEnable; + static float s_PostBloomAmount; }; float CEnginePostMaterialProxy::s_vBloomAAValues[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; float CEnginePostMaterialProxy::s_vBloomAAValues2[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; float CEnginePostMaterialProxy::s_vBloomUVTransform[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; int CEnginePostMaterialProxy::s_PostBloomEnable = 1; +float CEnginePostMaterialProxy::s_PostBloomAmount = 1.0f; CEnginePostMaterialProxy::CEnginePostMaterialProxy() { @@ -1237,10 +1324,22 @@ CEnginePostMaterialProxy::CEnginePostMaterialProxy() m_pMaterialParam_AAValues2 = NULL; m_pMaterialParam_BloomUVTransform = NULL; m_pMaterialParam_BloomEnable = NULL; + m_pMaterialParam_BloomAmount = NULL; m_pMaterialParam_ColCorrectEnable = NULL; m_pMaterialParam_ColCorrectNumLookups = NULL; m_pMaterialParam_ColCorrectDefaultWeight = NULL; m_pMaterialParam_ColCorrectLookupWeights = NULL; + m_pMaterialParam_LocalContrastStrength = NULL; + m_pMaterialParam_LocalContrastEdgeStrength = NULL; + m_pMaterialParam_VignetteStart = NULL; + m_pMaterialParam_VignetteEnd = NULL; + m_pMaterialParam_VignetteBlurEnable = NULL; + m_pMaterialParam_VignetteBlurStrength = NULL; + m_pMaterialParam_FadeToBlackStrength = NULL; + m_pMaterialParam_DepthBlurFocalDistance = NULL; + m_pMaterialParam_DepthBlurStrength = NULL; + m_pMaterialParam_ScreenBlurStrength = NULL; + m_pMaterialParam_FilmGrainStrength = NULL; } CEnginePostMaterialProxy::~CEnginePostMaterialProxy() @@ -1251,15 +1350,32 @@ CEnginePostMaterialProxy::~CEnginePostMaterialProxy() bool CEnginePostMaterialProxy::Init( IMaterial *pMaterial, KeyValues *pKeyValues ) { bool bFoundVar = false; - + m_pMaterialParam_AAValues = pMaterial->FindVar( "$AAInternal1", &bFoundVar, false ); m_pMaterialParam_AAValues2 = pMaterial->FindVar( "$AAInternal3", &bFoundVar, false ); m_pMaterialParam_BloomUVTransform = pMaterial->FindVar( "$AAInternal2", &bFoundVar, false ); m_pMaterialParam_BloomEnable = pMaterial->FindVar( "$bloomEnable", &bFoundVar, false ); + m_pMaterialParam_BloomAmount = pMaterial->FindVar( "$bloomAmount", &bFoundVar, false ); m_pMaterialParam_ColCorrectEnable = pMaterial->FindVar( "$colCorrectEnable", &bFoundVar, false ); m_pMaterialParam_ColCorrectNumLookups = pMaterial->FindVar( "$colCorrect_NumLookups", &bFoundVar, false ); m_pMaterialParam_ColCorrectDefaultWeight = pMaterial->FindVar( "$colCorrect_DefaultWeight", &bFoundVar, false ); m_pMaterialParam_ColCorrectLookupWeights = pMaterial->FindVar( "$colCorrect_LookupWeights", &bFoundVar, false ); + m_pMaterialParam_LocalContrastStrength = pMaterial->FindVar( "$localContrastScale", &bFoundVar, false ); + m_pMaterialParam_LocalContrastEdgeStrength = pMaterial->FindVar( "$localContrastEdgeScale", &bFoundVar, false ); + m_pMaterialParam_VignetteStart = pMaterial->FindVar( "$localContrastVignetteStart", &bFoundVar, false ); + m_pMaterialParam_VignetteEnd = pMaterial->FindVar( "$localContrastVignetteEnd", &bFoundVar, false ); + m_pMaterialParam_VignetteBlurEnable = pMaterial->FindVar( "$blurredVignetteEnable", &bFoundVar, false ); + m_pMaterialParam_VignetteBlurStrength = pMaterial->FindVar( "$blurredVignetteScale", &bFoundVar, false ); + m_pMaterialParam_FadeToBlackStrength = pMaterial->FindVar( "$fadeToBlackScale", &bFoundVar, false ); + m_pMaterialParam_DepthBlurFocalDistance = pMaterial->FindVar( "$depthBlurFocalDistance", &bFoundVar, false ); + m_pMaterialParam_DepthBlurStrength = pMaterial->FindVar( "$depthBlurStrength", &bFoundVar, false ); + m_pMaterialParam_ScreenBlurStrength = pMaterial->FindVar( "$screenBlurStrength", &bFoundVar, false ); + m_pMaterialParam_FilmGrainStrength = pMaterial->FindVar( "$noiseScale", &bFoundVar, false ); + m_pMaterialParam_VomitEnable = pMaterial->FindVar( "$vomitEnable", &bFoundVar, false ); + m_pMaterialParam_VomitColor1 = pMaterial->FindVar( "$vomitColor1", &bFoundVar, false ); + m_pMaterialParam_VomitColor2 = pMaterial->FindVar( "$vomitColor2", &bFoundVar, false ); + m_pMaterialParam_FadeColor = pMaterial->FindVar( "$fadeColor", &bFoundVar, false ); + m_pMaterialParam_FadeType = pMaterial->FindVar( "$fade", &bFoundVar, false ); return true; } @@ -1277,6 +1393,56 @@ void CEnginePostMaterialProxy::OnBind( C_BaseEntity *pEnt ) if ( m_pMaterialParam_BloomEnable ) m_pMaterialParam_BloomEnable->SetIntValue( s_PostBloomEnable ); + + if ( m_pMaterialParam_BloomAmount ) + m_pMaterialParam_BloomAmount->SetFloatValue( s_PostBloomAmount ); + + if ( m_pMaterialParam_LocalContrastStrength ) + m_pMaterialParam_LocalContrastStrength->SetFloatValue( s_LocalPostProcessParameters.m_flParameters[ PPPN_LOCAL_CONTRAST_STRENGTH ] ); + + if ( m_pMaterialParam_LocalContrastEdgeStrength ) + m_pMaterialParam_LocalContrastEdgeStrength->SetFloatValue( s_LocalPostProcessParameters.m_flParameters[ PPPN_LOCAL_CONTRAST_EDGE_STRENGTH ] ); + + if ( m_pMaterialParam_VignetteStart ) + m_pMaterialParam_VignetteStart->SetFloatValue( s_LocalPostProcessParameters.m_flParameters[ PPPN_VIGNETTE_START ] ); + + if ( m_pMaterialParam_VignetteEnd ) + m_pMaterialParam_VignetteEnd->SetFloatValue( s_LocalPostProcessParameters.m_flParameters[ PPPN_VIGNETTE_END ] ); + + if ( m_pMaterialParam_VignetteBlurEnable ) + m_pMaterialParam_VignetteBlurEnable->SetIntValue( s_LocalPostProcessParameters.m_flParameters[ PPPN_VIGNETTE_BLUR_STRENGTH ] > 0.0f ? 1 : 0 ); + + if ( m_pMaterialParam_VignetteBlurStrength ) + m_pMaterialParam_VignetteBlurStrength->SetFloatValue( s_LocalPostProcessParameters.m_flParameters[ PPPN_VIGNETTE_BLUR_STRENGTH ] ); + + if ( m_pMaterialParam_FadeToBlackStrength ) + m_pMaterialParam_FadeToBlackStrength->SetFloatValue( s_LocalPostProcessParameters.m_flParameters[ PPPN_FADE_TO_BLACK_STRENGTH ] ); + + if ( m_pMaterialParam_DepthBlurFocalDistance ) + m_pMaterialParam_DepthBlurFocalDistance->SetFloatValue( s_LocalPostProcessParameters.m_flParameters[ PPPN_DEPTH_BLUR_FOCAL_DISTANCE ] ); + + if ( m_pMaterialParam_DepthBlurStrength ) + m_pMaterialParam_DepthBlurStrength->SetFloatValue( s_LocalPostProcessParameters.m_flParameters[ PPPN_DEPTH_BLUR_STRENGTH ] ); + + if ( m_pMaterialParam_ScreenBlurStrength ) + m_pMaterialParam_ScreenBlurStrength->SetFloatValue( s_LocalPostProcessParameters.m_flParameters[ PPPN_SCREEN_BLUR_STRENGTH ] ); + + if ( m_pMaterialParam_FilmGrainStrength ) + m_pMaterialParam_FilmGrainStrength->SetFloatValue( s_LocalPostProcessParameters.m_flParameters[ PPPN_FILM_GRAIN_STRENGTH ] ); + + + + if ( m_pMaterialParam_FadeType ) + { + int nFadeType = ( s_bViewFadeModulate ) ? 2 : 1; + nFadeType = ( s_viewFadeColor[3] > 0.0f ) ? nFadeType : 0; + m_pMaterialParam_FadeType->SetIntValue( nFadeType ); + } + + if ( m_pMaterialParam_FadeColor ) + { + m_pMaterialParam_FadeColor->SetVecValue( s_viewFadeColor.Base(), 4 ); + } } IMaterial *CEnginePostMaterialProxy::GetMaterial() @@ -1349,26 +1515,29 @@ void CEnginePostMaterialProxy::SetupEnginePostMaterialTextureTransform( const Ve } IMaterial * CEnginePostMaterialProxy::SetupEnginePostMaterial( const Vector4D & fullViewportBloomUVs, const Vector4D & fullViewportFBUVs, const Vector2D & destTexSize, - bool bPerformSoftwareAA, bool bPerformBloom, bool bPerformColCorrect, float flAAStrength ) + bool bPerformSoftwareAA, bool bPerformBloom, bool bPerformColCorrect, float flAAStrength, float flBloomAmount ) { // Shouldn't get here if none of the effects are enabled Assert( bPerformSoftwareAA || bPerformBloom || bPerformColCorrect ); - s_PostBloomEnable = bPerformBloom ? 1 : 0; + s_PostBloomEnable = bPerformBloom ? 1 : 0; + s_PostBloomAmount = flBloomAmount; SetupEnginePostMaterialAA( bPerformSoftwareAA, flAAStrength ); - if ( bPerformSoftwareAA || bPerformColCorrect ) + //if ( bPerformSoftwareAA || bPerformColCorrect ) { SetupEnginePostMaterialTextureTransform( fullViewportBloomUVs, fullViewportFBUVs, destTexSize ); return materials->FindMaterial( "dev/engine_post", TEXTURE_GROUP_OTHER, true); } + /* else { // Just use the old bloomadd material (which uses additive blending, unlike engine_post) // NOTE: this path is what gets used for DX8 (which cannot enable AA or col-correction) return materials->FindMaterial( "dev/bloomadd", TEXTURE_GROUP_OTHER, true); } + */ } EXPOSE_INTERFACE( CEnginePostMaterialProxy, IMaterialProxy, "engine_post" IMATERIAL_PROXY_INTERFACE_VERSION ); @@ -1519,6 +1688,28 @@ void DumpTGAofRenderTarget( const int width, const int height, const char *pFile static bool s_bScreenEffectTextureIsUpdated = false; +// WARNING: This function sets rendertarget and viewport. Save and restore is left to the caller. +static void DownsampleFBQuarterSize( IMatRenderContext *pRenderContext, int nSrcWidth, int nSrcHeight, ITexture* pDest, + bool bFloatHDR = false ) +{ + Assert( pRenderContext ); + Assert( pDest ); + + IMaterial *downsample_mat = materials->FindMaterial( bFloatHDR ? "dev/downsample" : "dev/downsample_non_hdr", TEXTURE_GROUP_OTHER, true ); + + // *Everything* in here relies on the small RTs being exactly 1/4 the full FB res + Assert( pDest->GetActualWidth() == nSrcWidth / 4 ); + Assert( pDest->GetActualHeight() == nSrcHeight / 4 ); + + // downsample fb to rt0 + SetRenderTargetAndViewPort( pDest ); + // note the -2's below. Thats because we are downsampling on each axis and the shader + // accesses pixels on both sides of the source coord + pRenderContext->DrawScreenSpaceRectangle( downsample_mat, 0, 0, nSrcWidth/4, nSrcHeight/4, + 0, 0, nSrcWidth-2, nSrcHeight-2, + nSrcWidth, nSrcHeight ); +} + static void Generate8BitBloomTexture( IMatRenderContext *pRenderContext, float flBloomScale, int x, int y, int w, int h ) { @@ -1581,7 +1772,7 @@ static void Generate8BitBloomTexture( IMatRenderContext *pRenderContext, float f // Gaussian blur y rt1 to rt0 SetRenderTargetAndViewPort( dest_rt0 ); IMaterialVar *pBloomAmountVar = yblur_mat->FindVar( "$bloomamount", NULL ); - pBloomAmountVar->SetFloatValue( flBloomScale ); + pBloomAmountVar->SetFloatValue( 1.0f ); // the bloom amount is now applied in engine_post or bloomadd materials pRenderContext->DrawScreenSpaceRectangle( yblur_mat, 0, 0, nSrcWidth / 4, nSrcHeight / 4, 0, 0, nSrcWidth / 4 - 1, nSrcHeight / 4 - 1, nSrcWidth / 4, nSrcHeight / 4 ); @@ -2326,7 +2517,7 @@ void DoEnginePostProcessing( int x, int y, int w, int h, bool bFlashlightIsOn, b // bloom, software-AA and colour-correction (applied in 1 pass, after generation of the bloom texture) bool bPerformSoftwareAA = IsX360() && ( engine->GetDXSupportLevel() >= 90 ) && ( flAAStrength != 0.0f ); - bool bPerformBloom = !bPostVGui && ( flBloomScale > 0.0f ) && ( engine->GetDXSupportLevel() >= 90 ); + bool bPerformBloom = true; //!bPostVGui && ( flBloomScale > 0.0f ) && ( engine->GetDXSupportLevel() >= 90 ); bool bPerformColCorrect = !bPostVGui && ( g_pMaterialSystemHardwareConfig->GetDXSupportLevel() >= 90) && ( g_pMaterialSystemHardwareConfig->GetHDRType() != HDR_TYPE_FLOAT ) && @@ -2404,7 +2595,7 @@ void DoEnginePostProcessing( int x, int y, int w, int h, bool bFlashlightIsOn, b { // Perform post-processing in one combined pass - IMaterial *post_mat = CEnginePostMaterialProxy::SetupEnginePostMaterial( fullViewportPostSrcCorners, fullViewportPostDestCorners, destTexSize, bPerformSoftwareAA, bPerformBloom, bPerformColCorrect, flAAStrength ); + IMaterial *post_mat = CEnginePostMaterialProxy::SetupEnginePostMaterial( fullViewportPostSrcCorners, fullViewportPostDestCorners, destTexSize, bPerformSoftwareAA, bPerformBloom, bPerformColCorrect, flAAStrength, flBloomScale ); if (bSplitScreenHDR) { @@ -2432,7 +2623,7 @@ void DoEnginePostProcessing( int x, int y, int w, int h, bool bFlashlightIsOn, b // Perform post-processing in three separate passes if ( bPerformSoftwareAA ) { - IMaterial *aa_mat = CEnginePostMaterialProxy::SetupEnginePostMaterial( fullViewportPostSrcCorners, fullViewportPostDestCorners, destTexSize, bPerformSoftwareAA, false, false, flAAStrength ); + IMaterial *aa_mat = CEnginePostMaterialProxy::SetupEnginePostMaterial( fullViewportPostSrcCorners, fullViewportPostDestCorners, destTexSize, bPerformSoftwareAA, false, false, flAAStrength, flBloomScale ); if (bSplitScreenHDR) { @@ -2457,7 +2648,7 @@ void DoEnginePostProcessing( int x, int y, int w, int h, bool bFlashlightIsOn, b if ( bPerformBloom ) { - IMaterial *bloom_mat = CEnginePostMaterialProxy::SetupEnginePostMaterial( fullViewportPostSrcCorners, fullViewportPostDestCorners, destTexSize, false, bPerformBloom, false, flAAStrength ); + IMaterial *bloom_mat = CEnginePostMaterialProxy::SetupEnginePostMaterial( fullViewportPostSrcCorners, fullViewportPostDestCorners, destTexSize, false, bPerformBloom, false, flAAStrength, flBloomScale ); if (bSplitScreenHDR) { @@ -2488,7 +2679,7 @@ void DoEnginePostProcessing( int x, int y, int w, int h, bool bFlashlightIsOn, b UpdateScreenEffectTexture( 0, x, y, w, h, false, &actualRect ); } - IMaterial *colcorrect_mat = CEnginePostMaterialProxy::SetupEnginePostMaterial( fullViewportPostSrcCorners, fullViewportPostDestCorners, destTexSize, false, false, bPerformColCorrect, flAAStrength ); + IMaterial *colcorrect_mat = CEnginePostMaterialProxy::SetupEnginePostMaterial( fullViewportPostSrcCorners, fullViewportPostDestCorners, destTexSize, false, false, bPerformColCorrect, flAAStrength, flBloomScale ); if (bSplitScreenHDR) { @@ -2636,6 +2827,7 @@ void DoEnginePostProcessing( int x, int y, int w, int h, bool bFlashlightIsOn, b // Motion Blur Material Proxy ========================================================================================= static float g_vMotionBlurValues[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; +static float g_vMotionBlurViewportValues[4] = { 0.0f, 0.0f, 1.0f, 1.0f }; class CMotionBlurMaterialProxy : public CEntityMaterialProxy { public: @@ -2647,6 +2839,7 @@ public: private: IMaterialVar *m_pMaterialParam; + IMaterialVar *m_pMaterialParamViewport; }; CMotionBlurMaterialProxy::CMotionBlurMaterialProxy() @@ -2667,6 +2860,10 @@ bool CMotionBlurMaterialProxy::Init( IMaterial *pMaterial, KeyValues *pKeyValues if ( bFoundVar == false) return false; + m_pMaterialParamViewport = pMaterial->FindVar( "$MotionBlurViewportInternal", &bFoundVar, false ); + if ( bFoundVar == false) + return false; + return true; } @@ -2676,6 +2873,11 @@ void CMotionBlurMaterialProxy::OnBind( C_BaseEntity *pEnt ) { m_pMaterialParam->SetVecValue( g_vMotionBlurValues, 4 ); } + + if ( m_pMaterialParamViewport != NULL ) + { + m_pMaterialParamViewport->SetVecValue( g_vMotionBlurViewportValues, 4 ); + } } IMaterial *CMotionBlurMaterialProxy::GetMaterial() @@ -2691,26 +2893,51 @@ EXPOSE_INTERFACE( CMotionBlurMaterialProxy, IMaterialProxy, "MotionBlur" IMATERI //===================================================================================================================== // Image-space Motion Blur ============================================================================================ //===================================================================================================================== + ConVar mat_motion_blur_forward_enabled( "mat_motion_blur_forward_enabled", "0" ); ConVar mat_motion_blur_falling_min( "mat_motion_blur_falling_min", "10.0" ); + ConVar mat_motion_blur_falling_max( "mat_motion_blur_falling_max", "20.0" ); ConVar mat_motion_blur_falling_intensity( "mat_motion_blur_falling_intensity", "1.0" ); //ConVar mat_motion_blur_roll_intensity( "mat_motion_blur_roll_intensity", "1.0" ); ConVar mat_motion_blur_rotation_intensity( "mat_motion_blur_rotation_intensity", "1.0" ); ConVar mat_motion_blur_strength( "mat_motion_blur_strength", "1.0" ); -void DoImageSpaceMotionBlur( const CViewSetup &viewBlur, int x, int y, int w, int h ) +struct MotionBlurHistory_t { -#ifdef CSS_PERF_TEST - return; -#endif - static ConVarRef mat_motion_blur_enabled( "mat_motion_blur_enabled" ); + MotionBlurHistory_t() + { + m_flLastTimeUpdate = 0.0f; + m_flPreviousPitch = 0.0f; + m_flPreviousYaw = 0.0f; + m_vPreviousPositon.Init( 0.0f, 0.0f, 0.0f ); + m_mPreviousFrameBasisVectors; + m_flNoRotationalMotionBlurUntil = 0.0f; + SetIdentityMatrix( m_mPreviousFrameBasisVectors ); + } - if ( ( !mat_motion_blur_enabled.GetInt() ) || ( g_pMaterialSystemHardwareConfig->GetDXSupportLevel() < 90 ) ) + float m_flLastTimeUpdate; + float m_flPreviousPitch; + float m_flPreviousYaw; + Vector m_vPreviousPositon; + matrix3x4_t m_mPreviousFrameBasisVectors; + float m_flNoRotationalMotionBlurUntil; +}; + + +void DoImageSpaceMotionBlur( const CViewSetup &viewBlur ) +{ + static ConVarRef mat_motion_blur_enabled( "mat_motion_blur_enabled" ); + if ( !mat_motion_blur_enabled.GetInt() ) { return; } + int x = viewBlur.x; + int y = viewBlur.y; + int w = viewBlur.width; + int h = viewBlur.height; + //======================================================================================================// // Get these convars here to make it easier to remove them later and to default each client differently // //======================================================================================================// @@ -2729,17 +2956,14 @@ void DoImageSpaceMotionBlur( const CViewSetup &viewBlur, int x, int y, int w, in //=====================// // Previous frame data // //=====================// - static float s_flLastTimeUpdate = 0.0f; - static float s_flPreviousPitch = 0.0f; - static float s_flPreviousYaw = 0.0f; - static float s_vPreviousPositon[3] = { 0.0f, 0.0f, 0.0f }; - static matrix3x4_t s_mPreviousFrameBasisVectors; - static float s_flNoRotationalMotionBlurUntil = 0.0f; + static MotionBlurHistory_t history; + //float vPreviousSideVec[3] = { s_mPreviousFrameBasisVectors[0][1], s_mPreviousFrameBasisVectors[1][1], s_mPreviousFrameBasisVectors[2][1] }; //float vPreviousForwardVec[3] = { s_mPreviousFrameBasisVectors[0][0], s_mPreviousFrameBasisVectors[1][0], s_mPreviousFrameBasisVectors[2][0] }; //float vPreviousUpVec[3] = { s_mPreviousFrameBasisVectors[0][2], s_mPreviousFrameBasisVectors[1][2], s_mPreviousFrameBasisVectors[2][2] }; - float flTimeElapsed = gpGlobals->realtime - s_flLastTimeUpdate; + float flTimeElapsed = gpGlobals->realtime - history.m_flLastTimeUpdate; + //===================================// // Get current pitch & wrap to +-180 // @@ -2759,8 +2983,11 @@ void DoImageSpaceMotionBlur( const CViewSetup &viewBlur, int x, int y, int w, in while ( flCurrentYaw < -180.0f ) flCurrentYaw += 360.0f; - //engine->Con_NPrintf( 0, "Blur Pitch: %6.2f Yaw: %6.2f", flCurrentPitch, flCurrentYaw ); - //engine->Con_NPrintf( 1, "Blur FOV: %6.2f Aspect: %6.2f Ortho: %s", view.fov, view.m_flAspectRatio, view.m_bOrtho ? "Yes" : "No" ); + + + /*engine->Con_NPrintf( 0, "Blur Pitch: %6.2f Yaw: %6.2f", flCurrentPitch, flCurrentYaw ); + engine->Con_NPrintf( 1, "Blur FOV: %6.2f Aspect: %6.2f Ortho: %s", view.fov, view.m_flAspectRatio, view.m_bOrtho ? "Yes" : "No" ); + engine->Con_NPrintf( 2, "View Angles: %6.2f %6.2f %6.2f", XYZ(view.angles) );*/ //===========================// // Get current basis vectors // @@ -2768,20 +2995,21 @@ void DoImageSpaceMotionBlur( const CViewSetup &viewBlur, int x, int y, int w, in matrix3x4_t mCurrentBasisVectors; AngleMatrix( viewBlur.angles, mCurrentBasisVectors ); - float vCurrentSideVec[3] = { mCurrentBasisVectors[0][1], mCurrentBasisVectors[1][1], mCurrentBasisVectors[2][1] }; - float vCurrentForwardVec[3] = { mCurrentBasisVectors[0][0], mCurrentBasisVectors[1][0], mCurrentBasisVectors[2][0] }; - //float vCurrentUpVec[3] = { mCurrentBasisVectors[0][2], mCurrentBasisVectors[1][2], mCurrentBasisVectors[2][2] }; + + Vector vCurrentSideVec( mCurrentBasisVectors[0][1], mCurrentBasisVectors[1][1], mCurrentBasisVectors[2][1] ); + Vector vCurrentForwardVec( mCurrentBasisVectors[0][0], mCurrentBasisVectors[1][0], mCurrentBasisVectors[2][0] ); + //Vector vCurrentUpVec( mCurrentBasisVectors[0][2], mCurrentBasisVectors[1][2], mCurrentBasisVectors[2][2] ); //======================// // Get current position // //======================// - float vCurrentPosition[3] = { viewBlur.origin.x, viewBlur.origin.y, viewBlur.origin.z }; + Vector vCurrentPosition = { viewBlur.origin.x, viewBlur.origin.y, viewBlur.origin.z }; //===============================================================// // Evaluate change in position to determine if we need to update // //===============================================================// - float vPositionChange[3] = { 0.0f, 0.0f, 0.0f }; - VectorSubtract( s_vPreviousPositon, vCurrentPosition, vPositionChange ); + Vector vPositionChange( 0.0f, 0.0f, 0.0f ); + VectorSubtract( history.m_vPreviousPositon, vCurrentPosition, vPositionChange ); if ( ( VectorLength( vPositionChange ) > 30.0f ) && ( flTimeElapsed >= 0.5f ) ) { //=======================================================// @@ -2795,7 +3023,7 @@ void DoImageSpaceMotionBlur( const CViewSetup &viewBlur, int x, int y, int w, in g_vMotionBlurValues[2] = 0.0f; g_vMotionBlurValues[3] = 0.0f; } - else if ( flTimeElapsed > ( 1.0f / 15.0f ) ) + else if ( ( flTimeElapsed > ( 1.0f / 15.0f ) ) ) { //==========================================// // If slower than 15 fps, don't motion blur // @@ -2805,7 +3033,7 @@ void DoImageSpaceMotionBlur( const CViewSetup &viewBlur, int x, int y, int w, in g_vMotionBlurValues[2] = 0.0f; g_vMotionBlurValues[3] = 0.0f; } - else if ( VectorLength( vPositionChange ) > 50.0f ) + else if ( ( VectorLength( vPositionChange ) > 50.0f ) ) { //================================================================================// // We moved a far distance in a frame, use the same motion blur as last frame // @@ -2813,7 +3041,7 @@ void DoImageSpaceMotionBlur( const CViewSetup &viewBlur, int x, int y, int w, in //================================================================================// //engine->Con_NPrintf( 8, " Position changed %f units @ %.2f time ", VectorLength( vPositionChange ), gpGlobals->realtime ); - s_flNoRotationalMotionBlurUntil = gpGlobals->realtime + 1.0f; // Wait a second until the portal craziness calms down + history.m_flNoRotationalMotionBlurUntil = gpGlobals->realtime + 1.0f; // Wait a second until the portal craziness calms down } else { @@ -2838,10 +3066,10 @@ void DoImageSpaceMotionBlur( const CViewSetup &viewBlur, int x, int y, int w, in // Yaw (Compensate for circle strafe) // //====================================// float flSideDotMotion = DotProduct( vCurrentSideVec, vPositionChange ); - float flYawDiffOriginal = s_flPreviousYaw - flCurrentYaw; - if ( ( ( s_flPreviousYaw - flCurrentYaw > 180.0f ) || ( s_flPreviousYaw - flCurrentYaw < -180.0f ) ) && - ( ( s_flPreviousYaw + flCurrentYaw > -180.0f ) && ( s_flPreviousYaw + flCurrentYaw < 180.0f ) ) ) - flYawDiffOriginal = s_flPreviousYaw + flCurrentYaw; + float flYawDiffOriginal = history.m_flPreviousYaw - flCurrentYaw; + if ( ( ( history.m_flPreviousYaw - flCurrentYaw > 180.0f ) || ( history.m_flPreviousYaw - flCurrentYaw < -180.0f ) ) && + ( ( history.m_flPreviousYaw + flCurrentYaw > -180.0f ) && ( history.m_flPreviousYaw + flCurrentYaw < 180.0f ) ) ) + flYawDiffOriginal = history.m_flPreviousYaw + flCurrentYaw; float flYawDiffAdjusted = flYawDiffOriginal + ( flSideDotMotion / 3.0f ); // Yes, 3.0 is a magic number, sue me @@ -2861,7 +3089,7 @@ void DoImageSpaceMotionBlur( const CViewSetup &viewBlur, int x, int y, int w, in // Pitch (Compensate for forward motion) // //=======================================// float flPitchCompensateMask = 1.0f - ( ( 1.0f - fabs( vCurrentForwardVec[2] ) ) * ( 1.0f - fabs( vCurrentForwardVec[2] ) ) ); - float flPitchDiffOriginal = s_flPreviousPitch - flCurrentPitch; + float flPitchDiffOriginal = history.m_flPreviousPitch - flCurrentPitch; float flPitchDiffAdjusted = flPitchDiffOriginal; if ( flCurrentPitch > 0.0f ) @@ -2911,24 +3139,21 @@ void DoImageSpaceMotionBlur( const CViewSetup &viewBlur, int x, int y, int w, in //===============================================================// // Dampen motion blur from 100%-0% as fps drops from 50fps-30fps // //===============================================================// - if ( !IsX360() ) // I'm not doing this on the 360 yet since I can't test it - { - float flSlowFps = 30.0f; - float flFastFps = 50.0f; - float flCurrentFps = ( flTimeElapsed > 0.0f ) ? ( 1.0f / flTimeElapsed ) : 0.0f; - float flDampenFactor = clamp( ( ( flCurrentFps - flSlowFps ) / ( flFastFps - flSlowFps ) ), 0.0f, 1.0f ); + float flSlowFps = 30.0f; + float flFastFps = 50.0f; + float flCurrentFps = ( flTimeElapsed > 0.0f ) ? ( 1.0f / flTimeElapsed ) : 0.0f; + float flDampenFactor = clamp( ( ( flCurrentFps - flSlowFps ) / ( flFastFps - flSlowFps ) ), 0.0f, 1.0f ); - //engine->Con_NPrintf( 4, "gpGlobals->realtime %.2f gpGlobals->curtime %.2f", gpGlobals->realtime, gpGlobals->curtime ); - //engine->Con_NPrintf( 5, "flCurrentFps %.2f", flCurrentFps ); - //engine->Con_NPrintf( 7, "flTimeElapsed %.2f", flTimeElapsed ); + //engine->Con_NPrintf( 4, "gpGlobals->realtime %.2f gpGlobals->curtime %.2f", gpGlobals->realtime, gpGlobals->curtime ); + //engine->Con_NPrintf( 5, "flCurrentFps %.2f", flCurrentFps ); + //engine->Con_NPrintf( 7, "flTimeElapsed %.2f", flTimeElapsed ); - g_vMotionBlurValues[0] *= flDampenFactor; - g_vMotionBlurValues[1] *= flDampenFactor; - g_vMotionBlurValues[2] *= flDampenFactor; - g_vMotionBlurValues[3] *= flDampenFactor; + g_vMotionBlurValues[0] *= flDampenFactor; + g_vMotionBlurValues[1] *= flDampenFactor; + g_vMotionBlurValues[2] *= flDampenFactor; + g_vMotionBlurValues[3] *= flDampenFactor; - //engine->Con_NPrintf( 6, "Dampen: %.2f", flDampenFactor ); - } + //engine->Con_NPrintf( 6, "Dampen: %.2f", flDampenFactor ); //engine->Con_NPrintf( 6, "Final values: { %6.2f%%, %6.2f%%, %6.2f%%, %6.2f%% }", g_vMotionBlurValues[0]*100.0f, g_vMotionBlurValues[1]*100.0f, g_vMotionBlurValues[2]*100.0f, g_vMotionBlurValues[3]*100.0f ); } @@ -2936,7 +3161,7 @@ void DoImageSpaceMotionBlur( const CViewSetup &viewBlur, int x, int y, int w, in //============================================// // Zero out blur if still in that time window // //============================================// - if ( gpGlobals->realtime < s_flNoRotationalMotionBlurUntil ) + if ( gpGlobals->realtime < history.m_flNoRotationalMotionBlurUntil ) { //engine->Con_NPrintf( 9, " No Rotation @ %f ", gpGlobals->realtime ); @@ -2947,17 +3172,60 @@ void DoImageSpaceMotionBlur( const CViewSetup &viewBlur, int x, int y, int w, in } else { - s_flNoRotationalMotionBlurUntil = 0.0f; + history.m_flNoRotationalMotionBlurUntil = 0.0f; } //====================================// // Store current frame for next frame // //====================================// - VectorCopy( vCurrentPosition, s_vPreviousPositon ); - s_mPreviousFrameBasisVectors = mCurrentBasisVectors; - s_flPreviousPitch = flCurrentPitch; - s_flPreviousYaw = flCurrentYaw; - s_flLastTimeUpdate = gpGlobals->realtime; + VectorCopy( vCurrentPosition, history.m_vPreviousPositon ); + history.m_mPreviousFrameBasisVectors = mCurrentBasisVectors; + history.m_flPreviousPitch = flCurrentPitch; + history.m_flPreviousYaw = flCurrentYaw; + history.m_flLastTimeUpdate = gpGlobals->realtime; + } + + //engine->Con_NPrintf( 6, "Final values: { %6.2f%%, %6.2f%%, %6.2f%%, %6.2f%% }", g_vMotionBlurValues[0]*100.0f, g_vMotionBlurValues[1]*100.0f, g_vMotionBlurValues[2]*100.0f, g_vMotionBlurValues[3]*100.0f ); + + //==========================================// + // Set global g_vMotionBlurViewportValues[] // + //==========================================// + if ( true ) + { + ITexture *pSrc = materials->FindTexture( "_rt_FullFrameFB", TEXTURE_GROUP_RENDER_TARGET ); + float flSrcWidth = ( float )pSrc->GetActualWidth(); + float flSrcHeight = ( float )pSrc->GetActualHeight(); + + // NOTE #1: float4 stored as ( minx, miny, maxy, maxx )...z&w have been swapped to save pixel shader instructions + // NOTE #2: This code should definitely work for 2 players (horizontal or vertical), or 4 players (4 corners), but + // it might have to be modified if we ever want to support other split screen configurations + + int nOffset; // Offset by one pixel to land in the correct half + + // Left + nOffset = ( x > 0 ) ? 1 : 0; + g_vMotionBlurViewportValues[0] = ( float )( x + nOffset ) / ( flSrcWidth - 1 ); + + // Right + nOffset = ( x < ( flSrcWidth - 1 ) ) ? -1 : 0; + g_vMotionBlurViewportValues[3] = ( float )( x + w + nOffset ) / ( flSrcWidth - 1 ); + + // Top + nOffset = ( y > 0 ) ? 1 : 0; // Offset by one pixel to land in the correct half + g_vMotionBlurViewportValues[1] = ( float )( y + nOffset ) / ( flSrcHeight - 1 ); + + // Bottom + nOffset = ( y < ( flSrcHeight - 1 ) ) ? -1 : 0; + g_vMotionBlurViewportValues[2] = ( float )( y + h + nOffset ) / ( flSrcHeight - 1 ); + + // Only allow clamping to happen in the middle of the screen, so nudge the clamp values out if they're on the border of the screen + for ( int i = 0; i < 4; i++ ) + { + if ( g_vMotionBlurViewportValues[i] <= 0.0f ) + g_vMotionBlurViewportValues[i] = -1.0f; + else if ( g_vMotionBlurViewportValues[i] >= 1.0f ) + g_vMotionBlurViewportValues[i] = 2.0f; + } } //=============================================================================================// @@ -2970,13 +3238,10 @@ void DoImageSpaceMotionBlur( const CViewSetup &viewBlur, int x, int y, int w, in ITexture *pSrc = materials->FindTexture( "_rt_FullFrameFB", TEXTURE_GROUP_RENDER_TARGET ); int nSrcWidth = pSrc->GetActualWidth(); int nSrcHeight = pSrc->GetActualHeight(); - int dest_width, dest_height, nDummy; - pRenderContext->GetViewport( nDummy, nDummy, dest_width, dest_height ); + int nViewportWidth, nViewportHeight, nDummy; + pRenderContext->GetViewport( nDummy, nDummy, nViewportWidth, nViewportHeight ); - if ( g_pMaterialSystemHardwareConfig->GetHDRType() != HDR_TYPE_FLOAT ) - { - UpdateScreenEffectTexture( 0, x, y, w, h, true ); // Do we need to check if we already did this? - } + UpdateScreenEffectTexture( 0, x, y, w, h, false ); // Get material pointer IMaterial *pMatMotionBlur = materials->FindMaterial( "dev/motion_blur", TEXTURE_GROUP_OTHER, true ); @@ -2988,14 +3253,200 @@ void DoImageSpaceMotionBlur( const CViewSetup &viewBlur, int x, int y, int w, in { pRenderContext->DrawScreenSpaceRectangle( pMatMotionBlur, - 0, 0, dest_width, dest_height, - 0, 0, nSrcWidth-1, nSrcHeight-1, + 0, 0, nViewportWidth, nViewportHeight, + x, y, x + w-1, y + h-1, nSrcWidth, nSrcHeight, GetClientWorldEntity()->GetClientRenderable() ); - - if ( g_bDumpRenderTargets ) - { - DumpTGAofRenderTarget( dest_width, dest_height, "MotionBlur" ); - } } } } + +//===================================================================================================================== +// Depth of field ===================================================================================================== +//===================================================================================================================== +ConVar mat_dof_enabled( "mat_dof_enabled", "1" ); +ConVar mat_dof_override( "mat_dof_override", "0" ); +ConVar mat_dof_near_blur_depth( "mat_dof_near_blur_depth", "20.0" ); +ConVar mat_dof_near_focus_depth( "mat_dof_near_focus_depth", "100.0" ); +ConVar mat_dof_far_focus_depth( "mat_dof_far_focus_depth", "250.0" ); +ConVar mat_dof_far_blur_depth( "mat_dof_far_blur_depth", "1000.0" ); +ConVar mat_dof_near_blur_radius( "mat_dof_near_blur_radius", "0.0" ); +ConVar mat_dof_far_blur_radius( "mat_dof_far_blur_radius", "10.0" ); +ConVar mat_dof_quality( "mat_dof_quality", "3" ); + +static float GetNearBlurDepth() +{ + return mat_dof_override.GetBool() ? mat_dof_near_blur_depth.GetFloat() : g_flDOFNearBlurDepth; +} + +static float GetNearFocusDepth() +{ + return mat_dof_override.GetBool() ? mat_dof_near_focus_depth.GetFloat() : g_flDOFNearFocusDepth; +} + +static float GetFarFocusDepth() +{ + return mat_dof_override.GetBool() ? mat_dof_far_focus_depth.GetFloat() : g_flDOFFarFocusDepth; +} + +static float GetFarBlurDepth() +{ + return mat_dof_override.GetBool() ? mat_dof_far_blur_depth.GetFloat() : g_flDOFFarBlurDepth; +} + +static float GetNearBlurRadius() +{ + return mat_dof_override.GetBool() ? mat_dof_near_blur_radius.GetFloat() : g_flDOFNearBlurRadius; +} + +static float GetFarBlurRadius() +{ + return mat_dof_override.GetBool() ? mat_dof_far_blur_radius.GetFloat() : g_flDOFFarBlurRadius; +} + +bool IsDepthOfFieldEnabled() +{ + const CViewSetup *pViewSetup = view->GetViewSetup(); + if ( !pViewSetup ) + return false; + + if ( g_pMaterialSystemHardwareConfig->GetDXSupportLevel() < 92 ) + return false; + + if ( !mat_dof_enabled.GetBool() ) + return false; + + if ( mat_dof_override.GetBool() == true ) + { + return mat_dof_enabled.GetBool(); + } + else + { + return g_bDOFEnabled; + } +} + +static inline bool SetMaterialVarFloat( IMaterial* pMat, const char* pVarName, float flValue ) +{ + Assert( pMat != NULL ); + Assert( pVarName != NULL ); + if ( pMat == NULL || pVarName == NULL ) + { + return false; + } + + bool bFound = false; + IMaterialVar* pVar = pMat->FindVar( pVarName, &bFound ); + if ( bFound ) + { + pVar->SetFloatValue( flValue ); + } + + return bFound; +} + +static inline bool SetMaterialVarInt( IMaterial* pMat, const char* pVarName, int nValue ) +{ + Assert( pMat != NULL ); + Assert( pVarName != NULL ); + if ( pMat == NULL || pVarName == NULL ) + { + return false; + } + + bool bFound = false; + IMaterialVar* pVar = pMat->FindVar( pVarName, &bFound ); + if ( bFound ) + { + pVar->SetIntValue( nValue ); + } + + return bFound; +} + +void DoDepthOfField( const CViewSetup &viewSetup ) +{ + if ( !IsDepthOfFieldEnabled() ) + { + return; + } + + // Copy from backbuffer to _rt_FullFrameFB + UpdateScreenEffectTexture( 0, viewSetup.x, viewSetup.y, viewSetup.width, viewSetup.height, false ); // Do we need to check if we already did this? + + CMatRenderContextPtr pRenderContext( materials ); + + ITexture *pSrc = materials->FindTexture( "_rt_FullFrameFB", TEXTURE_GROUP_RENDER_TARGET ); + int nSrcWidth = pSrc->GetActualWidth(); + int nSrcHeight = pSrc->GetActualHeight(); + + int nViewportWidth = 0; + int nViewportHeight = 0; + int nDummy = 0; + pRenderContext->GetViewport( nDummy, nDummy, nViewportWidth, nViewportHeight ); + + if ( mat_dof_quality.GetInt() < 2 ) + { + ///////////////////////////////////// + // Downsample backbuffer to 1/4 size + ///////////////////////////////////// + + // Update downsampled framebuffer. TODO: Don't do this again for the bloom if we already did it here... + pRenderContext->PushRenderTargetAndViewport(); + ITexture *dest_rt0 = materials->FindTexture( "_rt_SmallFB0", TEXTURE_GROUP_RENDER_TARGET ); + + // *Everything* in here relies on the small RTs being exactly 1/4 the full FB res + Assert( dest_rt0->GetActualWidth() == pSrc->GetActualWidth() / 4 ); + Assert( dest_rt0->GetActualHeight() == pSrc->GetActualHeight() / 4 ); + + // Downsample fb to rt0 + DownsampleFBQuarterSize( pRenderContext, nSrcWidth, nSrcHeight, dest_rt0, true ); + + ////////////////////////////////////// + // Additional blur using 3x3 gaussian + ////////////////////////////////////// + + IMaterial *pMat = materials->FindMaterial( "dev/blurgaussian_3x3", TEXTURE_GROUP_OTHER, true ); + + if ( pMat == NULL ) + return; + + SetMaterialVarFloat( pMat, "$c0_x", 0.5f / (float)dest_rt0->GetActualWidth() ); + SetMaterialVarFloat( pMat, "$c0_y", 0.5f / (float)dest_rt0->GetActualHeight() ); + SetMaterialVarFloat( pMat, "$c1_x", -0.5f / (float)dest_rt0->GetActualWidth() ); + SetMaterialVarFloat( pMat, "$c1_y", 0.5f / (float)dest_rt0->GetActualHeight() ); + + ITexture *dest_rt1 = materials->FindTexture( "_rt_SmallFB1", TEXTURE_GROUP_RENDER_TARGET ); + SetRenderTargetAndViewPort( dest_rt1 ); + + pRenderContext->DrawScreenSpaceRectangle( + pMat, 0, 0, nSrcWidth/4, nSrcHeight/4, + 0, 0, dest_rt0->GetActualWidth()-1, dest_rt0->GetActualHeight()-1, + dest_rt0->GetActualWidth(), dest_rt0->GetActualHeight() ); + + pRenderContext->PopRenderTargetAndViewport(); + } + + // Render depth-of-field quad + IMaterial *pMatDOF = materials->FindMaterial( "dev/depth_of_field", TEXTURE_GROUP_OTHER, true ); + + if ( pMatDOF == NULL ) + return; + + SetMaterialVarFloat( pMatDOF, "$nearPlane", viewSetup.zNear ); + SetMaterialVarFloat( pMatDOF, "$farPlane", viewSetup.zFar ); + + // pull from convars/globals + SetMaterialVarFloat( pMatDOF, "$nearBlurDepth", GetNearBlurDepth() ); + SetMaterialVarFloat( pMatDOF, "$nearFocusDepth", GetNearFocusDepth() ); + SetMaterialVarFloat( pMatDOF, "$farFocusDepth", GetFarFocusDepth() ); + SetMaterialVarFloat( pMatDOF, "$farBlurDepth", GetFarBlurDepth() ); + SetMaterialVarFloat( pMatDOF, "$nearBlurRadius", GetNearBlurRadius() ); + SetMaterialVarFloat( pMatDOF, "$farBlurRadius", GetFarBlurRadius() ); + SetMaterialVarInt( pMatDOF, "$quality", mat_dof_quality.GetInt() ); + + pRenderContext->DrawScreenSpaceRectangle( + pMatDOF, + 0, 0, nViewportWidth, nViewportHeight, + 0, 0, nSrcWidth-1, nSrcHeight-1, + nSrcWidth, nSrcHeight, GetClientWorldEntity()->GetClientRenderable() ); +} diff --git a/src/game/client/viewpostprocess.h b/src/game/client/viewpostprocess.h index eb2ad9cc..f40a6937 100644 --- a/src/game/client/viewpostprocess.h +++ b/src/game/client/viewpostprocess.h @@ -11,8 +11,20 @@ #pragma once #endif +struct PostProcessParameters_t; + void DoEnginePostProcessing( int x, int y, int w, int h, bool bFlashlightIsOn, bool bPostVGui = false ); -void DoImageSpaceMotionBlur( const CViewSetup &view, int x, int y, int w, int h ); +void DoImageSpaceMotionBlur( const CViewSetup &viewSetup ); void DumpTGAofRenderTarget( const int width, const int height, const char *pFilename ); +void SetRenderTargetAndViewPort( ITexture *rt ); + +bool IsDepthOfFieldEnabled(); +void DoDepthOfField( const CViewSetup &view ); + +void SetPostProcessParams( const PostProcessParameters_t* pPostProcessParameters ); +void SetPostProcessParams( const PostProcessParameters_t* pPostProcessParameters, bool override ); + +void SetViewFadeParams( byte r, byte g, byte b, byte a, bool bModulate ); + #endif // VIEWPOSTPROCESS_H diff --git a/src/game/client/viewrender.cpp b/src/game/client/viewrender.cpp index b4011e28..e49f4459 100644 --- a/src/game/client/viewrender.cpp +++ b/src/game/client/viewrender.cpp @@ -77,6 +77,11 @@ #include "c_point_camera.h" #endif // USE_MONITORS +#ifdef MAPBASE +#include "mapbase/c_func_fake_worldportal.h" +#include "colorcorrectionmgr.h" +#endif + // Projective textures #include "C_Env_Projected_Texture.h" @@ -121,6 +126,10 @@ static ConVar r_drawtranslucentrenderables( "r_drawtranslucentrenderables", "1", static ConVar r_drawopaquerenderables( "r_drawopaquerenderables", "1", FCVAR_CHEAT ); static ConVar r_threaded_renderables( "r_threaded_renderables", "0" ); +#ifdef MAPBASE +static ConVar r_skybox_use_complex_views( "r_skybox_use_complex_views", "0", FCVAR_CHEAT, "Enable complex views in skyboxes, like reflective glass" ); +#endif + // FIXME: This is not static because we needed to turn it off for TF2 playtests ConVar r_DrawDetailProps( "r_DrawDetailProps", "1", FCVAR_NONE, "0=Off, 1=Normal, 2=Wireframe" ); @@ -176,6 +185,10 @@ extern ConVar cl_leveloverview; extern ConVar localplayer_visionflags; +#ifdef MAPBASE +static ConVar r_nearz_skybox( "r_nearz_skybox", "2.0", FCVAR_CHEAT ); +#endif + //----------------------------------------------------------------------------- // Globals //----------------------------------------------------------------------------- @@ -484,6 +497,11 @@ protected: void SSAO_DepthPass(); void DrawDepthOfField(); + +#ifdef MAPBASE + virtual ITexture *GetRefractionTexture() { return GetWaterRefractionTexture(); } + virtual ITexture *GetReflectionTexture() { return GetWaterReflectionTexture(); } +#endif }; @@ -670,6 +688,11 @@ public: void Draw(); cplane_t m_ReflectionPlane; + +#ifdef MAPBASE + ITexture *GetReflectionTexture() { return m_pRenderTarget; } + ITexture *m_pRenderTarget; +#endif }; class CRefractiveGlassView : public CSimpleWorldView @@ -687,6 +710,11 @@ public: void Draw(); cplane_t m_ReflectionPlane; + +#ifdef MAPBASE + ITexture *GetRefractionTexture() { return m_pRenderTarget; } + ITexture *m_pRenderTarget; +#endif }; @@ -796,6 +824,8 @@ CLIENTEFFECT_REGISTER_BEGIN( PrecachePostProcessingEffects ) CLIENTEFFECT_MATERIAL( "dev/copyfullframefb_vanilla" ) CLIENTEFFECT_MATERIAL( "dev/copyfullframefb" ) CLIENTEFFECT_MATERIAL( "dev/engine_post" ) + CLIENTEFFECT_MATERIAL( "dev/depth_of_field" ) + CLIENTEFFECT_MATERIAL( "dev/blurgaussian_3x3" ) CLIENTEFFECT_MATERIAL( "dev/motion_blur" ) CLIENTEFFECT_MATERIAL( "dev/upscale" ) @@ -1209,6 +1239,73 @@ IMaterial *CViewRender::GetScreenOverlayMaterial( ) } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: Sets the screen space effect material (can't be done during rendering) +//----------------------------------------------------------------------------- +void CViewRender::SetIndexedScreenOverlayMaterial( int i, IMaterial *pMaterial ) +{ + if (i < 0 || i >= MAX_SCREEN_OVERLAYS) + return; + + m_IndexedScreenOverlayMaterials[i].Init( pMaterial ); + + if (pMaterial == NULL) + { + // Check if we should set to false + int i; + for (i = 0; i < MAX_SCREEN_OVERLAYS; i++) + { + if (m_IndexedScreenOverlayMaterials[i] != NULL) + break; + } + + if (i == MAX_SCREEN_OVERLAYS) + m_bUsingIndexedScreenOverlays = false; + } + else + { + m_bUsingIndexedScreenOverlays = true; + } +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +IMaterial *CViewRender::GetIndexedScreenOverlayMaterial( int i ) +{ + if (i < 0 || i >= MAX_SCREEN_OVERLAYS) + return NULL; + + return m_IndexedScreenOverlayMaterials[i]; +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CViewRender::ResetIndexedScreenOverlays() +{ + for (int i = 0; i < MAX_SCREEN_OVERLAYS; i++) + { + m_IndexedScreenOverlayMaterials[i].Init( NULL ); + } + + m_bUsingIndexedScreenOverlays = false; +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +int CViewRender::GetMaxIndexedScreenOverlays( ) const +{ + return MAX_SCREEN_OVERLAYS; +} +#endif + + //----------------------------------------------------------------------------- // Purpose: Performs screen space effects, if any //----------------------------------------------------------------------------- @@ -1246,6 +1343,43 @@ void CViewRender::PerformScreenOverlay( int x, int y, int w, int h ) } } +#ifdef MAPBASE + if (m_bUsingIndexedScreenOverlays) + { + for (int i = 0; i < MAX_SCREEN_OVERLAYS; i++) + { + if (!m_IndexedScreenOverlayMaterials[i]) + continue; + + tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ ); + + if ( m_IndexedScreenOverlayMaterials[i]->NeedsFullFrameBufferTexture() ) + { + // FIXME: check with multi/sub-rect renders. Should this be 0,0,w,h instead? + DrawScreenEffectMaterial( m_IndexedScreenOverlayMaterials[i], x, y, w, h ); + } + else if ( m_IndexedScreenOverlayMaterials[i]->NeedsPowerOfTwoFrameBufferTexture() ) + { + // First copy the FB off to the offscreen texture + UpdateRefractTexture( x, y, w, h, true ); + + // Now draw the entire screen using the material... + CMatRenderContextPtr pRenderContext( materials ); + ITexture *pTexture = GetPowerOfTwoFrameBufferTexture( ); + int sw = pTexture->GetActualWidth(); + int sh = pTexture->GetActualHeight(); + // Note - don't offset by x,y - already done by the viewport. + pRenderContext->DrawScreenSpaceRectangle( m_IndexedScreenOverlayMaterials[i], 0, 0, w, h, + 0, 0, sw-1, sh-1, sw, sh ); + } + else + { + byte color[4] = { 255, 255, 255, 255 }; + render->ViewDrawFade( color, m_IndexedScreenOverlayMaterials[i] ); + } + } + } +#endif { const char *pszScriptMaterial = nullptr; C_BasePlayer *pLocalPlayer = C_BasePlayer::GetLocalPlayer(); @@ -1401,6 +1535,9 @@ void CViewRender::ViewDrawScene( bool bDrew3dSkybox, SkyboxVisibility_t nSkyboxV if ( r_flashlightdepthtexture.GetBool() && (viewID == VIEW_MAIN) ) { g_pClientShadowMgr->ComputeShadowDepthTextures( viewRender ); +#ifdef ASW_PROJECTED_TEXTURES + CMatRenderContextPtr pRenderContext( materials ); +#endif } m_BaseDrawFlags = baseDrawFlags; @@ -2078,6 +2215,26 @@ void CViewRender::RenderView( const CViewSetup &viewRender, int nClearFlags, int { CViewSetup viewMiddle = GetView( STEREO_EYE_MONO ); DrawMonitors( viewMiddle ); + +#ifdef MAPBASE + // Any fake world portals? + Frustum_t frustum; + GeneratePerspectiveFrustum( viewRender.origin, viewRender.angles, viewRender.zNear, viewRender.zFar, viewRender.fov, viewRender.m_flAspectRatio, frustum ); + + Vector vecAbsPlaneNormal; + float flLocalPlaneDist; + C_FuncFakeWorldPortal *pPortalEnt = NextFakeWorldPortal( NULL, viewRender, vecAbsPlaneNormal, flLocalPlaneDist, frustum ); + while ( pPortalEnt != NULL ) + { + ITexture *pCameraTarget = pPortalEnt->RenderTarget(); + int width = pCameraTarget->GetActualWidth(); + int height = pCameraTarget->GetActualHeight(); + + DrawFakeWorldPortal( pCameraTarget, pPortalEnt, viewMiddle, C_BasePlayer::GetLocalPlayer(), 0, 0, width, height, viewRender, vecAbsPlaneNormal, flLocalPlaneDist ); + + pPortalEnt = NextFakeWorldPortal( pPortalEnt, viewRender, vecAbsPlaneNormal, flLocalPlaneDist, frustum ); + } +#endif } #endif @@ -2086,6 +2243,10 @@ void CViewRender::RenderView( const CViewSetup &viewRender, int nClearFlags, int // Must be first render->SceneBegin(); +#ifdef MAPBASE // From Alien Swarm SDK + g_pColorCorrectionMgr->UpdateColorCorrection(); +#endif + pRenderContext.GetFrom( materials ); pRenderContext->TurnOnToneMapping(); pRenderContext.SafeRelease(); @@ -2096,6 +2257,7 @@ void CViewRender::RenderView( const CViewSetup &viewRender, int nClearFlags, int bool bDrew3dSkybox = false; SkyboxVisibility_t nSkyboxVisible = SKYBOX_NOT_VISIBLE; +#ifndef MAPBASE // Moved to respective ViewDrawScenes() for script_intro skybox fix // if the 3d skybox world is drawn, then don't draw the normal skybox CSkyboxView *pSkyView = new CSkyboxView( this ); if ( ( bDrew3dSkybox = pSkyView->Setup( viewRender, &nClearFlags, &nSkyboxVisible ) ) != false ) @@ -2103,6 +2265,7 @@ void CViewRender::RenderView( const CViewSetup &viewRender, int nClearFlags, int AddViewToScene( pSkyView ); } SafeRelease( pSkyView ); +#endif // Force it to clear the framebuffer if they're in solid space. if ( ( nClearFlags & VIEW_CLEAR_COLOR ) == 0 ) @@ -2113,14 +2276,37 @@ void CViewRender::RenderView( const CViewSetup &viewRender, int nClearFlags, int } } +#ifdef MAPBASE + // For script_intro viewmodel fix + bool bDrawnViewmodel = false; +#endif + // Render world and all entities, particles, etc. if( !g_pIntroData ) { +#ifdef MAPBASE + // Moved here for the script_intro skybox fix. + // We can't put it in ViewDrawScene() directly because other functions use it as well. + + // if the 3d skybox world is drawn, then don't draw the normal skybox + CSkyboxView *pSkyView = new CSkyboxView( this ); + if ( ( bDrew3dSkybox = pSkyView->Setup( viewRender, &nClearFlags, &nSkyboxVisible ) ) != false ) + { + AddViewToScene( pSkyView ); + } + SafeRelease( pSkyView ); +#endif + ViewDrawScene( bDrew3dSkybox, nSkyboxVisible, viewRender, nClearFlags, VIEW_MAIN, whatToDraw & RENDERVIEW_DRAWVIEWMODEL ); } else { +#ifdef MAPBASE + ViewDrawScene_Intro( viewRender, nClearFlags, *g_pIntroData, bDrew3dSkybox, nSkyboxVisible, whatToDraw & RENDERVIEW_DRAWVIEWMODEL ); + bDrawnViewmodel = true; +#else ViewDrawScene_Intro( viewRender, nClearFlags, *g_pIntroData ); +#endif } // We can still use the 'current view' stuff set up in ViewDrawScene @@ -2140,15 +2326,25 @@ void CViewRender::RenderView( const CViewSetup &viewRender, int nClearFlags, int RenderPlayerSprites(); // Image-space motion blur - if ( !building_cubemaps.GetBool() && viewRender.m_bDoBloomAndToneMapping ) // We probably should use a different view. variable here + if ( !building_cubemaps.GetBool() /*&& viewRender.m_bDoBloomAndToneMapping*/ ) // We probably should use a different view. variable here { + if ( IsDepthOfFieldEnabled() ) + { + pRenderContext.GetFrom( materials ); + { + PIXEVENT( pRenderContext, "DoDepthOfField()" ); + DoDepthOfField( viewRender ); + } + pRenderContext.SafeRelease(); + } + static ConVarRef mat_motion_blur_enabled( "mat_motion_blur_enabled" ); if ( ( mat_motion_blur_enabled.GetInt() ) && ( g_pMaterialSystemHardwareConfig->GetDXSupportLevel() >= 90 ) ) { pRenderContext.GetFrom( materials ); { PIXEVENT( pRenderContext, "DoImageSpaceMotionBlur" ); - DoImageSpaceMotionBlur( viewRender, viewRender.x, viewRender.y, viewRender.width, viewRender.height ); + DoImageSpaceMotionBlur( viewRender ); } pRenderContext.SafeRelease(); } @@ -2157,6 +2353,9 @@ void CViewRender::RenderView( const CViewSetup &viewRender, int nClearFlags, int GetClientModeNormal()->DoPostScreenSpaceEffects( &viewRender ); // Now actually draw the viewmodel +#ifdef MAPBASE + if (!bDrawnViewmodel) +#endif DrawViewModels( viewRender, whatToDraw & RENDERVIEW_DRAWVIEWMODEL ); DrawUnderwaterOverlay(); @@ -2675,6 +2874,34 @@ void CViewRender::DrawWorldAndEntities( bool bDrawSkybox, const CViewSetup &view { tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "bCheapWater" ); cplane_t glassReflectionPlane; +#ifdef MAPBASE + // New expansions allow for custom render targets and multiple mirror renders + Frustum_t frustum; + GeneratePerspectiveFrustum( viewIn.origin, viewIn.angles, viewIn.zNear, viewIn.zFar, viewIn.fov, viewIn.m_flAspectRatio, frustum ); + + ITexture *pTextureTargets[2]; + C_BaseEntity *pReflectiveGlass = NextReflectiveGlass( NULL, viewIn, glassReflectionPlane, frustum, pTextureTargets ); + while ( pReflectiveGlass != NULL ) + { + if (pTextureTargets[0]) + { + CRefPtr pGlassReflectionView = new CReflectiveGlassView( this ); + pGlassReflectionView->m_pRenderTarget = pTextureTargets[0]; + pGlassReflectionView->Setup( viewIn, VIEW_CLEAR_DEPTH | VIEW_CLEAR_COLOR, bDrawSkybox, fogVolumeInfo, info, glassReflectionPlane ); + AddViewToScene( pGlassReflectionView ); + } + + if (pTextureTargets[1]) + { + CRefPtr pGlassRefractionView = new CRefractiveGlassView( this ); + pGlassRefractionView->Setup( viewIn, VIEW_CLEAR_DEPTH | VIEW_CLEAR_COLOR, bDrawSkybox, fogVolumeInfo, info, glassReflectionPlane ); + pGlassRefractionView->m_pRenderTarget = pTextureTargets[1]; + AddViewToScene( pGlassRefractionView ); + } + + pReflectiveGlass = NextReflectiveGlass( pReflectiveGlass, viewIn, glassReflectionPlane, frustum, pTextureTargets ); + } +#else if ( IsReflectiveGlassInView( viewIn, glassReflectionPlane ) ) { CRefPtr pGlassReflectionView = new CReflectiveGlassView( this ); @@ -2685,6 +2912,7 @@ void CViewRender::DrawWorldAndEntities( bool bDrawSkybox, const CViewSetup &view pGlassRefractionView->Setup( viewIn, VIEW_CLEAR_DEPTH | VIEW_CLEAR_COLOR, bDrawSkybox, fogVolumeInfo, info, glassReflectionPlane ); AddViewToScene( pGlassRefractionView ); } +#endif CRefPtr pNoWaterView = new CSimpleWorldView( this ); pNoWaterView->Setup( viewIn, nClearFlags, bDrawSkybox, fogVolumeInfo, info, pCustomVisibility ); @@ -2975,7 +3203,12 @@ void CViewRender::GetWaterLODParams( float &flCheapWaterStartDistance, float &fl // Input : &view - // &introData - //----------------------------------------------------------------------------- +#ifdef MAPBASE +void CViewRender::ViewDrawScene_Intro( const CViewSetup &viewRender, int nClearFlags, const IntroData_t &introData, + bool bDrew3dSkybox, SkyboxVisibility_t nSkyboxVisible, bool bDrawViewModel, ViewCustomVisibility_t *pCustomVisibility ) +#else void CViewRender::ViewDrawScene_Intro( const CViewSetup &viewRender, int nClearFlags, const IntroData_t &introData ) +#endif { VPROF( "CViewRender::ViewDrawScene" ); @@ -3005,11 +3238,43 @@ void CViewRender::ViewDrawScene_Intro( const CViewSetup &viewRender, int nClearF CViewSetup playerView( viewRender ); playerView.origin = introData.m_vecCameraView; playerView.angles = introData.m_vecCameraViewAngles; +#ifdef MAPBASE + // Ortho handling (change this code if we ever use m_hCameraEntity for other things) + if (introData.m_hCameraEntity /*&& introData.m_hCameraEntity->IsOrtho()*/) + { + playerView.m_bOrtho = true; + introData.m_hCameraEntity->GetOrthoDimensions( playerView.m_OrthoTop, playerView.m_OrthoBottom, + playerView.m_OrthoLeft, playerView.m_OrthoRight ); + } +#endif if ( introData.m_playerViewFOV ) { playerView.fov = ScaleFOVByWidthRatio( introData.m_playerViewFOV, engine->GetScreenAspectRatio() / ( 4.0f / 3.0f ) ); } +#ifdef MAPBASE + bool drawSkybox; + int nViewFlags; + if (introData.m_bDrawSky2) + { + drawSkybox = r_skybox.GetBool(); + nViewFlags = VIEW_CLEAR_DEPTH; + + // if the 3d skybox world is drawn, then don't draw the normal skybox + CSkyboxView *pSkyView = new CSkyboxView( this ); + if ( ( bDrew3dSkybox = pSkyView->Setup( playerView, &nClearFlags, &nSkyboxVisible ) ) != false ) + { + AddViewToScene( pSkyView ); + } + SafeRelease( pSkyView ); + } + else + { + drawSkybox = false; + nViewFlags = (VIEW_CLEAR_COLOR | VIEW_CLEAR_DEPTH); + } +#endif + g_pClientShadowMgr->PreRender(); // Shadowed flashlights supported on ps_2_b and up... @@ -3024,10 +3289,38 @@ void CViewRender::ViewDrawScene_Intro( const CViewSetup &viewRender, int nClearF IGameSystem::PreRenderAllSystems(); // Start view, clear frame/z buffer if necessary +#ifdef MAPBASE + SetupVis( playerView, visFlags, pCustomVisibility ); +#else SetupVis( playerView, visFlags ); +#endif + +#ifdef MAPBASE + if (introData.m_bDrawSky2) + { + if ( !bDrew3dSkybox && + ( nSkyboxVisible == SKYBOX_NOT_VISIBLE ) /*&& ( visFlags & IVRenderView::VIEW_SETUP_VIS_EX_RETURN_FLAGS_USES_RADIAL_VIS )*/ ) + { + // This covers the case where we don't see a 3dskybox, yet radial vis is clipping + // the far plane. Need to clear to fog color in this case. + nClearFlags |= VIEW_CLEAR_COLOR; + //SetClearColorToFogColor( ); + } + if ( bDrew3dSkybox || ( nSkyboxVisible == SKYBOX_NOT_VISIBLE ) ) + { + drawSkybox = false; + } + } +#endif + +#ifdef MAPBASE + render->Push3DView( playerView, nViewFlags, NULL, GetFrustum() ); + DrawWorldAndEntities( drawSkybox, playerView, nViewFlags ); +#else render->Push3DView( playerView, VIEW_CLEAR_COLOR | VIEW_CLEAR_DEPTH, NULL, GetFrustum() ); DrawWorldAndEntities( true /* drawSkybox */, playerView, VIEW_CLEAR_COLOR | VIEW_CLEAR_DEPTH ); +#endif render->PopView( GetFrustum() ); // Free shadow depth textures for use in future view @@ -3043,12 +3336,28 @@ void CViewRender::ViewDrawScene_Intro( const CViewSetup &viewRender, int nClearF Rect_t actualRect; UpdateScreenEffectTexture( 0, viewRender.x, viewRender.y, viewRender.width, viewRender.height, false, &actualRect ); +#ifdef MAPBASE + if (introData.m_bDrawSky) + { + // if the 3d skybox world is drawn, then don't draw the normal skybox + CSkyboxView *pSkyView = new CSkyboxView( this ); + if ( ( bDrew3dSkybox = pSkyView->Setup( viewRender, &nClearFlags, &nSkyboxVisible ) ) != false ) + { + AddViewToScene( pSkyView ); + } + SafeRelease( pSkyView ); + } +#endif + g_pClientShadowMgr->PreRender(); // Shadowed flashlights supported on ps_2_b and up... if ( r_flashlightdepthtexture.GetBool() ) { g_pClientShadowMgr->ComputeShadowDepthTextures( viewRender ); +#ifdef ASW_PROJECTED_TEXTURES + CMatRenderContextPtr pRenderContext( materials ); +#endif } // ----------------------------------------------------------------------- @@ -3065,7 +3374,41 @@ void CViewRender::ViewDrawScene_Intro( const CViewSetup &viewRender, int nClearF // Clear alpha to 255 so that masking with the vortigaunts (0) works properly. pRenderContext->ClearColor4ub( 0, 0, 0, 255 ); +#ifdef MAPBASE + bool drawSkybox; + int nViewFlags; + if (introData.m_bDrawSky) + { + drawSkybox = r_skybox.GetBool(); + nViewFlags = VIEW_CLEAR_DEPTH; + + if ( !bDrew3dSkybox && + ( nSkyboxVisible == SKYBOX_NOT_VISIBLE ) /*&& ( visFlags & IVRenderView::VIEW_SETUP_VIS_EX_RETURN_FLAGS_USES_RADIAL_VIS )*/ ) + { + // This covers the case where we don't see a 3dskybox, yet radial vis is clipping + // the far plane. Need to clear to fog color in this case. + nViewFlags |= VIEW_CLEAR_COLOR; + //SetClearColorToFogColor( ); + } + + if ( bDrew3dSkybox || ( nSkyboxVisible == SKYBOX_NOT_VISIBLE ) ) + { + drawSkybox = false; + } + } + else + { + drawSkybox = false; + nViewFlags = (VIEW_CLEAR_COLOR | VIEW_CLEAR_DEPTH); + } + + DrawWorldAndEntities( drawSkybox, viewRender, nViewFlags ); + + // Solution for viewmodels not drawing in script_intro + DrawViewModels( viewRender, bDrawViewModel ); +#else DrawWorldAndEntities( true /* drawSkybox */, viewRender, VIEW_CLEAR_COLOR | VIEW_CLEAR_DEPTH ); +#endif UpdateScreenEffectTexture( 1, viewRender.x, viewRender.y, viewRender.width, viewRender.height ); @@ -3144,6 +3487,11 @@ void CViewRender::ViewDrawScene_Intro( const CViewSetup &viewRender, int nClearF // Let the particle manager simulate things that haven't been simulated. ParticleMgr()->PostRender(); +#ifdef MAPBASE + // Invoke post-render methods + IGameSystem::PostRenderAllSystems(); +#endif + FinishCurrentView(); // Free shadow depth textures for use in future view @@ -3214,15 +3562,230 @@ bool CViewRender::DrawOneMonitor( ITexture *pRenderTarget, int cameraNum, C_Poin monitorView.origin = pCameraEnt->GetAbsOrigin(); monitorView.angles = pCameraEnt->GetAbsAngles(); monitorView.fov = pCameraEnt->GetFOV(); +#ifdef MAPBASE + if (pCameraEnt->IsOrtho()) + { + monitorView.m_bOrtho = true; + pCameraEnt->GetOrthoDimensions( monitorView.m_OrthoTop, monitorView.m_OrthoBottom, + monitorView.m_OrthoLeft, monitorView.m_OrthoRight ); + } + else + { + monitorView.m_bOrtho = false; + } +#else monitorView.m_bOrtho = false; +#endif monitorView.m_flAspectRatio = pCameraEnt->UseScreenAspectRatio() ? 0.0f : 1.0f; monitorView.m_bViewToProjectionOverride = false; +#ifdef MAPBASE + // + // Monitor sky handling + // + SkyboxVisibility_t nSkyMode = pCameraEnt->SkyMode(); + if ( nSkyMode == SKYBOX_3DSKYBOX_VISIBLE ) + { + int nClearFlags = (VIEW_CLEAR_DEPTH | VIEW_CLEAR_COLOR); + bool bDrew3dSkybox = false; + + Frustum frustum; + render->Push3DView( monitorView, nClearFlags, pRenderTarget, (VPlane *)frustum ); + + // if the 3d skybox world is drawn, then don't draw the normal skybox + CSkyboxView *pSkyView = new CSkyboxView( this ); + if ( ( bDrew3dSkybox = pSkyView->Setup( monitorView, &nClearFlags, &nSkyMode ) ) != false ) + { + AddViewToScene( pSkyView ); + } + SafeRelease( pSkyView ); + + ViewDrawScene( bDrew3dSkybox, nSkyMode, monitorView, nClearFlags, VIEW_MONITOR ); + render->PopView( frustum ); + } + else if (nSkyMode == SKYBOX_NOT_VISIBLE) + { + // @MULTICORE (toml 8/11/2006): this should be a renderer.... + Frustum frustum; + render->Push3DView( monitorView, VIEW_CLEAR_DEPTH, pRenderTarget, (VPlane *)frustum ); + + CMatRenderContextPtr pRenderContext( materials ); + pRenderContext->PushRenderTargetAndViewport( pRenderTarget ); + pRenderContext->SetIntRenderingParameter( INT_RENDERPARM_WRITE_DEPTH_TO_DESTALPHA, 1 ); + if ( pRenderTarget ) + { + pRenderContext->OverrideAlphaWriteEnable( true, true ); + } + + ViewDrawScene( false, nSkyMode, monitorView, 0, VIEW_MONITOR ); + + pRenderContext->PopRenderTargetAndViewport(); + render->PopView( frustum ); + } + else + { + // @MULTICORE (toml 8/11/2006): this should be a renderer.... + Frustum frustum; + render->Push3DView( monitorView, VIEW_CLEAR_DEPTH | VIEW_CLEAR_COLOR, pRenderTarget, (VPlane *)frustum ); + ViewDrawScene( false, nSkyMode, monitorView, 0, VIEW_MONITOR ); + render->PopView( frustum ); + } +#else // @MULTICORE (toml 8/11/2006): this should be a renderer.... Frustum frustum; render->Push3DView( monitorView, VIEW_CLEAR_DEPTH | VIEW_CLEAR_COLOR, pRenderTarget, (VPlane *)frustum ); ViewDrawScene( false, SKYBOX_2DSKYBOX_VISIBLE, monitorView, 0, VIEW_MONITOR ); render->PopView( frustum ); +#endif + + // Reset the world fog parameters. + if ( fogEnabled ) + { + if ( pFogParams ) + { + *pFogParams = oldFogParams; + } + monitorView.zFar = flOldZFar; + } +#endif // USE_MONITORS + return true; +} + +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: Sets up scene and renders WIP fake world portal view. +// Based on code from monitors, mirrors, and logic_measure_movement. +// +// Input : cameraNum - +// &cameraView +// *localPlayer - +// x - +// y - +// width - +// height - +// highend - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CViewRender::DrawFakeWorldPortal( ITexture *pRenderTarget, C_FuncFakeWorldPortal *pCameraEnt, const CViewSetup &cameraView, C_BasePlayer *localPlayer, + int x, int y, int width, int height, + const CViewSetup &mainView, const Vector &vecAbsPlaneNormal, float flLocalPlaneDist ) +{ +#ifdef USE_MONITORS + VPROF_INCREMENT_COUNTER( "cameras rendered", 1 ); + // Setup fog state for the camera. + fogparams_t oldFogParams; + float flOldZFar = 0.0f; + + // If fog should be disabled instead of using the player's controller, a blank fog controller can just be used + bool fogEnabled = true; //pCameraEnt->IsFogEnabled(); + + CViewSetup monitorView = cameraView; + + fogparams_t *pFogParams = NULL; + + if ( fogEnabled ) + { + if ( !localPlayer ) + return false; + + pFogParams = localPlayer->GetFogParams(); + + // Save old fog data. + oldFogParams = *pFogParams; + + if ( pCameraEnt->GetFog() ) + { + *pFogParams = *pCameraEnt->GetFog(); + } + } + + monitorView.x = x; + monitorView.y = y; + monitorView.width = width; + monitorView.height = height; + monitorView.m_bOrtho = mainView.m_bOrtho; + monitorView.fov = mainView.fov; + monitorView.m_flAspectRatio = mainView.m_flAspectRatio; + monitorView.m_bViewToProjectionOverride = false; + + matrix3x4_t worldToView; + AngleIMatrix( mainView.angles, mainView.origin, worldToView ); + + matrix3x4_t targetToWorld; + { + // NOTE: m_PlaneAngles is angle offset + QAngle targetAngles = pCameraEnt->m_hTargetPlane->GetAbsAngles() - pCameraEnt->m_PlaneAngles; + AngleMatrix( targetAngles, pCameraEnt->m_hTargetPlane->GetAbsOrigin(), targetToWorld ); + } + + matrix3x4_t portalToWorld; + { + Vector left, up; + VectorVectors( vecAbsPlaneNormal, left, up ); + VectorNegate( left ); + portalToWorld.Init( vecAbsPlaneNormal, left, up, pCameraEnt->GetAbsOrigin() ); + } + + matrix3x4_t portalToView; + ConcatTransforms( worldToView, portalToWorld, portalToView ); + + if ( pCameraEnt->m_flScale > 0.0f ) + { + portalToView[0][3] /= pCameraEnt->m_flScale; + portalToView[1][3] /= pCameraEnt->m_flScale; + portalToView[2][3] /= pCameraEnt->m_flScale; + } + + matrix3x4_t viewToPortal; + MatrixInvert( portalToView, viewToPortal ); + + matrix3x4_t newViewToWorld; + ConcatTransforms( targetToWorld, viewToPortal, newViewToWorld ); + + MatrixAngles( newViewToWorld, monitorView.angles, monitorView.origin ); + + + // @MULTICORE (toml 8/11/2006): this should be a renderer.... + int nClearFlags = (VIEW_CLEAR_DEPTH | VIEW_CLEAR_COLOR | VIEW_CLEAR_OBEY_STENCIL); + bool bDrew3dSkybox = false; + + Frustum frustum; + render->Push3DView( monitorView, nClearFlags, pRenderTarget, (VPlane *)frustum ); + + // + // Sky handling + // + SkyboxVisibility_t nSkyMode = pCameraEnt->SkyMode(); + if ( nSkyMode == SKYBOX_3DSKYBOX_VISIBLE ) + { + // if the 3d skybox world is drawn, then don't draw the normal skybox + CSkyboxView *pSkyView = new CSkyboxView( this ); + if ( ( bDrew3dSkybox = pSkyView->Setup( monitorView, &nClearFlags, &nSkyMode ) ) != false ) + { + AddViewToScene( pSkyView ); + } + SafeRelease( pSkyView ); + } + + Vector4D plane; + + // target direction + MatrixGetColumn( targetToWorld, 0, plane.AsVector3D() ); + VectorNormalize( plane.AsVector3D() ); + VectorNegate( plane.AsVector3D() ); + + plane.w = + MatrixColumnDotProduct( targetToWorld, 3, plane.AsVector3D() ) // target clip plane distance + - flLocalPlaneDist // portal plane distance on the brush. This distance needs to be accounted for while placing the exit target + - 0.1; + + CMatRenderContextPtr pRenderContext( materials ); + pRenderContext->PushCustomClipPlane( plane.Base() ); + + ViewDrawScene( bDrew3dSkybox, nSkyMode, monitorView, nClearFlags, VIEW_MONITOR ); + + pRenderContext->PopCustomClipPlane(); + render->PopView( frustum ); // Reset the world fog parameters. if ( fogEnabled ) @@ -3236,6 +3799,7 @@ bool CViewRender::DrawOneMonitor( ITexture *pRenderTarget, int cameraNum, C_Poin #endif // USE_MONITORS return true; } +#endif void CViewRender::DrawMonitors( const CViewSetup &cameraView ) { @@ -3286,6 +3850,17 @@ void CViewRender::DrawMonitors( const CViewSetup &cameraView ) } #endif +#ifdef MAPBASE + // Check if the camera has its own render target + // (Multiple render target support) + if ( pCameraTarget != pCameraEnt->RenderTarget() ) + { + pCameraTarget = pCameraEnt->RenderTarget(); + width = pCameraTarget->GetActualWidth(); + height = pCameraTarget->GetActualHeight(); + } +#endif + if ( !DrawOneMonitor( pCameraTarget, cameraNum, pCameraEnt, cameraView, player, 0, 0, width, height ) ) continue; @@ -4882,7 +5457,20 @@ void CSkyboxView::DrawInternal( view_id_t iSkyBoxViewID, bool bInvokePreAndPostR // if you can get really close to the skybox geometry it's possible that you'll be able to clip into it // with this near plane. If so, move it in a bit. It's at 2.0 to give us more precision. That means you // need to keep the eye position at least 2 * scale away from the geometry in the skybox +#ifdef MAPBASE + zNear = r_nearz_skybox.GetFloat(); + + // Use the fog's farz if specified + if (m_pSky3dParams->fog.farz > 0) + { + zFar = ( m_pSky3dParams->scale > 0.0f ? + m_pSky3dParams->fog.farz / m_pSky3dParams->scale : + m_pSky3dParams->fog.farz ); + } + else +#else zNear = 2.0; +#endif zFar = MAX_TRACE_LENGTH; // scale origin by sky scale @@ -4892,13 +5480,56 @@ void CSkyboxView::DrawInternal( view_id_t iSkyBoxViewID, bool bInvokePreAndPostR VectorScale( origin, scale, origin ); } Enable3dSkyboxFog(); +#ifdef MAPBASE + // Skybox angle support. + // + // If any of the angles aren't 0, do the rotation code. + if (m_pSky3dParams->skycamera) + { + // Re-use the x coordinate to determine if we shuld do this with angles + if (m_pSky3dParams->angles.GetX() != 0) + { + const matrix3x4_t &matSky = m_pSky3dParams->skycamera->EntityToWorldTransform(); + matrix3x4_t matView; + AngleMatrix( angles, origin, matView ); + ConcatTransforms( matSky, matView, matView ); + MatrixAngles( matView, angles, origin ); + } + else + { + VectorAdd( origin, m_pSky3dParams->skycamera->GetAbsOrigin(), origin ); + } + } + else + { + if (m_pSky3dParams->angles.GetX() != 0 || + m_pSky3dParams->angles.GetY() != 0 || + m_pSky3dParams->angles.GetZ() != 0) + { + matrix3x4_t matSky, matView; + AngleMatrix( m_pSky3dParams->angles, m_pSky3dParams->origin, matSky ); + AngleMatrix( angles, origin, matView ); + ConcatTransforms( matSky, matView, matView ); + MatrixAngles( matView, angles, origin ); + } + else + { + VectorAdd( origin, m_pSky3dParams->origin, origin ); + } + } +#else VectorAdd( origin, m_pSky3dParams->origin, origin ); +#endif // BUGBUG: Fix this!!! We shouldn't need to call setup vis for the sky if we're connecting // the areas. We'd have to mark all the clusters in the skybox area in the PVS of any // cluster with sky. Then we could just connect the areas to do our vis. //m_bOverrideVisOrigin could hose us here, so call direct +#ifdef MAPBASE + render->ViewSetupVis( false, 1, m_pSky3dParams->skycamera ? &m_pSky3dParams->skycamera->GetAbsOrigin() : &m_pSky3dParams->origin.Get() ); +#else render->ViewSetupVis( false, 1, &m_pSky3dParams->origin.Get() ); +#endif render->Push3DView( (*this), m_ClearFlags, pRenderTarget, GetFrustum(), pDepthTarget ); // Store off view origin and angles @@ -4932,6 +5563,51 @@ void CSkyboxView::DrawInternal( view_id_t iSkyBoxViewID, bool bInvokePreAndPostR DrawTranslucentRenderables( true, false ); DrawNoZBufferTranslucentRenderables(); +#ifdef MAPBASE + // Allows reflective glass to be drawn in the skybox. + // New expansions also allow for custom render targets and multiple mirror renders + if (r_skybox_use_complex_views.GetBool()) + { + VisibleFogVolumeInfo_t fogVolumeInfo; + render->GetVisibleFogVolume( origin, &fogVolumeInfo ); + + WaterRenderInfo_t info; + info.m_bCheapWater = true; + info.m_bRefract = false; + info.m_bReflect = false; + info.m_bReflectEntities = false; + info.m_bDrawWaterSurface = false; + info.m_bOpaqueWater = true; + + cplane_t glassReflectionPlane; + Frustum_t frustum; + GeneratePerspectiveFrustum( origin, angles, zNear, zFar, fov, m_flAspectRatio, frustum ); + + ITexture *pTextureTargets[2]; + C_BaseEntity *pReflectiveGlass = NextReflectiveGlass( NULL, (*this), glassReflectionPlane, frustum, pTextureTargets ); + while ( pReflectiveGlass != NULL ) + { + if (pTextureTargets[0]) + { + CRefPtr pGlassReflectionView = new CReflectiveGlassView( m_pMainView ); + pGlassReflectionView->m_pRenderTarget = pTextureTargets[0]; + pGlassReflectionView->Setup( (*this), VIEW_CLEAR_DEPTH, true, fogVolumeInfo, info, glassReflectionPlane ); + m_pMainView->AddViewToScene( pGlassReflectionView ); + } + + if (pTextureTargets[1]) + { + CRefPtr pGlassRefractionView = new CRefractiveGlassView( m_pMainView ); + pGlassRefractionView->m_pRenderTarget = pTextureTargets[1]; + pGlassRefractionView->Setup( (*this), VIEW_CLEAR_DEPTH, true, fogVolumeInfo, info, glassReflectionPlane ); + m_pMainView->AddViewToScene( pGlassRefractionView ); + } + + pReflectiveGlass = NextReflectiveGlass( pReflectiveGlass, (*this), glassReflectionPlane, frustum, pTextureTargets ); + } + } +#endif + m_pMainView->DisableFog(); CGlowOverlay::UpdateSkyOverlays( zFar, m_bCacheFullSceneState ); @@ -4979,6 +5655,20 @@ bool CSkyboxView::Setup( const CViewSetup &viewRender, int *pClearFlags, SkyboxV *pClearFlags |= VIEW_CLEAR_DEPTH; // Need to clear depth after rednering the skybox m_DrawFlags = DF_RENDER_UNDERWATER | DF_RENDER_ABOVEWATER | DF_RENDER_WATER; +#ifdef MAPBASE + if (m_pSky3dParams->skycolor.GetA() != 0 && *pSkyboxVisible != SKYBOX_NOT_VISIBLE) + { + m_ClearFlags |= (VIEW_CLEAR_COLOR | VIEW_CLEAR_DEPTH); + m_DrawFlags |= DF_CLIP_SKYBOX; + + color32 color = m_pSky3dParams->skycolor.Get(); + + CMatRenderContextPtr pRenderContext( materials ); + pRenderContext->ClearColor4ub( color.r, color.g, color.b, color.a ); + pRenderContext.SafeRelease(); + } + else +#endif if( r_skybox.GetBool() ) { m_DrawFlags |= DF_DRAWSKYBOX; @@ -5252,7 +5942,7 @@ bool CBaseWorldView::AdjustView( float waterHeight ) { if( m_DrawFlags & DF_RENDER_REFRACTION ) { - ITexture *pTexture = GetWaterRefractionTexture(); + ITexture *pTexture = GetRefractionTexture(); // Use the aspect ratio of the main view! So, don't recompute it here x = y = 0; @@ -5264,7 +5954,7 @@ bool CBaseWorldView::AdjustView( float waterHeight ) if( m_DrawFlags & DF_RENDER_REFLECTION ) { - ITexture *pTexture = GetWaterReflectionTexture(); + ITexture *pTexture = GetReflectionTexture(); // If the main view is overriding the projection matrix (for Stereo or // some other nefarious purpose) make sure to include any Y offset in @@ -5326,14 +6016,14 @@ void CBaseWorldView::PushView( float waterHeight ) pRenderContext->SetHeightClipMode( clipMode ); // Have to re-set up the view since we reset the size - render->Push3DView( *this, m_ClearFlags, GetWaterRefractionTexture(), GetFrustum() ); + render->Push3DView( *this, m_ClearFlags, GetRefractionTexture(), GetFrustum() ); return; } if( m_DrawFlags & DF_RENDER_REFLECTION ) { - ITexture *pTexture = GetWaterReflectionTexture(); + ITexture *pTexture = GetReflectionTexture(); pRenderContext->SetFogZ( waterHeight ); @@ -5387,11 +6077,11 @@ void CBaseWorldView::PopView() // these renders paths used their surfaces, so blit their results if ( m_DrawFlags & DF_RENDER_REFRACTION ) { - pRenderContext->CopyRenderTargetToTextureEx( GetWaterRefractionTexture(), NULL, NULL ); + pRenderContext->CopyRenderTargetToTextureEx( GetRefractionTexture(), NULL, NULL ); } if ( m_DrawFlags & DF_RENDER_REFLECTION ) { - pRenderContext->CopyRenderTargetToTextureEx( GetWaterReflectionTexture(), NULL, NULL ); + pRenderContext->CopyRenderTargetToTextureEx( GetReflectionTexture(), NULL, NULL ); } } @@ -6268,7 +6958,11 @@ void CReflectiveGlassView::Setup( const CViewSetup &viewRender, int nClearFlags, bool CReflectiveGlassView::AdjustView( float flWaterHeight ) { +#ifdef MAPBASE + ITexture *pTexture = GetReflectionTexture(); +#else ITexture *pTexture = GetWaterReflectionTexture(); +#endif // Use the aspect ratio of the main view! So, don't recompute it here x = y = 0; @@ -6294,7 +6988,11 @@ bool CReflectiveGlassView::AdjustView( float flWaterHeight ) void CReflectiveGlassView::PushView( float waterHeight ) { +#ifdef MAPBASE + render->Push3DView( *this, m_ClearFlags, GetReflectionTexture(), GetFrustum() ); +#else render->Push3DView( *this, m_ClearFlags, GetWaterReflectionTexture(), GetFrustum() ); +#endif Vector4D plane; VectorCopy( m_ReflectionPlane.normal, plane.AsVector3D() ); @@ -6322,6 +7020,12 @@ void CReflectiveGlassView::Draw() CMatRenderContextPtr pRenderContext( materials ); PIXEVENT( pRenderContext, "CReflectiveGlassView::Draw" ); +#ifdef MAPBASE + // Store off view origin and angles and set the new view + int nSaveViewID = CurrentViewID(); + SetupCurrentView( origin, angles, VIEW_REFLECTION ); +#endif + // Disable occlusion visualization in reflection bool bVisOcclusion = r_visocclusion.GetInt(); r_visocclusion.SetValue( 0 ); @@ -6330,6 +7034,11 @@ void CReflectiveGlassView::Draw() r_visocclusion.SetValue( bVisOcclusion ); +#ifdef MAPBASE + // finish off the view. restore the previous view. + SetupCurrentView( origin, angles, (view_id_t)nSaveViewID ); +#endif + pRenderContext->ClearColor4ub( 0, 0, 0, 255 ); pRenderContext->Flush(); } @@ -6349,7 +7058,11 @@ void CRefractiveGlassView::Setup( const CViewSetup &viewRender, int nClearFlags, bool CRefractiveGlassView::AdjustView( float flWaterHeight ) { +#ifdef MAPBASE + ITexture *pTexture = GetRefractionTexture(); +#else ITexture *pTexture = GetWaterRefractionTexture(); +#endif // Use the aspect ratio of the main view! So, don't recompute it here x = y = 0; @@ -6361,7 +7074,11 @@ bool CRefractiveGlassView::AdjustView( float flWaterHeight ) void CRefractiveGlassView::PushView( float waterHeight ) { +#ifdef MAPBASE + render->Push3DView( *this, m_ClearFlags, GetRefractionTexture(), GetFrustum() ); +#else render->Push3DView( *this, m_ClearFlags, GetWaterRefractionTexture(), GetFrustum() ); +#endif Vector4D plane; VectorMultiply( m_ReflectionPlane.normal, -1, plane.AsVector3D() ); @@ -6391,8 +7108,19 @@ void CRefractiveGlassView::Draw() CMatRenderContextPtr pRenderContext( materials ); PIXEVENT( pRenderContext, "CRefractiveGlassView::Draw" ); +#ifdef MAPBASE + // Store off view origin and angles and set the new view + int nSaveViewID = CurrentViewID(); + SetupCurrentView( origin, angles, VIEW_REFRACTION ); +#endif + BaseClass::Draw(); +#ifdef MAPBASE + // finish off the view. restore the previous view. + SetupCurrentView( origin, angles, (view_id_t)nSaveViewID ); +#endif + pRenderContext->ClearColor4ub( 0, 0, 0, 255 ); pRenderContext->Flush(); } diff --git a/src/game/client/viewrender.h b/src/game/client/viewrender.h index 052e3a7b..f732914f 100644 --- a/src/game/client/viewrender.h +++ b/src/game/client/viewrender.h @@ -37,6 +37,10 @@ class CReplayScreenshotTaker; class CStunEffect; #endif // HL2_EPISODIC +#ifdef MAPBASE + class C_FuncFakeWorldPortal; +#endif + //----------------------------------------------------------------------------- // Data specific to intro mode to control rendering. //----------------------------------------------------------------------------- @@ -51,11 +55,22 @@ struct IntroData_t bool m_bDrawPrimary; Vector m_vecCameraView; QAngle m_vecCameraViewAngles; +#ifdef MAPBASE + // Used for ortho views + CHandle m_hCameraEntity; +#endif float m_playerViewFOV; CUtlVector m_Passes; // Fade overriding for the intro float m_flCurrentFadeColor[4]; + +#ifdef MAPBASE + // Draws the skybox. + bool m_bDrawSky; + // Draws the skybox in the secondary camera as well. + bool m_bDrawSky2; +#endif }; // Robin, make this point at something to get intro mode. @@ -436,6 +451,12 @@ private: bool DrawOneMonitor( ITexture *pRenderTarget, int cameraNum, C_PointCamera *pCameraEnt, const CViewSetup &cameraView, C_BasePlayer *localPlayer, int x, int y, int width, int height ); +#ifdef MAPBASE + bool DrawFakeWorldPortal( ITexture *pRenderTarget, C_FuncFakeWorldPortal *pCameraEnt, const CViewSetup &cameraView, C_BasePlayer *localPlayer, + int x, int y, int width, int height, + const CViewSetup &mainView, const Vector &vecAbsPlaneNormal, float flLocalPlaneDist ); +#endif + // Drawing primitives bool ShouldDrawViewModel( bool drawViewmodel ); void DrawViewModels( const CViewSetup &view, bool drawViewmodel ); @@ -447,12 +468,23 @@ private: IMaterial *GetScreenOverlayMaterial( ); void PerformScreenOverlay( int x, int y, int w, int h ); +#ifdef MAPBASE + void SetIndexedScreenOverlayMaterial( int i, IMaterial *pMaterial ); + IMaterial *GetIndexedScreenOverlayMaterial( int i ); + void ResetIndexedScreenOverlays(); + int GetMaxIndexedScreenOverlays() const; +#endif + void DrawUnderwaterOverlay( void ); // Water-related methods void DrawWorldAndEntities( bool drawSkybox, const CViewSetup &view, int nClearFlags, ViewCustomVisibility_t *pCustomVisibility = NULL ); +#ifdef MAPBASE + virtual void ViewDrawScene_Intro( const CViewSetup &view, int nClearFlags, const IntroData_t &introData, bool bDrew3dSkybox = false, SkyboxVisibility_t nSkyboxVisible = SKYBOX_NOT_VISIBLE, bool bDrawViewModel = false, ViewCustomVisibility_t *pCustomVisibility = NULL ); +#else virtual void ViewDrawScene_Intro( const CViewSetup &view, int nClearFlags, const IntroData_t &introData ); +#endif #ifdef PORTAL // Intended for use in the middle of another ViewDrawScene call, this allows stencils to be drawn after opaques but before translucents are drawn in the main view. @@ -486,6 +518,10 @@ private: CMaterialReference m_TranslucentSingleColor; CMaterialReference m_ModulateSingleColor; CMaterialReference m_ScreenOverlayMaterial; +#ifdef MAPBASE + CMaterialReference m_IndexedScreenOverlayMaterials[MAX_SCREEN_OVERLAYS]; + bool m_bUsingIndexedScreenOverlays; +#endif CMaterialReference m_UnderWaterOverlayMaterial; CMaterialReference m_ScriptOverlayMaterial; diff --git a/src/game/client/vscript_client.cpp b/src/game/client/vscript_client.cpp index 57e00dc5..c1143f4c 100644 --- a/src/game/client/vscript_client.cpp +++ b/src/game/client/vscript_client.cpp @@ -13,8 +13,10 @@ #include "characterset.h" #include "isaverestore.h" #include "gamerules.h" -#include "vscript_client_nut.h" +#include "vscript_client.nut" +#ifndef MAPBASE_VSCRIPT #include "gameui/gameui_interface.h" +#endif #ifdef PANORAMA_ENABLE #include "panorama/panorama.h" @@ -28,6 +30,17 @@ #include "matchmaking/imatchframework.h" #endif // PORTAL2_PUZZLEMAKER +#ifdef MAPBASE_VSCRIPT +#include "view.h" +#include "c_world.h" +#include "proxyentity.h" +#include "materialsystem/imaterial.h" +#include "materialsystem/imaterialvar.h" +#include "mapbase/matchers.h" +#include "mapbase/vscript_singletons.h" +#include "mapbase/vscript_vgui.h" +#endif + extern IScriptManager *scriptmanager; extern ScriptClassDesc_t * GetScriptDesc( CBaseEntity * ); @@ -45,6 +58,456 @@ extern ScriptClassDesc_t * GetScriptDesc( CBaseEntity * ); #endif // VMPROFILE +#ifdef MAPBASE_VSCRIPT +static ScriptHook_t g_Hook_OnEntityCreated; +static ScriptHook_t g_Hook_OnEntityDeleted; + +//----------------------------------------------------------------------------- +// Purpose: A clientside variant of CScriptEntityIterator. +//----------------------------------------------------------------------------- +class CScriptClientEntityIterator : public IClientEntityListener +{ +public: + HSCRIPT GetLocalPlayer() + { + return ToHScript( C_BasePlayer::GetLocalPlayer() ); + } + + HSCRIPT First() { return Next(NULL); } + + HSCRIPT Next( HSCRIPT hStartEntity ) + { + return ToHScript( ClientEntityList().NextBaseEntity( ToEnt( hStartEntity ) ) ); + } + + HSCRIPT CreateByClassname( const char *className ) + { + return ToHScript( CreateEntityByName( className ) ); + } + + HSCRIPT FindByClassname( HSCRIPT hStartEntity, const char *szName ) + { + const CEntInfo *pInfo = hStartEntity ? ClientEntityList().GetEntInfoPtr( ToEnt( hStartEntity )->GetRefEHandle() )->m_pNext : ClientEntityList().FirstEntInfo(); + for ( ;pInfo; pInfo = pInfo->m_pNext ) + { + C_BaseEntity *ent = (C_BaseEntity *)pInfo->m_pEntity; + if ( !ent ) + continue; + + if ( Matcher_Match( szName, ent->GetClassname() ) ) + return ToHScript( ent ); + } + + return NULL; + } + + HSCRIPT FindByName( HSCRIPT hStartEntity, const char *szName ) + { + const CEntInfo *pInfo = hStartEntity ? ClientEntityList().GetEntInfoPtr( ToEnt( hStartEntity )->GetRefEHandle() )->m_pNext : ClientEntityList().FirstEntInfo(); + for ( ;pInfo; pInfo = pInfo->m_pNext ) + { + C_BaseEntity *ent = (C_BaseEntity *)pInfo->m_pEntity; + if ( !ent ) + continue; + + if ( Matcher_Match( szName, ent->GetEntityName() ) ) + return ToHScript( ent ); + } + + return NULL; + } + + void EnableEntityListening() + { + // Start getting entity updates! + ClientEntityList().AddListenerEntity( this ); + } + + void DisableEntityListening() + { + // Stop getting entity updates! + ClientEntityList().RemoveListenerEntity( this ); + } + + void OnEntityCreated( CBaseEntity *pEntity ) + { + if ( g_pScriptVM && GetScriptHookManager().IsEventHooked( "OnEntityCreated" ) ) + { + // entity + ScriptVariant_t args[] = { ScriptVariant_t( pEntity->GetScriptInstance() ) }; + g_Hook_OnEntityCreated.Call( NULL, NULL, args ); + } + }; + + void OnEntityDeleted( CBaseEntity *pEntity ) + { + if ( g_pScriptVM && GetScriptHookManager().IsEventHooked( "OnEntityDeleted" ) ) + { + // entity + ScriptVariant_t args[] = { ScriptVariant_t( pEntity->GetScriptInstance() ) }; + g_Hook_OnEntityDeleted.Call( NULL, NULL, args ); + } + }; + +private: +} g_ScriptEntityIterator; + +BEGIN_SCRIPTDESC_ROOT_NAMED( CScriptClientEntityIterator, "CEntities", SCRIPT_SINGLETON "The global list of entities" ) + DEFINE_SCRIPTFUNC( GetLocalPlayer, "Get local player" ) + DEFINE_SCRIPTFUNC( First, "Begin an iteration over the list of entities" ) + DEFINE_SCRIPTFUNC( Next, "Continue an iteration over the list of entities, providing reference to a previously found entity" ) + DEFINE_SCRIPTFUNC( CreateByClassname, "Creates an entity by classname" ) + DEFINE_SCRIPTFUNC( FindByClassname, "Find entities by class name. Pass 'null' to start an iteration, or reference to a previously found entity to continue a search" ) + DEFINE_SCRIPTFUNC( FindByName, "Find entities by name. Pass 'null' to start an iteration, or reference to a previously found entity to continue a search" ) + + DEFINE_SCRIPTFUNC( EnableEntityListening, "Enables the 'OnEntity' hooks. This function must be called before using them." ) + DEFINE_SCRIPTFUNC( DisableEntityListening, "Disables the 'OnEntity' hooks." ) + + BEGIN_SCRIPTHOOK( g_Hook_OnEntityCreated, "OnEntityCreated", FIELD_VOID, "Called when an entity is created. Requires EnableEntityListening() to be fired beforehand." ) + DEFINE_SCRIPTHOOK_PARAM( "entity", FIELD_HSCRIPT ) + END_SCRIPTHOOK() + + BEGIN_SCRIPTHOOK( g_Hook_OnEntityDeleted, "OnEntityDeleted", FIELD_VOID, "Called when an entity is deleted. Requires EnableEntityListening() to be fired beforehand." ) + DEFINE_SCRIPTHOOK_PARAM( "entity", FIELD_HSCRIPT ) + END_SCRIPTHOOK() +END_SCRIPTDESC(); + +//----------------------------------------------------------------------------- +// Purpose: A base class for VScript-utilizing clientside classes which can persist +// across levels, requiring their scripts to be shut down manually. +//----------------------------------------------------------------------------- +abstract_class IClientScriptPersistable +{ +public: + virtual void TermScript() = 0; +}; + +CUtlVector g_ScriptPersistableList; + +#define SCRIPT_MAT_PROXY_MAX_VARS 8 + +//----------------------------------------------------------------------------- +// Purpose: A material proxy which runs a VScript and allows it to read/write +// to material variables. +//----------------------------------------------------------------------------- +class CScriptMaterialProxy : public IMaterialProxy, public IClientScriptPersistable +{ +public: + CScriptMaterialProxy(); + virtual ~CScriptMaterialProxy(); + + virtual void Release( void ); + virtual bool Init( IMaterial *pMaterial, KeyValues *pKeyValues ); + virtual void OnBind( void *pRenderable ); + virtual IMaterial *GetMaterial() { return NULL; } + + // Proxies can persist across levels and aren't bound to a loaded map. + // The VM, however, is bound to the loaded map, so the proxy's script variables persisting + // causes problems when they're used in a new level with a new VM. + // As a result, we call InitScript() and TermScript() during OnBind and when the level is unloaded respectively. + bool InitScript(); + void TermScript(); + + bool ValidateIndex(int i) + { + if (i > SCRIPT_MAT_PROXY_MAX_VARS || i < 0) + { + CGWarning( 0, CON_GROUP_VSCRIPT, "VScriptProxy: %i out of range", i ); + return false; + } + + return true; + } + + const char *GetVarString( int i ); + int GetVarInt( int i ); + float GetVarFloat( int i ); + const Vector& GetVarVector( int i ); + + void SetVarString( int i, const char *value ); + void SetVarInt( int i, int value ); + void SetVarFloat( int i, float value ); + void SetVarVector( int i, const Vector &value ); + + const char *GetVarName( int i ); + +private: + IMaterialVar *m_MaterialVars[SCRIPT_MAT_PROXY_MAX_VARS]; + + // Save the keyvalue string for InitScript() + char m_szFilePath[MAX_PATH]; + + CScriptScope m_ScriptScope; + HSCRIPT m_hScriptInstance; + HSCRIPT m_hFuncOnBind; +}; + +class CMaterialProxyScriptInstanceHelper : public IScriptInstanceHelper +{ + bool ToString( void *p, char *pBuf, int bufSize ); + void *BindOnRead( HSCRIPT hInstance, void *pOld, const char *pszId ); +}; + +CMaterialProxyScriptInstanceHelper g_MaterialProxyScriptInstanceHelper; + +BEGIN_SCRIPTDESC_ROOT_NAMED( CScriptMaterialProxy, "CScriptMaterialProxy", "Material proxy for VScript" ) + DEFINE_SCRIPT_INSTANCE_HELPER( &g_MaterialProxyScriptInstanceHelper ) + DEFINE_SCRIPTFUNC( GetVarString, "Gets a material var's string value" ) + DEFINE_SCRIPTFUNC( GetVarInt, "Gets a material var's int value" ) + DEFINE_SCRIPTFUNC( GetVarFloat, "Gets a material var's float value" ) + DEFINE_SCRIPTFUNC( GetVarVector, "Gets a material var's vector value" ) + DEFINE_SCRIPTFUNC( SetVarString, "Sets a material var's string value" ) + DEFINE_SCRIPTFUNC( SetVarInt, "Sets a material var's int value" ) + DEFINE_SCRIPTFUNC( SetVarFloat, "Sets a material var's float value" ) + DEFINE_SCRIPTFUNC( SetVarVector, "Sets a material var's vector value" ) + + DEFINE_SCRIPTFUNC( GetVarName, "Gets a material var's name" ) +END_SCRIPTDESC(); + +CScriptMaterialProxy::CScriptMaterialProxy() +{ + m_hScriptInstance = NULL; + m_hFuncOnBind = NULL; + + V_memset( m_MaterialVars, 0, sizeof(m_MaterialVars) ); +} + +CScriptMaterialProxy::~CScriptMaterialProxy() +{ +} + + +//----------------------------------------------------------------------------- +// Cleanup +//----------------------------------------------------------------------------- +void CScriptMaterialProxy::Release( void ) +{ + if ( m_hScriptInstance && g_pScriptVM ) + { + g_pScriptVM->RemoveInstance( m_hScriptInstance ); + m_hScriptInstance = NULL; + } + + g_ScriptPersistableList.FindAndRemove( this ); + + delete this; +} + +bool CScriptMaterialProxy::Init( IMaterial *pMaterial, KeyValues *pKeyValues ) +{ + for (KeyValues *pKey = pKeyValues->GetFirstSubKey(); pKey != NULL; pKey = pKey->GetNextKey()) + { + // Get each variable we're looking for + if (Q_strnicmp( pKey->GetName(), "var", 3 ) == 0) + { + int index = atoi(pKey->GetName() + 3); + if (index > SCRIPT_MAT_PROXY_MAX_VARS) + { + Warning("VScript material proxy only supports 8 vars (not %i)\n", index); + continue; + } + + bool foundVar; + m_MaterialVars[index] = pMaterial->FindVar( pKey->GetString(), &foundVar ); + + // Don't init if we didn't find the var + if (!foundVar) + return false; + } + else if (FStrEq( pKey->GetName(), "scriptfile" )) + { + Q_strncpy( m_szFilePath, pKey->GetString(), sizeof( m_szFilePath ) ); + } + } + + return true; +} + +bool CScriptMaterialProxy::InitScript() +{ + if (!m_ScriptScope.IsInitialized()) + { + if (scriptmanager == NULL) + { + ExecuteOnce(DevMsg("Cannot execute script because scripting is disabled (-scripting)\n")); + return false; + } + + if (g_pScriptVM == NULL) + { + ExecuteOnce(DevMsg(" Cannot execute script because there is no available VM\n")); + return false; + } + + char* iszScriptId = (char*)stackalloc( 1024 ); + g_pScriptVM->GenerateUniqueKey("VScriptProxy", iszScriptId, 1024); + + m_hScriptInstance = g_pScriptVM->RegisterInstance( GetScriptDescForClass( CScriptMaterialProxy ), this ); + g_pScriptVM->SetInstanceUniqeId( m_hScriptInstance, iszScriptId ); + + bool bResult = m_ScriptScope.Init( iszScriptId ); + + if (!bResult) + { + CGMsg( 1, CON_GROUP_VSCRIPT, "VScriptProxy couldn't create ScriptScope!\n" ); + return false; + } + + g_pScriptVM->SetValue( m_ScriptScope, "self", m_hScriptInstance ); + } + + // Don't init if we can't run the script + if (!VScriptRunScript( m_szFilePath, m_ScriptScope, true )) + return false; + + m_hFuncOnBind = m_ScriptScope.LookupFunction( "OnBind" ); + + if (!m_hFuncOnBind) + { + // Don't init if we can't find our func + Warning("VScript material proxy can't find OnBind function\n"); + return false; + } + + g_ScriptPersistableList.AddToTail( this ); + return true; +} + +void CScriptMaterialProxy::TermScript() +{ + if ( m_hScriptInstance ) + { + g_pScriptVM->RemoveInstance( m_hScriptInstance ); + m_hScriptInstance = NULL; + } + + m_hFuncOnBind = NULL; + + m_ScriptScope.Term(); +} + +void CScriptMaterialProxy::OnBind( void *pRenderable ) +{ + if (m_hFuncOnBind != NULL) + { + C_BaseEntity *pEnt = NULL; + if (pRenderable) + { + IClientRenderable *pRend = (IClientRenderable*)pRenderable; + pEnt = pRend->GetIClientUnknown()->GetBaseEntity(); + if ( pEnt ) + { + g_pScriptVM->SetValue( m_ScriptScope, "entity", pEnt->GetScriptInstance() ); + } + } + + if (!pEnt) + { + g_pScriptVM->SetValue( m_ScriptScope, "entity", SCRIPT_VARIANT_NULL ); + } + + m_ScriptScope.Call( m_hFuncOnBind, NULL ); + } + else + { + // The VM might not exist if we do it from Init(), so we have to do it here. + // TODO: We have no handling for if this fails, how do you cancel a proxy? + if (InitScript()) + OnBind( pRenderable ); + } +} + +const char *CScriptMaterialProxy::GetVarString( int i ) +{ + if (!ValidateIndex( i ) || !m_MaterialVars[i]) + return NULL; + + return m_MaterialVars[i]->GetStringValue(); +} + +int CScriptMaterialProxy::GetVarInt( int i ) +{ + if (!ValidateIndex( i ) || !m_MaterialVars[i]) + return 0; + + return m_MaterialVars[i]->GetIntValue(); +} + +float CScriptMaterialProxy::GetVarFloat( int i ) +{ + if (!ValidateIndex( i ) || !m_MaterialVars[i]) + return 0.0f; + + return m_MaterialVars[i]->GetFloatValue(); +} + +const Vector& CScriptMaterialProxy::GetVarVector( int i ) +{ + if (!ValidateIndex( i ) || !m_MaterialVars[i]) + return vec3_origin; + + if (m_MaterialVars[i]->GetType() != MATERIAL_VAR_TYPE_VECTOR) + return vec3_origin; + + return *(reinterpret_cast(m_MaterialVars[i]->GetVecValue())); +} + +void CScriptMaterialProxy::SetVarString( int i, const char *value ) +{ + if (!ValidateIndex( i ) || !m_MaterialVars[i]) + return; + + return m_MaterialVars[i]->SetStringValue( value ); +} + +void CScriptMaterialProxy::SetVarInt( int i, int value ) +{ + if (!ValidateIndex( i ) || !m_MaterialVars[i]) + return; + + return m_MaterialVars[i]->SetIntValue( value ); +} + +void CScriptMaterialProxy::SetVarFloat( int i, float value ) +{ + if (!ValidateIndex( i ) || !m_MaterialVars[i]) + return; + + return m_MaterialVars[i]->SetFloatValue( value ); +} + +void CScriptMaterialProxy::SetVarVector( int i, const Vector &value ) +{ + if (!ValidateIndex( i ) || !m_MaterialVars[i]) + return; + + return m_MaterialVars[i]->SetVecValue( value.Base(), 3 ); +} + +const char *CScriptMaterialProxy::GetVarName( int i ) +{ + if (!ValidateIndex( i ) || !m_MaterialVars[i]) + return NULL; + + return m_MaterialVars[i]->GetName(); +} + +EXPOSE_INTERFACE( CScriptMaterialProxy, IMaterialProxy, "VScriptProxy" IMATERIAL_PROXY_INTERFACE_VERSION ); + +bool CMaterialProxyScriptInstanceHelper::ToString( void *p, char *pBuf, int bufSize ) +{ + CScriptMaterialProxy *pProxy = (CScriptMaterialProxy *)p; + V_snprintf( pBuf, bufSize, "(proxy: %s)", pProxy->GetMaterial() != NULL ? pProxy->GetMaterial()->GetName() : "" ); + return true; +} + +void *CMaterialProxyScriptInstanceHelper::BindOnRead( HSCRIPT hInstance, void *pOld, const char *pszId ) +{ + // TODO: Material proxy save/restore? + return NULL; +} +#endif // MAPBASE_VSCRIPT + //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- @@ -126,6 +589,53 @@ int GetDeveloperLevel() return developer.GetInt(); } +#ifdef MAPBASE_VSCRIPT +int ScriptScreenWidth(); +int ScriptScreenHeight(); + +static float FrameTime() +{ + return gpGlobals->frametime; +} + +static bool Con_IsVisible() +{ + return engine->Con_IsVisible(); +} + +static bool IsWindowedMode() +{ + return engine->IsWindowedMode(); +} + +// Creates a client-side prop +HSCRIPT CreateProp( const char *pszEntityName, const Vector &vOrigin, const char *pszModelName, int iAnim ) +{ + C_BaseAnimating *pBaseEntity = (C_BaseAnimating *)CreateEntityByName( pszEntityName ); + if (!pBaseEntity) + return NULL; + + pBaseEntity->SetAbsOrigin( vOrigin ); + pBaseEntity->SetModelName( pszModelName ); + if (!pBaseEntity->InitializeAsClientEntity( pszModelName, RENDER_GROUP_OPAQUE_ENTITY )) + { + Warning("Can't initialize %s as client entity\n", pszEntityName); + return NULL; + } + + pBaseEntity->SetPlaybackRate( 1.0f ); + + int iSequence = pBaseEntity->SelectWeightedSequence( (Activity)iAnim ); + + if ( iSequence != -1 ) + { + pBaseEntity->SetSequence( iSequence ); + } + + return ToHScript( pBaseEntity ); +} +#endif + bool VScriptClientInit() { VMPROF_START @@ -135,6 +645,18 @@ bool VScriptClientInit() ScriptLanguage_t scriptLanguage = SL_DEFAULT; char const *pszScriptLanguage; +#ifdef MAPBASE_VSCRIPT + if (GetClientWorldEntity()->GetScriptLanguage() != SL_NONE) + { + // Allow world entity to override script language + scriptLanguage = GetClientWorldEntity()->GetScriptLanguage(); + + // Less than SL_NONE means the script language should literally be none + if (scriptLanguage < SL_NONE) + scriptLanguage = SL_NONE; + } + else +#endif if ( CommandLine()->CheckParm( "-scriptlang", &pszScriptLanguage ) ) { if( !Q_stricmp(pszScriptLanguage, "gamemonkey") ) @@ -149,9 +671,19 @@ bool VScriptClientInit() { scriptLanguage = SL_PYTHON; } +#ifdef MAPBASE_VSCRIPT + else if( !Q_stricmp(pszScriptLanguage, "lua") ) + { + scriptLanguage = SL_LUA; + } +#endif else { +#ifdef MAPBASE_VSCRIPT + CGWarning( 1, CON_GROUP_VSCRIPT, "-scriptlang does not recognize a language named '%s'. virtual machine did NOT start.\n", pszScriptLanguage ); +#else DevWarning("-scriptlang does not recognize a language named '%s'. virtual machine did NOT start.\n", pszScriptLanguage ); +#endif scriptLanguage = SL_NONE; } @@ -163,15 +695,48 @@ bool VScriptClientInit() if( g_pScriptVM ) { +#ifdef MAPBASE_VSCRIPT + CGMsg( 0, CON_GROUP_VSCRIPT, "VSCRIPT CLIENT: Started VScript virtual machine using script language '%s'\n", g_pScriptVM->GetLanguageName() ); +#else Log_Msg( LOG_VScript, "VSCRIPT: Started VScript virtual machine using script language '%s'\n", g_pScriptVM->GetLanguageName() ); +#endif + +#ifdef MAPBASE_VSCRIPT + GetScriptHookManager().OnInit(); +#endif ScriptRegisterFunction( g_pScriptVM, GetMapName, "Get the name of the map."); ScriptRegisterFunction( g_pScriptVM, Time, "Get the current server time" ); + ScriptRegisterFunction( g_pScriptVM, DoUniqueString, SCRIPT_ALIAS( "UniqueString", "Generate a string guaranteed to be unique across the life of the script VM, with an optional root string." ) ); ScriptRegisterFunction( g_pScriptVM, DoIncludeScript, "Execute a script (internal)" ); ScriptRegisterFunction( g_pScriptVM, GetDeveloperLevel, "Gets the level of 'develoer'" ); #if defined( PORTAL2_PUZZLEMAKER ) ScriptRegisterFunction( g_pScriptVM, RequestMapRating, "Pops up the map rating dialog for user input" ); #endif // PORTAL2_PUZZLEMAKER - +#ifdef MAPBASE_VSCRIPT + ScriptRegisterFunction( g_pScriptVM, FrameTime, "Get the time spent on the client in the last frame" ); + ScriptRegisterFunction( g_pScriptVM, Con_IsVisible, "Returns true if the console is visible" ); + ScriptRegisterFunctionNamed( g_pScriptVM, ScriptScreenWidth, "ScreenWidth", "Width of the screen in pixels" ); + ScriptRegisterFunctionNamed( g_pScriptVM, ScriptScreenHeight, "ScreenHeight", "Height of the screen in pixels" ); + ScriptRegisterFunction( g_pScriptVM, IsWindowedMode, "" ); + + ScriptRegisterFunction( g_pScriptVM, MainViewOrigin, "" ); + ScriptRegisterFunction( g_pScriptVM, MainViewAngles, "" ); + ScriptRegisterFunction( g_pScriptVM, PrevMainViewOrigin, "" ); + ScriptRegisterFunction( g_pScriptVM, PrevMainViewAngles, "" ); + ScriptRegisterFunction( g_pScriptVM, MainViewForward, "" ); + ScriptRegisterFunction( g_pScriptVM, MainViewRight, "" ); + ScriptRegisterFunction( g_pScriptVM, MainViewUp, "" ); + + ScriptRegisterFunction( g_pScriptVM, CurrentViewOrigin, "" ); + ScriptRegisterFunction( g_pScriptVM, CurrentViewAngles, "" ); + ScriptRegisterFunction( g_pScriptVM, CurrentViewForward, "" ); + ScriptRegisterFunction( g_pScriptVM, CurrentViewRight, "" ); + ScriptRegisterFunction( g_pScriptVM, CurrentViewUp, "" ); + + ScriptRegisterFunction( g_pScriptVM, CreateProp, "Create an animating prop" ); +#endif + + if ( GameRules() ) { GameRules()->RegisterScriptFunctions(); @@ -181,26 +746,60 @@ bool VScriptClientInit() g_pScriptVM->RegisterInstance( &g_ScriptPanorama, "Panorama" ); #endif +#ifdef MAPBASE_VSCRIPT + g_pScriptVM->RegisterAllClasses(); + g_pScriptVM->RegisterAllEnums(); + + g_pScriptVM->RegisterInstance( &g_ScriptEntityIterator, "Entities" ); + + IGameSystem::RegisterVScriptAllSystems(); + + RegisterSharedScriptConstants(); + RegisterSharedScriptFunctions(); + RegisterScriptVGUI(); +#else + //g_pScriptVM->RegisterInstance( &g_ScriptEntityIterator, "Entities" ); +#endif + if ( scriptLanguage == SL_SQUIRREL ) { g_pScriptVM->Run( g_Script_vscript_client ); } + VScriptRunScript( "vscript_client", true ); VScriptRunScript( "mapspawn", false ); +#ifdef MAPBASE_VSCRIPT + RunAddonScripts(); +#endif + VMPROF_SHOW( pszScriptLanguage, "virtual machine startup" ); return true; } else { +#ifdef MAPBASE_VSCRIPT + CGWarning( 1, CON_GROUP_VSCRIPT, "VM Did not start!\n" ); +#else DevWarning("VM Did not start!\n"); +#endif } } +#ifdef MAPBASE_VSCRIPT + else + { + CGMsg( 0, CON_GROUP_VSCRIPT, "VSCRIPT CLIENT: Not starting because language is set to 'none'\n" ); + } +#endif } else { +#ifdef MAPBASE_VSCRIPT + CGWarning( 0, CON_GROUP_VSCRIPT, "\nVSCRIPT: Scripting is disabled.\n" ); +#else Log_Msg( LOG_VScript, "\nVSCRIPT: Scripting is disabled.\n" ); +#endif } g_pScriptVM = NULL; return false; @@ -210,6 +809,18 @@ void VScriptClientTerm() { if( g_pScriptVM != NULL ) { +#ifdef MAPBASE_VSCRIPT + // Things like proxies can persist across levels, so we have to shut down their scripts manually + for (int i = g_ScriptPersistableList.Count()-1; i >= 0; i--) + { + if (g_ScriptPersistableList[i]) + { + g_ScriptPersistableList[i]->TermScript(); + g_ScriptPersistableList.FastRemove( i ); + } + } +#endif + if( g_pScriptVM ) { scriptmanager->DestroyVM( g_pScriptVM ); @@ -241,6 +852,13 @@ public: virtual void LevelShutdownPostEntity( void ) { +#ifdef MAPBASE_VSCRIPT + g_ScriptEntityIterator.DisableEntityListening(); + + g_ScriptNetMsg->LevelShutdownPreVM(); + + GetScriptHookManager().OnShutdown(); +#endif VScriptClientTerm(); } @@ -260,6 +878,7 @@ bool IsEntityCreationAllowedInScripts( void ) return g_VScriptGameSystem.m_bAllowEntityCreationInScripts; } +#ifndef MAPBASE_VSCRIPT // // Slart: These were Portal 2 only, now they're not // @@ -299,6 +918,7 @@ class CSetMixLayerTriggerHelper : public CAutoGameSystem }; static CSetMixLayerTriggerHelper g_SetMixLayerTriggerHelper; +#endif #ifdef PANORAMA_ENABLE diff --git a/src/game/client/vscript_client.h b/src/game/client/vscript_client.h index b2aa3aae..9b69563e 100644 --- a/src/game/client/vscript_client.h +++ b/src/game/client/vscript_client.h @@ -4,8 +4,8 @@ // //============================================================================= -#ifndef VSCRIPT_SERVER_H -#define VSCRIPT_SERVER_H +#ifndef VSCRIPT_CLIENT_H +#define VSCRIPT_CLIENT_H #include "vscript/ivscript.h" #include "vscript_shared.h" @@ -19,4 +19,8 @@ extern IScriptVM * g_pScriptVM; // Only allow scripts to create entities during map initialization bool IsEntityCreationAllowedInScripts( void ); -#endif // VSCRIPT_SERVER_H +#ifdef MAPBASE_VSCRIPT +extern IScriptManager * scriptmanager; +#endif + +#endif // VSCRIPT_CLIENT_H diff --git a/src/game/client/vscript_client.nut b/src/game/client/vscript_client.nut new file mode 100644 index 00000000..7b4f2810 --- /dev/null +++ b/src/game/client/vscript_client.nut @@ -0,0 +1,36 @@ +static char g_Script_vscript_client[] = R"vscript( +//========== Copyright © 2008, Valve Corporation, All rights reserved. ======== +// +// Purpose: +// +//============================================================================= + +local DoUniqueString = DoUniqueString +local DoDispatchParticleEffect = DoDispatchParticleEffect + +function UniqueString( string = "" ) +{ + return DoUniqueString( "" + string ); +} + +function IncludeScript( name, scope = null ) +{ + if ( !scope ) + { + scope = this; + } + return ::DoIncludeScript( name, scope ); +} + +function DispatchParticleEffect( particleName, origin, angles, entity = null ) +{ + return DoDispatchParticleEffect( particleName, origin, angles, entity ); +} + +function ImpulseScale( flTargetMass, flDesiredSpeed ) +{ + return flTargetMass * flDesiredSpeed; +} +__Documentation.RegisterHelp( "ImpulseScale", "float ImpulseScale(float, float)", "Returns an impulse scale required to push an object." ); + +)vscript"; \ No newline at end of file diff --git a/src/game/client/weapon_selection.cpp b/src/game/client/weapon_selection.cpp index 45334fcd..bf5c40be 100644 --- a/src/game/client/weapon_selection.cpp +++ b/src/game/client/weapon_selection.cpp @@ -265,9 +265,14 @@ int CBaseHudWeaponSelection::KeyInput( int down, ButtonCode_t keynum, const char return 0; } - if ( down >= 1 && keynum >= KEY_1 && keynum <= KEY_9 ) + //Tony; check 0 as well, otherwise you have to have 0 bound to slot10 no matter what. + if ( down >= 1 && keynum >= KEY_0 && keynum <= KEY_9 ) { - if ( HandleHudMenuInput( keynum - KEY_0 ) ) + //Tony; 0 is actually '10' (slot10) + if (keynum == KEY_0) + keynum = KEY_A; //Dealing with button codes, so just use KEY_A, which is equal to 11 anyway. + + if ( HandleHudMenuInput( keynum - 1 ) ) return 0; } diff --git a/src/game/client/worldlight.cpp b/src/game/client/worldlight.cpp new file mode 100644 index 00000000..29e383a7 --- /dev/null +++ b/src/game/client/worldlight.cpp @@ -0,0 +1,494 @@ +//========= Copyright (C) 2018, CSProMod Team, All rights reserved. =========// +// +// Purpose: provide world light related functions to the client +// +// As the engine provides no access to brush/model data (brushdata_t, model_t), +// we hence have no access to dworldlight_t. Therefore, we manually extract the +// world light data from the BSP itself, before entities are initialised on map +// load. +// +// To find the brightest light at a point, all world lights are iterated. +// Lights whose radii do not encompass our sample point are quickly rejected, +// as are lights which are not in our PVS, or visible from the sample point. +// If the sky light is visible from the sample point, then it shall supersede +// all other world lights. +// +// Written: November 2011 +// Author: Saul Rennison +// +//===========================================================================// + +#include "cbase.h" +#include "worldlight.h" +#include "bspfile.h" +#include "filesystem.h" +#include "client_factorylist.h" // FactoryList_Retrieve +#include "eiface.h" // IVEngineServer + +static IVEngineServer *g_pEngineServer = NULL; + +#ifdef MAPBASE +ConVar cl_worldlight_use_new_method("cl_worldlight_use_new_method", "1", FCVAR_NONE, "Uses the new world light iteration method which splits lights into multiple lists for each cluster."); +#endif + +//----------------------------------------------------------------------------- +// Singleton exposure +//----------------------------------------------------------------------------- +static CWorldLights s_WorldLights; +CWorldLights *g_pWorldLights = &s_WorldLights; + +//----------------------------------------------------------------------------- +// Purpose: calculate intensity ratio for a worldlight by distance +// Author: Valve Software +//----------------------------------------------------------------------------- +static float Engine_WorldLightDistanceFalloff( const dworldlight_t *wl, const Vector& delta ) +{ + float falloff; + + switch (wl->type) + { + case emit_surface: + // Cull out stuff that's too far + if(wl->radius != 0) + { + if(DotProduct( delta, delta ) > (wl->radius * wl->radius)) + return 0.0f; + } + + return InvRSquared(delta); + break; + + case emit_skylight: + return 1.f; + break; + + case emit_quakelight: + // X - r; + falloff = wl->linear_attn - FastSqrt( DotProduct( delta, delta ) ); + if(falloff < 0) + return 0.f; + + return falloff; + break; + + case emit_skyambient: + return 1.f; + break; + + case emit_point: + case emit_spotlight: // directional & positional + { + float dist2, dist; + + dist2 = DotProduct(delta, delta); + dist = FastSqrt(dist2); + + // Cull out stuff that's too far + if(wl->radius != 0 && dist > wl->radius) + return 0.f; + + return 1.f / (wl->constant_attn + wl->linear_attn * dist + wl->quadratic_attn * dist2); + } + + break; + } + + return 1.f; +} + +//----------------------------------------------------------------------------- +// Purpose: initialise game system and members +//----------------------------------------------------------------------------- +CWorldLights::CWorldLights() : CAutoGameSystem("World lights") +{ + m_nWorldLights = 0; + m_pWorldLights = NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: clear worldlights, free memory +//----------------------------------------------------------------------------- +void CWorldLights::Clear() +{ + m_nWorldLights = 0; + + if(m_pWorldLights) + { + delete [] m_pWorldLights; + m_pWorldLights = NULL; + } +} + +//----------------------------------------------------------------------------- +// Purpose: get the IVEngineServer, we need this for the PVS functions +//----------------------------------------------------------------------------- +bool CWorldLights::Init() +{ +#ifdef MAPBASE + // Moved to its own clientside interface after I found out it was possible + // (still have no idea how or why this works) + if ((g_pEngineServer = serverengine) == NULL) + return false; + + return true; +#else + factorylist_t factories; + FactoryList_Retrieve(factories); + + if((g_pEngineServer = (IVEngineServer*)factories.appSystemFactory(INTERFACEVERSION_VENGINESERVER, NULL)) == NULL) + return false; + + return true; +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: get all world lights from the BSP +//----------------------------------------------------------------------------- +void CWorldLights::LevelInitPreEntity() +{ + // Get the map path + const char *pszMapName = modelinfo->GetModelName(modelinfo->GetModel(1)); + + // Open map + FileHandle_t hFile = g_pFullFileSystem->Open(pszMapName, "rb"); + if(!hFile) + { + Warning("CWorldLights: unable to open map\n"); + return; + } + + // Read the BSP header. We don't need to do any version checks, etc. as we + // can safely assume that the engine did this for us + dheader_t hdr; + g_pFullFileSystem->Read(&hdr, sizeof(hdr), hFile); + + // Grab the light lump and seek to it + lump_t &lightLump = hdr.lumps[LUMP_WORLDLIGHTS]; + + // INSOLENCE: If the worldlights lump is empty, that means theres no normal, LDR lights to extract + // This can happen when, for example, the map is compiled in HDR mode only + // So move on to the HDR worldlights lump + if (lightLump.filelen == 0) + { + lightLump = hdr.lumps[LUMP_WORLDLIGHTS_HDR]; + } + + // If we can't divide the lump data into a whole number of worldlights, + // then the BSP format changed and we're unaware + if(lightLump.filelen % sizeof(dworldlight_t)) + { + Warning("CWorldLights: unknown world light lump\n"); + + // Close file + g_pFullFileSystem->Close(hFile); + return; + } + + g_pFullFileSystem->Seek(hFile, lightLump.fileofs, FILESYSTEM_SEEK_HEAD); + + // Allocate memory for the worldlights + m_nWorldLights = lightLump.filelen / sizeof(dworldlight_t); + m_pWorldLights = new dworldlight_t[m_nWorldLights]; + + // Read worldlights then close + g_pFullFileSystem->Read(m_pWorldLights, lightLump.filelen, hFile); + g_pFullFileSystem->Close(hFile); + + DevMsg("CWorldLights: load successful (%d lights at 0x%p)\n", m_nWorldLights, m_pWorldLights); + +#ifdef MAPBASE + // Now that the lights have been gathered, begin separating them into lists for each PVS cluster. + // This code is adapted from the soundscape cluster list code (see soundscape_system.cpp) and is intended to + // reduce frame drops in large maps which use dynamic RTT shadow angles. + CUtlVector clusterbounds; + int clusterCount = g_pEngineServer->GetClusterCount(); + clusterbounds.SetCount( clusterCount ); + g_pEngineServer->GetAllClusterBounds( clusterbounds.Base(), clusterCount ); + m_WorldLightsInCluster.SetCount(clusterCount); + for ( int i = 0; i < clusterCount; i++ ) + { + m_WorldLightsInCluster[i].lightCount = 0; + m_WorldLightsInCluster[i].firstLight = 0; + } + unsigned char myPVS[16 * 1024]; + CUtlVector clusterIndexList; + CUtlVector lightIndexList; + + // Find the clusters visible from each light, then add it to those clusters' light lists + // (Also try to clip for radius if possible) + for (int i = 0; i < m_nWorldLights; ++i) + { + dworldlight_t *light = &m_pWorldLights[i]; + + // Assign the sun to its own pointer + if (light->type == emit_skylight) + { + m_iSunIndex = i; + continue; + } + + float radiusSq = light->radius * light->radius; + if (radiusSq == 0.0f) + { + // TODO: Use intensity instead? + radiusSq = FLT_MAX; + } + + g_pEngineServer->GetPVSForCluster( light->cluster, sizeof( myPVS ), myPVS ); + for ( int j = 0; j < clusterCount; j++ ) + { + if ( myPVS[ j >> 3 ] & (1<<(j&7)) ) + { + float distSq = CalcSqrDistanceToAABB( clusterbounds[j].mins, clusterbounds[j].maxs, light->origin ); + if ( distSq < radiusSq ) + { + m_WorldLightsInCluster[j].lightCount++; + clusterIndexList.AddToTail(j); + lightIndexList.AddToTail(i); + } + } + } + } + + m_WorldLightsIndexList.SetCount(lightIndexList.Count()); + + // Compute the starting index of each cluster + int firstLight = 0; + for ( int i = 0; i < clusterCount; i++ ) + { + m_WorldLightsInCluster[i].firstLight = firstLight; + firstLight += m_WorldLightsInCluster[i].lightCount; + m_WorldLightsInCluster[i].lightCount = 0; + } + + // Now add each light index to the appropriate cluster's list + for ( int i = 0; i < lightIndexList.Count(); i++ ) + { + int cluster = clusterIndexList[i]; + int outIndex = m_WorldLightsInCluster[cluster].lightCount + m_WorldLightsInCluster[cluster].firstLight; + m_WorldLightsInCluster[cluster].lightCount++; + m_WorldLightsIndexList[outIndex] = lightIndexList[i]; + } + + //DevMsg( "CWorldLights: Light clusters list has %i elements; Light index list has %i\n", m_WorldLightsInCluster.Count(), m_WorldLightsIndexList.Count() ); +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: find the brightest light source at a point +//----------------------------------------------------------------------------- +bool CWorldLights::GetBrightestLightSource(const Vector &vecPosition, Vector &vecLightPos, Vector &vecLightBrightness) +{ + if(!m_nWorldLights || !m_pWorldLights) + return false; + + // Default light position and brightness to zero + vecLightBrightness.Init(); + vecLightPos.Init(); + + // Find the size of the PVS for our current position + int nCluster = g_pEngineServer->GetClusterForOrigin(vecPosition); + +#ifdef MAPBASE + if (cl_worldlight_use_new_method.GetBool()) + { + FindBrightestLightSourceNew( vecPosition, vecLightPos, vecLightBrightness, nCluster ); + } + else +#endif + { + FindBrightestLightSourceOld( vecPosition, vecLightPos, vecLightBrightness, nCluster ); + } + + //engine->Con_NPrintf(m_nWorldLights, "result: %d", !vecLightBrightness.IsZero()); + return !vecLightBrightness.IsZero(); +} + +void CWorldLights::FindBrightestLightSourceOld( const Vector &vecPosition, Vector &vecLightPos, Vector &vecLightBrightness, int nCluster ) +{ + // Find the size of the PVS for our current position + int nPVSSize = g_pEngineServer->GetPVSForCluster(nCluster, 0, NULL); + + // Get the PVS at our position + byte *pvs = new byte[nPVSSize]; + g_pEngineServer->GetPVSForCluster(nCluster, nPVSSize, pvs); + + // Iterate through all the worldlights + for(int i = 0; i < m_nWorldLights; ++i) + { + dworldlight_t *light = &m_pWorldLights[i]; + + // Skip skyambient + if(light->type == emit_skyambient) + { + //engine->Con_NPrintf(i, "%d: skyambient", i); + continue; + } + + // Handle sun + if(light->type == emit_skylight) + { + // Calculate sun position + Vector vecAbsStart = vecPosition + Vector(0,0,30); + Vector vecAbsEnd = vecAbsStart - (light->normal * MAX_TRACE_LENGTH); + + trace_t tr; + UTIL_TraceLine(vecPosition, vecAbsEnd, MASK_OPAQUE, NULL, COLLISION_GROUP_NONE, &tr); + + // If we didn't hit anything then we have a problem + if(!tr.DidHit()) + { + //engine->Con_NPrintf(i, "%d: skylight: couldn't touch sky", i); + continue; + } + + // If we did hit something, and it wasn't the skybox, then skip + // this worldlight + if(!(tr.surface.flags & SURF_SKY) && !(tr.surface.flags & SURF_SKY2D)) + { + //engine->Con_NPrintf(i, "%d: skylight: no sight to sun", i); + continue; + } + + // Act like we didn't find any valid worldlights, so the shadow + // manager uses the default shadow direction instead (should be the + // sun direction) + + delete[] pvs; + + return; + } + + // Calculate square distance to this worldlight + Vector vecDelta = light->origin - vecPosition; + float flDistSqr = vecDelta.LengthSqr(); + float flRadiusSqr = light->radius * light->radius; + + // Skip lights that are out of our radius + if(flRadiusSqr > 0 && flDistSqr >= flRadiusSqr) + { + //engine->Con_NPrintf(i, "%d: out-of-radius (dist: %d, radius: %d)", i, sqrt(flDistSqr), light->radius); + continue; + } + + // Is it out of our PVS? + if(!g_pEngineServer->CheckOriginInPVS(light->origin, pvs, nPVSSize)) + { + //engine->Con_NPrintf(i, "%d: out of PVS", i); + continue; + } + + // Calculate intensity at our position + float flRatio = Engine_WorldLightDistanceFalloff(light, vecDelta); + Vector vecIntensity = light->intensity * flRatio; + + // Is this light more intense than the one we already found? + if(vecIntensity.LengthSqr() <= vecLightBrightness.LengthSqr()) + { + //engine->Con_NPrintf(i, "%d: too dim", i); + continue; + } + + // Can we see the light? + trace_t tr; + Vector vecAbsStart = vecPosition + Vector(0,0,30); + UTIL_TraceLine(vecAbsStart, light->origin, MASK_OPAQUE, NULL, COLLISION_GROUP_NONE, &tr); + + if(tr.DidHit()) + { + //engine->Con_NPrintf(i, "%d: trace failed", i); + continue; + } + + vecLightPos = light->origin; + vecLightBrightness = vecIntensity; + + //engine->Con_NPrintf(i, "%d: set (%.2f)", i, vecIntensity.Length()); + } + + delete[] pvs; +} + +#ifdef MAPBASE +void CWorldLights::FindBrightestLightSourceNew( const Vector &vecPosition, Vector &vecLightPos, Vector &vecLightBrightness, int nCluster ) +{ + // Handle sun + if (m_iSunIndex != -1) + { + dworldlight_t *light = &m_pWorldLights[m_iSunIndex]; + + // Calculate sun position + Vector vecAbsStart = vecPosition + Vector(0,0,30); + Vector vecAbsEnd = vecAbsStart - (light->normal * MAX_TRACE_LENGTH); + + trace_t tr; + UTIL_TraceLine(vecPosition, vecAbsEnd, MASK_OPAQUE, NULL, COLLISION_GROUP_NONE, &tr); + + // If we didn't hit anything then we have a problem + if(tr.DidHit()) + { + // If we did hit something, and it wasn't the skybox, then skip + // this worldlight + if((tr.surface.flags & SURF_SKY) && (tr.surface.flags & SURF_SKY2D)) + { + // Act like we didn't find any valid worldlights, so the shadow + // manager uses the default shadow direction instead (should be the + // sun direction) + + return; + } + } + } + + // Iterate through all the worldlights + if ( nCluster >= 0 && nCluster < m_WorldLightsInCluster.Count() ) + { + // find all soundscapes that could possibly attach to this player and update them + for ( int j = 0; j < m_WorldLightsInCluster[nCluster].lightCount; j++ ) + { + int ssIndex = m_WorldLightsIndexList[m_WorldLightsInCluster[nCluster].firstLight + j]; + dworldlight_t *light = &m_pWorldLights[ssIndex]; + + // Calculate square distance to this worldlight + Vector vecDelta = light->origin - vecPosition; + float flDistSqr = vecDelta.LengthSqr(); + float flRadiusSqr = light->radius * light->radius; + + // Skip lights that are out of our radius + if(flRadiusSqr > 0 && flDistSqr >= flRadiusSqr) + { + //engine->Con_NPrintf(i, "%d: out-of-radius (dist: %d, radius: %d)", i, sqrt(flDistSqr), light->radius); + continue; + } + + // Calculate intensity at our position + float flRatio = Engine_WorldLightDistanceFalloff(light, vecDelta); + Vector vecIntensity = light->intensity * flRatio; + + // Is this light more intense than the one we already found? + if(vecIntensity.LengthSqr() <= vecLightBrightness.LengthSqr()) + { + //engine->Con_NPrintf(i, "%d: too dim", i); + continue; + } + + // Can we see the light? + trace_t tr; + Vector vecAbsStart = vecPosition + Vector(0,0,30); + UTIL_TraceLine(vecAbsStart, light->origin, MASK_OPAQUE, NULL, COLLISION_GROUP_NONE, &tr); + + if(tr.DidHit()) + { + //engine->Con_NPrintf(i, "%d: trace failed", i); + continue; + } + + vecLightPos = light->origin; + vecLightBrightness = vecIntensity; + + //engine->Con_NPrintf(i, "%d: set (%.2f)", i, vecIntensity.Length()); + } + } +} +#endif diff --git a/src/game/client/worldlight.h b/src/game/client/worldlight.h new file mode 100644 index 00000000..b621ce1f --- /dev/null +++ b/src/game/client/worldlight.h @@ -0,0 +1,65 @@ +//========= Copyright (C) 2018, CSProMod Team, All rights reserved. =========// +// +// Purpose: provide world light related functions to the client +// +// Written: November 2011 +// Author: Saul Rennison +// +//===========================================================================// + +#pragma once + +#include "igamesystem.h" // CAutoGameSystem + +class Vector; +struct dworldlight_t; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class CWorldLights : public CAutoGameSystem +{ +public: + CWorldLights(); + ~CWorldLights() { Clear(); } + + //------------------------------------------------------------------------- + // Find the brightest light source at a point + //------------------------------------------------------------------------- + bool GetBrightestLightSource(const Vector &vecPosition, Vector &vecLightPos, Vector &vecLightBrightness); + void FindBrightestLightSourceOld( const Vector &vecPosition, Vector &vecLightPos, Vector &vecLightBrightness, int nCluster ); +#ifdef MAPBASE + void FindBrightestLightSourceNew(const Vector &vecPosition, Vector &vecLightPos, Vector &vecLightBrightness, int nCluster); + bool GetCumulativeLightSource(const Vector &vecPosition, Vector &vecLightPos, float flMinBrightnessSqr); +#endif + + // CAutoGameSystem overrides +public: + virtual bool Init(); + virtual void LevelInitPreEntity(); + virtual void LevelShutdownPostEntity() { Clear(); } + +private: + void Clear(); + + int m_nWorldLights; + dworldlight_t *m_pWorldLights; + +#ifdef MAPBASE + int m_iSunIndex = -1; // The sun's personal index + + struct clusterLightList_t + { + unsigned short lightCount; + unsigned short firstLight; + }; + + CUtlVector m_WorldLightsInCluster; + CUtlVector m_WorldLightsIndexList; +#endif +}; + +//----------------------------------------------------------------------------- +// Singleton exposure +//----------------------------------------------------------------------------- +extern CWorldLights *g_pWorldLights; diff --git a/src/game/server/AI_Criteria.cpp b/src/game/server/AI_Criteria.cpp index d90a2c68..fffdc171 100644 --- a/src/game/server/AI_Criteria.cpp +++ b/src/game/server/AI_Criteria.cpp @@ -170,6 +170,22 @@ void AI_CriteriaSet::Describe() } } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: Merges another AI_CriteriaSet without clearing +// Input : src - +//----------------------------------------------------------------------------- +void AI_CriteriaSet::MergeSet( const AI_CriteriaSet& src ) +{ + for ( short i = src.m_Lookup.FirstInorder(); + i != src.m_Lookup.InvalidIndex(); + i = src.m_Lookup.NextInorder( i ) ) + { + m_Lookup.Insert( src.m_Lookup[ i ] ); + } +} +#endif + BEGIN_SIMPLE_DATADESC( AI_ResponseParams ) DEFINE_FIELD( flags, FIELD_SHORT ), DEFINE_FIELD( odds, FIELD_SHORT ), @@ -196,7 +212,11 @@ AI_Response::AI_Response() m_szMatchingRule[0] = 0; m_pCriteria = NULL; +#ifdef MAPBASE + m_iContextFlags = 0; +#else m_bApplyContextToWorld = false; +#endif } //----------------------------------------------------------------------------- @@ -205,6 +225,10 @@ AI_Response::AI_Response( const AI_Response &from ) { m_pCriteria = NULL; *this = from; +#ifdef MAPBASE + m_iContextFlags = from.m_iContextFlags; +#else +#endif } //----------------------------------------------------------------------------- @@ -238,9 +262,12 @@ AI_Response &AI_Response::operator=( const AI_Response &from ) m_Params = from.m_Params; +#ifdef MAPBASE + m_iContextFlags = from.m_iContextFlags; +#else m_szContext = from.m_szContext; m_bApplyContextToWorld = from.m_bApplyContextToWorld; - +#endif return *this; } @@ -265,9 +292,32 @@ void AI_Response::Init( ResponseType_t type, const char *responseName, const AI_ m_Params = responseparams; m_szContext = applyContext; +#ifdef MAPBASE + bApplyContextToWorld ? m_iContextFlags = APPLYCONTEXT_WORLD : m_iContextFlags = 0; +#else m_bApplyContextToWorld = bApplyContextToWorld; +#endif } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: +// Input : *response - +// *criteria - +//----------------------------------------------------------------------------- +void AI_Response::Init( ResponseType_t type, const char *responseName, const AI_CriteriaSet& criteria, const AI_ResponseParams& responseparams, const char *ruleName, const char *applyContext, int iContextFlags ) +{ + m_Type = type; + Q_strncpy( m_szResponseName, responseName, sizeof( m_szResponseName ) ); + // Copy underlying criteria + m_pCriteria = new AI_CriteriaSet( criteria ); + Q_strncpy( m_szMatchingRule, ruleName ? ruleName : "NULL", sizeof( m_szMatchingRule ) ); + m_Params = responseparams; + SetContext( applyContext ); + m_iContextFlags = iContextFlags; +} +#endif + //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- @@ -282,6 +332,18 @@ void AI_Response::Describe() DevMsg( "Matched rule '%s', ", m_szMatchingRule ); if ( m_szContext.Length() ) DevMsg( "Contexts to set '%s' on %s, ", m_szContext.Get(), m_bApplyContextToWorld ? "world" : "speaker" ); +#ifdef MAPBASE + DevMsg( "Contexts to set '%s' on ", m_szContext ); + if (m_iContextFlags & APPLYCONTEXT_WORLD) + DevMsg("world, "); + else if (m_iContextFlags & APPLYCONTEXT_SQUAD) + DevMsg("squad, "); + else if (m_iContextFlags & APPLYCONTEXT_ENEMY) + DevMsg("enemy, "); + else + DevMsg("speaker, "); +#else +#endif DevMsg( "response %s = '%s'\n", DescribeResponse( (ResponseType_t)m_Type ), m_szResponseName ); } diff --git a/src/game/server/AI_Criteria.h b/src/game/server/AI_Criteria.h index f10c2d05..65413605 100644 --- a/src/game/server/AI_Criteria.h +++ b/src/game/server/AI_Criteria.h @@ -4,6 +4,9 @@ // //=============================================================================// +#ifdef NEW_RESPONSE_SYSTEM +#include "ai_criteria_new.h" +#else #ifndef AI_CRITERIA_H #define AI_CRITERIA_H #ifdef _WIN32 @@ -37,6 +40,10 @@ public: const char *GetValue( int index ) const; float GetWeight( int index ) const; +#ifdef MAPBASE + void MergeSet( const AI_CriteriaSet& src ); +#endif + private: struct CritEntry_t @@ -170,6 +177,24 @@ enum ResponseType_t NUM_RESPONSES, }; +#ifdef MAPBASE +// I wanted to add more options to apply contexts to, +// so I replaced the existing system with a flag-based integer instead of a bunch of booleans. +// +// New ones should be implemented in: +// CResponseSystem::ParseRule() - AI_ResponseSystem.cpp +// AI_Response::Describe() - AI_Criteria.cpp +// CAI_Expresser::SpeakDispatchResponse() - ai_speech.cpp +enum +{ + APPLYCONTEXT_SELF = (1 << 0), // Included for contexts that apply to both self and something else + APPLYCONTEXT_WORLD = (1 << 1), // Apply to world + + APPLYCONTEXT_SQUAD = (1 << 2), // Apply to squad + APPLYCONTEXT_ENEMY = (1 << 3), // Apply to enemy +}; +#endif + class AI_Response { public: @@ -199,7 +224,12 @@ public: void SetContext( const char *context ); const char * GetContext( void ) const { return m_szContext.Length() ? m_szContext.Get() : NULL; } +#ifdef MAPBASE + int GetContextFlags() { return m_iContextFlags; } + bool IsApplyContextToWorld( void ) { return (m_iContextFlags & APPLYCONTEXT_WORLD) != 0; } +#else bool IsApplyContextToWorld( void ) { return m_bApplyContextToWorld; } +#endif void Describe(); @@ -213,6 +243,16 @@ public: const char *applyContext, bool bApplyContextToWorld ); +#ifdef MAPBASE + void Init( ResponseType_t type, + const char *responseName, + const AI_CriteriaSet& criteria, + const AI_ResponseParams& responseparams, + const char *matchingRule, + const char *applyContext, + int iContextFlags ); +#endif + static const char *DescribeResponse( ResponseType_t type ); enum @@ -233,7 +273,12 @@ private: AI_ResponseParams m_Params; CUtlString m_szContext; +#ifdef MAPBASE + int m_iContextFlags; +#else bool m_bApplyContextToWorld; +#endif }; #endif // AI_CRITERIA_H +#endif diff --git a/src/game/server/AI_ResponseSystem.cpp b/src/game/server/AI_ResponseSystem.cpp index 9c849b2d..1b68bb8e 100644 --- a/src/game/server/AI_ResponseSystem.cpp +++ b/src/game/server/AI_ResponseSystem.cpp @@ -22,6 +22,9 @@ #include "stringpool.h" #include "fmtstr.h" #include "multiplay_gamerules.h" +#ifdef MAPBASE +#include "mapbase/matchers.h" +#endif // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" @@ -30,6 +33,10 @@ ConVar rr_debugresponses( "rr_debugresponses", "0", FCVAR_NONE, "Show verbose ma ConVar rr_debugrule( "rr_debugrule", "", FCVAR_NONE, "If set to the name of the rule, that rule's score will be shown whenever a concept is passed into the response rules system."); ConVar rr_dumpresponses( "rr_dumpresponses", "0", FCVAR_NONE, "Dump all response_rules.txt and rules (requires restart)" ); +#ifdef MAPBASE +ConVar rr_enhanced_saverestore( "rr_enhanced_saverestore", "0", FCVAR_NONE, "Enables enhanced save/restore capabilities for the Response System." ); +#endif + static CUtlSymbolTable g_RS; inline static char *CopyString( const char *in ) @@ -57,6 +64,9 @@ public: minequals = false; usemax = false; maxequals = false; +#ifdef MAPBASE + isbit = false; +#endif maxval = 0.0f; minval = 0.0f; @@ -99,6 +109,14 @@ public: return; } +#ifdef MAPBASE + if (isbit) + { + DevMsg( " matcher: &%s%s\n", notequal ? "~" : "", GetToken() ); + return; + } +#endif + if ( notequal ) { DevMsg( " matcher: !=%s\n", GetToken() ); @@ -118,6 +136,9 @@ public: bool minequals : 1; //5 bool usemax : 1; //6 bool maxequals : 1; //7 +#ifdef MAPBASE + bool isbit : 1; //8 +#endif void SetToken( char const *s ) { @@ -461,7 +482,11 @@ struct Rule m_bMatchOnce = false; m_bEnabled = true; m_szContext = NULL; +#ifdef MAPBASE + m_iContextFlags = 0; +#else m_bApplyContextToWorld = false; +#endif } Rule& operator =( const Rule& src ) @@ -487,7 +512,11 @@ struct Rule SetContext( src.m_szContext ); m_bMatchOnce = src.m_bMatchOnce; m_bEnabled = src.m_bEnabled; +#ifdef MAPBASE + m_iContextFlags = src.m_iContextFlags; +#else m_bApplyContextToWorld = src.m_bApplyContextToWorld; +#endif return *this; } @@ -511,7 +540,11 @@ struct Rule SetContext( src.m_szContext ); m_bMatchOnce = src.m_bMatchOnce; m_bEnabled = src.m_bEnabled; +#ifdef MAPBASE + m_iContextFlags = src.m_iContextFlags; +#else m_bApplyContextToWorld = src.m_bApplyContextToWorld; +#endif } ~Rule() @@ -530,14 +563,22 @@ struct Rule bool IsEnabled() const { return m_bEnabled; } void Disable() { m_bEnabled = false; } bool IsMatchOnce() const { return m_bMatchOnce; } +#ifdef MAPBASE + bool IsApplyContextToWorld() const { return (m_iContextFlags & APPLYCONTEXT_WORLD) != 0; } +#else bool IsApplyContextToWorld() const { return m_bApplyContextToWorld; } +#endif // Indices into underlying criteria and response dictionaries CUtlVector< unsigned short > m_Criteria; CUtlVector< unsigned short> m_Responses; char *m_szContext; +#ifdef MAPBASE + int m_iContextFlags; +#else bool m_bApplyContextToWorld : 1; +#endif bool m_bMatchOnce : 1; bool m_bEnabled : 1; @@ -668,7 +709,11 @@ public: void ParseOneResponse( const char *responseGroupName, ResponseGroup& group ); +#ifdef MAPBASE + void ParseInclude( CStringPool &includedFiles, const char *scriptfile = NULL ); +#else void ParseInclude( CStringPool &includedFiles ); +#endif void ParseResponse( void ); void ParseCriterion( void ); void ParseRule( void ); @@ -865,6 +910,7 @@ void CResponseSystem::ResolveToken( Matcher& matcher, char *token, size_t bufsiz } +#ifndef MAPBASE // Moved to matchers.h static bool AppearsToBeANumber( char const *token ) { if ( atof( token ) != 0.0f ) @@ -881,6 +927,7 @@ static bool AppearsToBeANumber( char const *token ) return true; } +#endif void CResponseSystem::ComputeMatcher( Criteria *c, Matcher& matcher ) { @@ -905,6 +952,9 @@ void CResponseSystem::ComputeMatcher( Criteria *c, Matcher& matcher ) bool lt = false; bool eq = false; bool nt = false; +#ifdef MAPBASE + bool bit = false; +#endif bool done = false; while ( !done ) @@ -937,6 +987,17 @@ void CResponseSystem::ComputeMatcher( Criteria *c, Matcher& matcher ) // Convert raw token to real token in case token is an enumerated type specifier ResolveToken( matcher, token, sizeof( token ), rawtoken ); +#ifdef MAPBASE + // Bits are an entirely different and independent story + if (bit) + { + matcher.isbit = true; + matcher.notequal = nt; + + matcher.isnumeric = true; + } + else +#endif // Fill in first data set if ( gt ) { @@ -978,6 +1039,13 @@ void CResponseSystem::ComputeMatcher( Criteria *c, Matcher& matcher ) case '!': nt = true; break; +#ifdef MAPBASE + case '~': + nt = true; + case '&': + bit = true; + break; +#endif default: rawtoken[ n++ ] = *in; break; @@ -1002,6 +1070,19 @@ bool CResponseSystem::CompareUsingMatcher( const char *setValue, Matcher& m, boo bool found = false; v = LookupEnumeration( setValue, found ); } + +#ifdef MAPBASE + // Bits are always a different story + if (m.isbit) + { + int v1 = v; + int v2 = atoi(m.GetToken()); + if (m.notequal) + return (v1 & v2) == 0; + else + return (v1 & v2) != 0; + } +#endif int minmaxcount = 0; @@ -1052,7 +1133,11 @@ bool CResponseSystem::CompareUsingMatcher( const char *setValue, Matcher& m, boo } else { +#ifdef MAPBASE + if ( Matcher_NamesMatch(m.GetToken(), setValue) ) +#else if ( !Q_stricmp( setValue, m.GetToken() ) ) +#endif return false; } @@ -1069,7 +1154,12 @@ bool CResponseSystem::CompareUsingMatcher( const char *setValue, Matcher& m, boo return v == (float)atof( m.GetToken() ); } +#ifdef MAPBASE + // Wildcards. + return Matcher_NamesMatch(m.GetToken(), setValue) ? true : false; +#else return !Q_stricmp( setValue, m.GetToken() ) ? true : false; +#endif } bool CResponseSystem::Compare( const char *setValue, Criteria *c, bool verbose /*= false*/ ) @@ -1745,11 +1835,19 @@ bool CResponseSystem::FindBestResponse( const AI_CriteriaSet& set, AI_Response& char ruleName[ 128 ]; char responseName[ 128 ]; const char *context; +#ifdef MAPBASE + int contextflags; +#else bool bcontexttoworld; +#endif ruleName[ 0 ] = 0; responseName[ 0 ] = 0; context = NULL; +#ifdef MAPBASE + contextflags = 0; +#else bcontexttoworld = false; +#endif if ( bestRule != -1 ) { Rule *r = &m_Rules[ bestRule ]; @@ -1770,12 +1868,20 @@ bool CResponseSystem::FindBestResponse( const AI_CriteriaSet& set, AI_Response& r->Disable(); } context = r->GetContext(); +#ifdef MAPBASE + contextflags = r->m_iContextFlags; +#else bcontexttoworld = r->IsApplyContextToWorld(); +#endif valid = true; } +#ifdef MAPBASE + response.Init( responseType, responseName, set, rp, ruleName, context, contextflags ); +#else response.Init( responseType, responseName, set, rp, ruleName, context, bcontexttoworld ); +#endif if ( showResult ) { @@ -1894,11 +2000,43 @@ void CResponseSystem::Precache() } } +#ifdef MAPBASE +void CResponseSystem::ParseInclude( CStringPool &includedFiles, const char *scriptfile ) +#else void CResponseSystem::ParseInclude( CStringPool &includedFiles ) +#endif { char includefile[ 256 ]; ParseToken(); +#ifdef MAPBASE + if (scriptfile) + { + // Gets first path + // (for example, an #include from a file in resource/script/resp will return resource) + size_t len = strlen(scriptfile)-1; + for (size_t i = 0; i < len; i++) + { + if (scriptfile[i] == CORRECT_PATH_SEPARATOR || scriptfile[i] == INCORRECT_PATH_SEPARATOR) + { + len = i; + } + } + Q_strncpy(includefile, scriptfile, len+1); + + if (len+1 != strlen(scriptfile)) + { + Q_strncat( includefile, "/", sizeof( includefile ) ); + Q_strncat( includefile, token, sizeof( includefile ) ); + } + else + includefile[0] = '\0'; + } + + if (!includefile[0]) + Q_snprintf( includefile, sizeof( includefile ), "scripts/%s", token ); +#else Q_snprintf( includefile, sizeof( includefile ), "scripts/%s", token ); +#endif // check if the file is already included if ( includedFiles.Find( includefile ) != NULL ) @@ -1939,8 +2077,19 @@ void CResponseSystem::LoadFromBuffer( const char *scriptfile, const char *buffer if ( !Q_stricmp( token, "#include" ) ) { +#ifdef MAPBASE + ParseInclude( includedFiles, scriptfile ); +#else ParseInclude( includedFiles ); +#endif } +#ifdef MAPBASE + else if ( !Q_stricmp( token, "#base" ) ) + { + // Actual #base in the future? + ParseInclude( includedFiles, scriptfile ); + } +#endif else if ( !Q_stricmp( token, "response" ) ) { ParseResponse(); @@ -2324,6 +2473,16 @@ void CResponseSystem::ParseResponse( void ) ParseOneResponse( responseGroupName, newGroup ); } +#ifdef MAPBASE + short existing = m_Responses.Find( responseGroupName ); + if ( existing != m_Responses.InvalidIndex() ) + { + //ResponseWarning( "Additional definition for response '%s', overwriting\n", responseGroupName ); + m_Responses[existing] = newGroup; + m_Responses.SetElementName(existing, responseGroupName); + return; + } +#endif m_Responses.Insert( responseGroupName, newGroup ); } @@ -2405,11 +2564,22 @@ int CResponseSystem::ParseOneCriterion( const char *criterionName ) ComputeMatcher( &newCriterion, newCriterion.matcher ); } +#ifdef MAPBASE + short existing = m_Criteria.Find( criterionName ); + if ( existing != m_Criteria.InvalidIndex() ) + { + //ResponseWarning( "Additional definition for criteria '%s', overwriting\n", criterionName ); + m_Criteria[existing] = newCriterion; + m_Criteria.SetElementName(existing, criterionName); + return existing; + } +#else if ( m_Criteria.Find( criterionName ) != m_Criteria.InvalidIndex() ) { ResponseWarning( "Multiple definitions for criteria '%s'\n", criterionName ); return m_Criteria.InvalidIndex(); } +#endif int idx = m_Criteria.Insert( criterionName, newCriterion ); return idx; @@ -2475,12 +2645,22 @@ void CResponseSystem::ParseEnumeration( void ) { m_Enumerations.Insert( sz, newEnum ); } +#ifdef MAPBASE + else + { + short existing = m_Enumerations.Find( sz ); + //ResponseWarning( "Additional definition for enumeration '%s', overwriting\n", sz ); + m_Enumerations[existing] = newEnum; + m_Enumerations.SetElementName(existing, sz); + } +#else /* else { ResponseWarning( "Ignoring duplication enumeration '%s'\n", sz ); } */ +#endif } } @@ -2531,10 +2711,28 @@ void CResponseSystem::ParseRule( void ) if ( !Q_stricmp( token, "applyContextToWorld" ) ) { +#ifdef MAPBASE + newRule.m_iContextFlags |= APPLYCONTEXT_WORLD; +#else newRule.m_bApplyContextToWorld = true; +#endif continue; } +#ifdef MAPBASE + if ( !Q_stricmp( token, "applyContextToSquad" ) ) + { + newRule.m_iContextFlags |= APPLYCONTEXT_SQUAD; + continue; + } + + if ( !Q_stricmp( token, "applyContextToEnemy" ) ) + { + newRule.m_iContextFlags |= APPLYCONTEXT_ENEMY; + continue; + } +#endif + if ( !Q_stricmp( token, "applyContext" ) ) { ParseToken(); @@ -2606,6 +2804,16 @@ void CResponseSystem::ParseRule( void ) if ( validRule ) { +#ifdef MAPBASE + short existing = m_Rules.Find( ruleName ); + if ( existing != m_Rules.InvalidIndex() ) + { + //ResponseWarning( "Additional definition for rule '%s', overwriting\n", ruleName ); + m_Rules[existing] = newRule; + m_Rules.SetElementName(existing, ruleName); + return; + } +#endif m_Rules.Insert( ruleName, newRule ); } else @@ -2791,7 +2999,11 @@ void CResponseSystem::CopyRuleFrom( Rule *pSrcRule, int iRule, CResponseSystem * dstRule.SetContext( pSrcRule->GetContext() ); dstRule.m_bMatchOnce = pSrcRule->m_bMatchOnce; dstRule.m_bEnabled = pSrcRule->m_bEnabled; +#ifdef MAPBASE + dstRule.m_iContextFlags = pSrcRule->m_iContextFlags; +#else dstRule.m_bApplyContextToWorld = pSrcRule->m_bApplyContextToWorld; +#endif // Copy off criteria. CopyCriteriaFrom( pSrcRule, &dstRule, pCustomSystem ); @@ -2806,6 +3018,10 @@ void CResponseSystem::CopyRuleFrom( Rule *pSrcRule, int iRule, CResponseSystem * pCustomSystem->m_Rules.Insert( m_Rules.GetElementName( iRule ), dstRule ); } +#ifdef MAPBASE +ConVar mapbase_rs_clear("mapbase_rs_clear", "1"); +#endif + //----------------------------------------------------------------------------- // Purpose: A special purpose response system associated with a custom entity //----------------------------------------------------------------------------- @@ -2853,6 +3069,32 @@ public: Clear(); delete this; } + +#ifdef MAPBASE + // From an old version of Mapbase's map-specific response system stuff. + /* + #define CopyRSDict(dict) for (unsigned int i = 0; i < dict.Count(); i++) \ + { \ + rs->dict.Insert(dict.GetElementName(i), dict[i]); \ + } \ + + bool MergeWithMain(bool bRemoveThis = true) + { + extern IResponseSystem *g_pResponseSystem; + CResponseSystem *rs = static_cast(g_pResponseSystem); + + CopyRSDict(m_Responses); + CopyRSDict(m_Criteria); + CopyRSDict(m_Rules); + CopyRSDict(m_Enumerations); + + if (mapbase_rs_clear.GetBool()) + Release(); + + return true; + } + */ +#endif private: char *m_pszScriptFile; @@ -2939,6 +3181,9 @@ public: Precache(); } +#ifdef MAPBASE + if (!rr_enhanced_saverestore.GetBool() || gpGlobals->eLoadType != MapLoad_Transition) +#endif ResetResponseGroups(); } @@ -3051,6 +3296,254 @@ CON_COMMAND( rr_reloadresponsesystems, "Reload all response system scripts." ) #endif } +#ifdef MAPBASE +// Designed for extern magic, this gives the <, >, etc. of response system criteria to the outside world. +// Mostly just used for Matcher_Match in matchers.h. +bool ResponseSystemCompare(const char *criterion, const char *value) +{ + Criteria criteria; + criteria.value = CopyString( criterion ); + defaultresponsesytem.ComputeMatcher(&criteria, criteria.matcher); + return defaultresponsesytem.CompareUsingMatcher(value, criteria.matcher, true); + + return false; +} + +// Another version that returns the criterion without the operators. +// I ended up not using this, but feel free to uncomment it. +// Just keep in mind it was scrapped before I could test it... +/* +const char *ResponseSystemCompare(const char *criterion, const char *value, bool bReturnToken) +{ + CResponseSystem *responsesys = dynamic_cast(g_pResponseSystem); + if (responsesys) + { + Criteria criteria; + criteria.value = CopyString( criterion ); + responsesys->ComputeMatcher(&criteria, criteria.matcher); + return responsesys->CompareUsingMatcher(value, criteria.matcher, true) ? criteria.matcher.GetToken() : NULL; + } + return NULL; +} +*/ + +//----------------------------------------------------------------------------- +// CResponseFilePrecacher +// +// Purpose: Precaches a single talker file. That's it. +// +// It copies from a bunch of the original Response System class and therefore it's really messy. +// Despite the horrors a normal programmer might find in here, I think it performs better than anything else I could've come up with. +//----------------------------------------------------------------------------- +class CResponseFilePrecacher +{ +public: + + // Stuff copied from the Response System. + // Direct copy-pastes are very compact, to say the least. + inline bool ParseToken( void ) + { + if ( m_bUnget ) + { m_bUnget = false; return true; } + if ( m_ScriptStack.Count() <= 0 ) + { return false; } + + m_ScriptStack[ 0 ].currenttoken = engine->ParseFile( m_ScriptStack[ 0 ].currenttoken, token, sizeof( token ) ); + m_ScriptStack[ 0 ].tokencount++; + return m_ScriptStack[ 0 ].currenttoken != NULL ? true : false; + } + + CUtlVector< CResponseSystem::ScriptEntry > m_ScriptStack; + bool m_bUnget; + char token[ 1204 ]; + + + void PrecacheResponse( const char *response, byte type ) + { + switch ( type ) + { + default: + break; + case RESPONSE_SCENE: + { + DevMsg("Precaching scene %s...\n", response); + + // fixup $gender references + char file[_MAX_PATH]; + Q_strncpy( file, response, sizeof(file) ); + char *gender = strstr( file, "$gender" ); + if ( gender ) + { + // replace with male & female + const char *postGender = gender + strlen("$gender"); + *gender = 0; + char genderFile[_MAX_PATH]; + + Q_snprintf( genderFile, sizeof(genderFile), "%smale%s", file, postGender); + PrecacheInstancedScene( genderFile ); + + Q_snprintf( genderFile, sizeof(genderFile), "%sfemale%s", file, postGender); + PrecacheInstancedScene( genderFile ); + } + else + { + PrecacheInstancedScene( file ); + } + } + break; + case RESPONSE_SPEAK: + { + DevMsg("Precaching sound %s...\n", response); + CBaseEntity::PrecacheScriptSound( response ); + } + break; + } + } + + bool IsRootCommand() + { + if (!Q_stricmp( token, "#include" ) || !Q_stricmp( token, "response" ) + || !Q_stricmp( token, "enumeration" ) || !Q_stricmp( token, "criteria" ) + || !Q_stricmp( token, "criterion" ) || !Q_stricmp( token, "rule" )) + return true; + return false; + } + + void ParseResponse( void ) + { + // Must go to response group name + ParseToken(); + + while ( 1 ) + { + ParseToken(); + + if ( !Q_stricmp( token, "{" ) ) + { + while ( 1 ) + { + ParseToken(); + if ( !Q_stricmp( token, "}" ) ) + break; + + byte type = ComputeResponseType( token ); + if (type == RESPONSE_NONE) + continue; + + ParseToken(); + char *value = CopyString( token ); + + PrecacheResponse(value, type); + } + break; + } + + byte type = ComputeResponseType( token ); + if (type == RESPONSE_NONE) + break; + + ParseToken(); + char *value = CopyString( token ); + + PrecacheResponse(value, type); + + break; + } + } + + bool LoadFromBuffer(const char *scriptfile, unsigned char *buffer, CStringPool &includedFiles) + { + includedFiles.Allocate( scriptfile ); + + CResponseSystem::ScriptEntry e; + e.name = filesystem->FindOrAddFileName( scriptfile ); + e.buffer = buffer; + e.currenttoken = (char *)e.buffer; + e.tokencount = 0; + m_ScriptStack.AddToHead( e ); + + while ( 1 ) + { + ParseToken(); + if ( !token[0] ) + { + break; + } + + if ( !Q_stricmp( token, "response" ) ) + { + ParseResponse(); + } + else if ( !Q_stricmp( token, "#include" ) || !Q_stricmp( token, "#base" ) ) + { + // Compacted version of ParseInclude(), including new changes. + // Look at that if you want to read. + char includefile[ 256 ]; + ParseToken(); + if (scriptfile) { size_t len = strlen(scriptfile)-1; + for (size_t i = 0; i < len; i++) + { if (scriptfile[i] == CORRECT_PATH_SEPARATOR || scriptfile[i] == INCORRECT_PATH_SEPARATOR) + { len = i; } + } Q_strncpy(includefile, scriptfile, len+1); + if (len+1 != strlen(scriptfile)) + { Q_snprintf(includefile, sizeof(includefile), "%s/%s", includefile, token); } + else includefile[0] = '\0'; + } if (!includefile[0]) Q_snprintf( includefile, sizeof( includefile ), "scripts/%s", token ); + + if ( includedFiles.Find( includefile ) == NULL ) + { + MEM_ALLOC_CREDIT(); + + // Try and load it + CUtlBuffer buf; + if ( filesystem->ReadFile( includefile, "GAME", buf ) ) + { + LoadFromBuffer( includefile, (unsigned char *)buf.PeekGet(), includedFiles ); + } + } + } + } + + if ( m_ScriptStack.Count() > 0 ) + m_ScriptStack.Remove( 0 ); + + return true; + } +}; + +// Loads a file directly to the main response system +bool LoadResponseSystemFile(const char *scriptfile) +{ + CUtlBuffer buf; + if ( !filesystem->ReadFile( scriptfile, "GAME", buf ) ) + { + return false; + } + + // This is a really messy and specialized system that precaches the responses and only the responses of a talker file. + CStringPool includedFiles; + CResponseFilePrecacher *rs = new CResponseFilePrecacher(); + if (!rs || !rs->LoadFromBuffer(scriptfile, (unsigned char *)buf.PeekGet(), includedFiles)) + { + Warning( "Failed to load response system data from %s", scriptfile ); + delete rs; + return false; + } + delete rs; + + CStringPool includedFiles2; + defaultresponsesytem.LoadFromBuffer(scriptfile, (const char *)buf.PeekGet(), includedFiles2); + + return true; +} + +// Called from Mapbase manifests to flush +void ReloadResponseSystem() +{ + defaultresponsesytem.ReloadAllResponseSystems(); +} +#endif + static short RESPONSESYSTEM_SAVE_RESTORE_VERSION = 1; // note: this won't save/restore settings from instanced response systems. Could add that with a CDefSaveRestoreOps implementation if needed @@ -3103,6 +3596,34 @@ public: pSave->EndBlock(); } + +#ifdef MAPBASE + // Enhanced Response System save/restore + int count2 = 0; + if (rr_enhanced_saverestore.GetBool()) + { + // Rule state save/load + count2 = rs.m_Rules.Count(); + pSave->WriteInt( &count2 ); + for ( int i = 0; i < count2; ++i ) + { + pSave->StartBlock( "Rule" ); + + pSave->WriteString( rs.m_Rules.GetElementName( i ) ); + const Rule *rule = &rs.m_Rules[ i ]; + + bool bEnabled = rule->m_bEnabled; + pSave->WriteBool( &bEnabled ); + + pSave->EndBlock(); + } + } + else + { + // Indicate this isn't using enhanced save/restore + pSave->WriteInt( &count2 ); + } +#endif } void Restore( IRestore *pRestore, bool createPlayers ) @@ -3166,6 +3687,34 @@ public: pRestore->EndBlock(); } + +#ifdef MAPBASE + // Enhanced Response System save/restore + count = pRestore->ReadInt(); + for ( int i = 0; i < count; ++i ) + { + char szRuleBlockName[SIZE_BLOCK_NAME_BUF]; + pRestore->StartBlock( szRuleBlockName ); + if ( !Q_stricmp( szRuleBlockName, "Rule" ) ) + { + char groupname[ 256 ]; + pRestore->ReadString( groupname, sizeof( groupname ), 0 ); + + // Try and find it + int idx = rs.m_Rules.Find( groupname ); + if ( idx != rs.m_Rules.InvalidIndex() ) + { + Rule *rule = &rs.m_Rules[ idx ]; + + bool bEnabled; + pRestore->ReadBool( &bEnabled ); + rule->m_bEnabled = bEnabled; + } + } + + pRestore->EndBlock(); + } +#endif } private: diff --git a/src/game/server/AI_ResponseSystem.h b/src/game/server/AI_ResponseSystem.h index a7b3a797..f1d031e4 100644 --- a/src/game/server/AI_ResponseSystem.h +++ b/src/game/server/AI_ResponseSystem.h @@ -4,6 +4,9 @@ // //=============================================================================// +#ifdef NEW_RESPONSE_SYSTEM +#include "ai_responsesystem_new.h" +#else #ifndef AI_RESPONSESYSTEM_H #define AI_RESPONSESYSTEM_H @@ -40,3 +43,4 @@ class ISaveRestoreBlockHandler *GetDefaultResponseSystemSaveRestoreBlockHandler( class ISaveRestoreOps *GetResponseSystemSaveRestoreOps(); #endif // AI_RESPONSESYSTEM_H +#endif diff --git a/src/game/server/BaseAnimatingOverlay.cpp b/src/game/server/BaseAnimatingOverlay.cpp index 4f8038f1..a1aa32a7 100644 --- a/src/game/server/BaseAnimatingOverlay.cpp +++ b/src/game/server/BaseAnimatingOverlay.cpp @@ -57,6 +57,45 @@ BEGIN_DATADESC( CBaseAnimatingOverlay ) END_DATADESC() +#ifdef MAPBASE_VSCRIPT +BEGIN_ENT_SCRIPTDESC( CBaseAnimatingOverlay, CBaseAnimating, "Animating models which support dynamic animation layers/overlays." ) + + DEFINE_SCRIPTFUNC( GetNumAnimOverlays, "Gets the current number of animation layers." ) + DEFINE_SCRIPTFUNC( RemoveAllGestures, "Removes all animation layers." ) + + DEFINE_SCRIPTFUNC( IsValidLayer, "Returns true if the specified layer index is valid." ) + DEFINE_SCRIPTFUNC( HasActiveLayer, "Returns true if there is currently an active layer." ) + DEFINE_SCRIPTFUNC( RemoveLayer, "Removes the specified layer index with the specified kill rate and delay." ) + DEFINE_SCRIPTFUNC( FastRemoveLayer, "Removes the specified layer index immediately." ) + + DEFINE_SCRIPTFUNC_NAMED( ScriptAddGesture, "AddGesture", "Adds a new animation layer using the specified activity name." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptAddGestureID, "AddGestureID", "Adds a new animation layer using the specified activity index." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptAddGestureSequence, "AddGestureSequence", "Adds a new animation layer using the specified activity name." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptAddGestureSequenceID, "AddGestureSequenceID", "Adds a new animation layer using the specified sequence index." ) + + DEFINE_SCRIPTFUNC_NAMED( ScriptFindGestureLayer, "FindGestureLayer", "Finds and returns the first active animation layer which uses the specified activity name." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptFindGestureLayerByID, "FindGestureLayerByID", "Finds and returns the first active animation layer which uses the specified activity index." ) + + DEFINE_SCRIPTFUNC_NAMED( ScriptGetLayerActivity, "GetLayerActivity", "Gets the activity name of the specified layer index." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptGetLayerActivityID, "GetLayerActivityID", "Gets the activity index of the specified layer index." ) + DEFINE_SCRIPTFUNC( GetLayerSequence, "Gets the sequence index of the specified layer index." ) + DEFINE_SCRIPTFUNC( SetLayerDuration, "Sets the duration of the specified layer index." ) + DEFINE_SCRIPTFUNC( GetLayerDuration, "Gets the duration of the specified layer index." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptSetLayerCycle, "SetLayerCycle", "Sets the cycle of the specified layer index." ) + DEFINE_SCRIPTFUNC( GetLayerCycle, "Gets the cycle of the specified layer index." ) + DEFINE_SCRIPTFUNC( SetLayerPlaybackRate, "Sets the playback rate of the specified layer index." ) + DEFINE_SCRIPTFUNC( SetLayerWeight, "Sets the weight of the specified layer index." ) + DEFINE_SCRIPTFUNC( GetLayerWeight, "Gets the weight of the specified layer index." ) + DEFINE_SCRIPTFUNC( SetLayerBlendIn, "Sets the fade-in of the specified layer index, with the fade being a 0-1 fraction of the cycle." ) + DEFINE_SCRIPTFUNC( SetLayerBlendOut, "Sets the fade-out of the specified layer index, with the fade being a 0-1 fraction of the cycle." ) + DEFINE_SCRIPTFUNC( SetLayerAutokill, "Sets whether or not the specified layer index should remove itself when it's finished playing." ) + DEFINE_SCRIPTFUNC( SetLayerLooping, "Sets whether or not the specified layer index should loop." ) + DEFINE_SCRIPTFUNC( SetLayerNoRestore, "Sets whether or not the specified layer index should restore after a save is loaded." ) + DEFINE_SCRIPTFUNC( SetLayerNoEvents, "Sets whether or not the specified layer index should fire animation events." ) + +END_SCRIPTDESC(); +#endif + #define ORDER_BITS 4 #define WEIGHT_BITS 8 @@ -356,7 +395,11 @@ void CBaseAnimatingOverlay::DispatchAnimEvents ( CBaseAnimating *eventHandler ) for ( int i = 0; i < m_AnimOverlay.Count(); i++ ) { +#ifdef MAPBASE // From Alien Swarm SDK + if (m_AnimOverlay[ i ].IsActive() && !m_AnimOverlay[ i ].NoEvents()) +#else if (m_AnimOverlay[ i ].IsActive()) +#endif { m_AnimOverlay[ i ].DispatchAnimEvents( eventHandler, this ); } @@ -429,6 +472,11 @@ void CAnimationLayer::DispatchAnimEvents( CBaseAnimating *eventHandler, CBaseAni event.eventtime = pOwner->m_flAnimTime + (flCycle - m_flCycle) / flCycleRate + pOwner->GetAnimTimeInterval(); } +#ifdef MAPBASE_VSCRIPT + if (eventHandler->m_ScriptScope.IsInitialized() && eventHandler->ScriptHookHandleAnimEvent( &event ) == false) + continue; +#endif + // Msg( "dispatch %d (%d : %.2f)\n", index - 1, event.event, event.eventtime ); eventHandler->HandleAnimEvent( &event ); } @@ -1068,6 +1116,38 @@ void CBaseAnimatingOverlay::SetLayerNoRestore( int iLayer, bool bNoRestore ) } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// From Alien Swarm SDK +//----------------------------------------------------------------------------- +void CBaseAnimatingOverlay::SetLayerNoEvents( int iLayer, bool bNoEvents ) +{ + if (!IsValidLayer( iLayer )) + return; + + if (bNoEvents) + { + m_AnimOverlay[iLayer].m_fFlags |= ANIM_LAYER_NOEVENTS; + } + else + { + m_AnimOverlay[iLayer].m_fFlags &= ~ANIM_LAYER_NOEVENTS; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CBaseAnimatingOverlay::IsLayerFinished( int iLayer ) +{ + if (!IsValidLayer( iLayer )) + return true; + + return m_AnimOverlay[iLayer].m_bSequenceFinished; +} +#endif + + //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- @@ -1165,4 +1245,36 @@ bool CBaseAnimatingOverlay::HasActiveLayer( void ) return false; } +#ifdef MAPBASE_VSCRIPT +int CBaseAnimatingOverlay::ScriptAddGesture( const char *pszActivity, bool autokill ) +{ + return AddGesture( (Activity)CAI_BaseNPC::GetActivityID( pszActivity ), autokill ); +} + +int CBaseAnimatingOverlay::ScriptAddGestureID( int iActivity, bool autokill ) +{ + return AddGesture( (Activity)iActivity, autokill ); +} + +int CBaseAnimatingOverlay::ScriptFindGestureLayer( const char *pszActivity ) +{ + return FindGestureLayer( (Activity)CAI_BaseNPC::GetActivityID( pszActivity ) ); +} + +int CBaseAnimatingOverlay::ScriptFindGestureLayerByID( int iActivity ) +{ + return FindGestureLayer( (Activity)iActivity ); +} + +const char *CBaseAnimatingOverlay::ScriptGetLayerActivity( int iLayer ) +{ + return CAI_BaseNPC::GetActivityName( GetLayerActivity( iLayer ) ); +} + +int CBaseAnimatingOverlay::ScriptGetLayerActivityID( int iLayer ) +{ + return GetLayerActivity( iLayer ); +} +#endif + //----------------------------------------------------------------------------- diff --git a/src/game/server/BaseAnimatingOverlay.h b/src/game/server/BaseAnimatingOverlay.h index 5184eac3..a428caa3 100644 --- a/src/game/server/BaseAnimatingOverlay.h +++ b/src/game/server/BaseAnimatingOverlay.h @@ -43,6 +43,9 @@ public: #define ANIM_LAYER_DONTRESTORE 0x0008 #define ANIM_LAYER_CHECKACCESS 0x0010 #define ANIM_LAYER_DYING 0x0020 +#ifdef MAPBASE // From Alien Swarm SDK +#define ANIM_LAYER_NOEVENTS 0x0040 +#endif int m_fFlags; @@ -80,6 +83,9 @@ public: void Dying( void ) { m_fFlags |= ANIM_LAYER_DYING; } bool IsDying( void ) { return ((m_fFlags & ANIM_LAYER_DYING) != 0); } void Dead( void ) { m_fFlags &= ~ANIM_LAYER_DYING; } +#ifdef MAPBASE // From Alien Swarm SDK + bool NoEvents( void ) { return ((m_fFlags & ANIM_LAYER_NOEVENTS) != 0); } +#endif bool IsAbandoned( void ); void MarkActive( void ); @@ -176,6 +182,11 @@ public: void SetLayerAutokill( int iLayer, bool bAutokill ); void SetLayerLooping( int iLayer, bool bLooping ); void SetLayerNoRestore( int iLayer, bool bNoRestore ); +#ifdef MAPBASE + void SetLayerNoEvents( int iLayer, bool bNoEvents ); // From Alien Swarm SDK + + bool IsLayerFinished( int iLayer ); +#endif Activity GetLayerActivity( int iLayer ); int GetLayerSequence( int iLayer ); @@ -196,9 +207,26 @@ public: private: int AllocateLayer( int iPriority = 0 ); // lower priorities are processed first +#ifdef MAPBASE_VSCRIPT + int ScriptAddGesture( const char *pszActivity, bool autokill ); + int ScriptAddGestureID( int iActivity, bool autokill ); + int ScriptAddGestureSequence( const char *pszSequence, bool autokill ) { return AddGestureSequence( LookupSequence( pszSequence ), autokill ); } + int ScriptAddGestureSequenceID( int iSequence, bool autokill ) { return AddGestureSequence( iSequence, autokill ); } + + int ScriptFindGestureLayer( const char *pszActivity ); + int ScriptFindGestureLayerByID( int iActivity ); + const char *ScriptGetLayerActivity( int iLayer ); + int ScriptGetLayerActivityID( int iLayer ); + + void ScriptSetLayerCycle( int iLayer, float flCycle ) { SetLayerCycle( iLayer, flCycle ); } +#endif + DECLARE_SERVERCLASS(); DECLARE_DATADESC(); DECLARE_PREDICTABLE(); +#ifdef MAPBASE_VSCRIPT + DECLARE_ENT_SCRIPTDESC(); +#endif }; EXTERN_SEND_TABLE(DT_BaseAnimatingOverlay); diff --git a/src/game/server/BasePropDoor.h b/src/game/server/BasePropDoor.h index d3d3fc47..39cced94 100644 --- a/src/game/server/BasePropDoor.h +++ b/src/game/server/BasePropDoor.h @@ -39,6 +39,9 @@ public: DECLARE_CLASS( CBasePropDoor, CDynamicProp ); DECLARE_SERVERCLASS(); +#ifdef MAPBASE_VSCRIPT + DECLARE_ENT_SCRIPTDESC(); +#endif CBasePropDoor( void ); @@ -83,6 +86,34 @@ public: virtual void ComputeDoorExtent( Extent *extent, unsigned int extentType ) = 0; // extent contains the volume encompassing by the door in the specified states // } +#ifdef MAPBASE + virtual bool PassesDoorFilter(CBaseEntity *pEntity) { return true; } + + virtual bool KeyValue( const char *szKeyName, const char *szValue ); + + float GetSpeed() const { return m_flSpeed; } +#endif + +#ifdef MAPBASE_VSCRIPT + bool ScriptIsDoorOpen() { return IsDoorOpen(); } + bool ScriptIsDoorAjar() { return IsDoorAjar(); } + bool ScriptIsDoorOpening() { return IsDoorOpening(); } + bool ScriptIsDoorClosed() { return IsDoorClosed(); } + bool ScriptIsDoorClosing() { return IsDoorClosing(); } + bool ScriptIsDoorLocked() { return IsDoorLocked(); } + bool ScriptIsDoorBlocked() const { return IsDoorBlocked(); } + HSCRIPT ScriptGetActivator() { return ToHScript( m_hActivator.Get() ); } + + HSCRIPT ScriptGetDoorList( int i ) { return m_hDoorList.IsValidIndex(i) ? ToHScript( m_hDoorList[i] ) : NULL; } + int GetDoorListCount() { return m_hDoorList.Count(); } + + const char *ScriptGetFullyOpenSound() { return STRING( m_SoundOpen ); } + const char *ScriptGetFullyClosedSound() { return STRING( m_SoundClose ); } + const char *ScriptGetMovingSound() { return STRING( m_SoundMoving ); } + const char *ScriptGetLockedSound() { return STRING( m_ls.sLockedSound ); } + const char *ScriptGetUnlockedSound() { return STRING( m_ls.sUnlockedSound ); } +#endif + protected: enum DoorState_t @@ -107,6 +138,12 @@ protected: inline CBaseEntity *GetActivator(); +#ifdef MAPBASE + inline float GetNPCOpenDistance() { return m_flNPCOpenDistance; } + inline Activity GetNPCOpenFrontActivity() { return m_eNPCOpenFrontActivity; } + inline Activity GetNPCOpenBackActivity() { return m_eNPCOpenBackActivity; } +#endif + private: // Implement these in your leaf class. @@ -171,6 +208,16 @@ private: void InputOpenAwayFrom(inputdata_t &inputdata); void InputToggle(inputdata_t &inputdata); void InputUnlock(inputdata_t &inputdata); +#ifdef MAPBASE + void InputAllowPlayerUse(inputdata_t &inputdata); + void InputDisallowPlayerUse(inputdata_t &inputdata); + + void InputSetFullyOpenSound(inputdata_t &inputdata); + void InputSetFullyClosedSound(inputdata_t &inputdata); + void InputSetMovingSound(inputdata_t &inputdata); + void InputSetLockedSound(inputdata_t &inputdata); + void InputSetUnlockedSound(inputdata_t &inputdata); +#endif void SetDoorBlocker( CBaseEntity *pBlocker ); @@ -196,6 +243,12 @@ private: string_t m_SoundOpen; string_t m_SoundClose; +#ifdef MAPBASE + float m_flNPCOpenDistance; + Activity m_eNPCOpenFrontActivity; + Activity m_eNPCOpenBackActivity; +#endif + // dvs: FIXME: can we remove m_flSpeed from CBaseEntity? //float m_flSpeed; // Rotation speed when opening or closing in degrees per second. diff --git a/src/game/server/CRagdollMagnet.cpp b/src/game/server/CRagdollMagnet.cpp index 454f7804..fc17cc9a 100644 --- a/src/game/server/CRagdollMagnet.cpp +++ b/src/game/server/CRagdollMagnet.cpp @@ -20,10 +20,17 @@ BEGIN_DATADESC( CRagdollMagnet ) DEFINE_KEYFIELD( m_force, FIELD_FLOAT, "force" ), DEFINE_KEYFIELD( m_axis, FIELD_VECTOR, "axis" ), DEFINE_KEYFIELD( m_bDisabled, FIELD_BOOLEAN, "StartDisabled" ), +#ifdef MAPBASE + DEFINE_KEYFIELD( m_BoneTarget, FIELD_STRING, "BoneTarget" ), +#endif DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ), DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ), +#ifdef MAPBASE + DEFINE_OUTPUT( m_OnUsed, "OnUsed" ), +#endif + END_DATADESC() //----------------------------------------------------------------------------- @@ -111,20 +118,54 @@ CRagdollMagnet *CRagdollMagnet::FindBestMagnet( CBaseEntity *pNPC ) // // NOTE: This function assumes pNPC is within this magnet's radius. //----------------------------------------------------------------------------- +#ifdef MAPBASE +Vector CRagdollMagnet::GetForceVector( CBaseEntity *pNPC, int *pBone ) +#else Vector CRagdollMagnet::GetForceVector( CBaseEntity *pNPC ) +#endif { Vector vecForceToApply; +#ifdef MAPBASE + Vector vecNPCPos = pNPC->WorldSpaceCenter(); + + if (pBone) + { + CBaseAnimating *pAnimating = pNPC->GetBaseAnimating(); + Assert( pAnimating != NULL ); + + const char *szBoneTarget = BoneTarget(); + Assert( szBoneTarget != NULL ); + + int iBone = pAnimating->LookupBone( szBoneTarget ); + + if (iBone != -1) + { + matrix3x4_t bonetoworld; + pAnimating->GetBoneTransform( iBone, bonetoworld ); + MatrixPosition( bonetoworld, vecNPCPos ); + *pBone = iBone; + } + } +#endif + if( IsBarMagnet() ) { CPlane axis; Vector vecForceDir; Vector vecClosest; +#ifdef MAPBASE + CalcClosestPointOnLineSegment( vecNPCPos, GetAbsOrigin(), m_axis, vecClosest, NULL ); + + vecForceDir = (vecClosest - vecNPCPos ); + VectorNormalize( vecForceDir ); +#else CalcClosestPointOnLineSegment( pNPC->WorldSpaceCenter(), GetAbsOrigin(), m_axis, vecClosest, NULL ); vecForceDir = (vecClosest - pNPC->WorldSpaceCenter() ); VectorNormalize( vecForceDir ); +#endif vecForceToApply = vecForceDir * m_force; } @@ -132,7 +173,11 @@ Vector CRagdollMagnet::GetForceVector( CBaseEntity *pNPC ) { Vector vecForce; +#ifdef MAPBASE + vecForce = GetAbsOrigin() - vecNPCPos; +#else vecForce = GetAbsOrigin() - pNPC->WorldSpaceCenter(); +#endif VectorNormalize( vecForce ); vecForceToApply = vecForce * m_force; diff --git a/src/game/server/CRagdollMagnet.h b/src/game/server/CRagdollMagnet.h index 1ba54ac4..2d14ef69 100644 --- a/src/game/server/CRagdollMagnet.h +++ b/src/game/server/CRagdollMagnet.h @@ -18,7 +18,11 @@ public: DECLARE_CLASS( CRagdollMagnet, CPointEntity ); DECLARE_DATADESC(); +#ifdef MAPBASE + Vector GetForceVector( CBaseEntity *pNPC, int *pBone = NULL ); +#else Vector GetForceVector( CBaseEntity *pNPC ); +#endif float GetRadius( void ) { return m_radius; } Vector GetAxisVector( void ) { return m_axis - GetAbsOrigin(); } float DistToPoint( const Vector &vecPoint ); @@ -35,11 +39,20 @@ public: void InputEnable( inputdata_t &inputdata ); void InputDisable( inputdata_t &inputdata ); +#ifdef MAPBASE + const char *BoneTarget() { return STRING(m_BoneTarget); } + + COutputVector m_OnUsed; +#endif + private: bool m_bDisabled; float m_radius; float m_force; Vector m_axis; +#ifdef MAPBASE + string_t m_BoneTarget; +#endif }; #endif //CRAGDOLLMAGNET_H diff --git a/src/game/server/CommentarySystem.cpp b/src/game/server/CommentarySystem.cpp index 9ae5f6e3..28e24b5d 100644 --- a/src/game/server/CommentarySystem.cpp +++ b/src/game/server/CommentarySystem.cpp @@ -22,6 +22,11 @@ #include "gamestats.h" #include "ai_basenpc.h" #include "Sprite.h" +#ifdef MAPBASE +#include "mapbase/SystemConvarMod.h" +#include +#include +#endif // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" @@ -42,6 +47,7 @@ enum teleport_stages_t TELEPORT_FADEIN, }; +#ifndef MAPBASE // This has been moved to mapbase/SystemConvarMod.h // Convar restoration save/restore #define MAX_MODIFIED_CONVAR_STRING 128 struct modifiedconvars_t @@ -52,6 +58,7 @@ struct modifiedconvars_t char pszCurrentValue[MAX_MODIFIED_CONVAR_STRING]; char pszOrgValue[MAX_MODIFIED_CONVAR_STRING]; }; +#endif bool g_bInCommentaryMode = false; bool IsInCommentaryMode( void ) @@ -67,8 +74,23 @@ class CPointCommentaryNode : public CBaseAnimating DECLARE_CLASS( CPointCommentaryNode, CBaseAnimating ); public: DECLARE_DATADESC(); +#ifdef MAPBASE_VSCRIPT + DECLARE_ENT_SCRIPTDESC(); +#endif DECLARE_SERVERCLASS(); + CPointCommentaryNode() + { +#ifdef MAPBASE + m_flViewTargetSpeedScale = 1.0f; + m_flViewPositionSpeedScale = 1.0f; + m_flReturnSpeedScale = 0.0f; + m_flPanelScale = 1.0f; + m_flPanelX = -1.0f; + m_flPanelY = -1.0f; +#endif + } + void Spawn( void ); void Precache( void ); void Activate( void ); @@ -97,11 +119,39 @@ public: void TeleportTo( CBasePlayer *pPlayer ); bool CanTeleportTo( void ); +#ifdef MAPBASE + bool IsActive() { return m_bActive; } + bool IsDisabled() { return m_bDisabled; } + + int GetCommentaryType() { return m_iCommentaryType; } + void SetCommentaryType( int iType ) { m_iCommentaryType = iType; } + + const char *GetCommentaryFile() { return STRING( m_iszCommentaryFile.Get() ); } + void SetCommentaryFile( const char *pszNewFile ) { m_iszCommentaryFile.Set( AllocPooledString( pszNewFile ) ); } + const char *GetSpeakers() { return STRING( m_iszSpeakers.Get() ); } + void SetSpeakers( const char *pszSpeakers ) { m_iszSpeakers.Set( AllocPooledString( pszSpeakers ) ); } + const char *GetPrintName() { return STRING( m_iszPrintName.Get() ); } + void SetPrintName( const char *pszPrintName ) { m_iszPrintName.Set( AllocPooledString( pszPrintName ) ); } + const char *GetFootnote() { return STRING( m_iszFootnote.Get() ); } + void SetFootnote( const char *pszFootnote ) { m_iszFootnote.Set( AllocPooledString( pszFootnote ) ); } +#endif + // Inputs void InputStartCommentary( inputdata_t &inputdata ); void InputStartUnstoppableCommentary( inputdata_t &inputdata ); void InputEnable( inputdata_t &inputdata ); void InputDisable( inputdata_t &inputdata ); +#ifdef MAPBASE + void InputSetViewTarget( inputdata_t &inputdata ); + void InputSetViewPosition( inputdata_t &inputdata ); + void InputSetViewTargetSpeed( inputdata_t &inputdata ); + void InputSetViewPositionSpeed( inputdata_t &inputdata ); + void InputSetReturnSpeed( inputdata_t &inputdata ); +#endif + +#ifdef MAPBASE_VSCRIPT + static ScriptHook_t g_Hook_PreStartCommentary; +#endif private: string_t m_iszPreCommands; @@ -114,6 +164,14 @@ private: string_t m_iszViewPosition; CNetworkVar( EHANDLE, m_hViewPosition ); EHANDLE m_hViewPositionMover; // Entity used to blend the view to the viewposition entity +#ifdef MAPBASE + float m_flViewTargetSpeedScale; + float m_flViewPositionSpeedScale; + float m_flReturnSpeedScale; + CNetworkVar( string_t, m_iszPrintName ); + CNetworkVar( string_t, m_iszFootnote ); + float m_flViewPositionChangedTime; // View position now blends relative to this value. Mainly needed for when SetViewPosition is used +#endif bool m_bPreventMovement; bool m_bUnderCrosshair; bool m_bUnstoppable; @@ -133,6 +191,13 @@ private: CNetworkVar( string_t, m_iszSpeakers ); CNetworkVar( int, m_iNodeNumber ); CNetworkVar( int, m_iNodeNumberMax ); + +#ifdef MAPBASE + CNetworkVar( int, m_iCommentaryType ); + CNetworkVar( float, m_flPanelScale ); + CNetworkVar( float, m_flPanelX ); + CNetworkVar( float, m_flPanelY ); +#endif }; BEGIN_DATADESC( CPointCommentaryNode ) @@ -161,6 +226,18 @@ BEGIN_DATADESC( CPointCommentaryNode ) DEFINE_FIELD( m_bPreventChangesWhileMoving, FIELD_BOOLEAN ), DEFINE_KEYFIELD( m_bDisabled, FIELD_BOOLEAN, "start_disabled" ), DEFINE_KEYFIELD( m_vecTeleportOrigin, FIELD_VECTOR, "teleport_origin" ), +#ifdef MAPBASE + DEFINE_KEYFIELD( m_flViewTargetSpeedScale, FIELD_FLOAT, "viewtarget_speed" ), + DEFINE_KEYFIELD( m_flViewPositionSpeedScale, FIELD_FLOAT, "viewposition_speed" ), + DEFINE_KEYFIELD( m_flReturnSpeedScale, FIELD_FLOAT, "return_speed" ), + DEFINE_KEYFIELD( m_iszPrintName, FIELD_STRING, "printname" ), + DEFINE_KEYFIELD( m_iszFootnote, FIELD_STRING, "footnote" ), + DEFINE_FIELD( m_flViewPositionChangedTime, FIELD_TIME ), + DEFINE_KEYFIELD( m_iCommentaryType, FIELD_INTEGER, "type" ), + DEFINE_KEYFIELD( m_flPanelScale, FIELD_FLOAT, "panelscale" ), + DEFINE_KEYFIELD( m_flPanelX, FIELD_FLOAT, "x" ), + DEFINE_KEYFIELD( m_flPanelY, FIELD_FLOAT, "y" ), +#endif // Outputs DEFINE_OUTPUT( m_pOnCommentaryStarted, "OnCommentaryStarted" ), @@ -171,6 +248,13 @@ BEGIN_DATADESC( CPointCommentaryNode ) DEFINE_INPUTFUNC( FIELD_VOID, "StartUnstoppableCommentary", InputStartUnstoppableCommentary ), DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ), DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ), +#ifdef MAPBASE + DEFINE_INPUTFUNC( FIELD_EHANDLE, "SetViewTarget", InputSetViewTarget ), + DEFINE_INPUTFUNC( FIELD_EHANDLE, "SetViewPosition", InputSetViewPosition ), + DEFINE_INPUTFUNC( FIELD_FLOAT, "SetViewTargetSpeed", InputSetViewTargetSpeed ), + DEFINE_INPUTFUNC( FIELD_FLOAT, "SetViewPositionSpeed", InputSetViewPositionSpeed ), + DEFINE_INPUTFUNC( FIELD_FLOAT, "SetReturnSpeed", InputSetReturnSpeed ), +#endif // Functions DEFINE_THINKFUNC( SpinThink ), @@ -178,6 +262,37 @@ BEGIN_DATADESC( CPointCommentaryNode ) DEFINE_THINKFUNC( UpdateViewPostThink ), END_DATADESC() +#ifdef MAPBASE_VSCRIPT + +ScriptHook_t CPointCommentaryNode::g_Hook_PreStartCommentary; + +BEGIN_ENT_SCRIPTDESC( CPointCommentaryNode, CBaseAnimating, "Commentary nodes which play commentary in commentary mode." ) + DEFINE_SCRIPTFUNC( IsDisabled, "" ) + DEFINE_SCRIPTFUNC( SetDisabled, "" ) + + DEFINE_SCRIPTFUNC( IsActive, "" ) + DEFINE_SCRIPTFUNC( GetCommentaryFile, "" ) + DEFINE_SCRIPTFUNC( SetCommentaryFile, "" ) + DEFINE_SCRIPTFUNC( GetSpeakers, "" ) + DEFINE_SCRIPTFUNC( SetSpeakers, "" ) + DEFINE_SCRIPTFUNC( GetPrintName, "" ) + DEFINE_SCRIPTFUNC( SetPrintName, "" ) + DEFINE_SCRIPTFUNC( GetFootnote, "" ) + DEFINE_SCRIPTFUNC( SetFootnote, "" ) + DEFINE_SCRIPTFUNC( GetCommentaryType, "" ) + DEFINE_SCRIPTFUNC( SetCommentaryType, "" ) + + DEFINE_SCRIPTFUNC( HasViewTarget, "" ) + DEFINE_SCRIPTFUNC( PreventsMovement, "" ) + DEFINE_SCRIPTFUNC( CannotBeStopped, "" ) + + DEFINE_SCRIPTFUNC( AbortPlaying, "Stops playing the node and snaps out of its camera control immediately. The game uses this function to shut down commentary while in the middle of playing a node, as it can't smoothly blend out (since the commentary entities need to be removed)." ) + + DEFINE_SIMPLE_SCRIPTHOOK( CPointCommentaryNode::g_Hook_PreStartCommentary, "PreStartCommentary", FIELD_BOOLEAN, "Called just before commentary begins. Use this to modify variables or commentary behavior before it begins. Returning false will prevent the commentary from starting." ) +END_SCRIPTDESC(); + +#endif // MAPBASE_VSCRIPT + IMPLEMENT_SERVERCLASS_ST( CPointCommentaryNode, DT_PointCommentaryNode ) SendPropBool( SENDINFO(m_bActive) ), SendPropStringT( SENDINFO(m_iszCommentaryFile) ), @@ -187,6 +302,14 @@ IMPLEMENT_SERVERCLASS_ST( CPointCommentaryNode, DT_PointCommentaryNode ) SendPropInt( SENDINFO(m_iNodeNumber), 8, SPROP_UNSIGNED ), SendPropInt( SENDINFO(m_iNodeNumberMax), 8, SPROP_UNSIGNED ), SendPropEHandle( SENDINFO(m_hViewPosition) ), +#ifdef MAPBASE + SendPropStringT( SENDINFO( m_iszPrintName ) ), + SendPropStringT( SENDINFO( m_iszFootnote ) ), + SendPropInt( SENDINFO( m_iCommentaryType ), 2, SPROP_UNSIGNED ), + SendPropFloat( SENDINFO( m_flPanelScale ) ), + SendPropFloat( SENDINFO( m_flPanelX ) ), + SendPropFloat( SENDINFO( m_flPanelY ) ), +#endif END_SEND_TABLE() LINK_ENTITY_TO_CLASS( point_commentary_node, CPointCommentaryNode ); @@ -666,7 +789,16 @@ public: Msg( "Commentary: Could not find commentary data file '%s'. \n", szFullName ); } +#ifdef MAPBASE // VDC Memory Leak Fixes + pkvFile->deleteThis(); +#endif + engine->LockNetworkStringTables( oldLock ); + +#ifdef MAPBASE + // Special commentary localization file (useful for things like text nodes or print names) + g_pVGuiLocalize->AddFile( "resource/commentary_%language%.txt", "MOD" ); +#endif } void ShutDownCommentary( void ) @@ -824,11 +956,13 @@ BEGIN_DATADESC_NO_BASE( CCommentarySystem ) DEFINE_FIELD( m_iCommentaryNodeCount, FIELD_INTEGER ), END_DATADESC() +#ifndef MAPBASE // This has been moved to mapbase/SystemConvarMod.h BEGIN_SIMPLE_DATADESC( modifiedconvars_t ) DEFINE_ARRAY( pszConvar, FIELD_CHARACTER, MAX_MODIFIED_CONVAR_STRING ), DEFINE_ARRAY( pszCurrentValue, FIELD_CHARACTER, MAX_MODIFIED_CONVAR_STRING ), DEFINE_ARRAY( pszOrgValue, FIELD_CHARACTER, MAX_MODIFIED_CONVAR_STRING ), END_DATADESC() +#endif //----------------------------------------------------------------------------- @@ -884,10 +1018,32 @@ bool IsListeningToCommentary( void ) void CPointCommentaryNode::Spawn( void ) { // No model specified? - char *szModel = (char *)STRING( GetModelName() ); + const char *szModel = STRING( GetModelName() ); if (!szModel || !*szModel) { +#ifdef MAPBASE + switch (m_iCommentaryType) + { + case COMMENTARY_TYPE_TEXT: + szModel = "models/extras/info_text.mdl"; + break; + + case COMMENTARY_TYPE_IMAGE: + szModel = "models/extras/info_image.mdl"; + break; + + case COMMENTARY_TYPE_SCENE: + szModel = "models/extras/info_scene.mdl"; + break; + + default: + case COMMENTARY_TYPE_AUDIO: + szModel = "models/extras/info_speech.mdl"; + break; + } +#else szModel = "models/extras/info_speech.mdl"; +#endif SetModelName( AllocPooledString(szModel) ); } @@ -898,6 +1054,12 @@ void CPointCommentaryNode::Spawn( void ) AddSolidFlags( FSOLID_CUSTOMRAYTEST | FSOLID_CUSTOMBOXTEST ); AddEffects( EF_NOSHADOW ); +#ifdef MAPBASE + // Default to view position speed scale (which in turn defaults to 1.0) + if (m_flReturnSpeedScale == 0.0f) + m_flReturnSpeedScale = m_flViewPositionSpeedScale; +#endif + // Setup for animation ResetSequence( LookupSequence("idle") ); SetThink( &CPointCommentaryNode::SpinThink ); @@ -1111,6 +1273,19 @@ void CPointCommentaryNode::StartCommentary( void ) if ( !pPlayer ) return; +#ifdef MAPBASE_VSCRIPT + if (m_ScriptScope.IsInitialized() && g_Hook_PreStartCommentary.CanRunInScope( m_ScriptScope )) + { + ScriptVariant_t functionReturn; + if ( g_Hook_PreStartCommentary.Call( m_ScriptScope, &functionReturn, NULL ) && functionReturn.m_type == FIELD_BOOLEAN ) + { + // Don't play the commentary if it returned false + if (functionReturn.m_bool == false) + return; + } + } +#endif + m_bActive = true; m_flAnimTime = gpGlobals->curtime; @@ -1132,6 +1307,21 @@ void CPointCommentaryNode::StartCommentary( void ) // Start the commentary m_flStartTime = gpGlobals->curtime; +#ifdef MAPBASE + if (m_hViewPosition.Get()) + { + m_flViewPositionChangedTime = gpGlobals->curtime; + } + else + { + m_flViewPositionChangedTime = -1.0f; + } + + // This is now used in certain places to denote the "last blend to" origin + m_vecFinishOrigin = pPlayer->EyePosition(); + m_vecFinishAngles = pPlayer->EyeAngles(); +#endif + // If we have a view target, start blending towards it if ( m_hViewTarget || m_hViewPosition.Get() ) { @@ -1206,6 +1396,10 @@ void CPointCommentaryNode::UpdateViewThink( void ) float dx = AngleDiff( angGoal.x, angCurrent.x ); float dy = AngleDiff( angGoal.y, angCurrent.y ); float mod = 1.0 - ExponentialDecay( 0.5, 0.3, gpGlobals->frametime ); +#ifdef MAPBASE + if (m_flViewTargetSpeedScale != 1.0f) + mod *= m_flViewTargetSpeedScale; +#endif float dxmod = dx * mod; float dymod = dy * mod; @@ -1246,16 +1440,85 @@ void CPointCommentaryNode::UpdateViewThink( void ) } // Blend to the target position over time. - float flCurTime = (gpGlobals->curtime - m_flStartTime); +#ifdef MAPBASE + float flCurTime = (gpGlobals->curtime - m_flViewPositionChangedTime); + if (m_flViewPositionSpeedScale != 1.0f) + flCurTime *= m_flViewPositionSpeedScale; +#else + float flCurTime = (gpGlobals->curtime - m_flStartTime); +#endif float flBlendPerc = clamp( flCurTime * 0.5f, 0.f, 1.f ); // Figure out the current view position Vector vecCurEye; +#ifdef MAPBASE + VectorLerp( m_vecFinishOrigin, m_hViewPosition.Get()->GetAbsOrigin(), flBlendPerc, vecCurEye ); +#else VectorLerp( pPlayer->EyePosition(), m_hViewPosition.Get()->GetAbsOrigin(), flBlendPerc, vecCurEye ); +#endif m_hViewPositionMover->SetAbsOrigin( vecCurEye ); SetNextThink( gpGlobals->curtime, s_pCommentaryUpdateViewThink ); } +#ifdef MAPBASE + else if ( m_flViewPositionChangedTime != -1.0f && m_hViewPositionMover ) + { + // Blend back to the player's position over time. + float flCurTime = (gpGlobals->curtime - m_flViewPositionChangedTime); + if (m_flViewPositionSpeedScale != 1.0f) + flCurTime *= m_flViewPositionSpeedScale; + + //float flTimeToBlend = MIN( 2.0, m_flViewPositionChangedTime - m_flStartTime ); + //float flBlendPerc = 1.0f - clamp( flCurTime / flTimeToBlend, 0.f, 1.f ); + float flBlendPerc = 1.0f - clamp( flCurTime * 0.5f, 0.f, 1.f ); + + //Msg("OUT: CurTime %.2f, BlendTime: %.2f, Blend: %.3f\n", flCurTime, flTimeToBlend, flBlendPerc ); + + // Only do this while we're still moving + if ( flBlendPerc > 0 ) + { + // Figure out the current view position + Vector vecPlayerPos = pPlayer->EyePosition(); + Vector vecToPosition = (m_vecFinishOrigin - vecPlayerPos); + Vector vecCurEye = pPlayer->EyePosition() + (vecToPosition * flBlendPerc); + m_hViewPositionMover->SetAbsOrigin( vecCurEye ); + + if ( m_hViewTarget ) + { + Quaternion quatFinish; + Quaternion quatOriginal; + Quaternion quatCurrent; + AngleQuaternion( m_vecOriginalAngles, quatOriginal ); + AngleQuaternion( m_vecFinishAngles, quatFinish ); + QuaternionSlerp( quatFinish, quatOriginal, 1.0 - flBlendPerc, quatCurrent ); + QAngle angCurrent; + QuaternionAngles( quatCurrent, angCurrent ); + m_hViewPositionMover->SetAbsAngles( angCurrent ); + } + + SetNextThink( gpGlobals->curtime, s_pCommentaryUpdateViewThink ); + return; + } + else + { + pPlayer->SnapEyeAngles( m_hViewPositionMover->GetAbsAngles() ); + + // Try to clean up the view position stuff without ending the commentary + if ( !m_hViewTargetAngles && pPlayer->GetActiveWeapon() ) + { + pPlayer->GetActiveWeapon()->Deploy(); + } + + if (pPlayer->GetViewEntity() == m_hViewPositionMover) + { + pPlayer->SetViewEntity( NULL ); + } + UTIL_Remove( m_hViewPositionMover ); + + m_flViewPositionChangedTime = -1.0f; + } + } +#endif } //----------------------------------------------------------------------------- @@ -1271,6 +1534,10 @@ void CPointCommentaryNode::UpdateViewPostThink( void ) { // Blend back to the player's position over time. float flCurTime = (gpGlobals->curtime - m_flFinishedTime); +#ifdef MAPBASE + if (m_flReturnSpeedScale != 1.0f) + flCurTime *= m_flReturnSpeedScale; +#endif float flTimeToBlend = MIN( 2.0, m_flFinishedTime - m_flStartTime ); float flBlendPerc = 1.0f - clamp( flCurTime / flTimeToBlend, 0.f, 1.f ); @@ -1432,6 +1699,79 @@ void CPointCommentaryNode::InputDisable( inputdata_t &inputdata ) SetDisabled( true ); } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPointCommentaryNode::InputSetViewTarget( inputdata_t &inputdata ) +{ + m_hViewTarget = inputdata.value.Entity(); + + // Do not let Activate() reassign this + m_iszViewTarget = NULL_STRING; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPointCommentaryNode::InputSetViewPosition( inputdata_t &inputdata ) +{ + if (m_hViewPosition.Get() && m_hViewPositionMover) + { + // In case the view position is being cleared, assign the "finish" vectors + m_vecFinishOrigin = m_hViewPositionMover->GetAbsOrigin(); + m_vecFinishAngles = m_hViewPositionMover->GetAbsAngles(); + } + else + { + CBasePlayer *pPlayer = GetCommentaryPlayer(); + if (pPlayer) + { + // And in case it's a new view position coming from the player, assign the "finish" vectors to the player + m_vecFinishOrigin = pPlayer->EyePosition(); + m_vecFinishAngles = m_vecOriginalAngles = pPlayer->EyeAngles(); + } + } + + m_hViewPosition = inputdata.value.Entity(); + + // Do not let Activate() reassign this + m_iszViewPosition = NULL_STRING; + + m_flViewPositionChangedTime = gpGlobals->curtime; + + // If we have a view target, start blending towards it + if ( m_hViewPosition.Get() ) + { + SetContextThink( &CPointCommentaryNode::UpdateViewThink, gpGlobals->curtime, s_pCommentaryUpdateViewThink ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPointCommentaryNode::InputSetViewTargetSpeed( inputdata_t &inputdata ) +{ + m_flViewTargetSpeedScale = inputdata.value.Float(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPointCommentaryNode::InputSetViewPositionSpeed( inputdata_t &inputdata ) +{ + m_flViewPositionSpeedScale = inputdata.value.Float(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPointCommentaryNode::InputSetReturnSpeed( inputdata_t &inputdata ) +{ + m_flReturnSpeedScale = inputdata.value.Float(); +} +#endif + //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- @@ -1493,7 +1833,11 @@ void CPointCommentaryNode::SetTransmit( CCheckTransmitInfo *pInfo, bool bAlways bool CPointCommentaryNode::PreventsMovement( void ) { // If we're moving the player's view at all, prevent movement +#ifdef MAPBASE + if ( m_hViewPosition.Get() || m_flViewPositionChangedTime != -1.0f ) +#else if ( m_hViewPosition.Get() ) +#endif return true; return m_bPreventMovement; diff --git a/src/game/server/EntityFlame.cpp b/src/game/server/EntityFlame.cpp index d3a1be10..80217efb 100644 --- a/src/game/server/EntityFlame.cpp +++ b/src/game/server/EntityFlame.cpp @@ -242,7 +242,12 @@ void CEntityFlame::FlameThink( void ) } CAI_BaseNPC *pNPC = m_hEntAttached->MyNPCPointer(); +#ifdef MAPBASE + // Don't extingish if the NPC is still dying + if ( pNPC && !pNPC->IsAlive() && pNPC->m_lifeState != LIFE_DYING ) +#else if ( pNPC && !pNPC->IsAlive() ) +#endif { UTIL_Remove( this ); // Notify the NPC that it's no longer burning! diff --git a/src/game/server/EnvBeam.cpp b/src/game/server/EnvBeam.cpp index b4308811..c74df00d 100644 --- a/src/game/server/EnvBeam.cpp +++ b/src/game/server/EnvBeam.cpp @@ -48,6 +48,11 @@ public: void InputTurnOff( inputdata_t &inputdata ); void InputToggle( inputdata_t &inputdata ); void InputStrikeOnce( inputdata_t &inputdata ); +#ifdef MAPBASE + void InputAmplitude( inputdata_t &inputdata ); + void InputSetStartEntity( inputdata_t &inputdata ) { m_iszStartEntity = inputdata.value.StringID(); BeamUpdateVars(); } + void InputSetEndEntity( inputdata_t &inputdata ) { m_iszEndEntity = inputdata.value.StringID(); BeamUpdateVars(); } +#endif void TurnOn( void ); void TurnOff( void ); @@ -123,6 +128,11 @@ BEGIN_DATADESC( CEnvBeam ) DEFINE_INPUTFUNC( FIELD_VOID, "TurnOff", InputTurnOff ), DEFINE_INPUTFUNC( FIELD_VOID, "Toggle", InputToggle ), DEFINE_INPUTFUNC( FIELD_VOID, "StrikeOnce", InputStrikeOnce ), +#ifdef MAPBASE + DEFINE_INPUTFUNC( FIELD_FLOAT, "Amplitude", InputAmplitude ), + DEFINE_INPUTFUNC( FIELD_STRING, "SetStartEntity", InputSetStartEntity ), + DEFINE_INPUTFUNC( FIELD_STRING, "SetEndEntity", InputSetEndEntity ), +#endif DEFINE_OUTPUT( m_OnTouchedByEntity, "OnTouchedByEntity" ), @@ -287,6 +297,17 @@ void CEnvBeam::InputStrikeOnce( inputdata_t &inputdata ) } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: Input handler for amplitude +//----------------------------------------------------------------------------- +void CEnvBeam::InputAmplitude( inputdata_t &inputdata ) +{ + m_noiseAmplitude = inputdata.value.Float(); +} +#endif + + //----------------------------------------------------------------------------- // Purpose: Turns the lightning on. If it is set for interval refiring, it will // begin doing so. If it is set to be continually on, it will do so. @@ -383,11 +404,53 @@ void CEnvBeam::Strike( void ) m_speed = clamp( (int) m_speed, 0, (int) MAX_BEAM_SCROLLSPEED ); +#ifdef MAPBASE + bool pointStart = IsStaticPointEntity( pStart ); + bool pointEnd = IsStaticPointEntity( pEnd ); +#else int pointStart = IsStaticPointEntity( pStart ); int pointEnd = IsStaticPointEntity( pEnd ); +#endif if ( pointStart || pointEnd ) { +#ifdef MAPBASE + if ( m_spawnflags & SF_BEAM_RING ) + { + te->BeamRing( filter, 0.0, + pStart->entindex(), + pEnd->entindex(), + m_spriteTexture, + 0, // No halo + m_frameStart, + (int)m_flFrameRate, + m_life, + m_boltWidth, + 0, // No spread + m_noiseAmplitude, + m_clrRender->r, m_clrRender->g, m_clrRender->b, m_clrRender->a, + m_speed ); + } + else + { + te->BeamEntPoint( filter, 0.0, + pStart->entindex(), + &pStart->GetAbsOrigin(), + pEnd->entindex(), + &pEnd->GetAbsOrigin(), + m_spriteTexture, + 0, // No halo + m_frameStart, + (int)m_flFrameRate, + m_life, + m_boltWidth, + m_boltWidth, // End width + 0, // No fade + m_noiseAmplitude, + m_clrRender->r, m_clrRender->g, m_clrRender->b, m_clrRender->a, + m_speed ); + } +#else if ( m_spawnflags & SF_BEAM_RING ) { // don't work @@ -410,6 +473,7 @@ void CEnvBeam::Strike( void ) m_noiseAmplitude, m_clrRender->r, m_clrRender->g, m_clrRender->b, m_clrRender->a, m_speed ); +#endif } else { diff --git a/src/game/server/EnvFade.cpp b/src/game/server/EnvFade.cpp index fa0185d5..8b6c58f2 100644 --- a/src/game/server/EnvFade.cpp +++ b/src/game/server/EnvFade.cpp @@ -58,6 +58,9 @@ END_DATADESC() #define SF_FADE_MODULATE 0x0002 // Modulate, don't blend #define SF_FADE_ONLYONE 0x0004 #define SF_FADE_STAYOUT 0x0008 +#ifdef MAPBASE +#define SF_FADE_DONT_PURGE 0x0016 +#endif //----------------------------------------------------------------------------- // Purpose: @@ -93,6 +96,13 @@ void CEnvFade::InputFade( inputdata_t &inputdata ) fadeFlags |= FFADE_STAYOUT; } +#ifdef MAPBASE + if ( !HasSpawnFlags(SF_FADE_DONT_PURGE) ) + { + fadeFlags |= FFADE_PURGE; + } +#endif + if ( m_spawnflags & SF_FADE_ONLYONE ) { if ( inputdata.pActivator && inputdata.pActivator->IsNetClient() ) @@ -102,7 +112,11 @@ void CEnvFade::InputFade( inputdata_t &inputdata ) } else { +#ifdef MAPBASE + UTIL_ScreenFadeAll( m_clrRender, Duration(), HoldTime(), fadeFlags ); +#else UTIL_ScreenFadeAll( m_clrRender, Duration(), HoldTime(), fadeFlags|FFADE_PURGE ); +#endif } m_OnBeginFade.FireOutput( inputdata.pActivator, this ); diff --git a/src/game/server/EnvHudHint.cpp b/src/game/server/EnvHudHint.cpp index 858fc7b9..2cb38eab 100644 --- a/src/game/server/EnvHudHint.cpp +++ b/src/game/server/EnvHudHint.cpp @@ -32,6 +32,9 @@ private: void InputShowHudHint( inputdata_t &inputdata ); void InputHideHudHint( inputdata_t &inputdata ); +#ifdef MAPBASE + void InputSetHudHint( inputdata_t &inputdata ); +#endif string_t m_iszMessage; DECLARE_DATADESC(); }; @@ -43,6 +46,9 @@ BEGIN_DATADESC( CEnvHudHint ) DEFINE_KEYFIELD( m_iszMessage, FIELD_STRING, "message" ), DEFINE_INPUTFUNC( FIELD_VOID, "ShowHudHint", InputShowHudHint ), DEFINE_INPUTFUNC( FIELD_VOID, "HideHudHint", InputHideHudHint ), +#ifdef MAPBASE + DEFINE_INPUTFUNC( FIELD_STRING, "SetHudHint", InputSetHudHint ), +#endif END_DATADESC() @@ -140,3 +146,12 @@ void CEnvHudHint::InputHideHudHint( inputdata_t &inputdata ) MessageEnd(); } } + +#ifdef MAPBASE +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CEnvHudHint::InputSetHudHint( inputdata_t &inputdata ) +{ + m_iszMessage = inputdata.value.StringID(); +} +#endif diff --git a/src/game/server/EnvLaser.cpp b/src/game/server/EnvLaser.cpp index 0e603db6..3db94e7c 100644 --- a/src/game/server/EnvLaser.cpp +++ b/src/game/server/EnvLaser.cpp @@ -31,6 +31,10 @@ BEGIN_DATADESC( CEnvLaser ) DEFINE_INPUTFUNC( FIELD_VOID, "TurnOff", InputTurnOff ), DEFINE_INPUTFUNC( FIELD_VOID, "Toggle", InputToggle ), +#ifdef MAPBASE + DEFINE_OUTPUT( m_OnTouchedByEntity, "OnTouchedByEntity" ), +#endif + END_DATADESC() @@ -221,6 +225,12 @@ void CEnvLaser::FireAtPoint( trace_t &tr ) // Apply damage and do sparks every 1/10th of a second. if ( gpGlobals->curtime >= m_flFireTime + 0.1 ) { +#ifdef MAPBASE + if ( tr.fraction != 1.0 && tr.m_pEnt && !tr.m_pEnt->IsWorld() ) + { + m_OnTouchedByEntity.FireOutput( tr.m_pEnt, this ); + } +#endif BeamDamage( &tr ); DoSparks( GetAbsStartPos(), tr.endpos ); } diff --git a/src/game/server/EnvLaser.h b/src/game/server/EnvLaser.h index 79165042..fc740442 100644 --- a/src/game/server/EnvLaser.h +++ b/src/game/server/EnvLaser.h @@ -37,6 +37,9 @@ public: void InputTurnOn( inputdata_t &inputdata ); void InputTurnOff( inputdata_t &inputdata ); void InputToggle( inputdata_t &inputdata ); +#ifdef MAPBASE + void InputSetTarget( inputdata_t &inputdata ) { m_iszLaserTarget = inputdata.value.StringID(); } +#endif DECLARE_DATADESC(); @@ -45,6 +48,10 @@ public: string_t m_iszSpriteName; Vector m_firePosition; +#ifdef MAPBASE + COutputEvent m_OnTouchedByEntity; +#endif + float m_flStartFrame; }; diff --git a/src/game/server/EnvMessage.cpp b/src/game/server/EnvMessage.cpp index a22dfc11..7725b67d 100644 --- a/src/game/server/EnvMessage.cpp +++ b/src/game/server/EnvMessage.cpp @@ -154,6 +154,9 @@ public: DECLARE_DATADESC(); void Spawn( void ); +#ifdef MAPBASE + void PrecacheCreditsThink(); +#endif void InputRollCredits( inputdata_t &inputdata ); void InputRollOutroCredits( inputdata_t &inputdata ); void InputShowLogo( inputdata_t &inputdata ); @@ -168,6 +171,11 @@ private: bool m_bRolledOutroCredits; float m_flLogoLength; + +#ifdef MAPBASE + // Custom credits.txt, defaults to that + string_t m_iszCreditsFile; +#endif }; LINK_ENTITY_TO_CLASS( env_credits, CCredits ); @@ -179,6 +187,12 @@ BEGIN_DATADESC( CCredits ) DEFINE_INPUTFUNC( FIELD_FLOAT, "SetLogoLength", InputSetLogoLength ), DEFINE_OUTPUT( m_OnCreditsDone, "OnCreditsDone"), +#ifdef MAPBASE + DEFINE_KEYFIELD( m_iszCreditsFile, FIELD_STRING, "CreditsFile" ), + + DEFINE_THINKFUNC( PrecacheCreditsThink ), +#endif + DEFINE_FIELD( m_bRolledOutroCredits, FIELD_BOOLEAN ), DEFINE_FIELD( m_flLogoLength, FIELD_FLOAT ) END_DATADESC() @@ -187,8 +201,35 @@ void CCredits::Spawn( void ) { SetSolid( SOLID_NONE ); SetMoveType( MOVETYPE_NONE ); + +#ifdef MAPBASE + // Ensures the player has time to spawn + SetContextThink( &CCredits::PrecacheCreditsThink, gpGlobals->curtime + 0.5f, "PrecacheCreditsContext" ); +#endif } +#ifdef MAPBASE +void CCredits::PrecacheCreditsThink() +{ + CBasePlayer *pPlayer = UTIL_GetLocalPlayer(); + if (!pPlayer) + { + Warning( "%s: No player\n", GetDebugName() ); + return; + } + + CSingleUserRecipientFilter user( pPlayer ); + user.MakeReliable(); + + UserMessageBegin( user, "CreditsMsg" ); + WRITE_BYTE( 4 ); + WRITE_STRING( STRING(m_iszCreditsFile) ); + MessageEnd(); + + SetContextThink( NULL, TICK_NEVER_THINK, "PrecacheCreditsContext" ); +} +#endif + static void CreditsDone_f( void ) { CCredits *pCredits = (CCredits*)gEntList.FindEntityByClassname( NULL, "env_credits" ); @@ -203,6 +244,10 @@ static ConCommand creditsdone("creditsdone", CreditsDone_f ); extern ConVar sv_unlockedchapters; +#ifdef MAPBASE +extern int Mapbase_GetChapterCount(); +#endif + void CCredits::OnRestore() { BaseClass::OnRestore(); @@ -217,6 +262,10 @@ void CCredits::OnRestore() void CCredits::RollOutroCredits() { +#ifdef MAPBASE + // Don't set this if we're using Mapbase chapters or if sv_unlockedchapters is already greater than 15 + if (Mapbase_GetChapterCount() <= 0 && sv_unlockedchapters.GetInt() < 15) +#endif sv_unlockedchapters.SetValue( "15" ); CBasePlayer *pPlayer = UTIL_GetLocalPlayer(); @@ -226,6 +275,9 @@ void CCredits::RollOutroCredits() UserMessageBegin( user, "CreditsMsg" ); WRITE_BYTE( 3 ); +#ifdef MAPBASE + WRITE_STRING( STRING(m_iszCreditsFile) ); +#endif MessageEnd(); } @@ -250,12 +302,18 @@ void CCredits::InputShowLogo( inputdata_t &inputdata ) { UserMessageBegin( user, "LogoTimeMsg" ); WRITE_FLOAT( m_flLogoLength ); +#ifdef MAPBASE + WRITE_STRING( STRING(m_iszCreditsFile) ); +#endif MessageEnd(); } else { UserMessageBegin( user, "CreditsMsg" ); WRITE_BYTE( 1 ); +#ifdef MAPBASE + WRITE_STRING( STRING(m_iszCreditsFile) ); +#endif MessageEnd(); } } @@ -274,5 +332,8 @@ void CCredits::InputRollCredits( inputdata_t &inputdata ) UserMessageBegin( user, "CreditsMsg" ); WRITE_BYTE( 2 ); +#ifdef MAPBASE + WRITE_STRING( STRING(m_iszCreditsFile) ); +#endif MessageEnd(); } diff --git a/src/game/server/RagdollBoogie.cpp b/src/game/server/RagdollBoogie.cpp index 6344b74a..0d54418d 100644 --- a/src/game/server/RagdollBoogie.cpp +++ b/src/game/server/RagdollBoogie.cpp @@ -15,6 +15,10 @@ #include "effect_dispatch_data.h" #include "te_effect_dispatch.h" #include "IEffects.h" +#ifdef MAPBASE +#include "saverestore_utlvector.h" +#include "interval.h" +#endif // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" @@ -37,6 +41,10 @@ BEGIN_DATADESC( CRagdollBoogie ) // Think this should be handled by StartTouch/etc. // DEFINE_FIELD( m_nSuppressionCount, FIELD_INTEGER ), +#ifdef MAPBASE + DEFINE_FIELD( m_vecColor, FIELD_VECTOR ), +#endif + DEFINE_FUNCTION( BoogieThink ), DEFINE_FUNCTION( ZapThink ), @@ -50,7 +58,11 @@ LINK_ENTITY_TO_CLASS( env_ragdoll_boogie, CRagdollBoogie ); // Input : pTarget - //----------------------------------------------------------------------------- CRagdollBoogie *CRagdollBoogie::Create( CBaseEntity *pTarget, float flMagnitude, +#ifdef MAPBASE + float flStartTime, float flLengthTime, int nSpawnFlags, const Vector *vecColor ) +#else float flStartTime, float flLengthTime, int nSpawnFlags ) +#endif { CRagdollProp *pRagdoll = dynamic_cast< CRagdollProp* >( pTarget ); if ( !pRagdoll ) @@ -64,6 +76,10 @@ CRagdollBoogie *CRagdollBoogie::Create( CBaseEntity *pTarget, float flMagnitude, pBoogie->AttachToEntity( pTarget ); pBoogie->SetBoogieTime( flStartTime, flLengthTime ); pBoogie->SetMagnitude( flMagnitude ); +#ifdef MAPBASE + if (vecColor != NULL) + pBoogie->SetColor( *vecColor ); +#endif pBoogie->Spawn(); return pBoogie; } @@ -115,6 +131,13 @@ void CRagdollBoogie::ZapThink() data.m_nEntIndex = GetMoveParent()->entindex(); data.m_flMagnitude = 4; data.m_flScale = HasSpawnFlags(SF_RAGDOLL_BOOGIE_ELECTRICAL_NARROW_BEAM) ? 1.0f : 2.0f; +#ifdef MAPBASE + if (!m_vecColor.IsZero()) + { + data.m_bCustomColors = true; + data.m_CustomColors.m_vecColor1 = m_vecColor; + } +#endif DispatchEffect( "TeslaHitboxes", data ); } @@ -266,3 +289,190 @@ void CRagdollBoogie::BoogieThink( void ) SetNextThink( gpGlobals->curtime + random->RandomFloat( 0.1, 0.2f ) ); } + +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Allows mappers to control ragdoll dancing +//----------------------------------------------------------------------------- +class CPointRagdollBoogie : public CBaseEntity +{ + DECLARE_DATADESC(); + DECLARE_CLASS( CPointRagdollBoogie, CBaseEntity ); + +public: + bool ApplyBoogie(CBaseEntity *pTarget, CBaseEntity *pActivator); + + void InputActivate( inputdata_t &inputdata ); + void InputDeactivate( inputdata_t &inputdata ); + void InputBoogieTarget( inputdata_t &inputdata ); + void InputSetZapColor( inputdata_t &inputdata ); + + bool KeyValue( const char *szKeyName, const char *szValue ); + +private: + float m_flStartTime; + interval_t m_BoogieLength; + float m_flMagnitude; + + Vector m_vecZapColor; + + // This allows us to change or remove active boogies later. + CUtlVector> m_Boogies; +}; + +//----------------------------------------------------------------------------- +// Save/load +//----------------------------------------------------------------------------- +BEGIN_DATADESC( CPointRagdollBoogie ) + + DEFINE_KEYFIELD( m_flStartTime, FIELD_FLOAT, "StartTime" ), + DEFINE_KEYFIELD( m_BoogieLength, FIELD_INTERVAL, "BoogieLength" ), + DEFINE_KEYFIELD( m_flMagnitude, FIELD_FLOAT, "Magnitude" ), + + DEFINE_KEYFIELD( m_vecZapColor, FIELD_VECTOR, "ZapColor" ), + + // Think this should be handled by StartTouch/etc. +// DEFINE_FIELD( m_nSuppressionCount, FIELD_INTEGER ), + + DEFINE_UTLVECTOR( m_Boogies, FIELD_EHANDLE ), + + // Inputs + DEFINE_INPUTFUNC( FIELD_VOID, "Activate", InputActivate ), + DEFINE_INPUTFUNC( FIELD_VOID, "Deactivate", InputDeactivate ), + DEFINE_INPUTFUNC( FIELD_STRING, "BoogieTarget", InputBoogieTarget ), + DEFINE_INPUTFUNC( FIELD_VECTOR, "SetZapColor", InputSetZapColor ), + +END_DATADESC() + +LINK_ENTITY_TO_CLASS( point_ragdollboogie, CPointRagdollBoogie ); + +//----------------------------------------------------------------------------- +// Purpose: +// Input : &inputdata - +//----------------------------------------------------------------------------- +bool CPointRagdollBoogie::ApplyBoogie( CBaseEntity *pTarget, CBaseEntity *pActivator ) +{ + if (dynamic_cast(pTarget)) + { + m_Boogies.AddToTail(CRagdollBoogie::Create(pTarget, m_flMagnitude, gpGlobals->curtime + m_flStartTime, RandomInterval(m_BoogieLength), GetSpawnFlags(), &m_vecZapColor)); + } + else if (pTarget->MyCombatCharacterPointer()) + { + // Basically CBaseCombatCharacter::BecomeRagdollBoogie(), but adjusted to our needs + CTakeDamageInfo info(this, pActivator, 1.0f, DMG_GENERIC); + + CBaseEntity *pRagdoll = CreateServerRagdoll(pTarget->MyCombatCharacterPointer(), 0, info, COLLISION_GROUP_INTERACTIVE_DEBRIS, true); + + pRagdoll->SetCollisionBounds(CollisionProp()->OBBMins(), CollisionProp()->OBBMaxs()); + + m_Boogies.AddToTail(CRagdollBoogie::Create(pRagdoll, m_flMagnitude, gpGlobals->curtime + m_flStartTime, RandomInterval(m_BoogieLength), GetSpawnFlags(), &m_vecZapColor)); + + CTakeDamageInfo ragdollInfo(this, pActivator, 10000.0, DMG_GENERIC | DMG_REMOVENORAGDOLL); + ragdollInfo.SetDamagePosition(WorldSpaceCenter()); + ragdollInfo.SetDamageForce(Vector(0, 0, 1)); + ragdollInfo.SetForceFriendlyFire(true); + pTarget->TakeDamage(ragdollInfo); + } + else + { + return false; + } + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : &inputdata - +//----------------------------------------------------------------------------- +void CPointRagdollBoogie::InputActivate( inputdata_t &inputdata ) +{ + CBaseEntity *pEnt = gEntList.FindEntityByName(NULL, STRING(m_target), this, inputdata.pActivator, inputdata.pCaller); + while (pEnt) + { + ApplyBoogie(pEnt, inputdata.pActivator); + + pEnt = gEntList.FindEntityByName(pEnt, STRING(m_target), this, inputdata.pActivator, inputdata.pCaller); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : &inputdata - +//----------------------------------------------------------------------------- +void CPointRagdollBoogie::InputDeactivate( inputdata_t &inputdata ) +{ + if (m_Boogies.Count() == 0) + return; + + for (int i = 0; i < m_Boogies.Count(); i++) + { + UTIL_Remove(m_Boogies[i]); + } + + m_Boogies.Purge(); + + //m_Boogies.RemoveAll(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : &inputdata - +//----------------------------------------------------------------------------- +void CPointRagdollBoogie::InputBoogieTarget( inputdata_t &inputdata ) +{ + CBaseEntity *pEnt = gEntList.FindEntityByName(NULL, inputdata.value.String(), this, inputdata.pActivator, inputdata.pCaller); + while (pEnt) + { + if (!ApplyBoogie(pEnt, inputdata.pActivator)) + { + Warning("%s was unable to apply ragdoll boogie to %s, classname %s.\n", GetDebugName(), pEnt->GetDebugName(), pEnt->GetClassname()); + } + + pEnt = gEntList.FindEntityByName(pEnt, inputdata.value.String(), this, inputdata.pActivator, inputdata.pCaller); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : &inputdata - +//----------------------------------------------------------------------------- +void CPointRagdollBoogie::InputSetZapColor( inputdata_t &inputdata ) +{ + inputdata.value.Vector3D( m_vecZapColor ); + if (!m_vecZapColor.IsZero()) + { + // Turn into ratios of 255 + m_vecZapColor /= 255.0f; + } + + // Apply to existing boogies + for (int i = 0; i < m_Boogies.Count(); i++) + { + if (m_Boogies[i]) + { + m_Boogies[i]->SetColor( m_vecZapColor ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Handles key values from the BSP before spawn is called. +//----------------------------------------------------------------------------- +bool CPointRagdollBoogie::KeyValue( const char *szKeyName, const char *szValue ) +{ + if ( FStrEq( szKeyName, "ZapColor" ) ) + { + UTIL_StringToVector(m_vecZapColor.Base(), szValue); + if (!m_vecZapColor.IsZero()) + { + // Turn into ratios of 255 + m_vecZapColor /= 255.0f; + } + } + else + return BaseClass::KeyValue( szKeyName, szValue ); + + return true; +} +#endif diff --git a/src/game/server/RagdollBoogie.h b/src/game/server/RagdollBoogie.h index cb52c955..8ca99e81 100644 --- a/src/game/server/RagdollBoogie.h +++ b/src/game/server/RagdollBoogie.h @@ -28,10 +28,18 @@ class CRagdollBoogie : public CBaseEntity DECLARE_CLASS( CRagdollBoogie, CBaseEntity ); public: +#ifdef MAPBASE + static CRagdollBoogie *Create( CBaseEntity *pTarget, float flMagnitude, float flStartTime, float flLengthTime = 0.0f, int nSpawnFlags = 0, const Vector *vecColor = NULL ); +#else static CRagdollBoogie *Create( CBaseEntity *pTarget, float flMagnitude, float flStartTime, float flLengthTime = 0.0f, int nSpawnFlags = 0 ); +#endif static void IncrementSuppressionCount( CBaseEntity *pTarget ); static void DecrementSuppressionCount( CBaseEntity *pTarget ); +#ifdef MAPBASE + void SetColor( const Vector &vecColor ) { m_vecColor = vecColor; } +#endif + void Spawn(); private: @@ -45,6 +53,10 @@ private: float m_flBoogieLength; float m_flMagnitude; int m_nSuppressionCount; + +#ifdef MAPBASE + Vector m_vecColor = Vector(1, 1, 1); +#endif }; #endif // RAGDOLLBOOGIE_H diff --git a/src/game/server/SkyCamera.cpp b/src/game/server/SkyCamera.cpp index ede4ec97..939a30e9 100644 --- a/src/game/server/SkyCamera.cpp +++ b/src/game/server/SkyCamera.cpp @@ -15,13 +15,24 @@ // automatically hooks in the system's callbacks CEntityClassList g_SkyList; template <> CSkyCamera *CEntityClassList::m_pClassList = NULL; +#ifdef MAPBASE +CHandle g_hActiveSkybox = NULL; +#endif //----------------------------------------------------------------------------- // Retrives the current skycamera //----------------------------------------------------------------------------- CSkyCamera* GetCurrentSkyCamera() { +#ifdef MAPBASE + if ( g_hActiveSkybox.Get() == NULL ) + { + g_hActiveSkybox = GetSkyCameraList(); + } + return g_hActiveSkybox.Get(); +#else return g_SkyList.m_pClassList; +#endif } CSkyCamera* GetSkyCameraList() @@ -38,6 +49,12 @@ BEGIN_DATADESC( CSkyCamera ) DEFINE_KEYFIELD( m_skyboxData.scale, FIELD_INTEGER, "scale" ), DEFINE_FIELD( m_skyboxData.origin, FIELD_VECTOR ), DEFINE_FIELD( m_skyboxData.area, FIELD_INTEGER ), +#ifdef MAPBASE + DEFINE_FIELD( m_skyboxData.angles, FIELD_VECTOR ), + DEFINE_FIELD( m_skyboxData.skycamera, FIELD_EHANDLE ), + DEFINE_KEYFIELD( m_skyboxData.skycolor, FIELD_COLOR32, "skycolor" ), + DEFINE_KEYFIELD( m_bUseAnglesForSky, FIELD_BOOLEAN, "use_angles_for_sky" ), +#endif // Quiet down classcheck // DEFINE_FIELD( m_skyboxData, sky3dparams_t ), @@ -56,6 +73,36 @@ BEGIN_DATADESC( CSkyCamera ) DEFINE_KEYFIELD( m_skyboxData.fog.end, FIELD_FLOAT, "fogend" ), DEFINE_KEYFIELD( m_skyboxData.fog.maxdensity, FIELD_FLOAT, "fogmaxdensity" ), DEFINE_KEYFIELD( m_skyboxData.fog.radial, FIELD_BOOLEAN, "fogradial" ), +#ifdef MAPBASE + DEFINE_KEYFIELD( m_skyboxData.fog.farz, FIELD_FLOAT, "farz" ), +#endif + +#ifdef MAPBASE + DEFINE_INPUTFUNC( FIELD_VOID, "ForceUpdate", InputForceUpdate ), + DEFINE_INPUTFUNC( FIELD_VOID, "StartUpdating", InputStartUpdating ), + DEFINE_INPUTFUNC( FIELD_VOID, "StopUpdating", InputStopUpdating ), + + DEFINE_INPUTFUNC( FIELD_VOID, "ActivateSkybox", InputActivateSkybox ), + DEFINE_INPUTFUNC( FIELD_VOID, "DeactivateSkybox", InputDeactivateSkybox ), + + DEFINE_INPUTFUNC( FIELD_FLOAT, "SetFogStartDist", InputSetFogStartDist ), + DEFINE_INPUTFUNC( FIELD_FLOAT, "SetFogEndDist", InputSetFogEndDist ), + DEFINE_INPUTFUNC( FIELD_FLOAT, "SetFogMaxDensity", InputSetFogMaxDensity ), + DEFINE_INPUTFUNC( FIELD_VOID, "TurnOnFog", InputTurnOnFog ), + DEFINE_INPUTFUNC( FIELD_VOID, "TurnOffFog", InputTurnOffFog ), + DEFINE_INPUTFUNC( FIELD_COLOR32, "SetFogColor", InputSetFogColor ), + DEFINE_INPUTFUNC( FIELD_COLOR32, "SetFogColorSecondary", InputSetFogColorSecondary ), + DEFINE_INPUTFUNC( FIELD_EHANDLE, "CopyFogController", InputCopyFogController ), + DEFINE_INPUTFUNC( FIELD_EHANDLE, "CopyFogControllerWithScale", InputCopyFogControllerWithScale ), + + DEFINE_INPUTFUNC( FIELD_INTEGER, "SetFarZ", InputSetFarZ ), + + DEFINE_INPUTFUNC( FIELD_COLOR32, "SetSkyColor", InputSetSkyColor ), + + DEFINE_INPUTFUNC( FIELD_INTEGER, "SetScale", InputSetScale ), + + DEFINE_THINKFUNC( UpdateThink ), +#endif END_DATADESC() @@ -95,6 +142,9 @@ CSkyCamera::CSkyCamera() g_SkyList.Insert( this ); m_skyboxData.fog.maxdensity = 1.0f; m_skyboxData.fog.radial = false; +#ifdef MAPBASE + m_skyboxData.skycolor.Init(0, 0, 0, 0); +#endif } CSkyCamera::~CSkyCamera() @@ -104,7 +154,24 @@ CSkyCamera::~CSkyCamera() void CSkyCamera::Spawn( void ) { +#ifdef MAPBASE + if (HasSpawnFlags(SF_SKY_MASTER)) + g_hActiveSkybox = this; + + if (HasSpawnFlags(SF_SKY_START_UPDATING)) + { + SetCameraEntityMode(); + + SetThink( &CSkyCamera::UpdateThink ); + SetNextThink( gpGlobals->curtime + TICK_INTERVAL ); + } + else + { + SetCameraPositionMode(); + } +#else m_skyboxData.origin = GetLocalOrigin(); +#endif m_skyboxData.area = engine->GetArea( m_skyboxData.origin ); Precache(); @@ -146,4 +213,260 @@ void CSkyCamera::Activate( ) } } #endif + +#ifdef MAPBASE + if (HasSpawnFlags( SF_SKY_MASTER )) + g_hActiveSkybox = this; +#endif } + +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CSkyCamera::AcceptInput( const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t Value, int outputID ) +{ + if (!BaseClass::AcceptInput( szInputName, pActivator, pCaller, Value, outputID )) + return false; + + if (g_hActiveSkybox == this) + { + // Most inputs require an update + DoUpdate( true ); + } + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CSkyCamera::SetCameraEntityMode() +{ + m_skyboxData.skycamera = this; + + // Ensure the viewrender knows whether this should be using angles + if (m_bUseAnglesForSky) + m_skyboxData.angles.SetX( 1 ); + else + m_skyboxData.angles.SetX( 0 ); +} + +void CSkyCamera::SetCameraPositionMode() +{ + // Must be absolute now that the sky_camera can be parented + m_skyboxData.skycamera = NULL; + m_skyboxData.origin = GetAbsOrigin(); + if (m_bUseAnglesForSky) + m_skyboxData.angles = GetAbsAngles(); +} + +//----------------------------------------------------------------------------- +// Purpose: Update sky position mid-game +//----------------------------------------------------------------------------- +bool CSkyCamera::DoUpdate( bool bUpdateData ) +{ + // Now that sky camera updating uses an entity handle directly transmitted to the client, + // this thinking is only used to update area and other parameters + + // Getting into another area is unlikely, but if it's not expensive, I guess it's okay. + int area = engine->GetArea( GetAbsOrigin() ); + if (m_skyboxData.area != area) + { + m_skyboxData.area = area; + bUpdateData = true; + } + + if ( m_bUseAngles ) + { + Vector fogForward; + AngleVectors( GetAbsAngles(), &fogForward ); + fogForward *= -1.0f; + + if ( m_skyboxData.fog.dirPrimary.Get() != fogForward ) + { + m_skyboxData.fog.dirPrimary = fogForward; + bUpdateData = true; + } + } + + if (bUpdateData) + { + // Updates client data, this completely ignores m_pOldSkyCamera + CBasePlayer *pPlayer = NULL; + for (int i = 1; i <= gpGlobals->maxClients; i++) + { + pPlayer = UTIL_PlayerByIndex(i); + if (pPlayer) + pPlayer->m_Local.m_skybox3d.CopyFrom(m_skyboxData); + } + } + + // Needed for entity interpolation + SetSimulationTime( gpGlobals->curtime ); + + return bUpdateData; +} + +void CSkyCamera::UpdateThink() +{ + if (DoUpdate()) + { + SetNextThink( gpGlobals->curtime + TICK_INTERVAL ); + } + else + { + SetNextThink( gpGlobals->curtime + 0.2f ); + } +} + +void CSkyCamera::InputForceUpdate( inputdata_t &inputdata ) +{ + if (m_skyboxData.skycamera == NULL) + { + m_skyboxData.origin = GetAbsOrigin(); + if (m_bUseAnglesForSky) + m_skyboxData.angles = GetAbsAngles(); + } + + DoUpdate( true ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CSkyCamera::InputStartUpdating( inputdata_t &inputdata ) +{ + if (GetCurrentSkyCamera() == this) + { + SetCameraEntityMode(); + DoUpdate( true ); + + SetThink( &CSkyCamera::UpdateThink ); + SetNextThink( gpGlobals->curtime + TICK_INTERVAL ); + } + + // If we become the current sky camera later, remember that we want to update + AddSpawnFlags( SF_SKY_START_UPDATING ); + + // Must update transmit state so we show up on the client + DispatchUpdateTransmitState(); +} + +void CSkyCamera::InputStopUpdating( inputdata_t &inputdata ) +{ + SetThink( NULL ); + SetNextThink( TICK_NEVER_THINK ); + RemoveSpawnFlags( SF_SKY_START_UPDATING ); + DispatchUpdateTransmitState(); + + SetCameraPositionMode(); + DoUpdate( true ); +} + +//----------------------------------------------------------------------------- +// Activate! +//----------------------------------------------------------------------------- +void CSkyCamera::InputActivateSkybox( inputdata_t &inputdata ) +{ + CSkyCamera *pActiveSky = GetCurrentSkyCamera(); + if (pActiveSky && pActiveSky->GetNextThink() != TICK_NEVER_THINK && pActiveSky != this) + { + // Deactivate that skybox + pActiveSky->SetThink( NULL ); + pActiveSky->SetNextThink( TICK_NEVER_THINK ); + pActiveSky->RemoveSpawnFlags( SF_SKY_MASTER ); + } + + g_hActiveSkybox = this; + AddSpawnFlags( SF_SKY_MASTER ); + + if (HasSpawnFlags( SF_SKY_START_UPDATING )) + InputStartUpdating( inputdata ); +} + +//----------------------------------------------------------------------------- +// Deactivate! +//----------------------------------------------------------------------------- +void CSkyCamera::InputDeactivateSkybox( inputdata_t &inputdata ) +{ + if (GetCurrentSkyCamera() == this) + { + g_hActiveSkybox = NULL; + RemoveSpawnFlags( SF_SKY_MASTER ); + + // ClientData doesn't catch this immediately + CBasePlayer *pPlayer = NULL; + for (int i = 1; i <= gpGlobals->maxClients; i++) + { + pPlayer = UTIL_PlayerByIndex( i ); + if (pPlayer) + pPlayer->m_Local.m_skybox3d.area = 255; + } + } + + SetThink( NULL ); + SetNextThink( TICK_NEVER_THINK ); +} + +//------------------------------------------------------------------------------ +// Purpose: Input handlers for setting fog stuff. +//------------------------------------------------------------------------------ +void CSkyCamera::InputSetFogStartDist( inputdata_t &inputdata ) { m_skyboxData.fog.start = inputdata.value.Float(); } +void CSkyCamera::InputSetFogEndDist( inputdata_t &inputdata ) { m_skyboxData.fog.end = inputdata.value.Float(); } +void CSkyCamera::InputSetFogMaxDensity( inputdata_t &inputdata ) { m_skyboxData.fog.maxdensity = inputdata.value.Float(); } +void CSkyCamera::InputTurnOnFog( inputdata_t &inputdata ) { m_skyboxData.fog.enable = true; } +void CSkyCamera::InputTurnOffFog( inputdata_t &inputdata ) { m_skyboxData.fog.enable = false; } +void CSkyCamera::InputSetFogColor( inputdata_t &inputdata ) { m_skyboxData.fog.colorPrimary = inputdata.value.Color32(); } +void CSkyCamera::InputSetFogColorSecondary( inputdata_t &inputdata ) { m_skyboxData.fog.colorSecondary = inputdata.value.Color32(); } + +void CSkyCamera::InputSetFarZ( inputdata_t &inputdata ) { m_skyboxData.fog.farz = inputdata.value.Int(); } + +void CSkyCamera::InputCopyFogController( inputdata_t &inputdata ) +{ + CFogController *pFogController = dynamic_cast(inputdata.value.Entity().Get()); + if (!pFogController) + return; + + m_skyboxData.fog.dirPrimary = pFogController->m_fog.dirPrimary; + m_skyboxData.fog.colorPrimary = pFogController->m_fog.colorPrimary; + m_skyboxData.fog.colorSecondary = pFogController->m_fog.colorSecondary; + //m_skyboxData.fog.colorPrimaryLerpTo = pFogController->m_fog.colorPrimaryLerpTo; + //m_skyboxData.fog.colorSecondaryLerpTo = pFogController->m_fog.colorSecondaryLerpTo; + m_skyboxData.fog.start = pFogController->m_fog.start; + m_skyboxData.fog.end = pFogController->m_fog.end; + m_skyboxData.fog.farz = pFogController->m_fog.farz; + m_skyboxData.fog.maxdensity = pFogController->m_fog.maxdensity; + + //m_skyboxData.fog.startLerpTo = pFogController->m_fog.startLerpTo; + //m_skyboxData.fog.endLerpTo = pFogController->m_fog.endLerpTo; + //m_skyboxData.fog.lerptime = pFogController->m_fog.lerptime; + //m_skyboxData.fog.duration = pFogController->m_fog.duration; + //m_skyboxData.fog.enable = pFogController->m_fog.enable; + m_skyboxData.fog.blend = pFogController->m_fog.blend; +} + +void CSkyCamera::InputCopyFogControllerWithScale( inputdata_t &inputdata ) +{ + CFogController *pFogController = dynamic_cast(inputdata.value.Entity().Get()); + if (!pFogController) + return; + + m_skyboxData.fog.dirPrimary = pFogController->m_fog.dirPrimary; + m_skyboxData.fog.colorPrimary = pFogController->m_fog.colorPrimary; + m_skyboxData.fog.colorSecondary = pFogController->m_fog.colorSecondary; + //m_skyboxData.fog.colorPrimaryLerpTo = pFogController->m_fog.colorPrimaryLerpTo; + //m_skyboxData.fog.colorSecondaryLerpTo = pFogController->m_fog.colorSecondaryLerpTo; + m_skyboxData.fog.start = pFogController->m_fog.start * m_skyboxData.scale; + m_skyboxData.fog.end = pFogController->m_fog.end * m_skyboxData.scale; + m_skyboxData.fog.farz = pFogController->m_fog.farz != -1 ? (pFogController->m_fog.farz * m_skyboxData.scale) : pFogController->m_fog.farz; + m_skyboxData.fog.maxdensity = pFogController->m_fog.maxdensity; + + //m_skyboxData.fog.startLerpTo = pFogController->m_fog.startLerpTo; + //m_skyboxData.fog.endLerpTo = pFogController->m_fog.endLerpTo; + //m_skyboxData.fog.lerptime = pFogController->m_fog.lerptime; + //m_skyboxData.fog.duration = pFogController->m_fog.duration; + //m_skyboxData.fog.enable = pFogController->m_fog.enable; + m_skyboxData.fog.blend = pFogController->m_fog.blend; +} +#endif diff --git a/src/game/server/SkyCamera.h b/src/game/server/SkyCamera.h index 8032d8fd..a1936568 100644 --- a/src/game/server/SkyCamera.h +++ b/src/game/server/SkyCamera.h @@ -14,13 +14,30 @@ class CSkyCamera; +#ifdef MAPBASE +#define SF_SKY_MASTER (1 << 0) +#define SF_SKY_START_UPDATING (1 << 1) + +//============================================================================= +// +// Sky Camera Class +// Now derived directly from CBaseEntity for parenting and angles! (please don't break anything) +// +//============================================================================= +class CSkyCamera : public CBaseEntity +#else //============================================================================= // // Sky Camera Class // class CSkyCamera : public CLogicalEntity +#endif { +#ifdef MAPBASE + DECLARE_CLASS( CSkyCamera, CBaseEntity ); +#else DECLARE_CLASS( CSkyCamera, CLogicalEntity ); +#endif public: @@ -30,9 +47,48 @@ public: virtual void Spawn( void ); virtual void Activate(); +#ifdef MAPBASE + bool AcceptInput( const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t Value, int outputID ); + + int UpdateTransmitState() { return HasSpawnFlags( SF_SKY_START_UPDATING ) ? SetTransmitState( FL_EDICT_ALWAYS ) : BaseClass::UpdateTransmitState(); } + + void SetCameraEntityMode(); + void SetCameraPositionMode(); + + bool DoUpdate( bool bUpdateData = false ); + void UpdateThink(); + + void InputForceUpdate( inputdata_t &inputdata ); + void InputStartUpdating( inputdata_t &inputdata ); + void InputStopUpdating( inputdata_t &inputdata ); + + void InputActivateSkybox( inputdata_t &inputdata ); + void InputDeactivateSkybox( inputdata_t &inputdata ); + + void InputSetFogStartDist( inputdata_t &data ); + void InputSetFogEndDist( inputdata_t &data ); + void InputTurnOnFog( inputdata_t &data ); + void InputTurnOffFog( inputdata_t &data ); + void InputSetFogColor( inputdata_t &data ); + void InputSetFogColorSecondary( inputdata_t &data ); + void InputSetFogMaxDensity( inputdata_t &inputdata ); + void InputCopyFogController( inputdata_t &inputdata ); + void InputCopyFogControllerWithScale( inputdata_t &inputdata ); + + void InputSetFarZ( inputdata_t &data ); + + void InputSetSkyColor( inputdata_t &inputdata ) { m_skyboxData.skycolor = inputdata.value.Color32(); } + + void InputSetScale( inputdata_t &inputdata ) { m_skyboxData.scale = inputdata.value.Int(); } +#endif + public: sky3dparams_t m_skyboxData; bool m_bUseAngles; +#ifdef MAPBASE + // Uses angles for actual skybox + bool m_bUseAnglesForSky; +#endif CSkyCamera *m_pNext; }; diff --git a/src/game/server/TemplateEntities.cpp b/src/game/server/TemplateEntities.cpp index a50693e4..20057d26 100644 --- a/src/game/server/TemplateEntities.cpp +++ b/src/game/server/TemplateEntities.cpp @@ -160,6 +160,70 @@ string_t Templates_FindByTargetName(const char *pszName) return NULL_STRING; } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: A new version of name fixup which targets all instances of a name +// in a keyvalue, including output parameters. +//----------------------------------------------------------------------------- +void Templates_NewNameFixup( CUtlVector< grouptemplate_t > &GroupTemplates, int i, int iCount, CEntityMapData *mapData, CUtlDict< int, int > &KeyInstanceCount, char *keyName, char *value ) +{ + do + { + // Ignore targetnames + if ( !stricmp( keyName, "targetname" ) ) + continue; + + // Add to the count for this + int idx = KeyInstanceCount.Find( keyName ); + if ( idx == KeyInstanceCount.InvalidIndex() ) + { + idx = KeyInstanceCount.Insert( keyName, 0 ); + } + KeyInstanceCount[idx]++; + + // Loop through our group templates + for ( int iTName = 0; iTName < iCount; iTName++ ) + { + char *pName = GroupTemplates[iTName].pszName; + if (strstr( value, pName ) == NULL) + continue; + + if ( template_debug.GetInt() ) + { + Msg("Template Connection Found: Key %s (\"%s\") in entity named \"%s\"(%d) matches entity %d's targetname\n", keyName, value, GroupTemplates[i].pszName, i, iTName ); + } + + char newvalue[MAPKEY_MAXLENGTH]; + char fixedup[MAPKEY_MAXLENGTH]; + Q_strncpy( fixedup, pName, MAPKEY_MAXLENGTH ); + Q_strncat( fixedup, ENTITYIO_FIXUP_STRING, sizeof( fixedup ), COPY_ALL_CHARACTERS ); + + // Get the current key instance. (-1 because it's this one we're changing) + int nKeyInstance = KeyInstanceCount[idx] - 1; + + // Add our IO value to the targetname + V_StrSubst( value, pName, fixedup, newvalue, MAPKEY_MAXLENGTH ); + + if ( template_debug.GetInt() ) + { + Msg(" Fixed up value: Key %s with \"%s\" in entity named \"%s\"(%d) has become \"%s\"\n", keyName, value, GroupTemplates[i].pszName, i, newvalue ); + } + + mapData->SetValue( keyName, newvalue, nKeyInstance ); + Q_strncpy( value, newvalue, MAPKEY_MAXLENGTH ); + + // Remember we changed this targetname + GroupTemplates[iTName].bChangeTargetname = true; + + // Set both entity's flags telling them their template needs fixup when it's spawned + g_Templates[ GroupTemplates[i].iIndex ]->bNeedsEntityIOFixup = true; + g_Templates[ GroupTemplates[iTName].iIndex ]->bNeedsEntityIOFixup = true; + } + } + while ( mapData->GetNextKey(keyName, value) ); +} +#endif + //----------------------------------------------------------------------------- // Purpose: A CPointTemplate has asked us to reconnect all the entity I/O links // inside it's templates. Go through the keys and add look for values @@ -209,6 +273,14 @@ void Templates_ReconnectIOForGroup( CPointTemplate *pGroup ) if ( !mapData->GetFirstKey(keyName, value) ) continue; +#ifdef MAPBASE + if ( pGroup->NameFixupExpanded() ) + { + Templates_NewNameFixup( GroupTemplates, i, iCount, mapData, KeyInstanceCount, keyName, value ); + continue; + } +#endif + do { // Ignore targetnames @@ -333,7 +405,9 @@ void Templates_StartUniqueInstance( void ) //----------------------------------------------------------------------------- char *Templates_GetEntityIOFixedMapData( int iIndex ) { +#ifndef MAPBASE // This code also runs when the point_template's script scope is active Assert( Templates_IndexRequiresEntityIOFixup( iIndex ) ); +#endif // First time through? if ( !g_Templates[iIndex]->pszFixedMapData ) diff --git a/src/game/server/ai_activity.cpp b/src/game/server/ai_activity.cpp index 3e620df6..cea14a1f 100644 --- a/src/game/server/ai_activity.cpp +++ b/src/game/server/ai_activity.cpp @@ -76,6 +76,23 @@ int CAI_BaseNPC::GetActivityID(const char* actName) return m_pActivitySR->GetStringID(actName); } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: Gets an activity ID or registers a new private one if it doesn't exist +//----------------------------------------------------------------------------- +int CAI_BaseNPC::GetOrRegisterActivity( const char *actName ) +{ + int actID = GetActivityID( actName ); + if (actID == ACT_INVALID) + { + actID = ActivityList_RegisterPrivateActivity( actName ); + AddActivityToSR( actName, actID ); + } + + return actID; +} +#endif + #define ADD_ACTIVITY_TO_SR(activityname) AddActivityToSR(#activityname,activityname) //----------------------------------------------------------------------------- @@ -2404,4 +2421,698 @@ void CAI_BaseNPC::InitDefaultActivitySR(void) ADD_ACTIVITY_TO_SR( ACT_MP_CYOA_PDA_INTRO ); ADD_ACTIVITY_TO_SR( ACT_MP_CYOA_PDA_IDLE ); ADD_ACTIVITY_TO_SR( ACT_MP_CYOA_PDA_OUTRO ); + +#if AR2_ACTIVITY_FIX == 1 + ADD_ACTIVITY_TO_SR( ACT_IDLE_AR2 ); + ADD_ACTIVITY_TO_SR( ACT_IDLE_ANGRY_AR2 ); + + ADD_ACTIVITY_TO_SR( ACT_IDLE_AR2_RELAXED ); + ADD_ACTIVITY_TO_SR( ACT_IDLE_AR2_STIMULATED ); + + ADD_ACTIVITY_TO_SR( ACT_WALK_AR2_RELAXED ); + ADD_ACTIVITY_TO_SR( ACT_RUN_AR2_RELAXED ); + ADD_ACTIVITY_TO_SR( ACT_WALK_AR2_STIMULATED ); + ADD_ACTIVITY_TO_SR( ACT_RUN_AR2_STIMULATED ); + + ADD_ACTIVITY_TO_SR( ACT_IDLE_AIM_AR2_STIMULATED ); + ADD_ACTIVITY_TO_SR( ACT_WALK_AIM_AR2_STIMULATED ); + ADD_ACTIVITY_TO_SR( ACT_RUN_AIM_AR2_STIMULATED ); + + ADD_ACTIVITY_TO_SR( ACT_WALK_AR2 ); + ADD_ACTIVITY_TO_SR( ACT_WALK_AIM_AR2 ); + ADD_ACTIVITY_TO_SR( ACT_RUN_AR2 ); + ADD_ACTIVITY_TO_SR( ACT_RUN_AIM_AR2 ); + + ADD_ACTIVITY_TO_SR( ACT_RELOAD_AR2 ); + ADD_ACTIVITY_TO_SR( ACT_RELOAD_AR2_LOW ); + + ADD_ACTIVITY_TO_SR( ACT_GESTURE_RELOAD_AR2 ); + + ADD_ACTIVITY_TO_SR( ACT_COVER_AR2_LOW ); +#endif + +#if SHARED_COMBINE_ACTIVITIES + ADD_ACTIVITY_TO_SR( ACT_COMBINE_THROW_GRENADE ); + ADD_ACTIVITY_TO_SR( ACT_COMBINE_AR2_ALTFIRE ); + + ADD_ACTIVITY_TO_SR( ACT_GESTURE_COMBINE_THROW_GRENADE ); + ADD_ACTIVITY_TO_SR( ACT_GESTURE_COMBINE_AR2_ALTFIRE ); + ADD_ACTIVITY_TO_SR( ACT_GESTURE_SPECIAL_ATTACK1 ); + ADD_ACTIVITY_TO_SR( ACT_GESTURE_SPECIAL_ATTACK2 ); + + ADD_ACTIVITY_TO_SR( ACT_GESTURE_SIGNAL_ADVANCE ); + ADD_ACTIVITY_TO_SR( ACT_GESTURE_SIGNAL_FORWARD ); + ADD_ACTIVITY_TO_SR( ACT_GESTURE_SIGNAL_GROUP ); + ADD_ACTIVITY_TO_SR( ACT_GESTURE_SIGNAL_HALT ); + ADD_ACTIVITY_TO_SR( ACT_GESTURE_SIGNAL_LEFT ); + ADD_ACTIVITY_TO_SR( ACT_GESTURE_SIGNAL_RIGHT ); + ADD_ACTIVITY_TO_SR( ACT_GESTURE_SIGNAL_TAKECOVER ); +#endif + +#if EXPANDED_HL2_WEAPON_ACTIVITIES + ADD_ACTIVITY_TO_SR( ACT_IDLE_REVOLVER ); + ADD_ACTIVITY_TO_SR( ACT_IDLE_ANGRY_REVOLVER ); + ADD_ACTIVITY_TO_SR( ACT_WALK_REVOLVER ); + ADD_ACTIVITY_TO_SR( ACT_RUN_REVOLVER ); + ADD_ACTIVITY_TO_SR( ACT_WALK_AIM_REVOLVER ); + ADD_ACTIVITY_TO_SR( ACT_RUN_AIM_REVOLVER ); + ADD_ACTIVITY_TO_SR( ACT_RANGE_ATTACK_REVOLVER ); + ADD_ACTIVITY_TO_SR( ACT_RELOAD_REVOLVER ); + ADD_ACTIVITY_TO_SR( ACT_RANGE_ATTACK_REVOLVER_LOW ); + ADD_ACTIVITY_TO_SR( ACT_RELOAD_REVOLVER_LOW ); + ADD_ACTIVITY_TO_SR( ACT_COVER_REVOLVER_LOW ); + ADD_ACTIVITY_TO_SR( ACT_RANGE_AIM_REVOLVER_LOW ); + ADD_ACTIVITY_TO_SR( ACT_GESTURE_RANGE_ATTACK_REVOLVER ); + ADD_ACTIVITY_TO_SR( ACT_GESTURE_RELOAD_REVOLVER ); + + ADD_ACTIVITY_TO_SR( ACT_IDLE_CROSSBOW ); + ADD_ACTIVITY_TO_SR( ACT_IDLE_ANGRY_CROSSBOW ); + ADD_ACTIVITY_TO_SR( ACT_WALK_CROSSBOW ); + ADD_ACTIVITY_TO_SR( ACT_RUN_CROSSBOW ); + ADD_ACTIVITY_TO_SR( ACT_WALK_AIM_CROSSBOW ); + ADD_ACTIVITY_TO_SR( ACT_RUN_AIM_CROSSBOW ); + ADD_ACTIVITY_TO_SR( ACT_RANGE_ATTACK_CROSSBOW ); + ADD_ACTIVITY_TO_SR( ACT_RELOAD_CROSSBOW ); + ADD_ACTIVITY_TO_SR( ACT_RANGE_ATTACK_CROSSBOW_LOW ); + ADD_ACTIVITY_TO_SR( ACT_RELOAD_CROSSBOW_LOW ); + ADD_ACTIVITY_TO_SR( ACT_COVER_CROSSBOW_LOW ); + ADD_ACTIVITY_TO_SR( ACT_RANGE_AIM_CROSSBOW_LOW ); + ADD_ACTIVITY_TO_SR( ACT_GESTURE_RANGE_ATTACK_CROSSBOW ); + ADD_ACTIVITY_TO_SR( ACT_GESTURE_RELOAD_CROSSBOW ); + + ADD_ACTIVITY_TO_SR( ACT_IDLE_CROSSBOW_RELAXED ); + ADD_ACTIVITY_TO_SR( ACT_IDLE_CROSSBOW_STIMULATED ); + ADD_ACTIVITY_TO_SR( ACT_WALK_CROSSBOW_RELAXED ); + ADD_ACTIVITY_TO_SR( ACT_RUN_CROSSBOW_RELAXED ); + ADD_ACTIVITY_TO_SR( ACT_WALK_CROSSBOW_STIMULATED ); + ADD_ACTIVITY_TO_SR( ACT_RUN_CROSSBOW_STIMULATED ); + + ADD_ACTIVITY_TO_SR( ACT_IDLE_AIM_CROSSBOW_STIMULATED ); + ADD_ACTIVITY_TO_SR( ACT_WALK_AIM_CROSSBOW_STIMULATED ); + ADD_ACTIVITY_TO_SR( ACT_RUN_AIM_CROSSBOW_STIMULATED ); + + ADD_ACTIVITY_TO_SR( ACT_IDLE_PISTOL_RELAXED ); + ADD_ACTIVITY_TO_SR( ACT_IDLE_PISTOL_STIMULATED ); + ADD_ACTIVITY_TO_SR( ACT_WALK_PISTOL_RELAXED ); + ADD_ACTIVITY_TO_SR( ACT_WALK_PISTOL_STIMULATED ); + ADD_ACTIVITY_TO_SR( ACT_RUN_PISTOL_RELAXED ); + ADD_ACTIVITY_TO_SR( ACT_RUN_PISTOL_STIMULATED ); + + ADD_ACTIVITY_TO_SR( ACT_IDLE_AIM_PISTOL_STIMULATED ); + ADD_ACTIVITY_TO_SR( ACT_WALK_AIM_PISTOL_STIMULATED ); + ADD_ACTIVITY_TO_SR( ACT_RUN_AIM_PISTOL_STIMULATED ); + + ADD_ACTIVITY_TO_SR( ACT_WALK_CROUCH_PISTOL ); + ADD_ACTIVITY_TO_SR( ACT_WALK_CROUCH_AIM_PISTOL ); + ADD_ACTIVITY_TO_SR( ACT_RUN_CROUCH_PISTOL ); + ADD_ACTIVITY_TO_SR( ACT_RUN_CROUCH_AIM_PISTOL ); + + ADD_ACTIVITY_TO_SR( ACT_IDLE_SHOTGUN ); + ADD_ACTIVITY_TO_SR( ACT_WALK_SHOTGUN ); + ADD_ACTIVITY_TO_SR( ACT_RUN_SHOTGUN ); + + ADD_ACTIVITY_TO_SR( ACT_COVER_SHOTGUN_LOW ); + ADD_ACTIVITY_TO_SR( ACT_RANGE_AIM_SHOTGUN_LOW ); + + ADD_ACTIVITY_TO_SR( ACT_WALK_SHOTGUN_RELAXED ); + ADD_ACTIVITY_TO_SR( ACT_WALK_SHOTGUN_STIMULATED ); + ADD_ACTIVITY_TO_SR( ACT_RUN_SHOTGUN_RELAXED ); + ADD_ACTIVITY_TO_SR( ACT_RUN_SHOTGUN_STIMULATED ); + + ADD_ACTIVITY_TO_SR( ACT_IDLE_AIM_SHOTGUN_STIMULATED ); + ADD_ACTIVITY_TO_SR( ACT_WALK_AIM_SHOTGUN_STIMULATED ); + ADD_ACTIVITY_TO_SR( ACT_RUN_AIM_SHOTGUN_STIMULATED ); + + ADD_ACTIVITY_TO_SR( ACT_RANGE_AIM_RPG_LOW ); + ADD_ACTIVITY_TO_SR( ACT_RANGE_ATTACK_RPG_LOW ); + ADD_ACTIVITY_TO_SR( ACT_GESTURE_RANGE_ATTACK_RPG ); + + ADD_ACTIVITY_TO_SR( ACT_RANGE_ATTACK_ANNABELLE ); + ADD_ACTIVITY_TO_SR( ACT_RANGE_ATTACK_ANNABELLE_LOW ); + ADD_ACTIVITY_TO_SR( ACT_GESTURE_RANGE_ATTACK_ANNABELLE ); + ADD_ACTIVITY_TO_SR( ACT_RELOAD_ANNABELLE ); + ADD_ACTIVITY_TO_SR( ACT_RELOAD_ANNABELLE_LOW ); + ADD_ACTIVITY_TO_SR( ACT_GESTURE_RELOAD_ANNABELLE ); + + ADD_ACTIVITY_TO_SR( ACT_WALK_MELEE ); + ADD_ACTIVITY_TO_SR( ACT_RUN_MELEE ); + + ADD_ACTIVITY_TO_SR( ACT_RUN_PACKAGE ); + ADD_ACTIVITY_TO_SR( ACT_RUN_SUITCASE ); + + ADD_ACTIVITY_TO_SR( ACT_ARM_RIFLE ); + ADD_ACTIVITY_TO_SR( ACT_ARM_SHOTGUN ); + ADD_ACTIVITY_TO_SR( ACT_ARM_RPG ); + ADD_ACTIVITY_TO_SR( ACT_ARM_MELEE ); + ADD_ACTIVITY_TO_SR( ACT_DISARM_RIFLE ); + ADD_ACTIVITY_TO_SR( ACT_DISARM_SHOTGUN ); + ADD_ACTIVITY_TO_SR( ACT_DISARM_RPG ); + ADD_ACTIVITY_TO_SR( ACT_DISARM_MELEE ); +#endif + +#if EXPANDED_HL2_UNUSED_WEAPON_ACTIVITIES + ADD_ACTIVITY_TO_SR( ACT_IDLE_AR1 ); + ADD_ACTIVITY_TO_SR( ACT_IDLE_ANGRY_AR1 ); + ADD_ACTIVITY_TO_SR( ACT_WALK_AR1 ); + ADD_ACTIVITY_TO_SR( ACT_RUN_AR1 ); + ADD_ACTIVITY_TO_SR( ACT_WALK_AIM_AR1 ); + ADD_ACTIVITY_TO_SR( ACT_RUN_AIM_AR1 ); + //ADD_ACTIVITY_TO_SR( ACT_RANGE_ATTACK_AR1 ); + ADD_ACTIVITY_TO_SR( ACT_RELOAD_AR1 ); + ADD_ACTIVITY_TO_SR( ACT_RANGE_ATTACK_AR1_LOW ); + ADD_ACTIVITY_TO_SR( ACT_RELOAD_AR1_LOW ); + ADD_ACTIVITY_TO_SR( ACT_COVER_AR1_LOW ); + ADD_ACTIVITY_TO_SR( ACT_RANGE_AIM_AR1_LOW ); + //ADD_ACTIVITY_TO_SR( ACT_GESTURE_RANGE_ATTACK_AR1 ); + ADD_ACTIVITY_TO_SR( ACT_GESTURE_RELOAD_AR1 ); + + ADD_ACTIVITY_TO_SR( ACT_IDLE_AR1_RELAXED ); + ADD_ACTIVITY_TO_SR( ACT_IDLE_AR1_STIMULATED ); + ADD_ACTIVITY_TO_SR( ACT_WALK_AR1_RELAXED ); + ADD_ACTIVITY_TO_SR( ACT_RUN_AR1_RELAXED ); + ADD_ACTIVITY_TO_SR( ACT_WALK_AR1_STIMULATED ); + ADD_ACTIVITY_TO_SR( ACT_RUN_AR1_STIMULATED ); + + ADD_ACTIVITY_TO_SR( ACT_IDLE_AIM_AR1_STIMULATED ); + ADD_ACTIVITY_TO_SR( ACT_WALK_AIM_AR1_STIMULATED ); + ADD_ACTIVITY_TO_SR( ACT_RUN_AIM_AR1_STIMULATED ); + + ADD_ACTIVITY_TO_SR( ACT_IDLE_AR3 ); + ADD_ACTIVITY_TO_SR( ACT_IDLE_ANGRY_AR3 ); + ADD_ACTIVITY_TO_SR( ACT_WALK_AR3 ); + ADD_ACTIVITY_TO_SR( ACT_RUN_AR3 ); + ADD_ACTIVITY_TO_SR( ACT_WALK_AIM_AR3 ); + ADD_ACTIVITY_TO_SR( ACT_RUN_AIM_AR3 ); + ADD_ACTIVITY_TO_SR( ACT_RANGE_ATTACK_AR3 ); + ADD_ACTIVITY_TO_SR( ACT_RELOAD_AR3 ); + ADD_ACTIVITY_TO_SR( ACT_RANGE_ATTACK_AR3_LOW ); + ADD_ACTIVITY_TO_SR( ACT_RELOAD_AR3_LOW ); + ADD_ACTIVITY_TO_SR( ACT_COVER_AR3_LOW ); + ADD_ACTIVITY_TO_SR( ACT_RANGE_AIM_AR3_LOW ); + ADD_ACTIVITY_TO_SR( ACT_GESTURE_RANGE_ATTACK_AR3 ); + ADD_ACTIVITY_TO_SR( ACT_GESTURE_RELOAD_AR3 ); + + ADD_ACTIVITY_TO_SR( ACT_IDLE_AR3_RELAXED ); + ADD_ACTIVITY_TO_SR( ACT_IDLE_AR3_STIMULATED ); + ADD_ACTIVITY_TO_SR( ACT_WALK_AR3_RELAXED ); + ADD_ACTIVITY_TO_SR( ACT_RUN_AR3_RELAXED ); + ADD_ACTIVITY_TO_SR( ACT_WALK_AR3_STIMULATED ); + ADD_ACTIVITY_TO_SR( ACT_RUN_AR3_STIMULATED ); + + ADD_ACTIVITY_TO_SR( ACT_IDLE_AIM_AR3_STIMULATED ); + ADD_ACTIVITY_TO_SR( ACT_WALK_AIM_AR3_STIMULATED ); + ADD_ACTIVITY_TO_SR( ACT_RUN_AIM_AR3_STIMULATED ); + + ADD_ACTIVITY_TO_SR( ACT_IDLE_SMG2 ); + ADD_ACTIVITY_TO_SR( ACT_IDLE_ANGRY_SMG2 ); + ADD_ACTIVITY_TO_SR( ACT_WALK_SMG2 ); + ADD_ACTIVITY_TO_SR( ACT_RUN_SMG2 ); + ADD_ACTIVITY_TO_SR( ACT_WALK_AIM_SMG2 ); + ADD_ACTIVITY_TO_SR( ACT_RUN_AIM_SMG2 ); + //ADD_ACTIVITY_TO_SR( ACT_RANGE_ATTACK_SMG2 ); + ADD_ACTIVITY_TO_SR( ACT_RELOAD_SMG2 ); + ADD_ACTIVITY_TO_SR( ACT_RANGE_ATTACK_SMG2_LOW ); + ADD_ACTIVITY_TO_SR( ACT_RELOAD_SMG2_LOW ); + ADD_ACTIVITY_TO_SR( ACT_COVER_SMG2_LOW ); + ADD_ACTIVITY_TO_SR( ACT_RANGE_AIM_SMG2_LOW ); + //ADD_ACTIVITY_TO_SR( ACT_GESTURE_RANGE_ATTACK_SMG2 ); + ADD_ACTIVITY_TO_SR( ACT_GESTURE_RELOAD_SMG2 ); + + ADD_ACTIVITY_TO_SR( ACT_IDLE_SMG2_RELAXED ); + ADD_ACTIVITY_TO_SR( ACT_IDLE_SMG2_STIMULATED ); + ADD_ACTIVITY_TO_SR( ACT_WALK_SMG2_RELAXED ); + ADD_ACTIVITY_TO_SR( ACT_RUN_SMG2_RELAXED ); + ADD_ACTIVITY_TO_SR( ACT_WALK_SMG2_STIMULATED ); + ADD_ACTIVITY_TO_SR( ACT_RUN_SMG2_STIMULATED ); + + ADD_ACTIVITY_TO_SR( ACT_IDLE_AIM_SMG2_STIMULATED ); + ADD_ACTIVITY_TO_SR( ACT_WALK_AIM_SMG2_STIMULATED ); + ADD_ACTIVITY_TO_SR( ACT_RUN_AIM_SMG2_STIMULATED ); + + ADD_ACTIVITY_TO_SR( ACT_IDLE_SMG3 ); + ADD_ACTIVITY_TO_SR( ACT_IDLE_ANGRY_SMG3 ); + ADD_ACTIVITY_TO_SR( ACT_WALK_SMG3 ); + ADD_ACTIVITY_TO_SR( ACT_RUN_SMG3 ); + ADD_ACTIVITY_TO_SR( ACT_WALK_AIM_SMG3 ); + ADD_ACTIVITY_TO_SR( ACT_RUN_AIM_SMG3 ); + //ADD_ACTIVITY_TO_SR( ACT_RANGE_ATTACK_SMG3 ); + ADD_ACTIVITY_TO_SR( ACT_RELOAD_SMG3 ); + ADD_ACTIVITY_TO_SR( ACT_RANGE_ATTACK_SMG3_LOW ); + ADD_ACTIVITY_TO_SR( ACT_RELOAD_SMG3_LOW ); + ADD_ACTIVITY_TO_SR( ACT_COVER_SMG3_LOW ); + ADD_ACTIVITY_TO_SR( ACT_RANGE_AIM_SMG3_LOW ); + //ADD_ACTIVITY_TO_SR( ACT_GESTURE_RANGE_ATTACK_SMG3 ); + ADD_ACTIVITY_TO_SR( ACT_GESTURE_RELOAD_SMG3 ); + + ADD_ACTIVITY_TO_SR( ACT_IDLE_SMG3_RELAXED ); + ADD_ACTIVITY_TO_SR( ACT_IDLE_SMG3_STIMULATED ); + ADD_ACTIVITY_TO_SR( ACT_WALK_SMG3_RELAXED ); + ADD_ACTIVITY_TO_SR( ACT_RUN_SMG3_RELAXED ); + ADD_ACTIVITY_TO_SR( ACT_WALK_SMG3_STIMULATED ); + ADD_ACTIVITY_TO_SR( ACT_RUN_SMG3_STIMULATED ); + + ADD_ACTIVITY_TO_SR( ACT_IDLE_AIM_SMG3_STIMULATED ); + ADD_ACTIVITY_TO_SR( ACT_WALK_AIM_SMG3_STIMULATED ); + ADD_ACTIVITY_TO_SR( ACT_RUN_AIM_SMG3_STIMULATED ); + + ADD_ACTIVITY_TO_SR( ACT_IDLE_HMG1 ); + ADD_ACTIVITY_TO_SR( ACT_IDLE_ANGRY_HMG1 ); + ADD_ACTIVITY_TO_SR( ACT_WALK_HMG1 ); + ADD_ACTIVITY_TO_SR( ACT_RUN_HMG1 ); + ADD_ACTIVITY_TO_SR( ACT_WALK_AIM_HMG1 ); + ADD_ACTIVITY_TO_SR( ACT_RUN_AIM_HMG1 ); + //ADD_ACTIVITY_TO_SR( ACT_RANGE_ATTACK_HMG1 ); + ADD_ACTIVITY_TO_SR( ACT_RELOAD_HMG1 ); + ADD_ACTIVITY_TO_SR( ACT_RANGE_ATTACK_HMG1_LOW ); + ADD_ACTIVITY_TO_SR( ACT_RELOAD_HMG1_LOW ); + ADD_ACTIVITY_TO_SR( ACT_COVER_HMG1_LOW ); + ADD_ACTIVITY_TO_SR( ACT_RANGE_AIM_HMG1_LOW ); + //ADD_ACTIVITY_TO_SR( ACT_GESTURE_RANGE_ATTACK_HMG1 ); + ADD_ACTIVITY_TO_SR( ACT_GESTURE_RELOAD_HMG1 ); + + ADD_ACTIVITY_TO_SR( ACT_IDLE_HMG1_RELAXED ); + ADD_ACTIVITY_TO_SR( ACT_IDLE_HMG1_STIMULATED ); + ADD_ACTIVITY_TO_SR( ACT_WALK_HMG1_RELAXED ); + ADD_ACTIVITY_TO_SR( ACT_RUN_HMG1_RELAXED ); + ADD_ACTIVITY_TO_SR( ACT_WALK_HMG1_STIMULATED ); + ADD_ACTIVITY_TO_SR( ACT_RUN_HMG1_STIMULATED ); + + ADD_ACTIVITY_TO_SR( ACT_IDLE_AIM_HMG1_STIMULATED ); + ADD_ACTIVITY_TO_SR( ACT_WALK_AIM_HMG1_STIMULATED ); + ADD_ACTIVITY_TO_SR( ACT_RUN_AIM_HMG1_STIMULATED ); + + ADD_ACTIVITY_TO_SR( ACT_IDLE_SNIPER_RIFLE ); + ADD_ACTIVITY_TO_SR( ACT_IDLE_ANGRY_SNIPER_RIFLE ); + ADD_ACTIVITY_TO_SR( ACT_WALK_SNIPER_RIFLE ); + ADD_ACTIVITY_TO_SR( ACT_RUN_SNIPER_RIFLE ); + ADD_ACTIVITY_TO_SR( ACT_WALK_AIM_SNIPER_RIFLE ); + ADD_ACTIVITY_TO_SR( ACT_RUN_AIM_SNIPER_RIFLE ); + //ADD_ACTIVITY_TO_SR( ACT_RANGE_ATTACK_SNIPER_RIFLE ); + ADD_ACTIVITY_TO_SR( ACT_RELOAD_SNIPER_RIFLE ); + ADD_ACTIVITY_TO_SR( ACT_RANGE_ATTACK_SNIPER_RIFLE_LOW ); + ADD_ACTIVITY_TO_SR( ACT_RELOAD_SNIPER_RIFLE_LOW ); + ADD_ACTIVITY_TO_SR( ACT_COVER_SNIPER_RIFLE_LOW ); + ADD_ACTIVITY_TO_SR( ACT_RANGE_AIM_SNIPER_RIFLE_LOW ); + //ADD_ACTIVITY_TO_SR( ACT_GESTURE_RANGE_ATTACK_SNIPER_RIFLE ); + ADD_ACTIVITY_TO_SR( ACT_GESTURE_RELOAD_SNIPER_RIFLE ); + + ADD_ACTIVITY_TO_SR( ACT_IDLE_SNIPER_RIFLE_RELAXED ); + ADD_ACTIVITY_TO_SR( ACT_IDLE_SNIPER_RIFLE_STIMULATED ); + ADD_ACTIVITY_TO_SR( ACT_WALK_SNIPER_RIFLE_RELAXED ); + ADD_ACTIVITY_TO_SR( ACT_RUN_SNIPER_RIFLE_RELAXED ); + ADD_ACTIVITY_TO_SR( ACT_WALK_SNIPER_RIFLE_STIMULATED ); + ADD_ACTIVITY_TO_SR( ACT_RUN_SNIPER_RIFLE_STIMULATED ); + + ADD_ACTIVITY_TO_SR( ACT_IDLE_AIM_SNIPER_RIFLE_STIMULATED ); + ADD_ACTIVITY_TO_SR( ACT_WALK_AIM_SNIPER_RIFLE_STIMULATED ); + ADD_ACTIVITY_TO_SR( ACT_RUN_AIM_SNIPER_RIFLE_STIMULATED ); + + ADD_ACTIVITY_TO_SR( ACT_IDLE_DUAL_PISTOLS ); + ADD_ACTIVITY_TO_SR( ACT_IDLE_ANGRY_DUAL_PISTOLS ); + ADD_ACTIVITY_TO_SR( ACT_WALK_DUAL_PISTOLS ); + ADD_ACTIVITY_TO_SR( ACT_RUN_DUAL_PISTOLS ); + ADD_ACTIVITY_TO_SR( ACT_WALK_AIM_DUAL_PISTOLS ); + ADD_ACTIVITY_TO_SR( ACT_RUN_AIM_DUAL_PISTOLS ); + ADD_ACTIVITY_TO_SR( ACT_RANGE_ATTACK_DUAL_PISTOLS ); + ADD_ACTIVITY_TO_SR( ACT_RELOAD_DUAL_PISTOLS ); + ADD_ACTIVITY_TO_SR( ACT_RANGE_ATTACK_DUAL_PISTOLS_LOW ); + ADD_ACTIVITY_TO_SR( ACT_RELOAD_DUAL_PISTOLS_LOW ); + ADD_ACTIVITY_TO_SR( ACT_COVER_DUAL_PISTOLS_LOW ); + ADD_ACTIVITY_TO_SR( ACT_RANGE_AIM_DUAL_PISTOLS_LOW ); + ADD_ACTIVITY_TO_SR( ACT_GESTURE_RANGE_ATTACK_DUAL_PISTOLS ); + ADD_ACTIVITY_TO_SR( ACT_GESTURE_RELOAD_DUAL_PISTOLS ); + + ADD_ACTIVITY_TO_SR( ACT_IDLE_DUAL_PISTOLS_RELAXED ); + ADD_ACTIVITY_TO_SR( ACT_IDLE_DUAL_PISTOLS_STIMULATED ); + ADD_ACTIVITY_TO_SR( ACT_WALK_DUAL_PISTOLS_RELAXED ); + ADD_ACTIVITY_TO_SR( ACT_RUN_DUAL_PISTOLS_RELAXED ); + ADD_ACTIVITY_TO_SR( ACT_WALK_DUAL_PISTOLS_STIMULATED ); + ADD_ACTIVITY_TO_SR( ACT_RUN_DUAL_PISTOLS_STIMULATED ); + + ADD_ACTIVITY_TO_SR( ACT_IDLE_AIM_DUAL_PISTOLS_STIMULATED ); + ADD_ACTIVITY_TO_SR( ACT_WALK_AIM_DUAL_PISTOLS_STIMULATED ); + ADD_ACTIVITY_TO_SR( ACT_RUN_AIM_DUAL_PISTOLS_STIMULATED ); +#endif + +#if EXPANDED_NAVIGATION_ACTIVITIES + ADD_ACTIVITY_TO_SR( ACT_CLIMB_ALL ); + ADD_ACTIVITY_TO_SR( ACT_CLIMB_IDLE ); + + ADD_ACTIVITY_TO_SR( ACT_CLIMB_MOUNT_TOP ); + ADD_ACTIVITY_TO_SR( ACT_CLIMB_MOUNT_BOTTOM ); + ADD_ACTIVITY_TO_SR( ACT_CLIMB_DISMOUNT_BOTTOM ); +#endif + +#if EXPANDED_HL2_COVER_ACTIVITIES + ADD_ACTIVITY_TO_SR( ACT_RANGE_ATTACK1_MED ); + ADD_ACTIVITY_TO_SR( ACT_RANGE_ATTACK2_MED ); + ADD_ACTIVITY_TO_SR( ACT_RANGE_AIM_MED ); + + ADD_ACTIVITY_TO_SR( ACT_RANGE_ATTACK_AR2_MED ); + ADD_ACTIVITY_TO_SR( ACT_RANGE_ATTACK_SMG1_MED ); + ADD_ACTIVITY_TO_SR( ACT_RANGE_ATTACK_SHOTGUN_MED ); + ADD_ACTIVITY_TO_SR( ACT_RANGE_ATTACK_PISTOL_MED ); + ADD_ACTIVITY_TO_SR( ACT_RANGE_ATTACK_RPG_MED ); + ADD_ACTIVITY_TO_SR( ACT_RANGE_ATTACK_REVOLVER_MED ); + ADD_ACTIVITY_TO_SR( ACT_RANGE_ATTACK_CROSSBOW_MED ); + + ADD_ACTIVITY_TO_SR( ACT_RANGE_AIM_AR2_MED ); + ADD_ACTIVITY_TO_SR( ACT_RANGE_AIM_SMG1_MED ); + ADD_ACTIVITY_TO_SR( ACT_RANGE_AIM_SHOTGUN_MED ); + ADD_ACTIVITY_TO_SR( ACT_RANGE_AIM_PISTOL_MED ); + ADD_ACTIVITY_TO_SR( ACT_RANGE_AIM_RPG_MED ); + ADD_ACTIVITY_TO_SR( ACT_RANGE_AIM_REVOLVER_MED ); + ADD_ACTIVITY_TO_SR( ACT_RANGE_AIM_CROSSBOW_MED ); + +#if EXPANDED_HL2_UNUSED_WEAPON_ACTIVITIES + ADD_ACTIVITY_TO_SR( ACT_RANGE_AIM_AR1_MED ); + ADD_ACTIVITY_TO_SR( ACT_RANGE_ATTACK_AR1_MED ); + ADD_ACTIVITY_TO_SR( ACT_RANGE_AIM_AR3_MED ); + ADD_ACTIVITY_TO_SR( ACT_RANGE_ATTACK_AR3_MED ); + ADD_ACTIVITY_TO_SR( ACT_RANGE_AIM_SMG2_MED ); + ADD_ACTIVITY_TO_SR( ACT_RANGE_ATTACK_SMG2_MED ); + ADD_ACTIVITY_TO_SR( ACT_RANGE_AIM_SMG3_MED ); + ADD_ACTIVITY_TO_SR( ACT_RANGE_ATTACK_SMG3_MED ); + ADD_ACTIVITY_TO_SR( ACT_RANGE_AIM_HMG1_MED ); + ADD_ACTIVITY_TO_SR( ACT_RANGE_ATTACK_HMG1_MED ); + ADD_ACTIVITY_TO_SR( ACT_RANGE_AIM_SNIPER_RIFLE_MED ); + ADD_ACTIVITY_TO_SR( ACT_RANGE_ATTACK_SNIPER_RIFLE_MED ); + ADD_ACTIVITY_TO_SR( ACT_RANGE_AIM_DUAL_PISTOLS_MED ); + ADD_ACTIVITY_TO_SR( ACT_RANGE_ATTACK_DUAL_PISTOLS_MED ); +#endif + + ADD_ACTIVITY_TO_SR( ACT_COVER_WALL_R ); + ADD_ACTIVITY_TO_SR( ACT_COVER_WALL_L ); + ADD_ACTIVITY_TO_SR( ACT_COVER_WALL_LOW_R ); + ADD_ACTIVITY_TO_SR( ACT_COVER_WALL_LOW_L ); + + ADD_ACTIVITY_TO_SR( ACT_COVER_WALL_R_RIFLE ); + ADD_ACTIVITY_TO_SR( ACT_COVER_WALL_L_RIFLE ); + ADD_ACTIVITY_TO_SR( ACT_COVER_WALL_LOW_R_RIFLE ); + ADD_ACTIVITY_TO_SR( ACT_COVER_WALL_LOW_L_RIFLE ); + + ADD_ACTIVITY_TO_SR( ACT_COVER_WALL_R_PISTOL ); + ADD_ACTIVITY_TO_SR( ACT_COVER_WALL_L_PISTOL ); + ADD_ACTIVITY_TO_SR( ACT_COVER_WALL_LOW_R_PISTOL ); + ADD_ACTIVITY_TO_SR( ACT_COVER_WALL_LOW_L_PISTOL ); +#endif + +#if EXPANDED_HL2DM_ACTIVITIES + ADD_ACTIVITY_TO_SR( ACT_HL2MP_WALK ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_WALK_PISTOL ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_WALK_SHOTGUN ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_WALK_SMG1 ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_WALK_AR2 ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_WALK_PHYSGUN ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_WALK_GRENADE ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_WALK_RPG ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_WALK_CROSSBOW ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_WALK_MELEE ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_WALK_SLAM ); + + ADD_ACTIVITY_TO_SR( ACT_HL2MP_GESTURE_RANGE_ATTACK2 ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_GESTURE_RANGE_ATTACK2_PISTOL ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_GESTURE_RANGE_ATTACK2_SHOTGUN ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_GESTURE_RANGE_ATTACK2_SMG1 ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_GESTURE_RANGE_ATTACK2_AR2 ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_GESTURE_RANGE_ATTACK2_PHYSGUN ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_GESTURE_RANGE_ATTACK2_GRENADE ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_GESTURE_RANGE_ATTACK2_RPG ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_GESTURE_RANGE_ATTACK2_CROSSBOW ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_GESTURE_RANGE_ATTACK2_MELEE ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_GESTURE_RANGE_ATTACK2_SLAM ); + + ADD_ACTIVITY_TO_SR( ACT_HL2MP_IDLE_REVOLVER ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_RUN_REVOLVER ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_WALK_REVOLVER ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_IDLE_CROUCH_REVOLVER ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_WALK_CROUCH_REVOLVER ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_GESTURE_RANGE_ATTACK_REVOLVER ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_GESTURE_RANGE_ATTACK2_REVOLVER ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_GESTURE_RELOAD_REVOLVER ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_JUMP_REVOLVER ); + +#if EXPANDED_HL2_UNUSED_WEAPON_ACTIVITIES + ADD_ACTIVITY_TO_SR( ACT_HL2MP_IDLE_AR1 ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_RUN_AR1 ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_WALK_AR1 ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_IDLE_CROUCH_AR1 ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_WALK_CROUCH_AR1 ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_GESTURE_RANGE_ATTACK_AR1 ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_GESTURE_RANGE_ATTACK2_AR1 ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_GESTURE_RELOAD_AR1 ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_JUMP_AR1 ); + + ADD_ACTIVITY_TO_SR( ACT_HL2MP_IDLE_AR3 ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_RUN_AR3 ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_WALK_AR3 ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_IDLE_CROUCH_AR3 ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_WALK_CROUCH_AR3 ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_GESTURE_RANGE_ATTACK_AR3 ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_GESTURE_RANGE_ATTACK2_AR3 ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_GESTURE_RELOAD_AR3 ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_JUMP_AR3 ); + + ADD_ACTIVITY_TO_SR( ACT_HL2MP_IDLE_SMG2 ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_RUN_SMG2 ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_WALK_SMG2 ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_IDLE_CROUCH_SMG2 ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_WALK_CROUCH_SMG2 ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_GESTURE_RANGE_ATTACK_SMG2 ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_GESTURE_RANGE_ATTACK2_SMG2 ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_GESTURE_RELOAD_SMG2 ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_JUMP_SMG2 ); + + ADD_ACTIVITY_TO_SR( ACT_HL2MP_IDLE_SMG3 ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_RUN_SMG3 ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_WALK_SMG3 ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_IDLE_CROUCH_SMG3 ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_WALK_CROUCH_SMG3 ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_GESTURE_RANGE_ATTACK_SMG3 ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_GESTURE_RANGE_ATTACK2_SMG3 ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_GESTURE_RELOAD_SMG3 ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_JUMP_SMG3 ); + + ADD_ACTIVITY_TO_SR( ACT_HL2MP_IDLE_HMG1 ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_RUN_HMG1 ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_WALK_HMG1 ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_IDLE_CROUCH_HMG1 ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_WALK_CROUCH_HMG1 ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_GESTURE_RANGE_ATTACK_HMG1 ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_GESTURE_RANGE_ATTACK2_HMG1 ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_GESTURE_RELOAD_HMG1 ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_JUMP_HMG1 ); + + ADD_ACTIVITY_TO_SR( ACT_HL2MP_IDLE_SNIPER_RIFLE ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_RUN_SNIPER_RIFLE ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_WALK_SNIPER_RIFLE ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_IDLE_CROUCH_SNIPER_RIFLE ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_WALK_CROUCH_SNIPER_RIFLE ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_GESTURE_RANGE_ATTACK_SNIPER_RIFLE ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_GESTURE_RANGE_ATTACK2_SNIPER_RIFLE ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_GESTURE_RELOAD_SNIPER_RIFLE ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_JUMP_SNIPER_RIFLE ); + + ADD_ACTIVITY_TO_SR( ACT_HL2MP_IDLE_DUAL_PISTOLS ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_RUN_DUAL_PISTOLS ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_WALK_DUAL_PISTOLS ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_IDLE_CROUCH_DUAL_PISTOLS ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_WALK_CROUCH_DUAL_PISTOLS ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_GESTURE_RANGE_ATTACK_DUAL_PISTOLS ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_GESTURE_RANGE_ATTACK2_DUAL_PISTOLS ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_GESTURE_RELOAD_DUAL_PISTOLS ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_JUMP_DUAL_PISTOLS ); +#endif + + ADD_ACTIVITY_TO_SR( ACT_HL2MP_IDLE_USE ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_RUN_USE ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_WALK_USE ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_IDLE_CROUCH_USE ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_WALK_CROUCH_USE ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_JUMP_USE ); + + ADD_ACTIVITY_TO_SR( ACT_HL2MP_IDLE_USE_HEAVY ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_RUN_USE_HEAVY ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_WALK_USE_HEAVY ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_IDLE_CROUCH_USE_HEAVY ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_WALK_CROUCH_USE_HEAVY ); + ADD_ACTIVITY_TO_SR( ACT_HL2MP_JUMP_USE_HEAVY ); +#endif } + +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: This is a multi-purpose table which links NPC activities to their gesture variants. +//----------------------------------------------------------------------------- +CAI_BaseNPC::actlink_t CAI_BaseNPC::gm_ActivityGestureLinks[] = +{ + { ACT_RANGE_ATTACK1, ACT_GESTURE_RANGE_ATTACK1 }, + { ACT_RANGE_ATTACK2, ACT_GESTURE_RANGE_ATTACK2 }, + { ACT_MELEE_ATTACK1, ACT_GESTURE_MELEE_ATTACK1 }, + { ACT_MELEE_ATTACK2, ACT_GESTURE_MELEE_ATTACK2 }, + { ACT_RELOAD, ACT_GESTURE_RELOAD }, + + { ACT_RANGE_ATTACK1_LOW, ACT_GESTURE_RANGE_ATTACK1 }, // NOTE: ACT_GESTURE_RANGE_ATTACK1_LOW exists, but isn't used + { ACT_RANGE_ATTACK2_LOW, ACT_GESTURE_RANGE_ATTACK2 }, // NOTE: ACT_GESTURE_RANGE_ATTACK2_LOW exists, but isn't used + { ACT_RELOAD_LOW, ACT_GESTURE_RELOAD }, + + { ACT_MELEE_ATTACK_SWING, ACT_GESTURE_MELEE_ATTACK_SWING }, + + // ----------------------------------------------------------- + + { ACT_RANGE_ATTACK_AR2, ACT_GESTURE_RANGE_ATTACK_AR2 }, + { ACT_RANGE_ATTACK_AR2_LOW, ACT_GESTURE_RANGE_ATTACK_AR2 }, + { ACT_RANGE_ATTACK_SMG1, ACT_GESTURE_RANGE_ATTACK_SMG1 }, + { ACT_RANGE_ATTACK_SMG1_LOW, ACT_GESTURE_RANGE_ATTACK_SMG1 }, + { ACT_RANGE_ATTACK_SHOTGUN, ACT_GESTURE_RANGE_ATTACK_SHOTGUN }, + { ACT_RANGE_ATTACK_SHOTGUN_LOW, ACT_GESTURE_RANGE_ATTACK_SHOTGUN }, + { ACT_RANGE_ATTACK_PISTOL, ACT_GESTURE_RANGE_ATTACK_PISTOL }, + { ACT_RANGE_ATTACK_PISTOL_LOW, ACT_GESTURE_RANGE_ATTACK_PISTOL }, + + // ----------------------------------------------------------- + + { ACT_SMALL_FLINCH, ACT_GESTURE_SMALL_FLINCH }, + { ACT_BIG_FLINCH, ACT_GESTURE_BIG_FLINCH }, + { ACT_FLINCH_HEAD, ACT_GESTURE_FLINCH_HEAD }, + { ACT_FLINCH_CHEST, ACT_GESTURE_FLINCH_CHEST }, + { ACT_FLINCH_STOMACH, ACT_GESTURE_FLINCH_STOMACH }, + { ACT_FLINCH_LEFTARM, ACT_GESTURE_FLINCH_LEFTARM }, + { ACT_FLINCH_RIGHTARM, ACT_GESTURE_FLINCH_RIGHTARM }, + { ACT_FLINCH_LEFTLEG, ACT_GESTURE_FLINCH_LEFTLEG }, + { ACT_FLINCH_RIGHTLEG, ACT_GESTURE_FLINCH_RIGHTLEG }, + + // ----------------------------------------------------------- + +#if AR2_ACTIVITY_FIX == 1 + { ACT_RELOAD_AR2, ACT_GESTURE_RELOAD_AR2 }, + { ACT_RELOAD_AR2_LOW, ACT_GESTURE_RELOAD_AR2 }, +#endif + { ACT_RELOAD_SMG1, ACT_GESTURE_RELOAD_SMG1 }, + { ACT_RELOAD_SMG1_LOW, ACT_GESTURE_RELOAD_SMG1 }, + { ACT_RELOAD_SHOTGUN, ACT_GESTURE_RELOAD_SHOTGUN }, + { ACT_RELOAD_SHOTGUN_LOW, ACT_GESTURE_RELOAD_SHOTGUN }, + { ACT_RELOAD_PISTOL, ACT_GESTURE_RELOAD_PISTOL }, + { ACT_RELOAD_PISTOL_LOW, ACT_GESTURE_RELOAD_PISTOL }, + +#if SHARED_COMBINE_ACTIVITIES + { ACT_SPECIAL_ATTACK1, ACT_GESTURE_SPECIAL_ATTACK1 }, + { ACT_SPECIAL_ATTACK2, ACT_GESTURE_SPECIAL_ATTACK2 }, + { ACT_COMBINE_THROW_GRENADE, ACT_GESTURE_COMBINE_THROW_GRENADE }, + { ACT_COMBINE_AR2_ALTFIRE, ACT_GESTURE_COMBINE_AR2_ALTFIRE }, + + { ACT_SIGNAL_ADVANCE, ACT_GESTURE_SIGNAL_ADVANCE }, + { ACT_SIGNAL_FORWARD, ACT_GESTURE_SIGNAL_FORWARD }, + { ACT_SIGNAL_GROUP, ACT_GESTURE_SIGNAL_GROUP }, + { ACT_SIGNAL_HALT, ACT_GESTURE_SIGNAL_HALT }, + { ACT_SIGNAL_LEFT, ACT_GESTURE_SIGNAL_LEFT }, + { ACT_SIGNAL_RIGHT, ACT_GESTURE_SIGNAL_RIGHT }, + { ACT_SIGNAL_TAKECOVER, ACT_GESTURE_SIGNAL_TAKECOVER }, +#endif + +#if EXPANDED_HL2_WEAPON_ACTIVITIES + { ACT_RANGE_ATTACK_REVOLVER, ACT_GESTURE_RANGE_ATTACK_REVOLVER }, + { ACT_RANGE_ATTACK_REVOLVER_LOW, ACT_GESTURE_RANGE_ATTACK_REVOLVER }, + { ACT_RANGE_ATTACK_CROSSBOW, ACT_GESTURE_RANGE_ATTACK_CROSSBOW }, + { ACT_RANGE_ATTACK_CROSSBOW_LOW, ACT_GESTURE_RANGE_ATTACK_CROSSBOW }, + { ACT_RANGE_ATTACK_RPG, ACT_GESTURE_RANGE_ATTACK_RPG }, + { ACT_RANGE_ATTACK_RPG_LOW, ACT_GESTURE_RANGE_ATTACK_RPG }, + { ACT_RANGE_ATTACK_ANNABELLE, ACT_GESTURE_RANGE_ATTACK_ANNABELLE }, + { ACT_RANGE_ATTACK_ANNABELLE_LOW, ACT_GESTURE_RANGE_ATTACK_ANNABELLE }, + + { ACT_RELOAD_REVOLVER, ACT_GESTURE_RELOAD_REVOLVER }, + { ACT_RELOAD_REVOLVER_LOW, ACT_GESTURE_RELOAD_REVOLVER }, + { ACT_RELOAD_CROSSBOW, ACT_GESTURE_RELOAD_CROSSBOW }, + { ACT_RELOAD_CROSSBOW_LOW, ACT_GESTURE_RELOAD_CROSSBOW }, + { ACT_RELOAD_ANNABELLE, ACT_GESTURE_RELOAD_ANNABELLE }, + { ACT_RELOAD_ANNABELLE_LOW, ACT_GESTURE_RELOAD_ANNABELLE }, +#endif + +#if EXPANDED_HL2_UNUSED_WEAPON_ACTIVITIES + { ACT_RANGE_ATTACK_AR1, ACT_GESTURE_RANGE_ATTACK_AR1 }, + { ACT_RANGE_ATTACK_AR1_LOW, ACT_GESTURE_RANGE_ATTACK_AR1 }, + { ACT_RANGE_ATTACK_AR3, ACT_GESTURE_RANGE_ATTACK_AR3 }, + { ACT_RANGE_ATTACK_AR3_LOW, ACT_GESTURE_RANGE_ATTACK_AR3 }, + { ACT_RANGE_ATTACK_AR2_GRENADE, ACT_GESTURE_RANGE_ATTACK_AR2_GRENADE }, + { ACT_RANGE_ATTACK_HMG1, ACT_GESTURE_RANGE_ATTACK_HMG1 }, + { ACT_RANGE_ATTACK_HMG1_LOW, ACT_GESTURE_RANGE_ATTACK_HMG1 }, + { ACT_RANGE_ATTACK_ML, ACT_GESTURE_RANGE_ATTACK_ML }, + { ACT_RANGE_ATTACK_SMG2, ACT_GESTURE_RANGE_ATTACK_SMG2 }, + { ACT_RANGE_ATTACK_SMG2_LOW, ACT_GESTURE_RANGE_ATTACK_SMG2 }, + { ACT_RANGE_ATTACK_SMG3, ACT_GESTURE_RANGE_ATTACK_SMG3 }, + { ACT_RANGE_ATTACK_SMG3_LOW, ACT_GESTURE_RANGE_ATTACK_SMG3 }, + { ACT_RANGE_ATTACK_SLAM, ACT_GESTURE_RANGE_ATTACK_SLAM }, + { ACT_RANGE_ATTACK_TRIPWIRE, ACT_GESTURE_RANGE_ATTACK_TRIPWIRE }, + { ACT_RANGE_ATTACK_THROW, ACT_GESTURE_RANGE_ATTACK_THROW }, + { ACT_RANGE_ATTACK_SNIPER_RIFLE, ACT_GESTURE_RANGE_ATTACK_SNIPER_RIFLE }, + { ACT_RANGE_ATTACK_SNIPER_RIFLE_LOW, ACT_GESTURE_RANGE_ATTACK_SNIPER_RIFLE }, + + { ACT_RELOAD_AR1, ACT_GESTURE_RELOAD_AR1 }, + { ACT_RELOAD_AR1_LOW, ACT_GESTURE_RELOAD_AR1 }, + { ACT_RELOAD_AR3, ACT_GESTURE_RELOAD_AR3 }, + { ACT_RELOAD_AR3_LOW, ACT_GESTURE_RELOAD_AR3 }, + { ACT_RELOAD_SMG2, ACT_GESTURE_RELOAD_SMG2 }, + { ACT_RELOAD_SMG2_LOW, ACT_GESTURE_RELOAD_SMG2 }, + { ACT_RELOAD_SMG3, ACT_GESTURE_RELOAD_SMG3 }, + { ACT_RELOAD_SMG3_LOW, ACT_GESTURE_RELOAD_SMG3 }, + { ACT_RELOAD_HMG1, ACT_GESTURE_RELOAD_HMG1 }, + { ACT_RELOAD_HMG1_LOW, ACT_GESTURE_RELOAD_HMG1 }, + { ACT_RELOAD_SNIPER_RIFLE, ACT_GESTURE_RELOAD_SNIPER_RIFLE }, + { ACT_RELOAD_SNIPER_RIFLE_LOW, ACT_GESTURE_RELOAD_SNIPER_RIFLE }, +#endif + +#if EXPANDED_HL2_COVER_ACTIVITIES + { ACT_RANGE_ATTACK1_MED, ACT_GESTURE_RANGE_ATTACK1 }, + { ACT_RANGE_ATTACK2_MED, ACT_GESTURE_RANGE_ATTACK2 }, + + { ACT_RANGE_ATTACK_AR2_MED, ACT_GESTURE_RANGE_ATTACK_AR2 }, + { ACT_RANGE_ATTACK_SMG1_MED, ACT_GESTURE_RANGE_ATTACK_SMG1 }, + { ACT_RANGE_ATTACK_SHOTGUN_MED, ACT_GESTURE_RANGE_ATTACK_SHOTGUN }, + { ACT_RANGE_ATTACK_PISTOL_MED, ACT_GESTURE_RANGE_ATTACK_PISTOL }, +#if EXPANDED_HL2_WEAPON_ACTIVITIES + { ACT_RANGE_ATTACK_RPG_MED, ACT_GESTURE_RANGE_ATTACK_RPG }, + { ACT_RANGE_ATTACK_REVOLVER_MED, ACT_GESTURE_RANGE_ATTACK_REVOLVER }, + { ACT_RANGE_ATTACK_CROSSBOW_MED, ACT_GESTURE_RANGE_ATTACK_CROSSBOW }, +#endif +#if EXPANDED_HL2_UNUSED_WEAPON_ACTIVITIES + { ACT_RANGE_ATTACK_AR1_MED, ACT_GESTURE_RANGE_ATTACK_AR1 }, + { ACT_RANGE_ATTACK_AR3_MED, ACT_GESTURE_RANGE_ATTACK_AR3 }, + { ACT_RANGE_ATTACK_SMG2_MED, ACT_GESTURE_RANGE_ATTACK_SMG2 }, + { ACT_RANGE_ATTACK_SMG3_MED, ACT_GESTURE_RANGE_ATTACK_SMG3 }, + { ACT_RANGE_ATTACK_HMG1_MED, ACT_GESTURE_RANGE_ATTACK_HMG1 }, + { ACT_RANGE_ATTACK_SNIPER_RIFLE_MED, ACT_GESTURE_RANGE_ATTACK_SNIPER_RIFLE }, +#endif +#endif +}; + +Activity CAI_BaseNPC::GetGestureVersionOfActivity( Activity inActivity ) +{ + actlink_t *pTable = gm_ActivityGestureLinks; + int actCount = ARRAYSIZE( gm_ActivityGestureLinks ); + + for ( int i = 0; i < actCount; i++, pTable++ ) + { + if ( inActivity == pTable->sequence ) + { + return pTable->gesture; + } + } + + return ACT_INVALID; +} + +Activity CAI_BaseNPC::GetSequenceVersionOfGesture( Activity inActivity ) +{ + actlink_t *pTable = gm_ActivityGestureLinks; + int actCount = ARRAYSIZE( gm_ActivityGestureLinks ); + + for (int i = 0; i < actCount; i++, pTable++) + { + if (inActivity == pTable->gesture) + { + return pTable->sequence; + } + } + + return ACT_INVALID; +} +#endif diff --git a/src/game/server/ai_baseactor.cpp b/src/game/server/ai_baseactor.cpp index e9814967..df0a5aaa 100644 --- a/src/game/server/ai_baseactor.cpp +++ b/src/game/server/ai_baseactor.cpp @@ -98,6 +98,15 @@ BEGIN_DATADESC( CAI_BaseActor ) END_DATADESC() +#ifdef MAPBASE_VSCRIPT +BEGIN_ENT_SCRIPTDESC( CAI_BaseActor, CAI_BaseNPC, "The base class for NPCs which act in complex choreo scenes." ) + + DEFINE_SCRIPTFUNC_NAMED( ScriptAddLookTarget, "AddLookTarget", "Add a potential look target for this actor with the specified importance, duration, and ramp." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptAddLookTargetPos, "AddLookTargetPos", "Add a potential look target position for this actor with the specified importance, duration, and ramp." ) + +END_SCRIPTDESC(); +#endif + BEGIN_SIMPLE_DATADESC( CAI_InterestTarget_t ) DEFINE_FIELD( m_eType, FIELD_INTEGER ), @@ -230,7 +239,11 @@ void CAI_BaseActor::SetModel( const char *szModelName ) // Purpose: //----------------------------------------------------------------------------- +#ifdef MAPBASE +bool CAI_BaseActor::StartSceneEvent( CSceneEventInfo *info, CChoreoScene *scene, CChoreoEvent *event, CChoreoActor *actor, CBaseEntity *pTarget, CSceneEntity *pSceneEnt ) +#else bool CAI_BaseActor::StartSceneEvent( CSceneEventInfo *info, CChoreoScene *scene, CChoreoEvent *event, CChoreoActor *actor, CBaseEntity *pTarget ) +#endif { Assert( info ); Assert( info->m_pScene ); @@ -314,6 +327,109 @@ bool CAI_BaseActor::StartSceneEvent( CSceneEventInfo *info, CChoreoScene *scene, { info->m_nType = SCENE_AI_DISABLEAI; } +#ifdef MAPBASE + else if (stricmp(event->GetParameters(), "AI_ADDCONTEXT") == 0) + { + // Adds a response context to the caller in place of the target field. + // This is supposed to be used with the talker system. + if (event->GetParameters2()) + { + info->m_nType = SCENE_AI_ADDCONTEXT; + AddContext(event->GetParameters2()); + return true; + } + } + else if (stricmp(event->GetParameters(), "AI_INPUT") == 0) + { + // Fires an input on an entity in place of the target field. + // This is supposed to be used with the talker system. + if (event->GetParameters2()) + { + info->m_nType = SCENE_AI_INPUT; + + const char *raw = event->GetParameters2(); + char sTarget[128]; + char sInput[128]; + char sParameter[128]; + char *colon1 = Q_strstr( raw, ":" ); + if (!colon1) + { + Warning("%s (%s) AI_INPUT missing colon separator!\n", GetClassname(), GetDebugName()); + return false; + } + + int len = colon1 - raw; + Q_strncpy( sTarget, raw, MIN( len + 1, sizeof(sTarget) ) ); + sTarget[MIN(len, sizeof(sTarget) - 1)] = 0; + + bool bParameter = true; + char *colon2 = Q_strstr(colon1 + 1, ":"); + if (!colon2) + { + DevMsg("Assuming no parameter\n"); + colon2 = colon1 + 1; + bParameter = false; + } + + if (bParameter) + { + len = MIN(colon2 - (colon1 + 1), sizeof(sInput) - 1); + Q_strncpy(sInput, colon1 + 1, MIN(len + 1, sizeof(sInput))); + sInput[MIN(len, sizeof(sInput) - 1)] = 0; + + Q_strncpy(sParameter, colon2 + 1, sizeof(sInput)); + } + else + { + len = colon2 - raw; + Q_strncpy(sInput, colon2, sizeof(sInput)); + } + + CBaseEntity *pEnt = gEntList.FindEntityByName(NULL, sTarget, this); + if (!pEnt) + { + DevMsg("%s not found with normal search, slamming to scene ent\n", sTarget); + pEnt = UTIL_FindNamedSceneEntity(sTarget, this, pSceneEnt); + if (!pEnt) + { + DevWarning("%s slammed to self!\n", sTarget); + pEnt = this; + } + } + + if (pEnt && sInput) + { + variant_t variant; + if (bParameter && sParameter) + { + const char *strParam = sParameter; + if (strParam[0] == '!') + { + CBaseEntity *pParamEnt = UTIL_FindNamedSceneEntity(strParam, this, pSceneEnt); + if (pParamEnt && pParamEnt->GetEntityName() != NULL_STRING && !gEntList.FindEntityProcedural(strParam)) + { + // We make sure it's a scene entity that can't be found with entlist procedural so we can translate !target# without messing with !activators, etc. + //const char *newname = pParamEnt->GetEntityName().ToCStr(); + strParam = pParamEnt->GetEntityName().ToCStr(); + } + } + + if (strParam) + { + variant.SetString(MAKE_STRING(strParam)); + } + } + + pEnt->AcceptInput(sInput, this, this, variant, 0); + return true; + } + else + { + Warning("%s (%s) AI_INPUT cannot find entity %s!\n", GetClassname(), GetDebugName(), sTarget); + } + } + } +#endif else { return BaseClass::StartSceneEvent( info, scene, event, actor, pTarget ); @@ -508,6 +624,11 @@ bool CAI_BaseActor::ProcessSceneEvent( CSceneEventInfo *info, CChoreoScene *scen Vector vecAimTargetLoc = info->m_hTarget->EyePosition(); Vector vecAimDir = vecAimTargetLoc - EyePosition(); +#ifdef MAPBASE + // Mind the ramp + vecAimDir *= event->GetIntensity(scene->GetTime()); +#endif + VectorNormalize( vecAimDir ); SetAim( vecAimDir); } @@ -548,6 +669,16 @@ bool CAI_BaseActor::ProcessSceneEvent( CSceneEventInfo *info, CChoreoScene *scen EnterSceneSequence( scene, event ); } return true; +#ifdef MAPBASE + case SCENE_AI_ADDCONTEXT: + { + } + return true; + case SCENE_AI_INPUT: + { + } + return true; +#endif default: return false; } @@ -704,7 +835,11 @@ void CAI_BaseActor::UpdateLatchedValues( ) // set head latch m_fLatchedPositions |= HUMANOID_LATCHED_HEAD; +#ifdef MAPBASE // From Alien Swarm SDK + if ( CanSkipAnimation() || !GetAttachment( "eyes", m_latchedEyeOrigin, &m_latchedHeadDirection )) +#else if (!HasCondition( COND_IN_PVS ) || !GetAttachment( "eyes", m_latchedEyeOrigin, &m_latchedHeadDirection )) +#endif { m_latchedEyeOrigin = BaseClass::EyePosition( ); AngleVectors( GetLocalAngles(), &m_latchedHeadDirection ); @@ -1007,6 +1142,24 @@ void CAI_BaseActor::UpdateHeadControl( const Vector &vHeadTarget, float flHeadIn ConcatTransforms( worldToForward, targetXform, headXform ); MatrixAngles( headXform, vTargetAngles ); +#ifdef MAPBASE + // This is here to cover an edge case where pose parameters set to NaN invalidate the model. + if (!vTargetAngles.IsValid()) + { + Warning( "================================================================================\n" + "!!!!! %s tried to set a NaN head angle (can happen when look targets have >1 importance) !!!!!\n" + "================================================================================\n", GetDebugName() ); + m_goalHeadCorrection.Init(); + Set( m_FlexweightHeadRightLeft, 0.0f ); + Set( m_FlexweightHeadUpDown, 0.0f ); + Set( m_FlexweightHeadTilt, 0.0f ); + Set( m_ParameterHeadYaw, 0.0f ); + Set( m_ParameterHeadPitch, 0.0f ); + Set( m_ParameterHeadRoll, 0.0f ); + return; + } +#endif + // partially debounce head goal float s0 = 1.0 - flHeadInfluence + GetHeadDebounce() * flHeadInfluence; float s1 = (1.0 - s0); @@ -1495,7 +1648,11 @@ void CAI_BaseActor::MaintainLookTargets( float flInterval ) } // don't bother with any of the rest if the player can't see you +#ifdef MAPBASE // From Alien Swarm SDK + if ( CanSkipAnimation() ) +#else if (!HasCondition( COND_IN_PVS )) +#endif { return; } @@ -1802,7 +1959,7 @@ void CAI_BaseActor::OnStateChange( NPC_STATE OldState, NPC_STATE NewState ) { PlayExpressionForState( NewState ); -#ifdef HL2_EPISODIC +#if defined(HL2_EPISODIC) || defined(MAPBASE) // If we've just switched states, ensure we stop any scenes that asked to be stopped if ( OldState == NPC_STATE_IDLE ) { @@ -1902,7 +2059,11 @@ bool CAI_BaseActor::UseSemaphore( void ) CAI_Expresser *CAI_BaseActor::CreateExpresser() { +#ifdef NEW_RESPONSE_SYSTEM + m_pExpresser = new CAI_ExpresserWithFollowup(this); +#else m_pExpresser = new CAI_Expresser(this); +#endif return m_pExpresser; } diff --git a/src/game/server/ai_baseactor.h b/src/game/server/ai_baseactor.h index 49caea53..f0d9f8ae 100644 --- a/src/game/server/ai_baseactor.h +++ b/src/game/server/ai_baseactor.h @@ -101,7 +101,11 @@ public: virtual void SetModel( const char *szModelName ); +#ifdef MAPBASE + virtual bool StartSceneEvent( CSceneEventInfo *info, CChoreoScene *scene, CChoreoEvent *event, CChoreoActor *actor, CBaseEntity *pTarget, CSceneEntity *pSceneEnt = NULL ); +#else virtual bool StartSceneEvent( CSceneEventInfo *info, CChoreoScene *scene, CChoreoEvent *event, CChoreoActor *actor, CBaseEntity *pTarget ); +#endif virtual bool ProcessSceneEvent( CSceneEventInfo *info, CChoreoScene *scene, CChoreoEvent *event ); virtual bool ClearSceneEvent( CSceneEventInfo *info, bool fastKill, bool canceled ); virtual bool CheckSceneEventCompletion( CSceneEventInfo *info, float currenttime, CChoreoScene *scene, CChoreoEvent *event ); @@ -166,6 +170,15 @@ public: void ClearExpression(); const char * GetExpression(); +#ifdef MAPBASE_VSCRIPT + //--------------------------------- + + void ScriptAddLookTarget( HSCRIPT pTarget, float flImportance, float flDuration, float flRamp = 0.0 ) { AddLookTarget(ToEnt(pTarget), flImportance, flDuration, flRamp); } + void ScriptAddLookTargetPos( const Vector &vecPosition, float flImportance, float flDuration, float flRamp = 0.0 ) { AddLookTarget(vecPosition, flImportance, flDuration, flRamp); } + + //--------------------------------- +#endif + enum { SCENE_AI_BLINK = 1, @@ -177,10 +190,19 @@ public: SCENE_AI_RANDOMHEADFLEX, SCENE_AI_IGNORECOLLISION, SCENE_AI_DISABLEAI +#ifdef MAPBASE + , + SCENE_AI_ADDCONTEXT, + SCENE_AI_INPUT, + SCENE_AI_GAMETEXT, // This is handled in CBaseFlex +#endif }; DECLARE_DATADESC(); +#ifdef MAPBASE_VSCRIPT + DECLARE_ENT_SCRIPTDESC(); +#endif private: enum { diff --git a/src/game/server/ai_basenpc.cpp b/src/game/server/ai_basenpc.cpp index ec4b17d1..a1e94432 100644 --- a/src/game/server/ai_basenpc.cpp +++ b/src/game/server/ai_basenpc.cpp @@ -95,6 +95,16 @@ #include "prop_portal_shared.h" #endif +#ifdef MAPBASE +#include "mapbase/matchers.h" +#include "items.h" +#include "point_camera.h" +#endif + +#ifdef MAPBASE_VSCRIPT +#include "mapbase/vscript_funcs_shared.h" +#endif + #include "env_debughistory.h" #include "collisionutils.h" @@ -156,6 +166,14 @@ ConVar ai_test_moveprobe_ignoresmall( "ai_test_moveprobe_ignoresmall", "0" ); extern ConVar ai_vehicle_avoidance; #endif // HL2_EPISODIC +#ifdef MAPBASE +extern ISoundEmitterSystemBase *soundemitterbase; + +ConVar ai_dynint_always_enabled( "ai_dynint_always_enabled", "0", FCVAR_NONE, "Makes the \"Don't Care\" setting equivalent to \"Yes\"." ); + +ConVar ai_debug_fake_sequence_gestures_always_play( "ai_debug_fake_sequence_gestures_always_play", "0", FCVAR_NONE, "Always plays fake sequence gestures." ); +#endif + #ifndef _RETAIL #define ShouldUseEfficiency() ( ai_use_think_optimizations.GetBool() && ai_use_efficiency.GetBool() ) #define ShouldUseFrameThinkLimits() ( ai_use_think_optimizations.GetBool() && ai_use_frame_think_limits.GetBool() ) @@ -291,6 +309,17 @@ int CAI_BaseNPC::gm_nSpawnedThisFrame; CSimpleSimTimer CAI_BaseNPC::m_AnyUpdateEnemyPosTimer; +#ifdef MAPBASE_VSCRIPT +// TODO: Better placement? +ScriptHook_t CAI_BaseNPC::g_Hook_QueryHearSound; +ScriptHook_t CAI_BaseNPC::g_Hook_QuerySeeEntity; +ScriptHook_t CAI_BaseNPC::g_Hook_TranslateActivity; +ScriptHook_t CAI_BaseNPC::g_Hook_TranslateSchedule; +ScriptHook_t CAI_BaseNPC::g_Hook_GetActualShootPosition; +ScriptHook_t CAI_BaseNPC::g_Hook_OverrideMove; +ScriptHook_t CAI_BaseNPC::g_Hook_ShouldPlayFakeSequenceGesture; +#endif + // // Deferred Navigation calls go here // @@ -544,7 +573,7 @@ void CAI_BaseNPC::CleanupOnDeath( CBaseEntity *pCulprit, bool bFireDeathOutput ) RemoveActorFromScriptedScenes( this, false /*all scenes*/ ); } else - DevMsg( "Unexpected double-death-cleanup\n" ); + CGMsg( 1, CON_GROUP_NPC_AI, "Unexpected double-death-cleanup\n" ); } void CAI_BaseNPC::SelectDeathPose( const CTakeDamageInfo &info ) @@ -642,13 +671,27 @@ void CAI_BaseNPC::Ignite( float flFlameLifetime, bool bNPCOnly, float flSize, bo { BaseClass::Ignite( flFlameLifetime, bNPCOnly, flSize, bCalledByLevelDesigner ); +#ifdef MAPBASE + // Alyx's enemy ignited code from below can now be run on any NPC as long as + // it's our current enemy. + if ( GetEnemy() && GetEnemy()->IsNPC() ) + { + GetEnemy()->MyNPCPointer()->EnemyIgnited( this ); + } +#endif + #ifdef HL2_EPISODIC CBasePlayer *pPlayer = AI_GetSinglePlayer(); - if ( pPlayer->IRelationType( this ) != D_LI ) + if ( pPlayer && pPlayer->IRelationType( this ) != D_LI ) { CNPC_Alyx *alyx = CNPC_Alyx::GetAlyx(); +#ifdef MAPBASE + // Alyx's code continues to run if Alyx was not this NPC's enemy. + if ( alyx && alyx != GetEnemy() ) +#else if ( alyx ) +#endif { alyx->EnemyIgnited( this ); } @@ -660,11 +703,209 @@ void CAI_BaseNPC::Ignite( float flFlameLifetime, bool bNPCOnly, float flSize, bo ConVar ai_block_damage( "ai_block_damage","0" ); +#ifdef MAPBASE +bool CAI_BaseNPC::FriendlyFireEnabled() +{ + if (m_FriendlyFireOverride != TRS_NONE) + return m_FriendlyFireOverride == TRS_TRUE; + + if (HL2GameRules()->GlobalFriendlyFire() != TRS_NONE) + return HL2GameRules()->GlobalFriendlyFire() == TRS_TRUE; + + return !(CapabilitiesGet() & bits_CAP_FRIENDLY_DMG_IMMUNE); +} + +//----------------------------------------------------------------------------- +void CAI_BaseNPC::InputSetFriendlyFire( inputdata_t &inputdata ) +{ + m_FriendlyFireOverride = TO_THREESTATE(inputdata.value.Int()); +} +#endif + +#ifdef MAPBASE_VSCRIPT +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +HSCRIPT CAI_BaseNPC::VScriptGetEnemy() +{ + return ToHScript( GetEnemy() ); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CAI_BaseNPC::VScriptSetEnemy( HSCRIPT pEnemy ) +{ + SetEnemy( ToEnt( pEnemy ) ); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +Vector CAI_BaseNPC::VScriptGetEnemyLKP() +{ + return GetEnemyLKP(); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +HSCRIPT CAI_BaseNPC::VScriptFindEnemyMemory( HSCRIPT pEnemy ) +{ + HSCRIPT hScript = NULL; + AI_EnemyInfo_t *info = GetEnemies()->Find( ToEnt(pEnemy) ); + if (info) + { + hScript = g_pScriptVM->RegisterInstance( reinterpret_cast(info) ); + } + + return hScript; +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +int CAI_BaseNPC::VScriptGetState() +{ + return (int)GetState(); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +HSCRIPT CAI_BaseNPC::VScriptGetHintNode() +{ + return ToHScript( GetHintNode() ); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +const char *CAI_BaseNPC::VScriptGetSchedule() +{ + const char *pName = NULL; + if (GetCurSchedule()) + pName = GetCurSchedule()->GetName(); + + if (!pName) + pName = "Unknown"; + + return pName; +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +int CAI_BaseNPC::VScriptGetScheduleID() +{ + if (!GetCurSchedule()) + return -1; + + int iSched = GetCurSchedule()->GetId(); + + // Local IDs are needed to correspond with user-friendly enums + if ( AI_IdIsGlobal( iSched ) ) + { + iSched = GetClassScheduleIdSpace()->ScheduleGlobalToLocal(iSched); + } + + return iSched; +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CAI_BaseNPC::VScriptSetSchedule( const char *szSchedule ) +{ + SetSchedule( GetScheduleID( szSchedule ) ); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +const char *CAI_BaseNPC::VScriptGetTask() +{ + const Task_t *pTask = GetTask(); + const char *pName = NULL; + if (pTask) + pName = TaskName( pTask->iTask ); + else + pName = "None"; + + return pName; +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +int CAI_BaseNPC::VScriptGetTaskID() +{ + const Task_t *pTask = GetTask(); + int iID = -1; + if (pTask) + iID = GetTaskID( TaskName( pTask->iTask ) ); + + return iID; +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +HSCRIPT CAI_BaseNPC::VScriptGetExpresser() +{ + HSCRIPT hScript = NULL; + CAI_Expresser *pExpresser = GetExpresser(); + if (pExpresser) + { + hScript = g_pScriptVM->RegisterInstance( pExpresser ); + } + + return hScript; +} + +HSCRIPT CAI_BaseNPC::VScriptGetCine() +{ + return ToHScript(m_hCine.Get()); +} + +HSCRIPT CAI_BaseNPC::VScriptGetSquad() +{ + HSCRIPT hScript = NULL; + CAI_Squad *pSquad = GetSquad(); + if (pSquad) + { + hScript = g_pScriptVM->RegisterInstance( pSquad ); + } + + return hScript; +} +#endif + bool CAI_BaseNPC::PassesDamageFilter( const CTakeDamageInfo &info ) { if ( ai_block_damage.GetBool() ) return false; // FIXME: hook a friendly damage filter to the npc instead? +#ifdef MAPBASE + if ( !FriendlyFireEnabled() && info.GetAttacker() && info.GetAttacker() != this && !info.IsForceFriendlyFire() ) + { + // check attackers relationship with me + CBaseCombatCharacter *npcEnemy = info.GetAttacker()->MyCombatCharacterPointer(); + + if ( npcEnemy && npcEnemy->IRelationType( this ) == D_LI ) + { + m_fNoDamageDecal = true; + + if ( npcEnemy->IsPlayer() ) + { + m_OnDamagedByPlayer.FireOutput( info.GetAttacker(), this ); + // This also counts as being harmed by player's squad. + m_OnDamagedByPlayerSquad.FireOutput( info.GetAttacker(), this ); + } + + return false; + } + + if ( IServerVehicle *pVehicle = info.GetAttacker()->GetServerVehicle() ) + { + m_fNoDamageDecal = true; + if (pVehicle->GetPassenger() && pVehicle->GetPassenger()->IRelationType(this) == D_LI) + { + // Players could bail from their cars to kill NPCs with this! + // Is there a "last passenger" variable we could use? + return false; + } + } + } +#else if ( (CapabilitiesGet() & bits_CAP_FRIENDLY_DMG_IMMUNE) && info.GetAttacker() && info.GetAttacker() != this ) { // check attackers relationship with me @@ -692,6 +933,7 @@ bool CAI_BaseNPC::PassesDamageFilter( const CTakeDamageInfo &info ) return false; } } +#endif if ( !BaseClass::PassesDamageFilter( info ) ) { @@ -714,7 +956,11 @@ int CAI_BaseNPC::OnTakeDamage_Alive( const CTakeDamageInfo &info ) return 0; if ( GetSleepState() == AISS_WAITING_FOR_THREAT ) +#ifdef MAPBASE + Wake( info.GetAttacker() ); +#else Wake(); +#endif // NOTE: This must happen after the base class is called; we need to reduce // health before the pain sound, since some NPCs use the final health @@ -766,24 +1012,27 @@ int CAI_BaseNPC::OnTakeDamage_Alive( const CTakeDamageInfo &info ) // only fire once per frame m_OnDamaged.FireOutput( info.GetAttacker(), this); - if( info.GetAttacker()->IsPlayer() ) + if ( info.GetAttacker() ) { - m_OnDamagedByPlayer.FireOutput( info.GetAttacker(), this ); - - // This also counts as being harmed by player's squad. - m_OnDamagedByPlayerSquad.FireOutput( info.GetAttacker(), this ); - } - else - { - // See if the person that injured me is an NPC. - CAI_BaseNPC *pAttacker = dynamic_cast( info.GetAttacker() ); - CBasePlayer *pPlayer = AI_GetSinglePlayer(); - - if( pAttacker && pAttacker->IsAlive() && pPlayer ) + if( info.GetAttacker()->IsPlayer() ) { - if( pAttacker->GetSquad() != NULL && pAttacker->IsInPlayerSquad() ) + m_OnDamagedByPlayer.FireOutput( info.GetAttacker(), this ); + + // This also counts as being harmed by player's squad. + m_OnDamagedByPlayerSquad.FireOutput( info.GetAttacker(), this ); + } + else + { + // See if the person that injured me is an NPC. + CAI_BaseNPC *pAttacker = info.GetAttacker()->MyNPCPointer(); + CBasePlayer *pPlayer = AI_GetSinglePlayer(); + + if( pAttacker && pAttacker->IsAlive() && pPlayer ) { - m_OnDamagedByPlayerSquad.FireOutput( info.GetAttacker(), this ); + if( pAttacker->GetSquad() != NULL && pAttacker->IsInPlayerSquad() ) + { + m_OnDamagedByPlayerSquad.FireOutput( info.GetAttacker(), this ); + } } } } @@ -995,7 +1244,8 @@ void CAI_BaseNPC::NotifyFriendsOfDamage( CBaseEntity *pAttackerEntity ) { if ( (originNpc.AsVector2D() - origin.AsVector2D()).LengthSqr() < NEAR_XY_SQ ) { - if ( pNpc->GetSquad() == GetSquad() || IRelationType( pNpc ) == D_LI ) + //Tony; add a check to make sure this doesn't get called if the npc isn't in a squad + if ( ( pNpc->GetSquad() == GetSquad() && !( pNpc->GetSquad() == NULL || GetSquad() == NULL ) ) || IRelationType( pNpc ) == D_LI ) pNpc->OnFriendDamaged( this, pAttacker ); } } @@ -1013,7 +1263,11 @@ void CAI_BaseNPC::OnFriendDamaged( CBaseCombatCharacter *pSquadmate, CBaseEntity float distSqToThreat = ( GetAbsOrigin() - pAttacker->GetAbsOrigin() ).LengthSqr(); if ( GetSleepState() != AISS_AWAKE && distSqToThreat < Square( 20 * 12 ) ) +#ifdef MAPBASE + Wake( pAttacker ); +#else Wake(); +#endif if ( distSqToThreat < Square( 50 * 12 ) ) ForceGatherConditions(); @@ -1182,7 +1436,12 @@ void CAI_BaseNPC::TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir break; } +#ifdef MAPBASE + bool bBloodAllowed = DamageFilterAllowsBlood( info ); + if ( subInfo.GetDamage() >= 1.0 && !(subInfo.GetDamageType() & DMG_SHOCK ) && bBloodAllowed ) +#else if ( subInfo.GetDamage() >= 1.0 && !(subInfo.GetDamageType() & DMG_SHOCK ) ) +#endif { if( !IsPlayer() || ( IsPlayer() && g_pGameRules->IsMultiplayer() ) ) { @@ -1197,6 +1456,12 @@ void CAI_BaseNPC::TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir m_fNoDamageDecal = true; } } +#ifdef MAPBASE + else if (!bBloodAllowed) + { + m_fNoDamageDecal = true; + } +#endif // Airboat gun will impart major force if it's about to kill him.... if ( info.GetDamageType() & DMG_AIRBOAT ) @@ -1286,7 +1551,12 @@ bool CAI_BaseNPC::PlayerInSpread( const Vector &sourcePos, const Vector &targetP { CBasePlayer *pPlayer = UTIL_PlayerByIndex( i ); +#ifdef MAPBASE + // "> D_FR" means it isn't D_HT, D_FR, or D_ER (error disposition) + if ( pPlayer && ( !ignoreHatedPlayers || IRelationType( pPlayer ) > D_FR ) && !(pPlayer->GetFlags() & FL_NOTARGET) ) +#else if ( pPlayer && ( !ignoreHatedPlayers || IRelationType( pPlayer ) != D_HT ) ) +#endif { if ( PointInSpread( pPlayer, sourcePos, targetPos, pPlayer->WorldSpaceCenter(), flSpread, maxDistOffCenter ) ) return true; @@ -1738,6 +2008,118 @@ void CAI_BaseNPC::ClearIgnoreConditions( int *pConditions, int nConditions ) } } +#ifdef MAPBASE +//------------------------------------------------------------------------------ +// Purpose: Adds a condition to this NPC, integral or not +//------------------------------------------------------------------------------ +void CAI_BaseNPC::InputSetCondition( inputdata_t &inputdata ) +{ + const char *pszCondition = inputdata.value.String(); + if (!pszCondition || !pszCondition[0]) + return; + + int iCondition = atoi(pszCondition); + if (iCondition == 0) + { + // Convert from string + iCondition = GetConditionID(pszCondition); + } + + SetCondition(iCondition); +} + +//------------------------------------------------------------------------------ +// Purpose: Removes a condition from this NPC, integral or not +//------------------------------------------------------------------------------ +void CAI_BaseNPC::InputClearCondition( inputdata_t &inputdata ) +{ + const char *pszCondition = inputdata.value.String(); + if (!pszCondition || !pszCondition[0]) + return; + + int iCondition = atoi(pszCondition); + if (iCondition == 0) + { + // Convert from string + iCondition = GetConditionID(pszCondition); + } + + ClearCondition(iCondition); +} + +//------------------------------------------------------------------------------ +// Purpose: Sets our think to CallNPCThink() in case SetThinkNull was fired before +//------------------------------------------------------------------------------ +void CAI_BaseNPC::InputSetThinkNPC( inputdata_t &inputdata ) +{ + SetThink ( &CAI_BaseNPC::CallNPCThink ); + SetNextThink(gpGlobals->curtime + inputdata.value.Float()); +} + +//------------------------------------------------------------------------------ +// Purpose: Sets our look distance +//------------------------------------------------------------------------------ +void CAI_BaseNPC::InputSetDistLook( inputdata_t &inputdata ) +{ + if ( inputdata.value.Float() != 0.0f ) + { + SetDistLook( inputdata.value.Float() ); + } + else + { + SetDistLook( 2048.0 ); + + if ( HasSpawnFlags( SF_NPC_LONG_RANGE ) ) + { + SetDistLook( 6000.0 ); + } + } +} + +//------------------------------------------------------------------------------ +// Purpose: Sets our distance too far +//------------------------------------------------------------------------------ +void CAI_BaseNPC::InputSetDistTooFar( inputdata_t &inputdata ) +{ + if ( inputdata.value.Float() != 0.0f ) + { + m_flDistTooFar = inputdata.value.Float(); + } + else + { + m_flDistTooFar = 1024.0; + + if ( HasSpawnFlags( SF_NPC_LONG_RANGE ) ) + { + m_flDistTooFar = 1e9f; + } + } +} + +//------------------------------------------------------------------------------ +// Purpose: +//------------------------------------------------------------------------------ +void CAI_BaseNPC::InputSetTarget( inputdata_t &inputdata ) +{ + m_target = inputdata.value.StringID(); + + if ( m_target != NULL_STRING )// this npc has a target + { + // Find the npc's initial target entity, stash it + SetGoalEnt( gEntList.FindEntityByName( NULL, m_target ) ); + + if ( !GetGoalEnt() ) + { + Warning( "ReadyNPC()--%s couldn't find target %s\n", GetClassname(), STRING(m_target)); + } + else + { + StartTargetHandling( GetGoalEnt() ); + } + } +} +#endif + //--------------------------------------------------------- //--------------------------------------------------------- bool CAI_BaseNPC::HasInterruptCondition( int iCondition ) @@ -1935,6 +2317,15 @@ bool CAI_BaseNPC::QueryHearSound( CSound *pSound ) return false; } +#ifdef MAPBASE + if ( pSound->SoundContext() & SOUND_CONTEXT_OWNER_ALLIES ) + { + CBaseCombatCharacter *pOwner = ToBaseCombatCharacter(pSound->m_hOwner); + if (!pOwner || pOwner->IRelationType(this) != D_LI) + return false; + } +#endif + if ( pSound->IsSoundType( SOUND_PLAYER ) && GetState() == NPC_STATE_IDLE && !FVisible( pSound->GetSoundReactOrigin() ) ) { // NPC's that are IDLE should disregard player movement sounds if they can't see them. @@ -1955,6 +2346,22 @@ bool CAI_BaseNPC::QueryHearSound( CSound *pSound ) if( ShouldIgnoreSound( pSound ) ) return false; +#ifdef MAPBASE_VSCRIPT + if (m_ScriptScope.IsInitialized() && g_Hook_QueryHearSound.CanRunInScope(m_ScriptScope)) + { + HSCRIPT hSound = g_pScriptVM->RegisterInstance( pSound ); + + ScriptVariant_t functionReturn = true; + ScriptVariant_t args[] = { hSound }; + g_Hook_QueryHearSound.Call( m_ScriptScope, &functionReturn, args ); + + g_pScriptVM->RemoveInstance( hSound ); + + if (functionReturn.Get() == false) + return false; + } +#endif + return true; } @@ -1962,12 +2369,31 @@ bool CAI_BaseNPC::QueryHearSound( CSound *pSound ) bool CAI_BaseNPC::QuerySeeEntity( CBaseEntity *pEntity, bool bOnlyHateOrFearIfNPC ) { + bool bValid = true; + if ( bOnlyHateOrFearIfNPC && pEntity->IsNPC() ) { Disposition_t disposition = IRelationType( pEntity ); - return ( disposition == D_HT || disposition == D_FR ); + bValid = ( disposition == D_HT || disposition == D_FR ); } - return true; + +#ifdef MAPBASE_VSCRIPT + if (bValid) + { + if (m_ScriptScope.IsInitialized() && g_Hook_QuerySeeEntity.CanRunInScope(m_ScriptScope)) + { + ScriptVariant_t functionReturn; + ScriptVariant_t args[] = { ToHScript(pEntity) }; + if (g_Hook_QuerySeeEntity.Call( m_ScriptScope, &functionReturn, args )) + { + if (functionReturn.Get() == false) + bValid = false; + } + } + } +#endif + + return bValid; } //----------------------------------------------------------------------------- @@ -2129,7 +2555,7 @@ void CAI_BaseNPC::OnListened() case SOUND_PLAYER_VEHICLE: condition = COND_HEAR_PLAYER; break; default: - DevMsg( "**ERROR: NPC %s hearing sound of unknown type %d!\n", GetClassname(), pCurrentSound->SoundType() ); + CGMsg( 1, CON_GROUP_NPC_AI, "**ERROR: NPC %s hearing sound of unknown type %d!\n", GetClassname(), pCurrentSound->SoundType() ); break; } } @@ -2317,7 +2743,7 @@ CSound* CAI_BaseNPC::GetBestSound( int validTypes ) return m_pLockedBestSound; CSound *pResult = GetSenses()->GetClosestSound( false, validTypes ); if ( pResult == NULL) - DevMsg( "Warning: NULL Return from GetBestSound\n" ); // condition previously set now no longer true. Have seen this when play too many sounds... + CGMsg( 1, CON_GROUP_NPC_AI, "Warning: NULL Return from GetBestSound\n" ); // condition previously set now no longer true. Have seen this when play too many sounds... return pResult; } @@ -2329,7 +2755,7 @@ CSound* CAI_BaseNPC::GetBestScent( void ) { CSound *pResult = GetSenses()->GetClosestSound( true ); if ( pResult == NULL) - DevMsg("Warning: NULL Return from GetBestScent\n" ); + CGMsg( 1, CON_GROUP_NPC_AI, "Warning: NULL Return from GetBestScent\n" ); return pResult; } @@ -2614,6 +3040,10 @@ void CAI_BaseNPC::PopulatePoseParameters( void ) m_poseAim_Yaw = LookupPoseParameter( "aim_yaw" ); m_poseMove_Yaw = LookupPoseParameter( "move_yaw" ); +#ifdef MAPBASE + m_poseInteractionRelativeYaw = LookupPoseParameter( "interaction_relative_yaw" ); +#endif + BaseClass::PopulatePoseParameters(); } @@ -3354,6 +3784,9 @@ void CAI_BaseNPC::UpdateSleepState( bool bInPVS ) { if ( GetSleepState() > AISS_AWAKE ) { +#ifdef MAPBASE +#define Wake() Wake(pLocalPlayer) +#endif CBasePlayer *pLocalPlayer = AI_GetSinglePlayer(); if ( !pLocalPlayer ) { @@ -3414,6 +3847,9 @@ void CAI_BaseNPC::UpdateSleepState( bool bInPVS ) } } } +#ifdef MAPBASE +#undef Wake +#endif } else { @@ -3884,6 +4320,12 @@ bool CAI_BaseNPC::CheckPVSCondition() { bool bInPVS = ( UTIL_FindClientInPVS( edict() ) != NULL ) || (UTIL_ClientPVSIsExpanded() && UTIL_FindClientInVisibilityPVS( edict() )); +#ifdef MAPBASE + // We can be in a player's PVS if there is an active point_camera nearby (fixes issues with choreo) + if (!bInPVS && UTIL_FindRTCameraInEntityPVS( edict() )) + bInPVS = true; +#endif + if ( bInPVS ) SetCondition( COND_IN_PVS ); else @@ -4176,6 +4618,24 @@ int CAI_BaseNPC::CapabilitiesGet( void ) const return capability; } +#ifdef MAPBASE +//------------------------------------------------------------------------------ +// Purpose: Adds capabilities to this NPC +//------------------------------------------------------------------------------ +void CAI_BaseNPC::InputAddCapabilities( inputdata_t &inputdata ) +{ + CapabilitiesAdd(inputdata.value.Int()); +} + +//------------------------------------------------------------------------------ +// Purpose: Removes capabilities from this NPC +//------------------------------------------------------------------------------ +void CAI_BaseNPC::InputRemoveCapabilities( inputdata_t &inputdata ) +{ + CapabilitiesRemove(inputdata.value.Int()); +} +#endif + // Set capability mask int CAI_BaseNPC::CapabilitiesAdd( int capability ) { @@ -4379,7 +4839,11 @@ void CAI_BaseNPC::SetState( NPC_STATE State ) if ( GetEnemy() != NULL ) { SetEnemy( NULL ); // not allowed to have an enemy anymore. +#ifdef MAPBASE + CGMsg( 2, CON_GROUP_NPC_AI, "Stripped enemy pointer from NPC going back to idle\n" ); +#else DevMsg( 2, "Stripped\n" ); +#endif } break; } @@ -4398,6 +4862,20 @@ void CAI_BaseNPC::SetState( NPC_STATE State ) // Notify the character that its state has changed. if( fNotifyChange ) { +#ifdef MAPBASE + // Doing OnStateChange here instead of in OnStateChange() to prevent override shenanigans. + + // Assume our enemy is the activator. + // States that don't have an enemy have a NULL activator, which is fine. + CBaseEntity *pActivator = GetEnemy(); + + // If we entered a script, use the scripted_sequence as the activator + if (m_NPCState == NPC_STATE_SCRIPT) + pActivator = m_hCine; + + m_OnStateChange.Set(m_NPCState, pActivator, this); +#endif + OnStateChange( OldState, m_NPCState ); } } @@ -4414,10 +4892,39 @@ void CAI_BaseNPC::Wake( bool bFireOutput ) if ( GetSleepState() != AISS_AWAKE ) { m_nWakeTick = gpGlobals->tickcount; +#ifndef MAPBASE SetSleepState( AISS_AWAKE ); +#endif RemoveEffects( EF_NODRAW ); if ( bFireOutput ) +#ifdef MAPBASE + { + // Activator is based on sleep state + CBaseEntity *pActivator = this; + + switch (GetSleepState()) + { + case AISS_WAITING_FOR_THREAT: + pActivator = GetEnemy(); + break; + + case AISS_WAITING_FOR_PVS: + case AISS_AUTO_PVS: + case AISS_AUTO_PVS_AFTER_PVS: + pActivator = UTIL_GetLocalPlayer(); + break; + + case AISS_WAITING_FOR_INPUT: + // I can't really do this here, but InputWake() uses the new function with pActivator, so it's fine + break; + } + + m_OnWake.FireOutput( pActivator, this ); + } + SetSleepState( AISS_AWAKE ); +#else m_OnWake.FireOutput( this, this ); +#endif if ( m_bWakeSquad && GetSquad() ) { @@ -4435,6 +4942,37 @@ void CAI_BaseNPC::Wake( bool bFireOutput ) } } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CAI_BaseNPC::Wake( CBaseEntity *pActivator ) +{ + if ( GetSleepState() != AISS_AWAKE ) + { + m_nWakeTick = gpGlobals->tickcount; + RemoveEffects( EF_NODRAW ); + + m_OnWake.FireOutput( pActivator, this ); + + SetSleepState( AISS_AWAKE ); + + if ( m_bWakeSquad && GetSquad() ) + { + AISquadIter_t iter; + for ( CAI_BaseNPC *pSquadMember = GetSquad()->GetFirstMember( &iter ); pSquadMember; pSquadMember = GetSquad()->GetNextMember( &iter ) ) + { + if ( pSquadMember->IsAlive() && pSquadMember != this ) + { + pSquadMember->m_bWakeSquad = false; + pSquadMember->Wake(pActivator); + } + } + + } + } +} +#endif + //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- void CAI_BaseNPC::Sleep() @@ -4698,7 +5236,7 @@ void CAI_BaseNPC::GatherConditions( void ) // @Note (toml 05-05-04): There seems to be a case where an NPC can not respond // to COND_NEW_ENEMY. Only evidence right now is save // games after the fact, so for now, just patching it up - DevMsg( 2, "Had to force COND_NEW_ENEMY\n" ); + CGMsg( 2, CON_GROUP_NPC_AI, "Had to force COND_NEW_ENEMY\n" ); SetCondition(COND_NEW_ENEMY); } } @@ -4750,8 +5288,30 @@ void CAI_BaseNPC::PrescheduleThink( void ) CheckForScriptedNPCInteractions(); #endif +#ifdef MAPBASE + // Please excuse the readability here. + if (CapabilitiesGet() & bits_CAP_USE_WEAPONS) + { + if ( CanUnholsterWeapon() ) + { + // If we should have our gun out, fetch it + if ( ShouldUnholsterWeapon() && m_iDesiredWeaponState == DESIREDWEAPONSTATE_IGNORE ) + { + SetDesiredWeaponState( DESIREDWEAPONSTATE_UNHOLSTERED ); + } + } + else if (m_iDesiredWeaponState == DESIREDWEAPONSTATE_UNHOLSTERED) + { + // If we cannot have our gun out, refuse to fetch it + SetDesiredWeaponState( DESIREDWEAPONSTATE_IGNORE ); + } + + // If our desired weapon state is not the current, fix it + if( (m_iDesiredWeaponState == DESIREDWEAPONSTATE_HOLSTERED || m_iDesiredWeaponState == DESIREDWEAPONSTATE_UNHOLSTERED || m_iDesiredWeaponState == DESIREDWEAPONSTATE_HOLSTERED_DESTROYED ) ) +#else // If we use weapons, and our desired weapon state is not the current, fix it if( (CapabilitiesGet() & bits_CAP_USE_WEAPONS) && (m_iDesiredWeaponState == DESIREDWEAPONSTATE_HOLSTERED || m_iDesiredWeaponState == DESIREDWEAPONSTATE_UNHOLSTERED || m_iDesiredWeaponState == DESIREDWEAPONSTATE_HOLSTERED_DESTROYED ) ) +#endif { if ( IsAlive() && !IsInAScript() ) { @@ -4774,6 +5334,9 @@ void CAI_BaseNPC::PrescheduleThink( void ) m_iDesiredWeaponState = DESIREDWEAPONSTATE_IGNORE; } } +#ifdef MAPBASE + } +#endif } //----------------------------------------------------------------------------- @@ -5130,6 +5693,62 @@ NPC_STATE CAI_BaseNPC::SelectIdealState( void ) return m_IdealNPCState; } +#ifdef MAPBASE +//------------------------------------------------------------------------------ +// Purpose: Creates a new weapon and makes us equip it. +//------------------------------------------------------------------------------ +CBaseCombatWeapon *CAI_BaseNPC::GiveWeapon( string_t iszWeaponName, bool bDiscardCurrent ) +{ + CBaseCombatWeapon *pWeapon = Weapon_Create( STRING(iszWeaponName) ); + if ( !pWeapon ) + { + Warning( "Couldn't create weapon %s to give NPC %s.\n", STRING(iszWeaponName), GetDebugName() ); + return NULL; + } + + // If I have a weapon already, drop it + if ( bDiscardCurrent && GetActiveWeapon() ) + { + Weapon_Drop( GetActiveWeapon() ); + } + + // If I have a name, make my weapon match it with "_weapon" appended + if ( GetEntityName() != NULL_STRING ) + { + pWeapon->SetName( AllocPooledString(UTIL_VarArgs("%s_weapon", STRING(GetEntityName()) )) ); + } + + Weapon_Equip( pWeapon ); + + // Handle this case + OnGivenWeapon( pWeapon ); + + return pWeapon; +} + +//------------------------------------------------------------------------------ +// Purpose: Creates a new weapon and puts it in our inventory. +//------------------------------------------------------------------------------ +CBaseCombatWeapon *CAI_BaseNPC::GiveWeaponHolstered( string_t iszWeaponName ) +{ + CBaseCombatWeapon *pWeapon = Weapon_Create( STRING(iszWeaponName) ); + if ( !pWeapon ) + { + Warning( "Couldn't create weapon %s to give NPC %s.\n", STRING(iszWeaponName), GetDebugName() ); + return NULL; + } + + // If I have a name, make my weapon match it with "_weapon" appended + if ( GetEntityName() != NULL_STRING ) + { + pWeapon->SetName( AllocPooledString(UTIL_VarArgs("%s_weapon", STRING(GetEntityName()) )) ); + } + + Weapon_EquipHolstered( pWeapon ); + + return pWeapon; +} +#else //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ void CAI_BaseNPC::GiveWeapon( string_t iszWeaponName ) @@ -5158,6 +5777,7 @@ void CAI_BaseNPC::GiveWeapon( string_t iszWeaponName ) // Handle this case OnGivenWeapon( pWeapon ); } +#endif //----------------------------------------------------------------------------- // Rather specific function that tells us if an NPC is in the process of @@ -5490,7 +6110,11 @@ bool CAI_BaseNPC::UpdateEnemyMemory( CBaseEntity *pEnemy, const Vector &position // If the was eluding me and allow the NPC to play a sound if (GetEnemies()->HasEludedMe(pEnemy)) { +#ifdef MAPBASE + FoundEnemySound( pEnemy ); +#else FoundEnemySound(); +#endif } float reactionDelay = ( !pInformer || pInformer == this ) ? GetReactionDelay( pEnemy ) : 0.0; bool result = GetEnemies()->UpdateMemory(GetNavigator()->GetNetwork(), pEnemy, position, reactionDelay, firstHand); @@ -5599,6 +6223,11 @@ void CAI_BaseNPC::GatherEnemyConditions( CBaseEntity *pEnemy ) EHANDLE hEnemy; hEnemy.Set( GetEnemy() ); +#ifdef MAPBASE + if (hEnemy->IsPlayer()) + m_OnFoundPlayer.Set(hEnemy, hEnemy, this); + m_OnFoundEnemy.Set(hEnemy, hEnemy, this); +#else if (GetEnemy()->IsPlayer()) { m_OnFoundPlayer.Set(hEnemy, this, this); @@ -5608,6 +6237,7 @@ void CAI_BaseNPC::GatherEnemyConditions( CBaseEntity *pEnemy ) { m_OnFoundEnemy.Set(hEnemy, this, this); } +#endif } Remember( bits_MEMORY_HAD_LOS ); } @@ -5904,6 +6534,136 @@ CAI_BaseNPC *CAI_BaseNPC::CreateCustomTarget( const Vector &vecOrigin, float dur #endif// HL2_DLL } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: This is used by TranslateActivity() before anything else. +// Input : eNewActivity - +// Output : Activity +//----------------------------------------------------------------------------- +Activity CAI_BaseNPC::TranslateCrouchActivity( Activity eNewActivity ) +{ + if (CapabilitiesGet() & bits_CAP_DUCK && CanTranslateCrouchActivity()) + { + // ======================================================================== + // The main issue with cover hint nodes is that crouch activities are not translated at the right time + // in the activity translation process. Weapons aren't given a chance to translate from them + // and therefore the crouch activities on many NPCs are inaccessible. + // + // Regular, non-hint-node crouching seen on Combine soldiers and Episodic Alyx + // seemingly don't work when put here, so don't bother. + // ======================================================================== + Activity nCoverActivity = eNewActivity; + if (eNewActivity == ACT_RELOAD) + { + nCoverActivity = GetReloadActivity(GetHintNode()); + } + // INCOVER is synonymous with crouching at crouch cover nodes. + // Any time we need to crouch at cover is when INCOVER is valid. + else if (HasMemory(bits_MEMORY_INCOVER)) + { + if (eNewActivity == ACT_IDLE || eNewActivity == ACT_COVER) + { + // They stick to cover sometimes, but that might just be when they can't path to the enemy. + if (GetState() == NPC_STATE_COMBAT /*&& !IsCurSchedule(SCHED_COMBAT_STAND, false)*/) + nCoverActivity = GetCoverActivity(GetHintNode()); + } + else if (eNewActivity == ACT_RANGE_ATTACK1) + { + // Soldiers are the only ones I've seen attack while INCOVER, + // so I don't think we have to give it its own function. + CAI_Hint *pHint = GetHintNode(); + if (pHint) + { + if (pHint->HintType() == HINT_TACTICAL_COVER_LOW) + { + nCoverActivity = ACT_RANGE_ATTACK1_LOW; + } + else if (pHint->HintType() == HINT_TACTICAL_COVER_MED) + { +#if EXPANDED_HL2_COVER_ACTIVITIES + nCoverActivity = ACT_RANGE_ATTACK1_MED; +#else + nCoverActivity = ACT_RANGE_ATTACK1_LOW; +#endif + } + } + } + } + + if (nCoverActivity != ACT_IDLE) + eNewActivity = nCoverActivity; + + /* + // --------------------------------------------------------------- + // Some NPCs don't have a cover activity defined so just use idle + // --------------------------------------------------------------- + if (nCoverActivity != eNewActivity) + { + if (SelectWeightedSequence(nCoverActivity) != ACTIVITY_NOT_AVAILABLE) + { + eNewActivity = nCoverActivity; + } + else if (eNewActivity == ACT_COVER) + { + // Untranslated ACT_COVER should revert to ACT_IDLE + eNewActivity = ACT_IDLE; + } + } + */ + } + + return eNewActivity; +} + +//----------------------------------------------------------------------------- +// Purpose: Backup activity (NPC version of Weapon_BackupActivity) +// This only gets called if the NPC absolutely does not have an animation. +// This means if we don't return another activity, the NPC will most likely T-pose. +// Input : eNewActivity - +// Output : Activity +//----------------------------------------------------------------------------- +Activity CAI_BaseNPC::NPC_BackupActivity( Activity eNewActivity ) +{ + //if (eNewActivity == ACT_DROP_WEAPON) + // return TranslateActivity(ACT_IDLE); + + // --------------------------------------------- + + // Accounts for certain act busy activities that aren't on all NPCs. + if (eNewActivity == ACT_BUSY_QUEUE || eNewActivity == ACT_BUSY_STAND) + return TranslateActivity(ACT_IDLE); + + // --------------------------------------------- + + if (eNewActivity == ACT_WALK_ANGRY) + return TranslateActivity(ACT_WALK); + + // --------------------------------------------- + + // If one climbing animation isn't available, use the other + if (eNewActivity == ACT_CLIMB_DOWN) + return ACT_CLIMB_UP; + else if (eNewActivity == ACT_CLIMB_UP) + return ACT_CLIMB_DOWN; + + // --------------------------------------------- + + switch (eNewActivity) + { + case ACT_COVER_MED: eNewActivity = ACT_COVER_LOW; break; +#if EXPANDED_HL2_COVER_ACTIVITIES + case ACT_RANGE_AIM_MED: eNewActivity = ACT_RANGE_AIM_LOW; break; + case ACT_RANGE_ATTACK1_MED: eNewActivity = ACT_RANGE_ATTACK1_LOW; break; +#endif + } + + //if (eNewActivity == ACT_COVER) + // return TranslateActivity(ACT_IDLE); + + return eNewActivity; +} +#endif + //----------------------------------------------------------------------------- // Purpose: // Input : eNewActivity - @@ -5911,6 +6671,51 @@ CAI_BaseNPC *CAI_BaseNPC::CreateCustomTarget( const Vector &vecOrigin, float dur //----------------------------------------------------------------------------- Activity CAI_BaseNPC::NPC_TranslateActivity( Activity eNewActivity ) { +#if EXPANDED_NAVIGATION_ACTIVITIES + if ( GetNavType() == NAV_CLIMB && eNewActivity == ACT_IDLE ) + { + // Schedules which break into idle activities should try to maintain the climbing animation. + return ACT_CLIMB_IDLE; + } +#endif + +#ifdef MAPBASE + Assert( eNewActivity != ACT_INVALID ); + + if (IsCrouching()) + { + switch (eNewActivity) + { + case ACT_RANGE_ATTACK1: eNewActivity = ACT_RANGE_ATTACK1_LOW; break; + case ACT_RELOAD: eNewActivity = ACT_RELOAD_LOW; break; + case ACT_IDLE: eNewActivity = ACT_RANGE_AIM_LOW; break; // ACT_CROUCHIDLE is more-or-less deprecated and not friendly to weapon translation + } + } + +#ifdef MAPBASE_VSCRIPT + if (m_ScriptScope.IsInitialized() && g_Hook_TranslateActivity.CanRunInScope(m_ScriptScope)) + { + // activity, activity_id + ScriptVariant_t functionReturn; + ScriptVariant_t args[] = { GetActivityName(eNewActivity), (int)eNewActivity }; + if (g_Hook_TranslateActivity.Call( m_ScriptScope, &functionReturn, args )) + { + if (functionReturn.GetType() == FIELD_INTEGER) + { + Activity activity = (Activity)functionReturn.Get(); + if (activity != ACT_INVALID) + eNewActivity = (Activity)functionReturn.Get(); + } + else + { + Activity activity = (Activity)GetActivityID( functionReturn.Get() ); + if (activity != ACT_INVALID) + eNewActivity = activity; + } + } + } +#endif +#else Assert( eNewActivity != ACT_INVALID ); if (eNewActivity == ACT_RANGE_ATTACK1) @@ -5967,6 +6772,7 @@ Activity CAI_BaseNPC::NPC_TranslateActivity( Activity eNewActivity ) return nCoverActivity; } } +#endif return eNewActivity; } @@ -5986,6 +6792,13 @@ Activity CAI_BaseNPC::TranslateActivity( Activity idealActivity, Activity *pIdea Activity last; Activity current; +#ifdef MAPBASE + // Crouch activities are translated before everything else. + idealActivity = TranslateCrouchActivity( idealActivity ); + //if ( idealWeaponActivity != idealActivity /*&& HaveSequenceForActivity(idealWeaponActivity)*/ ) + // idealActivity = idealWeaponActivity; +#endif + idealWeaponActivity = Weapon_TranslateActivity( idealActivity, &bIdealWeaponRequired ); if ( pIdealWeaponActivity ) *pIdealWeaponActivity = idealWeaponActivity; @@ -6013,6 +6826,12 @@ Activity CAI_BaseNPC::TranslateActivity( Activity idealActivity, Activity *pIdea if ( HaveSequenceForActivity( weaponTranslation ) ) return weaponTranslation; +#ifdef MAPBASE + // This is used so NPCs can use any weapon, restored AR2 activities on one NPC don't T-pose another, etc. + Activity backupActivity = Weapon_BackupActivity(baseTranslation, bWeaponRequired); + if (baseTranslation != backupActivity && HaveSequenceForActivity(backupActivity)) + return backupActivity; +#endif if ( bWeaponRequired ) { @@ -6033,11 +6852,31 @@ Activity CAI_BaseNPC::TranslateActivity( Activity idealActivity, Activity *pIdea return baseTranslation; if ( idealWeaponActivity != baseTranslation && HaveSequenceForActivity( idealWeaponActivity ) ) +#ifdef MAPBASE + return idealWeaponActivity; +#else return idealActivity; +#endif if ( idealActivity != idealWeaponActivity && HaveSequenceForActivity( idealActivity ) ) return idealActivity; +#ifdef MAPBASE + // We absolutely do not have an activity for this. + // Do the same as above, but with any backup activity we may have available. + backupActivity = NPC_BackupActivity(baseTranslation); + if (backupActivity != baseTranslation && HaveSequenceForActivity(backupActivity)) + return backupActivity; + + backupActivity = NPC_BackupActivity(idealWeaponActivity); + if (backupActivity != idealWeaponActivity && HaveSequenceForActivity(backupActivity)) + return backupActivity; + + backupActivity = NPC_BackupActivity(idealActivity); + if (backupActivity != idealActivity && HaveSequenceForActivity(backupActivity)) + return backupActivity; +#endif + Assert( !HaveSequenceForActivity( idealActivity ) ); if ( idealActivity == ACT_RUN ) { @@ -6066,7 +6905,12 @@ void CAI_BaseNPC::ResolveActivityToSequence(Activity NewActivity, int &iSequence translatedActivity = TranslateActivity( NewActivity, &weaponActivity ); +#ifdef MAPBASE + // Cover cases where TranslateActivity() returns a sequence by using ACT_SCRIPT_CUSTOM_MOVE + if ( NewActivity == ACT_SCRIPT_CUSTOM_MOVE || translatedActivity == ACT_SCRIPT_CUSTOM_MOVE ) +#else if ( NewActivity == ACT_SCRIPT_CUSTOM_MOVE ) +#endif { iSequence = GetScriptCustomMoveSequence(); } @@ -6252,6 +7096,23 @@ void CAI_BaseNPC::SetIdealActivity( Activity NewActivity ) // Perform translation in case we need to change sequences within a single activity, // such as between a standing idle and a crouching idle. ResolveActivityToSequence(m_IdealActivity, m_nIdealSequence, m_IdealTranslatedActivity, m_IdealWeaponActivity); + +#ifdef MAPBASE + // Check if we need a gesture to imitate this sequence + if ( ShouldPlayFakeSequenceGesture( m_IdealActivity, m_IdealTranslatedActivity ) ) + { + Activity nGesture = SelectFakeSequenceGesture( m_IdealActivity, m_IdealTranslatedActivity ); + if (nGesture != -1) + { + PlayFakeSequenceGesture( nGesture, m_IdealActivity, m_IdealTranslatedActivity ); + } + } + else if (GetFakeSequenceGesture() != -1) + { + // Reset the current gesture sequence if there is one + ResetFakeSequenceGesture(); + } +#endif } @@ -6300,6 +7161,14 @@ void CAI_BaseNPC::AdvanceToIdealActivity(void) //DevMsg("%s: Unable to get from sequence %s to %s!\n", GetClassname(), GetSequenceName(GetSequence()), GetSequenceName(m_nIdealSequence)); SetActivity(m_IdealActivity); } + +#ifdef MAPBASE + // If there was a gesture imitating a sequence, get rid of it + if ( GetFakeSequenceGesture() != -1 ) + { + ResetFakeSequenceGesture(); + } +#endif } @@ -6357,6 +7226,12 @@ void CAI_BaseNPC::MaintainActivity(void) } // Else a transition sequence is in progress, do nothing. } +#ifdef MAPBASE + else if (GetFakeSequenceGesture() != -1) + { + // Don't advance even if the sequence gesture is finished, as advancing would just play the original activity afterwards + } +#endif // Else get a specific sequence for the activity and try to transition to that. else { @@ -6375,11 +7250,103 @@ void CAI_BaseNPC::MaintainActivity(void) } +#ifdef MAPBASE +bool CAI_BaseNPC::ShouldPlayFakeSequenceGesture( Activity nActivity, Activity nTranslatedActivity ) +{ + // Don't do anything if we're resetting our activity + if (GetActivity() == ACT_RESET) + return false; + + // No need to do this while we're moving or for sequences which will make us move + if (IsMoving()) + return false; + + if (ai_debug_fake_sequence_gestures_always_play.GetBool()) + return true; + +#ifdef MAPBASE_VSCRIPT + if (m_ScriptScope.IsInitialized() && g_Hook_ShouldPlayFakeSequenceGesture.CanRunInScope(m_ScriptScope)) + { + // activity, translatedActivity + ScriptVariant_t functionReturn; + ScriptVariant_t args[] = { GetActivityName( nActivity ), GetActivityName( nTranslatedActivity ) }; + if (g_Hook_ShouldPlayFakeSequenceGesture.Call( m_ScriptScope, &functionReturn, args )) + { + if (functionReturn.GetType() == FIELD_BOOLEAN) + return functionReturn.Get(); + } + } +#endif + + if (GetHintNode() && GetHintNode()->HintActivityName() != NULL_STRING) + { + switch (GetHintNode()->HintType()) + { + // Cover nodes with custom activities should allow NPCs to do things like reload while in cover. + case HINT_TACTICAL_COVER_LOW: + case HINT_TACTICAL_COVER_MED: + case HINT_TACTICAL_COVER_CUSTOM: + if (HasMemory( bits_MEMORY_INCOVER )) + { + // Don't attack while using a custom animation in cover + if (nActivity != ACT_RANGE_ATTACK1 && nActivity != ACT_RANGE_ATTACK1_LOW) + return true; + } + break; + } + } + + return false; +} + +Activity CAI_BaseNPC::SelectFakeSequenceGesture( Activity nActivity, Activity nTranslatedActivity ) +{ + return GetGestureVersionOfActivity( nTranslatedActivity ); +} + +inline void CAI_BaseNPC::PlayFakeSequenceGesture( Activity nActivity, Activity nSequence, Activity nTranslatedSequence ) +{ + RestartGesture( nActivity, true, true ); + m_FakeSequenceGestureLayer = FindGestureLayer( nActivity ); + + switch ( nSequence ) + { + case ACT_RANGE_ATTACK1: + //case ACT_RANGE_ATTACK2: + { + OnRangeAttack1(); + + // FIXME: this seems a bit wacked + Weapon_SetActivity( Weapon_TranslateActivity( nSequence ), 0 ); + } break; + } +} + +inline int CAI_BaseNPC::GetFakeSequenceGesture() +{ + return m_FakeSequenceGestureLayer; +} + +void CAI_BaseNPC::ResetFakeSequenceGesture() +{ + SetLayerCycle( m_FakeSequenceGestureLayer, 1.0f ); + m_FakeSequenceGestureLayer = -1; +} +#endif + + //----------------------------------------------------------------------------- // Purpose: Returns true if our ideal activity has finished playing. //----------------------------------------------------------------------------- bool CAI_BaseNPC::IsActivityFinished( void ) { +#ifdef MAPBASE + if (GetFakeSequenceGesture() != -1) + { + return IsLayerFinished( GetFakeSequenceGesture() ); + } +#endif + return (IsSequenceFinished() && (GetSequence() == m_nIdealSequence)); } @@ -6415,6 +7382,15 @@ void CAI_BaseNPC::OnChangeActivity( Activity eNewActivity ) eNewActivity == ACT_WALK ) { Stand(); + +#ifdef MAPBASE + // Unlock custom cover nodes + if (GetHintNode() && GetHintNode()->HintType() == HINT_TACTICAL_COVER_CUSTOM && HasMemory(bits_MEMORY_INCOVER)) + { + GetHintNode()->Unlock( GetHintDelay( GetHintNode()->HintType() ) ); + SetHintNode( NULL ); + } +#endif } } @@ -6691,7 +7667,11 @@ void CAI_BaseNPC::SetHullSizeNormal( bool force ) if ( m_fIsUsingSmallHull || force ) { // Find out what the height difference will be between the versions and adjust our bbox accordingly to keep us level +#ifdef MAPBASE // From Alien Swarm SDK + const float flScale = MIN( 1.0f, GetModelScale() ); // NOTE: Cannot scale NPC bounding box up, as pathfinding will fail (hull needs to match the traces used for the node network) +#else const float flScale = GetModelScale(); +#endif Vector vecMins = ( GetHullMins() * flScale ); Vector vecMaxs = ( GetHullMaxs() * flScale ); @@ -6983,7 +7963,11 @@ void CAI_BaseNPC::OnChangeActiveWeapon( CBaseCombatWeapon *pOldWeapon, CBaseComb //----------------------------------------------------------------------------- bool CAI_BaseNPC::CanHolsterWeapon( void ) { +#ifdef MAPBASE + int seq = SelectWeightedSequence( TranslateActivity(ACT_DISARM) ); +#else int seq = SelectWeightedSequence( ACT_DISARM ); +#endif return (seq >= 0); } @@ -6995,12 +7979,22 @@ int CAI_BaseNPC::HolsterWeapon( void ) if ( IsWeaponHolstered() ) return -1; +#ifdef MAPBASE + Activity activity = TranslateActivity( ACT_DISARM ); + int iHolsterGesture = FindGestureLayer( activity ); + if ( iHolsterGesture != -1 ) + return iHolsterGesture; + + int iLayer = AddGesture( activity, true ); + //iLayer = AddGesture( ACT_GESTURE_DISARM, true ); +#else int iHolsterGesture = FindGestureLayer( ACT_DISARM ); if ( iHolsterGesture != -1 ) return iHolsterGesture; int iLayer = AddGesture( ACT_DISARM, true ); //iLayer = AddGesture( ACT_GESTURE_DISARM, true ); +#endif if (iLayer != -1) { @@ -7022,6 +8016,13 @@ int CAI_BaseNPC::HolsterWeapon( void ) ClearCondition(COND_NO_PRIMARY_AMMO); ClearCondition(COND_NO_SECONDARY_AMMO); } +#ifdef MAPBASE + else + { + // We don't have the animation, so just make our weapon holster instantaneously. + DoHolster(); + } +#endif return iLayer; } @@ -7034,18 +8035,43 @@ int CAI_BaseNPC::UnholsterWeapon( void ) if ( !IsWeaponHolstered() ) return -1; +#ifdef MAPBASE + Activity activity = TranslateActivity( ACT_ARM ); + int iHolsterGesture = FindGestureLayer( activity ); +#else int iHolsterGesture = FindGestureLayer( ACT_ARM ); +#endif if ( iHolsterGesture != -1 ) return iHolsterGesture; +#ifdef MAPBASE + int i = m_iLastHolsteredWeapon >= 0 && GetWeapon(m_iLastHolsteredWeapon) ? m_iLastHolsteredWeapon : -1; + if (i == -1) + { + // Set i to the first weapon you can find + for (i = 0;;) + { + if (GetWeapon(i)) + break; + + if (++i >= WeaponCount()) + return -1; + } + } +#else // Deploy the first weapon you can find for (int i = 0; i < WeaponCount(); i++) +#endif { if ( GetWeapon( i )) { SetActiveWeapon( GetWeapon(i) ); +#ifdef MAPBASE + int iLayer = AddGesture( TranslateActivity( ACT_ARM ), true ); +#else int iLayer = AddGesture( ACT_ARM, true ); +#endif //iLayer = AddGesture( ACT_GESTURE_ARM, true ); if (iLayer != -1) @@ -7056,6 +8082,13 @@ int CAI_BaseNPC::UnholsterWeapon( void ) m_iDesiredWeaponState = DESIREDWEAPONSTATE_CHANGING; } +#ifdef MAPBASE + else + { + // We don't have the animation, so just make our weapon unholster instantaneously. + DoUnholster(); + } +#endif // Refill the clip if ( GetActiveWeapon()->UsesClipsForAmmo1() ) @@ -7096,6 +8129,26 @@ void CAI_BaseNPC::InputHolsterAndDestroyWeapon( inputdata_t &inputdata ) //----------------------------------------------------------------------------- void CAI_BaseNPC::InputUnholsterWeapon( inputdata_t &inputdata ) { +#ifdef MAPBASE + // Support for unholstering a specific weapon + if (inputdata.value.String()) + { + if (IsWeaponHolstered()) + { + for (int i=0;im_iClassname == inputdata.value.StringID() ) + { + //Weapon_Switch(m_hMyWeapons[i]); + //DoHolster(); + m_iLastHolsteredWeapon = i; + } + } + } + } +#endif + m_iDesiredWeaponState = DESIREDWEAPONSTATE_UNHOLSTERED; } @@ -7121,6 +8174,246 @@ bool CAI_BaseNPC::IsWeaponStateChanging( void ) return ( m_iDesiredWeaponState == DESIREDWEAPONSTATE_CHANGING || m_iDesiredWeaponState == DESIREDWEAPONSTATE_CHANGING_DESTROY ); } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: Allows NPC to holster from more than just the animation event +//----------------------------------------------------------------------------- +bool CAI_BaseNPC::DoHolster( void ) +{ + // Cache off the weapon. + CBaseCombatWeapon *pWeapon = GetActiveWeapon(); + + if (pWeapon) + { + // Mark this as our last weapon + for (int i = 0; i < MAX_WEAPONS; i++) + { + if (m_hMyWeapons[i].Get() == pWeapon) + { + // Set to this weapon if we don't have a "target" weapon to unholster + if (m_iLastHolsteredWeapon == -1) + m_iLastHolsteredWeapon = i; + + break; + } + } + + GetActiveWeapon()->Holster(); + SetActiveWeapon( NULL ); + + //Force the NPC to recalculate it's arrival activity since it'll most likely be wrong now that we don't have a weapon out. + GetNavigator()->SetArrivalSequence( ACT_INVALID ); + + if ( m_iDesiredWeaponState == DESIREDWEAPONSTATE_CHANGING_DESTROY ) + { + // Get rid of it! + UTIL_Remove( pWeapon ); + } + + if ( m_iDesiredWeaponState != DESIREDWEAPONSTATE_IGNORE ) + { + m_iDesiredWeaponState = DESIREDWEAPONSTATE_IGNORE; + m_Activity = ACT_RESET; + } + + return true; + } + else + { + return false; + } +} + +//----------------------------------------------------------------------------- +// Purpose: Allows NPC to unholster from more than just the animation event +//----------------------------------------------------------------------------- +bool CAI_BaseNPC::DoUnholster( void ) +{ + if (GetActiveWeapon()) + { + GetActiveWeapon()->Deploy(); + + //Force the NPC to recalculate it's arrival activity since it'll most likely be wrong now. + GetNavigator()->SetArrivalSequence( ACT_INVALID ); + + if ( m_iDesiredWeaponState != DESIREDWEAPONSTATE_IGNORE ) + { + m_iDesiredWeaponState = DESIREDWEAPONSTATE_IGNORE; + m_Activity = ACT_RESET; + } + + // Clear last holstered weapon + m_iLastHolsteredWeapon = -1; + + return true; + } + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: Returns true if the NPC should be unholstering their weapon +//----------------------------------------------------------------------------- +bool CAI_BaseNPC::ShouldUnholsterWeapon( void ) +{ + return GetState() == NPC_STATE_COMBAT; +} + +//----------------------------------------------------------------------------- +// Purpose: Returns true if the NPC can unholster their weapon +//----------------------------------------------------------------------------- +bool CAI_BaseNPC::CanUnholsterWeapon( void ) +{ + // Don't unholster during special navigation + if ( GetNavType() == NAV_JUMP || GetNavType() == NAV_CLIMB ) + return false; + + return IsWeaponHolstered(); +} + +//------------------------------------------------------------------------------ +// Purpose: Give the NPC in question the weapon specified +//------------------------------------------------------------------------------ +void CAI_BaseNPC::InputGiveWeaponHolstered( inputdata_t &inputdata ) +{ + string_t iszWeaponName = inputdata.value.StringID(); + if ( iszWeaponName != NULL_STRING ) + { + GiveWeaponHolstered( iszWeaponName ); + } +} + +//------------------------------------------------------------------------------ +// Purpose: Makes the NPC change to the specified weapon via successive holster/unholster animations, creates it if it doesn't exist +//------------------------------------------------------------------------------ +void CAI_BaseNPC::InputChangeWeapon( inputdata_t &inputdata ) +{ + CBaseCombatWeapon *pSwitchTo = NULL; // The weapon itself + int iSwitchTo; // Index in m_hMyWeapons + for (int i=0;im_iClassname == inputdata.value.StringID() ) + { + pSwitchTo = m_hMyWeapons[i]; + iSwitchTo = i; + break; + } + } + + if (!pSwitchTo) + { + // We must not have it + pSwitchTo = GiveWeaponHolstered(inputdata.value.StringID()); + for (int i = 0; im_iClassname); + g_EventQueue.AddEvent(this, "UnholsterWeapon", variant, GetLayerDuration(iHolsterLayer) /*+ 0.65*/, this, this); + } +} + +//------------------------------------------------------------------------------ +// Purpose: Makes the NPC pick up the specified weapon if it can +//------------------------------------------------------------------------------ +void CAI_BaseNPC::InputPickupWeapon( inputdata_t &inputdata ) +{ + string_t iszWeaponName = inputdata.value.StringID(); + if (iszWeaponName == NULL_STRING) + return; + + // Doing a custom search implementation to prevent us from trying to pick up other people's weapons + // Also works generically so you could do things like "PickupWeapon > weapon_smg1" to pick up the nearest SMG + float flCurDist = MAX_TRACE_LENGTH; + CBaseCombatWeapon *pWeapon = NULL; + + CBaseEntity *pSearch = NULL; + while ((pSearch = gEntList.FindEntityGeneric(pSearch, STRING(iszWeaponName), this, inputdata.pActivator, inputdata.pCaller)) != NULL) + { + if (!pSearch->edict()) + continue; + + // Don't pick up non-weapons or weapons we can't use + CBaseCombatWeapon *pSearchWeapon = pSearch->MyCombatWeaponPointer(); + if (!pSearchWeapon || pSearchWeapon->GetOwner() || pSearchWeapon->IsLocked(this)) + continue; + + // If the weapon is marked to deny NPC pickup, only reject it if we're not searching for this weapon specifically. + // It might only have that spawnflag to prevent other NPCs from picking it up automatically. + // If we're searching for any weapon in general though (classnames or wildcards), don't use it. + if (pSearchWeapon->HasSpawnFlags( SF_WEAPON_NO_NPC_PICKUP ) && iszWeaponName != pSearchWeapon->GetEntityName()) + continue; + + float flDist = (pSearch->GetAbsOrigin() - GetAbsOrigin()).LengthSqr(); + if (flDist < flCurDist) + { + pWeapon = pSearchWeapon; + flCurDist = flDist; + } + } + + if (!pWeapon) + { + Msg("%s couldn't find %s to pick up\n", GetDebugName(), STRING(iszWeaponName)); + return; + } + + m_flNextWeaponSearchTime = gpGlobals->curtime + 10.0; + // Now lock the weapon for several seconds while we go to pick it up. + pWeapon->Lock( 10.0, this ); + SetTarget( pWeapon ); + SetSchedule(SCHED_NEW_WEAPON); +} + +//------------------------------------------------------------------------------ +// Purpose: Makes the NPC pick up the specified item if it can +//------------------------------------------------------------------------------ +void CAI_BaseNPC::InputPickupItem( inputdata_t &inputdata ) +{ + string_t iszItemName = inputdata.value.StringID(); + if (iszItemName == NULL_STRING) + return; + + // Searching generically allows for things like "PickupItem > item_health*" to pick up the nearest healthkit + CBaseEntity *pEntity = gEntList.FindEntityGenericNearest(STRING(iszItemName), GetLocalOrigin(), 0, this, inputdata.pActivator, inputdata.pCaller); + if (!pEntity) + return; + + SetTarget( pEntity ); + + // Can apply to anything, not just healthkits + SetSchedule(SCHED_GET_HEALTHKIT); +} +#endif + //----------------------------------------------------------------------------- // Set up the shot regulator based on the equipped weapon //----------------------------------------------------------------------------- @@ -7152,6 +8445,7 @@ void CAI_BaseNPC::InitRelationshipTable(void) } +#ifndef MAPBASE // Moved to CBaseCombatCharacter //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- @@ -7236,6 +8530,28 @@ void CAI_BaseNPC::AddRelationship( const char *pszRelationship, CBaseEntity *pAc } else { +#ifdef MAPBASE + if (!Q_strnicmp(entityString, "CLASS_", 5)) + { + // Go through all of the classes and find which one this is + Class_T resultClass = CLASS_NONE; + for (int i = 0; i < NUM_AI_CLASSES; i++) + { + if (FStrEq(g_pGameRules->AIClassText(i), entityString)) + { + resultClass = (Class_T)i; + } + } + + if (resultClass != CLASS_NONE) + { + AddClassRelationship( resultClass, disposition, priority ); + bFoundEntity = true; + } + } + + if (!bFoundEntity) +#endif DevWarning( "Couldn't set relationship to unknown entity or class (%s)!\n", entityString ); } } @@ -7244,6 +8560,7 @@ void CAI_BaseNPC::AddRelationship( const char *pszRelationship, CBaseEntity *pAc entityString = strtok(NULL," "); } } +#endif //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- @@ -7448,7 +8765,7 @@ bool CAI_BaseNPC::InitSquad( void ) { if ( !m_SquadName ) { - DevMsg(2, "Found %s that isn't in a squad\n",GetClassname()); + CGMsg( 2, CON_GROUP_NPC_AI, "Found %s that isn't in a squad\n", GetClassname() ); } else { @@ -7976,6 +9293,24 @@ Activity CAI_BaseNPC::GetReloadActivity( CAI_Hint* pHint ) case HINT_TACTICAL_COVER_LOW: case HINT_TACTICAL_COVER_MED: { +#ifdef MAPBASE + if (HasMemory(bits_MEMORY_INCOVER)) + { + // Don't do that trace thing. INCOVER means we're already crouching. + nReloadActivity = ACT_RELOAD_LOW; + } + else + { + Vector vEyePos = GetAbsOrigin() + GetCrouchEyeOffset(); + // Check if this location will block the threat's line of sight to me + trace_t tr; + AI_TraceLOS(vEyePos, GetEnemy()->EyePosition(), this, &tr); + if (tr.fraction != 1.0) + { + nReloadActivity = ACT_RELOAD_LOW; + } + } +#else if (SelectWeightedSequence( ACT_RELOAD_LOW ) != ACTIVITY_NOT_AVAILABLE) { Vector vEyePos = GetAbsOrigin() + EyeOffset(ACT_RELOAD_LOW); @@ -7987,6 +9322,7 @@ Activity CAI_BaseNPC::GetReloadActivity( CAI_Hint* pHint ) nReloadActivity = ACT_RELOAD_LOW; } } +#endif break; } } @@ -8012,19 +9348,53 @@ Activity CAI_BaseNPC::GetCoverActivity( CAI_Hint *pHint ) { case HINT_TACTICAL_COVER_MED: { +#ifdef MAPBASE + // NPCs which lack ACT_COVER_MED should fall through to HINT_TACTICAL_COVER_LOW + if (SelectWeightedSequence( ACT_COVER_MED ) != ACTIVITY_NOT_AVAILABLE) + { + nCoverActivity = ACT_COVER_MED; + } +#else nCoverActivity = ACT_COVER_MED; break; +#endif } case HINT_TACTICAL_COVER_LOW: { +#ifdef MAPBASE + // Make sure nCoverActivity wasn't already assigned above, then fall through to HINT_TACTICAL_COVER_CUSTOM + if (nCoverActivity == ACT_INVALID) + nCoverActivity = ACT_COVER_LOW; +#else nCoverActivity = ACT_COVER_LOW; break; +#endif } + +#ifdef MAPBASE + case HINT_TACTICAL_COVER_CUSTOM: + { + if (pHint->HintActivityName() != NULL_STRING) + { + nCoverActivity = (Activity)CAI_BaseNPC::GetActivityID( STRING(pHint->HintActivityName()) ); + if (nCoverActivity == ACT_INVALID) + { + m_iszSceneCustomMoveSeq = pHint->HintActivityName(); + nCoverActivity = ACT_SCRIPT_CUSTOM_MOVE; + } + } + break; + } +#endif } } if ( nCoverActivity == ACT_INVALID ) +#ifdef MAPBASE + nCoverActivity = ACT_IDLE; +#else nCoverActivity = ACT_COVER; +#endif return nCoverActivity; } @@ -8055,6 +9425,13 @@ float CAI_BaseNPC::CalcIdealYaw( const Vector &vecTarget ) return UTIL_VecToYaw( vecProjection - GetLocalOrigin() ); } +#ifdef MAPBASE + // Allow hint nodes to override the yaw without needing to control AI + else if (GetHintNode() && GetHintNode()->OverridesNPCYaw( this )) + { + return GetHintNode()->Yaw(); + } +#endif else { return UTIL_VecToYaw ( vecTarget - GetLocalOrigin() ); @@ -8078,7 +9455,7 @@ void CAI_BaseNPC::SetDefaultEyeOffset ( void ) { if ( Classify() != CLASS_NONE ) { - DevMsg( "WARNING: %s(%s) has no eye offset in .qc!\n", GetClassname(), STRING(GetModelName()) ); + CGMsg( 1, CON_GROUP_NPC_AI, "WARNING: %s(%s) has no eye offset in .qc!\n", GetClassname(), STRING( GetModelName() ) ); } VectorAdd( WorldAlignMins(), WorldAlignMaxs(), m_vDefaultEyeOffset ); m_vDefaultEyeOffset *= 0.75; @@ -8186,6 +9563,12 @@ void CAI_BaseNPC::HandleAnimEvent( animevent_t *pEvent ) { m_hCine->FireScriptEvent( atoi( pEvent->options ) ); } +#ifdef MAPBASE + else if ( GetHintNode() ) + { + GetHintNode()->FireScriptEvent( atoi( pEvent->options ) ); + } +#endif else { // FIXME: look so see if it's playing a vcd and fire those instead @@ -8394,8 +9777,12 @@ void CAI_BaseNPC::HandleAnimEvent( animevent_t *pEvent ) { if ( GetActiveWeapon() ) { +#ifdef MAPBASE + GetActiveWeapon()->Reload_NPC( true ); +#else GetActiveWeapon()->WeaponSound( RELOAD_NPC ); GetActiveWeapon()->m_iClip1 = GetActiveWeapon()->GetMaxClip1(); +#endif ClearCondition(COND_LOW_PRIMARY_AMMO); ClearCondition(COND_NO_PRIMARY_AMMO); ClearCondition(COND_NO_SECONDARY_AMMO); @@ -8415,8 +9802,12 @@ void CAI_BaseNPC::HandleAnimEvent( animevent_t *pEvent ) case EVENT_WEAPON_RELOAD_FILL_CLIP: { if ( GetActiveWeapon() ) - { + { +#ifdef MAPBASE + GetActiveWeapon()->Reload_NPC( false ); +#else GetActiveWeapon()->m_iClip1 = GetActiveWeapon()->GetMaxClip1(); +#endif ClearCondition(COND_LOW_PRIMARY_AMMO); ClearCondition(COND_NO_PRIMARY_AMMO); ClearCondition(COND_NO_SECONDARY_AMMO); @@ -8431,7 +9822,11 @@ void CAI_BaseNPC::HandleAnimEvent( animevent_t *pEvent ) case NPC_EVENT_OPEN_DOOR: { +#ifdef MAPBASE + CBasePropDoor *pDoor = m_hOpeningDoor; +#else CBasePropDoor *pDoor = (CBasePropDoor *)(CBaseEntity *)GetNavigator()->GetPath()->GetCurWaypoint()->GetEHandleData(); +#endif if (pDoor != NULL) { OpenPropDoorNow( pDoor ); @@ -8445,6 +9840,11 @@ void CAI_BaseNPC::HandleAnimEvent( animevent_t *pEvent ) { if (pEvent->event == AE_NPC_HOLSTER) { +#ifdef MAPBASE + CBaseCombatWeapon *pWeapon = GetActiveWeapon(); + if (DoHolster()) + m_OnHolsterWeapon.Set(pWeapon, pWeapon, this); +#else // Cache off the weapon. CBaseCombatWeapon *pWeapon = GetActiveWeapon(); @@ -8467,11 +9867,16 @@ void CAI_BaseNPC::HandleAnimEvent( animevent_t *pEvent ) m_iDesiredWeaponState = DESIREDWEAPONSTATE_IGNORE; m_Activity = ACT_RESET; } +#endif return; } else if (pEvent->event == AE_NPC_DRAW) { +#ifdef MAPBASE + if (DoUnholster()) + m_OnUnholsterWeapon.Set(GetActiveWeapon(), GetActiveWeapon(), this); +#else if (GetActiveWeapon()) { GetActiveWeapon()->Deploy(); @@ -8485,6 +9890,7 @@ void CAI_BaseNPC::HandleAnimEvent( animevent_t *pEvent ) m_Activity = ACT_RESET; } } +#endif return; } else if ( pEvent->event == AE_NPC_BODYDROP_HEAVY ) @@ -9565,6 +10971,20 @@ void CAI_BaseNPC::CollectShotStats( const Vector &vecShootOrigin, const Vector & //----------------------------------------------------------------------------- Vector CAI_BaseNPC::GetActualShootPosition( const Vector &shootOrigin ) { +#ifdef MAPBASE_VSCRIPT + if (m_ScriptScope.IsInitialized() && g_Hook_GetActualShootPosition.CanRunInScope(m_ScriptScope)) + { + // shootOrigin, target + ScriptVariant_t functionReturn; + ScriptVariant_t args[] = { shootOrigin, ToHScript( GetEnemy() ) }; + if (g_Hook_GetActualShootPosition.Call( m_ScriptScope, &functionReturn, args )) + { + if (functionReturn.GetType() == FIELD_VECTOR && functionReturn.Get().LengthSqr() != 0.0f) + return functionReturn.Get(); + } + } +#endif + // Project the target's location into the future. Vector vecEnemyLKP = GetEnemyLKP(); Vector vecEnemyOffset = GetEnemy()->BodyTarget( shootOrigin ) - GetEnemy()->GetAbsOrigin(); @@ -9899,6 +11319,7 @@ int CAI_BaseNPC::PlayScriptedSentence( const char *pszSentence, float delay, flo return PlaySentence( pszSentence, delay, volume, soundlevel, pListener ); } +#ifndef MAPBASE // Moved to CBaseCombatCharacter //----------------------------------------------------------------------------- // Purpose: // Input : @@ -9973,6 +11394,7 @@ CBaseEntity *CAI_BaseNPC::FindNamedEntity( const char *name, IEntityFindFilter * return NULL; } +#endif void CAI_BaseNPC::CorpseFallThink( void ) @@ -10272,7 +11694,7 @@ bool CAI_BaseNPC::ChooseEnemy( void ) } else if ( !fHaveCondNewEnemy && !fHaveCondLostEnemy && GetCurSchedule() ) { - DevMsg( 2, "WARNING: AI enemy went NULL but schedule (%s) is not interested\n", GetCurSchedule()->GetName() ); + CGMsg( 2, CON_GROUP_NPC_AI, "WARNING: AI enemy went NULL but schedule (%s) is not interested\n", GetCurSchedule()->GetName() ); } m_bSkippedChooseEnemy = false; @@ -10328,7 +11750,11 @@ bool CAI_BaseNPC::ChooseEnemy( void ) if ( fEnemyEluded ) { SetCondition( COND_LOST_ENEMY ); +#ifdef MAPBASE + LostEnemySound( pInitialEnemy ); +#else LostEnemySound(); +#endif } if ( fEnemyWasPlayer ) @@ -10352,11 +11778,56 @@ bool CAI_BaseNPC::ChooseEnemy( void ) //========================================================= void CAI_BaseNPC::PickupWeapon( CBaseCombatWeapon *pWeapon ) { +#ifdef MAPBASE + if ( pWeapon->VPhysicsGetObject() && pWeapon->VPhysicsGetObject()->GetGameFlags() & FVPHYSICS_PLAYER_HELD ) + { + CBasePlayer *pPlayer = UTIL_GetLocalPlayer(); + pPlayer->ForceDropOfCarriedPhysObjects( pWeapon ); + } +#endif + pWeapon->OnPickedUp( this ); Weapon_Equip( pWeapon ); m_iszPendingWeapon = NULL_STRING; } +#ifdef MAPBASE +extern ConVar sk_healthvial; + +//------------------------------------------------------------------------------ +// Purpose: Moved from CNPC_Citizen to CAI_BaseNPC for new pickup input +//------------------------------------------------------------------------------ +void CAI_BaseNPC::PickupItem( CBaseEntity *pItem ) +{ + // Must cache number of elements in case any fire only once (therefore being removed after being fired) + int iNumElements = m_OnItemPickup.NumberOfElements(); + if (iNumElements > 0) + m_OnItemPickup.Set( pItem, pItem, this ); + + Assert( pItem != NULL ); + if( FClassnameIs( pItem, "item_health*" ) ) // item_healthkit, item_healthvial, item_healthkit_custom, etc. + { + if ( TakeHealth( static_cast(pItem)->GetItemAmount(), DMG_GENERIC ) ) + { + RemoveAllDecals(); + UTIL_Remove( pItem ); + } + } + else if ( FClassnameIs(pItem, "item_ammo_ar2_altfire") || FClassnameIs(pItem, "item_ammo_smg1_grenade") || + FClassnameIs(pItem, "weapon_frag") ) + { + AddGrenades( 1 ); + UTIL_Remove( pItem ); + } + + // Only warn if we didn't have any elements of OnItemPickup + else if (iNumElements <= 0) + { + DevMsg("%s doesn't know how to pick up %s!\n", GetClassname(), pItem->GetClassname() ); + } +} +#endif + //========================================================= // DropItem - dead npc drops named item //========================================================= @@ -10396,6 +11867,10 @@ CBaseEntity *CAI_BaseNPC::DropItem ( const char *pszItemName, Vector vecPos, QAn pItem->ApplyLocalAngularVelocityImpulse( AngularImpulse( 0, random->RandomFloat( 0, 100 ), 0 ) ); } +#ifdef MAPBASE + m_OnItemDrop.Set( pItem, pItem, this ); +#endif + return pItem; } else @@ -10631,6 +12106,9 @@ BEGIN_DATADESC( CAI_BaseNPC ) DEFINE_FIELD( m_iInteractionPlaying, FIELD_INTEGER ), DEFINE_UTLVECTOR(m_ScriptedInteractions,FIELD_EMBEDDED), DEFINE_FIELD( m_flInteractionYaw, FIELD_FLOAT ), +#ifdef MAPBASE + DEFINE_INPUT( m_iDynamicInteractionsAllowed, FIELD_INTEGER, "SetDynamicInteractions" ), +#endif DEFINE_EMBEDDED( m_CheckOnGroundTimer ), DEFINE_FIELD( m_vDefaultEyeOffset, FIELD_VECTOR ), DEFINE_FIELD( m_flNextEyeLookTime, FIELD_TIME ), @@ -10641,7 +12119,11 @@ BEGIN_DATADESC( CAI_BaseNPC ) DEFINE_FIELD( m_flHeadYaw, FIELD_FLOAT ), DEFINE_FIELD( m_flHeadPitch, FIELD_FLOAT ), DEFINE_FIELD( m_flOriginalYaw, FIELD_FLOAT ), +#ifdef MAPBASE + DEFINE_KEYFIELD( m_bInAScript, FIELD_BOOLEAN, "SpawnWithStartScripting" ), +#else DEFINE_FIELD( m_bInAScript, FIELD_BOOLEAN ), +#endif DEFINE_FIELD( m_scriptState, FIELD_INTEGER ), DEFINE_FIELD( m_hCine, FIELD_EHANDLE ), DEFINE_CUSTOM_FIELD( m_ScriptArrivalActivity, ActivityDataOps() ), @@ -10662,6 +12144,9 @@ BEGIN_DATADESC( CAI_BaseNPC ) DEFINE_KEYFIELD( m_bIgnoreUnseenEnemies, FIELD_BOOLEAN , "ignoreunseenenemies"), DEFINE_EMBEDDED( m_ShotRegulator ), DEFINE_FIELD( m_iDesiredWeaponState, FIELD_INTEGER ), +#ifdef MAPBASE + DEFINE_FIELD( m_iLastHolsteredWeapon, FIELD_INTEGER ), +#endif // m_pSquad Saved specially in ai_saverestore.cpp DEFINE_KEYFIELD(m_SquadName, FIELD_STRING, "squadname" ), DEFINE_FIELD( m_iMySquadSlot, FIELD_INTEGER ), @@ -10704,6 +12189,13 @@ BEGIN_DATADESC( CAI_BaseNPC ) DEFINE_FIELD( m_bImportanRagdoll, FIELD_BOOLEAN ), DEFINE_FIELD( m_bPlayerAvoidState, FIELD_BOOLEAN ), +#ifdef MAPBASE + DEFINE_KEYFIELD( m_FriendlyFireOverride, FIELD_INTEGER, "FriendlyFireOverride" ), + + DEFINE_KEYFIELD( m_flSpeedModifier, FIELD_FLOAT, "BaseSpeedModifier" ), + DEFINE_FIELD( m_FakeSequenceGestureLayer, FIELD_INTEGER ), +#endif + // Satisfy classcheck // DEFINE_FIELD( m_ScheduleHistory, CUtlVector < AIScheduleChoice_t > ), @@ -10745,11 +12237,21 @@ BEGIN_DATADESC( CAI_BaseNPC ) DEFINE_OUTPUT( m_OnForcedInteractionStarted, "OnForcedInteractionStarted" ), DEFINE_OUTPUT( m_OnForcedInteractionAborted, "OnForcedInteractionAborted" ), DEFINE_OUTPUT( m_OnForcedInteractionFinished, "OnForcedInteractionFinished" ), +#ifdef MAPBASE + DEFINE_OUTPUT( m_OnItemPickup, "OnItemPickup" ), + DEFINE_OUTPUT( m_OnItemDrop, "OnItemDrop" ), +#endif // Inputs +#ifndef MAPBASE DEFINE_INPUTFUNC( FIELD_STRING, "SetRelationship", InputSetRelationship ), +#endif DEFINE_INPUTFUNC( FIELD_STRING, "SetEnemyFilter", InputSetEnemyFilter ), +#ifdef MAPBASE + DEFINE_INPUTFUNC( FIELD_FLOAT, "SetHealthFraction", InputSetHealthFraction ), +#else DEFINE_INPUTFUNC( FIELD_INTEGER, "SetHealth", InputSetHealth ), +#endif DEFINE_INPUTFUNC( FIELD_VOID, "BeginRappel", InputBeginRappel ), DEFINE_INPUTFUNC( FIELD_STRING, "SetSquad", InputSetSquad ), DEFINE_INPUTFUNC( FIELD_VOID, "Wake", InputWake ), @@ -10766,12 +12268,42 @@ BEGIN_DATADESC( CAI_BaseNPC ) DEFINE_INPUTFUNC( FIELD_VOID, "DisableSpeedModifier", InputDisableSpeedModifier ), DEFINE_INPUTFUNC( FIELD_INTEGER, "SetSpeedModRadius", InputSetSpeedModifierRadius ), DEFINE_INPUTFUNC( FIELD_INTEGER, "SetSpeedModSpeed", InputSetSpeedModifierSpeed ), +#ifndef MAPBASE // Moved to CBaseCombatCharacter DEFINE_INPUTFUNC( FIELD_VOID, "HolsterWeapon", InputHolsterWeapon ), DEFINE_INPUTFUNC( FIELD_VOID, "HolsterAndDestroyWeapon", InputHolsterAndDestroyWeapon ), DEFINE_INPUTFUNC( FIELD_VOID, "UnholsterWeapon", InputUnholsterWeapon ), +#endif DEFINE_INPUTFUNC( FIELD_STRING, "ForceInteractionWithNPC", InputForceInteractionWithNPC ), DEFINE_INPUTFUNC( FIELD_STRING, "UpdateEnemyMemory", InputUpdateEnemyMemory ), +#ifdef MAPBASE + DEFINE_INPUTFUNC( FIELD_INTEGER, "SetFriendlyFire", InputSetFriendlyFire ), + + DEFINE_INPUTFUNC( FIELD_STRING, "GiveWeaponHolstered", InputGiveWeaponHolstered ), + DEFINE_INPUTFUNC( FIELD_STRING, "ChangeWeapon", InputChangeWeapon ), + DEFINE_INPUTFUNC( FIELD_STRING, "PickupWeapon", InputPickupWeapon ), + DEFINE_INPUTFUNC( FIELD_STRING, "PickupItem", InputPickupItem ), + DEFINE_OUTPUT( m_OnHolsterWeapon, "OnHolsterWeapon" ), + DEFINE_OUTPUT( m_OnUnholsterWeapon, "OnUnholsterWeapon" ), + + DEFINE_INPUTFUNC( FIELD_INTEGER, "AddCapabilities", InputAddCapabilities ), + DEFINE_INPUTFUNC( FIELD_INTEGER, "RemoveCapabilities", InputRemoveCapabilities ), + + DEFINE_INPUTFUNC( FIELD_STRING, "SetCondition", InputSetCondition ), + DEFINE_INPUTFUNC( FIELD_STRING, "ClearCondition", InputClearCondition ), + + DEFINE_INPUTFUNC( FIELD_STRING, "SetHintGroup", InputSetHintGroup ), + + DEFINE_INPUTFUNC( FIELD_FLOAT, "SetThinkNPC", InputSetThinkNPC ), + + DEFINE_INPUTFUNC( FIELD_FLOAT, "SetDistLook", InputSetDistLook ), + DEFINE_INPUTFUNC( FIELD_FLOAT, "SetDistTooFar", InputSetDistTooFar ), + + DEFINE_INPUTFUNC( FIELD_FLOAT, "SetSpeedModifier", InputSetSpeedModifier ), + + DEFINE_OUTPUT( m_OnStateChange, "OnStateChange" ), +#endif + // Function pointers DEFINE_USEFUNC( NPCUse ), DEFINE_THINKFUNC( CallNPCThink ), @@ -10780,6 +12312,119 @@ BEGIN_DATADESC( CAI_BaseNPC ) END_DATADESC() +#ifdef MAPBASE_VSCRIPT +BEGIN_ENT_SCRIPTDESC( CAI_BaseNPC, CBaseCombatCharacter, "The base class all NPCs derive from." ) + + DEFINE_SCRIPTFUNC_NAMED( VScriptGetEnemy, "GetEnemy", "Get the NPC's current enemy." ) + DEFINE_SCRIPTFUNC_NAMED( VScriptSetEnemy, "SetEnemy", "Set the NPC's current enemy." ) + DEFINE_SCRIPTFUNC_NAMED( VScriptGetEnemyLKP, "GetEnemyLKP", "Get the last known position of the NPC's current enemy." ) + + DEFINE_SCRIPTFUNC_NAMED( VScriptFindEnemyMemory, "FindEnemyMemory", "Get information about the NPC's current enemy." ) + + DEFINE_SCRIPTFUNC( GetLastAttackTime, "Get the last time the NPC has used an attack (e.g. fired a bullet from a gun)." ) + DEFINE_SCRIPTFUNC( GetLastDamageTime, "Get the last time the NPC has been damaged." ) + DEFINE_SCRIPTFUNC( GetLastPlayerDamageTime, "Get the last time the NPC has been damaged by a player." ) + DEFINE_SCRIPTFUNC( GetLastEnemyTime, "Get the last time the NPC has seen an enemy." ) + + DEFINE_SCRIPTFUNC_NAMED( VScriptGetState, "GetNPCState", "Get the NPC's current state." ) + + DEFINE_SCRIPTFUNC_NAMED( VScriptWake, "Wake", "Awakens the NPC if it is currently asleep." ) + DEFINE_SCRIPTFUNC_NAMED( VScriptSleep, "Sleep", "Puts the NPC into a sleeping state." ) + + DEFINE_SCRIPTFUNC_NAMED( VScriptGetSleepState, "GetSleepState", "Get the NPC's sleep state. (see AISS_ set of constants)" ) + DEFINE_SCRIPTFUNC_NAMED( VScriptSetSleepState, "SetSleepState", "Set the NPC's sleep state. (see AISS_ set of constants)" ) + DEFINE_SCRIPTFUNC( AddSleepFlags, "Add to the NPC's sleep flags. (see AI_SLEEP_ set of constants)" ) + DEFINE_SCRIPTFUNC( RemoveSleepFlags, "Remove from NPC's sleep flags. (see AI_SLEEP_ set of constants)" ) + DEFINE_SCRIPTFUNC( HasSleepFlags, "Return true if the NPC has the specified sleep flags. (see AI_SLEEP_ set of constants)" ) + + DEFINE_SCRIPTFUNC_NAMED( VScriptGetHintGroup, "GetHintGroup", "Get the name of the NPC's hint group." ) + DEFINE_SCRIPTFUNC_NAMED( VScriptGetHintNode, "GetHintNode", "Get the NPC's current AI hint." ) + + DEFINE_SCRIPTFUNC( CapabilitiesGet, "Get the capabilities the NPC currently possesses." ) + DEFINE_SCRIPTFUNC( CapabilitiesAdd, "Add capabilities to the NPC." ) + DEFINE_SCRIPTFUNC( CapabilitiesRemove, "Remove capabilities from the NPC." ) + DEFINE_SCRIPTFUNC( CapabilitiesClear, "Clear capabilities for the NPC." ) + + DEFINE_SCRIPTFUNC_NAMED( ScriptGetActivity, "GetActivity", "Get the NPC's current activity." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptGetActivityID, "GetActivityID", "Get the NPC's current activity ID." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptSetActivity, "SetActivity", "Set the NPC's current activity." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptSetActivityID, "SetActivityID", "Set the NPC's current activity ID." ) + DEFINE_SCRIPTFUNC( ResetActivity, "Reset the NPC's current activity." ) + + DEFINE_SCRIPTFUNC_NAMED( ScriptTranslateActivity, "TranslateActivity", "Translates the specified activity string and returns the translated activity ID." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptTranslateActivityID, "TranslateActivityID", "Translates the specified activity ID and returns the translated activity ID." ) + + DEFINE_SCRIPTFUNC_NAMED( VScriptGetGestureVersionOfActivity, "GetGestureVersionOfActivity", "Get the gesture activity counterpart of the specified sequence activity, if one exists." ) + DEFINE_SCRIPTFUNC_NAMED( VScriptGetGestureVersionOfActivityID, "GetGestureVersionOfActivityID", "Get the gesture activity ID counterpart of the specified sequence activity ID, if one exists." ) + DEFINE_SCRIPTFUNC_NAMED( VScriptGetSequenceVersionOfGesture, "GetSequenceVersionOfGesture", "Get the sequence activity counterpart of the specified gesture activity, if one exists." ) + DEFINE_SCRIPTFUNC_NAMED( VScriptGetSequenceVersionOfGestureID, "GetSequenceVersionOfGestureID", "Get the sequence activity ID counterpart of the specified gesture activity ID, if one exists." ) + + DEFINE_SCRIPTFUNC_NAMED( VScriptGetSchedule, "GetSchedule", "Get the NPC's current schedule." ) + DEFINE_SCRIPTFUNC_NAMED( VScriptGetScheduleID, "GetScheduleID", "Get the NPC's current schedule ID." ) + DEFINE_SCRIPTFUNC_NAMED( VScriptSetSchedule, "SetSchedule", "Set the NPC's current schedule." ) + DEFINE_SCRIPTFUNC_NAMED( VScriptSetScheduleID, "SetScheduleID", "Set the NPC's current schedule ID." ) + DEFINE_SCRIPTFUNC_NAMED( VScriptGetTask, "GetTask", "Get the NPC's current task." ) + DEFINE_SCRIPTFUNC_NAMED( VScriptGetTaskID, "GetTaskID", "Get the NPC's current task ID." ) + DEFINE_SCRIPTFUNC( ClearSchedule, "Clear the NPC's current schedule for the specified reason." ) + + DEFINE_SCRIPTFUNC_NAMED( VScriptHasCondition, "HasCondition", "Get whether the NPC has a condition." ) + DEFINE_SCRIPTFUNC_NAMED( VScriptHasConditionID, "HasConditionID", "Get whether the NPC has a condition ID." ) + DEFINE_SCRIPTFUNC_NAMED( VScriptSetCondition, "SetCondition", "Set a condition on the NPC." ) + DEFINE_SCRIPTFUNC_NAMED( SetCondition, "SetConditionID", "Set a condition on the NPC by ID." ) + DEFINE_SCRIPTFUNC_NAMED( VScriptClearCondition, "ClearCondition", "Clear a condition on the NPC." ) + DEFINE_SCRIPTFUNC_NAMED( ClearCondition, "ClearConditionID", "Clear a condition on the NPC by ID." ) + + DEFINE_SCRIPTFUNC( IsMoving, "Check if the NPC is moving." ) + + DEFINE_SCRIPTFUNC_NAMED( VScriptGetExpresser, "GetExpresser", "Get a handle for this NPC's expresser." ) + + DEFINE_SCRIPTFUNC( IsCommandable, "Check if the NPC is commandable." ) + DEFINE_SCRIPTFUNC( IsInPlayerSquad, "Check if the NPC is in the player's squad." ) + DEFINE_SCRIPTFUNC( IsMedic, "Returns true if this NPC is a medic." ) + + DEFINE_SCRIPTFUNC_NAMED( VScriptGetCine, "GetCine", "Get the NPC's currently running scripted sequence if it has one." ) + DEFINE_SCRIPTFUNC( GetScriptState, "Get the NPC's current scripted sequence state." ) + + DEFINE_SCRIPTFUNC_NAMED( VScriptGetSquad, "GetSquad", "Get the NPC's squad if it has one." ) + DEFINE_SCRIPTFUNC( IsInSquad, "Returns true if the NPC is in a squad." ) + DEFINE_SCRIPTFUNC( NumWeaponsInSquad, "Get the number of weapons in a squad." ) + + DEFINE_SCRIPTFUNC( IsCrouching, "Returns true if the NPC is crouching." ) + DEFINE_SCRIPTFUNC( Crouch, "Tells the NPC to crouch." ) + DEFINE_SCRIPTFUNC( Stand, "Tells the NPC to stand if it is crouching." ) + + // + // Hooks + // + BEGIN_SCRIPTHOOK( CAI_BaseNPC::g_Hook_QueryHearSound, "QueryHearSound", FIELD_BOOLEAN, "Called when the NPC is deciding whether to hear a CSound or not." ) + DEFINE_SCRIPTHOOK_PARAM( "sound", FIELD_HSCRIPT ) + END_SCRIPTHOOK() + BEGIN_SCRIPTHOOK( CAI_BaseNPC::g_Hook_QuerySeeEntity, "QuerySeeEntity", FIELD_BOOLEAN, "Called when the NPC is deciding whether to see an entity or not." ) + DEFINE_SCRIPTHOOK_PARAM( "entity", FIELD_HSCRIPT ) + END_SCRIPTHOOK() + BEGIN_SCRIPTHOOK( CAI_BaseNPC::g_Hook_TranslateActivity, "NPC_TranslateActivity", FIELD_VARIANT, "Called when the NPC is translating their current activity. The activity is provided in both string and ID form. Should return either an activity string or an activity ID. Return -1 to not translate." ) + DEFINE_SCRIPTHOOK_PARAM( "activity", FIELD_CSTRING ) + DEFINE_SCRIPTHOOK_PARAM( "activity_id", FIELD_INTEGER ) + END_SCRIPTHOOK() + BEGIN_SCRIPTHOOK( CAI_BaseNPC::g_Hook_TranslateSchedule, "NPC_TranslateSchedule", FIELD_VARIANT, "Called when the NPC is translating their current schedule. The schedule is provided in both string and ID form. Should return either a schedule string or a schedule ID. Return -1 to not translate." ) + DEFINE_SCRIPTHOOK_PARAM( "schedule", FIELD_CSTRING ) + DEFINE_SCRIPTHOOK_PARAM( "schedule_id", FIELD_INTEGER ) + END_SCRIPTHOOK() + BEGIN_SCRIPTHOOK( CAI_BaseNPC::g_Hook_GetActualShootPosition, "GetActualShootPosition", FIELD_VECTOR, "Called when the NPC is getting their actual shoot position, using the default shoot position as the parameter. (NOTE: NPCs which override this themselves might not always use this hook!)" ) + DEFINE_SCRIPTHOOK_PARAM( "shootOrigin", FIELD_VECTOR ) + DEFINE_SCRIPTHOOK_PARAM( "target", FIELD_HSCRIPT ) + END_SCRIPTHOOK() + BEGIN_SCRIPTHOOK( CAI_BaseNPC::g_Hook_OverrideMove, "OverrideMove", FIELD_VOID, "Called when the NPC runs movement code, allowing the NPC's movement to be overridden by some other method. (NOTE: NPCs which override this themselves might not always use this hook!)" ) + DEFINE_SCRIPTHOOK_PARAM( "interval", FIELD_FLOAT ) + END_SCRIPTHOOK() + BEGIN_SCRIPTHOOK( CAI_BaseNPC::g_Hook_ShouldPlayFakeSequenceGesture, "ShouldPlayFakeSequenceGesture", FIELD_BOOLEAN, "Called when an activity is set on a NPC. Returning true will make the NPC convert the activity into a gesture (if a gesture is available) and continue their current activity instead." ) + DEFINE_SCRIPTHOOK_PARAM( "activity", FIELD_CSTRING ) + DEFINE_SCRIPTHOOK_PARAM( "translatedActivity", FIELD_CSTRING ) + END_SCRIPTHOOK() + +END_SCRIPTDESC(); +#endif + BEGIN_SIMPLE_DATADESC( AIScheduleState_t ) DEFINE_FIELD( iCurTask, FIELD_INTEGER ), DEFINE_FIELD( fTaskStatus, FIELD_INTEGER ), @@ -10834,6 +12479,9 @@ BEGIN_SIMPLE_DATADESC( ScriptedNPCInteraction_t ) DEFINE_FIELD( vecRelativeOrigin, FIELD_VECTOR ), DEFINE_FIELD( angRelativeAngles, FIELD_VECTOR ), DEFINE_FIELD( vecRelativeVelocity, FIELD_VECTOR ), +#ifdef MAPBASE + DEFINE_FIELD( vecRelativeEndPos, FIELD_VECTOR ), +#endif DEFINE_FIELD( flDelay, FIELD_FLOAT ), DEFINE_FIELD( flDistSqr, FIELD_FLOAT ), DEFINE_FIELD( iszMyWeapon, FIELD_STRING ), @@ -10842,6 +12490,13 @@ BEGIN_SIMPLE_DATADESC( ScriptedNPCInteraction_t ) DEFINE_FIELD( matDesiredLocalToWorld, FIELD_VMATRIX ), DEFINE_FIELD( bValidOnCurrentEnemy, FIELD_BOOLEAN ), DEFINE_FIELD( flNextAttemptTime, FIELD_TIME ), +#ifdef MAPBASE + DEFINE_EMBEDDED_ARRAY( sTheirPhases, SNPCINT_NUM_PHASES ), + DEFINE_FIELD( bHasSeparateSequenceNames, FIELD_BOOLEAN ), + DEFINE_FIELD( flMaxAngleDiff, FIELD_FLOAT ), + DEFINE_FIELD( iszRelatedInteractions, FIELD_STRING ), + DEFINE_FIELD( MiscCriteria, FIELD_STRING ), +#endif END_DATADESC() //------------------------------------- @@ -11069,7 +12724,7 @@ void CAI_BaseNPC::DiscardScheduleState() // for now, just go back to idle and let the AI figure out what to do. SetState( NPC_STATE_IDLE ); SetIdealState( NPC_STATE_IDLE ); - DevMsg(1, "Scripted Sequence stripped on level transition for %s\n", GetDebugName() ); + CGMsg( 1, CON_GROUP_NPC_SCRIPTS, "Scripted Sequence stripped on level transition for %s\n", GetDebugName() ); } } @@ -11254,6 +12909,36 @@ bool CAI_BaseNPC::KeyValue( const char *szKeyName, const char *szValue ) return bResult; } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +bool CAI_BaseNPC::GetKeyValue( const char *szKeyName, char *szValue, int iMaxLen ) +{ + // Why was GetKeyValue("squadname") pointless again? + /* + if (FStrEq(szKeyName, "squadname")) + { + // "squadname" does change initially and in InputSetSquad(), but not in SetSquad() itself. + // Probably not in any other function that changes the squad either. This is needed so we get + // an accurate reading of the squad name. + GetSquad() ? + Q_strncpy(szValue, GetSquad()->GetName(), iMaxLen) : + Q_strncpy(szValue, "", iMaxLen); + } + else*/ if (FStrEq(szKeyName, "additionalequipment")) + { + if (GetActiveWeapon()) + Q_strncpy(szValue, GetActiveWeapon()->GetClassname(), iMaxLen); + else + return false; + } + else + return BaseClass::GetKeyValue(szKeyName, szValue, iMaxLen); + + return true; +} +#endif + //----------------------------------------------------------------------------- // Purpose: Debug function to make this NPC freeze in place (or unfreeze). //----------------------------------------------------------------------------- @@ -11386,6 +13071,13 @@ CAI_BaseNPC::CAI_BaseNPC(void) m_bInChoreo = true; // assume so until call to UpdateEfficiency() SetCollisionGroup( COLLISION_GROUP_NPC ); + +#ifdef MAPBASE + m_iDynamicInteractionsAllowed = TRS_NONE; + m_flSpeedModifier = 1.0f; + + m_FakeSequenceGestureLayer = -1; +#endif } //----------------------------------------------------------------------------- @@ -11418,7 +13110,7 @@ void CAI_BaseNPC::UpdateOnRemove(void) if ( !m_bDidDeathCleanup ) { if ( m_NPCState == NPC_STATE_DEAD ) - DevMsg( "May not have cleaned up on NPC death\n"); + CGMsg( 1, CON_GROUP_NPC_AI, "May not have cleaned up on NPC death\n" ); CleanupOnDeath( NULL, false ); } @@ -11533,6 +13225,7 @@ CAI_Pathfinder *CAI_BaseNPC::CreatePathfinder() return new CAI_Pathfinder( this ); } +#ifndef MAPBASE //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- @@ -11540,6 +13233,7 @@ void CAI_BaseNPC::InputSetRelationship( inputdata_t &inputdata ) { AddRelationship( inputdata.value.String(), inputdata.pActivator ); } +#endif //----------------------------------------------------------------------------- @@ -11552,6 +13246,36 @@ void CAI_BaseNPC::InputSetEnemyFilter( inputdata_t &inputdata ) m_hEnemyFilter = dynamic_cast(pFilter); } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CAI_BaseNPC::InputSetHealthFraction( inputdata_t &inputdata ) +{ + // npc_helicopter uses an identically named input and scales down whole numbers instead of using fractions directly. + // This function is overridden by npc_helicopter for other reasons, but support for its differing behavior is also available through this input. + float flFactor = inputdata.value.Float(); + if ( flFactor > 1.0f ) + { + flFactor *= 0.01f; + } + + float flNewHealth = (GetMaxHealth() * flFactor); + int iNewHealth = (int)flNewHealth; + if (flNewHealth < (GetMaxHealth() / 2)) + iNewHealth = (int)ceil(flNewHealth); + + int iDelta = abs(GetHealth() - iNewHealth); + if ( iNewHealth > GetHealth() ) + { + TakeHealth( iDelta, DMG_GENERIC ); + } + else if ( iNewHealth < GetHealth() ) + { + TakeDamage( CTakeDamageInfo( this, this, iDelta, DMG_GENERIC ) ); + } +} +#else //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- @@ -11568,6 +13292,7 @@ void CAI_BaseNPC::InputSetHealth( inputdata_t &inputdata ) TakeDamage( CTakeDamageInfo( this, this, iDelta, DMG_GENERIC ) ); } } +#endif //----------------------------------------------------------------------------- // Purpose: @@ -11610,7 +13335,11 @@ void CAI_BaseNPC::InputSetSquad( inputdata_t &inputdata ) //----------------------------------------------------------------------------- void CAI_BaseNPC::InputWake( inputdata_t &inputdata ) { +#ifdef MAPBASE + Wake( inputdata.pActivator ); +#else Wake(); +#endif // Check if we have a path to follow. This is normally done in StartNPC, // but putting the NPC to sleep will cancel it, so we have to do it again. @@ -11637,10 +13366,16 @@ void CAI_BaseNPC::InputForgetEntity( inputdata_t &inputdata ) { const char *pszEntityToForget = inputdata.value.String(); +#ifndef MAPBASE // I would assume this was here for a reason, but wildcards have nothing to do with ClearMemory(), so I really have no idea. if ( g_pDeveloper->GetInt() && pszEntityToForget[strlen( pszEntityToForget ) - 1] == '*' ) DevMsg( "InputForgetEntity does not support wildcards\n" ); +#endif +#ifdef MAPBASE + CBaseEntity *pEntity = gEntList.FindEntityGeneric( NULL, pszEntityToForget, this, inputdata.pActivator, inputdata.pCaller ); +#else CBaseEntity *pEntity = gEntList.FindEntityByName( NULL, pszEntityToForget ); +#endif if ( pEntity ) { if ( GetEnemy() == pEntity ) @@ -11672,7 +13407,11 @@ void CAI_BaseNPC::InputIgnoreDangerSounds( inputdata_t &inputdata ) void CAI_BaseNPC::InputUpdateEnemyMemory( inputdata_t &inputdata ) { const char *pszEnemy = inputdata.value.String(); +#ifdef MAPBASE + CBaseEntity *pEnemy = gEntList.FindEntityByName( NULL, pszEnemy, this, inputdata.pActivator, inputdata.pCaller ); +#else CBaseEntity *pEnemy = gEntList.FindEntityByName( NULL, pszEnemy ); +#endif if( pEnemy ) { @@ -11812,10 +13551,18 @@ bool CAI_BaseNPC::CineCleanup() if ( m_hForcedInteractionPartner ) { // We've finished a forced interaction. Let the mapmaker know. +#ifdef MAPBASE + m_OnForcedInteractionFinished.FireOutput( m_hInteractionPartner, this ); +#else m_OnForcedInteractionFinished.FireOutput( this, this ); +#endif } // Clear interaction partner, because we're not running a scripted sequence anymore +#ifdef MAPBASE + // We need the interaction partner for server ragdoll death cleanup, so don't clear if we're not alive + if (IsAlive()) +#endif m_hInteractionPartner = NULL; CleanupForcedInteraction(); } @@ -11987,6 +13734,10 @@ void CAI_BaseNPC::Teleport( const Vector *newPosition, const QAngle *newAngles, CleanupScriptsOnTeleport( false ); BaseClass::Teleport( newPosition, newAngles, newVelocity ); + +#ifdef MAPBASE // From Alien Swarm SDK + CheckPVSCondition(); +#endif } //----------------------------------------------------------------------------- @@ -12110,6 +13861,20 @@ bool CAI_BaseNPC::OverrideMoveFacing( const AILocalMoveGoal_t &move, float flInt bool CAI_BaseNPC::OverrideMove( float flInterval ) { +#ifdef MAPBASE_VSCRIPT + if (m_ScriptScope.IsInitialized() && g_Hook_OverrideMove.CanRunInScope(m_ScriptScope)) + { + // interval + ScriptVariant_t functionReturn; + ScriptVariant_t args[] = { flInterval }; + if (g_Hook_OverrideMove.Call( m_ScriptScope, &functionReturn, args )) + { + if (functionReturn.GetType() == FIELD_BOOLEAN) + return functionReturn.Get(); + } + } +#endif + return false; } @@ -12141,7 +13906,19 @@ bool CAI_BaseNPC::OnCalcBaseMove( AILocalMoveGoal_t *pMoveGoal, { if ( pMoveGoal->directTrace.pObstruction ) { +#ifdef MAPBASE + CBaseEntity *pObstruction = pMoveGoal->directTrace.pObstruction; + CBasePropDoor *pPropDoor = dynamic_cast( pObstruction ); + + // It could be another entity (e.g. a func_brush) parented to a blank door + if (!pPropDoor && pObstruction->GetParent()) + { + pObstruction = pObstruction->GetParent(); + pPropDoor = dynamic_cast( pObstruction ); + } +#else CBasePropDoor *pPropDoor = dynamic_cast( pMoveGoal->directTrace.pObstruction ); +#endif if ( pPropDoor && OnUpcomingPropDoor( pMoveGoal, pPropDoor, distClear, pResult ) ) { return true; @@ -12233,7 +14010,11 @@ bool CAI_BaseNPC::OnUpcomingPropDoor( AILocalMoveGoal_t *pMoveGoal, m_hOpeningDoor = NULL; } +#ifdef MAPBASE + if ((CapabilitiesGet() & bits_CAP_DOORS_GROUP) && !pDoor->IsDoorLocked() && (pDoor->IsDoorClosed() || pDoor->IsDoorClosing()) && pDoor->PassesDoorFilter(this)) +#else if ((CapabilitiesGet() & bits_CAP_DOORS_GROUP) && !pDoor->IsDoorLocked() && (pDoor->IsDoorClosed() || pDoor->IsDoorClosing())) +#endif { AI_Waypoint_t *pOpenDoorRoute = NULL; @@ -12265,6 +14046,10 @@ bool CAI_BaseNPC::OnUpcomingPropDoor( AILocalMoveGoal_t *pMoveGoal, GetNavigator()->GetPath()->PrependWaypoints( pOpenDoorRoute ); +#ifdef MAPBASE + GetNavigator()->SetArrivalDirection( opendata.vecFaceDir ); +#endif + m_hOpeningDoor = pDoor; pMoveGoal->maxDist = distClear; *pResult = AIMR_CHANGE_TYPE; @@ -12285,6 +14070,21 @@ bool CAI_BaseNPC::OnUpcomingPropDoor( AILocalMoveGoal_t *pMoveGoal, //----------------------------------------------------------------------------- void CAI_BaseNPC::OpenPropDoorBegin( CBasePropDoor *pDoor ) { +#ifdef MAPBASE + opendata_t opendata; + pDoor->GetNPCOpenData(this, opendata); + + if (HaveSequenceForActivity( opendata.eActivity )) + { + int iLayer = AddGesture( opendata.eActivity ); + float flDuration = GetLayerDuration( iLayer ); + + // Face the door and wait for the activity to finish before trying to move through the doorway. + m_flMoveWaitFinished = gpGlobals->curtime + flDuration + pDoor->GetOpenInterval(); + AddFacingTarget( opendata.vecFaceDir, 1.0, flDuration ); + } + else +#else // dvs: not quite working, disabled for now. //opendata_t opendata; //pDoor->GetNPCOpenData(this, opendata); @@ -12294,6 +14094,7 @@ void CAI_BaseNPC::OpenPropDoorBegin( CBasePropDoor *pDoor ) // SetIdealActivity(opendata.eActivity); //} //else +#endif { // We don't have an appropriate sequence, just open the door magically. OpenPropDoorNow( pDoor ); @@ -12313,6 +14114,15 @@ void CAI_BaseNPC::OpenPropDoorNow( CBasePropDoor *pDoor ) // Wait for the door to finish opening before trying to move through the doorway. m_flMoveWaitFinished = gpGlobals->curtime + pDoor->GetOpenInterval(); + +#ifdef MAPBASE + // Remove the door from our waypoint + if (GetNavigator()->GetPath() && GetNavigator()->GetCurWaypointFlags() & bits_WP_TO_DOOR) + { + GetNavigator()->GetPath()->GetCurWaypoint()->ModifyFlags( bits_WP_TO_DOOR, false ); + GetNavigator()->GetPath()->GetCurWaypoint()->m_hData = NULL; + } +#endif } @@ -12483,7 +14293,7 @@ static void AIMsgGuts( CAI_BaseNPC *pAI, unsigned flags, const char *pszMsg ) else pszFmt2 = "%s (%s: %d/%s) [%d]"; - DevMsg( pszFmt2, + CGMsg( 1, CON_GROUP_NPC_AI, pszFmt2, pszMsg, pAI->GetClassname(), pAI->entindex(), @@ -12680,7 +14490,11 @@ void CAI_BaseNPC::TestPlayerPushing( CBaseEntity *pEntity ) // Heuristic for determining if the player is pushing me away CBasePlayer *pPlayer = ToBasePlayer( pEntity ); +#ifdef MAPBASE + if ( pPlayer && !( pPlayer->GetFlags() & FL_NOTARGET ) && IRelationType( pPlayer ) > D_FR ) +#else if ( pPlayer && !( pPlayer->GetFlags() & FL_NOTARGET ) ) +#endif { if ( (pPlayer->m_nButtons & (IN_FORWARD|IN_BACK|IN_MOVELEFT|IN_MOVERIGHT)) || pPlayer->GetAbsVelocity().AsVector2D().LengthSqr() > 50*50 ) @@ -12922,6 +14736,34 @@ void CAI_BaseNPC::InputSetSpeedModifierSpeed( inputdata_t &inputdata ) m_iSpeedModSpeed = inputdata.value.Int(); } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: Get movement speed, multipled by modifier +//----------------------------------------------------------------------------- +float CAI_BaseNPC::GetSequenceGroundSpeed( CStudioHdr *pStudioHdr, int iSequence ) +{ + float t = SequenceDuration( pStudioHdr, iSequence ); + + if (t > 0) + { + return (GetSequenceMoveDist( pStudioHdr, iSequence ) * m_flSpeedModifier / t); + } + else + { + return 0; + } +} + +//----------------------------------------------------------------------------- +// Purpose: Hammer input to change the speed of the NPC (based on 1upD's npc_shadow_walker code) +// Not to be confused with the inputs above +//----------------------------------------------------------------------------- +void CAI_BaseNPC::InputSetSpeedModifier( inputdata_t &inputdata ) +{ + this->m_flSpeedModifier = inputdata.value.Float(); +} +#endif + //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- @@ -12937,161 +14779,412 @@ bool CAI_BaseNPC::IsAllowedToDodge( void ) //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- -void CAI_BaseNPC::ParseScriptedNPCInteractions( void ) +void CAI_BaseNPC::ParseScriptedNPCInteractions(void) { // Already parsed them? - if ( m_ScriptedInteractions.Count() ) + if (m_ScriptedInteractions.Count()) return; // Parse the model's key values and find any dynamic interactions - KeyValues *modelKeyValues = new KeyValues(""); - CUtlBuffer buf( 1024, 0, CUtlBuffer::TEXT_BUFFER ); + KeyValues* modelKeyValues = new KeyValues(""); + CUtlBuffer buf(1024, 0, CUtlBuffer::TEXT_BUFFER); - if (! modelinfo->GetModelKeyValue( GetModel(), buf )) + if (!modelinfo->GetModelKeyValue(GetModel(), buf)) return; - - if ( modelKeyValues->LoadFromBuffer( modelinfo->GetModelName( GetModel() ), buf ) ) + + if (modelKeyValues->LoadFromBuffer(modelinfo->GetModelName(GetModel()), buf)) { - // Do we have a dynamic interactions section? - KeyValues *pkvInteractions = modelKeyValues->FindKey("dynamic_interactions"); - if ( pkvInteractions ) +#ifdef MAPBASE + CUtlVector iszUsedNames; + for (KeyValues* pkvModelBlock = modelKeyValues; pkvModelBlock != nullptr; pkvModelBlock = pkvModelBlock->GetNextKey()) { - KeyValues *pkvNode = pkvInteractions->GetFirstSubKey(); - while ( pkvNode ) + // Do we have a dynamic interactions section? + KeyValues* pkvInteractions = pkvModelBlock->FindKey("dynamic_interactions"); + if (pkvInteractions) + { + KeyValues* pkvNode = pkvInteractions->GetFirstSubKey(); + while (pkvNode) + { + ScriptedNPCInteraction_t sInteraction; + sInteraction.iszInteractionName = AllocPooledString(pkvNode->GetName()); + + if (iszUsedNames.Find(sInteraction.iszInteractionName) != iszUsedNames.InvalidIndex()) + { + DevMsg(2, "Scripted interaction %s already defined on %s\n", pkvNode->GetName(), GetClassname()); + pkvNode = pkvNode->GetNextKey(); + continue; + } + + // The method for parsing dynamic interactions has been reworked. + // Unknown values are now stored as response contexts to test against response criteria. + + bool bValidInteraction = true; + + // Default values + UTIL_StringToVector(sInteraction.vecRelativeOrigin.Base(), "0 0 0"); + sInteraction.flDelay = 10.0; + sInteraction.flDistSqr = (DSS_MAX_DIST * DSS_MAX_DIST); + + // Misc. response criteria + char* szCriteria = ""; + + KeyValues* pCurNode = pkvNode->GetFirstSubKey(); + const char* szName = NULL; + const char* szValue = NULL; + while (pCurNode) + { + szName = pCurNode->GetName(); + szValue = pCurNode->GetString(); + + if (!szName || !szValue) + { + DevWarning("ERROR: Invalid dynamic interaction string\n"); + pCurNode = pCurNode->GetNextKey(); + } + + if (!Q_strncmp(szName, "classname", 9)) + { + bool pass = false; + if (Q_strstr(szValue, "!=")) + { + szValue += 2; + pass = true; + } + + if (!FStrEq(GetClassname(), szValue)) + pass = !pass; + } + else if (!Q_strncmp(szName, "mapbase", 7)) + { + sInteraction.iFlags |= SCNPC_FLAG_MAPBASE_ADDITION; + } + else if (!Q_strncmp(szName, "trigger", 7)) + { + if (!Q_strncmp(szValue, "auto_in_combat", 14)) + sInteraction.iTriggerMethod = SNPCINT_AUTOMATIC_IN_COMBAT; + } + else if (!Q_strncmp(szValue, "loop_break_trigger", 18)) + { + char szTrigger[256]; + Q_strncpy(szTrigger, szValue, sizeof(szTrigger)); + char* pszParam = strtok(szTrigger, " "); + while (pszParam) + { + if (!Q_strncmp(pszParam, "on_damage", 9)) + { + sInteraction.iLoopBreakTriggerMethod |= SNPCINT_LOOPBREAK_ON_DAMAGE; + } + else if (!Q_strncmp(pszParam, "on_flashlight_illum", 19)) + { + sInteraction.iLoopBreakTriggerMethod |= SNPCINT_LOOPBREAK_ON_FLASHLIGHT_ILLUM; + } + + pszParam = strtok(NULL, " "); + } + } + else if (!Q_strncmp(szName, "origin_relative", 15)) + UTIL_StringToVector(sInteraction.vecRelativeOrigin.Base(), szValue); + else if (!Q_strncmp(szName, "angles_relative", 15)) + { + sInteraction.iFlags |= SCNPC_FLAG_TEST_OTHER_ANGLES; + UTIL_StringToVector(sInteraction.angRelativeAngles.Base(), szValue); + } + else if (!Q_strncmp(szName, "velocity_relative", 17)) + { + sInteraction.iFlags |= SCNPC_FLAG_TEST_OTHER_VELOCITY; + UTIL_StringToVector(sInteraction.vecRelativeVelocity.Base(), szValue); + } + else if (!Q_strncmp(szName, "end_position", 12)) + { + sInteraction.iFlags |= SCNPC_FLAG_TEST_END_POSITION; + UTIL_StringToVector(sInteraction.vecRelativeEndPos.Base(), szValue); + } + + else if (!Q_strncmp(szName, "entry_sequence", 14)) + sInteraction.sPhases[SNPCINT_ENTRY].iszSequence = AllocPooledString(szValue); + else if (!Q_strncmp(szName, "entry_activity", 14)) + sInteraction.sPhases[SNPCINT_ENTRY].iActivity = GetOrRegisterActivity(szValue); + + else if (!Q_strncmp(szName, "sequence", 8)) + sInteraction.sPhases[SNPCINT_SEQUENCE].iszSequence = AllocPooledString(szValue); + else if (!Q_strncmp(szName, "activity", 8)) + sInteraction.sPhases[SNPCINT_SEQUENCE].iActivity = GetOrRegisterActivity(szValue); + + else if (!Q_strncmp(szName, "exit_sequence", 13)) + sInteraction.sPhases[SNPCINT_EXIT].iszSequence = AllocPooledString(szValue); + else if (!Q_strncmp(szName, "exit_activity", 13)) + sInteraction.sPhases[SNPCINT_EXIT].iActivity = GetOrRegisterActivity(szValue); + + else if (!Q_strncmp(szName, "their_", 6)) + { + const char *szTheirName = szName + 6; + + if (!Q_strncmp(szTheirName, "entry_sequence", 14)) + { + sInteraction.bHasSeparateSequenceNames = true; + sInteraction.sTheirPhases[SNPCINT_ENTRY].iszSequence = AllocPooledString(szValue); + } + else if (!Q_strncmp(szTheirName, "entry_activity", 14)) + { + sInteraction.bHasSeparateSequenceNames = true; + sInteraction.sTheirPhases[SNPCINT_ENTRY].iActivity = GetOrRegisterActivity(szValue); + } + + else if (!Q_strncmp(szTheirName, "sequence", 8)) + { + sInteraction.bHasSeparateSequenceNames = true; + sInteraction.sTheirPhases[SNPCINT_SEQUENCE].iszSequence = AllocPooledString(szValue); + } + else if (!Q_strncmp(szTheirName, "activity", 8)) + { + sInteraction.bHasSeparateSequenceNames = true; + sInteraction.sTheirPhases[SNPCINT_SEQUENCE].iActivity = GetOrRegisterActivity(szValue); + } + + else if (!Q_strncmp(szTheirName, "exit_sequence", 13)) + { + sInteraction.bHasSeparateSequenceNames = true; + sInteraction.sTheirPhases[SNPCINT_EXIT].iszSequence = AllocPooledString(szValue); + } + else if (!Q_strncmp(szTheirName, "exit_activity", 13)) + { + sInteraction.bHasSeparateSequenceNames = true; + sInteraction.sTheirPhases[SNPCINT_EXIT].iActivity = GetOrRegisterActivity(szValue); + } + + // Add anything else to our miscellaneous criteria + else + { + szCriteria = const_cast(UTIL_VarArgs("%s,%s:%s", szCriteria, szName, szValue)); + } + } + + else if (!Q_strncmp(szName, "delay", 5)) + sInteraction.flDelay = atof(szValue); + else if (!Q_strncmp(szName, "origin_max_delta", 16)) + sInteraction.flDistSqr = atof(szValue); + else if (!Q_strncmp(szName, "angles_max_diff", 15)) + sInteraction.flMaxAngleDiff = atof(szValue); + + else if (!Q_strncmp(szName, "loop_in_action", 14) && !FStrEq(szValue, "0")) + sInteraction.iFlags |= SCNPC_FLAG_LOOP_IN_ACTION; + + else if (!Q_strncmp(szName, "dont_teleport_at_end", 20)) + { + if (!Q_stricmp(szValue, "me")) + sInteraction.iFlags |= SCNPC_FLAG_DONT_TELEPORT_AT_END_ME; + else if (!Q_stricmp(szValue, "them")) + sInteraction.iFlags |= SCNPC_FLAG_DONT_TELEPORT_AT_END_THEM; + else if (!Q_stricmp( szValue, "both" )) + { + sInteraction.iFlags |= SCNPC_FLAG_DONT_TELEPORT_AT_END_ME; + sInteraction.iFlags |= SCNPC_FLAG_DONT_TELEPORT_AT_END_THEM; + } + } + + else if (!Q_strncmp(szName, "needs_weapon", 12)) + { + if (!Q_strncmp(szValue, "ME", 2)) + sInteraction.iFlags |= SCNPC_FLAG_NEEDS_WEAPON_ME; + else if (!Q_strncmp(szValue, "THEM", 4)) + sInteraction.iFlags |= SCNPC_FLAG_NEEDS_WEAPON_THEM; + else if (!Q_strncmp(szValue, "BOTH", 4)) + { + sInteraction.iFlags |= SCNPC_FLAG_NEEDS_WEAPON_ME; + sInteraction.iFlags |= SCNPC_FLAG_NEEDS_WEAPON_THEM; + } + } + + else if (!Q_strncmp(szName, "weapon_mine", 11)) + { + sInteraction.iFlags |= SCNPC_FLAG_NEEDS_WEAPON_ME; + sInteraction.iszMyWeapon = AllocPooledString(szValue); + } + else if (!Q_strncmp(szName, "weapon_theirs", 13)) + { + sInteraction.iFlags |= SCNPC_FLAG_NEEDS_WEAPON_THEM; + sInteraction.iszTheirWeapon = AllocPooledString(szValue); + } + + else if (!Q_strncmp(szName, "related_interactions", 20)) + { + sInteraction.iszRelatedInteractions = AllocPooledString(szValue); + } + + // Add anything else to our miscellaneous criteria + else + { + szCriteria = const_cast(UTIL_VarArgs("%s,%s:%s", szCriteria, szName, szValue)); + } + + pCurNode = pCurNode->GetNextKey(); + } + + if (!bValidInteraction) + { + DevMsg("Scripted interaction %s rejected by %s\n", pkvNode->GetName(), GetClassname()); + pkvNode = pkvNode->GetNextKey(); + continue; + } + + if (szCriteria[0] == ',') + { + szCriteria += 1; + sInteraction.MiscCriteria = AllocPooledString(szCriteria); + } + + + // Add it to the list + AddScriptedNPCInteraction(&sInteraction); + iszUsedNames.AddToTail(sInteraction.iszInteractionName); + + // Move to next interaction + pkvNode = pkvNode->GetNextKey(); + } + } + } +#else +// Do we have a dynamic interactions section? + KeyValues* pkvInteractions = modelKeyValues->FindKey("dynamic_interactions"); + if (pkvInteractions) + { + KeyValues* pkvNode = pkvInteractions->GetFirstSubKey(); + while (pkvNode) { ScriptedNPCInteraction_t sInteraction; - sInteraction.iszInteractionName = AllocPooledString( pkvNode->GetName() ); + sInteraction.iszInteractionName = AllocPooledString(pkvNode->GetName()); + // Trigger method - const char *pszTrigger = pkvNode->GetString( "trigger", NULL ); - if ( pszTrigger ) + const char* pszTrigger = pkvNode->GetString("trigger", NULL); + if (pszTrigger) { - if ( !Q_strncmp( pszTrigger, "auto_in_combat", 14) ) + if (!Q_strncmp(pszTrigger, "auto_in_combat", 14)) { sInteraction.iTriggerMethod = SNPCINT_AUTOMATIC_IN_COMBAT; } } // Loop Break trigger method - pszTrigger = pkvNode->GetString( "loop_break_trigger", NULL ); - if ( pszTrigger ) + pszTrigger = pkvNode->GetString("loop_break_trigger", NULL); + if (pszTrigger) { char szTrigger[256]; - Q_strncpy( szTrigger, pszTrigger, sizeof(szTrigger) ); - char *pszParam = strtok( szTrigger, " " ); + Q_strncpy(szTrigger, pszTrigger, sizeof(szTrigger)); + char* pszParam = strtok(szTrigger, " "); while (pszParam) { - if ( !Q_strncmp( pszParam, "on_damage", 9) ) + if (!Q_strncmp(pszParam, "on_damage", 9)) { sInteraction.iLoopBreakTriggerMethod |= SNPCINT_LOOPBREAK_ON_DAMAGE; } - if ( !Q_strncmp( pszParam, "on_flashlight_illum", 19) ) + if (!Q_strncmp(pszParam, "on_flashlight_illum", 19)) { sInteraction.iLoopBreakTriggerMethod |= SNPCINT_LOOPBREAK_ON_FLASHLIGHT_ILLUM; } - pszParam = strtok(NULL," "); + pszParam = strtok(NULL, " "); } } // Origin - const char *pszOrigin = pkvNode->GetString( "origin_relative", "0 0 0" ); - UTIL_StringToVector( sInteraction.vecRelativeOrigin.Base(), pszOrigin ); + const char* pszOrigin = pkvNode->GetString("origin_relative", "0 0 0"); + UTIL_StringToVector(sInteraction.vecRelativeOrigin.Base(), pszOrigin); // Angles - const char *pszAngles = pkvNode->GetString( "angles_relative", NULL ); - if ( pszAngles ) + const char* pszAngles = pkvNode->GetString("angles_relative", NULL); + if (pszAngles) { sInteraction.iFlags |= SCNPC_FLAG_TEST_OTHER_ANGLES; - UTIL_StringToVector( sInteraction.angRelativeAngles.Base(), pszAngles ); + UTIL_StringToVector(sInteraction.angRelativeAngles.Base(), pszAngles); } // Velocity - const char *pszVelocity = pkvNode->GetString( "velocity_relative", NULL ); - if ( pszVelocity ) + const char* pszVelocity = pkvNode->GetString("velocity_relative", NULL); + if (pszVelocity) { sInteraction.iFlags |= SCNPC_FLAG_TEST_OTHER_VELOCITY; - UTIL_StringToVector( sInteraction.vecRelativeVelocity.Base(), pszVelocity ); + UTIL_StringToVector(sInteraction.vecRelativeVelocity.Base(), pszVelocity); } // Entry Sequence - const char *pszSequence = pkvNode->GetString( "entry_sequence", NULL ); - if ( pszSequence ) + const char* pszSequence = pkvNode->GetString("entry_sequence", NULL); + if (pszSequence) { - sInteraction.sPhases[SNPCINT_ENTRY].iszSequence = AllocPooledString( pszSequence ); + sInteraction.sPhases[SNPCINT_ENTRY].iszSequence = AllocPooledString(pszSequence); } // Entry Activity - const char *pszActivity = pkvNode->GetString( "entry_activity", NULL ); - if ( pszActivity ) + const char* pszActivity = pkvNode->GetString("entry_activity", NULL); + if (pszActivity) { - sInteraction.sPhases[SNPCINT_ENTRY].iActivity = GetActivityID( pszActivity ); + sInteraction.sPhases[SNPCINT_ENTRY].iActivity = GetActivityID(pszActivity); } // Sequence - pszSequence = pkvNode->GetString( "sequence", NULL ); - if ( pszSequence ) + pszSequence = pkvNode->GetString("sequence", NULL); + if (pszSequence) { - sInteraction.sPhases[SNPCINT_SEQUENCE].iszSequence = AllocPooledString( pszSequence ); + sInteraction.sPhases[SNPCINT_SEQUENCE].iszSequence = AllocPooledString(pszSequence); } // Activity - pszActivity = pkvNode->GetString( "activity", NULL ); - if ( pszActivity ) + pszActivity = pkvNode->GetString("activity", NULL); + if (pszActivity) { - sInteraction.sPhases[SNPCINT_SEQUENCE].iActivity = GetActivityID( pszActivity ); + sInteraction.sPhases[SNPCINT_SEQUENCE].iActivity = GetActivityID(pszActivity); } // Exit Sequence - pszSequence = pkvNode->GetString( "exit_sequence", NULL ); - if ( pszSequence ) + pszSequence = pkvNode->GetString("exit_sequence", NULL); + if (pszSequence) { - sInteraction.sPhases[SNPCINT_EXIT].iszSequence = AllocPooledString( pszSequence ); + sInteraction.sPhases[SNPCINT_EXIT].iszSequence = AllocPooledString(pszSequence); } // Exit Activity - pszActivity = pkvNode->GetString( "exit_activity", NULL ); - if ( pszActivity ) + pszActivity = pkvNode->GetString("exit_activity", NULL); + if (pszActivity) { - sInteraction.sPhases[SNPCINT_EXIT].iActivity = GetActivityID( pszActivity ); + sInteraction.sPhases[SNPCINT_EXIT].iActivity = GetActivityID(pszActivity); } // Delay - sInteraction.flDelay = pkvNode->GetFloat( "delay", 10.0 ); + sInteraction.flDelay = pkvNode->GetFloat("delay", 10.0); // Delta - sInteraction.flDistSqr = pkvNode->GetFloat( "origin_max_delta", (DSS_MAX_DIST * DSS_MAX_DIST) ); + sInteraction.flDistSqr = pkvNode->GetFloat("origin_max_delta", (DSS_MAX_DIST * DSS_MAX_DIST)); // Loop? - if ( pkvNode->GetFloat( "loop_in_action", 0 ) ) + if (pkvNode->GetFloat("loop_in_action", 0)) { sInteraction.iFlags |= SCNPC_FLAG_LOOP_IN_ACTION; } // Fixup position? - const char *pszDontFixup = pkvNode->GetString( "dont_teleport_at_end", NULL ); - if ( pszDontFixup ) + const char* pszDontFixup = pkvNode->GetString("dont_teleport_at_end", NULL); + if (pszDontFixup) { - if ( !Q_stricmp( pszDontFixup, "me" ) || !Q_stricmp( pszDontFixup, "both" ) ) + if (!Q_stricmp(pszDontFixup, "me") || !Q_stricmp(pszDontFixup, "both")) { sInteraction.iFlags |= SCNPC_FLAG_DONT_TELEPORT_AT_END_ME; } - else if ( !Q_stricmp( pszDontFixup, "them" ) || !Q_stricmp( pszDontFixup, "both" ) ) + else if (!Q_stricmp(pszDontFixup, "them") || !Q_stricmp(pszDontFixup, "both")) { sInteraction.iFlags |= SCNPC_FLAG_DONT_TELEPORT_AT_END_THEM; } } // Needs a weapon? - const char *pszNeedsWeapon = pkvNode->GetString( "needs_weapon", NULL ); - if ( pszNeedsWeapon ) + const char* pszNeedsWeapon = pkvNode->GetString("needs_weapon", NULL); + if (pszNeedsWeapon) { - if ( !Q_strncmp( pszNeedsWeapon, "ME", 2 ) ) + if (!Q_strncmp(pszNeedsWeapon, "ME", 2)) { sInteraction.iFlags |= SCNPC_FLAG_NEEDS_WEAPON_ME; } - else if ( !Q_strncmp( pszNeedsWeapon, "THEM", 4 ) ) + else if (!Q_strncmp(pszNeedsWeapon, "THEM", 4)) { sInteraction.iFlags |= SCNPC_FLAG_NEEDS_WEAPON_THEM; } - else if ( !Q_strncmp( pszNeedsWeapon, "BOTH", 4 ) ) + else if (!Q_strncmp(pszNeedsWeapon, "BOTH", 4)) { sInteraction.iFlags |= SCNPC_FLAG_NEEDS_WEAPON_ME; sInteraction.iFlags |= SCNPC_FLAG_NEEDS_WEAPON_THEM; @@ -13099,26 +15192,28 @@ void CAI_BaseNPC::ParseScriptedNPCInteractions( void ) } // Specific weapon types - const char *pszWeaponName = pkvNode->GetString( "weapon_mine", NULL ); - if ( pszWeaponName ) + const char* pszWeaponName = pkvNode->GetString("weapon_mine", NULL); + if (pszWeaponName) { sInteraction.iFlags |= SCNPC_FLAG_NEEDS_WEAPON_ME; - sInteraction.iszMyWeapon = AllocPooledString( pszWeaponName ); + sInteraction.iszMyWeapon = AllocPooledString(pszWeaponName); } - pszWeaponName = pkvNode->GetString( "weapon_theirs", NULL ); - if ( pszWeaponName ) + pszWeaponName = pkvNode->GetString("weapon_theirs", NULL); + if (pszWeaponName) { sInteraction.iFlags |= SCNPC_FLAG_NEEDS_WEAPON_THEM; - sInteraction.iszTheirWeapon = AllocPooledString( pszWeaponName ); + sInteraction.iszTheirWeapon = AllocPooledString(pszWeaponName); } // Add it to the list - AddScriptedNPCInteraction( &sInteraction ); + AddScriptedNPCInteraction(&sInteraction); // Move to next interaction pkvNode = pkvNode->GetNextKey(); } } +#endif // MAPBASE + } modelKeyValues->deleteThis(); @@ -13147,8 +15242,23 @@ void CAI_BaseNPC::AddScriptedNPCInteraction( ScriptedNPCInteraction_t *pInteract //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- -const char *CAI_BaseNPC::GetScriptedNPCInteractionSequence( ScriptedNPCInteraction_t *pInteraction, int iPhase ) +const char *CAI_BaseNPC::GetScriptedNPCInteractionSequence( ScriptedNPCInteraction_t *pInteraction, int iPhase, bool bOtherNPC ) { +#ifdef MAPBASE + if (bOtherNPC && pInteraction->bHasSeparateSequenceNames) + { + // Check unique phases + if ( pInteraction->sTheirPhases[iPhase].iActivity != ACT_INVALID ) + { + int iSequence = SelectWeightedSequence( (Activity)pInteraction->sTheirPhases[iPhase].iActivity ); + return GetSequenceName( iSequence ); + } + + if ( pInteraction->sTheirPhases[iPhase].iszSequence != NULL_STRING ) + return STRING(pInteraction->sTheirPhases[iPhase].iszSequence); + } +#endif + if ( pInteraction->sPhases[iPhase].iActivity != ACT_INVALID ) { int iSequence = SelectWeightedSequence( (Activity)pInteraction->sPhases[iPhase].iActivity ); @@ -13242,6 +15352,37 @@ void CAI_BaseNPC::StartScriptedNPCInteraction( CAI_BaseNPC *pOtherNPC, ScriptedN // Setup next attempt pInteraction->flNextAttemptTime = gpGlobals->curtime + pInteraction->flDelay + RandomFloat(-2,2); +#ifdef MAPBASE + if (pInteraction->iszRelatedInteractions != NULL_STRING) + { + // Delay related interactions as well + char szRelatedInteractions[256]; + Q_strncpy( szRelatedInteractions, STRING( pInteraction->iszRelatedInteractions ), sizeof( szRelatedInteractions ) ); + + char *pszInteraction = strtok( szRelatedInteractions, "," ); + while (pszInteraction) + { + bool bWildCard = Matcher_ContainsWildcard( pszInteraction ); + + for ( int i = 0; i < m_ScriptedInteractions.Count(); i++ ) + { + ScriptedNPCInteraction_t *pOtherInteraction = &m_ScriptedInteractions[i]; + + if ( Matcher_NamesMatch( pszInteraction, STRING( pOtherInteraction->iszInteractionName ) ) && pOtherInteraction != pInteraction ) + { + if (pOtherInteraction->flNextAttemptTime < pInteraction->flNextAttemptTime) + pOtherInteraction->flNextAttemptTime = pInteraction->flNextAttemptTime; + + // Not looking for multiple + if (!bWildCard) + break; + } + } + + pszInteraction = strtok( NULL, "," ); + } + } +#endif // Spawn a scripted sequence for this NPC to play the interaction anim CAI_ScriptedSequence *pMySequence = (CAI_ScriptedSequence*)CreateEntityByName( "scripted_sequence" ); @@ -13273,6 +15414,15 @@ void CAI_BaseNPC::StartScriptedNPCInteraction( CAI_BaseNPC *pOtherNPC, ScriptedN CAI_ScriptedSequence *pTheirSequence = NULL; if ( pOtherNPC ) { +#ifdef MAPBASE + if (pInteraction->bHasSeparateSequenceNames) + { + pszEntrySequence = GetScriptedNPCInteractionSequence( pInteraction, SNPCINT_ENTRY, true ); + pszSequence = GetScriptedNPCInteractionSequence( pInteraction, SNPCINT_SEQUENCE, true ); + pszExitSequence = GetScriptedNPCInteractionSequence( pInteraction, SNPCINT_EXIT, true ); + } +#endif + pTheirSequence = (CAI_ScriptedSequence*)CreateEntityByName( "scripted_sequence" ); pTheirSequence->KeyValue( "m_iszEntry", pszEntrySequence ); pTheirSequence->KeyValue( "m_iszPlay", pszSequence ); @@ -13296,6 +15446,26 @@ void CAI_BaseNPC::StartScriptedNPCInteraction( CAI_BaseNPC *pOtherNPC, ScriptedN // Tell their sequence to keep their position relative to me pTheirSequence->SetupInteractionPosition( this, pInteraction->matDesiredLocalToWorld ); + +#ifdef MAPBASE + if ( !(pInteraction->iFlags & SCNPC_FLAG_TEST_OTHER_ANGLES) ) + { + // Set up interaction yaw pose if it exists + float flYaw = AngleDistance( angDesired.y, angOtherAngles.y ); + + int nInteractionPose = LookupPoseInteractionRelativeYaw(); + if (nInteractionPose > -1) + { + SetPoseParameter( nInteractionPose, flYaw ); + } + + nInteractionPose = pOtherNPC->LookupPoseInteractionRelativeYaw(); + if (nInteractionPose > -1) + { + pOtherNPC->SetPoseParameter( nInteractionPose, flYaw ); + } + } +#endif } // Spawn both sequences at once @@ -13348,6 +15518,11 @@ void CAI_BaseNPC::StartScriptedNPCInteraction( CAI_BaseNPC *pOtherNPC, ScriptedN //----------------------------------------------------------------------------- bool CAI_BaseNPC::CanRunAScriptedNPCInteraction( bool bForced ) { +#ifdef MAPBASE + if ( m_iDynamicInteractionsAllowed == TRS_FALSE && !bForced ) + return false; +#endif + if ( m_NPCState != NPC_STATE_IDLE && m_NPCState != NPC_STATE_ALERT && m_NPCState != NPC_STATE_COMBAT ) return false; @@ -13379,7 +15554,7 @@ bool CAI_BaseNPC::CanRunAScriptedNPCInteraction( bool bForced ) return false; // Default AI prevents interactions while melee attacking, but not ranged attacking - if ( IsCurSchedule( SCHED_MELEE_ATTACK1 ) || IsCurSchedule( SCHED_MELEE_ATTACK2 ) ) + if ( ( IsCurSchedule( SCHED_MELEE_ATTACK1 ) || IsCurSchedule( SCHED_MELEE_ATTACK2 ) ) && !CanStartDynamicInteractionDuringMelee() ) return false; } @@ -13431,6 +15606,11 @@ void CAI_BaseNPC::CheckForScriptedNPCInteractions( void ) if ( pInteraction->flNextAttemptTime > gpGlobals->curtime ) continue; +#ifdef MAPBASE + if ( !InteractionIsAllowed(pNPC, pInteraction) ) + continue; +#endif + Vector vecOrigin; QAngle angAngles; if ( InteractionCouldStart( pNPC, pInteraction, vecOrigin, angAngles ) ) @@ -13473,7 +15653,12 @@ void CAI_BaseNPC::CalculateValidEnemyInteractions( void ) // If we have a damage filter that prevents us hurting the enemy, // don't interact with him, since most interactions kill the enemy. // Create a fake damage info to test it with. +#ifdef MAPBASE + // DMG_PREVENT_PHYSICS_FORCE can be used to identify dynamic interaction tests + CTakeDamageInfo tempinfo( this, this, vec3_origin, vec3_origin, 1.0, DMG_BULLET | DMG_PREVENT_PHYSICS_FORCE ); +#else CTakeDamageInfo tempinfo( this, this, vec3_origin, vec3_origin, 1.0, DMG_BULLET ); +#endif if ( !pNPC->PassesDamageFilter( tempinfo ) ) continue; @@ -13484,23 +15669,88 @@ void CAI_BaseNPC::CalculateValidEnemyInteractions( void ) continue; // Check the specific weapon type +#ifdef MAPBASE + if ( pInteraction->iszMyWeapon != NULL_STRING ) + { + const char *myweapon = STRING(pInteraction->iszMyWeapon); + bool pass = false; + if (Q_strstr(myweapon, "!=")) + { + myweapon += 2; + pass = true; + } + + if (Q_strstr(myweapon, "WEPCLASS")) + pass = (GetActiveWeapon()->WeaponClassFromString(myweapon) == GetActiveWeapon()->WeaponClassify()) ? !pass : pass; + else + pass = (GetActiveWeapon()->m_iClassname == pInteraction->iszMyWeapon) ? !pass : pass; + + if (!pass) + continue; + } +#else if ( pInteraction->iszMyWeapon != NULL_STRING && GetActiveWeapon()->m_iClassname != pInteraction->iszMyWeapon ) continue; +#endif } if ( pInteraction->iFlags & SCNPC_FLAG_NEEDS_WEAPON_THEM ) { if ( !pNPC->GetActiveWeapon() ) continue; +#ifdef MAPBASE + if ( pInteraction->iszTheirWeapon != NULL_STRING ) + { + const char *theirweapon = STRING(pInteraction->iszTheirWeapon); + bool pass = false; + if (Q_strstr(theirweapon, "!=")) + { + theirweapon += 2; + pass = true; + } + + if (Q_strstr(theirweapon, "WEPCLASS")) + pass = (pNPC->GetActiveWeapon()->WeaponClassFromString(theirweapon) == pNPC->GetActiveWeapon()->WeaponClassify()) ? !pass : pass; + else + pass = (pNPC->GetActiveWeapon()->m_iClassname == pInteraction->iszTheirWeapon) ? !pass : pass; + + if (!pass) + continue; + } +#else // Check the specific weapon type if ( pInteraction->iszTheirWeapon != NULL_STRING && pNPC->GetActiveWeapon()->m_iClassname != pInteraction->iszTheirWeapon ) continue; +#endif } // Script needs the other NPC, so make sure they're not dead if ( !pNPC->IsAlive() ) continue; +#ifdef MAPBASE + // If they have an interaction with the same name, it means we're not supposed to engage with them + bool bSame = false; + for ( int i2 = 0; i2 < pNPC->m_ScriptedInteractions.Count(); i2++ ) + { + // These strings are pooled, so this works + if (m_ScriptedInteractions[i].iszInteractionName == pNPC->m_ScriptedInteractions[i2].iszInteractionName) + { + bSame = true; + break; + } + } + + if (bSame) + continue; + + // Resolve the activity or sequence, and make sure our enemy has it + const char *pszSequence = GetScriptedNPCInteractionSequence( pInteraction, SNPCINT_SEQUENCE, true ); + if ( !pszSequence ) + continue; + if ( pNPC->LookupSequence( pszSequence ) == -1 ) + continue; +#else // Use sequence? or activity? if ( pInteraction->sPhases[SNPCINT_SEQUENCE].iActivity != ACT_INVALID ) { @@ -13516,6 +15766,7 @@ void CAI_BaseNPC::CalculateValidEnemyInteractions( void ) if ( pNPC->LookupSequence( STRING(pInteraction->sPhases[SNPCINT_SEQUENCE].iszSequence) ) == -1 ) continue; } +#endif pInteraction->bValidOnCurrentEnemy = true; bFound = true; @@ -13584,7 +15835,11 @@ void CAI_BaseNPC::CheckForcedNPCInteractions( void ) if ( m_hForcedInteractionPartner ) { // We've aborted a forced interaction. Let the mapmaker know. +#ifdef MAPBASE + m_OnForcedInteractionAborted.FireOutput( pNPC, this ); +#else m_OnForcedInteractionAborted.FireOutput( this, this ); +#endif } CleanupForcedInteraction(); @@ -13594,7 +15849,11 @@ void CAI_BaseNPC::CheckForcedNPCInteractions( void ) } StartScriptedNPCInteraction( pNPC, pInteraction, vecOrigin, angAngles ); +#ifdef MAPBASE + m_OnForcedInteractionStarted.FireOutput( pNPC, this ); +#else m_OnForcedInteractionStarted.FireOutput( this, this ); +#endif } @@ -13660,6 +15919,112 @@ bool CanNPCsTradePlaces( CAI_BaseNPC *pNPC1, CAI_BaseNPC *pNPC2, bool bDebug ) return true; } +#ifdef MAPBASE +bool CAI_BaseNPC::InteractionIsAllowed( CAI_BaseNPC *pOtherNPC, ScriptedNPCInteraction_t *pInteraction ) +{ + // Now that female citizens have hunter interactions, Alyx is vulnerable to being murdered by hunters *dynamically*! + // Citizens also have antlion interaction kill animations, so antlions could potentially murder her as well. + // + // Forced interactions should still work, but this prevents regular interactions from targeting vital allies. + // Hopefully there aren't any maps that already have hunters murder Barneys. + if (pOtherNPC->Classify() == CLASS_PLAYER_ALLY_VITAL) + return false; + + if (m_iDynamicInteractionsAllowed == TRS_FALSE) + return false; + + // To maintain existing behavior, Mapbase additions require either explicit TRS_YES or ai_dynint_always_enabled. + if (pInteraction->iFlags & SCNPC_FLAG_MAPBASE_ADDITION && m_iDynamicInteractionsAllowed == TRS_NONE && !ai_dynint_always_enabled.GetBool()) + return false; + + // Test misc. criteria here since some of it may not have been valid on initial calculation, but could be now + if (pInteraction->MiscCriteria != NULL_STRING) + { + // Test against response system criteria + AI_CriteriaSet set; + ModifyOrAppendCriteria( set ); + CBasePlayer *pPlayer = UTIL_GetLocalPlayer(); + if (pPlayer) + pPlayer->ModifyOrAppendPlayerCriteria( set ); + + // Get criteria from target if we want it + if ( V_strstr( STRING( pInteraction->MiscCriteria ), "their_" ) ) + { + // Currently, in order to get everything which might be desired, we call the other NPC's ModifyOrAppendCriteria. + // We put it in a separate criteria set, then assign a prefix and append it to the main set, similar to how contexts are appended. + // This includes a few global criterions which we might not need, so we throw them out before they're merged. + // This isn't a very efficient solution, but there are no better options available without rewriting parts of the response criteria system. + AI_CriteriaSet theirSet; + pOtherNPC->ModifyOrAppendCriteria( theirSet ); + + set.EnsureCapacity( (theirSet.GetCount()-2) + set.GetCount() ); // We know we'll be throwing out 2 global criterions + + char sz[ 128 ]; + for ( int i = 0; i < theirSet.GetCount(); i++ ) + { + const char *name = theirSet.GetName( i ); + const char *value = theirSet.GetValue( i ); + + if (FStrEq( name, "map" ) || FStrEq( name, "episodic" ) || FStrEq( name, "is_console" ) + || FStrEq( name, "month" ) || FStrEq( name, "day" ) + || FStrEq( name, "is_console" ) || FStrEq( name, "is_pc" ) + || V_strnicmp( name, "world", 5 ) == 0) + { + // Global criterion, ignore + continue; + } + + Q_snprintf( sz, sizeof( sz ), "their_%s", name ); + + if (ai_debug_dyninteractions.GetInt() == 3) + Msg( "%i: %s -> %s:%s\n", i, name, sz, value ); + + set.AppendCriteria( sz, value ); + } + + // Append this afterwards because it has its own prefix system + pOtherNPC->AppendContextToCriteria( set, "their_" ); + } + + ReAppendContextCriteria( set ); + + int index; + const char *criteriavalue; + char key[128]; + char value[128]; + const char *p = STRING( pInteraction->MiscCriteria ); + while ( p ) + { +#ifdef NEW_RESPONSE_SYSTEM + p = SplitContext( p, key, sizeof( key ), value, sizeof( value ), NULL, STRING( pInteraction->MiscCriteria ) ); +#else + p = SplitContext( p, key, sizeof( key ), value, sizeof( value ), NULL ); +#endif + + index = set.FindCriterionIndex( key ); + if (index != -1) + { + criteriavalue = set.GetValue( index ); + if (!Matcher_Match( value, criteriavalue )) + { + return false; + } + } + else + { + // Test with empty string in case our criteria is != or something + criteriavalue = ""; + if (!Matcher_Match( value, criteriavalue )) + { + return false; + } + } + } + } + + return true; +} +#endif //----------------------------------------------------------------------------- // Purpose: @@ -13695,6 +16060,11 @@ bool CAI_BaseNPC::InteractionCouldStart( CAI_BaseNPC *pOtherNPC, ScriptedNPCInte { Msg(" %s distsqr: %0.2f (%0.2f %0.2f %0.2f), desired: <%0.2f (%0.2f %0.2f %0.2f)\n", GetDebugName(), flDistSqr, pOtherNPC->GetAbsOrigin().x, pOtherNPC->GetAbsOrigin().y, pOtherNPC->GetAbsOrigin().z, pInteraction->flDistSqr, vecOrigin.x, vecOrigin.y, vecOrigin.z ); +#ifdef MAPBASE + Vector vecForward, vecRight; + GetVectors( &vecForward, &vecRight, NULL ); + NDebugOverlay::Circle( vecOrigin + Vector(0,0,2), vecForward, vecRight, FastSqrt(pInteraction->flDistSqr), 255, 0, 0, 255, true, 0.1f ); +#endif } } } @@ -13707,6 +16077,11 @@ bool CAI_BaseNPC::InteractionCouldStart( CAI_BaseNPC *pOtherNPC, ScriptedNPCInte Msg(" %s is at: %0.2f %0.2f %0.2f\n", GetDebugName(), GetAbsOrigin().x, GetAbsOrigin().y, GetAbsOrigin().z ); Msg(" %s distsqr: %0.2f (%0.2f %0.2f %0.2f), desired: (%0.2f %0.2f %0.2f)\n", GetDebugName(), flDistSqr, pOtherNPC->GetAbsOrigin().x, pOtherNPC->GetAbsOrigin().y, pOtherNPC->GetAbsOrigin().z, vecOrigin.x, vecOrigin.y, vecOrigin.z ); +#ifdef MAPBASE + Vector vecForward, vecRight; + GetVectors( &vecForward, &vecRight, NULL ); + NDebugOverlay::Circle( vecOrigin + Vector( 0, 0, 2 ), vecForward, vecRight, FastSqrt( pInteraction->flDistSqr ), 255, 0, 0, 255, true, 0.1f ); +#endif if ( pOtherNPC ) { @@ -13723,14 +16098,28 @@ bool CAI_BaseNPC::InteractionCouldStart( CAI_BaseNPC *pOtherNPC, ScriptedNPCInte for ( int ang = 0; ang < 3; ang++ ) { float flAngDiff = AngleDiff( angEnemyAngles[ang], angAngles[ang] ); +#ifdef MAPBASE + if ( fabs(flAngDiff) > pInteraction->flMaxAngleDiff ) +#else if ( fabs(flAngDiff) > DSS_MAX_ANGLE_DIFF ) +#endif { bMatches = false; break; } } if ( !bMatches ) + { +#ifdef MAPBASE + if ( bDebug ) + { + Msg(" %s angle not matched: (%0.2f %0.2f %0.2f), desired (%0.2f, %0.2f, %0.2f)\n", GetDebugName(), + anglemod(angEnemyAngles.x), anglemod(angEnemyAngles.y), anglemod(angEnemyAngles.z), anglemod(angAngles.x), anglemod(angAngles.y), anglemod(angAngles.z) ); + Msg(" diff: (%0.2f, %0.2f, %0.2f)\n", AngleDiff( angEnemyAngles.x, angAngles.x ), AngleDiff( angEnemyAngles.y, angAngles.y ), AngleDiff( angEnemyAngles.z, angAngles.z ) ); + } +#endif return false; + } if ( bDebug ) { @@ -13738,6 +16127,13 @@ bool CAI_BaseNPC::InteractionCouldStart( CAI_BaseNPC *pOtherNPC, ScriptedNPCInte anglemod(angEnemyAngles.x), anglemod(angEnemyAngles.y), anglemod(angEnemyAngles.z), anglemod(angAngles.x), anglemod(angAngles.y), anglemod(angAngles.z) ); } } +#ifdef MAPBASE + else + { + // If we're not using angles, then use the NPC's current angles + angAngles = pOtherNPC->GetAbsAngles(); + } +#endif // TODO: Velocity check, if we're supposed to if ( pInteraction->iFlags & SCNPC_FLAG_TEST_OTHER_VELOCITY ) @@ -13801,6 +16197,67 @@ bool CAI_BaseNPC::InteractionCouldStart( CAI_BaseNPC *pOtherNPC, ScriptedNPCInte return false; } +#ifdef MAPBASE + // Make sure we could fit at the sequence end position too. + if (pInteraction->iFlags & SCNPC_FLAG_TEST_END_POSITION) + { + VMatrix matTestToWorld; + matTestToWorld.SetupMatrixOrgAngles(pInteraction->vecRelativeEndPos, angMyCurrent); + MatrixMultiply( matMeToWorld, matTestToWorld, matLocalToWorld ); + Vector vecPos = (matLocalToWorld.GetTranslation()); + + // Start from the NPC's position. + AI_TraceHull( pOtherNPC->GetAbsOrigin(), vecPos, GetHullMins(), GetHullMaxs(), MASK_SOLID, &traceFilter, &tr ); + if ( tr.fraction != 1.0 ) + { + if ( bDebug ) + { + NDebugOverlay::Box( vecPos, GetHullMins(), GetHullMaxs(), 255,0,0, 100, 1.0 ); + NDebugOverlay::HorzArrow( GetAbsOrigin(), vecPos, 16.0f, 255, 0, 0, 255, true, 1.0f ); + } + return false; + } + else if ( bDebug ) + { + //NDebugOverlay::Box( vecPos, GetHullMins(), GetHullMaxs(), 0,255,0, 100, 1.0 ); + + NDebugOverlay::Axis( vecPos, angAngles, 20, true, 1.0 ); + } + } + else + { + // Instead, make sure we fit into where the sequence movement ends at + const char *pszSequence = GetScriptedNPCInteractionSequence( pInteraction, SNPCINT_SEQUENCE ); + int nSeq = LookupSequence( pszSequence ); + if ( pszSequence && nSeq != -1 ) + { + Vector vecDeltaPos; + QAngle angDeltaAngles; + GetSequenceMovement( nSeq, 0.0f, 1.0f, vecDeltaPos, angDeltaAngles ); + if (!vecDeltaPos.IsZero()) + { + QAngle angInteraction = GetAbsAngles(); + angInteraction[YAW] = m_flInteractionYaw; + + Vector vecPos; + VectorRotate( vecDeltaPos, angInteraction, vecPos ); + vecPos += GetAbsOrigin(); + + AI_TraceHull( vecPos, vecPos, GetHullMins(), GetHullMaxs(), MASK_SOLID, &traceFilter, &tr); + if ( tr.fraction != 1.0 ) + { + if ( bDebug ) + { + NDebugOverlay::Box( vecPos, GetHullMins(), GetHullMaxs(), 255,0,0, 100, 1.0 ); + NDebugOverlay::HorzArrow( GetAbsOrigin(), vecPos, 16.0f, 255, 0, 0, 255, true, 1.0f ); + } + return false; + } + } + } + } +#endif + // If the NPCs are swapping places during this interaction, make sure they can fit at each // others' origins before allowing the interaction. if ( !CanNPCsTradePlaces( this, pOtherNPC, bDebug ) ) @@ -13820,6 +16277,25 @@ bool CAI_BaseNPC::HasInteractionCantDie( void ) return ( m_bCannotDieDuringInteraction && IsRunningDynamicInteraction() ); } +//----------------------------------------------------------------------------- +// Purpose: Return true if this NPC has valid interactions on the current enemy. +//----------------------------------------------------------------------------- +bool CAI_BaseNPC::HasValidInteractionsOnCurrentEnemy( void ) +{ + if ( !GetEnemy() || !GetEnemy()->IsNPC() ) + return false; + + for ( int i = 0; i < m_ScriptedInteractions.Count(); i++ ) + { + ScriptedNPCInteraction_t *pInteraction = &m_ScriptedInteractions[i]; + + if ( pInteraction->bValidOnCurrentEnemy ) + return true; + } + + return false; +} + //----------------------------------------------------------------------------- // Purpose: // Input : &inputdata - @@ -13997,6 +16473,9 @@ void CAI_BaseNPC::ModifyOrAppendCriteria( AI_CriteriaSet& set ) set.AppendCriteria( "timesinceseenplayer", "-1" ); } +#ifdef MAPBASE + ModifyOrAppendEnemyCriteria(set, GetEnemy()); +#else // Append distance to my enemy if ( GetEnemy() ) { @@ -14006,8 +16485,111 @@ void CAI_BaseNPC::ModifyOrAppendCriteria( AI_CriteriaSet& set ) { set.AppendCriteria( "distancetoenemy", "-1" ); } +#endif + +#ifdef MAPBASE + // Append our gender + // I know scenes can use $gender01, but some rules now use more than scenes. + const char *modelname = STRING(GetModelName()); + if (modelname) + { + set.AppendCriteria( "gender", UTIL_VarArgs("%i", soundemitterbase->GetActorGender(modelname)) ); + } + + if (IsInSquad()) + { + set.AppendCriteria( "insquad", "1" ); + set.AppendCriteria( "squadmates", UTIL_VarArgs( "%i", GetSquad()->NumMembers() ) ); + set.AppendCriteria( "isleader", GetSquad()->IsLeader(this) ? "0" : "1" ); + } + else + { + set.AppendCriteria( "insquad", "0" ); + } +#endif } +#ifdef MAPBASE +// Went for a different structure that directly takes AI_CriteriaSet for modifiers +/* +#define GetModifiersFromCriteria( function, output, maxlen ) AI_CriteriaSet set; \ + function; \ + for (int i = 0; i < set.GetCount(); i++) { Q_snprintf(output, maxlen, "%s,%s:%s", output, set.GetName(i), set.GetValue(i)); } \ + memmove(output, output + 1, strlen(output + 1) + 1); \ + */ + +//----------------------------------------------------------------------------- +// Purpose: Appends enemy criteria so some classes could re-define it for, say, killing something +//----------------------------------------------------------------------------- +void CAI_BaseNPC::ModifyOrAppendEnemyCriteria( AI_CriteriaSet& set, CBaseEntity *pEnemy ) +{ + if ( pEnemy ) + { + set.AppendCriteria( "enemy", pEnemy->GetClassname() ); + set.AppendCriteria( "enemyclass", g_pGameRules->AIClassText( pEnemy->Classify() ) ); // UTIL_VarArgs("%i", pEnemy->Classify()) + set.AppendCriteria( "distancetoenemy", UTIL_VarArgs( "%f", EnemyDistance(pEnemy) ) ); + set.AppendCriteria( "timesincecombat", "-1" ); + + CAI_BaseNPC *pNPC = pEnemy->MyNPCPointer(); + if (pNPC) + { + set.AppendCriteria("enemy_is_npc", "1"); + + set.AppendCriteria( "enemy_activity", CAI_BaseNPC::GetActivityName( pNPC->GetActivity() ) ); + set.AppendCriteria( "enemy_weapon", pNPC->GetActiveWeapon() ? pNPC->GetActiveWeapon()->GetClassname() : "0" ); + } + else + { + set.AppendCriteria("enemy_is_npc", "0"); + } + + pEnemy->AppendContextToCriteria( set, "enemy_" ); + } + else + { + if ( GetLastEnemyTime() == 0.0 ) + set.AppendCriteria( "timesincecombat", "999999.0" ); + else + set.AppendCriteria( "timesincecombat", UTIL_VarArgs( "%f", gpGlobals->curtime - GetLastEnemyTime() ) ); + + set.AppendCriteria( "distancetoenemy", "-1" ); + } +} + +/* +//----------------------------------------------------------------------------- +// Purpose: Gets enemy criteria in context form +//----------------------------------------------------------------------------- +void CAI_BaseNPC::GetEnemyCriteriaModifiers( CBaseEntity *pEnemy, char *szCriteria, int iMaxLen ) +{ + GetModifiersFromCriteria(ModifyOrAppendEnemyCriteria(set, pEnemy), szCriteria, iMaxLen); +} +*/ + +//----------------------------------------------------------------------------- +// Purpose: Appends damage criteria for pain sounds, death sounds, etc. +//----------------------------------------------------------------------------- +void CAI_BaseNPC::ModifyOrAppendDamageCriteria( AI_CriteriaSet& set, const CTakeDamageInfo &info ) +{ + if ( info.GetAttacker() && info.GetAttacker() != this ) + set.AppendCriteria("attacker", info.GetAttacker()->GetClassname()); + else + set.AppendCriteria("attacker", ""); + + set.AppendCriteria("damagetype", UTIL_VarArgs("%i", info.GetDamageType())); +} + +/* +//----------------------------------------------------------------------------- +// Purpose: Gets damage criteria in context form +//----------------------------------------------------------------------------- +void CAI_BaseNPC::GetDamageCriteriaModifiers( const CTakeDamageInfo &info, char *szCriteria, int iMaxLen ) +{ + GetModifiersFromCriteria(ModifyOrAppendDamageCriteria(set, info), szCriteria, iMaxLen); +} +*/ +#endif + //----------------------------------------------------------------------------- // If I were crouching at my current location, could I shoot this target? //----------------------------------------------------------------------------- @@ -14036,6 +16618,26 @@ bool CAI_BaseNPC::CouldShootIfCrouching( CBaseEntity *pTarget ) return bResult; } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: Check if this position will block our line of sight if aiming low. +//----------------------------------------------------------------------------- +bool CAI_BaseNPC::CouldShootIfCrouchingAt( const Vector &vecPosition, const Vector &vecForward, const Vector &vecRight, float flDist ) +{ + Vector vGunPos = vecPosition; + vGunPos += (GetCrouchGunOffset() + vecRight * 8); + + trace_t tr; + AI_TraceLOS( vGunPos, vGunPos + (vecForward * flDist), this, &tr ); + if (tr.fraction != 1.0) + { + return false; + } + + return true; +} +#endif + //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- @@ -14050,6 +16652,18 @@ bool CAI_BaseNPC::IsCrouchedActivity( Activity activity ) case ACT_COVER_PISTOL_LOW: case ACT_COVER_SMG1_LOW: case ACT_RELOAD_SMG1_LOW: +#ifdef MAPBASE +#if AR2_ACTIVITY_FIX == 1 + case ACT_COVER_AR2_LOW: + case ACT_RELOAD_AR2_LOW: +#endif + case ACT_RELOAD_PISTOL_LOW: + case ACT_RELOAD_SHOTGUN_LOW: +#if EXPANDED_HL2_WEAPON_ACTIVITIES + case ACT_RELOAD_REVOLVER_LOW: + case ACT_RELOAD_CROSSBOW_LOW: +#endif +#endif return true; } diff --git a/src/game/server/ai_basenpc.h b/src/game/server/ai_basenpc.h index 7c65c0cc..7429a1f2 100644 --- a/src/game/server/ai_basenpc.h +++ b/src/game/server/ai_basenpc.h @@ -64,7 +64,9 @@ class CBaseGrenade; class CBaseDoor; class CBasePropDoor; struct AI_Waypoint_t; +#ifndef NEW_RESPONSE_SYSTEM class AI_Response; +#endif class CBaseFilter; typedef CBitVec CAI_ScheduleBits; @@ -95,6 +97,36 @@ extern bool AIStrongOpt( void ); // Max's of the box used to search for a weapon to pick up. 45x45x~8 ft. #define WEAPON_SEARCH_DELTA Vector( 540, 540, 100 ) +#ifdef MAPBASE +// Defines Mapbase's extended NPC response system usage. +#define EXPANDED_RESPONSE_SYSTEM_USAGE +#endif + +#ifdef EXPANDED_RESPONSE_SYSTEM_USAGE + +// This macro implements the response system on any NPC, particularly non-actors that can't use CAI_ExpresserHost. +// NOTE: Because of the lack of CAI_ExpresserHost, some Response System settings like odds, delays, etc. cannot be used. +// It's recommended to just use CAI_ExpresserHost if possible. +#define DeclareResponseSystem IResponseSystem *GetResponseSystem() { extern IResponseSystem *g_pResponseSystem; return g_pResponseSystem; } + +// Default CAI_ExpresserHost implementation for NPCs using CAI_ExpresserHost. +#define DeclareDefaultExpresser() virtual CAI_Expresser *CreateExpresser( void ) { m_pExpresser = new CAI_Expresser(this); if (!m_pExpresser) return NULL; m_pExpresser->Connect(this); return m_pExpresser; } \\ + virtual CAI_Expresser *GetExpresser() { return m_pExpresser; } \\ + virtual void PostConstructor(const char *szClassname) { BaseClass::PostConstructor(szClassname); CreateExpresser(); } \\ + private: \\ + CAI_Expresser *m_pExpresser; \\ + public: + +// Variant of DeclareDefaultExpresser() that doesn't implement its own PostConstructor. +// CreateExpresser() should still be called from there. +#define DeclareDefaultExpresser_ExistingPC() virtual CAI_Expresser *CreateExpresser( void ) { m_pExpresser = new CAI_Expresser(this); if (!m_pExpresser) return NULL; m_pExpresser->Connect(this); return m_pExpresser; } \\ + virtual CAI_Expresser *GetExpresser() { return m_pExpresser; } \\ + private: \\ + CAI_Expresser *m_pExpresser; \\ + public: + +#endif + enum Interruptability_t { GENERAL_INTERRUPTABILITY, @@ -316,6 +348,10 @@ struct UnreachableEnt_t #define SCNPC_FLAG_NEEDS_WEAPON_THEM ( 1 << 5 ) #define SCNPC_FLAG_DONT_TELEPORT_AT_END_ME ( 1 << 6 ) #define SCNPC_FLAG_DONT_TELEPORT_AT_END_THEM ( 1 << 7 ) +#ifdef MAPBASE +#define SCNPC_FLAG_MAPBASE_ADDITION ( 1 << 8 ) +#define SCNPC_FLAG_TEST_END_POSITION ( 1 << 9 ) +#endif // ----------------------------------------- // Scripted NPC interaction trigger methods @@ -387,11 +423,22 @@ struct ScriptedNPCInteraction_t flNextAttemptTime = 0; iszMyWeapon = NULL_STRING; iszTheirWeapon = NULL_STRING; +#ifdef MAPBASE + vecRelativeEndPos = vec3_origin; + bHasSeparateSequenceNames = false; + flMaxAngleDiff = DSS_MAX_ANGLE_DIFF; + iszRelatedInteractions = NULL_STRING; + MiscCriteria = NULL_STRING; +#endif for ( int i = 0; i < SNPCINT_NUM_PHASES; i++) { sPhases[i].iszSequence = NULL_STRING; sPhases[i].iActivity = ACT_INVALID; +#ifdef MAPBASE + sTheirPhases[i].iszSequence = NULL_STRING; + sTheirPhases[i].iActivity = ACT_INVALID; +#endif } } @@ -403,6 +450,9 @@ struct ScriptedNPCInteraction_t Vector vecRelativeOrigin; // (forward, right, up) QAngle angRelativeAngles; Vector vecRelativeVelocity; // Desired relative velocity of the other NPC +#ifdef MAPBASE + Vector vecRelativeEndPos; // Relative position that the NPC must fit in +#endif float flDelay; // Delay before interaction can be used again float flDistSqr; // Max distance sqr from the relative origin the NPC is allowed to be to trigger string_t iszMyWeapon; // Classname of the weapon I'm holding, if any @@ -415,6 +465,17 @@ struct ScriptedNPCInteraction_t float flNextAttemptTime; +#ifdef MAPBASE + ScriptedNPCInteraction_Phases_t sTheirPhases[SNPCINT_NUM_PHASES]; // The animations played by the target NPC, if they are different + bool bHasSeparateSequenceNames; + + float flMaxAngleDiff; + string_t iszRelatedInteractions; // These interactions will be delayed as well when this interaction is used. + + // Unrecognized keyvalues which are tested against response criteria later. + string_t MiscCriteria; +#endif + DECLARE_SIMPLE_DATADESC(); }; @@ -499,6 +560,9 @@ public: DECLARE_DATADESC(); DECLARE_SERVERCLASS(); +#ifdef MAPBASE_VSCRIPT + DECLARE_ENT_SCRIPTDESC(); +#endif virtual int Save( ISave &save ); virtual int Restore( IRestore &restore ); @@ -510,6 +574,9 @@ public: virtual unsigned int PhysicsSolidMaskForEntity( void ) const; virtual bool KeyValue( const char *szKeyName, const char *szValue ); +#ifdef MAPBASE + virtual bool GetKeyValue( const char *szKeyName, char *szValue, int iMaxLen ); +#endif //--------------------------------- @@ -565,6 +632,10 @@ public: // Thinking, including core thinking, movement, animation virtual void NPCThink( void ); +#ifdef MAPBASE + void InputSetThinkNPC( inputdata_t &inputdata ); +#endif + // Core thinking (schedules & tasks) virtual void RunAI( void );// core ai function! @@ -607,6 +678,7 @@ public: virtual bool ShouldAlwaysThink(); void ForceGatherConditions() { m_bForceConditionsGather = true; SetEfficiency( AIE_NORMAL ); } // Force an NPC out of PVS to call GatherConditions on next think + bool IsForceGatherConditionsSet() { return m_bForceConditionsGather; } virtual float LineOfSightDist( const Vector &vecDir = vec3_invalid, float zEye = FLT_MAX ); @@ -777,6 +849,9 @@ protected: // pose parameters int m_poseAim_Pitch; int m_poseAim_Yaw; int m_poseMove_Yaw; +#ifdef MAPBASE + int m_poseInteractionRelativeYaw; +#endif virtual void PopulatePoseParameters( void ); public: @@ -784,6 +859,10 @@ public: // Return the stored pose parameter for "move_yaw" inline int LookupPoseMoveYaw() { return m_poseMove_Yaw; } + +#ifdef MAPBASE + inline int LookupPoseInteractionRelativeYaw() { return m_poseInteractionRelativeYaw; } +#endif //----------------------------------------------------- @@ -853,6 +932,11 @@ public: bool DidChooseEnemy() const { return !m_bSkippedChooseEnemy; } +#ifdef MAPBASE + void InputSetCondition( inputdata_t &inputdata ); + void InputClearCondition( inputdata_t &inputdata ); +#endif + private: CAI_ScheduleBits m_Conditions; CAI_ScheduleBits m_CustomInterruptConditions; //Bit string assembled by the schedule running, then @@ -895,8 +979,12 @@ public: void RemoveSleepFlags( int flags ) { m_SleepFlags &= ~flags; } bool HasSleepFlags( int flags ) { return (m_SleepFlags & flags) == flags; } - void UpdateSleepState( bool bInPVS ); + virtual void UpdateSleepState( bool bInPVS ); virtual void Wake( bool bFireOutput = true ); +#ifdef MAPBASE + // A version of Wake() that takes an activator + virtual void Wake( CBaseEntity *pActivator ); +#endif void Sleep(); bool WokeThisTick() const; @@ -926,10 +1014,19 @@ public: Activity TranslateActivity( Activity idealActivity, Activity *pIdealWeaponActivity = NULL ); Activity NPC_TranslateActivity( Activity eNewActivity ); +#ifdef MAPBASE + Activity TranslateCrouchActivity( Activity baseAct ); + virtual bool CanTranslateCrouchActivity( void ) { return true; } + virtual Activity NPC_BackupActivity( Activity eNewActivity ); +#endif Activity GetActivity( void ) { return m_Activity; } virtual void SetActivity( Activity NewActivity ); Activity GetIdealActivity( void ) { return m_IdealActivity; } void SetIdealActivity( Activity NewActivity ); +#ifdef MAPBASE + Activity GetTranslatedActivity( void ) { return m_translatedActivity; } + Activity GetIdealTranslatedActivity( void ) { return m_IdealTranslatedActivity; } +#endif void ResetIdealActivity( Activity newIdealActivity ); void SetSequenceByName( const char *szSequence ); void SetSequenceById( int iSequence ); @@ -946,6 +1043,25 @@ public: void SetActivityAndSequence(Activity NewActivity, int iSequence, Activity translatedActivity, Activity weaponActivity); +#ifdef MAPBASE + //----------------------------------------------------- + + // Returns the gesture variant of an activity (i.e. "ACT_GESTURE_RANGE_ATTACK1") + static Activity GetGestureVersionOfActivity( Activity inActivity ); + + // Returns the sequence variant of a gesture activity + static Activity GetSequenceVersionOfGesture( Activity inActivity ); + + //----------------------------------------------------- + + virtual bool ShouldPlayFakeSequenceGesture( Activity nActivity, Activity nTranslatedActivity ); + virtual Activity SelectFakeSequenceGesture( Activity nActivity, Activity nTranslatedActivity ); + void PlayFakeSequenceGesture( Activity nActivity, Activity nSequence, Activity nTranslatedSequence ); + + int GetFakeSequenceGesture(); + void ResetFakeSequenceGesture(); +#endif + private: void AdvanceToIdealActivity(void); @@ -959,6 +1075,10 @@ private: Activity m_IdealTranslatedActivity; // Desired actual translated animation state Activity m_IdealWeaponActivity; // Desired weapon animation state +#ifdef MAPBASE + int m_FakeSequenceGestureLayer; // The gesture layer impersonating a sequence (-1 if invalid) +#endif + CNetworkVar(int, m_iDeathPose ); CNetworkVar(int, m_iDeathFrame ); @@ -973,6 +1093,10 @@ public: const CAI_Senses * GetSenses() const { return m_pSenses; } void SetDistLook( float flDistLook ); +#ifdef MAPBASE + void InputSetDistLook( inputdata_t &inputdata ); + void InputSetDistTooFar( inputdata_t &inputdata ); +#endif virtual bool QueryHearSound( CSound *pSound ); virtual bool QuerySeeEntity( CBaseEntity *pEntity, bool bOnlyHateOrFearIfNPC = false ); @@ -1042,6 +1166,9 @@ public: CBaseEntity *GetEnemyOccluder(void); virtual void StartTargetHandling( CBaseEntity *pTargetEnt ); +#ifdef MAPBASE + void InputSetTarget( inputdata_t &inputdata ); +#endif //--------------------------------- @@ -1122,34 +1249,109 @@ private: public: CAI_MoveMonitor m_CommandMoveMonitor; +#ifdef MAPBASE + ThreeState_t m_FriendlyFireOverride = TRS_NONE; + virtual bool FriendlyFireEnabled(); + void InputSetFriendlyFire( inputdata_t &inputdata ); + + // Grenade-related functions from Combine soldiers ported to ai_basenpc so they could be shared. + // + // This is necessary because other NPCs can use them now and many instances where they were used relied on dynamic_casts. + virtual Vector GetAltFireTarget() { return GetEnemy() ? GetEnemy()->BodyTarget(Weapon_ShootPosition()) : vec3_origin; } + virtual void DelayGrenadeCheck(float delay) { ; } + virtual void AddGrenades( int inc, CBaseEntity *pLastGrenade = NULL ) { ; } +#endif + +#ifdef MAPBASE_VSCRIPT +private: + + // VScript stuff uses "VScript" instead of just "Script" to avoid + // confusion with NPC_STATE_SCRIPT or StartScripting + HSCRIPT VScriptGetEnemy(); + void VScriptSetEnemy( HSCRIPT pEnemy ); + Vector VScriptGetEnemyLKP(); + + HSCRIPT VScriptFindEnemyMemory( HSCRIPT pEnemy ); + + int VScriptGetState(); + + void VScriptWake( HSCRIPT hActivator ) { Wake( ToEnt(hActivator) ); } + void VScriptSleep() { Sleep(); } + + int VScriptGetSleepState() { return (int)GetSleepState(); } + void VScriptSetSleepState( int sleepState ) { SetSleepState( (AI_SleepState_t)sleepState ); } + + const char* VScriptGetHintGroup() { return STRING( GetHintGroup() ); } + HSCRIPT VScriptGetHintNode(); + + const char* ScriptGetActivity() { return GetActivityName( GetActivity() ); } + int ScriptGetActivityID() { return GetActivity(); } + void ScriptSetActivity( const char *szActivity ) { SetActivity( (Activity)GetActivityID( szActivity ) ); } + void ScriptSetActivityID( int iActivity ) { SetActivity((Activity)iActivity); } + int ScriptTranslateActivity( const char *szActivity ) { return TranslateActivity( (Activity)GetActivityID( szActivity ) ); } + int ScriptTranslateActivityID( int iActivity ) { return TranslateActivity( (Activity)iActivity ); } + + const char* VScriptGetGestureVersionOfActivity( const char *pszActivity ) { return GetActivityName( GetGestureVersionOfActivity( (Activity)GetActivityID( pszActivity ) ) ); } + int VScriptGetGestureVersionOfActivityID( int iActivity ) { return GetGestureVersionOfActivity( (Activity)iActivity ); } + const char* VScriptGetSequenceVersionOfGesture( const char *pszActivity ) { return GetActivityName( GetSequenceVersionOfGesture( (Activity)GetActivityID( pszActivity ) ) ); } + int VScriptGetSequenceVersionOfGestureID( int iActivity ) { return GetSequenceVersionOfGesture( (Activity)iActivity ); } + + const char* VScriptGetSchedule(); + int VScriptGetScheduleID(); + void VScriptSetSchedule( const char *szSchedule ); + void VScriptSetScheduleID( int iSched ) { SetSchedule( iSched ); } + const char* VScriptGetTask(); + int VScriptGetTaskID(); + + bool VScriptHasCondition( const char *szCondition ) { return HasCondition( GetConditionID( szCondition ) ); } + bool VScriptHasConditionID( int iCondition ) { return HasCondition( iCondition ); } + void VScriptSetCondition( const char *szCondition ) { SetCondition( GetConditionID( szCondition ) ); } + void VScriptClearCondition( const char *szCondition ) { ClearCondition( GetConditionID( szCondition ) ); } + + HSCRIPT VScriptGetExpresser(); + + HSCRIPT VScriptGetCine(); + int GetScriptState() { return m_scriptState; } + + HSCRIPT VScriptGetSquad(); +#endif + //----------------------------------------------------- // Dynamic scripted NPC interactions //----------------------------------------------------- public: float GetInteractionYaw( void ) const { return m_flInteractionYaw; } + bool IsRunningDynamicInteraction( void ) { return (m_iInteractionState != NPCINT_NOT_RUNNING && (m_hCine != NULL)); } + bool IsActiveDynamicInteraction( void ) { return (m_iInteractionState == NPCINT_RUNNING_ACTIVE && (m_hCine != NULL)); } + CAI_BaseNPC *GetInteractionPartner( void ); + protected: void ParseScriptedNPCInteractions( void ); void AddScriptedNPCInteraction( ScriptedNPCInteraction_t *pInteraction ); - const char *GetScriptedNPCInteractionSequence( ScriptedNPCInteraction_t *pInteraction, int iPhase ); + const char *GetScriptedNPCInteractionSequence( ScriptedNPCInteraction_t *pInteraction, int iPhase, bool bOtherNPC = false ); void StartRunningInteraction( CAI_BaseNPC *pOtherNPC, bool bActive ); void StartScriptedNPCInteraction( CAI_BaseNPC *pOtherNPC, ScriptedNPCInteraction_t *pInteraction, Vector vecOtherOrigin, QAngle angOtherAngles ); void CheckForScriptedNPCInteractions( void ); void CalculateValidEnemyInteractions( void ); void CheckForcedNPCInteractions( void ); +#ifdef MAPBASE + // This is checked during automatic dynamic interactions, but not during forced interactions. + // This is so we can control interaction permissions while still letting forced interactions play when needed. + virtual bool InteractionIsAllowed( CAI_BaseNPC *pOtherNPC, ScriptedNPCInteraction_t *pInteraction ); +#endif bool InteractionCouldStart( CAI_BaseNPC *pOtherNPC, ScriptedNPCInteraction_t *pInteraction, Vector &vecOrigin, QAngle &angAngles ); virtual bool CanRunAScriptedNPCInteraction( bool bForced = false ); - bool IsRunningDynamicInteraction( void ) { return (m_iInteractionState != NPCINT_NOT_RUNNING && (m_hCine != NULL)); } - bool IsActiveDynamicInteraction( void ) { return (m_iInteractionState == NPCINT_RUNNING_ACTIVE && (m_hCine != NULL)); } ScriptedNPCInteraction_t *GetRunningDynamicInteraction( void ) { return &(m_ScriptedInteractions[m_iInteractionPlaying]); } void SetInteractionCantDie( bool bCantDie ) { m_bCannotDieDuringInteraction = bCantDie; } bool HasInteractionCantDie( void ); + bool HasValidInteractionsOnCurrentEnemy( void ); + virtual bool CanStartDynamicInteractionDuringMelee() { return false; } void InputForceInteractionWithNPC( inputdata_t &inputdata ); void StartForcedInteraction( CAI_BaseNPC *pNPC, int iInteraction ); void CleanupForcedInteraction( void ); void CalculateForcedInteractionPosition( void ); - CAI_BaseNPC *GetInteractionPartner( void ); private: // Forced interactions @@ -1162,10 +1364,20 @@ private: bool m_bCannotDieDuringInteraction; int m_iInteractionState; int m_iInteractionPlaying; +#ifdef MAPBASE +public: +#endif CUtlVector m_ScriptedInteractions; float m_flInteractionYaw; +#ifdef MAPBASE + // Allows mappers to control dynamic interactions. + // DI added by Mapbase requires this to be on TRS_TRUE (1). Others, like Alyx's interactions, only require TRS_NONE (2). + // TRS_FALSE (0) disables all dynamic interactions, including existing ones. + ThreeState_t m_iDynamicInteractionsAllowed; +#endif + public: //----------------------------------------------------- @@ -1189,6 +1401,11 @@ public: virtual void FearSound( void ) { return; }; virtual void LostEnemySound( void ) { return; }; virtual void FoundEnemySound( void ) { return; }; +#ifdef MAPBASE + // New versions of the above functions which pass the enemy in question as a parameter. Chains to the original by default + virtual void LostEnemySound( CBaseEntity *pEnemy ) { LostEnemySound(); }; + virtual void FoundEnemySound( CBaseEntity *pEnemy ) { FoundEnemySound(); }; +#endif virtual void BarnacleDeathSound( void ) { CTakeDamageInfo info; PainSound( info ); } virtual void SpeakSentence( int sentenceType ) { return; }; @@ -1209,6 +1426,10 @@ public: virtual void PlayerHasIlluminatedNPC( CBasePlayer *pPlayer, float flDot ); virtual void ModifyOrAppendCriteria( AI_CriteriaSet& set ); +#ifdef MAPBASE + virtual void ModifyOrAppendEnemyCriteria( AI_CriteriaSet& set, CBaseEntity *pEnemy ); + virtual void ModifyOrAppendDamageCriteria( AI_CriteriaSet& set, const CTakeDamageInfo &info ); +#endif protected: float SoundWaitTime() const { return m_flSoundWaitTime; } @@ -1226,6 +1447,11 @@ public: int CapabilitiesRemove( int capabilities ); void CapabilitiesClear( void ); +#ifdef MAPBASE + void InputAddCapabilities( inputdata_t &inputdata ); + void InputRemoveCapabilities( inputdata_t &inputdata ); +#endif + private: int m_afCapability; // tells us what a npc can/can't do. @@ -1562,8 +1788,25 @@ public: bool IsWeaponStateChanging( void ); void SetDesiredWeaponState( DesiredWeaponState_t iState ) { m_iDesiredWeaponState = iState; } +#ifdef MAPBASE + virtual bool DoHolster(void); + virtual bool DoUnholster(void); + + virtual bool ShouldUnholsterWeapon(); + virtual bool CanUnholsterWeapon(); + + void InputGiveWeaponHolstered( inputdata_t &inputdata ); + void InputChangeWeapon( inputdata_t &inputdata ); + void InputPickupWeapon( inputdata_t &inputdata ); + void InputPickupItem( inputdata_t &inputdata ); +#endif + // NOTE: The Shot Regulator is used to manage the RangeAttack1 weapon. inline CAI_ShotRegulator* GetShotRegulator() { return &m_ShotRegulator; } +#ifdef MAPBASE + // A special function for ai_weaponmodifier. + inline void SetShotRegulator(CAI_ShotRegulator NewRegulator) { m_ShotRegulator = NewRegulator; } +#endif virtual void OnRangeAttack1(); protected: @@ -1582,6 +1825,10 @@ protected: float m_flLastAttackTime; // Last time that I attacked my current enemy float m_flLastEnemyTime; float m_flNextWeaponSearchTime; // next time to search for a better weapon +#ifdef MAPBASE +public: + int m_iLastHolsteredWeapon; +#endif string_t m_iszPendingWeapon; // THe NPC should create and equip this weapon. bool m_bIgnoreUnseenEnemies; @@ -1624,6 +1871,10 @@ public: void SetHintGroup( string_t name, bool bHintGroupNavLimiting = false ); bool IsLimitingHintGroups( void ) { return m_bHintGroupNavLimiting; } +#ifdef MAPBASE + void InputSetHintGroup( inputdata_t &inputdata ) { SetHintGroup(inputdata.value.StringID()); } +#endif + //--------------------------------- CAI_TacticalServices *GetTacticalServices() { return m_pTacticalServices; } @@ -1663,7 +1914,9 @@ public: //----------------------------------------------------- void InitRelationshipTable( void ); +#ifndef MAPBASE void AddRelationship( const char *pszRelationship, CBaseEntity *pActivator ); +#endif virtual void AddEntityRelationship( CBaseEntity *pEntity, Disposition_t nDisposition, int nPriority ); virtual void AddClassRelationship( Class_T nClass, Disposition_t nDisposition, int nPriority ); @@ -1695,7 +1948,9 @@ public: //--------------------------------- +#ifndef MAPBASE // Moved to CBaseCombatCharacter virtual CBaseEntity *FindNamedEntity( const char *pszName, IEntityFindFilter *pFilter = NULL ); +#endif //--------------------------------- // States @@ -1706,7 +1961,12 @@ public: virtual bool ShouldLookForBetterWeapon(); bool Weapon_IsBetterAvailable ( void ) ; virtual Vector Weapon_ShootPosition( void ); +#ifdef MAPBASE + virtual CBaseCombatWeapon* GiveWeapon( string_t iszWeaponName, bool bDiscardCurrent = true ); + virtual CBaseCombatWeapon* GiveWeaponHolstered( string_t iszWeaponName ); +#else virtual void GiveWeapon( string_t iszWeaponName ); +#endif virtual void OnGivenWeapon( CBaseCombatWeapon *pNewWeapon ) { } bool IsMovingToPickupWeapon(); virtual bool WeaponLOSCondition(const Vector &ownerPos, const Vector &targetPos, bool bSetConditions); @@ -1740,6 +2000,9 @@ public: //--------------------------------- virtual void Ignite( float flFlameLifetime, bool bNPCOnly = true, float flSize = 0.0f, bool bCalledByLevelDesigner = false ); +#ifdef MAPBASE + virtual void EnemyIgnited( CAI_BaseNPC *pVictim ) {} +#endif virtual bool PassesDamageFilter( const CTakeDamageInfo &info ); //--------------------------------- @@ -1803,16 +2066,27 @@ public: //--------------------------------- virtual void PickupWeapon( CBaseCombatWeapon *pWeapon ); +#ifdef MAPBASE + virtual void PickupItem( CBaseEntity *pItem ); +#else virtual void PickupItem( CBaseEntity *pItem ) { }; +#endif CBaseEntity* DropItem( const char *pszItemName, Vector vecPos, QAngle vecAng );// drop an item. //--------------------------------- // Inputs //--------------------------------- +#ifndef MAPBASE // Moved to CBaseCombatCharacter void InputSetRelationship( inputdata_t &inputdata ); +#endif void InputSetEnemyFilter( inputdata_t &inputdata ); +#ifdef MAPBASE + // This is virtual so npc_helicopter can override it + virtual void InputSetHealthFraction( inputdata_t &inputdata ); +#else void InputSetHealth( inputdata_t &inputdata ); +#endif void InputBeginRappel( inputdata_t &inputdata ); void InputSetSquad( inputdata_t &inputdata ); void InputWake( inputdata_t &inputdata ); @@ -1903,6 +2177,16 @@ public: COutputEvent m_OnForcedInteractionAborted; COutputEvent m_OnForcedInteractionFinished; +#ifdef MAPBASE + COutputEHANDLE m_OnHolsterWeapon; + COutputEHANDLE m_OnUnholsterWeapon; + + COutputEHANDLE m_OnItemPickup; + COutputEHANDLE m_OnItemDrop; + + COutputInt m_OnStateChange; +#endif + public: // use this to shrink the bbox temporarily void SetHullSizeNormal( bool force = false ); @@ -1972,6 +2256,9 @@ public: static const char* GetActivityName (int actID); static void AddActivityToSR(const char *actName, int conID); +#ifdef MAPBASE + static int GetOrRegisterActivity( const char *actName ); +#endif static void AddEventToSR(const char *eventName, int conID); static const char* GetEventName (int actID); @@ -1986,6 +2273,10 @@ public: inline void ForceCrouch( void ); inline void ClearForceCrouch( void ); +#ifdef MAPBASE + bool CouldShootIfCrouchingAt( const Vector &vecPosition, const Vector &vecForward, const Vector &vecRight, float flDist = 48.0f ); +#endif + protected: virtual bool Crouch( void ); virtual bool Stand( void ); @@ -2045,6 +2336,16 @@ private: static CAI_GlobalScheduleNamespace gm_SchedulingSymbols; static CAI_ClassScheduleIdSpace gm_ClassScheduleIdSpace; +#ifdef MAPBASE + typedef struct + { + Activity sequence; + Activity gesture; + } actlink_t; + + static actlink_t gm_ActivityGestureLinks[]; +#endif + public: //---------------------------------------------------- // Debugging tools @@ -2089,6 +2390,16 @@ public: CUtlVector m_ScheduleHistory; #endif//AI_MONITOR_FOR_OSCILLATION +#ifdef MAPBASE_VSCRIPT + static ScriptHook_t g_Hook_QueryHearSound; + static ScriptHook_t g_Hook_QuerySeeEntity; + static ScriptHook_t g_Hook_TranslateActivity; + static ScriptHook_t g_Hook_TranslateSchedule; + static ScriptHook_t g_Hook_GetActualShootPosition; + static ScriptHook_t g_Hook_OverrideMove; + static ScriptHook_t g_Hook_ShouldPlayFakeSequenceGesture; +#endif + private: // Break into pieces! @@ -2116,6 +2427,15 @@ public: void InputSetSpeedModifierRadius( inputdata_t &inputdata ); void InputSetSpeedModifierSpeed( inputdata_t &inputdata ); +#ifdef MAPBASE + // Hammer input to change the speed of the NPC (based on 1upD's npc_shadow_walker code) + // Not to be confused with the inputs above + virtual float GetSequenceGroundSpeed( CStudioHdr *pStudioHdr, int iSequence ); + inline float GetSequenceGroundSpeed( int iSequence ) { return GetSequenceGroundSpeed( GetModelPtr(), iSequence ); } + void InputSetSpeedModifier( inputdata_t &inputdata ); + float m_flSpeedModifier; +#endif + virtual bool ShouldProbeCollideAgainstEntity( CBaseEntity *pEntity ); bool m_bPlayerAvoidState; @@ -2675,7 +2995,11 @@ public: derivedClass::AccessClassScheduleIdSpaceDirect().Init( #derivedClass, BaseClass::GetSchedulingSymbols(), &BaseClass::AccessClassScheduleIdSpaceDirect() ); \ derivedClass::gm_SquadSlotIdSpace.Init( &CAI_BaseNPC::gm_SquadSlotNamespace, &BaseClass::gm_SquadSlotIdSpace); +#ifdef MAPBASE +#define ADD_CUSTOM_INTERACTION( interaction ) { CBaseCombatCharacter::AddInteractionWithString( interaction, #interaction ); } +#else #define ADD_CUSTOM_INTERACTION( interaction ) { interaction = CBaseCombatCharacter::GetInteractionID(); } +#endif #define ADD_CUSTOM_SQUADSLOT_NAMED(derivedClass,squadSlotName,squadSlotEN)\ if ( !derivedClass::gm_SquadSlotIdSpace.AddSymbol( squadSlotName, squadSlotEN, "squadslot", derivedClass::gm_pszErrorClassName ) ) return; @@ -3058,10 +3382,19 @@ public: // NOTE: YOU MUST DEFINE THE OUTPUTS IN YOUR CLASS'S DATADESC! // THE DO SO, INSERT THE FOLLOWING MACRO INTO YOUR CLASS'S DATADESC. // +#ifdef MAPBASE +#define DEFINE_BASENPCINTERACTABLE_DATADESC() \ + DEFINE_OUTPUT( m_OnAlyxStartedInteraction, "OnAlyxStartedInteraction" ), \ + DEFINE_OUTPUT( m_OnAlyxFinishedInteraction, "OnAlyxFinishedInteraction" ), \ + DEFINE_OUTPUT( m_OnHacked, "OnHacked" ), \ + DEFINE_INPUTFUNC( FIELD_VOID, "InteractivePowerDown", InputPowerdown ), \ + DEFINE_INPUTFUNC( FIELD_VOID, "Hack", InputDoInteraction ) +#else #define DEFINE_BASENPCINTERACTABLE_DATADESC() \ DEFINE_OUTPUT( m_OnAlyxStartedInteraction, "OnAlyxStartedInteraction" ), \ DEFINE_OUTPUT( m_OnAlyxFinishedInteraction, "OnAlyxFinishedInteraction" ), \ DEFINE_INPUTFUNC( FIELD_VOID, "InteractivePowerDown", InputPowerdown ) +#endif template class CNPCBaseInteractive : public NPC_CLASS, public INPCInteractive @@ -3077,6 +3410,13 @@ public: } +#ifdef MAPBASE + virtual void InputDoInteraction( inputdata_t &inputdata ) + { + NotifyInteraction(inputdata.pActivator ? inputdata.pActivator->MyNPCPointer() : NULL); + } +#endif + // Alyx specific interactions virtual void AlyxStartedInteraction( void ) { @@ -3092,6 +3432,9 @@ public: // Alyx specific interactions COutputEvent m_OnAlyxStartedInteraction; COutputEvent m_OnAlyxFinishedInteraction; +#ifdef MAPBASE + COutputEvent m_OnHacked; +#endif }; // diff --git a/src/game/server/ai_basenpc_schedule.cpp b/src/game/server/ai_basenpc_schedule.cpp index 45ddec10..c20f9194 100644 --- a/src/game/server/ai_basenpc_schedule.cpp +++ b/src/game/server/ai_basenpc_schedule.cpp @@ -470,7 +470,7 @@ CAI_Schedule *CAI_BaseNPC::GetNewSchedule( void ) // You may not be in combat state with no enemy!!! (sjb) 11/4/03 if( m_NPCState == NPC_STATE_COMBAT && !GetEnemy() ) { - DevMsg("**ERROR: Combat State with no enemy! slamming to ALERT\n"); + CGMsg( 1, CON_GROUP_NPC_AI, "**ERROR: Combat State with no enemy! slamming to ALERT\n" ); SetState( NPC_STATE_ALERT ); } @@ -693,7 +693,7 @@ void CAI_BaseNPC::MaintainSchedule ( void ) if ( !GetCurSchedule() || GetCurSchedule()->NumTasks() == 0 ) { - DevMsg("ERROR: Missing or invalid schedule!\n"); + CGMsg( 1, CON_GROUP_NPC_AI, "ERROR: Missing or invalid schedule!\n" ); SetActivity ( ACT_IDLE ); return; } @@ -980,8 +980,12 @@ bool CAI_BaseNPC::FindCoverFromEnemy( bool bNodesOnly, float flMinDistance, floa // FIXME: add to goal if (GetHintNode()) { +#ifdef MAPBASE + GetHintNode()->NPCHandleStartNav( this, true ); +#else GetNavigator()->SetArrivalActivity( GetCoverActivity( GetHintNode() ) ); GetNavigator()->SetArrivalDirection( GetHintNode()->GetDirection() ); +#endif } return true; @@ -1004,7 +1008,7 @@ bool CAI_BaseNPC::FindCoverFromBestSound( Vector *pCoverPos ) } else { - DevMsg( 2, "Attempting to find cover from best sound, but best sound not founc.\n" ); + CGMsg( 2, CON_GROUP_NPC_AI, "Attempting to find cover from best sound, but best sound not founc.\n" ); } return false; @@ -1360,6 +1364,14 @@ void CAI_BaseNPC::StartTask( const Task_t *pTask ) break; case TASK_STOP_MOVING: +#ifdef MAPBASE + if ( GetNavType() == NAV_CLIMB ) + { + // Don't clear the goal so that the climb can finish + DbgNavMsg( this, "Start TASK_STOP_MOVING with climb workaround\n" ); + } + else +#endif if ( ( GetNavigator()->IsGoalSet() && GetNavigator()->IsGoalActive() ) || GetNavType() == NAV_JUMP ) { DbgNavMsg( this, "Start TASK_STOP_MOVING\n" ); @@ -1585,6 +1597,45 @@ void CAI_BaseNPC::StartTask( const Task_t *pTask ) SetWait( pTask->flTaskData ); break; +#ifdef MAPBASE + case TASK_FACE_INTERACTION_ANGLES: + { + if ( !m_hForcedInteractionPartner ) + { + TaskFail( FAIL_NO_TARGET ); + return; + } + + // Get our running interaction from our partner, + // as this should only run with the NPC "receiving" the interaction + ScriptedNPCInteraction_t *pInteraction = m_hForcedInteractionPartner->GetRunningDynamicInteraction(); + + if ( !(pInteraction->iFlags & SCNPC_FLAG_TEST_OTHER_ANGLES) ) + { + TaskComplete(); + return; + } + + // Get our target's origin + Vector vecTarget = m_hForcedInteractionPartner->GetAbsOrigin(); + + // Face the angles the interaction actually wants us at, opposite to the partner + float angInteractionAngle = pInteraction->angRelativeAngles.y; + angInteractionAngle += 180.0f; + + GetMotor()->SetIdealYaw( AngleNormalize( CalcIdealYaw( vecTarget ) + angInteractionAngle ) ); + + if (FacingIdeal()) + TaskComplete(); + else + { + GetMotor()->SetIdealYaw( CalcReasonableFacing( true ) ); + SetTurnActivity(); + } + } + break; +#endif + case TASK_FACE_ENEMY: { Vector vecEnemyLKP = GetEnemyLKP(); @@ -2707,7 +2758,7 @@ void CAI_BaseNPC::StartTask( const Task_t *pTask ) case TASK_SOUND_ANGRY: { // sounds are complete as soon as we get here, cause we've already played them. - DevMsg( 2, "SOUND\n" ); + CGMsg( 2, CON_GROUP_NPC_AI, "SOUND\n" ); TaskComplete(); break; } @@ -2749,7 +2800,7 @@ void CAI_BaseNPC::StartTask( const Task_t *pTask ) { if ( !m_hCine ) { - DevMsg( "Scripted sequence destroyed while in use\n" ); + CGMsg( 1, CON_GROUP_NPC_SCRIPTS, "Scripted sequence destroyed while in use\n" ); TaskFail( FAIL_SCHEDULE_NOT_FOUND ); break; } @@ -2760,13 +2811,20 @@ void CAI_BaseNPC::StartTask( const Task_t *pTask ) { if ( !m_hCine ) { - DevMsg( "Scripted sequence destroyed while in use\n" ); + CGMsg( 1, CON_GROUP_NPC_SCRIPTS, "Scripted sequence destroyed while in use\n" ); TaskFail( FAIL_SCHEDULE_NOT_FOUND ); break; } string_t iszArrivalText; +#ifdef MAPBASE + if ( m_hCine->m_iszPreIdle != NULL_STRING ) + { + iszArrivalText = m_hCine->m_iszPreIdle; + } + else +#endif if ( m_hCine->m_iszEntry != NULL_STRING ) { iszArrivalText = m_hCine->m_iszEntry; @@ -2857,6 +2915,9 @@ void CAI_BaseNPC::StartTask( const Task_t *pTask ) // if ( m_hCine->m_iszPreIdle != NULL_STRING ) { +#ifdef MAPBASE + m_hCine->OnPreIdleSequence( this ); +#endif m_hCine->StartSequence( ( CAI_BaseNPC * )this, m_hCine->m_iszPreIdle, false ); if ( FStrEq( STRING( m_hCine->m_iszPreIdle ), STRING( m_hCine->m_iszPlay ) ) ) { @@ -2969,7 +3030,16 @@ void CAI_BaseNPC::StartTask( const Task_t *pTask ) case TASK_ITEM_PICKUP: { - SetIdealActivity( ACT_PICKUP_GROUND ); +#ifdef MAPBASE + if (GetTarget() && fabs( GetTarget()->WorldSpaceCenter().z - GetAbsOrigin().z ) >= 12.0f) + { + SetIdealActivity( ACT_PICKUP_RACK ); + } + else +#endif + { + SetIdealActivity( ACT_PICKUP_GROUND ); + } } break; @@ -3097,7 +3167,7 @@ void CAI_BaseNPC::StartTask( const Task_t *pTask ) default: { - DevMsg( "No StartTask entry for %s\n", TaskName( task ) ); + CGMsg( 1, CON_GROUP_NPC_AI, "No StartTask entry for %s\n", TaskName( task ) ); } break; } @@ -3244,7 +3314,7 @@ void CAI_BaseNPC::RunTask( const Task_t *pTask ) // Put a debugging check in here if (GetHintNode()->User() != this) { - DevMsg("Hint node (%s) being used by non-owner!\n",GetHintNode()->GetDebugName()); + CGMsg( 1, CON_GROUP_NPC_AI, "Hint node (%s) being used by non-owner!\n", GetHintNode()->GetDebugName() ); } if ( IsActivityFinished() ) @@ -3293,8 +3363,40 @@ void CAI_BaseNPC::RunTask( const Task_t *pTask ) // a navigation while in the middle of a climb if (GetNavType() == NAV_CLIMB) { +#ifdef MAPBASE + if (GetActivity() != ACT_CLIMB_DISMOUNT) + { + // Try to just pause the climb, but dismount if we're in SCHED_FAIL + if (IsCurSchedule( SCHED_FAIL, false )) + { + GetMotor()->MoveClimbStop(); + } + else + { + GetMotor()->MoveClimbPause(); + } + + TaskComplete(); + } + else if (IsActivityFinished()) + { + // Dismount complete. + GetMotor()->MoveClimbStop(); + + // Fix up our position if we have to + Vector vecTeleportOrigin; + if (GetMotor()->MoveClimbShouldTeleportToSequenceEnd( vecTeleportOrigin )) + { + SetLocalOrigin( vecTeleportOrigin ); + } + + TaskComplete(); + } + break; +#else // wait until you reach the end break; +#endif } DbgNavMsg( this, "TASK_STOP_MOVING Complete\n" ); @@ -3339,6 +3441,17 @@ void CAI_BaseNPC::RunTask( const Task_t *pTask ) // If the yaw is locked, this function will not act correctly Assert( GetMotor()->IsYawLocked() == false ); +#ifdef MAPBASE + if ( GetHintNode() && GetHintNode()->OverridesNPCYaw( this ) ) + { + // If the yaw is supposed to use that of a hint node, chain to TASK_FACE_HINTNODE + GetMotor()->SetIdealYaw( GetHintNode()->Yaw() ); + GetMotor()->SetIdealYaw( CalcReasonableFacing( true ) ); // CalcReasonableFacing() is based on previously set ideal yaw + ChainRunTask( TASK_FACE_HINTNODE, pTask->flTaskData ); + break; + } +#endif + Vector vecEnemyLKP = GetEnemyLKP(); if (!FInAimCone( vecEnemyLKP )) { @@ -3380,6 +3493,42 @@ void CAI_BaseNPC::RunTask( const Task_t *pTask ) } break; +#ifdef MAPBASE + case TASK_FACE_INTERACTION_ANGLES: + { + if ( !m_hForcedInteractionPartner ) + { + TaskFail( FAIL_NO_TARGET ); + return; + } + + // Get our running interaction from our partner, + // as this should only run with the NPC "receiving" the interaction + ScriptedNPCInteraction_t *pInteraction = m_hForcedInteractionPartner->GetRunningDynamicInteraction(); + + if ( !(pInteraction->iFlags & SCNPC_FLAG_TEST_OTHER_ANGLES) ) + { + TaskComplete(); + return; + } + + // Get our target's origin + Vector vecTarget = m_hForcedInteractionPartner->GetAbsOrigin(); + + // Face the angles the interaction actually wants us at, opposite to the partner + float angInteractionAngle = pInteraction->angRelativeAngles.y; + angInteractionAngle += 180.0f; + + GetMotor()->SetIdealYawAndUpdate( CalcIdealYaw( vecTarget ) + angInteractionAngle, AI_KEEP_YAW_SPEED ); + + if (IsWaitFinished()) + { + TaskComplete(); + } + } + break; +#endif + case TASK_FIND_COVER_FROM_BEST_SOUND: { switch( GetTaskInterrupt() ) @@ -3668,9 +3817,22 @@ void CAI_BaseNPC::RunTask( const Task_t *pTask ) { if( GetNavigator()->SetGoal(vecGoal) ) { +#ifdef MAPBASE + // Pushaway destinations could be an entire floor above. + // That would get frustrating. Only go to hints within a path distance of 300 units, + // only slightly above our initial search conditions. + if (GetNavigator()->BuildAndGetPathDistToGoal() < 300.0f) + { + pHint->NPCHandleStartNav(this, false); + pHint->DisableForSeconds( 0.1f ); // Force others to find their own. + TaskComplete(); + break; + } +#else pHint->DisableForSeconds( 0.1f ); // Force others to find their own. TaskComplete(); break; +#endif } } } @@ -3881,15 +4043,25 @@ void CAI_BaseNPC::RunTask( const Task_t *pTask ) if ( m_hCine && m_hCine->IsTimeToStart() ) { TaskComplete(); +#ifdef MAPBASE + m_hCine->OnBeginSequence(this); +#else m_hCine->OnBeginSequence(); +#endif // If we have an entry, we have to play it first if ( m_hCine->m_iszEntry != NULL_STRING ) { +#ifdef MAPBASE + m_hCine->OnEntrySequence( this ); +#endif m_hCine->StartSequence( (CAI_BaseNPC *)this, m_hCine->m_iszEntry, true ); } else { +#ifdef MAPBASE + m_hCine->OnActionSequence( this ); +#endif m_hCine->StartSequence( (CAI_BaseNPC *)this, m_hCine->m_iszPlay, true ); } @@ -3904,7 +4076,7 @@ void CAI_BaseNPC::RunTask( const Task_t *pTask ) } else if (!m_hCine) { - DevMsg( "Cine died!\n"); + CGMsg( 1, CON_GROUP_NPC_SCRIPTS, "Cine died!\n" ); TaskComplete(); } else if ( IsRunningDynamicInteraction() ) @@ -3953,6 +4125,15 @@ void CAI_BaseNPC::RunTask( const Task_t *pTask ) m_hCine->SynchronizeSequence( this ); } } + +#ifdef MAPBASE + if ( IsRunningDynamicInteraction() && m_poseInteractionRelativeYaw > -1 ) + { + // Animations in progress require pose parameters to be set every frame, so keep setting the interaction relative yaw pose. + // The random value is added to help it pass server transmit checks. + SetPoseParameter( m_poseInteractionRelativeYaw, GetPoseParameter( m_poseInteractionRelativeYaw ) + RandomFloat( -0.1f, 0.1f ) ); + } +#endif break; } @@ -3960,7 +4141,7 @@ void CAI_BaseNPC::RunTask( const Task_t *pTask ) { if ( !m_hCine ) { - DevMsg( "Scripted sequence destroyed while in use\n" ); + CGMsg( 1, CON_GROUP_NPC_SCRIPTS, "Scripted sequence destroyed while in use\n" ); TaskFail( FAIL_SCHEDULE_NOT_FOUND ); break; } @@ -3979,7 +4160,7 @@ void CAI_BaseNPC::RunTask( const Task_t *pTask ) { if ( !m_hCine ) { - DevMsg( "Scripted sequence destroyed while in use\n" ); + CGMsg( 1, CON_GROUP_NPC_SCRIPTS, "Scripted sequence destroyed while in use\n" ); TaskFail( FAIL_SCHEDULE_NOT_FOUND ); break; } @@ -4136,7 +4317,7 @@ void CAI_BaseNPC::RunTask( const Task_t *pTask ) default: { - DevMsg( "No RunTask entry for %s\n", TaskName( pTask->iTask ) ); + CGMsg( 1, CON_GROUP_NPC_AI, "No RunTask entry for %s\n", TaskName( pTask->iTask ) ); TaskComplete(); } break; @@ -4172,6 +4353,15 @@ void CAI_BaseNPC::SetTurnActivity ( void ) float flYD; flYD = GetMotor()->DeltaIdealYaw(); +#ifdef MAPBASE + // Allow AddTurnGesture() to decide this + if (GetMotor()->AddTurnGesture( flYD )) + { + SetIdealActivity( ACT_IDLE ); + Remember( bits_MEMORY_TURNING ); + return; + } +#else // FIXME: unknown case, update yaw should catch these /* if (GetMotor()->AddTurnGesture( flYD )) @@ -4181,6 +4371,7 @@ void CAI_BaseNPC::SetTurnActivity ( void ) return; } */ +#endif if( flYD <= -80 && flYD >= -100 && SelectWeightedSequence( ACT_90_RIGHT ) != ACTIVITY_NOT_AVAILABLE ) { @@ -4308,7 +4499,7 @@ int CAI_BaseNPC::GetScriptCustomMoveSequence( void ) iSequence = LookupSequence( STRING( m_hCine->m_iszCustomMove ) ); if ( iSequence == ACTIVITY_NOT_AVAILABLE ) { - DevMsg( "SCRIPT_CUSTOM_MOVE: %s has no sequence:%s\n", GetClassname(), STRING(m_hCine->m_iszCustomMove) ); + CGMsg( 1, CON_GROUP_NPC_SCRIPTS, "SCRIPT_CUSTOM_MOVE: %s has no sequence:%s\n", GetClassname(), STRING(m_hCine->m_iszCustomMove) ); } } else if ( m_iszSceneCustomMoveSeq != NULL_STRING ) diff --git a/src/game/server/ai_behavior.cpp b/src/game/server/ai_behavior.cpp index 7071bd50..4730e122 100644 --- a/src/game/server/ai_behavior.cpp +++ b/src/game/server/ai_behavior.cpp @@ -411,6 +411,35 @@ void CAI_BehaviorBase::HandleAnimEvent( animevent_t *pEvent ) m_pBackBridge->BackBridge_HandleAnimEvent( pEvent ); } +#ifdef MAPBASE +//------------------------------------- + +bool CAI_BehaviorBase::CanUnholsterWeapon( void ) +{ + Assert( m_pBackBridge != NULL ); + + return m_pBackBridge->BackBridge_CanUnholsterWeapon(); +} + +//------------------------------------- + +bool CAI_BehaviorBase::ShouldPickADeathPose( void ) +{ + Assert( m_pBackBridge != NULL ); + + return m_pBackBridge->BackBridge_ShouldPickADeathPose(); +} + +//------------------------------------- + +bool CAI_BehaviorBase::CanTranslateCrouchActivity( void ) +{ + Assert( m_pBackBridge != NULL ); + + return m_pBackBridge->BackBridge_CanTranslateCrouchActivity(); +} +#endif + //------------------------------------- bool CAI_BehaviorBase::NotifyChangeBehaviorStatus( bool fCanFinishSchedule ) diff --git a/src/game/server/ai_behavior.h b/src/game/server/ai_behavior.h index addac6f9..00d46265 100644 --- a/src/game/server/ai_behavior.h +++ b/src/game/server/ai_behavior.h @@ -130,6 +130,11 @@ public: void BridgeModifyOrAppendCriteria( AI_CriteriaSet& criteriaSet ); void BridgeTeleport( const Vector *newPosition, const QAngle *newAngles, const Vector *newVelocity ); void BridgeHandleAnimEvent( animevent_t *pEvent ); +#ifdef MAPBASE + bool BridgeCanUnholsterWeapon( void ); + bool BridgeShouldPickADeathPose( void ); + bool BridgeCanTranslateCrouchActivity( void ); +#endif virtual void GatherConditions(); virtual void GatherConditionsNotActive() { return; } // Override this and your behavior will call this in place of GatherConditions() when your behavior is NOT the active one. @@ -215,6 +220,11 @@ protected: virtual void ModifyOrAppendCriteria( AI_CriteriaSet& criteriaSet ); virtual void Teleport( const Vector *newPosition, const QAngle *newAngles, const Vector *newVelocity ); virtual void HandleAnimEvent( animevent_t *pEvent ); +#ifdef MAPBASE + virtual bool CanUnholsterWeapon( void ); + virtual bool ShouldPickADeathPose( void ); + virtual bool CanTranslateCrouchActivity( void ); +#endif virtual bool ShouldAlwaysThink(); @@ -361,6 +371,14 @@ public: virtual void BackBridge_HandleAnimEvent( animevent_t *pEvent ) = 0; +#ifdef MAPBASE + // For func_tank behavior + virtual bool BackBridge_CanUnholsterWeapon( void ) = 0; + + virtual bool BackBridge_ShouldPickADeathPose( void ) = 0; + virtual bool BackBridge_CanTranslateCrouchActivity( void ) = 0; +#endif + //------------------------------------- }; @@ -457,6 +475,11 @@ public: Activity GetFlinchActivity( bool bHeavyDamage, bool bGesture ); bool OnCalcBaseMove( AILocalMoveGoal_t *pMoveGoal, float distClear, AIMoveResult_t *pResult ); void HandleAnimEvent( animevent_t *pEvent ); +#ifdef MAPBASE + bool CanUnholsterWeapon( void ); + bool ShouldPickADeathPose( void ); + bool CanTranslateCrouchActivity( void ); +#endif bool ShouldAlwaysThink(); @@ -517,6 +540,14 @@ private: void BackBridge_HandleAnimEvent( animevent_t *pEvent ); +#ifdef MAPBASE + // For func_tank behavior + bool BackBridge_CanUnholsterWeapon( void ); + + bool BackBridge_ShouldPickADeathPose( void ); + bool BackBridge_CanTranslateCrouchActivity( void ); +#endif + CAI_BehaviorBase **AccessBehaviors(); int NumBehaviors(); @@ -887,6 +918,29 @@ inline void CAI_BehaviorBase::BridgeHandleAnimEvent( animevent_t *pEvent ) HandleAnimEvent( pEvent ); } +#ifdef MAPBASE +//----------------------------------------------------------------------------- + +inline bool CAI_BehaviorBase::BridgeCanUnholsterWeapon( void ) +{ + return CanUnholsterWeapon(); +} + +//----------------------------------------------------------------------------- + +inline bool CAI_BehaviorBase::BridgeShouldPickADeathPose( void ) +{ + return ShouldPickADeathPose(); +} + +//----------------------------------------------------------------------------- + +inline bool CAI_BehaviorBase::BridgeCanTranslateCrouchActivity( void ) +{ + return CanTranslateCrouchActivity(); +} +#endif + //----------------------------------------------------------------------------- template @@ -1462,6 +1516,32 @@ inline void CAI_BehaviorHost::BackBridge_HandleAnimEvent( animevent_t BaseClass::HandleAnimEvent( pEvent ); } +#ifdef MAPBASE +//------------------------------------- + +template +inline bool CAI_BehaviorHost::BackBridge_CanUnholsterWeapon( void ) +{ + return BaseClass::CanUnholsterWeapon(); +} + +//------------------------------------- + +template +inline bool CAI_BehaviorHost::BackBridge_ShouldPickADeathPose( void ) +{ + return BaseClass::ShouldPickADeathPose(); +} + +//------------------------------------- + +template +inline bool CAI_BehaviorHost::BackBridge_CanTranslateCrouchActivity( void ) +{ + return BaseClass::CanTranslateCrouchActivity(); +} +#endif + //------------------------------------- template @@ -1865,6 +1945,41 @@ inline void CAI_BehaviorHost::HandleAnimEvent( animevent_t *pEvent ) return BaseClass::HandleAnimEvent( pEvent ); } +#ifdef MAPBASE +//------------------------------------- + +template +inline bool CAI_BehaviorHost::CanUnholsterWeapon( void ) +{ + if ( m_pCurBehavior ) + return m_pCurBehavior->BridgeCanUnholsterWeapon(); + + return BaseClass::CanUnholsterWeapon(); +} + +//------------------------------------- + +template +inline bool CAI_BehaviorHost::ShouldPickADeathPose( void ) +{ + if (m_pCurBehavior) + return m_pCurBehavior->BridgeShouldPickADeathPose(); + + return BaseClass::ShouldPickADeathPose(); +} + +//------------------------------------- + +template +inline bool CAI_BehaviorHost::CanTranslateCrouchActivity( void ) +{ + if (m_pCurBehavior) + return m_pCurBehavior->BridgeCanTranslateCrouchActivity(); + + return BaseClass::CanTranslateCrouchActivity(); +} +#endif + //------------------------------------- template diff --git a/src/game/server/ai_behavior_assault.cpp b/src/game/server/ai_behavior_assault.cpp index e02627cc..bacb4d6a 100644 --- a/src/game/server/ai_behavior_assault.cpp +++ b/src/game/server/ai_behavior_assault.cpp @@ -251,7 +251,12 @@ CAssaultPoint *CAI_AssaultBehavior::FindAssaultPoint( string_t iszAssaultPointNa CUtlVectorpAssaultPoints; CUtlVectorpClearAssaultPoints; +#ifdef MAPBASE + // Prevents non-assault points (e.g. rally points) from crashing the game + CAssaultPoint *pAssaultEnt = dynamic_cast(gEntList.FindEntityByName( NULL, iszAssaultPointName )); +#else CAssaultPoint *pAssaultEnt = (CAssaultPoint *)gEntList.FindEntityByName( NULL, iszAssaultPointName ); +#endif while( pAssaultEnt != NULL ) { diff --git a/src/game/server/ai_behavior_fear.cpp b/src/game/server/ai_behavior_fear.cpp index a6eeef3e..4d25da24 100644 --- a/src/game/server/ai_behavior_fear.cpp +++ b/src/game/server/ai_behavior_fear.cpp @@ -20,6 +20,9 @@ BEGIN_DATADESC( CAI_FearBehavior ) DEFINE_FIELD( m_hMovingToHint, FIELD_EHANDLE ), DEFINE_EMBEDDED( m_SafePlaceMoveMonitor ), DEFINE_FIELD( m_flDeferUntil, FIELD_TIME ), +#ifdef MAPBASE + DEFINE_FIELD( m_hFearGoal, FIELD_EHANDLE ), +#endif END_DATADESC(); #define BEHAVIOR_FEAR_SAFETY_TIME 5 @@ -61,6 +64,11 @@ void CAI_FearBehavior::StartTask( const Task_t *pTask ) m_hSafePlaceHint = m_hMovingToHint; m_hSafePlaceHint->Lock( GetOuter() ); m_SafePlaceMoveMonitor.SetMark( GetOuter(), FEAR_SAFE_PLACE_TOLERANCE ); +#ifdef MAPBASE + m_hSafePlaceHint->NPCStartedUsing( GetOuter() ); + if (m_hFearGoal) + m_hFearGoal->m_OnArriveAtFearNode.FireOutput(m_hSafePlaceHint, GetOuter()); +#endif TaskComplete(); break; @@ -149,7 +157,11 @@ void CAI_FearBehavior::RunTask( const Task_t *pTask ) } else { +#ifdef MAPBASE + m_hMovingToHint->NPCHandleStartNav( GetOuter(), true ); +#else GetNavigator()->SetArrivalDirection( m_hMovingToHint->GetAbsAngles() ); +#endif } } break; @@ -231,6 +243,10 @@ void CAI_FearBehavior::ReleaseAllHints() // If I have a safe place, unlock it for others. m_hSafePlaceHint->Unlock(); +#ifdef MAPBASE + m_hSafePlaceHint->NPCStoppedUsing(GetOuter()); +#endif + // Don't make it available right away. I probably left for a good reason. // We also don't want to oscillate m_hSafePlaceHint->DisableForSeconds( 4.0f ); @@ -274,6 +290,22 @@ bool CAI_FearBehavior::CanSelectSchedule() if( GetOuter()->IRelationType(pEnemy) != D_FR ) return false; +#ifdef MAPBASE + // Don't run fear behavior if we've been ordered somewhere + if (GetOuter()->GetCommandGoal() != vec3_invalid) + return false; + + // Don't run fear behavior if we're running any non-follow behaviors + if (GetOuter()->GetRunningBehavior() && GetOuter()->GetRunningBehavior() != this && !FStrEq(GetOuter()->GetRunningBehavior()->GetName(), "Follow")) + return false; + + if (m_hFearGoal && m_iszFearTarget != NULL_STRING) + { + if (pEnemy->NameMatches(m_iszFearTarget) || pEnemy->ClassMatches(m_iszFearTarget)) + return true; + } +#endif + if( !pEnemy->ClassMatches("npc_hunter") ) return false; @@ -457,6 +489,9 @@ CAI_Hint *CAI_FearBehavior::FindFearWithdrawalDest() hintCriteria.AddHintType( HINT_PLAYER_ALLY_FEAR_DEST ); hintCriteria.SetFlag( bits_HINT_NODE_VISIBLE_TO_PLAYER | bits_HINT_NOT_CLOSE_TO_ENEMY /*| bits_HINT_NODE_IN_VIEWCONE | bits_HINT_NPC_IN_NODE_FOV*/ ); +#ifdef MAPBASE + hintCriteria.SetFlag(bits_HINT_NODE_USE_GROUP); +#endif hintCriteria.AddIncludePosition( AI_GetSinglePlayer()->GetAbsOrigin(), ( ai_fear_player_dist.GetFloat() ) ); pHint = CAI_HintManager::FindHint( pOuter, hintCriteria ); @@ -478,6 +513,108 @@ CAI_Hint *CAI_FearBehavior::FindFearWithdrawalDest() return pHint; } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +Activity CAI_FearBehavior::NPC_TranslateActivity( Activity activity ) +{ + if ( activity == ACT_IDLE && m_hSafePlaceHint && m_hSafePlaceHint->HintActivityName() != NULL_STRING ) + { + return GetOuter()->GetHintActivity(m_hSafePlaceHint->HintType(), (Activity)CAI_BaseNPC::GetActivityID( STRING(m_hSafePlaceHint->HintActivityName()) ) ); + } + return BaseClass::NPC_TranslateActivity( activity ); +} + +//----------------------------------------------------------------------------- +// Updates the fear goal's target. +//----------------------------------------------------------------------------- +void CAI_FearBehavior::OnRestore() +{ + BaseClass::OnRestore(); + + if (m_hFearGoal.Get() != NULL) + { + m_iszFearTarget = m_hFearGoal->m_target; + } +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CAI_FearBehavior::SetParameters( CAI_FearGoal *pGoal, string_t target ) +{ + m_hFearGoal = pGoal; + m_iszFearTarget = target; +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +//============================================================================= +//============================================================================= +// >AI_GOAL_FEAR +//============================================================================= +//============================================================================= +LINK_ENTITY_TO_CLASS( ai_goal_fear, CAI_FearGoal ); + +BEGIN_DATADESC( CAI_FearGoal ) + //DEFINE_KEYFIELD( m_iSomething, FIELD_INTEGER, "something" ), + + // Inputs + DEFINE_INPUTFUNC( FIELD_VOID, "Activate", InputActivate ), + + // Outputs + //DEFINE_OUTPUT( m_OnSeeFearEntity, "OnSeeFearEntity" ), + DEFINE_OUTPUT( m_OnArriveAtFearNode, "OnArrivedAtNode" ), +END_DATADESC() + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CAI_FearGoal::EnableGoal( CAI_BaseNPC *pAI ) +{ + CAI_FearBehavior *pBehavior; + + if ( !pAI->GetBehavior( &pBehavior ) ) + { + return; + } + + pBehavior->SetParameters(this, m_target); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CAI_FearGoal::DisableGoal( CAI_BaseNPC *pAI ) +{ + CAI_FearBehavior *pBehavior; + + if ( !pAI->GetBehavior( &pBehavior ) ) + { + return; + } + + pBehavior->SetParameters(NULL, NULL_STRING); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : &inputdata - +//----------------------------------------------------------------------------- +void CAI_FearGoal::InputActivate( inputdata_t &inputdata ) +{ + BaseClass::InputActivate( inputdata ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : &inputdata - +//----------------------------------------------------------------------------- +void CAI_FearGoal::InputDeactivate( inputdata_t &inputdata ) +{ + BaseClass::InputDeactivate( inputdata ); +} +#endif + AI_BEGIN_CUSTOM_SCHEDULE_PROVIDER( CAI_FearBehavior ) DECLARE_TASK( TASK_FEAR_GET_PATH_TO_SAFETY_HINT ) @@ -531,6 +668,25 @@ AI_BEGIN_CUSTOM_SCHEDULE_PROVIDER( CAI_FearBehavior ) //=============================================== //=============================================== +#ifdef MAPBASE + DEFINE_SCHEDULE + ( + SCHED_FEAR_STAY_IN_SAFE_PLACE, + + " Tasks" + " TASK_FEAR_WAIT_FOR_SAFETY 0" + "" + " Interrupts" + "" + " COND_NEW_ENEMY" + " COND_HEAR_DANGER" + " COND_FEAR_ENEMY_CLOSE" + " COND_FEAR_ENEMY_TOO_CLOSE" + " COND_CAN_RANGE_ATTACK1" + " COND_FEAR_SEPARATED_FROM_PLAYER" + " COND_ENEMY_DEAD" // Allows the fearful to follow the player when enemy dies + ); +#else DEFINE_SCHEDULE ( SCHED_FEAR_STAY_IN_SAFE_PLACE, @@ -547,6 +703,7 @@ AI_BEGIN_CUSTOM_SCHEDULE_PROVIDER( CAI_FearBehavior ) " COND_CAN_RANGE_ATTACK1" " COND_FEAR_SEPARATED_FROM_PLAYER" ); +#endif AI_END_CUSTOM_SCHEDULE_PROVIDER() diff --git a/src/game/server/ai_behavior_fear.h b/src/game/server/ai_behavior_fear.h index b13848cb..e92ae689 100644 --- a/src/game/server/ai_behavior_fear.h +++ b/src/game/server/ai_behavior_fear.h @@ -20,6 +20,37 @@ #include "ai_behavior.h" +#ifdef MAPBASE +#include "ai_goalentity.h" + +//========================================================= +//========================================================= +class CAI_FearGoal : public CAI_GoalEntity +{ + DECLARE_CLASS( CAI_FearGoal, CAI_GoalEntity ); +public: + CAI_FearGoal() + { + } + + void EnableGoal( CAI_BaseNPC *pAI ); + void DisableGoal( CAI_BaseNPC *pAI ); + + // Inputs + virtual void InputActivate( inputdata_t &inputdata ); + virtual void InputDeactivate( inputdata_t &inputdata ); + + // Note that the outer is the caller in these outputs + //COutputEvent m_OnSeeFearEntity; + COutputEvent m_OnArriveAtFearNode; + + DECLARE_DATADESC(); + +protected: + // Put something here +}; +#endif + class CAI_FearBehavior : public CAI_SimpleBehavior { DECLARE_CLASS( CAI_FearBehavior, CAI_SimpleBehavior ); @@ -56,6 +87,17 @@ public: void BuildScheduleTestBits(); int TranslateSchedule( int scheduleType ); +#ifdef MAPBASE + virtual Activity NPC_TranslateActivity( Activity activity ); + + virtual void OnRestore(); + virtual void SetParameters( CAI_FearGoal *pGoal, string_t target ); + CHandle m_hFearGoal; + + // Points to goal's fear target + string_t m_iszFearTarget; +#endif + enum { diff --git a/src/game/server/ai_behavior_follow.cpp b/src/game/server/ai_behavior_follow.cpp index 0ebca7a0..58e16ff7 100644 --- a/src/game/server/ai_behavior_follow.cpp +++ b/src/game/server/ai_behavior_follow.cpp @@ -20,6 +20,9 @@ #ifdef HL2_EPISODIC #include "info_darknessmode_lightsource.h" +#ifdef MAPBASE + #include "globalstate.h" +#endif #endif // memdbgon must be the last include file in a .cpp file!!! @@ -403,8 +406,13 @@ bool CAI_FollowBehavior::SetFollowGoal( CAI_FollowGoal *pGoal, bool fFinishCurSc } SetFollowTarget( pGoal->GetGoalEntity() ); +#ifdef MAPBASE + Assert( pGoal->m_iFormation < AIF_NUM_FORMATIONS ); + SetParameters( AI_FollowParams_t( (AI_Formations_t)pGoal->m_iFormation, pGoal->m_bNormalMemoryDiscard ) ); +#else Assert( pGoal->m_iFormation == AIF_SIMPLE || pGoal->m_iFormation == AIF_WIDE || pGoal->m_iFormation == AIF_MEDIUM || pGoal->m_iFormation == AIF_SIDEKICK || pGoal->m_iFormation == AIF_VORTIGAUNT ); SetParameters( AI_FollowParams_t( (AI_Formations_t)pGoal->m_iFormation ) ); +#endif m_hFollowGoalEnt = pGoal; m_flTimeUpdatedFollowPosition = 0; return true; @@ -764,7 +772,12 @@ void CAI_FollowBehavior::GatherConditions( void ) #ifdef HL2_EPISODIC // Let followers know if the player is lit in the darkness +#ifdef MAPBASE + // If the darkness mode counter is 1, follow behavior is not affected by darkness. + if ( GetFollowTarget()->IsPlayer() && HL2GameRules()->IsAlyxInDarknessMode() && GlobalEntity_GetCounter("ep_alyx_darknessmode") != 1 ) +#else if ( GetFollowTarget()->IsPlayer() && HL2GameRules()->IsAlyxInDarknessMode() ) +#endif { if ( LookerCouldSeeTargetInDarkness( GetOuter(), GetFollowTarget() ) ) { @@ -907,6 +920,11 @@ void CAI_FollowBehavior::ClearFollowPoint() { if ( GetHintNode() && GetHintNode()->HintType() == HINT_FOLLOW_WAIT_POINT ) { +#ifdef MAPBASE + // If we were in range, we were probably already using it + if (GetFollowTarget() && (GetHintNode()->GetAbsOrigin() - GetFollowTarget()->GetAbsOrigin()).LengthSqr() < Square(MAX(m_FollowNavGoal.followPointTolerance, GetGoalRange()))) + GetHintNode()->NPCStoppedUsing(GetOuter()); +#endif GetHintNode()->Unlock(); SetHintNode( NULL ); } @@ -931,7 +949,14 @@ CAI_Hint *CAI_FollowBehavior::FindFollowPoint() CHintCriteria hintCriteria; hintCriteria.SetHintType( HINT_FOLLOW_WAIT_POINT ); +#ifdef MAPBASE + // NOTE: Does this make them stop following? + hintCriteria.SetGroup( GetOuter()->GetHintGroup() ); + hintCriteria.SetFlag( bits_HINT_NODE_VISIBLE | bits_HINT_NODE_NEAREST | bits_HINT_NODE_USE_GROUP ); + //hintCriteria.SetFlag( bits_HINT_NODE_VISIBLE | bits_HINT_NODE_NEAREST ); +#else hintCriteria.SetFlag( bits_HINT_NODE_VISIBLE | bits_HINT_NODE_NEAREST ); +#endif // Add the search position hintCriteria.AddIncludePosition( GetGoalPosition(), MAX( m_FollowNavGoal.followPointTolerance, GetGoalRange() ) ); @@ -1033,6 +1058,9 @@ int CAI_FollowBehavior::SelectScheduleFollowPoints() { if ( bNewHint || distSqToPoint > WAIT_HINT_MIN_DIST ) return SCHED_FOLLOWER_GO_TO_WAIT_POINT; +#ifdef MAPBASE + GetHintNode()->NPCStartedUsing(GetOuter()); +#endif if ( !ShouldIgnoreFollowPointFacing() ) return SCHED_FOLLOWER_STAND_AT_WAIT_POINT; } @@ -2111,6 +2139,9 @@ bool CAI_FollowBehavior::ShouldAlwaysThink() BEGIN_DATADESC( CAI_FollowGoal ) DEFINE_KEYFIELD( m_iFormation, FIELD_INTEGER, "Formation" ), +#ifdef MAPBASE + DEFINE_KEYFIELD( m_bNormalMemoryDiscard, FIELD_BOOLEAN, "NormalMemoryDiscard" ), +#endif #ifdef HL2_EPISODIC DEFINE_INPUTFUNC( FIELD_VOID, "OutsideTransition", InputOutsideTransition ), diff --git a/src/game/server/ai_behavior_follow.h b/src/game/server/ai_behavior_follow.h index ad734f53..0ded5b78 100644 --- a/src/game/server/ai_behavior_follow.h +++ b/src/game/server/ai_behavior_follow.h @@ -36,6 +36,9 @@ enum AI_Formations_t AIF_SIDEKICK, AIF_HUNTER, AIF_VORTIGAUNT, +#ifdef MAPBASE + AIF_NUM_FORMATIONS, +#endif }; enum AI_FollowFormationFlags_t @@ -68,6 +71,10 @@ public: int m_iFormation; +#ifdef MAPBASE + bool m_bNormalMemoryDiscard = false; +#endif + DECLARE_DATADESC(); }; diff --git a/src/game/server/ai_behavior_lead.cpp b/src/game/server/ai_behavior_lead.cpp index ca25ae8d..b1a954f9 100644 --- a/src/game/server/ai_behavior_lead.cpp +++ b/src/game/server/ai_behavior_lead.cpp @@ -543,8 +543,12 @@ int CAI_LeadBehavior::SelectSchedule() if ( !m_flWeaponSafetyTimeOut || (m_flWeaponSafetyTimeOut > gpGlobals->curtime) ) return SCHED_LEAD_PLAYERNEEDSWEAPON; +#ifdef MAPBASE + pFollower->GiveNamedItem( STRING(m_weaponname) ); +#else string_t iszItem = AllocPooledString( "weapon_bugbait" ); pFollower->GiveNamedItem( STRING(iszItem) ); +#endif } } @@ -1649,6 +1653,9 @@ public: private: string_t m_iszWeaponName; +#ifdef MAPBASE + float m_flTimeoutTime = 60; +#endif string_t m_iszMissingWeaponConceptModifier; DECLARE_DATADESC(); @@ -1664,6 +1671,9 @@ LINK_ENTITY_TO_CLASS( ai_goal_lead_weapon, CAI_LeadGoal_Weapon ); BEGIN_DATADESC( CAI_LeadGoal_Weapon ) DEFINE_KEYFIELD( m_iszWeaponName, FIELD_STRING, "WeaponName"), +#ifdef MAPBASE + DEFINE_KEYFIELD( m_flTimeoutTime, FIELD_FLOAT, "TimeoutTime" ), +#endif DEFINE_KEYFIELD( m_iszMissingWeaponConceptModifier, FIELD_STRING, "MissingWeaponConceptModifier"), END_DATADESC() @@ -1688,6 +1698,10 @@ void CAI_LeadGoal_Weapon::InputActivate( inputdata_t &inputdata ) CAI_LeadBehavior *pBehavior = GetLeadBehavior(); if ( pBehavior ) { +#ifdef MAPBASE + pBehavior->SetWaitForWeapon( m_iszWeaponName, m_flTimeoutTime ); +#else pBehavior->SetWaitForWeapon( m_iszWeaponName ); +#endif } } diff --git a/src/game/server/ai_behavior_lead.h b/src/game/server/ai_behavior_lead.h index d59a87d3..ef4bb025 100644 --- a/src/game/server/ai_behavior_lead.h +++ b/src/game/server/ai_behavior_lead.h @@ -9,12 +9,19 @@ #include "simtimer.h" #include "ai_behavior.h" +#ifdef NEW_RESPONSE_SYSTEM +#include "ai_speechconcept.h" +#endif #if defined( _WIN32 ) #pragma once #endif +#ifdef NEW_RESPONSE_SYSTEM +typedef CAI_Concept AIConcept_t; +#else typedef const char *AIConcept_t; +#endif // Speak concepts #define TLK_LEAD_START "TLK_LEAD_START" @@ -125,7 +132,11 @@ public: bool Connect( CAI_LeadBehaviorHandler *); bool Disconnect( CAI_LeadBehaviorHandler *); +#ifdef MAPBASE + void SetWaitForWeapon( string_t iszWeaponName, float flTimeout = 60 ) { m_weaponname = iszWeaponName; m_flWeaponSafetyTimeOut = gpGlobals->curtime + flTimeout; } +#else void SetWaitForWeapon( string_t iszWeaponName ) { m_weaponname = iszWeaponName; m_flWeaponSafetyTimeOut = gpGlobals->curtime + 60; } +#endif enum { diff --git a/src/game/server/ai_behavior_rappel.cpp b/src/game/server/ai_behavior_rappel.cpp index 7dfe4e14..a9a75e91 100644 --- a/src/game/server/ai_behavior_rappel.cpp +++ b/src/game/server/ai_behavior_rappel.cpp @@ -125,6 +125,9 @@ bool CAI_RappelBehavior::KeyValue( const char *szKeyName, const char *szValue ) void CAI_RappelBehavior::Precache() { +#ifdef MAPBASE + CBaseEntity::PrecacheModel( "cable/cable_rappel.vmt" ); +#endif CBaseEntity::PrecacheModel( "cable/cable.vmt" ); } @@ -300,7 +303,14 @@ void CAI_RappelBehavior::GatherConditions() if( HasCondition( COND_CAN_RANGE_ATTACK1 ) ) { // Shoot at the enemy so long as I'm six feet or more above them. +#ifdef MAPBASE + // There seems to be an underlying issue here. COND_CAN_RANGE_ATTACK1 should not be valid without an enemy, + // but crashes have been reported from GetEnemy() returning null in this code. + Assert( GetEnemy() ); + if( GetEnemy() && (GetAbsOrigin().z - GetEnemy()->GetAbsOrigin().z >= 36.0f) && GetOuter()->GetShotRegulator()->ShouldShoot() ) +#else if( (GetAbsOrigin().z - GetEnemy()->GetAbsOrigin().z >= 36.0f) && GetOuter()->GetShotRegulator()->ShouldShoot() ) +#endif { Activity activity = GetOuter()->TranslateActivity( ACT_GESTURE_RANGE_ATTACK1 ); Assert( activity != ACT_INVALID ); @@ -386,7 +396,11 @@ void CAI_RappelBehavior::CreateZipline() if( attachment > 0 ) { CBeam *pBeam; +#ifdef MAPBASE + pBeam = CBeam::BeamCreate( "cable/cable_rappel.vmt", 1 ); +#else pBeam = CBeam::BeamCreate( "cable/cable.vmt", 1 ); +#endif pBeam->SetColor( 150, 150, 150 ); pBeam->SetWidth( 0.3 ); pBeam->SetEndWidth( 0.3 ); diff --git a/src/game/server/ai_behavior_standoff.cpp b/src/game/server/ai_behavior_standoff.cpp index 7783d162..8ec5bde1 100644 --- a/src/game/server/ai_behavior_standoff.cpp +++ b/src/game/server/ai_behavior_standoff.cpp @@ -250,6 +250,23 @@ void CAI_StandoffBehavior::SetActive( bool fActive ) { if ( fActive != m_fActive ) { +#ifdef MAPBASE + // These sentences are only spoken if the standoff behavior is active, so they have to be arranged separately + if ( fActive ) + { + m_fActive = fActive; + NotifyChangeBehaviorStatus(); + + GetOuter()->SpeakSentence( STANDOFF_SENTENCE_BEGIN_STANDOFF ); + } + else + { + GetOuter()->SpeakSentence( STANDOFF_SENTENCE_END_STANDOFF ); + + m_fActive = fActive; + NotifyChangeBehaviorStatus(); + } +#else if ( fActive ) { GetOuter()->SpeakSentence( STANDOFF_SENTENCE_BEGIN_STANDOFF ); @@ -261,6 +278,7 @@ void CAI_StandoffBehavior::SetActive( bool fActive ) m_fActive = fActive; NotifyChangeBehaviorStatus(); +#endif } } @@ -288,7 +306,12 @@ bool CAI_StandoffBehavior::CanSelectSchedule() if ( !m_fActive ) return false; +#ifdef MAPBASE + // Allow NPCs with innate range attacks to use standoffs + return ( GetNpcState() == NPC_STATE_COMBAT && (GetOuter()->GetActiveWeapon() != NULL || GetOuter()->CapabilitiesGet() & bits_CAP_INNATE_RANGE_ATTACK1) ); +#else return ( GetNpcState() == NPC_STATE_COMBAT && GetOuter()->GetActiveWeapon() != NULL ); +#endif } //------------------------------------- @@ -526,6 +549,11 @@ int CAI_StandoffBehavior::SelectScheduleCheckCover( void ) if ( GetOuter()->GetShotRegulator()->IsInRestInterval() ) { StandoffMsg( "Regulated to not shoot\n" ); +#ifdef MAPBASE + if ( GetHintType() == HINT_TACTICAL_COVER_MED || GetCoverActivity() == ACT_COVER_MED ) + SetPosture( AIP_CROUCHING_MED ); + else +#endif if ( GetHintType() == HINT_TACTICAL_COVER_LOW ) SetPosture( AIP_CROUCHING ); else @@ -545,7 +573,11 @@ int CAI_StandoffBehavior::SelectScheduleEstablishAim( void ) { if ( HasCondition( COND_ENEMY_OCCLUDED ) ) { +#if EXPANDED_HL2_COVER_ACTIVITIES + if ( GetPosture() == AIP_CROUCHING || GetPosture() == AIP_CROUCHING_MED ) +#else if ( GetPosture() == AIP_CROUCHING ) +#endif { // force a stand up, just in case GetOuter()->SpeakSentence( STANDOFF_SENTENCE_STAND_CHECK_TARGET ); @@ -583,7 +615,11 @@ int CAI_StandoffBehavior::SelectScheduleAttack( void ) !HasCondition( COND_CAN_MELEE_ATTACK1 ) && HasCondition( COND_TOO_FAR_TO_ATTACK ) ) { +#ifdef MAPBASE + if ( (GetOuter()->GetActiveWeapon() && ( GetOuter()->GetActiveWeapon()->CapabilitiesGet() & bits_CAP_WEAPON_RANGE_ATTACK1 )) || GetOuter()->CapabilitiesGet() & bits_CAP_INNATE_RANGE_ATTACK1 ) +#else if ( GetOuter()->GetActiveWeapon() && ( GetOuter()->GetActiveWeapon()->CapabilitiesGet() & bits_CAP_WEAPON_RANGE_ATTACK1 ) ) +#endif { if ( !HasCondition( COND_ENEMY_OCCLUDED ) || random->RandomInt(0,99) < 50 ) // Don't advance, just fire anyway @@ -656,6 +692,15 @@ Activity CAI_MappedActivityBehavior_Temporary::GetMappedActivity( AI_Posture_t p { if ( posture != AIP_STANDING ) { +#if EXPANDED_HL2_COVER_ACTIVITIES + // See UpdateTranslateActivityMap() for more information on what this is for + if ( posture == AIP_CROUCHING_MED ) + { + if (activity != ACT_RANGE_ATTACK1) + posture = AIP_CROUCHING; + } +#endif + unsigned short iActivityTranslation = m_ActivityMap.Find( MAKE_ACTMAP_KEY( posture, activity ) ); if ( iActivityTranslation != m_ActivityMap.InvalidIndex() ) { @@ -673,10 +718,28 @@ Activity CAI_StandoffBehavior::NPC_TranslateActivity( Activity activity ) Activity coverActivity = GetCoverActivity(); if ( coverActivity != ACT_INVALID ) { +#ifdef MAPBASE + if ( GetPosture() == AIP_STANDING ) + { + if ( coverActivity == ACT_COVER_LOW ) + SetPosture( AIP_CROUCHING ); + else if ( coverActivity == ACT_COVER_MED ) + { + SetPosture( AIP_CROUCHING_MED ); + coverActivity = ACT_COVER_LOW; + } + } + else if (coverActivity == ACT_COVER_MED) + coverActivity = ACT_COVER_LOW; + if ( activity == ACT_IDLE ) activity = coverActivity; +#else + if (activity == ACT_IDLE) + activity = coverActivity; if ( GetPosture() == AIP_STANDING && coverActivity == ACT_COVER_LOW ) SetPosture( AIP_CROUCHING ); +#endif } Activity result = GetMappedActivity( GetPosture(), activity ); @@ -1077,10 +1140,31 @@ void CAI_StandoffBehavior::UnlockHintNode() Activity CAI_StandoffBehavior::GetCoverActivity() { +#if EXPANDED_HL2_COVER_ACTIVITIES + // GetCoverActivity() already checks everything we checked here. + Activity coverActivity = GetOuter()->GetCoverActivity( GetHintNode() ); + + if (coverActivity == ACT_COVER_LOW) + { + // Check if this node will block our line of sight if aiming low. + Vector vHintPos, vHintForward, vHintRight; + GetHintNode()->GetPosition( GetHullType(), &vHintPos ); + vHintForward = GetHintNode()->GetDirection(); + + GetHintNode()->GetVectors( NULL, &vHintRight, NULL ); + if (GetOuter()->CouldShootIfCrouchingAt( vHintPos, vHintForward, vHintRight )) + { + coverActivity = ACT_COVER_MED; + } + } + + return coverActivity == ACT_IDLE ? ACT_INVALID : coverActivity; +#else CAI_Hint *pHintNode = GetHintNode(); if ( pHintNode && pHintNode->HintType() == HINT_TACTICAL_COVER_LOW ) return GetOuter()->GetCoverActivity( pHintNode ); return ACT_INVALID; +#endif } @@ -1092,6 +1176,14 @@ struct AI_ActivityMapping_t Activity activity; const char * pszWeapon; Activity translation; +#ifdef MAPBASE + Activity backup; + + AI_ActivityMapping_t( AI_Posture_t _p, Activity _a, const char *_w, Activity _t, Activity _b = ACT_INVALID ) + { + posture = _p; activity = _a; pszWeapon = _w; translation = _t; backup = _b; + } +#endif }; void CAI_MappedActivityBehavior_Temporary::UpdateTranslateActivityMap() @@ -1105,15 +1197,60 @@ void CAI_MappedActivityBehavior_Temporary::UpdateTranslateActivityMap() { AIP_CROUCHING, ACT_WALK_AIM, NULL, ACT_WALK_CROUCH_AIM, }, { AIP_CROUCHING, ACT_RUN_AIM, NULL, ACT_RUN_CROUCH_AIM, }, { AIP_CROUCHING, ACT_RELOAD, NULL, ACT_RELOAD_LOW, }, +#ifdef MAPBASE + { AIP_CROUCHING, ACT_RANGE_ATTACK1, NULL, ACT_RANGE_ATTACK1_LOW, }, + { AIP_CROUCHING, ACT_COVER_MED, NULL, ACT_COVER_LOW, }, +#else { AIP_CROUCHING, ACT_RANGE_ATTACK_SMG1, NULL, ACT_RANGE_ATTACK_SMG1_LOW, }, { AIP_CROUCHING, ACT_RANGE_ATTACK_AR2, NULL, ACT_RANGE_ATTACK_AR2_LOW, }, +#endif +#if EXPANDED_HL2_COVER_ACTIVITIES + // + // ============ Really long explanation that should be in a wiki/documentation article somewhere ~ Blixibon, 10/27/2021 ============ + // + // Standoff behavior assumes low attack animations allow NPCs to see over barricades, with ACT_COVER_LOW being their "safely in cover" animation. + // This is why AIP_CROUCHING translates ACT_RANGE_ATTACK1 to its low animation, but translates ACT_IDLE_ANGRY to ACT_COVER_LOW instead of ACT_RANGE_AIM_LOW, + // as this would ideally allow NPCs to pop in and out of cover to shoot. + // This is also why AIP_PEEKING translates ACT_COVER_LOW to ACT_RANGE_AIM_LOW, as it's supposed to force the NPC to peek over their cover. + // + // However, this assumption mainly just applies to metrocops. Citizens' low attacking animations crouch low to the ground (which isn't effective for + // shooting over most barricades) and, while they do have a distinct ACT_COVER_LOW animation with transitions, they are close enough together that popping + // in and out of cover is redundant in most cases. Meanwhile, Combine soldiers have identical ACT_COVER_LOW and ACT_RANGE_AIM_LOW animations, which means + // they do not pop in and out of cover and AIP_PEEKING does nothing. This may be the reason why Combine soldiers occasionally get stuck in cover after a fight. + // + // ------------------------------------------------------------- + // + // As part of Mapbase v7.0's NPC activity overhaul, a new "medium cover" activity set has been added. Metrocops' previous "low cover" animation set (which, as + // mentioned before, is different from that of other NPCs) has been retroactively changed to use "medium cover". This was done for a few reasons unrelated to + // standoff behavior, but the important point is that these activities indicate a new cover height. This means we can use them to give standoff behavior more leeway + // for judging which animations to use in various levels of cover. + // + // Standoff behavior can use "medium cover" animations in cover which is too high for the "low" animations, and when the medium cover animations are not available, + // it simply falls back to the "standing" animations, thus resolving the issue with other NPCs not peeking in and out of cover without requiring new medium cover + // animations. + // + // In Mapbase, this is done by changing AIP_PEEKING to use the medium cover animations and adding a new alternate crouching posture posture called "AIP_CROUCHING_MED", + // which only uses the medium cover attack activity and otherwise automatically falls back to AIP_CROUCHING. AIP_CROUCHING_MED is automatically set if the NPC cannot + // get LOS from a regular crouching position. + // + { AIP_CROUCHING_MED, ACT_RANGE_ATTACK1, NULL, ACT_RANGE_ATTACK1_MED, }, + + //---- + { AIP_PEEKING, ACT_IDLE, NULL, ACT_RANGE_AIM_MED, }, + { AIP_PEEKING, ACT_IDLE_ANGRY, NULL, ACT_RANGE_AIM_MED, }, + { AIP_PEEKING, ACT_COVER_LOW, NULL, ACT_RANGE_AIM_MED, ACT_IDLE_ANGRY }, + { AIP_PEEKING, ACT_COVER_MED, NULL, ACT_RANGE_AIM_MED, ACT_IDLE_ANGRY }, + { AIP_PEEKING, ACT_RANGE_ATTACK1, NULL, ACT_RANGE_ATTACK1_MED, }, + { AIP_PEEKING, ACT_RELOAD, NULL, ACT_RELOAD_LOW, }, +#else //---- { AIP_PEEKING, ACT_IDLE, NULL, ACT_RANGE_AIM_LOW, }, { AIP_PEEKING, ACT_IDLE_ANGRY, NULL, ACT_RANGE_AIM_LOW, }, { AIP_PEEKING, ACT_COVER_LOW, NULL, ACT_RANGE_AIM_LOW, }, { AIP_PEEKING, ACT_RANGE_ATTACK1, NULL, ACT_RANGE_ATTACK1_LOW, }, { AIP_PEEKING, ACT_RELOAD, NULL, ACT_RELOAD_LOW, }, +#endif }; m_ActivityMap.RemoveAll(); @@ -1124,11 +1261,24 @@ void CAI_MappedActivityBehavior_Temporary::UpdateTranslateActivityMap() { if ( !mappings[i].pszWeapon || stricmp( mappings[i].pszWeapon, pszWeaponClass ) == 0 ) { +#ifdef MAPBASE + // Check NPC backup activity + if ( HaveSequenceForActivity( mappings[i].translation ) || HaveSequenceForActivity( GetOuter()->Weapon_TranslateActivity( mappings[i].translation ) ) || HaveSequenceForActivity( GetOuter()->Weapon_BackupActivity( mappings[i].translation ) ) ) +#else if ( HaveSequenceForActivity( mappings[i].translation ) || HaveSequenceForActivity( GetOuter()->Weapon_TranslateActivity( mappings[i].translation ) ) ) +#endif { Assert( m_ActivityMap.Find( MAKE_ACTMAP_KEY( mappings[i].posture, mappings[i].activity ) ) == m_ActivityMap.InvalidIndex() ); m_ActivityMap.Insert( MAKE_ACTMAP_KEY( mappings[i].posture, mappings[i].activity ), mappings[i].translation ); } +#ifdef MAPBASE + // Check activity map backup activity + else if ( mappings[i].backup != ACT_INVALID && HaveSequenceForActivity( mappings[i].backup ) ) + { + Assert( m_ActivityMap.Find( MAKE_ACTMAP_KEY( mappings[i].posture, mappings[i].activity ) ) == m_ActivityMap.InvalidIndex() ); + m_ActivityMap.Insert( MAKE_ACTMAP_KEY( mappings[i].posture, mappings[i].activity ), mappings[i].backup ); + } +#endif } } } diff --git a/src/game/server/ai_behavior_standoff.h b/src/game/server/ai_behavior_standoff.h index c08059e8..9141e874 100644 --- a/src/game/server/ai_behavior_standoff.h +++ b/src/game/server/ai_behavior_standoff.h @@ -51,6 +51,9 @@ enum AI_Posture_t AIP_INDIFFERENT, AIP_STANDING, AIP_CROUCHING, +#if EXPANDED_HL2_COVER_ACTIVITIES + AIP_CROUCHING_MED, // See UpdateTranslateActivityMap() for more information on what this is for +#endif AIP_PEEKING, }; @@ -149,6 +152,14 @@ protected: // Standoff overrides base AI crouch handling bool IsCrouching( void ) { return false; } + +#ifdef MAPBASE + // Standoff overrides base cover activity translation + bool CanTranslateCrouchActivity( void ) { return false; } + + // Don't do death poses while crouching + bool ShouldPickADeathPose( void ) { return (GetPosture() != AIP_CROUCHING && GetPosture() != AIP_PEEKING) && BaseClass::ShouldPickADeathPose(); } +#endif private: diff --git a/src/game/server/ai_blended_movement.cpp b/src/game/server/ai_blended_movement.cpp index 7c4ba8af..5262b644 100644 --- a/src/game/server/ai_blended_movement.cpp +++ b/src/game/server/ai_blended_movement.cpp @@ -1640,10 +1640,17 @@ void CAI_BlendedMotor::MaintainTurnActivity( void ) ConVar scene_flatturn( "scene_flatturn", "1" ); +#ifdef MAPBASE +ConVar ai_turning_enabled( "ai_turning_enabled", "1", FCVAR_NONE, "Enables NPC turning, which was previously disabled by Valve at some point after 2004 due to a now-unknown major issue." ); +#endif + bool CAI_BlendedMotor::AddTurnGesture( float flYD ) { // some funky bug with human turn gestures, disable for now +#ifdef MAPBASE + if (!ai_turning_enabled.GetBool()) +#endif return false; // try using a turn gesture diff --git a/src/game/server/ai_concommands.cpp b/src/game/server/ai_concommands.cpp index 8c647f55..6c4370d9 100644 --- a/src/game/server/ai_concommands.cpp +++ b/src/game/server/ai_concommands.cpp @@ -390,6 +390,166 @@ void CC_NPC_Focus( const CCommand &args ) static ConCommand npc_focus("npc_focus", CC_NPC_Focus, "Displays red line to NPC's enemy (if has one) and blue line to NPC's target entity (if has one)\n\tArguments: {npc_name} / {npc class_name} / no argument picks what player is looking at", FCVAR_CHEAT); ConVar npc_create_equipment("npc_create_equipment", ""); + +#ifdef MAPBASE +extern int EntityFactory_AutoComplete( const char *cmdname, CUtlVector< CUtlString > &commands, CUtlRBTree< CUtlString > &symbols, char *substring, int checklen = 0 ); +extern bool UtlStringLessFunc( const CUtlString &lhs, const CUtlString &rhs ); + +//------------------------------------------------------------------------------ +// Purpose: Create an NPC of the given type +//------------------------------------------------------------------------------ +class CNPCCreateAutoCompletionFunctor : public ICommandCallback, public ICommandCompletionCallback +{ +public: + virtual bool CreateAimed() { return false; } + + virtual void CommandCallback( const CCommand &args ) + { + MDLCACHE_CRITICAL_SECTION(); + + bool allowPrecache = CBaseEntity::IsPrecacheAllowed(); + CBaseEntity::SetAllowPrecache( true ); + + // Try to create entity + CAI_BaseNPC *baseNPC = dynamic_cast< CAI_BaseNPC * >( CreateEntityByName(args[1]) ); + if (baseNPC) + { + baseNPC->KeyValue( "additionalequipment", npc_create_equipment.GetString() ); + + if ( args.ArgC() == 3 ) + { + baseNPC->SetName( AllocPooledString( args[2] ) ); + } + else if ( args.ArgC() > 3 ) + { + baseNPC->SetName( AllocPooledString( args[2] ) ); + + // Pass in any additional parameters. + for ( int i = 3; i + 1 < args.ArgC(); i += 2 ) + { + const char *pKeyName = args[i]; + const char *pValue = args[i+1]; + baseNPC->KeyValue( pKeyName, pValue ); + } + } + + DispatchSpawn(baseNPC); + + // Now attempt to drop into the world + CBasePlayer* pPlayer = UTIL_GetCommandClient(); + trace_t tr; + Vector forward; + QAngle angles; + pPlayer->EyeVectors( &forward ); + + bool bCreateAimed = CreateAimed(); + if (bCreateAimed) + { + VectorAngles( forward, angles ); + angles.x = 0; + angles.z = 0; + } + + // Pass through the player's vehicle + CTraceFilterSkipTwoEntities filter( pPlayer, pPlayer->GetVehicleEntity(), COLLISION_GROUP_NONE ); + AI_TraceLine(pPlayer->EyePosition(), + pPlayer->EyePosition() + forward * MAX_TRACE_LENGTH,MASK_NPCSOLID, + &filter, &tr ); + + if ( tr.fraction != 1.0) + { + if (baseNPC->CapabilitiesGet() & bits_CAP_MOVE_FLY) + { + Vector pos = tr.endpos - forward * 36; + baseNPC->Teleport( &pos, bCreateAimed ? &angles : NULL, NULL ); + } + else + { + // Raise the end position a little up off the floor, place the npc and drop him down + tr.endpos.z += 12; + baseNPC->Teleport( &tr.endpos, bCreateAimed ? &angles : NULL, NULL ); + UTIL_DropToFloor( baseNPC, MASK_NPCSOLID ); + } + + // Now check that this is a valid location for the new npc to be + Vector vUpBit = baseNPC->GetAbsOrigin(); + vUpBit.z += 1; + + AI_TraceHull( baseNPC->GetAbsOrigin(), vUpBit, baseNPC->GetHullMins(), baseNPC->GetHullMaxs(), + MASK_NPCSOLID, baseNPC, COLLISION_GROUP_NONE, &tr ); + + // NEW: For vphysics/flying entities, do a second attempt which teleports based on bounding box + if ( (baseNPC->GetMoveType() == MOVETYPE_VPHYSICS || baseNPC->CapabilitiesGet() & bits_CAP_MOVE_FLY) && (tr.startsolid || tr.fraction < 1.0) ) + { + vUpBit.z += baseNPC->BoundingRadius(); + baseNPC->Teleport( &vUpBit, NULL, NULL ); + UTIL_DropToFloor( baseNPC, MASK_NPCSOLID ); + + Vector vUpBit2 = vUpBit; + vUpBit2.z += 1; + AI_TraceHull( vUpBit, vUpBit2, baseNPC->CollisionProp()->OBBMins(), baseNPC->CollisionProp()->OBBMaxs(), + MASK_NPCSOLID, baseNPC, COLLISION_GROUP_NONE, &tr ); + } + + if ( tr.startsolid || (tr.fraction < 1.0) ) + { + baseNPC->SUB_Remove(); + DevMsg("Can't create %s. Bad Position!\n",args[1]); + NDebugOverlay::Box(baseNPC->GetAbsOrigin(), baseNPC->GetHullMins(), baseNPC->GetHullMaxs(), 255, 0, 0, 0, 0); + } + } + else if (bCreateAimed) + { + baseNPC->Teleport( NULL, &angles, NULL ); + } + + baseNPC->Activate(); + } + CBaseEntity::SetAllowPrecache( allowPrecache ); + } + + virtual int CommandCompletionCallback( const char *partial, CUtlVector< CUtlString > &commands ) + { + if ( !g_pGameRules ) + { + return 0; + } + + const char *cmdname = CreateAimed() ? "npc_create_aimed" : "npc_create"; + + char *substring = (char *)partial; + if ( Q_strstr( partial, cmdname ) ) + { + substring = (char *)partial + strlen( cmdname ) + 1; + } + + int checklen = Q_strlen( substring ); + + if (checklen <= 0) + { + // Only show classnames prefixed with "npc" unless the user starts typing other characters + substring = "npc"; + checklen = 3; + } + + CUtlRBTree< CUtlString > symbols( 0, 0, UtlStringLessFunc ); + return EntityFactory_AutoComplete( cmdname, commands, symbols, substring, checklen ); + } +}; + +static CNPCCreateAutoCompletionFunctor g_NPCCreateAutoComplete; +static ConCommand npc_create("npc_create", &g_NPCCreateAutoComplete, "Creates an NPC of the given type where the player is looking (if the given NPC can actually stand at that location).\n\tArguments: {npc_class_name}", FCVAR_CHEAT, &g_NPCCreateAutoComplete); + +class CNPCCreateAimedAutoCompletionFunctor : public CNPCCreateAutoCompletionFunctor +{ +public: + virtual bool CreateAimed() { return true; } +}; + +static CNPCCreateAimedAutoCompletionFunctor g_NPCCreateAimedAutoComplete; + +static ConCommand npc_create_aimed("npc_create_aimed", &g_NPCCreateAimedAutoComplete, "Creates an NPC aimed away from the player of the given type where the player is looking (if the given NPC can actually stand at that location).\n\tArguments: {npc_class_name}", FCVAR_CHEAT, &g_NPCCreateAimedAutoComplete); +#else //------------------------------------------------------------------------------ // Purpose: Create an NPC of the given type //------------------------------------------------------------------------------ @@ -411,6 +571,20 @@ void CC_NPC_Create( const CCommand &args ) { baseNPC->SetName( AllocPooledString( args[2] ) ); } +#ifdef MAPBASE + else if ( args.ArgC() > 3 ) + { + baseNPC->SetName( AllocPooledString( args[2] ) ); + + // Pass in any additional parameters. + for ( int i = 3; i + 1 < args.ArgC(); i += 2 ) + { + const char *pKeyName = args[i]; + const char *pValue = args[i+1]; + baseNPC->KeyValue( pKeyName, pValue ); + } + } +#endif DispatchSpawn(baseNPC); // Now attempt to drop into the world @@ -526,6 +700,7 @@ void CC_NPC_Create_Aimed( const CCommand &args ) CBaseEntity::SetAllowPrecache( allowPrecache ); } static ConCommand npc_create_aimed("npc_create_aimed", CC_NPC_Create_Aimed, "Creates an NPC aimed away from the player of the given type where the player is looking (if the given NPC can actually stand at that location). Note that this only works for npc classes that are already in the world. You can not create an entity that doesn't have an instance in the level.\n\tArguments: {npc_class_name}", FCVAR_CHEAT); +#endif //------------------------------------------------------------------------------ // Purpose: Destroy unselected NPCs @@ -697,6 +872,134 @@ void CC_NPC_Reset( void ) } static ConCommand npc_reset("npc_reset", CC_NPC_Reset, "Reloads schedules for all NPC's from their script files\n\tArguments: -none-", FCVAR_CHEAT); +#ifdef MAPBASE +extern bool UtlStringLessFunc( const CUtlString &lhs, const CUtlString &rhs ); + +//------------------------------------------------------------------------------ +// Purpose : Auto-completes with entities in the entity list, but only uses NPC-derived entities. +// Input : cmdname - The name of the command. +// &commands - Where the complete autocompletes should be sent to. +// substring - The current search query. (only pool entities that start with this) +// checklen - The number of characters to check. +// Output : A pointer to a cUtlRBTRee containing all of the entities. +//------------------------------------------------------------------------------ +static int AutoCompleteNPCs(const char *cmdname, CUtlVector< CUtlString > &commands, CUtlRBTree< CUtlString > &symbols, char *substring, int checklen = 0) +{ + CBaseEntity *pos = NULL; + while ((pos = gEntList.NextEnt(pos)) != NULL) + { + if (!pos->IsNPC()) + continue; + + const char *name = pos->GetClassname(); + if (pos->GetEntityName() == NULL_STRING || Q_strnicmp(STRING(pos->GetEntityName()), substring, checklen)) + { + if (Q_strnicmp(pos->GetClassname(), substring, checklen)) + continue; + } + else + name = STRING(pos->GetEntityName()); + + CUtlString sym = name; + int idx = symbols.Find(sym); + if (idx == symbols.InvalidIndex()) + { + symbols.Insert(sym); + } + + // Too many + if (symbols.Count() >= COMMAND_COMPLETION_MAXITEMS) + break; + } + + // Now fill in the results + for (int i = symbols.FirstInorder(); i != symbols.InvalidIndex(); i = symbols.NextInorder(i)) + { + const char *name = symbols[i].String(); + + char buf[512]; + Q_strncpy(buf, name, sizeof(buf)); + Q_strlower(buf); + + CUtlString command; + command = CFmtStr("%s %s", cmdname, buf); + commands.AddToTail(command); + } + + return symbols.Count(); +} + +//------------------------------------------------------------------------------ +// There's a big set of NPC debug commands that do similar operations and +// can fall under this base class for auto-completion, etc. +//------------------------------------------------------------------------------ +class CNPCDebugAutoCompletionFunctor : public ICommandCallback, public ICommandCompletionCallback +{ +public: + virtual const char *CommandName() { return NULL; } + virtual void CommandCallback( const CCommand &args ) + { + SetDebugBits( UTIL_GetCommandClient(), args[1], OVERLAY_NPC_NEAREST_BIT ); + } + + virtual int CommandCompletionCallback( const char *partial, CUtlVector< CUtlString > &commands ) + { + if ( !g_pGameRules ) + { + return 0; + } + + const char *cmdname = CommandName(); + + char *substring = (char *)partial; + if ( Q_strstr( partial, cmdname ) ) + { + substring = (char *)partial + strlen( cmdname ) + 1; + } + + int checklen = Q_strlen( substring ); + + if (checklen == 0 || atoi(substring) != 0) + { + // Must be the picker or an entity index + return 0; + } + + CUtlRBTree< CUtlString > symbols( 0, 0, UtlStringLessFunc ); + return AutoCompleteNPCs(cmdname, commands, symbols, substring, checklen); + } +}; + +#define NPCDebugCommand(name, functor, bit, help) class CNPC##functor##AutoCompletionFunctor : public CNPCDebugAutoCompletionFunctor \ +{ \ +public: \ + virtual const char *CommandName() { return #name; } \ + virtual void CommandCallback( const CCommand &args ) \ + { \ + SetDebugBits( UTIL_GetCommandClient(), args[1], bit ); \ + } \ +}; \ +static CNPC##functor##AutoCompletionFunctor g_NPC##functor##AutoCompletionFunctor; \ +static ConCommand name(#name, &g_NPC##functor##AutoCompletionFunctor, help, FCVAR_CHEAT, &g_NPC##functor##AutoCompletionFunctor); + +NPCDebugCommand( npc_nearest, Nearest, OVERLAY_NPC_NEAREST_BIT, "Draw's a while box around the NPC(s) nearest node\n\tArguments: {entity_name} / {class_name} / no argument picks what player is looking at " ); +NPCDebugCommand( npc_route, Route, OVERLAY_NPC_ROUTE_BIT, "Displays the current route of the given NPC as a line on the screen. Waypoints along the route are drawn as small cyan rectangles. Line is color coded in the following manner:\n\tBlue - path to a node\n\tCyan - detour around an object (triangulation)\n\tRed - jump\n\tMaroon - path to final target position\n\tArguments: {npc_name} / {npc_class_name} / no argument picks what player is looking at " ); +NPCDebugCommand( npc_select, Select, OVERLAY_NPC_SELECTED_BIT, "Select or deselects the given NPC(s) for later manipulation. Selected NPC's are shown surrounded by a red translucent box\n\tArguments: {entity_name} / {class_name} / no argument picks what player is looking at " ); +NPCDebugCommand( npc_combat, Combat, OVERLAY_NPC_SQUAD_BIT, "Displays text debugging information about the squad and enemy of the selected NPC (See Overlay Text)\n\tArguments: {npc_name} / {npc class_name} / no argument picks what player is looking at" ); +NPCDebugCommand( npc_tasks, Tasks, OVERLAY_NPC_TASK_BIT, "Displays detailed text debugging information about the all the tasks of the selected NPC current schedule (See Overlay Text)\n\tArguments: {npc_name} / {npc class_name} / no argument picks what player is looking at " ); +NPCDebugCommand( npc_task_text, TaskText, OVERLAY_TASK_TEXT_BIT, "Outputs text debugging information to the console about the all the tasks + break conditions of the selected NPC current schedule\n\tArguments: {npc_name} / {npc class_name} / no argument picks what player is looking at " ); +NPCDebugCommand( npc_conditions, Conditions, OVERLAY_NPC_CONDITIONS_BIT, "Displays all the current AI conditions that an NPC has in the overlay text.\n\tArguments: {npc_name} / {npc class_name} / no argument picks what player is looking at" ); +NPCDebugCommand( npc_viewcone, Viewcone, OVERLAY_NPC_VIEWCONE_BIT, "Displays the viewcone of the NPC (where they are currently looking and what the extents of there vision is)\n\tArguments: {entity_name} / {class_name} / no argument picks what player is looking at" ); +NPCDebugCommand( npc_relationships, Relationships, OVERLAY_NPC_RELATION_BIT, "Displays the relationships between this NPC and all others.\n\tArguments: {entity_name} / {class_name} / no argument picks what player is looking at" ); +NPCDebugCommand( npc_steering, Steering, OVERLAY_NPC_STEERING_REGULATIONS, "Displays the steering obstructions of the NPC( used to perform local avoidance )\n\tArguments: {entity_name} / {class_name} / no argument picks what player is looking at" ); + +// For backwards compatibility +void CC_NPC_Squads( const CCommand &args ) +{ + SetDebugBits( UTIL_GetCommandClient(),args[1],OVERLAY_NPC_SQUAD_BIT); +} +static ConCommand npc_squads("npc_squads", CC_NPC_Squads, "Obsolete. Replaced by npc_combat", FCVAR_CHEAT); +#else //------------------------------------------------------------------------------ // Purpose: Show the selected NPC's nearest node //------------------------------------------------------------------------------ @@ -791,6 +1094,7 @@ void CC_NPC_ViewSteeringRegulations( const CCommand &args ) SetDebugBits( UTIL_GetCommandClient(), args[1], OVERLAY_NPC_STEERING_REGULATIONS); } static ConCommand npc_steering("npc_steering", CC_NPC_ViewSteeringRegulations, "Displays the steering obstructions of the NPC (used to perform local avoidance)\n\tArguments: {entity_name} / {class_name} / no argument picks what player is looking at", FCVAR_CHEAT); +#endif void CC_NPC_ViewSteeringRegulationsAll( void ) { diff --git a/src/game/server/ai_default.cpp b/src/game/server/ai_default.cpp index ffa85e08..8f660ec3 100644 --- a/src/game/server/ai_default.cpp +++ b/src/game/server/ai_default.cpp @@ -374,6 +374,10 @@ int CAI_BaseNPC::TranslateSchedule( int scheduleType ) return scheduleType; } +#ifdef MAPBASE +extern ScriptHook_t g_Hook_TranslateSchedule; +#endif + //========================================================= // GetScheduleOfType - returns a pointer to one of the // NPC's available schedules of the indicated type. @@ -386,6 +390,35 @@ CAI_Schedule *CAI_BaseNPC::GetScheduleOfType( int scheduleType ) scheduleType = TranslateSchedule( scheduleType ); AI_PROFILE_SCOPE_END(); +#ifdef MAPBASE_VSCRIPT + if ( m_ScriptScope.IsInitialized() && g_Hook_TranslateSchedule.CanRunInScope(m_ScriptScope) ) + { + int newSchedule = scheduleType; + if ( AI_IdIsLocal( newSchedule ) ) + { + newSchedule = GetClassScheduleIdSpace()->ScheduleLocalToGlobal(newSchedule); + } + + // schedule, schedule_id (local ID) + ScriptVariant_t functionReturn; + ScriptVariant_t args[] = { GetSchedulingSymbols()->ScheduleIdToSymbol( newSchedule ), scheduleType }; + if (g_Hook_TranslateSchedule.Call( m_ScriptScope, &functionReturn, args )) + { + if (functionReturn.m_type == FIELD_INTEGER) + { + newSchedule = functionReturn.m_int; + } + else + { + newSchedule = GetScheduleID( functionReturn.m_pszString ); + } + + if (newSchedule != scheduleType && newSchedule > -1) + scheduleType = newSchedule; + } + } +#endif + // Get a pointer to that schedule CAI_Schedule *schedule = GetSchedule(scheduleType); @@ -1774,6 +1807,24 @@ AI_DEFINE_SCHEDULE // Run to cover, but don't turn to face enemy and upon // fail run around randomly //========================================================= +#ifdef MAPBASE +AI_DEFINE_SCHEDULE +( + SCHED_RUN_FROM_ENEMY, + + " Tasks" + " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_RUN_FROM_ENEMY_FALLBACK" + " TASK_STOP_MOVING 0" + " TASK_FIND_COVER_FROM_ENEMY 0" + " TASK_RUN_PATH 0" + " TASK_WAIT_FOR_MOVEMENT 0" + " TASK_REMEMBER MEMORY:INCOVER" // Now that crouch nodes are fixed, this is necessary in case cover leads to a crouch node + "" + " Interrupts" + " COND_NEW_ENEMY" + " COND_ENEMY_DEAD" +); +#else AI_DEFINE_SCHEDULE ( SCHED_RUN_FROM_ENEMY, @@ -1789,6 +1840,7 @@ AI_DEFINE_SCHEDULE " COND_NEW_ENEMY" " COND_ENEMY_DEAD" ); +#endif AI_DEFINE_SCHEDULE ( @@ -2351,6 +2403,19 @@ AI_DEFINE_SCHEDULE //========================================================= // > SCHED_INTERACTION_WAIT_FOR_PARTNER //========================================================= +#ifdef MAPBASE +AI_DEFINE_SCHEDULE +( + SCHED_INTERACTION_WAIT_FOR_PARTNER, + + " Tasks" + " TASK_FACE_INTERACTION_ANGLES 0" // New task to fix forced interaction anomalies + " TASK_WAIT 1" + "" + " Interrupts" + " COND_NO_CUSTOM_INTERRUPTS" +); +#else AI_DEFINE_SCHEDULE ( SCHED_INTERACTION_WAIT_FOR_PARTNER, @@ -2362,6 +2427,7 @@ AI_DEFINE_SCHEDULE " Interrupts" " COND_NO_CUSTOM_INTERRUPTS" ); +#endif //========================================================= // > SCHED_SLEEP diff --git a/src/game/server/ai_dynamiclink.cpp b/src/game/server/ai_dynamiclink.cpp index 5f5fba9c..a08402e8 100644 --- a/src/game/server/ai_dynamiclink.cpp +++ b/src/game/server/ai_dynamiclink.cpp @@ -15,6 +15,14 @@ #include "ai_link.h" #include "ai_network.h" #include "ai_networkmanager.h" +#ifdef MAPBASE +#include "ai_hint.h" +#include "ai_basenpc.h" +#include "filters.h" +#include "point_template.h" +#include "TemplateEntities.h" +#include "mapentities.h" +#endif #include "saverestore_utlvector.h" #include "editor_sendcommand.h" #include "bitstring.h" @@ -169,6 +177,156 @@ void CAI_DynamicLinkController::InputSetInvert( inputdata_t &inputdata ) } } +#ifdef MAPBASE +//============================================================================= +// >> CAI_CustomLinkController +// Uses the specified link class +//============================================================================= +class CAI_CustomLinkController : public CAI_DynamicLinkController +{ + DECLARE_CLASS( CAI_CustomLinkController, CAI_DynamicLinkController ); +public: + CAI_CustomLinkController(); + + void GenerateLinksFromVolume(); + int GetReferenceLinkIndex(); + + string_t m_iszReferenceLinkTemplate; + int m_iReferenceLink; + + DECLARE_DATADESC(); +}; + +LINK_ENTITY_TO_CLASS(info_template_link_controller, CAI_CustomLinkController); + +BEGIN_DATADESC( CAI_CustomLinkController ) + + DEFINE_KEYFIELD( m_iszReferenceLinkTemplate, FIELD_STRING, "ReferenceTemplate" ), + //DEFINE_FIELD( m_iReferenceLink, FIELD_INTEGER ), // I don't know if this should be saved. It's only a cached variable, so not saving it shouldn't hurt anything. + +END_DATADESC() + +CAI_CustomLinkController::CAI_CustomLinkController() +{ + m_iReferenceLink = -1; +} + +int CAI_CustomLinkController::GetReferenceLinkIndex() +{ + if (m_iReferenceLink != -1) + return m_iReferenceLink; + + CBaseEntity *pEnt = gEntList.FindEntityByName(NULL, STRING(m_iszReferenceLinkTemplate), this); + if (CPointTemplate *pTemplate = dynamic_cast(pEnt)) + { + Assert(pTemplate->GetTemplateEntity(0)); + + m_iReferenceLink = pTemplate->GetTemplateIndexForTemplate(0); + return m_iReferenceLink; + } + + return -1; +} + +void CAI_CustomLinkController::GenerateLinksFromVolume() +{ + Assert( m_ControlledLinks.Count() == 0 ); + + int nNodes = g_pBigAINet->NumNodes(); + CAI_Node **ppNodes = g_pBigAINet->AccessNodes(); + + float MinDistCareSq = 0; + if (m_bUseAirLinkRadius) + { + MinDistCareSq = Square(MAX_AIR_NODE_LINK_DIST + 0.1); + } + else + { + MinDistCareSq = Square(MAX_NODE_LINK_DIST + 0.1); + } + + const Vector &origin = WorldSpaceCenter(); + Vector vAbsMins, vAbsMaxs; + CollisionProp()->WorldSpaceAABB( &vAbsMins, &vAbsMaxs ); + vAbsMins -= Vector( 1, 1, 1 ); + vAbsMaxs += Vector( 1, 1, 1 ); + + int iReference = GetReferenceLinkIndex(); + if (iReference == -1) + { + Warning("WARNING! %s reference link is invalid!\n", GetDebugName()); + return; + } + + // Get the map data before the loop + char *pMapData = (char*)STRING( Templates_FindByIndex( iReference ) ); + + // Make sure the entity is a dynamic link before doing anything + CBaseEntity *pEntity = NULL; + MapEntity_ParseEntity( pEntity, pMapData, NULL ); + if ( !dynamic_cast(pEntity) ) + { + Warning("WARNING! %s reference link is not a node link!\n", GetDebugName()); + UTIL_RemoveImmediate(pEntity); + return; + } + + UTIL_RemoveImmediate(pEntity); + + for ( int i = 0; i < nNodes; i++ ) + { + CAI_Node *pNode = ppNodes[i]; + const Vector &nodeOrigin = pNode->GetOrigin(); + if ( origin.DistToSqr(nodeOrigin) < MinDistCareSq ) + { + int nLinks = pNode->NumLinks(); + for ( int j = 0; j < nLinks; j++ ) + { + CAI_Link *pLink = pNode->GetLinkByIndex( j ); + int iLinkDest = pLink->DestNodeID( i ); + if ( iLinkDest > i ) + { + const Vector &originOther = ppNodes[iLinkDest]->GetOrigin(); + if ( origin.DistToSqr(originOther) < MinDistCareSq ) + { + if ( IsBoxIntersectingRay( vAbsMins, vAbsMaxs, nodeOrigin, originOther - nodeOrigin ) ) + { + Assert( IsBoxIntersectingRay( vAbsMins, vAbsMaxs, originOther, nodeOrigin - originOther ) ); + + CBaseEntity *pEntity = NULL; + + // Create the entity from the mapdata + MapEntity_ParseEntity( pEntity, pMapData, NULL ); + if ( pEntity == NULL ) + { + Msg("%s failed to initialize templated link with mapdata: %s\n", GetDebugName(), pMapData ); + return; + } + + // We already made sure it was an info_node_link template earlier. + CAI_DynamicLink *pLink = static_cast(pEntity); + + pLink->m_nSrcID = i; + pLink->m_nDestID = iLinkDest; + pLink->m_nSrcEditID = g_pAINetworkManager->GetEditOps()->GetWCIdFromNodeId( pLink->m_nSrcID ); + pLink->m_nDestEditID = g_pAINetworkManager->GetEditOps()->GetWCIdFromNodeId( pLink->m_nDestID ); + pLink->m_nLinkState = m_nLinkState; + pLink->m_strAllowUse = m_strAllowUse; + pLink->m_bInvertAllow = m_bInvertAllow; + pLink->m_bFixedUpIds = true; + pLink->m_bNotSaved = true; + + pLink->Spawn(); + m_ControlledLinks.AddToTail( pLink ); + } + } + } + } + } + } +} +#endif + //----------------------------------------------------------------------------- LINK_ENTITY_TO_CLASS(info_node_link, CAI_DynamicLink); @@ -579,6 +737,242 @@ CAI_DynamicLink::~CAI_DynamicLink(void) { } } +#ifdef MAPBASE +//------------------------------------------------------------------------------ +// Purpose : Determines if usage is allowed by a NPC, whether the link is disabled or not. +// This was created for info_node_link derivatives. +// Input : +// Output : +//------------------------------------------------------------------------------ +bool CAI_DynamicLink::UseAllowed(CAI_BaseNPC *pNPC, bool bFromEnd) +{ + if (!(FindLink()->m_LinkInfo & bits_LINK_OFF)) + return true; + + if ( m_strAllowUse == NULL_STRING ) + return false; + + const char *pszAllowUse = STRING( m_strAllowUse ); + if ( m_bInvertAllow ) + { + // Exlude only the specified entity name or classname + if ( !pNPC->NameMatches(pszAllowUse) && !pNPC->ClassMatches( pszAllowUse ) ) + return true; + } + else + { + // Exclude everything but the allowed entity name or classname + if ( pNPC->NameMatches( pszAllowUse) || pNPC->ClassMatches( pszAllowUse ) ) + return true; + } + + return false; +} + +//============================================================================= +// >> CAI_DynanicLinkOneWay +//============================================================================= +class CAI_DynamicLinkOneWay : public CAI_DynamicLink +{ + DECLARE_CLASS( CAI_DynamicLinkOneWay, CAI_DynamicLink ); +public: + virtual bool UseAllowed(CAI_BaseNPC *pNPC, bool bFromEnd); + //virtual void SetLinkState( void ); + + bool m_bNormalWhenEnabled; + + DECLARE_DATADESC(); +}; + +LINK_ENTITY_TO_CLASS(info_node_link_oneway, CAI_DynamicLinkOneWay); + +BEGIN_DATADESC( CAI_DynamicLinkOneWay ) + + DEFINE_KEYFIELD( m_bNormalWhenEnabled, FIELD_BOOLEAN, "Usage" ), + +END_DATADESC() + +//------------------------------------------------------------------------------ +// Purpose : Determines if usage is allowed by a NPC. +// This was created for info_node_link derivatives. +// Input : +// Output : +//------------------------------------------------------------------------------ +bool CAI_DynamicLinkOneWay::UseAllowed(CAI_BaseNPC *pNPC, bool bFromEnd) +{ + if (m_bNormalWhenEnabled) + return (m_nLinkState == LINK_OFF && bFromEnd) ? BaseClass::UseAllowed(pNPC, bFromEnd) : true; + + if (bFromEnd || m_nLinkState == LINK_OFF) + return BaseClass::UseAllowed(pNPC, bFromEnd); + + return true; +} + +#if 0 +//------------------------------------------------------------------------------ +// Purpose : Updates network link state if dynamic link state has changed +// Input : +// Output : +//------------------------------------------------------------------------------ +void CAI_DynamicLinkOneWay::SetLinkState(void) +{ + if (m_bNormalWhenEnabled) + return BaseClass::SetLinkState(); + + if ( !gm_bInitialized ) + { + // Safe to quietly return. Consistency will be enforced when InitDynamicLinks() is called + return; + } + + if (m_nSrcID == NO_NODE || m_nDestID == NO_NODE) + { + Vector pos = GetAbsOrigin(); + DevWarning("ERROR: Dynamic link at %f %f %f pointing to invalid node ID!!\n", pos.x, pos.y, pos.z); + return; + } + + CAI_Node * pSrcNode = g_pBigAINet->GetNode(m_nSrcID, false); + if ( pSrcNode ) + { + CAI_Link* pLink = FindLink(); + if ( pLink ) + { + // One-way always registers as off so it always calls UseAllowed() + pLink->m_pDynamicLink = this; + pLink->m_LinkInfo |= bits_LINK_OFF; + } + else + { + DevMsg("Dynamic Link Error: (%s) unable to form between nodes %d and %d\n", GetDebugName(), m_nSrcID, m_nDestID ); + } + } +} +#endif + +//============================================================================= +// >> CAI_DynamicLinkFilter +//============================================================================= +class CAI_DynamicLinkFilter : public CAI_DynamicLink +{ + DECLARE_CLASS( CAI_DynamicLinkFilter, CAI_DynamicLink ); +public: + virtual bool UseAllowed(CAI_BaseNPC *pNPC, bool bFromEnd); + //virtual void SetLinkState( void ); + + bool m_bNormalWhenEnabled; + + DECLARE_DATADESC(); +}; + +LINK_ENTITY_TO_CLASS(info_node_link_filtered, CAI_DynamicLinkFilter); + +BEGIN_DATADESC( CAI_DynamicLinkFilter ) + + DEFINE_KEYFIELD( m_bNormalWhenEnabled, FIELD_BOOLEAN, "Usage" ), + DEFINE_INPUTFUNC( FIELD_STRING, "SetLinkFilter", InputSetDamageFilter ), + +END_DATADESC() + +//------------------------------------------------------------------------------ +// Purpose : Determines if usage is allowed by a NPC. +// This was created for info_node_link derivatives. +// Input : +// Output : +//------------------------------------------------------------------------------ +bool CAI_DynamicLinkFilter::UseAllowed(CAI_BaseNPC *pNPC, bool bFromEnd) +{ + if ( !m_hDamageFilter ) + { + m_hDamageFilter = gEntList.FindEntityByName( NULL, m_iszDamageFilterName ); + if (!m_hDamageFilter) + { + Warning("%s (%s) couldn't find filter \"%s\"!\n", GetClassname(), GetDebugName(), STRING(m_iszDamageFilterName)); + return BaseClass::UseAllowed(pNPC, bFromEnd); + } + } + + CBaseFilter *pFilter = (CBaseFilter *)(m_hDamageFilter.Get()); + + if (m_bNormalWhenEnabled) + return (m_nLinkState == LINK_OFF) ? (pFilter->PassesFilter(this, pNPC) || BaseClass::UseAllowed(pNPC, bFromEnd)) : true; + + if (m_nLinkState == LINK_OFF) + return BaseClass::UseAllowed(pNPC, bFromEnd); + + return pFilter->PassesFilter(this, pNPC); +} + +//============================================================================= +// >> CAI_DynamicLinkLogic +//============================================================================= +class CAI_DynamicLinkLogic : public CAI_DynamicLink +{ + DECLARE_CLASS( CAI_DynamicLinkLogic, CAI_DynamicLink ); +public: + virtual bool UseAllowed(CAI_BaseNPC *pNPC, bool bFromEnd); + virtual bool FinalUseAllowed(CAI_BaseNPC *pNPC, bool bFromEnd); + + COutputEvent m_OnUsageAccepted; + COutputEvent m_OnUsageAcceptedWhileDisabled; + + DECLARE_DATADESC(); +}; + +LINK_ENTITY_TO_CLASS(info_node_link_logic, CAI_DynamicLinkLogic); + +BEGIN_DATADESC( CAI_DynamicLinkLogic ) + + DEFINE_OUTPUT( m_OnUsageAccepted, "OnUsageAccepted" ), + DEFINE_OUTPUT( m_OnUsageAcceptedWhileDisabled, "OnUsageAcceptedWhileDisabled" ), + +END_DATADESC() + +//------------------------------------------------------------------------------ +// Purpose : Determines if usage is allowed by a NPC. +// This was created for info_node_link derivatives. +// Input : +// Output : +//------------------------------------------------------------------------------ +bool CAI_DynamicLinkLogic::UseAllowed(CAI_BaseNPC *pNPC, bool bFromEnd) +{ + // + // If the link is off, we want to fire "OnUsageAcceptedWhileDisabled", but we have to make sure + // the rest of the pathfinding calculations work. Yes, they might do all of this just to find a disabled link, + // but we have to fire the output somehow. + // + // Links already enabled go through regular usage rules. + // + if (m_nLinkState == LINK_OFF) + return true; + else + return BaseClass::UseAllowed( pNPC, bFromEnd ); +} + +//------------------------------------------------------------------------------ +// Purpose : After nothing else is left, finally determines if usage is allowed by a NPC. +// This was created for info_node_link derivatives. +// Input : +// Output : +//------------------------------------------------------------------------------ +bool CAI_DynamicLinkLogic::FinalUseAllowed(CAI_BaseNPC *pNPC, bool bFromEnd) +{ + if (m_nLinkState == LINK_ON) + { + m_OnUsageAccepted.FireOutput(pNPC, this); + return true; + } + else + { + m_OnUsageAcceptedWhileDisabled.FireOutput(pNPC, this); + + // We skipped the usage rules before. Do them now. + return BaseClass::UseAllowed(pNPC, bFromEnd); + } +} +#endif + LINK_ENTITY_TO_CLASS(info_radial_link_controller, CAI_RadialLinkController); BEGIN_DATADESC( CAI_RadialLinkController ) diff --git a/src/game/server/ai_dynamiclink.h b/src/game/server/ai_dynamiclink.h index 172e3828..9f3d12f1 100644 --- a/src/game/server/ai_dynamiclink.h +++ b/src/game/server/ai_dynamiclink.h @@ -65,6 +65,13 @@ public: int ObjectCaps(); +#ifdef MAPBASE + virtual bool UseAllowed(CAI_BaseNPC *pNPC, bool bFromEnd); + + // Called after we know the NPC meets all of the node's criteria + virtual bool FinalUseAllowed(CAI_BaseNPC *pNPC, bool bFromEnd) { return true; } +#endif + // ---------------- // Inputs // ---------------- @@ -83,6 +90,9 @@ class CAI_DynamicLinkController : public CServerOnlyEntity { DECLARE_CLASS( CAI_DynamicLinkController, CServerOnlyEntity ); public: +#ifdef MAPBASE + virtual +#endif void GenerateLinksFromVolume(); // ---------------- diff --git a/src/game/server/ai_expresserfollowup.cpp b/src/game/server/ai_expresserfollowup.cpp new file mode 100644 index 00000000..38a83a3d --- /dev/null +++ b/src/game/server/ai_expresserfollowup.cpp @@ -0,0 +1,519 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" + +#include "ai_speech.h" + +#include "game.h" +#include "eventqueue.h" +#include "ai_basenpc.h" +#include "basemultiplayerplayer.h" +#include "ai_baseactor.h" +#include "sceneentity.h" +//#include "flex_expresser.h" +/* +#include "engine/ienginesound.h" +#include "keyvalues.h" +#include "ai_criteria.h" +#include "isaverestore.h" +#include "sceneentity.h" +*/ + + + +// memdbgon must be the last include file in a .cpp file!!! +#include + +static const char *GetResponseName( CBaseEntity *pEnt ) +{ + Assert( pEnt ); + if ( pEnt == NULL ) + return ""; + return STRING( pEnt->GetEntityName() ); +} + +// This is a tiny helper function for below -- what I'd use a lambda for, usually +static void DispatchComeback( CAI_ExpresserWithFollowup *pExpress, CBaseEntity *pSpeaker, CBaseEntity *pRespondent, AI_ResponseFollowup &followup ) +{ + AssertMsg(pSpeaker != NULL, "Response expressor somehow got called with a NULL Outer.\n"); + if ( !pRespondent ) + { + return; + } + + float delay = followup.followup_delay; + if (pSpeaker == pRespondent && delay < 0) + { + Warning("Response rule with a 'self' target specified negative delay, which isn't legal because that would make someone talk over himself."); + delay = 0; + } + + // Msg( "%s: Dispatch comeback about %s to %s\n", pSpeaker->GetBotString(), g_pConceptManager->GetTopicName( handle ), pRespondent->GetBotString() ); + + // build an input event that we will use to force the bot to talk through the IO system + variant_t value; + // Don't send along null contexts + if (followup.followup_contexts && followup.followup_contexts[0] != '\0') + { + value.SetString( MAKE_STRING( followup.followup_contexts ) ); + g_EventQueue.AddEvent( pRespondent, "AddContext", value, delay - 0.01, pSpeaker, pSpeaker ); + } + + /* + value.SetString(MAKE_STRING(followup.followup_concept)); + g_EventQueue.AddEvent( pRespondent, "SpeakResponseConcept", value, delay , pSpeaker, pSpeaker ); + */ + + AI_CriteriaSet criteria; + + // add in the FROM context so dispatchee knows was from me + const char * RESTRICT pszSpeakerName = GetResponseName( pSpeaker ); + criteria.AppendCriteria( "From", pszSpeakerName ); +#ifdef MAPBASE + // See DispatchFollowupThroughQueue() + criteria.AppendCriteria( "From_idx", CNumStr( pSpeaker->entindex() ) ); + criteria.AppendCriteria( "From_class", pSpeaker->GetClassname() ); +#endif + // if a SUBJECT criteria is missing, put it back in. + if ( criteria.FindCriterionIndex( "Subject" ) == -1 ) + { + criteria.AppendCriteria( "Subject", pszSpeakerName ); + } + + // add in any provided contexts from the parameters onto the ones stored in the followup + criteria.Merge( followup.followup_contexts ); + +#ifdef MAPBASE + if (CAI_ExpresserSink *pSink = dynamic_cast(pRespondent)) + { + criteria.AppendCriteria( "dist_from_issuer", UTIL_VarArgs( "%f", (pRespondent->GetAbsOrigin() - pSpeaker->GetAbsOrigin()).Length() ) ); + g_ResponseQueueManager.GetQueue()->AppendFollowupCriteria( followup.followup_concept, criteria, pSink->GetSinkExpresser(), pSink, pRespondent, pSpeaker, kDRT_SPECIFIC ); + + pSink->Speak( followup.followup_concept, &criteria ); + } +#else + // This is kludgy and needs to be fixed in class hierarchy, but for now, try to guess at the most likely + // kinds of targets and dispatch to them. + if (CBaseMultiplayerPlayer *pPlayer = dynamic_cast(pRespondent)) + { + pPlayer->Speak( followup.followup_concept, &criteria ); + } + + else if (CAI_BaseActor *pActor = dynamic_cast(pRespondent)) + { + pActor->Speak( followup.followup_concept, &criteria ); + } +#endif +} + +#if 0 +//----------------------------------------------------------------------------- +// Purpose: Placeholder for rules based response system +// Input : concept - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CAI_ExpresserWithFollowup::Speak( AIConcept_t &concept, const char *modifiers /*= NULL*/, char *pszOutResponseChosen /* = NULL*/, size_t bufsize /* = 0 */, IRecipientFilter *filter /* = NULL */ ) +{ + AI_Response *result = SpeakFindResponse( concept, modifiers ); + if ( !result ) + { + return false; + } + + CNPC_CompanionBot *pBot = dynamic_cast(GetOuter()); + if ( pBot ) + { + pBot->SetConversationTopic( g_pConceptManager->GetTopic( handle ) ); + pBot->SetLastSpeaker( g_pConceptManager->GetSpeaker( handle ) ); + // Msg( "%s: Conversing about %s\n", pBot->GetBotString(), g_pConceptManager->GetTopicName( handle ) ); + } + + SpeechMsg( GetOuter(), "%s (%x) spoke %s (%f)\n", STRING(GetOuter()->GetEntityName()), GetOuter(), g_pConceptManager->GetConcept( handle ), gpGlobals->curtime ); + + bool spoke = SpeakDispatchResponse( handle, result, filter ); + if ( pszOutResponseChosen ) + { + result->GetResponse( pszOutResponseChosen, bufsize ); + } + + return spoke; +} +#endif + + +// Work out the character from the "subject" context. +// Right now, this is a simple find by entity name search. +// But you can define arbitrary subject names, like L4D does +// for "biker", "manager", etc. +static CBaseEntity *AscertainSpeechSubjectFromContext( AI_Response *response, AI_CriteriaSet &criteria, const char *pContextName ) +{ + const char *subject = criteria.GetValue( criteria.FindCriterionIndex( pContextName ) ); + if (subject) + { + CBaseEntity *pEnt = gEntList.FindEntityByName( NULL, subject ); + +#ifdef MAPBASE + // Allow entity indices to be used (see DispatchFollowupThroughQueue() for one particular use case) + if (!pEnt && atoi(subject)) + { + pEnt = CBaseEntity::Instance( atoi( subject ) ); + } +#endif + + return pEnt; + + } + else + { + return NULL; + } +} + +// TODO: Currently uses awful stricmp. Use symbols! Once I know which ones we want, that is. +static CResponseQueue::CFollowupTargetSpec_t ResolveFollowupTargetToEntity( AIConcept_t &concept, AI_CriteriaSet &criteria, const char * RESTRICT szTarget, AI_Response * RESTRICT response = NULL ) +{ + + + + if ( Q_stricmp(szTarget, "self") == 0 ) + { + return CResponseQueue::CFollowupTargetSpec_t( kDRT_SPECIFIC, concept.GetSpeaker() ); + } + else if ( Q_stricmp(szTarget, "subject") == 0 ) + { + return CResponseQueue::CFollowupTargetSpec_t( AscertainSpeechSubjectFromContext( response, criteria, "Subject" ) ); + } + else if ( Q_stricmp(szTarget, "from") == 0 ) + { +#ifdef MAPBASE + // See DispatchFollowupThroughQueue() + return CResponseQueue::CFollowupTargetSpec_t( AscertainSpeechSubjectFromContext( response, criteria, "From_idx" ) ); +#else + return CResponseQueue::CFollowupTargetSpec_t( AscertainSpeechSubjectFromContext( response, criteria, "From" ) ); +#endif + } + else if ( Q_stricmp(szTarget, "any") == 0 ) + { + return CResponseQueue::CFollowupTargetSpec_t( kDRT_ANY, concept.GetSpeaker() ); + } + else if ( Q_stricmp(szTarget, "all") == 0 ) + { + return CResponseQueue::CFollowupTargetSpec_t( kDRT_ALL ); + } + + // last resort, try a named lookup +#ifdef MAPBASE + else if ( CBaseEntity *pSpecific = gEntList.FindEntityByName(NULL, szTarget, concept.GetSpeaker()) ) // it could be anything +#else + else if ( CBaseEntity *pSpecific = gEntList.FindEntityByName(NULL, szTarget) ) // it could be anything +#endif + { + return CResponseQueue::CFollowupTargetSpec_t( pSpecific ); + } + + Warning("Couldn't resolve response target %s\n", szTarget ); + return CResponseQueue::CFollowupTargetSpec_t(); // couldn't resolve. +} + + +// TODO: Currently uses awful stricmp. Use symbols! Once I know which ones we want, that is. +static CResponseQueue::CFollowupTargetSpec_t ResolveFollowupTargetToEntity( AIConcept_t &concept, AI_CriteriaSet &criteria, AI_Response * RESTRICT response, AI_ResponseFollowup * RESTRICT followup ) +{ + const char * RESTRICT szTarget = followup->followup_target; + const CResponseQueue::CFollowupTargetSpec_t INVALID; // default: invalid result + if ( szTarget == NULL ) + return INVALID; + else + return ResolveFollowupTargetToEntity( concept, criteria, szTarget, response ); +} + + +ConVar chet_debug_idle( "chet_debug_idle", "0", FCVAR_ARCHIVE, "If set one, many debug prints to help track down the TLK_IDLE issue. Set two for super verbose info" ); +// extern ConVar chet_debug_idle; +bool CAI_ExpresserWithFollowup::Speak( AIConcept_t concept, const char *modifiers /*= NULL*/, char *pszOutResponseChosen /* = NULL*/, size_t bufsize /* = 0 */, IRecipientFilter *filter /* = NULL */ ) +{ + VPROF("CAI_Expresser::Speak"); + if ( IsSpeechGloballySuppressed() ) + { + return false; + } + + concept.SetSpeaker(GetOuter()); + AI_CriteriaSet criteria; + GatherCriteria(&criteria, concept, modifiers); + GetOuter()->ModifyOrAppendDerivedCriteria(criteria); + AI_Response result; + if ( !FindResponse( result, concept, &criteria ) ) + { + if (chet_debug_idle.GetBool()) + { + + const char *name = GetOuter()->GetDebugName(); + + Msg( "TLK_IDLE: %s did not FindResponse\n", name ); + } + return false; + } + else + { + if (chet_debug_idle.GetBool()) + { + + + const char *name = GetOuter()->GetDebugName(); + + Msg( "TLK_IDLE: %s SUCCESSFUL FindResponse\n", name ); + } + } + + SpeechMsg( GetOuter(), "%s (%p) spoke %s (%f)", STRING(GetOuter()->GetEntityName()), GetOuter(), (const char*)concept, gpGlobals->curtime ); + // Msg( "%s:%s to %s:%s\n", GetOuter()->GetDebugName(), concept.GetStringConcept(), criteria.GetValue(criteria.FindCriterionIndex("Subject")), pTarget ? pTarget->GetDebugName() : "none" ); + + bool spoke = SpeakDispatchResponse( concept, &result, &criteria, filter ); + if ( pszOutResponseChosen ) + { + result.GetResponse( pszOutResponseChosen, bufsize ); + } + + return spoke; +} + +extern ISoundEmitterSystemBase* soundemitterbase; + +static float GetSpeechDurationForResponse( const AI_Response * RESTRICT response, const char *szActorModel) +{ + switch (response->GetType()) + { + case ResponseRules::RESPONSE_SCENE: + { + char szScene[MAX_PATH]; + soundemitterbase->GenderExpandString(szActorModel, response->GetResponsePtr(), szScene, MAX_PATH); + return GetSceneSpeechDuration(szScene); + } + break; + default: + break; + } + + return 0.f; +} + +//----------------------------------------------------------------------------- +// Purpose: Dispatches the result +// Input : *response - +//----------------------------------------------------------------------------- +bool CAI_ExpresserWithFollowup::SpeakDispatchResponse( AIConcept_t concept, AI_Response *response, AI_CriteriaSet *criteria, IRecipientFilter *filter ) +{ + // This gives the chance for the other bot to respond. + if ( !concept.GetSpeaker().IsValid() ) + { + concept.SetSpeaker(GetOuter()); + } + + bool bInterrupted = IsSpeaking(); + bool bSuc = CAI_Expresser::SpeakDispatchResponse( concept, response, criteria, filter ); + if (!bSuc) + { + return false; + } + + if ( bInterrupted ) + { + g_ResponseQueueManager.GetQueue()->RemoveSpeechQueuedFor( GetOuter() ); + } + + // Record my followup details so that I may defer its use til end of the speech + AI_ResponseFollowup * RESTRICT followup = response->GetParams()->m_pFollowup; + if ( followup ) + { + if ( followup->followup_entityiotarget && followup->followup_entityioinput ) + { +#ifdef MAPBASE + CBaseEntity * RESTRICT pTarget = ResolveFollowupTargetToEntity( concept, *criteria, followup->followup_entityiotarget, response ).m_hHandle; +#else + CBaseEntity * RESTRICT pTarget = gEntList.FindEntityByName( NULL, followup->followup_entityiotarget ); +#endif + if ( pTarget ) + { + g_EventQueue.AddEvent( pTarget, followup->followup_entityioinput, variant_t(), followup->followup_entityiodelay, GetOuter(), GetOuter() ); + } + } + if ( followup->IsValid() ) + { + // 11th hour change: rather than trigger followups from the end of a VCD, + // instead fire it from the end of the last speech event in the VCD, because + // there's a multisecond facial relax delay built into the scene. + // The speech length is stored in the cache, so we can post the followup now. + if ( response->GetType() == ResponseRules::RESPONSE_SCENE && + followup->followup_delay >= 0 ) + { + float fTimeToLastSpeech = GetSpeechDurationForResponse( response, STRING(GetOuter()->GetModelName()) ); + // failsafe + if ( fTimeToLastSpeech > 0 ) + { + DispatchFollowupThroughQueue( followup->followup_concept, followup->followup_contexts, + ResolveFollowupTargetToEntity( concept, *criteria, response, followup ), + fTimeToLastSpeech + followup->followup_delay, GetOuter() ); + } + else // error + { + // old way, copied from "else" below + m_pPostponedFollowup = followup; + if ( criteria ) + m_followupTarget = ResolveFollowupTargetToEntity( concept, *criteria, response, m_pPostponedFollowup ); + else + { + AI_CriteriaSet tmpCriteria; + m_followupTarget = ResolveFollowupTargetToEntity( concept, tmpCriteria, response, m_pPostponedFollowup ); + } + } + } + else if ( followup->followup_delay < 0 ) + { + // a negative delay has a special meaning. Usually the comeback dispatches after + // the currently said line is finished; the delay is added to that, to provide a + // pause between when character A finishes speaking and B begins. + // A negative delay (-n) actually means "dispatch the comeback n seconds + // after I start talking". + // In this case we do not need to postpone the followup; we just throw it directly + // into the queue. + DispatchFollowupThroughQueue( followup->followup_concept, followup->followup_contexts, + ResolveFollowupTargetToEntity( concept, *criteria, response, followup ), + -followup->followup_delay, GetOuter() ); + } +#ifndef MAPBASE // RESPONSE_PRINT now notes speaking time + else if ( response->GetType() == ResponseRules::RESPONSE_PRINT ) + { // zero-duration responses dispatch immediately via the queue (must be the queue bec. + // the m_pPostponedFollowup will never trigger) + DispatchFollowupThroughQueue( followup->followup_concept, followup->followup_contexts, + ResolveFollowupTargetToEntity( concept, *criteria, response, followup ), + followup->followup_delay, GetOuter() ); + } +#endif + else + { + // this is kind of a quick patch to immediately deal with the issue of null criteria + // (arose while branching to main) without replumbing a bunch of stuff -- to be fixed + // 5.13.08 egr + m_pPostponedFollowup = followup; + if ( criteria ) + m_followupTarget = ResolveFollowupTargetToEntity( concept, *criteria, response, m_pPostponedFollowup ); + else + { + AI_CriteriaSet tmpCriteria; + m_followupTarget = ResolveFollowupTargetToEntity( concept, tmpCriteria, response, m_pPostponedFollowup ); + } + } + } + } + + + return bSuc; +} + +// This is a gimmick used when a negative delay is specified in a followup, which is a shorthand +// for "this many seconds after the beginning of the line" rather than "this may seconds after the end +// of the line", eg to create a THEN rule when two characters talk over each other. +// It's static to avoid accidental use of the postponed followup/target members. +void CAI_ExpresserWithFollowup::DispatchFollowupThroughQueue( const AIConcept_t &concept, + const char * RESTRICT criteriaStr, + const CResponseQueue::CFollowupTargetSpec_t &target, + float delay, + CBaseEntity * RESTRICT pOuter + ) +{ + AI_CriteriaSet criteria; + // Don't add my own criteria! GatherCriteria( &criteria, followup.followup_concept, followup.followup_contexts ); + + criteria.AppendCriteria( "From", STRING( pOuter->GetEntityName() ) ); +#ifdef MAPBASE + // The index of the "From" entity. + // In HL2 mods, many followup users would be generic NPCs (e.g. citizens) who might not have any particular significance. + // Those generic NPCs are quite likely to have no name or have a name in common with other entities. As a result, Mapbase + // changes internal operations of the "From" context to search for an entity index. This won't be 100% reliable if the source + // talker dies and another entity is created immediately afterwards, but it's a lot more reliable than a simple entity name search. + criteria.AppendCriteria( "From_idx", CNumStr( pOuter->entindex() ) ); + + // Generic NPCs should also be attributable by classname + criteria.AppendCriteria( "From_class", pOuter->GetClassname() ); +#endif + + criteria.Merge( criteriaStr ); + g_ResponseQueueManager.GetQueue()->Add( concept, &criteria, gpGlobals->curtime + delay, target, pOuter ); +} + +//----------------------------------------------------------------------------- +// Purpose: Handles the new concept objects +//----------------------------------------------------------------------------- +void CAI_ExpresserWithFollowup::SpeakDispatchFollowup( AI_ResponseFollowup &followup ) +{ + if ( !m_followupTarget.IsValid() ) + return; + + // If a specific entity target is given, use the old pathway for now + if ( m_followupTarget.m_iTargetType == kDRT_SPECIFIC && followup.followup_delay == 0 ) + { + CBaseEntity *pTarget = m_followupTarget.m_hHandle.Get(); + if (!pTarget) + { + return; + } + DispatchComeback( this, GetOuter(), pTarget, followup ); + } + else + { + DispatchFollowupThroughQueue( followup.followup_concept, followup.followup_contexts, m_followupTarget, followup.followup_delay, GetOuter() ); + } + // clear out the followup member just in case. + m_pPostponedFollowup = NULL; + m_followupTarget.m_iTargetType = kDRT_MAX; +} + +void CAI_ExpresserWithFollowup::OnSpeechFinished() +{ + if (m_pPostponedFollowup && m_pPostponedFollowup->IsValid()) + { +#ifdef MAPBASE + // HACKHACK: Non-scene speech (e.g. noscene speak/sentence) fire OnSpeechFinished() immediately, + // so add the actual speech time to the followup delay + if (GetTimeSpeechCompleteWithoutDelay() > gpGlobals->curtime) + m_pPostponedFollowup->followup_delay += GetTimeSpeechCompleteWithoutDelay() - gpGlobals->curtime; +#endif + return SpeakDispatchFollowup(*m_pPostponedFollowup); + } +} + + + + +void CC_RR_ForceConcept_f( const CCommand &args ) +{ + if ( args.ArgC() < 3 ) + { + Msg("USAGE: rr_forceconcept \"criteria1:value1,criteria2:value2,...\"\n"); + return; + } + + AI_CriteriaSet criteria; + if ( args.ArgC() >= 3 ) + { + const char *criteriastring = args[3]; + criteria.Merge( criteriastring ); + } + + AIConcept_t concept( args[2] ); + QueueSpeak( concept, ResolveFollowupTargetToEntity( concept, criteria, args[1] ), criteria ); +} + + +static ConCommand rr_forceconcept( "rr_forceconcept", CC_RR_ForceConcept_f, + "fire a response concept directly at a given character.\n" + "USAGE: rr_forceconcept \"criteria1:value1,criteria2:value2,...\"\n" + "criteria values are optional.\n" + + , FCVAR_CHEAT ); diff --git a/src/game/server/ai_goalentity.cpp b/src/game/server/ai_goalentity.cpp index 31981097..916d9210 100644 --- a/src/game/server/ai_goalentity.cpp +++ b/src/game/server/ai_goalentity.cpp @@ -38,6 +38,15 @@ BEGIN_DATADESC( CAI_GoalEntity ) END_DATADESC() +#ifdef MAPBASE_VSCRIPT +BEGIN_ENT_SCRIPTDESC( CAI_GoalEntity, CBaseEntity, "The base class for goal entities used to control NPC behavior." ) + + DEFINE_SCRIPTFUNC( IsActive, "Check if the goal entity is active." ) + DEFINE_SCRIPTFUNC( NumActors, "Get the number of actors using this goal entity." ) + +END_SCRIPTDESC(); +#endif + //------------------------------------- diff --git a/src/game/server/ai_goalentity.h b/src/game/server/ai_goalentity.h index 2a6805f4..56252d25 100644 --- a/src/game/server/ai_goalentity.h +++ b/src/game/server/ai_goalentity.h @@ -27,6 +27,9 @@ class CAI_GoalEntity : public CBaseEntity, public IEntityListener { DECLARE_CLASS( CAI_GoalEntity, CBaseEntity ); +#ifdef MAPBASE_VSCRIPT + DECLARE_ENT_SCRIPTDESC(); +#endif public: CAI_GoalEntity() : m_iszActor(NULL_STRING), diff --git a/src/game/server/ai_hint.cpp b/src/game/server/ai_hint.cpp index de814f38..53fcdf5d 100644 --- a/src/game/server/ai_hint.cpp +++ b/src/game/server/ai_hint.cpp @@ -35,6 +35,10 @@ CHintCriteria::CHintCriteria( void ) m_strGroup = NULL_STRING; m_iFlags = 0; m_HintTypes.Purge(); +#ifdef MAPBASE // From Alien Swarm SDK + m_pfnFilter = NULL; + m_pFilterContext = NULL; +#endif } //----------------------------------------------------------------------------- @@ -892,13 +896,44 @@ BEGIN_DATADESC( CAI_Hint ) // Inputs DEFINE_INPUTFUNC( FIELD_VOID, "EnableHint", InputEnableHint ), DEFINE_INPUTFUNC( FIELD_VOID, "DisableHint", InputDisableHint ), +#ifdef MAPBASE + DEFINE_INPUTFUNC( FIELD_STRING, "SetHintGroup", InputSetHintGroup ), +#endif // Outputs DEFINE_OUTPUT( m_OnNPCStartedUsing, "OnNPCStartedUsing" ), DEFINE_OUTPUT( m_OnNPCStoppedUsing, "OnNPCStoppedUsing" ), +#ifdef MAPBASE + DEFINE_OUTPUT( m_OnScriptEvent[0], "OnScriptEvent01" ), + DEFINE_OUTPUT( m_OnScriptEvent[1], "OnScriptEvent02" ), + DEFINE_OUTPUT( m_OnScriptEvent[2], "OnScriptEvent03" ), + DEFINE_OUTPUT( m_OnScriptEvent[3], "OnScriptEvent04" ), + DEFINE_OUTPUT( m_OnScriptEvent[4], "OnScriptEvent05" ), + DEFINE_OUTPUT( m_OnScriptEvent[5], "OnScriptEvent06" ), + DEFINE_OUTPUT( m_OnScriptEvent[6], "OnScriptEvent07" ), + DEFINE_OUTPUT( m_OnScriptEvent[7], "OnScriptEvent08" ), +#endif + END_DATADESC( ); +#ifdef MAPBASE_VSCRIPT +BEGIN_ENT_SCRIPTDESC( CAI_Hint, CBaseEntity, "An entity which gives contextual pointers for NPCs." ) + + DEFINE_SCRIPTFUNC_NAMED( ScriptGetHintType, "GetHintType", "Get the hint's type ID." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptGetUser, "GetUser", "Get the hint's current user." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptGetHintGroup, "GetHintGroup", "Get the name of the hint's group." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptGetHintActivity, "GetHintActivity", "Get the name of the hint activity." ) + + DEFINE_SCRIPTFUNC( IsDisabled, "Check if the hint is disabled." ) + DEFINE_SCRIPTFUNC( IsLocked, "Check if the hint is locked." ) + DEFINE_SCRIPTFUNC( GetNodeId, "Get the hint's node ID." ) + DEFINE_SCRIPTFUNC( Yaw, "Get the hint's yaw." ) + DEFINE_SCRIPTFUNC( GetDirection, "Get the hint's direction." ) + +END_SCRIPTDESC(); +#endif + //------------------------------------------------------------------------------ // Purpose : //------------------------------------------------------------------------------ @@ -915,6 +950,18 @@ void CAI_Hint::InputDisableHint( inputdata_t &inputdata ) m_NodeData.iDisabled = true; } +#ifdef MAPBASE +void CAI_Hint::SetGroup( string_t iszNewGroup ) +{ + m_NodeData.strGroup = iszNewGroup; +} + +void CAI_Hint::InputSetHintGroup( inputdata_t &inputdata ) +{ + SetGroup(inputdata.value.StringID()); +} +#endif + //------------------------------------------------------------------------------ // Purpose : @@ -1077,6 +1124,85 @@ bool CAI_Hint::IsInNodeFOV( CBaseEntity *pOther ) return false; } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// An easy way of engaging certain hint parameters on certain hint types that didn't use it before. +//----------------------------------------------------------------------------- +void CAI_Hint::NPCHandleStartNav( CAI_BaseNPC *pNPC, bool bDefaultFacing ) +{ + Assert( pNPC != NULL ); + + HintIgnoreFacing_t facing = GetIgnoreFacing(); + if (facing == HIF_DEFAULT) + facing = bDefaultFacing ? HIF_NO : HIF_YES; + + if (facing == HIF_NO) + pNPC->GetNavigator()->SetArrivalDirection( GetDirection() ); + + if (HintActivityName() != NULL_STRING) + { + Activity hintActivity = (Activity)CAI_BaseNPC::GetActivityID( STRING(HintActivityName()) ); + if ( hintActivity != ACT_INVALID ) + { + pNPC->GetNavigator()->SetArrivalActivity( pNPC->GetHintActivity(HintType(), hintActivity) ); + } + else + { + int iSequence = pNPC->LookupSequence(STRING(HintActivityName())); + if ( iSequence != ACT_INVALID ) + { + pNPC->GetNavigator()->SetArrivalSequence( iSequence ); + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Returns true if this hint should override a NPC's yaw even during regular AI. +//----------------------------------------------------------------------------- +bool CAI_Hint::OverridesNPCYaw( CAI_BaseNPC *pNPC ) +{ + switch (HintType()) + { + case HINT_TACTICAL_COVER_CUSTOM: + case HINT_TACTICAL_COVER_MED: + case HINT_TACTICAL_COVER_LOW: + { + if (pNPC->HasMemory( bits_MEMORY_INCOVER )) + { + // By default, don't override yaw on cover nodes unless they use custom activities. + HintIgnoreFacing_t facing = GetIgnoreFacing(); + if (facing == HIF_DEFAULT) + return ( HintActivityName() != NULL_STRING ); + + return facing == HIF_NO; + } + + break; + } + + case HINT_PLAYER_ALLY_MOVE_AWAY_DEST: + { + Vector vHintPos; + GetPosition( pNPC, &vHintPos ); + if (VectorsAreEqual( vHintPos, pNPC->GetAbsOrigin(), 0.1f )) + { + // By default, don't override yaw on move away destinations unless they use custom activities. + HintIgnoreFacing_t facing = GetIgnoreFacing(); + if (facing == HIF_DEFAULT) + return ( HintActivityName() != NULL_STRING ); + + return facing == HIF_NO; + } + + break; + } + } + + return false; +} +#endif + //----------------------------------------------------------------------------- // Purpose: Locks the node for use by an AI for hints // Output : Returns true if the node was available for locking, false on failure. @@ -1194,6 +1320,30 @@ bool CAI_Hint::HintMatchesCriteria( CAI_BaseNPC *pNPC, const CHintCriteria &hint return false; } +#ifdef MAPBASE + // Test against generic filter + // (From Alien Swarm SDK) + if ( !hintCriteria.PassesFilter( this ) ) + { + REPORTFAILURE( "Failed filter test" ); + return false; + } + + // (From Alien Swarm SDK) + int nRadius = GetRadius(); + if ( nRadius != 0 ) + { + // Calculate our distance + float distance = (GetAbsOrigin() - position).LengthSqr(); + + if ( distance > nRadius * nRadius ) + { + REPORTFAILURE( "Not within the node's radius." ); + return false; + } + } +#endif + if ( hintCriteria.HasFlag(bits_HINT_NPC_IN_NODE_FOV) ) { if ( pNPC == NULL ) @@ -1313,10 +1463,18 @@ bool CAI_Hint::HintMatchesCriteria( CAI_BaseNPC *pNPC, const CHintCriteria &hint { trace_t tr; // Can my bounding box fit there? +#ifdef MAPBASE // From Alien Swarm SDK + Vector vStep( 0, 0, pNPC->StepHeight() ); + AI_TraceHull ( GetAbsOrigin() + vStep, GetAbsOrigin(), pNPC->WorldAlignMins(), pNPC->WorldAlignMaxs() - vStep, + MASK_SOLID, pNPC, COLLISION_GROUP_NONE, &tr ); + + if ( tr.fraction < 0.95 ) +#else AI_TraceHull ( GetAbsOrigin(), GetAbsOrigin(), pNPC->WorldAlignMins(), pNPC->WorldAlignMaxs(), MASK_SOLID, pNPC, COLLISION_GROUP_NONE, &tr ); if ( tr.fraction != 1.0 ) +#endif { REPORTFAILURE( "Node isn't clear." ); return false; @@ -1332,6 +1490,15 @@ bool CAI_Hint::HintMatchesCriteria( CAI_BaseNPC *pNPC, const CHintCriteria &hint // Calculate our distance float distance = (GetAbsOrigin() - position).Length(); +#ifdef MAPBASE + // Divide by hint weight + float flWeight = GetHintWeight(); + if ( flWeight != 1.0f ) + { + distance *= GetHintWeightInverse(); + } +#endif + // Must be closer than the current best if ( distance > *flNearestDistance ) { @@ -1428,6 +1595,15 @@ int CAI_Hint::DrawDebugTextOverlays(void) EntityText(text_offset,tempstr,0); text_offset++; +#ifdef MAPBASE + if (m_NodeData.strGroup != NULL_STRING) + { + Q_snprintf(tempstr,sizeof(tempstr),"hintgroup %s", STRING(m_NodeData.strGroup) ) ; + EntityText(text_offset,tempstr,0); + text_offset++; + } +#endif + if ( m_NodeData.iDisabled ) { Q_snprintf(tempstr,sizeof(tempstr),"DISABLED" ); @@ -1503,6 +1679,14 @@ void CAI_Hint::OnRestore() m_NodeData.nNodeID = g_pAINetworkManager->GetEditOps()->GetNodeIdFromWCId( m_NodeData.nWCNodeID ); FixupTargetNode(); +#ifdef MAPBASE + if (m_NodeData.flWeight != 0.0f && m_NodeData.flWeight != 1.0f) + { + // Re-invert the weight + m_NodeData.flWeightInverse = 1.0f / m_NodeData.flWeight; + } +#endif + CAI_Node *pNode = GetNode(); if ( !pNode ) @@ -1534,6 +1718,19 @@ void CAI_Hint::NPCStoppedUsing( CAI_BaseNPC *pNPC ) m_OnNPCStoppedUsing.Set( pNPC, pNPC, this ); } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CAI_Hint::FireScriptEvent( int nEvent ) +{ + if ( ( nEvent >= 1 ) && ( nEvent <= 8 ) ) + { + m_OnScriptEvent[nEvent - 1].FireOutput( m_hHintOwner, this ); + } +} +#endif + CON_COMMAND(ai_dump_hints, "") { @@ -1623,6 +1820,11 @@ hinttypedescs_t g_pszHintDescriptions[] = { HINT_HL1_WORLD_ALIEN_BLOOD, "HL1: World: Alien Blood" }, { HINT_CSTRIKE_HOSTAGE_ESCAPE, "CS Port: Hostage Escape" }, + +#ifdef MAPBASE + { HINT_TACTICAL_COVER_CUSTOM, "Mapbase: Custom Cover" }, + { HINT_TACTICAL_GRENADE_THROW, "Mapbase: Grenade Throw Hint" }, +#endif }; //----------------------------------------------------------------------------- @@ -1678,6 +1880,11 @@ void CC_ai_drop_hint( const CCommand &args ) nodeData.fIgnoreFacing = HIF_DEFAULT; nodeData.minState = NPC_STATE_IDLE; nodeData.maxState = NPC_STATE_COMBAT; +#ifdef MAPBASE + nodeData.nRadius = 0; // From Alien Swarm SDK + nodeData.flWeight = 1.0f; + nodeData.flWeightInverse = 1.0f; +#endif CAI_Hint *pHint = CAI_HintManager::CreateHint( &nodeData, NULL ); if ( pHint ) { diff --git a/src/game/server/ai_hint.h b/src/game/server/ai_hint.h index 89daef3d..b18be429 100644 --- a/src/game/server/ai_hint.h +++ b/src/game/server/ai_hint.h @@ -112,6 +112,14 @@ enum Hint_e // CS port hints HINT_CSTRIKE_HOSTAGE_ESCAPE = 1100, + +#ifdef MAPBASE + // Mapbase hints + // (these start at a high number to avoid potential conflicts with mod hints) + + HINT_TACTICAL_COVER_CUSTOM = 10000, // Cover node with a custom hint activity (NPCs can take cover and reload here while playing said activity) + HINT_TACTICAL_GRENADE_THROW, // Pre-determined position for NPCs to throw grenades at when their target in combat is near it +#endif }; const char *GetHintTypeDescription( Hint_e iHintType ); const char *GetHintTypeDescription( CAI_Hint *pHint ); @@ -120,6 +128,10 @@ const char *GetHintTypeDescription( CAI_Hint *pHint ); // CHintCriteria //----------------------------------------------------------------------------- +#ifdef MAPBASE // From Alien Swarm SDK +typedef bool (*HintSearchFilterFunc_t)( void *pContext, CAI_Hint *pCandidate ); +#endif + class CHintCriteria { public: @@ -134,6 +146,11 @@ public: void SetGroup( string_t group ); string_t GetGroup( void ) const { return m_strGroup; } +#ifdef MAPBASE // From Alien Swarm SDK + void SetFilterFunc( HintSearchFilterFunc_t pfnFilter, void *pContext = NULL ) { m_pfnFilter = pfnFilter; m_pFilterContext = pContext; } + bool PassesFilter( CAI_Hint *pCandidate ) const { return (m_pfnFilter) ? (*m_pfnFilter)(m_pFilterContext, pCandidate) : true; } +#endif + int GetFirstHintType( void ) const { return m_iFirstHintType; } int GetLastHintType( void ) const { return m_iLastHintType; } bool MatchesHintType( int hintType ) const; @@ -176,6 +193,11 @@ private: zoneList_t m_zoneInclude; zoneList_t m_zoneExclude; + +#ifdef MAPBASE + HintSearchFilterFunc_t m_pfnFilter; + void * m_pFilterContext; +#endif }; class CAI_Node; @@ -281,11 +303,19 @@ public: float Yaw( void ); CAI_Node *GetNode( void ); string_t GetGroup( void ) const { return m_NodeData.strGroup; } +#ifdef MAPBASE + void SetGroup( string_t iszNewGroup ); +#endif CBaseEntity *User( void ) const { return m_hHintOwner; }; Hint_e HintType( void ) const { return (Hint_e)m_NodeData.nHintType; }; void SetHintType( int hintType, bool force = false ); string_t HintActivityName( void ) const { return m_NodeData.iszActivityName; } int GetTargetNode( void ) const { return m_nTargetNodeID; } +#ifdef MAPBASE + // HACKHACK: This is for when target nodes need to be accessed before being sorted into engine IDs + int GetTargetWCNodeID( void ) const { return m_NodeData.nTargetWCNodeID; } + int GetWCNodeID( void ) const { return m_NodeData.nWCNodeID; } +#endif bool IsDisabled( void ) const { return (m_NodeData.iDisabled != 0); } void SetDisabled( bool bDisabled ) { m_NodeData.iDisabled = bDisabled; } void DisableForSeconds( float flSeconds ); @@ -293,6 +323,9 @@ public: void FixupTargetNode(); void NPCStartedUsing( CAI_BaseNPC *pNPC ); void NPCStoppedUsing( CAI_BaseNPC *pNPC ); +#ifdef MAPBASE + void FireScriptEvent( int nEvent ); +#endif HintIgnoreFacing_t GetIgnoreFacing() const { return m_NodeData.fIgnoreFacing; } @@ -302,10 +335,33 @@ public: int GetNodeId() { return m_NodeData.nNodeID; } int GetWCId() { return m_NodeData.nWCNodeID; } +#ifdef MAPBASE + int GetRadius() const { return m_NodeData.nRadius; } // From Alien Swarm SDK + + float GetHintWeight() const { return m_NodeData.flWeight; } + float GetHintWeightInverse() const { return m_NodeData.flWeightInverse; } // Used to multiply distances +#endif + bool HintMatchesCriteria( CAI_BaseNPC *pNPC, const CHintCriteria &hintCriteria, const Vector &position, float *flNearestDistance, bool bIgnoreLock = false, bool bIgnoreHintType = false ); bool IsInNodeFOV( CBaseEntity *pOther ); +#ifdef MAPBASE + void NPCHandleStartNav( CAI_BaseNPC *pNPC, bool bDefaultFacing ); + + // Returns true if this hint should override a NPC's yaw even during regular AI. + bool OverridesNPCYaw( CAI_BaseNPC *pNPC ); +#endif + +#ifdef MAPBASE_VSCRIPT + int ScriptGetHintType() { return (int)HintType(); } + HSCRIPT ScriptGetUser() { return ToHScript( User() ); } + const char* ScriptGetHintGroup() { return STRING( GetGroup() ); } + const char* ScriptGetHintActivity() { return STRING( HintActivityName() ); } +#endif + +#ifndef MAPBASE private: +#endif void Spawn( void ); virtual void Activate(); virtual void UpdateOnRemove( void ); @@ -317,6 +373,9 @@ private: // Input handlers void InputEnableHint( inputdata_t &inputdata ); void InputDisableHint( inputdata_t &inputdata ); +#ifdef MAPBASE + void InputSetHintGroup( inputdata_t &inputdata ); +#endif private: @@ -329,10 +388,17 @@ private: float m_nodeFOV; Vector m_vecForward; +#ifdef MAPBASE + COutputEvent m_OnScriptEvent[8]; +#endif + // The next hint in list of all hints friend class CAI_HintManager; DECLARE_DATADESC(); +#ifdef MAPBASE_VSCRIPT + DECLARE_ENT_SCRIPTDESC(); +#endif }; #define SF_ALLOW_JUMP_UP 65536 diff --git a/src/game/server/ai_initutils.cpp b/src/game/server/ai_initutils.cpp index 8c476719..cd792798 100644 --- a/src/game/server/ai_initutils.cpp +++ b/src/game/server/ai_initutils.cpp @@ -173,6 +173,10 @@ BEGIN_SIMPLE_DATADESC( HintNodeData ) DEFINE_KEYFIELD( fIgnoreFacing, FIELD_INTEGER, "IgnoreFacing" ), DEFINE_KEYFIELD( minState, FIELD_INTEGER, "MinimumState" ), DEFINE_KEYFIELD( maxState, FIELD_INTEGER, "MaximumState" ), +#ifdef MAPBASE + DEFINE_KEYFIELD( nRadius, FIELD_INTEGER, "radius" ), // From Alien Swarm SDK + DEFINE_KEYFIELD( flWeight, FIELD_FLOAT, "hintweight" ), +#endif END_DATADESC() @@ -205,6 +209,17 @@ int CNodeEnt::Spawn( const char *pMapData ) m_NodeData.minState = NPC_STATE_IDLE; if ( m_NodeData.maxState == NPC_STATE_NONE ) m_NodeData.maxState = NPC_STATE_COMBAT; +#ifdef MAPBASE + if (m_NodeData.flWeight == 0.0f) + { + m_NodeData.flWeight = 1.0f; + } + else if (m_NodeData.flWeight != 1.0f) + { + // Invert the weight so that it could be used as a direct multiplier for distances, etc. + m_NodeData.flWeightInverse = 1.0f / m_NodeData.flWeight; + } +#endif // --------------------------------------------------------------------------------- // If just a hint node (not used for navigation) just create a hint and bail // --------------------------------------------------------------------------------- @@ -227,7 +242,11 @@ int CNodeEnt::Spawn( const char *pMapData ) // --------------------------------------------------------------------------------- CAI_Hint *pHint = NULL; - if ( ClassMatches( "info_node_hint" ) || ClassMatches( "info_node_air_hint" ) ) + if ( ClassMatches( "info_node_hint" ) || ClassMatches( "info_node_air_hint" ) +#ifdef MAPBASE + || ClassMatches( "info_node_climb" ) // Climb nodes contain hint data in the FGD +#endif + ) { if ( m_NodeData.nHintType || m_NodeData.strGroup != NULL_STRING || m_NodeData.strEntityName != NULL_STRING ) { diff --git a/src/game/server/ai_initutils.h b/src/game/server/ai_initutils.h index 22aaf5f3..70bd019c 100644 --- a/src/game/server/ai_initutils.h +++ b/src/game/server/ai_initutils.h @@ -42,6 +42,11 @@ struct HintNodeData HintIgnoreFacing_t fIgnoreFacing; NPC_STATE minState; NPC_STATE maxState; +#ifdef MAPBASE + int nRadius; // From Alien Swarm SDK + float flWeight; + float flWeightInverse; // Not saved +#endif int nWCNodeID; // Node ID assigned by worldcraft (not same as engine!) diff --git a/src/game/server/ai_motor.cpp b/src/game/server/ai_motor.cpp index 49f3d5f4..43eb86a3 100644 --- a/src/game/server/ai_motor.cpp +++ b/src/game/server/ai_motor.cpp @@ -14,6 +14,9 @@ #include "ai_basenpc.h" #include "ai_localnavigator.h" #include "ai_moveprobe.h" +#ifdef MAPBASE +#include "ai_hint.h" +#endif #include "saverestore_utlvector.h" // memdbgon must be the last include file in a .cpp file!!! @@ -235,18 +238,47 @@ void CAI_Motor::MoveClimbStart( const Vector &climbDest, const Vector &climbDir // > code are not reciprocal for all state, and furthermore, stomp // > other state? + bool bGoingUp = (climbDir.z > 0.01); +#if EXPANDED_NAVIGATION_ACTIVITIES + if ( bGoingUp && GetOuter()->HaveSequenceForActivity( ACT_CLIMB_MOUNT_BOTTOM ) ) + { + SetActivity( ACT_CLIMB_MOUNT_BOTTOM ); + + // Steal m_vecDismount for this + GetOuter()->GetSequenceLinearMotion( GetSequence(), &m_vecDismount ); + GetOuter()->SetCycle( GetOuter()->GetMovementFrame( m_vecDismount.z - climbDist ) ); + } + else if ( !bGoingUp && GetOuter()->HaveSequenceForActivity( ACT_CLIMB_MOUNT_TOP ) ) + { + SetActivity( ACT_CLIMB_MOUNT_TOP ); + + // Steal m_vecDismount for this + GetOuter()->GetSequenceLinearMotion( GetSequence(), &m_vecDismount ); + GetOuter()->SetCycle( GetOuter()->GetMovementFrame( m_vecDismount.z - climbDist ) ); + } + else +#endif if ( fabsf( climbDir.z ) < .1 ) { SetActivity( GetNavigator()->GetMovementActivity() ); } else { - SetActivity( (climbDir.z > -0.01 ) ? ACT_CLIMB_UP : ACT_CLIMB_DOWN ); + SetActivity( bGoingUp ? ACT_CLIMB_UP : ACT_CLIMB_DOWN ); } m_nDismountSequence = SelectWeightedSequence( ACT_CLIMB_DISMOUNT ); if (m_nDismountSequence != ACT_INVALID) { +#if EXPANDED_NAVIGATION_ACTIVITIES + if ( !bGoingUp ) + { + int nBottomDismount = SelectWeightedSequence( ACT_CLIMB_DISMOUNT_BOTTOM ); + if (nBottomDismount != ACTIVITY_NOT_AVAILABLE) + m_nDismountSequence = nBottomDismount; + } +#endif + GetOuter()->GetSequenceLinearMotion( m_nDismountSequence, &m_vecDismount ); } else @@ -262,6 +294,76 @@ void CAI_Motor::MoveClimbStart( const Vector &climbDest, const Vector &climbDir AIMoveResult_t CAI_Motor::MoveClimbExecute( const Vector &climbDest, const Vector &climbDir, float climbDist, float yaw, int climbNodesLeft ) { +#if EXPANDED_NAVIGATION_ACTIVITIES + if ( (GetActivity() == ACT_CLIMB_MOUNT_TOP || GetActivity() == ACT_CLIMB_MOUNT_BOTTOM) ) + { + if (!GetOuter()->IsActivityFinished()) + { + // Wait for the mounting to finish + SetGroundEntity( NULL ); + } + else + { + // Fix up our position if we have to + Vector vecTeleportOrigin; + if (MoveClimbShouldTeleportToSequenceEnd( vecTeleportOrigin )) + { + SetLocalOrigin( vecTeleportOrigin ); + } + + // Reset activity and start from the beginning + GetOuter()->ResetActivity(); + return MoveClimbExecute( climbDest, climbDir, climbDist, yaw, climbNodesLeft ); + } + } + else if ( fabsf( climbDir.z ) > .1 && (GetActivity() != ACT_CLIMB_DISMOUNT && GetActivity() != ACT_CLIMB_DISMOUNT_BOTTOM) ) + { + bool bGoingUp = (climbDir.z > -0.01); + if ( GetOuter()->HaveSequenceForActivity( ACT_CLIMB_ALL ) ) + { + SetActivity( ACT_CLIMB_ALL ); + + // TODO: Use UTIL_VecToPitch() instead if move_yaw becomes a true climb yaw and not just an up-down scalar + SetPoseParameter( GetOuter()->LookupPoseMoveYaw(), climbDir.z < 0 ? 180.0 : -180.0 ); + } + else + { + Activity desiredActivity = bGoingUp ? ACT_CLIMB_UP : ACT_CLIMB_DOWN; + if ( GetActivity() != desiredActivity ) + { + SetActivity( desiredActivity ); + } + } + + if (m_nDismountSequence != ACT_INVALID) + { + if (climbNodesLeft <= 2 && climbDist < fabs( m_vecDismount.z )) + { + if (bGoingUp) + { + // fixme: No other way to force m_nIdealSequence? + GetOuter()->SetActivity( ACT_CLIMB_DISMOUNT ); + GetOuter()->SetCycle( GetOuter()->GetMovementFrame( m_vecDismount.z - climbDist ) ); + } + else + { + if (GetSequence() != m_nDismountSequence && GetOuter()->GetSequenceActivity( m_nDismountSequence ) == ACT_CLIMB_DISMOUNT_BOTTOM) + { + SetActivity( ACT_CLIMB_DISMOUNT_BOTTOM ); + } + } + } + } + } + else if ( climbDir.Length() == 0 && GetOuter()->GetInstantaneousVelocity() <= 0.01 ) + { + // The NPC is somehow stuck climbing with no direction or movement. + // This can be caused by NPCs getting stuck in each other and/or being moved away from the ladder. + // In these cases, the NPC has to be made unstuck, or else they may remain in an immobile climbing state forever. + Warning( "%s had to abort climbing due to no direction or movement\n", GetOuter()->GetDebugName() ); + return AIMR_ILLEGAL; + } +#else if ( fabsf( climbDir.z ) > .1 ) { if ( GetActivity() != ACT_CLIMB_DISMOUNT ) @@ -292,13 +394,34 @@ AIMoveResult_t CAI_Motor::MoveClimbExecute( const Vector &climbDest, const Vecto } } } +#endif float climbSpeed = GetOuter()->GetInstantaneousVelocity(); if (m_nDismountSequence != ACT_INVALID) { // catch situations where the climb mount/dismount finished before reaching goal +#if EXPANDED_NAVIGATION_ACTIVITIES + if ((GetActivity() == ACT_CLIMB_DISMOUNT || GetActivity() == ACT_CLIMB_DISMOUNT_BOTTOM)) + { + SetGroundEntity( NULL ); + + if (GetOuter()->IsActivityFinished()) + { + // Fix up our position if we have to + Vector vecTeleportOrigin; + if (MoveClimbShouldTeleportToSequenceEnd( vecTeleportOrigin )) + { + // Just force it to complete + climbDist = 0.0f; + } + + climbSpeed = 200.0f; + } + } +#else climbSpeed = MAX( climbSpeed, 30.0 ); +#endif } else { @@ -314,7 +437,7 @@ AIMoveResult_t CAI_Motor::MoveClimbExecute( const Vector &climbDest, const Vecto climbDist = 0; const float climbTime = climbDist / climbSpeed; - + SetMoveInterval( GetMoveInterval() - climbTime ); SetLocalOrigin( climbDest ); @@ -330,6 +453,20 @@ AIMoveResult_t CAI_Motor::MoveClimbExecute( const Vector &climbDest, const Vecto // -------------------------------------------- SetIdealYawAndUpdate( yaw ); +#ifdef MAPBASE + // Lock the yaw if we're in position + if ( UTIL_AngleMod( yaw ) == UTIL_AngleMod( GetLocalAngles().y ) ) + { + SetYawLocked( true ); + } + else if ( IsYawLocked() ) + { + // We're in a different position now. Unlock the yaw and update it + SetYawLocked( false ); + UpdateYaw( -1 ); + } +#endif + return AIMR_OK; } @@ -340,11 +477,62 @@ void CAI_Motor::MoveClimbStop() else SetActivity( ACT_IDLE ); +#ifdef MAPBASE + // Unlock desired weapon state so NPCs can unholster their weapons again. + GetOuter()->SetDesiredWeaponState( DESIREDWEAPONSTATE_IGNORE ); + + // Unlock yaw + SetYawLocked( false ); +#endif + GetOuter()->RemoveFlag( FL_FLY ); SetSmoothedVelocity( vec3_origin ); SetGravity( 1.0 ); } +#ifdef MAPBASE +void CAI_Motor::MoveClimbPause() +{ + if (GetActivity() != ACT_CLIMB_DISMOUNT +#if EXPANDED_NAVIGATION_ACTIVITIES + && GetActivity() != ACT_CLIMB_MOUNT_TOP && GetActivity() != ACT_CLIMB_MOUNT_BOTTOM +#endif + ) + { + if ( GetActivity() == ACT_CLIMB_ALL ) + { + SetPoseParameter( GetOuter()->LookupPoseMoveYaw(), 0.0f ); + } + + SetSmoothedVelocity( vec3_origin ); + } + else + { + // If already dismounting, do nothing + } +} + +//----------------------------------------------------------------------------- +// Purpose: This is part of a hack needed in cases where ladder mount/dismount animations collide with the world and don't move properly. +// It's based off of the same code as scripted_sequence's teleportation fixup, although this function only resolves the bone origin and +// returns whether or not teleportation is necessary, as the teleportation is achieved in different ways for different uses of this code. +//----------------------------------------------------------------------------- +bool CAI_Motor::MoveClimbShouldTeleportToSequenceEnd( Vector &teleportOrigin ) +{ + QAngle new_angle; + GetOuter()->GetBonePosition( 0, teleportOrigin, new_angle ); + + // Ensure that there is actually a distance needed to teleport there + if ((GetLocalOrigin() - teleportOrigin).Length2DSqr() > Square( 8.0 )) + { + teleportOrigin.z = GetLocalOrigin().z; + return true; + } + + return false; +} +#endif + //----------------------------------------------------------------------------- // Purpose: Motion for jumping // Input : @@ -651,9 +839,21 @@ void CAI_Motor::MoveFacing( const AILocalMoveGoal_t &move ) { // FIXME: move this up to navigator so that path goals can ignore these overrides. Vector dir; - float flInfluence = GetFacingDirection( dir ); - dir = move.facing * (1 - flInfluence) + dir * flInfluence; - VectorNormalize( dir ); + +#ifdef MAPBASE + if (IsDeceleratingToGoal() && (GetOuter()->GetHintNode() /*|| GetOuter()->m_hOpeningDoor*/)) + { + // Don't let the facing queue interfere with arrival direction in important cases + dir = move.facing; + VectorNormalize( dir ); + } + else +#endif + { + float flInfluence = GetFacingDirection( dir ); + dir = move.facing * (1 - flInfluence) + dir * flInfluence; + VectorNormalize( dir ); + } // ideal facing direction float idealYaw = UTIL_AngleMod( UTIL_VecToYaw( dir ) ); diff --git a/src/game/server/ai_motor.h b/src/game/server/ai_motor.h index d7f14293..29c05f84 100644 --- a/src/game/server/ai_motor.h +++ b/src/game/server/ai_motor.h @@ -62,6 +62,10 @@ public: virtual void MoveClimbStart( const Vector &climbDest, const Vector &climbDir, float climbDist, float yaw ); virtual AIMoveResult_t MoveClimbExecute( const Vector &climbDest, const Vector &climbDir, float climbDist, float yaw, int climbNodesLeft ); virtual void MoveClimbStop(); +#ifdef MAPBASE + virtual void MoveClimbPause(); + virtual bool MoveClimbShouldTeleportToSequenceEnd( Vector &teleportOrigin ); +#endif //--------------------------------- @@ -83,6 +87,9 @@ public: const Vector & GetCurVel() const { return m_vecVelocity; } virtual float OverrideMaxYawSpeed( Activity activity ) { return -1; } +#ifdef MAPBASE + virtual +#endif bool IsDeceleratingToGoal() const { return false; } //--------------------------------- diff --git a/src/game/server/ai_moveprobe.cpp b/src/game/server/ai_moveprobe.cpp index b2a86d83..bdf8796b 100644 --- a/src/game/server/ai_moveprobe.cpp +++ b/src/game/server/ai_moveprobe.cpp @@ -94,10 +94,16 @@ bool CAI_MoveProbe::ShouldBrushBeIgnored( CBaseEntity *pEntity ) CFuncBrush *pFuncBrush = assert_cast(pEntity); // this is true if my class or entity name matches the exclusion name on the func brush +#ifdef MAPBASE + // The only way it could conflict is if a map has a NPC using a classname as its targetname, like a single npc_fastzombie entity referred to as "npc_zombie". + // I doubt anyone's doing that unless they don't know what they're doing, and even then this still shouldn't be barred from plain-HL2 mappers. + bool nameMatches = GetOuter()->ClassMatches(pFuncBrush->m_iszExcludedClass) || GetOuter()->NameMatches(pFuncBrush->m_iszExcludedClass); +#else #if HL2_EPISODIC bool nameMatches = ( pFuncBrush->m_iszExcludedClass == GetOuter()->m_iClassname ) || GetOuter()->NameMatches(pFuncBrush->m_iszExcludedClass); #else // do not match against entity name in base HL2 (just in case there is some case somewhere that might be broken by this) bool nameMatches = ( pFuncBrush->m_iszExcludedClass == GetOuter()->m_iClassname ); +#endif #endif // return true (ignore brush) if the name matches, or, if exclusion is inverted, if the name does not match diff --git a/src/game/server/ai_navigator.cpp b/src/game/server/ai_navigator.cpp index ddbb5c3e..dda616a0 100644 --- a/src/game/server/ai_navigator.cpp +++ b/src/game/server/ai_navigator.cpp @@ -999,12 +999,23 @@ int CAI_Navigator::GetArrivalSequence( int curSequence ) activity = ACT_IDLE; } - sequence = GetOuter()->SelectWeightedSequence( GetOuter()->TranslateActivity( activity ), curSequence ); + Activity translatedActivity = GetOuter()->TranslateActivity( activity ); + sequence = GetOuter()->SelectWeightedSequence( translatedActivity, curSequence ); if ( sequence == ACT_INVALID ) { - DevMsg( GetOuter(), "No appropriate sequence for arrival activity %s (%d)\n", GetOuter()->GetActivityName( GetPath()->GetArrivalActivity() ), GetPath()->GetArrivalActivity() ); - sequence = GetOuter()->SelectWeightedSequence( GetOuter()->TranslateActivity( ACT_IDLE ), curSequence ); +#ifdef MAPBASE + if ( translatedActivity == ACT_SCRIPT_CUSTOM_MOVE ) + { + // ACT_SCRIPT_CUSTOM_MOVE allows activity translation to resolve into specific sequences + sequence = GetOuter()->GetScriptCustomMoveSequence(); + } + else +#endif + { + DevMsg( GetOuter(), "No appropriate sequence for arrival activity %s (%d)\n", GetOuter()->GetActivityName( GetPath()->GetArrivalActivity() ), GetPath()->GetArrivalActivity() ); + sequence = GetOuter()->SelectWeightedSequence( GetOuter()->TranslateActivity( ACT_IDLE ), curSequence ); + } } Assert( sequence != ACT_INVALID ); GetPath()->SetArrivalSequence( sequence ); @@ -1642,6 +1653,15 @@ void CAI_Navigator::MoveCalcBaseGoal( AILocalMoveGoal_t *pMoveGoal ) AI_Waypoint_t *pCurWaypoint = GetPath()->GetCurWaypoint(); if ( pCurWaypoint->GetNext() && pCurWaypoint->GetNext()->NavType() != pCurWaypoint->NavType() ) pMoveGoal->flags |= AILMG_TARGET_IS_TRANSITION; + +#ifdef MAPBASE + // TODO: Better place for this code? + if (pMoveGoal->flags & AILMG_TARGET_IS_TRANSITION && pCurWaypoint->GetNext()->NavType() == NAV_CLIMB) + { + // NPCs need to holster their weapons before climbing. + GetOuter()->SetDesiredWeaponState( DESIREDWEAPONSTATE_HOLSTERED ); + } +#endif } const Task_t *pCurTask = GetOuter()->GetTask(); @@ -2164,11 +2184,26 @@ bool CAI_Navigator::OnMoveBlocked( AIMoveResult_t *pResult ) if (pDoor != NULL) { GetOuter()->OpenPropDoorBegin( pDoor ); +#ifdef MAPBASE + // Tell the navigation to stop running until we're done. + OnNewGoal(); +#endif *pResult = AIMR_OK; return true; } } +#ifdef MAPBASE + if ( GetOuter()->m_hOpeningDoor ) + { + // In the process of opening a door + // Because navigation is now supposed to terminate when a NPC begins opening a door, this code should not be reached. + DbgNavMsg( GetOuter(), "CAI_Navigator::OnMoveBlocked had to check for m_hOpeningDoor\n" ); + *pResult = AIMR_OK; + return true; + } +#endif + // Allow the NPC to override this behavior if ( GetOuter()->OnMoveBlocked( pResult )) @@ -2591,8 +2626,12 @@ bool CAI_Navigator::Move( float flInterval ) if ( GetNavType() == NAV_CLIMB ) { +#ifdef MAPBASE + GetMotor()->MoveClimbPause(); +#else GetMotor()->MoveClimbStop(); SetNavType( NAV_GROUND ); +#endif } GetMotor()->MoveStop(); AssertMsg( TaskIsRunning() || TaskIsComplete(), ("Schedule stalled!!\n") ); @@ -2695,6 +2734,11 @@ void CAI_Navigator::AdvancePath() if (pDoor != NULL) { GetOuter()->OpenPropDoorBegin(pDoor); + +#ifdef MAPBASE + // Tell the navigation to stop running until we're done. + OnNewGoal(); +#endif } else { @@ -3880,7 +3924,12 @@ bool CAI_Navigator::GetStoppingPath( CAI_WaypointList * pClippedWaypoints ) AI_Waypoint_t *pCurWaypoint = GetPath()->GetCurWaypoint(); if ( pCurWaypoint ) { +#if EXPANDED_NAVIGATION_ACTIVITIES + // Since regular climb nav can interrupt itself now, only do this when dismounting + bool bMustCompleteCurrent = ( (pCurWaypoint->NavType() == NAV_CLIMB && (GetActivity() == ACT_CLIMB_DISMOUNT || GetActivity() == ACT_CLIMB_MOUNT_TOP)) || pCurWaypoint->NavType() == NAV_JUMP ); +#else bool bMustCompleteCurrent = ( pCurWaypoint->NavType() == NAV_CLIMB || pCurWaypoint->NavType() == NAV_JUMP ); +#endif float distRemaining = GetMotor()->MinStoppingDist( 0 ); if ( bMustCompleteCurrent ) diff --git a/src/game/server/ai_network.cpp b/src/game/server/ai_network.cpp index 78b7a53f..1323b7ce 100644 --- a/src/game/server/ai_network.cpp +++ b/src/game/server/ai_network.cpp @@ -16,6 +16,9 @@ #include "ai_navigator.h" #include "world.h" #include "ai_moveprobe.h" +#ifdef MAPBASE_VSCRIPT +#include "ai_hint.h" +#endif // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" @@ -31,6 +34,53 @@ extern float MOVE_HEIGHT_EPSILON; // later point we will probabaly have multiple AINetworkds per level CAI_Network* g_pBigAINet; +#ifdef MAPBASE_VSCRIPT +BEGIN_SCRIPTDESC_ROOT( CAI_Network, SCRIPT_SINGLETON "The global list of AI nodes." ) + DEFINE_SCRIPTFUNC( NumNodes, "Number of nodes in the level" ) + + DEFINE_SCRIPTFUNC_NAMED( ScriptGetNodePosition, "GetNodePosition", "Get position of node using a generic human hull" ) + DEFINE_SCRIPTFUNC_NAMED( ScriptGetNodePositionWithHull, "GetNodePositionWithHull", "Get position of node using the specified hull" ) + DEFINE_SCRIPTFUNC( GetNodeYaw, "Get yaw of node" ) + + DEFINE_SCRIPTFUNC_NAMED( ScriptNearestNodeToPoint, "NearestNodeToPoint", "Get ID of nearest node" ) + DEFINE_SCRIPTFUNC_NAMED( ScriptNearestNodeToPointWithNPC, "NearestNodeToPointForNPC", "Get ID of nearest node using the specified NPC's properties" ) + + DEFINE_SCRIPTFUNC_NAMED( ScriptGetNodeType, "GetNodeType", "Get a node's type" ) + + DEFINE_SCRIPTFUNC_NAMED( ScriptGetNodeHint, "GetNodeHint", "Get a node's hint" ) +END_SCRIPTDESC(); + +HSCRIPT CAI_Network::ScriptGetNodeHint( int nodeID ) +{ + CAI_Node *pNode = GetNode( nodeID ); + if (!pNode) + return NULL; + + return ToHScript( pNode->GetHint() ); +} + +int CAI_Network::ScriptGetNodeType( int nodeID ) +{ + CAI_Node *pNode = GetNode( nodeID ); + if (!pNode) + return NULL; + + return (int)pNode->GetType(); +} + +int CAI_Network::ScriptNearestNodeToPointWithNPC( HSCRIPT hNPC, const Vector &vecPosition, bool bCheckVisibility ) +{ + CBaseEntity *pEnt = ToEnt( hNPC ); + if (!pEnt || !pEnt->MyNPCPointer()) + { + Warning("vscript: NearestNodeToPointWithNPC - Invalid NPC\n"); + return NO_NODE; + } + + return NearestNodeToPoint( pEnt->MyNPCPointer(), vecPosition, bCheckVisibility ); +} +#endif + //----------------------------------------------------------------------------- abstract_class INodeListFilter @@ -94,6 +144,31 @@ public: int m_capabilities; // cache this }; +#ifdef MAPBASE +//------------------------------------- +// Purpose: A version of CNodeFilter which allows hints to influence the result. +//------------------------------------- +class CNodeHintFilter : public CNodeFilter +{ +public: + CNodeHintFilter( CAI_BaseNPC *pNPC, const Vector &pos ) : CNodeFilter( pNPC, pos ) {} + CNodeHintFilter( const Vector &pos ) : CNodeFilter( pos ) {} + + virtual float NodeDistanceSqr( CAI_Node &node ) + { + // Heavier hints are considered closer + if ( node.GetHint() && node.GetHint()->GetHintWeight() != 1.0f && (node.GetHint()->GetGroup() == NULL_STRING || node.GetHint()->GetGroup() == m_pNPC->GetHintGroup()) ) + { + return CNodeFilter::NodeDistanceSqr( node ) * node.GetHint()->GetHintWeightInverse(); + } + else + { + return CNodeFilter::NodeDistanceSqr( node ); + } + } +}; +#endif + //----------------------------------------------------------------------------- // CAI_Network //----------------------------------------------------------------------------- @@ -301,7 +376,12 @@ int CAI_Network::NearestNodeToPoint( CAI_BaseNPC *pNPC, const Vector &vecOrigin, // First get nodes distances and eliminate those that are beyond // the maximum allowed distance for local movements // --------------------------------------------------------------- +#ifdef MAPBASE + // Allow hint weight to influence supposed distance + CNodeHintFilter filter( pNPC, vecOrigin ); +#else CNodeFilter filter( pNPC, vecOrigin ); +#endif #ifdef AI_PERF_MON m_nPerfStatNN++; @@ -555,8 +635,13 @@ CAI_Node *CAI_Network::AddNode( const Vector &origin, float yaw ) CAI_Link *CAI_Network::CreateLink( int srcID, int destID, CAI_DynamicLink *pDynamicLink ) { +#ifdef MAPBASE // From Alien Swarm SDK + CAI_Node *pSrcNode = GetNode( srcID ); + CAI_Node *pDestNode = GetNode( destID ); +#else CAI_Node *pSrcNode = g_pBigAINet->GetNode( srcID ); CAI_Node *pDestNode = g_pBigAINet->GetNode( destID ); +#endif Assert( pSrcNode && pDestNode && pSrcNode != pDestNode ); diff --git a/src/game/server/ai_network.h b/src/game/server/ai_network.h index 0c4d525b..29b667d7 100644 --- a/src/game/server/ai_network.h +++ b/src/game/server/ai_network.h @@ -127,6 +127,17 @@ public: } CAI_Node** AccessNodes() const { return m_pAInode; } + +#ifdef MAPBASE_VSCRIPT + Vector ScriptGetNodePosition( int nodeID ) { return GetNodePosition( HULL_HUMAN, nodeID ); } + Vector ScriptGetNodePositionWithHull( int nodeID, int hull ) { return GetNodePosition( (Hull_t)hull, nodeID ); } + + int ScriptNearestNodeToPoint( const Vector &vecPosition, bool bCheckVisibility = true ) { return NearestNodeToPoint( NULL, vecPosition, bCheckVisibility ); } + int ScriptNearestNodeToPointWithNPC( HSCRIPT hNPC, const Vector &vecPosition, bool bCheckVisibility = true ); + + HSCRIPT ScriptGetNodeHint( int nodeID ); + int ScriptGetNodeType( int nodeID ); +#endif private: friend class CAI_NetworkManager; diff --git a/src/game/server/ai_networkmanager.cpp b/src/game/server/ai_networkmanager.cpp index dcc0647b..1388a919 100644 --- a/src/game/server/ai_networkmanager.cpp +++ b/src/game/server/ai_networkmanager.cpp @@ -25,6 +25,9 @@ #include "ndebugoverlay.h" #include "ai_hint.h" #include "tier0/icommandline.h" +#ifdef MAPBASE +#include "gameinterface.h" +#endif // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" @@ -69,6 +72,14 @@ CON_COMMAND( ai_debug_node_connect, "Debug the attempted connection between two // line to properly override the node graph building. ConVar g_ai_norebuildgraph( "ai_norebuildgraph", "0" ); +#ifdef MAPBASE +ConVar g_ai_norebuildgraphmessage( "ai_norebuildgraphmessage", "0", FCVAR_ARCHIVE, "Stops the \"Node graph out of date\" message from appearing when rebuilding node graph" ); + +ConVar g_ai_norebuildgraph_if_in_chapters( "ai_norebuildgraph_if_in_chapters", "0", FCVAR_NONE, "Ignores rebuilding nodegraph if it's in chapters.txt. This allows for bypassing problems with Steam rebuilding nodegraphs in a mod's main maps without affecting custom maps." ); + +extern CUtlVector *Mapbase_GetChapterMaps(); +extern CUtlVector *Mapbase_GetChapterList(); +#endif //----------------------------------------------------------------------------- @@ -945,6 +956,13 @@ void CAI_NetworkManager::InitializeAINetworks() } } +#ifdef MAPBASE_VSCRIPT + if (g_pScriptVM) + { + g_pScriptVM->RegisterInstance( g_pBigAINet, "AINetwork" ); + } +#endif + // Reset node counter used during load CNodeEnt::m_nNodeCount = 0; @@ -976,6 +994,24 @@ bool CAI_NetworkManager::IsAIFileCurrent ( const char *szMapName ) // dvd build process validates and guarantees correctness, timestamps are allowed to be wrong return true; } + +#ifdef MAPBASE + if (g_ai_norebuildgraph_if_in_chapters.GetBool()) + { + // Look in the mod's chapter list. If this map is part of one of the chapters, consider it to have a good node graph + CUtlVector *ModChapterComments = Mapbase_GetChapterMaps(); + if (ModChapterComments->Count() > 0) + { + for ( int i = 0; i < ModChapterComments->Count(); i++ ) + { + if ( !Q_strnicmp( STRING(gpGlobals->mapname), ModChapterComments->Element(i).pBSPName, strlen(ModChapterComments->Element(i).pBSPName) ) ) + { + return true; + } + } + } + } +#endif { const char *pGameDir = CommandLine()->ParmValue( "-game", "hl2" ); @@ -1110,9 +1146,18 @@ void CAI_NetworkManager::DelayedInit( void ) #endif DevMsg( "Node Graph out of Date. Rebuilding... (%d, %d, %d)\n", (int)m_bDontSaveGraph, (int)!CAI_NetworkManager::NetworksLoaded(), (int) engine->IsInEditMode() ); +#ifdef MAPBASE + if (!g_ai_norebuildgraphmessage.GetBool()) + UTIL_CenterPrintAll( "Node Graph out of Date. Rebuilding...\n" ); + + // Do it much sooner after map load + g_pAINetworkManager->SetNextThink( gpGlobals->curtime + 0.5 ); + m_bNeedGraphRebuild = true; +#else UTIL_CenterPrintAll( "Node Graph out of Date. Rebuilding...\n" ); m_bNeedGraphRebuild = true; g_pAINetworkManager->SetNextThink( gpGlobals->curtime + 1 ); +#endif return; } @@ -3040,6 +3085,16 @@ int CAI_NetworkBuilder::ComputeConnection( CAI_Node *pSrcNode, CAI_Node *pDestNo } else { +#ifdef MAPBASE + // This is kind of a hack since target node IDs are designed to be used *after* the nodegraph is generated. + // However, for the purposes of forcing a climb connection outside of regular lineup bounds, it seems to be a reasonable solution. + if (pSrcNode->GetHint() && pDestNode->GetHint() && + (pSrcNode->GetHint()->GetTargetWCNodeID() == pDestNode->GetHint()->GetWCId() || pDestNode->GetHint()->GetTargetWCNodeID() == pSrcNode->GetHint()->GetWCId())) + { + DebugConnectMsg( srcId, destId, " Ignoring climbing lineup due to manual target ID linkage\n" ); + } + else +#endif if ( !IsInLineForClimb(srcPos, UTIL_YawToVector( pSrcNode->m_flYaw ), destPos, UTIL_YawToVector( pDestNode->m_flYaw ) ) ) { Assert( !IsInLineForClimb(destPos, UTIL_YawToVector( pDestNode->m_flYaw ), srcPos, UTIL_YawToVector( pSrcNode->m_flYaw ) ) ); diff --git a/src/game/server/ai_pathfinder.cpp b/src/game/server/ai_pathfinder.cpp index 5099925e..61c641a5 100644 --- a/src/game/server/ai_pathfinder.cpp +++ b/src/game/server/ai_pathfinder.cpp @@ -597,6 +597,17 @@ bool CAI_Pathfinder::IsLinkUsable(CAI_Link *pLink, int startID) // -------------------------------------------------------------------------- // Skip if link turned off // -------------------------------------------------------------------------- +#ifdef MAPBASE + if (pLink->m_pDynamicLink) + { + if (!pLink->m_pDynamicLink->UseAllowed(GetOuter(), startID == pLink->m_pDynamicLink->m_nDestID)) + return false; + } + else if (pLink->m_LinkInfo & bits_LINK_OFF) + { + return false; + } +#else if (pLink->m_LinkInfo & bits_LINK_OFF) { CAI_DynamicLink *pDynamicLink = pLink->m_pDynamicLink; @@ -618,6 +629,7 @@ bool CAI_Pathfinder::IsLinkUsable(CAI_Link *pLink, int startID) return false; } } +#endif // -------------------------------------------------------------------------- // Get the destination nodeID @@ -691,6 +703,12 @@ bool CAI_Pathfinder::IsLinkUsable(CAI_Link *pLink, int startID) return false; } } +#ifdef MAPBASE + if (pLink->m_pDynamicLink) + { + return pLink->m_pDynamicLink->FinalUseAllowed(GetOuter(), startID == pLink->m_pDynamicLink->m_nDestID); + } +#endif return true; } diff --git a/src/game/server/ai_playerally.cpp b/src/game/server/ai_playerally.cpp index 1ce4da31..c7e684e1 100644 --- a/src/game/server/ai_playerally.cpp +++ b/src/game/server/ai_playerally.cpp @@ -12,6 +12,10 @@ #include "eventqueue.h" #include "ai_behavior_lead.h" #include "gameinterface.h" +#ifdef MAPBASE +#include "mapbase/matchers.h" +#include "ai_memory.h" +#endif // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" @@ -125,6 +129,14 @@ ConceptInfo_t g_ConceptInfos[] = // Passenger behaviour { TLK_PASSENGER_NEW_RADAR_CONTACT, SPEECH_IMPORTANT, -1, -1, -1, -1, -1, -1, AICF_DEFAULT, }, + +#ifdef MAPBASE + { TLK_TAKING_FIRE, SPEECH_IMPORTANT,-1, -1, -1, -1, -1, -1, AICF_DEFAULT, }, + { TLK_NEW_ENEMY, SPEECH_IMPORTANT,-1, -1, -1, -1, -1, -1, AICF_DEFAULT, }, + { TLK_COMBAT_IDLE, SPEECH_IMPORTANT,-1, -1, -1, -1, -1, -1, AICF_DEFAULT, }, + { TLK_LOSTENEMY, SPEECH_IMPORTANT,-1, -1, -1, -1, -1, -1, AICF_DEFAULT, }, + { TLK_REFINDENEMY, SPEECH_IMPORTANT,-1, -1, -1, -1, -1, -1, AICF_DEFAULT, }, +#endif }; //----------------------------------------------------------------------------- @@ -134,12 +146,23 @@ bool ConceptStringLessFunc( const string_t &lhs, const string_t &rhs ) return CaselessStringLessThan( STRING(lhs), STRING(rhs) ); } +#ifdef NEW_RESPONSE_SYSTEM +bool ConceptInfoStringLessFunc( const AIConcept_t& lhs, const AIConcept_t& rhs ) +{ + return CaselessStringLessThan( lhs.GetStringConcept(), rhs.GetStringConcept() ); +} +#endif + //----------------------------------------------------------------------------- class CConceptInfoMap : public CUtlMap { public: CConceptInfoMap() : +#ifdef NEW_RESPONSE_SYSTEM + CUtlMap( ConceptInfoStringLessFunc ) +#else CUtlMap( CaselessStringLessThan ) +#endif { for ( int i = 0; i < ARRAYSIZE(g_ConceptInfos); i++ ) { @@ -338,6 +361,9 @@ BEGIN_DATADESC( CAI_PlayerAlly ) DEFINE_INPUTFUNC( FIELD_STRING, "SpeakResponseConcept", InputSpeakResponseConcept ), DEFINE_INPUTFUNC( FIELD_VOID, "MakeGameEndAlly", InputMakeGameEndAlly ), DEFINE_INPUTFUNC( FIELD_VOID, "MakeRegularAlly", InputMakeRegularAlly ), +#ifdef MAPBASE + DEFINE_INPUTFUNC( FIELD_STRING, "AskQuestion", InputAskQuestion ), +#endif DEFINE_INPUTFUNC( FIELD_INTEGER, "AnswerQuestion", InputAnswerQuestion ), DEFINE_INPUTFUNC( FIELD_INTEGER, "AnswerQuestionHello", InputAnswerQuestionHello ), DEFINE_INPUTFUNC( FIELD_VOID, "EnableSpeakWhileScripting", InputEnableSpeakWhileScripting ), @@ -552,7 +578,11 @@ void CAI_PlayerAlly::PrescheduleThink( void ) if ( SelectNonCombatSpeech( &selection ) ) { SetSpeechTarget( selection.hSpeechTarget ); +#ifdef NEW_RESPONSE_SYSTEM + SpeakDispatchResponse( selection.concept.c_str(), &selection.Response ); +#else SpeakDispatchResponse( selection.concept.c_str(), selection.Response ); +#endif m_flNextIdleSpeechTime = gpGlobals->curtime + RandomFloat( 20,30 ); } else @@ -601,6 +631,7 @@ bool CAI_PlayerAlly::SelectSpeechResponse( AIConcept_t concept, const char *pszM pSelection->hSpeechTarget = pTarget; return true; } + } return false; @@ -611,6 +642,8 @@ bool CAI_PlayerAlly::SelectSpeechResponse( AIConcept_t concept, const char *pszM void CAI_PlayerAlly::SetPendingSpeech( AIConcept_t concept, AI_Response &Response ) { m_PendingResponse = Response; +#ifndef NEW_RESPONSE_SYSTEM +#endif m_PendingConcept = concept; m_TimePendingSet = gpGlobals->curtime; } @@ -692,7 +725,11 @@ bool CAI_PlayerAlly::SelectInterjection() if ( SelectIdleSpeech( &selection ) ) { SetSpeechTarget( selection.hSpeechTarget ); +#ifdef NEW_RESPONSE_SYSTEM + SpeakDispatchResponse( selection.concept.c_str(), &selection.Response ); +#else SpeakDispatchResponse( selection.concept.c_str(), selection.Response ); +#endif return true; } } @@ -727,8 +764,13 @@ bool CAI_PlayerAlly::SelectQuestionAndAnswerSpeech( AISpeechSelection_t *pSelect return false; // if there is a friend nearby to speak to, play sentence, set friend's response time, return +#ifdef MAPBASE + CAI_PlayerAlly *pFriend = dynamic_cast(FindSpeechTarget( AIST_NPCS | AIST_NOT_GAGGED )); + if ( pFriend && !pFriend->IsMoving() ) +#else CAI_PlayerAlly *pFriend = dynamic_cast(FindSpeechTarget( AIST_NPCS )); if ( pFriend && !pFriend->IsMoving() && !pFriend->HasSpawnFlags(SF_NPC_GAG) ) +#endif return SelectQuestionFriend( pFriend, pSelection ); return false; @@ -752,11 +794,11 @@ void CAI_PlayerAlly::PostSpeakDispatchResponse( AIConcept_t concept, AI_Response { if ( bSaidHelloToNPC ) { - Warning("Q&A: '%s' said Hello to '%s' (concept %s)\n", GetDebugName(), GetSpeechTarget()->GetDebugName(), concept ); + Warning("Q&A: '%s' said Hello to '%s' (concept %s)\n", GetDebugName(), GetSpeechTarget()->GetDebugName(), (const char*)concept ); } else { - Warning("Q&A: '%s' questioned '%s' (concept %s)\n", GetDebugName(), GetSpeechTarget()->GetDebugName(), concept ); + Warning("Q&A: '%s' questioned '%s' (concept %s)\n", GetDebugName(), GetSpeechTarget()->GetDebugName(), (const char*)concept ); } NDebugOverlay::HorzArrow( GetAbsOrigin(), GetSpeechTarget()->GetAbsOrigin(), 8, 0, 255, 0, 64, true, duration ); } @@ -818,7 +860,17 @@ bool CAI_PlayerAlly::SelectQuestionFriend( CBaseEntity *pFriend, AISpeechSelecti // If we haven't said hello, say hello first. // Only ever say hello to NPCs other than my type. +#ifdef MAPBASE + // Why only say hello to NPCs other than my type? + // Are citizens a hivemind? Do they not greet each other? + // They don't have any responses for it anyway, so SelectSpeechResponse() will fail + // and TLK_HELLO_NPC will be marked as spoken. + // + // Responses could be added so modders/mappers can take advantage of this. + if ( !GetExpresser()->SpokeConcept( TLK_HELLO_NPC ) ) +#else if ( !GetExpresser()->SpokeConcept( TLK_HELLO_NPC ) && !FClassnameIs( this, pFriend->GetClassname()) ) +#endif { if ( SelectSpeechResponse( TLK_HELLO_NPC, NULL, pFriend, pSelection ) ) return true; @@ -846,6 +898,87 @@ bool CAI_PlayerAlly::SelectAnswerFriend( CBaseEntity *pFriend, AISpeechSelection return SelectSpeechResponse( TLK_ANSWER, NULL, pFriend, pSelection ); } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: Asks a question now. +//----------------------------------------------------------------------------- +bool CAI_PlayerAlly::AskQuestionNow( CBaseEntity *pSpeechTarget, int iQARandomNumber, const char *concept ) +{ + m_hPotentialSpeechTarget = pSpeechTarget; + m_iQARandomNumber = iQARandomNumber; + + if (!m_hPotentialSpeechTarget) + m_hPotentialSpeechTarget = /*dynamic_cast*/(FindSpeechTarget( AIST_NPCS | AIST_NOT_GAGGED )); + + if (m_iQARandomNumber == -1) + m_iQARandomNumber = RandomInt(0, 100); + + AISpeechSelection_t selection; +#ifdef NEW_RESPONSE_SYSTEM + if (SelectSpeechResponse( concept, NULL, m_hPotentialSpeechTarget.Get(), &selection )) + { + SetSpeechTarget( selection.hSpeechTarget ); + ClearPendingSpeech(); + + // Speak immediately + return SpeakDispatchResponse( selection.concept.c_str(), &selection.Response ); + } + + return false; +#else + SelectSpeechResponse( concept, NULL, m_hPotentialSpeechTarget.Get(), &selection ); + + SetSpeechTarget( selection.hSpeechTarget ); + ClearPendingSpeech(); + + if (!selection.pResponse) + return false; + + // Speak immediately + return SpeakDispatchResponse( selection.concept.c_str(), selection.pResponse ); +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CAI_PlayerAlly::InputAskQuestion( inputdata_t &inputdata ) +{ + CBaseEntity *pSpeechTarget = NULL; + int iQARandomNumber = 0; + const char *concept = TLK_QUESTION; + + // I didn't feel like using strtok today. + CUtlStringList vecStrings; + V_SplitString(inputdata.value.String(), " ", vecStrings); + FOR_EACH_VEC( vecStrings, i ) + { + // 0 : QA Number (-1 for N/A) + // 1 : Speech Target + // 2 : Concept + switch (i) + { + case 0: iQARandomNumber = atoi(vecStrings[i]); break; + case 1: pSpeechTarget = gEntList.FindEntityByName(NULL, vecStrings[i], this, inputdata.pActivator, inputdata.pCaller); break; + case 2: concept = vecStrings[i]; break; + } + } + + if (pSpeechTarget == NULL) + { + CAI_PlayerAlly *pFriend = dynamic_cast(FindSpeechTarget( AIST_NPCS | AIST_NOT_GAGGED )); + if ( pFriend ) + pSpeechTarget = pFriend; + } + else if (pSpeechTarget == this) + { + pSpeechTarget = NULL; + } + + AskQuestionNow(pSpeechTarget, iQARandomNumber, concept); +} +#endif + //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- @@ -892,7 +1025,11 @@ void CAI_PlayerAlly::AnswerQuestion( CAI_PlayerAlly *pQuestioner, int iQARandomN } SetSpeechTarget( selection.hSpeechTarget ); +#ifdef NEW_RESPONSE_SYSTEM + SpeakDispatchResponse( selection.concept.c_str(), &selection.Response ); +#else SpeakDispatchResponse( selection.concept.c_str(), selection.Response ); +#endif // Prevent idle speech for a while DeferAllIdleSpeech( random->RandomFloat( TALKER_DEFER_IDLE_SPEAK_MIN, TALKER_DEFER_IDLE_SPEAK_MAX ), GetSpeechTarget()->MyNPCPointer() ); @@ -944,7 +1081,12 @@ int CAI_PlayerAlly::SelectNonCombatSpeechSchedule() if ( SelectNonCombatSpeech( &selection ) ) { SetSpeechTarget( selection.hSpeechTarget ); + +#ifdef NEW_RESPONSE_SYSTEM SetPendingSpeech( selection.concept.c_str(), selection.Response ); +#else + SetPendingSpeech( selection.concept.c_str(), &selection.Response ); +#endif } } @@ -1019,7 +1161,11 @@ void CAI_PlayerAlly::StartTask( const Task_t *pTask ) case TASK_TALKER_SPEAK_PENDING: if ( !m_PendingConcept.empty() ) { +#ifdef NEW_RESPONSE_SYSTEM + SpeakDispatchResponse( m_PendingConcept.c_str(), &m_PendingResponse ); +#else SpeakDispatchResponse( m_PendingConcept.c_str(), m_PendingResponse ); +#endif m_PendingConcept.erase(); TaskComplete(); } @@ -1078,6 +1224,24 @@ void CAI_PlayerAlly::Touch( CBaseEntity *pOther ) } } +#ifdef MAPBASE +ConVar mapbase_ally_flinching("mapbase_ally_flinching", "1", FCVAR_ARCHIVE, "Enables/disables the new flinching animations."); +//----------------------------------------------------------------------------- +// Purpose: This is to adjust for the new citizen flinching animations, +// as they would exist on all NPCs that use citizen animations. +// +// Vortigaunts and Alyx in the Episodes are the only ones who can flinch normally, +// and that's been rectified with their own functions. (they currently skip CAI_PlayerAlly's implementation) +//----------------------------------------------------------------------------- +bool CAI_PlayerAlly::CanFlinch( void ) +{ + if (mapbase_ally_flinching.GetBool() != true) + return false; + + return BaseClass::CanFlinch(); +} +#endif + //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- void CAI_PlayerAlly::OnKilledNPC( CBaseCombatCharacter *pKilled ) @@ -1088,11 +1252,47 @@ void CAI_PlayerAlly::OnKilledNPC( CBaseCombatCharacter *pKilled ) ( pKilled->MyNPCPointer()->GetLastPlayerDamageTime() == 0 || gpGlobals->curtime - pKilled->MyNPCPointer()->GetLastPlayerDamageTime() > 5 ) ) { +#ifdef MAPBASE + m_hPotentialSpeechTarget = pKilled; + SetSpeechTarget(pKilled); +#endif SpeakIfAllowed( TLK_ENEMY_DEAD ); } } } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CAI_PlayerAlly::OnEnemyRangeAttackedMe( CBaseEntity *pEnemy, const Vector &vecDir, const Vector &vecEnd ) +{ + BaseClass::OnEnemyRangeAttackedMe( pEnemy, vecDir, vecEnd ); + + if ( IRelationType( pEnemy ) <= D_FR ) + { + AI_CriteriaSet modifiers; + ModifyOrAppendEnemyCriteria( modifiers, pEnemy ); + + Vector vecEntDir = (pEnemy->EyePosition() - EyePosition()); + float flDot = DotProduct( vecEntDir.Normalized(), vecDir ); + modifiers.AppendCriteria( "shot_dot", CNumStr( flDot ) ); + + if (GetLastDamageTime() == gpGlobals->curtime) + modifiers.AppendCriteria( "missed", "0" ); + else + modifiers.AppendCriteria( "missed", "1" ); + + // Check if they're out of ammo + if ( pEnemy->IsCombatCharacter() && pEnemy->MyCombatCharacterPointer()->GetActiveWeapon() && pEnemy->MyCombatCharacterPointer()->GetActiveWeapon()->Clip1() <= 0 ) + modifiers.AppendCriteria( "last_attack", "1" ); + else + modifiers.AppendCriteria( "last_attack", "0" ); + + SpeakIfAllowed( TLK_TAKING_FIRE, modifiers ); + } +} +#endif + //----------------------------------------------------------------------------- void CAI_PlayerAlly::TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator ) { @@ -1175,8 +1375,14 @@ void CAI_PlayerAlly::Event_Killed( const CTakeDamageInfo &info ) CBasePlayer *player = AI_GetSinglePlayer(); if ( player ) { +#ifdef MAPBASE + variant_t variant; + variant.SetEntity(this); + player->AcceptInput( "OnSquadMemberKilled", info.GetAttacker(), this, variant, 0 ); +#else variant_t emptyVariant; player->AcceptInput( "OnSquadMemberKilled", this, this, emptyVariant, 0 ); +#endif } } @@ -1186,6 +1392,10 @@ void CAI_PlayerAlly::Event_Killed( const CTakeDamageInfo &info ) CAI_PlayerAlly *pMourner = dynamic_cast(FindSpeechTarget( AIST_NPCS )); if ( pMourner ) { +#ifdef MAPBASE + pMourner->m_hPotentialSpeechTarget = this; + pMourner->SetSpeechTarget(this); +#endif pMourner->SpeakIfAllowed( TLK_ALLY_KILLED ); } @@ -1212,6 +1422,28 @@ void CAI_PlayerAlly::PainSound( const CTakeDamageInfo &info ) SpeakIfAllowed( TLK_WOUND ); } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +void CAI_PlayerAlly::LostEnemySound( CBaseEntity *pEnemy ) +{ + AI_CriteriaSet modifiers; + ModifyOrAppendEnemyCriteria( modifiers, pEnemy ); + + modifiers.AppendCriteria( "lastseenenemy", gpGlobals->curtime - GetEnemies()->LastTimeSeen( pEnemy ) ); + + SpeakIfAllowed( TLK_LOSTENEMY, modifiers ); +} + +//----------------------------------------------------------------------------- +void CAI_PlayerAlly::FoundEnemySound( CBaseEntity *pEnemy ) +{ + AI_CriteriaSet modifiers; + ModifyOrAppendEnemyCriteria( modifiers, pEnemy ); + + SpeakIfAllowed( TLK_REFINDENEMY, modifiers ); +} +#endif + //----------------------------------------------------------------------------- // Purpose: Implemented to look at talk target //----------------------------------------------------------------------------- @@ -1287,6 +1519,11 @@ bool CAI_PlayerAlly::IsValidSpeechTarget( int flags, CBaseEntity *pEntity ) // Don't bother people who don't want to be bothered if ( !pNPC->CanBeUsedAsAFriend() ) return false; + +#ifdef MAPBASE + if (flags & AIST_NOT_GAGGED && pNPC->HasSpawnFlags(SF_NPC_GAG)) + return false; +#endif } if ( flags & AIST_FACING_TARGET ) @@ -1581,6 +1818,54 @@ bool CAI_PlayerAlly::IsAllowedToSpeak( AIConcept_t concept, bool bRespondingToPl return true; } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: Specifically for player allies handling followup responses. +// Better-accounts for unknown concepts so that users are free in what they use. +//----------------------------------------------------------------------------- +bool CAI_PlayerAlly::IsAllowedToSpeakFollowup( AIConcept_t concept, CBaseEntity *pIssuer, bool bSpecific ) +{ + CAI_AllySpeechManager * pSpeechManager = GetAllySpeechManager(); + ConceptInfo_t * pInfo = pSpeechManager->GetConceptInfo( concept ); + ConceptCategory_t category = SPEECH_PRIORITY; // Must be SPEECH_PRIORITY to get around semaphore + + if ( !IsOkToSpeak( category, true ) ) + return false; + + // If this followup is specifically targeted towards us, speak if we're not already speaking + // If it's meant to be spoken by anyone, respect speech delay and semaphore + if ( bSpecific ) + { + if ( !GetExpresser()->CanSpeakAfterMyself() ) + return false; + } + else + { + if ( !GetExpresser()->CanSpeak() ) + return false; + + CAI_TimedSemaphore *pSemaphore = GetExpresser()->GetMySpeechSemaphore( this ); + if ( pSemaphore && !pSemaphore->IsAvailable( this ) ) + { + // Only if the semaphore holder isn't the one dispatching the followup + if ( pSemaphore->GetOwner() != pIssuer ) + return false; + } + } + + if ( !pSpeechManager->ConceptDelayExpired( concept ) ) + return false; + + if ( ( pInfo && pInfo->flags & AICF_SPEAK_ONCE ) && GetExpresser()->SpokeConcept( concept ) ) + return false; + + if ( !GetExpresser()->CanSpeakConcept( concept ) ) + return false; + + return true; +} +#endif + //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- bool CAI_PlayerAlly::SpeakIfAllowed( AIConcept_t concept, const char *modifiers, bool bRespondingToPlayer, char *pszOutResponseChosen, size_t bufsize ) @@ -1592,17 +1877,41 @@ bool CAI_PlayerAlly::SpeakIfAllowed( AIConcept_t concept, const char *modifiers, return false; } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +bool CAI_PlayerAlly::SpeakIfAllowed( AIConcept_t concept, AI_CriteriaSet& modifiers, bool bRespondingToPlayer, char *pszOutResponseChosen, size_t bufsize ) +{ + if ( IsAllowedToSpeak( concept, bRespondingToPlayer ) ) + { + return Speak( concept, modifiers, pszOutResponseChosen, bufsize ); + } + return false; +} +#endif + //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- void CAI_PlayerAlly::ModifyOrAppendCriteria( AI_CriteriaSet& set ) { BaseClass::ModifyOrAppendCriteria( set ); +#ifdef MAPBASE + // For the below speechtarget criteria + if (GetSpeechTarget() && !m_hPotentialSpeechTarget) + m_hPotentialSpeechTarget = GetSpeechTarget(); +#endif + if ( m_hPotentialSpeechTarget ) { set.AppendCriteria( "speechtarget", m_hPotentialSpeechTarget->GetClassname() ); set.AppendCriteria( "speechtargetname", STRING(m_hPotentialSpeechTarget->GetEntityName()) ); set.AppendCriteria( "randomnum", UTIL_VarArgs("%d", m_iQARandomNumber) ); + +#ifdef MAPBASE + // Speech target contexts. + m_hPotentialSpeechTarget->AppendContextToCriteria(set, "speechtarget_"); +#endif } // Do we have a speech filter? If so, append it's criteria too @@ -1619,11 +1928,13 @@ void CAI_PlayerAlly::OnSpokeConcept( AIConcept_t concept, AI_Response *response CAI_AllySpeechManager *pSpeechManager = GetAllySpeechManager(); pSpeechManager->OnSpokeConcept( this, concept, response ); +#ifndef MAPBASE // This has been moved directly to CAI_Expresser if( response != NULL && (response->GetParams()->flags & AI_ResponseParams::RG_WEAPONDELAY) ) { // Stop shooting, as instructed, so that my speech can be heard. GetShotRegulator()->FireNoEarlierThan( gpGlobals->curtime + response->GetWeaponDelay() ); } +#endif } //----------------------------------------------------------------------------- @@ -1693,6 +2004,18 @@ bool CAI_PlayerAlly::RespondedTo( const char *ResponseConcept, bool bForce, bool { // We're being forced to respond to the event, probably because it's the // player dying or something equally important. +#ifdef NEW_RESPONSE_SYSTEM + AI_Response response; + bool result = SpeakFindResponse( response, ResponseConcept, NULL ); + if ( result ) + { + // We've got something to say. Stop any scenes we're in, and speak the response. + if ( bCancelScene ) + RemoveActorFromScriptedScenes( this, false ); + + return SpeakDispatchResponse( ResponseConcept, &response ); + } +#else AI_Response response; bool result = SpeakFindResponse( response, ResponseConcept, NULL ); if ( result ) @@ -1703,6 +2026,7 @@ bool CAI_PlayerAlly::RespondedTo( const char *ResponseConcept, bool bForce, bool return SpeakDispatchResponse( ResponseConcept, response ); } +#endif return false; } diff --git a/src/game/server/ai_playerally.h b/src/game/server/ai_playerally.h index 9851f2b2..a15cfc6f 100644 --- a/src/game/server/ai_playerally.h +++ b/src/game/server/ai_playerally.h @@ -131,6 +131,15 @@ #define TLK_TGCATCHUP "TLK_TGCATCHUP" #define TLK_TGENDTOUR "TLK_TGENDTOUR" +#ifdef MAPBASE +// Additional concepts for companions in mods +#define TLK_TAKING_FIRE "TLK_TAKING_FIRE" // Someone fired at me (regardless of whether I was hit) +#define TLK_NEW_ENEMY "TLK_NEW_ENEMY" // A new enemy appeared while combat was already in progress +#define TLK_COMBAT_IDLE "TLK_COMBAT_IDLE" // Similar to TLK_ATTACKING, but specifically for when *not* currently attacking (e.g. when in cover or reloading) +#define TLK_LOSTENEMY "TLK_LOSTENEMY" // Current enemy has eluded squad +#define TLK_REFINDENEMY "TLK_REFINDENEMY" // Found a previously eluded enemy +#endif + //----------------------------------------------------------------------------- #define TALKRANGE_MIN 500.0 // don't talk to anyone farther away than this @@ -243,6 +252,10 @@ enum AISpeechTargetSearchFlags_t AIST_IGNORE_RELATIONSHIP = (1<<2), AIST_ANY_QUALIFIED = (1<<3), AIST_FACING_TARGET = (1<<4), +#ifdef MAPBASE + // I needed this for something + AIST_NOT_GAGGED = (1<<5), +#endif }; struct AISpeechSelection_t @@ -283,11 +296,19 @@ public: void ClearTransientConditions(); void Touch( CBaseEntity *pOther ); +#ifdef MAPBASE + virtual bool CanFlinch( void ); +#endif + //--------------------------------- // Combat //--------------------------------- void OnKilledNPC( CBaseCombatCharacter *pKilled ); +#ifdef MAPBASE + void OnEnemyRangeAttackedMe( CBaseEntity *pEnemy, const Vector &vecDir, const Vector &vecEnd ); +#endif + //--------------------------------- // Damage handling //--------------------------------- @@ -301,6 +322,11 @@ public: virtual void PainSound( const CTakeDamageInfo &info ); +#ifdef MAPBASE + virtual void LostEnemySound( CBaseEntity *pEnemy ); + virtual void FoundEnemySound( CBaseEntity *pEnemy ); +#endif + //--------------------------------- // Speech & Acting //--------------------------------- @@ -312,6 +338,12 @@ public: CBaseEntity *GetSpeechTarget() { return m_hTalkTarget.Get(); } void SetSpeechTarget( CBaseEntity *pSpeechTarget ) { m_hTalkTarget = pSpeechTarget; } + +#ifdef MAPBASE + // Needed for additional speech target responses + CBaseEntity *GetPotentialSpeechTarget() { return m_hPotentialSpeechTarget.Get(); } + void SetPotentialSpeechTarget( CBaseEntity *pSpeechTarget ) { m_hPotentialSpeechTarget = pSpeechTarget; } +#endif void SetSpeechFilter( CAI_SpeechFilter *pFilter ) { m_hSpeechFilter = pFilter; } CAI_SpeechFilter *GetSpeechFilter( void ) { return m_hSpeechFilter; } @@ -359,7 +391,13 @@ public: bool ShouldSpeakRandom( AIConcept_t concept, int iChance ); bool IsAllowedToSpeak( AIConcept_t concept, bool bRespondingToPlayer = false ); +#ifdef MAPBASE + bool IsAllowedToSpeakFollowup( AIConcept_t concept, CBaseEntity *pIssuer, bool bSpecific ); +#endif virtual bool SpeakIfAllowed( AIConcept_t concept, const char *modifiers = NULL, bool bRespondingToPlayer = false, char *pszOutResponseChosen = NULL, size_t bufsize = 0 ); +#ifdef MAPBASE + virtual bool SpeakIfAllowed( AIConcept_t concept, AI_CriteriaSet& modifiers, bool bRespondingToPlayer = false, char *pszOutResponseChosen = NULL, size_t bufsize = 0 ); +#endif void ModifyOrAppendCriteria( AI_CriteriaSet& set ); //--------------------------------- @@ -385,6 +423,10 @@ public: virtual const char *GetDeathMessageText( void ) { return "GAMEOVER_ALLY"; } void InputMakeGameEndAlly( inputdata_t &inputdata ); void InputMakeRegularAlly( inputdata_t &inputdata ); +#ifdef MAPBASE + bool AskQuestionNow( CBaseEntity *pSpeechTarget = NULL, int iQARandomNumber = -1, const char *concept = TLK_QUESTION ); + void InputAskQuestion( inputdata_t &inputdata ); +#endif void InputAnswerQuestion( inputdata_t &inputdata ); void InputAnswerQuestionHello( inputdata_t &inputdata ); void InputEnableSpeakWhileScripting( inputdata_t &inputdata ); diff --git a/src/game/server/ai_relationship.cpp b/src/game/server/ai_relationship.cpp index eec28d99..e5f12d62 100644 --- a/src/game/server/ai_relationship.cpp +++ b/src/game/server/ai_relationship.cpp @@ -7,6 +7,9 @@ #include "cbase.h" #include "ndebugoverlay.h" #include "ai_basenpc.h" +#ifdef MAPBASE +#include "mapbase/matchers.h" +#endif // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" @@ -34,6 +37,9 @@ public: void Activate(); void SetActive( bool bActive ); +#ifdef MAPBASE + virtual +#endif void ChangeRelationships( int disposition, int iReverting, CBaseEntity *pActivator = NULL, CBaseEntity *pCaller = NULL ); void ApplyRelationship( CBaseEntity *pActivator = NULL, CBaseEntity *pCaller = NULL ); @@ -45,11 +51,20 @@ public: bool IsASubject( CBaseEntity *pEntity ); bool IsATarget( CBaseEntity *pEntity ); +#ifdef MAPBASE + // Assume no insane person already has "CLASS_" at the beginning of an entity's (class)name. + inline bool IsSubjectClassify() { return Q_strncmp(STRING(m_target), "CLASS_", 6) == 0; } + inline bool IsTargetClassify() { return Q_strncmp(STRING(m_target), "CLASS_", 6) == 0; } +#endif void OnEntitySpawned( CBaseEntity *pEntity ); void OnEntityDeleted( CBaseEntity *pEntity ); +#ifdef MAPBASE +protected: +#else private: +#endif void ApplyRelationshipThink( void ); CBaseEntity *FindEntityForProceduralName( string_t iszName, CBaseEntity *pActivator, CBaseEntity *pCaller ); @@ -259,6 +274,12 @@ bool CAI_Relationship::IsASubject( CBaseEntity *pEntity ) if( pEntity->ClassMatches( m_iszSubject ) ) return true; +#ifdef MAPBASE + // Hopefully doesn't impact performance. + if (Matcher_NamesMatch(STRING(m_iszSubject), g_pGameRules->AIClassText((pEntity->Classify())))) + return true; +#endif + return false; } @@ -272,6 +293,12 @@ bool CAI_Relationship::IsATarget( CBaseEntity *pEntity ) if( pEntity->ClassMatches( m_target ) ) return true; +#ifdef MAPBASE + // Hopefully doesn't impact performance. + if (Matcher_NamesMatch(STRING(m_target), g_pGameRules->AIClassText((pEntity->Classify())))) + return true; +#endif + return false; } @@ -494,3 +521,150 @@ void CAI_Relationship::ChangeRelationships( int disposition, int iReverting, CBa } } +#ifdef MAPBASE +//========================================================= +//========================================================= +class CAI_ClassRelationship : public CAI_Relationship +{ + DECLARE_CLASS( CAI_ClassRelationship, CAI_Relationship ); + +public: + + // Must override CAI_Relationship + void Spawn() { m_bIsActive = false; } + void Activate(); + + bool KeyValue( const char *szKeyName, const char *szValue ); + + void ChangeRelationships( int disposition, int iReverting, CBaseEntity *pActivator = NULL, CBaseEntity *pCaller = NULL ); + + inline bool SubjectUsingClassify() { return m_iSubjectClass != NUM_AI_CLASSES; } + inline bool TargetUsingClassify() { return m_iTargetClass != NUM_AI_CLASSES; } + +protected: + + Class_T m_iSubjectClass; + Class_T m_iTargetClass; + + DECLARE_DATADESC(); +}; + +LINK_ENTITY_TO_CLASS( ai_relationship_classify, CAI_ClassRelationship ); + +BEGIN_DATADESC( CAI_ClassRelationship ) + + DEFINE_FIELD( m_iSubjectClass, FIELD_INTEGER ), + DEFINE_FIELD( m_iTargetClass, FIELD_INTEGER ), + +END_DATADESC() + +//--------------------------------------------------------- +//--------------------------------------------------------- +void CAI_ClassRelationship::Activate() +{ + BaseClass::Activate(); + + // Must re-apply every time a save is loaded + if ( m_bIsActive ) + { + ApplyRelationship(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Caches entity key values until spawn is called. +// Input : szKeyName - +// szValue - +// Output : +//----------------------------------------------------------------------------- +bool CAI_ClassRelationship::KeyValue( const char *szKeyName, const char *szValue ) +{ + // Override regular subject and target from ai_relationship + if (FStrEq(szKeyName, "subject")) + { + m_iSubjectClass = (Class_T)atoi(szValue); + + // Direct string maybe + if (m_iSubjectClass == CLASS_NONE) + { + for (int i = 0; i < NUM_AI_CLASSES; i++) + { + if (FStrEq(szValue, g_pGameRules->AIClassText(i))) + { + m_iSubjectClass = (Class_T)i; + } + } + } + } + else if (FStrEq(szKeyName, "target")) + { + m_iTargetClass = (Class_T)atoi(szValue); + + // Direct string maybe + if (m_iTargetClass == CLASS_NONE) + { + for (int i = 0; i < NUM_AI_CLASSES; i++) + { + if (FStrEq(szValue, g_pGameRules->AIClassText(i))) + { + m_iTargetClass = (Class_T)i; + } + } + } + } + else + return BaseClass::KeyValue(szKeyName, szValue); + + return true; +} + +//--------------------------------------------------------- +//--------------------------------------------------------- +void CAI_ClassRelationship::ChangeRelationships( int disposition, int iReverting, CBaseEntity *pActivator, CBaseEntity *pCaller ) +{ + if( iReverting != NOT_REVERTING && m_iPreviousDisposition == -1 ) + { + // Trying to revert without having ever set the relationships! + DevMsg( 2, "ai_relationship cannot revert changes before they are applied!\n"); + return; + } + + if ( !CBaseCombatCharacter::DefaultRelationshipsLoaded() ) + return; + + if ( m_iPreviousDisposition == -1 && iReverting == NOT_REVERTING ) + { + // Set previous disposition. + m_iPreviousDisposition = CBaseCombatCharacter::GetDefaultRelationshipDisposition( m_iSubjectClass, m_iTargetClass ); + m_iPreviousRank = CBaseCombatCharacter::GetDefaultRelationshipPriority( m_iSubjectClass, m_iTargetClass ); + } + + // We can't actually reset to "default" without resetting all class relationships period, so we just use the previous disposition. + // There probably wouldn't be much overlap with this entity anyway. + if ( iReverting == REVERTING_TO_PREV || iReverting == REVERTING_TO_DEFAULT ) + { + CBaseCombatCharacter::SetDefaultRelationship(m_iSubjectClass, m_iTargetClass, (Disposition_t)m_iPreviousDisposition, m_iPreviousRank ); + + if( m_bReciprocal ) + { + CBaseCombatCharacter::SetDefaultRelationship(m_iTargetClass, m_iSubjectClass, (Disposition_t)m_iPreviousDisposition, m_iPreviousRank ); + } + } + else if( CBaseCombatCharacter::GetDefaultRelationshipDisposition( m_iSubjectClass, m_iTargetClass ) != disposition || + CBaseCombatCharacter::GetDefaultRelationshipPriority( m_iSubjectClass, m_iTargetClass ) != m_iRank ) + { + // Apply the relationship to the subject + CBaseCombatCharacter::SetDefaultRelationship(m_iSubjectClass, m_iTargetClass, (Disposition_t)disposition, m_iRank ); + + // Notification flags can't be used here. Sorry. + + // This relationship is applied to target and subject alike + if ( m_bReciprocal ) + { + // Apply the relationship to the target + CBaseCombatCharacter::SetDefaultRelationship(m_iTargetClass, m_iSubjectClass, (Disposition_t)disposition, m_iRank ); + } + } +} +#endif + diff --git a/src/game/server/ai_scriptconditions.cpp b/src/game/server/ai_scriptconditions.cpp index a416eb49..7e637ba5 100644 --- a/src/game/server/ai_scriptconditions.cpp +++ b/src/game/server/ai_scriptconditions.cpp @@ -52,6 +52,9 @@ BEGIN_DATADESC( CAI_ScriptConditions ) DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ), DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ), +#ifdef MAPBASE + DEFINE_INPUTFUNC( FIELD_EHANDLE, "SatisfyConditions", InputSatisfyConditions ), +#endif //--------------------------------- @@ -195,12 +198,20 @@ bool CAI_ScriptConditions::EvalState( const EvalArgs_t &args ) -1, // NPC_STATE_PLAYDEAD -1, // NPC_STATE_PRONE -1, // NPC_STATE_DEAD +#ifdef MAPBASE + 3, // A "Don't care" for the maximum value +#endif }; int valState = stateVals[pNpc->m_NPCState]; if ( valState < 0 ) { +#ifdef MAPBASE + if (m_fMinState == 0) + return true; +#endif + if ( pNpc->m_NPCState == NPC_STATE_SCRIPT && m_fScriptStatus >= TRS_TRUE ) return true; @@ -676,6 +687,25 @@ void CAI_ScriptConditions::InputDisable( inputdata_t &inputdata ) Disable(); } +#ifdef MAPBASE +//----------------------------------------------------------------------------- + +void CAI_ScriptConditions::InputSatisfyConditions( inputdata_t &inputdata ) +{ + // This satisfies things. + CBaseEntity *pActivator = HasSpawnFlags(SF_ACTOR_AS_ACTIVATOR) ? inputdata.value.Entity() : this; + m_OnConditionsSatisfied.FireOutput(pActivator, this); + + + //All done! + if ( m_ElementList.Count() == 1 ) + { + Disable(); + m_ElementList.Purge(); + } +} +#endif + //----------------------------------------------------------------------------- bool CAI_ScriptConditions::IsInFOV( CBaseEntity *pViewer, CBaseEntity *pViewed, float fov, bool bTrueCone ) @@ -829,6 +859,11 @@ void CAI_ScriptConditions::OnEntitySpawned( CBaseEntity *pEntity ) if ( pEntity->MyNPCPointer() == NULL ) return; +#ifdef MAPBASE + if ( m_Actor == NULL_STRING ) + return; +#endif + if ( pEntity->NameMatches( m_Actor ) ) { if ( ActorInList( pEntity ) == false ) diff --git a/src/game/server/ai_scriptconditions.h b/src/game/server/ai_scriptconditions.h index c1860c83..10e5929b 100644 --- a/src/game/server/ai_scriptconditions.h +++ b/src/game/server/ai_scriptconditions.h @@ -159,6 +159,9 @@ private: // Input handlers void InputEnable( inputdata_t &inputdata ); void InputDisable( inputdata_t &inputdata ); +#ifdef MAPBASE + void InputSatisfyConditions( inputdata_t &inputdata ); +#endif // Output handlers COutputEvent m_OnConditionsSatisfied; diff --git a/src/game/server/ai_speech.cpp b/src/game/server/ai_speech.cpp index da85fdd9..bceef11f 100644 --- a/src/game/server/ai_speech.cpp +++ b/src/game/server/ai_speech.cpp @@ -16,6 +16,9 @@ #include "AI_Criteria.h" #include "isaverestore.h" #include "sceneentity.h" +#ifdef MAPBASE +#include "ai_squad.h" +#endif // memdbgon must be the last include file in a .cpp file!!! #include @@ -180,6 +183,25 @@ BEGIN_SIMPLE_DATADESC( CAI_Expresser ) DEFINE_FIELD( m_flLastTimeAcceptedSpeak, FIELD_TIME ), END_DATADESC() +#ifdef MAPBASE_VSCRIPT +BEGIN_SCRIPTDESC_ROOT( CAI_Expresser, "Expresser class for complex speech." ) + + DEFINE_SCRIPTFUNC( IsSpeaking, "Check if the actor is speaking." ) + DEFINE_SCRIPTFUNC( CanSpeak, "Check if the actor can speak." ) + DEFINE_SCRIPTFUNC( BlockSpeechUntil, "Block speech for a certain amount of time. This is stored in curtime." ) + DEFINE_SCRIPTFUNC( ForceNotSpeaking, "If the actor is speaking, force the system to recognize them as not speaking." ) + + DEFINE_SCRIPTFUNC( GetVoicePitch, "Get the actor's voice pitch. Used in sentences." ) + DEFINE_SCRIPTFUNC( SetVoicePitch, "Set the actor's voice pitch. Used in sentences." ) + + DEFINE_SCRIPTFUNC_NAMED( ScriptSpeakRawScene, "SpeakRawScene", "Speak a raw, instanced VCD scene as though it were played through the Response System. Return whether the scene successfully plays." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptSpeakAutoGeneratedScene, "SpeakAutoGeneratedScene", "Speak an automatically generated, instanced VCD scene for this sound as though it were played through the Response System. Return whether the scene successfully plays." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptSpeakRawSentence, "SpeakRawSentence", "Speak a raw sentence as though it were played through the Response System. Return the sentence's index; -1 if not successfully played." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptSpeak, "Speak", "Speak a response concept with the specified modifiers." ) + +END_SCRIPTDESC(); +#endif + //------------------------------------- bool CAI_Expresser::SemaphoreIsAvailable( CBaseEntity *pTalker ) @@ -274,6 +296,17 @@ static const int LEN_SPECIFIC_SCENE_MODIFIER = strlen( AI_SPECIFIC_SCENE_MODIFIE //----------------------------------------------------------------------------- bool CAI_Expresser::SpeakFindResponse( AI_Response &outResponse, AIConcept_t concept, const char *modifiers /*= NULL*/ ) { +#ifdef MAPBASE + AI_CriteriaSet set; + + if (modifiers) + { + MergeModifiers(set, modifiers); + } + + // Now return the code in the new function. + return SpeakFindResponse(concept, set); +#else IResponseSystem *rs = GetOuter()->GetResponseSystem(); if ( !rs ) { @@ -318,6 +351,10 @@ bool CAI_Expresser::SpeakFindResponse( AI_Response &outResponse, AIConcept_t con pPlayer->ModifyOrAppendPlayerCriteria( set ); } +#ifdef MAPBASE + GetOuter()->ReAppendContextCriteria( set ); +#endif + // Now that we have a criteria set, ask for a suitable response bool found = rs->FindBestResponse( set, outResponse, this ); @@ -351,14 +388,184 @@ bool CAI_Expresser::SpeakFindResponse( AI_Response &outResponse, AIConcept_t con return false; return true; +#endif } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: Merges modifiers with set. +//----------------------------------------------------------------------------- +void CAI_Expresser::MergeModifiers( AI_CriteriaSet& set, const char *modifiers ) +{ + char copy_modifiers[ 255 ]; + const char *pCopy; + char key[ 128 ] = { 0 }; + char value[ 128 ] = { 0 }; + + Q_strncpy( copy_modifiers, modifiers, sizeof( copy_modifiers ) ); + pCopy = copy_modifiers; + + while( pCopy ) + { + pCopy = SplitContext( pCopy, key, sizeof( key ), value, sizeof( value ), NULL ); + + if( *key && *value ) + { + set.AppendCriteria( key, value, CONCEPT_WEIGHT ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Searches for a possible response, takes an AI_CriteriaSet instead. +// Input : concept - +// NULL - +// Output : AI_Response +//----------------------------------------------------------------------------- +AI_Response *CAI_Expresser::SpeakFindResponse( AIConcept_t concept, const AI_CriteriaSet &modifiers ) +{ + IResponseSystem *rs = GetOuter()->GetResponseSystem(); + if ( !rs ) + { + Assert( !"No response system installed for CAI_Expresser::GetOuter()!!!" ); + return NULL; + } + + AI_CriteriaSet set; + // Always include the concept name + set.AppendCriteria( "concept", concept, CONCEPT_WEIGHT ); + + // Tier 1: Criteria + // Let our outer fill in most match criteria + GetOuter()->ModifyOrAppendCriteria( set ); + + // Append local player criteria to set, but not if this is a player doing the talking + if ( !GetOuter()->IsPlayer() ) + { + CBasePlayer *pPlayer = UTIL_PlayerByIndex( 1 ); + if( pPlayer ) + pPlayer->ModifyOrAppendPlayerCriteria( set ); + } + + // Tier 2: Modifiers + set.MergeSet(modifiers); + + // Tier 3: Contexts + GetOuter()->ReAppendContextCriteria( set ); + + // Now that we have a criteria set, ask for a suitable response + AI_Response *result = new AI_Response; + Assert( result && "new AI_Response: Returned a NULL AI_Response!" ); + bool found = rs->FindBestResponse( set, *result, this ); + + if ( rr_debugresponses.GetInt() == 3 ) + { + if ( ( GetOuter()->MyNPCPointer() && GetOuter()->m_debugOverlays & OVERLAY_NPC_SELECTED_BIT ) || GetOuter()->IsPlayer() ) + { + const char *pszName; + if ( GetOuter()->IsPlayer() ) + { + pszName = ((CBasePlayer*)GetOuter())->GetPlayerName(); + } + else + { + pszName = GetOuter()->GetDebugName(); + } + + if ( found ) + { + char response[ 256 ]; + result->GetResponse( response, sizeof( response ) ); + + Warning( "RESPONSERULES: %s spoke '%s'. Found response '%s'.\n", pszName, concept, response ); + } + else + { + Warning( "RESPONSERULES: %s spoke '%s'. Found no matching response.\n", pszName, concept ); + } + } + } + + if ( !found ) + { + //Assert( !"rs->FindBestResponse: Returned a NULL AI_Response!" ); + delete result; + return NULL; + } + + char response[ 256 ]; + result->GetResponse( response, sizeof( response ) ); + + if ( !response[0] ) + { + delete result; + return NULL; + } + + if ( result->GetOdds() < 100 && random->RandomInt( 1, 100 ) <= result->GetOdds() ) + { + delete result; + return NULL; + } + + return result; +} +#endif + //----------------------------------------------------------------------------- // Purpose: Dispatches the result // Input : *response - //----------------------------------------------------------------------------- -bool CAI_Expresser::SpeakDispatchResponse( AIConcept_t concept, AI_Response& response, IRecipientFilter *filter /* = NULL */ ) +#ifdef MAPBASE +bool CAI_Expresser::SpeakDispatchResponse( AIConcept_t concept, AI_Response &result, IRecipientFilter *filter, const AI_CriteriaSet *modifiers ) +#else +bool CAI_Expresser::SpeakDispatchResponse( AIConcept_t concept, AI_Response &result, IRecipientFilter *filter /* = NULL */ ) +#endif { +#ifdef MAPBASE + if (response[0] == '$') + { + const char *context = response + 1; + const char *replace = GetOuter()->GetContextValue(context); + + // If we can't find the context, check modifiers + if (!replace && modifiers) + { + for (int i = 0; i < modifiers->GetCount(); i++) + { + if (FStrEq(context, modifiers->GetName(i))) + { + replace = modifiers->GetValue(i); + break; + } + } + } + + if (replace) + { + CGMsg( 1, CON_GROUP_CHOREO, "Replacing %s with %s...\n", response, replace ); + Q_strncpy(response, replace, sizeof(response)); + + // Precache it now because it may not have been precached before + switch ( result->GetType() ) + { + case RESPONSE_SPEAK: + { + GetOuter()->PrecacheScriptSound( response ); + } + break; + + case RESPONSE_SCENE: + { + // TODO: Gender handling? + PrecacheInstancedScene( response ); + } + break; + } + } + } +#endif + bool spoke = false; float delay = response.GetDelay(); const char *szResponse = response.GetResponsePtr(); @@ -366,7 +573,7 @@ bool CAI_Expresser::SpeakDispatchResponse( AIConcept_t concept, AI_Response& res if ( IsSpeaking() && concept[0] != 0 ) { - DevMsg( "SpeakDispatchResponse: Entity ( %i/%s ) already speaking, forcing '%s'\n", GetOuter()->entindex(), STRING( GetOuter()->GetEntityName() ), concept ); + CGMsg( 1, CON_GROUP_CHOREO, "SpeakDispatchResponse: Entity ( %i/%s ) already speaking, forcing '%s'\n", GetOuter()->entindex(), STRING( GetOuter()->GetEntityName() ), concept ); // Tracker 15911: Can break the game if we stop an imported map placed lcs here, so only // cancel actor out of instanced scripted scenes. ywb @@ -375,7 +582,7 @@ bool CAI_Expresser::SpeakDispatchResponse( AIConcept_t concept, AI_Response& res if ( IsRunningScriptedScene( GetOuter() ) ) { - DevMsg( "SpeakDispatchResponse: Entity ( %i/%s ) refusing to speak due to scene entity, tossing '%s'\n", GetOuter()->entindex(), STRING( GetOuter()->GetEntityName() ), concept ); + CGMsg( 1, CON_GROUP_CHOREO, "SpeakDispatchResponse: Entity ( %i/%s ) refusing to speak due to scene entity, tossing '%s'\n", GetOuter()->entindex(), STRING( GetOuter()->GetEntityName() ), concept ); return false; } } @@ -390,14 +597,18 @@ bool CAI_Expresser::SpeakDispatchResponse( AIConcept_t concept, AI_Response& res if ( !response.ShouldntUseScene() ) { // This generates a fake CChoreoScene wrapping the sound.txt name +#ifdef MAPBASE + spoke = SpeakAutoGeneratedScene( szResponse, delay, result, filter ); +#else spoke = SpeakAutoGeneratedScene( szResponse, delay ); +#endif } else { float speakTime = GetResponseDuration( response ); GetOuter()->EmitSound( szResponse ); - DevMsg( "SpeakDispatchResponse: Entity ( %i/%s ) playing sound '%s'\n", GetOuter()->entindex(), STRING( GetOuter()->GetEntityName() ), szResponse ); + CGMsg( 1, CON_GROUP_CHOREO, "SpeakDispatchResponse: Entity ( %i/%s ) playing sound '%s'\n", GetOuter()->entindex(), STRING( GetOuter()->GetEntityName() ), response ); NoteSpeaking( speakTime, delay ); spoke = true; } @@ -436,6 +647,57 @@ bool CAI_Expresser::SpeakDispatchResponse( AIConcept_t concept, AI_Response& res NDebugOverlay::Text( vPrintPos, CFmtStr( "%s: %s", concept, szResponse ), true, 1.5 ); } +#ifdef MAPBASE + if (response.GetContext()) + { + const char *pszContext = response.GetContext(); + + // Check for operators + char *pOperator = Q_strstr(pszContext, ":")+1; + if (pOperator && (pOperator[0] == '+' || pOperator[0] == '-' || + pOperator[0] == '*' || pOperator[0] == '/')) + { + pszContext = ParseApplyContext(pszContext); + } + + int iContextFlags = response.GetContextFlags(); + if ( iContextFlags & APPLYCONTEXT_SQUAD ) + { + CAI_BaseNPC *pNPC = GetOuter()->MyNPCPointer(); + if (pNPC && pNPC->GetSquad()) + { + AISquadIter_t iter; + CAI_BaseNPC *pSquadmate = pNPC->GetSquad()->GetFirstMember( &iter ); + while ( pSquadmate ) + { + pSquadmate->AddContext( pszContext ); + + pSquadmate = pNPC->GetSquad()->GetNextMember( &iter ); + } + } + } + if ( iContextFlags & APPLYCONTEXT_ENEMY ) + { + CBaseEntity *pEnemy = GetOuter()->GetEnemy(); + if ( pEnemy ) + { + pEnemy->AddContext( pszContext ); + } + } + if ( iContextFlags & APPLYCONTEXT_WORLD ) + { + CBaseEntity *pEntity = CBaseEntity::Instance( engine->PEntityOfEntIndex( 0 ) ); + if ( pEntity ) + { + pEntity->AddContext( pszContext ); + } + } + if ( iContextFlags == 0 || iContextFlags & APPLYCONTEXT_SELF ) + { + GetOuter()->AddContext( pszContext ); + } + } +#else if ( response.IsApplyContextToWorld() ) { CBaseEntity *pEntity = CBaseEntity::Instance( engine->PEntityOfEntIndex( 0 ) ); @@ -448,6 +710,7 @@ bool CAI_Expresser::SpeakDispatchResponse( AIConcept_t concept, AI_Response& res { GetOuter()->AddContext( response.GetContext() ); } +#endif SetSpokeConcept( concept, &response ); } @@ -516,6 +779,32 @@ bool CAI_Expresser::Speak( AIConcept_t concept, const char *modifiers /*= NULL*/ return spoke; } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: Uses an AI_CriteriaSet directly instead of using context-format modifier text. +// Input : concept - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CAI_Expresser::Speak( AIConcept_t concept, const AI_CriteriaSet& modifiers, char *pszOutResponseChosen /* = NULL*/, size_t bufsize /* = 0 */, IRecipientFilter *filter /* = NULL */ ) +{ + AI_Response *result = SpeakFindResponse( concept, modifiers ); + if ( !result ) + { + return false; + } + + SpeechMsg( GetOuter(), "%s (%p) spoke %s (%f)\n", STRING(GetOuter()->GetEntityName()), GetOuter(), concept, gpGlobals->curtime ); + + bool spoke = SpeakDispatchResponse( concept, result, filter, &modifiers ); + if ( pszOutResponseChosen ) + { + result->GetResponse( pszOutResponseChosen, bufsize ); + } + + return spoke; +} +#endif + //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- @@ -544,9 +833,17 @@ bool CAI_Expresser::SpeakRawScene( const char *pszScene, float delay, AI_Respons } // This will create a fake .vcd/CChoreoScene to wrap the sound to be played +#ifdef MAPBASE +bool CAI_Expresser::SpeakAutoGeneratedScene( char const *soundname, float delay, AI_Response *response, IRecipientFilter *filter ) +#else bool CAI_Expresser::SpeakAutoGeneratedScene( char const *soundname, float delay ) +#endif { +#ifdef MAPBASE + float speakTime = GetOuter()->PlayAutoGeneratedSoundScene( soundname, delay, response, filter ); +#else float speakTime = GetOuter()->PlayAutoGeneratedSoundScene( soundname ); +#endif if ( speakTime > 0 ) { SpeechMsg( GetOuter(), "SpeakAutoGeneratedScene( %s, %f) %f\n", soundname, delay, speakTime ); @@ -763,8 +1060,32 @@ void CAI_Expresser::SetSpokeConcept( AIConcept_t concept, AI_Response *response, slot->response = new AI_Response( *response ); } +#ifdef MAPBASE + // This "weapondelay" was just in player allies before. + // Putting it here eliminates the need to implement OnSpokeConcept on all NPCs for weapondelay. + if ( bCallback ) + { + if( response != NULL && (response->GetParams()->flags & AI_ResponseParams::RG_WEAPONDELAY) ) + { + if ( GetOuter()->IsNPC() ) + { + // Stop shooting, as instructed, so that my speech can be heard. + GetOuter()->MyNPCPointer()->GetShotRegulator()->FireNoEarlierThan( gpGlobals->curtime + response->GetWeaponDelay() ); + } + else + { + char szResponse[64]; + response->GetName(szResponse, sizeof(szResponse)); + Warning("%s response %s wants to use weapondelay, but %s is not a NPC!\n", GetOuter()->GetDebugName(), szResponse, GetOuter()->GetDebugName()); + } + } + + GetSink()->OnSpokeConcept( concept, response ); + } +#else if ( bCallback ) GetSink()->OnSpokeConcept( concept, response ); +#endif } //------------------------------------- @@ -774,6 +1095,32 @@ void CAI_Expresser::ClearSpokeConcept( AIConcept_t concept ) m_ConceptHistories.Remove( concept ); } +#ifdef MAPBASE +//------------------------------------- + +AIConcept_t CAI_Expresser::GetLastSpokeConcept( AIConcept_t excludeConcept /* = NULL */ ) +{ + int iLastSpokenIndex = m_ConceptHistories.InvalidIndex(); + float flLast = 0.0f; + for ( int i = m_ConceptHistories.First(); i != m_ConceptHistories.InvalidIndex(); i = m_ConceptHistories.Next(i ) ) + { + ConceptHistory_t *h = &m_ConceptHistories[ i ]; + + // If an 'exclude concept' was provided, skip over this entry in the history if it matches the exclude concept + if ( excludeConcept != NULL && FStrEq( m_ConceptHistories.GetElementName( i ), excludeConcept ) ) + continue; + + if ( h->timeSpoken >= flLast ) + { + iLastSpokenIndex = i; + flLast = h->timeSpoken; + } + } + + return iLastSpokenIndex != m_ConceptHistories.InvalidIndex() ? m_ConceptHistories.GetElementName( iLastSpokenIndex ) : NULL; +} +#endif + //------------------------------------- void CAI_Expresser::DumpHistories() @@ -831,11 +1178,71 @@ void CAI_Expresser::SpeechMsg( CBaseEntity *pFlex, const char *pszFormat, ... ) } else { - DevMsg( "%s", string ); + CGMsg( 1, CON_GROUP_CHOREO, "%s", string ); } UTIL_LogPrintf( "%s", string ); } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +char *CAI_Expresser::ParseApplyContext( const char *szContext ) +{ + char szKey[128]; + char szValue[128]; + float flDuration = 0.0; + + SplitContext(szContext, szKey, sizeof(szKey), szValue, sizeof(szValue), &flDuration); + + // This is the value without the operator + const char *pszValue = szValue + 1; + + // This is the operator itself + char cOperator = szValue[0]; + + // This is the value to operate with (e.g. the "7" in "+7") + float flNewValue = atof( pszValue ); + + if (flNewValue == 0.0f) + { + // If it's really 0, then this is a waste of time + Warning("\"%s\" was detected by applyContext operators as an operable number, but it's not.\n", szValue); + return szContext; + } + + // This is the existing value; will be operated upon and become the final assignment + float flValue = 0.0f; + const char *szExistingValue = GetOuter()->GetContextValue(szKey); + if (szExistingValue) + flValue = atof(szExistingValue); + + // Do the operation based on what the character was + switch (cOperator) + { + case '+': flValue += flNewValue; break; + case '-': flValue -= flNewValue; break; + case '*': flValue *= flNewValue; break; + case '/': flValue /= flNewValue; break; + } + + Q_snprintf(szValue, sizeof(szValue), "%f", flValue); + + // Remove all trailing 0's from the float to maintain whole integers + int i; + for (i = strlen(szValue)-1; szValue[i] == '0'; i--) + { + szValue[i] = '\0'; + } + + // If there were only zeroes past the period, this is a whole number. Remove the period + if (szValue[i] == '.') + szValue[i] = '\0'; + + return UTIL_VarArgs("%s:%s:%f", szKey, szValue, flDuration); +} +#endif + //----------------------------------------------------------------------------- @@ -854,6 +1261,7 @@ void CAI_ExpresserHost_NPC_DoModifyOrAppendCriteria( CAI_BaseNPC *pSpeaker, AI_C set.AppendCriteria( "npcstate", UTIL_VarArgs( "[NPCState::%s]", pStateNames[pSpeaker->m_NPCState] ) ); } +#ifndef MAPBASE if ( pSpeaker->GetEnemy() ) { set.AppendCriteria( "enemy", pSpeaker->GetEnemy()->GetClassname() ); @@ -866,6 +1274,7 @@ void CAI_ExpresserHost_NPC_DoModifyOrAppendCriteria( CAI_BaseNPC *pSpeaker, AI_C else set.AppendCriteria( "timesincecombat", UTIL_VarArgs( "%f", gpGlobals->curtime - pSpeaker->GetLastEnemyTime() ) ); } +#endif set.AppendCriteria( "speed", UTIL_VarArgs( "%.3f", pSpeaker->GetSmoothedVelocity().Length() ) ); @@ -984,4 +1393,4 @@ void CMultiplayer_Expresser::AllowMultipleScenes() void CMultiplayer_Expresser::DisallowMultipleScenes() { m_bAllowMultipleScenes = false; -} \ No newline at end of file +} diff --git a/src/game/server/ai_speech.h b/src/game/server/ai_speech.h index fa173c1e..185891f0 100644 --- a/src/game/server/ai_speech.h +++ b/src/game/server/ai_speech.h @@ -5,6 +5,9 @@ // $NoKeywords: $ //=============================================================================// +#ifdef NEW_RESPONSE_SYSTEM +#include "ai_speech_new.h" +#else #ifndef AI_SPEECH_H #define AI_SPEECH_H @@ -157,10 +160,19 @@ public: // -------------------------------- bool Speak( AIConcept_t concept, const char *modifiers = NULL, char *pszOutResponseChosen = NULL, size_t bufsize = 0, IRecipientFilter *filter = NULL ); +#ifdef MAPBASE + bool Speak( AIConcept_t concept, const AI_CriteriaSet& modifiers, char *pszOutResponseChosen = NULL, size_t bufsize = 0, IRecipientFilter *filter = NULL ); + AI_Response *SpeakFindResponse( AIConcept_t concept, const AI_CriteriaSet& modifiers ); + void MergeModifiers( AI_CriteriaSet& set, const char *modifiers ); +#endif // These two methods allow looking up a response and dispatching it to be two different steps bool SpeakFindResponse( AI_Response &response, AIConcept_t concept, const char *modifiers = NULL ); +#ifdef MAPBASE + bool SpeakDispatchResponse( AIConcept_t concept, AI_Response &response, IRecipientFilter *filter = NULL, const AI_CriteriaSet *modifiers = NULL ); +#else bool SpeakDispatchResponse( AIConcept_t concept, AI_Response &response, IRecipientFilter *filter = NULL ); +#endif float GetResponseDuration( AI_Response &response ); virtual int SpeakRawSentence( const char *pszSentence, float delay, float volume = VOL_NORM, soundlevel_t soundlevel = SNDLVL_TALKING, CBaseEntity *pListener = NULL ); @@ -175,6 +187,10 @@ public: bool CanSpeakAfterMyself(); float GetTimeSpeechComplete() const { return m_flStopTalkTime; } void BlockSpeechUntil( float time ); + +#ifdef MAPBASE + float GetRealTimeSpeechComplete() const { return m_flStopTalkTimeWithoutDelay; } +#endif // -------------------------------- @@ -183,6 +199,10 @@ public: float GetTimeSpokeConcept( AIConcept_t concept ); // returns -1 if never void SetSpokeConcept( AIConcept_t concept, AI_Response *response, bool bCallback = true ); void ClearSpokeConcept( AIConcept_t concept ); + +#ifdef MAPBASE + AIConcept_t GetLastSpokeConcept( AIConcept_t excludeConcept = NULL ); +#endif // -------------------------------- @@ -194,17 +214,33 @@ public: // Force the NPC to release the semaphore & clear next speech time void ForceNotSpeaking( void ); +#ifdef MAPBASE_VSCRIPT + bool ScriptSpeakRawScene( char const *soundname, float delay ) { return SpeakRawScene( soundname, delay, NULL ); } + bool ScriptSpeakAutoGeneratedScene( char const *soundname, float delay ) { return SpeakAutoGeneratedScene( soundname, delay ); } + int ScriptSpeakRawSentence( char const *pszSentence, float delay ) { return SpeakRawSentence( pszSentence, delay ); } + bool ScriptSpeak( char const *concept, const char *modifiers ) { return Speak( concept, modifiers[0] != '\0' ? modifiers : NULL ); } +#endif + protected: CAI_TimedSemaphore *GetMySpeechSemaphore( CBaseEntity *pNpc ); bool SpeakRawScene( const char *pszScene, float delay, AI_Response *response, IRecipientFilter *filter = NULL ); // This will create a fake .vcd/CChoreoScene to wrap the sound to be played +#ifdef MAPBASE + bool SpeakAutoGeneratedScene( char const *soundname, float delay, AI_Response *response = NULL, IRecipientFilter *filter = NULL ); +#else bool SpeakAutoGeneratedScene( char const *soundname, float delay ); +#endif void DumpHistories(); void SpeechMsg( CBaseEntity *pFlex, PRINTF_FORMAT_STRING const char *pszFormat, ... ); +#ifdef MAPBASE + // Handles context operators + char *ParseApplyContext( const char *szContext ); +#endif + // -------------------------------- CAI_ExpresserSink *GetSink() { return m_pSink; } @@ -281,9 +317,15 @@ public: virtual void NoteSpeaking( float duration, float delay ); virtual bool Speak( AIConcept_t concept, const char *modifiers = NULL, char *pszOutResponseChosen = NULL, size_t bufsize = 0, IRecipientFilter *filter = NULL ); +#ifdef MAPBASE + virtual bool Speak( AIConcept_t concept, const AI_CriteriaSet& modifiers, char *pszOutResponseChosen = NULL, size_t bufsize = 0, IRecipientFilter *filter = NULL ); +#endif // These two methods allow looking up a response and dispatching it to be two different steps bool SpeakFindResponse( AI_Response& response, AIConcept_t concept, const char *modifiers = NULL ); +#ifdef MAPBASE + bool SpeakFindResponse( AIConcept_t concept, const AI_CriteriaSet& modifiers ); +#endif bool SpeakDispatchResponse( AIConcept_t concept, AI_Response& response ); virtual void PostSpeakDispatchResponse( AIConcept_t concept, AI_Response& response ) { return; } float GetResponseDuration( AI_Response& response ); @@ -324,6 +366,18 @@ inline bool CAI_ExpresserHost::Speak( AIConcept_t concept, const char return this->GetExpresser()->Speak( concept, modifiers, pszOutResponseChosen, bufsize, filter ); } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Version of Speak() that takes a direct AI_CriteriaSet for modifiers. +//----------------------------------------------------------------------------- +template +inline bool CAI_ExpresserHost::Speak( AIConcept_t concept, const AI_CriteriaSet& modifiers, char *pszOutResponseChosen /*=NULL*/, size_t bufsize /* = 0 */, IRecipientFilter *filter /* = NULL */ ) +{ + AssertOnce( this->GetExpresser()->GetOuter() == this ); + return this->GetExpresser()->Speak( concept, modifiers, pszOutResponseChosen, bufsize, filter ); +} +#endif + //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- template @@ -365,6 +419,16 @@ inline bool CAI_ExpresserHost::SpeakFindResponse( AI_Response& respons return this->GetExpresser()->SpeakFindResponse( response, concept, modifiers ); } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +template +inline AI_Response *CAI_ExpresserHost::SpeakFindResponse( AIConcept_t concept, const AI_CriteriaSet& modifiers ) +{ + return this->GetExpresser()->SpeakFindResponse( concept, modifiers ); +} +#endif + //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- template @@ -399,3 +463,4 @@ inline void CAI_ExpresserHost::DispatchResponse( const char *conceptNa //----------------------------------------------------------------------------- #endif // AI_SPEECH_H +#endif diff --git a/src/game/server/ai_speech_new.cpp b/src/game/server/ai_speech_new.cpp new file mode 100644 index 00000000..d29b6b39 --- /dev/null +++ b/src/game/server/ai_speech_new.cpp @@ -0,0 +1,1777 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" + +#include "ai_speech.h" + +#include "game.h" +#include "engine/IEngineSound.h" +#include "KeyValues.h" +#include "ai_basenpc.h" +#include "AI_Criteria.h" +#include "isaverestore.h" +#include "sceneentity.h" +#include "ai_speechqueue.h" +#ifdef MAPBASE +#include "ai_squad.h" +#endif + +// memdbgon must be the last include file in a .cpp file!!! +#include + +#define DEBUG_AISPEECH 1 +#ifdef DEBUG_AISPEECH +ConVar ai_debug_speech( "ai_debug_speech", "0" ); +#define DebuggingSpeech() ai_debug_speech.GetBool() +#else +inline void SpeechMsg( ... ) {} +#define DebuggingSpeech() (false) +#endif + +extern ConVar rr_debugresponses; + +#ifdef MAPBASE +ConVar ai_speech_print_mode( "ai_speech_print_mode", "1", FCVAR_NONE, "Set this value to 1 to print responses as game_text instead of debug point_message-like text." ); +#endif + +//----------------------------------------------------------------------------- + +CAI_TimedSemaphore g_AIFriendliesTalkSemaphore; +CAI_TimedSemaphore g_AIFoesTalkSemaphore; + +ConceptHistory_t::~ConceptHistory_t() +{ +} + +ConceptHistory_t::ConceptHistory_t( const ConceptHistory_t& src ) +{ + timeSpoken = src.timeSpoken; + m_response = src.m_response ; +} + +ConceptHistory_t& ConceptHistory_t::operator =( const ConceptHistory_t& src ) +{ + if ( this == &src ) + return *this; + + timeSpoken = src.timeSpoken; + m_response = src.m_response ; + + return *this; +} + +BEGIN_SIMPLE_DATADESC( ConceptHistory_t ) + DEFINE_FIELD( timeSpoken, FIELD_TIME ), // Relative to server time + // DEFINE_EMBEDDED( response, FIELD_??? ), // This is manually saved/restored by the ConceptHistory saverestore ops below +END_DATADESC() + +class CConceptHistoriesDataOps : public CDefSaveRestoreOps +{ +public: + virtual void Save( const SaveRestoreFieldInfo_t &fieldInfo, ISave *pSave ) + { + CUtlDict< ConceptHistory_t, int > *ch = ((CUtlDict< ConceptHistory_t, int > *)fieldInfo.pField); + int count = ch->Count(); + pSave->WriteInt( &count ); + for ( int i = 0 ; i < count; i++ ) + { + ConceptHistory_t *pHistory = &(*ch)[ i ]; + + pSave->StartBlock(); + { + + // Write element name + pSave->WriteString( ch->GetElementName( i ) ); + + // Write data + pSave->WriteAll( pHistory ); + // Write response blob + bool hasresponse = !pHistory->m_response.IsEmpty() ; + pSave->WriteBool( &hasresponse ); + if ( hasresponse ) + { + pSave->WriteAll( &pHistory->m_response ); + } + // TODO: Could blat out pHistory->criteria pointer here, if it's needed + } + pSave->EndBlock(); + } + } + + virtual void Restore( const SaveRestoreFieldInfo_t &fieldInfo, IRestore *pRestore ) + { + CUtlDict< ConceptHistory_t, int > *ch = ((CUtlDict< ConceptHistory_t, int > *)fieldInfo.pField); + + int count = pRestore->ReadInt(); + Assert( count >= 0 ); + for ( int i = 0 ; i < count; i++ ) + { + char conceptname[ 512 ]; + conceptname[ 0 ] = 0; + ConceptHistory_t history; + + pRestore->StartBlock(); + { + pRestore->ReadString( conceptname, sizeof( conceptname ), 0 ); + + pRestore->ReadAll( &history ); + + bool hasresponse = false; + + pRestore->ReadBool( &hasresponse ); + if ( hasresponse ) + { + history.m_response; + pRestore->ReadAll( &history.m_response ); + } + else + { + history.m_response.Invalidate(); + } + } + + pRestore->EndBlock(); + + // TODO: Could restore pHistory->criteria pointer here, if it's needed + + // Add to utldict + if ( conceptname[0] != 0 ) + { + ch->Insert( conceptname, history ); + } + else + { + Assert( !"Error restoring ConceptHistory_t, discarding!" ); + } + } + } + + virtual void MakeEmpty( const SaveRestoreFieldInfo_t &fieldInfo ) + { + } + + virtual bool IsEmpty( const SaveRestoreFieldInfo_t &fieldInfo ) + { + CUtlDict< ConceptHistory_t, int > *ch = ((CUtlDict< ConceptHistory_t, int > *)fieldInfo.pField); + return ch->Count() == 0 ? true : false; + } +}; + +CConceptHistoriesDataOps g_ConceptHistoriesSaveDataOps; + +///////////////////////////////////////////////// +// context operators +RR::CApplyContextOperator RR::sm_OpCopy(0); // " +RR::CIncrementOperator RR::sm_OpIncrement(2); // "++" +RR::CDecrementOperator RR::sm_OpDecrement(2); // "--" +#ifdef MAPBASE +RR::CMultiplyOperator RR::sm_OpMultiply(2); // "**" +RR::CDivideOperator RR::sm_OpDivide(2); // "/=" +#endif +RR::CToggleOperator RR::sm_OpToggle(1); // "!" + +#ifdef MAPBASE +// LEGACY - See below +RR::CIncrementOperator RR::sm_OpLegacyIncrement(1); // "+" +RR::CDecrementOperator RR::sm_OpLegacyDecrement(1); // "-" +RR::CMultiplyOperator RR::sm_OpLegacyMultiply(1); // "*" +RR::CDivideOperator RR::sm_OpLegacyDivide(1); // "/" +#endif + +RR::CApplyContextOperator *RR::CApplyContextOperator::FindOperator( const char *pContextString ) +{ + if ( !pContextString || pContextString[0] == 0 ) + { + return &sm_OpCopy; + } + +#ifdef MAPBASE + // This is one of those freak coincidences where Mapbase implemented its own context operators with no knowledge of context operators from later versions of the engine. + // Mapbase's context operators only required *one* operator character (e.g. '+' as opposed to '++') and supported multiplication and division. + // Although Valve's system now replaces Mapbase's system, multiplication and division have been added and maintaining the old syntax is needed for legacy support. + // That being said, it's believed that almost nobody knew that Mapbase supported context operators in the first place, so there might not be much legacy to support anyway. + switch (pContextString[0]) + { + case '+': + { + if (pContextString[1] != '+') + { + Warning( "\"%s\" needs another '+' to qualify as a proper operator. This code is regarding it as an operator anyway for legacy support, which might be going away soon!!!\n", pContextString ); + return &sm_OpLegacyIncrement; + } + else if (pContextString[2] == '\0') + break; + return &sm_OpIncrement; + } + case '-': + { + if (pContextString[1] != '-') + { + Warning( "\"%s\" needs another '-' to qualify as a proper operator. This code is regarding it as an operator anyway for legacy support, which might be going away soon!!!\n", pContextString ); + return &sm_OpLegacyDecrement; + } + else if (pContextString[2] == '\0') + break; + return &sm_OpIncrement; + } + case '*': + { + if (pContextString[1] != '*') + { + Warning( "\"%s\" needs another '*' to qualify as a proper operator. This code is regarding it as an operator anyway for legacy support, which might be going away soon!!!\n", pContextString ); + return &sm_OpLegacyMultiply; + } + else if (pContextString[2] == '\0') + break; + return &sm_OpMultiply; + } + case '/': + { + if (pContextString[1] != '=') + { + Warning( "\"%s\" needs a '=' after the '/' to qualify as a proper operator. This code is regarding it as an operator anyway for legacy support, which might be going away soon!!!\n", pContextString ); + return &sm_OpLegacyDivide; + } + else if (pContextString[2] == '\0') + break; + return &sm_OpDivide; + } break; + case '!': + { + return &sm_OpToggle; + } + } + + return &sm_OpCopy; +#else + if ( pContextString[0] == '+' && pContextString [1] == '+' && pContextString[2] != '\0' ) + { + return &sm_OpIncrement; + } + else if ( pContextString[0] == '-' && pContextString [1] == '-' && pContextString[2] != '\0' ) + { + return &sm_OpDecrement; + } +#ifdef MAPBASE + else if ( pContextString[0] == '*' && pContextString [1] == '*' && pContextString[2] != '\0' ) + { + return &sm_OpMultiply; + } + else if ( pContextString[0] == '/' && pContextString [1] == '=' && pContextString[2] != '\0' ) + { + return &sm_OpDivide; + } +#endif + else if ( pContextString[0] == '!' ) + { + return &sm_OpToggle; + } + else + { + return &sm_OpCopy; + } +#endif +} + +// default is just copy +bool RR::CApplyContextOperator::Apply( const char *pOldValue, const char *pOperator, char *pNewValue, int pNewValBufSize ) +{ + Assert( pOperator && pNewValue && pNewValBufSize > 0 ); + Assert( m_nSkipChars == 0 ); + if ( pOperator ) + { + V_strncpy( pNewValue, pOperator, pNewValBufSize ); + } + else + { + *pNewValue = 0; + } + return true; +} + +bool RR::CIncrementOperator::Apply( const char *pOldValue, const char *pOperator, char *pNewValue, int pNewValBufSize ) +{ + Assert( pOperator[0] == '+' && pOperator[1] == '+' ); + // parse out the old value as a numeric + int nOld = pOldValue ? V_atoi(pOldValue) : 0; + int nInc = V_atoi( pOperator+m_nSkipChars ); + V_snprintf( pNewValue, pNewValBufSize, "%d", nOld+nInc ); + return true; +} + +bool RR::CDecrementOperator::Apply( const char *pOldValue, const char *pOperator, char *pNewValue, int pNewValBufSize ) +{ + Assert( pOperator[0] == '-' && pOperator[1] == '-' ); + // parse out the old value as a numeric + int nOld = pOldValue ? V_atoi(pOldValue) : 0; + int nInc = V_atoi( pOperator+m_nSkipChars ); + V_snprintf( pNewValue, pNewValBufSize, "%d", nOld-nInc ); + return true; +} + +#ifdef MAPBASE +bool RR::CMultiplyOperator::Apply( const char *pOldValue, const char *pOperator, char *pNewValue, int pNewValBufSize ) +{ + Assert( pOperator[0] == '*' && pOperator[1] == '*' ); + // parse out the old value as a numeric + int nOld = pOldValue ? V_atoi(pOldValue) : 0; + int nInc = V_atoi( pOperator+m_nSkipChars ); + V_snprintf( pNewValue, pNewValBufSize, "%d", nOld*nInc ); + return true; +} + +bool RR::CDivideOperator::Apply( const char *pOldValue, const char *pOperator, char *pNewValue, int pNewValBufSize ) +{ + Assert( pOperator[0] == '/' && pOperator[1] == '=' ); + // parse out the old value as a numeric + int nOld = pOldValue ? V_atoi(pOldValue) : 0; + int nInc = V_atoi( pOperator+m_nSkipChars ); + if (nInc == 0) + V_strncpy( pNewValue, "0", pNewValBufSize ); + else + V_snprintf( pNewValue, pNewValBufSize, "%d", nOld/nInc ); + return true; +} +#endif + +bool RR::CToggleOperator::Apply( const char *pOldValue, const char *pOperator, char *pNewValue, int pNewValBufSize ) +{ + Assert( pOperator[0] == '!' ); + // parse out the old value as a numeric + int nOld = pOldValue ? V_atoi(pOldValue) : 0; + V_snprintf( pNewValue, pNewValBufSize, "%d", nOld ? 0 : 1 ); + return true; +} + + +//----------------------------------------------------------------------------- +// +// CLASS: CAI_Expresser +// + +BEGIN_SIMPLE_DATADESC( CAI_Expresser ) + // m_pSink (reconnected on load) +// DEFINE_FIELD( m_pOuter, CHandle < CBaseFlex > ), + DEFINE_CUSTOM_FIELD( m_ConceptHistories, &g_ConceptHistoriesSaveDataOps ), + DEFINE_FIELD( m_flStopTalkTime, FIELD_TIME ), + DEFINE_FIELD( m_flStopTalkTimeWithoutDelay, FIELD_TIME ), + DEFINE_FIELD( m_flBlockedTalkTime, FIELD_TIME ), + DEFINE_FIELD( m_voicePitch, FIELD_INTEGER ), + DEFINE_FIELD( m_flLastTimeAcceptedSpeak, FIELD_TIME ), +END_DATADESC() + +#ifdef MAPBASE_VSCRIPT +BEGIN_SCRIPTDESC_ROOT( CAI_Expresser, "Expresser class for complex speech." ) + + DEFINE_SCRIPTFUNC( IsSpeaking, "Check if the actor is speaking." ) + DEFINE_SCRIPTFUNC( CanSpeak, "Check if the actor can speak." ) + DEFINE_SCRIPTFUNC( BlockSpeechUntil, "Block speech for a certain amount of time. This is stored in curtime." ) + DEFINE_SCRIPTFUNC( ForceNotSpeaking, "If the actor is speaking, force the system to recognize them as not speaking." ) + + DEFINE_SCRIPTFUNC( GetVoicePitch, "Get the actor's voice pitch. Used in sentences." ) + DEFINE_SCRIPTFUNC( SetVoicePitch, "Set the actor's voice pitch. Used in sentences." ) + + DEFINE_SCRIPTFUNC_NAMED( ScriptSpeakRawScene, "SpeakRawScene", "Speak a raw, instanced VCD scene as though it were played through the Response System. Return whether the scene successfully plays." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptSpeakAutoGeneratedScene, "SpeakAutoGeneratedScene", "Speak an automatically generated, instanced VCD scene for this sound as though it were played through the Response System. Return whether the scene successfully plays." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptSpeakRawSentence, "SpeakRawSentence", "Speak a raw sentence as though it were played through the Response System. Return the sentence's index; -1 if not successfully played." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptSpeak, "Speak", "Speak a response concept with the specified modifiers." ) + +END_SCRIPTDESC(); +#endif + +//------------------------------------- + +bool CAI_Expresser::SemaphoreIsAvailable( CBaseEntity *pTalker ) +{ + if ( !GetSink()->UseSemaphore() ) + return true; + + CAI_TimedSemaphore *pSemaphore = GetMySpeechSemaphore( pTalker->MyNPCPointer() ); + return (pSemaphore ? pSemaphore->IsAvailable( pTalker ) : true); +} + +//------------------------------------- + +float CAI_Expresser::GetSemaphoreAvailableTime( CBaseEntity *pTalker ) +{ + CAI_TimedSemaphore *pSemaphore = GetMySpeechSemaphore( pTalker->MyNPCPointer() ); + return (pSemaphore ? pSemaphore->GetReleaseTime() : 0); +} + +//------------------------------------- + +int CAI_Expresser::GetVoicePitch() const +{ + return m_voicePitch + random->RandomInt(0,3); +} + +#ifdef DEBUG +static int g_nExpressers; +#endif + +/* +inline bool ShouldBeInExpresserQueue( CBaseFlex *pOuter ) +{ + return true; // return IsTerrorPlayer( pOuter, TEAM_SURVIVOR ); +} +*/ + +CAI_Expresser::CAI_Expresser( CBaseFlex *pOuter ) + : m_pOuter( pOuter ), + m_pSink( NULL ), + m_flStopTalkTime( 0 ), + m_flBlockedTalkTime( 0 ), + m_flStopTalkTimeWithoutDelay( 0 ), + m_voicePitch( 100 ), + m_flLastTimeAcceptedSpeak( 0 ) +{ +#ifdef DEBUG + g_nExpressers++; +#endif + if (m_pOuter) + { + // register me with the global expresser queue. + + // L4D: something a little ass backwards is happening here. We only want + // survivors to be in the queue. However, the team number isn't + // specified yet. So, we actually need to do this in the player's ChangeTeam. + g_ResponseQueueManager.GetQueue()->AddExpresserHost(m_pOuter); + + } +} + +CAI_Expresser::~CAI_Expresser() +{ + m_ConceptHistories.Purge(); + + CBaseFlex *RESTRICT outer = GetOuter(); + if ( outer ) + { + CAI_TimedSemaphore *pSemaphore = GetMySpeechSemaphore( outer ); + if ( pSemaphore ) + { + if ( pSemaphore->GetOwner() == outer ) + pSemaphore->Release(); + +#ifdef DEBUG + g_nExpressers--; + if ( g_nExpressers == 0 && pSemaphore->GetOwner() ) + CGMsg( 2, CON_GROUP_SPEECH_AI, "Speech semaphore being held by non-talker entity\n" ); +#endif + } + + g_ResponseQueueManager.GetQueue()->RemoveExpresserHost(outer); + } +} + +//----------------------------------------------------------------------------- +void CAI_Expresser::TestAllResponses() +{ + IResponseSystem *pResponseSystem = GetOuter()->GetResponseSystem(); + if ( pResponseSystem ) + { + CUtlVector responses; + pResponseSystem->GetAllResponses( &responses ); + for ( int i = 0; i < responses.Count(); i++ ) + { + char response[ 256 ]; + responses[i].GetResponse( response, sizeof( response ) ); + + Msg( "Response: %s\n", response ); + AIConcept_t concept; + SpeakDispatchResponse( concept, &responses[i], NULL ); + } + } +} + +//----------------------------------------------------------------------------- +void CAI_Expresser::SetOuter( CBaseFlex *pOuter ) +{ + // if we're changing outers (which is a strange thing to do) + // unregister the old one from the queue. + if ( m_pOuter && ( m_pOuter != pOuter ) ) + { + AssertMsg2( false, "Expresser is switching its Outer from %s to %s. Why?", m_pOuter->GetDebugName(), pOuter->GetDebugName() ); + // unregister me with the global expresser queue + g_ResponseQueueManager.GetQueue()->RemoveExpresserHost(m_pOuter); + } + + m_pOuter = pOuter; +} + +//----------------------------------------------------------------------------- + +static const int LEN_SPECIFIC_SCENE_MODIFIER = strlen( AI_SPECIFIC_SCENE_MODIFIER ); + + +// This function appends "Global world" criteria that are always added to +// any character doing any match. This represents global concepts like weather, who's +// alive, etc. +static void ModifyOrAppendGlobalCriteria( AI_CriteriaSet * RESTRICT outputSet ) +{ + return; +} + + +void CAI_Expresser::GatherCriteria( AI_CriteriaSet * RESTRICT outputSet, const AIConcept_t &concept, const char * RESTRICT modifiers ) +{ + // Always include the concept name + outputSet->AppendCriteria( "concept", concept, CONCEPT_WEIGHT ); + +#if 1 + outputSet->Merge( modifiers ); +#else + // Always include any optional modifiers + if ( modifiers != NULL ) + { + char copy_modifiers[ 255 ]; + const char *pCopy; + char key[ 128 ] = { 0 }; + char value[ 128 ] = { 0 }; + + Q_strncpy( copy_modifiers, modifiers, sizeof( copy_modifiers ) ); + pCopy = copy_modifiers; + + while( pCopy ) + { + pCopy = SplitContext( pCopy, key, sizeof( key ), value, sizeof( value ), NULL, modifiers ); + + if( *key && *value ) + { + outputSet->AppendCriteria( key, value, CONCEPT_WEIGHT ); + } + } + } +#endif + + // include any global criteria + ModifyOrAppendGlobalCriteria( outputSet ); + + // Let our outer fill in most match criteria + GetOuter()->ModifyOrAppendCriteria( *outputSet ); + + // Append local player criteria to set, but not if this is a player doing the talking + if ( !GetOuter()->IsPlayer() ) + { + CBasePlayer *pPlayer = UTIL_PlayerByIndex( 1 ); + if( pPlayer ) + pPlayer->ModifyOrAppendPlayerCriteria( *outputSet ); + } + +#ifdef MAPBASE + GetOuter()->ReAppendContextCriteria( *outputSet ); +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: Searches for a possible response +// Input : concept - +// NULL - +// Output : AI_Response +//----------------------------------------------------------------------------- +// AI_Response *CAI_Expresser::SpeakFindResponse( AIConcept_t concept, const char *modifiers /*= NULL*/ ) +bool CAI_Expresser::FindResponse( AI_Response &outResponse, const AIConcept_t &concept, AI_CriteriaSet *criteria ) +{ + VPROF("CAI_Expresser::FindResponse"); + IResponseSystem *rs = GetOuter()->GetResponseSystem(); + if ( !rs ) + { + Assert( !"No response system installed for CAI_Expresser::GetOuter()!!!" ); + return NULL; + } + + // if I'm dead, I can't possibly match dialog. +#ifndef MAPBASE // Except for...you know...death sounds. + if ( !GetOuter()->IsAlive() ) + { + return false; + } +#endif + +#if 0 // this is the old technique, where we always gathered criteria in this function + AI_CriteriaSet set; + // Always include the concept name + set.AppendCriteria( "concept", concept, CONCEPT_WEIGHT ); + + // Always include any optional modifiers + if ( modifiers != NULL ) + { + char copy_modifiers[ 255 ]; + const char *pCopy; + char key[ 128 ] = { 0 }; + char value[ 128 ] = { 0 }; + + Q_strncpy( copy_modifiers, modifiers, sizeof( copy_modifiers ) ); + pCopy = copy_modifiers; + + while( pCopy ) + { + pCopy = SplitContext( pCopy, key, sizeof( key ), value, sizeof( value ), NULL, modifiers ); + + if( *key && *value ) + { + set.AppendCriteria( key, value, CONCEPT_WEIGHT ); + } + } + } + + // Let our outer fill in most match criteria + GetOuter()->ModifyOrAppendCriteria( set ); + + // Append local player criteria to set, but not if this is a player doing the talking + if ( !GetOuter()->IsPlayer() ) + { + CBasePlayer *pPlayer = UTIL_PlayerByIndex( 1 ); + if( pPlayer ) + pPlayer->ModifyOrAppendPlayerCriteria( set ); + } +#else + AI_CriteriaSet localCriteriaSet; // put it on the stack so we don't deal with new/delete + if (criteria == NULL) + { + GatherCriteria( &localCriteriaSet, concept, NULL ); + criteria = &localCriteriaSet; + } +#endif + + /// intercept any deferred criteria that are being sent to world + AI_CriteriaSet worldWritebackCriteria; + AI_CriteriaSet::InterceptWorldSetContexts( criteria, &worldWritebackCriteria ); + + // Now that we have a criteria set, ask for a suitable response + bool found = rs->FindBestResponse( *criteria, outResponse, this ); + + if ( rr_debugresponses.GetInt() == 4 ) + { + if ( ( GetOuter()->MyNPCPointer() && GetOuter()->m_debugOverlays & OVERLAY_NPC_SELECTED_BIT ) || GetOuter()->IsPlayer() ) + { + const char *pszName; + if ( GetOuter()->IsPlayer() ) + { + pszName = ((CBasePlayer*)GetOuter())->GetPlayerName(); + } + else + { + pszName = GetOuter()->GetDebugName(); + } + + if ( found ) + { + char response[ 256 ]; + outResponse.GetResponse( response, sizeof( response ) ); + + Warning( "RESPONSERULES: %s spoke '%s'. Found response '%s'.\n", pszName, (const char*)concept, response ); + } + else + { + Warning( "RESPONSERULES: %s spoke '%s'. Found no matching response.\n", pszName, (const char*)concept ); + } + } + } + + if ( !found ) + { + return false; + } + else if ( worldWritebackCriteria.GetCount() > 0 ) + { + Assert( CBaseEntity::Instance( INDEXENT( 0 ) )->IsWorld( ) ); + worldWritebackCriteria.WriteToEntity( CBaseEntity::Instance( INDEXENT( 0 ) ) ); + } + + if ( outResponse.IsEmpty() ) + { + // AssertMsg2( false, "RR: %s got empty but valid response for %s", GetOuter()->GetDebugName(), concept.GetStringConcept() ); + return false; + } + else + { + return true; + } +} + +#if 0 +//----------------------------------------------------------------------------- +// Purpose: Searches for a possible response; writes it into a response passed as +// parameter rather than new'ing one up. +// Input : concept - +// NULL - +// Output : bool : true on success, false on fail +//----------------------------------------------------------------------------- +AI_Response *CAI_Expresser::SpeakFindResponse( AI_Response *result, const AIConcept_t &concept, AI_CriteriaSet *criteria ) +{ + Assert(response); + + IResponseSystem *rs = GetOuter()->GetResponseSystem(); + if ( !rs ) + { + Assert( !"No response system installed for CAI_Expresser::GetOuter()!!!" ); + return NULL; + } + +#if 0 + AI_CriteriaSet set; + // Always include the concept name + set.AppendCriteria( "concept", concept, CONCEPT_WEIGHT ); + + // Always include any optional modifiers + if ( modifiers != NULL ) + { + char copy_modifiers[ 255 ]; + const char *pCopy; + char key[ 128 ] = { 0 }; + char value[ 128 ] = { 0 }; + + Q_strncpy( copy_modifiers, modifiers, sizeof( copy_modifiers ) ); + pCopy = copy_modifiers; + + while( pCopy ) + { + pCopy = SplitContext( pCopy, key, sizeof( key ), value, sizeof( value ), NULL, modifiers ); + + if( *key && *value ) + { + set.AppendCriteria( key, value, CONCEPT_WEIGHT ); + } + } + } + + // Let our outer fill in most match criteria + GetOuter()->ModifyOrAppendCriteria( set ); + + // Append local player criteria to set, but not if this is a player doing the talking + if ( !GetOuter()->IsPlayer() ) + { + CBasePlayer *pPlayer = UTIL_PlayerByIndex( 1 ); + if( pPlayer ) + pPlayer->ModifyOrAppendPlayerCriteria( set ); + } +#else + AI_CriteriaSet &set = *criteria; +#endif + + // Now that we have a criteria set, ask for a suitable response + bool found = rs->FindBestResponse( set, *result, this ); + + if ( rr_debugresponses.GetInt() == 4 ) + { + if ( ( GetOuter()->MyNPCPointer() && GetOuter()->m_debugOverlays & OVERLAY_NPC_SELECTED_BIT ) || GetOuter()->IsPlayer() ) + { + const char *pszName; + if ( GetOuter()->IsPlayer() ) + { + pszName = ((CBasePlayer*)GetOuter())->GetPlayerName(); + } + else + { + pszName = GetOuter()->GetDebugName(); + } + + if ( found ) + { + char response[ 256 ]; + result->GetResponse( response, sizeof( response ) ); + + Warning( "RESPONSERULES: %s spoke '%s'. Found response '%s'.\n", pszName, concept, response ); + } + else + { + Warning( "RESPONSERULES: %s spoke '%s'. Found no matching response.\n", pszName, concept ); + } + } + } + + if ( !found ) + { + //Assert( !"rs->FindBestResponse: Returned a NULL AI_Response!" ); + return false; + } + + char response[ 256 ]; + result->GetResponse( response, sizeof( response ) ); + + if ( !response[0] ) + { + return false; + } + + return true; +} +#endif + +//----------------------------------------------------------------------------- +// Purpose: Dispatches the result +// Input : *response - +//----------------------------------------------------------------------------- +bool CAI_Expresser::SpeakDispatchResponse( AIConcept_t concept, AI_Response *result, AI_CriteriaSet *criteria, IRecipientFilter *filter /* = NULL */ ) +{ + char response[ 256 ]; + result->GetResponse( response, sizeof( response ) ); + + float delay = result->GetDelay(); + + bool spoke = false; + + soundlevel_t soundlevel = result->GetSoundLevel(); + + if ( IsSpeaking() && concept[0] != 0 && result->GetType() != ResponseRules::RESPONSE_PRINT ) + { + const char *entityName = STRING( GetOuter()->GetEntityName() ); + if ( GetOuter()->IsPlayer() ) + { + entityName = ToBasePlayer( GetOuter() )->GetPlayerName(); + } + CGMsg( 2, CON_GROUP_SPEECH_AI, "SpeakDispatchResponse: Entity ( %i/%s ) already speaking, forcing '%s'\n", GetOuter()->entindex(), entityName ? entityName : "UNKNOWN", (const char*)concept ); + + // Tracker 15911: Can break the game if we stop an imported map placed lcs here, so only + // cancel actor out of instanced scripted scenes. ywb + RemoveActorFromScriptedScenes( GetOuter(), true /*instanced scenes only*/ ); + GetOuter()->SentenceStop(); + + if ( IsRunningScriptedScene( GetOuter() ) ) + { + CGMsg( 1, CON_GROUP_SPEECH_AI, "SpeakDispatchResponse: Entity ( %i/%s ) refusing to speak due to scene entity, tossing '%s'\n", GetOuter()->entindex(), entityName ? entityName : "UNKNOWN", (const char*)concept ); + return false; + } + } + + switch ( result->GetType() ) + { + default: + case ResponseRules::RESPONSE_NONE: + break; + + case ResponseRules::RESPONSE_SPEAK: + { + if ( !result->ShouldntUseScene() ) + { + // This generates a fake CChoreoScene wrapping the sound.txt name +#ifdef MAPBASE + spoke = SpeakAutoGeneratedScene( response, delay, result, filter ); +#else + spoke = SpeakAutoGeneratedScene( response, delay ); +#endif + } + else + { + float speakTime = GetResponseDuration( result ); + GetOuter()->EmitSound( response ); + + CGMsg( 2, CON_GROUP_SPEECH_AI, "SpeakDispatchResponse: Entity ( %i/%s ) playing sound '%s'\n", GetOuter()->entindex(), STRING( GetOuter()->GetEntityName() ), response ); + NoteSpeaking( speakTime, delay ); + spoke = true; +#ifdef MAPBASE + // Not really any other way of doing this + OnSpeechFinished(); +#endif + } + } + break; + + case ResponseRules::RESPONSE_SENTENCE: + { + spoke = ( -1 != SpeakRawSentence( response, delay, VOL_NORM, soundlevel ) ) ? true : false; +#ifdef MAPBASE + // Not really any other way of doing this + OnSpeechFinished(); +#endif + } + break; + + case ResponseRules::RESPONSE_SCENE: + { + spoke = SpeakRawScene( response, delay, result, filter ); + } + break; + + case ResponseRules::RESPONSE_RESPONSE: + { + // This should have been recursively resolved already + Assert( 0 ); + } + break; + case ResponseRules::RESPONSE_PRINT: + { +#ifdef MAPBASE + // Note speaking for print responses + int responseLen = Q_strlen( response ); + float responseDuration = ((float)responseLen) * 0.1f; + NoteSpeaking( responseDuration, delay ); + + // game_text print responses + hudtextparms_t textParams; + textParams.holdTime = 4.0f + responseDuration; // Give extra padding for the text itself + textParams.fadeinTime = 0.5f; + textParams.fadeoutTime = 0.5f; + + textParams.channel = 3; + textParams.x = -1; + textParams.y = 0.6; + textParams.effect = 0; + + textParams.r1 = 255; + textParams.g1 = 255; + textParams.b1 = 255; + + if (ai_speech_print_mode.GetBool() && GetOuter()->GetGameTextSpeechParams( textParams )) + { + CRecipientFilter filter; + filter.AddAllPlayers(); + filter.MakeReliable(); + + UserMessageBegin( filter, "HudMsg" ); + WRITE_BYTE ( textParams.channel & 0xFF ); + WRITE_FLOAT( textParams.x ); + WRITE_FLOAT( textParams.y ); + WRITE_BYTE ( textParams.r1 ); + WRITE_BYTE ( textParams.g1 ); + WRITE_BYTE ( textParams.b1 ); + WRITE_BYTE ( textParams.a1 ); + WRITE_BYTE ( textParams.r2 ); + WRITE_BYTE ( textParams.g2 ); + WRITE_BYTE ( textParams.b2 ); + WRITE_BYTE ( textParams.a2 ); + WRITE_BYTE ( textParams.effect ); + WRITE_FLOAT( textParams.fadeinTime ); + WRITE_FLOAT( textParams.fadeoutTime ); + WRITE_FLOAT( textParams.holdTime ); + WRITE_FLOAT( textParams.fxTime ); + WRITE_STRING( response ); + WRITE_STRING( "" ); // No custom font + WRITE_BYTE ( responseLen ); + MessageEnd(); + + spoke = true; + + OnSpeechFinished(); + } + else +#endif + if ( g_pDeveloper->GetInt() > 0 ) + { + Vector vPrintPos; + GetOuter()->CollisionProp()->NormalizedToWorldSpace( Vector(0.5,0.5,1.0f), &vPrintPos ); + NDebugOverlay::Text( vPrintPos, response, true, 1.5 ); + } + spoke = true; +#ifdef MAPBASE + OnSpeechFinished(); +#endif + } + break; + case ResponseRules::RESPONSE_ENTITYIO: + { + spoke = FireEntIOFromResponse( response, GetOuter() ); +#ifdef MAPBASE + OnSpeechFinished(); +#endif + } + break; +#ifdef MAPBASE_VSCRIPT + case ResponseRules::RESPONSE_VSCRIPT: + { + spoke = RunScriptResponse( GetOuter(), response, criteria, false ); + OnSpeechFinished(); + } + break; + case ResponseRules::RESPONSE_VSCRIPT_FILE: + { + spoke = RunScriptResponse( GetOuter(), response, criteria, true ); + OnSpeechFinished(); + } + break; +#endif + } + + if ( spoke ) + { + m_flLastTimeAcceptedSpeak = gpGlobals->curtime; + if ( DebuggingSpeech() && g_pDeveloper->GetInt() > 0 && response && result->GetType() != ResponseRules::RESPONSE_PRINT ) + { + Vector vPrintPos; + GetOuter()->CollisionProp()->NormalizedToWorldSpace( Vector(0.5,0.5,1.0f), &vPrintPos ); + NDebugOverlay::Text( vPrintPos, CFmtStr( "%s: %s", (const char*)concept, response ), true, 1.5 ); + } + +#ifdef MAPBASE + if (result->GetContext()) + { + const char *pszContext = result->GetContext(); + + int iContextFlags = result->GetContextFlags(); + if ( iContextFlags & ResponseRules::APPLYCONTEXT_SQUAD ) + { + CAI_BaseNPC *pNPC = GetOuter()->MyNPCPointer(); + if (pNPC && pNPC->GetSquad()) + { + AISquadIter_t iter; + CAI_BaseNPC *pSquadmate = pNPC->GetSquad()->GetFirstMember( &iter ); + while ( pSquadmate ) + { + pSquadmate->AddContext( pszContext ); + + pSquadmate = pNPC->GetSquad()->GetNextMember( &iter ); + } + } + } + if ( iContextFlags & ResponseRules::APPLYCONTEXT_ENEMY ) + { + CBaseEntity *pEnemy = GetOuter()->GetEnemy(); + if ( pEnemy ) + { + pEnemy->AddContext( pszContext ); + } + } + if ( iContextFlags & ResponseRules::APPLYCONTEXT_WORLD ) + { + CBaseEntity *pEntity = CBaseEntity::Instance( INDEXENT( 0 ) ); + if ( pEntity ) + { + pEntity->AddContext( pszContext ); + } + } + if ( iContextFlags == 0 || iContextFlags & ResponseRules::APPLYCONTEXT_SELF ) + { + GetOuter()->AddContext( pszContext ); + } + } +#else + if ( result->IsApplyContextToWorld() ) + { + CBaseEntity *pEntity = CBaseEntity::Instance( INDEXENT( 0 ) ); + if ( pEntity ) + { + pEntity->AddContext( result->GetContext() ); + } + } + else + { + GetOuter()->AddContext( result->GetContext() ); + } +#endif + SetSpokeConcept( concept, result ); + } + else + { + } + + return spoke; +} + +bool CAI_Expresser::FireEntIOFromResponse( char *response, CBaseEntity *pInitiator ) +{ + // find the space-separator in the response name, then split into entityname, input, and parameter + // may barf in linux; there, should make some StringTokenizer() class that wraps the strtok_s behavior, etc. + char *pszEntname; + char *pszInput; + char *pszParam; + char *strtokContext; + + pszEntname = V_strtok_s( response, " ", &strtokContext ); + if ( !pszEntname ) + { + Warning( "Response was entityio but had bad value %s\n", response ); + return false; + } + + pszInput = V_strtok_s( NULL, " ", &strtokContext ); + if ( !pszInput ) + { + Warning( "Response was entityio but had bad value %s\n", response ); + return false; + } + + pszParam = V_strtok_s( NULL, " ", &strtokContext ); + + // poke entity io + CBaseEntity *pTarget = gEntList.FindEntityByName( NULL, pszEntname, pInitiator ); + if ( !pTarget ) + { + CGMsg( 0, CON_GROUP_SPEECH_AI, "Response rule targeted %s with entityio, but that doesn't exist.\n", pszEntname ); + // but this is actually a legit use case, so return true (below). + } + else + { + // pump the action into the target + variant_t variant; + if ( pszParam ) + { + variant.SetString( MAKE_STRING(pszParam) ); + } + pTarget->AcceptInput( pszInput, pInitiator, pInitiator, variant, 0 ); + + } + return true; +} + +#ifdef MAPBASE_VSCRIPT +bool CAI_Expresser::RunScriptResponse( CBaseEntity *pTarget, const char *response, AI_CriteriaSet *criteria, bool file ) +{ + if (!pTarget->ValidateScriptScope()) + return false; + + ScriptVariant_t varCriteriaTable; + g_pScriptVM->CreateTable( varCriteriaTable ); + + if (criteria) + { + // Sort all of the criteria into a table. + // Letting VScript have access to this is important because not all criteria is appended in ModifyOrAppendCriteria() and + // not all contexts are actually appended as contexts. This is specifically important for followup responses. + int count = criteria->GetCount(); + for ( int i = 0 ; i < count ; ++i ) + { + // TODO: Weight? + g_pScriptVM->SetValue( varCriteriaTable, criteria->GetName(i), criteria->GetValue(i) ); + } + + g_pScriptVM->SetValue( "criteria", varCriteriaTable ); + } + + bool bSuccess = false; + if (file) + { + bSuccess = pTarget->RunScriptFile( response ); + } + else + { + bSuccess = pTarget->RunScript( response, "ResponseScript" ); + } + + g_pScriptVM->ClearValue( "criteria" ); + g_pScriptVM->ReleaseScript( varCriteriaTable ); + + return bSuccess; +} +#endif + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *response - +// Output : float +//----------------------------------------------------------------------------- +float CAI_Expresser::GetResponseDuration( AI_Response *result ) +{ + Assert( result ); + char response[ 256 ]; + result->GetResponse( response, sizeof( response ) ); + + switch ( result->GetType() ) + { + case ResponseRules::RESPONSE_SPEAK: + { + return GetOuter()->GetSoundDuration( response, STRING( GetOuter()->GetModelName() ) ); + } + break; + case ResponseRules::RESPONSE_SENTENCE: + { + Assert( 0 ); + return 999.0f; + } + break; + case ResponseRules::RESPONSE_SCENE: + { + return GetSceneDuration( response ); + } + break; + case ResponseRules::RESPONSE_RESPONSE: + { + // This should have been recursively resolved already + Assert( 0 ); + } + break; + case ResponseRules::RESPONSE_PRINT: + { + return 1.0; + } + break; + default: + case ResponseRules::RESPONSE_NONE: + case ResponseRules::RESPONSE_ENTITYIO: + return 0.0f; + } + + return 0.0f; +} + +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CAI_Expresser::SetUsingProspectiveResponses( bool bToggle ) +{ + VPROF("CAI_Expresser::SetUsingProspectiveResponses"); + IResponseSystem *rs = GetOuter()->GetResponseSystem(); + if ( !rs ) + { + Assert( !"No response system installed for CAI_Expresser::GetOuter()!!!" ); + return; + } + + rs->SetProspective( bToggle ); +} + +void CAI_Expresser::MarkResponseAsUsed( AI_Response *response ) +{ + VPROF("CAI_Expresser::MarkResponseAsUsed"); + IResponseSystem *rs = GetOuter()->GetResponseSystem(); + if ( !rs ) + { + Assert( !"No response system installed for CAI_Expresser::GetOuter()!!!" ); + return; + } + + rs->MarkResponseAsUsed( response->GetInternalIndices()[0], response->GetInternalIndices()[1] ); +} +#endif + +//----------------------------------------------------------------------------- +// Purpose: Placeholder for rules based response system +// Input : concept - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CAI_Expresser::Speak( AIConcept_t concept, const char *modifiers /*= NULL*/, char *pszOutResponseChosen /* = NULL*/, size_t bufsize /* = 0 */, IRecipientFilter *filter /* = NULL */ ) +{ + concept.SetSpeaker(GetOuter()); + AI_CriteriaSet criteria; + GatherCriteria(&criteria, concept, modifiers); + + return Speak( concept, &criteria, pszOutResponseChosen, bufsize, filter ); +} + + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CAI_Expresser::Speak( const AIConcept_t &concept, AI_CriteriaSet * RESTRICT criteria, char *pszOutResponseChosen , size_t bufsize , IRecipientFilter *filter ) +{ + VPROF("CAI_Expresser::Speak"); + if ( IsSpeechGloballySuppressed() ) + { + return false; + } + + GetOuter()->ModifyOrAppendDerivedCriteria(*criteria); + AI_Response result; + if ( !FindResponse( result, concept, criteria ) ) + { + return false; + } + + SpeechMsg( GetOuter(), "%s (%p) spoke %s (%f)", STRING(GetOuter()->GetEntityName()), GetOuter(), (const char*)concept, gpGlobals->curtime ); + // Msg( "%s:%s to %s:%s\n", GetOuter()->GetDebugName(), concept.GetStringConcept(), criteria.GetValue(criteria.FindCriterionIndex("Subject")), pTarget ? pTarget->GetDebugName() : "none" ); + + bool spoke = SpeakDispatchResponse( concept, &result, criteria, filter ); + if ( pszOutResponseChosen ) + { + result.GetResponse( pszOutResponseChosen, bufsize ); + } + + return spoke; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CAI_Expresser::SpeakRawScene( const char *pszScene, float delay, AI_Response *response, IRecipientFilter *filter /* = NULL */ ) +{ + float sceneLength = GetOuter()->PlayScene( pszScene, delay, response, filter ); + if ( sceneLength > 0 ) + { + SpeechMsg( GetOuter(), "SpeakRawScene( %s, %f) %f\n", pszScene, delay, sceneLength ); + +#if defined( HL2_EPISODIC ) + char szInstanceFilename[256]; + GetOuter()->GenderExpandString( pszScene, szInstanceFilename, sizeof( szInstanceFilename ) ); + // Only mark ourselves as speaking if the scene has speech + if ( GetSceneSpeechCount(szInstanceFilename) > 0 ) + { + NoteSpeaking( sceneLength, delay ); + } +#else + NoteSpeaking( sceneLength, delay ); +#endif + + return true; + } + return false; +} + +// This will create a fake .vcd/CChoreoScene to wrap the sound to be played +#ifdef MAPBASE +bool CAI_Expresser::SpeakAutoGeneratedScene( char const *soundname, float delay, AI_Response *response, IRecipientFilter *filter ) +#else +bool CAI_Expresser::SpeakAutoGeneratedScene( char const *soundname, float delay ) +#endif +{ +#ifdef MAPBASE + float speakTime = GetOuter()->PlayAutoGeneratedSoundScene( soundname, delay, response, filter ); +#else + float speakTime = GetOuter()->PlayAutoGeneratedSoundScene( soundname ); +#endif + if ( speakTime > 0 ) + { + SpeechMsg( GetOuter(), "SpeakAutoGeneratedScene( %s, %f) %f\n", soundname, delay, speakTime ); + NoteSpeaking( speakTime, delay ); + return true; + } + return false; +} + +//------------------------------------- + +int CAI_Expresser::SpeakRawSentence( const char *pszSentence, float delay, float volume, soundlevel_t soundlevel, CBaseEntity *pListener ) +{ + int sentenceIndex = -1; + + if ( !pszSentence ) + return sentenceIndex; + + if ( pszSentence[0] == AI_SP_SPECIFIC_SENTENCE ) + { + sentenceIndex = SENTENCEG_Lookup( pszSentence ); + + if( sentenceIndex == -1 ) + { + // sentence not found + return -1; + } + + CPASAttenuationFilter filter( GetOuter(), soundlevel ); + CBaseEntity::EmitSentenceByIndex( filter, GetOuter()->entindex(), CHAN_VOICE, sentenceIndex, volume, soundlevel, 0, GetVoicePitch()); + } + else + { + sentenceIndex = SENTENCEG_PlayRndSz( GetOuter()->NetworkProp()->edict(), pszSentence, volume, soundlevel, 0, GetVoicePitch() ); + } + + SpeechMsg( GetOuter(), "SpeakRawSentence( %s, %f) %f\n", pszSentence, delay, engine->SentenceLength( sentenceIndex ) ); + NoteSpeaking( engine->SentenceLength( sentenceIndex ), delay ); + + return sentenceIndex; +} + +//------------------------------------- + +void CAI_Expresser::BlockSpeechUntil( float time ) +{ + SpeechMsg( GetOuter(), "BlockSpeechUntil(%f) %f\n", time, time - gpGlobals->curtime ); + m_flBlockedTalkTime = time; +} + + +//------------------------------------- + +void CAI_Expresser::NoteSpeaking( float duration, float delay ) +{ + duration += delay; + + GetSink()->OnStartSpeaking(); + + if ( duration <= 0 ) + { + // no duration :( + m_flStopTalkTime = gpGlobals->curtime + 3; + duration = 0; + } + else + { + m_flStopTalkTime = gpGlobals->curtime + duration; + } + + m_flStopTalkTimeWithoutDelay = m_flStopTalkTime - delay; + + SpeechMsg( GetOuter(), "NoteSpeaking( %f, %f ) (stop at %f)\n", duration, delay, m_flStopTalkTime ); + + if ( GetSink()->UseSemaphore() ) + { + CAI_TimedSemaphore *pSemaphore = GetMySpeechSemaphore( GetOuter() ); + if ( pSemaphore ) + { + pSemaphore->Acquire( duration, GetOuter() ); + } + } +} + +//------------------------------------- + +void CAI_Expresser::ForceNotSpeaking( void ) +{ + if ( IsSpeaking() ) + { + m_flStopTalkTime = gpGlobals->curtime; + m_flStopTalkTimeWithoutDelay = gpGlobals->curtime; + + CAI_TimedSemaphore *pSemaphore = GetMySpeechSemaphore( GetOuter() ); + if ( pSemaphore ) + { + if ( pSemaphore->GetOwner() == GetOuter() ) + { + pSemaphore->Release(); + } + } + } +} + +//------------------------------------- + +bool CAI_Expresser::IsSpeaking( void ) +{ + if ( m_flStopTalkTime > gpGlobals->curtime ) + SpeechMsg( GetOuter(), "IsSpeaking() %f\n", m_flStopTalkTime - gpGlobals->curtime ); + + if ( m_flLastTimeAcceptedSpeak == gpGlobals->curtime ) // only one speak accepted per think + return true; + + return ( m_flStopTalkTime > gpGlobals->curtime ); +} + +//------------------------------------- + +bool CAI_Expresser::CanSpeak() +{ + if ( m_flLastTimeAcceptedSpeak == gpGlobals->curtime ) // only one speak accepted per think + return false; + + float timeOk = MAX( m_flStopTalkTime, m_flBlockedTalkTime ); + return ( timeOk <= gpGlobals->curtime ); +} + +//----------------------------------------------------------------------------- +// Purpose: Returns true if it's ok for this entity to speak after himself. +// The base CanSpeak() includes the default speech delay, and won't +// return true until that delay time has passed after finishing the +// speech. This returns true as soon as the speech finishes. +//----------------------------------------------------------------------------- +bool CAI_Expresser::CanSpeakAfterMyself() +{ + if ( m_flLastTimeAcceptedSpeak == gpGlobals->curtime ) // only one speak accepted per think + return false; + + float timeOk = MAX( m_flStopTalkTimeWithoutDelay, m_flBlockedTalkTime ); + return ( timeOk <= gpGlobals->curtime ); +} + +//------------------------------------- +bool CAI_Expresser::CanSpeakConcept( const AIConcept_t &concept ) +{ + // Not in history? + int iter = m_ConceptHistories.Find( concept ); + if ( iter == m_ConceptHistories.InvalidIndex() ) + { + return true; + } + + ConceptHistory_t *history = &m_ConceptHistories[iter]; + Assert( history ); + + const AI_Response &response = history->m_response; + if ( response.IsEmpty() ) + return true; + + if ( response.GetSpeakOnce() ) + return false; + + float respeakDelay = response.GetRespeakDelay(); + + if ( respeakDelay != 0.0f ) + { + if ( history->timeSpoken != -1 && ( gpGlobals->curtime < history->timeSpoken + respeakDelay ) ) + return false; + } + + return true; +} + +//------------------------------------- + +bool CAI_Expresser::SpokeConcept( const AIConcept_t &concept ) +{ + return GetTimeSpokeConcept( concept ) != -1.f; +} + +//------------------------------------- + +float CAI_Expresser::GetTimeSpokeConcept( const AIConcept_t &concept ) +{ + int iter = m_ConceptHistories.Find( concept ); + if ( iter == m_ConceptHistories.InvalidIndex() ) + return -1; + + ConceptHistory_t *h = &m_ConceptHistories[iter]; + return h->timeSpoken; +} + +//------------------------------------- + +void CAI_Expresser::SetSpokeConcept( const AIConcept_t &concept, AI_Response *response, bool bCallback ) +{ + int idx = m_ConceptHistories.Find( concept ); + if ( idx == m_ConceptHistories.InvalidIndex() ) + { + ConceptHistory_t h; + h.timeSpoken = gpGlobals->curtime; + idx = m_ConceptHistories.Insert( concept, h ); + } + + ConceptHistory_t *slot = &m_ConceptHistories[ idx ]; + + slot->timeSpoken = gpGlobals->curtime; + // Update response info + if ( response ) + { + slot->m_response = *response; + } + + if ( bCallback ) + GetSink()->OnSpokeConcept( concept, response ); +} + +//------------------------------------- + +void CAI_Expresser::ClearSpokeConcept( const AIConcept_t &concept ) +{ + m_ConceptHistories.Remove( concept ); +} + +#ifdef MAPBASE +//------------------------------------- + +AIConcept_t CAI_Expresser::GetLastSpokeConcept( AIConcept_t excludeConcept /* = NULL */ ) +{ + int iLastSpokenIndex = m_ConceptHistories.InvalidIndex(); + float flLast = 0.0f; + for ( int i = m_ConceptHistories.First(); i != m_ConceptHistories.InvalidIndex(); i = m_ConceptHistories.Next(i ) ) + { + ConceptHistory_t *h = &m_ConceptHistories[ i ]; + + // If an 'exclude concept' was provided, skip over this entry in the history if it matches the exclude concept + if ( excludeConcept != NULL && FStrEq( m_ConceptHistories.GetElementName( i ), excludeConcept ) ) + continue; + + if ( h->timeSpoken >= flLast ) + { + iLastSpokenIndex = i; + flLast = h->timeSpoken; + } + } + + return iLastSpokenIndex != m_ConceptHistories.InvalidIndex() ? m_ConceptHistories.GetElementName( iLastSpokenIndex ) : NULL; +} +#endif + +//------------------------------------- + +void CAI_Expresser::DumpHistories() +{ + int c = 1; + for ( int i = m_ConceptHistories.First(); i != m_ConceptHistories.InvalidIndex(); i = m_ConceptHistories.Next(i ) ) + { + ConceptHistory_t *h = &m_ConceptHistories[ i ]; + + CGMsg( 1, CON_GROUP_SPEECH_AI, "%i: %s at %f\n", c++, m_ConceptHistories.GetElementName( i ), h->timeSpoken ); + } +} + +//------------------------------------- + +bool CAI_Expresser::IsValidResponse( ResponseType_t type, const char *pszValue ) +{ + if ( type == ResponseRules::RESPONSE_SCENE ) + { + char szInstanceFilename[256]; + GetOuter()->GenderExpandString( pszValue, szInstanceFilename, sizeof( szInstanceFilename ) ); + return ( GetSceneDuration( szInstanceFilename ) > 0 ); + } + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CAI_TimedSemaphore *CAI_Expresser::GetMySpeechSemaphore( CBaseEntity *pNpc ) +{ + if ( !pNpc->MyNPCPointer() ) + return NULL; + + return (pNpc->MyNPCPointer()->IsPlayerAlly() ? &g_AIFriendliesTalkSemaphore : &g_AIFoesTalkSemaphore ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CAI_Expresser::SpeechMsg( CBaseEntity *pFlex, const char *pszFormat, ... ) +{ + if ( !DebuggingSpeech() ) + return; + + va_list arg_ptr; + + va_start(arg_ptr, pszFormat); + CFmtStr formatted; + formatted.sprintf_argv(pszFormat, arg_ptr); + va_end(arg_ptr); + + if ( pFlex->MyNPCPointer() ) + { + + DevMsg( pFlex->MyNPCPointer(), "%s", formatted.Get() ); + } + else + { + CGMsg( 1, CON_GROUP_SPEECH_AI, "%s", formatted.Get() ); + } + UTIL_LogPrintf( "%s", formatted.Get() ); +} + +//----------------------------------------------------------------------------- +// Purpose: returns true when l4d is in credits screen or some other +// speech-forbidden state +//----------------------------------------------------------------------------- +bool CAI_Expresser::IsSpeechGloballySuppressed() +{ + return false; +} + +//----------------------------------------------------------------------------- + +void CAI_ExpresserHost_NPC_DoModifyOrAppendCriteria( CAI_BaseNPC *pSpeaker, AI_CriteriaSet& set ) +{ + // Append current activity name + const char *pActivityName = pSpeaker->GetActivityName( pSpeaker->GetActivity() ); + if ( pActivityName ) + { + set.AppendCriteria( "activity", pActivityName ); + } + + static const char *pStateNames[] = { "None", "Idle", "Alert", "Combat", "Scripted", "PlayDead", "Dead" }; + if ( (int)pSpeaker->m_NPCState < ARRAYSIZE(pStateNames) ) + { + set.AppendCriteria( "npcstate", UTIL_VarArgs( "[NPCState::%s]", pStateNames[pSpeaker->m_NPCState] ) ); + } + + if ( pSpeaker->GetEnemy() ) + { + set.AppendCriteria( "enemy", pSpeaker->GetEnemy()->GetClassname() ); + set.AppendCriteria( "timesincecombat", "-1" ); + } + else + { + if ( pSpeaker->GetLastEnemyTime() == 0.0 ) + set.AppendCriteria( "timesincecombat", "999999.0" ); + else + set.AppendCriteria( "timesincecombat", UTIL_VarArgs( "%f", gpGlobals->curtime - pSpeaker->GetLastEnemyTime() ) ); + } + + set.AppendCriteria( "speed", UTIL_VarArgs( "%.3f", pSpeaker->GetSmoothedVelocity().Length() ) ); + + CBaseCombatWeapon *weapon = pSpeaker->GetActiveWeapon(); + if ( weapon ) + { + set.AppendCriteria( "weapon", weapon->GetClassname() ); + } + else + { + set.AppendCriteria( "weapon", "none" ); + } + + CBasePlayer *pPlayer = AI_GetSinglePlayer(); + if ( pPlayer ) + { + Vector distance = pPlayer->GetAbsOrigin() - pSpeaker->GetAbsOrigin(); + + set.AppendCriteria( "distancetoplayer", UTIL_VarArgs( "%f", distance.Length() ) ); + + } + else + { + set.AppendCriteria( "distancetoplayer", UTIL_VarArgs( "%i", MAX_COORD_RANGE ) ); + } + + if ( pSpeaker->HasCondition( COND_SEE_PLAYER ) ) + { + set.AppendCriteria( "seeplayer", "1" ); + } + else + { + set.AppendCriteria( "seeplayer", "0" ); + } + + if ( pPlayer && pPlayer->FInViewCone( pSpeaker ) && pPlayer->FVisible( pSpeaker ) ) + { + set.AppendCriteria( "seenbyplayer", "1" ); + } + else + { + set.AppendCriteria( "seenbyplayer", "0" ); + } +} + +//----------------------------------------------------------------------------- + +extern CBaseEntity *FindPickerEntity( CBasePlayer *pPlayer ); + +CON_COMMAND( npc_speakall, "Force the npc to try and speak all their responses" ) +{ + CBaseEntity *pEntity; + + if ( args[1] && *args[1] ) + { + pEntity = gEntList.FindEntityByName( NULL, args[1], NULL ); + if ( !pEntity ) + { + pEntity = gEntList.FindEntityByClassname( NULL, args[1] ); + } + } + else + { + pEntity = UTIL_GetCommandClient() ? FindPickerEntity( UTIL_GetCommandClient() ) : NULL; + } + + if ( pEntity ) + { + CAI_BaseNPC *pNPC = pEntity->MyNPCPointer(); + if (pNPC) + { + if ( pNPC->GetExpresser() ) + { + bool save = engine->LockNetworkStringTables( false ); + pNPC->GetExpresser()->TestAllResponses(); + engine->LockNetworkStringTables( save ); + } + } + } +} +//----------------------------------------------------------------------------- + +CMultiplayer_Expresser::CMultiplayer_Expresser( CBaseFlex *pOuter ) : CAI_ExpresserWithFollowup( pOuter ) +{ + m_bAllowMultipleScenes = false; +} + +bool CMultiplayer_Expresser::IsSpeaking( void ) +{ + if ( m_bAllowMultipleScenes ) + { + return false; + } + + return CAI_Expresser::IsSpeaking(); +} + + +void CMultiplayer_Expresser::AllowMultipleScenes() +{ + m_bAllowMultipleScenes = true; +} + +void CMultiplayer_Expresser::DisallowMultipleScenes() +{ + m_bAllowMultipleScenes = false; +} diff --git a/src/game/server/ai_speech_new.h b/src/game/server/ai_speech_new.h new file mode 100644 index 00000000..8290d471 --- /dev/null +++ b/src/game/server/ai_speech_new.h @@ -0,0 +1,730 @@ +//========= Copyright (c) 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef AI_SPEECH_H +#define AI_SPEECH_H + +#include "utlmap.h" + +#include "soundflags.h" +#include "AI_Criteria.h" +#include "AI_ResponseSystem.h" +#include "utldict.h" +#include "ai_speechconcept.h" + +#if defined( _WIN32 ) +#pragma once +#endif + +class KeyValues; + +using ResponseRules::ResponseType_t; +using ResponseRules::AI_ResponseFollowup; + + +//----------------------------------------------------------------------------- +// Purpose: Used to share a global resource or prevent a system stepping on +// own toes. +//----------------------------------------------------------------------------- + +class CAI_TimedSemaphore +{ +public: + CAI_TimedSemaphore() + : m_ReleaseTime( 0 ) + { + m_hCurrentTalker = NULL; + } + + void Acquire( float time, CBaseEntity *pTalker ) { m_ReleaseTime = gpGlobals->curtime + time; m_hCurrentTalker = pTalker; } + void Release() { m_ReleaseTime = 0; m_hCurrentTalker = NULL; } + + // Current owner of the semaphore is always allowed to talk + bool IsAvailable( CBaseEntity *pTalker ) const { return ((gpGlobals->curtime > m_ReleaseTime) || (m_hCurrentTalker == pTalker)); } + float GetReleaseTime() const { return m_ReleaseTime; } + + CBaseEntity *GetOwner() { return m_hCurrentTalker; } + +private: + float m_ReleaseTime; + EHANDLE m_hCurrentTalker; +}; + +//----------------------------------------------------------------------------- + +extern CAI_TimedSemaphore g_AIFriendliesTalkSemaphore; +extern CAI_TimedSemaphore g_AIFoesTalkSemaphore; + +#define GetSpeechSemaphore( pNpc ) (((pNpc)->IsPlayerAlly()) ? &g_AIFriendliesTalkSemaphore : &g_AIFoesTalkSemaphore ) +//----------------------------------------------------------------------------- +// Basic speech system types +//----------------------------------------------------------------------------- + +//------------------------------------- +// Constants + + +const float AIS_NO_DELAY = 0; +const soundlevel_t AIS_DEF_SNDLVL = SNDLVL_TALKING; +#define AI_NULL_CONCEPT NULL + +#define AI_NULL_SENTENCE NULL + +// Sentence prefix constants +#define AI_SP_SPECIFIC_SENTENCE '!' +#define AI_SP_WAVFILE '^' +#define AI_SP_SCENE_GROUP '=' +#define AI_SP_SPECIFIC_SCENE '?' + +#define AI_SPECIFIC_SENTENCE(str_constant) "!" str_constant +#define AI_WAVFILE(str_constant) "^" str_constant +// @Note (toml 09-12-02): as scene groups are not currently implemented, the string is a semi-colon delimited list +#define AI_SCENE_GROUP(str_constant) "=" str_constant +#define AI_SPECIFIC_SCENE(str_constant) "?" str_constant + +// Designer overriding modifiers +#define AI_SPECIFIC_SCENE_MODIFIER "scene:" + +//------------------------------------- + +//------------------------------------- +// An id that represents the core meaning of a spoken phrase, +// eventually to be mapped to a sentence group or scene + +#if AI_CONCEPTS_ARE_STRINGS +typedef const char *AIConcept_t; +inline bool CompareConcepts( AIConcept_t c1, AIConcept_t c2 ) +{ + return ( (void *)c1 == (void *)c2 || ( c1 && c2 && Q_stricmp( c1, c2 ) == 0 ) ); +} +#else +typedef CAI_Concept AIConcept_t; +inline bool CompareConcepts( AIConcept_t c1, AIConcept_t c2 ) +{ + return c1.m_iConcept == c2.m_iConcept; +} +#endif + + +//----------------------------------------------------------------------------- +// CAI_Expresser +// +// Purpose: Provides the functionality of going from abstract concept ("hello") +// to specific sentence/scene/wave +// + +//------------------------------------- +// Sink supports behavior control and receives notifications of internal events + +class CAI_ExpresserSink +{ +public: + virtual void OnSpokeConcept( AIConcept_t concept, AI_Response *response ) {}; + virtual void OnStartSpeaking() {} + virtual bool UseSemaphore() { return true; } + +#ifdef MAPBASE + // Works around issues with CAI_ExpresserHost<> class hierarchy + virtual CAI_Expresser *GetSinkExpresser() { return NULL; } + virtual bool IsAllowedToSpeakFollowup( AIConcept_t concept, CBaseEntity *pIssuer, bool bSpecific ) { return true; } + virtual bool Speak( AIConcept_t concept, AI_CriteriaSet *pCriteria, char *pszOutResponseChosen = NULL, size_t bufsize = 0, IRecipientFilter *filter = NULL ) { return false; } +#endif +}; + +struct ConceptHistory_t +{ + DECLARE_SIMPLE_DATADESC(); + + ConceptHistory_t(float timeSpoken = -1 ) + : timeSpoken( timeSpoken ), m_response( ) + { + } + + ConceptHistory_t( const ConceptHistory_t& src ); + ConceptHistory_t& operator = ( const ConceptHistory_t& src ); + + ~ConceptHistory_t(); + + float timeSpoken; + AI_Response m_response; +}; +//------------------------------------- + +class CAI_Expresser : public ResponseRules::IResponseFilter +{ +public: + CAI_Expresser( CBaseFlex *pOuter = NULL ); + ~CAI_Expresser(); + + // -------------------------------- + + bool Connect( CAI_ExpresserSink *pSink ) { m_pSink = pSink; return true; } + bool Disconnect( CAI_ExpresserSink *pSink ) { m_pSink = NULL; return true;} + + void TestAllResponses(); + + // -------------------------------- + + bool Speak( AIConcept_t concept, const char *modifiers = NULL, char *pszOutResponseChosen = NULL, size_t bufsize = 0, IRecipientFilter *filter = NULL ); + bool Speak( const AIConcept_t &concept, AI_CriteriaSet *criteria, char *pszOutResponseChosen = NULL, size_t bufsize = 0, IRecipientFilter *filter = NULL ); + + // Given modifiers (which are colon-delimited strings), fill out a criteria set including this + // character's contexts and the ones in the modifier. This lets us hang on to them after a call + // to SpeakFindResponse. + void GatherCriteria( AI_CriteriaSet *outputCritera, const AIConcept_t &concept, const char *modifiers ); + // These two methods allow looking up a response and dispatching it to be two different steps + // AI_Response *SpeakFindResponse( AIConcept_t concept, const char *modifiers = NULL ); + // AI_Response *SpeakFindResponse( AIConcept_t &concept, AI_CriteriaSet *criteria ); + // Find the appropriate response for the given concept. Return false if none found. + // Fills out the response object that you provide. + bool FindResponse( AI_Response &outResponse, const AIConcept_t &concept, AI_CriteriaSet *modifiers = NULL ); + virtual bool SpeakDispatchResponse( AIConcept_t concept, AI_Response *response, AI_CriteriaSet *criteria, IRecipientFilter *filter = NULL ); + float GetResponseDuration( AI_Response *response ); + +#ifdef MAPBASE + void SetUsingProspectiveResponses( bool bToggle ); + void MarkResponseAsUsed( AI_Response *response ); +#endif + + virtual int SpeakRawSentence( const char *pszSentence, float delay, float volume = VOL_NORM, soundlevel_t soundlevel = SNDLVL_TALKING, CBaseEntity *pListener = NULL ); + + bool SemaphoreIsAvailable( CBaseEntity *pTalker ); + float GetSemaphoreAvailableTime( CBaseEntity *pTalker ); + + virtual void OnSpeechFinished() {}; + + // This function can be overriden by games to suppress speech altogether during glue screens, etc + static bool IsSpeechGloballySuppressed(); + + // -------------------------------- + + virtual bool IsSpeaking(); + bool CanSpeak(); + bool CanSpeakAfterMyself(); + float GetTimeSpeechComplete() const { return m_flStopTalkTime; } +#ifdef MAPBASE + float GetTimeSpeechCompleteWithoutDelay() const { return m_flStopTalkTimeWithoutDelay; } +#endif + void BlockSpeechUntil( float time ); + + // -------------------------------- + + bool CanSpeakConcept( const AIConcept_t &concept ); + bool SpokeConcept( const AIConcept_t &concept ); + float GetTimeSpokeConcept( const AIConcept_t &concept ); // returns -1 if never + void SetSpokeConcept( const AIConcept_t &concept, AI_Response *response, bool bCallback = true ); + void ClearSpokeConcept( const AIConcept_t &concept ); + +#ifdef MAPBASE + AIConcept_t GetLastSpokeConcept( AIConcept_t excludeConcept = NULL ); +#endif + + // -------------------------------- + + void SetVoicePitch( int voicePitch ) { m_voicePitch = voicePitch; } + int GetVoicePitch() const; + + void NoteSpeaking( float duration, float delay = 0 ); + + // Force the NPC to release the semaphore & clear next speech time + void ForceNotSpeaking( void ); + +#ifdef MAPBASE_VSCRIPT + bool ScriptSpeakRawScene( char const *soundname, float delay ) { return SpeakRawScene( soundname, delay, NULL ); } + bool ScriptSpeakAutoGeneratedScene( char const *soundname, float delay ) { return SpeakAutoGeneratedScene( soundname, delay ); } + int ScriptSpeakRawSentence( char const *pszSentence, float delay ) { return SpeakRawSentence( pszSentence, delay ); } + bool ScriptSpeak( char const *concept, const char *modifiers ) { return Speak( concept, modifiers[0] != '\0' ? modifiers : NULL ); } +#endif + + // helper used in dealing with RESPONSE_ENTITYIO + // response is the output of AI_Response::GetName + // note: the response string will get stomped on (by strtok) + // returns false on failure (eg, couldn't match parse contents) + static bool FireEntIOFromResponse( char *response, CBaseEntity *pInitiator ); + +#ifdef MAPBASE_VSCRIPT + // Used for RESPONSE_VSCRIPT(_FILE) + static bool RunScriptResponse( CBaseEntity *pTarget, const char *response, AI_CriteriaSet *criteria, bool file ); +#endif + +#ifdef MAPBASE +public: +#else +protected: +#endif + CAI_TimedSemaphore *GetMySpeechSemaphore( CBaseEntity *pNpc ); + +protected: + + bool SpeakRawScene( const char *pszScene, float delay, AI_Response *response, IRecipientFilter *filter = NULL ); + // This will create a fake .vcd/CChoreoScene to wrap the sound to be played +#ifdef MAPBASE + bool SpeakAutoGeneratedScene( char const *soundname, float delay, AI_Response *response = NULL, IRecipientFilter *filter = NULL ); +#else + bool SpeakAutoGeneratedScene( char const *soundname, float delay ); +#endif + + void DumpHistories(); + + void SpeechMsg( CBaseEntity *pFlex, PRINTF_FORMAT_STRING const char *pszFormat, ... ) FMTFUNCTION(3, 4); + + // -------------------------------- + + CAI_ExpresserSink *GetSink() { return m_pSink; } + +private: + // -------------------------------- + + virtual bool IsValidResponse( ResponseType_t type, const char *pszValue ); + + // -------------------------------- + + CAI_ExpresserSink *m_pSink; + + // -------------------------------- + // + // Speech concept data structures + // + + CUtlDict< ConceptHistory_t, int > m_ConceptHistories; + + // -------------------------------- + // + // Speaking states + // + + float m_flStopTalkTime; // when in the future that I'll be done saying this sentence. + float m_flStopTalkTimeWithoutDelay; // same as the above, but minus the delay before other people can speak + float m_flBlockedTalkTime; + int m_voicePitch; // pitch of voice for this head + float m_flLastTimeAcceptedSpeak; // because speech may not be blocked until NoteSpeaking called by scene ent, this handles in-think blocking + + DECLARE_SIMPLE_DATADESC(); + + // -------------------------------- + // +public: + void SetOuter( CBaseFlex *pOuter ); + + CBaseFlex * GetOuter() { return m_pOuter; } + const CBaseFlex * GetOuter() const { return m_pOuter; } + +private: + CHandle m_pOuter; +}; + +//----------------------------------------------------------------------------- +// +// An NPC base class to assist a branch of the inheritance graph +// in utilizing CAI_Expresser +// + +template +class CAI_ExpresserHost : public BASE_NPC, public CAI_ExpresserSink +{ + DECLARE_CLASS_NOFRIEND( CAI_ExpresserHost, BASE_NPC ); + +public: +#ifdef MAPBASE + CAI_Expresser *GetSinkExpresser() { return this->GetExpresser(); } +#endif + + virtual void NoteSpeaking( float duration, float delay ); + + virtual bool Speak( AIConcept_t concept, const char *modifiers = NULL, char *pszOutResponseChosen = NULL, size_t bufsize = 0, IRecipientFilter *filter = NULL ); + virtual bool Speak( AIConcept_t concept, AI_CriteriaSet *pCriteria, char *pszOutResponseChosen = NULL, size_t bufsize = 0, IRecipientFilter *filter = NULL ); +#ifdef MAPBASE + virtual bool Speak( AIConcept_t concept, AI_CriteriaSet& modifiers, char *pszOutResponseChosen = NULL, size_t bufsize = 0, IRecipientFilter *filter = NULL ) { return Speak( concept, &modifiers, pszOutResponseChosen, bufsize, filter ); } +#endif + + + void GatherCriteria( AI_CriteriaSet *outputCritera, const AIConcept_t &concept, const char *modifiers ); + // These two methods allow looking up a response and dispatching it to be two different steps +#ifdef MAPBASE + //AI_Response *SpeakFindResponse( AIConcept_t concept, const AI_CriteriaSet& modifiers ); + inline bool SpeakDispatchResponse( const AIConcept_t &concept, AI_Response &response, AI_CriteriaSet *criteria = NULL ) { return SpeakDispatchResponse( concept, &response, criteria ); } +#endif + bool SpeakFindResponse( AI_Response& outResponse, const AIConcept_t &concept, const char *modifiers = NULL ); + // AI_Response * SpeakFindResponse( AIConcept_t concept, const char *modifiers = NULL ); + // AI_Response *SpeakFindResponse( AIConcept_t concept, AI_CriteriaSet *criteria ); + // AI_Response *SpeakFindResponse( AIConcept_t concept ); + // Find the appropriate response for the given concept. Return false if none found. + // Fills out the response object that you provide. + bool FindResponse( AI_Response &outResponse, const AIConcept_t &concept, AI_CriteriaSet *criteria = NULL ); + + bool SpeakDispatchResponse( AIConcept_t concept, AI_Response *response, AI_CriteriaSet *criteria = NULL ); + virtual void PostSpeakDispatchResponse( AIConcept_t concept, AI_Response *response ) { return; } + float GetResponseDuration( AI_Response *response ); + + float GetTimeSpeechComplete() const { return this->GetExpresser()->GetTimeSpeechComplete(); } + + bool IsSpeaking() { return this->GetExpresser()->IsSpeaking(); } + bool CanSpeak() { return this->GetExpresser()->CanSpeak(); } + bool CanSpeakAfterMyself() { return this->GetExpresser()->CanSpeakAfterMyself(); } + + void SetSpokeConcept( AIConcept_t concept, AI_Response *response, bool bCallback = true ) { this->GetExpresser()->SetSpokeConcept( concept, response, bCallback ); } + float GetTimeSpokeConcept( AIConcept_t concept ) { return this->GetExpresser()->GetTimeSpokeConcept( concept ); } + bool SpokeConcept( AIConcept_t concept ) { return this->GetExpresser()->SpokeConcept( concept ); } + +protected: + int PlaySentence( const char *pszSentence, float delay, float volume = VOL_NORM, soundlevel_t soundlevel = SNDLVL_TALKING, CBaseEntity *pListener = NULL ); + virtual void ModifyOrAppendCriteria( AI_CriteriaSet& set ); + + virtual IResponseSystem *GetResponseSystem(); + // Override of base entity response input handler + virtual void DispatchResponse( const char *conceptName ); +}; + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +template +inline void CAI_ExpresserHost::NoteSpeaking( float duration, float delay ) +{ + this->GetExpresser()->NoteSpeaking( duration, delay ); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +template +inline bool CAI_ExpresserHost::Speak( AIConcept_t concept, const char *modifiers /*= NULL*/, char *pszOutResponseChosen /*=NULL*/, size_t bufsize /* = 0 */, IRecipientFilter *filter /* = NULL */ ) +{ + AssertOnce( this->GetExpresser()->GetOuter() == this ); + return this->GetExpresser()->Speak( concept, modifiers, pszOutResponseChosen, bufsize, filter ); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +template +inline bool CAI_ExpresserHost::Speak( AIConcept_t concept, AI_CriteriaSet *pCriteria, char *pszOutResponseChosen /*=NULL*/, size_t bufsize /* = 0 */, IRecipientFilter *filter /* = NULL */ ) +{ + AssertOnce( this->GetExpresser()->GetOuter() == this ); + CAI_Expresser * const RESTRICT pExpresser = this->GetExpresser(); + concept.SetSpeaker(this); + // add in any local criteria to the one passed on the command line. + pExpresser->GatherCriteria( pCriteria, concept, NULL ); + // call the "I have aleady gathered criteria" version of Expresser::Speak + return pExpresser->Speak( concept, pCriteria, pszOutResponseChosen, bufsize, filter ); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +template +inline int CAI_ExpresserHost::PlaySentence( const char *pszSentence, float delay, float volume, soundlevel_t soundlevel, CBaseEntity *pListener ) +{ + return this->GetExpresser()->SpeakRawSentence( pszSentence, delay, volume, soundlevel, pListener ); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +extern void CAI_ExpresserHost_NPC_DoModifyOrAppendCriteria( CAI_BaseNPC *pSpeaker, AI_CriteriaSet& criteriaSet ); + +template +inline void CAI_ExpresserHost::ModifyOrAppendCriteria( AI_CriteriaSet& criteriaSet ) +{ + BaseClass::ModifyOrAppendCriteria( criteriaSet ); + + + if ( this->MyNPCPointer() ) + { + CAI_ExpresserHost_NPC_DoModifyOrAppendCriteria( this->MyNPCPointer(), criteriaSet ); + } + +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +template +inline IResponseSystem *CAI_ExpresserHost::GetResponseSystem() +{ + extern IResponseSystem *g_pResponseSystem; + // Expressive NPC's use the general response system + return g_pResponseSystem; +} + + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +template +inline void CAI_ExpresserHost::GatherCriteria( AI_CriteriaSet *outputCriteria, const AIConcept_t &concept, const char *modifiers ) +{ + return this->GetExpresser()->GatherCriteria( outputCriteria, concept, modifiers ); +} + + +#if 1 +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +template +inline bool CAI_ExpresserHost::SpeakFindResponse(AI_Response& outResponse, const AIConcept_t &concept, const char *modifiers /*= NULL*/ ) +{ + AI_CriteriaSet criteria; + GatherCriteria(&criteria, concept, modifiers); + return FindResponse( outResponse, concept, &criteria ); +} +#else +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +template +inline AI_Response *CAI_ExpresserHost::SpeakFindResponse( const AIConcept_t &concept, const char *modifiers /*= NULL*/ ) +{ + return this->GetExpresser()->SpeakFindResponse( concept, modifiers ); +} +#endif + +#if 0 +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +template +inline AI_Response *CAI_ExpresserHost::SpeakFindResponse( const AIConcept_t &concept, AI_CriteriaSet *criteria /*= NULL*/ ) +{ + return this->GetExpresser()->SpeakFindResponse( concept, criteria ); +} + + +//----------------------------------------------------------------------------- +// In this case we clearly don't care to hang on to the criteria, so make a convenience +// class that generates a one off. +//----------------------------------------------------------------------------- +template +inline AI_Response * CAI_ExpresserHost::SpeakFindResponse( const AIConcept_t &concept ) +{ + AI_CriteriaSet criteria; + GatherCriteria( &criteria, concept, NULL ); + return this->GetExpresser()->SpeakFindResponse( concept, &criteria ); +} +#endif + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +template +inline bool CAI_ExpresserHost::FindResponse( AI_Response &outResponse, const AIConcept_t &concept, AI_CriteriaSet *criteria ) +{ + return this->GetExpresser()->FindResponse( outResponse, concept, criteria ); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +template +inline bool CAI_ExpresserHost::SpeakDispatchResponse( AIConcept_t concept, AI_Response *response, AI_CriteriaSet *criteria ) +{ + if ( this->GetExpresser()->SpeakDispatchResponse( concept, response, criteria ) ) + { + PostSpeakDispatchResponse( concept, response ); + return true; + } + + return false; +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +template +inline float CAI_ExpresserHost::GetResponseDuration( AI_Response *response ) +{ + return this->GetExpresser()->GetResponseDuration( response ); +} + +//----------------------------------------------------------------------------- +// Override of base entity response input handler +//----------------------------------------------------------------------------- +template +inline void CAI_ExpresserHost::DispatchResponse( const char *conceptName ) +{ + Speak( AIConcept_t( conceptName ) ); +} + +//----------------------------------------------------------------------------- + +/// A shim under CAI_ExpresserHost you can use when deriving a new expresser +/// host type under CAI_BaseNPC. This does the extra step of declaring an m_pExpresser +/// member and initializing it from CreateComponents(). If your BASE_NPC class isn't +/// actually an NPC, then CreateComponents() never gets called and you won't have +/// an expresser created. +/// Note: you still need to add m_pExpresser to the Datadesc for your derived type. +/// This is because I couldn't figure out how to make a templatized datadesc declaration +/// that works generically on the template type. +template +class CAI_ExpresserHostWithData : public CAI_ExpresserHost +{ + DECLARE_CLASS_NOFRIEND( CAI_ExpresserHostWithData, CAI_ExpresserHost ); + +public: + CAI_ExpresserHostWithData( ) : m_pExpresser(NULL) {}; + + virtual CAI_Expresser *GetExpresser() { return m_pExpresser; } + const CAI_Expresser *GetExpresser() const { return m_pExpresser; } + + virtual bool CreateComponents() + { + return BaseClass::CreateComponents() && ( CreateExpresser() != NULL ); + } + +protected: + EXPRESSER_TYPE *CreateExpresser( void ) + { + AssertMsg1( m_pExpresser == NULL, "Tried to double-initialize expresser in %s\n", this->GetDebugName() ); + m_pExpresser = new EXPRESSER_TYPE(this); + if ( !m_pExpresser) + { + AssertMsg1( false, "Creating an expresser failed in %s\n", this->GetDebugName() ); + return NULL; + } + + m_pExpresser->Connect(this); + return m_pExpresser; + } + + virtual ~CAI_ExpresserHostWithData( void ) + { + delete m_pExpresser; + m_pExpresser = NULL; + } + + EXPRESSER_TYPE *m_pExpresser; +}; + +/// response rules +namespace RR +{ + /// some applycontext clauses have operators preceding them, + /// like ++1 which means "take the current value and increment it + /// by one". These classes detect these cases and do the appropriate + /// thing. + class CApplyContextOperator + { + public: + inline CApplyContextOperator( int nSkipChars ) : m_nSkipChars(nSkipChars) {}; + + /// perform whatever this operator does upon the given context value. + /// Default op is simply to copy old to new. + /// pOldValue should be the currently set value of the context. May be NULL meaning no prior value. + /// pOperator the value that applycontext says to set + /// pNewValue a pointer to a buffer where the real new value will be writ. + /// returns true on success; false on failure (eg, tried to increment a + /// non-numeric value). + virtual bool Apply( const char *pOldValue, const char *pOperator, char *pNewValue, int pNewValBufSize ); + + /// This is the function that should be called from outside, + /// fed the input string, it'll select the right operator + /// to apply. + static CApplyContextOperator *FindOperator( const char *pContextString ); + + protected: + int m_nSkipChars; // how many chars to "skip" in the value string to get past the op specifier to the actual value + // eg, "++3" has a m_nSkipChars of 2, because the op string "++" is two characters. + }; + + class CIncrementOperator : public CApplyContextOperator + { + public: + inline CIncrementOperator( int nSkipChars ) : CApplyContextOperator(nSkipChars) {}; + virtual bool Apply( const char *pOldValue, const char *pOperator, char *pNewValue, int pNewValBufSize ); + }; + + class CDecrementOperator : public CApplyContextOperator + { + public: + inline CDecrementOperator( int nSkipChars ) : CApplyContextOperator(nSkipChars) {}; + virtual bool Apply( const char *pOldValue, const char *pOperator, char *pNewValue, int pNewValBufSize ); + }; + +#ifdef MAPBASE + class CMultiplyOperator : public CApplyContextOperator + { + public: + inline CMultiplyOperator( int nSkipChars ) : CApplyContextOperator(nSkipChars) {}; + virtual bool Apply( const char *pOldValue, const char *pOperator, char *pNewValue, int pNewValBufSize ); + }; + + class CDivideOperator : public CApplyContextOperator + { + public: + inline CDivideOperator( int nSkipChars ) : CApplyContextOperator(nSkipChars) {}; + virtual bool Apply( const char *pOldValue, const char *pOperator, char *pNewValue, int pNewValBufSize ); + }; +#endif + + class CToggleOperator : public CApplyContextOperator + { + public: + inline CToggleOperator( int nSkipChars ) : CApplyContextOperator(nSkipChars) {}; + virtual bool Apply( const char *pOldValue, const char *pOperator, char *pNewValue, int pNewValBufSize ); + }; + + // the singleton operators + extern CApplyContextOperator sm_OpCopy; + extern CIncrementOperator sm_OpIncrement; + extern CDecrementOperator sm_OpDecrement; +#ifdef MAPBASE + extern CMultiplyOperator sm_OpMultiply; + extern CDivideOperator sm_OpDivide; +#endif + extern CToggleOperator sm_OpToggle; + +#ifdef MAPBASE + // LEGACY - See CApplyContextOperator::FindOperator() + extern CIncrementOperator sm_OpLegacyIncrement; + extern CDecrementOperator sm_OpLegacyDecrement; + extern CMultiplyOperator sm_OpLegacyMultiply; + extern CDivideOperator sm_OpLegacyDivide; +#endif +}; + + +//----------------------------------------------------------------------------- +#include "ai_speechqueue.h" + +//----------------------------------------------------------------------------- +// A kind of AI Expresser that can dispatch a follow-up speech event when it +// finishes speaking. +//----------------------------------------------------------------------------- +class CAI_ExpresserWithFollowup : public CAI_Expresser +{ +public: + CAI_ExpresserWithFollowup( CBaseFlex *pOuter = NULL ) : CAI_Expresser(pOuter), + m_pPostponedFollowup(NULL) + {}; + virtual bool Speak( AIConcept_t concept, const char *modifiers = NULL, char *pszOutResponseChosen = NULL, size_t bufsize = 0, IRecipientFilter *filter = NULL ); + virtual bool SpeakDispatchResponse( AIConcept_t concept, AI_Response *response, AI_CriteriaSet *criteria, IRecipientFilter *filter = NULL ); + virtual void SpeakDispatchFollowup( AI_ResponseFollowup &followup ); + + virtual void OnSpeechFinished(); + + typedef CAI_Expresser BaseClass; +protected: + static void DispatchFollowupThroughQueue( const AIConcept_t &concept, + const char *criteriaStr, + const CResponseQueue::CFollowupTargetSpec_t &target, + float delay, + CBaseEntity * RESTRICT pOuter ); + + AI_ResponseFollowup *m_pPostponedFollowup; // TODO: save/restore + CResponseQueue::CFollowupTargetSpec_t m_followupTarget; +}; + +class CMultiplayer_Expresser : public CAI_ExpresserWithFollowup +{ +public: + CMultiplayer_Expresser( CBaseFlex *pOuter = NULL ); + //~CMultiplayer_Expresser(); + + virtual bool IsSpeaking(); + + void AllowMultipleScenes(); + void DisallowMultipleScenes(); + +private: + bool m_bAllowMultipleScenes; + +}; + + +#endif // AI_SPEECH_H diff --git a/src/game/server/ai_speechqueue.cpp b/src/game/server/ai_speechqueue.cpp new file mode 100644 index 00000000..58fccde6 --- /dev/null +++ b/src/game/server/ai_speechqueue.cpp @@ -0,0 +1,559 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" + +#include "basemultiplayerplayer.h" +#include "ai_baseactor.h" +#include "ai_speech.h" +//#include "flex_expresser.h" +#ifdef MAPBASE +#include "sceneentity.h" +#endif +// memdbgon must be the last include file in a .cpp file!!! +#include + +extern ConVar ai_debug_speech; +#define DebuggingSpeech() ai_debug_speech.GetBool() +extern ConVar rr_debugresponses; + +ConVar rr_followup_maxdist( "rr_followup_maxdist", "1800", FCVAR_CHEAT, "'then ANY' or 'then ALL' response followups will be dispatched only to characters within this distance." ); + +/////////////////////////////////////////////////////////////////////////////// +// RESPONSE QUEUE DATA STRUCTURE +/////////////////////////////////////////////////////////////////////////////// + +CResponseQueue::CResponseQueue( int queueSize ) : m_Queue(queueSize), m_ExpresserTargets(8,8) +{}; + +/// Add a deferred response. +void CResponseQueue::Add( const AIConcept_t &concept, ///< concept to dispatch + const AI_CriteriaSet * RESTRICT contexts, + float time, ///< when to dispatch it. You can specify a time of zero to mean "immediately." + const CFollowupTargetSpec_t &targetspec, + CBaseEntity *pIssuer + ) +{ + // Add a response. + AssertMsg( m_Queue.Count() < AI_RESPONSE_QUEUE_SIZE, "AI Response queue overfilled." ); + QueueType_t::IndexLocalType_t idx = m_Queue.AddToTail(); + m_Queue[idx].Init( concept, contexts, time, targetspec, pIssuer ); +} + + +/// Remove a deferred response matching the concept and issuer. +void CResponseQueue::Remove( const AIConcept_t &concept, ///< concept to dispatch + CBaseEntity * const RESTRICT pIssuer ///< the entity issuing the response, if one exists. + ) RESTRICT +{ + // walk through the queue until we find a response matching the concept and issuer, then strike it. + QueueType_t::IndexLocalType_t idx = m_Queue.Head(); + while (idx != m_Queue.InvalidIndex()) + { + CDeferredResponse &response = m_Queue[idx]; + QueueType_t::IndexLocalType_t previdx = idx; // advance the index immediately because we may be deleting the "current" element + idx = m_Queue.Next(idx); // is now the next index + if ( CompareConcepts( response.m_concept, concept ) && // if concepts match and + ( !pIssuer || ( response.m_hIssuer.Get() == pIssuer ) ) // issuer is null, or matches the one in the response + ) + { + m_Queue.Remove(previdx); + } + } +} + + +void CResponseQueue::RemoveSpeechQueuedFor( const CBaseEntity *pSpeaker ) +{ + // walk through the queue until we find a response matching the speaker, then strike it. + // because responses are dispatched from inside a loop that is already walking through the + // queue, it's not safe to actually remove the elements. Instead, quash it by replacing it + // with a null event. + + for ( QueueType_t::IndexLocalType_t idx = m_Queue.Head() ; + idx != m_Queue.InvalidIndex() ; + idx = m_Queue.Next(idx) ) // is now the next index + { + CDeferredResponse &response = m_Queue[idx]; + if ( response.m_Target.m_hHandle.Get() == pSpeaker ) + { + response.Quash(); + } + } +} + +// TODO: use a more compact representation. +void CResponseQueue::DeferContextsFromCriteriaSet( DeferredContexts_t &contextsOut, const AI_CriteriaSet * RESTRICT criteriaIn ) +{ + contextsOut.Reset(); + if (criteriaIn) + { + contextsOut.Merge(criteriaIn); + } +} + +void CResponseQueue::PerFrameDispatch() +{ +failsafe: + // Walk through the list, find any messages whose time has come, and dispatch them. Then remove them. + QueueType_t::IndexLocalType_t idx = m_Queue.Head(); + while (idx != m_Queue.InvalidIndex()) + { + // do we need to dispatch this concept? + CDeferredResponse &response = m_Queue[idx]; + QueueType_t::IndexLocalType_t previdx = idx; // advance the index immediately because we may be deleting the "current" element + idx = m_Queue.Next(idx); // is now the next index + + if ( response.IsQuashed() ) + { + // we can delete this entry now + m_Queue.Remove(previdx); + } + else if ( response.m_fDispatchTime <= gpGlobals->curtime ) + { + // dispatch. we've had bugs where dispatches removed things from inside the queue; + // so, as a failsafe, if the queue length changes as a result, start over. + int oldLength = m_Queue.Count(); + DispatchOneResponse(response); + if ( m_Queue.Count() < oldLength ) + { + AssertMsg( false, "Response queue length changed in non-reentrant way! FAILSAFE TRIGGERED" ); + goto failsafe; // ick + } + + // we can delete this entry now + m_Queue.Remove(previdx); + } + } +} + + +/// Add an expressor owner to this queue. +void CResponseQueue::AddExpresserHost(CBaseEntity *host) +{ + EHANDLE ehost(host); + // see if it's in there already + if (m_ExpresserTargets.HasElement(ehost)) + { + AssertMsg1(false, "Tried to add %s to response queue when it was already in there.", host->GetDebugName()); + } + else + { + // zip through the queue front to back, first see if there's any invalid handles to replace + int count = m_ExpresserTargets.Count(); + for (int i = 0 ; i < count ; ++i ) + { + if ( !m_ExpresserTargets[i].Get() ) + { + m_ExpresserTargets[i] = ehost; + return; + } + } + + // if we're down here we didn't find one to replace, so append the host to the end. + m_ExpresserTargets.AddToTail(ehost); + } +} + +/// Remove an expresser host from this queue. +void CResponseQueue::RemoveExpresserHost(CBaseEntity *host) +{ + int idx = m_ExpresserTargets.Find(host); + if (idx == -1) + { + // AssertMsg1(false, "Tried to remove %s from response queue, but it's not in there to begin with!", host->GetDebugName() ); + } + else + { + m_ExpresserTargets.FastRemove(idx); + } +} + +#ifdef MAPBASE +/// Get the expresser for a base entity. +static CAI_Expresser *InferExpresserFromBaseEntity(CBaseEntity * RESTRICT pEnt, CAI_ExpresserSink **ppSink = NULL) +{ + if ( CAI_ExpresserSink *pSink = dynamic_cast(pEnt) ) + { + if (ppSink) + *ppSink = pSink; + return pSink->GetSinkExpresser(); + } + + return NULL; +} +#else +/// Get the expresser for a base entity. +/// TODO: Kind of an ugly hack until I get the class hierarchy straightened out. +static CAI_Expresser *InferExpresserFromBaseEntity(CBaseEntity * RESTRICT pEnt) +{ + if ( CBaseMultiplayerPlayer *pPlayer = dynamic_cast(pEnt) ) + { + return pPlayer->GetExpresser(); + } + else if ( CAI_BaseActor *pActor = dynamic_cast(pEnt) ) + { + return pActor->GetExpresser(); + } + /* + else if ( CFlexExpresser *pFlex = dynamic_cast(pEnt) ) + { + return pFlex->GetExpresser(); + } + */ + else + { + return NULL; + } +} +#endif + + +void CResponseQueue::CDeferredResponse::Quash() +{ + m_Target = CFollowupTargetSpec_t(); + m_fDispatchTime = 0; +} + +#ifdef MAPBASE +void CResponseQueue::AppendFollowupCriteria( AIConcept_t concept, AI_CriteriaSet &set, CAI_Expresser *pEx, + CAI_ExpresserSink *pSink, CBaseEntity *pTarget, CBaseEntity *pIssuer, DeferredResponseTarget_t nTargetType ) +{ + // Allows control over which followups interrupt speech routines + set.AppendCriteria( "followup_allowed_to_speak", (pSink->IsAllowedToSpeakFollowup( concept, pIssuer, nTargetType == kDRT_SPECIFIC )) ? "1" : "0" ); + + set.AppendCriteria( "followup_target_type", UTIL_VarArgs( "%i", (int)nTargetType ) ); + + // NOTE: This assumes any expresser entity derived from CBaseFlex is also derived from CBaseCombatCharacter + if (pTarget->IsCombatCharacter()) + set.AppendCriteria( "is_speaking", (pEx->IsSpeaking() || IsRunningScriptedSceneWithSpeechAndNotPaused( assert_cast(pTarget) )) ? "1" : "0" ); + else + set.AppendCriteria( "is_speaking", "0" ); +} +#endif + +bool CResponseQueue::DispatchOneResponse(CDeferredResponse &response) +{ + // find the target. + CBaseEntity * RESTRICT pTarget = NULL; + AI_CriteriaSet &deferredCriteria = response.m_contexts; + CAI_Expresser * RESTRICT pEx = NULL; + CBaseEntity * RESTRICT pIssuer = response.m_hIssuer.Get(); // MAY BE NULL + float followupMaxDistSq; + { + /* + CFlexExpresser * RESTRICT pOrator = CFlexExpresser::AsFlexExpresser( pIssuer ); + if ( pOrator ) + { + // max dist is overridden. "0" means infinite distance (for orators only), + // anything else is a finite distance. + if ( pOrator->m_flThenAnyMaxDist > 0 ) + { + followupMaxDistSq = pOrator->m_flThenAnyMaxDist * pOrator->m_flThenAnyMaxDist; + } + else + { + followupMaxDistSq = FLT_MAX; + } + + } + else + */ + { + followupMaxDistSq = rr_followup_maxdist.GetFloat(); // square of max audibility distance + followupMaxDistSq *= followupMaxDistSq; + } + } + + switch (response.m_Target.m_iTargetType) + { + case kDRT_SPECIFIC: + { + pTarget = response.m_Target.m_hHandle.Get(); + } + break; + case kDRT_ANY: + { + return DispatchOneResponse_ThenANY( response, &deferredCriteria, pIssuer, followupMaxDistSq ); + } + break; + case kDRT_ALL: + { + bool bSaidAnything = false; + Vector issuerLocation; + if ( pIssuer ) + { + issuerLocation = pIssuer->GetAbsOrigin(); + } + + // find all characters + int numExprs = GetNumExpresserTargets(); + for ( int i = 0 ; i < numExprs; ++i ) + { + pTarget = GetExpresserHost(i); + float distIssuerToTargetSq = 0.0f; + if ( pIssuer ) + { + distIssuerToTargetSq = (pTarget->GetAbsOrigin() - issuerLocation).LengthSqr(); + if ( distIssuerToTargetSq > followupMaxDistSq ) + continue; // too far + } + +#ifdef MAPBASE + CAI_ExpresserSink *pSink = NULL; + pEx = InferExpresserFromBaseEntity( pTarget, &pSink ); +#else + pEx = InferExpresserFromBaseEntity(pTarget); +#endif + if ( !pEx || pTarget == pIssuer ) + continue; + + AI_CriteriaSet characterCriteria; + pEx->GatherCriteria(&characterCriteria, response.m_concept, NULL); + characterCriteria.Merge(&deferredCriteria); + if ( pIssuer ) + { + characterCriteria.AppendCriteria( "dist_from_issuer", UTIL_VarArgs( "%f", sqrt(distIssuerToTargetSq) ) ); + } + +#ifdef MAPBASE + AppendFollowupCriteria( response.m_concept, characterCriteria, pEx, pSink, pTarget, pIssuer, kDRT_ALL ); +#endif + + AI_Response prospectiveResponse; + if ( pEx->FindResponse( prospectiveResponse, response.m_concept, &characterCriteria ) ) + { + // dispatch it + bSaidAnything = pEx->SpeakDispatchResponse(response.m_concept, &prospectiveResponse, &deferredCriteria) || bSaidAnything ; + } + } + + return bSaidAnything; + + } + break; + default: + // WTF? + AssertMsg1( false, "Unknown deferred response type %d\n", response.m_Target.m_iTargetType ); + return false; + } + + if (!pTarget) + return false; // we're done right here. + + // Get the expresser for the target. +#ifdef MAPBASE + CAI_ExpresserSink *pSink = NULL; + pEx = InferExpresserFromBaseEntity( pTarget, &pSink ); +#else + pEx = InferExpresserFromBaseEntity(pTarget); +#endif + if (!pEx) + return false; + + AI_CriteriaSet characterCriteria; + pEx->GatherCriteria(&characterCriteria, response.m_concept, NULL); + characterCriteria.Merge(&deferredCriteria); +#ifdef MAPBASE + if ( pIssuer ) + { + characterCriteria.AppendCriteria( "dist_from_issuer", UTIL_VarArgs( "%f", (pTarget->GetAbsOrigin() - pIssuer->GetAbsOrigin()).Length() ) ); + } + + AppendFollowupCriteria( response.m_concept, characterCriteria, pEx, pSink, pTarget, pIssuer, kDRT_SPECIFIC ); +#endif + pEx->Speak( response.m_concept, &characterCriteria ); + + return true; +} + +// +ConVar rr_thenany_score_slop( "rr_thenany_score_slop", "0.0", FCVAR_CHEAT, "When computing respondents for a 'THEN ANY' rule, all rule-matching scores within this much of the best score will be considered." ); +#define EXARRAYMAX 32 // maximum number of prospective expressers in the array (hardcoded for simplicity) +bool CResponseQueue::DispatchOneResponse_ThenANY( CDeferredResponse &response, AI_CriteriaSet * RESTRICT pDeferredCriteria, CBaseEntity * const RESTRICT pIssuer, float followupMaxDistSq ) +{ + CBaseEntity * RESTRICT pTarget = NULL; + CAI_Expresser * RESTRICT pEx = NULL; + float bestScore = 0; + float slop = rr_thenany_score_slop.GetFloat(); + Vector issuerLocation; + if ( pIssuer ) + { + issuerLocation = pIssuer->GetAbsOrigin(); + } + + // this is an array of prospective respondents. + CAI_Expresser * RESTRICT pBestEx[EXARRAYMAX]; + AI_Response responseToSay[EXARRAYMAX]; + int numExFound = 0; // and this is the high water mark for the array. + + // Here's the algorithm: we're going to walk through all the characters, finding the + // highest scoring ones for this rule. Let the highest score be called k. + // Because there may be (n) many characters all scoring k, we store an array of + // all characters with score k, then choose randomly from that array at return. + // We also define an allowable error for k in the global cvar + // rr_thenany_score_slop , which may be zero. + + // find all characters (except the issuer) + int numExprs = GetNumExpresserTargets(); + AssertMsg1( numExprs <= EXARRAYMAX, "Response queue has %d possible expresser targets, please increase EXARRAYMAX ", numExprs ); + for ( int i = 0 ; i < numExprs; ++i ) + { + pTarget = GetExpresserHost(i); + if ( pTarget == pIssuer ) + continue; // don't dispatch to myself + + if ( !pTarget->IsAlive() ) + continue; // dead men tell no tales + + float distIssuerToTargetSq = 0.0f; + if ( pIssuer ) + { + distIssuerToTargetSq = (pTarget->GetAbsOrigin() - issuerLocation).LengthSqr(); + if ( distIssuerToTargetSq > followupMaxDistSq ) + continue; // too far + } + +#ifdef MAPBASE + CAI_ExpresserSink *pSink = NULL; + pEx = InferExpresserFromBaseEntity( pTarget, &pSink ); +#else + pEx = InferExpresserFromBaseEntity(pTarget); +#endif + if ( !pEx ) + continue; + + AI_CriteriaSet characterCriteria; + pEx->GatherCriteria(&characterCriteria, response.m_concept, NULL); + characterCriteria.Merge( pDeferredCriteria ); + pTarget->ModifyOrAppendDerivedCriteria( characterCriteria ); + if ( pIssuer ) + { + characterCriteria.AppendCriteria( "dist_from_issuer", UTIL_VarArgs( "%f", sqrt(distIssuerToTargetSq) ) ); + } + +#ifdef MAPBASE + AppendFollowupCriteria( response.m_concept, characterCriteria, pEx, pSink, pTarget, pIssuer, kDRT_ANY ); +#endif + + AI_Response prospectiveResponse; + +#ifdef MAPBASE + pEx->SetUsingProspectiveResponses( true ); +#endif + + if ( pEx->FindResponse( prospectiveResponse, response.m_concept, &characterCriteria ) ) + { + float score = prospectiveResponse.GetMatchScore(); + if ( score > 0 && !prospectiveResponse.IsEmpty() ) // ignore scores that are zero, regardless of slop + { + // if this score is better than all we've seen (outside the slop), then replace the array with + // an entry just to this expresser + if ( score > bestScore + slop ) + { + responseToSay[0] = prospectiveResponse; + pBestEx[0] = pEx; + bestScore = score; + numExFound = 1; + } + else if ( score >= bestScore - slop ) // if this score is at least as good as the best we've seen, but not better than all + { + if ( numExFound >= EXARRAYMAX ) + { +#ifdef MAPBASE + pEx->SetUsingProspectiveResponses( false ); +#endif + continue; // SAFETY: don't overflow the array + } + + responseToSay[numExFound] = prospectiveResponse; + pBestEx[numExFound] = pEx; + bestScore = fpmax( score, bestScore ); + numExFound += 1; + } + } + } + +#ifdef MAPBASE + pEx->SetUsingProspectiveResponses( false ); +#endif + } + + // if I have a response, dispatch it. + if ( numExFound > 0 ) + { + // get a random number between 0 and the responses found + int iSelect = numExFound > 1 ? RandomInt( 0, numExFound - 1 ) : 0; + + if ( pBestEx[iSelect] != NULL ) + { +#ifdef MAPBASE + pBestEx[iSelect]->MarkResponseAsUsed( responseToSay + iSelect ); +#endif + return pBestEx[iSelect]->SpeakDispatchResponse( response.m_concept, responseToSay + iSelect, pDeferredCriteria ); + } + else + { + AssertMsg( false, "Response queue somehow found a response, but no expresser for it.\n" ); + return false; + } + } + else + { // I did not find a response. + return false; + } + + return false; // just in case +} + +void CResponseQueue::Evacuate() +{ + m_Queue.RemoveAll(); +} + +#undef EXARRAYMAX + + +/////////////////////////////////////////////////////////////////////////////// +// RESPONSE QUEUE MANAGER +/////////////////////////////////////////////////////////////////////////////// + + +void CResponseQueueManager::LevelInitPreEntity( void ) +{ + if (m_pQueue == NULL) + { + m_pQueue = new CResponseQueue(AI_RESPONSE_QUEUE_SIZE); + } +} + +CResponseQueueManager::~CResponseQueueManager() +{ + if (m_pQueue != NULL) + { + delete m_pQueue; + m_pQueue = NULL; + } +} + +void CResponseQueueManager::Shutdown() +{ + if (m_pQueue != NULL) + { + delete m_pQueue; + m_pQueue = NULL; + } +} + +void CResponseQueueManager::FrameUpdatePostEntityThink() +{ + Assert(m_pQueue); + m_pQueue->PerFrameDispatch(); +} + +CResponseQueueManager g_ResponseQueueManager( "CResponseQueueManager" ); + diff --git a/src/game/server/ai_speechqueue.h b/src/game/server/ai_speechqueue.h new file mode 100644 index 00000000..87ab064f --- /dev/null +++ b/src/game/server/ai_speechqueue.h @@ -0,0 +1,244 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: An event queue of AI concepts that dispatches them to appropriate characters. +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef AI_SPEECHQUEUE_H +#define AI_SPEECHQUEUE_H + +#if defined( _WIN32 ) +#pragma once +#endif + +#include "ai_speech.h" + +#define AI_RESPONSE_QUEUE_SIZE 64 + +enum DeferredResponseTarget_t // possible targets for a deferred response +{ + kDRT_ANY, // best matching respondent within range -- except for the one in the m_hTarget handle + kDRT_ALL, // send to everyone in range -- except for the one in the m_hTarget handle + kDRT_SPECIFIC, // a specific entity is targeted + + kDRT_MAX, // high water mark +}; + +// Allows you to postpone AI speech concepts to a later time, or to direct them to +// a specific character, or all of them. +class CResponseQueue +{ + //////////////////// Local types //////////////////// +public: + + // We pack up contexts to send along with the concept. + // For now I'll just copy criteria sets, but it will be better to do something + // more efficient in the future. + typedef AI_CriteriaSet DeferredContexts_t; + + struct CFollowupTargetSpec_t ///< to whom a followup is directed. Can be a specific entity or something more exotic. + { + DeferredResponseTarget_t m_iTargetType; ///< ANY, ALL, or SPECIFIC. If specific, pass through a handle to: + EHANDLE m_hHandle; ///< a specific target for the message, or a specific character to OMIT. + inline bool IsValid( void ) const; + + // constructors/destructors + explicit CFollowupTargetSpec_t(const DeferredResponseTarget_t &targetType, const EHANDLE &handle) + : m_iTargetType(targetType), m_hHandle(handle) + {}; + explicit CFollowupTargetSpec_t(const EHANDLE &handle) + : m_iTargetType(kDRT_SPECIFIC), m_hHandle(handle) + {}; + CFollowupTargetSpec_t(DeferredResponseTarget_t target) // eg, ANY, ALL, etc. + : m_iTargetType(target) + { + AssertMsg(m_iTargetType != kDRT_SPECIFIC, "Response rule followup tried to specify an entity target, but didn't provide the target.\n" ); + } + CFollowupTargetSpec_t(void) // default: invalid + : m_iTargetType(kDRT_MAX) + {}; + }; + + /// A single deferred response. + struct CDeferredResponse + { + AIConcept_t m_concept; + DeferredContexts_t m_contexts; ///< contexts to send along with the concept + float m_fDispatchTime; + EHANDLE m_hIssuer; ///< an entity, if issued by an entity + /* + DeferredResponseTarget_t m_iTargetType; + EHANDLE m_hTarget; // May be invalid. + */ + CFollowupTargetSpec_t m_Target; + + inline void Init( const AIConcept_t &concept, const AI_CriteriaSet * RESTRICT contexts, float dtime, const CFollowupTargetSpec_t &target, CBaseEntity *pIssuer ); + inline bool IsQuashed() { return !m_Target.IsValid(); } + void Quash(); ///< make this response invalid. + }; + /// write + static void DeferContextsFromCriteriaSet( DeferredContexts_t &contextsOut, const AI_CriteriaSet *criteriaIn ); + + //////////////////// Methods //////////////////// +public: + CResponseQueue( int queueSize ); + + /// Add a deferred response. + void Add( const AIConcept_t &concept, ///< concept to dispatch + const AI_CriteriaSet * RESTRICT contexts, ///< the contexts that come with it (may be NULL) + float time, ///< when to dispatch it. You can specify a time of zero to mean "immediately." + const CFollowupTargetSpec_t &targetspec, /// All information necessary to target this response + CBaseEntity *pIssuer = NULL ///< the entity who should not respond if this is a ANY or ALL rule. (eg, don't let people talk to themselves.) + ); + + /// Remove all deferred responses matching the concept and issuer. + void Remove( const AIConcept_t &concept, ///< concept to dispatch + CBaseEntity * const pIssuer = NULL ///< the entity issuing the response, if one exists. + ); + + /// Remove all deferred responses queued to be spoken by given character + void RemoveSpeechQueuedFor( const CBaseEntity *pSpeaker ); + + /// Empty out all pending events + void Evacuate(); + + /// Go through and dispatch any deferred responses. + void PerFrameDispatch(); + + /// Add an expressor owner to this queue. + void AddExpresserHost(CBaseEntity *host); + + /// Remove an expresser host from this queue. + void RemoveExpresserHost(CBaseEntity *host); + + /// Iterate over potential expressers for this queue + inline int GetNumExpresserTargets() const; + inline CBaseEntity *GetExpresserHost(int which) const; + +#ifdef MAPBASE + void AppendFollowupCriteria( AIConcept_t concept, AI_CriteriaSet &set, CAI_Expresser *pEx, + CAI_ExpresserSink *pSink, CBaseEntity *pTarget, CBaseEntity *pIssuer, DeferredResponseTarget_t nTargetType ); +#endif + +protected: + /// Actually send off one response to a consumer + /// Return true if dispatch succeeded + bool DispatchOneResponse( CDeferredResponse &response ); + +private: + /// Helper function for one case in DispatchOneResponse + /// (for better organization) + bool DispatchOneResponse_ThenANY( CDeferredResponse &response, AI_CriteriaSet * RESTRICT pDeferredCriteria, CBaseEntity * const RESTRICT pIssuer, float followupMaxDistSq ); + + //////////////////// Data //////////////////// +protected: + typedef CUtlFixedLinkedList< CDeferredResponse > QueueType_t; + QueueType_t m_Queue; // the queue of deferred responses, will eventually be sorted + /// Note about the queue type: if you move to replace it with a sorted priority queue, + /// make sure it is a type such that an iterator is not invalidated by inserts and deletes. + /// CResponseQueue::PerFrameDispatch() iterates over the queue calling DispatchOneResponse + /// on each in turn, and those responses may very easily add new events to the queue. + /// A crash will result if the iterator used in CResponseQueue::PerFrameDispatch()'s loop + /// becomes invalid. + + CUtlVector m_ExpresserTargets; // a list of legitimate expresser targets +}; + +inline void CResponseQueue::CDeferredResponse::Init(const AIConcept_t &concept, const AI_CriteriaSet * RESTRICT contexts, float dtime, const CFollowupTargetSpec_t &target, CBaseEntity *pIssuer ) +{ + m_concept = concept; + m_fDispatchTime = dtime; + /* + m_iTargetType = targetType; + m_hTarget = handle ; + */ + m_Target = target; + m_hIssuer = pIssuer; + DeferContextsFromCriteriaSet(m_contexts, contexts); +} + +int CResponseQueue::GetNumExpresserTargets() const +{ + return m_ExpresserTargets.Count(); +} + +CBaseEntity *CResponseQueue::GetExpresserHost(int which) const +{ + return m_ExpresserTargets[which]; +} + + +// The wrapper game system that contains a response queue, and ticks it each frame. + +class CResponseQueueManager : public CAutoGameSystemPerFrame +{ +public: + CResponseQueueManager(char const *name) : CAutoGameSystemPerFrame( name ) + { + m_pQueue = NULL; + } + virtual ~CResponseQueueManager(void); + virtual void Shutdown(); + virtual void FrameUpdatePostEntityThink( void ); + virtual void LevelInitPreEntity( void ); + + inline CResponseQueue *GetQueue(void) { Assert(m_pQueue); return m_pQueue; } + +protected: + CResponseQueue *m_pQueue; +}; + + +// Valid if the target type enum is within bounds. Furthermore if it +// specifies a specific entity, that handle must be valid. +bool CResponseQueue::CFollowupTargetSpec_t::IsValid( void ) const +{ + if (m_iTargetType >= kDRT_MAX) + return false; + if (m_iTargetType < 0) + return false; + if (m_iTargetType == kDRT_SPECIFIC && !m_hHandle.IsValid()) + return false; + + return true; +} + +extern CResponseQueueManager g_ResponseQueueManager; + + +// Handy global helper funcs + +/// Automatically queue up speech to happen immediately -- calls straight through to response rules add +inline void QueueSpeak( const AIConcept_t &concept, ///< concept name to say + const CResponseQueue::CFollowupTargetSpec_t& targetspec, ///< kDRT_ANY, kDRT_ALL, etc + CBaseEntity *pIssuer = NULL ///< if specifying ANY or ALL, use this to specify the one you *don't* want to speak + ) +{ + return g_ResponseQueueManager.GetQueue()->Add( concept, NULL, 0.0f, targetspec, pIssuer ); +} + +/// Automatically queue up speech to happen immediately -- calls straight through to response rules add +inline void QueueSpeak( const AIConcept_t &concept, ///< concept name to say + const CResponseQueue::CFollowupTargetSpec_t& targetspec, ///< kDRT_ANY, kDRT_ALL, etc + const AI_CriteriaSet &criteria, ///< criteria to pass in + CBaseEntity *pIssuer = NULL ///< if specifying ANY or ALL, use this to specify the one you *don't* want to speak + ) +{ + return g_ResponseQueueManager.GetQueue()->Add( concept, &criteria, 0.0f, targetspec, pIssuer ); +} + +/// Automatically queue up speech to happen immediately -- calls straight through to response rules add +inline void QueueSpeak( const AIConcept_t &concept, ///< concept name to say + const EHANDLE &target, ///< which entity shall speak + float delay, ///< how far in the future to speak + const AI_CriteriaSet &criteria, ///< criteria to pass in + CBaseEntity *pIssuer = NULL ) +{ + return g_ResponseQueueManager.GetQueue()->Add( concept, &criteria, gpGlobals->curtime + delay, + CResponseQueue::CFollowupTargetSpec_t(target), pIssuer ); +} + + + +#endif // AI_SPEECHQUEUE_H diff --git a/src/game/server/ai_squad.cpp b/src/game/server/ai_squad.cpp index dc8bed0c..7bec62e1 100644 --- a/src/game/server/ai_squad.cpp +++ b/src/game/server/ai_squad.cpp @@ -114,6 +114,48 @@ void CAI_SquadManager::DeleteAllSquads(void) CAI_SquadManager::m_pSquads = NULL; } +#ifdef MAPBASE_VSCRIPT +//------------------------------------- +// Purpose: +//------------------------------------- +HSCRIPT CAI_SquadManager::ScriptGetFirstSquad() +{ + return m_pSquads ? g_pScriptVM->RegisterInstance( m_pSquads ) : NULL; +} + +HSCRIPT CAI_SquadManager::ScriptGetNextSquad( HSCRIPT hStart ) +{ + CAI_Squad *pSquad = HScriptToClass( hStart ); + return (pSquad && pSquad->m_pNextSquad) ? g_pScriptVM->RegisterInstance( pSquad->m_pNextSquad ) : NULL; +} + +//------------------------------------- +// Purpose: +//------------------------------------- +HSCRIPT CAI_SquadManager::ScriptFindSquad( const char *squadName ) +{ + CAI_Squad *pSquad = FindSquad( MAKE_STRING(squadName) ); + return pSquad ? g_pScriptVM->RegisterInstance( pSquad ) : NULL; +} + +HSCRIPT CAI_SquadManager::ScriptFindCreateSquad( const char *squadName ) +{ + CAI_Squad *pSquad = FindCreateSquad( MAKE_STRING( squadName ) ); + return pSquad ? g_pScriptVM->RegisterInstance( pSquad ) : NULL; +} + +BEGIN_SCRIPTDESC_ROOT( CAI_SquadManager, SCRIPT_SINGLETON "Manager for NPC squads." ) + + DEFINE_SCRIPTFUNC_NAMED( ScriptGetFirstSquad, "GetFirstSquad", "Get the first squad in the squad list." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptGetNextSquad, "GetNextSquad", "Get the next squad in the squad list starting from the specified squad." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptFindSquad, "FindSquad", "Find the specified squad in the squad list. Returns null if none found." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptFindCreateSquad, "FindCreateSquad", "Find the specified squad in the squad list or create it if it doesn't exist." ) + + DEFINE_SCRIPTFUNC( NumSquads, "Get the number of squads in the list." ) + +END_SCRIPTDESC(); +#endif + //----------------------------------------------------------------------------- // CAI_Squad // @@ -151,6 +193,38 @@ BEGIN_SIMPLE_DATADESC( CAI_Squad ) END_DATADESC() +#ifdef MAPBASE_VSCRIPT +BEGIN_SCRIPTDESC_ROOT( CAI_Squad, "NPC squads used for schedule coordination, sharing information about enemies, etc." ) + + DEFINE_SCRIPTFUNC( GetName, "Get the squad's name." ) + + DEFINE_SCRIPTFUNC_NAMED( ScriptGetFirstMember, "GetFirstMember", "Get the squad's first member. The parameter is for whether to ignore silent members (see CAI_Squad::IsSilentMember() for more info)." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptGetMember, "GetMember", "Get one of the squad's members by their index." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptGetAnyMember, "GetAnyMember", "Randomly get any one of the squad's members." ) + DEFINE_SCRIPTFUNC( NumMembers, "Get the squad's number of members. The parameter is for whether to ignore silent members (see CAI_Squad::IsSilentMember() for more info)." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptGetSquadIndex, "GetSquadIndex", "Get the index of the specified NPC in the squad." ) + + DEFINE_SCRIPTFUNC_NAMED( ScriptUpdateEnemyMemory, "UpdateEnemyMemory", "Updates the squad's memory of an enemy. The first parameter is the updater, the second parameter is the enemy, and the third parameter is the position." ) + + DEFINE_SCRIPTFUNC_NAMED( ScriptSquadMemberInRange, "SquadMemberInRange", "Get the first squad member found around the specified position in the specified range." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptNearestSquadMember, "NearestSquadMember", "Get the squad member nearest to the specified member." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptGetVisibleSquadMembers, "GetVisibleSquadMembers", "Get the number of squad members visible to the specified member." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptGetSquadMemberNearestTo, "GetSquadMemberNearestTo", "Get the squad member nearest to a point." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptIsMember, "IsMember", "Returns true if the specified NPC is a member of the squad." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptIsLeader, "IsLeader", "Returns true if the specified NPC is the squad's leader." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptGetLeader, "GetLeader", "Get the squad's leader." ) + + DEFINE_SCRIPTFUNC_NAMED( ScriptAddToSquad, "AddToSquad", "Adds a NPC to the squad." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptRemoveFromSquad, "RemoveFromSquad", "Removes a NPC from the squad." ) + + DEFINE_SCRIPTFUNC_NAMED( ScriptIsSilentMember, "IsSilentMember", "Returns true if the specified NPC is a \"silent squad member\", which means it's only in squads for enemy information purposes and does not actually participate in any tactics. For example, this is used for npc_enemyfinder and vital allies (e.g. Alyx) in the player's squad. Please note that this does not check if the NPC is in the squad first." ) + + DEFINE_SCRIPTFUNC_NAMED( ScriptSetSquadData, "SetSquadData", "Set the squad data in the specified slot." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptGetSquadData, "GetSquadData", "Get the squad data in the specified slot." ) + +END_SCRIPTDESC(); +#endif + //------------------------------------- CAI_Squad::CAI_Squad(string_t newName) @@ -220,7 +294,7 @@ void CAI_Squad::RemoveFromSquad( CAI_BaseNPC *pNPC, bool bDeath ) int myIndex = m_SquadMembers.Find(pNPC); if (myIndex == -1) { - DevMsg("ERROR: Attempting to remove non-existing squad membmer!\n"); + CGMsg( 1, CON_GROUP_NPC_AI, "ERROR: Attempting to remove non-existing squad member!\n" ); return; } m_SquadMembers.Remove(myIndex); @@ -263,7 +337,7 @@ void CAI_Squad::AddToSquad(CAI_BaseNPC *pNPC) if (m_SquadMembers.Count() == MAX_SQUAD_MEMBERS) { - DevMsg("Error!! Squad %s is too big!!! Replacing last member\n", STRING( this->m_Name )); + CGMsg( 1, CON_GROUP_NPC_AI, "Error!! Squad %s is too big!!! Replacing last member\n", STRING( this->m_Name ) ); m_SquadMembers.Remove(m_SquadMembers.Count()-1); } m_SquadMembers.AddToTail(pNPC); @@ -485,7 +559,7 @@ void CAI_Squad::SquadNewEnemy( CBaseEntity *pEnemy ) { if ( !pEnemy ) { - DevMsg( "ERROR: SquadNewEnemy() - pEnemy is NULL!\n" ); + CGMsg( 1, CON_GROUP_NPC_AI, "ERROR: SquadNewEnemy() - pEnemy is NULL!\n" ); return; } @@ -604,7 +678,7 @@ bool CAI_Squad::OccupyStrategySlotRange( CBaseEntity *pEnemy, int slotIDStart, i // As a debug measure check to see if slot was filled if (!IsSlotOccupied(pEnemy, *pSlot)) { - DevMsg( "ERROR! Vacating an empty slot!\n"); + CGMsg( 1, CON_GROUP_NPC_AI, "ERROR! Vacating an empty slot!\n" ); } // Free the slot @@ -643,7 +717,7 @@ void CAI_Squad::VacateStrategySlot( CBaseEntity *pEnemy, int slot) // As a debug measure check to see if slot was filled if (!IsSlotOccupied(pEnemy, slot)) { - DevMsg( "ERROR! Vacating an empty slot!\n"); + CGMsg( 1, CON_GROUP_NPC_AI, "ERROR! Vacating an empty slot!\n" ); } // Free the slot @@ -783,5 +857,44 @@ bool CAI_Squad::IsSquadInflictor( CBaseEntity *pInflictor ) return (m_hSquadInflictor.Get() == pInflictor); } +#ifdef MAPBASE_VSCRIPT +//------------------------------------------------------------------------------ + +// Functions tailored specifically for VScript. + +HSCRIPT CAI_Squad::ScriptGetFirstMember( bool bIgnoreSilentMembers ) { return ToHScript( GetFirstMember( NULL, bIgnoreSilentMembers ) ); } +HSCRIPT CAI_Squad::ScriptGetMember( int iIndex ) { return iIndex < m_SquadMembers.Count() ? ToHScript( m_SquadMembers[iIndex] ) : NULL; } +HSCRIPT CAI_Squad::ScriptGetAnyMember() { return ToHScript( GetAnyMember() ); } +//int CAI_Squad::ScriptNumMembers( bool bIgnoreSilentMembers ) { return NumMembers( bIgnoreSilentMembers ); } +int CAI_Squad::ScriptGetSquadIndex( HSCRIPT hNPC ) { return GetSquadIndex( HScriptToClass( hNPC ) ); } + +void CAI_Squad::ScriptUpdateEnemyMemory( HSCRIPT hUpdater, HSCRIPT hEnemy, const Vector &position ) { UpdateEnemyMemory( HScriptToClass( hUpdater ), ToEnt( hEnemy ), position ); } + +HSCRIPT CAI_Squad::ScriptSquadMemberInRange( const Vector &vecLocation, float flDist ) { return ToHScript( SquadMemberInRange( vecLocation, flDist ) ); } +HSCRIPT CAI_Squad::ScriptNearestSquadMember( HSCRIPT hMember ) { return ToHScript( NearestSquadMember( HScriptToClass( hMember ) ) ); } +int CAI_Squad::ScriptGetVisibleSquadMembers( HSCRIPT hMember ) { return GetVisibleSquadMembers( HScriptToClass( hMember ) ); } +HSCRIPT CAI_Squad::ScriptGetSquadMemberNearestTo( const Vector &vecLocation ) { return ToHScript( GetSquadMemberNearestTo( vecLocation ) ); } +bool CAI_Squad::ScriptIsMember( HSCRIPT hMember ) { return SquadIsMember( HScriptToClass( hMember ) ); } +bool CAI_Squad::ScriptIsLeader( HSCRIPT hLeader ) { return IsLeader( HScriptToClass( hLeader ) ); } +HSCRIPT CAI_Squad::ScriptGetLeader( void ) { return ToHScript( GetLeader() ); } + +void CAI_Squad::ScriptAddToSquad( HSCRIPT hNPC ) { AddToSquad( HScriptToClass( hNPC ) ); } +void CAI_Squad::ScriptRemoveFromSquad( HSCRIPT hNPC ) { RemoveFromSquad( HScriptToClass( hNPC ), false ); } + +bool CAI_Squad::ScriptIsSilentMember( HSCRIPT hNPC ) { return IsSilentMember( HScriptToClass( hNPC ) ); } + +void CAI_Squad::ScriptSetSquadData( int iSlot, const char *data ) +{ + SetSquadData( iSlot, data ); +} + +const char *CAI_Squad::ScriptGetSquadData( int iSlot ) +{ + const char *data; + GetSquadData( iSlot, &data ); + return data; +} +#endif + //============================================================================= diff --git a/src/game/server/ai_squad.h b/src/game/server/ai_squad.h index 846e9a11..9604d1f0 100644 --- a/src/game/server/ai_squad.h +++ b/src/game/server/ai_squad.h @@ -52,6 +52,14 @@ public: void DeleteSquad( CAI_Squad *pSquad ); void DeleteAllSquads(void); +#ifdef MAPBASE_VSCRIPT + HSCRIPT ScriptGetFirstSquad(); + HSCRIPT ScriptGetNextSquad( HSCRIPT hStart ); + + HSCRIPT ScriptFindSquad( const char *squadName ); + HSCRIPT ScriptFindCreateSquad( const char *squadName ); +#endif + private: CAI_Squad * m_pSquads; // A linked list of all squads @@ -152,6 +160,36 @@ private: void VacateSlot( CBaseEntity *pEnemy, int i ); bool IsSlotOccupied( CBaseEntity *pEnemy, int i ) const; +#ifdef MAPBASE_VSCRIPT + // Functions tailored specifically for VScript. + ALLOW_SCRIPT_ACCESS(); +private: + + HSCRIPT ScriptGetFirstMember( bool bIgnoreSilentMembers ); + HSCRIPT ScriptGetMember( int iIndex ); + HSCRIPT ScriptGetAnyMember(); + //int ScriptNumMembers( bool bIgnoreSilentMembers ); + int ScriptGetSquadIndex( HSCRIPT hNPC ); + + void ScriptUpdateEnemyMemory( HSCRIPT hUpdater, HSCRIPT hEnemy, const Vector &position ); + + HSCRIPT ScriptSquadMemberInRange( const Vector &vecLocation, float flDist ); + HSCRIPT ScriptNearestSquadMember( HSCRIPT hMember ); + int ScriptGetVisibleSquadMembers( HSCRIPT hMember ); + HSCRIPT ScriptGetSquadMemberNearestTo( const Vector &vecLocation ); + bool ScriptIsMember( HSCRIPT hMember ); + bool ScriptIsLeader( HSCRIPT hLeader ); + HSCRIPT ScriptGetLeader( void ); + + void ScriptAddToSquad( HSCRIPT hNPC ); + void ScriptRemoveFromSquad( HSCRIPT hNPC ); + + bool ScriptIsSilentMember( HSCRIPT hNPC ); + + void ScriptSetSquadData( int iSlot, const char *data ); + const char *ScriptGetSquadData( int iSlot ); +#endif + private: friend class CAI_SaveRestoreBlockHandler; friend class CAI_SquadManager; diff --git a/src/game/server/ai_tacticalservices.cpp b/src/game/server/ai_tacticalservices.cpp index 87399c4f..8bcdcc08 100644 --- a/src/game/server/ai_tacticalservices.cpp +++ b/src/game/server/ai_tacticalservices.cpp @@ -411,7 +411,12 @@ int CAI_TacticalServices::FindCoverNode(const Vector &vNearPos, const Vector &vT // -------------------------------------------------------- pNode->Lock( 1.0 ); +#ifdef MAPBASE + if ( pNode->GetHint() && ( pNode->GetHint()->HintType() == HINT_TACTICAL_COVER_MED || pNode->GetHint()->HintType() == HINT_TACTICAL_COVER_LOW + || pNode->GetHint()->HintType() == HINT_TACTICAL_COVER_CUSTOM ) ) +#else if ( pNode->GetHint() && ( pNode->GetHint()->HintType() == HINT_TACTICAL_COVER_MED || pNode->GetHint()->HintType() == HINT_TACTICAL_COVER_LOW ) ) +#endif { if ( GetOuter()->GetHintNode() ) { diff --git a/src/game/server/ai_task.cpp b/src/game/server/ai_task.cpp index a2cf6be6..6e471351 100644 --- a/src/game/server/ai_task.cpp +++ b/src/game/server/ai_task.cpp @@ -222,6 +222,9 @@ void CAI_BaseNPC::InitDefaultTaskSR(void) ADD_DEF_TASK( TASK_ADD_HEALTH ); ADD_DEF_TASK( TASK_GET_PATH_TO_INTERACTION_PARTNER ); ADD_DEF_TASK( TASK_PRE_SCRIPT ); +#ifdef MAPBASE + ADD_DEF_TASK( TASK_FACE_INTERACTION_ANGLES ); +#endif } diff --git a/src/game/server/ai_task.h b/src/game/server/ai_task.h index 33763730..25e09413 100644 --- a/src/game/server/ai_task.h +++ b/src/game/server/ai_task.h @@ -494,6 +494,11 @@ enum sharedtasks_e // First task of all schedules for playing back scripted sequences TASK_PRE_SCRIPT, +#ifdef MAPBASE + // Faces the actual interaction angles instead of just facing the enemy + TASK_FACE_INTERACTION_ANGLES, +#endif + // ====================================== // IMPORTANT: This must be the last enum // ====================================== diff --git a/src/game/server/baseanimating.cpp b/src/game/server/baseanimating.cpp index fb59bca9..11747b13 100644 --- a/src/game/server/baseanimating.cpp +++ b/src/game/server/baseanimating.cpp @@ -27,6 +27,14 @@ #include "datacache/idatacache.h" #include "smoke_trail.h" #include "props.h" +#ifdef MAPBASE +#include "ai_speech.h" +#include "gib.h" +#include "CRagdollMagnet.h" +#endif +#ifdef MAPBASE_VSCRIPT +#include "mapbase/vscript_funcs_shared.h" +#endif // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" @@ -202,9 +210,17 @@ BEGIN_DATADESC( CBaseAnimating ) DEFINE_INPUTFUNC( FIELD_INTEGER, "IgniteNumHitboxFires", InputIgniteNumHitboxFires ), DEFINE_INPUTFUNC( FIELD_FLOAT, "IgniteHitboxFireScale", InputIgniteHitboxFireScale ), DEFINE_INPUTFUNC( FIELD_VOID, "BecomeRagdoll", InputBecomeRagdoll ), +#ifdef MAPBASE + DEFINE_INPUTFUNC( FIELD_VOID, "CreateSeparateRagdoll", InputCreateSeparateRagdoll ), + DEFINE_INPUTFUNC( FIELD_VOID, "CreateSeparateRagdollClient", InputCreateSeparateRagdollClient ), + DEFINE_INPUTFUNC( FIELD_STRING, "SetPoseParameter", InputSetPoseParameter ), +#endif DEFINE_INPUTFUNC( FIELD_STRING, "SetLightingOriginHack", InputSetLightingOriginRelative ), DEFINE_INPUTFUNC( FIELD_STRING, "SetLightingOrigin", InputSetLightingOrigin ), DEFINE_OUTPUT( m_OnIgnite, "OnIgnite" ), +#ifdef MAPBASE + DEFINE_OUTPUT( m_OnServerRagdoll, "OnServerRagdoll" ), +#endif DEFINE_INPUT( m_fadeMinDist, FIELD_FLOAT, "fademindist" ), DEFINE_INPUT( m_fadeMaxDist, FIELD_FLOAT, "fademaxdist" ), @@ -266,6 +282,11 @@ IMPLEMENT_SERVERCLASS_ST(CBaseAnimating, DT_BaseAnimating) END_SEND_TABLE() +#ifdef MAPBASE_VSCRIPT +ScriptHook_t CBaseAnimating::g_Hook_OnServerRagdoll; +ScriptHook_t CBaseAnimating::g_Hook_HandleAnimEvent; +#endif + BEGIN_ENT_SCRIPTDESC( CBaseAnimating, CBaseEntity, "Animating models" ) #ifdef PORTAL2 DEFINE_SCRIPTFUNC( GetObjectScaleLevel, "The scale size of the entity" ) @@ -285,6 +306,31 @@ BEGIN_ENT_SCRIPTDESC( CBaseAnimating, CBaseEntity, "Animating models" ) DEFINE_SCRIPTFUNC_NAMED( ScriptGetSequenceDuration, "GetSequenceDuration", "Get a sequence duration by id" ) DEFINE_SCRIPTFUNC_NAMED( ScriptGetAttachmentOrigin, "GetAttachmentOrigin", "Get the attachement id's origin vector" ) DEFINE_SCRIPTFUNC_NAMED( ScriptGetAttachmentAngles, "GetAttachmentAngles", "Get the attachement id's angles as a p,y,r vector" ) +#ifdef MAPBASE_VSCRIPT + DEFINE_SCRIPTFUNC_NAMED( ScriptGetAttachmentMatrix, "GetAttachmentMatrix", "Get the attachement id's matrix transform" ) + DEFINE_SCRIPTFUNC_NAMED( ScriptGetPoseParameter, "GetPoseParameter", "Get the specified pose parameter's value" ) + DEFINE_SCRIPTFUNC( LookupBone, "Get the named bone id" ) + DEFINE_SCRIPTFUNC_NAMED( ScriptGetBoneTransform, "GetBoneTransform", "Get the transform for the specified bone" ) + DEFINE_SCRIPTFUNC( GetPhysicsBone, "Get physics bone from bone index" ) + DEFINE_SCRIPTFUNC( GetNumBones, "Get the number of bones" ) + DEFINE_SCRIPTFUNC( GetSequence, "Gets the current sequence" ) + DEFINE_SCRIPTFUNC( SetSequence, "Sets the current sequence" ) + DEFINE_SCRIPTFUNC( SequenceLoops, "Does the current sequence loop?" ) + DEFINE_SCRIPTFUNC_NAMED( ScriptSequenceDuration, "SequenceDuration", "Get the specified sequence duration" ) + DEFINE_SCRIPTFUNC( LookupSequence, "Gets the index of the specified sequence name" ) + DEFINE_SCRIPTFUNC( LookupActivity, "Gets the ID of the specified activity name" ) + DEFINE_SCRIPTFUNC_NAMED( HasMovement, "SequenceHasMovement", "Checks if the specified sequence has movement" ) + DEFINE_SCRIPTFUNC( GetSequenceMoveYaw, "Gets the move yaw of the specified sequence" ) + DEFINE_SCRIPTFUNC_NAMED( ScriptGetSequenceMoveDist, "GetSequenceMoveDist", "Gets the move distance of the specified sequence" ) + DEFINE_SCRIPTFUNC( GetSequenceName, "Gets the name of the specified sequence index" ) + DEFINE_SCRIPTFUNC( GetSequenceActivityName, "Gets the activity name of the specified sequence index" ) + DEFINE_SCRIPTFUNC_NAMED( ScriptGetSequenceActivity, "GetSequenceActivity", "Gets the activity ID of the specified sequence index" ) + DEFINE_SCRIPTFUNC_NAMED( ScriptSelectWeightedSequence, "SelectWeightedSequence", "Selects a sequence for the specified activity ID" ) + DEFINE_SCRIPTFUNC_NAMED( ScriptSelectHeaviestSequence, "SelectHeaviestSequence", "Selects the sequence with the heaviest weight for the specified activity ID" ) + DEFINE_SCRIPTFUNC_NAMED( ScriptGetSequenceKeyValues, "GetSequenceKeyValues", "Get a KeyValue class instance on the specified sequence. WARNING: This uses the same KeyValue pointer as GetModelKeyValues!" ) + DEFINE_SCRIPTFUNC( ResetSequenceInfo, "" ) + DEFINE_SCRIPTFUNC( StudioFrameAdvance, "" ) +#endif DEFINE_SCRIPTFUNC( IsSequenceFinished, "Ask whether the main sequence is done playing" ) DEFINE_SCRIPTFUNC( SetBodygroup, "Sets a bodygroup") DEFINE_SCRIPTFUNC( GetBodygroup, "Get a bodygroup by id") @@ -307,6 +353,28 @@ BEGIN_ENT_SCRIPTDESC( CBaseAnimating, CBaseEntity, "Animating models" ) DEFINE_SCRIPTFUNC( StudioFrameAdvanceManual, "Advance animation frame to some time in the future with a manual interval" ) DEFINE_SCRIPTFUNC_NAMED( ScriptDispatchAnimEvents, "DispatchAnimEvents", "Dispatch animation events to a CBaseAnimating" ) DEFINE_SCRIPTFUNC_WRAPPED( LookupPoseParameter, "Looks up a pose parameter index by name" ); +#ifdef MAPBASE_VSCRIPT + DEFINE_SCRIPTFUNC( GetBodygroupCount, "Gets the number of models in a bodygroup" ) + DEFINE_SCRIPTFUNC( GetNumBodyGroups, "Gets the number of bodygroups" ) + DEFINE_SCRIPTFUNC( GetModelScale, "Gets the model's scale" ) + DEFINE_SCRIPTFUNC( SetModelScale, "Sets the model's scale with the specified change duration" ) + + DEFINE_SCRIPTFUNC( Dissolve, "Use 'sprites/blueglow1.vmt' for the default material, Time() for the default start time, false for npcOnly if you don't want it to check if the entity is a NPC first, 0 for the default dissolve type, Vector(0,0,0) for the default dissolver origin, and 0 for the default magnitude." ) + DEFINE_SCRIPTFUNC( Ignite, "'NPCOnly' only lets this fall through if the entity is a NPC and 'CalledByLevelDesigner' determines whether to treat this like the Ignite input or just an internal ignition call." ) + DEFINE_SCRIPTFUNC( Scorch, "Makes the entity darker from scorching" ) + + DEFINE_SCRIPTFUNC( IsRagdoll, "" ) + DEFINE_SCRIPTFUNC( CanBecomeRagdoll, "" ) + + BEGIN_SCRIPTHOOK( CBaseAnimating::g_Hook_OnServerRagdoll, "OnServerRagdoll", FIELD_VOID, "Called when this entity creates/turns into a server-side ragdoll." ) + DEFINE_SCRIPTHOOK_PARAM( "ragdoll", FIELD_HSCRIPT ) + DEFINE_SCRIPTHOOK_PARAM( "submodel", FIELD_BOOLEAN ) + END_SCRIPTHOOK() + + BEGIN_SCRIPTHOOK( CBaseAnimating::g_Hook_HandleAnimEvent, "HandleAnimEvent", FIELD_BOOLEAN, "Called when handling animation events. Return false to cancel base handling." ) + DEFINE_SCRIPTHOOK_PARAM( "event", FIELD_HSCRIPT ) + END_SCRIPTHOOK() +#endif END_SCRIPTDESC(); CBaseAnimating::CBaseAnimating() @@ -465,6 +533,11 @@ void CBaseAnimating::StudioFrameAdvanceInternal( CStudioHdr *pStudioHdr, float f float flNewCycle = GetCycle() + flCycleDelta; if (flNewCycle < 0.0 || flNewCycle >= 1.0) { + if (flNewCycle >= 1.0f) + { + ReachedEndOfSequence(); + } + if (m_bSequenceLoops) { flNewCycle -= (int)(flNewCycle); @@ -570,7 +643,11 @@ void CBaseAnimating::StudioFrameAdvance() //----------------------------------------------------------------------------- // Set the relative lighting origin //----------------------------------------------------------------------------- +#ifdef MAPBASE +void CBaseAnimating::SetLightingOriginRelative( string_t strLightingOriginRelative, inputdata_t *inputdata ) +#else void CBaseAnimating::SetLightingOriginRelative( string_t strLightingOriginRelative ) +#endif { if ( strLightingOriginRelative == NULL_STRING ) { @@ -578,7 +655,11 @@ void CBaseAnimating::SetLightingOriginRelative( string_t strLightingOriginRelati } else { +#ifdef MAPBASE + CBaseEntity *pLightingOrigin = gEntList.FindEntityByName( NULL, strLightingOriginRelative, this, inputdata ? inputdata->pActivator : NULL, inputdata ? inputdata->pCaller : NULL ); +#else CBaseEntity *pLightingOrigin = gEntList.FindEntityByName( NULL, strLightingOriginRelative ); +#endif if ( !pLightingOrigin ) { DevWarning( "%s: Could not find info_lighting_relative '%s'!\n", GetClassname(), STRING( strLightingOriginRelative ) ); @@ -608,7 +689,11 @@ void CBaseAnimating::SetLightingOriginRelative( string_t strLightingOriginRelati //----------------------------------------------------------------------------- // Set the lighting origin //----------------------------------------------------------------------------- +#ifdef MAPBASE +void CBaseAnimating::SetLightingOrigin( string_t strLightingOrigin, inputdata_t *inputdata ) +#else void CBaseAnimating::SetLightingOrigin( string_t strLightingOrigin ) +#endif { if ( strLightingOrigin == NULL_STRING ) { @@ -616,7 +701,11 @@ void CBaseAnimating::SetLightingOrigin( string_t strLightingOrigin ) } else { +#ifdef MAPBASE + CBaseEntity *pLightingOrigin = gEntList.FindEntityByName( NULL, strLightingOrigin, this, inputdata ? inputdata->pActivator : NULL, inputdata ? inputdata->pCaller : NULL ); +#else CBaseEntity *pLightingOrigin = gEntList.FindEntityByName( NULL, strLightingOrigin ); +#endif if ( !pLightingOrigin ) { DevWarning( "%s: Could not find lighting origin entity named '%s'!\n", GetClassname(), STRING( strLightingOrigin ) ); @@ -639,9 +728,15 @@ void CBaseAnimating::SetLightingOrigin( string_t strLightingOrigin ) //----------------------------------------------------------------------------- void CBaseAnimating::InputSetLightingOriginRelative( inputdata_t &inputdata ) { +#ifdef MAPBASE + // Pass our input data now. + // (and stop doing that MAKE_STRING nonsense) + SetLightingOriginRelative( inputdata.value.StringID(), &inputdata ); +#else // Find our specified target string_t strLightingOriginRelative = MAKE_STRING( inputdata.value.String() ); SetLightingOriginRelative( strLightingOriginRelative ); +#endif } //----------------------------------------------------------------------------- @@ -650,9 +745,15 @@ void CBaseAnimating::InputSetLightingOriginRelative( inputdata_t &inputdata ) //----------------------------------------------------------------------------- void CBaseAnimating::InputSetLightingOrigin( inputdata_t &inputdata ) { +#ifdef MAPBASE + // Pass our input data now. + // (and stop doing that MAKE_STRING nonsense) + SetLightingOrigin( inputdata.value.StringID(), &inputdata ); +#else // Find our specified target string_t strLightingOrigin = MAKE_STRING( inputdata.value.String() ); SetLightingOrigin( strLightingOrigin ); +#endif } //----------------------------------------------------------------------------- @@ -1211,6 +1312,11 @@ void CBaseAnimating::DispatchAnimEvents ( CBaseAnimating *eventHandler ) event.eventtime = m_flAnimTime + (flCycle - GetCycle()) / flCycleRate + GetAnimTimeInterval(); } +#ifdef MAPBASE_VSCRIPT + if (eventHandler->ScriptHookHandleAnimEvent( &event ) == false) + continue; +#endif + /* if (m_debugOverlays & OVERLAY_NPC_SELECTED_BIT) { @@ -1242,6 +1348,29 @@ void CBaseAnimating::DispatchAnimEvents ( CBaseAnimating *eventHandler ) } } +#ifdef MAPBASE_VSCRIPT +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CBaseAnimating::ScriptHookHandleAnimEvent( animevent_t *pEvent ) +{ + if (m_ScriptScope.IsInitialized() && g_Hook_HandleAnimEvent.CanRunInScope(m_ScriptScope)) + { + HSCRIPT hEvent = g_pScriptVM->RegisterInstance( reinterpret_cast(pEvent) ); + + // event + ScriptVariant_t args[] = { hEvent }; + ScriptVariant_t returnValue = true; + g_Hook_HandleAnimEvent.Call( m_ScriptScope, &returnValue, args ); + + g_pScriptVM->RemoveInstance( hEvent ); + return returnValue.Get(); + } + + return true; +} +#endif + //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- @@ -1254,6 +1383,33 @@ void CBaseAnimating::HandleAnimEvent( animevent_t *pEvent ) EmitSound( pEvent->options ); return; } +#ifdef MAPBASE + else if ( pEvent->event == AE_NPC_RESPONSE ) + { + if (MyNPCPointer() && MyNPCPointer()->GetExpresser() && !MyNPCPointer()->GetExpresser()->IsSpeaking()) + { + DispatchResponse( pEvent->options ); + } + return; + } + else if ( pEvent->event == AE_NPC_RESPONSE_FORCED ) + { + DispatchResponse( pEvent->options ); + return; + } + else if ( pEvent->event == AE_VSCRIPT_RUN ) + { + if (!RunScript( pEvent->options )) + Warning( "%s failed to run AE_VSCRIPT_RUN on server with \"%s\"\n", GetDebugName(), pEvent->options ); + return; + } + else if ( pEvent->event == AE_VSCRIPT_RUN_FILE ) + { + if (!RunScriptFile( pEvent->options )) + Warning( "%s failed to run AE_VSCRIPT_RUN_FILE on server with \"%s\"\n", GetDebugName(), pEvent->options ); + return; + } +#endif else if ( pEvent->event == AE_RAGDOLL ) { // Convert to ragdoll immediately @@ -1864,7 +2020,7 @@ ConVar ai_setupbones_debug( "ai_setupbones_debug", "0", 0, "Shows that bones tha -inline bool CBaseAnimating::CanSkipAnimation( void ) +bool CBaseAnimating::CanSkipAnimation( void ) { if ( !sv_pvsskipanimation.GetBool() ) return false; @@ -2183,6 +2339,67 @@ bool CBaseAnimating::GetAttachment( int iAttachment, Vector &absOrigin, Vector * return bRet; } +#ifdef MAPBASE_VSCRIPT +HSCRIPT CBaseAnimating::ScriptGetAttachmentMatrix( int iAttachment ) +{ + static matrix3x4_t matrix; + + CBaseAnimating::GetAttachment( iAttachment, matrix ); + return g_pScriptVM->RegisterInstance( &matrix ); +} + +#ifndef VSCRIPT_PRIORITIZE_TF2_SYNTAX +float CBaseAnimating::ScriptGetPoseParameter( const char* szName ) +{ + CStudioHdr* pHdr = GetModelPtr(); + if (pHdr == NULL) + return 0.0f; + + int iPoseParam = LookupPoseParameter( pHdr, szName ); + return GetPoseParameter( iPoseParam ); +} + +void CBaseAnimating::ScriptSetPoseParameter( const char* szName, float fValue ) +{ + CStudioHdr* pHdr = GetModelPtr(); + if (pHdr == NULL) + return; + + int iPoseParam = LookupPoseParameter( pHdr, szName ); + SetPoseParameter( pHdr, iPoseParam, fValue ); +} +#endif + +void CBaseAnimating::ScriptGetBoneTransform( int iBone, HSCRIPT hTransform ) +{ + if (hTransform == NULL) + return; + + GetBoneTransform( iBone, *HScriptToClass( hTransform ) ); +} + +//----------------------------------------------------------------------------- +// VScript access to sequence's key values +// for iteration and value access, use: +// ScriptFindKey, ScriptGetFirstSubKey, ScriptGetString, +// ScriptGetInt, ScriptGetFloat, ScriptGetNextKey +// NOTE: This is recycled from ScriptGetModelKeyValues() and uses its pointer!!! +//----------------------------------------------------------------------------- +HSCRIPT CBaseAnimating::ScriptGetSequenceKeyValues( int iSequence ) +{ + KeyValues *pSeqKeyValues = GetSequenceKeyValues( iSequence ); + HSCRIPT hScript = NULL; + if ( pSeqKeyValues ) + { + // UNDONE: how does destructor get called on this + m_pScriptModelKeyValues = hScript = scriptmanager->CreateScriptKeyValues( g_pScriptVM, pSeqKeyValues, true ); + + // UNDONE: who calls ReleaseInstance on this??? Does name need to be unique??? + } + + return hScript; +} +#endif //----------------------------------------------------------------------------- // Returns the attachment in local space @@ -2808,9 +3025,9 @@ void CBaseAnimating::InvalidateBoneCache( void ) bool CBaseAnimating::TestCollision( const Ray_t &ray, unsigned int fContentsMask, trace_t& tr ) { // Return a special case for scaled physics objects - if ( GetModelScale() != 1.0f ) + IPhysicsObject *pPhysObject = VPhysicsGetObject(); + if ( GetModelScale() != 1.0f && pPhysObject ) { - IPhysicsObject *pPhysObject = VPhysicsGetObject(); Vector vecPosition; QAngle vecAngles; pPhysObject->GetPosition( &vecPosition, &vecAngles ); @@ -3376,6 +3593,18 @@ void CBaseAnimating::CopyAnimationDataFrom( CBaseAnimating *pSource ) this->m_flAnimTime = pSource->m_flAnimTime; this->m_nBody = pSource->m_nBody; this->m_nSkin = pSource->m_nSkin; +#ifdef MAPBASE + this->m_clrRender = pSource->m_clrRender; + this->m_nRenderMode = pSource->m_nRenderMode; + this->m_nRenderFX = pSource->m_nRenderFX; + this->m_iViewHideFlags = pSource->m_iViewHideFlags; + this->m_fadeMinDist = pSource->m_fadeMinDist; + this->m_fadeMaxDist = pSource->m_fadeMaxDist; + this->m_flFadeScale = pSource->m_flFadeScale; + + if (this->GetModelScale() != pSource->GetModelScale()) + this->SetModelScale( pSource->GetModelScale() ); +#endif this->LockStudioHdr(); } @@ -3710,6 +3939,72 @@ void CBaseAnimating::InputBecomeRagdoll( inputdata_t &inputdata ) BecomeRagdollOnClient( vec3_origin ); } +#ifdef MAPBASE +void CBaseAnimating::InputCreateSeparateRagdoll( inputdata_t &inputdata ) +{ + CTakeDamageInfo info( this, inputdata.pActivator, 0.0f, DMG_GENERIC ); + + // See if there's a ragdoll magnet that should influence our force. + CRagdollMagnet *pMagnet = CRagdollMagnet::FindBestMagnet( this ); + if( pMagnet ) + { + info.SetDamageForce(pMagnet->GetForceVector( this )); + pMagnet->m_OnUsed.Set(info.GetDamageForce(), this, pMagnet); + } + + CreateServerRagdoll( this, 0, info, COLLISION_GROUP_INTERACTIVE_DEBRIS, true ); +} + +void CBaseAnimating::InputCreateSeparateRagdollClient( inputdata_t &inputdata ) +{ + // I remember there being a reason why this must be initialized to all 0's... + Vector forceVector = Vector(0.0f, 0.0f, 0.0f); + + // See if there's a ragdoll magnet that should influence our force. + CRagdollMagnet *pMagnet = CRagdollMagnet::FindBestMagnet( this ); + if( pMagnet ) + { + forceVector += pMagnet->GetForceVector( this ); + pMagnet->m_OnUsed.Set(forceVector, this, pMagnet); + } + + CBaseEntity *pRagdoll = CreateRagGib( STRING( GetModelName() ), GetAbsOrigin(), GetAbsAngles(), forceVector, 25.0f ); + + if (pRagdoll->GetBaseAnimating()) + { + pRagdoll->GetBaseAnimating()->CopyAnimationDataFrom( this ); + } +} + +void CBaseAnimating::InputSetPoseParameter( inputdata_t &inputdata ) +{ + char token[64]; + Q_strncpy( token, inputdata.value.String(), sizeof(token) ); + char *sChar = strchr( token, ' ' ); + if ( sChar ) + { + *sChar = '\0'; + + // Name + const int index = LookupPoseParameter( token ); + if (index == -1) + { + Warning("SetPoseParameter: Could not find pose parameter \"%s\" on %s\n", token, GetDebugName()); + return; + } + + // Value + const float value = atof( sChar+1 ); + + SetPoseParameter( index, value ); + } + else + { + Warning("SetPoseParameter: \"%s\" is invalid; format is \" \"\n", inputdata.value.String()); + } +} +#endif + //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- diff --git a/src/game/server/baseanimating.h b/src/game/server/baseanimating.h index 97316268..262edb52 100644 --- a/src/game/server/baseanimating.h +++ b/src/game/server/baseanimating.h @@ -84,6 +84,7 @@ public: virtual void StudioFrameAdvance(); // advance animation frame to some time in the future void StudioFrameAdvanceManual( float flInterval ); bool IsValidSequence( int iSequence ); + virtual void ReachedEndOfSequence() { return; } inline float GetPlaybackRate(); inline void SetPlaybackRate( float rate ); @@ -101,6 +102,9 @@ public: float SequenceDuration( CStudioHdr *pStudioHdr, int iSequence ); inline float SequenceDuration( int iSequence ) { return SequenceDuration(GetModelPtr(), iSequence); } float ScriptGetSequenceDuration( int iSequence ); +#ifdef MAPBASE_VSCRIPT + inline float ScriptSequenceDuration( int iSequence ) { return SequenceDuration(GetModelPtr(), iSequence); } +#endif float GetSequenceCycleRate( CStudioHdr *pStudioHdr, int iSequence ); inline float GetSequenceCycleRate( int iSequence ) { return GetSequenceCycleRate(GetModelPtr(),iSequence); } float GetLastVisibleCycle( CStudioHdr *pStudioHdr, int iSequence ); @@ -152,6 +156,9 @@ public: return this->DispatchAnimEvents( pAnimating ); } virtual void HandleAnimEvent( animevent_t *pEvent ); +#ifdef MAPBASE_VSCRIPT + bool ScriptHookHandleAnimEvent( animevent_t *pEvent ); +#endif int LookupPoseParameter( CStudioHdr *pStudioHdr, const char *szName ); inline int LookupPoseParameter( const char *szName ) { return LookupPoseParameter(GetModelPtr(), szName); } @@ -161,7 +168,12 @@ public: inline float SetPoseParameter( const char *szName, float flValue ) { return SetPoseParameter( GetModelPtr(), szName, flValue ); } float SetPoseParameter( CStudioHdr *pStudioHdr, int iParameter, float flValue ); inline float SetPoseParameter( int iParameter, float flValue ) { return SetPoseParameter( GetModelPtr(), iParameter, flValue ); } +#ifdef VSCRIPT_PRIORITIZE_TF2_SYNTAX +#ifdef MAPBASE_VSCRIPT + inline float ScriptGetPoseParameter( int iParameter ) { return GetPoseParameter( iParameter ); } +#endif inline float ScriptSetPoseParameter( int iParameter, float flValue ) { return SetPoseParameter( iParameter, flValue ); } +#endif float GetPoseParameter( const char *szName ); float GetPoseParameter( int iParameter ); @@ -201,6 +213,26 @@ public: int GetAttachmentBone( int iAttachment ); virtual bool GetAttachment( int iAttachment, matrix3x4_t &attachmentToWorld ); +#ifdef MAPBASE_VSCRIPT + HSCRIPT ScriptGetAttachmentMatrix(int iAttachment); +#ifndef VSCRIPT_PRIORITIZE_TF2_SYNTAX + float ScriptGetPoseParameter(const char* szName); + void ScriptSetPoseParameter(const char* szName, float fValue); +#endif + + void ScriptGetBoneTransform( int iBone, HSCRIPT hTransform ); + + int ScriptGetSequenceActivity( int iSequence ) { return GetSequenceActivity( iSequence ); } + float ScriptGetSequenceMoveDist( int iSequence ) { return GetSequenceMoveDist( GetModelPtr(), iSequence ); } + int ScriptSelectHeaviestSequence( int activity ) { return SelectHeaviestSequence( (Activity)activity ); } + int ScriptSelectWeightedSequence( int activity, int curSequence ) { return SelectWeightedSequence( (Activity)activity, curSequence ); } + + HSCRIPT ScriptGetSequenceKeyValues( int iSequence ); + + static ScriptHook_t g_Hook_OnServerRagdoll; + static ScriptHook_t g_Hook_HandleAnimEvent; +#endif + // These return the attachment in the space of the entity bool GetAttachmentLocal( const char *szName, Vector &origin, QAngle &angles ); bool GetAttachmentLocal( int iAttachment, Vector &origin, QAngle &angles ); @@ -320,6 +352,11 @@ public: void InputIgniteNumHitboxFires( inputdata_t &inputdata ); void InputIgniteHitboxFireScale( inputdata_t &inputdata ); void InputBecomeRagdoll( inputdata_t &inputdata ); +#ifdef MAPBASE + void InputCreateSeparateRagdoll( inputdata_t &inputdata ); + void InputCreateSeparateRagdollClient( inputdata_t &inputdata ); + void InputSetPoseParameter( inputdata_t &inputdata ); +#endif // Dissolve, returns true if the ragdoll has been created bool Dissolve( const char *pMaterialName, float flStartTime, bool bNPCOnly = true, int nDissolveType = 0, Vector vDissolverOrigin = vec3_origin, int iMagnitude = 0 ); @@ -331,11 +368,19 @@ public: float m_flLastEventCheck; // cycle index of when events were last checked virtual void SetLightingOriginRelative( CBaseEntity *pLightingOriginRelative ); +#ifdef MAPBASE + void SetLightingOriginRelative( string_t strLightingOriginRelative, inputdata_t *inputdata = NULL ); +#else void SetLightingOriginRelative( string_t strLightingOriginRelative ); +#endif CBaseEntity *GetLightingOriginRelative(); virtual void SetLightingOrigin( CBaseEntity *pLightingOrigin ); +#ifdef MAPBASE + void SetLightingOrigin( string_t strLightingOrigin, inputdata_t *inputdata = NULL ); +#else void SetLightingOrigin( string_t strLightingOrigin ); +#endif CBaseEntity *GetLightingOrigin(); const float* GetPoseParameterArray() { return m_flPoseParameter.Base(); } @@ -365,6 +410,9 @@ private: void InputSetModel( inputdata_t &inputdata ); void InputSetCycle( inputdata_t &inputdata ); void InputSetPlaybackRate( inputdata_t &inputdata ); +#ifdef MAPBASE +public: // From Alien Swarm SDK +#endif bool CanSkipAnimation( void ); @@ -443,6 +491,9 @@ protected: public: COutputEvent m_OnIgnite; +#ifdef MAPBASE + COutputEHANDLE m_OnServerRagdoll; +#endif private: CStudioHdr *m_pStudioHdr; diff --git a/src/game/server/basebludgeonweapon.cpp b/src/game/server/basebludgeonweapon.cpp index ff8df099..c755e2ef 100644 --- a/src/game/server/basebludgeonweapon.cpp +++ b/src/game/server/basebludgeonweapon.cpp @@ -28,6 +28,15 @@ IMPLEMENT_SERVERCLASS_ST( CBaseHLBludgeonWeapon, DT_BaseHLBludgeonWeapon ) END_SEND_TABLE() +#ifdef MAPBASE +BEGIN_DATADESC(CBaseHLBludgeonWeapon) + +DEFINE_FIELD(m_flDelayedFire, FIELD_TIME), +DEFINE_FIELD(m_bShotDelayed, FIELD_BOOLEAN), + +END_DATADESC() +#endif // MAPBASE + #define BLUDGEON_HULL_DIM 16 static const Vector g_bludgeonMins(-BLUDGEON_HULL_DIM,-BLUDGEON_HULL_DIM,-BLUDGEON_HULL_DIM); @@ -39,6 +48,9 @@ static const Vector g_bludgeonMaxs(BLUDGEON_HULL_DIM,BLUDGEON_HULL_DIM,BLUDGEON_ CBaseHLBludgeonWeapon::CBaseHLBludgeonWeapon() { m_bFiresUnderwater = true; +#ifdef MAPBASE + m_bShotDelayed = false; +#endif // MAPBASE } //----------------------------------------------------------------------------- @@ -93,6 +105,22 @@ void CBaseHLBludgeonWeapon::ItemPostFrame( void ) if ( pOwner == NULL ) return; +#ifdef MAPBASE + if (pOwner->HasSpawnFlags( SF_PLAYER_SUPPRESS_FIRING )) + { + m_bShotDelayed = false; + WeaponIdle(); + return; + } + + // See if we need to fire off our secondary round + if (m_bShotDelayed) + { + if (gpGlobals->curtime > m_flDelayedFire) + DelayedAttack(); + } + else +#endif if ( (pOwner->m_nButtons & IN_ATTACK) && (m_flNextPrimaryAttack <= gpGlobals->curtime) ) { PrimaryAttack(); @@ -154,7 +182,12 @@ void CBaseHLBludgeonWeapon::Hit( trace_t &traceHit, Activity nHitActivity, bool pPlayer->EyeVectors( &hitDirection, NULL, NULL ); VectorNormalize( hitDirection ); - CTakeDamageInfo info( GetOwner(), GetOwner(), GetDamageForActivity( nHitActivity ), DMG_CLUB ); +#ifdef MAPBASE + CTakeDamageInfo info(GetOwner(), GetOwner(), GetDamageForActivity(nHitActivity), GetDamageType()); +#else + CTakeDamageInfo info(GetOwner(), GetOwner(), GetDamageForActivity(nHitActivity), DMG_CLUB); +#endif // MAPBASE + if( pPlayer && pHitEntity->IsNPC() ) { @@ -226,7 +259,7 @@ Activity CBaseHLBludgeonWeapon::ChooseIntersectionPointAndActivity( trace_t &hit } - return ACT_VM_HITCENTER; + return GetPrimaryAttackActivity(); } //----------------------------------------------------------------------------- @@ -284,7 +317,6 @@ void CBaseHLBludgeonWeapon::ImpactEffect( trace_t &traceHit ) UTIL_ImpactTrace( &traceHit, DMG_CLUB ); } - //------------------------------------------------------------------------------ // Purpose : Starts the swing of the weapon and determines the animation // Input : bIsSecondary - is this a secondary attack? @@ -307,10 +339,14 @@ void CBaseHLBludgeonWeapon::Swing( int bIsSecondary ) Vector swingEnd = swingStart + forward * GetRange(); UTIL_TraceLine( swingStart, swingEnd, MASK_SHOT_HULL, pOwner, COLLISION_GROUP_NONE, &traceHit ); - Activity nHitActivity = ACT_VM_HITCENTER; + Activity nHitActivity = GetPrimaryAttackActivity(); // Like bullets, bludgeon traces have to trace against triggers. - CTakeDamageInfo triggerInfo( GetOwner(), GetOwner(), GetDamageForActivity( nHitActivity ), DMG_CLUB ); +#ifdef MAPBASE + CTakeDamageInfo triggerInfo(GetOwner(), GetOwner(), GetDamageForActivity(nHitActivity), GetDamageType()); +#else + CTakeDamageInfo triggerInfo(GetOwner(), GetOwner(), GetDamageForActivity(nHitActivity), DMG_CLUB); +#endif // MAPBASE triggerInfo.SetDamagePosition( traceHit.startpos ); triggerInfo.SetDamageForce( forward ); TraceAttackToTriggers( triggerInfo, traceHit.startpos, traceHit.endpos, forward ); @@ -361,16 +397,20 @@ void CBaseHLBludgeonWeapon::Swing( int bIsSecondary ) { nHitActivity = bIsSecondary ? ACT_VM_MISSCENTER2 : ACT_VM_MISSCENTER; +#ifndef MAPBASE // We want to test the first swing again Vector testEnd = swingStart + forward * GetRange(); - + // See if we happened to hit water - ImpactWater( swingStart, testEnd ); + ImpactWater(swingStart, testEnd); +#endif // !MAPBASE } +#ifndef MAPBASE else { Hit( traceHit, nHitActivity, bIsSecondary ? true : false ); } +#endif // Send the anim SendWeaponAnim( nHitActivity ); @@ -379,6 +419,132 @@ void CBaseHLBludgeonWeapon::Swing( int bIsSecondary ) m_flNextPrimaryAttack = gpGlobals->curtime + GetFireRate(); m_flNextSecondaryAttack = gpGlobals->curtime + SequenceDuration(); +#ifndef MAPBASE //Play swing sound WeaponSound( SINGLE ); +#endif + +#ifdef MAPBASE + pOwner->SetAnimation( PLAYER_ATTACK1 ); + + if (GetHitDelay() > 0.f) + { + //Play swing sound + WeaponSound(SINGLE); + + m_flDelayedFire = gpGlobals->curtime + GetHitDelay(); + m_bShotDelayed = true; + } + else + { + if (traceHit.fraction == 1.0f) + { + // We want to test the first swing again + Vector testEnd = swingStart + forward * GetRange(); + + //Play swing sound + WeaponSound(SINGLE); + + // See if we happened to hit water + ImpactWater(swingStart, testEnd); + } + else + { + // Other melee sounds + if (traceHit.m_pEnt && traceHit.m_pEnt->IsWorld()) + WeaponSound(MELEE_HIT_WORLD); + else if (traceHit.m_pEnt && !traceHit.m_pEnt->PassesDamageFilter(triggerInfo)) + WeaponSound(MELEE_MISS); + else + WeaponSound(MELEE_HIT); + + Hit(traceHit, nHitActivity, bIsSecondary ? true : false); + } + } +#endif } + +#ifdef MAPBASE +void CBaseHLBludgeonWeapon::DelayedAttack(void) +{ + m_bShotDelayed = false; + + trace_t traceHit; + + // Try a ray + CBasePlayer* pOwner = ToBasePlayer(GetOwner()); + if (!pOwner) + return; + + pOwner->RumbleEffect(RUMBLE_CROWBAR_SWING, 0, RUMBLE_FLAG_RESTART); + + Vector swingStart = pOwner->Weapon_ShootPosition(); + Vector forward; + + forward = pOwner->GetAutoaimVector(AUTOAIM_SCALE_DEFAULT, GetRange()); + + Vector swingEnd = swingStart + forward * GetRange(); + UTIL_TraceLine(swingStart, swingEnd, MASK_SHOT_HULL, pOwner, COLLISION_GROUP_NONE, &traceHit); + + if (traceHit.fraction == 1.0) + { + float bludgeonHullRadius = 1.732f * BLUDGEON_HULL_DIM; // hull is +/- 16, so use cuberoot of 2 to determine how big the hull is from center to the corner point + + // Back off by hull "radius" + swingEnd -= forward * bludgeonHullRadius; + + UTIL_TraceHull(swingStart, swingEnd, g_bludgeonMins, g_bludgeonMaxs, MASK_SHOT_HULL, pOwner, COLLISION_GROUP_NONE, &traceHit); + if (traceHit.fraction < 1.0 && traceHit.m_pEnt) + { + Vector vecToTarget = traceHit.m_pEnt->GetAbsOrigin() - swingStart; + VectorNormalize(vecToTarget); + + float dot = vecToTarget.Dot(forward); + + // YWB: Make sure they are sort of facing the guy at least... + if (dot < 0.70721f) + { + // Force amiss + traceHit.fraction = 1.0f; + } + else + { + ChooseIntersectionPointAndActivity(traceHit, g_bludgeonMins, g_bludgeonMaxs, pOwner); + } + } + } + + if (traceHit.fraction == 1.0f) + { + // We want to test the first swing again + Vector testEnd = swingStart + forward * GetRange(); + + // See if we happened to hit water + ImpactWater(swingStart, testEnd); + } + else + { + CTakeDamageInfo triggerInfo(GetOwner(), GetOwner(), GetDamageForActivity(GetActivity()), GetDamageType()); + triggerInfo.SetDamagePosition(traceHit.startpos); + triggerInfo.SetDamageForce(forward); + + // Other melee sounds + if (traceHit.m_pEnt && traceHit.m_pEnt->IsWorld()) + WeaponSound(MELEE_HIT_WORLD); + else if (traceHit.m_pEnt && !traceHit.m_pEnt->PassesDamageFilter(triggerInfo)) + WeaponSound(MELEE_MISS); + else + WeaponSound(MELEE_HIT); + + Hit(traceHit, GetActivity(), false); + } +} + +bool CBaseHLBludgeonWeapon::CanHolster(void) +{ + if (m_bShotDelayed) + return false; + + return BaseClass::CanHolster(); +} +#endif // MAPBASE diff --git a/src/game/server/basebludgeonweapon.h b/src/game/server/basebludgeonweapon.h index 33f7e5bb..1f06a7f8 100644 --- a/src/game/server/basebludgeonweapon.h +++ b/src/game/server/basebludgeonweapon.h @@ -23,6 +23,9 @@ public: CBaseHLBludgeonWeapon(); DECLARE_SERVERCLASS(); +#ifdef MAPBASE + DECLARE_DATADESC(); +#endif // MAPBASE virtual void Spawn( void ); virtual void Precache( void ); @@ -30,6 +33,9 @@ public: //Attack functions virtual void PrimaryAttack( void ); virtual void SecondaryAttack( void ); +#ifdef MAPBASE + void DelayedAttack(void); +#endif // MAPBASE virtual void ItemPostFrame( void ); @@ -44,6 +50,12 @@ public: virtual int CapabilitiesGet( void ); virtual int WeaponMeleeAttack1Condition( float flDot, float flDist ); +#ifdef MAPBASE + virtual int GetDamageType() { return DMG_CLUB; } + virtual float GetHitDelay() { return 0.f; } + virtual bool CanHolster(void); +#endif // MAPBASE + protected: virtual void ImpactEffect( trace_t &trace ); @@ -52,6 +64,11 @@ private: void Swing( int bIsSecondary ); void Hit( trace_t &traceHit, Activity nHitActivity, bool bIsSecondary ); Activity ChooseIntersectionPointAndActivity( trace_t &hitTrace, const Vector &mins, const Vector &maxs, CBasePlayer *pOwner ); + +#ifdef MAPBASE + float m_flDelayedFire; + bool m_bShotDelayed; +#endif // MAPBASE }; #endif diff --git a/src/game/server/basecombatcharacter.cpp b/src/game/server/basecombatcharacter.cpp index 038d6d3e..8e9400f4 100644 --- a/src/game/server/basecombatcharacter.cpp +++ b/src/game/server/basecombatcharacter.cpp @@ -72,12 +72,17 @@ ConVar ai_force_serverside_ragdoll( "ai_force_serverside_ragdoll", "0" ); ConVar nb_last_area_update_tolerance( "nb_last_area_update_tolerance", "4.0", FCVAR_CHEAT, "Distance a character needs to travel in order to invalidate cached area" ); // 4.0 tested as sweet spot (for wanderers, at least). More resulted in little benefit, less quickly diminished benefit [7/31/2008 tom] +#ifdef MAPBASE +// ShouldUseVisibilityCache() is used as an actual function now +ConVar ai_use_visibility_cache( "ai_use_visibility_cache", "1" ); +#else #ifndef _RETAIL ConVar ai_use_visibility_cache( "ai_use_visibility_cache", "1" ); #define ShouldUseVisibilityCache() ai_use_visibility_cache.GetBool() #else #define ShouldUseVisibilityCache() true #endif +#endif BEGIN_DATADESC( CBaseCombatCharacter ) @@ -101,19 +106,124 @@ BEGIN_DATADESC( CBaseCombatCharacter ) DEFINE_FIELD( m_flDamageAccumulator, FIELD_FLOAT ), DEFINE_INPUT( m_impactEnergyScale, FIELD_FLOAT, "physdamagescale" ), DEFINE_FIELD( m_CurrentWeaponProficiency, FIELD_INTEGER), +#ifdef MAPBASE + DEFINE_INPUT( m_ProficiencyOverride, FIELD_INTEGER, "SetProficiencyOverride"), +#endif DEFINE_UTLVECTOR( m_Relationship, FIELD_EMBEDDED), DEFINE_AUTO_ARRAY( m_iAmmo, FIELD_INTEGER ), DEFINE_AUTO_ARRAY( m_hMyWeapons, FIELD_EHANDLE ), DEFINE_FIELD( m_hActiveWeapon, FIELD_EHANDLE ), +#ifdef MAPBASE + DEFINE_INPUT( m_bForceServerRagdoll, FIELD_BOOLEAN, "SetForceServerRagdoll" ), +#else DEFINE_FIELD( m_bForceServerRagdoll, FIELD_BOOLEAN ), +#endif DEFINE_FIELD( m_bPreventWeaponPickup, FIELD_BOOLEAN ), +#ifndef MAPBASE // See CBaseEntity::InputKilledNPC() DEFINE_INPUTFUNC( FIELD_VOID, "KilledNPC", InputKilledNPC ), +#endif + +#ifdef MAPBASE + DEFINE_INPUTFUNC( FIELD_INTEGER, "SetBloodColor", InputSetBloodColor ), + + DEFINE_INPUTFUNC( FIELD_STRING, "SetRelationship", InputSetRelationship ), + + DEFINE_INPUTFUNC( FIELD_VOID, "HolsterWeapon", InputHolsterWeapon ), + DEFINE_INPUTFUNC( FIELD_VOID, "HolsterAndDestroyWeapon", InputHolsterAndDestroyWeapon ), + DEFINE_INPUTFUNC( FIELD_STRING, "UnholsterWeapon", InputUnholsterWeapon ), + DEFINE_INPUTFUNC( FIELD_STRING, "SwitchToWeapon", InputSwitchToWeapon ), + + DEFINE_INPUTFUNC( FIELD_STRING, "GiveWeapon", InputGiveWeapon ), + DEFINE_INPUTFUNC( FIELD_STRING, "DropWeapon", InputDropWeapon ), + DEFINE_INPUTFUNC( FIELD_EHANDLE, "PickupWeaponInstant", InputPickupWeaponInstant ), + DEFINE_OUTPUT( m_OnWeaponEquip, "OnWeaponEquip" ), + DEFINE_OUTPUT( m_OnWeaponDrop, "OnWeaponDrop" ), + + DEFINE_OUTPUT( m_OnKilledEnemy, "OnKilledEnemy" ), + DEFINE_OUTPUT( m_OnKilledPlayer, "OnKilledPlayer" ), + DEFINE_OUTPUT( m_OnHealthChanged, "OnHealthChanged" ), +#endif END_DATADESC() +#ifdef MAPBASE_VSCRIPT +ScriptHook_t CBaseCombatCharacter::g_Hook_RelationshipType; +ScriptHook_t CBaseCombatCharacter::g_Hook_RelationshipPriority; + +BEGIN_ENT_SCRIPTDESC( CBaseCombatCharacter, CBaseFlex, "The base class shared by players and NPCs." ) + + DEFINE_SCRIPTFUNC_NAMED( ScriptGetActiveWeapon, "GetActiveWeapon", "Get the character's active weapon entity." ) + DEFINE_SCRIPTFUNC( WeaponCount, "Get the number of weapons a character possesses." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptGetWeapon, "GetWeapon", "Get a specific weapon in the character's inventory." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptGetWeaponByType, "FindWeapon", "Find a specific weapon in the character's inventory by its classname." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptGetAllWeapons, "GetAllWeapons", "Get the character's weapon inventory." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptGetCurrentWeaponProficiency, "GetCurrentWeaponProficiency", "Get the character's current proficiency (accuracy) with their current weapon." ) + + DEFINE_SCRIPTFUNC_NAMED( Weapon_ShootPosition, "ShootPosition", "Get the character's shoot position." ) + DEFINE_SCRIPTFUNC_NAMED( Weapon_DropAll, "DropAllWeapons", "Make the character drop all of its weapons." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptEquipWeapon, "EquipWeapon", "Make the character equip the specified weapon entity. If they don't already own the weapon, they will acquire it instantly." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptDropWeapon, "DropWeapon", "Make the character drop the specified weapon entity if they own it." ) + + DEFINE_SCRIPTFUNC_NAMED( ScriptGiveAmmo, "GiveAmmo", "Gives the specified amount of the specified ammo type. The third parameter is whether or not to suppress the ammo pickup sound. Returns the amount of ammo actually given, which is 0 if the player's ammo for this type is already full." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptRemoveAmmo, "RemoveAmmo", "Removes the specified amount of the specified ammo type." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptGetAmmoCount, "GetAmmoCount", "Get the ammo count of the specified ammo type." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptSetAmmoCount, "SetAmmoCount", "Set the ammo count of the specified ammo type." ) + + DEFINE_SCRIPTFUNC( DoMuzzleFlash, "Does a muzzle flash." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptGetAttackSpread, "GetAttackSpread", "Get the attack spread." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptGetSpreadBias, "GetSpreadBias", "Get the spread bias." ) + + DEFINE_SCRIPTFUNC_NAMED( ScriptRelationType, "GetRelationship", "Get a character's relationship to a specific entity." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptRelationPriority, "GetRelationPriority", "Get a character's relationship priority for a specific entity." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptSetRelationship, "SetRelationship", "Set a character's relationship with a specific entity." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptSetClassRelationship, "SetClassRelationship", "Set a character's relationship with a specific Classify() class." ) + + DEFINE_SCRIPTFUNC_NAMED( ScriptGetVehicleEntity, "GetVehicleEntity", "Get the entity for a character's current vehicle if they're in one." ) + + DEFINE_SCRIPTFUNC_NAMED( ScriptInViewCone, "InViewCone", "Check if the specified position is in the character's viewcone." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptEntInViewCone, "EntInViewCone", "Check if the specified entity is in the character's viewcone." ) + + DEFINE_SCRIPTFUNC_NAMED( ScriptInAimCone, "InAimCone", "Check if the specified position is in the character's aim cone." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptEntInViewCone, "EntInAimCone", "Check if the specified entity is in the character's aim cone." ) + + DEFINE_SCRIPTFUNC_NAMED( ScriptBodyAngles, "BodyAngles", "Get the body's angles." ) + DEFINE_SCRIPTFUNC( BodyDirection2D, "Get the body's 2D direction." ) + DEFINE_SCRIPTFUNC( BodyDirection3D, "Get the body's 3D direction." ) + DEFINE_SCRIPTFUNC( HeadDirection2D, "Get the head's 2D direction." ) + DEFINE_SCRIPTFUNC( HeadDirection3D, "Get the head's 3D direction." ) + DEFINE_SCRIPTFUNC( EyeDirection2D, "Get the eyes' 2D direction." ) + DEFINE_SCRIPTFUNC( EyeDirection3D, "Get the eyes' 3D direction." ) + + DEFINE_SCRIPTFUNC( LastHitGroup, "Get the last hitgroup." ) + + DEFINE_SCRIPTFUNC_NAMED( ScriptGetLastKnownArea, "GetLastKnownArea", "Return the last nav area occupied - NULL if unknown" ) + +#ifdef GLOWS_ENABLE + DEFINE_SCRIPTFUNC( AddGlowEffect, "" ) + DEFINE_SCRIPTFUNC( RemoveGlowEffect, "" ) + DEFINE_SCRIPTFUNC( IsGlowEffectActive, "" ) + DEFINE_SCRIPTFUNC( SetGlowColor, "" ) +#endif + + // + // Hooks + // + BEGIN_SCRIPTHOOK( CBaseCombatCharacter::g_Hook_RelationshipType, "RelationshipType", FIELD_INTEGER, "Called when a character's relationship to another entity is requested. Returning a disposition will make the game use that disposition instead of the default relationship. (note: 'default' in this case includes overrides from ai_relationship/SetRelationship)" ) + DEFINE_SCRIPTHOOK_PARAM( "entity", FIELD_HSCRIPT ) + DEFINE_SCRIPTHOOK_PARAM( "def", FIELD_INTEGER ) + END_SCRIPTHOOK() + + BEGIN_SCRIPTHOOK( CBaseCombatCharacter::g_Hook_RelationshipPriority, "RelationshipPriority", FIELD_INTEGER, "Called when a character's relationship priority for another entity is requested. Returning a number will make the game use that priority instead of the default priority. (note: 'default' in this case includes overrides from ai_relationship/SetRelationship)" ) + DEFINE_SCRIPTHOOK_PARAM( "entity", FIELD_HSCRIPT ) + DEFINE_SCRIPTHOOK_PARAM( "def", FIELD_INTEGER ) + END_SCRIPTHOOK() + +END_SCRIPTDESC(); +#endif + BEGIN_SIMPLE_DATADESC( Relationship_t ) DEFINE_FIELD( entity, FIELD_EHANDLE ), @@ -196,6 +306,8 @@ END_SEND_TABLE(); IMPLEMENT_SERVERCLASS_ST(CBaseCombatCharacter, DT_BaseCombatCharacter) #ifdef GLOWS_ENABLE SendPropBool( SENDINFO( m_bGlowEnabled ) ), + SendPropVector( SENDINFO( m_GlowColor ), 8, 0, 0, 1 ), + SendPropFloat( SENDINFO( m_GlowAlpha ) ), #endif // GLOWS_ENABLE // Data that only gets sent to the local player. SendPropDataTable( "bcc_localdata", 0, &REFERENCE_SEND_TABLE(DT_BCCLocalPlayerExclusive), SendProxy_SendBaseCombatCharacterLocalDataTable ), @@ -229,6 +341,21 @@ int CBaseCombatCharacter::GetInteractionID(void) return (m_lastInteraction); } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: New method of adding interactions which allows their name to be available (currently used for VScript) +//----------------------------------------------------------------------------- +void CBaseCombatCharacter::AddInteractionWithString( int &interaction, const char *szName ) +{ + interaction = GetInteractionID(); + + if (g_pScriptVM) + { + ScriptRegisterConstantNamed( g_pScriptVM, interaction, szName, "An interaction which could be used with HandleInteraction or DispatchInteraction. NOTE: These are usually only initialized by certain types of NPCs when an instance of one spawns in the level for the first time!!! (the fact you're seeing this one means there was an NPC in the level which initialized it)" ); + } +} +#endif + // ============================================================================ bool CBaseCombatCharacter::HasHumanGibs( void ) { @@ -335,11 +462,16 @@ bool CBaseCombatCharacter::FVisible( CBaseEntity *pEntity, int traceMask, CBaseE { VPROF( "CBaseCombatCharacter::FVisible" ); +#ifdef MAPBASE + if ( traceMask != MASK_BLOCKLOS || !ShouldUseVisibilityCache( pEntity ) || pEntity == this || !ai_use_visibility_cache.GetBool() + ) +#else if ( traceMask != MASK_BLOCKLOS || !ShouldUseVisibilityCache() || pEntity == this #if defined(HL2_DLL) || Classify() == CLASS_BULLSEYE || pEntity->Classify() == CLASS_BULLSEYE #endif ) +#endif { return BaseClass::FVisible( pEntity, traceMask, ppBlocker ); } @@ -445,6 +577,17 @@ void CBaseCombatCharacter::ResetVisibilityCache( CBaseCombatCharacter *pBCC ) } } +#ifdef MAPBASE +bool CBaseCombatCharacter::ShouldUseVisibilityCache( CBaseEntity *pEntity ) +{ +#ifdef HL2_DLL + return Classify() != CLASS_BULLSEYE && pEntity->Classify() != CLASS_BULLSEYE; +#else + return true; +#endif +} +#endif + #ifdef PORTAL bool CBaseCombatCharacter::FVisibleThroughPortal( const CProp_Portal *pPortal, CBaseEntity *pEntity, int traceMask, CBaseEntity **ppBlocker ) { @@ -750,6 +893,8 @@ CBaseCombatCharacter::CBaseCombatCharacter( void ) #ifdef GLOWS_ENABLE m_bGlowEnabled.Set( false ); + m_GlowColor.GetForModify().Init( 0.76f, 0.76f, 0.76f ); + m_GlowAlpha.Set(1.0f); #endif // GLOWS_ENABLE } @@ -1139,6 +1284,11 @@ bool CTraceFilterMelee::ShouldHitEntity( IHandleEntity *pHandleEntity, int conte if ( pEntity->m_takedamage == DAMAGE_NO ) return false; +#ifdef MAPBASE // Moved from CheckTraceHullAttack() + if( m_pPassEnt && !pEntity->CanBeHitByMeleeAttack( const_cast(EntityFromEntityHandle( m_pPassEnt ) ) ) ) + return false; +#endif + // FIXME: Do not translate this to the driver because the driver only accepts damage from the vehicle // Translate the vehicle into its driver for damage /* @@ -1166,7 +1316,11 @@ bool CTraceFilterMelee::ShouldHitEntity( IHandleEntity *pHandleEntity, int conte if ( pBCC && pVictimBCC ) { // Can only damage other NPCs that we hate +#ifdef MAPBASE + if ( m_bDamageAnyNPC || pBCC->IRelationType( pEntity ) <= D_FR ) +#else if ( m_bDamageAnyNPC || pBCC->IRelationType( pEntity ) == D_HT ) +#endif { if ( info.GetDamage() ) { @@ -1182,6 +1336,10 @@ bool CTraceFilterMelee::ShouldHitEntity( IHandleEntity *pHandleEntity, int conte } else { +#ifdef MAPBASE + // Do not override an existing hit entity + if (!m_pHit) +#endif m_pHit = pEntity; // Make sure if the player is holding this, he drops it @@ -1257,11 +1415,13 @@ CBaseEntity *CBaseCombatCharacter::CheckTraceHullAttack( const Vector &vStart, c pEntity = traceFilter.m_pHit; } +#ifndef MAPBASE // Moved to CTraceFilterMelee if( pEntity && !pEntity->CanBeHitByMeleeAttack(this) ) { // If we touched something, but it shouldn't be hit, return nothing. pEntity = NULL; } +#endif return pEntity; @@ -1467,6 +1627,15 @@ void CBaseCombatCharacter::FixupBurningServerRagdoll( CBaseEntity *pRagdoll ) } } +inline bool CBaseCombatCharacter::ShouldFadeServerRagdolls() const +{ +#ifdef MAPBASE + return IsNPC() ? HasSpawnFlags( SF_NPC_FADE_CORPSE ) : true; +#else + return true; +#endif +} + bool CBaseCombatCharacter::BecomeRagdollBoogie( CBaseEntity *pKiller, const Vector &forceVector, float duration, int flags ) { Assert( CanBecomeRagdoll() ); @@ -1475,7 +1644,7 @@ bool CBaseCombatCharacter::BecomeRagdollBoogie( CBaseEntity *pKiller, const Vect info.SetDamageForce( forceVector ); - CBaseEntity *pRagdoll = CreateServerRagdoll( this, 0, info, COLLISION_GROUP_INTERACTIVE_DEBRIS, true ); + CBaseEntity *pRagdoll = CreateServerRagdoll( this, 0, info, COLLISION_GROUP_INTERACTIVE_DEBRIS, ShouldFadeServerRagdolls() ); pRagdoll->SetCollisionBounds( CollisionProp()->OBBMins(), CollisionProp()->OBBMaxs() ); @@ -1489,6 +1658,30 @@ bool CBaseCombatCharacter::BecomeRagdollBoogie( CBaseEntity *pKiller, const Vect return true; } +#ifdef MAPBASE +CBaseEntity *CBaseCombatCharacter::BecomeRagdollBoogie( CBaseEntity *pKiller, const Vector &forceVector, float duration, int flags, const Vector *vecColor ) +{ + Assert( CanBecomeRagdoll() ); + + CTakeDamageInfo info( pKiller, pKiller, 1.0f, DMG_GENERIC ); + + info.SetDamageForce( forceVector ); + + CBaseEntity *pRagdoll = CreateServerRagdoll( this, 0, info, COLLISION_GROUP_INTERACTIVE_DEBRIS, ShouldFadeServerRagdolls() ); + + pRagdoll->SetCollisionBounds( CollisionProp()->OBBMins(), CollisionProp()->OBBMaxs() ); + + CBaseEntity *pBoogie = CRagdollBoogie::Create( pRagdoll, 200, gpGlobals->curtime, duration, flags, vecColor ); + + CTakeDamageInfo ragdollInfo( pKiller, pKiller, 10000.0, DMG_GENERIC | DMG_REMOVENORAGDOLL ); + ragdollInfo.SetDamagePosition( WorldSpaceCenter() ); + ragdollInfo.SetDamageForce( Vector( 0, 0, 1 ) ); + TakeDamage( ragdollInfo ); + + return pBoogie; +} +#endif + //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- @@ -1523,7 +1716,7 @@ bool CBaseCombatCharacter::BecomeRagdoll( const CTakeDamageInfo &info, const Vec #endif // in single player create ragdolls on the server when the player hits someone // with their vehicle - for more dramatic death/collisions - CBaseEntity *pRagdoll = CreateServerRagdoll( this, m_nForceBone, info2, COLLISION_GROUP_INTERACTIVE_DEBRIS, true ); + CBaseEntity *pRagdoll = CreateServerRagdoll( this, m_nForceBone, info2, COLLISION_GROUP_INTERACTIVE_DEBRIS, ShouldFadeServerRagdolls() ); FixupBurningServerRagdoll( pRagdoll ); RemoveDeferred(); return true; @@ -1537,7 +1730,7 @@ bool CBaseCombatCharacter::BecomeRagdoll( const CTakeDamageInfo &info, const Vec // Burning corpses are server-side in episodic, if we're in darkness mode if ( IsOnFire() && HL2GameRules()->IsAlyxInDarknessMode() ) { - CBaseEntity *pRagdoll = CreateServerRagdoll( this, m_nForceBone, newinfo, COLLISION_GROUP_DEBRIS ); + CBaseEntity *pRagdoll = CreateServerRagdoll( this, m_nForceBone, newinfo, COLLISION_GROUP_DEBRIS, ShouldFadeServerRagdolls() ); FixupBurningServerRagdoll( pRagdoll ); RemoveDeferred(); return true; @@ -1558,7 +1751,7 @@ bool CBaseCombatCharacter::BecomeRagdoll( const CTakeDamageInfo &info, const Vec return false; //FIXME: This is fairly leafy to be here, but time is short! - CBaseEntity *pRagdoll = CreateServerRagdoll( this, m_nForceBone, newinfo, COLLISION_GROUP_INTERACTIVE_DEBRIS, true ); + CBaseEntity *pRagdoll = CreateServerRagdoll( this, m_nForceBone, newinfo, COLLISION_GROUP_INTERACTIVE_DEBRIS, ShouldFadeServerRagdolls() ); FixupBurningServerRagdoll( pRagdoll ); PhysSetEntityGameFlags( pRagdoll, FVPHYSICS_NO_SELF_COLLISIONS ); RemoveDeferred(); @@ -1568,7 +1761,7 @@ bool CBaseCombatCharacter::BecomeRagdoll( const CTakeDamageInfo &info, const Vec if( hl2_episodic.GetBool() && Classify() == CLASS_PLAYER_ALLY_VITAL ) { - CreateServerRagdoll( this, m_nForceBone, newinfo, COLLISION_GROUP_INTERACTIVE_DEBRIS, true ); + CreateServerRagdoll( this, m_nForceBone, newinfo, COLLISION_GROUP_INTERACTIVE_DEBRIS, ShouldFadeServerRagdolls() ); RemoveDeferred(); return true; } @@ -1597,7 +1790,23 @@ void CBaseCombatCharacter::Event_Killed( const CTakeDamageInfo &info ) CRagdollMagnet *pMagnet = CRagdollMagnet::FindBestMagnet( this ); if( pMagnet ) { +#ifdef MAPBASE + if (pMagnet->BoneTarget() && pMagnet->BoneTarget()[0] != '\0') + { + int iBone = -1; + forceVector += pMagnet->GetForceVector( this, &iBone ); + if (iBone != -1) + m_nForceBone = GetPhysicsBone(iBone); + } + else + { + forceVector += pMagnet->GetForceVector( this ); + } + + pMagnet->m_OnUsed.Set(forceVector, this, pMagnet); +#else forceVector += pMagnet->GetForceVector( this ); +#endif } CBaseCombatWeapon *pDroppedWeapon = m_hActiveWeapon.Get(); @@ -1616,7 +1825,14 @@ void CBaseCombatCharacter::Event_Killed( const CTakeDamageInfo &info ) // if flagged to drop a health kit if (HasSpawnFlags(SF_NPC_DROP_HEALTHKIT)) { - CBaseEntity::Create( "item_healthvial", GetAbsOrigin(), GetAbsAngles() ); + CBaseEntity *pItem = CBaseEntity::Create( "item_healthvial", GetAbsOrigin(), GetAbsAngles() ); + if (pItem) + { +#ifdef MAPBASE + if (MyNPCPointer()) + MyNPCPointer()->m_OnItemDrop.Set( pItem, pItem, this ); +#endif + } } // clear the deceased's sound channels.(may have been firing or reloading when killed) EmitSound( "BaseCombatCharacter.StopWeaponSounds" ); @@ -1650,7 +1866,11 @@ void CBaseCombatCharacter::Event_Killed( const CTakeDamageInfo &info ) } } #ifdef HL2_DLL +#ifdef MAPBASE + else if ( PlayerHasMegaPhysCannon() && GlobalEntity_GetCounter("super_phys_gun") != 1 ) +#else else if ( PlayerHasMegaPhysCannon() ) +#endif { if ( pDroppedWeapon ) { @@ -1910,7 +2130,11 @@ void CBaseCombatCharacter::Weapon_Drop( CBaseCombatWeapon *pWeapon, const Vector return; // If I'm an NPC, fill the weapon with ammo before I drop it. +#ifdef MAPBASE + if ( GetFlags() & FL_NPC && !pWeapon->HasSpawnFlags(SF_WEAPON_PRESERVE_AMMO) ) +#else if ( GetFlags() & FL_NPC ) +#endif { if ( pWeapon->UsesClipsForAmmo1() ) { @@ -2046,6 +2270,10 @@ void CBaseCombatCharacter::Weapon_Drop( CBaseCombatWeapon *pWeapon, const Vector pWeapon->Drop( vecThrow ); Weapon_Detach( pWeapon ); +#ifdef MAPBASE + m_OnWeaponDrop.FireOutput(pWeapon, this); +#endif + if ( HasSpawnFlags( SF_NPC_NO_WEAPON_DROP ) ) { // Don't drop weapons when the super physgun is happening. @@ -2068,6 +2296,227 @@ void CBaseCombatCharacter::SetLightingOriginRelative( CBaseEntity *pLightingOrig } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: Gives character new weapon and equips it +// Input : New weapon +//----------------------------------------------------------------------------- +void CBaseCombatCharacter::Weapon_Equip( CBaseCombatWeapon *pWeapon ) +{ + Weapon_HandleEquip(pWeapon); + + // Players don't automatically holster their current weapon + if ( IsPlayer() == false ) + { + if ( m_hActiveWeapon ) + { + m_hActiveWeapon->Holster(); + // FIXME: isn't this handeled by the weapon? + m_hActiveWeapon->AddEffects( EF_NODRAW ); + } + SetActiveWeapon( pWeapon ); + m_hActiveWeapon->RemoveEffects( EF_NODRAW ); + + } + + WeaponProficiency_t proficiency; + proficiency = CalcWeaponProficiency( pWeapon ); + + if( weapon_showproficiency.GetBool() != 0 ) + { + Msg("%s equipped with %s, proficiency is %s\n", GetClassname(), pWeapon->GetClassname(), GetWeaponProficiencyName( proficiency ) ); + } + + SetCurrentWeaponProficiency( proficiency ); +} + +//----------------------------------------------------------------------------- +// Purpose: Puts a new weapon in the inventory +// Input : New weapon +//----------------------------------------------------------------------------- +void CBaseCombatCharacter::Weapon_EquipHolstered( CBaseCombatWeapon *pWeapon ) +{ + Weapon_HandleEquip(pWeapon); + pWeapon->AddEffects( EF_NODRAW ); +} + +//----------------------------------------------------------------------------- +// Purpose: Adds new weapon to the character +// Input : New weapon +//----------------------------------------------------------------------------- +void CBaseCombatCharacter::Weapon_HandleEquip( CBaseCombatWeapon *pWeapon ) +{ + // Add the weapon to my weapon inventory + if (IsPlayer()) + { + // This code drops existing weapons that are in the same bucket and bucket position. + // This doesn't really harm anything since that situation would've broken the HUD anyway. + // + // It goes through every single index in case there's a NULL pointer in between weapons. + int iFirstNullIndex = -1; + for (int i=0;iGetSlot() == m_hMyWeapons[i]->GetSlot() && + pWeapon->GetPosition() == m_hMyWeapons[i]->GetPosition()) + { + // Replace our existing weapon in this slot + Weapon_Drop(m_hMyWeapons[i]); + { + // We found a slot, we don't care about the first null index anymore + iFirstNullIndex = -1; + + m_hMyWeapons.Set( i, pWeapon ); + break; + } + } + } + } + + if (iFirstNullIndex != -1) + m_hMyWeapons.Set( iFirstNullIndex, pWeapon ); + } + else + { + for (int i=0;iChangeTeam( GetTeamNumber() ); + + bool bPreserveAmmo = pWeapon->HasSpawnFlags(SF_WEAPON_PRESERVE_AMMO); + if (!bPreserveAmmo) + { + // ---------------------- + // Give Primary Ammo + // ---------------------- + // If gun doesn't use clips, just give ammo + if (pWeapon->GetMaxClip1() == -1) + { +#ifdef HL2_DLL + if( FStrEq(STRING(gpGlobals->mapname), "d3_c17_09") && FClassnameIs(pWeapon, "weapon_rpg") && pWeapon->NameMatches("player_spawn_items") ) + { + // !!!HACK - Don't give any ammo with the spawn equipment RPG in d3_c17_09. This is a chapter + // start and the map is way to easy if you start with 3 RPG rounds. It's fine if a player conserves + // them and uses them here, but it's not OK to start with enough ammo to bypass the snipers completely. + GiveAmmo( 0, pWeapon->m_iPrimaryAmmoType); + } + else +#endif // HL2_DLL + GiveAmmo(pWeapon->GetDefaultClip1(), pWeapon->m_iPrimaryAmmoType); + } + // If default ammo given is greater than clip + // size, fill clips and give extra ammo + else if ( pWeapon->GetDefaultClip1() > pWeapon->GetMaxClip1() ) + { + pWeapon->m_iClip1 = pWeapon->GetMaxClip1(); + GiveAmmo( (pWeapon->GetDefaultClip1() - pWeapon->GetMaxClip1()), pWeapon->m_iPrimaryAmmoType); + } + + // ---------------------- + // Give Secondary Ammo + // ---------------------- + // If gun doesn't use clips, just give ammo + if (pWeapon->GetMaxClip2() == -1) + { + GiveAmmo(pWeapon->GetDefaultClip2(), pWeapon->m_iSecondaryAmmoType); + } + // If default ammo given is greater than clip + // size, fill clips and give extra ammo + else if ( pWeapon->GetDefaultClip2() > pWeapon->GetMaxClip2() ) + { + pWeapon->m_iClip2 = pWeapon->GetMaxClip2(); + GiveAmmo( (pWeapon->GetDefaultClip2() - pWeapon->GetMaxClip2()), pWeapon->m_iSecondaryAmmoType); + } + } + else //if (IsPlayer()) + { + if (pWeapon->UsesClipsForAmmo1()) + { + if (pWeapon->m_iClip1 > pWeapon->GetMaxClip1()) + { + // Handle excess ammo + GiveAmmo( pWeapon->m_iClip1 - pWeapon->GetMaxClip1(), pWeapon->m_iPrimaryAmmoType ); + pWeapon->m_iClip1 = pWeapon->GetMaxClip1(); + } + } + else if (pWeapon->m_iClip1 > 0) + { + // Just because the weapon can't use clips doesn't mean + // the mapper can't override their clip value for ammo. + GiveAmmo(pWeapon->m_iClip1, pWeapon->m_iPrimaryAmmoType); + pWeapon->m_iClip1 = WEAPON_NOCLIP; + } + + if (pWeapon->UsesClipsForAmmo2()) + { + if (pWeapon->m_iClip2 > pWeapon->GetMaxClip2()) + { + // Handle excess ammo + GiveAmmo(pWeapon->m_iClip2 - pWeapon->GetMaxClip2(), pWeapon->m_iSecondaryAmmoType); + pWeapon->m_iClip2 = pWeapon->GetMaxClip2(); + } + } + else if (pWeapon->m_iClip2 > 0) + { + // Just because the weapon can't use clips doesn't mean + // the mapper can't override their clip value for ammo. + GiveAmmo(pWeapon->m_iClip2, pWeapon->m_iSecondaryAmmoType); + pWeapon->m_iClip2 = WEAPON_NOCLIP; + } + } + + pWeapon->Equip( this ); + + // Gotta do this *after* Equip because it may whack maxRange + if ( IsPlayer() == false ) + { + // If SF_NPC_LONG_RANGE spawn flags is set let weapon work from any distance + if ( HasSpawnFlags(SF_NPC_LONG_RANGE) ) + { + pWeapon->m_fMaxRange1 = 999999999; + pWeapon->m_fMaxRange2 = 999999999; + } + } + else if (bPreserveAmmo) + { + // The clip doesn't update on the client unless we do this. + // This is the only way I've figured out how to update without doing something worse. + // TODO: Remove this hack, we've finally fixed it + /* + variant_t clip1; + clip1.SetInt(pWeapon->m_iClip1); + variant_t clip2; + clip2.SetInt(pWeapon->m_iClip2); + + pWeapon->m_iClip1 = pWeapon->m_iClip1 - 1; + pWeapon->m_iClip2 = pWeapon->m_iClip2 - 1; + + g_EventQueue.AddEvent(pWeapon, "SetAmmo1", clip1, 0.0001f, this, this, 0); + g_EventQueue.AddEvent(pWeapon, "SetAmmo2", clip2, 0.0001f, this, this, 0); + */ + } + + // Pass the lighting origin over to the weapon if we have one + pWeapon->SetLightingOriginRelative( GetLightingOriginRelative() ); + + //if (m_aliveTimer.IsLessThen(0.01f)) + m_OnWeaponEquip.FireOutput(pWeapon, this); +} +#else //----------------------------------------------------------------------------- // Purpose: Add new weapon to the character // Input : New weapon @@ -2181,6 +2630,7 @@ void CBaseCombatCharacter::Weapon_Equip( CBaseCombatWeapon *pWeapon ) } } } +#endif //----------------------------------------------------------------------------- @@ -2315,6 +2765,12 @@ bool CBaseCombatCharacter::Weapon_CanUse( CBaseCombatWeapon *pWeapon ) if ( SelectWeightedSequence(translatedActivity) == ACTIVITY_NOT_AVAILABLE ) { +#ifdef MAPBASE + // Do we have a backup? + translatedActivity = Weapon_BackupActivity((Activity)(pTable->baseAct), true, pWeapon); + if (SelectWeightedSequence(translatedActivity) != ACTIVITY_NOT_AVAILABLE) + return true; +#endif return false; } } @@ -2323,6 +2779,76 @@ bool CBaseCombatCharacter::Weapon_CanUse( CBaseCombatWeapon *pWeapon ) return true; } +#ifdef MAPBASE + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +static Activity Weapon_BackupActivityFromList( CBaseCombatCharacter *pBCC, acttable_t *pTable, int actCount, Activity activity, bool weaponTranslationWasRequired, CBaseCombatWeapon *pWeapon ) +{ + int i = 0; + for ( ; i < actCount; i++, pTable++ ) + { + if ( activity == pTable->baseAct ) + { + // Don't pick backup activities we don't actually have an animation for. + if (!pBCC->GetModelPtr()->HaveSequenceForActivity(pTable->weaponAct)) + break; + + return (Activity)pTable->weaponAct; + } + } + + // We didn't succeed in finding an activity. See if we can recurse + acttable_t *pBackupTable = CBaseCombatWeapon::GetDefaultBackupActivityList( pTable - i, actCount ); + if (pBackupTable) + { + return Weapon_BackupActivityFromList( pBCC, pBackupTable, actCount, activity, weaponTranslationWasRequired, pWeapon ); + } + + return activity; +} + +//----------------------------------------------------------------------------- +// Purpose: Uses an activity from a different weapon when the activity we were originally looking for does not exist on this character. +// This gives NPCs and players the ability to use weapons they are otherwise unable to use. +//----------------------------------------------------------------------------- +Activity CBaseCombatCharacter::Weapon_BackupActivity( Activity activity, bool weaponTranslationWasRequired, CBaseCombatWeapon *pSpecificWeapon ) +{ + CBaseCombatWeapon *pWeapon = pSpecificWeapon ? pSpecificWeapon : GetActiveWeapon(); + if (!pWeapon) + return activity; + + // Make sure the weapon allows this activity to have a backup. + if (!pWeapon->SupportsBackupActivity(activity)) + return activity; + + // UNDONE: Sometimes, a NPC is supposed to use the default activity. Return that if the weapon translation was "not required" and we have an original activity. + /* + if (!weaponTranslationWasRequired && GetModelPtr()->HaveSequenceForActivity(activity) && !IsPlayer()) + { + return activity; + } + */ + + acttable_t *pTable = pWeapon->GetBackupActivityList(); + int actCount = pWeapon->GetBackupActivityListCount(); + if (!pTable) + { + // Look for a default list + acttable_t *pTable = pWeapon->ActivityList( actCount ); + pTable = CBaseCombatWeapon::GetDefaultBackupActivityList( pTable, actCount ); + } + + if (pTable && GetModelPtr()) + { + return Weapon_BackupActivityFromList( this, pTable, actCount, activity, weaponTranslationWasRequired, pWeapon ); + } + + return activity; +} +#endif + //----------------------------------------------------------------------------- // Purpose: // Input : @@ -2369,6 +2895,11 @@ int CBaseCombatCharacter::TakeHealth (float flHealth, int bitsDamageType) { if (!m_takedamage) return 0; + +#ifdef MAPBASE + float flRatio = clamp( (float)m_iHealth / (float)m_iMaxHealth, 0.f, 1.f ); + m_OnHealthChanged.Set(flRatio, NULL, this); +#endif return BaseClass::TakeHealth(flHealth, bitsDamageType); } @@ -2435,8 +2966,21 @@ int CBaseCombatCharacter::OnTakeDamage( const CTakeDamageInfo &info ) { case LIFE_ALIVE: retVal = OnTakeDamage_Alive( info ); +#ifdef MAPBASE + if (retVal) + { + float flRatio = clamp( (float)m_iHealth / (float)m_iMaxHealth, 0.f, 1.f ); + m_OnHealthChanged.Set(flRatio, NULL, this); + } +#endif if ( m_iHealth <= 0 ) { +#ifdef MAPBASE_VSCRIPT + // False = Cheat death + if (ScriptDeathHook( const_cast(&info) ) == false) + return retVal; +#endif + IPhysicsObject *pPhysics = VPhysicsGetObject(); if ( pPhysics ) { @@ -2462,11 +3006,28 @@ int CBaseCombatCharacter::OnTakeDamage( const CTakeDamageInfo &info ) break; case LIFE_DYING: +#ifdef MAPBASE + retVal = OnTakeDamage_Dying( info ); + if (retVal) + { + float flRatio = clamp( (float)m_iHealth / (float)m_iMaxHealth, 0.f, 1.f ); + m_OnHealthChanged.Set(flRatio, NULL, this); + } + return retVal; +#else return OnTakeDamage_Dying( info ); +#endif default: case LIFE_DEAD: retVal = OnTakeDamage_Dead( info ); +#ifdef MAPBASE + if (retVal) + { + float flRatio = clamp( (float)m_iHealth / (float)m_iMaxHealth, 0.f, 1.f ); + m_OnHealthChanged.Set(flRatio, NULL, this); + } +#endif if ( m_iHealth <= 0 && g_pGameRules->Damage_ShouldGibCorpse( info.GetDamageType() ) && ShouldGib( info ) ) { Event_Gibbed( info ); @@ -2626,6 +3187,29 @@ void CBaseCombatCharacter::AddClassRelationship ( Class_T class_type, Dispositio m_Relationship[index].priority = ( priority != DEF_RELATIONSHIP_PRIORITY ) ? priority : 0; } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: Removes a class relationship from our list +// Input : *class_type - Class with whom the relationship should be ended +// Output : True is relation was removed, false if it was not found +//----------------------------------------------------------------------------- +bool CBaseCombatCharacter::RemoveClassRelationship( Class_T class_type ) +{ + // Find the relationship in our list, if it exists + for ( int i = m_Relationship.Count()-1; i >= 0; i-- ) + { + if ( m_Relationship[i].classType == class_type ) + { + // Done, remove it + m_Relationship.Remove( i ); + return true; + } + } + + return false; +} +#endif + //----------------------------------------------------------------------------- // Purpose: Add or Change a entity relationship for this entity // Input : @@ -2707,6 +3291,54 @@ void CBaseCombatCharacter::SetDefaultRelationship(Class_T nClass, Class_T nClass } } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: Determine whether or not default relationships are loaded +// Input : +// Output : +//----------------------------------------------------------------------------- +bool CBaseCombatCharacter::DefaultRelationshipsLoaded() +{ + return m_DefaultRelationship != NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: Fetch the default (ignore ai_relationship changes) relationship +// Input : +// Output : +//----------------------------------------------------------------------------- +Disposition_t CBaseCombatCharacter::GetDefaultRelationshipDisposition( Class_T nClassSource, Class_T nClassTarget ) +{ + Assert( m_DefaultRelationship != NULL ); + + return m_DefaultRelationship[nClassSource][nClassTarget].disposition; +} + +//----------------------------------------------------------------------------- +// Purpose: Fetch the default (ignore ai_relationship changes) priority +// Input : +// Output : +//----------------------------------------------------------------------------- +int CBaseCombatCharacter::GetDefaultRelationshipPriority( Class_T nClassSource, Class_T nClassTarget ) +{ + Assert( m_DefaultRelationship != NULL ); + + return m_DefaultRelationship[nClassSource][nClassTarget].priority; +} + +//----------------------------------------------------------------------------- +// Purpose: Fetch the default (ignore ai_relationship changes) priority +// Input : +// Output : +//----------------------------------------------------------------------------- +int CBaseCombatCharacter::GetDefaultRelationshipPriority( Class_T nClassTarget ) +{ + Assert( m_DefaultRelationship != NULL ); + + return m_DefaultRelationship[Classify()][nClassTarget].priority; +} +#endif + //----------------------------------------------------------------------------- // Purpose: Fetch the default (ignore ai_relationship changes) relationship // Input : @@ -2762,7 +3394,24 @@ Relationship_t *CBaseCombatCharacter::FindEntityRelationship( CBaseEntity *pTarg Disposition_t CBaseCombatCharacter::IRelationType ( CBaseEntity *pTarget ) { if ( pTarget ) + { +#ifdef MAPBASE_VSCRIPT + if (m_ScriptScope.IsInitialized() && g_Hook_RelationshipType.CanRunInScope( m_ScriptScope )) + { + // entity, default + ScriptVariant_t functionReturn; + ScriptVariant_t args[] = { ScriptVariant_t( pTarget->GetScriptInstance() ), FindEntityRelationship( pTarget )->disposition }; + if (g_Hook_RelationshipType.Call( m_ScriptScope, &functionReturn, args ) && (functionReturn.m_type == FIELD_INTEGER && functionReturn.m_int != D_ER)) + { + // Use the disposition returned by the script + return (Disposition_t)functionReturn.m_int; + } + } +#endif + return FindEntityRelationship( pTarget )->disposition; + } + return D_NU; } @@ -2774,10 +3423,151 @@ Disposition_t CBaseCombatCharacter::IRelationType ( CBaseEntity *pTarget ) int CBaseCombatCharacter::IRelationPriority( CBaseEntity *pTarget ) { if ( pTarget ) + { +#ifdef MAPBASE_VSCRIPT + if (m_ScriptScope.IsInitialized() && g_Hook_RelationshipPriority.CanRunInScope( m_ScriptScope )) + { + // entity, default + ScriptVariant_t functionReturn; + ScriptVariant_t args[] = { ScriptVariant_t( pTarget->GetScriptInstance() ), FindEntityRelationship( pTarget )->priority }; + if (g_Hook_RelationshipPriority.Call( m_ScriptScope, &functionReturn, args ) && functionReturn.m_type == FIELD_INTEGER) + { + // Use the priority returned by the script + return functionReturn.m_int; + } + } +#endif + return FindEntityRelationship( pTarget )->priority; + } + return 0; } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: Ported from CAI_BaseNPC so players can use it +//----------------------------------------------------------------------------- +void CBaseCombatCharacter::AddRelationship( const char *pszRelationship, CBaseEntity *pActivator ) +{ + // Parse the keyvalue data + char parseString[1000]; + Q_strncpy(parseString, pszRelationship, sizeof(parseString)); + + // Look for an entity string + char *entityString = strtok(parseString," "); + while (entityString) + { + // Get the disposition + char *dispositionString = strtok(NULL," "); + Disposition_t disposition = D_NU; + if ( dispositionString ) + { + if (!stricmp(dispositionString,"D_HT")) + { + disposition = D_HT; + } + else if (!stricmp(dispositionString,"D_FR")) + { + disposition = D_FR; + } + else if (!stricmp(dispositionString,"D_LI")) + { + disposition = D_LI; + } + else if (!stricmp(dispositionString,"D_NU")) + { + disposition = D_NU; + } + else + { + disposition = D_NU; + Warning( "***ERROR***\nBad relationship type (%s) to unknown entity (%s)!\n", dispositionString,entityString ); + Assert( 0 ); + return; + } + } + else + { + Warning("Can't parse relationship info (%s) - Expecting 'name [D_HT, D_FR, D_LI, D_NU] [1-99]'\n", pszRelationship ); + Assert(0); + return; + } + + // Get the priority + char *priorityString = strtok(NULL," "); + int priority = ( priorityString ) ? atoi(priorityString) : DEF_RELATIONSHIP_PRIORITY; + + bool bFoundEntity = false; + + // Try to get pointer to an entity of this name + CBaseEntity *entity = gEntList.FindEntityByName( NULL, entityString ); + while( entity ) + { + // make sure you catch all entities of this name. + bFoundEntity = true; + AddEntityRelationship(entity, disposition, priority ); + entity = gEntList.FindEntityByName( entity, entityString ); + } + + if( !bFoundEntity ) + { + // Need special condition for player as we can only have one + if (!stricmp("player", entityString) || !stricmp("!player", entityString)) + { + AddClassRelationship( CLASS_PLAYER, disposition, priority ); + } + // Otherwise try to create one too see if a valid classname and get class type + else + { + // HACKHACK: + CBaseEntity *pEntity = CanCreateEntityClass( entityString ) ? CreateEntityByName( entityString ) : NULL; + if (pEntity) + { + AddClassRelationship( pEntity->Classify(), disposition, priority ); + UTIL_RemoveImmediate(pEntity); + } + else + { + // NEW: Classify class relationships + if (!Q_strnicmp(entityString, "CLASS_", 5)) + { + // Go through all of the classes and find which one this is + Class_T resultClass = CLASS_NONE; + for (int i = 0; i < NUM_AI_CLASSES; i++) + { + if (FStrEq(g_pGameRules->AIClassText(i), entityString)) + { + resultClass = (Class_T)i; + } + } + + if (resultClass != CLASS_NONE) + { + AddClassRelationship( resultClass, disposition, priority ); + bFoundEntity = true; + } + } + + if (!bFoundEntity) + DevWarning( "Couldn't set relationship to unknown entity or class (%s)!\n", entityString ); + } + } + } + // Check for another entity in the list + entityString = strtok(NULL," "); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseCombatCharacter::InputSetRelationship( inputdata_t &inputdata ) +{ + AddRelationship( inputdata.value.String(), inputdata.pActivator ); +} +#endif + //----------------------------------------------------------------------------- // Purpose: Get shoot position of BCC at current position/orientation // Input : @@ -2810,6 +3600,11 @@ CBaseEntity *CBaseCombatCharacter::FindHealthItem( const Vector &vecPosition, co if( pItem ) { +#ifdef MAPBASE + if (pItem->HasSpawnFlags(SF_ITEM_NO_NPC_PICKUP)) + continue; +#endif + // Healthkits and healthvials if( pItem->ClassMatches( "item_health*" ) && FVisible( pItem ) ) { @@ -2854,6 +3649,18 @@ CBaseEntity *CBaseCombatCharacter::Weapon_FindUsable( const Vector &range ) { bool bConservative = false; +#ifdef MAPBASE + if (HasContext("weapon_conservative:1")) + bConservative = true; +#ifdef HL2_DLL + else if (hl2_episodic.GetBool() && !GetActiveWeapon()) + { + // Unarmed citizens are conservative in their weapon finding...in Episode One + if (Classify() != CLASS_PLAYER_ALLY_VITAL && Q_strncmp(STRING(gpGlobals->mapname), "ep1_", 4) == 0) + bConservative = true; + } +#endif +#else #ifdef HL2_DLL if( hl2_episodic.GetBool() && !GetActiveWeapon() ) { @@ -2863,6 +3670,7 @@ CBaseEntity *CBaseCombatCharacter::Weapon_FindUsable( const Vector &range ) bConservative = true; } } +#endif #endif CBaseCombatWeapon *weaponList[64]; @@ -2886,26 +3694,56 @@ CBaseEntity *CBaseCombatCharacter::Weapon_FindUsable( const Vector &range ) if ( pWeapon->CanBePickedUpByNPCs() == false ) continue; +#ifdef MAPBASE + if ( pWeapon->HasSpawnFlags(SF_WEAPON_NO_NPC_PICKUP) ) + continue; +#endif + if ( velocity.LengthSqr() > 1 || !Weapon_CanUse(pWeapon) ) continue; if ( pWeapon->IsLocked(this) ) continue; +#ifdef MAPBASE + // Skip weapons we already own + if ( Weapon_OwnsThisType(pWeapon->GetClassname()) ) + continue; +#endif + if ( GetActiveWeapon() ) { // Already armed. Would picking up this weapon improve my situation? +#ifndef MAPBASE if( GetActiveWeapon()->m_iClassname == pWeapon->m_iClassname ) { // No, I'm already using this type of weapon. continue; } +#endif +#ifdef MAPBASE + if ( pWeapon->IsMeleeWeapon() && !GetActiveWeapon()->IsMeleeWeapon() ) + { + // This weapon is a melee weapon and the weapon I have now is not. + // Picking up this weapon might not improve my situation. + continue; + } + + if ( pWeapon->GetWeight() != 0 && GetActiveWeapon()->GetWeight() > pWeapon->GetWeight() ) + { + // Discard if our target weapon supports weight but our current weapon has more of it. + // + // (RIP going from AR2 to shotgun) + continue; + } +#else if( FClassnameIs( pWeapon, "weapon_pistol" ) ) { // No, it's a pistol. continue; } +#endif } float fCurDist = (pWeapon->GetLocalOrigin() - GetLocalOrigin()).Length(); @@ -2918,6 +3756,23 @@ CBaseEntity *CBaseCombatCharacter::Weapon_FindUsable( const Vector &range ) if ( pBestWeapon ) { +#ifdef MAPBASE + // NPCs now use weight to determine which weapon is best. + // All HL2 weapons are weighted and are usually good enough. + // + // This probably won't cause problems... + if (pWeapon->GetWeight() > 1) + { +#if 0 + float flRatio = MIN( (2.5f / (pWeapon->GetWeight() - (GetActiveWeapon() ? GetActiveWeapon()->GetWeight() : 0))), 1.0 ); + if (flRatio < 0) + flRatio *= -1; flRatio += 1.0f; + fCurDist *= flRatio; +#else + fCurDist *= MIN( (2.5f / pWeapon->GetWeight()), 1.0 ); +#endif + } +#else // UNDONE: Better heuristic needed here // Need to pick by power of weapons // Don't want to pick a weapon right next to a NPC! @@ -2927,6 +3782,7 @@ CBaseEntity *CBaseCombatCharacter::Weapon_FindUsable( const Vector &range ) { fCurDist *= 0.5; } +#endif // choose the last range attack weapon you find or the first available other weapon if ( ! (pWeapon->CapabilitiesGet() & bits_CAP_RANGE_ATTACK_GROUP) ) @@ -3284,6 +4140,12 @@ bool CBaseCombatCharacter::IsGlowEffectActive( void ) { return m_bGlowEnabled; } + +void CBaseCombatCharacter::SetGlowColor( float red, float green, float blue, float alpha ) +{ + m_GlowColor.GetForModify().Init( red, green, blue ); + m_GlowAlpha.Set( alpha ); +} #endif // GLOWS_ENABLE //----------------------------------------------------------------------------- @@ -3353,6 +4215,306 @@ void CBaseCombatCharacter::InputKilledNPC( inputdata_t &inputdata ) OnKilledNPC( inputdata.pActivator ? inputdata.pActivator->MyCombatCharacterPointer() : NULL ); } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: Handle enemy kills. (this technically measures players too) +//----------------------------------------------------------------------------- +void CBaseCombatCharacter::OnKilledNPC( CBaseCombatCharacter *pKilled ) +{ + m_OnKilledEnemy.Set(pKilled, pKilled, this); + + // Fire an additional output if this was a player + if (pKilled && pKilled->IsPlayer()) + m_OnKilledPlayer.Set(pKilled, pKilled, this); +} + +//------------------------------------------------------------------------------ +// Purpose: Give the NPC in question the weapon specified +//------------------------------------------------------------------------------ +void CBaseCombatCharacter::InputGiveWeapon( inputdata_t &inputdata ) +{ + // Give the NPC the specified weapon + string_t iszWeaponName = inputdata.value.StringID(); + if ( iszWeaponName != NULL_STRING ) + { + if (IsNPC()) + { + if( Classify() == CLASS_PLAYER_ALLY_VITAL ) + { + MyNPCPointer()->m_iszPendingWeapon = iszWeaponName; + } + else + { + MyNPCPointer()->GiveWeapon( iszWeaponName ); + } + } + else + { + CBaseCombatWeapon *pWeapon = Weapon_Create(STRING(iszWeaponName)); + if (pWeapon) + { + Weapon_Equip(pWeapon); + } + else + { + Warning( "Couldn't create weapon %s to give %s.\n", STRING(iszWeaponName), GetDebugName() ); + return; + } + } + } +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CBaseCombatCharacter::InputDropWeapon( inputdata_t &inputdata ) +{ + CBaseCombatWeapon *pWeapon = FStrEq(inputdata.value.String(), "") ? GetActiveWeapon() : Weapon_OwnsThisType(inputdata.value.String()); + if (pWeapon) + { + Weapon_Drop(pWeapon); + } +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CBaseCombatCharacter::InputPickupWeaponInstant( inputdata_t &inputdata ) +{ + if (inputdata.value.Entity() && inputdata.value.Entity()->IsBaseCombatWeapon()) + { + CBaseCombatWeapon *pWeapon = inputdata.value.Entity()->MyCombatWeaponPointer(); + if (pWeapon->GetOwner()) + { + Msg("Ignoring PickupWeaponInstant on %s because %s already has an owner\n", GetDebugName(), pWeapon->GetDebugName()); + return; + } + + if (CBaseCombatWeapon *pExistingWeapon = Weapon_OwnsThisType(pWeapon->GetClassname())) + { + // Drop our existing weapon then! + Weapon_Drop(pExistingWeapon); + } + + if (IsNPC()) + { + Weapon_Equip(pWeapon); + MyNPCPointer()->OnGivenWeapon(pWeapon); + } + else + { + Weapon_Equip(pWeapon); + } + + pWeapon->OnPickedUp( this ); + } + else + { + Warning("%s received PickupWeaponInstant with invalid entity %s\n", GetDebugName(), inputdata.value.Entity() ? inputdata.value.Entity()->GetDebugName() : "<>"); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseCombatCharacter::InputHolsterWeapon( inputdata_t &inputdata ) +{ + CBaseCombatWeapon *pWeapon = GetActiveWeapon(); + if (pWeapon) + { + pWeapon->Holster(); + //SetActiveWeapon( NULL ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseCombatCharacter::InputHolsterAndDestroyWeapon( inputdata_t &inputdata ) +{ + CBaseCombatWeapon *pWeapon = GetActiveWeapon(); + if (pWeapon) + { + pWeapon->Holster(); + SetActiveWeapon( NULL ); + + if (pWeapon->GetActivity() == ACT_VM_HOLSTER) + { + // Remove when holster is finished + pWeapon->ThinkSet( &CBaseEntity::SUB_Remove, gpGlobals->curtime + pWeapon->GetViewModelSequenceDuration() ); + } + else + { + // Remove now + UTIL_Remove( pWeapon ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseCombatCharacter::InputUnholsterWeapon( inputdata_t &inputdata ) +{ + // NPCs can handle strings, but players fall back to SwitchToWeapon + if (inputdata.value.StringID() != NULL_STRING) + InputSwitchToWeapon( inputdata ); + + CBaseCombatWeapon *pWeapon = GetActiveWeapon(); + if (pWeapon && pWeapon->IsEffectActive(EF_NODRAW)) + { + pWeapon->Deploy(); + } +} + +//------------------------------------------------------------------------------ +// Purpose: Makes the NPC instantly switch to the specified weapon, creates it if it doesn't exist +//------------------------------------------------------------------------------ +void CBaseCombatCharacter::InputSwitchToWeapon( inputdata_t &inputdata ) +{ + for (int i = 0; im_iClassname == inputdata.value.StringID()) + { + Weapon_Switch( m_hMyWeapons[i] ); + return; + } + } + + // We must not have it + if (IsNPC()) + MyNPCPointer()->GiveWeapon( inputdata.value.StringID(), false ); + else + { + CBaseCombatWeapon *pWeapon = Weapon_Create( inputdata.value.String() ); + if (pWeapon) + { + Weapon_Equip( pWeapon ); + } + else + { + Warning( "Couldn't create weapon %s to give %s.\n", inputdata.value.String(), GetDebugName() ); + } + } +} + +#define FINDNAMEDENTITY_MAX_ENTITIES 32 +//----------------------------------------------------------------------------- +// Purpose: FindNamedEntity has been moved from CAI_BaseNPC to CBaseCombatCharacter so players can use it. +// Coincidentally, everything that it did on NPCs could be done on BaseCombatCharacters with no consequences. +// Input : +// Output : +//----------------------------------------------------------------------------- +CBaseEntity *CBaseCombatCharacter::FindNamedEntity( const char *szName, IEntityFindFilter *pFilter ) +{ + const char *name = szName; + if (name[0] == '!') + name++; + + if ( !stricmp( name, "player" )) + { + return AI_GetSinglePlayer(); + } + else if ( !stricmp( name, "enemy" ) ) + { + return GetEnemy(); + } + else if ( !stricmp( name, "self" ) || !stricmp( name, "target1" ) ) + { + return this; + } + else if ( !stricmp( name, "nearestfriend" ) || !strnicmp( name, "friend", 6 ) ) + { + // Just look for the nearest friendly NPC within 500 units + // (most of this was stolen from CAI_PlayerAlly::FindSpeechTarget()) + const Vector & vAbsOrigin = GetAbsOrigin(); + float closestDistSq = Square(500.0); + CBaseEntity * pNearest = NULL; + float distSq; + int i; + for ( i = 0; i < g_AI_Manager.NumAIs(); i++ ) + { + CAI_BaseNPC *pNPC = (g_AI_Manager.AccessAIs())[i]; + + if ( pNPC == this ) + continue; + + distSq = ( vAbsOrigin - pNPC->GetAbsOrigin() ).LengthSqr(); + + if ( distSq > closestDistSq ) + continue; + + if ( IRelationType( pNPC ) == D_LI ) + { + closestDistSq = distSq; + pNearest = pNPC; + } + } + + if (stricmp(name, "friend_npc") != 0) + { + // Okay, find the nearest friendly client. + for ( i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBaseEntity *pPlayer = UTIL_PlayerByIndex( i ); + if ( pPlayer ) + { + // Don't get players with notarget + if (pPlayer->GetFlags() & FL_NOTARGET) + continue; + + distSq = ( vAbsOrigin - pPlayer->GetAbsOrigin() ).LengthSqr(); + + if ( distSq > closestDistSq ) + continue; + + if ( IRelationType( pPlayer ) == D_LI ) + { + closestDistSq = distSq; + pNearest = pPlayer; + } + } + } + } + + return pNearest; + } + else if (!stricmp( name, "weapon" )) + { + return GetActiveWeapon(); + } + + // HACKHACK: FindEntityProcedural can go through this now, so running this code could cause an infinite loop. + // As a result, FindEntityProcedural currently identifies itself with this entity filter. + else if (!pFilter || !dynamic_cast(pFilter)) + { + // search for up to 32 entities with the same name and choose one randomly + CBaseEntity *entityList[ FINDNAMEDENTITY_MAX_ENTITIES ]; + CBaseEntity *entity; + int iCount; + + entity = NULL; + for( iCount = 0; iCount < FINDNAMEDENTITY_MAX_ENTITIES; iCount++ ) + { + entity = gEntList.FindEntityByName( entity, szName, this, NULL, NULL, pFilter ); + if ( !entity ) + { + break; + } + entityList[ iCount ] = entity; + } + + if ( iCount > 0 ) + { + int index = RandomInt( 0, iCount - 1 ); + entity = entityList[ index ]; + return entity; + } + } + + return NULL; +} +#endif + //----------------------------------------------------------------------------- // Purpose: Overload our muzzle flash and send it to any actively held weapon //----------------------------------------------------------------------------- @@ -3371,6 +4533,211 @@ void CBaseCombatCharacter::DoMuzzleFlash() } } +#ifdef MAPBASE_VSCRIPT +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +HSCRIPT CBaseCombatCharacter::ScriptGetActiveWeapon() +{ + return ToHScript( GetActiveWeapon() ); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +HSCRIPT CBaseCombatCharacter::ScriptGetWeapon( int i ) +{ + Assert( i >= 0 && i < MAX_WEAPONS ); + + if ( i < 0 || i >= MAX_WEAPONS ) + return NULL; + + return ToHScript( GetWeapon( i ) ); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +HSCRIPT CBaseCombatCharacter::ScriptGetWeaponByType( const char *pszWeapon, int iSubType ) +{ + return ToHScript( Weapon_OwnsThisType( pszWeapon, iSubType ) ); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CBaseCombatCharacter::ScriptGetAllWeapons( HSCRIPT hTable ) +{ + for (int i=0;iSetValue( hTable, m_hMyWeapons[i]->GetClassname(), ToHScript( m_hMyWeapons[i] ) ); + } + } +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CBaseCombatCharacter::ScriptDropWeapon( HSCRIPT hWeapon ) +{ + CBaseCombatWeapon *pWeapon = HScriptToClass( hWeapon ); + if (!pWeapon) + return; + + if (pWeapon->GetOwner() == this) + { + // Drop the weapon + Weapon_Drop( pWeapon ); + } + else + { + CGMsg( 1, CON_GROUP_VSCRIPT, "ScriptDropWeapon: %s is not owned by %s", pWeapon->GetDebugName(), GetDebugName() ); + } +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CBaseCombatCharacter::ScriptEquipWeapon( HSCRIPT hWeapon ) +{ + CBaseCombatWeapon *pWeapon = HScriptToClass( hWeapon ); + if (!pWeapon) + return; + + if (pWeapon->GetOwner() == this) + { + // Switch to this weapon + Weapon_Switch( pWeapon ); + } + else + { + if (CBaseCombatWeapon *pExistingWeapon = Weapon_OwnsThisType( pWeapon->GetClassname() )) + { + // Drop our existing weapon then! + Weapon_Drop( pExistingWeapon ); + } + + if (IsNPC()) + { + Weapon_Equip( pWeapon ); + MyNPCPointer()->OnGivenWeapon( pWeapon ); + } + else + { + Weapon_Equip( pWeapon ); + } + + pWeapon->OnPickedUp( this ); + } +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +int CBaseCombatCharacter::ScriptGiveAmmo( int iCount, int iAmmoIndex, bool bSuppressSound ) +{ + return GiveAmmo( iCount, iAmmoIndex, bSuppressSound ); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CBaseCombatCharacter::ScriptRemoveAmmo( int iCount, int iAmmoIndex ) +{ + if (iAmmoIndex == -1) + { + Warning( "%i is not a valid ammo type\n", iAmmoIndex ); + return; + } + + RemoveAmmo( iCount, iAmmoIndex ); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +int CBaseCombatCharacter::ScriptGetAmmoCount( int iType ) const +{ + Assert( iType == -1 || iType < MAX_AMMO_SLOTS ); + + if ( iType < 0 || iType >= MAX_AMMO_SLOTS ) + return 0; + + return GetAmmoCount( iType ); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CBaseCombatCharacter::ScriptSetAmmoCount( int iType, int iCount ) +{ + Assert( iType == -1 || iType < MAX_AMMO_SLOTS ); + + if ( iType < 0 || iType >= MAX_AMMO_SLOTS ) + return; + + return SetAmmoCount( iCount, iType ); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +const Vector& CBaseCombatCharacter::ScriptGetAttackSpread( HSCRIPT hWeapon, HSCRIPT hTarget ) +{ + CBaseEntity *pWeapon = ToEnt( hWeapon ); + if (!pWeapon || !pWeapon->IsBaseCombatWeapon()) + { + Warning( "GetAttackSpread: %s is not a valid weapon\n", pWeapon ? pWeapon->GetDebugName() : "Null entity" ); + return vec3_origin; + } + + // TODO: Make this a simple non-reference Vector? + static Vector vec; + vec = GetAttackSpread( pWeapon->MyCombatWeaponPointer(), ToEnt( hTarget ) ); + return vec; +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +float CBaseCombatCharacter::ScriptGetSpreadBias( HSCRIPT hWeapon, HSCRIPT hTarget ) +{ + CBaseEntity *pWeapon = ToEnt( hWeapon ); + if (!pWeapon || !pWeapon->IsBaseCombatWeapon()) + { + Warning( "GetSpreadBias: %s is not a valid weapon\n", pWeapon ? pWeapon->GetDebugName() : "Null entity" ); + return 1.0f; + } + + return GetSpreadBias( pWeapon->MyCombatWeaponPointer(), ToEnt( hTarget ) ); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +int CBaseCombatCharacter::ScriptRelationType( HSCRIPT pTarget ) +{ + return (int)IRelationType( ToEnt( pTarget ) ); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +int CBaseCombatCharacter::ScriptRelationPriority( HSCRIPT pTarget ) +{ + return IRelationPriority( ToEnt( pTarget ) ); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CBaseCombatCharacter::ScriptSetRelationship( HSCRIPT pTarget, int disposition, int priority ) +{ + AddEntityRelationship( ToEnt( pTarget ), (Disposition_t)disposition, priority ); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CBaseCombatCharacter::ScriptSetClassRelationship( int classify, int disposition, int priority ) +{ + AddClassRelationship( (Class_T)classify, (Disposition_t)disposition, priority); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +HSCRIPT CBaseCombatCharacter::ScriptGetVehicleEntity() +{ + return ToHScript( GetVehicleEntity() ); +} +#endif + //----------------------------------------------------------------------------- // Purpose: return true if given target cant be seen because of fog //----------------------------------------------------------------------------- @@ -3629,6 +4996,8 @@ HSCRIPT CBaseCombatCharacter::ScriptGetLastKnownArea( void ) const #endif } +#ifndef MAPBASE_VSCRIPT BEGIN_ENT_SCRIPTDESC( CBaseCombatCharacter, CBaseFlex, "Base combat characters." ) DEFINE_SCRIPTFUNC_NAMED( ScriptGetLastKnownArea, "GetLastKnownArea", "Return the last nav area occupied - NULL if unknown" ) END_SCRIPTDESC(); +#endif diff --git a/src/game/server/basecombatcharacter.h b/src/game/server/basecombatcharacter.h index c2c49dba..913edea3 100644 --- a/src/game/server/basecombatcharacter.h +++ b/src/game/server/basecombatcharacter.h @@ -141,6 +141,10 @@ public: virtual bool FVisible( const Vector &vecTarget, int traceMask = MASK_BLOCKLOS, CBaseEntity **ppBlocker = NULL ) { return BaseClass::FVisible( vecTarget, traceMask, ppBlocker ); } static void ResetVisibilityCache( CBaseCombatCharacter *pBCC = NULL ); +#ifdef MAPBASE + virtual bool ShouldUseVisibilityCache( CBaseEntity *pEntity ); +#endif + #ifdef PORTAL virtual bool FVisibleThroughPortal( const CProp_Portal *pPortal, CBaseEntity *pEntity, int traceMask = MASK_BLOCKLOS, CBaseEntity **ppBlocker = NULL ); #endif @@ -159,8 +163,10 @@ public: virtual bool ShouldShootMissTarget( CBaseCombatCharacter *pAttacker ); virtual CBaseEntity *FindMissTarget( void ); +#ifndef MAPBASE // This function now exists in CBaseEntity // Do not call HandleInteraction directly, use DispatchInteraction bool DispatchInteraction( int interactionType, void *data, CBaseCombatCharacter* sourceEnt ) { return ( interactionType > 0 ) ? HandleInteraction( interactionType, data, sourceEnt ) : false; } +#endif virtual bool HandleInteraction( int interactionType, void *data, CBaseCombatCharacter* sourceEnt ); virtual QAngle BodyAngles(); @@ -230,7 +236,14 @@ public: virtual void Weapon_HandleAnimEvent( animevent_t *pEvent ); CBaseCombatWeapon* Weapon_OwnsThisType( const char *pszWeapon, int iSubType = 0 ) const; // True if already owns a weapon of this class virtual bool Weapon_CanUse( CBaseCombatWeapon *pWeapon ); // True is allowed to use this class of weapon +#ifdef MAPBASE + virtual Activity Weapon_BackupActivity( Activity activity, bool weaponTranslationWasRequired = false, CBaseCombatWeapon *pSpecificWeapon = NULL ); virtual void Weapon_Equip( CBaseCombatWeapon *pWeapon ); // Adds weapon to player + virtual void Weapon_EquipHolstered( CBaseCombatWeapon *pWeapon ); // Pretty much only useful for NPCs + virtual void Weapon_HandleEquip( CBaseCombatWeapon *pWeapon ); +#else + virtual void Weapon_Equip( CBaseCombatWeapon *pWeapon ); // Adds weapon to player +#endif virtual bool Weapon_EquipAmmoOnly( CBaseCombatWeapon *pWeapon ); // Adds weapon ammo to player, leaves weapon bool Weapon_Detach( CBaseCombatWeapon *pWeapon ); // Clear any pointers to the weapon. virtual void Weapon_Drop( CBaseCombatWeapon *pWeapon, const Vector *pvecTarget = NULL, const Vector *pVelocity = NULL ); @@ -252,6 +265,10 @@ public: virtual bool CanBecomeServerRagdoll( void ) { return true; } +#ifdef MAPBASE + virtual void OnEnemyRangeAttackedMe( CBaseEntity *pEnemy, const Vector &vecDir, const Vector &vecEnd ) {} +#endif + // ----------------------- // Damage // ----------------------- @@ -293,7 +310,29 @@ public: // Killed a character void InputKilledNPC( inputdata_t &inputdata ); +#ifdef MAPBASE + + void InputGiveWeapon( inputdata_t &inputdata ); + void InputDropWeapon( inputdata_t &inputdata ); + void InputPickupWeaponInstant( inputdata_t &inputdata ); + COutputEvent m_OnWeaponEquip; + COutputEvent m_OnWeaponDrop; + + virtual void InputHolsterWeapon( inputdata_t &inputdata ); + virtual void InputHolsterAndDestroyWeapon( inputdata_t &inputdata ); + virtual void InputUnholsterWeapon( inputdata_t &inputdata ); + void InputSwitchToWeapon( inputdata_t &inputdata ); + + COutputEHANDLE m_OnKilledEnemy; + COutputEHANDLE m_OnKilledPlayer; + virtual void OnKilledNPC( CBaseCombatCharacter *pKilled ); + + virtual CBaseEntity *FindNamedEntity( const char *pszName, IEntityFindFilter *pFilter = NULL ); + + COutputFloat m_OnHealthChanged; +#else virtual void OnKilledNPC( CBaseCombatCharacter *pKilled ) {}; +#endif // Exactly one of these happens immediately after killed (gibbed may happen later when the corpse gibs) // Character gibbed or faded out (violence controls) (only fired once) @@ -309,6 +348,14 @@ public: virtual bool BecomeRagdollBoogie( CBaseEntity *pKiller, const Vector &forceVector, float duration, int flags ); +#ifdef MAPBASE + // A version of BecomeRagdollBoogie() that allows the color to change and returns the entity itself instead. + // In order to avoid breaking anything, it doesn't change the original function. + virtual CBaseEntity *BecomeRagdollBoogie( CBaseEntity *pKiller, const Vector &forceVector, float duration, int flags, const Vector *vecColor ); + + bool ShouldFadeServerRagdolls() const; +#endif + CBaseEntity *FindHealthItem( const Vector &vecPosition, const Vector &range ); @@ -332,6 +379,11 @@ public: virtual Disposition_t IRelationType( CBaseEntity *pTarget ); virtual int IRelationPriority( CBaseEntity *pTarget ); +#ifdef MAPBASE + void AddRelationship( const char *pszRelationship, CBaseEntity *pActivator ); + void InputSetRelationship( inputdata_t &inputdata ); +#endif + virtual void SetLightingOriginRelative( CBaseEntity *pLightingOrigin ); protected: @@ -347,6 +399,9 @@ public: // Blood color (see BLOOD_COLOR_* macros in baseentity.h) void SetBloodColor( int nBloodColor ); +#ifdef MAPBASE + void InputSetBloodColor( inputdata_t &inputdata ); +#endif // Weapons.. CBaseCombatWeapon* GetActiveWeapon() const; @@ -354,23 +409,80 @@ public: CBaseCombatWeapon* GetWeapon( int i ) const; bool RemoveWeapon( CBaseCombatWeapon *pWeapon ); virtual void RemoveAllWeapons(); - WeaponProficiency_t GetCurrentWeaponProficiency() { return m_CurrentWeaponProficiency; } + WeaponProficiency_t GetCurrentWeaponProficiency() + { +#ifdef MAPBASE + // Mapbase adds proficiency override + return (m_ProficiencyOverride > WEAPON_PROFICIENCY_INVALID) ? m_ProficiencyOverride : m_CurrentWeaponProficiency; +#else + return m_CurrentWeaponProficiency; +#endif + } void SetCurrentWeaponProficiency( WeaponProficiency_t iProficiency ) { m_CurrentWeaponProficiency = iProficiency; } virtual WeaponProficiency_t CalcWeaponProficiency( CBaseCombatWeapon *pWeapon ); +#ifdef MAPBASE + inline bool OverridingWeaponProficiency() { return (m_ProficiencyOverride > WEAPON_PROFICIENCY_INVALID); } +#endif virtual Vector GetAttackSpread( CBaseCombatWeapon *pWeapon, CBaseEntity *pTarget = NULL ); virtual float GetSpreadBias( CBaseCombatWeapon *pWeapon, CBaseEntity *pTarget ); virtual void DoMuzzleFlash(); +#ifdef MAPBASE_VSCRIPT + HSCRIPT ScriptGetActiveWeapon(); + HSCRIPT ScriptGetWeapon( int i ); + HSCRIPT ScriptGetWeaponByType( const char *pszWeapon, int iSubType = 0 ); + void ScriptGetAllWeapons( HSCRIPT hTable ); + int ScriptGetCurrentWeaponProficiency() { return GetCurrentWeaponProficiency(); } + + void ScriptDropWeapon( HSCRIPT hWeapon ); + void ScriptEquipWeapon( HSCRIPT hWeapon ); + + int ScriptGiveAmmo( int iCount, int iAmmoIndex, bool bSuppressSound = false ); + void ScriptRemoveAmmo( int iCount, int iAmmoIndex ); + int ScriptGetAmmoCount( int iType ) const; + void ScriptSetAmmoCount( int iType, int iCount ); + + const Vector& ScriptGetAttackSpread( HSCRIPT hWeapon, HSCRIPT hTarget ); + float ScriptGetSpreadBias( HSCRIPT hWeapon, HSCRIPT hTarget ); + + int ScriptRelationType( HSCRIPT pTarget ); + int ScriptRelationPriority( HSCRIPT pTarget ); + void ScriptSetRelationship( HSCRIPT pTarget, int disposition, int priority ); + void ScriptSetClassRelationship( int classify, int disposition, int priority ); + + HSCRIPT ScriptGetVehicleEntity(); + + bool ScriptInViewCone( const Vector &vecSpot ) { return FInViewCone( vecSpot ); } + bool ScriptEntInViewCone( HSCRIPT pEntity ) { return FInViewCone( ToEnt( pEntity ) ); } + + bool ScriptInAimCone( const Vector &vecSpot ) { return FInAimCone( vecSpot ); } + bool ScriptEntInAimCone( HSCRIPT pEntity ) { return FInAimCone( ToEnt( pEntity ) ); } + + const Vector& ScriptBodyAngles( void ) { static Vector vec; QAngle qa = BodyAngles(); vec.x = qa.x; vec.y = qa.y; vec.z = qa.z; return vec; } + + static ScriptHook_t g_Hook_RelationshipType; + static ScriptHook_t g_Hook_RelationshipPriority; +#endif + // Interactions static void InitInteractionSystem(); // Relationships static void AllocateDefaultRelationships( ); static void SetDefaultRelationship( Class_T nClass, Class_T nClassTarget, Disposition_t nDisposition, int nPriority ); +#ifdef MAPBASE + static bool DefaultRelationshipsLoaded(); + static Disposition_t GetDefaultRelationshipDisposition( Class_T nClassSource, Class_T nClassTarget ); + static int GetDefaultRelationshipPriority( Class_T nClassSource, Class_T nClassTarget ); + int GetDefaultRelationshipPriority( Class_T nClassTarget ); +#endif Disposition_t GetDefaultRelationshipDisposition( Class_T nClassTarget ); virtual void AddEntityRelationship( CBaseEntity *pEntity, Disposition_t nDisposition, int nPriority ); virtual bool RemoveEntityRelationship( CBaseEntity *pEntity ); virtual void AddClassRelationship( Class_T nClass, Disposition_t nDisposition, int nPriority ); +#ifdef MAPBASE + virtual bool RemoveClassRelationship( Class_T nClass ); +#endif virtual void ChangeTeam( int iTeamNum ) OVERRIDE; @@ -427,6 +539,7 @@ public: void AddGlowEffect( void ); void RemoveGlowEffect( void ); bool IsGlowEffectActive( void ); + void SetGlowColor( float red, float green, float blue, float alpha ); #endif // GLOWS_ENABLE #ifdef INVASION_DLL @@ -460,7 +573,9 @@ public: public: // returns the last body region that took damage int LastHitGroup() const { return m_LastHitGroup; } +#ifndef MAPBASE // For filter_damage_transfer protected: +#endif void SetLastHitGroup( int nHitGroup ) { m_LastHitGroup = nHitGroup; } public: @@ -469,6 +584,8 @@ public: #ifdef GLOWS_ENABLE protected: CNetworkVar( bool, m_bGlowEnabled ); + CNetworkVector( m_GlowColor ); + CNetworkVar( float, m_GlowAlpha ); #endif // GLOWS_ENABLE private: @@ -491,6 +608,11 @@ protected: public: static int GetInteractionID(); // Returns the next interaction # +#ifdef MAPBASE + // Mapbase's new method for adding interactions which allows them to be handled with their names, currently for VScript + static void AddInteractionWithString( int &interaction, const char *szName ); +#endif + protected: // Visibility-related stuff bool ComputeLOS( const Vector &vecEyePosition, const Vector &vecTarget ) const; @@ -513,6 +635,11 @@ private: // cached off as the CurrentWeaponProficiency. WeaponProficiency_t m_CurrentWeaponProficiency; +#ifdef MAPBASE + // Weapon proficiency can be overridden with this. + WeaponProficiency_t m_ProficiencyOverride = WEAPON_PROFICIENCY_INVALID; +#endif + // --------------- // Relationships // --------------- diff --git a/src/game/server/basecombatweapon.cpp b/src/game/server/basecombatweapon.cpp index 29d34710..6a56ad4f 100644 --- a/src/game/server/basecombatweapon.cpp +++ b/src/game/server/basecombatweapon.cpp @@ -49,6 +49,7 @@ short g_sModelIndexBubbles; // holds the index for the bubbles model short g_sModelIndexBloodDrop; // holds the sprite index for the initial blood short g_sModelIndexBloodSpray; // holds the sprite index for splattered blood +#ifndef MAPBASE_VSCRIPT BEGIN_ENT_SCRIPTDESC( CBaseCombatWeapon, BASECOMBATWEAPON_DERIVED_FROM, "Base Combat Weapon" ) DEFINE_SCRIPTFUNC( SetCustomViewModel, "Sets a custom view model for this weapon by model name" ) DEFINE_SCRIPTFUNC( SetCustomViewModelModelIndex, "Sets a custom view model for this weapon by modelindex" ) @@ -86,6 +87,7 @@ BEGIN_ENT_SCRIPTDESC( CBaseCombatWeapon, BASECOMBATWEAPON_DERIVED_FROM, "Base Co DEFINE_SCRIPTFUNC( GetPrimaryAmmoCount, "Current primary ammo count if no clip is used or to give a player if they pick up this weapon legacy style (not TF)" ) DEFINE_SCRIPTFUNC( GetSecondaryAmmoCount, "Current secondary ammo count if no clip is used or to give a player if they pick up this weapon legacy style (not TF)" ) END_SCRIPTDESC(); +#endif ConVar weapon_showproficiency( "weapon_showproficiency", "0" ); extern ConVar ai_debug_shoot_positions; @@ -379,7 +381,11 @@ bool CBaseCombatWeapon::WeaponLOSCondition( const Vector &ownerPos, const Vector if ( pBCC ) { +#ifdef MAPBASE + if ( npcOwner->IRelationType( pBCC ) <= D_FR ) +#else if ( npcOwner->IRelationType( pBCC ) == D_HT ) +#endif return true; if ( bSetConditions ) @@ -406,7 +412,12 @@ bool CBaseCombatWeapon::WeaponLOSCondition( const Vector &ownerPos, const Vector //----------------------------------------------------------------------------- int CBaseCombatWeapon::WeaponRangeAttack1Condition( float flDot, float flDist ) { +#ifdef MAPBASE + // HACKHACK: HasPrimaryAmmo() checks the NPC's reserve ammo counts, which should not be evaluated here if we use clips + if ( UsesPrimaryAmmo() && (UsesClipsForAmmo1() ? !m_iClip1 : !HasPrimaryAmmo()) ) +#else if ( UsesPrimaryAmmo() && !HasPrimaryAmmo() ) +#endif { return COND_NO_PRIMARY_AMMO; } @@ -515,7 +526,11 @@ void CBaseCombatWeapon::Kill( void ) //----------------------------------------------------------------------------- void CBaseCombatWeapon::FallInit( void ) { +#ifdef MAPBASE + SetModel( (GetDroppedModel() && GetDroppedModel()[0]) ? GetDroppedModel() : GetWorldModel() ); +#else SetModel( GetWorldModel() ); +#endif VPhysicsDestroyObject(); if ( !VPhysicsInitNormal( SOLID_BBOX, GetSolidFlags() | FSOLID_TRIGGER, false ) ) @@ -753,6 +768,11 @@ void CBaseCombatWeapon::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_ { m_OnPlayerUse.FireOutput( pActivator, pCaller ); +#ifdef MAPBASE + // Mark that we're being +USE'd, not bumped + AddSpawnFlags(SF_WEAPON_USED); +#endif + // // Bump the weapon to try equipping it before picking it up physically. This is // important in a few spots in the game where the player could potentially +use pickup @@ -766,6 +786,10 @@ void CBaseCombatWeapon::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_ { pPlayer->PickupObject( this ); } + +#ifdef MAPBASE + RemoveSpawnFlags(SF_WEAPON_USED); +#endif } } diff --git a/src/game/server/baseentity.cpp b/src/game/server/baseentity.cpp index 3462fd37..d01c7e86 100644 --- a/src/game/server/baseentity.cpp +++ b/src/game/server/baseentity.cpp @@ -63,6 +63,13 @@ #include "tier1/utlstring.h" #include "utlhashtable.h" #include "vscript_server.h" +#ifdef MAPBASE +#include "mapbase/matchers.h" +#include "mapbase/datadesc_mod.h" +#endif +#ifdef NEW_RESPONSE_SYSTEM +#include "ai_speech.h" +#endif #if defined( TF_DLL ) #include "tf_gamerules.h" @@ -100,6 +107,11 @@ ConVar sv_netvisdist( "sv_netvisdist", "10000", FCVAR_CHEAT | FCVAR_DEVELOPMENTO ConVar sv_script_think_interval("sv_script_think_interval", "0.1"); +#ifdef MAPBASE_VSCRIPT +ConVar ent_text_allow_script( "ent_text_allow_script", "1" ); +#endif + + // This table encodes edict data. void SendProxy_AnimTime( const SendProp *pProp, const void *pStruct, const void *pVarData, DVariant *pOut, int iElement, int objectID ) { @@ -277,6 +289,11 @@ IMPLEMENT_SERVERCLASS_ST_NOBASE( CBaseEntity, DT_BaseEntity ) SendPropInt (SENDINFO(m_nRenderMode), 8, SPROP_UNSIGNED ), SendPropInt (SENDINFO(m_fEffects), EF_MAX_BITS, SPROP_UNSIGNED), SendPropInt (SENDINFO(m_clrRender), 32, SPROP_UNSIGNED), +#ifdef MAPBASE + // Keep consistent with VIEW_ID_COUNT in viewrender.h + SendPropInt (SENDINFO(m_iViewHideFlags), 9, SPROP_UNSIGNED ), + SendPropBool (SENDINFO(m_bDisableFlashlight) ), +#endif SendPropInt (SENDINFO(m_iTeamNum), TEAMNUM_NUM_BITS, 0), SendPropInt (SENDINFO(m_CollisionGroup), 5, SPROP_UNSIGNED), SendPropFloat (SENDINFO(m_flElasticity), 0, SPROP_COORD), @@ -286,6 +303,8 @@ IMPLEMENT_SERVERCLASS_ST_NOBASE( CBaseEntity, DT_BaseEntity ) SendPropEHandle (SENDINFO_NAME(m_hMoveParent, moveparent)), SendPropInt (SENDINFO(m_iParentAttachment), NUM_PARENTATTACHMENT_BITS, SPROP_UNSIGNED), + SendPropStringT( SENDINFO( m_iName ) ), + SendPropInt (SENDINFO_NAME( m_MoveType, movetype ), MOVETYPE_MAX_BITS, SPROP_UNSIGNED ), SendPropInt (SENDINFO_NAME( m_MoveCollide, movecollide ), MOVECOLLIDE_MAX_BITS, SPROP_UNSIGNED ), #if PREDICTION_ERROR_CHECK_LEVEL > 1 @@ -445,6 +464,17 @@ extern bool g_bDisableEhandleAccess; //----------------------------------------------------------------------------- CBaseEntity::~CBaseEntity( ) { +#ifdef MAPBASE_VSCRIPT + // HACKHACK: This is needed to fix a crash when an entity removes itself with Destroy() during its own think function. + // (see https://github.com/mapbase-source/source-sdk-2013/issues/138) + FOR_EACH_VEC( m_ScriptThinkFuncs, i ) + { + HSCRIPT h = m_ScriptThinkFuncs[i]->m_hfnThink; + if ( h ) g_pScriptVM->ReleaseScript( h ); + } + m_ScriptThinkFuncs.PurgeAndDeleteElements(); +#endif // MAPBASE_VSCRIPT + // FIXME: This can't be called from UpdateOnRemove! There's at least one // case where friction sounds are added between the call to UpdateOnRemove + ~CBaseEntity PhysCleanupFrictionSounds( this ); @@ -1118,6 +1148,42 @@ int CBaseEntity::DrawDebugTextOverlays(void) EntityText( offset,tempstr,0 ); offset++; } + +#ifdef MAPBASE + if (m_ResponseContexts.Count() > 0) + { + const char *contexts = UTIL_VarArgs("%s:%s", STRING(m_ResponseContexts[0].m_iszName), STRING(m_ResponseContexts[0].m_iszValue)); + for (int i = 1; i < GetContextCount(); i++) + { + contexts = UTIL_VarArgs("%s,%s:%s", contexts, STRING(m_ResponseContexts[i].m_iszName), STRING(m_ResponseContexts[i].m_iszValue)); + } + Q_snprintf(tempstr, sizeof(tempstr), "Response Contexts:%s", contexts); + EntityText(offset, tempstr, 0); + offset++; + } +#endif + +#ifdef MAPBASE_VSCRIPT + // 'OnEntText' hook inspired by later source games + if (m_ScriptScope.IsInitialized() && g_Hook_OnEntText.CanRunInScope( m_ScriptScope )) + { + if (ent_text_allow_script.GetBool()) + { + ScriptVariant_t functionReturn; + if ( g_Hook_OnEntText.Call( m_ScriptScope, &functionReturn, NULL ) && functionReturn.GetType() == FIELD_CSTRING) + { + CUtlStringList outStrings; + V_SplitString( functionReturn.Get(), "\n", outStrings); + + FOR_EACH_VEC( outStrings, i ) + { + EntityText( offset, outStrings[i], 0, 224, 240, 255 ); + offset++; + } + } + } + } +#endif } if (m_debugOverlays & OVERLAY_VIEWOFFSET) @@ -1132,7 +1198,11 @@ int CBaseEntity::DrawDebugTextOverlays(void) void CBaseEntity::SetParent( string_t newParent, CBaseEntity *pActivator, int iAttachment ) { // find and notify the new parent +#ifdef MAPBASE + CBaseEntity *pParent = gEntList.FindEntityByName( NULL, newParent, this, pActivator ); +#else CBaseEntity *pParent = gEntList.FindEntityByName( NULL, newParent, NULL, pActivator ); +#endif // debug check if ( newParent != NULL_STRING && pParent == NULL ) @@ -1142,7 +1212,11 @@ void CBaseEntity::SetParent( string_t newParent, CBaseEntity *pActivator, int iA else { // make sure there isn't any ambiguity +#ifdef MAPBASE + if ( gEntList.FindEntityByName( pParent, newParent, this, pActivator ) ) +#else if ( gEntList.FindEntityByName( pParent, newParent, NULL, pActivator ) ) +#endif { Msg( "Entity %s(%s) has ambigious parent %s\n", STRING(m_iClassname), GetDebugName(), STRING(newParent) ); } @@ -1372,28 +1446,39 @@ void CBaseEntity::FireNamedOutput( const char *pszOutput, variant_t variant, CBa if ( pszOutput == NULL ) return; - datamap_t *dmap = GetDataDescMap(); - while ( dmap ) + CBaseEntityOutput *pOutput = FindNamedOutput( pszOutput ); + if ( pOutput ) { - int fields = dmap->dataNumFields; - for ( int i = 0; i < fields; i++ ) - { - typedescription_t *dataDesc = &dmap->dataDesc[i]; - if ( ( dataDesc->fieldType == FIELD_CUSTOM ) && ( dataDesc->flags & FTYPEDESC_OUTPUT ) ) - { - CBaseEntityOutput *pOutput = ( CBaseEntityOutput * )( ( intp )this + ( int )dataDesc->fieldOffset[0] ); - if ( !Q_stricmp( dataDesc->externalName, pszOutput ) ) - { - pOutput->FireOutput( variant, pActivator, pCaller, flDelay ); - return; - } - } - } - - dmap = dmap->baseMap; + pOutput->FireOutput( variant, pActivator, pCaller, flDelay ); + return; } } +#ifdef MAPBASE_VSCRIPT +void CBaseEntity::ScriptFireOutput( const char *pszOutput, HSCRIPT hActivator, HSCRIPT hCaller, const char *szValue, float flDelay ) +{ + variant_t value; + value.SetString( MAKE_STRING(szValue) ); + + FireNamedOutput( pszOutput, value, ToEnt(hActivator), ToEnt(hCaller), flDelay ); +} + +float CBaseEntity::GetMaxOutputDelay( const char *pszOutput ) +{ + CBaseEntityOutput *pOutput = FindNamedOutput( pszOutput ); + if ( pOutput ) + { + return pOutput->GetMaxDelay(); + } + return 0; +} + +//void CBaseEntity::CancelEventsByInput( const char *szInput ) +//{ +// g_EventQueue.CancelEventsByInput( this, szInput ); +//} +#endif // MAPBASE_VSCRIPT + CBaseEntityOutput *CBaseEntity::FindNamedOutput( const char *pszOutput ) { if ( pszOutput == NULL ) @@ -1562,6 +1647,7 @@ int CBaseEntity::OnTakeDamage( const CTakeDamageInfo &info ) return 1; } +#ifdef VSCRIPT_PRIORITIZE_TF2_SYNTAX //----------------------------------------------------------------------------- // VScript: Scale damage done and call OnTakeDamage //----------------------------------------------------------------------------- @@ -1574,6 +1660,7 @@ void CBaseEntity::ScriptTakeDamage( float flDamage, int nDamageType, HSCRIPT hAt CTakeDamageInfo info( pAttacker, pAttacker, flDamage, nDamageType ); TakeDamage( info ); } +#endif //----------------------------------------------------------------------------- // VScript: Scale damage done and call OnTakeDamage @@ -1674,17 +1761,27 @@ int CBaseEntity::TakeDamage( const CTakeDamageInfo &inputInfo ) } } +#ifndef MAPBASE // Moved below the gamerules AllowDamage() check // Make sure our damage filter allows the damage. if ( !PassesDamageFilter( inputInfo )) { return 0; } +#endif if( !g_pGameRules->AllowDamage(this, inputInfo) ) { return 0; } +#ifdef MAPBASE + // Make sure our damage filter allows the damage. + if ( !PassesFinalDamageFilter( inputInfo )) + { + return 0; + } +#endif + if ( PhysIsInCallback() ) { PhysCallbackDamage( this, inputInfo ); @@ -1704,6 +1801,11 @@ int CBaseEntity::TakeDamage( const CTakeDamageInfo &inputInfo ) //Msg("%s took %.2f Damage, at %.2f\n", GetClassname(), info.GetDamage(), gpGlobals->curtime ); +#ifdef MAPBASE + // Modify damage if we have a filter that does that + DamageFilterDamageMod(info); +#endif + if ( ScriptHookEnabled( "OnTakeDamage" ) ) { IScriptVM *pVM = g_pScriptVM; @@ -1833,7 +1935,27 @@ int CBaseEntity::VPhysicsTakeDamage( const CTakeDamageInfo &info ) if ( gameFlags & FVPHYSICS_PLAYER_HELD ) { // if the player is holding the object, use it's real mass (player holding reduced the mass) - CBasePlayer *pPlayer = UTIL_GetLocalPlayer(); + CBasePlayer *pPlayer = NULL; + + if ( AI_IsSinglePlayer() ) + { + pPlayer = UTIL_GetLocalPlayer(); + } + else + { + // See which MP player is holding the physics object and then use that player to get the real mass of the object. + // This is ugly but better than having linkage between an object and its "holding" player. + for ( int i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBasePlayer *tempPlayer = UTIL_PlayerByIndex( i ); + if ( tempPlayer && (tempPlayer->GetHeldObject() == this ) ) + { + pPlayer = tempPlayer; + break; + } + } + } + if ( pPlayer ) { float mass = pPlayer->GetHeldObjectMass( VPhysicsGetObject() ); @@ -1867,6 +1989,12 @@ int CBaseEntity::VPhysicsTakeDamage( const CTakeDamageInfo &info ) // Character killed (only fired once) void CBaseEntity::Event_Killed( const CTakeDamageInfo &info ) { +#ifdef MAPBASE_VSCRIPT + // False = Cheat death + if (ScriptDeathHook( const_cast(&info) ) == false) + return; +#endif + if( info.GetAttacker() ) { info.GetAttacker()->Event_KilledOther(this, info); @@ -1901,6 +2029,22 @@ void CBaseEntity::SendOnKilledGameEvent( const CTakeDamageInfo &info ) } } +void CBaseEntity::Event_KilledOther( CBaseEntity *pVictim, const CTakeDamageInfo &info ) +{ +#ifdef MAPBASE_VSCRIPT + if (m_ScriptScope.IsInitialized() && g_Hook_OnKilledOther.CanRunInScope( m_ScriptScope )) + { + HSCRIPT hInfo = g_pScriptVM->RegisterInstance( const_cast(&info) ); + + // victim, info + ScriptVariant_t args[] = { ScriptVariant_t( pVictim->GetScriptInstance() ), ScriptVariant_t( hInfo ) }; + g_Hook_OnKilledOther.Call( m_ScriptScope, NULL, args ); + + g_pScriptVM->RemoveInstance( hInfo ); + } +#endif +} + bool CBaseEntity::HasTarget( string_t targetname ) { @@ -2045,6 +2189,10 @@ BEGIN_DATADESC_NO_BASE( CBaseEntity ) DEFINE_KEYFIELD( m_fEffects, FIELD_INTEGER, "effects" ), DEFINE_KEYFIELD( m_clrRender, FIELD_COLOR32, "rendercolor" ), DEFINE_GLOBAL_KEYFIELD( m_nModelIndex, FIELD_SHORT, "modelindex" ), +#ifdef MAPBASE + DEFINE_KEYFIELD( m_iViewHideFlags, FIELD_INTEGER, "viewhideflags" ), + DEFINE_KEYFIELD( m_bDisableFlashlight, FIELD_BOOLEAN, "disableflashlight" ), +#endif #if !defined( NO_ENTITY_PREDICTION ) // DEFINE_FIELD( m_PredictableID, CPredictableId ), #endif @@ -2087,7 +2235,11 @@ BEGIN_DATADESC_NO_BASE( CBaseEntity ) DEFINE_KEYFIELD( m_MoveType, FIELD_CHARACTER, "MoveType" ), DEFINE_FIELD( m_MoveCollide, FIELD_CHARACTER ), +#ifdef MAPBASE + DEFINE_KEYFIELD( m_hOwnerEntity, FIELD_EHANDLE, "OwnerEntity" ), +#else DEFINE_FIELD( m_hOwnerEntity, FIELD_EHANDLE ), +#endif DEFINE_KEYFIELD( m_CollisionGroup, FIELD_INTEGER, "CollisionGroup" ), DEFINE_PHYSPTR( m_pPhysicsObject), DEFINE_FIELD( m_flElasticity, FIELD_FLOAT ), @@ -2138,7 +2290,12 @@ BEGIN_DATADESC_NO_BASE( CBaseEntity ) DEFINE_KEYFIELD( m_vecViewOffset, FIELD_VECTOR, "view_ofs" ), +#ifdef MAPBASE + // You know, m_fFlags access + DEFINE_KEYFIELD( m_fFlags, FIELD_INTEGER, "m_fFlags" ), +#else DEFINE_FIELD( m_fFlags, FIELD_INTEGER ), +#endif #if !defined( NO_ENTITY_PREDICTION ) // DEFINE_FIELD( m_bIsPlayerSimulated, FIELD_INTEGER ), // DEFINE_FIELD( m_hPlayerSimulationOwner, FIELD_EHANDLE ), @@ -2187,22 +2344,109 @@ BEGIN_DATADESC_NO_BASE( CBaseEntity ) DEFINE_INPUTFUNC( FIELD_VOID, "EnableShadow", InputEnableShadow ), DEFINE_INPUTFUNC( FIELD_STRING, "AddOutput", InputAddOutput ), +#ifdef MAPBASE + DEFINE_INPUTFUNC( FIELD_STRING, "ChangeVariable", InputChangeVariable ), +#endif +#ifdef MAPBASE + DEFINE_INPUTFUNC( FIELD_INPUT, "PassUser1", InputPassUser1 ), + DEFINE_INPUTFUNC( FIELD_INPUT, "PassUser2", InputPassUser2 ), + DEFINE_INPUTFUNC( FIELD_INPUT, "PassUser3", InputPassUser3 ), + DEFINE_INPUTFUNC( FIELD_INPUT, "PassUser4", InputPassUser4 ), + + DEFINE_INPUTFUNC( FIELD_VOID, "FireUser1", InputFireUser1 ), + DEFINE_INPUTFUNC( FIELD_VOID, "FireUser2", InputFireUser2 ), + DEFINE_INPUTFUNC( FIELD_VOID, "FireUser3", InputFireUser3 ), + DEFINE_INPUTFUNC( FIELD_VOID, "FireUser4", InputFireUser4 ), + + DEFINE_INPUTFUNC( FIELD_VOID, "FireRandomUser", InputFireRandomUser ), + DEFINE_INPUTFUNC( FIELD_INPUT, "PassRandomUser", InputPassRandomUser ), +#else DEFINE_INPUTFUNC( FIELD_STRING, "FireUser1", InputFireUser1 ), DEFINE_INPUTFUNC( FIELD_STRING, "FireUser2", InputFireUser2 ), DEFINE_INPUTFUNC( FIELD_STRING, "FireUser3", InputFireUser3 ), DEFINE_INPUTFUNC( FIELD_STRING, "FireUser4", InputFireUser4 ), +#endif + +#ifdef MAPBASE + DEFINE_OUTPUT( m_OutUser1, "OutUser1" ), + DEFINE_OUTPUT( m_OutUser2, "OutUser2" ), + DEFINE_OUTPUT( m_OutUser3, "OutUser3" ), + DEFINE_OUTPUT( m_OutUser4, "OutUser4" ), +#endif DEFINE_INPUTFUNC( FIELD_STRING, "RunScriptFile", InputRunScriptFile ), DEFINE_INPUTFUNC( FIELD_STRING, "RunScriptCode", InputRunScript ), DEFINE_INPUTFUNC( FIELD_STRING, "CallScriptFunction", InputCallScriptFunction ), DEFINE_INPUTFUNC( FIELD_VOID, "TerminateScriptScope", InputTerminateScriptScope ), +#ifdef MAPBASE_VSCRIPT + DEFINE_INPUTFUNC( FIELD_STRING, "RunScriptCodeQuotable", InputRunScriptQuotable ), + DEFINE_INPUTFUNC( FIELD_VOID, "ClearScriptScope", InputClearScriptScope ), +#endif DEFINE_OUTPUT( m_OnUser1, "OnUser1" ), DEFINE_OUTPUT( m_OnUser2, "OnUser2" ), DEFINE_OUTPUT( m_OnUser3, "OnUser3" ), DEFINE_OUTPUT( m_OnUser4, "OnUser4" ), +#ifdef MAPBASE + DEFINE_INPUTFUNC( FIELD_STRING, "SetEntityName", InputSetEntityName ), + + DEFINE_INPUTFUNC( FIELD_STRING, "SetTarget", InputSetTarget ), + DEFINE_INPUTFUNC( FIELD_EHANDLE, "SetOwnerEntity", InputSetOwnerEntity ), + + DEFINE_INPUTFUNC( FIELD_INTEGER, "SetHealth", InputSetHealth ), + DEFINE_INPUTFUNC( FIELD_INTEGER, "AddHealth", InputAddHealth ), + DEFINE_INPUTFUNC( FIELD_INTEGER, "RemoveHealth", InputRemoveHealth ), + + DEFINE_INPUTFUNC( FIELD_INTEGER, "SetMaxHealth", InputSetMaxHealth ), + + DEFINE_INPUTFUNC( FIELD_STRING, "FireOutput", InputFireOutput ), + DEFINE_INPUTFUNC( FIELD_STRING, "RemoveOutput", InputRemoveOutput ), + //DEFINE_INPUTFUNC( FIELD_STRING, "CancelOutput", InputCancelOutput ), // Find a way to implement this + DEFINE_INPUTFUNC( FIELD_STRING, "ReplaceOutput", InputReplaceOutput ), + DEFINE_INPUTFUNC( FIELD_STRING, "AcceptInput", InputAcceptInput ), + DEFINE_INPUTFUNC( FIELD_VOID, "CancelPending", InputCancelPending ), + + DEFINE_INPUTFUNC( FIELD_VOID, "FreeChildren", InputFreeChildren ), + + DEFINE_INPUTFUNC( FIELD_VECTOR, "SetLocalOrigin", InputSetLocalOrigin ), + DEFINE_INPUTFUNC( FIELD_VECTOR, "SetLocalAngles", InputSetLocalAngles ), + DEFINE_INPUTFUNC( FIELD_VECTOR, "SetAbsOrigin", InputSetAbsOrigin ), + DEFINE_INPUTFUNC( FIELD_VECTOR, "SetAbsAngles", InputSetAbsAngles ), + DEFINE_INPUTFUNC( FIELD_VECTOR, "SetLocalVelocity", InputSetLocalVelocity ), + DEFINE_INPUTFUNC( FIELD_VECTOR, "SetLocalAngularVelocity", InputSetLocalAngularVelocity ), + + DEFINE_INPUTFUNC( FIELD_INTEGER, "AddSpawnFlags", InputAddSpawnFlags ), + DEFINE_INPUTFUNC( FIELD_INTEGER, "RemoveSpawnFlags", InputRemoveSpawnFlags ), + DEFINE_INPUTFUNC( FIELD_INTEGER, "SetRenderMode", InputSetRenderMode ), + DEFINE_INPUTFUNC( FIELD_INTEGER, "SetRenderFX", InputSetRenderFX ), + DEFINE_INPUTFUNC( FIELD_INTEGER, "SetViewHideFlags", InputSetViewHideFlags ), + DEFINE_INPUTFUNC( FIELD_INTEGER, "AddEffects", InputAddEffects ), + DEFINE_INPUTFUNC( FIELD_INTEGER, "RemoveEffects", InputRemoveEffects ), + DEFINE_INPUTFUNC( FIELD_VOID, "EnableDraw", InputDrawEntity ), + DEFINE_INPUTFUNC( FIELD_VOID, "DisableDraw", InputUndrawEntity ), + DEFINE_INPUTFUNC( FIELD_VOID, "EnableReceivingFlashlight", InputEnableReceivingFlashlight ), + DEFINE_INPUTFUNC( FIELD_VOID, "DisableReceivingFlashlight", InputDisableReceivingFlashlight ), + DEFINE_INPUTFUNC( FIELD_INTEGER, "AddEFlags", InputAddEFlags ), + DEFINE_INPUTFUNC( FIELD_INTEGER, "RemoveEFlags", InputRemoveEFlags ), + DEFINE_INPUTFUNC( FIELD_INTEGER, "AddSolidFlags", InputAddSolidFlags ), + DEFINE_INPUTFUNC( FIELD_INTEGER, "RemoveSolidFlags", InputRemoveSolidFlags ), + DEFINE_INPUTFUNC( FIELD_INTEGER, "SetMoveType", InputSetMoveType ), + DEFINE_INPUTFUNC( FIELD_INTEGER, "SetCollisionGroup", InputSetCollisionGroup ), + + DEFINE_INPUTFUNC( FIELD_EHANDLE, "Touch", InputTouch ), + + DEFINE_INPUTFUNC( FIELD_INPUT, "KilledNPC", InputKilledNPC ), + + DEFINE_INPUTFUNC( FIELD_VOID, "KillIfNotVisible", InputKillIfNotVisible ), + DEFINE_INPUTFUNC( FIELD_FLOAT, "KillWhenNotVisible", InputKillWhenNotVisible ), + + DEFINE_INPUTFUNC( FIELD_STRING, "SetThinkNull", InputSetThinkNull ), + + DEFINE_OUTPUT( m_OnKilled, "OnKilled" ), +#endif + // Function Pointers DEFINE_FUNCTION( SUB_Remove ), DEFINE_FUNCTION( SUB_DoNothing ), @@ -2212,6 +2456,14 @@ BEGIN_DATADESC_NO_BASE( CBaseEntity ) DEFINE_FUNCTION( SUB_Vanish ), DEFINE_FUNCTION( SUB_CallUseToggle ), DEFINE_THINKFUNC( ShadowCastDistThink ), + DEFINE_THINKFUNC( ScriptThink ), +#ifdef MAPBASE_VSCRIPT + DEFINE_THINKFUNC( ScriptContextThink ), +#endif + +#ifdef MAPBASE + DEFINE_FUNCTION( SUB_RemoveWhenNotVisible ), +#endif DEFINE_THINKFUNC( ScriptThink ), @@ -2228,9 +2480,21 @@ BEGIN_DATADESC_NO_BASE( CBaseEntity ) END_DATADESC() -DEFINE_SCRIPT_INSTANCE_HELPER( CBaseEntity, &g_BaseEntityScriptInstanceHelper ) +#ifdef MAPBASE_VSCRIPT +ScriptHook_t CBaseEntity::g_Hook_UpdateOnRemove; +ScriptHook_t CBaseEntity::g_Hook_OnEntText; + +ScriptHook_t CBaseEntity::g_Hook_VPhysicsCollision; +ScriptHook_t CBaseEntity::g_Hook_FireBullets; +ScriptHook_t CBaseEntity::g_Hook_OnDeath; +ScriptHook_t CBaseEntity::g_Hook_OnKilledOther; +ScriptHook_t CBaseEntity::g_Hook_HandleInteraction; +ScriptHook_t CBaseEntity::g_Hook_ModifyEmitSoundParams; +ScriptHook_t CBaseEntity::g_Hook_ModifySentenceParams; +#endif BEGIN_ENT_SCRIPTDESC_ROOT( CBaseEntity, "Root class of all server-side entities" ) + DEFINE_SCRIPT_INSTANCE_HELPER( &g_BaseEntityScriptInstanceHelper ) DEFINE_SCRIPTFUNC_NAMED( ConnectOutputToScript, "ConnectOutput", "Adds an I/O connection that will call the named function when the specified output fires" ) DEFINE_SCRIPTFUNC_NAMED( DisconnectOutputFromScript, "DisconnectOutput", "Removes a connected script function from an I/O event." ) @@ -2260,17 +2524,40 @@ BEGIN_ENT_SCRIPTDESC_ROOT( CBaseEntity, "Root class of all server-side entities" DEFINE_SCRIPTFUNC_NAMED( ScriptInputKill, "Kill", "" ) DEFINE_SCRIPTFUNC( GetClassname, "" ) DEFINE_SCRIPTFUNC_NAMED( GetEntityNameAsCStr, "GetName", "" ) +#ifdef MAPBASE_VSCRIPT + DEFINE_SCRIPTFUNC( GetDebugName, "If name exists returns name, otherwise returns classname" ) + DEFINE_SCRIPTFUNC_NAMED( SetNameAsCStr, "SetName", "" ) +#endif DEFINE_SCRIPTFUNC( GetPreTemplateName, "Get the entity name stripped of template unique decoration" ) DEFINE_SCRIPTFUNC_NAMED( ScriptGetEHandle, "GetEntityHandle", "Get the entity as an EHANDLE" ) DEFINE_SCRIPTFUNC_NAMED( GetAbsOrigin, "GetOrigin", "This is GetAbsOrigin with a funny script name for some reason. Not changing it for legacy compat though." ) DEFINE_SCRIPTFUNC( SetAbsOrigin, "SetAbsOrigin" ) DEFINE_SCRIPTFUNC_NAMED( ScriptSetOrigin, "SetOrigin", "THIS DOESNT CALL SetAbsOrigin IT CALLS Teleport" ) + DEFINE_SCRIPTFUNC_NAMED( ScriptSetForward, "SetForwardVector", "Set the orientation of the entity to have this forward vector" ) DEFINE_SCRIPTFUNC_NAMED( ScriptGetForward, "GetForwardVector", "Get the forward vector of the entity" ) - DEFINE_SCRIPTFUNC_NAMED( ScriptGetRight, "GetRightVector", "Get the right vector of the entity" ) +#ifdef MAPBASE_VSCRIPT + DEFINE_SCRIPTFUNC_NAMED( ScriptGetRight, "GetRightVector", "Get the right vector of the entity" ) + DEFINE_SCRIPTFUNC_NAMED( ScriptGetLeft, "GetLeftVector", SCRIPT_HIDE ) +#else DEFINE_SCRIPTFUNC_NAMED( ScriptGetLeft, "GetLeftVector", "!!!LEGACY FOR COMPAT!!! Get the **right** vector of the entity. This is purely for compatibility. DO NOT USE ME. Use GetRightVector!" ) +#endif + DEFINE_SCRIPTFUNC_NAMED( ScriptGetUp, "GetUpVector", "Get the up vector of the entity" ) - DEFINE_SCRIPTFUNC_NAMED( ScriptSetForward, "SetForwardVector", "Set the orientation of the entity to have this forward vector" ) + +#ifdef MAPBASE_VSCRIPT + DEFINE_SCRIPTFUNC_NAMED( ScriptSetOriginAngles, "SetOriginAngles", "Set both the origin and the angles" ) + DEFINE_SCRIPTFUNC_NAMED( ScriptSetOriginAnglesVelocity, "SetOriginAnglesVelocity", "Set the origin, the angles, and the velocity" ) + + DEFINE_SCRIPTFUNC_NAMED( ScriptEntityToWorldTransform, "EntityToWorldTransform", "Get the entity's transform" ) + + DEFINE_SCRIPTFUNC_NAMED( ScriptGetPhysicsObject, "GetPhysicsObject", "Get the entity's physics object if it has one" ) + DEFINE_SCRIPTFUNC_NAMED( ScriptPhysicsInitNormal, "PhysicsInitNormal", "Initializes the entity's physics object with the specified solid type, solid flags, and whether to start asleep" ) + DEFINE_SCRIPTFUNC_NAMED( ScriptPhysicsDestroyObject, "PhysicsDestroyObject", "Destroys the entity's physics object" ) + + DEFINE_SCRIPTFUNC( BodyTarget, "" ) + DEFINE_SCRIPTFUNC( HeadTarget, "" ) +#endif // im not sure these do anything useful... DEFINE_SCRIPTFUNC( GetAbsVelocity, "Returns the current absolute velocity of the entity" ) @@ -2298,6 +2585,8 @@ BEGIN_ENT_SCRIPTDESC_ROOT( CBaseEntity, "Root class of all server-side entities" DEFINE_SCRIPTFUNC_NAMED( WorldSpaceCenter, "GetCenter", "Get vector to center of object - absolute coords") DEFINE_SCRIPTFUNC_NAMED( ScriptEyePosition, "EyePosition", "Get vector to eye position - absolute coords") + DEFINE_SCRIPTFUNC_NAMED( ScriptSetAngles, "SetAngles", "Set entity pitch, yaw, roll") + DEFINE_SCRIPTFUNC_NAMED( ScriptGetAngles, "GetAngles", "Get entity pitch, yaw, roll as a vector") DEFINE_SCRIPTFUNC( SetAbsAngles, "Set entity pitch, yaw, roll as QAngles") DEFINE_SCRIPTFUNC( GetAbsAngles, "Get entity pitch, yaw, roll as QAngles") @@ -2324,7 +2613,10 @@ BEGIN_ENT_SCRIPTDESC_ROOT( CBaseEntity, "Root class of all server-side entities" DEFINE_SCRIPTFUNC_NAMED( ScriptSetOwner, "SetOwner", "" ) DEFINE_SCRIPTFUNC_NAMED( GetTeamNumber, "GetTeam", "" ) DEFINE_SCRIPTFUNC_NAMED( ChangeTeam, "SetTeam", "" ) - + +#ifdef MAPBASE_VSCRIPT + DEFINE_SCRIPTFUNC_NAMED( ScriptSetParent, "SetParent", "" ) +#endif DEFINE_SCRIPTFUNC_NAMED( ScriptGetMoveParent, "GetMoveParent", "If in hierarchy, retrieves the entity's parent" ) DEFINE_SCRIPTFUNC_NAMED( ScriptGetRootMoveParent, "GetRootMoveParent", "If in hierarchy, walks up the hierarchy to find the root parent" ) DEFINE_SCRIPTFUNC_NAMED( ScriptFirstMoveChild, "FirstMoveChild", "" ) @@ -2408,8 +2700,170 @@ BEGIN_ENT_SCRIPTDESC_ROOT( CBaseEntity, "Root class of all server-side entities" DEFINE_SCRIPTFUNC_NAMED( ScriptAcceptInput, "AcceptInput", "Generate a synchronous I/O event" ) DEFINE_SCRIPTFUNC( IsAlive, "" ) + +#ifdef MAPBASE_VSCRIPT + DEFINE_SCRIPTFUNC( Activate, "" ) + + DEFINE_SCRIPTFUNC_NAMED( ScriptIsVisible, "IsVisible", "Check if the specified position can be visible to this entity." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptIsEntVisible, "IsEntVisible", "Check if the specified entity can be visible to this entity." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptIsVisibleWithMask, "IsVisibleWithMask", "Check if the specified position can be visible to this entity with a specific trace mask." ) + + DEFINE_SCRIPTFUNC_NAMED( ScriptTakeDamage, "TakeDamage", "Apply damage to this entity with a given info handle" ) + DEFINE_SCRIPTFUNC_NAMED( ScriptFireBullets, "FireBullets", "Fire bullets from entity with a given info handle" ) + + DEFINE_SCRIPTFUNC( TakeHealth, "Give this entity health" ) + + DEFINE_SCRIPTFUNC( GetWaterLevel, "Get current level of water submergence" ) + + DEFINE_SCRIPTFUNC_NAMED( ScriptGetContext, "GetContext", "Get a response context value" ) + DEFINE_SCRIPTFUNC_NAMED( ScriptAddContext, "AddContext", "Add a response context value" ) + DEFINE_SCRIPTFUNC( GetContextExpireTime, "Get a response context's expiration time" ) + DEFINE_SCRIPTFUNC( GetContextCount, "Get the number of response contexts" ) + DEFINE_SCRIPTFUNC_NAMED( ScriptGetContextIndex, "GetContextIndex", "Get a response context at a specific index in the form of a table" ) + + DEFINE_SCRIPTFUNC_NAMED( ScriptGetGroundEntity, "GetGroundEntity", "Get the entity we're standing on." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptSetGroundEntity, "SetGroundEntity", "Set the entity we're standing on." ) + + DEFINE_SCRIPTFUNC_NAMED( ScriptFollowEntity, "FollowEntity", "Begin following the specified entity. This makes this entity non-solid, parents it to the target entity, and teleports it to the specified entity's origin. The second parameter is whether or not to use bonemerging while following." ) + DEFINE_SCRIPTFUNC( StopFollowingEntity, "Stops following an entity if we're following one." ) + DEFINE_SCRIPTFUNC( IsFollowingEntity, "Returns true if this entity is following another entity." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptGetFollowedEntity, "GetFollowedEntity", "Get the entity we're following." ) + + DEFINE_SCRIPTFUNC_NAMED( ScriptClassify, "Classify", "Get Class_T class ID (corresponds to the CLASS_ set of constants)" ) + + DEFINE_SCRIPTFUNC_NAMED( ScriptFireOutput, "FireOutput", "Fire an entity output" ) + DEFINE_SCRIPTFUNC( GetMaxOutputDelay, "Get the longest delay for all events attached to an output" ) + //DEFINE_SCRIPTFUNC( CancelEventsByInput, "Cancel all I/O events for this entity, match input" ) // Commented out due to unpredictability and unknown risks + + DEFINE_SCRIPTFUNC_NAMED( ScriptAddOutput, "AddOutput", "Add an output" ) + DEFINE_SCRIPTFUNC_NAMED( ScriptGetKeyValue, "GetKeyValue", "Get a keyvalue" ) + + DEFINE_SCRIPTFUNC_NAMED( ScriptGetColorVector, "GetRenderColorVector", "Get the render color as a vector" ) + DEFINE_SCRIPTFUNC_NAMED( ScriptGetColorR, "GetRenderColorR", "Get the render color's R value" ) + DEFINE_SCRIPTFUNC_NAMED( ScriptGetColorG, "GetRenderColorG", "Get the render color's G value" ) + DEFINE_SCRIPTFUNC_NAMED( ScriptGetColorB, "GetRenderColorB", "Get the render color's B value" ) + DEFINE_SCRIPTFUNC_NAMED( ScriptGetAlpha, "GetRenderAlpha", "Get the render color's alpha value" ) + DEFINE_SCRIPTFUNC_NAMED( ScriptSetColorVector, "SetRenderColorVector", "Set the render color as a vector" ) + DEFINE_SCRIPTFUNC_NAMED( ScriptSetColor, "SetRenderColor", "Set the render color" ) + DEFINE_SCRIPTFUNC_NAMED( ScriptSetColorR, "SetRenderColorR", "Set the render color's R value" ) + DEFINE_SCRIPTFUNC_NAMED( ScriptSetColorG, "SetRenderColorG", "Set the render color's G value" ) + DEFINE_SCRIPTFUNC_NAMED( ScriptSetColorB, "SetRenderColorB", "Set the render color's B value" ) + DEFINE_SCRIPTFUNC_NAMED( ScriptSetAlpha, "SetRenderAlpha", "Set the render color's alpha value" ) + + // LEGACY + DEFINE_SCRIPTFUNC_NAMED( ScriptGetColorVector, "GetColorVector", SCRIPT_HIDE ) + DEFINE_SCRIPTFUNC_NAMED( ScriptGetColorR, "GetColorR", SCRIPT_HIDE ) + DEFINE_SCRIPTFUNC_NAMED( ScriptGetColorG, "GetColorG", SCRIPT_HIDE ) + DEFINE_SCRIPTFUNC_NAMED( ScriptGetColorB, "GetColorB", SCRIPT_HIDE ) + DEFINE_SCRIPTFUNC_NAMED( ScriptGetAlpha, "GetAlpha", SCRIPT_HIDE ) + DEFINE_SCRIPTFUNC_NAMED( ScriptSetColorVector, "SetColorVector", SCRIPT_HIDE ) + DEFINE_SCRIPTFUNC_NAMED( ScriptSetColorR, "SetColorR", SCRIPT_HIDE ) + DEFINE_SCRIPTFUNC_NAMED( ScriptSetColorG, "SetColorG", SCRIPT_HIDE ) + DEFINE_SCRIPTFUNC_NAMED( ScriptSetColorB, "SetColorB", SCRIPT_HIDE ) + DEFINE_SCRIPTFUNC_NAMED( ScriptSetAlpha, "SetAlpha", SCRIPT_HIDE ) + // END LEGACY + + DEFINE_SCRIPTFUNC_NAMED( ScriptGetRenderMode, "GetRenderMode", "Get render mode" ) + DEFINE_SCRIPTFUNC_NAMED( ScriptSetRenderMode, "SetRenderMode", "Set render mode" ) + + DEFINE_SCRIPTFUNC( GetSpawnFlags, "Get spawnflags" ) + DEFINE_SCRIPTFUNC( AddSpawnFlags, "Add spawnflag(s)" ) + DEFINE_SCRIPTFUNC( RemoveSpawnFlags, "Remove spawnflag(s)" ) + DEFINE_SCRIPTFUNC( ClearSpawnFlags, "Clear spawnflag(s)" ) + DEFINE_SCRIPTFUNC( HasSpawnFlags, "Check if the entity has specific spawnflag(s) ticked" ) + + DEFINE_SCRIPTFUNC( GetEffects, "Get effects" ) + DEFINE_SCRIPTFUNC( AddEffects, "Add effect(s)" ) + DEFINE_SCRIPTFUNC( RemoveEffects, "Remove effect(s)" ) + DEFINE_SCRIPTFUNC( ClearEffects, "Clear effect(s)" ) + DEFINE_SCRIPTFUNC( SetEffects, "Set effect(s)" ) + DEFINE_SCRIPTFUNC( IsEffectActive, "Check if an effect is active" ) + + DEFINE_SCRIPTFUNC( GetTransmitState, "" ) + DEFINE_SCRIPTFUNC( SetTransmitState, "" ) + + DEFINE_SCRIPTFUNC_NAMED( ScriptGetMoveType, "GetMoveType", "Get the move type" ) + DEFINE_SCRIPTFUNC_NAMED( ScriptSetMoveType, "SetMoveType", "Set the move type" ) + + DEFINE_SCRIPTFUNC( GetCollisionGroup, "Get the collision group" ) + DEFINE_SCRIPTFUNC( SetCollisionGroup, "Set the collision group" ) + + DEFINE_SCRIPTFUNC( GetGravity, "" ) + DEFINE_SCRIPTFUNC( SetGravity, "" ) + DEFINE_SCRIPTFUNC( GetFriction, "" ) + DEFINE_SCRIPTFUNC( SetFriction, "" ) + DEFINE_SCRIPTFUNC( GetMass, "" ) + DEFINE_SCRIPTFUNC( SetMass, "" ) + + DEFINE_SCRIPTFUNC_NAMED( ScriptGetSolid, "GetSolid", "" ) + DEFINE_SCRIPTFUNC_NAMED( ScriptSetSolid, "SetSolid", "" ) + + DEFINE_SCRIPTFUNC( IsNPC, "Returns true if this entity is a NPC." ) + DEFINE_SCRIPTFUNC( IsCombatCharacter, "Returns true if this entity is a combat character (player or NPC)." ) + DEFINE_SCRIPTFUNC_NAMED( IsBaseCombatWeapon, "IsWeapon", "Returns true if this entity is a weapon." ) + DEFINE_SCRIPTFUNC( IsWorld, "Returns true if this entity is the world." ) + + DEFINE_SCRIPTFUNC_NAMED( ScriptDispatchInteraction, "DispatchInteraction", "Dispatches an interaction on this entity. See the g_interaction set of constants for more information." ) + + DEFINE_SCRIPTFUNC_NAMED( ScriptGetTakeDamage, "GetTakeDamage", "Gets this entity's m_takedamage value. (DAMAGE_YES, DAMAGE_NO, etc.)" ) + DEFINE_SCRIPTFUNC_NAMED( ScriptSetTakeDamage, "SetTakeDamage", "Sets this entity's m_takedamage value. (DAMAGE_YES, DAMAGE_NO, etc.)" ) + + // DEFINE_SCRIPTFUNC( IsMarkedForDeletion, "Returns true if the entity is valid and marked for deletion." ) + + DEFINE_SCRIPTFUNC( GetOrCreatePrivateScriptScope, "Create and retrieve the script-side data associated with an entity" ) + + DEFINE_SCRIPTFUNC_NAMED( ScriptSetThinkFunction, "SetThinkFunction", "" ) + DEFINE_SCRIPTFUNC_NAMED( ScriptStopThinkFunction, "StopThinkFunction", "" ) + DEFINE_SCRIPTFUNC_NAMED( ScriptSetContextThink, "SetContextThink", "Set a think function on this entity." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptSetThink, "SetThink", "" ) + DEFINE_SCRIPTFUNC_NAMED( ScriptStopThink, "StopThink", "" ) + + // + // Hooks + // + DEFINE_SIMPLE_SCRIPTHOOK( CBaseEntity::g_Hook_UpdateOnRemove, "UpdateOnRemove", FIELD_VOID, "Called when the entity is being removed." ) + DEFINE_SIMPLE_SCRIPTHOOK( CBaseEntity::g_Hook_OnEntText, "OnEntText", FIELD_CSTRING, "Called every frame when ent_text is enabled on the entity. Return a string to be added to the ent_text printout." ) + + BEGIN_SCRIPTHOOK( CBaseEntity::g_Hook_VPhysicsCollision, "VPhysicsCollision", FIELD_VOID, "Called for every single VPhysics-related collision experienced by this entity." ) + DEFINE_SCRIPTHOOK_PARAM( "entity", FIELD_HSCRIPT ) + DEFINE_SCRIPTHOOK_PARAM( "speed", FIELD_FLOAT ) + DEFINE_SCRIPTHOOK_PARAM( "point", FIELD_VECTOR ) + DEFINE_SCRIPTHOOK_PARAM( "normal", FIELD_VECTOR ) + END_SCRIPTHOOK() + + BEGIN_SCRIPTHOOK( CBaseEntity::g_Hook_FireBullets, "FireBullets", FIELD_VOID, "Called for every single VPhysics-related collision experienced by this entity." ) + DEFINE_SCRIPTHOOK_PARAM( "entity", FIELD_HSCRIPT ) + DEFINE_SCRIPTHOOK_PARAM( "speed", FIELD_FLOAT ) + DEFINE_SCRIPTHOOK_PARAM( "point", FIELD_VECTOR ) + DEFINE_SCRIPTHOOK_PARAM( "normal", FIELD_VECTOR ) + END_SCRIPTHOOK() + + BEGIN_SCRIPTHOOK( CBaseEntity::g_Hook_OnDeath, "OnDeath", FIELD_BOOLEAN, "Called when the entity dies (Event_Killed). Returning false makes the entity cancel death, although this could have unforeseen consequences. For hooking any damage instead of just death, see filter_script and PassesFinalDamageFilter." ) + DEFINE_SCRIPTHOOK_PARAM( "info", FIELD_HSCRIPT ) + END_SCRIPTHOOK() + + BEGIN_SCRIPTHOOK( CBaseEntity::g_Hook_OnKilledOther, "OnKilledOther", FIELD_VOID, "Called when the entity kills another entity." ) + DEFINE_SCRIPTHOOK_PARAM( "victim", FIELD_HSCRIPT ) + DEFINE_SCRIPTHOOK_PARAM( "info", FIELD_HSCRIPT ) + END_SCRIPTHOOK() + + BEGIN_SCRIPTHOOK( CBaseEntity::g_Hook_HandleInteraction, "HandleInteraction", FIELD_BOOLEAN, "Called for internal game interactions. See the g_interaction set of constants for more information. Returning true or false will return that value without falling to any internal handling. Returning nothing will allow the interaction to fall to any internal handling." ) + DEFINE_SCRIPTHOOK_PARAM( "interaction", FIELD_INTEGER ) + //DEFINE_SCRIPTHOOK_PARAM( "data", FIELD_VARIANT ) + DEFINE_SCRIPTHOOK_PARAM( "sourceEnt", FIELD_HSCRIPT ) + END_SCRIPTHOOK() + + BEGIN_SCRIPTHOOK( CBaseEntity::g_Hook_ModifyEmitSoundParams, "ModifyEmitSoundParams", FIELD_VOID, "Called every time a sound is emitted on this entity, allowing for its parameters to be modified." ) + DEFINE_SCRIPTHOOK_PARAM( "params", FIELD_HSCRIPT ) + END_SCRIPTHOOK() + + BEGIN_SCRIPTHOOK( CBaseEntity::g_Hook_ModifySentenceParams, "ModifySentenceParams", FIELD_VOID, "Called every time a sentence is emitted on this entity, allowing for its parameters to be modified." ) + DEFINE_SCRIPTHOOK_PARAM( "params", FIELD_HSCRIPT ) + END_SCRIPTHOOK() +#endif END_SCRIPTDESC(); + // For code error checking extern bool g_bReceivedChainedUpdateOnRemove; @@ -2479,9 +2933,9 @@ void CBaseEntity::UpdateOnRemove( void ) // we need to do this now since we will set the name to nothing later if( m_bForcePurgeFixedupStrings ) { - if( m_iName != NULL_STRING ) + if( m_iName.Get() != NULL_STRING ) { - RemovePooledString( STRING( m_iName ) ); + RemovePooledString( STRING( m_iName.Get() ) ); } if( m_iszScriptId != NULL_STRING ) @@ -2527,6 +2981,13 @@ void CBaseEntity::UpdateOnRemove( void ) if ( m_hScriptInstance ) { +#ifdef MAPBASE_VSCRIPT + if ( m_ScriptScope.IsInitialized() && g_Hook_UpdateOnRemove.CanRunInScope( m_ScriptScope ) ) + { + g_Hook_UpdateOnRemove.Call( m_ScriptScope, NULL, NULL ); + } +#endif // MAPBASE_VSCRIPT + g_pScriptVM->RemoveInstance( m_hScriptInstance ); m_hScriptInstance = NULL; } @@ -3118,6 +3579,21 @@ void CBaseEntity::VPhysicsCollision( int index, gamevcollisionevent_t *pEvent ) int otherIndex = !index; CBaseEntity *pHitEntity = pEvent->pEntities[otherIndex]; +#ifdef MAPBASE_VSCRIPT + if (m_ScriptScope.IsInitialized() && g_Hook_VPhysicsCollision.CanRunInScope(m_ScriptScope)) + { + Vector vecContactPoint; + pEvent->pInternalData->GetContactPoint( vecContactPoint ); + + Vector vecSurfaceNormal; + pEvent->pInternalData->GetSurfaceNormal( vecSurfaceNormal ); + + // entity, speed, point, normal + ScriptVariant_t args[] = { ScriptVariant_t( pHitEntity->GetScriptInstance() ), pEvent->collisionSpeed, vecContactPoint, vecSurfaceNormal }; + g_Hook_VPhysicsCollision.Call( m_ScriptScope, NULL, args ); + } +#endif + // Don't make sounds / effects if neither entity is MOVETYPE_VPHYSICS. The game // physics should have done so. if ( GetMoveType() != MOVETYPE_VPHYSICS && pHitEntity->GetMoveType() != MOVETYPE_VPHYSICS ) @@ -3440,14 +3916,80 @@ bool CBaseEntity::PassesDamageFilter( const CTakeDamageInfo &info ) { CBaseFilter *pFilter = dynamic_cast< CBaseFilter * >( m_hDamageFilter.Get() ); if ( pFilter ) +#ifdef MAPBASE + return pFilter->PassesDamageFilter(this, info); +#else return pFilter->PassesDamageFilter(info); +#endif } return true; } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: A damage filter pass for when this is most certainly the part where we might actually take damage. +// Made for the "damage" family of filters, including filter_damage_transfer. +//----------------------------------------------------------------------------- +bool CBaseEntity::PassesFinalDamageFilter( const CTakeDamageInfo &info ) +{ + if (!PassesDamageFilter(info)) + return false; + + if (m_hDamageFilter) + { + CBaseFilter *pFilter = (CBaseFilter *)(m_hDamageFilter.Get()); + if (!pFilter->PassesFinalDamageFilter(this, info)) + { + return false; + } + } + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: A hack for damage transfers. +//----------------------------------------------------------------------------- +bool CBaseEntity::DamageFilterAllowsBlood( const CTakeDamageInfo &info ) +{ + if (m_hDamageFilter) + { + CBaseFilter *pFilter = (CBaseFilter *)(m_hDamageFilter.Get()); + if (!pFilter->BloodAllowed(this, info)) + { + return false; + } + } + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Modifies damage taken. Returns true if damage was successfully modded. +//----------------------------------------------------------------------------- +bool CBaseEntity::DamageFilterDamageMod( CTakeDamageInfo &info ) +{ + if (m_hDamageFilter) + { + CBaseFilter *pFilter = (CBaseFilter *)(m_hDamageFilter.Get()); + if (pFilter->DamageMod(this, info)) + { + return true; + } + } + + return false; +} +#endif + FORCEINLINE bool NamesMatch( const char *pszQuery, string_t nameToMatch ) { +#ifdef MAPBASE + // NamesMatch has been turned into Matcher_NamesMatch in matchers.h + // for a wider range of accessibility and flexibility. + return Matcher_NamesMatch(pszQuery, STRING(nameToMatch)); +#else if ( nameToMatch == NULL_STRING ) return (!pszQuery || *pszQuery == 0 || *pszQuery == '*'); @@ -3482,6 +4024,7 @@ FORCEINLINE bool NamesMatch( const char *pszQuery, string_t nameToMatch ) return true; return false; +#endif } bool CBaseEntity::NameMatchesComplex( const char *pszNameOrWildcard ) @@ -3721,6 +4264,7 @@ void CBaseEntity::OnSave( IEntitySaveUtils *pUtils ) //----------------------------------------------------------------------------- void CBaseEntity::OnRestore() { +#ifndef MAPBASE // It's your fault if you're trying to load old, broken saves from a possibly closed 2013 beta in Mapbase. #if defined( PORTAL ) || defined( HL2_EPISODIC ) || defined ( HL2_DLL ) || defined( HL2_LOSTCOAST ) // We had a short period during the 2013 beta where the FL_* flags had a bogus value near the top, so detect // these bad saves and just give up. Only saves from the short beta period should have been effected. @@ -3732,6 +4276,7 @@ void CBaseEntity::OnRestore() engine->ServerCommand("wait;wait;disconnect;showconsole\n"); } +#endif #endif SimThink_EntityChanged( this ); @@ -4191,9 +4736,9 @@ const char *CBaseEntity::GetDebugName(void) if ( this == NULL ) return "<>"; - if ( m_iName != NULL_STRING ) + if ( m_iName.Get() != NULL_STRING ) { - return STRING(m_iName); + return STRING(m_iName.Get()); } else { @@ -4370,17 +4915,12 @@ bool CBaseEntity::AcceptInput( const char *szInputName, CBaseEntity *pActivator, { // found a match - char szBuffer[256]; // mapper debug message - if (pCaller != NULL) - { - Q_snprintf( szBuffer, sizeof(szBuffer), "(%0.2f) input %s: %s.%s(%s)\n", gpGlobals->curtime, STRING(pCaller->m_iName), GetDebugName(), szInputName, Value.String() ); - } - else - { - Q_snprintf( szBuffer, sizeof(szBuffer), "(%0.2f) input : %s.%s(%s)\n", gpGlobals->curtime, GetDebugName(), szInputName, Value.String() ); - } - DevMsg( 2, "%s", szBuffer ); +#ifdef MAPBASE + CGMsg( 2, CON_GROUP_IO_SYSTEM, "(%0.2f) input %s: %s.%s(%s)\n", gpGlobals->curtime, pCaller ? STRING(pCaller->m_iName.Get()) : "", GetDebugName(), szInputName, Value.String() ); +#else + DevMsg( 2, "(%0.2f) input %s: %s.%s(%s)\n", gpGlobals->curtime, pCaller ? STRING(pCaller->m_iName.Get()) : "", GetDebugName(), szInputName, Value.String() ); +#endif ADD_DEBUG_HISTORY( HISTORY_ENTITY_IO, szBuffer ); if (m_debugOverlays & OVERLAY_MESSAGE_BIT) @@ -4393,15 +4933,51 @@ bool CBaseEntity::AcceptInput( const char *szInputName, CBaseEntity *pActivator, { if ( !(Value.FieldType() == FIELD_VOID && dmap->dataDesc[i].fieldType == FIELD_STRING) ) // allow empty strings { +#ifdef MAPBASE + // Activator, etc. support for EHANDLE convert + if ( !Value.Convert( (fieldtype_t)dmap->dataDesc[i].fieldType, this, pActivator, pCaller ) ) + { + bool bBadConversion = true; + + // Attempt to convert to string and back. + // Almost all field types support being converted to a string, and many support being parsed from a string too. + fieldtype_t originalfield = Value.FieldType(); + if (Value.Convert(FIELD_STRING)) + { + bBadConversion = !(Value.Convert((fieldtype_t)dmap->dataDesc[i].fieldType, this, pActivator, pCaller)); + if (!bBadConversion) + { + // Actual support should be added for each field, but if it works, it works. + // Warning against it only matters if you're a programmer and want to add support for each field. + // Only send a warning in dev mode. + DevWarning("!! Had to convert to string and back\n" + "!! Source Field Type: %i, Target Field Type: %i\n", + originalfield, dmap->dataDesc[i].fieldType); + } + } + + if (bBadConversion) + { + Warning( "!! ERROR: bad input/output link:\n!! Unable to convert value \"%s\" from %s (%s) to field type %i\n!! Target Entity: %s (%s), Input: %s\n", + Value.GetDebug(), + ( pCaller != NULL ) ? STRING(pCaller->m_iClassname) : "", + ( pCaller != NULL ) ? STRING(pCaller->m_iName.Get()) : "", + dmap->dataDesc[i].fieldType, + STRING(m_iClassname), GetDebugName(), szInputName ); + return false; + } + } +#else if ( !Value.Convert( (fieldtype_t)dmap->dataDesc[i].fieldType ) ) { // bad conversion Warning( "!! ERROR: bad input/output link:\n!! %s(%s,%s) doesn't match type from %s(%s)\n", STRING(m_iClassname), GetDebugName(), szInputName, ( pCaller != NULL ) ? STRING(pCaller->m_iClassname) : "", - ( pCaller != NULL ) ? STRING(pCaller->m_iName) : "" ); + ( pCaller != NULL ) ? STRING(pCaller->m_iName.Get()) : "" ); return false; } +#endif } } @@ -4424,16 +5000,10 @@ bool CBaseEntity::AcceptInput( const char *szInputName, CBaseEntity *pActivator, if ( m_ScriptScope.IsInitialized() ) { - char szScriptFunctionName[255]; - Q_strcpy( szScriptFunctionName, "Input" ); - Q_strcat( szScriptFunctionName, szInputName, 255 ); - - g_pScriptVM->SetValue( "activator", ( pActivator ) ? ScriptVariant_t( pActivator->GetScriptInstance() ) : SCRIPT_VARIANT_NULL ); - g_pScriptVM->SetValue( "caller", ( pCaller ) ? ScriptVariant_t( pCaller->GetScriptInstance() ) : SCRIPT_VARIANT_NULL ); - - if( CallScriptFunction( szScriptFunctionName, &functionReturn ) ) + ScriptVariant_t functionReturn; + if ( ScriptInputHook( szInputName, pActivator, pCaller, Value, functionReturn ) ) { - bCallInputFunc = functionReturn; + bCallInputFunc = functionReturn.Get(); } } @@ -4441,11 +5011,10 @@ bool CBaseEntity::AcceptInput( const char *szInputName, CBaseEntity *pActivator, { (this->*pfnInput)( data ); } - + if ( m_ScriptScope.IsInitialized() ) { - g_pScriptVM->ClearValue( "activator" ); - g_pScriptVM->ClearValue( "caller" ); + ScriptInputHookClearParams(); } } else if ( dmap->dataDesc[i].flags & FTYPEDESC_KEY ) @@ -4469,10 +5038,92 @@ bool CBaseEntity::AcceptInput( const char *szInputName, CBaseEntity *pActivator, } } - DevMsg( 2, "unhandled input: (%s) -> (%s,%s)\n", szInputName, STRING(m_iClassname), GetDebugName()/*,", from (%s,%s)" STRING(pCaller->m_iClassname), STRING(pCaller->m_iName)*/ ); +#ifdef MAPBASE_VSCRIPT + // Allow VScript to handle unhandled inputs. + if (m_ScriptScope.IsInitialized()) + { + ScriptVariant_t functionReturn; + + if ( ScriptInputHook( szInputName, pActivator, pCaller, Value, functionReturn ) ) + { + if (functionReturn.Get()) + return true; + } + + ScriptInputHookClearParams(); + } +#endif + +#ifdef MAPBASE + CGMsg( 2, CON_GROUP_IO_SYSTEM, "unhandled input: (%s) -> (%s,%s)\n", szInputName, STRING(m_iClassname), GetDebugName() ); +#else + DevMsg( 2, "unhandled input: (%s) -> (%s,%s)\n", szInputName, STRING(m_iClassname), GetDebugName()/*,", from (%s,%s)" STRING(pCaller->m_iClassname), STRING(pCaller->m_iName.Get())*/ ); +#endif return false; } +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CBaseEntity::ScriptInputHook( const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t Value, ScriptVariant_t &functionReturn ) +{ + char szScriptFunctionName[255]; + Q_strcpy( szScriptFunctionName, "Input" ); + Q_strcat( szScriptFunctionName, szInputName, 255 ); + + g_pScriptVM->SetValue( "activator", ( pActivator ) ? ScriptVariant_t( pActivator->GetScriptInstance() ) : SCRIPT_VARIANT_NULL ); + g_pScriptVM->SetValue( "caller", ( pCaller ) ? ScriptVariant_t( pCaller->GetScriptInstance() ) : SCRIPT_VARIANT_NULL ); +#ifdef MAPBASE_VSCRIPT + Value.SetScriptVariant( functionReturn ); + g_pScriptVM->SetValue( "parameter", functionReturn ); +#endif + + bool bHandled = false; + if( CallScriptFunction( szScriptFunctionName, &functionReturn ) ) + { + bHandled = true; + } + + return bHandled; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseEntity::ScriptInputHookClearParams() +{ + g_pScriptVM->ClearValue( "activator" ); + g_pScriptVM->ClearValue( "caller" ); +#ifdef MAPBASE_VSCRIPT + g_pScriptVM->ClearValue( "parameter" ); +#endif +} + +#ifdef MAPBASE_VSCRIPT +bool CBaseEntity::ScriptDeathHook( CTakeDamageInfo *info ) +{ + if (m_ScriptScope.IsInitialized() && g_Hook_OnDeath.CanRunInScope( m_ScriptScope )) + { + HSCRIPT hInfo = g_pScriptVM->RegisterInstance( info ); + + // info + ScriptVariant_t functionReturn; + ScriptVariant_t args[] = { ScriptVariant_t( hInfo ) }; + if ( g_Hook_OnDeath.Call( m_ScriptScope, &functionReturn, args ) && (functionReturn.GetType() == FIELD_BOOLEAN && functionReturn.Get() == false)) + { + // Make this entity cheat death + g_pScriptVM->RemoveInstance( hInfo ); + return false; + } + + g_pScriptVM->RemoveInstance( hInfo ); + } + + return true; +} +#endif + + //----------------------------------------------------------------------------- // Purpose: Input handler for the entity alpha. // Input : nAlpha - Alpha value (0 - 255). @@ -4512,6 +5163,17 @@ void CBaseEntity::InputColor( inputdata_t &inputdata ) void CBaseEntity::InputUse( inputdata_t &inputdata ) { Use( inputdata.pActivator, inputdata.pCaller, (USE_TYPE)inputdata.nOutputID, 0 ); + +#ifdef MAPBASE + IGameEvent *event = gameeventmanager->CreateEvent( "player_use" ); + if ( event ) + { + event->SetInt( "userid", inputdata.pActivator && inputdata.pActivator->IsPlayer() ? + ((CBasePlayer*)inputdata.pActivator)->GetUserID() : 0 ); + event->SetInt( "entity", entindex() ); + gameeventmanager->FireEvent( event ); + } +#endif // MAPBASE } @@ -4630,7 +5292,23 @@ void CBaseEntity::InputKill( inputdata_t &inputdata ) SetOwnerEntity( NULL ); } +#ifdef MAPBASE + m_OnKilled.FireOutput( inputdata.pActivator, this ); +#endif + +#ifdef MAPBASE + // Kick players + if ( IsPlayer() ) + { + engine->ServerCommand( UTIL_VarArgs( "kickid %d CBaseEntity::InputKill()\n", engine->GetPlayerUserId(edict()) ) ); + } + else + { + UTIL_Remove( this ); + } +#else UTIL_Remove( this ); +#endif } void CBaseEntity::InputKillHierarchy( inputdata_t &inputdata ) @@ -4650,6 +5328,13 @@ void CBaseEntity::InputKillHierarchy( inputdata_t &inputdata ) SetOwnerEntity( NULL ); } +#ifdef MAPBASE + m_OnKilled.FireOutput( inputdata.pActivator, this ); + + // Kicking players in InputKillHierarchy does not exist in future Valve games + // if ( IsPlayer() ) +#endif + UTIL_Remove( this ); } @@ -4908,6 +5593,37 @@ void CBaseEntity::NotifySystemEvent( CBaseEntity *pNotify, notify_system_event_t } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CBaseEntity::DispatchInteraction( int interactionType, void *data, CBaseCombatCharacter* sourceEnt ) +{ + if (interactionType <= 0) + return false; + + if (m_ScriptScope.IsInitialized() && g_Hook_HandleInteraction.CanRunInScope( m_ScriptScope )) + { + //HSCRIPT hData = g_pScriptVM->RegisterInstance( data ); + + // interaction, data, sourceEnt + ScriptVariant_t functionReturn; + ScriptVariant_t args[] = { interactionType/*, ScriptVariant_t( hData )*/, ScriptVariant_t( ToHScript( sourceEnt ) ) }; + if ( g_Hook_HandleInteraction.Call( m_ScriptScope, &functionReturn, args ) && (functionReturn.GetType() == FIELD_BOOLEAN)) + { + // Return the interaction here + //g_pScriptVM->RemoveInstance( hData ); + return functionReturn.Get(); + } + + //g_pScriptVM->RemoveInstance( hData ); + } + + return HandleInteraction( interactionType, data, sourceEnt ); +} +#endif + + //----------------------------------------------------------------------------- // Purpose: Holds an entity's previous abs origin and angles at the time of // teleportation. Used for child & constrained entity fixup to prevent @@ -5334,7 +6050,7 @@ void CBaseEntity::PrecacheModelComponents( int nModelIndex ) { char token[256]; const char *pOptions = pEvent->pszOptions(); - nexttoken( token, pOptions, ' ' ); + nexttoken( token, sizeof( token ), pOptions, ' ' ); if ( token[0] ) { PrecacheParticleSystem( token ); @@ -5476,11 +6192,17 @@ HSCRIPT CBaseEntity::ScriptGetModelKeyValues( void ) if ( pModelKeyValues->LoadFromBuffer( pszModelName, pBuffer ) ) { // UNDONE: how does destructor get called on this +#ifdef MAPBASE_VSCRIPT + m_pScriptModelKeyValues = hScript = scriptmanager->CreateScriptKeyValues( g_pScriptVM, pModelKeyValues, true ); // Allow VScript to delete this when the instance is removed. +#else m_pScriptModelKeyValues = new CScriptKeyValues( pModelKeyValues ); +#endif // UNDONE: who calls ReleaseInstance on this??? Does name need to be unique??? +#ifndef MAPBASE_VSCRIPT hScript = g_pScriptVM->RegisterInstance( m_pScriptModelKeyValues ); +#endif /* KeyValues *pParticleEffects = pModelKeyValues->FindKey("Particles"); @@ -5568,6 +6290,59 @@ void ConsoleFireTargets( CBasePlayer *pPlayer, const char *name) FireTargets( name, pPlayer, pPlayer, USE_TOGGLE, 0 ); } +#ifdef MAPBASE +//------------------------------------------------------------------------------ +// Purpose : More concommands needed access to entities, so this has been moved to its own function. +// Input : cmdname - The name of the command. +// &commands - Where the complete autocompletes should be sent to. +// substring - The current search query. (only pool entities that start with this) +// checklen - The number of characters to check. +// Output : A pointer to a cUtlRBTRee containing all of the entities. +//------------------------------------------------------------------------------ +static int AutoCompleteEntities(const char *cmdname, CUtlVector< CUtlString > &commands, CUtlRBTree< CUtlString > &symbols, char *substring, int checklen = 0) +{ + CBaseEntity *pos = NULL; + while ((pos = gEntList.NextEnt(pos)) != NULL) + { + const char *name = pos->GetClassname(); + if (pos->GetEntityName() == NULL_STRING || Q_strnicmp(STRING(pos->GetEntityName()), substring, checklen)) + { + if (Q_strnicmp(pos->GetClassname(), substring, checklen)) + continue; + } + else + name = STRING(pos->GetEntityName()); + + CUtlString sym = name; + int idx = symbols.Find(sym); + if (idx == symbols.InvalidIndex()) + { + symbols.Insert(sym); + } + + // Too many + if (symbols.Count() >= COMMAND_COMPLETION_MAXITEMS) + break; + } + + // Now fill in the results + for (int i = symbols.FirstInorder(); i != symbols.InvalidIndex(); i = symbols.NextInorder(i)) + { + const char *name = symbols[i].String(); + + char buf[512]; + Q_strncpy(buf, name, sizeof(buf)); + Q_strlower(buf); + + CUtlString command; + command = CFmtStr("%s %s", cmdname, buf); + commands.AddToTail(command); + } + + return symbols.Count(); +} +#endif + //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ void DumpScriptScope( CBasePlayer* pPlayer, const char *name) @@ -5649,12 +6424,48 @@ void CC_Ent_Name( const CCommand& args ) } static ConCommand ent_name("ent_name", CC_Ent_Name, 0, FCVAR_CHEAT); + //------------------------------------------------------------------------------ +#ifdef MAPBASE +class CEntTextAutoCompletionFunctor : public ICommandCallback, public ICommandCompletionCallback +{ +public: + virtual void CommandCallback( const CCommand &command ) + { + SetDebugBits(UTIL_GetCommandClient(), command.Arg(1), OVERLAY_TEXT_BIT); + } + + virtual int CommandCompletionCallback( const char *partial, CUtlVector< CUtlString > &commands ) + { + if ( !g_pGameRules ) + { + return 0; + } + + const char *cmdname = "ent_text"; + + char *substring = (char *)partial; + if ( Q_strstr( partial, cmdname ) ) + { + substring = (char *)partial + strlen( cmdname ) + 1; + } + + int checklen = Q_strlen( substring ); + + CUtlRBTree< CUtlString > symbols( 0, 0, UtlStringLessFunc ); + return AutoCompleteEntities(cmdname, commands, symbols, substring, checklen); + } +}; + +static CEntTextAutoCompletionFunctor g_EntTextAutoComplete; +static ConCommand ent_text("ent_text", &g_EntTextAutoComplete, "Displays text debugging information about the given entity(ies) on top of the entity (See Overlay Text)\n\tArguments: {entity_name} / {class_name} / no argument picks what player is looking at ", FCVAR_CHEAT, &g_EntTextAutoComplete); +#else void CC_Ent_Text( const CCommand& args ) { SetDebugBits(UTIL_GetCommandClient(),args[1],OVERLAY_TEXT_BIT); } static ConCommand ent_text("ent_text", CC_Ent_Text, "Displays text debugging information about the given entity(ies) on top of the entity (See Overlay Text)\n\tArguments: {entity_name} / {class_name} / no argument picks what player is looking at ", FCVAR_CHEAT); +#endif //------------------------------------------------------------------------------ void CC_Ent_Script_Dump( const CCommand& args ) @@ -5985,6 +6796,8 @@ void CC_Ent_FireTarget( const CCommand& args ) } static ConCommand firetarget("firetarget", CC_Ent_FireTarget, 0, FCVAR_CHEAT); +#ifndef MAPBASE +#endif class CEntFireAutoCompletionFunctor : public ICommandCallback, public ICommandCompletionCallback { public: @@ -6005,7 +6818,11 @@ public: { const char *target = "", *action = "Use"; variant_t value; +#ifdef MAPBASE + float delay = 0; +#else int delay = 0; +#endif target = STRING( AllocPooledString(command.Arg( 1 ) ) ); @@ -6043,7 +6860,11 @@ public: } if ( command.ArgC() >= 5 ) { +#ifdef MAPBASE + delay = atof( command.Arg( 4 ) ); +#else delay = atoi( command.Arg( 4 ) ); +#endif } g_EventQueue.AddEvent( target, action, value, delay, pPlayer, pPlayer ); @@ -6076,6 +6897,10 @@ public: checklen = Q_strlen( substring ); } +#ifdef MAPBASE + CUtlRBTree< CUtlString > symbols( 0, 0, UtlStringLessFunc ); + return AutoCompleteEntities(cmdname, commands, symbols, substring, checklen); +#else CUtlRBTree< CUtlString > symbols( 0, 0, UtlStringLessFunc ); CBaseEntity *pos = NULL; @@ -6115,6 +6940,7 @@ public: } return symbols.Count(); +#endif } private: int EntFire_AutoCompleteInput( const char *partial, CUtlVector< CUtlString > &commands ) @@ -6143,7 +6969,12 @@ private: Q_strncat( targetEntity, substring, sizeof( targetEntity ), nEntityNameLength ); // Find the target entity by name +#ifdef MAPBASE + CBasePlayer *pPlayer = UTIL_GetCommandClient(); + CBaseEntity *target = gEntList.FindEntityGeneric( NULL, targetEntity, pPlayer, pPlayer, pPlayer ); +#else CBaseEntity *target = gEntList.FindEntityByName( NULL, targetEntity ); +#endif if ( target == NULL ) return 0; @@ -6170,10 +7001,14 @@ private: if ( !( field->flags & FTYPEDESC_INPUT ) ) continue; +#ifndef MAPBASE // What did input variables ever do to you? + // Only want input functions if ( field->flags & FTYPEDESC_SAVE ) continue; +#endif + // See if we've got a partial string for the input name already if ( inputPartial != NULL ) { @@ -6288,6 +7123,153 @@ void CC_Ent_Info( const CCommand& args ) } static ConCommand ent_info("ent_info", CC_Ent_Info, "Usage:\n ent_info \n", FCVAR_CHEAT); +#ifdef MAPBASE +//------------------------------------------------------------------------------ +// Purpose : +// Input : +// Output : +//------------------------------------------------------------------------------ +void CC_Ent_Info_Datatable( const CCommand& args ) +{ + CBasePlayer *pPlayer = ToBasePlayer( UTIL_GetCommandClient() ); + if (!pPlayer) + { + return; + } + + if ( args.ArgC() < 2 ) + { + ClientPrint( pPlayer, HUD_PRINTCONSOLE, "Usage:\n ent_info_datatable \n" ); + } + else + { + // Each element corresponds to a specific field type. + // Hey, if you've got a better idea, be my guest. + static const char *g_FieldStrings[FIELD_TYPECOUNT] = + { + "VOID", + "FLOAT", + "STRING", + "VECTOR", + "QUATERNION", + "INTEGER", + "BOOLEAN", + "SHORT", + "CHARACTER", + "COLOR32", + "EMBEDDED", + "CUSTOM", + + "CLASSPTR", + "EHANDLE", + "EDICT", + + "POSITION_VECTOR", + "TIME", + "TICK", + "MODELNAME", + "SOUNDNAME", + + "INPUT", + "FUNCTION", + "VMATRIX", + "VMATRIX_WORLDSPACE", + "MATRIX3X4_WORLDSPACE", + "INTERVAL", + "MODELINDEX", + "MATERIALINDEX", + + "VECTOR2D", + }; + + // iterate through all the ents printing out their details + CBaseEntity *ent = CreateEntityByName( args[1] ); + + if ( ent ) + { +#define ENT_INFO_BY_HIERARCHY 1 +#ifdef ENT_INFO_BY_HIERARCHY + CUtlVector dmap_namelist; + + CUtlVector< CUtlVector > dmap_fieldlist; + CUtlVector< CUtlVector > dmap_fieldtypelist; + + datamap_t *dmap; + int dmapnum = 0; + for ( dmap = ent->GetDataDescMap(); dmap != NULL; dmap = dmap->baseMap ) + { + dmap_fieldlist.AddToTail(); + dmap_fieldtypelist.AddToTail(); + + // search through all the actions in the data description, printing out details + for ( int i = 0; i < dmap->dataNumFields; i++ ) + { + dmap_fieldlist[dmapnum].AddToTail(dmap->dataDesc[i].fieldName); + dmap_fieldtypelist[dmapnum].AddToTail(dmap->dataDesc[i].fieldType); + } + + dmapnum++; + dmap_namelist.AddToTail(dmap->dataClassName); + } + + char offset[64] = { 0 }; // Needed so garbage isn't spewed at the beginning + for ( int i = 0; i < dmapnum; i++ ) + { + Q_strncat(offset, " ", sizeof(offset)); + + // Header for each class + ClientPrint( pPlayer, HUD_PRINTCONSOLE, UTIL_VarArgs("%s=========| %s |=========\n", offset, dmap_namelist[i]) ); + + Q_strncat(offset, " ", sizeof(offset)); + + int iFieldCount = dmap_fieldlist[i].Count(); + for ( int index = 0; index < iFieldCount; index++ ) + { + int iType = dmap_fieldtypelist[i][index]; + ClientPrint( pPlayer, HUD_PRINTCONSOLE, UTIL_VarArgs("%s%s (%i): %s\n", offset, g_FieldStrings[iType], iType, dmap_fieldlist[i][index]) ); + } + + // Clean up after ourselves + dmap_fieldlist[i].RemoveAll(); + dmap_fieldtypelist[i].RemoveAll(); + } +#else // This sorts by field type instead + CUtlVector fieldlist[FIELD_TYPECOUNT]; + + datamap_t *dmap; + for ( dmap = ent->GetDataDescMap(); dmap != NULL; dmap = dmap->baseMap ) + { + // search through all the actions in the data description, printing out details + for ( int i = 0; i < dmap->dataNumFields; i++ ) + { + fieldlist[dmap->dataDesc[i].fieldType].AddToTail(dmap->dataDesc[i].fieldName); + } + } + + for ( int i = 0; i < FIELD_TYPECOUNT; i++ ) + { + const char *typestring = g_FieldStrings[i]; + for ( int index = 0; index < fieldlist[i].Count(); index++ ) + { + ClientPrint( pPlayer, HUD_PRINTCONSOLE, UTIL_VarArgs(" %s (%i): %s\n", typestring, i, fieldlist[i][index]) ); + } + + // Clean up after ourselves + fieldlist[i].RemoveAll(); + } +#endif + + delete ent; + } + else + { + ClientPrint( pPlayer, HUD_PRINTCONSOLE, UTIL_VarArgs("no such entity %s\n", args[1]) ); + } + } +} +static ConCommand ent_info_datatable("ent_info_datatable", CC_Ent_Info_Datatable, "Usage:\n ent_info_datatable \n", FCVAR_CHEAT); +#endif + //------------------------------------------------------------------------------ // Purpose : @@ -7015,8 +7997,10 @@ void CBaseEntity::ModifyOrAppendCriteria( AI_CriteriaSet& set ) set.AppendCriteria( szGlobalName, UTIL_VarArgs( "%i", iGlobalState ) ); } +#ifndef MAPBASE // We do this later now so contexts can override criteria. I originally didn't want to remove it here in case there would be problems, but I think I have all of the bases covered. // Append anything from I/O or keyvalues pairs AppendContextToCriteria( set ); +#endif if( hl2_episodic.GetBool() ) { @@ -7024,11 +8008,21 @@ void CBaseEntity::ModifyOrAppendCriteria( AI_CriteriaSet& set ) } // Append anything from world I/O/keyvalues with "world" as prefix +#ifdef MAPBASE + CWorld *world = GetWorldEntity(); +#else CWorld *world = dynamic_cast< CWorld * >( CBaseEntity::Instance( engine->PEntityOfEntIndex( 0 ) ) ); +#endif if ( world ) { world->AppendContextToCriteria( set, "world" ); } + +#ifdef MAPBASE + // Append base stuff + set.AppendCriteria("spawnflags", UTIL_VarArgs("%i", GetSpawnFlags())); + set.AppendCriteria("flags", UTIL_VarArgs("%i", GetFlags())); +#endif } //----------------------------------------------------------------------------- @@ -7055,6 +8049,29 @@ void CBaseEntity::AppendContextToCriteria( AI_CriteriaSet& set, const char *pref } } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: +// Input : set - +// "" - +//----------------------------------------------------------------------------- +void CBaseEntity::ReAppendContextCriteria( AI_CriteriaSet& set ) +{ + // Append contexts again. This allows it to override standard criteria, including that of derived classes. + CWorld *world = GetWorldEntity(); + if ( world ) + { + // I didn't know this until recently, but world contexts are actually prefixed by "world". + // I'm changing this here to reduce confusion and allow greater potential. + // (e.g. disabling combine soldier episodic on/off in Episodic binaries with world context "episodic:0", even though that's not happening anymore) + // The old prefixed ones still exist. They're just not appended again. + world->AppendContextToCriteria( set ); + } + + AppendContextToCriteria( set ); +} +#endif + //----------------------------------------------------------------------------- // Purpose: Removes expired concepts from list // Output : @@ -7115,6 +8132,7 @@ const char *CBaseEntity::GetContextValue( int index ) const } return m_ResponseContexts[ index ].m_iszValue.ToCStr(); + } //----------------------------------------------------------------------------- @@ -7155,6 +8173,139 @@ int CBaseEntity::FindContextByName( const char *name ) const return -1; } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: Searches entity for named context string and/or value. +// Intended to be called by entities rather than the conventional response system. +// Input : *name - Context name. +// *value - Context value. (optional) +// Output : bool +//----------------------------------------------------------------------------- +bool CBaseEntity::HasContext( const char *name, const char *value ) const +{ + int c = m_ResponseContexts.Count(); + for ( int i = 0; i < c; i++ ) + { + if ( Matcher_NamesMatch( name, STRING(m_ResponseContexts[i].m_iszName) ) ) + { + if (value == NULL) + return true; + else + return Matcher_Match( value, STRING(m_ResponseContexts[i].m_iszValue) ); + } + } + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: Searches entity for named context string and/or value. +// Intended to be called by entities rather than the conventional response system. +// Input : *name - Context name. +// *value - Context value. (optional) +// Output : bool +//----------------------------------------------------------------------------- +bool CBaseEntity::HasContext( string_t name, string_t value ) const +{ + int c = m_ResponseContexts.Count(); + for ( int i = 0; i < c; i++ ) + { + if ( name == m_ResponseContexts[i].m_iszName ) + { + if (value == NULL_STRING) + return true; + else + return value == m_ResponseContexts[i].m_iszValue; + } + } + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: Searches entity for named context string and/or value. +// Intended to be called by entities rather than the conventional response system. +// Input : *nameandvalue - Context name and value. +// Output : bool +//----------------------------------------------------------------------------- +bool CBaseEntity::HasContext( const char *nameandvalue ) const +{ + char key[ 128 ]; + char value[ 128 ]; + + const char *p = nameandvalue; + while ( p ) + { +#ifdef NEW_RESPONSE_SYSTEM + p = SplitContext( p, key, sizeof( key ), value, sizeof( value ), NULL, nameandvalue ); +#else + p = SplitContext( p, key, sizeof( key ), value, sizeof( value ), NULL ); +#endif + + return HasContext( key, value ); + } + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : index - +// Output : const char +//----------------------------------------------------------------------------- +const char *CBaseEntity::GetContextValue( const char *contextName ) const +{ + int idx = FindContextByName( contextName ); + if ( idx == -1 ) + return ""; + + return m_ResponseContexts[ idx ].m_iszValue.ToCStr(); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +float CBaseEntity::GetContextExpireTime( const char *name ) +{ + int idx = FindContextByName( name ); + if ( idx == -1 ) + return 0.0f; + + return m_ResponseContexts[ idx ].m_fExpirationTime; +} + +//----------------------------------------------------------------------------- +// Purpose: Internal method or removing contexts and can remove multiple contexts in one call +// Input : *contextName - +//----------------------------------------------------------------------------- +void CBaseEntity::RemoveContext( const char *contextName ) +{ + char key[ 128 ]; + char value[ 128 ]; + float duration; + + const char *p = contextName; + while ( p ) + { + duration = 0.0f; +#ifdef NEW_RESPONSE_SYSTEM + p = SplitContext( p, key, sizeof( key ), value, sizeof( value ), &duration, contextName ); +#else + p = SplitContext( p, key, sizeof( key ), value, sizeof( value ), &duration ); +#endif + if ( duration ) + { + duration += gpGlobals->curtime; + } + + int iIndex = FindContextByName( key ); + if ( iIndex != -1 ) + { + m_ResponseContexts.Remove( iIndex ); + } + } +} +#endif + //----------------------------------------------------------------------------- // Purpose: // Input : inputdata - @@ -7294,6 +8445,32 @@ void CBaseEntity::InputCallScriptFunction( inputdata_t& inputdata ) CallScriptFunction( inputdata.value.String(), NULL ); } +#ifdef MAPBASE_VSCRIPT +//--------------------------------------------------------- +// Send the string to the VM as source code and execute it +//--------------------------------------------------------- +void CBaseEntity::InputRunScriptQuotable(inputdata_t& inputdata) +{ + char szQuotableCode[1024]; + if (V_StrSubst( inputdata.value.String(), "''", "\"", szQuotableCode, sizeof( szQuotableCode ), false )) + { + RunScript( szQuotableCode, "InputRunScriptQuotable" ); + } + else + { + RunScript( inputdata.value.String(), "InputRunScriptQuotable" ); + } +} + +//--------------------------------------------------------- +// Clear this entity's script scope +//--------------------------------------------------------- +void CBaseEntity::InputClearScriptScope(inputdata_t& inputdata) +{ + m_ScriptScope.Term(); +} +#endif + // #define VMPROFILE // define to profile vscript calls #ifdef VMPROFILE @@ -7354,6 +8531,36 @@ bool CBaseEntity::CallScriptFunction( const char *pFunctionName, ScriptVariant_t return false; } +#ifdef MAPBASE_VSCRIPT +//----------------------------------------------------------------------------- +// Gets a function handle +//----------------------------------------------------------------------------- +HSCRIPT CBaseEntity::LookupScriptFunction(const char* pFunctionName) +{ + START_VMPROFILE + + if (!m_ScriptScope.IsInitialized()) + { + return NULL; + } + + return m_ScriptScope.LookupFunction(pFunctionName); +} + +//----------------------------------------------------------------------------- +// Calls and releases a function handle (ASSUMES SCRIPT SCOPE AND FUNCTION ARE VALID!) +//----------------------------------------------------------------------------- +bool CBaseEntity::CallScriptFunctionHandle(HSCRIPT hFunc, ScriptVariant_t* pFunctionReturn) +{ + m_ScriptScope.Call(hFunc, pFunctionReturn); + m_ScriptScope.ReleaseFunction(hFunc); + + UPDATE_VMPROFILE + + return true; +} +#endif + //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- @@ -7439,6 +8646,30 @@ void CBaseEntity::ScriptThink( void ) } } +#ifdef MAPBASE_VSCRIPT +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CBaseEntity::ScriptSetThinkFunction( const char *szFunc, float flTime ) +{ + // Empty string stops thinking + if (!szFunc || szFunc[0] == '\0') + { + ScriptStopThinkFunction(); + } + else + { + m_iszScriptThinkFunction = AllocPooledString(szFunc); + flTime = max( 0.0f, flTime ); + SetContextThink( &CBaseEntity::ScriptThink, gpGlobals->curtime + flTime, "ScriptThink" ); + } +} + +void CBaseEntity::ScriptStopThinkFunction() +{ + m_iszScriptThinkFunction = NULL_STRING; + SetContextThink( NULL, TICK_NEVER_THINK, "ScriptThink" ); +} +#endif // MAPBASE_VSCRIPT //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- @@ -7532,6 +8763,665 @@ bool CBaseEntity::RunScript( const char *pScriptText, const char *pDebugFilename return true; } +#ifdef MAPBASE +void CBaseEntity::InputPassUser1( inputdata_t& inputdata ) +{ + m_OutUser1.Set( inputdata.value, inputdata.pActivator, this ); +} + +void CBaseEntity::InputPassUser2( inputdata_t& inputdata ) +{ + m_OutUser2.Set( inputdata.value, inputdata.pActivator, this ); +} + +void CBaseEntity::InputPassUser3( inputdata_t& inputdata ) +{ + m_OutUser3.Set( inputdata.value, inputdata.pActivator, this ); +} + +void CBaseEntity::InputPassUser4( inputdata_t& inputdata ) +{ + m_OutUser4.Set( inputdata.value, inputdata.pActivator, this ); +} + + +void CBaseEntity::InputFireRandomUser( inputdata_t& inputdata ) +{ + switch (RandomInt(1, 4)) + { + case 1: m_OnUser1.FireOutput( inputdata.pActivator, this ); break; + case 2: m_OnUser2.FireOutput( inputdata.pActivator, this ); break; + case 3: m_OnUser3.FireOutput( inputdata.pActivator, this ); break; + case 4: m_OnUser4.FireOutput( inputdata.pActivator, this ); break; + } +} + +void CBaseEntity::InputPassRandomUser( inputdata_t& inputdata ) +{ + switch (RandomInt(1, 4)) + { + case 1: m_OutUser1.Set( inputdata.value, inputdata.pActivator, this ); break; + case 2: m_OutUser2.Set( inputdata.value, inputdata.pActivator, this ); break; + case 3: m_OutUser3.Set( inputdata.value, inputdata.pActivator, this ); break; + case 4: m_OutUser4.Set( inputdata.value, inputdata.pActivator, this ); break; + } +} +#endif + +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: Sets the entity's targetname. +//----------------------------------------------------------------------------- +void CBaseEntity::InputSetEntityName( inputdata_t& inputdata ) +{ + SetName( inputdata.value.StringID() ); +} + +//----------------------------------------------------------------------------- +// Purpose: Sets the generic target field. +//----------------------------------------------------------------------------- +void CBaseEntity::InputSetTarget( inputdata_t& inputdata ) +{ + m_target = inputdata.value.StringID(); + Activate(); +} + +//----------------------------------------------------------------------------- +// Purpose: Sets our owner entity. +//----------------------------------------------------------------------------- +void CBaseEntity::InputSetOwnerEntity( inputdata_t& inputdata ) +{ + SetOwnerEntity(inputdata.value.Entity()); +} + +//----------------------------------------------------------------------------- +// Purpose: Input handler for adding to the entity's health. +// Input : Integer health points to add. +//----------------------------------------------------------------------------- +void CBaseEntity::InputAddHealth( inputdata_t &inputdata ) +{ + TakeHealth( abs(inputdata.value.Int()), DMG_GENERIC ); +} + +//----------------------------------------------------------------------------- +// Purpose: Input handler for removing health from the entity. +// Input : Integer health points to remove. +//----------------------------------------------------------------------------- +void CBaseEntity::InputRemoveHealth( inputdata_t &inputdata ) +{ + TakeDamage( CTakeDamageInfo( this, this, abs(inputdata.value.Int()), DMG_GENERIC ) ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseEntity::InputSetHealth( inputdata_t &inputdata ) +{ + int iNewHealth = inputdata.value.Int(); + int iDelta = abs(GetHealth() - iNewHealth); + if ( iNewHealth > GetHealth() ) + { + TakeHealth( iDelta, DMG_GENERIC ); + } + else if ( iNewHealth < GetHealth() ) + { + TakeDamage( CTakeDamageInfo( this, this, iDelta, DMG_GENERIC ) ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseEntity::InputSetMaxHealth( inputdata_t &inputdata ) +{ + int iNewMaxHealth = inputdata.value.Int(); + SetMaxHealth(iNewMaxHealth); + + if (GetHealth() > iNewMaxHealth) + { + SetHealth(iNewMaxHealth); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Forces the named output to fire. +// In addition to the output itself, parameter may include !activator, !caller, and what to pass. +//----------------------------------------------------------------------------- +void CBaseEntity::InputFireOutput( inputdata_t& inputdata ) +{ + char sParameter[MAX_PATH]; + Q_strncpy( sParameter, inputdata.value.String(), sizeof(sParameter) ); + if ( sParameter ) + { + int iter = 0; + char *data[5] = {sParameter}; + char *sToken = strtok( sParameter, ":" ); + while ( sToken && iter < 5 ) + { + data[iter] = sToken; + iter++; + sToken = strtok( NULL, ":" ); + } + + //DevMsg("data[0]: %s\ndata[1]: %s\ndata[2]: %s\ndata[3]: %s\ndata[4]: %s\n", data[0], data[1], data[2], data[3], data[4]); + + // Format: :::: + // + // data[0] = Output Name + // data[1] = Activator + // data[2] = Caller + // data[3] = Parameter + // data[4] = Delay + // + CBaseEntity *pActivator = inputdata.pActivator; + if (data[1]) + pActivator = gEntList.FindEntityByName(NULL, data[1], this, inputdata.pActivator, inputdata.pCaller); + + CBaseEntity *pCaller = this; + if (data[2]) + pCaller = gEntList.FindEntityByName(NULL, data[2], this, inputdata.pActivator, inputdata.pCaller); + + variant_t parameter; + if (data[3]) + { + parameter.SetString(MAKE_STRING(data[3])); + } + + float flDelay = 0.0f; + if (data[4]) + flDelay = atof(data[4]); + + FireNamedOutput(data[0], parameter, pActivator, pCaller, flDelay); + //Msg("Output Name: %s, Activator: %s, Caller: %s, Data: %s, Delay: %f\n", data[0], pActivator->GetDebugName(), pCaller->GetDebugName(), parameter.String(), flDelay); + } + else + { + Warning("FireOutput input fired with bad parameter. Format: ::::\n"); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Removes all outputs of the specified name. +//----------------------------------------------------------------------------- +void CBaseEntity::InputRemoveOutput( inputdata_t& inputdata ) +{ + const char *szOutput = inputdata.value.String(); + datamap_t *dmap = GetDataDescMap(); + while ( dmap ) + { + int fields = dmap->dataNumFields; + for ( int i = 0; i < fields; i++ ) + { + typedescription_t *dataDesc = &dmap->dataDesc[i]; + if ( ( dataDesc->fieldType == FIELD_CUSTOM ) && ( dataDesc->flags & FTYPEDESC_OUTPUT ) ) + { + // If our names match, remove + if (Matcher_NamesMatch(szOutput, dataDesc->externalName)) + { + CBaseEntityOutput *pOutput = (CBaseEntityOutput *)((int)this + (int)dataDesc->fieldOffset[0]); + pOutput->DeleteAllElements(); + } + } + } + + dmap = dmap->baseMap; + } +} + +// Find a way to implement this +/* +//------------------------------------------------------------------------------ +// Purpose: Cancels all I/O events of a specific output. +//------------------------------------------------------------------------------ +void CBaseEntity::InputCancelOutput( inputdata_t &inputdata ) +{ + +} +*/ + +//----------------------------------------------------------------------------- +// Purpose: Replaces all outputs of the specified name. +//----------------------------------------------------------------------------- +void CBaseEntity::InputReplaceOutput( inputdata_t& inputdata ) +{ + char sParameter[128]; + Q_strncpy( sParameter, inputdata.value.String(), sizeof(sParameter) ); + if (!sParameter) + return; + + int iter = 0; + char *data[2]; + char *sToken = strtok( sParameter, ": " ); + while ( sToken && iter < 2 ) + { + data[iter] = sToken; + iter++; + sToken = strtok( NULL, ": " ); + } + + const char *szOutput = data[0]; + const char *szNewOutput = data[1]; + if (!szOutput || !szNewOutput) + { + Warning("ReplaceOutput input fired with bad parameter. Format: :\n"); + return; + } + + int iOutputsReplaced = 0; + datamap_t *dmap = GetDataDescMap(); + while ( dmap ) + { + int fields = dmap->dataNumFields; + for ( int i = 0; i < fields; i++ ) + { + typedescription_t *dataDesc = &dmap->dataDesc[i]; + if ( ( dataDesc->fieldType == FIELD_CUSTOM ) && ( dataDesc->flags & FTYPEDESC_OUTPUT ) ) + { + // If our names match, replace + if (Matcher_NamesMatch(szOutput, dataDesc->externalName)) + { + CBaseEntityOutput *pOutput = (CBaseEntityOutput *)((int)this + (int)dataDesc->fieldOffset[0]); + const char *szTarget; + const char *szInputName; + const char *szParam; + float flDelay; + int iNumTimes; + char szData[256]; + for ( CEventAction *ev = pOutput->GetActionList(); ev != NULL; ev = ev->m_pNext ) + { + // This is the only way I think we could do this. Accomplishes the job more or less anyway + szTarget = STRING(ev->m_iTarget); + szInputName = STRING(ev->m_iTargetInput); + szParam = ev->m_iParameter == NULL_STRING ? "" : STRING(ev->m_iParameter); + flDelay = ev->m_flDelay; + iNumTimes = ev->m_nTimesToFire; + Q_snprintf(szData, sizeof(szData), "%s,%s,%s,%f,%i", szTarget, szInputName, szParam, flDelay, iNumTimes); + + KeyValue(szNewOutput, szData); + + DevMsg("ReplaceOutput: %s %s\n", szNewOutput, szData); + + iOutputsReplaced++; + } + pOutput->DeleteAllElements(); + } + } + } + + dmap = dmap->baseMap; + } + + if (iOutputsReplaced == 0) + { + Warning("ReplaceOutput unable to find %s on %s\n", szOutput, GetDebugName()); + } + else + { + DevMsg("Replaced %i instances of %s with %s on %s\n", iOutputsReplaced, szOutput, szNewOutput, GetDebugName()); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Forces the named input to fire...what? +// Inputception...or is it just "Inception"? Whatever. +// True inception would be using this input to fire AcceptInput. +// (it would probably crash, I haven't tested it) +// +// In addition to the input itself, parameter may include !activator, !caller, and what to pass. +//----------------------------------------------------------------------------- +void CBaseEntity::InputAcceptInput( inputdata_t& inputdata ) +{ + char sParameter[MAX_PATH]; + Q_strncpy( sParameter, inputdata.value.String(), sizeof(sParameter) ); + if ( sParameter ) + { + int iter = 0; + char *data[5] = {sParameter}; + char *sToken = strtok( sParameter, ":" ); + while ( sToken && iter < 5 ) + { + data[iter] = sToken; + iter++; + sToken = strtok( NULL, ":" ); + } + + //DevMsg("data[0]: %s\ndata[1]: %s\ndata[2]: %s\ndata[3]: %s\ndata[4]: %s\n", data[0], data[1], data[2], data[3], data[4]); + + // Format: :::: + // + // data[0] = Input Name + // data[1] = Parameter + // data[2] = Activator + // data[3] = Caller + // data[4] = Output ID + // + variant_t parameter; + if (data[1]) + { + parameter.SetString(MAKE_STRING(data[1])); + } + + CBaseEntity *pActivator = inputdata.pActivator; + if (data[2]) + pActivator = gEntList.FindEntityByName(NULL, data[2], this, inputdata.pActivator, inputdata.pCaller); + + CBaseEntity *pCaller = this; + if (data[3]) + pCaller = gEntList.FindEntityByName(NULL, data[3], this, inputdata.pActivator, inputdata.pCaller); + + int iOutputID = -1; + if (data[4]) + iOutputID = atoi(data[4]); + + AcceptInput(data[0], pActivator, pCaller, parameter, iOutputID); + Msg("Input Name: %s, Activator: %s, Caller: %s, Data: %s, Output ID: %i\n", data[0], pActivator ? pActivator->GetDebugName() : "None", pCaller ? pCaller->GetDebugName() : "None", parameter.String(), iOutputID); + } + else + { + Warning("AcceptInput input fired with bad parameter. Format: ::::\n"); + } +} + +//------------------------------------------------------------------------------ +// Purpose: Cancels any I/O events in the queue that were fired by this entity. +//------------------------------------------------------------------------------ +void CBaseEntity::InputCancelPending( inputdata_t &inputdata ) +{ + g_EventQueue.CancelEvents( this ); +} + +//----------------------------------------------------------------------------- +// Purpose: Frees all of our children, entities parented to this entity. +//----------------------------------------------------------------------------- +void CBaseEntity::InputFreeChildren( inputdata_t& inputdata ) +{ + UnlinkAllChildren( this ); +} + +//----------------------------------------------------------------------------- +// Purpose: Sets our origin. +//----------------------------------------------------------------------------- +void CBaseEntity::InputSetLocalOrigin( inputdata_t& inputdata ) +{ + Vector vec; + inputdata.value.Vector3D(vec); + SetLocalOrigin(vec); +} + +//----------------------------------------------------------------------------- +// Purpose: Sets our angles. +//----------------------------------------------------------------------------- +void CBaseEntity::InputSetLocalAngles( inputdata_t& inputdata ) +{ + QAngle ang; + inputdata.value.Angle3D(ang); + SetLocalAngles(ang); +} + +//----------------------------------------------------------------------------- +// Purpose: Sets our origin. +//----------------------------------------------------------------------------- +void CBaseEntity::InputSetAbsOrigin( inputdata_t& inputdata ) +{ + Vector vec; + inputdata.value.Vector3D(vec); + SetAbsOrigin(vec); +} + +//----------------------------------------------------------------------------- +// Purpose: Sets our angles. +//----------------------------------------------------------------------------- +void CBaseEntity::InputSetAbsAngles( inputdata_t& inputdata ) +{ + QAngle ang; + inputdata.value.Angle3D(ang); + SetAbsAngles(ang); +} + +//----------------------------------------------------------------------------- +// Purpose: Sets our velocity. +//----------------------------------------------------------------------------- +void CBaseEntity::InputSetLocalVelocity( inputdata_t& inputdata ) +{ + Vector vec; + inputdata.value.Vector3D(vec); + SetLocalVelocity(vec); +} + +//----------------------------------------------------------------------------- +// Purpose: Sets our angular velocity. +//----------------------------------------------------------------------------- +void CBaseEntity::InputSetLocalAngularVelocity( inputdata_t& inputdata ) +{ + Vector vec; + inputdata.value.Vector3D(vec); + SetLocalAngularVelocity(QAngle(vec.x, vec.y, vec.z)); +} + +//----------------------------------------------------------------------------- +// Purpose: Adds spawn flags. +//----------------------------------------------------------------------------- +void CBaseEntity::InputAddSpawnFlags( inputdata_t& inputdata ) +{ + AddSpawnFlags(inputdata.value.Int()); +} + +//----------------------------------------------------------------------------- +// Purpose: Removes spawn flags. +//----------------------------------------------------------------------------- +void CBaseEntity::InputRemoveSpawnFlags( inputdata_t& inputdata ) +{ + RemoveSpawnFlags(inputdata.value.Int()); +} + +//----------------------------------------------------------------------------- +// Purpose: Sets our render mode. +//----------------------------------------------------------------------------- +void CBaseEntity::InputSetRenderMode( inputdata_t& inputdata ) +{ + SetRenderMode((RenderMode_t)inputdata.value.Int()); +} + +//----------------------------------------------------------------------------- +// Purpose: Sets our render FX. +//----------------------------------------------------------------------------- +void CBaseEntity::InputSetRenderFX( inputdata_t& inputdata ) +{ + m_nRenderFX = inputdata.value.Int(); +} + +//----------------------------------------------------------------------------- +// Purpose: Sets our view hide flags. +//----------------------------------------------------------------------------- +void CBaseEntity::InputSetViewHideFlags( inputdata_t& inputdata ) +{ + m_iViewHideFlags = inputdata.value.Int(); +} + +//----------------------------------------------------------------------------- +// Purpose: Adds effects. +//----------------------------------------------------------------------------- +void CBaseEntity::InputAddEffects( inputdata_t& inputdata ) +{ + AddEffects(inputdata.value.Int()); +} + +//----------------------------------------------------------------------------- +// Purpose: Removes effects. +//----------------------------------------------------------------------------- +void CBaseEntity::InputRemoveEffects( inputdata_t& inputdata ) +{ + RemoveEffects(inputdata.value.Int()); +} + +//----------------------------------------------------------------------------- +// Purpose: Shortcut for removing nodraw. +//----------------------------------------------------------------------------- +void CBaseEntity::InputDrawEntity( inputdata_t& inputdata ) +{ + RemoveEffects(EF_NODRAW); +} + +//----------------------------------------------------------------------------- +// Purpose: Shortcut to adding nodraw. +//----------------------------------------------------------------------------- +void CBaseEntity::InputUndrawEntity( inputdata_t& inputdata ) +{ + AddEffects(EF_NODRAW); +} + +//----------------------------------------------------------------------------- +// Purpose: Inspired by the Portal 2 input of the same name. +//----------------------------------------------------------------------------- +void CBaseEntity::InputEnableReceivingFlashlight( inputdata_t& inputdata ) +{ + m_bDisableFlashlight = false; +} + +//----------------------------------------------------------------------------- +// Purpose: Inspired by the Portal 2 input of the same name. +//----------------------------------------------------------------------------- +void CBaseEntity::InputDisableReceivingFlashlight( inputdata_t& inputdata ) +{ + m_bDisableFlashlight = true; +} + +//----------------------------------------------------------------------------- +// Purpose: Adds eflags. +//----------------------------------------------------------------------------- +void CBaseEntity::InputAddEFlags( inputdata_t& inputdata ) +{ + AddEFlags(inputdata.value.Int()); +} + +//----------------------------------------------------------------------------- +// Purpose: Removes eflags. +//----------------------------------------------------------------------------- +void CBaseEntity::InputRemoveEFlags( inputdata_t& inputdata ) +{ + RemoveEFlags(inputdata.value.Int()); +} + +//----------------------------------------------------------------------------- +// Purpose: Adds solid flags. +//----------------------------------------------------------------------------- +void CBaseEntity::InputAddSolidFlags( inputdata_t& inputdata ) +{ + AddSolidFlags(inputdata.value.Int()); +} + +//----------------------------------------------------------------------------- +// Purpose: Removes solid flags. +//----------------------------------------------------------------------------- +void CBaseEntity::InputRemoveSolidFlags( inputdata_t& inputdata ) +{ + RemoveSolidFlags(inputdata.value.Int()); +} + +//----------------------------------------------------------------------------- +// Purpose: Sets the movetype. +//----------------------------------------------------------------------------- +void CBaseEntity::InputSetMoveType( inputdata_t& inputdata ) +{ + SetMoveType((MoveType_t)inputdata.value.Int()); +} + +//----------------------------------------------------------------------------- +// Purpose: Sets the collision group. +//----------------------------------------------------------------------------- +void CBaseEntity::InputSetCollisionGroup( inputdata_t& inputdata ) +{ + SetCollisionGroup(inputdata.value.Int()); +} + +//----------------------------------------------------------------------------- +// Purpose: Touch touch :) +//----------------------------------------------------------------------------- +void CBaseEntity::InputTouch( inputdata_t& inputdata ) +{ + if (inputdata.value.Entity()) + Touch( inputdata.value.Entity() ); + else + Warning( "%s InputTouch: Can't touch null entity", GetDebugName() ); +} + +//----------------------------------------------------------------------------- +// Purpose: Passes KilledNPC to our possibly more capable parents. +//----------------------------------------------------------------------------- +void CBaseEntity::InputKilledNPC( inputdata_t &inputdata ) +{ + // Don't get stuck in an endless loop + if (inputdata.value.Int() > 16) + return; + else + inputdata.value.SetInt(inputdata.value.Int() + 1); + + if (GetOwnerEntity()) + { + GetOwnerEntity()->AcceptInput("KilledNPC", inputdata.pActivator, inputdata.pCaller, inputdata.value, inputdata.nOutputID); + } + else if (HasPhysicsAttacker(4.0f)) + { + HasPhysicsAttacker(4.0f)->AcceptInput("KilledNPC", inputdata.pActivator, inputdata.pCaller, inputdata.value, inputdata.nOutputID); + } + else if (GetMoveParent()) + { + GetMoveParent()->AcceptInput("KilledNPC", inputdata.pActivator, inputdata.pCaller, inputdata.value, inputdata.nOutputID); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Remove if not visible by any players +//----------------------------------------------------------------------------- +void CBaseEntity::InputKillIfNotVisible( inputdata_t& inputdata ) +{ +#ifdef MAPBASE_MP + // Go through each client and check if we're in their viewcone. + // If we're in someone's viewcone, return immediately. + for ( int i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBasePlayer *pPlayer = UTIL_PlayerByIndex( i ); + if ( pPlayer && pPlayer->FInViewCone( this ) ) + return; + } +#else + CBasePlayer *pPlayer = UTIL_GetLocalPlayer(); + if ( !pPlayer || !pPlayer->FInViewCone( this ) ) +#endif + { + m_OnKilled.FireOutput(inputdata.pActivator, this); + UTIL_Remove(this); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Remove when not visible by any players +//----------------------------------------------------------------------------- +void CBaseEntity::InputKillWhenNotVisible( inputdata_t& inputdata ) +{ + SetContextThink( &CBaseEntity::SUB_RemoveWhenNotVisible, gpGlobals->curtime + inputdata.value.Float(), "SUB_RemoveWhenNotVisible" ); + //SetRenderColorA( 255 ); + //m_nRenderMode = kRenderNormal; +} + +//----------------------------------------------------------------------------- +// Purpose: Stop thinking +//----------------------------------------------------------------------------- +void CBaseEntity::InputSetThinkNull( inputdata_t& inputdata ) +{ + const char *szContext = inputdata.value.String(); + if (szContext && szContext[0] != '\0') + { + SetContextThink( NULL, TICK_NEVER_THINK, szContext ); + } + else + { + SetThink( NULL ); + SetNextThink( TICK_NEVER_THINK ); + } +} +#endif + //----------------------------------------------------------------------------- // Purpose: // Input : *contextName - @@ -7540,30 +9430,71 @@ void CBaseEntity::AddContext( const char *contextName ) { char key[ 128 ]; char value[ 128 ]; - float duration; + float duration = 0.0f; const char *p = contextName; while ( p ) { duration = 0.0f; +#ifdef NEW_RESPONSE_SYSTEM + p = SplitContext( p, key, sizeof( key ), value, sizeof( value ), &duration, contextName ); +#else p = SplitContext( p, key, sizeof( key ), value, sizeof( value ), &duration ); +#endif if ( duration ) { duration += gpGlobals->curtime; } - int iIndex = FindContextByName( key ); - if ( iIndex != -1 ) - { - // Set the existing context to the new value - m_ResponseContexts[iIndex].m_iszValue = AllocPooledString( value ); - m_ResponseContexts[iIndex].m_fExpirationTime = duration; - continue; - } + AddContext( key, value, duration ); + } +} +void CBaseEntity::AddContext( const char *name, const char *value, float duration ) +{ + int iIndex = FindContextByName( name ); + if ( iIndex != -1 ) + { + // Set the existing context to the new value + +#ifdef NEW_RESPONSE_SYSTEM + char buf[64]; + if ( RR::CApplyContextOperator::FindOperator( value )->Apply( + m_ResponseContexts[iIndex].m_iszValue.ToCStr(), value, buf, sizeof(buf) ) ) + { + m_ResponseContexts[iIndex].m_iszValue = AllocPooledString( buf ); + } + else + { + Warning( "RR: could not apply operator %s to prior value %s\n", + value, m_ResponseContexts[iIndex].m_iszValue.ToCStr() ); + m_ResponseContexts[iIndex].m_iszValue = AllocPooledString( value ); + } +#else + m_ResponseContexts[iIndex].m_iszValue = AllocPooledString( value ); +#endif + m_ResponseContexts[iIndex].m_fExpirationTime = duration; + } + else + { ResponseContext_t newContext; - newContext.m_iszName = AllocPooledString( key ); + newContext.m_iszName = AllocPooledString( name ); + +#ifdef NEW_RESPONSE_SYSTEM + char buf[64]; + if ( RR::CApplyContextOperator::FindOperator( value )->Apply( + NULL, value, buf, sizeof(buf) ) ) + { + newContext.m_iszValue = AllocPooledString( buf ); + } + else + { + newContext.m_iszValue = AllocPooledString( value ); + } +#else newContext.m_iszValue = AllocPooledString( value ); +#endif + newContext.m_fExpirationTime = duration; m_ResponseContexts.AddToTail( newContext ); @@ -7653,12 +9584,78 @@ void CBaseEntity::InputAddOutput( inputdata_t &inputdata ) } } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: +// Input : &inputdata - +//----------------------------------------------------------------------------- +void CBaseEntity::InputChangeVariable( inputdata_t &inputdata ) +{ + const char *szKeyName = NULL; + const char *szValue = NULL; + + char sOutputName[MAX_PATH]; + Q_strncpy( sOutputName, inputdata.value.String(), sizeof(sOutputName) ); + char *sChar = strchr( sOutputName, ' ' ); + if ( sChar ) + { + *sChar = '\0'; + // Now replace all the :'s in the string with ,'s. + // Has to be done this way because Hammer doesn't allow ,'s inside parameters. + char *sColon = strchr( sChar+1, ':' ); + while ( sColon ) + { + *sColon = ','; + sColon = strchr( sChar+1, ':' ); + } + + szKeyName = sOutputName; + szValue = sChar + 1; + } + else + { + Warning("ChangeVariable input fired with bad string. Format: ,,,,\n"); + return; + } + + if (szKeyName == NULL) + return; + + for ( datamap_t *dmap = GetDataDescMap(); dmap != NULL; dmap = dmap->baseMap ) + { + // search through all the readable fields in the data description, looking for a match + for ( int i = 0; i < dmap->dataNumFields; i++ ) + { + if ( dmap->dataDesc[i].flags & (FTYPEDESC_SAVE | FTYPEDESC_KEY) ) + { + if ( Matcher_NamesMatch(szKeyName, dmap->dataDesc[i].fieldName) ) + { + // Copied from ::ParseKeyvalue...or technically logic_datadesc_accessor... + typedescription_t *pField = &dmap->dataDesc[i]; + char *data = Datadesc_SetFieldString( szValue, this, pField, NULL ); + + if (!data) + { + Warning( "%s cannot set field of type %i.\n", GetDebugName(), dmap->dataDesc[i].fieldType ); + } + } + } + } + } +} +#endif + //----------------------------------------------------------------------------- // Purpose: // Input : *conceptName - //----------------------------------------------------------------------------- void CBaseEntity::DispatchResponse( const char *conceptName ) { +#ifdef NEW_RESPONSE_SYSTEM + #undef IResponseSystem + using namespace ResponseRules; +#endif + IResponseSystem *rs = GetResponseSystem(); if ( !rs ) return; @@ -7674,6 +9671,10 @@ void CBaseEntity::DispatchResponse( const char *conceptName ) if( pPlayer ) pPlayer->ModifyOrAppendPlayerCriteria( set ); +#ifdef MAPBASE + ReAppendContextCriteria( set ); +#endif + // Now that we have a criteria set, ask for a suitable response AI_Response result; bool found = rs->FindBestResponse( set, result ); @@ -7682,6 +9683,94 @@ void CBaseEntity::DispatchResponse( const char *conceptName ) // Handle the response here... const char *szResponse = result.GetResponsePtr(); +#ifdef NEW_RESPONSE_SYSTEM + switch (result.GetType()) + { + case ResponseRules::RESPONSE_SPEAK: + { + EmitSound( szResponse ); + } + break; + case ResponseRules::RESPONSE_SENTENCE: + { + int sentenceIndex = SENTENCEG_Lookup( szResponse ); + if (sentenceIndex == -1) + { + // sentence not found + break; + } + + // FIXME: Get pitch from npc? + CPASAttenuationFilter filter(this); + CBaseEntity::EmitSentenceByIndex(filter, entindex(), CHAN_VOICE, sentenceIndex, 1, result.GetSoundLevel(), 0, PITCH_NORM); + } + break; + case ResponseRules::RESPONSE_SCENE: + { + // Try to fire scene w/o an actor + InstancedScriptedScene( NULL, szResponse ); + } + break; + case ResponseRules::RESPONSE_PRINT: + { + + } + break; + case ResponseRules::RESPONSE_ENTITYIO: + { + // Need to copy the string for tokenization + char szTokenResponse[ ResponseRules::CRR_Response::MAX_RESPONSE_NAME ]; + V_strncpy( szTokenResponse, szResponse, sizeof( szTokenResponse ) ); + CAI_Expresser::FireEntIOFromResponse( szTokenResponse, this ); + break; + } +#ifdef MAPBASE_VSCRIPT + case ResponseRules::RESPONSE_VSCRIPT: + { + CAI_Expresser::RunScriptResponse( this, szResponse, &set, false ); + break; + } + case ResponseRules::RESPONSE_VSCRIPT_FILE: + { + CAI_Expresser::RunScriptResponse( this, szResponse, &set, true ); + break; + } +#endif + default: + // Don't know how to handle .vcds!!! + break; + } +#else +#ifdef MAPBASE + if (szResponse[0] == '$') + { + const char *context = szResponse + 1; + const char *replace = GetContextValue(context); + + if (replace) + { + DevMsg("Replacing %s with %s...\n", szResponse, replace); + Q_strncpy( szResponse, replace, sizeof( szResponse )); + + // Precache it now because it may not have been precached before + switch ( result.GetType() ) + { + case RESPONSE_SPEAK: + { + PrecacheScriptSound( szResponse ); + } + break; + + case RESPONSE_SCENE: + { + // TODO: Gender handling? + PrecacheInstancedScene( szResponse ); + } + break; + } + } + } +#endif switch ( result.GetType() ) { case RESPONSE_SPEAK: @@ -7691,6 +9780,13 @@ void CBaseEntity::DispatchResponse( const char *conceptName ) case RESPONSE_SENTENCE: { int sentenceIndex = SENTENCEG_Lookup( szResponse ); +#ifdef MAPBASE + if (response[0] != '!') + { + SENTENCEG_PlayRndSz( edict(), szResponse, 1, result.GetSoundLevel(), 0, PITCH_NORM ); + break; + } +#endif if( sentenceIndex == -1 ) { // sentence not found @@ -7704,8 +9800,29 @@ void CBaseEntity::DispatchResponse( const char *conceptName ) break; case RESPONSE_SCENE: +#ifdef MAPBASE + // Most flexing actors that use scenes override DispatchResponse via CAI_Expresser in ai_speech. + // So, in order for non-actors to use scenes by themselves, they actually don't really use them at all. + // Most scenes that would be used as responses only have one sound, so we take the first sound in a scene and emit it manually. + // + // Of course, env_speaker uses scenes without using itself as an actor, but that overrides DispatchResponse in Mapbase + // with the original code intact. Hopefully no other entity uses this like that... + + //if (!ClassMatches("env_speaker")) + { + // Expand gender string + GenderExpandString( response, response, sizeof( response ) ); + + // Trust that it's been precached + const char *pszSound = GetFirstSoundInScene(response); + EmitSound(pszSound); + } + //else + // InstancedScriptedScene(NULL, response); +#else // Try to fire scene w/o an actor InstancedScriptedScene( NULL, szResponse ); +#endif break; case RESPONSE_PRINT: @@ -7714,6 +9831,7 @@ void CBaseEntity::DispatchResponse( const char *conceptName ) // Don't know how to handle .vcds!!! break; } +#endif } //----------------------------------------------------------------------------- @@ -7735,6 +9853,10 @@ void CBaseEntity::DumpResponseCriteria( void ) pPlayer->ModifyOrAppendPlayerCriteria( set ); } +#ifdef MAPBASE + ReAppendContextCriteria( set ); +#endif + // Now dump it all to console set.Describe(); } @@ -8036,6 +10158,7 @@ void CBaseEntity::RemoveRecipientsIfNotCloseCaptioning( CRecipientFilter& filter } } +#ifndef MAPBASE // Moved to SoundEmitterSystem.cpp //----------------------------------------------------------------------------- // Purpose: Wrapper to emit a sentence and also a close caption token for the sentence as appropriate. // Input : filter - @@ -8058,6 +10181,7 @@ void CBaseEntity::EmitSentenceByIndex( IRecipientFilter& filter, int iEntIndex, enginesound->EmitSentenceByIndex( filter, iEntIndex, iChannel, iSentenceIndex, flVolume, iSoundlevel, iFlags, iPitch, 0, pOrigin, pDirection, &dummy, bUpdatePositions, soundtime ); } +#endif void CBaseEntity::SetRefEHandle( const CBaseHandle &handle ) @@ -8239,6 +10363,11 @@ void CBaseEntity::SUB_FadeOut( void ) if ( m_clrRender->a == 0 ) { +#ifdef MAPBASE + // This was meant for KillWhenNotVisible before it used its own function, + // but there's not really any harm for keeping this here. + m_OnKilled.FireOutput(this, this); +#endif UTIL_Remove(this); } else @@ -8247,6 +10376,33 @@ void CBaseEntity::SUB_FadeOut( void ) } } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: For KillWhenNotVisible, based off of SUB_FadeOut +//----------------------------------------------------------------------------- +void CBaseEntity::SUB_RemoveWhenNotVisible( void ) +{ + if ( SUB_AllowedToFade() == false ) + { + SetNextThink( gpGlobals->curtime + 1, "SUB_RemoveWhenNotVisible" ); + SetRenderColorA( 255 ); + return; + } + + SetRenderColorA( m_clrRender->a - 1 ); + + if ( m_clrRender->a == 0 ) + { + m_OnKilled.FireOutput(this, this); + UTIL_Remove(this); + } + else + { + SetNextThink( gpGlobals->curtime, "SUB_RemoveWhenNotVisible" ); + } +} +#endif + //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- @@ -8257,7 +10413,7 @@ HSCRIPT CBaseEntity::GetScriptInstance() if ( m_iszScriptId == NULL_STRING ) { char *szName = (char *)stackalloc( 1024 ); - g_pScriptVM->GenerateUniqueKey( ( m_iName != NULL_STRING ) ? STRING(GetEntityName()) : GetClassname(), szName, 1024 ); + g_pScriptVM->GenerateUniqueKey( ( m_iName.Get() != NULL_STRING ) ? STRING(GetEntityName()) : GetClassname(), szName, 1024 ); m_iszScriptId = AllocPooledString( szName ); } @@ -8316,6 +10472,13 @@ void CBaseEntity::RunVScripts() return; } +#ifdef MAPBASE_VSCRIPT + if (g_pScriptVM == NULL) + { + return; + } +#endif + ValidateScriptScope(); // All functions we want to have call chained instead of overwritten @@ -8354,7 +10517,11 @@ void CBaseEntity::RunVScripts() for( int i = 0 ; i < szScripts.Count() ; i++ ) { - Log_Msg( LOG_VScript, "%s executing script: %s\n", GetDebugName(), szScripts[i] ); +#ifdef MAPBASE + CGMsg( 0, CON_GROUP_VSCRIPT, "%s executing script: %s\n", GetDebugName(), szScripts[i] ); +#else + Log( "%s executing script: %s\n", GetDebugName(), szScripts[i]); +#endif RunScriptFile( szScripts[i], IsWorld() ); @@ -8392,6 +10559,13 @@ void CBaseEntity::RunPrecacheScripts( void ) { return; } + +#ifdef MAPBASE_VSCRIPT + if (g_pScriptVM == NULL) + { + return; + } +#endif HSCRIPT hScriptPrecache = m_ScriptScope.LookupFunction( "DispatchPrecache" ); if ( hScriptPrecache ) @@ -8408,6 +10582,13 @@ void CBaseEntity::RunOnPostSpawnScripts( void ) return; } +#ifdef MAPBASE_VSCRIPT + if (g_pScriptVM == NULL) + { + return; + } +#endif + HSCRIPT hFuncConnect = g_pScriptVM->LookupFunction("ConnectOutputs"); if ( hFuncConnect ) { @@ -8424,8 +10605,9 @@ void CBaseEntity::RunOnPostSpawnScripts( void ) m_ScriptScope.ReleaseFunction( hFuncDisp ); } - } +} +#ifndef MAPBASE_VSCRIPT // This is shared now HSCRIPT CBaseEntity::GetScriptOwnerEntity() { return ToHScript( GetOwnerEntity() ); @@ -8435,6 +10617,7 @@ void CBaseEntity::SetScriptOwnerEntity( HSCRIPT pOwner ) { SetOwnerEntity( ToEnt( pOwner ) ); } +#endif inline bool AnyPlayersInHierarchy_R( CBaseEntity *pEnt ) { @@ -8508,7 +10691,228 @@ void CBaseEntity::SetCollisionBoundsFromModel() } } +#ifdef MAPBASE_VSCRIPT +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +#ifndef VSCRIPT_PRIORITIZE_TF2_SYNTAX +int CBaseEntity::ScriptTakeDamage( HSCRIPT pInfo ) +{ + CTakeDamageInfo *info = HScriptToClass< CTakeDamageInfo >( pInfo ); + if ( info ) + { + return OnTakeDamage( *info ); + } + + return 0; +} +#endif + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CBaseEntity::ScriptFireBullets( HSCRIPT pInfo ) +{ + FireBulletsInfo_t *info = HScriptToClass< FireBulletsInfo_t >( pInfo ); + if ( info ) + { + FireBullets( *info ); + } +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CBaseEntity::ScriptAddContext( const char *name, const char *value, float duration ) +{ + AddContext( name, value, duration ); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +const char *CBaseEntity::ScriptGetContext( const char *name ) +{ + return GetContextValue( name ); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +HSCRIPT CBaseEntity::ScriptGetContextIndex( int index ) +{ + if (index >= m_ResponseContexts.Count()) + return NULL; + + ScriptVariant_t varTable; + g_pScriptVM->CreateTable( varTable ); + + g_pScriptVM->SetValue( varTable, "name", STRING( m_ResponseContexts[index].m_iszName ) ); + g_pScriptVM->SetValue( varTable, "value", STRING( m_ResponseContexts[index].m_iszValue ) ); + g_pScriptVM->SetValue( varTable, "expiration_time", m_ResponseContexts[index].m_fExpirationTime ); + + return varTable.Get(); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +int CBaseEntity::ScriptClassify( void ) +{ + return (int)Classify(); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +bool CBaseEntity::ScriptAddOutput( const char *pszOutputName, const char *pszTarget, const char *pszAction, const char *pszParameter, float flDelay, int iMaxTimes ) +{ + const char *pszValue = UTIL_VarArgs("%s,%s,%s,%f,%i", pszTarget, pszAction, pszParameter, flDelay, iMaxTimes); + return KeyValue( pszOutputName, pszValue ); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +const char *CBaseEntity::ScriptGetKeyValue( const char *pszKeyName ) +{ + static char szValue[128]; + if ( GetKeyValue( pszKeyName, szValue, sizeof(szValue) ) ) + return szValue; + return NULL; +} + +//----------------------------------------------------------------------------- +// Vscript: Dispatch an interaction to the entity +//----------------------------------------------------------------------------- +bool CBaseEntity::ScriptDispatchInteraction( int interactionType, HSCRIPT data, HSCRIPT sourceEnt ) +{ + return DispatchInteraction( interactionType, data, ToEnt( sourceEnt ) ? ToEnt( sourceEnt )->MyCombatCharacterPointer() : NULL ); +} +#endif + + +#ifdef MAPBASE +extern int EntityFactory_AutoComplete( const char *cmdname, CUtlVector< CUtlString > &commands, CUtlRBTree< CUtlString > &symbols, char *substring, int checklen = 0 ); + +//------------------------------------------------------------------------------ +// Purpose: Create an entity of the given type +//------------------------------------------------------------------------------ +class CEntCreateAutoCompletionFunctor : public ICommandCallback, public ICommandCompletionCallback +{ +public: + virtual bool CreateAimed() { return false; } + + virtual void CommandCallback( const CCommand &args ) + { + MDLCACHE_CRITICAL_SECTION(); + + CBasePlayer *pPlayer = UTIL_GetCommandClient(); + if (!pPlayer) + { + return; + } + + // Don't allow regular users to create point_servercommand entities for the same reason as blocking ent_fire + if ( !Q_stricmp( args[1], "point_servercommand" ) ) + { + if ( engine->IsDedicatedServer() ) + { + // We allow people with disabled autokick to do it, because they already have rcon. + if ( pPlayer->IsAutoKickDisabled() == false ) + return; + } + else if ( gpGlobals->maxClients > 1 ) + { + // On listen servers with more than 1 player, only allow the host to create point_servercommand. + CBasePlayer *pHostPlayer = UTIL_GetListenServerHost(); + if ( pPlayer != pHostPlayer ) + return; + } + } + + bool allowPrecache = CBaseEntity::IsPrecacheAllowed(); + CBaseEntity::SetAllowPrecache( true ); + + // Try to create entity + CBaseEntity *entity = dynamic_cast< CBaseEntity * >( CreateEntityByName(args[1]) ); + if (entity) + { + // Pass in any additional parameters. + for ( int i = 2; i + 1 < args.ArgC(); i += 2 ) + { + const char *pKeyName = args[i]; + const char *pValue = args[i+1]; + entity->KeyValue( pKeyName, pValue ); + } + + DispatchSpawn(entity); + + // Now attempt to drop into the world + trace_t tr; + Vector forward; + pPlayer->EyeVectors( &forward ); + + // Pass through the player's vehicle + CTraceFilterSkipTwoEntities filter( pPlayer, pPlayer->GetVehicleEntity(), COLLISION_GROUP_NONE ); + UTIL_TraceLine(pPlayer->EyePosition(), + pPlayer->EyePosition() + forward * MAX_TRACE_LENGTH,MASK_SOLID, + &filter, &tr ); + + if ( tr.fraction != 1.0 ) + { + // Raise the end position a little up off the floor, place the npc and drop him down + tr.endpos.z += 12; + + if (CreateAimed()) + { + QAngle angles; + VectorAngles( forward, angles ); + angles.x = 0; + angles.z = 0; + entity->Teleport( &tr.endpos, &angles, NULL ); + } + else + { + entity->Teleport( &tr.endpos, NULL, NULL ); + } + + UTIL_DropToFloor( entity, MASK_SOLID ); + } + + entity->Activate(); + } + CBaseEntity::SetAllowPrecache( allowPrecache ); + } + + virtual int CommandCompletionCallback( const char *partial, CUtlVector< CUtlString > &commands ) + { + if ( !g_pGameRules ) + { + return 0; + } + + const char *cmdname = CreateAimed() ? "ent_create_aimed" : "ent_create"; + + char *substring = (char *)partial; + if ( Q_strstr( partial, cmdname ) ) + { + substring = (char *)partial + strlen( cmdname ) + 1; + } + + int checklen = Q_strlen( substring ); + + CUtlRBTree< CUtlString > symbols( 0, 0, UtlStringLessFunc ); + return EntityFactory_AutoComplete( cmdname, commands, symbols, substring, checklen ); + } +}; + +static CEntCreateAutoCompletionFunctor g_EntCreateAutoComplete; +static ConCommand ent_create("ent_create", &g_EntCreateAutoComplete, "Creates an entity of the given type where the player is looking. Additional parameters can be passed in in the form: ent_create ... ", FCVAR_GAMEDLL | FCVAR_CHEAT, &g_EntCreateAutoComplete); + +class CEntCreateAimedAutoCompletionFunctor : public CEntCreateAutoCompletionFunctor +{ +public: + virtual bool CreateAimed() { return true; } +}; + +static CEntCreateAimedAutoCompletionFunctor g_EntCreateAimedAutoComplete; + +static ConCommand ent_create_aimed("ent_create_aimed", &g_EntCreateAimedAutoComplete, "Creates an entity of the given type where the player is looking. Additional parameters can be passed in in the form: ent_create_aimed ... ", FCVAR_CHEAT, &g_EntCreateAimedAutoComplete); +#else //------------------------------------------------------------------------------ // Purpose: Create an NPC of the given type //------------------------------------------------------------------------------ @@ -8579,6 +10983,7 @@ void CC_Ent_Create( const CCommand& args ) CBaseEntity::SetAllowPrecache( allowPrecache ); } static ConCommand ent_create("ent_create", CC_Ent_Create, "Creates an entity of the given type where the player is looking. Additional parameters can be passed in in the form: ent_create ... ", FCVAR_GAMEDLL | FCVAR_CHEAT); +#endif //------------------------------------------------------------------------------ // Purpose: Teleport a specified entity to where the player is looking @@ -8641,6 +11046,51 @@ bool CC_GetCommandEnt( const CCommand& args, CBaseEntity **ent, Vector *vecTarge return true; } +#ifdef MAPBASE +class CEntTeleportAutoCompletionFunctor : public ICommandCallback, public ICommandCompletionCallback +{ +public: + virtual void CommandCallback( const CCommand &command ) + { + if ( command.ArgC() < 2 ) + { + Msg( "Format: ent_teleport \n" ); + return; + } + + CBaseEntity *pEnt; + Vector vecTargetPoint; + if ( CC_GetCommandEnt( command, &pEnt, &vecTargetPoint, NULL ) ) + { + pEnt->Teleport( &vecTargetPoint, NULL, NULL ); + } + } + + virtual int CommandCompletionCallback( const char *partial, CUtlVector< CUtlString > &commands ) + { + if ( !g_pGameRules ) + { + return 0; + } + + const char *cmdname = "ent_teleport"; + + char *substring = (char *)partial; + if ( Q_strstr( partial, cmdname ) ) + { + substring = (char *)partial + strlen( cmdname ) + 1; + } + + int checklen = Q_strlen( substring ); + + CUtlRBTree< CUtlString > symbols( 0, 0, UtlStringLessFunc ); + return AutoCompleteEntities(cmdname, commands, symbols, substring, checklen); + } +}; + +static CEntTeleportAutoCompletionFunctor g_EntTeleportAutoComplete; +static ConCommand ent_teleport("ent_teleport", &g_EntTeleportAutoComplete, "Teleport the specified entity to where the player is looking.\n\tFormat: ent_teleport ", FCVAR_CHEAT, &g_EntTeleportAutoComplete); +#else //------------------------------------------------------------------------------ // Purpose: Teleport a specified entity to where the player is looking //------------------------------------------------------------------------------ @@ -8661,6 +11111,7 @@ void CC_Ent_Teleport( const CCommand& args ) } static ConCommand ent_teleport("ent_teleport", CC_Ent_Teleport, "Teleport the specified entity to where the player is looking.\n\tFormat: ent_teleport ", FCVAR_CHEAT); +#endif //------------------------------------------------------------------------------ // Purpose: Orient a specified entity to match the player's angles diff --git a/src/game/server/baseentity.h b/src/game/server/baseentity.h index 73eeae0b..23c2fecb 100644 --- a/src/game/server/baseentity.h +++ b/src/game/server/baseentity.h @@ -20,6 +20,13 @@ #include "ServerNetworkProperty.h" #include "shareddefs.h" #include "engine/ivmodelinfo.h" +#include "vscript/ivscript.h" +#include "vscript_server.h" +#ifdef NEW_RESPONSE_SYSTEM +#include "AI_Criteria.h" +#include "AI_ResponseSystem.h" +#endif + #include "vscript/ivscript.h" #include "vscript_server.h" @@ -28,8 +35,10 @@ class CDmgAccumulator; struct CSoundParameters; +#ifndef NEW_RESPONSE_SYSTEM class AI_CriteriaSet; class IResponseSystem; +#endif class IEntitySaveUtils; class CRecipientFilter; class CStudioHdr; @@ -51,6 +60,11 @@ struct IsSame // FIXME: Could do this in the script file by making it required and bumping up weighting there instead... #define CONCEPT_WEIGHT 5.0f +#ifdef NEW_RESPONSE_SYSTEM +// Relax the namespace standard a bit so that less code has to be changed +#define IResponseSystem ResponseRules::IResponseSystem +#endif + typedef CHandle EHANDLE; #define MANUALMODE_GETSET_PROP(type, accessorName, varName) \ @@ -97,6 +111,7 @@ typedef struct KeyValueData_s KeyValueData; class CUserCmd; class CSkyCamera; class CEntityMapData; +class CWorld; class INextBot; class IHasAttributes; @@ -327,6 +342,8 @@ CBaseEntity *CreateEntityByName( const char *className, int iForceEdictIndex = - CBaseNetworkable *CreateNetworkableByName( const char *className ); CBaseEntity *ToEnt( HSCRIPT hScript ); +CBaseEntity* ToEnt(HSCRIPT hScript); + // creates an entity and calls all the necessary spawn functions extern void SpawnEntityByName( const char *className, CEntityMapData *mapData = NULL ); @@ -348,6 +365,16 @@ struct thinkfunc_t DECLARE_SIMPLE_DATADESC(); }; +#ifdef MAPBASE_VSCRIPT +struct scriptthinkfunc_t +{ + float m_flNextThink; + HSCRIPT m_hfnThink; + unsigned m_iContextHash; + bool m_bNoParam; +}; +#endif + struct EmitSound_t; struct rotatingpushmove_t; @@ -566,6 +593,11 @@ public: bool IsFollowingEntity(); CBaseEntity *GetFollowedEntity(); +#ifdef MAPBASE_VSCRIPT + void ScriptFollowEntity( HSCRIPT hBaseEntity, bool bBoneMerge ); + HSCRIPT ScriptGetFollowedEntity(); +#endif + // initialization virtual void Spawn( void ); virtual void Precache( void ) {} @@ -592,7 +624,16 @@ public: void ValidateEntityConnections(); void FireNamedOutput( const char *pszOutput, variant_t variant, CBaseEntity *pActivator, CBaseEntity *pCaller, float flDelay = 0.0f ); +#ifdef MAPBASE + virtual +#endif CBaseEntityOutput *FindNamedOutput( const char *pszOutput ); +#ifdef MAPBASE_VSCRIPT + void ScriptFireOutput( const char *pszOutput, HSCRIPT hActivator, HSCRIPT hCaller, const char *szValue, float flDelay ); + float GetMaxOutputDelay( const char *pszOutput ); + //void CancelEventsByInput( const char *szInput ); +#endif + // Activate - called for each entity after each load game and level load virtual void Activate( void ); @@ -604,6 +645,9 @@ public: CBaseEntity *NextMovePeer( void ); void SetName( string_t newTarget ); +#ifdef MAPBASE_VSCRIPT + void SetNameAsCStr( const char *newTarget ); +#endif void SetParent( string_t newParent, CBaseEntity *pActivator, int iAttachment = -1 ); // Set the movement parent. Your local origin and angles will become relative to this parent. @@ -669,6 +713,12 @@ public: // returns true if the the value in the pass in should be set, false if the input is to be ignored virtual bool AcceptInput( const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t Value, int outputID ); + bool ScriptInputHook( const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t Value, ScriptVariant_t &functionReturn ); + void ScriptInputHookClearParams(); +#ifdef MAPBASE_VSCRIPT + bool ScriptDeathHook( CTakeDamageInfo *info ); +#endif + // // Input handlers. // @@ -695,6 +745,9 @@ public: void InputDisableShadow( inputdata_t &inputdata ); void InputEnableShadow( inputdata_t &inputdata ); void InputAddOutput( inputdata_t &inputdata ); +#ifdef MAPBASE + void InputChangeVariable( inputdata_t &inputdata ); +#endif void InputFireUser1( inputdata_t &inputdata ); void InputFireUser2( inputdata_t &inputdata ); void InputFireUser3( inputdata_t &inputdata ); @@ -705,6 +758,10 @@ public: void InputCallScriptFunction( inputdata_t &inputdata ); void TerminateScriptScope(); void InputTerminateScriptScope( inputdata_t &inputdata ); +#ifdef MAPBASE_VSCRIPT + void InputRunScriptQuotable( inputdata_t &inputdata ); + void InputClearScriptScope( inputdata_t &inputdata ); +#endif inline void ScriptDisableDraw() { @@ -733,6 +790,72 @@ public: bool RunScript( const char *pScriptText, const char *pDebugFilename = "CBaseEntity::RunScript" ); void ScriptInputKill( void ); +#ifdef MAPBASE + void InputPassUser1( inputdata_t &inputdata ); + void InputPassUser2( inputdata_t &inputdata ); + void InputPassUser3( inputdata_t &inputdata ); + void InputPassUser4( inputdata_t &inputdata ); + + void InputFireRandomUser( inputdata_t &inputdata ); + void InputPassRandomUser( inputdata_t &inputdata ); + + void InputSetEntityName( inputdata_t &inputdata ); + + virtual void InputSetTarget( inputdata_t &inputdata ); + virtual void InputSetOwnerEntity( inputdata_t &inputdata ); + + virtual void InputAddHealth( inputdata_t &inputdata ); + virtual void InputRemoveHealth( inputdata_t &inputdata ); + virtual void InputSetHealth( inputdata_t &inputdata ); + + virtual void InputSetMaxHealth( inputdata_t &inputdata ); + + void InputFireOutput( inputdata_t &inputdata ); + void InputRemoveOutput( inputdata_t &inputdata ); + //virtual void InputCancelOutput( inputdata_t &inputdata ); // Find a way to implement this + void InputReplaceOutput( inputdata_t &inputdata ); + void InputAcceptInput( inputdata_t &inputdata ); + virtual void InputCancelPending( inputdata_t &inputdata ); + + void InputFreeChildren( inputdata_t &inputdata ); + + void InputSetLocalOrigin( inputdata_t &inputdata ); + void InputSetLocalAngles( inputdata_t &inputdata ); + void InputSetAbsOrigin( inputdata_t &inputdata ); + void InputSetAbsAngles( inputdata_t &inputdata ); + void InputSetLocalVelocity( inputdata_t &inputdata ); + void InputSetLocalAngularVelocity( inputdata_t &inputdata ); + + void InputAddSpawnFlags( inputdata_t &inputdata ); + void InputRemoveSpawnFlags( inputdata_t &inputdata ); + void InputSetRenderMode( inputdata_t &inputdata ); + void InputSetRenderFX( inputdata_t &inputdata ); + void InputSetViewHideFlags( inputdata_t &inputdata ); + void InputAddEffects( inputdata_t &inputdata ); + void InputRemoveEffects( inputdata_t &inputdata ); + void InputDrawEntity( inputdata_t &inputdata ); + void InputUndrawEntity( inputdata_t &inputdata ); + void InputEnableReceivingFlashlight( inputdata_t &inputdata ); + void InputDisableReceivingFlashlight( inputdata_t &inputdata ); + void InputAddEFlags( inputdata_t &inputdata ); + void InputRemoveEFlags( inputdata_t &inputdata ); + void InputAddSolidFlags( inputdata_t &inputdata ); + void InputRemoveSolidFlags( inputdata_t &inputdata ); + void InputSetMoveType( inputdata_t &inputdata ); + void InputSetCollisionGroup( inputdata_t &inputdata ); + + void InputTouch( inputdata_t &inputdata ); + + virtual void InputKilledNPC( inputdata_t &inputdata ); + + void InputKillIfNotVisible( inputdata_t &inputdata ); + void InputKillWhenNotVisible( inputdata_t &inputdata ); + + void InputSetThinkNull( inputdata_t &inputdata ); + + COutputEvent m_OnKilled; +#endif + // Returns the origin at which to play an inputted dispatcheffect virtual void GetInputDispatchEffectPosition( const char *sInputString, Vector &pOrigin, QAngle &pAngles ); @@ -787,6 +910,13 @@ public: void SetAIWalkable( bool bBlocksLOS ); bool IsAIWalkable( void ); + +#ifdef MAPBASE + // Handle a potentially complex command from a client. + // Returns true if the command was handled successfully. + virtual bool HandleEntityCommand(CBasePlayer* pClient, KeyValues* pKeyValues) { return false; } +#endif // MAPBASE + private: int SaveDataDescBlock( ISave &save, datamap_t *dmap ); int RestoreDataDescBlock( IRestore &restore, datamap_t *dmap ); @@ -860,6 +990,19 @@ public: CNetworkArray( int, m_nModelIndexOverrides, MAX_VISION_MODES ); // used to override the base model index on the client if necessary #endif +#ifdef MAPBASE + // Prevents this entity from drawing under certain view IDs. Each flag is (1 << the view ID to hide from). + // For example, hiding an entity from VIEW_MONITOR prevents it from showing up on RT camera monitors + // and hiding an entity from VIEW_MAIN just prevents it from showing up through the player's own "eyes". + // Doing this via flags allows for the entity to be hidden from multiple view IDs at the same time. + // + // This was partly inspired by Underhell's keyvalue that allows entities to only render in mirrors and cameras. + CNetworkVar( int, m_iViewHideFlags ); + + // Disables receiving projected textures. Based on a keyvalue from later Source games. + CNetworkVar( bool, m_bDisableFlashlight ); +#endif + // was pev->rendercolor CNetworkColor32( m_clrRender ); const color32 GetRenderColor() const; @@ -906,13 +1049,30 @@ protected: #endif void RemoveExpiredConcepts( void ); +#ifdef MAPBASE + // Some new code needs to access these functions from outside of the class. +public: +#endif int GetContextCount() const; // Call RemoveExpiredConcepts to clean out expired concepts const char *GetContextName( int index ) const; // note: context may be expired const char *GetContextValue( int index ) const; // note: context may be expired bool ContextExpired( int index ) const; int FindContextByName( const char *name ) const; +#ifndef MAPBASE public: +#endif + +#ifdef MAPBASE + bool HasContext( const char *name, const char *value ) const; + bool HasContext( string_t name, string_t value ) const; // NOTE: string_t version only compares pointers! + bool HasContext( const char *nameandvalue ) const; + const char *GetContextValue( const char *contextName ) const; + float GetContextExpireTime( const char *name ); + void RemoveContext( const char *nameandvalue ); +#endif + void AddContext( const char *nameandvalue ); + void AddContext( const char *name, const char *value, float duration = 0.0f ); protected: CUtlVector< ResponseContext_t > m_ResponseContexts; @@ -956,6 +1116,12 @@ public: // Call this to do a TraceAttack on an entity, performs filtering. Don't call TraceAttack() directly except when chaining up to base class void DispatchTraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator = NULL ); virtual bool PassesDamageFilter( const CTakeDamageInfo &info ); +#ifdef MAPBASE + // Special filter functions made for the "damage" family of filters, including filter_damage_transfer. + bool PassesFinalDamageFilter( const CTakeDamageInfo &info ); + bool DamageFilterAllowsBlood( const CTakeDamageInfo &info ); + bool DamageFilterDamageMod( CTakeDamageInfo &info ); +#endif protected: @@ -970,7 +1136,10 @@ public: // This is what you should call to apply damage to an entity. int TakeDamage( const CTakeDamageInfo &info ); + +#ifdef VSCRIPT_PRIORITIZE_TF2_SYNTAX void ScriptTakeDamage( float flDamage, int nDamageType, HSCRIPT hAttacker ); +#endif void ScriptTakeDamageEx( HSCRIPT hInflictor, HSCRIPT hAttacker, HSCRIPT hWeapon, const Vector damageForce, const Vector damagePosition, float flDamage, int ndamageType ); void ScriptTakeDamageCustom( HSCRIPT hInflictor, HSCRIPT hAttacker, HSCRIPT hWeapon, const Vector damageForce, const Vector damagePosition, float flDamage, int ndamageType, int nCustomDamageType ); Vector GetPhysVelocity() const; @@ -988,7 +1157,7 @@ public: void SendOnKilledGameEvent( const CTakeDamageInfo &info ); // Notifier that I've killed some other entity. (called from Victim's Event_Killed). - virtual void Event_KilledOther( CBaseEntity *pVictim, const CTakeDamageInfo &info ) { return; } + virtual void Event_KilledOther( CBaseEntity *pVictim, const CTakeDamageInfo &info ); // UNDONE: Make this data? virtual int BloodColor( void ); @@ -1001,7 +1170,7 @@ public: virtual INextBot *MyNextBotPointer( void ) { return NULL; } virtual float GetDelay( void ) { return 0; } virtual bool IsMoving( void ); - bool IsWorld() { return entindex() == 0; } + bool IsWorld() const { extern CWorld *g_WorldEntity; return (void *)this == (void *)g_WorldEntity; } // Ported from the Alien Swarm SDK to fix false IsWorld() positives on server-only entities virtual char const *DamageDecal( int bitsDamageType, int gameMaterial ); virtual void DecalTrace( trace_t *pTrace, char const *decalName ); virtual void ImpactTrace( trace_t *pTrace, int iDamageType, const char *pCustomImpactName = NULL ); @@ -1126,6 +1295,10 @@ public: void SUB_CallUseToggle( void ) { this->Use( this, this, USE_TOGGLE, 0 ); } void SUB_PerformFadeOut( void ); virtual bool SUB_AllowedToFade( void ); +#ifdef MAPBASE + // For KillWhenNotVisible + void SUB_RemoveWhenNotVisible( void ); +#endif // change position, velocity, orientation instantly // passing NULL means no change @@ -1212,7 +1385,16 @@ public: #endif // _DEBUG virtual void ModifyOrAppendCriteria( AI_CriteriaSet& set ); +#ifdef NEW_RESPONSE_SYSTEM + // this computes criteria that depend on the other criteria having been set. + // needs to be done in a second pass because we may have multiple overrids for + // a context before it all settles out. + virtual void ModifyOrAppendDerivedCriteria( AI_CriteriaSet& set ) {}; +#endif void AppendContextToCriteria( AI_CriteriaSet& set, const char *prefix = "" ); +#ifdef MAPBASE + void ReAppendContextCriteria( AI_CriteriaSet& set ); +#endif void DumpResponseCriteria( void ); // Return the IHasAttributes interface for this base entity. Removes the need for: @@ -1258,6 +1440,9 @@ public: const char *GetScriptId(); const char *GetScriptThinkFunc(); HSCRIPT GetScriptScope(); +#ifdef MAPBASE_VSCRIPT + HSCRIPT GetOrCreatePrivateScriptScope(); +#endif void RunPrecacheScripts( void ); void RunOnPostSpawnScripts( void ); @@ -1344,12 +1529,91 @@ public: bool ScriptAcceptInput( const char *pInputName, const char *pValue, HSCRIPT hActivator, HSCRIPT hCaller ); +#ifdef MAPBASE_VSCRIPT + void ScriptSetThinkFunction(const char *szFunc, float time); + void ScriptStopThinkFunction(); + void ScriptSetContextThink( const char* szContext, HSCRIPT hFunc, float time ); + void ScriptSetThink( HSCRIPT hFunc, float time ); + void ScriptStopThink(); + void ScriptContextThink(); +private: + CUtlVector< scriptthinkfunc_t* > m_ScriptThinkFuncs; +public: + + HSCRIPT LookupScriptFunction(const char* pFunctionName); + bool CallScriptFunctionHandle(HSCRIPT hFunc, ScriptVariant_t* pFunctionReturn); + + void ScriptSetOriginAngles(const Vector &vecOrigin, const QAngle &angAngles) { Teleport(&vecOrigin, &angAngles, NULL); } + void ScriptSetOriginAnglesVelocity(const Vector &vecOrigin, const QAngle &angAngles, const Vector &vecVelocity) { Teleport(&vecOrigin, &angAngles, &vecVelocity); } + + HSCRIPT ScriptEntityToWorldTransform( void ); + + HSCRIPT ScriptGetPhysicsObject( void ); + void ScriptPhysicsInitNormal( int nSolidType, int nSolidFlags, bool createAsleep ); + void ScriptPhysicsDestroyObject() { VPhysicsDestroyObject(); } + + void ScriptSetParent(HSCRIPT hParent, const char *szAttachment); + + bool ScriptIsVisible( const Vector &vecSpot ) { return FVisible( vecSpot ); } + bool ScriptIsEntVisible( HSCRIPT pEntity ) { return FVisible( ToEnt( pEntity ) ); } + bool ScriptIsVisibleWithMask( const Vector &vecSpot, int traceMask ) { return FVisible( vecSpot, traceMask ); } + +#ifndef VSCRIPT_PRIORITIZE_TF2_SYNTAX + int ScriptTakeDamage( HSCRIPT pInfo ); +#endif + void ScriptFireBullets( HSCRIPT pInfo ); + + void ScriptAddContext( const char *name, const char *value, float duration = 0.0f ); + const char *ScriptGetContext( const char *name ); + HSCRIPT ScriptGetContextIndex( int index ); + + int ScriptClassify(void); + + bool ScriptAddOutput( const char *pszOutputName, const char *pszTarget, const char *pszAction, const char *pszParameter, float flDelay, int iMaxTimes ); + const char *ScriptGetKeyValue( const char *pszKeyName ); + + const Vector& ScriptGetColorVector(); + int ScriptGetColorR() { return m_clrRender.GetR(); } + int ScriptGetColorG() { return m_clrRender.GetG(); } + int ScriptGetColorB() { return m_clrRender.GetB(); } + int ScriptGetAlpha() { return m_clrRender.GetA(); } + void ScriptSetColorVector( const Vector& vecColor ); + void ScriptSetColor( int r, int g, int b ); + void ScriptSetColorR( int iVal ) { SetRenderColorR( iVal ); } + void ScriptSetColorG( int iVal ) { SetRenderColorG( iVal ); } + void ScriptSetColorB( int iVal ) { SetRenderColorB( iVal ); } + void ScriptSetAlpha( int iVal ) { SetRenderColorA( iVal ); } + + int ScriptGetRenderMode() { return GetRenderMode(); } + void ScriptSetRenderMode( int nRenderMode ) { SetRenderMode( (RenderMode_t)nRenderMode ); } + + bool ScriptDispatchInteraction( int interactionType, HSCRIPT data, HSCRIPT sourceEnt ); + + int ScriptGetTakeDamage() { return m_takedamage; } + void ScriptSetTakeDamage( int val ) { m_takedamage = val; } + + static ScriptHook_t g_Hook_UpdateOnRemove; + static ScriptHook_t g_Hook_OnEntText; + + static ScriptHook_t g_Hook_VPhysicsCollision; + static ScriptHook_t g_Hook_FireBullets; + static ScriptHook_t g_Hook_OnDeath; + static ScriptHook_t g_Hook_OnKilledOther; + static ScriptHook_t g_Hook_HandleInteraction; + static ScriptHook_t g_Hook_ModifyEmitSoundParams; + static ScriptHook_t g_Hook_ModifySentenceParams; +#endif + string_t m_iszVScripts; string_t m_iszScriptThinkFunction; CScriptScope m_ScriptScope; HSCRIPT m_hScriptInstance; string_t m_iszScriptId; +#ifdef MAPBASE_VSCRIPT + HSCRIPT m_pScriptModelKeyValues; +#else CScriptKeyValues *m_pScriptModelKeyValues; +#endif // virtual functions used by a few classes @@ -1367,6 +1631,20 @@ public: virtual float GetDamage() { return 0; } virtual void SetDamage(float flDamage) {} +#ifdef MAPBASE + // Some entities want to use interactions regardless of whether they're a CBaseCombatCharacter. + // Valve ran into this issue with frag grenades when they started deriving from CBaseAnimating instead of CBaseCombatCharacter, + // preventing them from using the barnacle interactions for rigged grenade timing so it's guaranteed to blow up in the barnacle's face. + // We're used to unaltered behavior now, so we're not restoring that as default, but making this a "base entity" thing is supposed to help in situtions like those. + // + // Also, keep in mind pretty much all existing DispatchInteraction() calls are only performed on CBaseCombatCharacters. + // You'll need to change their code manually if you want other, non-character entities to use the interaction. + bool DispatchInteraction( int interactionType, void *data, CBaseCombatCharacter* sourceEnt ); + + // Do not call HandleInteraction directly, use DispatchInteraction + virtual bool HandleInteraction( int interactionType, void *data, CBaseCombatCharacter* sourceEnt ) { return false; } +#endif + virtual Vector EyePosition( void ); // position of eyes inline Vector ScriptEyePosition() { return EyePosition(); } virtual const QAngle &EyeAngles( void ); // Direction of eyes in world space @@ -1420,6 +1698,10 @@ public: void SetGravity( float gravity ); float GetFriction( void ) const; void SetFriction( float flFriction ); +#ifdef MAPBASE_VSCRIPT + void SetMass(float mass); + float GetMass(); +#endif virtual bool FVisible ( CBaseEntity *pEntity, int traceMask = MASK_BLOCKLOS, CBaseEntity **ppBlocker = NULL ); virtual bool FVisible( const Vector &vecTarget, int traceMask = MASK_BLOCKLOS, CBaseEntity **ppBlocker = NULL ); @@ -1439,6 +1721,11 @@ public: void SetGroundEntity( CBaseEntity *ground ); CBaseEntity *GetGroundEntity( void ); CBaseEntity *GetGroundEntity( void ) const { return const_cast(this)->GetGroundEntity(); } + +#ifdef MAPBASE_VSCRIPT + HSCRIPT ScriptGetGroundEntity(); + void ScriptSetGroundEntity( HSCRIPT hGroundEnt ); +#endif // Gets the velocity we impart to a player standing on us virtual void GetGroundVelocityToApply( Vector &vecGroundVel ) { vecGroundVel = vec3_origin; } @@ -1539,6 +1826,12 @@ public: void GenderExpandString( char const *in, char *out, int maxlen ); virtual void ModifyEmitSoundParams( EmitSound_t ¶ms ); +#ifdef MAPBASE + // Same as above, but for sentences + // (which don't actually have EmitSound_t params) + virtual void ModifySentenceParams( int &iSentenceIndex, int &iChannel, float &flVolume, soundlevel_t &iSoundlevel, int &iFlags, int &iPitch, + const Vector **pOrigin, const Vector **pDirection, bool &bUpdatePositions, float &soundtime, int &iSpecialDSP, int &iSpeakerIndex ); +#endif static float GetSoundDuration( const char *soundname, char const *actormodel ); @@ -1570,7 +1863,11 @@ public: static void EmitCloseCaption( IRecipientFilter& filter, int entindex, char const *token, CUtlVector< Vector >& soundorigins, float duration, bool warnifmissing = false ); static void EmitSentenceByIndex( IRecipientFilter& filter, int iEntIndex, int iChannel, int iSentenceIndex, float flVolume, soundlevel_t iSoundlevel, int iFlags = 0, int iPitch = PITCH_NORM, - const Vector *pOrigin = NULL, const Vector *pDirection = NULL, bool bUpdatePositions = true, float soundtime = 0.0f ); + const Vector *pOrigin = NULL, const Vector *pDirection = NULL, bool bUpdatePositions = true, float soundtime = 0.0f +#ifdef MAPBASE + , int iSpecialDSP = 0, int iSpeakerIndex = -1 // Needed for env_microphone +#endif + ); static bool IsPrecacheAllowed(); static void SetAllowPrecache( bool allow ); @@ -1660,6 +1957,11 @@ public: // Callbacks for the physgun/cannon picking up an entity virtual CBasePlayer *HasPhysicsAttacker( float dt ) { return NULL; } +#ifdef MAPBASE + // This function needed to be extended to phys_magnet and I didn't like the dynamic_casts. + virtual bool CanBePickedUpByPhyscannon() { return false; } +#endif + // UNDONE: Make this data? virtual unsigned int PhysicsSolidMaskForEntity( void ) const; @@ -1804,7 +2106,7 @@ private: // was pev->flags CNetworkVarForDerived( int, m_fFlags ); - string_t m_iName; // name used to identify this entity + CNetworkVar( string_t, m_iName ); // name used to identify this entity // Damage modifiers friend class CDamageModifier; @@ -1898,6 +2200,12 @@ private: CNetworkVar( bool, m_bAlternateSorting ); // User outputs. Fired when the "FireInputX" input is triggered. +#ifdef MAPBASE + COutputVariant m_OutUser1; + COutputVariant m_OutUser2; + COutputVariant m_OutUser3; + COutputVariant m_OutUser4; +#endif COutputEvent m_OnUser1; COutputEvent m_OnUser2; COutputEvent m_OnUser3; @@ -2154,16 +2462,16 @@ inline string_t CBaseEntity::GetEntityName() inline const char *CBaseEntity::GetEntityNameAsCStr() { - return STRING( m_iName ); + return STRING( m_iName.Get() ); } inline const char *CBaseEntity::GetPreTemplateName() { - const char *pszDelimiter = V_strrchr( STRING( m_iName ), '&' ); + const char *pszDelimiter = V_strrchr( STRING( m_iName.Get() ), '&' ); if ( !pszDelimiter ) - return STRING( m_iName ); + return STRING( m_iName.Get() ); static char szStrippedName[128]; - V_strncpy( szStrippedName, STRING( m_iName ), MIN( (intp)(ARRAYSIZE(szStrippedName)), (intp)(pszDelimiter - STRING( m_iName ) + 1 )) ); + V_strncpy( szStrippedName, STRING( m_iName.Get() ), MIN( ARRAYSIZE(szStrippedName), pszDelimiter - STRING( m_iName.Get() ) + 1 ) ); return szStrippedName; } @@ -2172,6 +2480,12 @@ inline void CBaseEntity::SetName( string_t newName ) m_iName = newName; } +#ifdef MAPBASE_VSCRIPT +inline void CBaseEntity::SetNameAsCStr( const char *newName ) +{ + m_iName = AllocPooledString(newName); +} +#endif inline bool CBaseEntity::NameMatches( const char *pszNameOrWildcard ) { @@ -2359,6 +2673,36 @@ inline const QAngle& CBaseEntity::GetAbsAngles( void ) const return m_angAbsRotation; } +#ifdef MAPBASE_VSCRIPT +inline float CBaseEntity::GetMass() +{ + IPhysicsObject *vPhys = VPhysicsGetObject(); + if (vPhys) + { + return vPhys->GetMass(); + } + else + { + Warning("Tried to call GetMass() on %s but it has no physics.\n", GetDebugName()); + return 0; + } +} + +inline void CBaseEntity::SetMass(float mass) +{ + mass = clamp(mass, VPHYSICS_MIN_MASS, VPHYSICS_MAX_MASS); + + IPhysicsObject *vPhys = VPhysicsGetObject(); + if (vPhys) + { + vPhys->SetMass(mass); + } + else + { + Warning("Tried to call SetMass() on %s but it has no physics.\n", GetDebugName()); + } +} +#endif //----------------------------------------------------------------------------- diff --git a/src/game/server/baseflex.cpp b/src/game/server/baseflex.cpp index 495d5db0..f7c3c3a9 100644 --- a/src/game/server/baseflex.cpp +++ b/src/game/server/baseflex.cpp @@ -95,12 +95,19 @@ BEGIN_DATADESC( CBaseFlex ) END_DATADESC() +#ifdef MAPBASE_VSCRIPT +BEGIN_ENT_SCRIPTDESC( CBaseFlex, CBaseAnimatingOverlay, "Animated characters who have vertex flex capability." ) +#else BEGIN_ENT_SCRIPTDESC( CBaseFlex, CBaseAnimating, "Animated characters who have vertex flex capability." ) -#if 0 +#endif +#ifdef MAPBASE_VSCRIPT DEFINE_SCRIPTFUNC_NAMED( ScriptGetOldestScene, "GetCurrentScene", "Returns the instance of the oldest active scene entity (if any)." ) DEFINE_SCRIPTFUNC_NAMED( ScriptGetSceneByIndex, "GetSceneByIndex", "Returns the instance of the scene entity at the specified index." ) #endif - DEFINE_SCRIPTFUNC_NAMED( ScriptPlayScene, "PlayScene", "Play the specified .vcd file." ) + +#ifdef MAPBASE + DEFINE_SCRIPTFUNC( SetViewtarget, "Sets the entity's eye target." ) +#endif END_SCRIPTDESC(); #if 0 @@ -439,7 +446,11 @@ bool CBaseFlex::ClearSceneEvent( CSceneEventInfo *info, bool fastKill, bool canc // expression - // duration - //----------------------------------------------------------------------------- +#ifdef MAPBASE +void CBaseFlex::AddSceneEvent( CChoreoScene *scene, CChoreoEvent *event, CBaseEntity *pTarget, CSceneEntity *pSceneEnt ) +#else void CBaseFlex::AddSceneEvent( CChoreoScene *scene, CChoreoEvent *event, CBaseEntity *pTarget ) +#endif { if ( !scene || !event ) { @@ -462,9 +473,14 @@ void CBaseFlex::AddSceneEvent( CChoreoScene *scene, CChoreoEvent *event, CBaseEn info.m_pEvent = event; info.m_pScene = scene; info.m_hTarget = pTarget; - info.m_bStarted = false; + info.m_bStarted = false; + info.m_hSceneEntity = pSceneEnt; +#ifdef MAPBASE + if (StartSceneEvent( &info, scene, event, actor, pTarget, pSceneEnt )) +#else if (StartSceneEvent( &info, scene, event, actor, pTarget )) +#endif { m_SceneEvents.AddToTail( info ); } @@ -780,7 +796,11 @@ bool CBaseFlex::StartMoveToSceneEvent( CSceneEventInfo *info, CChoreoScene *scen //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- +#ifdef MAPBASE +bool CBaseFlex::StartSceneEvent( CSceneEventInfo *info, CChoreoScene *scene, CChoreoEvent *event, CChoreoActor *actor, CBaseEntity *pTarget, CSceneEntity *pSceneEnt ) +#else bool CBaseFlex::StartSceneEvent( CSceneEventInfo *info, CChoreoScene *scene, CChoreoEvent *event, CChoreoActor *actor, CBaseEntity *pTarget ) +#endif { switch ( event->GetType() ) { @@ -810,6 +830,67 @@ bool CBaseFlex::StartSceneEvent( CSceneEventInfo *info, CChoreoScene *scene, CCh case CChoreoEvent::EXPRESSION: // These are handled client-side return true; + +#ifdef MAPBASE + case CChoreoEvent::GENERIC: + { + // This is handled in CBaseFlex so that any flex entity--including players--could use this text. + if (stricmp(event->GetParameters(), "AI_GAMETEXT") == 0) + { + // game_text-based lines, for placeholders + if ( event->GetParameters2() ) + { + info->m_nType = 12; // SCENE_AI_GAMETEXT + + hudtextparms_t textParams; + textParams.holdTime = event->GetDuration(); + textParams.fadeinTime = 0.5f; + textParams.fadeoutTime = 0.5f; + + textParams.channel = 3; + textParams.x = -1; + textParams.y = 0.6; + textParams.effect = 0; + + textParams.r1 = 255; + textParams.g1 = 255; + textParams.b1 = 255; + + if ( GetGameTextSpeechParams( textParams ) ) + { + CRecipientFilter filter; + filter.AddAllPlayers(); + filter.MakeReliable(); + + UserMessageBegin( filter, "HudMsg" ); + WRITE_BYTE ( textParams.channel & 0xFF ); + WRITE_FLOAT( textParams.x ); + WRITE_FLOAT( textParams.y ); + WRITE_BYTE ( textParams.r1 ); + WRITE_BYTE ( textParams.g1 ); + WRITE_BYTE ( textParams.b1 ); + WRITE_BYTE ( textParams.a1 ); + WRITE_BYTE ( textParams.r2 ); + WRITE_BYTE ( textParams.g2 ); + WRITE_BYTE ( textParams.b2 ); + WRITE_BYTE ( textParams.a2 ); + WRITE_BYTE ( textParams.effect ); + WRITE_FLOAT( textParams.fadeinTime ); + WRITE_FLOAT( textParams.fadeoutTime ); + WRITE_FLOAT( textParams.holdTime ); + WRITE_FLOAT( textParams.fxTime ); + WRITE_STRING( event->GetParameters2() ); + WRITE_STRING( "" ); // No custom font + WRITE_BYTE ( Q_strlen( event->GetParameters2() ) ); + MessageEnd(); + } + return true; + } + } + + return false; + } +#endif } return false; @@ -2048,12 +2129,105 @@ float CBaseFlex::PlayScene( const char *pszScene, float flDelay, AI_Response *re // Input : *soundname - // Output : float //----------------------------------------------------------------------------- +#ifdef MAPBASE +float CBaseFlex::PlayAutoGeneratedSoundScene( const char *soundname, float flDelay, AI_Response *response, IRecipientFilter *filter ) +{ + return InstancedAutoGeneratedSoundScene( this, soundname, NULL, flDelay, false, response, false, filter ); +} +#else float CBaseFlex::PlayAutoGeneratedSoundScene( const char *soundname ) { return InstancedAutoGeneratedSoundScene( this, soundname ); } +#endif + +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: Parameters for scene event AI_GameText +//----------------------------------------------------------------------------- +bool CBaseFlex::GetGameTextSpeechParams( hudtextparms_t ¶ms ) +{ + ScriptVariant_t varTable; + if (g_pScriptVM->GetValue(m_ScriptScope, "m_GameTextSpeechParams", &varTable) && varTable.m_type == FIELD_HSCRIPT) + { + int nIterator = -1; + ScriptVariant_t varKey, varValue; + while ((nIterator = g_pScriptVM->GetKeyValue( varTable.m_hScript, nIterator, &varKey, &varValue )) != -1) + { + if (FStrEq( varKey.m_pszString, "color" )) + { + params.r1 = varValue.m_pVector->x; + params.g1 = varValue.m_pVector->y; + params.b1 = varValue.m_pVector->z; + } + else if (FStrEq( varKey.m_pszString, "color2" )) + { + params.r2 = varValue.m_pVector->x; + params.g2 = varValue.m_pVector->y; + params.b2 = varValue.m_pVector->z; + } + else if (FStrEq( varKey.m_pszString, "channel" )) + { + params.channel = varValue.m_int; + } + else if (FStrEq( varKey.m_pszString, "x" )) + { + params.x = varValue.m_float; + } + else if (FStrEq( varKey.m_pszString, "y" )) + { + params.y = varValue.m_float; + } + else if (FStrEq( varKey.m_pszString, "effect" )) + { + params.effect = varValue.m_int; + } + else if (FStrEq( varKey.m_pszString, "fxtime" )) + { + params.fxTime = varValue.m_float; + } + + g_pScriptVM->ReleaseValue( varKey ); + g_pScriptVM->ReleaseValue( varValue ); + } + } + + return true; +} +#endif +//-------------------------------------------------------------------------------------------------- +// Returns the script instance of the scene entity associated with our oldest ("top level") scene event +//-------------------------------------------------------------------------------------------------- +HSCRIPT CBaseFlex::ScriptGetOldestScene( void ) +{ + if ( m_SceneEvents.Count() > 0 ) + { + CSceneEventInfo curScene = m_SceneEvents.Head(); + return ToHScript( (CBaseEntity*)(curScene.m_hSceneEntity.Get()) ); + } + else + { + return NULL; + } +} + +//-------------------------------------------------------------------------------------------------- +// Returns the script instance of the scene at the specified index, or null if index >= count +//-------------------------------------------------------------------------------------------------- +HSCRIPT CBaseFlex::ScriptGetSceneByIndex( int index ) +{ + if ( m_SceneEvents.IsValidIndex( index ) ) + { + CSceneEventInfo curScene = m_SceneEvents.Element( index ); + return ToHScript( (CBaseEntity*)(curScene.m_hSceneEntity.Get()) ); + } + else + { + return NULL; + } +} // FIXME: move to CBaseActor @@ -2201,8 +2375,13 @@ void CBaseFlex::DoBodyLean( void ) { m_vecPrevVelocity = vecDelta; float decay = ExponentialDecay( 0.5, 0.1, dt ); +#ifdef MAPBASE // From Alien Swarm SDK + m_vecShift = m_vecShift * decay; + m_vecLean = m_vecLean * decay; +#else m_vecShift = m_vecLean * decay; m_vecLean = m_vecShift * decay; +#endif } m_vecPrevOrigin = vecOrigin; diff --git a/src/game/server/baseflex.h b/src/game/server/baseflex.h index b9d3c43a..3e2f88af 100644 --- a/src/game/server/baseflex.h +++ b/src/game/server/baseflex.h @@ -19,7 +19,9 @@ struct flexsettinghdr_t; struct flexsetting_t; +#ifndef NEW_RESPONSE_SYSTEM class AI_Response; +#endif //----------------------------------------------------------------------------- // Purpose: A .vfe referenced by a scene during .vcd playback @@ -73,7 +75,11 @@ public: void RemoveChoreoScene( CChoreoScene *scene, bool canceled = false ); // Start the specifics of an scene event +#ifdef MAPBASE + virtual bool StartSceneEvent( CSceneEventInfo *info, CChoreoScene *scene, CChoreoEvent *event, CChoreoActor *actor, CBaseEntity *pTarget, CSceneEntity *pSceneEnt = NULL ); +#else virtual bool StartSceneEvent( CSceneEventInfo *info, CChoreoScene *scene, CChoreoEvent *event, CChoreoActor *actor, CBaseEntity *pTarget ); +#endif // Manipulation of events for the object // Should be called by think function to process all scene events @@ -92,7 +98,11 @@ public: virtual bool ClearSceneEvent( CSceneEventInfo *info, bool fastKill, bool canceled ); // Add the event to the queue for this actor +#ifdef MAPBASE + void AddSceneEvent( CChoreoScene *scene, CChoreoEvent *event, CBaseEntity *pTarget = NULL, CSceneEntity *pSceneEnt = NULL ); +#else void AddSceneEvent( CChoreoScene *scene, CChoreoEvent *event, CBaseEntity *pTarget = NULL ); +#endif // Remove the event from the queue for this actor void RemoveSceneEvent( CChoreoScene *scene, CChoreoEvent *event, bool fastKill ); @@ -117,10 +127,22 @@ public: void SentenceStop( void ) { EmitSound( "AI_BaseNPC.SentenceStop" ); } virtual float PlayScene( const char *pszScene, float flDelay = 0.0f, AI_Response *response = NULL, IRecipientFilter *filter = NULL ); +#ifdef MAPBASE + virtual float PlayAutoGeneratedSoundScene( const char *soundname, float flDelay = 0.0f, AI_Response *response = NULL, IRecipientFilter *filter = NULL ); +#else virtual float PlayAutoGeneratedSoundScene( const char *soundname ); +#endif + + // Returns the script instance of the scene entity associated with our oldest ("top level") scene event + virtual HSCRIPT ScriptGetOldestScene( void ); + virtual HSCRIPT ScriptGetSceneByIndex( int index ); virtual int GetSpecialDSP( void ) { return 0; } +#ifdef MAPBASE + virtual bool GetGameTextSpeechParams( hudtextparms_t ¶ms ); +#endif + protected: // For handling .vfe files // Search list, or add if not in list diff --git a/src/game/server/basemultiplayerplayer.cpp b/src/game/server/basemultiplayerplayer.cpp index 29adbe5b..04333096 100644 --- a/src/game/server/basemultiplayerplayer.cpp +++ b/src/game/server/basemultiplayerplayer.cpp @@ -28,6 +28,7 @@ CBaseMultiplayerPlayer::CBaseMultiplayerPlayer() CBaseMultiplayerPlayer::~CBaseMultiplayerPlayer() { m_pAchievementKV->deleteThis(); + delete m_pExpresser; } BEGIN_ENT_SCRIPTDESC( CBaseMultiplayerPlayer, CBasePlayer, "Multiplayer Player" ) @@ -94,7 +95,13 @@ bool CBaseMultiplayerPlayer::SpeakConcept( AI_Response &response, int iConcept ) { // Save the current concept. m_iCurrentConcept = iConcept; - return SpeakFindResponse( response, g_pszMPConcepts[iConcept] ); +#ifdef NEW_RESPONSE_SYSTEM + CAI_Concept concept(g_pszMPConcepts[iConcept]); + concept.SetSpeaker(this); + return FindResponse( response, concept ); +#else + return SpeakFindResponse( g_pszMPConcepts[iConcept] ); +#endif } //----------------------------------------------------------------------------- diff --git a/src/game/server/basemultiplayerplayer.h b/src/game/server/basemultiplayerplayer.h index ff41aa0b..14eaf518 100644 --- a/src/game/server/basemultiplayerplayer.h +++ b/src/game/server/basemultiplayerplayer.h @@ -29,7 +29,7 @@ public: virtual bool SpeakIfAllowed( AIConcept_t concept, const char *modifiers = NULL, char *pszOutResponseChosen = NULL, size_t bufsize = 0, IRecipientFilter *filter = NULL ); virtual IResponseSystem *GetResponseSystem(); - bool SpeakConcept( AI_Response& response, int iConcept ); + bool SpeakConcept( AI_Response &response, int iConcept ); virtual bool SpeakConceptIfAllowed( int iConcept, const char *modifiers = NULL, char *pszOutResponseChosen = NULL, size_t bufsize = 0, IRecipientFilter *filter = NULL ); virtual bool CanHearAndReadChatFrom( CBasePlayer *pPlayer ); diff --git a/src/game/server/bmodels.cpp b/src/game/server/bmodels.cpp index 3fa00f20..d8b15e0e 100644 --- a/src/game/server/bmodels.cpp +++ b/src/game/server/bmodels.cpp @@ -22,9 +22,7 @@ // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" -#define SF_BRUSH_ACCDCC 16// brush should accelerate and decelerate when toggled -#define SF_BRUSH_HURT 32// rotating brush that inflicts pain based on rotation speed -#define SF_ROTATING_NOT_SOLID 64 // some special rotating objects are not solid. +//Tony; moved the spawnflags to util.h to prevent more mistakes in the future. // =================== FUNC_WALL ============================================== class CFuncWall : public CBaseEntity @@ -458,6 +456,11 @@ protected: bool m_bSolidBsp; // Brush is SOLID_BSP +#ifdef MAPBASE + int m_iMinPitch = 30; // FANPITCHMIN + int m_iMaxPitch = 100; // FANPITCHMAX +#endif + public: Vector m_vecClientOrigin; QAngle m_vecClientAngles; @@ -480,6 +483,10 @@ BEGIN_DATADESC( CFuncRotating ) DEFINE_FIELD( m_angStart, FIELD_VECTOR ), DEFINE_FIELD( m_bStopAtStartPos, FIELD_BOOLEAN ), DEFINE_KEYFIELD( m_bSolidBsp, FIELD_BOOLEAN, "solidbsp" ), +#ifdef MAPBASE + DEFINE_KEYFIELD( m_iMinPitch, FIELD_INTEGER, "minpitch" ), + DEFINE_KEYFIELD( m_iMaxPitch, FIELD_INTEGER, "maxpitch" ), +#endif // Function Pointers DEFINE_FUNCTION( SpinUpMove ), @@ -831,8 +838,14 @@ void CFuncRotating::HurtTouch ( CBaseEntity *pOther ) } +#ifdef MAPBASE +// In Mapbase, use the keyvalues instead +#define FANPITCHMIN m_iMinPitch +#define FANPITCHMAX m_iMaxPitch +#else #define FANPITCHMIN 30 #define FANPITCHMAX 100 +#endif //----------------------------------------------------------------------------- @@ -1098,6 +1111,18 @@ void CFuncRotating::RotateMove( void ) { SetMoveDoneTime( 10 ); +#ifdef MAPBASE + QAngle angNormalizedAngles = GetLocalAngles(); + if (m_vecMoveAng.x) + angNormalizedAngles.x = AngleNormalize( angNormalizedAngles.x ); + if (m_vecMoveAng.y) + angNormalizedAngles.y = AngleNormalize( angNormalizedAngles.y ); + if (m_vecMoveAng.z) + angNormalizedAngles.z = AngleNormalize( angNormalizedAngles.z ); + + SetLocalAngles(angNormalizedAngles); +#endif + if ( m_bStopAtStartPos ) { SetMoveDoneTime( GetNextMoveInterval() ); @@ -1386,6 +1411,9 @@ public: void InputEnable( inputdata_t &inputdata ); void InputDisable( inputdata_t &inputdata ); +#ifdef MAPBASE + void InputSetFilter( inputdata_t &inputdata ); +#endif private: @@ -1400,10 +1428,17 @@ BEGIN_DATADESC( CFuncVPhysicsClip ) // Keyfields DEFINE_KEYFIELD( m_iFilterName, FIELD_STRING, "filtername" ), DEFINE_FIELD( m_hFilter, FIELD_EHANDLE ), +#ifdef MAPBASE + DEFINE_KEYFIELD( m_bDisabled, FIELD_BOOLEAN, "StartDisabled" ), +#else DEFINE_FIELD( m_bDisabled, FIELD_BOOLEAN ), +#endif DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ), DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ), +#ifdef MAPBASE + DEFINE_INPUTFUNC( FIELD_EHANDLE, "SetFilter", InputSetFilter ), +#endif END_DATADESC() @@ -1446,6 +1481,12 @@ bool CFuncVPhysicsClip::EntityPassesFilter( CBaseEntity *pOther ) if ( pFilter ) return pFilter->PassesFilter( this, pOther ); +#ifdef MAPBASE + // I couldn't figure out what else made this crash. The entity shouldn't be NULL. + if ( !pOther->VPhysicsGetObject() ) + return false; +#endif + if ( pOther->GetMoveType() == MOVETYPE_VPHYSICS && pOther->VPhysicsGetObject()->IsMoveable() ) return true; @@ -1469,3 +1510,19 @@ void CFuncVPhysicsClip::InputDisable( inputdata_t &inputdata ) VPhysicsGetObject()->EnableCollisions(false); m_bDisabled = true; } + +#ifdef MAPBASE +void CFuncVPhysicsClip::InputSetFilter( inputdata_t &inputdata ) +{ + if (inputdata.value.Entity()) + { + m_iFilterName = inputdata.value.Entity()->GetEntityName(); + m_hFilter = dynamic_cast(inputdata.value.Entity().Get()); + } + else + { + m_iFilterName = NULL_STRING; + m_hFilter = NULL; + } +} +#endif diff --git a/src/game/server/buttons.cpp b/src/game/server/buttons.cpp index e044dd84..9d8df267 100644 --- a/src/game/server/buttons.cpp +++ b/src/game/server/buttons.cpp @@ -898,6 +898,13 @@ void CRotButton::Spawn( void ) SetUse(&CRotButton::ButtonUse); +#ifdef MAPBASE + if (HasSpawnFlags(SF_BUTTON_LOCKED)) + { + m_bLocked = true; + } +#endif + // // If touching activates the button, set its touch function. // diff --git a/src/game/server/cbase.cpp b/src/game/server/cbase.cpp index e2c060b9..acff959c 100644 --- a/src/game/server/cbase.cpp +++ b/src/game/server/cbase.cpp @@ -85,6 +85,10 @@ OUTPUTS: #include "datacache/imdlcache.h" #include "env_debughistory.h" #include "fgdlib/entitydefs.h" +#ifdef MAPBASE +#include "mapbase/variant_tools.h" +#include "mapbase/matchers.h" +#endif #include "tier0/vprof.h" @@ -327,7 +331,12 @@ void CBaseEntityOutput::FireOutput(variant_t Value, CBaseEntity *pActivator, CBa // variant_t ValueOverride; ValueOverride.SetString( ev->m_iParameter ); +#ifdef MAPBASE + // I found this while making point_advanced_finder. FireOutput()'s own delay parameter doesn't work with...uh...parameters. + g_EventQueue.AddEvent( STRING(ev->m_iTarget), STRING(ev->m_iTargetInput), ValueOverride, ev->m_flDelay + fDelay, pActivator, pCaller, ev->m_iIDStamp ); +#else g_EventQueue.AddEvent( STRING(ev->m_iTarget), STRING(ev->m_iTargetInput), ValueOverride, ev->m_flDelay, pActivator, pCaller, ev->m_iIDStamp ); +#endif } if ( ev->m_flDelay ) @@ -348,7 +357,11 @@ void CBaseEntityOutput::FireOutput(variant_t Value, CBaseEntity *pActivator, CBa ev->m_flDelay, STRING(ev->m_iParameter) ); +#ifdef MAPBASE + CGMsg( 2, CON_GROUP_IO_SYSTEM, "%s", szBuffer ); +#else DevMsg( 2, "%s", szBuffer ); +#endif ADD_DEBUG_HISTORY( HISTORY_ENTITY_IO, szBuffer ); } else @@ -367,7 +380,11 @@ void CBaseEntityOutput::FireOutput(variant_t Value, CBaseEntity *pActivator, CBa STRING(ev->m_iTargetInput), STRING(ev->m_iParameter) ); +#ifdef MAPBASE + CGMsg( 2, CON_GROUP_IO_SYSTEM, "%s", szBuffer ); +#else DevMsg( 2, "%s", szBuffer ); +#endif ADD_DEBUG_HISTORY( HISTORY_ENTITY_IO, szBuffer ); } @@ -388,7 +405,12 @@ void CBaseEntityOutput::FireOutput(variant_t Value, CBaseEntity *pActivator, CBa { char szBuffer[256]; Q_snprintf( szBuffer, sizeof(szBuffer), "Removing from action list: (%s,%s) -> (%s,%s)\n", pCaller ? STRING(pCaller->m_iClassname) : "NULL", pCaller ? STRING(pCaller->GetEntityName()) : "NULL", STRING(ev->m_iTarget), STRING(ev->m_iTargetInput)); + +#ifdef MAPBASE + CGMsg( 2, CON_GROUP_IO_SYSTEM, "%s", szBuffer ); +#else DevMsg( 2, "%s", szBuffer ); +#endif ADD_DEBUG_HISTORY( HISTORY_ENTITY_IO, szBuffer ); bRemove = true; } @@ -464,6 +486,7 @@ void CBaseEntityOutput::RemoveEventAction( CEventAction *pEventAction ) } } + // save data description for the event queue BEGIN_SIMPLE_DATADESC( CBaseEntityOutput ) @@ -892,7 +915,12 @@ void CEventQueue::Dump( void ) //----------------------------------------------------------------------------- // Purpose: adds the action into the correct spot in the priority queue, targeting entity via string name //----------------------------------------------------------------------------- -void CEventQueue::AddEvent( const char *target, const char *targetInput, variant_t Value, float fireDelay, CBaseEntity *pActivator, CBaseEntity *pCaller, int outputID ) +#ifdef MAPBASE_VSCRIPT +int +#else +void +#endif +CEventQueue::AddEvent( const char *target, const char *targetInput, variant_t Value, float fireDelay, CBaseEntity *pActivator, CBaseEntity *pCaller, int outputID ) { // build the new event EventQueuePrioritizedEvent_t *newEvent = new EventQueuePrioritizedEvent_t; @@ -910,12 +938,22 @@ void CEventQueue::AddEvent( const char *target, const char *targetInput, variant newEvent->m_iOutputID = outputID; AddEvent( newEvent ); + +#ifdef MAPBASE_VSCRIPT + Assert( sizeof(EventQueuePrioritizedEvent_t*) == sizeof(int) ); + return reinterpret_cast(newEvent); // POINTER_TO_INT +#endif } //----------------------------------------------------------------------------- // Purpose: adds the action into the correct spot in the priority queue, targeting entity via pointer //----------------------------------------------------------------------------- -void CEventQueue::AddEvent( CBaseEntity *target, const char *targetInput, variant_t Value, float fireDelay, CBaseEntity *pActivator, CBaseEntity *pCaller, int outputID ) +#ifdef MAPBASE_VSCRIPT +int +#else +void +#endif +CEventQueue::AddEvent( CBaseEntity *target, const char *targetInput, variant_t Value, float fireDelay, CBaseEntity *pActivator, CBaseEntity *pCaller, int outputID ) { // build the new event EventQueuePrioritizedEvent_t *newEvent = new EventQueuePrioritizedEvent_t; @@ -933,6 +971,11 @@ void CEventQueue::AddEvent( CBaseEntity *target, const char *targetInput, varian newEvent->m_iOutputID = outputID; AddEvent( newEvent ); + +#ifdef MAPBASE_VSCRIPT + Assert( sizeof(EventQueuePrioritizedEvent_t*) == sizeof(int) ); + return reinterpret_cast(newEvent); // POINTER_TO_INT +#endif } void CEventQueue::AddEvent( CBaseEntity *target, const char *action, float fireDelay, CBaseEntity *pActivator, CBaseEntity *pCaller, int outputID ) @@ -1009,17 +1052,32 @@ void CEventQueue::ServiceEvents( void ) { // In the context the event, the searching entity is also the caller CBaseEntity *pSearchingEntity = pe->m_pCaller; - CBaseEntity *target = NULL; - while ( 1 ) +#ifdef MAPBASE + // This is a hack to access the entity from a FIELD_EHANDLE input + if ( FStrEq( STRING( pe->m_iTarget ), "!output" ) ) { - target = gEntList.FindEntityByName( target, pe->m_iTarget, pSearchingEntity, pe->m_pActivator, pe->m_pCaller ); - if ( !target ) - break; + pe->m_VariantValue.Convert( FIELD_EHANDLE ); + CBaseEntity *target = pe->m_VariantValue.Entity(); // pump the action into the target - target->AcceptInput( STRING(pe->m_iTargetInput), pe->m_pActivator, pe->m_pCaller, pe->m_VariantValue, pe->m_iOutputID ); + target->AcceptInput( STRING( pe->m_iTargetInput ), pe->m_pActivator, pe->m_pCaller, pe->m_VariantValue, pe->m_iOutputID ); targetFound = true; } + else +#endif + { + CBaseEntity *target = NULL; + while ( 1 ) + { + target = gEntList.FindEntityByName( target, pe->m_iTarget, pSearchingEntity, pe->m_pActivator, pe->m_pCaller ); + if ( !target ) + break; + + // pump the action into the target + target->AcceptInput( STRING(pe->m_iTargetInput), pe->m_pActivator, pe->m_pCaller, pe->m_VariantValue, pe->m_iOutputID ); + targetFound = true; + } + } } // direct pointer @@ -1061,7 +1119,11 @@ void CEventQueue::ServiceEvents( void ) char szBuffer[256]; Q_snprintf( szBuffer, sizeof(szBuffer), "unhandled input: (%s) -> (%s), from (%s,%s); target entity not found\n", STRING(pe->m_iTargetInput), STRING(pe->m_iTarget), pClass, pName ); +#ifdef MAPBASE + CGMsg( 2, CON_GROUP_IO_SYSTEM, "%s", szBuffer ); +#else DevMsg( 2, "%s", szBuffer ); +#endif ADD_DEBUG_HISTORY( HISTORY_ENTITY_IO, szBuffer ); } @@ -1209,6 +1271,77 @@ void ServiceEventQueue( void ) } +#ifdef MAPBASE_VSCRIPT +//----------------------------------------------------------------------------- +// Remove pending events on entity by input. +// +// Also removes events that were targeted with their debug name (classname when unnamed). +// E.g. CancelEventsByInput( pRelay, "Trigger" ) removes all pending logic_relay "Trigger" events. +//----------------------------------------------------------------------------- +void CEventQueue::CancelEventsByInput( CBaseEntity *pTarget, const char *szInput ) +{ + if ( !pTarget ) + return; + + string_t iszDebugName = MAKE_STRING( pTarget->GetDebugName() ); + EventQueuePrioritizedEvent_t *pCur = m_Events.m_pNext; + + while ( pCur ) + { + bool bRemove = false; + + if ( pTarget == pCur->m_pEntTarget || pCur->m_iTarget == iszDebugName ) + { + if ( !V_strncmp( STRING(pCur->m_iTargetInput), szInput, strlen(szInput) ) ) + { + bRemove = true; + } + } + + EventQueuePrioritizedEvent_t *pPrev = pCur; + pCur = pCur->m_pNext; + + if ( bRemove ) + { + RemoveEvent(pPrev); + delete pPrev; + } + } +} + +bool CEventQueue::RemoveEvent( int event ) +{ + EventQueuePrioritizedEvent_t *pe = reinterpret_cast(event); // INT_TO_POINTER + + for ( EventQueuePrioritizedEvent_t *pCur = m_Events.m_pNext; pCur; pCur = pCur->m_pNext ) + { + if ( pCur == pe ) + { + RemoveEvent(pCur); + delete pCur; + return true; + } + } + + return false; +} + +float CEventQueue::GetTimeLeft( int event ) +{ + EventQueuePrioritizedEvent_t *pe = reinterpret_cast(event); // INT_TO_POINTER + + for ( EventQueuePrioritizedEvent_t *pCur = m_Events.m_pNext; pCur; pCur = pCur->m_pNext ) + { + if ( pCur == pe ) + { + return (pCur->m_flFireTime - gpGlobals->curtime); + } + } + + return 0.f; +} +#endif // MAPBASE_VSCRIPT + // save data description for the event queue BEGIN_SIMPLE_DATADESC( CEventQueue ) @@ -1339,6 +1472,23 @@ void variant_t::Set( fieldtype_t ftype, void *data ) break; } +#ifdef MAPBASE + // There's this output class called COutputVariant which could output any data type, like a FIELD_INPUT input function. + // Well...nobody added support for it. It was there, but it wasn't functional. + // Mapbase adds support for it so you could variant your outputs as you please. + case FIELD_INPUT: + { + variant_t *variant = (variant_t*)data; + + // Pretty much just copying over its stored value. + fieldType = variant->FieldType(); + variant->SetOther(data); + + Set(fieldType, data); + break; + } +#endif + case FIELD_EHANDLE: eVal = *((EHANDLE *)data); break; case FIELD_CLASSPTR: eVal = *((CBaseEntity **)data); break; case FIELD_VOID: @@ -1379,6 +1529,10 @@ void variant_t::SetOther( void *data ) } } +#ifdef MAPBASE +// This way we don't have to use string comparisons when reading failed conversions +static const char *g_szNoConversion = "No conversion to string"; +#endif //----------------------------------------------------------------------------- // Purpose: Converts the variant to a new type. This function defines which I/O @@ -1411,6 +1565,24 @@ bool variant_t::Convert( fieldtype_t newType ) return true; } +#ifdef MAPBASE + if (newType == FIELD_STRING) + { + // I got a conversion error when I tried to convert int to string. I'm actually quite baffled. + // Was that case really not handled before? Did I do something that overrode something that already did this? + const char *szString = ToString(); + + // g_szNoConversion is returned in ToString() when we can't convert to a string, + // so this is safe and it lets us get away with a pointer comparison. + if (szString != g_szNoConversion) + { + SetString(AllocPooledString(szString)); + return true; + } + return false; + } +#endif + switch ( fieldType ) { case FIELD_INTEGER: @@ -1528,8 +1700,14 @@ bool variant_t::Convert( fieldtype_t newType ) CBaseEntity *ent = NULL; if ( iszVal != NULL_STRING ) { +#ifdef MAPBASE + // We search by both entity name and class name now. + // We also have an entirely new version of Convert specifically for !activators on FIELD_EHANDLE. + ent = gEntList.FindEntityGeneric( NULL, STRING(iszVal) ); +#else // FIXME: do we need to pass an activator in here? ent = gEntList.FindEntityByName( NULL, iszVal ); +#endif } SetEntity( ent ); return true; @@ -1539,6 +1717,7 @@ bool variant_t::Convert( fieldtype_t newType ) break; } +#ifndef MAPBASE // ToString() above handles this case FIELD_EHANDLE: { switch ( newType ) @@ -1556,12 +1735,64 @@ bool variant_t::Convert( fieldtype_t newType ) } break; } +#endif + +#ifdef MAPBASE + case FIELD_VOID: + { + // Many fields already turn into some equivalent of "NULL" when given a null string_t. + // This takes advantage of that and allows FIELD_VOID to be converted to more than just empty strings. + SetString(NULL_STRING); + return Convert(newType); + } +#endif } // invalid conversion return false; } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Only for when something like !activator needs to become a FIELD_EHANDLE, or when that's a possibility. +//----------------------------------------------------------------------------- +bool variant_t::Convert( fieldtype_t newType, CBaseEntity *pSelf, CBaseEntity *pActivator, CBaseEntity *pCaller ) +{ + // Support for turning !activator, !caller, and !self into a FIELD_EHANDLE. + // Extremely necessary. + if (newType == FIELD_EHANDLE) + { + if (newType == fieldType) + return true; + + CBaseEntity *ent = NULL; + if (iszVal != NULL_STRING) + { + ent = gEntList.FindEntityGeneric(NULL, STRING(iszVal), pSelf, pActivator, pCaller); + } + SetEntity(ent); + return true; + } + +#if 0 // This was scrapped almost immediately. See the Trello card for details. + // Serves as a way of converting the name of the !activator, !caller, or !self into a string + // without passing the text "!activator" and stuff. + else if (fieldType == FIELD_STRING && STRING(iszVal)[0] == '&') + { + const char *val = STRING(iszVal) + 1; + + #define GetRealName(string, ent) if (FStrEq(val, string)) { if (ent) {SetString(ent->GetEntityName());} return true; } + + GetRealName("!activator", pActivator) + else GetRealName("!caller", pCaller) + else GetRealName("!self", pSelf) + } +#endif + + return Convert(newType); +} +#endif + //----------------------------------------------------------------------------- // Purpose: All types must be able to display as strings for debugging purposes. @@ -1627,13 +1858,22 @@ const char *variant_t::ToString( void ) const case FIELD_EHANDLE: { +#ifdef MAPBASE + // This is a really bad idea. + const char *pszName = (Entity()) ? Entity()->GetDebugName() : "<>"; +#else const char *pszName = (Entity()) ? STRING(Entity()->GetEntityName()) : "<>"; +#endif Q_strncpy( szBuf, pszName, 512 ); return (szBuf); } } +#ifdef MAPBASE + return g_szNoConversion; +#else return("No conversion to string"); +#endif } #define classNameTypedef variant_t // to satisfy DEFINE... macros @@ -1841,6 +2081,21 @@ class CVariantSaveDataOps : public CDefSaveRestoreOps // Don't no how to. This is okay, since objects of this type // are always born clean before restore, and not reused } + +#ifdef MAPBASE + // Parses a keyvalue string into a variant_t. + // We could just turn it into a string since variant_t can convert it later, but this keyvalue is probably a variant_t for a reason, + // meaning it might use strings and numbers completely differently without converting them. + // As a result, we try to read it to figure out what type it is. + virtual bool Parse( const SaveRestoreFieldInfo_t &fieldInfo, char const* szValue ) + { + variant_t *var = (variant_t*)fieldInfo.pField; + + *var = Variant_Parse(szValue); + + return true; + } +#endif }; CVariantSaveDataOps g_VariantSaveDataOps; diff --git a/src/game/server/cbase.h b/src/game/server/cbase.h index 0d56ec3b..2b00af39 100644 --- a/src/game/server/cbase.h +++ b/src/game/server/cbase.h @@ -80,6 +80,9 @@ #include "baseentity_shared.h" #include "basetoggle.h" #include "igameevents.h" +#ifdef MAPBASE +#include "tier1/mapbase_con_groups.h" +#endif // saverestore.h declarations class ISave; @@ -101,6 +104,13 @@ extern void FireTargets( const char *targetName, CBaseEntity *pActivator, CBaseE #define MAX_OLD_ENEMIES 4 // how many old enemies to remember +#ifdef MAPBASE +// Use the model keyvalue if it is defined +#define DefaultOrCustomModel(defaultModel) GetModelName() != NULL_STRING ? STRING(GetModelName()) : defaultModel +#else +#define DefaultOrCustomModel() defaultModel +#endif + // used by suit voice to indicate damage sustained and repaired type to player enum diff --git a/src/game/server/client.cpp b/src/game/server/client.cpp index ae184025..592ea48f 100644 --- a/src/game/server/client.cpp +++ b/src/game/server/client.cpp @@ -45,6 +45,10 @@ #include "weapon_physcannon.h" #endif +#ifdef MAPBASE +#include "fmtstr.h" +#endif + // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" @@ -414,6 +418,16 @@ void ClientPrecache( void ) CBaseEntity::PrecacheScriptSound( "Bounce.Shell" ); CBaseEntity::PrecacheScriptSound( "Bounce.Concrete" ); +#ifdef MAPBASE + // Game Instructor sounds + CBaseEntity::PrecacheScriptSound( "Instructor.LessonStart" ); + CBaseEntity::PrecacheScriptSound( "Instructor.ImportantLessonStart" ); + + // TODO: Does sv_pure cover this? This is from the ASW SDK to prevent people from making simple scripted wall hacks + //engine->ForceExactFile( "scripts/instructor_lessons.txt" ); + //engine->ForceExactFile( "scripts/mod_lessons.txt" ); +#endif + ClientGamePrecache(); } @@ -1615,6 +1629,32 @@ void ClientCommand( CBasePlayer *pPlayer, const CCommand &args ) { if ( !g_pGameRules->ClientCommand( pPlayer, args ) ) { +#ifdef MAPBASE_VSCRIPT + // Console command hook for VScript + if ( pPlayer->m_ScriptScope.IsInitialized() ) + { + ScriptVariant_t functionReturn; + g_pScriptVM->SetValue( "command", ScriptVariant_t( pCmd ) ); + + ScriptVariant_t varTable; + g_pScriptVM->CreateTable( varTable ); + HSCRIPT hTable = varTable.m_hScript; + for ( int i = 0; i < args.ArgC(); i++ ) + { + g_pScriptVM->SetValue( hTable, CNumStr( i ), ScriptVariant_t( args[i] ) ); + } + g_pScriptVM->SetValue( "args", varTable ); + + pPlayer->CallScriptFunction( "ClientCommand", &functionReturn ); + + g_pScriptVM->ClearValue( "command" ); + g_pScriptVM->ClearValue( "args" ); + g_pScriptVM->ReleaseValue( varTable ); + + if (functionReturn.m_bool) + return; + } +#endif if ( Q_strlen( pCmd ) > 128 ) { ClientPrint( pPlayer, HUD_PRINTCONSOLE, "Console command too long.\n" ); diff --git a/src/game/server/colorcorrection.cpp b/src/game/server/colorcorrection.cpp index 16d01222..69e6db85 100644 --- a/src/game/server/colorcorrection.cpp +++ b/src/game/server/colorcorrection.cpp @@ -5,9 +5,8 @@ // $NoKeywords: $ //===========================================================================// -#include - #include "cbase.h" +#include "colorcorrection.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" @@ -16,64 +15,6 @@ static const char *s_pFadeInContextThink = "ColorCorrectionFadeInThink"; static const char *s_pFadeOutContextThink = "ColorCorrectionFadeOutThink"; - -//------------------------------------------------------------------------------ -// FIXME: This really should inherit from something more lightweight -//------------------------------------------------------------------------------ - - -//------------------------------------------------------------------------------ -// Purpose : Shadow control entity -//------------------------------------------------------------------------------ -class CColorCorrection : public CBaseEntity -{ - DECLARE_CLASS( CColorCorrection, CBaseEntity ); -public: - DECLARE_SERVERCLASS(); - DECLARE_DATADESC(); - - CColorCorrection(); - - void Spawn( void ); - int UpdateTransmitState(); - void Activate( void ); - - virtual int ObjectCaps( void ) { return BaseClass::ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } - - // Inputs - void InputEnable( inputdata_t &inputdata ); - void InputDisable( inputdata_t &inputdata ); - void InputSetFadeInDuration ( inputdata_t &inputdata ); - void InputSetFadeOutDuration ( inputdata_t &inputdata ); - -private: - void FadeIn ( void ); - void FadeOut ( void ); - - void FadeInThink( void ); // Fades lookup weight from Cur->MaxWeight - void FadeOutThink( void ); // Fades lookup weight from CurWeight->0.0 - - - - float m_flFadeInDuration; // Duration for a full 0->MaxWeight transition - float m_flFadeOutDuration; // Duration for a full Max->0 transition - float m_flStartFadeInWeight; - float m_flStartFadeOutWeight; - float m_flTimeStartFadeIn; - float m_flTimeStartFadeOut; - - float m_flMaxWeight; - - bool m_bStartDisabled; - CNetworkVar( bool, m_bEnabled ); - - CNetworkVar( float, m_MinFalloff ); - CNetworkVar( float, m_MaxFalloff ); - CNetworkVar( float, m_flCurWeight ); - CNetworkString( m_netlookupFilename, MAX_PATH ); - - string_t m_lookupFilename; -}; LINK_ENTITY_TO_CLASS(color_correction, CColorCorrection); @@ -97,12 +38,19 @@ BEGIN_DATADESC( CColorCorrection ) DEFINE_KEYFIELD( m_bEnabled, FIELD_BOOLEAN, "enabled" ), DEFINE_KEYFIELD( m_bStartDisabled, FIELD_BOOLEAN, "StartDisabled" ), +#ifdef MAPBASE // From Alien Swarm SDK + DEFINE_KEYFIELD( m_bExclusive, FIELD_BOOLEAN, "exclusive" ), +#endif // DEFINE_ARRAY( m_netlookupFilename, FIELD_CHARACTER, MAX_PATH ), DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ), DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ), DEFINE_INPUTFUNC( FIELD_FLOAT, "SetFadeInDuration", InputSetFadeInDuration ), DEFINE_INPUTFUNC( FIELD_FLOAT, "SetFadeOutDuration", InputSetFadeOutDuration ), +#ifdef MAPBASE + DEFINE_INPUTFUNC( FIELD_FLOAT, "SetMinFalloff", InputSetMinFalloff ), + DEFINE_INPUTFUNC( FIELD_FLOAT, "SetMaxFalloff", InputSetMaxFalloff ), +#endif END_DATADESC() @@ -112,8 +60,18 @@ IMPLEMENT_SERVERCLASS_ST_NOBASE(CColorCorrection, DT_ColorCorrection) SendPropFloat( SENDINFO(m_MinFalloff) ), SendPropFloat( SENDINFO(m_MaxFalloff) ), SendPropFloat( SENDINFO(m_flCurWeight) ), +#ifdef MAPBASE // From Alien Swarm SDK + SendPropFloat( SENDINFO(m_flMaxWeight) ), + SendPropFloat( SENDINFO(m_flFadeInDuration) ), + SendPropFloat( SENDINFO(m_flFadeOutDuration) ), +#endif SendPropString( SENDINFO(m_netlookupFilename) ), SendPropBool( SENDINFO(m_bEnabled) ), +#ifdef MAPBASE // From Alien Swarm SDK + SendPropBool( SENDINFO(m_bMaster) ), + SendPropBool( SENDINFO(m_bClientSide) ), + SendPropBool( SENDINFO(m_bExclusive) ), +#endif END_SEND_TABLE() @@ -132,6 +90,11 @@ CColorCorrection::CColorCorrection() : BaseClass() m_flTimeStartFadeOut = 0.0f; m_netlookupFilename.GetForModify()[0] = 0; m_lookupFilename = NULL_STRING; +#ifdef MAPBASE // From Alien Swarm SDK + m_bMaster = false; + m_bClientSide = false; + m_bExclusive = false; +#endif } @@ -175,6 +138,11 @@ void CColorCorrection::Activate( void ) { BaseClass::Activate(); +#ifdef MAPBASE // From Alien Swarm SDK (moved to Activate() for save/restore support) + m_bMaster = IsMaster(); + m_bClientSide = IsClientSide(); +#endif + Q_strncpy( m_netlookupFilename.GetForModify(), STRING( m_lookupFilename ), MAX_PATH ); } @@ -183,6 +151,11 @@ void CColorCorrection::Activate( void ) //----------------------------------------------------------------------------- void CColorCorrection::FadeIn ( void ) { +#ifdef MAPBASE // From Alien Swarm SDK + if ( m_bClientSide || ( m_bEnabled && m_flCurWeight >= m_flMaxWeight ) ) + return; +#endif + m_bEnabled = true; m_flTimeStartFadeIn = gpGlobals->curtime; m_flStartFadeInWeight = m_flCurWeight; @@ -194,6 +167,11 @@ void CColorCorrection::FadeIn ( void ) //----------------------------------------------------------------------------- void CColorCorrection::FadeOut ( void ) { +#ifdef MAPBASE // From Alien Swarm SDK + if ( m_bClientSide || ( !m_bEnabled && m_flCurWeight <= 0.0f ) ) + return; +#endif + m_bEnabled = false; m_flTimeStartFadeOut = gpGlobals->curtime; m_flStartFadeOutWeight = m_flCurWeight; @@ -230,7 +208,11 @@ void CColorCorrection::FadeInThink( void ) flFadeRatio = clamp ( flFadeRatio, 0.0f, 1.0f ); m_flStartFadeInWeight = clamp ( m_flStartFadeInWeight, 0.0f, 1.0f ); +#ifdef MAPBASE + m_flCurWeight = Lerp( flFadeRatio, m_flStartFadeInWeight, m_flMaxWeight.Get() ); +#else m_flCurWeight = Lerp( flFadeRatio, m_flStartFadeInWeight, m_flMaxWeight ); +#endif SetNextThink( gpGlobals->curtime + COLOR_CORRECTION_ENT_THINK_RATE, s_pFadeInContextThink ); } @@ -312,3 +294,94 @@ void CColorCorrection::InputSetFadeOutDuration( inputdata_t& inputdata ) { m_flFadeOutDuration = inputdata.value.Float(); } + +#ifdef MAPBASE +void CColorCorrection::InputSetMinFalloff( inputdata_t& inputdata ) +{ + m_MinFalloff = inputdata.value.Float(); +} + +void CColorCorrection::InputSetMaxFalloff( inputdata_t& inputdata ) +{ + m_MaxFalloff = inputdata.value.Float(); +} +#endif + +#ifdef MAPBASE // From Alien Swarm SDK +CColorCorrectionSystem s_ColorCorrectionSystem( "ColorCorrectionSystem" ); + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CColorCorrectionSystem *ColorCorrectionSystem( void ) +{ + return &s_ColorCorrectionSystem; +} + + +//----------------------------------------------------------------------------- +// Purpose: Clear out the fog controller. +//----------------------------------------------------------------------------- +void CColorCorrectionSystem::LevelInitPreEntity( void ) +{ + m_hMasterController = NULL; + ListenForGameEvent( "round_start" ); +} + +//----------------------------------------------------------------------------- +// Purpose: Find the master controller. If no controller is +// set as Master, use the first controller found. +//----------------------------------------------------------------------------- +void CColorCorrectionSystem::InitMasterController( void ) +{ + CColorCorrection *pColorCorrection = NULL; + do + { + pColorCorrection = static_cast( gEntList.FindEntityByClassname( pColorCorrection, "color_correction" ) ); + if ( pColorCorrection ) + { + if ( m_hMasterController.Get() == NULL ) + { + m_hMasterController = pColorCorrection; + } + else + { + if ( pColorCorrection->IsMaster() ) + { + m_hMasterController = pColorCorrection; + } + } + } + } while ( pColorCorrection ); +} + +//----------------------------------------------------------------------------- +// Purpose: On a multiplayer map restart, re-find the master controller. +//----------------------------------------------------------------------------- +void CColorCorrectionSystem::FireGameEvent( IGameEvent *pEvent ) +{ + InitMasterController(); +} + +//----------------------------------------------------------------------------- +// Purpose: On level load find the master fog controller. If no controller is +// set as Master, use the first fog controller found. +//----------------------------------------------------------------------------- +void CColorCorrectionSystem::LevelInitPostEntity( void ) +{ + InitMasterController(); + + // HACK: Singleplayer games don't get a call to CBasePlayer::Spawn on level transitions. + // CBasePlayer::Activate is called before this is called so that's too soon to set up the fog controller. + // We don't have a hook similar to Activate that happens after LevelInitPostEntity + // is called, or we could just do this in the player itself. + if ( gpGlobals->maxClients == 1 ) + { + CBasePlayer *pPlayer = UTIL_GetLocalPlayer(); + if ( pPlayer && ( pPlayer->m_hColorCorrectionCtrl.Get() == NULL ) ) + { + pPlayer->InitColorCorrectionController(); + } + } +} +#endif diff --git a/src/game/server/colorcorrection.h b/src/game/server/colorcorrection.h new file mode 100644 index 00000000..2e96eb7c --- /dev/null +++ b/src/game/server/colorcorrection.h @@ -0,0 +1,147 @@ +//========= Mapbase - https://github.com/mapbase-source/source-sdk-2013 ============// +// +// Note that this header exists in the Alien Swarm SDK, but not in stock Source SDK 2013. +// Although technically a new Mapbase file, it only serves to move otherwise identical code, +// so most code and repo conventions will pretend it was always there. +// +// -------------------------------------------------------------------- +// +// Purpose: Color correction entity. +// +//=============================================================================// + +#ifndef COLOR_CORRECTION_H +#define COLOR_CORRECTION_H +#ifdef _WIN32 +#pragma once +#endif + +#include +#include "cbase.h" +#ifdef MAPBASE // From Alien Swarm SDK +#include "GameEventListener.h" + +// Spawn Flags +#define SF_COLORCORRECTION_MASTER 0x0001 +#define SF_COLORCORRECTION_CLIENTSIDE 0x0002 +#endif + +//------------------------------------------------------------------------------ +// FIXME: This really should inherit from something more lightweight +//------------------------------------------------------------------------------ + + +//------------------------------------------------------------------------------ +// Purpose : Shadow control entity +//------------------------------------------------------------------------------ +class CColorCorrection : public CBaseEntity +{ + DECLARE_CLASS( CColorCorrection, CBaseEntity ); +public: + DECLARE_SERVERCLASS(); + DECLARE_DATADESC(); + + CColorCorrection(); + + void Spawn( void ); + int UpdateTransmitState(); + void Activate( void ); + + virtual int ObjectCaps( void ) { return BaseClass::ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + +#ifdef MAPBASE // From Alien Swarm SDK + bool IsMaster( void ) const { return HasSpawnFlags( SF_COLORCORRECTION_MASTER ); } + + bool IsClientSide( void ) const { return HasSpawnFlags( SF_COLORCORRECTION_CLIENTSIDE ); } + + bool IsExclusive( void ) const { return m_bExclusive; } +#endif + + // Inputs + void InputEnable( inputdata_t &inputdata ); + void InputDisable( inputdata_t &inputdata ); + void InputSetFadeInDuration ( inputdata_t &inputdata ); + void InputSetFadeOutDuration ( inputdata_t &inputdata ); +#ifdef MAPBASE + void InputSetMinFalloff( inputdata_t &inputdata ); + void InputSetMaxFalloff( inputdata_t &inputdata ); +#endif + +private: + void FadeIn ( void ); + void FadeOut ( void ); + + void FadeInThink( void ); // Fades lookup weight from Cur->MaxWeight + void FadeOutThink( void ); // Fades lookup weight from CurWeight->0.0 + + + +#ifdef MAPBASE // From Alien Swarm SDK + CNetworkVar( float, m_flFadeInDuration ); // Duration for a full 0->MaxWeight transition + CNetworkVar( float, m_flFadeOutDuration ); // Duration for a full Max->0 transition +#else + float m_flFadeInDuration; // Duration for a full 0->MaxWeight transition + float m_flFadeOutDuration; // Duration for a full Max->0 transition +#endif + float m_flStartFadeInWeight; + float m_flStartFadeOutWeight; + float m_flTimeStartFadeIn; + float m_flTimeStartFadeOut; + +#ifdef MAPBASE // From Alien Swarm SDK + CNetworkVar( float, m_flMaxWeight ); +#else + float m_flMaxWeight; +#endif + + bool m_bStartDisabled; + CNetworkVar( bool, m_bEnabled ); +#ifdef MAPBASE // From Alien Swarm SDK + CNetworkVar( bool, m_bMaster ); + CNetworkVar( bool, m_bClientSide ); + CNetworkVar( bool, m_bExclusive ); +#endif + + CNetworkVar( float, m_MinFalloff ); + CNetworkVar( float, m_MaxFalloff ); + CNetworkVar( float, m_flCurWeight ); + CNetworkString( m_netlookupFilename, MAX_PATH ); + + string_t m_lookupFilename; +}; + +#ifdef MAPBASE // From Alien Swarm SDK +//============================================================================= +// +// ColorCorrection Controller System. Just a place to store a master controller +// +class CColorCorrectionSystem : public CAutoGameSystem, public CGameEventListener +{ +public: + + // Creation/Init. + CColorCorrectionSystem( char const *name ) : CAutoGameSystem( name ) + { + m_hMasterController = NULL; + } + + ~CColorCorrectionSystem() + { + m_hMasterController = NULL; + } + + virtual void LevelInitPreEntity(); + virtual void LevelInitPostEntity(); + virtual void FireGameEvent( IGameEvent *pEvent ); + CColorCorrection *GetMasterColorCorrection( void ) { return m_hMasterController; } + +private: + + void InitMasterController( void ); + CHandle< CColorCorrection > m_hMasterController; +}; + +CColorCorrectionSystem *ColorCorrectionSystem( void ); +#endif + +#endif // COLOR_CORRECTION_H diff --git a/src/game/server/colorcorrectionvolume.cpp b/src/game/server/colorcorrectionvolume.cpp index a56cd533..abc55d75 100644 --- a/src/game/server/colorcorrectionvolume.cpp +++ b/src/game/server/colorcorrectionvolume.cpp @@ -48,7 +48,11 @@ public: private: +#ifdef MAPBASE // From Alien Swarm SDK + CNetworkVar( bool, m_bEnabled ); +#else bool m_bEnabled; +#endif bool m_bStartDisabled; CNetworkVar( float, m_Weight ); @@ -61,7 +65,11 @@ private: float m_LastExitWeight; float m_LastExitTime; +#ifdef MAPBASE // From Alien Swarm SDK + CNetworkVar( float, m_FadeDuration ); +#else float m_FadeDuration; +#endif }; LINK_ENTITY_TO_CLASS(color_correction_volume, CColorCorrectionVolume); @@ -90,6 +98,11 @@ END_DATADESC() IMPLEMENT_SERVERCLASS_ST_NOBASE(CColorCorrectionVolume, DT_ColorCorrectionVolume) +#ifdef MAPBASE // From Alien Swarm SDK + SendPropBool( SENDINFO(m_bEnabled) ), + SendPropFloat( SENDINFO(m_MaxWeight) ), + SendPropFloat( SENDINFO(m_FadeDuration) ), +#endif SendPropFloat( SENDINFO(m_Weight) ), SendPropString( SENDINFO(m_lookupFilename) ), END_SEND_TABLE() diff --git a/src/game/server/effects.cpp b/src/game/server/effects.cpp index af7e163d..d4cb9ff0 100644 --- a/src/game/server/effects.cpp +++ b/src/game/server/effects.cpp @@ -29,11 +29,19 @@ #include "Sprite.h" #include "precipitation_shared.h" #include "shot_manipulator.h" +#ifdef MAPBASE +#include "point_template.h" +#include "TemplateEntities.h" +#include "mapentities_shared.h" +#endif // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" #define SF_FUNNEL_REVERSE 1 // funnel effect repels particles instead of attracting them. +#ifdef MAPBASE +#define SF_FUNNEL_DONT_REMOVE 2 +#endif #define SF_GIBSHOOTER_REPEATABLE (1<<0) // allows a gibshooter to be refired #define SF_SHOOTER_FLAMING (1<<1) // gib is on fire @@ -536,7 +544,11 @@ CBaseEntity *CGibShooter::SpawnGib( const Vector &vecShootDir, float flSpeed ) { // UNDONE: Assume a mass of 200 for now Vector force = vecShootDir * flSpeed * 200; +#ifdef MAPBASE + return CreateRagGib( STRING( GetModelName() ), GetAbsOrigin(), GetAbsAngles(), force, m_flGibLife, HasSpawnFlags(SF_SHOOTER_FLAMING) ); +#else return CreateRagGib( STRING( GetModelName() ), GetAbsOrigin(), GetAbsAngles(), force, m_flGibLife ); +#endif } case GIB_SIMULATE_PHYSICS: @@ -1246,6 +1258,10 @@ public: void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); int m_iSprite; // Don't save, precache +#ifdef MAPBASE + // This unfortunately doesn't work with the way the effect is set up + //string_t m_iszSprite; +#endif }; LINK_ENTITY_TO_CLASS( env_funnel, CEnvFunnel ); @@ -1255,6 +1271,10 @@ LINK_ENTITY_TO_CLASS( env_funnel, CEnvFunnel ); //--------------------------------------------------------- BEGIN_DATADESC( CEnvFunnel ) +#ifdef MAPBASE + //DEFINE_KEYFIELD( m_iszSprite, FIELD_STRING, "Sprite" ), + DEFINE_INPUTFUNC( FIELD_VOID, "Activate", InputUse ), +#endif // DEFINE_FIELD( m_iSprite, FIELD_INTEGER ), END_DATADESC() @@ -1263,7 +1283,15 @@ END_DATADESC() void CEnvFunnel::Precache ( void ) { +#ifdef MAPBASE + //if (m_iszSprite == NULL_STRING) + // m_iszSprite = AllocPooledString("sprites/flare6.vmt"); + + //m_iSprite = PrecacheModel(STRING(m_iszSprite)); m_iSprite = PrecacheModel ( "sprites/flare6.vmt" ); +#else + m_iSprite = PrecacheModel ( "sprites/flare6.vmt" ); +#endif } void CEnvFunnel::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) @@ -1272,6 +1300,10 @@ void CEnvFunnel::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE us te->LargeFunnel( filter, 0.0, &GetAbsOrigin(), m_iSprite, HasSpawnFlags( SF_FUNNEL_REVERSE ) ? 1 : 0 ); +#ifdef MAPBASE + if (HasSpawnFlags(SF_FUNNEL_DONT_REMOVE)) + return; +#endif SetThink( &CEnvFunnel::SUB_Remove ); SetNextThink( gpGlobals->curtime ); } @@ -1480,6 +1512,7 @@ public: DECLARE_SERVERCLASS(); CPrecipitation(); + int UpdateTransmitState(); void Spawn( void ); CNetworkVar( PrecipitationType_t, m_nPrecipType ); @@ -1493,7 +1526,10 @@ END_DATADESC() // Just send the normal entity stuff IMPLEMENT_SERVERCLASS_ST( CPrecipitation, DT_Precipitation) - SendPropInt( SENDINFO( m_nPrecipType ), Q_log2( NUM_PRECIPITATION_TYPES ) + 1, SPROP_UNSIGNED ) + SendPropInt( SENDINFO( m_nPrecipType ), Q_log2( NUM_PRECIPITATION_TYPES ) + 1, SPROP_UNSIGNED ), +#ifdef MAPBASE + SendPropInt( SENDINFO( m_spawnflags ), 2, SPROP_UNSIGNED ), +#endif END_SEND_TABLE() @@ -1502,17 +1538,35 @@ CPrecipitation::CPrecipitation() m_nPrecipType = PRECIPITATION_TYPE_RAIN; // default to rain. } +int CPrecipitation::UpdateTransmitState() +{ + return SetTransmitState( FL_EDICT_ALWAYS ); +} + void CPrecipitation::Spawn( void ) { + //SetTransmitState( FL_EDICT_ALWAYS ); + SetTransmitState( FL_EDICT_PVSCHECK ); + PrecacheMaterial( "effects/fleck_ash1" ); PrecacheMaterial( "effects/fleck_ash2" ); PrecacheMaterial( "effects/fleck_ash3" ); PrecacheMaterial( "effects/ember_swirling001" ); Precache(); - SetSolid( SOLID_NONE ); // Remove model & collisions SetMoveType( MOVETYPE_NONE ); SetModel( STRING( GetModelName() ) ); // Set size + if ( IsParticleRainType( m_nPrecipType ) ) + { + SetSolid( SOLID_VPHYSICS ); + AddSolidFlags( FSOLID_NOT_SOLID ); + AddSolidFlags( FSOLID_FORCE_WORLD_ALIGNED ); + VPhysicsInitStatic(); + } + else + { + SetSolid( SOLID_NONE ); // Remove model & collisions + } // Default to rain. if ( m_nPrecipType < 0 || m_nPrecipType > NUM_PRECIPITATION_TYPES ) @@ -1559,6 +1613,11 @@ BEGIN_DATADESC( CEnvWind ) DEFINE_KEYFIELD( m_EnvWindShared.m_iGustDirChange, FIELD_INTEGER, "gustdirchange" ), DEFINE_KEYFIELD( m_EnvWindShared.m_flGustDuration, FIELD_FLOAT, "gustduration" ), // DEFINE_KEYFIELD( m_EnvWindShared.m_iszGustSound, FIELD_STRING, "gustsound" ), +#ifdef MAPBASE + DEFINE_KEYFIELD( m_EnvWindShared.m_windRadius, FIELD_FLOAT, "windradius" ), + DEFINE_KEYFIELD( m_EnvWindShared.m_windRadiusInner, FIELD_FLOAT, "windradiusinner" ), + DEFINE_KEYFIELD( m_EnvWindShared.m_flTreeSwayScale, FIELD_FLOAT, "treeswayscale" ), +#endif // Just here to quiet down classcheck // DEFINE_FIELD( m_EnvWindShared, CEnvWindShared ), @@ -1566,6 +1625,20 @@ BEGIN_DATADESC( CEnvWind ) DEFINE_FIELD( m_EnvWindShared.m_iWindDir, FIELD_INTEGER ), DEFINE_FIELD( m_EnvWindShared.m_flWindSpeed, FIELD_FLOAT ), +#ifdef MAPBASE + DEFINE_INPUT( m_EnvWindShared.m_iMinWind, FIELD_INTEGER, "SetMinWind" ), + DEFINE_INPUT( m_EnvWindShared.m_iMaxWind, FIELD_INTEGER, "SetMaxWind" ), + DEFINE_INPUT( m_EnvWindShared.m_iMinGust, FIELD_INTEGER, "SetMinGust" ), + DEFINE_INPUT( m_EnvWindShared.m_iMaxGust, FIELD_INTEGER, "SetMaxGust" ), + DEFINE_INPUT( m_EnvWindShared.m_flMinGustDelay, FIELD_FLOAT, "SetMinGustDelay" ), + DEFINE_INPUT( m_EnvWindShared.m_flMaxGustDelay, FIELD_FLOAT, "SetMaxGustDelay" ), + DEFINE_INPUT( m_EnvWindShared.m_iGustDirChange, FIELD_INTEGER, "SetGustDirChange" ), + DEFINE_INPUT( m_EnvWindShared.m_flGustDuration, FIELD_FLOAT, "SetGustDuration" ), + DEFINE_INPUT( m_EnvWindShared.m_windRadius, FIELD_FLOAT, "SetWindRadius" ), + DEFINE_INPUT( m_EnvWindShared.m_windRadiusInner, FIELD_FLOAT, "SetWindRadiusInner" ), + DEFINE_INPUT( m_EnvWindShared.m_flTreeSwayScale, FIELD_FLOAT, "SetTreeSwayScale" ), +#endif + DEFINE_OUTPUT( m_EnvWindShared.m_OnGustStart, "OnGustStart" ), DEFINE_OUTPUT( m_EnvWindShared.m_OnGustEnd, "OnGustEnd" ), @@ -1594,6 +1667,12 @@ BEGIN_SEND_TABLE_NOBASE(CEnvWindShared, DT_EnvWindShared) SendPropFloat (SENDINFO(m_flGustDuration), 0, SPROP_NOSCALE), // Sound related // SendPropInt (SENDINFO(m_iszGustSound), 10, SPROP_UNSIGNED ), +#ifdef MAPBASE + SendPropFloat (SENDINFO(m_windRadius), 0, SPROP_NOSCALE), + SendPropFloat (SENDINFO(m_windRadiusInner), 0, SPROP_NOSCALE), + SendPropVector (SENDINFO(m_location), -1, SPROP_COORD), + SendPropFloat (SENDINFO(m_flTreeSwayScale), 0, SPROP_NOSCALE), +#endif END_SEND_TABLE() // This table encodes the CBaseEntity data. @@ -1615,7 +1694,13 @@ void CEnvWind::Spawn( void ) SetSolid( SOLID_NONE ); AddEffects( EF_NODRAW ); +#ifdef MAPBASE + m_EnvWindShared.m_iInitialWindDir = (int)(anglemod( m_EnvWindShared.m_iInitialWindDir )); + m_EnvWindShared.Init( entindex(), 0, gpGlobals->curtime, GetLocalAngles().y, 0 ); + m_EnvWindShared.m_location = GetAbsOrigin(); +#else m_EnvWindShared.Init( entindex(), 0, gpGlobals->frametime, GetLocalAngles().y, 0 ); +#endif SetThink( &CEnvWind::WindThink ); SetNextThink( gpGlobals->curtime ); @@ -2037,6 +2122,10 @@ public: void InputEnable( inputdata_t &inputdata ); void InputDisable( inputdata_t &inputdata ); +#ifdef MAPBASE + void InputFireBurst( inputdata_t &inputdata ); +#endif + int m_iMinBurstSize; int m_iMaxBurstSize; @@ -2062,6 +2151,10 @@ public: EHANDLE m_hTarget; +#ifdef MAPBASE + COutputEvent m_OnFire; +#endif + DECLARE_DATADESC(); }; @@ -2090,6 +2183,11 @@ BEGIN_DATADESC( CEnvGunfire ) DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ), DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ), +#ifdef MAPBASE + DEFINE_INPUTFUNC( FIELD_INTEGER, "FireBurst", InputFireBurst ), + + DEFINE_OUTPUT( m_OnFire, "OnFire" ), +#endif END_DATADESC() LINK_ENTITY_TO_CLASS( env_gunfire, CEnvGunfire ); @@ -2234,10 +2332,26 @@ void CEnvGunfire::ShootThink() m_iShotsRemaining--; +#ifdef MAPBASE + m_OnFire.FireOutput(m_hTarget, this); +#endif + if( m_iShotsRemaining == 0 ) { +#ifdef MAPBASE + if (m_bDisabled) + { + StopShooting(); + } + else + { + StartShooting(); + SetNextThink( gpGlobals->curtime + random->RandomFloat( m_flMinBurstDelay, m_flMaxBurstDelay ) ); + } +#else StartShooting(); SetNextThink( gpGlobals->curtime + random->RandomFloat( m_flMinBurstDelay, m_flMaxBurstDelay ) ); +#endif } } @@ -2257,6 +2371,18 @@ void CEnvGunfire::InputDisable( inputdata_t &inputdata ) SetThink( NULL ); } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CEnvGunfire::InputFireBurst( inputdata_t &inputdata ) +{ + m_iShotsRemaining = inputdata.value.Int(); + + SetThink( &CEnvGunfire::ShootThink ); + SetNextThink( gpGlobals->curtime ); +} +#endif + //----------------------------------------------------------------------------- // Quadratic spline beam effect //----------------------------------------------------------------------------- @@ -2384,3 +2510,123 @@ void CEnvViewPunch::InputViewPunch( inputdata_t &inputdata ) { DoViewPunch(); } + +#ifdef MAPBASE +class CBreakableGibShooter : public CBaseEntity +{ + DECLARE_CLASS( CBreakableGibShooter, CBaseEntity ); + DECLARE_DATADESC(); +public: + + int GetRandomTemplateModelIndex( CPointTemplate *pTemplate ); + + void Precache( void ); + + void Shoot( void ); + + // ---------------- + // Inputs + // ---------------- + void InputShoot( inputdata_t &inputdata ); + +public: + + int m_iModelType; + enum + { + MODELTYPE_BREAKABLECHUNKS, + MODELTYPE_MODEL, + MODELTYPE_TEMPLATE, + }; + + int m_iCount; + float m_flDelay; + Vector m_vecGibSize; + float m_flGibSpeed; + int m_iRandomization; + float m_flLifetime; + int m_iGibFlags; +}; + +BEGIN_DATADESC( CBreakableGibShooter ) + + DEFINE_KEYFIELD( m_iModelType, FIELD_INTEGER, "modeltype" ), + DEFINE_INPUT( m_iCount, FIELD_INTEGER, "SetCount" ), + DEFINE_INPUT( m_flDelay, FIELD_FLOAT, "SetDelay" ), + DEFINE_INPUT( m_vecGibSize, FIELD_VECTOR, "SetGibSize" ), + DEFINE_INPUT( m_flGibSpeed, FIELD_FLOAT, "SetGibSpeed" ), + DEFINE_INPUT( m_iRandomization, FIELD_INTEGER, "SetRandomization" ), + DEFINE_INPUT( m_flLifetime, FIELD_FLOAT, "SetLifetime" ), + DEFINE_INPUT( m_iGibFlags, FIELD_INTEGER, "SetGibFlags" ), + + DEFINE_INPUTFUNC( FIELD_VOID, "Shoot", InputShoot ), + +END_DATADESC() + + +LINK_ENTITY_TO_CLASS( env_break_shooter, CBreakableGibShooter ); + + +int CBreakableGibShooter::GetRandomTemplateModelIndex( CPointTemplate *pTemplate ) +{ + int iIndex = RandomInt( 0, pTemplate->GetNumTemplates() ); + const char *szTemplate = STRING(Templates_FindByIndex(pTemplate->GetTemplateIndexForTemplate(iIndex))); + + // This might seem a little messy, but I think it's cheaper than creating the entity. + char szModel[MAPKEY_MAXLENGTH]; + bool modelExtracted = MapEntity_ExtractValue(szTemplate, "model", szModel); + + return modelinfo->GetModelIndex( modelExtracted ? szModel : NULL ); +} + +void CBreakableGibShooter::Precache( void ) +{ + if (m_iModelType == MODELTYPE_MODEL) + PrecacheModel( STRING(GetModelName()) ); +} + +void CBreakableGibShooter::Shoot( void ) +{ + int iModelIndex = 0; + if (m_iModelType == MODELTYPE_MODEL) + iModelIndex = modelinfo->GetModelIndex( STRING(GetModelName()) ); + + CPointTemplate *pTemplate = NULL; + if (m_iModelType == MODELTYPE_TEMPLATE) + { + pTemplate = dynamic_cast(gEntList.FindEntityByName(NULL, STRING(GetModelName()), this)); + if (!pTemplate) + { + Warning("%s cannot find point_template %s!\n", GetDebugName(), STRING(GetModelName())); + return; + } + } + + CPVSFilter filter( GetAbsOrigin() ); + for ( int i = 0; i < m_iCount; i++ ) + { + if (m_iModelType == MODELTYPE_BREAKABLECHUNKS) + iModelIndex = modelinfo->GetModelIndex( g_PropDataSystem.GetRandomChunkModel( STRING( GetModelName() ) ) ); + else if (m_iModelType == MODELTYPE_TEMPLATE) + iModelIndex = GetRandomTemplateModelIndex( pTemplate ); + + // All objects except the first one in this run are marked as slaves... + int slaveFlag = 0; + if ( i != 0 ) + { + slaveFlag = BREAK_SLAVE; + } + + Vector vecShootDir; + AngleVectors( GetAbsAngles(), &vecShootDir ); + VectorNormalize( vecShootDir ); + + te->BreakModel( filter, m_flDelay, GetAbsOrigin(), GetAbsAngles(), m_vecGibSize, vecShootDir * m_flGibSpeed, iModelIndex, m_iRandomization, 1, m_flLifetime, m_iGibFlags | slaveFlag ); + } +} + +void CBreakableGibShooter::InputShoot( inputdata_t &inputdata ) +{ + Shoot(); +} +#endif diff --git a/src/game/server/entity_tools_server.cpp b/src/game/server/entity_tools_server.cpp index b08d0754..d3c59959 100644 --- a/src/game/server/entity_tools_server.cpp +++ b/src/game/server/entity_tools_server.cpp @@ -14,6 +14,13 @@ #include "sceneentity.h" #include "particles/particles.h" +#if _MSC_VER >= 1900 +#include "icommandline.h" +#endif + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + //----------------------------------------------------------------------------- // Interface from engine to tools for manipulating entities diff --git a/src/game/server/entitylist.cpp b/src/game/server/entitylist.cpp index 3acec657..cd8a8326 100644 --- a/src/game/server/entitylist.cpp +++ b/src/game/server/entitylist.cpp @@ -20,6 +20,10 @@ #ifdef HL2_DLL #include "npc_playercompanion.h" +#ifdef MAPBASE +#include "hl2_player.h" +#include "mapbase_matchers_base.h" +#endif #endif // HL2_DLL // memdbgon must be the last include file in a .cpp file!!! @@ -75,6 +79,15 @@ public: // IEntityListener virtual void OnEntityCreated( CBaseEntity *pEntity ) {} + virtual void OnEntitySpawned( CBaseEntity *pEntity ) + { + // From Alien Swarm SDK + if ( ShouldAddEntity( pEntity ) ) + { + RemoveEntity( pEntity ); + AddEntity( pEntity ); + } + } virtual void OnEntityDeleted( CBaseEntity *pEntity ) { if ( !(pEntity->GetFlags() & FL_AIMTARGET) ) @@ -102,6 +115,10 @@ public: memcpy( pList, m_targetList.Base(), sizeof(CBaseEntity *) * count ); return count; } + CBaseEntity *ListElement( int iIndex ) // From Alien Swarm SDK + { + return m_targetList[iIndex]; + } private: CUtlVector m_targetList; @@ -117,6 +134,10 @@ int AimTarget_ListCopy( CBaseEntity *pList[], int listMax ) { return g_AimManager.ListCopy( pList, listMax ); } +CBaseEntity *AimTarget_ListElement( int iIndex ) +{ + return g_AimManager.ListElement( iIndex ); +} void AimTarget_ForceRepopulateList() { g_AimManager.ForceRepopulateList(); @@ -290,6 +311,10 @@ CBaseEntityClassList::~CBaseEntityClassList() { } +#ifdef MAPBASE_VSCRIPT +static CUtlVector g_CustomProcedurals; +#endif + CGlobalEntityList::CGlobalEntityList() { m_iHighestEnt = m_iNumEnts = m_iNumEdicts = 0; @@ -377,6 +402,10 @@ void CGlobalEntityList::Clear( void ) // free the memory g_DeleteList.Purge(); +#ifdef MAPBASE_VSCRIPT + g_CustomProcedurals.Purge(); +#endif + CBaseEntity::m_nDebugPlayer = -1; CBaseEntity::m_bInDebugSelect = false; m_iHighestEnt = 0; @@ -504,6 +533,43 @@ CBaseEntity *CGlobalEntityList::FindEntityByClassname( CBaseEntity *pStartEntity return NULL; } +// From Alien Swarm SDK +CBaseEntity *CGlobalEntityList::FindEntityByClassnameFast( CBaseEntity *pStartEntity, string_t iszClassname ) +{ + /* + if ( pStartEntity ) + { + return pStartEntity->m_pNextByClass; + } + + EntsByStringList_t key = { iszClassname }; + UtlHashHandle_t hEntry = g_EntsByClassname.Find( key ); + if ( hEntry != g_EntsByClassname.InvalidHandle() ) + { + return g_EntsByClassname[hEntry].pHead; + } + */ + + const CEntInfo *pInfo = pStartEntity ? GetEntInfoPtr( pStartEntity->GetRefEHandle() )->m_pNext : FirstEntInfo(); + + for ( ;pInfo; pInfo = pInfo->m_pNext ) + { + CBaseEntity *pEntity = (CBaseEntity *)pInfo->m_pEntity; + if ( !pEntity ) + { + DevWarning( "NULL entity in global entity list!\n" ); + continue; + } + + if ( pEntity->m_iClassname == iszClassname) + { + return pEntity; + } + } + + return NULL; +} + //----------------------------------------------------------------------------- // Purpose: Finds an entity given a procedural name. @@ -556,12 +622,60 @@ CBaseEntity *CGlobalEntityList::FindEntityProcedural( const char *szName, CBaseE } else if ( FStrEq( pName, "picker" ) ) { +#ifdef MAPBASE_MP + // TODO: Player could be activator instead + CBasePlayer *pPlayer = ToBasePlayer(pSearchingEntity); + return FindPickerEntity( pPlayer ? pPlayer : UTIL_PlayerByIndex(1) ); +#else return FindPickerEntity( UTIL_PlayerByIndex(1) ); +#endif } else if ( FStrEq( pName, "self" ) ) { return pSearchingEntity; } +#ifdef MAPBASE + else if ( FStrEq( pName, "parent" ) ) + { + return pSearchingEntity ? pSearchingEntity->GetParent() : NULL; + } + else if ( FStrEq( pName, "owner" ) ) + { + return pSearchingEntity ? pSearchingEntity->GetOwnerEntity() : NULL; + } + else if ( FStrEq( pName, "plrsquadrep" ) ) + { +#ifdef HL2_DLL + CHL2_Player *pPlayer = static_cast(UTIL_PlayerByIndex(1)); + if (pPlayer) + { + return pPlayer->GetSquadCommandRepresentative(); + } +#endif + } + else if (strchr(pName, ':')) + { + char name[128]; + Q_strncpy(name, pName, strchr(pName, ':')-pName+1); + + CBaseEntity *pEntity = FindEntityProcedural(UTIL_VarArgs("!%s", name), pSearchingEntity, pActivator, pCaller); + if (pEntity && pEntity->IsNPC()) + { + const char *target = (Q_strstr(pName, ":") + 1); + if (target[0] != '!') + target = UTIL_VarArgs("!%s", target); + + return pEntity->MyNPCPointer()->FindNamedEntity(target); + } + } + else if (pSearchingEntity && pSearchingEntity->IsCombatCharacter()) + { + // Perhaps the entity itself has the answer? + // This opens up new possibilities. The weird filter is there so it doesn't go through this twice. + CNullEntityFilter pFilter; + return pSearchingEntity->MyCombatCharacterPointer()->FindNamedEntity(szName, &pFilter); + } +#endif else { Warning( "Invalid entity search name %s\n", szName ); @@ -573,6 +687,99 @@ CBaseEntity *CGlobalEntityList::FindEntityProcedural( const char *szName, CBaseE } +#ifdef MAPBASE_VSCRIPT +//----------------------------------------------------------------------------- +// Purpose: Finds an entity given a custom procedural name. +// Input : szName - The procedural name to search for, should start with '!'. +// pSearchingEntity - +// pActivator - The activator entity if this was called from an input +// or Use handler. +//----------------------------------------------------------------------------- +CBaseEntity *CGlobalEntityList::FindEntityCustomProcedural( CBaseEntity *pStartEntity, const char *szName, CBaseEntity *pSearchingEntity, CBaseEntity *pActivator, CBaseEntity *pCaller ) +{ + const char *pName = szName; + + // + // Check for the name escape character. + // + if ( szName[0] == '!' ) + pName = szName + 1; + + // + // Look through the custom procedural list. + // + for (int i = 0; i < g_CustomProcedurals.Count(); i++) + { + if (Matcher_NamesMatch( g_CustomProcedurals[i].szName, pName )) + { + if (pStartEntity && g_CustomProcedurals[i].bCanReturnMultiple == false) + return NULL; + + // name, startEntity, searchingEntity, activator, caller + ScriptVariant_t functionReturn; + ScriptVariant_t args[] = { + ScriptVariant_t( pName ), + ScriptVariant_t( ToHScript( pStartEntity ) ), + ScriptVariant_t( ToHScript( pSearchingEntity ) ), + ScriptVariant_t( ToHScript( pActivator ) ), + ScriptVariant_t( ToHScript( pCaller ) ), + }; + + g_pScriptVM->ExecuteFunction( g_CustomProcedurals[i].hFunc, args, 5, &functionReturn, NULL, true ); + + if (pStartEntity && ToEnt( functionReturn.m_hScript ) == pStartEntity) + { + Warning( "WARNING: Custom procedural %s returned entity identical to start entity (%s), returning null\n", g_CustomProcedurals[i].szName, pStartEntity->GetDebugName() ); + return NULL; + } + + return ToEnt( functionReturn.m_hScript ); + } + } + + return NULL; +} + +void CGlobalEntityList::AddCustomProcedural( const char *pszName, HSCRIPT hFunc, bool bCanReturnMultiple ) +{ + // + // Check for any existing custom procedurals to replace. + // + for (int i = 0; i < g_CustomProcedurals.Count(); i++) + { + if (FStrEq( pszName, g_CustomProcedurals[i].szName )) + { + g_CustomProcedurals.FastRemove( i ); + break; + } + } + + // + // Add a new custom procedural. + // + int i = g_CustomProcedurals.AddToTail(); + g_CustomProcedurals[i].szName = pszName; + g_CustomProcedurals[i].hFunc = hFunc; + g_CustomProcedurals[i].bCanReturnMultiple = bCanReturnMultiple; +} + +void CGlobalEntityList::RemoveCustomProcedural( const char *pszName ) +{ + // + // Remove the specified custom procedural. + // + for (int i = 0; i < g_CustomProcedurals.Count(); i++) + { + if (FStrEq( pszName, g_CustomProcedurals[i].szName )) + { + g_CustomProcedurals.FastRemove( i ); + break; + } + } +} +#endif + + //----------------------------------------------------------------------------- // Purpose: Iterates the entities with a given name. // Input : pStartEntity - Last entity found, NULL to start a new iteration. @@ -587,6 +794,16 @@ CBaseEntity *CGlobalEntityList::FindEntityByName( CBaseEntity *pStartEntity, con if ( szName[0] == '!' ) { +#ifdef MAPBASE + if (g_CustomProcedurals.Count() > 0) + { + // Search for a custom procedural + CBaseEntity *ent = FindEntityCustomProcedural( pStartEntity, szName, pSearchingEntity, pActivator, pCaller ); + if (ent != NULL) + return ent; + } +#endif + // // Avoid an infinite loop, only find one match per procedural search! // @@ -607,7 +824,7 @@ CBaseEntity *CGlobalEntityList::FindEntityByName( CBaseEntity *pStartEntity, con continue; } - if ( !ent->m_iName ) + if ( !ent->m_iName.Get() ) continue; if ( ent->NameMatches( szName ) ) @@ -622,6 +839,35 @@ CBaseEntity *CGlobalEntityList::FindEntityByName( CBaseEntity *pStartEntity, con return NULL; } +// From Alien Swarm SDK +CBaseEntity *CGlobalEntityList::FindEntityByNameFast( CBaseEntity *pStartEntity, string_t iszName ) +{ + if ( iszName == NULL_STRING || STRING(iszName)[0] == 0 ) + return NULL; + + const CEntInfo *pInfo = pStartEntity ? GetEntInfoPtr( pStartEntity->GetRefEHandle() )->m_pNext : FirstEntInfo(); + + for ( ;pInfo; pInfo = pInfo->m_pNext ) + { + CBaseEntity *ent = (CBaseEntity *)pInfo->m_pEntity; + if ( !ent ) + { + DevWarning( "NULL entity in global entity list!\n" ); + continue; + } + + if ( !ent->m_iName.Get() ) + continue; + + if ( ent->m_iName.Get() == iszName ) + { + return ent; + } + } + + return NULL; +} + //----------------------------------------------------------------------------- // Purpose: // Input : pStartEntity - @@ -861,6 +1107,80 @@ CBaseEntity *CGlobalEntityList::FindEntityByClassnameNearest( const char *szName } +// From Alien Swarm SDK +CBaseEntity *CGlobalEntityList::FindEntityByClassnameNearestFast( string_t iszName, const Vector &vecSrc, float flRadius ) +{ + CBaseEntity *pEntity = NULL; + + // + // Check for matching class names within the search radius. + // + float flMaxDist2 = flRadius * flRadius; + if (flMaxDist2 == 0) + { + flMaxDist2 = MAX_TRACE_LENGTH * MAX_TRACE_LENGTH; + } + + CBaseEntity *pSearch = NULL; + while ((pSearch = gEntList.FindEntityByClassnameFast( pSearch, iszName )) != NULL) + { + if ( !pSearch->edict() ) + continue; + + float flDist2 = (pSearch->GetAbsOrigin() - vecSrc).LengthSqr(); + + if (flMaxDist2 > flDist2) + { + pEntity = pSearch; + flMaxDist2 = flDist2; + } + } + + return pEntity; +} + + +//----------------------------------------------------------------------------- +// Purpose: Finds the nearest entity by class name withing given search radius. +// From Alien Swarm SDK +// Input : szName - Entity name to search for. Treated as a target name first, +// then as an entity class name, ie "info_target". +// vecSrc - Center of search radius. +// flRadius - Search radius for classname search, 0 to search everywhere. +// Output : Returns a pointer to the found entity, NULL if none. +//----------------------------------------------------------------------------- +CBaseEntity *CGlobalEntityList::FindEntityByClassnameNearest2D( const char *szName, const Vector &vecSrc, float flRadius ) +{ + CBaseEntity *pEntity = NULL; + + // + // Check for matching class names within the search radius. + // + float flMaxDist2 = flRadius * flRadius; + if (flMaxDist2 == 0) + { + flMaxDist2 = MAX_TRACE_LENGTH * MAX_TRACE_LENGTH; + } + + CBaseEntity *pSearch = NULL; + while ((pSearch = gEntList.FindEntityByClassname( pSearch, szName )) != NULL) + { + if ( !pSearch->edict() ) + continue; + + float flDist2 = (pSearch->GetAbsOrigin().AsVector2D() - vecSrc.AsVector2D()).LengthSqr(); + + if (flMaxDist2 > flDist2) + { + pEntity = pSearch; + flMaxDist2 = flDist2; + } + } + + return pEntity; +} + + //----------------------------------------------------------------------------- // Purpose: Finds the first entity within radius distance by class name. @@ -950,6 +1270,20 @@ CBaseEntity *CGlobalEntityList::FindEntityByClassnameWithin( CBaseEntity *pStart // or Use handler, NULL otherwise. // Output : Returns a pointer to the found entity, NULL if none. //----------------------------------------------------------------------------- +#ifdef MAPBASE +CBaseEntity *CGlobalEntityList::FindEntityGeneric( CBaseEntity *pStartEntity, const char *szName, CBaseEntity *pSearchingEntity, CBaseEntity *pActivator, CBaseEntity *pCaller, IEntityFindFilter *pFilter ) +{ + CBaseEntity *pEntity = NULL; + + pEntity = gEntList.FindEntityByName( pStartEntity, szName, pSearchingEntity, pActivator, pCaller, pFilter ); + if (!pEntity) + { + pEntity = gEntList.FindEntityByClassname( pStartEntity, szName, pFilter ); + } + + return pEntity; +} +#else CBaseEntity *CGlobalEntityList::FindEntityGeneric( CBaseEntity *pStartEntity, const char *szName, CBaseEntity *pSearchingEntity, CBaseEntity *pActivator, CBaseEntity *pCaller ) { CBaseEntity *pEntity = NULL; @@ -962,6 +1296,7 @@ CBaseEntity *CGlobalEntityList::FindEntityGeneric( CBaseEntity *pStartEntity, co return pEntity; } +#endif //----------------------------------------------------------------------------- diff --git a/src/game/server/entitylist.h b/src/game/server/entitylist.h index 08d2d77e..1b63e74e 100644 --- a/src/game/server/entitylist.h +++ b/src/game/server/entitylist.h @@ -66,6 +66,36 @@ public: virtual CBaseEntity *GetFilterResult( void ) = 0; }; +#ifdef MAPBASE +// Returns false every time. Created for some sick hack involving FindEntityProcedural looking at FindNamedEntity. +class CNullEntityFilter : public IEntityFindFilter +{ +public: + virtual bool ShouldFindEntity( CBaseEntity *pEntity ) { return false; } + virtual CBaseEntity *GetFilterResult( void ) { return NULL; } +}; + +//----------------------------------------------------------------------------- +// Custom procedural names via VScript. This allows users to reference new '!' targets +// or override existing ones. It is capable of returning multiple entities when needed. +// +// This is useful if you want I/O and beyond to reference an entity/a number of entities +// which may possess differing or ambiguous targetnames, or if you want to reference an +// entity specific to the context of the operation (e.g. getting the searching entity's +// current weapon). +// +// For example, you could add a new procedural called "!first_child" which uses VScript to +// return the searching entity's first child entity. You could also add a procedural called +// "!children" which returns all of the searching entity's child entities. +//----------------------------------------------------------------------------- +struct CustomProcedural_t +{ + HSCRIPT hFunc; + const char *szName; + bool bCanReturnMultiple; +}; +#endif + //----------------------------------------------------------------------------- // Purpose: a global list of all the entities in the game. All iteration through // entities is done through this object. @@ -146,10 +176,15 @@ public: CBaseEntity *FindEntityByNameNearest( const char *szName, const Vector &vecSrc, float flRadius, CBaseEntity *pSearchingEntity = NULL, CBaseEntity *pActivator = NULL, CBaseEntity *pCaller = NULL, IEntityFindFilter *pFilter = NULL ); CBaseEntity *FindEntityByNameWithin( CBaseEntity *pStartEntity, const char *szName, const Vector &vecSrc, float flRadius, CBaseEntity *pSearchingEntity = NULL, CBaseEntity *pActivator = NULL, CBaseEntity *pCaller = NULL, IEntityFindFilter *pFilter = NULL ); CBaseEntity *FindEntityByClassnameNearest( const char *szName, const Vector &vecSrc, float flRadius, IEntityFindFilter *pFilter = NULL ); + CBaseEntity *FindEntityByClassnameNearest2D( const char *szName, const Vector &vecSrc, float flRadius ); // From Alien Swarm SDK CBaseEntity *FindEntityByClassnameWithin( CBaseEntity *pStartEntity , const char *szName, const Vector &vecSrc, float flRadius, IEntityFindFilter *pFilter = NULL ); CBaseEntity *FindEntityByClassnameWithin( CBaseEntity *pStartEntity , const char *szName, const Vector &vecMins, const Vector &vecMaxs, IEntityFindFilter *pFilter = NULL ); +#ifdef MAPBASE + CBaseEntity *FindEntityGeneric( CBaseEntity *pStartEntity, const char *szName, CBaseEntity *pSearchingEntity = NULL, CBaseEntity *pActivator = NULL, CBaseEntity *pCaller = NULL, IEntityFindFilter *pFilter = NULL ); +#else CBaseEntity *FindEntityGeneric( CBaseEntity *pStartEntity, const char *szName, CBaseEntity *pSearchingEntity = NULL, CBaseEntity *pActivator = NULL, CBaseEntity *pCaller = NULL ); +#endif CBaseEntity *FindEntityGenericWithin( CBaseEntity *pStartEntity, const char *szName, const Vector &vecSrc, float flRadius, CBaseEntity *pSearchingEntity = NULL, CBaseEntity *pActivator = NULL, CBaseEntity *pCaller = NULL ); CBaseEntity *FindEntityGenericNearest( const char *szName, const Vector &vecSrc, float flRadius, CBaseEntity *pSearchingEntity = NULL, CBaseEntity *pActivator = NULL, CBaseEntity *pCaller = NULL ); @@ -157,7 +192,19 @@ public: CBaseEntity *FindEntityClassNearestFacing( const Vector &origin, const Vector &facing, float threshold, char *classname); CBaseEntity *FindEntityProcedural( const char *szName, CBaseEntity *pSearchingEntity = NULL, CBaseEntity *pActivator = NULL, CBaseEntity *pCaller = NULL ); - +#ifdef MAPBASE_VSCRIPT + CBaseEntity *FindEntityCustomProcedural( CBaseEntity *pStartEntity, const char *szName, CBaseEntity *pSearchingEntity = NULL, CBaseEntity *pActivator = NULL, CBaseEntity *pCaller = NULL ); + + void AddCustomProcedural( const char *pszName, HSCRIPT hFunc, bool bCanReturnMultiple ); + void RemoveCustomProcedural( const char *pszName ); +#endif + + // Fast versions that require a (real) string_t, and won't do wildcarding + // From Alien Swarm SDK + CBaseEntity *FindEntityByClassnameFast( CBaseEntity *pStartEntity, string_t iszClassname ); + CBaseEntity *FindEntityByClassnameNearestFast( string_t iszClassname, const Vector &vecSrc, float flRadius ); + CBaseEntity *FindEntityByNameFast( CBaseEntity *pStartEntity, string_t iszName ); + CGlobalEntityList(); // CBaseEntityList overrides. @@ -354,6 +401,7 @@ extern INotify *g_pNotify; void EntityTouch_Add( CBaseEntity *pEntity ); int AimTarget_ListCount(); int AimTarget_ListCopy( CBaseEntity *pList[], int listMax ); +CBaseEntity *AimTarget_ListElement( int iIndex ); void AimTarget_ForceRepopulateList(); void SimThink_EntityChanged( CBaseEntity *pEntity ); diff --git a/src/game/server/entityoutput.h b/src/game/server/entityoutput.h index 3a3c9b56..7a78ed02 100644 --- a/src/game/server/entityoutput.h +++ b/src/game/server/entityoutput.h @@ -84,6 +84,13 @@ public: const CEventAction *GetActionForTarget( string_t iSearchTarget ) const; void ScriptRemoveEventAction( CEventAction *pEventAction, const char *szTarget, const char *szTargetInput, const char *szParameter ); + +#ifdef MAPBASE + // Needed for ReplaceOutput, hopefully not bad + CEventAction *GetActionList() { return m_ActionList; } + void SetActionList(CEventAction *newlist) { m_ActionList = newlist; } +#endif + protected: variant_t m_Value; CEventAction *m_ActionList; @@ -152,6 +159,29 @@ public: { m_Value.Vector3D(vec); } + +#ifdef MAPBASE + // Shortcut to using QAngles in Vector outputs, makes it look cleaner and allows easy modification + void Init( const QAngle &value ) + { + // reinterpret_cast(value) + m_Value.SetAngle3D( value ); + } + + // Shortcut to using QAngles in Vector outputs, makes it look cleaner and allows easy modification + void Set( const QAngle &value, CBaseEntity *pActivator, CBaseEntity *pCaller ) + { + // reinterpret_cast(value) + m_Value.SetAngle3D( value ); + FireOutput( m_Value, pActivator, pCaller ); + } + + // Shortcut to using QAngles in Vector outputs, makes it look cleaner and allows easy modification + void Get( QAngle &ang ) + { + m_Value.Angle3D(ang); + } +#endif }; diff --git a/src/game/server/env_dof_controller.cpp b/src/game/server/env_dof_controller.cpp new file mode 100644 index 00000000..d061fc73 --- /dev/null +++ b/src/game/server/env_dof_controller.cpp @@ -0,0 +1,186 @@ +//====== Copyright © 1996-2004, Valve Corporation, All rights reserved. ======= +// +// Purpose: Depth of field controller entity +// +//============================================================================= + +#include "cbase.h" +#include "baseentity.h" +#include "entityoutput.h" +#include "env_dof_controller.h" +#include "ai_utils.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +LINK_ENTITY_TO_CLASS( env_dof_controller, CEnvDOFController ); + +BEGIN_DATADESC( CEnvDOFController ) + + DEFINE_KEYFIELD( m_bDOFEnabled, FIELD_BOOLEAN, "enabled" ), + DEFINE_KEYFIELD( m_flNearBlurDepth, FIELD_FLOAT, "near_blur" ), + DEFINE_KEYFIELD( m_flNearFocusDepth, FIELD_FLOAT, "near_focus" ), + DEFINE_KEYFIELD( m_flFarFocusDepth, FIELD_FLOAT, "far_focus" ), + DEFINE_KEYFIELD( m_flFarBlurDepth, FIELD_FLOAT, "far_blur" ), + DEFINE_KEYFIELD( m_flNearBlurRadius, FIELD_FLOAT, "near_radius" ), + DEFINE_KEYFIELD( m_flFarBlurRadius, FIELD_FLOAT, "far_radius" ), + DEFINE_KEYFIELD( m_strFocusTargetName, FIELD_STRING, "focus_target" ), + DEFINE_KEYFIELD( m_flFocusTargetRange, FIELD_FLOAT, "focus_range" ), + + DEFINE_FIELD( m_hFocusTarget, FIELD_EHANDLE ), + + DEFINE_THINKFUNC( UpdateParamBlend ), + + // Inputs + DEFINE_INPUTFUNC( FIELD_FLOAT, "SetNearBlurDepth", InputSetNearBlurDepth ), + DEFINE_INPUTFUNC( FIELD_FLOAT, "SetNearFocusDepth", InputSetNearFocusDepth ), + DEFINE_INPUTFUNC( FIELD_FLOAT, "SetFarFocusDepth", InputSetFarFocusDepth ), + DEFINE_INPUTFUNC( FIELD_FLOAT, "SetFarBlurDepth", InputSetFarBlurDepth ), + DEFINE_INPUTFUNC( FIELD_FLOAT, "SetNearBlurRadius", InputSetNearBlurRadius ), + DEFINE_INPUTFUNC( FIELD_FLOAT, "SetFarBlurRadius", InputSetFarBlurRadius ), + DEFINE_INPUTFUNC( FIELD_STRING, "SetFocusTarget", InputSetFocusTarget ), + DEFINE_INPUTFUNC( FIELD_FLOAT, "SetFocusTargetRange", InputSetFocusTargetRange ), + +END_DATADESC() + +IMPLEMENT_SERVERCLASS_ST( CEnvDOFController, DT_EnvDOFController ) + SendPropInt( SENDINFO(m_bDOFEnabled), 1, SPROP_UNSIGNED ), + SendPropFloat( SENDINFO(m_flNearBlurDepth), 0, SPROP_NOSCALE), + SendPropFloat( SENDINFO(m_flNearFocusDepth), 0, SPROP_NOSCALE), + SendPropFloat( SENDINFO(m_flFarFocusDepth), 0, SPROP_NOSCALE), + SendPropFloat( SENDINFO(m_flFarBlurDepth), 0, SPROP_NOSCALE), + SendPropFloat( SENDINFO(m_flNearBlurRadius), 0, SPROP_NOSCALE), + SendPropFloat( SENDINFO(m_flFarBlurRadius), 0, SPROP_NOSCALE), +END_SEND_TABLE() + +void CEnvDOFController::Spawn() +{ + SetSolid( SOLID_NONE ); + SetMoveType( MOVETYPE_NONE ); + +#ifdef MAPBASE + // Find our target entity and hold on to it + m_hFocusTarget = gEntList.FindEntityByName( NULL, m_strFocusTargetName, this ); + + // Update if we have a focal target + if ( m_hFocusTarget ) + { + SetThink( &CEnvDOFController::UpdateParamBlend ); + SetNextThink( gpGlobals->curtime + 0.1f ); + } +#endif +} + +void CEnvDOFController::Activate() +{ + BaseClass::Activate(); + +#ifndef MAPBASE // Mapbase moves this to Spawn() to avoid issues with save/restore and entities set via the SetFocusTarget input + // Find our target entity and hold on to it + m_hFocusTarget = gEntList.FindEntityByName( NULL, m_strFocusTargetName ); + + // Update if we have a focal target + if ( m_hFocusTarget ) + { + SetThink( &CEnvDOFController::UpdateParamBlend ); + SetNextThink( gpGlobals->curtime + 0.1f ); + } +#endif +} + +int CEnvDOFController::UpdateTransmitState() +{ + return SetTransmitState( FL_EDICT_ALWAYS ); +} + +void CEnvDOFController::InputSetNearBlurDepth( inputdata_t &inputdata ) +{ + m_flNearBlurDepth = inputdata.value.Float(); +} + +void CEnvDOFController::InputSetNearFocusDepth( inputdata_t &inputdata ) +{ + m_flNearFocusDepth = inputdata.value.Float(); +} + +void CEnvDOFController::InputSetFarFocusDepth( inputdata_t &inputdata ) +{ + m_flFarFocusDepth = inputdata.value.Float(); +} + +void CEnvDOFController::InputSetFarBlurDepth( inputdata_t &inputdata ) +{ + m_flFarBlurDepth = inputdata.value.Float(); +} + +void CEnvDOFController::InputSetNearBlurRadius( inputdata_t &inputdata ) +{ + m_flNearBlurRadius = inputdata.value.Float(); + m_bDOFEnabled = ( m_flNearBlurRadius > 0.0f ) || ( m_flFarBlurRadius > 0.0f ); +} + +void CEnvDOFController::InputSetFarBlurRadius( inputdata_t &inputdata ) +{ + m_flFarBlurRadius = inputdata.value.Float(); + m_bDOFEnabled = ( m_flNearBlurRadius > 0.0f ) || ( m_flFarBlurRadius > 0.0f ); +} + +void CEnvDOFController::SetControllerState( DOFControlSettings_t setting ) +{ + m_flNearBlurDepth = setting.flNearBlurDepth; + m_flNearBlurRadius = setting.flNearBlurRadius; + m_flNearFocusDepth = setting.flNearFocusDistance; + + m_flFarBlurDepth = setting.flFarBlurDepth; + m_flFarBlurRadius = setting.flFarBlurRadius; + m_flFarFocusDepth = setting.flFarFocusDistance; + + m_bDOFEnabled = ( m_flNearBlurRadius > 0.0f ) || ( m_flFarBlurRadius > 0.0f ); +} + +#define BLUR_DEPTH 500.0f + +//----------------------------------------------------------------------------- +// Purpose: Blend the parameters to the specified value +//----------------------------------------------------------------------------- +void CEnvDOFController::UpdateParamBlend() +{ + // Update our focal target if we have one + if ( m_hFocusTarget ) + { + CBasePlayer *pPlayer = AI_GetSinglePlayer(); + float flDistToFocus = ( m_hFocusTarget->GetAbsOrigin() - pPlayer->GetAbsOrigin() ).Length(); + m_flFarFocusDepth.GetForModify() = flDistToFocus + m_flFocusTargetRange; + m_flFarBlurDepth.GetForModify() = m_flFarFocusDepth + BLUR_DEPTH; + + SetThink( &CEnvDOFController::UpdateParamBlend ); + SetNextThink( gpGlobals->curtime + 0.1f ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Set the "focus" target entity +//----------------------------------------------------------------------------- +void CEnvDOFController::InputSetFocusTarget( inputdata_t &inputdata ) +{ +#ifdef MAPBASE + m_hFocusTarget = gEntList.FindEntityByName( NULL, inputdata.value.String(), this, inputdata.pActivator, inputdata.pCaller ); +#else + m_hFocusTarget = gEntList.FindEntityByName( NULL, inputdata.value.String() ); +#endif + + // Update if we have a focal target + if ( m_hFocusTarget ) + { + SetThink( &CEnvDOFController::UpdateParamBlend ); + SetNextThink( gpGlobals->curtime + 0.1f ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Set the range behind the focus entity that we'll blur (in units) +//----------------------------------------------------------------------------- +void CEnvDOFController::InputSetFocusTargetRange( inputdata_t &inputdata ) +{ + m_flFocusTargetRange = inputdata.value.Float(); +} diff --git a/src/game/server/env_dof_controller.h b/src/game/server/env_dof_controller.h new file mode 100644 index 00000000..e323ef52 --- /dev/null +++ b/src/game/server/env_dof_controller.h @@ -0,0 +1,56 @@ +#pragma once + +struct DOFControlSettings_t +{ + // Near plane + float flNearBlurDepth; + float flNearBlurRadius; + float flNearFocusDistance; + // Far plane + float flFarBlurDepth; + float flFarBlurRadius; + float flFarFocusDistance; +}; + +//----------------------------------------------------------------------------- +// Purpose: Entity that controls depth of field postprocessing +//----------------------------------------------------------------------------- +class CEnvDOFController : public CPointEntity +{ + DECLARE_CLASS( CEnvDOFController, CPointEntity ); +public: + DECLARE_DATADESC(); + DECLARE_SERVERCLASS(); + + virtual void Spawn(); + virtual void Activate(); + virtual int UpdateTransmitState(); + void SetControllerState( DOFControlSettings_t setting ); + + void UpdateParamBlend(); + + // Inputs + void InputSetNearBlurDepth( inputdata_t &inputdata ); + void InputSetNearFocusDepth( inputdata_t &inputdata ); + void InputSetFarFocusDepth( inputdata_t &inputdata ); + void InputSetFarBlurDepth( inputdata_t &inputdata ); + void InputSetNearBlurRadius( inputdata_t &inputdata ); + void InputSetFarBlurRadius( inputdata_t &inputdata ); + + void InputSetFocusTarget( inputdata_t &inputdata ); + void InputSetFocusTargetRange( inputdata_t &inputdata ); + +private: + float m_flFocusTargetRange; + + string_t m_strFocusTargetName; // Name of the entity to focus on + EHANDLE m_hFocusTarget; + + CNetworkVar( bool, m_bDOFEnabled ); + CNetworkVar( float, m_flNearBlurDepth ); + CNetworkVar( float, m_flNearFocusDepth ); + CNetworkVar( float, m_flFarFocusDepth ); + CNetworkVar( float, m_flFarBlurDepth ); + CNetworkVar( float, m_flNearBlurRadius ); + CNetworkVar( float, m_flFarBlurRadius ); +}; diff --git a/src/game/server/env_entity_maker.cpp b/src/game/server/env_entity_maker.cpp index f2aeb5df..70da6a38 100644 --- a/src/game/server/env_entity_maker.cpp +++ b/src/game/server/env_entity_maker.cpp @@ -39,6 +39,10 @@ public: void CheckSpawnThink( void ); void InputForceSpawn( inputdata_t &inputdata ); void InputForceSpawnAtEntityOrigin( inputdata_t &inputdata ); +#ifdef MAPBASE + void InputForceSpawnAtEntityCenter( inputdata_t &inputdata ); + void InputForceSpawnAtPosition( inputdata_t &inputdata ); +#endif void SpawnEntityFromScript(); void SpawnEntityAtEntityOriginFromScript( HSCRIPT hEntity ); @@ -68,6 +72,9 @@ private: COutputEvent m_pOutputOnSpawned; COutputEvent m_pOutputOnFailedSpawn; +#ifdef MAPBASE + COutputEHANDLE m_pOutputOutEntity; +#endif }; BEGIN_DATADESC( CEnvEntityMaker ) @@ -85,10 +92,17 @@ BEGIN_DATADESC( CEnvEntityMaker ) // Outputs DEFINE_OUTPUT( m_pOutputOnSpawned, "OnEntitySpawned" ), DEFINE_OUTPUT( m_pOutputOnFailedSpawn, "OnEntityFailedSpawn" ), +#ifdef MAPBASE + DEFINE_OUTPUT( m_pOutputOutEntity, "OutSpawnedEntity" ), +#endif // Inputs DEFINE_INPUTFUNC( FIELD_VOID, "ForceSpawn", InputForceSpawn ), DEFINE_INPUTFUNC( FIELD_STRING, "ForceSpawnAtEntityOrigin", InputForceSpawnAtEntityOrigin ), +#ifdef MAPBASE + DEFINE_INPUTFUNC( FIELD_STRING, "ForceSpawnAtEntityCenter", InputForceSpawnAtEntityCenter ), + DEFINE_INPUTFUNC( FIELD_VECTOR, "ForceSpawnAtPosition", InputForceSpawnAtPosition ), +#endif // Functions DEFINE_THINKFUNC( CheckSpawnThink ), @@ -214,6 +228,11 @@ void CEnvEntityMaker::SpawnEntity( Vector vecAlternateOrigin, QAngle vecAlternat for ( int i = 0; i < hNewEntities.Count(); i++ ) { CBaseEntity *pEntity = hNewEntities[i]; + +#ifdef MAPBASE + m_pOutputOutEntity.Set(pEntity, pEntity, this); +#endif + if ( pEntity->GetMoveType() == MOVETYPE_NONE ) continue; @@ -251,6 +270,15 @@ void CEnvEntityMaker::SpawnEntity( Vector vecAlternateOrigin, QAngle vecAlternat } } } +#ifdef MAPBASE + else + { + for ( int i = 0; i < hNewEntities.Count(); i++ ) + { + m_pOutputOutEntity.Set(hNewEntities[i], hNewEntities[i], this); + } + } +#endif pTemplate->CreationComplete( hNewEntities ); } @@ -296,7 +324,6 @@ void CEnvEntityMaker::SpawnEntityAtLocationFromScript( const Vector &vecAlternat SpawnEntity( vecAlternateOrigin, *((QAngle *)&vecAlternateAngles) ); } - //----------------------------------------------------------------------------- // Purpose: Returns whether or not the template entities can fit if spawned. // Input : pBlocker - Returns blocker unless NULL. @@ -420,3 +447,30 @@ void CEnvEntityMaker::InputForceSpawnAtEntityOrigin( inputdata_t &inputdata ) SpawnEntity( pTargetEntity->GetAbsOrigin(), pTargetEntity->GetAbsAngles() ); } } + +#ifdef MAPBASE +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CEnvEntityMaker::InputForceSpawnAtEntityCenter( inputdata_t &inputdata ) +{ + CBaseEntity *pTargetEntity = gEntList.FindEntityByName( NULL, inputdata.value.String(), this, inputdata.pActivator, inputdata.pCaller ); + + if( pTargetEntity ) + { + SpawnEntity( pTargetEntity->WorldSpaceCenter(), pTargetEntity->GetAbsAngles() ); + } +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CEnvEntityMaker::InputForceSpawnAtPosition(inputdata_t &inputdata) +{ + Vector vecPos; + inputdata.value.Vector3D(vecPos); + if (vecPos != vec3_origin && vecPos.IsValid()) + { + SpawnEntity(vecPos, GetLocalAngles()); + } +} +#endif // MAPBASE + diff --git a/src/game/server/env_global_light.cpp b/src/game/server/env_global_light.cpp new file mode 100644 index 00000000..6c1549b2 --- /dev/null +++ b/src/game/server/env_global_light.cpp @@ -0,0 +1,336 @@ +//========= Copyright 1996-2010, Valve Corporation, All rights reserved. ============// +// +// Purpose: global dynamic light. Ported from Insolence's port of Alien Swarm's env_global_light. +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//------------------------------------------------------------------------------ +// FIXME: This really should inherit from something more lightweight +//------------------------------------------------------------------------------ + + +//------------------------------------------------------------------------------ +// Purpose : Sunlight shadow control entity +//------------------------------------------------------------------------------ +class CGlobalLight : public CBaseEntity +{ +public: + DECLARE_CLASS( CGlobalLight, CBaseEntity ); + + CGlobalLight(); + + void Spawn( void ); + bool KeyValue( const char *szKeyName, const char *szValue ); + virtual bool GetKeyValue( const char *szKeyName, char *szValue, int iMaxLen ); + int UpdateTransmitState(); + + // Inputs + void InputSetAngles( inputdata_t &inputdata ); + void InputEnable( inputdata_t &inputdata ); + void InputDisable( inputdata_t &inputdata ); + void InputSetTexture( inputdata_t &inputdata ); + void InputSetEnableShadows( inputdata_t &inputdata ); + void InputSetLightColor( inputdata_t &inputdata ); +#ifdef MAPBASE + void InputSetBrightness( inputdata_t &inputdata ); + void InputSetColorTransitionTime( inputdata_t &inputdata ); + void InputSetXOffset( inputdata_t &inputdata ) { m_flEastOffset = inputdata.value.Float(); } + void InputSetYOffset( inputdata_t &inputdata ) { m_flForwardOffset = inputdata.value.Float(); } + void InputSetOrthoSize( inputdata_t &inputdata ) { m_flOrthoSize = inputdata.value.Float(); } + void InputSetDistance( inputdata_t &inputdata ) { m_flSunDistance = inputdata.value.Float(); } + void InputSetFOV( inputdata_t &inputdata ) { m_flFOV = inputdata.value.Float(); } + void InputSetNearZDistance( inputdata_t &inputdata ) { m_flNearZ = inputdata.value.Float(); } + void InputSetNorthOffset( inputdata_t &inputdata ) { m_flNorthOffset = inputdata.value.Float(); } +#endif + + virtual int ObjectCaps( void ) { return BaseClass::ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + + DECLARE_SERVERCLASS(); + DECLARE_DATADESC(); + +private: + CNetworkVector( m_shadowDirection ); + + CNetworkVar( bool, m_bEnabled ); + + CNetworkString( m_TextureName, MAX_PATH ); +#ifdef MAPBASE + CNetworkVar( int, m_nSpotlightTextureFrame ); +#endif + CNetworkColor32( m_LightColor ); +#ifdef MAPBASE + CNetworkVar( float, m_flBrightnessScale ); +#endif + CNetworkVar( float, m_flColorTransitionTime ); + CNetworkVar( float, m_flSunDistance ); + CNetworkVar( float, m_flFOV ); + CNetworkVar( float, m_flNearZ ); + CNetworkVar( float, m_flNorthOffset ); +#ifdef MAPBASE + CNetworkVar( float, m_flEastOffset ); // xoffset + CNetworkVar( float, m_flForwardOffset ); // yoffset + CNetworkVar( float, m_flOrthoSize ); +#endif + CNetworkVar( bool, m_bEnableShadows ); +}; + +LINK_ENTITY_TO_CLASS(env_global_light, CGlobalLight); + +BEGIN_DATADESC( CGlobalLight ) + + DEFINE_KEYFIELD( m_bEnabled, FIELD_BOOLEAN, "enabled" ), + DEFINE_AUTO_ARRAY_KEYFIELD( m_TextureName, FIELD_CHARACTER, "texturename" ), +#ifdef MAPBASE + DEFINE_KEYFIELD( m_nSpotlightTextureFrame, FIELD_INTEGER, "textureframe" ), +#endif + DEFINE_KEYFIELD( m_flSunDistance, FIELD_FLOAT, "distance" ), + DEFINE_KEYFIELD( m_flFOV, FIELD_FLOAT, "fov" ), + DEFINE_KEYFIELD( m_flNearZ, FIELD_FLOAT, "nearz" ), + DEFINE_KEYFIELD( m_flNorthOffset, FIELD_FLOAT, "northoffset" ), +#ifdef MAPBASE + DEFINE_KEYFIELD( m_flEastOffset, FIELD_FLOAT, "eastoffset" ), + DEFINE_KEYFIELD( m_flForwardOffset, FIELD_FLOAT, "forwardoffset" ), + DEFINE_KEYFIELD( m_flOrthoSize, FIELD_FLOAT, "orthosize" ), +#endif + DEFINE_KEYFIELD( m_bEnableShadows, FIELD_BOOLEAN, "enableshadows" ), + DEFINE_FIELD( m_LightColor, FIELD_COLOR32 ), +#ifdef MAPBASE + DEFINE_KEYFIELD( m_flBrightnessScale, FIELD_FLOAT, "brightnessscale" ), +#endif + DEFINE_KEYFIELD( m_flColorTransitionTime, FIELD_FLOAT, "colortransitiontime" ), + + DEFINE_FIELD( m_shadowDirection, FIELD_VECTOR ), + + // Inputs +#ifdef MAPBASE + DEFINE_INPUTFUNC( FIELD_FLOAT, "SetXOffset", InputSetXOffset ), + DEFINE_INPUTFUNC( FIELD_FLOAT, "SetYOffset", InputSetYOffset ), + DEFINE_INPUTFUNC( FIELD_FLOAT, "SetOrthoSize", InputSetOrthoSize ), + DEFINE_INPUTFUNC( FIELD_FLOAT, "SetDistance", InputSetDistance ), + DEFINE_INPUTFUNC( FIELD_FLOAT, "SetFOV", InputSetFOV ), + DEFINE_INPUTFUNC( FIELD_FLOAT, "SetNearZDistance", InputSetNearZDistance ), + DEFINE_INPUTFUNC( FIELD_FLOAT, "SetNorthOffset", InputSetNorthOffset ), +#else + DEFINE_INPUT( m_flSunDistance, FIELD_FLOAT, "SetDistance" ), + DEFINE_INPUT( m_flFOV, FIELD_FLOAT, "SetFOV" ), + DEFINE_INPUT( m_flNearZ, FIELD_FLOAT, "SetNearZDistance" ), + DEFINE_INPUT( m_flNorthOffset, FIELD_FLOAT, "SetNorthOffset" ), +#endif + + DEFINE_INPUTFUNC( FIELD_COLOR32, "LightColor", InputSetLightColor ), + DEFINE_INPUTFUNC( FIELD_STRING, "SetAngles", InputSetAngles ), + DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ), + DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ), + DEFINE_INPUTFUNC( FIELD_STRING, "SetTexture", InputSetTexture ), + DEFINE_INPUTFUNC( FIELD_BOOLEAN, "EnableShadows", InputSetEnableShadows ), +#ifdef MAPBASE + DEFINE_INPUTFUNC( FIELD_FLOAT, "SetBrightness", InputSetBrightness ), + DEFINE_INPUTFUNC( FIELD_FLOAT, "SetColorTransitionTime", InputSetColorTransitionTime ), +#endif + +END_DATADESC() + + +IMPLEMENT_SERVERCLASS_ST_NOBASE(CGlobalLight, DT_GlobalLight) + SendPropVector(SENDINFO(m_shadowDirection), -1, SPROP_NOSCALE ), + SendPropBool(SENDINFO(m_bEnabled) ), + SendPropString(SENDINFO(m_TextureName)), +#ifdef MAPBASE + SendPropInt(SENDINFO(m_nSpotlightTextureFrame)), +#endif + /*SendPropInt(SENDINFO (m_LightColor ), 32, SPROP_UNSIGNED, SendProxy_Color32ToInt32 ),*/ + SendPropInt(SENDINFO (m_LightColor ), 32, SPROP_UNSIGNED, SendProxy_Color32ToInt ), +#ifdef MAPBASE + SendPropFloat( SENDINFO( m_flBrightnessScale ) ), +#endif + SendPropFloat( SENDINFO( m_flColorTransitionTime ) ), + SendPropFloat(SENDINFO(m_flSunDistance), 0, SPROP_NOSCALE ), + SendPropFloat(SENDINFO(m_flFOV), 0, SPROP_NOSCALE ), + SendPropFloat(SENDINFO(m_flNearZ), 0, SPROP_NOSCALE ), + SendPropFloat(SENDINFO(m_flNorthOffset), 0, SPROP_NOSCALE ), +#ifdef MAPBASE + SendPropFloat(SENDINFO(m_flEastOffset), 0, SPROP_NOSCALE ), + SendPropFloat(SENDINFO(m_flForwardOffset), 0, SPROP_NOSCALE ), + SendPropFloat(SENDINFO(m_flOrthoSize), 0, SPROP_NOSCALE ), +#endif + SendPropBool( SENDINFO( m_bEnableShadows ) ), +END_SEND_TABLE() + + +CGlobalLight::CGlobalLight() +{ +#if defined( _X360 ) + Q_strcpy( m_TextureName.GetForModify(), "effects/flashlight_border" ); +#else + Q_strcpy( m_TextureName.GetForModify(), "effects/flashlight001" ); +#endif +#ifdef MAPBASE + m_LightColor.Init( 255, 255, 255, 255 ); +#else + m_LightColor.Init( 255, 255, 255, 1 ); +#endif + m_flColorTransitionTime = 0.5f; + m_flSunDistance = 10000.0f; + m_flFOV = 5.0f; + m_bEnableShadows = false; +#ifdef MAPBASE + m_nSpotlightTextureFrame = 0; + m_flBrightnessScale = 1.0f; + m_flOrthoSize = 1000.0f; +#endif + m_bEnabled = true; +} + + +//------------------------------------------------------------------------------ +// Purpose : Send even though we don't have a model +//------------------------------------------------------------------------------ +int CGlobalLight::UpdateTransmitState() +{ + // ALWAYS transmit to all clients. + return SetTransmitState( FL_EDICT_ALWAYS ); +} + + +bool CGlobalLight::KeyValue( const char *szKeyName, const char *szValue ) +{ +#ifdef MAPBASE + if ( FStrEq( szKeyName, "lightcolor" ) || FStrEq( szKeyName, "color" ) ) +#else + if ( FStrEq( szKeyName, "color" ) ) +#endif + { + float tmp[4]; + UTIL_StringToFloatArray( tmp, 4, szValue ); + + m_LightColor.SetR( tmp[0] ); + m_LightColor.SetG( tmp[1] ); + m_LightColor.SetB( tmp[2] ); + m_LightColor.SetA( tmp[3] ); + } + else if ( FStrEq( szKeyName, "angles" ) ) + { + QAngle angles; + UTIL_StringToVector( angles.Base(), szValue ); + if (angles == vec3_angle) + { + angles.Init( 80, 30, 0 ); + } + Vector vForward; + AngleVectors( angles, &vForward ); + m_shadowDirection = vForward; + return true; + } + else if ( FStrEq( szKeyName, "texturename" ) ) + { +#if defined( _X360 ) + if ( Q_strcmp( szValue, "effects/flashlight001" ) == 0 ) + { + // Use this as the default for Xbox + Q_strcpy( m_TextureName.GetForModify(), "effects/flashlight_border" ); + } + else + { + Q_strcpy( m_TextureName.GetForModify(), szValue ); + } +#else + Q_strcpy( m_TextureName.GetForModify(), szValue ); +#endif + } + else if ( FStrEq( szKeyName, "StartDisabled" ) ) + { + m_bEnabled.Set( atoi( szValue ) <= 0 ); + } + + return BaseClass::KeyValue( szKeyName, szValue ); +} + +bool CGlobalLight::GetKeyValue( const char *szKeyName, char *szValue, int iMaxLen ) +{ + if ( FStrEq( szKeyName, "color" ) ) + { + Q_snprintf( szValue, iMaxLen, "%d %d %d %d", m_LightColor.GetR(), m_LightColor.GetG(), m_LightColor.GetB(), m_LightColor.GetA() ); + return true; + } + else if ( FStrEq( szKeyName, "texturename" ) ) + { + Q_snprintf( szValue, iMaxLen, "%s", m_TextureName.Get() ); + return true; + } + else if ( FStrEq( szKeyName, "StartDisabled" ) ) + { + Q_snprintf( szValue, iMaxLen, "%d", !m_bEnabled.Get() ); + return true; + } + return BaseClass::GetKeyValue( szKeyName, szValue, iMaxLen ); +} + +//------------------------------------------------------------------------------ +// Purpose : +//------------------------------------------------------------------------------ +void CGlobalLight::Spawn( void ) +{ + Precache(); + SetSolid( SOLID_NONE ); +} + +//------------------------------------------------------------------------------ +// Input values +//------------------------------------------------------------------------------ +void CGlobalLight::InputSetAngles( inputdata_t &inputdata ) +{ + const char *pAngles = inputdata.value.String(); + + QAngle angles; + UTIL_StringToVector( angles.Base(), pAngles ); + + Vector vTemp; + AngleVectors( angles, &vTemp ); + m_shadowDirection = vTemp; +} + +//------------------------------------------------------------------------------ +// Purpose : Input handlers +//------------------------------------------------------------------------------ +void CGlobalLight::InputEnable( inputdata_t &inputdata ) +{ + m_bEnabled = true; +} + +void CGlobalLight::InputDisable( inputdata_t &inputdata ) +{ + m_bEnabled = false; +} + +void CGlobalLight::InputSetTexture( inputdata_t &inputdata ) +{ + Q_strcpy( m_TextureName.GetForModify(), inputdata.value.String() ); +} + +void CGlobalLight::InputSetEnableShadows( inputdata_t &inputdata ) +{ + m_bEnableShadows = inputdata.value.Bool(); +} + +void CGlobalLight::InputSetLightColor( inputdata_t &inputdata ) +{ + m_LightColor = inputdata.value.Color32(); +} + +#ifdef MAPBASE +void CGlobalLight::InputSetBrightness( inputdata_t &inputdata ) +{ + m_flBrightnessScale = inputdata.value.Float(); +} + +void CGlobalLight::InputSetColorTransitionTime( inputdata_t &inputdata ) +{ + m_flColorTransitionTime = inputdata.value.Float(); +} +#endif diff --git a/src/game/server/env_instructor_hint.cpp b/src/game/server/env_instructor_hint.cpp new file mode 100644 index 00000000..3ee20fb2 --- /dev/null +++ b/src/game/server/env_instructor_hint.cpp @@ -0,0 +1,319 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: An entity for creating instructor hints entirely with map logic +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" +#include "baseentity.h" +#include "world.h" +#ifdef MAPBASE +#include "eventqueue.h" +#endif + +#ifdef INFESTED_DLL + #include "asw_marine.h" + #include "asw_player.h" +#endif + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class CEnvInstructorHint : public CPointEntity +{ +public: + DECLARE_CLASS( CEnvInstructorHint, CPointEntity ); + DECLARE_DATADESC(); + +#ifdef MAPBASE + CEnvInstructorHint( void ); +#endif + virtual ~CEnvInstructorHint( void ) {} + +#ifdef MAPBASE + virtual void OnRestore( void ); +#endif + +private: + void InputShowHint( inputdata_t &inputdata ); + void InputEndHint( inputdata_t &inputdata ); + +#ifdef MAPBASE + void InputSetCaption( inputdata_t &inputdata ) { m_iszCaption = inputdata.value.StringID(); } +#endif + + string_t m_iszReplace_Key; + string_t m_iszHintTargetEntity; + int m_iTimeout; + string_t m_iszIcon_Onscreen; + string_t m_iszIcon_Offscreen; + string_t m_iszCaption; + string_t m_iszActivatorCaption; + color32 m_Color; + float m_fIconOffset; + float m_fRange; + uint8 m_iPulseOption; + uint8 m_iAlphaOption; + uint8 m_iShakeOption; + bool m_bStatic; + bool m_bNoOffscreen; + bool m_bForceCaption; + string_t m_iszBinding; + bool m_bAllowNoDrawTarget; + bool m_bLocalPlayerOnly; +#ifdef MAPBASE + string_t m_iszStartSound; + int m_iHintTargetPos; + float m_flActiveUntil; + CHandle m_hActivator; + EHANDLE m_hTarget; + bool m_bFilterByActivator; +#endif +}; + +LINK_ENTITY_TO_CLASS( env_instructor_hint, CEnvInstructorHint ); + +BEGIN_DATADESC( CEnvInstructorHint ) + + DEFINE_KEYFIELD( m_iszReplace_Key, FIELD_STRING, "hint_replace_key" ), + DEFINE_KEYFIELD( m_iszHintTargetEntity, FIELD_STRING, "hint_target" ), + DEFINE_KEYFIELD( m_iTimeout, FIELD_INTEGER, "hint_timeout" ), + DEFINE_KEYFIELD( m_iszIcon_Onscreen, FIELD_STRING, "hint_icon_onscreen" ), + DEFINE_KEYFIELD( m_iszIcon_Offscreen, FIELD_STRING, "hint_icon_offscreen" ), + DEFINE_KEYFIELD( m_iszCaption, FIELD_STRING, "hint_caption" ), + DEFINE_KEYFIELD( m_iszActivatorCaption, FIELD_STRING, "hint_activator_caption" ), + DEFINE_KEYFIELD( m_Color, FIELD_COLOR32, "hint_color" ), + DEFINE_KEYFIELD( m_fIconOffset, FIELD_FLOAT, "hint_icon_offset" ), + DEFINE_KEYFIELD( m_fRange, FIELD_FLOAT, "hint_range" ), + DEFINE_KEYFIELD( m_iPulseOption, FIELD_CHARACTER, "hint_pulseoption" ), + DEFINE_KEYFIELD( m_iAlphaOption, FIELD_CHARACTER, "hint_alphaoption" ), + DEFINE_KEYFIELD( m_iShakeOption, FIELD_CHARACTER, "hint_shakeoption" ), + DEFINE_KEYFIELD( m_bStatic, FIELD_BOOLEAN, "hint_static" ), + DEFINE_KEYFIELD( m_bNoOffscreen, FIELD_BOOLEAN, "hint_nooffscreen" ), + DEFINE_KEYFIELD( m_bForceCaption, FIELD_BOOLEAN, "hint_forcecaption" ), + DEFINE_KEYFIELD( m_iszBinding, FIELD_STRING, "hint_binding" ), + DEFINE_KEYFIELD( m_bAllowNoDrawTarget, FIELD_BOOLEAN, "hint_allow_nodraw_target" ), + DEFINE_KEYFIELD( m_bLocalPlayerOnly, FIELD_BOOLEAN, "hint_local_player_only" ), +#ifdef MAPBASE + DEFINE_KEYFIELD( m_iszStartSound, FIELD_STRING, "hint_start_sound" ), + DEFINE_KEYFIELD( m_iHintTargetPos, FIELD_INTEGER, "hint_target_pos" ), + + DEFINE_FIELD( m_flActiveUntil, FIELD_TIME ), + DEFINE_FIELD( m_hActivator, FIELD_EHANDLE ), + DEFINE_FIELD( m_hTarget, FIELD_EHANDLE ), + DEFINE_FIELD( m_bFilterByActivator, FIELD_BOOLEAN ), +#endif + + DEFINE_INPUTFUNC( FIELD_STRING, "ShowHint", InputShowHint ), + DEFINE_INPUTFUNC( FIELD_VOID, "EndHint", InputEndHint ), + +#ifdef MAPBASE + DEFINE_INPUTFUNC( FIELD_STRING, "SetCaption", InputSetCaption ), +#endif + +END_DATADESC() + + +#define LOCATOR_ICON_FX_PULSE_SLOW 0x00000001 +#define LOCATOR_ICON_FX_ALPHA_SLOW 0x00000008 +#define LOCATOR_ICON_FX_SHAKE_NARROW 0x00000040 +#define LOCATOR_ICON_FX_STATIC 0x00000100 // This icon draws at a fixed location on the HUD. + +#ifdef MAPBASE +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +CEnvInstructorHint::CEnvInstructorHint( void ) +{ + m_hActivator = NULL; + m_hTarget = NULL; + m_bFilterByActivator = false; + m_flActiveUntil = -1.0f; +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CEnvInstructorHint::OnRestore( void ) +{ + BaseClass::OnRestore(); + + int iTimeLeft = 0; + if ( m_flActiveUntil < 0.0f ) + { + return; + } + if ( m_iTimeout != 0 ) + { + iTimeLeft = static_cast( m_flActiveUntil - gpGlobals->curtime ); + if ( iTimeLeft <= 0 ) + { + return; + } + } + + int iOriginalTimeout = m_iTimeout; + m_iTimeout = iTimeLeft; + g_EventQueue.AddEvent( this, "ShowHint", 0.01f, NULL, this ); + m_iTimeout = iOriginalTimeout; +} +#endif + +//----------------------------------------------------------------------------- +// Purpose: Input handler for showing the message and/or playing the sound. +//----------------------------------------------------------------------------- +void CEnvInstructorHint::InputShowHint( inputdata_t &inputdata ) +{ + IGameEvent * event = gameeventmanager->CreateEvent( "instructor_server_hint_create", false ); + if ( event ) + { + CBaseEntity *pTargetEntity = NULL; + +#ifdef MAPBASE + pTargetEntity = m_hTarget; + + if ( pTargetEntity == NULL ) +#endif + pTargetEntity = gEntList.FindEntityByName( NULL, m_iszHintTargetEntity ); + + if( pTargetEntity == NULL && !m_bStatic ) + pTargetEntity = inputdata.pActivator; + + if( pTargetEntity == NULL ) + pTargetEntity = GetWorldEntity(); + + char szColorString[128]; + Q_snprintf( szColorString, sizeof( szColorString ), "%.3d,%.3d,%.3d", m_Color.r, m_Color.g, m_Color.b ); + + int iFlags = 0; + + iFlags |= (m_iPulseOption == 0) ? 0 : (LOCATOR_ICON_FX_PULSE_SLOW << (m_iPulseOption - 1)); + iFlags |= (m_iAlphaOption == 0) ? 0 : (LOCATOR_ICON_FX_ALPHA_SLOW << (m_iAlphaOption - 1)); + iFlags |= (m_iShakeOption == 0) ? 0 : (LOCATOR_ICON_FX_SHAKE_NARROW << (m_iShakeOption - 1)); + iFlags |= m_bStatic ? LOCATOR_ICON_FX_STATIC : 0; + + CBasePlayer *pActivator = NULL; + bool bFilterByActivator = m_bLocalPlayerOnly; + +#ifdef INFESTED_DLL + CASW_Marine *pMarine = dynamic_cast( inputdata.pActivator ); + if ( pMarine ) + { + pActivator = pMarine->GetCommander(); + } +#else +#ifdef MAPBASE + if ( m_hActivator ) + { + pActivator = m_hActivator; + bFilterByActivator = m_bFilterByActivator; + } + else +#endif + + if ( inputdata.value.StringID() != NULL_STRING ) + { + CBaseEntity *pTarget = gEntList.FindEntityByName( NULL, inputdata.value.String() ); + pActivator = dynamic_cast( pTarget ); + if ( pActivator ) + { + bFilterByActivator = true; + } + } + else + { + if ( GameRules()->IsMultiplayer() == false ) + { + pActivator = UTIL_GetLocalPlayer(); + } + else + { + Warning( "Failed to play server side instructor hint: no player specified for hint\n" ); + Assert( 0 ); + } + } +#endif + + const char *pActivatorCaption = m_iszActivatorCaption.ToCStr(); + if ( !pActivatorCaption || pActivatorCaption[ 0 ] == '\0' ) + { + pActivatorCaption = m_iszCaption.ToCStr(); + } + + event->SetString( "hint_name", GetEntityName().ToCStr() ); + event->SetString( "hint_replace_key", m_iszReplace_Key.ToCStr() ); + event->SetInt( "hint_target", pTargetEntity->entindex() ); + event->SetInt( "hint_activator_userid", ( pActivator ? pActivator->GetUserID() : 0 ) ); + event->SetInt( "hint_timeout", m_iTimeout ); + event->SetString( "hint_icon_onscreen", m_iszIcon_Onscreen.ToCStr() ); + event->SetString( "hint_icon_offscreen", m_iszIcon_Offscreen.ToCStr() ); + event->SetString( "hint_caption", m_iszCaption.ToCStr() ); + event->SetString( "hint_activator_caption", pActivatorCaption ); + event->SetString( "hint_color", szColorString ); + event->SetFloat( "hint_icon_offset", m_fIconOffset ); + event->SetFloat( "hint_range", m_fRange ); + event->SetInt( "hint_flags", iFlags ); + event->SetString( "hint_binding", m_iszBinding.ToCStr() ); + event->SetBool( "hint_allow_nodraw_target", m_bAllowNoDrawTarget ); + event->SetBool( "hint_nooffscreen", m_bNoOffscreen ); + event->SetBool( "hint_forcecaption", m_bForceCaption ); + event->SetBool( "hint_local_player_only", bFilterByActivator ); +#ifdef MAPBASE + event->SetString( "hint_start_sound", m_iszStartSound.ToCStr() ); + event->SetInt( "hint_target_pos", m_iHintTargetPos ); +#endif + + gameeventmanager->FireEvent( event ); + +#ifdef MAPBASE + m_flActiveUntil = gpGlobals->curtime + m_iTimeout; + m_hTarget = pTargetEntity; + m_hActivator = pActivator; + m_bFilterByActivator = bFilterByActivator; +#endif + } +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CEnvInstructorHint::InputEndHint( inputdata_t &inputdata ) +{ + IGameEvent * event = gameeventmanager->CreateEvent( "instructor_server_hint_stop", false ); + if ( event ) + { + event->SetString( "hint_name", GetEntityName().ToCStr() ); + + gameeventmanager->FireEvent( event ); + +#ifdef MAPBASE + m_flActiveUntil = -1.0f; + m_hActivator = NULL; + m_hTarget = NULL; + m_bFilterByActivator = false; +#endif + } +} + +//----------------------------------------------------------------------------- +// Purpose: A generic target entity that gets replicated to the client for instructor hint targetting +//----------------------------------------------------------------------------- +class CInfoInstructorHintTarget : public CPointEntity +{ +public: + DECLARE_CLASS( CInfoInstructorHintTarget, CPointEntity ); + + virtual int UpdateTransmitState( void ) // set transmit filter to transmit always + { + return SetTransmitState( FL_EDICT_ALWAYS ); + } + + DECLARE_DATADESC(); +}; + +LINK_ENTITY_TO_CLASS( info_target_instructor_hint, CInfoInstructorHintTarget ); + +BEGIN_DATADESC( CInfoInstructorHintTarget ) + +END_DATADESC() diff --git a/src/game/server/env_projectedtexture.cpp b/src/game/server/env_projectedtexture.cpp index 3cdacb40..15fb1367 100644 --- a/src/game/server/env_projectedtexture.cpp +++ b/src/game/server/env_projectedtexture.cpp @@ -6,11 +6,471 @@ #include "cbase.h" #include "shareddefs.h" +#ifdef ASW_PROJECTED_TEXTURES +#include "env_projectedtexture.h" +#endif // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" +#ifdef ASW_PROJECTED_TEXTURES + +LINK_ENTITY_TO_CLASS( env_projectedtexture, CEnvProjectedTexture ); + +BEGIN_DATADESC( CEnvProjectedTexture ) + DEFINE_FIELD( m_hTargetEntity, FIELD_EHANDLE ), +#ifdef MAPBASE + DEFINE_KEYFIELD( m_bDontFollowTarget, FIELD_BOOLEAN, "dontfollowtarget" ), + DEFINE_FIELD( m_bAlwaysUpdate, FIELD_BOOLEAN ), +#endif + DEFINE_FIELD( m_bState, FIELD_BOOLEAN ), + DEFINE_KEYFIELD( m_flLightFOV, FIELD_FLOAT, "lightfov" ), +#ifdef MAPBASE + DEFINE_KEYFIELD( m_flLightHorFOV, FIELD_FLOAT, "lighthorfov" ), +#endif + DEFINE_KEYFIELD( m_bEnableShadows, FIELD_BOOLEAN, "enableshadows" ), + DEFINE_KEYFIELD( m_bLightOnlyTarget, FIELD_BOOLEAN, "lightonlytarget" ), + DEFINE_KEYFIELD( m_bLightWorld, FIELD_BOOLEAN, "lightworld" ), + DEFINE_KEYFIELD( m_bCameraSpace, FIELD_BOOLEAN, "cameraspace" ), + DEFINE_KEYFIELD( m_flAmbient, FIELD_FLOAT, "ambient" ), + DEFINE_AUTO_ARRAY( m_SpotlightTextureName, FIELD_CHARACTER ), + DEFINE_KEYFIELD( m_nSpotlightTextureFrame, FIELD_INTEGER, "textureframe" ), + DEFINE_KEYFIELD( m_flNearZ, FIELD_FLOAT, "nearz" ), + DEFINE_KEYFIELD( m_flFarZ, FIELD_FLOAT, "farz" ), + DEFINE_KEYFIELD( m_nShadowQuality, FIELD_INTEGER, "shadowquality" ), +#ifdef MAPBASE + DEFINE_KEYFIELD( m_bAlwaysDraw, FIELD_BOOLEAN, "alwaysdraw" ), + DEFINE_KEYFIELD( m_bProjectedTextureVersion, FIELD_BOOLEAN, "ProjectedTextureVersion" ), +#endif + DEFINE_KEYFIELD( m_flBrightnessScale, FIELD_FLOAT, "brightnessscale" ), + DEFINE_FIELD( m_LightColor, FIELD_COLOR32 ), + DEFINE_KEYFIELD( m_flColorTransitionTime, FIELD_FLOAT, "colortransitiontime" ), +#ifdef MAPBASE + DEFINE_FIELD( m_flConstantAtten, FIELD_FLOAT ), + DEFINE_FIELD( m_flLinearAtten, FIELD_FLOAT ), + DEFINE_FIELD( m_flQuadraticAtten, FIELD_FLOAT ), + DEFINE_KEYFIELD( m_flShadowAtten, FIELD_FLOAT, "shadowatten" ), + DEFINE_KEYFIELD( m_flShadowFilter, FIELD_FLOAT, "shadowfilter" ), +#endif + + DEFINE_INPUTFUNC( FIELD_VOID, "TurnOn", InputTurnOn ), + DEFINE_INPUTFUNC( FIELD_VOID, "TurnOff", InputTurnOff ), + DEFINE_INPUTFUNC( FIELD_VOID, "AlwaysUpdateOn", InputAlwaysUpdateOn ), + DEFINE_INPUTFUNC( FIELD_VOID, "AlwaysUpdateOff", InputAlwaysUpdateOff ), + DEFINE_INPUTFUNC( FIELD_FLOAT, "FOV", InputSetFOV ), +#ifdef MAPBASE + DEFINE_INPUTFUNC( FIELD_FLOAT, "VerFOV", InputSetVerFOV ), + DEFINE_INPUTFUNC( FIELD_FLOAT, "HorFOV", InputSetHorFOV ), +#endif + DEFINE_INPUTFUNC( FIELD_EHANDLE, "Target", InputSetTarget ), + DEFINE_INPUTFUNC( FIELD_BOOLEAN, "CameraSpace", InputSetCameraSpace ), + DEFINE_INPUTFUNC( FIELD_BOOLEAN, "LightOnlyTarget", InputSetLightOnlyTarget ), + DEFINE_INPUTFUNC( FIELD_BOOLEAN, "LightWorld", InputSetLightWorld ), + DEFINE_INPUTFUNC( FIELD_BOOLEAN, "EnableShadows", InputSetEnableShadows ), + DEFINE_INPUTFUNC( FIELD_COLOR32, "LightColor", InputSetLightColor ), + DEFINE_INPUTFUNC( FIELD_FLOAT, "Ambient", InputSetAmbient ), + DEFINE_INPUTFUNC( FIELD_STRING, "SpotlightTexture", InputSetSpotlightTexture ), +#ifdef MAPBASE + DEFINE_INPUTFUNC( FIELD_INTEGER, "SetSpotlightFrame", InputSetSpotlightFrame ), + DEFINE_INPUTFUNC( FIELD_FLOAT, "SetBrightness", InputSetBrightness ), + DEFINE_INPUTFUNC( FIELD_FLOAT, "SetColorTransitionTime", InputSetColorTransitionTime ), + DEFINE_INPUTFUNC( FIELD_FLOAT, "SetQuadratic", InputSetQuadratic ), + DEFINE_INPUTFUNC( FIELD_FLOAT, "SetLinear", InputSetLinear ), + DEFINE_INPUTFUNC( FIELD_FLOAT, "SetConstant", InputSetConstant ), + DEFINE_INPUTFUNC( FIELD_FLOAT, "SetShadowAtten", InputSetShadowAtten ), + DEFINE_INPUTFUNC( FIELD_FLOAT, "SetFilter", InputSetFilter ), + DEFINE_INPUTFUNC( FIELD_FLOAT, "SetNearZ", InputSetNearZ ), + DEFINE_INPUTFUNC( FIELD_FLOAT, "SetFarZ", InputSetFarZ ), + DEFINE_INPUTFUNC( FIELD_VOID, "AlwaysDrawOn", InputAlwaysDrawOn ), + DEFINE_INPUTFUNC( FIELD_VOID, "AlwaysDrawOff", InputAlwaysDrawOff ), + DEFINE_INPUTFUNC( FIELD_VOID, "StopFollowingTarget", InputStopFollowingTarget ), + DEFINE_INPUTFUNC( FIELD_VOID, "StartFollowingTarget", InputStartFollowingTarget ), +#endif + DEFINE_THINKFUNC( InitialThink ), +END_DATADESC() + +IMPLEMENT_SERVERCLASS_ST( CEnvProjectedTexture, DT_EnvProjectedTexture ) + SendPropEHandle( SENDINFO( m_hTargetEntity ) ), +#ifdef MAPBASE + SendPropBool( SENDINFO( m_bDontFollowTarget ) ), +#endif + SendPropBool( SENDINFO( m_bState ) ), + SendPropBool( SENDINFO( m_bAlwaysUpdate ) ), + SendPropFloat( SENDINFO( m_flLightFOV ) ), +#ifdef MAPBASE + SendPropFloat( SENDINFO( m_flLightHorFOV ) ), +#endif + SendPropBool( SENDINFO( m_bEnableShadows ) ), + SendPropBool( SENDINFO( m_bLightOnlyTarget ) ), + SendPropBool( SENDINFO( m_bLightWorld ) ), + SendPropBool( SENDINFO( m_bCameraSpace ) ), + SendPropFloat( SENDINFO( m_flBrightnessScale ) ), + SendPropInt( SENDINFO ( m_LightColor ), 32, SPROP_UNSIGNED, SendProxy_Color32ToInt ), + SendPropFloat( SENDINFO( m_flColorTransitionTime ) ), + SendPropFloat( SENDINFO( m_flAmbient ) ), + SendPropString( SENDINFO( m_SpotlightTextureName ) ), + SendPropInt( SENDINFO( m_nSpotlightTextureFrame ) ), + SendPropFloat( SENDINFO( m_flNearZ ), 16, SPROP_ROUNDDOWN, 0.0f, 500.0f ), + SendPropFloat( SENDINFO( m_flFarZ ), 18, SPROP_ROUNDDOWN, 0.0f, 1500.0f ), + SendPropInt( SENDINFO( m_nShadowQuality ), 1, SPROP_UNSIGNED ), // Just one bit for now +#ifdef MAPBASE + SendPropFloat( SENDINFO( m_flConstantAtten ) ), + SendPropFloat( SENDINFO( m_flLinearAtten ) ), + SendPropFloat( SENDINFO( m_flQuadraticAtten ) ), + SendPropFloat( SENDINFO( m_flShadowAtten ) ), + SendPropFloat( SENDINFO( m_flShadowFilter ) ), + SendPropBool( SENDINFO( m_bAlwaysDraw ) ), + + // Not needed on the client right now, change when it actually is needed + //SendPropBool( SENDINFO( m_bProjectedTextureVersion ) ), +#endif +END_SEND_TABLE() + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CEnvProjectedTexture::CEnvProjectedTexture( void ) +{ + m_bState = true; + m_bAlwaysUpdate = false; + m_flLightFOV = 45.0f; + m_bEnableShadows = false; + m_bLightOnlyTarget = false; + m_bLightWorld = true; + m_bCameraSpace = false; + +#ifndef MAPBASE + Q_strcpy( m_SpotlightTextureName.GetForModify(), "effects/flashlight_border" ); +#endif + Q_strcpy( m_SpotlightTextureName.GetForModify(), "effects/flashlight001" ); + + m_nSpotlightTextureFrame = 0; + m_flBrightnessScale = 1.0f; + m_LightColor.Init( 255, 255, 255, 255 ); +#ifdef MAPBASE + m_flColorTransitionTime = 0.0f; +#else + m_flColorTransitionTime = 0.5f; +#endif + m_flAmbient = 0.0f; + m_flNearZ = 4.0f; + m_flFarZ = 750.0f; + m_nShadowQuality = 0; +#ifdef MAPBASE + m_flQuadraticAtten = 0.0f; + m_flLinearAtten = 100.0f; + m_flConstantAtten = 0.0f; + m_flShadowAtten = 0.0f; + m_flShadowFilter = 0.5f; +#endif +} + +void UTIL_ColorStringToLinearFloatColor( Vector &color, const char *pString ) +{ + float tmp[4]; + UTIL_StringToFloatArray( tmp, 4, pString ); + if( tmp[3] <= 0.0f ) + { + tmp[3] = 255.0f; + } + tmp[3] *= ( 1.0f / 255.0f ); + color.x = tmp[0] * ( 1.0f / 255.0f ) * tmp[3]; + color.y = tmp[1] * ( 1.0f / 255.0f ) * tmp[3]; + color.z = tmp[2] * ( 1.0f / 255.0f ) * tmp[3]; +} + +bool CEnvProjectedTexture::KeyValue( const char *szKeyName, const char *szValue ) +{ + if ( FStrEq( szKeyName, "lightcolor" ) ) + { +#ifdef MAPBASE + // + // Most existing projected textures don't have intensity implemented. + // This can give them 0% intensity, which makes them invisible. + // If intensity is not detected, assume 255. + // + // Also, get rid of the floats, for god's sake. Have you ever seen a color32 with decimals? + // + int tmp[4]; + tmp[3] = 255; + UTIL_StringToIntArray_PreserveArray( tmp, 4, szValue ); + + m_LightColor.SetR( tmp[0] ); + m_LightColor.SetG( tmp[1] ); + m_LightColor.SetB( tmp[2] ); + m_LightColor.SetA( tmp[3] ); +#else + float tmp[4]; + UTIL_StringToFloatArray( tmp, 4, szValue ); + + m_LightColor.SetR( tmp[0] ); + m_LightColor.SetG( tmp[1] ); + m_LightColor.SetB( tmp[2] ); + m_LightColor.SetA( tmp[3] ); +#endif + } + else if ( FStrEq( szKeyName, "texturename" ) ) + { +#if defined( _X360 ) + if ( Q_strcmp( szValue, "effects/flashlight001" ) == 0 ) + { + // Use this as the default for Xbox + Q_strcpy( m_SpotlightTextureName.GetForModify(), "effects/flashlight_border" ); + } + else + { + Q_strcpy( m_SpotlightTextureName.GetForModify(), szValue ); + } +#else + Q_strcpy( m_SpotlightTextureName.GetForModify(), szValue ); +#endif + } +#ifdef MAPBASE + else if ( FStrEq( szKeyName, "constant_attn" ) ) + { + m_flConstantAtten = CorrectConstantAtten( atof( szValue ) ); + } + else if ( FStrEq( szKeyName, "linear_attn" ) ) + { + m_flLinearAtten = CorrectLinearAtten( atof( szValue ) ); + } + else if ( FStrEq( szKeyName, "quadratic_attn" ) ) + { + m_flQuadraticAtten = CorrectQuadraticAtten( atof( szValue ) ); + } +#endif + else + { + return BaseClass::KeyValue( szKeyName, szValue ); + } + + return true; +} + +bool CEnvProjectedTexture::GetKeyValue( const char *szKeyName, char *szValue, int iMaxLen ) +{ + if ( FStrEq( szKeyName, "lightcolor" ) ) + { + Q_snprintf( szValue, iMaxLen, "%d %d %d %d", m_LightColor.GetR(), m_LightColor.GetG(), m_LightColor.GetB(), m_LightColor.GetA() ); + return true; + } + else if ( FStrEq( szKeyName, "texturename" ) ) + { + Q_snprintf( szValue, iMaxLen, "%s", m_SpotlightTextureName.Get() ); + return true; + } +#ifdef MAPBASE + else if ( FStrEq( szKeyName, "constant_attn" ) ) + { + // Undo correction + Q_snprintf( szValue, iMaxLen, "%f", m_flConstantAtten *= 2.0f ); + return true; + } + else if ( FStrEq( szKeyName, "linear_attn" ) ) + { + // Undo correction + Q_snprintf( szValue, iMaxLen, "%f", m_flLinearAtten *= 0.01f ); + return true; + } + else if ( FStrEq( szKeyName, "quadratic_attn" ) ) + { + // Undo correction + Q_snprintf( szValue, iMaxLen, "%f", m_flQuadraticAtten *= 0.0001f ); + return true; + } +#endif + return BaseClass::GetKeyValue( szKeyName, szValue, iMaxLen ); +} + +void CEnvProjectedTexture::InputTurnOn( inputdata_t &inputdata ) +{ + m_bState = true; +} + +void CEnvProjectedTexture::InputTurnOff( inputdata_t &inputdata ) +{ + m_bState = false; +} + +void CEnvProjectedTexture::InputAlwaysUpdateOn( inputdata_t &inputdata ) +{ + m_bAlwaysUpdate = true; +} + +void CEnvProjectedTexture::InputAlwaysUpdateOff( inputdata_t &inputdata ) +{ + m_bAlwaysUpdate = false; +} + +void CEnvProjectedTexture::InputSetFOV( inputdata_t &inputdata ) +{ + m_flLightFOV = inputdata.value.Float(); +#ifdef MAPBASE + m_flLightHorFOV = inputdata.value.Float(); +#endif +} + +#ifdef MAPBASE +void CEnvProjectedTexture::InputSetVerFOV( inputdata_t &inputdata ) +{ + m_flLightFOV = inputdata.value.Float(); +} + +void CEnvProjectedTexture::InputSetHorFOV( inputdata_t &inputdata ) +{ + m_flLightHorFOV = inputdata.value.Float(); +} + +void CEnvProjectedTexture::InputSetFilter( inputdata_t &inputdata ) +{ + m_flShadowFilter = inputdata.value.Float(); +} +#endif + +void CEnvProjectedTexture::InputSetTarget( inputdata_t &inputdata ) +{ +#ifdef MAPBASE + // env_projectedtexture's "Target" uses FIELD_EHANDLE while Mapbase's base entity "SetTarget" uses FIELD_STRING + if (inputdata.value.FieldType() != FIELD_EHANDLE) + inputdata.value.Convert(FIELD_EHANDLE, this, inputdata.pActivator, inputdata.pCaller); +#endif + m_hTargetEntity = inputdata.value.Entity(); +} + +void CEnvProjectedTexture::InputSetCameraSpace( inputdata_t &inputdata ) +{ + m_bCameraSpace = inputdata.value.Bool(); +} + +void CEnvProjectedTexture::InputSetLightOnlyTarget( inputdata_t &inputdata ) +{ + m_bLightOnlyTarget = inputdata.value.Bool(); +} + +void CEnvProjectedTexture::InputSetLightWorld( inputdata_t &inputdata ) +{ + m_bLightWorld = inputdata.value.Bool(); +} + +void CEnvProjectedTexture::InputSetEnableShadows( inputdata_t &inputdata ) +{ + m_bEnableShadows = inputdata.value.Bool(); +} + +void CEnvProjectedTexture::InputSetLightColor( inputdata_t &inputdata ) +{ + m_LightColor = inputdata.value.Color32(); +} + +void CEnvProjectedTexture::InputSetAmbient( inputdata_t &inputdata ) +{ + m_flAmbient = inputdata.value.Float(); +} + +void CEnvProjectedTexture::InputSetSpotlightTexture( inputdata_t &inputdata ) +{ + Q_strcpy( m_SpotlightTextureName.GetForModify(), inputdata.value.String() ); +} + +#ifdef MAPBASE +void CEnvProjectedTexture::InputSetSpotlightFrame( inputdata_t &inputdata ) +{ + m_nSpotlightTextureFrame = inputdata.value.Int(); +} + +void CEnvProjectedTexture::InputSetBrightness( inputdata_t &inputdata ) +{ + m_flBrightnessScale = inputdata.value.Float(); +} + +void CEnvProjectedTexture::InputSetColorTransitionTime( inputdata_t &inputdata ) +{ + m_flColorTransitionTime = inputdata.value.Float(); +} + +void CEnvProjectedTexture::InputSetNearZ( inputdata_t &inputdata ) +{ + m_flNearZ = inputdata.value.Float(); +} + +void CEnvProjectedTexture::InputSetFarZ( inputdata_t &inputdata ) +{ + m_flFarZ = inputdata.value.Float(); +} +#endif + +#ifdef MAPBASE +void CEnvProjectedTexture::Spawn( void ) +{ + // Set m_flLightHorFOV to m_flLightFOV if it's still 0, indicating either legacy support or desire to do this + if (m_flLightHorFOV == 0.0f) + { + m_flLightHorFOV = m_flLightFOV; + } + + m_bState = ( ( GetSpawnFlags() & ENV_PROJECTEDTEXTURE_STARTON ) != 0 ); + m_bAlwaysUpdate = ( ( GetSpawnFlags() & ENV_PROJECTEDTEXTURE_ALWAYSUPDATE ) != 0 ); + + BaseClass::Spawn(); +} +#endif + +void CEnvProjectedTexture::Activate( void ) +{ +#ifndef MAPBASE // Putting this in Activate() breaks projected textures which start off or don't start always updating in savegames. Moved to Spawn() instead + m_bState = ( ( GetSpawnFlags() & ENV_PROJECTEDTEXTURE_STARTON ) != 0 ); + m_bAlwaysUpdate = ( ( GetSpawnFlags() & ENV_PROJECTEDTEXTURE_ALWAYSUPDATE ) != 0 ); +#endif + + SetThink( &CEnvProjectedTexture::InitialThink ); + SetNextThink( gpGlobals->curtime + 0.1f ); + +#ifdef MAPBASE + if (m_bProjectedTextureVersion == 0 && GetMoveParent()) + { + // Pre-Mapbase projected textures should use the VDC parenting fix. + m_bAlwaysUpdate = true; + } +#endif + + BaseClass::Activate(); +} + +#ifdef MAPBASE +void CEnvProjectedTexture::SetParent( CBaseEntity* pNewParent, int iAttachment ) +{ + BaseClass::SetParent( pNewParent, iAttachment ); + + if (m_bProjectedTextureVersion == 0) + { + // Pre-Mapbase projected textures should use the VDC parenting fix. + // Since the ASW changes structure projected textures differently, + // we have to set it here on the server when our parent changes. + // Don't run this code if we already have the spawnflag though. + if ( ( GetSpawnFlags() & ENV_PROJECTEDTEXTURE_ALWAYSUPDATE ) == 0 ) + { + m_bAlwaysUpdate = GetMoveParent() != NULL; + } + } +} +#endif + +void CEnvProjectedTexture::InitialThink( void ) +{ + m_hTargetEntity = gEntList.FindEntityByName( NULL, m_target ); +} + +int CEnvProjectedTexture::UpdateTransmitState() +{ + return SetTransmitState( FL_EDICT_ALWAYS ); +} + +#else + #define ENV_PROJECTEDTEXTURE_STARTON (1<<0) +#ifdef MAPBASE +#define ENV_PROJECTEDTEXTURE_ALWAYSUPDATE (1<<1) +#endif //----------------------------------------------------------------------------- // Purpose: @@ -31,13 +491,21 @@ public: void InputTurnOn( inputdata_t &inputdata ); void InputTurnOff( inputdata_t &inputdata ); +#ifdef MAPBASE + void InputAlwaysUpdateOn( inputdata_t &inputdata ); + void InputAlwaysUpdateOff( inputdata_t &inputdata ); +#endif void InputSetFOV( inputdata_t &inputdata ); void InputSetTarget( inputdata_t &inputdata ); void InputSetCameraSpace( inputdata_t &inputdata ); void InputSetLightOnlyTarget( inputdata_t &inputdata ); void InputSetLightWorld( inputdata_t &inputdata ); void InputSetEnableShadows( inputdata_t &inputdata ); +#ifndef MAPBASE // void InputSetLightColor( inputdata_t &inputdata ); +#else + void InputSetLightColor( inputdata_t &inputdata ); +#endif void InputSetSpotlightTexture( inputdata_t &inputdata ); void InputSetAmbient( inputdata_t &inputdata ); @@ -48,6 +516,9 @@ public: private: CNetworkVar( bool, m_bState ); +#ifdef MAPBASE + CNetworkVar( bool, m_bAlwaysUpdate ); +#endif CNetworkVar( float, m_flLightFOV ); CNetworkVar( bool, m_bEnableShadows ); CNetworkVar( bool, m_bLightOnlyTarget ); @@ -73,7 +544,11 @@ BEGIN_DATADESC( CEnvProjectedTexture ) DEFINE_KEYFIELD( m_bLightWorld, FIELD_BOOLEAN, "lightworld" ), DEFINE_KEYFIELD( m_bCameraSpace, FIELD_BOOLEAN, "cameraspace" ), DEFINE_KEYFIELD( m_flAmbient, FIELD_FLOAT, "ambient" ), +#ifndef MAPBASE DEFINE_AUTO_ARRAY_KEYFIELD( m_SpotlightTextureName, FIELD_CHARACTER, "texturename" ), +#else + DEFINE_AUTO_ARRAY( m_SpotlightTextureName, FIELD_CHARACTER ), +#endif DEFINE_KEYFIELD( m_nSpotlightTextureFrame, FIELD_INTEGER, "textureframe" ), DEFINE_KEYFIELD( m_flNearZ, FIELD_FLOAT, "nearz" ), DEFINE_KEYFIELD( m_flFarZ, FIELD_FLOAT, "farz" ), @@ -82,14 +557,22 @@ BEGIN_DATADESC( CEnvProjectedTexture ) DEFINE_INPUTFUNC( FIELD_VOID, "TurnOn", InputTurnOn ), DEFINE_INPUTFUNC( FIELD_VOID, "TurnOff", InputTurnOff ), +#ifdef MAPBASE + DEFINE_INPUTFUNC( FIELD_VOID, "AlwaysUpdateOn", InputAlwaysUpdateOn ), + DEFINE_INPUTFUNC( FIELD_VOID, "AlwaysUpdateOff", InputAlwaysUpdateOff ), +#endif DEFINE_INPUTFUNC( FIELD_FLOAT, "FOV", InputSetFOV ), DEFINE_INPUTFUNC( FIELD_EHANDLE, "Target", InputSetTarget ), DEFINE_INPUTFUNC( FIELD_BOOLEAN, "CameraSpace", InputSetCameraSpace ), DEFINE_INPUTFUNC( FIELD_BOOLEAN, "LightOnlyTarget", InputSetLightOnlyTarget ), DEFINE_INPUTFUNC( FIELD_BOOLEAN, "LightWorld", InputSetLightWorld ), DEFINE_INPUTFUNC( FIELD_BOOLEAN, "EnableShadows", InputSetEnableShadows ), +#ifndef MAPBASE // this is broken . . need to be able to set color and intensity like light_dynamic // DEFINE_INPUTFUNC( FIELD_COLOR32, "LightColor", InputSetLightColor ), +#else + DEFINE_INPUTFUNC( FIELD_STRING, "LightColor", InputSetLightColor ), +#endif DEFINE_INPUTFUNC( FIELD_FLOAT, "Ambient", InputSetAmbient ), DEFINE_INPUTFUNC( FIELD_STRING, "SpotlightTexture", InputSetSpotlightTexture ), DEFINE_THINKFUNC( InitialThink ), @@ -98,6 +581,9 @@ END_DATADESC() IMPLEMENT_SERVERCLASS_ST( CEnvProjectedTexture, DT_EnvProjectedTexture ) SendPropEHandle( SENDINFO( m_hTargetEntity ) ), SendPropBool( SENDINFO( m_bState ) ), +#ifdef MAPBASE + SendPropBool( SENDINFO( m_bAlwaysUpdate ) ), +#endif SendPropFloat( SENDINFO( m_flLightFOV ) ), SendPropBool( SENDINFO( m_bEnableShadows ) ), SendPropBool( SENDINFO( m_bLightOnlyTarget ) ), @@ -161,6 +647,12 @@ bool CEnvProjectedTexture::KeyValue( const char *szKeyName, const char *szValue UTIL_ColorStringToLinearFloatColor( tmp, szValue ); m_LinearFloatLightColor = tmp; } +#ifdef MAPBASE + else if ( FStrEq(szKeyName, "texturename") ) + { + Q_strcpy(m_SpotlightTextureName.GetForModify(), szValue); + } +#endif else { return BaseClass::KeyValue( szKeyName, szValue ); @@ -179,6 +671,18 @@ void CEnvProjectedTexture::InputTurnOff( inputdata_t &inputdata ) m_bState = false; } +#ifdef MAPBASE +void CEnvProjectedTexture::InputAlwaysUpdateOn( inputdata_t &inputdata ) +{ + m_bAlwaysUpdate = true; +} + +void CEnvProjectedTexture::InputAlwaysUpdateOff( inputdata_t &inputdata ) +{ + m_bAlwaysUpdate = false; +} +#endif + void CEnvProjectedTexture::InputSetFOV( inputdata_t &inputdata ) { m_flLightFOV = inputdata.value.Float(); @@ -209,10 +713,19 @@ void CEnvProjectedTexture::InputSetEnableShadows( inputdata_t &inputdata ) m_bEnableShadows = inputdata.value.Bool(); } +#ifndef MAPBASE //void CEnvProjectedTexture::InputSetLightColor( inputdata_t &inputdata ) //{ // m_cLightColor = inputdata.value.Color32(); //} +#else +void CEnvProjectedTexture::InputSetLightColor( inputdata_t &inputdata ) +{ + Vector tmp; + UTIL_ColorStringToLinearFloatColor( tmp, inputdata.value.String() ); + m_LinearFloatLightColor = tmp; +} +#endif void CEnvProjectedTexture::InputSetAmbient( inputdata_t &inputdata ) { @@ -230,6 +743,9 @@ void CEnvProjectedTexture::Activate( void ) { m_bState = true; } +#ifdef MAPBASE + m_bAlwaysUpdate = ( ( GetSpawnFlags() & ENV_PROJECTEDTEXTURE_ALWAYSUPDATE ) != 0 ); +#endif SetThink( &CEnvProjectedTexture::InitialThink ); SetNextThink( gpGlobals->curtime + 0.1f ); @@ -239,7 +755,19 @@ void CEnvProjectedTexture::Activate( void ) void CEnvProjectedTexture::InitialThink( void ) { +#ifndef MAPBASE m_hTargetEntity = gEntList.FindEntityByName( NULL, m_target ); +#else + if (m_hTargetEntity == NULL && m_target != NULL_STRING) + m_hTargetEntity = gEntList.FindEntityByName(NULL, m_target); + if (m_hTargetEntity == NULL) + return; + Vector vecToTarget = (m_hTargetEntity->GetAbsOrigin() - GetAbsOrigin()); + QAngle vecAngles; + VectorAngles(vecToTarget, vecAngles); + SetAbsAngles(vecAngles); + SetNextThink(gpGlobals->curtime + 0.1); +#endif } int CEnvProjectedTexture::UpdateTransmitState() @@ -247,6 +775,8 @@ int CEnvProjectedTexture::UpdateTransmitState() return SetTransmitState( FL_EDICT_ALWAYS ); } +#endif + // Console command for creating env_projectedtexture entities void CC_CreateFlashlight( const CCommand &args ) diff --git a/src/game/server/env_projectedtexture.h b/src/game/server/env_projectedtexture.h new file mode 100644 index 00000000..6cb248ba --- /dev/null +++ b/src/game/server/env_projectedtexture.h @@ -0,0 +1,122 @@ + +#ifndef ENV_PROJECTEDTEXTURE_H +#define ENV_PROJECTEDTEXTURE_H +#ifdef _WIN32 +#pragma once +#endif + +#define ENV_PROJECTEDTEXTURE_STARTON (1<<0) +#define ENV_PROJECTEDTEXTURE_ALWAYSUPDATE (1<<1) + +#ifdef ASW_PROJECTED_TEXTURES +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class CEnvProjectedTexture : public CPointEntity +{ + DECLARE_CLASS( CEnvProjectedTexture, CPointEntity ); +public: + DECLARE_DATADESC(); + DECLARE_SERVERCLASS(); + + CEnvProjectedTexture(); + bool KeyValue( const char *szKeyName, const char *szValue ); + virtual bool GetKeyValue( const char *szKeyName, char *szValue, int iMaxLen ); + + // Always transmit to clients + virtual int UpdateTransmitState(); +#ifdef MAPBASE + virtual void Spawn( void ); +#endif + virtual void Activate( void ); +#ifdef MAPBASE + void SetParent( CBaseEntity* pNewParent, int iAttachment = -1 ); +#endif + + void InputTurnOn( inputdata_t &inputdata ); + void InputTurnOff( inputdata_t &inputdata ); + void InputAlwaysUpdateOn( inputdata_t &inputdata ); + void InputAlwaysUpdateOff( inputdata_t &inputdata ); + void InputSetFOV( inputdata_t &inputdata ); +#ifdef MAPBASE + void InputSetVerFOV( inputdata_t &inputdata ); + void InputSetHorFOV( inputdata_t &inputdata ); +#endif + void InputSetTarget( inputdata_t &inputdata ); + void InputSetCameraSpace( inputdata_t &inputdata ); + void InputSetLightOnlyTarget( inputdata_t &inputdata ); + void InputSetLightWorld( inputdata_t &inputdata ); + void InputSetEnableShadows( inputdata_t &inputdata ); + void InputSetLightColor( inputdata_t &inputdata ); + void InputSetSpotlightTexture( inputdata_t &inputdata ); + void InputSetAmbient( inputdata_t &inputdata ); +#ifdef MAPBASE + void InputSetSpotlightFrame( inputdata_t &inputdata ); + void InputSetBrightness( inputdata_t &inputdata ); + void InputSetColorTransitionTime( inputdata_t &inputdata ); + void InputSetConstant( inputdata_t &inputdata ) { m_flConstantAtten = CorrectConstantAtten(inputdata.value.Float()); } + void InputSetLinear( inputdata_t &inputdata ) { m_flLinearAtten = CorrectLinearAtten(inputdata.value.Float()); } + void InputSetQuadratic( inputdata_t &inputdata ) { m_flQuadraticAtten = CorrectQuadraticAtten(inputdata.value.Float()); } + void InputSetShadowAtten( inputdata_t &inputdata ) { m_flShadowAtten = inputdata.value.Float(); } + void InputSetNearZ( inputdata_t &inputdata ); + void InputSetFarZ( inputdata_t &inputdata ); + void InputAlwaysDrawOn( inputdata_t &inputdata ) { m_bAlwaysDraw = true; } + void InputAlwaysDrawOff( inputdata_t &inputdata ) { m_bAlwaysDraw = false; } + void InputStopFollowingTarget( inputdata_t &inputdata ) { m_bDontFollowTarget = true; } + void InputStartFollowingTarget( inputdata_t &inputdata ) { m_bDontFollowTarget = false; } + void InputSetFilter( inputdata_t &inputdata ); + + // Corrects keyvalue/input attenuation for internal FlashlightEffect_t attenuation. + float CorrectConstantAtten( float fl ) { return fl * 0.5f; } + float CorrectLinearAtten( float fl ) { return fl * 100.0f; } + float CorrectQuadraticAtten( float fl ) { return fl * 10000.0f; } +#endif + + void InitialThink( void ); + + CNetworkHandle( CBaseEntity, m_hTargetEntity ); +#ifdef MAPBASE + CNetworkVar( bool, m_bDontFollowTarget ); +#endif + +private: + + CNetworkVar( bool, m_bState ); + CNetworkVar( bool, m_bAlwaysUpdate ); + CNetworkVar( float, m_flLightFOV ); +#ifdef MAPBASE + CNetworkVar( float, m_flLightHorFOV ); +#endif + CNetworkVar( bool, m_bEnableShadows ); + CNetworkVar( bool, m_bLightOnlyTarget ); + CNetworkVar( bool, m_bLightWorld ); + CNetworkVar( bool, m_bCameraSpace ); + CNetworkVar( float, m_flBrightnessScale ); + CNetworkColor32( m_LightColor ); + CNetworkVar( float, m_flColorTransitionTime ); + CNetworkVar( float, m_flAmbient ); + CNetworkString( m_SpotlightTextureName, MAX_PATH ); + CNetworkVar( int, m_nSpotlightTextureFrame ); + CNetworkVar( float, m_flNearZ ); + CNetworkVar( float, m_flFarZ ); + CNetworkVar( int, m_nShadowQuality ); +#ifdef MAPBASE + CNetworkVar( float, m_flConstantAtten ); + CNetworkVar( float, m_flLinearAtten ); + CNetworkVar( float, m_flQuadraticAtten ); + CNetworkVar( float, m_flShadowAtten ); + + CNetworkVar( float, m_flShadowFilter ); + + CNetworkVar( bool, m_bAlwaysDraw ); + + // 1 = New projected texture + // 0 = Non-Mapbase projected texture, e.g. one that uses the VDC parenting fix instead of the spawnflag + // Not needed on the client right now, change to CNetworkVar when it actually is needed + bool m_bProjectedTextureVersion; +#endif +}; +#endif + + +#endif // ENV_PROJECTEDTEXTURE_H \ No newline at end of file diff --git a/src/game/server/env_screenoverlay.cpp b/src/game/server/env_screenoverlay.cpp index cc5356ff..14cf5b09 100644 --- a/src/game/server/env_screenoverlay.cpp +++ b/src/game/server/env_screenoverlay.cpp @@ -39,6 +39,9 @@ protected: CNetworkVar( float, m_flStartTime ); CNetworkVar( int, m_iDesiredOverlay ); CNetworkVar( bool, m_bIsActive ); +#ifdef MAPBASE + CNetworkVar( int, m_iOverlayIndex ); +#endif }; LINK_ENTITY_TO_CLASS( env_screenoverlay, CEnvScreenOverlay ); @@ -74,6 +77,9 @@ BEGIN_DATADESC( CEnvScreenOverlay ) DEFINE_FIELD( m_iDesiredOverlay, FIELD_INTEGER ), DEFINE_FIELD( m_flStartTime, FIELD_TIME ), DEFINE_FIELD( m_bIsActive, FIELD_BOOLEAN ), +#ifdef MAPBASE + DEFINE_KEYFIELD( m_iOverlayIndex, FIELD_INTEGER, "OverlayIndex" ), +#endif DEFINE_INPUTFUNC( FIELD_VOID, "StartOverlays", InputStartOverlay ), DEFINE_INPUTFUNC( FIELD_VOID, "StopOverlays", InputStopOverlay ), @@ -89,6 +95,9 @@ IMPLEMENT_SERVERCLASS_ST( CEnvScreenOverlay, DT_EnvScreenOverlay ) SendPropFloat( SENDINFO( m_flStartTime ), 32, SPROP_NOSCALE ), SendPropInt( SENDINFO( m_iDesiredOverlay ), 5 ), SendPropBool( SENDINFO( m_bIsActive ) ), +#ifdef MAPBASE + SendPropInt( SENDINFO( m_iOverlayIndex ), 5 ), +#endif END_SEND_TABLE() //----------------------------------------------------------------------------- @@ -99,6 +108,9 @@ CEnvScreenOverlay::CEnvScreenOverlay( void ) m_flStartTime = 0; m_iDesiredOverlay = 0; m_bIsActive = false; +#ifdef MAPBASE + m_iOverlayIndex = -1; +#endif } //----------------------------------------------------------------------------- diff --git a/src/game/server/env_tonemap_controller.cpp b/src/game/server/env_tonemap_controller.cpp index 613d4548..95cf6135 100644 --- a/src/game/server/env_tonemap_controller.cpp +++ b/src/game/server/env_tonemap_controller.cpp @@ -5,58 +5,18 @@ //============================================================================= #include "cbase.h" +#include "env_tonemap_controller.h" #include "baseentity.h" #include "entityoutput.h" #include "convar.h" + +#include "player.h" //Tony; need player.h so we can trigger inputs on the player, from our inputs! + // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" ConVar mat_hdr_tonemapscale( "mat_hdr_tonemapscale", "1.0", FCVAR_CHEAT, "The HDR tonemap scale. 1 = Use autoexposure, 0 = eyes fully closed, 16 = eyes wide open." ); -// 0 - eyes fully closed / fully black -// 1 - nominal -// 16 - eyes wide open / fully white - -//----------------------------------------------------------------------------- -// Purpose: Entity that controls player's tonemap -//----------------------------------------------------------------------------- -class CEnvTonemapController : public CPointEntity -{ - DECLARE_CLASS( CEnvTonemapController, CPointEntity ); -public: - DECLARE_DATADESC(); - DECLARE_SERVERCLASS(); - - void Spawn( void ); - int UpdateTransmitState( void ); - void UpdateTonemapScaleBlend( void ); - - // Inputs - void InputSetTonemapScale( inputdata_t &inputdata ); - void InputBlendTonemapScale( inputdata_t &inputdata ); - void InputSetTonemapRate( inputdata_t &inputdata ); - void InputSetAutoExposureMin( inputdata_t &inputdata ); - void InputSetAutoExposureMax( inputdata_t &inputdata ); - void InputUseDefaultAutoExposure( inputdata_t &inputdata ); - void InputSetBloomScale( inputdata_t &inputdata ); - void InputUseDefaultBloomScale( inputdata_t &inputdata ); - void InputSetBloomScaleRange( inputdata_t &inputdata ); - -private: - float m_flBlendTonemapStart; // HDR Tonemap at the start of the blend - float m_flBlendTonemapEnd; // Target HDR Tonemap at the end of the blend - float m_flBlendEndTime; // Time at which the blend ends - float m_flBlendStartTime; // Time at which the blend started - - CNetworkVar( bool, m_bUseCustomAutoExposureMin ); - CNetworkVar( bool, m_bUseCustomAutoExposureMax ); - CNetworkVar( bool, m_bUseCustomBloomScale ); - CNetworkVar( float, m_flCustomAutoExposureMin ); - CNetworkVar( float, m_flCustomAutoExposureMax ); - CNetworkVar( float, m_flCustomBloomScale); - CNetworkVar( float, m_flCustomBloomScaleMinimum); -}; - LINK_ENTITY_TO_CLASS( env_tonemap_controller, CEnvTonemapController ); BEGIN_DATADESC( CEnvTonemapController ) @@ -83,7 +43,11 @@ BEGIN_DATADESC( CEnvTonemapController ) DEFINE_INPUTFUNC( FIELD_VOID, "UseDefaultAutoExposure", InputUseDefaultAutoExposure ), DEFINE_INPUTFUNC( FIELD_VOID, "UseDefaultBloomScale", InputUseDefaultBloomScale ), DEFINE_INPUTFUNC( FIELD_FLOAT, "SetBloomScale", InputSetBloomScale ), +#ifdef MAPBASE + DEFINE_INPUTFUNC( FIELD_STRING, "SetBloomScaleRange", InputSetBloomScaleRange ), +#else DEFINE_INPUTFUNC( FIELD_FLOAT, "SetBloomScaleRange", InputSetBloomScaleRange ), +#endif END_DATADESC() IMPLEMENT_SERVERCLASS_ST( CEnvTonemapController, DT_EnvTonemapController ) @@ -113,6 +77,67 @@ int CEnvTonemapController::UpdateTransmitState() return SetTransmitState( FL_EDICT_ALWAYS ); } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CEnvTonemapController::KeyValue( const char *szKeyName, const char *szValue ) +{ + if ( FStrEq( szKeyName, "TonemapScale" ) ) + { + float flTonemapScale = atof( szValue ); + if (flTonemapScale != -1.0f) + { + mat_hdr_tonemapscale.SetValue( flTonemapScale ); + } + } + else if (FStrEq( szKeyName, "TonemapRate" )) + { + float flTonemapRate = atof( szValue ); + if (flTonemapRate != -1.0f) + { + ConVarRef mat_hdr_manual_tonemap_rate( "mat_hdr_manual_tonemap_rate" ); + if ( mat_hdr_manual_tonemap_rate.IsValid() ) + { + mat_hdr_manual_tonemap_rate.SetValue( flTonemapRate ); + } + } + } + else if (FStrEq( szKeyName, "AutoExposureMin" )) + { + float flAutoExposureMin = atof( szValue ); + if (flAutoExposureMin != -1.0f) + { + m_flCustomAutoExposureMin = flAutoExposureMin; + m_bUseCustomAutoExposureMin = true; + } + } + else if (FStrEq( szKeyName, "AutoExposureMax" )) + { + float flAutoExposureMax = atof( szValue ); + if (flAutoExposureMax != -1.0f) + { + m_flCustomAutoExposureMax = flAutoExposureMax; + m_bUseCustomAutoExposureMax = true; + } + } + else if (FStrEq( szKeyName, "BloomScale" )) + { + float flBloomScale = atof( szValue ); + if (flBloomScale != -1.0f) + { + m_flCustomBloomScale = flBloomScale; + m_flCustomBloomScaleMinimum = flBloomScale; + m_bUseCustomBloomScale = true; + } + } + else + return BaseClass::KeyValue( szKeyName, szValue ); + + return true; +} +#endif + //----------------------------------------------------------------------------- // Purpose: Set the tonemap scale to the specified value //----------------------------------------------------------------------------- @@ -162,7 +187,7 @@ void CEnvTonemapController::InputBlendTonemapScale( inputdata_t &inputdata ) void CEnvTonemapController::InputSetBloomScaleRange( inputdata_t &inputdata ) { float bloom_max=1, bloom_min=1; - int nargs=sscanf("%f %f",inputdata.value.String(), bloom_max, bloom_min ); + int nargs=sscanf( inputdata.value.String(), "%f %f", &bloom_max, &bloom_min ); if (nargs != 2) { Warning("%s (%s) received SetBloomScaleRange input without 2 arguments. Syntax: \n", GetClassname(), GetDebugName() ); @@ -170,6 +195,9 @@ void CEnvTonemapController::InputSetBloomScaleRange( inputdata_t &inputdata ) } m_flCustomBloomScale=bloom_max; m_flCustomBloomScaleMinimum=bloom_min; +#ifdef MAPBASE + m_bUseCustomBloomScale = true; +#endif } //----------------------------------------------------------------------------- @@ -247,3 +275,109 @@ void CEnvTonemapController::InputUseDefaultBloomScale( inputdata_t &inputdata ) { m_bUseCustomBloomScale = false; } + +#ifdef MAPBASE // From Alien Swarm SDK +//-------------------------------------------------------------------------------------------------------- +LINK_ENTITY_TO_CLASS( trigger_tonemap, CTonemapTrigger ); + +BEGIN_DATADESC( CTonemapTrigger ) + DEFINE_KEYFIELD( m_tonemapControllerName, FIELD_STRING, "TonemapName" ), +END_DATADESC() + + +//-------------------------------------------------------------------------------------------------------- +void CTonemapTrigger::Spawn( void ) +{ + AddSpawnFlags( SF_TRIGGER_ALLOW_CLIENTS ); + + BaseClass::Spawn(); + InitTrigger(); + + m_hTonemapController = gEntList.FindEntityByName( NULL, m_tonemapControllerName ); +} + + +//-------------------------------------------------------------------------------------------------------- +void CTonemapTrigger::StartTouch( CBaseEntity *other ) +{ + if ( !PassesTriggerFilters( other ) ) + return; + + BaseClass::StartTouch( other ); + + CBasePlayer *player = ToBasePlayer( other ); + if ( !player ) + return; + + player->OnTonemapTriggerStartTouch( this ); +} + + +//-------------------------------------------------------------------------------------------------------- +void CTonemapTrigger::EndTouch( CBaseEntity *other ) +{ + if ( !PassesTriggerFilters( other ) ) + return; + + BaseClass::EndTouch( other ); + + CBasePlayer *player = ToBasePlayer( other ); + if ( !player ) + return; + + player->OnTonemapTriggerEndTouch( this ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Clear out the tonemap controller. +//----------------------------------------------------------------------------- +void CTonemapSystem::LevelInitPreEntity( void ) +{ + m_hMasterController = NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: On level load find the master fog controller. If no controller is +// set as Master, use the first fog controller found. +//----------------------------------------------------------------------------- +void CTonemapSystem::LevelInitPostEntity( void ) +{ + // Overall master controller + CEnvTonemapController *pTonemapController = NULL; + do + { + pTonemapController = static_cast( gEntList.FindEntityByClassname( pTonemapController, "env_tonemap_controller" ) ); + if ( pTonemapController ) + { + if ( m_hMasterController == NULL ) + { + m_hMasterController = pTonemapController; + } + else + { + if ( pTonemapController->IsMaster() ) + { + m_hMasterController = pTonemapController; + } + } + } + } while ( pTonemapController ); + + +} + + +//-------------------------------------------------------------------------------------------------------- +CTonemapSystem s_TonemapSystem( "TonemapSystem" ); + + +//-------------------------------------------------------------------------------------------------------- +CTonemapSystem *TheTonemapSystem( void ) +{ + return &s_TonemapSystem; +} + + +//-------------------------------------------------------------------------------------------------------- +#endif diff --git a/src/game/server/env_tonemap_controller.h b/src/game/server/env_tonemap_controller.h new file mode 100644 index 00000000..94e3e0f4 --- /dev/null +++ b/src/game/server/env_tonemap_controller.h @@ -0,0 +1,140 @@ +//========= Mapbase - https://github.com/mapbase-source/source-sdk-2013 ============// +// +// Note that this header exists in the Alien Swarm SDK, but not in stock Source SDK 2013. +// Although technically a new Mapbase file, it only serves to move otherwise identical code, +// so most code and repo conventions will pretend it was always there. +// +// -------------------------------------------------------------------- +// +// Purpose: +// +//=============================================================================// + +#ifndef ENV_TONEMAP_CONTROLLER_H +#define ENV_TONEMAP_CONTROLLER_H + +#include "triggers.h" + +// 0 - eyes fully closed / fully black +// 1 - nominal +// 16 - eyes wide open / fully white + +#ifdef MAPBASE // From Alien Swarm SDK +// Spawn Flags +#define SF_TONEMAP_MASTER 0x0001 +#endif + +//----------------------------------------------------------------------------- +// Purpose: Entity that controls player's tonemap +//----------------------------------------------------------------------------- +class CEnvTonemapController : public CPointEntity +{ + DECLARE_CLASS( CEnvTonemapController, CPointEntity ); +public: + DECLARE_DATADESC(); + DECLARE_SERVERCLASS(); + + void Spawn( void ); + int UpdateTransmitState( void ); + void UpdateTonemapScaleBlend( void ); + +#ifdef MAPBASE + bool IsMaster( void ) const { return HasSpawnFlags( SF_TONEMAP_MASTER ); } // From Alien Swarm SDK + + bool KeyValue( const char *szKeyName, const char *szValue ); +#endif + + // Inputs + void InputSetTonemapScale( inputdata_t &inputdata ); + void InputBlendTonemapScale( inputdata_t &inputdata ); + void InputSetTonemapRate( inputdata_t &inputdata ); + void InputSetAutoExposureMin( inputdata_t &inputdata ); + void InputSetAutoExposureMax( inputdata_t &inputdata ); + void InputUseDefaultAutoExposure( inputdata_t &inputdata ); + void InputSetBloomScale( inputdata_t &inputdata ); + void InputUseDefaultBloomScale( inputdata_t &inputdata ); + void InputSetBloomScaleRange( inputdata_t &inputdata ); + +private: + float m_flBlendTonemapStart; // HDR Tonemap at the start of the blend + float m_flBlendTonemapEnd; // Target HDR Tonemap at the end of the blend + float m_flBlendEndTime; // Time at which the blend ends + float m_flBlendStartTime; // Time at which the blend started + +#ifdef MAPBASE // From Alien Swarm SDK +public: +#endif + CNetworkVar( bool, m_bUseCustomAutoExposureMin ); + CNetworkVar( bool, m_bUseCustomAutoExposureMax ); + CNetworkVar( bool, m_bUseCustomBloomScale ); + CNetworkVar( float, m_flCustomAutoExposureMin ); + CNetworkVar( float, m_flCustomAutoExposureMax ); + CNetworkVar( float, m_flCustomBloomScale); + CNetworkVar( float, m_flCustomBloomScaleMinimum); +}; + +#ifdef MAPBASE // From Alien Swarm SDK +//-------------------------------------------------------------------------------------------------------- +class CTonemapTrigger : public CBaseTrigger +{ +public: + DECLARE_CLASS( CTonemapTrigger, CBaseTrigger ); + DECLARE_DATADESC(); + + virtual void Spawn( void ); + virtual void StartTouch( CBaseEntity *other ); + virtual void EndTouch( CBaseEntity *other ); + + CBaseEntity *GetTonemapController( void ) const; + +private: + string_t m_tonemapControllerName; + EHANDLE m_hTonemapController; +}; + + +//-------------------------------------------------------------------------------------------------------- +inline CBaseEntity *CTonemapTrigger::GetTonemapController( void ) const +{ + return m_hTonemapController.Get(); +} + + +//-------------------------------------------------------------------------------------------------------- +// Tonemap Controller System. +class CTonemapSystem : public CAutoGameSystem +{ +public: + + // Creation/Init. + CTonemapSystem( char const *name ) : CAutoGameSystem( name ) + { + m_hMasterController = NULL; + } + + ~CTonemapSystem() + { + m_hMasterController = NULL; + } + + virtual void LevelInitPreEntity(); + virtual void LevelInitPostEntity(); + CBaseEntity *GetMasterTonemapController( void ) const; + +private: + + EHANDLE m_hMasterController; +}; + + +//-------------------------------------------------------------------------------------------------------- +inline CBaseEntity *CTonemapSystem::GetMasterTonemapController( void ) const +{ + return m_hMasterController.Get(); +} + +//-------------------------------------------------------------------------------------------------------- +CTonemapSystem *TheTonemapSystem( void ); +#endif + +#endif //ENV_TONEMAP_CONTROLLER_H \ No newline at end of file diff --git a/src/game/server/env_zoom.cpp b/src/game/server/env_zoom.cpp index 2a9f8478..ec679152 100644 --- a/src/game/server/env_zoom.cpp +++ b/src/game/server/env_zoom.cpp @@ -23,6 +23,10 @@ public: void InputZoom( inputdata_t &inputdata ); void InputUnZoom( inputdata_t &inputdata ); +#ifdef MAPBASE + void InputUnZoomWithRate( inputdata_t &inputdata ); + void InputSetZoomRate( inputdata_t &inputdata ); +#endif int GetFOV( void ) { return m_nFOV; } float GetSpeed( void ) { return m_flSpeed; } @@ -43,6 +47,10 @@ BEGIN_DATADESC( CEnvZoom ) DEFINE_INPUTFUNC( FIELD_VOID, "Zoom", InputZoom ), DEFINE_INPUTFUNC( FIELD_VOID, "UnZoom", InputUnZoom ), +#ifdef MAPBASE + DEFINE_INPUTFUNC( FIELD_VOID, "UnZoomWithRate", InputUnZoomWithRate ), + DEFINE_INPUTFUNC( FIELD_FLOAT, "SetZoomRate", InputSetZoomRate ), +#endif END_DATADESC() @@ -114,3 +122,29 @@ void CEnvZoom::InputUnZoom( inputdata_t &inputdata ) } } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: +// Input : &inputdata - +//----------------------------------------------------------------------------- +void CEnvZoom::InputUnZoomWithRate( inputdata_t &inputdata ) +{ + CBasePlayer *pPlayer = UTIL_GetLocalPlayer(); + + if ( pPlayer ) + { + // Stuff the values + pPlayer->SetFOV( this, 0, m_flSpeed, m_nFOV ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : &inputdata - +//----------------------------------------------------------------------------- +void CEnvZoom::InputSetZoomRate( inputdata_t &inputdata ) +{ + m_flSpeed = inputdata.value.Float(); +} +#endif + diff --git a/src/game/server/envmicrophone.cpp b/src/game/server/envmicrophone.cpp index 9e85b0d9..23b1758c 100644 --- a/src/game/server/envmicrophone.cpp +++ b/src/game/server/envmicrophone.cpp @@ -19,6 +19,9 @@ #include "soundflags.h" #include "engine/IEngineSound.h" #include "filters.h" +#ifdef MAPBASE +#include "fmtstr.h" +#endif // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" @@ -27,6 +30,10 @@ const float MICROPHONE_SETTLE_EPSILON = 0.005; +#ifdef MAPBASE +static ConVar sv_microphones_always_pickup_sentences( "sv_microphones_always_pickup_sentences", "0", FCVAR_NONE, "Allows env_microphones to always detect and play back sentences, regardless of their keyvalues." ); +#endif + // List of env_microphones who want to be told whenever a sound is started static CUtlVector< CHandle > s_Microphones; @@ -44,6 +51,13 @@ BEGIN_DATADESC( CEnvMicrophone ) DEFINE_KEYFIELD(m_iszListenFilter, FIELD_STRING, "ListenFilter"), DEFINE_FIELD(m_hListenFilter, FIELD_EHANDLE), DEFINE_FIELD(m_hSpeaker, FIELD_EHANDLE), +#ifdef MAPBASE + DEFINE_KEYFIELD(m_iszLandmarkName, FIELD_STRING, "landmark"), + DEFINE_FIELD(m_hLandmark, FIELD_EHANDLE), + DEFINE_KEYFIELD(m_flPitchScale, FIELD_FLOAT, "PitchScale"), + DEFINE_KEYFIELD(m_flVolumeScale, FIELD_FLOAT, "VolumeScale"), + DEFINE_KEYFIELD(m_nChannel, FIELD_INTEGER, "channel"), +#endif // DEFINE_FIELD(m_bAvoidFeedback, FIELD_BOOLEAN), // DONT SAVE DEFINE_KEYFIELD(m_iSpeakerDSPPreset, FIELD_INTEGER, "speaker_dsp_preset" ), DEFINE_KEYFIELD(m_flMaxRange, FIELD_FLOAT, "MaxRange"), @@ -52,6 +66,12 @@ BEGIN_DATADESC( CEnvMicrophone ) DEFINE_INPUTFUNC(FIELD_VOID, "Enable", InputEnable), DEFINE_INPUTFUNC(FIELD_VOID, "Disable", InputDisable), DEFINE_INPUTFUNC(FIELD_STRING, "SetSpeakerName", InputSetSpeakerName), +#ifdef MAPBASE + DEFINE_INPUTFUNC(FIELD_INTEGER, "SetDSPPreset", InputSetDSPPreset), + DEFINE_INPUTFUNC( FIELD_FLOAT, "SetPitchScale", InputSetPitchScale ), + DEFINE_INPUTFUNC( FIELD_FLOAT, "SetVolumeScale", InputSetVolumeScale ), + DEFINE_INPUTFUNC( FIELD_INTEGER, "SetChannel", InputSetChannel ), +#endif DEFINE_OUTPUT(m_SoundLevel, "SoundLevel"), DEFINE_OUTPUT(m_OnRoutedSound, "OnRoutedSound" ), @@ -190,6 +210,13 @@ void CEnvMicrophone::ActivateSpeaker( void ) s_Microphones.AddToTail( this ); } } + +#ifdef MAPBASE + if (m_iszLandmarkName != NULL_STRING) + { + m_hLandmark = gEntList.FindEntityByName(NULL, m_iszLandmarkName, this, NULL, NULL); + } +#endif } //----------------------------------------------------------------------------- @@ -216,7 +243,11 @@ void CEnvMicrophone::InputDisable( inputdata_t &inputdata ) m_bDisabled = true; if ( m_hSpeaker ) { +#ifdef MAPBASE + CBaseEntity::StopSound( m_hSpeaker->entindex(), m_nChannel, m_szLastSound ); +#else CBaseEntity::StopSound( m_hSpeaker->entindex(), CHAN_STATIC, m_szLastSound ); +#endif m_szLastSound[0] = 0; // Remove ourselves from the list of active mics @@ -234,6 +265,45 @@ void CEnvMicrophone::InputSetSpeakerName( inputdata_t &inputdata ) SetSpeakerName( inputdata.value.StringID() ); } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: +// Input : &inputdata - +//----------------------------------------------------------------------------- +void CEnvMicrophone::InputSetDSPPreset( inputdata_t &inputdata ) +{ + m_iSpeakerDSPPreset = inputdata.value.Int(); + ActivateSpeaker(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : &inputdata - +//----------------------------------------------------------------------------- +void CEnvMicrophone::InputSetPitchScale( inputdata_t &inputdata ) +{ + m_flPitchScale = inputdata.value.Float(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : &inputdata - +//----------------------------------------------------------------------------- +void CEnvMicrophone::InputSetVolumeScale( inputdata_t &inputdata ) +{ + m_flVolumeScale = inputdata.value.Float(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : &inputdata - +//----------------------------------------------------------------------------- +void CEnvMicrophone::InputSetChannel( inputdata_t &inputdata ) +{ + m_nChannel = inputdata.value.Int(); +} +#endif + //----------------------------------------------------------------------------- // Purpose: Checks whether this microphone can hear a given sound, and at what // relative volume level. @@ -473,6 +543,21 @@ MicrophoneResult_t CEnvMicrophone::SoundPlayed( int entindex, const char *soundn } } +#ifdef MAPBASE + // Something similar to trigger_teleport landmarks for sounds transmitting to speaker + Vector vecOrigin = m_hSpeaker->GetAbsOrigin(); + if (m_hLandmark) + { + Vector vecSoundPos; + if (pOrigin) + vecSoundPos = *pOrigin; + else if (CBaseEntity *pEntity = CBaseEntity::Instance(engine->PEntityOfEntIndex(entindex))) + vecSoundPos = pEntity->GetAbsOrigin(); + + vecOrigin += (vecSoundPos - m_hLandmark->GetAbsOrigin()); + } +#endif + m_bAvoidFeedback = true; // Add the speaker flag. Detected at playback and applies the speaker filter. @@ -480,17 +565,41 @@ MicrophoneResult_t CEnvMicrophone::SoundPlayed( int entindex, const char *soundn CPASAttenuationFilter filter( m_hSpeaker ); EmitSound_t ep; - ep.m_nChannel = CHAN_STATIC; - ep.m_pSoundName = soundname; - ep.m_flVolume = flVolume; - ep.m_SoundLevel = soundlevel; - ep.m_nFlags = iFlags; - ep.m_nPitch = iPitch; - ep.m_pOrigin = &m_hSpeaker->GetAbsOrigin(); - ep.m_flSoundTime = soundtime; - ep.m_nSpeakerEntity = entindex; - CBaseEntity::EmitSound( filter, m_hSpeaker->entindex(), ep ); +#ifdef MAPBASE + if (m_bHearingSentence) + { + CBaseEntity::EmitSentenceByIndex( filter, m_hSpeaker->entindex(), m_nChannel, atoi(soundname), flVolume, soundlevel, 0, iPitch, &vecOrigin, NULL, true, soundtime, + m_iSpeakerDSPPreset, entindex ); + } + else +#endif + { +#ifdef MAPBASE + ep.m_nChannel = m_nChannel; + if (m_flVolumeScale != 1.0f) + ep.m_flVolume = (flVolume * m_flVolumeScale); + else + ep.m_flVolume = flVolume; + if (m_flPitchScale != 1.0f) + ep.m_nPitch = (int)((float)iPitch * m_flPitchScale); + else + ep.m_nPitch = iPitch; + ep.m_pOrigin = &vecOrigin; +#else + ep.m_nChannel = CHAN_STATIC; + ep.m_flVolume = flVolume; + ep.m_nPitch = iPitch; + ep.m_pOrigin = &m_hSpeaker->GetAbsOrigin(); +#endif + ep.m_pSoundName = soundname; + ep.m_SoundLevel = soundlevel; + ep.m_nFlags = iFlags; + ep.m_flSoundTime = soundtime; + ep.m_nSpeakerEntity = entindex; + + CBaseEntity::EmitSound( filter, m_hSpeaker->entindex(), ep ); + } Q_strncpy( m_szLastSound, soundname, sizeof(m_szLastSound) ); m_OnRoutedSound.FireOutput( this, this, 0 ); @@ -558,3 +667,56 @@ bool CEnvMicrophone::OnSoundPlayed( int entindex, const char *soundname, soundle return bSwallowed; } + +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: Called by the sound system whenever a sentence is played so that +// active microphones can have a chance to pick up the sound. +// Output : Returns whether or not the sentence was swallowed by the microphone. +// Swallowed sentences should not be played by the sound system. +//----------------------------------------------------------------------------- +bool CEnvMicrophone::OnSentencePlayed( int entindex, int sentenceIndex, soundlevel_t soundlevel, float flVolume, int iFlags, int iPitch, const Vector *pOrigin, float soundtime, CUtlVector< Vector >& soundorigins ) +{ + bool bSwallowed = false; + + // Loop through all registered microphones and tell them the sound was just played + int iCount = s_Microphones.Count(); + if ( iCount > 0 ) + { + CNumStr szSentenceStr( sentenceIndex ); + + // Iterate backwards because we might be deleting microphones. + for ( int i = iCount - 1; i >= 0; i-- ) + { + if ( s_Microphones[i] && (s_Microphones[i]->ShouldHearSentences() || sv_microphones_always_pickup_sentences.GetBool()) ) + { + // HACKHACK: Don't want to duplicate all of the code, so just use the same function with a new member variable + s_Microphones[i]->ToggleHearingSentence( true ); + MicrophoneResult_t eResult = s_Microphones[i]->SoundPlayed( + entindex, + szSentenceStr, + soundlevel, + flVolume, + iFlags, + iPitch, + pOrigin, + soundtime, + soundorigins ); + s_Microphones[i]->ToggleHearingSentence( false ); + + if ( eResult == MicrophoneResult_Swallow ) + { + // Microphone told us to swallow it + bSwallowed = true; + } + else if ( eResult == MicrophoneResult_Remove ) + { + s_Microphones.FastRemove( i ); + } + } + } + } + + return bSwallowed; +} +#endif diff --git a/src/game/server/envmicrophone.h b/src/game/server/envmicrophone.h index 1cad0761..e330099c 100644 --- a/src/game/server/envmicrophone.h +++ b/src/game/server/envmicrophone.h @@ -20,6 +20,9 @@ const int SF_MICROPHONE_SOUND_BULLET_IMPACT = 0x08; const int SF_MICROPHONE_SWALLOW_ROUTED_SOUNDS = 0x10; const int SF_MICROPHONE_SOUND_EXPLOSION = 0x20; const int SF_MICROPHONE_IGNORE_NONATTENUATED = 0x40; +#ifdef MAPBASE +const int SF_MICROPHONE_SOUND_SENTENCE = 0x80; +#endif // Return codes from SoundPlayed @@ -50,10 +53,20 @@ public: void SetSensitivity( float flSensitivity ); void SetSpeakerName( string_t iszSpeakerName ); +#ifdef MAPBASE + bool ShouldHearSentences() const { return HasSpawnFlags( SF_MICROPHONE_SOUND_SENTENCE ); } + inline void ToggleHearingSentence( bool bToggle ) { m_bHearingSentence = bToggle; } +#endif void InputEnable( inputdata_t &inputdata ); void InputDisable( inputdata_t &inputdata ); void InputSetSpeakerName( inputdata_t &inputdata ); +#ifdef MAPBASE + void InputSetDSPPreset( inputdata_t &inputdata ); + void InputSetPitchScale( inputdata_t &inputdata ); + void InputSetVolumeScale( inputdata_t &inputdata ); + void InputSetChannel( inputdata_t &inputdata ); +#endif DECLARE_DATADESC(); @@ -61,6 +74,12 @@ public: static bool OnSoundPlayed( int entindex, const char *soundname, soundlevel_t soundlevel, float flVolume, int iFlags, int iPitch, const Vector *pOrigin, float soundtime, CUtlVector< Vector >& soundorigins ); +#ifdef MAPBASE + // Same as above, except for sentences. + static bool OnSentencePlayed( int entindex, int sentenceIndex, soundlevel_t soundlevel, + float flVolume, int iFlags, int iPitch, const Vector *pOrigin, float soundtime, CUtlVector< Vector >& soundorigins ); +#endif + private: // Per-microphone notification that a sound has played. @@ -79,6 +98,15 @@ private: int m_iSpeakerDSPPreset; // Speaker DSP preset to use when this microphone is enabled string_t m_iszListenFilter; CHandle m_hListenFilter; +#ifdef MAPBASE + string_t m_iszLandmarkName; + EHANDLE m_hLandmark; + float m_flPitchScale = 1.0f; + float m_flVolumeScale = 1.0f; + int m_nChannel = CHAN_STATIC; + + bool m_bHearingSentence; // HACKHACK: Allows SoundPlayed() to know when to play a sentence instead +#endif COutputFloat m_SoundLevel; // Fired when the sampled volume level changes. COutputEvent m_OnRoutedSound; // Fired when a sound has been played through our speaker diff --git a/src/game/server/episodic/ai_behavior_passenger_companion.cpp b/src/game/server/episodic/ai_behavior_passenger_companion.cpp index f96c2a55..a9568c69 100644 --- a/src/game/server/episodic/ai_behavior_passenger_companion.cpp +++ b/src/game/server/episodic/ai_behavior_passenger_companion.cpp @@ -530,7 +530,11 @@ void CAI_PassengerBehaviorCompanion::GatherConditions( void ) AIEnemiesIter_t iter; for( AI_EnemyInfo_t *pEMemory = GetEnemies()->GetFirst(&iter); pEMemory != NULL; pEMemory = GetEnemies()->GetNext(&iter) ) { +#ifdef MAPBASE + if( GetOuter()->IRelationType( pEMemory->hEnemy ) <= D_FR ) +#else if( GetOuter()->IRelationType( pEMemory->hEnemy ) == D_HT ) +#endif { if( pEMemory->timeLastSeen == gpGlobals->curtime ) { diff --git a/src/game/server/episodic/npc_hunter.cpp b/src/game/server/episodic/npc_hunter.cpp index d55053bc..0699d890 100644 --- a/src/game/server/episodic/npc_hunter.cpp +++ b/src/game/server/episodic/npc_hunter.cpp @@ -63,6 +63,9 @@ #include "weapon_striderbuster.h" #include "monstermaker.h" #include "weapon_rpg.h" +#ifdef MAPBASE +#include "mapbase/GlobalStrings.h" +#endif // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" @@ -201,11 +204,19 @@ int g_interactionHunterFoundEnemy = 0; //----------------------------------------------------------------------------- // Local stuff. //----------------------------------------------------------------------------- +#ifdef MAPBASE +#define s_iszStriderClassname gm_isz_class_Strider +#define s_iszPhysPropClassname gm_isz_class_PropPhysics +static string_t s_iszStriderBusterClassname; +static string_t s_iszMagnadeClassname; +static string_t s_iszHuntersToRunOver; +#else static string_t s_iszStriderClassname; static string_t s_iszStriderBusterClassname; static string_t s_iszMagnadeClassname; static string_t s_iszPhysPropClassname; static string_t s_iszHuntersToRunOver; +#endif //----------------------------------------------------------------------------- @@ -1436,6 +1447,10 @@ private: string_t m_iszFollowTarget; // Name of the strider we should follow. CSimpleStopwatch m_BeginFollowDelay; +#ifdef MAPBASE + bool m_bNoIdlePatrol; +#endif + int m_nKillingDamageType; HunterEyeStates_t m_eEyeState; @@ -1538,6 +1553,10 @@ BEGIN_DATADESC( CNPC_Hunter ) DEFINE_KEYFIELD( m_iszFollowTarget, FIELD_STRING, "FollowTarget" ), +#ifdef MAPBASE + DEFINE_KEYFIELD( m_bNoIdlePatrol, FIELD_BOOLEAN, "NoIdlePatrol" ), +#endif + DEFINE_FIELD( m_aimYaw, FIELD_FLOAT ), DEFINE_FIELD( m_aimPitch, FIELD_FLOAT ), @@ -1675,8 +1694,16 @@ CNPC_Hunter::~CNPC_Hunter() //----------------------------------------------------------------------------- void CNPC_Hunter::Precache() { +#ifdef MAPBASE + if (GetModelName() == NULL_STRING) + SetModelName( AllocPooledString( "models/hunter.mdl" ) ); + + PrecacheModel( STRING( GetModelName() ) ); + PropBreakablePrecacheAll( GetModelName() ); +#else PrecacheModel( "models/hunter.mdl" ); PropBreakablePrecacheAll( MAKE_STRING("models/hunter.mdl") ); +#endif PrecacheScriptSound( "NPC_Hunter.Idle" ); PrecacheScriptSound( "NPC_Hunter.Scan" ); @@ -1737,7 +1764,11 @@ void CNPC_Hunter::Spawn() { Precache(); +#ifdef MAPBASE + SetModel( STRING( GetModelName() ) ); +#else SetModel( "models/hunter.mdl" ); +#endif BaseClass::Spawn(); //m_debugOverlays |= OVERLAY_NPC_ROUTE_BIT | OVERLAY_BBOX_BIT | OVERLAY_PIVOT_BIT; @@ -1973,11 +2004,17 @@ void CNPC_Hunter::Activate() { BaseClass::Activate(); +#ifdef MAPBASE + s_iszStriderBusterClassname = AllocPooledString( "weapon_striderbuster" ); + s_iszMagnadeClassname = AllocPooledString( "npc_grenade_magna" ); + s_iszHuntersToRunOver = AllocPooledString( "hunters_to_run_over" ); +#else s_iszStriderBusterClassname = AllocPooledString( "weapon_striderbuster" ); s_iszStriderClassname = AllocPooledString( "npc_strider" ); s_iszMagnadeClassname = AllocPooledString( "npc_grenade_magna" ); s_iszPhysPropClassname = AllocPooledString( "prop_physics" ); s_iszHuntersToRunOver = AllocPooledString( "hunters_to_run_over" ); +#endif // If no one has initialized the hunters to run over counter, just zero it out. if ( !GlobalEntity_IsInTable( s_iszHuntersToRunOver ) ) @@ -3106,6 +3143,9 @@ int CNPC_Hunter::SelectSchedule() { case NPC_STATE_IDLE: { +#ifdef MAPBASE + if (!m_bNoIdlePatrol) +#endif return SCHED_HUNTER_PATROL; } @@ -3487,7 +3527,13 @@ void CNPC_Hunter::StartTask( const Task_t *pTask ) { SetLastAttackTime( gpGlobals->curtime ); +#ifdef MAPBASE + // The "VS_PLAYER" animation looks better than the regular animation when used against non-humans + if ( GetEnemy() && (GetEnemy()->IsPlayer() || + (GetEnemy()->IsCombatCharacter() && GetEnemy()->MyCombatCharacterPointer()->GetHullType() != HULL_HUMAN)) ) +#else if ( GetEnemy() && GetEnemy()->IsPlayer() ) +#endif { ResetIdealActivity( ( Activity )ACT_HUNTER_MELEE_ATTACK1_VS_PLAYER ); } @@ -4089,7 +4135,11 @@ bool CNPC_Hunter::HandleChargeImpact( Vector vecImpact, CBaseEntity *pEntity ) } // Hit anything we don't like +#ifdef MAPBASE + if ( IRelationType( pEntity ) <= D_FR && ( GetNextAttack() < gpGlobals->curtime ) ) +#else if ( IRelationType( pEntity ) == D_HT && ( GetNextAttack() < gpGlobals->curtime ) ) +#endif { EmitSound( "NPC_Hunter.ChargeHitEnemy" ); @@ -4371,7 +4421,11 @@ void CNPC_Hunter::HandleAnimEvent( animevent_t *pEvent ) //----------------------------------------------------------------------------- void CNPC_Hunter::AddEntityRelationship( CBaseEntity *pEntity, Disposition_t nDisposition, int nPriority ) { +#ifdef MAPBASE + if ( nDisposition == D_HT && pEntity->ClassMatches(gm_isz_class_Bullseye) ) +#else if ( nDisposition == D_HT && pEntity->ClassMatches("npc_bullseye") ) +#endif UpdateEnemyMemory( pEntity, pEntity->GetAbsOrigin() ); BaseClass::AddEntityRelationship( pEntity, nDisposition, nPriority ); } @@ -5006,7 +5060,11 @@ int CNPC_Hunter::RangeAttack2Conditions( float flDot, float flDist ) { return COND_TOO_FAR_TO_ATTACK; } +#ifdef MAPBASE + else if ( !bIsBuster && ( !GetEnemy() || !GetEnemy()->ClassMatches( gm_isz_class_Bullseye ) ) && flDist < hunter_flechette_min_range.GetFloat() ) +#else else if ( !bIsBuster && ( !GetEnemy() || !GetEnemy()->ClassMatches( "npc_bullseye" ) ) && flDist < hunter_flechette_min_range.GetFloat() ) +#endif { return COND_TOO_CLOSE_TO_ATTACK; } @@ -5611,6 +5669,40 @@ int CNPC_Hunter::OnTakeDamage( const CTakeDamageInfo &info ) } } } +#ifdef MAPBASE + else if (info.GetDamageType() == DMG_CLUB && + info.GetInflictor() && info.GetInflictor()->IsNPC()) + { + // If the *inflictor* is a NPC doing club damage, it's most likely an antlion guard or even another hunter charging us. + // Add DMG_CRUSH so we ragdoll immediately if we die. + myInfo.AddDamageType( DMG_CRUSH ); + } + + + // "So, do you know what the alternative fire method does on the AR2? It kills hunters. How did--" + // "No, only Freeman's does it!" + // "What do you mean 'Only Freeman's does it'?" + // "Only energy balls fired by the player can dissolve hunters. Energy balls fired by NPCs only do a metered amount of damage." + // "...Huh. Well, in that case, we'll just use rocket launchers." + // + // That instructor was straight-up lying to those rebels, but now he's telling the truth. + // Hunters die to NPC balls instantly and act like it was a player ball. + // Implementation is sketchy, but it was the best I could do. + if (myInfo.GetDamageType() & DMG_DISSOLVE && + info.GetAttacker() && info.GetAttacker()->IsNPC() && + info.GetInflictor() && info.GetInflictor()->ClassMatches("prop_combine_ball")) + { + // We divide by the ally damage scale to counter its usage in OnTakeDamage_Alive. + myInfo.SetDamage( (float)GetMaxHealth() / sk_hunter_citizen_damage_scale.GetFloat() ); + + myInfo.AddDamageType( DMG_CRUSH ); + //myInfo.SetDamagePosition( info.GetInflictor()->GetAbsOrigin() ); + //myInfo.SetDamageForce( info.GetInflictor()->GetAbsVelocity() ); + + // Make the NPC's ball explode + info.GetInflictor()->AcceptInput( "Explode", this, this, variant_t(), 0 ); + } +#endif return BaseClass::OnTakeDamage( myInfo ); } diff --git a/src/game/server/episodic/npc_magnusson.cpp b/src/game/server/episodic/npc_magnusson.cpp index 31008353..30e1f697 100644 --- a/src/game/server/episodic/npc_magnusson.cpp +++ b/src/game/server/episodic/npc_magnusson.cpp @@ -37,6 +37,11 @@ public: Class_T Classify ( void ); void HandleAnimEvent( animevent_t *pEvent ); int GetSoundInterests ( void ); + +#ifdef MAPBASE + // Use Magnusson's default subtitle color (209,178,178) + bool GetGameTextSpeechParams( hudtextparms_t ¶ms ) { params.r1 = 209; params.g1 = 178; params.b1 = 178; return BaseClass::GetGameTextSpeechParams( params ); } +#endif }; LINK_ENTITY_TO_CLASS( npc_magnusson, CNPC_Magnusson ); diff --git a/src/game/server/episodic/vehicle_jeep_episodic.cpp b/src/game/server/episodic/vehicle_jeep_episodic.cpp index 368f1b9f..69bb4c3f 100644 --- a/src/game/server/episodic/vehicle_jeep_episodic.cpp +++ b/src/game/server/episodic/vehicle_jeep_episodic.cpp @@ -328,6 +328,9 @@ BEGIN_DATADESC( CPropJeepEpisodic ) DEFINE_ARRAY( m_hHazardLights, FIELD_EHANDLE, NUM_HAZARD_LIGHTS ), DEFINE_FIELD( m_flCargoStartTime, FIELD_TIME ), DEFINE_FIELD( m_bBlink, FIELD_BOOLEAN ), +#ifdef MAPBASE + DEFINE_KEYFIELD( m_bNoHazardLights, FIELD_BOOLEAN, "NoHazardLights" ), +#endif DEFINE_FIELD( m_bRadarEnabled, FIELD_BOOLEAN ), DEFINE_FIELD( m_bRadarDetectsEnemies, FIELD_BOOLEAN ), DEFINE_FIELD( m_hRadarScreen, FIELD_EHANDLE ), @@ -357,13 +360,20 @@ BEGIN_DATADESC( CPropJeepEpisodic ) DEFINE_INPUTFUNC( FIELD_VOID, "EnableRadarDetectEnemies", InputEnableRadarDetectEnemies ), DEFINE_INPUTFUNC( FIELD_VOID, "AddBusterToCargo", InputAddBusterToCargo ), DEFINE_INPUTFUNC( FIELD_VOID, "OutsideTransition", InputOutsideTransition ), +#ifndef MAPBASE DEFINE_INPUTFUNC( FIELD_VOID, "DisablePhysGun", InputDisablePhysGun ), DEFINE_INPUTFUNC( FIELD_VOID, "EnablePhysGun", InputEnablePhysGun ), +#endif DEFINE_INPUTFUNC( FIELD_VOID, "CreateLinkController", InputCreateLinkController ), DEFINE_INPUTFUNC( FIELD_VOID, "DestroyLinkController", InputDestroyLinkController ), DEFINE_INPUTFUNC( FIELD_BOOLEAN, "SetCargoHopperVisibility", InputSetCargoVisibility ), +#ifdef MAPBASE + DEFINE_INPUTFUNC( FIELD_VOID, "EnableHazardLights", InputEnableHazardLights ), + DEFINE_INPUTFUNC( FIELD_VOID, "DisableHazardLights", InputDisableHazardLights ), +#endif + END_DATADESC(); IMPLEMENT_SERVERCLASS_ST(CPropJeepEpisodic, DT_CPropJeepEpisodic) @@ -1354,6 +1364,11 @@ void CPropJeepEpisodic::DriveVehicle( float flFrameTime, CUserCmd *ucmd, int iBu //----------------------------------------------------------------------------- void CPropJeepEpisodic::CreateHazardLights( void ) { +#ifdef MAPBASE + if (m_bNoHazardLights) + return; +#endif + static const char *s_szAttach[NUM_HAZARD_LIGHTS] = { "rearlight_r", @@ -1412,6 +1427,26 @@ void CPropJeepEpisodic::DestroyHazardLights( void ) SetContextThink( NULL, gpGlobals->curtime, "HazardBlink" ); } +#ifdef MAPBASE +void CPropJeepEpisodic::InputEnableHazardLights( inputdata_t &data ) +{ + if (m_bNoHazardLights) + { + m_bNoHazardLights = false; + CreateHazardLights(); + } +} + +void CPropJeepEpisodic::InputDisableHazardLights( inputdata_t &data ) +{ + if (!m_bNoHazardLights) + { + m_bNoHazardLights = true; + DestroyHazardLights(); + } +} +#endif + //----------------------------------------------------------------------------- // Purpose: // Input : nRole - @@ -1664,6 +1699,7 @@ void CPropJeepEpisodic::InputOutsideTransition( inputdata_t &inputdata ) Warning("No valid vehicle teleport points!\n"); } +#ifndef MAPBASE //----------------------------------------------------------------------------- // Purpose: Stop players punting the car around. //----------------------------------------------------------------------------- @@ -1678,6 +1714,7 @@ void CPropJeepEpisodic::InputEnablePhysGun( inputdata_t &data ) { RemoveEFlags( EFL_NO_PHYSCANNON_INTERACTION ); } +#endif //----------------------------------------------------------------------------- // Create and parent two radial node link controllers. diff --git a/src/game/server/episodic/vehicle_jeep_episodic.h b/src/game/server/episodic/vehicle_jeep_episodic.h index 70cb5894..869cf33a 100644 --- a/src/game/server/episodic/vehicle_jeep_episodic.h +++ b/src/game/server/episodic/vehicle_jeep_episodic.h @@ -104,9 +104,15 @@ private: void InputEnableRadarDetectEnemies( inputdata_t &data ); void InputAddBusterToCargo( inputdata_t &data ); void InputSetCargoVisibility( inputdata_t &data ); +#ifdef MAPBASE + void InputEnableHazardLights( inputdata_t &data ); + void InputDisableHazardLights( inputdata_t &data ); +#endif void InputOutsideTransition( inputdata_t &data ); +#ifndef MAPBASE void InputDisablePhysGun( inputdata_t &data ); void InputEnablePhysGun( inputdata_t &data ); +#endif void InputCreateLinkController( inputdata_t &data ); void InputDestroyLinkController( inputdata_t &data ); void CreateAvoidanceZone( void ); @@ -116,6 +122,10 @@ private: bool m_bAddingCargo; bool m_bBlink; +#ifdef MAPBASE + bool m_bNoHazardLights; +#endif + float m_flCargoStartTime; // Time when the cargo was first added to the vehicle (used for animating into hold) float m_flNextAvoidBroadcastTime; // Next time we'll warn entity to move out of us diff --git a/src/game/server/eventqueue.h b/src/game/server/eventqueue.h index 1c7f030e..d5cc2d5f 100644 --- a/src/game/server/eventqueue.h +++ b/src/game/server/eventqueue.h @@ -40,9 +40,14 @@ class CEventQueue { public: // pushes an event into the queue, targeting a string name (m_iName), or directly by a pointer - void AddEvent( const char *target, const char *action, variant_t Value, float fireDelay, CBaseEntity *pActivator, CBaseEntity *pCaller, int outputID = 0 ); +#ifdef MAPBASE_VSCRIPT + int AddEvent( const char *target, const char *action, variant_t Value, float fireDelay, CBaseEntity *pActivator, CBaseEntity *pCaller, int outputID = 0 ); + int AddEvent( CBaseEntity *target, const char *action, variant_t Value, float fireDelay, CBaseEntity *pActivator, CBaseEntity *pCaller, int outputID = 0 ); +#else + void AddEvent( const char *target, const char *action, variant_t Value, float fireDelay, CBaseEntity *pActivator, CBaseEntity *pCaller, int outputID = 0 ); + void AddEvent( CBaseEntity *target, const char *action, variant_t Value, float fireDelay, CBaseEntity *pActivator, CBaseEntity *pCaller, int outputID = 0 ); +#endif void AddEvent( CBaseEntity *target, const char *action, float fireDelay, CBaseEntity *pActivator, CBaseEntity *pCaller, int outputID = 0 ); - void AddEvent( CBaseEntity *target, const char *action, variant_t Value, float fireDelay, CBaseEntity *pActivator, CBaseEntity *pCaller, int outputID = 0 ); void CancelEvents( CBaseEntity *pCaller ); void CancelEventOn( CBaseEntity *pTarget, const char *sInputName ); @@ -66,6 +71,12 @@ public: void Dump( void ); +#ifdef MAPBASE_VSCRIPT + void CancelEventsByInput( CBaseEntity *pTarget, const char *szInput ); + bool RemoveEvent( int event ); + float GetTimeLeft( int event ); +#endif // MAPBASE_VSCRIPT + private: void AddEvent( EventQueuePrioritizedEvent_t *event ); diff --git a/src/game/server/explode.cpp b/src/game/server/explode.cpp index 42d8df35..d9031dd4 100644 --- a/src/game/server/explode.cpp +++ b/src/game/server/explode.cpp @@ -17,6 +17,10 @@ // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" +#ifdef MAPBASE +ConVar explosion_sparks("explosion_sparks", "0", FCVAR_NONE); +#endif + //----------------------------------------------------------------------------- // Purpose: Spark shower, created by the explosion entity. //----------------------------------------------------------------------------- @@ -111,6 +115,9 @@ public: // Input handlers void InputExplode( inputdata_t &inputdata ); +#ifdef MAPBASE + void InputSetIgnoredEntity( inputdata_t &inputdata ); +#endif DECLARE_DATADESC(); @@ -150,6 +157,9 @@ BEGIN_DATADESC( CEnvExplosion ) // Inputs DEFINE_INPUTFUNC(FIELD_VOID, "Explode", InputExplode), +#ifdef MAPBASE + DEFINE_INPUTFUNC(FIELD_EHANDLE, "SetIgnoredEntity", InputSetIgnoredEntity), +#endif END_DATADESC() @@ -352,7 +362,11 @@ void CEnvExplosion::InputExplode( inputdata_t &inputdata ) SetNextThink( gpGlobals->curtime + 0.3 ); // Only do these effects if we're not submerged +#ifdef MAPBASE + if ( explosion_sparks.GetBool() && !(UTIL_PointContents( GetAbsOrigin() ) & CONTENTS_WATER) ) +#else if ( UTIL_PointContents( GetAbsOrigin() ) & CONTENTS_WATER ) +#endif { // draw sparks if ( !( m_spawnflags & SF_ENVEXPLOSION_NOSPARKS ) ) @@ -369,6 +383,16 @@ void CEnvExplosion::InputExplode( inputdata_t &inputdata ) } } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: Input handler for setting the ignored entity. +//----------------------------------------------------------------------------- +void CEnvExplosion::InputSetIgnoredEntity( inputdata_t &inputdata ) +{ + m_hEntityIgnore = inputdata.value.Entity(); +} +#endif + void CEnvExplosion::Smoke( void ) { @@ -388,7 +412,7 @@ void ExplosionCreate( const Vector ¢er, const QAngle &angles, CEnvExplosion *pExplosion = (CEnvExplosion*)CBaseEntity::Create( "env_explosion", center, angles, pOwner ); Q_snprintf( buf,sizeof(buf), "%3d", magnitude ); - char *szKeyName = "iMagnitude"; + const char *szKeyName = "iMagnitude"; char *szValue = buf; pExplosion->KeyValue( szKeyName, szValue ); diff --git a/src/game/server/filters.cpp b/src/game/server/filters.cpp index 6179254d..ca2f6c7a 100644 --- a/src/game/server/filters.cpp +++ b/src/game/server/filters.cpp @@ -9,6 +9,12 @@ #include "entitylist.h" #include "ai_squad.h" #include "ai_basenpc.h" +#ifdef MAPBASE +#include "mapbase/matchers.h" +#include "AI_Criteria.h" +#include "ai_hint.h" +#include "mapbase/GlobalStrings.h" +#endif // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" @@ -21,9 +27,16 @@ LINK_ENTITY_TO_CLASS(filter_base, CBaseFilter); BEGIN_DATADESC( CBaseFilter ) DEFINE_KEYFIELD(m_bNegated, FIELD_BOOLEAN, "Negated"), +#ifdef MAPBASE + DEFINE_KEYFIELD(m_bPassCallerWhenTested, FIELD_BOOLEAN, "PassCallerWhenTested"), +#endif // Inputs DEFINE_INPUTFUNC( FIELD_INPUT, "TestActivator", InputTestActivator ), +#ifdef MAPBASE + DEFINE_INPUTFUNC( FIELD_EHANDLE, "TestEntity", InputTestEntity ), + DEFINE_INPUTFUNC( FIELD_INPUT, "SetField", InputSetField ), +#endif // Outputs DEFINE_OUTPUT( m_OnPass, "OnPass"), @@ -31,6 +44,18 @@ BEGIN_DATADESC( CBaseFilter ) END_DATADESC() +#ifdef MAPBASE_VSCRIPT +BEGIN_ENT_SCRIPTDESC( CBaseFilter, CBaseEntity, "All entities which could be used as filters." ) + + DEFINE_SCRIPTFUNC_NAMED( ScriptPassesFilter, "PassesFilter", "Check if the given caller and entity pass the filter. The caller is the one who requests the filter result; For example, the entity being damaged when using this as a damage filter." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptPassesDamageFilter, "PassesDamageFilter", "Check if the given caller and damage info pass the damage filter, with the second parameter being a CTakeDamageInfo instance. The caller is the one who requests the filter result; For example, the entity being damaged when using this as a damage filter." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptPassesFinalDamageFilter, "PassesFinalDamageFilter", "Used by filter_damage_redirect to distinguish between standalone filter calls and actually damaging an entity. Returns true if there's no unique behavior. Parameters are identical to PassesDamageFilter." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptBloodAllowed, "BloodAllowed", "Check if the given caller and damage info allow for the production of blood." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptDamageMod, "DamageMod", "Mods the damage info with the given caller." ) + +END_SCRIPTDESC(); +#endif + //----------------------------------------------------------------------------- bool CBaseFilter::PassesFilterImpl( CBaseEntity *pCaller, CBaseEntity *pEntity ) @@ -46,17 +71,37 @@ bool CBaseFilter::PassesFilter( CBaseEntity *pCaller, CBaseEntity *pEntity ) } +#ifdef MAPBASE +bool CBaseFilter::PassesDamageFilter(CBaseEntity *pCaller, const CTakeDamageInfo &info) +{ + bool baseResult = PassesDamageFilterImpl(pCaller, info); + return (m_bNegated) ? !baseResult : baseResult; +} +#endif + bool CBaseFilter::PassesDamageFilter(const CTakeDamageInfo &info) { +#ifdef MAPBASE + Warning("WARNING: Deprecated usage of PassesDamageFilter!\n"); + bool baseResult = PassesDamageFilterImpl(NULL, info); +#else bool baseResult = PassesDamageFilterImpl(info); +#endif return (m_bNegated) ? !baseResult : baseResult; } +#ifdef MAPBASE +bool CBaseFilter::PassesDamageFilterImpl( CBaseEntity *pCaller, const CTakeDamageInfo &info ) +{ + return PassesFilterImpl( pCaller, info.GetAttacker() ); +} +#else bool CBaseFilter::PassesDamageFilterImpl( const CTakeDamageInfo &info ) { return PassesFilterImpl( NULL, info.GetAttacker() ); } +#endif //----------------------------------------------------------------------------- // Purpose: Input handler for testing the activator. If the activator passes the @@ -66,14 +111,63 @@ void CBaseFilter::InputTestActivator( inputdata_t &inputdata ) { if ( PassesFilter( inputdata.pCaller, inputdata.pActivator ) ) { +#ifdef MAPBASE + m_OnPass.FireOutput( inputdata.pActivator, m_bPassCallerWhenTested ? inputdata.pCaller : this ); +#else m_OnPass.FireOutput( inputdata.pActivator, this ); +#endif } else { +#ifdef MAPBASE + m_OnFail.FireOutput( inputdata.pActivator, m_bPassCallerWhenTested ? inputdata.pCaller : this ); +#else m_OnFail.FireOutput( inputdata.pActivator, this ); +#endif } } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: Input handler for testing the activator. If the activator passes the +// filter test, the OnPass output is fired. If not, the OnFail output is fired. +//----------------------------------------------------------------------------- +void CBaseFilter::InputTestEntity( inputdata_t &inputdata ) +{ + if ( !inputdata.value.Entity() ) + { + // HACKHACK: Not firing OnFail in this case is intentional for the time being (activator shouldn't be null) + return; + } + + if ( PassesFilter( inputdata.pCaller, inputdata.value.Entity() ) ) + { + m_OnPass.FireOutput( inputdata.value.Entity(), m_bPassCallerWhenTested ? inputdata.pCaller : this ); + } + else + { + m_OnFail.FireOutput( inputdata.value.Entity(), m_bPassCallerWhenTested ? inputdata.pCaller : this ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Tries to set the filter's target since most filters use "filtername" anyway +//----------------------------------------------------------------------------- +void CBaseFilter::InputSetField( inputdata_t& inputdata ) +{ + KeyValue("filtername", inputdata.value.String()); + Activate(); +} +#endif + +#ifdef MAPBASE_VSCRIPT +bool CBaseFilter::ScriptPassesFilter( HSCRIPT pCaller, HSCRIPT pEntity ) { return PassesFilter( ToEnt(pCaller), ToEnt(pEntity) ); } +bool CBaseFilter::ScriptPassesDamageFilter( HSCRIPT pCaller, HSCRIPT pInfo ) { return (pInfo) ? PassesDamageFilter( ToEnt( pCaller ), *const_cast(HScriptToClass( pInfo )) ) : false; } +bool CBaseFilter::ScriptPassesFinalDamageFilter( HSCRIPT pCaller, HSCRIPT pInfo ) { return (pInfo) ? PassesFinalDamageFilter( ToEnt( pCaller ), *const_cast(HScriptToClass( pInfo )) ) : false; } +bool CBaseFilter::ScriptBloodAllowed( HSCRIPT pCaller, HSCRIPT pInfo ) { return (pInfo) ? BloodAllowed( ToEnt( pCaller ), *const_cast(HScriptToClass( pInfo )) ) : false; } +bool CBaseFilter::ScriptDamageMod( HSCRIPT pCaller, HSCRIPT pInfo ) { return (pInfo) ? DamageMod( ToEnt( pCaller ), *HScriptToClass( pInfo ) ) : false; } +#endif + // ################################################################### // > FilterMultiple @@ -97,8 +191,18 @@ class CFilterMultiple : public CBaseFilter EHANDLE m_hFilter[MAX_FILTERS]; bool PassesFilterImpl( CBaseEntity *pCaller, CBaseEntity *pEntity ); +#ifdef MAPBASE + bool PassesDamageFilterImpl(CBaseEntity *pCaller, const CTakeDamageInfo &info); +#else bool PassesDamageFilterImpl(const CTakeDamageInfo &info); +#endif void Activate(void); + +#ifdef MAPBASE + bool BloodAllowed( CBaseEntity *pCaller, const CTakeDamageInfo &info ); + bool PassesFinalDamageFilter( CBaseEntity *pCaller, const CTakeDamageInfo &info ); + bool DamageMod( CBaseEntity *pCaller, CTakeDamageInfo &info ); +#endif }; LINK_ENTITY_TO_CLASS(filter_multi, CFilterMultiple); @@ -145,6 +249,13 @@ void CFilterMultiple::Activate( void ) Warning("filter_multi: Tried to add entity (%s) which is not a filter entity!\n", STRING( m_iFilterName[i] ) ); continue; } +#ifdef MAPBASE + else if ( pFilter == this ) + { + Warning("filter_multi: Tried to add itself!\n"); + continue; + } +#endif // Take this entity and increment out array pointer m_hFilter[nNextFilter] = pFilter; @@ -198,7 +309,11 @@ bool CFilterMultiple::PassesFilterImpl( CBaseEntity *pCaller, CBaseEntity *pEnti // Purpose: Returns true if the entity passes our filter, false if not. // Input : pEntity - Entity to test. //----------------------------------------------------------------------------- +#ifdef MAPBASE +bool CFilterMultiple::PassesDamageFilterImpl(CBaseEntity *pCaller, const CTakeDamageInfo &info) +#else bool CFilterMultiple::PassesDamageFilterImpl(const CTakeDamageInfo &info) +#endif { // Test against each filter if (m_nFilterType == FILTER_AND) @@ -208,7 +323,11 @@ bool CFilterMultiple::PassesDamageFilterImpl(const CTakeDamageInfo &info) if (m_hFilter[i] != NULL) { CBaseFilter* pFilter = (CBaseFilter *)(m_hFilter[i].Get()); +#ifdef MAPBASE + if (!pFilter->PassesDamageFilter(pCaller, info)) +#else if (!pFilter->PassesDamageFilter(info)) +#endif { return false; } @@ -223,7 +342,11 @@ bool CFilterMultiple::PassesDamageFilterImpl(const CTakeDamageInfo &info) if (m_hFilter[i] != NULL) { CBaseFilter* pFilter = (CBaseFilter *)(m_hFilter[i].Get()); +#ifdef MAPBASE + if (pFilter->PassesDamageFilter(pCaller, info)) +#else if (pFilter->PassesDamageFilter(info)) +#endif { return true; } @@ -233,6 +356,125 @@ bool CFilterMultiple::PassesDamageFilterImpl(const CTakeDamageInfo &info) } } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: Returns true if blood should be allowed, false if not. +// Input : pEntity - Entity to test. +//----------------------------------------------------------------------------- +bool CFilterMultiple::BloodAllowed( CBaseEntity *pCaller, const CTakeDamageInfo &info ) +{ + // Test against each filter + if (m_nFilterType == FILTER_AND) + { + for (int i=0;iBloodAllowed(pCaller, info)) + { + return false; + } + } + } + return true; + } + else // m_nFilterType == FILTER_OR + { + for (int i=0;iBloodAllowed(pCaller, info)) + { + return true; + } + } + } + return false; + } +} + +//----------------------------------------------------------------------------- +// Purpose: Returns true if the entity passes our filter, false if not. +// Input : pEntity - Entity to test. +//----------------------------------------------------------------------------- +bool CFilterMultiple::PassesFinalDamageFilter( CBaseEntity *pCaller, const CTakeDamageInfo &info ) +{ + // Test against each filter + if (m_nFilterType == FILTER_AND) + { + for (int i=0;iPassesFinalDamageFilter(pCaller, info)) + { + return false; + } + } + } + return true; + } + else // m_nFilterType == FILTER_OR + { + for (int i=0;iPassesFinalDamageFilter(pCaller, info)) + { + return true; + } + } + } + return false; + } +} + +//----------------------------------------------------------------------------- +// Purpose: Returns true if damage should be modded, false if not. +// Input : pEntity - Entity to test. +//----------------------------------------------------------------------------- +bool CFilterMultiple::DamageMod( CBaseEntity *pCaller, CTakeDamageInfo &info ) +{ + // Test against each filter + if (m_nFilterType == FILTER_AND) + { + for (int i=0;iDamageMod(pCaller, info)) + { + return false; + } + } + } + return true; + } + else // m_nFilterType == FILTER_OR + { + for (int i=0;iDamageMod(pCaller, info)) + { + return true; + } + } + } + return false; + } +} +#endif + // ################################################################### // > FilterName @@ -257,6 +499,14 @@ public: return pEntity->NameMatches( STRING(m_iFilterName) ); } } + +#ifdef MAPBASE + void InputSetField( inputdata_t& inputdata ) + { + inputdata.value.Convert(FIELD_STRING); + m_iFilterName = inputdata.value.StringID(); + } +#endif }; LINK_ENTITY_TO_CLASS( filter_activator_name, CFilterName ); @@ -285,6 +535,14 @@ public: { return pEntity->ClassMatches( STRING(m_iFilterClass) ); } + +#ifdef MAPBASE + void InputSetField( inputdata_t& inputdata ) + { + inputdata.value.Convert(FIELD_STRING); + m_iFilterClass = inputdata.value.StringID(); + } +#endif }; LINK_ENTITY_TO_CLASS( filter_activator_class, CFilterClass ); @@ -312,6 +570,14 @@ public: { return ( pEntity->GetTeamNumber() == m_iFilterTeam ); } + +#ifdef MAPBASE + void InputSetField( inputdata_t& inputdata ) + { + inputdata.value.Convert(FIELD_INTEGER); + m_iFilterTeam = inputdata.value.Int(); + } +#endif }; LINK_ENTITY_TO_CLASS( filter_activator_team, FilterTeam ); @@ -342,6 +608,14 @@ public: return ( pEntity->VPhysicsGetObject()->GetMass() > m_fFilterMass ); } + +#ifdef MAPBASE + void InputSetField( inputdata_t& inputdata ) + { + inputdata.value.Convert(FIELD_FLOAT); + m_fFilterMass = inputdata.value.Float(); + } +#endif }; LINK_ENTITY_TO_CLASS( filter_activator_mass_greater, CFilterMassGreater ); @@ -370,12 +644,58 @@ protected: return true; } +#ifdef MAPBASE + bool PassesDamageFilterImpl(CBaseEntity *pCaller, const CTakeDamageInfo &info) +#else bool PassesDamageFilterImpl(const CTakeDamageInfo &info) +#endif { +#ifdef MAPBASE + switch (m_iFilterType) + { + case 1: return (info.GetDamageType() & m_iDamageType) != 0; + case 2: + { + int iRecvDT = info.GetDamageType(); + int iOurDT = m_iDamageType; + while (iRecvDT) + { + if (iRecvDT & iOurDT) + return true; + + iRecvDT >>= 1; iOurDT >>= 1; + } + return false; + } break; + } +#endif return info.GetDamageType() == m_iDamageType; } +#ifdef MAPBASE + void InputSetField( inputdata_t& inputdata ) + { + inputdata.value.Convert(FIELD_INTEGER); + m_iDamageType = inputdata.value.Int(); + } + + bool KeyValue( const char *szKeyName, const char *szValue ) + { + if (FStrEq( szKeyName, "damageor" ) || FStrEq( szKeyName, "damagepresets" )) + { + m_iDamageType |= atoi( szValue ); + } + else + return BaseClass::KeyValue( szKeyName, szValue ); + + return true; + } +#endif + int m_iDamageType; +#ifdef MAPBASE + int m_iFilterType; +#endif }; LINK_ENTITY_TO_CLASS( filter_damage_type, FilterDamageType ); @@ -384,6 +704,9 @@ BEGIN_DATADESC( FilterDamageType ) // Keyfields DEFINE_KEYFIELD( m_iDamageType, FIELD_INTEGER, "damagetype" ), +#ifdef MAPBASE + DEFINE_KEYFIELD( m_iFilterType, FIELD_INTEGER, "FilterType" ), +#endif END_DATADESC() @@ -403,7 +726,19 @@ class CFilterEnemy : public CBaseFilter public: virtual bool PassesFilterImpl( CBaseEntity *pCaller, CBaseEntity *pEntity ); - virtual bool PassesDamageFilterImpl( const CTakeDamageInfo &info ); +#ifdef MAPBASE + virtual bool PassesDamageFilterImpl(CBaseEntity *pCaller, const CTakeDamageInfo &info); +#else + virtual bool PassesDamageFilterImpl(const CTakeDamageInfo &info); +#endif + +#ifdef MAPBASE + void InputSetField( inputdata_t& inputdata ) + { + inputdata.value.Convert(FIELD_STRING); + m_iszEnemyName = inputdata.value.StringID(); + } +#endif private: @@ -415,7 +750,9 @@ private: float m_flRadius; // Radius (enemies are acquired at this range) float m_flOuterRadius; // Outer radius (enemies are LOST at this range) int m_nMaxSquadmatesPerEnemy; // Maximum number of squadmates who may share the same enemy +#ifndef MAPBASE string_t m_iszPlayerName; // "!player" +#endif }; //----------------------------------------------------------------------------- @@ -452,7 +789,11 @@ bool CFilterEnemy::PassesFilterImpl( CBaseEntity *pCaller, CBaseEntity *pEntity //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- +#ifdef MAPBASE +bool CFilterEnemy::PassesDamageFilterImpl( CBaseEntity *pCaller, const CTakeDamageInfo &info ) +#else bool CFilterEnemy::PassesDamageFilterImpl( const CTakeDamageInfo &info ) +#endif { // NOTE: This function has no meaning to this implementation of the filter class! Assert( 0 ); @@ -470,6 +811,9 @@ bool CFilterEnemy::PassesNameFilter( CBaseEntity *pEnemy ) if ( m_iszEnemyName == NULL_STRING ) return true; +#ifdef MAPBASE + if ( m_iszEnemyName == gm_isz_name_player ) +#else // Cache off the special case player name if ( m_iszPlayerName == NULL_STRING ) { @@ -477,6 +821,7 @@ bool CFilterEnemy::PassesNameFilter( CBaseEntity *pEnemy ) } if ( m_iszEnemyName == m_iszPlayerName ) +#endif { if ( pEnemy->IsPlayer() ) { @@ -488,7 +833,11 @@ bool CFilterEnemy::PassesNameFilter( CBaseEntity *pEnemy ) } // May be either a targetname or classname +#ifdef MAPBASE + bool bNameOrClassnameMatches = ( pEnemy->NameMatches(STRING(m_iszEnemyName)) || pEnemy->ClassMatches(STRING(m_iszEnemyName)) ); +#else bool bNameOrClassnameMatches = ( m_iszEnemyName == pEnemy->GetEntityName() || m_iszEnemyName == pEnemy->m_iClassname ); +#endif // We only leave this code block in a state meaning we've "succeeded" in any context if ( m_bNegated ) @@ -626,6 +975,1366 @@ BEGIN_DATADESC( CFilterEnemy ) DEFINE_KEYFIELD( m_flRadius, FIELD_FLOAT, "filter_radius" ), DEFINE_KEYFIELD( m_flOuterRadius, FIELD_FLOAT, "filter_outer_radius" ), DEFINE_KEYFIELD( m_nMaxSquadmatesPerEnemy, FIELD_INTEGER, "filter_max_per_enemy" ), +#ifndef MAPBASE DEFINE_FIELD( m_iszPlayerName, FIELD_STRING ), +#endif END_DATADESC() + +#ifdef MAPBASE +// ################################################################### +// > CFilterModel +// ################################################################### +class CFilterModel : public CBaseFilter +{ + DECLARE_CLASS( CFilterModel, CBaseFilter ); + DECLARE_DATADESC(); + +public: + string_t m_iFilterModel; + string_t m_strFilterSkin; + + bool PassesFilterImpl( CBaseEntity *pCaller, CBaseEntity *pEntity ) + { + if (!pEntity) + return false; + + if (FStrEq(STRING(m_strFilterSkin), "-1") /*m_strFilterSkin == NULL_STRING|| FStrEq(STRING(m_strFilterSkin), "")*/) + return Matcher_NamesMatch(STRING(m_iFilterModel), STRING(pEntity->GetModelName())); + else if (pEntity->GetBaseAnimating()) + { + //DevMsg("Skin isn't null\n"); + return Matcher_NamesMatch(STRING(m_iFilterModel), STRING(pEntity->GetModelName())) && Matcher_Match(STRING(m_strFilterSkin), pEntity->GetBaseAnimating()->m_nSkin); + } + return false; + } + + void InputSetField( inputdata_t& inputdata ) + { + inputdata.value.Convert(FIELD_STRING); + m_iFilterModel = inputdata.value.StringID(); + } + + bool KeyValue( const char *szKeyName, const char *szValue ) + { + if (FStrEq( szKeyName, "filtername" )) + { + m_iFilterModel = AllocPooledString( szValue ); + return true; + } + else + return BaseClass::KeyValue( szKeyName, szValue ); + } +}; + +LINK_ENTITY_TO_CLASS( filter_activator_model, CFilterModel ); + +BEGIN_DATADESC( CFilterModel ) + + // Keyfields + DEFINE_KEYFIELD( m_iFilterModel, FIELD_STRING, "filtermodel" ), + DEFINE_KEYFIELD( m_strFilterSkin, FIELD_STRING, "skin" ), + +END_DATADESC() + +// ################################################################### +// > CFilterContext +// ################################################################### +class CFilterContext : public CBaseFilter +{ + DECLARE_CLASS( CFilterContext, CBaseFilter ); + DECLARE_DATADESC(); + +public: + bool m_bAny; + + bool PassesFilterImpl( CBaseEntity *pCaller, CBaseEntity *pEntity ) + { + bool passes = false; + ResponseContext_t curcontext; + const char *contextvalue; + for (int i = 0; i < GetContextCount(); i++) + { + curcontext = m_ResponseContexts[i]; + if (!pEntity->HasContext(STRING(curcontext.m_iszName), NULL)) + { + if (m_bAny) + continue; + else + return false; + } + + contextvalue = pEntity->GetContextValue(STRING(curcontext.m_iszName)); + if (Matcher_NamesMatch(STRING(m_ResponseContexts[i].m_iszValue), contextvalue)) + { + passes = true; + if (m_bAny) + break; + } + else if (!m_bAny) + { + return false; + } + } + + return passes; + } + + void InputSetField( inputdata_t& inputdata ) + { + m_ResponseContexts.RemoveAll(); + AddContext(inputdata.value.String()); + } +}; + +LINK_ENTITY_TO_CLASS( filter_activator_context, CFilterContext ); + +BEGIN_DATADESC( CFilterContext ) + + // Keyfields + DEFINE_KEYFIELD( m_bAny, FIELD_BOOLEAN, "any" ), + +END_DATADESC() + +// ################################################################### +// > CFilterSquad +// ################################################################### +class CFilterSquad : public CBaseFilter +{ + DECLARE_CLASS( CFilterSquad, CBaseFilter ); + DECLARE_DATADESC(); + +public: + string_t m_iFilterName; + bool m_bAllowSilentSquadMembers; + + bool PassesFilterImpl( CBaseEntity *pCaller, CBaseEntity *pEntity ) + { + CAI_BaseNPC *pNPC = pEntity->MyNPCPointer(); + if (pEntity && pNPC) + { + if (pNPC->GetSquad() && Matcher_NamesMatch(STRING(m_iFilterName), pNPC->GetSquad()->GetName())) + { + if (CAI_Squad::IsSilentMember(pNPC)) + { + return m_bAllowSilentSquadMembers; + } + else + { + return true; + } + } + } + + return false; + } + + void InputSetField( inputdata_t& inputdata ) + { + inputdata.value.Convert(FIELD_STRING); + m_iFilterName = inputdata.value.StringID(); + } +}; + +LINK_ENTITY_TO_CLASS( filter_activator_squad, CFilterSquad ); + +BEGIN_DATADESC( CFilterSquad ) + + // Keyfields + DEFINE_KEYFIELD( m_iFilterName, FIELD_STRING, "filtername" ), + DEFINE_KEYFIELD( m_bAllowSilentSquadMembers, FIELD_BOOLEAN, "allowsilentmembers" ), + +END_DATADESC() + +// ################################################################### +// > CFilterHintGroup +// ################################################################### +class CFilterHintGroup : public CBaseFilter +{ + DECLARE_CLASS( CFilterHintGroup, CBaseFilter ); + DECLARE_DATADESC(); + +public: + string_t m_iFilterName; + ThreeState_t m_fHintLimiting; + + bool PassesFilterImpl( CBaseEntity *pCaller, CBaseEntity *pEntity ) + { + if (!pEntity) + return false; + + if (CAI_BaseNPC *pNPC = pEntity->MyNPCPointer()) + { + if (pNPC->GetHintGroup() == NULL_STRING || Matcher_NamesMatch(STRING(m_iFilterName), STRING(pNPC->GetHintGroup()))) + { + switch (m_fHintLimiting) + { + case TRS_FALSE: return !pNPC->IsLimitingHintGroups(); break; + case TRS_TRUE: return pNPC->IsLimitingHintGroups(); break; + } + + return true; + } + } + else if (CAI_Hint *pHint = dynamic_cast(pEntity)) + { + // Just in case someone somehow puts a hint node through a filter. + // Maybe they'd use a point_advanced_finder or something, I dunno. + return Matcher_NamesMatch(STRING(m_iFilterName), STRING(pHint->GetGroup())); + } + + return false; + } + + void InputSetField( inputdata_t& inputdata ) + { + inputdata.value.Convert(FIELD_STRING); + m_iFilterName = inputdata.value.StringID(); + } +}; + +LINK_ENTITY_TO_CLASS( filter_activator_hintgroup, CFilterHintGroup ); + +BEGIN_DATADESC( CFilterHintGroup ) + + // Keyfields + DEFINE_KEYFIELD( m_iFilterName, FIELD_STRING, "filtername" ), + DEFINE_KEYFIELD( m_fHintLimiting, FIELD_INTEGER, "hintlimiting" ), + +END_DATADESC() + +extern bool ReadUnregisteredKeyfields(CBaseEntity *pTarget, const char *szKeyName, variant_t *variant); + +// ################################################################### +// > CFilterKeyfield +// ################################################################### +class CFilterKeyfield : public CBaseFilter +{ + DECLARE_CLASS( CFilterKeyfield, CBaseFilter ); + DECLARE_DATADESC(); + +public: + string_t m_iFilterKey; + string_t m_iFilterValue; + + bool PassesFilterImpl( CBaseEntity *pCaller, CBaseEntity *pEntity ) + { + variant_t var; + bool found = (pEntity->ReadKeyField(STRING(m_iFilterKey), &var) || ReadUnregisteredKeyfields(pEntity, STRING(m_iFilterKey), &var)); + return m_iFilterValue != NULL_STRING ? Matcher_Match(STRING(m_iFilterValue), var.String()) : found; + } + + void InputSetField( inputdata_t& inputdata ) + { + inputdata.value.Convert(FIELD_STRING); + m_iFilterKey = inputdata.value.StringID(); + } +}; + +LINK_ENTITY_TO_CLASS( filter_activator_keyfield, CFilterKeyfield ); + +BEGIN_DATADESC( CFilterKeyfield ) + + // Keyfields + DEFINE_KEYFIELD( m_iFilterKey, FIELD_STRING, "keyname" ), + DEFINE_KEYFIELD( m_iFilterValue, FIELD_STRING, "value" ), + +END_DATADESC() + +// ################################################################### +// > CFilterRelationship +// ################################################################### +class CFilterRelationship : public CBaseFilter +{ + DECLARE_CLASS( CFilterKeyfield, CBaseFilter ); + DECLARE_DATADESC(); + +public: + Disposition_t m_iDisposition; + string_t m_iszPriority; // string_t to support matchers + bool m_bInvertTarget; + bool m_bReciprocal; + EHANDLE m_hTarget; + + bool RelationshipPasses(CBaseCombatCharacter *pBCC, CBaseEntity *pTarget) + { + if (!pBCC || !pTarget) + return m_iDisposition == D_NU; + + Disposition_t disposition = pBCC->IRelationType(pTarget); + int priority = pBCC->IRelationPriority(pTarget); + + bool passes = (disposition == m_iDisposition); + if (!passes) + return false; + + if (m_iszPriority != NULL_STRING) + { + passes = Matcher_Match(STRING(m_iszPriority), priority); + } + + return passes; + } + + bool PassesFilterImpl( CBaseEntity *pCaller, CBaseEntity *pEntity ) + { + CBaseEntity *pSubject = NULL; + if (m_target != NULL_STRING) + { + if (!m_hTarget) + { + m_hTarget = gEntList.FindEntityGeneric(NULL, STRING(m_target), pCaller, pEntity, pCaller); + } + pSubject = m_hTarget; + } + + if (!pSubject) + pSubject = pCaller; + + // No subject or entity, cannot continue + if (!pSubject || !pEntity) + return m_iDisposition == D_NU; + + CBaseCombatCharacter *pBCC1 = !m_bInvertTarget ? pSubject->MyCombatCharacterPointer() : pEntity->MyCombatCharacterPointer(); + CBaseEntity *pTarget = m_bInvertTarget ? pSubject : pEntity; + if (!pBCC1) + { + //Warning("Error: %s subject %s is not a character that uses relationships!\n", GetDebugName(), !m_bInvertTarget ? pSubject->GetDebugName() : pEntity->GetDebugName()); + return m_iDisposition == D_NU; + } + + bool passes = RelationshipPasses(pBCC1, pTarget); + if (m_bReciprocal) + passes = RelationshipPasses(pTarget->MyCombatCharacterPointer(), pBCC1); + + return passes; + } + + void InputSetField( inputdata_t& inputdata ) + { + inputdata.value.Convert(FIELD_STRING); + m_target = inputdata.value.StringID(); + } +}; + +LINK_ENTITY_TO_CLASS( filter_activator_relationship, CFilterRelationship ); + +BEGIN_DATADESC( CFilterRelationship ) + + // Keyfields + DEFINE_KEYFIELD( m_iDisposition, FIELD_INTEGER, "disposition" ), + DEFINE_KEYFIELD( m_iszPriority, FIELD_STRING, "rank" ), + DEFINE_KEYFIELD( m_bInvertTarget, FIELD_BOOLEAN, "inverttarget" ), + DEFINE_KEYFIELD( m_bReciprocal, FIELD_BOOLEAN, "Reciprocal" ), + DEFINE_FIELD( m_hTarget, FIELD_EHANDLE ), + +END_DATADESC() + +// ################################################################### +// > CFilterClassify +// ################################################################### +class CFilterClassify : public CBaseFilter +{ + DECLARE_CLASS( CFilterClassify, CBaseFilter ); + DECLARE_DATADESC(); + +public: + Class_T m_iFilterClassify; + + bool PassesFilterImpl( CBaseEntity *pCaller, CBaseEntity *pEntity ) + { + return pEntity->Classify() == m_iFilterClassify; + } + + void InputSetField( inputdata_t& inputdata ) + { + inputdata.value.Convert(FIELD_INTEGER); + m_iFilterClassify = (Class_T)inputdata.value.Int(); + } +}; + +LINK_ENTITY_TO_CLASS( filter_activator_classify, CFilterClassify ); + +BEGIN_DATADESC( CFilterClassify ) + + // Keyfields + DEFINE_KEYFIELD( m_iFilterClassify, FIELD_INTEGER, "filterclassify" ), + +END_DATADESC() + +// ################################################################### +// > CFilterCriteria +// ################################################################### +class CFilterCriteria : public CBaseFilter +{ + DECLARE_CLASS( CFilterCriteria, CBaseFilter ); + DECLARE_DATADESC(); + +public: + bool m_bAny; + bool m_bFull; // All criteria functions are gathered + + bool PassesFilterImpl( CBaseEntity *pCaller, CBaseEntity *pEntity ) + { + if (!pEntity) + return false; + + AI_CriteriaSet set; + pEntity->ModifyOrAppendCriteria( set ); + if (m_bFull) + { + // Meeets the full wrath of the response criteria + CBasePlayer *pPlayer = UTIL_GetLocalPlayer(); + if( pPlayer ) + pPlayer->ModifyOrAppendPlayerCriteria( set ); + + pEntity->ReAppendContextCriteria( set ); + } + + bool passes = false; + const char *contextname; + const char *contextvalue; + const char *matchingvalue; + for (int i = 0; i < set.GetCount(); i++) + { + contextname = set.GetName(i); + contextvalue = set.GetValue(i); + + matchingvalue = GetContextValue(contextname); + if (matchingvalue == NULL) + { + if (m_bAny) + continue; + else + return false; + } + else + { + if (Matcher_NamesMatch(matchingvalue, contextvalue)) + { + passes = true; + if (m_bAny) + break; + } + else if (!m_bAny) + { + return false; + } + } + } + + return passes; + } + + void InputSetField( inputdata_t& inputdata ) + { + m_ResponseContexts.RemoveAll(); + AddContext(inputdata.value.String()); + } +}; + +LINK_ENTITY_TO_CLASS( filter_activator_criteria, CFilterCriteria ); + +BEGIN_DATADESC( CFilterCriteria ) + + // Keyfields + DEFINE_KEYFIELD( m_bAny, FIELD_BOOLEAN, "any" ), + DEFINE_KEYFIELD( m_bFull, FIELD_BOOLEAN, "full" ), + +END_DATADESC() + +extern bool TestEntityTriggerIntersection_Accurate( CBaseEntity *pTrigger, CBaseEntity *pEntity ); + +// ################################################################### +// > CFilterInVolume +// Passes when the entity is within the specified volume. +// ################################################################### +class CFilterInVolume : public CBaseFilter +{ + DECLARE_CLASS( CFilterInVolume, CBaseFilter ); + DECLARE_DATADESC(); + +public: + string_t m_iszVolumeTester; + + void Spawn() + { + BaseClass::Spawn(); + + // Assume no string = use activator + if (m_iszVolumeTester == NULL_STRING) + m_iszVolumeTester = AllocPooledString("!activator"); + } + + bool PassesFilterImpl( CBaseEntity *pCaller, CBaseEntity *pEntity ) + { + CBaseEntity *pVolume = gEntList.FindEntityByNameNearest(STRING(m_target), pEntity->GetLocalOrigin(), 0, this, pEntity, pCaller); + if (!pVolume) + { + Msg("%s cannot find volume %s\n", GetDebugName(), STRING(m_target)); + return false; + } + + CBaseEntity *pTarget = gEntList.FindEntityByName(NULL, STRING(m_iszVolumeTester), this, pEntity, pCaller); + if (pTarget) + return TestEntityTriggerIntersection_Accurate(pVolume, pTarget); + else + { + Msg("%s cannot find target entity %s, returning false\n", GetDebugName(), STRING(m_iszVolumeTester)); + return false; + } + } + + void InputSetField( inputdata_t& inputdata ) + { + inputdata.value.Convert(FIELD_STRING); + m_iszVolumeTester = inputdata.value.StringID(); + } +}; + +LINK_ENTITY_TO_CLASS( filter_activator_involume, CFilterInVolume ); + +BEGIN_DATADESC( CFilterInVolume ) + + // Keyfields + DEFINE_KEYFIELD( m_iszVolumeTester, FIELD_STRING, "tester" ), + +END_DATADESC() + +// ################################################################### +// > CFilterSurfaceProp +// ################################################################### +class CFilterSurfaceData : public CBaseFilter +{ + DECLARE_CLASS( CFilterSurfaceData, CBaseFilter ); + DECLARE_DATADESC(); + +public: + string_t m_iFilterSurface; + int m_iSurfaceIndex; + + enum + { + SURFACETYPE_SURFACEPROP, + SURFACETYPE_GAMEMATERIAL, + }; + + // Gets the surfaceprop's game material and filters by that. + int m_iSurfaceType; + + void ParseSurfaceIndex() + { + m_iSurfaceIndex = physprops->GetSurfaceIndex(STRING(m_iFilterSurface)); + + switch (m_iSurfaceType) + { + case SURFACETYPE_GAMEMATERIAL: + { + const surfacedata_t *pSurfaceData = physprops->GetSurfaceData(m_iSurfaceIndex); + if (pSurfaceData) + m_iSurfaceIndex = pSurfaceData->game.material; + else + Warning("Can't get surface data for %s\n", STRING(m_iFilterSurface)); + } break; + } + } + + void Activate() + { + BaseClass::Activate(); + ParseSurfaceIndex(); + } + + bool PassesFilterImpl( CBaseEntity *pCaller, CBaseEntity *pEntity ) + { + if (pEntity->VPhysicsGetObject()) + { + int iMatIndex = pEntity->VPhysicsGetObject()->GetMaterialIndex(); + switch (m_iSurfaceType) + { + case SURFACETYPE_GAMEMATERIAL: + { + const surfacedata_t *pSurfaceData = physprops->GetSurfaceData(iMatIndex); + if (pSurfaceData) + return m_iSurfaceIndex == pSurfaceData->game.material; + } + default: + return iMatIndex == m_iSurfaceIndex; + } + } + + return false; + } + + void InputSetField( inputdata_t& inputdata ) + { + inputdata.value.Convert(FIELD_STRING); + m_iFilterSurface = inputdata.value.StringID(); + ParseSurfaceIndex(); + } +}; + +LINK_ENTITY_TO_CLASS( filter_activator_surfacedata, CFilterSurfaceData ); + +BEGIN_DATADESC( CFilterSurfaceData ) + + // Keyfields + DEFINE_KEYFIELD( m_iFilterSurface, FIELD_STRING, "filterstring" ), + DEFINE_KEYFIELD( m_iSurfaceType, FIELD_INTEGER, "SurfaceType" ), + +END_DATADESC() + +// =================================================================== +// Redirect filters +// +// Redirects certain data to a specific filter. +// =================================================================== +class CBaseFilterRedirect : public CBaseFilter +{ + DECLARE_CLASS( CBaseFilterRedirect, CBaseFilter ); + +public: + inline CBaseEntity *GetTargetFilter() + { + // Yes, this hijacks damage filter functionality. + // It's not like it was using it before anyway. + return m_hDamageFilter.Get(); + } + + bool RedirectToFilter( CBaseEntity *pCaller, CBaseEntity *pEntity ) + { + if (GetTargetFilter() && pEntity) + { + CBaseFilter *pFilter = static_cast(GetTargetFilter()); + return pFilter->PassesFilter(pCaller, pEntity); + } + + return pEntity != NULL; + } + + bool RedirectToDamageFilter( CBaseEntity *pCaller, const CTakeDamageInfo &info ) + { + if (GetTargetFilter()) + { + CBaseFilter *pFilter = static_cast(GetTargetFilter()); + return pFilter->PassesDamageFilter(pCaller, info); + } + + return true; + } + + virtual bool PassesDamageFilterImpl( CBaseEntity *pCaller, const CTakeDamageInfo &info ) + { + return RedirectToDamageFilter( pCaller, info ); + } + + virtual bool PassesFilterImpl( CBaseEntity *pCaller, CBaseEntity *pEntity ) + { + return RedirectToFilter( pCaller, pEntity ); + } + + virtual bool BloodAllowed( CBaseEntity *pCaller, const CTakeDamageInfo &info ) + { + if (GetTargetFilter()) + { + CBaseFilter *pFilter = static_cast(GetTargetFilter()); + return pFilter->BloodAllowed(pCaller, info); + } + + return true; + } + + virtual bool DamageMod( CBaseEntity *pCaller, CTakeDamageInfo &info ) + { + if (GetTargetFilter()) + { + CBaseFilter *pFilter = static_cast(GetTargetFilter()); + return pFilter->DamageMod( pCaller, info ); + } + + return true; + } + + void InputSetField( inputdata_t& inputdata ) + { + inputdata.value.Convert(FIELD_STRING); + InputSetDamageFilter(inputdata); + } + + enum + { + REDIRECT_MUST_PASS_TO_DAMAGE_CALLER, // Must pass to damage caller, if damage is allowed + REDIRECT_MUST_PASS_TO_ACT, // Must pass to do action + REDIRECT_MUST_PASS_ACTIVATORS, // Each activator must pass this filter + }; +}; + +// ################################################################### +// > CFilterRedirectInflictor +// Uses the specified filter to filter by damage inflictor. +// ################################################################### +class CFilterRedirectInflictor : public CBaseFilterRedirect +{ + DECLARE_CLASS( CFilterRedirectInflictor, CBaseFilterRedirect ); + +public: + bool PassesDamageFilterImpl( CBaseEntity *pCaller, const CTakeDamageInfo &info ) + { + return RedirectToFilter(pCaller, info.GetInflictor()); + } +}; + +LINK_ENTITY_TO_CLASS( filter_redirect_inflictor, CFilterRedirectInflictor ); + +// ################################################################### +// > CFilterRedirectWeapon +// Uses the specified filter to filter by either the entity's active weapon or the weapon causing damage, +// depending on the context. +// ################################################################### +class CFilterRedirectWeapon : public CBaseFilterRedirect +{ + DECLARE_CLASS( CFilterRedirectWeapon, CBaseFilterRedirect ); + +public: + bool PassesFilterImpl( CBaseEntity *pCaller, CBaseEntity *pEntity ) + { + CBaseCombatCharacter *pBCC = pEntity->MyCombatCharacterPointer(); + if (pBCC && pBCC->GetActiveWeapon()) + { + return RedirectToFilter( pCaller, pBCC->GetActiveWeapon() ); + } + + return false; + } + + bool PassesDamageFilterImpl( CBaseEntity *pCaller, const CTakeDamageInfo &info ) + { + // Pass any weapon found in the damage info + if (info.GetWeapon()) + { + return RedirectToFilter( pCaller, info.GetWeapon() ); + } + + // Check the attacker's active weapon instead + if (info.GetAttacker()) + { + return PassesFilterImpl( pCaller, info.GetAttacker() ); + } + + // No weapon to check + return false; + } +}; + +LINK_ENTITY_TO_CLASS( filter_redirect_weapon, CFilterRedirectWeapon ); + +// ################################################################### +// > CFilterRedirectOwner +// Uses the specified filter to filter by owner entity. +// ################################################################### +class CFilterRedirectOwner : public CBaseFilterRedirect +{ + DECLARE_CLASS( CFilterRedirectOwner, CBaseFilterRedirect ); + +public: + bool PassesFilterImpl( CBaseEntity *pCaller, CBaseEntity *pEntity ) + { + if (pEntity->GetOwnerEntity()) + { + return RedirectToFilter(pCaller, pEntity->GetOwnerEntity()); + } + + return false; + } +}; + +LINK_ENTITY_TO_CLASS( filter_redirect_owner, CFilterRedirectOwner ); + +// ################################################################### +// > CFilterDamageTransfer +// Transfers damage to another entity. +// ################################################################### +class CFilterDamageTransfer : public CBaseFilterRedirect +{ + DECLARE_CLASS( CFilterDamageTransfer, CBaseFilterRedirect ); + DECLARE_DATADESC(); + +public: + void Spawn() + { + BaseClass::Spawn(); + + // Assume no string = use activator + if (m_target == NULL_STRING) + m_target = AllocPooledString("!activator"); + + // A number less than or equal to 0 is always synonymous with no limit + if (m_iMaxEntities <= 0) + m_iMaxEntities = MAX_EDICTS; + } + + // Some secondary filter modes shouldn't be used in non-final filter passes + // Always return true on non-standard secondary filter modes + /* + bool PassesFilterImpl( CBaseEntity *pCaller, CBaseEntity *pEntity ) + { + return true; + } + */ + + // A hack because of the way final damage filtering now works. + bool BloodAllowed( CBaseEntity *pCaller, const CTakeDamageInfo &info ) + { + if (!m_bCallerDamageAllowed) + return false; + else + return m_iSecondaryFilterMode == REDIRECT_MUST_PASS_TO_DAMAGE_CALLER && GetTargetFilter() ? RedirectToDamageFilter(pCaller, info) : true; + } + + // PassesFinalDamageFilter() was created for the express purpose of having filter_damage_transfer function without + // passing damage on filter checks that don't actually lead to us taking damage in the first place. + // PassesFinalDamageFilter() is only called in certain base entity functions where we DEFINITELY will take damage otherwise. + bool PassesFinalDamageFilter( CBaseEntity *pCaller, const CTakeDamageInfo &info ) + { + if (m_iSecondaryFilterMode == REDIRECT_MUST_PASS_TO_ACT) + { + // Transfer only if the secondary filter passes + if (!RedirectToDamageFilter(pCaller, info)) + { + // Otherwise just return the other flag + return m_bCallerDamageAllowed; + } + } + + CBaseEntity *pTarget = gEntList.FindEntityGeneric(NULL, STRING(m_target), this, info.GetAttacker(), pCaller); + int iNumDamaged = 0; + while (pTarget) + { + // Avoid recursive loops! + if (pTarget->m_hDamageFilter != this) + { + CTakeDamageInfo info2 = info; + + // Adjust damage position stuff + if (m_bAdjustDamagePosition) + { + info2.SetDamagePosition(pTarget->GetAbsOrigin() + (pCaller->GetAbsOrigin() - info.GetDamagePosition())); + + if (pCaller->IsCombatCharacter() && pTarget->IsCombatCharacter()) + pTarget->MyCombatCharacterPointer()->SetLastHitGroup(pCaller->MyCombatCharacterPointer()->LastHitGroup()); + } + + if (m_iSecondaryFilterMode != REDIRECT_MUST_PASS_ACTIVATORS || RedirectToFilter(pCaller, pTarget)) + { + pTarget->TakeDamage(info2); + iNumDamaged++; + } + } + + if (iNumDamaged < m_iMaxEntities) + pTarget = gEntList.FindEntityGeneric(pTarget, STRING(m_target), this, info.GetAttacker(), pCaller); + else + break; + } + + // We've transferred the damage, now determine whether the caller should take damage. + // Boolean surpasses all. + if (!m_bCallerDamageAllowed) + return false; + else + return m_iSecondaryFilterMode == REDIRECT_MUST_PASS_TO_DAMAGE_CALLER && GetTargetFilter() ? RedirectToDamageFilter(pCaller, info) : true; + } + + /* + void InputSetTarget( inputdata_t& inputdata ) + { + m_target = inputdata.value.StringID(); + m_hTarget = NULL; + } + */ + + inline CBaseEntity *GetTarget(CBaseEntity *pCaller, CBaseEntity *pActivator) + { + return gEntList.FindEntityGeneric(NULL, STRING(m_target), this, pActivator, pCaller); + } + + //EHANDLE m_hTarget; + + bool m_bAdjustDamagePosition; + + // See CBaseRedirectFilter enum for more info + int m_iSecondaryFilterMode; + + // If enabled, the caller can be damaged after the transfer. If disabled, the caller cannot. + bool m_bCallerDamageAllowed; + + int m_iMaxEntities = MAX_EDICTS; +}; + +LINK_ENTITY_TO_CLASS( filter_damage_transfer, CFilterDamageTransfer ); + +BEGIN_DATADESC( CFilterDamageTransfer ) + + //DEFINE_FIELD( m_hTarget, FIELD_EHANDLE ), + DEFINE_KEYFIELD( m_bAdjustDamagePosition, FIELD_BOOLEAN, "AdjustDamagePosition" ), + DEFINE_KEYFIELD( m_iMaxEntities, FIELD_INTEGER, "MaxEntities" ), + DEFINE_KEYFIELD( m_iSecondaryFilterMode, FIELD_INTEGER, "SecondaryFilterMode" ), + DEFINE_KEYFIELD( m_bCallerDamageAllowed, FIELD_BOOLEAN, "CallerDamageAllowed" ), + +END_DATADESC() + +// ################################################################### +// > CFilterBloodControl +// Takes advantage of hacks created for filter_damage_transfer to control blood. +// ################################################################### +class CFilterBloodControl : public CBaseFilterRedirect +{ + DECLARE_CLASS( CFilterBloodControl, CBaseFilterRedirect ); + DECLARE_DATADESC(); +public: + bool PassesFilterImpl( CBaseEntity *pCaller, CBaseEntity *pEntity ) + { + if (GetTargetFilter() && m_bSecondaryFilterIsDamageFilter) + return RedirectToFilter(pCaller, pEntity); + + return true; + } + + bool BloodAllowed( CBaseEntity *pCaller, const CTakeDamageInfo &info ) + { + if (m_bBloodDisabled) + return false; + + return GetTargetFilter() ? RedirectToDamageFilter(pCaller, info) : true; + } + + void InputDisableBlood( inputdata_t &inputdata ) { m_bBloodDisabled = true; } + void InputEnableBlood( inputdata_t &inputdata ) { m_bBloodDisabled = false; } + + bool m_bBloodDisabled; + + // Uses the secondary filter as a damage filter instead of just a blood filter + bool m_bSecondaryFilterIsDamageFilter; +}; + +LINK_ENTITY_TO_CLASS( filter_blood_control, CFilterBloodControl ); + +BEGIN_DATADESC( CFilterBloodControl ) + + DEFINE_KEYFIELD( m_bBloodDisabled, FIELD_BOOLEAN, "BloodDisabled" ), + DEFINE_KEYFIELD( m_bSecondaryFilterIsDamageFilter, FIELD_BOOLEAN, "SecondaryFilterMode" ), + + DEFINE_INPUTFUNC( FIELD_VOID, "DisableBlood", InputDisableBlood ), + DEFINE_INPUTFUNC( FIELD_VOID, "EnableBlood", InputEnableBlood ), + +END_DATADESC() + +// ################################################################### +// > CFilterDamageMod +// Modifies damage. +// ################################################################### +class CFilterDamageMod : public CBaseFilterRedirect +{ + DECLARE_CLASS( CFilterDamageMod, CBaseFilterRedirect ); + DECLARE_DATADESC(); +public: + bool PassesFilterImpl( CBaseEntity *pCaller, CBaseEntity *pEntity ) + { + if (GetTargetFilter() && m_iSecondaryFilterMode == REDIRECT_MUST_PASS_TO_DAMAGE_CALLER) + return RedirectToFilter(pCaller, pEntity); + + return true; + } + + bool PassesDamageFilterImpl( CBaseEntity *pCaller, const CTakeDamageInfo &info ) + { + if (GetTargetFilter() && m_iSecondaryFilterMode == REDIRECT_MUST_PASS_TO_DAMAGE_CALLER) + return RedirectToDamageFilter( pCaller, info ); + + return true; + } + + bool DamageMod( CBaseEntity *pCaller, CTakeDamageInfo &info ) + { + if (GetTargetFilter()) + { + bool bPass = true; + + switch (m_iSecondaryFilterMode) + { + case REDIRECT_MUST_PASS_TO_DAMAGE_CALLER: + case REDIRECT_MUST_PASS_TO_ACT: bPass = (RedirectToDamageFilter( pCaller, info )); break; + + case REDIRECT_MUST_PASS_ACTIVATORS: bPass = (info.GetAttacker() && RedirectToFilter(pCaller, info.GetAttacker())); break; + } + + if (!bPass) + return false; + } + + if (m_flDamageMultiplier != 1.0f) + info.ScaleDamage(m_flDamageMultiplier); + if (m_flDamageAddend != 0.0f) + info.AddDamage(m_flDamageAddend); + + if (m_iDamageBitsAdded != 0) + info.AddDamageType(m_iDamageBitsAdded); + if (m_iDamageBitsRemoved != 0) + info.AddDamageType(~m_iDamageBitsRemoved); + + if (m_iszNewAttacker != NULL_STRING) + { + if (!m_hNewAttacker) + m_hNewAttacker = gEntList.FindEntityByName(NULL, m_iszNewAttacker, this, info.GetAttacker(), pCaller); + info.SetAttacker(m_hNewAttacker); + } + if (m_iszNewInflictor != NULL_STRING) + { + if (!m_hNewInflictor) + m_hNewInflictor = gEntList.FindEntityByName(NULL, m_iszNewInflictor, this, info.GetAttacker(), pCaller); + info.SetInflictor(m_hNewInflictor); + } + if (m_iszNewWeapon != NULL_STRING) + { + if (!m_hNewWeapon) + m_hNewWeapon = gEntList.FindEntityByName(NULL, m_iszNewWeapon, this, info.GetAttacker(), pCaller); + info.SetWeapon(m_hNewWeapon); + } + + return true; + } + + void InputSetNewAttacker( inputdata_t &inputdata ) { m_iszNewAttacker = inputdata.value.StringID(); m_hNewAttacker = NULL; } + void InputSetNewInflictor( inputdata_t &inputdata ) { m_iszNewInflictor = inputdata.value.StringID(); m_hNewInflictor = NULL; } + void InputSetNewWeapon( inputdata_t &inputdata ) { m_iszNewWeapon = inputdata.value.StringID(); m_hNewWeapon = NULL; } + + float m_flDamageMultiplier = 1.0f; + float m_flDamageAddend; + int m_iDamageBitsAdded; + int m_iDamageBitsRemoved; + + string_t m_iszNewAttacker; EHANDLE m_hNewAttacker; + string_t m_iszNewInflictor; EHANDLE m_hNewInflictor; + string_t m_iszNewWeapon; EHANDLE m_hNewWeapon; + + // See CBaseRedirectFilter enum for more info + int m_iSecondaryFilterMode; +}; + +LINK_ENTITY_TO_CLASS( filter_damage_mod, CFilterDamageMod ); + +BEGIN_DATADESC( CFilterDamageMod ) + + DEFINE_KEYFIELD( m_iszNewAttacker, FIELD_STRING, "NewAttacker" ), + DEFINE_KEYFIELD( m_iszNewInflictor, FIELD_STRING, "NewInflictor" ), + DEFINE_KEYFIELD( m_iszNewWeapon, FIELD_STRING, "NewWeapon" ), + DEFINE_FIELD( m_hNewAttacker, FIELD_EHANDLE ), + DEFINE_FIELD( m_hNewInflictor, FIELD_EHANDLE ), + DEFINE_FIELD( m_hNewWeapon, FIELD_EHANDLE ), + + DEFINE_INPUT( m_flDamageMultiplier, FIELD_FLOAT, "SetDamageMultiplier" ), + DEFINE_INPUT( m_flDamageAddend, FIELD_FLOAT, "SetDamageAddend" ), + DEFINE_INPUT( m_iDamageBitsAdded, FIELD_INTEGER, "SetDamageBitsAdded" ), + DEFINE_INPUT( m_iDamageBitsRemoved, FIELD_INTEGER, "SetDamageBitsRemoved" ), + + DEFINE_KEYFIELD( m_iSecondaryFilterMode, FIELD_INTEGER, "SecondaryFilterMode" ), + + DEFINE_INPUTFUNC( FIELD_STRING, "SetNewAttacker", InputSetNewAttacker ), + DEFINE_INPUTFUNC( FIELD_STRING, "SetNewInflictor", InputSetNewInflictor ), + DEFINE_INPUTFUNC( FIELD_STRING, "SetNewWeapon", InputSetNewWeapon ), + +END_DATADESC() + +// ################################################################### +// > CFilterDamageLogic +// Fires outputs from damage information. +// ################################################################### +class CFilterDamageLogic : public CBaseFilterRedirect +{ + DECLARE_CLASS( CFilterDamageLogic, CBaseFilterRedirect ); + DECLARE_DATADESC(); +public: + bool PassesFinalDamageFilter( CBaseEntity *pCaller, const CTakeDamageInfo &info ) + { + bool bPassesFilter = !GetTargetFilter() || RedirectToDamageFilter( pCaller, info ); + if (!bPassesFilter) + { + if (m_iSecondaryFilterMode == 2) + return true; + else if (m_iSecondaryFilterMode != 1) + return false; + } + + CBaseEntity *pActivator = info.GetAttacker(); + + m_OutInflictor.Set( info.GetInflictor(), pActivator, pCaller ); + m_OutAttacker.Set( info.GetAttacker(), pActivator, pCaller ); + m_OutWeapon.Set( info.GetWeapon(), pActivator, pCaller ); + + m_OutDamage.Set( info.GetDamage(), pActivator, pCaller ); + m_OutMaxDamage.Set( info.GetMaxDamage(), pActivator, pCaller ); + m_OutBaseDamage.Set( info.GetBaseDamage(), pActivator, pCaller ); + + m_OutDamageType.Set( info.GetDamageType(), pActivator, pCaller ); + m_OutDamageCustom.Set( info.GetDamageCustom(), pActivator, pCaller ); + m_OutDamageStats.Set( info.GetDamageStats(), pActivator, pCaller ); + m_OutAmmoType.Set( info.GetAmmoType(), pActivator, pCaller ); + + m_OutDamageForce.Set( info.GetDamageForce(), pActivator, pCaller ); + m_OutDamagePosition.Set( info.GetDamagePosition(), pActivator, pCaller ); + + m_OutForceFriendlyFire.Set( info.IsForceFriendlyFire() ? 1 : 0, pActivator, pCaller ); + + return bPassesFilter; + } + + bool PassesDamageFilterImpl( CBaseEntity *pCaller, const CTakeDamageInfo &info ) + { + if (GetTargetFilter() && m_iSecondaryFilterMode != 2) + return RedirectToDamageFilter( pCaller, info ); + + return true; + } + + bool PassesFilterImpl( CBaseEntity *pCaller, CBaseEntity *pEntity ) + { + if (GetTargetFilter() && m_iSecondaryFilterMode != 2) + return RedirectToFilter( pCaller, pEntity ); + + return true; + } + + // 0 = Use as a regular damage filter. If it doesn't pass, damage won't be outputted. + // 1 = Fire outputs even if the secondary filter doesn't pass. + // 2 = Only use the secondary filter for whether to output damage, other damage is actually dealt. + int m_iSecondaryFilterMode; + + // Outputs + COutputEHANDLE m_OutInflictor; + COutputEHANDLE m_OutAttacker; + COutputEHANDLE m_OutWeapon; + + COutputFloat m_OutDamage; + COutputFloat m_OutMaxDamage; + COutputFloat m_OutBaseDamage; + + COutputInt m_OutDamageType; + COutputInt m_OutDamageCustom; + COutputInt m_OutDamageStats; + COutputInt m_OutAmmoType; + + COutputVector m_OutDamageForce; + COutputPositionVector m_OutDamagePosition; + + COutputInt m_OutForceFriendlyFire; +}; + +LINK_ENTITY_TO_CLASS( filter_damage_logic, CFilterDamageLogic ); + +BEGIN_DATADESC( CFilterDamageLogic ) + + DEFINE_KEYFIELD( m_iSecondaryFilterMode, FIELD_INTEGER, "SecondaryFilterMode" ), + + // Outputs + DEFINE_OUTPUT( m_OutInflictor, "OutInflictor" ), + DEFINE_OUTPUT( m_OutAttacker, "OutAttacker" ), + DEFINE_OUTPUT( m_OutWeapon, "OutWeapon" ), + + DEFINE_OUTPUT( m_OutDamage, "OutDamage" ), + DEFINE_OUTPUT( m_OutMaxDamage, "OutMaxDamage" ), + DEFINE_OUTPUT( m_OutBaseDamage, "OutBaseDamage" ), + + DEFINE_OUTPUT( m_OutDamageType, "OutDamageType" ), + DEFINE_OUTPUT( m_OutDamageCustom, "OutDamageCustom" ), + DEFINE_OUTPUT( m_OutDamageStats, "OutDamageStats" ), + DEFINE_OUTPUT( m_OutAmmoType, "OutAmmoType" ), + + DEFINE_OUTPUT( m_OutDamageForce, "OutDamageForce" ), + DEFINE_OUTPUT( m_OutDamagePosition, "OutDamagePosition" ), + + DEFINE_OUTPUT( m_OutForceFriendlyFire, "OutForceFriendlyFire" ), + +END_DATADESC() +#endif + +#ifdef MAPBASE_VSCRIPT +ScriptHook_t g_Hook_PassesFilter; +ScriptHook_t g_Hook_PassesDamageFilter; +ScriptHook_t g_Hook_PassesFinalDamageFilter; +ScriptHook_t g_Hook_BloodAllowed; +ScriptHook_t g_Hook_DamageMod; + +// ################################################################### +// > CFilterScript +// ################################################################### +class CFilterScript : public CBaseFilter +{ + DECLARE_CLASS( CFilterScript, CBaseFilter ); + DECLARE_DATADESC(); + DECLARE_ENT_SCRIPTDESC(); + +public: + bool PassesFilterImpl( CBaseEntity *pCaller, CBaseEntity *pEntity ) + { + if ( !m_ScriptScope.IsInitialized() ) + { + Warning( "%s: No script scope, cannot filter\n", GetDebugName() ); + return false; + } + + if ( g_Hook_PassesFilter.CanRunInScope( m_ScriptScope ) ) + { + // caller, activator + ScriptVariant_t functionReturn; + ScriptVariant_t args[] = { ToHScript( pCaller ), ToHScript( pEntity ) }; + g_Hook_PassesFilter.Call( m_ScriptScope, &functionReturn, args ); + + return functionReturn.m_bool; + } + + Warning( "%s: No PassesFilter function\n", GetDebugName() ); + return false; + } + + bool PassesDamageFilterImpl( CBaseEntity *pCaller, const CTakeDamageInfo &info ) + { + if ( !m_ScriptScope.IsInitialized() ) + { + Warning( "%s: No script scope, cannot filter\n", GetDebugName() ); + return false; + } + + if ( g_Hook_PassesDamageFilter.CanRunInScope( m_ScriptScope ) ) + { + HSCRIPT pInfo = g_pScriptVM->RegisterInstance( const_cast(&info) ); + + // caller, info + ScriptVariant_t functionReturn; + ScriptVariant_t args[] = { ToHScript( pCaller ), pInfo }; + g_Hook_PassesDamageFilter.Call( m_ScriptScope, &functionReturn, args ); + + g_pScriptVM->RemoveInstance( pInfo ); + + return functionReturn.m_bool; + } + + // Fall back to main filter function + return PassesFilterImpl( pCaller, info.GetAttacker() ); + } + + bool PassesFinalDamageFilter( CBaseEntity *pCaller, const CTakeDamageInfo &info ) + { + if ( !m_ScriptScope.IsInitialized() ) + { + Warning( "%s: No script scope, cannot filter\n", GetDebugName() ); + return false; + } + + if ( g_Hook_PassesFinalDamageFilter.CanRunInScope( m_ScriptScope ) ) + { + HSCRIPT pInfo = g_pScriptVM->RegisterInstance( const_cast(&info) ); + + // caller, info + ScriptVariant_t functionReturn; + ScriptVariant_t args[] = { ToHScript( pCaller ), pInfo }; + g_Hook_PassesFinalDamageFilter.Call( m_ScriptScope, &functionReturn, args ); + + g_pScriptVM->RemoveInstance( pInfo ); + + return functionReturn.m_bool; + } + + return BaseClass::PassesFinalDamageFilter( pCaller, info ); + } + + bool BloodAllowed( CBaseEntity *pCaller, const CTakeDamageInfo &info ) + { + if ( !m_ScriptScope.IsInitialized() ) + { + Warning( "%s: No script scope, cannot filter\n", GetDebugName() ); + return false; + } + + if ( g_Hook_BloodAllowed.CanRunInScope( m_ScriptScope ) ) + { + HSCRIPT pInfo = g_pScriptVM->RegisterInstance( const_cast(&info) ); + + // caller, info + ScriptVariant_t functionReturn; + ScriptVariant_t args[] = { ToHScript( pCaller ), pInfo }; + g_Hook_BloodAllowed.Call( m_ScriptScope, &functionReturn, args ); + + g_pScriptVM->RemoveInstance( pInfo ); + + return functionReturn.m_bool; + } + + return BaseClass::BloodAllowed( pCaller, info ); + } + + bool DamageMod( CBaseEntity *pCaller, CTakeDamageInfo &info ) + { + if ( !m_ScriptScope.IsInitialized() ) + { + Warning( "%s: No script scope, cannot filter\n", GetDebugName() ); + return false; + } + + if ( g_Hook_DamageMod.CanRunInScope( m_ScriptScope ) ) + { + HSCRIPT pInfo = g_pScriptVM->RegisterInstance( &info ); + + // caller, info + ScriptVariant_t functionReturn; + ScriptVariant_t args[] = { ToHScript( pCaller ), pInfo }; + g_Hook_DamageMod.Call( m_ScriptScope, &functionReturn, args ); + + g_pScriptVM->RemoveInstance( pInfo ); + + return functionReturn.m_bool; + } + + return BaseClass::DamageMod( pCaller, info ); + } +}; + +LINK_ENTITY_TO_CLASS( filter_script, CFilterScript ); + +BEGIN_DATADESC( CFilterScript ) +END_DATADESC() + +BEGIN_ENT_SCRIPTDESC( CFilterScript, CBaseFilter, "The filter_script entity which allows VScript functions to hook onto filter methods." ) + + // + // Hooks + // + + // The CFilterScript class is visible in the help string, so "A hook used by filter_script" is redundant, but these names are also + // used for functions in CBaseFilter. In order to reduce confusion, the description emphasizes that these are hooks. + BEGIN_SCRIPTHOOK( g_Hook_PassesFilter, "PassesFilter", FIELD_BOOLEAN, "A hook used by filter_script to determine what entities should pass it. Return true if the entity should pass or false if it should not. This hook is required for regular filtering." ) + DEFINE_SCRIPTHOOK_PARAM( "caller", FIELD_HSCRIPT ) + DEFINE_SCRIPTHOOK_PARAM( "activator", FIELD_HSCRIPT ) + END_SCRIPTHOOK() + + BEGIN_SCRIPTHOOK( g_Hook_PassesDamageFilter, "PassesDamageFilter", FIELD_BOOLEAN, "A hook used by filter_script to determine what damage should pass it when it's being used as a damage filter. Return true if the info should pass or false if it should not. If this hook is not defined in a filter_script, damage filter requests will instead check PassesFilter with the attacker as the activator." ) + DEFINE_SCRIPTHOOK_PARAM( "caller", FIELD_HSCRIPT ) + DEFINE_SCRIPTHOOK_PARAM( "info", FIELD_HSCRIPT ) + END_SCRIPTHOOK() + + BEGIN_SCRIPTHOOK( g_Hook_PassesFinalDamageFilter, "PassesFinalDamageFilter", FIELD_BOOLEAN, "A completely optional hook used by filter_script which only runs when the entity will take damage. This is different from PassesDamageFilter, which is sometimes used in cases where damage is not actually about to be taken. This also runs after a regular PassesDamageFilter check. Return true if the info should pass or false if it should not. If this hook is not defined, it will always return true." ) + DEFINE_SCRIPTHOOK_PARAM( "caller", FIELD_HSCRIPT ) + DEFINE_SCRIPTHOOK_PARAM( "info", FIELD_HSCRIPT ) + END_SCRIPTHOOK() + + BEGIN_SCRIPTHOOK( g_Hook_BloodAllowed, "BloodAllowed", FIELD_BOOLEAN, "A completely optional hook used by filter_script to determine if a caller is allowed to emit blood after taking damage. Return true if blood should be allowed or false if it should not. If this hook is not defined, it will always return true." ) + DEFINE_SCRIPTHOOK_PARAM( "caller", FIELD_HSCRIPT ) + DEFINE_SCRIPTHOOK_PARAM( "info", FIELD_HSCRIPT ) + END_SCRIPTHOOK() + + BEGIN_SCRIPTHOOK( g_Hook_DamageMod, "DamageMod", FIELD_BOOLEAN, "A completely optional hook used by filter_script to modify damage being taken by an entity. You are free to use CTakeDamageInfo functions on the damage info handle and it will change how the caller is damaged. Returning true or false currently has no effect on vanilla code, but you should generally return true if the damage info has been modified by your code and false if it was not. If this hook is not defined, it will always return false." ) + DEFINE_SCRIPTHOOK_PARAM( "caller", FIELD_HSCRIPT ) + DEFINE_SCRIPTHOOK_PARAM( "info", FIELD_HSCRIPT ) + END_SCRIPTHOOK() + +END_SCRIPTDESC() +#endif diff --git a/src/game/server/filters.h b/src/game/server/filters.h index 483de243..faaa20fd 100644 --- a/src/game/server/filters.h +++ b/src/game/server/filters.h @@ -38,15 +38,47 @@ class CBaseFilter : public CLogicalEntity public: DECLARE_DATADESC(); +#ifdef MAPBASE_VSCRIPT + DECLARE_ENT_SCRIPTDESC(); +#endif bool PassesFilter( CBaseEntity *pCaller, CBaseEntity *pEntity ); +#ifdef MAPBASE + bool PassesDamageFilter( CBaseEntity *pCaller, const CTakeDamageInfo &info ); + + // This was made for filter_damage_transfer. Should return true on all other filters. + virtual bool PassesFinalDamageFilter( CBaseEntity *pCaller, const CTakeDamageInfo &info ) { return true; } + + virtual bool BloodAllowed( CBaseEntity *pCaller, const CTakeDamageInfo &info ) { return true; } + + virtual bool DamageMod( CBaseEntity *pCaller, CTakeDamageInfo &info ) { return false; } + + // Deprecated. Pass the caller in front. bool PassesDamageFilter( const CTakeDamageInfo &info ); +#else + bool PassesDamageFilter( const CTakeDamageInfo &info ); +#endif + +#ifdef MAPBASE_VSCRIPT + bool ScriptPassesFilter( HSCRIPT pCaller, HSCRIPT pEntity ); + bool ScriptPassesDamageFilter( HSCRIPT pCaller, HSCRIPT pInfo ); + bool ScriptPassesFinalDamageFilter( HSCRIPT pCaller, HSCRIPT pInfo ); + bool ScriptBloodAllowed( HSCRIPT pCaller, HSCRIPT pInfo ); + bool ScriptDamageMod( HSCRIPT pCaller, HSCRIPT pInfo ); +#endif bool m_bNegated; // Inputs void InputTestActivator( inputdata_t &inputdata ); +#ifdef MAPBASE + void InputTestEntity( inputdata_t &inputdata ); + virtual void InputSetField( inputdata_t &inputdata ); + + bool m_bPassCallerWhenTested; +#endif + // Outputs COutputEvent m_OnPass; // Fired when filter is passed COutputEvent m_OnFail; // Fired when filter is failed @@ -54,7 +86,49 @@ public: protected: virtual bool PassesFilterImpl( CBaseEntity *pCaller, CBaseEntity *pEntity ); +#ifdef MAPBASE + virtual bool PassesDamageFilterImpl(CBaseEntity *pCaller, const CTakeDamageInfo &info); +#else virtual bool PassesDamageFilterImpl(const CTakeDamageInfo &info); +#endif }; +#ifdef MAPBASE +//========================================================= +// Trace filter that uses a filter entity. +// If the regular trace filter stuff tells this trace to hit an entity, it will go through a filter entity. +// If the entity passes the filter, the trace will go through. +// This can be negated with m_bHitIfPassed, meaning entities that pass will be hit. +// Use m_bFilterExclusive to make the filter the sole factor in hitting an entity. +//========================================================= +class CTraceFilterEntityFilter : public CTraceFilterSimple +{ +public: + CTraceFilterEntityFilter( const IHandleEntity *passentity, int collisionGroup ) : CTraceFilterSimple( passentity, collisionGroup ) {} + CTraceFilterEntityFilter( int collisionGroup ) : CTraceFilterSimple( NULL, collisionGroup ) {} + + bool ShouldHitEntity( IHandleEntity *pHandleEntity, int contentsMask ) + { + bool base = CTraceFilterSimple::ShouldHitEntity( pHandleEntity, contentsMask ); + CBaseEntity *pEntity = EntityFromEntityHandle( pHandleEntity ); + + if (m_bFilterExclusive && m_pFilter) + return m_pFilter->PassesFilter(m_pCaller, pEntity) ? m_bHitIfPassed : !m_bHitIfPassed; + else if (m_pFilter && (base ? !m_bHitIfPassed : m_bHitIfPassed)) + { + return m_bHitIfPassed ? m_pFilter->PassesFilter(m_pCaller, pEntity) : !m_pFilter->PassesFilter(m_pCaller, pEntity); + } + + return base; + } + + CBaseFilter *m_pFilter; + CBaseEntity *m_pCaller; + + bool m_bHitIfPassed; + bool m_bFilterExclusive; + +}; +#endif + #endif // FILTERS_H diff --git a/src/game/server/fire.cpp b/src/game/server/fire.cpp index ae207a51..878d6e64 100644 --- a/src/game/server/fire.cpp +++ b/src/game/server/fire.cpp @@ -17,11 +17,16 @@ #include "ispatialpartition.h" #include "collisionutils.h" #include "tier0/vprof.h" +#ifdef MAPBASE +#include "weapon_flaregun.h" +#include "mapbase/GlobalStrings.h" +#endif // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" +#ifndef MAPBASE /******************************************************************** NOTE: if you are looking at this file becase you would like flares to be considered as fires (and thereby trigger gas traps), be aware @@ -40,7 +45,13 @@ For some partial work towards this end, see changelist 192474. ********************************************************************/ - +#else +// ================================================================ // +// env_firesensors now have the ability to sense flares, toggled via spawnflag. +// This is different from integrating it with the greater fire system and easily preserves original behavior. +// You could try treating flares as real fires yourself if you think it's an issue. +// ================================================================ // +#endif @@ -243,7 +254,11 @@ IterationRetval_t CFireSphere::EnumElement( IHandleEntity *pHandleEntity ) { // UNDONE: Measure which of these is faster // CFire *pFire = dynamic_cast(pEntity); +#ifdef MAPBASE + if ( !EntIsClass( pEntity, gm_isz_class_EnvFire ) ) +#else if ( !FClassnameIs( pEntity, "env_fire" ) ) +#endif return ITERATION_CONTINUE; CFire *pFire = static_cast(pEntity); @@ -995,7 +1010,11 @@ void CFire::Update( float simTime ) { continue; } +#ifdef MAPBASE + else if ( EntIsClass( pOther, gm_isz_class_EnvFire ) ) +#else else if ( FClassnameIs( pOther, "env_fire" ) ) +#endif { if ( fireCount < ARRAYSIZE(pFires) ) { @@ -1307,6 +1326,9 @@ void CEnvFireSource::InputDisable( inputdata_t &inputdata ) // CEnvFireSensor detects changes in heat //================================================== #define SF_FIRESENSOR_START_ON 1 +#ifdef MAPBASE +#define SF_FIRESENSOR_ACCEPT_FLARES 2 +#endif class CEnvFireSensor : public CBaseEntity { @@ -1318,6 +1340,9 @@ public: void TurnOff(); void InputEnable( inputdata_t &inputdata ); void InputDisable( inputdata_t &inputdata ); +#ifdef MAPBASE + int DrawDebugTextOverlays(void); +#endif DECLARE_DATADESC(); @@ -1328,6 +1353,10 @@ private: float m_targetLevel; float m_targetTime; float m_levelTime; +#ifdef MAPBASE + // Only stored for access in debug overlays, don't save + float m_curheat; +#endif COutputEvent m_OnHeatLevelStart; COutputEvent m_OnHeatLevelEnd; @@ -1385,6 +1414,28 @@ void CEnvFireSensor::Think() heat += pFires[i]->GetHeatLevel(); } +#ifdef MAPBASE + if (HasSpawnFlags(SF_FIRESENSOR_ACCEPT_FLARES)) + { + // Also look for nearby flares + CBaseEntity *pEntity = gEntList.FindEntityByClassnameWithin( NULL, "env_flare", GetAbsOrigin(), m_radius ); + while (pEntity) + { + CFlare *pFlare = static_cast(pEntity); + if (pFlare) + { + heat += (pFlare->m_flTimeBurnOut > -1.0 ? (pFlare->m_flTimeBurnOut - gpGlobals->curtime) : 32); + } + + pEntity = gEntList.FindEntityByClassnameWithin( pEntity, "env_flare", GetAbsOrigin(), m_radius ); + } + } +#endif + +#ifdef MAPBASE + m_curheat = heat; +#endif + if ( heat >= m_targetLevel ) { m_levelTime += time; @@ -1442,6 +1493,28 @@ void CEnvFireSensor::InputDisable( inputdata_t &inputdata ) TurnOff(); } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: Draw any debug text overlays +// Output : Current text offset from the top +//----------------------------------------------------------------------------- +int CEnvFireSensor::DrawDebugTextOverlays( void ) +{ + int text_offset = BaseClass::DrawDebugTextOverlays(); + + if (m_debugOverlays & OVERLAY_TEXT_BIT) + { + char tempstr[512]; + + // print flame size + Q_snprintf(tempstr, sizeof(tempstr), " Current Heat: %f", m_curheat); + EntityText(text_offset,tempstr,0); + text_offset++; + } + return text_offset; +} +#endif + //----------------------------------------------------------------------------- // Purpose: Draw any debug text overlays // Output : Current text offset from the top diff --git a/src/game/server/fish.cpp b/src/game/server/fish.cpp index e6620068..a6953f62 100644 --- a/src/game/server/fish.cpp +++ b/src/game/server/fish.cpp @@ -540,6 +540,19 @@ BEGIN_DATADESC( CFishPool ) DEFINE_FIELD( m_isDormant, FIELD_BOOLEAN ), DEFINE_UTLVECTOR( m_fishes, FIELD_EHANDLE ), +#ifdef MAPBASE + DEFINE_INPUT( m_nSkin, FIELD_INTEGER, "skin" ), + + DEFINE_KEYFIELD( m_flLoudPanicRange, FIELD_FLOAT, "LoudPanicRange" ), + DEFINE_KEYFIELD( m_flQuietPanicRange, FIELD_FLOAT, "QuietPanicRange" ), + + DEFINE_INPUTFUNC( FIELD_VOID, "SpawnFish", InputSpawnFish ), + DEFINE_INPUTFUNC( FIELD_VECTOR, "PanicLoudFromPoint", InputPanicLoudFromPoint ), + DEFINE_INPUTFUNC( FIELD_VECTOR, "PanicQuietFromPoint", InputPanicQuietFromPoint ), + + DEFINE_OUTPUT( m_OnSpawnFish, "OnSpawnFish" ), +#endif + DEFINE_THINKFUNC( Update ), END_DATADESC() @@ -553,6 +566,14 @@ CFishPool::CFishPool( void ) m_swimDepth = 0.0f; m_isDormant = false; +#ifdef MAPBASE + m_nSkin = 0; + + // Original defaults + m_flLoudPanicRange = 500.0f; + m_flQuietPanicRange = 75.0f; +#endif + m_visTimer.Start( 0.5f ); ListenForGameEvent( "player_shoot" ); @@ -588,6 +609,10 @@ void CFishPool::Spawn() CHandle hFish; hFish.Set( fish ); m_fishes.AddToTail( hFish ); +#ifdef MAPBASE + fish->m_nSkin = m_nSkin; + m_OnSpawnFish.Set( hFish->GetBaseEntity(), fish, this ); +#endif } } } @@ -638,10 +663,14 @@ void CFishPool::FireGameEvent( IGameEvent *event ) CBasePlayer *player = UTIL_PlayerByUserId( event->GetInt( "userid" ) ); // the fish panic +#ifdef MAPBASE + float range = (Q_strcmp( "player_footstep", event->GetName() )) ? m_flLoudPanicRange : m_flQuietPanicRange; +#else const float loudRange = 500.0f; const float quietRange = 75.0f; float range = (Q_strcmp( "player_footstep", event->GetName() )) ? loudRange : quietRange; +#endif for( int i=0; iResetVisible(); } @@ -731,3 +769,63 @@ void CFishPool::Update( void ) } } +#ifdef MAPBASE +//------------------------------------------------------------------------------------------------------------- +/** + * Inputs + */ +void CFishPool::InputSpawnFish( inputdata_t &inputdata ) +{ + QAngle heading( 0.0f, RandomFloat( 0, 360.0f ), 0.0f ); + + CFish *fish = (CFish *)Create( "fish", GetAbsOrigin(), heading, this ); + fish->Initialize( this, m_fishes.Count() ); + + if (fish) + { + CHandle hFish; + hFish.Set( fish ); + m_fishes.AddToTail( hFish ); +#ifdef MAPBASE + m_OnSpawnFish.Set( hFish->GetBaseEntity(), fish, this); +#endif + } +} + +void CFishPool::InputPanicLoudFromPoint( inputdata_t &inputdata ) +{ + // Make the fish panic from this point + Vector vecPoint; + inputdata.value.Vector3D( vecPoint ); + for( int i=0; iGetAbsOrigin()).IsLengthGreaterThan( m_flLoudPanicRange )) + { + // event too far away to care + continue; + } + + m_fishes[i]->Panic(); + } +} + +void CFishPool::InputPanicQuietFromPoint( inputdata_t &inputdata ) +{ + // Make the fish panic from this point + Vector vecPoint; + inputdata.value.Vector3D( vecPoint ); + for( int i=0; iGetAbsOrigin()).IsLengthGreaterThan( m_flQuietPanicRange )) + { + // event too far away to care + continue; + } + + m_fishes[i]->Panic(); + } +} +#endif + diff --git a/src/game/server/fish.h b/src/game/server/fish.h index 6eb7e64d..257bdbcd 100644 --- a/src/game/server/fish.h +++ b/src/game/server/fish.h @@ -109,6 +109,12 @@ public: float GetWaterLevel( void ) const; ///< return Z coordinate of water in world coords float GetMaxRange( void ) const; ///< return how far a fish is allowed to wander +#ifdef MAPBASE + void InputSpawnFish( inputdata_t &inputdata ); + void InputPanicLoudFromPoint( inputdata_t &inputdata ); + void InputPanicQuietFromPoint( inputdata_t &inputdata ); +#endif + private: int m_fishCount; ///< number of fish in the pool float m_maxRange; ///< how far a fish is allowed to wander @@ -120,6 +126,15 @@ private: CUtlVector< CHandle > m_fishes; ///< vector of all fish in this pool +#ifdef MAPBASE + int m_nSkin; // Sets the skin of spawned fish + + float m_flLoudPanicRange; + float m_flQuietPanicRange; + + COutputEHANDLE m_OnSpawnFish; +#endif + CountdownTimer m_visTimer; ///< for throttling line of sight checks between all fish }; diff --git a/src/game/server/fogcontroller.cpp b/src/game/server/fogcontroller.cpp index a68229e1..74b332b2 100644 --- a/src/game/server/fogcontroller.cpp +++ b/src/game/server/fogcontroller.cpp @@ -403,5 +403,17 @@ void CFogSystem::LevelInitPostEntity( void ) pPlayer->InitFogController(); } } + else + { + for ( int i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBasePlayer *pPlayer = UTIL_PlayerByIndex( i ); + + if ( pPlayer && ( pPlayer->m_Local.m_PlayerFog.m_hCtrl.Get() == NULL ) ) + { + pPlayer->InitFogController(); + } + } + } } diff --git a/src/game/server/fogvolume.cpp b/src/game/server/fogvolume.cpp new file mode 100644 index 00000000..f2f9a718 --- /dev/null +++ b/src/game/server/fogvolume.cpp @@ -0,0 +1,153 @@ +//-------------------------------------------------------------------------------------------------------- +// Copyright (c) 2007 Turtle Rock Studios, Inc. - All Rights Reserved + +#include "cbase.h" +#include "fogvolume.h" +#include "collisionutils.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + + +CUtlVector< CFogVolume * > TheFogVolumes; + +ConVar fog_volume_debug( "fog_volume_debug", "0", 0, "If enabled, prints diagnostic information about the current fog volume" ); + +//-------------------------------------------------------------------------------------------------------- +LINK_ENTITY_TO_CLASS(fog_volume, CFogVolume); + +BEGIN_DATADESC( CFogVolume ) + + DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ), + DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ), + + DEFINE_KEYFIELD( m_fogName, FIELD_STRING, "FogName" ), + DEFINE_KEYFIELD( m_postProcessName, FIELD_STRING, "PostProcessName" ), + DEFINE_KEYFIELD( m_colorCorrectionName, FIELD_STRING, "ColorCorrectionName" ), + DEFINE_KEYFIELD( m_bDisabled, FIELD_BOOLEAN, "StartDisabled" ), + + DEFINE_FIELD( m_hFogController, FIELD_EHANDLE ), + DEFINE_FIELD( m_hPostProcessController, FIELD_EHANDLE ), + DEFINE_FIELD( m_hColorCorrectionController, FIELD_EHANDLE ), + +END_DATADESC() + + +//-------------------------------------------------------------------------------------------------------- +CFogVolume *CFogVolume::FindFogVolumeForPosition( const Vector &position ) +{ + CFogVolume *fogVolume = NULL; + for ( int i=0; iCollisionProp()->WorldToCollisionSpace( position, &vecRelativeCenter ); + if ( IsBoxIntersectingSphere( fogVolume->CollisionProp()->OBBMins(), fogVolume->CollisionProp()->OBBMaxs(), vecRelativeCenter, 1.0f ) ) + { + break; + } + fogVolume = NULL; + } + + // This doesn't work well if there are multiple players or multiple fog volume queries per frame; might want to relocate this if that's the case + if ( fog_volume_debug.GetBool() ) + { + if ( fogVolume ) + { + char fogVolumeName[256]; + fogVolume->GetKeyValue( "targetname", fogVolumeName, 256 ); + engine->Con_NPrintf( 0, "Fog Volume ""%s"" found at position (%f %f %f)", fogVolumeName, position.x, position.y, position.z ); + engine->Con_NPrintf( 1, "Fog: %s, post process: %s, color correct: %s", fogVolume->m_fogName, fogVolume->m_postProcessName, fogVolume->m_colorCorrectionName ); + } + else + { + engine->Con_NPrintf( 0, "No Fog Volume found at given position (%f %f %f)", position.x, position.y, position.z ); + } + } + + return fogVolume; +} + + +//-------------------------------------------------------------------------------------------------------- +CFogVolume::CFogVolume() : + BaseClass(), + m_bDisabled( false ), + m_bInFogVolumesList( false ) +{ +} + + +//-------------------------------------------------------------------------------------------------------- +CFogVolume::~CFogVolume() +{ + RemoveFromGlobalList(); +} + + +//-------------------------------------------------------------------------------------------------------- +void CFogVolume::Spawn( void ) +{ + BaseClass::Spawn(); + + SetSolid( SOLID_BSP ); + SetSolidFlags( FSOLID_NOT_SOLID ); + SetModel( STRING( GetModelName() ) ); +} + + +//-------------------------------------------------------------------------------------------------------- +void CFogVolume::AddToGlobalList() +{ + if ( !m_bInFogVolumesList ) + { + TheFogVolumes.AddToTail( this ); + m_bInFogVolumesList = true; + } +} + + +//-------------------------------------------------------------------------------------------------------- +void CFogVolume::RemoveFromGlobalList() +{ + if ( m_bInFogVolumesList ) + { + TheFogVolumes.FindAndRemove( this ); + m_bInFogVolumesList = false; + } +} + + +//---------------------------------------------------------------------------- +void CFogVolume::InputEnable( inputdata_t &data ) +{ + m_bDisabled = false; + AddToGlobalList(); +} + + +//---------------------------------------------------------------------------- +void CFogVolume::InputDisable( inputdata_t &data ) +{ + m_bDisabled = true; + RemoveFromGlobalList(); +} + + +//---------------------------------------------------------------------------- +// Called when the level loads or is restored +//---------------------------------------------------------------------------- +void CFogVolume::Activate() +{ + BaseClass::Activate(); + + m_hFogController = dynamic_cast< CFogController* >( gEntList.FindEntityByName( NULL, m_fogName ) ); + m_hPostProcessController = dynamic_cast< CPostProcessController* >( gEntList.FindEntityByName( NULL, m_postProcessName ) ); + m_hColorCorrectionController = dynamic_cast< CColorCorrection* >( gEntList.FindEntityByName( NULL, m_colorCorrectionName ) ); + + if ( !m_bDisabled ) + { + AddToGlobalList(); + } +} diff --git a/src/game/server/fogvolume.h b/src/game/server/fogvolume.h new file mode 100644 index 00000000..6bd5880a --- /dev/null +++ b/src/game/server/fogvolume.h @@ -0,0 +1,74 @@ +//-------------------------------------------------------------------------------------------------------- +// Copyright (c) 2007 Turtle Rock Studios, Inc. - All Rights Reserved + +#ifndef FOG_VOLUME_H +#define FOG_VOLUME_H + +#ifdef _WIN32 +#pragma once +#endif + + +class CFogController; +class CPostProcessController; +class CColorCorrection; + + +//-------------------------------------------------------------------------------------------------------- +// Fog volume entity +class CFogVolume : public CServerOnlyEntity +{ + DECLARE_CLASS( CFogVolume, CServerOnlyEntity ); + DECLARE_DATADESC(); + +public: + CFogVolume(); + virtual ~CFogVolume(); + virtual void Spawn( void ); + virtual void Activate(); + + static CFogVolume *FindFogVolumeForPosition( const Vector &position ); + + const char *GetFogControllerName() const + { + return STRING( m_fogName ); + } + + CFogController* GetFogController( ) const + { + return m_hFogController.Get(); + } + + CPostProcessController* GetPostProcessController( ) const + { + return m_hPostProcessController.Get(); + } + + CColorCorrection* GetColorCorrectionController( ) const + { + return m_hColorCorrectionController.Get(); + } + + void InputEnable( inputdata_t &data ); + void InputDisable( inputdata_t &data ); + +private: + string_t m_fogName; + string_t m_postProcessName; + string_t m_colorCorrectionName; + + CHandle< CFogController > m_hFogController; + CHandle< CPostProcessController > m_hPostProcessController; + CHandle< CColorCorrection > m_hColorCorrectionController; + + bool m_bDisabled; + bool m_bInFogVolumesList; + + void AddToGlobalList(); + void RemoveFromGlobalList(); +}; + +extern CUtlVector< CFogVolume * > TheFogVolumes; + + +#endif // FOG_VOLUME_H \ No newline at end of file diff --git a/src/game/server/fourwheelvehiclephysics.cpp b/src/game/server/fourwheelvehiclephysics.cpp index 3a70d02d..ba4a827a 100644 --- a/src/game/server/fourwheelvehiclephysics.cpp +++ b/src/game/server/fourwheelvehiclephysics.cpp @@ -148,6 +148,39 @@ BEGIN_DATADESC_NO_BASE( CFourWheelVehiclePhysics ) DEFINE_FIELD( m_bLastSkid, FIELD_BOOLEAN ), END_DATADESC() +#ifdef MAPBASE_VSCRIPT +BEGIN_SCRIPTDESC_ROOT( CFourWheelVehiclePhysics, "Handler for four-wheel vehicle physics." ) + + DEFINE_SCRIPTFUNC( SetThrottle, "Sets the throttle." ) + DEFINE_SCRIPTFUNC( SetMaxThrottle, "Sets the max throttle." ) + DEFINE_SCRIPTFUNC( SetMaxReverseThrottle, "Sets the max reverse throttle." ) + DEFINE_SCRIPTFUNC( SetSteering, "Sets the steering." ) + DEFINE_SCRIPTFUNC( SetSteeringDegrees, "Sets the degrees of steering." ) + DEFINE_SCRIPTFUNC( SetAction, "Sets the action." ) + DEFINE_SCRIPTFUNC( SetHandbrake, "Sets the handbrake." ) + DEFINE_SCRIPTFUNC( SetBoost, "Sets the boost." ) + DEFINE_SCRIPTFUNC( SetHasBrakePedal, "Sets whether a handbrake pedal exists." ) + + DEFINE_SCRIPTFUNC( SetDisableEngine, "Sets whether the engine is disabled." ) + DEFINE_SCRIPTFUNC( IsEngineDisabled, "Checks whether the engine is disabled." ) + + DEFINE_SCRIPTFUNC( EnableMotion, "Enables vehicle motion." ) + DEFINE_SCRIPTFUNC( DisableMotion, "Disables vehicle motion." ) + + DEFINE_SCRIPTFUNC( GetSpeed, "Gets the speed." ) + DEFINE_SCRIPTFUNC( GetMaxSpeed, "Gets the max speed." ) + DEFINE_SCRIPTFUNC( GetRPM, "Gets the RPM." ) + DEFINE_SCRIPTFUNC( GetThrottle, "Gets the throttle." ) + DEFINE_SCRIPTFUNC( HasBoost, "Checks if the vehicle has the ability to boost." ) + DEFINE_SCRIPTFUNC( BoostTimeLeft, "Gets how much time is left in any current boost." ) + DEFINE_SCRIPTFUNC( IsBoosting, "Checks if the vehicle is boosting." ) + DEFINE_SCRIPTFUNC( GetHLSpeed, "Gets HL speed." ) + DEFINE_SCRIPTFUNC( GetSteering, "Gets the steeering." ) + DEFINE_SCRIPTFUNC( GetSteeringDegrees, "Gets the degrees of steeering." ) + +END_SCRIPTDESC(); +#endif + //----------------------------------------------------------------------------- // Constructor diff --git a/src/game/server/func_areaportal.cpp b/src/game/server/func_areaportal.cpp index ca391317..d2e50a35 100644 --- a/src/game/server/func_areaportal.cpp +++ b/src/game/server/func_areaportal.cpp @@ -42,6 +42,11 @@ public: virtual bool UpdateVisibility( const Vector &vOrigin, float fovDistanceAdjustFactor, bool &bIsOpenOnClient ); +#ifdef MAPBASE + // For func_areaportal_oneway. + int GetPortalState() { return m_state; } +#endif + DECLARE_DATADESC(); private: @@ -189,3 +194,197 @@ int CAreaPortal::UpdateTransmitState() return SetTransmitState( FL_EDICT_DONTSEND ); } +#ifdef MAPBASE +// An areaportal that automatically closes and opens depending on the direction of the client. +// http://developer.valvesoftware.com/wiki/CAreaPortalOneWay +class CAreaPortalOneWay : public CAreaPortal // CAPOW! +{ + DECLARE_CLASS( CAreaPortalOneWay, CAreaPortal ); + DECLARE_DATADESC(); + +public: + Vector m_vecOpenVector; + bool m_bAvoidPop; + bool m_bOneWayActive; + + void Spawn(); + void Activate(); + int Restore(IRestore &restore); + bool UpdateVisibility( const Vector &vOrigin, float fovDistanceAdjustFactor, bool &bIsOpenOnClient ); + + void InputDisableOneWay( inputdata_t &inputdata ); + void InputEnableOneWay( inputdata_t &inputdata ); + void InputToggleOneWay( inputdata_t &inputdata ); + void InputInvertOneWay( inputdata_t &inputdata ); + +protected: + void RemoteUpdate( bool IsOpen ); + + bool m_bRemotelyUpdated; + bool m_bRemoteCalcWasOpen; + CHandle m_hNextPortal; // This get saved to disc... + CAreaPortalOneWay* m_pNextPortal; // ...while this gets used at runtime, avoiding loads of casts + +private: + void UpdateNextPortal( bool IsOpen ); + + // These two are irrelevant once the entity has established itself + string_t m_strGroupName; + Vector m_vecOrigin_; // The portal won't compile properly if vecOrigin itself has a value, but it's okay to move something in at runtime +}; + +LINK_ENTITY_TO_CLASS( func_areaportal_oneway, CAreaPortalOneWay ); + +BEGIN_DATADESC( CAreaPortalOneWay ) + DEFINE_KEYFIELD( m_vecOpenVector, FIELD_VECTOR, "onewayfacing" ), + DEFINE_KEYFIELD( m_bAvoidPop, FIELD_BOOLEAN, "avoidpop" ), + DEFINE_KEYFIELD_NOT_SAVED( m_vecOrigin_, FIELD_VECTOR, "origin_" ), + DEFINE_KEYFIELD_NOT_SAVED( m_strGroupName, FIELD_STRING, "group" ), + DEFINE_FIELD( m_bOneWayActive, FIELD_BOOLEAN ), + DEFINE_FIELD( m_hNextPortal, FIELD_EHANDLE ), + DEFINE_INPUTFUNC( FIELD_VOID, "DisableOneWay", InputDisableOneWay ), + DEFINE_INPUTFUNC( FIELD_VOID, "EnableOneWay", InputEnableOneWay ), + DEFINE_INPUTFUNC( FIELD_VOID, "ToggleOneWay", InputToggleOneWay ), + DEFINE_INPUTFUNC( FIELD_VOID, "InvertOneWay", InputInvertOneWay ), +END_DATADESC() + +void CAreaPortalOneWay::Spawn() +{ + // Convert our angle from Hammer to a proper vector + QAngle angOpenDir = QAngle( m_vecOpenVector.x, m_vecOpenVector.y, m_vecOpenVector.z ); + AngleVectors( angOpenDir, &m_vecOpenVector ); + + SetLocalOrigin(m_vecOrigin_); + m_bOneWayActive = true; + m_bRemotelyUpdated = false; + + BaseClass::Spawn(); +} + +void CAreaPortalOneWay::Activate() +{ + // Optimisation: share open/closed value for CAPOWs with the same GroupName. + if (m_strGroupName != NULL_STRING) + { + for( unsigned short i = GetPortalListElement(); i != g_AreaPortals.InvalidIndex(); i = g_AreaPortals.Next(i) ) + { + CAreaPortalOneWay* pCur = dynamic_cast(g_AreaPortals[i]); + + if ( pCur && pCur != this && strcmp( STRING(m_strGroupName),STRING(pCur->m_strGroupName) ) == 0 ) + { + m_pNextPortal = pCur; + m_hNextPortal = pCur; + break; + } + } + } + + BaseClass::Activate(); +} + +int CAreaPortalOneWay::Restore(IRestore &restore) +{ + if ( m_hNextPortal.IsValid() ) + m_pNextPortal = m_hNextPortal.Get(); + + return BaseClass::Restore(restore); +} + +// Disable the CAPOW (becomes a normal AP) +void CAreaPortalOneWay::InputDisableOneWay( inputdata_t &inputdata ) +{ + m_bOneWayActive = false; +} + +// Re-enable the CAPOW +void CAreaPortalOneWay::InputEnableOneWay( inputdata_t &inputdata ) +{ + m_bOneWayActive = true; +} + +// Toggle CAPOW +void CAreaPortalOneWay::InputToggleOneWay( inputdata_t &inputdata ) +{ + m_bOneWayActive = !m_bOneWayActive; +} + +// Flip the one way direction +void CAreaPortalOneWay::InputInvertOneWay( inputdata_t &inputdata ) +{ + m_vecOpenVector.Negate(); +} + +// Recieve a shared state from another CAPOW, then pass it on to the next +void CAreaPortalOneWay::RemoteUpdate( bool IsOpen ) +{ + m_bRemotelyUpdated = true; + m_bRemoteCalcWasOpen = IsOpen; + UpdateNextPortal(IsOpen); +} + +// Inline func since this code is required three times +inline void CAreaPortalOneWay::UpdateNextPortal( bool IsOpen ) +{ + if (m_pNextPortal) + m_pNextPortal->RemoteUpdate(IsOpen); +} + +#define VIEWER_PADDING 80 // Value copied from func_areaportalbase.cpp + +bool CAreaPortalOneWay::UpdateVisibility( const Vector &vOrigin, float fovDistanceAdjustFactor, bool &bIsOpenOnClient ) +{ + if (!m_bOneWayActive) + return BaseClass::UpdateVisibility( vOrigin, fovDistanceAdjustFactor, bIsOpenOnClient ); + + if( m_portalNumber == -1 || GetPortalState() == AREAPORTAL_CLOSED ) + { + bIsOpenOnClient = false; + return false; + } + + // Has another CAPOW on our plane already done a calculation? + // Note that the CAPOW chain is traversed with new values in RemoteUpdate(), NOT here + if (m_bRemotelyUpdated) + { + m_bRemotelyUpdated = false; + return m_bRemoteCalcWasOpen ? BaseClass::UpdateVisibility( vOrigin, fovDistanceAdjustFactor, bIsOpenOnClient ) : false; + } + + // *********************** + // If we've got this far then we're the first CAPOW in the chain this frame + // and need to calculate a value and pass it along said chain ourselves + // *********************** + + float dist = VIEWER_PADDING; // Assume open for backfacing tests... + VPlane plane; + if( engine->GetAreaPortalPlane(vOrigin,m_portalNumber,&plane) ) + dist = plane.DistTo(vOrigin); // ...but if we find a plane, use a genuine figure instead. + // This is done because GetAreaPortalPlane only works for + // portals facing the current area. + + // We can use LocalOrigin here because APs never have parents. + float dot = DotProduct(m_vecOpenVector,vOrigin - GetLocalOrigin()); + + if( dot > 0 ) + { + // We are on the open side of the portal. Pass the result on! + UpdateNextPortal(true); + + // The following backfacing check is the inverse of CFuncAreaPortalBase's: + // it /closes/ the portal if the camera is /behind/ the plane. IsOpenOnClient + // is left alone as per func_areaportalbase.h + return dist < -VIEWER_PADDING ? false : true; + } + else // Closed side + { + // To avoid latency pop when crossing the portal's plane, it is only + // closed on the client if said client is outside the "padding zone". + if ( !m_bAvoidPop || (m_bAvoidPop && dist > VIEWER_PADDING) ) + bIsOpenOnClient = false; + + // We are definitely closed on the server, however. + UpdateNextPortal(false); + return false; + } +} +#endif diff --git a/src/game/server/func_areaportalbase.h b/src/game/server/func_areaportalbase.h index ff6c167e..124ece47 100644 --- a/src/game/server/func_areaportalbase.h +++ b/src/game/server/func_areaportalbase.h @@ -95,6 +95,10 @@ public: // see into area 2. virtual bool UpdateVisibility( const Vector &vOrigin, float fovDistanceAdjustFactor, bool &bIsOpenOnClient ); +#ifdef MAPBASE + // For func_areaportal_oneway. + unsigned short GetPortalListElement() { return m_AreaPortalsElement; } +#endif public: diff --git a/src/game/server/func_break.cpp b/src/game/server/func_break.cpp index 55ab50c2..6894bc9d 100644 --- a/src/game/server/func_break.cpp +++ b/src/game/server/func_break.cpp @@ -223,6 +223,14 @@ bool CBreakable::KeyValue( const char *szKeyName, const char *szValue ) int object = atoi( szValue ); if ( object > 0 && object < ARRAYSIZE(pSpawnObjects) ) m_iszSpawnObject = MAKE_STRING( pSpawnObjects[object] ); +#ifdef MAPBASE + // "0" is the default value of a "choices" field in Hammer, representing nothing selected + // atoi() returning 0 may also indicate a failed conversion, so check szValue directly + else if ( FStrEq( szValue, "0" ) ) + m_iszSpawnObject = NULL_STRING; + else + m_iszSpawnObject = AllocPooledString(szValue); +#endif } else if (FStrEq(szKeyName, "propdata") ) { @@ -436,9 +444,10 @@ void CBreakable::Precache( void ) case matCinderBlock: pGibName = "ConcreteChunks"; break; + #endif -#if HL2_EPISODIC +#if HL2_EPISODIC || MAPBASE case matNone: pGibName = ""; break; diff --git a/src/game/server/func_breakablesurf.cpp b/src/game/server/func_breakablesurf.cpp index 902722b5..7ce51e8f 100644 --- a/src/game/server/func_breakablesurf.cpp +++ b/src/game/server/func_breakablesurf.cpp @@ -35,6 +35,9 @@ // Spawn flags #define SF_BREAKABLESURF_CRACK_DECALS 0x00000001 #define SF_BREAKABLESURF_DAMAGE_FROM_HELD_OBJECTS 0x00000002 +#ifdef MAPBASE +#define SF_BREAKABLESURF_PLAY_BREAK_SOUND 0x00000004 +#endif //############################################################################# // > CWindowPane @@ -609,7 +612,15 @@ void CBreakableSurface::Die( CBaseEntity *pBreaker, const Vector &vAttackDir ) return; // Play a break sound +#ifdef MAPBASE + if ( HasSpawnFlags(SF_BREAKABLESURF_PLAY_BREAK_SOUND) ) + { + Vector centerPos = (m_vLLVertex + m_vURVertex) / 2; + PhysBreakSound( this, VPhysicsGetObject(), centerPos ); + } +#else PhysBreakSound( this, VPhysicsGetObject(), GetAbsOrigin() ); +#endif m_bIsBroken = true; m_iHealth = 0.0f; diff --git a/src/game/server/func_lod.cpp b/src/game/server/func_lod.cpp index f4e2c663..5714f0fa 100644 --- a/src/game/server/func_lod.cpp +++ b/src/game/server/func_lod.cpp @@ -32,6 +32,9 @@ public: // (waits until it's out of the view frustrum or until there's a lot of motion) // (m_fDisappearDist+): the bmodel is forced to be invisible CNetworkVar( float, m_fDisappearDist ); +#ifdef MAPBASE + CNetworkVar( float, m_fDisappearMaxDist ); +#endif // CBaseEntity overrides. public: @@ -45,6 +48,9 @@ public: IMPLEMENT_SERVERCLASS_ST(CFunc_LOD, DT_Func_LOD) SendPropFloat(SENDINFO(m_fDisappearDist), 0, SPROP_NOSCALE), +#ifdef MAPBASE + SendPropFloat(SENDINFO(m_fDisappearMaxDist), 0, SPROP_NOSCALE), +#endif END_SEND_TABLE() @@ -57,6 +63,9 @@ LINK_ENTITY_TO_CLASS(func_lod, CFunc_LOD); BEGIN_DATADESC( CFunc_LOD ) DEFINE_FIELD( m_fDisappearDist, FIELD_FLOAT ), +#ifdef MAPBASE + DEFINE_FIELD( m_fDisappearMaxDist, FIELD_FLOAT ), +#endif END_DATADESC() @@ -102,6 +111,16 @@ bool CFunc_LOD::KeyValue( const char *szKeyName, const char *szValue ) { m_fDisappearDist = (float)atof(szValue); } +#ifdef MAPBASE + else if (FStrEq(szKeyName, "DisappearMaxDist")) + { + m_fDisappearMaxDist = (float)atof(szValue); + } + else if (FStrEq(szKeyName, "DisappearMinDist")) // Forwards compatibility + { + m_fDisappearDist = (float)atof(szValue); + } +#endif else if (FStrEq(szKeyName, "Solid")) { if (atoi(szValue) != 0) diff --git a/src/game/server/func_movelinear.cpp b/src/game/server/func_movelinear.cpp index 397fefd1..0fd92a82 100644 --- a/src/game/server/func_movelinear.cpp +++ b/src/game/server/func_movelinear.cpp @@ -39,6 +39,9 @@ BEGIN_DATADESC( CFuncMoveLinear ) DEFINE_KEYFIELD( m_flBlockDamage, FIELD_FLOAT, "BlockDamage"), DEFINE_KEYFIELD( m_flStartPosition, FIELD_FLOAT, "StartPosition"), DEFINE_KEYFIELD( m_flMoveDistance, FIELD_FLOAT, "MoveDistance"), +#ifdef MAPBASE + DEFINE_FIELD( m_vecReference, FIELD_VECTOR ), +#endif // DEFINE_PHYSPTR( m_pFluidController ), // Inputs @@ -84,9 +87,16 @@ void CFuncMoveLinear::Spawn( void ) m_flMoveDistance = DotProductAbs( m_vecMoveDir, vecOBB ) - m_flLip; } +#ifdef MAPBASE + m_vecPosition1 = GetLocalOrigin() - (m_vecMoveDir * m_flMoveDistance * m_flStartPosition); + m_vecPosition2 = m_vecPosition1 + (m_vecMoveDir * m_flMoveDistance); + m_vecFinalDest = GetLocalOrigin(); + m_vecReference = GetLocalOrigin(); +#else m_vecPosition1 = GetAbsOrigin() - (m_vecMoveDir * m_flMoveDistance * m_flStartPosition); m_vecPosition2 = m_vecPosition1 + (m_vecMoveDir * m_flMoveDistance); m_vecFinalDest = GetAbsOrigin(); +#endif SetTouch( NULL ); @@ -116,6 +126,30 @@ bool CFuncMoveLinear::ShouldSavePhysics( void ) } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: Sets the movement parent of this entity. This entity will be moved +// to a local coordinate calculated from its current absolute offset +// from the parent entity and will then follow the parent entity. +// Input : pParentEntity - This entity's new parent in the movement hierarchy. +//----------------------------------------------------------------------------- +void CFuncMoveLinear::SetParent( CBaseEntity *pParentEntity, int iAttachment ) +{ + Vector oldLocal = GetLocalOrigin(); + + BaseClass::SetParent( pParentEntity, iAttachment ); + + // SOLID_NONE indicates we haven't spawned yet + if (GetSolid() != SOLID_NONE) + { + m_vecReference = ((m_vecReference - oldLocal) + GetLocalOrigin()); + m_vecPosition1 = m_vecReference - (m_vecMoveDir * m_flMoveDistance * m_flStartPosition); + m_vecPosition2 = m_vecPosition1 + (m_vecMoveDir * m_flMoveDistance); + m_vecFinalDest = m_vecReference - m_vecFinalDest; + } +} +#endif + //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- @@ -256,6 +290,16 @@ void CFuncMoveLinear::MoveDone( void ) SetNextThink( gpGlobals->curtime + 0.1f ); BaseClass::MoveDone(); +#ifdef MAPBASE + if ( GetLocalOrigin() == m_vecPosition2 ) + { + m_OnFullyOpen.FireOutput( this, this ); + } + else if ( GetLocalOrigin() == m_vecPosition1 ) + { + m_OnFullyClosed.FireOutput( this, this ); + } +#else if ( GetAbsOrigin() == m_vecPosition2 ) { m_OnFullyOpen.FireOutput( this, this ); @@ -264,6 +308,7 @@ void CFuncMoveLinear::MoveDone( void ) { m_OnFullyClosed.FireOutput( this, this ); } +#endif } @@ -277,8 +322,9 @@ void CFuncMoveLinear::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TY if ( value > 1.0 ) value = 1.0; + Vector move = m_vecPosition1 + (value * (m_vecPosition2 - m_vecPosition1)); - + Vector delta = move - GetLocalOrigin(); float speed = delta.Length() * 10; @@ -380,6 +426,36 @@ int CFuncMoveLinear::DrawDebugTextOverlays(void) if (m_debugOverlays & OVERLAY_TEXT_BIT) { +#ifdef MAPBASE + if (GetMoveParent()) + { + Vector vecReference, vecPosition1, vecPosition2; + QAngle angReference; + + vecReference = m_vecFinalDest + GetMoveParent()->GetAbsOrigin(); + angReference = GetAbsAngles(); + vecPosition1 = vecReference + m_vecPosition1; + vecPosition2 = vecReference + m_vecPosition2; + + NDebugOverlay::Axis( vecReference, angReference, 12.0f, true, 0.15f ); + NDebugOverlay::Axis( vecPosition1, angReference, 5.0f, true, 0.15f ); + NDebugOverlay::Axis( vecPosition2, angReference, 2.5f, true, 0.15f ); + + char tempstr[512]; + float flTravelDist = (vecPosition1 - vecPosition2).Length(); + float flCurDist = (vecPosition1 - GetAbsOrigin()).Length(); + Q_snprintf(tempstr,sizeof(tempstr),"Current Pos: %3.3f",flCurDist/flTravelDist); + EntityText(text_offset,tempstr,0); + text_offset++; + + float flTargetDist = (vecPosition1 - m_vecFinalDest).Length(); + Q_snprintf(tempstr,sizeof(tempstr),"Target Pos: %3.3f",flTargetDist/flTravelDist); + EntityText(text_offset,tempstr,0); + text_offset++; + } + else + { +#else char tempstr[512]; float flTravelDist = (m_vecPosition1 - m_vecPosition2).Length(); float flCurDist = (m_vecPosition1 - GetLocalOrigin()).Length(); @@ -391,6 +467,10 @@ int CFuncMoveLinear::DrawDebugTextOverlays(void) Q_snprintf(tempstr,sizeof(tempstr),"Target Pos: %3.3f",flTargetDist/flTravelDist); EntityText(text_offset,tempstr,0); text_offset++; +#endif +#ifdef MAPBASE + } +#endif } return text_offset; } diff --git a/src/game/server/func_movelinear.h b/src/game/server/func_movelinear.h index 8f09adc2..ac58f963 100644 --- a/src/game/server/func_movelinear.h +++ b/src/game/server/func_movelinear.h @@ -27,6 +27,10 @@ public: bool CreateVPhysics( void ); bool ShouldSavePhysics( void ); +#ifdef MAPBASE + void SetParent( CBaseEntity* pNewParent, int iAttachment = -1 ); +#endif + void MoveTo(Vector vPosition, float flSpeed); void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); void MoveDone( void ); @@ -53,6 +57,11 @@ public: float m_flBlockDamage; // Damage inflicted when blocked. float m_flStartPosition; // Position of brush when spawned float m_flMoveDistance; // Total distance the brush can move +#ifdef MAPBASE + // For the parenting fix. + // Prevents position inconsistencies when changing parent. + Vector m_vecReference; +#endif IPhysicsFluidController *m_pFluidController; diff --git a/src/game/server/func_reflective_glass.cpp b/src/game/server/func_reflective_glass.cpp index d68840ef..187556b5 100644 --- a/src/game/server/func_reflective_glass.cpp +++ b/src/game/server/func_reflective_glass.cpp @@ -15,13 +15,44 @@ class CFuncReflectiveGlass : public CFuncBrush DECLARE_DATADESC(); DECLARE_CLASS( CFuncReflectiveGlass, CFuncBrush ); DECLARE_SERVERCLASS(); + + CFuncReflectiveGlass() + { +#ifdef MAPBASE + m_iszReflectRenderTarget = AllocPooledString( "_rt_WaterReflection" ); + m_iszRefractRenderTarget = AllocPooledString( "_rt_WaterRefraction" ); +#endif + } + +#ifdef MAPBASE + void InputSetReflectRenderTarget( inputdata_t &inputdata ) { m_iszReflectRenderTarget = inputdata.value.StringID(); } + void InputSetRefractRenderTarget( inputdata_t &inputdata ) { m_iszRefractRenderTarget = inputdata.value.StringID(); } + + CNetworkVar( string_t, m_iszReflectRenderTarget ); + CNetworkVar( string_t, m_iszRefractRenderTarget ); +#endif }; // automatically hooks in the system's callbacks BEGIN_DATADESC( CFuncReflectiveGlass ) + +#ifdef MAPBASE + DEFINE_KEYFIELD( m_iszReflectRenderTarget, FIELD_STRING, "ReflectRenderTarget" ), + DEFINE_KEYFIELD( m_iszRefractRenderTarget, FIELD_STRING, "RefractRenderTarget" ), + + DEFINE_INPUTFUNC( FIELD_STRING, "SetReflectRenderTarget", InputSetReflectRenderTarget ), + DEFINE_INPUTFUNC( FIELD_STRING, "SetRefractRenderTarget", InputSetRefractRenderTarget ), +#endif + END_DATADESC() LINK_ENTITY_TO_CLASS( func_reflective_glass, CFuncReflectiveGlass ); IMPLEMENT_SERVERCLASS_ST( CFuncReflectiveGlass, DT_FuncReflectiveGlass ) + +#ifdef MAPBASE + SendPropStringT( SENDINFO( m_iszReflectRenderTarget ) ), + SendPropStringT( SENDINFO( m_iszRefractRenderTarget ) ), +#endif + END_SEND_TABLE() diff --git a/src/game/server/game_ui.cpp b/src/game/server/game_ui.cpp index a514a0f8..def725ac 100644 --- a/src/game/server/game_ui.cpp +++ b/src/game/server/game_ui.cpp @@ -38,6 +38,9 @@ public: // Input handlers void InputDeactivate( inputdata_t &inputdata ); void InputActivate( inputdata_t &inputdata ); +#ifdef MAPBASE + void InputGetButtons( inputdata_t &inputdata ); +#endif void Think( void ); void Deactivate( CBaseEntity *pActivator ); @@ -54,6 +57,14 @@ public: COutputEvent m_pressedBack; COutputEvent m_pressedAttack; COutputEvent m_pressedAttack2; +#ifdef MAPBASE + COutputEvent m_pressedUse; + COutputEvent m_pressedJump; + COutputEvent m_pressedCrouch; + COutputEvent m_pressedAttack3; + COutputEvent m_pressedSprint; + COutputEvent m_pressedReload; +#endif COutputEvent m_unpressedMoveLeft; COutputEvent m_unpressedMoveRight; @@ -61,12 +72,24 @@ public: COutputEvent m_unpressedBack; COutputEvent m_unpressedAttack; COutputEvent m_unpressedAttack2; +#ifdef MAPBASE + COutputEvent m_unpressedUse; + COutputEvent m_unpressedJump; + COutputEvent m_unpressedCrouch; + COutputEvent m_unpressedAttack3; + COutputEvent m_unpressedSprint; + COutputEvent m_unpressedReload; +#endif COutputFloat m_xaxis; COutputFloat m_yaxis; COutputFloat m_attackaxis; COutputFloat m_attack2axis; +#ifdef MAPBASE + COutputInt m_OutButtons; +#endif + bool m_bForceUpdate; int m_nLastButtonState; @@ -84,6 +107,9 @@ BEGIN_DATADESC( CGameUI ) DEFINE_INPUTFUNC( FIELD_VOID, "Deactivate", InputDeactivate ), DEFINE_INPUTFUNC( FIELD_STRING, "Activate", InputActivate ), +#ifdef MAPBASE + DEFINE_INPUTFUNC( FIELD_VOID, "GetButtons", InputGetButtons ), +#endif DEFINE_OUTPUT( m_playerOn, "PlayerOn" ), DEFINE_OUTPUT( m_playerOff, "PlayerOff" ), @@ -94,6 +120,14 @@ BEGIN_DATADESC( CGameUI ) DEFINE_OUTPUT( m_pressedBack, "PressedBack" ), DEFINE_OUTPUT( m_pressedAttack, "PressedAttack" ), DEFINE_OUTPUT( m_pressedAttack2, "PressedAttack2" ), +#ifdef MAPBASE + DEFINE_OUTPUT( m_pressedUse, "PressedUse" ), + DEFINE_OUTPUT( m_pressedJump, "PressedJump" ), + DEFINE_OUTPUT( m_pressedCrouch, "PressedCrouch" ), + DEFINE_OUTPUT( m_pressedAttack3, "PressedAttack3" ), + DEFINE_OUTPUT( m_pressedSprint, "PressedSprint" ), + DEFINE_OUTPUT( m_pressedReload, "PressedReload" ), +#endif DEFINE_OUTPUT( m_unpressedMoveLeft, "UnpressedMoveLeft" ), DEFINE_OUTPUT( m_unpressedMoveRight, "UnpressedMoveRight" ), @@ -101,6 +135,16 @@ BEGIN_DATADESC( CGameUI ) DEFINE_OUTPUT( m_unpressedBack, "UnpressedBack" ), DEFINE_OUTPUT( m_unpressedAttack, "UnpressedAttack" ), DEFINE_OUTPUT( m_unpressedAttack2, "UnpressedAttack2" ), +#ifdef MAPBASE + DEFINE_OUTPUT( m_unpressedUse, "UnpressedUse" ), + DEFINE_OUTPUT( m_unpressedJump, "UnpressedJump" ), + DEFINE_OUTPUT( m_unpressedCrouch, "UnpressedCrouch" ), + DEFINE_OUTPUT( m_unpressedAttack3, "UnpressedAttack3" ), + DEFINE_OUTPUT( m_unpressedSprint, "UnpressedSprint" ), + DEFINE_OUTPUT( m_unpressedReload, "UnpressedReload" ), + + DEFINE_OUTPUT( m_OutButtons, "OutButtons" ), +#endif DEFINE_OUTPUT( m_xaxis, "XAxis" ), DEFINE_OUTPUT( m_yaxis, "YAxis" ), @@ -170,7 +214,11 @@ void CGameUI::Deactivate( CBaseEntity *pActivator ) } else { +#ifdef MAPBASE + Warning("%s Deactivate(): I have no player when called by %s!\n", GetEntityName().ToCStr(), pActivator ? pActivator->GetEntityName().ToCStr() : "(null)"); +#else Warning("%s Deactivate(): I have no player when called by %s!\n", GetEntityName().ToCStr(), pActivator ? pActivator->GetEntityName().ToCStr() : NULL); +#endif } // Stop thinking @@ -297,6 +345,13 @@ void CGameUI::Think( void ) if ((( pPlayer->m_afButtonPressed & IN_USE ) && ( m_spawnflags & SF_GAMEUI_USE_DEACTIVATES )) || (( pPlayer->m_afButtonPressed & IN_JUMP ) && ( m_spawnflags & SF_GAMEUI_JUMP_DEACTIVATES ))) { +#ifdef MAPBASE + if (pPlayer->m_afButtonPressed & IN_USE) + m_pressedUse.FireOutput( pPlayer, this, 0 ); + if (pPlayer->m_afButtonPressed & IN_JUMP) + m_pressedJump.FireOutput( pPlayer, this, 0 ); +#endif + Deactivate( pPlayer ); return; } @@ -380,6 +435,80 @@ void CGameUI::Think( void ) } } +#ifdef MAPBASE + if ( nButtonsChanged & IN_USE ) + { + if ( m_nLastButtonState & IN_USE ) + { + m_unpressedUse.FireOutput( pPlayer, this, 0 ); + } + else + { + m_pressedUse.FireOutput( pPlayer, this, 0 ); + } + } + + if ( nButtonsChanged & IN_JUMP ) + { + if ( m_nLastButtonState & IN_JUMP ) + { + m_unpressedJump.FireOutput( pPlayer, this, 0 ); + } + else + { + m_pressedJump.FireOutput( pPlayer, this, 0 ); + } + } + + if ( nButtonsChanged & IN_DUCK ) + { + if ( m_nLastButtonState & IN_DUCK ) + { + m_unpressedCrouch.FireOutput( pPlayer, this, 0 ); + } + else + { + m_pressedCrouch.FireOutput( pPlayer, this, 0 ); + } + } + + if ( nButtonsChanged & IN_ATTACK3 ) + { + if ( m_nLastButtonState & IN_ATTACK3 ) + { + m_unpressedAttack3.FireOutput( pPlayer, this, 0 ); + } + else + { + m_pressedAttack3.FireOutput( pPlayer, this, 0 ); + } + } + + if ( nButtonsChanged & IN_SPEED ) + { + if ( m_nLastButtonState & IN_SPEED ) + { + m_unpressedSprint.FireOutput( pPlayer, this, 0 ); + } + else + { + m_pressedSprint.FireOutput( pPlayer, this, 0 ); + } + } + + if ( nButtonsChanged & IN_RELOAD ) + { + if ( m_nLastButtonState & IN_RELOAD ) + { + m_unpressedReload.FireOutput( pPlayer, this, 0 ); + } + else + { + m_pressedReload.FireOutput( pPlayer, this, 0 ); + } + } +#endif + // Setup for the next frame m_nLastButtonState = pPlayer->m_nButtons; @@ -437,3 +566,13 @@ void CGameUI::Think( void ) m_bForceUpdate = false; } + +#ifdef MAPBASE +//------------------------------------------------------------------------------ +// Purpose: Gets and outputs the player's current buttons +//------------------------------------------------------------------------------ +void CGameUI::InputGetButtons( inputdata_t &inputdata ) +{ + m_OutButtons.Set(m_player ? m_player->m_nButtons : m_nLastButtonState, m_player, this); +} +#endif diff --git a/src/game/server/gameinterface.cpp b/src/game/server/gameinterface.cpp index eb99cdf4..ed656551 100644 --- a/src/game/server/gameinterface.cpp +++ b/src/game/server/gameinterface.cpp @@ -90,6 +90,12 @@ #include "serverbenchmark_base.h" #include "querycache.h" #include "player_voice_listener.h" +#ifdef MAPBASE +#include "world.h" +#endif + +#include "vscript/ivscript.h" +#include "vscript_server.h" #ifdef TF_DLL #include "gc_clientsystem.h" @@ -627,7 +633,14 @@ bool CServerGameDLL::DLLInit( CreateInterfaceFn appSystemFactory, if ( !CommandLine()->CheckParm( "-noscripting") ) { - scriptmanager = (IScriptManager *)appSystemFactory( VSCRIPT_INTERFACE_VERSION, NULL ); +#ifndef MAPBASE_VSCRIPT // Mapbase VScript uses .lib + scriptmanager = (IScriptManager*)appSystemFactory(VSCRIPT_INTERFACE_VERSION, NULL); +#endif + + if (scriptmanager == nullptr) + { + scriptmanager = (IScriptManager*)Sys_GetFactoryThis()(VSCRIPT_INTERFACE_VERSION, NULL); + } } // If not running dedicated, grab the engine vgui interface @@ -687,6 +700,7 @@ bool CServerGameDLL::DLLInit( CreateInterfaceFn appSystemFactory, g_pGameSaveRestoreBlockSet->AddBlockHandler( GetCommentarySaveRestoreBlockHandler() ); g_pGameSaveRestoreBlockSet->AddBlockHandler( GetEventQueueSaveRestoreBlockHandler() ); g_pGameSaveRestoreBlockSet->AddBlockHandler( GetAchievementSaveRestoreBlockHandler() ); + g_pGameSaveRestoreBlockSet->AddBlockHandler( GetVScriptSaveRestoreBlockHandler() ); // The string system must init first + shutdown last IGameSystem::Add( GameStringSystem() ); @@ -707,6 +721,9 @@ bool CServerGameDLL::DLLInit( CreateInterfaceFn appSystemFactory, IGameSystem::Add( SoundEmitterSystem() ); // load Mod specific game events ( MUST be before InitAllSystems() so it can pickup the mod specific events) +#ifdef MAPBASE + gameeventmanager->LoadEventsFromFile("resource/MapbaseEvents.res"); +#endif gameeventmanager->LoadEventsFromFile("resource/ModEvents.res"); #ifdef CSTRIKE_DLL // BOTPORT: TODO: move these ifdefs out @@ -759,6 +776,7 @@ void CServerGameDLL::DLLShutdown( void ) // Due to dependencies, these are not autogamesystems ModelSoundsCacheShutdown(); + g_pGameSaveRestoreBlockSet->RemoveBlockHandler( GetVScriptSaveRestoreBlockHandler() ); g_pGameSaveRestoreBlockSet->RemoveBlockHandler( GetAchievementSaveRestoreBlockHandler() ); g_pGameSaveRestoreBlockSet->RemoveBlockHandler( GetCommentarySaveRestoreBlockHandler() ); g_pGameSaveRestoreBlockSet->RemoveBlockHandler( GetEventQueueSaveRestoreBlockHandler() ); @@ -1738,9 +1756,41 @@ static TITLECOMMENT gTitleComments[] = #endif }; +#ifdef MAPBASE +extern CUtlVector *Mapbase_GetChapterMaps(); +extern CUtlVector *Mapbase_GetChapterList(); +#endif + #ifdef _XBOX void CServerGameDLL::GetTitleName( const char *pMapName, char* pTitleBuff, int titleBuffSize ) { +#ifdef MAPBASE + // Check the world entity for a chapter title + if ( CWorld *pWorld = GetWorldEntity() ) + { + const char *pWorldChapter = pWorld->GetChapterTitle(); + if ( pWorldChapter && pWorldChapter[0] != '\0' ) + { + Q_strncpy( chapterTitle, pWorldChapter, sizeof( chapterTitle ) ); + return; + } + } + + // Look in the mod's chapter list + CUtlVector *ModChapterComments = Mapbase_GetChapterMaps(); + if (ModChapterComments->Count() > 0) + { + for ( int i = 0; i < ModChapterComments->Count(); i++ ) + { + if ( !Q_strnicmp( mapname, ModChapterComments->Element(i).pBSPName, strlen(ModChapterComments->Element(i).pBSPName) ) ) + { + Q_strncpy( pTitleBuff, ModChapterComments->Element(i).pTitleName, titleBuffSize ); + return; + } + } + } +#endif + // Try to find a matching title comment for this mapname for ( int i = 0; i < ARRAYSIZE(gTitleComments); i++ ) { @@ -1750,6 +1800,7 @@ void CServerGameDLL::GetTitleName( const char *pMapName, char* pTitleBuff, int t return; } } + Q_strncpy( pTitleBuff, pMapName, titleBuffSize ); } #endif @@ -1787,6 +1838,44 @@ void CServerGameDLL::GetSaveComment( char *text, int maxlength, float flMinutes, break; } } + +#ifdef MAPBASE + // Look in the mod's chapter list + CUtlVector *ModChapterComments = Mapbase_GetChapterMaps(); + if (ModChapterComments->Count() > 0) + { + for ( int i = 0; i < ModChapterComments->Count(); i++ ) + { + if ( !Q_strnicmp( mapname, ModChapterComments->Element(i).pBSPName, strlen(ModChapterComments->Element(i).pBSPName) ) ) + { + // found one + int j; + + // Got a message, post-process it to be save name friendly + Q_strncpy( comment, ModChapterComments->Element(i).pTitleName, sizeof( comment ) ); + pName = comment; + j = 0; + // Strip out CRs + while ( j < 64 && comment[j] ) + { + if ( comment[j] == '\n' || comment[j] == '\r' ) + comment[j] = 0; + else + j++; + } + break; + } + } + } + + // Check the world entity for a chapter title + if ( CWorld *pWorld = GetWorldEntity() ) + { + const char *pWorldChapter = pWorld->GetChapterTitle(); + if ( pWorldChapter && pWorldChapter[0] != '\0' ) + pName = pWorldChapter; + } +#endif // If we didn't get one, use the designer's map name, or the BSP name itself if ( !pName ) @@ -2150,6 +2239,68 @@ void UpdateChapterRestrictions( const char *mapname ) } } +#ifdef MAPBASE + // Look in the mod's chapter list + CUtlVector *ModChapterComments = Mapbase_GetChapterMaps(); + if (ModChapterComments->Count() > 0) + { + for ( int i = 0; i < ModChapterComments->Count(); i++ ) + { + if ( !Q_strnicmp( mapname, ModChapterComments->Element(i).pBSPName, strlen(ModChapterComments->Element(i).pBSPName) ) ) + { + // found + Q_strncpy( chapterTitle, ModChapterComments->Element(i).pTitleName, sizeof( chapterTitle ) ); + int j = 0; + while ( j < 64 && chapterTitle[j] ) + { + if ( chapterTitle[j] == '\n' || chapterTitle[j] == '\r' ) + chapterTitle[j] = 0; + else + j++; + } + + // Mods can order their own custom chapter names, + // allowing for more flexible string name usage, multiple names in one chapter, etc. + CUtlVector *ModChapterList = Mapbase_GetChapterList(); + for ( int i = 0; i < ModChapterList->Count(); i++ ) + { + if ( !Q_strnicmp( chapterTitle, ModChapterList->Element(i).pChapterName, strlen(chapterTitle) ) ) + { + // ok we have the string, see if it's newer + int nNewChapter = ModChapterList->Element(i).iChapter; + int nUnlockedChapter = sv_unlockedchapters.GetInt(); + + if ( nUnlockedChapter < nNewChapter ) + { + // ok we're at a higher chapter, unlock + sv_unlockedchapters.SetValue( nNewChapter ); + + // HACK: Call up through a better function than this? 7/23/07 - jdw + if ( IsX360() ) + { + engine->ServerCommand( "host_writeconfig\n" ); + } + } + + g_nCurrentChapterIndex = nNewChapter; + return; + } + } + + break; + } + } + } + + // Check the world entity for a chapter title. + if ( CWorld *pWorld = GetWorldEntity() ) + { + const char *pWorldChapter = pWorld->GetChapterTitle(); + if ( pWorldChapter && pWorldChapter[0] != '\0' ) + Q_strncpy( chapterTitle, pWorldChapter, sizeof( chapterTitle ) ); + } +#endif + if ( !chapterTitle[0] ) return; @@ -3073,7 +3224,11 @@ float CServerGameClients::ProcessUsercmds( edict_t *player, bf_read *buf, int nu for ( i = totalcmds - 1; i >= 0; i-- ) { to = &cmds[ i ]; +#if defined( MAPBASE_VSCRIPT ) + ReadUsercmd( buf, to, from, pPlayer ); // Tell whose UserCmd it is +#else ReadUsercmd( buf, to, from ); +#endif from = to; if ( ( fabs( to->forwardmove ) > sv_max_usercmd_move_magnitude.GetFloat() ) || diff --git a/src/game/server/gameinterface.h b/src/game/server/gameinterface.h index 3cd3586c..c153bd07 100644 --- a/src/game/server/gameinterface.h +++ b/src/game/server/gameinterface.h @@ -228,5 +228,22 @@ public: }; EXPOSE_SINGLE_INTERFACE( CServerGameTags, IServerGameTags, INTERFACEVERSION_SERVERGAMETAGS ); +#ifdef MAPBASE +// +// Dynamic mod-based mod title comments +// +typedef struct +{ + char pBSPName[64]; + char pTitleName[64]; +} MODTITLECOMMENT; + +typedef struct +{ + int iChapter; + char pChapterName[64]; +} MODCHAPTER; +#endif + #endif // GAMEINTERFACE_H diff --git a/src/game/server/genericactor.cpp b/src/game/server/genericactor.cpp index 73996fea..64585da5 100644 --- a/src/game/server/genericactor.cpp +++ b/src/game/server/genericactor.cpp @@ -17,6 +17,9 @@ #include "tier1/strtools.h" #include "vstdlib/random.h" #include "engine/IEngineSound.h" +#ifdef MAPBASE +#include "ai_speech.h" +#endif // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" @@ -48,6 +51,9 @@ public: void TempGunEffect( void ); string_t m_strHullName; +#ifdef MAPBASE + Class_T m_iClassify = CLASS_NONE; +#endif DECLARE_DATADESC(); }; @@ -56,6 +62,9 @@ LINK_ENTITY_TO_CLASS( generic_actor, CGenericActor ); BEGIN_DATADESC( CGenericActor ) DEFINE_KEYFIELD(m_strHullName, FIELD_STRING, "hull_name" ), +#ifdef MAPBASE + DEFINE_INPUT(m_iClassify, FIELD_INTEGER, "SetClassify" ), +#endif END_DATADESC() @@ -66,7 +75,11 @@ END_DATADESC() //========================================================= Class_T CGenericActor::Classify ( void ) { +#ifdef MAPBASE + return m_iClassify; +#else return CLASS_NONE; +#endif } //========================================================= @@ -138,7 +151,12 @@ void CGenericActor::Spawn() m_flFieldOfView = 0.5;// indicates the width of this NPC's forward view cone ( as a dotproduct result ) m_NPCState = NPC_STATE_NONE; +#ifdef MAPBASE + CapabilitiesAdd( bits_CAP_SQUAD ); + CapabilitiesAdd( bits_CAP_MOVE_GROUND | bits_CAP_DOORS_GROUP ); +#else CapabilitiesAdd( bits_CAP_MOVE_GROUND | bits_CAP_OPEN_DOORS ); +#endif // remove head turn if no eyes or forward attachment if (LookupAttachment( "eyes" ) > 0 && LookupAttachment( "forward" ) > 0) @@ -173,6 +191,164 @@ void CGenericActor::Precache() +#ifdef MAPBASE +//========================================================= +#define TLK_ACTOR_PAIN "TLK_WOUND" +#define TLK_ACTOR_DEATH "TLK_DEATH" +#define TLK_ACTOR_ALERT "TLK_STARTCOMBAT" +#define TLK_ACTOR_IDLE "TLK_IDLE" +#define TLK_ACTOR_FEAR "TLK_FEAR" +#define TLK_ACTOR_LOSTENEMY "TLK_LOSTENEMY" +#define TLK_ACTOR_FOUNDENEMY "TLK_REFINDENEMY" +//========================================================= +// Enhanced generic actor with built-in response system usage, weapon capabilities, and more. +//========================================================= +class CGenericActorCustom : public CGenericActor +{ +private: + DECLARE_CLASS( CGenericActorCustom, CGenericActor ); +public: + //DECLARE_DATADESC(); + + CGenericActorCustom() { } + void Spawn( void ); + void Precache( void ); + + bool KeyValue( const char *szKeyName, const char *szValue ); + + void SpeakIfAllowed( const char *concept, AI_CriteriaSet *modifiers = NULL ); + void ModifyOrAppendCriteria( AI_CriteriaSet& set ); + + void PainSound( const CTakeDamageInfo &info ); + void DeathSound( const CTakeDamageInfo &info ); + void AlertSound( void ); + void IdleSound( void ); + void FearSound( void ); + void LostEnemySound( void ); + void FoundEnemySound( void ); +}; + +LINK_ENTITY_TO_CLASS( generic_actor_custom, CGenericActorCustom ); + +//BEGIN_DATADESC( CGenericActorCustom ) +//END_DATADESC() + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CGenericActorCustom::Spawn() +{ + BaseClass::Spawn(); + + CapabilitiesAdd( bits_CAP_USE_WEAPONS ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CGenericActorCustom::Precache() +{ + BaseClass::Precache(); +} + +//----------------------------------------------------------------------------- +// Purpose: Cache user entity field values until spawn is called. +// Input : szKeyName - Key to handle. +// szValue - Value for key. +// Output : Returns true if the key was handled, false if not. +//----------------------------------------------------------------------------- +bool CGenericActorCustom::KeyValue( const char *szKeyName, const char *szValue ) +{ + if (FStrEq(szKeyName, "UseShotRegulator")) + { + if (atoi(szValue) > 0) + CapabilitiesAdd( bits_CAP_USE_SHOT_REGULATOR ); + else + CapabilitiesRemove( bits_CAP_USE_SHOT_REGULATOR ); + + return true; + } + + return BaseClass::KeyValue( szKeyName, szValue ); +} + +//----------------------------------------------------------------------------- +// Purpose: Speak concept +//----------------------------------------------------------------------------- +void CGenericActorCustom::SpeakIfAllowed( const char *concept, AI_CriteriaSet *modifiers ) +{ + AI_CriteriaSet empty; + Speak( concept, modifiers ? *modifiers : empty ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CGenericActorCustom::ModifyOrAppendCriteria( AI_CriteriaSet& set ) +{ + BaseClass::ModifyOrAppendCriteria( set ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CGenericActorCustom::PainSound( const CTakeDamageInfo &info ) +{ + AI_CriteriaSet modifiers; + ModifyOrAppendDamageCriteria( modifiers, info ); + SpeakIfAllowed( TLK_ACTOR_PAIN, &modifiers ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CGenericActorCustom::DeathSound( const CTakeDamageInfo &info ) +{ + AI_CriteriaSet modifiers; + ModifyOrAppendDamageCriteria( modifiers, info ); + SpeakIfAllowed( TLK_ACTOR_DEATH, &modifiers ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CGenericActorCustom::AlertSound( void ) +{ + SpeakIfAllowed( TLK_ACTOR_ALERT ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CGenericActorCustom::IdleSound( void ) +{ + SpeakIfAllowed( TLK_ACTOR_IDLE ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CGenericActorCustom::FearSound( void ) +{ + SpeakIfAllowed( TLK_ACTOR_FEAR ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CGenericActorCustom::LostEnemySound( void ) +{ + SpeakIfAllowed( TLK_ACTOR_LOSTENEMY ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CGenericActorCustom::FoundEnemySound( void ) +{ + SpeakIfAllowed( TLK_ACTOR_FOUNDENEMY ); +} +#endif diff --git a/src/game/server/genericmonster.cpp b/src/game/server/genericmonster.cpp index ed1be819..71fd197f 100644 --- a/src/game/server/genericmonster.cpp +++ b/src/game/server/genericmonster.cpp @@ -17,6 +17,12 @@ #include "physics_bone_follower.h" #include "ai_baseactor.h" #include "ai_senses.h" +#ifdef MAPBASE +/* +#include "ai_basenpc_flyer.h" +#include "player_pickup.h" +*/ +#endif // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" @@ -470,3 +476,97 @@ void CNPC_Furniture::DrawDebugGeometryOverlays( void ) BaseClass::DrawDebugGeometryOverlays(); } + +#ifdef MAPBASE +/* +//========================================================= +// Generic flying monster +//========================================================= + +class CGenericFlyingMonster : public CAI_BaseFlyingBot +{ +public: + DECLARE_CLASS( CGenericFlyingMonster, CAI_BaseFlyingBot ); + + CGenericFlyingMonster(); + + void Spawn( void ); + void Precache( void ); + int GetSoundInterests ( void ); +}; + +LINK_ENTITY_TO_CLASS( monster_flying_generic, CGenericFlyingMonster ); + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CGenericFlyingMonster::CGenericFlyingMonster() +{ +} + +//========================================================= +// GetSoundInterests - generic NPC can't hear. +//========================================================= +int CGenericFlyingMonster::GetSoundInterests ( void ) +{ + return NULL; +} + +//========================================================= +// Spawn +//========================================================= +void CGenericFlyingMonster::Spawn() +{ + Precache(); + + SetModel( STRING( GetModelName() ) ); + + if ( FStrEq( STRING( GetModelName() ), "models/player.mdl" ) || FStrEq( STRING( GetModelName() ), "models/holo.mdl" ) ) + UTIL_SetSize(this, VEC_HULL_MIN, VEC_HULL_MAX); + else + UTIL_SetSize(this, NAI_Hull::Mins(HULL_HUMAN), NAI_Hull::Maxs(HULL_HUMAN)); + + SetSolid( SOLID_BBOX ); + AddSolidFlags( FSOLID_NOT_STANDABLE ); + SetMoveType( MOVETYPE_FLY ); + m_bloodColor = BLOOD_COLOR_RED; + m_flFieldOfView = 0.5;// indicates the width of this NPC's forward view cone ( as a dotproduct result ) + m_NPCState = NPC_STATE_NONE; + + CapabilitiesAdd( bits_CAP_MOVE_FLY ); + + SetNavType( NAV_FLY ); + + AddFlag( FL_FLY ); + + NPCInit(); + if ( !HasSpawnFlags(SF_GENERICNPC_NOTSOLID) ) + { + trace_t tr; + UTIL_TraceEntity( this, GetAbsOrigin(), GetAbsOrigin(), MASK_SOLID, &tr ); + if ( tr.startsolid ) + { + Msg("Placed npc_generic in solid!!! (%s)\n", STRING(GetModelName()) ); + m_spawnflags |= SF_GENERICNPC_NOTSOLID; + } + } + + if ( HasSpawnFlags(SF_GENERICNPC_NOTSOLID) ) + { + AddSolidFlags( FSOLID_NOT_SOLID ); + m_takedamage = DAMAGE_NO; + VPhysicsDestroyObject(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: precaches all resources this NPC needs +//----------------------------------------------------------------------------- +void CGenericFlyingMonster::Precache() +{ + BaseClass::Precache(); + + PrecacheModel( STRING( GetModelName() ) ); +} +*/ +#endif diff --git a/src/game/server/globalstate.cpp b/src/game/server/globalstate.cpp index d8da390c..49a984ab 100644 --- a/src/game/server/globalstate.cpp +++ b/src/game/server/globalstate.cpp @@ -144,6 +144,28 @@ public: return m_list.Count(); } +#ifdef MAPBASE_VSCRIPT + virtual void RegisterVScript() + { + g_pScriptVM->RegisterInstance( this, "Globals" ); + } + + int ScriptAddEntity( const char *pGlobalname, const char *pMapName, int state ) + { + return AddEntity( pGlobalname, pMapName, (GLOBALESTATE)state ); + } + + void ScriptSetState( int globalIndex, int state ) + { + SetState( globalIndex, (GLOBALESTATE)state ); + } + + int ScriptGetState( int globalIndex ) + { + return (int)GetState( globalIndex ); + } +#endif + void Reset( void ); int Save( ISave &save ); int Restore( IRestore &restore ); @@ -324,3 +346,15 @@ CON_COMMAND(server_game_time, "Gives the game time in seconds (server's curtime) ShowServerGameTime(); } + +#ifdef MAPBASE_VSCRIPT +BEGIN_SCRIPTDESC_ROOT( CGlobalState, SCRIPT_SINGLETON "Global state system." ) + DEFINE_SCRIPTFUNC( GetIndex, "Gets the index of the specified global name. Returns -1 if it does not exist." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptAddEntity, "AddGlobal", "Adds a new global with a specific map name and state. Returns its index." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptGetState, "GetState", "Gets the state of the specified global." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptSetState, "SetState", "Sets the state of the specified global." ) + DEFINE_SCRIPTFUNC( GetCounter, "Gets the counter of the specified global." ) + DEFINE_SCRIPTFUNC( SetCounter, "Sets the counter of the specified global." ) + DEFINE_SCRIPTFUNC( AddToCounter, "Adds to the counter of the specified global." ) +END_SCRIPTDESC(); +#endif diff --git a/src/game/server/hl2/ai_behavior_actbusy.cpp b/src/game/server/hl2/ai_behavior_actbusy.cpp index ff56bc54..ac8ae526 100644 --- a/src/game/server/hl2/ai_behavior_actbusy.cpp +++ b/src/game/server/hl2/ai_behavior_actbusy.cpp @@ -18,6 +18,9 @@ #include "SoundEmitterSystem/isoundemittersystembase.h" #include "entityblocker.h" #include "npcevent.h" +#ifdef MAPBASE +#include "interval.h" +#endif // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" @@ -58,6 +61,9 @@ BEGIN_DATADESC( CAI_ActBusyBehavior ) DEFINE_FIELD( m_bInQueue, FIELD_BOOLEAN ), DEFINE_FIELD( m_iCurrentBusyAnim, FIELD_INTEGER ), DEFINE_FIELD( m_hActBusyGoal, FIELD_EHANDLE ), +#ifdef MAPBASE + DEFINE_FIELD( m_hNextActBusyGoal, FIELD_EHANDLE ), +#endif DEFINE_FIELD( m_bNeedToSetBounds, FIELD_BOOLEAN ), DEFINE_FIELD( m_hSeeEntity, FIELD_EHANDLE ), DEFINE_FIELD( m_fTimeLastSawSeeEntity, FIELD_TIME ), @@ -65,6 +71,9 @@ BEGIN_DATADESC( CAI_ActBusyBehavior ) DEFINE_FIELD( m_bExitedBusyToDueSeeEnemy, FIELD_BOOLEAN ), DEFINE_FIELD( m_iNumConsecutivePathFailures, FIELD_INTEGER ), DEFINE_FIELD( m_bAutoFireWeapon, FIELD_BOOLEAN ), +#ifdef MAPBASE + DEFINE_FIELD( m_flNextAutoFireTime, FIELD_TIME ), +#endif DEFINE_FIELD( m_flDeferUntil, FIELD_TIME ), DEFINE_FIELD( m_iNumEnemiesInSafeZone, FIELD_INTEGER ), END_DATADESC(); @@ -91,7 +100,11 @@ public: virtual void LevelShutdownPostEntity( void ); // Read in the data from the anim data file +#ifdef MAPBASE + void ParseAnimDataFile( const char *file = "scripts/actbusy.txt" ); +#else void ParseAnimDataFile( void ); +#endif // Parse a keyvalues section into an act busy anim bool ParseActBusyFromKV( busyanim_t *pAnim, KeyValues *pSection ); @@ -107,6 +120,13 @@ protected: CActBusyAnimData g_ActBusyAnimDataSystem; +#ifdef MAPBASE +void ParseCustomActbusyFile( const char *file ) +{ + g_ActBusyAnimDataSystem.ParseAnimDataFile(file); +} +#endif + //----------------------------------------------------------------------------- // Inherited from IAutoServerSystem //----------------------------------------------------------------------------- @@ -126,10 +146,18 @@ void CActBusyAnimData::LevelShutdownPostEntity( void ) //----------------------------------------------------------------------------- // Clear out the stats + their history //----------------------------------------------------------------------------- +#ifdef MAPBASE +void CActBusyAnimData::ParseAnimDataFile( const char *file ) +#else void CActBusyAnimData::ParseAnimDataFile( void ) +#endif { KeyValues *pKVAnimData = new KeyValues( "ActBusyAnimDatafile" ); +#ifdef MAPBASE + if ( pKVAnimData->LoadFromFile( filesystem, file ) ) +#else if ( pKVAnimData->LoadFromFile( filesystem, "scripts/actbusy.txt" ) ) +#endif { // Now try and parse out each act busy anim KeyValues *pKVAnim = pKVAnimData->GetFirstSubKey(); @@ -207,6 +235,10 @@ bool CActBusyAnimData::ParseActBusyFromKV( busyanim_t *pAnim, KeyValues *pSectio pAnim->iBusyInterruptType = BA_INT_NONE; } +#ifdef MAPBASE + pAnim->bTranslateActivity = pSection->GetBool("translateactivity", false); +#endif + return true; } @@ -271,7 +303,11 @@ void CAI_ActBusyBehavior::Enable( CAI_ActBusyGoal *pGoal, float flRange, bool bV m_bMovingToBusy = false; m_bNeedsToPlayExitAnim = false; m_bLeaving = false; +#ifdef MAPBASE + m_flNextBusySearchTime = gpGlobals->curtime + (m_hActBusyGoal.Get() ? m_hActBusyGoal->NextBusySearchInterval().start : ai_actbusy_search_time.GetFloat()); +#else m_flNextBusySearchTime = gpGlobals->curtime + ai_actbusy_search_time.GetFloat(); +#endif m_flEndBusyAt = 0; m_bVisibleOnly = bVisibleOnly; m_bInQueue = dynamic_cast(m_hActBusyGoal.Get()) != NULL; @@ -354,6 +390,16 @@ void CAI_ActBusyBehavior::ForceActBusy( CAI_ActBusyGoal *pGoal, CAI_Hint *pHintN { Assert( !m_bLeaving ); +#ifdef MAPBASE + if ( m_bNeedsToPlayExitAnim && !bTeleportToBusy ) + { + if ( HasAnimForActBusy( m_iCurrentBusyAnim, BA_EXIT ) ) + { + m_hNextActBusyGoal = pGoal; + //m_bNextActBusyVisOnly = bVisibleOnly; + } + } +#else if ( m_bNeedsToPlayExitAnim ) { // If we hit this, the mapmaker's told this NPC to actbusy somewhere while it's still in an actbusy. @@ -364,6 +410,7 @@ void CAI_ActBusyBehavior::ForceActBusy( CAI_ActBusyGoal *pGoal, CAI_Hint *pHintN return; } } +#endif if ( ai_debug_actbusy.GetInt() == 4 ) { @@ -379,7 +426,18 @@ void CAI_ActBusyBehavior::ForceActBusy( CAI_ActBusyGoal *pGoal, CAI_Hint *pHintN Msg("\n"); } +#ifdef MAPBASE + if (!m_hNextActBusyGoal) + { + Enable( pGoal, m_flBusySearchRange, bVisibleOnly ); + } + else + { + Enable( NULL, m_flBusySearchRange, bVisibleOnly ); + } +#else Enable( pGoal, m_flBusySearchRange, bVisibleOnly ); +#endif m_bForceActBusy = true; m_flForcedMaxTime = flMaxTime; m_bTeleportToBusy = bTeleportToBusy; @@ -703,7 +761,12 @@ bool CAI_ActBusyBehavior::ShouldIgnoreSound( CSound *pSound ) //----------------------------------------------------------------------------- void CAI_ActBusyBehavior::OnFriendDamaged( CBaseCombatCharacter *pSquadmate, CBaseEntity *pAttacker ) { +#ifdef MAPBASE + // Now that this has been extended beyond Alyx, it doesn't just need to be the player anymore + if( IsCombatActBusy() && IsInSafeZone( pAttacker ) ) +#else if( IsCombatActBusy() && pSquadmate->IsPlayer() && IsInSafeZone( pAttacker ) ) +#endif { SetCondition( COND_ACTBUSY_AWARE_OF_ENEMY_IN_SAFE_ZONE ); // Break the actbusy, if we're running it. m_flDeferUntil = gpGlobals->curtime + 4.0f; // Stop actbusying and go deal with that enemy!! @@ -804,7 +867,12 @@ void CAI_ActBusyBehavior::GatherConditions( void ) SetCondition( COND_ACTBUSY_LOST_SEE_ENTITY ); m_hActBusyGoal->NPCLostSeeEntity( GetOuter() ); +#ifdef MAPBASE + // Now that this has been extended beyond Alyx, this could just apply to player allies in general + if( IsCombatActBusy() && (m_hSeeEntity->IsPlayer() && GetOuter()->IsPlayerAlly()) ) +#else if( IsCombatActBusy() && (GetOuter()->Classify() == CLASS_PLAYER_ALLY_VITAL && m_hSeeEntity->IsPlayer()) ) +#endif { // Defer any actbusying for several seconds. This serves as a heuristic for waiting // for the player to settle after moving out of the room. This helps Alyx pick a more @@ -892,13 +960,21 @@ void CAI_ActBusyBehavior::GatherConditions( void ) } } +#ifdef MAPBASE + if( m_bAutoFireWeapon && m_flNextAutoFireTime <= gpGlobals->curtime && random->RandomInt(0, 4) <= 3 ) +#else if( m_bAutoFireWeapon && random->RandomInt(0, 5) <= 3 ) +#endif { CBaseCombatWeapon *pWeapon = GetOuter()->GetActiveWeapon(); if( pWeapon ) { pWeapon->Operator_ForceNPCFire( GetOuter(), false ); +#ifdef MAPBASE + pWeapon->DoMuzzleFlash(); + m_flNextAutoFireTime = gpGlobals->curtime + pWeapon->GetFireRate(); +#endif } } @@ -1158,7 +1234,11 @@ int CAI_ActBusyBehavior::SelectScheduleWhileNotBusy( int iBase ) } else { +#ifdef MAPBASE + m_flNextBusySearchTime = gpGlobals->curtime + (m_hActBusyGoal ? RandomInterval(m_hActBusyGoal->NextBusySearchInterval()) : RandomFloat(ai_actbusy_search_time.GetFloat(), ai_actbusy_search_time.GetFloat()*2)); +#else m_flNextBusySearchTime = gpGlobals->curtime + RandomFloat(ai_actbusy_search_time.GetFloat(), ai_actbusy_search_time.GetFloat()*2); +#endif } // We may already have a node @@ -1349,6 +1429,23 @@ int CAI_ActBusyBehavior::SelectSchedule() if ( m_bLeaving ) return SelectScheduleForLeaving(); +#ifdef MAPBASE + if (m_hNextActBusyGoal) + { + if (m_bBusy) + { + m_flEndBusyAt = gpGlobals->curtime; + } + else + { + // Next busy + // (the parameters should've been safely stored when we transferred) + Enable(m_hNextActBusyGoal, m_flBusySearchRange, m_bVisibleOnly); + m_hNextActBusyGoal = NULL; + } + } +#endif + // NPCs should not be busy if the actbusy behaviour has been disabled, or if they've received player squad commands bool bShouldNotBeBusy = (!m_bEnabled || HasCondition( COND_PLAYER_ADDED_TO_SQUAD ) || HasCondition( COND_RECEIVED_ORDERS ) ); if ( bShouldNotBeBusy ) @@ -1615,7 +1712,20 @@ bool CAI_ActBusyBehavior::HasAnimForActBusy( int iActBusy, busyanimparts_t AnimP // Try and play the activity second if ( pBusyAnim->iActivities[AnimPart] != ACT_INVALID ) +#ifdef MAPBASE + { + if (pBusyAnim->bTranslateActivity == true) + { + return GetOuter()->HaveSequenceForActivity( GetOuter()->TranslateActivity(pBusyAnim->iActivities[AnimPart]) ); + } + else + { + return GetOuter()->HaveSequenceForActivity( pBusyAnim->iActivities[AnimPart] ); + } + } +#else return GetOuter()->HaveSequenceForActivity( pBusyAnim->iActivities[AnimPart] ); +#endif return false; } @@ -1645,7 +1755,11 @@ void CAI_ActBusyBehavior::PlaySoundForActBusy( busyanimparts_t AnimPart ) CAI_Expresser *pExpresser = GetOuter()->GetExpresser(); if ( pExpresser ) { +#ifdef NEW_RESPONSE_SYSTEM + CAI_Concept concept = STRING(pBusyAnim->iszSounds[AnimPart]); +#else const char *concept = STRING(pBusyAnim->iszSounds[AnimPart]); +#endif // Must be able to speak the concept if ( !pExpresser->IsSpeaking() && pExpresser->CanSpeakConcept( concept ) ) @@ -1677,7 +1791,11 @@ bool CAI_ActBusyBehavior::PlayAnimForActBusy( busyanimparts_t AnimPart ) // Try and play the activity second if ( pBusyAnim->iActivities[AnimPart] != ACT_INVALID ) { +#ifdef MAPBASE + GetOuter()->SetIdealActivity( pBusyAnim->bTranslateActivity ? GetOuter()->TranslateActivity(pBusyAnim->iActivities[AnimPart]) : pBusyAnim->iActivities[AnimPart] ); +#else GetOuter()->SetIdealActivity( pBusyAnim->iActivities[AnimPart] ); +#endif return true; } @@ -2196,7 +2314,11 @@ void CAI_ActBusyBehavior::NotifyBusyEnding( void ) } else { +#ifdef MAPBASE + m_flNextBusySearchTime = gpGlobals->curtime + (m_hActBusyGoal ? RandomInterval(m_hActBusyGoal->NextBusySearchInterval()) : RandomFloat(ai_actbusy_search_time.GetFloat(), ai_actbusy_search_time.GetFloat()*2)); +#else m_flNextBusySearchTime = gpGlobals->curtime + (RandomFloat(ai_actbusy_search_time.GetFloat(), ai_actbusy_search_time.GetFloat()*2)); +#endif } } @@ -2306,6 +2428,12 @@ BEGIN_DATADESC( CAI_ActBusyGoal ) DEFINE_KEYFIELD( m_bVisibleOnly, FIELD_BOOLEAN, "visibleonly" ), DEFINE_KEYFIELD( m_iType, FIELD_INTEGER, "type" ), DEFINE_KEYFIELD( m_bAllowCombatActBusyTeleport, FIELD_BOOLEAN, "allowteleport" ), +#ifdef MAPBASE + // interval_t's can be saved. No instance of its use exists in vanilla Source 2013, + // so it's either an unused field type or something used in the engine. It appears to be functional either way. + // I added built-in keyvalue support as an experiment, and this keyvalue is part of said experiment. + DEFINE_KEYFIELD( m_NextBusySearch, FIELD_INTERVAL, "NextBusy" ), +#endif DEFINE_KEYFIELD( m_iszSeeEntityName, FIELD_STRING, "SeeEntity" ), DEFINE_KEYFIELD( m_flSeeEntityTimeout, FIELD_FLOAT, "SeeEntityTimeout" ), DEFINE_KEYFIELD( m_iszSafeZoneVolume, FIELD_STRING, "SafeZone" ), @@ -2316,15 +2444,33 @@ BEGIN_DATADESC( CAI_ActBusyGoal ) DEFINE_INPUTFUNC( FIELD_STRING, "ForceNPCToActBusy", InputForceNPCToActBusy ), DEFINE_INPUTFUNC( FIELD_EHANDLE, "ForceThisNPCToActBusy", InputForceThisNPCToActBusy ), DEFINE_INPUTFUNC( FIELD_EHANDLE, "ForceThisNPCToLeave", InputForceThisNPCToLeave ), +#ifdef MAPBASE + DEFINE_INPUTFUNC( FIELD_EHANDLE, "ForceThisNPCToStopBusy", InputForceThisNPCToStopBusy ), +#endif // Outputs DEFINE_OUTPUT( m_OnNPCStartedBusy, "OnNPCStartedBusy" ), DEFINE_OUTPUT( m_OnNPCFinishedBusy, "OnNPCFinishedBusy" ), +#ifdef MAPBASE + DEFINE_OUTPUT( m_OnNPCStartedLeavingBusy, "OnNPCStartedLeavingBusy" ), + DEFINE_OUTPUT( m_OnNPCMovingToBusy, "OnNPCMovingToBusy" ), + DEFINE_OUTPUT( m_OnNPCAbortedMoveTo, "OnNPCAbortedMoveTo" ), +#endif DEFINE_OUTPUT( m_OnNPCLeft, "OnNPCLeft" ), DEFINE_OUTPUT( m_OnNPCLostSeeEntity, "OnNPCLostSeeEntity" ), DEFINE_OUTPUT( m_OnNPCSeeEnemy, "OnNPCSeeEnemy" ), END_DATADESC() +#ifdef MAPBASE_VSCRIPT +BEGIN_ENT_SCRIPTDESC( CAI_ActBusyGoal, CAI_GoalEntity, "A goal entity which makes NPCs act busy." ) + + DEFINE_SCRIPTFUNC_NAMED( ScriptForceBusy, "ForceBusy", "Force a NPC to act busy." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptForceBusyComplex, "ForceBusyComplex", "Force a NPC to act busy with additional parameters." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptStopBusy, "StopBusy", "Force a NPC to stop busying." ) + +END_SCRIPTDESC(); +#endif + //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- @@ -2586,12 +2732,37 @@ void CAI_ActBusyGoal::InputForceThisNPCToLeave( inputdata_t &inputdata ) pBehavior->ForceActBusyLeave(); } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: Forces a specific NPC to stop acting busy +//----------------------------------------------------------------------------- +void CAI_ActBusyGoal::InputForceThisNPCToStopBusy( inputdata_t &inputdata ) +{ + CAI_ActBusyBehavior *pBehavior = GetBusyBehaviorForNPC( inputdata.value.Entity(), "InputForceThisNPCToStopBusy" ); + if ( !pBehavior ) + return; + + if (!IsActive() && pBehavior->GetActBusyGoal() == this) + { + pBehavior->Disable(); + } + else + { + // Just stop busying + pBehavior->StopBusying(); + } +} +#endif + //----------------------------------------------------------------------------- // Purpose: // Input : *pNPC - //----------------------------------------------------------------------------- void CAI_ActBusyGoal::NPCMovingToBusy( CAI_BaseNPC *pNPC ) { +#ifdef MAPBASE + m_OnNPCMovingToBusy.Set( pNPC, pNPC, this ); +#endif } //----------------------------------------------------------------------------- @@ -2608,6 +2779,9 @@ void CAI_ActBusyGoal::NPCStartedBusy( CAI_BaseNPC *pNPC ) //----------------------------------------------------------------------------- void CAI_ActBusyGoal::NPCStartedLeavingBusy( CAI_BaseNPC *pNPC ) { +#ifdef MAPBASE + m_OnNPCStartedLeavingBusy.Set( pNPC, pNPC, this ); +#endif } //----------------------------------------------------------------------------- @@ -2616,6 +2790,9 @@ void CAI_ActBusyGoal::NPCStartedLeavingBusy( CAI_BaseNPC *pNPC ) //----------------------------------------------------------------------------- void CAI_ActBusyGoal::NPCAbortedMoveTo( CAI_BaseNPC *pNPC ) { +#ifdef MAPBASE + m_OnNPCAbortedMoveTo.Set( pNPC, pNPC, this ); +#endif } //----------------------------------------------------------------------------- @@ -2650,6 +2827,64 @@ void CAI_ActBusyGoal::NPCSeeEnemy( CAI_BaseNPC *pNPC ) m_OnNPCSeeEnemy.Set( pNPC, pNPC, this ); } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +interval_t &CAI_ActBusyGoal::NextBusySearchInterval() +{ + if (m_NextBusySearch.start == 0) + { + // Return an interval_t version of the convar + static interval_t defaultInterval; + defaultInterval.start = ai_actbusy_search_time.GetFloat(); + defaultInterval.range = ai_actbusy_search_time.GetFloat(); // Range is end - start, so we don't have to multiply it here + return defaultInterval; + } + + return m_NextBusySearch; +} +#endif + +#ifdef MAPBASE_VSCRIPT +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CAI_ActBusyGoal::ScriptForceBusy( HSCRIPT hNPC, HSCRIPT hHint, bool bTeleportOnly ) +{ + CAI_ActBusyBehavior *pBehavior = GetBusyBehaviorForNPC( ToEnt( hNPC ), "ForceBusy (vscript)" ); + if ( !pBehavior ) + return; + + // Tell the NPC to immediately act busy + pBehavior->SetBusySearchRange( m_flBusySearchRange ); + pBehavior->ForceActBusy( this, dynamic_cast(ToEnt( hHint )), NO_MAX_TIME, false, bTeleportOnly ); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CAI_ActBusyGoal::ScriptForceBusyComplex( HSCRIPT hNPC, HSCRIPT hHint, bool bTeleportOnly, bool bVisibleOnly, bool bUseNearestBusy, float flMaxTime, int activity, HSCRIPT pSeeEntity ) +{ + CAI_ActBusyBehavior *pBehavior = GetBusyBehaviorForNPC( ToEnt( hNPC ), "ForceBusyComplex (vscript)" ); + if ( !pBehavior ) + return; + + // Tell the NPC to immediately act busy + pBehavior->SetBusySearchRange( m_flBusySearchRange ); + pBehavior->ForceActBusy( this, dynamic_cast(ToEnt( hHint )), flMaxTime, bVisibleOnly, bTeleportOnly, bUseNearestBusy, ToEnt( pSeeEntity ), (Activity)activity ); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CAI_ActBusyGoal::ScriptStopBusy( HSCRIPT hNPC ) +{ + CAI_ActBusyBehavior *pBehavior = GetBusyBehaviorForNPC( ToEnt( hNPC ), "StopBusy (vscript)" ); + if ( !pBehavior ) + return; + + // Just stop busying + pBehavior->StopBusying(); +} +#endif + //========================================================================================================== // ACT BUSY QUEUE //========================================================================================================== diff --git a/src/game/server/hl2/ai_behavior_actbusy.h b/src/game/server/hl2/ai_behavior_actbusy.h index 750b600f..264fdb3d 100644 --- a/src/game/server/hl2/ai_behavior_actbusy.h +++ b/src/game/server/hl2/ai_behavior_actbusy.h @@ -50,6 +50,9 @@ struct busyanim_t float flMaxTime; // Max time spent in this busy animation. 0 means continue until interrupted. busyinterrupt_t iBusyInterruptType; bool bUseAutomovement; +#ifdef MAPBASE + bool bTranslateActivity; +#endif }; struct busysafezone_t @@ -150,6 +153,10 @@ public: bool IsInSafeZone( CBaseEntity *pEntity ); int CountEnemiesInSafeZone(); +#ifdef MAPBASE + CAI_ActBusyGoal *GetActBusyGoal() const { return m_hActBusyGoal; } +#endif + private: virtual int SelectSchedule( void ); int SelectScheduleForLeaving( void ); @@ -181,6 +188,10 @@ private: bool m_bInQueue; int m_iCurrentBusyAnim; CHandle m_hActBusyGoal; +#ifdef MAPBASE + // So exit animations can play + CHandle m_hNextActBusyGoal; +#endif bool m_bNeedToSetBounds; EHANDLE m_hSeeEntity; float m_fTimeLastSawSeeEntity; @@ -189,6 +200,9 @@ private: int m_iNumConsecutivePathFailures; // Count how many times we failed to find a path to a node, so we can consider teleporting. bool m_bAutoFireWeapon; +#ifdef MAPBASE + float m_flNextAutoFireTime; +#endif float m_flDeferUntil; int m_iNumEnemiesInSafeZone; @@ -225,6 +239,16 @@ public: int GetType() { return m_iType; } bool IsCombatActBusyTeleportAllowed() { return m_bAllowCombatActBusyTeleport; } +#ifdef MAPBASE + interval_t &NextBusySearchInterval(); +#endif + +#ifdef MAPBASE_VSCRIPT + void ScriptForceBusy( HSCRIPT hNPC, HSCRIPT hHint, bool bTeleportOnly ); + void ScriptForceBusyComplex( HSCRIPT hNPC, HSCRIPT hHint, bool bTeleportOnly, bool bVisibleOnly, bool bUseNearestBusy, float flMaxTime, int activity, HSCRIPT pSeeEntity ); + void ScriptStopBusy( HSCRIPT hNPC ); +#endif + protected: CAI_ActBusyBehavior *GetBusyBehaviorForNPC( const char *pszActorName, CBaseEntity *pActivator, CBaseEntity *pCaller, const char *sInputName ); CAI_ActBusyBehavior *GetBusyBehaviorForNPC( CBaseEntity *pEntity, const char *sInputName ); @@ -238,14 +262,23 @@ protected: void InputForceNPCToActBusy( inputdata_t &inputdata ); void InputForceThisNPCToActBusy( inputdata_t &inputdata ); void InputForceThisNPCToLeave( inputdata_t &inputdata ); +#ifdef MAPBASE + void InputForceThisNPCToStopBusy( inputdata_t &inputdata ); +#endif DECLARE_DATADESC(); +#ifdef MAPBASE_VSCRIPT + DECLARE_ENT_SCRIPTDESC(); +#endif protected: float m_flBusySearchRange; bool m_bVisibleOnly; int m_iType; bool m_bAllowCombatActBusyTeleport; +#ifdef MAPBASE + interval_t m_NextBusySearch; +#endif public: // Let the actbusy behavior query these so we don't have to duplicate the data. @@ -257,6 +290,11 @@ public: protected: COutputEHANDLE m_OnNPCStartedBusy; COutputEHANDLE m_OnNPCFinishedBusy; +#ifdef MAPBASE + COutputEHANDLE m_OnNPCStartedLeavingBusy; + COutputEHANDLE m_OnNPCMovingToBusy; + COutputEHANDLE m_OnNPCAbortedMoveTo; +#endif COutputEHANDLE m_OnNPCLeft; COutputEHANDLE m_OnNPCLostSeeEntity; COutputEHANDLE m_OnNPCSeeEnemy; diff --git a/src/game/server/hl2/ai_behavior_functank.cpp b/src/game/server/hl2/ai_behavior_functank.cpp index 0c1ae3bf..105bf6e2 100644 --- a/src/game/server/hl2/ai_behavior_functank.cpp +++ b/src/game/server/hl2/ai_behavior_functank.cpp @@ -50,6 +50,12 @@ bool CAI_FuncTankBehavior::CanSelectSchedule() if ( !m_hFuncTank ) return false; +#ifdef MAPBASE + // We're glued to our func_tank, don't get off of it + if ( m_hFuncTank->m_bControllerGlued ) + return true; +#endif + // Are you alive, in a script? if ( !GetOuter()->IsInterruptable() ) return false; @@ -92,6 +98,42 @@ void CAI_FuncTankBehavior::PrescheduleThink() } } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CAI_FuncTankBehavior::IsInterruptable( void ) +{ + if ( m_hFuncTank && m_hFuncTank->m_bControllerGlued ) + return false; + + return BaseClass::IsInterruptable(); +} + +ConVar ai_tank_allow_expanded_npcs( "ai_tank_allow_expanded_npcs", "1", FCVAR_NONE, "Allows Father Grigori, Barney, and vortigaunts to automatically man func_tanks." ); + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CAI_FuncTankBehavior::CanManTank( CFuncTank *pTank, bool bForced ) +{ + if (!bForced) + { + // In order to prevent potential problems in existing maps, Father Grigori, Barney, and vortigaunts can be set to not automatically man func_tanks by default. + if (ai_tank_allow_expanded_npcs.GetBool() == false) + { + const char *pszClass = GetOuter()->GetClassname(); + if ( FStrEq( pszClass, "npc_monk" ) || FStrEq( pszClass, "npc_barney" ) || FStrEq( pszClass, "npc_vortigaunt" ) ) + return false; + } + } + + return true; +} +#endif + //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- @@ -114,6 +156,15 @@ int CAI_FuncTankBehavior::SelectSchedule() // If we are not mounted to a func_tank look for one. if ( !IsMounted() ) { +#ifdef MAPBASE + // Forced mounts use a special schedule. + // If our outer is parented, automatically grab the tank if we're in its control volume. + if (HasCondition(COND_FUNCTANK_FORCED) || (GetOuter()->GetParent() && m_hFuncTank->m_hControlVolume)) + { + return SCHED_FORCE_MOUNT_FUNCTANK; + } +#endif + return SCHED_MOVE_TO_FUNCTANK; } @@ -139,6 +190,24 @@ int CAI_FuncTankBehavior::SelectSchedule() return SCHED_IDLE_STAND; } +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CAI_FuncTankBehavior::ModifyOrAppendCriteria( AI_CriteriaSet &set ) +{ + BaseClass::ModifyOrAppendCriteria( set ); + +#ifdef MAPBASE + set.AppendCriteria( "ft_mounted", m_bMounted ? "1" : "0" ); + + if (m_hFuncTank) + { + set.AppendCriteria( "ft_classname", m_hFuncTank->GetClassname() ); + m_hFuncTank->AppendContextToCriteria( set, "ft_" ); + } +#endif +} + //----------------------------------------------------------------------------- // Purpose: // Input : activity - @@ -180,6 +249,10 @@ void CAI_FuncTankBehavior::Dismount( void ) // Set this condition to force breakout of any func_tank behavior schedules SetCondition( COND_FUNCTANK_DISMOUNT ); + +#ifdef MAPBASE + ClearCondition( COND_FUNCTANK_FORCED ); +#endif } //----------------------------------------------------------------------------- @@ -204,7 +277,11 @@ int CAI_FuncTankBehavior::OnTakeDamage_Alive( const CTakeDamageInfo &info ) if ( m_hFuncTank && bValidDismountAttacker == true ) { +#ifdef MAPBASE + if ( !m_hFuncTank->IsEntityInViewCone( pAttacker ) && !m_hFuncTank->m_bControllerGlued ) +#else if ( !m_hFuncTank->IsEntityInViewCone( pAttacker ) ) +#endif { SetCondition( COND_FUNCTANK_DISMOUNT ); } @@ -571,7 +648,12 @@ void CAI_FuncTankBehavior::GatherConditions() } } +#ifdef MAPBASE + // So they don't unholster every time there's a tank in the map looking for NPCs + if (!m_hFuncTank && m_bMounted) +#else if ( !m_hFuncTank ) +#endif { m_bMounted = false; GetOuter()->SetDesiredWeaponState( DESIREDWEAPONSTATE_UNHOLSTERED ); @@ -704,6 +786,9 @@ AI_BEGIN_CUSTOM_SCHEDULE_PROVIDER( CAI_FuncTankBehavior ) DECLARE_TASK( TASK_FUNCTANK_ANNOUNCE_SCAN ) DECLARE_CONDITION( COND_FUNCTANK_DISMOUNT ) +#ifdef MAPBASE + DECLARE_CONDITION( COND_FUNCTANK_FORCED ) +#endif //========================================================= //========================================================= @@ -773,4 +858,20 @@ AI_BEGIN_CUSTOM_SCHEDULE_PROVIDER( CAI_FuncTankBehavior ) " Interrupts" ) +#ifdef MAPBASE + //========================================================= + //========================================================= + DEFINE_SCHEDULE + ( + SCHED_FORCE_MOUNT_FUNCTANK, + + " Tasks" + " TASK_STOP_MOVING 0" + " TASK_FACE_FUNCTANK 0" + " TASK_HOLSTER_WEAPON 0" + " " + " Interrupts" + ) +#endif + AI_END_CUSTOM_SCHEDULE_PROVIDER() diff --git a/src/game/server/hl2/ai_behavior_functank.h b/src/game/server/hl2/ai_behavior_functank.h index f2718b5d..6dfc1eca 100644 --- a/src/game/server/hl2/ai_behavior_functank.h +++ b/src/game/server/hl2/ai_behavior_functank.h @@ -49,6 +49,13 @@ public: void BeginScheduleSelection(); void EndScheduleSelection(); void PrescheduleThink(); +#ifdef MAPBASE + bool IsInterruptable( void ); + + bool CanManTank( CFuncTank *pTank, bool bForced ); +#endif + + void ModifyOrAppendCriteria( AI_CriteriaSet &set ); Activity NPC_TranslateActivity( Activity activity ); @@ -61,6 +68,9 @@ public: SCHED_FIRE_FUNCTANK, SCHED_SCAN_WITH_FUNCTANK, SCHED_FAIL_MOVE_TO_FUNCTANK, +#ifdef MAPBASE + SCHED_FORCE_MOUNT_FUNCTANK, +#endif }; // Tasks @@ -82,6 +92,9 @@ public: enum { COND_FUNCTANK_DISMOUNT = BaseClass::NEXT_CONDITION, +#ifdef MAPBASE + COND_FUNCTANK_FORCED, +#endif NEXT_CONDITION, }; @@ -104,6 +117,12 @@ public: bool IsMounted( void ) { return m_bMounted; } +#ifdef MAPBASE + void SetMounted( bool bMounted ) { m_bMounted = bMounted; } + + bool CanUnholsterWeapon( void ) { return !IsMounted(); } +#endif + private: // Schedule diff --git a/src/game/server/hl2/ai_behavior_police.cpp b/src/game/server/hl2/ai_behavior_police.cpp index eb1b45b3..8dc5ec40 100644 --- a/src/game/server/hl2/ai_behavior_police.cpp +++ b/src/game/server/hl2/ai_behavior_police.cpp @@ -11,6 +11,9 @@ #include "ai_memory.h" #include "collisionutils.h" #include "npc_metropolice.h" +#ifdef MAPBASE +#include "npc_combine.h" +#endif // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" @@ -125,12 +128,62 @@ void CAI_PolicingBehavior::HostSpeakSentence( const char *pSentence, SentencePri if ( pCop != NULL ) { +#ifdef METROPOLICE_USES_RESPONSE_SYSTEM + pCop->SpeakIfAllowed( pSentence, nSoundPriority, nCriteria ); +#else CAI_Sentence< CNPC_MetroPolice > *pSentences = pCop->GetSentences(); pSentences->Speak( pSentence, nSoundPriority, nCriteria ); +#endif } +#ifdef MAPBASE + else if ( CNPC_Combine *pCombine = dynamic_cast(GetOuter()) ) + { + pCombine->SpeakIfAllowed( pSentence, nSoundPriority, nCriteria ); + } + else if ( GetOuter()->GetExpresser() ) + { +#ifdef NEW_RESPONSE_SYSTEM + CAI_Concept concept = pSentence; + GetOuter()->GetExpresser()->Speak( concept ); +#else + GetOuter()->GetExpresser()->Speak( pSentence ); +#endif + } +#endif } +#ifdef METROPOLICE_USES_RESPONSE_SYSTEM +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CAI_PolicingBehavior::HostSpeakSentence( const char *pSentence, const char *modifiers, SentencePriority_t nSoundPriority, SentenceCriteria_t nCriteria ) +{ + // If we're a cop, turn the baton on + CNPC_MetroPolice *pCop = dynamic_cast(GetOuter()); + + if ( pCop != NULL ) + { + pCop->SpeakIfAllowed( pSentence, modifiers, nSoundPriority, nCriteria ); + } +#ifdef MAPBASE + else if ( CNPC_Combine *pCombine = dynamic_cast(GetOuter()) ) + { + pCombine->SpeakIfAllowed( pSentence, modifiers, nSoundPriority, nCriteria ); + } + else if ( GetOuter()->GetExpresser() ) + { +#ifdef NEW_RESPONSE_SYSTEM + CAI_Concept concept( pSentence ); + GetOuter()->GetExpresser()->Speak( concept, modifiers ); +#else + GetOuter()->GetExpresser()->Speak( pSentence, modifiers ); +#endif + } +#endif +} +#endif + //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- @@ -175,6 +228,10 @@ void CAI_PolicingBehavior::GatherConditions( void ) // See if we need to knock out our target immediately if ( ShouldKnockOutTarget( pTarget ) ) { +#ifdef MAPBASE + // If this isn't actually an enemy of ours and we're already warning, don't set this condition + if (GetOuter()->IRelationType( m_hPoliceGoal->GetTarget() ) <= D_FR || !IsCurSchedule(SCHED_POLICE_WARN_TARGET, false)) +#endif SetCondition( COND_POLICE_TARGET_TOO_CLOSE_SUPPRESS ); } @@ -189,6 +246,10 @@ void CAI_PolicingBehavior::GatherConditions( void ) if ( flDistSqr < (m_hPoliceGoal->GetRadius()*m_hPoliceGoal->GetRadius()) ) { +#ifdef MAPBASE + // If this isn't actually an enemy of ours and we're already warning, don't set this condition + if (GetOuter()->IRelationType( m_hPoliceGoal->GetTarget() ) <= D_FR || !IsCurSchedule(SCHED_POLICE_WARN_TARGET, false)) +#endif SetCondition( COND_POLICE_TARGET_TOO_CLOSE_SUPPRESS ); } } @@ -214,6 +275,9 @@ void CAI_PolicingBehavior::AnnouncePolicing( void ) "METROPOLICE_MOVE_ALONG_C", }; +#ifdef METROPOLICE_USES_RESPONSE_SYSTEM + HostSpeakSentence(TLK_COP_MOVE_ALONG, UTIL_VarArgs("numwarnings:%i", m_nNumWarnings), SENTENCE_PRIORITY_MEDIUM, SENTENCE_CRITERIA_NORMAL); +#else if ( m_nNumWarnings <= 3 ) { HostSpeakSentence( pWarnings[ m_nNumWarnings - 1 ], SENTENCE_PRIORITY_MEDIUM, SENTENCE_CRITERIA_NORMAL ); @@ -226,6 +290,7 @@ void CAI_PolicingBehavior::AnnouncePolicing( void ) int iSentence = RandomInt( 0, 1 ); HostSpeakSentence( pWarnings[ iSentence ], SENTENCE_PRIORITY_MEDIUM, SENTENCE_CRITERIA_NORMAL ); } +#endif } //----------------------------------------------------------------------------- @@ -239,6 +304,12 @@ int CAI_PolicingBehavior::TranslateSchedule( int scheduleType ) { if ( m_hPoliceGoal->ShouldRemainAtPost() && !MaintainGoalPosition() ) return BaseClass::TranslateSchedule( SCHED_COMBAT_FACE ); + +#ifdef MAPBASE + // If this isn't actually an enemy of ours, keep warning + if ( GetOuter()->IRelationType(m_hPoliceGoal->GetTarget()) > D_FR ) + return BaseClass::TranslateSchedule( SCHED_POLICE_WARN_TARGET ); +#endif } return BaseClass::TranslateSchedule( scheduleType ); @@ -335,7 +406,11 @@ void CAI_PolicingBehavior::StartTask( const Task_t *pTask ) if ( GetNavigator()->SetGoal( harassPos, pTask->flTaskData ) ) { +#ifdef MAPBASE + GetNavigator()->SetMovementActivity( GetOuter()->TranslateActivity(ACT_WALK_ANGRY) ); +#else GetNavigator()->SetMovementActivity( (Activity) ACT_WALK_ANGRY ); +#endif GetNavigator()->SetArrivalDirection( m_hPoliceGoal->GetTarget() ); TaskComplete(); } diff --git a/src/game/server/hl2/ai_behavior_police.h b/src/game/server/hl2/ai_behavior_police.h index 84f132be..ed1f58da 100644 --- a/src/game/server/hl2/ai_behavior_police.h +++ b/src/game/server/hl2/ai_behavior_police.h @@ -68,6 +68,9 @@ public: private: void HostSpeakSentence( const char *pSentence, SentencePriority_t nSoundPriority, SentenceCriteria_t nCriteria ); +#ifdef MAPBASE + void HostSpeakSentence( const char *pSentence, const char *modifiers, SentencePriority_t nSoundPriority, SentenceCriteria_t nCriteria ); +#endif int TranslateSchedule( int scheduleType ); diff --git a/src/game/server/hl2/basehlcombatweapon.cpp b/src/game/server/hl2/basehlcombatweapon.cpp index 33a700fc..894c290b 100644 --- a/src/game/server/hl2/basehlcombatweapon.cpp +++ b/src/game/server/hl2/basehlcombatweapon.cpp @@ -394,6 +394,10 @@ void CHLSelectFireMachineGun::SecondaryAttack( void ) { m_iSecondaryAttacks++; gamestats->Event_WeaponFired( pOwner, false, GetClassname() ); + +#ifdef MAPBASE + pOwner->SetAnimation( PLAYER_ATTACK2 ); +#endif } } diff --git a/src/game/server/hl2/cbasehelicopter.cpp b/src/game/server/hl2/cbasehelicopter.cpp index 1243fe18..2b46bce6 100644 --- a/src/game/server/hl2/cbasehelicopter.cpp +++ b/src/game/server/hl2/cbasehelicopter.cpp @@ -101,6 +101,10 @@ BEGIN_DATADESC( CBaseHelicopter ) DEFINE_FIELD( m_bSuppressSound, FIELD_BOOLEAN ), DEFINE_FIELD( m_flStartupTime, FIELD_TIME ), +#ifdef MAPBASE + DEFINE_KEYFIELD( m_bAllowAnyDamage, FIELD_BOOLEAN, "AllowAnyDamage" ), +#endif + DEFINE_FIELD( m_cullBoxMins, FIELD_VECTOR ), DEFINE_FIELD( m_cullBoxMaxs, FIELD_VECTOR ), @@ -193,8 +197,15 @@ void CBaseHelicopter::Spawn( void ) AddFlag( FL_NPC ); +#ifdef MAPBASE + if (m_flMaxSpeed == 0) + m_flMaxSpeed = BASECHOPPER_MAX_SPEED; + if (m_flMaxSpeedFiring == 0) + m_flMaxSpeedFiring = BASECHOPPER_MAX_FIRING_SPEED; +#else m_flMaxSpeed = BASECHOPPER_MAX_SPEED; m_flMaxSpeedFiring = BASECHOPPER_MAX_FIRING_SPEED; +#endif m_takedamage = DAMAGE_AIM; // Don't start up if the level designer has asked the @@ -1110,6 +1121,11 @@ void CBaseHelicopter::InputDisableRotorSound( inputdata_t &inputdata ) //----------------------------------------------------------------------------- void CBaseHelicopter::InputKill( inputdata_t &inputdata ) { +#ifdef MAPBASE + // Finally, an InputKill override that makes sense. + m_OnKilled.FireOutput( inputdata.pActivator, this ); +#endif + StopRotorWash(); m_bSuppressSound = true; @@ -1269,7 +1285,11 @@ void CBaseHelicopter::TraceAttack( const CTakeDamageInfo &info, const Vector &ve // Take no damage from trace attacks unless it's blast damage. RadiusDamage() sometimes calls // TraceAttack() as a means for delivering blast damage. Usually when the explosive penetrates // the target. (RPG missiles do this sometimes). +#ifdef MAPBASE + if ( info.GetDamageType() & (DMG_BLAST|DMG_AIRBOAT) || m_bAllowAnyDamage ) +#else if( info.GetDamageType() & (DMG_BLAST|DMG_AIRBOAT) ) +#endif { BaseClass::TraceAttack( info, vecDir, ptr, pAccumulator ); } @@ -1520,9 +1540,23 @@ bool CBaseHelicopter::ChooseEnemy( void ) // New enemy! Clear the timers and set conditions. SetEnemy( pNewEnemy ); m_flLastSeen = m_flPrevSeen = gpGlobals->curtime; +#ifdef MAPBASE + Remember( ( pNewEnemy->IsPlayer() ) ? bits_MEMORY_HAD_PLAYER : bits_MEMORY_HAD_ENEMY ); +#endif } else { +#ifdef MAPBASE + if (!pNewEnemy) + { + if ( HasMemory( bits_MEMORY_HAD_PLAYER ) ) + { + m_OnLostPlayer.FireOutput( GetEnemy(), this ); + } + m_OnLostEnemy.FireOutput( GetEnemy(), this ); + } +#endif + SetEnemy( NULL ); SetState( NPC_STATE_ALERT ); } @@ -1542,6 +1576,45 @@ bool CBaseHelicopter::ChooseEnemy( void ) //----------------------------------------------------------------------------- void CBaseHelicopter::GatherEnemyConditions( CBaseEntity *pEnemy ) { +#ifdef MAPBASE + // --------------------------- + // Ported from CAI_BaseNPC. Need it to fire outputs without setting all kinds of conditions + // --------------------------- + if ( HasCondition( COND_NEW_ENEMY ) || GetSenses()->GetTimeLastUpdate( GetEnemy() ) == gpGlobals->curtime ) + { + bool bSensesDidSee = GetSenses()->DidSeeEntity( pEnemy ); + + if ( !bSensesDidSee && ( ( EnemyDistance( pEnemy ) >= GetSenses()->GetDistLook() ) || !FVisible( pEnemy, MASK_BLOCKLOS ) ) ) + { + // No LOS to enemy + if (HasMemory( bits_MEMORY_HAD_LOS )) + { + // Send output event + if (GetEnemy()->IsPlayer()) + { + m_OnLostPlayerLOS.FireOutput( GetEnemy(), this ); + } + m_OnLostEnemyLOS.FireOutput( GetEnemy(), this ); + } + Forget( bits_MEMORY_HAD_LOS ); + } + else + { + if (!HasMemory( bits_MEMORY_HAD_LOS )) + { + // Send output event + EHANDLE hEnemy; + hEnemy.Set( GetEnemy() ); + + if (GetEnemy()->IsPlayer()) + m_OnFoundPlayer.Set(hEnemy, hEnemy, this); + m_OnFoundEnemy.Set(hEnemy, hEnemy, this); + } + Remember( bits_MEMORY_HAD_LOS ); + } + } +#endif + // ------------------- // If enemy is dead // ------------------- @@ -1595,3 +1668,103 @@ void ExpandBBox(Vector &vecMins, Vector &vecMaxs) vecMins.Init(-maxval, -maxval, -maxval); vecMaxs.Init(maxval, maxval, maxval); } + +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// A custom helicopter +//----------------------------------------------------------------------------- +class CNPC_CustomHelicopter : public CBaseHelicopter +{ +public: + DECLARE_CLASS( CNPC_CustomHelicopter, CBaseHelicopter ); + DECLARE_DATADESC(); + + CNPC_CustomHelicopter(); + ~CNPC_CustomHelicopter(); + + virtual void Precache( void ); + virtual void Spawn( void ); + + void InitializeRotorSound( void ); + + float GetAcceleration( void ) { return m_flAcceleration; } + + float m_flAcceleration; + + string_t m_iszRotorSound; + string_t m_iszRotorBlast; +}; + +LINK_ENTITY_TO_CLASS( npc_helicopter_custom, CNPC_CustomHelicopter ); + +BEGIN_DATADESC( CNPC_CustomHelicopter ) + + DEFINE_KEYFIELD( m_flMaxSpeed, FIELD_FLOAT, "MaxSpeed" ), + DEFINE_KEYFIELD( m_flMaxSpeedFiring, FIELD_FLOAT, "MaxSpeedfiring" ), + + DEFINE_KEYFIELD( m_flAcceleration, FIELD_FLOAT, "Acceleration" ), + + DEFINE_KEYFIELD( m_iszRotorSound, FIELD_STRING, "RotorSound" ), + DEFINE_KEYFIELD( m_iszRotorBlast, FIELD_STRING, "RotorBlast" ), + +END_DATADESC() + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CNPC_CustomHelicopter::CNPC_CustomHelicopter() +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CNPC_CustomHelicopter::~CNPC_CustomHelicopter() +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CNPC_CustomHelicopter::Precache( void ) +{ + PrecacheModel( STRING(GetModelName()) ); + + PrecacheScriptSound( STRING(m_iszRotorSound) ); + PrecacheScriptSound( STRING(m_iszRotorBlast) ); + + BaseClass::Precache(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CNPC_CustomHelicopter::Spawn( void ) +{ + SetModel( STRING(GetModelName()) ); + + BaseClass::Spawn(); +} + +//------------------------------------------------------------------------------ +// Purpose: Create our rotor sound +//------------------------------------------------------------------------------ +void CNPC_CustomHelicopter::InitializeRotorSound( void ) +{ + if ( !m_pRotorSound ) + { + CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController(); + CPASAttenuationFilter filter( this ); + + m_pRotorSound = controller.SoundCreate( filter, entindex(), STRING(m_iszRotorSound) ); + m_pRotorBlast = controller.SoundCreate( filter, entindex(), STRING(m_iszRotorBlast) ); + } + else + { + Assert(m_pRotorSound); + Assert(m_pRotorBlast); + } + + BaseClass::InitializeRotorSound(); +} +#endif diff --git a/src/game/server/hl2/cbasehelicopter.h b/src/game/server/hl2/cbasehelicopter.h index 4ca3b091..714f62f4 100644 --- a/src/game/server/hl2/cbasehelicopter.h +++ b/src/game/server/hl2/cbasehelicopter.h @@ -215,6 +215,10 @@ protected: EHANDLE m_hRotorWash; // Attached rotorwash entity +#ifdef MAPBASE + bool m_bAllowAnyDamage; +#endif + // Inputs void InputActivate( inputdata_t &inputdata ); @@ -263,6 +267,10 @@ private: typedef CHandle AvoidSphereHandle_t; float m_flRadius; +#ifdef MAPBASE + string_t m_iszAvoidFilter; + EHANDLE m_hAvoidFilter; +#endif static CUtlVector< AvoidSphereHandle_t > s_AvoidSpheres; }; diff --git a/src/game/server/hl2/combine_mine.cpp b/src/game/server/hl2/combine_mine.cpp index 4eb7283f..aa9bdf29 100644 --- a/src/game/server/hl2/combine_mine.cpp +++ b/src/game/server/hl2/combine_mine.cpp @@ -58,6 +58,10 @@ char *pszMineStateNames[] = // Approximate radius of the bomb's model #define BOUNCEBOMB_RADIUS 24 +#ifdef MAPBASE +ConVar combine_mine_trace_dist( "combine_mine_trace_dist", "1024" ); +#endif + BEGIN_DATADESC( CBounceBomb ) DEFINE_THINKFUNC( ExplodeThink ), DEFINE_ENTITYFUNC( ExplodeTouch ), @@ -87,9 +91,20 @@ BEGIN_DATADESC( CBounceBomb ) DEFINE_FIELD( m_bFoeNearest, FIELD_BOOLEAN ), DEFINE_FIELD( m_flIgnoreWorldTime, FIELD_TIME ), DEFINE_KEYFIELD( m_bDisarmed, FIELD_BOOLEAN, "StartDisarmed" ), +#ifdef MAPBASE + DEFINE_KEYFIELD( m_iInitialState, FIELD_INTEGER, "InitialState" ), + DEFINE_KEYFIELD( m_bCheapWarnSound, FIELD_BOOLEAN, "CheapWarnSound" ), + DEFINE_KEYFIELD( m_iLOSMask, FIELD_INTEGER, "LOSMask" ), + DEFINE_INPUT( m_bUnavoidable, FIELD_BOOLEAN, "SetUnavoidable" ), + DEFINE_KEYFIELD( m_vecPlantOrientation, FIELD_VECTOR, "PlantOrientation" ), +#endif DEFINE_KEYFIELD( m_iModification, FIELD_INTEGER, "Modification" ), +#ifdef MAPBASE + DEFINE_KEYFIELD( m_bPlacedByPlayer, FIELD_BOOLEAN, "Friendly" ), +#else DEFINE_FIELD( m_bPlacedByPlayer, FIELD_BOOLEAN ), +#endif DEFINE_FIELD( m_bHeldByPhysgun, FIELD_BOOLEAN ), DEFINE_FIELD( m_iFlipAttempts, FIELD_INTEGER ), @@ -97,6 +112,16 @@ BEGIN_DATADESC( CBounceBomb ) DEFINE_FIELD( m_flTimeGrabbed, FIELD_TIME ), DEFINE_FIELD( m_iMineState, FIELD_INTEGER ), +#ifdef MAPBASE + DEFINE_KEYFIELD( m_bFilterExclusive, FIELD_BOOLEAN, "FilterExclusive" ), + DEFINE_KEYFIELD( m_iszEnemyFilter, FIELD_STRING, "enemyfilter" ), + DEFINE_FIELD( m_hEnemyFilter, FIELD_EHANDLE ), + DEFINE_INPUTFUNC( FIELD_STRING, "SetEnemyFilter", InputSetEnemyFilter ), + DEFINE_KEYFIELD( m_iszFriendFilter, FIELD_STRING, "friendfilter" ), + DEFINE_FIELD( m_hFriendFilter, FIELD_EHANDLE ), + DEFINE_INPUTFUNC( FIELD_STRING, "SetFriendFilter", InputSetFriendFilter ), +#endif + // Physics Influence DEFINE_FIELD( m_hPhysicsAttacker, FIELD_EHANDLE ), DEFINE_FIELD( m_flLastPhysicsInfluenceTime, FIELD_TIME ), @@ -106,6 +131,16 @@ BEGIN_DATADESC( CBounceBomb ) DEFINE_OUTPUT( m_OnPulledUp, "OnPulledUp" ), DEFINE_INPUTFUNC( FIELD_VOID, "Disarm", InputDisarm ), +#ifdef MAPBASE + DEFINE_INPUTFUNC( FIELD_VOID, "Bounce", InputBounce ), + DEFINE_INPUTFUNC( FIELD_EHANDLE, "BounceAtTarget", InputBounceAtTarget ), + DEFINE_INPUTFUNC( FIELD_VECTOR, "SetPlantOrientation", InputSetPlantOrientation ), + DEFINE_INPUTFUNC( FIELD_VECTOR, "SetPlantOrientationRaw", InputSetPlantOrientationRaw ), + + DEFINE_OUTPUT( m_OnTriggered, "OnTriggered" ), + DEFINE_OUTPUT( m_OnExplode, "OnExplode" ), +#endif + END_DATADESC() string_t CBounceBomb::gm_iszFloorTurretClassname; @@ -130,6 +165,13 @@ void CBounceBomb::Precache() gm_iszFloorTurretClassname = AllocPooledString( "npc_turret_floor" ); gm_iszGroundTurretClassname = AllocPooledString( "npc_turret_ground" ); + +#ifdef MAPBASE + if (m_iszEnemyFilter != NULL_STRING) + m_hEnemyFilter = dynamic_cast(gEntList.FindEntityByName(NULL, STRING(m_iszEnemyFilter), this)); + if (m_iszFriendFilter != NULL_STRING) + m_hFriendFilter = dynamic_cast(gEntList.FindEntityByName( NULL, STRING(m_iszFriendFilter), this )); +#endif } //--------------------------------------------------------- @@ -178,10 +220,23 @@ void CBounceBomb::Spawn() { SetMineState( MINE_STATE_DORMANT ); } +#ifdef MAPBASE + else + { + // NOTE: MINE_STATE_DEPLOY and MINE_STATE_DORMANT are swapped in this case! + if (m_iInitialState == 0) + SetMineState( MINE_STATE_DEPLOY ); + else if (m_iInitialState == 1) + SetMineState( MINE_STATE_DORMANT ); + else + SetMineState( m_iInitialState ); + } +#else else { SetMineState( MINE_STATE_DEPLOY ); } +#endif // default to a different skin for cavern turrets (unless explicitly overridden) if ( m_iModification == MINE_MODIFICATION_CAVERN ) @@ -218,6 +273,14 @@ void CBounceBomb::Spawn() // pretend like the player set me down. m_bPlacedByPlayer = true; } + +#ifdef MAPBASE + if (m_vecPlantOrientation != vec3_invalid) + { + // Turn angles into direction + AngleVectors( QAngle( m_vecPlantOrientation.x, m_vecPlantOrientation.y, m_vecPlantOrientation.z ), &m_vecPlantOrientation ); + } +#endif } //--------------------------------------------------------- @@ -261,8 +324,12 @@ void CBounceBomb::SetMineState( int iState ) { case MINE_STATE_DORMANT: { +#ifdef MAPBASE + SilenceWarnSound( 0.1 ); +#else CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController(); controller.SoundChangeVolume( m_pWarnSound, 0.0, 0.1 ); +#endif UpdateLight( false, 0, 0, 0, 0 ); SetThink( NULL ); } @@ -270,8 +337,12 @@ void CBounceBomb::SetMineState( int iState ) case MINE_STATE_CAPTIVE: { +#ifdef MAPBASE + SilenceWarnSound( 0.2 ); +#else CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController(); controller.SoundChangeVolume( m_pWarnSound, 0.0, 0.2 ); +#endif // Unhook unsigned int flags = VPhysicsGetObject()->GetCallbackFlags(); @@ -314,8 +385,12 @@ void CBounceBomb::SetMineState( int iState ) // Scare NPC's CSoundEnt::InsertSound( SOUND_DANGER, GetAbsOrigin(), 300, 1.0f, this ); +#ifdef MAPBASE + SilenceWarnSound( 0.2 ); +#else CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController(); controller.SoundChangeVolume( m_pWarnSound, 0.0, 0.2 ); +#endif SetTouch( &CBounceBomb::ExplodeTouch ); unsigned int flags = VPhysicsGetObject()->GetCallbackFlags(); @@ -352,7 +427,11 @@ void CBounceBomb::SetMineState( int iState ) else { SetThink( &CBounceBomb::BounceThink ); +#ifdef MAPBASE + SetNextThink( gpGlobals->curtime + m_flExplosionDelay ); +#else SetNextThink( gpGlobals->curtime + 0.5 ); +#endif } } break; @@ -630,7 +709,20 @@ void CBounceBomb::SettleThink() { // If i'm not resting on the world, jump randomly. trace_t tr; - UTIL_TraceLine( GetAbsOrigin(), GetAbsOrigin() - Vector( 0, 0, 1024 ), MASK_SHOT|CONTENTS_GRATE, this, COLLISION_GROUP_NONE, &tr ); +#ifdef MAPBASE + Vector vecTraceDir; + if (m_vecPlantOrientation != vec3_invalid) + { + vecTraceDir = m_vecPlantOrientation * combine_mine_trace_dist.GetFloat(); + } + else + { + vecTraceDir = Vector( 0, 0, combine_mine_trace_dist.GetFloat() ); + } +#else + Vector vecTraceDir = Vector( 0, 0, 1024 ); +#endif + UTIL_TraceLine( GetAbsOrigin(), GetAbsOrigin() - vecTraceDir, MASK_SHOT|CONTENTS_GRATE, this, COLLISION_GROUP_NONE, &tr ); bool bHop = false; if( tr.m_pEnt ) @@ -664,6 +756,20 @@ void CBounceBomb::SettleThink() // Check for upside-down Vector vecUp; GetVectors( NULL, NULL, &vecUp ); +#ifdef MAPBASE + if (m_vecPlantOrientation != vec3_invalid) + { + float flDiff = abs(m_vecPlantOrientation.z - vecUp.z); + if ( flDiff >= 0.2f ) + { + // Landed upside down. Right self + Vector vecForce( 0, 0, 2500 ); + Flip( vecForce, AngularImpulse( 60, 0, 0 ) ); + return; + } + } + else +#endif if( vecUp.z <= 0.8 ) { // Landed upside down. Right self @@ -773,7 +879,11 @@ void CBounceBomb::Wake( bool bAwake ) CReliableBroadcastRecipientFilter filter; +#ifdef MAPBASE + if( !m_pWarnSound && !m_bCheapWarnSound ) +#else if( !m_pWarnSound ) +#endif { m_pWarnSound = controller.SoundCreate( filter, entindex(), "NPC_CombineMine.ActiveLoop" ); controller.Play( m_pWarnSound, 1.0, PITCH_NORM ); @@ -785,7 +895,11 @@ void CBounceBomb::Wake( bool bAwake ) if( m_bFoeNearest ) { EmitSound( "NPC_CombineMine.TurnOn" ); +#ifdef MAPBASE + UpdateWarnSound( 1.0, 0.1 ); +#else controller.SoundChangeVolume( m_pWarnSound, 1.0, 0.1 ); +#endif } unsigned char r, g, b; @@ -811,7 +925,11 @@ void CBounceBomb::Wake( bool bAwake ) } SetNearestNPC( NULL ); +#ifdef MAPBASE + SilenceWarnSound( 0.1 ); +#else controller.SoundChangeVolume( m_pWarnSound, 0.0, 0.1 ); +#endif UpdateLight( false, 0, 0, 0, 0 ); } @@ -845,6 +963,27 @@ float CBounceBomb::FindNearestNPC() if( pNPC->EyePosition().z < GetAbsOrigin().z ) continue; +#ifdef MAPBASE + bool bPassesFilter = false; + if (m_hEnemyFilter || m_hFriendFilter) + { + // If we have an enemy or friend filter, always accept those who pass it + // If we're only supposed to be using filters, only find entities that pass one of them + + if (m_hEnemyFilter && m_hEnemyFilter->PassesFilter( this, pNPC )) + bPassesFilter = true; + + else if (m_hFriendFilter && m_hFriendFilter->PassesFilter( this, pNPC )) + bPassesFilter = true; + + if (m_bFilterExclusive && !bPassesFilter) + continue; + } + + if (!bPassesFilter) + { +#endif + // Disregard things that want to be disregarded if( pNPC->Classify() == CLASS_NONE ) continue; @@ -857,13 +996,21 @@ float CBounceBomb::FindNearestNPC() if( pNPC->m_iClassname == gm_iszFloorTurretClassname || pNPC->m_iClassname == gm_iszGroundTurretClassname ) continue; +#ifdef MAPBASE + } +#endif + float flDist = (GetAbsOrigin() - pNPC->GetAbsOrigin()).LengthSqr(); if( flDist < flNearest ) { // Now do a visibility test. +#ifdef MAPBASE + if( FVisible( pNPC, m_iLOSMask ) ) +#else if( FVisible( pNPC, MASK_SOLID_BRUSHONLY ) ) +#endif { flNearest = flDist; SetNearestNPC( pNPC ); @@ -872,19 +1019,55 @@ float CBounceBomb::FindNearestNPC() } } +#ifdef MAPBASE_MP + for (i = 1; i <= gpGlobals->maxClients; i++) + { + CBaseEntity *pPlayer = UTIL_PlayerByIndex( i ); + if ( pPlayer && !(pPlayer->GetFlags() & FL_NOTARGET) ) + { + float flDist = (pPlayer->GetAbsOrigin() - GetAbsOrigin() ).LengthSqr(); + + if( flDist < flNearest && FVisible( pPlayer, m_iLOSMask ) ) + { + flNearest = flDist; + SetNearestNPC( pPlayer ); + } + } + } +#else // finally, check the player. CBasePlayer *pPlayer = UTIL_GetLocalPlayer(); if( pPlayer && !(pPlayer->GetFlags() & FL_NOTARGET) ) { +#ifdef MAPBASE + bool bPassesFilter = true; + if ((m_hEnemyFilter || m_hFriendFilter) && m_bFilterExclusive) + { + // If we have an enemy or friend filter, and that's all we're supposed to be using, + // don't accept the player if they don't pass our filters + + if (m_hEnemyFilter && !m_hEnemyFilter->PassesFilter( this, pPlayer )) + bPassesFilter = false; + + else if (m_hFriendFilter && !m_hFriendFilter->PassesFilter( this, pPlayer )) + bPassesFilter = false; + } +#endif + float flDist = (pPlayer->GetAbsOrigin() - GetAbsOrigin() ).LengthSqr(); +#ifdef MAPBASE + if( flDist < flNearest && FVisible( pPlayer, m_iLOSMask ) && bPassesFilter ) +#else if( flDist < flNearest && FVisible( pPlayer, MASK_SOLID_BRUSHONLY ) ) +#endif { flNearest = flDist; SetNearestNPC( pPlayer ); } } +#endif if( m_hNearestNPC.Get() ) { @@ -894,8 +1077,9 @@ float CBounceBomb::FindNearestNPC() if( m_bFoeNearest ) { // Changing state to where a friend is nearest. - +#ifndef MAPBASE if( IsFriend( m_hNearestNPC ) ) +#endif { // Friend UpdateLight( true, 0, 255, 0, 190 ); @@ -921,6 +1105,14 @@ float CBounceBomb::FindNearestNPC() //--------------------------------------------------------- bool CBounceBomb::IsFriend( CBaseEntity *pEntity ) { +#ifdef MAPBASE + if (m_hFriendFilter && m_hFriendFilter->PassesFilter(this, pEntity)) + return true; + + if (m_hEnemyFilter && m_hEnemyFilter->PassesFilter(this, pEntity)) + return false; +#endif + int classify = pEntity->Classify(); bool bIsCombine = false; @@ -930,10 +1122,16 @@ bool CBounceBomb::IsFriend( CBaseEntity *pEntity ) return false; } - if( classify == CLASS_METROPOLICE || + if( classify == CLASS_METROPOLICE || classify == CLASS_COMBINE || classify == CLASS_MILITARY || classify == CLASS_COMBINE_HUNTER || +#ifdef MAPBASE + classify == CLASS_MANHACK || + classify == CLASS_STALKER || + classify == CLASS_PROTOSNIPER || + classify == CLASS_COMBINE_GUNSHIP || +#endif classify == CLASS_SCANNER ) { bIsCombine = true; @@ -976,7 +1174,12 @@ void CBounceBomb::SearchThink() if( m_pConstraint && gpGlobals->curtime - m_flTimeGrabbed >= 1.0f ) { +#ifdef MAPBASE + // We don't already store our holder for some reason + m_OnPulledUp.FireOutput( UTIL_GetLocalPlayer(), this ); +#else m_OnPulledUp.FireOutput( this, this ); +#endif SetMineState( MINE_STATE_CAPTIVE ); return; } @@ -1002,6 +1205,9 @@ void CBounceBomb::SearchThink() if( flNearestNPCDist <= BOUNCEBOMB_DETONATE_RADIUS && !IsFriend( m_hNearestNPC ) ) { +#ifdef MAPBASE + m_OnTriggered.FireOutput( m_hNearestNPC, this ); +#endif if( m_bBounce ) { SetMineState( MINE_STATE_TRIGGERED ); @@ -1087,6 +1293,11 @@ void CBounceBomb::ExplodeThink() { ExplosionCreate( GetAbsOrigin(), GetAbsAngles(), (pThrower) ? pThrower : this, BOUNCEBOMB_EXPLODE_DAMAGE, BOUNCEBOMB_EXPLODE_RADIUS, true); } + +#ifdef MAPBASE + m_OnExplode.FireOutput( m_hNearestNPC, this ); +#endif + UTIL_Remove( this ); } @@ -1145,6 +1356,82 @@ void CBounceBomb::CloseHooks() #endif } +#ifdef MAPBASE +extern int g_interactionBarnacleVictimBite; +extern int g_interactionBarnacleVictimFinalBite; +extern int ACT_BARNACLE_BITE_SMALL_THINGS; +//----------------------------------------------------------------------------- +// Purpose: Uses the new CBaseEntity interaction implementation and +// replaces the dynamic_casting from npc_barnacle +// Input : The type of interaction, extra info pointer, and who started it +// Output : true - if sub-class has a response for the interaction +// false - if sub-class has no response +//----------------------------------------------------------------------------- +bool CBounceBomb::HandleInteraction( int interactionType, void *data, CBaseCombatCharacter* sourceEnt ) +{ + // This was originally done in npc_barnacle itself, but + // we've transitioned to interactions so we could extend special behavior to others + // without just adding more casting. + if ( interactionType == g_interactionBarnacleVictimBite ) + { + Assert( sourceEnt && sourceEnt->IsNPC() ); + sourceEnt->MyNPCPointer()->SetActivity( (Activity)ACT_BARNACLE_BITE_SMALL_THINGS ); + return true; + } + else if ( interactionType == g_interactionBarnacleVictimFinalBite ) + { + ExplodeThink(); + return true; + } + + return BaseClass::HandleInteraction(interactionType, data, sourceEnt); +} + +//----------------------------------------------------------------------------- +void CBounceBomb::UpdateWarnSound( float flVolume, float flDelta ) +{ + CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController(); + if (m_bCheapWarnSound && !m_pWarnSound) + { + CReliableBroadcastRecipientFilter filter; + //m_pWarnSound = controller.SoundCreate( filter, entindex(), "NPC_CombineMine.ActiveLoop" ); + //controller.Play( m_pWarnSound, flVolume, PITCH_NORM ); + + EmitSound_t params; + params.m_pSoundName = "NPC_CombineMine.ActiveLoop"; + params.m_flVolume = flVolume; + params.m_nPitch = PITCH_NORM; + + EmitSound( filter, entindex(), params ); + } + else + { + controller.SoundChangeVolume( m_pWarnSound, flVolume, flDelta ); + } +} + +void CBounceBomb::SilenceWarnSound( float flDelta ) +{ + CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController(); + if (m_bCheapWarnSound) + { + //if ( m_pWarnSound ) + //{ + // controller.SoundDestroy( m_pWarnSound ); + //} + + StopSound( "NPC_CombineMine.ActiveLoop" ); + } + else + { + if ( m_pWarnSound ) + { + controller.SoundChangeVolume( m_pWarnSound, 0.0, flDelta ); + } + } +} +#endif + //--------------------------------------------------------- //--------------------------------------------------------- void CBounceBomb::InputDisarm( inputdata_t &inputdata ) @@ -1165,6 +1452,56 @@ void CBounceBomb::InputDisarm( inputdata_t &inputdata ) } } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CBounceBomb::InputSetEnemyFilter( inputdata_t &inputdata ) +{ + m_iszEnemyFilter = inputdata.value.StringID(); + m_hEnemyFilter = dynamic_cast(gEntList.FindEntityByName( NULL, STRING(m_iszEnemyFilter), this )); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CBounceBomb::InputSetFriendFilter( inputdata_t &inputdata ) +{ + m_iszFriendFilter = inputdata.value.StringID(); + m_hFriendFilter = dynamic_cast(gEntList.FindEntityByName( NULL, STRING(m_iszFriendFilter), this )); +} + +//--------------------------------------------------------- +//--------------------------------------------------------- +void CBounceBomb::InputBounce( inputdata_t &inputdata ) +{ + m_hNearestNPC = NULL; + SetMineState(MINE_STATE_TRIGGERED); +} + +//--------------------------------------------------------- +//--------------------------------------------------------- +void CBounceBomb::InputBounceAtTarget( inputdata_t &inputdata ) +{ + m_hNearestNPC = inputdata.value.Entity(); + SetMineState(MINE_STATE_TRIGGERED); +} + +//--------------------------------------------------------- +//--------------------------------------------------------- +void CBounceBomb::InputSetPlantOrientation( inputdata_t &inputdata ) +{ + Vector vecInput; + inputdata.value.Vector3D( vecInput ); + AngleVectors( QAngle(vecInput.x, vecInput.y, vecInput.z), &m_vecPlantOrientation ); +} + +//--------------------------------------------------------- +//--------------------------------------------------------- +void CBounceBomb::InputSetPlantOrientationRaw( inputdata_t &inputdata ) +{ + inputdata.value.Vector3D( m_vecPlantOrientation ); +} +#endif + //--------------------------------------------------------- //--------------------------------------------------------- void CBounceBomb::OnPhysGunDrop( CBasePlayer *pPhysGunUser, PhysGunDrop_t Reason ) @@ -1207,6 +1544,18 @@ CBasePlayer *CBounceBomb::HasPhysicsAttacker( float dt ) return NULL; } +//--------------------------------------------------------- +//--------------------------------------------------------- +bool CBounceBomb::ShouldBeAvoidedByCompanions() +{ +#ifdef MAPBASE + if (m_bUnavoidable) + return false; +#endif + + return !IsPlayerPlaced() && IsAwake(); +} + //--------------------------------------------------------- //--------------------------------------------------------- void CBounceBomb::OnPhysGunPickup( CBasePlayer *pPhysGunUser, PhysGunPickup_t reason ) diff --git a/src/game/server/hl2/combine_mine.h b/src/game/server/hl2/combine_mine.h index 48dbb468..097968db 100644 --- a/src/game/server/hl2/combine_mine.h +++ b/src/game/server/hl2/combine_mine.h @@ -24,13 +24,20 @@ class CSoundPatch; #define BOUNCEBOMB_EXPLODE_RADIUS 125.0 #define BOUNCEBOMB_EXPLODE_DAMAGE 150.0 #include "player_pickup.h" +#ifdef MAPBASE +#include "filters.h" +#endif class CBounceBomb : public CBaseAnimating, public CDefaultPlayerPickupVPhysics { DECLARE_CLASS( CBounceBomb, CBaseAnimating ); public: +#ifdef MAPBASE + CBounceBomb() { m_pWarnSound = NULL; m_bPlacedByPlayer = false; m_flExplosionDelay = 0.5f; m_iLOSMask = MASK_SOLID_BRUSHONLY; m_vecPlantOrientation = vec3_invalid; } +#else CBounceBomb() { m_pWarnSound = NULL; m_bPlacedByPlayer = false; } +#endif void Precache(); void Spawn(); void OnRestore(); @@ -65,6 +72,9 @@ public: bool IsPlayerPlaced() { return m_bPlacedByPlayer; } + // Determines whether companions should treat the mine as a navigation obstacle and avoid it + bool ShouldBeAvoidedByCompanions(); + bool CreateVPhysics() { VPhysicsInitNormal( SOLID_VPHYSICS, 0, false ); @@ -76,6 +86,14 @@ public: void OpenHooks( bool bSilent = false ); void CloseHooks(); +#ifdef MAPBASE + // Uses the new CBaseEntity interaction implementation and replaces the dynamic_casting from npc_barnacle + bool HandleInteraction( int interactionType, void *data, CBaseCombatCharacter* sourceEnt ); + + void UpdateWarnSound( float flVolume, float flDelta ); + void SilenceWarnSound( float flDelta ); +#endif + DECLARE_DATADESC(); static string_t gm_iszFloorTurretClassname; @@ -104,6 +122,19 @@ private: float m_flIgnoreWorldTime; bool m_bDisarmed; +#ifdef MAPBASE + int m_iInitialState; + bool m_bCheapWarnSound; + + // Allows control over the mask used in LOS + int m_iLOSMask; + + bool m_bUnavoidable; + + // What direction the mine should be facing when planting itself (i.e. facing up, facing left, etc.) + // vec3_invalid = use default (0 0 1 or -90 0 0) + Vector m_vecPlantOrientation; +#endif bool m_bPlacedByPlayer; @@ -119,8 +150,29 @@ private: IPhysicsConstraint *m_pConstraint; int m_iMineState; +#ifdef MAPBASE + // Makes the filters the exclusive factor in determining friend/foe + bool m_bFilterExclusive; + + string_t m_iszEnemyFilter; + CHandle m_hEnemyFilter; + void InputSetEnemyFilter( inputdata_t &inputdata ); + + string_t m_iszFriendFilter; + CHandle m_hFriendFilter; + void InputSetFriendFilter( inputdata_t &inputdata ); +#endif + COutputEvent m_OnPulledUp; void InputDisarm( inputdata_t &inputdata ); +#ifdef MAPBASE + void InputBounce( inputdata_t &inputdata ); + void InputBounceAtTarget( inputdata_t &inputdata ); + void InputSetPlantOrientation( inputdata_t &inputdata ); + void InputSetPlantOrientationRaw( inputdata_t &inputdata ); + COutputEvent m_OnTriggered; + COutputEvent m_OnExplode; +#endif }; diff --git a/src/game/server/hl2/env_headcrabcanister.cpp b/src/game/server/hl2/env_headcrabcanister.cpp index ed1883d1..4cf7fffb 100644 --- a/src/game/server/hl2/env_headcrabcanister.cpp +++ b/src/game/server/hl2/env_headcrabcanister.cpp @@ -33,6 +33,9 @@ ConVar sk_env_headcrabcanister_shake_radius( "sk_env_headcrabcanister_shake_radi ConVar sk_env_headcrabcanister_shake_radius_vehicle( "sk_env_headcrabcanister_shake_radius_vehicle", "2500" ); #define ENV_HEADCRABCANISTER_TRAIL_TIME 3.0f +#ifdef MAPBASE +#define RANDOM_CRAB_TYPE -1 +#endif //----------------------------------------------------------------------------- // Spawn flags @@ -95,6 +98,9 @@ private: void InputOpenCanister( inputdata_t &inputdata ); void InputSpawnHeadcrabs( inputdata_t &inputdata ); void InputStopSmoke( inputdata_t &inputdata ); +#ifdef MAPBASE + void InputStopHissing( inputdata_t &inputdata ); +#endif // Think(s) void HeadcrabCanisterSkyboxThink( void ); @@ -152,6 +158,9 @@ private: COutputEHANDLE m_OnLaunched; COutputEvent m_OnImpacted; COutputEvent m_OnOpened; +#ifdef MAPBASE + COutputEHANDLE m_OnCrab; +#endif // Only for skybox only cannisters. float m_flMinRefireTime; @@ -201,11 +210,17 @@ BEGIN_DATADESC( CEnvHeadcrabCanister ) DEFINE_INPUTFUNC( FIELD_VOID, "OpenCanister", InputOpenCanister ), DEFINE_INPUTFUNC( FIELD_VOID, "SpawnHeadcrabs", InputSpawnHeadcrabs ), DEFINE_INPUTFUNC( FIELD_VOID, "StopSmoke", InputStopSmoke ), +#ifdef MAPBASE + DEFINE_INPUTFUNC( FIELD_VOID, "StopHissing", InputStopHissing ), +#endif // Outputs DEFINE_OUTPUT( m_OnLaunched, "OnLaunched" ), DEFINE_OUTPUT( m_OnImpacted, "OnImpacted" ), DEFINE_OUTPUT( m_OnOpened, "OnOpened" ), +#ifdef MAPBASE + DEFINE_OUTPUT( m_OnCrab, "OnCrab" ), +#endif END_DATADESC() @@ -246,7 +261,22 @@ void CEnvHeadcrabCanister::Precache( void ) PrecacheScriptSound( "HeadcrabCanister.SkyboxExplosion" ); PrecacheScriptSound( "HeadcrabCanister.Open" ); +#ifdef MAPBASE + if ( m_nHeadcrabType != RANDOM_CRAB_TYPE ) + { + UTIL_PrecacheOther( s_pHeadcrabClass[m_nHeadcrabType] ); + } + else + { + // precache all the headcrabs if we're spawning random species + for ( int i = 0; i < ARRAYSIZE( s_pHeadcrabClass ); i++ ) + { + UTIL_PrecacheOther( s_pHeadcrabClass[i] ); + } + } +#else UTIL_PrecacheOther( s_pHeadcrabClass[m_nHeadcrabType] ); +#endif } @@ -545,6 +575,17 @@ void CEnvHeadcrabCanister::InputStopSmoke( inputdata_t &inputdata ) } } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: +// Input : &inputdata - +//----------------------------------------------------------------------------- +void CEnvHeadcrabCanister::InputStopHissing( inputdata_t &inputdata ) +{ + StopSound( "HeadcrabCanister.AfterLanding" ); +} +#endif + //============================================================================= // // Enumerator for swept bbox collision. @@ -710,7 +751,17 @@ void CEnvHeadcrabCanister::HeadcrabCanisterSpawnHeadcrabThink() int nHeadCrabAttachment = LookupAttachment( "headcrab" ); if ( GetAttachment( nHeadCrabAttachment, vecSpawnPosition, vecSpawnAngles ) ) { +#ifdef MAPBASE + int iHeadcrabType = m_nHeadcrabType; + if ( m_nHeadcrabType == RANDOM_CRAB_TYPE ) + { + iHeadcrabType = RandomInt( 0, ARRAYSIZE( s_pHeadcrabClass ) - 1 ); + } + + CBaseEntity *pEnt = CreateEntityByName( s_pHeadcrabClass[iHeadcrabType] ); +#else CBaseEntity *pEnt = CreateEntityByName( s_pHeadcrabClass[m_nHeadcrabType] ); +#endif CBaseHeadcrab *pHeadCrab = assert_cast(pEnt); // Necessary to get it to eject properly (don't allow the NPC @@ -725,6 +776,10 @@ void CEnvHeadcrabCanister::HeadcrabCanisterSpawnHeadcrabThink() pHeadCrab->SetLocalOrigin( vec3_origin ); pHeadCrab->SetLocalAngles( vec3_angle ); pHeadCrab->CrawlFromCanister(); + +#ifdef MAPBASE + m_OnCrab.Set(pHeadCrab, pHeadCrab, this); +#endif } if ( m_nHeadcrabCount != 0 ) diff --git a/src/game/server/hl2/env_speaker.cpp b/src/game/server/hl2/env_speaker.cpp index dd141eca..ad46a25c 100644 --- a/src/game/server/hl2/env_speaker.cpp +++ b/src/game/server/hl2/env_speaker.cpp @@ -21,6 +21,9 @@ #include "ndebugoverlay.h" #include "soundscape.h" #include "AI_ResponseSystem.h" +#ifdef MAPBASE +#include "sceneentity.h" +#endif // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" @@ -35,6 +38,10 @@ LINK_ENTITY_TO_CLASS( env_speaker, CSpeaker ); BEGIN_DATADESC( CSpeaker ) +#ifdef MAPBASE + DEFINE_FIELD( m_hTarget, FIELD_EHANDLE ), +#endif + DEFINE_KEYFIELD( m_delayMin, FIELD_FLOAT, "delaymin" ), DEFINE_KEYFIELD( m_delayMax, FIELD_FLOAT, "delaymax" ), DEFINE_KEYFIELD( m_iszRuleScriptFile, FIELD_STRING, "rulescript" ), @@ -50,6 +57,10 @@ BEGIN_DATADESC( CSpeaker ) DEFINE_INPUTFUNC( FIELD_VOID, "TurnOff", InputTurnOff ), DEFINE_INPUTFUNC( FIELD_VOID, "Toggle", InputToggle ), +#ifdef MAPBASE + DEFINE_OUTPUT( m_OnSpeak, "OnSpeak" ), +#endif + END_DATADESC() @@ -181,6 +192,163 @@ void CSpeaker::SpeakerThink( void ) g_AIFoesTalkSemaphore.Acquire( 5, this ); } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +inline CBaseEntity *CSpeaker::GetTarget() +{ + if (!m_hTarget && m_target != NULL_STRING) + m_hTarget = gEntList.FindEntityByName(NULL, STRING(m_target), this, NULL, this); + return m_hTarget; +} + +//----------------------------------------------------------------------------- +// Purpose: Copied from CBaseEntity so we could use a !target for everything +// Input : *conceptName - +//----------------------------------------------------------------------------- +void CSpeaker::DispatchResponse( const char *conceptName ) +{ + IResponseSystem *rs = GetResponseSystem(); + if ( !rs ) + return; + + CBaseEntity *pTarget = GetTarget(); + if (!pTarget) + pTarget = this; + + // See CBaseEntity and stuff... + AI_CriteriaSet set; + set.AppendCriteria( "concept", conceptName, CONCEPT_WEIGHT ); + ModifyOrAppendCriteria( set ); + CBasePlayer *pPlayer = UTIL_GetLocalPlayer(); + if( pPlayer ) + pPlayer->ModifyOrAppendPlayerCriteria( set ); + ReAppendContextCriteria( set ); + AI_Response result; + bool found = rs->FindBestResponse( set, result ); + if ( !found ) + { + return; + } + + // Handle the response here... + char response[ 256 ]; + result.GetResponse( response, sizeof( response ) ); + if (response[0] == '$') + { + response[0] = '\0'; + DevMsg("Replacing %s with %s...\n", response, GetContextValue(response)); + Q_strncpy(response, GetContextValue(response), sizeof(response)); + PrecacheScriptSound( response ); + } + +#ifdef NEW_RESPONSE_SYSTEM + switch (result.GetType()) + { + case ResponseRules::RESPONSE_SPEAK: + { + pTarget->EmitSound( response ); + } + break; + case ResponseRules::RESPONSE_SENTENCE: + { + int sentenceIndex = SENTENCEG_Lookup( response ); + if (sentenceIndex == -1) + { + // sentence not found + break; + } + + // FIXME: Get pitch from npc? + CPASAttenuationFilter filter( pTarget ); + CBaseEntity::EmitSentenceByIndex( filter, pTarget->entindex(), CHAN_VOICE, sentenceIndex, 1, result.GetSoundLevel(), 0, PITCH_NORM ); + } + break; + case ResponseRules::RESPONSE_SCENE: + { + CBaseFlex *pFlex = NULL; + if (pTarget != this) + { + // Attempt to get flex on the target + pFlex = dynamic_cast(pTarget); + } + InstancedScriptedScene(pFlex, response); + } + break; + case ResponseRules::RESPONSE_PRINT: + { + + } + break; + case ResponseRules::RESPONSE_ENTITYIO: + { + CAI_Expresser::FireEntIOFromResponse( response, pTarget ); + break; + } +#ifdef MAPBASE_VSCRIPT + case ResponseRules::RESPONSE_VSCRIPT: + { + CAI_Expresser::RunScriptResponse( pTarget, response, &set, false ); + break; + } + case ResponseRules::RESPONSE_VSCRIPT_FILE: + { + CAI_Expresser::RunScriptResponse( pTarget, response, &set, true ); + break; + } +#endif + default: + break; + } +#else + switch ( result.GetType() ) + { + case RESPONSE_SPEAK: + { + pTarget->EmitSound( response ); + } + break; + case RESPONSE_SENTENCE: + { + int sentenceIndex = SENTENCEG_Lookup( response ); + if( sentenceIndex == -1 ) + { + // sentence not found + break; + } + + // FIXME: Get pitch from npc? + CPASAttenuationFilter filter( pTarget ); + CBaseEntity::EmitSentenceByIndex( filter, pTarget->entindex(), CHAN_VOICE, sentenceIndex, 1, result.GetSoundLevel(), 0, PITCH_NORM ); + } + break; + case RESPONSE_SCENE: + { + CBaseFlex *pFlex = NULL; + if (pTarget != this) + { + // Attempt to get flex on the target + pFlex = dynamic_cast(pTarget); + } + InstancedScriptedScene(pFlex, response); + } + break; + case RESPONSE_PRINT: + { + + } + break; + default: + break; + } +#endif + + // AllocPooledString? + m_OnSpeak.Set(MAKE_STRING(response), pTarget, this); +} +#endif + void CSpeaker::InputTurnOn( inputdata_t &inputdata ) { diff --git a/src/game/server/hl2/env_speaker.h b/src/game/server/hl2/env_speaker.h index 20fbeb6b..a28a2a47 100644 --- a/src/game/server/hl2/env_speaker.h +++ b/src/game/server/hl2/env_speaker.h @@ -36,6 +36,13 @@ protected: void SpeakerThink( void ); +#ifdef MAPBASE + EHANDLE m_hTarget; + virtual void InputSetTarget( inputdata_t &inputdata ) { BaseClass::InputSetTarget(inputdata); m_hTarget = NULL; } + CBaseEntity *GetTarget(); + virtual void DispatchResponse( const char *conceptName ); +#endif + void InputToggle( inputdata_t &inputdata ); float m_delayMin; @@ -49,6 +56,10 @@ public: void InputTurnOff( inputdata_t &inputdata ); void InputTurnOn( inputdata_t &inputdata ); + +#ifdef MAPBASE + COutputString m_OnSpeak; +#endif }; #endif // ENV_SPEAKER_H diff --git a/src/game/server/hl2/func_recharge.cpp b/src/game/server/hl2/func_recharge.cpp index 37c42637..c3bf74c1 100644 --- a/src/game/server/hl2/func_recharge.cpp +++ b/src/game/server/hl2/func_recharge.cpp @@ -35,6 +35,9 @@ public: DECLARE_CLASS( CRecharge, CBaseToggle ); void Spawn( ); +#ifdef MAPBASE + void Precache( void ); +#endif bool CreateVPhysics(); int DrawDebugTextOverlays(void); void Off(void); @@ -45,6 +48,10 @@ public: private: void InputRecharge( inputdata_t &inputdata ); +#ifdef MAPBASE + void InputSetCharge( inputdata_t &inputdata ); + void InputSetChargeNoMax( inputdata_t &inputdata ); +#endif float MaxJuice() const; void UpdateJuice( int newJuice ); @@ -56,6 +63,10 @@ private: int m_iJuice; int m_iOn; // 0 = off, 1 = startup, 2 = going float m_flSoundTime; +#ifdef MAPBASE + int m_iMaxJuice; + int m_iIncrementValue; +#endif int m_nState; @@ -70,9 +81,16 @@ BEGIN_DATADESC( CRecharge ) DEFINE_FIELD( m_flNextCharge, FIELD_TIME ), DEFINE_FIELD( m_iReactivate, FIELD_INTEGER), - DEFINE_FIELD( m_iJuice, FIELD_INTEGER), +#ifdef MAPBASE + DEFINE_KEYFIELD(m_iJuice, FIELD_INTEGER, "Charge"), +#else + DEFINE_FIELD(m_iJuice, FIELD_INTEGER), +#endif DEFINE_FIELD( m_iOn, FIELD_INTEGER), DEFINE_FIELD( m_flSoundTime, FIELD_TIME ), +#ifdef MAPBASE + DEFINE_INPUT( m_iIncrementValue, FIELD_INTEGER, "SetIncrementValue" ), +#endif DEFINE_FIELD( m_nState, FIELD_INTEGER ), // Function Pointers @@ -86,6 +104,10 @@ BEGIN_DATADESC( CRecharge ) DEFINE_OUTPUT(m_OnPlayerUse, "OnPlayerUse" ), DEFINE_INPUTFUNC( FIELD_VOID, "Recharge", InputRecharge ), +#ifdef MAPBASE + DEFINE_INPUTFUNC( FIELD_INTEGER, "SetCharge", InputSetCharge ), + DEFINE_INPUTFUNC( FIELD_FLOAT, "SetChargeNoMax", InputSetChargeNoMax ), +#endif END_DATADESC() @@ -123,13 +145,30 @@ void CRecharge::Spawn() SetModel( STRING( GetModelName() ) ); +#ifdef MAPBASE + // In case the juice was overridden + if (m_iJuice == 0) + UpdateJuice( MaxJuice() ); + else if (m_iJuice == -1) + m_iJuice = 0; +#else UpdateJuice( MaxJuice() ); +#endif m_nState = 0; CreateVPhysics(); } +#ifdef MAPBASE +void CRecharge::Precache( void ) +{ + PrecacheScriptSound( "SuitRecharge.Deny" ); + PrecacheScriptSound( "SuitRecharge.Start" ); + PrecacheScriptSound( "SuitRecharge.ChargingLoop" ); +} +#endif + bool CRecharge::CreateVPhysics() { VPhysicsInitStatic(); @@ -156,6 +195,14 @@ int CRecharge::DrawDebugTextOverlays(void) //----------------------------------------------------------------------------- float CRecharge::MaxJuice() const { +#ifdef MAPBASE + if ( m_iMaxJuice != 0 ) + { + // It must've been overridden by the mapper + return m_iMaxJuice; + } +#endif + if ( HasSpawnFlags( SF_CITADEL_RECHARGER ) ) { return sk_suitcharger_citadel.GetFloat(); @@ -199,6 +246,18 @@ void CRecharge::InputRecharge( inputdata_t &inputdata ) Recharge(); } +#ifdef MAPBASE +void CRecharge::InputSetCharge( inputdata_t &inputdata ) +{ + m_iMaxJuice = m_iJuice = inputdata.value.Int(); +} + +void CRecharge::InputSetChargeNoMax( inputdata_t &inputdata ) +{ + UpdateJuice(inputdata.value.Int()); +} +#endif + void CRecharge::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) { // if it's not a player, ignore @@ -287,6 +346,11 @@ void CRecharge::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE use } } +#ifdef MAPBASE + if (m_iIncrementValue != 0) + nIncrementArmor = m_iIncrementValue; +#endif + if (pl->ArmorValue() < nMaxArmor) { UpdateJuice( m_iJuice - nIncrementArmor ); @@ -350,6 +414,9 @@ public: private: void InputRecharge( inputdata_t &inputdata ); void InputSetCharge( inputdata_t &inputdata ); +#ifdef MAPBASE + void InputSetChargeNoMax( inputdata_t &inputdata ); +#endif float MaxJuice() const; void UpdateJuice( int newJuice ); void Precache( void ); @@ -365,6 +432,9 @@ private: int m_nState; int m_iCaps; int m_iMaxJuice; +#ifdef MAPBASE + int m_iIncrementValue; +#endif COutputFloat m_OutRemainingCharge; COutputEvent m_OnHalfEmpty; @@ -380,12 +450,21 @@ BEGIN_DATADESC( CNewRecharge ) DEFINE_FIELD( m_flNextCharge, FIELD_TIME ), DEFINE_FIELD( m_iReactivate, FIELD_INTEGER), +#ifdef MAPBASE + DEFINE_KEYFIELD( m_iJuice, FIELD_INTEGER, "Charge" ), +#else DEFINE_FIELD( m_iJuice, FIELD_INTEGER), +#endif DEFINE_FIELD( m_iOn, FIELD_INTEGER), DEFINE_FIELD( m_flSoundTime, FIELD_TIME ), DEFINE_FIELD( m_nState, FIELD_INTEGER ), DEFINE_FIELD( m_iCaps, FIELD_INTEGER ), +#ifdef MAPBASE + DEFINE_KEYFIELD( m_iMaxJuice, FIELD_INTEGER, "MaxCharge" ), + DEFINE_INPUT( m_iIncrementValue, FIELD_INTEGER, "SetIncrementValue" ), +#else DEFINE_FIELD( m_iMaxJuice, FIELD_INTEGER ), +#endif // Function Pointers DEFINE_FUNCTION( Off ), @@ -400,6 +479,9 @@ BEGIN_DATADESC( CNewRecharge ) DEFINE_INPUTFUNC( FIELD_VOID, "Recharge", InputRecharge ), DEFINE_INPUTFUNC( FIELD_INTEGER, "SetCharge", InputSetCharge ), +#ifdef MAPBASE + DEFINE_INPUTFUNC( FIELD_FLOAT, "SetChargeNoMax", InputSetChargeNoMax ), +#endif END_DATADESC() @@ -412,6 +494,10 @@ LINK_ENTITY_TO_CLASS( item_suitcharger, CNewRecharge); #define CITADEL_CHARGES_PER_SECOND 10 / CHARGE_RATE #define CALLS_PER_SECOND 7.0f * CHARGES_PER_SECOND +#ifdef MAPBASE +#define CUSTOM_CHARGES_PER_SECOND(inc) inc / CHARGE_RATE +#endif + bool CNewRecharge::KeyValue( const char *szKeyName, const char *szValue ) { @@ -436,7 +522,14 @@ bool CNewRecharge::KeyValue( const char *szKeyName, const char *szValue ) void CNewRecharge::Precache( void ) { +#ifdef MAPBASE + if ( GetModelName() == NULL_STRING ) + SetModelName( AllocPooledString(HEALTH_CHARGER_MODEL_NAME) ); + + PrecacheModel( STRING(GetModelName()) ); +#else PrecacheModel( HEALTH_CHARGER_MODEL_NAME ); +#endif PrecacheScriptSound( "SuitRecharge.Deny" ); PrecacheScriptSound( "SuitRecharge.Start" ); @@ -446,6 +539,14 @@ void CNewRecharge::Precache( void ) void CNewRecharge::SetInitialCharge( void ) { +#ifdef MAPBASE + if ( m_iMaxJuice != 0 ) + { + // It must've been overridden by the mapper + return; + } +#endif + if ( HasSpawnFlags( SF_KLEINER_RECHARGER ) ) { // The charger in Kleiner's lab. @@ -470,14 +571,31 @@ void CNewRecharge::Spawn() SetSolid( SOLID_VPHYSICS ); CreateVPhysics(); +#ifdef MAPBASE + SetModel( STRING(GetModelName()) ); +#else SetModel( HEALTH_CHARGER_MODEL_NAME ); +#endif AddEffects( EF_NOSHADOW ); ResetSequence( LookupSequence( "idle" ) ); SetInitialCharge(); +#ifdef MAPBASE + // In case the juice was overridden + if (m_iJuice == 0) + UpdateJuice( MaxJuice() ); + else if (m_iJuice == -1) + { + UpdateJuice( 0 ); + ResetSequence( LookupSequence( "empty" ) ); + } + else + UpdateJuice( m_iJuice ); +#else UpdateJuice( MaxJuice() ); +#endif m_nState = 0; m_iCaps = FCAP_CONTINUOUS_USE; @@ -579,14 +697,26 @@ void CNewRecharge::InputRecharge( inputdata_t &inputdata ) void CNewRecharge::InputSetCharge( inputdata_t &inputdata ) { - ResetSequence( LookupSequence( "idle" ) ); - int iJuice = inputdata.value.Int(); m_flJuice = m_iMaxJuice = m_iJuice = iJuice; + + ResetSequence( m_iJuice > 0 ? LookupSequence( "idle" ) : LookupSequence( "empty" ) ); StudioFrameAdvance(); } +#ifdef MAPBASE +void CNewRecharge::InputSetChargeNoMax( inputdata_t &inputdata ) +{ + m_flJuice = inputdata.value.Float(); + + UpdateJuice(m_flJuice); + + ResetSequence( m_iJuice > 0 ? LookupSequence( "idle" ) : LookupSequence( "empty" ) ); + StudioFrameAdvance(); +} +#endif + void CNewRecharge::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) { // if it's not a player, ignore @@ -606,6 +736,11 @@ void CNewRecharge::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE if ( HasSpawnFlags( SF_CITADEL_RECHARGER ) ) flCharges = CITADEL_CHARGES_PER_SECOND; +#ifdef MAPBASE + if ( m_iIncrementValue != 0 ) + flCharges = CUSTOM_CHARGES_PER_SECOND(m_iIncrementValue); +#endif + m_flJuice -= flCharges / flCalls; StudioFrameAdvance(); } @@ -667,6 +802,11 @@ void CNewRecharge::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE } } +#ifdef MAPBASE + if (m_iIncrementValue != 0) + nIncrementArmor = m_iIncrementValue; +#endif + // If we're over our limit, debounce our keys if ( pPlayer->ArmorValue() >= nMaxArmor) { diff --git a/src/game/server/hl2/func_tank.cpp b/src/game/server/hl2/func_tank.cpp index a990d129..9d7960c2 100644 --- a/src/game/server/hl2/func_tank.cpp +++ b/src/game/server/hl2/func_tank.cpp @@ -39,6 +39,10 @@ #include "particle_parse.h" // NVNT turret recoil #include "haptics/haptic_utils.h" +#ifdef MAPBASE +#include "shot_manipulator.h" +#include "filters.h" +#endif #ifdef HL2_DLL #include "hl2_player.h" @@ -49,6 +53,10 @@ extern Vector PointOnLineNearestPoint(const Vector& vStartPos, const Vector& vEndPos, const Vector& vPoint); +#ifdef MAPBASE +extern ConVar ai_debug_shoot_positions; +#endif + ConVar mortar_visualize("mortar_visualize", "0" ); BEGIN_DATADESC( CFuncTank ) @@ -70,13 +78,19 @@ BEGIN_DATADESC( CFuncTank ) DEFINE_KEYFIELD( m_spriteScale, FIELD_FLOAT, "spritescale" ), DEFINE_KEYFIELD( m_iszSpriteSmoke, FIELD_STRING, "spritesmoke" ), DEFINE_KEYFIELD( m_iszSpriteFlash, FIELD_STRING, "spriteflash" ), +#ifdef MAPBASE + DEFINE_KEYFIELD( m_iszShootSound, FIELD_SOUNDNAME, "shootsound" ), +#endif +#ifndef AMMOTYPE_MOVED DEFINE_KEYFIELD( m_bulletType, FIELD_INTEGER, "bullet" ), +#endif DEFINE_FIELD( m_nBulletCount, FIELD_INTEGER ), DEFINE_KEYFIELD( m_spread, FIELD_INTEGER, "firespread" ), DEFINE_KEYFIELD( m_iBulletDamage, FIELD_INTEGER, "bullet_damage" ), DEFINE_KEYFIELD( m_iBulletDamageVsPlayer, FIELD_INTEGER, "bullet_damage_vs_player" ), DEFINE_KEYFIELD( m_iszMaster, FIELD_STRING, "master" ), +#ifndef AMMOTYPE_MOVED #ifdef HL2_EPISODIC DEFINE_KEYFIELD( m_iszAmmoType, FIELD_STRING, "ammotype" ), DEFINE_FIELD( m_iAmmoType, FIELD_INTEGER ), @@ -85,6 +99,7 @@ BEGIN_DATADESC( CFuncTank ) DEFINE_FIELD( m_iMediumAmmoType, FIELD_INTEGER ), DEFINE_FIELD( m_iLargeAmmoType, FIELD_INTEGER ), #endif // HL2_EPISODIC +#endif // AMMOTYPE_MOVED DEFINE_KEYFIELD( m_soundStartRotate, FIELD_SOUNDNAME, "rotatestartsound" ), DEFINE_KEYFIELD( m_soundStopRotate, FIELD_SOUNDNAME, "rotatestopsound" ), @@ -115,7 +130,11 @@ BEGIN_DATADESC( CFuncTank ) DEFINE_FIELD( m_hControlVolume, FIELD_EHANDLE ), DEFINE_KEYFIELD( m_iszControlVolume, FIELD_STRING, "control_volume" ), DEFINE_FIELD( m_flNextControllerSearch, FIELD_TIME ), +#ifdef MAPBASE + DEFINE_KEYFIELD( m_bShouldFindNPCs, FIELD_BOOLEAN, "ShouldFindNPCs" ), +#else DEFINE_FIELD( m_bShouldFindNPCs, FIELD_BOOLEAN ), +#endif DEFINE_FIELD( m_bNPCInRoute, FIELD_BOOLEAN ), DEFINE_KEYFIELD( m_iszNPCManPoint, FIELD_STRING, "npc_man_point" ), DEFINE_FIELD( m_bReadyToFire, FIELD_BOOLEAN ), @@ -140,6 +159,16 @@ BEGIN_DATADESC( CFuncTank ) DEFINE_KEYFIELD( m_iEffectHandling, FIELD_INTEGER, "effecthandling" ), +#ifdef MAPBASE + DEFINE_KEYFIELD( m_bDontHitController, FIELD_BOOLEAN, "DontHitController" ), + DEFINE_KEYFIELD( m_bControllerGlued, FIELD_BOOLEAN, "ControllerGlued" ), + + DEFINE_KEYFIELD( m_iszTraceFilter, FIELD_STRING, "TraceFilter" ), + DEFINE_FIELD( m_hTraceFilter, FIELD_EHANDLE ), + + DEFINE_KEYFIELD( m_flPlayerBBoxDist, FIELD_FLOAT, "PlayerBBoxDist" ), +#endif + // Inputs DEFINE_INPUTFUNC( FIELD_VOID, "Activate", InputActivate ), DEFINE_INPUTFUNC( FIELD_VOID, "Deactivate", InputDeactivate ), @@ -151,6 +180,10 @@ BEGIN_DATADESC( CFuncTank ) DEFINE_INPUTFUNC( FIELD_EHANDLE, "SetTargetEntity", InputSetTargetEntity ), DEFINE_INPUTFUNC( FIELD_VOID, "ClearTargetEntity", InputClearTargetEntity ), DEFINE_INPUTFUNC( FIELD_STRING, "FindNPCToManTank", InputFindNPCToManTank ), +#ifdef MAPBASE + DEFINE_INPUTFUNC( FIELD_STRING, "TeleportNPCToManTank", InputTeleportNPCToManTank ), + DEFINE_INPUTFUNC( FIELD_STRING, "ForceNPCToManTank", InputForceNPCToManTank ), +#endif DEFINE_INPUTFUNC( FIELD_VOID, "StopFindingNPCs", InputStopFindingNPCs ), DEFINE_INPUTFUNC( FIELD_VOID, "StartFindingNPCs", InputStartFindingNPCs ), DEFINE_INPUTFUNC( FIELD_VOID, "ForceNPCOff", InputForceNPCOff ), @@ -178,6 +211,10 @@ CFuncTank::CFuncTank() m_bNPCInRoute = false; m_flNextControllerSearch = 0; m_bShouldFindNPCs = true; + +#ifdef MAPBASE + m_flPlayerBBoxDist = 24; +#endif } //----------------------------------------------------------------------------- @@ -353,7 +390,11 @@ void CFuncTank::InputFindNPCToManTank( inputdata_t &inputdata ) return; // NPC assigned to man the func_tank? +#ifdef MAPBASE + CBaseEntity *pEntity = gEntList.FindEntityByNameNearest( inputdata.value.String(), GetAbsOrigin(), 0, this, inputdata.pActivator, inputdata.pCaller ); +#else CBaseEntity *pEntity = gEntList.FindEntityByName( NULL, inputdata.value.StringID() ); +#endif if ( pEntity ) { CAI_BaseNPC *pNPC = pEntity->MyNPCPointer(); @@ -361,7 +402,11 @@ void CFuncTank::InputFindNPCToManTank( inputdata_t &inputdata ) { // Verify the npc has the func_tank controller behavior. CAI_FuncTankBehavior *pBehavior; +#ifdef MAPBASE + if ( pNPC->GetBehavior( &pBehavior ) && pBehavior->CanManTank( this, true ) ) +#else if ( pNPC->GetBehavior( &pBehavior ) ) +#endif { m_hController = pNPC; pBehavior->SetFuncTank( this ); @@ -375,6 +420,124 @@ void CFuncTank::InputFindNPCToManTank( inputdata_t &inputdata ) NPC_FindController(); } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: Input handler for telling the func_tank to teleport an NPC to man it. +//----------------------------------------------------------------------------- +void CFuncTank::InputTeleportNPCToManTank( inputdata_t &inputdata ) +{ + // Verify the func_tank is controllable and available. + if ( !IsNPCControllable() && !IsNPCSetController() ) + return; + + // If we have a controller already - don't look for one. + if ( HasController() ) + return; + + // NPC assigned to man the func_tank? + CBaseEntity *pEntity = gEntList.FindEntityByNameNearest( inputdata.value.String(), GetAbsOrigin(), 0, this, inputdata.pActivator, inputdata.pCaller ); + if ( pEntity ) + { + CAI_BaseNPC *pNPC = pEntity->MyNPCPointer(); + if ( pNPC ) + { + // Verify the npc has the func_tank controller behavior. + CAI_FuncTankBehavior *pBehavior; + if ( pNPC->GetBehavior( &pBehavior ) && pBehavior->CanManTank( this, true ) ) + { + Vector vecVec; + QAngle angAng; + Vector vecVel = vec3_origin; + if ( m_iszNPCManPoint != NULL_STRING ) + { + CBaseEntity *pEntity = gEntList.FindEntityByName( NULL, m_iszNPCManPoint, this ); + if ( pEntity ) + { + vecVec = pEntity->GetAbsOrigin(); + } + } + + angAng = pNPC->GetAbsAngles(); + angAng.y = UTIL_VecToYaw ( GetAbsOrigin() - vecVec ); // Yaw from man point to turret + + pNPC->Teleport(&vecVec, &angAng, &vecVel); + + m_hController = pNPC; + pBehavior->SetFuncTank( this ); + NPC_SetInRoute( true ); + +#if 1 + pNPC->GetMotor()->SetIdealYawToTarget( GetAbsOrigin() ); + pNPC->SetTurnActivity(); + + pNPC->DoHolster(); + + pNPC->SpeakSentence( FUNCTANK_SENTENCE_JUST_MOUNTED ); + + // We are at the correct position and facing for the func_tank, mount it. + StartControl( pNPC ); + pNPC->ClearEnemyMemory(); + pBehavior->SetMounted(true); + + pNPC->SetIdealActivity( ACT_IDLE_MANNEDGUN ); +#endif + + return; + } + } + } + else + { + Warning("%s unable to find NPC \"%s\" to teleport to tank\n", GetDebugName(), inputdata.value.String()); + } + + // NPC_FindController() doesn't return a NPC and teleporting a random NPC seems kind of dangerous anyway. +} + +//----------------------------------------------------------------------------- +// Purpose: Input handler for telling the func_tank to find a NPC and instantly make them man it, no matter what they're doing or where they are. +//----------------------------------------------------------------------------- +void CFuncTank::InputForceNPCToManTank( inputdata_t &inputdata ) +{ + // Verify the func_tank is controllable and available. + if ( !IsNPCControllable() && !IsNPCSetController() ) + return; + + // If we have a controller already - don't look for one. + if ( HasController() ) + return; + + // NPC assigned to man the func_tank? + CBaseEntity *pEntity = gEntList.FindEntityByNameNearest( inputdata.value.String(), GetAbsOrigin(), 0, this, inputdata.pActivator, inputdata.pCaller ); + if ( pEntity ) + { + CAI_BaseNPC *pNPC = pEntity->MyNPCPointer(); + if ( pNPC ) + { + // Verify the npc has the func_tank controller behavior. + CAI_FuncTankBehavior *pBehavior; + if ( pNPC->GetBehavior( &pBehavior ) && pBehavior->CanManTank( this, true ) ) + { + // Set the forced condition + pBehavior->SetCondition( CAI_FuncTankBehavior::COND_FUNCTANK_FORCED ); + + m_hController = pNPC; + pBehavior->SetFuncTank( this ); + NPC_SetInRoute( true ); + + return; + } + } + } + else + { + Warning("%s unable to find NPC \"%s\" to force to tank\n", GetDebugName(), inputdata.value.String()); + } + + // NPC_FindController() doesn't return a NPC and teleporting a random NPC seems kind of dangerous anyway. +} +#endif + //----------------------------------------------------------------------------- // Purpose: // Input : &inputdata - @@ -468,7 +631,11 @@ void CFuncTank::NPC_FindController( void ) continue; CAI_FuncTankBehavior *pBehavior; +#ifdef MAPBASE + if ( pNPC->GetBehavior( &pBehavior ) && pBehavior->CanManTank( this, false ) ) +#else if ( pNPC->GetBehavior( &pBehavior ) ) +#endif { // Don't mount the func_tank if your "enemy" is within X feet or it or the npc. CBaseEntity *pEnemy = pNPC->GetEnemy(); @@ -735,13 +902,16 @@ void CFuncTank::Spawn( void ) { Precache(); +#ifndef AMMOTYPE_MOVED #ifdef HL2_EPISODIC - m_iAmmoType = GetAmmoDef()->Index( STRING( m_iszAmmoType ) ); + m_iAmmoType = GetAmmoDef()->Index(STRING(m_iszAmmoType)); #else - m_iSmallAmmoType = GetAmmoDef()->Index("Pistol"); - m_iMediumAmmoType = GetAmmoDef()->Index("SMG1"); - m_iLargeAmmoType = GetAmmoDef()->Index("AR2"); -#endif // HL2_EPISODIC + m_iSmallAmmoType = GetAmmoDef()->Index("Pistol"); + m_iMediumAmmoType = GetAmmoDef()->Index("SMG1"); + m_iLargeAmmoType = GetAmmoDef()->Index("AR2"); +#endif // HL2_EPISODIC +#endif // AMMOTYPE_MOVED + SetMoveType( MOVETYPE_PUSH ); // so it doesn't get pushed by anything SetSolid( SOLID_VPHYSICS ); @@ -878,6 +1048,15 @@ void CFuncTank::Activate( void ) m_nBarrelAttachment = pAnim->LookupAttachment( STRING(m_iszBarrelAttachment) ); } } + +#ifdef MAPBASE + if ( m_iszTraceFilter != NULL_STRING ) + { + m_hTraceFilter = dynamic_cast(gEntList.FindEntityByName( NULL, STRING(m_iszTraceFilter) )); + if (!m_hTraceFilter) + Warning("WARNING: %s trace filter %s is not a filter!\n", GetDebugName(), STRING(m_iszTraceFilter)); + } +#endif } bool CFuncTank::CreateVPhysics() @@ -893,6 +1072,10 @@ void CFuncTank::Precache( void ) PrecacheModel( STRING(m_iszSpriteSmoke) ); if ( m_iszSpriteFlash != NULL_STRING ) PrecacheModel( STRING(m_iszSpriteFlash) ); +#ifdef MAPBASE + if ( m_iszShootSound != NULL_STRING ) + PrecacheScriptSound( STRING(m_iszShootSound) ); +#endif if ( m_soundStartRotate != NULL_STRING ) PrecacheScriptSound( STRING(m_soundStartRotate) ); @@ -1046,6 +1229,16 @@ bool CFuncTank::StartControl( CBaseCombatCharacter *pController ) SetNextThink( gpGlobals->curtime + 0.1f ); // Let the map maker know a controller has been found +#ifdef MAPBASE + if ( m_hController->IsPlayer() ) + { + m_OnGotPlayerController.FireOutput( m_hController, this ); + } + else + { + m_OnGotController.FireOutput( m_hController, this ); + } +#else if ( m_hController->IsPlayer() ) { m_OnGotPlayerController.FireOutput( this, this ); @@ -1054,6 +1247,7 @@ bool CFuncTank::StartControl( CBaseCombatCharacter *pController ) { m_OnGotController.FireOutput( this, this ); } +#endif OnStartControlled(); return true; @@ -1071,8 +1265,13 @@ void CFuncTank::StopControl() OnStopControlled(); +#ifdef MAPBASE + // Arm player/npc weapon if they're not in a vehicle. + if ( !m_hController->IsInAVehicle() && m_hController->GetActiveWeapon() ) +#else // Arm player/npc weapon. if ( m_hController->GetActiveWeapon() ) +#endif { m_hController->GetActiveWeapon()->Deploy(); } @@ -1087,6 +1286,16 @@ void CFuncTank::StopControl() SetNextThink( TICK_NEVER_THINK ); // Let the map maker know a controller has been lost. +#ifdef MAPBASE + if ( m_hController->IsPlayer() ) + { + m_OnLostPlayerController.FireOutput( m_hController, this ); + } + else + { + m_OnLostController.FireOutput( m_hController, this ); + } +#else if ( m_hController->IsPlayer() ) { m_OnLostPlayerController.FireOutput( this, this ); @@ -1095,6 +1304,7 @@ void CFuncTank::StopControl() { m_OnLostController.FireOutput( this, this ); } +#endif // Reset the func_tank as unmanned (player/npc). if ( m_hController->IsPlayer() ) @@ -1161,7 +1371,12 @@ void CFuncTank::ControllerPostFrame( void ) Vector start = WorldBarrelPosition(); Vector dir = forward; +#ifdef MAPBASE + CTraceFilterSimple traceFilter = GetTraceFilter(); + UTIL_TraceHull( start, start + forward * 8192, -Vector(8,8,8), Vector(8,8,8), MASK_SHOT, &traceFilter, &tr ); +#else UTIL_TraceHull( start, start + forward * 8192, -Vector(8,8,8), Vector(8,8,8), MASK_SHOT, this, COLLISION_GROUP_NONE, &tr ); +#endif if( tr.m_pEnt && tr.m_pEnt->m_takedamage != DAMAGE_NO && (tr.m_pEnt->GetFlags() & FL_AIMTARGET) ) { @@ -1189,6 +1404,9 @@ void CFuncTank::ControllerPostFrame( void ) if( --m_iAmmoCount == 0 ) { // Kick the player off the gun, and make myself not usable. +#ifdef MAPBASE + m_OnAmmoDepleted.FireOutput(pPlayer, this); +#endif m_spawnflags &= ~SF_TANK_CANCONTROL; StopControl(); return; @@ -1335,6 +1553,11 @@ void CFuncTank::NPC_Fire( void ) { SetNextAttack( gpGlobals->curtime + m_fireTime ); } + +#ifdef MAPBASE + // This is now needed in some cases + m_fireLast = gpGlobals->curtime; +#endif } @@ -1660,6 +1883,15 @@ QAngle CFuncTank::AimBarrelAt( const Vector &parentTarget ) { return GetLocalAngles(); } +#ifdef MAPBASE + else if ( m_barrelPos.LengthSqr() == 0.0f ) + { + // Do a simpler calculation that doesn't take barrel into account + float targetToCenterYaw = atan2( target.y, target.x ); + float targetToCenterPitch = atan2( target.z, sqrt( quadTargetXY ) ); + return QAngle( -RAD2DEG( targetToCenterPitch ), RAD2DEG( targetToCenterYaw ), 0 ); + } +#endif else { // We're trying to aim the offset barrel at an arbitrary point. @@ -1706,8 +1938,14 @@ void CFuncTank::CalcPlayerCrosshairTarget( Vector *pVecTarget ) vecDir = pPlayer->GetAutoaimVector( AUTOAIM_SCALE_DEFAULT ); } +#ifdef MAPBASE + CTraceFilterSimple traceFilter = GetTraceFilter(); + UTIL_TraceLine( vecStart + vecDir * m_flPlayerBBoxDist, vecStart + vecDir * 8192, MASK_BLOCKLOS_AND_NPCS, &traceFilter, &tr ); + //DebugDrawLine(tr.startpos, tr.endpos, 222, 222, 0, false, 0.1); +#else // Make sure to start the trace outside of the player's bbox! UTIL_TraceLine( vecStart + vecDir * 24, vecStart + vecDir * 8192, MASK_BLOCKLOS_AND_NPCS, this, COLLISION_GROUP_NONE, &tr ); +#endif *pVecTarget = tr.endpos; } @@ -1822,11 +2060,15 @@ bool CFuncTank::RotateTankToAngles( const QAngle &angles, float *pDistX, float * //----------------------------------------------------------------------------- // We lost our target! //----------------------------------------------------------------------------- -void CFuncTank::LostTarget( void ) +void CFuncTank::LostTarget( CBaseEntity *pTarget ) { if (m_fireLast != 0) { +#ifdef MAPBASE + m_OnLoseTarget.Set(pTarget, pTarget, this); +#else m_OnLoseTarget.FireOutput(this, this); +#endif m_fireLast = 0; } } @@ -1929,7 +2171,11 @@ void CFuncTank::AimFuncTankAtTarget( void ) m_hTarget = FindTarget( m_targetEntityName, NULL ); } +#ifdef MAPBASE + LostTarget(pEntity); +#else LostTarget(); +#endif return; } @@ -1953,8 +2199,13 @@ void CFuncTank::AimFuncTankAtTarget( void ) { if ( m_hTarget ) { +#ifdef MAPBASE + LostTarget(m_hTarget); + m_hTarget = NULL; +#else m_hTarget = NULL; LostTarget(); +#endif } return; } @@ -2053,18 +2304,30 @@ void CFuncTank::AimFuncTankAtTarget( void ) { if (m_fireLast == 0) { +#ifdef MAPBASE + m_OnAquireTarget.Set(pTarget, pTarget, this); +#else m_OnAquireTarget.FireOutput(this, this); +#endif } FiringSequence( barrelEnd, forward, this ); } else { +#ifdef MAPBASE + LostTarget(pTarget); +#else LostTarget(); +#endif } } else { +#ifdef MAPBASE + LostTarget(pTarget); +#else LostTarget(); +#endif } } @@ -2203,6 +2466,55 @@ const char *CFuncTank::GetTracerType( void ) //----------------------------------------------------------------------------- void CFuncTank::Fire( int bulletCount, const Vector &barrelEnd, const Vector &forward, CBaseEntity *pAttacker, bool bIgnoreSpread ) { +#ifdef MAPBASE + bool bSpriteSmoke = m_iszSpriteSmoke != NULL_STRING; + bool bSpriteFlash = m_iszSpriteFlash != NULL_STRING; + + if (bSpriteSmoke || bSpriteFlash) + { + if (bSpriteSmoke) + { + CSprite *pSmoke = CSprite::SpriteCreate( STRING(m_iszSpriteSmoke), barrelEnd, TRUE ); + pSmoke->AnimateAndDie( random->RandomFloat( 15.0, 20.0 ) ); + pSmoke->SetTransparency( kRenderTransAlpha, m_clrRender->r, m_clrRender->g, m_clrRender->b, 255, kRenderFxNone ); + + Vector vecVelocity( 0, 0, random->RandomFloat(40, 80) ); + pSmoke->SetAbsVelocity( vecVelocity ); + pSmoke->SetScale( m_spriteScale ); + } + + if (bSpriteFlash) + { + CSprite *pFlash = CSprite::SpriteCreate( STRING(m_iszSpriteFlash), barrelEnd, TRUE ); + pFlash->AnimateAndDie( 5 ); + pFlash->SetTransparency( kRenderTransAdd, 255, 255, 255, 255, kRenderFxNoDissipation ); + pFlash->SetScale( m_spriteScale ); + } + } + else if ( m_iEffectHandling == EH_AR2 ) + { + DoMuzzleFlash(); + } + else if ( m_iEffectHandling == EH_COMBINE_CANNON ) + { + DoMuzzleFlash(); + } + + if (m_iszShootSound != NULL_STRING) + { + EmitSound(STRING(m_iszShootSound)); + } + else if ( m_iEffectHandling == EH_AR2 ) + { + // Play the AR2 sound + EmitSound( "Weapon_functank.Single" ); + } + else if ( m_iEffectHandling == EH_COMBINE_CANNON ) + { + // Play the cannon sound + EmitSound( "NPC_Combine_Cannon.FireBullet" ); + } +#else // If we have a specific effect handler, apply it's effects if ( m_iEffectHandling == EH_AR2 ) { @@ -2238,6 +2550,7 @@ void CFuncTank::Fire( int bulletCount, const Vector &barrelEnd, const Vector &fo pSprite->SetScale( m_spriteScale ); } } +#endif if( pAttacker && pAttacker->IsPlayer() ) { @@ -2252,7 +2565,11 @@ void CFuncTank::Fire( int bulletCount, const Vector &barrelEnd, const Vector &fo } +#ifdef MAPBASE + m_OnFire.FireOutput(pAttacker, this); +#else m_OnFire.FireOutput(this, this); +#endif m_bReadyToFire = false; } @@ -2384,6 +2701,84 @@ bool CFuncTank::IsEntityInViewCone( CBaseEntity *pEntity ) return true; } +#ifdef MAPBASE +//========================================================= +// I decided to make a custom trace filter for func_tank trace filters. +// If the base class thinks the trace should hit the entity, it goes through +// a filter and if it passes the filter, the trace passes the entity instead. +// +// This is different from CTraceFilterEntityFilter because it's simplified and can use its own exclusion list. +// (it also came before it) +//========================================================= +class CTankTraceFilter : public CTraceFilterSimpleList +{ +public: + CTankTraceFilter( int collisionGroup ) : CTraceFilterSimpleList( collisionGroup ) {} + + bool ShouldHitEntity( IHandleEntity *pHandleEntity, int contentsMask ) + { + bool base = CTraceFilterSimpleList::ShouldHitEntity( pHandleEntity, contentsMask ); + + // Our base is telling us to hit. If it passes the filter, don't. + if ( base && m_pFilter ) + { + CBaseEntity *pEntity = EntityFromEntityHandle( pHandleEntity ); + return !m_pFilter->PassesFilter(m_pCaller, pEntity); + + // TODO: Should we use this code from CBulletsTraceFilter? + /* + CBaseEntity *pEntity = EntityFromEntityHandle( pHandleEntity ); + CBaseEntity *pPassEntity = EntityFromEntityHandle( m_PassEntities[0] ); + if ( pEntity && pPassEntity && pEntity->GetOwnerEntity() == pPassEntity && + pPassEntity->IsSolidFlagSet(FSOLID_NOT_SOLID) && pPassEntity->IsSolidFlagSet( FSOLID_CUSTOMBOXTEST ) && + pPassEntity->IsSolidFlagSet( FSOLID_CUSTOMRAYTEST ) ) + { + // It's a bone follower of the entity to ignore (toml 8/3/2007) + return false; + } + */ + } + + return base; + } + + CBaseFilter *m_pFilter; + CBaseEntity *m_pCaller; + +}; + +//----------------------------------------------------------------------------- +// Purpose: Gets our general trace filter we use for LOS, trace locations, etc. +// This was created so we could easily change the trace filter func_tank generally uses, +// but it can be overridden by derived classes, so there's that. +//----------------------------------------------------------------------------- +CTraceFilterSimple CFuncTank::GetTraceFilter() +{ + //CTraceFilterSkipTwoEntities traceFilter( this, GetParent(), COLLISION_GROUP_NONE ); + + CTankTraceFilter traceFilter( COLLISION_GROUP_NONE ); + traceFilter.SetPassEntity(this); + + if (GetParent()) + { + CBaseEntity *pParent = GetParent(); + traceFilter.AddEntityToIgnore(pParent); + + // Add the parent's parent too. (for func_tanks mounted on moving things, like vehicles) + if (pParent->GetParent()) + traceFilter.AddEntityToIgnore(pParent->GetParent()); + } + + if (m_bDontHitController) + traceFilter.AddEntityToIgnore(GetController()); + + traceFilter.m_pFilter = m_hTraceFilter.Get(); + traceFilter.m_pCaller = this; + + return traceFilter; +} +#endif + //----------------------------------------------------------------------------- // Purpose: Return true if this func tank can see the enemy //----------------------------------------------------------------------------- @@ -2398,7 +2793,11 @@ bool CFuncTank::HasLOSTo( CBaseEntity *pEntity ) trace_t tr; // Ignore the func_tank and any prop it's parented to +#ifdef MAPBASE + CTraceFilterSimple traceFilter = GetTraceFilter(); +#else CTraceFilterSkipTwoEntities traceFilter( this, GetParent(), COLLISION_GROUP_NONE ); +#endif // UNDONE: Should this hit BLOCKLOS brushes? AI_TraceLine( vecBarrelEnd, vecTarget, MASK_BLOCKLOS_AND_NPCS, &traceFilter, &tr ); @@ -2428,10 +2827,76 @@ class CFuncTankGun : public CFuncTank public: DECLARE_CLASS( CFuncTankGun, CFuncTank ); +#ifdef AMMOTYPE_MOVED + DECLARE_DATADESC(); + + string_t m_iszAmmoType; // The name of the ammodef that we use when we fire. Bullet damage still comes from keyvalues. + int m_iAmmoType; // The cached index of the ammodef that we use when we fire. +#endif // !AMMOTYPE_MOVED + +#ifdef AMMOTYPE_MOVED + void Spawn( void ); + bool KeyValue( const char *szKeyName, const char *szValue ); +#endif // AMMOTYPE_MOVED + + void Fire( int bulletCount, const Vector &barrelEnd, const Vector &forward, CBaseEntity *pAttacker, bool bIgnoreSpread ); }; + +#ifdef AMMOTYPE_MOVED +BEGIN_DATADESC(CFuncTankGun) + + DEFINE_KEYFIELD( m_iszAmmoType, FIELD_STRING, "ammotype" ), + DEFINE_FIELD( m_iAmmoType, FIELD_INTEGER ), + +END_DATADESC() +#endif // AMMOTYPE_MOVED + LINK_ENTITY_TO_CLASS( func_tank, CFuncTankGun ); +#ifdef AMMOTYPE_MOVED +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CFuncTankGun::Spawn( void ) +{ + if (m_iszAmmoType != NULL_STRING) + m_iAmmoType = GetAmmoDef()->Index(STRING(m_iszAmmoType)); + + BaseClass::Spawn(); +} + +//----------------------------------------------------------------------------- +// Purpose: Caches entity key values until spawn is called. +// Input : szKeyName - +// szValue - +// Output : +//----------------------------------------------------------------------------- +bool CFuncTankGun::KeyValue( const char *szKeyName, const char *szValue ) +{ + if (FStrEq(szKeyName, "bullet")) + { + switch (atoi(szValue)) + { + case TANK_BULLET_SMALL: + m_iAmmoType = GetAmmoDef()->Index("Pistol"); + break; + case TANK_BULLET_MEDIUM: + m_iAmmoType = GetAmmoDef()->Index("SMG1"); + break; + case TANK_BULLET_LARGE: + m_iAmmoType = GetAmmoDef()->Index("AR2"); + break; + default: + m_iAmmoType = -1; + } + return true; + } + + return BaseClass::KeyValue( szKeyName, szValue ); +} +#endif // AMMOTYPE_MOVED + //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- @@ -2459,7 +2924,27 @@ void CFuncTankGun::Fire( int bulletCount, const Vector &barrelEnd, const Vector info.m_pAttacker = pAttacker; info.m_pAdditionalIgnoreEnt = GetParent(); -#ifdef HL2_EPISODIC +#ifdef MAPBASE + CUtlVector ignorelist; + + if (pAttacker) + { + if (m_bDontHitController) + { + ignorelist.AddToTail(pAttacker); + } + + // Ignore any vehicle our controller is in + if (pAttacker->MyCombatCharacterPointer() && pAttacker->MyCombatCharacterPointer()->IsInAVehicle()) + { + ignorelist.AddToTail(pAttacker->MyCombatCharacterPointer()->GetVehicleEntity()); + } + } + + info.m_pIgnoreEntList = &ignorelist; +#endif + +#if defined(HL2_EPISODIC) || defined(AMMOTYPE_MOVED) if ( m_iAmmoType != -1 ) { for ( i = 0; i < bulletCount; i++ ) @@ -2515,7 +3000,11 @@ public: color32 m_flPulseColor; float m_flPulseLife; float m_flPulseLag; +#ifdef MAPBASE + #define m_sPulseFireSound m_iszShootSound +#else string_t m_sPulseFireSound; +#endif }; LINK_ENTITY_TO_CLASS( func_tankpulselaser, CFuncTankPulseLaser ); @@ -2695,6 +3184,82 @@ void CFuncTankLaser::Fire( int bulletCount, const Vector &barrelEnd, const Vecto } } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Missile for func_tankrocket so kills are credited properly and don't kill friendlies +//----------------------------------------------------------------------------- +class CFuncTankMissile : public CMissile +{ + DECLARE_CLASS( CFuncTankMissile, CMissile ); + DECLARE_DATADESC(); + +public: + virtual void Spawn( void ); + + EHANDLE m_hTurret; + +private: + void FTnkMissileTouch( CBaseEntity *pOther ); +}; + +BEGIN_DATADESC( CFuncTankMissile ) + + DEFINE_FIELD( m_hTurret, FIELD_EHANDLE ), + + // Function Pointers + DEFINE_FUNCTION( FTnkMissileTouch ), + +END_DATADESC() + +LINK_ENTITY_TO_CLASS( func_tankrocket_missile, CFuncTankMissile ); + +void CFuncTankMissile::Spawn( void ) +{ + Precache(); + + SetSolid( SOLID_BBOX ); + SetModel("models/weapons/w_missile_launch.mdl"); + UTIL_SetSize( this, -Vector(4,4,4), Vector(4,4,4) ); + + SetTouch( &CFuncTankMissile::FTnkMissileTouch ); + + SetMoveType( MOVETYPE_FLYGRAVITY, MOVECOLLIDE_FLY_BOUNCE ); + SetThink( &CMissile::IgniteThink ); + + SetNextThink( gpGlobals->curtime + 0.3f ); + + m_takedamage = DAMAGE_YES; + m_iHealth = m_iMaxHealth = 100; + m_bloodColor = DONT_BLEED; + + AddFlag( FL_OBJECT ); +} + +//----------------------------------------------------------------------------- +// The actual explosion +//----------------------------------------------------------------------------- +void CFuncTankMissile::FTnkMissileTouch( CBaseEntity *pOther ) +{ + Assert( pOther ); + + // Don't touch triggers (but DO hit weapons) + if ( pOther->IsSolidFlagSet(FSOLID_TRIGGER|FSOLID_VOLUME_CONTENTS) && pOther->GetCollisionGroup() != COLLISION_GROUP_WEAPON ) + { + // Some NPCs are triggers that can take damage (like antlion grubs). We should hit them. + if ( ( pOther->m_takedamage == DAMAGE_NO ) || ( pOther->m_takedamage == DAMAGE_EVENTS_ONLY ) ) + return; + } + + // Do not touch the turret we fired from + if (pOther == m_hTurret.Get()) + { + return; + } + + Explode(); +} +#endif + class CFuncTankRocket : public CFuncTank { public: @@ -2720,13 +3285,23 @@ LINK_ENTITY_TO_CLASS( func_tankrocket, CFuncTankRocket ); void CFuncTankRocket::Precache( void ) { +#ifdef MAPBASE + UTIL_PrecacheOther( "func_tankrocket_missile" ); +#else UTIL_PrecacheOther( "rpg_missile" ); +#endif CFuncTank::Precache(); } void CFuncTankRocket::Fire( int bulletCount, const Vector &barrelEnd, const Vector &forward, CBaseEntity *pAttacker, bool bIgnoreSpread ) { +#ifdef MAPBASE + CFuncTankMissile *pRocket = (CFuncTankMissile*)CBaseEntity::Create( "func_tankrocket_missile", barrelEnd, GetAbsAngles(), GetController() ); + pRocket->AddEffects( EF_NOSHADOW ); + pRocket->m_hTurret.Set(this); +#else CMissile *pRocket = (CMissile *) CBaseEntity::Create( "rpg_missile", barrelEnd, GetAbsAngles(), this ); +#endif pRocket->DumbFire(); pRocket->SetNextThink( gpGlobals->curtime + 0.1f ); @@ -2743,6 +3318,9 @@ void CFuncTankRocket::Fire( int bulletCount, const Vector &barrelEnd, const Vect CFuncTank::Fire( bulletCount, barrelEnd, forward, this, bIgnoreSpread ); } +#ifdef MAPBASE +static const char *s_pAirboatGunThinkContext = "AirboatGunThinkContext"; +#endif //----------------------------------------------------------------------------- // Airboat gun @@ -2753,15 +3331,32 @@ public: DECLARE_CLASS( CFuncTankAirboatGun, CFuncTank ); DECLARE_DATADESC(); +#ifdef MAPBASE + CFuncTankAirboatGun() + { + // -1 = original behavior + m_spread = -1; + } +#endif + void Precache( void ); virtual void Spawn(); virtual void Activate(); virtual void Fire( int bulletCount, const Vector &barrelEnd, const Vector &forward, CBaseEntity *pAttacker, bool bIgnoreSpread ); virtual void ControllerPostFrame(); +#ifdef MAPBASE + virtual void FuncTankAirboatGunThink(); + virtual void TankActivate(void); + virtual void TankDeactivate(void); + virtual void OnStartControlled(); +#endif virtual void OnStopControlled(); virtual const char *GetTracerType( void ); virtual Vector WorldBarrelPosition( void ); virtual void DoImpactEffect( trace_t &tr, int nDamageType ); +#ifdef MAPBASE + virtual void StopLoopingSounds() { DestroySounds(); BaseClass::StopLoopingSounds(); } +#endif private: void CreateSounds(); @@ -2778,6 +3373,12 @@ private: CHandle m_hAirboatGunModel; int m_nGunBarrelAttachment; float m_flLastImpactEffectTime; + +#ifdef MAPBASE + float m_flHeavyShotInterval = 0.2f; + int m_iHeavyShotSpread; + bool m_bUseDamageKV; +#endif }; @@ -2793,6 +3394,15 @@ BEGIN_DATADESC( CFuncTankAirboatGun ) // DEFINE_FIELD( m_hAirboatGunModel, FIELD_EHANDLE ), // DEFINE_FIELD( m_nGunBarrelAttachment, FIELD_INTEGER ), DEFINE_FIELD( m_flLastImpactEffectTime, FIELD_TIME ), +#ifdef MAPBASE + DEFINE_KEYFIELD( m_flHeavyShotInterval, FIELD_FLOAT, "heavy_shot_interval" ), + DEFINE_KEYFIELD( m_iHeavyShotSpread, FIELD_INTEGER, "heavy_shot_spread" ), + DEFINE_KEYFIELD( m_bUseDamageKV, FIELD_BOOLEAN, "use_damage_kv" ), +#endif + +#ifdef MAPBASE + DEFINE_THINKFUNC( FuncTankAirboatGunThink ), +#endif END_DATADESC() @@ -2805,7 +3415,16 @@ LINK_ENTITY_TO_CLASS( func_tankairboatgun, CFuncTankAirboatGun ); void CFuncTankAirboatGun::Precache( void ) { BaseClass::Precache(); +#ifdef MAPBASE + // Odd placement, but it works + if (m_iszShootSound == NULL_STRING) + { + m_iszShootSound = AllocPooledString("Airboat.FireGunLoop"); + PrecacheScriptSound(STRING(m_iszShootSound)); + } +#else PrecacheScriptSound( "Airboat.FireGunLoop" ); +#endif PrecacheScriptSound( "Airboat.FireGunRevDown"); CreateSounds(); } @@ -2820,6 +3439,10 @@ void CFuncTankAirboatGun::Spawn( void ) m_flNextHeavyShotTime = 0.0f; m_bIsFiring = false; m_flLastImpactEffectTime = -1; + +#ifdef MAPBASE + SetContextThink( &CFuncTankAirboatGun::FuncTankAirboatGunThink, gpGlobals->curtime, s_pAirboatGunThinkContext ); +#endif } @@ -2838,6 +3461,13 @@ void CFuncTankAirboatGun::Activate() m_nGunBarrelAttachment = m_hAirboatGunModel->LookupAttachment( "muzzle" ); } } +#ifdef MAPBASE + else if (GetParent() && GetParent()->GetBaseAnimating()) + { + m_hAirboatGunModel = GetParent()->GetBaseAnimating(); + m_nGunBarrelAttachment = GetGunBarrelAttachment(); + } +#endif } @@ -2851,7 +3481,11 @@ void CFuncTankAirboatGun::CreateSounds() CPASAttenuationFilter filter( this ); if (!m_pGunFiringSound) { +#ifdef MAPBASE + m_pGunFiringSound = controller.SoundCreate( filter, entindex(), STRING(m_iszShootSound) ); +#else m_pGunFiringSound = controller.SoundCreate( filter, entindex(), "Airboat.FireGunLoop" ); +#endif controller.Play( m_pGunFiringSound, 0, 100 ); } } @@ -2914,11 +3548,73 @@ void CFuncTankAirboatGun::ControllerPostFrame( void ) } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Maintains airboat gun sounds on NPCs +//----------------------------------------------------------------------------- +void CFuncTankAirboatGun::FuncTankAirboatGunThink( void ) +{ + if (!GetController()) + { + if (m_fireLast != 0) + { + StartFiring(); + } + else + { + StopFiring(); + } + } + else if (GetController()->IsNPC()) + { + // Attempt to estimate when we wouldn't be firing + if ((gpGlobals->curtime - m_fireLast - (1.0 / m_fireRate)) < 0.1f) + { + StartFiring(); + } + else + { + StopFiring(); + } + } + + SetNextThink( gpGlobals->curtime + 0.05f, s_pAirboatGunThinkContext ); +} + + +void CFuncTankAirboatGun::TankActivate(void) +{ + SetNextThink( gpGlobals->curtime + 0.05f, s_pAirboatGunThinkContext ); + BaseClass::TankActivate(); +} + +void CFuncTankAirboatGun::TankDeactivate(void) +{ + DevMsg("Tank deactivate\n"); + SetNextThink( TICK_NEVER_THINK, s_pAirboatGunThinkContext ); + StopFiring(); + BaseClass::TankDeactivate(); +} + +void CFuncTankAirboatGun::OnStartControlled() +{ + if (GetController() && GetController()->IsNPC()) + SetNextThink( gpGlobals->curtime + 0.05f, s_pAirboatGunThinkContext ); + + BaseClass::OnStartControlled(); +} +#endif + + //----------------------------------------------------------------------------- // Stop controlled //----------------------------------------------------------------------------- void CFuncTankAirboatGun::OnStopControlled() { +#ifdef MAPBASE + DevMsg("Tank stop control\n"); + SetNextThink( TICK_NEVER_THINK, s_pAirboatGunThinkContext ); +#endif StopFiring(); BaseClass::OnStopControlled(); } @@ -2999,16 +3695,35 @@ void CFuncTankAirboatGun::Fire( int bulletCount, const Vector &barrelEnd, const info.m_flDistance = 4096; info.m_iAmmoType = ammoType; +#ifdef MAPBASE + info.m_pAttacker = pAttacker; + info.m_pAdditionalIgnoreEnt = GetParent(); + + if (m_bUseDamageKV) + { + info.m_flDamage = m_iBulletDamage; + info.m_iPlayerDamage = m_iBulletDamageVsPlayer; + } +#endif + if ( gpGlobals->curtime >= m_flNextHeavyShotTime ) { info.m_iShots = 1; +#ifdef MAPBASE + info.m_vecSpread = gTankSpread[m_iHeavyShotSpread]; +#else info.m_vecSpread = VECTOR_CONE_PRECALCULATED; +#endif info.m_flDamageForceScale = 1000.0f; } else { info.m_iShots = 2; +#ifdef MAPBASE + info.m_vecSpread = m_spread != -1 ? gTankSpread[m_spread] : VECTOR_CONE_5DEGREES; +#else info.m_vecSpread = VECTOR_CONE_5DEGREES; +#endif } FireBullets( info ); @@ -3016,10 +3731,39 @@ void CFuncTankAirboatGun::Fire( int bulletCount, const Vector &barrelEnd, const DoMuzzleFlash(); // NOTE: This must occur after FireBullets +#ifdef MAPBASE + if ( gpGlobals->curtime >= m_flNextHeavyShotTime && m_flHeavyShotInterval != -1 ) +#else if ( gpGlobals->curtime >= m_flNextHeavyShotTime ) +#endif { +#ifdef MAPBASE + m_flNextHeavyShotTime = gpGlobals->curtime + m_flHeavyShotInterval; +#else m_flNextHeavyShotTime = gpGlobals->curtime + AIRBOAT_GUN_HEAVY_SHOT_INTERVAL; +#endif } + +#ifdef MAPBASE + // Things from CFuncTank::Fire(). + // We can't use everything because it overrides a few things. + if( pAttacker && pAttacker->IsPlayer() ) + { + if ( IsX360() ) + { + // Now, if you're playing Mapbase on the Xbox 360, the airboat gun turret will make your controller rumble! + // Isn't that lovely? Hmm? + UTIL_PlayerByIndex(1)->RumbleEffect( RUMBLE_AR2, 0, RUMBLE_FLAG_RESTART | RUMBLE_FLAG_RANDOM_AMPLITUDE ); + } + else + { + CSoundEnt::InsertSound( SOUND_MOVE_AWAY, barrelEnd + forward * 32.0f, 32.0f, 0.2f, pAttacker, SOUNDENT_CHANNEL_WEAPON ); + } + } + + m_OnFire.FireOutput(pAttacker, this); + m_bReadyToFire = false; +#endif } @@ -3216,7 +3960,11 @@ class CMortarShell : public CBaseEntity public: DECLARE_CLASS( CMortarShell, CBaseEntity ); +#ifdef MAPBASE + static CMortarShell *Create( const Vector &vecStart, const trace_t &tr, const Vector &vecShotDir, float flImpactDelay, float flWarnDelay, string_t warnSound ); +#else static CMortarShell *Create( const Vector &vecStart, const Vector &vecTarget, const Vector &vecShotDir, float flImpactDelay, float flWarnDelay, string_t warnSound ); +#endif void Spawn( void ); void Precache( void ); @@ -3226,6 +3974,15 @@ public: void FadeThink( void ); int UpdateTransmitState( void ); +#ifdef MAPBASE + void SetRadius(float fl) { m_flRadius = fl; } + void SetMagnitude(int i) { m_Magnitude = i; } + +public: + + bool m_bDontHitController; +#endif + private: void FixUpImpactPoint( const Vector &initialPos, const Vector &initialNormal, Vector *endPos, Vector *endNormal ); @@ -3240,6 +3997,9 @@ private: Vector m_vecFiredFrom; Vector m_vecFlyDir; float m_flSpawnedTime; +#ifdef MAPBASE + int m_Magnitude; +#endif CHandle m_pBeamEffect[4]; @@ -3268,6 +4028,10 @@ BEGIN_DATADESC( CMortarShell ) DEFINE_AUTO_ARRAY( m_pBeamEffect, FIELD_EHANDLE), DEFINE_FIELD( m_flRadius, FIELD_FLOAT ), DEFINE_FIELD( m_vecSurfaceNormal, FIELD_VECTOR ), +#ifdef MAPBASE + DEFINE_FIELD( m_bDontHitController, FIELD_BOOLEAN ), + DEFINE_FIELD( m_Magnitude, FIELD_INTEGER ), +#endif DEFINE_FUNCTION( FlyThink ), DEFINE_FUNCTION( FadeThink ), @@ -3327,13 +4091,19 @@ void CMortarShell::FixUpImpactPoint( const Vector &initialPos, const Vector &ini #define MORTAR_BLAST_DAMAGE 50 #define MORTAR_BLAST_HEIGHT 7500 +#ifdef MAPBASE +CMortarShell *CMortarShell::Create( const Vector &vecStart, const trace_t &tr, const Vector &vecShotDir, float flImpactDelay, float flWarnDelay, string_t warnSound ) +#else CMortarShell *CMortarShell::Create( const Vector &vecStart, const Vector &vecTarget, const Vector &vecShotDir, float flImpactDelay, float flWarnDelay, string_t warnSound ) +#endif { CMortarShell *pShell = (CMortarShell *)CreateEntityByName("mortarshell" ); +#ifndef MAPBASE // Place the mortar shell at the target location so that it can make the sound and explode. trace_t tr; UTIL_TraceLine( vecTarget, vecTarget + ( vecShotDir * 128.0f ), MASK_SOLID_BRUSHONLY, pShell, COLLISION_GROUP_NONE, &tr ); +#endif Vector targetPos, targetNormal; pShell->FixUpImpactPoint( tr.endpos, tr.plane.normal, &targetPos, &targetNormal ); @@ -3619,7 +4389,11 @@ void CMortarShell::Impact( void ) // Fire the bullets Vector vecSrc, vecShootDir; +#ifdef MAPBASE + float flRadius = m_flRadius; +#else float flRadius = MORTAR_BLAST_RADIUS; +#endif trace_t tr; UTIL_TraceLine( GetAbsOrigin(), GetAbsOrigin() - Vector( 0, 0, 128 ), MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr ); @@ -3680,7 +4454,11 @@ void CMortarShell::Impact( void ) FBEAM_FADEOUT ); +#ifdef MAPBASE + RadiusDamage( CTakeDamageInfo( this, GetOwnerEntity(), m_Magnitude / 2, (DMG_BLAST|DMG_DISSOLVE) ), GetAbsOrigin(), flRadius, CLASS_NONE, m_bDontHitController ? GetOwnerEntity() : NULL ); +#else RadiusDamage( CTakeDamageInfo( this, GetOwnerEntity(), MORTAR_BLAST_DAMAGE, (DMG_BLAST|DMG_DISSOLVE) ), GetAbsOrigin(), MORTAR_BLAST_RADIUS, CLASS_NONE, NULL ); +#endif EmitSound( "Weapon_Mortar.Impact" ); @@ -3772,7 +4550,11 @@ public: int m_Magnitude; float m_fireDelay; +#ifdef MAPBASE + #define m_fireStartSound m_iszShootSound +#else string_t m_fireStartSound; +#endif //string_t m_fireEndSound; string_t m_incomingSound; @@ -3781,6 +4563,11 @@ public: bool m_fLastShotMissed; +#ifdef MAPBASE + float m_flRadius; + int m_iMortarTraceMask = MASK_SOLID_BRUSHONLY; +#endif + // store future firing event CBaseEntity *m_pAttacker; }; @@ -3799,6 +4586,11 @@ BEGIN_DATADESC( CFuncTankMortar ) DEFINE_FIELD( m_fLastShotMissed, FIELD_BOOLEAN ), +#ifdef MAPBASE + DEFINE_KEYFIELD( m_flRadius, FIELD_FLOAT, "radius" ), + DEFINE_KEYFIELD( m_iMortarTraceMask, FIELD_INTEGER, "trace_mask" ), +#endif + DEFINE_FIELD( m_pAttacker, FIELD_CLASSPTR ), // Inputs @@ -3933,13 +4725,21 @@ void CFuncTankMortar::Fire( int bulletCount, const Vector &barrelEnd, const Vect vecSpot.z = GetAbsOrigin().z; // Trace up to find the fake 'apex' of the shell. The skybox or 1024 units, whichever comes first. +#ifdef MAPBASE + UTIL_TraceLine( vecSpot, vecSpot + Vector(0, 0, 1024), m_iMortarTraceMask, NULL, COLLISION_GROUP_NONE, &tr ); +#else UTIL_TraceLine( vecSpot, vecSpot + Vector(0, 0, 1024), MASK_SOLID_BRUSHONLY, NULL, COLLISION_GROUP_NONE, &tr ); +#endif vecSpot = tr.endpos; //NDebugOverlay::Line( tr.startpos, tr.endpos, 0,255,0, false, 5 ); // Now trace from apex to target +#ifdef MAPBASE + UTIL_TraceLine( vecSpot, vecProjectedPosition, m_iMortarTraceMask, NULL, COLLISION_GROUP_NONE, &tr ); +#else UTIL_TraceLine( vecSpot, vecProjectedPosition, MASK_SOLID_BRUSHONLY, NULL, COLLISION_GROUP_NONE, &tr ); +#endif if( mortar_visualize.GetBool() ) { @@ -3962,7 +4762,16 @@ void CFuncTankMortar::Fire( int bulletCount, const Vector &barrelEnd, const Vect Vector vecFinalDir = tr.endpos - tr.startpos; VectorNormalize( vecFinalDir ); +#ifdef MAPBASE + CMortarShell *pShell = CMortarShell::Create( barrelEnd, tr, vecFinalDir, m_fireDelay, m_flWarningTime, m_incomingSound ); + pShell->SetOwnerEntity(GetController()); + pShell->m_bDontHitController = m_bDontHitController; + if (m_flRadius != 0) + pShell->SetRadius(m_flRadius); + pShell->SetMagnitude(m_Magnitude); +#else CMortarShell::Create( barrelEnd, tr.endpos, vecFinalDir, m_fireDelay, m_flWarningTime, m_incomingSound ); +#endif BaseClass::Fire( bulletCount, barrelEnd, vecForward, this, bIgnoreSpread ); } @@ -4063,6 +4872,9 @@ private: Vector m_vecTrueForward; bool m_bShouldHarrass; bool m_bLastTargetWasNPC; // Tells whether the last entity we fired a shot at was an NPC (otherwise it was the player) +#ifdef MAPBASE + bool m_bControllableVersion = false; // Allows new behavior that makes player/NPC control easier. Doesn't use spawnflag for legacy purposes, as if anyone used this as a regular tank before. +#endif }; BEGIN_DATADESC( CFuncTankCombineCannon ) @@ -4074,6 +4886,9 @@ BEGIN_DATADESC( CFuncTankCombineCannon ) DEFINE_FIELD( m_vecTrueForward, FIELD_VECTOR ), DEFINE_FIELD( m_bShouldHarrass, FIELD_BOOLEAN ), DEFINE_FIELD( m_bLastTargetWasNPC, FIELD_BOOLEAN ), +#ifdef MAPBASE + DEFINE_KEYFIELD( m_bControllableVersion, FIELD_BOOLEAN, "ControllableVersion" ), +#endif DEFINE_INPUTFUNC( FIELD_VOID, "EnableHarrass", InputEnableHarrass ), DEFINE_INPUTFUNC( FIELD_VOID, "DisableHarrass", InputDisableHarrass ), @@ -4100,7 +4915,12 @@ void CFuncTankCombineCannon::Spawn() { BaseClass::Spawn(); m_flTimeBeamOn = gpGlobals->curtime; +#ifdef MAPBASE + if (!m_bControllableVersion) + CreateBeam(); +#else CreateBeam(); +#endif m_bShouldHarrass = true; @@ -4154,8 +4974,14 @@ void CFuncTankCombineCannon::DestroyBeam() //--------------------------------------------------------- void CFuncTankCombineCannon::AdjustRateOfFire() { +#ifdef MAPBASE + // Only maintain 1.5 rounds per second if we're using legacy behavior. + if (!m_bControllableVersion) + m_fireRate = 1.5; +#else // Maintain 1.5 rounds per second rate of fire. m_fireRate = 1.5; +#endif /* if( m_hTarget.Get() != NULL && m_hTarget->IsPlayer() ) { @@ -4208,6 +5034,12 @@ void CFuncTankCombineCannon::FuncTankPostThink() { AdjustRateOfFire(); +#ifdef MAPBASE + // Controllables don't sweep + if (m_bControllableVersion) + return; +#endif + if( m_hTarget.Get() == NULL ) { if( gpGlobals->curtime > m_flTimeNextSweep ) @@ -4263,7 +5095,11 @@ void CFuncTankCombineCannon::FuncTankPostThink() // Ignore the func_tank and any prop it's parented to, and check line of sight to the point // Trace to the point. If an opaque trace doesn't reach the point, that means the beam hit // something closer, (including a blockLOS), so try again. +#ifdef MAPBASE + CTraceFilterSimple traceFilter = GetTraceFilter(); +#else CTraceFilterSkipTwoEntities traceFilter( this, GetParent(), COLLISION_GROUP_NONE ); +#endif AI_TraceLine( vecBarrelEnd, vecTest, MASK_BLOCKLOS_AND_NPCS, &traceFilter, &trLOS ); AI_TraceLine( vecBarrelEnd, vecTest, MASK_SHOT, &traceFilter, &trShoot ); @@ -4320,13 +5156,23 @@ void CFuncTankCombineCannon::FuncTankPostThink() //--------------------------------------------------------- void CFuncTankCombineCannon::Fire( int bulletCount, const Vector &barrelEnd, const Vector &forward, CBaseEntity *pAttacker, bool bIgnoreSpread ) { +#ifdef MAPBASE + // If we're in aim-at-pos mode and not in the new controllable version, don't fire in this mode + if ( HasSpawnFlags(SF_TANK_AIM_AT_POS) && !m_bControllableVersion ) + return; +#else // Specifically do NOT fire in aim at pos mode. This is just for show. if( HasSpawnFlags(SF_TANK_AIM_AT_POS) ) return; +#endif Vector vecAdjustedForward = forward; +#ifdef MAPBASE + if ( !IsPlayerManned() && m_hTarget != NULL ) +#else if( m_hTarget != NULL ) +#endif { Vector vecToTarget = m_hTarget->BodyTarget( barrelEnd, false ) - barrelEnd; VectorNormalize( vecToTarget ); @@ -4429,3 +5275,93 @@ void CFuncTankCombineCannon::InputDisableHarrass( inputdata_t &inputdata ) LINK_ENTITY_TO_CLASS( func_tank_combine_cannon, CFuncTankCombineCannon ); + +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: Func tank that fires a bunch of outputs instead +//----------------------------------------------------------------------------- +class CFuncTankLogic : public CFuncTank +{ +public: + DECLARE_CLASS(CFuncTankLogic, CFuncTank); + DECLARE_DATADESC(); + + CFuncTankLogic(); + + void Fire(int bulletCount, const Vector &barrelEnd, const Vector &forward, CBaseEntity *pAttacker, bool bIgnoreSpread); + +protected: + + bool m_bShootsThroughWater; + + COutputVector m_OnFire_BarrelPos; + COutputVector m_OnFire_BarrelAng; + COutputVector m_OnFire_ShootPos; + COutputEHANDLE m_OnFire_FirstEnt; +}; + +LINK_ENTITY_TO_CLASS(func_tanklogic, CFuncTankLogic); + +BEGIN_DATADESC(CFuncTankLogic) + + //DEFINE_KEYFIELD( m_bDontHitController, FIELD_BOOLEAN, "DontHitController" ), + + DEFINE_OUTPUT( m_OnFire_BarrelPos, "OnFire_BarrelPos" ), + DEFINE_OUTPUT( m_OnFire_BarrelAng, "OnFire_BarrelAng" ), + DEFINE_OUTPUT( m_OnFire_ShootPos, "OnFire_ShootPos" ), + DEFINE_OUTPUT( m_OnFire_FirstEnt, "OnFire_FirstEnt" ), + +END_DATADESC() + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CFuncTankLogic::CFuncTankLogic() +{ + // This is overriden by KV later + m_bDontHitController = true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CFuncTankLogic::Fire(int bulletCount, const Vector &barrelEnd, const Vector &forward, CBaseEntity *pAttacker, bool bIgnoreSpread) +{ + // A bunch of stuff from FireBullets() to replicate firing a regular func_tank. + // It mostly just has the trace stuff. + Vector vecDir; + Vector vecEnd; + + trace_t tr; + CTraceFilterSimple traceFilter = GetTraceFilter(); + + CShotManipulator Manipulator( forward ); + vecDir = Manipulator.ApplySpread( bIgnoreSpread ? gTankSpread[0] : gTankSpread[m_spread] ); + + vecEnd = barrelEnd + vecDir * MAX_TRACE_LENGTH; + + int mask = MASK_SHOT; + + if (!m_bShootsThroughWater) + mask |= CONTENTS_WATER; + + AI_TraceLine(barrelEnd, vecEnd, mask, &traceFilter, &tr); + + if ( tr.startsolid ) + { + tr.endpos = tr.startpos; + tr.fraction = 0.0f; + } + + if ( ai_debug_shoot_positions.GetBool() ) + NDebugOverlay::Line(barrelEnd, tr.endpos, 255, 255, 255, false, .1 ); + + BaseClass::Fire(bulletCount, barrelEnd, forward, pAttacker, bIgnoreSpread); + + m_OnFire_BarrelPos.Set(barrelEnd, pAttacker, this); + m_OnFire_BarrelAng.Set(forward, pAttacker, this); + m_OnFire_ShootPos.Set(tr.endpos, pAttacker, this); + m_OnFire_FirstEnt.Set(tr.m_pEnt, tr.m_pEnt, this); +} +#endif // MAPBASE + diff --git a/src/game/server/hl2/func_tank.h b/src/game/server/hl2/func_tank.h index 5952e992..da945117 100644 --- a/src/game/server/hl2/func_tank.h +++ b/src/game/server/hl2/func_tank.h @@ -55,6 +55,20 @@ enum TANKBULLET #define MORTAR_BLAST_RADIUS 350 +#ifdef MAPBASE +// This moves variables related to ammo types from CFuncTank to CFuncTankGun, as CFuncTankGun and its derivatives are the only classes that use it. +// It also completely replaces the legacy "bullet" keyvalue with the AmmoType keyvalue, making bullet translate to the ammo type variable directly. +// This fixes the issue where some func_tanks don't fire any bullets. +// +// This code was created in September-October 2018 and moving existing variables like this seems risky and somewhat pointless, but the nature of func_tanks +// make this unlikely to cause any problems and helps my OCD. +// +// Disable this preprocessor if it causes problems. +#define AMMOTYPE_MOVED 1 + +class CTraceFilterSimple; +#endif + // Custom damage // env_laser (duration is 0.5 rate of fire) @@ -134,6 +148,13 @@ public: virtual void DoMuzzleFlash( void ); virtual const char *GetTracerType( void ); +#ifdef MAPBASE + virtual CTraceFilterSimple GetTraceFilter(); + + // Needed because func_tankairboatgun needs the barrel + int GetGunBarrelAttachment() { return m_nBarrelAttachment; } +#endif + protected: virtual float GetShotSpeed() { return 0; } @@ -179,6 +200,10 @@ protected: private: void InputFindNPCToManTank( inputdata_t &inputdata ); +#ifdef MAPBASE + void InputTeleportNPCToManTank( inputdata_t &inputdata ); + void InputForceNPCToManTank( inputdata_t &inputdata ); +#endif void InputStopFindingNPCs( inputdata_t &inputdata ); void InputStartFindingNPCs( inputdata_t &inputdata ); void InputForceNPCOff( inputdata_t &inputdata ); @@ -217,7 +242,11 @@ private: bool RotateTankToAngles( const QAngle &angles, float *pDistX = NULL, float *pDistY = NULL ); // We lost our target! +#ifdef MAPBASE + void LostTarget( CBaseEntity *pTarget ); +#else void LostTarget( void ); +#endif // Purpose: void ComputeLeadingPosition( const Vector &vecShootPosition, CBaseEntity *pTarget, Vector *pLeadPosition ); @@ -237,6 +266,7 @@ protected: int m_iBulletDamage; // 0 means use Bullet type's default damage int m_iBulletDamageVsPlayer; // Damage vs player. 0 means use m_iBulletDamage +#ifndef AMMOTYPE_MOVED #ifdef HL2_EPISODIC string_t m_iszAmmoType; // The name of the ammodef that we use when we fire. Bullet damage still comes from keyvalues. int m_iAmmoType; // The cached index of the ammodef that we use when we fire. @@ -244,7 +274,9 @@ protected: int m_iSmallAmmoType; int m_iMediumAmmoType; int m_iLargeAmmoType; -#endif // HL2_EPISODIC +#endif // HL2_EPISODIC +#endif // !AMMOTYPE_MOVED + int m_spread; // firing spread @@ -255,6 +287,15 @@ protected: int m_nBulletCount; +#ifdef MAPBASE + bool m_bDontHitController; + string_t m_iszTraceFilter; + CHandle m_hTraceFilter; + + // Created to nullify aiming problems when the func_tank is on a vehicle or the player is too close to the barrel + float m_flPlayerBBoxDist; +#endif + private: // This is either the player manning the func_tank, or an NPC. The NPC is either manning the tank, or running @@ -293,6 +334,19 @@ private: string_t m_iszSpriteSmoke; string_t m_iszSpriteFlash; +#ifdef MAPBASE +public: + // This is kind of tricky to implement. + // Some derived classes already have their own shoot sound variables, making this one redundant, etc. + // To rectify this, m_iszShootSound replaces all of them and takes their keyfield names. + // It's like the TF spy of func_tank. + string_t m_iszShootSound; + + // NPC controllers cannot leave. >:) + bool m_bControllerGlued; +private: +#endif + string_t m_iszMaster; // Master entity (game_team_master or multisource) string_t m_soundStartRotate; @@ -327,9 +381,16 @@ private: float m_flNextLeadFactor; float m_flNextLeadFactorTime; +#ifdef MAPBASE +public: + COutputEvent m_OnFire; + COutputEHANDLE m_OnLoseTarget; + COutputEHANDLE m_OnAquireTarget; +#else COutputEvent m_OnFire; COutputEvent m_OnLoseTarget; COutputEvent m_OnAquireTarget; +#endif COutputEvent m_OnAmmoDepleted; COutputEvent m_OnGotController; COutputEvent m_OnLostController; diff --git a/src/game/server/hl2/grenade_ar2.cpp b/src/game/server/hl2/grenade_ar2.cpp index 2a16d7fc..9fea8129 100644 --- a/src/game/server/hl2/grenade_ar2.cpp +++ b/src/game/server/hl2/grenade_ar2.cpp @@ -34,6 +34,9 @@ extern ConVar sk_npc_dmg_smg1_grenade; extern ConVar sk_max_smg1_grenade; ConVar sk_smg1_grenade_radius ( "sk_smg1_grenade_radius","0"); +#ifdef MAPBASE +ConVar smg1_grenade_credit_transfer("smg1_grenade_credit_transfer", "1"); +#endif ConVar g_CV_SmokeTrail("smoke_trail", "1", 0); // temporary dust explosion switch @@ -161,6 +164,14 @@ void CGrenadeAR2::GrenadeAR2Think( void ) void CGrenadeAR2::Event_Killed( const CTakeDamageInfo &info ) { +#ifdef MAPBASE + if (smg1_grenade_credit_transfer.GetBool() && info.GetAttacker()->MyCombatCharacterPointer()) + { + CBaseCombatCharacter *pBCC = info.GetAttacker()->MyCombatCharacterPointer(); + SetThrower(pBCC); + SetOwnerEntity(pBCC); + } +#endif Detonate( ); } diff --git a/src/game/server/hl2/grenade_bugbait.cpp b/src/game/server/hl2/grenade_bugbait.cpp index 12e3dda1..feddcd56 100644 --- a/src/game/server/hl2/grenade_bugbait.cpp +++ b/src/game/server/hl2/grenade_bugbait.cpp @@ -35,6 +35,9 @@ CBugBaitSensor* GetBugBaitSensorList() CBugBaitSensor::CBugBaitSensor( void ) { g_BugBaitSensorList.Insert( this ); +#ifdef MAPBASE + m_bUseRadius = true; +#endif } CBugBaitSensor::~CBugBaitSensor( void ) @@ -50,10 +53,24 @@ BEGIN_DATADESC( CBugBaitSensor ) DEFINE_KEYFIELD( m_bEnabled, FIELD_BOOLEAN, "Enabled" ), DEFINE_KEYFIELD( m_flRadius, FIELD_FLOAT, "radius" ), +#ifdef MAPBASE + DEFINE_KEYFIELD( m_bUseRadius, FIELD_BOOLEAN, "useradius" ), + DEFINE_KEYFIELD( m_vecMins, FIELD_VECTOR, "bmins" ), + DEFINE_KEYFIELD( m_vecMaxs, FIELD_VECTOR, "bmaxs" ), +#endif + DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ), DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ), DEFINE_INPUTFUNC( FIELD_VOID, "Toggle", InputToggle ), +#ifdef MAPBASE + DEFINE_INPUTFUNC( FIELD_VOID, "EnableRadius", InputEnableRadius ), + DEFINE_INPUTFUNC( FIELD_VOID, "DisableRadius", InputDisableRadius ), + DEFINE_INPUTFUNC( FIELD_INTEGER, "SetRadius", InputSetRadius ), + DEFINE_INPUTFUNC( FIELD_VECTOR, "SetMins", InputSetMins ), + DEFINE_INPUTFUNC( FIELD_VECTOR, "SetMaxs", InputSetMaxs ), +#endif + // Function Pointers DEFINE_OUTPUT( m_OnBaited, "OnBaited" ), @@ -200,6 +217,11 @@ void CGrenadeBugBait::BugBaitTouch( CBaseEntity *pOther ) // Tell all spawners to now fight to this position g_AntlionMakerManager.BroadcastFightGoal( GetAbsOrigin() ); +#ifdef MAPBASE + m_OnDetonate.FireOutput(GetThrower(), this); + m_OnDetonate_OutPosition.Set(GetAbsOrigin(), GetThrower(), this); +#endif + //Go away UTIL_Remove( this ); } @@ -262,18 +284,42 @@ bool CGrenadeBugBait::ActivateBugbaitTargets( CBaseEntity *pOwner, Vector vecOri continue; //Make sure we're within range of the sensor - if ( pSensor->GetRadius() > ( pSensor->GetAbsOrigin() - vecOrigin ).Length() ) - { - //Tell the sensor it's been hit - if ( pSensor->Baited( pOwner ) ) +#ifdef MAPBASE + if ( pSensor->UsesRadius() ){ +#endif + if ( pSensor->GetRadius() > (pSensor->GetAbsOrigin() - vecOrigin).Length() ) { - //If we're suppressing the call to antlions, then don't make a bugbait sound - if ( pSensor->SuppressCall() ) + //Tell the sensor it's been hit + if ( pSensor->Baited( pOwner ) ) { - suppressCall = true; + //If we're suppressing the call to antlions, then don't make a bugbait sound + if ( pSensor->SuppressCall() ) + { + suppressCall = true; + } + } + } +#ifdef MAPBASE + } + else{ + Vector vMins = pSensor->GetAbsMins(); + Vector vMaxs = pSensor->GetAbsMaxs(); + bool inBox = ((vecOrigin.x >= vMins.x && vecOrigin.x <= vMaxs.x) && + (vecOrigin.y >= vMins.y && vecOrigin.y <= vMaxs.y) && + (vecOrigin.z >= vMins.z && vecOrigin.z <= vMaxs.z)); + if ( inBox ){ + //Tell the sensor it's been hit + if ( pSensor->Baited( pOwner ) ) + { + //If we're suppressing the call to antlions, then don't make a bugbait sound + if ( pSensor->SuppressCall() ) + { + suppressCall = true; + } } } } +#endif } return suppressCall; diff --git a/src/game/server/hl2/grenade_bugbait.h b/src/game/server/hl2/grenade_bugbait.h index 7a5df993..fb2a68cc 100644 --- a/src/game/server/hl2/grenade_bugbait.h +++ b/src/game/server/hl2/grenade_bugbait.h @@ -63,6 +63,28 @@ public: m_bEnabled = !m_bEnabled; } +#ifdef MAPBASE + void InputEnableRadius( inputdata_t &data ){ + m_bUseRadius = true; + } + + void InputDisableRadius( inputdata_t &data ){ + m_bUseRadius = false; + } + + void InputSetRadius( inputdata_t &data ){ + m_flRadius = data.value.Int(); + } + + void InputSetMins( inputdata_t &data ){ + data.value.Vector3D( m_vecMins ); + } + + void InputSetMaxs( inputdata_t &data ){ + data.value.Vector3D( m_vecMaxs ); + } +#endif + bool SuppressCall( void ) { return ( HasSpawnFlags( SF_BUGBAIT_SUPPRESS_CALL ) ); @@ -91,10 +113,28 @@ public: return !m_bEnabled; } +#ifdef MAPBASE + bool UsesRadius( void ) const { + return m_bUseRadius; + } + + Vector GetAbsMins( void ) const { + return GetAbsOrigin() + m_vecMins; + } + Vector GetAbsMaxs( void ) const { + return GetAbsOrigin() + m_vecMaxs; + } +#endif + protected: float m_flRadius; bool m_bEnabled; +#ifdef MAPBASE + bool m_bUseRadius; + Vector m_vecMins; + Vector m_vecMaxs; +#endif COutputEvent m_OnBaited; public: diff --git a/src/game/server/hl2/grenade_frag.cpp b/src/game/server/hl2/grenade_frag.cpp index b5c8f3c3..3dbd59db 100644 --- a/src/game/server/hl2/grenade_frag.cpp +++ b/src/game/server/hl2/grenade_frag.cpp @@ -11,6 +11,9 @@ #include "Sprite.h" #include "SpriteTrail.h" #include "soundent.h" +#ifdef MAPBASE +#include "mapbase/ai_grenade.h" +#endif // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" @@ -123,8 +126,31 @@ void CGrenadeFrag::Spawn( void ) SetCollisionGroup( COLLISION_GROUP_WEAPON ); CreateVPhysics(); +#ifdef MAPBASE + if (GetThrower() && GetThrower()->IsNPC()) + { + // One of OnThrowGrenade's useful applications is replacing it with another entity using point_entity_replace. + // However, the grenade is always able to let out a blip before being replaced, which can be confusing/undesirable. + // This code checks to see if OnThrowGrenade is being used for anything, in which case the first blip will be very slightly delayed. + // This doesn't interfere with when the grenade actually detonates and shouldn't be noticable if the grenade is kept by OnThrowGrenade anyway. + CAI_GrenadeUserSink *pGrenadeUser = dynamic_cast(GetThrower()); + if (pGrenadeUser && pGrenadeUser->UsingOnThrowGrenade()) + { + // We delay the blip by 0.05, so replacement must occur within that period in order to skip the blip. + m_flNextBlipTime = gpGlobals->curtime + 0.05f; + } + } + + // Do the blip if m_flNextBlipTime wasn't changed + if (m_flNextBlipTime <= gpGlobals->curtime) + { + BlipSound(); + m_flNextBlipTime = gpGlobals->curtime + FRAG_GRENADE_BLIP_FREQUENCY; + } +#else BlipSound(); m_flNextBlipTime = gpGlobals->curtime + FRAG_GRENADE_BLIP_FREQUENCY; +#endif AddSolidFlags( FSOLID_NOT_STANDABLE ); diff --git a/src/game/server/hl2/grenade_spit.cpp b/src/game/server/hl2/grenade_spit.cpp index 4b713e6d..5f3e8ef9 100644 --- a/src/game/server/hl2/grenade_spit.cpp +++ b/src/game/server/hl2/grenade_spit.cpp @@ -131,7 +131,19 @@ void CGrenadeSpit::GrenadeSpitTouch( CBaseEntity *pOther ) if ( pOther->IsSolidFlagSet(FSOLID_VOLUME_CONTENTS | FSOLID_TRIGGER) ) { // Some NPCs are triggers that can take damage (like antlion grubs). We should hit them. +#ifdef MAPBASE + // But some physics objects that are also triggers (like weapons) shouldn't go through this check. + // + // Note: rpg_missile has the same code, except it properly accounts for weapons in a different way. + // This was discovered after I implemented this and both work fine, but if this ever causes problems, + // use rpg_missile's implementation: + // + // if ( pOther->IsSolidFlagSet(FSOLID_TRIGGER|FSOLID_VOLUME_CONTENTS) && pOther->GetCollisionGroup() != COLLISION_GROUP_WEAPON ) + // + if ( pOther->GetMoveType() == MOVETYPE_NONE && (( pOther->m_takedamage == DAMAGE_NO ) || ( pOther->m_takedamage == DAMAGE_EVENTS_ONLY )) ) +#else if ( ( pOther->m_takedamage == DAMAGE_NO ) || ( pOther->m_takedamage == DAMAGE_EVENTS_ONLY ) ) +#endif return; } diff --git a/src/game/server/hl2/hl2_client.cpp b/src/game/server/hl2/hl2_client.cpp index 6125de82..12dc248f 100644 --- a/src/game/server/hl2/hl2_client.cpp +++ b/src/game/server/hl2/hl2_client.cpp @@ -137,6 +137,21 @@ void respawn( CBaseEntity *pEdict, bool fCopyCorpse ) // respawn player pEdict->Spawn(); } +#ifdef MAPBASE + else if (g_pGameRules->AllowSPRespawn()) + { + // In SP respawns, only create corpse if drawing externally + CBasePlayer *pPlayer = (CBasePlayer*)pEdict; + if ( fCopyCorpse && pPlayer->GetDrawPlayerModelExternally() ) + { + // make a copy of the dead body for appearances sake + pPlayer->CreateCorpse(); + } + + // respawn player + pPlayer->Spawn(); + } +#endif else { // restart the entire server engine->ServerCommand("reload\n"); diff --git a/src/game/server/hl2/hl2_player.cpp b/src/game/server/hl2/hl2_player.cpp index 7b2d595e..41c2261f 100644 --- a/src/game/server/hl2/hl2_player.cpp +++ b/src/game/server/hl2/hl2_player.cpp @@ -55,6 +55,11 @@ #include "portal_player.h" #endif // PORTAL +#ifdef MAPBASE +#include "triggers.h" +#include "mapbase/variant_tools.h" +#endif + // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" @@ -65,6 +70,10 @@ extern ConVar weapon_showproficiency; extern ConVar autoaim_max_dist; +#ifdef MAPBASE +extern ConVar player_squad_autosummon_enabled; +#endif + // Do not touch with without seeing me, please! (sjb) // For consistency's sake, enemy gunfire is traced against a scaled down // version of the player's hull, not the hitboxes for the player's model @@ -105,6 +114,15 @@ ConVar autoaim_unlock_target( "autoaim_unlock_target", "0.8666" ); ConVar sv_stickysprint("sv_stickysprint", "0", FCVAR_ARCHIVE | FCVAR_ARCHIVE_XBOX); +#ifdef MAPBASE +ConVar player_autoswitch_enabled( "player_autoswitch_enabled", "1", FCVAR_NONE, "This convar was added by Mapbase to toggle whether players automatically switch to their ''best'' weapon upon picking up ammo for it after it was dry." ); + +#ifdef SP_ANIM_STATE +ConVar hl2_use_sp_animstate( "hl2_use_sp_animstate", "1", FCVAR_NONE, "Allows SP HL2 players to use HL2:DM animations for custom player models. (changes may not apply until model is reloaded)" ); +#endif + +#endif + #define FLASH_DRAIN_TIME 1.1111 // 100 units / 90 secs #define FLASH_CHARGE_TIME 50.0f // 100 units / 2 secs @@ -155,12 +173,26 @@ static impactdamagetable_t gCappedPlayerImpactDamageTable = // Flashlight utility bool g_bCacheLegacyFlashlightStatus = true; +#ifdef MAPBASE +extern ThreeState_t Flashlight_GetLegacyVersionKey(); +#endif bool g_bUseLegacyFlashlight; bool Flashlight_UseLegacyVersion( void ) { // If this is the first run through, cache off what the answer should be (cannot change during a session) if ( g_bCacheLegacyFlashlightStatus ) { +#ifdef MAPBASE + // Check if there's a gameinfo setting. + ThreeState_t iGameKey = Flashlight_GetLegacyVersionKey(); + if (iGameKey != TRS_NONE) + { + g_bUseLegacyFlashlight = (iGameKey == TRS_TRUE); + g_bCacheLegacyFlashlightStatus = false; + return g_bUseLegacyFlashlight; + } +#endif + char modDir[MAX_PATH]; if ( UTIL_GetModDir( modDir, sizeof(modDir) ) == false ) return false; @@ -198,6 +230,17 @@ public: COutputInt m_RequestedPlayerHealth; +#ifdef MAPBASE + COutputInt m_OnGetAmmo; + COutputEvent m_PlayerDamaged; + COutputEvent m_OnSquadMemberKilled; + COutputInt m_RequestedPlayerArmor; + COutputFloat m_RequestedPlayerAuxPower; + COutputFloat m_RequestedPlayerFlashBattery; + + COutputEvent m_OnPlayerSpawn; +#endif + void InputRequestPlayerHealth( inputdata_t &inputdata ); void InputSetFlashlightSlowDrain( inputdata_t &inputdata ); void InputSetFlashlightNormalDrain( inputdata_t &inputdata ); @@ -210,14 +253,169 @@ public: #ifdef PORTAL void InputSuppressCrosshair( inputdata_t &inputdata ); #endif // PORTAL2 +#ifdef MAPBASE + void InputRequestPlayerArmor( inputdata_t &inputdata ); + void InputRequestPlayerAuxPower( inputdata_t &inputdata ); + void InputRequestPlayerFlashBattery( inputdata_t &inputdata ); + + void InputGetAmmoOnWeapon( inputdata_t &inputdata ); + + void InputSetHandModel( inputdata_t &inputdata ); + void InputSetHandModelSkin( inputdata_t &inputdata ); + void InputSetHandModelBodyGroup( inputdata_t &inputdata ); + + void InputSetPlayerModel( inputdata_t &inputdata ); + void InputSetPlayerDrawExternally( inputdata_t &inputdata ); +#endif void Activate ( void ); +#ifdef MAPBASE + bool KeyValue( const char *szKeyName, const char *szValue ); + + bool AcceptInput( const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t Value, int outputID ); + + void NotifyPlayerHasProxy(); + + // This is here because the player might not be available when we spawn. + // Hope there wouldn't be enough time for this to need to be saved... + CUtlDict m_QueuedKV; + + int m_MaxArmor = 100; + int m_SuitZoomFOV = 25; +#endif + bool PassesDamageFilter( const CTakeDamageInfo &info ); EHANDLE m_hPlayer; }; +#ifdef MAPBASE +static CUtlVector g_pCommandRedirects; + +//----------------------------------------------------------------------------- +// Redirects player squad commands +//----------------------------------------------------------------------------- +class CCommandRedirect : public CBaseTrigger +{ + DECLARE_CLASS( CCommandRedirect, CBaseTrigger ); +public: + CCommandRedirect() + { + g_pCommandRedirects.AddToTail(this); + //int i = g_pCommandRedirects.AddToTail(); + //g_pCommandRedirects[i].Set( this ); + } + + ~CCommandRedirect() + { + g_pCommandRedirects.FindAndRemove(this); + /* + for (int i = 0; i < g_pCommandRedirects.Count(); i++) + { + if (g_pCommandRedirects[i].Get() == this) + { + g_pCommandRedirects.Remove( i ); + break; + } + } + */ + } + + void Spawn() + { + BaseClass::Spawn(); + InitTrigger(); + } + + // Will the command point change? + // True = Command point changes + // False = Comand point doesn't change + bool TestRedirect(Vector *vecNewCommandPoint, CHL2_Player *pPlayer) + { + // Output the goal before doing anything else. + m_OnCommandGoal.Set(*vecNewCommandPoint, pPlayer, this); + + if (m_target == NULL_STRING) + { + // Not targeting anything. Don't redirect and just leave it at the output + return false; + } + else if (FStrEq(STRING(m_target), "-1")) + { + // Completely cancel the squad command. + *vecNewCommandPoint = vec3_origin; + return true; + } + else + { + // Player is caller. + // Player squad representative is activator. + CBaseEntity *pEntOfInterest = gEntList.FindEntityGeneric(NULL, STRING(m_target), this, pPlayer->GetSquadCommandRepresentative(), pPlayer); + if (pEntOfInterest) + { + // Use the entity's absolute origin as the new command point. + *vecNewCommandPoint = pEntOfInterest->GetAbsOrigin(); + return true; + } + else + { + Warning("%s couldn't find target entity \"%s\"\n", GetDebugName(), STRING(m_target)); + } + } + + return false; + } + + void HandleAllies(CAI_Squad *pSquad, CHL2_Player *pPlayer) + { + if (m_bRepOnly) + { + CBaseEntity *pSquadRep = pPlayer->GetSquadCommandRepresentative(); + if (pSquadRep) + m_OutAlly.Set(pSquadRep, pSquadRep, this); + } + else + { + AISquadIter_t iter; + for ( CBaseEntity *pAllyNpc = pSquad->GetFirstMember(&iter); pAllyNpc; pAllyNpc = pSquad->GetNextMember(&iter) ) + { + m_OutAlly.Set(pAllyNpc, pAllyNpc, this); + } + } + } + + bool PassesTriggerFilters(CBaseEntity *pOther) + { + return pOther->IsPlayer() || (pOther->MyNPCPointer() && pOther->MyNPCPointer()->IsInPlayerSquad()); + } + + bool IsDisabled() { return m_bDisabled; } + + DECLARE_DATADESC(); + +private: + bool m_bRepOnly; + + COutputVector m_OnCommandGoal; + COutputEHANDLE m_OutAlly; +}; + +LINK_ENTITY_TO_CLASS( func_commandredirect, CCommandRedirect ); +BEGIN_DATADESC( CCommandRedirect ) + + DEFINE_KEYFIELD( m_bRepOnly, FIELD_BOOLEAN, "reponly" ), + DEFINE_KEYFIELD( m_bDisabled, FIELD_BOOLEAN, "StartDisabled" ), + + DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ), + DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ), + + DEFINE_OUTPUT( m_OnCommandGoal, "OnCommandGoal" ), + DEFINE_OUTPUT( m_OutAlly, "OutAlly" ), + +END_DATADESC() +#endif + //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ @@ -360,10 +558,34 @@ BEGIN_DATADESC( CHL2_Player ) DEFINE_INPUTFUNC( FIELD_FLOAT, "IgnoreFallDamage", InputIgnoreFallDamage ), DEFINE_INPUTFUNC( FIELD_FLOAT, "IgnoreFallDamageWithoutReset", InputIgnoreFallDamageWithoutReset ), +#ifdef MAPBASE + DEFINE_INPUTFUNC( FIELD_EHANDLE, "OnSquadMemberKilled", OnSquadMemberKilled ), +#else DEFINE_INPUTFUNC( FIELD_VOID, "OnSquadMemberKilled", OnSquadMemberKilled ), +#endif DEFINE_INPUTFUNC( FIELD_VOID, "DisableFlashlight", InputDisableFlashlight ), DEFINE_INPUTFUNC( FIELD_VOID, "EnableFlashlight", InputEnableFlashlight ), DEFINE_INPUTFUNC( FIELD_VOID, "ForceDropPhysObjects", InputForceDropPhysObjects ), +#ifdef MAPBASE + DEFINE_INPUTFUNC( FIELD_VOID, "SquadForceSummon", InputSquadForceSummon ), + DEFINE_INPUTFUNC( FIELD_INPUT, "SquadForceGoTo", InputSquadForceGoTo ), // FIELD_INPUT so it supports vectors, ehandles, and strings + + DEFINE_INPUTFUNC( FIELD_INTEGER, "AddArmor", InputAddArmor ), + DEFINE_INPUTFUNC( FIELD_INTEGER, "RemoveArmor", InputRemoveArmor ), + DEFINE_INPUTFUNC( FIELD_INTEGER, "SetArmor", InputSetArmor ), + + DEFINE_INPUTFUNC( FIELD_FLOAT, "AddAuxPower", InputAddAuxPower ), + DEFINE_INPUTFUNC( FIELD_FLOAT, "RemoveAuxPower", InputRemoveAuxPower ), + DEFINE_INPUTFUNC( FIELD_FLOAT, "SetAuxPower", InputSetAuxPower ), + + DEFINE_INPUTFUNC( FIELD_VOID, "TurnFlashlightOn", InputTurnFlashlightOn ), + DEFINE_INPUTFUNC( FIELD_VOID, "TurnFlashlightOff", InputTurnFlashlightOff ), + + DEFINE_INPUTFUNC( FIELD_VOID, "EnableGeigerCounter", InputEnableGeigerCounter ), + DEFINE_INPUTFUNC( FIELD_VOID, "DisableGeigerCounter", InputDisableGeigerCounter ), + DEFINE_INPUTFUNC( FIELD_VOID, "ShowSquadHUD", InputShowSquadHUD ), + DEFINE_INPUTFUNC( FIELD_VOID, "HideSquadHUD", InputHideSquadHUD ), +#endif DEFINE_SOUNDPATCH( m_sndLeeches ), DEFINE_SOUNDPATCH( m_sndWaterSplashes ), @@ -381,6 +603,27 @@ BEGIN_DATADESC( CHL2_Player ) END_DATADESC() +#ifdef MAPBASE_VSCRIPT +BEGIN_ENT_SCRIPTDESC( CHL2_Player, CBasePlayer, "The HL2 player entity." ) + + DEFINE_SCRIPTFUNC_NAMED( SuitPower_Drain, "RemoveAuxPower", "Removes from the player's available aux power." ) + DEFINE_SCRIPTFUNC_NAMED( SuitPower_Charge, "AddAuxPower", "Adds to the player's available aux power." ) + DEFINE_SCRIPTFUNC_NAMED( SuitPower_SetCharge, "SetAuxPower", "Sets the player's available aux power." ) + DEFINE_SCRIPTFUNC_NAMED( SuitPower_GetCurrentPercentage, "GetAuxPower", "Gets the player's available aux power." ) + DEFINE_SCRIPTFUNC( GetFlashlightBattery, "Gets the energy available in the player's flashlight. If the legacy (aux power-based) flashlight is enabled, this returns the aux power." ) + + DEFINE_SCRIPTFUNC( InitCustomSuitDevice, "Initializes a custom suit device. (just sets drain rate for now)" ) + DEFINE_SCRIPTFUNC( AddCustomSuitDevice, "Adds a custom suit device ID. (1-3)" ) + DEFINE_SCRIPTFUNC( RemoveCustomSuitDevice, "Removes a custom suit device ID. (1-3)" ) + DEFINE_SCRIPTFUNC( IsCustomSuitDeviceActive, "Checks if a custom suit device is active." ) + +#ifdef SP_ANIM_STATE + DEFINE_SCRIPTFUNC( AddAnimStateLayer, "Adds a custom sequence index as a misc. layer for the singleplayer anim state, wtih parameters for blending in/out, setting the playback rate, holding the animation at the end, and only playing when the player is still." ) +#endif + +END_SCRIPTDESC(); +#endif + CHL2_Player::CHL2_Player() { m_nNumMissPositions = 0; @@ -409,10 +652,23 @@ CHL2_Player::CHL2_Player() #endif CSuitPowerDevice SuitDeviceBreather( bits_SUIT_DEVICE_BREATHER, 6.7f ); // 100 units in 15 seconds (plus three padded seconds) +#ifdef MAPBASE +// Default: 100 units in 8 seconds +CSuitPowerDevice SuitDeviceCustom[] = +{ + { bits_SUIT_DEVICE_CUSTOM0, 12.5f }, + { bits_SUIT_DEVICE_CUSTOM1, 12.5f }, + { bits_SUIT_DEVICE_CUSTOM2, 12.5f }, +}; +#endif + IMPLEMENT_SERVERCLASS_ST(CHL2_Player, DT_HL2_Player) SendPropDataTable(SENDINFO_DT(m_HL2Local), &REFERENCE_SEND_TABLE(DT_HL2Local), SendProxy_SendLocalDataTable), SendPropBool( SENDINFO(m_fIsSprinting) ), +#ifdef SP_ANIM_STATE + SendPropFloat( SENDINFO(m_flAnimRenderYaw), 0, SPROP_NOSCALE ), +#endif END_SEND_TABLE() BEGIN_SEND_TABLE_NOBASE( LadderMove_t, DT_LadderMove ) @@ -424,8 +680,10 @@ BEGIN_SEND_TABLE_NOBASE( LadderMove_t, DT_LadderMove ) SendPropVector( SENDINFO( m_vecStartPosition ) ), END_SEND_TABLE() +#ifndef MAPBASE_VSCRIPT BEGIN_ENT_SCRIPTDESC( CHL2_Player, CBasePlayer, "Half-Life 2 Player" ) END_SCRIPTDESC(); +#endif void CHL2_Player::Precache( void ) { @@ -960,6 +1218,16 @@ void CHL2_Player::PostThink( void ) { HandleAdmireGlovesAnimation(); } + +#ifdef SP_ANIM_STATE + if (m_pPlayerAnimState) + { + QAngle angEyeAngles = EyeAngles(); + m_pPlayerAnimState->Update( angEyeAngles.y, angEyeAngles.x ); + + m_flAnimRenderYaw.Set( m_pPlayerAnimState->GetRenderAngles().y ); + } +#endif } void CHL2_Player::StartAdmireGlovesAnimation( void ) @@ -1055,6 +1323,10 @@ Class_T CHL2_Player::Classify ( void ) if(IsInAVehicle()) { IServerVehicle *pVehicle = GetVehicle(); +#ifdef MAPBASE + if (!pVehicle) + return CLASS_PLAYER; +#endif return pVehicle->ClassifyPassenger( this, CLASS_PLAYER ); } else @@ -1161,6 +1433,119 @@ void CHL2_Player::PlayerRunCommand(CUserCmd *ucmd, IMoveHelper *moveHelper) BaseClass::PlayerRunCommand( ucmd, moveHelper ); } +#ifdef MAPBASE +void CHL2_Player::SpawnedAtPoint( CBaseEntity *pSpawnPoint ) +{ + FirePlayerProxyOutput( "OnPlayerSpawn", variant_t(), this, pSpawnPoint ); +} + +//----------------------------------------------------------------------------- + +ConVar player_use_anim_enabled( "player_use_anim_enabled", "1" ); +ConVar player_use_anim_heavy_mass( "player_use_anim_heavy_mass", "20.0" ); + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +Activity CHL2_Player::Weapon_TranslateActivity( Activity baseAct, bool *pRequired ) +{ + Activity weaponTranslation = BaseClass::Weapon_TranslateActivity( baseAct, pRequired ); + +#if EXPANDED_HL2DM_ACTIVITIES + // +USE activities + // HACKHACK: Make sure m_hUseEntity is a pickup controller first + if ( m_hUseEntity && m_hUseEntity->ClassMatches("player_pickup") && player_use_anim_enabled.GetBool()) + { + CBaseEntity* pHeldEnt = GetPlayerHeldEntity( this ); + float flMass = pHeldEnt ? + (pHeldEnt->VPhysicsGetObject() ? PlayerPickupGetHeldObjectMass( m_hUseEntity, pHeldEnt->VPhysicsGetObject() ) : player_use_anim_heavy_mass.GetFloat()) : + (m_hUseEntity->VPhysicsGetObject() ? m_hUseEntity->GetMass() : player_use_anim_heavy_mass.GetFloat()); + if ( flMass >= player_use_anim_heavy_mass.GetFloat() ) + { + // Heavy versions + switch (baseAct) + { + case ACT_HL2MP_IDLE: weaponTranslation = ACT_HL2MP_IDLE_USE_HEAVY; break; + case ACT_HL2MP_RUN: weaponTranslation = ACT_HL2MP_RUN_USE_HEAVY; break; + case ACT_HL2MP_WALK: weaponTranslation = ACT_HL2MP_WALK_USE_HEAVY; break; + case ACT_HL2MP_IDLE_CROUCH: weaponTranslation = ACT_HL2MP_IDLE_CROUCH_USE_HEAVY; break; + case ACT_HL2MP_WALK_CROUCH: weaponTranslation = ACT_HL2MP_WALK_CROUCH_USE_HEAVY; break; + case ACT_HL2MP_JUMP: weaponTranslation = ACT_HL2MP_JUMP_USE_HEAVY; break; + } + } + else + { + switch (baseAct) + { + case ACT_HL2MP_IDLE: weaponTranslation = ACT_HL2MP_IDLE_USE; break; + case ACT_HL2MP_RUN: weaponTranslation = ACT_HL2MP_RUN_USE; break; + case ACT_HL2MP_WALK: weaponTranslation = ACT_HL2MP_WALK_USE; break; + case ACT_HL2MP_IDLE_CROUCH: weaponTranslation = ACT_HL2MP_IDLE_CROUCH_USE; break; + case ACT_HL2MP_WALK_CROUCH: weaponTranslation = ACT_HL2MP_WALK_CROUCH_USE; break; + case ACT_HL2MP_JUMP: weaponTranslation = ACT_HL2MP_JUMP_USE; break; + } + } + } +#endif + + return weaponTranslation; +} + +#ifdef SP_ANIM_STATE +// Set the activity based on an event or current state +void CHL2_Player::SetAnimation( PLAYER_ANIM playerAnim ) +{ + if (!m_pPlayerAnimState) + { + BaseClass::SetAnimation( playerAnim ); + return; + } + + m_pPlayerAnimState->SetPlayerAnimation( playerAnim ); +} + +void CHL2_Player::AddAnimStateLayer( int iSequence, float flBlendIn, float flBlendOut, float flPlaybackRate, bool bHoldAtEnd, bool bOnlyWhenStill ) +{ + if (!m_pPlayerAnimState) + return; + + m_pPlayerAnimState->AddMiscSequence( iSequence, flBlendIn, flBlendOut, flPlaybackRate, bHoldAtEnd, bOnlyWhenStill ); +} +#endif + +//----------------------------------------------------------------------------- +// Purpose: model-change notification. Fires on dynamic load completion as well +//----------------------------------------------------------------------------- +CStudioHdr *CHL2_Player::OnNewModel() +{ + CStudioHdr *hdr = BaseClass::OnNewModel(); + +#ifdef SP_ANIM_STATE + // Clears the animation state if we already have one. + if ( m_pPlayerAnimState != NULL ) + { + m_pPlayerAnimState->Release(); + m_pPlayerAnimState = NULL; + } + + if ( hdr && hdr->HaveSequenceForActivity(ACT_HL2MP_IDLE) && hl2_use_sp_animstate.GetBool() ) + { + // Here we create and init the player animation state. + m_pPlayerAnimState = CreatePlayerAnimationState(this); + } + else + { + m_flAnimRenderYaw = FLT_MAX; + } +#endif + + return hdr; +} + +extern char g_szDefaultPlayerModel[MAX_PATH]; +extern bool g_bDefaultPlayerDrawExternally; +#endif + //----------------------------------------------------------------------------- // Purpose: Sets HL2 specific defaults. //----------------------------------------------------------------------------- @@ -1169,12 +1554,30 @@ void CHL2_Player::Spawn(void) #ifndef HL2MP #ifndef PORTAL +#ifdef MAPBASE + if ( GetModelName() == NULL_STRING ) + SetModel( g_szDefaultPlayerModel ); +#else SetModel( "models/player.mdl" ); #endif +#endif #endif BaseClass::Spawn(); +#ifdef MAPBASE + // Ported from CHL2MP_Player. Fixes issues with respawning players in SP + if ( !IsObserver() ) + { + pl.deadflag = false; + RemoveSolidFlags( FSOLID_NOT_SOLID ); + + RemoveEffects( EF_NODRAW ); + } + + SetDrawPlayerModelExternally( g_bDefaultPlayerDrawExternally ); +#endif + // // Our player movement speed is set once here. This will override the cl_xxxx // cvars unless they are set to be lower than this. @@ -1314,7 +1717,11 @@ void CHL2_Player::ToggleZoom(void) //----------------------------------------------------------------------------- void CHL2_Player::StartZooming( void ) { +#ifdef MAPBASE + int iFOV = GetPlayerProxy() ? GetPlayerProxy()->m_SuitZoomFOV : 25; +#else int iFOV = 25; +#endif if ( SetFOV( this, iFOV, 0.4f ) ) { m_HL2Local.m_bZooming = true; @@ -1383,6 +1790,14 @@ void CHL2_Player::InitVCollision( const Vector &vecAbsOrigin, const Vector &vecA CHL2_Player::~CHL2_Player( void ) { +#ifdef SP_ANIM_STATE + // Clears the animation state. + if ( m_pPlayerAnimState != NULL ) + { + m_pPlayerAnimState->Release(); + m_pPlayerAnimState = NULL; + } +#endif } //----------------------------------------------------------------------------- @@ -1399,10 +1814,49 @@ bool CHL2_Player::CommanderFindGoal( commandgoal_t *pGoal ) //--------------------------------- // MASK_SHOT on purpose! So that you don't hit the invisible hulls of the NPCs. +#ifdef MAPBASE + // Get either our +USE entity or the gravity gun entity + CBaseEntity *pHeldEntity = GetPlayerHeldEntity(this); + if ( !pHeldEntity ) + pHeldEntity = PhysCannonGetHeldEntity( GetActiveWeapon() ); + + CTraceFilterSkipTwoEntities filter( this, pHeldEntity, COLLISION_GROUP_INTERACTIVE_DEBRIS ); +#else CTraceFilterSkipTwoEntities filter( this, PhysCannonGetHeldEntity( GetActiveWeapon() ), COLLISION_GROUP_INTERACTIVE_DEBRIS ); +#endif UTIL_TraceLine( EyePosition(), EyePosition() + forward * MAX_COORD_RANGE, MASK_SHOT, &filter, &tr ); +#ifdef MAPBASE + // func_commandredirect handling + if (g_pCommandRedirects.Count() > 0) + { + for (int i = 0; i < g_pCommandRedirects.Count(); i++) + { + CCommandRedirect *pCommandRedirect = static_cast(g_pCommandRedirects[i]); + if (!pCommandRedirect || pCommandRedirect->IsDisabled() || !pCommandRedirect->PointIsWithin(tr.endpos)) + continue; + + // First, give it our allies so it could fire outputs + pCommandRedirect->HandleAllies(m_pPlayerAISquad, this); + + Vector vec = tr.endpos; + if (pCommandRedirect->TestRedirect(&vec, this)) + { + // If it returned a 0 vector, cancel the command + if (vec.IsZero()) + { + return false; + } + + // Just set our goal to this and skip the code below which checks the target position's validity + pGoal->m_vecGoalLocation = vec; + return true; + } + } + } + //else +#endif if( !tr.DidHitWorld() ) { CUtlVector Allies; @@ -1523,7 +1977,11 @@ void CHL2_Player::CommanderUpdate() { CAI_BaseNPC *pCommandRepresentative = GetSquadCommandRepresentative(); bool bFollowMode = false; +#ifdef MAPBASE + if ( pCommandRepresentative && !HasSpawnFlags(SF_PLAYER_HIDE_SQUAD_HUD) ) +#else if ( pCommandRepresentative ) +#endif { bFollowMode = ( pCommandRepresentative->GetCommandGoal() == vec3_invalid ); @@ -1569,8 +2027,22 @@ void CHL2_Player::CommanderUpdate() { m_CommanderUpdateTimer.Set(2.5); +#ifdef MAPBASE + if ( pCommandRepresentative->ShouldAutoSummon() ) + { + if (!HL2GameRules()->AutosummonDisabled() && player_squad_autosummon_enabled.GetBool()) + CommanderExecute( CC_FOLLOW ); + else + { + // Show a hud hint if autosummoning has been disabled + UTIL_HudHintText( this, "#Valve_Hint_Command_recall" ); + //m_CommanderUpdateTimer.Set(10.0); + } + } +#else if ( pCommandRepresentative->ShouldAutoSummon() ) CommanderExecute( CC_FOLLOW ); +#endif } } @@ -1698,6 +2170,95 @@ void CHL2_Player::CommanderMode() } } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CHL2_Player::InputSquadForceSummon( inputdata_t &inputdata ) +{ + CommanderExecute( CC_FOLLOW ); +} + +//----------------------------------------------------------------------------- +// Purpose: Forces the player's squad to go to a specific location or entity. +//----------------------------------------------------------------------------- +void CHL2_Player::InputSquadForceGoTo( inputdata_t &inputdata ) +{ + CAI_BaseNPC *pPlayerSquadLeader = GetSquadCommandRepresentative(); + + if ( !pPlayerSquadLeader ) + return; + + int i; + CUtlVector Allies; + commandgoal_t goal; + + variant_t var = Variant_ParseInput(inputdata); + + if (var.FieldType() == FIELD_VECTOR) + { + goal.m_pGoalEntity = NULL; + var.Vector3D(goal.m_vecGoalLocation); + } + else + { + goal.m_pGoalEntity = var.FieldType() == FIELD_EHANDLE ? var.Entity().Get() : gEntList.FindEntityByNameNearest(var.String(), pPlayerSquadLeader->GetAbsOrigin(), 0, this, inputdata.pActivator, inputdata.pCaller); + goal.m_vecGoalLocation = vec3_invalid; + } + + AISquadIter_t iter; + for ( CAI_BaseNPC *pAllyNpc = m_pPlayerAISquad->GetFirstMember(&iter); pAllyNpc; pAllyNpc = m_pPlayerAISquad->GetNextMember(&iter) ) + { + if ( pAllyNpc->IsCommandable() ) + Allies.AddToTail( pAllyNpc ); + } + + CAI_BaseNPC * pTargetNpc = (goal.m_pGoalEntity) ? goal.m_pGoalEntity->MyNPCPointer() : NULL; + + bool bHandled = false; + if( pTargetNpc ) + { + bHandled = !CommanderExecuteOne( pTargetNpc, goal, Allies.Base(), Allies.Count() ); + } + + for ( i = 0; !bHandled && i < Allies.Count(); i++ ) + { + if ( Allies[i] != pTargetNpc && Allies[i]->IsPlayerAlly() ) + { + bHandled = !CommanderExecuteOne( Allies[i], goal, Allies.Base(), Allies.Count() ); + } + } + + //CommanderExecute( CC_SEND ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CHL2_Player::InputEnableGeigerCounter( inputdata_t &inputdata ) +{ + RemoveSpawnFlags(SF_PLAYER_NO_GEIGER); +} + +void CHL2_Player::InputDisableGeigerCounter( inputdata_t &inputdata ) +{ + AddSpawnFlags(SF_PLAYER_NO_GEIGER); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CHL2_Player::InputShowSquadHUD( inputdata_t &inputdata ) +{ + RemoveSpawnFlags(SF_PLAYER_HIDE_SQUAD_HUD); +} + +void CHL2_Player::InputHideSquadHUD( inputdata_t &inputdata ) +{ + AddSpawnFlags(SF_PLAYER_HIDE_SQUAD_HUD); +} +#endif + //----------------------------------------------------------------------------- // Purpose: // Input : iImpulse - @@ -2266,6 +2827,72 @@ void CHL2_Player::InputIgnoreFallDamageWithoutReset( inputdata_t &inputdata ) m_bIgnoreFallDamageResetAfterImpact = false; } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CHL2_Player::InputAddArmor( inputdata_t &inputdata ) +{ + int iArmor = MIN((GetPlayerProxy() ? GetPlayerProxy()->m_MaxArmor : 100) - ArmorValue(), inputdata.value.Int()); + + IncrementArmorValue( iArmor ); +} + +//----------------------------------------------------------------------------- +// This can also add to the player's armor by passing a negative input +// which can bypass the maximum armor set in the player proxy. +//----------------------------------------------------------------------------- +void CHL2_Player::InputRemoveArmor( inputdata_t &inputdata ) +{ + int iArmor = MIN(ArmorValue(), inputdata.value.Int()); + + IncrementArmorValue( -iArmor ); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CHL2_Player::InputSetArmor( inputdata_t &inputdata ) +{ + int iArmor = MIN(GetPlayerProxy() ? GetPlayerProxy()->m_MaxArmor : 100, inputdata.value.Int()); + + SetArmorValue( iArmor ); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CHL2_Player::InputAddAuxPower( inputdata_t &inputdata ) +{ + SuitPower_Charge( inputdata.value.Float() ); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CHL2_Player::InputRemoveAuxPower( inputdata_t &inputdata ) +{ + SuitPower_Drain( inputdata.value.Float() ); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CHL2_Player::InputSetAuxPower( inputdata_t &inputdata ) +{ + SuitPower_SetCharge( inputdata.value.Float() ); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CHL2_Player::InputTurnFlashlightOn( inputdata_t &inputdata ) +{ + FlashlightTurnOn(); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CHL2_Player::InputTurnFlashlightOff( inputdata_t &inputdata ) +{ + FlashlightTurnOff(); +} +#endif + //----------------------------------------------------------------------------- // Purpose: Notification of a player's npc ally in the players squad being killed //----------------------------------------------------------------------------- @@ -2276,6 +2903,10 @@ void CHL2_Player::OnSquadMemberKilled( inputdata_t &data ) user.MakeReliable(); UserMessageBegin( user, "SquadMemberDied" ); MessageEnd(); + +#ifdef MAPBASE + FirePlayerProxyOutput("OnSquadMemberKilled", data.value, data.pActivator, data.value.Entity()); +#endif } //----------------------------------------------------------------------------- @@ -2377,6 +3008,10 @@ int CHL2_Player::OnTakeDamage( const CTakeDamageInfo &info ) gamestats->Event_PlayerDamage( this, info ); +#ifdef MAPBASE + FirePlayerProxyOutput("PlayerDamaged", variant_t(), info.GetAttacker(), this); +#endif + return BaseClass::OnTakeDamage( playerDamage ); } @@ -2490,7 +3125,19 @@ void CHL2_Player::Event_Killed( const CTakeDamageInfo &info ) { BaseClass::Event_Killed( info ); +#ifdef MAPBASE + FirePlayerProxyOutput( "PlayerDied", variant_t(), info.GetAttacker(), this ); + + if (IsSuitEquipped()) + { + // Make sure all devices are deactivated (for respawn) + m_HL2Local.m_bitsActiveDevices = 0x00000000; + m_HL2Local.m_flSuitPowerLoad = 0; + m_HL2Local.m_flTimeAllSuitDevicesOff = gpGlobals->curtime; + } +#else FirePlayerProxyOutput( "PlayerDied", variant_t(), this, this ); +#endif NotifyScriptsOfDeath(); { @@ -2612,6 +3259,15 @@ bool CHL2_Player::ShouldKeepLockedAutoaimTarget( EHANDLE hLockedTarget ) return true; } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +bool CHL2_Player::CanAutoSwitchToNextBestWeapon( CBaseCombatWeapon *pWeapon ) +{ + return player_autoswitch_enabled.GetBool(); +} +#endif + //----------------------------------------------------------------------------- // Purpose: // Input : iCount - @@ -2653,6 +3309,9 @@ int CHL2_Player::GiveAmmo( int nCount, int nAmmoIndex, bool bSuppressSound) if ( pWeapon && pWeapon->GetPrimaryAmmoType() == nAmmoIndex ) { +#ifdef MAPBASE + if (CanAutoSwitchToNextBestWeapon(pWeapon)) +#endif SwitchToNextBestWeapon(GetActiveWeapon()); } } @@ -2665,12 +3324,45 @@ int CHL2_Player::GiveAmmo( int nCount, int nAmmoIndex, bool bSuppressSound) bool CHL2_Player::Weapon_CanUse( CBaseCombatWeapon *pWeapon ) { #ifndef HL2MP +#ifdef MAPBASE + if ( pWeapon->ClassMatches( "weapon_stunstick" ) ) + { + switch (HL2GameRules()->GetStunstickPickupBehavior()) + { + // Default, including 0 + default: + { + if ( ApplyBattery( 0.5 ) ) + UTIL_Remove( pWeapon ); + return false; + } break; + + // Allow pickup, if already picked up just apply battery + case 1: + { + if ( Weapon_OwnsThisType("weapon_stunstick") ) + { + if ( ApplyBattery( 0.5 ) ) + UTIL_Remove( pWeapon ); + return false; + } + } break; + + // Don't pickup, don't even apply battery + case 2: return false; + + // Just pickup, never apply battery + case 3: break; + } + } +#else if ( pWeapon->ClassMatches( "weapon_stunstick" ) ) { if ( ApplyBattery( 0.5 ) ) UTIL_Remove( pWeapon ); return false; } +#endif #endif return BaseClass::Weapon_CanUse( pWeapon ); @@ -2962,6 +3654,20 @@ void CHL2_Player::UpdateWeaponPosture( void ) { m_LowerWeaponTimer.Set( .3 ); VPROF( "CHL2_Player::UpdateWeaponPosture-CheckLower" ); + +#ifdef MAPBASE + if (m_nButtons & IN_VGUIMODE) + { + //We're over a friendly, drop our weapon + if (Weapon_Lower() == false) + { + //FIXME: We couldn't lower our weapon! + } + + return; + } +#endif // MAPBASE + Vector vecAim = BaseClass::GetAutoaimVector( AUTOAIM_SCALE_DIRECT_ONLY ); const float CHECK_FRIENDLY_RANGE = 50 * 12; @@ -3189,6 +3895,11 @@ float CHL2_Player::GetHeldObjectMass( IPhysicsObject *pHeldObject ) return mass; } +CBaseEntity *CHL2_Player::GetHeldObject( void ) +{ + return PhysCannonGetHeldEntity( GetActiveWeapon() ); +} + //----------------------------------------------------------------------------- // Purpose: Force the player to drop any physics objects he's carrying //----------------------------------------------------------------------------- @@ -3333,7 +4044,10 @@ Vector CHL2_Player::EyeDirection2D( void ) Vector CHL2_Player::EyeDirection3D( void ) { Vector vecForward; - +#ifdef MAPBASE + EyeVectors( &vecForward ); + return vecForward; +#else // Return the vehicle angles if we request them if ( GetVehicle() != NULL ) { @@ -3344,6 +4058,7 @@ Vector CHL2_Player::EyeDirection3D( void ) AngleVectors( EyeAngles(), &vecForward ); return vecForward; +#endif } @@ -3701,6 +4416,64 @@ void CHL2_Player::DisplayLadderHudHint() #endif//CLIENT_DLL } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CHL2_Player::InitCustomSuitDevice( int iDeviceID, float flDrainRate ) +{ + if (iDeviceID < 0 || iDeviceID > 2) + { + Warning("InitCustomSuitDevice : \"%i\" is not a valid custom device slot\n", iDeviceID); + return; + } + + SuitDeviceCustom[iDeviceID].SetDeviceDrainRate( flDrainRate ); +} + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CHL2_Player::AddCustomSuitDevice( int iDeviceID ) +{ + if (iDeviceID < 0 || iDeviceID > 2) + { + Warning("AddCustomSuitDevice : \"%i\" is not a valid custom device slot\n", iDeviceID); + return; + } + + SuitPower_AddDevice( SuitDeviceCustom[iDeviceID] ); +} + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CHL2_Player::RemoveCustomSuitDevice( int iDeviceID ) +{ + if (iDeviceID < 0 || iDeviceID > 2) + { + Warning("AddCustomSuitDevice : \"%i\" is not a valid custom device slot\n", iDeviceID); + return; + } + + SuitPower_RemoveDevice( SuitDeviceCustom[iDeviceID] ); +} + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +bool CHL2_Player::IsCustomSuitDeviceActive( int iDeviceID ) +{ + if (iDeviceID < 0 || iDeviceID > 2) + { + Warning("IsCustomSuitDeviceActive : \"%i\" is not a valid custom device slot\n", iDeviceID); + return false; + } + + return SuitPower_IsDeviceActive( SuitDeviceCustom[iDeviceID] ); +} +#endif + //----------------------------------------------------------------------------- // Shuts down sounds //----------------------------------------------------------------------------- @@ -3732,6 +4505,22 @@ void CHL2_Player::ModifyOrAppendPlayerCriteria( AI_CriteriaSet& set ) } } +#ifdef MAPBASE +const char *CHL2_Player::GetOverrideStepSound( const char *pszBaseStepSoundName ) +{ + int idx = FindContextByName("footsteps"); + if (idx != -1) + { + const char *szSound = GetContextValue(idx); + if (szSound[0] != '\0') + { + return szSound; + } + } + return pszBaseStepSoundName; +} +#endif + //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- @@ -3786,6 +4575,9 @@ CLogicPlayerProxy *CHL2_Player::GetPlayerProxy( void ) pProxy->m_hPlayer = this; m_hPlayerProxy = pProxy; +#ifdef MAPBASE + pProxy->NotifyPlayerHasProxy(); +#endif } return pProxy; @@ -3809,6 +4601,15 @@ BEGIN_DATADESC( CLogicPlayerProxy ) DEFINE_OUTPUT( m_PlayerHasNoAmmo, "PlayerHasNoAmmo" ), DEFINE_OUTPUT( m_PlayerDied, "PlayerDied" ), DEFINE_OUTPUT( m_PlayerMissedAR2AltFire, "PlayerMissedAR2AltFire" ), +#ifdef MAPBASE + DEFINE_OUTPUT( m_PlayerDamaged, "PlayerDamaged" ), + DEFINE_OUTPUT( m_OnSquadMemberKilled, "OnSquadMemberKilled" ), + DEFINE_OUTPUT( m_OnGetAmmo, "OnGetAmmo" ), + DEFINE_OUTPUT( m_RequestedPlayerArmor, "PlayerArmor" ), + DEFINE_OUTPUT( m_RequestedPlayerAuxPower, "PlayerAuxPower" ), + DEFINE_OUTPUT( m_RequestedPlayerFlashBattery, "PlayerFlashBattery" ), + DEFINE_OUTPUT( m_OnPlayerSpawn, "OnPlayerSpawn" ), +#endif DEFINE_INPUTFUNC( FIELD_VOID, "RequestPlayerHealth", InputRequestPlayerHealth ), DEFINE_INPUTFUNC( FIELD_VOID, "SetFlashlightSlowDrain", InputSetFlashlightSlowDrain ), DEFINE_INPUTFUNC( FIELD_VOID, "SetFlashlightNormalDrain", InputSetFlashlightNormalDrain ), @@ -3821,6 +4622,19 @@ BEGIN_DATADESC( CLogicPlayerProxy ) #ifdef PORTAL DEFINE_INPUTFUNC( FIELD_VOID, "SuppressCrosshair", InputSuppressCrosshair ), #endif // PORTAL +#ifdef MAPBASE + DEFINE_INPUTFUNC( FIELD_VOID, "RequestPlayerArmor", InputRequestPlayerArmor ), + DEFINE_INPUTFUNC( FIELD_VOID, "RequestPlayerAuxPower", InputRequestPlayerAuxPower ), + DEFINE_INPUTFUNC( FIELD_VOID, "RequestPlayerFlashBattery", InputRequestPlayerFlashBattery ), + DEFINE_INPUTFUNC( FIELD_STRING, "GetAmmoOnWeapon", InputGetAmmoOnWeapon ), + DEFINE_INPUTFUNC( FIELD_STRING, "SetHandModel", InputSetHandModel ), + DEFINE_INPUTFUNC( FIELD_INTEGER, "SetHandModelSkin", InputSetHandModelSkin ), + DEFINE_INPUTFUNC( FIELD_INTEGER, "SetHandModelBodyGroup", InputSetHandModelBodyGroup ), + DEFINE_INPUTFUNC( FIELD_STRING, "SetPlayerModel", InputSetPlayerModel ), + DEFINE_INPUTFUNC( FIELD_BOOLEAN, "SetPlayerDrawExternally", InputSetPlayerDrawExternally ), + DEFINE_INPUT( m_MaxArmor, FIELD_INTEGER, "SetMaxInputArmor" ), + DEFINE_INPUT( m_SuitZoomFOV, FIELD_INTEGER, "SetSuitZoomFOV" ), +#endif DEFINE_FIELD( m_hPlayer, FIELD_EHANDLE ), END_DATADESC() @@ -3834,12 +4648,153 @@ void CLogicPlayerProxy::Activate( void ) } } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: Cache user entity field values until spawn is called. +// Input : szKeyName - Key to handle. +// szValue - Value for key. +// Output : Returns true if the key was handled, false if not. +//----------------------------------------------------------------------------- +bool CLogicPlayerProxy::KeyValue( const char *szKeyName, const char *szValue ) +{ + bool bPlayerKV = false; + + if (Q_strnicmp(szKeyName, "HandsVM", 7) == 0) + { + if (m_hPlayer) + { + szKeyName += 7; + CBasePlayer *pPlayer = static_cast( m_hPlayer.Get() ); + CBaseViewModel *vm = pPlayer->GetViewModel(1); + if (vm) + { + if (*szKeyName == NULL && PrecacheModel(szValue)) // HandsVM + vm->SetModel(szValue); + else if (FStrEq(szKeyName, "Skin")) // HandsVMSkin + vm->m_nSkin = atoi(szValue); + else if (FStrEq(szKeyName, "Body")) // HandsVMBody + vm->m_nBody = atoi(szValue); + } + return true; + } + } + else if (FStrEq(szKeyName, "ResponseContext")) + { + bPlayerKV = true; + if (m_hPlayer) + return m_hPlayer->KeyValue(szKeyName, szValue); + } + else if (FStrEq(szKeyName, "HideSquadHUD")) + { + if (m_hPlayer) + { + if (szValue[0] != '0') + m_hPlayer->AddSpawnFlags(SF_PLAYER_HIDE_SQUAD_HUD); + else + m_hPlayer->RemoveSpawnFlags(SF_PLAYER_HIDE_SQUAD_HUD); + return true; + } + } + else if (FStrEq(szKeyName, "PlayerModel")) + { + if (m_hPlayer) + { + if (PrecacheModel( szValue )) + { + m_hPlayer->SetModel( szValue ); + } + return true; + } + } + else + { + if (BaseClass::KeyValue( szKeyName, szValue )) + return true; + + if (m_hPlayer) + { + DevMsg("logic_playerproxy: Passing unhandled keyvalue \"%s, %s\" to player\n", szKeyName, szValue); + return m_hPlayer->KeyValue(szKeyName, szValue); + } + } + + // If we reach this point, player is not available to test unidentified/special KV + // Queue it up + DevMsg("logic_playerproxy: Queueing %s, %s\n", szKeyName, szValue); + m_QueuedKV.Insert(bPlayerKV ? UTIL_VarArgs("&&%s", szKeyName) : szKeyName, AllocPooledString(szValue)); + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: calls the appropriate message mapped function in the entity according +// to the fired action. +// Input : char *szInputName - input destination +// *pActivator - entity which initiated this sequence of actions +// *pCaller - entity from which this event is sent +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CLogicPlayerProxy::AcceptInput( const char *szInputName, CBaseEntity *pActivator, CBaseEntity *pCaller, variant_t Value, int outputID ) +{ + bool base = BaseClass::AcceptInput( szInputName, pActivator, pCaller, Value, outputID ); + + if (!base) + { + if (m_hPlayer) + { + DevMsg("logic_playerproxy: Passing unhandled input \"%s\" to player\n", szInputName); + return m_hPlayer->AcceptInput( szInputName, pActivator, pCaller, Value, outputID ); + } + else + { + DevMsg("logic_playerproxy: Player not found!\n"); + + // Need to allocate the string here in case szInputName is freed before the input fires + g_EventQueue.AddEvent("!player", STRING( AllocPooledString(szInputName) ), Value, 0.01f, pActivator, pCaller); + } + } + + return base; +} + +//----------------------------------------------------------------------------- +// Purpose: Notifies logic_playerproxy when player is valid +//----------------------------------------------------------------------------- +void CLogicPlayerProxy::NotifyPlayerHasProxy() +{ + Assert( m_hPlayer != NULL ); + + // Handle any queued keyvalues + int iQueueCount = m_QueuedKV.Count(); + for (int i = 0; i < iQueueCount; i++) + { + const char *name = m_QueuedKV.GetElementName(i); + const char *value = STRING(m_QueuedKV[i]); + DevMsg("logic_playerproxy: Handing over %s, %s from dict\n", name, value); + + if (name[0] == '&' && name[1] == '&') + { + // We're supposed to send this to the player + m_hPlayer->KeyValue(name + 2, value); + } + + KeyValue(name, value); + } + + m_QueuedKV.RemoveAll(); +} +#endif + bool CLogicPlayerProxy::PassesDamageFilter( const CTakeDamageInfo &info ) { if (m_hDamageFilter) { CBaseFilter *pFilter = (CBaseFilter *)(m_hDamageFilter.Get()); +#ifdef MAPBASE + return pFilter->PassesDamageFilter(m_hPlayer.Get(), info); +#else return pFilter->PassesDamageFilter(info); +#endif } return true; @@ -3862,6 +4817,43 @@ void CLogicPlayerProxy::InputRequestPlayerHealth( inputdata_t &inputdata ) m_RequestedPlayerHealth.Set( m_hPlayer->GetHealth(), inputdata.pActivator, inputdata.pCaller ); } +#ifdef MAPBASE +void CLogicPlayerProxy::InputRequestPlayerArmor( inputdata_t &inputdata ) +{ + if ( m_hPlayer == NULL ) + return; + + m_RequestedPlayerArmor.Set( static_cast(m_hPlayer.Get())->ArmorValue(), inputdata.pActivator, inputdata.pCaller ); +} + +void CLogicPlayerProxy::InputRequestPlayerAuxPower( inputdata_t &inputdata ) +{ + if ( m_hPlayer == NULL ) + return; + + m_RequestedPlayerAuxPower.Set( static_cast(m_hPlayer.Get())->SuitPower_GetCurrentPercentage(), inputdata.pActivator, inputdata.pCaller ); +} + +void CLogicPlayerProxy::InputRequestPlayerFlashBattery( inputdata_t &inputdata ) +{ + if ( m_hPlayer == NULL ) + return; + + m_RequestedPlayerFlashBattery.Set( static_cast(m_hPlayer.Get())->GetFlashlightBattery(), inputdata.pActivator, inputdata.pCaller ); +} + +// If it's the EP2 flashlight, it returns the flashlight battery. If it's the legacy flashlight, it returns the aux power. +// Note that this is on CHL2_Player, not CLogicPlayerProxy. +float CHL2_Player::GetFlashlightBattery() +{ +#ifdef HL2_EPISODIC + return Flashlight_UseLegacyVersion() ? SuitPower_GetCurrentPercentage() : m_HL2Local.m_flFlashBattery; +#else + return SuitPower_GetCurrentPercentage(); +#endif +} +#endif + void CLogicPlayerProxy::InputSetFlashlightSlowDrain( inputdata_t &inputdata ) { if( m_hPlayer == NULL ) @@ -3908,6 +4900,99 @@ void CLogicPlayerProxy::InputRequestAmmoState( inputdata_t &inputdata ) m_PlayerHasNoAmmo.FireOutput( this, this, 0 ); } +#ifdef MAPBASE +void CLogicPlayerProxy::InputGetAmmoOnWeapon( inputdata_t &inputdata ) +{ + if( m_hPlayer == NULL ) + return; + + CHL2_Player *pPlayer = dynamic_cast(m_hPlayer.Get()); + + const char *szClass = inputdata.value.String(); + + // Support secondary cases + bool bAmmo2 = szClass[0] == '@'; + if (bAmmo2) + szClass++; + + bool bClipOnly = szClass[0] == '#'; + if (bClipOnly) + szClass++; + + if (szClass[0] != NULL) + { + // Find weapon that matches class + for ( int i = 0 ; i < pPlayer->WeaponCount(); ++i ) + { + CBaseCombatWeapon* pCheck = pPlayer->GetWeapon( i ); + + if ( pCheck && FClassnameIs(pCheck, szClass) ) + { + int ammo = 0; + if (!bAmmo2) + { + // Ammo 1 + if (!bClipOnly) + ammo = pPlayer->GetAmmoCount(pCheck->GetPrimaryAmmoType()); + + if (pCheck->UsesClipsForAmmo1()) + ammo += pCheck->m_iClip1; + else + ammo += pCheck->GetPrimaryAmmoCount(); + } + else + { + // Ammo 2 + if (!bClipOnly) + ammo = pPlayer->GetAmmoCount(pCheck->GetSecondaryAmmoType()); + + if (pCheck->UsesClipsForAmmo2()) + ammo += pCheck->m_iClip2; + else + ammo += pCheck->GetSecondaryAmmoCount(); + } + + m_OnGetAmmo.Set( ammo, this, 0 ); + return; + } + } + } + else + { + // Get current weapon ammo + if (CBaseCombatWeapon *pCheck = pPlayer->GetActiveWeapon()) + { + int ammo = 0; + if (!bAmmo2) + { + // Ammo 1 + if (!bClipOnly) + ammo = pPlayer->GetAmmoCount(pCheck->GetPrimaryAmmoType()); + + if (pCheck->UsesClipsForAmmo1()) + ammo += pCheck->m_iClip1; + else + ammo += pCheck->GetPrimaryAmmoCount(); + } + else + { + // Ammo 2 + if (!bClipOnly) + ammo = pPlayer->GetAmmoCount(pCheck->GetSecondaryAmmoType()); + + if (pCheck->UsesClipsForAmmo2()) + ammo += pCheck->m_iClip2; + else + ammo += pCheck->GetSecondaryAmmoCount(); + } + + m_OnGetAmmo.Set( ammo, this, 0 ); + return; + } + } +} +#endif + void CLogicPlayerProxy::InputLowerWeapon( inputdata_t &inputdata ) { if( m_hPlayer == NULL ) @@ -3963,3 +5048,73 @@ void CLogicPlayerProxy::InputSuppressCrosshair( inputdata_t &inputdata ) pPlayer->SuppressCrosshair( true ); } #endif // PORTAL + +#ifdef MAPBASE +void CLogicPlayerProxy::InputSetHandModel( inputdata_t &inputdata ) +{ + if (!m_hPlayer) + return; + + string_t iszModel = inputdata.value.StringID(); + + if (iszModel != NULL_STRING) + PrecacheModel(STRING(iszModel)); + + CBasePlayer *pPlayer = static_cast( m_hPlayer.Get() ); + CBaseViewModel *vm = pPlayer->GetViewModel(1); + if (vm) + vm->SetModel(STRING(iszModel)); +} + +void CLogicPlayerProxy::InputSetHandModelSkin( inputdata_t &inputdata ) +{ + if (!m_hPlayer) + return; + + CBasePlayer *pPlayer = static_cast( m_hPlayer.Get() ); + CBaseViewModel *vm = pPlayer->GetViewModel(1); + if (vm) + vm->m_nSkin = inputdata.value.Int(); +} + +void CLogicPlayerProxy::InputSetHandModelBodyGroup( inputdata_t &inputdata ) +{ + if (!m_hPlayer) + return; + + CBasePlayer *pPlayer = static_cast( m_hPlayer.Get() ); + CBaseViewModel *vm = pPlayer->GetViewModel(1); + if (vm) + vm->m_nBody = inputdata.value.Int(); +} + +void CLogicPlayerProxy::InputSetPlayerModel( inputdata_t &inputdata ) +{ + if (!m_hPlayer) + return; + + string_t iszModel = inputdata.value.StringID(); + + if (iszModel != NULL_STRING) + PrecacheModel( STRING( iszModel ) ); + else + { + // We're resetting the model. The original model should've been cached to our own model name. + iszModel = GetModelName(); + } + + // Cache the original model as our own model name. + SetModelName( m_hPlayer->GetModelName() ); + + m_hPlayer->SetModel( STRING(iszModel) ); +} + +void CLogicPlayerProxy::InputSetPlayerDrawExternally( inputdata_t &inputdata ) +{ + if (!m_hPlayer) + return; + + CBasePlayer *pPlayer = static_cast(m_hPlayer.Get()); + pPlayer->SetDrawPlayerModelExternally( inputdata.value.Bool() ); +} +#endif diff --git a/src/game/server/hl2/hl2_player.h b/src/game/server/hl2/hl2_player.h index fb838793..5e566022 100644 --- a/src/game/server/hl2/hl2_player.h +++ b/src/game/server/hl2/hl2_player.h @@ -15,6 +15,13 @@ #include "simtimer.h" #include "soundenvelope.h" +// In HL2MP we need to inherit from BaseMultiplayerPlayer! +#if defined ( HL2MP ) +#include "basemultiplayerplayer.h" +#elif defined ( MAPBASE ) +#include "mapbase/singleplayer_animstate.h" +#endif + class CAI_Squad; class CPropCombineBall; @@ -70,15 +77,26 @@ public: else return m_flDrainRate; } +#ifdef MAPBASE + void SetDeviceDrainRate( float flDrainRate ) { m_flDrainRate = flDrainRate; } +#endif }; //============================================================================= // >> HL2_PLAYER //============================================================================= +#if defined ( HL2MP ) +class CHL2_Player : public CBaseMultiplayerPlayer +#else class CHL2_Player : public CBasePlayer +#endif { public: +#if defined ( HL2MP ) + DECLARE_CLASS( CHL2_Player, CBaseMultiplayerPlayer ); +#else DECLARE_CLASS( CHL2_Player, CBasePlayer ); +#endif CHL2_Player(); ~CHL2_Player( void ); @@ -108,6 +126,23 @@ public: virtual void Splash( void ); virtual void ModifyOrAppendPlayerCriteria( AI_CriteriaSet& set ); +#ifdef MAPBASE + // For the logic_playerproxy output + void SpawnedAtPoint( CBaseEntity *pSpawnPoint ); + + Activity Weapon_TranslateActivity( Activity baseAct, bool *pRequired = NULL ); + +#ifdef SP_ANIM_STATE + void SetAnimation( PLAYER_ANIM playerAnim ); + + void AddAnimStateLayer( int iSequence, float flBlendIn = 0.0f, float flBlendOut = 0.0f, float flPlaybackRate = 1.0f, bool bHoldAtEnd = false, bool bOnlyWhenStill = false ); +#endif + + virtual CStudioHdr* OnNewModel(); + + virtual const char *GetOverrideStepSound( const char *pszBaseStepSoundName ); +#endif + void DrawDebugGeometryOverlays(void); virtual Vector EyeDirection2D( void ); @@ -140,6 +175,11 @@ public: void SetFlashlightEnabled( bool bState ); +#ifdef MAPBASE + // Needed for logic_playerproxy + float GetFlashlightBattery(); +#endif + // Apply a battery bool ApplyBattery( float powerMultiplier = 1.0 ); @@ -160,6 +200,17 @@ public: int GetNumSquadCommandables(); int GetNumSquadCommandableMedics(); +#ifdef MAPBASE + void InputSquadForceSummon( inputdata_t &inputdata ); + void InputSquadForceGoTo( inputdata_t &inputdata ); + + void InputEnableGeigerCounter( inputdata_t &inputdata ); + void InputDisableGeigerCounter( inputdata_t &inputdata ); + + void InputShowSquadHUD( inputdata_t &inputdata ); + void InputHideSquadHUD( inputdata_t &inputdata ); +#endif + // Locator void UpdateLocatorPosition( const Vector &vecPosition ); @@ -196,6 +247,19 @@ public: void InputEnableFlashlight( inputdata_t &inputdata ); void InputDisableFlashlight( inputdata_t &inputdata ); +#ifdef MAPBASE + void InputAddArmor( inputdata_t &inputdata ); + void InputRemoveArmor( inputdata_t &inputdata ); + void InputSetArmor( inputdata_t &inputdata ); + + void InputAddAuxPower( inputdata_t &inputdata ); + void InputRemoveAuxPower( inputdata_t &inputdata ); + void InputSetAuxPower( inputdata_t &inputdata ); + + void InputTurnFlashlightOn( inputdata_t &inputdata ); + void InputTurnFlashlightOff( inputdata_t &inputdata ); +#endif + const impactdamagetable_t &GetPhysicsImpactDamageTable(); virtual int OnTakeDamage( const CTakeDamageInfo &info ); virtual int OnTakeDamage_Alive( const CTakeDamageInfo &info ); @@ -211,6 +275,10 @@ public: void SetLocatorTargetEntity( CBaseEntity *pEntity ) { m_hLocatorTargetEntity.Set( pEntity ); } +#ifdef MAPBASE + virtual bool CanAutoSwitchToNextBestWeapon( CBaseCombatWeapon *pWeapon ); +#endif + virtual int GiveAmmo( int nCount, int nAmmoIndex, bool bSuppressSound); virtual bool BumpWeapon( CBaseCombatWeapon *pWeapon ); @@ -242,6 +310,7 @@ public: virtual bool IsHoldingEntity( CBaseEntity *pEnt ); virtual void ForceDropOfCarriedPhysObjects( CBaseEntity *pOnlyIfHoldindThis ); virtual float GetHeldObjectMass( IPhysicsObject *pHeldObject ); + virtual CBaseEntity *GetHeldObject( void ); virtual bool IsFollowingPhysics( void ) { return (m_afPhysicsFlags & PFLAG_ONBARNACLE) > 0; } void InputForceDropPhysObjects( inputdata_t &data ); @@ -283,6 +352,13 @@ public: // HUD HINTS void DisplayLadderHudHint(); +#ifdef MAPBASE + void InitCustomSuitDevice( int iDeviceID, float flDrainRate ); + void AddCustomSuitDevice( int iDeviceID ); + void RemoveCustomSuitDevice( int iDeviceID ); + bool IsCustomSuitDeviceActive( int iDeviceID ); +#endif + CSoundPatch *m_sndLeeches; CSoundPatch *m_sndWaterSplashes; @@ -361,6 +437,14 @@ private: float m_flTimeNextLadderHint; // Next time we're eligible to display a HUD hint about a ladder. friend class CHL2GameMovement; + +#ifdef SP_ANIM_STATE + CSinglePlayerAnimState* m_pPlayerAnimState; + + // At the moment, we network the render angles since almost none of the player anim stuff is done on the client in SP. + // If any of this is ever adapted for MP, this method should be replaced with replicating/moving the anim state to the client. + CNetworkVar( float, m_flAnimRenderYaw ); +#endif }; diff --git a/src/game/server/hl2/hl2_triggers.cpp b/src/game/server/hl2/hl2_triggers.cpp index 55f718fe..9aeee1a9 100644 --- a/src/game/server/hl2/hl2_triggers.cpp +++ b/src/game/server/hl2/hl2_triggers.cpp @@ -589,6 +589,10 @@ class CTriggerWateryDeath : public CBaseTrigger public: DECLARE_DATADESC(); +#ifdef MAPBASE + CTriggerWateryDeath(); +#endif + void Spawn( void ); void Precache( void ); void Touch( CBaseEntity *pOther ); @@ -614,6 +618,13 @@ private: CUtlVector< float > m_flEntityKillTimes; float m_flNextPullSound; float m_flPainValue; + +#ifdef MAPBASE + float m_flBiteInterval; + float m_flPainStep; + float m_flMaxPain; + COutputInt m_OnDamage; +#endif }; BEGIN_DATADESC( CTriggerWateryDeath ) @@ -621,6 +632,12 @@ BEGIN_DATADESC( CTriggerWateryDeath ) DEFINE_UTLVECTOR( m_hLeeches, FIELD_EHANDLE ), DEFINE_FIELD( m_flNextPullSound, FIELD_TIME ), DEFINE_FIELD( m_flPainValue, FIELD_FLOAT ), +#ifdef MAPBASE + DEFINE_KEYFIELD( m_flBiteInterval, FIELD_FLOAT, "BiteInterval" ), + DEFINE_KEYFIELD( m_flPainStep, FIELD_FLOAT, "PainStep" ), + DEFINE_KEYFIELD( m_flMaxPain, FIELD_FLOAT, "MaxPain" ), + DEFINE_OUTPUT( m_OnDamage, "OnDamage" ), +#endif END_DATADESC() @@ -631,6 +648,15 @@ LINK_ENTITY_TO_CLASS( trigger_waterydeath, CTriggerWateryDeath ); #define WD_PAINVALUE_STEP 2.0 #define WD_MAX_DAMAGE 15.0f +#ifdef MAPBASE +CTriggerWateryDeath::CTriggerWateryDeath() +{ + m_flBiteInterval = WD_KILLTIME_NEXT_BITE; + m_flPainStep = WD_PAINVALUE_STEP; + m_flMaxPain = WD_MAX_DAMAGE; +} +#endif + //----------------------------------------------------------------------------- // Purpose: Called when spawning, after keyvalues have been handled. //----------------------------------------------------------------------------- @@ -707,6 +733,23 @@ void CTriggerWateryDeath::Touch( CBaseEntity *pOther ) { //EmitSound( filter, entindex(), "WateryDeath.Bite", &pOther->GetAbsOrigin() ); // Kill it +#ifdef MAPBASE + if ( pOther->IsPlayer() ) + { + m_flPainValue = MIN( m_flPainValue + m_flPainStep, m_flMaxPain ); + } + else + { + m_flPainValue = m_flMaxPain; + } + + // Do nothing if there is no damage + if (m_flPainValue <= 0.0f) + { + m_flEntityKillTimes[iIndex] = gpGlobals->curtime + m_flBiteInterval; + return; + } +#else if ( pOther->IsPlayer() ) { m_flPainValue = MIN( m_flPainValue + WD_PAINVALUE_STEP, WD_MAX_DAMAGE ); @@ -715,6 +758,7 @@ void CTriggerWateryDeath::Touch( CBaseEntity *pOther ) { m_flPainValue = WD_MAX_DAMAGE; } +#endif // Use DMG_GENERIC & make the target inflict the damage on himself. // This ensures that if the target is the player, the damage isn't modified by skill @@ -723,7 +767,13 @@ void CTriggerWateryDeath::Touch( CBaseEntity *pOther ) GuessDamageForce( &info, (pOther->GetAbsOrigin() - GetAbsOrigin()), pOther->GetAbsOrigin() ); pOther->TakeDamage( info ); +#ifdef MAPBASE + m_OnDamage.Set(m_flPainValue, pOther, this); + + m_flEntityKillTimes[iIndex] = gpGlobals->curtime + m_flBiteInterval; +#else m_flEntityKillTimes[iIndex] = gpGlobals->curtime + WD_KILLTIME_NEXT_BITE; +#endif } } diff --git a/src/game/server/hl2/item_ammo.cpp b/src/game/server/hl2/item_ammo.cpp index 416c7f2f..99133eaa 100644 --- a/src/game/server/hl2/item_ammo.cpp +++ b/src/game/server/hl2/item_ammo.cpp @@ -15,6 +15,64 @@ // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" +#ifdef MAPBASE +// ======================================================================== +// >> CItemAmmo +// +// All ammo items now derive from this for multiplier purposes. +// ======================================================================== +class CItemAmmo : public CItem +{ +public: + DECLARE_CLASS( CItemAmmo, CItem ); + DECLARE_DATADESC(); + + int ITEM_GiveAmmo( CBasePlayer *pPlayer, float flCount, const char *pszAmmoName, bool bSuppressSound = false ) + { + int iAmmoType = GetAmmoDef()->Index(pszAmmoName); + if (iAmmoType == -1) + { + Msg("ERROR: Attempting to give unknown ammo type (%s)\n",pszAmmoName); + return 0; + } + + flCount *= g_pGameRules->GetAmmoQuantityScale(iAmmoType); + + // Don't give out less than 1 of anything. + flCount = MAX( 1.0f, flCount ); + + // Mapper-specific ammo multiplier. + // If it results in 0, the ammo will simply be ignored. + // If the ammo multiplier is negative, assume it's actually a direct number to override with. + if (m_flAmmoMultiplier != 1.0f) + { + if (m_flAmmoMultiplier >= 0) + flCount *= m_flAmmoMultiplier; + else + flCount = -m_flAmmoMultiplier; + } + + return pPlayer->GiveAmmo( flCount, iAmmoType, bSuppressSound ); + } + + void InputSetAmmoMultiplier( inputdata_t &inputdata ) { m_flAmmoMultiplier = inputdata.value.Float(); } + + float m_flAmmoMultiplier = 1.0f; +}; + +BEGIN_DATADESC( CItemAmmo ) + + DEFINE_KEYFIELD( m_flAmmoMultiplier, FIELD_FLOAT, "AmmoMultiplier" ), + + DEFINE_INPUTFUNC( FIELD_FLOAT, "SetAmmoMultiplier", InputSetAmmoMultiplier ), + +END_DATADESC() + +// Almost all instances of CItem below are for declaring the base class, which is now CItemAmmo. +// This is here so we don't have to #ifdef all of them. +#define CItem CItemAmmo + +#else //--------------------------------------------------------- // Applies ammo quantity scale. //--------------------------------------------------------- @@ -34,6 +92,7 @@ int ITEM_GiveAmmo( CBasePlayer *pPlayer, float flCount, const char *pszAmmoName, return pPlayer->GiveAmmo( flCount, iAmmoType, bSuppressSound ); } +#endif // ======================================================================== // >> BoxSRounds @@ -661,6 +720,10 @@ enum AMMOCRATE_CROSSBOW, AMMOCRATE_AR2_ALTFIRE, AMMOCRATE_SMG_ALTFIRE, +#ifdef MAPBASE + AMMOCRATE_SLAM, + AMMOCRATE_EMPTY, +#endif NUM_AMMO_CRATE_TYPES, }; @@ -703,6 +766,10 @@ protected: COutputEvent m_OnUsed; CHandle< CBasePlayer > m_hActivator; +#ifdef MAPBASE + COutputEvent m_OnAmmoTaken; +#endif + DECLARE_DATADESC(); }; @@ -723,6 +790,10 @@ BEGIN_DATADESC( CItem_AmmoCrate ) DEFINE_OUTPUT( m_OnUsed, "OnUsed" ), +#ifdef MAPBASE + DEFINE_OUTPUT( m_OnAmmoTaken, "OnAmmoTaken" ), +#endif + DEFINE_INPUTFUNC( FIELD_VOID, "Kill", InputKill ), DEFINE_THINKFUNC( CrateThink ), @@ -742,12 +813,22 @@ const char *CItem_AmmoCrate::m_lpzModelNames[NUM_AMMO_CRATE_TYPES] = "models/items/ammocrate_rockets.mdl", // RPG rounds "models/items/ammocrate_buckshot.mdl", // Buckshot "models/items/ammocrate_grenade.mdl", // Grenades +#ifdef MAPBASE + "models/items/ammocrate_357.mdl", // 357 + "models/items/ammocrate_xbow.mdl", // Crossbow + "models/items/ammocrate_ar2alt.mdl", // Combine Ball +#else "models/items/ammocrate_smg1.mdl", // 357 "models/items/ammocrate_smg1.mdl", // Crossbow - + //FIXME: This model is incorrect! "models/items/ammocrate_ar2.mdl", // Combine Ball +#endif "models/items/ammocrate_smg2.mdl", // smg grenade +#ifdef MAPBASE + "models/items/ammocrate_slam.mdl", // slam + "models/items/ammocrate_empty.mdl", // empty +#endif }; // Ammo type names @@ -763,6 +844,10 @@ const char *CItem_AmmoCrate::m_lpzAmmoNames[NUM_AMMO_CRATE_TYPES] = "XBowBolt", "AR2AltFire", "SMG1_Grenade", +#ifdef MAPBASE + "slam", + NULL, +#endif }; // Ammo amount given per +use @@ -778,6 +863,10 @@ int CItem_AmmoCrate::m_nAmmoAmounts[NUM_AMMO_CRATE_TYPES] = 50, // Crossbow 3, // AR2 alt-fire 5, +#ifdef MAPBASE + 5, // SLAM + NULL, // Empty +#endif }; const char *CItem_AmmoCrate::m_pGiveWeapon[NUM_AMMO_CRATE_TYPES] = @@ -792,6 +881,10 @@ const char *CItem_AmmoCrate::m_pGiveWeapon[NUM_AMMO_CRATE_TYPES] = NULL, // Crossbow NULL, // AR2 alt-fire NULL, // SMG alt-fire +#ifdef MAPBASE + "weapon_slam", // SLAM + NULL, // Empty +#endif }; #define AMMO_CRATE_CLOSE_DELAY 1.5f @@ -847,6 +940,10 @@ void CItem_AmmoCrate::Precache( void ) //----------------------------------------------------------------------------- void CItem_AmmoCrate::SetupCrate( void ) { +#ifdef MAPBASE + // Custom models might be desired on, say, empty crates with custom textures + if (GetModelName() == NULL_STRING) +#endif SetModelName( AllocPooledString( m_lpzModelNames[m_nAmmoType] ) ); m_nAmmoIndex = GetAmmoDef()->Index( m_lpzAmmoNames[m_nAmmoType] ); @@ -970,13 +1067,24 @@ void CItem_AmmoCrate::HandleAnimEvent( animevent_t *pEvent ) } else { +#ifdef MAPBASE + m_OnAmmoTaken.FireOutput(m_hActivator, this); +#endif SetBodygroup( 1, false ); } } } +#ifdef MAPBASE + // Empty ammo crates should still fire OnAmmoTaken + if ( m_hActivator->GiveAmmo( m_nAmmoAmounts[m_nAmmoType], m_nAmmoIndex ) != 0 || m_nAmmoType == AMMOCRATE_EMPTY ) +#else if ( m_hActivator->GiveAmmo( m_nAmmoAmounts[m_nAmmoType], m_nAmmoIndex ) != 0 ) +#endif { +#ifdef MAPBASE + m_OnAmmoTaken.FireOutput(m_hActivator, this); +#endif SetBodygroup( 1, false ); } m_hActivator = NULL; @@ -1034,6 +1142,13 @@ void CItem_AmmoCrate::CrateThink( void ) //----------------------------------------------------------------------------- void CItem_AmmoCrate::InputKill( inputdata_t &data ) { +#ifdef MAPBASE + // Why is this its own function? + // item_dynamic_resupply and item_item_crate are in the same boat. + // I don't understand. + m_OnKilled.FireOutput( data.pActivator, this ); +#endif + UTIL_Remove( this ); } diff --git a/src/game/server/hl2/item_battery.cpp b/src/game/server/hl2/item_battery.cpp index 7e299fc5..d5c8b416 100644 --- a/src/game/server/hl2/item_battery.cpp +++ b/src/game/server/hl2/item_battery.cpp @@ -23,12 +23,12 @@ public: void Spawn( void ) { Precache( ); - SetModel( "models/items/battery.mdl" ); + SetModel( DefaultOrCustomModel( "models/items/battery.mdl" ) ); BaseClass::Spawn( ); } void Precache( void ) { - PrecacheModel ("models/items/battery.mdl"); + PrecacheModel( DefaultOrCustomModel( "models/items/battery.mdl" ) ); PrecacheScriptSound( "ItemBattery.Touch" ); @@ -36,10 +36,30 @@ public: bool MyTouch( CBasePlayer *pPlayer ) { CHL2_Player *pHL2Player = dynamic_cast( pPlayer ); +#ifdef MAPBASE + return ( pHL2Player && pHL2Player->ApplyBattery( m_flPowerMultiplier ) ); +#else return ( pHL2Player && pHL2Player->ApplyBattery() ); +#endif } + +#ifdef MAPBASE + void InputSetPowerMultiplier( inputdata_t &inputdata ) { m_flPowerMultiplier = inputdata.value.Float(); } + float m_flPowerMultiplier = 1.0f; + + DECLARE_DATADESC(); +#endif }; LINK_ENTITY_TO_CLASS(item_battery, CItemBattery); PRECACHE_REGISTER(item_battery); +#ifdef MAPBASE +BEGIN_DATADESC( CItemBattery ) + + DEFINE_KEYFIELD( m_flPowerMultiplier, FIELD_FLOAT, "PowerMultiplier" ), + DEFINE_INPUTFUNC( FIELD_FLOAT, "SetPowerMultiplier", InputSetPowerMultiplier ), + +END_DATADESC() +#endif + diff --git a/src/game/server/hl2/item_dynamic_resupply.cpp b/src/game/server/hl2/item_dynamic_resupply.cpp index 6bdcff62..bc0a6c96 100644 --- a/src/game/server/hl2/item_dynamic_resupply.cpp +++ b/src/game/server/hl2/item_dynamic_resupply.cpp @@ -116,6 +116,10 @@ private: float m_flDesiredAmmo[ NUM_AMMO_ITEMS ]; bool m_bIsMaster; + +#ifdef MAPBASE + COutputEHANDLE m_OnItem; +#endif }; LINK_ENTITY_TO_CLASS(item_dynamic_resupply, CItem_DynamicResupply); @@ -153,6 +157,10 @@ BEGIN_DATADESC( CItem_DynamicResupply ) DEFINE_FIELD( m_version, FIELD_INTEGER ), DEFINE_FIELD( m_bIsMaster, FIELD_BOOLEAN ), +#ifdef MAPBASE + DEFINE_OUTPUT( m_OnItem, "OnItem" ), +#endif + // Silence, Classcheck! // DEFINE_ARRAY( m_flDesiredHealth, FIELD_FLOAT, NUM_HEALTH_ITEMS ), // DEFINE_ARRAY( m_flDesiredAmmo, FIELD_FLOAT, NUM_AMMO_ITEMS ), @@ -282,6 +290,11 @@ void CItem_DynamicResupply::CheckPVSThink( void ) //----------------------------------------------------------------------------- void CItem_DynamicResupply::InputKill( inputdata_t &data ) { +#ifdef MAPBASE + // What's the point of this being its own function? + m_OnKilled.FireOutput( data.pActivator, this ); +#endif + UTIL_Remove( this ); } @@ -345,7 +358,12 @@ void CItem_DynamicResupply::SpawnFullItem( CItem_DynamicResupply *pMaster, CBase // If we're supposed to fallback to just a health vial, do that and finish. if ( pMaster->HasSpawnFlags(SF_DYNAMICRESUPPLY_FALLBACK_TO_VIAL) ) { +#ifdef MAPBASE + CBaseEntity *pItem = CBaseEntity::Create("item_healthvial", GetAbsOrigin(), GetAbsAngles(), this); + m_OnItem.Set(pItem, pItem, this); +#else CBaseEntity::Create( "item_healthvial", GetAbsOrigin(), GetAbsAngles(), this ); +#endif if ( iDebug ) { @@ -364,7 +382,12 @@ void CItem_DynamicResupply::SpawnFullItem( CItem_DynamicResupply *pMaster, CBase { if ( flChoice <= flRatio[i] ) { +#ifdef MAPBASE + CBaseEntity *pItem = CBaseEntity::Create( g_DynamicResupplyAmmoItems[i].sEntityName, GetAbsOrigin(), GetAbsAngles(), this ); + m_OnItem.Set(pItem, pItem, this); +#else CBaseEntity::Create( g_DynamicResupplyAmmoItems[i].sEntityName, GetAbsOrigin(), GetAbsAngles(), this ); +#endif if ( iDebug ) { @@ -546,6 +569,10 @@ bool CItem_DynamicResupply::SpawnItemFromRatio( int nCount, DynamicResupplyItems pEnt->SetAbsVelocity( GetAbsVelocity() ); pEnt->SetLocalAngularVelocity( GetLocalAngularVelocity() ); +#ifdef MAPBASE + m_OnItem.Set(pEnt, pEnt, this); +#endif + // Move the entity up so that it doesn't go below the spawn origin Vector vecWorldMins, vecWorldMaxs; pEnt->CollisionProp()->WorldSpaceAABB( &vecWorldMins, &vecWorldMaxs ); @@ -654,4 +681,9 @@ void DynamicResupply_InitFromAlternateMaster( CBaseEntity *pTargetEnt, string_t memcpy( pTargetResupply->m_flDesiredHealth, pMasterResupply->m_flDesiredHealth, sizeof( pMasterResupply->m_flDesiredHealth ) ); memcpy( pTargetResupply->m_flDesiredAmmo, pMasterResupply->m_flDesiredAmmo, sizeof( pMasterResupply->m_flDesiredAmmo ) ); +#ifdef MAPBASE + if (pMasterResupply->HasSpawnFlags(SF_DYNAMICRESUPPLY_FALLBACK_TO_VIAL)) + pTargetResupply->AddSpawnFlags(SF_DYNAMICRESUPPLY_FALLBACK_TO_VIAL); +#endif + } \ No newline at end of file diff --git a/src/game/server/hl2/item_healthkit.cpp b/src/game/server/hl2/item_healthkit.cpp index 460cc7b5..e55f0190 100644 --- a/src/game/server/hl2/item_healthkit.cpp +++ b/src/game/server/hl2/item_healthkit.cpp @@ -31,11 +31,29 @@ public: void Spawn( void ); void Precache( void ); bool MyTouch( CBasePlayer *pPlayer ); + +#ifdef MAPBASE + float GetItemAmount() { return sk_healthkit.GetFloat() * m_flHealthMultiplier; } + + void InputSetHealthMultiplier( inputdata_t &inputdata ) { m_flHealthMultiplier = inputdata.value.Float(); } + float m_flHealthMultiplier = 1.0f; + + DECLARE_DATADESC(); +#endif }; LINK_ENTITY_TO_CLASS( item_healthkit, CHealthKit ); PRECACHE_REGISTER(item_healthkit); +#ifdef MAPBASE +BEGIN_DATADESC( CHealthKit ) + + DEFINE_KEYFIELD( m_flHealthMultiplier, FIELD_FLOAT, "HealthMultiplier" ), + DEFINE_INPUTFUNC( FIELD_FLOAT, "SetHealthMultiplier", InputSetHealthMultiplier ), + +END_DATADESC() +#endif + //----------------------------------------------------------------------------- // Purpose: @@ -67,7 +85,11 @@ void CHealthKit::Precache( void ) //----------------------------------------------------------------------------- bool CHealthKit::MyTouch( CBasePlayer *pPlayer ) { +#ifdef MAPBASE + if ( pPlayer->TakeHealth( GetItemAmount(), DMG_GENERIC ) ) +#else if ( pPlayer->TakeHealth( sk_healthkit.GetFloat(), DMG_GENERIC ) ) +#endif { CSingleUserRecipientFilter user( pPlayer ); user.MakeReliable(); @@ -79,7 +101,7 @@ bool CHealthKit::MyTouch( CBasePlayer *pPlayer ) CPASAttenuationFilter filter( pPlayer, "HealthKit.Touch" ); EmitSound( filter, pPlayer->entindex(), "HealthKit.Touch" ); - if ( g_pGameRules->ItemShouldRespawn( this ) ) + if ( g_pGameRules->ItemShouldRespawn( this ) == GR_ITEM_RESPAWN_YES ) { Respawn(); } @@ -120,7 +142,11 @@ public: bool MyTouch( CBasePlayer *pPlayer ) { +#ifdef MAPBASE + if ( pPlayer->TakeHealth( GetItemAmount(), DMG_GENERIC ) ) +#else if ( pPlayer->TakeHealth( sk_healthvial.GetFloat(), DMG_GENERIC ) ) +#endif { CSingleUserRecipientFilter user( pPlayer ); user.MakeReliable(); @@ -132,7 +158,7 @@ public: CPASAttenuationFilter filter( pPlayer, "HealthVial.Touch" ); EmitSound( filter, pPlayer->entindex(), "HealthVial.Touch" ); - if ( g_pGameRules->ItemShouldRespawn( this ) ) + if ( g_pGameRules->ItemShouldRespawn( this ) == GR_ITEM_RESPAWN_YES ) { Respawn(); } @@ -146,11 +172,132 @@ public: return false; } + +#ifdef MAPBASE + float GetItemAmount() { return sk_healthvial.GetFloat() * m_flHealthMultiplier; } + + void InputSetHealthMultiplier( inputdata_t &inputdata ) { m_flHealthMultiplier = inputdata.value.Float(); } + float m_flHealthMultiplier = 1.0f; + + DECLARE_DATADESC(); +#endif }; LINK_ENTITY_TO_CLASS( item_healthvial, CHealthVial ); PRECACHE_REGISTER( item_healthvial ); +#ifdef MAPBASE +BEGIN_DATADESC( CHealthVial ) + + DEFINE_KEYFIELD( m_flHealthMultiplier, FIELD_FLOAT, "HealthMultiplier" ), + DEFINE_INPUTFUNC( FIELD_FLOAT, "SetHealthMultiplier", InputSetHealthMultiplier ), + +END_DATADESC() + + +//----------------------------------------------------------------------------- +// Small health kit. Heals the player when picked up. +//----------------------------------------------------------------------------- +class CHealthKitCustom : public CItem +{ +public: + DECLARE_CLASS( CHealthKitCustom, CItem ); + CHealthKitCustom(); + + void Spawn( void ); + void Precache( void ); + bool MyTouch( CBasePlayer *pPlayer ); + + float GetItemAmount() { return m_flHealthAmount; } + + void InputSetHealthAmount( inputdata_t &inputdata ) { m_flHealthAmount = inputdata.value.Float(); } + + float m_flHealthAmount; + string_t m_iszTouchSound; + + DECLARE_DATADESC(); +}; + +LINK_ENTITY_TO_CLASS( item_healthkit_custom, CHealthKitCustom ); +//PRECACHE_REGISTER(item_healthkit_custom); + +#ifdef MAPBASE +BEGIN_DATADESC( CHealthKitCustom ) + + DEFINE_KEYFIELD( m_flHealthAmount, FIELD_FLOAT, "HealthAmount" ), + DEFINE_KEYFIELD( m_iszTouchSound, FIELD_STRING, "TouchSound" ), + + DEFINE_INPUTFUNC( FIELD_FLOAT, "SetHealthAmount", InputSetHealthAmount ), + +END_DATADESC() +#endif + + +CHealthKitCustom::CHealthKitCustom() +{ + SetModelName( AllocPooledString( "models/items/healthkit.mdl" ) ); + m_flHealthAmount = sk_healthkit.GetFloat(); + m_iszTouchSound = AllocPooledString( "HealthKit.Touch" ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CHealthKitCustom::Spawn( void ) +{ + Precache(); + SetModel( STRING( GetModelName() ) ); + + BaseClass::Spawn(); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CHealthKitCustom::Precache( void ) +{ + PrecacheModel( STRING( GetModelName() ) ); + + PrecacheScriptSound( STRING( m_iszTouchSound ) ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pPlayer - +// Output : +//----------------------------------------------------------------------------- +bool CHealthKitCustom::MyTouch( CBasePlayer *pPlayer ) +{ + if ( pPlayer->TakeHealth( GetItemAmount(), DMG_GENERIC ) ) + { + CSingleUserRecipientFilter user( pPlayer ); + user.MakeReliable(); + + UserMessageBegin( user, "ItemPickup" ); + WRITE_STRING( GetClassname() ); + MessageEnd(); + + CPASAttenuationFilter filter( pPlayer, STRING( m_iszTouchSound ) ); + EmitSound( filter, pPlayer->entindex(), STRING( m_iszTouchSound ) ); + + if ( g_pGameRules->ItemShouldRespawn( this ) == GR_ITEM_RESPAWN_YES ) + { + Respawn(); + } + else + { + UTIL_Remove(this); + } + + return true; + } + + return false; +} +#endif + LINK_ENTITY_TO_CLASS(func_healthcharger, CWallHealth); @@ -382,6 +529,8 @@ void CWallHealth::Off(void) SetThink( NULL ); } + + LINK_ENTITY_TO_CLASS( item_healthcharger, CNewWallHealth); @@ -389,12 +538,20 @@ BEGIN_DATADESC( CNewWallHealth ) DEFINE_FIELD( m_flNextCharge, FIELD_TIME), DEFINE_FIELD( m_iReactivate, FIELD_INTEGER), +#ifdef MAPBASE + DEFINE_KEYFIELD( m_iJuice, FIELD_INTEGER, "Charge" ), +#else DEFINE_FIELD( m_iJuice, FIELD_INTEGER), +#endif DEFINE_FIELD( m_iOn, FIELD_INTEGER), DEFINE_FIELD( m_flSoundTime, FIELD_TIME), DEFINE_FIELD( m_nState, FIELD_INTEGER ), DEFINE_FIELD( m_iCaps, FIELD_INTEGER ), DEFINE_FIELD( m_flJuice, FIELD_FLOAT ), +#ifdef MAPBASE + DEFINE_KEYFIELD( m_iMaxJuice, FIELD_INTEGER, "MaxCharge" ), + DEFINE_INPUT( m_iIncrementValue, FIELD_INTEGER, "SetIncrementValue" ), +#endif // Function Pointers DEFINE_FUNCTION( Off ), @@ -402,6 +559,15 @@ BEGIN_DATADESC( CNewWallHealth ) DEFINE_OUTPUT( m_OnPlayerUse, "OnPlayerUse" ), DEFINE_OUTPUT( m_OutRemainingHealth, "OutRemainingHealth"), +#ifdef MAPBASE + DEFINE_OUTPUT(m_OnHalfEmpty, "OnHalfEmpty" ), + DEFINE_OUTPUT(m_OnEmpty, "OnEmpty" ), + DEFINE_OUTPUT(m_OnFull, "OnFull" ), + + DEFINE_INPUTFUNC( FIELD_VOID, "Recharge", InputRecharge ), + DEFINE_INPUTFUNC( FIELD_INTEGER, "SetCharge", InputSetCharge ), + DEFINE_INPUTFUNC( FIELD_FLOAT, "SetChargeNoMax", InputSetChargeNoMax ), +#endif END_DATADESC() @@ -410,6 +576,10 @@ END_DATADESC() #define CHARGES_PER_SECOND 1.0f / CHARGE_RATE #define CALLS_PER_SECOND 7.0f * CHARGES_PER_SECOND +#ifdef MAPBASE +#define CUSTOM_CHARGES_PER_SECOND(inc) inc / CHARGE_RATE +#endif + //----------------------------------------------------------------------------- // Purpose: @@ -434,6 +604,30 @@ bool CNewWallHealth::KeyValue( const char *szKeyName, const char *szValue ) return(BaseClass::KeyValue( szKeyName, szValue )); } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CNewWallHealth::SetInitialCharge( void ) +{ + if ( m_iMaxJuice != 0 ) + { + // It must've been overridden by the mapper + return; + } + + m_iMaxJuice = sk_healthcharger.GetFloat(); +} + +//----------------------------------------------------------------------------- +// Max juice for recharger +//----------------------------------------------------------------------------- +float CNewWallHealth::MaxJuice() const +{ + return m_iMaxJuice; +} +#endif + //----------------------------------------------------------------------------- // Purpose: @@ -446,12 +640,34 @@ void CNewWallHealth::Spawn(void) SetSolid( SOLID_VPHYSICS ); CreateVPhysics(); +#ifdef MAPBASE + SetModel( STRING(GetModelName()) ); +#else SetModel( HEALTH_CHARGER_MODEL_NAME ); +#endif AddEffects( EF_NOSHADOW ); ResetSequence( LookupSequence( "idle" ) ); +#ifdef MAPBASE + if (m_iIncrementValue == 0) + m_iIncrementValue = 1; + + SetInitialCharge(); + + // In case the juice was overridden + if (m_iJuice == 0) + UpdateJuice( MaxJuice() ); + else if (m_iJuice == -1) + { + UpdateJuice( 0 ); + ResetSequence( LookupSequence( "empty" ) ); + } + else + UpdateJuice( m_iJuice ); +#else m_iJuice = sk_healthcharger.GetFloat(); +#endif m_nState = 0; @@ -461,7 +677,11 @@ void CNewWallHealth::Spawn(void) CreateVPhysics(); m_flJuice = m_iJuice; +#ifdef MAPBASE + SetCycle( 1.0f - ( m_flJuice / MaxJuice() ) ); +#else SetCycle( 1.0f - ( m_flJuice / sk_healthcharger.GetFloat() ) ); +#endif } int CNewWallHealth::DrawDebugTextOverlays(void) @@ -491,7 +711,14 @@ bool CNewWallHealth::CreateVPhysics(void) //----------------------------------------------------------------------------- void CNewWallHealth::Precache(void) { +#ifdef MAPBASE + if ( GetModelName() == NULL_STRING ) + SetModelName( AllocPooledString(HEALTH_CHARGER_MODEL_NAME) ); + + PrecacheModel( STRING(GetModelName()) ); +#else PrecacheModel( HEALTH_CHARGER_MODEL_NAME ); +#endif PrecacheScriptSound( "WallHealth.Deny" ); PrecacheScriptSound( "WallHealth.Start" ); @@ -503,7 +730,11 @@ void CNewWallHealth::StudioFrameAdvance( void ) { m_flPlaybackRate = 0; +#ifdef MAPBASE + float flMaxJuice = MaxJuice() + 0.1f; +#else float flMaxJuice = sk_healthcharger.GetFloat(); +#endif SetCycle( 1.0f - (float)( m_flJuice / flMaxJuice ) ); // Msg( "Cycle: %f - Juice: %d - m_flJuice :%f - Interval: %f\n", (float)GetCycle(), (int)m_iJuice, (float)m_flJuice, GetAnimTimeInterval() ); @@ -519,6 +750,62 @@ void CNewWallHealth::StudioFrameAdvance( void ) m_flAnimTime = gpGlobals->curtime; } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: +// Input : newJuice - +//----------------------------------------------------------------------------- +void CNewWallHealth::UpdateJuice( int newJuice ) +{ + bool reduced = newJuice < m_iJuice; + if ( reduced ) + { + // Fire 1/2 way output and/or empyt output + int oneHalfJuice = (int)(MaxJuice() * 0.5f); + if ( newJuice <= oneHalfJuice && m_iJuice > oneHalfJuice ) + { + m_OnHalfEmpty.FireOutput( this, this ); + } + + if ( newJuice <= 0 ) + { + m_OnEmpty.FireOutput( this, this ); + } + } + else if ( newJuice != m_iJuice && + newJuice == (int)MaxJuice() ) + { + m_OnFull.FireOutput( this, this ); + } + m_iJuice = newJuice; +} + +void CNewWallHealth::InputRecharge( inputdata_t &inputdata ) +{ + Recharge(); +} + +void CNewWallHealth::InputSetCharge( inputdata_t &inputdata ) +{ + int iJuice = inputdata.value.Int(); + + m_flJuice = m_iMaxJuice = m_iJuice = iJuice; + + ResetSequence( m_iJuice > 0 ? LookupSequence( "idle" ) : LookupSequence( "empty" ) ); + StudioFrameAdvance(); +} + +void CNewWallHealth::InputSetChargeNoMax( inputdata_t &inputdata ) +{ + m_flJuice = inputdata.value.Float(); + + UpdateJuice(m_flJuice); + + ResetSequence( m_iJuice > 0 ? LookupSequence( "idle" ) : LookupSequence( "empty" ) ); + StudioFrameAdvance(); +} +#endif + //----------------------------------------------------------------------------- // Purpose: // Input : *pActivator - @@ -545,6 +832,11 @@ void CNewWallHealth::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYP float flCharges = CHARGES_PER_SECOND; float flCalls = CALLS_PER_SECOND; +#ifdef MAPBASE + if ( m_iIncrementValue != 0 ) + flCharges = CUSTOM_CHARGES_PER_SECOND(m_iIncrementValue); +#endif + m_flJuice -= flCharges / flCalls; StudioFrameAdvance(); } @@ -610,13 +902,24 @@ void CNewWallHealth::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYP } // charge the player +#ifdef MAPBASE + if ( pActivator->TakeHealth( m_iIncrementValue, DMG_GENERIC ) ) + { + UpdateJuice(m_iJuice - m_iIncrementValue); + } +#else if ( pActivator->TakeHealth( 1, DMG_GENERIC ) ) { m_iJuice--; } +#endif // Send the output. +#ifdef MAPBASE + float flRemaining = m_iJuice / MaxJuice(); +#else float flRemaining = m_iJuice / sk_healthcharger.GetFloat(); +#endif m_OutRemainingHealth.Set(flRemaining, pActivator, this); // govern the rate of charge @@ -630,7 +933,12 @@ void CNewWallHealth::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYP void CNewWallHealth::Recharge(void) { EmitSound( "WallHealth.Recharge" ); +#ifdef MAPBASE + UpdateJuice(MaxJuice()); + m_flJuice = m_iJuice; +#else m_flJuice = m_iJuice = sk_healthcharger.GetFloat(); +#endif m_nState = 0; ResetSequence( LookupSequence( "idle" ) ); diff --git a/src/game/server/hl2/item_healthkit.h b/src/game/server/hl2/item_healthkit.h index 00b89236..7f450a41 100644 --- a/src/game/server/hl2/item_healthkit.h +++ b/src/game/server/hl2/item_healthkit.h @@ -22,6 +22,17 @@ public: int GetJuice() const { return m_iJuice; } +#ifdef MAPBASE + void InputRecharge( inputdata_t &inputdata ); + void InputSetCharge( inputdata_t &inputdata ); + void InputSetChargeNoMax( inputdata_t &inputdata ); + void UpdateJuice( int newJuice ); + float MaxJuice() const; + void SetInitialCharge( void ); + int m_iMaxJuice; + int m_iIncrementValue; +#endif + float m_flNextCharge; int m_iReactivate ; // DeathMatch Delay until reactvated int m_iJuice; @@ -32,6 +43,11 @@ public: int m_iCaps; COutputFloat m_OutRemainingHealth; +#ifdef MAPBASE + COutputEvent m_OnHalfEmpty; + COutputEvent m_OnEmpty; + COutputEvent m_OnFull; +#endif COutputEvent m_OnPlayerUse; void StudioFrameAdvance ( void ); diff --git a/src/game/server/hl2/item_itemcrate.cpp b/src/game/server/hl2/item_itemcrate.cpp index 20ee2ff7..c1d47364 100644 --- a/src/game/server/hl2/item_itemcrate.cpp +++ b/src/game/server/hl2/item_itemcrate.cpp @@ -8,6 +8,9 @@ #include "props.h" #include "items.h" #include "item_dynamic_resupply.h" +#ifdef MAPBASE +#include "point_template.h" +#endif // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" @@ -16,6 +19,9 @@ const char *pszItemCrateModelName[] = { "models/items/item_item_crate.mdl", "models/items/item_beacon_crate.mdl", +#ifdef MAPBASE + "models/items/item_item_crate.mdl", // Custom model placeholder/fallback, this should never be selected +#endif }; //----------------------------------------------------------------------------- @@ -39,14 +45,32 @@ public: virtual void VPhysicsCollision( int index, gamevcollisionevent_t *pEvent ); virtual void OnPhysGunPickup( CBasePlayer *pPhysGunUser, PhysGunPickup_t reason ); +#ifdef MAPBASE + void InputSetContents( inputdata_t &data ); + void InputSetItemCount( inputdata_t &data ); + void InputMergeContentsWithPlayer( inputdata_t &data ); + + // Item crates always override prop data for custom models + bool OverridePropdata( void ) { return true; } +#endif + protected: virtual void OnBreak( const Vector &vecVelocity, const AngularImpulse &angVel, CBaseEntity *pBreaker ); +#ifdef MAPBASE + bool ShouldRandomizeAngles( CBaseEntity *pEnt ); + #define ITEM_ITEMCRATE_TEMPLATE_TARGET m_strAlternateMaster + CPointTemplate *FindTemplate(); +#endif + private: // Crate types. Add more! enum CrateType_t { CRATE_SPECIFIC_ITEM = 0, +#ifdef MAPBASE + CRATE_POINT_TEMPLATE, +#endif CRATE_TYPE_COUNT, }; @@ -54,6 +78,9 @@ private: { CRATE_APPEARANCE_DEFAULT = 0, CRATE_APPEARANCE_RADAR_BEACON, +#ifdef MAPBASE + CRATE_APPEARANCE_CUSTOM, +#endif }; private: @@ -64,6 +91,9 @@ private: CrateAppearance_t m_CrateAppearance; COutputEvent m_OnCacheInteraction; +#ifdef MAPBASE + COutputEHANDLE m_OnItem; +#endif }; @@ -82,6 +112,13 @@ BEGIN_DATADESC( CItem_ItemCrate ) DEFINE_KEYFIELD( m_CrateAppearance, FIELD_INTEGER, "CrateAppearance" ), DEFINE_INPUTFUNC( FIELD_VOID, "Kill", InputKill ), DEFINE_OUTPUT( m_OnCacheInteraction, "OnCacheInteraction" ), +#ifdef MAPBASE + DEFINE_OUTPUT( m_OnItem, "OnItem" ), + + DEFINE_INPUTFUNC( FIELD_STRING, "SetContents", InputSetContents ), + DEFINE_INPUTFUNC( FIELD_INTEGER, "SetItemCount", InputSetItemCount ), + DEFINE_INPUTFUNC( FIELD_EHANDLE, "MergeContentsWithPlayer", InputMergeContentsWithPlayer ), +#endif END_DATADESC() @@ -92,8 +129,17 @@ END_DATADESC() void CItem_ItemCrate::Precache( void ) { // Set this here to quiet base prop warnings +#ifdef MAPBASE + // Set our model name here instead of in Spawn() so we could use custom crates. + if (m_CrateAppearance != CRATE_APPEARANCE_CUSTOM) + SetModelName(AllocPooledString(pszItemCrateModelName[m_CrateAppearance])); + + PrecacheModel( STRING(GetModelName()) ); + SetModel( STRING(GetModelName()) ); +#else PrecacheModel( pszItemCrateModelName[m_CrateAppearance] ); SetModel( pszItemCrateModelName[m_CrateAppearance] ); +#endif BaseClass::Precache(); if ( m_CrateType == CRATE_SPECIFIC_ITEM ) @@ -118,7 +164,9 @@ void CItem_ItemCrate::Spawn( void ) } DisableAutoFade(); +#ifndef MAPBASE SetModelName( AllocPooledString( pszItemCrateModelName[m_CrateAppearance] ) ); +#endif if ( NULL_STRING == m_strItemClass ) { @@ -128,7 +176,11 @@ void CItem_ItemCrate::Spawn( void ) } Precache( ); +#ifdef MAPBASE + SetModel( STRING(GetModelName()) ); +#else SetModel( pszItemCrateModelName[m_CrateAppearance] ); +#endif AddEFlags( EFL_NO_ROTORWASH_PUSH ); BaseClass::Spawn( ); } @@ -140,9 +192,76 @@ void CItem_ItemCrate::Spawn( void ) //----------------------------------------------------------------------------- void CItem_ItemCrate::InputKill( inputdata_t &data ) { +#ifdef MAPBASE + // Why is this its own function anyway? + // It just overwrites the death notice stuff. + m_OnKilled.FireOutput(data.pActivator, this); +#endif + UTIL_Remove( this ); } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: +// Input : &data - +//----------------------------------------------------------------------------- +void CItem_ItemCrate::InputSetContents( inputdata_t &data ) +{ + switch( m_CrateType ) + { + case CRATE_POINT_TEMPLATE: + ITEM_ITEMCRATE_TEMPLATE_TARGET = data.value.StringID(); + break; + + case CRATE_SPECIFIC_ITEM: + default: + m_strItemClass = data.value.StringID(); + break; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : &data - +//----------------------------------------------------------------------------- +void CItem_ItemCrate::InputSetItemCount( inputdata_t &data ) +{ + m_nItemCount = data.value.Int(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : &data - +//----------------------------------------------------------------------------- +void CItem_ItemCrate::InputMergeContentsWithPlayer( inputdata_t &data ) +{ + CBasePlayer *pPlayer = ToBasePlayer(data.value.Entity()); + if (!pPlayer) + pPlayer = UTIL_GetLocalPlayer(); + + if (pPlayer) + { + switch( m_CrateType ) + { + case CRATE_POINT_TEMPLATE: + { + Warning( "%s: item_itemcrate MergeContentsWithPlayer is not supported on template crates yet!!!\n", GetDebugName() ); + } break; + + case CRATE_SPECIFIC_ITEM: + default: + { + for (int i = 0; i < m_nItemCount; i++) + { + pPlayer->GiveNamedItem( STRING( m_strItemClass ) ); + } + } break; + } + } +} +#endif + //----------------------------------------------------------------------------- // Item crates blow up immediately @@ -178,6 +297,34 @@ void CItem_ItemCrate::VPhysicsCollision( int index, gamevcollisionevent_t *pEven } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: Finds the template for CRATE_POINT_TEMPLATE. +//----------------------------------------------------------------------------- +inline CPointTemplate *CItem_ItemCrate::FindTemplate() +{ + return dynamic_cast(gEntList.FindEntityByName( NULL, STRING(ITEM_ITEMCRATE_TEMPLATE_TARGET) )); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CItem_ItemCrate::ShouldRandomizeAngles( CBaseEntity *pEnt ) +{ + // Angles probably not supposed to be randomized. + if (m_CrateType == CRATE_POINT_TEMPLATE) + return false; + + // If we have only one NPC, it's probably supposed to spawn correctly. + // (if we have a bunch, it's probably a gag) + if (m_nItemCount == 1 && pEnt->IsNPC()) + return false; + + return true; +} +#endif + + //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- @@ -189,7 +336,31 @@ void CItem_ItemCrate::OnBreak( const Vector &vecVelocity, const AngularImpulse & m_OnCacheInteraction.FireOutput(pBreaker,this); +#ifdef MAPBASE + int iCount = m_nItemCount; + CUtlVector hNewEntities; + CPointTemplate *pTemplate = FindTemplate(); + + if (m_CrateType == CRATE_POINT_TEMPLATE) + { + if (pTemplate && pTemplate->CreateInstance(GetLocalOrigin(), GetLocalAngles(), &hNewEntities)) + { + iCount = hNewEntities.Count() * m_nItemCount; + } + else + { + // This only runs if our template can't be found or its template instancing didn't work. + Warning("item_item_crate %s with CRATE_POINT_TEMPLATE couldn't find point_template %s! Falling back to CRATE_SPECIFIC_ITEM...\n", GetDebugName(), STRING(ITEM_ITEMCRATE_TEMPLATE_TARGET)); + m_CrateType = CRATE_SPECIFIC_ITEM; + } + } +#endif + +#ifdef MAPBASE + for ( int i = 0; i < iCount; i++ ) +#else for ( int i = 0; i < m_nItemCount; ++i ) +#endif { CBaseEntity *pSpawn = NULL; switch( m_CrateType ) @@ -198,6 +369,25 @@ void CItem_ItemCrate::OnBreak( const Vector &vecVelocity, const AngularImpulse & pSpawn = CreateEntityByName( STRING(m_strItemClass) ); break; +#ifdef MAPBASE + case CRATE_POINT_TEMPLATE: + { + if (i >= hNewEntities.Count()) + { + if (!pTemplate || !pTemplate->CreateInstance(GetLocalOrigin(), GetLocalAngles(), &hNewEntities)) + { + pSpawn = NULL; + i = iCount; + break; + } + + i = 0; + iCount -= hNewEntities.Count(); + } + pSpawn = hNewEntities[i]; + } break; +#endif + default: break; } @@ -205,6 +395,55 @@ void CItem_ItemCrate::OnBreak( const Vector &vecVelocity, const AngularImpulse & if ( !pSpawn ) return; +#ifdef MAPBASE + Vector vecOrigin; + CollisionProp()->RandomPointInBounds(Vector(0.25, 0.25, 0.25), Vector(0.75, 0.75, 0.75), &vecOrigin); + pSpawn->SetAbsOrigin(vecOrigin); + + if (ShouldRandomizeAngles(pSpawn)) + { + // Give a little randomness... + QAngle vecAngles; + vecAngles.x = random->RandomFloat(-20.0f, 20.0f); + vecAngles.y = random->RandomFloat(0.0f, 360.0f); + vecAngles.z = random->RandomFloat(-20.0f, 20.0f); + pSpawn->SetAbsAngles(vecAngles); + + Vector vecActualVelocity; + vecActualVelocity.Random(-10.0f, 10.0f); + // vecActualVelocity += vecVelocity; + pSpawn->SetAbsVelocity(vecActualVelocity); + + QAngle angVel; + AngularImpulseToQAngle(angImpulse, angVel); + pSpawn->SetLocalAngularVelocity(angVel); + } + else + { + // Only modify the Y value. + QAngle vecAngles; + vecAngles.x = 0; + vecAngles.y = GetLocalAngles().y; + vecAngles.z = 0; + pSpawn->SetAbsAngles(vecAngles); + } + + // We handle dynamic resupplies differently + bool bDynResup = FClassnameIs( pSpawn, "item_dynamic_resupply" ); + if (!bDynResup) + m_OnItem.Set(pSpawn, pSpawn, this); + else if (m_OnItem.NumberOfElements() > 0) + { + // This is here so it could fire OnItem for each item + CEventAction *ourlist = m_OnItem.GetActionList(); + char outputdata[256]; + for (CEventAction *ev = ourlist; ev != NULL; ev = ev->m_pNext) + { + Q_snprintf(outputdata, sizeof(outputdata), "%s,%s,%s,%f,%i", STRING(ev->m_iTarget), STRING(ev->m_iTargetInput), STRING(ev->m_iParameter), ev->m_flDelay, ev->m_nTimesToFire); + pSpawn->KeyValue("OnItem", outputdata); + } + } +#else // Give a little randomness... Vector vecOrigin; CollisionProp()->RandomPointInBounds( Vector(0.25, 0.25, 0.25), Vector( 0.75, 0.75, 0.75 ), &vecOrigin ); @@ -224,6 +463,7 @@ void CItem_ItemCrate::OnBreak( const Vector &vecVelocity, const AngularImpulse & QAngle angVel; AngularImpulseToQAngle( angImpulse, angVel ); pSpawn->SetLocalAngularVelocity( angVel ); +#endif // If we're creating an item, it can't be picked up until it comes to rest // But only if it wasn't broken by a vehicle @@ -236,7 +476,11 @@ void CItem_ItemCrate::OnBreak( const Vector &vecVelocity, const AngularImpulse & pSpawn->Spawn(); // Avoid missing items drops by a dynamic resupply because they don't think immediately +#ifdef MAPBASE + if (bDynResup) +#else if ( FClassnameIs( pSpawn, "item_dynamic_resupply" ) ) +#endif { if ( m_strAlternateMaster != NULL_STRING ) { diff --git a/src/game/server/hl2/item_suit.cpp b/src/game/server/hl2/item_suit.cpp index 5441234f..67d327b9 100644 --- a/src/game/server/hl2/item_suit.cpp +++ b/src/game/server/hl2/item_suit.cpp @@ -49,7 +49,11 @@ public: else UTIL_EmitSoundSuit(pPlayer->edict(), "!HEV_AAx"); // long version of suit logon +#ifdef MAPBASE + pPlayer->EquipSuit(!HasSpawnFlags(SF_SUIT_SHORTLOGON)); +#else pPlayer->EquipSuit(); +#endif return true; } diff --git a/src/game/server/hl2/npc_BaseZombie.cpp b/src/game/server/hl2/npc_BaseZombie.cpp index e7e5c6fc..bd4ead34 100644 --- a/src/game/server/hl2/npc_BaseZombie.cpp +++ b/src/game/server/hl2/npc_BaseZombie.cpp @@ -159,6 +159,10 @@ ConVar zombie_decaymax( "zombie_decaymax", "0.4" ); ConVar zombie_ambushdist( "zombie_ambushdist", "16000" ); +#ifdef MAPBASE +ConVar zombie_no_flinch_during_unique_anim( "zombie_no_flinch_during_unique_anim", "1", FCVAR_NONE, "Prevents zombies from flinching during actbusies and scripted sequences." ); +#endif + //========================================================= // For a couple of reasons, we keep a running count of how // many zombies in the world are angry at any given time. @@ -205,7 +209,14 @@ BEGIN_DATADESC( CNPC_BaseZombie ) DEFINE_SOUNDPATCH( m_pMoanSound ), DEFINE_FIELD( m_fIsTorso, FIELD_BOOLEAN ), +#ifdef MAPBASE + DEFINE_KEYFIELD( m_fIsHeadless, FIELD_BOOLEAN, "Headless" ), + DEFINE_KEYFIELD( m_flMeleeReach, FIELD_FLOAT, "MeleeReach" ), + DEFINE_KEYFIELD( m_flMaxDistToSwat, FIELD_FLOAT, "MaxDistToSwat" ), + DEFINE_KEYFIELD( m_iMaxObjMassToSwat, FIELD_INTEGER, "MaxObjMassToSwat" ), +#else DEFINE_FIELD( m_fIsHeadless, FIELD_BOOLEAN ), +#endif DEFINE_FIELD( m_flNextFlinch, FIELD_TIME ), DEFINE_FIELD( m_bHeadShot, FIELD_BOOLEAN ), DEFINE_FIELD( m_flBurnDamage, FIELD_FLOAT ), @@ -220,6 +231,11 @@ BEGIN_DATADESC( CNPC_BaseZombie ) DEFINE_FIELD( m_hObstructor, FIELD_EHANDLE ), DEFINE_FIELD( m_bIsSlumped, FIELD_BOOLEAN ), +#ifdef MAPBASE + DEFINE_OUTPUT( m_OnSwattedProp, "OnSwattedProp" ), + DEFINE_OUTPUT( m_OnCrab, "OnCrab" ), +#endif + END_DATADESC() @@ -243,6 +259,12 @@ CNPC_BaseZombie::CNPC_BaseZombie() // moan loop. m_iMoanSound = g_numZombies; +#ifdef MAPBASE + m_flMeleeReach = ZOMBIE_MELEE_REACH; + m_flMaxDistToSwat = ZOMBIE_PLAYER_MAX_SWAT_DIST; + m_iMaxObjMassToSwat = ZOMBIE_MAX_PHYSOBJ_MASS; +#endif + g_numZombies++; } @@ -282,7 +304,11 @@ bool CNPC_BaseZombie::FindNearestPhysicsObject( int iMaxMass ) float dist = VectorNormalize(vecDirToEnemy); vecDirToEnemy.z = 0; - if( dist > ZOMBIE_PLAYER_MAX_SWAT_DIST ) +#ifndef MAPBASE + if (dist > ZOMBIE_PLAYER_MAX_SWAT_DIST) +#else + if (dist > m_flMaxDistToSwat) +#endif { // Player is too far away. Don't bother // trying to swat anything at them until @@ -311,6 +337,12 @@ bool CNPC_BaseZombie::FindNearestPhysicsObject( int iMaxMass ) pEntity->VPhysicsGetObject()->IsAsleep() && pEntity->VPhysicsGetObject()->IsMoveable() ) { +#ifdef MAPBASE + // Don't swat props that don't want to be swatted + if (pEntity->HasSpawnFlags(SF_PHYSPROP_NO_ZOMBIE_SWAT) && dynamic_cast(pEntity)) + return ITERATION_CONTINUE; +#endif + return CFlaggedEntitiesEnum::EnumElement( pHandleEntity ); } return ITERATION_CONTINUE; @@ -716,6 +748,11 @@ bool CNPC_BaseZombie::ShouldBecomeTorso( const CTakeDamageInfo &info, float flDa if ( info.GetDamageType() & DMG_REMOVENORAGDOLL ) return false; +#ifdef MAPBASE + if ( HasSpawnFlags(SF_ZOMBIE_NO_TORSO) ) + return false; +#endif + if ( m_fIsTorso ) { // Already split. @@ -761,7 +798,11 @@ bool CNPC_BaseZombie::ShouldBecomeTorso( const CTakeDamageInfo &info, float flDa //----------------------------------------------------------------------------- HeadcrabRelease_t CNPC_BaseZombie::ShouldReleaseHeadcrab( const CTakeDamageInfo &info, float flDamageThreshold ) { +#ifdef MAPBASE + if ( m_iHealth <= 0 && !m_fIsHeadless && !HasSpawnFlags(SF_ZOMBIE_NO_HEADCRAB_SPAWN)) +#else if ( m_iHealth <= 0 ) +#endif { if ( info.GetDamageType() & DMG_REMOVENORAGDOLL ) return RELEASE_NO; @@ -853,7 +894,11 @@ int CNPC_BaseZombie::OnTakeDamage_Alive( const CTakeDamageInfo &inputInfo ) bool bSquashed = IsSquashed(info); bool bKilledByVehicle = ( ( info.GetDamageType() & DMG_VEHICLE ) != 0 ); +#ifdef MAPBASE + if ( !m_fIsTorso && (bChopped || bSquashed) && !bKilledByVehicle && !(info.GetDamageType() & DMG_REMOVENORAGDOLL) && !HasSpawnFlags(SF_ZOMBIE_NO_TORSO) ) +#else if( !m_fIsTorso && (bChopped || bSquashed) && !bKilledByVehicle && !(info.GetDamageType() & DMG_REMOVENORAGDOLL) ) +#endif { if( bChopped ) { @@ -1062,6 +1107,10 @@ bool CNPC_BaseZombie::ShouldIgniteZombieGib( void ) #endif } +#ifdef MAPBASE +extern CBaseAnimating *CreateServerRagdollSubmodel( CBaseAnimating *pOwner, const char *pModelName, const Vector &position, const QAngle &angles, int collisionGroup ); +#endif + //----------------------------------------------------------------------------- // Purpose: Handle the special case of a zombie killed by a physics chopper. //----------------------------------------------------------------------------- @@ -1082,6 +1131,14 @@ void CNPC_BaseZombie::DieChopped( const CTakeDamageInfo &info ) } } +#ifdef MAPBASE + // Hack for fast zombies using base torso rules. + if (m_iHealth > 0) + { + SetHealth( 0 ); + } +#endif + float flFadeTime = 0.0; if( HasSpawnFlags( SF_NPC_FADE_CORPSE ) ) @@ -1103,9 +1160,32 @@ void CNPC_BaseZombie::DieChopped( const CTakeDamageInfo &info ) vecLegsForce.z *= -10; } +#ifdef MAPBASE + CBaseEntity *pLegGib = NULL; + if ( m_bForceServerRagdoll ) + { + pLegGib = CreateServerRagdollSubmodel( this, GetLegsModel(), GetAbsOrigin(), GetAbsAngles(), COLLISION_GROUP_INTERACTIVE_DEBRIS ); + pLegGib->VPhysicsGetObject()->AddVelocity(&vecLegsForce, NULL); + if (ShouldIgniteZombieGib()) + static_cast(pLegGib)->Ignite( random->RandomFloat( 8.0, 12.0 ), false ); + + if ( flFadeTime > 0.0 ) + { + pLegGib->SUB_StartFadeOut( flFadeTime, false ); + } + } + else + pLegGib = CreateRagGib( GetLegsModel(), GetAbsOrigin(), GetAbsAngles(), vecLegsForce, flFadeTime, ShouldIgniteZombieGib() ); +#else CBaseEntity *pLegGib = CreateRagGib( GetLegsModel(), GetAbsOrigin(), GetAbsAngles(), vecLegsForce, flFadeTime, ShouldIgniteZombieGib() ); +#endif if ( pLegGib ) { +#ifdef MAPBASE + // Inherit some misc. properties + pLegGib->m_iViewHideFlags = m_iViewHideFlags; +#endif + CopyRenderColorTo( pLegGib ); } @@ -1122,15 +1202,42 @@ void CNPC_BaseZombie::DieChopped( const CTakeDamageInfo &info ) QAngle TorsoAngles; TorsoAngles = GetAbsAngles(); TorsoAngles.x -= 90.0f; +#ifdef MAPBASE + CBaseEntity *pTorsoGib = NULL; + if ( m_bForceServerRagdoll ) + { + pTorsoGib = CreateServerRagdollSubmodel( this, GetTorsoModel(), GetAbsOrigin() + Vector( 0, 0, 64 ), TorsoAngles, COLLISION_GROUP_INTERACTIVE_DEBRIS ); + pTorsoGib->VPhysicsGetObject()->AddVelocity(&forceVector, NULL); + if (ShouldIgniteZombieGib()) + static_cast(pLegGib)->Ignite( random->RandomFloat( 8.0, 12.0 ), false ); + + if ( flFadeTime > 0.0 ) + { + pTorsoGib->SUB_StartFadeOut( flFadeTime, false ); + } + } + else + pTorsoGib = CreateRagGib( GetTorsoModel(), GetAbsOrigin() + Vector( 0, 0, 64 ), TorsoAngles, forceVector, flFadeTime, ShouldIgniteZombieGib() ); +#else CBaseEntity *pTorsoGib = CreateRagGib( GetTorsoModel(), GetAbsOrigin() + Vector( 0, 0, 64 ), TorsoAngles, forceVector, flFadeTime, ShouldIgniteZombieGib() ); +#endif if ( pTorsoGib ) { CBaseAnimating *pAnimating = dynamic_cast(pTorsoGib); if( pAnimating ) { pAnimating->SetBodygroup( ZOMBIE_BODYGROUP_HEADCRAB, !m_fIsHeadless ); +#ifdef MAPBASE + // Inherit some animating properties + pAnimating->m_nSkin = m_nSkin; +#endif } +#ifdef MAPBASE + // Inherit some misc. properties + pTorsoGib->m_iViewHideFlags = m_iViewHideFlags; +#endif + pTorsoGib->SetOwnerEntity( this ); CopyRenderColorTo( pTorsoGib ); @@ -1559,6 +1666,10 @@ void CNPC_BaseZombie::HandleAnimEvent( animevent_t *pEvent ) pPhysObj->AddVelocity( &v, &angVelocity ); +#ifdef MAPBASE + m_OnSwattedProp.Set(pPhysicsEntity, pPhysicsEntity, this); +#endif + // If we don't put the object scan time well into the future, the zombie // will re-select the object he just hit as it is flying away from him. // It will likely always be the nearest object because the zombie moved @@ -1651,7 +1762,11 @@ void CNPC_BaseZombie::HandleAnimEvent( animevent_t *pEvent ) dmgInfo.SetDamagePosition( vecHeadCrabPosition ); +#ifdef MAPBASE + ReleaseHeadcrab( vecHeadCrabPosition, vVelocity *iSpeed, true, false, true ); +#else ReleaseHeadcrab( EyePosition(), vVelocity * iSpeed, true, false, true ); +#endif GuessDamageForce( &dmgInfo, vVelocity, vecHeadCrabPosition, 0.5f ); TakeDamage( dmgInfo ); @@ -1833,6 +1948,31 @@ void CNPC_BaseZombie::OnScheduleChange( void ) } +//--------------------------------------------------------- +//--------------------------------------------------------- + +bool CNPC_BaseZombie::CanFlinch( void ) +{ + if (!BaseClass::CanFlinch()) + return false; + +#ifdef MAPBASE + if (zombie_no_flinch_during_unique_anim.GetBool()) + { + // Don't flinch if currently playing actbusy animation (navigating to or from one is fine) + if (m_ActBusyBehavior.IsInsideActBusy()) + return false; + + // Don't flinch if currently playing scripted sequence (navigating to or from one is fine) + if (m_NPCState == NPC_STATE_SCRIPT && (IsCurSchedule( SCHED_SCRIPTED_WAIT, false ) || IsCurSchedule( SCHED_SCRIPTED_FACE, false ))) + return false; + } +#endif + + return true; +} + + //--------------------------------------------------------- //--------------------------------------------------------- int CNPC_BaseZombie::SelectFailSchedule( int failedSchedule, int failedTask, AI_TaskFailureCode_t taskFailCode ) @@ -2027,7 +2167,11 @@ void CNPC_BaseZombie::GatherConditions( void ) // between him and the object he's heading for already. if( gpGlobals->curtime >= m_flNextSwatScan && (m_hPhysicsEnt == NULL) ) { +#ifdef MAPBASE + FindNearestPhysicsObject(m_iMaxObjMassToSwat); +#else FindNearestPhysicsObject( ZOMBIE_MAX_PHYSOBJ_MASS ); +#endif m_flNextSwatScan = gpGlobals->curtime + 2.0; } } @@ -2255,7 +2399,26 @@ void CNPC_BaseZombie::BecomeTorso( const Vector &vecTorsoForce, const Vector &ve if ( m_fIsTorso == true ) { // -40 on Z to make up for the +40 on Z that we did above. This stops legs spawning above the head. +#ifdef MAPBASE + CBaseEntity *pGib = NULL; + if ( m_bForceServerRagdoll ) + { + pGib = CreateServerRagdollSubmodel( this, GetLegsModel(), GetAbsOrigin() - Vector(0, 0, 40), GetAbsAngles(), COLLISION_GROUP_INTERACTIVE_DEBRIS ); + if (pGib && pGib->VPhysicsGetObject()) + { + pGib->VPhysicsGetObject()->AddVelocity( &vecLegsForce, NULL ); + + if (flFadeTime > 0.0) + { + pGib->SUB_StartFadeOut( flFadeTime, false ); + } + } + } + else + pGib = CreateRagGib( GetLegsModel(), GetAbsOrigin() - Vector(0, 0, 40), GetAbsAngles(), vecLegsForce, flFadeTime ); +#else CBaseEntity *pGib = CreateRagGib( GetLegsModel(), GetAbsOrigin() - Vector(0, 0, 40), GetAbsAngles(), vecLegsForce, flFadeTime ); +#endif // don't collide with this thing ever if ( pGib ) @@ -2322,6 +2485,20 @@ void CNPC_BaseZombie::RemoveHead( void ) } +//--------------------------------------------------------- +//--------------------------------------------------------- +void CNPC_BaseZombie::SetModel( const char *szModelName ) +{ +#ifdef MAPBASE + // Zombies setting the same model again is a problem when they should maintain their current sequence (e.g. during dynamic interactions) + if ( IsRunningDynamicInteraction() && GetModelIndex() != 0 && FStrEq( szModelName, STRING(GetModelName()) ) ) + return; +#endif + + BaseClass::SetModel( szModelName ); +} + + bool CNPC_BaseZombie::ShouldPlayFootstepMoan( void ) { if( random->RandomInt( 1, zombie_stepfreq.GetInt() * s_iAngryZombies ) == 1 ) @@ -2337,9 +2514,15 @@ bool CNPC_BaseZombie::ShouldPlayFootstepMoan( void ) #define CRAB_HULL_EXPAND 1.1f //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- -bool CNPC_BaseZombie::HeadcrabFits( CBaseAnimating *pCrab ) +bool CNPC_BaseZombie::HeadcrabFits( CBaseAnimating *pCrab, const Vector *vecOrigin ) { - Vector vecSpawnLoc = pCrab->GetAbsOrigin(); + Vector vecSpawnLoc; +#ifdef MAPBASE + if (vecOrigin) + vecSpawnLoc = *vecOrigin; + else +#endif + vecSpawnLoc = pCrab->GetAbsOrigin(); CTraceFilterSimpleList traceFilter( COLLISION_GROUP_NONE ); traceFilter.AddEntityToIgnore( pCrab ); @@ -2391,7 +2574,25 @@ void CNPC_BaseZombie::ReleaseHeadcrab( const Vector &vecOrigin, const Vector &ve if( fRagdollCrab ) { //Vector vecForce = Vector( 0, 0, random->RandomFloat( 700, 1100 ) ); +#ifdef MAPBASE + CBaseEntity *pGib = NULL; + if ( m_bForceServerRagdoll ) + { + pGib = CreateServerRagdollSubmodel( this, GetHeadcrabModel(), vecOrigin, GetLocalAngles(), COLLISION_GROUP_INTERACTIVE_DEBRIS ); + if (pGib && pGib->VPhysicsGetObject()) + { + pGib->VPhysicsGetObject()->AddVelocity(&vecVelocity, NULL); + if (ShouldIgniteZombieGib()) + static_cast(pGib)->Ignite( random->RandomFloat( 8.0, 12.0 ), false ); + + pGib->SUB_StartFadeOut( 15, false ); + } + } + else + pGib = CreateRagGib( GetHeadcrabModel(), vecOrigin, GetLocalAngles(), vecVelocity, 15, ShouldIgniteZombieGib() ); +#else CBaseEntity *pGib = CreateRagGib( GetHeadcrabModel(), vecOrigin, GetLocalAngles(), vecVelocity, 15, ShouldIgniteZombieGib() ); +#endif if ( pGib ) { @@ -2404,23 +2605,42 @@ void CNPC_BaseZombie::ReleaseHeadcrab( const Vector &vecOrigin, const Vector &ve SetHeadcrabSpawnLocation( iCrabAttachment, pAnimatingGib ); } +#ifdef MAPBASE + // Server ragdolls don't have a valid origin on spawn, so we have to use the origin originally passed + if( !HeadcrabFits( pAnimatingGib, m_bForceServerRagdoll ? &vecOrigin : NULL ) ) +#else if( !HeadcrabFits(pAnimatingGib) ) +#endif { UTIL_Remove(pGib); return; } +#ifdef MAPBASE + // Inherit some misc. properties + pGib->m_iViewHideFlags = m_iViewHideFlags; +#endif + pGib->SetOwnerEntity( this ); CopyRenderColorTo( pGib ); if( UTIL_ShouldShowBlood(BLOOD_COLOR_YELLOW) ) { - UTIL_BloodImpact( pGib->WorldSpaceCenter(), Vector(0,0,1), BLOOD_COLOR_YELLOW, 1 ); + Vector vecGibCenter; +#ifdef MAPBASE + // Server ragdolls don't have a valid origin on spawn, so we have to use the origin originally passed + if (m_bForceServerRagdoll) + vecGibCenter = vecOrigin; + else +#endif + vecGibCenter = pGib->WorldSpaceCenter(); + + UTIL_BloodImpact( vecGibCenter, Vector(0,0,1), BLOOD_COLOR_YELLOW, 1 ); for ( int i = 0 ; i < 3 ; i++ ) { - Vector vecSpot = pGib->WorldSpaceCenter(); + Vector vecSpot = vecGibCenter; vecSpot.x += random->RandomFloat( -8, 8 ); vecSpot.y += random->RandomFloat( -8, 8 ); @@ -2449,6 +2669,15 @@ void CNPC_BaseZombie::ReleaseHeadcrab( const Vector &vecOrigin, const Vector &ve // add on the parent flags pCrab->AddSpawnFlags( m_spawnflags & ZOMBIE_CRAB_INHERITED_SPAWNFLAGS ); + +#ifdef MAPBASE + // Inherit some misc. properties + pCrab->m_bForceServerRagdoll = m_bForceServerRagdoll; + pCrab->m_iViewHideFlags = m_iViewHideFlags; + + // Add response context for companion response (more reliable than checking for post-death zombie entity) + pCrab->AddContext( "from_zombie", "1", 2.0f ); +#endif // make me the crab's owner to avoid collision issues pCrab->SetOwnerEntity( this ); @@ -2502,6 +2731,10 @@ void CNPC_BaseZombie::ReleaseHeadcrab( const Vector &vecOrigin, const Vector &ve CopyRenderColorTo( pCrab ); pCrab->Activate(); + +#ifdef MAPBASE + m_OnCrab.Set( pCrab, pCrab, this ); +#endif } if( fRemoveHead ) diff --git a/src/game/server/hl2/npc_BaseZombie.h b/src/game/server/hl2/npc_BaseZombie.h index d4800f3c..cfc11566 100644 --- a/src/game/server/hl2/npc_BaseZombie.h +++ b/src/game/server/hl2/npc_BaseZombie.h @@ -42,6 +42,11 @@ extern int AE_ZOMBIE_POUND; #define ZOMBIE_BLOOD_RIGHT_HAND 1 #define ZOMBIE_BLOOD_BOTH_HANDS 2 #define ZOMBIE_BLOOD_BITE 3 + +#ifdef MAPBASE + #define SF_ZOMBIE_NO_TORSO ( 1 << 15 ) + #define SF_ZOMBIE_NO_HEADCRAB_SPAWN ( 1 << 16 ) +#endif enum HeadcrabRelease_t @@ -137,7 +142,14 @@ public: } int MeleeAttack1Conditions ( float flDot, float flDist ); - virtual float GetClawAttackRange() const { return ZOMBIE_MELEE_REACH; } + virtual float GetClawAttackRange() const + { +#ifdef MAPBASE + return m_flMeleeReach; +#else + return ZOMBIE_MELEE_REACH; +#endif + } // No range attacks int RangeAttack1Conditions ( float flDot, float flDist ) { return( 0 ); } @@ -147,6 +159,8 @@ public: int OnTakeDamage_Alive( const CTakeDamageInfo &info ); virtual float GetReactionDelay( CBaseEntity *pEnemy ) { return 0.0; } + bool CanFlinch( void ); + virtual int SelectSchedule ( void ); virtual int SelectFailSchedule( int failedSchedule, int failedTask, AI_TaskFailureCode_t taskFailCode ); virtual void BuildScheduleTestBits( void ); @@ -182,9 +196,10 @@ public: // Headcrab releasing/breaking apart void RemoveHead( void ); virtual void SetZombieModel( void ) { }; + virtual void SetModel( const char *szModelName ); virtual void BecomeTorso( const Vector &vecTorsoForce, const Vector &vecLegsForce ); virtual bool CanBecomeLiveTorso() { return false; } - virtual bool HeadcrabFits( CBaseAnimating *pCrab ); + virtual bool HeadcrabFits( CBaseAnimating *pCrab, const Vector *vecOrigin = NULL ); void ReleaseHeadcrab( const Vector &vecOrigin, const Vector &vecVelocity, bool fRemoveHead, bool fRagdollBody, bool fRagdollCrab = false ); void SetHeadcrabSpawnLocation( int iCrabAttachment, CBaseAnimating *pCrab ); @@ -250,6 +265,12 @@ protected: float m_flNextFlinch; +#ifdef MAPBASE + float m_flMeleeReach; + float m_flMaxDistToSwat; + int m_iMaxObjMassToSwat; +#endif + bool m_bHeadShot; // Used to determine the survival of our crab beyond our death. // @@ -259,6 +280,10 @@ protected: float m_flBurnDamageResetTime; // Time at which we reset the burn damage. EHANDLE m_hPhysicsEnt; +#ifdef MAPBASE + COutputEHANDLE m_OnSwattedProp; + COutputEHANDLE m_OnCrab; +#endif float m_flNextMoanSound; float m_flNextSwat; diff --git a/src/game/server/hl2/npc_PoisonZombie.cpp b/src/game/server/hl2/npc_PoisonZombie.cpp index eafebe2d..1611e15b 100644 --- a/src/game/server/hl2/npc_PoisonZombie.cpp +++ b/src/game/server/hl2/npc_PoisonZombie.cpp @@ -285,7 +285,11 @@ void CNPC_PoisonZombie::Spawn( void ) { Precache(); +#ifndef MAPBASE // Controlled by KV m_fIsTorso = m_fIsHeadless = false; +#else + m_fIsTorso = false; +#endif #ifdef HL2_EPISODIC SetBloodColor( BLOOD_COLOR_ZOMBIE ); @@ -632,10 +636,16 @@ void CNPC_PoisonZombie::BreatheOffShort( void ) { if ( m_bNearEnemy ) { +#ifdef MAPBASE + if (m_pFastBreathSound) +#endif ENVELOPE_CONTROLLER.SoundPlayEnvelope( m_pFastBreathSound, SOUNDCTRL_CHANGE_VOLUME, envPoisonZombieBreatheVolumeOffShort, ARRAYSIZE(envPoisonZombieBreatheVolumeOffShort) ); } else { +#ifdef MAPBASE + if (m_pSlowBreathSound) +#endif ENVELOPE_CONTROLLER.SoundPlayEnvelope( m_pSlowBreathSound, SOUNDCTRL_CHANGE_VOLUME, envPoisonZombieBreatheVolumeOffShort, ARRAYSIZE(envPoisonZombieBreatheVolumeOffShort) ); } } diff --git a/src/game/server/hl2/npc_alyx.cpp b/src/game/server/hl2/npc_alyx.cpp index f9dc1286..72b3f328 100644 --- a/src/game/server/hl2/npc_alyx.cpp +++ b/src/game/server/hl2/npc_alyx.cpp @@ -33,6 +33,10 @@ END_DATADESC() int AE_ALYX_EMPTOOL_ATTACHMENT; int AE_ALYX_EMPTOOL_SEQUENCE; +#ifdef MAPBASE +ConVar sk_alyx_health( "sk_alyx_health", "80" ); +#endif + //========================================================= // Classify - indicates this NPC's place in the // relationship table. @@ -127,7 +131,11 @@ void CNPC_Alyx::Spawn() AddEFlags( EFL_NO_DISSOLVE | EFL_NO_MEGAPHYSCANNON_RAGDOLL | EFL_NO_PHYSCANNON_INTERACTION ); +#ifdef MAPBASE + m_iHealth = sk_alyx_health.GetInt(); +#else m_iHealth = 80; +#endif NPCInit(); } diff --git a/src/game/server/hl2/npc_alyx.h b/src/game/server/hl2/npc_alyx.h index 96c1c917..266dca6d 100644 --- a/src/game/server/hl2/npc_alyx.h +++ b/src/game/server/hl2/npc_alyx.h @@ -31,6 +31,16 @@ public: bool IsReadinessCapable() { return false; } void DeathSound( const CTakeDamageInfo &info ); +#ifdef MAPBASE + // Alyx was never meant to automatically unholster her weapon in non-episodic Half-Life 2. + // Now that all allies can holster/unholster, this is a precaution in case it breaks anything. + // Try OnFoundEnemy > UnholsterWeapon if you want Alyx to automatically unholster in non-episodic HL2 maps. + bool CanUnholsterWeapon() { return false; } + + // Use Alyx's default subtitle color (255,212,255) + bool GetGameTextSpeechParams( hudtextparms_t ¶ms ) { params.r1 = 255; params.g1 = 212; params.b1 = 255; return BaseClass::GetGameTextSpeechParams( params ); } +#endif + EHANDLE m_hEmpTool; DECLARE_DATADESC(); diff --git a/src/game/server/hl2/npc_alyx_episodic.cpp b/src/game/server/hl2/npc_alyx_episodic.cpp index e98c89f5..dd5a35b0 100644 --- a/src/game/server/hl2/npc_alyx_episodic.cpp +++ b/src/game/server/hl2/npc_alyx_episodic.cpp @@ -36,6 +36,9 @@ #include "ai_interactions.h" #include "weapon_flaregun.h" #include "env_debughistory.h" +#ifdef MAPBASE +#include "mapbase/GlobalStrings.h" +#endif extern Vector PointOnLineNearestPoint(const Vector& vStartPos, const Vector& vEndPos, const Vector& vPoint); @@ -72,6 +75,10 @@ bool IsInCommentaryMode( void ); #define ALYX_MIN_ENEMY_HEALTH_TO_CROUCH 15 #define ALYX_CROUCH_DELAY 5 // Time after crouching before Alyx will crouch again +#ifdef MAPBASE +ConVar sk_alyx_health( "sk_alyx_health", "80" ); +#endif + //----------------------------------------------------------------------------- // Interactions //----------------------------------------------------------------------------- @@ -136,14 +143,23 @@ END_DATADESC() static int AE_ALYX_EMPTOOL_ATTACHMENT; static int AE_ALYX_EMPTOOL_SEQUENCE; static int AE_ALYX_EMPTOOL_USE; +#ifndef MAPBASE static int COMBINE_AE_BEGIN_ALTFIRE; static int COMBINE_AE_ALTFIRE; +#endif ConVar npc_alyx_readiness( "npc_alyx_readiness", "1" ); ConVar npc_alyx_force_stop_moving( "npc_alyx_force_stop_moving", "1" ); ConVar npc_alyx_readiness_transitions( "npc_alyx_readiness_transitions", "1" ); ConVar npc_alyx_crouch( "npc_alyx_crouch", "1" ); +#ifdef MAPBASE +ConVar npc_alyx_interact_manhacks( "npc_alyx_interact_manhacks", "1" ); +ConVar npc_alyx_interact_turrets( "npc_alyx_interact_turrets", "0" ); + +ConVar npc_alyx_allow_fly( "npc_alyx_allow_fly", "0", FCVAR_NONE, "Allows Alyx to use FL_FLY outside of scripted sequences, actbusy, or navigation." ); +#endif + // global pointer to Alyx for fast lookups CEntityClassList g_AlyxList; template <> CNPC_Alyx *CEntityClassList::m_pClassList = NULL; @@ -264,6 +280,7 @@ void CNPC_Alyx::HandleAnimEvent( animevent_t *pEvent ) } return; } +#ifndef MAPBASE else if ( pEvent->event == COMBINE_AE_BEGIN_ALTFIRE ) { EmitSound( "Weapon_CombineGuard.Special1" ); @@ -280,6 +297,7 @@ void CNPC_Alyx::HandleAnimEvent( animevent_t *pEvent ) return; } +#endif switch( pEvent->event ) { @@ -303,7 +321,9 @@ CNPC_Alyx *CNPC_Alyx::GetAlyx( void ) //========================================================= bool CNPC_Alyx::CreateBehaviors() { +#ifndef MAPBASE // Moved to CNPC_PlayerCompanion AddBehavior( &m_FuncTankBehavior ); +#endif bool result = BaseClass::CreateBehaviors(); return result; @@ -334,7 +354,11 @@ void CNPC_Alyx::Spawn() AddEFlags( EFL_NO_DISSOLVE | EFL_NO_MEGAPHYSCANNON_RAGDOLL | EFL_NO_PHYSCANNON_INTERACTION ); +#ifdef MAPBASE + m_iHealth = sk_alyx_health.GetInt(); +#else m_iHealth = 80; +#endif m_bloodColor = DONT_BLEED; NPCInit(); @@ -379,9 +403,15 @@ void CNPC_Alyx::Precache() UTIL_PrecacheOther( "env_alyxemp" ); CLASSNAME_ALYXGUN = AllocPooledString( "weapon_alyxgun" ); +#ifdef MAPBASE + CLASSNAME_SMG1 = gm_iszSMG1Classname; + CLASSNAME_SHOTGUN = gm_iszShotgunClassname; + CLASSNAME_AR2 = gm_iszAR2Classname; +#else CLASSNAME_SMG1 = AllocPooledString( "weapon_smg1" ); CLASSNAME_SHOTGUN = AllocPooledString( "weapon_shotgun" ); CLASSNAME_AR2 = AllocPooledString( "weapon_ar2" ); +#endif } //----------------------------------------------------------------------------- @@ -391,7 +421,15 @@ void CNPC_Alyx::Activate( void ) { // Alyx always kicks her health back up to full after loading a savegame. // Avoids problems with players saving the game in places where she dies immediately afterwards. +#ifdef MAPBASE + // Alyx's health can be >80 thanks to the new convar, and we don't want a 1000-health Alyx to reload + // from 1 health to 1000 health, so this should only kick in if her health is less than her default + // (we also probably don't want this to happen if she's not an ally) + if (IsPlayerAlly() && m_iHealth < 80) + m_iHealth = 80; +#else m_iHealth = 80; +#endif BaseClass::Activate(); @@ -426,6 +464,19 @@ void CNPC_Alyx::Activate( void ) { g_HackOutland10DamageHack = true; } + +#ifdef MAPBASE + // Please, this is not the worst hack you've caught me doing. + for ( int i = 0; i < m_ScriptedInteractions.Count(); i++ ) + { + ScriptedNPCInteraction_t *pInteraction = &m_ScriptedInteractions[i]; + + if (pInteraction->iszMyWeapon == CLASSNAME_ALYXGUN) + pInteraction->iszMyWeapon = AllocPooledString("WEPCLASS_HANDGUN"); + else if (pInteraction->iszMyWeapon == CLASSNAME_SHOTGUN) + pInteraction->iszMyWeapon = AllocPooledString("!=WEPCLASS_HANDGUN"); + } +#endif } //----------------------------------------------------------------------------- @@ -612,11 +663,13 @@ void CNPC_Alyx::PrescheduleThink( void ) } } +#ifndef MAPBASE // See CAI_BaseNPC // If Alyx is in combat, and she doesn't have her gun out, fetch it if ( GetState() == NPC_STATE_COMBAT && IsWeaponHolstered() && !m_FuncTankBehavior.IsRunning() ) { SetDesiredWeaponState( DESIREDWEAPONSTATE_UNHOLSTERED ); } +#endif // If we're in stealth mode, and we can still see the stealth node, keep using it if ( GetReadinessLevel() == AIRL_STEALTH ) @@ -759,11 +812,12 @@ void CNPC_Alyx::GatherConditions() } } - +#ifndef MAPBASE // Moved to CNPC_PlayerCompanion if ( m_NPCState == NPC_STATE_COMBAT ) { DoCustomCombatAI(); } +#endif if( HasInteractTarget() ) { @@ -835,7 +889,11 @@ void CNPC_Alyx::GatherConditions() // ROBIN: This was here to solve a problem in a playtest. We've since found what we think was the cause. // It's a useful piece of debug to have lying there, so I've left it in. - if ( (GetFlags() & FL_FLY) && m_NPCState != NPC_STATE_SCRIPT && !m_ActBusyBehavior.IsActive() && !m_PassengerBehavior.IsEnabled() ) + if ( (GetFlags() & FL_FLY) && m_NPCState != NPC_STATE_SCRIPT && !m_ActBusyBehavior.IsActive() && !m_PassengerBehavior.IsEnabled() +#ifdef MAPBASE + && GetNavType() != NAV_CLIMB && !npc_alyx_allow_fly.GetBool() +#endif + ) { Warning( "Removed FL_FLY from Alyx, who wasn't running a script or actbusy. Time %.2f, map %s.\n", gpGlobals->curtime, STRING(gpGlobals->mapname) ); RemoveFlag( FL_FLY ); @@ -928,7 +986,12 @@ bool CNPC_Alyx::IsValidEnemy( CBaseEntity *pEnemy ) return false; } +#ifdef MAPBASE + // Come to the defense of anyone we like, not just ourselves or the player. + if( pEnemy->GetEnemy() != this && IRelationType(pEnemy->GetEnemy()) == D_LI ) +#else if( pEnemy->GetEnemy() != this && !pEnemy->GetEnemy()->IsPlayer() ) +#endif { return false; } @@ -991,7 +1054,12 @@ void CNPC_Alyx::Event_KilledOther( CBaseEntity *pVictim, const CTakeDamageInfo & return; } +#ifdef MAPBASE + // Don't do the custom target thing against dissolve or blast damage (Alyx can do that with companion grenades/balls) + if( !HasShotgun() && !(info.GetDamageType() & (DMG_DISSOLVE | DMG_BLAST)) ) +#else if( !HasShotgun() ) +#endif { CAI_BaseNPC *pTarget = CreateCustomTarget( pVictim->GetAbsOrigin(), 2.0f ); @@ -1009,6 +1077,13 @@ void CNPC_Alyx::Event_KilledOther( CBaseEntity *pVictim, const CTakeDamageInfo & pMemory->timeFirstSeen = gpGlobals->curtime - 10.0f; } } + +#ifdef MAPBASE + // This call has a side effect of causing Alyx to speak a regular companion TLK_ENEMY_DEAD, which may conflict with the TLK_ALYX_ENEMY_DEAD + // further up, but this is fine because concepts are protected against interrupting each other and Alyx may even be overridden + // to use TLK_ENEMY_DEAD instead, which is used by other NPCs and appends more modifiers. + BaseClass::Event_KilledOther( pVictim, info ); +#endif } //----------------------------------------------------------------------------- @@ -1017,10 +1092,14 @@ void CNPC_Alyx::Event_KilledOther( CBaseEntity *pVictim, const CTakeDamageInfo & //----------------------------------------------------------------------------- void CNPC_Alyx::EnemyIgnited( CAI_BaseNPC *pVictim ) { +#ifdef MAPBASE + BaseClass::EnemyIgnited( pVictim ); +#else if ( FVisible( pVictim ) ) { SpeakIfAllowed( TLK_ENEMY_BURNING ); } +#endif } //----------------------------------------------------------------------------- @@ -1177,6 +1256,7 @@ void CNPC_Alyx::DoCustomSpeechAI( void ) CBasePlayer *pPlayer = AI_GetSinglePlayer(); +#ifndef MAPBASE // Ported to CNPC_PlayerCompanion if ( HasCondition(COND_NEW_ENEMY) && GetEnemy() ) { if ( GetEnemy()->Classify() == CLASS_HEADCRAB ) @@ -1203,6 +1283,7 @@ void CNPC_Alyx::DoCustomSpeechAI( void ) } } } +#endif // Darkness mode speech ClearCondition( COND_ALYX_IN_DARK ); @@ -1620,9 +1701,88 @@ Activity CNPC_Alyx::NPC_TranslateActivity( Activity activity ) case ACT_DROP_WEAPON: if ( HasShotgun() ) return (Activity)ACT_DROP_WEAPON_SHOTGUN; } +#if EXPANDED_HL2_WEAPON_ACTIVITIES + // Alyx has her own pistol readiness animations which use the default activities + switch (activity) + { + case ACT_IDLE_PISTOL_RELAXED: + return ACT_IDLE_RELAXED; + case ACT_IDLE_PISTOL_STIMULATED: + return ACT_IDLE_STIMULATED; + case ACT_WALK_PISTOL_RELAXED: + return ACT_WALK; + case ACT_WALK_PISTOL_STIMULATED: + return ACT_WALK_PISTOL; + case ACT_RUN_PISTOL_RELAXED: + return ACT_RUN; + case ACT_RUN_PISTOL_STIMULATED: + return ACT_RUN_PISTOL; + } +#endif + return activity; } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +Activity CNPC_Alyx::Weapon_TranslateActivity( Activity activity, bool *pRequired ) +{ + activity = BaseClass::Weapon_TranslateActivity( activity, pRequired ); + +#if EXPANDED_HL2_WEAPON_ACTIVITIES + // Alyx has her own pistol readiness animations which use the default activities + switch (activity) + { + case ACT_IDLE_PISTOL_RELAXED: + return ACT_IDLE_RELAXED; + case ACT_IDLE_PISTOL_STIMULATED: + return ACT_IDLE_STIMULATED; + case ACT_WALK_PISTOL_RELAXED: + return ACT_WALK; + case ACT_WALK_PISTOL_STIMULATED: + return ACT_WALK_PISTOL; + case ACT_RUN_PISTOL_RELAXED: + return ACT_RUN; + case ACT_RUN_PISTOL_STIMULATED: + return ACT_RUN_PISTOL; + } +#endif + + return activity; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +Activity CNPC_Alyx::Weapon_BackupActivity( Activity activity, bool weaponTranslationWasRequired, CBaseCombatWeapon *pSpecificWeapon ) +{ + activity = BaseClass::Weapon_BackupActivity( activity, weaponTranslationWasRequired, pSpecificWeapon ); + +#if EXPANDED_HL2_WEAPON_ACTIVITIES + // Alyx has her own pistol readiness animations which use the default activities + switch (activity) + { + case ACT_IDLE_PISTOL_RELAXED: + return ACT_IDLE_RELAXED; + case ACT_IDLE_PISTOL_STIMULATED: + return ACT_IDLE_STIMULATED; + case ACT_WALK_PISTOL_RELAXED: + return ACT_WALK; + case ACT_WALK_PISTOL_STIMULATED: + return ACT_WALK_PISTOL; + case ACT_RUN_PISTOL_RELAXED: + return ACT_RUN; + case ACT_RUN_PISTOL_STIMULATED: + return ACT_RUN_PISTOL; + } +#endif + + return activity; +} +#endif + bool CNPC_Alyx::ShouldDeferToFollowBehavior() { return BaseClass::ShouldDeferToFollowBehavior(); @@ -1763,6 +1923,7 @@ int CNPC_Alyx::SelectSchedule( void ) //----------------------------------------------------------------------------- int CNPC_Alyx::SelectScheduleDanger( void ) { +#ifndef MAPBASE if( HasCondition( COND_HEAR_DANGER ) ) { CSound *pSound; @@ -1775,6 +1936,7 @@ int CNPC_Alyx::SelectScheduleDanger( void ) SpeakIfAllowed( TLK_DANGER_ZOMBINE_GRENADE ); } } +#endif return BaseClass::SelectScheduleDanger(); } @@ -1849,6 +2011,29 @@ int CNPC_Alyx::TranslateSchedule( int scheduleType ) //Warning("CROUCH: Standing, no enemy.\n" ); Stand(); } + +#ifdef MAPBASE + // This stuff was ported from npc_playercompanion to help Alyx use grenades. + if (HasGrenades() && !IsCrouching()) + { + if (CanAltFireEnemy( true ) && OccupyStrategySlot( SQUAD_SLOT_SPECIAL_ATTACK )) + { + return SCHED_PC_AR2_ALTFIRE; + } + + if ( !OccupyStrategySlotRange( SQUAD_SLOT_ATTACK1, SQUAD_SLOT_ATTACK2 ) ) + { + // Try throwing a grenade if Alyx is in a squad that already has attacking well in hand. + if ( CanGrenadeEnemy() ) + { + if ( OccupyStrategySlot( SQUAD_SLOT_SPECIAL_ATTACK ) ) + { + return SCHED_RANGE_ATTACK2; + } + } + } + } +#endif } return SCHED_ALYX_RANGE_ATTACK1; @@ -2941,7 +3126,12 @@ bool CNPC_Alyx::Crouch( void ) return false; // Alyx will ignore crouch requests while she has the shotgun +#ifdef MAPBASE + // Alyx will ignore crouch requests from anything that isn't a "pistol", e.g. her Alyx gun. + if ( GetActiveWeapon() && GetActiveWeapon()->WeaponClassify() != WEPCLASS_HANDGUN ) +#else if ( HasShotgun() ) +#endif return false; bool bWasStanding = !IsCrouching(); @@ -2974,9 +3164,13 @@ void CNPC_Alyx::DesireCrouch( void ) //----------------------------------------------------------------------------- void CNPC_Alyx::ModifyOrAppendCriteria( AI_CriteriaSet &set ) { +#ifdef MAPBASE + float fLengthOfLastCombat; +#else AIEnemiesIter_t iter; float fLengthOfLastCombat; int iNumEnemies; +#endif if ( GetState() == NPC_STATE_COMBAT ) { @@ -2989,6 +3183,7 @@ void CNPC_Alyx::ModifyOrAppendCriteria( AI_CriteriaSet &set ) set.AppendCriteria( "combat_length", UTIL_VarArgs( "%.3f", fLengthOfLastCombat ) ); +#ifndef MAPBASE // Moved to CNPC_PlayerCompanion iNumEnemies = 0; for ( AI_EnemyInfo_t *pEMemory = GetEnemies()->GetFirst(&iter); pEMemory != NULL; pEMemory = GetEnemies()->GetNext(&iter) ) { @@ -2998,6 +3193,7 @@ void CNPC_Alyx::ModifyOrAppendCriteria( AI_CriteriaSet &set ) } } set.AppendCriteria( "num_enemies", UTIL_VarArgs( "%d", iNumEnemies ) ); +#endif set.AppendCriteria( "darkness_mode", UTIL_VarArgs( "%d", HasCondition( COND_ALYX_IN_DARK ) ) ); set.AppendCriteria( "water_level", UTIL_VarArgs( "%d", GetWaterLevel() ) ); @@ -3149,7 +3345,12 @@ bool CNPC_Alyx::PlayerInSpread( const Vector &sourcePos, const Vector &targetPos { CBasePlayer *pPlayer = UTIL_PlayerByIndex( i ); +#ifdef MAPBASE + // "> D_FR" means it isn't D_HT, D_FR, or D_ER (error disposition) + if ( pPlayer && ( !ignoreHatedPlayers || IRelationType( pPlayer ) > D_FR ) && !(pPlayer->GetFlags() & FL_NOTARGET) ) +#else if ( pPlayer && ( !ignoreHatedPlayers || IRelationType( pPlayer ) != D_HT ) ) +#endif { //If the player is being lifted by a barnacle then go ahead and ignore the player and shoot. #ifdef HL2_EPISODIC @@ -3324,8 +3525,10 @@ AI_BEGIN_CUSTOM_NPC( npc_alyx, CNPC_Alyx ) DECLARE_ANIMEVENT( AE_ALYX_EMPTOOL_ATTACHMENT ) DECLARE_ANIMEVENT( AE_ALYX_EMPTOOL_SEQUENCE ) DECLARE_ANIMEVENT( AE_ALYX_EMPTOOL_USE ) +#ifndef MAPBASE DECLARE_ANIMEVENT( COMBINE_AE_BEGIN_ALTFIRE ) DECLARE_ANIMEVENT( COMBINE_AE_ALTFIRE ) +#endif DECLARE_CONDITION( COND_ALYX_HAS_INTERACT_TARGET ) DECLARE_CONDITION( COND_ALYX_NO_INTERACT_TARGET ) diff --git a/src/game/server/hl2/npc_alyx_episodic.h b/src/game/server/hl2/npc_alyx_episodic.h index 5657dae7..f24ddf67 100644 --- a/src/game/server/hl2/npc_alyx_episodic.h +++ b/src/game/server/hl2/npc_alyx_episodic.h @@ -54,6 +54,14 @@ public: bool OnBeginMoveAndShoot(); void SpeakAttacking( void ); +#ifdef MAPBASE + // This skips CAI_PlayerAlly's CanFlinch() function since Episodic Alyx can flinch to begin with. + virtual bool CanFlinch( void ) { return CAI_BaseActor::CanFlinch(); } + + // Use Alyx's default subtitle color (255,212,255) + bool GetGameTextSpeechParams( hudtextparms_t ¶ms ) { params.r1 = 255; params.g1 = 212; params.b1 = 255; return BaseClass::GetGameTextSpeechParams( params ); } +#endif + virtual float GetJumpGravity() const { return 1.8f; } // Crouching @@ -83,6 +91,10 @@ public: bool CanSeeEntityInDarkness( CBaseEntity *pEntity ); bool IsCoverPosition( const Vector &vecThreat, const Vector &vecPosition ); Activity NPC_TranslateActivity ( Activity activity ); +#ifdef MAPBASE + Activity Weapon_TranslateActivity( Activity baseAct, bool *pRequired = NULL ); + Activity Weapon_BackupActivity( Activity activity, bool weaponTranslationWasRequired = false, CBaseCombatWeapon *pSpecificWeapon = NULL ); +#endif bool ShouldDeferToFollowBehavior(); void BuildScheduleTestBits(); bool ShouldBehaviorSelectSchedule( CAI_BehaviorBase *pBehavior ); @@ -223,7 +235,9 @@ private: bool m_bShouldHaveEMP; +#ifndef MAPBASE // Moved to CNPC_PlayerCompanion CAI_FuncTankBehavior m_FuncTankBehavior; +#endif COutputEvent m_OnFinishInteractWithObject; COutputEvent m_OnPlayerUse; diff --git a/src/game/server/hl2/npc_antlion.cpp b/src/game/server/hl2/npc_antlion.cpp index 8cb7f789..bda685ea 100644 --- a/src/game/server/hl2/npc_antlion.cpp +++ b/src/game/server/hl2/npc_antlion.cpp @@ -65,6 +65,9 @@ ConVar sk_antlion_worker_burst_radius( "sk_antlion_worker_burst_radius", "160", ConVar g_test_new_antlion_jump( "g_test_new_antlion_jump", "1", FCVAR_ARCHIVE ); ConVar antlion_easycrush( "antlion_easycrush", "1" ); +#ifdef MAPBASE +ConVar antlion_no_ignite_die( "antlion_no_ignite_die", "0" ); +#endif ConVar g_antlion_cascade_push( "g_antlion_cascade_push", "1", FCVAR_ARCHIVE ); ConVar g_debug_antlion_worker( "g_debug_antlion_worker", "0" ); @@ -247,6 +250,9 @@ BEGIN_DATADESC( CNPC_Antlion ) DEFINE_INPUTFUNC( FIELD_VOID, "IgnoreBugbait", InputIgnoreBugbait ), DEFINE_INPUTFUNC( FIELD_VOID, "HearBugbait", InputHearBugbait ), DEFINE_INPUTFUNC( FIELD_STRING, "JumpAtTarget", InputJumpAtTarget ), +#ifdef MAPBASE + DEFINE_INPUTFUNC( FIELD_STRING, "SetFollowTarget", InputSetFollowTarget ), +#endif DEFINE_OUTPUT( m_OnReachFightGoal, "OnReachedFightGoal" ), DEFINE_OUTPUT( m_OnUnBurrowed, "OnUnBurrowed" ), @@ -274,17 +280,17 @@ void CNPC_Antlion::Spawn( void ) #ifdef HL2_EPISODIC if ( IsWorker() ) { - SetModel( ANTLION_WORKER_MODEL ); + SetModel( DefaultOrCustomModel(ANTLION_WORKER_MODEL) ); AddSpawnFlags( SF_NPC_LONG_RANGE ); SetBloodColor( BLOOD_COLOR_ANTLION_WORKER ); } else { - SetModel( ANTLION_MODEL ); + SetModel( DefaultOrCustomModel(ANTLION_MODEL) ); SetBloodColor( BLOOD_COLOR_ANTLION ); } #else - SetModel( ANTLION_MODEL ); + SetModel( DefaultOrCustomModel(ANTLION_MODEL) ); SetBloodColor( BLOOD_COLOR_YELLOW ); #endif // HL2_EPISODIC @@ -361,6 +367,45 @@ void CNPC_Antlion::Spawn( void ) BaseClass::Spawn(); m_nSkin = random->RandomInt( 0, ANTLION_SKIN_COUNT-1 ); + +#if defined(MAPBASE) && defined(HL2_EPISODIC) + // Implement dynamic interactions here since we can't recompile the model + if (GetModelPtr()) + { + ScriptedNPCInteraction_t sInteraction01; + sInteraction01.iszInteractionName = AllocPooledString( "antlion_v_soldier_01" ); + sInteraction01.sPhases[SNPCINT_SEQUENCE].iszSequence = AllocPooledString( "antlion_soldier_DI_01" ); + + sInteraction01.vecRelativeOrigin = Vector(224, 0, 0); + sInteraction01.angRelativeAngles = QAngle(0, 180, 0); + sInteraction01.iFlags |= SCNPC_FLAG_TEST_OTHER_ANGLES; + sInteraction01.iFlags |= SCNPC_FLAG_TEST_END_POSITION; + sInteraction01.vecRelativeEndPos = Vector(312, -10, 0); + sInteraction01.iTriggerMethod = SNPCINT_AUTOMATIC_IN_COMBAT; + sInteraction01.flDelay = 15.0f; + sInteraction01.iFlags |= SCNPC_FLAG_MAPBASE_ADDITION; + sInteraction01.flDistSqr = (8 * 8); + sInteraction01.flMaxAngleDiff = 180.0f; // Initiate from any angle + + + ScriptedNPCInteraction_t sInteraction02; + sInteraction02.iszInteractionName = AllocPooledString( "antlion_v_soldier_02" ); + sInteraction02.sPhases[SNPCINT_SEQUENCE].iszSequence = AllocPooledString( "antlion_soldier_DI_02" ); + + sInteraction02.vecRelativeOrigin = Vector(64, 0, 0); + sInteraction02.angRelativeAngles = QAngle(0, 180, 0); + sInteraction02.iFlags |= SCNPC_FLAG_TEST_OTHER_ANGLES; + sInteraction02.iTriggerMethod = SNPCINT_AUTOMATIC_IN_COMBAT; + sInteraction02.flDelay = 7.5f; + sInteraction02.iFlags |= SCNPC_FLAG_MAPBASE_ADDITION; + sInteraction02.flDistSqr = (8 * 8); + sInteraction02.flMaxAngleDiff = 180.0f; // Initiate from any angle + + + AddScriptedNPCInteraction(&sInteraction01); + AddScriptedNPCInteraction(&sInteraction02); + } +#endif } //----------------------------------------------------------------------------- @@ -657,7 +702,11 @@ void CNPC_Antlion::MeleeAttack( float distance, float damage, QAngle &viewPunch, vecForceDir = ( pHurt->WorldSpaceCenter() - WorldSpaceCenter() ); //FIXME: Until the interaction is setup, kill combine soldiers in one hit -- jdw +#ifdef MAPBASE + if ( pHurt->Classify() == CLASS_COMBINE && FClassnameIs( pHurt, "npc_combine_s" ) && GlobalEntity_GetState("antlion_noinstakill") != GLOBAL_ON ) +#else if ( FClassnameIs( pHurt, "npc_combine_s" ) ) +#endif { CTakeDamageInfo dmgInfo( this, this, pHurt->m_iHealth+25, DMG_SLASH ); CalculateMeleeDamageForce( &dmgInfo, vecForceDir, pHurt->GetAbsOrigin() ); @@ -2465,6 +2514,38 @@ int CNPC_Antlion::SelectSchedule( void ) return SCHED_ANTLION_WORKER_RANGE_ATTACK1; } } + +#ifdef MAPBASE + // "Nemesis" is assigned to enemies we hate with 10+ priority. + // Since bugbait targets are given 99 priority, this means the AI here usually only applies + // when the antlion worker is following bugbait. + // + // This is just so antlion workers are more potent when commanded by bugbait, + // which wasn't explored in the official games, but may be used in HL2 mods. + if ( m_hFollowTarget && IsAllied() /*HasCondition( COND_SEE_NEMESIS )*/ ) + { + // Establish LOF if we can't see the enemy + if ( HasCondition( COND_ENEMY_OCCLUDED ) ) + return SCHED_ESTABLISH_LINE_OF_FIRE; + + // See if we need to destroy breakable cover + if ( HasCondition( COND_WEAPON_SIGHT_OCCLUDED ) ) + return SCHED_SHOOT_ENEMY_COVER; + + // Just face as usual if we're not too close to attack, + // otherwise fall back to base class and charge like any other antlion + if ( !HasCondition( COND_TOO_CLOSE_TO_ATTACK ) ) + { + // Run around randomly if our target is looking in our direction + if ( HasCondition( COND_BEHIND_ENEMY ) == false ) + return SCHED_ANTLION_WORKER_RUN_RANDOM; + + return SCHED_COMBAT_FACE; + } + } + else + { +#endif // Back up, we're too near an enemy or can't see them if ( HasCondition( COND_TOO_CLOSE_TO_ATTACK ) || HasCondition( COND_ENEMY_OCCLUDED ) ) @@ -2480,6 +2561,9 @@ int CNPC_Antlion::SelectSchedule( void ) // Face our target and continue to fire return SCHED_COMBAT_FACE; +#ifdef MAPBASE + } +#endif } else { @@ -2544,6 +2628,15 @@ int CNPC_Antlion::SelectSchedule( void ) void CNPC_Antlion::Ignite ( float flFlameLifetime, bool bNPCOnly, float flSize, bool bCalledByLevelDesigner ) { #ifdef HL2_EPISODIC + +#ifdef MAPBASE + if (antlion_no_ignite_die.GetBool()) + { + BaseClass::Ignite(flFlameLifetime, bNPCOnly, flSize, bCalledByLevelDesigner); + return; + } +#endif + float flDamage = m_iHealth + 1; CTakeDamageInfo dmgInfo( this, this, flDamage, DMG_GENERIC ); @@ -2887,7 +2980,11 @@ int CNPC_Antlion::MeleeAttack1Conditions( float flDot, float flDist ) AI_TraceHull( WorldSpaceCenter(), GetEnemy()->WorldSpaceCenter(), -Vector(8,8,8), Vector(8,8,8), MASK_NPCSOLID, this, COLLISION_GROUP_NONE, &tr ); // If the hit entity isn't our target and we don't hate it, don't hit it +#ifdef MAPBASE + if ( tr.m_pEnt != GetEnemy() && tr.fraction < 1.0f && IRelationType( tr.m_pEnt ) > D_FR ) +#else if ( tr.m_pEnt != GetEnemy() && tr.fraction < 1.0f && IRelationType( tr.m_pEnt ) != D_HT ) +#endif return 0; #else @@ -4224,6 +4321,25 @@ void CNPC_Antlion::SetFollowTarget( CBaseEntity *pTarget ) } } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: +// Input : &inputdata - +//----------------------------------------------------------------------------- +void CNPC_Antlion::InputSetFollowTarget( inputdata_t &inputdata ) +{ + if ( IsAlive() == false ) + return; + + CBaseEntity *pEntity = gEntList.FindEntityByName( NULL, inputdata.value.String(), NULL, inputdata.pActivator, inputdata.pCaller ); + + if ( pEntity != NULL ) + { + SetFollowTarget( pEntity ); + } +} +#endif + //----------------------------------------------------------------------------- // Purpose: // Output : Returns true on success, false on failure. @@ -4438,7 +4554,15 @@ bool CNPC_Antlion::IsHeavyDamage( const CTakeDamageInfo &info ) bool CNPC_Antlion::CanRunAScriptedNPCInteraction( bool bForced /*= false*/ ) { // Workers shouldn't do DSS's because they explode +#ifdef MAPBASE + // Now that antlions have their DI restored, one might want workers to use them as well. + // Forced interactions are allowed now, but I went a step further and enabling dynamic interactions + // will disregard this check. This will allow vortigaunts to use dangerous melee interactions with workers, + // but if you're concerned about that just turn the antlion's interactions off. + if ( IsWorker() && !bForced && m_iDynamicInteractionsAllowed != TRS_TRUE ) +#else if ( IsWorker() ) +#endif return false; return BaseClass::CanRunAScriptedNPCInteraction( bForced ); diff --git a/src/game/server/hl2/npc_antlion.h b/src/game/server/hl2/npc_antlion.h index 1de36066..dd26ec2a 100644 --- a/src/game/server/hl2/npc_antlion.h +++ b/src/game/server/hl2/npc_antlion.h @@ -143,6 +143,9 @@ public: void InputJumpAtTarget( inputdata_t &inputdata ); void SetFollowTarget( CBaseEntity *pTarget ); +#ifdef MAPBASE + void InputSetFollowTarget( inputdata_t &inputdata ); +#endif int TranslateSchedule( int scheduleType ); virtual Activity NPC_TranslateActivity( Activity baseAct ); diff --git a/src/game/server/hl2/npc_antliongrub.cpp b/src/game/server/hl2/npc_antliongrub.cpp index 16ee2ed5..07444e9a 100644 --- a/src/game/server/hl2/npc_antliongrub.cpp +++ b/src/game/server/hl2/npc_antliongrub.cpp @@ -15,6 +15,9 @@ #include "items.h" #include "item_dynamic_resupply.h" #include "npc_vortigaunt_episodic.h" +#ifdef MAPBASE +#include "filters.h" +#endif // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" @@ -659,6 +662,16 @@ void CAntlionGrub::GrubTouch( CBaseEntity *pOther ) IPhysicsObject *pPhysOther = pOther->VPhysicsGetObject(); // bool bThrown = ( pTarget->VPhysicsGetObject()->GetGameFlags() & FVPHYSICS_WAS_THROWN ) != 0; if ( pOther->IsPlayer() || FClassnameIs(pOther,"npc_vortigaunt") || ( pPhysOther && (pPhysOther->GetGameFlags() & FVPHYSICS_WAS_THROWN )) ) { +#ifdef MAPBASE + if (m_hDamageFilter) + { + // Don't squash if they don't pass our damage filter + CBaseFilter *pFilter = static_cast(m_hDamageFilter.Get()); + if (pFilter && !pFilter->PassesFilter(this, pOther)) + return; + } +#endif + m_OnAgitated.FireOutput( pOther, pOther ); Squash( pOther, true, true ); } @@ -844,6 +857,13 @@ void CGrubNugget::Spawn( void ) { Precache(); +#ifdef MAPBASE + if ( GetModelName() != NULL_STRING ) + { + SetModel( STRING(GetModelName()) ); + } + else +#endif if ( m_nDenomination == NUGGET_LARGE ) { SetModel( "models/grub_nugget_large.mdl" ); @@ -875,6 +895,10 @@ void CGrubNugget::Precache( void ) PrecacheModel("models/grub_nugget_small.mdl"); PrecacheModel("models/grub_nugget_medium.mdl"); PrecacheModel("models/grub_nugget_large.mdl"); +#ifdef MAPBASE + if (GetModelName() != NULL_STRING) + PrecacheModel( STRING(GetModelName()) ); +#endif PrecacheScriptSound( "GrubNugget.Touch" ); PrecacheScriptSound( "NPC_Antlion_Grub.Explode" ); diff --git a/src/game/server/hl2/npc_antlionguard.cpp b/src/game/server/hl2/npc_antlionguard.cpp index faf0099f..5f1481ea 100644 --- a/src/game/server/hl2/npc_antlionguard.cpp +++ b/src/game/server/hl2/npc_antlionguard.cpp @@ -190,6 +190,17 @@ Activity ACT_ANTLIONGUARD_CHARGE_STOP; Activity ACT_ANTLIONGUARD_CHARGE_HIT; Activity ACT_ANTLIONGUARD_CHARGE_ANTICIPATION; +#ifdef MAPBASE +// Unused activities +Activity ACT_ANTLIONGUARD_COVER_ENTER; +Activity ACT_ANTLIONGUARD_COVER_LOOP; +Activity ACT_ANTLIONGUARD_COVER_EXIT; +Activity ACT_ANTLIONGUARD_COVER_ADVANCE; +Activity ACT_ANTLIONGUARD_COVER_FLINCH; +Activity ACT_ANTLIONGUARD_SNEAK; +Activity ACT_ANTLIONGUARD_RUN_FULL; +#endif + // Anim events int AE_ANTLIONGUARD_CHARGE_HIT; int AE_ANTLIONGUARD_SHOVE_PHYSOBJECT; @@ -667,7 +678,7 @@ void CNPC_AntlionGuard::UpdateOnRemove( void ) //----------------------------------------------------------------------------- void CNPC_AntlionGuard::Precache( void ) { - PrecacheModel( ANTLIONGUARD_MODEL ); + PrecacheModel( DefaultOrCustomModel( ANTLIONGUARD_MODEL ) ); PrecacheScriptSound( "NPC_AntlionGuard.Shove" ); PrecacheScriptSound( "NPC_AntlionGuard.HitHard" ); @@ -768,7 +779,7 @@ void CNPC_AntlionGuard::Spawn( void ) { Precache(); - SetModel( ANTLIONGUARD_MODEL ); + SetModel( DefaultOrCustomModel( ANTLIONGUARD_MODEL ) ); // Switch our skin (for now), if we're the cavern guard if ( m_bCavernBreed ) @@ -1573,7 +1584,11 @@ public: if ( pVictimBCC ) { // Can only damage other NPCs that we hate +#ifdef MAPBASE + if ( m_pAttacker->IRelationType( pEntity ) <= D_FR ) +#else if ( m_pAttacker->IRelationType( pEntity ) == D_HT ) +#endif { pEntity->TakeDamage( info ); return true; @@ -2612,8 +2627,15 @@ public: if ( !pEntity->IsNPC() && pEntity->GetMoveType() == MOVETYPE_VPHYSICS ) { IPhysicsObject *pPhysics = pEntity->VPhysicsGetObject(); +#ifdef MAPBASE + // A MOVETYPE_VPHYSICS object without a VPhysics object is an odd edge case, but it's evidently possible + // since my game crashed after an antlion guard tried to see me through an EP2 jalopy. + // Perhaps that's a sign of an underlying issue? + if ( pPhysics && pPhysics->IsMoveable() && pPhysics->GetMass() < m_minMass ) +#else Assert(pPhysics); if ( pPhysics->IsMoveable() && pPhysics->GetMass() < m_minMass ) +#endif return false; } @@ -2729,7 +2751,11 @@ bool CNPC_AntlionGuard::HandleChargeImpact( Vector vecImpact, CBaseEntity *pEnti } // Hit anything we don't like +#ifdef MAPBASE + if ( IRelationType( pEntity ) <= D_FR && ( GetNextAttack() < gpGlobals->curtime ) ) +#else if ( IRelationType( pEntity ) == D_HT && ( GetNextAttack() < gpGlobals->curtime ) ) +#endif { EmitSound( "NPC_AntlionGuard.Shove" ); @@ -3227,6 +3253,9 @@ void CNPC_AntlionGuard::SummonAntlions( void ) // Make the antlion fire my input when he dies pAntlion->KeyValue( "OnDeath", UTIL_VarArgs("%s,SummonedAntlionDied,,0,-1", STRING(GetEntityName())) ); +#ifdef MAPBASE + pAntlion->KeyValue( "OnKilled", UTIL_VarArgs("%s,SummonedAntlionDied,,0,-1", STRING(GetEntityName())) ); +#endif // Start the antlion burrowed, and tell him to come up pAntlion->m_bStartBurrowed = true; @@ -3366,6 +3395,11 @@ void CNPC_AntlionGuard::InputClearChargeTarget( inputdata_t &inputdata ) //----------------------------------------------------------------------------- Activity CNPC_AntlionGuard::NPC_TranslateActivity( Activity baseAct ) { +#ifdef MAPBASE + // Needed for VScript NPC_TranslateActiviy hook + baseAct = BaseClass::NPC_TranslateActivity( baseAct ); +#endif + //See which run to use if ( ( baseAct == ACT_RUN ) && IsCurSchedule( SCHED_ANTLIONGUARD_CHARGE ) ) return (Activity) ACT_ANTLIONGUARD_CHARGE_RUN; @@ -4676,6 +4710,15 @@ AI_BEGIN_CUSTOM_NPC( npc_antlionguard, CNPC_AntlionGuard ) DECLARE_ACTIVITY( ACT_ANTLIONGUARD_PHYSHIT_FL ) DECLARE_ACTIVITY( ACT_ANTLIONGUARD_PHYSHIT_RR ) DECLARE_ACTIVITY( ACT_ANTLIONGUARD_PHYSHIT_RL ) +#ifdef MAPBASE + DECLARE_ACTIVITY( ACT_ANTLIONGUARD_COVER_ENTER ) + DECLARE_ACTIVITY( ACT_ANTLIONGUARD_COVER_LOOP ) + DECLARE_ACTIVITY( ACT_ANTLIONGUARD_COVER_EXIT ) + DECLARE_ACTIVITY( ACT_ANTLIONGUARD_COVER_ADVANCE ) + DECLARE_ACTIVITY( ACT_ANTLIONGUARD_COVER_FLINCH ) + DECLARE_ACTIVITY( ACT_ANTLIONGUARD_SNEAK ) + DECLARE_ACTIVITY( ACT_ANTLIONGUARD_RUN_FULL ) +#endif //Adrian: events go here DECLARE_ANIMEVENT( AE_ANTLIONGUARD_CHARGE_HIT ) diff --git a/src/game/server/hl2/npc_attackchopper.cpp b/src/game/server/hl2/npc_attackchopper.cpp index f5a0477f..d4c42849 100644 --- a/src/game/server/hl2/npc_attackchopper.cpp +++ b/src/game/server/hl2/npc_attackchopper.cpp @@ -42,6 +42,10 @@ #include "physics_bone_follower.h" #endif // HL2_EPISODIC +#ifdef MAPBASE +#include "filters.h" +#endif + // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" @@ -115,6 +119,9 @@ static const char *s_pChunkModelName[CHOPPER_MAX_CHUNKS] = #define SF_HELICOPTER_IGNORE_AVOID_FORCES 0x00080000 #define SF_HELICOPTER_AGGRESSIVE 0x00100000 #define SF_HELICOPTER_LONG_SHADOW 0x00200000 +#ifdef MAPBASE +#define SF_HELICOPTER_AIM_WITH_GUN_OFF 0x00400000 +#endif #define CHOPPER_SLOW_BOMB_SPEED 250 @@ -347,6 +354,10 @@ protected: void CollisionCallback( CHelicopterChunk *pCaller ); +#ifdef MAPBASE + void InputFallApart( inputdata_t &inputdata ); +#endif + void FallThink( void ); bool m_bLanded; @@ -760,8 +771,13 @@ private: CSoundPatch *m_pGunFiringSound; // Outputs +#ifndef MAPBASE COutputInt m_OnHealthChanged; +#endif COutputEvent m_OnShotDown; +#ifdef MAPBASE + COutputEHANDLE m_OutBomb; +#endif // Crashing EHANDLE m_hCrashPoint; @@ -842,6 +858,10 @@ BEGIN_DATADESC( CNPC_AttackHelicopter ) DEFINE_KEYFIELD( m_flMaxSpeed, FIELD_FLOAT, "PatrolSpeed" ), DEFINE_KEYFIELD( m_bNonCombat, FIELD_BOOLEAN, "NonCombat" ), +#ifdef MAPBASE + DEFINE_KEYFIELD( m_flFieldOfView, FIELD_FLOAT, "FieldOfView" ), +#endif + DEFINE_FIELD( m_hCrashPoint, FIELD_EHANDLE ), DEFINE_INPUTFUNC( FIELD_VOID, "ResetIdleTime", InputResetIdleTime ), @@ -866,7 +886,9 @@ BEGIN_DATADESC( CNPC_AttackHelicopter ) DEFINE_INPUTFUNC( FIELD_VOID, "StartContinuousShooting", InputStartContinuousShooting ), DEFINE_INPUTFUNC( FIELD_VOID, "StartFastShooting", InputStartFastShooting ), DEFINE_INPUTFUNC( FIELD_VOID, "GunOff", InputGunOff ), +#ifndef MAPBASE // This has been added to all NPCs. npc_helicopter overrides it with its original function, but the datadesc entry isn't needed anymore. DEFINE_INPUTFUNC( FIELD_FLOAT, "SetHealthFraction", InputSetHealthFraction ), +#endif DEFINE_INPUTFUNC( FIELD_VOID, "StartBombExplodeOnContact", InputStartBombExplodeOnContact ), DEFINE_INPUTFUNC( FIELD_VOID, "StopBombExplodeOnContact", InputStopBombExplodeOnContact ), @@ -878,8 +900,13 @@ BEGIN_DATADESC( CNPC_AttackHelicopter ) DEFINE_THINKFUNC( BlinkLightsThink ), DEFINE_THINKFUNC( SpotlightThink ), +#ifndef MAPBASE DEFINE_OUTPUT( m_OnHealthChanged, "OnHealthChanged" ), +#endif DEFINE_OUTPUT( m_OnShotDown, "OnShotDown" ), +#ifdef MAPBASE + DEFINE_OUTPUT( m_OutBomb, "OutBomb" ), +#endif END_DATADESC() @@ -893,6 +920,9 @@ CNPC_AttackHelicopter::CNPC_AttackHelicopter() : m_bBombsExplodeOnContact( false ) { m_flMaxSpeed = 0; +#ifdef MAPBASE + m_flFieldOfView = -1.0; // 360 degrees +#endif } CNPC_AttackHelicopter::~CNPC_AttackHelicopter(void) @@ -937,6 +967,13 @@ void CNPC_AttackHelicopter::Precache( void ) { BaseClass::Precache(); +#ifdef MAPBASE + if ( GetModelName() != NULL_STRING ) + { + PrecacheModel( STRING(GetModelName()) ); + } + else +#endif if ( !HasSpawnFlags(SF_HELICOPTER_ELECTRICAL_DRONE) ) { PrecacheModel( CHOPPER_MODEL_NAME ); @@ -1038,6 +1075,13 @@ void CNPC_AttackHelicopter::Spawn( void ) m_bBombingSuppressed = false; m_bIgnorePathVisibilityTests = false; +#ifdef MAPBASE + if ( GetModelName() != NULL_STRING ) + { + SetModel( STRING(GetModelName()) ); + } + else +#endif if ( !HasSpawnFlags(SF_HELICOPTER_ELECTRICAL_DRONE) ) { SetModel( CHOPPER_MODEL_NAME ); @@ -1070,6 +1114,11 @@ void CNPC_AttackHelicopter::Spawn( void ) SetPauseState( PAUSE_NO_PAUSE ); +#ifdef MAPBASE + if (m_iHealth != 0) + m_iMaxHealth = m_iHealth; + else +#endif m_iMaxHealth = m_iHealth = sk_helicopter_health.GetInt(); m_flMaxSpeed = flLoadedSpeed; @@ -1081,7 +1130,9 @@ void CNPC_AttackHelicopter::Spawn( void ) m_nGrenadeCount = CHOPPER_BOMB_DROP_COUNT; +#ifndef MAPBASE // Moved to constructor because this is a keyvalue now m_flFieldOfView = -1.0; // 360 degrees +#endif m_flIdleTimeDelay = 0.0f; m_iAmmoType = GetAmmoDef()->Index("HelicopterGun"); @@ -2790,6 +2841,10 @@ CGrenadeHelicopter *CNPC_AttackHelicopter::SpawnBombEntity( const Vector &vecPos } #endif // HL2_EPISODIC +#ifdef MAPBASE + m_OutBomb.Set(pGrenade, pGrenade, this); +#endif + return pGrenade; } @@ -3475,9 +3530,16 @@ void CNPC_AttackHelicopter::TraceAttack( const CTakeDamageInfo &info, const Vect // Take no damage from trace attacks unless it's blast damage. RadiusDamage() sometimes calls // TraceAttack() as a means for delivering blast damage. Usually when the explosive penetrates // the target. (RPG missiles do this sometimes). +#ifdef MAPBASE + if ( ( info.GetDamageType() & DMG_AIRBOAT ) || + ( info.GetInflictor()->Classify() == CLASS_MISSILE ) || + ( info.GetAttacker()->Classify() == CLASS_MISSILE ) || + m_bAllowAnyDamage ) +#else if ( ( info.GetDamageType() & DMG_AIRBOAT ) || ( info.GetInflictor()->Classify() == CLASS_MISSILE ) || ( info.GetAttacker()->Classify() == CLASS_MISSILE ) ) +#endif { BaseClass::BaseClass::TraceAttack( info, vecDir, ptr, pAccumulator ); } @@ -3490,7 +3552,11 @@ void CNPC_AttackHelicopter::TraceAttack( const CTakeDamageInfo &info, const Vect int CNPC_AttackHelicopter::OnTakeDamage( const CTakeDamageInfo &info ) { // We don't take blast damage from anything but the airboat or missiles (or myself!) +#ifdef MAPBASE + if( info.GetInflictor() != this && !m_bAllowAnyDamage ) +#else if( info.GetInflictor() != this ) +#endif { if ( ( ( info.GetDamageType() & DMG_AIRBOAT ) == 0 ) && ( info.GetInflictor()->Classify() != CLASS_MISSILE ) && @@ -3605,12 +3671,14 @@ int CNPC_AttackHelicopter::OnTakeDamage_Alive( const CTakeDamageInfo &info ) ExplodeAndThrowChunk( info.GetDamagePosition() ); } +#ifndef MAPBASE // We need to make sure the base OnHealthChanged works with helicopters int nPrevPercent = (int)(100.0f * nPrevHealth / GetMaxHealth()); int nCurrPercent = (int)(100.0f * GetHealth() / GetMaxHealth()); if (( (nPrevPercent + 9) / 10 ) != ( (nCurrPercent + 9) / 10 )) { m_OnHealthChanged.Set( nCurrPercent, this, this ); } +#endif } return nRetVal; @@ -4797,6 +4865,26 @@ void CNPC_AttackHelicopter::Hunt( void ) { BullrushBombs(); } +#ifdef MAPBASE + // Some may want the hunter-chopper to aim at different positions searching for its target + // without actually firing at anything. Gun aiming is only handled in FireGun(), which is + // disabled when the gun is disabled. point_posecontroller doesn't seem to work well for this either, + // so a new spawnflag is handled here to allow the chopper to aim at its enemy even when the gun is off. + else if ( HasSpawnFlags( SF_HELICOPTER_AIM_WITH_GUN_OFF ) && GetEnemy() ) + { + // Get gun attachment points + Vector vBasePos; + GetAttachment( m_nGunBaseAttachment, vBasePos ); + + Vector vecFireAtPosition; + ComputeFireAtPosition( &vecFireAtPosition ); + + Vector vTargetDir = vecFireAtPosition - vBasePos; + VectorNormalize( vTargetDir ); + + PoseGunTowardTargetDirection( vTargetDir ); + } +#endif } #ifdef HL2_EPISODIC @@ -5598,6 +5686,9 @@ LINK_ENTITY_TO_CLASS( npc_heli_avoidsphere, CAvoidSphere ); BEGIN_DATADESC( CAvoidSphere ) DEFINE_KEYFIELD( m_flRadius, FIELD_FLOAT, "radius" ), +#ifdef MAPBASE + DEFINE_KEYFIELD( m_iszAvoidFilter, FIELD_STRING, "AvoidFilter" ), +#endif END_DATADESC() @@ -5638,6 +5729,18 @@ void CAvoidSphere::Activate( ) { BaseClass::Activate(); s_AvoidSpheres.AddToTail( this ); + +#ifdef MAPBASE + m_hAvoidFilter = gEntList.FindEntityByName( NULL, m_iszAvoidFilter, this ); + if (m_hAvoidFilter) + { + if (dynamic_cast(m_hAvoidFilter.Get()) == NULL) + { + Warning( "%s: \"%s\" is not a valid filter", GetDebugName(), m_hAvoidFilter->GetDebugName() ); + m_hAvoidFilter = NULL; + } + } +#endif } void CAvoidSphere::UpdateOnRemove( ) @@ -5664,6 +5767,12 @@ void CAvoidSphere::ComputeAvoidanceForces( CBaseEntity *pEntity, float flEntityR CAvoidSphere *pSphere = s_AvoidSpheres[i].Get(); const Vector &vecAvoidCenter = pSphere->WorldSpaceCenter(); +#ifdef MAPBASE + // Continue if not passing the avoid sphere filter + if ( pSphere->m_hAvoidFilter && !(static_cast( pSphere->m_hAvoidFilter.Get())->PassesFilter(pSphere, pEntity )) ) + continue; +#endif + // NOTE: This test can be thought of sweeping a sphere through space // and seeing if it intersects the avoidance sphere float flTotalRadius = flEntityRadius + pSphere->m_flRadius; @@ -5941,6 +6050,10 @@ BEGIN_DATADESC( CHelicopterChunk ) DEFINE_PHYSPTR( m_pTailConstraint ), DEFINE_PHYSPTR( m_pCockpitConstraint ), +#ifdef MAPBASE + DEFINE_INPUTFUNC( FIELD_VOID, "FallApart", InputFallApart ), +#endif + END_DATADESC() //----------------------------------------------------------------------------- @@ -6042,6 +6155,17 @@ void CHelicopterChunk::CollisionCallback( CHelicopterChunk *pCaller ) } } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pCaller - +//----------------------------------------------------------------------------- +void CHelicopterChunk::InputFallApart( inputdata_t &inputdata ) +{ + CollisionCallback(this); +} +#endif + //----------------------------------------------------------------------------- // Purpose: // Input : &vecPos - diff --git a/src/game/server/hl2/npc_barnacle.cpp b/src/game/server/hl2/npc_barnacle.cpp index 1c195b90..d26f702a 100644 --- a/src/game/server/hl2/npc_barnacle.cpp +++ b/src/game/server/hl2/npc_barnacle.cpp @@ -40,6 +40,10 @@ ConVar sk_barnacle_health( "sk_barnacle_health","0"); static ConVar npc_barnacle_swallow( "npc_barnacle_swallow", "0", 0, "Use prototype swallow code." ); +#ifdef MAPBASE +ConVar npc_barnacle_ignite( "npc_barnacle_ignite", "0", FCVAR_NONE, "Allows barnacles to be ignited by flares and beyond." ); +#endif + const char *CNPC_Barnacle::m_szGibNames[NUM_BARNACLE_GIBS] = { "models/gibs/hgibs.mdl", @@ -69,6 +73,9 @@ int g_interactionBarnacleVictimDangle = 0; int g_interactionBarnacleVictimReleased = 0; int g_interactionBarnacleVictimGrab = 0; int g_interactionBarnacleVictimBite = 0; +#ifdef MAPBASE +int g_interactionBarnacleVictimFinalBite = 0; +#endif LINK_ENTITY_TO_CLASS( npc_barnacle, CNPC_Barnacle ); @@ -177,7 +184,7 @@ BEGIN_DATADESC( CNPC_Barnacle ) DEFINE_INPUTFUNC( FIELD_VOID, "DropTongue", InputDropTongue ), DEFINE_INPUTFUNC( FIELD_INTEGER, "SetDropTongueSpeed", InputSetDropTongueSpeed ), -#ifdef HL2_EPISODIC +#if HL2_EPISODIC || MAPBASE DEFINE_INPUTFUNC( FIELD_VOID, "LetGo", InputLetGo ), DEFINE_OUTPUT( m_OnGrab, "OnGrab" ), DEFINE_OUTPUT( m_OnRelease, "OnRelease" ), @@ -187,7 +194,9 @@ BEGIN_DATADESC( CNPC_Barnacle ) DEFINE_THINKFUNC( BarnacleThink ), DEFINE_THINKFUNC( WaitTillDead ), +#ifndef MAPBASE DEFINE_FIELD( m_bSwallowingBomb, FIELD_BOOLEAN ), +#endif END_DATADESC() @@ -257,7 +266,7 @@ void CNPC_Barnacle::Spawn() { Precache( ); - SetModel( "models/barnacle.mdl" ); + SetModel( DefaultOrCustomModel( "models/barnacle.mdl" ) ); UTIL_SetSize( this, Vector(-16, -16, -40), Vector(16, 16, 0) ); SetSolid( SOLID_BBOX ); @@ -275,7 +284,9 @@ void CNPC_Barnacle::Spawn() m_cGibs = 0; m_bLiftingPrey = false; m_bSwallowingPrey = false; +#ifndef MAPBASE m_bSwallowingBomb = false; +#endif m_flDigestFinish = 0; m_takedamage = DAMAGE_YES; m_pConstraint = NULL; @@ -406,6 +417,16 @@ void CNPC_Barnacle::PlayerHasIlluminatedNPC( CBasePlayer *pPlayer, float flDot ) } } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CNPC_Barnacle::AllowedToIgnite( void ) +{ + return npc_barnacle_ignite.GetBool(); +} +#endif + //----------------------------------------------------------------------------- // Purpose: Initialize tongue position when first spawned // Input : @@ -509,7 +530,11 @@ void CNPC_Barnacle::BarnacleThink ( void ) } else if ( GetEnemy() ) { +#ifdef MAPBASE + if ( m_bLiftingPrey ) +#else if ( m_bLiftingPrey || m_bSwallowingBomb == true ) +#endif { LiftPrey(); } @@ -664,6 +689,12 @@ bool CNPC_Barnacle::CanPickup( CBaseCombatCharacter *pBCC ) if( FClassnameIs( pBCC, "npc_turret_floor" ) ) return false; +#ifdef MAPBASE + // Don't pickup rollermines + if( FClassnameIs( pBCC, "npc_rollermine" ) ) + return false; +#endif + // Don't pick up a dead player or NPC if( !pBCC->IsAlive() ) return false; @@ -1145,6 +1176,24 @@ void CNPC_Barnacle::LiftPhysicsObject( float flBiteZOffset ) // If we got a physics prop, wait until the thing has settled down m_bLiftingPrey = false; +#ifdef MAPBASE + Vector tipPos = m_vecTip.Get(); + Activity curAct = GetActivity(); + + // Other, non-character entities use this now + if (pVictim->DispatchInteraction( g_interactionBarnacleVictimBite, &tipPos, this )) + { + // Make sure the interaction isn't making us use an irregular activity + // (e.g. biting) + if (GetActivity() == curAct) + SetActivity( (Activity)ACT_BARNACLE_TASTE_SPIT ); + } + else + { + // Start the spit animation. + SetActivity( (Activity)ACT_BARNACLE_TASTE_SPIT ); + } +#else if ( hl2_episodic.GetBool() ) { CBounceBomb *pBounce = dynamic_cast( pVictim ); @@ -1181,6 +1230,7 @@ void CNPC_Barnacle::LiftPhysicsObject( float flBiteZOffset ) pBCC->DispatchInteraction( g_interactionBarnacleVictimBite, &tipPos, this ); } +#endif #endif } else @@ -1355,7 +1405,7 @@ void CNPC_Barnacle::InputDropTongue( inputdata_t &inputdata ) void CNPC_Barnacle::AttachTongueToTarget( CBaseEntity *pTouchEnt, Vector vecGrabPos ) { -#if HL2_EPISODIC +#if HL2_EPISODIC || MAPBASE m_OnGrab.Set( pTouchEnt, this, this ); #endif @@ -1577,6 +1627,18 @@ void CNPC_Barnacle::BitePrey( void ) CBaseCombatCharacter *pVictim = GetEnemyCombatCharacterPointer(); +#ifdef MAPBASE + if ( pVictim == NULL ) + { + if ( GetEnemy() ) + { + Vector tipPos = m_vecTip.Get(); + GetEnemy()->DispatchInteraction( g_interactionBarnacleVictimFinalBite, &tipPos, this ); + } + + return; + } +#else #ifdef HL2_EPISODIC if ( pVictim == NULL ) { @@ -1614,6 +1676,7 @@ void CNPC_Barnacle::BitePrey( void ) { return; } +#endif EmitSound( "NPC_Barnacle.FinalBite" ); @@ -1701,6 +1764,12 @@ void CNPC_Barnacle::BitePrey( void ) #endif +#ifdef MAPBASE + Vector tipPos = m_vecTip.Get(); + if (pVictim->DispatchInteraction( g_interactionBarnacleVictimFinalBite, &tipPos, this )) + return; +#endif + // Players are never swallowed, nor is anything we don't have a ragdoll for if ( !m_hRagdoll || pVictim->IsPlayer() ) { @@ -1873,7 +1942,7 @@ void CNPC_Barnacle::RemoveRagdoll( bool bDestroyRagdoll ) void CNPC_Barnacle::LostPrey( bool bRemoveRagdoll ) { -#if HL2_EPISODIC +#if HL2_EPISODIC || MAPBASE m_OnRelease.Set( GetEnemy(), this, this ); #endif @@ -1885,13 +1954,20 @@ void CNPC_Barnacle::LostPrey( bool bRemoveRagdoll ) PhysEnableEntityCollisions( this, pEnemy ); #endif +#ifdef MAPBASE + // These can be CBaseEntity-based now + pEnemy->DispatchInteraction( g_interactionBarnacleVictimReleased, NULL, this ); +#endif + //No one survives being snatched by a barnacle anymore, so leave // this flag set so that their entity gets removed. //GetEnemy()->RemoveEFlags( EFL_IS_BEING_LIFTED_BY_BARNACLE ); CBaseCombatCharacter *pVictim = GetEnemyCombatCharacterPointer(); if ( pVictim ) { +#ifndef MAPBASE pVictim->DispatchInteraction( g_interactionBarnacleVictimReleased, NULL, this ); +#endif pVictim->RemoveEFlags( EFL_IS_BEING_LIFTED_BY_BARNACLE ); if ( m_hRagdoll ) @@ -2213,6 +2289,11 @@ bool CNPC_Barnacle::IsPoisonous( CBaseEntity *pVictim ) if ( FClassnameIs(pVictim,"npc_headcrab_black") ) return true; +#ifdef MAPBASE + if (FClassnameIs( pVictim, "npc_poisonzombie" )) + return true; +#endif + if ( FClassnameIs(pVictim,"npc_antlion") && static_cast(pVictim)->IsWorker() ) @@ -2220,10 +2301,12 @@ bool CNPC_Barnacle::IsPoisonous( CBaseEntity *pVictim ) return false; } +#endif +#if HL2_EPISODIC || MAPBASE //========================================================= // script input to immediately abandon whatever I am lifting //========================================================= @@ -2240,8 +2323,10 @@ void CNPC_Barnacle::InputLetGo( inputdata_t &inputdata ) LostPrey( false ); } } +#endif +#if HL2_EPISODIC // Barnacle has custom impact damage tables, so it can take grave damage from sawblades. static impactentry_t barnacleLinearTable[] = { @@ -2296,7 +2381,7 @@ const impactdamagetable_t &CNPC_Barnacle::GetPhysicsImpactDamageTable( void ) //========================================================= void CNPC_Barnacle::Precache() { - PrecacheModel("models/barnacle.mdl"); + PrecacheModel( DefaultOrCustomModel( "models/barnacle.mdl" ) ); // Precache all gibs for ( int i=0; i < ARRAYSIZE(m_szGibNames); i++ ) @@ -2712,6 +2797,9 @@ AI_BEGIN_CUSTOM_NPC( npc_barnacle, CNPC_Barnacle ) DECLARE_INTERACTION( g_interactionBarnacleVictimReleased ) DECLARE_INTERACTION( g_interactionBarnacleVictimGrab ) DECLARE_INTERACTION( g_interactionBarnacleVictimBite ) +#ifdef MAPBASE + DECLARE_INTERACTION( g_interactionBarnacleVictimFinalBite ) +#endif // Conditions diff --git a/src/game/server/hl2/npc_barnacle.h b/src/game/server/hl2/npc_barnacle.h index bcb191cf..373be24b 100644 --- a/src/game/server/hl2/npc_barnacle.h +++ b/src/game/server/hl2/npc_barnacle.h @@ -84,6 +84,10 @@ public: int OnTakeDamage_Alive( const CTakeDamageInfo &info ); void PlayerHasIlluminatedNPC( CBasePlayer *pPlayer, float flDot ); +#ifdef MAPBASE + bool AllowedToIgnite( void ); +#endif + // The tongue's vphysics updated void OnTongueTipUpdated(); @@ -145,6 +149,19 @@ private: +#ifdef MAPBASE + +#if HL2_EPISODIC + /// Decides whether something should poison the barnacle upon eating + static bool IsPoisonous( CBaseEntity *pVictim ); + const impactdamagetable_t &GetPhysicsImpactDamageTable( void ); +#endif + + // Regular HL2 DLL has these now + void InputLetGo( inputdata_t &inputdata ); + COutputEHANDLE m_OnGrab, m_OnRelease; + +#else #if HL2_EPISODIC /// Decides whether something should poison the barnacle upon eating static bool IsPoisonous( CBaseEntity *pVictim ); @@ -153,6 +170,7 @@ private: COutputEHANDLE m_OnGrab, m_OnRelease; const impactdamagetable_t &GetPhysicsImpactDamageTable( void ); +#endif #endif CNetworkVar( float, m_flAltitude ); @@ -195,7 +213,9 @@ private: Vector m_vLastEnemyPos; float m_flLastPull; CSimpleSimTimer m_StuckTimer; +#ifndef MAPBASE // Handled by interactions now bool m_bSwallowingBomb; +#endif #ifdef HL2_EPISODIC bool m_bSwallowingPoison; #endif diff --git a/src/game/server/hl2/npc_barney.cpp b/src/game/server/hl2/npc_barney.cpp index 337d0eb6..cfb80afc 100644 --- a/src/game/server/hl2/npc_barney.cpp +++ b/src/game/server/hl2/npc_barney.cpp @@ -51,8 +51,10 @@ public: virtual void Precache() { +#ifndef MAPBASE // This is now done in CNPC_PlayerCompanion::Precache() // Prevents a warning SelectModel( ); +#endif BaseClass::Precache(); PrecacheScriptSound( "NPC_Barney.FootstepLeft" ); @@ -81,6 +83,11 @@ public: void GatherConditions(); void UseFunc( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); +#ifdef MAPBASE + // Use Barney's default subtitle color (215,255,255) + bool GetGameTextSpeechParams( hudtextparms_t ¶ms ) { params.r1 = 215; params.g1 = 255; params.b1 = 255; return BaseClass::GetGameTextSpeechParams( params ); } +#endif + CAI_FuncTankBehavior m_FuncTankBehavior; COutputEvent m_OnPlayerUse; @@ -111,6 +118,9 @@ END_DATADESC() //----------------------------------------------------------------------------- void CNPC_Barney::SelectModel() { +#ifdef MAPBASE + if (GetModelName() == NULL_STRING) +#endif SetModelName( AllocPooledString( BARNEY_MODEL ) ); } @@ -121,7 +131,11 @@ void CNPC_Barney::Spawn( void ) { Precache(); +#ifdef MAPBASE + m_iHealth = sk_barney_health.GetInt(); +#else m_iHealth = 80; +#endif m_iszIdleExpression = MAKE_STRING("scenes/Expressions/BarneyIdle.vcd"); m_iszAlertExpression = MAKE_STRING("scenes/Expressions/BarneyAlert.vcd"); diff --git a/src/game/server/hl2/npc_basescanner.cpp b/src/game/server/hl2/npc_basescanner.cpp index b05441db..fdae50da 100644 --- a/src/game/server/hl2/npc_basescanner.cpp +++ b/src/game/server/hl2/npc_basescanner.cpp @@ -41,6 +41,10 @@ BEGIN_DATADESC( CNPC_BaseScanner ) DEFINE_FIELD( m_flAttackFarDist, FIELD_FLOAT ), DEFINE_FIELD( m_flAttackRange, FIELD_FLOAT ), +#ifdef MAPBASE + DEFINE_KEYFIELD( m_flCustomMaxSpeed, FIELD_FLOAT, "CustomFlightSpeed" ), +#endif + DEFINE_FIELD( m_nPoseTail, FIELD_INTEGER ), DEFINE_FIELD( m_nPoseDynamo, FIELD_INTEGER ), DEFINE_FIELD( m_nPoseFlare, FIELD_INTEGER ), @@ -52,7 +56,11 @@ BEGIN_DATADESC( CNPC_BaseScanner ) DEFINE_FIELD( m_pSmokeTrail, FIELD_CLASSPTR ), DEFINE_INPUTFUNC( FIELD_FLOAT, "SetDistanceOverride", InputSetDistanceOverride ), +#ifdef MAPBASE + DEFINE_INPUTFUNC( FIELD_FLOAT, "SetFlightSpeed", InputSetFlightSpeed ), +#else DEFINE_INPUTFUNC( FIELD_INTEGER, "SetFlightSpeed", InputSetFlightSpeed ), +#endif DEFINE_THINKFUNC( DiveBombSoundThink ), END_DATADESC() @@ -861,12 +869,16 @@ void CNPC_BaseScanner::SpeakSentence( int sentenceType ) //----------------------------------------------------------------------------- void CNPC_BaseScanner::InputSetFlightSpeed(inputdata_t &inputdata) { +#ifdef MAPBASE + m_flCustomMaxSpeed = inputdata.value.Float(); +#else //FIXME: Currently unsupported /* m_flFlightSpeed = inputdata.value.Int(); m_bFlightSpeedOverridden = (m_flFlightSpeed > 0); */ +#endif } //----------------------------------------------------------------------------- @@ -1266,6 +1278,14 @@ void CNPC_BaseScanner::MoveToTarget( float flInterval, const Vector &vecMoveTarg myZAccel = flDist / flInterval; } +#ifdef MAPBASE + if (m_flSpeedModifier != 1.0f) + { + myAccel *= m_flSpeedModifier; + //myZAccel *= m_flSpeedModifier; + } +#endif + MoveInDirection( flInterval, targetDir, myAccel, myZAccel, myDecay ); // calc relative banking targets @@ -1655,6 +1675,11 @@ void CNPC_BaseScanner::PainSound( const CTakeDamageInfo &info ) //----------------------------------------------------------------------------- float CNPC_BaseScanner::GetMaxSpeed() { +#ifdef MAPBASE + if (m_flCustomMaxSpeed > 0.0f) + return m_flCustomMaxSpeed; +#endif + return SCANNER_MAX_SPEED; } diff --git a/src/game/server/hl2/npc_basescanner.h b/src/game/server/hl2/npc_basescanner.h index ab69c81c..1a286b81 100644 --- a/src/game/server/hl2/npc_basescanner.h +++ b/src/game/server/hl2/npc_basescanner.h @@ -200,6 +200,11 @@ protected: float m_flAttackFarDist; float m_flAttackRange; +#ifdef MAPBASE + // Custom max speed for mappers to control + float m_flCustomMaxSpeed; +#endif + private: CSoundPatch *m_pEngineSound; diff --git a/src/game/server/hl2/npc_breen.cpp b/src/game/server/hl2/npc_breen.cpp index e12bab0c..e1409c80 100644 --- a/src/game/server/hl2/npc_breen.cpp +++ b/src/game/server/hl2/npc_breen.cpp @@ -34,6 +34,11 @@ public: void HandleAnimEvent( animevent_t *pEvent ); int GetSoundInterests ( void ); bool UseSemaphore( void ); + +#ifdef MAPBASE + // Use Breen's default subtitle color (188,188,188) + bool GetGameTextSpeechParams( hudtextparms_t ¶ms ) { params.r1 = 188; params.g1 = 188; params.b1 = 188; return BaseClass::GetGameTextSpeechParams( params ); } +#endif }; LINK_ENTITY_TO_CLASS( npc_breen, CNPC_Breen ); diff --git a/src/game/server/hl2/npc_bullsquid.cpp b/src/game/server/hl2/npc_bullsquid.cpp index 2c0706da..954ea868 100644 --- a/src/game/server/hl2/npc_bullsquid.cpp +++ b/src/game/server/hl2/npc_bullsquid.cpp @@ -7,11 +7,11 @@ #include "cbase.h" #include "game.h" -#include "AI_Default.h" -#include "AI_Schedule.h" -#include "AI_Hull.h" -#include "AI_Navigator.h" -#include "AI_Motor.h" +#include "ai_default.h" +#include "ai_schedule.h" +#include "ai_hull.h" +#include "ai_navigator.h" +#include "ai_motor.h" #include "ai_squad.h" #include "npc_bullsquid.h" #include "npcevent.h" @@ -30,8 +30,8 @@ #include "engine/IEngineSound.h" #include "movevars_shared.h" -#include "AI_Hint.h" -#include "AI_Senses.h" +#include "ai_hint.h" +#include "ai_senses.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" diff --git a/src/game/server/hl2/npc_citizen17.cpp b/src/game/server/hl2/npc_citizen17.cpp index adb9eb8c..49c68968 100644 --- a/src/game/server/hl2/npc_citizen17.cpp +++ b/src/game/server/hl2/npc_citizen17.cpp @@ -25,6 +25,12 @@ #include "eventqueue.h" +#ifdef MAPBASE +#include "hl2_gamerules.h" +#include "mapbase/GlobalStrings.h" +#include "collisionutils.h" +#endif + #include "ai_squad.h" #include "ai_pathfinder.h" #include "ai_route.h" @@ -69,6 +75,10 @@ ConVar npc_citizen_squad_marker( "npc_citizen_squad_marker", "0" ); ConVar npc_citizen_explosive_resist( "npc_citizen_explosive_resist", "0" ); ConVar npc_citizen_auto_player_squad( "npc_citizen_auto_player_squad", "1" ); ConVar npc_citizen_auto_player_squad_allow_use( "npc_citizen_auto_player_squad_allow_use", "0" ); +#ifdef MAPBASE +ConVar npc_citizen_squad_secondary_toggle_use_button("npc_citizen_squad_toggle_use_button", "262144"); // IN_WALK by default +ConVar npc_citizen_squad_secondary_toggle_use_always( "npc_citizen_squad_secondary_toggle_use_always", "0", FCVAR_NONE, "Allows all citizens not strictly stuck to the player's squad to be toggled via Alt + E." ); +#endif ConVar npc_citizen_dont_precache_all( "npc_citizen_dont_precache_all", "0" ); @@ -84,15 +94,29 @@ ConVar sk_citizen_heal_toss_player_delay("sk_citizen_heal_toss_player_delay", "2 #define MEDIC_THROW_SPEED npc_citizen_medic_throw_speed.GetFloat() +#ifdef MAPBASE +// We use a boolean now, so NameMatches("griggs") is handled in CNPC_Citizen::Spawn(). +#define USE_EXPERIMENTAL_MEDIC_CODE() (npc_citizen_heal_chuck_medkit.GetBool() && m_bTossesMedkits) +#else #define USE_EXPERIMENTAL_MEDIC_CODE() (npc_citizen_heal_chuck_medkit.GetBool() && NameMatches("griggs")) #endif +#endif +#ifdef MAPBASE +ConVar player_squad_autosummon_enabled( "player_squad_autosummon_enabled", "1" ); +#endif ConVar player_squad_autosummon_time( "player_squad_autosummon_time", "5" ); ConVar player_squad_autosummon_move_tolerance( "player_squad_autosummon_move_tolerance", "20" ); ConVar player_squad_autosummon_player_tolerance( "player_squad_autosummon_player_tolerance", "10" ); ConVar player_squad_autosummon_time_after_combat( "player_squad_autosummon_time_after_combat", "8" ); ConVar player_squad_autosummon_debug( "player_squad_autosummon_debug", "0" ); +#ifdef MAPBASE +ConVar npc_citizen_resupplier_adjust_ammo("npc_citizen_resupplier_adjust_ammo", "1", FCVAR_NONE, "If what ammo we give to the player would go over their max, should we adjust what we give accordingly (1) or cancel it altogether? (0)" ); + +ConVar npc_citizen_nocollide_player( "npc_citizen_nocollide_player", "0" ); +#endif + #define ShouldAutosquad() (npc_citizen_auto_player_squad.GetBool()) enum SquadSlot_T @@ -247,6 +271,10 @@ class CMattsPipe : public CWeaponCrowbar void SetPickupTouch( void ) { /* do nothing */ } }; +#ifdef MAPBASE +LINK_ENTITY_TO_CLASS(weapon_mattpipe, CMattsPipe); +#endif + //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- @@ -335,6 +363,10 @@ BEGIN_DATADESC( CNPC_Citizen ) DEFINE_KEYFIELD( m_bNotifyNavFailBlocked, FIELD_BOOLEAN, "notifynavfailblocked" ), DEFINE_KEYFIELD( m_bNeverLeavePlayerSquad, FIELD_BOOLEAN, "neverleaveplayersquad" ), DEFINE_KEYFIELD( m_iszDenyCommandConcept, FIELD_STRING, "denycommandconcept" ), +#ifdef MAPBASE + DEFINE_INPUT( m_bTossesMedkits, FIELD_BOOLEAN, "SetTossMedkits" ), + DEFINE_KEYFIELD( m_bAlternateAiming, FIELD_BOOLEAN, "AlternateAiming" ), +#endif DEFINE_OUTPUT( m_OnJoinedPlayerSquad, "OnJoinedPlayerSquad" ), DEFINE_OUTPUT( m_OnLeftPlayerSquad, "OnLeftPlayerSquad" ), @@ -342,11 +374,20 @@ BEGIN_DATADESC( CNPC_Citizen ) DEFINE_OUTPUT( m_OnStationOrder, "OnStationOrder" ), DEFINE_OUTPUT( m_OnPlayerUse, "OnPlayerUse" ), DEFINE_OUTPUT( m_OnNavFailBlocked, "OnNavFailBlocked" ), +#ifdef MAPBASE + DEFINE_OUTPUT( m_OnHealedNPC, "OnHealedNPC" ), + DEFINE_OUTPUT( m_OnHealedPlayer, "OnHealedPlayer" ), + DEFINE_OUTPUT( m_OnThrowMedkit, "OnTossMedkit" ), + DEFINE_OUTPUT( m_OnGiveAmmo, "OnGiveAmmo" ), +#endif DEFINE_INPUTFUNC( FIELD_VOID, "RemoveFromPlayerSquad", InputRemoveFromPlayerSquad ), DEFINE_INPUTFUNC( FIELD_VOID, "StartPatrolling", InputStartPatrolling ), DEFINE_INPUTFUNC( FIELD_VOID, "StopPatrolling", InputStopPatrolling ), DEFINE_INPUTFUNC( FIELD_VOID, "SetCommandable", InputSetCommandable ), +#ifdef MAPBASE + DEFINE_INPUTFUNC( FIELD_VOID, "SetUnCommandable", InputSetUnCommandable ), +#endif DEFINE_INPUTFUNC( FIELD_VOID, "SetMedicOn", InputSetMedicOn ), DEFINE_INPUTFUNC( FIELD_VOID, "SetMedicOff", InputSetMedicOff ), DEFINE_INPUTFUNC( FIELD_VOID, "SetAmmoResupplierOn", InputSetAmmoResupplierOn ), @@ -357,11 +398,35 @@ BEGIN_DATADESC( CNPC_Citizen ) DEFINE_INPUTFUNC( FIELD_VOID, "ThrowHealthKit", InputForceHealthKitToss ), #endif +#ifdef MAPBASE + DEFINE_INPUTFUNC( FIELD_STRING, "SetPoliceGoal", InputSetPoliceGoal ), +#endif + DEFINE_USEFUNC( CommanderUse ), DEFINE_USEFUNC( SimpleUse ), END_DATADESC() +#ifdef MAPBASE_VSCRIPT +ScriptHook_t CNPC_Citizen::g_Hook_SelectModel; + +BEGIN_ENT_SCRIPTDESC( CNPC_Citizen, CAI_BaseActor, "npc_citizen from Half-Life 2" ) + + DEFINE_SCRIPTFUNC( IsAmmoResupplier, "Returns true if this citizen is an ammo resupplier." ) + DEFINE_SCRIPTFUNC( CanHeal, "Returns true if this citizen is a medic or ammo resupplier currently able to heal/give ammo." ) + + DEFINE_SCRIPTFUNC( GetCitizenType, "Gets the citizen's type. 1 = Downtrodden, 2 = Refugee, 3 = Rebel, 4 = Unique" ) + DEFINE_SCRIPTFUNC( SetCitizenType, "Sets the citizen's type. 1 = Downtrodden, 2 = Refugee, 3 = Rebel, 4 = Unique" ) + + BEGIN_SCRIPTHOOK( CNPC_Citizen::g_Hook_SelectModel, "SelectModel", FIELD_CSTRING, "Called when a citizen is selecting a random model. 'model_path' is the directory of the selected model and 'model_head' is the name. The 'gender' parameter uses the 'GENDER_' constants and is based only on the citizen's random head spawnflags. If a full model path string is returned, it will be used as the model instead." ) + DEFINE_SCRIPTHOOK_PARAM( "model_path", FIELD_CSTRING ) + DEFINE_SCRIPTHOOK_PARAM( "model_head", FIELD_CSTRING ) + DEFINE_SCRIPTHOOK_PARAM( "gender", FIELD_INTEGER ) + END_SCRIPTHOOK() + +END_SCRIPTDESC(); +#endif + //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- @@ -373,7 +438,12 @@ CSimpleSimTimer CNPC_Citizen::gm_PlayerSquadEvaluateTimer; bool CNPC_Citizen::CreateBehaviors() { BaseClass::CreateBehaviors(); +#ifdef MAPBASE + AddBehavior( &m_RappelBehavior ); + AddBehavior( &m_PolicingBehavior ); +#else // Moved to CNPC_PlayerCompanion AddBehavior( &m_FuncTankBehavior ); +#endif return true; } @@ -382,7 +452,12 @@ bool CNPC_Citizen::CreateBehaviors() //----------------------------------------------------------------------------- void CNPC_Citizen::Precache() { +#ifdef MAPBASE + // CNPC_PlayerCompanion::Precache() is responsible for calling this now + BaseClass::Precache(); +#else SelectModel(); +#endif SelectExpressionType(); if ( !npc_citizen_dont_precache_all.GetBool() ) @@ -419,7 +494,9 @@ void CNPC_Citizen::Precache() } } +#ifndef MAPBASE // See above BaseClass::Precache(); +#endif } //----------------------------------------------------------------------------- @@ -481,6 +558,26 @@ void CNPC_Citizen::Spawn() m_bShouldPatrol = false; m_iHealth = sk_citizen_health.GetFloat(); +#ifdef MAPBASE + // Now only gets citizen_trains. + if ( GetMoveParent() && FClassnameIs( GetMoveParent(), "func_tracktrain" ) ) + { + if ( NameMatches("citizen_train_2") ) + { + CapabilitiesRemove( bits_CAP_MOVE_GROUND ); + SetMoveType( MOVETYPE_NONE ); + SetSequenceByName( "d1_t01_TrainRide_Sit_Idle" ); + SetIdealActivity( ACT_DO_NOT_DISTURB ); + } + else if ( NameMatches("citizen_train_1") ) + { + CapabilitiesRemove( bits_CAP_MOVE_GROUND ); + SetMoveType( MOVETYPE_NONE ); + SetSequenceByName( "d1_t01_TrainRide_Stand" ); + SetIdealActivity( ACT_DO_NOT_DISTURB ); + } + } +#else // Are we on a train? Used in trainstation to have NPCs on trains. if ( GetMoveParent() && FClassnameIs( GetMoveParent(), "func_tracktrain" ) ) { @@ -497,6 +594,7 @@ void CNPC_Citizen::Spawn() SetIdealActivity( ACT_DO_NOT_DISTURB ); } } +#endif m_flStopManhackFlinch = -1; @@ -528,6 +626,13 @@ void CNPC_Citizen::Spawn() // Use render bounds instead of human hull for guys sitting in chairs, etc. m_ActBusyBehavior.SetUseRenderBounds( HasSpawnFlags( SF_CITIZEN_USE_RENDER_BOUNDS ) ); + +#ifdef MAPBASE + if (NameMatches("griggs")) + { + m_bTossesMedkits = true; + } +#endif } //----------------------------------------------------------------------------- @@ -589,6 +694,14 @@ void CNPC_Citizen::SelectModel() if ( m_Type == CT_DEFAULT ) { +#ifdef MAPBASE + if (HL2GameRules()->GetDefaultCitizenType() != CT_DEFAULT) + { + m_Type = static_cast(HL2GameRules()->GetDefaultCitizenType()); + } + else + { +#endif struct CitizenTypeMapping { const char *pszMapTag; @@ -621,6 +734,9 @@ void CNPC_Citizen::SelectModel() if ( m_Type == CT_DEFAULT ) m_Type = CT_DOWNTRODDEN; +#ifdef MAPBASE + } +#endif } if( HasSpawnFlags( SF_CITIZEN_RANDOM_HEAD | SF_CITIZEN_RANDOM_HEAD_MALE | SF_CITIZEN_RANDOM_HEAD_FEMALE ) || GetModelName() == NULL_STRING ) @@ -681,6 +797,54 @@ void CNPC_Citizen::SelectModel() pszModelName = g_ppszRandomHeads[m_iHead]; SetModelName(NULL_STRING); } + +#ifdef MAPBASE_VSCRIPT + if (m_ScriptScope.IsInitialized() && g_Hook_SelectModel.CanRunInScope( m_ScriptScope )) + { + gender_t scriptGender; + switch (gender) + { + case 'm': + scriptGender = GENDER_MALE; + break; + case 'f': + scriptGender = GENDER_FEMALE; + break; + default: + scriptGender = GENDER_NONE; + break; + } + + const char *pszModelPath = CFmtStr( "models/Humans/%s/", (const char *)(CFmtStr( g_ppszModelLocs[m_Type], (IsMedic()) ? "m" : "" )) ); + + // model_path, model_head, gender + ScriptVariant_t args[] = { pszModelPath, pszModelName, (int)scriptGender }; + ScriptVariant_t returnValue = NULL; + g_Hook_SelectModel.Call( m_ScriptScope, &returnValue, args ); + + if (returnValue.m_type == FIELD_CSTRING && returnValue.m_pszString[0] != '\0') + { + // Refresh the head if it's different + const char *pszNewHead = strrchr( returnValue.m_pszString, '/' ); + if ( pszNewHead && Q_stricmp(pszNewHead+1, pszModelName) != 0 ) + { + pszNewHead++; + for ( int i = 0; i < ARRAYSIZE(g_ppszRandomHeads); i++ ) + { + if ( Q_stricmp( g_ppszRandomHeads[i], pszModelName ) == 0 ) + { + m_iHead = i; + break; + } + } + } + + // Just set the model right here + SetModelName( AllocPooledString( returnValue.m_pszString ) ); + return; + } + } +#endif } Assert( pszModelName || GetModelName() != NULL_STRING ); @@ -751,7 +915,11 @@ void CNPC_Citizen::SelectExpressionType() void CNPC_Citizen::FixupMattWeapon() { CBaseCombatWeapon *pWeapon = GetActiveWeapon(); +#ifdef MAPBASE + if ( pWeapon && EntIsClass( pWeapon, gm_isz_class_Crowbar ) && NameMatches( "matt" ) ) +#else if ( pWeapon && pWeapon->ClassMatches( "weapon_crowbar" ) && NameMatches( "matt" ) ) +#endif { Weapon_Drop( pWeapon ); UTIL_Remove( pWeapon ); @@ -1165,6 +1333,19 @@ int CNPC_Citizen::SelectFailSchedule( int failedSchedule, int failedTask, AI_Tas //----------------------------------------------------------------------------- int CNPC_Citizen::SelectSchedule() { +#ifdef MAPBASE + if ( IsWaitingToRappel() && BehaviorSelectSchedule() ) + { + return BaseClass::SelectSchedule(); + } + + if ( GetMoveType() == MOVETYPE_NONE && !Q_strncmp(STRING(GetEntityName()), "citizen_train_", 14) ) + { + // Only "sit on train" if we're a citizen_train_ + Assert( GetMoveParent() && FClassnameIs( GetMoveParent(), "func_tracktrain" ) ); + return SCHED_CITIZEN_SIT_ON_TRAIN; + } +#else // If we can't move, we're on a train, and should be sitting. if ( GetMoveType() == MOVETYPE_NONE ) { @@ -1173,13 +1354,23 @@ int CNPC_Citizen::SelectSchedule() Assert( GetMoveParent() && FClassnameIs( GetMoveParent(), "func_tracktrain" ) ); return SCHED_CITIZEN_SIT_ON_TRAIN; } +#endif +#ifdef MAPBASE + if ( GetActiveWeapon() && EntIsClass(GetActiveWeapon(), gm_isz_class_RPG) ) + { + CWeaponRPG *pRPG = static_cast(GetActiveWeapon()); +#else CWeaponRPG *pRPG = dynamic_cast(GetActiveWeapon()); +#endif if ( pRPG && pRPG->IsGuiding() ) { DevMsg( "Citizen in select schedule but RPG is guiding?\n"); pRPG->StopGuiding(); } +#ifdef MAPBASE + } +#endif return BaseClass::SelectSchedule(); } @@ -1351,7 +1542,11 @@ int CNPC_Citizen::SelectScheduleRetrieveItem() // Been kicked out of the player squad since the time I located the health. ClearCondition( COND_HEALTH_ITEM_AVAILABLE ); } +#ifdef MAPBASE + else if ( m_FollowBehavior.GetFollowTarget() ) +#else else +#endif { CBaseEntity *pBase = FindHealthItem(m_FollowBehavior.GetFollowTarget()->GetAbsOrigin(), Vector( 120, 120, 120 ) ); CItem *pItem = dynamic_cast(pBase); @@ -1479,9 +1674,15 @@ int CNPC_Citizen::TranslateSchedule( int scheduleType ) case SCHED_RANGE_ATTACK1: // If we have an RPG, we use a custom schedule for it +#ifdef MAPBASE + if ( !IsMortar( GetEnemy() ) && GetActiveWeapon() && EntIsClass(GetActiveWeapon(), gm_isz_class_RPG) ) + { + if ( GetEnemy() && EntIsClass(GetEnemy(), gm_isz_class_Strider) ) +#else if ( !IsMortar( GetEnemy() ) && GetActiveWeapon() && FClassnameIs( GetActiveWeapon(), "weapon_rpg" ) ) { if ( GetEnemy() && GetEnemy()->ClassMatches( "npc_strider" ) ) +#endif { if (OccupyStrategySlotRange( SQUAD_SLOT_CITIZEN_RPG1, SQUAD_SLOT_CITIZEN_RPG2 ) ) { @@ -1494,14 +1695,21 @@ int CNPC_Citizen::TranslateSchedule( int scheduleType ) } else { +#ifndef MAPBASE // This has been disabled for now. CBasePlayer *pPlayer = AI_GetSinglePlayer(); +#ifdef MAPBASE + // Don't avoid player if notarget is on + if ( pPlayer && GetEnemy() && !(pPlayer->GetFlags() & FL_NOTARGET) && ( ( GetEnemy()->GetAbsOrigin() - +#else if ( pPlayer && GetEnemy() && ( ( GetEnemy()->GetAbsOrigin() - +#endif pPlayer->GetAbsOrigin() ).LengthSqr() < RPG_SAFE_DISTANCE * RPG_SAFE_DISTANCE ) ) { // Don't fire our RPG at an enemy too close to the player return SCHED_STANDOFF; } else +#endif { return SCHED_CITIZEN_RANGE_ATTACK1_RPG; } @@ -1591,10 +1799,16 @@ void CNPC_Citizen::StartTask( const Task_t *pTask ) break; } +#ifdef MAPBASE + SetSpeechTarget( GetTarget() ); +#endif Speak( TLK_HEAL ); } else if ( IsAmmoResupplier() ) { +#ifdef MAPBASE + SetSpeechTarget( GetTarget() ); +#endif Speak( TLK_GIVEAMMO ); } SetIdealActivity( (Activity)ACT_CIT_HEAL ); @@ -1767,13 +1981,20 @@ void CNPC_Citizen::RunTask( const Task_t *pTask ) } Vector vecEnemyPos = GetEnemy()->BodyTarget(GetAbsOrigin(), false); +#ifndef MAPBASE // This has been disabled for now. CBasePlayer *pPlayer = AI_GetSinglePlayer(); +#ifdef MAPBASE + // Don't avoid player if notarget is on + if ( pPlayer && !(pPlayer->GetFlags() & FL_NOTARGET) && ( ( vecEnemyPos - pPlayer->GetAbsOrigin() ).LengthSqr() < RPG_SAFE_DISTANCE * RPG_SAFE_DISTANCE ) ) +#else if ( pPlayer && ( ( vecEnemyPos - pPlayer->GetAbsOrigin() ).LengthSqr() < RPG_SAFE_DISTANCE * RPG_SAFE_DISTANCE ) ) +#endif { m_bRPGAvoidPlayer = true; Speak( TLK_WATCHOUT ); } else +#endif { // Pull the laserdot towards the target Vector vecToTarget = (vecEnemyPos - vecLaserPos); @@ -1838,9 +2059,18 @@ Activity CNPC_Citizen::NPC_TranslateActivity( Activity activity ) { if ( activity == ACT_MELEE_ATTACK1 ) { +#ifdef MAPBASE + // It could be the new weapon punt activity. + if (GetActiveWeapon() && GetActiveWeapon()->IsMeleeWeapon()) + { + return ACT_MELEE_ATTACK_SWING; + } +#else return ACT_MELEE_ATTACK_SWING; +#endif } +#ifndef MAPBASE // Covered by the new backup activity system // !!!HACK - Citizens don't have the required animations for shotguns, // so trick them into using the rifle counterparts for now (sjb) if ( activity == ACT_RUN_AIM_SHOTGUN ) @@ -1851,6 +2081,29 @@ Activity CNPC_Citizen::NPC_TranslateActivity( Activity activity ) return ACT_IDLE_ANGRY_SMG1; if ( activity == ACT_RANGE_ATTACK_SHOTGUN_LOW ) return ACT_RANGE_ATTACK_SMG1_LOW; +#endif + +#ifdef MAPBASE + if (m_bAlternateAiming) + { + if (activity == ACT_RUN_AIM_RIFLE) + return ACT_RUN_AIM_RIFLE_STIMULATED; + if (activity == ACT_WALK_AIM_RIFLE) + return ACT_WALK_AIM_RIFLE_STIMULATED; + + if (activity == ACT_RUN_AIM_AR2) + return ACT_RUN_AIM_AR2_STIMULATED; + if (activity == ACT_WALK_AIM_AR2) + return ACT_WALK_AIM_AR2_STIMULATED; + +#if EXPANDED_HL2_WEAPON_ACTIVITIES + if (activity == ACT_RUN_AIM_PISTOL) + return ACT_RUN_AIM_PISTOL_STIMULATED; + if (activity == ACT_WALK_AIM_PISTOL) + return ACT_WALK_AIM_PISTOL_STIMULATED; +#endif + } +#endif return BaseClass::NPC_TranslateActivity( activity ); } @@ -1878,7 +2131,12 @@ void CNPC_Citizen::HandleAnimEvent( animevent_t *pEvent ) { // Heal my target (if within range) #if HL2_EPISODIC +#ifdef MAPBASE + // Don't throw medkits at NPCs, that's not how it works + if ( USE_EXPERIMENTAL_MEDIC_CODE() && IsMedic() && GetTarget() && !GetTarget()->IsNPC() ) +#else if ( USE_EXPERIMENTAL_MEDIC_CODE() && IsMedic() ) +#endif { CBaseCombatCharacter *pTarget = dynamic_cast( GetTarget() ); Assert(pTarget); @@ -1918,6 +2176,7 @@ void CNPC_Citizen::HandleAnimEvent( animevent_t *pEvent ) } } +#ifndef MAPBASE // Moved to CAI_BaseNPC //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ void CNPC_Citizen::PickupItem( CBaseEntity *pItem ) @@ -1944,6 +2203,7 @@ void CNPC_Citizen::PickupItem( CBaseEntity *pItem ) DevMsg("Citizen doesn't know how to pick up %s!\n", pItem->GetClassname() ); } } +#endif //----------------------------------------------------------------------------- // Purpose: @@ -2092,7 +2352,12 @@ bool CNPC_Citizen::IsManhackMeleeCombatant() { CBaseCombatWeapon *pWeapon = GetActiveWeapon(); CBaseEntity *pEnemy = GetEnemy(); +#ifdef MAPBASE + // Any melee weapon passes + return ( pEnemy && pWeapon && pEnemy->Classify() == CLASS_MANHACK && pWeapon->IsMeleeWeapon() ); +#else return ( pEnemy && pWeapon && pEnemy->Classify() == CLASS_MANHACK && pWeapon->ClassMatches( "weapon_crowbar" ) ); +#endif } //----------------------------------------------------------------------------- @@ -2103,6 +2368,14 @@ Vector CNPC_Citizen::GetActualShootPosition( const Vector &shootOrigin ) { Vector vecTarget = BaseClass::GetActualShootPosition( shootOrigin ); +#ifdef MAPBASE + // The gunship RPG code does not appear to be funcitonal, so only set the laser position. + if ( GetActiveWeapon() && EntIsClass(GetActiveWeapon(), gm_isz_class_RPG) && GetEnemy() ) + { + CWeaponRPG *pRPG = static_cast(GetActiveWeapon()); + pRPG->SetNPCLaserPosition( vecTarget ); + } +#else CWeaponRPG *pRPG = dynamic_cast(GetActiveWeapon()); // If we're firing an RPG at a gunship, aim off to it's side, because we'll auger towards it. if ( pRPG && GetEnemy() ) @@ -2142,8 +2415,8 @@ Vector CNPC_Citizen::GetActualShootPosition( const Vector &shootOrigin ) { pRPG->SetNPCLaserPosition( vecTarget ); } - } +#endif return vecTarget; } @@ -2194,19 +2467,31 @@ bool CNPC_Citizen::ShouldLookForBetterWeapon() { bool bDefer = false; +#ifdef MAPBASE + if ( EntIsClass(pWeapon, gm_isz_class_AR2) ) +#else if( FClassnameIs( pWeapon, "weapon_ar2" ) ) +#endif { // Content to keep this weapon forever m_flNextWeaponSearchTime = OTHER_DEFER_SEARCH_TIME; bDefer = true; } +#ifdef MAPBASE + else if( EntIsClass(pWeapon, gm_isz_class_RPG) ) +#else else if( FClassnameIs( pWeapon, "weapon_rpg" ) ) +#endif { // Content to keep this weapon forever m_flNextWeaponSearchTime = OTHER_DEFER_SEARCH_TIME; bDefer = true; } +#ifdef MAPBASE + else if ( EntIsClass(pWeapon, gm_isz_class_Shotgun) ) +#else else if( FClassnameIs( pWeapon, "weapon_shotgun" ) ) +#endif { // Shotgunners do not defer their weapon search indefinitely. // If more than one citizen in the squad has a shotgun, we force @@ -2294,6 +2579,20 @@ int CNPC_Citizen::OnTakeDamage_Alive( const CTakeDamageInfo &info ) return BaseClass::OnTakeDamage_Alive( newInfo ); } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CNPC_Citizen::ModifyOrAppendCriteria( AI_CriteriaSet& set ) +{ + BaseClass::ModifyOrAppendCriteria( set ); + + // No need to tell me. + set.AppendCriteria("medic", IsMedic() ? "1" : "0"); + + set.AppendCriteria("citizentype", UTIL_VarArgs("%i", m_Type)); +} +#endif + //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- bool CNPC_Citizen::IsCommandable() @@ -2337,6 +2636,11 @@ bool CNPC_Citizen::CanJoinPlayerSquad() if ( IRelationType( UTIL_GetLocalPlayer() ) != D_LI ) return false; +#ifdef MAPBASE + if ( IsWaitingToRappel() ) + return false; +#endif + return true; } @@ -2511,6 +2815,64 @@ bool CNPC_Citizen::SpeakCommandResponse( AIConcept_t concept, const char *modifi ( modifiers ) ? CFmtStr(",%s", modifiers).operator const char *() : "" ) ); } +#ifdef MAPBASE +extern ConVar ai_debug_avoidancebounds; + +//----------------------------------------------------------------------------- +// Purpose: Implements player nocollide. +//----------------------------------------------------------------------------- +void CNPC_Citizen::SetPlayerAvoidState( void ) +{ + bool bShouldPlayerAvoid = false; + Vector vNothing; + + GetSequenceLinearMotion( GetSequence(), &vNothing ); + bool bIsMoving = ( IsMoving() || ( vNothing != vec3_origin ) ); + + m_bPlayerAvoidState = ShouldPlayerAvoid(); + bool bSquadNoCollide = (IsInPlayerSquad() && npc_citizen_nocollide_player.GetBool()); + + // If we are coming out of a script, check if we are stuck inside the player. + if ( m_bPerformAvoidance || ( m_bPlayerAvoidState && bIsMoving ) || bSquadNoCollide ) + { + trace_t trace; + Vector vMins, vMaxs; + + GetPlayerAvoidBounds( &vMins, &vMaxs ); + + CBasePlayer *pLocalPlayer = AI_GetSinglePlayer(); + + if ( pLocalPlayer ) + { + bShouldPlayerAvoid = (!bSquadNoCollide || !pLocalPlayer->IsMoving()) && IsBoxIntersectingBox( GetAbsOrigin() + vMins, GetAbsOrigin() + vMaxs, + pLocalPlayer->GetAbsOrigin() + pLocalPlayer->WorldAlignMins(), pLocalPlayer->GetAbsOrigin() + pLocalPlayer->WorldAlignMaxs() ); + } + + if ( ai_debug_avoidancebounds.GetBool() ) + { + int iRed = ( bShouldPlayerAvoid == true ) ? 255 : 0; + + NDebugOverlay::Box( GetAbsOrigin(), vMins, vMaxs, iRed, 0, 255, 64, 0.1 ); + } + } + + m_bPerformAvoidance = bShouldPlayerAvoid; + + if ( GetCollisionGroup() == COLLISION_GROUP_NPC || GetCollisionGroup() == COLLISION_GROUP_NPC_ACTOR ) + { + if ( m_bPerformAvoidance == true || + (bSquadNoCollide && !m_bPlayerAvoidState)) + { + SetCollisionGroup( COLLISION_GROUP_NPC_ACTOR ); + } + else + { + SetCollisionGroup( COLLISION_GROUP_NPC ); + } + } +} +#endif + //----------------------------------------------------------------------------- // Purpose: return TRUE if the commander mode should try to give this order // to more people. return FALSE otherwise. For instance, we don't @@ -2553,7 +2915,11 @@ void CNPC_Citizen::MoveOrder( const Vector &vecDest, CAI_BaseNPC **Allies, int n if ( !AI_IsSinglePlayer() ) return; +#ifdef MAPBASE + if ( m_iszDenyCommandConcept != NULL_STRING ) +#else if( hl2_episodic.GetBool() && m_iszDenyCommandConcept != NULL_STRING ) +#endif { SpeakCommandResponse( STRING(m_iszDenyCommandConcept) ); return; @@ -2635,6 +3001,28 @@ void CNPC_Citizen::OnMoveOrder() BaseClass::OnMoveOrder(); } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +inline bool CNPC_Citizen::ShouldAllowSquadToggleUse( CBasePlayer *pPlayer ) +{ + if (HasSpawnFlags( SF_CITIZEN_NOT_COMMANDABLE )) + return false; + + //if (!HL2GameRules() || !HL2GameRules()->AllowSquadToggleUse()) + if (!HasSpawnFlags( SF_CITIZEN_PLAYER_TOGGLE_SQUAD )) + { + if (!npc_citizen_squad_secondary_toggle_use_always.GetBool() || m_bNeverLeavePlayerSquad) + return false; + + // npc_citizen_squad_secondary_toggle_use_always was invoked + AddSpawnFlags( SF_CITIZEN_PLAYER_TOGGLE_SQUAD ); + } + + return (pPlayer->m_nButtons & npc_citizen_squad_secondary_toggle_use_button.GetInt()) != 0; +} +#endif + //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- void CNPC_Citizen::CommanderUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) @@ -2654,6 +3042,31 @@ void CNPC_Citizen::CommanderUse( CBaseEntity *pActivator, CBaseEntity *pCaller, // Don't say hi after you've been addressed by the player SetSpokeConcept( TLK_HELLO, NULL ); +#ifdef MAPBASE + if ( ShouldAllowSquadToggleUse(UTIL_GetLocalPlayer()) || npc_citizen_auto_player_squad_allow_use.GetBool() ) + { + // Version of TogglePlayerSquadState() that has "used" as a modifier + static const char *szSquadUseModifier = "used:1"; + if ( !IsInPlayerSquad() ) + { + AddToPlayerSquad(); + + if ( HaveCommandGoal() ) + { + SpeakCommandResponse( TLK_COMMANDED, szSquadUseModifier ); + } + else if ( m_FollowBehavior.GetFollowTarget() == UTIL_GetLocalPlayer() ) + { + SpeakCommandResponse( TLK_STARTFOLLOW, szSquadUseModifier ); + } + } + else + { + SpeakCommandResponse( TLK_STOPFOLLOW, szSquadUseModifier ); + RemoveFromPlayerSquad(); + } + } +#else if ( npc_citizen_auto_player_squad_allow_use.GetBool() ) { if ( !ShouldAutosquad() ) @@ -2661,8 +3074,37 @@ void CNPC_Citizen::CommanderUse( CBaseEntity *pActivator, CBaseEntity *pCaller, else if ( !IsInPlayerSquad() && npc_citizen_auto_player_squad_allow_use.GetBool() ) AddToPlayerSquad(); } +#endif else if ( GetCurSchedule() && ConditionInterruptsCurSchedule( COND_IDLE_INTERRUPT ) ) { +#ifdef MAPBASE + // Just do regular idle question behavior so question groups, etc. work on +USE. + if ( IsAllowedToSpeak( TLK_QUESTION, true ) ) + { + // 1 = Old "SpeakIdleResponse" behavior + // 2, 3 = AskQuestion() for QA groups, etc. + // 4 = Just speak + int iRandom = random->RandomInt(1, 4); + if ( iRandom == 1 ) + { + CBaseEntity *pRespondant = FindSpeechTarget( AIST_NPCS ); + if ( pRespondant ) + { + g_EventQueue.AddEvent( pRespondant, "SpeakIdleResponse", ( GetTimeSpeechComplete() - gpGlobals->curtime ) + .2, this, this ); + } + } + if ( iRandom < 4 ) + { + // Ask someone else + AskQuestionNow(); + } + else + { + // Just speak + Speak( TLK_QUESTION ); + } + } +#else if ( SpeakIfAllowed( TLK_QUESTION, NULL, true ) ) { if ( random->RandomInt( 1, 4 ) < 4 ) @@ -2674,6 +3116,7 @@ void CNPC_Citizen::CommanderUse( CBaseEntity *pActivator, CBaseEntity *pCaller, } } } +#endif } } } @@ -2883,6 +3326,11 @@ void CNPC_Citizen::UpdatePlayerSquad() if ( !pCitizen->CanJoinPlayerSquad() ) continue; +#ifdef MAPBASE + if ( pCitizen->HasSpawnFlags(SF_CITIZEN_PLAYER_TOGGLE_SQUAD) ) + continue; +#endif + bool bShouldAdd = false; if ( pCitizen->HasCondition( COND_SEE_PLAYER ) ) @@ -2940,7 +3388,11 @@ void CNPC_Citizen::UpdatePlayerSquad() if ( ppAIs[j]->GetClassname() != GetClassname() ) continue; +#ifdef MAPBASE + if ( ppAIs[j]->HasSpawnFlags( SF_CITIZEN_NOT_COMMANDABLE | SF_CITIZEN_PLAYER_TOGGLE_SQUAD ) ) +#else if ( ppAIs[j]->HasSpawnFlags( SF_CITIZEN_NOT_COMMANDABLE ) ) +#endif continue; CNPC_Citizen *pCitizen = assert_cast(ppAIs[j]); @@ -3415,7 +3867,11 @@ bool CNPC_Citizen::ShouldHealTarget( CBaseEntity *pTarget, bool bActiveUse ) { Disposition_t disposition; +#ifdef MAPBASE + if ( pTarget && ( ( disposition = IRelationType( pTarget ) ) != D_LI && disposition != D_NU ) ) +#else if ( !pTarget && ( ( disposition = IRelationType( pTarget ) ) != D_LI && disposition != D_NU ) ) +#endif return false; // Don't heal if I'm in the middle of talking @@ -3497,6 +3953,14 @@ bool CNPC_Citizen::ShouldHealTarget( CBaseEntity *pTarget, bool bActiveUse ) if ( ((CBasePlayer*)pTarget)->Weapon_GetWpnForAmmo( iAmmoType ) ) return true; } +#ifdef MAPBASE + else if ( (iMax - iCount) < m_iAmmoAmount && (iMax - iCount) != 0 ) + { + // If we're allowed to adjust our ammo, the amount of ammo we give may be reduced, but that's better than not giving any at all! + if (npc_citizen_resupplier_adjust_ammo.GetBool() == true && ((CBasePlayer*)pTarget)->Weapon_GetWpnForAmmo( iAmmoType )) + return true; + } +#endif } } } @@ -3515,14 +3979,25 @@ bool CNPC_Citizen::ShouldHealTossTarget( CBaseEntity *pTarget, bool bActiveUse ) if ( !IsMedic() ) return false; +#ifdef MAPBASE + if ( pTarget && ( ( disposition = IRelationType( pTarget ) ) != D_LI && disposition != D_NU ) ) +#else if ( !pTarget && ( ( disposition = IRelationType( pTarget ) ) != D_LI && disposition != D_NU ) ) +#endif return false; // Don't heal if I'm in the middle of talking if ( IsSpeaking() ) return false; +#ifdef MAPBASE + // NPCs cannot be healed by throwing medkits at them. + // I don't think NPCs even pass through this function anyway, it's just the actual heal event that's the problem. + if (!pTarget->IsPlayer()) + return false; +#else bool bTargetIsPlayer = pTarget->IsPlayer(); +#endif // Don't heal or give ammo to targets in vehicles CBaseCombatCharacter *pCCTarget = pTarget->MyCombatCharacterPointer(); @@ -3550,18 +4025,26 @@ bool CNPC_Citizen::ShouldHealTossTarget( CBaseEntity *pTarget, bool bActiveUse ) } // Are we ready to heal again? +#ifdef MAPBASE + bool bReadyToHeal = m_flPlayerHealTime <= gpGlobals->curtime; +#else bool bReadyToHeal = ( ( bTargetIsPlayer && m_flPlayerHealTime <= gpGlobals->curtime ) || ( !bTargetIsPlayer && m_flAllyHealTime <= gpGlobals->curtime ) ); +#endif // Only heal if we're ready if ( bReadyToHeal ) { int requiredHealth; +#ifdef MAPBASE + requiredHealth = pTarget->GetMaxHealth() - sk_citizen_heal_player.GetFloat(); +#else if ( bTargetIsPlayer ) requiredHealth = pTarget->GetMaxHealth() - sk_citizen_heal_player.GetFloat(); else requiredHealth = pTarget->GetMaxHealth() * sk_citizen_heal_player_min_pct.GetFloat(); +#endif if ( ( pTarget->m_iHealth <= requiredHealth ) && IRelationType( pTarget ) == D_LI ) return true; @@ -3583,6 +4066,11 @@ void CNPC_Citizen::Heal() CBaseEntity *pTarget = GetTarget(); +#ifdef MAPBASE + if ( !pTarget ) + return; +#endif + Vector target = pTarget->GetAbsOrigin() - GetAbsOrigin(); if ( target.Length() > HEAL_TARGET_RANGE * 2 ) return; @@ -3625,6 +4113,10 @@ void CNPC_Citizen::Heal() EmitSound( filter, pTarget->entindex(), "HealthKit.Touch" ); } +#ifdef MAPBASE + pTarget->IsPlayer() ? m_OnHealedPlayer.FireOutput(pTarget, this) : m_OnHealedNPC.FireOutput(pTarget, this); +#endif + pTarget->TakeHealth( healAmt, DMG_GENERIC ); pTarget->RemoveAllDecals(); } @@ -3643,6 +4135,10 @@ void CNPC_Citizen::Heal() else { ((CBasePlayer*)pTarget)->GiveAmmo( m_iAmmoAmount, iAmmoType, false ); + +#ifdef MAPBASE + m_OnGiveAmmo.FireOutput(pTarget, this); +#endif } m_flPlayerGiveAmmoTime = gpGlobals->curtime + sk_citizen_giveammo_player_delay.GetFloat(); @@ -3715,6 +4211,10 @@ void CNPC_Citizen::TossHealthKit(CBaseCombatCharacter *pThrowAt, const Vector &o pPhysicsObject->SetVelocity( &tossVelocity, &angDummy ); } } + +#ifdef MAPBASE + m_OnThrowMedkit.Set(pHealthKit, pHealthKit, this); +#endif } else { @@ -3790,6 +4290,16 @@ void CNPC_Citizen::InputSetCommandable( inputdata_t &inputdata ) gm_PlayerSquadEvaluateTimer.Force(); } +#ifdef MAPBASE +//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ +void CNPC_Citizen::InputSetUnCommandable( inputdata_t &inputdata ) +{ + AddSpawnFlags( SF_CITIZEN_NOT_COMMANDABLE ); + RemoveFromPlayerSquad(); +} +#endif + //----------------------------------------------------------------------------- // Purpose: // Input : &inputdata - @@ -3833,6 +4343,39 @@ void CNPC_Citizen::InputSpeakIdleResponse( inputdata_t &inputdata ) SpeakIfAllowed( TLK_ANSWER, NULL, true ); } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: +// Input : &inputdata - +//----------------------------------------------------------------------------- +void CNPC_Citizen::InputSetPoliceGoal( inputdata_t &inputdata ) +{ + if (/*!inputdata.value.String() ||*/ inputdata.value.String()[0] == 0) + { + m_PolicingBehavior.Disable(); + return; + } + + CBaseEntity *pGoal = gEntList.FindEntityByName( NULL, inputdata.value.String() ); + + if ( pGoal == NULL ) + { + DevMsg( "SetPoliceGoal: %s (%s) unable to find ai_goal_police: %s\n", GetClassname(), GetDebugName(), inputdata.value.String() ); + return; + } + + CAI_PoliceGoal *pPoliceGoal = dynamic_cast(pGoal); + + if ( pPoliceGoal == NULL ) + { + DevMsg( "SetPoliceGoal: %s (%s)'s target %s is not an ai_goal_police entity!\n", GetClassname(), GetDebugName(), inputdata.value.String() ); + return; + } + + m_PolicingBehavior.Enable( pPoliceGoal ); +} +#endif + //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- void CNPC_Citizen::DeathSound( const CTakeDamageInfo &info ) @@ -3840,7 +4383,13 @@ void CNPC_Citizen::DeathSound( const CTakeDamageInfo &info ) // Sentences don't play on dead NPCs SentenceStop(); +#ifdef MAPBASE + AI_CriteriaSet set; + ModifyOrAppendDamageCriteria(set, info); + Speak( TLK_DEATH, set ); +#else EmitSound( "NPC_Citizen.Die" ); +#endif } //------------------------------------------------------------------------------ diff --git a/src/game/server/hl2/npc_citizen17.h b/src/game/server/hl2/npc_citizen17.h index 49efbc7a..5d23429d 100644 --- a/src/game/server/hl2/npc_citizen17.h +++ b/src/game/server/hl2/npc_citizen17.h @@ -11,6 +11,10 @@ #include "npc_playercompanion.h" #include "ai_behavior_functank.h" +#ifdef MAPBASE +#include "ai_behavior_rappel.h" +#include "ai_behavior_police.h" +#endif struct SquadCandidate_t; @@ -33,6 +37,9 @@ struct SquadCandidate_t; #define SF_CITIZEN_RANDOM_HEAD_MALE ( 1 << 22 ) //4194304 #define SF_CITIZEN_RANDOM_HEAD_FEMALE ( 1 << 23 )//8388608 #define SF_CITIZEN_USE_RENDER_BOUNDS ( 1 << 24 )//16777216 +#ifdef MAPBASE +#define SF_CITIZEN_PLAYER_TOGGLE_SQUAD ( 1 << 25 ) //33554432 Prevents the citizen from joining the squad automatically, but still being commandable if the player toggles it +#endif //------------------------------------- // Animation events @@ -130,7 +137,9 @@ public: void HandleAnimEvent( animevent_t *pEvent ); void TaskFail( AI_TaskFailureCode_t code ); +#ifndef MAPBASE // Moved to CAI_BaseNPC void PickupItem( CBaseEntity *pItem ); +#endif void SimpleUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); @@ -161,6 +170,11 @@ public: // Damage handling //--------------------------------- int OnTakeDamage_Alive( const CTakeDamageInfo &info ); + +#ifdef MAPBASE + //--------------------------------- + void ModifyOrAppendCriteria( AI_CriteriaSet& set ); +#endif //--------------------------------- // Commander mode @@ -179,6 +193,9 @@ public: void MoveOrder( const Vector &vecDest, CAI_BaseNPC **Allies, int numAllies ); void OnMoveOrder(); void CommanderUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); +#ifdef MAPBASE + bool ShouldAllowSquadToggleUse( CBasePlayer *pPlayer ); +#endif bool ShouldSpeakRadio( CBaseEntity *pListener ); void OnMoveToCommandGoalFailed(); void AddToPlayerSquad(); @@ -195,6 +212,10 @@ public: void AddInsignia(); void RemoveInsignia(); bool SpeakCommandResponse( AIConcept_t concept, const char *modifiers = NULL ); + +#ifdef MAPBASE + virtual void SetPlayerAvoidState( void ); +#endif //--------------------------------- // Scanner interaction @@ -235,11 +256,17 @@ public: void InputStartPatrolling( inputdata_t &inputdata ); void InputStopPatrolling( inputdata_t &inputdata ); void InputSetCommandable( inputdata_t &inputdata ); +#ifdef MAPBASE + void InputSetUnCommandable( inputdata_t &inputdata ); +#endif void InputSetMedicOn( inputdata_t &inputdata ); void InputSetMedicOff( inputdata_t &inputdata ); void InputSetAmmoResupplierOn( inputdata_t &inputdata ); void InputSetAmmoResupplierOff( inputdata_t &inputdata ); void InputSpeakIdleResponse( inputdata_t &inputdata ); +#ifdef MAPBASE + void InputSetPoliceGoal( inputdata_t &inputdata ); +#endif //--------------------------------- // Sounds & speech @@ -250,6 +277,11 @@ public: virtual void OnChangeRunningBehavior( CAI_BehaviorBase *pOldBehavior, CAI_BehaviorBase *pNewBehavior ); +#ifdef MAPBASE + int GetCitizenType() { return (int)m_Type; } + void SetCitizenType( int iType ) { m_Type = (CitizenType_t)iType; } +#endif + private: //----------------------------------------------------- // Conditions, Schedules, Tasks @@ -303,6 +335,10 @@ private: bool m_bWasInPlayerSquad; float m_flTimeLastCloseToPlayer; string_t m_iszDenyCommandConcept; +#ifdef MAPBASE + bool m_bTossesMedkits; + bool m_bAlternateAiming; +#endif CSimpleSimTimer m_AutoSummonTimer; Vector m_vAutoSummonAnchor; @@ -326,9 +362,24 @@ private: COutputEvent m_OnStationOrder; COutputEvent m_OnPlayerUse; COutputEvent m_OnNavFailBlocked; +#ifdef MAPBASE + COutputEvent m_OnHealedNPC; + COutputEvent m_OnHealedPlayer; + COutputEHANDLE m_OnThrowMedkit; + COutputEvent m_OnGiveAmmo; +#endif //----------------------------------------------------- +#ifdef MAPBASE + CAI_RappelBehavior m_RappelBehavior; + CAI_PolicingBehavior m_PolicingBehavior; + + // Rappel + virtual bool IsWaitingToRappel( void ) { return m_RappelBehavior.IsWaitingToRappel(); } + void BeginRappel() { m_RappelBehavior.BeginRappel(); } +#else // Moved to CNPC_PlayerCompanion CAI_FuncTankBehavior m_FuncTankBehavior; +#endif CHandle m_hSavedFollowGoalEnt; @@ -337,6 +388,10 @@ private: //----------------------------------------------------- +#ifdef MAPBASE_VSCRIPT + static ScriptHook_t g_Hook_SelectModel; + DECLARE_ENT_SCRIPTDESC(); +#endif DECLARE_DATADESC(); #ifdef _XBOX protected: diff --git a/src/game/server/hl2/npc_combine.cpp b/src/game/server/hl2/npc_combine.cpp index fb335e67..667eb62f 100644 --- a/src/game/server/hl2/npc_combine.cpp +++ b/src/game/server/hl2/npc_combine.cpp @@ -29,23 +29,43 @@ #include "weapon_physcannon.h" #include "SoundEmitterSystem/isoundemittersystembase.h" #include "npc_headcrab.h" +#ifdef MAPBASE +#include "mapbase/GlobalStrings.h" +#include "globalstate.h" +#include "sceneentity.h" +#endif // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" int g_fCombineQuestion; // true if an idle grunt asked a question. Cleared when someone answers. YUCK old global from grunt code +#ifdef MAPBASE +ConVar npc_combine_idle_walk_easy( "npc_combine_idle_walk_easy", "1", FCVAR_NONE, "Mapbase: Allows Combine soldiers to use ACT_WALK_EASY as a walking animation when idle." ); +ConVar npc_combine_unarmed_anims( "npc_combine_unarmed_anims", "1", FCVAR_NONE, "Mapbase: Allows Combine soldiers to use unarmed idle/walk animations when they have no weapon." ); +ConVar npc_combine_protected_run( "npc_combine_protected_run", "0", FCVAR_NONE, "Mapbase: Allows Combine soldiers to use \"protected run\" animations." ); +ConVar npc_combine_altfire_not_allies_only( "npc_combine_altfire_not_allies_only", "1", FCVAR_NONE, "Mapbase: Elites are normally only allowed to fire their alt-fire attack at the player and the player's allies; This allows elites to alt-fire at other enemies too." ); + +ConVar npc_combine_new_cover_behavior( "npc_combine_new_cover_behavior", "1", FCVAR_NONE, "Mapbase: Toggles small patches for parts of npc_combine AI related to soldiers failing to take cover. These patches are minimal and only change cases where npc_combine would otherwise look at an enemy without shooting or run up to the player to melee attack when they don't have to. Consult the Mapbase wiki for more information." ); + +ConVar npc_combine_fixed_shootpos( "npc_combine_fixed_shootpos", "0", FCVAR_NONE, "Mapbase: Toggles fixed Combine soldier shoot position." ); +#endif + #define COMBINE_SKIN_DEFAULT 0 #define COMBINE_SKIN_SHOTGUNNER 1 +#ifndef MAPBASE #define COMBINE_GRENADE_THROW_SPEED 650 #define COMBINE_GRENADE_TIMER 3.5 #define COMBINE_GRENADE_FLUSH_TIME 3.0 // Don't try to flush an enemy who has been out of sight for longer than this. #define COMBINE_GRENADE_FLUSH_DIST 256.0 // Don't try to flush an enemy who has moved farther than this distance from the last place I saw him. +#endif #define COMBINE_LIMP_HEALTH 20 +#ifndef MAPBASE #define COMBINE_MIN_GRENADE_CLEAR_DIST 250 +#endif #define COMBINE_EYE_STANDING_POSITION Vector( 0, 0, 66 ) #define COMBINE_GUN_STANDING_POSITION Vector( 0, 0, 57 ) @@ -60,7 +80,11 @@ int g_fCombineQuestion; // true if an idle grunt asked a question. Cleared wh //----------------------------------------------------------------------------- // This is the index to the name of the shotgun's classname in the string pool // so that we can get away with an integer compare rather than a string compare. +#ifdef MAPBASE +#define s_iszShotgunClassname gm_isz_class_Shotgun +#else string_t s_iszShotgunClassname; +#endif //----------------------------------------------------------------------------- // Interactions @@ -73,13 +97,17 @@ int g_interactionCombineBash = 0; // melee bash attack #define COMBINE_AE_RELOAD ( 2 ) #define COMBINE_AE_KICK ( 3 ) #define COMBINE_AE_AIM ( 4 ) +#ifndef MAPBASE #define COMBINE_AE_GREN_TOSS ( 7 ) +#endif #define COMBINE_AE_GREN_LAUNCH ( 8 ) #define COMBINE_AE_GREN_DROP ( 9 ) #define COMBINE_AE_CAUGHT_ENEMY ( 10) // grunt established sight with an enemy (player only) that had previously eluded the squad. +#ifndef MAPBASE int COMBINE_AE_BEGIN_ALTFIRE; int COMBINE_AE_ALTFIRE; +#endif //========================================================= // Combine activities @@ -91,12 +119,21 @@ int COMBINE_AE_ALTFIRE; //Activity ACT_COMBINE_WALKING_AR2; //Activity ACT_COMBINE_STANDING_SHOTGUN; //Activity ACT_COMBINE_CROUCHING_SHOTGUN; +#if !SHARED_COMBINE_ACTIVITIES Activity ACT_COMBINE_THROW_GRENADE; +#endif Activity ACT_COMBINE_LAUNCH_GRENADE; Activity ACT_COMBINE_BUGBAIT; +#if !SHARED_COMBINE_ACTIVITIES Activity ACT_COMBINE_AR2_ALTFIRE; +#endif Activity ACT_WALK_EASY; Activity ACT_WALK_MARCH; +#ifdef MAPBASE +Activity ACT_TURRET_CARRY_IDLE; +Activity ACT_TURRET_CARRY_WALK; +Activity ACT_TURRET_CARRY_RUN; +#endif // ----------------------------------------------- // > Squad slots @@ -114,6 +151,9 @@ enum TacticalVariant_T TACTICAL_VARIANT_DEFAULT = 0, TACTICAL_VARIANT_PRESSURE_ENEMY, // Always try to close in on the player. TACTICAL_VARIANT_PRESSURE_ENEMY_UNTIL_CLOSE, // Act like VARIANT_PRESSURE_ENEMY, but go to VARIANT_DEFAULT once within 30 feet +#ifdef MAPBASE + TACTICAL_VARIANT_GRENADE_HAPPY, // Throw grenades as if you're fighting a turret +#endif }; enum PathfindingVariant_T @@ -135,20 +175,33 @@ BEGIN_DATADESC( CNPC_Combine ) DEFINE_FIELD( m_nKickDamage, FIELD_INTEGER ), DEFINE_FIELD( m_vecTossVelocity, FIELD_VECTOR ), +#ifndef MAPBASE DEFINE_FIELD( m_hForcedGrenadeTarget, FIELD_EHANDLE ), +#endif DEFINE_FIELD( m_bShouldPatrol, FIELD_BOOLEAN ), DEFINE_FIELD( m_bFirstEncounter, FIELD_BOOLEAN ), DEFINE_FIELD( m_flNextPainSoundTime, FIELD_TIME ), DEFINE_FIELD( m_flNextAlertSoundTime, FIELD_TIME ), +#ifndef MAPBASE DEFINE_FIELD( m_flNextGrenadeCheck, FIELD_TIME ), +#endif DEFINE_FIELD( m_flNextLostSoundTime, FIELD_TIME ), DEFINE_FIELD( m_flAlertPatrolTime, FIELD_TIME ), +#ifndef MAPBASE DEFINE_FIELD( m_flNextAltFireTime, FIELD_TIME ), +#endif DEFINE_FIELD( m_nShots, FIELD_INTEGER ), DEFINE_FIELD( m_flShotDelay, FIELD_FLOAT ), DEFINE_FIELD( m_flStopMoveShootTime, FIELD_TIME ), +#ifndef MAPBASE // See ai_grenade.h DEFINE_KEYFIELD( m_iNumGrenades, FIELD_INTEGER, "NumGrenades" ), +#else +DEFINE_INPUT( m_bUnderthrow, FIELD_BOOLEAN, "UnderthrowGrenades" ), +DEFINE_INPUT( m_bAlternateCapable, FIELD_BOOLEAN, "SetAlternateCapable" ), +#endif +#ifndef COMBINE_SOLDIER_USES_RESPONSE_SYSTEM DEFINE_EMBEDDED( m_Sentences ), +#endif // m_AssaultBehavior (auto saved by AI) // m_StandoffBehavior (auto saved by AI) @@ -167,11 +220,27 @@ DEFINE_INPUTFUNC( FIELD_STRING, "Assault", InputAssault ), DEFINE_INPUTFUNC( FIELD_VOID, "HitByBugbait", InputHitByBugbait ), +#ifndef MAPBASE DEFINE_INPUTFUNC( FIELD_STRING, "ThrowGrenadeAtTarget", InputThrowGrenadeAtTarget ), +#else +DEFINE_INPUTFUNC( FIELD_BOOLEAN, "SetElite", InputSetElite ), +DEFINE_INPUTFUNC( FIELD_VOID, "DropGrenade", InputDropGrenade ), + +DEFINE_INPUTFUNC( FIELD_INTEGER, "SetTacticalVariant", InputSetTacticalVariant ), + +DEFINE_INPUTFUNC( FIELD_STRING, "SetPoliceGoal", InputSetPoliceGoal ), + +DEFINE_AIGRENADE_DATADESC() +#endif + +#ifndef MAPBASE DEFINE_FIELD( m_iLastAnimEventHandled, FIELD_INTEGER ), +#endif DEFINE_FIELD( m_fIsElite, FIELD_BOOLEAN ), +#ifndef MAPBASE DEFINE_FIELD( m_vecAltFireTarget, FIELD_VECTOR ), +#endif DEFINE_KEYFIELD( m_iTacticalVariant, FIELD_INTEGER, "tacticalvariant" ), DEFINE_KEYFIELD( m_iPathfindingVariant, FIELD_INTEGER, "pathfindingvariant" ), @@ -196,7 +265,9 @@ bool CNPC_Combine::CreateComponents() if ( !BaseClass::CreateComponents() ) return false; +#ifndef COMBINE_SOLDIER_USES_RESPONSE_SYSTEM m_Sentences.Init( this, "NPC_Combine.SentenceParameters" ); +#endif return true; } @@ -249,6 +320,7 @@ void CNPC_Combine::InputHitByBugbait( inputdata_t &inputdata ) SetCondition( COND_COMBINE_HIT_BY_BUGBAIT ); } +#ifndef MAPBASE //----------------------------------------------------------------------------- // Purpose: Force the combine soldier to throw a grenade at the target // If I'm a combine elite, fire my combine ball at the target instead. @@ -260,7 +332,11 @@ void CNPC_Combine::InputThrowGrenadeAtTarget( inputdata_t &inputdata ) if ( m_NPCState == NPC_STATE_SCRIPT && m_hCine ) return; +#ifdef MAPBASE + CBaseEntity *pEntity = gEntList.FindEntityByName( NULL, inputdata.value.String(), this, inputdata.pActivator, inputdata.pCaller ); +#else CBaseEntity *pEntity = gEntList.FindEntityByName( NULL, inputdata.value.String(), NULL, inputdata.pActivator, inputdata.pCaller ); +#endif if ( !pEntity ) { DevMsg("%s (%s) received ThrowGrenadeAtTarget input, but couldn't find target entity '%s'\n", GetClassname(), GetDebugName(), inputdata.value.String() ); @@ -272,6 +348,66 @@ void CNPC_Combine::InputThrowGrenadeAtTarget( inputdata_t &inputdata ) ClearSchedule( "Told to throw grenade via input" ); } +#endif + +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Instant transformation of arsenal from grenades to energy balls, or vice versa +//----------------------------------------------------------------------------- +void CNPC_Combine::InputSetElite( inputdata_t &inputdata ) +{ + m_fIsElite = inputdata.value.Bool(); +} + +//----------------------------------------------------------------------------- +// We were told to drop a grenade +//----------------------------------------------------------------------------- +void CNPC_Combine::InputDropGrenade( inputdata_t &inputdata ) +{ + SetCondition( COND_COMBINE_DROP_GRENADE ); + + ClearSchedule( "Told to drop grenade via input" ); +} + +//----------------------------------------------------------------------------- +// Changes our tactical variant easily +//----------------------------------------------------------------------------- +void CNPC_Combine::InputSetTacticalVariant( inputdata_t &inputdata ) +{ + m_iTacticalVariant = inputdata.value.Int(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : &inputdata - +//----------------------------------------------------------------------------- +void CNPC_Combine::InputSetPoliceGoal( inputdata_t &inputdata ) +{ + if (/*!inputdata.value.String() ||*/ inputdata.value.String()[0] == 0) + { + m_PolicingBehavior.Disable(); + return; + } + + CBaseEntity *pGoal = gEntList.FindEntityByName( NULL, inputdata.value.String() ); + + if ( pGoal == NULL ) + { + DevMsg( "SetPoliceGoal: %s (%s) unable to find ai_goal_police: %s\n", GetClassname(), GetDebugName(), inputdata.value.String() ); + return; + } + + CAI_PoliceGoal *pPoliceGoal = dynamic_cast(pGoal); + + if ( pPoliceGoal == NULL ) + { + DevMsg( "SetPoliceGoal: %s (%s)'s target %s is not an ai_goal_police entity!\n", GetClassname(), GetDebugName(), inputdata.value.String() ); + return; + } + + m_PolicingBehavior.Enable( pPoliceGoal ); +} +#endif //----------------------------------------------------------------------------- // Purpose: @@ -283,7 +419,9 @@ void CNPC_Combine::Precache() PrecacheScriptSound( "NPC_Combine.GrenadeLaunch" ); PrecacheScriptSound( "NPC_Combine.WeaponBash" ); +#ifndef MAPBASE // Now that we use WeaponSound(SPECIAL1), this isn't necessary PrecacheScriptSound( "Weapon_CombineGuard.Special1" ); +#endif BaseClass::Precache(); } @@ -292,7 +430,9 @@ void CNPC_Combine::Precache() //----------------------------------------------------------------------------- void CNPC_Combine::Activate() { +#ifndef MAPBASE s_iszShotgunClassname = FindPooledString( "weapon_shotgun" ); +#endif BaseClass::Activate(); } @@ -350,6 +490,14 @@ void CNPC_Combine::Spawn( void ) m_flNextAltFireTime = gpGlobals->curtime; NPCInit(); + +#ifdef MAPBASE + // This was moved from CalcWeaponProficiency() so soldiers don't change skin unnaturally and uncontrollably + if ( GetActiveWeapon() && EntIsClass(GetActiveWeapon(), gm_isz_class_Shotgun) && m_nSkin != COMBINE_SKIN_SHOTGUNNER ) + { + m_nSkin = COMBINE_SKIN_SHOTGUNNER; + } +#endif } //----------------------------------------------------------------------------- @@ -364,6 +512,9 @@ bool CNPC_Combine::CreateBehaviors() AddBehavior( &m_StandoffBehavior ); AddBehavior( &m_FollowBehavior ); AddBehavior( &m_FuncTankBehavior ); +#ifdef MAPBASE + AddBehavior( &m_PolicingBehavior ); +#endif return BaseClass::CreateBehaviors(); } @@ -372,6 +523,7 @@ bool CNPC_Combine::CreateBehaviors() //----------------------------------------------------------------------------- void CNPC_Combine::PostNPCInit() { +#ifndef MAPBASE if( IsElite() ) { // Give a warning if a Combine Soldier is equipped with anything other than @@ -381,6 +533,7 @@ void CNPC_Combine::PostNPCInit() DevWarning("**Combine Elite Soldier MUST be equipped with AR2\n"); } } +#endif BaseClass::PostNPCInit(); } @@ -395,7 +548,12 @@ void CNPC_Combine::GatherConditions() if( GetState() == NPC_STATE_COMBAT ) { +#ifdef MAPBASE + // Don't override the standoff + if( IsCurSchedule( SCHED_COMBINE_WAIT_IN_COVER, false ) && !m_StandoffBehavior.IsActive() ) +#else if( IsCurSchedule( SCHED_COMBINE_WAIT_IN_COVER, false ) ) +#endif { // Soldiers that are standing around doing nothing poll for attack slots so // that they can respond quickly when one comes available. If they can @@ -428,7 +586,9 @@ void CNPC_Combine::PrescheduleThink() BaseClass::PrescheduleThink(); // Speak any queued sentences +#ifndef COMBINE_SOLDIER_USES_RESPONSE_SYSTEM m_Sentences.UpdateSentenceQueue(); +#endif if ( IsOnFire() ) { @@ -467,7 +627,7 @@ void CNPC_Combine::PrescheduleThink() } } - +#ifndef MAPBASE //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- void CNPC_Combine::DelayAltFireAttack( float flDelay ) @@ -494,7 +654,11 @@ void CNPC_Combine::DelaySquadAltFireAttack( float flDelay ) { CNPC_Combine *pCombine = dynamic_cast(pSquadmate); +#ifdef MAPBASE + if( pCombine && pCombine->IsAltFireCapable() ) +#else if( pCombine && pCombine->IsElite() ) +#endif { pCombine->DelayAltFireAttack( flDelay ); } @@ -502,6 +666,7 @@ void CNPC_Combine::DelaySquadAltFireAttack( float flDelay ) pSquadmate = m_pSquad->GetNextMember( &iter ); } } +#endif //----------------------------------------------------------------------------- // Purpose: degrees to turn in 0.1 seconds @@ -586,6 +751,25 @@ Class_T CNPC_Combine::Classify ( void ) return CLASS_COMBINE; } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: Function for gauging whether we're capable of alt-firing. +//----------------------------------------------------------------------------- +bool CNPC_Combine::IsAltFireCapable( void ) +{ + // The base class tells us if we're carrying an alt-fire-able weapon. + return (IsElite() || m_bAlternateCapable) && BaseClass::IsAltFireCapable(); +} + +//----------------------------------------------------------------------------- +// Purpose: Function for gauging whether we're capable of throwing grenades. +//----------------------------------------------------------------------------- +bool CNPC_Combine::IsGrenadeCapable( void ) +{ + return !IsElite() || m_bAlternateCapable; +} +#endif + //----------------------------------------------------------------------------- // Continuous movement tasks @@ -719,7 +903,6 @@ void CNPC_Combine::RunTaskChaseEnemyContinuously( const Task_t *pTask ) m_vSavePosition = pEnemy->WorldSpaceCenter(); } - //========================================================= // start task //========================================================= @@ -813,7 +996,11 @@ void CNPC_Combine::StartTask( const Task_t *pTask ) { m_flLastAttackTime = gpGlobals->curtime; +#ifdef COMBINE_SOLDIER_USES_RESPONSE_SYSTEM + SpeakIfAllowed( TLK_CMB_ANNOUNCE, SENTENCE_PRIORITY_HIGH ); +#else m_Sentences.Speak( "COMBINE_ANNOUNCE", SENTENCE_PRIORITY_HIGH ); +#endif // Wait two seconds SetWait( 2.0 ); @@ -837,7 +1024,11 @@ void CNPC_Combine::StartTask( const Task_t *pTask ) } else { +#ifdef COMBINE_SOLDIER_USES_RESPONSE_SYSTEM + SpeakIfAllowed( TLK_CMB_THROWGRENADE, SENTENCE_PRIORITY_MEDIUM ); +#else m_Sentences.Speak( "COMBINE_THROW_GRENADE", SENTENCE_PRIORITY_MEDIUM ); +#endif SetActivity(ACT_IDLE); // Wait two seconds @@ -857,6 +1048,9 @@ void CNPC_Combine::StartTask( const Task_t *pTask ) break; case TASK_COMBINE_GET_PATH_TO_FORCED_GREN_LOS: +#ifdef MAPBASE + StartTask_GetPathToForced(pTask); +#else { if ( !m_hForcedGrenadeTarget ) { @@ -895,6 +1089,7 @@ void CNPC_Combine::StartTask( const Task_t *pTask ) m_vInterruptSavePosition = posLos; } } +#endif break; case TASK_COMBINE_IGNORE_ATTACKS: @@ -912,6 +1107,9 @@ void CNPC_Combine::StartTask( const Task_t *pTask ) case TASK_COMBINE_DEFER_SQUAD_GRENADES: { +#ifdef MAPBASE + StartTask_DeferSquad(pTask); +#else if ( m_pSquad ) { // iterate my squad and stop everyone from throwing grenades for a little while. @@ -920,18 +1118,23 @@ void CNPC_Combine::StartTask( const Task_t *pTask ) CAI_BaseNPC *pSquadmate = m_pSquad ? m_pSquad->GetFirstMember( &iter ) : NULL; while ( pSquadmate ) { +#ifdef MAPBASE + pSquadmate->DelayGrenadeCheck(5); +#else CNPC_Combine *pCombine = dynamic_cast(pSquadmate); if( pCombine ) { pCombine->m_flNextGrenadeCheck = gpGlobals->curtime + 5; } +#endif pSquadmate = m_pSquad->GetNextMember( &iter ); } } TaskComplete(); +#endif break; } @@ -973,7 +1176,11 @@ void CNPC_Combine::StartTask( const Task_t *pTask ) m_pSquad->SquadRemember(bits_MEMORY_PLAYER_HURT); } +#ifdef COMBINE_SOLDIER_USES_RESPONSE_SYSTEM + SpeakIfAllowed( TLK_CMB_PLAYERHIT, SENTENCE_PRIORITY_INVALID ); +#else m_Sentences.Speak( "COMBINE_PLAYERHIT", SENTENCE_PRIORITY_INVALID ); +#endif JustMadeSound( SENTENCE_PRIORITY_HIGH ); } if ( pEntity->MyNPCPointer() ) @@ -992,6 +1199,15 @@ void CNPC_Combine::StartTask( const Task_t *pTask ) break; case TASK_RANGE_ATTACK1: { +#ifdef MAPBASE + // The game can crash if a soldier's weapon is removed while they're shooting + if (!GetActiveWeapon()) + { + TaskFail( "No weapon" ); + break; + } +#endif + m_nShots = GetActiveWeapon()->GetRandomBurst(); m_flShotDelay = GetActiveWeapon()->GetFireRate(); @@ -1079,6 +1295,19 @@ void CNPC_Combine::RunTask( const Task_t *pTask ) } break; +#ifdef MAPBASE + case TASK_COMBINE_PLAY_SEQUENCE_FACE_ALTFIRE_TARGET: + RunTask_FaceAltFireTarget(pTask); + break; + + case TASK_COMBINE_FACE_TOSS_DIR: + RunTask_FaceTossDir(pTask); + break; + + case TASK_COMBINE_GET_PATH_TO_FORCED_GREN_LOS: + RunTask_GetPathToForced(pTask); + break; +#else case TASK_COMBINE_PLAY_SEQUENCE_FACE_ALTFIRE_TARGET: GetMotor()->SetIdealYawToTargetAndUpdate( m_vecAltFireTarget, AI_KEEP_YAW_SPEED ); @@ -1124,6 +1353,7 @@ void CNPC_Combine::RunTask( const Task_t *pTask ) } } break; +#endif case TASK_RANGE_ATTACK1: { @@ -1239,7 +1469,11 @@ void CNPC_Combine::Event_Killed( const CTakeDamageInfo &info ) } // In the Citadel we need to dissolve this +#ifdef MAPBASE + if ( PlayerHasMegaPhysCannon() && GlobalEntity_GetCounter("super_phys_gun") != 1 ) +#else if ( PlayerHasMegaPhysCannon() ) +#endif { CBaseCombatWeapon *pWeapon = static_cast(pItem); @@ -1288,9 +1522,53 @@ void CNPC_Combine::BuildScheduleTestBits( void ) { SetCustomInterruptCondition( COND_COMBINE_ON_FIRE ); } + +#ifdef MAPBASE + if (npc_combine_new_cover_behavior.GetBool()) + { + if ( IsCurSchedule( SCHED_COMBINE_COMBAT_FAIL ) ) + { + SetCustomInterruptCondition( COND_NEW_ENEMY ); + SetCustomInterruptCondition( COND_LIGHT_DAMAGE ); + SetCustomInterruptCondition( COND_HEAVY_DAMAGE ); + } + else if ( IsCurSchedule( SCHED_COMBINE_MOVE_TO_MELEE ) ) + { + SetCustomInterruptCondition( COND_HEAR_DANGER ); + SetCustomInterruptCondition( COND_HEAR_MOVE_AWAY ); + } + } +#endif } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: +// Input : eNewActivity - +// Output : Activity +//----------------------------------------------------------------------------- +Activity CNPC_Combine::Weapon_TranslateActivity( Activity eNewActivity, bool *pRequired ) +{ + return BaseClass::Weapon_TranslateActivity(eNewActivity, pRequired); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +Activity CNPC_Combine::NPC_BackupActivity( Activity eNewActivity ) +{ + // Some models might not contain ACT_COMBINE_BUGBAIT, which the soldier model uses instead of ACT_IDLE_ON_FIRE. + // Contrariwise, soldiers may be called to use ACT_IDLE_ON_FIRE in other parts of the AI and need to translate to ACT_COMBINE_BUGBAIT. + if (eNewActivity == ACT_COMBINE_BUGBAIT) + return ACT_IDLE_ON_FIRE; + else if (eNewActivity == ACT_IDLE_ON_FIRE) + return ACT_COMBINE_BUGBAIT; + + return BaseClass::NPC_BackupActivity( eNewActivity ); +} +#endif + //----------------------------------------------------------------------------- // Purpose: Translate base class activities into combot activites //----------------------------------------------------------------------------- @@ -1302,6 +1580,7 @@ Activity CNPC_Combine::NPC_TranslateActivity( Activity eNewActivity ) if (eNewActivity == ACT_RANGE_ATTACK2) { +#ifndef MAPBASE // grunt is going to a secondary long range attack. This may be a thrown // grenade or fired grenade, we must determine which and pick proper sequence if (Weapon_OwnsThisType( "weapon_grenadelauncher" ) ) @@ -1309,8 +1588,19 @@ Activity CNPC_Combine::NPC_TranslateActivity( Activity eNewActivity ) return ( Activity )ACT_COMBINE_LAUNCH_GRENADE; } else +#else + if (m_bUnderthrow) { + return ACT_SPECIAL_ATTACK1; + } + else +#endif + { +#if SHARED_COMBINE_ACTIVITIES + return ACT_COMBINE_THROW_GRENADE; +#else return ( Activity )ACT_COMBINE_THROW_GRENADE; +#endif } } else if (eNewActivity == ACT_IDLE) @@ -1338,6 +1628,35 @@ Activity CNPC_Combine::NPC_TranslateActivity( Activity eNewActivity ) break; } } +#ifdef MAPBASE + else if (!GetActiveWeapon() && !npc_combine_unarmed_anims.GetBool()) + { + if (eNewActivity == ACT_IDLE || eNewActivity == ACT_IDLE_ANGRY) + eNewActivity = ACT_IDLE_SMG1; + else if (eNewActivity == ACT_WALK) + eNewActivity = ACT_WALK_RIFLE; + else if (eNewActivity == ACT_RUN) + eNewActivity = ACT_RUN_RIFLE; + } + else if (m_NPCState == NPC_STATE_IDLE && eNewActivity == ACT_WALK) + { + if (npc_combine_idle_walk_easy.GetBool()) + { + // ACT_WALK_EASY has been replaced with ACT_WALK_RELAXED for weapon translation purposes + eNewActivity = ACT_WALK_RELAXED; + } + else if (GetActiveWeapon()) + { + eNewActivity = ACT_WALK_RIFLE; + } + } + + if ( eNewActivity == ACT_RUN && ( IsCurSchedule( SCHED_TAKE_COVER_FROM_BEST_SOUND ) || IsCurSchedule( SCHED_FLEE_FROM_BEST_SOUND ) ) ) + { + if ( random->RandomInt( 0, 1 ) && npc_combine_protected_run.GetBool() && HaveSequenceForActivity( ACT_RUN_PROTECTED ) ) + eNewActivity = ACT_RUN_PROTECTED; + } +#endif return BaseClass::NPC_TranslateActivity( eNewActivity ); } @@ -1399,13 +1718,20 @@ void CNPC_Combine::AnnounceAssault(void) // Make sure player can see me if ( FVisible( pBCC ) ) { +#ifdef COMBINE_SOLDIER_USES_RESPONSE_SYSTEM + SpeakIfAllowed( TLK_CMB_ASSAULT ); +#else m_Sentences.Speak( "COMBINE_ASSAULT" ); +#endif } } void CNPC_Combine::AnnounceEnemyType( CBaseEntity *pEnemy ) { +#ifdef COMBINE_SOLDIER_USES_RESPONSE_SYSTEM + SpeakIfAllowed( TLK_CMB_ENEMY, SENTENCE_PRIORITY_HIGH ); +#else const char *pSentenceName = "COMBINE_MONST"; switch ( pEnemy->Classify() ) { @@ -1439,6 +1765,7 @@ void CNPC_Combine::AnnounceEnemyType( CBaseEntity *pEnemy ) } m_Sentences.Speak( pSentenceName, SENTENCE_PRIORITY_HIGH ); +#endif } void CNPC_Combine::AnnounceEnemyKill( CBaseEntity *pEnemy ) @@ -1446,6 +1773,11 @@ void CNPC_Combine::AnnounceEnemyKill( CBaseEntity *pEnemy ) if (!pEnemy ) return; +#ifdef COMBINE_SOLDIER_USES_RESPONSE_SYSTEM + AI_CriteriaSet set; + ModifyOrAppendEnemyCriteria(set, pEnemy); + SpeakIfAllowed( TLK_CMB_KILLENEMY, set, SENTENCE_PRIORITY_HIGH ); +#else const char *pSentenceName = "COMBINE_KILL_MONST"; switch ( pEnemy->Classify() ) { @@ -1475,6 +1807,7 @@ void CNPC_Combine::AnnounceEnemyKill( CBaseEntity *pEnemy ) } m_Sentences.Speak( pSentenceName, SENTENCE_PRIORITY_HIGH ); +#endif } //----------------------------------------------------------------------------- @@ -1705,6 +2038,9 @@ int CNPC_Combine::SelectSchedule( void ) { Vector vecTarget = m_hForcedGrenadeTarget->WorldSpaceCenter(); +#ifdef MAPBASE + // This was switched to IsAltFireCapable() before, but m_bAlternateCapable makes it necessary to use IsElite() again. +#endif if ( IsElite() ) { if ( FVisible( m_hForcedGrenadeTarget ) ) @@ -1733,6 +2069,12 @@ int CNPC_Combine::SelectSchedule( void ) } } +#ifdef MAPBASE + // Drop a grenade? + if ( HasCondition( COND_COMBINE_DROP_GRENADE ) ) + return SCHED_COMBINE_DROP_GRENADE; +#endif + if ( m_NPCState != NPC_STATE_SCRIPT) { // If we're hit by bugbait, thrash around @@ -1776,7 +2118,11 @@ int CNPC_Combine::SelectSchedule( void ) { // I hear something dangerous, probably need to take cover. // dangerous sound nearby!, call it out +#ifndef COMBINE_SOLDIER_USES_RESPONSE_SYSTEM const char *pSentenceName = "COMBINE_DANGER"; +#else + bool bGrenade = false; +#endif CBaseEntity *pSoundOwner = pSound->m_hOwner; if ( pSoundOwner ) @@ -1787,12 +2133,20 @@ int CNPC_Combine::SelectSchedule( void ) if ( IRelationType( pGrenade->GetThrower() ) != D_LI ) { // special case call out for enemy grenades +#ifndef COMBINE_SOLDIER_USES_RESPONSE_SYSTEM pSentenceName = "COMBINE_GREN"; +#else + bGrenade = true; +#endif } } } +#ifdef COMBINE_SOLDIER_USES_RESPONSE_SYSTEM + SpeakIfAllowed( TLK_CMB_DANGER, UTIL_VarArgs( "grenade:%d", bGrenade ) ); +#else m_Sentences.Speak( pSentenceName, SENTENCE_PRIORITY_NORMAL, SENTENCE_CRITERIA_NORMAL ); +#endif // If the sound is approaching danger, I have no enemy, and I don't see it, turn to face. if( !GetEnemy() && pSound->IsSoundType(SOUND_CONTEXT_DANGER_APPROACH) && pSound->m_hOwner && !FInViewCone(pSound->GetSoundReactOrigin()) ) @@ -1881,7 +2235,12 @@ int CNPC_Combine::SelectFailSchedule( int failedSchedule, int failedTask, AI_Tas { if( failedSchedule == SCHED_COMBINE_TAKE_COVER1 ) { +#ifdef MAPBASE + if( IsInSquad() && IsStrategySlotRangeOccupied(SQUAD_SLOT_ATTACK1, SQUAD_SLOT_ATTACK2) && HasCondition(COND_SEE_ENEMY) + && ( !npc_combine_new_cover_behavior.GetBool() || (taskFailCode == FAIL_NO_COVER) ) ) +#else if( IsInSquad() && IsStrategySlotRangeOccupied(SQUAD_SLOT_ATTACK1, SQUAD_SLOT_ATTACK2) && HasCondition(COND_SEE_ENEMY) ) +#endif { // This eases the effects of an unfortunate bug that usually plagues shotgunners. Since their rate of fire is low, // they spend relatively long periods of time without an attack squad slot. If you corner a shotgunner, usually @@ -1912,9 +2271,11 @@ bool CNPC_Combine::ShouldChargePlayer() int CNPC_Combine::SelectScheduleAttack() { +#ifndef MAPBASE // Moved to SelectSchedule() // Drop a grenade? if ( HasCondition( COND_COMBINE_DROP_GRENADE ) ) return SCHED_COMBINE_DROP_GRENADE; +#endif // Kick attack? if ( HasCondition( COND_CAN_MELEE_ATTACK1 ) ) @@ -1924,7 +2285,11 @@ int CNPC_Combine::SelectScheduleAttack() // If I'm fighting a combine turret (it's been hacked to attack me), I can't really // hurt it with bullets, so become grenade happy. +#ifdef MAPBASE + if ( GetEnemy() && ( (IsUsingTacticalVariant(TACTICAL_VARIANT_GRENADE_HAPPY)) || GetEnemy()->ClassMatches(gm_isz_class_FloorTurret) ) ) +#else if ( GetEnemy() && GetEnemy()->Classify() == CLASS_COMBINE && FClassnameIs(GetEnemy(), "npc_turret_floor") ) +#endif { // Don't do this until I've been fighting the turret for a few seconds float flTimeAtFirstHand = GetEnemies()->TimeAtFirstHand(GetEnemy()); @@ -1940,7 +2305,12 @@ int CNPC_Combine::SelectScheduleAttack() // If we're not in the viewcone of the turret, run up and hit it. Do this a bit later to // give other squadmembers a chance to throw a grenade before I run in. +#ifdef MAPBASE + // Don't do turret charging of we're just grenade happy. + if ( !IsUsingTacticalVariant(TACTICAL_VARIANT_GRENADE_HAPPY) && !GetEnemy()->MyNPCPointer()->FInViewCone( this ) && OccupyStrategySlot( SQUAD_SLOT_GRENADE1 ) ) +#else if ( !GetEnemy()->MyNPCPointer()->FInViewCone( this ) && OccupyStrategySlot( SQUAD_SLOT_GRENADE1 ) ) +#endif return SCHED_COMBINE_CHARGE_TURRET; } @@ -2058,7 +2428,11 @@ int CNPC_Combine::TranslateSchedule( int scheduleType ) HasCondition(COND_CAN_RANGE_ATTACK2) && OccupyStrategySlot( SQUAD_SLOT_GRENADE1 ) ) { +#ifdef COMBINE_SOLDIER_USES_RESPONSE_SYSTEM + SpeakIfAllowed( TLK_CMB_THROWGRENADE ); +#else m_Sentences.Speak( "COMBINE_THROW_GRENADE" ); +#endif return SCHED_COMBINE_TOSS_GRENADE_COVER1; } else @@ -2097,6 +2471,13 @@ int CNPC_Combine::TranslateSchedule( int scheduleType ) return TranslateSchedule( SCHED_RANGE_ATTACK1 ); } +#ifdef MAPBASE + if ( npc_combine_new_cover_behavior.GetBool() && HasCondition( COND_CAN_RANGE_ATTACK2 ) && OccupyStrategySlot( SQUAD_SLOT_GRENADE1 ) ) + { + return TranslateSchedule( SCHED_RANGE_ATTACK2 ); + } +#endif + // Run somewhere randomly return TranslateSchedule( SCHED_FAIL ); break; @@ -2237,6 +2618,12 @@ int CNPC_Combine::TranslateSchedule( int scheduleType ) Stand(); } +#ifdef MAPBASE + // SCHED_COMBINE_WAIT_IN_COVER uses INCOVER, but only gets out of it when the soldier moves. + // That seems to mess up shooting, so this Forget() attempts to fix that. + Forget( bits_MEMORY_INCOVER ); +#endif + return SCHED_COMBINE_RANGE_ATTACK1; } case SCHED_RANGE_ATTACK2: @@ -2320,12 +2707,24 @@ void CNPC_Combine::HandleAnimEvent( animevent_t *pEvent ) { if ( pEvent->event == COMBINE_AE_BEGIN_ALTFIRE ) { +#ifdef MAPBASE + if (GetActiveWeapon()) + GetActiveWeapon()->WeaponSound(SPECIAL1); +#else EmitSound( "Weapon_CombineGuard.Special1" ); +#endif +#ifdef COMBINE_SOLDIER_USES_RESPONSE_SYSTEM + SpeakIfAllowed( TLK_CMB_THROWGRENADE, "altfire:1", SENTENCE_PRIORITY_MEDIUM ); +#endif handledEvent = true; } else if ( pEvent->event == COMBINE_AE_ALTFIRE ) { - if( IsElite() ) +#ifdef MAPBASE + if ( IsAltFireCapable() && GetActiveWeapon() ) +#else + if ( IsElite() ) +#endif { animevent_t fakeEvent; @@ -2343,7 +2742,15 @@ void CNPC_Combine::HandleAnimEvent( animevent_t *pEvent ) // that makes sure the elite has grenades in order to fire a combine ball, we // preserve the legacy behavior while making it possible for a designer to prevent // elites from shooting combine balls by setting grenades to '0' in hammer. (sjb) EP2_OUTLAND_10 +#ifdef MAPBASE + // + // Here's a tip: In Mapbase, "OnThrowGrenade" is fired during alt-fire as well, fired by the weapon so it could pass its alt-fire projectile. + // So if you want elites to decrement on each grenade again, you could fire "!self > AddGrenades -1" every time an elite fires OnThrowGrenade. + // + // AddGrenades(-1); +#else // m_iNumGrenades--; +#endif } handledEvent = true; @@ -2367,8 +2774,12 @@ void CNPC_Combine::HandleAnimEvent( animevent_t *pEvent ) // We never actually run out of ammo, just need to refill the clip if (GetActiveWeapon()) { +#ifdef MAPBASE + GetActiveWeapon()->Reload_NPC(); +#else GetActiveWeapon()->WeaponSound( RELOAD_NPC ); GetActiveWeapon()->m_iClip1 = GetActiveWeapon()->GetMaxClip1(); +#endif GetActiveWeapon()->m_iClip2 = GetActiveWeapon()->GetMaxClip2(); } ClearCondition(COND_LOW_PRIMARY_AMMO); @@ -2395,13 +2806,24 @@ void CNPC_Combine::HandleAnimEvent( animevent_t *pEvent ) GetVectors( &forward, NULL, &up ); vecThrow = forward * 750 + up * 175; +#ifdef MAPBASE + CBaseEntity *pGrenade = Fraggrenade_Create( vecStart, vec3_angle, vecThrow, vecSpin, this, COMBINE_GRENADE_TIMER, true ); + m_OnThrowGrenade.Set(pGrenade, pGrenade, this); +#else Fraggrenade_Create( vecStart, vec3_angle, vecThrow, vecSpin, this, COMBINE_GRENADE_TIMER, true ); +#endif } else { // Use the Velocity that AI gave us. +#ifdef MAPBASE + CBaseEntity *pGrenade = Fraggrenade_Create( vecStart, vec3_angle, m_vecTossVelocity, vecSpin, this, COMBINE_GRENADE_TIMER, true ); + m_OnThrowGrenade.Set(pGrenade, pGrenade, this); + AddGrenades(-1, pGrenade); +#else Fraggrenade_Create( vecStart, vec3_angle, m_vecTossVelocity, vecSpin, this, COMBINE_GRENADE_TIMER, true ); m_iNumGrenades--; +#endif } // wait six seconds before even looking again to see if a grenade can be thrown. @@ -2429,10 +2851,34 @@ void CNPC_Combine::HandleAnimEvent( animevent_t *pEvent ) case COMBINE_AE_GREN_DROP: { Vector vecStart; +#ifdef MAPBASE + QAngle angStart; + m_vecTossVelocity.x = 15; + m_vecTossVelocity.y = 0; + m_vecTossVelocity.z = 0; + + GetAttachment( "lefthand", vecStart, angStart ); + + CBaseEntity *pGrenade = NULL; + if (m_NPCState == NPC_STATE_SCRIPT) + { + // While scripting, have the grenade face upwards like it was originally and also don't decrement grenade count. + pGrenade = Fraggrenade_Create( vecStart, vec3_angle, m_vecTossVelocity, vec3_origin, this, COMBINE_GRENADE_TIMER, true ); + } + else + { + pGrenade = Fraggrenade_Create( vecStart, angStart, m_vecTossVelocity, vec3_origin, this, COMBINE_GRENADE_TIMER, true ); + AddGrenades(-1); + } + + // Well, technically we're not throwing, but...still. + m_OnThrowGrenade.Set(pGrenade, pGrenade, this); +#else GetAttachment( "lefthand", vecStart ); Fraggrenade_Create( vecStart, vec3_angle, m_vecTossVelocity, vec3_origin, this, COMBINE_GRENADE_TIMER, true ); m_iNumGrenades--; +#endif } handledEvent = true; break; @@ -2463,13 +2909,21 @@ void CNPC_Combine::HandleAnimEvent( animevent_t *pEvent ) } } +#ifdef COMBINE_SOLDIER_USES_RESPONSE_SYSTEM + SpeakIfAllowed( TLK_CMB_KICK ); +#else m_Sentences.Speak( "COMBINE_KICK" ); +#endif handledEvent = true; break; } case COMBINE_AE_CAUGHT_ENEMY: +#ifdef COMBINE_SOLDIER_USES_RESPONSE_SYSTEM + SpeakIfAllowed( TLK_CMB_ENEMY ); //SpeakIfAllowed( "TLK_CMB_ALERT" ); +#else m_Sentences.Speak( "COMBINE_ALERT" ); +#endif handledEvent = true; break; @@ -2507,6 +2961,28 @@ Vector CNPC_Combine::Weapon_ShootPosition( ) // FIXME: rename this "estimated" since it's not based on animation // FIXME: the orientation won't be correct when testing from arbitary positions for arbitary angles +#ifdef MAPBASE + // HACKHACK: This weapon shoot position code does not work properly when in close range, causing the aim + // to drift to the left as the enemy gets closer to it. + // This problem is usually bearable for regular combat, but it causes dynamic interaction yaw to be offset + // as well, preventing most from ever being triggered. + // Ideally, this should be fixed from the root cause, but due to the sensitivity of such a change, this is + // currently being tied to a cvar which is off by default. + // + // If the cvar is disabled but the soldier has valid interactions on its current enemy, then a separate hack + // will still attempt to correct the drift as the enemy gets closer. + if ( npc_combine_fixed_shootpos.GetBool() ) + { + right *= 0.0f; + } + else if ( HasValidInteractionsOnCurrentEnemy() ) + { + float flDistSqr = GetEnemy()->WorldSpaceCenter().DistToSqr( WorldSpaceCenter() ); + if (flDistSqr < Square( 128.0f )) + right *= (flDistSqr / Square( 128.0f )); + } +#endif + if ( bStanding ) { if( HasShotgun() ) @@ -2556,16 +3032,82 @@ void CNPC_Combine::SpeakSentence( int sentenceType ) // If I'm moving more than 20ft, I need to talk about it if ( GetNavigator()->GetPath()->GetPathLength() > 20 * 12.0f ) { +#ifdef COMBINE_SOLDIER_USES_RESPONSE_SYSTEM + SpeakIfAllowed( TLK_CMB_FLANK ); +#else m_Sentences.Speak( "COMBINE_FLANK" ); +#endif } break; } } +#ifdef COMBINE_SOLDIER_USES_RESPONSE_SYSTEM +//========================================================= +bool CNPC_Combine::SpeakIfAllowed( const char *concept, const char *modifiers, SentencePriority_t sentencepriority, SentenceCriteria_t sentencecriteria ) +{ + AI_CriteriaSet set; + if (modifiers) + { +#ifdef NEW_RESPONSE_SYSTEM + GatherCriteria( &set, concept, modifiers ); +#else + GetExpresser()->MergeModifiers(set, modifiers); +#endif + } + return SpeakIfAllowed( concept, set, sentencepriority, sentencecriteria ); +} + +//========================================================= +//========================================================= +bool CNPC_Combine::SpeakIfAllowed( const char *concept, AI_CriteriaSet& modifiers, SentencePriority_t sentencepriority, SentenceCriteria_t sentencecriteria ) +{ + if ( sentencepriority != SENTENCE_PRIORITY_INVALID && !FOkToMakeSound( sentencepriority ) ) + return false; + + if ( !GetExpresser()->CanSpeakConcept( concept ) ) + return false; + + // Don't interrupt scripted VCD dialogue + if ( IsRunningScriptedSceneWithSpeechAndNotPaused( this, true ) ) + return false; + + if ( Speak( concept, modifiers ) ) + { + JustMadeSound( sentencepriority, 2.0f /*GetTimeSpeechComplete()*/ ); + return true; + } + + return false; +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CNPC_Combine::ModifyOrAppendCriteria( AI_CriteriaSet& set ) +{ + BaseClass::ModifyOrAppendCriteria( set ); + + set.AppendCriteria( "numgrenades", UTIL_VarArgs("%d", m_iNumGrenades) ); + + if (IsElite()) + { + set.AppendCriteria( "elite", "1" ); + } + else + { + set.AppendCriteria( "elite", "0" ); + } +} +#endif + //========================================================= // PainSound //========================================================= +#ifdef MAPBASE +void CNPC_Combine::PainSound ( const CTakeDamageInfo &info ) +#else void CNPC_Combine::PainSound ( void ) +#endif { // NOTE: The response system deals with this at the moment if ( GetFlags() & FL_DISSOLVING ) @@ -2573,6 +3115,11 @@ void CNPC_Combine::PainSound ( void ) if ( gpGlobals->curtime > m_flNextPainSoundTime ) { +#ifdef COMBINE_SOLDIER_USES_RESPONSE_SYSTEM + AI_CriteriaSet set; + ModifyOrAppendDamageCriteria(set, info); + SpeakIfAllowed( TLK_CMB_PAIN, set, SENTENCE_PRIORITY_INVALID, SENTENCE_CRITERIA_ALWAYS ); +#else const char *pSentenceName = "COMBINE_PAIN"; float healthRatio = (float)GetHealth() / (float)GetMaxHealth(); if ( !HasMemory(bits_MEMORY_PAIN_LIGHT_SOUND) && healthRatio > 0.9 ) @@ -2587,6 +3134,7 @@ void CNPC_Combine::PainSound ( void ) } m_Sentences.Speak( pSentenceName, SENTENCE_PRIORITY_INVALID, SENTENCE_CRITERIA_ALWAYS ); +#endif m_flNextPainSoundTime = gpGlobals->curtime + 1; } } @@ -2597,11 +3145,26 @@ void CNPC_Combine::PainSound ( void ) // Input : // Output : //----------------------------------------------------------------------------- +#ifdef MAPBASE +void CNPC_Combine::LostEnemySound( CBaseEntity *pEnemy ) +#else void CNPC_Combine::LostEnemySound( void) +#endif { if ( gpGlobals->curtime <= m_flNextLostSoundTime ) return; +#ifdef COMBINE_SOLDIER_USES_RESPONSE_SYSTEM + AI_CriteriaSet modifiers; + ModifyOrAppendEnemyCriteria( modifiers, pEnemy ); + + modifiers.AppendCriteria( "lastseenenemy", gpGlobals->curtime - GetEnemies()->LastTimeSeen( pEnemy ) ); + + if (SpeakIfAllowed( TLK_CMB_LOSTENEMY, modifiers )) + { + m_flNextLostSoundTime = gpGlobals->curtime + random->RandomFloat(5.0,15.0); + } +#else const char *pSentence; if (!(CBaseEntity*)GetEnemy() || gpGlobals->curtime - GetEnemyLastTimeSeen() > 10) { @@ -2616,6 +3179,7 @@ void CNPC_Combine::LostEnemySound( void) { m_flNextLostSoundTime = gpGlobals->curtime + random->RandomFloat(5.0,15.0); } +#endif } //----------------------------------------------------------------------------- @@ -2624,9 +3188,20 @@ void CNPC_Combine::LostEnemySound( void) // Input : // Output : //----------------------------------------------------------------------------- +#ifdef MAPBASE +void CNPC_Combine::FoundEnemySound( CBaseEntity *pEnemy ) +#else void CNPC_Combine::FoundEnemySound( void) +#endif { +#ifdef COMBINE_SOLDIER_USES_RESPONSE_SYSTEM + AI_CriteriaSet modifiers; + ModifyOrAppendEnemyCriteria( modifiers, pEnemy ); + + SpeakIfAllowed( TLK_CMB_REFINDENEMY, modifiers, SENTENCE_PRIORITY_HIGH ); +#else m_Sentences.Speak( "COMBINE_REFIND_ENEMY", SENTENCE_PRIORITY_HIGH ); +#endif } //----------------------------------------------------------------------------- @@ -2641,7 +3216,11 @@ void CNPC_Combine::AlertSound( void) { if ( gpGlobals->curtime > m_flNextAlertSoundTime ) { +#ifdef COMBINE_SOLDIER_USES_RESPONSE_SYSTEM + SpeakIfAllowed( TLK_CMB_GOALERT, SENTENCE_PRIORITY_HIGH ); +#else m_Sentences.Speak( "COMBINE_GO_ALERT", SENTENCE_PRIORITY_HIGH ); +#endif m_flNextAlertSoundTime = gpGlobals->curtime + 10.0f; } } @@ -2651,16 +3230,22 @@ void CNPC_Combine::AlertSound( void) //========================================================= void CNPC_Combine::NotifyDeadFriend ( CBaseEntity* pFriend ) { +#ifndef COMBINE_SOLDIER_USES_RESPONSE_SYSTEM if ( GetSquad()->NumMembers() < 2 ) { m_Sentences.Speak( "COMBINE_LAST_OF_SQUAD", SENTENCE_PRIORITY_INVALID, SENTENCE_CRITERIA_NORMAL ); JustMadeSound(); return; } +#endif // relaxed visibility test so that guys say this more often //if( FInViewCone( pFriend ) && FVisible( pFriend ) ) { +#ifdef COMBINE_SOLDIER_USES_RESPONSE_SYSTEM + SpeakIfAllowed( TLK_CMB_MANDOWN ); +#else m_Sentences.Speak( "COMBINE_MAN_DOWN" ); +#endif } BaseClass::NotifyDeadFriend(pFriend); } @@ -2668,13 +3253,23 @@ void CNPC_Combine::NotifyDeadFriend ( CBaseEntity* pFriend ) //========================================================= // DeathSound //========================================================= +#ifdef MAPBASE +void CNPC_Combine::DeathSound ( const CTakeDamageInfo &info ) +#else void CNPC_Combine::DeathSound ( void ) +#endif { +#ifdef COMBINE_SOLDIER_USES_RESPONSE_SYSTEM + AI_CriteriaSet set; + ModifyOrAppendDamageCriteria(set, info); + SpeakIfAllowed(TLK_CMB_DIE, set, SENTENCE_PRIORITY_INVALID, SENTENCE_CRITERIA_ALWAYS); +#else // NOTE: The response system deals with this at the moment if ( GetFlags() & FL_DISSOLVING ) return; m_Sentences.Speak( "COMBINE_DIE", SENTENCE_PRIORITY_INVALID, SENTENCE_CRITERIA_ALWAYS ); +#endif } //========================================================= @@ -2686,6 +3281,11 @@ void CNPC_Combine::IdleSound( void ) { if (!g_fCombineQuestion) { +#ifdef COMBINE_SOLDIER_USES_RESPONSE_SYSTEM + int iRandom = random->RandomInt(0, 2); + SpeakIfAllowed( TLK_CMB_QUESTION, UTIL_VarArgs("combinequestion:%d", iRandom) ); + g_fCombineQuestion = iRandom + 1; +#else // ask question or make statement switch (random->RandomInt(0,2)) { @@ -2707,9 +3307,14 @@ void CNPC_Combine::IdleSound( void ) m_Sentences.Speak( "COMBINE_IDLE" ); break; } +#endif } else { +#ifdef COMBINE_SOLDIER_USES_RESPONSE_SYSTEM + SpeakIfAllowed( TLK_CMB_ANSWER, UTIL_VarArgs("combinequestion:%d", g_fCombineQuestion) ); + g_fCombineQuestion = 0; +#else switch (g_fCombineQuestion) { case 1: // check in @@ -2725,6 +3330,7 @@ void CNPC_Combine::IdleSound( void ) } break; } +#endif } } } @@ -2744,6 +3350,7 @@ int CNPC_Combine::RangeAttack2Conditions( float flDot, float flDist ) return COND_NONE; } +#ifndef MAPBASE //----------------------------------------------------------------------------- // Purpose: Return true if the combine has grenades, hasn't checked lately, and // can throw a grenade at the target point. @@ -2858,12 +3465,17 @@ bool CNPC_Combine::CheckCanThrowGrenade( const Vector &vecTarget ) return false; } } +#endif //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- bool CNPC_Combine::CanAltFireEnemy( bool bUseFreeKnowledge ) { - if (!IsElite() ) +#ifdef MAPBASE + if ( !IsAltFireCapable() ) +#else + if ( !IsElite() ) +#endif return false; if (IsCrouching()) @@ -2884,7 +3496,14 @@ bool CNPC_Combine::CanAltFireEnemy( bool bUseFreeKnowledge ) CBaseEntity *pEnemy = GetEnemy(); +#ifdef MAPBASE + // "Our weapons alone cannot take down the antlion guard!" + // "Wait, you're an elite, don't you have, like, disintegration balls or somethi--" + // "SHUT UP!" + if ( !npc_combine_altfire_not_allies_only.GetBool() && !pEnemy->IsPlayer() && (!pEnemy->IsNPC() || !pEnemy->MyNPCPointer()->IsPlayerAlly()) ) +#else if( !pEnemy->IsPlayer() && (!pEnemy->IsNPC() || !pEnemy->MyNPCPointer()->IsPlayerAlly()) ) +#endif return false; Vector vecTarget; @@ -2913,14 +3532,23 @@ bool CNPC_Combine::CanAltFireEnemy( bool bUseFreeKnowledge ) } // Trace a hull about the size of the combine ball. +#ifdef MAPBASE + UTIL_TraceHull( vShootPosition, vecTarget, mins, maxs, MASK_COMBINE_BALL_LOS, this, COLLISION_GROUP_NONE, &tr ); +#else UTIL_TraceHull( vShootPosition, vecTarget, mins, maxs, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr ); +#endif float flLength = (vShootPosition - vecTarget).Length(); flLength *= tr.fraction; //If the ball can travel at least 65% of the distance to the player then let the NPC shoot it. +#ifdef MAPBASE + // (unless it hit the world) + if( tr.fraction >= 0.65 && (!tr.m_pEnt || !tr.m_pEnt->IsWorld()) && flLength > 128.0f ) +#else if( tr.fraction >= 0.65 && flLength > 128.0f ) +#endif { // Target is valid m_vecAltFireTarget = vecTarget; @@ -2938,7 +3566,11 @@ bool CNPC_Combine::CanAltFireEnemy( bool bUseFreeKnowledge ) //----------------------------------------------------------------------------- bool CNPC_Combine::CanGrenadeEnemy( bool bUseFreeKnowledge ) { - if( IsElite() ) +#ifdef MAPBASE + if ( !IsGrenadeCapable() ) +#else + if ( IsElite() ) +#endif return false; CBaseEntity *pEnemy = GetEnemy(); @@ -3029,6 +3661,7 @@ Vector CNPC_Combine::EyePosition( void ) */ } +#ifndef MAPBASE //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- Vector CNPC_Combine::GetAltFireTarget() @@ -3037,6 +3670,7 @@ Vector CNPC_Combine::GetAltFireTarget() return m_vecAltFireTarget; } +#endif //----------------------------------------------------------------------------- // Purpose: @@ -3070,13 +3704,46 @@ Vector CNPC_Combine::GetCrouchEyeOffset( void ) return COMBINE_EYE_CROUCHING_POSITION; } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CNPC_Combine::IsCrouchedActivity( Activity activity ) +{ + if (BaseClass::IsCrouchedActivity( activity )) + return true; + + Activity realActivity = TranslateActivity(activity); + + // Soldiers need to consider these crouched activities, but not all NPCs should. + switch ( realActivity ) + { + case ACT_RANGE_AIM_LOW: + case ACT_RANGE_AIM_AR2_LOW: + case ACT_RANGE_AIM_SMG1_LOW: + case ACT_RANGE_AIM_PISTOL_LOW: + case ACT_RANGE_ATTACK1_LOW: + case ACT_RANGE_ATTACK_AR2_LOW: + case ACT_RANGE_ATTACK_SMG1_LOW: + case ACT_RANGE_ATTACK_SHOTGUN_LOW: + case ACT_RANGE_ATTACK_PISTOL_LOW: + case ACT_RANGE_ATTACK2_LOW: + return true; + } + + return false; +} +#endif + //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- void CNPC_Combine::SetActivity( Activity NewActivity ) { BaseClass::SetActivity( NewActivity ); +#ifndef MAPBASE // CAI_GrenadeUser m_iLastAnimEventHandled = -1; +#endif } //----------------------------------------------------------------------------- @@ -3138,7 +3805,11 @@ void CNPC_Combine::OnEndMoveAndShoot() //----------------------------------------------------------------------------- WeaponProficiency_t CNPC_Combine::CalcWeaponProficiency( CBaseCombatWeapon *pWeapon ) { +#ifdef MAPBASE + if( pWeapon->ClassMatches( gm_isz_class_AR2 ) ) +#else if( FClassnameIs( pWeapon, "weapon_ar2" ) ) +#endif { if( hl2_episodic.GetBool() ) { @@ -3149,19 +3820,36 @@ WeaponProficiency_t CNPC_Combine::CalcWeaponProficiency( CBaseCombatWeapon *pWea return WEAPON_PROFICIENCY_GOOD; } } +#ifdef MAPBASE + else if( pWeapon->ClassMatches( gm_isz_class_Shotgun ) ) +#else else if( FClassnameIs( pWeapon, "weapon_shotgun" ) ) +#endif { +#ifndef MAPBASE // Moved so soldiers don't change skin unnaturally and uncontrollably if( m_nSkin != COMBINE_SKIN_SHOTGUNNER ) { m_nSkin = COMBINE_SKIN_SHOTGUNNER; } +#endif return WEAPON_PROFICIENCY_PERFECT; } +#ifdef MAPBASE + else if( pWeapon->ClassMatches( gm_isz_class_SMG1 ) ) +#else else if( FClassnameIs( pWeapon, "weapon_smg1" ) ) +#endif { return WEAPON_PROFICIENCY_GOOD; } +#ifdef MAPBASE + else if ( pWeapon->ClassMatches( gm_isz_class_Pistol ) ) + { + // Mods which need a lower soldier pistol accuracy can either change this value or use proficiency override in Hammer. + return WEAPON_PROFICIENCY_VERY_GOOD; + } +#endif return BaseClass::CalcWeaponProficiency( pWeapon ); } @@ -3277,7 +3965,12 @@ bool CNPC_Combine::IsRunningApproachEnemySchedule() bool CNPC_Combine::ShouldPickADeathPose( void ) { +#ifdef MAPBASE + // Check base class as well + return !IsCrouching() && BaseClass::ShouldPickADeathPose(); +#else return !IsCrouching(); +#endif } //----------------------------------------------------------------------------- @@ -3300,12 +3993,21 @@ DECLARE_TASK( TASK_COMBINE_GET_PATH_TO_FORCED_GREN_LOS ) DECLARE_TASK( TASK_COMBINE_SET_STANDING ) //Activities +#if !SHARED_COMBINE_ACTIVITIES DECLARE_ACTIVITY( ACT_COMBINE_THROW_GRENADE ) +#endif DECLARE_ACTIVITY( ACT_COMBINE_LAUNCH_GRENADE ) DECLARE_ACTIVITY( ACT_COMBINE_BUGBAIT ) +#if !SHARED_COMBINE_ACTIVITIES DECLARE_ACTIVITY( ACT_COMBINE_AR2_ALTFIRE ) +#endif DECLARE_ACTIVITY( ACT_WALK_EASY ) DECLARE_ACTIVITY( ACT_WALK_MARCH ) +#ifdef MAPBASE +DECLARE_ACTIVITY( ACT_TURRET_CARRY_IDLE ) +DECLARE_ACTIVITY( ACT_TURRET_CARRY_WALK ) +DECLARE_ACTIVITY( ACT_TURRET_CARRY_RUN ) +#endif DECLARE_ANIMEVENT( COMBINE_AE_BEGIN_ALTFIRE ) DECLARE_ANIMEVENT( COMBINE_AE_ALTFIRE ) diff --git a/src/game/server/hl2/npc_combine.h b/src/game/server/hl2/npc_combine.h index ab166723c..d6a500b9 100644 --- a/src/game/server/hl2/npc_combine.h +++ b/src/game/server/hl2/npc_combine.h @@ -21,6 +21,15 @@ #include "ai_behavior_actbusy.h" #include "ai_sentence.h" #include "ai_baseactor.h" +#ifdef MAPBASE +#include "mapbase/ai_grenade.h" +#include "ai_behavior_police.h" +#endif +#ifdef EXPANDED_RESPONSE_SYSTEM_USAGE +#include "mapbase/expandedrs_combine.h" +//#define CAI_Sentence CAI_SentenceTalker +#define COMBINE_SOLDIER_USES_RESPONSE_SYSTEM 1 +#endif // Used when only what combine to react to what the spotlight sees #define SF_COMBINE_NO_LOOK (1 << 16) @@ -30,11 +39,19 @@ //========================================================= // >> CNPC_Combine //========================================================= +#ifdef MAPBASE +class CNPC_Combine : public CAI_GrenadeUser +{ + DECLARE_DATADESC(); + DEFINE_CUSTOM_AI; + DECLARE_CLASS( CNPC_Combine, CAI_GrenadeUser ); +#else class CNPC_Combine : public CAI_BaseActor { DECLARE_DATADESC(); DEFINE_CUSTOM_AI; DECLARE_CLASS( CNPC_Combine, CAI_BaseActor ); +#endif public: CNPC_Combine(); @@ -42,8 +59,10 @@ public: // Create components virtual bool CreateComponents(); +#ifndef MAPBASE // CAI_GrenadeUser bool CanThrowGrenade( const Vector &vecTarget ); bool CheckCanThrowGrenade( const Vector &vecTarget ); +#endif virtual bool CanGrenadeEnemy( bool bUseFreeKnowledge = true ); virtual bool CanAltFireEnemy( bool bUseFreeKnowledge ); int GetGrenadeConditions( float flDot, float flDist ); @@ -56,6 +75,10 @@ public: virtual Vector GetCrouchEyeOffset( void ); +#ifdef MAPBASE + virtual bool IsCrouchedActivity( Activity activity ); +#endif + void Event_Killed( const CTakeDamageInfo &info ); @@ -69,7 +92,17 @@ public: void InputStopPatrolling( inputdata_t &inputdata ); void InputAssault( inputdata_t &inputdata ); void InputHitByBugbait( inputdata_t &inputdata ); +#ifndef MAPBASE void InputThrowGrenadeAtTarget( inputdata_t &inputdata ); +#else + void InputSetElite( inputdata_t &inputdata ); + + void InputDropGrenade( inputdata_t &inputdata ); + + void InputSetTacticalVariant( inputdata_t &inputdata ); + + void InputSetPoliceGoal( inputdata_t &inputdata ); +#endif bool UpdateEnemyMemory( CBaseEntity *pEnemy, const Vector &position, CBaseEntity *pInformer = NULL ); @@ -79,8 +112,16 @@ public: Class_T Classify( void ); bool IsElite() { return m_fIsElite; } +#ifdef MAPBASE + bool IsAltFireCapable(); + bool IsGrenadeCapable(); + const char* GetGrenadeAttachment() { return "lefthand"; } +#else +#endif +#ifndef MAPBASE // CAI_GrenadeUser void DelayAltFireAttack( float flDelay ); void DelaySquadAltFireAttack( float flDelay ); +#endif float MaxYawSpeed( void ); bool ShouldMoveAndShoot(); bool OverrideMoveFacing( const AILocalMoveGoal_t &move, float flInterval );; @@ -90,7 +131,9 @@ public: Vector EyeOffset( Activity nActivity ); Vector EyePosition( void ); Vector BodyTarget( const Vector &posSrc, bool bNoisy = true ); +#ifndef MAPBASE // CAI_GrenadeUser Vector GetAltFireTarget(); +#endif void StartTask( const Task_t *pTask ); void RunTask( const Task_t *pTask ); @@ -98,6 +141,10 @@ public: void GatherConditions(); virtual void PrescheduleThink(); +#ifdef MAPBASE + Activity Weapon_TranslateActivity( Activity baseAct, bool *pRequired = NULL ); + Activity NPC_BackupActivity( Activity eNewActivity ); +#endif Activity NPC_TranslateActivity( Activity eNewActivity ); void BuildScheduleTestBits( void ); virtual int SelectSchedule( void ); @@ -125,12 +172,22 @@ public: // ------------- // Sounds // ------------- +#ifdef MAPBASE + void DeathSound( const CTakeDamageInfo &info ); + void PainSound( const CTakeDamageInfo &info ); +#else void DeathSound( void ); void PainSound( void ); +#endif void IdleSound( void ); void AlertSound( void ); +#ifdef MAPBASE + void LostEnemySound( CBaseEntity *pEnemy ); + void FoundEnemySound( CBaseEntity *pEnemy ); +#else void LostEnemySound( void ); void FoundEnemySound( void ); +#endif void AnnounceAssault( void ); void AnnounceEnemyType( CBaseEntity *pEnemy ); void AnnounceEnemyKill( CBaseEntity *pEnemy ); @@ -143,6 +200,15 @@ public: // Speaking void SpeakSentence( int sentType ); +#ifdef COMBINE_SOLDIER_USES_RESPONSE_SYSTEM + bool SpeakIfAllowed( const char *concept, SentencePriority_t sentencepriority = SENTENCE_PRIORITY_NORMAL, SentenceCriteria_t sentencecriteria = SENTENCE_CRITERIA_IN_SQUAD ) + { + return SpeakIfAllowed( concept, NULL, sentencepriority, sentencecriteria ); + } + bool SpeakIfAllowed( const char *concept, const char *modifiers, SentencePriority_t sentencepriority = SENTENCE_PRIORITY_NORMAL, SentenceCriteria_t sentencecriteria = SENTENCE_CRITERIA_IN_SQUAD ); + bool SpeakIfAllowed( const char *concept, AI_CriteriaSet& modifiers, SentencePriority_t sentencepriority = SENTENCE_PRIORITY_NORMAL, SentenceCriteria_t sentencecriteria = SENTENCE_CRITERIA_IN_SQUAD ); + void ModifyOrAppendCriteria( AI_CriteriaSet& set ); +#endif virtual int TranslateSchedule( int scheduleType ); void OnStartSchedule( int scheduleType ); @@ -151,7 +217,9 @@ public: protected: void SetKickDamage( int nDamage ) { m_nKickDamage = nDamage; } +#ifndef COMBINE_SOLDIER_USES_RESPONSE_SYSTEM CAI_Sentence< CNPC_Combine > *GetSentences() { return &m_Sentences; } +#endif private: //========================================================= @@ -257,37 +325,58 @@ private: private: int m_nKickDamage; +#ifndef MAPBASE // CAI_GrenadeUser Vector m_vecTossVelocity; EHANDLE m_hForcedGrenadeTarget; +#else + // Underthrow grenade at target + bool m_bUnderthrow; + bool m_bAlternateCapable; +#endif bool m_bShouldPatrol; bool m_bFirstEncounter;// only put on the handsign show in the squad's first encounter. // Time Variables float m_flNextPainSoundTime; float m_flNextAlertSoundTime; +#ifndef MAPBASE // CAI_GrenadeUser float m_flNextGrenadeCheck; +#endif float m_flNextLostSoundTime; float m_flAlertPatrolTime; // When to stop doing alert patrol +#ifndef MAPBASE // CAI_GrenadeUser float m_flNextAltFireTime; // Elites only. Next time to begin considering alt-fire attack. +#endif int m_nShots; float m_flShotDelay; float m_flStopMoveShootTime; +#ifndef COMBINE_SOLDIER_USES_RESPONSE_SYSTEM CAI_Sentence< CNPC_Combine > m_Sentences; +#endif +#ifndef MAPBASE // CAI_GrenadeUser int m_iNumGrenades; +#endif CAI_AssaultBehavior m_AssaultBehavior; CCombineStandoffBehavior m_StandoffBehavior; CAI_FollowBehavior m_FollowBehavior; CAI_FuncTankBehavior m_FuncTankBehavior; CAI_RappelBehavior m_RappelBehavior; CAI_ActBusyBehavior m_ActBusyBehavior; +#ifdef MAPBASE + CAI_PolicingBehavior m_PolicingBehavior; +#endif public: +#ifndef MAPBASE // CAI_GrenadeUser int m_iLastAnimEventHandled; +#endif bool m_fIsElite; +#ifndef MAPBASE // CAI_GrenadeUser Vector m_vecAltFireTarget; +#endif int m_iTacticalVariant; int m_iPathfindingVariant; diff --git a/src/game/server/hl2/npc_combinecamera.cpp b/src/game/server/hl2/npc_combinecamera.cpp index 26d1f07e..13bbba1b 100644 --- a/src/game/server/hl2/npc_combinecamera.cpp +++ b/src/game/server/hl2/npc_combinecamera.cpp @@ -547,7 +547,11 @@ bool CNPC_CombineCamera::FVisible(CBaseEntity *pEntity, int traceMask, CBaseEnti // If we hit something that's okay to hit anyway, still fire if ( pHitEntity && pHitEntity->MyCombatCharacterPointer() ) { +#ifdef MAPBASE + if (IRelationType(pHitEntity) <= D_FR) +#else if (IRelationType(pHitEntity) == D_HT) +#endif return true; } @@ -623,6 +627,14 @@ void CNPC_CombineCamera::ActiveThink() if ( !pTarget ) { // Nobody suspicious. Go back to being idle. +#ifdef MAPBASE + if (m_hEnemyTarget) + { + m_OnLostEnemy.FireOutput( m_hEnemyTarget, this ); + if (m_hEnemyTarget->IsPlayer()) + m_OnLostPlayer.FireOutput( m_hEnemyTarget, this ); + } +#endif m_hEnemyTarget = NULL; EmitSound("NPC_CombineCamera.BecomeIdle"); SetAngry(false); diff --git a/src/game/server/hl2/npc_combinedropship.cpp b/src/game/server/hl2/npc_combinedropship.cpp index 85e3db40..08b0d445 100644 --- a/src/game/server/hl2/npc_combinedropship.cpp +++ b/src/game/server/hl2/npc_combinedropship.cpp @@ -175,6 +175,11 @@ public: virtual int OnTakeDamage( const CTakeDamageInfo &info ); virtual void Event_Killed( const CTakeDamageInfo &info ); +#ifdef MAPBASE + // NOTE: This function is shared across containers and dropships; this is the container's version + bool AllowsAnyDamage( const CTakeDamageInfo &info ); +#endif + private: enum { @@ -247,6 +252,11 @@ public: void MakeTracer( const Vector &vecTracerSrc, const trace_t &tr, int iTracerType ); int OnTakeDamage_Alive( const CTakeDamageInfo &inputInfo ); +#ifdef MAPBASE + // NOTE: This function is shared across containers and dropships; this is the dropship's version + bool AllowsAnyDamage() { return m_bAllowAnyDamage; } +#endif + // Input handlers. void InputLandLeave( inputdata_t &inputdata ); void InputLandTake( inputdata_t &inputdata ); @@ -254,6 +264,9 @@ public: void InputDropMines( inputdata_t &inputdata ); void InputDropStrider( inputdata_t &inputdata ); void InputDropAPC( inputdata_t &inputdata ); +#ifdef MAPBASE + void InputDropCargo( inputdata_t &inputdata ); +#endif void InputPickup( inputdata_t &inputdata ); void InputSetGunRange( inputdata_t &inputdata ); @@ -317,6 +330,9 @@ private: CHandle m_hPickupTarget; int m_iContainerMoveType; bool m_bWaitForDropoffInput; +#ifdef MAPBASE + bool m_bDontEmitDanger; +#endif DECLARE_DATADESC(); DEFINE_CUSTOM_AI; @@ -357,6 +373,10 @@ private: COutputFloat m_OnContainerShotDownBeforeDropoff; COutputEvent m_OnContainerShotDownAfterDropoff; +#ifdef MAPBASE + COutputEHANDLE m_OnSpawnNPC; +#endif + protected: // Because the combine dropship is a leaf class, we can use // static variables to store this information, and save some memory. @@ -614,6 +634,23 @@ void CCombineDropshipContainer::Event_Killed( const CTakeDamageInfo &info ) } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CCombineDropshipContainer::AllowsAnyDamage( const CTakeDamageInfo &info ) +{ + if (GetOwnerEntity()) + { + CNPC_CombineDropship *pDropship = assert_cast(GetOwnerEntity()); + return pDropship->AllowsAnyDamage() && pDropship->PassesDamageFilter(info); + } + + return false; +} +#endif + + //----------------------------------------------------------------------------- // Damage effects //----------------------------------------------------------------------------- @@ -623,7 +660,11 @@ int CCombineDropshipContainer::OnTakeDamage( const CTakeDamageInfo &info ) return 0; // Airboat guns + explosive damage is all that can hurt it +#ifdef MAPBASE + if (( info.GetDamageType() & (DMG_BLAST | DMG_AIRBOAT) ) == 0 && !AllowsAnyDamage(info) ) +#else if (( info.GetDamageType() & (DMG_BLAST | DMG_AIRBOAT) ) == 0 ) +#endif return 0; CTakeDamageInfo dmgInfo = info; @@ -648,9 +689,15 @@ int CCombineDropshipContainer::OnTakeDamage( const CTakeDamageInfo &info ) if ( m_iHealth <= 0 ) { - m_iHealth = 0; - Event_Killed( dmgInfo ); - return 0; +#ifdef MAPBASE_VSCRIPT + // False = Cheat death + if (ScriptDeathHook( const_cast(&info) ) != false) +#endif + { + m_iHealth = 0; + Event_Killed( dmgInfo ); + return 0; + } } // Spawn damage effects @@ -764,6 +811,9 @@ BEGIN_DATADESC( CNPC_CombineDropship ) DEFINE_FIELD( m_hPickupTarget, FIELD_EHANDLE ), DEFINE_FIELD( m_iContainerMoveType, FIELD_INTEGER ), DEFINE_FIELD( m_bWaitForDropoffInput, FIELD_BOOLEAN ), +#ifdef MAPBASE + DEFINE_KEYFIELD( m_bDontEmitDanger, FIELD_BOOLEAN, "DontEmitDanger" ), +#endif DEFINE_FIELD( m_hLandTarget, FIELD_EHANDLE ), DEFINE_FIELD( m_bHasDroppedOff, FIELD_BOOLEAN ), DEFINE_KEYFIELD( m_bInvulnerable, FIELD_BOOLEAN, "Invulnerable" ), @@ -810,6 +860,9 @@ BEGIN_DATADESC( CNPC_CombineDropship ) DEFINE_INPUTFUNC( FIELD_INTEGER, "DropMines", InputDropMines ), DEFINE_INPUTFUNC( FIELD_VOID, "DropStrider", InputDropStrider ), DEFINE_INPUTFUNC( FIELD_VOID, "DropAPC", InputDropAPC ), +#ifdef MAPBASE + DEFINE_INPUTFUNC( FIELD_VOID, "DropCargo", InputDropCargo ), +#endif DEFINE_INPUTFUNC( FIELD_STRING, "Pickup", InputPickup ), DEFINE_INPUTFUNC( FIELD_FLOAT, "SetGunRange", InputSetGunRange ), DEFINE_INPUTFUNC( FIELD_STRING, "NPCFinishDustoff", InputNPCFinishDustoff ), @@ -823,6 +876,10 @@ BEGIN_DATADESC( CNPC_CombineDropship ) DEFINE_OUTPUT( m_OnContainerShotDownBeforeDropoff, "OnCrateShotDownBeforeDropoff" ), DEFINE_OUTPUT( m_OnContainerShotDownAfterDropoff, "OnCrateShotDownAfterDropoff" ), +#ifdef MAPBASE + DEFINE_OUTPUT( m_OnSpawnNPC, "OnSpawnNPC" ), +#endif + END_DATADESC() @@ -895,6 +952,11 @@ void CNPC_CombineDropship::Spawn( void ) m_iMachineGunBaseAttachment = m_hContainer->LookupAttachment( "gun_base" ); // NOTE: gun_ref must have the same position as gun_base, but rotates with the gun m_iMachineGunRefAttachment = m_hContainer->LookupAttachment( "gun_ref" ); + +#ifdef MAPBASE + m_poseWeapon_Pitch = m_hContainer->LookupPoseParameter("weapon_pitch"); //added these two lines + m_poseWeapon_Yaw = m_hContainer->LookupPoseParameter("weapon_yaw"); +#endif } break; @@ -906,6 +968,9 @@ void CNPC_CombineDropship::Spawn( void ) m_hContainer->SetOwnerEntity(this); m_hContainer->Spawn(); m_hContainer->SetAbsOrigin( GetAbsOrigin() - Vector( 0, 0 , 100 ) ); +#ifdef MAPBASE + m_OnSpawnNPC.Set( m_hContainer->GetBaseEntity(), m_hContainer, this); +#endif break; case CRATE_APC: @@ -933,7 +998,11 @@ void CNPC_CombineDropship::Spawn( void ) IPhysicsObject *pPhysicsObject = m_hContainer->VPhysicsGetObject(); if ( pPhysicsObject ) { +#ifdef MAPBASE + pPhysicsObject->SetShadow( 1e4, 1e4, true, true ); // (allowing physics movement and rotation) +#else pPhysicsObject->SetShadow( 1e4, 1e4, false, false ); +#endif } m_hContainer->SetParent(this, 0); @@ -1402,7 +1471,11 @@ int CNPC_CombineDropship::OnTakeDamage_Alive( const CTakeDamageInfo &inputInfo ) // code above to see how to do it. if ( m_hContainer && !m_bInvulnerable ) { +#ifdef MAPBASE + if ( (inputInfo.GetDamageType() & DMG_AIRBOAT) || (m_iCrateType == CRATE_SOLDIER) || m_bAllowAnyDamage ) +#else if ( (inputInfo.GetDamageType() & DMG_AIRBOAT) || (m_iCrateType == CRATE_SOLDIER) ) +#endif { m_hContainer->TakeDamage( inputInfo ); } @@ -1757,12 +1830,60 @@ void CNPC_CombineDropship::InputDropAPC( inputdata_t &inputdata ) UTIL_SetSize( this, DROPSHIP_BBOX_MIN, DROPSHIP_BBOX_MAX ); +#ifdef MAPBASE + m_OnFinishedDropoff.FireOutput( m_hContainer, this ); + m_hContainer = NULL; +#else m_hContainer = NULL; m_OnFinishedDropoff.FireOutput( this, this ); +#endif SetLandingState( LANDING_NO ); m_hLandTarget = NULL; } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CNPC_CombineDropship::InputDropCargo( inputdata_t &inputdata ) +{ + if ( !m_hContainer ) + { + Warning("npc_combinedropship %s was told to drop cargo, but isn't carrying any!\n", STRING(GetEntityName()) ); + return; + } + + m_hContainer->SetParent(NULL, 0); +// m_hContainer->SetOwnerEntity(NULL); + + Vector vecAbsVelocity = GetAbsVelocity(); + if ( vecAbsVelocity.z > 0 ) + { + vecAbsVelocity.z = 0.0f; + } + if ( m_hContainer->GetHealth() > 0 ) + { + vecAbsVelocity = vec3_origin; + } + + m_hContainer->SetAbsVelocity( vecAbsVelocity ); + m_hContainer->SetMoveType( (MoveType_t)m_iContainerMoveType ); + + // If the container has a physics object, remove it's shadow + IPhysicsObject *pPhysicsObject = m_hContainer->VPhysicsGetObject(); + if ( pPhysicsObject ) + { + pPhysicsObject->RemoveShadowController(); + } + + UTIL_SetSize( this, DROPSHIP_BBOX_MIN, DROPSHIP_BBOX_MAX ); + + m_OnFinishedDropoff.FireOutput( m_hContainer, this ); + m_hContainer = NULL; + SetLandingState( LANDING_NO ); + m_hLandTarget = NULL; +} +#endif + //----------------------------------------------------------------------------- // Drop the soldier container @@ -1799,13 +1920,19 @@ void CNPC_CombineDropship::DropSoldierContainer( ) UTIL_SetSize( this, DROPSHIP_BBOX_MIN, DROPSHIP_BBOX_MAX ); +#ifndef MAPBASE m_hContainer = NULL; +#endif SetLandingState( LANDING_NO ); m_hLandTarget = NULL; if ( m_bHasDroppedOff ) { +#ifdef MAPBASE + m_OnContainerShotDownAfterDropoff.FireOutput( m_hContainer, this ); +#else m_OnContainerShotDownAfterDropoff.FireOutput( this, this ); +#endif } else { @@ -1815,8 +1942,16 @@ void CNPC_CombineDropship::DropSoldierContainer( ) Msg("Dropship died, troops not unloaded: %d\n", iTroopsNotUnloaded ); } +#ifdef MAPBASE + m_OnContainerShotDownBeforeDropoff.Set( iTroopsNotUnloaded, m_hContainer, this ); +#else m_OnContainerShotDownBeforeDropoff.Set( iTroopsNotUnloaded, this, this ); +#endif } + +#ifdef MAPBASE + m_hContainer = NULL; +#endif } @@ -2091,10 +2226,17 @@ void CNPC_CombineDropship::PrescheduleThink( void ) // place danger sounds 1 foot above ground to get troops to scatter if they are below dropship Vector vecBottom = GetAbsOrigin(); vecBottom.z += WorldAlignMins().z; +#ifdef MAPBASE + if (!m_bDontEmitDanger) + { +#endif Vector vecSpot = vecBottom + Vector(0, 0, -1) * (flAltitude - 12 ); CSoundEnt::InsertSound( SOUND_DANGER, vecSpot, 400, 0.1, this, 0 ); CSoundEnt::InsertSound( SOUND_PHYSICS_DANGER, vecSpot, 400, 0.1, this, 1 ); // NDebugOverlay::Cross3D( vecSpot, -Vector(4,4,4), Vector(4,4,4), 255, 0, 255, false, 10.0f ); +#ifdef MAPBASE + } +#endif // now check to see if player is below us, if so, cause heat damage to them (i.e. get them to move) trace_t tr; @@ -2266,7 +2408,11 @@ void CNPC_CombineDropship::PrescheduleThink( void ) SetLandingState( LANDING_NO ); m_hLandTarget = NULL; m_bHasDroppedOff = true; +#ifdef MAPBASE + m_OnFinishedDropoff.FireOutput( m_hContainer, this ); +#else m_OnFinishedDropoff.FireOutput( this, this ); +#endif } if ( m_hContainer ) @@ -2326,7 +2472,11 @@ void CNPC_CombineDropship::PrescheduleThink( void ) m_hContainer->SetMoveType( MOVETYPE_PUSH ); m_hContainer->SetGroundEntity( NULL ); +#ifdef MAPBASE + m_OnFinishedPickup.FireOutput( m_hContainer, this ); +#else m_OnFinishedPickup.FireOutput( this, this ); +#endif SetLandingState( LANDING_NO ); } } @@ -2404,6 +2554,9 @@ void CNPC_CombineDropship::SpawnTroop( void ) QAngle vecDeployEndAngles; m_hContainer->GetAttachment( m_iAttachmentTroopDeploy, vecDeployEndPoint, vecDeployEndAngles ); vecDeployEndPoint = GetDropoffFinishPosition( vecDeployEndPoint, NULL, vecNPCMins, vecNPCMaxs ); +#ifdef MAPBASE + if (!m_bDontEmitDanger) +#endif CSoundEnt::InsertSound( SOUND_DANGER, vecDeployEndPoint, 120.0f, 2.0f, this ); // Make sure there are no NPCs on the spot @@ -2479,6 +2632,10 @@ void CNPC_CombineDropship::SpawnTroop( void ) pSequence->AcceptInput( "BeginSequence", this, this, emptyVariant, 0 ); m_hLastTroopToLeave = pNPC; + +#ifdef MAPBASE + m_OnSpawnNPC.Set( pNPC, pNPC, this ); +#endif } //----------------------------------------------------------------------------- @@ -2642,7 +2799,12 @@ float CNPC_CombineDropship::GetAltitude( void ) //----------------------------------------------------------------------------- void CNPC_CombineDropship::DropMine( void ) { +#ifdef MAPBASE + CBaseEntity *pMine = NPC_Rollermine_DropFromPoint( GetAbsOrigin(), this, STRING( m_sRollermineTemplateData ) ); + m_OnSpawnNPC.Set( pMine, pMine, this ); +#else NPC_Rollermine_DropFromPoint( GetAbsOrigin(), this, STRING(m_sRollermineTemplateData) ); +#endif } //------------------------------------------------------------------------------ diff --git a/src/game/server/hl2/npc_combinegunship.cpp b/src/game/server/hl2/npc_combinegunship.cpp index 25831053..dfe0aa20 100644 --- a/src/game/server/hl2/npc_combinegunship.cpp +++ b/src/game/server/hl2/npc_combinegunship.cpp @@ -51,6 +51,10 @@ // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" +#ifdef MAPBASE +#define BELLYBLAST +#endif + #define GUNSHIP_MSG_BIG_SHOT 1 #define GUNSHIP_MSG_STREAKS 2 @@ -370,6 +374,10 @@ private: int m_iDoSmokePuff; int m_iAmmoType; int m_iBurstSize; + +#ifdef MAPBASE + int m_iHealthIncrements; +#endif bool m_fBlindfire; bool m_fOmniscient; @@ -420,7 +428,11 @@ BEGIN_DATADESC( CNPC_CombineGunship ) DEFINE_FIELD( m_flNextGroundAttack,FIELD_TIME ), DEFINE_FIELD( m_bIsGroundAttacking,FIELD_BOOLEAN ), +#ifdef MAPBASE + DEFINE_KEYFIELD( m_bCanGroundAttack, FIELD_BOOLEAN, "CanGroundAttack" ), +#else DEFINE_FIELD( m_bCanGroundAttack, FIELD_BOOLEAN ), +#endif DEFINE_FIELD( m_flGroundAttackTime,FIELD_TIME ), DEFINE_FIELD( m_pRotorWashModel, FIELD_CLASSPTR ), DEFINE_FIELD( m_pSmokeTrail, FIELD_EHANDLE ), @@ -437,6 +449,9 @@ BEGIN_DATADESC( CNPC_CombineGunship ) DEFINE_FIELD( m_iDoSmokePuff, FIELD_INTEGER ), DEFINE_FIELD( m_iAmmoType, FIELD_INTEGER ), DEFINE_FIELD( m_iBurstSize, FIELD_INTEGER ), +#ifdef MAPBASE + DEFINE_KEYFIELD( m_iHealthIncrements, FIELD_INTEGER, "HealthIncrements" ), +#endif DEFINE_FIELD( m_flBurstDelay, FIELD_FLOAT ), DEFINE_FIELD( m_fBlindfire, FIELD_BOOLEAN ), DEFINE_FIELD( m_fOmniscient, FIELD_BOOLEAN ), @@ -552,6 +567,11 @@ void CNPC_CombineGunship::Spawn( void ) m_iMaxHealth = m_iHealth = 100; +#ifdef MAPBASE + if (m_iHealthIncrements == 0) + m_iHealthIncrements = sk_gunship_health_increments.GetInt(); +#endif + m_flFieldOfView = -0.707; // 270 degrees m_fHelicopterFlags |= BITS_HELICOPTER_GUN_ON; @@ -601,8 +621,10 @@ void CNPC_CombineGunship::Spawn( void ) m_bPreFire = false; m_bInvulnerable = false; +#ifndef MAPBASE // Spawnflag has been replaced with KV // See if we should start being able to attack m_bCanGroundAttack = ( m_spawnflags & SF_GUNSHIP_NO_GROUND_ATTACK ) ? false : true; +#endif m_flEndDestructTime = 0; @@ -2831,7 +2853,13 @@ void CNPC_CombineGunship::MakeTracer( const Vector &vecTracerSrc, const trace_t void CNPC_CombineGunship::TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator ) { // Reflect bullets +#ifdef MAPBASE + // There's a keyvalue that allows any damage, but still reflect if the bullets wouldn't pass our damage filter. + if ( info.GetDamageType() & DMG_BULLET && + (!m_bAllowAnyDamage || !PassesDamageFilter(info)) ) +#else if ( info.GetDamageType() & DMG_BULLET ) +#endif { if ( random->RandomInt( 0, 2 ) == 0 ) { @@ -2912,7 +2940,11 @@ void CNPC_CombineGunship::FireDamageOutputsUpto( int iDamageNumber ) int CNPC_CombineGunship::OnTakeDamage_Alive( const CTakeDamageInfo &inputInfo ) { // Allow npc_kill to kill me +#ifdef MAPBASE + if ( inputInfo.GetDamageType() != DMG_GENERIC && !m_bAllowAnyDamage ) +#else if ( inputInfo.GetDamageType() != DMG_GENERIC ) +#endif { // Ignore mundane bullet damage. if ( ( inputInfo.GetDamageType() & DMG_BLAST ) == false ) @@ -2945,7 +2977,11 @@ int CNPC_CombineGunship::OnTakeDamage_Alive( const CTakeDamageInfo &inputInfo ) { // Take a percentage of our health away // Adjust health for damage +#ifdef MAPBASE + int iHealthIncrements = m_iHealthIncrements; +#else int iHealthIncrements = sk_gunship_health_increments.GetInt(); +#endif if ( g_pGameRules->IsSkillLevel( SKILL_EASY ) ) { iHealthIncrements = ceil( iHealthIncrements * 0.5 ); diff --git a/src/game/server/hl2/npc_combines.cpp b/src/game/server/hl2/npc_combines.cpp index 04b58aad..8bbb3478 100644 --- a/src/game/server/hl2/npc_combines.cpp +++ b/src/game/server/hl2/npc_combines.cpp @@ -96,7 +96,12 @@ void CNPC_CombineS::Precache() { const char *pModelName = STRING( GetModelName() ); +#ifdef MAPBASE + // Need to do this for dirt variant + if( !Q_strnicmp( pModelName, "models/combine_super_sold", 25 ) ) +#else if( !Q_stricmp( pModelName, "models/combine_super_soldier.mdl" ) ) +#endif { m_fIsElite = true; } @@ -122,14 +127,21 @@ void CNPC_CombineS::Precache() void CNPC_CombineS::DeathSound( const CTakeDamageInfo &info ) { +#ifdef COMBINE_SOLDIER_USES_RESPONSE_SYSTEM + AI_CriteriaSet set; + ModifyOrAppendDamageCriteria(set, info); + SpeakIfAllowed( TLK_CMB_DIE, set, SENTENCE_PRIORITY_INVALID, SENTENCE_CRITERIA_ALWAYS ); +#else // NOTE: The response system deals with this at the moment if ( GetFlags() & FL_DISSOLVING ) return; GetSentences()->Speak( "COMBINE_DIE", SENTENCE_PRIORITY_INVALID, SENTENCE_CRITERIA_ALWAYS ); +#endif } +#ifndef MAPBASE // Moved to CAI_GrenadeUser //----------------------------------------------------------------------------- // Purpose: Soldiers use CAN_RANGE_ATTACK2 to indicate whether they can throw // a grenade. Because they check only every half-second or so, this @@ -151,6 +163,7 @@ void CNPC_CombineS::ClearAttackConditions( ) SetCondition( COND_CAN_RANGE_ATTACK2 ); } } +#endif void CNPC_CombineS::PrescheduleThink( void ) { @@ -307,7 +320,15 @@ void CNPC_CombineS::Event_Killed( const CTakeDamageInfo &info ) if ( HasSpawnFlags( SF_COMBINE_NO_AR2DROP ) == false ) #endif { +#ifdef MAPBASE + CBaseEntity *pItem; + if (GetActiveWeapon() && FClassnameIs(GetActiveWeapon(), "weapon_smg1")) + pItem = DropItem( "item_ammo_smg1_grenade", WorldSpaceCenter()+RandomVector(-4,4), RandomAngle(0,360) ); + else + pItem = DropItem( "item_ammo_ar2_altfire", WorldSpaceCenter()+RandomVector(-4,4), RandomAngle(0,360) ); +#else CBaseEntity *pItem = DropItem( "item_ammo_ar2_altfire", WorldSpaceCenter()+RandomVector(-4,4), RandomAngle(0,360) ); +#endif if ( pItem ) { diff --git a/src/game/server/hl2/npc_combines.h b/src/game/server/hl2/npc_combines.h index e9dc75b2..7c7d8fbe 100644 --- a/src/game/server/hl2/npc_combines.h +++ b/src/game/server/hl2/npc_combines.h @@ -35,7 +35,9 @@ public: void Event_Killed( const CTakeDamageInfo &info ); void OnListened(); +#ifndef MAPBASE // Moved to CAI_GrenadeUser void ClearAttackConditions( void ); +#endif bool m_fIsBlocking; diff --git a/src/game/server/hl2/npc_eli.cpp b/src/game/server/hl2/npc_eli.cpp index 4b42c6c0..c66e9273 100644 --- a/src/game/server/hl2/npc_eli.cpp +++ b/src/game/server/hl2/npc_eli.cpp @@ -37,6 +37,11 @@ public: int GetSoundInterests( void ); void SetupWithoutParent( void ); void PrescheduleThink( void ); + +#ifdef MAPBASE + // Use Eli's default subtitle color (255,208,172) + bool GetGameTextSpeechParams( hudtextparms_t ¶ms ) { params.r1 = 255; params.g1 = 208; params.b1 = 172; return BaseClass::GetGameTextSpeechParams( params ); } +#endif }; LINK_ENTITY_TO_CLASS( npc_eli, CNPC_Eli ); diff --git a/src/game/server/hl2/npc_enemyfinder.cpp b/src/game/server/hl2/npc_enemyfinder.cpp index 02afc08b..d9855815 100644 --- a/src/game/server/hl2/npc_enemyfinder.cpp +++ b/src/game/server/hl2/npc_enemyfinder.cpp @@ -59,6 +59,10 @@ public: void InputTurnOff( inputdata_t &inputdata ); virtual void Wake( bool bFireOutput = true ); +#ifdef MAPBASE + // A version of Wake() that takes an activator + virtual void Wake(CBaseEntity* pActivator); +#endif private: int m_nStartOn; @@ -69,6 +73,10 @@ private: bool m_bEnemyStatus; +#ifdef MAPBASE + Class_T m_iClassify = CLASS_NONE; +#endif + COutputEvent m_OnLostEnemies; COutputEvent m_OnAcquireEnemies; @@ -103,6 +111,10 @@ BEGIN_DATADESC( CNPC_EnemyFinder ) DEFINE_FIELD( m_bEnemyStatus, FIELD_BOOLEAN ), +#ifdef MAPBASE + DEFINE_INPUT( m_iClassify, FIELD_INTEGER, "SetClassify" ), +#endif + DEFINE_INPUTFUNC( FIELD_VOID, "TurnOn", InputTurnOn ), DEFINE_INPUTFUNC( FIELD_VOID, "TurnOff", InputTurnOff ), @@ -226,6 +238,16 @@ void CNPC_EnemyFinder::Wake( bool bFireOutput ) AddEffects( EF_NODRAW ); } +#ifdef MAPBASE +void CNPC_EnemyFinder::Wake(CBaseEntity* pActivator) +{ + BaseClass::Wake(pActivator); + + //Enemy finder is not allowed to become visible. + AddEffects(EF_NODRAW); +} +#endif // MAPBASE + //------------------------------------------------------------------------------ // //------------------------------------------------------------------------------ @@ -417,7 +439,11 @@ bool CNPC_EnemyFinder::ShouldAlwaysThink() return true; CBasePlayer *pPlayer = AI_GetSinglePlayer(); +#ifdef MAPBASE + if ( pPlayer && IRelationType( pPlayer ) <= D_FR ) +#else if ( pPlayer && IRelationType( pPlayer ) == D_HT ) +#endif { float playerDistSqr = GetAbsOrigin().DistToSqr( pPlayer->GetAbsOrigin() ); @@ -454,6 +480,11 @@ void CNPC_EnemyFinder::GatherConditions() //----------------------------------------------------------------------------- Class_T CNPC_EnemyFinder::Classify( void ) { +#ifdef MAPBASE + if (m_iClassify != CLASS_NONE) + return m_iClassify; +#endif + if ( GetSquad() ) { AISquadIter_t iter; diff --git a/src/game/server/hl2/npc_fastzombie.cpp b/src/game/server/hl2/npc_fastzombie.cpp index 0ecd7155..6e5ec136 100644 --- a/src/game/server/hl2/npc_fastzombie.cpp +++ b/src/game/server/hl2/npc_fastzombie.cpp @@ -61,6 +61,12 @@ enum COND_FASTZOMBIE_CLIMB_TOUCH = LAST_BASE_ZOMBIE_CONDITION, }; +#ifdef MAPBASE +ConVar sk_zombie_fast_health( "sk_zombie_fast_health", "50"); +ConVar sk_zombie_fast_dmg_one_slash( "sk_zombie_fast_dmg_claw","3"); +ConVar sk_zombie_fast_dmg_both_slash( "sk_zombie_fast_dmg_leap","5"); +#endif + envelopePoint_t envFastZombieVolumeJump[] = { { 1.0f, 1.0f, @@ -255,7 +261,9 @@ public: void OnChangeActivity( Activity NewActivity ); void OnStateChange( NPC_STATE OldState, NPC_STATE NewState ); void Event_Killed( const CTakeDamageInfo &info ); +#ifndef MAPBASE bool ShouldBecomeTorso( const CTakeDamageInfo &info, float flDamageThreshold ); +#endif virtual Vector GetAutoAimCenter() { return WorldSpaceCenter() - Vector( 0, 0, 12.0f ); } @@ -652,7 +660,9 @@ void CFastZombie::Spawn( void ) m_fJustJumped = false; +#ifndef MAPBASE // Controlled by KV m_fIsTorso = m_fIsHeadless = false; +#endif if( FClassnameIs( this, "npc_fastzombie" ) ) { @@ -670,7 +680,11 @@ void CFastZombie::Spawn( void ) SetBloodColor( BLOOD_COLOR_YELLOW ); #endif // HL2_EPISODIC +#ifdef MAPBASE + m_iHealth = sk_zombie_fast_health.GetInt(); +#else m_iHealth = 50; +#endif m_flFieldOfView = 0.2; CapabilitiesClear(); @@ -1084,7 +1098,11 @@ void CFastZombie::HandleAnimEvent( animevent_t *pEvent ) right = right * -50; QAngle angle( -3, -5, -3 ); +#ifdef MAPBASE + ClawAttack( GetClawAttackRange(), sk_zombie_fast_dmg_one_slash.GetInt(), angle, right, ZOMBIE_BLOOD_RIGHT_HAND ); +#else ClawAttack( GetClawAttackRange(), 3, angle, right, ZOMBIE_BLOOD_RIGHT_HAND ); +#endif return; } @@ -1094,7 +1112,11 @@ void CFastZombie::HandleAnimEvent( animevent_t *pEvent ) AngleVectors( GetLocalAngles(), NULL, &right, NULL ); right = right * 50; QAngle angle( -3, 5, -3 ); +#ifdef MAPBASE + ClawAttack( GetClawAttackRange(), sk_zombie_fast_dmg_one_slash.GetInt(), angle, right, ZOMBIE_BLOOD_LEFT_HAND ); +#else ClawAttack( GetClawAttackRange(), 3, angle, right, ZOMBIE_BLOOD_LEFT_HAND ); +#endif return; } @@ -1274,7 +1296,11 @@ void CFastZombie::StartTask( const Task_t *pTask ) CBaseEntity *pEnemy = GetEnemy(); Vector vecJumpDir; - if ( GetActivity() == ACT_CLIMB_UP || GetActivity() == ACT_CLIMB_DOWN ) + if ( GetActivity() == ACT_CLIMB_UP || GetActivity() == ACT_CLIMB_DOWN +#if EXPANDED_NAVIGATION_ACTIVITIES + || GetActivity() == ACT_CLIMB_ALL +#endif + ) { // Jump off the pipe backwards! Vector forward; @@ -1427,7 +1453,11 @@ int CFastZombie::TranslateSchedule( int scheduleType ) break; case SCHED_FASTZOMBIE_UNSTICK_JUMP: - if ( GetActivity() == ACT_CLIMB_UP || GetActivity() == ACT_CLIMB_DOWN || GetActivity() == ACT_CLIMB_DISMOUNT ) + if ( GetActivity() == ACT_CLIMB_UP || GetActivity() == ACT_CLIMB_DOWN || GetActivity() == ACT_CLIMB_DISMOUNT +#if EXPANDED_NAVIGATION_ACTIVITIES + || (GetActivity() >= ACT_CLIMB_ALL && GetActivity() <= ACT_CLIMB_DISMOUNT_BOTTOM) +#endif + ) { return SCHED_FASTZOMBIE_CLIMBING_UNSTICK_JUMP; } @@ -1455,8 +1485,10 @@ int CFastZombie::TranslateSchedule( int scheduleType ) //--------------------------------------------------------- Activity CFastZombie::NPC_TranslateActivity( Activity baseAct ) { +#ifndef MAPBASE // Now covered by CAI_BaseNPC::NPC_BackupActivity if ( baseAct == ACT_CLIMB_DOWN ) return ACT_CLIMB_UP; +#endif return BaseClass::NPC_TranslateActivity( baseAct ); } @@ -1480,7 +1512,11 @@ void CFastZombie::LeapAttackTouch( CBaseEntity *pOther ) forward *= 500; QAngle qaPunch( 15, random->RandomInt(-5,5), random->RandomInt(-5,5) ); +#ifdef MAPBASE + ClawAttack( GetClawAttackRange(), sk_zombie_fast_dmg_both_slash.GetInt(), qaPunch, forward, ZOMBIE_BLOOD_BOTH_HANDS ); +#else ClawAttack( GetClawAttackRange(), 5, qaPunch, forward, ZOMBIE_BLOOD_BOTH_HANDS ); +#endif SetTouch( NULL ); } @@ -1848,6 +1884,7 @@ void CFastZombie::Event_Killed( const CTakeDamageInfo &info ) BaseClass::Event_Killed( dInfo ); } +#ifndef MAPBASE //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- bool CFastZombie::ShouldBecomeTorso( const CTakeDamageInfo &info, float flDamageThreshold ) @@ -1868,6 +1905,7 @@ bool CFastZombie::ShouldBecomeTorso( const CTakeDamageInfo &info, float flDamage return false; } +#endif //============================================================================= #ifdef HL2_EPISODIC diff --git a/src/game/server/hl2/npc_fisherman.cpp b/src/game/server/hl2/npc_fisherman.cpp index 4530f261..967c5795 100644 --- a/src/game/server/hl2/npc_fisherman.cpp +++ b/src/game/server/hl2/npc_fisherman.cpp @@ -64,8 +64,10 @@ public: virtual void Precache() { +#ifndef MAPBASE // This is now done in CNPC_PlayerCompanion::Precache() // Prevents a warning - SelectModel( ); + SelectModel(); +#endif BaseClass::Precache(); PrecacheScriptSound( "NPC_Fisherman.FootstepLeft" ); diff --git a/src/game/server/hl2/npc_headcrab.cpp b/src/game/server/hl2/npc_headcrab.cpp index 6e7d3e71..b1b8db81 100644 --- a/src/game/server/hl2/npc_headcrab.cpp +++ b/src/game/server/hl2/npc_headcrab.cpp @@ -72,6 +72,10 @@ ConVar g_debug_headcrab( "g_debug_headcrab", "0", FCVAR_CHEAT ); //------------------------------------ #define SF_HEADCRAB_START_HIDDEN (1 << 16) #define SF_HEADCRAB_START_HANGING (1 << 17) +#ifdef MAPBASE +#define SF_HEADCRAB_DONT_DROWN (1 << 18) +#define SF_HEADCRAB_NO_MELEE_INSTAKILL (1 << 19) +#endif //----------------------------------------------------------------------------- @@ -218,6 +222,10 @@ BEGIN_DATADESC( CBaseHeadcrab ) DEFINE_INPUTFUNC( FIELD_VOID, "StartHangingFromCeiling", InputStartHangingFromCeiling ), DEFINE_INPUTFUNC( FIELD_VOID, "DropFromCeiling", InputDropFromCeiling ), +#ifdef MAPBASE + DEFINE_OUTPUT( m_OnLeap, "OnLeap" ), +#endif + // Function Pointers DEFINE_THINKFUNC( EliminateRollAndPitch ), DEFINE_THINKFUNC( ThrowThink ), @@ -482,6 +490,11 @@ void CBaseHeadcrab::Leap( const Vector &vecVel ) m_bMidJump = true; SetThink( &CBaseHeadcrab::ThrowThink ); SetNextThink( gpGlobals->curtime ); + +#ifdef MAPBASE + // We usually leap at an enemy, so use that as the activator + m_OnLeap.FireOutput(GetEnemy(), this); +#endif } @@ -926,7 +939,11 @@ void CBaseHeadcrab::LeapTouch( CBaseEntity *pOther ) { m_bMidJump = false; +#ifdef MAPBASE + if ( IRelationType( pOther ) <= D_FR ) +#else if ( IRelationType( pOther ) == D_HT ) +#endif { // Don't hit if back on ground if ( !( GetFlags() & FL_ONGROUND ) ) @@ -1032,7 +1049,11 @@ void CBaseHeadcrab::GatherConditions( void ) BaseClass::GatherConditions(); +#ifdef MAPBASE + if (GetWaterLevel() > 1 && m_lifeState == LIFE_ALIVE && !HasSpawnFlags( SF_HEADCRAB_DONT_DROWN )) +#else if( m_lifeState == LIFE_ALIVE && GetWaterLevel() > 1 ) +#endif { // Start Drowning! SetCondition( COND_HEADCRAB_IN_WATER ); @@ -1712,7 +1733,12 @@ int CBaseHeadcrab::OnTakeDamage_Alive( const CTakeDamageInfo &inputInfo ) // // Certain death from melee bludgeon weapons! // +#ifdef MAPBASE + // (unless the mapper said no) + if ( info.GetDamageType() & DMG_CLUB && !HasSpawnFlags( SF_HEADCRAB_NO_MELEE_INSTAKILL ) ) +#else if ( info.GetDamageType() & DMG_CLUB ) +#endif { info.SetDamage( m_iHealth ); } @@ -2230,6 +2256,9 @@ void CBaseHeadcrab::GrabHintNode( CAI_Hint *pHint ) { SetHintNode( pHint ); pHint->Lock( this ); +#ifdef MAPBASE + pHint->NPCStartedUsing( this ); +#endif } } @@ -2404,7 +2433,7 @@ void CBaseHeadcrab::CreateDust( bool placeDecal ) //----------------------------------------------------------------------------- void CHeadcrab::Precache( void ) { - PrecacheModel( "models/headcrabclassic.mdl" ); + PrecacheModel( DefaultOrCustomModel( "models/headcrabclassic.mdl" ) ); PrecacheScriptSound( "NPC_HeadCrab.Gib" ); PrecacheScriptSound( "NPC_HeadCrab.Idle" ); @@ -2426,7 +2455,7 @@ void CHeadcrab::Precache( void ) void CHeadcrab::Spawn( void ) { Precache(); - SetModel( "models/headcrabclassic.mdl" ); + SetModel( DefaultOrCustomModel( "models/headcrabclassic.mdl" ) ); BaseClass::Spawn(); @@ -2541,7 +2570,7 @@ END_DATADESC() //----------------------------------------------------------------------------- void CFastHeadcrab::Precache( void ) { - PrecacheModel( "models/headcrab.mdl" ); + PrecacheModel( DefaultOrCustomModel( "models/headcrab.mdl" ) ); PrecacheScriptSound( "NPC_FastHeadcrab.Idle" ); PrecacheScriptSound( "NPC_FastHeadcrab.Alert" ); @@ -2560,7 +2589,7 @@ void CFastHeadcrab::Precache( void ) void CFastHeadcrab::Spawn( void ) { Precache(); - SetModel( "models/headcrab.mdl" ); + SetModel( DefaultOrCustomModel( "models/headcrab.mdl" ) ); BaseClass::Spawn(); @@ -3060,7 +3089,7 @@ void CBlackHeadcrab::TelegraphSound( void ) void CBlackHeadcrab::Spawn( void ) { Precache(); - SetModel( "models/headcrabblack.mdl" ); + SetModel( DefaultOrCustomModel( "models/headcrabblack.mdl" ) ); BaseClass::Spawn(); @@ -3077,7 +3106,7 @@ void CBlackHeadcrab::Spawn( void ) //----------------------------------------------------------------------------- void CBlackHeadcrab::Precache( void ) { - PrecacheModel( "models/headcrabblack.mdl" ); + PrecacheModel( DefaultOrCustomModel( "models/headcrabblack.mdl" ) ); PrecacheScriptSound( "NPC_BlackHeadcrab.Telegraph" ); PrecacheScriptSound( "NPC_BlackHeadcrab.Attack" ); diff --git a/src/game/server/hl2/npc_headcrab.h b/src/game/server/hl2/npc_headcrab.h index daf79b46..8c4103ad 100644 --- a/src/game/server/hl2/npc_headcrab.h +++ b/src/game/server/hl2/npc_headcrab.h @@ -149,6 +149,10 @@ protected: bool m_bHangingFromCeiling; float m_flIlluminatedTime; + +#ifdef MAPBASE + COutputEvent m_OnLeap; +#endif }; diff --git a/src/game/server/hl2/npc_kleiner.cpp b/src/game/server/hl2/npc_kleiner.cpp index e8081762..4e5e5cf8 100644 --- a/src/game/server/hl2/npc_kleiner.cpp +++ b/src/game/server/hl2/npc_kleiner.cpp @@ -35,6 +35,11 @@ public: Class_T Classify ( void ); void HandleAnimEvent( animevent_t *pEvent ); int GetSoundInterests ( void ); + +#ifdef MAPBASE + // Use Kleiner's default subtitle color (255,255,200) + bool GetGameTextSpeechParams( hudtextparms_t ¶ms ) { params.r1 = 255; params.g1 = 255; params.b1 = 200; return BaseClass::GetGameTextSpeechParams( params ); } +#endif }; LINK_ENTITY_TO_CLASS( npc_kleiner, CNPC_Kleiner ); diff --git a/src/game/server/hl2/npc_launcher.cpp b/src/game/server/hl2/npc_launcher.cpp index 9e942862..817bab23 100644 --- a/src/game/server/hl2/npc_launcher.cpp +++ b/src/game/server/hl2/npc_launcher.cpp @@ -62,6 +62,9 @@ public: // Outputs // ---------------- COutputEvent m_OnLaunch; // Triggered when missile is launched. +#ifdef MAPBASE + COutputEHANDLE m_OutMissile; // Passes the missile. +#endif // ---------------- // Inputs @@ -126,6 +129,9 @@ BEGIN_DATADESC( CNPC_Launcher ) DEFINE_INPUTFUNC( FIELD_VOID, "ClearEnemyEntity", InputClearEnemy ), DEFINE_OUTPUT( m_OnLaunch, "OnLaunch" ), +#ifdef MAPBASE + DEFINE_OUTPUT( m_OutMissile, "OutMissile" ), +#endif // Function Pointers DEFINE_THINKFUNC( LauncherThink ), @@ -278,6 +284,13 @@ void CNPC_Launcher::LaunchGrenade( CBaseEntity* pEnemy ) pGrenade->SetDamage(m_flDamage); pGrenade->SetDamageRadius(m_flDamageRadius); pGrenade->Launch(m_flLaunchSpeed,m_sPathCornerName); + +#ifdef MAPBASE + if (GetOwnerEntity()) + pGrenade->SetOwnerEntity(GetOwnerEntity()); + + m_OutMissile.Set(pGrenade, pGrenade, this); +#endif } else { @@ -292,6 +305,13 @@ void CNPC_Launcher::LaunchGrenade( CBaseEntity* pEnemy ) pGrenade->SetDamage(m_flDamage); pGrenade->SetDamageRadius(m_flDamageRadius); pGrenade->Launch(this,pEnemy,vLaunchVelocity,m_flHomingSpeed,GetGravity(),m_nSmokeTrail); + +#ifdef MAPBASE + if (GetOwnerEntity()) + pGrenade->SetOwnerEntity(GetOwnerEntity()); + + m_OutMissile.Set(pGrenade, pGrenade, this); +#endif } CPASAttenuationFilter filter( this, 0.3 ); diff --git a/src/game/server/hl2/npc_manhack.cpp b/src/game/server/hl2/npc_manhack.cpp index feef84f8..e221e938 100644 --- a/src/game/server/hl2/npc_manhack.cpp +++ b/src/game/server/hl2/npc_manhack.cpp @@ -78,6 +78,10 @@ #define MANHACK_CHARGE_MIN_DIST 200 +#if defined(MAPBASE) && defined(HL2_EPISODIC) +extern ConVar npc_alyx_interact_manhacks; +#endif + ConVar sk_manhack_health( "sk_manhack_health","0"); ConVar sk_manhack_melee_dmg( "sk_manhack_melee_dmg","0"); ConVar sk_manhack_v2( "sk_manhack_v2","1"); @@ -149,7 +153,11 @@ BEGIN_DATADESC( CNPC_Manhack ) DEFINE_FIELD( m_bGib, FIELD_BOOLEAN), DEFINE_FIELD( m_bHeld, FIELD_BOOLEAN), +#ifdef MAPBASE + DEFINE_KEYFIELD( m_bHackedByAlyx, FIELD_BOOLEAN, "Hacked" ), +#else DEFINE_FIELD( m_bHackedByAlyx, FIELD_BOOLEAN), +#endif DEFINE_FIELD( m_vecLoiterPosition, FIELD_POSITION_VECTOR), DEFINE_FIELD( m_fTimeNextLoiterPulse, FIELD_TIME), @@ -159,6 +167,10 @@ BEGIN_DATADESC( CNPC_Manhack ) DEFINE_FIELD( m_flBladeSpeed, FIELD_FLOAT), DEFINE_KEYFIELD( m_bIgnoreClipbrushes, FIELD_BOOLEAN, "ignoreclipbrushes" ), DEFINE_FIELD( m_hSmokeTrail, FIELD_EHANDLE), +#ifdef MAPBASE + DEFINE_FIELD( m_hPrevOwner, FIELD_EHANDLE ), + DEFINE_KEYFIELD( m_bNoSprites, FIELD_BOOLEAN, "NoSprites" ), +#endif // DEFINE_FIELD( m_pLightGlow, FIELD_CLASSPTR ), // DEFINE_FIELD( m_pEyeGlow, FIELD_CLASSPTR ), @@ -187,6 +199,10 @@ BEGIN_DATADESC( CNPC_Manhack ) // Function Pointers DEFINE_INPUTFUNC( FIELD_VOID, "DisableSwarm", InputDisableSwarm ), DEFINE_INPUTFUNC( FIELD_VOID, "Unpack", InputUnpack ), +#ifdef MAPBASE + DEFINE_INPUTFUNC( FIELD_VOID, "EnableSprites", InputEnableSprites ), + DEFINE_INPUTFUNC( FIELD_VOID, "DisableSprites", InputDisableSprites ), +#endif DEFINE_ENTITYFUNC( CrashTouch ), @@ -1519,7 +1535,11 @@ void CNPC_Manhack::Slice( CBaseEntity *pHitEntity, float flInterval, trace_t &tr pHitEntity->TakeDamage( info ); // Spawn some extra blood where we hit +#ifdef MAPBASE + if ( pHitEntity->BloodColor() == DONT_BLEED || (IRelationType(pHitEntity) > D_FR && !pHitEntity->PassesDamageFilter(info)) ) +#else if ( pHitEntity->BloodColor() == DONT_BLEED ) +#endif { CEffectData data; Vector velocity = GetCurrentVelocity(); @@ -2173,9 +2193,9 @@ void CNPC_Manhack::Precache(void) // // Model. // - PrecacheModel("models/manhack.mdl"); + PrecacheModel( DefaultOrCustomModel( "models/manhack.mdl" ) ); PrecacheModel( MANHACK_GLOW_SPRITE ); - PropBreakablePrecacheAll( MAKE_STRING("models/manhack.mdl") ); + PropBreakablePrecacheAll( MAKE_STRING( DefaultOrCustomModel( "models/manhack.mdl" ) ) ); PrecacheScriptSound( "NPC_Manhack.Die" ); PrecacheScriptSound( "NPC_Manhack.Bat" ); @@ -2369,7 +2389,7 @@ void CNPC_Manhack::Spawn(void) AddSpawnFlags( SF_NPC_FADE_CORPSE ); #endif // _XBOX - SetModel( "models/manhack.mdl" ); + SetModel( DefaultOrCustomModel( "models/manhack.mdl" ) ); SetHullType(HULL_TINY_CENTERED); SetHullSizeNormal(); @@ -2453,7 +2473,9 @@ void CNPC_Manhack::Spawn(void) SetCollisionGroup( COLLISION_GROUP_NONE ); m_bHeld = false; +#ifndef MAPBASE m_bHackedByAlyx = false; +#endif StopLoitering(); } @@ -2462,6 +2484,11 @@ void CNPC_Manhack::Spawn(void) //----------------------------------------------------------------------------- void CNPC_Manhack::StartEye( void ) { +#ifdef MAPBASE + if (m_bNoSprites) + return; +#endif + //Create our Eye sprite if ( m_pEyeGlow == NULL ) { @@ -2981,6 +3008,26 @@ void CNPC_Manhack::InputUnpack( inputdata_t &inputdata ) SetCondition( COND_LIGHT_DAMAGE ); } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: Creates the sprite if it has been destroyed +//----------------------------------------------------------------------------- +void CNPC_Manhack::InputEnableSprites( inputdata_t &inputdata ) +{ + m_bNoSprites = false; + StartEye(); +} + +//----------------------------------------------------------------------------- +// Purpose: Destroys the sprite +//----------------------------------------------------------------------------- +void CNPC_Manhack::InputDisableSprites( inputdata_t &inputdata ) +{ + KillSprites( 0.0 ); + m_bNoSprites = true; +} +#endif + //----------------------------------------------------------------------------- // Purpose: // Input : *pPhysGunUser - @@ -3007,6 +3054,11 @@ void CNPC_Manhack::OnPhysGunPickup( CBasePlayer *pPhysGunUser, PhysGunPickup_t r } else { +#ifdef MAPBASE + // Store the previous owner in case of npc_maker + m_hPrevOwner.Set(GetOwnerEntity()); +#endif + // Suppress collisions between the manhack and the player; we're currently bumping // almost certainly because it's not purely a physics object. SetOwnerEntity( pPhysGunUser ); @@ -3023,7 +3075,13 @@ void CNPC_Manhack::OnPhysGunPickup( CBasePlayer *pPhysGunUser, PhysGunPickup_t r void CNPC_Manhack::OnPhysGunDrop( CBasePlayer *pPhysGunUser, PhysGunDrop_t Reason ) { // Stop suppressing collisions between the manhack and the player +#ifndef MAPBASE SetOwnerEntity( NULL ); +#else + SetOwnerEntity( m_hPrevOwner ); + + m_hPrevOwner = NULL; +#endif m_bHeld = false; @@ -3088,6 +3146,20 @@ float CNPC_Manhack::GetMaxEnginePower() return 1.0f; } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: Option to restore Alyx's interactions with non-rollermines +//----------------------------------------------------------------------------- +bool CNPC_Manhack::CanInteractWith( CAI_BaseNPC *pUser ) +{ +#ifdef HL2_EPISODIC + return npc_alyx_interact_manhacks.GetBool(); +#else + return false; +#endif +} +#endif + //----------------------------------------------------------------------------- // Purpose: @@ -3199,16 +3271,43 @@ void CNPC_Manhack::SetEyeState( int state ) if ( m_pEyeGlow ) { //Toggle our state +#ifdef MAPBASE + // Makes it easier to distinguish between hostile and friendly manhacks. + if( m_bHackedByAlyx ) + { + m_pEyeGlow->SetColor( 0, 0, 255 ); + m_pEyeGlow->SetScale( 0.35f, 0.6f ); + } + else + { + m_pEyeGlow->SetColor( 255, 128, 0 ); + m_pEyeGlow->SetScale( 0.15f, 0.1f ); + } +#else m_pEyeGlow->SetColor( 255, 128, 0 ); m_pEyeGlow->SetScale( 0.15f, 0.1f ); +#endif m_pEyeGlow->SetBrightness( 164, 0.1f ); m_pEyeGlow->m_nRenderFX = kRenderFxStrobeFast; } if ( m_pLightGlow ) { +#ifdef MAPBASE + if( m_bHackedByAlyx ) + { + m_pLightGlow->SetColor( 0, 0, 255 ); + m_pLightGlow->SetScale( 0.35f, 0.6f ); + } + else + { + m_pLightGlow->SetColor( 255, 128, 0 ); + m_pLightGlow->SetScale( 0.15f, 0.1f ); + } +#else m_pLightGlow->SetColor( 255, 128, 0 ); m_pLightGlow->SetScale( 0.15f, 0.1f ); +#endif m_pLightGlow->SetBrightness( 164, 0.1f ); m_pLightGlow->m_nRenderFX = kRenderFxStrobeFast; } diff --git a/src/game/server/hl2/npc_manhack.h b/src/game/server/hl2/npc_manhack.h index 17a3cedb..a080f0eb 100644 --- a/src/game/server/hl2/npc_manhack.h +++ b/src/game/server/hl2/npc_manhack.h @@ -145,6 +145,10 @@ public: void InputDisableSwarm( inputdata_t &inputdata ); void InputUnpack( inputdata_t &inputdata ); +#ifdef MAPBASE + void InputEnableSprites( inputdata_t &inputdata ); + void InputDisableSprites( inputdata_t &inputdata ); +#endif // CDefaultPlayerPickupVPhysics virtual void OnPhysGunPickup( CBasePlayer *pPhysGunUser, PhysGunPickup_t reason ); @@ -155,7 +159,11 @@ public: float GetMaxEnginePower(); // INPCInteractive Functions +#ifdef MAPBASE + virtual bool CanInteractWith( CAI_BaseNPC *pUser ); +#else virtual bool CanInteractWith( CAI_BaseNPC *pUser ) { return false; } // Disabled for now (sjb) +#endif virtual bool HasBeenInteractedWith() { return m_bHackedByAlyx; } virtual void NotifyInteraction( CAI_BaseNPC *pUser ) { @@ -163,6 +171,9 @@ public: KillSprites(0.0f); m_bHackedByAlyx = true; StartEye(); +#ifdef MAPBASE + m_OnHacked.FireOutput(pUser, this); +#endif } virtual void InputPowerdown( inputdata_t &inputdata ) @@ -254,6 +265,11 @@ private: CSprite *m_pLightGlow; CHandle m_hSmokeTrail; +#ifdef MAPBASE + EHANDLE m_hPrevOwner; + + bool m_bNoSprites; +#endif int m_iPanel1; int m_iPanel2; diff --git a/src/game/server/hl2/npc_metropolice.cpp b/src/game/server/hl2/npc_metropolice.cpp index ea8b2065..63a534e5 100644 --- a/src/game/server/hl2/npc_metropolice.cpp +++ b/src/game/server/hl2/npc_metropolice.cpp @@ -19,6 +19,10 @@ #include "iservervehicle.h" #include "items.h" #include "hl2_gamerules.h" +#ifdef MAPBASE +#include "grenade_frag.h" +#include "mapbase/GlobalStrings.h" +#endif // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" @@ -119,6 +123,10 @@ ConVar metropolice_chase_use_follow( "metropolice_chase_use_follow", "0" ); ConVar metropolice_move_and_melee("metropolice_move_and_melee", "1" ); ConVar metropolice_charge("metropolice_charge", "1" ); +#ifdef MAPBASE +ConVar metropolice_new_component_behavior("metropolice_new_component_behavior", "1"); +#endif + // How many clips of pistol ammo a metropolice carries. #define METROPOLICE_NUM_CLIPS 5 #define METROPOLICE_BURST_RELOAD_COUNT 20 @@ -172,7 +180,9 @@ BEGIN_DATADESC( CNPC_MetroPolice ) DEFINE_KEYFIELD( m_fWeaponDrawn, FIELD_BOOLEAN, "weapondrawn" ), DEFINE_FIELD( m_LastShootSlot, FIELD_INTEGER ), DEFINE_EMBEDDED( m_TimeYieldShootSlot ), +#ifndef METROPOLICE_USES_RESPONSE_SYSTEM DEFINE_EMBEDDED( m_Sentences ), +#endif DEFINE_FIELD( m_bPlayerIsNear, FIELD_BOOLEAN ), DEFINE_FIELD( m_vecBurstTargetPos, FIELD_POSITION_VECTOR ), @@ -225,13 +235,34 @@ BEGIN_DATADESC( CNPC_MetroPolice ) DEFINE_KEYFIELD( m_iManhacks, FIELD_INTEGER, "manhacks" ), DEFINE_INPUTFUNC( FIELD_VOID, "EnableManhackToss", InputEnableManhackToss ), +#ifdef MAPBASE + DEFINE_INPUTFUNC( FIELD_VOID, "DisableManhackToss", InputDisableManhackToss ), + DEFINE_INPUTFUNC( FIELD_VOID, "DeployManhack", InputDeployManhack ), + DEFINE_INPUTFUNC( FIELD_INTEGER, "AddManhacks", InputAddManhacks ), + DEFINE_INPUTFUNC( FIELD_INTEGER, "SetManhacks", InputSetManhacks ), +#endif DEFINE_INPUTFUNC( FIELD_STRING, "SetPoliceGoal", InputSetPoliceGoal ), DEFINE_INPUTFUNC( FIELD_VOID, "ActivateBaton", InputActivateBaton ), +#ifdef MAPBASE + DEFINE_INPUTFUNC( FIELD_VOID, "AdministerJustice", InputAdministerJustice ), + DEFINE_INPUTFUNC( FIELD_INTEGER, "AddWarnings", InputAddWarnings ), + DEFINE_INPUTFUNC( FIELD_INTEGER, "SetWarnings", InputSetWarnings ), +#endif DEFINE_USEFUNC( PrecriminalUse ), DEFINE_OUTPUT( m_OnStunnedPlayer, "OnStunnedPlayer" ), DEFINE_OUTPUT( m_OnCupCopped, "OnCupCopped" ), +#ifdef MAPBASE + DEFINE_OUTPUT( m_OnHitByPhysicsObject, "OnHitByPhysicsObject" ), + DEFINE_OUTPUT( m_OutManhack, "OutManhack" ), +#endif + +#ifdef MAPBASE + DEFINE_AIGRENADE_DATADESC() + DEFINE_INPUT( m_iGrenadeCapabilities, FIELD_INTEGER, "SetGrenadeCapabilities" ), + DEFINE_INPUT( m_iGrenadeDropCapabilities, FIELD_INTEGER, "SetGrenadeDropCapabilities" ), +#endif END_DATADESC() @@ -333,7 +364,11 @@ public: if ( pBCC && pVictimBCC ) { // Can only damage other NPCs that we hate +#ifdef MAPBASE + if ( m_bDamageAnyNPC || pBCC->IRelationType( pEntity ) <= D_FR || pEntity->IsPlayer() ) +#else if ( m_bDamageAnyNPC || pBCC->IRelationType( pEntity ) == D_HT || pEntity->IsPlayer() ) +#endif { if ( info.GetDamage() ) { @@ -434,9 +469,20 @@ void CNPC_MetroPolice::NotifyDeadFriend( CBaseEntity* pFriend ) { BaseClass::NotifyDeadFriend(pFriend); +#ifdef MAPBASE + // m_hManhack is set to NULL after it's finished deploying, which means this has no chance of playing unless the manhack + // was still being deployed. This is thought to be an unintended oversight. + // Mapbase stores the metrocop thrower as the manhack's owner entity, so this code is now able to check that instead. + if ( pFriend->GetOwnerEntity() == this ) +#else if ( pFriend == m_hManhack ) +#endif { +#ifdef METROPOLICE_USES_RESPONSE_SYSTEM + SpeakIfAllowed(TLK_COP_MANHACKKILLED, "my_manhack:1", SENTENCE_PRIORITY_NORMAL, SENTENCE_CRITERIA_NORMAL); +#else m_Sentences.Speak( "METROPOLICE_MANHACK_KILLED", SENTENCE_PRIORITY_NORMAL, SENTENCE_CRITERIA_NORMAL ); +#endif DevMsg("My manhack died!\n"); m_hManhack = NULL; return; @@ -452,6 +498,9 @@ void CNPC_MetroPolice::NotifyDeadFriend( CBaseEntity* pFriend ) m_nIdleChatterType = METROPOLICE_CHATTER_ASK_QUESTION; } +#ifdef METROPOLICE_USES_RESPONSE_SYSTEM + SpeakIfAllowed(TLK_COP_MANDOWN, SENTENCE_PRIORITY_MEDIUM, SENTENCE_CRITERIA_NORMAL); +#else if ( GetSquad()->NumMembers() < 2 ) { m_Sentences.Speak( "METROPOLICE_LAST_OF_SQUAD", SENTENCE_PRIORITY_MEDIUM, SENTENCE_CRITERIA_NORMAL ); @@ -459,6 +508,7 @@ void CNPC_MetroPolice::NotifyDeadFriend( CBaseEntity* pFriend ) } m_Sentences.Speak( "METROPOLICE_MAN_DOWN", SENTENCE_PRIORITY_MEDIUM ); +#endif } @@ -466,6 +516,14 @@ void CNPC_MetroPolice::NotifyDeadFriend( CBaseEntity* pFriend ) //----------------------------------------------------------------------------- CNPC_MetroPolice::CNPC_MetroPolice() { +#ifdef MAPBASE + m_iGrenadeCapabilities = GRENCAP_GRENADE; + + if (ai_grenade_always_drop.GetBool()) + { + m_iGrenadeDropCapabilities = (eGrenadeDropCapabilities)(GRENDROPCAP_GRENADE | GRENDROPCAP_ALTFIRE | GRENDROPCAP_INTERRUPTED); + } +#endif } @@ -476,10 +534,12 @@ void CNPC_MetroPolice::OnScheduleChange() { BaseClass::OnScheduleChange(); +#ifndef MAPBASE // Moved to Event_KilledOther() if ( GetEnemy() && HasCondition( COND_ENEMY_DEAD ) ) { AnnounceEnemyKill( GetEnemy() ); } +#endif } @@ -490,8 +550,10 @@ void CNPC_MetroPolice::PrescheduleThink( void ) { BaseClass::PrescheduleThink(); +#ifndef METROPOLICE_USES_RESPONSE_SYSTEM // Speak any queued sentences m_Sentences.UpdateSentenceQueue(); +#endif // Look at near players, always m_bPlayerIsNear = false; @@ -572,6 +634,11 @@ bool CNPC_MetroPolice::OverrideMoveFacing( const AILocalMoveGoal_t &move, float //----------------------------------------------------------------------------- void CNPC_MetroPolice::Precache( void ) { +#ifdef MAPBASE + // It doesn't matter if we can't find models/police.mdl in the string pool because then we know this NPC doesn't have that model. + if ( GetModelName() == NULL_STRING || GetModelName() == FindPooledString("models/police.mdl") ) + { +#endif if ( HasSpawnFlags( SF_NPC_START_EFFICIENT ) ) { SetModelName( AllocPooledString("models/police_cheaple.mdl" ) ); @@ -580,6 +647,9 @@ void CNPC_MetroPolice::Precache( void ) { SetModelName( AllocPooledString("models/police.mdl") ); } +#ifdef MAPBASE + } +#endif PrecacheModel( STRING( GetModelName() ) ); @@ -602,7 +672,9 @@ bool CNPC_MetroPolice::CreateComponents() if ( !BaseClass::CreateComponents() ) return false; +#ifndef METROPOLICE_USES_RESPONSE_SYSTEM m_Sentences.Init( this, "NPC_Metropolice.SentenceParameters" ); +#endif return true; } @@ -681,7 +753,11 @@ void CNPC_MetroPolice::Spawn( void ) pWeapon = GetActiveWeapon(); +#ifdef MAPBASE + if (!EntIsClass(pWeapon, gm_isz_class_Pistol) && !EntIsClass(pWeapon, gm_isz_class_357)) +#else if( !FClassnameIs( pWeapon, "weapon_pistol" ) ) +#endif { m_fWeaponDrawn = true; } @@ -706,7 +782,11 @@ void CNPC_MetroPolice::Spawn( void ) // Clear out spawnflag if we're missing the smg1 if( HasSpawnFlags( SF_METROPOLICE_ALWAYS_STITCH ) ) { +#ifdef MAPBASE + if ( !Weapon_OwnsThisType( STRING(gm_isz_class_SMG1) ) ) +#else if ( !Weapon_OwnsThisType( "weapon_smg1" ) ) +#endif { Warning( "Warning! Metrocop is trying to use the stitch behavior but he has no smg1!\n" ); RemoveSpawnFlags( SF_METROPOLICE_ALWAYS_STITCH ); @@ -752,6 +832,23 @@ void CNPC_MetroPolice::SpeakFuncTankSentence( int nSentenceType ) { switch ( nSentenceType ) { +#ifdef METROPOLICE_USES_RESPONSE_SYSTEM + case FUNCTANK_SENTENCE_MOVE_TO_MOUNT: + SpeakIfAllowed( TLK_COP_FT_APPROACH, SENTENCE_PRIORITY_MEDIUM ); + break; + + case FUNCTANK_SENTENCE_JUST_MOUNTED: + SpeakIfAllowed( TLK_COP_FT_MOUNT, SENTENCE_PRIORITY_HIGH ); + break; + + case FUNCTANK_SENTENCE_SCAN_FOR_ENEMIES: + SpeakIfAllowed( TLK_COP_FT_SCAN, SENTENCE_PRIORITY_NORMAL ); + break; + + case FUNCTANK_SENTENCE_DISMOUNTING: + SpeakIfAllowed( TLK_COP_FT_DISMOUNT, SENTENCE_PRIORITY_HIGH ); + break; +#else case FUNCTANK_SENTENCE_MOVE_TO_MOUNT: m_Sentences.Speak( "METROPOLICE_FT_APPROACH", SENTENCE_PRIORITY_MEDIUM ); break; @@ -767,6 +864,7 @@ void CNPC_MetroPolice::SpeakFuncTankSentence( int nSentenceType ) case FUNCTANK_SENTENCE_DISMOUNTING: m_Sentences.Speak( "METROPOLICE_FT_DISMOUNT", SENTENCE_PRIORITY_HIGH ); break; +#endif } } @@ -778,6 +876,31 @@ void CNPC_MetroPolice::SpeakStandoffSentence( int nSentenceType ) { switch ( nSentenceType ) { +#ifdef METROPOLICE_USES_RESPONSE_SYSTEM + case STANDOFF_SENTENCE_BEGIN_STANDOFF: + SpeakIfAllowed( TLK_COP_SO_BEGIN, SENTENCE_PRIORITY_HIGH, SENTENCE_CRITERIA_SQUAD_LEADER ); + break; + + case STANDOFF_SENTENCE_END_STANDOFF: + SpeakIfAllowed( TLK_COP_SO_END, SENTENCE_PRIORITY_HIGH, SENTENCE_CRITERIA_SQUAD_LEADER ); + break; + + case STANDOFF_SENTENCE_OUT_OF_AMMO: + AnnounceOutOfAmmo( ); + break; + + case STANDOFF_SENTENCE_FORCED_TAKE_COVER: + SpeakIfAllowed( TLK_COP_SO_FORCE_COVER ); + break; + + case STANDOFF_SENTENCE_STAND_CHECK_TARGET: + if ( gm_flTimeLastSpokePeek != 0 && gpGlobals->curtime - gm_flTimeLastSpokePeek > 20 ) + { + SpeakIfAllowed( TLK_COP_SO_PEEK ); + gm_flTimeLastSpokePeek = gpGlobals->curtime; + } + break; +#else case STANDOFF_SENTENCE_BEGIN_STANDOFF: m_Sentences.Speak( "METROPOLICE_SO_BEGIN", SENTENCE_PRIORITY_HIGH, SENTENCE_CRITERIA_SQUAD_LEADER ); break; @@ -801,6 +924,7 @@ void CNPC_MetroPolice::SpeakStandoffSentence( int nSentenceType ) gm_flTimeLastSpokePeek = gpGlobals->curtime; } break; +#endif } } @@ -811,6 +935,37 @@ void CNPC_MetroPolice::SpeakAssaultSentence( int nSentenceType ) { switch ( nSentenceType ) { +#ifdef METROPOLICE_USES_RESPONSE_SYSTEM + case ASSAULT_SENTENCE_HIT_RALLY_POINT: + SpeakIfAllowed( TLK_COP_AS_HIT_RALLY, SENTENCE_PRIORITY_NORMAL ); + break; + + case ASSAULT_SENTENCE_HIT_ASSAULT_POINT: + SpeakIfAllowed( TLK_COP_AS_HIT_ASSAULT, SENTENCE_PRIORITY_NORMAL ); + break; + + case ASSAULT_SENTENCE_SQUAD_ADVANCE_TO_RALLY: + if ( SpeakIfAllowed( TLK_COP_AS_ADV_RALLY, SENTENCE_PRIORITY_MEDIUM, SENTENCE_CRITERIA_SQUAD_LEADER ) ) + { + GetSquad()->BroadcastInteraction( g_interactionMetrocopClearSentenceQueues, NULL ); + } + break; + + case ASSAULT_SENTENCE_SQUAD_ADVANCE_TO_ASSAULT: + if ( SpeakIfAllowed( TLK_COP_AS_ADV_ASSAULT, SENTENCE_PRIORITY_MEDIUM, SENTENCE_CRITERIA_SQUAD_LEADER ) ) + { + GetSquad()->BroadcastInteraction( g_interactionMetrocopClearSentenceQueues, NULL ); + } + break; + + case ASSAULT_SENTENCE_COVER_NO_AMMO: + AnnounceOutOfAmmo( ); + break; + + case ASSAULT_SENTENCE_UNDER_ATTACK: + SpeakIfAllowed( TLK_COP_GO_ALERT ); + break; +#else case ASSAULT_SENTENCE_HIT_RALLY_POINT: m_Sentences.SpeakQueued( "METROPOLICE_AS_HIT_RALLY", SENTENCE_PRIORITY_NORMAL ); break; @@ -840,6 +995,7 @@ void CNPC_MetroPolice::SpeakAssaultSentence( int nSentenceType ) case ASSAULT_SENTENCE_UNDER_ATTACK: m_Sentences.Speak( "METROPOLICE_GO_ALERT" ); break; +#endif } } @@ -860,7 +1016,12 @@ void CNPC_MetroPolice::SpeakSentence( int nSentenceType ) return; } +#ifdef MAPBASE + // Fixed issues with standoff sentences not playing when they should + if ( m_StandoffBehavior.IsActive() ) +#else if ( GetRunningBehavior() == &m_StandoffBehavior ) +#endif { SpeakStandoffSentence( nSentenceType ); return; @@ -875,6 +1036,70 @@ void CNPC_MetroPolice::SpeakSentence( int nSentenceType ) switch ( nSentenceType ) { +#ifdef METROPOLICE_USES_RESPONSE_SYSTEM + case METROPOLICE_SENTENCE_FREEZE: + SpeakIfAllowed( TLK_COP_FREEZE, SENTENCE_PRIORITY_MEDIUM, SENTENCE_CRITERIA_NORMAL ); + break; + + case METROPOLICE_SENTENCE_HES_OVER_HERE: + SpeakIfAllowed( TLK_COP_OVER_HERE, SENTENCE_PRIORITY_MEDIUM, SENTENCE_CRITERIA_NORMAL ); + break; + + case METROPOLICE_SENTENCE_HES_RUNNING: + SpeakIfAllowed( TLK_COP_HES_RUNNING, SENTENCE_PRIORITY_HIGH, SENTENCE_CRITERIA_NORMAL ); + break; + + case METROPOLICE_SENTENCE_TAKE_HIM_DOWN: + SpeakIfAllowed( TLK_COP_TAKE_HIM_DOWN, SENTENCE_PRIORITY_HIGH, SENTENCE_CRITERIA_NORMAL ); + break; + + case METROPOLICE_SENTENCE_ARREST_IN_POSITION: + SpeakIfAllowed( TLK_COP_ARREST_IN_POS, SENTENCE_PRIORITY_MEDIUM, SENTENCE_CRITERIA_NORMAL ); + break; + + case METROPOLICE_SENTENCE_DEPLOY_MANHACK: + SpeakIfAllowed( TLK_COP_DEPLOY_MANHACK ); + break; + + case METROPOLICE_SENTENCE_MOVE_INTO_POSITION: + { + CBaseEntity *pEntity = GetEnemy(); + + // NOTE: This is a good time to check to see if the player is hurt. + // Have the cops notice this and call out + if ( pEntity && !HasSpawnFlags( SF_METROPOLICE_ARREST_ENEMY ) ) + { + if ( pEntity->IsPlayer() && (pEntity->GetHealth() <= 20) ) + { + if ( !HasMemory(bits_MEMORY_PLAYER_HURT) ) + { + if ( SpeakIfAllowed( TLK_COP_PLAYERHIT, SENTENCE_PRIORITY_HIGH ) ) + { +#ifdef MAPBASE + if (GetSquad()) + GetSquad()->SquadRemember(bits_MEMORY_PLAYER_HURT); +#else + m_pSquad->SquadRemember(bits_MEMORY_PLAYER_HURT); +#endif + } + } + } + + if ( GetNavigator()->GetPath()->GetPathLength() > 20 * 12.0f ) + { + SpeakIfAllowed( TLK_COP_FLANK ); + } + } + } + break; + + case METROPOLICE_SENTENCE_HEARD_SOMETHING: + if ( ( GetState() == NPC_STATE_ALERT ) || ( GetState() == NPC_STATE_IDLE ) ) + { + SpeakIfAllowed( TLK_COP_HEARD_SOMETHING, SENTENCE_PRIORITY_MEDIUM ); + } + break; +#else case METROPOLICE_SENTENCE_FREEZE: m_Sentences.Speak( "METROPOLICE_FREEZE", SENTENCE_PRIORITY_MEDIUM, SENTENCE_CRITERIA_NORMAL ); break; @@ -932,9 +1157,56 @@ void CNPC_MetroPolice::SpeakSentence( int nSentenceType ) m_Sentences.Speak( "METROPOLICE_HEARD_SOMETHING", SENTENCE_PRIORITY_MEDIUM ); } break; +#endif } } +#ifdef METROPOLICE_USES_RESPONSE_SYSTEM +//========================================================= +//========================================================= +bool CNPC_MetroPolice::SpeakIfAllowed( const char *concept, const char *modifiers, SentencePriority_t sentencepriority, SentenceCriteria_t sentencecriteria ) +{ + AI_CriteriaSet set; + if (modifiers) + { +#ifdef NEW_RESPONSE_SYSTEM + GatherCriteria( &set, concept, modifiers ); +#else + GetExpresser()->MergeModifiers(set, modifiers); +#endif + } + return SpeakIfAllowed( concept, set, sentencepriority, sentencecriteria ); +} + +//========================================================= +//========================================================= +bool CNPC_MetroPolice::SpeakIfAllowed( const char *concept, AI_CriteriaSet& modifiers, SentencePriority_t sentencepriority, SentenceCriteria_t sentencecriteria ) +{ + if ( sentencepriority != SENTENCE_PRIORITY_INVALID && !FOkToMakeSound( sentencepriority ) ) + return false; + + if ( !GetExpresser()->CanSpeakConcept( concept ) ) + return false; + + if ( Speak( concept, modifiers ) ) + { + JustMadeSound( sentencepriority, 2.0f /*GetTimeSpeechComplete()*/ ); + return true; + } + + return false; +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CNPC_MetroPolice::ModifyOrAppendCriteria( AI_CriteriaSet& set ) +{ + BaseClass::ModifyOrAppendCriteria( set ); + + set.AppendCriteria( "numwarnings", UTIL_VarArgs("%d", m_nNumWarnings) ); +} +#endif + //----------------------------------------------------------------------------- // Speaking @@ -954,6 +1226,24 @@ void CNPC_MetroPolice::AnnounceEnemyType( CBaseEntity *pEnemy ) if ( m_pSquad->IsLeader( this ) || ( m_pSquad->GetLeader() && m_pSquad->GetLeader()->GetEnemy() != GetEnemy() ) ) { +#ifdef METROPOLICE_USES_RESPONSE_SYSTEM + // First contact, and I'm the squad leader. + bool bEnemyInVehicle = false; + switch ( pEnemy->Classify() ) + { + case CLASS_PLAYER: + { + CBasePlayer *pPlayer = assert_cast( pEnemy ); + if ( pPlayer && pPlayer->IsInAVehicle() ) + { + bEnemyInVehicle = true; + } + } + break; + } + + SpeakIfAllowed( TLK_COP_ENEMY, UTIL_VarArgs("enemy_in_vehicle:%d", bEnemyInVehicle), SENTENCE_PRIORITY_HIGH ); +#else // First contact, and I'm the squad leader. const char *pSentenceName = "METROPOLICE_MONST"; switch ( pEnemy->Classify() ) @@ -998,6 +1288,7 @@ void CNPC_MetroPolice::AnnounceEnemyType( CBaseEntity *pEnemy ) } m_Sentences.Speak( pSentenceName, SENTENCE_PRIORITY_HIGH ); +#endif } else { @@ -1019,6 +1310,11 @@ void CNPC_MetroPolice::AnnounceEnemyKill( CBaseEntity *pEnemy ) if ( !pEnemy ) return; +#ifdef METROPOLICE_USES_RESPONSE_SYSTEM + AI_CriteriaSet set; + ModifyOrAppendEnemyCriteria(set, pEnemy); + SpeakIfAllowed( TLK_COP_KILLENEMY, set, SENTENCE_PRIORITY_HIGH ); +#else const char *pSentenceName = "METROPOLICE_KILL_MONST"; switch ( pEnemy->Classify() ) { @@ -1053,6 +1349,7 @@ void CNPC_MetroPolice::AnnounceEnemyKill( CBaseEntity *pEnemy ) } m_Sentences.Speak( pSentenceName, SENTENCE_PRIORITY_HIGH ); +#endif } @@ -1061,6 +1358,16 @@ void CNPC_MetroPolice::AnnounceEnemyKill( CBaseEntity *pEnemy ) //----------------------------------------------------------------------------- void CNPC_MetroPolice::AnnounceOutOfAmmo( ) { +#ifdef METROPOLICE_USES_RESPONSE_SYSTEM + if ( HasCondition( COND_NO_PRIMARY_AMMO ) ) + { + SpeakIfAllowed( TLK_COP_NOAMMO ); + } + else + { + SpeakIfAllowed( TLK_COP_LOWAMMO ); + } +#else if ( HasCondition( COND_NO_PRIMARY_AMMO ) ) { m_Sentences.Speak( "METROPOLICE_COVER_NO_AMMO" ); @@ -1069,6 +1376,7 @@ void CNPC_MetroPolice::AnnounceOutOfAmmo( ) { m_Sentences.Speak( "METROPOLICE_COVER_LOW_AMMO" ); } +#endif } //----------------------------------------------------------------------------- @@ -1076,6 +1384,38 @@ void CNPC_MetroPolice::AnnounceOutOfAmmo( ) //----------------------------------------------------------------------------- void CNPC_MetroPolice::AnnounceTakeCoverFromDanger( CSound *pSound ) { +#ifdef METROPOLICE_USES_RESPONSE_SYSTEM + bool bGrenade = false; + bool bVehicle = false; + bool bManhack = false; + + CBaseEntity *pSoundOwner = pSound->m_hOwner; + if ( pSoundOwner ) + { + CBaseGrenade *pGrenade = dynamic_cast(pSoundOwner); + if ( pGrenade ) + { + if ( IRelationType( pGrenade->GetThrower() ) != D_LI ) + { + // special case call out for enemy grenades + bGrenade = true; + } + } + else if ( pSoundOwner->GetServerVehicle() ) + { + bVehicle = true; + } + else if ( FClassnameIs( pSoundOwner, "npc_manhack" ) ) + { + if ( pSoundOwner->HasPhysicsAttacker( 1.0f ) ) + { + bManhack = true; + } + } + } + + SpeakIfAllowed(TLK_COP_DANGER, UTIL_VarArgs("grenade:%d,vehicle:%d,manhack:%d", bGrenade, bVehicle, bManhack), SENTENCE_PRIORITY_HIGH, SENTENCE_CRITERIA_NORMAL); +#else CBaseEntity *pSoundOwner = pSound->m_hOwner; if ( pSoundOwner ) { @@ -1110,6 +1450,7 @@ void CNPC_MetroPolice::AnnounceTakeCoverFromDanger( CSound *pSound ) // dangerous sound nearby!, call it out const char *pSentenceName = "METROPOLICE_DANGER"; m_Sentences.Speak( pSentenceName, SENTENCE_PRIORITY_HIGH, SENTENCE_CRITERIA_NORMAL ); +#endif } @@ -1198,7 +1539,12 @@ void CNPC_MetroPolice::OnUpdateShotRegulator( ) BaseClass::OnUpdateShotRegulator(); // FIXME: This code (except the burst interval) could be used for all weapon types +#ifdef MAPBASE + // Only if we actually have the pistol out + if ( GetActiveWeapon() && EntIsClass( GetActiveWeapon(), gm_isz_class_Pistol ) ) +#else if( Weapon_OwnsThisType( "weapon_pistol" ) ) +#endif { if ( m_nBurstMode == BURST_NOT_ACTIVE ) { @@ -1298,6 +1644,11 @@ bool CNPC_MetroPolice::ShouldAttemptToStitch() //----------------------------------------------------------------------------- Vector CNPC_MetroPolice::StitchAimTarget( const Vector &posSrc, bool bNoisy ) { +#ifdef MAPBASE + if ( !GetEnemy() ) + return vec3_origin; +#endif + // This will make us aim a stitch at the feet of the player so we can see it if ( !GetEnemy()->IsPlayer() ) return GetShootTarget()->BodyTarget( posSrc, bNoisy ); @@ -2444,12 +2795,54 @@ void CNPC_MetroPolice::InputEnableManhackToss( inputdata_t &inputdata ) } } +#ifdef MAPBASE +void CNPC_MetroPolice::InputDisableManhackToss( inputdata_t &inputdata ) +{ + if ( !HasSpawnFlags( SF_METROPOLICE_NO_MANHACK_DEPLOY ) ) + { + AddSpawnFlags( SF_METROPOLICE_NO_MANHACK_DEPLOY ); + } +} + +void CNPC_MetroPolice::InputDeployManhack( inputdata_t &inputdata ) +{ + // I am aware this bypasses regular deployment conditions, but the mapper wants us to deploy a manhack, damn it! + // We do have to have one, though. + if ( m_iManhacks > 0 ) + { + SetSchedule(SCHED_METROPOLICE_DEPLOY_MANHACK); + } +} + +void CNPC_MetroPolice::InputAddManhacks( inputdata_t &inputdata ) +{ + m_iManhacks += inputdata.value.Int(); + + SetBodygroup( METROPOLICE_BODYGROUP_MANHACK, (m_iManhacks > 0) ); +} + +void CNPC_MetroPolice::InputSetManhacks( inputdata_t &inputdata ) +{ + m_iManhacks = inputdata.value.Int(); + + SetBodygroup( METROPOLICE_BODYGROUP_MANHACK, (m_iManhacks > 0) ); +} +#endif + //----------------------------------------------------------------------------- // Purpose: // Input : &inputdata - //----------------------------------------------------------------------------- void CNPC_MetroPolice::InputSetPoliceGoal( inputdata_t &inputdata ) { +#ifdef MAPBASE + if (/*!inputdata.value.String() ||*/ inputdata.value.String()[0] == 0) + { + m_PolicingBehavior.Disable(); + return; + } +#endif + CBaseEntity *pGoal = gEntList.FindEntityByName( NULL, inputdata.value.String() ); if ( pGoal == NULL ) @@ -2478,6 +2871,35 @@ void CNPC_MetroPolice::InputActivateBaton( inputdata_t &inputdata ) SetBatonState( inputdata.value.Bool() ); } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: +// Input : &inputdata - +//----------------------------------------------------------------------------- +void CNPC_MetroPolice::InputAdministerJustice( inputdata_t &inputdata ) +{ + AdministerJustice(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : &inputdata - +//----------------------------------------------------------------------------- +void CNPC_MetroPolice::InputAddWarnings( inputdata_t &inputdata ) +{ + m_nNumWarnings += inputdata.value.Int(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : &inputdata - +//----------------------------------------------------------------------------- +void CNPC_MetroPolice::InputSetWarnings( inputdata_t &inputdata ) +{ + m_nNumWarnings = inputdata.value.Int(); +} +#endif + //----------------------------------------------------------------------------- // Purpose: @@ -2485,7 +2907,11 @@ void CNPC_MetroPolice::InputActivateBaton( inputdata_t &inputdata ) //----------------------------------------------------------------------------- void CNPC_MetroPolice::AlertSound( void ) { +#ifdef METROPOLICE_USES_RESPONSE_SYSTEM + SpeakIfAllowed( TLK_COP_GO_ALERT ); +#else m_Sentences.Speak( "METROPOLICE_GO_ALERT" ); +#endif } @@ -2498,7 +2924,13 @@ void CNPC_MetroPolice::DeathSound( const CTakeDamageInfo &info ) if ( IsOnFire() ) return; +#ifdef METROPOLICE_USES_RESPONSE_SYSTEM + AI_CriteriaSet set; + ModifyOrAppendDamageCriteria(set, info); + SpeakIfAllowed( TLK_COP_DIE, set, SENTENCE_PRIORITY_INVALID, SENTENCE_CRITERIA_ALWAYS ); +#else m_Sentences.Speak( "METROPOLICE_DIE", SENTENCE_PRIORITY_INVALID, SENTENCE_CRITERIA_ALWAYS ); +#endif } @@ -2508,7 +2940,11 @@ void CNPC_MetroPolice::DeathSound( const CTakeDamageInfo &info ) // Input : // Output : //----------------------------------------------------------------------------- +#ifdef MAPBASE +void CNPC_MetroPolice::LostEnemySound( CBaseEntity *pEnemy ) +#else void CNPC_MetroPolice::LostEnemySound( void) +#endif { // Don't announce enemies when the player isn't a criminal if ( !PlayerIsCriminal() ) @@ -2517,6 +2953,17 @@ void CNPC_MetroPolice::LostEnemySound( void) if ( gpGlobals->curtime <= m_flNextLostSoundTime ) return; +#ifdef METROPOLICE_USES_RESPONSE_SYSTEM + AI_CriteriaSet modifiers; + ModifyOrAppendEnemyCriteria( modifiers, pEnemy ); + + modifiers.AppendCriteria( "lastseenenemy", gpGlobals->curtime - GetEnemies()->LastTimeSeen( pEnemy ) ); + + if (SpeakIfAllowed(TLK_COP_LOSTENEMY, modifiers )) + { + m_flNextLostSoundTime = gpGlobals->curtime + random->RandomFloat(5.0,15.0); + } +#else const char *pSentence; if (!(CBaseEntity*)GetEnemy() || gpGlobals->curtime - GetEnemyLastTimeSeen() > 10) { @@ -2531,6 +2978,7 @@ void CNPC_MetroPolice::LostEnemySound( void) { m_flNextLostSoundTime = gpGlobals->curtime + random->RandomFloat(5.0,15.0); } +#endif } @@ -2540,13 +2988,24 @@ void CNPC_MetroPolice::LostEnemySound( void) // Input : // Output : //----------------------------------------------------------------------------- +#ifdef MAPBASE +void CNPC_MetroPolice::FoundEnemySound( CBaseEntity *pEnemy ) +#else void CNPC_MetroPolice::FoundEnemySound( void) +#endif { // Don't announce enemies when I'm in arrest behavior if ( HasSpawnFlags( SF_METROPOLICE_ARREST_ENEMY ) ) return; +#ifdef METROPOLICE_USES_RESPONSE_SYSTEM + AI_CriteriaSet modifiers; + ModifyOrAppendEnemyCriteria( modifiers, pEnemy ); + + SpeakIfAllowed( TLK_COP_REFINDENEMY, modifiers, SENTENCE_PRIORITY_HIGH ); +#else m_Sentences.Speak( "METROPOLICE_REFIND_ENEMY", SENTENCE_PRIORITY_HIGH ); +#endif } @@ -2571,6 +3030,59 @@ bool CNPC_MetroPolice::ShouldPlayIdleSound( void ) //----------------------------------------------------------------------------- void CNPC_MetroPolice::IdleSound( void ) { +#ifdef METROPOLICE_USES_RESPONSE_SYSTEM + // This happens when the NPC is waiting for his buddies to respond to him + switch( m_nIdleChatterType ) + { + case METROPOLICE_CHATTER_WAIT_FOR_RESPONSE: + break; + + case METROPOLICE_CHATTER_ASK_QUESTION: + { + if ( m_bPlayerIsNear && !HasMemory(bits_MEMORY_PLAYER_HARASSED) ) + { + if ( SpeakIfAllowed( TLK_COP_HARASS, SENTENCE_PRIORITY_NORMAL, SENTENCE_CRITERIA_NORMAL ) ) + { + Remember( bits_MEMORY_PLAYER_HARASSED ); + if ( GetSquad() ) + { + GetSquad()->SquadRemember(bits_MEMORY_PLAYER_HARASSED); + } + } + return; + } + + if ( !random->RandomInt(0,1) ) + break; + + int nQuestionType = random->RandomInt( 0, METROPOLICE_CHATTER_RESPONSE_TYPE_COUNT ); + if ( !IsInSquad() || ( nQuestionType == METROPOLICE_CHATTER_RESPONSE_TYPE_COUNT ) ) + { + SpeakIfAllowed(TLK_COP_IDLE); + break; + } + + if ( SpeakIfAllowed( TLK_COP_QUESTION, UTIL_VarArgs("combinequestion:%d", nQuestionType) ) ) + { + GetSquad()->BroadcastInteraction( g_interactionMetrocopIdleChatter, (void*)(METROPOLICE_CHATTER_RESPONSE + nQuestionType), this ); + m_nIdleChatterType = METROPOLICE_CHATTER_WAIT_FOR_RESPONSE; + } + } + break; + + default: + { + int nResponseType = m_nIdleChatterType - METROPOLICE_CHATTER_RESPONSE; + + if ( SpeakIfAllowed( TLK_COP_ANSWER, UTIL_VarArgs("combinequestion:%d", nResponseType) ) ) + { + GetSquad()->BroadcastInteraction( g_interactionMetrocopIdleChatter, (void*)(METROPOLICE_CHATTER_ASK_QUESTION), this ); + m_nIdleChatterType = METROPOLICE_CHATTER_ASK_QUESTION; + } + } + break; + } +#else bool bIsCriminal = PlayerIsCriminal(); // This happens when the NPC is waiting for his buddies to respond to him @@ -2636,6 +3148,7 @@ void CNPC_MetroPolice::IdleSound( void ) } break; } +#endif } @@ -2651,6 +3164,12 @@ void CNPC_MetroPolice::PainSound( const CTakeDamageInfo &info ) if ( IsOnFire() ) return; +#ifdef METROPOLICE_USES_RESPONSE_SYSTEM + AI_CriteriaSet set; + ModifyOrAppendDamageCriteria(set, info); + SpeakIfAllowed(TLK_COP_PAIN, set, SENTENCE_PRIORITY_INVALID, SENTENCE_CRITERIA_ALWAYS); + m_flNextPainSoundTime = gpGlobals->curtime + 1; +#else float healthRatio = (float)GetHealth() / (float)GetMaxHealth(); if ( healthRatio > 0.0f ) { @@ -2670,6 +3189,7 @@ void CNPC_MetroPolice::PainSound( const CTakeDamageInfo &info ) m_Sentences.Speak( pSentenceName, SENTENCE_PRIORITY_INVALID, SENTENCE_CRITERIA_ALWAYS ); m_flNextPainSoundTime = gpGlobals->curtime + 1; } +#endif } //----------------------------------------------------------------------------- @@ -2769,7 +3289,7 @@ void CNPC_MetroPolice::OnAnimEventStartDeployManhack( void ) if ( m_iManhacks <= 0 ) { - DevMsg( "Error: Throwing manhack but out of manhacks!\n" ); + CGMsg( 1, CON_GROUP_NPC_AI, "Error: Throwing manhack but out of manhacks!\n" ); return; } @@ -2806,6 +3326,10 @@ void CNPC_MetroPolice::OnAnimEventStartDeployManhack( void ) pManhack->SetParent( this, handAttachment ); m_hManhack = pManhack; + +#ifdef MAPBASE + m_OutManhack.Set(m_hManhack->GetBaseEntity(), pManhack, this); +#endif } //----------------------------------------------------------------------------- @@ -2883,7 +3407,7 @@ void CNPC_MetroPolice::OnAnimEventShove( void ) //----------------------------------------------------------------------------- void CNPC_MetroPolice::OnAnimEventBatonOn( void ) { -#ifndef HL2MP +#if !defined(HL2MP) || defined(MAPBASE) CWeaponStunStick *pStick = dynamic_cast(GetActiveWeapon()); @@ -2900,7 +3424,7 @@ void CNPC_MetroPolice::OnAnimEventBatonOn( void ) //----------------------------------------------------------------------------- void CNPC_MetroPolice::OnAnimEventBatonOff( void ) { -#ifndef HL2MP +#if !defined(HL2MP) || defined(MAPBASE) CWeaponStunStick *pStick = dynamic_cast(GetActiveWeapon()); @@ -2989,7 +3513,9 @@ bool CNPC_MetroPolice::HandleInteraction(int interactionType, void *data, CBaseC if ( interactionType == g_interactionMetrocopClearSentenceQueues ) { +#ifndef METROPOLICE_USES_RESPONSE_SYSTEM m_Sentences.ClearQueue(); +#endif return true; } @@ -3010,8 +3536,15 @@ bool CNPC_MetroPolice::HandleInteraction(int interactionType, void *data, CBaseC CBaseProp *pProp = (CBaseProp*)data; if( pProp != NULL ) { +#ifdef MAPBASE + if( pProp->NameMatches("cupcop_can") ) + m_OnCupCopped.FireOutput( sourceEnt, this ); + + m_OnHitByPhysicsObject.Set(pProp, sourceEnt, this); +#else if( pProp->NameMatches("cupcop_can") ) m_OnCupCopped.FireOutput( this, NULL ); +#endif } return true; @@ -3033,7 +3566,11 @@ Activity CNPC_MetroPolice::NPC_TranslateActivity( Activity newActivity ) // If we're shoving, see if we should be more forceful in doing so if ( newActivity == ACT_PUSH_PLAYER ) { +#ifdef MAPBASE + if ( m_nNumWarnings >= METROPOLICE_MAX_WARNINGS && Weapon_TranslateActivity( ACT_MELEE_ATTACK1, NULL ) == ACT_MELEE_ATTACK_SWING ) +#else if ( m_nNumWarnings >= METROPOLICE_MAX_WARNINGS ) +#endif return ACT_MELEE_ATTACK1; } @@ -3046,9 +3583,71 @@ Activity CNPC_MetroPolice::NPC_TranslateActivity( Activity newActivity ) newActivity = ACT_IDLE_ANGRY; } +#ifdef MAPBASE + if (newActivity == ACT_RANGE_ATTACK2) + { + return ACT_COMBINE_THROW_GRENADE; + } +#endif + return newActivity; } +#ifdef MAPBASE +Activity CNPC_MetroPolice::Weapon_TranslateActivity( Activity baseAct, bool *pRequired ) +{ + Activity translated = BaseClass::Weapon_TranslateActivity(baseAct, pRequired); + + if (!m_fWeaponDrawn) + { + // If our pistol is holstered, don't act like we have one in our hands. + switch (translated) + { + case ACT_WALK_PISTOL: return ACT_WALK; + case ACT_RUN_PISTOL: return ACT_RUN; + case ACT_IDLE_PISTOL: return ACT_IDLE; + } + } + + return translated; +} + +int CNPC_MetroPolice::UnholsterWeapon() +{ +#if 0 + if (!m_fWeaponDrawn && (!IsCurSchedule(SCHED_METROPOLICE_DRAW_PISTOL))) + SetSchedule(SCHED_METROPOLICE_DRAW_PISTOL); + + return -1; +#else + // Remain compatible with the original behavior + if (IsCurSchedule(SCHED_METROPOLICE_DRAW_PISTOL)) + return -1; + else if (!m_fWeaponDrawn) + { + SetSchedule(SCHED_METROPOLICE_DRAW_PISTOL); + return -1; + } + + return BaseClass::UnholsterWeapon(); +#endif +} + +void CNPC_MetroPolice::OnChangeRunningBehavior( CAI_BehaviorBase *pOldBehavior, CAI_BehaviorBase *pNewBehavior ) +{ + BaseClass::OnChangeRunningBehavior( pOldBehavior, pNewBehavior ); + + // Fix the npc_metropolice using an invisible gun + if (!m_fWeaponDrawn) + { + // We can't just stop and draw, so fall back to gesture unholstering. + // Our implementation of UnholsterWeapon() just handles stopping and drawing, which we can skip in this case. + m_fWeaponDrawn = true; + BaseClass::UnholsterWeapon(); + } +} +#endif + //----------------------------------------------------------------------------- // Purpose: Makes the held manhack solid //----------------------------------------------------------------------------- @@ -3068,6 +3667,12 @@ void CNPC_MetroPolice::ReleaseManhack( void ) // Make us active m_hManhack->RemoveSpawnFlags( SF_NPC_WAIT_FOR_SCRIPT ); m_hManhack->ClearSchedule( "Manhack released by metropolice" ); + +#ifdef MAPBASE + // FSOLID_COLLIDE_WITH_OWNER allows us to be remembered as the manhack's owner without making us invulnerable to it + m_hManhack->SetOwnerEntity( this ); + m_hManhack->AddSolidFlags( FSOLID_COLLIDE_WITH_OWNER ); +#endif // Start him with knowledge of our current enemy if ( GetEnemy() ) @@ -3109,11 +3714,31 @@ void CNPC_MetroPolice::Event_Killed( const CTakeDamageInfo &info ) DropItem( "item_healthvial", WorldSpaceCenter()+RandomVector(-4,4), RandomAngle(0,360) ); pHL2GameRules->NPC_DroppedHealth(); } + +#ifdef MAPBASE + // Drop grenades if we should + DropGrenadeItemsOnDeath( info, pPlayer ); +#endif } BaseClass::Event_Killed( info ); } +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CNPC_MetroPolice::Event_KilledOther( CBaseEntity *pVictim, const CTakeDamageInfo &info ) +{ + BaseClass::Event_KilledOther( pVictim, info ); + +#ifdef MAPBASE // Moved from OnScheduleChange() + if ( pVictim && (pVictim->IsPlayer() || pVictim->IsNPC()) ) + { + AnnounceEnemyKill( pVictim ); + } +#endif +} + //----------------------------------------------------------------------------- // Try to enter a slot where we shoot a pistol //----------------------------------------------------------------------------- @@ -3160,6 +3785,17 @@ int CNPC_MetroPolice::SelectRangeAttackSchedule() return SCHED_METROPOLICE_DEPLOY_MANHACK; } +#ifdef MAPBASE + // Throw a grenade if not allowed to engage with weapon. + if ( CanGrenadeEnemy() ) + { + if ( OccupyStrategySlot( SQUAD_SLOT_SPECIAL_ATTACK ) ) + { + return SCHED_METROPOLICE_RANGE_ATTACK2; + } + } +#endif + return SCHED_METROPOLICE_ADVANCE; } @@ -3305,6 +3941,15 @@ int CNPC_MetroPolice::SelectScheduleNoDirectEnemy() return SCHED_METROPOLICE_SMASH_PROP; } +#ifdef MAPBASE + // If you see your enemy and you're still arming yourself, wait and don't just charge in + // (if your weapon is holstered, you're probably about to arm yourself) + if ( HasCondition( COND_SEE_ENEMY ) && GetWeapon(0) && (IsWeaponHolstered() || FindGestureLayer( TranslateActivity( ACT_ARM ) ) != -1) ) + { + return SCHED_COMBAT_FACE; + } +#endif + return SCHED_METROPOLICE_CHASE_ENEMY; } @@ -3333,7 +3978,11 @@ int CNPC_MetroPolice::SelectCombatSchedule() { m_nRecentDamage = 0; m_flRecentDamageTime = 0; +#ifdef METROPOLICE_USES_RESPONSE_SYSTEM + SpeakIfAllowed(TLK_COP_COVER_HEAVY_DAMAGE, SENTENCE_PRIORITY_MEDIUM, SENTENCE_CRITERIA_NORMAL); +#else m_Sentences.Speak( "METROPOLICE_COVER_HEAVY_DAMAGE", SENTENCE_PRIORITY_MEDIUM, SENTENCE_CRITERIA_NORMAL ); +#endif return SCHED_TAKE_COVER_FROM_ENEMY; } @@ -3376,7 +4025,11 @@ int CNPC_MetroPolice::SelectCombatSchedule() CBaseEntity *pBlocker = GetEnemyOccluder(); if ( pBlocker && pBlocker->GetHealth() > 0 && OccupyStrategySlotRange( SQUAD_SLOT_POLICE_ATTACK_OCCLUDER1, SQUAD_SLOT_POLICE_ATTACK_OCCLUDER2 ) ) { +#ifdef METROPOLICE_USES_RESPONSE_SYSTEM + SpeakIfAllowed(TLK_COP_SHOOTCOVER); +#else m_Sentences.Speak( "METROPOLICE_SHOOT_COVER" ); +#endif return SCHED_SHOOT_ENEMY_COVER; } } @@ -3385,6 +4038,27 @@ int CNPC_MetroPolice::SelectCombatSchedule() { if ( GetEnemy() && !(GetEnemy()->GetFlags() & FL_NOTARGET) ) { +#ifdef MAPBASE + if ( HasGrenades() ) + { + // We don't see our enemy. If it hasn't been long since I last saw him, + // and he's pretty close to the last place I saw him, throw a grenade in + // to flush him out. A wee bit of cheating here... + + float flTime; + float flDist; + + flTime = gpGlobals->curtime - GetEnemies()->LastTimeSeen( GetEnemy() ); + flDist = ( GetEnemy()->GetAbsOrigin() - GetEnemies()->LastSeenPosition( GetEnemy() ) ).Length(); + + //Msg("Time: %f Dist: %f\n", flTime, flDist ); + if ( flTime <= COMBINE_GRENADE_FLUSH_TIME && flDist <= COMBINE_GRENADE_FLUSH_DIST && CanGrenadeEnemy( false ) && OccupyStrategySlot( SQUAD_SLOT_SPECIAL_ATTACK ) ) + { + return SCHED_METROPOLICE_RANGE_ATTACK2; + } + } +#endif + // Charge in and break the enemy's cover! return SCHED_ESTABLISH_LINE_OF_FIRE; } @@ -3800,6 +4474,121 @@ int CNPC_MetroPolice::SelectAirboatCombatSchedule() } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Standoff schedule selection +//----------------------------------------------------------------------------- +int CNPC_MetroPolice::SelectBehaviorOverrideSchedule() +{ + // Announce a new enemy + if ( HasCondition( COND_NEW_ENEMY ) ) + { + AnnounceEnemyType( GetEnemy() ); + } + + int nResult = SelectScheduleNewEnemy(); + if ( nResult != SCHED_NONE ) + return nResult; + + if (!HasBaton() && ((float)m_nRecentDamage / (float)GetMaxHealth()) > RECENT_DAMAGE_THRESHOLD) + { + m_nRecentDamage = 0; + m_flRecentDamageTime = 0; +#ifdef METROPOLICE_USES_RESPONSE_SYSTEM + SpeakIfAllowed(TLK_COP_COVER_HEAVY_DAMAGE, SENTENCE_PRIORITY_MEDIUM, SENTENCE_CRITERIA_NORMAL); +#else + m_Sentences.Speak( "METROPOLICE_COVER_HEAVY_DAMAGE", SENTENCE_PRIORITY_MEDIUM, SENTENCE_CRITERIA_NORMAL ); +#endif + + return SCHED_TAKE_COVER_FROM_ENEMY; + } + + if ( HasCondition( COND_CAN_RANGE_ATTACK1 ) ) + { + nResult = SelectRangeAttackSchedule(); + if ( !GetShotRegulator()->IsInRestInterval() && nResult != SCHED_METROPOLICE_ADVANCE && nResult != SCHED_RANGE_ATTACK1 ) + return nResult; + } + + if ( HasCondition( COND_TOO_CLOSE_TO_ATTACK ) ) + { + return SCHED_BACK_AWAY_FROM_ENEMY; + } + + if ( HasCondition( COND_LOW_PRIMARY_AMMO ) || HasCondition( COND_NO_PRIMARY_AMMO ) ) + { + AnnounceOutOfAmmo( ); + return SCHED_HIDE_AND_RELOAD; + } + + if ( HasCondition(COND_WEAPON_SIGHT_OCCLUDED) && !HasBaton() ) + { + // If they are hiding behind something that we can destroy, start shooting at it. + CBaseEntity *pBlocker = GetEnemyOccluder(); + if ( pBlocker && pBlocker->GetHealth() > 0 && OccupyStrategySlotRange( SQUAD_SLOT_POLICE_ATTACK_OCCLUDER1, SQUAD_SLOT_POLICE_ATTACK_OCCLUDER2 ) ) + { +#ifdef METROPOLICE_USES_RESPONSE_SYSTEM + SpeakIfAllowed(TLK_COP_SHOOTCOVER); +#else + m_Sentences.Speak( "METROPOLICE_SHOOT_COVER" ); +#endif + return SCHED_SHOOT_ENEMY_COVER; + } + } + + if (HasCondition(COND_ENEMY_OCCLUDED)) + { + if ( GetEnemy() && !(GetEnemy()->GetFlags() & FL_NOTARGET) ) + { + if ( HasGrenades() ) + { + // We don't see our enemy. If it hasn't been long since I last saw him, + // and he's pretty close to the last place I saw him, throw a grenade in + // to flush him out. A wee bit of cheating here... + + float flTime; + float flDist; + + flTime = gpGlobals->curtime - GetEnemies()->LastTimeSeen( GetEnemy() ); + flDist = ( GetEnemy()->GetAbsOrigin() - GetEnemies()->LastSeenPosition( GetEnemy() ) ).Length(); + + //Msg("Time: %f Dist: %f\n", flTime, flDist ); + if ( flTime <= COMBINE_GRENADE_FLUSH_TIME && flDist <= COMBINE_GRENADE_FLUSH_DIST && CanGrenadeEnemy( false ) && OccupyStrategySlot( SQUAD_SLOT_SPECIAL_ATTACK ) ) + { + return SCHED_METROPOLICE_RANGE_ATTACK2; + } + } + } + } + + // If you can't attack, but you can deploy a manhack, do it! + if( CanDeployManhack() && OccupyStrategySlot( SQUAD_SLOT_POLICE_DEPLOY_MANHACK ) ) + return SCHED_METROPOLICE_DEPLOY_MANHACK; + + return SCHED_NONE; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CNPC_MetroPolice::IsCrouchedActivity( Activity activity ) +{ + return BaseClass::IsCrouchedActivity( activity ); +} + +//----------------------------------------------------------------------------- +// Standoff schedule selection +//----------------------------------------------------------------------------- +int CNPC_MetroPolice::CMetroPoliceStandoffBehavior::SelectScheduleAttack() +{ + int result = metropolice_new_component_behavior.GetBool() ? GetOuter()->SelectBehaviorOverrideSchedule() : SCHED_NONE; + if (result == SCHED_NONE) + result = BaseClass::SelectScheduleAttack(); + return result; +} +#endif + + //----------------------------------------------------------------------------- // Purpose: // Input : &info - @@ -3884,6 +4673,9 @@ void CNPC_MetroPolice::PlayFlinchGesture( void ) //----------------------------------------------------------------------------- void CNPC_MetroPolice::AnnounceHarrassment( void ) { +#ifdef METROPOLICE_USES_RESPONSE_SYSTEM + SpeakIfAllowed(TLK_COP_BACK_UP, SENTENCE_PRIORITY_MEDIUM, SENTENCE_CRITERIA_NORMAL); +#else static const char *pWarnings[3] = { "METROPOLICE_BACK_UP_A", @@ -3892,6 +4684,7 @@ void CNPC_MetroPolice::AnnounceHarrassment( void ) }; m_Sentences.Speak( pWarnings[ random->RandomInt( 0, ARRAYSIZE(pWarnings)-1 ) ], SENTENCE_PRIORITY_MEDIUM, SENTENCE_CRITERIA_NORMAL ); +#endif } //----------------------------------------------------------------------------- @@ -4013,7 +4806,11 @@ int CNPC_MetroPolice::SelectSchedule( void ) if ( HasCondition(COND_METROPOLICE_ON_FIRE) ) { +#ifdef METROPOLICE_USES_RESPONSE_SYSTEM + SpeakIfAllowed(TLK_COP_ON_FIRE, "hurt_by_fire:1", SENTENCE_PRIORITY_INVALID, SENTENCE_CRITERIA_ALWAYS); +#else m_Sentences.Speak( "METROPOLICE_ON_FIRE", SENTENCE_PRIORITY_INVALID, SENTENCE_CRITERIA_ALWAYS ); +#endif return SCHED_METROPOLICE_BURNING_STAND; } @@ -4025,17 +4822,66 @@ int CNPC_MetroPolice::SelectSchedule( void ) // See which state our player relationship is in if ( PlayerIsCriminal() == false ) { +#ifdef METROPOLICE_USES_RESPONSE_SYSTEM + SpeakIfAllowed( TLK_COP_HIT_BY_PHYSOBJ, SENTENCE_PRIORITY_INVALID, SENTENCE_CRITERIA_ALWAYS ); +#else m_Sentences.Speak( "METROPOLICE_HIT_BY_PHYSOBJECT", SENTENCE_PRIORITY_INVALID, SENTENCE_CRITERIA_ALWAYS ); +#endif m_nNumWarnings = METROPOLICE_MAX_WARNINGS; AdministerJustice(); } else if ( GlobalEntity_GetState( "gordon_precriminal" ) == GLOBAL_ON ) { // We're not allowed to respond, but warn them +#ifdef METROPOLICE_USES_RESPONSE_SYSTEM + SpeakIfAllowed( TLK_COP_HARASS, SENTENCE_PRIORITY_INVALID, SENTENCE_CRITERIA_ALWAYS ); +#else m_Sentences.Speak( "METROPOLICE_IDLE_HARASS_PLAYER", SENTENCE_PRIORITY_INVALID, SENTENCE_CRITERIA_ALWAYS ); +#endif } } +#ifdef MAPBASE + if ( m_hForcedGrenadeTarget ) + { + if ( m_flNextGrenadeCheck < gpGlobals->curtime ) + { + Vector vecTarget = m_hForcedGrenadeTarget->WorldSpaceCenter(); + + // The fact we have a forced grenade target overrides whether we're marked as "capable". + // If we're *only* alt-fire capable, use an energy ball. If not, throw a grenade. + if (!IsAltFireCapable() || IsGrenadeCapable()) + { + Vector vecTarget = m_hForcedGrenadeTarget->WorldSpaceCenter(); + { + // If we can, throw a grenade at the target. + // Ignore grenade count / distance / etc + if ( CheckCanThrowGrenade( vecTarget ) ) + { + m_hForcedGrenadeTarget = NULL; + return SCHED_METROPOLICE_FORCED_GRENADE_THROW; + } + } + } + else + { + if ( FVisible( m_hForcedGrenadeTarget ) ) + { + m_vecAltFireTarget = vecTarget; + m_hForcedGrenadeTarget = NULL; + return SCHED_METROPOLICE_AR2_ALTFIRE; + } + } + } + + // Can't throw at the target, so lets try moving to somewhere where I can see it + if ( !FVisible( m_hForcedGrenadeTarget ) ) + { + return SCHED_METROPOLICE_MOVE_TO_FORCED_GREN_LOS; + } + } +#endif + int nSched = SelectFlinchSchedule(); if ( nSched != SCHED_NONE ) return nSched; @@ -4123,9 +4969,18 @@ int CNPC_MetroPolice::SelectSchedule( void ) // This will cause the cops to run backwards + shoot at the same time if ( !bHighHealth && !HasBaton() ) { +#ifdef MAPBASE + // Don't do this with low-capacity weapons or weapons which don't use clips + if ( GetActiveWeapon() && GetActiveWeapon()->UsesClipsForAmmo1() && GetActiveWeapon()->GetMaxClip1() > 10 && (GetActiveWeapon()->m_iClip1 <= 5) ) +#else if ( GetActiveWeapon() && (GetActiveWeapon()->m_iClip1 <= 5) ) +#endif { +#ifdef METROPOLICE_USES_RESPONSE_SYSTEM + SpeakIfAllowed( TLK_COP_LOWAMMO ); +#else m_Sentences.Speak( "METROPOLICE_COVER_LOW_AMMO" ); +#endif return SCHED_HIDE_AND_RELOAD; } } @@ -4168,7 +5023,11 @@ int CNPC_MetroPolice::SelectSchedule( void ) break; case NPC_STATE_COMBAT: +#ifdef MAPBASE + if (!IsEnemyInAnAirboat() || !GetActiveWeapon() || !EntIsClass(GetActiveWeapon(), gm_isz_class_SMG1)) +#else if (!IsEnemyInAnAirboat() || !Weapon_OwnsThisType( "weapon_smg1" ) ) +#endif { int nResult = SelectCombatSchedule(); if ( nResult != SCHED_NONE ) @@ -4244,6 +5103,14 @@ int CNPC_MetroPolice::TranslateSchedule( int scheduleType ) if ( nSched != SCHED_NONE ) return nSched; } +#ifdef MAPBASE + if ( CanAltFireEnemy(false) && OccupyStrategySlot(SQUAD_SLOT_SPECIAL_ATTACK) ) + { + // If this metrocop has the balls to alt-fire the enemy's last known position, + // do so! + return SCHED_METROPOLICE_AR2_ALTFIRE; + } +#endif return SCHED_METROPOLICE_ESTABLISH_LINE_OF_FIRE; case SCHED_WAKE_ANGRY: @@ -4270,7 +5137,21 @@ int CNPC_MetroPolice::TranslateSchedule( int scheduleType ) return SCHED_METROPOLICE_DRAW_PISTOL; } +#ifdef MAPBASE + if (CanAltFireEnemy( true ) && OccupyStrategySlot( SQUAD_SLOT_SPECIAL_ATTACK )) + { + // Since I'm holding this squadslot, no one else can try right now. If I die before the shot + // goes off, I won't have affected anyone else's ability to use this attack at their nearest + // convenience. + return SCHED_METROPOLICE_AR2_ALTFIRE; + } +#endif + +#ifdef MAPBASE + if (GetActiveWeapon() && EntIsClass(GetActiveWeapon(), gm_isz_class_SMG1)) +#else if( Weapon_OwnsThisType( "weapon_smg1" ) ) +#endif { if ( IsEnemyInAnAirboat() ) { @@ -4289,10 +5170,44 @@ int CNPC_MetroPolice::TranslateSchedule( int scheduleType ) } } break; +#ifdef MAPBASE + case SCHED_TAKE_COVER_FROM_ENEMY: + { + if ( m_pSquad ) + { + // Have to explicitly check innate range attack condition as may have weapon with range attack 2 + if ( g_pGameRules->IsSkillLevel( SKILL_HARD ) && + HasCondition(COND_CAN_RANGE_ATTACK2) && + OccupyStrategySlot( SQUAD_SLOT_SPECIAL_ATTACK ) ) + { + #ifdef METROPOLICE_USES_RESPONSE_SYSTEM + SpeakIfAllowed( TLK_COP_THROWGRENADE ); + #else + m_Sentences.Speak( "COMBINE_THROW_GRENADE" ); + #endif + return SCHED_METROPOLICE_RANGE_ATTACK2; + } + } + } + break; + case SCHED_HIDE_AND_RELOAD: + { + if( CanGrenadeEnemy() && OccupyStrategySlot( SQUAD_SLOT_SPECIAL_ATTACK ) && random->RandomInt( 0, 100 ) < 20 ) + { + // If I COULD throw a grenade and I need to reload, 20% chance I'll throw a grenade before I hide to reload. + return SCHED_METROPOLICE_RANGE_ATTACK2; + } + } + break; +#endif case SCHED_METROPOLICE_ADVANCE: if ( m_NextChargeTimer.Expired() && metropolice_charge.GetBool() ) { +#ifdef MAPBASE + if (GetActiveWeapon() && EntIsClass(GetActiveWeapon(), gm_isz_class_Pistol)) +#else if ( Weapon_OwnsThisType( "weapon_pistol" ) ) +#endif { if ( GetEnemy() && GetEnemy()->GetAbsOrigin().DistToSqr( GetAbsOrigin() ) > 300*300 ) { @@ -4417,7 +5332,11 @@ void CNPC_MetroPolice::StartTask( const Task_t *pTask ) break; } +#ifdef METROPOLICE_USES_RESPONSE_SYSTEM + SpeakIfAllowed(TLK_COP_ACTIVATE_BATON, SENTENCE_PRIORITY_NORMAL, SENTENCE_CRITERIA_NORMAL); +#else m_Sentences.Speak( "METROPOLICE_ACTIVATE_BATON", SENTENCE_PRIORITY_NORMAL, SENTENCE_CRITERIA_NORMAL ); +#endif SetIdealActivity( (Activity) ACT_ACTIVATE_BATON ); } else @@ -4428,7 +5347,11 @@ void CNPC_MetroPolice::StartTask( const Task_t *pTask ) break; } +#ifdef METROPOLICE_USES_RESPONSE_SYSTEM + SpeakIfAllowed(TLK_COP_DEACTIVATE_BATON, SENTENCE_PRIORITY_NORMAL, SENTENCE_CRITERIA_NORMAL); +#else m_Sentences.Speak( "METROPOLICE_DEACTIVATE_BATON", SENTENCE_PRIORITY_NORMAL, SENTENCE_CRITERIA_NORMAL ); +#endif SetIdealActivity( (Activity) ACT_DEACTIVATE_BATON ); } } @@ -4465,6 +5388,23 @@ void CNPC_MetroPolice::StartTask( const Task_t *pTask ) TaskComplete(); break; +#ifdef MAPBASE + case TASK_METROPOLICE_GET_PATH_TO_FORCED_GREN_LOS: + StartTask_GetPathToForced( pTask ); + break; + + case TASK_METROPOLICE_DEFER_SQUAD_GRENADES: + StartTask_DeferSquad( pTask ); + break; + + case TASK_METROPOLICE_FACE_TOSS_DIR: + break; + + case TASK_METROPOLICE_PLAY_SEQUENCE_FACE_ALTFIRE_TARGET: + StartTask_FaceAltFireTarget( pTask ); + break; +#endif + case TASK_METROPOLICE_GET_PATH_TO_STITCH: { if ( !ShouldAttemptToStitch() ) @@ -4828,6 +5768,20 @@ void CNPC_MetroPolice::RunTask( const Task_t *pTask ) } break; +#ifdef MAPBASE + case TASK_METROPOLICE_PLAY_SEQUENCE_FACE_ALTFIRE_TARGET: + RunTask_FaceAltFireTarget( pTask ); + break; + + case TASK_METROPOLICE_GET_PATH_TO_FORCED_GREN_LOS: + RunTask_GetPathToForced( pTask ); + break; + + case TASK_METROPOLICE_FACE_TOSS_DIR: + RunTask_FaceTossDir( pTask ); + break; +#endif + default: BaseClass::RunTask( pTask ); break; @@ -4947,18 +5901,34 @@ void CNPC_MetroPolice::BuildScheduleTestBits( void ) { ClearCustomInterruptCondition( COND_CAN_MELEE_ATTACK1 ); } + +#ifdef MAPBASE + if (gpGlobals->curtime < m_flNextAttack) + { + ClearCustomInterruptCondition( COND_CAN_RANGE_ATTACK1 ); + ClearCustomInterruptCondition( COND_CAN_RANGE_ATTACK2 ); + } +#endif } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- WeaponProficiency_t CNPC_MetroPolice::CalcWeaponProficiency( CBaseCombatWeapon *pWeapon ) { +#ifdef MAPBASE + if (EntIsClass(pWeapon, gm_isz_class_Pistol)) +#else if( FClassnameIs( pWeapon, "weapon_pistol" ) ) +#endif { return WEAPON_PROFICIENCY_POOR; } +#ifdef MAPBASE + if (EntIsClass(pWeapon, gm_isz_class_SMG1)) +#else if( FClassnameIs( pWeapon, "weapon_smg1" ) ) +#endif { return WEAPON_PROFICIENCY_VERY_GOOD; } @@ -5040,7 +6010,11 @@ bool CNPC_MetroPolice::HasBaton( void ) CBaseCombatWeapon *pWeapon = GetActiveWeapon(); if ( pWeapon ) +#ifdef MAPBASE + return EntIsClass(pWeapon, gm_isz_class_Stunstick); +#else return FClassnameIs( pWeapon, "weapon_stunstick" ); +#endif return false; } @@ -5051,7 +6025,7 @@ bool CNPC_MetroPolice::HasBaton( void ) //----------------------------------------------------------------------------- bool CNPC_MetroPolice::BatonActive( void ) { -#ifndef HL2MP +#if !defined(HL2MP) || defined(MAPBASE) CWeaponStunStick *pStick = dynamic_cast(GetActiveWeapon()); @@ -5180,12 +6154,20 @@ AI_BEGIN_CUSTOM_NPC( npc_metropolice, CNPC_MetroPolice ) DECLARE_ANIMEVENT( AE_METROPOLICE_START_DEPLOY ); DECLARE_ANIMEVENT( AE_METROPOLICE_DRAW_PISTOL ); DECLARE_ANIMEVENT( AE_METROPOLICE_DEPLOY_MANHACK ); +#ifdef MAPBASE + DECLARE_ANIMEVENT( COMBINE_AE_BEGIN_ALTFIRE ) + DECLARE_ANIMEVENT( COMBINE_AE_ALTFIRE ) +#endif DECLARE_SQUADSLOT( SQUAD_SLOT_POLICE_CHARGE_ENEMY ); DECLARE_SQUADSLOT( SQUAD_SLOT_POLICE_HARASS ); DECLARE_SQUADSLOT( SQUAD_SLOT_POLICE_DEPLOY_MANHACK ); DECLARE_SQUADSLOT( SQUAD_SLOT_POLICE_ATTACK_OCCLUDER1 ); DECLARE_SQUADSLOT( SQUAD_SLOT_POLICE_ATTACK_OCCLUDER2 ); +#ifdef MAPBASE + DECLARE_SQUADSLOT( SQUAD_SLOT_POLICE_COVERING_FIRE1 ); + DECLARE_SQUADSLOT( SQUAD_SLOT_POLICE_COVERING_FIRE2 ); +#endif DECLARE_SQUADSLOT( SQUAD_SLOT_POLICE_ARREST_ENEMY ); DECLARE_ACTIVITY( ACT_METROPOLICE_DRAW_PISTOL ); @@ -5223,6 +6205,12 @@ AI_BEGIN_CUSTOM_NPC( npc_metropolice, CNPC_MetroPolice ) DECLARE_TASK( TASK_METROPOLICE_WAIT_FOR_SENTENCE ); DECLARE_TASK( TASK_METROPOLICE_GET_PATH_TO_PRECHASE ); DECLARE_TASK( TASK_METROPOLICE_CLEAR_PRECHASE ); +#ifdef MAPBASE + DECLARE_TASK( TASK_METROPOLICE_GET_PATH_TO_FORCED_GREN_LOS ) + DECLARE_TASK( TASK_METROPOLICE_DEFER_SQUAD_GRENADES ) + DECLARE_TASK( TASK_METROPOLICE_FACE_TOSS_DIR ) + DECLARE_TASK( TASK_METROPOLICE_PLAY_SEQUENCE_FACE_ALTFIRE_TARGET ) +#endif DECLARE_CONDITION( COND_METROPOLICE_ON_FIRE ); DECLARE_CONDITION( COND_METROPOLICE_ENEMY_RESISTING_ARREST ); @@ -5842,5 +6830,85 @@ DEFINE_SCHEDULE " COND_ENEMY_DEAD" ); +#ifdef MAPBASE +//========================================================= + // Mapmaker forced grenade throw + //========================================================= + DEFINE_SCHEDULE + ( + SCHED_METROPOLICE_FORCED_GRENADE_THROW, + + " Tasks" + " TASK_STOP_MOVING 0" + " TASK_METROPOLICE_FACE_TOSS_DIR 0" + " TASK_ANNOUNCE_ATTACK 2" // 2 = grenade + " TASK_PLAY_SEQUENCE ACTIVITY:ACT_RANGE_ATTACK2" + " TASK_METROPOLICE_DEFER_SQUAD_GRENADES 0" + "" + " Interrupts" + ) + + //========================================================= + // Move to LOS of the mapmaker's forced grenade throw target + //========================================================= + DEFINE_SCHEDULE + ( + SCHED_METROPOLICE_MOVE_TO_FORCED_GREN_LOS, + + " Tasks " + " TASK_SET_TOLERANCE_DISTANCE 48" + " TASK_METROPOLICE_GET_PATH_TO_FORCED_GREN_LOS 0" + " TASK_SPEAK_SENTENCE 1" + " TASK_RUN_PATH 0" + " TASK_WAIT_FOR_MOVEMENT 0" + " " + " Interrupts " + " COND_NEW_ENEMY" + " COND_ENEMY_DEAD" + " COND_CAN_MELEE_ATTACK1" + " COND_CAN_MELEE_ATTACK2" + " COND_HEAR_DANGER" + " COND_HEAR_MOVE_AWAY" + " COND_HEAVY_DAMAGE" + ) + + //========================================================= + // SCHED_METROPOLICE_RANGE_ATTACK2 + // + // secondary range attack. Overriden because base class stops attacking when the enemy is occluded. + // combines's grenade toss requires the enemy be occluded. + //========================================================= + DEFINE_SCHEDULE + ( + SCHED_METROPOLICE_RANGE_ATTACK2, + + " Tasks" + " TASK_STOP_MOVING 0" + " TASK_METROPOLICE_FACE_TOSS_DIR 0" + " TASK_ANNOUNCE_ATTACK 2" // 2 = grenade + " TASK_PLAY_SEQUENCE ACTIVITY:ACT_RANGE_ATTACK2" + " TASK_METROPOLICE_DEFER_SQUAD_GRENADES 0" + " TASK_SET_SCHEDULE SCHEDULE:SCHED_HIDE_AND_RELOAD" // don't run immediately after throwing grenade. + "" + " Interrupts" + ) + + //========================================================= + // AR2 Alt Fire Attack + //========================================================= + DEFINE_SCHEDULE + ( + SCHED_METROPOLICE_AR2_ALTFIRE, + + " Tasks" + " TASK_STOP_MOVING 0" + " TASK_ANNOUNCE_ATTACK 1" + " TASK_METROPOLICE_PLAY_SEQUENCE_FACE_ALTFIRE_TARGET ACTIVITY:ACT_COMBINE_AR2_ALTFIRE" + "" + " Interrupts" + " COND_TOO_CLOSE_TO_ATTACK" + ) +#endif + AI_END_CUSTOM_NPC() diff --git a/src/game/server/hl2/npc_metropolice.h b/src/game/server/hl2/npc_metropolice.h index 544fc68b..690bf2ae 100644 --- a/src/game/server/hl2/npc_metropolice.h +++ b/src/game/server/hl2/npc_metropolice.h @@ -24,13 +24,26 @@ #include "ai_behavior_police.h" #include "ai_behavior_follow.h" #include "ai_sentence.h" +#ifdef MAPBASE +#include "mapbase/ai_grenade.h" +#endif #include "props.h" +#ifdef EXPANDED_RESPONSE_SYSTEM_USAGE +#include "mapbase/expandedrs_combine.h" +#define METROPOLICE_USES_RESPONSE_SYSTEM 1 +#endif class CNPC_MetroPolice; +#ifdef MAPBASE +class CNPC_MetroPolice : public CAI_GrenadeUser +{ + DECLARE_CLASS( CNPC_MetroPolice, CAI_GrenadeUser ); +#else class CNPC_MetroPolice : public CAI_BaseActor { DECLARE_CLASS( CNPC_MetroPolice, CAI_BaseActor ); +#endif DECLARE_DATADESC(); public: @@ -46,11 +59,28 @@ public: float MaxYawSpeed( void ); void HandleAnimEvent( animevent_t *pEvent ); Activity NPC_TranslateActivity( Activity newActivity ); +#ifdef MAPBASE + Activity Weapon_TranslateActivity( Activity baseAct, bool *pRequired ); + + virtual int UnholsterWeapon( void ); + virtual void OnChangeRunningBehavior( CAI_BehaviorBase *pOldBehavior, CAI_BehaviorBase *pNewBehavior ); + + const char* GetGrenadeAttachment() { return "LHand"; } + + virtual bool IsAltFireCapable() { return (m_iGrenadeCapabilities & GRENCAP_ALTFIRE) != 0 && BaseClass::IsAltFireCapable(); } + virtual bool IsGrenadeCapable() { return (m_iGrenadeCapabilities & GRENCAP_GRENADE) != 0; } + + virtual bool ShouldDropGrenades() { return (m_iGrenadeDropCapabilities & GRENDROPCAP_GRENADE) != 0 && BaseClass::ShouldDropGrenades(); } + virtual bool ShouldDropInterruptedGrenades() { return (m_iGrenadeDropCapabilities & GRENDROPCAP_INTERRUPTED) != 0 && BaseClass::ShouldDropInterruptedGrenades(); } + virtual bool ShouldDropAltFire() { return (m_iGrenadeDropCapabilities & GRENDROPCAP_ALTFIRE) != 0 && BaseClass::ShouldDropAltFire(); } +#endif Vector EyeDirection3D( void ) { return CAI_BaseHumanoid::EyeDirection3D(); } // cops don't have eyes virtual void Event_Killed( const CTakeDamageInfo &info ); + virtual void Event_KilledOther( CBaseEntity *pVictim, const CTakeDamageInfo &info ); + virtual void OnScheduleChange(); float GetIdealAccel( void ) const; @@ -87,6 +117,15 @@ public: // Speaking virtual void SpeakSentence( int nSentenceType ); +#ifdef METROPOLICE_USES_RESPONSE_SYSTEM + bool SpeakIfAllowed( const char *concept, SentencePriority_t sentencepriority = SENTENCE_PRIORITY_NORMAL, SentenceCriteria_t sentencecriteria = SENTENCE_CRITERIA_IN_SQUAD ) + { + return SpeakIfAllowed( concept, NULL, sentencepriority, sentencecriteria ); + } + bool SpeakIfAllowed( const char *concept, const char *modifiers, SentencePriority_t sentencepriority = SENTENCE_PRIORITY_NORMAL, SentenceCriteria_t sentencecriteria = SENTENCE_CRITERIA_IN_SQUAD ); + bool SpeakIfAllowed( const char *concept, AI_CriteriaSet& modifiers, SentencePriority_t sentencepriority = SENTENCE_PRIORITY_NORMAL, SentenceCriteria_t sentencecriteria = SENTENCE_CRITERIA_IN_SQUAD ); + void ModifyOrAppendCriteria( AI_CriteriaSet& set ); +#endif // Set up the shot regulator based on the equipped weapon virtual void OnUpdateShotRegulator( ); @@ -101,7 +140,9 @@ public: void SetBatonState( bool state ); bool BatonActive( void ); +#ifndef METROPOLICE_USES_RESPONSE_SYSTEM CAI_Sentence< CNPC_MetroPolice > *GetSentences() { return &m_Sentences; } +#endif virtual bool AllowedToIgnite( void ) { return true; } @@ -131,8 +172,13 @@ private: void SpeakAssaultSentence( int nSentenceType ); void SpeakStandoffSentence( int nSentenceType ); +#ifdef MAPBASE + virtual void LostEnemySound( CBaseEntity *pEnemy ); + virtual void FoundEnemySound( CBaseEntity *pEnemy ); +#else virtual void LostEnemySound( void ); virtual void FoundEnemySound( void ); +#endif virtual void AlertSound( void ); virtual void PainSound( const CTakeDamageInfo &info ); virtual void DeathSound( const CTakeDamageInfo &info ); @@ -164,8 +210,19 @@ private: // Inputs void InputEnableManhackToss( inputdata_t &inputdata ); +#ifdef MAPBASE + void InputDisableManhackToss( inputdata_t &inputdata ); + void InputDeployManhack( inputdata_t &inputdata ); + void InputAddManhacks( inputdata_t &inputdata ); + void InputSetManhacks( inputdata_t &inputdata ); +#endif void InputSetPoliceGoal( inputdata_t &inputdata ); void InputActivateBaton( inputdata_t &inputdata ); +#ifdef MAPBASE + void InputAdministerJustice( inputdata_t &inputdata ); + void InputAddWarnings( inputdata_t &inputdata ); + void InputSetWarnings( inputdata_t &inputdata ); +#endif void NotifyDeadFriend ( CBaseEntity* pFriend ); @@ -202,6 +259,21 @@ private: int SelectAirboatCombatSchedule(); int SelectAirboatRangeAttackSchedule(); +#ifdef MAPBASE + int SelectBehaviorOverrideSchedule(); + + bool IsCrouchedActivity( Activity activity ); + + // This is something Valve did with Combine soldiers so they would throw grenades during standoffs. + // We're using a similar thing here so metrocops deploy manhacks. + class CMetroPoliceStandoffBehavior : public CAI_ComponentWithOuter + { + typedef CAI_ComponentWithOuter BaseClass; + + virtual int SelectScheduleAttack(); + }; +#endif + // Handle flinching bool IsHeavyDamage( const CTakeDamageInfo &info ); @@ -361,6 +433,12 @@ private: SCHED_METROPOLICE_ALERT_FACE_BESTSOUND, SCHED_METROPOLICE_RETURN_TO_PRECHASE, SCHED_METROPOLICE_SMASH_PROP, +#ifdef MAPBASE + SCHED_METROPOLICE_FORCED_GRENADE_THROW, + SCHED_METROPOLICE_MOVE_TO_FORCED_GREN_LOS, + SCHED_METROPOLICE_RANGE_ATTACK2, + SCHED_METROPOLICE_AR2_ALTFIRE, +#endif }; enum @@ -387,6 +465,12 @@ private: TASK_METROPOLICE_WAIT_FOR_SENTENCE, TASK_METROPOLICE_GET_PATH_TO_PRECHASE, TASK_METROPOLICE_CLEAR_PRECHASE, +#ifdef MAPBASE + TASK_METROPOLICE_GET_PATH_TO_FORCED_GREN_LOS, + TASK_METROPOLICE_DEFER_SQUAD_GRENADES, + TASK_METROPOLICE_FACE_TOSS_DIR, + TASK_METROPOLICE_PLAY_SEQUENCE_FACE_ALTFIRE_TARGET, +#endif }; private: @@ -441,19 +525,33 @@ private: // Outputs COutputEvent m_OnStunnedPlayer; COutputEvent m_OnCupCopped; +#ifdef MAPBASE + COutputEHANDLE m_OnHitByPhysicsObject; + COutputEHANDLE m_OutManhack; + + // Determines whether this NPC is allowed to use grenades or alt-fire stuff. + eGrenadeCapabilities m_iGrenadeCapabilities; + eGrenadeDropCapabilities m_iGrenadeDropCapabilities; +#endif AIHANDLE m_hManhack; CHandle m_hBlockingProp; CAI_ActBusyBehavior m_ActBusyBehavior; +#ifdef MAPBASE + CMetroPoliceStandoffBehavior m_StandoffBehavior; +#else CAI_StandoffBehavior m_StandoffBehavior; +#endif CAI_AssaultBehavior m_AssaultBehavior; CAI_FuncTankBehavior m_FuncTankBehavior; CAI_RappelBehavior m_RappelBehavior; CAI_PolicingBehavior m_PolicingBehavior; CAI_FollowBehavior m_FollowBehavior; +#ifndef METROPOLICE_USES_RESPONSE_SYSTEM CAI_Sentence< CNPC_MetroPolice > m_Sentences; +#endif int m_nRecentDamage; float m_flRecentDamageTime; diff --git a/src/game/server/hl2/npc_monk.cpp b/src/game/server/hl2/npc_monk.cpp index c20f558c..e517a6fe 100644 --- a/src/game/server/hl2/npc_monk.cpp +++ b/src/game/server/hl2/npc_monk.cpp @@ -16,6 +16,9 @@ #include "ai_behavior.h" #include "ai_behavior_assault.h" #include "ai_behavior_lead.h" +#ifdef MAPBASE +#include "ai_behavior_functank.h" +#endif #include "npcevent.h" #include "ai_playerally.h" #include "ai_senses.h" @@ -103,6 +106,10 @@ private: CAI_AssaultBehavior m_AssaultBehavior; CAI_LeadBehavior m_LeadBehavior; +#ifdef MAPBASE + CAI_FuncTankBehavior m_FuncTankBehavior; +#endif + int m_iNumZombies; int m_iDangerousZombies; bool m_bPerfectAccuracy; @@ -113,6 +120,9 @@ private: BEGIN_DATADESC( CNPC_Monk ) // m_AssaultBehavior // m_LeadBehavior +#ifdef MAPBASE +// m_FuncTankBehavior +#endif DEFINE_FIELD( m_iNumZombies, FIELD_INTEGER ), DEFINE_FIELD( m_iDangerousZombies, FIELD_INTEGER ), DEFINE_FIELD( m_bPerfectAccuracy, FIELD_BOOLEAN ), @@ -132,6 +142,9 @@ bool CNPC_Monk::CreateBehaviors() { AddBehavior( &m_LeadBehavior ); AddBehavior( &m_AssaultBehavior ); +#ifdef MAPBASE + AddBehavior( &m_FuncTankBehavior ); +#endif return BaseClass::CreateBehaviors(); } @@ -183,6 +196,9 @@ Class_T CNPC_Monk::Classify( void ) return CLASS_PLAYER_ALLY_VITAL; } +#ifdef MAPBASE +ConVar npc_monk_use_old_acts( "npc_monk_use_old_acts", "1" ); +#endif //----------------------------------------------------------------------------- // Purpose: @@ -216,6 +232,45 @@ Activity CNPC_Monk::NPC_TranslateActivity( Activity eNewActivity ) } } +#if defined(EXPANDED_HL2_WEAPON_ACTIVITIES) && AR2_ACTIVITY_FIX == 1 + if (npc_monk_use_old_acts.GetBool()) + { + // HACKHACK: Don't break the balcony scene + if ( FStrEq( STRING(gpGlobals->mapname), "d1_town_02" ) && eNewActivity == ACT_IDLE ) + { + eNewActivity = ACT_IDLE_SMG1; + } + else + { + switch (eNewActivity) + { + case ACT_IDLE_AR2: + eNewActivity = ACT_IDLE_SMG1; + break; + + case ACT_IDLE_ANGRY_SHOTGUN: + case ACT_IDLE_ANGRY_AR2: + eNewActivity = ACT_IDLE_ANGRY_SMG1; + break; + + case ACT_WALK_AIM_SHOTGUN: + case ACT_WALK_AIM_AR2: + eNewActivity = ACT_WALK_AIM_RIFLE; + break; + + case ACT_RUN_AIM_SHOTGUN: + case ACT_RUN_AIM_AR2: + eNewActivity = ACT_RUN_AIM_RIFLE; + break; + + case ACT_RANGE_ATTACK_SHOTGUN_LOW: + case ACT_RANGE_ATTACK_AR2_LOW: + eNewActivity = ACT_RANGE_ATTACK_SMG1_LOW; + break; + } + } + } +#else // We need these so that we can pick up the shotgun to throw it in the balcony scene if ( eNewActivity == ACT_IDLE_ANGRY_SHOTGUN ) { @@ -233,6 +288,7 @@ Activity CNPC_Monk::NPC_TranslateActivity( Activity eNewActivity ) { return ACT_RANGE_ATTACK_SMG1_LOW; } +#endif return eNewActivity; } @@ -665,6 +721,17 @@ int CNPC_Monk::SelectFailSchedule( int failedSchedule, int failedTask, AI_TaskFa { if( HasCondition( COND_CAN_RANGE_ATTACK1 ) ) { +#ifdef MAPBASE + // I thought it would be a nice touch. + if (RandomInt(1, 2) == 1 && CanRunAScriptedNPCInteraction(false)) + { + for ( int i = 0; i < m_ScriptedInteractions.Count(); i++ ) + { + m_ScriptedInteractions[i].flNextAttemptTime = gpGlobals->curtime; + } + } +#endif + // Most likely backed into a corner. Just blaze away. return SCHED_MONK_RANGE_ATTACK1; } diff --git a/src/game/server/hl2/npc_mossman.cpp b/src/game/server/hl2/npc_mossman.cpp index ff924cd2..f92a8f51 100644 --- a/src/game/server/hl2/npc_mossman.cpp +++ b/src/game/server/hl2/npc_mossman.cpp @@ -41,6 +41,11 @@ public: bool CreateBehaviors( void ); int SelectSchedule( void ); +#ifdef MAPBASE + // Use Mossman's default subtitle color (220,255,198) + bool GetGameTextSpeechParams( hudtextparms_t ¶ms ) { params.r1 = 220; params.g1 = 255; params.b1 = 198; return BaseClass::GetGameTextSpeechParams( params ); } +#endif + private: CAI_FollowBehavior m_FollowBehavior; }; diff --git a/src/game/server/hl2/npc_playercompanion.cpp b/src/game/server/hl2/npc_playercompanion.cpp index a03a752f..660cb218 100644 --- a/src/game/server/hl2/npc_playercompanion.cpp +++ b/src/game/server/hl2/npc_playercompanion.cpp @@ -31,12 +31,23 @@ #include "grenade_frag.h" #include #include "physics_npc_solver.h" +#ifdef MAPBASE +#include "mapbase/GlobalStrings.h" +#include "world.h" +#include "vehicle_base.h" +#include "npc_headcrab.h" +#include "npc_BaseZombie.h" +#endif ConVar ai_debug_readiness("ai_debug_readiness", "0" ); ConVar ai_use_readiness("ai_use_readiness", "1" ); // 0 = off, 1 = on, 2 = on for player squad only ConVar ai_readiness_decay( "ai_readiness_decay", "120" );// How many seconds it takes to relax completely ConVar ai_new_aiming( "ai_new_aiming", "1" ); +#ifdef COMPANION_MELEE_ATTACK +ConVar sk_companion_melee_damage("sk_companion_melee_damage", "25"); +#endif + #define GetReadinessUse() ai_use_readiness.GetInt() extern ConVar g_debug_transitions; @@ -46,6 +57,15 @@ extern ConVar g_debug_transitions; int AE_COMPANION_PRODUCE_FLARE; int AE_COMPANION_LIGHT_FLARE; int AE_COMPANION_RELEASE_FLARE; +#if COMPANION_MELEE_ATTACK +#define AE_PC_MELEE 3 + +#define COMPANION_MELEE_DIST 64.0 +#endif + +#ifdef MAPBASE +ConVar ai_allow_new_weapons( "ai_allow_new_weapons", "1", FCVAR_NONE, "Allows companion NPCs to automatically pick up and use weapons they were unable pick up before, i.e. 357s or crossbows." ); +#endif #define MAX_TIME_BETWEEN_BARRELS_EXPLODING 5.0f #define MAX_TIME_BETWEEN_CONSECUTIVE_PLAYER_KILLS 3.0f @@ -97,7 +117,9 @@ BEGIN_DATADESC( CNPC_PlayerCompanion ) #endif // HL2_EPISODIC //------------------------------------------------------------------------------ +#ifndef MAPBASE DEFINE_INPUTFUNC( FIELD_STRING, "GiveWeapon", InputGiveWeapon ), +#endif DEFINE_FIELD( m_flReadiness, FIELD_FLOAT ), DEFINE_FIELD( m_flReadinessSensitivity, FIELD_FLOAT ), @@ -130,6 +152,16 @@ BEGIN_DATADESC( CNPC_PlayerCompanion ) DEFINE_OUTPUT( m_OnWeaponPickup, "OnWeaponPickup" ), +#ifdef MAPBASE + DEFINE_AIGRENADE_DATADESC() + DEFINE_INPUT( m_iGrenadeCapabilities, FIELD_INTEGER, "SetGrenadeCapabilities" ), + DEFINE_INPUT( m_iGrenadeDropCapabilities, FIELD_INTEGER, "SetGrenadeDropCapabilities" ), +#endif + +#ifdef COMPANION_MELEE_ATTACK + DEFINE_FIELD( m_nMeleeDamage, FIELD_INTEGER ), +#endif + END_DATADESC() //----------------------------------------------------------------------------- @@ -137,11 +169,33 @@ END_DATADESC() CNPC_PlayerCompanion::eCoverType CNPC_PlayerCompanion::gm_fCoverSearchType; bool CNPC_PlayerCompanion::gm_bFindingCoverFromAllEnemies; +#ifdef MAPBASE +string_t CNPC_PlayerCompanion::gm_iszMortarClassname; +string_t CNPC_PlayerCompanion::gm_iszGroundTurretClassname; +#else string_t CNPC_PlayerCompanion::gm_iszMortarClassname; string_t CNPC_PlayerCompanion::gm_iszFloorTurretClassname; string_t CNPC_PlayerCompanion::gm_iszGroundTurretClassname; string_t CNPC_PlayerCompanion::gm_iszShotgunClassname; string_t CNPC_PlayerCompanion::gm_iszRollerMineClassname; +#ifdef MAPBASE +string_t CNPC_PlayerCompanion::gm_iszSMG1Classname; +string_t CNPC_PlayerCompanion::gm_iszAR2Classname; +#endif +#endif + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +CNPC_PlayerCompanion::CNPC_PlayerCompanion() +{ +#ifdef MAPBASE + if (ai_grenade_always_drop.GetBool()) + { + m_iGrenadeDropCapabilities = (eGrenadeDropCapabilities)(GRENDROPCAP_GRENADE | GRENDROPCAP_ALTFIRE | GRENDROPCAP_INTERRUPTED); + } +#endif +} //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- @@ -167,6 +221,10 @@ bool CNPC_PlayerCompanion::CreateBehaviors() AddBehavior( &m_FollowBehavior ); AddBehavior( &m_LeadBehavior ); #endif//HL2_EPISODIC + +#ifdef MAPBASE + AddBehavior( &m_FuncTankBehavior ); +#endif return BaseClass::CreateBehaviors(); } @@ -175,11 +233,25 @@ bool CNPC_PlayerCompanion::CreateBehaviors() //----------------------------------------------------------------------------- void CNPC_PlayerCompanion::Precache() { +#ifdef MAPBASE + gm_iszMortarClassname = AllocPooledString( "func_tankmortar" ); + gm_iszGroundTurretClassname = AllocPooledString( "npc_turret_ground" ); +#else gm_iszMortarClassname = AllocPooledString( "func_tankmortar" ); gm_iszFloorTurretClassname = AllocPooledString( "npc_turret_floor" ); gm_iszGroundTurretClassname = AllocPooledString( "npc_turret_ground" ); gm_iszShotgunClassname = AllocPooledString( "weapon_shotgun" ); gm_iszRollerMineClassname = AllocPooledString( "npc_rollermine" ); +#ifdef MAPBASE + gm_iszSMG1Classname = AllocPooledString( "weapon_smg1" ); + gm_iszAR2Classname = AllocPooledString( "weapon_ar2" ); +#endif +#endif + +#ifdef MAPBASE + // Moved from Spawn() + SelectModel(); +#endif PrecacheModel( STRING( GetModelName() ) ); @@ -188,6 +260,10 @@ void CNPC_PlayerCompanion::Precache() PrecacheModel( "models/props_junk/flare.mdl" ); #endif // HL2_EPISODIC +#ifdef MAPBASE + PrecacheScriptSound( "Weapon_CombineGuard.Special1" ); +#endif + BaseClass::Precache(); } @@ -195,7 +271,9 @@ void CNPC_PlayerCompanion::Precache() //----------------------------------------------------------------------------- void CNPC_PlayerCompanion::Spawn() { +#ifndef MAPBASE // Moved to Precache() SelectModel(); +#endif Precache(); @@ -234,7 +312,7 @@ void CNPC_PlayerCompanion::Spawn() m_AnnounceAttackTimer.Set( 10, 30 ); -#ifdef HL2_EPISODIC +#if HL2_EPISODIC && !MAPBASE // Mapbase permits this flag since the warning can be distracting and stripping the flag might break some HL2 maps in Episodic mods // We strip this flag because it's been made obsolete by the StartScripting behavior if ( HasSpawnFlags( SF_NPC_ALTCOLLISION ) ) { @@ -245,6 +323,10 @@ void CNPC_PlayerCompanion::Spawn() m_hFlare = NULL; #endif // HL2_EPISODIC +#if COMPANION_MELEE_ATTACK + m_nMeleeDamage = sk_companion_melee_damage.GetInt(); +#endif + BaseClass::Spawn(); } @@ -260,7 +342,7 @@ int CNPC_PlayerCompanion::Restore( IRestore &restore ) m_StandoffBehavior.SetActive( false ); } -#ifdef HL2_EPISODIC +#if HL2_EPISODIC && !MAPBASE // Mapbase permits this flag since the warning can be distracting and stripping the flag might break some HL2 maps in Episodic mods // We strip this flag because it's been made obsolete by the StartScripting behavior if ( HasSpawnFlags( SF_NPC_ALTCOLLISION ) ) { @@ -315,8 +397,13 @@ Disposition_t CNPC_PlayerCompanion::IRelationType( CBaseEntity *pTarget ) else if ( baseRelationship == D_HT && pTarget->IsNPC() && ((CAI_BaseNPC *)pTarget)->GetActiveWeapon() && +#ifdef MAPBASE + (EntIsClass( ((CAI_BaseNPC *)pTarget)->GetActiveWeapon(), gm_iszShotgunClassname ) && + ( !GetActiveWeapon() || !EntIsClass( GetActiveWeapon(), gm_iszShotgunClassname ) ) ) ) +#else ((CAI_BaseNPC *)pTarget)->GetActiveWeapon()->ClassMatches( gm_iszShotgunClassname ) && ( !GetActiveWeapon() || !GetActiveWeapon()->ClassMatches( gm_iszShotgunClassname ) ) ) +#endif { if ( (pTarget->GetAbsOrigin() - GetAbsOrigin()).LengthSqr() < Square( 25 * 12 ) ) { @@ -496,6 +583,14 @@ void CNPC_PlayerCompanion::GatherConditions() DoCustomSpeechAI(); } +#ifdef MAPBASE + // Alyx's custom combat AI copied to CNPC_PlayerCompanion for reasons specified in said function. + if ( m_NPCState == NPC_STATE_COMBAT ) + { + DoCustomCombatAI(); + } +#endif + if ( AI_IsSinglePlayer() && hl2_episodic.GetBool() && !GetEnemy() && HasCondition( COND_HEAR_PLAYER ) ) { Vector los = ( UTIL_GetLocalPlayer()->EyePosition() - EyePosition() ); @@ -538,10 +633,64 @@ void CNPC_PlayerCompanion::DoCustomSpeechAI( void ) } // Mention the player is dead +#ifdef MAPBASE + // (unless we hate them) + if ( HasCondition( COND_TALKER_PLAYER_DEAD ) && (!pPlayer || IRelationType(pPlayer) > D_FR) ) +#else if ( HasCondition( COND_TALKER_PLAYER_DEAD ) ) +#endif { SpeakIfAllowed( TLK_PLDEAD ); } + +#ifdef MAPBASE + // Unique new enemy concepts ported from Alyx + // The casts have been changed to dynamic_cast due to the risk of non-CBaseHeadcrab/CNPC_BaseZombie enemies using those classes + if ( HasCondition(COND_NEW_ENEMY) && GetEnemy() ) + { + int nClass = GetEnemy()->Classify(); + if ( nClass == CLASS_HEADCRAB ) + { + CBaseHeadcrab *pHC = dynamic_cast(GetEnemy()); + if ( pHC ) + { + // If we see a headcrab for the first time as he's jumping at me, freak out! + if ( ( GetEnemy()->GetEnemy() == this ) && pHC->IsJumping() && gpGlobals->curtime - GetEnemies()->FirstTimeSeen(GetEnemy()) < 0.5 ) + { + SpeakIfAllowed( "TLK_SPOTTED_INCOMING_HEADCRAB" ); + } + else + { + // If we see a headcrab leaving a zombie that just died, mention it + // (Note that this is now a response context since some death types remove the zombie instantly) + int nContext = pHC->FindContextByName( "from_zombie" ); + if ( nContext > -1 && !ContextExpired( nContext ) ) // pHC->GetOwnerEntity() && ( pHC->GetOwnerEntity()->Classify() == CLASS_ZOMBIE ) && !pHC->GetOwnerEntity()->IsAlive() + { + SpeakIfAllowed( "TLK_SPOTTED_HEADCRAB_LEAVING_ZOMBIE" ); + } + } + } + } + else if ( nClass == CLASS_ZOMBIE ) + { + CNPC_BaseZombie *pZombie = dynamic_cast(GetEnemy()); + // If we see a zombie getting up, mention it + if ( pZombie && pZombie->IsGettingUp() ) + { + SpeakIfAllowed( "TLK_SPOTTED_ZOMBIE_WAKEUP" ); + } + } + + if ( gpGlobals->curtime - GetEnemies()->TimeAtFirstHand( GetEnemy() ) <= 1.0f && nClass != CLASS_BULLSEYE ) + { + // New concept which did not originate from Alyx, but is in the same category as the above concepts. + // This is meant to be used when a new enemy enters the arena while combat is already in progress. + // (Note that this can still trigger when combat begins, but unlike TLK_STARTCOMBAT, it has no delay + // between combat engagements.) + SpeakIfAllowed( TLK_NEW_ENEMY ); + } + } +#endif } //----------------------------------------------------------------------------- @@ -573,6 +722,15 @@ void CNPC_PlayerCompanion::BuildScheduleTestBits() SetCustomInterruptCondition( COND_PLAYER_PUSHING ); } +#if COMPANION_MELEE_ATTACK + if (IsCurSchedule(SCHED_RANGE_ATTACK1) || + IsCurSchedule(SCHED_BACK_AWAY_FROM_ENEMY) || + IsCurSchedule(SCHED_RUN_FROM_ENEMY)) + { + SetCustomInterruptCondition( COND_CAN_MELEE_ATTACK1 ); + } +#endif + if ( ( ConditionInterruptsCurSchedule( COND_GIVE_WAY ) || IsCurSchedule(SCHED_HIDE_AND_RELOAD ) || IsCurSchedule(SCHED_RELOAD ) || @@ -715,6 +873,46 @@ int CNPC_PlayerCompanion::SelectSchedule() } } +#ifdef MAPBASE + if ( m_hForcedGrenadeTarget ) + { + // Can't throw at the target, so lets try moving to somewhere where I can see it + if ( !FVisible( m_hForcedGrenadeTarget ) ) + { + return SCHED_PC_MOVE_TO_FORCED_GREN_LOS; + } + else if ( m_flNextGrenadeCheck < gpGlobals->curtime ) + { + Vector vecTarget = m_hForcedGrenadeTarget->WorldSpaceCenter(); + + // The fact we have a forced grenade target overrides whether we're marked as "capable". + // If we're *only* alt-fire capable, use an energy ball. If not, throw a grenade. + if (!IsAltFireCapable() || IsGrenadeCapable()) + { + Vector vecTarget = m_hForcedGrenadeTarget->WorldSpaceCenter(); + { + // If we can, throw a grenade at the target. + // Ignore grenade count / distance / etc + if ( CheckCanThrowGrenade( vecTarget ) ) + { + m_hForcedGrenadeTarget = NULL; + return SCHED_PC_FORCED_GRENADE_THROW; + } + } + } + else + { + if ( FVisible( m_hForcedGrenadeTarget ) ) + { + m_vecAltFireTarget = vecTarget; + m_hForcedGrenadeTarget = NULL; + return SCHED_PC_AR2_ALTFIRE; + } + } + } + } +#endif + int nSched = SelectFlinchSchedule(); if ( nSched != SCHED_NONE ) return nSched; @@ -763,8 +961,21 @@ int CNPC_PlayerCompanion::SelectScheduleDanger() if ( pSound && (pSound->m_iType & SOUND_DANGER) ) { +#ifdef MAPBASE + if ( pSound->SoundChannel() == SOUNDENT_CHANNEL_ZOMBINE_GRENADE ) + { + SetSpeechTarget( pSound->m_hOwner ); + SpeakIfAllowed( TLK_DANGER_ZOMBINE_GRENADE ); + } + else if (!(pSound->SoundContext() & (SOUND_CONTEXT_MORTAR | SOUND_CONTEXT_FROM_SNIPER)) || IsOkToCombatSpeak()) + { + SetSpeechTarget( pSound->m_hOwner ); + SpeakIfAllowed( TLK_DANGER ); + } +#else if ( !(pSound->SoundContext() & (SOUND_CONTEXT_MORTAR|SOUND_CONTEXT_FROM_SNIPER)) || IsOkToCombatSpeak() ) SpeakIfAllowed( TLK_DANGER ); +#endif if ( HasCondition( COND_PC_SAFE_FROM_MORTAR ) ) { @@ -873,10 +1084,39 @@ bool CNPC_PlayerCompanion::IgnorePlayerPushing( void ) //----------------------------------------------------------------------------- int CNPC_PlayerCompanion::SelectScheduleCombat() { +#if COMPANION_MELEE_ATTACK + if ( HasCondition( COND_CAN_MELEE_ATTACK1 ) ) + { + DevMsg("Returning melee attack schedule\n"); + return SCHED_MELEE_ATTACK1; + } +#endif + if ( CanReload() && (HasCondition ( COND_NO_PRIMARY_AMMO ) || HasCondition(COND_LOW_PRIMARY_AMMO)) ) { return SCHED_HIDE_AND_RELOAD; } + +#ifdef MAPBASE + if ( HasGrenades() && GetEnemy() && !HasCondition(COND_SEE_ENEMY) ) + { + // We don't see our enemy. If it hasn't been long since I last saw him, + // and he's pretty close to the last place I saw him, throw a grenade in + // to flush him out. A wee bit of cheating here... + + float flTime; + float flDist; + + flTime = gpGlobals->curtime - GetEnemies()->LastTimeSeen( GetEnemy() ); + flDist = ( GetEnemy()->GetAbsOrigin() - GetEnemies()->LastSeenPosition( GetEnemy() ) ).Length(); + + //Msg("Time: %f Dist: %f\n", flTime, flDist ); + if ( flTime <= COMBINE_GRENADE_FLUSH_TIME && flDist <= COMBINE_GRENADE_FLUSH_DIST && CanGrenadeEnemy( false ) && OccupyStrategySlot( SQUAD_SLOT_SPECIAL_ATTACK ) ) + { + return SCHED_PC_RANGE_ATTACK2; + } + } +#endif return SCHED_NONE; } @@ -908,6 +1148,14 @@ bool CNPC_PlayerCompanion::ShouldDeferToFollowBehavior() return false; } +#if COMPANION_MELEE_ATTACK + if (HasCondition(COND_CAN_MELEE_ATTACK1) /*&& !GetFollowBehavior().IsActive()*/) + { + // We should only get melee condition if we're not moving + return false; + } +#endif + // Even though assault and act busy are placed ahead of the follow behavior in precedence, the below // code is necessary because we call ShouldDeferToFollowBehavior BEFORE we call the generic // BehaviorSelectSchedule, which tries the behaviors in priority order. @@ -943,6 +1191,12 @@ bool CNPC_PlayerCompanion::IsValidReasonableFacing( const Vector &vecSightDir, f if( ai_new_aiming.GetBool() ) { +#ifdef MAPBASE + // Hint node facing should still be obeyed + if (GetHintNode() && GetHintNode()->GetIgnoreFacing() != HIF_YES) + return true; +#endif + Vector vecEyePositionCentered = GetAbsOrigin(); vecEyePositionCentered.z = EyePosition().z; @@ -978,6 +1232,10 @@ int CNPC_PlayerCompanion::TranslateSchedule( int scheduleType ) pWeapon->Clip1() < ( pWeapon->GetMaxClip1() * .75 ) && pPlayer->GetAmmoCount( pWeapon->GetPrimaryAmmoType() ) ) { +#ifdef MAPBASE + // Less annoying + if ( !pWeapon->m_bInReload && (gpGlobals->curtime - GetLastEnemyTime()) > 5.0f ) +#endif SpeakIfAllowed( TLK_PLRELOAD ); } } @@ -1009,6 +1267,14 @@ int CNPC_PlayerCompanion::TranslateSchedule( int scheduleType ) return SCHED_PC_FLEE_FROM_BEST_SOUND; case SCHED_ESTABLISH_LINE_OF_FIRE: +#ifdef MAPBASE + if ( CanAltFireEnemy(false) && OccupyStrategySlot(SQUAD_SLOT_SPECIAL_ATTACK) ) + { + // If this companion has the balls to alt-fire the enemy's last known position, + // do so! + return SCHED_PC_AR2_ALTFIRE; + } +#endif case SCHED_MOVE_TO_WEAPON_RANGE: if ( IsMortar( GetEnemy() ) ) return SCHED_TAKE_COVER_FROM_ENEMY; @@ -1017,13 +1283,21 @@ int CNPC_PlayerCompanion::TranslateSchedule( int scheduleType ) case SCHED_CHASE_ENEMY: if ( IsMortar( GetEnemy() ) ) return SCHED_TAKE_COVER_FROM_ENEMY; +#ifdef MAPBASE + if ( GetEnemy() && EntIsClass( GetEnemy(), gm_isz_class_Gunship ) ) +#else if ( GetEnemy() && FClassnameIs( GetEnemy(), "npc_combinegunship" ) ) +#endif return SCHED_ESTABLISH_LINE_OF_FIRE; break; case SCHED_ESTABLISH_LINE_OF_FIRE_FALLBACK: // If we're fighting a gunship, try again +#ifdef MAPBASE + if ( GetEnemy() && EntIsClass( GetEnemy(), gm_isz_class_Gunship ) ) +#else if ( GetEnemy() && FClassnameIs( GetEnemy(), "npc_combinegunship" ) ) +#endif return SCHED_ESTABLISH_LINE_OF_FIRE; break; @@ -1034,10 +1308,44 @@ int CNPC_PlayerCompanion::TranslateSchedule( int scheduleType ) if ( GetShotRegulator()->IsInRestInterval() ) return SCHED_STANDOFF; +#ifdef MAPBASE + if (CanAltFireEnemy( true ) && OccupyStrategySlot( SQUAD_SLOT_SPECIAL_ATTACK )) + { + // Since I'm holding this squadslot, no one else can try right now. If I die before the shot + // goes off, I won't have affected anyone else's ability to use this attack at their nearest + // convenience. + return SCHED_PC_AR2_ALTFIRE; + } + + if ( !OccupyStrategySlotRange( SQUAD_SLOT_ATTACK1, SQUAD_SLOT_ATTACK2 ) ) + { + // Throw a grenade if not allowed to engage with weapon. + if ( CanGrenadeEnemy() ) + { + if ( OccupyStrategySlot( SQUAD_SLOT_SPECIAL_ATTACK ) ) + { + return SCHED_PC_RANGE_ATTACK2; + } + } + + return SCHED_STANDOFF; + } +#else if( !OccupyStrategySlotRange( SQUAD_SLOT_ATTACK1, SQUAD_SLOT_ATTACK2 ) ) return SCHED_STANDOFF; +#endif break; +#if COMPANION_MELEE_ATTACK + //case SCHED_BACK_AWAY_FROM_ENEMY: + // if (HasCondition(COND_CAN_MELEE_ATTACK1)) + // return SCHED_MELEE_ATTACK1; + // break; + + case SCHED_MELEE_ATTACK1: + return SCHED_PC_MELEE_AND_MOVE_AWAY; +#endif + case SCHED_FAIL_TAKE_COVER: if ( IsEnemyTurret() ) { @@ -1046,17 +1354,53 @@ int CNPC_PlayerCompanion::TranslateSchedule( int scheduleType ) break; case SCHED_RUN_FROM_ENEMY_FALLBACK: { +#if COMPANION_MELEE_ATTACK + if (HasCondition(COND_CAN_MELEE_ATTACK1) && !HasCondition(COND_HEAVY_DAMAGE)) + { + return SCHED_MELEE_ATTACK1; + } +#endif if ( HasCondition( COND_CAN_RANGE_ATTACK1 ) ) { return SCHED_RANGE_ATTACK1; } break; } + +#ifdef MAPBASE + case SCHED_TAKE_COVER_FROM_ENEMY: + { + if ( m_pSquad ) + { + // Have to explicitly check innate range attack condition as may have weapon with range attack 2 + if ( HasCondition(COND_CAN_RANGE_ATTACK2) && + OccupyStrategySlot( SQUAD_SLOT_SPECIAL_ATTACK ) ) + { + SpeakIfAllowed("TLK_THROWGRENADE"); + return SCHED_PC_RANGE_ATTACK2; + } + } + } + break; + case SCHED_HIDE_AND_RELOAD: + { + if( CanGrenadeEnemy() && OccupyStrategySlot( SQUAD_SLOT_SPECIAL_ATTACK ) && random->RandomInt( 0, 100 ) < 20 ) + { + // If I COULD throw a grenade and I need to reload, 20% chance I'll throw a grenade before I hide to reload. + return SCHED_PC_RANGE_ATTACK2; + } + } + break; +#endif } return BaseClass::TranslateSchedule( scheduleType ); } +#ifdef MAPBASE +//extern float GetCurrentGravity( void ); +#endif + //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- void CNPC_PlayerCompanion::StartTask( const Task_t *pTask ) @@ -1114,6 +1458,23 @@ void CNPC_PlayerCompanion::StartTask( const Task_t *pTask ) } break; +#ifdef MAPBASE + case TASK_PC_PLAY_SEQUENCE_FACE_ALTFIRE_TARGET: + StartTask_FaceAltFireTarget( pTask ); + break; + + case TASK_PC_GET_PATH_TO_FORCED_GREN_LOS: + StartTask_GetPathToForced( pTask ); + break; + + case TASK_PC_DEFER_SQUAD_GRENADES: + StartTask_DeferSquad( pTask ); + break; + + case TASK_PC_FACE_TOSS_DIR: + break; +#endif + default: BaseClass::StartTask( pTask ); break; @@ -1161,6 +1522,20 @@ void CNPC_PlayerCompanion::RunTask( const Task_t *pTask ) } break; +#ifdef MAPBASE + case TASK_PC_PLAY_SEQUENCE_FACE_ALTFIRE_TARGET: + RunTask_FaceAltFireTarget( pTask ); + break; + + case TASK_PC_GET_PATH_TO_FORCED_GREN_LOS: + RunTask_GetPathToForced( pTask ); + break; + + case TASK_PC_FACE_TOSS_DIR: + RunTask_FaceTossDir( pTask ); + break; +#endif + default: BaseClass::RunTask( pTask ); break; @@ -1343,6 +1718,19 @@ Activity CNPC_PlayerCompanion::TranslateActivityReadiness( Activity activity ) continue; } +#ifdef MAPBASE + // If we don't have the readiness activity we selected and there's no backup activity available, break the loop and return the base act. + bool bRequired; + if ( !HaveSequenceForActivity( actremap.mappedActivity ) && !HaveSequenceForActivity( Weapon_TranslateActivity( actremap.mappedActivity, &bRequired ) ) ) + { + Activity backupAct = Weapon_BackupActivity( actremap.mappedActivity, bRequired ); + if ( backupAct != actremap.mappedActivity ) + return backupAct; + else + break; + } +#endif + // We've successfully passed all criteria for remapping this return actremap.mappedActivity; } @@ -1376,6 +1764,15 @@ Activity CNPC_PlayerCompanion::NPC_TranslateActivity( Activity activity ) } } +#ifdef MAPBASE + // Vorts use ACT_RANGE_ATTACK2, but they should translate to ACT_VORTIGAUNT_DISPEL + // before that reaches this code... + if (activity == ACT_RANGE_ATTACK2) + { + activity = ACT_COMBINE_THROW_GRENADE; + } +#endif + return TranslateActivityReadiness( activity ); } @@ -1449,14 +1846,44 @@ void CNPC_PlayerCompanion::HandleAnimEvent( animevent_t *pEvent ) case EVENT_WEAPON_RELOAD: if ( GetActiveWeapon() ) { +#ifdef MAPBASE + GetActiveWeapon()->Reload_NPC(); +#else GetActiveWeapon()->WeaponSound( RELOAD_NPC ); GetActiveWeapon()->m_iClip1 = GetActiveWeapon()->GetMaxClip1(); +#endif ClearCondition(COND_LOW_PRIMARY_AMMO); ClearCondition(COND_NO_PRIMARY_AMMO); ClearCondition(COND_NO_SECONDARY_AMMO); } break; +#if COMPANION_MELEE_ATTACK + case AE_PC_MELEE: + { + CBaseEntity *pHurt = CheckTraceHullAttack(COMPANION_MELEE_DIST, -Vector(16, 16, 18), Vector(16, 16, 18), 0, DMG_CLUB); + CBaseCombatCharacter* pBCC = ToBaseCombatCharacter(pHurt); + if (pBCC) + { + Vector forward, up; + AngleVectors(GetLocalAngles(), &forward, NULL, &up); + + if (pBCC->IsPlayer()) + { + pBCC->ViewPunch(QAngle(-12, -7, 0)); + pHurt->ApplyAbsVelocityImpulse(forward * 100 + up * 50); + } + + CTakeDamageInfo info(this, this, m_nMeleeDamage, DMG_CLUB); + CalculateMeleeDamageForce(&info, forward, pBCC->GetAbsOrigin()); + pBCC->TakeDamage(info); + + EmitSound("NPC_Combine.WeaponBash"); + } + break; + } +#endif + default: BaseClass::HandleAnimEvent( pEvent ); break; @@ -1533,6 +1960,20 @@ void CNPC_PlayerCompanion::ModifyOrAppendCriteria( AI_CriteriaSet& set ) set.AppendCriteria( "hurt_by_fire", "1" ); } +#ifdef MAPBASE + // Ported from Alyx. + AIEnemiesIter_t iter; + int iNumEnemies = 0; + for ( AI_EnemyInfo_t *pEMemory = GetEnemies()->GetFirst(&iter); pEMemory != NULL; pEMemory = GetEnemies()->GetNext(&iter) ) + { + if ( pEMemory->hEnemy->IsAlive() && ( pEMemory->hEnemy->Classify() != CLASS_BULLSEYE ) ) + { + iNumEnemies++; + } + } + set.AppendCriteria( "num_enemies", UTIL_VarArgs( "%d", iNumEnemies ) ); +#endif + if ( m_bReadinessCapable ) { switch( GetReadinessLevel() ) @@ -1574,11 +2015,33 @@ bool CNPC_PlayerCompanion::IsReadinessCapable() return false; #endif +#ifdef MAPBASE +#ifdef HL2_EPISODIC + if (GetActiveWeapon()) +#else + // We already know we have a weapon due to the check above +#endif + { + // Rather than looking up the activity string, we just make sure our weapon accepts a few basic readiness activity overrides. + // This lets us make sure our weapon is readiness-capable to begin with. + if ( TranslateActivity( ACT_IDLE_RELAXED ) == ACT_IDLE_RELAXED && + TranslateActivity( ACT_IDLE_STIMULATED ) == ACT_IDLE_STIMULATED && + TranslateActivity( ACT_IDLE_AGITATED ) == ACT_IDLE_AGITATED ) + return false; + + if (LookupActivity( "ACT_IDLE_AIM_RIFLE_STIMULATED" ) == ACT_INVALID) + return false; + + if (EntIsClass(GetActiveWeapon(), gm_isz_class_RPG)) + return false; + } +#else if( GetActiveWeapon() && LookupActivity("ACT_IDLE_AIM_RIFLE_STIMULATED") == ACT_INVALID ) return false; if( GetActiveWeapon() && FClassnameIs( GetActiveWeapon(), "weapon_rpg" ) ) return false; +#endif return true; } @@ -2329,7 +2792,11 @@ Vector CNPC_PlayerCompanion::GetActualShootPosition( const Vector &shootOrigin ) //------------------------------------------------------------------------------ WeaponProficiency_t CNPC_PlayerCompanion::CalcWeaponProficiency( CBaseCombatWeapon *pWeapon ) { +#ifdef MAPBASE + if ( EntIsClass(pWeapon, gm_iszAR2Classname) ) +#else if( FClassnameIs( pWeapon, "weapon_ar2" ) ) +#endif { return WEAPON_PROFICIENCY_VERY_GOOD; } @@ -2346,10 +2813,21 @@ bool CNPC_PlayerCompanion::Weapon_CanUse( CBaseCombatWeapon *pWeapon ) // If this weapon is a shotgun, take measures to control how many // are being used in this squad. Don't allow a companion to pick up // a shotgun if a squadmate already has one. +#ifdef MAPBASE + if (EntIsClass(pWeapon, gm_iszShotgunClassname)) +#else if( pWeapon->ClassMatches( gm_iszShotgunClassname ) ) +#endif { return (NumWeaponsInSquad("weapon_shotgun") < 1 ); } +#ifdef MAPBASE + else if (EntIsClass( pWeapon, gm_isz_class_Pistol ) || EntIsClass( pWeapon, gm_isz_class_357 ) || EntIsClass( pWeapon, gm_isz_class_Crossbow )) + { + // The AI automatically detects these weapons as usable now that there's animations for them, so ensure this behavior can be toggled in situations where that's not desirable + return ai_allow_new_weapons.GetBool(); + } +#endif else { return true; @@ -2366,6 +2844,15 @@ bool CNPC_PlayerCompanion::ShouldLookForBetterWeapon() if ( m_bDontPickupWeapons ) return false; +#ifdef MAPBASE + // Now that citizens can holster weapons, they might look for a new one while unarmed. + // Since that could already be worked around with OnHolster > DisableWeaponPickup, I decided to keep it that way in case it's desirable. + + // Don't look for a new weapon if we have secondary ammo for our current one. + if (m_iNumGrenades > 0 && IsAltFireCapable() && GetActiveWeapon() && GetActiveWeapon()->UsesSecondaryAmmo()) + return false; +#endif + return BaseClass::ShouldLookForBetterWeapon(); } @@ -2377,15 +2864,114 @@ void CNPC_PlayerCompanion::Weapon_Equip( CBaseCombatWeapon *pWeapon ) m_bReadinessCapable = IsReadinessCapable(); } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +bool CNPC_PlayerCompanion::DoUnholster() +{ + if ( BaseClass::DoUnholster() ) + { + m_bReadinessCapable = IsReadinessCapable(); + return true; + } + + return false; +} +#endif + //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ void CNPC_PlayerCompanion::PickupWeapon( CBaseCombatWeapon *pWeapon ) { BaseClass::PickupWeapon( pWeapon ); +#ifdef MAPBASE + SetPotentialSpeechTarget( pWeapon ); + SetSpeechTarget(pWeapon); + SpeakIfAllowed( TLK_NEWWEAPON ); + m_OnWeaponPickup.FireOutput( pWeapon, this ); +#else SpeakIfAllowed( TLK_NEWWEAPON ); m_OnWeaponPickup.FireOutput( this, this ); +#endif } +#if COMPANION_MELEE_ATTACK +//----------------------------------------------------------------------------- +// Purpose: Cache user entity field values until spawn is called. +// Input : szKeyName - Key to handle. +// szValue - Value for key. +// Output : Returns true if the key was handled, false if not. +//----------------------------------------------------------------------------- +bool CNPC_PlayerCompanion::KeyValue( const char *szKeyName, const char *szValue ) +{ + // MeleeAttack01 restoration, see CNPC_PlayerCompanion::MeleeAttack1Conditions + if (FStrEq(szKeyName, "EnableMeleeAttack")) + { + if (!FStrEq(szValue, "0")) + CapabilitiesAdd( bits_CAP_INNATE_MELEE_ATTACK1 ); + else + CapabilitiesRemove( bits_CAP_INNATE_MELEE_ATTACK1 ); + + return true; + } + + return BaseClass::KeyValue( szKeyName, szValue ); +} + +//----------------------------------------------------------------------------- +// Purpose: For unused citizen melee attack (vorts might use this too) +// Input : +// Output : +//----------------------------------------------------------------------------- +int CNPC_PlayerCompanion::MeleeAttack1Conditions ( float flDot, float flDist ) +{ + if (!GetActiveWeapon()) + return COND_NONE; + + if (IsMoving()) + { + // Is moving, cond_none + return COND_NONE; + } + + if (flDist > COMPANION_MELEE_DIST) + { + return COND_NONE; // COND_TOO_FAR_TO_ATTACK; + } + else if (flDot < 0.7) + { + return COND_NONE; // COND_NOT_FACING_ATTACK; + } + + if (GetEnemy()) + { + // Check Z + if ( fabs(GetEnemy()->GetAbsOrigin().z - GetAbsOrigin().z) > 64 ) + return COND_NONE; + + if ( GetEnemy()->MyCombatCharacterPointer() && GetEnemy()->MyCombatCharacterPointer()->GetHullType() == HULL_TINY ) + { + return COND_NONE; + } + } + + // Make sure not trying to kick through a window or something. + trace_t tr; + Vector vecSrc, vecEnd; + + vecSrc = WorldSpaceCenter(); + vecEnd = GetEnemy()->WorldSpaceCenter(); + + AI_TraceLine(vecSrc, vecEnd, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr); + if( tr.m_pEnt != GetEnemy() ) + { + return COND_NONE; + } + + return COND_CAN_MELEE_ATTACK1; +} +#endif + //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- @@ -2880,11 +3466,19 @@ bool CNPC_PlayerCompanion::OverrideMove( float flInterval ) if ( !overrode && GetNavigator()->GetGoalType() != GOALTYPE_NONE ) { +#ifdef MAPBASE + #define iszEnvFire gm_isz_class_EnvFire +#else string_t iszEnvFire = AllocPooledString( "env_fire" ); +#endif string_t iszBounceBomb = AllocPooledString( "combine_mine" ); #ifdef HL2_EPISODIC +#ifdef MAPBASE + #define iszNPCTurretFloor gm_isz_class_FloorTurret +#else string_t iszNPCTurretFloor = AllocPooledString( "npc_turret_floor" ); +#endif string_t iszEntityFlame = AllocPooledString( "entityflame" ); #endif // HL2_EPISODIC @@ -2946,7 +3540,7 @@ bool CNPC_PlayerCompanion::OverrideMove( float flInterval ) else if ( pEntity->m_iClassname == iszBounceBomb ) { CBounceBomb *pBomb = static_cast(pEntity); - if ( pBomb && !pBomb->IsPlayerPlaced() && pBomb->IsAwake() ) + if ( pBomb && pBomb->ShouldBeAvoidedByCompanions() ) { UTIL_TraceLine( WorldSpaceCenter(), pEntity->WorldSpaceCenter(), MASK_BLOCKLOS, pEntity, COLLISION_GROUP_NONE, &tr ); if (tr.fraction == 1.0 && !tr.startsolid) @@ -3503,6 +4097,7 @@ void CNPC_PlayerCompanion::InputDisableWeaponPickup( inputdata_t &inputdata ) m_bDontPickupWeapons = true; } +#ifndef MAPBASE // See CAI_BaseNPC::InputGiveWeapon() //------------------------------------------------------------------------------ // Purpose: Give the NPC in question the weapon specified //------------------------------------------------------------------------------ @@ -3522,6 +4117,7 @@ void CNPC_PlayerCompanion::InputGiveWeapon( inputdata_t &inputdata ) } } } +#endif #if HL2_EPISODIC //------------------------------------------------------------------------------ @@ -3570,6 +4166,9 @@ void CNPC_PlayerCompanion::OnPlayerKilledOther( CBaseEntity *pVictim, const CTak } CBaseEntity *pInflictor = info.GetInflictor(); +#ifdef MAPBASE + AI_CriteriaSet modifiers; +#else int iNumBarrels = 0; int iConsecutivePlayerKills = 0; bool bPuntedGrenade = false; @@ -3578,6 +4177,7 @@ void CNPC_PlayerCompanion::OnPlayerKilledOther( CBaseEntity *pVictim, const CTak bool bVictimWasAttacker = false; bool bHeadshot = false; bool bOneShot = false; +#endif if ( dynamic_cast( pInflictor ) && ( info.GetDamageType() & DMG_BLAST ) ) { @@ -3590,7 +4190,11 @@ void CNPC_PlayerCompanion::OnPlayerKilledOther( CBaseEntity *pVictim, const CTak m_iNumConsecutiveBarrelsExploded++; m_fLastBarrelExploded = gpGlobals->curtime; +#ifdef MAPBASE + modifiers.AppendCriteria( "num_barrels", UTIL_VarArgs("%i", m_iNumConsecutiveBarrelsExploded) ); +#else iNumBarrels = m_iNumConsecutiveBarrelsExploded; +#endif } else { @@ -3602,7 +4206,11 @@ void CNPC_PlayerCompanion::OnPlayerKilledOther( CBaseEntity *pVictim, const CTak } m_iNumConsecutivePlayerKills++; m_fLastPlayerKill = gpGlobals->curtime; +#ifdef MAPBASE + modifiers.AppendCriteria( "consecutive_player_kills", UTIL_VarArgs("%i", m_iNumConsecutivePlayerKills) ); +#else iConsecutivePlayerKills = m_iNumConsecutivePlayerKills; +#endif } // don't comment on kills when she can't see the victim @@ -3612,29 +4220,53 @@ void CNPC_PlayerCompanion::OnPlayerKilledOther( CBaseEntity *pVictim, const CTak } // check if the player killed an enemy by punting a grenade +#ifdef MAPBASE + modifiers.AppendCriteria( "punted_grenade", ( pInflictor && Fraggrenade_WasPunted( pInflictor ) && Fraggrenade_WasCreatedByCombine( pInflictor ) ) ? "1" : "0" ); +#else if ( pInflictor && Fraggrenade_WasPunted( pInflictor ) && Fraggrenade_WasCreatedByCombine( pInflictor ) ) { bPuntedGrenade = true; } +#endif // check if the victim was Alyx's enemy +#ifdef MAPBASE + modifiers.AppendCriteria( "victim_was_enemy", GetEnemy() == pVictim ? "1" : "0" ); +#else if ( GetEnemy() == pVictim ) { bVictimWasEnemy = true; } +#endif AI_EnemyInfo_t *pEMemory = GetEnemies()->Find( pVictim ); if ( pEMemory != NULL ) { // was Alyx being mobbed by this enemy? +#ifdef MAPBASE + modifiers.AppendCriteria( "victim_was_mob", pEMemory->bMobbedMe ? "1" : "0" ); + modifiers.AppendCriteria( "victim_was_attacker", pEMemory->timeLastReceivedDamageFrom > 0 ? "1" : "0" ); +#else bVictimWasMob = pEMemory->bMobbedMe; // has Alyx recieved damage from this enemy? if ( pEMemory->timeLastReceivedDamageFrom > 0 ) { bVictimWasAttacker = true; } +#endif } +#ifdef MAPBASE + else + { + modifiers.AppendCriteria( "victim_was_mob", "0" ); + modifiers.AppendCriteria( "victim_was_attacker", "0" ); + } +#endif +#ifdef MAPBASE + modifiers.AppendCriteria( "headshot", ((pCombatVictim->LastHitGroup() == HITGROUP_HEAD) && (info.GetDamageType() & DMG_BULLET)) ? "1" : "0" ); + modifiers.AppendCriteria( "oneshot", ((pCombatVictim->GetDamageCount() == 1) && (info.GetDamageType() & DMG_BULLET)) ? "1" : "0" ); +#else // Was it a headshot? if ( ( pCombatVictim->LastHitGroup() == HITGROUP_HEAD ) && ( info.GetDamageType() & DMG_BULLET ) ) { @@ -3646,18 +4278,200 @@ void CNPC_PlayerCompanion::OnPlayerKilledOther( CBaseEntity *pVictim, const CTak { bOneShot = true; } +#endif +#ifdef MAPBASE + ModifyOrAppendEnemyCriteria(modifiers, pVictim); +#else // set up the speech modifiers CFmtStrN<512> modifiers( "num_barrels:%d,distancetoplayerenemy:%f,playerAmmo:%s,consecutive_player_kills:%d," "punted_grenade:%d,victim_was_enemy:%d,victim_was_mob:%d,victim_was_attacker:%d,headshot:%d,oneshot:%d", iNumBarrels, EnemyDistance( pVictim ), info.GetAmmoName(), iConsecutivePlayerKills, bPuntedGrenade, bVictimWasEnemy, bVictimWasMob, bVictimWasAttacker, bHeadshot, bOneShot ); +#endif SpeakIfAllowed( TLK_PLAYER_KILLED_NPC, modifiers ); BaseClass::OnPlayerKilledOther( pVictim, info ); } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CNPC_PlayerCompanion::Event_Killed( const CTakeDamageInfo &info ) +{ + // For now, allied player companions are set to always drop grenades and other items + // even if the player did not kill them + CBasePlayer *pPlayer = UTIL_GetLocalPlayer(); + if (!IsPlayerAlly( pPlayer )) + { + pPlayer = ToBasePlayer( info.GetAttacker() ); + + // See if there's a player in a vehicle instead (from CNPC_CombineS) + if ( !pPlayer ) + { + CPropVehicleDriveable *pVehicle = dynamic_cast( info.GetAttacker() ) ; + if ( pVehicle && pVehicle->GetDriver() && pVehicle->GetDriver()->IsPlayer() ) + { + pPlayer = assert_cast( pVehicle->GetDriver() ); + } + } + } + + if ( pPlayer != NULL ) + { + // Drop grenades if we should + DropGrenadeItemsOnDeath( info, pPlayer ); + } + + BaseClass::Event_Killed( info ); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CNPC_PlayerCompanion::Event_KilledOther( CBaseEntity *pVictim, const CTakeDamageInfo &info ) +{ + BaseClass::Event_KilledOther( pVictim, info ); + + if ( pVictim ) + { + if (pVictim->IsPlayer() || (pVictim->IsNPC() && + ( pVictim->MyNPCPointer()->GetLastPlayerDamageTime() == 0 || + gpGlobals->curtime - pVictim->MyNPCPointer()->GetLastPlayerDamageTime() > 5 )) ) + { + AI_CriteriaSet modifiers; + + AI_EnemyInfo_t *pEMemory = GetEnemies()->Find( pVictim ); + if ( pEMemory != NULL ) + { + modifiers.AppendCriteria( "victim_was_mob", pEMemory->bMobbedMe ? "1" : "0" ); + modifiers.AppendCriteria( "victim_was_attacker", pEMemory->timeLastReceivedDamageFrom > 0 ? "1" : "0" ); + } + else + { + modifiers.AppendCriteria( "victim_was_mob", "0" ); + modifiers.AppendCriteria( "victim_was_attacker", "0" ); + } + + CBaseCombatCharacter *pCombatVictim = pVictim->MyCombatCharacterPointer(); + if (pCombatVictim) + { + modifiers.AppendCriteria( "headshot", ((pCombatVictim->LastHitGroup() == HITGROUP_HEAD) && (info.GetDamageType() & DMG_BULLET)) ? "1" : "0" ); + modifiers.AppendCriteria( "oneshot", ((pCombatVictim->GetDamageCount() == 1) && (info.GetDamageType() & DMG_BULLET)) ? "1" : "0" ); + } + else + { + modifiers.AppendCriteria( "headshot", "0" ); + modifiers.AppendCriteria( "oneshot", "0" ); + } + + SetPotentialSpeechTarget( pVictim ); + SetSpeechTarget( pVictim ); + SpeakIfAllowed( TLK_ENEMY_DEAD, modifiers ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Called by enemy NPC's when they are ignited +// Input : pVictim - entity that was ignited +//----------------------------------------------------------------------------- +void CNPC_PlayerCompanion::EnemyIgnited( CAI_BaseNPC *pVictim ) +{ + BaseClass::EnemyIgnited( pVictim ); + + if ( FVisible( pVictim ) ) + { + SpeakIfAllowed( TLK_ENEMY_BURNING ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Handles custom combat speech stuff ported from Alyx. +//----------------------------------------------------------------------------- +void CNPC_PlayerCompanion::DoCustomCombatAI( void ) +{ + #define COMPANION_MIN_MOB_DIST_SQR Square(120) // Any enemy closer than this adds to the 'mob' + #define COMPANION_MIN_CONSIDER_DIST Square(1200) // Only enemies within this range are counted and considered to generate AI speech + + AIEnemiesIter_t iter; + + float visibleEnemiesScore = 0.0f; + float closeEnemiesScore = 0.0f; + + for ( AI_EnemyInfo_t *pEMemory = GetEnemies()->GetFirst(&iter); pEMemory != NULL; pEMemory = GetEnemies()->GetNext(&iter) ) + { + if ( IRelationType( pEMemory->hEnemy ) != D_NU && IRelationType( pEMemory->hEnemy ) != D_LI && pEMemory->hEnemy->GetAbsOrigin().DistToSqr(GetAbsOrigin()) <= COMPANION_MIN_CONSIDER_DIST ) + { + if( pEMemory->hEnemy && pEMemory->hEnemy->IsAlive() && gpGlobals->curtime - pEMemory->timeLastSeen <= 0.5f && pEMemory->hEnemy->Classify() != CLASS_BULLSEYE ) + { + if( pEMemory->hEnemy->GetAbsOrigin().DistToSqr(GetAbsOrigin()) <= COMPANION_MIN_MOB_DIST_SQR ) + { + closeEnemiesScore += 1.0f; + } + else + { + visibleEnemiesScore += 1.0f; + } + } + } + } + + if( closeEnemiesScore > 2 ) + { + SetCondition( COND_MOBBED_BY_ENEMIES ); + + // mark anyone in the mob as having mobbed me + for ( AI_EnemyInfo_t *pEMemory = GetEnemies()->GetFirst(&iter); pEMemory != NULL; pEMemory = GetEnemies()->GetNext(&iter) ) + { + if ( pEMemory->bMobbedMe ) + continue; + + if ( IRelationType( pEMemory->hEnemy ) != D_NU && IRelationType( pEMemory->hEnemy ) != D_LI && pEMemory->hEnemy->GetAbsOrigin().DistToSqr(GetAbsOrigin()) <= COMPANION_MIN_CONSIDER_DIST ) + { + if( pEMemory->hEnemy && pEMemory->hEnemy->IsAlive() && gpGlobals->curtime - pEMemory->timeLastSeen <= 0.5f && pEMemory->hEnemy->Classify() != CLASS_BULLSEYE ) + { + if( pEMemory->hEnemy->GetAbsOrigin().DistToSqr(GetAbsOrigin()) <= COMPANION_MIN_MOB_DIST_SQR ) + { + pEMemory->bMobbedMe = true; + } + } + } + } + } + else + { + ClearCondition( COND_MOBBED_BY_ENEMIES ); + } + + // Say a combat thing + if( HasCondition( COND_MOBBED_BY_ENEMIES ) ) + { + SpeakIfAllowed( TLK_MOBBED ); + } + else if( visibleEnemiesScore > 4 ) + { + SpeakIfAllowed( TLK_MANY_ENEMIES ); + } + + // If we're not currently attacking or vulnerable, try speaking + else if ( gpGlobals->curtime - GetLastAttackTime() > 1.0f && (!HasCondition( COND_SEE_ENEMY ) || IsCurSchedule( SCHED_RELOAD ) || IsCurSchedule( SCHED_HIDE_AND_RELOAD )) ) + { + int chance = ( IsMoving() ) ? 20 : 3; + if ( ShouldSpeakRandom( TLK_COMBAT_IDLE, chance ) ) + { + AI_CriteriaSet modifiers; + + modifiers.AppendCriteria( "in_cover", HasMemory( bits_MEMORY_INCOVER ) ? "1" : "0" ); + modifiers.AppendCriteria( "lastseenenemy", UTIL_VarArgs( "%f", gpGlobals->curtime - GetEnemyLastTimeSeen() ) ); + + SpeakIfAllowed( TLK_COMBAT_IDLE, modifiers ); + } + } +} +#endif + //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- bool CNPC_PlayerCompanion::IsNavigationUrgent( void ) @@ -3712,10 +4526,20 @@ AI_BEGIN_CUSTOM_NPC( player_companion_base, CNPC_PlayerCompanion ) DECLARE_TASK( TASK_PC_WAITOUT_MORTAR ) DECLARE_TASK( TASK_PC_GET_PATH_OFF_COMPANION ) +#ifdef MAPBASE + DECLARE_TASK( TASK_PC_PLAY_SEQUENCE_FACE_ALTFIRE_TARGET ) + DECLARE_TASK( TASK_PC_GET_PATH_TO_FORCED_GREN_LOS ) + DECLARE_TASK( TASK_PC_DEFER_SQUAD_GRENADES ) + DECLARE_TASK( TASK_PC_FACE_TOSS_DIR ) +#endif DECLARE_ANIMEVENT( AE_COMPANION_PRODUCE_FLARE ) DECLARE_ANIMEVENT( AE_COMPANION_LIGHT_FLARE ) DECLARE_ANIMEVENT( AE_COMPANION_RELEASE_FLARE ) +#ifdef MAPBASE + DECLARE_ANIMEVENT( COMBINE_AE_BEGIN_ALTFIRE ) + DECLARE_ANIMEVENT( COMBINE_AE_ALTFIRE ) +#endif //========================================================= // > TakeCoverFromBestSound @@ -3845,6 +4669,107 @@ AI_BEGIN_CUSTOM_NPC( player_companion_base, CNPC_PlayerCompanion ) "" ) +#ifdef COMPANION_MELEE_ATTACK + DEFINE_SCHEDULE + ( + SCHED_PC_MELEE_AND_MOVE_AWAY, + + " Tasks" + " TASK_STOP_MOVING 0" + " TASK_FACE_ENEMY 0" + " TASK_ANNOUNCE_ATTACK 1" // 1 = primary attack + " TASK_MELEE_ATTACK1 0" + " TASK_SET_SCHEDULE SCHEDULE:SCHED_MOVE_AWAY_FROM_ENEMY" + "" + " Interrupts" + " COND_NEW_ENEMY" + " COND_ENEMY_DEAD" + //" COND_LIGHT_DAMAGE" + " COND_HEAVY_DAMAGE" + " COND_ENEMY_OCCLUDED" + ) +#endif + +#ifdef MAPBASE + //========================================================= + // AR2 Alt Fire Attack + //========================================================= + DEFINE_SCHEDULE + ( + SCHED_PC_AR2_ALTFIRE, + + " Tasks" + " TASK_STOP_MOVING 0" + " TASK_ANNOUNCE_ATTACK 1" + " TASK_PC_PLAY_SEQUENCE_FACE_ALTFIRE_TARGET ACTIVITY:ACT_COMBINE_AR2_ALTFIRE" + "" + " Interrupts" + " COND_TOO_CLOSE_TO_ATTACK" + ) + + //========================================================= + // Move to LOS of the mapmaker's forced grenade throw target + //========================================================= + DEFINE_SCHEDULE + ( + SCHED_PC_MOVE_TO_FORCED_GREN_LOS, + + " Tasks " + " TASK_SET_TOLERANCE_DISTANCE 48" + " TASK_PC_GET_PATH_TO_FORCED_GREN_LOS 0" + " TASK_SPEAK_SENTENCE 1" + " TASK_RUN_PATH 0" + " TASK_WAIT_FOR_MOVEMENT 0" + " " + " Interrupts " + " COND_NEW_ENEMY" + " COND_ENEMY_DEAD" + " COND_CAN_MELEE_ATTACK1" + " COND_CAN_MELEE_ATTACK2" + " COND_HEAR_DANGER" + " COND_HEAR_MOVE_AWAY" + " COND_HEAVY_DAMAGE" + ) + + //========================================================= + // Mapmaker forced grenade throw + //========================================================= + DEFINE_SCHEDULE + ( + SCHED_PC_FORCED_GRENADE_THROW, + + " Tasks" + " TASK_STOP_MOVING 0" + " TASK_PC_FACE_TOSS_DIR 0" + " TASK_ANNOUNCE_ATTACK 2" // 2 = grenade + " TASK_PLAY_SEQUENCE ACTIVITY:ACT_RANGE_ATTACK2" + " TASK_PC_DEFER_SQUAD_GRENADES 0" + "" + " Interrupts" + ) + + //========================================================= + // SCHED_PC_RANGE_ATTACK2 + // + // secondary range attack. Overriden because base class stops attacking when the enemy is occluded. + // combines's grenade toss requires the enemy be occluded. + //========================================================= + DEFINE_SCHEDULE + ( + SCHED_PC_RANGE_ATTACK2, + + " Tasks" + " TASK_STOP_MOVING 0" + " TASK_PC_FACE_TOSS_DIR 0" + " TASK_ANNOUNCE_ATTACK 2" // 2 = grenade + " TASK_PLAY_SEQUENCE ACTIVITY:ACT_RANGE_ATTACK2" + " TASK_PC_DEFER_SQUAD_GRENADES 0" + " TASK_SET_SCHEDULE SCHEDULE:SCHED_HIDE_AND_RELOAD" // don't run immediately after throwing grenade. + "" + " Interrupts" + ) +#endif + AI_END_CUSTOM_NPC() diff --git a/src/game/server/hl2/npc_playercompanion.h b/src/game/server/hl2/npc_playercompanion.h index f1c3b1f3..e0f6769e 100644 --- a/src/game/server/hl2/npc_playercompanion.h +++ b/src/game/server/hl2/npc_playercompanion.h @@ -22,6 +22,11 @@ #include "ai_behavior_passenger_companion.h" #endif +#ifdef MAPBASE +#include "ai_behavior_functank.h" +#include "mapbase/ai_grenade.h" +#endif + #if defined( _WIN32 ) #pragma once #endif @@ -85,17 +90,29 @@ public: class CPhysicsProp; +#ifdef MAPBASE +// If you think about it, this is really unnecessary. +//#define COMPANION_MELEE_ATTACK 1 +#endif + //----------------------------------------------------------------------------- // // CLASS: CNPC_PlayerCompanion // //----------------------------------------------------------------------------- - +#ifdef MAPBASE +class CNPC_PlayerCompanion : public CAI_GrenadeUser +{ + DECLARE_CLASS( CNPC_PlayerCompanion, CAI_GrenadeUser ); +#else class CNPC_PlayerCompanion : public CAI_PlayerAlly { DECLARE_CLASS( CNPC_PlayerCompanion, CAI_PlayerAlly ); - +#endif public: + + CNPC_PlayerCompanion(); + //--------------------------------- bool CreateBehaviors(); void Precache(); @@ -189,7 +206,9 @@ public: virtual void ReadinessLevelChanged( int iPriorLevel ) { } +#ifndef MAPBASE void InputGiveWeapon( inputdata_t &inputdata ); +#endif #ifdef HL2_EPISODIC //--------------------------------- @@ -217,6 +236,15 @@ public: public: virtual void OnPlayerKilledOther( CBaseEntity *pVictim, const CTakeDamageInfo &info ); +#ifdef MAPBASE + // This is just here to overwrite ai_playerally's TLK_ENEMY_DEAD + virtual void OnKilledNPC(CBaseCombatCharacter *pKilled) {} + + virtual void Event_Killed( const CTakeDamageInfo &info ); + virtual void Event_KilledOther( CBaseEntity *pVictim, const CTakeDamageInfo &info ); + virtual void EnemyIgnited( CAI_BaseNPC *pVictim ); + virtual void DoCustomCombatAI( void ); +#endif //--------------------------------- //--------------------------------- @@ -255,7 +283,15 @@ public: bool ShouldLookForBetterWeapon(); bool Weapon_CanUse( CBaseCombatWeapon *pWeapon ); void Weapon_Equip( CBaseCombatWeapon *pWeapon ); +#ifdef MAPBASE + bool DoUnholster( void ); +#endif void PickupWeapon( CBaseCombatWeapon *pWeapon ); + +#if COMPANION_MELEE_ATTACK + bool KeyValue( const char *szKeyName, const char *szValue ); + int MeleeAttack1Conditions( float flDot, float flDist ); +#endif bool FindCoverPos( CBaseEntity *pEntity, Vector *pResult); bool FindCoverPosInRadius( CBaseEntity *pEntity, const Vector &goalPos, float coverRadius, Vector *pResult ); @@ -308,6 +344,21 @@ public: bool AllowReadinessValueChange( void ); +#ifdef MAPBASE + virtual bool IsAltFireCapable() { return (m_iGrenadeCapabilities & GRENCAP_ALTFIRE) != 0 && BaseClass::IsAltFireCapable(); } + virtual bool IsGrenadeCapable() { return (m_iGrenadeCapabilities & GRENCAP_GRENADE) != 0; } + + virtual bool ShouldDropGrenades() { return (m_iGrenadeDropCapabilities & GRENDROPCAP_GRENADE) != 0 && BaseClass::ShouldDropGrenades(); } + virtual bool ShouldDropInterruptedGrenades() { return (m_iGrenadeDropCapabilities & GRENDROPCAP_INTERRUPTED) != 0 && BaseClass::ShouldDropInterruptedGrenades(); } + virtual bool ShouldDropAltFire() { return (m_iGrenadeDropCapabilities & GRENDROPCAP_ALTFIRE) != 0 && BaseClass::ShouldDropAltFire(); } + +private: + + // Determines whether this NPC is allowed to use grenades or alt-fire stuff. + eGrenadeCapabilities m_iGrenadeCapabilities; + eGrenadeDropCapabilities m_iGrenadeDropCapabilities; +#endif + protected: //----------------------------------------------------- // Conditions, Schedules, Tasks @@ -326,10 +377,25 @@ protected: SCHED_PC_FAIL_TAKE_COVER_TURRET, SCHED_PC_FAKEOUT_MORTAR, SCHED_PC_GET_OFF_COMPANION, +#ifdef COMPANION_MELEE_ATTACK + SCHED_PC_MELEE_AND_MOVE_AWAY, +#endif +#ifdef MAPBASE + SCHED_PC_AR2_ALTFIRE, + SCHED_PC_MOVE_TO_FORCED_GREN_LOS, + SCHED_PC_FORCED_GRENADE_THROW, + SCHED_PC_RANGE_ATTACK2, // Grenade throw +#endif NEXT_SCHEDULE, TASK_PC_WAITOUT_MORTAR = BaseClass::NEXT_TASK, TASK_PC_GET_PATH_OFF_COMPANION, +#ifdef MAPBASE + TASK_PC_PLAY_SEQUENCE_FACE_ALTFIRE_TARGET, + TASK_PC_GET_PATH_TO_FORCED_GREN_LOS, + TASK_PC_DEFER_SQUAD_GRENADES, + TASK_PC_FACE_TOSS_DIR, +#endif NEXT_TASK, }; @@ -371,6 +437,9 @@ protected: CAI_OperatorBehavior m_OperatorBehavior; CAI_PassengerBehaviorCompanion m_PassengerBehavior; CAI_FearBehavior m_FearBehavior; +#endif +#ifdef MAPBASE + CAI_FuncTankBehavior m_FuncTankBehavior; #endif //----------------------------------------------------- @@ -406,11 +475,25 @@ protected: //----------------------------------------------------- +#ifdef MAPBASE + static string_t gm_iszMortarClassname; + #define gm_iszFloorTurretClassname gm_isz_class_FloorTurret + static string_t gm_iszGroundTurretClassname; + #define gm_iszShotgunClassname gm_isz_class_Shotgun + #define gm_iszRollerMineClassname gm_isz_class_Rollermine + #define gm_iszSMG1Classname gm_isz_class_SMG1 + #define gm_iszAR2Classname gm_isz_class_AR2 +#else static string_t gm_iszMortarClassname; static string_t gm_iszFloorTurretClassname; static string_t gm_iszGroundTurretClassname; static string_t gm_iszShotgunClassname; static string_t gm_iszRollerMineClassname; +#ifdef MAPBASE + static string_t gm_iszSMG1Classname; + static string_t gm_iszAR2Classname; +#endif +#endif //----------------------------------------------------- @@ -424,6 +507,10 @@ protected: COutputEvent m_OnWeaponPickup; +#if COMPANION_MELEE_ATTACK + int m_nMeleeDamage; +#endif + CStopwatch m_SpeechWatch_PlayerLooking; DECLARE_DATADESC(); diff --git a/src/game/server/hl2/npc_rollermine.cpp b/src/game/server/hl2/npc_rollermine.cpp index d850fe2c..fc803f1f 100644 --- a/src/game/server/hl2/npc_rollermine.cpp +++ b/src/game/server/hl2/npc_rollermine.cpp @@ -310,6 +310,16 @@ protected: bool IsActive() { return m_flActiveTime > gpGlobals->curtime ? false : true; } + inline float GetForwardSpeed() const + { +#ifdef MAPBASE + if (m_flSpeedModifier != 1.0f) + return m_flForwardSpeed * m_flSpeedModifier; + else +#endif + return m_flForwardSpeed; + } + // INPCInteractive Functions virtual bool CanInteractWith( CAI_BaseNPC *pUser ) { return true; } virtual bool HasBeenInteractedWith() { return m_bHackedByAlyx; } @@ -388,7 +398,11 @@ BEGIN_DATADESC( CNPC_RollerMine ) DEFINE_FIELD( m_bBuried, FIELD_BOOLEAN ), DEFINE_FIELD( m_wakeUp, FIELD_BOOLEAN ), DEFINE_FIELD( m_bEmbedOnGroundImpact, FIELD_BOOLEAN ), +#ifdef MAPBASE + DEFINE_KEYFIELD( m_bHackedByAlyx, FIELD_BOOLEAN, "Hacked" ), +#else DEFINE_FIELD( m_bHackedByAlyx, FIELD_BOOLEAN ), +#endif DEFINE_FIELD( m_bPowerDown, FIELD_BOOLEAN ), DEFINE_FIELD( m_flPowerDownTime, FIELD_TIME ), @@ -543,6 +557,9 @@ void CNPC_RollerMine::Spawn( void ) BaseClass::Spawn(); AddEFlags( EFL_NO_DISSOLVE ); +#ifdef MAPBASE + AddEFlags( EFL_NO_MEGAPHYSCANNON_RAGDOLL ); +#endif CapabilitiesClear(); CapabilitiesAdd( bits_CAP_MOVE_GROUND | bits_CAP_INNATE_RANGE_ATTACK1 | bits_CAP_SQUAD ); @@ -1306,7 +1323,7 @@ void CNPC_RollerMine::RunTask( const Task_t *pTask ) Vector vecRight; AngleVectors( QAngle( 0, yaw, 0 ), NULL, &vecRight, NULL ); - m_RollerController.m_vecAngular += WorldToLocalRotation( SetupMatrixAngles(GetLocalAngles()), vecRight, -m_flForwardSpeed * 5 ); + m_RollerController.m_vecAngular += WorldToLocalRotation( SetupMatrixAngles(GetLocalAngles()), vecRight, -GetForwardSpeed() * 5 ); TaskComplete(); return; @@ -1316,6 +1333,8 @@ void CNPC_RollerMine::RunTask( const Task_t *pTask ) } { + float flForwardSpeed = GetForwardSpeed(); + float yaw = UTIL_VecToYaw( GetNavigator()->GetCurWaypointPos() - GetLocalOrigin() ); Vector vecRight; @@ -1355,17 +1374,17 @@ void CNPC_RollerMine::RunTask( const Task_t *pTask ) vecCompensate.y = -vecVelocity.x; vecCompensate.z = 0; - m_RollerController.m_vecAngular = WorldToLocalRotation( SetupMatrixAngles(GetLocalAngles()), vecCompensate, m_flForwardSpeed * -0.75 ); + m_RollerController.m_vecAngular = WorldToLocalRotation( SetupMatrixAngles(GetLocalAngles()), vecCompensate, flForwardSpeed * -0.75 ); } if( m_bHackedByAlyx ) { // Move faster. - m_RollerController.m_vecAngular += WorldToLocalRotation( SetupMatrixAngles(GetLocalAngles()), vecRight, m_flForwardSpeed * 2.0f ); + m_RollerController.m_vecAngular += WorldToLocalRotation( SetupMatrixAngles(GetLocalAngles()), vecRight, flForwardSpeed * 2.0f ); } else { - m_RollerController.m_vecAngular += WorldToLocalRotation( SetupMatrixAngles(GetLocalAngles()), vecRight, m_flForwardSpeed ); + m_RollerController.m_vecAngular += WorldToLocalRotation( SetupMatrixAngles(GetLocalAngles()), vecRight, flForwardSpeed ); } } break; @@ -1489,8 +1508,10 @@ void CNPC_RollerMine::RunTask( const Task_t *pTask ) vecCompensate.z = 0; VectorNormalize( vecCompensate ); - m_RollerController.m_vecAngular = WorldToLocalRotation( SetupMatrixAngles(GetLocalAngles()), vecCompensate, m_flForwardSpeed * -0.75 ); - m_RollerController.m_vecAngular += WorldToLocalRotation( SetupMatrixAngles(GetLocalAngles()), vecRight, m_flForwardSpeed * flTorqueFactor ); + float flForwardSpeed = GetForwardSpeed(); + + m_RollerController.m_vecAngular = WorldToLocalRotation( SetupMatrixAngles(GetLocalAngles()), vecCompensate, flForwardSpeed * -0.75 ); + m_RollerController.m_vecAngular += WorldToLocalRotation( SetupMatrixAngles(GetLocalAngles()), vecRight, flForwardSpeed * flTorqueFactor ); // Taunt when I get closer if( !(m_iSoundEventFlags & ROLLERMINE_SE_TAUNT) && UTIL_DistApprox( GetLocalOrigin(), vecTargetPosition ) <= 400 ) @@ -1608,8 +1629,10 @@ void CNPC_RollerMine::RunTask( const Task_t *pTask ) vecCompensate.z = 0; VectorNormalize( vecCompensate ); - m_RollerController.m_vecAngular = WorldToLocalRotation( SetupMatrixAngles(GetLocalAngles()), vecCompensate, m_flForwardSpeed * -0.75 ); - m_RollerController.m_vecAngular += WorldToLocalRotation( SetupMatrixAngles(GetLocalAngles()), vecRight, m_flForwardSpeed * flTorqueFactor ); + float flForwardSpeed = GetForwardSpeed(); + + m_RollerController.m_vecAngular = WorldToLocalRotation( SetupMatrixAngles(GetLocalAngles()), vecCompensate, flForwardSpeed * -0.75 ); + m_RollerController.m_vecAngular += WorldToLocalRotation( SetupMatrixAngles(GetLocalAngles()), vecRight, flForwardSpeed * flTorqueFactor ); // Once we're near the player, slow & stop if ( GetAbsOrigin().DistToSqr( vecTargetPosition ) < (ROLLERMINE_RETURN_TO_PLAYER_DIST*2.0) ) @@ -1957,6 +1980,10 @@ void CNPC_RollerMine::NotifyInteraction( CAI_BaseNPC *pUser ) // Force the rollermine open here. At very least, this ensures that the // correct, smaller bounding box is recomputed around it. Open(); + +#ifdef MAPBASE + m_OnHacked.FireOutput(pUser, this); +#endif } //----------------------------------------------------------------------------- @@ -2561,6 +2588,12 @@ float CNPC_RollerMine::RollingSpeed() float rollingSpeed = angVel.Length() - 90; rollingSpeed = clamp( rollingSpeed, 1, MAX_ROLLING_SPEED ); rollingSpeed *= (1/MAX_ROLLING_SPEED); +#ifdef MAPBASE + if (m_flSpeedModifier != 1.0f) + { + rollingSpeed *= m_flSpeedModifier; + } +#endif return rollingSpeed; } return 0; diff --git a/src/game/server/hl2/npc_scanner.cpp b/src/game/server/hl2/npc_scanner.cpp index 86cfcad2..7df3e1ce 100644 --- a/src/game/server/hl2/npc_scanner.cpp +++ b/src/game/server/hl2/npc_scanner.cpp @@ -179,6 +179,11 @@ BEGIN_DATADESC( CNPC_CScanner ) DEFINE_INPUTFUNC( FIELD_STRING, "DeployMine", InputDeployMine ), DEFINE_INPUTFUNC( FIELD_STRING, "EquipMine", InputEquipMine ), +#ifdef MAPBASE + DEFINE_INPUTFUNC( FIELD_VOID, "DisablePhotos", InputDisablePhotos ), + DEFINE_INPUTFUNC( FIELD_VOID, "EnablePhotos", InputEnablePhotos ), +#endif + DEFINE_OUTPUT( m_OnPhotographPlayer, "OnPhotographPlayer" ), DEFINE_OUTPUT( m_OnPhotographNPC, "OnPhotographNPC" ), @@ -217,6 +222,10 @@ CNPC_CScanner::CNPC_CScanner() { m_bIsClawScanner = false; } + +#ifdef MAPBASE + CapabilitiesAdd( bits_CAP_INNATE_MELEE_ATTACK1 ); +#endif } //----------------------------------------------------------------------------- @@ -247,11 +256,11 @@ void CNPC_CScanner::Spawn(void) if( m_bIsClawScanner ) { - SetModel( "models/shield_scanner.mdl"); + SetModel( DefaultOrCustomModel( "models/shield_scanner.mdl" ) ); } else { - SetModel( "models/combine_scanner.mdl"); + SetModel( DefaultOrCustomModel( "models/combine_scanner.mdl" ) ); } m_iHealth = sk_scanner_health.GetFloat(); @@ -289,7 +298,9 @@ void CNPC_CScanner::Spawn(void) // -------------------------------------------- +#ifndef MAPBASE // Moved to constructor so keyvalue works CapabilitiesAdd( bits_CAP_INNATE_MELEE_ATTACK1 ); +#endif m_bPhotoTaken = false; @@ -319,6 +330,27 @@ void CNPC_CScanner::Activate() m_pEyeFlash->SetScale( 1.4 ); } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: Cache user entity field values until spawn is called. +// Input : szKeyName - Key to handle. +// szValue - Value for key. +// Output : Returns true if the key was handled, false if not. +//----------------------------------------------------------------------------- +bool CNPC_CScanner::KeyValue( const char *szKeyName, const char *szValue ) +{ + // Any "Counter" outputs are changed to "OutCounter" before spawning. + if (FStrEq(szKeyName, "DisablePhotos") && FStrEq(szValue, "1")) + { + CapabilitiesRemove(bits_CAP_INNATE_MELEE_ATTACK1); + } + else + return BaseClass::KeyValue( szKeyName, szValue ); + + return true; +} +#endif + //------------------------------------------------------------------------------ // Purpose: Override to split in two when attacked //------------------------------------------------------------------------------ @@ -533,7 +565,7 @@ void CNPC_CScanner::Precache(void) // Model if( m_bIsClawScanner ) { - PrecacheModel("models/shield_scanner.mdl"); + PrecacheModel( DefaultOrCustomModel( "models/shield_scanner.mdl" ) ); PrecacheModel("models/gibs/Shield_Scanner_Gib1.mdl"); PrecacheModel("models/gibs/Shield_Scanner_Gib2.mdl"); @@ -559,7 +591,7 @@ void CNPC_CScanner::Precache(void) } else { - PrecacheModel("models/combine_scanner.mdl"); + PrecacheModel( DefaultOrCustomModel( "models/combine_scanner.mdl" ) ); PrecacheModel("models/gibs/scanner_gib01.mdl" ); PrecacheModel("models/gibs/scanner_gib02.mdl" ); @@ -958,7 +990,12 @@ void CNPC_CScanner::DeployMine() //----------------------------------------------------------------------------- float CNPC_CScanner::GetMaxSpeed() { +#ifdef MAPBASE + // Don't stomp custom max speed in base class + if( IsStriderScout() && m_flCustomMaxSpeed <= 0.0f ) +#else if( IsStriderScout() ) +#endif { return SCANNER_SCOUT_MAX_SPEED; } @@ -1034,6 +1071,24 @@ void CNPC_CScanner::InputEquipMine(inputdata_t &inputdata) pEnt->Spawn(); } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CNPC_CScanner::InputDisablePhotos( inputdata_t &inputdata ) +{ + CapabilitiesRemove(bits_CAP_INNATE_MELEE_ATTACK1); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CNPC_CScanner::InputEnablePhotos( inputdata_t &inputdata ) +{ + CapabilitiesAdd(bits_CAP_INNATE_MELEE_ATTACK1); +} +#endif + //----------------------------------------------------------------------------- // Purpose: Tells the scanner to go photograph an entity. @@ -1450,6 +1505,10 @@ int CNPC_CScanner::SelectSchedule(void) return SCHED_CSCANNER_SPOTLIGHT_HOVER; // Melee attack if possible +#ifdef MAPBASE + if ( CapabilitiesGet() & bits_CAP_INNATE_MELEE_ATTACK1 ) + { +#endif if ( HasCondition( COND_CAN_MELEE_ATTACK1 ) ) { if ( random->RandomInt(0,1) ) @@ -1458,6 +1517,13 @@ int CNPC_CScanner::SelectSchedule(void) // TODO: a schedule where he makes an alarm sound? return SCHED_SCANNER_CHASE_ENEMY; } +#ifdef MAPBASE + } + else + { + return SCHED_CSCANNER_SPOTLIGHT_HOVER; + } +#endif // If I'm far from the enemy, stay up high and approach in spotlight mode float fAttack2DDist = ( GetEnemyLKP() - GetAbsOrigin() ).Length2D(); @@ -1866,6 +1932,15 @@ void CNPC_CScanner::UpdateOnRemove( void ) //------------------------------------------------------------------------------ void CNPC_CScanner::TakePhoto(void) { +#ifdef MAPBASE + // Only take photos if we're allowed + if (!(CapabilitiesGet() & bits_CAP_INNATE_MELEE_ATTACK1)) + { + m_bPhotoTaken = true; + return; + } +#endif + ScannerEmitSound( "TakePhoto" ); m_pEyeFlash->SetScale( 1.4 ); @@ -2492,6 +2567,14 @@ void CNPC_CScanner::MoveToTarget( float flInterval, const Vector &vecMoveTarget myZAccel = flDist / flInterval; } +#ifdef MAPBASE + if (m_flSpeedModifier != 1.0f) + { + myAccel *= m_flSpeedModifier; + //myZAccel *= m_flSpeedModifier; + } +#endif + MoveInDirection( flInterval, targetDir, myAccel, myZAccel, myDecay ); // calc relative banking targets diff --git a/src/game/server/hl2/npc_scanner.h b/src/game/server/hl2/npc_scanner.h index f34eba35..109b2a7d 100644 --- a/src/game/server/hl2/npc_scanner.h +++ b/src/game/server/hl2/npc_scanner.h @@ -51,6 +51,9 @@ public: virtual char *GetScannerSoundPrefix( void ); void Spawn(void); void Activate(); +#ifdef MAPBASE + bool KeyValue( const char *szKeyName, const char *szValue ); +#endif void StartTask( const Task_t *pTask ); void UpdateOnRemove( void ); void DeployMine(); @@ -67,6 +70,10 @@ public: void InputInspectTargetSpotlight( inputdata_t &inputdata ); void InputDeployMine( inputdata_t &inputdata ); void InputEquipMine( inputdata_t &inputdata ); +#ifdef MAPBASE + void InputDisablePhotos( inputdata_t &inputdata ); + void InputEnablePhotos( inputdata_t &inputdata ); +#endif void InputShouldInspect( inputdata_t &inputdata ); void InspectTarget( inputdata_t &inputdata, ScannerFlyMode_t eFlyMode ); diff --git a/src/game/server/hl2/npc_stalker.cpp b/src/game/server/hl2/npc_stalker.cpp index 204d7a55..67ed567a 100644 --- a/src/game/server/hl2/npc_stalker.cpp +++ b/src/game/server/hl2/npc_stalker.cpp @@ -126,7 +126,14 @@ BEGIN_DATADESC( CNPC_Stalker ) DEFINE_FIELD( m_flNextBreatheSoundTime, FIELD_TIME ), DEFINE_FIELD( m_flNextScrambleSoundTime, FIELD_TIME ), DEFINE_FIELD( m_nextSmokeTime, FIELD_TIME ), +#ifdef MAPBASE + // Funnily enough, we can very easily treat this as a boolean value in Hammer + // since "0" means don't attack and "1" means attack. There is no unique behavior beyond 1. + DEFINE_KEYFIELD( m_iPlayerAggression, FIELD_INTEGER, "Aggression" ), + DEFINE_KEYFIELD( m_bBleed, FIELD_BOOLEAN, "Bleed" ), +#else DEFINE_FIELD( m_iPlayerAggression, FIELD_INTEGER ), +#endif DEFINE_FIELD( m_flNextScreamTime, FIELD_TIME ), // Function Pointers @@ -271,7 +278,7 @@ void CNPC_Stalker::Spawn( void ) { Precache( ); - SetModel( "models/stalker.mdl" ); + SetModel( DefaultOrCustomModel( "models/stalker.mdl" ) ); SetHullType(HULL_HUMAN); SetHullSizeNormal(); @@ -300,7 +307,9 @@ void CNPC_Stalker::Spawn( void ) m_flDistTooFar = MAX_STALKER_FIRE_RANGE; +#ifndef MAPBASE m_iPlayerAggression = 0; +#endif GetSenses()->SetDistLook(MAX_STALKER_FIRE_RANGE - 1); } @@ -312,7 +321,7 @@ void CNPC_Stalker::Spawn( void ) //----------------------------------------------------------------------------- void CNPC_Stalker::Precache( void ) { - PrecacheModel("models/stalker.mdl"); + PrecacheModel( DefaultOrCustomModel( "models/stalker.mdl" ) ); PrecacheModel("sprites/laser.vmt"); PrecacheModel("sprites/redglow1.vmt"); @@ -329,6 +338,11 @@ void CNPC_Stalker::Precache( void ) PrecacheScriptSound( "NPC_Stalker.Pain" ); PrecacheScriptSound( "NPC_Stalker.Die" ); +#ifdef MAPBASE + if (m_bBleed) + PrecacheParticleSystem( "blood_impact_synth_01" ); +#endif + BaseClass::Precache(); } @@ -389,6 +403,33 @@ void CNPC_Stalker::Event_Killed( const CTakeDamageInfo &info ) BaseClass::Event_Killed( info ); } +#ifdef MAPBASE +extern void DispatchParticleEffect( const char *pszParticleName, Vector vecOrigin, QAngle vecAngles, CBaseEntity *pEntity = NULL ); + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CNPC_Stalker::TraceAttack( const CTakeDamageInfo &inputInfo, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator ) +{ + // Is it insane to desire for stalkers to bleed? No. + // Is it insane to port the hunter's blood particle system because + // it fits with the fact stalkers probably don't run on regular blood anymore? Maybe. + if (m_bBleed) + { + if ( ( inputInfo.GetDamageType() & DMG_BULLET ) || + ( inputInfo.GetDamageType() & DMG_BUCKSHOT ) || + ( inputInfo.GetDamageType() & DMG_CLUB ) || + ( inputInfo.GetDamageType() & DMG_NEVERGIB ) ) + { + QAngle vecAngles; + VectorAngles( ptr->plane.normal, vecAngles ); + DispatchParticleEffect( "blood_impact_synth_01", ptr->endpos, vecAngles ); + } + } + + BaseClass::TraceAttack( inputInfo, vecDir, ptr, pAccumulator ); +} +#endif + //----------------------------------------------------------------------------- // Purpose: // Input : @@ -1106,6 +1147,30 @@ void CNPC_Stalker::DrawAttackBeam(void) */ } +#ifdef MAPBASE +//------------------------------------------------------------------------------ +// Purpose: Fixes stalker beam cleanup not working correctly +//------------------------------------------------------------------------------ +void CNPC_Stalker::UpdateOnRemove( void ) +{ + if (m_pBeam) + { + StopSound(m_pBeam->entindex(), "NPC_Stalker.BurnWall" ); + StopSound(m_pBeam->entindex(), "NPC_Stalker.BurnFlesh" ); + + UTIL_Remove( m_pLightGlow ); + UTIL_Remove( m_pBeam); + m_pBeam = NULL; + m_bPlayingHitWall = false; + m_bPlayingHitFlesh = false; + + SetThink(NULL); + } + + BaseClass::UpdateOnRemove(); +} +#endif + //------------------------------------------------------------------------------ // Purpose : Draw attack beam and do damage / decals // Input : @@ -1168,7 +1233,11 @@ bool CNPC_Stalker::InnateWeaponLOSCondition( const Vector &ownerPos, const Vecto } else if (pBCC) { +#ifdef MAPBASE + if (IRelationType( pBCC ) <= D_FR) +#else if (IRelationType( pBCC ) == D_HT) +#endif { return true; } diff --git a/src/game/server/hl2/npc_stalker.h b/src/game/server/hl2/npc_stalker.h index dc2c08e5..206d199d 100644 --- a/src/game/server/hl2/npc_stalker.h +++ b/src/game/server/hl2/npc_stalker.h @@ -46,9 +46,19 @@ public: float m_bPlayingHitFlesh; CBeam* m_pBeam; CSprite* m_pLightGlow; +#ifdef MAPBASE + // This is a keyvalue now, so we have to initialize the value through somewhere that isn't Spawn() + int m_iPlayerAggression = 0; + bool m_bBleed; +#else int m_iPlayerAggression; +#endif float m_flNextScreamTime; +#ifdef MAPBASE + void UpdateOnRemove( void ); +#endif + void KillAttackBeam(void); void DrawAttackBeam(void); void CalcBeamPosition(void); @@ -97,6 +107,9 @@ public: void PainSound( const CTakeDamageInfo &info ); void Event_Killed( const CTakeDamageInfo &info ); +#ifdef MAPBASE + void TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator ); +#endif void DoSmokeEffect( const Vector &position ); void AddZigZagToPath(void); diff --git a/src/game/server/hl2/npc_strider.cpp b/src/game/server/hl2/npc_strider.cpp index 60d37cdf..a4289fdb 100644 --- a/src/game/server/hl2/npc_strider.cpp +++ b/src/game/server/hl2/npc_strider.cpp @@ -54,6 +54,10 @@ #include "filters.h" #include "saverestore_utlvector.h" #include "eventqueue.h" +#ifdef MAPBASE +#include "npc_basescanner.h" +#include "mapbase/GlobalStrings.h" +#endif // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" @@ -328,6 +332,11 @@ BEGIN_DATADESC( CNPC_Strider ) DEFINE_FIELD( m_hCannonTarget, FIELD_EHANDLE ), DEFINE_EMBEDDED( m_AttemptCannonLOSTimer ), +#ifdef MAPBASE + DEFINE_KEYFIELD( m_strStompFilter, FIELD_STRING, "stompfilter" ), + DEFINE_FIELD( m_hStompFilter, FIELD_EHANDLE ), +#endif + DEFINE_FIELD( m_flSpeedScale, FIELD_FLOAT ), DEFINE_FIELD( m_flTargetSpeedScale, FIELD_FLOAT ), @@ -386,12 +395,19 @@ BEGIN_DATADESC( CNPC_Strider ) DEFINE_INPUTFUNC( FIELD_VOID, "EnableMinigun", InputEnableMinigun ), DEFINE_INPUTFUNC( FIELD_FLOAT, "StopShootingMinigunForSeconds", InputStopShootingMinigunForSeconds ), DEFINE_INPUTFUNC( FIELD_VOID, "DisableCrouch", InputDisableCrouch ), +#ifdef MAPBASE + DEFINE_INPUTFUNC( FIELD_VOID, "EnableCrouch", InputEnableCrouch ), +#endif DEFINE_INPUTFUNC( FIELD_VOID, "DisableMoveToLOS", InputDisableMoveToLOS ), DEFINE_INPUTFUNC( FIELD_STRING, "DisableCollisionWith", InputDisableCollisionWith ), DEFINE_INPUTFUNC( FIELD_STRING, "EnableCollisionWith", InputEnableCollisionWith ), DEFINE_INPUTFUNC( FIELD_VOID, "Explode", InputExplode ), DEFINE_INPUTFUNC( FIELD_FLOAT, "ScaleGroundSpeed", InputScaleGroundSpeed ), +#ifdef MAPBASE + DEFINE_INPUTFUNC( FIELD_STRING, "SetStompFilter", InputSetStompFilter ), +#endif + // Function Pointers // DEFINE_FUNCTION( JumpTouch ), DEFINE_THINKFUNC( CarriedThink ), @@ -429,6 +445,11 @@ CNPC_Strider::CNPC_Strider() //--------------------------------------------------------- CNPC_Strider::~CNPC_Strider() { +#ifdef MAPBASE + if (m_hFocus) + UTIL_Remove( m_hFocus ); +#endif + delete m_pMinigun; } @@ -1105,7 +1126,11 @@ void CNPC_Strider::GatherConditions() // Don't switch targets if shooting at a bullseye! Level designers depend on bullseyes. if( GetEnemy() && m_pMinigun->IsShooting() && GetTimeEnemyAcquired() != gpGlobals->curtime ) { +#ifdef MAPBASE + if( m_pMinigun->IsOnTarget( 3 ) && !EntIsClass( GetEnemy(), gm_isz_class_Bullseye ) ) +#else if( m_pMinigun->IsOnTarget( 3 ) && !FClassnameIs( GetEnemy(), "npc_bullseye" ) ) +#endif { if( m_iVisibleEnemies > 1 ) { @@ -1993,7 +2018,11 @@ Disposition_t CNPC_Strider::IRelationType( CBaseEntity *pTarget ) //--------------------------------------------------------- void CNPC_Strider::AddEntityRelationship( CBaseEntity *pEntity, Disposition_t nDisposition, int nPriority ) { +#ifdef MAPBASE + if ( nDisposition == D_HT && EntIsClass(pEntity, gm_isz_class_Bullseye) ) +#else if ( nDisposition == D_HT && pEntity->ClassMatches("npc_bullseye") ) +#endif UpdateEnemyMemory( pEntity, pEntity->GetAbsOrigin() ); BaseClass::AddEntityRelationship( pEntity, nDisposition, nPriority ); } @@ -2336,6 +2365,15 @@ void CNPC_Strider::InputDisableCrouch( inputdata_t &inputdata ) m_bDontCrouch = true; } +#ifdef MAPBASE +//--------------------------------------------------------- +//--------------------------------------------------------- +void CNPC_Strider::InputEnableCrouch( inputdata_t &inputdata ) +{ + m_bDontCrouch = false; +} +#endif + //--------------------------------------------------------- //--------------------------------------------------------- void CNPC_Strider::InputDisableMoveToLOS( inputdata_t &inputdata ) @@ -2361,6 +2399,16 @@ void CNPC_Strider::InputScaleGroundSpeed( inputdata_t &inputdata ) m_flTargetSpeedScale = inputdata.value.Float(); } +#ifdef MAPBASE +//--------------------------------------------------------- +//--------------------------------------------------------- +void CNPC_Strider::InputSetStompFilter( inputdata_t &inputdata ) +{ + m_strStompFilter = inputdata.value.StringID(); + m_hStompFilter = NULL; +} +#endif + //--------------------------------------------------------- //--------------------------------------------------------- bool CNPC_Strider::FVisible( CBaseEntity *pEntity, int traceMask, CBaseEntity **ppBlocker ) @@ -2426,7 +2474,11 @@ bool CNPC_Strider::IsValidEnemy( CBaseEntity *pTarget ) //--------------------------------------------------------- bool CNPC_Strider::UpdateEnemyMemory( CBaseEntity *pEnemy, const Vector &position, CBaseEntity *pInformer ) { +#ifdef MAPBASE + if (dynamic_cast(pInformer)) +#else if( pInformer && FClassnameIs( pInformer, "npc_cscanner" ) ) +#endif { EmitSound( "NPC_Strider.Alert" ); // Move Strider's focus to this location and make strider mad at it @@ -2557,6 +2609,14 @@ int CNPC_Strider::MeleeAttack1Conditions( float flDot, float flDist ) return COND_NONE; } +#ifdef MAPBASE + if (GetStompFilter()) + { + if (!GetStompFilter()->PassesFilter(this, pEnemy)) + return COND_NONE; + } +#endif + // recompute this because the base class function does not work for the strider flDist = StriderEnemyDistance( pEnemy ); @@ -4378,6 +4438,35 @@ void CNPC_Strider::StompHit( int followerBoneIndex ) } } +#ifdef MAPBASE +//--------------------------------------------------------- +//--------------------------------------------------------- +CBaseFilter *CNPC_Strider::GetStompFilter() +{ + if (m_hStompFilter) + return m_hStompFilter; + + if (m_strStompFilter != NULL_STRING) + { + CBaseEntity *pEntity = gEntList.FindEntityByName(NULL, m_strStompFilter); + if (pEntity) + { + m_hStompFilter = dynamic_cast(pEntity); + if (m_hStompFilter) + return m_hStompFilter; + else + Warning("%s stomp filter %s not a filter!", GetDebugName(), STRING(m_strStompFilter)); + } + else + { + Warning("%s stomp filter %s not found!", GetDebugName(), STRING(m_strStompFilter)); + } + } + + return NULL; +} +#endif + //--------------------------------------------------------- //--------------------------------------------------------- void CNPC_Strider::FootFX( const Vector &origin ) diff --git a/src/game/server/hl2/npc_strider.h b/src/game/server/hl2/npc_strider.h index dd645781..d5bdd209 100644 --- a/src/game/server/hl2/npc_strider.h +++ b/src/game/server/hl2/npc_strider.h @@ -14,6 +14,9 @@ #include "smoke_trail.h" #include "physics_bone_follower.h" #include "physics_prop_ragdoll.h" +#ifdef MAPBASE +#include "filters.h" +#endif #if defined( _WIN32 ) #pragma once @@ -168,10 +171,17 @@ public: void InputDisableAggressiveBehavior( inputdata_t &inputdata ); void InputStopShootingMinigunForSeconds( inputdata_t &inputdata ); void InputDisableCrouch( inputdata_t &inputdata ); +#ifdef MAPBASE + void InputEnableCrouch( inputdata_t &inputdata ); +#endif void InputDisableMoveToLOS( inputdata_t &inputdata ); void InputExplode( inputdata_t &inputdata ); void InputScaleGroundSpeed( inputdata_t &inputdata ); +#ifdef MAPBASE + void InputSetStompFilter( inputdata_t &inputdata ); +#endif + //--------------------------------- // Combat //--------------------------------- @@ -356,6 +366,10 @@ public: Vector BackFootHit( float eventtime ); void StompHit( int followerBoneIndex ); +#ifdef MAPBASE + CBaseFilter *GetStompFilter(); +#endif + void FootFX( const Vector &origin ); Vector CalculateStompHitPosition( CBaseEntity *pEnemy ); bool IsLegBoneFollower( CBoneFollower *pFollower ); @@ -452,6 +466,11 @@ private: EHANDLE m_hCannonTarget; CSimpleSimTimer m_AttemptCannonLOSTimer; +#ifdef MAPBASE + string_t m_strStompFilter; + CHandle m_hStompFilter; +#endif + float m_flSpeedScale; float m_flTargetSpeedScale; diff --git a/src/game/server/hl2/npc_turret_ceiling.cpp b/src/game/server/hl2/npc_turret_ceiling.cpp index ec8b4207..0637525a 100644 --- a/src/game/server/hl2/npc_turret_ceiling.cpp +++ b/src/game/server/hl2/npc_turret_ceiling.cpp @@ -26,7 +26,11 @@ //Debug visualization ConVar g_debug_turret_ceiling( "g_debug_turret_ceiling", "0" ); +#ifdef MAPBASE +#define CEILING_TURRET_MODEL GetTurretModel() +#else #define CEILING_TURRET_MODEL "models/combine_turrets/ceiling_turret.mdl" +#endif #define CEILING_TURRET_GLOW_SPRITE "sprites/glow1.vmt" /* // we now inherit these from the ai_basenpc baseclass #define CEILING_TURRET_BC_YAW "aim_yaw" @@ -49,6 +53,9 @@ ConVar g_debug_turret_ceiling( "g_debug_turret_ceiling", "0" ); #define SF_CEILING_TURRET_STARTINACTIVE 0x00000040 #define SF_CEILING_TURRET_NEVERRETIRE 0x00000080 #define SF_CEILING_TURRET_OUT_OF_AMMO 0x00000100 +#ifdef MAPBASE +#define SF_CEILING_TURRET_NO_SPRITE 0x00000400 +#endif //Heights #define CEILING_TURRET_RETRACT_HEIGHT 24 @@ -110,6 +117,14 @@ public: void InputToggle( inputdata_t &inputdata ); void InputEnable( inputdata_t &inputdata ); void InputDisable( inputdata_t &inputdata ); +#ifdef MAPBASE + void InputDepleteAmmo( inputdata_t &inputdata ); + void InputRestoreAmmo( inputdata_t &inputdata ); + void InputCreateSprite( inputdata_t &inputdata ); + void InputDestroySprite( inputdata_t &inputdata ); + + virtual void StopLoopingSounds( void ) { StopSound(GetMoveSound()); } +#endif void SetLastSightTime(); @@ -147,8 +162,31 @@ public: } protected: + +#ifdef MAPBASE + virtual const char *GetTurretModel() { return "models/combine_turrets/ceiling_turret.mdl"; } + + virtual const char *GetRetireSound() { return "NPC_CeilingTurret.Retire"; } + virtual const char *GetDeploySound() { return "NPC_CeilingTurret.Deploy"; } + virtual const char *GetMoveSound() { return "NPC_CeilingTurret.Move"; } + virtual const char *GetActiveSound() { return "NPC_CeilingTurret.Active"; } + virtual const char *GetAlertSound() { return "NPC_CeilingTurret.Alert"; } + virtual const char *GetShootSound() { return "NPC_CeilingTurret.ShotSounds"; } + virtual const char *GetPingSound() { return "NPC_CeilingTurret.Ping"; } + virtual const char *GetDieSound() { return "NPC_CeilingTurret.Die"; } + + virtual float GetFireRate(bool bFightingPlayer = false) { return bFightingPlayer ? 0.5f : 0.1f; } + + virtual void SetIdleGoalAngles() { m_vecGoalAngles = GetAbsAngles(); } +#endif +#ifdef MAPBASE + virtual bool PreThink( turretState_e state ); + void DryFire( void ); + const char *GetTracerType( void ) { return "AR2Tracer"; } +#else bool PreThink( turretState_e state ); +#endif void Shoot( const Vector &vecSrc, const Vector &vecDirToEnemy ); void SetEyeState( eyeState_t state ); void Ping( void ); @@ -157,8 +195,14 @@ protected: void Disable( void ); void SpinUp( void ); void SpinDown( void ); +#ifdef MAPBASE + virtual +#endif void SetHeight( float height ); +#ifdef MAPBASE + virtual +#endif bool UpdateFacing( void ); int m_iAmmoType; @@ -179,7 +223,9 @@ protected: COutputEvent m_OnDeploy; COutputEvent m_OnRetire; +#ifndef MAPBASE COutputEvent m_OnTipped; +#endif DECLARE_DATADESC(); }; @@ -198,6 +244,9 @@ BEGIN_DATADESC( CNPC_CeilingTurret ) DEFINE_FIELD( m_flPingTime, FIELD_TIME ), DEFINE_FIELD( m_vecGoalAngles, FIELD_VECTOR ), DEFINE_FIELD( m_pEyeGlow, FIELD_CLASSPTR ), +#ifdef MAPBASE + DEFINE_INPUT( m_flFieldOfView, FIELD_FLOAT, "FieldOfView" ), +#endif DEFINE_THINKFUNC( Retire ), DEFINE_THINKFUNC( Deploy ), @@ -210,10 +259,18 @@ BEGIN_DATADESC( CNPC_CeilingTurret ) DEFINE_INPUTFUNC( FIELD_VOID, "Toggle", InputToggle ), DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ), DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ), +#ifdef MAPBASE + DEFINE_INPUTFUNC( FIELD_VOID, "DepleteAmmo", InputDepleteAmmo ), + DEFINE_INPUTFUNC( FIELD_VOID, "RestoreAmmo", InputRestoreAmmo ), + DEFINE_INPUTFUNC( FIELD_VOID, "CreateSprite", InputCreateSprite ), + DEFINE_INPUTFUNC( FIELD_VOID, "DestroySprite", InputDestroySprite ), +#endif DEFINE_OUTPUT( m_OnDeploy, "OnDeploy" ), DEFINE_OUTPUT( m_OnRetire, "OnRetire" ), +#ifndef MAPBASE DEFINE_OUTPUT( m_OnTipped, "OnTipped" ), +#endif END_DATADESC() @@ -247,7 +304,7 @@ CNPC_CeilingTurret::~CNPC_CeilingTurret( void ) //----------------------------------------------------------------------------- void CNPC_CeilingTurret::Precache( void ) { - PrecacheModel( CEILING_TURRET_MODEL ); + PrecacheModel( DefaultOrCustomModel( CEILING_TURRET_MODEL ) ); PrecacheModel( CEILING_TURRET_GLOW_SPRITE ); // Activities @@ -258,6 +315,16 @@ void CNPC_CeilingTurret::Precache( void ) ADD_CUSTOM_ACTIVITY( CNPC_CeilingTurret, ACT_CEILING_TURRET_FIRE ); ADD_CUSTOM_ACTIVITY( CNPC_CeilingTurret, ACT_CEILING_TURRET_DRYFIRE ); +#ifdef MAPBASE + PrecacheScriptSound( GetRetireSound() ); + PrecacheScriptSound( GetDeploySound() ); + PrecacheScriptSound( GetMoveSound() ); + PrecacheScriptSound( GetActiveSound() ); + PrecacheScriptSound( GetAlertSound() ); + PrecacheScriptSound( GetShootSound() ); + PrecacheScriptSound( GetPingSound() ); + PrecacheScriptSound( GetDieSound() ); +#else PrecacheScriptSound( "NPC_CeilingTurret.Retire" ); PrecacheScriptSound( "NPC_CeilingTurret.Deploy" ); PrecacheScriptSound( "NPC_CeilingTurret.Move" ); @@ -266,6 +333,7 @@ void CNPC_CeilingTurret::Precache( void ) PrecacheScriptSound( "NPC_CeilingTurret.ShotSounds" ); PrecacheScriptSound( "NPC_CeilingTurret.Ping" ); PrecacheScriptSound( "NPC_CeilingTurret.Die" ); +#endif PrecacheScriptSound( "NPC_FloorTurret.DryFire" ); @@ -279,15 +347,22 @@ void CNPC_CeilingTurret::Spawn( void ) { Precache(); - SetModel( CEILING_TURRET_MODEL ); + SetModel( DefaultOrCustomModel( CEILING_TURRET_MODEL ) ); BaseClass::Spawn(); m_HackedGunPos = Vector( 0, 0, 12.75 ); SetViewOffset( EyeOffset( ACT_IDLE ) ); +#ifndef MAPBASE // We use this as a keyvalue now. m_flFieldOfView = 0.0f; +#endif m_takedamage = DAMAGE_YES; +#ifdef MAPBASE + if (m_iHealth == 0) + m_iHealth = 1000; +#else m_iHealth = 1000; +#endif m_bloodColor = BLOOD_COLOR_MECH; SetSolid( SOLID_BBOX ); @@ -304,9 +379,16 @@ void CNPC_CeilingTurret::Spawn( void ) m_iAmmoType = GetAmmoDef()->Index( "AR2" ); //Create our eye sprite +#ifdef MAPBASE + if (!HasSpawnFlags(SF_CEILING_TURRET_NO_SPRITE)) + { +#endif m_pEyeGlow = CSprite::SpriteCreate( CEILING_TURRET_GLOW_SPRITE, GetLocalOrigin(), false ); m_pEyeGlow->SetTransparency( kRenderTransAdd, 255, 0, 0, 128, kRenderFxNoDissipation ); m_pEyeGlow->SetAttachment( this, 2 ); +#ifdef MAPBASE + } +#endif //Set our autostart state m_bAutoStart = !!( m_spawnflags & SF_CEILING_TURRET_AUTOACTIVATE ); @@ -361,7 +443,11 @@ int CNPC_CeilingTurret::OnTakeDamage( const CTakeDamageInfo &inputInfo ) ExplosionCreate( GetAbsOrigin(), GetLocalAngles(), this, 100, 100, false ); SetThink( &CNPC_CeilingTurret::DeathThink ); +#ifdef MAPBASE + StopSound( GetAlertSound() ); +#else StopSound( "NPC_CeilingTurret.Alert" ); +#endif m_OnDamaged.FireOutput( info.GetInflictor(), this ); @@ -397,7 +483,11 @@ void CNPC_CeilingTurret::Retire( void ) if ( UpdateFacing() == false ) { SetActivity( (Activity) ACT_CEILING_TURRET_CLOSE ); +#ifdef MAPBASE + EmitSound( GetRetireSound() ); +#else EmitSound( "NPC_CeilingTurret.Retire" ); +#endif //Notify of the retraction m_OnRetire.FireOutput( NULL, this ); @@ -424,6 +514,10 @@ void CNPC_CeilingTurret::Retire( void ) SetEyeState( TURRET_EYE_DISABLED ); SetThink( &CNPC_CeilingTurret::SUB_DoNothing ); } + +#ifdef MAPBASE + StopLoopingSounds(); +#endif } } @@ -447,10 +541,18 @@ void CNPC_CeilingTurret::Deploy( void ) { m_bActive = true; SetActivity( (Activity) ACT_CEILING_TURRET_OPEN ); +#ifdef MAPBASE + EmitSound( GetDeploySound() ); +#else EmitSound( "NPC_CeilingTurret.Deploy" ); +#endif //Notify we're deploying +#ifdef MAPBASE + m_OnDeploy.FireOutput( GetEnemy(), this ); +#else m_OnDeploy.FireOutput( NULL, this ); +#endif } //If we're done, then start searching @@ -465,7 +567,11 @@ void CNPC_CeilingTurret::Deploy( void ) m_flPlaybackRate = 0; SetThink( &CNPC_CeilingTurret::SearchThink ); +#ifdef MAPBASE + EmitSound( GetMoveSound() ); +#else EmitSound( "NPC_CeilingTurret.Move" ); +#endif } SetLastSightTime(); @@ -569,7 +675,11 @@ bool CNPC_CeilingTurret::FVisible( CBaseEntity *pEntity, int traceMask, CBaseEnt // If we hit something that's okay to hit anyway, still fire if ( pHitEntity && pHitEntity->MyCombatCharacterPointer() ) { +#ifdef MAPBASE + if (IRelationType(pHitEntity) <= D_FR) +#else if (IRelationType(pHitEntity) == D_HT) +#endif return true; } @@ -671,6 +781,20 @@ void CNPC_CeilingTurret::ActiveThink( void ) //Fire the gun if ( DotProduct( vecDirToEnemy, vecMuzzleDir ) >= 0.9848 ) // 10 degree slop { +#ifdef MAPBASE + if ( m_spawnflags & SF_CEILING_TURRET_OUT_OF_AMMO ) + { + ResetActivity(); + SetActivity( (Activity) ACT_CEILING_TURRET_DRYFIRE ); + DryFire(); + } + else + { + ResetActivity(); + SetActivity( (Activity) ACT_CEILING_TURRET_FIRE ); + Shoot( vecMuzzle, vecMuzzleDir ); + } +#else if ( m_spawnflags & SF_CEILING_TURRET_OUT_OF_AMMO ) { SetActivity( (Activity) ACT_CEILING_TURRET_DRYFIRE ); @@ -682,6 +806,7 @@ void CNPC_CeilingTurret::ActiveThink( void ) //Fire the weapon Shoot( vecMuzzle, vecMuzzleDir ); +#endif } } else @@ -733,6 +858,9 @@ void CNPC_CeilingTurret::SearchThink( void ) //If we've found a target, spin up the barrel and start to attack if ( GetEnemy() != NULL ) { +#ifdef MAPBASE + m_flShotTime = gpGlobals->curtime + GetFireRate( GetEnemy()->IsPlayer() ); +#else //Give players a grace period if ( GetEnemy()->IsPlayer() ) { @@ -742,13 +870,18 @@ void CNPC_CeilingTurret::SearchThink( void ) { m_flShotTime = gpGlobals->curtime + 0.1f; } +#endif m_flLastSight = 0; SetThink( &CNPC_CeilingTurret::ActiveThink ); SetEyeState( TURRET_EYE_SEE_TARGET ); SpinUp(); +#ifdef MAPBASE + EmitSound( GetActiveSound() ); +#else EmitSound( "NPC_CeilingTurret.Active" ); +#endif return; } @@ -799,10 +932,76 @@ void CNPC_CeilingTurret::AutoSearchThink( void ) if ( GetEnemy() != NULL ) { SetThink( &CNPC_CeilingTurret::Deploy ); +#ifdef MAPBASE + EmitSound( GetAlertSound() ); +#else EmitSound( "NPC_CeilingTurret.Alert" ); +#endif } } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// I decided to move dry firing to its own function instead of keeping it in Shoot, similar to npc_turret_floor +//----------------------------------------------------------------------------- +void CNPC_CeilingTurret::DryFire( void ) +{ + EmitSound( "NPC_FloorTurret.DryFire"); + EmitSound( GetActiveSound() ); + + if ( RandomFloat( 0, 1 ) > 0.7 ) + { + m_flShotTime = gpGlobals->curtime + random->RandomFloat( 0.5, 1.5 ); + } + else + { + m_flShotTime = gpGlobals->curtime; + } +} + +//----------------------------------------------------------------------------- +// Purpose: Fire! +//----------------------------------------------------------------------------- +void CNPC_CeilingTurret::Shoot( const Vector &vecSrc, const Vector &vecDirToEnemy ) +{ + FireBulletsInfo_t info; + + if ( GetEnemy() != NULL ) + { + Vector vecDir = GetActualShootTrajectory( vecSrc ); + + info.m_vecSrc = vecSrc; + info.m_vecDirShooting = vecDir; + info.m_iTracerFreq = 1; + info.m_iShots = 1; + info.m_pAttacker = this; + info.m_vecSpread = VECTOR_CONE_PRECALCULATED; + info.m_flDistance = MAX_COORD_RANGE; + info.m_iAmmoType = m_iAmmoType; + } + else + { + // Just shoot where you're facing! + + info.m_vecSrc = vecSrc; + info.m_vecDirShooting = vecDirToEnemy; + info.m_iTracerFreq = 1; + info.m_iShots = 1; + info.m_pAttacker = this; + info.m_vecSpread = GetAttackSpread( NULL, NULL ); + info.m_flDistance = MAX_COORD_RANGE; + info.m_iAmmoType = m_iAmmoType; + } + + FireBullets( info ); +#ifdef MAPBASE + EmitSound( GetShootSound() ); +#else + EmitSound( "NPC_CeilingTurret.ShotSounds" ); +#endif + DoMuzzleFlash(); +} +#else //----------------------------------------------------------------------------- // Purpose: Fire! //----------------------------------------------------------------------------- @@ -859,6 +1058,7 @@ void CNPC_CeilingTurret::Shoot( const Vector &vecSrc, const Vector &vecDirToEnem EmitSound( "NPC_CeilingTurret.ShotSounds" ); DoMuzzleFlash(); } +#endif //----------------------------------------------------------------------------- // Purpose: Allows a generic think function before the others are called @@ -946,7 +1146,11 @@ void CNPC_CeilingTurret::Ping( void ) return; //Ping! +#ifdef MAPBASE + EmitSound( GetPingSound() ); +#else EmitSound( "NPC_CeilingTurret.Ping" ); +#endif SetEyeState( TURRET_EYE_SEEKING_TARGET ); @@ -1023,6 +1227,52 @@ void CNPC_CeilingTurret::InputDisable( inputdata_t &inputdata ) Disable(); } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: Stops the turret from firing live rounds (still attempts to though) +//----------------------------------------------------------------------------- +void CNPC_CeilingTurret::InputDepleteAmmo( inputdata_t &inputdata ) +{ + AddSpawnFlags( SF_CEILING_TURRET_OUT_OF_AMMO ); +} + +//----------------------------------------------------------------------------- +// Purpose: Allows the turret to fire live rounds again +//----------------------------------------------------------------------------- +void CNPC_CeilingTurret::InputRestoreAmmo( inputdata_t &inputdata ) +{ + RemoveSpawnFlags( SF_CEILING_TURRET_OUT_OF_AMMO ); +} + +//----------------------------------------------------------------------------- +// Purpose: Creates the sprite if it has been destroyed +//----------------------------------------------------------------------------- +void CNPC_CeilingTurret::InputCreateSprite( inputdata_t &inputdata ) +{ + if (m_pEyeGlow) + return; + + m_pEyeGlow = CSprite::SpriteCreate( CEILING_TURRET_GLOW_SPRITE, GetLocalOrigin(), false ); + m_pEyeGlow->SetTransparency( kRenderTransAdd, 255, 0, 0, 128, kRenderFxNoDissipation ); + m_pEyeGlow->SetAttachment( this, 2 ); + + RemoveSpawnFlags(SF_CEILING_TURRET_NO_SPRITE); +} + +//----------------------------------------------------------------------------- +// Purpose: Destroys the sprite +//----------------------------------------------------------------------------- +void CNPC_CeilingTurret::InputDestroySprite( inputdata_t &inputdata ) +{ + if (!m_pEyeGlow) + return; + + UTIL_Remove(m_pEyeGlow); + m_pEyeGlow = NULL; + AddSpawnFlags(SF_CEILING_TURRET_NO_SPRITE); +} +#endif + //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- @@ -1048,14 +1298,26 @@ void CNPC_CeilingTurret::DeathThink( void ) return; //Level out our angles +#ifdef MAPBASE + SetIdleGoalAngles(); +#else m_vecGoalAngles = GetAbsAngles(); +#endif SetNextThink( gpGlobals->curtime ); if ( m_lifeState != LIFE_DEAD ) { m_lifeState = LIFE_DEAD; +#ifdef MAPBASE + StopLoopingSounds(); +#endif + +#ifdef MAPBASE + EmitSound( GetDieSound() ); +#else EmitSound( "NPC_CeilingTurret.Die" ); +#endif SetActivity( (Activity) ACT_CEILING_TURRET_CLOSE ); } @@ -1124,4 +1386,367 @@ bool CNPC_CeilingTurret::CanBeAnEnemyOf( CBaseEntity *pEnemy ) } return BaseClass::CanBeAnEnemyOf( pEnemy ); -} \ No newline at end of file +} + + + +#ifdef MAPBASE +// +// Eli's Lab Turret +// + +//#define LAB_TURRET_MANUAL_YAW 1 + +int ACT_CEILING_TURRET_MIRROR_CLOSED_IDLE; +int ACT_CEILING_TURRET_MIRROR_OPEN; +int ACT_CEILING_TURRET_MIRROR_CLOSE; + +class CNPC_LabTurret : public CNPC_CeilingTurret +{ + DECLARE_CLASS( CNPC_LabTurret, CNPC_CeilingTurret ); +public: + CNPC_LabTurret(); + + Class_T Classify( void ) + { + if( m_bEnabled ) + return CLASS_PLAYER_ALLY; + + return CLASS_NONE; + } + + const char *GetTracerType( void ) { return "Tracer"; } + bool PreThink( turretState_e state ); + + void SetIdleGoalAngles(); + + void Precache( void ); + void Spawn(); + Activity NPC_TranslateActivity( Activity activity ); + +#ifdef LAB_TURRET_MANUAL_YAW + void SetArmYaw( float flYaw ); + void ArmYawThink(); + + void InputSetArmYaw( inputdata_t &inputdata ); +#else + inline void SetArmYaw( float flYaw ) { SetPoseParameter( m_poseMove_Yaw, flYaw ); } +#endif + + void StopLoopingSounds( void ) { StopSound(GetMoveSound()); } + +protected: + + const char *GetTurretModel() { return "models/props_lab/labturret_npc.mdl"; } + + const char *GetRetireSound() { return "NPC_LabTurret.Retire"; } + const char *GetDeploySound() { return "NPC_LabTurret.Deploy"; } + const char *GetMoveSound() { return "NPC_LabTurret.Move"; } + const char *GetActiveSound() { return "NPC_LabTurret.Active"; } + const char *GetAlertSound() { return "NPC_LabTurret.Alert"; } + const char *GetShootSound() { return "NPC_LabTurret.ShotSounds"; } + const char *GetPingSound() { return "NPC_LabTurret.Ping"; } + const char *GetDieSound() { return "NPC_LabTurret.Die"; } + + float GetFireRate(bool bFightingPlayer = false) { return 1.0f; } + + void SetHeight( float height ); + + bool UpdateFacing( void ); + + bool m_bManualArmYaw; + +#ifdef LAB_TURRET_MANUAL_YAW + // The arm must reset each retire and be restored each deploy + float m_flStoredArmYaw; + float m_flGoalArmYaw; +#endif + + bool m_bMirrored; + + DECLARE_DATADESC(); +}; + +BEGIN_DATADESC( CNPC_LabTurret ) + + DEFINE_KEYFIELD( m_bManualArmYaw, FIELD_BOOLEAN, "ManualArmYaw" ), +#ifdef LAB_TURRET_MANUAL_YAW + DEFINE_KEYFIELD( m_flStoredArmYaw, FIELD_FLOAT, "ArmYaw" ), + DEFINE_FIELD( m_flGoalArmYaw, FIELD_FLOAT ), +#endif + DEFINE_KEYFIELD( m_bMirrored, FIELD_BOOLEAN, "Mirrored" ), + +#ifdef LAB_TURRET_MANUAL_YAW + DEFINE_THINKFUNC( ArmYawThink ), +#endif + + // Inputs +#ifdef LAB_TURRET_MANUAL_YAW + DEFINE_INPUTFUNC( FIELD_FLOAT, "SetArmYaw", InputSetArmYaw ), +#endif + +END_DATADESC() + +LINK_ENTITY_TO_CLASS( npc_turret_lab, CNPC_LabTurret ); + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CNPC_LabTurret::CNPC_LabTurret() +{ + // Lab turrets can rotate much more, so they can see from more angles than regular turrets. + m_flFieldOfView = -0.5; +} + +//----------------------------------------------------------------------------- +// Purpose: Precache +//----------------------------------------------------------------------------- +void CNPC_LabTurret::Precache( void ) +{ + BaseClass::Precache(); + + // Activities + ADD_CUSTOM_ACTIVITY( CNPC_LabTurret, ACT_CEILING_TURRET_MIRROR_CLOSED_IDLE ); + ADD_CUSTOM_ACTIVITY( CNPC_LabTurret, ACT_CEILING_TURRET_MIRROR_OPEN ); + ADD_CUSTOM_ACTIVITY( CNPC_LabTurret, ACT_CEILING_TURRET_MIRROR_CLOSE ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CNPC_LabTurret::Spawn( void ) +{ + BaseClass::Spawn(); + + m_iAmmoType = GetAmmoDef()->Index( "SMG1" ); + + if (m_bMirrored) + SetActivity((Activity)ACT_CEILING_TURRET_MIRROR_CLOSED_IDLE); + + SetPoseParameter( m_poseMove_Yaw, 0 ); +} + +//----------------------------------------------------------------------------- +// Purpose: Override base class activiites +//----------------------------------------------------------------------------- +Activity CNPC_LabTurret::NPC_TranslateActivity( Activity activity ) +{ + if (m_bMirrored) + { + if (activity == ACT_CEILING_TURRET_CLOSED_IDLE) + return (Activity)ACT_CEILING_TURRET_MIRROR_CLOSED_IDLE; + if (activity == ACT_CEILING_TURRET_OPEN) + return (Activity)ACT_CEILING_TURRET_MIRROR_OPEN; + if (activity == ACT_CEILING_TURRET_CLOSE) + return (Activity)ACT_CEILING_TURRET_MIRROR_CLOSE; + } + + return BaseClass::NPC_TranslateActivity(activity); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CNPC_LabTurret::PreThink( turretState_e state ) +{ + if (state == TURRET_RETIRING || state == TURRET_DEAD) + { +#ifdef LAB_TURRET_MANUAL_YAW + if (m_bManualArmYaw) + m_flStoredArmYaw = GetPoseParameter(m_poseMove_Yaw); +#endif + + SetArmYaw( 0 ); + } +#ifdef LAB_TURRET_MANUAL_YAW + else if (state == TURRET_DEPLOYING) + { + if (m_bManualArmYaw) + SetArmYaw( m_flStoredArmYaw ); + } +#endif + + return BaseClass::PreThink(state); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CNPC_LabTurret::SetIdleGoalAngles( void ) +{ + m_vecGoalAngles = GetAbsAngles(); + + if (m_bMirrored) + { + //m_vecGoalAngles.x = AngleNormalize( m_vecGoalAngles.x + 90 ); + m_vecGoalAngles.y = AngleNormalize( m_vecGoalAngles.y + 270 ); + } + else + { + //m_vecGoalAngles.x = AngleNormalize( m_vecGoalAngles.x + 270 ); + m_vecGoalAngles.y = AngleNormalize( m_vecGoalAngles.y + 90 ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : height - +//----------------------------------------------------------------------------- +void CNPC_LabTurret::SetHeight( float height ) +{ + Vector forward, right, up; + AngleVectors( GetLocalAngles(), &forward, &right, &up ); + + height /= 2; + + Vector mins = ( forward * -8.0f ) + ( right * -height ) + ( up * -8.0f ); + Vector maxs = ( forward * height ) + ( right * height ) + ( up * CEILING_TURRET_RETRACT_HEIGHT ); + + if ( mins.x > maxs.x ) + { + V_swap( mins.x, maxs.x ); + } + + if ( mins.y > maxs.y ) + { + V_swap( mins.y, maxs.y ); + } + + if ( mins.z > maxs.z ) + { + V_swap( mins.z, maxs.z ); + } + + SetCollisionBounds( mins, maxs ); +} + +//----------------------------------------------------------------------------- +// Purpose: Causes the turret to face its desired angles +//----------------------------------------------------------------------------- +bool CNPC_LabTurret::UpdateFacing( void ) +{ + bool bMoved = false; + + matrix3x4_t localToWorld; + + GetAttachment( LookupAttachment( "eyes" ), localToWorld ); + + Vector vecGoalDir; + AngleVectors( m_vecGoalAngles, &vecGoalDir ); + + Vector vecGoalLocalDir; + VectorIRotate( vecGoalDir, localToWorld, vecGoalLocalDir ); + + if ( g_debug_turret_ceiling.GetBool() ) + { + Vector vecMuzzle, vecMuzzleDir; + QAngle vecMuzzleAng; + + GetAttachment( "eyes", vecMuzzle, vecMuzzleAng ); + AngleVectors( vecMuzzleAng, &vecMuzzleDir ); + + NDebugOverlay::Cross3D( vecMuzzle, -Vector(2,2,2), Vector(2,2,2), 255, 255, 0, false, 0.05 ); + NDebugOverlay::Cross3D( vecMuzzle+(vecMuzzleDir*256), -Vector(2,2,2), Vector(2,2,2), 255, 255, 0, false, 0.05 ); + NDebugOverlay::Line( vecMuzzle, vecMuzzle+(vecMuzzleDir*256), 255, 255, 0, false, 0.05 ); + + NDebugOverlay::Cross3D( vecMuzzle, -Vector(2,2,2), Vector(2,2,2), 255, 0, 0, false, 0.05 ); + NDebugOverlay::Cross3D( vecMuzzle+(vecGoalDir*256), -Vector(2,2,2), Vector(2,2,2), 255, 0, 0, false, 0.05 ); + NDebugOverlay::Line( vecMuzzle, vecMuzzle+(vecGoalDir*256), 255, 0, 0, false, 0.05 ); + } + + QAngle vecGoalLocalAngles; + VectorAngles( vecGoalLocalDir, vecGoalLocalAngles ); + +#ifdef LAB_TURRET_MANUAL_YAW + if ( !m_bManualArmYaw && m_flGoalArmYaw == 0.0f ) +#else + if ( !m_bManualArmYaw ) +#endif + { + // Update yaw + float flDiff = AngleNormalize( UTIL_ApproachAngle( vecGoalLocalAngles.y, 0.0, 0.1f * MaxYawSpeed() ) ); + + SetPoseParameter( m_poseMove_Yaw, GetPoseParameter( m_poseMove_Yaw ) + ( flDiff / 1.5f ) * 0.5f ); + + // Recalculate muzzle + GetAttachment( LookupAttachment( "eyes" ), localToWorld ); + VectorIRotate( vecGoalDir, localToWorld, vecGoalLocalDir ); + VectorAngles( vecGoalLocalDir, vecGoalLocalAngles ); + + if ( fabs( flDiff ) > 0.1f ) + { + bMoved = true; + } + } + + // Update pitch + // Pitch is faster than the others, but it also kind of jiggles when targetting. + float flDiff = AngleNormalize( UTIL_ApproachAngle( vecGoalLocalAngles.x, 0.0, (GetActivity() != ACT_CEILING_TURRET_CLOSE ? 0.15f : 0.1f) * MaxYawSpeed() ) ); + + SetPoseParameter( m_poseAim_Pitch, GetPoseParameter( m_poseAim_Pitch ) + ( flDiff * 2.0f ) ); + + if ( fabs( flDiff ) > 0.1f ) + { + bMoved = true; + } + + // Update yaw + flDiff = AngleNormalize( UTIL_ApproachAngle( vecGoalLocalAngles.y, 0.0, 0.1f * MaxYawSpeed() ) ); + + SetPoseParameter( m_poseAim_Yaw, GetPoseParameter( m_poseAim_Yaw ) + ( flDiff / 1.5f ) ); + + if ( fabs( flDiff ) > 0.1f ) + { + bMoved = true; + } + + InvalidateBoneCache(); + + return bMoved; +} + +#ifdef LAB_TURRET_MANUAL_YAW +//----------------------------------------------------------------------------- +// Purpose: Change move yaw +//----------------------------------------------------------------------------- +void CNPC_LabTurret::SetArmYaw( float flYaw ) +{ + m_flGoalArmYaw = flYaw; + + SetContextThink(&CNPC_LabTurret::ArmYawThink, gpGlobals->curtime, "SetArmYaw"); +} + +//----------------------------------------------------------------------------- +// Purpose: Change move yaw +//----------------------------------------------------------------------------- +void CNPC_LabTurret::ArmYawThink() +{ + //GetBoneTransform( LookupBone( "eyes" ), localToWorld ); + + // Update yaw + float flDiff = AngleNormalize( UTIL_ApproachAngle( m_flGoalArmYaw, GetPoseParameter( m_poseMove_Yaw ), 0.05f * MaxYawSpeed() ) ); + + SetPoseParameter( m_poseMove_Yaw, GetPoseParameter( m_poseAim_Pitch ) + ( flDiff / 1.5f ) ); + + InvalidateBoneCache(); + + if ( fabs( flDiff ) > 0.1f ) + { + SetNextThink(gpGlobals->curtime, "SetArmYaw"); + } + else + { + m_flGoalArmYaw = 0.0f; + SetContextThink(NULL, gpGlobals->curtime, "SetArmYaw"); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Change move yaw +//----------------------------------------------------------------------------- +void CNPC_LabTurret::InputSetArmYaw( inputdata_t &inputdata ) +{ + SetArmYaw( inputdata.value.Float() ); +} +#endif +#endif diff --git a/src/game/server/hl2/npc_turret_floor.cpp b/src/game/server/hl2/npc_turret_floor.cpp index d934e72d..f365dfd7 100644 --- a/src/game/server/hl2/npc_turret_floor.cpp +++ b/src/game/server/hl2/npc_turret_floor.cpp @@ -40,6 +40,15 @@ ConVar g_debug_turret( "g_debug_turret", "0" ); extern ConVar physcannon_tracelength; +#if defined(MAPBASE) && defined(HL2_EPISODIC) +extern ConVar npc_alyx_interact_turrets; +#endif + +#ifdef MAPBASE +// m_iKeySkin has been replaced with the original m_nSkin so we can make it show up in Hammer, etc. +#define m_iKeySkin m_nSkin +#endif + // Interactions int g_interactionTurretStillStanding = 0; @@ -132,6 +141,10 @@ BEGIN_DATADESC( CNPC_FloorTurret ) DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ), DEFINE_INPUTFUNC( FIELD_VOID, "DepleteAmmo", InputDepleteAmmo ), DEFINE_INPUTFUNC( FIELD_VOID, "RestoreAmmo", InputRestoreAmmo ), +#ifdef MAPBASE + DEFINE_INPUTFUNC( FIELD_VOID, "CreateSprite", InputCreateSprite ), + DEFINE_INPUTFUNC( FIELD_VOID, "DestroySprite", InputDestroySprite ), +#endif DEFINE_INPUTFUNC( FIELD_VOID, "SelfDestruct", InputSelfDestruct ), DEFINE_OUTPUT( m_OnDeploy, "OnDeploy" ), @@ -139,6 +152,9 @@ BEGIN_DATADESC( CNPC_FloorTurret ) DEFINE_OUTPUT( m_OnTipped, "OnTipped" ), DEFINE_OUTPUT( m_OnPhysGunPickup, "OnPhysGunPickup" ), DEFINE_OUTPUT( m_OnPhysGunDrop, "OnPhysGunDrop" ), +#ifdef MAPBASE + DEFINE_OUTPUT( m_OnStartTipped, "OnStartTipped" ), +#endif DEFINE_BASENPCINTERACTABLE_DATADESC(), @@ -288,10 +304,12 @@ void CNPC_FloorTurret::Spawn( void ) // add one mod 4 nextSkin = (nextSkin + 1) & 0x03; } +#ifndef MAPBASE else { // at least make sure that it's in the right range m_nSkin = clamp(m_iKeySkin,1,4); } +#endif } BaseClass::Spawn(); @@ -1511,6 +1529,10 @@ bool CNPC_FloorTurret::PreThink( turretState_e state ) SetEyeState( TURRET_EYE_DEAD ); } +#ifdef MAPBASE + m_OnStartTipped.FireOutput( this, this ); +#endif + //Stop being targetted SetState( NPC_STATE_DEAD ); m_lifeState = LIFE_DEAD; @@ -1545,7 +1567,11 @@ bool CNPC_FloorTurret::PreThink( turretState_e state ) void CNPC_FloorTurret::SetEyeState( eyeState_t state ) { // Must have a valid eye to affect +#ifdef MAPBASE + if ( !m_hEyeGlow && !HasSpawnFlags(SF_FLOOR_TURRET_NO_SPRITE) ) +#else if ( !m_hEyeGlow ) +#endif { // Create our eye sprite m_hEyeGlow = CSprite::SpriteCreate( FLOOR_TURRET_GLOW_SPRITE, GetLocalOrigin(), false ); @@ -1775,6 +1801,39 @@ void CNPC_FloorTurret::InputRestoreAmmo( inputdata_t &inputdata ) RemoveSpawnFlags( SF_FLOOR_TURRET_OUT_OF_AMMO ); } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: Creates the sprite if it has been destroyed +//----------------------------------------------------------------------------- +void CNPC_FloorTurret::InputCreateSprite( inputdata_t &inputdata ) +{ + if (m_hEyeGlow) + return; + + m_hEyeGlow = CSprite::SpriteCreate( FLOOR_TURRET_GLOW_SPRITE, GetLocalOrigin(), false ); + if ( !m_hEyeGlow ) + return; + + m_hEyeGlow->SetTransparency( kRenderWorldGlow, 255, 0, 0, 128, kRenderFxNoDissipation ); + m_hEyeGlow->SetAttachment( this, m_iEyeAttachment ); + + RemoveSpawnFlags(SF_FLOOR_TURRET_NO_SPRITE); +} + +//----------------------------------------------------------------------------- +// Purpose: Destroys the sprite +//----------------------------------------------------------------------------- +void CNPC_FloorTurret::InputDestroySprite( inputdata_t &inputdata ) +{ + if (!m_hEyeGlow) + return; + + UTIL_Remove(m_hEyeGlow); + m_hEyeGlow = NULL; + AddSpawnFlags(SF_FLOOR_TURRET_NO_SPRITE); +} +#endif + //----------------------------------------------------------------------------- // Purpose: Allow players and npc's to turn the turret on and off //----------------------------------------------------------------------------- @@ -1994,6 +2053,20 @@ int CNPC_FloorTurret::DrawDebugTextOverlays( void ) return text_offset; } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: Option to restore Alyx's interactions with non-rollermines +//----------------------------------------------------------------------------- +bool CNPC_FloorTurret::CanInteractWith( CAI_BaseNPC *pUser ) +{ +#ifdef HL2_EPISODIC + return npc_alyx_interact_turrets.GetBool(); +#else + return false; +#endif +} +#endif + void CNPC_FloorTurret::UpdateMuzzleMatrix() { if ( gpGlobals->tickcount != m_muzzleToWorldTick ) diff --git a/src/game/server/hl2/npc_turret_floor.h b/src/game/server/hl2/npc_turret_floor.h index 53c54b0c..af006a01 100644 --- a/src/game/server/hl2/npc_turret_floor.h +++ b/src/game/server/hl2/npc_turret_floor.h @@ -43,6 +43,9 @@ enum eyeState_t #define SF_FLOOR_TURRET_FASTRETIRE 0x00000080 #define SF_FLOOR_TURRET_OUT_OF_AMMO 0x00000100 #define SF_FLOOR_TURRET_CITIZEN 0x00000200 // Citizen modified turret +#ifdef MAPBASE +#define SF_FLOOR_TURRET_NO_SPRITE 0x00000400 +#endif class CTurretTipController; class CBeam; @@ -131,6 +134,11 @@ public: void InputDepleteAmmo( inputdata_t &inputdata ); void InputRestoreAmmo( inputdata_t &inputdata ); void InputSelfDestruct( inputdata_t &inputdata ); +#ifdef MAPBASE + void InputCreateSprite( inputdata_t &inputdata ); + void InputDestroySprite( inputdata_t &inputdata ); + void InputPowerdown( inputdata_t &inputdata ) { InputSelfDestruct(inputdata); } +#endif virtual bool IsValidEnemy( CBaseEntity *pEnemy ); bool CanBeAnEnemyOf( CBaseEntity *pEnemy ); @@ -168,13 +176,20 @@ public: int DrawDebugTextOverlays( void ); // INPCInteractive Functions +#ifdef MAPBASE + virtual bool CanInteractWith( CAI_BaseNPC *pUser ); +#else virtual bool CanInteractWith( CAI_BaseNPC *pUser ) { return false; } // Disabled for now (sjb) +#endif virtual bool HasBeenInteractedWith() { return m_bHackedByAlyx; } virtual void NotifyInteraction( CAI_BaseNPC *pUser ) { // For now, turn green so we can tell who is hacked. SetRenderColor( 0, 255, 0 ); m_bHackedByAlyx = true; +#ifdef MAPBASE + m_OnHacked.FireOutput(pUser, this); +#endif } static float fMaxTipControllerVelocity; @@ -220,7 +235,9 @@ protected: bool m_bCarriedByPlayer; bool m_bUseCarryAngles; float m_flPlayerDropTime; +#ifndef MAPBASE // Replaced with m_nSkin. int m_iKeySkin; +#endif CHandle m_hLastNPCToKickMe; // Stores the last NPC who tried to knock me over float m_flKnockOverFailedTime; // Time at which we should tell the NPC that he failed to knock me over @@ -248,6 +265,9 @@ protected: COutputEvent m_OnTipped; COutputEvent m_OnPhysGunPickup; COutputEvent m_OnPhysGunDrop; +#ifdef MAPBASE + COutputEvent m_OnStartTipped; +#endif bool m_bHackedByAlyx; HSOUNDSCRIPTHANDLE m_ShotSounds; diff --git a/src/game/server/hl2/npc_turret_ground.cpp b/src/game/server/hl2/npc_turret_ground.cpp index bacaac0a..c7dceeae 100644 --- a/src/game/server/hl2/npc_turret_ground.cpp +++ b/src/game/server/hl2/npc_turret_ground.cpp @@ -69,7 +69,7 @@ END_DATADESC() void CNPC_GroundTurret::Precache( void ) { PrecacheModel( GROUNDTURRET_BEAM_SPRITE ); - PrecacheModel( "models/combine_turrets/ground_turret.mdl" ); + PrecacheModel( DefaultOrCustomModel( "models/combine_turrets/ground_turret.mdl" ) ); PrecacheScriptSound( "NPC_CeilingTurret.Deploy" ); m_ShotSounds = PrecacheScriptSound( "NPC_FloorTurret.ShotSounds" ); @@ -88,7 +88,7 @@ void CNPC_GroundTurret::Spawn( void ) { Precache(); - UTIL_SetModel( this, "models/combine_turrets/ground_turret.mdl" ); + UTIL_SetModel( this, DefaultOrCustomModel( "models/combine_turrets/ground_turret.mdl" ) ); SetNavType( NAV_FLY ); SetSolid( SOLID_VPHYSICS ); @@ -130,8 +130,10 @@ void CNPC_GroundTurret::Spawn( void ) if( !GetParent() ) { DevMsg("ERROR! npc_ground_turret with no parent!\n"); +#ifndef MAPBASE UTIL_Remove(this); return; +#endif } m_flTimeNextShoot = gpGlobals->curtime; diff --git a/src/game/server/hl2/npc_vortigaunt_episodic.cpp b/src/game/server/hl2/npc_vortigaunt_episodic.cpp index 3337a65b..218da1a5 100644 --- a/src/game/server/hl2/npc_vortigaunt_episodic.cpp +++ b/src/game/server/hl2/npc_vortigaunt_episodic.cpp @@ -581,6 +581,31 @@ bool CNPC_Vortigaunt::InnateWeaponLOSCondition( const Vector &ownerPos, const Ve UTIL_PredictedPosition( this, flTimeDelta, &vecNewOwnerPos ); UTIL_PredictedPosition( GetEnemy(), flTimeDelta, &vecNewTargetPos ); +#ifdef MAPBASE + // There's apparently a null pointer crash here + if (!GetEnemy()) + return false; + + // The fix below and its accompanying comment were created by DKY. + + /* + + Fix for LOS test failures when the Vort is attempting to find a viable + shoot position-- Valve's "predict where we'll be in a moment" hack (as + described above) completely breaks SCHED_ESTABLISH_LINE_OF_FIRE because it + causes all LOS checks to fail for every node if the Vort's current position + is not a valid shooting position. + - Thank you, dky.tehkingd.u! + + */ + + // Determine the Vort's predicted position delta + Vector ownerDelta = vecNewOwnerPos - GetAbsOrigin(); + + // Offset our requested LOS check location by the predicted delta. + vecNewOwnerPos = ownerPos + ownerDelta; +#endif + Vector vecDelta = vecNewTargetPos - GetEnemy()->GetAbsOrigin(); Vector vecFinalTargetPos = GetEnemy()->BodyTarget( vecNewOwnerPos ) + vecDelta; @@ -1045,9 +1070,17 @@ Activity CNPC_Vortigaunt::NPC_TranslateActivity( Activity eNewActivity ) if ( GetReadinessLevel() >= AIRL_STIMULATED ) return ACT_IDLE_STIMULATED; } - + if ( eNewActivity == ACT_RANGE_ATTACK2 ) + { +#ifdef MAPBASE + // If we're capable of using grenades, use ACT_COMBINE_THROW_GRENADE + if (IsGrenadeCapable()) + return ACT_COMBINE_THROW_GRENADE; + else +#endif return (Activity) ACT_VORTIGAUNT_DISPEL; + } return BaseClass::NPC_TranslateActivity( eNewActivity ); } @@ -2693,6 +2726,15 @@ void CNPC_Vortigaunt::OnSquishedGrub( const CBaseEntity *pGrub ) //----------------------------------------------------------------------------- void CNPC_Vortigaunt::AimGun( void ) { +#ifdef MAPBASE + // Use base for func_tank + if (m_FuncTankBehavior.IsRunning()) + { + BaseClass::AimGun(); + return; + } +#endif + // If our aim lock is on, don't bother if ( m_flAimDelay >= gpGlobals->curtime ) return; @@ -2776,7 +2818,12 @@ bool CNPC_Vortigaunt::CanFlinch( void ) if ( IsCurSchedule( SCHED_VORTIGAUNT_DISPEL_ANTLIONS ) || IsCurSchedule( SCHED_RANGE_ATTACK1 ) ) return false; +#ifdef MAPBASE + // This skips CAI_PlayerAlly's CanFlinch() function since Episodic vorts can flinch to begin with. + return CAI_BaseActor::CanFlinch(); +#else return BaseClass::CanFlinch(); +#endif } //----------------------------------------------------------------------------- diff --git a/src/game/server/hl2/npc_vortigaunt_episodic.h b/src/game/server/hl2/npc_vortigaunt_episodic.h index e0da23d3..91e6a3c6 100644 --- a/src/game/server/hl2/npc_vortigaunt_episodic.h +++ b/src/game/server/hl2/npc_vortigaunt_episodic.h @@ -138,6 +138,13 @@ public: // used so a grub can notify me that I stepped on it. Says a line. void OnSquishedGrub( const CBaseEntity *pGrub ); +#ifdef MAPBASE + // Use the vortigaunts' default subtitle color (188,241,174) + bool GetGameTextSpeechParams( hudtextparms_t ¶ms ) { params.r1 = 188; params.g1 = 241; params.b1 = 174; return BaseClass::GetGameTextSpeechParams( params ); } + + const char* GetGrenadeAttachment() { return "rightclaw"; } +#endif + private: int NumAntlionsInRadius( float flRadius ); diff --git a/src/game/server/hl2/npc_zombie.cpp b/src/game/server/hl2/npc_zombie.cpp index 6762c45c..83a6ed9e 100644 --- a/src/game/server/hl2/npc_zombie.cpp +++ b/src/game/server/hl2/npc_zombie.cpp @@ -17,6 +17,10 @@ #include "soundenvelope.h" #include "engine/IEngineSound.h" #include "ammodef.h" +#ifdef MAPBASE +#include "AI_ResponseSystem.h" +#include "ai_speech.h" +#endif // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" @@ -75,6 +79,18 @@ envelopePoint_t envZombieMoanIgnited[] = }, }; +#ifdef MAPBASE +//------------------------------------------------------------------------------ +// Move these to CNPC_BaseZombie if other zombies end up using the response system +//------------------------------------------------------------------------------ +#define TLK_ZOMBIE_PAIN "TLK_WOUND" +#define TLK_ZOMBIE_DEATH "TLK_DEATH" +#define TLK_ZOMBIE_ALERT "TLK_STARTCOMBAT" +#define TLK_ZOMBIE_IDLE "TLK_QUESTION" +#define TLK_ZOMBIE_ATTACK "TLK_MELEE" +#define TLK_ZOMBIE_MOAN "TLK_MOAN" +#endif + //============================================================================= //============================================================================= @@ -263,6 +279,17 @@ void CZombie::Spawn( void ) { Precache(); +#ifdef MAPBASE + if( Q_strstr( GetClassname(), "torso" ) ) + { + // This was placed as an npc_zombie_torso + m_fIsTorso = true; + } + else + { + m_fIsTorso = false; + } +#else if( FClassnameIs( this, "npc_zombie" ) ) { m_fIsTorso = false; @@ -274,6 +301,7 @@ void CZombie::Spawn( void ) } m_fIsHeadless = false; +#endif #ifdef HL2_EPISODIC SetBloodColor( BLOOD_COLOR_ZOMBIE ); @@ -880,7 +908,7 @@ bool CZombie::IsSquashed( const CTakeDamageInfo &info ) return false; } - if( info.GetDamageType() & DMG_CRUSH ) + if( info.GetDamageType() & DMG_CRUSH && info.GetInflictor() ) // Mapbase - Fixes a crash with inflictor-less crush damage { IPhysicsObject *pCrusher = info.GetInflictor()->VPhysicsGetObject(); if( pCrusher && pCrusher->GetMass() >= ZOMBIE_SQUASH_MASS && info.GetInflictor()->WorldSpaceCenter().z > EyePosition().z ) @@ -999,3 +1027,281 @@ AI_BEGIN_CUSTOM_NPC( npc_zombie, CZombie ) AI_END_CUSTOM_NPC() //============================================================================= + +#ifdef MAPBASE +class CZombieCustom : public CAI_ExpresserHost +{ + DECLARE_DATADESC(); + DECLARE_CLASS( CZombieCustom, CAI_ExpresserHost ); + +public: + CZombieCustom(); + + void Spawn( void ); + void Precache( void ); + + void SpeakIfAllowed( const char *concept, AI_CriteriaSet *modifiers = NULL ); + void ModifyOrAppendCriteria( AI_CriteriaSet& set ); + virtual CAI_Expresser *CreateExpresser( void ); + virtual CAI_Expresser *GetExpresser() { return m_pExpresser; } + virtual void PostConstructor( const char *szClassname ); + + void PainSound( const CTakeDamageInfo &info ); + void DeathSound( const CTakeDamageInfo &info ); + void AlertSound( void ); + void IdleSound( void ); + void AttackSound( void ); + + const char *GetMoanSound( int nSound ); + + void SetZombieModel( void ); + + virtual const char *GetLegsModel( void ) { return STRING(m_iszLegsModel); } + virtual const char *GetTorsoModel( void ) { return STRING(m_iszTorsoModel); } + virtual const char *GetHeadcrabClassname( void ) { return STRING(m_iszHeadcrabClassname); } + virtual const char *GetHeadcrabModel( void ) { return STRING(m_iszHeadcrabModel); } + + string_t m_iszLegsModel; + string_t m_iszTorsoModel; + string_t m_iszHeadcrabClassname; + string_t m_iszHeadcrabModel; + + CAI_Expresser *m_pExpresser; +}; + +BEGIN_DATADESC( CZombieCustom ) + + DEFINE_KEYFIELD( m_iszLegsModel, FIELD_STRING, "LegsModel" ), + DEFINE_KEYFIELD( m_iszTorsoModel, FIELD_STRING, "TorsoModel" ), + DEFINE_KEYFIELD( m_iszHeadcrabClassname, FIELD_STRING, "HeadcrabClassname" ), + DEFINE_KEYFIELD( m_iszHeadcrabModel, FIELD_STRING, "HeadcrabModel" ), + +END_DATADESC() + +LINK_ENTITY_TO_CLASS( npc_zombie_custom, CZombieCustom ); +LINK_ENTITY_TO_CLASS( npc_zombie_custom_torso, CZombieCustom ); + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CZombieCustom::CZombieCustom() +{ + m_iszLegsModel = AllocPooledString( CZombie::GetLegsModel() ); + m_iszTorsoModel = AllocPooledString( CZombie::GetTorsoModel() ); + m_iszHeadcrabClassname = AllocPooledString( CZombie::GetHeadcrabClassname() ); + m_iszHeadcrabModel = AllocPooledString( CZombie::GetHeadcrabModel() ); + + SetModelName( AllocPooledString("models/zombie/classic.mdl") ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CZombieCustom::Spawn( void ) +{ + int iHealth = m_iHealth; + + BaseClass::Spawn(); + + if (iHealth > 0) + { + m_iMaxHealth = iHealth; + m_iHealth = iHealth; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CZombieCustom::Precache( void ) +{ + BaseClass::Precache(); + + PrecacheModel(STRING(GetModelName())); + + if (m_iszLegsModel != NULL_STRING) + PrecacheModel( STRING(m_iszLegsModel) ); + + if (m_iszTorsoModel != NULL_STRING) + PrecacheModel( STRING(m_iszTorsoModel) ); + + if (m_iszHeadcrabClassname != NULL_STRING) + UTIL_PrecacheOther( STRING(m_iszHeadcrabClassname) ); + + if (m_iszHeadcrabModel != NULL_STRING) + PrecacheModel( STRING(m_iszHeadcrabModel) ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CZombieCustom::SetZombieModel( void ) +{ + Hull_t lastHull = GetHullType(); + + if ( m_fIsTorso ) + { + SetModel( GetTorsoModel() ); + SetHullType( HULL_TINY ); + } + else + { + SetModel( STRING(GetModelName()) ); + SetHullType( HULL_HUMAN ); + } + + SetBodygroup( ZOMBIE_BODYGROUP_HEADCRAB, !m_fIsHeadless ); + + SetHullSizeNormal( true ); + SetDefaultEyeOffset(); + SetActivity( ACT_IDLE ); + + // hull changed size, notify vphysics + // UNDONE: Solve this generally, systematically so other + // NPCs can change size + if ( lastHull != GetHullType() ) + { + if ( VPhysicsGetObject() ) + { + SetupVPhysicsHull(); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CZombieCustom::PainSound( const CTakeDamageInfo &info ) +{ + AI_CriteriaSet modifiers; + ModifyOrAppendDamageCriteria( modifiers, info ); + SpeakIfAllowed( TLK_ZOMBIE_PAIN, &modifiers ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CZombieCustom::DeathSound( const CTakeDamageInfo &info ) +{ + AI_CriteriaSet modifiers; + ModifyOrAppendDamageCriteria( modifiers, info ); + SpeakIfAllowed( TLK_ZOMBIE_DEATH, &modifiers ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CZombieCustom::AlertSound( void ) +{ + SpeakIfAllowed( TLK_ZOMBIE_ALERT ); + + // Don't let a moan sound cut off the alert sound. + m_flNextMoanSound += random->RandomFloat( 2.0, 4.0 ); +} + +//----------------------------------------------------------------------------- +// Purpose: Returns a moan sound for this class of zombie. +//----------------------------------------------------------------------------- +const char *CZombieCustom::GetMoanSound( int nSound ) +{ + AI_CriteriaSet modifiers; + + // We could probably do this through the response system alone now, but whatever. + modifiers.AppendCriteria( "moansound", UTIL_VarArgs("%i", nSound & 4) ); + +#ifdef NEW_RESPONSE_SYSTEM + AI_Response response; + CAI_Concept concept = "TLK_ZOMBIE_MOAN"; + concept.SetSpeaker( this ); + if (!FindResponse( response, concept, &modifiers )) + return "NPC_BaseZombie.Moan1"; +#else + AI_Response *response = SpeakFindResponse(TLK_ZOMBIE_MOAN, modifiers); + + if ( !response ) + return "NPC_BaseZombie.Moan1"; +#endif + + // Must be static so it could be returned + static char szSound[128]; +#ifdef NEW_RESPONSE_SYSTEM + response.GetName(szSound, sizeof(szSound)); +#else + response->GetName(szSound, sizeof(szSound)); + delete response; +#endif + + return szSound; +} + +//----------------------------------------------------------------------------- +// Purpose: Play a random idle sound. +//----------------------------------------------------------------------------- +void CZombieCustom::IdleSound( void ) +{ + if( GetState() == NPC_STATE_IDLE && random->RandomFloat( 0, 1 ) == 0 ) + { + // Moan infrequently in IDLE state. + return; + } + + SpeakIfAllowed( TLK_ZOMBIE_IDLE ); + MakeAISpookySound( 360.0f ); +} + +//----------------------------------------------------------------------------- +// Purpose: Play a random attack sound. +//----------------------------------------------------------------------------- +void CZombieCustom::AttackSound( void ) +{ + SpeakIfAllowed( TLK_ZOMBIE_ATTACK ); +} + +//----------------------------------------------------------------------------- +// Purpose: Speak concept +//----------------------------------------------------------------------------- +void CZombieCustom::SpeakIfAllowed(const char *concept, AI_CriteriaSet *modifiers) +{ + AI_CriteriaSet empty; + Speak( concept, modifiers ? *modifiers : empty ); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CZombieCustom::ModifyOrAppendCriteria( AI_CriteriaSet& set ) +{ + BaseClass::ModifyOrAppendCriteria( set ); + + set.AppendCriteria( "slumped", IsSlumped() ? "1" : "0" ); + + // Does this or a name already exist? + set.AppendCriteria( "onfire", IsOnFire() ? "1" : "0" ); + + // Custom zombies (and zombie torsos) must make zombie sounds. + // This can be overridden with response contexts. + set.AppendCriteria( "classname", "npc_zombie" ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CAI_Expresser *CZombieCustom::CreateExpresser( void ) +{ + m_pExpresser = new CAI_Expresser(this); + if (!m_pExpresser) + return NULL; + + m_pExpresser->Connect(this); + return m_pExpresser; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CZombieCustom::PostConstructor(const char *szClassname) +{ + BaseClass::PostConstructor(szClassname); + CreateExpresser(); +} +#endif diff --git a/src/game/server/hl2/npc_zombine.cpp b/src/game/server/hl2/npc_zombine.cpp index 479d220b..c25104d2 100644 --- a/src/game/server/hl2/npc_zombine.cpp +++ b/src/game/server/hl2/npc_zombine.cpp @@ -167,8 +167,15 @@ private: float m_flSuperFastAttackTime; float m_flGrenadePullTime; +#ifdef MAPBASE + int m_iGrenadeCount = ZOMBINE_MAX_GRENADES; +#else int m_iGrenadeCount; +#endif +#ifdef MAPBASE + COutputEHANDLE m_OnGrenade; +#endif EHANDLE m_hGrenade; protected: @@ -184,7 +191,12 @@ BEGIN_DATADESC( CNPC_Zombine ) DEFINE_FIELD( m_flSuperFastAttackTime, FIELD_TIME ), DEFINE_FIELD( m_hGrenade, FIELD_EHANDLE ), DEFINE_FIELD( m_flGrenadePullTime, FIELD_TIME ), +#ifdef MAPBASE + DEFINE_KEYFIELD( m_iGrenadeCount, FIELD_INTEGER, "NumGrenades" ), + DEFINE_OUTPUT( m_OnGrenade, "OnPullGrenade" ), +#else DEFINE_FIELD( m_iGrenadeCount, FIELD_INTEGER ), +#endif DEFINE_INPUTFUNC( FIELD_VOID, "StartSprint", InputStartSprint ), DEFINE_INPUTFUNC( FIELD_VOID, "PullGrenade", InputPullGrenade ), END_DATADESC() @@ -201,7 +213,9 @@ void CNPC_Zombine::Spawn( void ) Precache(); m_fIsTorso = false; +#ifndef MAPBASE // Controlled by KV m_fIsHeadless = false; +#endif #ifdef HL2_EPISODIC SetBloodColor( BLOOD_COLOR_ZOMBIE ); @@ -226,7 +240,9 @@ void CNPC_Zombine::Spawn( void ) g_flZombineGrenadeTimes = gpGlobals->curtime; m_flGrenadePullTime = gpGlobals->curtime; +#ifndef MAPBASE m_iGrenadeCount = ZOMBINE_MAX_GRENADES; +#endif } void CNPC_Zombine::Precache( void ) @@ -560,6 +576,9 @@ void CNPC_Zombine::HandleAnimEvent( animevent_t *pEvent ) pGrenade->SetParent( this, iAttachment ); pGrenade->SetDamage( 200.0f ); +#ifdef MAPBASE + m_OnGrenade.Set(pGrenade, pGrenade, this); +#endif m_hGrenade = pGrenade; EmitSound( "Zombine.ReadyGrenade" ); diff --git a/src/game/server/hl2/prop_combine_ball.cpp b/src/game/server/hl2/prop_combine_ball.cpp index b9370349..1606e5b1 100644 --- a/src/game/server/hl2/prop_combine_ball.cpp +++ b/src/game/server/hl2/prop_combine_ball.cpp @@ -227,6 +227,10 @@ BEGIN_DATADESC( CPropCombineBall ) DEFINE_INPUTFUNC( FIELD_VOID, "FadeAndRespawn", InputFadeAndRespawn ), DEFINE_INPUTFUNC( FIELD_VOID, "Kill", InputKill ), DEFINE_INPUTFUNC( FIELD_VOID, "Socketed", InputSocketed ), +#ifdef MAPBASE + DEFINE_INPUTFUNC( FIELD_FLOAT, "SetLifetime", InputSetLifetime ), + DEFINE_INPUTFUNC( FIELD_FLOAT, "AddLifetime", InputAddLifetime ), +#endif END_DATADESC() @@ -566,6 +570,10 @@ void CPropCombineBall::InputKill( inputdata_t &inputdata ) SetOwnerEntity( NULL ); } +#ifdef MAPBASE + m_OnKilled.FireOutput( inputdata.pActivator, this ); +#endif + UTIL_Remove( this ); NotifySpawnerOfRemoval(); @@ -596,6 +604,86 @@ void CPropCombineBall::InputSocketed( inputdata_t &inputdata ) NotifySpawnerOfRemoval(); } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPropCombineBall::InputSetLifetime( inputdata_t &inputdata ) +{ + if (m_bHeld) + { + // Special handling when held + float dt = inputdata.value.Float(); + float flSoundRampTime = GetBallHoldDissolveTime() - GetBallHoldSoundRampTime(); + + if (dt > flSoundRampTime) + { + if ( m_pHoldingSound ) + { + // Reset holding sound to regular pitch + CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController(); + controller.SoundChangePitch( m_pHoldingSound, 100, flSoundRampTime ); + } + + SetContextThink( &CPropCombineBall::DissolveRampSoundThink, gpGlobals->curtime + dt - flSoundRampTime, s_pHoldDissolveContext ); + } + else + { + if ( m_pHoldingSound ) + { + // Do pitch ramp based on our custom time, which is less than the normal pitch ramp time + CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController(); + controller.SoundChangePitch( m_pHoldingSound, 150, dt ); + } + SetContextThink( &CPropCombineBall::DissolveThink, gpGlobals->curtime + dt, s_pHoldDissolveContext ); + } + } + else + { + SetContextThink( &CPropCombineBall::ExplodeThink, gpGlobals->curtime + inputdata.value.Float(), s_pExplodeTimerContext ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPropCombineBall::InputAddLifetime( inputdata_t &inputdata ) +{ + if (m_bHeld) + { + // Special handling when held + float dt = (GetNextThink( s_pHoldDissolveContext ) - gpGlobals->curtime + inputdata.value.Float()); + float flSoundRampTime = GetBallHoldDissolveTime() - GetBallHoldSoundRampTime(); + + if (dt > flSoundRampTime) + { + if ( m_pHoldingSound ) + { + // Reset holding sound to regular pitch + CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController(); + controller.SoundChangePitch( m_pHoldingSound, 100, flSoundRampTime ); + } + + SetContextThink( &CPropCombineBall::DissolveRampSoundThink, gpGlobals->curtime + dt - flSoundRampTime, s_pHoldDissolveContext ); + } + else + { + if ( m_pHoldingSound ) + { + // Do pitch ramp based on our custom time, which is less than the normal pitch ramp time + CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController(); + controller.SoundChangePitch( m_pHoldingSound, 150, dt ); + } + SetContextThink( &CPropCombineBall::DissolveThink, gpGlobals->curtime + dt, s_pHoldDissolveContext ); + } + } + else + { + SetContextThink( &CPropCombineBall::ExplodeThink, gpGlobals->curtime + (GetNextThink( s_pExplodeTimerContext ) + inputdata.value.Float()), s_pExplodeTimerContext ); + } +} +#endif + //----------------------------------------------------------------------------- // Cleanup. //----------------------------------------------------------------------------- @@ -706,9 +794,63 @@ void CPropCombineBall::WhizSoundThink() pPhysicsObject->GetPosition( &vecPosition, NULL ); pPhysicsObject->GetVelocity( &vecVelocity, NULL ); - if ( gpGlobals->maxClients == 1 ) + // Multiplayer equivelent, loops through players and decides if it should go or not, like SP. + if ( gpGlobals->maxClients > 1 ) + { + CBasePlayer *pPlayer = NULL; + + for (int i = 1;i <= gpGlobals->maxClients; i++) + { + pPlayer = UTIL_PlayerByIndex( i ); + if ( pPlayer ) + { + Vector vecDelta; + VectorSubtract( pPlayer->GetAbsOrigin(), vecPosition, vecDelta ); + VectorNormalize( vecDelta ); + if ( DotProduct( vecDelta, vecVelocity ) > 0.5f ) + { + Vector vecEndPoint; + VectorMA( vecPosition, 2.0f * TICK_INTERVAL, vecVelocity, vecEndPoint ); + float flDist = CalcDistanceToLineSegment( pPlayer->GetAbsOrigin(), vecPosition, vecEndPoint ); + if ( flDist < 200.0f ) + { + // We're basically doing what CPASAttenuationFilter does, on a per-user basis, if it passes we create the filter and send off the sound + // if it doesn't, we skip the player. + float distance, maxAudible; + Vector vecRelative; + + VectorSubtract( pPlayer->EarPosition(), vecPosition, vecRelative ); + distance = VectorLength( vecRelative ); + maxAudible = ( 2 * SOUND_NORMAL_CLIP_DIST ) / ATTN_NORM; + if ( distance <= maxAudible ) + continue; + + // Set the recipient to the player it checked against so multiple sounds don't play. + CSingleUserRecipientFilter filter( pPlayer ); + + EmitSound_t ep; + ep.m_nChannel = CHAN_STATIC; + if ( hl2_episodic.GetBool() ) + { + ep.m_pSoundName = "NPC_CombineBall_Episodic.WhizFlyby"; + } + else + { + ep.m_pSoundName = "NPC_CombineBall.WhizFlyby"; + } + ep.m_flVolume = 1.0f; + ep.m_SoundLevel = SNDLVL_NORM; + + EmitSound( filter, entindex(), ep ); + } + } + } + } + } + else { CBasePlayer *pPlayer = UTIL_GetLocalPlayer(); + if ( pPlayer ) { Vector vecDelta; @@ -743,6 +885,7 @@ void CPropCombineBall::WhizSoundThink() } } } + } SetContextThink( &CPropCombineBall::WhizSoundThink, gpGlobals->curtime + 2.0f * TICK_INTERVAL, s_pWhizThinkContext ); @@ -937,6 +1080,113 @@ void CPropCombineBall::OnPhysGunDrop( CBasePlayer *pPhysGunUser, PhysGunDrop_t R StopAnimating(); } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPropCombineBall::SpawnerDestroyed( CBaseEntity *pActivator, bool *bSeekEnemy ) +{ + SetState( STATE_THROWN ); + WhizSoundThink(); + + m_bHeld = false; + m_bLaunched = true; + + // Stop with the dissolving + SetContextThink( NULL, gpGlobals->curtime, s_pHoldDissolveContext ); + + // We're ready to start colliding again. + SetCollisionGroup( HL2COLLISION_GROUP_COMBINE_BALL ); + + if ( m_pGlowTrail ) + { + m_pGlowTrail->TurnOn(); + m_pGlowTrail->SetRenderColor( 255, 255, 255, 255 ); + } + + // Set our desired speed to be launched at + SetSpeed( 1500.0f ); + + SetOwnerEntity( pActivator ); + SetWeaponLaunched( false ); + + if (!VPhysicsGetObject()) + return; + + if (pActivator->IsPlayer()) + { + PhysClearGameFlags( VPhysicsGetObject(), FVPHYSICS_NO_NPC_IMPACT_DMG ); + PhysSetGameFlags( VPhysicsGetObject(), FVPHYSICS_DMG_DISSOLVE | FVPHYSICS_HEAVY_OBJECT ); + } + else + { + // Don't do impact damage. Just touch them and do your dissolve damage and move on. + PhysSetGameFlags( VPhysicsGetObject(), FVPHYSICS_NO_NPC_IMPACT_DMG ); + } + + //if (pActivator->IsPlayer()) + //{ + // SetPlayerLaunched( ToBasePlayer( pActivator ) ); + //} + + Vector vecVelocity; + + if (bSeekEnemy) + { + CBaseEntity *pBestTarget = NULL; + CBaseEntity *list[256]; + + float distance; + float flBestDist = MAX_COORD_FLOAT; + int nCount = UTIL_EntitiesInSphere( list, 256, GetAbsOrigin(), sk_combine_ball_search_radius.GetFloat(), FL_NPC | FL_CLIENT ); + + for ( int i = 0; i < nCount; i++ ) + { + if ( !IsAttractiveTarget( list[i] ) ) + continue; + + distance = (list[i]->WorldSpaceCenter() - GetAbsOrigin()).LengthSqr(); + if ( distance < flBestDist ) + { + pBestTarget = list[i]; + flBestDist = distance; + } + } + + if ( pBestTarget ) + { + VectorSubtract( pBestTarget->WorldSpaceCenter(), GetAbsOrigin(), vecVelocity ); + VectorNormalize( vecVelocity ); + } + + *bSeekEnemy = (pBestTarget != NULL); + } + + if (bSeekEnemy == NULL || *bSeekEnemy == false) + { + // Choose a random direction based on current velocity + VPhysicsGetObject()->GetVelocity( &vecVelocity, NULL ); + VectorNormalize( vecVelocity ); + + QAngle shotAng; + VectorAngles( vecVelocity, shotAng ); + + // Offset by some small cone + shotAng[PITCH] += random->RandomInt( -75, 75 ); + shotAng[YAW] += random->RandomInt( -75, 75 ); + + AngleVectors( shotAng, &vecVelocity, NULL, NULL ); + } + + vecVelocity *= GetSpeed(); + + VPhysicsGetObject()->SetVelocity( &vecVelocity, &vec3_origin ); + + SetBallAsLaunched(); + StopAnimating(); +} +#endif + //------------------------------------------------------------------------------ // Stop looping sounds //------------------------------------------------------------------------------ @@ -1245,6 +1495,12 @@ void CPropCombineBall::OnHitEntity( CBaseEntity *pHitEntity, float flSpeed, int m_flNextDamageTime = gpGlobals->curtime + 0.1f; } +#ifdef MAPBASE + // Damage forces for NPC balls. + info.SetDamagePosition( GetAbsOrigin() ); + info.SetDamageForce( GetAbsVelocity() ); +#endif + pHitEntity->TakeDamage( info ); } } @@ -1700,6 +1956,9 @@ BEGIN_DATADESC( CFuncCombineBallSpawner ) DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ), DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ), +#ifdef MAPBASE + DEFINE_INPUTFUNC( FIELD_VOID, "Destroy", InputDestroy ), +#endif DEFINE_OUTPUT( m_OnBallGrabbed, "OnBallGrabbed" ), DEFINE_OUTPUT( m_OnBallReinserted, "OnBallReinserted" ), @@ -1852,6 +2111,35 @@ void CFuncCombineBallSpawner::InputDisable( inputdata_t &inputdata ) SetThink( NULL ); } +#ifdef MAPBASE +void CFuncCombineBallSpawner::InputDestroy( inputdata_t &inputdata ) +{ + if ( !m_bEnabled ) + { + UTIL_Remove( this ); + return; + } + + // One ball always seeks the nearest enemy + bool bSoughtEnemy = false; + + CBaseEntity *pEnt = gEntList.FindEntityByClassname( NULL, "prop_combine_ball" ); + while (pEnt) + { + CPropCombineBall *pBall = static_cast(pEnt); + if (pBall && pBall->GetSpawner() == this) + { + BallGrabbed( pBall ); + pBall->SpawnerDestroyed( inputdata.pActivator, bSoughtEnemy ? NULL : &bSoughtEnemy ); + } + + pEnt = gEntList.FindEntityByClassname( pEnt, "prop_combine_ball" ); + } + + UTIL_Remove( this ); +} +#endif + //----------------------------------------------------------------------------- // Choose a random point inside the cylinder diff --git a/src/game/server/hl2/prop_combine_ball.h b/src/game/server/hl2/prop_combine_ball.h index 1de4390d..d798fde1 100644 --- a/src/game/server/hl2/prop_combine_ball.h +++ b/src/game/server/hl2/prop_combine_ball.h @@ -65,6 +65,10 @@ public: void InputFadeAndRespawn( inputdata_t &inputdata ); void InputKill( inputdata_t &inputdata ); void InputSocketed( inputdata_t &inputdata ); +#ifdef MAPBASE + void InputSetLifetime( inputdata_t &inputdata ); + void InputAddLifetime( inputdata_t &inputdata ); +#endif enum { @@ -94,6 +98,9 @@ public: void SetSpawner( CFuncCombineBallSpawner *pSpawner ) { m_hSpawner = pSpawner; } void NotifySpawnerOfRemoval( void ); +#ifdef MAPBASE + void SpawnerDestroyed( CBaseEntity *pActivator, bool *bSeekEnemy ); +#endif float LastCaptureTime() const; @@ -239,6 +246,9 @@ private: // Input void InputEnable( inputdata_t &inputdata ); void InputDisable( inputdata_t &inputdata ); +#ifdef MAPBASE + void InputDestroy( inputdata_t &inputdata ); +#endif // Fire ball grabbed output void GrabBallTouch( CBaseEntity *pOther ); diff --git a/src/game/server/hl2/proto_sniper.cpp b/src/game/server/hl2/proto_sniper.cpp index fe9844eb..4315e35c 100644 --- a/src/game/server/hl2/proto_sniper.cpp +++ b/src/game/server/hl2/proto_sniper.cpp @@ -33,6 +33,13 @@ #include "effect_color_tables.h" #include "npc_rollermine.h" #include "eventqueue.h" +#ifdef MAPBASE +#include "CRagdollMagnet.h" +#endif +#ifdef EXPANDED_RESPONSE_SYSTEM_USAGE +#include "mapbase/expandedrs_combine.h" +#include "ai_speech.h" +#endif #include "effect_dispatch_data.h" #include "te_effect_dispatch.h" @@ -62,6 +69,9 @@ extern ConVar sk_dmg_sniper_penetrate_npc; #define SF_SNIPER_STARTDISABLED (1 << 19) #define SF_SNIPER_FAST (1 << 20) ///< This is faster-shooting sniper. Paint time is decreased 25%. Bullet speed increases 150%. #define SF_SNIPER_NOSWEEP (1 << 21) ///< This sniper doesn't sweep to the target or use decoys. +#ifdef MAPBASE +#define SF_SNIPER_DIE_ON_FIRE (1 << 22) // This sniper dies on fire. +#endif // If the last time I fired at someone was between 0 and this many seconds, draw // a bead on them much faster. (use subsequent paint time) @@ -194,9 +204,15 @@ private: //========================================================= //========================================================= +#ifdef EXPANDED_RESPONSE_SYSTEM_USAGE +class CProtoSniper : public CAI_ExpresserHost +{ + DECLARE_CLASS( CProtoSniper, CAI_ExpresserHost ); +#else class CProtoSniper : public CAI_BaseNPC { DECLARE_CLASS( CProtoSniper, CAI_BaseNPC ); +#endif public: CProtoSniper( void ); @@ -234,6 +250,10 @@ public: virtual int SelectSchedule( void ); virtual int TranslateSchedule( int scheduleType ); +#ifdef MAPBASE + Activity NPC_TranslateActivity( Activity eNewActivity ); +#endif + bool KeyValue( const char *szKeyName, const char *szValue ); void PrescheduleThink( void ); @@ -262,7 +282,21 @@ public: void NotifyShotMissedTarget(); +#ifdef EXPANDED_RESPONSE_SYSTEM_USAGE + //DeclareResponseSystem() + bool SpeakIfAllowed(const char *concept, const char *modifiers = NULL); + void ModifyOrAppendCriteria( AI_CriteriaSet& set ); + + virtual CAI_Expresser *CreateExpresser( void ); + virtual CAI_Expresser *GetExpresser() { return m_pExpresser; } + virtual void PostConstructor( const char *szClassname ); +#endif + private: + +#ifdef EXPANDED_RESPONSE_SYSTEM_USAGE + CAI_Expresser * m_pExpresser; +#endif bool ShouldSnapShot( void ); void ClearTargetGroup( void ); @@ -304,6 +338,10 @@ private: bool IsPlayerAllySniper(); +#ifdef MAPBASE + const Vector &GetPaintCursor() { return m_vecPaintCursor; } +#endif + private: /// This is the variable from which m_flPaintTime gets set. @@ -363,11 +401,19 @@ private: bool m_bKilledPlayer; bool m_bShootZombiesInChest; ///< if true, do not try to shoot zombies in the headcrab +#ifdef MAPBASE + string_t m_iszBeamName; // Custom beam texture + color32 m_BeamColor; // Custom beam color +#endif + COutputEvent m_OnShotFired; DEFINE_CUSTOM_AI; DECLARE_DATADESC(); +#ifdef MAPBASE_VSCRIPT + DECLARE_ENT_SCRIPTDESC(); +#endif }; @@ -440,6 +486,11 @@ BEGIN_DATADESC( CProtoSniper ) DEFINE_FIELD( m_bWarnedTargetEntity, FIELD_BOOLEAN ), DEFINE_FIELD( m_flTimeLastShotMissed, FIELD_TIME ), +#ifdef MAPBASE + DEFINE_KEYFIELD( m_iszBeamName, FIELD_STRING, "BeamName" ), + DEFINE_KEYFIELD( m_BeamColor, FIELD_COLOR32, "BeamColor" ), +#endif + // Inputs DEFINE_INPUTFUNC( FIELD_VOID, "EnableSniper", InputEnableSniper ), DEFINE_INPUTFUNC( FIELD_VOID, "DisableSniper", InputDisableSniper ), @@ -460,6 +511,26 @@ BEGIN_DATADESC( CProtoSniper ) END_DATADESC() +#ifdef MAPBASE_VSCRIPT +BEGIN_ENT_SCRIPTDESC( CProtoSniper, CAI_BaseNPC, "Combine sniper NPC." ) + + DEFINE_SCRIPTFUNC( GetBulletSpeed, "" ) + DEFINE_SCRIPTFUNC( GetBulletOrigin, "" ) + DEFINE_SCRIPTFUNC( ScopeGlint, "" ) + + DEFINE_SCRIPTFUNC( GetPositionParameter, "" ) + DEFINE_SCRIPTFUNC( IsSweepingRandomly, "" ) + DEFINE_SCRIPTFUNC( FindFrustratedShot, "" ) + + DEFINE_SCRIPTFUNC( IsLaserOn, "" ) + DEFINE_SCRIPTFUNC( LaserOn, "" ) + DEFINE_SCRIPTFUNC( LaserOff, "" ) + + DEFINE_SCRIPTFUNC( GetPaintCursor, "Get the point the sniper is currently aiming at." ) + +END_SCRIPTDESC() +#endif + //========================================================= @@ -496,6 +567,10 @@ enum Sniper_Conds COND_SNIPER_FRUSTRATED, COND_SNIPER_SWEEP_TARGET, COND_SNIPER_NO_SHOT, +#ifdef MAPBASE + // Using COND_ENEMY_DEAD made us take credit for other people's kills + COND_SNIPER_KILLED_ENEMY, +#endif }; @@ -557,7 +632,11 @@ CProtoSniper::CProtoSniper( void ) : m_flKeyfieldPaintTime(SNIPER_DEFAULT_PAINT_ bool CProtoSniper::QuerySeeEntity( CBaseEntity *pEntity, bool bOnlyHateOrFearIfNPC ) { Disposition_t disp = IRelationType(pEntity); +#ifdef MAPBASE + if( disp > D_FR ) +#else if( disp != D_HT ) +#endif { // Don't bother with anything I wouldn't shoot. return false; @@ -643,8 +722,13 @@ void CProtoSniper::LaserOn( const Vector &vecTarget, const Vector &vecDeviance ) { if (!m_pBeam) { +#ifdef MAPBASE + m_pBeam = CBeam::BeamCreate( STRING(m_iszBeamName), 1.0f ); + m_pBeam->SetColor( m_BeamColor.r, m_BeamColor.g, m_BeamColor.b ); +#else m_pBeam = CBeam::BeamCreate( "effects/bluelaser1.vmt", 1.0f ); m_pBeam->SetColor( 0, 100, 255 ); +#endif } else { @@ -856,6 +940,14 @@ void CProtoSniper::OnScheduleChange( void ) { LaserOff(); +#ifdef MAPBASE + if ( m_bKilledPlayer && HasCondition( COND_SEE_PLAYER ) ) + { + // IMPOSSIBLE! (possible when SP respawn is enabled) + m_bKilledPlayer = false; + } +#endif + BaseClass::OnScheduleChange(); } @@ -904,10 +996,40 @@ LINK_ENTITY_TO_CLASS( sniperbullet, CSniperBullet ); //----------------------------------------------------------------------------- void CProtoSniper::Precache( void ) { +#ifdef MAPBASE + if (GetModelName() == NULL_STRING) + SetModelName(AllocPooledString("models/combine_soldier.mdl")); + + PrecacheModel(STRING(GetModelName())); + + // Should we bother to make these customizable? These are static, too. + sHaloSprite = PrecacheModel("sprites/light_glow03.vmt"); + sFlashSprite = PrecacheModel( "sprites/muzzleflash1.vmt" ); + + if (m_iszBeamName == NULL_STRING) + { + m_iszBeamName = AllocPooledString("effects/bluelaser1.vmt"); + m_BeamColor.r = 0; + m_BeamColor.g = 100; + m_BeamColor.b = 255; + } + else if (Q_GetFileExtension(STRING(m_iszBeamName)) == NULL) + { + // The path doesn't have a .vmt. Fix this or we crash! + // + // I know I warn against using .vmt even though this code ultimately ensures there is no consequence other than a warning, + // but it's bad practice and remember: That old string without the .vmt would likely be floating around somewhere doing nothing. + Warning("%s beam name \"%s\" lacks .vmt!\n", GetDebugName(), STRING(m_iszBeamName)); + m_iszBeamName = AllocPooledString(UTIL_VarArgs("%s.vmt", STRING(m_iszBeamName))); + } + + PrecacheModel(STRING(m_iszBeamName)); +#else PrecacheModel("models/combine_soldier.mdl"); sHaloSprite = PrecacheModel("sprites/light_glow03.vmt"); sFlashSprite = PrecacheModel( "sprites/muzzleflash1.vmt" ); PrecacheModel("effects/bluelaser1.vmt"); +#endif UTIL_PrecacheOther( "sniperbullet" ); @@ -931,8 +1053,12 @@ void CProtoSniper::Spawn( void ) { Precache(); +#ifdef MAPBASE + SetModel( STRING(GetModelName()) ); +#else /// HACK: SetModel( "models/combine_soldier.mdl" ); +#endif //m_hBullet = (CSniperBullet *)Create( "sniperbullet", GetBulletOrigin(), GetLocalAngles(), NULL ); @@ -985,7 +1111,11 @@ void CProtoSniper::Spawn( void ) // Point the cursor straight ahead so that the sniper's // first sweep of the laser doesn't look weird. Vector vecForward; +#ifdef MAPBASE + AngleVectors( GetAbsAngles(), &vecForward ); +#else AngleVectors( GetLocalAngles(), &vecForward ); +#endif m_vecPaintCursor = GetBulletOrigin() + vecForward * 1024; m_fWeaponLoaded = true; @@ -1200,7 +1330,11 @@ Vector CProtoSniper::GetBulletOrigin( void ) else { Vector vecForward; +#ifdef MAPBASE + AngleVectors( GetAbsAngles(), &vecForward ); +#else AngleVectors( GetLocalAngles(), &vecForward ); +#endif return WorldSpaceCenter() + vecForward * 20; } } @@ -1327,11 +1461,32 @@ void CProtoSniper::Event_Killed( const CTakeDamageInfo &info ) { if( !(m_spawnflags & SF_SNIPER_NOCORPSE) ) { +#ifdef MAPBASE + Vector vecForce; + + // See if there's a ragdoll magnet that should influence our force. + // However, to avoid conflicts with existing maps, only allow magnets that have us as their target. + CRagdollMagnet *pMagnet = CRagdollMagnet::FindBestMagnet( this ); + if( pMagnet && pMagnet->m_target == GetEntityName() ) + { + vecForce = pMagnet->GetForceVector( this ); + pMagnet->m_OnUsed.Set(vecForce, this, pMagnet); + } + else + { + Vector vecForward; + AngleVectors( GetAbsAngles(), &vecForward ); + float flForce = random->RandomFloat( 500, 700 ) * 10; + + vecForce = (vecForward * flForce) + Vector(0, 0, 600); + } +#else Vector vecForward; float flForce = random->RandomFloat( 500, 700 ) * 10; AngleVectors( GetLocalAngles(), &vecForward ); +#endif float flFadeTime = 0.0; @@ -1341,8 +1496,13 @@ void CProtoSniper::Event_Killed( const CTakeDamageInfo &info ) } CBaseEntity *pGib; +#ifdef MAPBASE + bool bShouldIgnite = IsOnFire() || HasSpawnFlags(SF_SNIPER_DIE_ON_FIRE); + pGib = CreateRagGib( STRING(GetModelName()), GetAbsOrigin(), GetAbsAngles(), vecForce, flFadeTime, bShouldIgnite ); +#else bool bShouldIgnite = IsOnFire() || hl2_episodic.GetBool(); pGib = CreateRagGib( "models/combine_soldier.mdl", GetLocalOrigin(), GetLocalAngles(), (vecForward * flForce) + Vector(0, 0, 600), flFadeTime, bShouldIgnite ); +#endif } @@ -1357,7 +1517,11 @@ void CProtoSniper::Event_Killed( const CTakeDamageInfo &info ) LaserOff(); +#ifdef EXPANDED_RESPONSE_SYSTEM_USAGE + SpeakIfAllowed( TLK_SNIPER_DIE ); +#else EmitSound( "NPC_Sniper.Die" ); +#endif UTIL_Remove( this ); } @@ -1366,6 +1530,13 @@ void CProtoSniper::Event_Killed( const CTakeDamageInfo &info ) //--------------------------------------------------------- void CProtoSniper::Event_KilledOther( CBaseEntity *pVictim, const CTakeDamageInfo &info ) { +#ifdef MAPBASE + BaseClass::Event_KilledOther( pVictim, info ); + + if (pVictim == GetEnemy()) + SetCondition(COND_SNIPER_KILLED_ENEMY); +#endif + if( pVictim && pVictim->IsPlayer() ) { m_bKilledPlayer = true; @@ -1384,10 +1555,18 @@ void CProtoSniper::UpdateOnRemove( void ) //--------------------------------------------------------- int CProtoSniper::SelectSchedule ( void ) { +#ifdef EXPANDED_RESPONSE_SYSTEM_USAGE + if (HasCondition(COND_SNIPER_KILLED_ENEMY)) + { + SpeakIfAllowed(TLK_SNIPER_TARGETDESTROYED); + ClearCondition(COND_SNIPER_KILLED_ENEMY); + } +#else if( HasCondition(COND_ENEMY_DEAD) && sniperspeak.GetBool() ) { EmitSound( "NPC_Sniper.TargetDestroyed" ); } +#endif if( !m_fWeaponLoaded ) { @@ -1420,10 +1599,14 @@ int CProtoSniper::SelectSchedule ( void ) // probably won't harm him. // Also, don't play the sound effect if we're an ally. +#ifdef EXPANDED_RESPONSE_SYSTEM_USAGE + SpeakIfAllowed(TLK_SNIPER_DANGER); +#else if ( IsPlayerAllySniper() == false ) { EmitSound( "NPC_Sniper.HearDanger" ); } +#endif } return SCHED_PSNIPER_SUPPRESSED; @@ -1795,7 +1978,11 @@ int CProtoSniper::RangeAttack1Conditions ( float flDot, float flDist ) float flDist; +#ifdef MAPBASE + flDist = ( GetAbsOrigin() - GetEnemy()->GetAbsOrigin() ).Length2D(); +#else flDist = ( GetLocalOrigin() - GetEnemy()->GetLocalOrigin() ).Length2D(); +#endif if( flDist <= m_flPatience ) { @@ -1861,6 +2048,23 @@ int CProtoSniper::TranslateSchedule( int scheduleType ) return BaseClass::TranslateSchedule( scheduleType ); } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +Activity CProtoSniper::NPC_TranslateActivity( Activity eNewActivity ) +{ + // ACT_IDLE is now just the soldier's unarmed idle animation. + // Use a gun-holding animation like what unhidden snipers were using before. + if (!HasSpawnFlags( SF_SNIPER_HIDDEN ) && eNewActivity == ACT_IDLE) + { + eNewActivity = ACT_IDLE_SMG1; + } + + return BaseClass::NPC_TranslateActivity( eNewActivity ); +} +#endif + //--------------------------------------------------------- //--------------------------------------------------------- void CProtoSniper::ScopeGlint() @@ -1893,7 +2097,11 @@ bool CProtoSniper::FireBullet( const Vector &vecTarget, bool bDirectShot ) vecBulletOrigin = GetBulletOrigin(); +#ifdef MAPBASE + pBullet = (CSniperBullet *)Create( "sniperbullet", GetBulletOrigin(), GetAbsAngles(), NULL ); +#else pBullet = (CSniperBullet *)Create( "sniperbullet", GetBulletOrigin(), GetLocalAngles(), NULL ); +#endif Assert( pBullet != NULL ); @@ -1994,10 +2202,18 @@ void CProtoSniper::StartTask( const Task_t *pTask ) // Otherwise, sweep from wherever the cursor was. if( m_hSweepTarget->HasSpawnFlags( SF_SNIPERTARGET_SNAPTO ) ) { +#ifdef MAPBASE + m_vecPaintCursor = m_hSweepTarget->GetAbsOrigin(); +#else m_vecPaintCursor = m_hSweepTarget->GetLocalOrigin(); +#endif } +#ifdef MAPBASE + LaserOn( m_hSweepTarget->GetAbsOrigin(), vec3_origin ); +#else LaserOn( m_hSweepTarget->GetLocalOrigin(), vec3_origin ); +#endif break; case TASK_SNIPER_PAINT_ENEMY: @@ -2066,7 +2282,11 @@ void CProtoSniper::StartTask( const Task_t *pTask ) else { // Try to start the laser where the player can't miss seeing it! +#ifdef MAPBASE + AngleVectors( GetEnemy()->GetAbsAngles(), &vecCursor ); +#else AngleVectors( GetEnemy()->GetLocalAngles(), &vecCursor ); +#endif vecCursor = vecCursor * 300; vecCursor += GetEnemy()->EyePosition(); LaserOn( vecCursor, Vector( 16, 16, 16 ) ); @@ -2200,7 +2420,11 @@ void CProtoSniper::RunTask( const Task_t *pTask ) if ( m_hSweepTarget->HasSpawnFlags( SF_SNIPERTARGET_SHOOTME ) ) { +#ifdef MAPBASE + FireBullet( m_hSweepTarget->GetAbsOrigin(), false ); +#else FireBullet( m_hSweepTarget->GetLocalOrigin(), false ); +#endif TaskComplete(); // Force a reload. } @@ -2209,7 +2433,11 @@ void CProtoSniper::RunTask( const Task_t *pTask ) // Bump the timer up, update the cursor, paint the new target! // This is done regardless of whether we just fired at the current target. +#ifdef MAPBASE + m_vecPaintCursor = m_hSweepTarget->GetAbsOrigin(); +#else m_vecPaintCursor = m_hSweepTarget->GetLocalOrigin(); +#endif if( IsSweepingRandomly() ) { // If sweeping randomly, just pick another target. @@ -2399,7 +2627,11 @@ Vector CProtoSniper::EyePosition( void ) { if( m_spawnflags & SF_SNIPER_HIDDEN ) { +#ifdef MAPBASE + return GetAbsOrigin(); +#else return GetLocalOrigin(); +#endif } else { @@ -2414,6 +2646,19 @@ Vector CProtoSniper::DesiredBodyTarget( CBaseEntity *pTarget ) // By default, aim for the center Vector vecTarget = pTarget->WorldSpaceCenter(); +#ifdef MAPBASE_VSCRIPT + if (m_ScriptScope.IsInitialized() && g_Hook_GetActualShootPosition.CanRunInScope(m_ScriptScope)) + { + ScriptVariant_t functionReturn; + ScriptVariant_t args[] = { GetBulletOrigin(), ToHScript( pTarget ) }; + if (g_Hook_GetActualShootPosition.Call( m_ScriptScope, &functionReturn, args )) + { + if (functionReturn.m_type == FIELD_VECTOR && functionReturn.m_pVector->LengthSqr() != 0.0f) + return *functionReturn.m_pVector; + } + } +#endif + float flTimeSinceLastMiss = gpGlobals->curtime - m_flTimeLastShotMissed; if( pTarget->GetFlags() & FL_CLIENT ) @@ -2538,7 +2783,11 @@ Vector CProtoSniper::LeadTarget( CBaseEntity *pTarget ) vecAngle.x = 0; vecAngle.z = 0; +#ifdef MAPBASE + vecAngle.y += pTarget->GetAbsAngles().y; +#else vecAngle.y += pTarget->GetLocalAngles().y; +#endif AngleVectors( vecAngle, &vecVelocity ); @@ -2573,7 +2822,11 @@ Vector CProtoSniper::LeadTarget( CBaseEntity *pTarget ) { Vector vecBulletOrigin; vecBulletOrigin = GetBulletOrigin(); +#ifdef MAPBASE + CPVSFilter filter( GetAbsOrigin() ); +#else CPVSFilter filter( GetLocalOrigin() ); +#endif te->ShowLine( filter, 0.0, &vecBulletOrigin, &vecAdjustedShot ); } @@ -2772,7 +3025,11 @@ bool CProtoSniper::FVisible( CBaseEntity *pEntity, int traceMask, CBaseEntity ** vecVerticalOffset = SNIPER_TARGET_VERTICAL_OFFSET; } +#ifdef MAPBASE + AngleVectors( pEntity->GetAbsAngles(), NULL, &vecRight, NULL ); +#else AngleVectors( pEntity->GetLocalAngles(), NULL, &vecRight, NULL ); +#endif vecEye = vecRight * SNIPER_EYE_DIST - vecVerticalOffset; UTIL_TraceLine( EyePosition(), pEntity->EyePosition() + vecEye, MASK_BLOCKLOS, this, COLLISION_GROUP_NONE, &tr ); @@ -2901,6 +3158,52 @@ void CProtoSniper::NotifyShotMissedTarget() // in these NPCs' walk and run animations. } +#ifdef EXPANDED_RESPONSE_SYSTEM_USAGE +//----------------------------------------------------------------------------- +// Purpose: Speak concept +//----------------------------------------------------------------------------- +bool CProtoSniper::SpeakIfAllowed(const char *concept, const char *modifiers) +{ + if (!GetExpresser()->CanSpeakConcept(concept)) + return false; + + return Speak(concept, modifiers); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CProtoSniper::ModifyOrAppendCriteria( AI_CriteriaSet& set ) +{ + BaseClass::ModifyOrAppendCriteria( set ); + + // We still need this + set.AppendCriteria( "sniperspeak", UTIL_VarArgs("%i", sniperspeak.GetInt()) ); + set.AppendCriteria( "playerally", UTIL_VarArgs("%d", IsPlayerAllySniper()) ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CAI_Expresser *CProtoSniper::CreateExpresser( void ) +{ + m_pExpresser = new CAI_Expresser(this); + if (!m_pExpresser) + return NULL; + + m_pExpresser->Connect(this); + return m_pExpresser; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CProtoSniper::PostConstructor(const char *szClassname) +{ + BaseClass::PostConstructor(szClassname); + CreateExpresser(); +} +#endif + //----------------------------------------------------------------------------- // // Schedules @@ -2916,6 +3219,9 @@ AI_BEGIN_CUSTOM_NPC( proto_sniper, CProtoSniper ) DECLARE_CONDITION( COND_SNIPER_FRUSTRATED ); DECLARE_CONDITION( COND_SNIPER_SWEEP_TARGET ); DECLARE_CONDITION( COND_SNIPER_NO_SHOT ); +#ifdef MAPBASE + DECLARE_CONDITION( COND_SNIPER_KILLED_ENEMY ); +#endif DECLARE_TASK( TASK_SNIPER_FRUSTRATED_ATTACK ); DECLARE_TASK( TASK_SNIPER_PAINT_ENEMY ); @@ -3141,6 +3447,18 @@ AI_BEGIN_CUSTOM_NPC( proto_sniper, CProtoSniper ) //========================================================= //========================================================= +#ifdef MAPBASE + DEFINE_SCHEDULE + ( + SCHED_PSNIPER_PLAYER_DEAD, + + " Tasks" + " TASK_SNIPER_PLAYER_DEAD 0" + " " + " Interrupts" + " COND_SEE_PLAYER" + ) +#else DEFINE_SCHEDULE ( SCHED_PSNIPER_PLAYER_DEAD, @@ -3150,6 +3468,7 @@ AI_BEGIN_CUSTOM_NPC( proto_sniper, CProtoSniper ) " " " Interrupts" ) +#endif AI_END_CUSTOM_NPC() diff --git a/src/game/server/hl2/script_intro.cpp b/src/game/server/hl2/script_intro.cpp index 2798af2c..6ac28e10 100644 --- a/src/game/server/hl2/script_intro.cpp +++ b/src/game/server/hl2/script_intro.cpp @@ -46,6 +46,13 @@ BEGIN_DATADESC(CScriptIntro) DEFINE_KEYFIELD( m_bAlternateFOV, FIELD_BOOLEAN, "alternatefovchange" ), +#ifdef MAPBASE + DEFINE_KEYFIELD( m_bDrawSky, FIELD_BOOLEAN, "DrawSky" ), + DEFINE_KEYFIELD( m_bDrawSky2, FIELD_BOOLEAN, "DrawSky2" ), + + DEFINE_KEYFIELD( m_bUseEyePosition, FIELD_BOOLEAN, "UseEyePosition" ), +#endif + // Inputs DEFINE_INPUTFUNC(FIELD_STRING, "SetCameraViewEntity", InputSetCameraViewEntity ), DEFINE_INPUTFUNC(FIELD_INTEGER, "SetBlendMode", InputSetBlendMode ), @@ -58,6 +65,10 @@ BEGIN_DATADESC(CScriptIntro) DEFINE_INPUTFUNC(FIELD_VOID, "Deactivate", InputDeactivate ), DEFINE_INPUTFUNC(FIELD_STRING, "FadeTo", InputFadeTo ), DEFINE_INPUTFUNC(FIELD_STRING, "SetFadeColor", InputSetFadeColor ), +#ifdef MAPBASE + DEFINE_INPUTFUNC(FIELD_BOOLEAN, "SetDrawSky", InputSetDrawSky ), + DEFINE_INPUTFUNC(FIELD_BOOLEAN, "SetDrawSky2", InputSetDrawSky2 ), +#endif DEFINE_THINKFUNC( BlendComplete ), @@ -71,7 +82,11 @@ IMPLEMENT_SERVERCLASS_ST( CScriptIntro, DT_ScriptIntro ) SendPropFloat( SENDINFO( m_flNextBlendTime ), 10 ), SendPropFloat( SENDINFO( m_flBlendStartTime ), 10 ), SendPropBool( SENDINFO( m_bActive ) ), - +#ifdef MAPBASE + SendPropBool( SENDINFO( m_bDrawSky ) ), + SendPropBool( SENDINFO( m_bDrawSky2 ) ), + SendPropBool( SENDINFO( m_bUseEyePosition ) ), +#endif // Fov & fov blends SendPropInt( SENDINFO( m_iFOV ), 9 ), @@ -394,3 +409,256 @@ void CScriptIntro::InputSetFadeColor( inputdata_t &inputdata ) m_flFadeColor.Set( 1, flG ); m_flFadeColor.Set( 2, flB ); } + +#ifdef MAPBASE +class CPlayerViewProxy : public CBaseEntity +{ +public: + DECLARE_CLASS( CPlayerViewProxy, CBaseEntity ); + DECLARE_DATADESC(); + DECLARE_SERVERCLASS(); + + CPlayerViewProxy(); + + void Spawn( void ); + int UpdateTransmitState( void ); + + void ActivateEnt( CBaseEntity *pActivator = NULL, CBaseEntity *pCaller = NULL ); + void DeactivateEnt(); + void MeasureThink(); + + Vector EyePosition( void ); // position of eyes + const QAngle &EyeAngles( void ); // Direction of eyes in world space + const QAngle &LocalEyeAngles( void ); // Direction of eyes + Vector EarPosition( void ); // position of ears + + // Inputs + void InputActivate( inputdata_t &inputdata ); + void InputDeactivate( inputdata_t &inputdata ); + +#ifdef MAPBASE_MP + // TODO: Mapbase MP should use reception filter or something to determine which player's eye position is offset + CNetworkVar( CHandle, m_hPlayer ); +#else + CHandle m_hPlayer; +#endif + + string_t m_iszMeasureReference; + EHANDLE m_hMeasureReference; + string_t m_iszTargetReference; + EHANDLE m_hTargetReference; + + CNetworkVar( float, m_flScale ); + + CNetworkVar( bool, m_bEnabled ); +}; + +LINK_ENTITY_TO_CLASS( info_player_view_proxy, CPlayerViewProxy ); + +BEGIN_DATADESC( CPlayerViewProxy ) + + // Keys + DEFINE_FIELD( m_hPlayer, FIELD_EHANDLE ), + DEFINE_KEYFIELD( m_iszMeasureReference, FIELD_STRING, "MeasureReference" ), + DEFINE_FIELD( m_hMeasureReference, FIELD_EHANDLE ), + DEFINE_KEYFIELD( m_iszTargetReference, FIELD_STRING, "TargetReference" ), + DEFINE_FIELD( m_hTargetReference, FIELD_EHANDLE ), + DEFINE_KEYFIELD( m_flScale, FIELD_FLOAT, "TargetScale" ), + DEFINE_KEYFIELD( m_bEnabled, FIELD_BOOLEAN, "Enabled" ), + + // Inputs + DEFINE_INPUTFUNC( FIELD_VOID, "Activate", InputActivate ), + DEFINE_INPUTFUNC( FIELD_VOID, "Deactivate", InputDeactivate ), + + DEFINE_THINKFUNC( MeasureThink ), + +END_DATADESC() + +IMPLEMENT_SERVERCLASS_ST( CPlayerViewProxy, DT_PlayerViewProxy ) +#ifdef MAPBASE_MP + SendPropEHandle( SENDINFO( m_hPlayer ) ), +#endif + SendPropBool( SENDINFO( m_bEnabled ) ), +END_SEND_TABLE() + +CPlayerViewProxy::CPlayerViewProxy() +{ + m_flScale = 1.0f; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPlayerViewProxy::Spawn( void ) +{ + if (m_bEnabled) + ActivateEnt(); +} + +//------------------------------------------------------------------------------ +// Purpose : Send even though we don't have a model. +//------------------------------------------------------------------------------ +int CPlayerViewProxy::UpdateTransmitState() +{ + return SetTransmitState( FL_EDICT_ALWAYS ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPlayerViewProxy::MeasureThink( void ) +{ + if (m_hPlayer.Get() == NULL) + { + // Player has disappeared! Stopping measure + return; + } + + if (m_bEnabled && m_hMeasureReference.Get() && m_hTargetReference.Get()) + { + matrix3x4_t matRefToMeasure, matWorldToMeasure; + MatrixInvert( m_hPlayer.Get()->EntityToWorldTransform(), matWorldToMeasure ); + ConcatTransforms( matWorldToMeasure, m_hMeasureReference.Get()->EntityToWorldTransform(), matRefToMeasure ); + + // Apply the scale factor + if ( ( m_flScale != 0.0f ) && ( m_flScale != 1.0f ) ) + { + Vector vecTranslation; + MatrixGetColumn( matRefToMeasure, 3, vecTranslation ); + vecTranslation /= m_flScale; + MatrixSetColumn( vecTranslation, 3, matRefToMeasure ); + } + + // Now apply the new matrix to the new reference point + matrix3x4_t matMeasureToRef, matNewTargetToWorld; + MatrixInvert( matRefToMeasure, matMeasureToRef ); + ConcatTransforms( m_hTargetReference.Get()->EntityToWorldTransform(), matMeasureToRef, matNewTargetToWorld ); + + Vector vecOrigin; + QAngle angAngles; + MatrixAngles( matNewTargetToWorld, angAngles, vecOrigin ); + Teleport( &vecOrigin, &angAngles, NULL ); + + SetNextThink( gpGlobals->curtime + TICK_INTERVAL ); + } + //else + //{ + // SetAbsOrigin( m_hPlayer.Get()->GetAbsOrigin() ); + // SetAbsAngles( m_hPlayer.Get()->GetAbsAngles() ); + //} +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +Vector CPlayerViewProxy::EyePosition( void ) +{ + if (m_hPlayer.Get()) + { + //Vector vecPlayerOffset = m_hPlayer.Get()->EyePosition() - m_hPlayer.Get()->GetAbsOrigin(); + //return GetAbsOrigin() + vecPlayerOffset; + + Vector vecOrigin; + QAngle angAngles; + float fldummy; + m_hPlayer->CalcView( vecOrigin, angAngles, fldummy, fldummy, fldummy ); + + return GetAbsOrigin() + (vecOrigin - m_hPlayer->GetAbsOrigin()); + } + else + return BaseClass::EyePosition(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +const QAngle &CPlayerViewProxy::EyeAngles( void ) +{ + if (m_hPlayer.Get()) + { + Vector vecOrigin; + static QAngle angAngles; + float fldummy; + m_hPlayer->CalcView( vecOrigin, angAngles, fldummy, fldummy, fldummy ); + + angAngles = GetAbsAngles() + (angAngles - m_hPlayer->GetAbsAngles()); + return angAngles; + + //return m_hPlayer.Get()->EyeAngles(); + } + else + return BaseClass::EyeAngles(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +const QAngle &CPlayerViewProxy::LocalEyeAngles( void ) +{ + if (m_hPlayer.Get()) { + static QAngle angAngles = GetAbsAngles() + (m_hPlayer->LocalEyeAngles() - m_hPlayer->GetAbsAngles()); + return angAngles; + } else + return BaseClass::LocalEyeAngles(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +Vector CPlayerViewProxy::EarPosition( void ) +{ + if (m_hPlayer.Get()) + { + Vector vecPlayerOffset = m_hPlayer.Get()->EarPosition() - m_hPlayer.Get()->GetAbsOrigin(); + return GetAbsOrigin() + vecPlayerOffset; + } + else + return BaseClass::EarPosition(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPlayerViewProxy::ActivateEnt( CBaseEntity *pActivator, CBaseEntity *pCaller ) +{ + m_bEnabled = true; + + m_hMeasureReference = gEntList.FindEntityByName( NULL, m_iszMeasureReference, this, pActivator, pCaller ); + m_hTargetReference = gEntList.FindEntityByName( NULL, m_iszTargetReference, this, pActivator, pCaller ); + + // Do something else in Mapbase MP + m_hPlayer = UTIL_GetLocalPlayer(); + + SetThink( &CPlayerViewProxy::MeasureThink ); + SetNextThink( gpGlobals->curtime + TICK_INTERVAL ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPlayerViewProxy::DeactivateEnt( void ) +{ + m_bEnabled = false; + + m_hMeasureReference = NULL; + m_hTargetReference = NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : &inputdata - +//----------------------------------------------------------------------------- +void CPlayerViewProxy::InputActivate( inputdata_t &inputdata ) +{ + ActivateEnt(inputdata.pActivator, inputdata.pCaller); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : &inputdata - +//----------------------------------------------------------------------------- +void CPlayerViewProxy::InputDeactivate( inputdata_t &inputdata ) +{ + DeactivateEnt(); +} +#endif diff --git a/src/game/server/hl2/script_intro.h b/src/game/server/hl2/script_intro.h index 5e59fa33..552c6188 100644 --- a/src/game/server/hl2/script_intro.h +++ b/src/game/server/hl2/script_intro.h @@ -44,6 +44,11 @@ public: void InputFadeTo( inputdata_t &inputdata ); void InputSetFadeColor( inputdata_t &inputdata ); +#ifdef MAPBASE + void InputSetDrawSky( inputdata_t &inputdata ) { m_bDrawSky = inputdata.value.Bool(); } + void InputSetDrawSky2( inputdata_t &inputdata ) { m_bDrawSky2 = inputdata.value.Bool(); } +#endif + bool GetIncludedPVSOrigin( Vector *pOrigin, CBaseEntity **ppCamera ); private: @@ -61,6 +66,11 @@ private: CNetworkVar( float, m_flBlendStartTime ); CNetworkVar( int, m_iStartFOV ); CNetworkVar( bool, m_bActive ); +#ifdef MAPBASE + CNetworkVar( bool, m_bDrawSky ); + CNetworkVar( bool, m_bDrawSky2 ); + CNetworkVar( bool, m_bUseEyePosition ); +#endif // Fov & fov blends CNetworkVar( int, m_iNextFOV ); diff --git a/src/game/server/hl2/vehicle_airboat.cpp b/src/game/server/hl2/vehicle_airboat.cpp index f29931c0..741d8319 100644 --- a/src/game/server/hl2/vehicle_airboat.cpp +++ b/src/game/server/hl2/vehicle_airboat.cpp @@ -1573,6 +1573,8 @@ void CPropAirboat::FireGun( ) Vector vecForward; GetAttachment( m_nGunBarrelAttachment, vecGunPosition, &vecForward ); + CDisablePredictionFiltering disabler; + // NOTE: For the airboat, unable to fire really means the aim is clamped Vector vecAimPoint; if ( !m_bUnableToFire ) diff --git a/src/game/server/hl2/vehicle_apc.cpp b/src/game/server/hl2/vehicle_apc.cpp index 80c4291f..63d5e3ed 100644 --- a/src/game/server/hl2/vehicle_apc.cpp +++ b/src/game/server/hl2/vehicle_apc.cpp @@ -561,9 +561,15 @@ int CPropAPC::OnTakeDamage( const CTakeDamageInfo &info ) m_iHealth -= dmgInfo.GetDamage(); if ( m_iHealth <= 0 ) { - m_iHealth = 0; - Event_Killed( dmgInfo ); - return 0; +#ifdef MAPBASE_VSCRIPT + // False = Cheat death + if (ScriptDeathHook( const_cast(&info) ) != false) +#endif + { + m_iHealth = 0; + Event_Killed( dmgInfo ); + return 0; + } } // Chain diff --git a/src/game/server/hl2/vehicle_jeep.cpp b/src/game/server/hl2/vehicle_jeep.cpp index 80eb1368..0b47f3cb 100644 --- a/src/game/server/hl2/vehicle_jeep.cpp +++ b/src/game/server/hl2/vehicle_jeep.cpp @@ -135,6 +135,10 @@ BEGIN_DATADESC( CPropJeep ) DEFINE_INPUTFUNC( FIELD_VOID, "ShowHudHint", InputShowHudHint ), DEFINE_INPUTFUNC( FIELD_VOID, "StartRemoveTauCannon", InputStartRemoveTauCannon ), DEFINE_INPUTFUNC( FIELD_VOID, "FinishRemoveTauCannon", InputFinishRemoveTauCannon ), +#ifdef MAPBASE + DEFINE_INPUTFUNC( FIELD_VOID, "DisablePhysGun", InputDisablePhysGun ), + DEFINE_INPUTFUNC( FIELD_VOID, "EnablePhysGun", InputEnablePhysGun ), +#endif DEFINE_THINKFUNC( JeepSeagullThink ), END_DATADESC() @@ -148,6 +152,11 @@ END_SEND_TABLE(); LINK_ENTITY_TO_CLASS( prop_vehicle_jeep, CPropJeep ); #endif +#ifdef MAPBASE +// Shortcut to old jeep for those who want to use the scout car in Episodic +LINK_ENTITY_TO_CLASS( prop_vehicle_jeep_old, CPropJeep ); +#endif + //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- @@ -898,6 +907,8 @@ void CPropJeep::FireCannon( void ) if ( m_bUnableToFire ) return; + CDisablePredictionFiltering disabler; + m_flCannonTime = gpGlobals->curtime + 0.2f; m_bCannonCharging = false; @@ -936,6 +947,8 @@ void CPropJeep::FireCannon( void ) //----------------------------------------------------------------------------- void CPropJeep::FireChargedCannon( void ) { + CDisablePredictionFiltering disabler; + bool penetrated = false; m_bCannonCharging = false; @@ -1674,6 +1687,23 @@ void CPropJeep::InputFinishRemoveTauCannon( inputdata_t &inputdata ) m_bHasGun = false; } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: Stop players punting the car around. +//----------------------------------------------------------------------------- +void CPropJeep::InputDisablePhysGun( inputdata_t &data ) +{ + AddEFlags( EFL_NO_PHYSCANNON_INTERACTION ); +} +//----------------------------------------------------------------------------- +// Purpose: Return to normal +//----------------------------------------------------------------------------- +void CPropJeep::InputEnablePhysGun( inputdata_t &data ) +{ + RemoveEFlags( EFL_NO_PHYSCANNON_INTERACTION ); +} +#endif + //======================================================================================================================================== // JEEP FOUR WHEEL PHYSICS VEHICLE SERVER VEHICLE //======================================================================================================================================== diff --git a/src/game/server/hl2/vehicle_jeep.h b/src/game/server/hl2/vehicle_jeep.h index ba08c2c4..982ee6db 100644 --- a/src/game/server/hl2/vehicle_jeep.h +++ b/src/game/server/hl2/vehicle_jeep.h @@ -118,6 +118,10 @@ private: void InputShowHudHint( inputdata_t &inputdata ); void InputStartRemoveTauCannon( inputdata_t &inputdata ); void InputFinishRemoveTauCannon( inputdata_t &inputdata ); +#ifdef MAPBASE + void InputDisablePhysGun( inputdata_t &data ); + void InputEnablePhysGun( inputdata_t &data ); +#endif protected: diff --git a/src/game/server/hl2/weapon_357.cpp b/src/game/server/hl2/weapon_357.cpp index 2b3a3a12..a3c05371 100644 --- a/src/game/server/hl2/weapon_357.cpp +++ b/src/game/server/hl2/weapon_357.cpp @@ -27,6 +27,11 @@ // CWeapon357 //----------------------------------------------------------------------------- +#ifdef MAPBASE +extern acttable_t *GetPistolActtable(); +extern int GetPistolActtableCount(); +#endif + class CWeapon357 : public CBaseHLCombatWeapon { DECLARE_CLASS( CWeapon357, CBaseHLCombatWeapon ); @@ -39,8 +44,46 @@ public: float WeaponAutoAimScale() { return 0.6f; } +#ifdef MAPBASE + int CapabilitiesGet( void ) { return bits_CAP_WEAPON_RANGE_ATTACK1; } + + virtual int GetMinBurst() { return 1; } + virtual int GetMaxBurst() { return 1; } + virtual float GetMinRestTime( void ) { return 1.0f; } + virtual float GetMaxRestTime( void ) { return 2.5f; } + + virtual float GetFireRate( void ) { return 1.0f; } + + virtual const Vector& GetBulletSpread( void ) + { + static Vector cone = VECTOR_CONE_15DEGREES; + if (!GetOwner() || !GetOwner()->IsNPC()) + return cone; + + static Vector AllyCone = VECTOR_CONE_2DEGREES; + static Vector NPCCone = VECTOR_CONE_5DEGREES; + + if( GetOwner()->MyNPCPointer()->IsPlayerAlly() ) + { + // 357 allies should be cooler + return AllyCone; + } + + return NPCCone; + } + + void FireNPCPrimaryAttack( CBaseCombatCharacter *pOperator, Vector &vecShootOrigin, Vector &vecShootDir ); + void Operator_ForceNPCFire( CBaseCombatCharacter *pOperator, bool bSecondary ); + + virtual acttable_t *GetBackupActivityList() { return GetPistolActtable(); } + virtual int GetBackupActivityListCount() { return GetPistolActtableCount(); } +#endif + DECLARE_SERVERCLASS(); DECLARE_DATADESC(); +#ifdef MAPBASE + DECLARE_ACTTABLE(); +#endif }; LINK_ENTITY_TO_CLASS( weapon_357, CWeapon357 ); @@ -53,6 +96,148 @@ END_SEND_TABLE() BEGIN_DATADESC( CWeapon357 ) END_DATADESC() +#ifdef MAPBASE +acttable_t CWeapon357::m_acttable[] = +{ +#if EXPANDED_HL2_WEAPON_ACTIVITIES + { ACT_IDLE, ACT_IDLE_REVOLVER, true }, + { ACT_IDLE_ANGRY, ACT_IDLE_ANGRY_REVOLVER, true }, + { ACT_RANGE_ATTACK1, ACT_RANGE_ATTACK_REVOLVER, true }, + { ACT_RELOAD, ACT_RELOAD_REVOLVER, true }, + { ACT_WALK_AIM, ACT_WALK_AIM_REVOLVER, true }, + { ACT_RUN_AIM, ACT_RUN_AIM_REVOLVER, true }, + { ACT_GESTURE_RANGE_ATTACK1, ACT_GESTURE_RANGE_ATTACK_REVOLVER, true }, + { ACT_RELOAD_LOW, ACT_RELOAD_REVOLVER_LOW, false }, + { ACT_RANGE_ATTACK1_LOW, ACT_RANGE_ATTACK_REVOLVER_LOW, false }, + { ACT_COVER_LOW, ACT_COVER_REVOLVER_LOW, false }, + { ACT_RANGE_AIM_LOW, ACT_RANGE_AIM_REVOLVER_LOW, false }, + { ACT_GESTURE_RELOAD, ACT_GESTURE_RELOAD_REVOLVER, false }, + { ACT_WALK, ACT_WALK_REVOLVER, true }, + { ACT_RUN, ACT_RUN_REVOLVER, true }, +#else + { ACT_IDLE, ACT_IDLE_PISTOL, true }, + { ACT_IDLE_ANGRY, ACT_IDLE_ANGRY_PISTOL, true }, + { ACT_RANGE_ATTACK1, ACT_RANGE_ATTACK_PISTOL, true }, + { ACT_RELOAD, ACT_RELOAD_PISTOL, true }, + { ACT_WALK_AIM, ACT_WALK_AIM_PISTOL, true }, + { ACT_RUN_AIM, ACT_RUN_AIM_PISTOL, true }, + { ACT_GESTURE_RANGE_ATTACK1, ACT_GESTURE_RANGE_ATTACK_PISTOL,true }, + { ACT_RELOAD_LOW, ACT_RELOAD_PISTOL_LOW, false }, + { ACT_RANGE_ATTACK1_LOW, ACT_RANGE_ATTACK_PISTOL_LOW, false }, + { ACT_COVER_LOW, ACT_COVER_PISTOL_LOW, false }, + { ACT_RANGE_AIM_LOW, ACT_RANGE_AIM_PISTOL_LOW, false }, + { ACT_GESTURE_RELOAD, ACT_GESTURE_RELOAD_PISTOL, false }, + { ACT_WALK, ACT_WALK_PISTOL, false }, + { ACT_RUN, ACT_RUN_PISTOL, false }, +#endif + + // + // Activities ported from weapon_alyxgun below + // + + // Readiness activities (not aiming) +#if EXPANDED_HL2_WEAPON_ACTIVITIES + { ACT_IDLE_RELAXED, ACT_IDLE_PISTOL_RELAXED, false },//never aims + { ACT_IDLE_STIMULATED, ACT_IDLE_PISTOL_STIMULATED, false }, +#else + { ACT_IDLE_RELAXED, ACT_IDLE_PISTOL, false },//never aims + { ACT_IDLE_STIMULATED, ACT_IDLE_STIMULATED, false }, +#endif + { ACT_IDLE_AGITATED, ACT_IDLE_ANGRY_PISTOL, false },//always aims + { ACT_IDLE_STEALTH, ACT_IDLE_STEALTH_PISTOL, false }, + +#if EXPANDED_HL2_WEAPON_ACTIVITIES + { ACT_WALK_RELAXED, ACT_WALK_PISTOL_RELAXED, false },//never aims + { ACT_WALK_STIMULATED, ACT_WALK_PISTOL_STIMULATED, false }, +#else + { ACT_WALK_RELAXED, ACT_WALK, false },//never aims + { ACT_WALK_STIMULATED, ACT_WALK_STIMULATED, false }, +#endif + { ACT_WALK_AGITATED, ACT_WALK_AIM_PISTOL, false },//always aims + { ACT_WALK_STEALTH, ACT_WALK_STEALTH_PISTOL, false }, + +#if EXPANDED_HL2_WEAPON_ACTIVITIES + { ACT_RUN_RELAXED, ACT_RUN_PISTOL_RELAXED, false },//never aims + { ACT_RUN_STIMULATED, ACT_RUN_PISTOL_STIMULATED, false }, +#else + { ACT_RUN_RELAXED, ACT_RUN, false },//never aims + { ACT_RUN_STIMULATED, ACT_RUN_STIMULATED, false }, +#endif + { ACT_RUN_AGITATED, ACT_RUN_AIM_PISTOL, false },//always aims + { ACT_RUN_STEALTH, ACT_RUN_STEALTH_PISTOL, false }, + + // Readiness activities (aiming) + { ACT_IDLE_AIM_RELAXED, ACT_IDLE_PISTOL, false },//never aims + { ACT_IDLE_AIM_STIMULATED, ACT_IDLE_ANGRY_PISTOL, false }, + { ACT_IDLE_AIM_AGITATED, ACT_IDLE_ANGRY_PISTOL, false },//always aims + { ACT_IDLE_AIM_STEALTH, ACT_IDLE_STEALTH_PISTOL, false }, + + { ACT_WALK_AIM_RELAXED, ACT_WALK, false },//never aims + { ACT_WALK_AIM_STIMULATED, ACT_WALK_AIM_PISTOL, false }, + { ACT_WALK_AIM_AGITATED, ACT_WALK_AIM_PISTOL, false },//always aims + { ACT_WALK_AIM_STEALTH, ACT_WALK_AIM_STEALTH_PISTOL, false },//always aims + + { ACT_RUN_AIM_RELAXED, ACT_RUN, false },//never aims + { ACT_RUN_AIM_STIMULATED, ACT_RUN_AIM_PISTOL, false }, + { ACT_RUN_AIM_AGITATED, ACT_RUN_AIM_PISTOL, false },//always aims + { ACT_RUN_AIM_STEALTH, ACT_RUN_AIM_STEALTH_PISTOL, false },//always aims + //End readiness activities + + // Crouch activities + { ACT_CROUCHIDLE_STIMULATED, ACT_CROUCHIDLE_STIMULATED, false }, + { ACT_CROUCHIDLE_AIM_STIMULATED,ACT_RANGE_AIM_PISTOL_LOW, false },//always aims + { ACT_CROUCHIDLE_AGITATED, ACT_RANGE_AIM_PISTOL_LOW, false },//always aims + + // Readiness translations + { ACT_READINESS_RELAXED_TO_STIMULATED, ACT_READINESS_PISTOL_RELAXED_TO_STIMULATED, false }, + { ACT_READINESS_RELAXED_TO_STIMULATED_WALK, ACT_READINESS_PISTOL_RELAXED_TO_STIMULATED_WALK, false }, + { ACT_READINESS_AGITATED_TO_STIMULATED, ACT_READINESS_PISTOL_AGITATED_TO_STIMULATED, false }, + { ACT_READINESS_STIMULATED_TO_RELAXED, ACT_READINESS_PISTOL_STIMULATED_TO_RELAXED, false }, + +#if EXPANDED_HL2_COVER_ACTIVITIES + { ACT_RANGE_AIM_MED, ACT_RANGE_AIM_REVOLVER_MED, false }, + { ACT_RANGE_ATTACK1_MED, ACT_RANGE_ATTACK_REVOLVER_MED, false }, +#endif + +#ifdef MAPBASE + // HL2:DM activities (for third-person animations in SP) +#if EXPANDED_HL2DM_ACTIVITIES + { ACT_HL2MP_IDLE, ACT_HL2MP_IDLE_REVOLVER, false }, + { ACT_HL2MP_RUN, ACT_HL2MP_RUN_REVOLVER, false }, + { ACT_HL2MP_WALK, ACT_HL2MP_WALK_REVOLVER, false }, + { ACT_HL2MP_IDLE_CROUCH, ACT_HL2MP_IDLE_CROUCH_REVOLVER, false }, + { ACT_HL2MP_WALK_CROUCH, ACT_HL2MP_WALK_CROUCH_REVOLVER, false }, + { ACT_HL2MP_GESTURE_RANGE_ATTACK, ACT_HL2MP_GESTURE_RANGE_ATTACK_REVOLVER, false }, + { ACT_HL2MP_GESTURE_RANGE_ATTACK2, ACT_HL2MP_GESTURE_RANGE_ATTACK2_REVOLVER, false }, + { ACT_HL2MP_GESTURE_RELOAD, ACT_HL2MP_GESTURE_RELOAD_REVOLVER, false }, + { ACT_HL2MP_JUMP, ACT_HL2MP_JUMP_REVOLVER, false }, +#else + { ACT_HL2MP_IDLE, ACT_HL2MP_IDLE_PISTOL, false }, + { ACT_HL2MP_RUN, ACT_HL2MP_RUN_PISTOL, false }, + { ACT_HL2MP_IDLE_CROUCH, ACT_HL2MP_IDLE_CROUCH_PISTOL, false }, + { ACT_HL2MP_WALK_CROUCH, ACT_HL2MP_WALK_CROUCH_PISTOL, false }, + { ACT_HL2MP_GESTURE_RANGE_ATTACK, ACT_HL2MP_GESTURE_RANGE_ATTACK_PISTOL, false }, + { ACT_HL2MP_GESTURE_RELOAD, ACT_HL2MP_GESTURE_RELOAD_PISTOL, false }, + { ACT_HL2MP_JUMP, ACT_HL2MP_JUMP_PISTOL, false }, +#endif +#endif +}; + + +IMPLEMENT_ACTTABLE( CWeapon357 ); + +// Allows Weapon_BackupActivity() to access the 357's activity table. +acttable_t *Get357Acttable() +{ + return CWeapon357::m_acttable; +} + +int Get357ActtableCount() +{ + return ARRAYSIZE(CWeapon357::m_acttable); +} +#endif + //----------------------------------------------------------------------------- // Purpose: Constructor //----------------------------------------------------------------------------- @@ -60,6 +245,13 @@ CWeapon357::CWeapon357( void ) { m_bReloadsSingly = false; m_bFiresUnderwater = false; + +#ifdef MAPBASE + m_fMinRange1 = 24; + m_fMaxRange1 = 1000; + m_fMinRange2 = 24; + m_fMaxRange2 = 200; +#endif } //----------------------------------------------------------------------------- @@ -87,9 +279,57 @@ void CWeapon357::Operator_HandleAnimEvent( animevent_t *pEvent, CBaseCombatChara break; } +#ifdef MAPBASE + case EVENT_WEAPON_PISTOL_FIRE: + { + Vector vecShootOrigin, vecShootDir; + vecShootOrigin = pOperator->Weapon_ShootPosition(); + + CAI_BaseNPC *npc = pOperator->MyNPCPointer(); + ASSERT( npc != NULL ); + + vecShootDir = npc->GetActualShootTrajectory( vecShootOrigin ); + + FireNPCPrimaryAttack( pOperator, vecShootOrigin, vecShootDir ); + } + break; + default: + BaseClass::Operator_HandleAnimEvent( pEvent, pOperator ); + break; +#endif } } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CWeapon357::FireNPCPrimaryAttack( CBaseCombatCharacter *pOperator, Vector &vecShootOrigin, Vector &vecShootDir ) +{ + CSoundEnt::InsertSound( SOUND_COMBAT|SOUND_CONTEXT_GUNFIRE, pOperator->GetAbsOrigin(), SOUNDENT_VOLUME_PISTOL, 0.2, pOperator, SOUNDENT_CHANNEL_WEAPON, pOperator->GetEnemy() ); + + WeaponSound( SINGLE_NPC ); + pOperator->FireBullets( 1, vecShootOrigin, vecShootDir, VECTOR_CONE_PRECALCULATED, MAX_TRACE_LENGTH, m_iPrimaryAmmoType, 1 ); + pOperator->DoMuzzleFlash(); + m_iClip1 = m_iClip1 - 1; +} + +//----------------------------------------------------------------------------- +// Purpose: Some things need this. (e.g. the new Force(X)Fire inputs or blindfire actbusy) +//----------------------------------------------------------------------------- +void CWeapon357::Operator_ForceNPCFire( CBaseCombatCharacter *pOperator, bool bSecondary ) +{ + // Ensure we have enough rounds in the clip + m_iClip1++; + + Vector vecShootOrigin, vecShootDir; + QAngle angShootDir; + GetAttachment( LookupAttachment( "muzzle" ), vecShootOrigin, angShootDir ); + AngleVectors( angShootDir, &vecShootDir ); + FireNPCPrimaryAttack( pOperator, vecShootOrigin, vecShootDir ); +} +#endif + //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- diff --git a/src/game/server/hl2/weapon_alyxgun.cpp b/src/game/server/hl2/weapon_alyxgun.cpp index 270a503c..5bf20eb6 100644 --- a/src/game/server/hl2/weapon_alyxgun.cpp +++ b/src/game/server/hl2/weapon_alyxgun.cpp @@ -37,20 +37,40 @@ acttable_t CWeaponAlyxGun::m_acttable[] = { ACT_RELOAD_LOW, ACT_RELOAD_PISTOL_LOW, true }, { ACT_RANGE_ATTACK1_LOW, ACT_RANGE_ATTACK_PISTOL_LOW, true }, { ACT_GESTURE_RELOAD, ACT_GESTURE_RELOAD_PISTOL, true }, +#ifdef MAPBASE + // For non-Alyx NPCs + { ACT_WALK, ACT_WALK_PISTOL, false }, + { ACT_RUN, ACT_RUN_PISTOL, false }, +#endif // Readiness activities (not aiming) +#if EXPANDED_HL2_WEAPON_ACTIVITIES + { ACT_IDLE_RELAXED, ACT_IDLE_PISTOL_RELAXED, false },//never aims + { ACT_IDLE_STIMULATED, ACT_IDLE_PISTOL_STIMULATED, false }, +#else { ACT_IDLE_RELAXED, ACT_IDLE_PISTOL, false },//never aims { ACT_IDLE_STIMULATED, ACT_IDLE_STIMULATED, false }, +#endif { ACT_IDLE_AGITATED, ACT_IDLE_ANGRY_PISTOL, false },//always aims { ACT_IDLE_STEALTH, ACT_IDLE_STEALTH_PISTOL, false }, +#if EXPANDED_HL2_WEAPON_ACTIVITIES + { ACT_WALK_RELAXED, ACT_WALK_PISTOL_RELAXED, false },//never aims + { ACT_WALK_STIMULATED, ACT_WALK_PISTOL_STIMULATED, false }, +#else { ACT_WALK_RELAXED, ACT_WALK, false },//never aims { ACT_WALK_STIMULATED, ACT_WALK_STIMULATED, false }, +#endif { ACT_WALK_AGITATED, ACT_WALK_AIM_PISTOL, false },//always aims { ACT_WALK_STEALTH, ACT_WALK_STEALTH_PISTOL, false }, - + +#if EXPANDED_HL2_WEAPON_ACTIVITIES + { ACT_RUN_RELAXED, ACT_RUN_PISTOL_RELAXED, false },//never aims + { ACT_RUN_STIMULATED, ACT_RUN_PISTOL_STIMULATED, false }, +#else { ACT_RUN_RELAXED, ACT_RUN, false },//never aims { ACT_RUN_STIMULATED, ACT_RUN_STIMULATED, false }, +#endif { ACT_RUN_AGITATED, ACT_RUN_AIM_PISTOL, false },//always aims { ACT_RUN_STEALTH, ACT_RUN_STEALTH_PISTOL, false }, @@ -85,6 +105,21 @@ acttable_t CWeaponAlyxGun::m_acttable[] = // { ACT_ARM, ACT_ARM_PISTOL, true }, // { ACT_DISARM, ACT_DISARM_PISTOL, true }, + +#ifdef MAPBASE + // HL2:DM activities (for third-person animations in SP) + { ACT_HL2MP_IDLE, ACT_HL2MP_IDLE_PISTOL, false }, + { ACT_HL2MP_RUN, ACT_HL2MP_RUN_PISTOL, false }, + { ACT_HL2MP_IDLE_CROUCH, ACT_HL2MP_IDLE_CROUCH_PISTOL, false }, + { ACT_HL2MP_WALK_CROUCH, ACT_HL2MP_WALK_CROUCH_PISTOL, false }, + { ACT_HL2MP_GESTURE_RANGE_ATTACK, ACT_HL2MP_GESTURE_RANGE_ATTACK_PISTOL, false }, + { ACT_HL2MP_GESTURE_RELOAD, ACT_HL2MP_GESTURE_RELOAD_PISTOL, false }, + { ACT_HL2MP_JUMP, ACT_HL2MP_JUMP_PISTOL, false }, +#if EXPANDED_HL2DM_ACTIVITIES + { ACT_HL2MP_WALK, ACT_HL2MP_WALK_PISTOL, false }, + { ACT_HL2MP_GESTURE_RANGE_ATTACK2, ACT_HL2MP_GESTURE_RANGE_ATTACK2_PISTOL, false }, +#endif +#endif }; IMPLEMENT_ACTTABLE(CWeaponAlyxGun); diff --git a/src/game/server/hl2/weapon_alyxgun.h b/src/game/server/hl2/weapon_alyxgun.h index cc3e862c..4dd95532 100644 --- a/src/game/server/hl2/weapon_alyxgun.h +++ b/src/game/server/hl2/weapon_alyxgun.h @@ -13,6 +13,11 @@ #pragma once #endif +#ifdef MAPBASE +extern acttable_t *GetPistolActtable(); +extern int GetPistolActtableCount(); +#endif + class CWeaponAlyxGun : public CHLSelectFireMachineGun { DECLARE_DATADESC(); @@ -51,6 +56,11 @@ public: SetTouch(NULL); } +#ifdef MAPBASE + virtual acttable_t *GetBackupActivityList() { return GetPistolActtable(); } + virtual int GetBackupActivityListCount() { return GetPistolActtableCount(); } +#endif + float m_flTooCloseTimer; DECLARE_ACTTABLE(); diff --git a/src/game/server/hl2/weapon_annabelle.cpp b/src/game/server/hl2/weapon_annabelle.cpp index 94a8a1a8..835fcc6a 100644 --- a/src/game/server/hl2/weapon_annabelle.cpp +++ b/src/game/server/hl2/weapon_annabelle.cpp @@ -20,6 +20,11 @@ extern ConVar sk_auto_reload_time; +#ifdef MAPBASE +extern acttable_t *GetShotgunActtable(); +extern int GetShotgunActtableCount(); +#endif + class CWeaponAnnabelle : public CBaseHLCombatWeapon { DECLARE_DATADESC(); @@ -41,6 +46,16 @@ public: virtual const Vector& GetBulletSpread( void ) { static Vector cone = vec3_origin; + +#ifdef MAPBASE + if (GetOwner() && GetOwner()->OverridingWeaponProficiency()) + { + // If the owner's weapon proficiency is being overridden, return a more realistic spread + static Vector cone2 = VECTOR_CONE_6DEGREES; + return cone2; + } +#endif + return cone; } @@ -61,6 +76,11 @@ public: void Operator_HandleAnimEvent( animevent_t *pEvent, CBaseCombatCharacter *pOperator ); +#ifdef MAPBASE + virtual acttable_t *GetBackupActivityList() { return GetShotgunActtable(); } + virtual int GetBackupActivityListCount() { return GetShotgunActtableCount(); } +#endif + DECLARE_ACTTABLE(); CWeaponAnnabelle(void); @@ -82,6 +102,58 @@ END_DATADESC() acttable_t CWeaponAnnabelle::m_acttable[] = { +#if defined(EXPANDED_HL2_WEAPON_ACTIVITIES) && AR2_ACTIVITY_FIX == 1 + { ACT_IDLE, ACT_IDLE_AR2, false }, + { ACT_IDLE_ANGRY, ACT_IDLE_ANGRY_AR2, true }, + { ACT_RANGE_AIM_LOW, ACT_RANGE_AIM_AR2_LOW, false }, + { ACT_RANGE_ATTACK1, ACT_RANGE_ATTACK_ANNABELLE, true }, + { ACT_RANGE_ATTACK1_LOW, ACT_RANGE_ATTACK_ANNABELLE_LOW, true }, + { ACT_RELOAD, ACT_RELOAD_ANNABELLE, true }, + { ACT_WALK, ACT_WALK_AR2, true }, + { ACT_WALK_AIM, ACT_WALK_AIM_AR2, true }, + { ACT_WALK_CROUCH, ACT_WALK_CROUCH_RIFLE, false }, + { ACT_WALK_CROUCH_AIM, ACT_WALK_CROUCH_AIM_RIFLE, false }, + { ACT_RUN, ACT_RUN_AR2, true }, + { ACT_RUN_AIM, ACT_RUN_AIM_AR2, true }, + { ACT_RUN_CROUCH, ACT_RUN_CROUCH_RIFLE, false }, + { ACT_RUN_CROUCH_AIM, ACT_RUN_CROUCH_AIM_RIFLE, false }, + { ACT_GESTURE_RANGE_ATTACK1, ACT_GESTURE_RANGE_ATTACK_ANNABELLE, true }, + { ACT_RELOAD_LOW, ACT_RELOAD_ANNABELLE_LOW, false }, + { ACT_GESTURE_RELOAD, ACT_GESTURE_RELOAD_ANNABELLE, false }, + + // Readiness activities (not aiming) + { ACT_IDLE_RELAXED, ACT_IDLE_AR2_RELAXED, false },//never aims + { ACT_IDLE_STIMULATED, ACT_IDLE_AR2_STIMULATED, false }, + { ACT_IDLE_AGITATED, ACT_IDLE_ANGRY_AR2, false },//always aims + + { ACT_WALK_RELAXED, ACT_WALK_AR2_RELAXED, false },//never aims + { ACT_WALK_STIMULATED, ACT_WALK_AR2_STIMULATED, false }, + { ACT_WALK_AGITATED, ACT_WALK_AIM_AR2, false },//always aims + + { ACT_RUN_RELAXED, ACT_RUN_AR2_RELAXED, false },//never aims + { ACT_RUN_STIMULATED, ACT_RUN_AR2_STIMULATED, false }, + { ACT_RUN_AGITATED, ACT_RUN_AIM_RIFLE, false },//always aims + +// Readiness activities (aiming) + { ACT_IDLE_AIM_RELAXED, ACT_IDLE_AR2_RELAXED, false },//never aims + { ACT_IDLE_AIM_STIMULATED, ACT_IDLE_AIM_AR2_STIMULATED, false }, + { ACT_IDLE_AIM_AGITATED, ACT_IDLE_ANGRY_AR2, false },//always aims + + { ACT_WALK_AIM_RELAXED, ACT_WALK_AR2_RELAXED, false },//never aims + { ACT_WALK_AIM_STIMULATED, ACT_WALK_AIM_AR2_STIMULATED, false }, + { ACT_WALK_AIM_AGITATED, ACT_WALK_AIM_AR2, false },//always aims + + { ACT_RUN_AIM_RELAXED, ACT_RUN_AR2_RELAXED, false },//never aims + { ACT_RUN_AIM_STIMULATED, ACT_RUN_AIM_AR2_STIMULATED, false }, + { ACT_RUN_AIM_AGITATED, ACT_RUN_AIM_RIFLE, false },//always aims +//End readiness activities + + { ACT_ARM, ACT_ARM_RIFLE, true }, + { ACT_DISARM, ACT_DISARM_RIFLE, true }, +#else +#ifdef MAPBASE + { ACT_IDLE, ACT_IDLE_SMG1, false }, +#endif { ACT_IDLE_ANGRY, ACT_IDLE_ANGRY_SMG1, true }, { ACT_RANGE_ATTACK1, ACT_RANGE_ATTACK_SHOTGUN, true }, { ACT_RELOAD, ACT_RELOAD_SMG1, true }, @@ -96,10 +168,45 @@ acttable_t CWeaponAnnabelle::m_acttable[] = { ACT_GESTURE_RANGE_ATTACK1, ACT_GESTURE_RANGE_ATTACK_SHOTGUN, true }, { ACT_RELOAD_LOW, ACT_RELOAD_SMG1_LOW, false }, { ACT_GESTURE_RELOAD, ACT_GESTURE_RELOAD_SMG1, false }, +#endif + +#if EXPANDED_HL2_COVER_ACTIVITIES + { ACT_COVER_WALL_R, ACT_COVER_WALL_R_RIFLE, false }, + { ACT_COVER_WALL_L, ACT_COVER_WALL_L_RIFLE, false }, + { ACT_COVER_WALL_LOW_R, ACT_COVER_WALL_LOW_R_RIFLE, false }, + { ACT_COVER_WALL_LOW_L, ACT_COVER_WALL_LOW_L_RIFLE, false }, +#endif + +#ifdef MAPBASE + // HL2:DM activities (for third-person animations in SP) + { ACT_HL2MP_IDLE, ACT_HL2MP_IDLE_AR2, false }, + { ACT_HL2MP_RUN, ACT_HL2MP_RUN_AR2, false }, + { ACT_HL2MP_IDLE_CROUCH, ACT_HL2MP_IDLE_CROUCH_AR2, false }, + { ACT_HL2MP_WALK_CROUCH, ACT_HL2MP_WALK_CROUCH_AR2, false }, + { ACT_HL2MP_GESTURE_RANGE_ATTACK, ACT_HL2MP_GESTURE_RANGE_ATTACK_SHOTGUN, false }, + { ACT_HL2MP_GESTURE_RELOAD, ACT_HL2MP_GESTURE_RELOAD_AR2, false }, + { ACT_HL2MP_JUMP, ACT_HL2MP_JUMP_AR2, false }, +#if EXPANDED_HL2DM_ACTIVITIES + { ACT_HL2MP_WALK, ACT_HL2MP_WALK_AR2, false }, + { ACT_HL2MP_GESTURE_RANGE_ATTACK2, ACT_HL2MP_GESTURE_RANGE_ATTACK2_SHOTGUN, false }, +#endif +#endif }; IMPLEMENT_ACTTABLE(CWeaponAnnabelle); +#ifdef MAPBASE +acttable_t* GetAnnabelleActtable() +{ + return CWeaponAnnabelle::m_acttable; +} + +int GetAnnabelleActtableCount() +{ + return ARRAYSIZE(CWeaponAnnabelle::m_acttable); +} +#endif // MAPBASE + void CWeaponAnnabelle::Precache( void ) { CBaseCombatWeapon::Precache(); @@ -172,6 +279,13 @@ bool CWeaponAnnabelle::StartReload( void ) pOwner->m_flNextAttack = gpGlobals->curtime; m_flNextPrimaryAttack = gpGlobals->curtime + SequenceDuration(); +#ifdef MAPBASE + if ( pOwner->IsPlayer() ) + { + static_cast(pOwner)->SetAnimation( PLAYER_RELOAD ); + } +#endif + m_bInReload = true; return true; } diff --git a/src/game/server/hl2/weapon_ar1.cpp b/src/game/server/hl2/weapon_ar1.cpp index 994cdabb..0d9c0240 100644 --- a/src/game/server/hl2/weapon_ar1.cpp +++ b/src/game/server/hl2/weapon_ar1.cpp @@ -37,6 +37,10 @@ float Damage[ MAX_SETTINGS ] = 20, }; +#ifdef MAPBASE +extern acttable_t *GetAR2Acttable(); +extern int GetAR2ActtableCount(); +#endif //========================================================= //========================================================= @@ -93,6 +97,12 @@ public: break; } } + +#ifdef MAPBASE + virtual acttable_t *GetBackupActivityList() { return GetAR2Acttable(); } + virtual int GetBackupActivityListCount() { return GetAR2ActtableCount(); } +#endif + DECLARE_ACTTABLE(); }; @@ -105,6 +115,77 @@ PRECACHE_WEAPON_REGISTER(weapon_ar1); acttable_t CWeaponAR1::m_acttable[] = { { ACT_RANGE_ATTACK1, ACT_RANGE_ATTACK_AR1, true }, + +#if EXPANDED_HL2_UNUSED_WEAPON_ACTIVITIES + // Optional new NPC activities + // (these should fall back to AR2 animations when they don't exist on an NPC) + { ACT_RELOAD, ACT_RELOAD_AR1, true }, + { ACT_IDLE, ACT_IDLE_AR1, true }, + { ACT_IDLE_ANGRY, ACT_IDLE_ANGRY_AR1, true }, + +// Readiness activities (not aiming) + { ACT_IDLE_RELAXED, ACT_IDLE_AR1_RELAXED, false },//never aims + { ACT_IDLE_STIMULATED, ACT_IDLE_AR1_STIMULATED, false }, + { ACT_IDLE_AGITATED, ACT_IDLE_ANGRY_AR1, false },//always aims + + { ACT_WALK_RELAXED, ACT_WALK_AR1_RELAXED, false },//never aims + { ACT_WALK_STIMULATED, ACT_WALK_AR1_STIMULATED, false }, + { ACT_WALK_AGITATED, ACT_WALK_AIM_AR1, false },//always aims + + { ACT_RUN_RELAXED, ACT_RUN_AR1_RELAXED, false },//never aims + { ACT_RUN_STIMULATED, ACT_RUN_AR1_STIMULATED, false }, + { ACT_RUN_AGITATED, ACT_RUN_AIM_AR1, false },//always aims + +// Readiness activities (aiming) + { ACT_IDLE_AIM_RELAXED, ACT_IDLE_AR1_RELAXED, false },//never aims + { ACT_IDLE_AIM_STIMULATED, ACT_IDLE_AIM_AR1_STIMULATED, false }, + { ACT_IDLE_AIM_AGITATED, ACT_IDLE_ANGRY_AR1, false },//always aims + + { ACT_WALK_AIM_RELAXED, ACT_WALK_AR1_RELAXED, false },//never aims + { ACT_WALK_AIM_STIMULATED, ACT_WALK_AIM_AR1_STIMULATED, false }, + { ACT_WALK_AIM_AGITATED, ACT_WALK_AIM_AR1, false },//always aims + + { ACT_RUN_AIM_RELAXED, ACT_RUN_AR1_RELAXED, false },//never aims + { ACT_RUN_AIM_STIMULATED, ACT_RUN_AIM_AR1_STIMULATED, false }, + { ACT_RUN_AIM_AGITATED, ACT_RUN_AIM_AR1, false },//always aims +//End readiness activities + + { ACT_WALK, ACT_WALK_AR1, true }, + { ACT_WALK_AIM, ACT_WALK_AIM_AR1, true }, + { ACT_WALK_CROUCH, ACT_WALK_CROUCH_RIFLE, true }, + { ACT_WALK_CROUCH_AIM, ACT_WALK_CROUCH_AIM_RIFLE, true }, + { ACT_RUN, ACT_RUN_AR1, true }, + { ACT_RUN_AIM, ACT_RUN_AIM_AR1, true }, + { ACT_RUN_CROUCH, ACT_RUN_CROUCH_RIFLE, true }, + { ACT_RUN_CROUCH_AIM, ACT_RUN_CROUCH_AIM_RIFLE, true }, + { ACT_GESTURE_RANGE_ATTACK1, ACT_GESTURE_RANGE_ATTACK_AR1, true }, + { ACT_RANGE_ATTACK1_LOW, ACT_RANGE_ATTACK_AR1_LOW, true }, + { ACT_COVER_LOW, ACT_COVER_AR1_LOW, false }, + { ACT_RANGE_AIM_LOW, ACT_RANGE_AIM_AR1_LOW, false }, + { ACT_RELOAD_LOW, ACT_RELOAD_AR1_LOW, false }, + { ACT_GESTURE_RELOAD, ACT_GESTURE_RELOAD_AR1, true }, + + { ACT_ARM, ACT_ARM_RIFLE, false }, + { ACT_DISARM, ACT_DISARM_RIFLE, false }, + +#if EXPANDED_HL2_COVER_ACTIVITIES + { ACT_RANGE_AIM_MED, ACT_RANGE_AIM_AR1_MED, false }, + { ACT_RANGE_ATTACK1_MED, ACT_RANGE_ATTACK_AR1_MED, false }, +#endif + +#if EXPANDED_HL2DM_ACTIVITIES + // HL2:DM activities (for third-person animations in SP) + { ACT_HL2MP_IDLE, ACT_HL2MP_IDLE_AR1, false }, + { ACT_HL2MP_RUN, ACT_HL2MP_RUN_AR1, false }, + { ACT_HL2MP_IDLE_CROUCH, ACT_HL2MP_IDLE_CROUCH_AR1, false }, + { ACT_HL2MP_WALK_CROUCH, ACT_HL2MP_WALK_CROUCH_AR1, false }, + { ACT_HL2MP_GESTURE_RANGE_ATTACK, ACT_HL2MP_GESTURE_RANGE_ATTACK_AR1, false }, + { ACT_HL2MP_GESTURE_RELOAD, ACT_HL2MP_GESTURE_RELOAD_AR1, false }, + { ACT_HL2MP_JUMP, ACT_HL2MP_JUMP_AR1, false }, + { ACT_HL2MP_WALK, ACT_HL2MP_WALK_AR1, false }, + { ACT_HL2MP_GESTURE_RANGE_ATTACK2, ACT_HL2MP_GESTURE_RANGE_ATTACK2_AR1, false }, +#endif +#endif }; IMPLEMENT_ACTTABLE(CWeaponAR1); diff --git a/src/game/server/hl2/weapon_ar2.cpp b/src/game/server/hl2/weapon_ar2.cpp index d9c7ec25..deee7973 100644 --- a/src/game/server/hl2/weapon_ar2.cpp +++ b/src/game/server/hl2/weapon_ar2.cpp @@ -27,6 +27,9 @@ #include "npc_combine.h" #include "rumble_shared.h" #include "gamestats.h" +#ifdef MAPBASE +#include "npc_playercompanion.h" +#endif // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" @@ -54,6 +57,56 @@ PRECACHE_WEAPON_REGISTER(weapon_ar2); acttable_t CWeaponAR2::m_acttable[] = { +#if AR2_ACTIVITY_FIX == 1 + { ACT_RANGE_ATTACK1, ACT_RANGE_ATTACK_AR2, true }, + { ACT_RELOAD, ACT_RELOAD_AR2, true }, + { ACT_IDLE, ACT_IDLE_AR2, true }, + { ACT_IDLE_ANGRY, ACT_IDLE_ANGRY_AR2, false }, + + { ACT_WALK, ACT_WALK_AR2, true }, + +// Readiness activities (not aiming) + { ACT_IDLE_RELAXED, ACT_IDLE_AR2_RELAXED, false },//never aims + { ACT_IDLE_STIMULATED, ACT_IDLE_AR2_STIMULATED, false }, + { ACT_IDLE_AGITATED, ACT_IDLE_ANGRY_AR2, false },//always aims + + { ACT_WALK_RELAXED, ACT_WALK_AR2_RELAXED, false },//never aims + { ACT_WALK_STIMULATED, ACT_WALK_AR2_STIMULATED, false }, + { ACT_WALK_AGITATED, ACT_WALK_AIM_AR2, false },//always aims + + { ACT_RUN_RELAXED, ACT_RUN_AR2_RELAXED, false },//never aims + { ACT_RUN_STIMULATED, ACT_RUN_AR2_STIMULATED, false }, + { ACT_RUN_AGITATED, ACT_RUN_AIM_RIFLE, false },//always aims + +// Readiness activities (aiming) + { ACT_IDLE_AIM_RELAXED, ACT_IDLE_AR2_RELAXED, false },//never aims + { ACT_IDLE_AIM_STIMULATED, ACT_IDLE_AIM_AR2_STIMULATED, false }, + { ACT_IDLE_AIM_AGITATED, ACT_IDLE_ANGRY_AR2, false },//always aims + + { ACT_WALK_AIM_RELAXED, ACT_WALK_AR2_RELAXED, false },//never aims + { ACT_WALK_AIM_STIMULATED, ACT_WALK_AIM_AR2_STIMULATED, false }, + { ACT_WALK_AIM_AGITATED, ACT_WALK_AIM_AR2, false },//always aims + + { ACT_RUN_AIM_RELAXED, ACT_RUN_AR2_RELAXED, false },//never aims + { ACT_RUN_AIM_STIMULATED, ACT_RUN_AIM_AR2_STIMULATED, false }, + { ACT_RUN_AIM_AGITATED, ACT_RUN_AIM_RIFLE, false },//always aims +//End readiness activities + + { ACT_WALK_AIM, ACT_WALK_AIM_AR2, true }, + { ACT_WALK_CROUCH, ACT_WALK_CROUCH_RIFLE, true }, + { ACT_WALK_CROUCH_AIM, ACT_WALK_CROUCH_AIM_RIFLE, true }, + { ACT_RUN, ACT_RUN_AR2, true }, + { ACT_RUN_AIM, ACT_RUN_AIM_AR2, true }, + { ACT_RUN_CROUCH, ACT_RUN_CROUCH_RIFLE, true }, + { ACT_RUN_CROUCH_AIM, ACT_RUN_CROUCH_AIM_RIFLE, true }, + { ACT_GESTURE_RANGE_ATTACK1, ACT_GESTURE_RANGE_ATTACK_AR2, false }, + { ACT_COVER_LOW, ACT_COVER_AR2_LOW, true }, + { ACT_RANGE_AIM_LOW, ACT_RANGE_AIM_AR2_LOW, false }, + { ACT_RANGE_ATTACK1_LOW, ACT_RANGE_ATTACK_AR2_LOW, false }, + { ACT_RELOAD_LOW, ACT_RELOAD_AR2_LOW, false }, + { ACT_GESTURE_RELOAD, ACT_GESTURE_RELOAD_AR2, true }, +// { ACT_RANGE_ATTACK2, ACT_RANGE_ATTACK_AR2_GRENADE, true }, +#else { ACT_RANGE_ATTACK1, ACT_RANGE_ATTACK_AR2, true }, { ACT_RELOAD, ACT_RELOAD_SMG1, true }, // FIXME: hook to AR2 unique { ACT_IDLE, ACT_IDLE_SMG1, true }, // FIXME: hook to AR2 unique @@ -102,10 +155,54 @@ acttable_t CWeaponAR2::m_acttable[] = { ACT_RELOAD_LOW, ACT_RELOAD_SMG1_LOW, false }, { ACT_GESTURE_RELOAD, ACT_GESTURE_RELOAD_SMG1, true }, // { ACT_RANGE_ATTACK2, ACT_RANGE_ATTACK_AR2_GRENADE, true }, +#endif + +#if EXPANDED_HL2_WEAPON_ACTIVITIES + { ACT_ARM, ACT_ARM_RIFLE, false }, + { ACT_DISARM, ACT_DISARM_RIFLE, false }, +#endif + +#if EXPANDED_HL2_COVER_ACTIVITIES + { ACT_RANGE_AIM_MED, ACT_RANGE_AIM_AR2_MED, false }, + { ACT_RANGE_ATTACK1_MED, ACT_RANGE_ATTACK_AR2_MED, false }, + + { ACT_COVER_WALL_R, ACT_COVER_WALL_R_RIFLE, false }, + { ACT_COVER_WALL_L, ACT_COVER_WALL_L_RIFLE, false }, + { ACT_COVER_WALL_LOW_R, ACT_COVER_WALL_LOW_R_RIFLE, false }, + { ACT_COVER_WALL_LOW_L, ACT_COVER_WALL_LOW_L_RIFLE, false }, +#endif + +#ifdef MAPBASE + // HL2:DM activities (for third-person animations in SP) + { ACT_HL2MP_IDLE, ACT_HL2MP_IDLE_AR2, false }, + { ACT_HL2MP_RUN, ACT_HL2MP_RUN_AR2, false }, + { ACT_HL2MP_IDLE_CROUCH, ACT_HL2MP_IDLE_CROUCH_AR2, false }, + { ACT_HL2MP_WALK_CROUCH, ACT_HL2MP_WALK_CROUCH_AR2, false }, + { ACT_HL2MP_GESTURE_RANGE_ATTACK, ACT_HL2MP_GESTURE_RANGE_ATTACK_AR2, false }, + { ACT_HL2MP_GESTURE_RELOAD, ACT_HL2MP_GESTURE_RELOAD_AR2, false }, + { ACT_HL2MP_JUMP, ACT_HL2MP_JUMP_AR2, false }, +#if EXPANDED_HL2DM_ACTIVITIES + { ACT_HL2MP_WALK, ACT_HL2MP_WALK_AR2, false }, + { ACT_HL2MP_GESTURE_RANGE_ATTACK2, ACT_HL2MP_GESTURE_RANGE_ATTACK2_AR2, false }, +#endif +#endif }; IMPLEMENT_ACTTABLE(CWeaponAR2); +#ifdef MAPBASE +// Allows Weapon_BackupActivity() to access the AR2's activity table. +acttable_t *GetAR2Acttable() +{ + return CWeaponAR2::m_acttable; +} + +int GetAR2ActtableCount() +{ + return ARRAYSIZE(CWeaponAR2::m_acttable); +} +#endif + CWeaponAR2::CWeaponAR2( ) { m_fMinRange1 = 65; @@ -249,7 +346,7 @@ void CWeaponAR2::DelayedAttack( void ) pOwner->SnapEyeAngles( angles ); - pOwner->ViewPunch( QAngle( random->RandomInt( -8, -12 ), random->RandomInt( 1, 2 ), 0 ) ); + pOwner->ViewPunch( QAngle( random->RandomInt( -12, -8 ), random->RandomInt( 1, 2 ), 0 ) ); // Decrease ammo pOwner->RemoveAmmo( 1, m_iSecondaryAmmoType ); @@ -285,6 +382,9 @@ void CWeaponAR2::SecondaryAttack( void ) if( pPlayer ) { pPlayer->RumbleEffect(RUMBLE_AR2_ALT_FIRE, 0, RUMBLE_FLAG_RESTART ); +#ifdef MAPBASE + pPlayer->SetAnimation( PLAYER_ATTACK2 ); +#endif } SendWeaponAnim( ACT_VM_FIDGET ); @@ -382,6 +482,10 @@ void CWeaponAR2::FireNPCSecondaryAttack( CBaseCombatCharacter *pOperator, bool b Vector vecTarget; +#ifdef MAPBASE + // It's shared across all NPCs now that it's available on more than just soldiers on more than just the AR2. + vecTarget = pNPC->GetAltFireTarget(); +#else CNPC_Combine *pSoldier = dynamic_cast( pNPC ); if ( pSoldier ) { @@ -389,6 +493,13 @@ void CWeaponAR2::FireNPCSecondaryAttack( CBaseCombatCharacter *pOperator, bool b // Therefore, we must ask them specifically what direction they are shooting. vecTarget = pSoldier->GetAltFireTarget(); } +#ifdef MAPBASE + else if ( CNPC_PlayerCompanion *pCompanion = dynamic_cast( pNPC ) ) + { + // Companions can use energy balls now. Isn't that lovely? + vecTarget = pCompanion->GetAltFireTarget(); + } +#endif else { // All other users of the AR2 alt-fire shoot directly at their enemy. @@ -397,6 +508,7 @@ void CWeaponAR2::FireNPCSecondaryAttack( CBaseCombatCharacter *pOperator, bool b vecTarget = pNPC->GetEnemy()->BodyTarget( vecSrc ); } +#endif vecAiming = vecTarget - vecSrc; VectorNormalize( vecAiming ); @@ -412,12 +524,25 @@ void CWeaponAR2::FireNPCSecondaryAttack( CBaseCombatCharacter *pOperator, bool b Vector vecVelocity = vecAiming * 1000.0f; // Fire the combine ball +#ifdef MAPBASE + CBaseEntity *pBall = CreateCombineBall( vecSrc, + vecVelocity, + flRadius, + sk_weapon_ar2_alt_fire_mass.GetFloat(), + flDuration, + pNPC ); + + variant_t var; + var.SetEntity(pBall); + pNPC->FireNamedOutput("OnThrowGrenade", var, pBall, pNPC); +#else CreateCombineBall( vecSrc, vecVelocity, flRadius, sk_weapon_ar2_alt_fire_mass.GetFloat(), flDuration, pNPC ); +#endif } //----------------------------------------------------------------------------- diff --git a/src/game/server/hl2/weapon_ar2.h b/src/game/server/hl2/weapon_ar2.h index d5376a83..5a855f2c 100644 --- a/src/game/server/hl2/weapon_ar2.h +++ b/src/game/server/hl2/weapon_ar2.h @@ -71,6 +71,9 @@ protected: bool m_bShotDelayed; int m_nVentPose; +#ifdef MAPBASE // Make act table accessible outside class +public: +#endif DECLARE_ACTTABLE(); DECLARE_DATADESC(); }; diff --git a/src/game/server/hl2/weapon_bugbait.cpp b/src/game/server/hl2/weapon_bugbait.cpp index 38c02239..d9e6f94c 100644 --- a/src/game/server/hl2/weapon_bugbait.cpp +++ b/src/game/server/hl2/weapon_bugbait.cpp @@ -58,6 +58,9 @@ public: bool ShouldDisplayHUDHint() { return true; } DECLARE_DATADESC(); +#ifdef MAPBASE + DECLARE_ACTTABLE(); +#endif protected: @@ -86,6 +89,26 @@ BEGIN_DATADESC( CWeaponBugBait ) END_DATADESC() +#ifdef MAPBASE +acttable_t CWeaponBugBait::m_acttable[] = +{ + // HL2:DM activities (for third-person animations in SP) + { ACT_HL2MP_IDLE, ACT_HL2MP_IDLE_GRENADE, false }, + { ACT_HL2MP_RUN, ACT_HL2MP_RUN_GRENADE, false }, + { ACT_HL2MP_IDLE_CROUCH, ACT_HL2MP_IDLE_CROUCH_GRENADE, false }, + { ACT_HL2MP_WALK_CROUCH, ACT_HL2MP_WALK_CROUCH_GRENADE, false }, + { ACT_HL2MP_GESTURE_RANGE_ATTACK, ACT_HL2MP_GESTURE_RANGE_ATTACK_GRENADE, false }, + { ACT_HL2MP_GESTURE_RELOAD, ACT_HL2MP_GESTURE_RELOAD_GRENADE, false }, + { ACT_HL2MP_JUMP, ACT_HL2MP_JUMP_GRENADE, false }, +#if EXPANDED_HL2DM_ACTIVITIES + { ACT_HL2MP_WALK, ACT_HL2MP_WALK_GRENADE, false }, + { ACT_HL2MP_GESTURE_RANGE_ATTACK2, ACT_HL2MP_GESTURE_RANGE_ATTACK2_GRENADE, false }, +#endif +}; + +IMPLEMENT_ACTTABLE( CWeaponBugBait ); +#endif + //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- @@ -292,6 +315,10 @@ void CWeaponBugBait::ThrowGrenade( CBasePlayer *pPlayer ) } m_bRedraw = true; + +#ifdef MAPBASE + pPlayer->SetAnimation( PLAYER_ATTACK1 ); +#endif } //----------------------------------------------------------------------------- @@ -349,6 +376,14 @@ void CWeaponBugBait::ItemPostFrame( void ) if ( pOwner == NULL ) return; +#ifdef MAPBASE + if (pOwner->HasSpawnFlags( SF_PLAYER_SUPPRESS_FIRING )) + { + WeaponIdle(); + return; + } +#endif + // See if we're cocked and ready to throw if ( m_bDrawBackFinished ) { diff --git a/src/game/server/hl2/weapon_cguard.cpp b/src/game/server/hl2/weapon_cguard.cpp index 572a90c5..4b8a67d4 100644 --- a/src/game/server/hl2/weapon_cguard.cpp +++ b/src/game/server/hl2/weapon_cguard.cpp @@ -86,6 +86,10 @@ void TE_ConcussiveExplosion( IRecipientFilter& filter, float delay, //Temp ent for the blast +#ifdef MAPBASE +#define SF_CONCUSSIVEBLAST_REPEATABLE 0x00000001 +#endif + class CConcussiveBlast : public CBaseEntity { DECLARE_DATADESC(); @@ -94,6 +98,13 @@ public: int m_spriteTexture; +#ifdef MAPBASE + float m_flDamage = 200; + float m_flRadius = 256; + float m_flMagnitude = 1.0; + string_t m_iszSoundName; +#endif + CConcussiveBlast( void ) {} //----------------------------------------------------------------------------- @@ -104,6 +115,11 @@ public: { m_spriteTexture = PrecacheModel( "sprites/lgtning.vmt" ); +#ifdef MAPBASE + if (m_iszSoundName != NULL_STRING) + PrecacheScriptSound(STRING(m_iszSoundName)); +#endif + BaseClass::Precache(); } @@ -150,10 +166,32 @@ public: ); //Do the radius damage +#ifdef MAPBASE + RadiusDamage( CTakeDamageInfo( this, GetOwnerEntity(), m_flDamage, DMG_BLAST|DMG_DISSOLVE ), GetAbsOrigin(), m_flRadius, CLASS_NONE, NULL ); +#else RadiusDamage( CTakeDamageInfo( this, GetOwnerEntity(), 200, DMG_BLAST|DMG_DISSOLVE ), GetAbsOrigin(), 256, CLASS_NONE, NULL ); +#endif +#ifdef MAPBASE + if (m_iszSoundName != NULL_STRING) + EmitSound(STRING(m_iszSoundName)); + + if (!HasSpawnFlags(SF_CONCUSSIVEBLAST_REPEATABLE)) +#endif UTIL_Remove( this ); } + +#ifdef MAPBASE + void InputExplode( inputdata_t &inputdata ) + { + Explode(m_flMagnitude); + } + + void InputExplodeWithMagnitude( inputdata_t &inputdata ) + { + Explode(inputdata.value.Int()); + } +#endif }; LINK_ENTITY_TO_CLASS( concussiveblast, CConcussiveBlast ); @@ -165,6 +203,16 @@ BEGIN_DATADESC( CConcussiveBlast ) // DEFINE_FIELD( m_spriteTexture, FIELD_INTEGER ), +#ifdef MAPBASE + DEFINE_KEYFIELD( m_flDamage, FIELD_FLOAT, "damage" ), + DEFINE_KEYFIELD( m_flRadius, FIELD_FLOAT, "radius" ), + DEFINE_KEYFIELD( m_flMagnitude, FIELD_FLOAT, "magnitude" ), + DEFINE_KEYFIELD( m_iszSoundName, FIELD_SOUNDNAME, "soundname" ), + + DEFINE_INPUTFUNC( FIELD_VOID, "Explode", InputExplode ), + DEFINE_INPUTFUNC( FIELD_INTEGER, "ExplodeWithMagnitude", InputExplodeWithMagnitude ), +#endif + END_DATADESC() diff --git a/src/game/server/hl2/weapon_citizenpackage.cpp b/src/game/server/hl2/weapon_citizenpackage.cpp index cdb9378a..95a02296 100644 --- a/src/game/server/hl2/weapon_citizenpackage.cpp +++ b/src/game/server/hl2/weapon_citizenpackage.cpp @@ -23,6 +23,13 @@ acttable_t CWeaponCitizenPackage::m_acttable[] = { { ACT_IDLE, ACT_IDLE_PACKAGE, false }, { ACT_WALK, ACT_WALK_PACKAGE, false }, +#if EXPANDED_HL2_WEAPON_ACTIVITIES + { ACT_RUN, ACT_RUN_PACKAGE, false }, + + { ACT_IDLE_ANGRY, ACT_IDLE_PACKAGE, false }, + { ACT_WALK_AIM, ACT_WALK_PACKAGE, false }, + { ACT_RUN_AIM, ACT_RUN_PACKAGE, false }, +#endif }; IMPLEMENT_ACTTABLE(CWeaponCitizenPackage); @@ -70,5 +77,12 @@ acttable_t CWeaponCitizenSuitcase::m_acttable[] = { { ACT_IDLE, ACT_IDLE_SUITCASE, false }, { ACT_WALK, ACT_WALK_SUITCASE, false }, +#if EXPANDED_HL2_WEAPON_ACTIVITIES + { ACT_RUN, ACT_RUN_SUITCASE, false }, + + { ACT_IDLE_ANGRY, ACT_IDLE_SUITCASE, false }, + { ACT_WALK_AIM, ACT_WALK_SUITCASE, false }, + { ACT_RUN_AIM, ACT_RUN_SUITCASE, false }, +#endif }; IMPLEMENT_ACTTABLE(CWeaponCitizenSuitcase); diff --git a/src/game/server/hl2/weapon_crossbow.cpp b/src/game/server/hl2/weapon_crossbow.cpp index badd50d8..f50a5c53 100644 --- a/src/game/server/hl2/weapon_crossbow.cpp +++ b/src/game/server/hl2/weapon_crossbow.cpp @@ -41,6 +41,10 @@ extern ConVar sk_plr_dmg_crossbow; extern ConVar sk_npc_dmg_crossbow; +#ifdef MAPBASE +ConVar weapon_crossbow_new_hit_locations( "weapon_crossbow_new_hit_locations", "1", FCVAR_NONE, "Toggles new crossbow knockback that properly pushes back the correct limbs." ); +#endif + void TE_StickyBolt( IRecipientFilter& filter, float delay, Vector vecDirection, const Vector *origin ); #define BOLT_SKIN_NORMAL 0 @@ -54,7 +58,11 @@ class CCrossbowBolt : public CBaseCombatCharacter DECLARE_CLASS( CCrossbowBolt, CBaseCombatCharacter ); public: +#ifdef MAPBASE + CCrossbowBolt(); +#else CCrossbowBolt() { }; +#endif ~CCrossbowBolt(); Class_T Classify( void ) { return CLASS_NONE; } @@ -66,7 +74,16 @@ public: void BoltTouch( CBaseEntity *pOther ); bool CreateVPhysics( void ); unsigned int PhysicsSolidMaskForEntity() const; +#ifdef MAPBASE + static CCrossbowBolt *BoltCreate( const Vector &vecOrigin, const QAngle &angAngles, CBaseCombatCharacter *pentOwner = NULL ); + + void InputSetDamage( inputdata_t &inputdata ); + float m_flDamage; + + virtual void SetDamage(float flDamage) { m_flDamage = flDamage; } +#else static CCrossbowBolt *BoltCreate( const Vector &vecOrigin, const QAngle &angAngles, CBasePlayer *pentOwner = NULL ); +#endif protected: @@ -89,12 +106,23 @@ BEGIN_DATADESC( CCrossbowBolt ) DEFINE_FIELD( m_pGlowSprite, FIELD_EHANDLE ), //DEFINE_FIELD( m_pGlowTrail, FIELD_EHANDLE ), +#ifdef MAPBASE + DEFINE_KEYFIELD( m_flDamage, FIELD_FLOAT, "Damage" ), + + // Inputs + DEFINE_INPUTFUNC( FIELD_FLOAT, "SetDamage", InputSetDamage ), +#endif + END_DATADESC() IMPLEMENT_SERVERCLASS_ST( CCrossbowBolt, DT_CrossbowBolt ) END_SEND_TABLE() +#ifdef MAPBASE +CCrossbowBolt *CCrossbowBolt::BoltCreate( const Vector &vecOrigin, const QAngle &angAngles, CBaseCombatCharacter *pentOwner ) +#else CCrossbowBolt *CCrossbowBolt::BoltCreate( const Vector &vecOrigin, const QAngle &angAngles, CBasePlayer *pentOwner ) +#endif { // Create a new entity with CCrossbowBolt private data CCrossbowBolt *pBolt = (CCrossbowBolt *)CreateEntityByName( "crossbow_bolt" ); @@ -102,10 +130,27 @@ CCrossbowBolt *CCrossbowBolt::BoltCreate( const Vector &vecOrigin, const QAngle pBolt->SetAbsAngles( angAngles ); pBolt->Spawn(); pBolt->SetOwnerEntity( pentOwner ); +#ifdef MAPBASE + if (pentOwner && pentOwner->IsNPC()) + pBolt->m_flDamage = sk_npc_dmg_crossbow.GetFloat(); + //else + // pBolt->m_flDamage = sk_plr_dmg_crossbow.GetFloat(); +#endif return pBolt; } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CCrossbowBolt::CCrossbowBolt( void ) +{ + // Independent bolts without m_flDamage set need damage + m_flDamage = sk_plr_dmg_crossbow.GetFloat(); +} +#endif + //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- @@ -194,6 +239,16 @@ void CCrossbowBolt::Precache( void ) PrecacheModel( "sprites/light_glow02_noz.vmt" ); } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCrossbowBolt::InputSetDamage( inputdata_t &inputdata ) +{ + m_flDamage = inputdata.value.Float(); +} +#endif + //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- void CCrossbowBolt::BoltTouch( CBaseEntity *pOther ) @@ -201,7 +256,19 @@ void CCrossbowBolt::BoltTouch( CBaseEntity *pOther ) if ( pOther->IsSolidFlagSet(FSOLID_VOLUME_CONTENTS | FSOLID_TRIGGER) ) { // Some NPCs are triggers that can take damage (like antlion grubs). We should hit them. +#ifdef MAPBASE + // But some physics objects that are also triggers (like weapons) shouldn't go through this check. + // + // Note: rpg_missile has the same code, except it properly accounts for weapons in a different way. + // This was discovered after I implemented this and both work fine, but if this ever causes problems, + // use rpg_missile's implementation: + // + // if ( pOther->IsSolidFlagSet(FSOLID_TRIGGER|FSOLID_VOLUME_CONTENTS) && pOther->GetCollisionGroup() != COLLISION_GROUP_WEAPON ) + // + if ( pOther->GetMoveType() == MOVETYPE_NONE && (( pOther->m_takedamage == DAMAGE_NO ) || ( pOther->m_takedamage == DAMAGE_EVENTS_ONLY )) ) +#else if ( ( pOther->m_takedamage == DAMAGE_NO ) || ( pOther->m_takedamage == DAMAGE_EVENTS_ONLY ) ) +#endif return; } @@ -226,9 +293,45 @@ void CCrossbowBolt::BoltTouch( CBaseEntity *pOther ) } #endif//HL2_EPISODIC +#ifdef MAPBASE + if (weapon_crossbow_new_hit_locations.GetInt() > 0) + { + // A very experimental and weird way of getting a crossbow bolt to deal accurate knockback. + CBaseAnimating *pOtherAnimating = pOther->GetBaseAnimating(); + if (pOtherAnimating && pOtherAnimating->GetModelPtr() && pOtherAnimating->GetModelPtr()->numbones() > 1) + { + int iClosestBone = -1; + float flCurDistSqr = Square(128.0f); + matrix3x4_t bonetoworld; + Vector vecBonePos; + for (int i = 0; i < pOtherAnimating->GetModelPtr()->numbones(); i++) + { + pOtherAnimating->GetBoneTransform( i, bonetoworld ); + MatrixPosition( bonetoworld, vecBonePos ); + + float flDist = vecBonePos.DistToSqr(GetLocalOrigin()); + if (flDist < flCurDistSqr) + { + iClosestBone = i; + flCurDistSqr = flDist; + } + } + + if (iClosestBone != -1) + { + tr.physicsbone = pOtherAnimating->GetPhysicsBone(iClosestBone); + } + } + } +#endif + if( GetOwnerEntity() && GetOwnerEntity()->IsPlayer() && pOther->IsNPC() ) { +#ifdef MAPBASE + CTakeDamageInfo dmgInfo( this, GetOwnerEntity(), m_flDamage, DMG_NEVERGIB ); +#else CTakeDamageInfo dmgInfo( this, GetOwnerEntity(), sk_plr_dmg_crossbow.GetFloat(), DMG_NEVERGIB ); +#endif dmgInfo.AdjustPlayerDamageInflictedForSkillLevel(); CalculateMeleeDamageForce( &dmgInfo, vecNormalizedVel, tr.endpos, 0.7f ); dmgInfo.SetDamagePosition( tr.endpos ); @@ -243,7 +346,11 @@ void CCrossbowBolt::BoltTouch( CBaseEntity *pOther ) } else { +#ifdef MAPBASE + CTakeDamageInfo dmgInfo( this, GetOwnerEntity(), m_flDamage, DMG_BULLET | DMG_NEVERGIB ); +#else CTakeDamageInfo dmgInfo( this, GetOwnerEntity(), sk_plr_dmg_crossbow.GetFloat(), DMG_BULLET | DMG_NEVERGIB ); +#endif CalculateMeleeDamageForce( &dmgInfo, vecNormalizedVel, tr.endpos, 0.7f ); dmgInfo.SetDamagePosition( tr.endpos ); pOther->DispatchTraceAttack( dmgInfo, vecNormalizedVel, &tr ); @@ -434,17 +541,48 @@ public: virtual void Drop( const Vector &vecVelocity ); virtual bool Holster( CBaseCombatWeapon *pSwitchingTo = NULL ); virtual bool Reload( void ); +#ifdef MAPBASE + virtual void Reload_NPC( bool bPlaySound = true ); +#endif virtual void ItemPostFrame( void ); virtual void ItemBusyFrame( void ); virtual void Operator_HandleAnimEvent( animevent_t *pEvent, CBaseCombatCharacter *pOperator ); +#ifdef MAPBASE + virtual void Operator_ForceNPCFire( CBaseCombatCharacter *pOperator, bool bSecondary ); +#endif virtual bool SendWeaponAnim( int iActivity ); virtual bool IsWeaponZoomed() { return m_bInZoom; } bool ShouldDisplayHUDHint() { return true; } +#ifdef MAPBASE + int CapabilitiesGet( void ) { return bits_CAP_WEAPON_RANGE_ATTACK1; } + + virtual int GetMinBurst() { return 1; } + virtual int GetMaxBurst() { return 1; } + + virtual float GetMinRestTime( void ) { return 3.0f; } // 1.5f + virtual float GetMaxRestTime( void ) { return 3.0f; } // 2.0f + + virtual float GetFireRate( void ) { return 5.0f; } + + virtual const Vector& GetBulletSpread( void ) + { + static Vector cone = VECTOR_CONE_15DEGREES; + if (!GetOwner() || !GetOwner()->IsNPC()) + return cone; + + static Vector NPCCone = VECTOR_CONE_5DEGREES; + + return NPCCone; + } +#endif DECLARE_SERVERCLASS(); DECLARE_DATADESC(); +#ifdef MAPBASE + DECLARE_ACTTABLE(); +#endif private: @@ -452,6 +590,10 @@ private: void SetSkin( int skinNum ); void CheckZoomToggle( void ); void FireBolt( void ); +#ifdef MAPBASE + void SetBolt( int iSetting ); + void FireNPCBolt( CAI_BaseNPC *pOwner, Vector &vecShootOrigin, Vector &vecShootDir ); +#endif void ToggleZoom( void ); // Various states for the crossbow's charger @@ -494,6 +636,144 @@ BEGIN_DATADESC( CWeaponCrossbow ) END_DATADESC() +#ifdef MAPBASE +acttable_t CWeaponCrossbow::m_acttable[] = +{ +#if EXPANDED_HL2_WEAPON_ACTIVITIES + { ACT_RANGE_ATTACK1, ACT_RANGE_ATTACK_CROSSBOW, true }, + { ACT_RELOAD, ACT_RELOAD_CROSSBOW, true }, + { ACT_IDLE, ACT_IDLE_CROSSBOW, true }, + { ACT_IDLE_ANGRY, ACT_IDLE_ANGRY_CROSSBOW, true }, + +// Readiness activities (not aiming) + { ACT_IDLE_RELAXED, ACT_IDLE_CROSSBOW_RELAXED, false },//never aims + { ACT_IDLE_STIMULATED, ACT_IDLE_CROSSBOW_STIMULATED, false }, + { ACT_IDLE_AGITATED, ACT_IDLE_ANGRY_CROSSBOW, false },//always aims + + { ACT_WALK_RELAXED, ACT_WALK_CROSSBOW_RELAXED, false },//never aims + { ACT_WALK_STIMULATED, ACT_WALK_CROSSBOW_STIMULATED, false }, + { ACT_WALK_AGITATED, ACT_WALK_AIM_CROSSBOW, false },//always aims + + { ACT_RUN_RELAXED, ACT_RUN_CROSSBOW_RELAXED, false },//never aims + { ACT_RUN_STIMULATED, ACT_RUN_CROSSBOW_STIMULATED, false }, + { ACT_RUN_AGITATED, ACT_RUN_AIM_CROSSBOW, false },//always aims + +// Readiness activities (aiming) + { ACT_IDLE_AIM_RELAXED, ACT_IDLE_CROSSBOW_RELAXED, false },//never aims + { ACT_IDLE_AIM_STIMULATED, ACT_IDLE_AIM_CROSSBOW_STIMULATED, false }, + { ACT_IDLE_AIM_AGITATED, ACT_IDLE_ANGRY_CROSSBOW, false },//always aims + + { ACT_WALK_AIM_RELAXED, ACT_WALK_CROSSBOW_RELAXED, false },//never aims + { ACT_WALK_AIM_STIMULATED, ACT_WALK_AIM_CROSSBOW_STIMULATED, false }, + { ACT_WALK_AIM_AGITATED, ACT_WALK_AIM_CROSSBOW, false },//always aims + + { ACT_RUN_AIM_RELAXED, ACT_RUN_CROSSBOW_RELAXED, false },//never aims + { ACT_RUN_AIM_STIMULATED, ACT_RUN_AIM_CROSSBOW_STIMULATED, false }, + { ACT_RUN_AIM_AGITATED, ACT_RUN_AIM_CROSSBOW, false },//always aims +//End readiness activities + + { ACT_WALK, ACT_WALK_CROSSBOW, true }, + { ACT_WALK_AIM, ACT_WALK_AIM_CROSSBOW, true }, + { ACT_WALK_CROUCH, ACT_WALK_CROUCH_RIFLE, true }, + { ACT_WALK_CROUCH_AIM, ACT_WALK_CROUCH_AIM_RIFLE, true }, + { ACT_RUN, ACT_RUN_CROSSBOW, true }, + { ACT_RUN_AIM, ACT_RUN_AIM_CROSSBOW, true }, + { ACT_RUN_CROUCH, ACT_RUN_CROUCH_RIFLE, true }, + { ACT_RUN_CROUCH_AIM, ACT_RUN_CROUCH_AIM_RIFLE, true }, + { ACT_GESTURE_RANGE_ATTACK1, ACT_GESTURE_RANGE_ATTACK_CROSSBOW, true }, + { ACT_RANGE_ATTACK1_LOW, ACT_RANGE_ATTACK_CROSSBOW_LOW, true }, + { ACT_COVER_LOW, ACT_COVER_CROSSBOW_LOW, false }, + { ACT_RANGE_AIM_LOW, ACT_RANGE_AIM_CROSSBOW_LOW, false }, + { ACT_RELOAD_LOW, ACT_RELOAD_CROSSBOW_LOW, false }, + { ACT_GESTURE_RELOAD, ACT_GESTURE_RELOAD_CROSSBOW, true }, + + { ACT_ARM, ACT_ARM_RIFLE, false }, + { ACT_DISARM, ACT_DISARM_RIFLE, false }, +#else + { ACT_RANGE_ATTACK1, ACT_RANGE_ATTACK_SMG1, true }, + { ACT_RELOAD, ACT_RELOAD_SMG1, true }, + { ACT_IDLE, ACT_IDLE_SMG1, true }, + { ACT_IDLE_ANGRY, ACT_IDLE_ANGRY_SMG1, true }, + + { ACT_WALK, ACT_WALK_RIFLE, true }, + { ACT_WALK_AIM, ACT_WALK_AIM_RIFLE, true }, + +// Readiness activities (not aiming) + { ACT_IDLE_RELAXED, ACT_IDLE_SMG1_RELAXED, false },//never aims + { ACT_IDLE_STIMULATED, ACT_IDLE_SMG1_STIMULATED, false }, + { ACT_IDLE_AGITATED, ACT_IDLE_ANGRY_SMG1, false },//always aims + + { ACT_WALK_RELAXED, ACT_WALK_RIFLE_RELAXED, false },//never aims + { ACT_WALK_STIMULATED, ACT_WALK_RIFLE_STIMULATED, false }, + { ACT_WALK_AGITATED, ACT_WALK_AIM_RIFLE, false },//always aims + + { ACT_RUN_RELAXED, ACT_RUN_RIFLE_RELAXED, false },//never aims + { ACT_RUN_STIMULATED, ACT_RUN_RIFLE_STIMULATED, false }, + { ACT_RUN_AGITATED, ACT_RUN_AIM_RIFLE, false },//always aims + +// Readiness activities (aiming) + { ACT_IDLE_AIM_RELAXED, ACT_IDLE_SMG1_RELAXED, false },//never aims + { ACT_IDLE_AIM_STIMULATED, ACT_IDLE_AIM_RIFLE_STIMULATED, false }, + { ACT_IDLE_AIM_AGITATED, ACT_IDLE_ANGRY_SMG1, false },//always aims + + { ACT_WALK_AIM_RELAXED, ACT_WALK_RIFLE_RELAXED, false },//never aims + { ACT_WALK_AIM_STIMULATED, ACT_WALK_AIM_RIFLE_STIMULATED, false }, + { ACT_WALK_AIM_AGITATED, ACT_WALK_AIM_RIFLE, false },//always aims + + { ACT_RUN_AIM_RELAXED, ACT_RUN_RIFLE_RELAXED, false },//never aims + { ACT_RUN_AIM_STIMULATED, ACT_RUN_AIM_RIFLE_STIMULATED, false }, + { ACT_RUN_AIM_AGITATED, ACT_RUN_AIM_RIFLE, false },//always aims +//End readiness activities + + { ACT_WALK_AIM, ACT_WALK_AIM_RIFLE, true }, + { ACT_WALK_CROUCH, ACT_WALK_CROUCH_RIFLE, true }, + { ACT_WALK_CROUCH_AIM, ACT_WALK_CROUCH_AIM_RIFLE, true }, + { ACT_RUN, ACT_RUN_RIFLE, true }, + { ACT_RUN_AIM, ACT_RUN_AIM_RIFLE, true }, + { ACT_RUN_CROUCH, ACT_RUN_CROUCH_RIFLE, true }, + { ACT_RUN_CROUCH_AIM, ACT_RUN_CROUCH_AIM_RIFLE, true }, + { ACT_GESTURE_RANGE_ATTACK1, ACT_GESTURE_RANGE_ATTACK_SMG1, true }, + { ACT_RANGE_ATTACK1_LOW, ACT_RANGE_ATTACK_SMG1_LOW, true }, + { ACT_COVER_LOW, ACT_COVER_SMG1_LOW, false }, + { ACT_RANGE_AIM_LOW, ACT_RANGE_AIM_SMG1_LOW, false }, + { ACT_RELOAD_LOW, ACT_RELOAD_SMG1_LOW, false }, + { ACT_GESTURE_RELOAD, ACT_GESTURE_RELOAD_SMG1, true }, +#endif + +#if EXPANDED_HL2_COVER_ACTIVITIES + { ACT_RANGE_AIM_MED, ACT_RANGE_AIM_CROSSBOW_MED, false }, + { ACT_RANGE_ATTACK1_MED, ACT_RANGE_ATTACK_CROSSBOW_MED, false }, +#endif + +#ifdef MAPBASE + // HL2:DM activities (for third-person animations in SP) + { ACT_HL2MP_IDLE, ACT_HL2MP_IDLE_CROSSBOW, false }, + { ACT_HL2MP_RUN, ACT_HL2MP_RUN_CROSSBOW, false }, + { ACT_HL2MP_IDLE_CROUCH, ACT_HL2MP_IDLE_CROUCH_CROSSBOW, false }, + { ACT_HL2MP_WALK_CROUCH, ACT_HL2MP_WALK_CROUCH_CROSSBOW, false }, + { ACT_HL2MP_GESTURE_RANGE_ATTACK, ACT_HL2MP_GESTURE_RANGE_ATTACK_CROSSBOW, false }, + { ACT_HL2MP_GESTURE_RELOAD, ACT_HL2MP_GESTURE_RELOAD_CROSSBOW, false }, + { ACT_HL2MP_JUMP, ACT_HL2MP_JUMP_CROSSBOW, false }, +#if EXPANDED_HL2DM_ACTIVITIES + { ACT_HL2MP_WALK, ACT_HL2MP_WALK_CROSSBOW, false }, + { ACT_HL2MP_GESTURE_RANGE_ATTACK2, ACT_HL2MP_GESTURE_RANGE_ATTACK2_CROSSBOW, false }, +#endif +#endif +}; + +IMPLEMENT_ACTTABLE(CWeaponCrossbow); + +acttable_t* GetCrossbowActtable() +{ + return CWeaponCrossbow::m_acttable; +} + +int GetCrossbowActtableCount() +{ + return ARRAYSIZE(CWeaponCrossbow::m_acttable); +} +#endif + //----------------------------------------------------------------------------- // Purpose: Constructor //----------------------------------------------------------------------------- @@ -504,6 +784,13 @@ CWeaponCrossbow::CWeaponCrossbow( void ) m_bAltFiresUnderwater = true; m_bInZoom = false; m_bMustReload = false; + +#ifdef MAPBASE + m_fMinRange1 = 24; + m_fMaxRange1 = 5000; + m_fMinRange2 = 24; + m_fMaxRange2 = 5000; +#endif } #define CROSSBOW_GLOW_SPRITE "sprites/light_glow02_noz.vmt" @@ -551,6 +838,10 @@ void CWeaponCrossbow::PrimaryAttack( void ) { m_iPrimaryAttacks++; gamestats->Event_WeaponFired( pPlayer, true, GetClassname() ); + +#ifdef MAPBASE + pPlayer->SetAnimation( PLAYER_ATTACK1 ); +#endif } } @@ -577,6 +868,18 @@ bool CWeaponCrossbow::Reload( void ) return false; } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CWeaponCrossbow::Reload_NPC( bool bPlaySound ) +{ + BaseClass::Reload_NPC( bPlaySound ); + + SetBolt( 0 ); +} +#endif + //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- @@ -678,6 +981,10 @@ void CWeaponCrossbow::FireBolt( void ) m_iClip1--; +#ifdef MAPBASE + SetBolt( 1 ); +#endif + pOwner->ViewPunch( QAngle( -2, 0, 0 ) ); WeaponSound( SINGLE ); @@ -699,6 +1006,56 @@ void CWeaponCrossbow::FireBolt( void ) SetChargerState( CHARGER_STATE_DISCHARGE ); } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: Sets whether or not the bolt is visible +//----------------------------------------------------------------------------- +inline void CWeaponCrossbow::SetBolt( int iSetting ) +{ + int iBody = FindBodygroupByName( "bolt" ); + if (iBody != -1 /*|| (GetOwner() && GetOwner()->IsPlayer())*/) // TODO: Player models check the viewmodel instead of the worldmodel, but setting the bodygroup regardless can cause a crash, so we need a better solution + SetBodygroup( iBody, iSetting ); + else + m_nSkin = iSetting; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CWeaponCrossbow::FireNPCBolt( CAI_BaseNPC *pOwner, Vector &vecShootOrigin, Vector &vecShootDir ) +{ + Assert(pOwner); + + QAngle angAiming; + VectorAngles( vecShootDir, angAiming ); + + CCrossbowBolt *pBolt = CCrossbowBolt::BoltCreate( vecShootOrigin, angAiming, pOwner ); + + if ( pOwner->GetWaterLevel() == 3 ) + { + pBolt->SetAbsVelocity( vecShootDir * BOLT_WATER_VELOCITY ); + } + else + { + pBolt->SetAbsVelocity( vecShootDir * BOLT_AIR_VELOCITY ); + } + + m_iClip1--; + + SetBolt( 1 ); + + WeaponSound( SINGLE_NPC ); + WeaponSound( SPECIAL2 ); + + CSoundEnt::InsertSound( SOUND_COMBAT, GetAbsOrigin(), 200, 0.2 ); + + m_flNextPrimaryAttack = m_flNextSecondaryAttack = gpGlobals->curtime + 2.5f; + + SetSkin( BOLT_SKIN_GLOW ); + SetChargerState( CHARGER_STATE_DISCHARGE ); +} +#endif + //----------------------------------------------------------------------------- // Purpose: // Output : Returns true on success, false on failure. @@ -707,11 +1064,18 @@ bool CWeaponCrossbow::Deploy( void ) { if ( m_iClip1 <= 0 ) { +#ifdef MAPBASE + SetBolt( 1 ); +#endif return DefaultDeploy( (char*)GetViewModel(), (char*)GetWorldModel(), ACT_CROSSBOW_DRAW_UNLOADED, (char*)GetAnimPrefix() ); } SetSkin( BOLT_SKIN_GLOW ); +#ifdef MAPBASE + SetBolt( 0 ); +#endif + return BaseClass::Deploy(); } @@ -761,7 +1125,11 @@ void CWeaponCrossbow::CreateChargerEffects( void ) { CBasePlayer *pOwner = ToBasePlayer( GetOwner() ); +#ifdef MAPBASE + if ( m_hChargerSprite != NULL || pOwner == NULL ) +#else if ( m_hChargerSprite != NULL ) +#endif return; m_hChargerSprite = CSprite::SpriteCreate( CROSSBOW_GLOW_SPRITE, GetAbsOrigin(), false ); @@ -855,6 +1223,10 @@ void CWeaponCrossbow::SetChargerState( ChargerState_t state ) // Shoot some sparks and draw a beam between the two outer points DoLoadEffect(); +#ifdef MAPBASE + SetBolt( 0 ); +#endif + break; case CHARGER_STATE_START_CHARGE: @@ -933,12 +1305,46 @@ void CWeaponCrossbow::Operator_HandleAnimEvent( animevent_t *pEvent, CBaseCombat SetChargerState( CHARGER_STATE_READY ); break; +#ifdef MAPBASE + case EVENT_WEAPON_SMG1: + { + CAI_BaseNPC *pNPC = pOperator->MyNPCPointer(); + Assert(pNPC); + + Vector vecSrc = pNPC->Weapon_ShootPosition(); + Vector vecAiming = pNPC->GetActualShootTrajectory( vecSrc ); + + FireNPCBolt( pNPC, vecSrc, vecAiming ); + //m_bMustReload = true; + } + break; +#endif + default: BaseClass::Operator_HandleAnimEvent( pEvent, pOperator ); break; } } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CWeaponCrossbow::Operator_ForceNPCFire( CBaseCombatCharacter *pOperator, bool bSecondary ) +{ + // Ensure we have enough rounds in the clip + m_iClip1++; + + Vector vecShootOrigin, vecShootDir; + QAngle angShootDir; + GetAttachment( LookupAttachment( "muzzle" ), vecShootOrigin, angShootDir ); + AngleVectors( angShootDir, &vecShootDir ); + FireNPCBolt( pOperator->MyNPCPointer(), vecShootOrigin, vecShootDir ); + + //m_bMustReload = true; +} +#endif + //----------------------------------------------------------------------------- // Purpose: Set the desired activity for the weapon and its viewmodel counterpart // Input : iActivity - activity to play diff --git a/src/game/server/hl2/weapon_crowbar.cpp b/src/game/server/hl2/weapon_crowbar.cpp index fdbc7943..7cc34b9c 100644 --- a/src/game/server/hl2/weapon_crowbar.cpp +++ b/src/game/server/hl2/weapon_crowbar.cpp @@ -42,6 +42,29 @@ acttable_t CWeaponCrowbar::m_acttable[] = { ACT_MELEE_ATTACK1, ACT_MELEE_ATTACK_SWING, true }, { ACT_IDLE, ACT_IDLE_ANGRY_MELEE, false }, { ACT_IDLE_ANGRY, ACT_IDLE_ANGRY_MELEE, false }, +#if EXPANDED_HL2_WEAPON_ACTIVITIES + { ACT_RUN, ACT_RUN_MELEE, false }, + { ACT_WALK, ACT_WALK_MELEE, false }, + + { ACT_ARM, ACT_ARM_MELEE, false }, + { ACT_DISARM, ACT_DISARM_MELEE, false }, +#endif + +#ifdef MAPBASE + // HL2:DM activities (for third-person animations in SP) + { ACT_RANGE_ATTACK1, ACT_RANGE_ATTACK_SLAM, true }, + { ACT_HL2MP_IDLE, ACT_HL2MP_IDLE_MELEE, false }, + { ACT_HL2MP_RUN, ACT_HL2MP_RUN_MELEE, false }, + { ACT_HL2MP_IDLE_CROUCH, ACT_HL2MP_IDLE_CROUCH_MELEE, false }, + { ACT_HL2MP_WALK_CROUCH, ACT_HL2MP_WALK_CROUCH_MELEE, false }, + { ACT_HL2MP_GESTURE_RANGE_ATTACK, ACT_HL2MP_GESTURE_RANGE_ATTACK_MELEE, false }, + { ACT_HL2MP_GESTURE_RELOAD, ACT_HL2MP_GESTURE_RELOAD_MELEE, false }, + { ACT_HL2MP_JUMP, ACT_HL2MP_JUMP_MELEE, false }, +#if EXPANDED_HL2DM_ACTIVITIES + { ACT_HL2MP_GESTURE_RANGE_ATTACK2, ACT_HL2MP_GESTURE_RANGE_ATTACK2_MELEE, false }, + { ACT_HL2MP_WALK, ACT_HL2MP_WALK_MELEE, false }, +#endif +#endif }; IMPLEMENT_ACTTABLE(CWeaponCrowbar); diff --git a/src/game/server/hl2/weapon_crowbar.h b/src/game/server/hl2/weapon_crowbar.h index 1890ef6e..26bb961f 100644 --- a/src/game/server/hl2/weapon_crowbar.h +++ b/src/game/server/hl2/weapon_crowbar.h @@ -46,6 +46,12 @@ public: // Animation event virtual void Operator_HandleAnimEvent( animevent_t *pEvent, CBaseCombatCharacter *pOperator ); +#ifdef MAPBASE + // Don't use backup activities + acttable_t *GetBackupActivityList() { return NULL; } + int GetBackupActivityListCount() { return 0; } +#endif + private: // Animation event handlers void HandleAnimEventMeleeHit( animevent_t *pEvent, CBaseCombatCharacter *pOperator ); diff --git a/src/game/server/hl2/weapon_flaregun.cpp b/src/game/server/hl2/weapon_flaregun.cpp index d3fd6b15..f2b74c46 100644 --- a/src/game/server/hl2/weapon_flaregun.cpp +++ b/src/game/server/hl2/weapon_flaregun.cpp @@ -19,6 +19,7 @@ #include "tier0/memdbgon.h" +#ifndef MAPBASE /******************************************************************** NOTE: if you are looking at this file becase you would like flares to be considered as fires (and thereby trigger gas traps), be aware @@ -37,6 +38,13 @@ For some partial work towards this end, see changelist 192474. ********************************************************************/ +#else +// ================================================================ // +// I've fixed this...more or less. env_firesensor detects flares now. +// I tried to integrate it with the greater fire system, but I found that too difficult. +// I probably didn't try hard enough. You could fix this yourself if you think it's a big issue. +// ================================================================ // +#endif #define FLARE_LAUNCH_SPEED 1500 @@ -120,6 +128,21 @@ void KillFlare( CBaseEntity *pOwnerEntity, CBaseEntity *pEntity, float flKillTim } } +#ifdef MAPBASE +// For prop_flare debugging. +float GetEnvFlareLifetime( CBaseEntity *pEntity ) +{ + CFlare *pFlare = static_cast< CFlare *>( pEntity ); + + if ( pFlare ) + { + return pFlare->m_flTimeBurnOut - gpGlobals->curtime; + } + + return 0.0f; +} +#endif + //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- @@ -545,10 +568,13 @@ void CFlare::Start( float lifeTime ) //----------------------------------------------------------------------------- void CFlare::Die( float fadeTime ) { - m_flTimeBurnOut = gpGlobals->curtime + fadeTime; + if (m_bInActiveList) + { + m_flTimeBurnOut = gpGlobals->curtime + fadeTime; - SetThink( &CFlare::FlareThink ); - SetNextThink( gpGlobals->curtime + 0.1f ); + SetThink(&CFlare::FlareThink); + SetNextThink(gpGlobals->curtime + 0.1f); + } } //----------------------------------------------------------------------------- diff --git a/src/game/server/hl2/weapon_frag.cpp b/src/game/server/hl2/weapon_frag.cpp index b0e188aa..91eddcd3 100644 --- a/src/game/server/hl2/weapon_frag.cpp +++ b/src/game/server/hl2/weapon_frag.cpp @@ -82,6 +82,21 @@ END_DATADESC() acttable_t CWeaponFrag::m_acttable[] = { { ACT_RANGE_ATTACK1, ACT_RANGE_ATTACK_SLAM, true }, + +#ifdef MAPBASE + // HL2:DM activities (for third-person animations in SP) + { ACT_HL2MP_IDLE, ACT_HL2MP_IDLE_GRENADE, false }, + { ACT_HL2MP_RUN, ACT_HL2MP_RUN_GRENADE, false }, + { ACT_HL2MP_IDLE_CROUCH, ACT_HL2MP_IDLE_CROUCH_GRENADE, false }, + { ACT_HL2MP_WALK_CROUCH, ACT_HL2MP_WALK_CROUCH_GRENADE, false }, + { ACT_HL2MP_GESTURE_RANGE_ATTACK, ACT_HL2MP_GESTURE_RANGE_ATTACK_GRENADE, false }, + { ACT_HL2MP_GESTURE_RELOAD, ACT_HL2MP_GESTURE_RELOAD_GRENADE, false }, + { ACT_HL2MP_JUMP, ACT_HL2MP_JUMP_GRENADE, false }, +#if EXPANDED_HL2DM_ACTIVITIES + { ACT_HL2MP_WALK, ACT_HL2MP_WALK_GRENADE, false }, + { ACT_HL2MP_GESTURE_RANGE_ATTACK2, ACT_HL2MP_GESTURE_RANGE_ATTACK2_GRENADE, false }, +#endif +#endif }; IMPLEMENT_ACTTABLE(CWeaponFrag); @@ -404,6 +419,10 @@ void CWeaponFrag::ThrowGrenade( CBasePlayer *pPlayer ) WeaponSound( SINGLE ); +#ifdef MAPBASE + pPlayer->SetAnimation( PLAYER_ATTACK1 ); +#endif + m_iPrimaryAttacks++; gamestats->Event_WeaponFired( pPlayer, true, GetClassname() ); } @@ -428,6 +447,10 @@ void CWeaponFrag::LobGrenade( CBasePlayer *pPlayer ) WeaponSound( WPN_DOUBLE ); +#ifdef MAPBASE + pPlayer->SetAnimation( PLAYER_ATTACK2 ); +#endif + m_bRedraw = true; m_iPrimaryAttacks++; @@ -472,6 +495,10 @@ void CWeaponFrag::RollGrenade( CBasePlayer *pPlayer ) WeaponSound( SPECIAL1 ); +#ifdef MAPBASE + pPlayer->SetAnimation( PLAYER_ATTACK2 ); +#endif + m_bRedraw = true; m_iPrimaryAttacks++; diff --git a/src/game/server/hl2/weapon_physcannon.cpp b/src/game/server/hl2/weapon_physcannon.cpp index 7e49b7f4..c2a3c4ca 100644 --- a/src/game/server/hl2/weapon_physcannon.cpp +++ b/src/game/server/hl2/weapon_physcannon.cpp @@ -40,6 +40,9 @@ #include "ai_interactions.h" #include "rumble_shared.h" #include "gamestats.h" +#ifdef MAPBASE +#include "mapbase/GlobalStrings.h" +#endif // NVNT haptic utils #include "haptics/haptic_utils.h" @@ -65,6 +68,10 @@ ConVar player_throwforce( "player_throwforce", "1000" ); ConVar physcannon_dmg_glass( "physcannon_dmg_glass", "15" ); ConVar physcannon_right_turrets( "physcannon_right_turrets", "0" ); +#ifdef MAPBASE +ConVar sv_player_enable_propsprint("sv_player_enable_propsprint", "0", FCVAR_NONE, "If enabled, allows the player to sprint while holding a physics object" ); +ConVar sv_player_enable_gravgun_sprint("sv_player_enable_gravgun_sprint", "0", FCVAR_NONE, "Enables the player to sprint while holding a phys. object with the gravity gun" ); +#endif extern ConVar hl2_normspeed; extern ConVar hl2_walkspeed; @@ -141,6 +148,10 @@ public: // Handle grate entities differently if ( HasContentsGrate( pEntity ) ) { +#ifdef MAPBASE + if (pEntity->CanBePickedUpByPhyscannon()) + return true; +#else // See if it's a grabbable physics prop CPhysicsProp *pPhysProp = dynamic_cast(pEntity); if ( pPhysProp != NULL ) @@ -166,6 +177,7 @@ public: // Somehow had a classname that didn't match the class! Assert(0); } +#endif // Don't bother with any other sort of grated entity return false; @@ -438,10 +450,12 @@ static void ComputePlayerMatrix( CBasePlayer *pPlayer, matrix3x4_t &out ) // Purpose: //----------------------------------------------------------------------------- // derive from this so we can add save/load data to it +#ifndef MAPBASE // Moved to weapon_physcannon.h for point_physics_control struct game_shadowcontrol_params_t : public hlshadowcontrol_params_t { DECLARE_SIMPLE_DATADESC(); }; +#endif BEGIN_SIMPLE_DATADESC( game_shadowcontrol_params_t ) @@ -476,6 +490,9 @@ public: float GetLoadWeight( void ) const { return m_flLoadWeight; } void SetAngleAlignment( float alignAngleCosine ) { m_angleAlignment = alignAngleCosine; } void SetIgnorePitch( bool bIgnore ) { m_bIgnoreRelativePitch = bIgnore; } +#ifdef MAPBASE + void SetDontUseListMass( bool bDontUse ) { m_bDontUseListMass = bDontUse; } +#endif QAngle TransformAnglesToPlayerSpace( const QAngle &anglesIn, CBasePlayer *pPlayer ); QAngle TransformAnglesFromPlayerSpace( const QAngle &anglesIn, CBasePlayer *pPlayer ); @@ -517,6 +534,12 @@ private: // NVNT player controlling this grab controller CBasePlayer* m_pControllingPlayer; +#ifdef MAPBASE + // Prevents using the added mass of every part of the object + // (not saved due to only being used upon attach) + bool m_bDontUseListMass; +#endif + friend class CWeaponPhysCannon; }; @@ -567,6 +590,9 @@ CGrabController::CGrabController( void ) m_flDistanceOffset = 0; // NVNT constructing m_pControllingPlayer to NULL m_pControllingPlayer = NULL; +#ifdef MAPBASE + m_bDontUseListMass = false; +#endif } CGrabController::~CGrabController( void ) @@ -769,12 +795,18 @@ void CGrabController::AttachEntity( CBasePlayer *pPlayer, CBaseEntity *pEntity, { float mass = pList[i]->GetMass(); pList[i]->GetDamping( NULL, &m_savedRotDamping[i] ); - m_flLoadWeight += mass; m_savedMass[i] = mass; - // reduce the mass to prevent the player from adding crazy amounts of energy to the system - pList[i]->SetMass( REDUCED_CARRY_MASS / flFactor ); - pList[i]->SetDamping( NULL, &damping ); +#ifdef MAPBASE + if (!m_bDontUseListMass || pList[i] == pPhys) +#endif + { + m_flLoadWeight += mass; + + // reduce the mass to prevent the player from adding crazy amounts of energy to the system + pList[i]->SetMass( REDUCED_CARRY_MASS / flFactor ); + pList[i]->SetDamping( NULL, &damping ); + } } // NVNT setting m_pControllingPlayer to the player attached @@ -808,7 +840,14 @@ void CGrabController::AttachEntity( CBasePlayer *pPlayer, CBaseEntity *pEntity, CPhysicsProp *pProp = dynamic_cast(pEntity); if ( pProp ) { +#ifdef MAPBASE + // If the prop has custom carry angles, don't override them + // (regular PreferredCarryAngles() code should cover it) + if (!pProp->m_bUsesCustomCarryAngles) + m_bHasPreferredCarryAngles = pProp->GetPropDataAngles( "preferred_carryangles", m_vecPreferredCarryAngles ); +#else m_bHasPreferredCarryAngles = pProp->GetPropDataAngles( "preferred_carryangles", m_vecPreferredCarryAngles ); +#endif m_flDistanceOffset = pProp->GetCarryDistanceOffset(); } else @@ -1027,9 +1066,19 @@ void CPlayerPickupController::Init( CBasePlayer *pPlayer, CBaseEntity *pObject ) CHL2_Player *pOwner = (CHL2_Player *)ToBasePlayer( pPlayer ); if ( pOwner ) { +#ifndef MAPBASE pOwner->EnableSprint( false ); +#else + if ( sv_player_enable_propsprint.GetBool() == false ) + { + pOwner->EnableSprint( false ); + } + else + { + pOwner->EnableSprint( true ); + } +#endif } - // If the target is debris, convert it to non-debris if ( pObject->GetCollisionGroup() == COLLISION_GROUP_DEBRIS ) { @@ -1046,7 +1095,24 @@ void CPlayerPickupController::Init( CBasePlayer *pPlayer, CBaseEntity *pObject ) Pickup_OnPhysGunPickup( pObject, m_pPlayer, PICKED_UP_BY_PLAYER ); +#ifdef MAPBASE + bool bUseGrabPos = false; + Vector vecGrabPos; + if ( dynamic_cast( pObject ) ) + { + m_grabController.SetDontUseListMass( true ); + + // Approximate where we're grabbing from + vecGrabPos = pPlayer->EyePosition() + (pPlayer->EyeDirection3D() * 16.0f); + bUseGrabPos = true; + } + else + m_grabController.SetDontUseListMass( false ); + + m_grabController.AttachEntity( pPlayer, pObject, pPhysics, false, vecGrabPos, bUseGrabPos ); +#else m_grabController.AttachEntity( pPlayer, pObject, pPhysics, false, vec3_origin, false ); +#endif // NVNT apply a downward force to simulate the mass of the held object. #if defined( WIN32 ) && !defined( _X360 ) HapticSetConstantForce(m_pPlayer,clamp(m_grabController.GetLoadWeight()*0.1,1,6)*Vector(0,-1,0)); @@ -1085,10 +1151,17 @@ void CPlayerPickupController::Shutdown( bool bThrown ) if ( m_pPlayer ) { CHL2_Player *pOwner = (CHL2_Player *)ToBasePlayer( m_pPlayer ); +#ifndef MAPBASE if ( pOwner ) { pOwner->EnableSprint( true ); } +#else + if ( pOwner && sv_player_enable_propsprint.GetBool() == false ) + { + pOwner->EnableSprint( true ); + } +#endif m_pPlayer->SetUseEntity( NULL ); if ( m_pPlayer->GetActiveWeapon() ) @@ -1146,7 +1219,11 @@ void CPlayerPickupController::Use( CBaseEntity *pActivator, CBaseEntity *pCaller Vector vecLaunch; m_pPlayer->EyeVectors( &vecLaunch ); // JAY: Scale this with mass because some small objects really go flying +#ifdef MAPBASE + float massFactor = pPhys ? clamp( pPhys->GetMass(), 0.5, 15 ) : 7.5; +#else float massFactor = clamp( pPhys->GetMass(), 0.5, 15 ); +#endif massFactor = RemapVal( massFactor, 0.5, 15, 0.5, 4 ); vecLaunch *= player_throwforce.GetFloat() * massFactor; @@ -1217,6 +1294,9 @@ public: DECLARE_SERVERCLASS(); DECLARE_DATADESC(); +#ifdef MAPBASE + DECLARE_ACTTABLE(); +#endif CWeaponPhysCannon( void ); @@ -1434,6 +1514,30 @@ BEGIN_DATADESC( CWeaponPhysCannon ) END_DATADESC() +#ifdef MAPBASE +acttable_t CWeaponPhysCannon::m_acttable[] = +{ + // HL2:DM activities (for third-person animations in SP) + { ACT_HL2MP_IDLE, ACT_HL2MP_IDLE_PHYSGUN, false }, + { ACT_HL2MP_RUN, ACT_HL2MP_RUN_PHYSGUN, false }, + { ACT_HL2MP_IDLE_CROUCH, ACT_HL2MP_IDLE_CROUCH_PHYSGUN, false }, + { ACT_HL2MP_WALK_CROUCH, ACT_HL2MP_WALK_CROUCH_PHYSGUN, false }, + { ACT_HL2MP_GESTURE_RANGE_ATTACK, ACT_HL2MP_GESTURE_RANGE_ATTACK_PHYSGUN, false }, + { ACT_HL2MP_GESTURE_RELOAD, ACT_HL2MP_GESTURE_RELOAD_PHYSGUN, false }, + { ACT_HL2MP_JUMP, ACT_HL2MP_JUMP_PHYSGUN, false }, + { ACT_RANGE_ATTACK1, ACT_RANGE_ATTACK_SLAM, false }, +#if EXPANDED_HL2DM_ACTIVITIES + { ACT_HL2MP_WALK, ACT_HL2MP_WALK_PHYSGUN, false }, + { ACT_HL2MP_GESTURE_RANGE_ATTACK2, ACT_HL2MP_GESTURE_RANGE_ATTACK2_PHYSGUN, false }, +#endif + + { ACT_ARM, ACT_ARM_RIFLE, false }, + { ACT_DISARM, ACT_DISARM_RIFLE, false }, +}; + +IMPLEMENT_ACTTABLE( CWeaponPhysCannon ); +#endif + enum { @@ -1717,6 +1821,9 @@ void CWeaponPhysCannon::DryFire( void ) if ( pOwner ) { pOwner->RumbleEffect( RUMBLE_PISTOL, 0, RUMBLE_FLAG_RESTART ); +#ifdef MAPBASE // TODO: Is this animation too dramatic? + pOwner->SetAnimation( PLAYER_ATTACK1 ); +#endif } } @@ -1773,6 +1880,11 @@ void CWeaponPhysCannon::PuntNonVPhysics( CBaseEntity *pEntity, const Vector &for PrimaryFireEffect(); SendWeaponAnim( ACT_VM_SECONDARYATTACK ); +#ifdef MAPBASE + CBasePlayer *pPlayer = ToBasePlayer( GetOwner() ); + if (pPlayer) + pPlayer->SetAnimation( PLAYER_ATTACK1 ); +#endif m_nChangeState = ELEMENT_STATE_CLOSED; m_flElementDebounce = gpGlobals->curtime + 0.5f; @@ -1923,6 +2035,10 @@ void CWeaponPhysCannon::PuntVPhysics( CBaseEntity *pEntity, const Vector &vecFor PrimaryFireEffect(); SendWeaponAnim( ACT_VM_SECONDARYATTACK ); +#ifdef MAPBASE + pOwner->SetAnimation( PLAYER_ATTACK1 ); +#endif + m_nChangeState = ELEMENT_STATE_CLOSED; m_flElementDebounce = gpGlobals->curtime + 0.5f; m_flCheckSuppressTime = gpGlobals->curtime + 0.25f; @@ -2041,6 +2157,10 @@ void CWeaponPhysCannon::PuntRagdoll( CBaseEntity *pEntity, const Vector &vecForw PrimaryFireEffect(); SendWeaponAnim( ACT_VM_SECONDARYATTACK ); +#ifdef MAPBASE + pOwner->SetAnimation( PLAYER_ATTACK1 ); +#endif + m_nChangeState = ELEMENT_STATE_CLOSED; m_flElementDebounce = gpGlobals->curtime + 0.5f; m_flCheckSuppressTime = gpGlobals->curtime + 0.25f; @@ -2084,6 +2204,10 @@ bool CWeaponPhysCannon::EntityAllowsPunts( CBaseEntity *pEntity ) if ( pEntity->HasSpawnFlags( SF_WEAPON_NO_PHYSCANNON_PUNT ) ) { +#ifdef MAPBASE + if (pEntity->IsBaseCombatWeapon() || pEntity->IsCombatItem()) + return false; +#else CBaseCombatWeapon *pWeapon = dynamic_cast(pEntity); if ( pWeapon != NULL ) @@ -2093,6 +2217,7 @@ bool CWeaponPhysCannon::EntityAllowsPunts( CBaseEntity *pEntity ) return false; } } +#endif } return true; @@ -2141,6 +2266,9 @@ void CWeaponPhysCannon::PrimaryAttack( void ) PrimaryFireEffect(); SendWeaponAnim( ACT_VM_SECONDARYATTACK ); +#ifdef MAPBASE + pOwner->SetAnimation( PLAYER_ATTACK1 ); +#endif return; } @@ -2425,6 +2553,7 @@ bool CWeaponPhysCannon::AttachObject( CBaseEntity *pObject, const Vector &vPosit // NVNT set the players constant force to simulate holding mass HapticSetConstantForce(pOwner,clamp(m_grabController.GetLoadWeight()*0.05,1,5)*Vector(0,-1,0)); #endif +#ifndef MAPBASE pOwner->EnableSprint( false ); float loadWeight = ( 1.0f - GetLoadPercentage() ); @@ -2432,6 +2561,22 @@ bool CWeaponPhysCannon::AttachObject( CBaseEntity *pObject, const Vector &vPosit //Msg( "Load perc: %f -- Movement speed: %f/%f\n", loadWeight, maxSpeed, hl2_normspeed.GetFloat() ); pOwner->SetMaxSpeed( maxSpeed ); +#else + if ( sv_player_enable_gravgun_sprint.GetBool() == false ) + { + pOwner->EnableSprint( false ); + + float loadWeight = ( 1.0f - GetLoadPercentage() ); + float maxSpeed = hl2_walkspeed.GetFloat() + ( ( hl2_normspeed.GetFloat() - hl2_walkspeed.GetFloat() ) * loadWeight ); + + //Msg( "Load perc: %f -- Movement speed: %f/%f\n", loadWeight, maxSpeed, hl2_normspeed.GetFloat() ); + pOwner->SetMaxSpeed( maxSpeed ); + } + else + { + pOwner->EnableSprint( true ); + } +#endif } // Don't drop again for a slight delay, in case they were pulling objects near them @@ -2448,7 +2593,11 @@ bool CWeaponPhysCannon::AttachObject( CBaseEntity *pObject, const Vector &vPosit } #if defined(HL2_DLL) +#ifdef MAPBASE + if( physcannon_right_turrets.GetBool() && EntIsClass(pObject, gm_isz_class_FloorTurret) ) +#else if( physcannon_right_turrets.GetBool() && pObject->ClassMatches("npc_turret_floor") ) +#endif { // We just picked up a turret. Is it already upright? Vector vecUp; @@ -2609,6 +2758,11 @@ CWeaponPhysCannon::FindObjectResult_t CWeaponPhysCannon::FindObject( void ) pullDir *= (mass + 0.5) * (1/50.0f); } + CPhysicsProp* pProp = dynamic_cast(pEntity); + if (pProp) { + pProp->OnPhysGunPull( pOwner ); + } + // Nudge it towards us pObj->ApplyForceCenter( pullDir ); return OBJECT_NOT_FOUND; @@ -2874,9 +3028,17 @@ void CWeaponPhysCannon::DetachObject( bool playSound, bool wasLaunched ) CHL2_Player *pOwner = (CHL2_Player *)ToBasePlayer( GetOwner() ); if( pOwner != NULL ) { +#ifndef MAPBASE pOwner->EnableSprint( true ); pOwner->SetMaxSpeed( hl2_normspeed.GetFloat() ); +#else + if (sv_player_enable_gravgun_sprint.GetBool() == false) + { + pOwner->EnableSprint( true ); + pOwner->SetMaxSpeed( hl2_normspeed.GetFloat() ); + } +#endif if( wasLaunched ) { pOwner->RumbleEffect( RUMBLE_357, 0, RUMBLE_FLAG_RESTART ); @@ -3287,6 +3449,15 @@ void CWeaponPhysCannon::ItemPostFrame() return; } +#ifdef MAPBASE + if (pOwner->HasSpawnFlags( SF_PLAYER_SUPPRESS_FIRING )) + { + m_nAttack2Debounce = 0; + WeaponIdle(); + return; + } +#endif + //Check for object in pickup range if ( m_bActive == false ) { @@ -3459,6 +3630,12 @@ bool CWeaponPhysCannon::CanPickupObject( CBaseEntity *pTarget ) if ( pOwner && pOwner->GetGroundEntity() == pTarget ) return false; +#ifdef MAPBASE + // The gravity gun can't pick up vehicles. + if ( pTarget->GetServerVehicle() ) + return false; +#endif + if ( !IsMegaPhysCannon() ) { if ( pTarget->VPhysicsIsFlesh( ) ) diff --git a/src/game/server/hl2/weapon_physcannon.h b/src/game/server/hl2/weapon_physcannon.h index d7ce48ee..01356d26 100644 --- a/src/game/server/hl2/weapon_physcannon.h +++ b/src/game/server/hl2/weapon_physcannon.h @@ -30,4 +30,11 @@ CBaseEntity *GetPlayerHeldEntity( CBasePlayer *pPlayer ); bool PhysCannonAccountableForObject( CBaseCombatWeapon *pPhysCannon, CBaseEntity *pObject ); +#ifdef MAPBASE // Moved here so point_physics_control can access, datadesc is still in weapon_physcannon.cpp +struct game_shadowcontrol_params_t : public hlshadowcontrol_params_t +{ + DECLARE_SIMPLE_DATADESC(); +}; +#endif + #endif // WEAPON_PHYSCANNON_H diff --git a/src/game/server/hl2/weapon_pistol.cpp b/src/game/server/hl2/weapon_pistol.cpp index d0a25412..8a49f366 100644 --- a/src/game/server/hl2/weapon_pistol.cpp +++ b/src/game/server/hl2/weapon_pistol.cpp @@ -52,6 +52,10 @@ public: void AddViewKick( void ); void DryFire( void ); void Operator_HandleAnimEvent( animevent_t *pEvent, CBaseCombatCharacter *pOperator ); +#ifdef MAPBASE + void FireNPCPrimaryAttack( CBaseCombatCharacter *pOperator, Vector &vecShootOrigin, Vector &vecShootDir ); + void Operator_ForceNPCFire( CBaseCombatCharacter *pOperator, bool bSecondary ); +#endif void UpdatePenaltyTime( void ); @@ -104,6 +108,12 @@ public: return 0.5f; } +#ifdef MAPBASE + // Pistols are their own backup activities + virtual acttable_t *GetBackupActivityList() { return NULL; } + virtual int GetBackupActivityListCount() { return 0; } +#endif + DECLARE_ACTTABLE(); private: @@ -145,11 +155,141 @@ acttable_t CWeaponPistol::m_acttable[] = { ACT_GESTURE_RELOAD, ACT_GESTURE_RELOAD_PISTOL, false }, { ACT_WALK, ACT_WALK_PISTOL, false }, { ACT_RUN, ACT_RUN_PISTOL, false }, + +#ifdef MAPBASE + // + // Activities ported from weapon_alyxgun below + // + +#if EXPANDED_HL2_WEAPON_ACTIVITIES + // Readiness activities (not aiming) + { ACT_IDLE_RELAXED, ACT_IDLE_PISTOL_RELAXED, false },//never aims + { ACT_IDLE_STIMULATED, ACT_IDLE_PISTOL_STIMULATED, false }, + { ACT_IDLE_AGITATED, ACT_IDLE_ANGRY_PISTOL, false },//always aims + { ACT_IDLE_STEALTH, ACT_IDLE_STEALTH_PISTOL, false }, + + { ACT_WALK_RELAXED, ACT_WALK_PISTOL_RELAXED, false },//never aims + { ACT_WALK_STIMULATED, ACT_WALK_PISTOL_STIMULATED, false }, + { ACT_WALK_AGITATED, ACT_WALK_AIM_PISTOL, false },//always aims + { ACT_WALK_STEALTH, ACT_WALK_STEALTH_PISTOL, false }, + + { ACT_RUN_RELAXED, ACT_RUN_PISTOL_RELAXED, false },//never aims + { ACT_RUN_STIMULATED, ACT_RUN_PISTOL_STIMULATED, false }, + { ACT_RUN_AGITATED, ACT_RUN_AIM_PISTOL, false },//always aims + { ACT_RUN_STEALTH, ACT_RUN_STEALTH_PISTOL, false }, + + // Readiness activities (aiming) + { ACT_IDLE_AIM_RELAXED, ACT_IDLE_PISTOL_RELAXED, false },//never aims + { ACT_IDLE_AIM_STIMULATED, ACT_IDLE_AIM_PISTOL_STIMULATED, false }, + { ACT_IDLE_AIM_AGITATED, ACT_IDLE_ANGRY_PISTOL, false },//always aims + { ACT_IDLE_AIM_STEALTH, ACT_IDLE_STEALTH_PISTOL, false }, + + { ACT_WALK_AIM_RELAXED, ACT_WALK_PISTOL_RELAXED, false },//never aims + { ACT_WALK_AIM_STIMULATED, ACT_WALK_AIM_PISTOL, false }, + { ACT_WALK_AIM_AGITATED, ACT_WALK_AIM_PISTOL, false },//always aims + { ACT_WALK_AIM_STEALTH, ACT_WALK_AIM_STEALTH_PISTOL, false },//always aims + + { ACT_RUN_AIM_RELAXED, ACT_RUN_PISTOL_RELAXED, false },//never aims + { ACT_RUN_AIM_STIMULATED, ACT_RUN_AIM_PISTOL, false }, + { ACT_RUN_AIM_AGITATED, ACT_RUN_AIM_PISTOL, false },//always aims + { ACT_RUN_AIM_STEALTH, ACT_RUN_AIM_STEALTH_PISTOL, false },//always aims + //End readiness activities +#else + // Readiness activities (not aiming) + { ACT_IDLE_RELAXED, ACT_IDLE_PISTOL, false },//never aims + { ACT_IDLE_STIMULATED, ACT_IDLE_STIMULATED, false }, + { ACT_IDLE_AGITATED, ACT_IDLE_ANGRY_PISTOL, false },//always aims + { ACT_IDLE_STEALTH, ACT_IDLE_STEALTH_PISTOL, false }, + + { ACT_WALK_RELAXED, ACT_WALK, false },//never aims + { ACT_WALK_STIMULATED, ACT_WALK_STIMULATED, false }, + { ACT_WALK_AGITATED, ACT_WALK_AIM_PISTOL, false },//always aims + { ACT_WALK_STEALTH, ACT_WALK_STEALTH_PISTOL, false }, + + { ACT_RUN_RELAXED, ACT_RUN, false },//never aims + { ACT_RUN_STIMULATED, ACT_RUN_STIMULATED, false }, + { ACT_RUN_AGITATED, ACT_RUN_AIM_PISTOL, false },//always aims + { ACT_RUN_STEALTH, ACT_RUN_STEALTH_PISTOL, false }, + + // Readiness activities (aiming) + { ACT_IDLE_AIM_RELAXED, ACT_IDLE_PISTOL, false },//never aims + { ACT_IDLE_AIM_STIMULATED, ACT_IDLE_ANGRY_PISTOL, false }, + { ACT_IDLE_AIM_AGITATED, ACT_IDLE_ANGRY_PISTOL, false },//always aims + { ACT_IDLE_AIM_STEALTH, ACT_IDLE_STEALTH_PISTOL, false }, + + { ACT_WALK_AIM_RELAXED, ACT_WALK, false },//never aims + { ACT_WALK_AIM_STIMULATED, ACT_WALK_AIM_PISTOL, false }, + { ACT_WALK_AIM_AGITATED, ACT_WALK_AIM_PISTOL, false },//always aims + { ACT_WALK_AIM_STEALTH, ACT_WALK_AIM_STEALTH_PISTOL, false },//always aims + + { ACT_RUN_AIM_RELAXED, ACT_RUN, false },//never aims + { ACT_RUN_AIM_STIMULATED, ACT_RUN_AIM_PISTOL, false }, + { ACT_RUN_AIM_AGITATED, ACT_RUN_AIM_PISTOL, false },//always aims + { ACT_RUN_AIM_STEALTH, ACT_RUN_AIM_STEALTH_PISTOL, false },//always aims + //End readiness activities +#endif + + // Crouch activities + { ACT_CROUCHIDLE_STIMULATED, ACT_CROUCHIDLE_STIMULATED, false }, + { ACT_CROUCHIDLE_AIM_STIMULATED,ACT_RANGE_AIM_PISTOL_LOW, false },//always aims + { ACT_CROUCHIDLE_AGITATED, ACT_RANGE_AIM_PISTOL_LOW, false },//always aims + + // Readiness translations + { ACT_READINESS_RELAXED_TO_STIMULATED, ACT_READINESS_PISTOL_RELAXED_TO_STIMULATED, false }, + { ACT_READINESS_RELAXED_TO_STIMULATED_WALK, ACT_READINESS_PISTOL_RELAXED_TO_STIMULATED_WALK, false }, + { ACT_READINESS_AGITATED_TO_STIMULATED, ACT_READINESS_PISTOL_AGITATED_TO_STIMULATED, false }, + { ACT_READINESS_STIMULATED_TO_RELAXED, ACT_READINESS_PISTOL_STIMULATED_TO_RELAXED, false }, +#endif + +#if EXPANDED_HL2_WEAPON_ACTIVITIES + { ACT_WALK_CROUCH, ACT_WALK_CROUCH_PISTOL, true }, + { ACT_WALK_CROUCH_AIM, ACT_WALK_CROUCH_AIM_PISTOL, true }, + { ACT_RUN_CROUCH, ACT_RUN_CROUCH_PISTOL, true }, + { ACT_RUN_CROUCH_AIM, ACT_RUN_CROUCH_AIM_PISTOL, true }, +#endif + +#if EXPANDED_HL2_COVER_ACTIVITIES + { ACT_RANGE_AIM_MED, ACT_RANGE_AIM_PISTOL_MED, false }, + { ACT_RANGE_ATTACK1_MED, ACT_RANGE_ATTACK_PISTOL_MED, false }, + + { ACT_COVER_WALL_R, ACT_COVER_WALL_R_PISTOL, false }, + { ACT_COVER_WALL_L, ACT_COVER_WALL_L_PISTOL, false }, + { ACT_COVER_WALL_LOW_R, ACT_COVER_WALL_LOW_R_PISTOL, false }, + { ACT_COVER_WALL_LOW_L, ACT_COVER_WALL_LOW_L_PISTOL, false }, +#endif + +#ifdef MAPBASE + // HL2:DM activities (for third-person animations in SP) + { ACT_HL2MP_IDLE, ACT_HL2MP_IDLE_PISTOL, false }, + { ACT_HL2MP_RUN, ACT_HL2MP_RUN_PISTOL, false }, + { ACT_HL2MP_IDLE_CROUCH, ACT_HL2MP_IDLE_CROUCH_PISTOL, false }, + { ACT_HL2MP_WALK_CROUCH, ACT_HL2MP_WALK_CROUCH_PISTOL, false }, + { ACT_HL2MP_GESTURE_RANGE_ATTACK, ACT_HL2MP_GESTURE_RANGE_ATTACK_PISTOL, false }, + { ACT_HL2MP_GESTURE_RELOAD, ACT_HL2MP_GESTURE_RELOAD_PISTOL, false }, + { ACT_HL2MP_JUMP, ACT_HL2MP_JUMP_PISTOL, false }, +#if EXPANDED_HL2DM_ACTIVITIES + { ACT_HL2MP_WALK, ACT_HL2MP_WALK_PISTOL, false }, + { ACT_HL2MP_GESTURE_RANGE_ATTACK2, ACT_HL2MP_GESTURE_RANGE_ATTACK2_PISTOL, false }, +#endif +#endif }; IMPLEMENT_ACTTABLE( CWeaponPistol ); +#ifdef MAPBASE +// Allows Weapon_BackupActivity() to access the pistol's activity table. +acttable_t *GetPistolActtable() +{ + return CWeaponPistol::m_acttable; +} + +int GetPistolActtableCount() +{ + return ARRAYSIZE(CWeaponPistol::m_acttable); +} +#endif + //----------------------------------------------------------------------------- // Purpose: Constructor //----------------------------------------------------------------------------- @@ -193,12 +333,16 @@ void CWeaponPistol::Operator_HandleAnimEvent( animevent_t *pEvent, CBaseCombatCh vecShootDir = npc->GetActualShootTrajectory( vecShootOrigin ); +#ifdef MAPBASE + FireNPCPrimaryAttack( pOperator, vecShootOrigin, vecShootDir ); +#else CSoundEnt::InsertSound( SOUND_COMBAT|SOUND_CONTEXT_GUNFIRE, pOperator->GetAbsOrigin(), SOUNDENT_VOLUME_PISTOL, 0.2, pOperator, SOUNDENT_CHANNEL_WEAPON, pOperator->GetEnemy() ); WeaponSound( SINGLE_NPC ); pOperator->FireBullets( 1, vecShootOrigin, vecShootDir, VECTOR_CONE_PRECALCULATED, MAX_TRACE_LENGTH, m_iPrimaryAmmoType, 2 ); pOperator->DoMuzzleFlash(); m_iClip1 = m_iClip1 - 1; +#endif } break; default: @@ -207,6 +351,36 @@ void CWeaponPistol::Operator_HandleAnimEvent( animevent_t *pEvent, CBaseCombatCh } } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CWeaponPistol::FireNPCPrimaryAttack( CBaseCombatCharacter *pOperator, Vector &vecShootOrigin, Vector &vecShootDir ) +{ + CSoundEnt::InsertSound( SOUND_COMBAT|SOUND_CONTEXT_GUNFIRE, pOperator->GetAbsOrigin(), SOUNDENT_VOLUME_PISTOL, 0.2, pOperator, SOUNDENT_CHANNEL_WEAPON, pOperator->GetEnemy() ); + + WeaponSound( SINGLE_NPC ); + pOperator->FireBullets( 1, vecShootOrigin, vecShootDir, VECTOR_CONE_PRECALCULATED, MAX_TRACE_LENGTH, m_iPrimaryAmmoType, 2 ); + pOperator->DoMuzzleFlash(); + m_iClip1 = m_iClip1 - 1; +} + +//----------------------------------------------------------------------------- +// Purpose: Some things need this. (e.g. the new Force(X)Fire inputs or blindfire actbusy) +//----------------------------------------------------------------------------- +void CWeaponPistol::Operator_ForceNPCFire( CBaseCombatCharacter *pOperator, bool bSecondary ) +{ + // Ensure we have enough rounds in the clip + m_iClip1++; + + Vector vecShootOrigin, vecShootDir; + QAngle angShootDir; + GetAttachment( LookupAttachment( "muzzle" ), vecShootOrigin, angShootDir ); + AngleVectors( angShootDir, &vecShootDir ); + FireNPCPrimaryAttack( pOperator, vecShootOrigin, vecShootDir ); +} +#endif + //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- @@ -352,6 +526,10 @@ bool CWeaponPistol::Reload( void ) return fRet; } +#ifdef MAPBASE +ConVar weapon_pistol_upwards_viewkick( "weapon_pistol_upwards_viewkick", "0" ); +#endif + //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- @@ -364,7 +542,11 @@ void CWeaponPistol::AddViewKick( void ) QAngle viewPunch; +#ifdef MAPBASE + viewPunch.x = weapon_pistol_upwards_viewkick.GetBool() ? random->RandomFloat( -0.5f, -0.25f ) : random->RandomFloat( 0.25f, 0.5f ); +#else viewPunch.x = random->RandomFloat( 0.25f, 0.5f ); +#endif viewPunch.y = random->RandomFloat( -.6f, .6f ); viewPunch.z = 0.0f; diff --git a/src/game/server/hl2/weapon_rpg.cpp b/src/game/server/hl2/weapon_rpg.cpp index e6278593..1ffc0326 100644 --- a/src/game/server/hl2/weapon_rpg.cpp +++ b/src/game/server/hl2/weapon_rpg.cpp @@ -43,6 +43,10 @@ static ConVar sk_apc_missile_damage("sk_apc_missile_damage", "15"); ConVar rpg_missle_use_custom_detonators( "rpg_missle_use_custom_detonators", "1" ); +#ifdef MAPBASE +ConVar weapon_rpg_use_old_behavior( "weapon_rpg_use_old_behavior", "0" ); +ConVar weapon_rpg_fire_rate( "weapon_rpg_fire_rate", "4.0" ); +#endif #define APC_MISSILE_DAMAGE sk_apc_missile_damage.GetFloat() @@ -1395,10 +1399,22 @@ PRECACHE_WEAPON_REGISTER(weapon_rpg); acttable_t CWeaponRPG::m_acttable[] = { { ACT_RANGE_ATTACK1, ACT_RANGE_ATTACK_RPG, true }, +#if EXPANDED_HL2_WEAPON_ACTIVITIES + { ACT_RANGE_AIM_LOW, ACT_RANGE_AIM_RPG_LOW, false }, + { ACT_RANGE_ATTACK1_LOW, ACT_RANGE_ATTACK_RPG_LOW, false }, + { ACT_GESTURE_RANGE_ATTACK1, ACT_GESTURE_RANGE_ATTACK_RPG, false }, +#endif +#ifdef MAPBASE + // Readiness activities should not be required + { ACT_IDLE_RELAXED, ACT_IDLE_RPG_RELAXED, false }, + { ACT_IDLE_STIMULATED, ACT_IDLE_ANGRY_RPG, false }, + { ACT_IDLE_AGITATED, ACT_IDLE_ANGRY_RPG, false }, +#else { ACT_IDLE_RELAXED, ACT_IDLE_RPG_RELAXED, true }, { ACT_IDLE_STIMULATED, ACT_IDLE_ANGRY_RPG, true }, { ACT_IDLE_AGITATED, ACT_IDLE_ANGRY_RPG, true }, +#endif { ACT_IDLE, ACT_IDLE_RPG, true }, { ACT_IDLE_ANGRY, ACT_IDLE_ANGRY_RPG, true }, @@ -1407,6 +1423,31 @@ acttable_t CWeaponRPG::m_acttable[] = { ACT_RUN, ACT_RUN_RPG, true }, { ACT_RUN_CROUCH, ACT_RUN_CROUCH_RPG, true }, { ACT_COVER_LOW, ACT_COVER_LOW_RPG, true }, + +#if EXPANDED_HL2_WEAPON_ACTIVITIES + { ACT_ARM, ACT_ARM_RPG, false }, + { ACT_DISARM, ACT_DISARM_RPG, false }, +#endif + +#if EXPANDED_HL2_COVER_ACTIVITIES + { ACT_RANGE_AIM_MED, ACT_RANGE_AIM_RPG_MED, false }, + { ACT_RANGE_ATTACK1_MED, ACT_RANGE_ATTACK_RPG_MED, false }, +#endif + +#ifdef MAPBASE + // HL2:DM activities (for third-person animations in SP) + { ACT_HL2MP_IDLE, ACT_HL2MP_IDLE_RPG, false }, + { ACT_HL2MP_RUN, ACT_HL2MP_RUN_RPG, false }, + { ACT_HL2MP_IDLE_CROUCH, ACT_HL2MP_IDLE_CROUCH_RPG, false }, + { ACT_HL2MP_WALK_CROUCH, ACT_HL2MP_WALK_CROUCH_RPG, false }, + { ACT_HL2MP_GESTURE_RANGE_ATTACK, ACT_HL2MP_GESTURE_RANGE_ATTACK_RPG, false }, + { ACT_HL2MP_GESTURE_RELOAD, ACT_HL2MP_GESTURE_RELOAD_RPG, false }, + { ACT_HL2MP_JUMP, ACT_HL2MP_JUMP_RPG, false }, +#if EXPANDED_HL2DM_ACTIVITIES + { ACT_HL2MP_WALK, ACT_HL2MP_WALK_RPG, false }, + { ACT_HL2MP_GESTURE_RANGE_ATTACK2, ACT_HL2MP_GESTURE_RANGE_ATTACK2_RPG, false }, +#endif +#endif }; IMPLEMENT_ACTTABLE(CWeaponRPG); @@ -1555,6 +1596,54 @@ void CWeaponRPG::Operator_HandleAnimEvent( animevent_t *pEvent, CBaseCombatChara } } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CWeaponRPG::Operator_ForceNPCFire( CBaseCombatCharacter *pOperator, bool bSecondary ) +{ + if ( m_hMissile != NULL ) + return; + + Vector muzzlePoint, vecShootDir; + QAngle angShootDir; + GetAttachment( LookupAttachment( "muzzle" ), muzzlePoint, angShootDir ); + AngleVectors( angShootDir, &vecShootDir ); + + // look for a better launch location + Vector altLaunchPoint; + if (GetAttachment( "missile", altLaunchPoint )) + { + // check to see if it's relativly free + trace_t tr; + AI_TraceHull( altLaunchPoint, altLaunchPoint + vecShootDir * (10.0f*12.0f), Vector( -24, -24, -24 ), Vector( 24, 24, 24 ), MASK_NPCSOLID, NULL, &tr ); + + if( tr.fraction == 1.0) + { + muzzlePoint = altLaunchPoint; + } + } + + m_hMissile = CMissile::Create( muzzlePoint, angShootDir, pOperator->edict() ); + m_hMissile->m_hOwner = this; + + // NPCs always get a grace period + m_hMissile->SetGracePeriod( 0.5 ); + + pOperator->DoMuzzleFlash(); + + WeaponSound( SINGLE_NPC ); + + // Make sure our laserdot is off + m_bGuiding = false; + + if ( m_hLaserDot ) + { + m_hLaserDot->TurnOff(); + } +} +#endif + //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- @@ -1630,6 +1719,10 @@ void CWeaponRPG::PrimaryAttack( void ) SendWeaponAnim( ACT_VM_PRIMARYATTACK ); WeaponSound( SINGLE ); + +#ifdef MAPBASE + pOwner->SetAnimation( PLAYER_ATTACK1 ); +#endif pOwner->RumbleEffect( RUMBLE_SHOTGUN_SINGLE, 0, RUMBLE_FLAG_RESTART ); @@ -2039,7 +2132,20 @@ bool CWeaponRPG::WeaponLOSCondition( const Vector &ownerPos, const Vector &targe Vector vecShootDir = npcOwner->GetActualShootTrajectory( vecMuzzle ); // Make sure I have a good 10 feet of wide clearance in front, or I'll blow my teeth out. +#ifdef MAPBASE + // Oh, and don't collide with ourselves or our owner. That would be stupid. + if (!weapon_rpg_use_old_behavior.GetBool()) + { + CTraceFilterSkipTwoEntities pTraceFilter( this, GetOwner(), COLLISION_GROUP_NONE ); + AI_TraceHull( vecMuzzle, vecMuzzle + vecShootDir * (10.0f*12.0f), Vector( -24, -24, -24 ), Vector( 24, 24, 24 ), MASK_NPCSOLID, &pTraceFilter, &tr ); + } + else + { +#endif AI_TraceHull( vecMuzzle, vecMuzzle + vecShootDir * (10.0f*12.0f), Vector( -24, -24, -24 ), Vector( 24, 24, 24 ), MASK_NPCSOLID, NULL, &tr ); +#ifdef MAPBASE + } +#endif if( tr.fraction != 1.0f ) bResult = false; @@ -2089,7 +2195,20 @@ int CWeaponRPG::WeaponRangeAttack1Condition( float flDot, float flDist ) Vector vecShootDir = pOwner->GetActualShootTrajectory( vecMuzzle ); // Make sure I have a good 10 feet of wide clearance in front, or I'll blow my teeth out. +#ifdef MAPBASE + // Oh, and don't collide with ourselves or our owner. That would be stupid. + if (!weapon_rpg_use_old_behavior.GetBool()) + { + CTraceFilterSkipTwoEntities pTraceFilter( this, GetOwner(), COLLISION_GROUP_NONE ); + AI_TraceHull( vecMuzzle, vecMuzzle + vecShootDir * (10.0f*12.0f), Vector( -24, -24, -24 ), Vector( 24, 24, 24 ), MASK_NPCSOLID, &pTraceFilter, &tr ); + } + else + { +#endif AI_TraceHull( vecMuzzle, vecMuzzle + vecShootDir * (10.0f*12.0f), Vector( -24, -24, -24 ), Vector( 24, 24, 24 ), MASK_NPCSOLID, NULL, &tr ); +#ifdef MAPBASE + } +#endif if( tr.fraction != 1.0 ) { @@ -2213,6 +2332,23 @@ void CWeaponRPG::UpdateLaserEffects( void ) } } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CWeaponRPG::SupportsBackupActivity(Activity activity) +{ + // NPCs shouldn't use their SMG activities to aim and fire RPGs while running. + if (activity == ACT_RUN_AIM || + activity == ACT_WALK_AIM || + activity == ACT_RUN_CROUCH_AIM || + activity == ACT_WALK_CROUCH_AIM) + return false; + + return true; +} +#endif + //============================================================================= // Laser Dot //============================================================================= diff --git a/src/game/server/hl2/weapon_rpg.h b/src/game/server/hl2/weapon_rpg.h index b8ca99af..a536d1c2 100644 --- a/src/game/server/hl2/weapon_rpg.h +++ b/src/game/server/hl2/weapon_rpg.h @@ -165,6 +165,9 @@ private: //----------------------------------------------------------------------------- CAPCMissile *FindAPCMissileInCone( const Vector &vecOrigin, const Vector &vecDirection, float flAngle ); +#ifdef MAPBASE +extern ConVar weapon_rpg_fire_rate; +#endif //----------------------------------------------------------------------------- // RPG @@ -182,7 +185,11 @@ public: void Precache( void ); void PrimaryAttack( void ); +#ifdef MAPBASE + virtual float GetFireRate( void ) { return weapon_rpg_fire_rate.GetFloat(); }; +#else virtual float GetFireRate( void ) { return 1; }; +#endif void ItemPostFrame( void ); void Activate( void ); @@ -196,6 +203,10 @@ public: virtual void Drop( const Vector &vecVelocity ); +#ifdef MAPBASE + bool SupportsBackupActivity(Activity activity); +#endif + int GetMinBurst() { return 1; } int GetMaxBurst() { return 1; } float GetMinRestTime() { return 4.0; } @@ -205,6 +216,9 @@ public: int WeaponRangeAttack1Condition( float flDot, float flDist ); void Operator_HandleAnimEvent( animevent_t *pEvent, CBaseCombatCharacter *pOperator ); +#ifdef MAPBASE + void Operator_ForceNPCFire( CBaseCombatCharacter *pOperator, bool bSecondary ); +#endif void StartGuiding( void ); void StopGuiding( void ); void ToggleGuiding( void ); diff --git a/src/game/server/hl2/weapon_shotgun.cpp b/src/game/server/hl2/weapon_shotgun.cpp index a832a3d6..5348e08a 100644 --- a/src/game/server/hl2/weapon_shotgun.cpp +++ b/src/game/server/hl2/weapon_shotgun.cpp @@ -24,6 +24,10 @@ extern ConVar sk_auto_reload_time; extern ConVar sk_plr_num_shotgun_pellets; +#ifdef MAPBASE +extern ConVar sk_plr_num_shotgun_pellets_double; +extern ConVar sk_npc_num_shotgun_pellets; +#endif class CWeaponShotgun : public CBaseHLCombatWeapon { @@ -104,6 +108,57 @@ END_DATADESC() acttable_t CWeaponShotgun::m_acttable[] = { +#if EXPANDED_HL2_WEAPON_ACTIVITIES + // Note that ACT_IDLE_SHOTGUN_AGITATED seems to be a stand-in for ACT_IDLE_SHOTGUN on citizens, + // but that isn't acceptable for NPCs which don't use readiness activities. + { ACT_IDLE, ACT_IDLE_SHOTGUN, true }, + + { ACT_RANGE_ATTACK1, ACT_RANGE_ATTACK_SHOTGUN, true }, + { ACT_RELOAD, ACT_RELOAD_SHOTGUN, false }, + { ACT_WALK, ACT_WALK_SHOTGUN, true }, + { ACT_IDLE_ANGRY, ACT_IDLE_ANGRY_SHOTGUN, true }, + +// Readiness activities (not aiming) + { ACT_IDLE_RELAXED, ACT_IDLE_SHOTGUN_RELAXED, false },//never aims + { ACT_IDLE_STIMULATED, ACT_IDLE_SHOTGUN_STIMULATED, false }, + { ACT_IDLE_AGITATED, ACT_IDLE_ANGRY_SHOTGUN, false },//always aims + + { ACT_WALK_RELAXED, ACT_WALK_SHOTGUN_RELAXED, false },//never aims + { ACT_WALK_STIMULATED, ACT_WALK_SHOTGUN_STIMULATED, false }, + { ACT_WALK_AGITATED, ACT_WALK_AIM_SHOTGUN, false },//always aims + + { ACT_RUN_RELAXED, ACT_RUN_SHOTGUN_RELAXED, false },//never aims + { ACT_RUN_STIMULATED, ACT_RUN_SHOTGUN_STIMULATED, false }, + { ACT_RUN_AGITATED, ACT_RUN_AIM_SHOTGUN, false },//always aims + +// Readiness activities (aiming) + { ACT_IDLE_AIM_RELAXED, ACT_IDLE_SHOTGUN_RELAXED, false },//never aims + { ACT_IDLE_AIM_STIMULATED, ACT_IDLE_AIM_SHOTGUN_STIMULATED, false }, + { ACT_IDLE_AIM_AGITATED, ACT_IDLE_ANGRY_SHOTGUN, false },//always aims + + { ACT_WALK_AIM_RELAXED, ACT_WALK_SHOTGUN_RELAXED, false },//never aims + { ACT_WALK_AIM_STIMULATED, ACT_WALK_AIM_SHOTGUN_STIMULATED, false }, + { ACT_WALK_AIM_AGITATED, ACT_WALK_AIM_SHOTGUN, false },//always aims + + { ACT_RUN_AIM_RELAXED, ACT_RUN_SHOTGUN_RELAXED, false },//never aims + { ACT_RUN_AIM_STIMULATED, ACT_RUN_AIM_SHOTGUN_STIMULATED, false }, + { ACT_RUN_AIM_AGITATED, ACT_RUN_AIM_SHOTGUN, false },//always aims +//End readiness activities + + { ACT_WALK_AIM, ACT_WALK_AIM_SHOTGUN, true }, + { ACT_WALK_CROUCH, ACT_WALK_CROUCH_RIFLE, true }, + { ACT_WALK_CROUCH_AIM, ACT_WALK_CROUCH_AIM_RIFLE, true }, + { ACT_RUN, ACT_RUN_SHOTGUN, true }, + { ACT_RUN_AIM, ACT_RUN_AIM_SHOTGUN, true }, + { ACT_RUN_CROUCH, ACT_RUN_CROUCH_RIFLE, true }, + { ACT_RUN_CROUCH_AIM, ACT_RUN_CROUCH_AIM_RIFLE, true }, + { ACT_GESTURE_RANGE_ATTACK1, ACT_GESTURE_RANGE_ATTACK_SHOTGUN, true }, + { ACT_RANGE_ATTACK1_LOW, ACT_RANGE_ATTACK_SHOTGUN_LOW, true }, + { ACT_RELOAD_LOW, ACT_RELOAD_SHOTGUN_LOW, false }, + { ACT_GESTURE_RELOAD, ACT_GESTURE_RELOAD_SHOTGUN, false }, + { ACT_COVER_LOW, ACT_COVER_SHOTGUN_LOW, false }, + { ACT_RANGE_AIM_LOW, ACT_RANGE_AIM_SHOTGUN_LOW, false }, +#else { ACT_IDLE, ACT_IDLE_SMG1, true }, // FIXME: hook to shotgun unique { ACT_RANGE_ATTACK1, ACT_RANGE_ATTACK_SHOTGUN, true }, @@ -149,10 +204,49 @@ acttable_t CWeaponShotgun::m_acttable[] = { ACT_RANGE_ATTACK1_LOW, ACT_RANGE_ATTACK_SHOTGUN_LOW, true }, { ACT_RELOAD_LOW, ACT_RELOAD_SHOTGUN_LOW, false }, { ACT_GESTURE_RELOAD, ACT_GESTURE_RELOAD_SHOTGUN, false }, +#endif + +#if EXPANDED_HL2_WEAPON_ACTIVITIES + { ACT_ARM, ACT_ARM_SHOTGUN, true }, + { ACT_DISARM, ACT_DISARM_SHOTGUN, true }, +#endif + +#if EXPANDED_HL2_COVER_ACTIVITIES + { ACT_RANGE_AIM_MED, ACT_RANGE_AIM_SHOTGUN_MED, false }, + { ACT_RANGE_ATTACK1_MED, ACT_RANGE_ATTACK_SHOTGUN_MED, false }, +#endif + +#ifdef MAPBASE + // HL2:DM activities (for third-person animations in SP) + { ACT_HL2MP_IDLE, ACT_HL2MP_IDLE_SHOTGUN, false }, + { ACT_HL2MP_RUN, ACT_HL2MP_RUN_SHOTGUN, false }, + { ACT_HL2MP_IDLE_CROUCH, ACT_HL2MP_IDLE_CROUCH_SHOTGUN, false }, + { ACT_HL2MP_WALK_CROUCH, ACT_HL2MP_WALK_CROUCH_SHOTGUN, false }, + { ACT_HL2MP_GESTURE_RANGE_ATTACK, ACT_HL2MP_GESTURE_RANGE_ATTACK_SHOTGUN, false }, + { ACT_HL2MP_GESTURE_RELOAD, ACT_HL2MP_GESTURE_RELOAD_SHOTGUN, false }, + { ACT_HL2MP_JUMP, ACT_HL2MP_JUMP_SHOTGUN, false }, +#if EXPANDED_HL2DM_ACTIVITIES + { ACT_HL2MP_WALK, ACT_HL2MP_WALK_SHOTGUN, false }, + { ACT_HL2MP_GESTURE_RANGE_ATTACK2, ACT_HL2MP_GESTURE_RANGE_ATTACK2_SHOTGUN, false }, +#endif +#endif }; IMPLEMENT_ACTTABLE(CWeaponShotgun); +#ifdef MAPBASE +// Allows Weapon_BackupActivity() to access the shotgun's activity table. +acttable_t *GetShotgunActtable() +{ + return CWeaponShotgun::m_acttable; +} + +int GetShotgunActtableCount() +{ + return ARRAYSIZE(CWeaponShotgun::m_acttable); +} +#endif + void CWeaponShotgun::Precache( void ) { CBaseCombatWeapon::Precache(); @@ -183,7 +277,11 @@ void CWeaponShotgun::FireNPCPrimaryAttack( CBaseCombatCharacter *pOperator, bool vecShootDir = npc->GetActualShootTrajectory( vecShootOrigin ); } +#ifdef MAPBASE + pOperator->FireBullets( sk_npc_num_shotgun_pellets.GetInt(), vecShootOrigin, vecShootDir, GetBulletSpread(), MAX_TRACE_LENGTH, m_iPrimaryAmmoType, 0 ); +#else pOperator->FireBullets( 8, vecShootOrigin, vecShootDir, GetBulletSpread(), MAX_TRACE_LENGTH, m_iPrimaryAmmoType, 0 ); +#endif } //----------------------------------------------------------------------------- @@ -308,6 +406,13 @@ bool CWeaponShotgun::StartReload( void ) pOwner->m_flNextAttack = gpGlobals->curtime; m_flNextPrimaryAttack = gpGlobals->curtime + SequenceDuration(); +#ifdef MAPBASE + if ( pOwner->IsPlayer() ) + { + static_cast(pOwner)->SetAnimation( PLAYER_RELOAD ); + } +#endif + m_bInReload = true; return true; } @@ -461,7 +566,11 @@ void CWeaponShotgun::PrimaryAttack( void ) pPlayer->SetAnimation( PLAYER_ATTACK1 ); // Don't fire again until fire animation has completed +#ifdef MAPBASE + m_flNextPrimaryAttack = gpGlobals->curtime + GetViewModelSequenceDuration(); +#else m_flNextPrimaryAttack = gpGlobals->curtime + SequenceDuration(); +#endif m_iClip1 -= 1; Vector vecSrc = pPlayer->Weapon_ShootPosition( ); @@ -516,17 +625,29 @@ void CWeaponShotgun::SecondaryAttack( void ) SendWeaponAnim( ACT_VM_SECONDARYATTACK ); // player "shoot" animation +#ifdef MAPBASE + pPlayer->SetAnimation( PLAYER_ATTACK2 ); +#else pPlayer->SetAnimation( PLAYER_ATTACK1 ); +#endif // Don't fire again until fire animation has completed +#ifdef MAPBASE + m_flNextPrimaryAttack = gpGlobals->curtime + GetViewModelSequenceDuration(); +#else m_flNextPrimaryAttack = gpGlobals->curtime + SequenceDuration(); +#endif m_iClip1 -= 2; // Shotgun uses same clip for primary and secondary attacks Vector vecSrc = pPlayer->Weapon_ShootPosition(); Vector vecAiming = pPlayer->GetAutoaimVector( AUTOAIM_SCALE_DEFAULT ); // Fire the bullets +#ifdef MAPBASE + pPlayer->FireBullets( sk_plr_num_shotgun_pellets_double.GetInt(), vecSrc, vecAiming, GetBulletSpread(), MAX_TRACE_LENGTH, m_iPrimaryAmmoType, 0, -1, -1, 0, NULL, false, false ); +#else pPlayer->FireBullets( 12, vecSrc, vecAiming, GetBulletSpread(), MAX_TRACE_LENGTH, m_iPrimaryAmmoType, 0, -1, -1, 0, NULL, false, false ); +#endif pPlayer->ViewPunch( QAngle(random->RandomFloat( -5, 5 ),0,0) ); pPlayer->SetMuzzleFlashTime( gpGlobals->curtime + 1.0 ); diff --git a/src/game/server/hl2/weapon_smg1.cpp b/src/game/server/hl2/weapon_smg1.cpp index 940b207c..616b672e 100644 --- a/src/game/server/hl2/weapon_smg1.cpp +++ b/src/game/server/hl2/weapon_smg1.cpp @@ -22,6 +22,9 @@ #include "tier0/memdbgon.h" extern ConVar sk_plr_dmg_smg1_grenade; +#ifdef MAPBASE +extern ConVar sk_npc_dmg_smg1_grenade; +#endif class CWeaponSMG1 : public CHLSelectFireMachineGun { @@ -131,10 +134,53 @@ acttable_t CWeaponSMG1::m_acttable[] = { ACT_RANGE_AIM_LOW, ACT_RANGE_AIM_SMG1_LOW, false }, { ACT_RELOAD_LOW, ACT_RELOAD_SMG1_LOW, false }, { ACT_GESTURE_RELOAD, ACT_GESTURE_RELOAD_SMG1, true }, + +#if EXPANDED_HL2_WEAPON_ACTIVITIES + { ACT_ARM, ACT_ARM_RIFLE, false }, + { ACT_DISARM, ACT_DISARM_RIFLE, false }, +#endif + +#if EXPANDED_HL2_COVER_ACTIVITIES + { ACT_RANGE_AIM_MED, ACT_RANGE_AIM_SMG1_MED, false }, + { ACT_RANGE_ATTACK1_MED, ACT_RANGE_ATTACK_SMG1_MED, false }, + + { ACT_COVER_WALL_R, ACT_COVER_WALL_R_RIFLE, false }, + { ACT_COVER_WALL_L, ACT_COVER_WALL_L_RIFLE, false }, + { ACT_COVER_WALL_LOW_R, ACT_COVER_WALL_LOW_R_RIFLE, false }, + { ACT_COVER_WALL_LOW_L, ACT_COVER_WALL_LOW_L_RIFLE, false }, +#endif + +#ifdef MAPBASE + // HL2:DM activities (for third-person animations in SP) + { ACT_HL2MP_IDLE, ACT_HL2MP_IDLE_SMG1, false }, + { ACT_HL2MP_RUN, ACT_HL2MP_RUN_SMG1, false }, + { ACT_HL2MP_IDLE_CROUCH, ACT_HL2MP_IDLE_CROUCH_SMG1, false }, + { ACT_HL2MP_WALK_CROUCH, ACT_HL2MP_WALK_CROUCH_SMG1, false }, + { ACT_HL2MP_GESTURE_RANGE_ATTACK, ACT_HL2MP_GESTURE_RANGE_ATTACK_SMG1, false }, + { ACT_HL2MP_GESTURE_RELOAD, ACT_HL2MP_GESTURE_RELOAD_SMG1, false }, + { ACT_HL2MP_JUMP, ACT_HL2MP_JUMP_SMG1, false }, +#if EXPANDED_HL2DM_ACTIVITIES + { ACT_HL2MP_WALK, ACT_HL2MP_WALK_SMG1, false }, + { ACT_HL2MP_GESTURE_RANGE_ATTACK2, ACT_HL2MP_GESTURE_RANGE_ATTACK2_SMG1, false }, +#endif +#endif }; IMPLEMENT_ACTTABLE(CWeaponSMG1); +#ifdef MAPBASE +// Allows Weapon_BackupActivity() to access the SMG1's activity table. +acttable_t *GetSMG1Acttable() +{ + return CWeaponSMG1::m_acttable; +} + +int GetSMG1ActtableCount() +{ + return ARRAYSIZE(CWeaponSMG1::m_acttable); +} +#endif + //========================================================= CWeaponSMG1::CWeaponSMG1( ) { @@ -202,6 +248,10 @@ void CWeaponSMG1::Operator_ForceNPCFire( CBaseCombatCharacter *pOperator, bool b FireNPCPrimaryAttack( pOperator, vecShootOrigin, vecShootDir ); } +#ifdef MAPBASE +float GetCurrentGravity( void ); +#endif + //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- @@ -228,6 +278,56 @@ void CWeaponSMG1::Operator_HandleAnimEvent( animevent_t *pEvent, CBaseCombatChar } break; +#ifdef MAPBASE + case EVENT_WEAPON_AR2_ALTFIRE: + { + WeaponSound( WPN_DOUBLE ); + + CAI_BaseNPC *npc = pOperator->MyNPCPointer(); + if (!npc) + return; + + Vector vecShootOrigin, vecShootDir; + vecShootOrigin = pOperator->Weapon_ShootPosition(); + vecShootDir = npc->GetShootEnemyDir( vecShootOrigin ); + + Vector vecTarget = npc->GetAltFireTarget(); + Vector vecThrow; + if (vecTarget == vec3_origin) + AngleVectors( npc->EyeAngles(), &vecThrow ); // Not much else to do, unfortunately + else + { + // Because this is happening right now, we can't "VecCheckThrow" and can only "VecDoThrow", you know what I mean? + // ...Anyway, this borrows from that so we'll never return vec3_origin. + //vecThrow = VecCheckThrow( this, vecShootOrigin, vecTarget, 600.0, 0.5 ); + + vecThrow = (vecTarget - vecShootOrigin); + + // throw at a constant time + float time = vecThrow.Length() / 600.0; + vecThrow = vecThrow * (1.0 / time); + + // adjust upward toss to compensate for gravity loss + vecThrow.z += (GetCurrentGravity() * 0.5) * time * 0.5; + } + + CGrenadeAR2 *pGrenade = (CGrenadeAR2*)Create( "grenade_ar2", vecShootOrigin, vec3_angle, npc ); + pGrenade->SetAbsVelocity( vecThrow ); + pGrenade->SetLocalAngularVelocity( QAngle( 0, 400, 0 ) ); + pGrenade->SetMoveType( MOVETYPE_FLYGRAVITY, MOVECOLLIDE_FLY_BOUNCE ); + + pGrenade->SetThrower( npc ); + + pGrenade->SetGravity(0.5); // lower gravity since grenade is aerodynamic and engine doesn't know it. + + pGrenade->SetDamage(sk_npc_dmg_smg1_grenade.GetFloat()); + + variant_t var; + var.SetEntity(pGrenade); + npc->FireNamedOutput("OnThrowGrenade", var, pGrenade, npc); + } + break; +#else /*//FIXME: Re-enable case EVENT_WEAPON_AR2_GRENADE: { @@ -254,6 +354,7 @@ void CWeaponSMG1::Operator_HandleAnimEvent( animevent_t *pEvent, CBaseCombatChar } break; */ +#endif default: BaseClass::Operator_HandleAnimEvent( pEvent, pOperator ); @@ -368,7 +469,11 @@ void CWeaponSMG1::SecondaryAttack( void ) CSoundEnt::InsertSound( SOUND_COMBAT, GetAbsOrigin(), 1000, 0.2, GetOwner(), SOUNDENT_CHANNEL_WEAPON ); // player "shoot" animation +#ifdef MAPBASE + pPlayer->SetAnimation( PLAYER_ATTACK2 ); +#else pPlayer->SetAnimation( PLAYER_ATTACK1 ); +#endif // Decrease ammo pPlayer->RemoveAmmo( 1, m_iSecondaryAmmoType ); diff --git a/src/game/server/hl2/weapon_smg2.cpp b/src/game/server/hl2/weapon_smg2.cpp index 498faddd..9c5037b9 100644 --- a/src/game/server/hl2/weapon_smg2.cpp +++ b/src/game/server/hl2/weapon_smg2.cpp @@ -46,6 +46,77 @@ PRECACHE_WEAPON_REGISTER(weapon_smg2); acttable_t CWeaponSMG2::m_acttable[] = { { ACT_RANGE_ATTACK1, ACT_RANGE_ATTACK_SMG2, true }, + +#if EXPANDED_HL2_UNUSED_WEAPON_ACTIVITIES + // Optional new NPC activities + // (these should fall back to SMG animations when they don't exist on an NPC) + { ACT_RELOAD, ACT_RELOAD_SMG2, true }, + { ACT_IDLE, ACT_IDLE_SMG2, true }, + { ACT_IDLE_ANGRY, ACT_IDLE_ANGRY_SMG2, true }, + +// Readiness activities (not aiming) + { ACT_IDLE_RELAXED, ACT_IDLE_SMG2_RELAXED, false },//never aims + { ACT_IDLE_STIMULATED, ACT_IDLE_SMG2_STIMULATED, false }, + { ACT_IDLE_AGITATED, ACT_IDLE_ANGRY_SMG2, false },//always aims + + { ACT_WALK_RELAXED, ACT_WALK_SMG2_RELAXED, false },//never aims + { ACT_WALK_STIMULATED, ACT_WALK_SMG2_STIMULATED, false }, + { ACT_WALK_AGITATED, ACT_WALK_AIM_SMG2, false },//always aims + + { ACT_RUN_RELAXED, ACT_RUN_SMG2_RELAXED, false },//never aims + { ACT_RUN_STIMULATED, ACT_RUN_SMG2_STIMULATED, false }, + { ACT_RUN_AGITATED, ACT_RUN_AIM_SMG2, false },//always aims + +// Readiness activities (aiming) + { ACT_IDLE_AIM_RELAXED, ACT_IDLE_SMG2_RELAXED, false },//never aims + { ACT_IDLE_AIM_STIMULATED, ACT_IDLE_AIM_SMG2_STIMULATED, false }, + { ACT_IDLE_AIM_AGITATED, ACT_IDLE_ANGRY_SMG2, false },//always aims + + { ACT_WALK_AIM_RELAXED, ACT_WALK_SMG2_RELAXED, false },//never aims + { ACT_WALK_AIM_STIMULATED, ACT_WALK_AIM_SMG2_STIMULATED, false }, + { ACT_WALK_AIM_AGITATED, ACT_WALK_AIM_SMG2, false },//always aims + + { ACT_RUN_AIM_RELAXED, ACT_RUN_SMG2_RELAXED, false },//never aims + { ACT_RUN_AIM_STIMULATED, ACT_RUN_AIM_SMG2_STIMULATED, false }, + { ACT_RUN_AIM_AGITATED, ACT_RUN_AIM_SMG2, false },//always aims +//End readiness activities + + { ACT_WALK, ACT_WALK_SMG2, true }, + { ACT_WALK_AIM, ACT_WALK_AIM_SMG2, true }, + { ACT_WALK_CROUCH, ACT_WALK_CROUCH_RIFLE, true }, + { ACT_WALK_CROUCH_AIM, ACT_WALK_CROUCH_AIM_RIFLE, true }, + { ACT_RUN, ACT_RUN_SMG2, true }, + { ACT_RUN_AIM, ACT_RUN_AIM_SMG2, true }, + { ACT_RUN_CROUCH, ACT_RUN_CROUCH_RIFLE, true }, + { ACT_RUN_CROUCH_AIM, ACT_RUN_CROUCH_AIM_RIFLE, true }, + { ACT_GESTURE_RANGE_ATTACK1, ACT_GESTURE_RANGE_ATTACK_SMG2, true }, + { ACT_RANGE_ATTACK1_LOW, ACT_RANGE_ATTACK_SMG2_LOW, true }, + { ACT_COVER_LOW, ACT_COVER_SMG2_LOW, false }, + { ACT_RANGE_AIM_LOW, ACT_RANGE_AIM_SMG2_LOW, false }, + { ACT_RELOAD_LOW, ACT_RELOAD_SMG2_LOW, false }, + { ACT_GESTURE_RELOAD, ACT_GESTURE_RELOAD_SMG2, true }, + + { ACT_ARM, ACT_ARM_RIFLE, false }, + { ACT_DISARM, ACT_DISARM_RIFLE, false }, + +#if EXPANDED_HL2_COVER_ACTIVITIES + { ACT_RANGE_AIM_MED, ACT_RANGE_AIM_SMG2_MED, false }, + { ACT_RANGE_ATTACK1_MED, ACT_RANGE_ATTACK_SMG2_MED, false }, +#endif + +#if EXPANDED_HL2DM_ACTIVITIES + // HL2:DM activities (for third-person animations in SP) + { ACT_HL2MP_IDLE, ACT_HL2MP_IDLE_SMG2, false }, + { ACT_HL2MP_RUN, ACT_HL2MP_RUN_SMG2, false }, + { ACT_HL2MP_IDLE_CROUCH, ACT_HL2MP_IDLE_CROUCH_SMG2, false }, + { ACT_HL2MP_WALK_CROUCH, ACT_HL2MP_WALK_CROUCH_SMG2, false }, + { ACT_HL2MP_GESTURE_RANGE_ATTACK, ACT_HL2MP_GESTURE_RANGE_ATTACK_SMG2, false }, + { ACT_HL2MP_GESTURE_RELOAD, ACT_HL2MP_GESTURE_RELOAD_SMG2, false }, + { ACT_HL2MP_JUMP, ACT_HL2MP_JUMP_SMG2, false }, + { ACT_HL2MP_WALK, ACT_HL2MP_WALK_SMG2, false }, + { ACT_HL2MP_GESTURE_RANGE_ATTACK2, ACT_HL2MP_GESTURE_RANGE_ATTACK2_SMG2, false }, +#endif +#endif }; IMPLEMENT_ACTTABLE(CWeaponSMG2); diff --git a/src/game/server/hl2/weapon_sniperrifle.cpp b/src/game/server/hl2/weapon_sniperrifle.cpp index 901a73ae..89887507 100644 --- a/src/game/server/hl2/weapon_sniperrifle.cpp +++ b/src/game/server/hl2/weapon_sniperrifle.cpp @@ -46,6 +46,10 @@ static int g_nZoomFOV[] = 5 }; +#ifdef MAPBASE +extern acttable_t *GetAR2Acttable(); +extern int GetAR2ActtableCount(); +#endif class CWeaponSniperRifle : public CBaseHLCombatWeapon { @@ -72,6 +76,11 @@ public: void Operator_HandleAnimEvent( animevent_t *pEvent, CBaseCombatCharacter *pOperator ); +#ifdef MAPBASE + virtual acttable_t *GetBackupActivityList() { return GetAR2Acttable(); } + virtual int GetBackupActivityListCount() { return GetAR2ActtableCount(); } +#endif + DECLARE_ACTTABLE(); protected: @@ -98,7 +107,78 @@ END_DATADESC() //----------------------------------------------------------------------------- acttable_t CWeaponSniperRifle::m_acttable[] = { - { ACT_RANGE_ATTACK1, ACT_RANGE_ATTACK_SNIPER_RIFLE, true } + { ACT_RANGE_ATTACK1, ACT_RANGE_ATTACK_SNIPER_RIFLE, true }, + +#if EXPANDED_HL2_UNUSED_WEAPON_ACTIVITIES + // Optional new NPC activities + // (these should fall back to AR2 animations when they don't exist on an NPC) + { ACT_RELOAD, ACT_RELOAD_SNIPER_RIFLE, true }, + { ACT_IDLE, ACT_IDLE_SNIPER_RIFLE, true }, + { ACT_IDLE_ANGRY, ACT_IDLE_ANGRY_SNIPER_RIFLE, true }, + +// Readiness activities (not aiming) + { ACT_IDLE_RELAXED, ACT_IDLE_SNIPER_RIFLE_RELAXED, false },//never aims + { ACT_IDLE_STIMULATED, ACT_IDLE_SNIPER_RIFLE_STIMULATED, false }, + { ACT_IDLE_AGITATED, ACT_IDLE_ANGRY_SNIPER_RIFLE, false },//always aims + + { ACT_WALK_RELAXED, ACT_WALK_SNIPER_RIFLE_RELAXED, false },//never aims + { ACT_WALK_STIMULATED, ACT_WALK_SNIPER_RIFLE_STIMULATED, false }, + { ACT_WALK_AGITATED, ACT_WALK_AIM_SNIPER_RIFLE, false },//always aims + + { ACT_RUN_RELAXED, ACT_RUN_SNIPER_RIFLE_RELAXED, false },//never aims + { ACT_RUN_STIMULATED, ACT_RUN_SNIPER_RIFLE_STIMULATED, false }, + { ACT_RUN_AGITATED, ACT_RUN_AIM_SNIPER_RIFLE, false },//always aims + +// Readiness activities (aiming) + { ACT_IDLE_AIM_RELAXED, ACT_IDLE_SNIPER_RIFLE_RELAXED, false },//never aims + { ACT_IDLE_AIM_STIMULATED, ACT_IDLE_AIM_SNIPER_RIFLE_STIMULATED, false }, + { ACT_IDLE_AIM_AGITATED, ACT_IDLE_ANGRY_SNIPER_RIFLE, false },//always aims + + { ACT_WALK_AIM_RELAXED, ACT_WALK_SNIPER_RIFLE_RELAXED, false },//never aims + { ACT_WALK_AIM_STIMULATED, ACT_WALK_AIM_SNIPER_RIFLE_STIMULATED, false }, + { ACT_WALK_AIM_AGITATED, ACT_WALK_AIM_SNIPER_RIFLE, false },//always aims + + { ACT_RUN_AIM_RELAXED, ACT_RUN_SNIPER_RIFLE_RELAXED, false },//never aims + { ACT_RUN_AIM_STIMULATED, ACT_RUN_AIM_SNIPER_RIFLE_STIMULATED, false }, + { ACT_RUN_AIM_AGITATED, ACT_RUN_AIM_SNIPER_RIFLE, false },//always aims +//End readiness activities + + { ACT_WALK, ACT_WALK_SNIPER_RIFLE, true }, + { ACT_WALK_AIM, ACT_WALK_AIM_SNIPER_RIFLE, true }, + { ACT_WALK_CROUCH, ACT_WALK_CROUCH_RIFLE, true }, + { ACT_WALK_CROUCH_AIM, ACT_WALK_CROUCH_AIM_RIFLE, true }, + { ACT_RUN, ACT_RUN_SNIPER_RIFLE, true }, + { ACT_RUN_AIM, ACT_RUN_AIM_SNIPER_RIFLE, true }, + { ACT_RUN_CROUCH, ACT_RUN_CROUCH_RIFLE, true }, + { ACT_RUN_CROUCH_AIM, ACT_RUN_CROUCH_AIM_RIFLE, true }, + { ACT_GESTURE_RANGE_ATTACK1, ACT_GESTURE_RANGE_ATTACK_SNIPER_RIFLE, true }, + { ACT_RANGE_ATTACK1_LOW, ACT_RANGE_ATTACK_SNIPER_RIFLE_LOW, true }, + { ACT_COVER_LOW, ACT_COVER_SNIPER_RIFLE_LOW, false }, + { ACT_RANGE_AIM_LOW, ACT_RANGE_AIM_SNIPER_RIFLE_LOW, false }, + { ACT_RELOAD_LOW, ACT_RELOAD_SNIPER_RIFLE_LOW, false }, + { ACT_GESTURE_RELOAD, ACT_GESTURE_RELOAD_SNIPER_RIFLE, true }, + + { ACT_ARM, ACT_ARM_RIFLE, false }, + { ACT_DISARM, ACT_DISARM_RIFLE, false }, + +#if EXPANDED_HL2_COVER_ACTIVITIES + { ACT_RANGE_AIM_MED, ACT_RANGE_AIM_SNIPER_RIFLE_MED, false }, + { ACT_RANGE_ATTACK1_MED, ACT_RANGE_ATTACK_SNIPER_RIFLE_MED, false }, +#endif + +#if EXPANDED_HL2DM_ACTIVITIES + // HL2:DM activities (for third-person animations in SP) + { ACT_HL2MP_IDLE, ACT_HL2MP_IDLE_SNIPER_RIFLE, false }, + { ACT_HL2MP_RUN, ACT_HL2MP_RUN_SNIPER_RIFLE, false }, + { ACT_HL2MP_IDLE_CROUCH, ACT_HL2MP_IDLE_CROUCH_SNIPER_RIFLE, false }, + { ACT_HL2MP_WALK_CROUCH, ACT_HL2MP_WALK_CROUCH_SNIPER_RIFLE, false }, + { ACT_HL2MP_GESTURE_RANGE_ATTACK, ACT_HL2MP_GESTURE_RANGE_ATTACK_SNIPER_RIFLE, false }, + { ACT_HL2MP_GESTURE_RELOAD, ACT_HL2MP_GESTURE_RELOAD_SNIPER_RIFLE, false }, + { ACT_HL2MP_JUMP, ACT_HL2MP_JUMP_SNIPER_RIFLE, false }, + { ACT_HL2MP_WALK, ACT_HL2MP_WALK_SNIPER_RIFLE, false }, + { ACT_HL2MP_GESTURE_RANGE_ATTACK2, ACT_HL2MP_GESTURE_RANGE_ATTACK2_SNIPER_RIFLE, false }, +#endif +#endif }; IMPLEMENT_ACTTABLE(CWeaponSniperRifle); diff --git a/src/game/server/hl2/weapon_stunstick.h b/src/game/server/hl2/weapon_stunstick.h index 901b4721..9d3942c8 100644 --- a/src/game/server/hl2/weapon_stunstick.h +++ b/src/game/server/hl2/weapon_stunstick.h @@ -4,6 +4,13 @@ // //=============================================================================// +#ifdef MAPBASE + +// Redirect to HL2:DM's stunstick. +// It has NPC support now. +#include "hl2mp/weapon_stunstick.h" + +#else #ifndef WEAPON_STUNSTICK_H #define WEAPON_STUNSTICK_H #ifdef _WIN32 @@ -13,7 +20,12 @@ #include "basebludgeonweapon.h" #define STUNSTICK_RANGE 75.0f +#ifdef MAPBASE +// MP refire +#define STUNSTICK_REFIRE 0.8f +#else #define STUNSTICK_REFIRE 0.6f +#endif class CWeaponStunStick : public CBaseHLBludgeonWeapon { @@ -56,3 +68,4 @@ private: }; #endif // WEAPON_STUNSTICK_H +#endif diff --git a/src/game/server/hl2mp/grenade_tripmine.cpp b/src/game/server/hl2mp/grenade_tripmine.cpp index e14a929d..39918869 100644 --- a/src/game/server/hl2mp/grenade_tripmine.cpp +++ b/src/game/server/hl2mp/grenade_tripmine.cpp @@ -34,6 +34,18 @@ BEGIN_DATADESC( CTripmineGrenade ) DEFINE_FIELD( m_pBeam, FIELD_CLASSPTR ), DEFINE_FIELD( m_posOwner, FIELD_POSITION_VECTOR ), DEFINE_FIELD( m_angleOwner, FIELD_VECTOR ), +#ifdef MAPBASE + DEFINE_KEYFIELD( m_flPowerUpTime, FIELD_FLOAT, "PowerUpTime" ), + DEFINE_FIELD( m_hAttacker, FIELD_EHANDLE ), + + // Inputs + DEFINE_INPUTFUNC( FIELD_VOID, "Activate", InputActivate ), + DEFINE_INPUTFUNC( FIELD_VOID, "Deactivate", InputDeactivate ), + DEFINE_INPUTFUNC( FIELD_EHANDLE, "SetOwner", InputSetOwner ), + + // Outputs + DEFINE_OUTPUT( m_OnExplode, "OnExplode" ), +#endif // Function Pointers DEFINE_THINKFUNC( WarningThink ), @@ -49,6 +61,10 @@ CTripmineGrenade::CTripmineGrenade() m_vecEnd.Init(); m_posOwner.Init(); m_angleOwner.Init(); + +#ifdef MAPBASE + m_flPowerUpTime = 2.0; +#endif } void CTripmineGrenade::Spawn( void ) @@ -65,18 +81,46 @@ void CTripmineGrenade::Spawn( void ) SetCycle( 0.0f ); m_nBody = 3; +#ifdef MAPBASE + if (m_flDamage == 0) + m_flDamage = sk_plr_dmg_tripmine.GetFloat(); + if (m_DmgRadius == 0) + m_DmgRadius = sk_tripmine_radius.GetFloat(); +#else m_flDamage = sk_plr_dmg_tripmine.GetFloat(); m_DmgRadius = sk_tripmine_radius.GetFloat(); +#endif ResetSequenceInfo( ); m_flPlaybackRate = 0; UTIL_SetSize(this, Vector( -4, -4, -2), Vector(4, 4, 2)); +#ifdef MAPBASE + if (!HasSpawnFlags(SF_TRIPMINE_START_INACTIVE)) + { + if (m_flPowerUpTime > 0) + { + m_flPowerUp = gpGlobals->curtime + m_flPowerUpTime; + + SetThink( &CTripmineGrenade::PowerupThink ); + SetNextThink( gpGlobals->curtime + 0.2 ); + } + else + { + MakeBeam( ); + RemoveSolidFlags( FSOLID_NOT_SOLID ); + m_bIsLive = true; + + //EmitSound( "TripmineGrenade.Activate" ); + } + } +#else m_flPowerUp = gpGlobals->curtime + 2.0; SetThink( &CTripmineGrenade::PowerupThink ); SetNextThink( gpGlobals->curtime + 0.2 ); +#endif m_takedamage = DAMAGE_YES; @@ -222,7 +266,11 @@ void CTripmineGrenade::BeamBreakThink( void ) if (pBCC || fabs( m_flBeamLength - tr.fraction ) > 0.001) { m_iHealth = 0; +#ifdef MAPBASE + Event_Killed( CTakeDamageInfo( (CBaseEntity*)m_hOwner, pEntity, 100, GIB_NORMAL ) ); +#else Event_Killed( CTakeDamageInfo( (CBaseEntity*)m_hOwner, this, 100, GIB_NORMAL ) ); +#endif return; } @@ -254,6 +302,10 @@ void CTripmineGrenade::Event_Killed( const CTakeDamageInfo &info ) { m_takedamage = DAMAGE_NO; +#ifdef MAPBASE + m_hAttacker = info.GetAttacker(); +#endif + SetThink( &CTripmineGrenade::DelayDeathThink ); SetNextThink( gpGlobals->curtime + 0.25 ); @@ -271,6 +323,48 @@ void CTripmineGrenade::DelayDeathThink( void ) ExplosionCreate( GetAbsOrigin() + m_vecDir * 8, GetAbsAngles(), m_hOwner, GetDamage(), 200, SF_ENVEXPLOSION_NOSPARKS | SF_ENVEXPLOSION_NODLIGHTS | SF_ENVEXPLOSION_NOSMOKE, 0.0f, this); +#ifdef MAPBASE + m_OnExplode.FireOutput(m_hAttacker.Get(), this); +#endif + UTIL_Remove( this ); } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: +// Input : +// Output : +//----------------------------------------------------------------------------- +void CTripmineGrenade::InputActivate( inputdata_t &inputdata ) +{ + if (m_flPowerUpTime > 0) + { + m_flPowerUp = gpGlobals->curtime + m_flPowerUpTime; + + SetThink( &CTripmineGrenade::PowerupThink ); + SetNextThink( gpGlobals->curtime + 0.2 ); + } + else + { + MakeBeam( ); + RemoveSolidFlags( FSOLID_NOT_SOLID ); + m_bIsLive = true; + + //EmitSound( "TripmineGrenade.Activate" ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : +// Output : +//----------------------------------------------------------------------------- +void CTripmineGrenade::InputDeactivate( inputdata_t &inputdata ) +{ + KillBeam( ); + //AddSolidFlags( FSOLID_NOT_SOLID ); + m_bIsLive = false; +} +#endif + diff --git a/src/game/server/hl2mp/grenade_tripmine.h b/src/game/server/hl2mp/grenade_tripmine.h index e1887096..9ec9e726 100644 --- a/src/game/server/hl2mp/grenade_tripmine.h +++ b/src/game/server/hl2mp/grenade_tripmine.h @@ -15,6 +15,9 @@ class CBeam; +#ifdef MAPBASE +#define SF_TRIPMINE_START_INACTIVE (1 << 0) +#endif class CTripmineGrenade : public CBaseGrenade { @@ -37,9 +40,24 @@ public: void MakeBeam( void ); void KillBeam( void ); +#ifdef MAPBASE + void PowerUp(); + + void InputActivate( inputdata_t &inputdata ); + void InputDeactivate( inputdata_t &inputdata ); + void InputSetOwner( inputdata_t &inputdata ) { m_hOwner = inputdata.value.Entity(); } + + COutputEvent m_OnExplode; +#endif + public: EHANDLE m_hOwner; +#ifdef MAPBASE + float m_flPowerUpTime; + EHANDLE m_hAttacker; +#endif + private: float m_flPowerUp; Vector m_vecDir; diff --git a/src/game/server/hltvdirector.h b/src/game/server/hltvdirector.h index 9fe60c7a..24891c80 100644 --- a/src/game/server/hltvdirector.h +++ b/src/game/server/hltvdirector.h @@ -67,7 +67,7 @@ public: // CBaseGameSystem overrides virtual void Shutdown(); virtual void FrameUpdatePostEntityThink(); virtual void LevelInitPostEntity(); - virtual char *GetFixedCameraEntityName( void ) { return "point_viewcontrol"; } + virtual const char *GetFixedCameraEntityName( void ) { return "point_viewcontrol"; } bool SetCameraMan( int iPlayerIndex ); int GetCameraMan() { return m_iCameraManIndex; } diff --git a/src/game/server/item_world.cpp b/src/game/server/item_world.cpp index ec1a63db..3ef4e77e 100644 --- a/src/game/server/item_world.cpp +++ b/src/game/server/item_world.cpp @@ -110,6 +110,14 @@ BEGIN_DATADESC( CItem ) DEFINE_THINKFUNC( FallThink ), #endif +#ifdef MAPBASE + DEFINE_INPUTFUNC( FIELD_VOID, "EnablePlayerPickup", InputEnablePlayerPickup ), + DEFINE_INPUTFUNC( FIELD_VOID, "DisablePlayerPickup", InputDisablePlayerPickup ), + DEFINE_INPUTFUNC( FIELD_VOID, "EnableNPCPickup", InputEnableNPCPickup ), + DEFINE_INPUTFUNC( FIELD_VOID, "DisableNPCPickup", InputDisableNPCPickup ), + DEFINE_INPUTFUNC( FIELD_VOID, "BreakConstraint", InputBreakConstraint ), +#endif + // Outputs DEFINE_OUTPUT( m_OnPlayerTouch, "OnPlayerTouch" ), DEFINE_OUTPUT( m_OnCacheInteraction, "OnCacheInteraction" ), @@ -351,6 +359,19 @@ bool UTIL_ItemCanBeTouchedByPlayer( CBaseEntity *pItem, CBasePlayer *pPlayer ) if ( pItem == NULL || pPlayer == NULL ) return false; +#ifdef MAPBASE + // Weapons go through this, but this is identical to SF_WEAPON_NO_PLAYER_PICKUP and that would be a convenient coincidence, + // but OnCacheInteraction worked with "No player pickup" before and SF_WEAPON_NO_PLAYER_PICKUP is often checked after this, + // so we have to make sure we're not dealing with a weapon for this check after all. + if (pItem->HasSpawnFlags(SF_ITEM_NO_PLAYER_PICKUP) && !pItem->IsBaseCombatWeapon()) + return false; + + // Fortunately, unlike the above code, this flag is identical in between weapons and items + // and can safely be used without identifying the entity. + if (pItem->HasSpawnFlags(SF_ITEM_ALWAYS_TOUCHABLE)) + return true; +#endif + // For now, always allow a vehicle riding player to pick up things they're driving over if ( pPlayer->IsInAVehicle() ) return true; @@ -583,3 +604,49 @@ void CItem::OnPhysGunDrop( CBasePlayer *pPhysGunUser, PhysGunDrop_t reason ) // Restore the pickup box to the original CollisionProp()->UseTriggerBounds( true, ITEM_PICKUP_BOX_BLOAT ); } + +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CItem::InputEnablePlayerPickup( inputdata_t &inputdata ) +{ + RemoveSpawnFlags(SF_ITEM_NO_PLAYER_PICKUP); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CItem::InputDisablePlayerPickup( inputdata_t &inputdata ) +{ + AddSpawnFlags(SF_ITEM_NO_PLAYER_PICKUP); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CItem::InputEnableNPCPickup( inputdata_t &inputdata ) +{ + RemoveSpawnFlags(SF_ITEM_NO_NPC_PICKUP); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CItem::InputDisableNPCPickup( inputdata_t &inputdata ) +{ + AddSpawnFlags(SF_ITEM_NO_NPC_PICKUP); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CItem::InputBreakConstraint( inputdata_t &inputdata ) +{ + if ( m_pConstraint != NULL ) + { + physenv->DestroyConstraint( m_pConstraint ); + m_pConstraint = NULL; + } +} +#endif diff --git a/src/game/server/items.h b/src/game/server/items.h index e0ead5ce..518e343f 100644 --- a/src/game/server/items.h +++ b/src/game/server/items.h @@ -36,6 +36,15 @@ #define SIZE_AMMO_AR2_ALTFIRE 1 #define SF_ITEM_START_CONSTRAINED 0x00000001 +#ifdef MAPBASE +// Copied from CBaseCombatWeapon's flags, including any additions we made to those. +// I really, REALLY hope no item uses their own spawnflags either. +#define SF_ITEM_NO_PLAYER_PICKUP (1<<1) +#define SF_ITEM_NO_PHYSCANNON_PUNT (1<<2) +#define SF_ITEM_NO_NPC_PICKUP (1<<3) + +#define SF_ITEM_ALWAYS_TOUCHABLE (1<<6) // This needs to stay synced with the weapon spawnflag +#endif class CItem : public CBaseAnimating, public CDefaultPlayerPickupVPhysics @@ -83,6 +92,21 @@ public: virtual const char *GetWeaponClassForAmmo() const { return NULL; } #endif +#ifdef MAPBASE + // This appeared to have no prior use in Source SDK 2013. + // It may have been originally intended for TF2 or some other game-specific item class. + virtual bool IsCombatItem() const { return true; } + + // Used to access item_healthkit values, etc. from outside of the class + virtual float GetItemAmount() { return 1.0f; } + + void InputEnablePlayerPickup( inputdata_t &inputdata ); + void InputDisablePlayerPickup( inputdata_t &inputdata ); + void InputEnableNPCPickup( inputdata_t &inputdata ); + void InputDisableNPCPickup( inputdata_t &inputdata ); + void InputBreakConstraint( inputdata_t &inputdata ); +#endif + DECLARE_DATADESC(); protected: virtual void ComeToRest( void ); diff --git a/src/game/server/lightglow.cpp b/src/game/server/lightglow.cpp index 59d0505d..d941f033 100644 --- a/src/game/server/lightglow.cpp +++ b/src/game/server/lightglow.cpp @@ -33,6 +33,10 @@ public: virtual int UpdateTransmitState( void ); void InputColor(inputdata_t &data); +#ifdef MAPBASE + void InputEnable( inputdata_t &data ) { m_bDisabled = false; } + void InputDisable( inputdata_t &data ) { m_bDisabled = true; } +#endif public: CNetworkVar( int, m_nHorizontalSize ); @@ -43,6 +47,10 @@ public: CNetworkVar( float, m_flGlowProxySize ); CNetworkVar( float, m_flHDRColorScale ); + +#ifdef MAPBASE + CNetworkVar( bool, m_bDisabled ); +#endif }; extern void SendProxy_Angles( const SendProp *pProp, const void *pStruct, const void *pData, DVariant *pOut, int iElement, int objectID ); @@ -60,6 +68,9 @@ IMPLEMENT_SERVERCLASS_ST_NOBASE( CLightGlow, DT_LightGlow ) SendPropEHandle (SENDINFO_NAME(m_hMoveParent, moveparent)), SendPropFloat( SENDINFO(m_flGlowProxySize ), 6, SPROP_ROUNDUP, 0.0f, 64.0f ), SendPropFloat( SENDINFO_NAME( m_flHDRColorScale, HDRColorScale ), 0, SPROP_NOSCALE, 0.0f, 100.0f ), +#ifdef MAPBASE + SendPropBool( SENDINFO( m_bDisabled ) ), +#endif END_SEND_TABLE() LINK_ENTITY_TO_CLASS( env_lightglow, CLightGlow ); @@ -73,6 +84,11 @@ BEGIN_DATADESC( CLightGlow ) DEFINE_KEYFIELD( m_nOuterMaxDist, FIELD_INTEGER, "OuterMaxDist" ), DEFINE_KEYFIELD( m_flGlowProxySize, FIELD_FLOAT, "GlowProxySize" ), DEFINE_KEYFIELD( m_flHDRColorScale, FIELD_FLOAT, "HDRColorScale" ), +#ifdef MAPBASE + DEFINE_KEYFIELD( m_bDisabled, FIELD_BOOLEAN, "StartDisabled" ), + DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ), + DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ), +#endif DEFINE_INPUTFUNC( FIELD_COLOR32, "Color", InputColor ), END_DATADESC() diff --git a/src/game/server/logic_measure_movement.cpp b/src/game/server/logic_measure_movement.cpp index bf074bd0..06c3da45 100644 --- a/src/game/server/logic_measure_movement.cpp +++ b/src/game/server/logic_measure_movement.cpp @@ -7,10 +7,31 @@ #include "cbase.h" #include "baseentity.h" +#ifdef MAPBASE +#include "filters.h" +#include "ai_basenpc.h" +#endif // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" +#ifdef MAPBASE +// These spawnflags were originally only on logic_measure_direction. +#define SF_LOGIC_MEASURE_MOVEMENT_IGNORE_X ( 1 << 0 ) +#define SF_LOGIC_MEASURE_MOVEMENT_IGNORE_Y ( 1 << 1 ) +#define SF_LOGIC_MEASURE_MOVEMENT_IGNORE_Z ( 1 << 2 ) + +// Uses the "Ignore X/Y/Z" flags for the origin instead of the angles. +// logic_measure_direction uses this flag to control trace direction. +#define SF_LOGIC_MEASURE_MOVEMENT_USE_IGNORE_FLAGS_FOR_ORIGIN ( 1 << 3 ) + +// Uses "Teleport" instead of "SetAbsOrigin" for smoother movement +#define SF_LOGIC_MEASURE_MOVEMENT_TELEPORT ( 1 << 4 ) + +// Specifically refuse to set the target's angles, rather than just turning them to 0 +#define SF_LOGIC_MEASURE_MOVEMENT_DONT_SET_ANGLES ( 1 << 5 ) +#endif + //----------------------------------------------------------------------------- // This will measure the movement of a target entity and move // another entity to match the movement of the first. @@ -23,7 +44,11 @@ class CLogicMeasureMovement : public CLogicalEntity public: virtual void Activate(); +#ifdef MAPBASE +public: +#else private: +#endif void SetMeasureTarget( const char *pName ); void SetMeasureReference( const char *pName ); void SetTarget( const char *pName ); @@ -37,13 +62,30 @@ private: void InputEnable( inputdata_t &inputdata ); void InputDisable( inputdata_t &inputdata ); +#ifdef MAPBASE + // Allows for derived class trickery + void MeasureThink(); //{ DoMeasure(); } + + // Allows for InputGetPosition(), etc. + virtual void DoMeasure(Vector &vecOrigin, QAngle &angAngles); + void HandleIgnoreFlags( float *vec ); + + void InputSetMeasureAttachment( inputdata_t &inputdata ); + void InputSetMeasureType( inputdata_t &inputdata ) { m_nMeasureType = inputdata.value.Int(); } + void InputGetPosition( inputdata_t &inputdata ); +#else void MeasureThink(); private: +#endif enum { MEASURE_POSITION = 0, MEASURE_EYE_POSITION, +#ifdef MAPBASE + MEASURE_ATTACHMENT, + //MEASURE_BARREL_POSITION, +#endif }; string_t m_strMeasureTarget; @@ -55,6 +97,16 @@ private: EHANDLE m_hTarget; EHANDLE m_hTargetReference; +#ifdef MAPBASE + string_t m_strAttachment; + int m_iAttachment; + + bool m_bOutputPosition; + + COutputVector m_OutPosition; + COutputVector m_OutAngles; +#endif + float m_flScale; int m_nMeasureType; }; @@ -70,6 +122,17 @@ BEGIN_DATADESC( CLogicMeasureMovement ) DEFINE_KEYFIELD( m_strTargetReference, FIELD_STRING, "TargetReference" ), DEFINE_KEYFIELD( m_flScale, FIELD_FLOAT, "TargetScale" ), DEFINE_KEYFIELD( m_nMeasureType, FIELD_INTEGER, "MeasureType" ), +#ifdef MAPBASE + DEFINE_INPUTFUNC( FIELD_INTEGER, "SetMeasureType", InputSetMeasureType ), + + DEFINE_KEYFIELD( m_strAttachment, FIELD_STRING, "MeasureAttachment" ), + DEFINE_FIELD( m_iAttachment, FIELD_EHANDLE ), + DEFINE_INPUTFUNC( FIELD_STRING, "SetMeasureAttachment", InputSetMeasureAttachment ), + + DEFINE_INPUT( m_bOutputPosition, FIELD_BOOLEAN, "ShouldOutputPosition" ), + + DEFINE_INPUTFUNC( FIELD_VOID, "GetPosition", InputGetPosition ), +#endif DEFINE_FIELD( m_hMeasureTarget, FIELD_EHANDLE ), DEFINE_FIELD( m_hMeasureReference, FIELD_EHANDLE ), @@ -79,12 +142,20 @@ BEGIN_DATADESC( CLogicMeasureMovement ) DEFINE_INPUTFUNC( FIELD_STRING, "SetMeasureTarget", InputSetMeasureTarget ), DEFINE_INPUTFUNC( FIELD_STRING, "SetMeasureReference", InputSetMeasureReference ), DEFINE_INPUTFUNC( FIELD_STRING, "SetTarget", InputSetTarget ), +#ifdef MAPBASE + DEFINE_INPUTFUNC( FIELD_STRING, "Target", InputSetTarget ), // For legacy support...even though that name was broken before. +#endif DEFINE_INPUTFUNC( FIELD_STRING, "SetTargetReference", InputSetTargetReference ), DEFINE_INPUTFUNC( FIELD_FLOAT, "SetTargetScale", InputSetTargetScale ), DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ), DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ), +#ifdef MAPBASE + DEFINE_OUTPUT( m_OutPosition, "OutPosition" ), + DEFINE_OUTPUT( m_OutAngles, "OutAngles" ), +#endif + DEFINE_THINKFUNC( MeasureThink ), END_DATADESC() @@ -112,40 +183,75 @@ void CLogicMeasureMovement::Activate() //----------------------------------------------------------------------------- void CLogicMeasureMovement::SetMeasureTarget( const char *pName ) { +#ifdef MAPBASE + m_hMeasureTarget = gEntList.FindEntityByName( NULL, pName, this ); +#else m_hMeasureTarget = gEntList.FindEntityByName( NULL, pName ); +#endif if ( !m_hMeasureTarget ) { if ( Q_strnicmp( STRING(m_strMeasureTarget), "!player", 8 ) ) { +#ifdef MAPBASE + Warning( "%s: Unable to find measure target entity %s\n", GetDebugName(), pName ); +#else Warning("logic_measure_movement: Unable to find measure target entity %s\n", pName ); +#endif } } +#ifdef MAPBASE + m_iAttachment = 0; +#endif } void CLogicMeasureMovement::SetMeasureReference( const char *pName ) { +#ifdef MAPBASE + m_hMeasureReference = gEntList.FindEntityByName( NULL, pName, this ); +#else m_hMeasureReference = gEntList.FindEntityByName( NULL, pName ); +#endif if ( !m_hMeasureReference ) { +#ifdef MAPBASE + Warning( "%s: Unable to find measure reference entity %s\n", GetDebugName(), pName ); +#else Warning("logic_measure_movement: Unable to find measure reference entity %s\n", pName ); +#endif } } void CLogicMeasureMovement::SetTarget( const char *pName ) { +#ifdef MAPBASE + m_hTarget = gEntList.FindEntityByName( NULL, pName, this ); +#else m_hTarget = gEntList.FindEntityByName( NULL, pName ); +#endif if ( !m_hTarget ) { +#ifdef MAPBASE + Warning( "%s: Unable to find movement target entity %s\n", GetDebugName(), pName ); +#else Warning("logic_measure_movement: Unable to find movement target entity %s\n", pName ); +#endif } } void CLogicMeasureMovement::SetTargetReference( const char *pName ) { +#ifdef MAPBASE + m_hTargetReference = gEntList.FindEntityByName( NULL, pName, this ); +#else m_hTargetReference = gEntList.FindEntityByName( NULL, pName ); +#endif if ( !m_hTargetReference ) { +#ifdef MAPBASE + Warning( "%s: Unable to find movement reference entity %s\n", GetDebugName(), pName ); +#else Warning("logic_measure_movement: Unable to find movement reference entity %s\n", pName ); +#endif } } @@ -165,6 +271,29 @@ void CLogicMeasureMovement::MeasureThink( ) // Make sure all entities are valid if ( m_hMeasureTarget.Get() && m_hMeasureReference.Get() && m_hTarget.Get() && m_hTargetReference.Get() ) { +#ifdef MAPBASE + Vector vecNewOrigin; + QAngle vecNewAngles; + DoMeasure(vecNewOrigin, vecNewAngles); + + if (m_bOutputPosition) + { + m_OutPosition.Set(vecNewOrigin, m_hTarget.Get(), this); + m_OutAngles.Set(vecNewAngles, m_hTarget.Get(), this); + } + + if (HasSpawnFlags( SF_LOGIC_MEASURE_MOVEMENT_TELEPORT )) + { + m_hTarget->Teleport( &vecNewOrigin, !HasSpawnFlags(SF_LOGIC_MEASURE_MOVEMENT_DONT_SET_ANGLES) ? &vecNewAngles : NULL, NULL ); + } + else + { + m_hTarget->SetAbsOrigin( vecNewOrigin ); + + if (!HasSpawnFlags(SF_LOGIC_MEASURE_MOVEMENT_DONT_SET_ANGLES)) + m_hTarget->SetAbsAngles( vecNewAngles ); + } +#else matrix3x4_t matRefToMeasure, matWorldToMeasure; switch( m_nMeasureType ) { @@ -175,7 +304,6 @@ void CLogicMeasureMovement::MeasureThink( ) case MEASURE_EYE_POSITION: AngleIMatrix( m_hMeasureTarget->EyeAngles(), m_hMeasureTarget->EyePosition(), matWorldToMeasure ); break; - // FIXME: Could add attachment point measurement here easily } @@ -201,11 +329,103 @@ void CLogicMeasureMovement::MeasureThink( ) MatrixAngles( matNewTargetToWorld, vecNewAngles, vecNewOrigin ); m_hTarget->SetAbsOrigin( vecNewOrigin ); m_hTarget->SetAbsAngles( vecNewAngles ); +#endif } SetNextThink( gpGlobals->curtime + TICK_INTERVAL ); } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: Moves logic_measure_movement's movement measurements to its own function, +// primarily to allow for the GetPosition input without any hacks. +// Also helps with derivative entities that would otherwise have to find a way to re-define the think function. +// Warning: Doesn't account for whether these handles are null! +//----------------------------------------------------------------------------- +void CLogicMeasureMovement::DoMeasure( Vector &vecOrigin, QAngle &angAngles ) +{ + matrix3x4_t matRefToMeasure, matWorldToMeasure; + switch( m_nMeasureType ) + { + case MEASURE_POSITION: + MatrixInvert( m_hMeasureTarget->EntityToWorldTransform(), matWorldToMeasure ); + break; + + case MEASURE_EYE_POSITION: + AngleIMatrix( m_hMeasureTarget->EyeAngles(), m_hMeasureTarget->EyePosition(), matWorldToMeasure ); + break; + + case MEASURE_ATTACHMENT: + if (CBaseAnimating *pAnimating = m_hMeasureTarget->GetBaseAnimating()) + { + if (m_iAttachment <= 0) + m_iAttachment = m_hMeasureTarget->GetBaseAnimating()->LookupAttachment(STRING(m_strAttachment)); + + if (m_iAttachment == -1) + Warning("WARNING: %s requesting invalid attachment %s on %s!\n", GetDebugName(), STRING(m_strAttachment), m_hMeasureTarget->GetDebugName()); + else + pAnimating->GetAttachment(m_iAttachment, matWorldToMeasure); + } + else + { + Warning("WARNING: %s requesting attachment point on non-animating entity %s!\n", GetDebugName(), m_hMeasureTarget->GetDebugName()); + } + break; + } + + ConcatTransforms( matWorldToMeasure, m_hMeasureReference->EntityToWorldTransform(), matRefToMeasure ); + + // Apply the scale factor + if ( ( m_flScale != 0.0f ) && ( m_flScale != 1.0f ) ) + { + Vector vecTranslation; + MatrixGetColumn( matRefToMeasure, 3, vecTranslation ); + vecTranslation /= m_flScale; + MatrixSetColumn( vecTranslation, 3, matRefToMeasure ); + } + + // Now apply the new matrix to the new reference point + matrix3x4_t matMeasureToRef, matNewTargetToWorld; + MatrixInvert( matRefToMeasure, matMeasureToRef ); + + // Handle origin ignorance + if (HasSpawnFlags( SF_LOGIC_MEASURE_MOVEMENT_USE_IGNORE_FLAGS_FOR_ORIGIN )) + { + // Get the position from the matrix's column directly and re-assign it + Vector vecPosition; + MatrixGetColumn( matMeasureToRef, 3, vecPosition ); + + HandleIgnoreFlags( vecPosition.Base() ); + + MatrixSetColumn( vecPosition, 3, matMeasureToRef ); + } + + ConcatTransforms( m_hTargetReference->EntityToWorldTransform(), matMeasureToRef, matNewTargetToWorld ); + + MatrixAngles( matNewTargetToWorld, angAngles, vecOrigin ); + + // If our spawnflags are greater than 0 (and don't just contain our default "TELEPORT" flag), we might need to ignore one of our angles. + if (GetSpawnFlags() && GetSpawnFlags() != SF_LOGIC_MEASURE_MOVEMENT_TELEPORT && !HasSpawnFlags(SF_LOGIC_MEASURE_MOVEMENT_USE_IGNORE_FLAGS_FOR_ORIGIN)) + { + HandleIgnoreFlags( angAngles.Base() ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Handles logic_measure_movement's ignore flags on the specified Vector/QAngle +//----------------------------------------------------------------------------- +FORCEINLINE void CLogicMeasureMovement::HandleIgnoreFlags( float *vec ) +{ + if (HasSpawnFlags( SF_LOGIC_MEASURE_MOVEMENT_IGNORE_X )) + vec[0] = 0.0f; + if (HasSpawnFlags( SF_LOGIC_MEASURE_MOVEMENT_IGNORE_Y )) + vec[1] = 0.0f; + if (HasSpawnFlags( SF_LOGIC_MEASURE_MOVEMENT_IGNORE_Z )) + vec[2] = 0.0f; +} +#endif + //----------------------------------------------------------------------------- // Enable, disable @@ -225,6 +445,84 @@ void CLogicMeasureMovement::InputDisable( inputdata_t &inputdata ) //----------------------------------------------------------------------------- // Methods to change various targets //----------------------------------------------------------------------------- +#ifdef MAPBASE + +// +// Inputs work differently now so they could take !activator, etc. +// + +void CLogicMeasureMovement::InputSetMeasureTarget( inputdata_t &inputdata ) +{ + m_strMeasureTarget = inputdata.value.StringID(); + m_hMeasureTarget = gEntList.FindEntityByName( NULL, STRING(m_strMeasureTarget), this, inputdata.pActivator, inputdata.pCaller ); + if ( !m_hMeasureTarget ) + { + if ( Q_strnicmp( STRING(m_strMeasureTarget), "!player", 8 ) ) + { + Warning( "%s: Unable to find measure target entity %s\n", GetDebugName(), STRING(m_strMeasureTarget) ); + } + } + + m_iAttachment = 0; + + if (!m_hTarget) + SetTarget( STRING(m_target) ); + if (!m_hTargetReference) + SetTargetReference( STRING(m_strTargetReference) ); +} + +void CLogicMeasureMovement::InputSetMeasureReference( inputdata_t &inputdata ) +{ + m_strMeasureReference = inputdata.value.StringID(); + m_hMeasureReference = gEntList.FindEntityByName( NULL, STRING(m_strMeasureReference), this, inputdata.pActivator, inputdata.pCaller ); + if ( !m_hMeasureReference ) + { + Warning( "%s: Unable to find measure reference entity %s\n", GetDebugName(), STRING(m_strMeasureReference) ); + } +} + +void CLogicMeasureMovement::InputSetTarget( inputdata_t &inputdata ) +{ + m_target = inputdata.value.StringID(); + m_hTarget = gEntList.FindEntityByName( NULL, STRING(m_target), this, inputdata.pActivator, inputdata.pCaller ); + if ( !m_hTarget ) + { + Warning( "%s: Unable to find movement target entity %s\n", GetDebugName(), STRING(m_target) ); + } +} + +void CLogicMeasureMovement::InputSetTargetReference( inputdata_t &inputdata ) +{ + m_strTargetReference = inputdata.value.StringID(); + m_hTargetReference = gEntList.FindEntityByName( NULL, STRING(m_strTargetReference), this, inputdata.pActivator, inputdata.pCaller ); + if ( !m_hTargetReference ) + { + Warning( "%s: Unable to find movement reference entity %s\n", GetDebugName(), STRING(m_strTargetReference) ); + } +} + +void CLogicMeasureMovement::InputSetMeasureAttachment( inputdata_t &inputdata ) +{ + m_strAttachment = inputdata.value.StringID(); + m_iAttachment = 0; +} + +// Just gets the position once and fires outputs without moving anything. +// We don't even need a target for this. +void CLogicMeasureMovement::InputGetPosition( inputdata_t &inputdata ) +{ + if ( !m_hMeasureTarget.Get() || !m_hMeasureReference.Get() || !m_hTargetReference.Get() ) + return; + + Vector vecNewOrigin; + QAngle vecNewAngles; + DoMeasure(vecNewOrigin, vecNewAngles); + + // m_bOutputPosition has been repurposed here to toggle between using the target or the input activator as the activator. + m_OutPosition.Set(vecNewOrigin, m_bOutputPosition ? m_hTarget.Get() : inputdata.pActivator, this); + m_OutAngles.Set(Vector(vecNewAngles.x, vecNewAngles.y, vecNewAngles.z), m_bOutputPosition ? m_hTarget.Get() : inputdata.pActivator, this); +} +#else void CLogicMeasureMovement::InputSetMeasureTarget( inputdata_t &inputdata ) { m_strMeasureTarget = MAKE_STRING( inputdata.value.String() ); @@ -250,8 +548,354 @@ void CLogicMeasureMovement::InputSetTargetReference( inputdata_t &inputdata ) m_strTargetReference = MAKE_STRING( inputdata.value.String() ); SetTargetReference( inputdata.value.String() ); } +#endif void CLogicMeasureMovement::InputSetTargetScale( inputdata_t &inputdata ) { m_flScale = inputdata.value.Float(); } + +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// This will measure the direction of a target entity and move +// another entity to where the target entity is facing. +// +// m_hMeasureTarget; // Whose direction is measured +// m_hMeasureReference; // Position where direction is measured +// m_hTarget; // Target whose origin is applied +// m_hTargetReference; // From where the target's origin is applied +//----------------------------------------------------------------------------- +class CLogicMeasureDirection : public CLogicMeasureMovement +{ + DECLARE_DATADESC(); + DECLARE_CLASS( CLogicMeasureDirection, CLogicMeasureMovement ); + +public: + + virtual void DoMeasure(Vector &vecOrigin, QAngle &angAngles); + + CBaseFilter *GetTraceFilter(); + //void InputSetTraceFilter( inputdata_t &inputdata ) { InputSetDamageFilter(inputdata); } + +private: + + float m_flTraceDistance; + int m_iMask; + int m_iCollisionGroup; + bool m_bHitIfPassed; + //string_t m_iszTraceFilter; + //CHandle m_hTraceFilter; + + bool m_bTraceTargetReference; + +}; + + +LINK_ENTITY_TO_CLASS( logic_measure_direction, CLogicMeasureDirection ); + + +BEGIN_DATADESC( CLogicMeasureDirection ) + + DEFINE_KEYFIELD( m_flTraceDistance, FIELD_FLOAT, "TraceDistance" ), + DEFINE_KEYFIELD( m_iMask, FIELD_INTEGER, "Mask" ), + DEFINE_KEYFIELD( m_iCollisionGroup, FIELD_INTEGER, "CollisionGroup" ), + DEFINE_KEYFIELD( m_bHitIfPassed, FIELD_BOOLEAN, "HitIfPassed" ), + DEFINE_KEYFIELD( m_bTraceTargetReference, FIELD_BOOLEAN, "TraceTargetReference" ), + + DEFINE_INPUTFUNC( FIELD_STRING, "SetTraceFilter", InputSetDamageFilter ), + +END_DATADESC() + +//----------------------------------------------------------------------------- +// Purpose: Gets our "trace filter". +//----------------------------------------------------------------------------- +inline CBaseFilter *CLogicMeasureDirection::GetTraceFilter() +{ + return static_cast(m_hDamageFilter.Get()); // pranked +} + +//----------------------------------------------------------------------------- +// Purpose: Does measure. +//----------------------------------------------------------------------------- +void CLogicMeasureDirection::DoMeasure( Vector &vecOrigin, QAngle &angAngles ) +{ + trace_t tr; + Vector vecStart, vecDir; + QAngle angStart; + switch( m_nMeasureType ) + { + case MEASURE_POSITION: + vecStart = m_hMeasureReference->GetAbsOrigin(); + angStart = m_hMeasureTarget->GetAbsAngles(); + break; + + case MEASURE_EYE_POSITION: + vecStart = m_hMeasureReference->EyePosition(); + angStart = m_hMeasureTarget->EyeAngles(); + break; + + case MEASURE_ATTACHMENT: + CBaseAnimating *pAnimating = m_hMeasureTarget->GetBaseAnimating(); + if (pAnimating) + { + if (m_iAttachment <= 0) + m_iAttachment = m_hMeasureTarget->GetBaseAnimating()->LookupAttachment(STRING(m_strAttachment)); + + if (m_iAttachment == -1) + Warning("WARNING: %s requesting invalid attachment %s on %s!\n", GetDebugName(), STRING(m_strAttachment), m_hMeasureTarget->GetDebugName()); + else + { + pAnimating->GetAttachment(m_iAttachment, vecStart, angStart); + } + } + else + { + Warning("WARNING: %s requesting attachment point on non-animating entity %s!\n", GetDebugName(), m_hMeasureTarget->GetDebugName()); + } + break; + } + + // If we have spawn flags, we might be supposed to ignore something + if (GetSpawnFlags() > 0) + { + if (!HasSpawnFlags(SF_LOGIC_MEASURE_MOVEMENT_USE_IGNORE_FLAGS_FOR_ORIGIN)) + AngleVectors(angStart, &vecDir); + + HandleIgnoreFlags( angStart.Base() ); + + if (HasSpawnFlags(SF_LOGIC_MEASURE_MOVEMENT_USE_IGNORE_FLAGS_FOR_ORIGIN)) + AngleVectors(angStart, &vecDir); + } + else + { + AngleVectors(angStart, &vecDir); + } + + CTraceFilterEntityFilter traceFilter(m_hMeasureReference, m_iCollisionGroup); + traceFilter.m_pFilter = GetTraceFilter(); + traceFilter.m_bHitIfPassed = m_bHitIfPassed; + UTIL_TraceLine( vecStart, vecStart + vecDir * (m_flTraceDistance != 0 ? m_flTraceDistance : MAX_TRACE_LENGTH), m_iMask, &traceFilter, &tr ); //MASK_BLOCKLOS_AND_NPCS + + Vector vecEnd = tr.endpos; + + // Apply the scale factor + float flScale = m_flScale; + if ( ( flScale != 0.0f ) && ( flScale != 1.0f ) ) + { + vecEnd = (vecStart + ((vecEnd - vecStart) / flScale)); + } + + Vector refPos = m_hTargetReference->GetAbsOrigin(); + Vector vecPos = refPos + (vecEnd - vecStart); + + if (m_bTraceTargetReference) + { + // Make sure we can go the whole distance there + UTIL_TraceLine( refPos, vecPos, m_iMask, &traceFilter, &tr ); + vecPos = tr.endpos; + } + + vecOrigin = vecPos; + angAngles = angStart; +} + + + +//----------------------------------------------------------------------------- +// The unused, "forgotten" entity brought back to life. +// Mirrors an entity's movement across a reference. +// It derives from logic_measure_movement now so it could use its features. +// This is unfinished and I'm still figuring out how it works. +// +// m_hMeasureTarget; // Whose position is mirrored (m_hRemoteTarget) +// m_hMeasureReference; // Position where position is mirrored (m_hMirrorRelative) +// m_hTarget; // Target whose origin is mirrored (m_hMovementTarget) +// m_hTargetReference; // From where the target's origin is mirrored (m_hMirrorTarget) +//----------------------------------------------------------------------------- +class CLogicMirrorMovement : public CLogicMeasureMovement +{ + DECLARE_DATADESC(); + DECLARE_CLASS( CLogicMirrorMovement, CLogicMeasureMovement ); + +public: + virtual void DoMeasure(Vector &vecOrigin, QAngle &angAngles); +}; + + +LINK_ENTITY_TO_CLASS( logic_mirror_movement, CLogicMirrorMovement ); + +BEGIN_DATADESC( CLogicMirrorMovement ) +END_DATADESC() + +//----------------------------------------------------------------------------- +// Purpose: Does measure. +//----------------------------------------------------------------------------- +void CLogicMirrorMovement::DoMeasure( Vector &vecOrigin, QAngle &angAngles ) +{ + + matrix3x4_t matRefToMeasure, matWorldToMeasure; + switch( m_nMeasureType ) + { + case MEASURE_POSITION: + MatrixInvert( m_hMeasureTarget->EntityToWorldTransform(), matWorldToMeasure ); + break; + + case MEASURE_EYE_POSITION: + AngleIMatrix( m_hMeasureTarget->EyeAngles(), m_hMeasureTarget->EyePosition(), matWorldToMeasure ); + break; + + case MEASURE_ATTACHMENT: + if (CBaseAnimating *pAnimating = m_hMeasureTarget->GetBaseAnimating()) + { + if (m_iAttachment <= 0) + m_iAttachment = m_hMeasureTarget->GetBaseAnimating()->LookupAttachment(STRING(m_strAttachment)); + + if (m_iAttachment == -1) + Warning("WARNING: %s requesting invalid attachment %s on %s!\n", GetDebugName(), STRING(m_strAttachment), m_hMeasureTarget->GetDebugName()); + else + pAnimating->GetAttachment(m_iAttachment, matWorldToMeasure); + } + else + { + Warning("WARNING: %s requesting attachment point on non-animating entity %s!\n", GetDebugName(), m_hMeasureTarget->GetDebugName()); + } + break; + } + + ConcatTransforms( matWorldToMeasure, m_hMeasureReference->EntityToWorldTransform(), matRefToMeasure ); + + // Apply the scale factor + if ( ( m_flScale != 0.0f ) && ( m_flScale != 1.0f ) ) + { + Vector vecTranslation; + MatrixGetColumn( matRefToMeasure, 3, vecTranslation ); + vecTranslation /= m_flScale; + MatrixSetColumn( vecTranslation, 3, matRefToMeasure ); + } + + MatrixScaleBy( -1.0f, matRefToMeasure ); + + QAngle angRot; + Vector vecPos; + MatrixAngles( matRefToMeasure, angRot ); + MatrixPosition( matRefToMeasure, vecPos ); + angRot.z *= -1.0f; + vecPos.z *= -1.0f; + AngleMatrix( angRot, vecPos, matWorldToMeasure ); + + // Now apply the new matrix to the new reference point + matrix3x4_t matMeasureToRef, matNewTargetToWorld; + MatrixInvert( matRefToMeasure, matMeasureToRef ); + + // Handle origin ignorance + if (HasSpawnFlags( SF_LOGIC_MEASURE_MOVEMENT_USE_IGNORE_FLAGS_FOR_ORIGIN )) + { + // Get the position from the matrix's column directly and re-assign it + Vector vecPosition; + MatrixGetColumn( matRefToMeasure, 3, vecPosition ); + + HandleIgnoreFlags( vecPosition.Base() ); + + MatrixSetColumn( vecPosition, 3, matRefToMeasure ); + } + + ConcatTransforms( m_hTargetReference->EntityToWorldTransform(), matMeasureToRef, matNewTargetToWorld ); + + MatrixAngles( matNewTargetToWorld, angAngles, vecOrigin ); + + // If our spawnflags are greater than 0 (and don't just contain our default "TELEPORT" flag), we might need to ignore one of our angles. + if (GetSpawnFlags() && GetSpawnFlags() != SF_LOGIC_MEASURE_MOVEMENT_TELEPORT && !HasSpawnFlags(SF_LOGIC_MEASURE_MOVEMENT_USE_IGNORE_FLAGS_FOR_ORIGIN)) + { + HandleIgnoreFlags( angAngles.Base() ); + } + + /* + VMatrix matPortal1ToWorldInv, matPortal2ToWorld; + MatrixInverseGeneral( m_hMeasureReference->EntityToWorldTransform(), matPortal1ToWorldInv ); + switch( m_nMeasureType ) + { + case MEASURE_POSITION: + matPortal2ToWorld = m_hMeasureTarget->EntityToWorldTransform(); + break; + + case MEASURE_EYE_POSITION: + matPortal2ToWorld.SetupMatrixOrgAngles( m_hMeasureTarget->EyePosition(), m_hMeasureTarget->EyeAngles() ); + break; + + case MEASURE_ATTACHMENT: + CBaseAnimating *pAnimating = m_hMeasureTarget->GetBaseAnimating(); + if (pAnimating) + { + if (m_iAttachment <= 0) + m_iAttachment = m_hMeasureTarget->GetBaseAnimating()->LookupAttachment(STRING(m_strAttachment)); + + if (m_iAttachment == -1) + Warning("WARNING: %s requesting invalid attachment %s on %s!\n", GetDebugName(), STRING(m_strAttachment), m_hMeasureTarget->GetDebugName()); + else + { + pAnimating->GetAttachment( m_iAttachment, matPortal2ToWorld.As3x4() ); + } + } + else + { + Warning("WARNING: %s requesting attachment point on non-animating entity %s!\n", GetDebugName(), m_hMeasureTarget->GetDebugName()); + } + break; + } + + // If we have spawn flags, we might be supposed to ignore something + if (GetSpawnFlags() > 0) + { + if (HasSpawnFlags( SF_LOGIC_MEASURE_MOVEMENT_USE_IGNORE_FLAGS_FOR_ORIGIN )) + { + // Get the position from the matrix's column directly and re-assign it + Vector vecPosition; + MatrixGetColumn( matPortal2ToWorld, 3, &vecPosition ); + + HandleIgnoreFlags( vecPosition.Base() ); + + MatrixSetColumn( matPortal2ToWorld, 3, vecPosition ); + } + else + { + // Get the angles from the matrix and re-assign it + QAngle angAngles; + MatrixToAngles( matPortal2ToWorld, angAngles ); + + HandleIgnoreFlags( angAngles.Base() ); + + matPortal2ToWorld.SetupMatrixAngles( angAngles ); + } + } + + // Apply the scale factor + if ( ( m_flScale != 0.0f ) && ( m_flScale != 1.0f ) ) + { + Vector vecTranslation; + MatrixGetColumn( matPortal2ToWorld.As3x4(), 3, vecTranslation ); + vecTranslation /= m_flScale; + MatrixSetColumn( vecTranslation, 3, matPortal2ToWorld.As3x4() ); + } + + // Get our scene camera's current orientation + Vector ptCameraPosition, vCameraLook, vCameraRight, vCameraUp; + ptCameraPosition = m_hTargetReference->EyePosition(); + m_hTargetReference->GetVectors( &vCameraLook, &vCameraRight, &vCameraUp ); + + // map this position and orientation to the remote portal, mirrored (invert the result) + Vector ptNewPosition, vNewLook; + ptNewPosition = matPortal1ToWorldInv * ptCameraPosition; + ptNewPosition = matPortal2ToWorld*(Vector( -ptNewPosition.x, -ptNewPosition.y, ptNewPosition.z )); + + vNewLook = matPortal1ToWorldInv.ApplyRotation( vCameraLook ); + vNewLook = matPortal2ToWorld.ApplyRotation( Vector( -vNewLook.x, -vNewLook.y, vNewLook.z ) ); + + // Set the point camera to the new location/orientation + QAngle qNewAngles; + VectorAngles( vNewLook, qNewAngles ); + + vecOrigin = ptNewPosition; + angAngles = qNewAngles; + */ +} +#endif diff --git a/src/game/server/logic_playmovie.cpp b/src/game/server/logic_playmovie.cpp new file mode 100644 index 00000000..4e62f738 --- /dev/null +++ b/src/game/server/logic_playmovie.cpp @@ -0,0 +1,136 @@ +//===== Copyright © 1996-2009, Valve Corporation, All rights reserved. ======// +// +// Purpose: Plays a movie and reports on finish +// +//===========================================================================// + +#include "cbase.h" + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class CLogicPlayMovie : public CLogicalEntity +{ +public: + DECLARE_CLASS( CLogicPlayMovie, CLogicalEntity ); + DECLARE_DATADESC(); + + CLogicPlayMovie( void ) { } + ~CLogicPlayMovie( void ) { } + + virtual void Precache( void ); + virtual void Spawn( void ); + +private: + + void InputPlayMovie( inputdata_t &data ); +#ifdef MAPBASE + void InputStopMovie( inputdata_t &data ); +#endif + void InputMovieFinished( inputdata_t &data ); + + string_t m_strMovieFilename; + bool m_bAllowUserSkip; +#ifdef MAPBASE + bool m_bLooping; + bool m_bMuted; + + bool m_bPlayingVideo; +#endif + + COutputEvent m_OnPlaybackFinished; +}; + +LINK_ENTITY_TO_CLASS( logic_playmovie, CLogicPlayMovie ); + +BEGIN_DATADESC( CLogicPlayMovie ) + + DEFINE_KEYFIELD( m_strMovieFilename, FIELD_STRING, "MovieFilename" ), + DEFINE_KEYFIELD( m_bAllowUserSkip, FIELD_BOOLEAN, "allowskip" ), +#ifdef MAPBASE + DEFINE_KEYFIELD( m_bLooping, FIELD_BOOLEAN, "loopvideo" ), + DEFINE_KEYFIELD( m_bMuted, FIELD_BOOLEAN, "mute" ), + + DEFINE_FIELD( m_bPlayingVideo, FIELD_BOOLEAN ), +#endif + + DEFINE_INPUTFUNC( FIELD_VOID, "PlayMovie", InputPlayMovie ), +#ifdef MAPBASE + DEFINE_INPUTFUNC( FIELD_VOID, "StopMovie", InputStopMovie ), +#endif + DEFINE_INPUTFUNC( FIELD_VOID, "__MovieFinished", InputMovieFinished ), + + DEFINE_OUTPUT( m_OnPlaybackFinished, "OnPlaybackFinished" ), + +END_DATADESC() + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CLogicPlayMovie::Precache( void ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CLogicPlayMovie::Spawn( void ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CLogicPlayMovie::InputPlayMovie( inputdata_t &data ) +{ + // Build the hacked string + + char szClientCmd[256]; + Q_snprintf( szClientCmd, sizeof(szClientCmd), + "playvideo_complex %s \"ent_fire %s __MovieFinished\" %d %d %d\n", + STRING(m_strMovieFilename), + GetEntityNameAsCStr(), + m_bAllowUserSkip, +#ifdef MAPBASE + m_bLooping, + m_bMuted +#else + 0, + 0 +#endif + ); + + // Send it on + engine->ServerCommand( szClientCmd ); + +#ifdef MAPBASE + m_bPlayingVideo = true; +#endif +} + +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CLogicPlayMovie::InputStopMovie( inputdata_t &data ) +{ + if (m_bPlayingVideo) + { + // Send it on + engine->ServerCommand( "stopvideos\n" ); + } +} +#endif + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CLogicPlayMovie::InputMovieFinished( inputdata_t &data ) +{ + // Simply fire our output + m_OnPlaybackFinished.FireOutput( this, this ); + +#ifdef MAPBASE + m_bPlayingVideo = false; +#endif +} diff --git a/src/game/server/logic_random_outputs.cpp b/src/game/server/logic_random_outputs.cpp new file mode 100644 index 00000000..b5c9f64b --- /dev/null +++ b/src/game/server/logic_random_outputs.cpp @@ -0,0 +1,221 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ==== +// +// When triggered, will attempt to fire off each of its outputs. Each output +// has its own chance of firing. +// +//============================================================================= + +#include "cbase.h" +#include "entityinput.h" +#include "entityoutput.h" +#include "eventqueue.h" +#include "soundent.h" +#include "logic_random_outputs.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +const int SF_REMOVE_ON_FIRE = 0x001; // Relay will remove itself after being triggered. +const int SF_ALLOW_FAST_RETRIGGER = 0x002; // Unless set, entity will disable itself until the last output is sent. + +LINK_ENTITY_TO_CLASS(logic_random_outputs, CLogicRandomOutputs); + + +BEGIN_DATADESC( CLogicRandomOutputs ) + + DEFINE_FIELD(m_bWaitForRefire, FIELD_BOOLEAN), + DEFINE_KEYFIELD(m_bDisabled, FIELD_BOOLEAN, "StartDisabled"), + + DEFINE_AUTO_ARRAY( m_flOnTriggerChance, FIELD_FLOAT ), + + // Inputs + DEFINE_INPUTFUNC(FIELD_VOID, "Enable", InputEnable), + DEFINE_INPUTFUNC(FIELD_VOID, "EnableRefire", InputEnableRefire), + DEFINE_INPUTFUNC(FIELD_VOID, "Disable", InputDisable), + DEFINE_INPUTFUNC(FIELD_VOID, "Toggle", InputToggle), + DEFINE_INPUTFUNC(FIELD_VOID, "Trigger", InputTrigger), + DEFINE_INPUTFUNC(FIELD_VOID, "CancelPending", InputCancelPending), + + // Outputs + DEFINE_OUTPUT(m_OnSpawn, "OnSpawn"), + DEFINE_OUTPUT(m_Output[0], "OnTrigger1"), + DEFINE_OUTPUT(m_Output[1], "OnTrigger2"), + DEFINE_OUTPUT(m_Output[2], "OnTrigger3"), + DEFINE_OUTPUT(m_Output[3], "OnTrigger4"), + DEFINE_OUTPUT(m_Output[4], "OnTrigger5"), + DEFINE_OUTPUT(m_Output[5], "OnTrigger6"), + DEFINE_OUTPUT(m_Output[6], "OnTrigger7"), + DEFINE_OUTPUT(m_Output[7], "OnTrigger8"), + +END_DATADESC() + + + +//----------------------------------------------------------------------------- +// Purpose: Constructor. +//----------------------------------------------------------------------------- +CLogicRandomOutputs::CLogicRandomOutputs(void) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: Read in the chance of firing each output +//----------------------------------------------------------------------------- +bool CLogicRandomOutputs::KeyValue( const char *szKeyName, const char *szValue ) +{ + if ( szValue && szValue[0] ) + { + for ( int i=0; i < NUM_RANDOM_OUTPUTS; i++ ) + { + if ( FStrEq( szKeyName, UTIL_VarArgs( "OnTriggerChance%d", i ) ) ) + { + m_flOnTriggerChance[i] = atof( szValue ); + return true; + } + } + } + + return BaseClass::KeyValue( szKeyName, szValue ); +} + +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: Give out the chance of firing each output +//----------------------------------------------------------------------------- +bool CLogicRandomOutputs::GetKeyValue( const char *szKeyName, char *szValue, int iMaxLen ) +{ + if ( !Q_strnicmp(szKeyName, "OnTriggerChance", 15) ) + { + for ( int i=0; i < NUM_RANDOM_OUTPUTS; i++ ) + { + if ( FStrEq( szKeyName, UTIL_VarArgs( "OnTriggerChance%d", i ) ) ) + { + Q_snprintf( szValue, iMaxLen, "%f", m_flOnTriggerChance[i] ); + return true; + } + } + } + + return BaseClass::GetKeyValue( szKeyName, szValue, iMaxLen ); +} +#endif + +//------------------------------------------------------------------------------ +// Kickstarts a think if we have OnSpawn connections. +//------------------------------------------------------------------------------ +void CLogicRandomOutputs::Activate() +{ + BaseClass::Activate(); + + if ( m_OnSpawn.NumberOfElements() > 0) + { + SetNextThink( gpGlobals->curtime + 0.01 ); + } +} + + +//----------------------------------------------------------------------------- +// If we have OnSpawn connections, this is called shortly after spawning to +// fire the OnSpawn output. +//----------------------------------------------------------------------------- +void CLogicRandomOutputs::Think() +{ + // Fire an output when we spawn. This is used for self-starting an entity + // template -- since the logic_random_outputs is inside the template, it gets all the + // name and I/O connection fixup, so can target other entities in the template. + m_OnSpawn.FireOutput( this, this ); + + // We only get here if we had OnSpawn connections, so this is safe. + if ( m_spawnflags & SF_REMOVE_ON_FIRE ) + { + UTIL_Remove(this); + } +} + + +//------------------------------------------------------------------------------ +// Purpose: Turns on the entity, allowing it to fire outputs. +//------------------------------------------------------------------------------ +void CLogicRandomOutputs::InputEnable( inputdata_t &inputdata ) +{ + m_bDisabled = false; +} + +//------------------------------------------------------------------------------ +// Purpose: Enables us to fire again. This input is only posted from our Trigger +// function to prevent rapid refire. +//------------------------------------------------------------------------------ +void CLogicRandomOutputs::InputEnableRefire( inputdata_t &inputdata ) +{ + Msg(" now enabling refire\n" ); + m_bWaitForRefire = false; +} + + +//------------------------------------------------------------------------------ +// Purpose: Cancels any I/O events in the queue that were fired by us. +//------------------------------------------------------------------------------ +void CLogicRandomOutputs::InputCancelPending( inputdata_t &inputdata ) +{ + g_EventQueue.CancelEvents( this ); + + // Stop waiting; allow another Trigger. + m_bWaitForRefire = false; +} + + +//------------------------------------------------------------------------------ +// Purpose: Turns off the entity, preventing it from firing outputs. +//------------------------------------------------------------------------------ +void CLogicRandomOutputs::InputDisable( inputdata_t &inputdata ) +{ + m_bDisabled = true; +} + + +//------------------------------------------------------------------------------ +// Purpose: Toggles the enabled/disabled state of the entity. +//------------------------------------------------------------------------------ +void CLogicRandomOutputs::InputToggle( inputdata_t &inputdata ) +{ + m_bDisabled = !m_bDisabled; +} + + +//----------------------------------------------------------------------------- +// Purpose: Input handler that triggers the logic_random_outputs. +//----------------------------------------------------------------------------- +void CLogicRandomOutputs::InputTrigger( inputdata_t &inputdata ) +{ + if ((!m_bDisabled) && (!m_bWaitForRefire)) + { + for ( int i=0 ; i < NUM_RANDOM_OUTPUTS ; i++ ) + { + if ( RandomFloat() <= m_flOnTriggerChance[i] ) + { + m_Output[i].FireOutput( inputdata.pActivator, this ); + } + } + + if (m_spawnflags & SF_REMOVE_ON_FIRE) + { + UTIL_Remove(this); + } + else if (!(m_spawnflags & SF_ALLOW_FAST_RETRIGGER)) + { + // find the max delay from all our outputs + float fMaxDelay = 0; + for ( int i=0 ; i < NUM_RANDOM_OUTPUTS ; i++ ) + { + fMaxDelay = MAX( fMaxDelay, m_Output[i].GetMaxDelay() ); + } + if ( fMaxDelay > 0 ) + { + // Disable the relay so that it cannot be refired until after the last output + // has been fired and post an input to re-enable ourselves. + m_bWaitForRefire = true; + g_EventQueue.AddEvent(this, "EnableRefire", fMaxDelay + 0.001, this, this); + } + } + } +} diff --git a/src/game/server/logic_random_outputs.h b/src/game/server/logic_random_outputs.h new file mode 100644 index 00000000..aeb60bff --- /dev/null +++ b/src/game/server/logic_random_outputs.h @@ -0,0 +1,54 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +#ifndef LOGICRANDOMOUTPUTS_H +#define LOGICRANDOMOUTPUTS_H + +#include "cbase.h" +#include "entityinput.h" +#include "entityoutput.h" +#include "eventqueue.h" + +#define NUM_RANDOM_OUTPUTS 8 + +class CLogicRandomOutputs : public CLogicalEntity +{ +public: + DECLARE_CLASS( CLogicRandomOutputs, CLogicalEntity ); + + CLogicRandomOutputs(); + + void Activate(); + void Think(); + virtual bool KeyValue( const char *szKeyName, const char *szValue ); +#ifdef MAPBASE + virtual bool GetKeyValue( const char *szKeyName, char *szValue, int iMaxLen ); +#endif + + // Input handlers + void InputEnable( inputdata_t &inputdata ); + void InputEnableRefire( inputdata_t &inputdata ); // Private input handler, not in FGD + void InputDisable( inputdata_t &inputdata ); + void InputToggle( inputdata_t &inputdata ); + void InputTrigger( inputdata_t &inputdata ); + void InputCancelPending( inputdata_t &inputdata ); + + DECLARE_DATADESC(); + + // Outputs + COutputEvent m_Output[ NUM_RANDOM_OUTPUTS ]; + COutputEvent m_OnSpawn; + + float m_flOnTriggerChance[ NUM_RANDOM_OUTPUTS ]; + +private: + + bool m_bDisabled; + bool m_bWaitForRefire; // Set to disallow a refire while we are waiting for our outputs to finish firing. +}; + +#endif //LOGICRANDOMOUTPUTS_H diff --git a/src/game/server/logicentities.cpp b/src/game/server/logicentities.cpp index 42b91100..d9250fcb 100644 --- a/src/game/server/logicentities.cpp +++ b/src/game/server/logicentities.cpp @@ -14,6 +14,12 @@ #include "saverestore_utlvector.h" #include "vstdlib/random.h" #include "gameinterface.h" +#ifdef MAPBASE +#include "mapbase/variant_tools.h" +#include "mapbase/matchers.h" +#include "mapbase/datadesc_mod.h" +#include "activitylist.h" +#endif // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" @@ -136,10 +142,13 @@ BEGIN_DATADESC( CLogicScript ) DEFINE_KEYFIELD( m_iszGroupMembers[12], FIELD_STRING, "Group12"), DEFINE_KEYFIELD( m_iszGroupMembers[13], FIELD_STRING, "Group13"), DEFINE_KEYFIELD( m_iszGroupMembers[14], FIELD_STRING, "Group14"), + DEFINE_KEYFIELD( m_iszGroupMembers[15], FIELD_STRING, "Group15"), // TODO: TF2 branch is missing Group15, which Mapbase uses. Hook into KeyValue function instead? DEFINE_KEYFIELD( m_iszGroupMembers[15], FIELD_STRING, "Group16"), END_DATADESC() + + //----------------------------------------------------------------------------- // Purpose: Compares a set of integer inputs to the one main input // Outputs true if they are all equivalant, false otherwise @@ -150,12 +159,24 @@ public: DECLARE_CLASS( CLogicCompareInteger, CLogicalEntity ); // outputs +#ifdef MAPBASE + COutputVariant m_OnEqual; + COutputVariant m_OnNotEqual; +#else COutputEvent m_OnEqual; COutputEvent m_OnNotEqual; +#endif // data +#ifdef MAPBASE + variant_t m_iValue; + bool m_iShouldCompareToValue; + bool m_bStrLenAllowed = true; + int DrawDebugTextOverlays(void); +#else int m_iIntegerValue; int m_iShouldCompareToValue; +#endif DECLARE_DATADESC(); @@ -163,6 +184,10 @@ public: // Input handlers void InputValue( inputdata_t &inputdata ); +#ifdef MAPBASE + void InputValueNoFire( inputdata_t &inputdata ); + void InputSetIntegerValue( inputdata_t &inputdata ); +#endif void InputCompareValues( inputdata_t &inputdata ); }; @@ -175,12 +200,22 @@ BEGIN_DATADESC( CLogicCompareInteger ) DEFINE_OUTPUT( m_OnEqual, "OnEqual" ), DEFINE_OUTPUT( m_OnNotEqual, "OnNotEqual" ), +#ifdef MAPBASE + DEFINE_KEYVARIANT( m_iValue, "IntegerValue" ), + DEFINE_KEYFIELD( m_iShouldCompareToValue, FIELD_BOOLEAN, "ShouldComparetoValue" ), + DEFINE_KEYFIELD( m_bStrLenAllowed, FIELD_BOOLEAN, "StrLenAllowed" ), +#else DEFINE_KEYFIELD( m_iIntegerValue, FIELD_INTEGER, "IntegerValue" ), DEFINE_KEYFIELD( m_iShouldCompareToValue, FIELD_INTEGER, "ShouldComparetoValue" ), +#endif DEFINE_FIELD( m_AllIntCompares, FIELD_INPUT ), DEFINE_INPUTFUNC( FIELD_INPUT, "InputValue", InputValue ), +#ifdef MAPBASE + DEFINE_INPUTFUNC( FIELD_INPUT, "InputValueNoFire", InputValueNoFire ), + DEFINE_INPUTFUNC( FIELD_INPUT, "SetReferenceValue", InputSetIntegerValue ), +#endif DEFINE_INPUTFUNC( FIELD_INPUT, "CompareValues", InputCompareValues ), END_DATADESC() @@ -193,9 +228,14 @@ END_DATADESC() //----------------------------------------------------------------------------- void CLogicCompareInteger::InputValue( inputdata_t &inputdata ) { +#ifdef MAPBASE + // Parse the input value, regardless of field type + inputdata.value = Variant_ParseInput(inputdata); +#else // make sure it's an int, if it can't be converted just throw it away if ( !inputdata.value.Convert(FIELD_INTEGER) ) return; +#endif // update the value list with the new value m_AllIntCompares.AddValue( inputdata.value, inputdata.nOutputID ); @@ -203,12 +243,38 @@ void CLogicCompareInteger::InputValue( inputdata_t &inputdata ) // if we haven't already this frame, send a message to ourself to update and fire if ( !m_AllIntCompares.m_bUpdatedThisFrame ) { +#ifdef MAPBASE + // Need to wait for all inputs to arrive + g_EventQueue.AddEvent( this, "CompareValues", 0.01, inputdata.pActivator, this, inputdata.nOutputID ); +#else // TODO: need to add this event with a lower priority, so it gets called after all inputs have arrived g_EventQueue.AddEvent( this, "CompareValues", 0, inputdata.pActivator, this, inputdata.nOutputID ); +#endif m_AllIntCompares.m_bUpdatedThisFrame = TRUE; } } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: Adds to the list of compared values without firing +//----------------------------------------------------------------------------- +void CLogicCompareInteger::InputValueNoFire( inputdata_t &inputdata ) +{ + // Parse the input value, regardless of field type + inputdata.value = Variant_ParseInput(inputdata); + + // update the value list with the new value + m_AllIntCompares.AddValue( inputdata.value, inputdata.nOutputID ); +} + +//----------------------------------------------------------------------------- +// Purpose: Sets our reference value +//----------------------------------------------------------------------------- +void CLogicCompareInteger::InputSetIntegerValue( inputdata_t &inputdata ) +{ + m_iValue = Variant_ParseInput(inputdata); +} +#endif //----------------------------------------------------------------------------- // Purpose: Forces a recompare @@ -218,6 +284,30 @@ void CLogicCompareInteger::InputCompareValues( inputdata_t &inputdata ) m_AllIntCompares.m_bUpdatedThisFrame = FALSE; // loop through all the values comparing them +#ifdef MAPBASE + variant_t value = m_iValue; + CMultiInputVar::inputitem_t *input = m_AllIntCompares.m_InputList; + + if ( !m_iShouldCompareToValue && input ) + { + value = input->value; + } + + while ( input ) + { + if ( !Variant_Equal(value, input->value, m_bStrLenAllowed) ) + { + // false + m_OnNotEqual.Set( input->value, inputdata.pActivator, this ); + return; + } + + input = input->next; + } + + // true! all values equal + m_OnEqual.Set( value, inputdata.pActivator, this ); +#else int value = m_iIntegerValue; CMultiInputVar::inputitem_t *input = m_AllIntCompares.m_InputList; @@ -240,8 +330,42 @@ void CLogicCompareInteger::InputCompareValues( inputdata_t &inputdata ) // true! all values equal m_OnEqual.FireOutput( inputdata.pActivator, this ); +#endif } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: Draw any debug text overlays +// Output : Current text offset from the top +//----------------------------------------------------------------------------- +int CLogicCompareInteger::DrawDebugTextOverlays( void ) +{ + int text_offset = BaseClass::DrawDebugTextOverlays(); + + if (m_debugOverlays & OVERLAY_TEXT_BIT) + { + char tempstr[512]; + + Q_snprintf(tempstr, sizeof(tempstr), " Reference Value: %s", m_iValue.GetDebug()); + EntityText(text_offset, tempstr, 0); + text_offset++; + + int count = 1; + CMultiInputVar::inputitem_t *input = m_AllIntCompares.m_InputList; + while ( input ) + { + Q_snprintf(tempstr, sizeof(tempstr), " Value %i: %s", count, input->value.GetDebug()); + EntityText(text_offset, tempstr, 0); + text_offset++; + + count++; + input = input->next; + } + } + return text_offset; +} +#endif + //----------------------------------------------------------------------------- // Purpose: Timer entity. Fires an output at regular or random intervals. @@ -289,6 +413,9 @@ public: int m_iUseRandomTime; float m_flLowerRandomBound; float m_flUpperRandomBound; +#ifdef MAPBASE + bool m_bUseBoundsForTimerInputs; +#endif // methods void ResetTimer( void ); @@ -307,6 +434,10 @@ BEGIN_DATADESC( CTimerEntity ) DEFINE_FIELD( m_bUpDownState, FIELD_BOOLEAN ), +#ifdef MAPBASE + DEFINE_KEYFIELD( m_bUseBoundsForTimerInputs, FIELD_BOOLEAN, "UseBoundsForTimerInputs" ), +#endif + // Inputs DEFINE_INPUTFUNC( FIELD_FLOAT, "RefireTime", InputRefireTime ), DEFINE_INPUTFUNC( FIELD_VOID, "FireTimer", InputFireTimer ), @@ -527,7 +658,24 @@ void CTimerEntity::InputAddToTimer( inputdata_t &inputdata ) // Add time to timer float flNextThink = GetNextThink(); +#ifdef MAPBASE + if (m_bUseBoundsForTimerInputs) + { + // Make sure it's not above our min or max bounds + if (flNextThink - gpGlobals->curtime > m_flUpperRandomBound) + return; + + flNextThink += inputdata.value.Float(); + flNextThink = clamp( flNextThink - gpGlobals->curtime, m_flLowerRandomBound, m_flUpperRandomBound ); + SetNextThink( gpGlobals->curtime + flNextThink ); + } + else + { + SetNextThink( flNextThink + inputdata.value.Float() ); + } +#else SetNextThink( flNextThink += inputdata.value.Float() ); +#endif } //----------------------------------------------------------------------------- @@ -542,6 +690,23 @@ void CTimerEntity::InputSubtractFromTimer( inputdata_t &inputdata ) // Subtract time from the timer but don't let the timer go negative float flNextThink = GetNextThink(); +#ifdef MAPBASE + if (m_bUseBoundsForTimerInputs) + { + // Make sure it's not above our min or max bounds + if (flNextThink - gpGlobals->curtime < m_flLowerRandomBound) + return; + + flNextThink -= inputdata.value.Float(); + flNextThink = clamp( flNextThink - gpGlobals->curtime, m_flLowerRandomBound, m_flUpperRandomBound ); + SetNextThink( gpGlobals->curtime + flNextThink ); + } + else + { + flNextThink -= inputdata.value.Float(); + SetNextThink( (flNextThink <= gpGlobals->curtime) ? gpGlobals->curtime : flNextThink ); + } +#else if ( ( flNextThink - gpGlobals->curtime ) <= inputdata.value.Float() ) { SetNextThink( gpGlobals->curtime ); @@ -550,6 +715,7 @@ void CTimerEntity::InputSubtractFromTimer( inputdata_t &inputdata ) { SetNextThink( flNextThink -= inputdata.value.Float() ); } +#endif } //----------------------------------------------------------------------------- @@ -962,6 +1128,39 @@ void CC_Global_Set( const CCommand &args ) static ConCommand global_set( "global_set", CC_Global_Set, "global_set : Sets the state of the given env_global (0 = OFF, 1 = ON, 2 = DEAD).", FCVAR_CHEAT ); +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Console command to set the counter of a global +//----------------------------------------------------------------------------- +void CC_Global_Counter( const CCommand &args ) +{ + const char *szGlobal = args[1]; + const char *szCounter = args[2]; + + if ( szGlobal == NULL || szCounter == NULL ) + { + Msg( "Usage: global_counter : Sets the counter of the given env_global.\n" ); + return; + } + + int nCounter = atoi( szCounter ); + + int nIndex = GlobalEntity_GetIndex( szGlobal ); + + if ( nIndex >= 0 ) + { + GlobalEntity_SetCounter( nIndex, nCounter ); + } + else + { + nIndex = GlobalEntity_Add( szGlobal, STRING( gpGlobals->mapname ), GLOBAL_ON ); + GlobalEntity_SetCounter( nIndex, nCounter ); + } +} + +static ConCommand global_counter( "global_counter", CC_Global_Counter, "global_counter : Sets the counter of the given env_global.", FCVAR_CHEAT ); +#endif + //----------------------------------------------------------------------------- // Purpose: Holds a global state that can be queried by other entities to change @@ -976,6 +1175,10 @@ public: void Spawn( void ); +#ifdef MAPBASE + bool KeyValue( const char *szKeyName, const char *szValue ); +#endif + // Input handlers void InputTurnOn( inputdata_t &inputdata ); void InputTurnOff( inputdata_t &inputdata ); @@ -1015,7 +1218,11 @@ BEGIN_DATADESC( CEnvGlobal ) DEFINE_INPUTFUNC( FIELD_INTEGER, "AddToCounter", InputAddToCounter ), DEFINE_INPUTFUNC( FIELD_VOID, "GetCounter", InputGetCounter ), +#ifdef MAPBASE + DEFINE_OUTPUT( m_outCounter, "OutCounter" ), +#else DEFINE_OUTPUT( m_outCounter, "Counter" ), +#endif END_DATADESC() @@ -1056,6 +1263,24 @@ void CEnvGlobal::Spawn( void ) } } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: Cache user entity field values until spawn is called. +// Input : szKeyName - Key to handle. +// szValue - Value for key. +// Output : Returns true if the key was handled, false if not. +//----------------------------------------------------------------------------- +bool CEnvGlobal::KeyValue( const char *szKeyName, const char *szValue ) +{ + // Any "Counter" outputs are changed to "OutCounter" before spawning. + if (FStrEq(szKeyName, "Counter") && strchr(szValue, ',')) + { + return BaseClass::KeyValue( "OutCounter", szValue ); + } + + return BaseClass::KeyValue( szKeyName, szValue ); +} +#endif //------------------------------------------------------------------------------ // Purpose: @@ -1415,7 +1640,11 @@ void CMultiSource::Register(void) class CMathCounter : public CLogicalEntity { DECLARE_CLASS( CMathCounter, CLogicalEntity ); +#ifdef MAPBASE +protected: +#else private: +#endif float m_flMin; // Minimum clamp value. If min and max are BOTH zero, no clamping is done. float m_flMax; // Maximum clamp value. bool m_bHitMin; // Set when we reach or go below our minimum value, cleared if we go above it again. @@ -1428,6 +1657,9 @@ private: int DrawDebugTextOverlays(void); +#ifdef MAPBASE + virtual +#endif void UpdateOutValue(CBaseEntity *pActivator, float fNewValue); // Inputs @@ -1442,12 +1674,20 @@ private: void InputGetValue( inputdata_t &inputdata ); void InputEnable( inputdata_t &inputdata ); void InputDisable( inputdata_t &inputdata ); +#ifdef MAPBASE + void InputSetMinValueNoFire( inputdata_t &inputdata ); + void InputSetMaxValueNoFire( inputdata_t &inputdata ); +#endif // Outputs COutputFloat m_OutValue; COutputFloat m_OnGetValue; // Used for polling the counter value. COutputEvent m_OnHitMin; COutputEvent m_OnHitMax; +#ifdef MAPBASE + COutputEvent m_OnChangedFromMin; + COutputEvent m_OnChangedFromMax; +#endif DECLARE_DATADESC(); }; @@ -1478,12 +1718,20 @@ BEGIN_DATADESC( CMathCounter ) DEFINE_INPUTFUNC(FIELD_VOID, "GetValue", InputGetValue), DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ), DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ), +#ifdef MAPBASE + DEFINE_INPUTFUNC( FIELD_FLOAT, "SetMaxValueNoFire", InputSetMaxValueNoFire ), + DEFINE_INPUTFUNC( FIELD_FLOAT, "SetMinValueNoFire", InputSetMinValueNoFire ), +#endif // Outputs DEFINE_OUTPUT(m_OutValue, "OutValue"), DEFINE_OUTPUT(m_OnHitMin, "OnHitMin"), DEFINE_OUTPUT(m_OnHitMax, "OnHitMax"), DEFINE_OUTPUT(m_OnGetValue, "OnGetValue"), +#ifdef MAPBASE + DEFINE_OUTPUT( m_OnChangedFromMin, "OnChangedFromMin" ), + DEFINE_OUTPUT( m_OnChangedFromMax, "OnChangedFromMax" ), +#endif END_DATADESC() @@ -1500,7 +1748,11 @@ bool CMathCounter::KeyValue(const char *szKeyName, const char *szValue) // if (!stricmp(szKeyName, "startvalue")) { +#ifdef MAPBASE + m_OutValue.Init(atof(szValue)); +#else m_OutValue.Init(atoi(szValue)); +#endif return(true); } @@ -1596,6 +1848,29 @@ void CMathCounter::InputSetHitMin( inputdata_t &inputdata ) UpdateOutValue( inputdata.pActivator, m_OutValue.Get() ); } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Change min/max +//----------------------------------------------------------------------------- +void CMathCounter::InputSetMaxValueNoFire( inputdata_t &inputdata ) +{ + m_flMax = inputdata.value.Float(); + if ( m_flMax < m_flMin ) + { + m_flMin = m_flMax; + } +} + +void CMathCounter::InputSetMinValueNoFire( inputdata_t &inputdata ) +{ + m_flMin = inputdata.value.Float(); + if ( m_flMax < m_flMin ) + { + m_flMax = m_flMin; + } +} +#endif + //----------------------------------------------------------------------------- // Purpose: Input handler for adding to the accumulator value. @@ -1754,6 +2029,14 @@ void CMathCounter::UpdateOutValue(CBaseEntity *pActivator, float fNewValue) } else { +#ifdef MAPBASE + // Fire an output if we just changed from the maximum value + if ( m_OutValue.Get() == m_flMax ) + { + m_OnChangedFromMax.FireOutput( pActivator, this ); + } +#endif + m_bHitMax = false; } @@ -1770,6 +2053,13 @@ void CMathCounter::UpdateOutValue(CBaseEntity *pActivator, float fNewValue) } else { +#ifdef MAPBASE + // Fire an output if we just changed from the maximum value + if ( m_OutValue.Get() == m_flMin ) + { + m_OnChangedFromMin.FireOutput( pActivator, this ); + } +#endif m_bHitMin = false; } @@ -1780,6 +2070,430 @@ void CMathCounter::UpdateOutValue(CBaseEntity *pActivator, float fNewValue) } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: Advanced math_counter with advanced calculation capabilities. +//----------------------------------------------------------------------------- +class CMathCounterAdvanced : public CMathCounter +{ + DECLARE_CLASS( CMathCounterAdvanced, CMathCounter ); +private: + + bool m_bPreserveValue; + bool m_bAlwaysOutputAsInt; + float m_flLerpPercent; + + void UpdateOutValue(CBaseEntity *pActivator, float fNewValue); + + void InputSetValueToPi( inputdata_t &inputdata ); + + void InputPower( inputdata_t &inputdata ); + void InputSquareRoot( inputdata_t &inputdata ); + + void InputRound( inputdata_t &inputdata ); + void InputFloor( inputdata_t &inputdata ); + void InputCeiling( inputdata_t &inputdata ); + void InputTrunc( inputdata_t &inputdata ); + + void InputSine( inputdata_t &inputdata ); + void InputCosine( inputdata_t &inputdata ); + void InputTangent( inputdata_t &inputdata ); + + void InputRandomInt( inputdata_t &inputdata ); + void InputRandomFloat( inputdata_t &inputdata ); + + void InputLerpTo( inputdata_t &inputdata ); + + DECLARE_DATADESC(); +}; + +LINK_ENTITY_TO_CLASS(math_counter_advanced, CMathCounterAdvanced); + + +BEGIN_DATADESC( CMathCounterAdvanced ) + + // Keys + DEFINE_INPUT(m_bPreserveValue, FIELD_BOOLEAN, "PreserveValue"), + DEFINE_INPUT(m_bAlwaysOutputAsInt, FIELD_BOOLEAN, "AlwaysOutputAsInt"), + DEFINE_INPUT(m_flLerpPercent, FIELD_FLOAT, "SetLerpPercent"), + + // Inputs + DEFINE_INPUTFUNC(FIELD_VOID, "SetValueToPi", InputSetValueToPi), + + DEFINE_INPUTFUNC(FIELD_VOID, "SquareRoot", InputSquareRoot), + DEFINE_INPUTFUNC(FIELD_INTEGER, "Power", InputPower), + + DEFINE_INPUTFUNC(FIELD_INTEGER, "Round", InputRound), + DEFINE_INPUTFUNC(FIELD_INTEGER, "Floor", InputFloor), + DEFINE_INPUTFUNC(FIELD_INTEGER, "Ceil", InputCeiling), + DEFINE_INPUTFUNC(FIELD_INTEGER, "Trunc", InputTrunc), + + DEFINE_INPUTFUNC(FIELD_VOID, "Sin", InputSine), + DEFINE_INPUTFUNC(FIELD_VOID, "Cos", InputCosine), + DEFINE_INPUTFUNC(FIELD_VOID, "Tan", InputTangent), + + DEFINE_INPUTFUNC(FIELD_STRING, "RandomInt", InputRandomInt), + DEFINE_INPUTFUNC(FIELD_STRING, "RandomFloat", InputRandomFloat), + + DEFINE_INPUTFUNC(FIELD_FLOAT, "LerpTo", InputLerpTo), + +END_DATADESC() + +//----------------------------------------------------------------------------- +// Purpose: Input handler for setting the current value to pi. +//----------------------------------------------------------------------------- +void CMathCounterAdvanced::InputSetValueToPi( inputdata_t &inputdata ) +{ + if( m_bDisabled ) + { + DevMsg("Math Counter %s ignoring SET TO PI because it is disabled\n", GetDebugName() ); + return; + } + + float fNewValue = M_PI; + UpdateOutValue( inputdata.pActivator, fNewValue ); +} + +//----------------------------------------------------------------------------- +// Purpose: Input handler for calculating the square root of the current value. +//----------------------------------------------------------------------------- +void CMathCounterAdvanced::InputSquareRoot( inputdata_t &inputdata ) +{ + if( m_bDisabled ) + { + DevMsg("Math Counter %s ignoring SQUARE ROOT because it is disabled\n", GetDebugName() ); + return; + } + + float fNewValue = sqrt(m_OutValue.Get()); + UpdateOutValue( inputdata.pActivator, fNewValue ); +} + +//----------------------------------------------------------------------------- +// Purpose: Input handler for exponentiation of the current value. Use 2 to square it. +// Input : Integer value to raise the current value's power. +//----------------------------------------------------------------------------- +void CMathCounterAdvanced::InputPower( inputdata_t &inputdata ) +{ + if( m_bDisabled ) + { + DevMsg("Math Counter %s ignoring POWER!!! because it is disabled\n", GetDebugName() ); + return; + } + + float fNewValue = pow(m_OutValue.Get(), inputdata.value.Int()); + UpdateOutValue( inputdata.pActivator, fNewValue ); +} + +// +// For some reason, I had trouble finding the original math functions at first. +// Then I just randomly stumbled upon them, bright as day. +// Oh well. These might be faster anyway. +// +FORCEINLINE int RoundToNumber(int input, int number) +{ + (input < 0 && number > 0) ? number *= -1 : 0; + int result = (input + (number / 2)); + result -= (result % number); + return result; +} + +// Warning: Negative numbers should be ceiled +FORCEINLINE int FloorToNumber(int input, int number) +{ + return (input - (input % number)); +} + +FORCEINLINE int CeilToNumber(int input, int number) +{ + (input < 0 && number > 0) ? number *= -1 : 0; + int result = (input - (input % number)); + return result != input ? result + number : result; +} + +FORCEINLINE int TruncToNumber(int input, int number) +{ + //(input < 0 && number > 0) ? number *= -1 : 0; + return (input - (input % number)); +} + +//----------------------------------------------------------------------------- +// Purpose: Input handler for rounding an integer to the specified number. (e.g. 126 rounding to 10 = 130, 1523 rounding to 5 = 1525) +// Input : Integer value to round the current value to. +//----------------------------------------------------------------------------- +void CMathCounterAdvanced::InputRound( inputdata_t &inputdata ) +{ + if( m_bDisabled ) + { + DevMsg("Math Counter %s ignoring ROUND because it is disabled\n", GetDebugName() ); + return; + } + + int iMultiple = inputdata.value.Int(); + int iNewValue; + if (iMultiple != 0) + { + // Round to the nearest input number. + iNewValue = RoundToNumber(m_OutValue.Get(), iMultiple); + } + else + { + // 0 just rounds floats. + iNewValue = static_cast(m_OutValue.Get() + 0.5f); + } + + UpdateOutValue( inputdata.pActivator, iNewValue ); +} + +//----------------------------------------------------------------------------- +// Purpose: Input handler for flooring an integer to the specified number. (e.g. 126 flooring to 10 = 120, 1528 flooring to 5 = 1525) +// Input : Integer value to floor the current value to. +//----------------------------------------------------------------------------- +void CMathCounterAdvanced::InputFloor( inputdata_t &inputdata ) +{ + if( m_bDisabled ) + { + DevMsg("Math Counter %s ignoring FLOOR because it is disabled\n", GetDebugName() ); + return; + } + + int iMultiple = inputdata.value.Int(); + int iNewValue; + if (iMultiple != 0) + { + iNewValue = m_OutValue.Get(); + if (iNewValue >= 0) + { + // Floor to the nearest input number. + iNewValue = FloorToNumber(m_OutValue.Get(), iMultiple); + } + else + { + // We have to do it differently for negatives. + iNewValue = CeilToNumber(m_OutValue.Get(), iMultiple); + } + } + else + { + // 0 just floors floats. + iNewValue = static_cast(m_OutValue.Get()); + } + + UpdateOutValue( inputdata.pActivator, iNewValue ); +} + +//----------------------------------------------------------------------------- +// Purpose: Input handler for ceiling an integer to the specified number. (e.g. 126 ceiling to 10 = 130, 1523 ceiling to 50 = 1550) +// Input : Integer value to ceil the current value to. +//----------------------------------------------------------------------------- +void CMathCounterAdvanced::InputCeiling( inputdata_t &inputdata ) +{ + if( m_bDisabled ) + { + DevMsg("Math Counter %s ignoring CEIL because it is disabled\n", GetDebugName() ); + return; + } + + int iMultiple = inputdata.value.Int(); + int iNewValue; + if (iMultiple != 0) + { + // Ceil to the nearest input number. + iNewValue = CeilToNumber(m_OutValue.Get(), iMultiple); + } + else + { + // 0 just ceils floats. + iNewValue = static_cast(m_OutValue.Get()) + (m_OutValue.Get() != 0 ? 1 : 0); + } + + UpdateOutValue( inputdata.pActivator, iNewValue ); +} + +//----------------------------------------------------------------------------- +// Purpose: Input handler for truncating an integer to the specified number. (e.g. 126 rounding to 10 = 120, -1523 rounding to 5 = 1520) +// Input : Integer value to truncate the current value to. +//----------------------------------------------------------------------------- +void CMathCounterAdvanced::InputTrunc( inputdata_t &inputdata ) +{ + if( m_bDisabled ) + { + DevMsg("Math Counter %s ignoring TRUNC because it is disabled\n", GetDebugName() ); + return; + } + + int iMultiple = inputdata.value.Int(); + int iNewValue; + if (iMultiple != 0) + { + // Floor always truncates negative numbers if we don't tell it not to + iNewValue = FloorToNumber(m_OutValue.Get(), iMultiple); + } + else + { + // 0 just ceils floats. + iNewValue = static_cast(m_OutValue.Get()); + if (iNewValue < 0) + iNewValue += 1; + } + + UpdateOutValue( inputdata.pActivator, iNewValue ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Input handler for applying sine to the current value. +//----------------------------------------------------------------------------- +void CMathCounterAdvanced::InputSine( inputdata_t &inputdata ) +{ + if( m_bDisabled ) + { + DevMsg("Math Counter %s ignoring SINE because it is disabled\n", GetDebugName() ); + return; + } + + float fNewValue = sin(m_OutValue.Get()); + UpdateOutValue( inputdata.pActivator, fNewValue ); +} + +//----------------------------------------------------------------------------- +// Purpose: Input handler for applying cosine to the current value. +//----------------------------------------------------------------------------- +void CMathCounterAdvanced::InputCosine( inputdata_t &inputdata ) +{ + if( m_bDisabled ) + { + DevMsg("Math Counter %s ignoring SINE because it is disabled\n", GetDebugName() ); + return; + } + + float fNewValue = cos(m_OutValue.Get()); + UpdateOutValue( inputdata.pActivator, fNewValue ); +} + +//----------------------------------------------------------------------------- +// Purpose: Input handler for applying tangent to the current value. +//----------------------------------------------------------------------------- +void CMathCounterAdvanced::InputTangent( inputdata_t &inputdata ) +{ + if( m_bDisabled ) + { + DevMsg("Math Counter %s ignoring SINE because it is disabled\n", GetDebugName() ); + return; + } + + float fNewValue = tan(m_OutValue.Get()); + UpdateOutValue( inputdata.pActivator, fNewValue ); +} + +//----------------------------------------------------------------------------- +// Purpose: Input handler for random int generation. +//----------------------------------------------------------------------------- +void CMathCounterAdvanced::InputRandomInt( inputdata_t &inputdata ) +{ + if( m_bDisabled ) + { + DevMsg("Math Counter %s ignoring RANDOMINT because it is disabled\n", GetDebugName() ); + return; + } + + int i1 = 0; + int i2 = 0; + + char szInput[128]; + Q_strncpy( szInput, inputdata.value.String(), sizeof(szInput) ); + char *sSpace = strchr( szInput, ' ' ); + if ( sSpace ) + { + i1 = atoi(szInput); + i2 = atoi(sSpace+1); + } + else + { + // No space, assume anything from 0 to X + i2 = atoi(szInput); + } + + float fNewValue = RandomInt(i1, i2); + UpdateOutValue( inputdata.pActivator, fNewValue ); +} + +//----------------------------------------------------------------------------- +// Purpose: Input handler for random float generation. +//----------------------------------------------------------------------------- +void CMathCounterAdvanced::InputRandomFloat( inputdata_t &inputdata ) +{ + if( m_bDisabled ) + { + DevMsg("Math Counter %s ignoring RANDOMFLOAT because it is disabled\n", GetDebugName() ); + return; + } + + float f1 = 0; + float f2 = 0; + + char szInput[128]; + Q_strncpy( szInput, inputdata.value.String(), sizeof(szInput) ); + char *sSpace = strchr( szInput, ' ' ); + if ( sSpace ) + { + f1 = atof(szInput); + f2 = atof(sSpace+1); + } + else + { + // No space, assume anything from 0 to X + f2 = atof(szInput); + } + + float fNewValue = RandomFloat(f1, f2); + UpdateOutValue( inputdata.pActivator, fNewValue ); +} + +//----------------------------------------------------------------------------- +// Purpose: Input handler for random float generation. +//----------------------------------------------------------------------------- +void CMathCounterAdvanced::InputLerpTo( inputdata_t &inputdata ) +{ + if( m_bDisabled ) + { + DevMsg("Math Counter %s ignoring LERPTO because it is disabled\n", GetDebugName() ); + return; + } + + float fNewValue = m_OutValue.Get() + (inputdata.value.Float() - m_OutValue.Get()) * m_flLerpPercent; + UpdateOutValue( inputdata.pActivator, fNewValue ); +} + +//----------------------------------------------------------------------------- +// Purpose: Sets the value to the new value, clamping and firing the output value. +// Input : fNewValue - Value to set. +//----------------------------------------------------------------------------- +void CMathCounterAdvanced::UpdateOutValue(CBaseEntity *pActivator, float fNewValue) +{ + if (m_bAlwaysOutputAsInt) + fNewValue = roundf(fNewValue); + + if (m_bPreserveValue) + { + //float fOriginal = m_OutValue.Get(); + //DevMsg("Preserve Before: %f\n", fOriginal); + //BaseClass::UpdateOutValue(pActivator, fNewValue); + //DevMsg("Preserve After: %f\n", fOriginal); + //m_OutValue.Init(fOriginal); + + variant_t var; + var.SetFloat(fNewValue); + m_OutValue.FireOutput( var, pActivator, this ); + } + else + { + BaseClass::UpdateOutValue(pActivator, fNewValue); + } +} +#endif + + //----------------------------------------------------------------------------- // Purpose: Compares a single string input to up to 16 case values, firing an @@ -1796,6 +2510,10 @@ class CLogicCase : public CLogicalEntity private: string_t m_nCase[MAX_LOGIC_CASES]; +#ifdef MAPBASE + bool m_bMultipleCasesAllowed; +#endif + int m_nShuffleCases; int m_nLastShuffleCase; unsigned char m_uchShuffleCaseMap[MAX_LOGIC_CASES]; @@ -1812,6 +2530,9 @@ private: // Outputs COutputEvent m_OnCase[MAX_LOGIC_CASES]; // Fired when the input value matches one of the case values. COutputVariant m_OnDefault; // Fired when no match was found. +#ifdef MAPBASE + COutputVariant m_OnUsed; // Fired when this entity receives any input at all. +#endif DECLARE_DATADESC(); }; @@ -1841,6 +2562,10 @@ BEGIN_DATADESC( CLogicCase ) DEFINE_KEYFIELD(m_nCase[13], FIELD_STRING, "Case14"), DEFINE_KEYFIELD(m_nCase[14], FIELD_STRING, "Case15"), DEFINE_KEYFIELD(m_nCase[15], FIELD_STRING, "Case16"), + +#ifdef MAPBASE + DEFINE_KEYFIELD(m_bMultipleCasesAllowed, FIELD_BOOLEAN, "MultipleCasesAllowed"), +#endif DEFINE_FIELD( m_nShuffleCases, FIELD_INTEGER ), DEFINE_FIELD( m_nLastShuffleCase, FIELD_INTEGER ), @@ -1870,6 +2595,9 @@ BEGIN_DATADESC( CLogicCase ) DEFINE_OUTPUT(m_OnCase[15], "OnCase16"), DEFINE_OUTPUT(m_OnDefault, "OnDefault"), +#ifdef MAPBASE + DEFINE_OUTPUT(m_OnUsed, "OnUsed"), +#endif END_DATADESC() @@ -1893,16 +2621,35 @@ void CLogicCase::Spawn( void ) //----------------------------------------------------------------------------- void CLogicCase::InputValue( inputdata_t &inputdata ) { +#ifdef MAPBASE + m_OnUsed.Set(inputdata.value, inputdata.pActivator, this); + bool bFoundCase = false; +#endif const char *pszValue = inputdata.value.String(); for (int i = 0; i < MAX_LOGIC_CASES; i++) { +#ifdef MAPBASE + if ((m_nCase[i] != NULL_STRING) && Matcher_Match(STRING(m_nCase[i]), pszValue)) + { + m_OnCase[i].FireOutput( inputdata.pActivator, this ); + + if (!m_bMultipleCasesAllowed) + return; + else if (!bFoundCase) + bFoundCase = true; + } +#else if ((m_nCase[i] != NULL_STRING) && !stricmp(STRING(m_nCase[i]), pszValue)) { m_OnCase[i].FireOutput( inputdata.pActivator, this ); return; } +#endif } +#ifdef MAPBASE + if (!bFoundCase) +#endif m_OnDefault.Set( inputdata.value, inputdata.pActivator, this ); } @@ -2034,23 +2781,50 @@ class CLogicCompare : public CLogicalEntity public: int DrawDebugTextOverlays(void); +#ifdef MAPBASE + void Spawn(); +#endif + private: // Inputs void InputSetValue( inputdata_t &inputdata ); void InputSetValueCompare( inputdata_t &inputdata ); void InputSetCompareValue( inputdata_t &inputdata ); +#ifdef MAPBASE + void InputSetCompareValueCompare( inputdata_t &inputdata ); +#endif void InputCompare( inputdata_t &inputdata ); +#ifdef MAPBASE + void DoCompare(CBaseEntity *pActivator, variant_t value); +#else void DoCompare(CBaseEntity *pActivator, float flInValue); +#endif +#ifdef MAPBASE + bool m_bStrLenAllowed = true; + bool m_bGreaterThanOrEqual; + variant_t m_InValue; // Place to hold the last input value for a recomparison. + variant_t m_CompareValue; // The value to compare the input value against. +#else float m_flInValue; // Place to hold the last input value for a recomparison. float m_flCompareValue; // The value to compare the input value against. +#endif // Outputs +#ifdef MAPBASE + COutputVariant m_OnLessThan; // Fired when the input value is less than the compare value. + COutputVariant m_OnEqualTo; // Fired when the input value is equal to the compare value. + COutputVariant m_OnNotEqualTo; // Fired when the input value is not equal to the compare value. + COutputVariant m_OnGreaterThan; // Fired when the input value is greater than the compare value. + COutputVariant m_OnLessThanOrEqualTo; // Fired when the input value is less than or equal to the compare value. + COutputVariant m_OnGreaterThanOrEqualTo; // Fired when the input value is greater than or equal to the compare value. +#else COutputFloat m_OnLessThan; // Fired when the input value is less than the compare value. COutputFloat m_OnEqualTo; // Fired when the input value is equal to the compare value. COutputFloat m_OnNotEqualTo; // Fired when the input value is not equal to the compare value. COutputFloat m_OnGreaterThan; // Fired when the input value is greater than the compare value. +#endif DECLARE_DATADESC(); }; @@ -2061,13 +2835,26 @@ LINK_ENTITY_TO_CLASS(logic_compare, CLogicCompare); BEGIN_DATADESC( CLogicCompare ) // Keys +#ifdef MAPBASE + DEFINE_KEYFIELD( m_bStrLenAllowed, FIELD_BOOLEAN, "StrLenAllowed" ), + DEFINE_KEYVARIANT(m_CompareValue, "CompareValue"), + DEFINE_KEYVARIANT(m_InValue, "InitialValue"), +#else DEFINE_KEYFIELD(m_flCompareValue, FIELD_FLOAT, "CompareValue"), DEFINE_KEYFIELD(m_flInValue, FIELD_FLOAT, "InitialValue"), +#endif // Inputs +#ifdef MAPBASE + DEFINE_INPUTFUNC(FIELD_INPUT, "SetValue", InputSetValue), + DEFINE_INPUTFUNC(FIELD_INPUT, "SetValueCompare", InputSetValueCompare), + DEFINE_INPUTFUNC(FIELD_INPUT, "SetCompareValue", InputSetCompareValue), + DEFINE_INPUTFUNC(FIELD_INPUT, "SetCompareValueCompare", InputSetCompareValueCompare), +#else DEFINE_INPUTFUNC(FIELD_FLOAT, "SetValue", InputSetValue), DEFINE_INPUTFUNC(FIELD_FLOAT, "SetValueCompare", InputSetValueCompare), DEFINE_INPUTFUNC(FIELD_FLOAT, "SetCompareValue", InputSetCompareValue), +#endif DEFINE_INPUTFUNC(FIELD_VOID, "Compare", InputCompare), // Outputs @@ -2075,18 +2862,40 @@ BEGIN_DATADESC( CLogicCompare ) DEFINE_OUTPUT(m_OnNotEqualTo, "OnNotEqualTo"), DEFINE_OUTPUT(m_OnGreaterThan, "OnGreaterThan"), DEFINE_OUTPUT(m_OnLessThan, "OnLessThan"), +#ifdef MAPBASE + DEFINE_OUTPUT(m_OnGreaterThanOrEqualTo, "OnGreaterThanOrEqualTo"), + DEFINE_OUTPUT(m_OnLessThanOrEqualTo, "OnLessThanOrEqualTo"), +#endif END_DATADESC() +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: Input handler for a new input value without performing a comparison. +//----------------------------------------------------------------------------- +void CLogicCompare::Spawn() +{ + // Empty initial values are equivalent to 0 + if (m_InValue.FieldType() == FIELD_STRING && m_InValue.String()[0] == '\0') + m_InValue.SetInt( 0 ); + + BaseClass::Spawn(); +} +#endif + //----------------------------------------------------------------------------- // Purpose: Input handler for a new input value without performing a comparison. //----------------------------------------------------------------------------- void CLogicCompare::InputSetValue( inputdata_t &inputdata ) { +#ifdef MAPBASE + m_InValue = Variant_ParseInput(inputdata); +#else m_flInValue = inputdata.value.Float(); +#endif } @@ -2095,8 +2904,13 @@ void CLogicCompare::InputSetValue( inputdata_t &inputdata ) //----------------------------------------------------------------------------- void CLogicCompare::InputSetValueCompare( inputdata_t &inputdata ) { +#ifdef MAPBASE + m_InValue = Variant_ParseInput(inputdata); + DoCompare( inputdata.pActivator, m_InValue ); +#else m_flInValue = inputdata.value.Float(); DoCompare( inputdata.pActivator, m_flInValue ); +#endif } //----------------------------------------------------------------------------- @@ -2104,15 +2918,34 @@ void CLogicCompare::InputSetValueCompare( inputdata_t &inputdata ) //----------------------------------------------------------------------------- void CLogicCompare::InputSetCompareValue( inputdata_t &inputdata ) { +#ifdef MAPBASE + m_CompareValue = Variant_ParseInput(inputdata); +#else m_flCompareValue = inputdata.value.Float(); +#endif } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: Input handler for a new input value and doing the comparison. +//----------------------------------------------------------------------------- +void CLogicCompare::InputSetCompareValueCompare( inputdata_t &inputdata ) +{ + m_CompareValue = Variant_ParseInput(inputdata); + DoCompare( inputdata.pActivator, m_InValue ); +} +#endif + //----------------------------------------------------------------------------- // Purpose: Input handler for forcing a recompare of the last input value. //----------------------------------------------------------------------------- void CLogicCompare::InputCompare( inputdata_t &inputdata ) { +#ifdef MAPBASE + DoCompare( inputdata.pActivator, m_InValue ); +#else DoCompare( inputdata.pActivator, m_flInValue ); +#endif } @@ -2121,6 +2954,33 @@ void CLogicCompare::InputCompare( inputdata_t &inputdata ) // output(s) based on the comparison result. // Input : flInValue - Value to compare against the comparison value. //----------------------------------------------------------------------------- +#ifdef MAPBASE +void CLogicCompare::DoCompare(CBaseEntity *pActivator, variant_t value) +{ + if (Variant_Equal(value, m_CompareValue, m_bStrLenAllowed)) + { + m_OnEqualTo.Set(value, pActivator, this); + + m_OnLessThanOrEqualTo.Set(value, pActivator, this); + m_OnGreaterThanOrEqualTo.Set(value, pActivator, this); + } + else + { + m_OnNotEqualTo.Set(value, pActivator, this); + + if (Variant_Greater(m_InValue, m_CompareValue, m_bStrLenAllowed)) + { + m_OnGreaterThan.Set(value, pActivator, this); + m_OnGreaterThanOrEqualTo.Set(value, pActivator, this); + } + else + { + m_OnLessThan.Set(value, pActivator, this); + m_OnLessThanOrEqualTo.Set(value, pActivator, this); + } + } +} +#else void CLogicCompare::DoCompare(CBaseEntity *pActivator, float flInValue) { if (flInValue == m_flCompareValue) @@ -2141,6 +3001,7 @@ void CLogicCompare::DoCompare(CBaseEntity *pActivator, float flInValue) } } } +#endif //----------------------------------------------------------------------------- // Purpose: Draw any debug text overlays @@ -2155,12 +3016,20 @@ int CLogicCompare::DrawDebugTextOverlays( void ) char tempstr[512]; // print duration +#ifdef MAPBASE + Q_snprintf(tempstr,sizeof(tempstr)," Initial Value: %s", m_InValue.GetDebug()); +#else Q_snprintf(tempstr,sizeof(tempstr)," Initial Value: %f", m_flInValue); +#endif EntityText(text_offset,tempstr,0); text_offset++; // print hold time +#ifdef MAPBASE + Q_snprintf(tempstr,sizeof(tempstr)," Compare Value: %s", m_CompareValue.GetDebug()); +#else Q_snprintf(tempstr,sizeof(tempstr)," Compare Value: %f", m_flCompareValue); +#endif EntityText(text_offset,tempstr,0); text_offset++; } @@ -2244,7 +3113,7 @@ void CLogicBranch::UpdateOnRemove() CBaseEntity *pEntity = m_Listeners.Element( i ).Get(); if ( pEntity ) { - g_EventQueue.AddEvent( this, "_OnLogicBranchRemoved", 0, this, this ); + g_EventQueue.AddEvent( pEntity, "_OnLogicBranchRemoved", 0, this, this ); } } @@ -2374,6 +3243,11 @@ int CLogicBranch::DrawDebugTextOverlays( void ) return text_offset; } +#ifdef MAPBASE +extern void MapbaseGameLog_Record( const char *szContext ); +extern ConVar mapbase_game_log_on_autosave; +#endif + //----------------------------------------------------------------------------- // Purpose: Autosaves when triggered //----------------------------------------------------------------------------- @@ -2410,6 +3284,13 @@ END_DATADESC() //----------------------------------------------------------------------------- void CLogicAutosave::InputSave( inputdata_t &inputdata ) { +#ifdef MAPBASE + if (mapbase_game_log_on_autosave.GetBool()) + { + MapbaseGameLog_Record( "autosave" ); + } +#endif + if ( m_bForceNewLevelUnit ) { engine->ClearSaveDir(); @@ -2423,6 +3304,13 @@ void CLogicAutosave::InputSave( inputdata_t &inputdata ) //----------------------------------------------------------------------------- void CLogicAutosave::InputSaveDangerous( inputdata_t &inputdata ) { +#ifdef MAPBASE + if (mapbase_game_log_on_autosave.GetBool()) + { + MapbaseGameLog_Record( "autosave_dangerous" ); + } +#endif + CBasePlayer *pPlayer = UTIL_PlayerByIndex( 1 ); if ( g_ServerGameDLL.m_fAutoSaveDangerousTime != 0.0f && g_ServerGameDLL.m_fAutoSaveDangerousTime >= gpGlobals->curtime ) @@ -2561,10 +3449,26 @@ class CLogicCollisionPair : public CLogicalEntity DECLARE_CLASS( CLogicCollisionPair, CLogicalEntity ); public: +#ifdef MAPBASE + // !activator, !caller, etc. support + void EnableCollisions( bool bEnable, CBaseEntity *pActivator = NULL, CBaseEntity *pCaller = NULL ) + { + IPhysicsObject *pPhysics0 = NULL; + IPhysicsObject *pPhysics1 = NULL; + + CBaseEntity *pEntity0 = gEntList.FindEntityByName( NULL, m_nameAttach1, this, pActivator, pCaller ); + if (pEntity0) + pPhysics0 = pEntity0->VPhysicsGetObject(); + + CBaseEntity *pEntity1 = gEntList.FindEntityByName( NULL, m_nameAttach2, this, pActivator, pCaller ); + if (pEntity1) + pPhysics1 = pEntity1->VPhysicsGetObject(); +#else void EnableCollisions( bool bEnable ) { IPhysicsObject *pPhysics0 = FindPhysicsObjectByNameOrWorld( m_nameAttach1, this ); IPhysicsObject *pPhysics1 = FindPhysicsObjectByNameOrWorld( m_nameAttach2, this ); +#endif // need two different objects to do anything if ( pPhysics0 && pPhysics1 && pPhysics0 != pPhysics1 ) @@ -2599,14 +3503,22 @@ public: { if ( m_succeeded && m_disabled ) return; +#ifdef MAPBASE + EnableCollisions( false, inputdata.pActivator, inputdata.pCaller ); +#else EnableCollisions( false ); +#endif } void InputEnableCollisions( inputdata_t &inputdata ) { if ( m_succeeded && !m_disabled ) return; +#ifdef MAPBASE + EnableCollisions( true, inputdata.pActivator, inputdata.pCaller ); +#else EnableCollisions( true ); +#endif } // If Activate() becomes PostSpawn() //void OnRestore() { Activate(); } @@ -2864,3 +3776,3404 @@ int CLogicBranchList::DrawDebugTextOverlays( void ) return text_offset; } + +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: Prints messages to the console. +//----------------------------------------------------------------------------- +class CLogicConsole : public CLogicalEntity +{ +public: + + DECLARE_CLASS( CLogicConsole, CLogicalEntity ); + + // Keys + int m_iDevLevel = 1; + Color m_MsgColor = Color(210, 250, 255, 255); + Color m_WarningColor = Color(255, 210, 210, 255); + bool m_bNewLineNotAuto = false; + + // TODO: Replace "append" with variable arguments? + inline void LCMsg(const char *msg, const char *append = NULL) { ConColorMsg(m_MsgColor, msg, append); } + inline void LCDevMsg(int lvl, const char *msg, const char *append = NULL) { developer.GetInt() >= lvl ? ConColorMsg(m_MsgColor, msg, append) : (void)0; } + inline void LCWarning(const char *msg, const char *append = NULL) { ConColorMsg(m_WarningColor, msg, append); } + inline void LCDevWarning(int lvl, const char *msg, const char *append = NULL) { developer.GetInt() >= lvl ? ConColorMsg(m_WarningColor, msg, append) : (void)0; } + + //inline void LCMsg(const char *msg, const char *append = NULL) { ColorSpewMessage(SPEW_MESSAGE, &m_MsgColor, msg, append); } + //inline void LCDevMsg(int lvl, const char *msg, const char *append = NULL) { developer.GetInt() >= lvl ? ColorSpewMessage(SPEW_MESSAGE, &m_MsgColor, msg, append) : (void)0; } + //inline void LCWarning(const char *msg, const char *append = NULL) { ColorSpewMessage(SPEW_MESSAGE, &m_WarningColor, msg, append); } + //inline void LCDevWarning(int lvl, const char *msg, const char *append = NULL) { developer.GetInt() >= lvl ? ColorSpewMessage(SPEW_MESSAGE, &m_WarningColor, msg, append) : (void)0; } + + // Inputs + void InputSendMsg( inputdata_t &inputdata ) { !m_bNewLineNotAuto ? LCMsg("%s\n", inputdata.value.String()) : LCMsg("%s", inputdata.value.String()); } + void InputSendWarning( inputdata_t &inputdata ) { !m_bNewLineNotAuto ? LCWarning("%s\n", inputdata.value.String()) : LCWarning("%s", inputdata.value.String()); } + void InputSendDevMsg( inputdata_t &inputdata ) { !m_bNewLineNotAuto ? LCDevMsg(m_iDevLevel, "%s\n", inputdata.value.String()) : LCDevMsg(m_iDevLevel, "%s", inputdata.value.String()); } + void InputSendDevWarning( inputdata_t &inputdata ) { !m_bNewLineNotAuto ? LCDevWarning(m_iDevLevel, "%s\n", inputdata.value.String()) : LCDevWarning(m_iDevLevel, "%s", inputdata.value.String()); } + + void InputNewLine( inputdata_t &inputdata ) { LCMsg("\n"); } + void InputDevNewLine( inputdata_t &inputdata ) { LCDevMsg(m_iDevLevel, "\n"); } + + // MAPBASE MP TODO: "ClearConsoleOnTarget" + // (and make this input broadcast to all players) + void InputClearConsole( inputdata_t &inputdata ) { UTIL_GetLocalPlayer() ? engine->ClientCommand(UTIL_GetLocalPlayer()->edict(), "clear") : (void)0; } + + DECLARE_DATADESC(); +}; + +LINK_ENTITY_TO_CLASS(logic_console, CLogicConsole); + + +BEGIN_DATADESC( CLogicConsole ) + + DEFINE_INPUT( m_iDevLevel, FIELD_INTEGER, "SetDevLvl" ), + DEFINE_INPUT( m_MsgColor, FIELD_COLOR32, "SetMsgColor" ), + DEFINE_INPUT( m_WarningColor, FIELD_COLOR32, "SetWarningColor" ), + DEFINE_INPUT( m_bNewLineNotAuto, FIELD_BOOLEAN, "SetNewLineNotAuto" ), + + DEFINE_INPUTFUNC( FIELD_STRING, "SendMsg", InputSendMsg ), + DEFINE_INPUTFUNC( FIELD_STRING, "SendWarning", InputSendWarning ), + DEFINE_INPUTFUNC( FIELD_STRING, "SendDevMsg", InputSendDevMsg ), + DEFINE_INPUTFUNC( FIELD_STRING, "SendDevWarning", InputSendDevWarning ), + + DEFINE_INPUTFUNC( FIELD_VOID, "NewLine", InputNewLine ), + DEFINE_INPUTFUNC( FIELD_VOID, "DevNewLine", InputDevNewLine ), + + DEFINE_INPUTFUNC( FIELD_VOID, "ClearConsole", InputClearConsole ), + +END_DATADESC() + +ConVar sv_allow_logic_convar( "sv_allow_logic_convar", "1", FCVAR_NOT_CONNECTED ); + +//----------------------------------------------------------------------------- +// Purpose: Gets console variables for the evil mapper. +//----------------------------------------------------------------------------- +class CLogicConvar : public CLogicalEntity +{ +public: + + DECLARE_CLASS( CLogicConvar, CLogicalEntity ); + + // Keys + string_t m_iszConVar; + string_t m_iszCompareValue; + + const char *GetConVarString( inputdata_t &inputdata ); + + // Inputs + void InputGetValue( inputdata_t &inputdata ); + void InputTest( inputdata_t &inputdata ); + + // Outputs + COutputEvent m_OnTrue; + COutputEvent m_OnFalse; + COutputString m_OutValue; + COutputEvent m_OnDenied; + + DECLARE_DATADESC(); +}; + +LINK_ENTITY_TO_CLASS(logic_convar, CLogicConvar); + + +BEGIN_DATADESC( CLogicConvar ) + + DEFINE_INPUT( m_iszConVar, FIELD_STRING, "SetConVar" ), + DEFINE_INPUT( m_iszCompareValue, FIELD_STRING, "SetTestValue" ), + + DEFINE_INPUTFUNC( FIELD_VOID, "GetValue", InputGetValue ), + DEFINE_INPUTFUNC( FIELD_VOID, "Test", InputTest ), + + DEFINE_OUTPUT(m_OnTrue, "OnTrue"), + DEFINE_OUTPUT(m_OnFalse, "OnFalse"), + DEFINE_OUTPUT(m_OutValue, "OutValue"), + DEFINE_OUTPUT(m_OnDenied, "OnDenied"), + +END_DATADESC() + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +const char *CLogicConvar::GetConVarString( inputdata_t &inputdata ) +{ + if (!sv_allow_logic_convar.GetBool()) + { + m_OnDenied.FireOutput(this, this); + + //return ConVarRef("", true); + return NULL; + } + + ConVarRef pCVar = ConVarRef(STRING(m_iszConVar), true); + if (!pCVar.IsValid()) + { + const char *pszCVar = STRING( m_iszConVar ); + CBasePlayer *pPlayer = ToBasePlayer( inputdata.pActivator ); + if (!pPlayer && AI_IsSinglePlayer()) + pPlayer = UTIL_PlayerByIndex( 1 ); + + if (pPlayer) + { + // Check if it's a common cheat command a player might be using + if (FStrEq( pszCVar, "god" )) + return (pPlayer->GetFlags() & FL_GODMODE) ? "1" : "0"; + if (FStrEq( pszCVar, "notarget" )) + return (pPlayer->GetFlags() & FL_NOTARGET) ? "1" : "0"; + if (FStrEq( pszCVar, "noclip" )) + return (pPlayer->IsEFlagSet(EFL_NOCLIP_ACTIVE)) ? "1" : "0"; + + // It might be a client convar + // This function returns a blank string if the convar doesn't exist, so we have to put this at the end + const char *pszClientValue = engine->GetClientConVarValue( pPlayer->GetClientIndex(), pszCVar ); + if (pszClientValue) + { + return pszClientValue; + } + } + + //Warning("Warning: %s has invalid convar \"%s\"\n", GetDebugName(), STRING(m_iszConVar)); + } + + return pCVar.GetString(); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CLogicConvar::InputGetValue( inputdata_t &inputdata ) +{ + const char *pCVarString = GetConVarString(inputdata); + if (pCVarString != NULL) + m_OutValue.Set( AllocPooledString( pCVarString ), inputdata.pActivator, this ); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CLogicConvar::InputTest( inputdata_t &inputdata ) +{ + const char *pCVarString = GetConVarString(inputdata); + if (pCVarString) + { + if (Matcher_Match( STRING( m_iszCompareValue ), pCVarString )) + { + m_OnTrue.FireOutput(inputdata.pActivator, this); + } + else + { + m_OnFalse.FireOutput(inputdata.pActivator, this); + } + } +} + +#define MAX_LOGIC_FORMAT_PARAMETERS 8 +//----------------------------------------------------------------------------- +// Purpose: Takes a string and a bunch of parameters and spits out a formatted string. +//----------------------------------------------------------------------------- +class CLogicFormat : public CLogicalEntity +{ +public: + + DECLARE_CLASS( CLogicFormat, CLogicalEntity ); + + // Keys + string_t m_iszInput; + string_t m_iszParameter[MAX_LOGIC_FORMAT_PARAMETERS]; + string_t m_iszBackupParameter; + + void FormatString(const char *szStringToFormat, char *szOutput, int outputlen); + + // Inputs + void InputGetFormattedString( inputdata_t &inputdata ); + + // Outputs + COutputString m_OutFormattedString; + + DECLARE_DATADESC(); +}; + +LINK_ENTITY_TO_CLASS(logic_format, CLogicFormat); + + +BEGIN_DATADESC( CLogicFormat ) + + DEFINE_INPUT( m_iszInput, FIELD_STRING, "SetInputValue" ), + DEFINE_INPUT( m_iszParameter[0], FIELD_STRING, "SetParameter0" ), + DEFINE_INPUT( m_iszParameter[1], FIELD_STRING, "SetParameter1" ), + DEFINE_INPUT( m_iszParameter[2], FIELD_STRING, "SetParameter2" ), + DEFINE_INPUT( m_iszParameter[3], FIELD_STRING, "SetParameter3" ), + DEFINE_INPUT( m_iszParameter[4], FIELD_STRING, "SetParameter4" ), + DEFINE_INPUT( m_iszParameter[5], FIELD_STRING, "SetParameter5" ), + DEFINE_INPUT( m_iszParameter[6], FIELD_STRING, "SetParameter6" ), + DEFINE_INPUT( m_iszParameter[7], FIELD_STRING, "SetParameter7" ), + DEFINE_INPUT( m_iszBackupParameter, FIELD_STRING, "SetBackupParameter" ), + + DEFINE_INPUTFUNC( FIELD_VOID, "GetFormattedValue", InputGetFormattedString ), + + DEFINE_OUTPUT(m_OutFormattedString, "OutFormattedValue"), + +END_DATADESC() + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CLogicFormat::InputGetFormattedString( inputdata_t &inputdata ) +{ + char szFormatted[256]; + if (m_iszInput != NULL_STRING) + { + FormatString(STRING(m_iszInput), szFormatted, sizeof(szFormatted)); + m_OutFormattedString.Set(AllocPooledString(szFormatted), inputdata.pActivator, this); + } +} + +//----------------------------------------------------------------------------- +// I'm bad at coding. +//----------------------------------------------------------------------------- +void CLogicFormat::FormatString(const char *szStringToFormat, char *szOutput, int outputlen) +{ + const char *szParameters[MAX_LOGIC_FORMAT_PARAMETERS]; + for (int i = 0; i < MAX_LOGIC_FORMAT_PARAMETERS; i++) + { + if (m_iszParameter[i] != NULL_STRING) + { + szParameters[i] = STRING(m_iszParameter[i]); + } + else if (m_iszBackupParameter != NULL_STRING) + { + szParameters[i] = STRING(m_iszBackupParameter); + } + else + { + szParameters[i] = ""; + } + } + + char szFormatted[256] = { 0 }; // Needed so garbage isn't spewed at the beginning + //Q_snprintf(szFormatted, sizeof(szFormatted), szInput, szParameters); + + char szInput[256]; + Q_strncpy(szInput, szStringToFormat, sizeof(szInput)); + + bool inparam = (szInput[0] == '{'); + int curparam = 0; + char *szToken = strtok(szInput, "{"); + while (szToken != NULL) + { + if (inparam) + { + curparam = atoi(szToken); + if (curparam < MAX_LOGIC_FORMAT_PARAMETERS /*&& szParameters[curparam] != NULL*/) //if (curparam < MAX_FORMAT_PARAMETERS) + { + Q_strncat(szFormatted, szParameters[curparam], sizeof(szFormatted)); + } + else + { + Warning("Warning: Parameter %i out of bounds in \"%s\"\n", curparam, szStringToFormat); + + // This might not be the best way to do this, but + // reaching it is supposed to be the result of a mistake anyway. + m_iszBackupParameter != NULL_STRING ? + Q_strncat( szFormatted, STRING(m_iszBackupParameter), sizeof( szFormatted ) ) : + Q_strncat( szFormatted, "", sizeof( szFormatted ) ); + } + + inparam = false; + szToken = strtok(NULL, "{"); + } + else + { + Q_strncat( szFormatted, szToken, sizeof( szFormatted ) ); + + inparam = true; + szToken = strtok(NULL, "}"); + } + } + + Q_strncpy(szOutput, szFormatted, outputlen); +} + + +//----------------------------------------------------------------------------- +// Purpose: Accesses a keyvalue from a specific entity +// Mostly ported from Half-Laugh. +//----------------------------------------------------------------------------- +class CLogicKeyfieldAccessor : public CLogicalEntity +{ + DECLARE_CLASS(CLogicKeyfieldAccessor, CLogicalEntity); + +protected: + CBaseEntity *GetTarget(CBaseEntity *pCaller, CBaseEntity *pActivator); + + virtual bool TestKey(CBaseEntity *pTarget, const char *szKeyName); + virtual bool SetKeyValue(CBaseEntity *pTarget, const char *szKeyName, const char *szValue); + virtual bool SetKeyValueBits(CBaseEntity *pTarget, const char *szKeyName, int iValue, bool bRemove = false); + + // Inputs + void InputTest(inputdata_t &inputdata); + void InputTestKey(inputdata_t &inputdata); + void InputTestTarget(inputdata_t &inputdata); + + void InputSetKey(inputdata_t &inputdata); + + void InputSetValue(inputdata_t &inputdata); + void InputAddBits(inputdata_t &inputdata); + void InputRemoveBits(inputdata_t &inputdata); + + //bool ReadUnregisteredKeyfields(CBaseEntity *pTarget, const char *szKeyName, variant_t *variant); + + COutputVariant m_OutValue; + COutputEvent m_OnFailed; + + string_t m_iszKey; + + DECLARE_DATADESC(); +}; + +LINK_ENTITY_TO_CLASS(logic_keyfield, CLogicKeyfieldAccessor); + + +BEGIN_DATADESC(CLogicKeyfieldAccessor) + +DEFINE_KEYFIELD( m_iszKey, FIELD_STRING, "keyname" ), + +// Inputs +DEFINE_INPUTFUNC(FIELD_VOID, "Test", InputTest), +DEFINE_INPUTFUNC(FIELD_STRING, "TestKey", InputTestKey), +DEFINE_INPUTFUNC(FIELD_STRING, "TestTarget", InputTestTarget), +DEFINE_INPUTFUNC(FIELD_STRING, "SetKey", InputSetKey), + +DEFINE_INPUTFUNC(FIELD_STRING, "SetValue", InputSetValue), +DEFINE_INPUTFUNC(FIELD_INTEGER, "AddBits", InputAddBits), +DEFINE_INPUTFUNC(FIELD_INTEGER, "RemoveBits", InputRemoveBits), + +DEFINE_OUTPUT( m_OutValue, "OutValue" ), +DEFINE_OUTPUT( m_OnFailed, "OnFailed" ), + +END_DATADESC() + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +inline CBaseEntity *CLogicKeyfieldAccessor::GetTarget(CBaseEntity *pCaller, CBaseEntity *pActivator) +{ + return gEntList.FindEntityByName(NULL, m_target, this, pActivator, pCaller); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CLogicKeyfieldAccessor::TestKey(CBaseEntity *pTarget, const char *szKeyName) +{ + variant_t variant; + if (pTarget->ReadKeyField(szKeyName, &variant) || ReadUnregisteredKeyfields(pTarget, szKeyName, &variant)) + { + m_OutValue.Set(variant, pTarget, this); + return true; + } + else + { + m_OnFailed.FireOutput(pTarget, this); + return false; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CLogicKeyfieldAccessor::SetKeyValue(CBaseEntity *pTarget, const char *szKeyName, const char *szValue) +{ + if (pTarget->KeyValue(szKeyName, szValue)) + { + // We'll still fire OutValue + variant_t variant; + if (!pTarget->ReadKeyField(szKeyName, &variant)) + ReadUnregisteredKeyfields(pTarget, szKeyName, &variant); + + m_OutValue.Set(variant, pTarget, this); + return true; + } + else + { + m_OnFailed.FireOutput(pTarget, this); + return false; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CLogicKeyfieldAccessor::SetKeyValueBits(CBaseEntity *pTarget, const char *szKeyName, int iValue, bool bRemove) +{ + variant_t variant; + if ((pTarget->ReadKeyField(szKeyName, &variant) || ReadUnregisteredKeyfields(pTarget, szKeyName, &variant)) && variant.FieldType() == FIELD_INTEGER) + { + if (bRemove) + variant.SetInt(variant.Int() & ~iValue); + else + variant.SetInt(variant.Int() | iValue); + + pTarget->KeyValue(szKeyName, UTIL_VarArgs("%i", variant.Int())); + + m_OutValue.Set(variant, pTarget, this); + return true; + } + else + { + m_OnFailed.FireOutput(pTarget, this); + return false; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CLogicKeyfieldAccessor::InputTest(inputdata_t &inputdata) +{ + CBaseEntity *pTarget = GetTarget(inputdata.pCaller, inputdata.pActivator); + if (pTarget && m_iszKey != NULL_STRING) + { + TestKey(pTarget, STRING(m_iszKey)); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CLogicKeyfieldAccessor::InputTestKey(inputdata_t &inputdata) +{ + const char *input = inputdata.value.String(); + CBaseEntity *pTarget = GetTarget(inputdata.pCaller, inputdata.pActivator); + if (input && pTarget) + { + TestKey(pTarget, input); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CLogicKeyfieldAccessor::InputTestTarget(inputdata_t &inputdata) +{ + m_target = inputdata.value.StringID(); + CBaseEntity *pTarget = gEntList.FindEntityByName(NULL, inputdata.value.StringID(), this, inputdata.pCaller, inputdata.pActivator); + if (pTarget && m_iszKey != NULL_STRING) + { + TestKey(pTarget, STRING(m_iszKey)); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CLogicKeyfieldAccessor::InputSetKey(inputdata_t &inputdata) +{ + m_iszKey = inputdata.value.StringID(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CLogicKeyfieldAccessor::InputSetValue(inputdata_t &inputdata) +{ + const char *input = inputdata.value.String(); + CBaseEntity *pTarget = GetTarget(inputdata.pCaller, inputdata.pActivator); + if (input && pTarget) + { + SetKeyValue(pTarget, STRING(m_iszKey), input); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CLogicKeyfieldAccessor::InputAddBits(inputdata_t &inputdata) +{ + int input = inputdata.value.Int(); + CBaseEntity *pTarget = GetTarget(inputdata.pCaller, inputdata.pActivator); + if (input && pTarget) + { + SetKeyValueBits(pTarget, STRING(m_iszKey), input, false); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CLogicKeyfieldAccessor::InputRemoveBits(inputdata_t &inputdata) +{ + int input = inputdata.value.Int(); + CBaseEntity *pTarget = GetTarget(inputdata.pCaller, inputdata.pActivator); + if (input && pTarget) + { + SetKeyValueBits(pTarget, STRING(m_iszKey), input, true); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Clamps the input value between two values +//----------------------------------------------------------------------------- +class CMathClamp : public CLogicalEntity +{ +public: + + DECLARE_CLASS( CMathClamp, CLogicalEntity ); + + // Keys + variant_t m_Max; + variant_t m_Min; + + // Inputs + void InputClampValue( inputdata_t &inputdata ); + + float ClampValue(float input, float min, float max, int *bounds); + void ClampValue(variant_t var, inputdata_t *inputdata); + + // Outputs + COutputVariant m_OutValue; + COutputVariant m_OnBeforeMin; + COutputVariant m_OnBeyondMax; + + DECLARE_DATADESC(); +}; + +LINK_ENTITY_TO_CLASS(math_clamp, CMathClamp); + + +BEGIN_DATADESC( CMathClamp ) + + DEFINE_INPUT( m_Max, FIELD_INPUT, "SetMax" ), + DEFINE_INPUT( m_Min, FIELD_INPUT, "SetMin" ), + + DEFINE_INPUTFUNC( FIELD_INPUT, "ClampValue", InputClampValue ), + + DEFINE_OUTPUT(m_OutValue, "OutValue"), + DEFINE_OUTPUT(m_OnBeforeMin, "OnBeforeMin"), + DEFINE_OUTPUT(m_OnBeyondMax, "OnBeyondMax"), + +END_DATADESC() + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CMathClamp::InputClampValue( inputdata_t &inputdata ) +{ + ClampValue(inputdata.value, &inputdata); +} + +//----------------------------------------------------------------------------- +// "bounds" returns 1 if the number was less than min, 2 if more than max. Must not be NULL +//----------------------------------------------------------------------------- +inline float CMathClamp::ClampValue(float input, float min, float max, int *bounds) +{ + if ( max < min ) + { + Warning("WARNING: Max value (%f) less than min value (%f) in %s!\n", max, min, GetDebugName()); + return max; + } + else if( input < min ) + { + *bounds = 1; + return min; + } + else if( input > max ) + { + *bounds = 2; + return max; + } + else + return input; +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CMathClamp::ClampValue(variant_t var, inputdata_t *inputdata) +{ + // Don't convert up here in case of invalid type + + int nBounds = 0; + + switch (var.FieldType()) + { + case FIELD_FLOAT: + { + m_Max.Convert(var.FieldType()); + m_Min.Convert(var.FieldType()); + + var.SetFloat(ClampValue(var.Float(), m_Max.Float(), m_Min.Float(), &nBounds)); + } break; + case FIELD_INTEGER: + { + m_Max.Convert(var.FieldType()); + m_Min.Convert(var.FieldType()); + + var.SetInt(ClampValue(var.Int(), m_Max.Int(), m_Min.Int(), &nBounds)); + } break; + case FIELD_VECTOR: + { + m_Max.Convert(var.FieldType()); + m_Min.Convert(var.FieldType()); + + Vector min; + Vector max; + m_Min.Vector3D(min); + m_Max.Vector3D(max); + + Vector vec; + var.Vector3D(vec); + + vec.x = ClampValue(vec.x, min.x, max.x, &nBounds); + vec.y = ClampValue(vec.y, min.y, max.y, &nBounds); + vec.z = ClampValue(vec.z, min.z, max.z, &nBounds); + + var.SetVector3D(vec); + } break; + default: + { + Warning("Error: Unsupported value %s in math_clamp %s\n", var.GetDebug(), STRING(GetEntityName())); + return; + } + } + + if (inputdata) + { + m_OutValue.Set(var, inputdata->pActivator, this); + if (nBounds == 1) + m_OnBeforeMin.Set(var, inputdata->pActivator, this); + else if (nBounds == 2) + m_OnBeyondMax.Set(var, inputdata->pActivator, this); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Bits calculations. +//----------------------------------------------------------------------------- +class CMathBits : public CLogicalEntity +{ + DECLARE_CLASS( CMathBits, CLogicalEntity ); +private: + + bool m_bDisabled; + + bool KeyValue(const char *szKeyName, const char *szValue); + + void UpdateOutValue(CBaseEntity *pActivator, int iNewValue); + + int DrawDebugTextOverlays(void); + + // Inputs + void InputAdd( inputdata_t &inputdata ); + void InputSubtract( inputdata_t &inputdata ); + void InputShiftLeft( inputdata_t &inputdata ); + void InputShiftRight( inputdata_t &inputdata ); + void InputApplyAnd( inputdata_t &inputdata ); + void InputApplyOr( inputdata_t &inputdata ); + void InputSetValue( inputdata_t &inputdata ); + void InputSetValueNoFire( inputdata_t &inputdata ); + void InputGetValue( inputdata_t &inputdata ); + void InputEnable( inputdata_t &inputdata ); + void InputDisable( inputdata_t &inputdata ); + void InputContainsBits( inputdata_t &inputdata ); + void InputContainsAllBits( inputdata_t &inputdata ); + + // Outputs + COutputInt m_OutValue; + COutputInt m_OnGetValue; + COutputEvent m_OnTrue; + COutputEvent m_OnFalse; + + DECLARE_DATADESC(); +}; + +LINK_ENTITY_TO_CLASS(math_bits, CMathBits); + + +BEGIN_DATADESC( CMathBits ) + + // Keys + DEFINE_KEYFIELD(m_bDisabled, FIELD_BOOLEAN, "StartDisabled" ), + + // Inputs + DEFINE_INPUTFUNC(FIELD_INTEGER, "Add", InputAdd), + DEFINE_INPUTFUNC(FIELD_INTEGER, "Subtract", InputSubtract), + DEFINE_INPUTFUNC(FIELD_INTEGER, "ShiftLeft", InputShiftLeft), + DEFINE_INPUTFUNC(FIELD_INTEGER, "ShiftRight", InputShiftRight), + DEFINE_INPUTFUNC(FIELD_INTEGER, "ApplyAnd", InputApplyAnd), + DEFINE_INPUTFUNC(FIELD_INTEGER, "ApplyOr", InputApplyOr), + DEFINE_INPUTFUNC(FIELD_INTEGER, "SetValue", InputSetValue), + DEFINE_INPUTFUNC(FIELD_INTEGER, "SetValueNoFire", InputSetValueNoFire), + DEFINE_INPUTFUNC(FIELD_VOID, "GetValue", InputGetValue), + DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ), + DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ), + DEFINE_INPUTFUNC(FIELD_INTEGER, "ContainsBits", InputContainsBits), + DEFINE_INPUTFUNC(FIELD_INTEGER, "ContainsAllBits", InputContainsAllBits), + + // Outputs + DEFINE_OUTPUT(m_OutValue, "OutValue"), + DEFINE_OUTPUT(m_OnGetValue, "OnGetValue"), + DEFINE_OUTPUT(m_OnTrue, "OnTrue"), + DEFINE_OUTPUT(m_OnFalse, "OnFalse"), + +END_DATADESC() + +//----------------------------------------------------------------------------- +// Purpose: Handles key values from the BSP before spawn is called. +//----------------------------------------------------------------------------- +bool CMathBits::KeyValue(const char *szKeyName, const char *szValue) +{ + // + // Set the initial value of the counter. + // + if (!stricmp(szKeyName, "startvalue")) + { + m_OutValue.Init(atoi(szValue)); + return(true); + } + + return(BaseClass::KeyValue(szKeyName, szValue)); +} + +//----------------------------------------------------------------------------- +// Purpose: Input handler for adding to the accumulator value. +// Input : Bit value to add. +//----------------------------------------------------------------------------- +void CMathBits::InputAdd( inputdata_t &inputdata ) +{ + if( m_bDisabled ) + { + DevMsg("Math Bits %s ignoring ADD because it is disabled\n", GetDebugName() ); + return; + } + + int iNewValue = m_OutValue.Get() | inputdata.value.Int(); + UpdateOutValue( inputdata.pActivator, iNewValue ); +} + +//----------------------------------------------------------------------------- +// Purpose: Input handler for subtracting from the current value. +// Input : Bit value to subtract. +//----------------------------------------------------------------------------- +void CMathBits::InputSubtract( inputdata_t &inputdata ) +{ + if( m_bDisabled ) + { + DevMsg("Math Bits %s ignoring SUBTRACT because it is disabled\n", GetDebugName() ); + return; + } + + int iNewValue = m_OutValue.Get() & ~inputdata.value.Int(); + UpdateOutValue( inputdata.pActivator, iNewValue ); +} + +//----------------------------------------------------------------------------- +// Purpose: Input handler for shifting from the current value. +// Input : Bit value to shift by. +//----------------------------------------------------------------------------- +void CMathBits::InputShiftLeft( inputdata_t &inputdata ) +{ + if( m_bDisabled ) + { + DevMsg("Math Bits %s ignoring SHIFTLEFT because it is disabled\n", GetDebugName() ); + return; + } + + int iNewValue = m_OutValue.Get() << inputdata.value.Int(); + UpdateOutValue( inputdata.pActivator, iNewValue ); +} + +//----------------------------------------------------------------------------- +// Purpose: Input handler for shifting from the current value. +// Input : Bit value to shift by. +//----------------------------------------------------------------------------- +void CMathBits::InputShiftRight( inputdata_t &inputdata ) +{ + if( m_bDisabled ) + { + DevMsg("Math Bits %s ignoring SHIFTRIGHT because it is disabled\n", GetDebugName() ); + return; + } + + int iNewValue = m_OutValue.Get() >> inputdata.value.Int(); + UpdateOutValue( inputdata.pActivator, iNewValue ); +} + +//----------------------------------------------------------------------------- +// Purpose: Input handler for applying & to the current value. +// Input : Bit value to shift by. +//----------------------------------------------------------------------------- +void CMathBits::InputApplyAnd( inputdata_t &inputdata ) +{ + if( m_bDisabled ) + { + DevMsg("Math Bits %s ignoring APPLYAND because it is disabled\n", GetDebugName() ); + return; + } + + int iNewValue = m_OutValue.Get() & inputdata.value.Int(); + UpdateOutValue( inputdata.pActivator, iNewValue ); +} + +//----------------------------------------------------------------------------- +// Purpose: Input handler for applying | to the current value. +// Input : Bit value to shift by. +//----------------------------------------------------------------------------- +void CMathBits::InputApplyOr( inputdata_t &inputdata ) +{ + if( m_bDisabled ) + { + DevMsg("Math Bits %s ignoring APPLYOR because it is disabled\n", GetDebugName() ); + return; + } + + int iNewValue = m_OutValue.Get() | inputdata.value.Int(); + UpdateOutValue( inputdata.pActivator, iNewValue ); +} + +//----------------------------------------------------------------------------- +// Purpose: Input handler for updating the value. +// Input : Bit value to set. +//----------------------------------------------------------------------------- +void CMathBits::InputSetValue( inputdata_t &inputdata ) +{ + if( m_bDisabled ) + { + DevMsg("Math Bits %s ignoring SETVALUE because it is disabled\n", GetDebugName() ); + return; + } + + UpdateOutValue( inputdata.pActivator, inputdata.value.Int() ); +} + +//----------------------------------------------------------------------------- +// Purpose: Input handler for updating the value. +// Input : Bit value to set. +//----------------------------------------------------------------------------- +void CMathBits::InputSetValueNoFire( inputdata_t &inputdata ) +{ + if( m_bDisabled ) + { + DevMsg("Math Bits %s ignoring SETVALUENOFIRE because it is disabled\n", GetDebugName() ); + return; + } + + m_OutValue.Init( inputdata.value.Int() ); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CMathBits::InputGetValue( inputdata_t &inputdata ) +{ + int iOutValue = m_OutValue.Get(); + m_OnGetValue.Set( iOutValue, inputdata.pActivator, inputdata.pCaller ); +} + +//----------------------------------------------------------------------------- +// Purpose: Input handler for checking whether a bit is stored. +// Input : Bit value to check. +//----------------------------------------------------------------------------- +void CMathBits::InputContainsBits( inputdata_t &inputdata ) +{ + if( m_bDisabled ) + { + DevMsg("Math Bits %s ignoring CONTAINS BITS because it is disabled\n", GetDebugName() ); + return; + } + + if (m_OutValue.Get() & inputdata.value.Int()) + m_OnTrue.FireOutput(inputdata.pActivator, this); + else + m_OnFalse.FireOutput(inputdata.pActivator, this); +} + +//----------------------------------------------------------------------------- +// Purpose: Input handler for checking whether all of the specified bits are stored. +// Input : Bit value to check. +//----------------------------------------------------------------------------- +void CMathBits::InputContainsAllBits( inputdata_t &inputdata ) +{ + if( m_bDisabled ) + { + DevMsg("Math Bits %s ignoring CONTAINS ALL BITS because it is disabled\n", GetDebugName() ); + return; + } + + bool bResult = false; + int iInput = inputdata.value.Int(); + int iValue = m_OutValue.Get(); + + for (int i = 1, n = 0; n < 32; (i <<= 1), n++) + { + DevMsg("%i\n", i); + if (iInput & i) + { + if (!(iValue & i)) + { + DevMsg("%i does not go into %i\n", i, iValue); + bResult = false; + break; + } + else if (!bResult) + { + bResult = true; + } + } + } + + if (bResult) + m_OnTrue.FireOutput(inputdata.pActivator, this); + else + m_OnFalse.FireOutput(inputdata.pActivator, this); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CMathBits::InputEnable( inputdata_t &inputdata ) +{ + m_bDisabled = false; +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CMathBits::InputDisable( inputdata_t &inputdata ) +{ + m_bDisabled = true; +} + +//----------------------------------------------------------------------------- +// Purpose: Sets the value to the new value, firing the output value. +// Input : iNewValue - Value to set. +//----------------------------------------------------------------------------- +void CMathBits::UpdateOutValue(CBaseEntity *pActivator, int iNewValue) +{ + m_OutValue.Set(iNewValue, pActivator, this); +} + +//----------------------------------------------------------------------------- +// Purpose: Draw any debug text overlays +// Input : +// Output : Current text offset from the top +//----------------------------------------------------------------------------- +int CMathBits::DrawDebugTextOverlays( void ) +{ + int text_offset = BaseClass::DrawDebugTextOverlays(); + + if (m_debugOverlays & OVERLAY_TEXT_BIT) + { + char tempstr[512]; + + Q_snprintf(tempstr,sizeof(tempstr),"current value: %i", m_OutValue.Get()); + EntityText(text_offset,tempstr,0); + text_offset++; + + if( m_bDisabled ) + { + Q_snprintf(tempstr,sizeof(tempstr),"*DISABLED*"); + } + else + { + Q_snprintf(tempstr,sizeof(tempstr),"Enabled."); + } + EntityText(text_offset,tempstr,0); + text_offset++; + + } + return text_offset; +} + +// These spawnflags control math_vector dimensions. +#define SF_MATH_VECTOR_DISABLE_X ( 1 << 0 ) +#define SF_MATH_VECTOR_DISABLE_Y ( 1 << 1 ) +#define SF_MATH_VECTOR_DISABLE_Z ( 1 << 2 ) + +//----------------------------------------------------------------------------- +// Purpose: Vector calculations. +//----------------------------------------------------------------------------- +class CMathVector : public CLogicalEntity +{ + DECLARE_CLASS( CMathVector, CLogicalEntity ); +private: + + bool m_bDisabled; + + bool KeyValue(const char *szKeyName, const char *szValue); + bool KeyValue( const char *szKeyName, const Vector &vecValue ); + + void UpdateOutValue(CBaseEntity *pActivator, Vector vecNewValue); + + int DrawDebugTextOverlays(void); + + // Inputs + void InputAdd( inputdata_t &inputdata ); + void InputSubtract( inputdata_t &inputdata ); + void InputDivide( inputdata_t &inputdata ); + void InputMultiply( inputdata_t &inputdata ); + void InputSetValue( inputdata_t &inputdata ); + void InputSetValueNoFire( inputdata_t &inputdata ); + void InputGetValue( inputdata_t &inputdata ); + void InputEnable( inputdata_t &inputdata ); + void InputDisable( inputdata_t &inputdata ); + + void PointAt( Vector &origin, Vector &target, Vector &out ); + void InputPointAtLocation( inputdata_t &inputdata ); + void InputPointAtEntity( inputdata_t &inputdata ); + + void InputNormalize( inputdata_t &inputdata ); + void InputNormalizeAngles( inputdata_t &inputdata ); + void InputVectorAngles( inputdata_t &inputdata ); + void InputAngleVectorForward( inputdata_t &inputdata ); + void InputAngleVectorRight( inputdata_t &inputdata ); + void InputAngleVectorUp( inputdata_t &inputdata ); + + void SetCoordinate(float value, char coord, CBaseEntity *pActivator); + void GetCoordinate(char coord, CBaseEntity *pActivator); + void AddCoordinate(float value, char coord, CBaseEntity *pActivator); + void SubtractCoordinate(float value, char coord, CBaseEntity *pActivator); + + void InputSetX( inputdata_t &inputdata ) { SetCoordinate(inputdata.value.Float(), 'X', inputdata.pActivator); } + void InputSetY( inputdata_t &inputdata ) { SetCoordinate(inputdata.value.Float(), 'Y', inputdata.pActivator); } + void InputSetZ( inputdata_t &inputdata ) { SetCoordinate(inputdata.value.Float(), 'Z', inputdata.pActivator); } + void InputGetX( inputdata_t &inputdata ) { GetCoordinate('X', inputdata.pActivator); } + void InputGetY( inputdata_t &inputdata ) { GetCoordinate('Y', inputdata.pActivator); } + void InputGetZ( inputdata_t &inputdata ) { GetCoordinate('Z', inputdata.pActivator); } + void InputAddX( inputdata_t &inputdata ) { AddCoordinate(inputdata.value.Float(), 'X', inputdata.pActivator); } + void InputAddY( inputdata_t &inputdata ) { AddCoordinate(inputdata.value.Float(), 'Y', inputdata.pActivator); } + void InputAddZ( inputdata_t &inputdata ) { AddCoordinate(inputdata.value.Float(), 'Z', inputdata.pActivator); } + void InputSubtractX( inputdata_t &inputdata ) { SubtractCoordinate(inputdata.value.Float(), 'X', inputdata.pActivator); } + void InputSubtractY( inputdata_t &inputdata ) { SubtractCoordinate(inputdata.value.Float(), 'Y', inputdata.pActivator); } + void InputSubtractZ( inputdata_t &inputdata ) { SubtractCoordinate(inputdata.value.Float(), 'Z', inputdata.pActivator); } + + // Outputs + COutputVector m_OutValue; + COutputFloat m_OutX; + COutputFloat m_OutY; + COutputFloat m_OutZ; + + COutputVector m_OnGetValue; + COutputFloat m_OnGetX; + COutputFloat m_OnGetY; + COutputFloat m_OnGetZ; + + DECLARE_DATADESC(); +}; + +LINK_ENTITY_TO_CLASS(math_vector, CMathVector); + + +BEGIN_DATADESC( CMathVector ) + + // Keys + DEFINE_KEYFIELD(m_bDisabled, FIELD_BOOLEAN, "StartDisabled" ), + + // Inputs + DEFINE_INPUTFUNC(FIELD_VECTOR, "Add", InputAdd), + DEFINE_INPUTFUNC(FIELD_VECTOR, "Subtract", InputSubtract), + DEFINE_INPUTFUNC(FIELD_VECTOR, "Divide", InputDivide), + DEFINE_INPUTFUNC(FIELD_VECTOR, "Multiply", InputMultiply), + DEFINE_INPUTFUNC(FIELD_VECTOR, "SetValue", InputSetValue), + DEFINE_INPUTFUNC(FIELD_VECTOR, "SetValueNoFire", InputSetValueNoFire), + DEFINE_INPUTFUNC(FIELD_VOID, "GetValue", InputGetValue), + DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ), + DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ), + + DEFINE_INPUTFUNC( FIELD_VECTOR, "PointAtLocation", InputPointAtLocation ), + DEFINE_INPUTFUNC( FIELD_EHANDLE, "PointAtEntity", InputPointAtEntity ), + + DEFINE_INPUTFUNC(FIELD_VOID, "Normalize", InputNormalize), + DEFINE_INPUTFUNC(FIELD_VOID, "NormalizeAngles", InputNormalizeAngles), + DEFINE_INPUTFUNC(FIELD_VOID, "VectorAngles", InputVectorAngles), + DEFINE_INPUTFUNC(FIELD_VOID, "AngleVectorForward", InputAngleVectorForward), + DEFINE_INPUTFUNC(FIELD_VOID, "AngleVectorRight", InputAngleVectorRight), + DEFINE_INPUTFUNC(FIELD_VOID, "AngleVectorUp", InputAngleVectorUp), + + DEFINE_INPUTFUNC(FIELD_FLOAT, "SetX", InputSetX), + DEFINE_INPUTFUNC(FIELD_FLOAT, "SetY", InputSetY), + DEFINE_INPUTFUNC(FIELD_FLOAT, "SetZ", InputSetZ), + DEFINE_INPUTFUNC(FIELD_VOID, "GetX", InputGetX), + DEFINE_INPUTFUNC(FIELD_VOID, "GetY", InputGetY), + DEFINE_INPUTFUNC(FIELD_VOID, "GetZ", InputGetZ), + DEFINE_INPUTFUNC(FIELD_FLOAT, "AddX", InputAddX), + DEFINE_INPUTFUNC(FIELD_FLOAT, "AddY", InputAddY), + DEFINE_INPUTFUNC(FIELD_FLOAT, "AddZ", InputAddZ), + DEFINE_INPUTFUNC(FIELD_FLOAT, "SubtractX", InputSubtractX), + DEFINE_INPUTFUNC(FIELD_FLOAT, "SubtractY", InputSubtractY), + DEFINE_INPUTFUNC(FIELD_FLOAT, "SubtractZ", InputSubtractZ), + + // Outputs + DEFINE_OUTPUT(m_OutValue, "OutValue"), + DEFINE_OUTPUT(m_OutX, "OutX"), + DEFINE_OUTPUT(m_OutY, "OutY"), + DEFINE_OUTPUT(m_OutZ, "OutZ"), + + DEFINE_OUTPUT(m_OnGetValue, "OnGetValue"), + DEFINE_OUTPUT(m_OnGetX, "OnGetX"), + DEFINE_OUTPUT(m_OnGetY, "OnGetY"), + DEFINE_OUTPUT(m_OnGetZ, "OnGetZ"), + +END_DATADESC() + +//----------------------------------------------------------------------------- +// Purpose: Handles key values from the BSP before spawn is called. +//----------------------------------------------------------------------------- +bool CMathVector::KeyValue(const char *szKeyName, const char *szValue) +{ + // + // Set the initial value of the counter. + // + if (!stricmp(szKeyName, "startvalue")) + { + Vector vec; + UTIL_StringToVector( vec.Base(), szValue ); + m_OutValue.Init(vec); + return(true); + } + + return(BaseClass::KeyValue(szKeyName, szValue)); +} + +//----------------------------------------------------------------------------- +// Purpose: Handles key values from the BSP before spawn is called. +//----------------------------------------------------------------------------- +bool CMathVector::KeyValue( const char *szKeyName, const Vector &vecValue ) +{ + // + // Set the initial value of the counter. + // + if (!stricmp(szKeyName, "startvalue")) + { + m_OutValue.Init(vecValue); + return true; + } + + // So, CLogicalEntity descends from CBaseEntity... + // Yup. + // ...and CBaseEntity has a version of KeyValue that takes vectors. + // Yup. + // Since it's virtual, I could easily override it just like I could with a KeyValue that takes strings, right? + // Sounds right to me. + // So let me override it. + // *No suitable function exists* + return CBaseEntity::KeyValue(szKeyName, vecValue); +} + +//----------------------------------------------------------------------------- +// Purpose: Input handler for adding to the accumulator value. +// Input : Bit value to add. +//----------------------------------------------------------------------------- +void CMathVector::InputAdd( inputdata_t &inputdata ) +{ + if( m_bDisabled ) + { + DevMsg("Math Vector %s ignoring ADD because it is disabled\n", GetDebugName() ); + return; + } + + Vector vec; + inputdata.value.Vector3D(vec); + Vector cur; + m_OutValue.Get(cur); + UpdateOutValue( inputdata.pActivator, cur + vec ); +} + +//----------------------------------------------------------------------------- +// Purpose: Input handler for subtracting from the current value. +// Input : Bit value to subtract. +//----------------------------------------------------------------------------- +void CMathVector::InputSubtract( inputdata_t &inputdata ) +{ + if( m_bDisabled ) + { + DevMsg("Math Vector %s ignoring SUBTRACT because it is disabled\n", GetDebugName() ); + return; + } + + Vector vec; + inputdata.value.Vector3D(vec); + Vector cur; + m_OutValue.Get(cur); + UpdateOutValue( inputdata.pActivator, cur - vec ); +} + +//----------------------------------------------------------------------------- +// Purpose: Input handler for multiplying the current value. +// Input : Float value to multiply the value by. +//----------------------------------------------------------------------------- +void CMathVector::InputDivide( inputdata_t &inputdata ) +{ + if( m_bDisabled ) + { + DevMsg("Math Vector %s ignoring DIVIDE because it is disabled\n", GetDebugName() ); + return; + } + + Vector vec; + inputdata.value.Vector3D(vec); + Vector cur; + m_OutValue.Get(cur); + + if (vec.x != 0) + cur.x /= vec.x; + if (vec.y != 0) + cur.y /= vec.y; + if (vec.z != 0) + cur.z /= vec.z; + + UpdateOutValue( inputdata.pActivator, cur ); + + //if (vec.x != 0 && vec.y != 0 && vec.z != 0) + //{ + // UpdateOutValue( inputdata.pActivator, cur / vec ); + //} + //else + //{ + // DevMsg( 1, "LEVEL DESIGN ERROR: Divide by zero in math_vector\n" ); + // UpdateOutValue( inputdata.pActivator, cur ); + //} +} + + +//----------------------------------------------------------------------------- +// Purpose: Input handler for multiplying the current value. +// Input : Float value to multiply the value by. +//----------------------------------------------------------------------------- +void CMathVector::InputMultiply( inputdata_t &inputdata ) +{ + if( m_bDisabled ) + { + DevMsg("Math Vector %s ignoring MULTIPLY because it is disabled\n", GetDebugName() ); + return; + } + + Vector vec; + inputdata.value.Vector3D(vec); + Vector cur; + m_OutValue.Get(cur); + UpdateOutValue( inputdata.pActivator, cur * vec ); +} + +//----------------------------------------------------------------------------- +// Purpose: Input handler for updating the value. +// Input : Bit value to set. +//----------------------------------------------------------------------------- +void CMathVector::InputSetValue( inputdata_t &inputdata ) +{ + if( m_bDisabled ) + { + DevMsg("Math Vector %s ignoring SETVALUE because it is disabled\n", GetDebugName() ); + return; + } + + Vector vec; + inputdata.value.Vector3D(vec); + UpdateOutValue( inputdata.pActivator, vec ); +} + +//----------------------------------------------------------------------------- +// Purpose: Input handler for updating the value. +// Input : Bit value to set. +//----------------------------------------------------------------------------- +void CMathVector::InputSetValueNoFire( inputdata_t &inputdata ) +{ + if( m_bDisabled ) + { + DevMsg("Math Vector %s ignoring SETVALUENOFIRE because it is disabled\n", GetDebugName() ); + return; + } + + Vector vec; + inputdata.value.Vector3D(vec); + m_OutValue.Init( vec ); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CMathVector::InputGetValue( inputdata_t &inputdata ) +{ + Vector cur; + m_OutValue.Get(cur); + m_OnGetValue.Set( cur, inputdata.pActivator, inputdata.pCaller ); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CMathVector::InputEnable( inputdata_t &inputdata ) +{ + m_bDisabled = false; +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CMathVector::InputDisable( inputdata_t &inputdata ) +{ + m_bDisabled = true; +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CMathVector::PointAt( Vector &origin, Vector &target, Vector &out ) +{ + out = origin - target; + VectorNormalize( out ); + + QAngle ang; + VectorAngles( out, ang ); + + out[0] = ang[0]; + out[1] = ang[1]; + out[2] = ang[2]; +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CMathVector::InputPointAtLocation( inputdata_t &inputdata ) +{ + if( m_bDisabled ) + { + DevMsg("Math Vector %s ignoring POINTATLOCATION because it is disabled\n", GetDebugName() ); + return; + } + + Vector cur; + m_OutValue.Get(cur); + + Vector location; + inputdata.value.Vector3D( location ); + + PointAt( cur, location, cur ); + + UpdateOutValue( inputdata.pActivator, cur ); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CMathVector::InputPointAtEntity( inputdata_t &inputdata ) +{ + if( m_bDisabled ) + { + DevMsg("Math Vector %s ignoring POINTATENTITY because it is disabled\n", GetDebugName() ); + return; + } + + if (!inputdata.value.Entity()) + { + Warning("%s received no entity to point at\n", GetDebugName()); + return; + } + + Vector cur; + m_OutValue.Get(cur); + + Vector location = inputdata.value.Entity()->GetAbsOrigin(); + + PointAt( cur, location, cur ); + + UpdateOutValue( inputdata.pActivator, cur ); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CMathVector::InputNormalize( inputdata_t &inputdata ) +{ + if( m_bDisabled ) + { + DevMsg("Math Vector %s ignoring NORMALIZE because it is disabled\n", GetDebugName() ); + return; + } + + Vector cur; + m_OutValue.Get(cur); + VectorNormalize(cur); + UpdateOutValue( inputdata.pActivator, cur ); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CMathVector::InputNormalizeAngles( inputdata_t &inputdata ) +{ + if( m_bDisabled ) + { + DevMsg("Math Vector %s ignoring NORMALIZEANGLES because it is disabled\n", GetDebugName() ); + return; + } + + Vector cur; + m_OutValue.Get(cur); + cur.x = AngleNormalize(cur.x); + cur.y = AngleNormalize(cur.y); + cur.z = AngleNormalize(cur.z); + UpdateOutValue( inputdata.pActivator, cur ); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CMathVector::InputVectorAngles( inputdata_t &inputdata ) +{ + if( m_bDisabled ) + { + DevMsg("Math Vector %s ignoring VECTORANGLES because it is disabled\n", GetDebugName() ); + return; + } + + Vector cur; + QAngle ang; + m_OutValue.Get(cur); + VectorAngles(cur, ang); + UpdateOutValue( inputdata.pActivator, Vector(ang.x, ang.y, ang.z) ); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CMathVector::InputAngleVectorForward( inputdata_t &inputdata ) +{ + if( m_bDisabled ) + { + DevMsg("Math Vector %s ignoring ANGLEVECTORFORWARD because it is disabled\n", GetDebugName() ); + return; + } + + Vector cur; + m_OutValue.Get(cur); + AngleVectors(QAngle(cur.x, cur.y, cur.z), &cur); + UpdateOutValue( inputdata.pActivator, cur ); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CMathVector::InputAngleVectorRight( inputdata_t &inputdata ) +{ + if( m_bDisabled ) + { + DevMsg("Math Vector %s ignoring ANGLEVECTORRIGHT because it is disabled\n", GetDebugName() ); + return; + } + + Vector cur; + m_OutValue.Get(cur); + AngleVectors(QAngle(cur.x, cur.y, cur.z), NULL, &cur, NULL); + UpdateOutValue( inputdata.pActivator, cur ); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CMathVector::InputAngleVectorUp( inputdata_t &inputdata ) +{ + if( m_bDisabled ) + { + DevMsg("Math Vector %s ignoring ANGLEVECTORUP because it is disabled\n", GetDebugName() ); + return; + } + + Vector cur; + m_OutValue.Get(cur); + AngleVectors(QAngle(cur.x, cur.y, cur.z), NULL, NULL, &cur); + UpdateOutValue( inputdata.pActivator, cur ); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CMathVector::SetCoordinate(float value, char coord, CBaseEntity *pActivator) +{ + if( m_bDisabled ) + { + DevMsg("Math Vector %s ignoring SET%c because it is disabled\n", GetDebugName(), coord ); + return; + } + + Vector vec; + m_OutValue.Get(vec); + switch (coord) + { + case 'X': vec.x = value; break; + case 'Y': vec.y = value; break; + case 'Z': vec.z = value; break; + } + UpdateOutValue( pActivator, vec ); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CMathVector::GetCoordinate(char coord, CBaseEntity *pActivator) +{ + if( m_bDisabled ) + { + DevMsg("Math Vector %s ignoring SET%c because it is disabled\n", GetDebugName(), coord ); + return; + } + + Vector vec; + m_OutValue.Get(vec); + switch (coord) + { + case 'X': m_OnGetX.Set(vec.x, pActivator, this); break; + case 'Y': m_OnGetY.Set(vec.y, pActivator, this); break; + case 'Z': m_OnGetZ.Set(vec.z, pActivator, this); break; + } +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CMathVector::AddCoordinate(float value, char coord, CBaseEntity *pActivator) +{ + if( m_bDisabled ) + { + DevMsg("Math Vector %s ignoring ADD%c because it is disabled\n", GetDebugName(), coord ); + return; + } + + Vector vec; + m_OutValue.Get(vec); + switch (coord) + { + case 'X': vec.x += value; break; + case 'Y': vec.y += value; break; + case 'Z': vec.z += value; break; + } + UpdateOutValue( pActivator, vec ); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CMathVector::SubtractCoordinate(float value, char coord, CBaseEntity *pActivator) +{ + if( m_bDisabled ) + { + DevMsg("Math Vector %s ignoring SUBTRACT%c because it is disabled\n", GetDebugName(), coord ); + return; + } + + Vector vec; + m_OutValue.Get(vec); + switch (coord) + { + case 'X': vec.x += value; break; + case 'Y': vec.y += value; break; + case 'Z': vec.z += value; break; + } + UpdateOutValue( pActivator, vec ); +} + +//----------------------------------------------------------------------------- +// Purpose: Sets the value to the new value, firing the output value. +// Input : vecNewValue - Value to set. +//----------------------------------------------------------------------------- +void CMathVector::UpdateOutValue(CBaseEntity *pActivator, Vector vecNewValue) +{ + if (HasSpawnFlags( SF_MATH_VECTOR_DISABLE_X )) + vecNewValue.x = 0; + if (HasSpawnFlags( SF_MATH_VECTOR_DISABLE_Y )) + vecNewValue.y = 0; + if (HasSpawnFlags( SF_MATH_VECTOR_DISABLE_Z )) + vecNewValue.z = 0; + + m_OutValue.Set(vecNewValue, pActivator, this); + + m_OutX.Set(vecNewValue.x, pActivator, this); + m_OutY.Set(vecNewValue.y, pActivator, this); + m_OutZ.Set(vecNewValue.z, pActivator, this); +} + +//----------------------------------------------------------------------------- +// Purpose: Draw any debug text overlays +// Input : +// Output : Current text offset from the top +//----------------------------------------------------------------------------- +int CMathVector::DrawDebugTextOverlays( void ) +{ + int text_offset = BaseClass::DrawDebugTextOverlays(); + + if (m_debugOverlays & OVERLAY_TEXT_BIT) + { + char tempstr[512]; + Vector cur; + m_OutValue.Get(cur); + + Q_snprintf(tempstr, sizeof(tempstr), "current value: [%g %g %g]", (double)cur[0], (double)cur[1], (double)cur[2]); + EntityText(text_offset,tempstr,0); + text_offset++; + + if( m_bDisabled ) + { + Q_snprintf(tempstr,sizeof(tempstr),"*DISABLED*"); + } + else + { + Q_snprintf(tempstr,sizeof(tempstr),"Enabled."); + } + EntityText(text_offset,tempstr,0); + text_offset++; + + } + return text_offset; +} + +//----------------------------------------------------------------------------- +// Purpose: Accesses/modifies any field in a datadesc based on its internal name. +// Oh boy. +//----------------------------------------------------------------------------- +class CLogicFieldAccessor : public CLogicKeyfieldAccessor +{ + DECLARE_CLASS(CLogicFieldAccessor, CLogicKeyfieldAccessor); + +private: + bool TestKey(CBaseEntity *pTarget, const char *szKeyName); + bool SetKeyValue(CBaseEntity *pTarget, const char *szKeyName, const char *szValue); + bool SetKeyValueBits(CBaseEntity *pTarget, const char *szKeyName, int iValue, bool bRemove = false); + + //DECLARE_DATADESC(); +}; + +LINK_ENTITY_TO_CLASS(logic_datadesc_accessor, CLogicFieldAccessor); + + +//BEGIN_DATADESC(CLogicFieldAccessor) + +//END_DATADESC() + + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CLogicFieldAccessor::TestKey(CBaseEntity *pTarget, const char *szKeyName) +{ + variant_t var; + for ( datamap_t *dmap = pTarget->GetDataDescMap(); dmap != NULL; dmap = dmap->baseMap ) + { + // search through all the readable fields in the data description, looking for a match + for ( int i = 0; i < dmap->dataNumFields; i++ ) + { + if ( dmap->dataDesc[i].flags & (FTYPEDESC_SAVE | FTYPEDESC_KEY) ) + { + DevMsg("Field Name: %s,\n", dmap->dataDesc[i].fieldName); + if ( Matcher_NamesMatch(szKeyName, dmap->dataDesc[i].fieldName) ) + { + fieldtype_t fieldtype = dmap->dataDesc[i].fieldType; + switch (fieldtype) + { + case FIELD_TIME: fieldtype = FIELD_FLOAT; break; + case FIELD_MODELNAME: fieldtype = FIELD_STRING; break; + case FIELD_SOUNDNAME: fieldtype = FIELD_STRING; break; + // There's definitely more of them. Add when demand becomes prevalent + } + + var.Set( fieldtype, ((char*)pTarget) + dmap->dataDesc[i].fieldOffset[ TD_OFFSET_NORMAL ] ); + DevMsg("FIELD TYPE: %i\n", fieldtype); + m_OutValue.Set(var, pTarget, this); + return true; + } + } + } + } + + m_OnFailed.FireOutput(pTarget, this); + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CLogicFieldAccessor::SetKeyValue(CBaseEntity *pTarget, const char *szKeyName, const char *szValue) +{ + for ( datamap_t *dmap = pTarget->GetDataDescMap(); dmap != NULL; dmap = dmap->baseMap ) + { + // search through all the readable fields in the data description, looking for a match + for ( int i = 0; i < dmap->dataNumFields; i++ ) + { + if ( dmap->dataDesc[i].flags & (FTYPEDESC_SAVE | FTYPEDESC_KEY) ) + { + DevMsg("Field Name: %s,\n", dmap->dataDesc[i].fieldName); + if ( Matcher_NamesMatch(szKeyName, dmap->dataDesc[i].fieldName) ) + { + // Copied from ::ParseKeyvalue... + fieldtype_t fieldtype = FIELD_VOID; + typedescription_t *pField = &dmap->dataDesc[i]; + char *data = Datadesc_SetFieldString( szValue, pTarget, pField, &fieldtype ); + + if (!data) + { + Warning( "%s cannot set field of type %i.\n", GetDebugName(), dmap->dataDesc[i].fieldType ); + } + else if (fieldtype != FIELD_VOID) + { + variant_t var; + var.Set(fieldtype, data); + m_OutValue.Set(var, pTarget, this); + return true; + } + } + } + } + } + + m_OnFailed.FireOutput(pTarget, this); + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CLogicFieldAccessor::SetKeyValueBits(CBaseEntity *pTarget, const char *szKeyName, int iValue, bool bRemove) +{ + variant_t var; + for ( datamap_t *dmap = pTarget->GetDataDescMap(); dmap != NULL; dmap = dmap->baseMap ) + { + // search through all the readable fields in the data description, looking for a match + for ( int i = 0; i < dmap->dataNumFields; i++ ) + { + if ( dmap->dataDesc[i].flags & (FTYPEDESC_SAVE | FTYPEDESC_KEY) ) + { + DevMsg("Field Name: %s,\n", dmap->dataDesc[i].fieldName); + if ( Matcher_NamesMatch(szKeyName, dmap->dataDesc[i].fieldName) ) + { + fieldtype_t fieldtype = dmap->dataDesc[i].fieldType; + if (fieldtype != FIELD_INTEGER) + break; + + var.Set( fieldtype, ((char*)pTarget) + dmap->dataDesc[i].fieldOffset[ TD_OFFSET_NORMAL ] ); + + if (bRemove) + var.SetInt(var.Int() & ~iValue); + else + var.SetInt(var.Int() | iValue); + + DevMsg("FIELD TYPE: %i\n", fieldtype); + m_OutValue.Set(var, pTarget, this); + return true; + } + } + } + } + + m_OnFailed.FireOutput(pTarget, this); + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: Passes global variables, like curtime. +//----------------------------------------------------------------------------- +class CGameGlobalVars : public CLogicalEntity +{ + DECLARE_CLASS( CGameGlobalVars, CLogicalEntity ); +private: + + // Inputs + void InputGetCurtime( inputdata_t &inputdata ) { m_OutCurtime.Set(gpGlobals->curtime, inputdata.pActivator, this); } + void InputGetFrameCount( inputdata_t &inputdata ) { m_OutFrameCount.Set(gpGlobals->framecount, inputdata.pActivator, this); } + void InputGetFrametime( inputdata_t &inputdata ) { m_OutFrametime.Set(gpGlobals->frametime, inputdata.pActivator, this); } + void InputGetTickCount( inputdata_t &inputdata ) { m_OutTickCount.Set(gpGlobals->tickcount, inputdata.pActivator, this); } + void InputGetIntervalPerTick( inputdata_t &inputdata ) { m_OutIntervalPerTick.Set(gpGlobals->interval_per_tick, inputdata.pActivator, this); } + + // Outputs + COutputFloat m_OutCurtime; + COutputInt m_OutFrameCount; + COutputFloat m_OutFrametime; + COutputInt m_OutTickCount; + COutputInt m_OutIntervalPerTick; + + DECLARE_DATADESC(); +}; + +LINK_ENTITY_TO_CLASS(game_globalvars, CGameGlobalVars); + + +BEGIN_DATADESC( CGameGlobalVars ) + + // Inputs + DEFINE_INPUTFUNC( FIELD_VOID, "GetCurtime", InputGetCurtime ), + DEFINE_INPUTFUNC( FIELD_VOID, "GetFrameCount", InputGetFrameCount ), + DEFINE_INPUTFUNC( FIELD_VOID, "GetFrametime", InputGetFrametime ), + DEFINE_INPUTFUNC( FIELD_VOID, "GetTickCount", InputGetTickCount ), + DEFINE_INPUTFUNC( FIELD_VOID, "GetIntervalPerTick", InputGetIntervalPerTick ), + + // Outputs + DEFINE_OUTPUT(m_OutCurtime, "OutCurtime"), + DEFINE_OUTPUT(m_OutFrameCount, "OutFrameCount"), + DEFINE_OUTPUT(m_OutFrametime, "OutFrametime"), + DEFINE_OUTPUT(m_OutTickCount, "OutTickCount"), + DEFINE_OUTPUT(m_OutIntervalPerTick, "OutIntervalPerTick"), + +END_DATADESC() + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//void CGameGlobalVars::InputGetCurtime( inputdata_t &inputdata ) +//{ +// m_OutCurtime.Set(gpGlobals->curtime, inputdata.pActivator, this); +//} + + +#define MathModCalc(val1, val2, op) \ + switch (op) \ + { \ + case '+': val1 += val2; break; \ + case '-': val1 -= val2; break; \ + case '*': val1 *= val2; break; \ + case '/': val1 /= val2; break; \ + } \ + +//----------------------------------------------------------------------------- +// Purpose: Modifies values on the fly. +//----------------------------------------------------------------------------- +class CMathMod : public CLogicalEntity +{ + DECLARE_CLASS( CMathMod, CLogicalEntity ); +private: + + bool KeyValue(const char *szKeyName, const char *szValue); + + // Inputs + void InputSetMod( inputdata_t &inputdata ); + void InputSetOperator( inputdata_t &inputdata ); + + void InputModInt( inputdata_t &inputdata ); + void InputModFloat( inputdata_t &inputdata ); + void InputModVector( inputdata_t &inputdata ); + + // Outputs + COutputInt m_OutInt; + COutputFloat m_OutFloat; + COutputVector m_OutVector; + + int m_Operator; + + variant_t m_Mod; + + DECLARE_DATADESC(); +}; + +LINK_ENTITY_TO_CLASS(math_mod, CMathMod); + + +BEGIN_DATADESC( CMathMod ) + + DEFINE_KEYFIELD( m_Operator, FIELD_INTEGER, "SetOperator" ), + + DEFINE_VARIANT( m_Mod ), + + // Inputs + DEFINE_INPUTFUNC( FIELD_INPUT, "SetMod", InputSetMod ), + DEFINE_INPUTFUNC( FIELD_STRING, "SetOperator", InputSetOperator ), + + DEFINE_INPUTFUNC( FIELD_INTEGER, "ModInt", InputModInt ), + DEFINE_INPUTFUNC( FIELD_FLOAT, "ModFloat", InputModFloat ), + DEFINE_INPUTFUNC( FIELD_VECTOR, "ModVector", InputModVector ), + + // Outputs + DEFINE_OUTPUT(m_OutInt, "OutInt"), + DEFINE_OUTPUT(m_OutFloat, "OutFloat"), + DEFINE_OUTPUT(m_OutVector, "OutVector"), + +END_DATADESC() + +//----------------------------------------------------------------------------- +// Purpose: Handles key values from the BSP before spawn is called. +//----------------------------------------------------------------------------- +bool CMathMod::KeyValue(const char *szKeyName, const char *szValue) +{ + if (!stricmp(szKeyName, "startvalue")) + { + // It converts later anyway + m_Mod.SetString(AllocPooledString(szValue)); + return true; + } + + return BaseClass::KeyValue(szKeyName, szValue); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CMathMod::InputSetMod( inputdata_t &inputdata ) +{ + m_Mod = inputdata.value; + //if (inputdata.value.FieldType() == FIELD_STRING) + // m_Mod = Variant_Parse(inputdata.value.String()); + //else + // m_Mod = inputdata.value; +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CMathMod::InputSetOperator( inputdata_t &inputdata ) +{ + m_Operator = inputdata.value.String()[0]; +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CMathMod::InputModInt( inputdata_t &inputdata ) +{ + m_Mod.Convert(FIELD_INTEGER); + + DevMsg("Operator is %c you see\n", m_Operator); + + int out = inputdata.value.Int(); + MathModCalc(out, m_Mod.Int(), m_Operator); + + m_OutInt.Set( out, inputdata.pActivator, this ); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CMathMod::InputModFloat( inputdata_t &inputdata ) +{ + m_Mod.Convert(FIELD_FLOAT); + + float out = inputdata.value.Float(); + MathModCalc(out, m_Mod.Float(), m_Operator); + + m_OutFloat.Set( out, inputdata.pActivator, this ); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CMathMod::InputModVector( inputdata_t &inputdata ) +{ + m_Mod.Convert(FIELD_VECTOR); + + Vector out; + inputdata.value.Vector3D(out); + Vector mod; + m_Mod.Vector3D(mod); + MathModCalc(out, mod, m_Operator); + + m_OutVector.Set( out, inputdata.pActivator, this ); +} + +//----------------------------------------------------------------------------- +// Purpose: Gets an entity's model information. +//----------------------------------------------------------------------------- +class CLogicModelInfo : public CLogicalEntity +{ + DECLARE_CLASS( CLogicModelInfo, CLogicalEntity ); +private: + + CBaseAnimating *GetTarget(inputdata_t &inputdata); + int GetPoseParameterIndex(CBaseAnimating *pTarget); + + // Inputs + //void InputSetTarget( inputdata_t &inputdata ) { BaseClass::InputSetTarget(inputdata); m_hTarget = NULL; } + void InputGetNumSkins( inputdata_t &inputdata ); + void InputLookupSequence( inputdata_t &inputdata ); + void InputLookupActivity( inputdata_t &inputdata ); + + void InputSetPoseParameterName( inputdata_t &inputdata ); + void InputSetPoseParameterValue( inputdata_t &inputdata ); + void InputGetPoseParameter( inputdata_t &inputdata ); + + // Outputs + COutputInt m_OutNumSkins; + COutputInt m_OnHasSequence; + COutputEvent m_OnLacksSequence; + + COutputFloat m_OutPoseParameterValue; + + // KeyValues + + string_t m_iszPoseParameterName; + int m_iPoseParameterIndex = -1; + + DECLARE_DATADESC(); +}; + +LINK_ENTITY_TO_CLASS(logic_modelinfo, CLogicModelInfo); + + +BEGIN_DATADESC( CLogicModelInfo ) + + DEFINE_KEYFIELD( m_iszPoseParameterName, FIELD_STRING, "PoseParameterName" ), + DEFINE_FIELD( m_iPoseParameterIndex, FIELD_INTEGER ), + + // Inputs + DEFINE_INPUTFUNC( FIELD_VOID, "GetNumSkins", InputGetNumSkins ), + DEFINE_INPUTFUNC( FIELD_STRING, "LookupSequence", InputLookupSequence ), + DEFINE_INPUTFUNC( FIELD_STRING, "LookupActivity", InputLookupActivity ), + DEFINE_INPUTFUNC( FIELD_STRING, "SetPoseParameterName", InputSetPoseParameterName ), + DEFINE_INPUTFUNC( FIELD_FLOAT, "SetPoseParameterValue", InputSetPoseParameterValue ), + DEFINE_INPUTFUNC( FIELD_VOID, "GetPoseParameter", InputGetPoseParameter ), + + // Outputs + DEFINE_OUTPUT(m_OutNumSkins, "OutNumSkins"), + DEFINE_OUTPUT(m_OnHasSequence, "OnHasSequence"), + DEFINE_OUTPUT(m_OnLacksSequence, "OnLacksSequence"), + DEFINE_OUTPUT(m_OutPoseParameterValue, "OutPoseParameterValue"), + +END_DATADESC() + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +inline CBaseAnimating *CLogicModelInfo::GetTarget(inputdata_t &inputdata) +{ + CBaseEntity *pEntity = gEntList.FindEntityByName(NULL, STRING(m_target), this, inputdata.pActivator, inputdata.pCaller); + if (!pEntity || !pEntity->GetBaseAnimating()) + return NULL; + return pEntity->GetBaseAnimating(); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +inline int CLogicModelInfo::GetPoseParameterIndex(CBaseAnimating *pTarget) +{ + if (m_iPoseParameterIndex == -1) + m_iPoseParameterIndex = pTarget->LookupPoseParameter(STRING(m_iszPoseParameterName)); + return m_iPoseParameterIndex; +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CLogicModelInfo::InputGetNumSkins( inputdata_t &inputdata ) +{ + CBaseAnimating *pAnimating = GetTarget(inputdata); + if (pAnimating && pAnimating->GetModelPtr()) + { + m_OutNumSkins.Set(pAnimating->GetModelPtr()->numskinfamilies(), pAnimating, this); + } +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CLogicModelInfo::InputLookupSequence( inputdata_t &inputdata ) +{ + CBaseAnimating *pAnimating = GetTarget(inputdata); + if (pAnimating && pAnimating->GetModelPtr()) + { + int index = pAnimating->LookupSequence(inputdata.value.String()); + + if (index != ACT_INVALID) + m_OnHasSequence.Set(index, pAnimating, this); + else + m_OnLacksSequence.FireOutput(pAnimating, this); + } +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CLogicModelInfo::InputLookupActivity( inputdata_t &inputdata ) +{ + CBaseAnimating *pAnimating = GetTarget(inputdata); + if (pAnimating && pAnimating->GetModelPtr()) + { + int iActivity = ActivityList_IndexForName(inputdata.value.String()); + if (iActivity == -1) + { + // Check if it's a raw activity ID + iActivity = atoi(inputdata.value.String()); + if (!ActivityList_NameForIndex(iActivity)) + { + Msg("%s received invalid LookupActivity %s\n", GetDebugName(), inputdata.value.String()); + return; + } + } + + int index = pAnimating->SelectWeightedSequence((Activity)iActivity); + + if (index != ACT_INVALID) + m_OnHasSequence.Set(index, pAnimating, this); + else + m_OnLacksSequence.FireOutput(pAnimating, this); + } +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CLogicModelInfo::InputSetPoseParameterName( inputdata_t &inputdata ) +{ + m_iszPoseParameterName = inputdata.value.StringID(); + m_iPoseParameterIndex = -1; + + CBaseAnimating *pAnimating = GetTarget(inputdata); + if (pAnimating && pAnimating->GetModelPtr()) + { + if (GetPoseParameterIndex(pAnimating) == -1) + Warning("%s: Pose parameter \"%s\" does not exist on %s\n", GetDebugName(), inputdata.value.String(), STRING(pAnimating->GetModelName())); + } +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CLogicModelInfo::InputSetPoseParameterValue( inputdata_t &inputdata ) +{ + CBaseAnimating *pAnimating = GetTarget(inputdata); + if (pAnimating && pAnimating->GetModelPtr()) + { + int index = GetPoseParameterIndex(pAnimating); + if (index != -1) + { + pAnimating->SetPoseParameter( index, inputdata.value.Float() ); + } + else + Warning("%s: Pose parameter \"%s\" does not exist on %s\n", GetDebugName(), STRING(m_iszPoseParameterName), STRING(pAnimating->GetModelName())); + } +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CLogicModelInfo::InputGetPoseParameter( inputdata_t &inputdata ) +{ + CBaseAnimating *pAnimating = GetTarget(inputdata); + if (pAnimating && pAnimating->GetModelPtr()) + { + int index = GetPoseParameterIndex(pAnimating); + if (index != -1) + { + m_OutPoseParameterValue.Set( pAnimating->GetPoseParameter( index ), pAnimating, this ); + } + else + Warning("%s: Pose parameter \"%s\" does not exist on %s\n", GetDebugName(), inputdata.value.String(), STRING(pAnimating->GetModelName())); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Checks and calculates an entity's position. +//----------------------------------------------------------------------------- +class CLogicEntityPosition : public CLogicalEntity +{ + DECLARE_CLASS( CLogicEntityPosition, CLogicalEntity ); +private: + EHANDLE m_hTarget; + + int m_iPositionType; + enum + { + POSITION_ORIGIN = 0, + POSITION_LOCAL, + POSITION_BBOX, + POSITION_EYES, + POSITION_EARS, + POSITION_ATTACHMENT, + }; + + // Something that accompanies the position type, like an attachment name. + string_t m_iszPositionParameter; + + CBaseEntity *GetTarget(CBaseEntity *pActivator, CBaseEntity *pCaller); + + Vector GetPosition(CBaseEntity *pEntity); + QAngle GetAngles(CBaseEntity *pEntity); + + // Inputs + void InputGetPosition( inputdata_t &inputdata ); + void InputSetPosition( inputdata_t &inputdata ); + void InputPredictPosition( inputdata_t &inputdata ); + + void InputSetTarget( inputdata_t &inputdata ) { BaseClass::InputSetTarget(inputdata); m_hTarget = NULL; } + + // Outputs + COutputVector m_OutPosition; + COutputVector m_OutAngles; + + DECLARE_DATADESC(); +}; + +LINK_ENTITY_TO_CLASS(logic_entity_position, CLogicEntityPosition); + +BEGIN_DATADESC( CLogicEntityPosition ) + + // Keys + DEFINE_FIELD( m_hTarget, FIELD_EHANDLE ), + DEFINE_KEYFIELD( m_iPositionType, FIELD_INTEGER, "PositionType" ), + DEFINE_KEYFIELD( m_iszPositionParameter, FIELD_STRING, "PositionParameter" ), + + // Inputs + DEFINE_INPUTFUNC( FIELD_VOID, "GetPosition", InputGetPosition ), + DEFINE_INPUTFUNC( FIELD_VECTOR, "SetPosition", InputSetPosition ), + DEFINE_INPUTFUNC( FIELD_FLOAT, "PredictPosition", InputPredictPosition ), + + // Outputs + DEFINE_OUTPUT(m_OutPosition, "OutPosition"), + DEFINE_OUTPUT(m_OutAngles, "OutAngles"), + +END_DATADESC() + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +inline CBaseEntity *CLogicEntityPosition::GetTarget(CBaseEntity *pActivator, CBaseEntity *pCaller) +{ + // Always reset with procedurals + if (!m_hTarget || STRING(m_target)[0] == '!') + m_hTarget = gEntList.FindEntityByName(NULL, STRING(m_target), this, pActivator, pCaller); + return m_hTarget.Get(); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +Vector CLogicEntityPosition::GetPosition(CBaseEntity *pEntity) +{ + switch (m_iPositionType) + { + case POSITION_ORIGIN: return pEntity->GetAbsOrigin(); + case POSITION_LOCAL: return pEntity->GetLocalOrigin(); + case POSITION_BBOX: return pEntity->WorldSpaceCenter(); + case POSITION_EYES: return pEntity->EyePosition(); + case POSITION_EARS: return pEntity->EarPosition(); + case POSITION_ATTACHMENT: + { + CBaseAnimating *pAnimating = pEntity->GetBaseAnimating(); + if (!pAnimating) + { + Warning("%s wants to measure one of %s's attachments, but %s doesn't support them!\n", GetDebugName(), pEntity->GetDebugName(), pEntity->GetDebugName()); + break; + } + + Vector vecPosition; + pAnimating->GetAttachment(STRING(m_iszPositionParameter), vecPosition); + return vecPosition; + } + } + + return vec3_origin; +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +QAngle CLogicEntityPosition::GetAngles(CBaseEntity *pEntity) +{ + switch (m_iPositionType) + { + case POSITION_BBOX: + case POSITION_EARS: + case POSITION_ORIGIN: return pEntity->GetAbsAngles(); break; + case POSITION_LOCAL: return pEntity->GetLocalAngles(); break; + case POSITION_EYES: return pEntity->EyeAngles(); break; + case POSITION_ATTACHMENT: + { + CBaseAnimating *pAnimating = pEntity->GetBaseAnimating(); + if (!pAnimating) + { + Warning("%s wants to measure one of %s's attachments, but %s doesn't support them!\n", GetDebugName(), pEntity->GetDebugName(), pEntity->GetDebugName()); + break; + } + + QAngle AttachmentAngles; + matrix3x4_t attachmentToWorld; + pAnimating->GetAttachment( pAnimating->LookupAttachment( STRING( m_iszPositionParameter ) ), attachmentToWorld ); + MatrixAngles( attachmentToWorld, AttachmentAngles ); + return AttachmentAngles; + } break; + } + + return vec3_angle; +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CLogicEntityPosition::InputGetPosition( inputdata_t &inputdata ) +{ + CBaseEntity *pEntity = GetTarget(inputdata.pActivator, inputdata.pCaller); + if (!pEntity) + { + m_OutPosition.Set( vec3_origin, NULL, this ); + m_OutAngles.Set( vec3_angle, NULL, this ); + return; + } + + m_OutPosition.Set( GetPosition(pEntity), pEntity, this ); + m_OutAngles.Set( GetAngles(pEntity), pEntity, this ); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CLogicEntityPosition::InputSetPosition( inputdata_t &inputdata ) +{ + CBaseEntity *pEntity = GetTarget(inputdata.pActivator, inputdata.pCaller); + if (!pEntity) + { + Warning("%s can't find entity %s for SetPosition!\n", GetDebugName(), STRING(m_target)); + return; + } + + Vector vec; + inputdata.value.Vector3D(vec); + + // If the position is local, they might want to move local origin instead + if (m_iPositionType == POSITION_LOCAL) + pEntity->SetLocalOrigin(vec); + else + pEntity->SetAbsOrigin(vec); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CLogicEntityPosition::InputPredictPosition( inputdata_t &inputdata ) +{ + CBaseEntity *pEntity = GetTarget(inputdata.pActivator, inputdata.pCaller); + if (!pEntity) + { + m_OutPosition.Set( vec3_origin, NULL, this ); + m_OutAngles.Set( vec3_angle, NULL, this ); + return; + } + + Vector vecPosition; + UTIL_PredictedPosition(pEntity, GetPosition(pEntity), inputdata.value.Float(), &vecPosition); + + QAngle angAngles; + UTIL_PredictedAngles(pEntity, GetAngles(pEntity), inputdata.value.Float(), &angAngles); + + m_OutPosition.Set( vecPosition, pEntity, this ); + m_OutAngles.Set( angAngles, pEntity, this ); +} + +//----------------------------------------------------------------------------- +// Purpose: Accesses context values +//----------------------------------------------------------------------------- +class CLogicContextAccessor : public CLogicalEntity +{ + DECLARE_CLASS(CLogicContextAccessor, CLogicalEntity); + +public: + CBaseEntity *GetTarget(CBaseEntity *pCaller, CBaseEntity *pActivator); + + bool TestContext(CBaseEntity *pTarget, const char *szKeyName); + void SetContext(CBaseEntity *pTarget, const char *szKeyName, string_t szValue); + + // Inputs + void InputTest(inputdata_t &inputdata); + void InputTestContext(inputdata_t &inputdata); + void InputTestTarget(inputdata_t &inputdata); + + void InputSetContext(inputdata_t &inputdata); + + void InputSetValue(inputdata_t &inputdata); + + //bool ReadUnregisteredKeyfields(CBaseEntity *pTarget, const char *szKeyName, variant_t *variant); + + COutputString m_OutValue; + COutputEvent m_OnFailed; + + string_t m_iszContext; + + DECLARE_DATADESC(); +}; + +LINK_ENTITY_TO_CLASS(logic_context_accessor, CLogicContextAccessor); + + +BEGIN_DATADESC(CLogicContextAccessor) + +DEFINE_KEYFIELD( m_iszContext, FIELD_STRING, "context" ), + +// Inputs +DEFINE_INPUTFUNC(FIELD_VOID, "Test", InputTest), +DEFINE_INPUTFUNC(FIELD_STRING, "TestContext", InputTestContext), +DEFINE_INPUTFUNC(FIELD_STRING, "TestTarget", InputTestTarget), +DEFINE_INPUTFUNC(FIELD_STRING, "SetContext", InputSetContext), +DEFINE_INPUTFUNC(FIELD_STRING, "SetValue", InputSetValue), + +DEFINE_OUTPUT( m_OutValue, "OutValue" ), +DEFINE_OUTPUT( m_OnFailed, "OnFailed" ), + +END_DATADESC() + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +inline CBaseEntity *CLogicContextAccessor::GetTarget(CBaseEntity *pCaller, CBaseEntity *pActivator) +{ + return gEntList.FindEntityByName(NULL, m_target, this, pActivator, pCaller); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CLogicContextAccessor::TestContext(CBaseEntity *pTarget, const char *szKeyName) +{ + int idx = pTarget->FindContextByName( szKeyName ); + if ( idx != -1 ) + { + m_OutValue.Set(FindPooledString(pTarget->GetContextValue(idx)), pTarget, this); + return true; + } + else + { + m_OnFailed.FireOutput(pTarget, this); + return false; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CLogicContextAccessor::SetContext(CBaseEntity *pTarget, const char *szKeyName, string_t szValue) +{ + pTarget->AddContext(szKeyName, STRING(szValue)); + + m_OutValue.Set(szValue, pTarget, this); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CLogicContextAccessor::InputTest(inputdata_t &inputdata) +{ + CBaseEntity *pTarget = GetTarget(inputdata.pCaller, inputdata.pActivator); + if (pTarget && m_iszContext != NULL_STRING) + { + TestContext(pTarget, STRING(m_iszContext)); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CLogicContextAccessor::InputTestContext(inputdata_t &inputdata) +{ + const char *input = inputdata.value.String(); + CBaseEntity *pTarget = GetTarget(inputdata.pCaller, inputdata.pActivator); + if (input && pTarget) + { + TestContext(pTarget, input); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CLogicContextAccessor::InputTestTarget(inputdata_t &inputdata) +{ + m_target = inputdata.value.StringID(); + CBaseEntity *pTarget = gEntList.FindEntityByName(NULL, inputdata.value.StringID(), this, inputdata.pCaller, inputdata.pActivator); + if (pTarget && m_iszContext != NULL_STRING) + { + TestContext(pTarget, STRING(m_iszContext)); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CLogicContextAccessor::InputSetContext(inputdata_t &inputdata) +{ + m_iszContext = inputdata.value.StringID(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CLogicContextAccessor::InputSetValue(inputdata_t &inputdata) +{ + CBaseEntity *pTarget = GetTarget(inputdata.pCaller, inputdata.pActivator); + if (pTarget) + { + SetContext(pTarget, STRING(m_iszContext), inputdata.value.StringID()); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Replicates light pattern functionality. +//----------------------------------------------------------------------------- +class CMathLightPattern : public CLogicalEntity +{ + DECLARE_CLASS( CMathLightPattern, CLogicalEntity ); +private: + + + string_t m_iszPattern; + + bool m_bDisabled; + + void Spawn(); + bool KeyValue( const char *szKeyName, const char *szValue ); + + void OutputCurPattern(); + + void StartPatternThink(); + void PatternThink(); + unsigned char m_NextLetter = 0; + + // How fast the pattern should be + float m_flPatternSpeed = 0.1f; + + inline bool VerifyPatternValid() { return (m_iszPattern != NULL_STRING && STRING( m_iszPattern )[0] != '\0'); } + + // Inputs + void InputSetStyle( inputdata_t &inputdata ); + void InputSetPattern( inputdata_t &inputdata ); + void InputEnable( inputdata_t &inputdata ); + void InputDisable( inputdata_t &inputdata ); + void InputToggle( inputdata_t &inputdata ); + + // Outputs + COutputFloat m_OutValue; + COutputString m_OutLetter; + COutputEvent m_OnLightOn; + COutputEvent m_OnLightOff; + + DECLARE_DATADESC(); +}; + +LINK_ENTITY_TO_CLASS( math_lightpattern, CMathLightPattern ); + +BEGIN_DATADESC( CMathLightPattern ) + + // Keys + DEFINE_KEYFIELD(m_iszPattern, FIELD_STRING, "pattern"), + DEFINE_KEYFIELD(m_bDisabled, FIELD_BOOLEAN, "StartDisabled" ), + DEFINE_KEYFIELD(m_flPatternSpeed, FIELD_FLOAT, "PatternSpeed"), + + // Inputs + DEFINE_INPUTFUNC( FIELD_INTEGER, "SetStyle", InputSetStyle ), + DEFINE_INPUTFUNC( FIELD_STRING, "SetPattern", InputSetPattern ), + DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ), + DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ), + DEFINE_INPUTFUNC( FIELD_VOID, "Toggle", InputToggle ), + + // Outputs + DEFINE_OUTPUT(m_OutValue, "OutValue"), + DEFINE_OUTPUT(m_OutLetter, "OutLetter"), + DEFINE_OUTPUT(m_OnLightOn, "OnLightOn"), + DEFINE_OUTPUT(m_OnLightOff, "OnLightOff"), + + DEFINE_THINKFUNC( PatternThink ), + DEFINE_FIELD( m_NextLetter, FIELD_CHARACTER ), + +END_DATADESC() + +extern const char *GetDefaultLightstyleString( int styleIndex ); + +static const char *s_pLightPatternContext = "PatternContext"; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CMathLightPattern::Spawn() +{ + BaseClass::Spawn(); + + if (!m_bDisabled && VerifyPatternValid()) + StartPatternThink(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CMathLightPattern::OutputCurPattern() +{ + // This code looks messy, but it does what it's supposed to and is safe enough. + // First, we get the next letter in the pattern sequence. + // Next, we calculate its integral proximity to the character 'a' (fully dark) + // and calculate its approximate brightness by dividing it by the number of letters in the alphabet other than a. + // We output that brightness value for things like projected textures and other custom intensity values + // so they could replicate the patterns of their corresponding vrad lights. + char cLetter = STRING(m_iszPattern)[m_NextLetter]; + int iValue = (cLetter - 'a'); + float flResult = iValue != 0 ? ((float)iValue / 25.0f) : 0.0f; + m_OutValue.Set(flResult, this, this); + + // User-friendly "Light on, light off" outputs + if (flResult > 0) + m_OnLightOn.FireOutput(this, this); + else + m_OnLightOff.FireOutput(this, this); + + // Create a string with cLetter and a null terminator. + char szLetter[2] = { cLetter, '\0' }; + m_OutLetter.Set( AllocPooledString(szLetter), this, this ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CMathLightPattern::StartPatternThink() +{ + // Output our current/next one immediately. + OutputCurPattern(); + + // Start thinking now. + SetContextThink( &CMathLightPattern::PatternThink, gpGlobals->curtime, s_pLightPatternContext ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CMathLightPattern::PatternThink() +{ + // Output our current/next one + OutputCurPattern(); + + // Increment + m_NextLetter++; + if (STRING(m_iszPattern)[m_NextLetter] == '\0') + m_NextLetter = 0; + + //m_OutLetter.Set(AllocPooledString(UTIL_VarArgs("%c", m_NextLetter)), this, this); + + SetNextThink( gpGlobals->curtime + m_flPatternSpeed, s_pLightPatternContext ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CMathLightPattern::KeyValue( const char *szKeyName, const char *szValue ) +{ + if ( FStrEq( szKeyName, "style" ) ) + { + m_iszPattern = AllocPooledString(GetDefaultLightstyleString(atoi(szValue))); + } + else + return BaseClass::KeyValue( szKeyName, szValue ); + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CMathLightPattern::InputSetStyle( inputdata_t &inputdata ) +{ + m_iszPattern = AllocPooledString(GetDefaultLightstyleString(inputdata.value.Int())); + m_NextLetter = 0; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CMathLightPattern::InputSetPattern( inputdata_t &inputdata ) +{ + m_iszPattern = inputdata.value.StringID(); + m_NextLetter = 0; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CMathLightPattern::InputEnable( inputdata_t &inputdata ) +{ + if (VerifyPatternValid()) + StartPatternThink(); + else + Warning("%s tried to enable without valid pattern\n", GetDebugName()); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CMathLightPattern::InputDisable( inputdata_t &inputdata ) +{ + SetContextThink( NULL, TICK_NEVER_THINK, s_pLightPatternContext ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CMathLightPattern::InputToggle( inputdata_t &inputdata ) +{ + if (GetNextThink(s_pLightPatternContext) != TICK_NEVER_THINK) + InputDisable(inputdata); + else + InputEnable(inputdata); +} + +//----------------------------------------------------------------------------- +// Purpose: Sequences for keypads, etc. +//----------------------------------------------------------------------------- +#define MAX_SEQUENCE_CASES 16 + +class CLogicSequence : public CLogicalEntity +{ + DECLARE_CLASS( CLogicSequence, CLogicalEntity ); +public: + CLogicSequence(); + + void Activate(); + + bool KeyValue( const char *szKeyName, const char *szValue ); + + void TestCase( int iCase, string_t iszValue, CBaseEntity *pActivator ); + void SequenceComplete( string_t iszValue, CBaseEntity *pActivator ); + +private: + string_t m_iszCase[MAX_SEQUENCE_CASES]; + int m_iNumCases; + + bool m_bDisabled; + + bool m_bDontIncrementOnPass; + + // Inputs + void InputEnable( inputdata_t &inputdata ); + void InputDisable( inputdata_t &inputdata ); + void InputToggle( inputdata_t &inputdata ); + void InputInValue( inputdata_t &inputdata ); + void InputSetCurrentCase( inputdata_t &inputdata ); + void InputSetCurrentCaseNoFire( inputdata_t &inputdata ); + void InputIncrementSequence( inputdata_t &inputdata ); + void InputResetSequence( inputdata_t &inputdata ); + + // Outputs + COutputInt m_CurCase; + COutputString m_OnCasePass; + COutputString m_OnCaseFail; + COutputString m_OnSequenceComplete; + + DECLARE_DATADESC(); +}; + +LINK_ENTITY_TO_CLASS( logic_sequence, CLogicSequence ); + + +BEGIN_DATADESC( CLogicSequence ) + + // Keys + DEFINE_KEYFIELD( m_iszCase[0], FIELD_STRING, "Case01" ), + DEFINE_KEYFIELD( m_iszCase[1], FIELD_STRING, "Case02" ), + DEFINE_KEYFIELD( m_iszCase[2], FIELD_STRING, "Case03" ), + DEFINE_KEYFIELD( m_iszCase[3], FIELD_STRING, "Case04" ), + DEFINE_KEYFIELD( m_iszCase[4], FIELD_STRING, "Case05" ), + DEFINE_KEYFIELD( m_iszCase[5], FIELD_STRING, "Case06" ), + DEFINE_KEYFIELD( m_iszCase[6], FIELD_STRING, "Case07" ), + DEFINE_KEYFIELD( m_iszCase[7], FIELD_STRING, "Case08" ), + DEFINE_KEYFIELD( m_iszCase[8], FIELD_STRING, "Case09" ), + DEFINE_KEYFIELD( m_iszCase[9], FIELD_STRING, "Case10" ), + DEFINE_KEYFIELD( m_iszCase[10], FIELD_STRING, "Case11" ), + DEFINE_KEYFIELD( m_iszCase[11], FIELD_STRING, "Case12" ), + DEFINE_KEYFIELD( m_iszCase[12], FIELD_STRING, "Case13" ), + DEFINE_KEYFIELD( m_iszCase[13], FIELD_STRING, "Case14" ), + DEFINE_KEYFIELD( m_iszCase[14], FIELD_STRING, "Case15" ), + DEFINE_KEYFIELD( m_iszCase[15], FIELD_STRING, "Case16" ), + + // This doesn't need to be saved, it can be assigned every Activate() + //DEFINE_FIELD( m_iNumCases, FIELD_INTEGER ), + + DEFINE_KEYFIELD( m_bDisabled, FIELD_BOOLEAN, "StartDisabled" ), + + DEFINE_KEYFIELD( m_bDontIncrementOnPass, FIELD_BOOLEAN, "DontIncrementOnPass" ), + + // Inputs + DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ), + DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ), + DEFINE_INPUTFUNC( FIELD_VOID, "Toggle", InputToggle ), + DEFINE_INPUTFUNC( FIELD_STRING, "InValue", InputInValue ), + DEFINE_INPUTFUNC( FIELD_INTEGER, "SetCurrentCase", InputSetCurrentCase ), + DEFINE_INPUTFUNC( FIELD_INTEGER, "SetCurrentCaseNoFire", InputSetCurrentCaseNoFire ), + DEFINE_INPUTFUNC( FIELD_INTEGER, "IncrementSequence", InputIncrementSequence ), + DEFINE_INPUTFUNC( FIELD_VOID, "ResetSequence", InputResetSequence ), + + // Outputs + DEFINE_OUTPUT( m_CurCase, "OutCurCase" ), + DEFINE_OUTPUT( m_OnCasePass, "OnCasePass" ), + DEFINE_OUTPUT( m_OnCaseFail, "OnCaseFail" ), + DEFINE_OUTPUT( m_OnSequenceComplete, "OnSequenceComplete" ), + +END_DATADESC() + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CLogicSequence::CLogicSequence() +{ + m_CurCase.Init( 1 ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CLogicSequence::Activate( void ) +{ + BaseClass::Activate(); + + // Count number of cases + for (m_iNumCases = 0; m_iNumCases < MAX_SEQUENCE_CASES; m_iNumCases++) + { + if (m_iszCase[m_iNumCases] == NULL_STRING) + break; + } +} + +//----------------------------------------------------------------------------- +// Purpose: Cache user entity field values until spawn is called. +// Input : szKeyName - Key to handle. +// szValue - Value for key. +// Output : Returns true if the key was handled, false if not. +//----------------------------------------------------------------------------- +bool CLogicSequence::KeyValue( const char *szKeyName, const char *szValue ) +{ + if (FStrEq( szKeyName, "StartCase" )) + { + m_CurCase.Init( atoi(szValue) ); + } + else + return BaseClass::KeyValue( szKeyName, szValue ); + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CLogicSequence::TestCase( int iCase, string_t iszValue, CBaseEntity *pActivator ) +{ + if (m_bDisabled) + { + DevMsg("%s ignoring case test because it is disabled\n", GetDebugName()); + return; + } + + // Arrays are 0-based, so the index is (iCase - 1) + int iIndex = iCase - 1; + if (iIndex >= m_iNumCases) + { + DevMsg("%s ignoring case test because the current case %i is greater than or equal to the number of cases %i\n", GetDebugName(), iCase, m_iNumCases); + return; + } + + if (Matcher_Match( STRING( m_iszCase[iIndex] ), STRING(iszValue) )) + { + m_OnCasePass.Set( iszValue, pActivator, this ); + + if (!m_bDontIncrementOnPass) + { + m_CurCase.Set(iCase + 1, pActivator, this); + + if (m_CurCase.Get() > m_iNumCases) + { + // Sequence complete! + SequenceComplete(iszValue, pActivator); + } + } + else + { + m_CurCase.Set(iCase, pActivator, this); + } + } + else + { + m_OnCaseFail.Set( iszValue, pActivator, this ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CLogicSequence::SequenceComplete( string_t iszValue, CBaseEntity *pActivator ) +{ + m_OnSequenceComplete.Set( iszValue, pActivator, this ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CLogicSequence::InputEnable( inputdata_t &inputdata ) +{ + m_bDisabled = false; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CLogicSequence::InputDisable( inputdata_t &inputdata ) +{ + m_bDisabled = true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CLogicSequence::InputToggle( inputdata_t &inputdata ) +{ + m_bDisabled = (m_bDisabled == false); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CLogicSequence::InputInValue( inputdata_t &inputdata ) +{ + TestCase( m_CurCase.Get(), inputdata.value.StringID(), inputdata.pActivator ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CLogicSequence::InputSetCurrentCase( inputdata_t &inputdata ) +{ + m_CurCase.Set( inputdata.value.Int(), inputdata.pActivator, this ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CLogicSequence::InputSetCurrentCaseNoFire( inputdata_t &inputdata ) +{ + m_CurCase.Init( inputdata.value.Int() ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CLogicSequence::InputIncrementSequence( inputdata_t &inputdata ) +{ + int iInc = inputdata.value.Int(); + m_CurCase.Set( m_CurCase.Get() + (iInc != 0 ? iInc : 1), inputdata.pActivator, this ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CLogicSequence::InputResetSequence( inputdata_t &inputdata ) +{ + m_CurCase.Set( 1, inputdata.pActivator, this ); +} + +//----------------------------------------------------------------------------- +// Purpose: Generates various types of numbers based on existing material proxies +//----------------------------------------------------------------------------- +class CMathGenerate : public CLogicalEntity +{ +public: + DECLARE_CLASS( CMathGenerate, CLogicalEntity ); + CMathGenerate(); + + enum GenerateType_t + { + GENERATE_SINE_WAVE, + GENERATE_LINEAR_RAMP, + GENERATE_UNIFORM_NOISE, + GENERATE_GAUSSIAN_NOISE, + GENERATE_EXPONENTIAL, + }; + + // Keys + float m_flMax; + float m_flMin; + + float m_flParam1; + float m_flParam2; + + bool m_bDisabled; + + GenerateType_t m_iGenerateType; + + // Inputs + void InputSetValue( inputdata_t &inputdata ); + void InputSetValueNoFire( inputdata_t &inputdata ); + void InputGetValue( inputdata_t &inputdata ); + void InputSetGenerateType( inputdata_t &inputdata ); + + void InputEnable( inputdata_t &inputdata ); + void InputDisable( inputdata_t &inputdata ); + void InputToggle( inputdata_t &inputdata ); + + void UpdateOutValue( float fNewValue, CBaseEntity *pActivator = NULL ); + void UpdateOutValueSine( float fNewValue, CBaseEntity *pActivator = NULL ); + + // Basic functions + void Spawn(); + bool KeyValue( const char *szKeyName, const char *szValue ); + + void StartGenerating(); + void StopGenerating(); + + // Number generation functions + void GenerateSineWave(); + void GenerateLinearRamp(); + void GenerateUniformNoise(); + void GenerateGaussianNoise(); + void GenerateExponential(); + + // The gaussian stream normally only exists on the client, so we use our own. + static CGaussianRandomStream m_GaussianStream; + + bool m_bHitMin; // Set when we reach or go below our minimum value, cleared if we go above it again. + bool m_bHitMax; // Set when we reach or exceed our maximum value, cleared if we fall below it again. + + // Outputs + COutputFloat m_OutValue; + COutputFloat m_OnGetValue; // Used for polling the counter value. + COutputEvent m_OnHitMin; + COutputEvent m_OnHitMax; + COutputEvent m_OnChangedFromMin; + COutputEvent m_OnChangedFromMax; + + DECLARE_DATADESC(); +}; + +LINK_ENTITY_TO_CLASS( math_generate, CMathGenerate ); + + +BEGIN_DATADESC( CMathGenerate ) + + DEFINE_INPUT( m_flMax, FIELD_FLOAT, "SetHitMax" ), + DEFINE_INPUT( m_flMin, FIELD_FLOAT, "SetHitMin" ), + DEFINE_INPUT( m_flParam1, FIELD_FLOAT, "SetParam1" ), + DEFINE_INPUT( m_flParam2, FIELD_FLOAT, "SetParam2" ), + DEFINE_KEYFIELD( m_bDisabled, FIELD_BOOLEAN, "StartDisabled" ), + + DEFINE_KEYFIELD( m_iGenerateType, FIELD_INTEGER, "GenerateType" ), + + DEFINE_FIELD( m_bHitMax, FIELD_BOOLEAN ), + DEFINE_FIELD( m_bHitMin, FIELD_BOOLEAN ), + + DEFINE_INPUTFUNC( FIELD_FLOAT, "SetValue", InputSetValue ), + DEFINE_INPUTFUNC( FIELD_FLOAT, "SetValueNoFire", InputSetValueNoFire ), + DEFINE_INPUTFUNC( FIELD_VOID, "GetValue", InputGetValue ), + DEFINE_INPUTFUNC( FIELD_INTEGER, "SetGenerateType", InputSetGenerateType ), + + DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ), + DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ), + DEFINE_INPUTFUNC( FIELD_VOID, "Toggle", InputToggle ), + + DEFINE_OUTPUT( m_OutValue, "OutValue" ), + DEFINE_OUTPUT( m_OnHitMin, "OnHitMin" ), + DEFINE_OUTPUT( m_OnHitMax, "OnHitMax" ), + DEFINE_OUTPUT( m_OnGetValue, "OnGetValue" ), + DEFINE_OUTPUT( m_OnChangedFromMin, "OnChangedFromMin" ), + DEFINE_OUTPUT( m_OnChangedFromMax, "OnChangedFromMax" ), + + DEFINE_THINKFUNC( GenerateSineWave ), + DEFINE_THINKFUNC( GenerateLinearRamp ), + DEFINE_THINKFUNC( GenerateUniformNoise ), + DEFINE_THINKFUNC( GenerateGaussianNoise ), + DEFINE_THINKFUNC( GenerateExponential ), + +END_DATADESC() + +CGaussianRandomStream CMathGenerate::m_GaussianStream; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CMathGenerate::CMathGenerate() +{ + m_GaussianStream.AttachToStream( random ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CMathGenerate::Spawn() +{ + BaseClass::Spawn(); + + if (!m_bDisabled) + StartGenerating(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CMathGenerate::KeyValue( const char *szKeyName, const char *szValue ) +{ + if (FStrEq( szKeyName, "InitialValue" )) + { + m_OutValue.Init( atof(szValue) ); + } + else + return BaseClass::KeyValue( szKeyName, szValue ); + + return true; +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CMathGenerate::InputSetValue( inputdata_t &inputdata ) +{ + UpdateOutValue(inputdata.value.Float(), inputdata.pActivator); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CMathGenerate::InputSetValueNoFire( inputdata_t &inputdata ) +{ + m_OutValue.Init(inputdata.value.Float()); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CMathGenerate::InputGetValue( inputdata_t &inputdata ) +{ + float flOutValue = m_OutValue.Get(); + m_OnGetValue.Set( flOutValue, inputdata.pActivator, inputdata.pCaller ); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CMathGenerate::InputSetGenerateType( inputdata_t &inputdata ) +{ + m_iGenerateType = (GenerateType_t)inputdata.value.Int(); + + if (GetNextThink() != TICK_NEVER_THINK) + { + // Change our generation function if we're already generating. + // StartGenerating() should set to the new function. + StartGenerating(); + } +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CMathGenerate::InputEnable( inputdata_t &inputdata ) +{ + m_bDisabled = false; + StartGenerating(); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CMathGenerate::InputDisable( inputdata_t &inputdata ) +{ + m_bDisabled = true; + StopGenerating(); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CMathGenerate::InputToggle( inputdata_t &inputdata ) +{ + m_bDisabled ? InputEnable(inputdata) : InputDisable(inputdata); +} + +//----------------------------------------------------------------------------- +// Purpose: Sets the value to the new value, clamping and firing the output value. +// Input : fNewValue - Value to set. +//----------------------------------------------------------------------------- +void CMathGenerate::UpdateOutValue( float fNewValue, CBaseEntity *pActivator ) +{ + if ((m_flMin != 0) || (m_flMax != 0)) + { + // + // Fire an output any time we reach or exceed our maximum value. + // + if ( fNewValue >= m_flMax || (m_iGenerateType == GENERATE_SINE_WAVE && fNewValue >= (m_flMax * 0.995f)) ) + { + if ( !m_bHitMax ) + { + m_bHitMax = true; + m_OnHitMax.FireOutput( pActivator, this ); + } + } + else + { + // Fire an output if we just changed from the maximum value + if ( m_OutValue.Get() == m_flMax ) + { + m_OnChangedFromMax.FireOutput( pActivator, this ); + } + + m_bHitMax = false; + } + + // + // Fire an output any time we reach or go below our minimum value. + // + if ( fNewValue <= m_flMin ) + { + if ( !m_bHitMin ) + { + m_bHitMin = true; + m_OnHitMin.FireOutput( pActivator, this ); + } + } + else + { + // Fire an output if we just changed from the maximum value + if ( m_OutValue.Get() == m_flMin ) + { + m_OnChangedFromMin.FireOutput( pActivator, this ); + } + + m_bHitMin = false; + } + + fNewValue = clamp(fNewValue, m_flMin, m_flMax); + } + + m_OutValue.Set(fNewValue, pActivator, this); +} + +//----------------------------------------------------------------------------- +// Purpose: Sets the value to the new value, clamping and firing the output value. +// Sine generation needs to use a different function to account for skips and imprecision. +// Input : fNewValue - Value to set. +//----------------------------------------------------------------------------- +void CMathGenerate::UpdateOutValueSine( float fNewValue, CBaseEntity *pActivator ) +{ + if ((m_flMin != 0) || (m_flMax != 0)) + { + // + // Fire an output any time we reach or exceed our maximum value. + // + if ( fNewValue >= (m_flMax * 0.995f) ) + { + if ( !m_bHitMax ) + { + m_bHitMax = true; + m_OnHitMax.FireOutput( pActivator, this ); + } + } + else + { + // Fire an output if we just changed from the maximum value + if ( m_bHitMax ) + { + m_OnChangedFromMax.FireOutput( pActivator, this ); + } + + m_bHitMax = false; + } + + // + // Fire an output any time we reach or go below our minimum value. + // + if ( fNewValue <= (m_flMin * 1.005f) ) + { + if ( !m_bHitMin ) + { + m_bHitMin = true; + m_OnHitMin.FireOutput( pActivator, this ); + } + } + else + { + // Fire an output if we just changed from the maximum value + if ( m_bHitMin ) + { + m_OnChangedFromMin.FireOutput( pActivator, this ); + } + + m_bHitMin = false; + } + + //fNewValue = clamp(fNewValue, m_flMin, m_flMax); + } + + m_OutValue.Set(fNewValue, pActivator, this); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CMathGenerate::StartGenerating() +{ + // Correct any min/max quirks here + if (m_flMin > m_flMax) + { + float flTemp = m_flMin; + m_flMin = m_flMax; + m_flMax = flTemp; + } + + switch (m_iGenerateType) + { + case GENERATE_SINE_WAVE: + SetThink( &CMathGenerate::GenerateSineWave ); + break; + case GENERATE_LINEAR_RAMP: + SetThink( &CMathGenerate::GenerateLinearRamp ); + break; + case GENERATE_UNIFORM_NOISE: + SetThink( &CMathGenerate::GenerateUniformNoise ); + break; + case GENERATE_GAUSSIAN_NOISE: + SetThink( &CMathGenerate::GenerateGaussianNoise ); + break; + case GENERATE_EXPONENTIAL: + SetThink( &CMathGenerate::GenerateExponential ); + break; + + default: + Warning("%s is set to invalid generation type %i! It won't do anything now.\n", GetDebugName(), m_iGenerateType); + StopGenerating(); + return; + } + + // All valid types should fall through to this + SetNextThink( gpGlobals->curtime + TICK_INTERVAL ); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CMathGenerate::StopGenerating() +{ + SetThink(NULL); + SetNextThink( TICK_NEVER_THINK ); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CMathGenerate::GenerateSineWave() +{ + // CSineProxy in mathproxy.cpp + float flSineTimeOffset = m_flParam2; + float flSinePeriod = m_flParam1; + float flValue; + + if (flSinePeriod == 0) + flSinePeriod = 1; + + // get a value in [0,1] + flValue = ( sin( 2.0f * M_PI * (gpGlobals->curtime - flSineTimeOffset) / flSinePeriod ) * 0.5f ) + 0.5f; + // get a value in [min,max] + flValue = ( m_flMax - m_flMin ) * flValue + m_flMin; + + UpdateOutValueSine( flValue ); + + SetNextThink( gpGlobals->curtime + TICK_INTERVAL ); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CMathGenerate::GenerateLinearRamp() +{ + // CLinearRampProxy in mathproxy.cpp + + // Param1 = rate + float flVal = m_flParam1 * gpGlobals->curtime + m_OutValue.Get(); + + // clamp + if (flVal < m_flMin) + flVal = m_flMin; + else if (flVal > m_flMax) + flVal = m_flMax; + + UpdateOutValue( flVal ); + + SetNextThink( gpGlobals->curtime + TICK_INTERVAL ); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CMathGenerate::GenerateUniformNoise() +{ + // CUniformNoiseProxy in mathproxy.cpp + + UpdateOutValue( random->RandomFloat( m_flMin, m_flMax ) ); + + SetNextThink( gpGlobals->curtime + TICK_INTERVAL ); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CMathGenerate::GenerateGaussianNoise() +{ + // CGaussianNoiseProxy in mathproxy.cpp + + float flMean = m_flParam1; + float flStdDev = m_flParam2; + float flVal = m_GaussianStream.RandomFloat( flMean, flStdDev ); + + // clamp + if (flVal < m_flMin) + flVal = m_flMin; + else if (flVal > m_flMax) + flVal = m_flMax; + + UpdateOutValue( flVal ); + + SetNextThink( gpGlobals->curtime + TICK_INTERVAL ); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CMathGenerate::GenerateExponential() +{ + // CExponentialProxy in mathproxy.cpp + + // Param1 = scale + // Param2 = offset + float flVal = m_flParam1 * exp( m_OutValue.Get() + m_flParam2 ); + + // clamp + if (flVal < m_flMin) + flVal = m_flMin; + else if (flVal > m_flMax) + flVal = m_flMax; + + UpdateOutValue( flVal ); + + SetNextThink( gpGlobals->curtime + TICK_INTERVAL ); +} +#endif diff --git a/src/game/server/logicrelay.cpp b/src/game/server/logicrelay.cpp index b556ec04..6daa15f4 100644 --- a/src/game/server/logicrelay.cpp +++ b/src/game/server/logicrelay.cpp @@ -15,6 +15,10 @@ #include "eventqueue.h" #include "soundent.h" #include "logicrelay.h" +#ifdef MAPBASE +#include "mapbase/variant_tools.h" +#include "saverestore_utlvector.h" +#endif // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" @@ -29,6 +33,11 @@ BEGIN_DATADESC( CLogicRelay ) DEFINE_FIELD(m_bWaitForRefire, FIELD_BOOLEAN), DEFINE_KEYFIELD(m_bDisabled, FIELD_BOOLEAN, "StartDisabled"), +#if RELAY_QUEUE_SYSTEM + DEFINE_KEYFIELD(m_bQueueTrigger, FIELD_BOOLEAN, "QueueDisabledTrigger"), + DEFINE_FIELD(m_bQueueWaiting, FIELD_BOOLEAN), + DEFINE_FIELD(m_flRefireTime, FIELD_TIME), +#endif // Inputs DEFINE_INPUTFUNC(FIELD_VOID, "Enable", InputEnable), @@ -36,10 +45,16 @@ BEGIN_DATADESC( CLogicRelay ) DEFINE_INPUTFUNC(FIELD_VOID, "Disable", InputDisable), DEFINE_INPUTFUNC(FIELD_VOID, "Toggle", InputToggle), DEFINE_INPUTFUNC(FIELD_VOID, "Trigger", InputTrigger), +#ifdef MAPBASE + DEFINE_INPUTFUNC(FIELD_INPUT, "TriggerWithParameter", InputTriggerWithParameter), +#endif DEFINE_INPUTFUNC(FIELD_VOID, "CancelPending", InputCancelPending), // Outputs DEFINE_OUTPUT(m_OnTrigger, "OnTrigger"), +#ifdef MAPBASE + DEFINE_OUTPUT(m_OnTriggerParameter, "OnTriggerParameter"), +#endif DEFINE_OUTPUT(m_OnSpawn, "OnSpawn"), END_DATADESC() @@ -93,6 +108,11 @@ void CLogicRelay::Think() void CLogicRelay::InputEnable( inputdata_t &inputdata ) { m_bDisabled = false; + +#if RELAY_QUEUE_SYSTEM + if (m_bQueueWaiting) + m_OnTrigger.FireOutput( inputdata.pActivator, this ); +#endif } //------------------------------------------------------------------------------ @@ -132,6 +152,11 @@ void CLogicRelay::InputDisable( inputdata_t &inputdata ) void CLogicRelay::InputToggle( inputdata_t &inputdata ) { m_bDisabled = !m_bDisabled; + +#if RELAY_QUEUE_SYSTEM + if (m_bQueueWaiting) + m_OnTrigger.FireOutput( inputdata.pActivator, this ); +#endif } @@ -144,6 +169,45 @@ void CLogicRelay::InputTrigger( inputdata_t &inputdata ) { m_OnTrigger.FireOutput( inputdata.pActivator, this ); + if (m_spawnflags & SF_REMOVE_ON_FIRE) + { + UTIL_Remove(this); + } + else if (!(m_spawnflags & SF_ALLOW_FAST_RETRIGGER)) + { + // + // Disable the relay so that it cannot be refired until after the last output + // has been fired and post an input to re-enable ourselves. + // + m_bWaitForRefire = true; + g_EventQueue.AddEvent(this, "EnableRefire", m_OnTrigger.GetMaxDelay() + 0.001, this, this); +#if RELAY_QUEUE_SYSTEM + if (m_bQueueTrigger) + m_flRefireTime = gpGlobals->curtime + m_OnTrigger.GetMaxDelay() + 0.002; +#endif + } + } +#if RELAY_QUEUE_SYSTEM + else if (m_bQueueTrigger) + { + if (m_bDisabled) + m_bQueueWaiting = true; + else // m_bWaitForRefire + m_OnTrigger.FireOutput( inputdata.pActivator, this, (gpGlobals->curtime - m_flRefireTime) ); + } +#endif +} + +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: Input handler that triggers the relay. +//----------------------------------------------------------------------------- +void CLogicRelay::InputTriggerWithParameter( inputdata_t &inputdata ) +{ + if ((!m_bDisabled) && (!m_bWaitForRefire)) + { + m_OnTriggerParameter.Set( inputdata.value, inputdata.pActivator, this ); + if (m_spawnflags & SF_REMOVE_ON_FIRE) { UTIL_Remove(this); @@ -159,4 +223,258 @@ void CLogicRelay::InputTrigger( inputdata_t &inputdata ) } } } +#endif + +#ifdef MAPBASE + +BEGIN_SIMPLE_DATADESC( LogicRelayQueueInfo_t ) + + DEFINE_FIELD( TriggerWithParameter, FIELD_BOOLEAN ), + DEFINE_FIELD( pActivator, FIELD_CLASSPTR ), + DEFINE_VARIANT( value ), + DEFINE_FIELD( outputID, FIELD_INTEGER ), + +END_DATADESC() + +LINK_ENTITY_TO_CLASS(logic_relay_queue, CLogicRelayQueue); + + +BEGIN_DATADESC( CLogicRelayQueue ) + + DEFINE_FIELD(m_bWaitForRefire, FIELD_BOOLEAN), + DEFINE_KEYFIELD(m_bDisabled, FIELD_BOOLEAN, "StartDisabled"), + + DEFINE_INPUT(m_iMaxQueueItems, FIELD_INTEGER, "SetMaxQueueItems"), + DEFINE_KEYFIELD(m_bDontQueueWhenDisabled, FIELD_BOOLEAN, "DontQueueWhenDisabled"), + + // Inputs + DEFINE_INPUTFUNC(FIELD_VOID, "Enable", InputEnable), + DEFINE_INPUTFUNC(FIELD_VOID, "EnableRefire", InputEnableRefire), + DEFINE_INPUTFUNC(FIELD_VOID, "Disable", InputDisable), + DEFINE_INPUTFUNC(FIELD_VOID, "Toggle", InputToggle), + DEFINE_INPUTFUNC(FIELD_VOID, "Trigger", InputTrigger), + DEFINE_INPUTFUNC(FIELD_INPUT, "TriggerWithParameter", InputTriggerWithParameter), + DEFINE_INPUTFUNC(FIELD_VOID, "CancelPending", InputCancelPending), + DEFINE_INPUTFUNC(FIELD_VOID, "ClearQueue", InputClearQueue), + + // Outputs + DEFINE_OUTPUT(m_OnTrigger, "OnTrigger"), + DEFINE_OUTPUT(m_OnTriggerParameter, "OnTriggerParameter"), + + DEFINE_UTLVECTOR(m_QueueItems, FIELD_EMBEDDED), + +END_DATADESC() + +//----------------------------------------------------------------------------- +// Purpose: Constructor. +//----------------------------------------------------------------------------- +CLogicRelayQueue::CLogicRelayQueue(void) +{ +} + + +//------------------------------------------------------------------------------ +// Purpose: Turns on the relay, allowing it to fire outputs. +//------------------------------------------------------------------------------ +void CLogicRelayQueue::InputEnable( inputdata_t &inputdata ) +{ + m_bDisabled = false; + + if (!m_bWaitForRefire && m_QueueItems.Count() > 0) + HandleNextQueueItem(); +} + +//------------------------------------------------------------------------------ +// Purpose: Enables us to fire again. This input is only posted from our Trigger +// function to prevent rapid refire. +//------------------------------------------------------------------------------ +void CLogicRelayQueue::InputEnableRefire( inputdata_t &inputdata ) +{ + m_bWaitForRefire = false; + + if (!m_bDisabled && m_QueueItems.Count() > 0) + HandleNextQueueItem(); +} + + +//------------------------------------------------------------------------------ +// Purpose: Cancels any I/O events in the queue that were fired by us. +//------------------------------------------------------------------------------ +void CLogicRelayQueue::InputCancelPending( inputdata_t &inputdata ) +{ + g_EventQueue.CancelEvents( this ); + + // Stop waiting; allow another Trigger. + m_bWaitForRefire = false; + + if (!m_bDisabled && m_QueueItems.Count() > 0) + HandleNextQueueItem(); +} + + +//------------------------------------------------------------------------------ +// Purpose: Clears the queue. +//------------------------------------------------------------------------------ +void CLogicRelayQueue::InputClearQueue( inputdata_t &inputdata ) +{ + m_QueueItems.RemoveAll(); +} + + +//------------------------------------------------------------------------------ +// Purpose: Turns off the relay, preventing it from firing outputs. +//------------------------------------------------------------------------------ +void CLogicRelayQueue::InputDisable( inputdata_t &inputdata ) +{ + m_bDisabled = true; +} + + +//------------------------------------------------------------------------------ +// Purpose: Toggles the enabled/disabled state of the relay. +//------------------------------------------------------------------------------ +void CLogicRelayQueue::InputToggle( inputdata_t &inputdata ) +{ + m_bDisabled = !m_bDisabled; + + if (!m_bDisabled && !m_bWaitForRefire && m_QueueItems.Count() > 0) + HandleNextQueueItem(); +} + + +//----------------------------------------------------------------------------- +// Purpose: Input handler that triggers the relay. +//----------------------------------------------------------------------------- +void CLogicRelayQueue::InputTrigger( inputdata_t &inputdata ) +{ + if ((!m_bDisabled) && (!m_bWaitForRefire)) + { + m_OnTrigger.FireOutput( inputdata.pActivator, this ); + + // + // Disable the relay so that it cannot be refired until after the last output + // has been fired and post an input to re-enable ourselves. + // + m_bWaitForRefire = true; + g_EventQueue.AddEvent(this, "EnableRefire", m_OnTrigger.GetMaxDelay() + 0.001, this, this); + } + else if ( (!m_bDisabled || !m_bDontQueueWhenDisabled) && (m_QueueItems.Count() < m_iMaxQueueItems) ) + { + AddQueueItem(inputdata.pActivator, inputdata.nOutputID); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Input handler that triggers the relay. +//----------------------------------------------------------------------------- +void CLogicRelayQueue::InputTriggerWithParameter( inputdata_t &inputdata ) +{ + if ((!m_bDisabled) && (!m_bWaitForRefire)) + { + m_OnTriggerParameter.Set( inputdata.value, inputdata.pActivator, this ); + + // + // Disable the relay so that it cannot be refired until after the last output + // has been fired and post an input to re-enable ourselves. + // + m_bWaitForRefire = true; + g_EventQueue.AddEvent(this, "EnableRefire", m_OnTrigger.GetMaxDelay() + 0.001, this, this); + } + else if ( (!m_bDisabled || !m_bDontQueueWhenDisabled) && (m_QueueItems.Count() < m_iMaxQueueItems) ) + { + AddQueueItem(inputdata.pActivator, inputdata.nOutputID, inputdata.value); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Handles next queue item. +//----------------------------------------------------------------------------- +void CLogicRelayQueue::HandleNextQueueItem() +{ + LogicRelayQueueInfo_t info = m_QueueItems.Element(0); + + //if (!info.TriggerWithParameter) + //{ + // m_OnTrigger.FireOutput(info.pActivator, this); + //} + //else + //{ + // m_OnTriggerParameter.Set(info.value, info.pActivator, this); + //} + + AcceptInput(info.TriggerWithParameter ? "TriggerWithParameter" : "Trigger", info.pActivator, this, info.value, info.outputID); + + m_QueueItems.Remove(0); +} + +//----------------------------------------------------------------------------- +// Purpose: Adds a queue item. +//----------------------------------------------------------------------------- +void CLogicRelayQueue::AddQueueItem(CBaseEntity *pActivator, int outputID, variant_t &value) +{ + LogicRelayQueueInfo_t info; + info.pActivator = pActivator; + info.outputID = outputID; + + info.value = value; + info.TriggerWithParameter = true; + + m_QueueItems.AddToTail(info); +} + +//----------------------------------------------------------------------------- +// Purpose: Adds a queue item without a parameter. +//----------------------------------------------------------------------------- +void CLogicRelayQueue::AddQueueItem(CBaseEntity *pActivator, int outputID) +{ + LogicRelayQueueInfo_t info; + info.pActivator = pActivator; + info.outputID = outputID; + + info.TriggerWithParameter = false; + + m_QueueItems.AddToTail(info); +} + +//----------------------------------------------------------------------------- +// Purpose: Draw any debug text overlays +// Output : Current text offset from the top +//----------------------------------------------------------------------------- +int CLogicRelayQueue::DrawDebugTextOverlays(void) +{ + int text_offset = BaseClass::DrawDebugTextOverlays(); + + if (m_debugOverlays & OVERLAY_TEXT_BIT) + { + // -------------- + // Print Target + // -------------- + char tempstr[255]; + + if (m_QueueItems.Count() > 0) + { + Q_snprintf(tempstr, sizeof(tempstr), "Queue Items: %i (%i)", m_QueueItems.Count(), m_iMaxQueueItems); + EntityText(text_offset, tempstr, 0); + text_offset++; + + for (int i = 0; i < m_QueueItems.Count(); i++) + { + Q_snprintf(tempstr, sizeof(tempstr), " Input: %s, Activator: %s, Output ID: %i", + m_QueueItems[i].TriggerWithParameter ? "TriggerWithParameter" : "Trigger", + m_QueueItems[i].pActivator ? m_QueueItems[i].pActivator->GetDebugName() : "None", + m_QueueItems[i].outputID); + EntityText(text_offset, tempstr, 0); + text_offset++; + } + } + else + { + Q_snprintf(tempstr, sizeof(tempstr), "Queue Items: 0 (%i)", m_iMaxQueueItems); + EntityText(text_offset, tempstr, 0); + text_offset++; + } + } + return text_offset; +} +#endif diff --git a/src/game/server/logicrelay.h b/src/game/server/logicrelay.h index 8d082acc..00791579 100644 --- a/src/game/server/logicrelay.h +++ b/src/game/server/logicrelay.h @@ -13,6 +13,13 @@ #include "entityoutput.h" #include "eventqueue.h" +#ifdef MAPBASE + +// I was originally going to add something similar to that queue thing to logic_relay directly, but I later decided to relegate it to a derivative entity. +#define RELAY_QUEUE_SYSTEM 0 + +#endif + class CLogicRelay : public CLogicalEntity { public: @@ -25,16 +32,26 @@ public: // Input handlers void InputEnable( inputdata_t &inputdata ); +#ifdef MAPBASE + void InputEnableRefire( inputdata_t &inputdata ); +#else void InputEnableRefire( inputdata_t &inputdata ); // Private input handler, not in FGD +#endif void InputDisable( inputdata_t &inputdata ); void InputToggle( inputdata_t &inputdata ); void InputTrigger( inputdata_t &inputdata ); +#ifdef MAPBASE + void InputTriggerWithParameter( inputdata_t &inputdata ); +#endif void InputCancelPending( inputdata_t &inputdata ); DECLARE_DATADESC(); // Outputs COutputEvent m_OnTrigger; +#ifdef MAPBASE + COutputVariant m_OnTriggerParameter; +#endif COutputEvent m_OnSpawn; bool IsDisabled( void ){ return m_bDisabled; } @@ -43,6 +60,66 @@ private: bool m_bDisabled; bool m_bWaitForRefire; // Set to disallow a refire while we are waiting for our outputs to finish firing. +#if RELAY_QUEUE_SYSTEM + bool m_bQueueTrigger; + bool m_bQueueWaiting; + float m_flRefireTime; +#endif }; +#ifdef MAPBASE +struct LogicRelayQueueInfo_t +{ + DECLARE_SIMPLE_DATADESC(); + + bool TriggerWithParameter; + CBaseEntity *pActivator; + variant_t value; + int outputID; +}; + +//#define LOGIC_RELAY_QUEUE_LIMIT 16 + +class CLogicRelayQueue : public CLogicalEntity +{ +public: + DECLARE_CLASS( CLogicRelayQueue, CLogicalEntity ); + + CLogicRelayQueue(); + + // Input handlers + void InputEnable( inputdata_t &inputdata ); + void InputEnableRefire( inputdata_t &inputdata ); + void InputDisable( inputdata_t &inputdata ); + void InputToggle( inputdata_t &inputdata ); + void InputTrigger( inputdata_t &inputdata ); + void InputTriggerWithParameter( inputdata_t &inputdata ); + void InputCancelPending( inputdata_t &inputdata ); + void InputClearQueue( inputdata_t &inputdata ); + + DECLARE_DATADESC(); + + // Outputs + COutputEvent m_OnTrigger; + COutputVariant m_OnTriggerParameter; + + bool IsDisabled( void ){ return m_bDisabled; } + + void HandleNextQueueItem(); + void AddQueueItem(CBaseEntity *pActivator, int outputID, variant_t &value); + void AddQueueItem(CBaseEntity *pActivator, int outputID); + + int DrawDebugTextOverlays( void ); + +private: + + bool m_bDisabled; + bool m_bWaitForRefire; // Set to disallow a refire while we are waiting for our outputs to finish firing. + + int m_iMaxQueueItems; + bool m_bDontQueueWhenDisabled; // Don't add to queue while disabled, only when waiting for refire + CUtlVector m_QueueItems; +}; +#endif + #endif //LOGICRELAY_H diff --git a/src/game/server/mapbase/GlobalStrings.cpp b/src/game/server/mapbase/GlobalStrings.cpp new file mode 100644 index 00000000..4abd6f63 --- /dev/null +++ b/src/game/server/mapbase/GlobalStrings.cpp @@ -0,0 +1,100 @@ +//========= Mapbase - https://github.com/mapbase-source/source-sdk-2013 ================== +// +// Purpose: See GlobalStrings.h for more information. +// +// $NoKeywords: $ +//============================================================================= + +#include "cbase.h" +#include "GlobalStrings.h" + + +// Global strings must be initially declared here. +// Be sure to sync them with the externs in GlobalStrings.h. + +// ------------------------------------------------------------- +// +// Classnames +// +// ------------------------------------------------------------- + +#ifdef HL2_DLL +string_t gm_isz_class_Shotgun; +string_t gm_isz_class_SMG1; +string_t gm_isz_class_AR2; +string_t gm_isz_class_Pistol; +string_t gm_isz_class_Stunstick; +string_t gm_isz_class_Crowbar; +string_t gm_isz_class_RPG; +string_t gm_isz_class_357; +string_t gm_isz_class_Grenade; +string_t gm_isz_class_Physcannon; +string_t gm_isz_class_Crossbow; + +string_t gm_isz_class_Strider; +string_t gm_isz_class_Gunship; +string_t gm_isz_class_Dropship; +string_t gm_isz_class_FloorTurret; +string_t gm_isz_class_CScanner; +string_t gm_isz_class_ClawScanner; +string_t gm_isz_class_Rollermine; +#endif + +string_t gm_isz_class_Bullseye; + +string_t gm_isz_class_PropPhysics; +string_t gm_isz_class_PropPhysicsOverride; +string_t gm_isz_class_FuncPhysbox; +string_t gm_isz_class_EnvFire; + +// ------------------------------------------------------------- + +string_t gm_isz_name_player; +string_t gm_isz_name_activator; + +// ------------------------------------------------------------- + +// ------------------------------------------------------------- + +// We know it hasn't been allocated yet +#define INITIALIZE_GLOBAL_STRING(string, text) string = AllocPooledString(text) //SetGlobalString(string, text) + +void InitGlobalStrings() +{ +#ifdef HL2_DLL + INITIALIZE_GLOBAL_STRING(gm_isz_class_Shotgun, "weapon_shotgun"); + INITIALIZE_GLOBAL_STRING(gm_isz_class_SMG1, "weapon_smg1"); + INITIALIZE_GLOBAL_STRING(gm_isz_class_AR2, "weapon_ar2"); + INITIALIZE_GLOBAL_STRING(gm_isz_class_Pistol, "weapon_pistol"); + INITIALIZE_GLOBAL_STRING(gm_isz_class_Stunstick, "weapon_stunstick"); + INITIALIZE_GLOBAL_STRING(gm_isz_class_Crowbar, "weapon_crowbar"); + INITIALIZE_GLOBAL_STRING(gm_isz_class_RPG, "weapon_rpg"); + INITIALIZE_GLOBAL_STRING(gm_isz_class_357, "weapon_357"); + INITIALIZE_GLOBAL_STRING(gm_isz_class_Grenade, "weapon_frag"); + INITIALIZE_GLOBAL_STRING(gm_isz_class_Physcannon, "weapon_physcannon"); + INITIALIZE_GLOBAL_STRING(gm_isz_class_Crossbow, "weapon_crossbow"); + + INITIALIZE_GLOBAL_STRING(gm_isz_class_Strider, "npc_strider"); + INITIALIZE_GLOBAL_STRING(gm_isz_class_Gunship, "npc_combinegunship"); + INITIALIZE_GLOBAL_STRING(gm_isz_class_Dropship, "npc_combinedropship"); + INITIALIZE_GLOBAL_STRING(gm_isz_class_FloorTurret, "npc_turret_floor"); + INITIALIZE_GLOBAL_STRING(gm_isz_class_CScanner, "npc_cscanner"); + INITIALIZE_GLOBAL_STRING(gm_isz_class_ClawScanner, "npc_clawscanner"); + INITIALIZE_GLOBAL_STRING(gm_isz_class_Rollermine, "npc_rollermine"); +#endif + + INITIALIZE_GLOBAL_STRING(gm_isz_class_Bullseye, "npc_bullseye"); + + INITIALIZE_GLOBAL_STRING(gm_isz_class_PropPhysics, "prop_physics"); + INITIALIZE_GLOBAL_STRING(gm_isz_class_PropPhysicsOverride, "prop_physics_override"); + INITIALIZE_GLOBAL_STRING(gm_isz_class_FuncPhysbox, "func_physbox"); + INITIALIZE_GLOBAL_STRING(gm_isz_class_EnvFire, "env_fire"); + + INITIALIZE_GLOBAL_STRING(gm_isz_name_player, "!player"); + INITIALIZE_GLOBAL_STRING(gm_isz_name_activator, "!activator"); +} + +// ------------------------------------------------------------- + + + diff --git a/src/game/server/mapbase/GlobalStrings.h b/src/game/server/mapbase/GlobalStrings.h new file mode 100644 index 00000000..13c4775a --- /dev/null +++ b/src/game/server/mapbase/GlobalStrings.h @@ -0,0 +1,86 @@ +//========= Mapbase - https://github.com/mapbase-source/source-sdk-2013 ================== +// +// Purpose: Shared global string library. +// +// $NoKeywords: $ +//============================================================================= + +#ifndef MAPBASE_GLOBAL_STRINGS_H +#define MAPBASE_GLOBAL_STRINGS_H +#ifdef _WIN32 +#pragma once +#endif + +#include "cbase.h" + +// ------------------------------------------------------------- +// +// Valve uses global pooled strings in various parts of the code, particularly Episodic code, +// so they could get away with integer/pointer comparisons instead of string comparisons. +// +// This system was developed early in Mapbase's development as an attempt to make this technique more widely used. +// For the most part, this mainly just serves to apply micro-optimize parts of the code. +// +// ------------------------------------------------------------- + +// ------------------------------------------------------------- +// +// Classnames +// +// ------------------------------------------------------------- + +#ifdef HL2_DLL +extern string_t gm_isz_class_Shotgun; +extern string_t gm_isz_class_SMG1; +extern string_t gm_isz_class_AR2; +extern string_t gm_isz_class_Pistol; +extern string_t gm_isz_class_Stunstick; +extern string_t gm_isz_class_Crowbar; +extern string_t gm_isz_class_RPG; +extern string_t gm_isz_class_357; +extern string_t gm_isz_class_Grenade; +extern string_t gm_isz_class_Physcannon; +extern string_t gm_isz_class_Crossbow; + +extern string_t gm_isz_class_Strider; +extern string_t gm_isz_class_Gunship; +extern string_t gm_isz_class_Dropship; +extern string_t gm_isz_class_FloorTurret; +extern string_t gm_isz_class_CScanner; +extern string_t gm_isz_class_ClawScanner; +extern string_t gm_isz_class_Rollermine; +#endif + +extern string_t gm_isz_class_Bullseye; + +extern string_t gm_isz_class_PropPhysics; +extern string_t gm_isz_class_PropPhysicsOverride; +extern string_t gm_isz_class_FuncPhysbox; +extern string_t gm_isz_class_EnvFire; + +// ------------------------------------------------------------- + +extern string_t gm_isz_name_player; +extern string_t gm_isz_name_activator; + +// ------------------------------------------------------------- + +// Does the classname of this entity match the string_t? +// +// This function is for comparing global strings and allows us to change how we compare them quickly. +inline bool EntIsClass( CBaseEntity *ent, string_t str2 ) +{ + //return ent->ClassMatches(str2); + + // Since classnames are pooled, the global string and the entity's classname should point to the same string in memory. + // As long as this rule is preserved, we only need a pointer comparison. A string comparison isn't necessary. + return ent->m_iClassname == str2; +} + +// ------------------------------------------------------------- + +void InitGlobalStrings(); + +// ------------------------------------------------------------- + +#endif diff --git a/src/game/server/mapbase/SystemConvarMod.cpp b/src/game/server/mapbase/SystemConvarMod.cpp new file mode 100644 index 00000000..2ff4380e --- /dev/null +++ b/src/game/server/mapbase/SystemConvarMod.cpp @@ -0,0 +1,352 @@ +//========= Mapbase - https://github.com/mapbase-source/source-sdk-2013 ============// +// +// Purpose: Mostly just Mapbase's convar mod code. +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" +#include "saverestore_utlvector.h" +#include "SystemConvarMod.h" +#include "fmtstr.h" + +ConVar g_debug_convarmod( "g_debug_convarmod", "0" ); + +BEGIN_SIMPLE_DATADESC( modifiedconvars_t ) + DEFINE_ARRAY( pszConvar, FIELD_CHARACTER, MAX_MODIFIED_CONVAR_STRING ), + DEFINE_ARRAY( pszCurrentValue, FIELD_CHARACTER, MAX_MODIFIED_CONVAR_STRING ), + DEFINE_ARRAY( pszOrgValue, FIELD_CHARACTER, MAX_MODIFIED_CONVAR_STRING ), +END_DATADESC() + + +// ====================================================================== +// +// Everything below here is for the Mapbase convar mod system. +// ...which is based off of the Commentary convar mod system. +// +// ====================================================================== + +#define MAX_CONVARMOD_STRING_SIZE 512 + +CHandle m_hConvarsChanging; +CMapbaseCVarModEntity *ChangingCVars( void ) { return m_hConvarsChanging.Get(); } +void SetChangingCVars( CMapbaseCVarModEntity *hEnt ) { m_hConvarsChanging = hEnt; } + +CUtlVector< CHandle > m_ModEntities; +bool m_bModActive; + +void ConvarChanged( IConVar *pConVar, const char *pOldString, float flOldValue ) +{ + ConVarRef var( pConVar ); + CMapbaseCVarModEntity *modent = ChangingCVars(); + + for ( int i = 0; i < m_ModEntities.Count(); i++ ) + { + if (!m_ModEntities[i]) + { + Warning("NULL mod entity at %i\n", i); + continue; + } + + if (m_ModEntities[i].Get()->NewCVar(&var, pOldString, modent)) + break; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CV_GlobalChange_Mapbase( IConVar *var, const char *pOldString, float flOldValue ) +{ + if ( !ChangingCVars() ) + { + // A convar has changed, but not due to Mapbase systems. Ignore it. + return; + } + + ConvarChanged( var, pOldString, flOldValue ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CC_MapbaseNotChangingCVars( void ) +{ + SetChangingCVars( NULL ); +} +static ConCommand mapbase_cvarsnotchanging("mapbase_cvarsnotchanging", CC_MapbaseNotChangingCVars, "An internal command used for ConVar modification.", FCVAR_HIDDEN); + +// ------------------------------------------------------------------------ + +void CV_InitMod() +{ + if (!m_bModActive) + { + cvar->InstallGlobalChangeCallback( CV_GlobalChange_Mapbase ); + m_bModActive = true; + } +} + +// ------------------------------------------------------------------------ + +void CVEnt_Precache(CMapbaseCVarModEntity *modent) +{ + // Now protected by FCVAR_NOT_CONNECTED + //if (Q_strstr(STRING(modent->m_target), "sv_allow_logic_convar")) + // return; + +#ifdef MAPBASE_MP + if (gpGlobals->maxClients > 1 && !modent->m_bUseServer) + { + Warning("WARNING: %s is using the local player in a multiplayer game and will not function.\n", modent->GetDebugName()); + } +#endif + + CV_InitMod(); +} +void CVEnt_Activate(CMapbaseCVarModEntity *modent) +{ + const char *pszCommands = STRING( modent->m_target ); + if ( Q_strnchr(pszCommands, '^', MAX_CONVARMOD_STRING_SIZE) ) + { + // Just like the commentary system, we convert ^s to "s here. + char szTmp[MAX_CONVARMOD_STRING_SIZE]; + Q_strncpy( szTmp, pszCommands, MAX_CONVARMOD_STRING_SIZE ); + int len = Q_strlen( szTmp ); + for ( int i = 0; i < len; i++ ) + { + if ( szTmp[i] == '^' ) + { + szTmp[i] = '"'; + } + } + + pszCommands = szTmp; + } + + CV_InitMod(); + + if (m_ModEntities.Find(modent) == m_ModEntities.InvalidIndex()) + m_ModEntities.AddToTail( modent ); + + if (modent->m_bUseServer) + { + SetChangingCVars( modent ); + + engine->ServerCommand( CFmtStr("%s\n", pszCommands) ); + engine->ServerCommand( "mapbase_cvarsnotchanging\n" ); + } + else + { + CBasePlayer *pPlayer = UTIL_GetLocalPlayer(); + edict_t *edict = pPlayer ? pPlayer->edict() : NULL; + if (edict) + { + SetChangingCVars( modent ); + + engine->ClientCommand( edict, pszCommands ); + engine->ClientCommand( edict, "mapbase_cvarsnotchanging" ); + } + else + { + Warning("%s unable to find local player edict\n", modent->GetDebugName()); + } + } +} +void CVEnt_Deactivate(CMapbaseCVarModEntity *modent) +{ + // Remove our global convar callback + cvar->RemoveGlobalChangeCallback( CV_GlobalChange_Mapbase ); + + // Reset any convars that have been changed by the commentary + for ( int i = 0; i < modent->m_ModifiedConvars.Count(); i++ ) + { + ConVar *pConVar = (ConVar *)cvar->FindVar( modent->m_ModifiedConvars[i].pszConvar ); + if ( pConVar ) + { + pConVar->SetValue( modent->m_ModifiedConvars[i].pszOrgValue ); + } + } + + modent->m_ModifiedConvars.Purge(); + + if (m_bModActive) + { + m_ModEntities.FindAndRemove(modent); + + if (m_ModEntities.Count() == 0) + { + // No more mod entities + m_bModActive = false; + } + else + { + // We're done and we're still active, install our callback again + cvar->InstallGlobalChangeCallback( CV_GlobalChange_Mapbase ); + } + } +} +void CVEnt_Restore(CMapbaseCVarModEntity *modent) +{ + m_ModEntities.AddToTail(modent); +} + +// ------------------------------------------------------------------------ + + +LINK_ENTITY_TO_CLASS( game_convar_mod, CMapbaseCVarModEntity ); + +// This classname should be phased out after beta +LINK_ENTITY_TO_CLASS( mapbase_convar_mod, CMapbaseCVarModEntity ); + +BEGIN_DATADESC( CMapbaseCVarModEntity ) + + DEFINE_UTLVECTOR( m_ModifiedConvars, FIELD_EMBEDDED ), + DEFINE_KEYFIELD( m_bUseServer, FIELD_BOOLEAN, "UseServer" ), + + // Inputs + DEFINE_INPUTFUNC( FIELD_VOID, "Activate", InputActivate ), + DEFINE_INPUTFUNC( FIELD_VOID, "Deactivate", InputDeactivate ), + + DEFINE_THINKFUNC( CvarModActivate ), + +END_DATADESC() + + +void CMapbaseCVarModEntity::UpdateOnRemove() +{ + BaseClass::UpdateOnRemove(); + + CVEnt_Deactivate(this); +} + +void CMapbaseCVarModEntity::Precache( void ) +{ + BaseClass::Precache(); + + CVEnt_Precache(this); +} + +void CMapbaseCVarModEntity::Spawn( void ) +{ + BaseClass::Spawn(); + + if (m_target == NULL_STRING) + { + Warning("WARNING: %s has no cvars specified! Removing...\n", GetDebugName()); + UTIL_Remove(this); + return; + } + + Precache(); + + if (HasSpawnFlags(SF_CVARMOD_START_ACTIVATED)) + { + if (!m_bUseServer && !UTIL_GetLocalPlayer()) + { + // The local player doesn't exist yet, so we should wait until they do + SetThink( &CMapbaseCVarModEntity::CvarModActivate ); + SetNextThink( gpGlobals->curtime + TICK_INTERVAL ); + } + else + { + CVEnt_Activate( this ); + } + } +} + +void CMapbaseCVarModEntity::CvarModActivate() +{ + if (UTIL_GetLocalPlayer()) + { + CVEnt_Activate( this ); + } + else + { + SetNextThink( gpGlobals->curtime + TICK_INTERVAL ); + } +} + +void CMapbaseCVarModEntity::OnRestore( void ) +{ + BaseClass::OnRestore(); + + // Set any convars that have already been changed by the mapper before the save + if (m_ModifiedConvars.Count() > 0) + { + for ( int i = 0; i < m_ModifiedConvars.Count(); i++ ) + { + ConVar *pConVar = (ConVar *)cvar->FindVar( m_ModifiedConvars[i].pszConvar ); + if ( pConVar ) + { + if (g_debug_convarmod.GetBool()) + Msg(" %s Restoring Convar %s: value %s (org %s)\n", GetDebugName(), m_ModifiedConvars[i].pszConvar, m_ModifiedConvars[i].pszCurrentValue, m_ModifiedConvars[i].pszOrgValue ); + pConVar->SetValue( m_ModifiedConvars[i].pszCurrentValue ); + } + } + + CVEnt_Restore(this); + } +} + +void CMapbaseCVarModEntity::InputActivate(inputdata_t &inputdata) +{ + CVEnt_Activate(this); +} + +void CMapbaseCVarModEntity::InputDeactivate(inputdata_t &inputdata) +{ + CVEnt_Deactivate(this); +} + +bool CMapbaseCVarModEntity::NewCVar( ConVarRef *var, const char *pOldString, CBaseEntity *modent ) +{ + for (int i = 0; i < m_ModifiedConvars.Count(); i++) + { + // If we find it, just update the current value + modifiedconvars_t modvar = m_ModifiedConvars[i]; + if ( !Q_strncmp( var->GetName(), modvar.pszConvar, MAX_MODIFIED_CONVAR_STRING ) ) + { + if (modent == this) + { + Q_strncpy( modvar.pszCurrentValue, var->GetString(), MAX_MODIFIED_CONVAR_STRING ); + if (g_debug_convarmod.GetBool()) + Msg(" %s Updating Convar %s: value %s (org %s)\n", GetDebugName(), modvar.pszConvar, modvar.pszCurrentValue, modvar.pszOrgValue ); + return true; + } + else + { + // A different entity is using this CVar now, remove ours + m_ModifiedConvars.Remove(i); + if (g_debug_convarmod.GetBool()) + Msg(" %s Removing Convar %s: value %s (org %s)\n", GetDebugName(), modvar.pszConvar, modvar.pszCurrentValue, modvar.pszOrgValue ); + return false; + } + } + } + + // Did I do that? + if (modent == this) + { + // Add the CVar to our list. + modifiedconvars_t newConvar; + Q_strncpy( newConvar.pszConvar, var->GetName(), MAX_MODIFIED_CONVAR_STRING ); + Q_strncpy( newConvar.pszCurrentValue, var->GetString(), MAX_MODIFIED_CONVAR_STRING ); + Q_strncpy( newConvar.pszOrgValue, pOldString, MAX_MODIFIED_CONVAR_STRING ); + m_ModifiedConvars.AddToTail( newConvar ); + + if (g_debug_convarmod.GetBool()) + { + Msg(" %s changed '%s' to '%s' (was '%s')\n", GetDebugName(), var->GetName(), var->GetString(), pOldString ); + Msg(" Convars stored: %d\n", m_ModifiedConvars.Count() ); + for ( int i = 0; i < m_ModifiedConvars.Count(); i++ ) + { + Msg(" Convar %d: %s, value %s (org %s)\n", i, m_ModifiedConvars[i].pszConvar, m_ModifiedConvars[i].pszCurrentValue, m_ModifiedConvars[i].pszOrgValue ); + } + } + + return true; + } + + return false; +} diff --git a/src/game/server/mapbase/SystemConvarMod.h b/src/game/server/mapbase/SystemConvarMod.h new file mode 100644 index 00000000..faaafc27 --- /dev/null +++ b/src/game/server/mapbase/SystemConvarMod.h @@ -0,0 +1,49 @@ +//========= Mapbase - https://github.com/mapbase-source/source-sdk-2013 ============// +// +// Purpose: modifiedconvarts_t from CommentarySystem.cpp moved to a header file so Mapbase can use it. +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" + + +// Convar restoration save/restore +#define MAX_MODIFIED_CONVAR_STRING 128 +struct modifiedconvars_t +{ + DECLARE_SIMPLE_DATADESC(); + + char pszConvar[MAX_MODIFIED_CONVAR_STRING]; + char pszCurrentValue[MAX_MODIFIED_CONVAR_STRING]; + char pszOrgValue[MAX_MODIFIED_CONVAR_STRING]; +}; + +// --------------------------------------------------------------------- + +#define SF_CVARMOD_START_ACTIVATED (1 << 0) + +class CMapbaseCVarModEntity : public CPointEntity +{ +public: + DECLARE_CLASS( CMapbaseCVarModEntity, CPointEntity ); + + void UpdateOnRemove(); + + void Precache( void ); + + void Spawn( void ); + void CvarModActivate(); + + void OnRestore( void ); + + void InputActivate(inputdata_t &inputdata); + void InputDeactivate(inputdata_t &inputdata); + + bool NewCVar( ConVarRef *var, const char *pOldString, CBaseEntity *modent ); + + CUtlVector< modifiedconvars_t > m_ModifiedConvars; + bool m_bUseServer; + + DECLARE_DATADESC(); +}; diff --git a/src/game/server/mapbase/ai_grenade.cpp b/src/game/server/mapbase/ai_grenade.cpp new file mode 100644 index 00000000..a9ff5302 --- /dev/null +++ b/src/game/server/mapbase/ai_grenade.cpp @@ -0,0 +1,15 @@ +//========= Mapbase - https://github.com/mapbase-source/source-sdk-2013 ============// +// +// Purpose: +// +//=============================================================================// + +#include "cbase.h" + +#include "ai_grenade.h" + + +int COMBINE_AE_BEGIN_ALTFIRE; +int COMBINE_AE_ALTFIRE; + +ConVar ai_grenade_always_drop( "ai_grenade_always_drop", "0", FCVAR_NONE, "Causes non-Combine grenade user NPCs to be allowed to drop grenades, alt-fire items, etc. *IF* the keyvalue has not already been set in Hammer. This is useful for debugging purposes or if a mod which was developed before this feature was introduced wants its NPCs to drop grenades and beyond without recompiling all of the maps." ); diff --git a/src/game/server/mapbase/ai_grenade.h b/src/game/server/mapbase/ai_grenade.h new file mode 100644 index 00000000..cc5238f1 --- /dev/null +++ b/src/game/server/mapbase/ai_grenade.h @@ -0,0 +1,901 @@ +//========= Mapbase - https://github.com/mapbase-source/source-sdk-2013 ============// +// +// Purpose: +// +//=============================================================================// + +#ifndef AI_GRENADE_H +#define AI_GRENADE_H +#ifdef _WIN32 +#pragma once +#endif + +// I wish I didn't have to #include all this, but I don't really have a choice. +// I guess something similar to CAI_BehaviorHost's backbridges could be tried. +#include "cbase.h" +#include "ai_basenpc.h" +#include "npcevent.h" +#include "grenade_frag.h" +#include "basegrenade_shared.h" +#include "ai_squad.h" +#include "GlobalStrings.h" +#include "gameweaponmanager.h" +#include "hl2_gamerules.h" +#include "weapon_physcannon.h" +#include "globalstate.h" +#include "ai_hint.h" + +#define COMBINE_AE_GREN_TOSS ( 7 ) + +#define COMBINE_GRENADE_THROW_SPEED 650 +#define COMBINE_GRENADE_TIMER 3.5 +#define COMBINE_GRENADE_FLUSH_TIME 3.0 // Don't try to flush an enemy who has been out of sight for longer than this. +#define COMBINE_GRENADE_FLUSH_DIST 256.0 // Don't try to flush an enemy who has moved farther than this distance from the last place I saw him. + +#define COMBINE_MIN_GRENADE_CLEAR_DIST 250 + +#define DEFINE_AIGRENADE_DATADESC() \ + DEFINE_KEYFIELD( m_iNumGrenades, FIELD_INTEGER, "NumGrenades" ), \ + DEFINE_FIELD( m_flNextGrenadeCheck, FIELD_TIME ), \ + DEFINE_FIELD( m_hForcedGrenadeTarget, FIELD_EHANDLE ), \ + DEFINE_FIELD( m_flNextAltFireTime, FIELD_TIME ), \ + DEFINE_FIELD( m_vecAltFireTarget, FIELD_VECTOR ), \ + DEFINE_FIELD( m_vecTossVelocity, FIELD_VECTOR ), \ + DEFINE_FIELD( m_iLastAnimEventHandled, FIELD_INTEGER ), \ + DEFINE_INPUTFUNC( FIELD_STRING, "ThrowGrenadeAtTarget", InputThrowGrenadeAtTarget ), \ + DEFINE_INPUTFUNC( FIELD_STRING, "ThrowGrenadeGestureAtTarget", InputThrowGrenadeGestureAtTarget ), \ + DEFINE_INPUTFUNC( FIELD_INTEGER, "SetGrenades", InputSetGrenades ), \ + DEFINE_INPUTFUNC( FIELD_INTEGER, "AddGrenades", InputAddGrenades ), \ + DEFINE_OUTPUT(m_OnThrowGrenade, "OnThrowGrenade"), \ + DEFINE_OUTPUT(m_OnOutOfGrenades, "OnOutOfGrenades"), \ + +// Use extern float GetCurrentGravity( void ); +#define SMGGrenadeArc(shootpos, targetpos) \ + Vector vecShootPos = shootpos; \ + Vector vecThrow = (targetpos - vecShootPos); \ + float time = vecThrow.Length() / 600.0; \ + vecThrow = vecThrow * (1.0 / time); \ + vecThrow.z += (GetCurrentGravity() * 0.5) * time * 0.5; \ + Vector vecFace = vecShootPos + (vecThrow * 0.5); \ + AddFacingTarget(vecFace, 1.0, 0.5); \ + +// Mask used for Combine ball hull traces. +// This used MASK_SHOT before, but this has been changed to MASK_SHOT_HULL. +// This fixes the existing problem of soldiers trying to fire energy balls through grates, +// but it's also important to prevent soldiers from blowing themselves up with their newfound SMG grenades. +#define MASK_COMBINE_BALL_LOS MASK_SHOT_HULL + +extern int COMBINE_AE_BEGIN_ALTFIRE; +extern int COMBINE_AE_ALTFIRE; + +extern ConVar ai_grenade_always_drop; + +enum eGrenadeCapabilities +{ + GRENCAP_GRENADE = (1 << 0), + GRENCAP_ALTFIRE = (1 << 1), +}; + +// What grenade/item types NPCs are capable of dropping +enum eGrenadeDropCapabilities +{ + GRENDROPCAP_GRENADE = (1 << 0), + GRENDROPCAP_ALTFIRE = (1 << 1), + GRENDROPCAP_INTERRUPTED = (1 << 2), // Drops grenades when interrupted mid-animation +}; + +//----------------------------------------------------------------------------- +// Other classes can use this and access some CAI_GrenadeUser functions. +//----------------------------------------------------------------------------- +class CAI_GrenadeUserSink +{ +public: + CAI_GrenadeUserSink() { } + + virtual bool UsingOnThrowGrenade() { return false; } +}; + +//----------------------------------------------------------------------------- +// +// Template class for NPCs using grenades or weapon alt-fire stuff. +// You'll still have to use DEFINE_AIGRENADE_DATADESC() in your derived class's datadesc. +// +// I wanted to have these functions defined in a CPP file, but template class definitions must be in the header. +// Please excuse the bloat below the class definition. +// +//----------------------------------------------------------------------------- +template +class CAI_GrenadeUser : public BASE_NPC, public CAI_GrenadeUserSink +{ + DECLARE_CLASS_NOFRIEND( CAI_GrenadeUser, BASE_NPC ); + +public: + CAI_GrenadeUser() : CAI_GrenadeUserSink() { } + + void AddGrenades( int inc, CBaseEntity *pLastGrenade = NULL ) + { + m_iNumGrenades += inc; + if (m_iNumGrenades <= 0) + m_OnOutOfGrenades.Set( pLastGrenade, pLastGrenade, this ); + } + + // Use secondary ammo as a way of checking if this is a weapon which can be alt-fired (e.g. AR2 or SMG) + virtual bool IsAltFireCapable() { return (this->GetActiveWeapon() && this->GetActiveWeapon()->UsesSecondaryAmmo()); } + virtual bool IsGrenadeCapable() { return true; } + inline bool HasGrenades() { return m_iNumGrenades > 0; } + + void InputSetGrenades( inputdata_t &inputdata ) { AddGrenades( inputdata.value.Int() - m_iNumGrenades ); } + void InputAddGrenades( inputdata_t &inputdata ) { AddGrenades( inputdata.value.Int() ); } + void InputThrowGrenadeAtTarget( inputdata_t &inputdata ); + void InputThrowGrenadeGestureAtTarget( inputdata_t &inputdata ); + + virtual void DelayGrenadeCheck( float delay ) { m_flNextGrenadeCheck = gpGlobals->curtime + delay; } + + void HandleAnimEvent( animevent_t *pEvent ); + void SetActivity( Activity NewActivity ); + + // Soldiers use "lefthand", cops use "LHand", and citizens use "anim_attachment_LH" + virtual const char* GetGrenadeAttachment() { return "anim_attachment_LH"; } + + void ClearAttackConditions( void ); + + bool FValidateHintType( CAI_Hint *pHint ); + + Vector GetAltFireTarget() { return m_vecAltFireTarget; } + virtual bool CanAltFireEnemy( bool bUseFreeKnowledge ); + void DelayAltFireAttack( float flDelay ); + void DelaySquadAltFireAttack( float flDelay ); + + virtual bool CanGrenadeEnemy( bool bUseFreeKnowledge = true ); + bool CanThrowGrenade( const Vector &vecTarget ); + bool CheckCanThrowGrenade( const Vector &vecTarget ); + + // For OnThrowGrenade + point_entity_replace, see grenade_frag.cpp + bool UsingOnThrowGrenade() { return m_OnThrowGrenade.NumberOfElements() > 0; } + + // For dropping grenades and beyond + void DropGrenadeItemsOnDeath( const CTakeDamageInfo &info, CBasePlayer *pPlayer ); + virtual bool ShouldDropGrenades() { return HasGrenades(); } + virtual bool ShouldDropInterruptedGrenades() { return true; } + virtual bool ShouldDropAltFire() { return HasGrenades(); } + +protected: + + void StartTask_FaceAltFireTarget( const Task_t *pTask ); + void StartTask_GetPathToForced( const Task_t *pTask ); + void StartTask_DeferSquad( const Task_t *pTask ); + + void RunTask_FaceAltFireTarget( const Task_t *pTask ); + void RunTask_GetPathToForced( const Task_t *pTask ); + void RunTask_FaceTossDir( const Task_t *pTask ); + +protected: // We can't have any private saved variables because only derived classes use the datadescs + + int m_iNumGrenades; + float m_flNextGrenadeCheck; + EHANDLE m_hForcedGrenadeTarget; + + float m_flNextAltFireTime; + Vector m_vecAltFireTarget; + Vector m_vecTossVelocity; + + // CNPC_Combine port for determining if we tossed a grenade + int m_iLastAnimEventHandled; + + COutputEHANDLE m_OnThrowGrenade; + COutputEHANDLE m_OnOutOfGrenades; +}; + +//------------------------------------------------------------------------------ +// Purpose: Handle animation events +//------------------------------------------------------------------------------ +template +void CAI_GrenadeUser::HandleAnimEvent( animevent_t *pEvent ) +{ + if ( pEvent->event == COMBINE_AE_BEGIN_ALTFIRE ) + { + if (this->GetActiveWeapon()) + this->GetActiveWeapon()->WeaponSound( SPECIAL1 ); + + m_iLastAnimEventHandled = pEvent->event; + + //SpeakIfAllowed( TLK_CMB_THROWGRENADE, "altfire:1" ); + return; + } + if ( pEvent->event == COMBINE_AE_ALTFIRE ) + { + animevent_t fakeEvent; + + fakeEvent.pSource = this; + fakeEvent.event = EVENT_WEAPON_AR2_ALTFIRE; + + // Weapon could've been dropped while playing animation + if (this->GetActiveWeapon()) + this->GetActiveWeapon()->Operator_HandleAnimEvent( &fakeEvent, this ); + + // Stop other squad members from combine balling for a while. + DelaySquadAltFireAttack( 10.0f ); + + AddGrenades(-1); + + m_iLastAnimEventHandled = pEvent->event; + + return; + } + + if ( pEvent->event == COMBINE_AE_GREN_TOSS ) + { + Vector vecSpin; + vecSpin.x = random->RandomFloat( -1000.0, 1000.0 ); + vecSpin.y = random->RandomFloat( -1000.0, 1000.0 ); + vecSpin.z = random->RandomFloat( -1000.0, 1000.0 ); + + Vector vecStart; + this->GetAttachment( GetGrenadeAttachment(), vecStart ); + + if( this->GetState() == NPC_STATE_SCRIPT ) + { + // Use a fixed velocity for grenades thrown in scripted state. + // Grenades thrown from a script do not count against grenades remaining for the AI to use. + Vector forward, up, vecThrow; + + this->GetVectors( &forward, NULL, &up ); + vecThrow = forward * 750 + up * 175; + + // This code is used by player allies now, so it's only "combine spawned" if the thrower isn't allied with the player. + CBaseEntity *pGrenade = Fraggrenade_Create( vecStart, vec3_angle, vecThrow, vecSpin, this, COMBINE_GRENADE_TIMER, !this->IsPlayerAlly() ); + m_OnThrowGrenade.Set(pGrenade, pGrenade, this); + } + else + { + // Use the Velocity that AI gave us. + CBaseEntity *pGrenade = Fraggrenade_Create( vecStart, vec3_angle, m_vecTossVelocity, vecSpin, this, COMBINE_GRENADE_TIMER, !this->IsPlayerAlly() ); + m_OnThrowGrenade.Set(pGrenade, pGrenade, this); + AddGrenades(-1, pGrenade); + } + + // wait six seconds before even looking again to see if a grenade can be thrown. + m_flNextGrenadeCheck = gpGlobals->curtime + 6; + + m_iLastAnimEventHandled = pEvent->event; + + return; + } + + BaseClass::HandleAnimEvent( pEvent ); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +template +void CAI_GrenadeUser::SetActivity( Activity NewActivity ) +{ + BaseClass::SetActivity( NewActivity ); + + m_iLastAnimEventHandled = -1; +} + +//----------------------------------------------------------------------------- +// Purpose: Force the combine soldier to throw a grenade at the target +// If I'm a combine elite, fire my combine ball at the target instead. +// Input : &inputdata - +//----------------------------------------------------------------------------- +template +void CAI_GrenadeUser::InputThrowGrenadeAtTarget( inputdata_t &inputdata ) +{ + // Ignore if we're inside a scripted sequence + if ( this->GetState() == NPC_STATE_SCRIPT && this->m_hCine ) + return; + + CBaseEntity *pEntity = gEntList.FindEntityByName( NULL, inputdata.value.String(), this, inputdata.pActivator, inputdata.pCaller ); + if ( !pEntity ) + { + DevMsg("%s (%s) received ThrowGrenadeAtTarget input, but couldn't find target entity '%s'\n", this->GetClassname(), this->GetDebugName(), inputdata.value.String() ); + return; + } + + m_hForcedGrenadeTarget = pEntity; + m_flNextGrenadeCheck = 0; + + this->ClearSchedule( "Told to throw grenade via input" ); +} + +//----------------------------------------------------------------------------- +// Purpose: Force the combine soldier to throw a grenade at the target using the gesture animation. +// If I'm a combine elite, fire my combine ball at the target instead. +// Input : &inputdata - +//----------------------------------------------------------------------------- +template +void CAI_GrenadeUser::InputThrowGrenadeGestureAtTarget( inputdata_t &inputdata ) +{ + // Ignore if we're inside a scripted sequence + //if ( this->GetState() == NPC_STATE_SCRIPT && this->m_hCine ) + // return; + + CBaseEntity *pEntity = gEntList.FindEntityByName( NULL, inputdata.value.String(), this, inputdata.pActivator, inputdata.pCaller ); + if ( !pEntity ) + { + DevMsg("%s (%s) received ThrowGrenadeGestureAtTarget input, but couldn't find target entity '%s'\n", this->GetClassname(), this->GetDebugName(), inputdata.value.String() ); + return; + } + + m_hForcedGrenadeTarget = pEntity; + m_flNextGrenadeCheck = 0; + + Vector vecTarget = m_hForcedGrenadeTarget->WorldSpaceCenter(); + +#if SHARED_COMBINE_ACTIVITIES + if (IsAltFireCapable()) + { + if (this->FVisible( m_hForcedGrenadeTarget )) + { + m_vecAltFireTarget = vecTarget; + m_hForcedGrenadeTarget = NULL; + + int iLayer = this->AddGesture( ACT_GESTURE_COMBINE_AR2_ALTFIRE ); + if (iLayer != -1) + { + this->GetShotRegulator()->FireNoEarlierThan( gpGlobals->curtime + this->GetLayerDuration( iLayer ) ); + } + } + } + else + { + // If we can, throw a grenade at the target. + // Ignore grenade count / distance / etc + if (CheckCanThrowGrenade( vecTarget )) + { + int iLayer = this->AddGesture( ACT_GESTURE_COMBINE_THROW_GRENADE ); + if (iLayer != -1) + { + this->GetShotRegulator()->FireNoEarlierThan( gpGlobals->curtime + this->GetLayerDuration( iLayer ) ); + } + } + } +#else + Warning("Gesture grenades/alt-fire not supported\n"); +#endif +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +template +bool CAI_GrenadeUser::CanAltFireEnemy( bool bUseFreeKnowledge ) +{ + if (!HasGrenades()) + return false; + + if (!IsAltFireCapable()) + return false; + + if (!this->GetActiveWeapon()) + return false; + + if (this->IsCrouching()) + return false; + + if ( gpGlobals->curtime < m_flNextAltFireTime || gpGlobals->curtime < m_flNextGrenadeCheck ) + return false; + + if( !this->GetEnemy() ) + return false; + + if (!EntIsClass(this->GetActiveWeapon(), gm_isz_class_AR2) && !EntIsClass(this->GetActiveWeapon(), gm_isz_class_SMG1)) + return false; + + CBaseEntity *pEnemy = this->GetEnemy(); + + Vector vecTarget; + + // Determine what point we're shooting at + if( bUseFreeKnowledge ) + { + vecTarget = this->GetEnemies()->LastKnownPosition( pEnemy ) + (pEnemy->GetViewOffset()*0.75);// approximates the chest + } + else + { + vecTarget = this->GetEnemies()->LastSeenPosition( pEnemy ) + (pEnemy->GetViewOffset()*0.75);// approximates the chest + } + + // Trace a hull about the size of the combine ball (don't shoot through grates!) + trace_t tr; + + Vector mins( -12, -12, -12 ); + Vector maxs( 12, 12, 12 ); + + Vector vShootPosition = this->EyePosition(); + + if ( this->GetActiveWeapon() ) + { + this->GetActiveWeapon()->GetAttachment( "muzzle", vShootPosition ); + } + + // Trace a hull about the size of the combine ball. + UTIL_TraceHull( vShootPosition, vecTarget, mins, maxs, MASK_COMBINE_BALL_LOS, this, COLLISION_GROUP_NONE, &tr ); + + float flLength = (vShootPosition - vecTarget).Length(); + + flLength *= tr.fraction; + + // If the ball can travel at least 65% of the distance to the player then let the NPC shoot it. + // (unless it hit the world) + if( tr.fraction >= 0.65 && (!tr.m_pEnt || !tr.m_pEnt->IsWorld()) && flLength > 128.0f ) + { + // Target is valid + m_vecAltFireTarget = vecTarget; + return true; + } + + + // Check again later + m_vecAltFireTarget = vec3_origin; + m_flNextGrenadeCheck = gpGlobals->curtime + 1.0f; + return false; +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +template +void CAI_GrenadeUser::DelayAltFireAttack( float flDelay ) +{ + float flNextAltFire = gpGlobals->curtime + flDelay; + + if( flNextAltFire > m_flNextAltFireTime ) + { + // Don't let this delay order preempt a previous request to wait longer. + m_flNextAltFireTime = flNextAltFire; + } +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +template +void CAI_GrenadeUser::DelaySquadAltFireAttack( float flDelay ) +{ + // Make sure to delay my own alt-fire attack. + DelayAltFireAttack( flDelay ); + + AISquadIter_t iter; + CAI_Squad *pSquad = this->GetSquad(); + CAI_BaseNPC *pSquadmate = pSquad ? pSquad->GetFirstMember( &iter ) : NULL; + while ( pSquadmate ) + { + CAI_GrenadeUser *pUser = dynamic_cast(pSquadmate); + if( pUser && pUser->IsAltFireCapable() ) + { + pUser->DelayAltFireAttack( flDelay ); + } + + pSquadmate = pSquad->GetNextMember( &iter ); + } +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +template +bool CAI_GrenadeUser::CanGrenadeEnemy( bool bUseFreeKnowledge ) +{ + CBaseEntity *pEnemy = this->GetEnemy(); + + Assert( pEnemy != NULL ); + + if( pEnemy ) + { + // I'm not allowed to throw grenades during dustoff + if ( this->IsCurSchedule(SCHED_DROPSHIP_DUSTOFF) ) + return false; + + if( bUseFreeKnowledge ) + { + // throw to where we think they are. + return CanThrowGrenade( this->GetEnemies()->LastKnownPosition( pEnemy ) ); + } + else + { + // hafta throw to where we last saw them. + return CanThrowGrenade( this->GetEnemies()->LastSeenPosition( pEnemy ) ); + } + } + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: Return true if the combine has grenades, hasn't checked lately, and +// can throw a grenade at the target point. +// Input : &vecTarget - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +template +bool CAI_GrenadeUser::CanThrowGrenade( const Vector &vecTarget ) +{ + if( m_iNumGrenades < 1 ) + { + // Out of grenades! + return false; + } + + if (!IsGrenadeCapable()) + { + // Must be capable of throwing grenades + return false; + } + + if ( gpGlobals->curtime < m_flNextGrenadeCheck ) + { + // Not allowed to throw another grenade right now. + return false; + } + + float flDist; + flDist = ( vecTarget - this->GetAbsOrigin() ).Length(); + + if( flDist > 1024 || flDist < 128 ) + { + // Too close or too far! + m_flNextGrenadeCheck = gpGlobals->curtime + 1; // one full second. + return false; + } + + // ----------------------- + // If moving, don't check. + // ----------------------- + if ( this->m_flGroundSpeed != 0 ) + return false; + + // --------------------------------------------------------------------- + // Are any of my squad members near the intended grenade impact area? + // --------------------------------------------------------------------- + CAI_Squad *pSquad = this->GetSquad(); + if ( pSquad ) + { + if (pSquad->SquadMemberInRange( vecTarget, COMBINE_MIN_GRENADE_CLEAR_DIST )) + { + // crap, I might blow my own guy up. Don't throw a grenade and don't check again for a while. + m_flNextGrenadeCheck = gpGlobals->curtime + 1; // one full second. + + // Tell my squad members to clear out so I can get a grenade in + // Mapbase uses a new context here that gets all nondescript allies away since this code is shared between Combine and non-Combine now. + CSoundEnt::InsertSound( SOUND_MOVE_AWAY | SOUND_CONTEXT_OWNER_ALLIES, vecTarget, COMBINE_MIN_GRENADE_CLEAR_DIST, 0.1, this ); + return false; + } + } + + CHintCriteria hintCriteria; + hintCriteria.SetHintType( HINT_TACTICAL_GRENADE_THROW ); + hintCriteria.SetFlag( bits_HINT_NPC_IN_NODE_FOV ); + hintCriteria.SetGroup( this->GetHintGroup() ); + hintCriteria.AddIncludePosition( this->GetAbsOrigin(), 1024 ); + + if (this->m_debugOverlays & OVERLAY_NPC_SELECTED_BIT) + hintCriteria.SetFlag( bits_HINT_NODE_REPORT_FAILURES ); + + // If there's a grenade throw hint nearby, try using it + CAI_Hint *pHint = CAI_HintManager::FindHint( this, vecTarget, hintCriteria ); + if ( pHint ) + { + if ( CheckCanThrowGrenade( pHint->GetAbsOrigin() ) ) + { + return true; + } + else + { + DevMsg( this, "Unable to throw grenade at hint %s\n", pHint->GetDebugName() ); + } + } + + return CheckCanThrowGrenade( vecTarget ); +} + +//----------------------------------------------------------------------------- +// Purpose: Returns true if the combine can throw a grenade at the specified target point +// Input : &vecTarget - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +template +bool CAI_GrenadeUser::CheckCanThrowGrenade( const Vector &vecTarget ) +{ + //NDebugOverlay::Line( this->EyePosition(), vecTarget, 0, 255, 0, false, 5 ); + + // --------------------------------------------------------------------- + // Check that throw is legal and clear + // --------------------------------------------------------------------- + // FIXME: this is only valid for hand grenades, not RPG's + Vector vecToss; + Vector vecMins = -Vector(4,4,4); + Vector vecMaxs = Vector(4,4,4); + if( this->FInViewCone( vecTarget ) && CBaseEntity::FVisible( vecTarget ) ) + { + vecToss = VecCheckThrow( this, this->EyePosition(), vecTarget, COMBINE_GRENADE_THROW_SPEED, 1.0, &vecMins, &vecMaxs ); + } + else + { + // Have to try a high toss. Do I have enough room? + trace_t tr; + AI_TraceLine( this->EyePosition(), this->EyePosition() + Vector( 0, 0, 64 ), MASK_SHOT, this, COLLISION_GROUP_NONE, &tr ); + if( tr.fraction != 1.0 ) + { + return false; + } + + vecToss = VecCheckToss( this, this->EyePosition(), vecTarget, -1, 1.0, true, &vecMins, &vecMaxs ); + } + + if ( vecToss != vec3_origin ) + { + m_vecTossVelocity = vecToss; + + // don't check again for a while. + m_flNextGrenadeCheck = gpGlobals->curtime + 1; // 1/3 second. + return true; + } + else + { + // don't check again for a while. + m_flNextGrenadeCheck = gpGlobals->curtime + 1; // one full second. + return false; + } +} + +//----------------------------------------------------------------------------- +// Purpose: This was copied from soldier code for general AI grenades. +// +// "Soldiers use CAN_RANGE_ATTACK2 to indicate whether they can throw +// a grenade. Because they check only every half-second or so, this +// condition must persist until it is updated again by the code +// that determines whether a grenade can be thrown, so prevent the +// base class from clearing it out. (sjb)" +//----------------------------------------------------------------------------- +template +void CAI_GrenadeUser::ClearAttackConditions() +{ + bool fCanRangeAttack2 = IsGrenadeCapable() && this->HasCondition( COND_CAN_RANGE_ATTACK2 ); + + // Call the base class. + BaseClass::ClearAttackConditions(); + + if( fCanRangeAttack2 ) + { + // We don't allow the base class to clear this condition because we + // don't sense for it every frame. + this->SetCondition( COND_CAN_RANGE_ATTACK2 ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +template +bool CAI_GrenadeUser::FValidateHintType( CAI_Hint *pHint ) +{ + if ( pHint->HintType() == HINT_TACTICAL_GRENADE_THROW ) + return true; + + return BaseClass::FValidateHintType( pHint ); +} + +//----------------------------------------------------------------------------- +// Purpose: Drops grenades and alt-fire items on death. Based on code from npc_combines.cpp and npc_combine.cpp +//----------------------------------------------------------------------------- +template +void CAI_GrenadeUser::DropGrenadeItemsOnDeath( const CTakeDamageInfo &info, CBasePlayer *pPlayer ) +{ + // Elites drop alt-fire ammo, so long as they weren't killed by dissolving. + if( IsAltFireCapable() && ShouldDropAltFire() ) + { + CBaseEntity *pItem; + if (this->GetActiveWeapon() && FClassnameIs( this->GetActiveWeapon(), "weapon_smg1" )) + pItem = this->DropItem( "item_ammo_smg1_grenade", this->WorldSpaceCenter()+RandomVector(-4,4), RandomAngle(0,360) ); + else + pItem = this->DropItem( "item_ammo_ar2_altfire", this->WorldSpaceCenter() + RandomVector( -4, 4 ), RandomAngle( 0, 360 ) ); + + if ( pItem ) + { + IPhysicsObject *pObj = pItem->VPhysicsGetObject(); + + if ( pObj ) + { + Vector vel = RandomVector( -64.0f, 64.0f ); + AngularImpulse angImp = RandomAngularImpulse( -300.0f, 300.0f ); + + vel[2] = 0.0f; + pObj->AddVelocity( &vel, &angImp ); + } + + if( info.GetDamageType() & DMG_DISSOLVE ) + { + CBaseAnimating *pAnimating = dynamic_cast(pItem); + + if( pAnimating ) + { + pAnimating->Dissolve( NULL, gpGlobals->curtime, false, ENTITY_DISSOLVE_NORMAL ); + } + } + else + { + WeaponManager_AddManaged( pItem ); + } + } + } + + if ( IsGrenadeCapable() ) + { + if ( ShouldDropGrenades() ) + { + CHalfLife2 *pHL2GameRules = static_cast(g_pGameRules); + + // Attempt to drop a grenade + if ( pHL2GameRules->NPC_ShouldDropGrenade( pPlayer ) ) + { + this->DropItem( "weapon_frag", this->WorldSpaceCenter()+RandomVector(-4,4), RandomAngle(0,360) ); + pHL2GameRules->NPC_DroppedGrenade(); + } + } + + // if I was killed before I could finish throwing my grenade, drop + // a grenade item that the player can retrieve. + if (this->GetActivity() == ACT_RANGE_ATTACK2 && ShouldDropInterruptedGrenades()) + { + if( m_iLastAnimEventHandled != COMBINE_AE_GREN_TOSS ) + { + // Drop the grenade as an item. + Vector vecStart; + this->GetAttachment( GetGrenadeAttachment(), vecStart ); + + CBaseEntity *pItem = this->DropItem( "weapon_frag", vecStart, RandomAngle(0,360) ); + + if ( pItem ) + { + IPhysicsObject *pObj = pItem->VPhysicsGetObject(); + + if ( pObj ) + { + Vector vel; + vel.x = random->RandomFloat( -100.0f, 100.0f ); + vel.y = random->RandomFloat( -100.0f, 100.0f ); + vel.z = random->RandomFloat( 800.0f, 1200.0f ); + AngularImpulse angImp = RandomAngularImpulse( -300.0f, 300.0f ); + + vel[2] = 0.0f; + pObj->AddVelocity( &vel, &angImp ); + } + + // In the Citadel we need to dissolve this + if ( PlayerHasMegaPhysCannon() && GlobalEntity_GetCounter("super_phys_gun") != 1 ) + { + CBaseCombatWeapon *pWeapon = static_cast(pItem); + + pWeapon->Dissolve( NULL, gpGlobals->curtime, false, ENTITY_DISSOLVE_NORMAL ); + } + } + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Task helpers +//----------------------------------------------------------------------------- +template +void CAI_GrenadeUser::StartTask_FaceAltFireTarget( const Task_t *pTask ) +{ + this->SetIdealActivity( (Activity)(int)pTask->flTaskData ); + this->GetMotor()->SetIdealYawToTargetAndUpdate( m_vecAltFireTarget, AI_KEEP_YAW_SPEED ); +} + +template +void CAI_GrenadeUser::StartTask_GetPathToForced( const Task_t *pTask ) +{ + if ( !m_hForcedGrenadeTarget ) + { + this->TaskFail(FAIL_NO_ENEMY); + return; + } + + float flMaxRange = 2000; + float flMinRange = 0; + + Vector vecEnemy = m_hForcedGrenadeTarget->GetAbsOrigin(); + Vector vecEnemyEye = vecEnemy + m_hForcedGrenadeTarget->GetViewOffset(); + + Vector posLos; + bool found = false; + + if ( this->GetTacticalServices()->FindLateralLos( vecEnemyEye, &posLos ) ) + { + float dist = ( posLos - vecEnemyEye ).Length(); + if ( dist < flMaxRange && dist > flMinRange ) + found = true; + } + + if ( !found && this->GetTacticalServices()->FindLos( vecEnemy, vecEnemyEye, flMinRange, flMaxRange, 1.0, &posLos ) ) + { + found = true; + } + + if ( !found ) + { + this->TaskFail( FAIL_NO_SHOOT ); + } + else + { + // else drop into run task to offer an interrupt + this->m_vInterruptSavePosition = posLos; + } +} + +template +void CAI_GrenadeUser::StartTask_DeferSquad( const Task_t *pTask ) +{ + CAI_Squad *pSquad = this->GetSquad(); + if ( pSquad ) + { + // iterate my squad and stop everyone from throwing grenades for a little while. + AISquadIter_t iter; + + CAI_BaseNPC *pSquadmate = pSquad ? pSquad->GetFirstMember( &iter ) : NULL; + while ( pSquadmate ) + { + pSquadmate->DelayGrenadeCheck(5); + + pSquadmate = pSquad->GetNextMember( &iter ); + } + } + + this->TaskComplete(); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +template +void CAI_GrenadeUser::RunTask_FaceAltFireTarget( const Task_t *pTask ) +{ + this->GetMotor()->SetIdealYawToTargetAndUpdate( m_vecAltFireTarget, AI_KEEP_YAW_SPEED ); + + // New Mapbase thing that fixes forced alt-fires not changing weapon yaw/pitch + this->SetAim( m_vecAltFireTarget - this->Weapon_ShootPosition() ); + + if (this->IsActivityFinished()) + { + this->TaskComplete(); + } +} + +template +void CAI_GrenadeUser::RunTask_GetPathToForced( const Task_t *pTask ) +{ + if ( !m_hForcedGrenadeTarget ) + { + this->TaskFail(FAIL_NO_ENEMY); + return; + } + + if ( this->GetTaskInterrupt() > 0 ) + { + this->ClearTaskInterrupt(); + + Vector vecEnemy = m_hForcedGrenadeTarget->GetAbsOrigin(); + AI_NavGoal_t goal( this->m_vInterruptSavePosition, ACT_RUN, AIN_HULL_TOLERANCE ); + + this->GetNavigator()->SetGoal( goal, AIN_CLEAR_TARGET ); + this->GetNavigator()->SetArrivalDirection( vecEnemy - goal.dest ); + } + else + { + this->TaskInterrupt(); + } +} + +template +void CAI_GrenadeUser::RunTask_FaceTossDir( const Task_t *pTask ) +{ + // project a point along the toss vector and turn to face that point. + this->GetMotor()->SetIdealYawToTargetAndUpdate( this->GetLocalOrigin() + m_vecTossVelocity * 64, AI_KEEP_YAW_SPEED ); + + if ( this->FacingIdeal() ) + { + this->TaskComplete( true ); + } +} + +#endif diff --git a/src/game/server/mapbase/ai_monitor.cpp b/src/game/server/mapbase/ai_monitor.cpp new file mode 100644 index 00000000..fa11a203 --- /dev/null +++ b/src/game/server/mapbase/ai_monitor.cpp @@ -0,0 +1,742 @@ +//========= Mapbase - https://github.com/mapbase-source/source-sdk-2013 ============// +// +// Purpose: An entity that watches an NPC for certain things. +// +//============================================================================= + +#include "cbase.h" +#include "ai_schedule.h" +#include "ai_hint.h" +#include "ai_route.h" +#include "ai_basenpc.h" +#include "saverestore_utlvector.h" + + +//#define AI_MONITOR_MAX_TARGETS 16 + +// Uses a CUtlVector instead of a CBitVec for conditions/schedules. +// Using a CUtlVector makes this a lot easier, if you ask me. Please note that the CBitVec version is incomplete. +#define AI_MONITOR_USE_UTLVECTOR 1 + +//----------------------------------------------------------------------------- +// Purpose: AI monitoring. Probably bad. +//----------------------------------------------------------------------------- +class CAI_Monitor : public CLogicalEntity +{ + DECLARE_CLASS( CAI_Monitor, CLogicalEntity ); +public: + CAI_Monitor(); + + void Spawn(); + void Activate( void ); + + virtual int Save( ISave &save ); + virtual int Restore( IRestore &restore ); +#if !AI_MONITOR_USE_UTLVECTOR + void SaveConditions( ISave &save, const CAI_ScheduleBits &conditions ); + void RestoreConditions( IRestore &restore, CAI_ScheduleBits *pConditions ); +#endif + + virtual bool KeyValue( const char *szKeyName, const char *szValue ); + //virtual bool GetKeyValue( const char *szKeyName, char *szValue, int iMaxLen ); + + // Populates our NPC list. + void PopulateNPCs(inputdata_t *inputdata); + + // Does evaluation, fires outputs, etc. + bool NPCDoEval(CAI_BaseNPC *pNPC); + + // Thinks. + void MonitorThink(); + + CAI_BaseNPC *GetFirstTarget(); + + void InputEnable( inputdata_t &inputdata ); + void InputDisable( inputdata_t &inputdata ); + void InputSetTarget( inputdata_t &inputdata ) { BaseClass::InputSetTarget(inputdata); PopulateNPCs(&inputdata); } + void InputPopulateNPCs( inputdata_t &inputdata ); + void InputTest( inputdata_t &inputdata ); + void InputTestNPC( inputdata_t &inputdata ); + + // Allows mappers to get condition/schedule names from ID + void InputGetConditionName( inputdata_t &inputdata ) { m_OutConditionName.Set(AllocPooledString(ConditionName(inputdata.value.Int())), inputdata.pActivator, this); } + void InputGetScheduleName( inputdata_t &inputdata ) { m_OutScheduleName.Set(AllocPooledString(ScheduleName(inputdata.value.Int())), inputdata.pActivator, this); } + COutputString m_OutConditionName; + COutputString m_OutScheduleName; + + void InputSetCondition( inputdata_t &inputdata ) { SetCondition(TranslateConditionString(inputdata.value.String())); } + void InputClearCondition( inputdata_t &inputdata ) { ClearCondition(TranslateConditionString(inputdata.value.String())); } +#if AI_MONITOR_USE_UTLVECTOR + void InputClearAllConditions( inputdata_t &inputdata ) { m_Conditions.RemoveAll(); } +#else + void InputClearAllConditions( inputdata_t &inputdata ) { m_Conditions.ClearAll(); } +#endif + + void InputSetSchedule( inputdata_t &inputdata ) { SetSchedule(TranslateScheduleString(inputdata.value.String())); } + void InputClearSchedule( inputdata_t &inputdata ) { ClearSchedule(TranslateScheduleString(inputdata.value.String())); } +#if AI_MONITOR_USE_UTLVECTOR + void InputClearAllSchedules( inputdata_t &inputdata ) { m_Schedules.RemoveAll(); } +#else + void InputClearAllSchedules( inputdata_t &inputdata ) { m_Schedules.ClearAll(); } +#endif + + void InputSetHint( inputdata_t &inputdata ) { SetHint(inputdata.value.Int()); } + void InputClearHint( inputdata_t &inputdata ) { ClearHint(inputdata.value.Int()); } + void InputClearAllHints( inputdata_t &inputdata ) { m_Hints.RemoveAll(); } + +public: + + bool m_bStartDisabled; + + // The NPCs. + CUtlVector pNPCs; + int m_iMaxEnts; + + // Stop and engage cooldown at first successful pass + bool m_bCooldownAtFirstSuccess; + + // Interval between monitors + float m_flThinkTime; + #define GetThinkTime() (m_flThinkTime != 0 ? m_flThinkTime : TICK_INTERVAL) + + // Cooldown after something is satisfied + float m_flCooldownTime; + #define GetCooldownTime() (m_flCooldownTime != -1 ? m_flCooldownTime : GetThinkTime()) + + // ------------------------------ + // Conditions + // ------------------------------ +#if AI_MONITOR_USE_UTLVECTOR + CUtlVector m_Conditions; +#else + CAI_ScheduleBits m_Conditions; +#endif + + COutputInt m_OnNPCHasCondition; + COutputInt m_OnNPCLacksCondition; + + // Condition functions, most of these are from CAI_BaseNPC. +#if AI_MONITOR_USE_UTLVECTOR + inline void SetCondition( int iCondition ) { m_Conditions.HasElement(iCondition) ? NULL : m_Conditions.AddToTail(iCondition); } + inline void ClearCondition( int iCondition ) { m_Conditions.FindAndRemove(iCondition); } + inline bool HasCondition( int iCondition ) { return m_Conditions.HasElement(iCondition); } +#else + inline void SetCondition( int iCondition ) { m_Conditions.Set(iCondition); } + inline void ClearCondition( int iCondition ) { m_Conditions.Clear(iCondition); } + inline bool HasCondition( int iCondition ) { return m_Conditions.IsBitSet(iCondition); } +#endif + + static int GetConditionID(const char* condName) { return CAI_BaseNPC::GetSchedulingSymbols()->ConditionSymbolToId(condName); } + const char *ConditionName(int conditionID); + + int TranslateConditionString(const char *condName); + inline int ConditionLocalToGlobal(CAI_BaseNPC *pTarget, int conditionID) { return pTarget->GetClassScheduleIdSpace()->ConditionLocalToGlobal(conditionID); } + + // ------------------------------ + // Schedules + // ------------------------------ +#if AI_MONITOR_USE_UTLVECTOR + CUtlVector m_Schedules; +#else + CAI_ScheduleBits m_Schedules; +#endif + + bool m_bTranslateSchedules; + + COutputInt m_OnNPCRunningSchedule; + COutputInt m_OnNPCNotRunningSchedule; + + // Schedule functions, some of these are from CAI_BaseNPC. +#if AI_MONITOR_USE_UTLVECTOR + inline void SetSchedule( int iSchedule ) { m_Schedules.HasElement(iSchedule) ? NULL : m_Schedules.AddToTail(iSchedule); } + inline void ClearSchedule( int iSchedule ) { m_Schedules.FindAndRemove(iSchedule); } + inline bool HasSchedule( int iSchedule ) { return m_Schedules.HasElement(iSchedule); } +#else + inline void SetSchedule( int iSchedule ) { m_Schedules.Set(iSchedule); } + inline void ClearSchedule( int iSchedule ) { m_Schedules.Clear(iSchedule); } + inline bool HasSchedule( int iSchedule ) { return m_Schedules.IsBitSet(iSchedule); } +#endif + + static int GetScheduleID(const char* schedName) { return CAI_BaseNPC::GetSchedulingSymbols()->ScheduleSymbolToId(schedName); } + const char *ScheduleName(int scheduleID); + + int TranslateScheduleString(const char *schedName); + inline int ScheduleLocalToGlobal(CAI_BaseNPC *pTarget, int scheduleID) { return pTarget->GetClassScheduleIdSpace()->ScheduleLocalToGlobal(scheduleID); } + + // ------------------------------ + // Tasks + // ------------------------------ + + // TODO + + // ------------------------------ + // Hints + // ------------------------------ + CUtlVector m_Hints; + + COutputInt m_OnNPCUsingHint; + COutputInt m_OnNPCNotUsingHint; + + inline void SetHint( int iHint ) { m_Hints.HasElement(iHint) ? NULL : m_Hints.AddToTail(iHint); } + inline void ClearHint( int iHint ) { m_Hints.FindAndRemove(iHint); } + inline bool HasHint( int iHint ) { return m_Hints.HasElement(iHint); } + + // Only register a hint as "being used" when the NPC is this distance away or less + float m_flDistanceFromHint; + + DECLARE_DATADESC(); +}; + +LINK_ENTITY_TO_CLASS( ai_monitor, CAI_Monitor ); + + +BEGIN_DATADESC( CAI_Monitor ) + +#if AI_MONITOR_USE_UTLVECTOR + DEFINE_UTLVECTOR( m_Conditions, FIELD_INTEGER ), + DEFINE_UTLVECTOR( m_Schedules, FIELD_INTEGER ), +#endif + DEFINE_UTLVECTOR( m_Hints, FIELD_INTEGER ), + + // Keys + DEFINE_KEYFIELD( m_bStartDisabled, FIELD_BOOLEAN, "StartDisabled" ), + DEFINE_INPUT( m_flThinkTime, FIELD_FLOAT, "SetMonitorInterval" ), + DEFINE_INPUT( m_flCooldownTime, FIELD_FLOAT, "SetCooldownTime" ), + DEFINE_KEYFIELD( m_bCooldownAtFirstSuccess, FIELD_BOOLEAN, "CooldownAt" ), + + DEFINE_KEYFIELD( m_iMaxEnts, FIELD_INTEGER, "MaxEnts" ), + + DEFINE_KEYFIELD( m_bTranslateSchedules, FIELD_BOOLEAN, "TranslateSchedules" ), + + DEFINE_KEYFIELD( m_flDistanceFromHint, FIELD_FLOAT, "HintDistance" ), + + // Inputs + DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ), + DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ), + DEFINE_INPUTFUNC( FIELD_VOID, "UpdateActors", InputPopulateNPCs ), + DEFINE_INPUTFUNC( FIELD_VOID, "Test", InputTest ), + DEFINE_INPUTFUNC( FIELD_EHANDLE, "TestNPC", InputTestNPC ), + + DEFINE_INPUTFUNC( FIELD_INTEGER, "GetConditionName", InputGetConditionName ), + DEFINE_INPUTFUNC( FIELD_INTEGER, "GetScheduleName", InputGetScheduleName ), + + DEFINE_INPUTFUNC( FIELD_STRING, "SetCondition", InputSetCondition ), + DEFINE_INPUTFUNC( FIELD_STRING, "ClearCondition", InputClearCondition ), + DEFINE_INPUTFUNC( FIELD_STRING, "ClearAllConditions", InputClearAllConditions ), + DEFINE_INPUTFUNC( FIELD_STRING, "SetSchedule", InputSetSchedule ), + DEFINE_INPUTFUNC( FIELD_STRING, "ClearSchedule", InputClearSchedule ), + DEFINE_INPUTFUNC( FIELD_STRING, "ClearAllSchedules", InputClearAllSchedules ), + DEFINE_INPUTFUNC( FIELD_INTEGER, "SetHint", InputSetHint ), + DEFINE_INPUTFUNC( FIELD_INTEGER, "ClearHint", InputClearHint ), + DEFINE_INPUTFUNC( FIELD_INTEGER, "ClearAllHints", InputClearAllHints ), + + // Outputs + DEFINE_OUTPUT(m_OutConditionName, "OutConditionName"), + DEFINE_OUTPUT(m_OutScheduleName, "OutScheduleName"), + DEFINE_OUTPUT(m_OnNPCHasCondition, "OnNPCHasCondition"), + DEFINE_OUTPUT(m_OnNPCLacksCondition, "OnNPCLacksCondition"), + DEFINE_OUTPUT(m_OnNPCRunningSchedule, "OnNPCRunningSchedule"), + DEFINE_OUTPUT(m_OnNPCNotRunningSchedule, "OnNPCNotRunningSchedule"), + DEFINE_OUTPUT(m_OnNPCUsingHint, "OnNPCUsingHint"), + DEFINE_OUTPUT(m_OnNPCNotUsingHint, "OnNPCNotUsingHint"), + + DEFINE_THINKFUNC( MonitorThink ), + +END_DATADESC() + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CAI_Monitor::CAI_Monitor() +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CAI_Monitor::Spawn() +{ + BaseClass::Spawn(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CAI_Monitor::Activate( void ) +{ + BaseClass::Activate(); + + if (!m_bStartDisabled) + { + SetThink(&CAI_Monitor::MonitorThink); + SetNextThink(gpGlobals->curtime + GetThinkTime()); + } + + PopulateNPCs(NULL); +} + +//----------------------------------------------------------------------------- +// Enable, disable +//----------------------------------------------------------------------------- +void CAI_Monitor::InputEnable( inputdata_t &inputdata ) +{ + PopulateNPCs(&inputdata); + + SetThink( &CAI_Monitor::MonitorThink ); + SetNextThink( gpGlobals->curtime + GetThinkTime() ); +} + +void CAI_Monitor::InputDisable( inputdata_t &inputdata ) +{ + SetThink( NULL ); +} + +void CAI_Monitor::InputPopulateNPCs( inputdata_t &inputdata ) +{ + PopulateNPCs(&inputdata); +} + +void CAI_Monitor::InputTest( inputdata_t &inputdata ) +{ + bool bFoundResults = false; + for (int i = 0; i < pNPCs.Count(); i++) + { + if (pNPCs[i] != NULL) + { + if (!bFoundResults) + bFoundResults = NPCDoEval(pNPCs[i]); + else if (!m_bCooldownAtFirstSuccess) + NPCDoEval(pNPCs[i]); + else + break; + } + else + { + // If we have a null NPC, we should probably update. + // This could probably go wrong in more than one way... + PopulateNPCs(NULL); + i--; + } + } +} + +void CAI_Monitor::InputTestNPC( inputdata_t &inputdata ) +{ + CAI_BaseNPC *pNPC = inputdata.value.Entity()->MyNPCPointer(); + if (!inputdata.value.Entity() || !pNPC) + return; + + NPCDoEval(pNPC); +} + +//----------------------------------------------------------------------------- +// Purpose: Save/restore stuff from CAI_BaseNPC +//----------------------------------------------------------------------------- +int CAI_Monitor::Save( ISave &save ) +{ +#if !AI_MONITOR_USE_UTLVECTOR + save.StartBlock(); + SaveConditions( save, m_Conditions ); + SaveConditions( save, m_Schedules ); + save.EndBlock(); +#endif + + return BaseClass::Save(save); +} + +int CAI_Monitor::Restore( IRestore &restore ) +{ +#if !AI_MONITOR_USE_UTLVECTOR + restore.StartBlock(); + RestoreConditions( restore, &m_Conditions ); + RestoreConditions( restore, &m_Schedules ); + restore.EndBlock(); +#endif + + return BaseClass::Restore(restore); +} + +#if !AI_MONITOR_USE_UTLVECTOR +void CAI_Monitor::SaveConditions( ISave &save, const CAI_ScheduleBits &conditions ) +{ + for (int i = 0; i < MAX_CONDITIONS; i++) + { + if (conditions.IsBitSet(i)) + { + const char *pszConditionName = ConditionName(AI_RemapToGlobal(i)); + if ( !pszConditionName ) + break; + save.WriteString( pszConditionName ); + } + } + save.WriteString( "" ); +} + +//------------------------------------- + +void CAI_Monitor::RestoreConditions( IRestore &restore, CAI_ScheduleBits *pConditions ) +{ + pConditions->ClearAll(); + char szCondition[256]; + for (;;) + { + restore.ReadString( szCondition, sizeof(szCondition), 0 ); + if ( !szCondition[0] ) + break; + int iCondition = GetConditionID( szCondition ); + if ( iCondition != -1 ) + pConditions->Set( AI_RemapFromGlobal( iCondition ) ); + } +} +#endif + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CAI_Monitor::PopulateNPCs(inputdata_t *inputdata) +{ + // pNPCs[i] != NULL && !pNPCs[i]->IsMarkedForDeletion() && !pNPCs[i]->GetState() != NPC_STATE_DEAD + //pNPCs = CUtlVector>(); + pNPCs.RemoveAll(); + + CBaseEntity *pActivator = inputdata ? inputdata->pActivator : NULL; + CBaseEntity *pCaller = inputdata ? inputdata->pCaller : NULL; + + CBaseEntity *pEnt = gEntList.FindEntityGeneric(NULL, STRING(m_target), this, pActivator, pCaller); + while (pEnt) + { + if (pEnt->IsNPC()) + { + pNPCs.AddToTail(pEnt->MyNPCPointer()); + DevMsg("Added %s to element %i\n", pEnt->GetDebugName(), pNPCs.Count()); + + // 0 = no limit because the list would already have at least one element by the time this is checked. + if (pNPCs.Count() == m_iMaxEnts) + break; + } + + pEnt = gEntList.FindEntityGeneric(pEnt, STRING(m_target), this, pActivator, pCaller); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CAI_BaseNPC *CAI_Monitor::GetFirstTarget() +{ + for (int i = 0; i < pNPCs.Count(); i++) + { + if (pNPCs[i] != NULL) + return pNPCs[i]; + } + + return NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CAI_Monitor::NPCDoEval(CAI_BaseNPC *pNPC) +{ + // Because we return based on this + m_OnNPCHasCondition.Init(0); + m_OnNPCRunningSchedule.Init(0); + m_OnNPCUsingHint.Init(0); + + + // ---------- + // Conditions + // ---------- +#if AI_MONITOR_USE_UTLVECTOR + for (int cond = 0; cond < m_Conditions.Count(); cond++) +#else + for (int cond = 0; cond < m_Conditions.GetNumBits(); cond++) +#endif + { + if (pNPC->HasCondition(m_Conditions[cond])) + { + DevMsg("NPC has condition %i, index %i, name %s\n", m_Conditions[cond], cond, ConditionName(m_Conditions[cond])); + m_OnNPCHasCondition.Set(m_Conditions[cond], pNPC, this); + m_OutConditionName.Set(MAKE_STRING(ConditionName(m_Conditions[cond])), pNPC, this); + } + else + { + DevMsg("NPC does not have condition %i, index %i, name %s\n", m_Conditions[cond], cond, ConditionName(m_Conditions[cond])); + m_OnNPCLacksCondition.Set(m_Conditions[cond], pNPC, this); + m_OutConditionName.Set(MAKE_STRING(ConditionName(m_Conditions[cond])), pNPC, this); + } + /* + bool bDecisive = false; + switch (m_ConditionsOp) + { + case AIMONITOR_CONDITIONAL_NOR: + bConditionsTrue = true; + case AIMONITOR_CONDITIONAL_OR: + { + if (pNPC->HasCondition(m_Conditions[cond])) + { + // One is valid, pass conditions + bConditionsTrue = !bConditionsTrue; + bDecisive = true; + break; + } + } break; + case AIMONITOR_CONDITIONAL_NAND: + bConditionsTrue = true; + case AIMONITOR_CONDITIONAL_AND: + { + if (!pNPCs[i]->HasCondition(m_Conditions[cond])) + { + // One is invalid, don't pass conditions + bConditionsTrue = !bConditionsTrue; + bDecisive = true; + break; + } + } break; + } + + if (bDecisive) + break; + */ + } + + // ---------- + // Schedules + // ---------- +#if AI_MONITOR_USE_UTLVECTOR + for (int sched = 0; sched < m_Schedules.Count(); sched++) +#else + for (int sched = 0; sched < m_Schedules.GetNumBits(); sched++) +#endif + { + if (pNPC->IsCurSchedule(m_bTranslateSchedules ? pNPC->TranslateSchedule(m_Schedules[sched]) : m_Schedules[sched])) + { + DevMsg("NPC is running schedule %i, index %i, name %s\n", m_Schedules[sched], sched, ScheduleName(m_Schedules[sched])); + m_OnNPCRunningSchedule.Set(m_Schedules[sched], pNPC, this); + m_OutScheduleName.Set(AllocPooledString(ScheduleName(m_Schedules[sched])), pNPC, this); + } + else + { + DevMsg("NPC is not running schedule %i, index %i, name %s\n", m_Schedules[sched], sched, ScheduleName(m_Schedules[sched])); + m_OnNPCNotRunningSchedule.Set(m_Schedules[sched], pNPC, this); + m_OutScheduleName.Set(AllocPooledString(ScheduleName(m_Schedules[sched])), pNPC, this); + } + } + + // ---------- + // Hints + // ---------- + CAI_Hint *pHint = pNPC->GetHintNode(); + if (m_Hints.Count() > 0) + { + if (!pHint || (m_flDistanceFromHint > 0 && pHint->GetLocalOrigin().DistTo(pNPC->GetLocalOrigin()) > m_flDistanceFromHint)) + { + for (int hint = 0; hint < m_Hints.Count(); hint++) + { + m_OnNPCNotUsingHint.Set(m_Hints[hint], pNPC, this); + } + } + else + { + for (int hint = 0; hint < m_Hints.Count(); hint++) + { + if (pHint->HintType() == m_Hints[hint]) + { + DevMsg("NPC is using hint %i, index %i\n", m_Hints[hint], hint); + m_OnNPCUsingHint.Set(m_Hints[hint], pNPC, this); + } + else + { + DevMsg("NPC is not using hint %i, index %i\n", m_Hints[hint], hint); + m_OnNPCNotUsingHint.Set(m_Hints[hint], pNPC, this); + } + } + } + } + + + // Return whether any of our "valid" outputs fired. + return (m_OnNPCHasCondition.Get() != 0 + || m_OnNPCRunningSchedule.Get() != 0 + || m_OnNPCUsingHint.Get() != 0 + ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CAI_Monitor::MonitorThink() +{ + bool bMonitorFoundResults = false; + for (int i = 0; i < pNPCs.Count(); i++) + { + if (pNPCs[i] != NULL) + { + if (!bMonitorFoundResults) + bMonitorFoundResults = NPCDoEval(pNPCs[i]); + else if (!m_bCooldownAtFirstSuccess) + NPCDoEval(pNPCs[i]); + else + break; + } + else + { + // If we have a null NPC, we should probably update. + // This could probably go wrong in more than one way... + PopulateNPCs(NULL); + i--; + } + } + + if (bMonitorFoundResults) + SetNextThink(gpGlobals->curtime + GetCooldownTime()); + else + SetNextThink(gpGlobals->curtime + GetThinkTime()); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +const char *CAI_Monitor::ConditionName(int conditionID) +{ + if ( AI_IdIsLocal( conditionID ) ) + { + // Get our first target and find a local condition + CAI_BaseNPC *pTarget = GetFirstTarget(); + if (!pTarget) + return NULL; + + conditionID = ConditionLocalToGlobal(pTarget, conditionID); + } + + return CAI_BaseNPC::GetSchedulingSymbols()->ConditionIdToSymbol(conditionID); +} + +const char *CAI_Monitor::ScheduleName(int scheduleID) +{ + if ( AI_IdIsLocal( scheduleID ) ) + { + // Get our first target and find a local condition + CAI_BaseNPC *pTarget = GetFirstTarget(); + if (!pTarget) + return NULL; + + scheduleID = ScheduleLocalToGlobal(pTarget, scheduleID); + } + + return CAI_BaseNPC::GetSchedulingSymbols()->ScheduleIdToSymbol(scheduleID); +} + +int CAI_Monitor::TranslateConditionString(const char *condName) +{ + if (condName[0] == 'C') + { + // String + int cond = GetConditionID(condName); + if (cond > -1) + { + DevMsg("Setting condition %i from %s\n", cond, condName); + return cond; + } + } + else + { + // Int + DevMsg("Setting condition %s\n", condName); + + // Assume the mapper didn't compensate for global ID stuff. + // (as if either of us understand it) + return atoi(condName) + GLOBAL_IDS_BASE; + } + return 0; +} + +int CAI_Monitor::TranslateScheduleString(const char *schedName) +{ + if (schedName[0] == 'S') + { + // String + int sched = GetScheduleID(schedName); + if (sched > -1) + { + DevMsg("Setting schedule %i from %s\n", sched, schedName); + return sched; + } + } + else + { + // Int + DevMsg("Setting schedule %s\n", schedName); + return atoi(schedName); + } + return 0; +} + +template +static void SetForEachDelimited( CAI_Monitor &monitor, const char *szValue, const char *delimiters, void (CAI_Monitor::*setter)(int), Translator translator) +{ + char *value = strdup(szValue); + char *token = strtok(value, ":"); + while (token) + { + (monitor.*setter)(translator(token)); + + token = strtok(NULL, ":"); + } + free(value); +} + +template +struct CAI_MonitorTranslator +{ + CAI_Monitor &monitor; + + CAI_MonitorTranslator(CAI_Monitor &monitor) : monitor(monitor) {} + + int operator()(const char *value) + { + return (monitor.*translator)(value); + } +}; + +//----------------------------------------------------------------------------- +// Purpose: Cache user entity field values until spawn is called. +// Input : szKeyName - Key to handle. +// szValue - Value for key. +// Output : Returns true if the key was handled, false if not. +//----------------------------------------------------------------------------- +bool CAI_Monitor::KeyValue( const char *szKeyName, const char *szValue ) +{ + if (FStrEq(szKeyName, "ConditionsSimple")) + { + // Hammer SmartEdit helper that shouldn't be overridden. + // It's not supposed to be overridden. + SetCondition(atoi(szValue)); + } + else if (FStrEq(szKeyName, "Conditions")) + { + SetForEachDelimited(*this, szValue, ":", &CAI_Monitor::SetCondition, CAI_MonitorTranslator<&CAI_Monitor::TranslateConditionString>(*this)); + } + else if (FStrEq(szKeyName, "SchedulesSimple")) + { + // Hammer SmartEdit helper that shouldn't be overridden. + SetCondition(atoi(szValue)); + } + else if (FStrEq(szKeyName, "Schedules")) + { + SetForEachDelimited(*this, szValue, ":", &CAI_Monitor::SetSchedule, CAI_MonitorTranslator<&CAI_Monitor::TranslateScheduleString>(*this)); + } + else if (FStrEq(szKeyName, "HintsSimple")) + { + // Hammer SmartEdit helper that shouldn't be overridden. + SetHint(atoi(szValue)); + } + else if (FStrEq(szKeyName, "Hints")) + { + SetForEachDelimited(*this, szValue, ":", &CAI_Monitor::SetHint, atoi); + } + else + return CBaseEntity::KeyValue( szKeyName, szValue ); + + return true; +} diff --git a/src/game/server/mapbase/ai_weaponmodifier.cpp b/src/game/server/mapbase/ai_weaponmodifier.cpp new file mode 100644 index 00000000..2ddec2ea --- /dev/null +++ b/src/game/server/mapbase/ai_weaponmodifier.cpp @@ -0,0 +1,250 @@ +//========= Mapbase - https://github.com/mapbase-source/source-sdk-2013 ============// +// +// Purpose: Be warned, because this entity is TERRIBLE! +// +//============================================================================= + +#include "cbase.h" +#include "ai_basenpc.h" +#include "saverestore_utlvector.h" + + + +//----------------------------------------------------------------------------- +// Purpose: A special CAI_ShotRegulator class designed to be used with ai_weaponmodifier. +// I'm too chicken to do anything fun with it. +//----------------------------------------------------------------------------- +typedef CAI_ShotRegulator CAI_CustomShotRegulator; +/* +class CAI_CustomShotRegulator : public CAI_ShotRegulator +{ + DECLARE_CLASS( CAI_CustomShotRegulator, CAI_ShotRegulator ); +public: + void SetMinBurstInterval( float flMinBurstInterval ) { m_flMinBurstInterval = flMinBurstInterval; } + void SetMaxBurstInterval( float flMaxBurstInterval ) { m_flMaxBurstInterval = flMaxBurstInterval; } +}; +*/ + +// A lot of functions can't set each range individually, so we have to do this for a lot of them. +// (again, too chicken to do something useful with CAI_CustomShotRegulator) +#define WeaponModifierSetRange(string, function) float minimum = 0; \ + float maximum = 0; \ + if (sscanf(string, "%f:%f", &minimum, &maximum)) \ + function(minimum, maximum); + +#define WEAPONMODIFIER_MAX_NPCS 16 + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class CAI_WeaponModifier : public CLogicalEntity +{ + DECLARE_CLASS( CAI_WeaponModifier, CLogicalEntity ); + DECLARE_DATADESC(); +public: + + void Spawn(); + + virtual int Save( ISave &save ); + virtual int Restore( IRestore &restore ); + + bool KeyValue(const char *szKeyName, const char *szValue); + + void EnableOnNPC(CAI_BaseNPC *pNPC); + void DisableOnNPC(CAI_BaseNPC *pNPC); + + // Inputs + void InputEnable( inputdata_t &inputdata ); + void InputDisable( inputdata_t &inputdata ); + void InputEnableOnNPC( inputdata_t &inputdata ); + void InputDisableOnNPC( inputdata_t &inputdata ); + + void InputSetBurstInterval( inputdata_t &inputdata ) { WeaponModifierSetRange(inputdata.value.String(), m_ModdedRegulator.SetBurstInterval); } + void InputSetRestInterval( inputdata_t &inputdata ) { WeaponModifierSetRange(inputdata.value.String(), m_ModdedRegulator.SetRestInterval); } + void InputSetBurstShotCountRange( inputdata_t &inputdata ) { WeaponModifierSetRange(inputdata.value.String(), m_ModdedRegulator.SetBurstShotCountRange); } + void InputSetBurstShotsRemaining( inputdata_t &inputdata ) { m_ModdedRegulator.SetBurstShotsRemaining(inputdata.value.Int()); } + + void InputEnableShooting( inputdata_t &inputdata ) { m_ModdedRegulator.EnableShooting(); } + void InputDisableShooting( inputdata_t &inputdata ) { m_ModdedRegulator.DisableShooting(); } + void InputFireNoEarlierThan( inputdata_t &inputdata ) { m_ModdedRegulator.FireNoEarlierThan(gpGlobals->curtime + inputdata.value.Float()); } + void InputReset( inputdata_t &inputdata ) { m_ModdedRegulator.Reset(inputdata.value.Bool()); } + + // The NPCs and their original regulators. + AIHANDLE m_hNPCs[WEAPONMODIFIER_MAX_NPCS]; + CAI_ShotRegulator m_StoredRegulators[WEAPONMODIFIER_MAX_NPCS]; + + CAI_CustomShotRegulator m_ModdedRegulator; + + bool m_bDisabled; +}; + +LINK_ENTITY_TO_CLASS(ai_weaponmodifier, CAI_WeaponModifier); + +BEGIN_DATADESC( CAI_WeaponModifier ) + + DEFINE_EMBEDDED( m_ModdedRegulator ), + + DEFINE_ARRAY( m_hNPCs, FIELD_EHANDLE, WEAPONMODIFIER_MAX_NPCS ), + DEFINE_EMBEDDED_ARRAY( m_StoredRegulators, WEAPONMODIFIER_MAX_NPCS ), + + DEFINE_KEYFIELD( m_bDisabled, FIELD_BOOLEAN, "StartDisabled" ), + + // Inputs + DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ), + DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ), + DEFINE_INPUTFUNC( FIELD_EHANDLE, "EnableOnNPC", InputEnableOnNPC ), + DEFINE_INPUTFUNC( FIELD_EHANDLE, "DisableOnNPC", InputDisableOnNPC ), + + DEFINE_INPUTFUNC( FIELD_STRING, "SetBurstInterval", InputSetBurstInterval ), + DEFINE_INPUTFUNC( FIELD_STRING, "SetRestInterval", InputSetRestInterval ), + DEFINE_INPUTFUNC( FIELD_STRING, "SetBurstShotCountRange", InputSetBurstShotCountRange ), + DEFINE_INPUTFUNC( FIELD_INTEGER, "SetBurstShotsRemaining", InputSetBurstShotsRemaining ), + + DEFINE_INPUTFUNC( FIELD_VOID, "EnableShooting", InputEnableShooting ), + DEFINE_INPUTFUNC( FIELD_VOID, "DisableShooting", InputDisableShooting ), + DEFINE_INPUTFUNC( FIELD_FLOAT, "FireNoEarlierThan", InputFireNoEarlierThan ), + DEFINE_INPUTFUNC( FIELD_BOOLEAN, "Reset", InputReset ), + +END_DATADESC() + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CAI_WeaponModifier::Spawn() +{ + if (!m_bDisabled) + { + inputdata_t inputdata; + InputEnable(inputdata); + } + + BaseClass::Spawn(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int CAI_WeaponModifier::Save( ISave &save ) +{ + return BaseClass::Save(save); +} + +int CAI_WeaponModifier::Restore( IRestore &restore ) +{ + return BaseClass::Restore(restore); +} + +//----------------------------------------------------------------------------- +// Purpose: Handles key values from the BSP before spawn is called. +//----------------------------------------------------------------------------- +bool CAI_WeaponModifier::KeyValue( const char *szKeyName, const char *szValue ) +{ + if (FStrEq(szKeyName, "BurstInterval")) + { + WeaponModifierSetRange(szValue, m_ModdedRegulator.SetBurstInterval); + } + else if (FStrEq(szKeyName, "RestInterval")) + { + WeaponModifierSetRange(szValue, m_ModdedRegulator.SetRestInterval); + } + else if (FStrEq(szKeyName, "BurstShotCountRange")) + { + WeaponModifierSetRange(szValue, m_ModdedRegulator.SetBurstShotCountRange); + } + else if (FStrEq(szKeyName, "BurstShotsRemaining")) + { + m_ModdedRegulator.SetBurstShotsRemaining(atoi(szValue)); + } + else + return BaseClass::KeyValue(szKeyName, szValue); + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CAI_WeaponModifier::EnableOnNPC( CAI_BaseNPC *pNPC ) +{ + for (int i = 0; i < WEAPONMODIFIER_MAX_NPCS; i++) + { + if (m_hNPCs[i] == NULL) + { + m_hNPCs[i] = pNPC; + m_StoredRegulators[i] = *pNPC->GetShotRegulator(); + + pNPC->SetShotRegulator(m_ModdedRegulator); + } + else if (m_hNPCs[i] == pNPC) + { + // We're already in it + return; + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CAI_WeaponModifier::DisableOnNPC( CAI_BaseNPC *pNPC ) +{ + for (int i = 0; i < WEAPONMODIFIER_MAX_NPCS; i++) + { + if (m_hNPCs[i] == pNPC) + { + pNPC->SetShotRegulator(m_StoredRegulators[i]); + m_hNPCs[i] = NULL; + + // Just reset it to our modded one + m_StoredRegulators[i] = m_ModdedRegulator; + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CAI_WeaponModifier::InputEnable( inputdata_t &inputdata ) +{ + CBaseEntity *pEntity = gEntList.FindEntityByName(NULL, STRING(m_target), this, inputdata.pActivator, inputdata.pCaller); + while (pEntity) + { + if (pEntity->IsNPC()) + { + EnableOnNPC(pEntity->MyNPCPointer()); + } + + pEntity = gEntList.FindEntityByName(pEntity, STRING(m_target), this, inputdata.pActivator, inputdata.pCaller); + } +} + +void CAI_WeaponModifier::InputDisable( inputdata_t &inputdata ) +{ + for (int i = 0; i < WEAPONMODIFIER_MAX_NPCS; i++) + { + m_hNPCs[i]->SetShotRegulator(m_StoredRegulators[i]); + m_hNPCs[i] = NULL; + + // Just reset it to our modded one + m_StoredRegulators[i] = m_ModdedRegulator; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CAI_WeaponModifier::InputEnableOnNPC( inputdata_t &inputdata ) +{ + if (inputdata.value.Entity() && inputdata.value.Entity()->IsNPC()) + { + EnableOnNPC(inputdata.value.Entity()->MyNPCPointer()); + } +} + +void CAI_WeaponModifier::InputDisableOnNPC( inputdata_t &inputdata ) +{ + if (inputdata.value.Entity() && inputdata.value.Entity()->IsNPC()) + { + DisableOnNPC(inputdata.value.Entity()->MyNPCPointer()); + } +} diff --git a/src/game/server/mapbase/closecaption_entity.cpp b/src/game/server/mapbase/closecaption_entity.cpp new file mode 100644 index 00000000..a166660e --- /dev/null +++ b/src/game/server/mapbase/closecaption_entity.cpp @@ -0,0 +1,81 @@ +//========= Mapbase - https://github.com/mapbase-source/source-sdk-2013 ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" + + +class CEnvCloseCaption : public CBaseEntity +{ + DECLARE_CLASS( CEnvCloseCaption, CBaseEntity ); +public: + + bool AllPlayers() { return true; } + + void InputSend( inputdata_t &inputdata ); + + //bool m_bCustom; + int m_iFlags; + float m_flDuration; + + DECLARE_DATADESC(); +}; + +LINK_ENTITY_TO_CLASS( env_closecaption, CEnvCloseCaption ); + +BEGIN_DATADESC( CEnvCloseCaption ) + + //DEFINE_KEYFIELD( m_bCustom, FIELD_BOOLEAN, "custom" ), + DEFINE_KEYFIELD( m_iFlags, FIELD_INTEGER, "flags" ), + DEFINE_INPUT( m_flDuration, FIELD_FLOAT, "SetDuration" ), + + // Inputs + DEFINE_INPUTFUNC( FIELD_STRING, "Send", InputSend ), + +END_DATADESC() + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CEnvCloseCaption::InputSend( inputdata_t &inputdata ) +{ + char szCC[512]; + Q_strncpy(szCC, inputdata.value.String(), sizeof(szCC)); + + byte byteflags = m_iFlags; + if ( AllPlayers() ) + { + CReliableBroadcastRecipientFilter user; + UserMessageBegin( user, "CloseCaption" ); + WRITE_STRING( szCC ); + WRITE_SHORT( MIN( 255, (int)( m_flDuration * 10.0f ) ) ), + WRITE_BYTE( byteflags ), + MessageEnd(); + } + else + { + CBaseEntity *pPlayer = NULL; + + if ( inputdata.pActivator && inputdata.pActivator->IsPlayer() ) + { + pPlayer = inputdata.pActivator; + } + else + { + pPlayer = UTIL_GetLocalPlayer(); + } + + if ( !pPlayer || !pPlayer->IsNetClient() ) + return; + + CSingleUserRecipientFilter user( (CBasePlayer *)pPlayer ); + user.MakeReliable(); + UserMessageBegin( user, "CloseCaption" ); + WRITE_STRING( szCC ); + WRITE_SHORT( MIN( 255, (int)( m_flDuration * 10.0f ) ) ), + WRITE_BYTE( byteflags ), + MessageEnd(); + } +} diff --git a/src/game/server/mapbase/custom_weapon_factory.cpp b/src/game/server/mapbase/custom_weapon_factory.cpp new file mode 100644 index 00000000..2feaec16 --- /dev/null +++ b/src/game/server/mapbase/custom_weapon_factory.cpp @@ -0,0 +1,208 @@ +//========= Mapbase - https://github.com/mapbase-source/source-sdk-2013 ============// +// +// Purpose: The central manager of the custom weapons system. +// +// Author: Peter Covington (petercov@outlook.com) +// +//==================================================================================// + +#include "cbase.h" +#include "custom_weapon_factory.h" + +#define GENERIC_MANIFEST_FILE "scripts/mapbase_default_manifest.txt" +#define AUTOLOADED_MANIFEST_FILE UTIL_VarArgs("maps/%s_manifest.txt", STRING(gpGlobals->mapname)) +#define GLOBAL_WEAPONS_MANIFEST "scripts/custom_weapon_manifest.txt" + +extern ConVar mapbase_load_default_manifest; + +IMPLEMENT_PRIVATE_SYMBOLTYPE(CustomWeaponSymbol); + +CCustomWeaponSystem::CCustomWeaponSystem() : CAutoGameSystem("CustomWeaponFactorySystem") +{ +} + +void CCustomWeaponSystem::LevelInitPreEntity() +{ + LoadCustomWeaponsManifest(GLOBAL_WEAPONS_MANIFEST); + + // Check for a generic "mapname_manifest.txt" file and load it. + if (filesystem->FileExists(AUTOLOADED_MANIFEST_FILE, "GAME")) + { + AddManifestFile(AUTOLOADED_MANIFEST_FILE); + } + else + { + // Load the generic script instead. + ParseGenericManifest(); + } +} + +// Get a generic, hardcoded manifest with hardcoded names. +void CCustomWeaponSystem::ParseGenericManifest() +{ + if (!mapbase_load_default_manifest.GetBool()) + return; + + KeyValues* pKV = new KeyValues("DefaultManifest"); + pKV->LoadFromFile(filesystem, GENERIC_MANIFEST_FILE); + + AddManifestFile(pKV/*, true*/); + + pKV->deleteThis(); +} + +void CCustomWeaponSystem::AddManifestFile(const char* file) +{ + KeyValues* pKV = new KeyValues(file); + if (!pKV->LoadFromFile(filesystem, file)) + { + Warning("Mapbase Manifest: \"%s\" is unreadable or missing (can't load KV, check for syntax errors)\n", file); + pKV->deleteThis(); + return; + } + + CGMsg(1, CON_GROUP_MAPBASE_MISC, "===== Mapbase Manifest: Loading manifest file %s =====\n", file); + + AddManifestFile(pKV, false); + + CGMsg(1, CON_GROUP_MAPBASE_MISC, "==============================================================================\n"); + + pKV->deleteThis(); +} + +void CCustomWeaponSystem::AddManifestFile(KeyValues* pKV, bool bDontWarn) +{ + KeyValues* pKey = pKV->FindKey("weapons"); + + if (pKey) + { + char value[MAX_PATH]; + value[0] = '\0'; + + // Parse %mapname%, etc. + bool inparam = false; + CUtlStringList outStrings; + V_SplitString(pKey->GetString(), "%", outStrings); + for (int i = 0; i < outStrings.Count(); i++) + { + if (inparam) + { + if (FStrEq(outStrings[i], "mapname")) + { + Q_strncat(value, STRING(gpGlobals->mapname), sizeof(value)); + } + else if (FStrEq(outStrings[i], "language")) + { +#ifdef CLIENT_DLL + char uilanguage[64]; + engine->GetUILanguage(uilanguage, sizeof(uilanguage)); + Q_strncat(value, uilanguage, sizeof(value)); +#else + // Give up, use English + Q_strncat(value, "english", sizeof(value)); +#endif + } + } + else + { + Q_strncat(value, outStrings[i], sizeof(value)); + } + + inparam = !inparam; + } + + outStrings.PurgeAndDeleteElements(); + bDontWarn = pKV->GetBool("NoErrors", bDontWarn); + + LoadCustomWeaponsManifest(value, bDontWarn); + } +} + +#define Factory CustomWeaponsFactoryDictionary() +void CCustomWeaponSystem::LoadCustomWeaponsManifest(const char* file, bool bDontWarn) +{ + KeyValuesAD pKV("weapons_manifest"); + if (pKV->LoadFromFile(filesystem, file, "GAME")) + { + for (KeyValues *pkvWeapon = pKV->GetFirstValue(); pkvWeapon != nullptr; pkvWeapon = pkvWeapon->GetNextValue()) + { + const char* pszClassname = pkvWeapon->GetName(); + KeyValuesAD pkvWeaponScript("WeaponData"); + if (pkvWeaponScript->LoadFromFile(filesystem, pkvWeapon->GetString(), "GAME")) + { + const char* pszFactory = pkvWeaponScript->GetString("custom_factory", nullptr); + unsigned short FactoryIndex = Factory.Find(pszFactory); + if (Factory.IsValidIndex(FactoryIndex)) + { + auto* pFactory = Factory.Element(FactoryIndex); + const void* pData = pFactory->ParseDataFromWeaponFile(pkvWeaponScript); + if (!pData) + continue; + + unsigned short ClassIndex = m_ClassFactories.Find(pszClassname); + if (!m_ClassFactories.IsValidIndex(ClassIndex)) + { + ClassIndex = m_ClassFactories.Insert(pszClassname); + m_ClassFactories[ClassIndex].pOldFactory = EntityFactoryDictionary()->FindFactory(pszClassname); + } + else + { + Assert(m_ClassFactories[ClassIndex].pNewFactory); + Assert(m_ClassFactories[ClassIndex].pData); + + m_ClassFactories[ClassIndex].pNewFactory->ReleaseData(m_ClassFactories[ClassIndex].pData); + } + + m_ClassFactories[ClassIndex].sDataFile = pkvWeapon->GetString(); + m_ClassFactories[ClassIndex].pNewFactory = pFactory; + m_ClassFactories[ClassIndex].pData = pData; + EntityFactoryDictionary()->UninstallFactory(pszClassname); + EntityFactoryDictionary()->InstallFactory(m_ClassFactories[ClassIndex].pNewFactory, pszClassname); + } + } + } + } +} +#undef Factory + +void CCustomWeaponSystem::LevelShutdownPostEntity() +{ + for (unsigned short i = 0; i < m_ClassFactories.Count(); i++) + { + EntityFactoryDictionary()->UninstallFactory(m_ClassFactories.GetElementName(i)); + const CustomClassName_t& entry = m_ClassFactories.Element(i); + if (entry.pOldFactory) + EntityFactoryDictionary()->InstallFactory(entry.pOldFactory, m_ClassFactories.GetElementName(i)); + + Assert(entry.pData); + entry.pNewFactory->ReleaseData(entry.pData); + } + + m_ClassFactories.Purge(); + g_CustomWeaponSymbolSymbolTable.RemoveAll(); +} + +void CCustomWeaponSystem::ParseWeapon(CBaseCombatWeapon* pWeapon, const char* pClassName) +{ + ICustomWeapon* pCustom = dynamic_cast (pWeapon); + if (!pCustom) + return; + + unsigned short i = m_ClassFactories.Find(pClassName); + if (!m_ClassFactories.IsValidIndex(i)) + return; + + pCustom->InitCustomWeaponFromData(m_ClassFactories[i].pData, m_ClassFactories[i].sDataFile.String()); +} + +CUtlDict& CustomWeaponsFactoryDictionary() +{ + static CUtlDict dict; + return dict; +} + +static CCustomWeaponSystem g_CustomWeaponsSystem; +CCustomWeaponSystem* CustomWeaponSystem() +{ + return &g_CustomWeaponsSystem; +} diff --git a/src/game/server/mapbase/custom_weapon_factory.h b/src/game/server/mapbase/custom_weapon_factory.h new file mode 100644 index 00000000..bf46880b --- /dev/null +++ b/src/game/server/mapbase/custom_weapon_factory.h @@ -0,0 +1,117 @@ +//========= Mapbase - https://github.com/mapbase-source/source-sdk-2013 ============// +// +// Purpose: See custom_weapon_factory.cpp +// +// Author: Peter Covington (petercov@outlook.com) +// +//==================================================================================// + +#ifndef CUSTOM_WEAPON_FACTORY_H +#define CUSTOM_WEAPON_FACTORY_H +#pragma once +#include "utldict.h" +#include "utlsymbol.h" + +DECLARE_PRIVATE_SYMBOLTYPE(CustomWeaponSymbol); + +class ICustomWeaponDataLoader : public IEntityFactory +{ +public: + virtual const void* ParseDataFromWeaponFile(KeyValues* pKV) const = 0; + virtual void ReleaseData(const void* pData) const = 0; +}; + +class ICustomWeapon +{ +public: + virtual void InitCustomWeaponFromData(const void* pData, const char *pszWeaponScript) = 0; +}; + +class CCustomWeaponSystem : public CAutoGameSystem +{ +public: + CCustomWeaponSystem(); + + // Level init, shutdown + virtual void LevelInitPreEntity(); + virtual void LevelShutdownPostEntity(); + + void ParseWeapon(CBaseCombatWeapon* pWeapon, const char* pClassName); + +private: + void ParseGenericManifest(); + void AddManifestFile(const char* file); + void AddManifestFile(KeyValues* pKV, bool bDontWarn = false); + void LoadCustomWeaponsManifest(const char* file, bool bDontWarn = false); + + typedef struct CustomClassName_s + { + CustomWeaponSymbol sDataFile; + ICustomWeaponDataLoader* pNewFactory; + IEntityFactory* pOldFactory; + const void* pData; + } CustomClassName_t; + CUtlDict m_ClassFactories; +}; + +CCustomWeaponSystem* CustomWeaponSystem(); + +CUtlDict< ICustomWeaponDataLoader*, unsigned short >& CustomWeaponsFactoryDictionary(); + +template +class CCustomWeaponEntityFactoryBase : public ICustomWeaponDataLoader +{ +public: + CCustomWeaponEntityFactoryBase(const char* pFactoryClass) + { + CustomWeaponsFactoryDictionary().Insert(pFactoryClass, this); + } + + IServerNetworkable* Create(const char* pClassName) + { + T* pEnt = _CreateEntityTemplate((T*)NULL, pClassName); + CustomWeaponSystem()->ParseWeapon(pEnt, pClassName); + return pEnt->NetworkProp(); + } + + void Destroy(IServerNetworkable* pNetworkable) + { + if (pNetworkable) + { + pNetworkable->Release(); + } + } + + virtual size_t GetEntitySize() + { + return sizeof(T); + } +}; + +template +class CDefaultCustomWeaponEntityFactory : public CCustomWeaponEntityFactoryBase +{ +public: + CDefaultCustomWeaponEntityFactory(const char *pFactoryClass) : CCustomWeaponEntityFactoryBase(pFactoryClass) + {} + + virtual const void* ParseDataFromWeaponFile(KeyValues* pKV) const + { + Data* pData = new Data; + if (pData && pData->Parse(pKV)) + return pData; + + delete pData; + return nullptr; + } + + virtual void ReleaseData(const void* pData) const + { + delete (Data*)pData; + } +}; + +#define DEFINE_CUSTOM_WEAPON_FACTORY(factoryName, DLLClassName, DataStruct) \ + static CDefaultCustomWeaponEntityFactory custom_weapon_##factoryName##_factory( #factoryName ); + +#endif // !CUSTOM_WEAPON_FACTORY_H diff --git a/src/game/server/mapbase/datadesc_mod.cpp b/src/game/server/mapbase/datadesc_mod.cpp new file mode 100644 index 00000000..6ef4c6c0 --- /dev/null +++ b/src/game/server/mapbase/datadesc_mod.cpp @@ -0,0 +1,199 @@ +//========= Mapbase - https://github.com/mapbase-source/source-sdk-2013 ============// +// +// Purpose: +// +//=============================================================================// + +#include "cbase.h" +#include "datadesc_mod.h" +#include "saverestore.h" + + +// Sets a field's value to a specific string. +char *Datadesc_SetFieldString( const char *szValue, CBaseEntity *pObject, typedescription_t *pField, fieldtype_t *pFieldType ) +{ + // Copied from ::ParseKeyvalue... + fieldtype_t fieldtype = FIELD_VOID; + int fieldOffset = pField->fieldOffset[ TD_OFFSET_NORMAL ]; + switch( pField->fieldType ) + { + case FIELD_MODELNAME: + case FIELD_SOUNDNAME: + case FIELD_STRING: + (*(string_t *)((char *)pObject + fieldOffset)) = AllocPooledString( szValue ); + fieldtype = FIELD_STRING; + break; + + case FIELD_TIME: + case FIELD_FLOAT: + (*(float *)((char *)pObject + fieldOffset)) = atof( szValue ); + fieldtype = FIELD_FLOAT; + break; + + case FIELD_BOOLEAN: + (*(bool *)((char *)pObject + fieldOffset)) = (bool)(atoi( szValue ) != 0); + fieldtype = FIELD_BOOLEAN; + break; + + case FIELD_CHARACTER: + (*(char *)((char *)pObject + fieldOffset)) = (char)atoi( szValue ); + fieldtype = FIELD_CHARACTER; + break; + + case FIELD_SHORT: + (*(short *)((char *)pObject + fieldOffset)) = (short)atoi( szValue ); + fieldtype = FIELD_SHORT; + break; + + case FIELD_INTEGER: + case FIELD_TICK: + (*(int *)((char *)pObject + fieldOffset)) = atoi( szValue ); + fieldtype = FIELD_INTEGER; + break; + + case FIELD_POSITION_VECTOR: + case FIELD_VECTOR: + UTIL_StringToVector( (float *)((char *)pObject + fieldOffset), szValue ); + fieldtype = FIELD_VECTOR; + break; + + case FIELD_VMATRIX: + case FIELD_VMATRIX_WORLDSPACE: + UTIL_StringToFloatArray( (float *)((char *)pObject + fieldOffset), 16, szValue ); + fieldtype = FIELD_VMATRIX; // ??? + break; + + case FIELD_MATRIX3X4_WORLDSPACE: + UTIL_StringToFloatArray( (float *)((char *)pObject + fieldOffset), 12, szValue ); + fieldtype = FIELD_VMATRIX; // ??? + break; + + case FIELD_COLOR32: + UTIL_StringToColor32( (color32 *) ((char *)pObject + fieldOffset), szValue ); + fieldtype = FIELD_COLOR32; + break; + + case FIELD_CUSTOM: + { + SaveRestoreFieldInfo_t fieldInfo = + { + (char *)pObject + fieldOffset, + pObject, + pField + }; + pField->pSaveRestoreOps->Parse( fieldInfo, szValue ); + fieldtype = FIELD_STRING; + break; + } + + default: + case FIELD_INTERVAL: + case FIELD_CLASSPTR: + case FIELD_MODELINDEX: + case FIELD_MATERIALINDEX: + case FIELD_EDICT: + return NULL; + //Warning( "%s cannot set field of type %i.\n", GetDebugName(), dmap->dataDesc[i].fieldType ); + break; + } + + if (pFieldType) + *pFieldType = fieldtype; + + return ((char*)pObject) + fieldOffset; +} + +//----------------------------------------------------------------------------- +// Purpose: ReadUnregisteredKeyfields() was a feeble attempt to obtain non-keyfield keyvalues from KeyValue() with variant_t. +// +// I didn't know about GetKeyValue() until 9/29/2018. +// I don't remember why I decided to write down the date I found out about it. Maybe I considered that monumental of a discovery. +// +// However, we still use ReadUnregisteredKeyfields() since GetKeyValue() only supports a string while this function was used for entire variant_ts. +// It now calls GetKeyValue() and returns it as an allocated string. +//----------------------------------------------------------------------------- +bool ReadUnregisteredKeyfields(CBaseEntity *pTarget, const char *szKeyName, variant_t *variant) +{ + if (!pTarget) + return false; + + char szValue[256]; + if (pTarget->GetKeyValue(szKeyName, szValue, sizeof(szValue))) + { + variant->SetString(AllocPooledString(szValue)); // MAKE_STRING causes badness, must pool + return true; + } + +#if 0 + if ( FStrEq( szKeyName, "targetname" ) ) + { + variant->SetString(pTarget->GetEntityName()); + return true; + } + + if( FStrEq( szKeyName, "origin" ) ) + { + variant->SetPositionVector3D(pTarget->GetAbsOrigin()); + return true; + } + + if( FStrEq( szKeyName, "angles" ) /*|| FStrEq( szKeyName, "angle" )*/ ) + { + Vector angles; + AngleVectors(pTarget->GetAbsAngles(), &angles); + variant->SetVector3D(angles); + return true; + } + + if ( FStrEq( szKeyName, "rendercolor" ) || FStrEq( szKeyName, "rendercolor32" )) + { + // Copy it over since we're not going to use the alpha + color32 theircolor = pTarget->GetRenderColor(); + color32 color; + color.r = theircolor.r; + color.g = theircolor.g; + color.b = theircolor.b; + variant->SetColor32(color); + return true; + } + + if ( FStrEq( szKeyName, "renderamt" ) ) + { + char szAlpha = pTarget->GetRenderColor().a; + variant->SetString(MAKE_STRING(&szAlpha)); + return true; + } + + if ( FStrEq( szKeyName, "disableshadows" )) + { + variant->SetBool((pTarget->GetEffects() & EF_NOSHADOW) != NULL); + return true; + } + + if ( FStrEq( szKeyName, "mins" )) + { + variant->SetVector3D(pTarget->CollisionProp()->OBBMinsPreScaled()); + return true; + } + + if ( FStrEq( szKeyName, "maxs" )) + { + variant->SetVector3D(pTarget->CollisionProp()->OBBMaxsPreScaled()); + return true; + } + + if ( FStrEq( szKeyName, "disablereceiveshadows" )) + { + variant->SetBool((pTarget->GetEffects() & EF_NORECEIVESHADOW) != NULL); + return true; + } + + if ( FStrEq( szKeyName, "nodamageforces" )) + { + variant->SetBool((pTarget->GetEFlags() & EFL_NO_DAMAGE_FORCES) != NULL); + return true; + } +#endif + + return false; +} diff --git a/src/game/server/mapbase/datadesc_mod.h b/src/game/server/mapbase/datadesc_mod.h new file mode 100644 index 00000000..e724259b --- /dev/null +++ b/src/game/server/mapbase/datadesc_mod.h @@ -0,0 +1,11 @@ +//========= Mapbase - https://github.com/mapbase-source/source-sdk-2013 ============// +// +// Purpose: +// +//=============================================================================// + +#include "cbase.h" + +char *Datadesc_SetFieldString( const char *szValue, CBaseEntity *pObject, typedescription_t *pField, fieldtype_t *pFieldType = NULL ); + +bool ReadUnregisteredKeyfields( CBaseEntity *pTarget, const char *szKeyName, variant_t *variant ); diff --git a/src/game/server/mapbase/expandedrs_combine.h b/src/game/server/mapbase/expandedrs_combine.h new file mode 100644 index 00000000..01206d2f --- /dev/null +++ b/src/game/server/mapbase/expandedrs_combine.h @@ -0,0 +1,173 @@ +//========= Mapbase - https://github.com/mapbase-source/source-sdk-2013 ============// +// +// Purpose: Response system properties (concepts, etc.) shared by npc_combine_s and npc_metropolice +// +//=============================================================================// + +#ifndef EXPANDEDRS_COMBINE_H +#define EXPANDEDRS_COMBINE_H + +#ifdef _WIN32 +#pragma once +#endif + +#if 0 +#include "ai_component.h" +#include "ai_basenpc.h" +#include "ai_sentence.h" +#endif + +// +// Concepts +// +// These should be consistent with player allies and each other. +// + +// +// Combine Soldiers +// +#define TLK_CMB_ANNOUNCE "TLK_ANNOUNCE" +#define TLK_CMB_THROWGRENADE "TLK_THROWGRENADE" +#define TLK_CMB_PLAYERHIT "TLK_PLAYERHIT" +#define TLK_CMB_ASSAULT "TLK_ASSAULT" +#define TLK_CMB_ENEMY "TLK_STARTCOMBAT" +#define TLK_CMB_KILLENEMY "TLK_ENEMY_DEAD" +#define TLK_CMB_DANGER "TLK_DANGER" +#define TLK_CMB_KICK "TLK_KICK" +#define TLK_CMB_FLANK "TLK_FLANK" +#define TLK_CMB_PAIN "TLK_WOUND" +#define TLK_CMB_LOSTENEMY "TLK_LOSTENEMY" +#define TLK_CMB_REFINDENEMY "TLK_REFINDENEMY" +#define TLK_CMB_GOALERT "TLK_GOALERT" +//#define TLK_CMB_LASTOFSQUAD "TLK_LASTOFSQUAD" +#define TLK_CMB_MANDOWN "TLK_ALLY_KILLED" +#define TLK_CMB_DIE "TLK_DEATH" +#define TLK_CMB_QUESTION "TLK_QUESTION" +#define TLK_CMB_ANSWER "TLK_ANSWER" + +// +// Metrocops +// +#define TLK_COP_MANHACKKILLED "TLK_ALLY_KILLED" +#define TLK_COP_MANDOWN "TLK_ALLY_KILLED" +#define TLK_COP_GO_ALERT "TLK_GOALERT" +#define TLK_COP_FREEZE "TLK_FREEZE" +#define TLK_COP_OVER_HERE "TLK_OVER_HERE" +#define TLK_COP_HES_RUNNING "TLK_HES_RUNNING" +#define TLK_COP_TAKE_HIM_DOWN "TLK_TAKE_HIM_DOWN" +#define TLK_COP_ARREST_IN_POS "TLK_ARREST_IN_POS" +#define TLK_COP_DEPLOY_MANHACK "TLK_DEPLOY_MANHACK" +#define TLK_COP_PLAYERHIT "TLK_PLAYERHIT" +#define TLK_COP_FLANK "TLK_FLANK" +#define TLK_COP_HEARD_SOMETHING "TLK_DARKNESS_HEARDSOUND" +#define TLK_COP_ENEMY "TLK_STARTCOMBAT" +#define TLK_COP_KILLENEMY "TLK_ENEMY_DEAD" +#define TLK_COP_NOAMMO "TLK_NOAMMO" +#define TLK_COP_LOWAMMO "TLK_LOWAMMO" +#define TLK_COP_DANGER "TLK_DANGER" +#define TLK_COP_DIE "TLK_DEATH" +#define TLK_COP_LOSTENEMY "TLK_LOSTENEMY" +#define TLK_COP_REFINDENEMY "TLK_REFINDENEMY" +#define TLK_COP_HARASS "TLK_STARE" +#define TLK_COP_IDLE "TLK_IDLE" +#define TLK_COP_QUESTION "TLK_QUESTION" +#define TLK_COP_ANSWER "TLK_ANSWER" +#define TLK_COP_PAIN "TLK_WOUND" +#define TLK_COP_COVER_HEAVY_DAMAGE "TLK_HEAVYDAMAGE" +#define TLK_COP_SHOOTCOVER "TLK_SHOOTCOVER" +#define TLK_COP_BACK_UP "TLK_BACK_UP" +#define TLK_COP_ON_FIRE "TLK_WOUND" +#define TLK_COP_HIT_BY_PHYSOBJ "TLK_PLYR_PHYSATK" +#define TLK_COP_THROWGRENADE "TLK_THROWGRENADE" +#define TLK_COP_ACTIVATE_BATON "TLK_ACTIVATE_BATON" +#define TLK_COP_DEACTIVATE_BATON "TLK_DEACTIVATE_BATON" + +#define TLK_COP_MOVE_ALONG "TLK_MOVE_ALONG" + +#define TLK_COP_FT_APPROACH "TLK_FT_APPROACH" +#define TLK_COP_FT_MOUNT "TLK_FT_MOUNT" +#define TLK_COP_FT_SCAN "TLK_FT_SCAN" +#define TLK_COP_FT_DISMOUNT "TLK_FT_DISMOUNT" +#define TLK_COP_SO_BEGIN "TLK_SO_BEGIN" +#define TLK_COP_SO_END "TLK_SO_END" +#define TLK_COP_SO_FORCE_COVER "TLK_SO_FORCE_COVER" +#define TLK_COP_SO_PEEK "TLK_SO_PEEK" +#define TLK_COP_AS_HIT_RALLY "TLK_AS_HIT_RALLY" +#define TLK_COP_AS_HIT_ASSAULT "TLK_AS_HIT_ASSAULT" +#define TLK_COP_AS_ADV_RALLY "TLK_AS_ADV_RALLY" +#define TLK_COP_AS_ADV_ASSAULT "TLK_AS_ADV_ASSAULT" + +// +// Snipers +// +#define TLK_SNIPER_DIE "TLK_DEATH" +#define TLK_SNIPER_TARGETDESTROYED "TLK_ENEMY_DEAD" +#define TLK_SNIPER_DANGER "TLK_DANGER" + + +#if 0 +static void FixupSentence(const char **ppSentence, const char **ppPrefix); + +//----------------------------------------------------------------------------- +// This is the met of the class +//----------------------------------------------------------------------------- +template< class NPC_CLASS > +class CAI_SentenceTalker : public CAI_Component +{ + DECLARE_CLASS_NOBASE( CAI_SentenceTalker ); + DECLARE_SIMPLE_DATADESC(); + +public: + //CAI_SentenceTalker(); + + void Init( NPC_CLASS *pOuter, const char *pGameSound = NULL ); + + // Check for queued-up-sentences + speak them + //void UpdateSentenceQueue(); + + // Returns the sentence index played, which can be used to determine + // the sentence length of time using engine->SentenceLength + int Speak( const char *pSentence, SentencePriority_t nSoundPriority = SENTENCE_PRIORITY_NORMAL, SentenceCriteria_t nCriteria = SENTENCE_CRITERIA_IN_SQUAD ); + + // Returns the sentence index played, which can be used to determine + // the sentence length of time using engine->SentenceLength. If the sentence + // was queued, then -1 is returned, which is the same result as if the sound wasn't played + //int SpeakQueued( const char *pSentence, SentencePriority_t nSoundPriority = SENTENCE_PRIORITY_NORMAL, SentenceCriteria_t nCriteria = SENTENCE_CRITERIA_IN_SQUAD ); + + // Clears the sentence queue + //void ClearQueue(); + +protected: + virtual float GetVolume() = 0; + virtual soundlevel_t GetSoundLevel() = 0; + +private: + // Speech criteria + bool ShouldSpeak( SentencePriority_t nSoundPriority, SentenceCriteria_t nCriteria ); + bool MatchesCriteria( SentenceCriteria_t nCriteria ); + + // Play the actual sentence + //int PlaySentence( const char *pSentence ); + + // Debug output + void SentenceMsg( const char *pStatus, const char *pSentence ); + + int m_voicePitch; + int m_nQueuedSentenceIndex; + float m_flQueueTimeout; + int m_nQueueSoundPriority; + + SentencePriority_t m_LastPriority; + +public: + string_t m_iRemovePrefix; +}; + +template< class NPC_CLASS > +void CAI_SentenceTalker< NPC_CLASS >::Init( NPC_CLASS *pOuter, const char *pGameSound ) +{ + SetOuter( pOuter ); +} +#endif + +#endif \ No newline at end of file diff --git a/src/game/server/mapbase/func_clientclip.cpp b/src/game/server/mapbase/func_clientclip.cpp new file mode 100644 index 00000000..1bfbf1d7 --- /dev/null +++ b/src/game/server/mapbase/func_clientclip.cpp @@ -0,0 +1,191 @@ +//========= Mapbase - https://github.com/mapbase-source/source-sdk-2013 ============// +// +// Purpose: A special brush that collides with clientside entities, primarily ragdolls. +// +//=============================================================================// + +#include "cbase.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + + +//----------------------------------------------------------------------------- +// Purpose: basic solid geometry +// enabled state: brush is visible +// disabled staute: brush not visible +//----------------------------------------------------------------------------- +class CFuncClientClip : public CBaseEntity +{ +public: + DECLARE_CLASS( CFuncClientClip, CBaseEntity ); + DECLARE_SERVERCLASS(); + + virtual void Spawn( void ); + bool CreateVPhysics( void ); + + virtual int DrawDebugTextOverlays( void ); + + void TurnOff( void ); + void TurnOn( void ); + + // Input handlers + void InputTurnOff( inputdata_t &inputdata ); + void InputTurnOn( inputdata_t &inputdata ); + void InputToggle( inputdata_t &inputdata ); + + CNetworkVar( bool, m_bDisabled ); + + DECLARE_DATADESC(); + + virtual bool IsOn( void ); + + int UpdateTransmitState() // always send to all clients + { + return SetTransmitState( FL_EDICT_ALWAYS ); + } +}; + +LINK_ENTITY_TO_CLASS( func_clip_client, CFuncClientClip ); + +BEGIN_DATADESC( CFuncClientClip ) + + DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputTurnOn ), + DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputTurnOff ), + DEFINE_INPUTFUNC( FIELD_VOID, "Toggle", InputToggle ), + DEFINE_KEYFIELD( m_bDisabled, FIELD_BOOLEAN, "StartDisabled" ), + +END_DATADESC() + +IMPLEMENT_SERVERCLASS_ST( CFuncClientClip, DT_FuncClientClip ) + SendPropBool( SENDINFO( m_bDisabled ) ), +END_SEND_TABLE() + + +void CFuncClientClip::Spawn( void ) +{ + SetMoveType( MOVETYPE_PUSH ); // so it doesn't get pushed by anything + + SetSolid( GetParent() ? SOLID_VPHYSICS : SOLID_BSP ); + AddEFlags( EFL_USE_PARTITION_WHEN_NOT_SOLID ); + + AddSolidFlags( FSOLID_NOT_SOLID ); + + SetModel( STRING( GetModelName() ) ); + + if ( m_bDisabled ) + TurnOff(); + + // If it can't move/go away, it's really part of the world + if ( !GetEntityName() || !m_iParent ) + AddFlag( FL_WORLDBRUSH ); + + CreateVPhysics(); +} + +//----------------------------------------------------------------------------- + +bool CFuncClientClip::CreateVPhysics( void ) +{ + // NOTE: Don't init this static. It's pretty common for these to be constrained + // and dynamically parented. Initing shadow avoids having to destroy the physics + // object later and lose the constraints. + IPhysicsObject *pPhys = VPhysicsInitShadow(false, false); + if ( pPhys ) + { + int contents = modelinfo->GetModelContents( GetModelIndex() ); + if ( ! (contents & (MASK_SOLID|MASK_PLAYERSOLID|MASK_NPCSOLID)) ) + { + // leave the physics shadow there in case it has crap constrained to it + // but disable collisions with it + pPhys->EnableCollisions( false ); + } + } + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int CFuncClientClip::DrawDebugTextOverlays( void ) +{ + int nOffset = BaseClass::DrawDebugTextOverlays(); + + if (m_debugOverlays & OVERLAY_TEXT_BIT) + { + char tempstr[512]; + Q_snprintf( tempstr,sizeof(tempstr), "angles: %g %g %g", (double)GetLocalAngles()[PITCH], (double)GetLocalAngles()[YAW], (double)GetLocalAngles()[ROLL] ); + EntityText( nOffset, tempstr, 0 ); + nOffset++; + + Q_snprintf(tempstr, sizeof(tempstr), " enabled: %d", !m_bDisabled); + EntityText(nOffset, tempstr, 0); + nOffset++; + } + + return nOffset; +} + + +//----------------------------------------------------------------------------- +// Purpose: Input handler for toggling the hidden/shown state of the brush. +//----------------------------------------------------------------------------- +void CFuncClientClip::InputToggle( inputdata_t &inputdata ) +{ + if ( IsOn() ) + { + TurnOff(); + return; + } + + TurnOn(); +} + + +//----------------------------------------------------------------------------- +// Purpose: Input handler for hiding the brush. +//----------------------------------------------------------------------------- +void CFuncClientClip::InputTurnOff( inputdata_t &inputdata ) +{ + TurnOff(); +} + + +//----------------------------------------------------------------------------- +// Purpose: Input handler for showing the brush. +//----------------------------------------------------------------------------- +void CFuncClientClip::InputTurnOn( inputdata_t &inputdata ) +{ + TurnOn(); +} + +//----------------------------------------------------------------------------- +// Purpose: Hides the brush. +//----------------------------------------------------------------------------- +void CFuncClientClip::TurnOff( void ) +{ + if ( !IsOn() ) + return; + + AddEffects( EF_NODRAW ); + m_bDisabled = true; +} + + +//----------------------------------------------------------------------------- +// Purpose: Shows the brush. +//----------------------------------------------------------------------------- +void CFuncClientClip::TurnOn( void ) +{ + if ( IsOn() ) + return; + + RemoveEffects( EF_NODRAW ); + m_bDisabled = false; +} + + +inline bool CFuncClientClip::IsOn( void ) +{ + return !m_bDisabled; +} diff --git a/src/game/server/mapbase/func_fake_worldportal.cpp b/src/game/server/mapbase/func_fake_worldportal.cpp new file mode 100644 index 00000000..6430d5df --- /dev/null +++ b/src/game/server/mapbase/func_fake_worldportal.cpp @@ -0,0 +1,102 @@ +//========= Mapbase - https://github.com/mapbase-source/source-sdk-2013 ============// +// +// Purpose: Recreates Portal 2 linked_portal_door visual functionality using SDK code only. +// (basically a combination of point_camera and func_reflective_glass) +// +//===========================================================================// + +#include "cbase.h" +#include "modelentities.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +class CFuncFakeWorldPortal : public CFuncBrush +{ + DECLARE_DATADESC(); + DECLARE_CLASS( CFuncFakeWorldPortal, CFuncBrush ); + DECLARE_SERVERCLASS(); + + CFuncFakeWorldPortal() + { + // Equivalent to SKYBOX_2DSKYBOX_VISIBLE, the original sky setting + m_iSkyMode = 2; + } + +public: + + virtual void Spawn( void ) + { + BaseClass::Spawn(); + + if (m_target != NULL_STRING) + { + m_hTargetPlane = gEntList.FindEntityByName( NULL, m_target, this ); + if (!m_hTargetPlane) + Warning("%s: Invalid target plane \"%s\"!\n", GetDebugName(), STRING(m_target)); + } + else + { + Warning("%s: No target plane!\n", GetDebugName()); + } + + if (m_iszFogController != NULL_STRING) + { + m_hFogController = gEntList.FindEntityByName( NULL, m_iszFogController, this ); + if (!m_hFogController) + Warning("%s: Invalid fog controller \"%s\"!\n", GetDebugName(), STRING(m_iszFogController)); + } + } + + // Input handlers + void InputSetTargetPlane( inputdata_t &inputdata ) { m_hTargetPlane = inputdata.value.Entity(); if (m_hTargetPlane) { m_target = m_hTargetPlane->GetEntityName(); } } + void InputSetTargetPlaneAngle( inputdata_t &inputdata ) { Vector vec; inputdata.value.Vector3D(vec); m_PlaneAngles.Init(vec.x, vec.y, vec.z); } + void InputSetSkyMode( inputdata_t &inputdata ) { m_iSkyMode = inputdata.value.Int(); } + void InputSetRenderTarget( inputdata_t &inputdata ) { m_iszRenderTarget = inputdata.value.StringID(); } + void InputSetFogController( inputdata_t &inputdata ) { m_hFogController = inputdata.value.Entity(); if (m_hFogController) { m_iszFogController = m_hFogController->GetEntityName(); } } + void InputSetScale( inputdata_t &inputdata ) { m_flScale = inputdata.value.Float(); } + +private: + + CNetworkHandle( CBaseEntity, m_hTargetPlane ); + CNetworkQAngle( m_PlaneAngles ); + CNetworkVar( int, m_iSkyMode ); + CNetworkVar( float, m_flScale ); + CNetworkVar( string_t, m_iszRenderTarget ); + + CNetworkHandle( CBaseEntity, m_hFogController ); + string_t m_iszFogController; +}; + +// automatically hooks in the system's callbacks +BEGIN_DATADESC( CFuncFakeWorldPortal ) + + DEFINE_FIELD( m_hTargetPlane, FIELD_EHANDLE ), + DEFINE_KEYFIELD( m_PlaneAngles, FIELD_VECTOR, "PlaneAngles" ), + DEFINE_KEYFIELD( m_iSkyMode, FIELD_INTEGER, "SkyMode" ), + DEFINE_KEYFIELD( m_flScale, FIELD_FLOAT, "scale" ), + DEFINE_KEYFIELD( m_iszRenderTarget, FIELD_STRING, "RenderTarget" ), + DEFINE_FIELD( m_hFogController, FIELD_EHANDLE ), + DEFINE_KEYFIELD( m_iszFogController, FIELD_STRING, "FogController" ), + + DEFINE_INPUTFUNC( FIELD_EHANDLE, "SetTargetPlane", InputSetTargetPlane ), + DEFINE_INPUTFUNC( FIELD_VECTOR, "SetTargetPlaneAngle", InputSetTargetPlaneAngle ), + DEFINE_INPUTFUNC( FIELD_INTEGER, "SetSkyMode", InputSetSkyMode ), + DEFINE_INPUTFUNC( FIELD_STRING, "SetRenderTarget", InputSetRenderTarget ), + DEFINE_INPUTFUNC( FIELD_EHANDLE, "SetFogController", InputSetFogController ), + DEFINE_INPUTFUNC( FIELD_FLOAT, "SetScale", InputSetScale ), + +END_DATADESC() + +LINK_ENTITY_TO_CLASS( func_fake_worldportal, CFuncFakeWorldPortal ); + +IMPLEMENT_SERVERCLASS_ST( CFuncFakeWorldPortal, DT_FuncFakeWorldPortal ) + + SendPropEHandle( SENDINFO( m_hTargetPlane ) ), + SendPropVector( SENDINFO( m_PlaneAngles ), -1, SPROP_COORD ), + SendPropInt( SENDINFO( m_iSkyMode ) ), + SendPropFloat( SENDINFO( m_flScale ) ), + SendPropStringT( SENDINFO( m_iszRenderTarget ) ), + SendPropEHandle( SENDINFO( m_hFogController ) ), + +END_SEND_TABLE() diff --git a/src/game/server/mapbase/logic_eventlistener.cpp b/src/game/server/mapbase/logic_eventlistener.cpp new file mode 100644 index 00000000..c7c65d86 --- /dev/null +++ b/src/game/server/mapbase/logic_eventlistener.cpp @@ -0,0 +1,275 @@ +//========= Mapbase - https://github.com/mapbase-source/source-sdk-2013 ==== +// +// Purpose: Source SDK-based replication of logic_eventlistener from later versions +// of Source. +// +// This is based entirely on Source 2013 code and Portal 2's FGD entry. +// It does not actually use code from Portal 2 or later. +// +// UPDATE FEBRUARY 2025: The real logic_eventlistener is now available. +// Thie file only remains to support point_event. +// TODO: Merge all CLogicEvenListener functions into CPointEvent +// +//============================================================================= + +#include "cbase.h" +#include "GameEventListener.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +class CLogicEventListener2 : public CPointEntity, + public CGameEventListener +{ +public: + DECLARE_CLASS( CLogicEventListener2, CPointEntity ); + + CLogicEventListener2(); + + void Activate(); + virtual void ListenForEvents(); + + bool KeyValue(const char *szKeyName, const char *szValue); + + virtual void FireGameEvent( IGameEvent *event ); + + // Input handlers + void InputEnable( inputdata_t &inputdata ); + void InputDisable( inputdata_t &inputdata ); + void InputToggle( inputdata_t &inputdata ); + + DECLARE_DATADESC(); + + string_t m_iszEventName; + + // Outputs + COutputEvent m_OnEventFired; + +protected: + + bool m_bDisabled; +}; + +// This has been deprecated by the real logic_eventlistener +//LINK_ENTITY_TO_CLASS(logic_eventlistener, CLogicEventListener); + + +BEGIN_DATADESC( CLogicEventListener2 ) + + DEFINE_KEYFIELD(m_iszEventName, FIELD_STRING, "EventName"), + DEFINE_KEYFIELD(m_bDisabled, FIELD_BOOLEAN, "StartDisabled"), + + // Inputs + DEFINE_INPUTFUNC(FIELD_VOID, "Enable", InputEnable), + DEFINE_INPUTFUNC(FIELD_VOID, "Disable", InputDisable), + DEFINE_INPUTFUNC(FIELD_VOID, "Toggle", InputToggle), + + // Outputs + DEFINE_OUTPUT(m_OnEventFired, "OnEventFired"), + +END_DATADESC() + + + +//----------------------------------------------------------------------------- +// Purpose: Constructor. +//----------------------------------------------------------------------------- +CLogicEventListener2::CLogicEventListener2(void) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CLogicEventListener2::Activate() +{ + BaseClass::Activate(); + ListenForEvents(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CLogicEventListener2::ListenForEvents() +{ + ListenForGameEvent(STRING(m_iszEventName)); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CLogicEventListener2::KeyValue(const char *szKeyName, const char *szValue) +{ + if (FStrEq(szKeyName, "IsEnabled")) + { + m_bDisabled = !FStrEq(szValue, "0"); + } + else + return BaseClass::KeyValue(szKeyName, szValue); + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: called when a game event is fired +//----------------------------------------------------------------------------- +void CLogicEventListener2::FireGameEvent( IGameEvent *event ) +{ + if (m_bDisabled) + return; + + m_OnEventFired.FireOutput(this, this); +} + +//------------------------------------------------------------------------------ +// Purpose: Turns on the entity, allowing it to fire outputs. +//------------------------------------------------------------------------------ +void CLogicEventListener2::InputEnable( inputdata_t &inputdata ) +{ + m_bDisabled = false; +} + +//------------------------------------------------------------------------------ +// Purpose: Turns off the entity, preventing it from firing outputs. +//------------------------------------------------------------------------------ +void CLogicEventListener2::InputDisable( inputdata_t &inputdata ) +{ + m_bDisabled = true; +} + +//------------------------------------------------------------------------------ +// Purpose: Toggles the enabled/disabled state of the entity. +//------------------------------------------------------------------------------ +void CLogicEventListener2::InputToggle( inputdata_t &inputdata ) +{ + m_bDisabled = !m_bDisabled; +} + + +#define POINT_EVENT_NUM_VALUES 8 + +class CPointEvent : public CLogicEventListener2 +{ +public: + DECLARE_CLASS( CPointEvent, CLogicEventListener2 ); + + CPointEvent(); + + string_t m_KeyNames[POINT_EVENT_NUM_VALUES]; + + void ListenForEvents(); + + void FireGameEvent( IGameEvent *event ); + + // Input handlers + void InputSetAllEvents( inputdata_t &inputdata ); + void InputAddEvent( inputdata_t &inputdata ); + //void InputRemoveEvent( inputdata_t &inputdata ); + + DECLARE_DATADESC(); + + // Outputs + COutputString m_OutEventName; + COutputString m_OutValue[POINT_EVENT_NUM_VALUES]; +}; + +LINK_ENTITY_TO_CLASS(point_event, CPointEvent); + + +BEGIN_DATADESC( CPointEvent ) + + DEFINE_KEYFIELD(m_KeyNames[0], FIELD_STRING, "KeyName01"), + DEFINE_KEYFIELD(m_KeyNames[1], FIELD_STRING, "KeyName02"), + DEFINE_KEYFIELD(m_KeyNames[2], FIELD_STRING, "KeyName03"), + DEFINE_KEYFIELD(m_KeyNames[3], FIELD_STRING, "KeyName04"), + DEFINE_KEYFIELD(m_KeyNames[4], FIELD_STRING, "KeyName05"), + DEFINE_KEYFIELD(m_KeyNames[5], FIELD_STRING, "KeyName06"), + DEFINE_KEYFIELD(m_KeyNames[6], FIELD_STRING, "KeyName07"), + DEFINE_KEYFIELD(m_KeyNames[7], FIELD_STRING, "KeyName08"), + + // Inputs + DEFINE_INPUTFUNC(FIELD_STRING, "SetAllEvents", InputSetAllEvents), + DEFINE_INPUTFUNC(FIELD_STRING, "AddEvent", InputAddEvent), + //DEFINE_INPUTFUNC(FIELD_STRING, "RemoveEvent", InputRemoveEvent), + + // Outputs + DEFINE_OUTPUT(m_OutEventName, "OutEventName"), + DEFINE_OUTPUT(m_OutValue[0], "OutValue01"), + DEFINE_OUTPUT(m_OutValue[1], "OutValue02"), + DEFINE_OUTPUT(m_OutValue[2], "OutValue03"), + DEFINE_OUTPUT(m_OutValue[3], "OutValue04"), + DEFINE_OUTPUT(m_OutValue[4], "OutValue05"), + DEFINE_OUTPUT(m_OutValue[5], "OutValue06"), + DEFINE_OUTPUT(m_OutValue[6], "OutValue07"), + DEFINE_OUTPUT(m_OutValue[7], "OutValue08"), + +END_DATADESC() + +//----------------------------------------------------------------------------- +// Purpose: Constructor. +//----------------------------------------------------------------------------- +CPointEvent::CPointEvent(void) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPointEvent::ListenForEvents() +{ + // Could easily do this with strtok... + // Oh well. I don't know the performance difference. + CUtlStringList vecEvents; + Q_SplitString(STRING(m_iszEventName), ":", vecEvents); + FOR_EACH_VEC(vecEvents, i) + { + ListenForGameEvent(vecEvents[i]); + } +} + +//----------------------------------------------------------------------------- +// Purpose: called when a game event is fired +//----------------------------------------------------------------------------- +void CPointEvent::FireGameEvent( IGameEvent *event ) +{ + if (m_bDisabled) + return; + + BaseClass::FireGameEvent(event); + m_OutEventName.Set(AllocPooledString(event->GetName()), this, this); + + for (int i = 0; i < POINT_EVENT_NUM_VALUES; i++) + { + const char *szValue = event->GetString(STRING(m_KeyNames[i]), NULL); + if (szValue != NULL) + { + m_OutValue[i].Set(AllocPooledString(szValue), this, this); + } + } +} + +//------------------------------------------------------------------------------ +// Purpose: +//------------------------------------------------------------------------------ +void CPointEvent::InputSetAllEvents( inputdata_t &inputdata ) +{ + StopListeningForAllEvents(); + + if (inputdata.value.StringID() != NULL_STRING) + { + CUtlStringList vecEvents; + Q_SplitString(inputdata.value.String(), ":", vecEvents); + FOR_EACH_VEC(vecEvents, i) + { + ListenForGameEvent(vecEvents[i]); + } + } +} + +//------------------------------------------------------------------------------ +// Purpose: +//------------------------------------------------------------------------------ +void CPointEvent::InputAddEvent( inputdata_t &inputdata ) +{ + ListenForGameEvent(inputdata.value.String()); +} diff --git a/src/game/server/mapbase/logic_externaldata.cpp b/src/game/server/mapbase/logic_externaldata.cpp new file mode 100644 index 00000000..588bb6c7 --- /dev/null +++ b/src/game/server/mapbase/logic_externaldata.cpp @@ -0,0 +1,363 @@ +//========= Mapbase - https://github.com/mapbase-source/source-sdk-2013 ============// +// +// Purpose: +// +//============================================================================= + +#include "cbase.h" +#include "filesystem.h" +#include "KeyValues.h" + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class CLogicExternalData : public CLogicalEntity +{ + DECLARE_CLASS( CLogicExternalData, CLogicalEntity ); + DECLARE_DATADESC(); +#ifdef MAPBASE_VSCRIPT + DECLARE_ENT_SCRIPTDESC(); +#endif + +public: + ~CLogicExternalData(); + + void LoadFile(); + void SaveFile(); + void SetBlock(string_t iszNewTarget, CBaseEntity *pActivator = NULL, CBaseEntity *pCaller = NULL); + + void Activate(); + + // Inputs + void InputWriteKeyValue( inputdata_t &inputdata ); + void InputRemoveKeyValue( inputdata_t &inputdata ); + void InputReadKey( inputdata_t &inputdata ); + void InputSetBlock( inputdata_t &inputdata ); + void InputSave( inputdata_t &inputdata ); + void InputReload( inputdata_t &inputdata ); + +#ifdef MAPBASE_VSCRIPT + HSCRIPT ScriptGetKeyValues( void ); + HSCRIPT ScriptGetKeyValueBlock( void ); + + void ScriptSetKeyValues( HSCRIPT hKV ); + void ScriptSetKeyValueBlock( HSCRIPT hKV ); + + void ScriptSetBlock( const char *szNewBlock, HSCRIPT hActivator = NULL, HSCRIPT hCaller = NULL ); +#endif + + char m_iszFile[MAX_PATH]; + + // Root file + KeyValues *m_pRoot; + + // Our specific block + KeyValues *m_pBlock; + //string_t m_iszBlock; // Use m_target + + bool m_bSaveEachChange; + bool m_bReloadBeforeEachAction; + string_t m_iszMapname; + + COutputString m_OutValue; +}; + +LINK_ENTITY_TO_CLASS(logic_externaldata, CLogicExternalData); + +BEGIN_DATADESC( CLogicExternalData ) + + // Keys + //DEFINE_KEYFIELD( m_iszBlock, FIELD_STRING, "Block" ), + DEFINE_KEYFIELD( m_bSaveEachChange, FIELD_BOOLEAN, "SaveEachChange" ), + DEFINE_KEYFIELD( m_bReloadBeforeEachAction, FIELD_BOOLEAN, "ReloadBeforeEachAction" ), + DEFINE_KEYFIELD( m_iszMapname, FIELD_STRING, "Mapname" ), + + // This should be cached each load + //DEFINE_ARRAY( m_iszFile, FIELD_CHARACTER, MAX_PATH ), + + // Inputs + DEFINE_INPUTFUNC( FIELD_STRING, "WriteKeyValue", InputWriteKeyValue ), + DEFINE_INPUTFUNC( FIELD_STRING, "RemoveKeyValue", InputRemoveKeyValue ), + DEFINE_INPUTFUNC( FIELD_STRING, "ReadKey", InputReadKey ), + DEFINE_INPUTFUNC( FIELD_STRING, "SetBlock", InputSetBlock ), + DEFINE_INPUTFUNC( FIELD_VOID, "Save", InputSave ), + DEFINE_INPUTFUNC( FIELD_VOID, "Reload", InputReload ), + + // Outputs + DEFINE_OUTPUT(m_OutValue, "OutValue"), + +END_DATADESC() + +#ifdef MAPBASE_VSCRIPT +BEGIN_ENT_SCRIPTDESC( CLogicExternalData, CBaseEntity, "An entity which loads keyvalues from an external data file." ) + + DEFINE_SCRIPTFUNC_NAMED( ScriptGetKeyValues, "GetKeyValues", "Gets the external data expressed in CScriptKeyValues." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptGetKeyValueBlock, "GetKeyValueBlock", "Gets the current external data block expressed in CScriptKeyValues." ) + + DEFINE_SCRIPTFUNC_NAMED( ScriptSetKeyValues, "SetKeyValues", "Sets the external data from a CScriptKeyValues object." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptSetKeyValueBlock, "SetKeyValueBlock", "Sets the current external data block from a CScriptKeyValues object." ) + + DEFINE_SCRIPTFUNC( LoadFile, "Loads external data from the external file." ) + DEFINE_SCRIPTFUNC( SaveFile, "Saves the external data to the external file." ) + +END_SCRIPTDESC(); +#endif + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CLogicExternalData::~CLogicExternalData() +{ + if (m_pRoot) + m_pRoot->deleteThis(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CLogicExternalData::LoadFile() +{ + if (m_pRoot) + m_pRoot->deleteThis(); + + m_pRoot = new KeyValues( m_iszFile ); + m_pRoot->LoadFromFile(g_pFullFileSystem, m_iszFile, "MOD"); + + // This shold work even if the file didn't load. + if (m_target != NULL_STRING) + { + m_pBlock = m_pRoot->FindKey(STRING(m_target), true); + } + else + { + // Just do things from root + m_pBlock = m_pRoot; + } + + if (!m_pBlock) + { + Warning("WARNING! %s has NULL m_pBlock! Removing...\n", GetDebugName()); + UTIL_Remove(this); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CLogicExternalData::SaveFile() +{ + DevMsg("Saving to %s...\n", m_iszFile); + m_pRoot->SaveToFile(g_pFullFileSystem, m_iszFile, "MOD", false, true); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CLogicExternalData::SetBlock(string_t iszNewTarget, CBaseEntity *pActivator, CBaseEntity *pCaller) +{ + if (STRING(iszNewTarget)[0] == '!') + { + if (FStrEq(STRING(iszNewTarget), "!self")) + iszNewTarget = GetEntityName(); + else if (pActivator && FStrEq(STRING(iszNewTarget), "!activator")) + iszNewTarget = pActivator->GetEntityName(); + else if (pCaller && FStrEq(STRING(iszNewTarget), "!caller")) + iszNewTarget = pCaller->GetEntityName(); + } + + m_target = iszNewTarget; + LoadFile(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CLogicExternalData::Activate() +{ + BaseClass::Activate(); + + if (m_iszMapname == NULL_STRING || STRING(m_iszMapname)[0] == '\0') + m_iszMapname = gpGlobals->mapname; + + Q_snprintf(m_iszFile, sizeof(m_iszFile), "maps/%s_externaldata.txt", STRING(m_iszMapname)); + DevMsg("LOGIC_EXTERNALDATA: %s\n", m_iszFile); + + // This handles !self, etc. even though the end result could just be assigning to itself. + // Also calls LoadFile() for initial load. + SetBlock(m_target); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CLogicExternalData::InputWriteKeyValue( inputdata_t &inputdata ) +{ + const char *szValue = inputdata.value.String(); + char key[256]; + char value[256]; + + // Separate key from value + char *delimiter = Q_strstr(szValue, " "); + if (delimiter && delimiter[1] != '\0') + { + Q_strncpy(key, szValue, MIN((delimiter - szValue) + 1, sizeof(key))); + Q_strncpy(value, delimiter + 1, sizeof(value)); + } + else + { + // Assume the value is just supposed to be null + Q_strncpy(key, szValue, sizeof(key)); + } + + if (m_bReloadBeforeEachAction) + LoadFile(); + + m_pBlock->SetString(key, value); + + if (m_bSaveEachChange) + SaveFile(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CLogicExternalData::InputRemoveKeyValue( inputdata_t &inputdata ) +{ + if (m_bReloadBeforeEachAction) + LoadFile(); + + KeyValues *pKV = m_pBlock->FindKey(inputdata.value.String()); + if (pKV) + { + m_pBlock->RemoveSubKey(pKV); + + if (m_bSaveEachChange) + SaveFile(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CLogicExternalData::InputReadKey( inputdata_t &inputdata ) +{ + if (m_bReloadBeforeEachAction) + LoadFile(); + + m_OutValue.Set(AllocPooledString(m_pBlock->GetString(inputdata.value.String())), inputdata.pActivator, this); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CLogicExternalData::InputSetBlock( inputdata_t &inputdata ) +{ + SetBlock(inputdata.value.StringID(), inputdata.pActivator, inputdata.pCaller); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CLogicExternalData::InputSave( inputdata_t &inputdata ) +{ + SaveFile(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CLogicExternalData::InputReload( inputdata_t &inputdata ) +{ + LoadFile(); +} + +#ifdef MAPBASE_VSCRIPT +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +HSCRIPT CLogicExternalData::ScriptGetKeyValues( void ) +{ + if (m_bReloadBeforeEachAction) + LoadFile(); + + HSCRIPT hScript = NULL; + if (m_pRoot) + { + // Does this need to be destructed or freed? m_pScriptModelKeyValues apparently doesn't. + hScript = scriptmanager->CreateScriptKeyValues( g_pScriptVM, m_pRoot, false ); + } + + return hScript; +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +HSCRIPT CLogicExternalData::ScriptGetKeyValueBlock( void ) +{ + if (m_bReloadBeforeEachAction) + LoadFile(); + + HSCRIPT hScript = NULL; + if (m_pBlock) + { + // Does this need to be destructed or freed? m_pScriptModelKeyValues apparently doesn't. + hScript = scriptmanager->CreateScriptKeyValues( g_pScriptVM, m_pBlock, false ); + } + + return hScript; +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +void CLogicExternalData::ScriptSetKeyValues( HSCRIPT hKV ) +{ + if (m_pRoot) + { + m_pRoot->deleteThis(); + m_pRoot = NULL; + } + + KeyValues *pKV = scriptmanager->GetKeyValuesFromScriptKV( g_pScriptVM, hKV ); + if (pKV) + { + m_pRoot = pKV; + } +} + +void CLogicExternalData::ScriptSetKeyValueBlock( HSCRIPT hKV ) +{ + if (m_pBlock) + { + m_pBlock->deleteThis(); + m_pBlock = NULL; + } + + KeyValues *pKV = scriptmanager->GetKeyValuesFromScriptKV( g_pScriptVM, hKV ); + if (pKV) + { + m_pBlock = pKV; + } +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CLogicExternalData::ScriptSetBlock( const char *szNewBlock, HSCRIPT hActivator, HSCRIPT hCaller ) +{ + CBaseEntity *pActivator = ToEnt( hActivator ); + CBaseEntity *pCaller = ToEnt( hCaller ); + string_t iszNewTarget = AllocPooledString(szNewBlock); + if (STRING(iszNewTarget)[0] == '!') + { + if (FStrEq(STRING(iszNewTarget), "!self")) + iszNewTarget = GetEntityName(); + else if (pActivator && FStrEq(STRING(iszNewTarget), "!activator")) + iszNewTarget = pActivator->GetEntityName(); + else if (pCaller && FStrEq(STRING(iszNewTarget), "!caller")) + iszNewTarget = pCaller->GetEntityName(); + } + + m_target = iszNewTarget; + LoadFile(); +} +#endif diff --git a/src/game/server/mapbase/logic_register_activator.cpp b/src/game/server/mapbase/logic_register_activator.cpp new file mode 100644 index 00000000..79a61dab --- /dev/null +++ b/src/game/server/mapbase/logic_register_activator.cpp @@ -0,0 +1,145 @@ +//========= Mapbase - https://github.com/mapbase-source/source-sdk-2013 ==== +// +// Purpose: Source SDK-based replication of logic_register_activator from later versions +// of Source. +// +// This is based entirely on Source 2013 code and Portal 2's FGD entry. +// It does not actually use code from Portal 2 or later. +// +//============================================================================= + +#include "cbase.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +class CLogicRegisterActivator : public CLogicalEntity +{ +public: + DECLARE_CLASS( CLogicRegisterActivator, CLogicalEntity ); + + CLogicRegisterActivator(); + + // Input handlers + void InputEnable( inputdata_t &inputdata ); + void InputDisable( inputdata_t &inputdata ); + void InputToggle( inputdata_t &inputdata ); + + void InputFireRegisteredAsActivator1( inputdata_t &inputdata ); + void InputFireRegisteredAsActivator2( inputdata_t &inputdata ); + void InputFireRegisteredAsActivator3( inputdata_t &inputdata ); + void InputFireRegisteredAsActivator4( inputdata_t &inputdata ); + void InputRegisterEntity( inputdata_t &inputdata ); + + DECLARE_DATADESC(); + + // Outputs + COutputEvent m_OnRegisteredActivate[ 4 ]; + + EHANDLE m_hActivator; + +private: + + bool m_bDisabled; +}; + +LINK_ENTITY_TO_CLASS(logic_register_activator, CLogicRegisterActivator); + + +BEGIN_DATADESC( CLogicRegisterActivator ) + + DEFINE_KEYFIELD(m_bDisabled, FIELD_BOOLEAN, "StartDisabled"), + + DEFINE_FIELD( m_hActivator, FIELD_EHANDLE ), + + // Inputs + DEFINE_INPUTFUNC(FIELD_VOID, "Enable", InputEnable), + DEFINE_INPUTFUNC(FIELD_VOID, "Disable", InputDisable), + DEFINE_INPUTFUNC(FIELD_VOID, "Toggle", InputToggle), + DEFINE_INPUTFUNC(FIELD_VOID, "FireRegisteredAsActivator1", InputFireRegisteredAsActivator1), + DEFINE_INPUTFUNC(FIELD_VOID, "FireRegisteredAsActivator2", InputFireRegisteredAsActivator2), + DEFINE_INPUTFUNC(FIELD_VOID, "FireRegisteredAsActivator3", InputFireRegisteredAsActivator3), + DEFINE_INPUTFUNC(FIELD_VOID, "FireRegisteredAsActivator4", InputFireRegisteredAsActivator4), + DEFINE_INPUTFUNC(FIELD_EHANDLE, "RegisterEntity", InputRegisterEntity), + + // Outputs + DEFINE_OUTPUT(m_OnRegisteredActivate[0], "OnRegisteredActivate1"), + DEFINE_OUTPUT(m_OnRegisteredActivate[1], "OnRegisteredActivate2"), + DEFINE_OUTPUT(m_OnRegisteredActivate[2], "OnRegisteredActivate3"), + DEFINE_OUTPUT(m_OnRegisteredActivate[3], "OnRegisteredActivate4"), + +END_DATADESC() + + + +//----------------------------------------------------------------------------- +// Purpose: Constructor. +//----------------------------------------------------------------------------- +CLogicRegisterActivator::CLogicRegisterActivator(void) +{ +} + +//------------------------------------------------------------------------------ +// Purpose: Turns on the entity, allowing it to fire outputs. +//------------------------------------------------------------------------------ +void CLogicRegisterActivator::InputEnable( inputdata_t &inputdata ) +{ + m_bDisabled = false; +} + +//------------------------------------------------------------------------------ +// Purpose: Turns off the entity, preventing it from firing outputs. +//------------------------------------------------------------------------------ +void CLogicRegisterActivator::InputDisable( inputdata_t &inputdata ) +{ + m_bDisabled = true; +} + +//------------------------------------------------------------------------------ +// Purpose: Toggles the enabled/disabled state of the entity. +//------------------------------------------------------------------------------ +void CLogicRegisterActivator::InputToggle( inputdata_t &inputdata ) +{ + m_bDisabled = !m_bDisabled; +} + + +//----------------------------------------------------------------------------- +// Purpose: Input handler that fires its respective OnRegisteredActivate with the stored activator. +//----------------------------------------------------------------------------- +void CLogicRegisterActivator::InputFireRegisteredAsActivator1( inputdata_t &inputdata ) +{ + m_OnRegisteredActivate[0].FireOutput(m_hActivator, this); +} + +//----------------------------------------------------------------------------- +// Purpose: Input handler that fires its respective OnRegisteredActivate with the stored activator. +//----------------------------------------------------------------------------- +void CLogicRegisterActivator::InputFireRegisteredAsActivator2( inputdata_t &inputdata ) +{ + m_OnRegisteredActivate[1].FireOutput(m_hActivator, this); +} + +//----------------------------------------------------------------------------- +// Purpose: Input handler that fires its respective OnRegisteredActivate with the stored activator. +//----------------------------------------------------------------------------- +void CLogicRegisterActivator::InputFireRegisteredAsActivator3( inputdata_t &inputdata ) +{ + m_OnRegisteredActivate[2].FireOutput(m_hActivator, this); +} + +//----------------------------------------------------------------------------- +// Purpose: Input handler that fires its respective OnRegisteredActivate with the stored activator. +//----------------------------------------------------------------------------- +void CLogicRegisterActivator::InputFireRegisteredAsActivator4( inputdata_t &inputdata ) +{ + m_OnRegisteredActivate[3].FireOutput(m_hActivator, this); +} + +//----------------------------------------------------------------------------- +// Purpose: Input handler that stores an entity as the activator. +//----------------------------------------------------------------------------- +void CLogicRegisterActivator::InputRegisterEntity( inputdata_t &inputdata ) +{ + m_hActivator = inputdata.value.Entity(); +} diff --git a/src/game/server/mapbase/logic_skill.cpp b/src/game/server/mapbase/logic_skill.cpp new file mode 100644 index 00000000..2d47a7cb --- /dev/null +++ b/src/game/server/mapbase/logic_skill.cpp @@ -0,0 +1,66 @@ +//========= Mapbase - https://github.com/mapbase-source/source-sdk-2013 ============// +// +// Purpose: Controls and detects difficulty level changes +// +//============================================================================= + +#include "cbase.h" + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +class CLogicSkill : public CLogicalEntity +{ + DECLARE_CLASS( CLogicSkill, CLogicalEntity ); + +private: + // Inputs + void InputTest( inputdata_t &inputdata ); + void InputStartListening( inputdata_t &inputdata ) {m_bListeningForSkillChanges = true;} + void InputStopListening( inputdata_t &inputdata ) {m_bListeningForSkillChanges = false;} + + // Used by gamerules to fire OnSkillChanged. + // Passes the level it changed to as well. + void InputSkillLevelChanged(inputdata_t &inputdata) { m_bListeningForSkillChanges ? m_OnSkillChanged.Set(inputdata.value.Int(), inputdata.pActivator, this) : (void)0; } + + COutputInt m_OnSkillChanged; + COutputEvent m_OnEasy; + COutputEvent m_OnMedium; + COutputEvent m_OnHard; + + bool m_bListeningForSkillChanges; + + DECLARE_DATADESC(); +}; + +LINK_ENTITY_TO_CLASS(logic_skill, CLogicSkill); + +BEGIN_DATADESC( CLogicSkill ) + + DEFINE_KEYFIELD( m_bListeningForSkillChanges, FIELD_BOOLEAN, "ListenForSkillChange" ), + + // Inputs + DEFINE_INPUTFUNC( FIELD_VOID, "Test", InputTest ), + DEFINE_INPUTFUNC( FIELD_VOID, "StartListening", InputStartListening ), + DEFINE_INPUTFUNC( FIELD_VOID, "StopListening", InputStopListening ), + + DEFINE_INPUTFUNC( FIELD_INTEGER, "SkillLevelChanged", InputSkillLevelChanged ), + + DEFINE_OUTPUT( m_OnSkillChanged, "OnSkillChanged" ), + DEFINE_OUTPUT( m_OnEasy, "OnEasy" ), + DEFINE_OUTPUT( m_OnMedium, "OnNormal" ), + DEFINE_OUTPUT( m_OnHard, "OnHard" ), + +END_DATADESC() + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CLogicSkill::InputTest( inputdata_t &inputdata ) +{ + switch (g_pGameRules->GetSkillLevel()) + { + case SKILL_EASY: m_OnEasy.FireOutput(this, this); break; + case SKILL_MEDIUM: m_OnMedium.FireOutput(this, this); break; + case SKILL_HARD: m_OnHard.FireOutput(this, this); break; + } +} diff --git a/src/game/server/mapbase/logic_substring.cpp b/src/game/server/mapbase/logic_substring.cpp new file mode 100644 index 00000000..7c7d153b --- /dev/null +++ b/src/game/server/mapbase/logic_substring.cpp @@ -0,0 +1,110 @@ +//====================== By Holly Liberatore / MoofEMP ======================// +// +// Purpose: Takes a string parameter and returns a substring defined by keyvalues +// +//===========================================================================// + +#include "cbase.h" + +#define SF_SUBSTRING_START_DISABLED (1 << 0) + +class CLogicSubstring : public CLogicalEntity +{ +public: + DECLARE_CLASS( CLogicSubstring, CLogicalEntity ); + DECLARE_DATADESC(); + + CLogicSubstring( void ) { } + + void InputDisable( inputdata_t &inputData ); + void InputEnable( inputdata_t &inputData ); + void InputInValue( inputdata_t &inputData ); + void InputSetLength( inputdata_t &inputData ); + void InputSetStartPos( inputdata_t &inputData ); + + void Spawn(void); + +private: + int m_nLength; + int m_nStartPos; + + bool m_bEnabled; + + COutputString m_OutValue; +}; + +LINK_ENTITY_TO_CLASS( logic_substring, CLogicSubstring ); + +BEGIN_DATADESC( CLogicSubstring ) + + DEFINE_FIELD( m_bEnabled, FIELD_BOOLEAN ), + + DEFINE_KEYFIELD(m_nLength, FIELD_INTEGER, "length" ), + DEFINE_KEYFIELD(m_nStartPos, FIELD_INTEGER, "startPos" ), + + DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ), + DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ), + DEFINE_INPUTFUNC( FIELD_STRING, "InValue", InputInValue ), + DEFINE_INPUTFUNC( FIELD_INTEGER, "SetLength", InputSetLength ), + DEFINE_INPUTFUNC( FIELD_INTEGER, "SetStartPos", InputSetStartPos ), + + DEFINE_OUTPUT( m_OutValue, "OutValue" ), + +END_DATADESC() + +//----------------------------------------------------------------------------- +// Purpose: Disable or enable the entity (disabling prevents any input functions from running) +//----------------------------------------------------------------------------- +void CLogicSubstring::InputDisable( inputdata_t &inputData ) { m_bEnabled = false; } +void CLogicSubstring::InputEnable ( inputdata_t &inputData ) { m_bEnabled = true ; } + +//----------------------------------------------------------------------------- +// Purpose: Trim substring from input +// Output: Substring +//----------------------------------------------------------------------------- +void CLogicSubstring::InputInValue( inputdata_t &inputData ) +{ + if( !m_bEnabled ) return; + + int inputLength = Q_strlen(inputData.value.String()); + int startPosCheck = m_nStartPos < 0 ? inputLength + m_nStartPos : m_nStartPos; + if( startPosCheck < 0 ) + { + startPosCheck = 0; + } + int lengthCheck = (m_nLength < 0 || m_nLength > inputLength - startPosCheck ? inputLength - startPosCheck : m_nLength) + 1; + if( lengthCheck < 1 || startPosCheck > inputLength ) + { + m_OutValue.Set( MAKE_STRING(""), inputData.pActivator, this ); + return; + } + char* strOutValue = (char*)malloc( lengthCheck ); + Q_strncpy( strOutValue, inputData.value.String() + startPosCheck, lengthCheck ); + m_OutValue.Set( AllocPooledString(strOutValue), inputData.pActivator, this ); + free(strOutValue); +} + +//----------------------------------------------------------------------------- +// Purpose: Setter methods for keyvalues +//----------------------------------------------------------------------------- +void CLogicSubstring::InputSetLength( inputdata_t &inputData ) +{ + if( !m_bEnabled ) return; + + m_nLength = inputData.value.Int(); +} + +void CLogicSubstring::InputSetStartPos( inputdata_t &inputData ) +{ + if( !m_bEnabled ) return; + + m_nStartPos = inputData.value.Int(); +} + +//----------------------------------------------------------------------------- +// Purpose: Respond to spawnflags when entity spawns +//----------------------------------------------------------------------------- +void CLogicSubstring::Spawn( void ) +{ + m_bEnabled = !HasSpawnFlags( SF_SUBSTRING_START_DISABLED ); +} diff --git a/src/game/server/mapbase/point_advanced_finder.cpp b/src/game/server/mapbase/point_advanced_finder.cpp new file mode 100644 index 00000000..27f9f194 --- /dev/null +++ b/src/game/server/mapbase/point_advanced_finder.cpp @@ -0,0 +1,393 @@ +//========= Mapbase - https://github.com/mapbase-source/source-sdk-2013 ============// +// +// Purpose: A ballsier version of point_entity_finder. +// Originally called logic_entityfinder because a lot of this was written +// before I knew about point_entity_finder in the first place. +// +//============================================================================= + +#include "cbase.h" +#include "eventqueue.h" +#include "filters.h" +#include "saverestore_utlvector.h" + +// Uses a CUtlVector instead of an array. +#define ENTITYFINDER_UTLVECTOR 1 + +// Delays outputs directly instead of relying on an input. +#define ENTITYFINDER_OUTPUT_DELAY 1 + +#if !ENTITYFINDER_STATIC_ARRAY +#define ENTITYFINDER_MAX_STORED_ENTITIES 64 +#endif + +//----------------------------------------------------------------------------- +// Purpose: Entity finder that uses filters and other criteria to search the level for a specific entity. +//----------------------------------------------------------------------------- +class CPointAdvancedFinder : public CLogicalEntity +{ + DECLARE_CLASS(CPointAdvancedFinder, CLogicalEntity); + +private: + // Inputs + void InputSearch(inputdata_t &inputdata); + void InputSetSearchFilter(inputdata_t &inputdata); + void InputSetSearchPoint(inputdata_t &inputdata); + void InputSetRadius(inputdata_t &inputdata) { m_flRadius = inputdata.value.Float(); } + void InputSetMaxResults(inputdata_t &inputdata) { m_iNumSearches = inputdata.value.Int(); } + void InputSetOutputDelay(inputdata_t &inputdata) { m_flOutputDelay = inputdata.value.Float(); } + void InputSetFiringMethod(inputdata_t &inputdata) { m_iFiringMethod = inputdata.value.Int(); } +#ifndef ENTITYFINDER_OUTPUT_DELAY + void InputFoundEntity(inputdata_t &inputdata); +#endif + + void Spawn(); + + Vector GetSearchOrigin(); + bool SearchForEntities(inputdata_t &inputdata); + void FoundEntity(CBaseEntity *pEntity, inputdata_t &inputdata); + + + string_t m_iszSearchFilter; + CHandle m_hSearchFilter; + + string_t m_iszSearchPoint; + EHANDLE m_hSearchPoint; + + float m_flRadius; + int m_iNumSearches; + int m_iFiringMethod; + enum + { + FIRINGMETHOD_NONE = -1, // -1 for point_entity_finder compatibility + FIRINGMETHOD_NEAREST, + FIRINGMETHOD_FARTHEST, + FIRINGMETHOD_RANDOM, + }; + + float m_flOutputDelay; + float m_flLastOutputDelay = 0.0f; + +#if ENTITYFINDER_UTLVECTOR + CUtlVector m_StoredEntities; +#else + CBaseEntity *m_StoredEntities[ENTITYFINDER_MAX_STORED_ENTITIES]; +#endif + + // Outputs + COutputEHANDLE m_OnFoundEntity; + COutputEvent m_OnSearchFailed; + + DECLARE_DATADESC(); +}; + +LINK_ENTITY_TO_CLASS(point_advanced_finder, CPointAdvancedFinder); + + +BEGIN_DATADESC(CPointAdvancedFinder) + + // Keys + DEFINE_KEYFIELD(m_iszSearchFilter, FIELD_STRING, "SearchFilter"), + DEFINE_FIELD(m_hSearchFilter, FIELD_EHANDLE), + DEFINE_KEYFIELD(m_iszSearchPoint, FIELD_STRING, "SearchPoint"), + DEFINE_FIELD(m_hSearchPoint, FIELD_EHANDLE), + DEFINE_KEYFIELD(m_flRadius, FIELD_FLOAT, "radius"), + DEFINE_KEYFIELD(m_iNumSearches, FIELD_INTEGER, "NumberOfEntities"), + DEFINE_KEYFIELD(m_flOutputDelay, FIELD_FLOAT, "OutputDelay"), + DEFINE_KEYFIELD(m_iFiringMethod, FIELD_INTEGER, "Method"), +#if ENTITYFINDER_UTLVECTOR + DEFINE_UTLVECTOR( m_StoredEntities, FIELD_CLASSPTR ), +#else + DEFINE_ARRAY(m_StoredEntities, FIELD_CLASSPTR, ENTITYFINDER_MAX_STORED_ENTITIES), +#endif + DEFINE_FIELD(m_flLastOutputDelay, FIELD_FLOAT), + + // Inputs + DEFINE_INPUTFUNC(FIELD_VOID, "BeginSearch", InputSearch), + DEFINE_INPUTFUNC(FIELD_STRING, "SetSearchFilter", InputSetSearchFilter), + DEFINE_INPUTFUNC(FIELD_STRING, "SetSearchPoint", InputSetSearchPoint), + DEFINE_INPUTFUNC(FIELD_FLOAT, "SetRadius", InputSetRadius), + DEFINE_INPUTFUNC(FIELD_INTEGER, "SetMaxResults", InputSetMaxResults), + DEFINE_INPUTFUNC(FIELD_FLOAT, "SetOutputDelay", InputSetOutputDelay), + DEFINE_INPUTFUNC(FIELD_INTEGER, "SetFiringMethod", InputSetFiringMethod), +#ifndef ENTITYFINDER_OUTPUT_DELAY + DEFINE_INPUTFUNC(FIELD_EHANDLE, "FoundEntity", InputFoundEntity), +#endif + + // Outputs + DEFINE_OUTPUT(m_OnFoundEntity, "OnFoundEntity"), + DEFINE_OUTPUT(m_OnSearchFailed, "OnSearchFailed"), + +END_DATADESC() + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPointAdvancedFinder::Spawn() +{ + if (m_iszSearchFilter == NULL_STRING) + { + Warning("%s (%s) has no search filter!\n", GetClassname(), GetDebugName()); + UTIL_Remove(this); + return; + } + + m_hSearchFilter = dynamic_cast(gEntList.FindEntityByName( NULL, m_iszSearchFilter, this )); + + m_hSearchPoint = gEntList.FindEntityByName( NULL, m_iszSearchPoint, this ); + + BaseClass::Spawn(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPointAdvancedFinder::InputSearch(inputdata_t &inputdata) +{ + SearchForEntities(inputdata); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPointAdvancedFinder::InputSetSearchFilter(inputdata_t &inputdata) +{ + m_iszSearchFilter = inputdata.value.StringID(); + m_hSearchFilter = dynamic_cast(gEntList.FindEntityByName( NULL, m_iszSearchFilter, this, inputdata.pActivator, inputdata.pCaller )); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPointAdvancedFinder::InputSetSearchPoint(inputdata_t &inputdata) +{ + m_iszSearchPoint = inputdata.value.StringID(); + m_hSearchPoint = gEntList.FindEntityByName( NULL, m_iszSearchPoint, this, inputdata.pActivator, inputdata.pCaller ); +} + +#ifndef ENTITYFINDER_OUTPUT_DELAY +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPointAdvancedFinder::InputFoundEntity(inputdata_t &inputdata) +{ + CBaseEntity *pEntity = inputdata.value.Entity(); + if (!pEntity) + return; + + m_OnFoundEntity.Set(pEntity, pEntity, inputdata.pCaller); +} +#endif + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +inline Vector CPointAdvancedFinder::GetSearchOrigin() +{ + if (m_hSearchPoint == NULL) + m_hSearchPoint = this; + + return m_hSearchPoint->GetAbsOrigin(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CPointAdvancedFinder::SearchForEntities(inputdata_t &inputdata) +{ + if ( !m_hSearchFilter ) + return false; + + m_flLastOutputDelay = 0.0f; + + int iNumResults = 0; + + float flRadius = m_flRadius * m_flRadius; + + bool bShouldStoreEntities = (m_iFiringMethod > FIRINGMETHOD_NONE || m_flOutputDelay > 0); +#if !ENTITYFINDER_UTLVECTOR + if (bShouldStoreEntities) + { + if (m_iNumSearches > ENTITYFINDER_MAX_STORED_ENTITIES) + { + Warning("%s (%s) needs to store entities, but we're asked to look for more than the maximum, %i, with %i! Reducing to max...\n", GetClassname(), GetDebugName(), ENTITYFINDER_MAX_STORED_ENTITIES, m_iNumSearches); + m_iNumSearches = ENTITYFINDER_MAX_STORED_ENTITIES; + } + else if (m_iNumSearches == 0) + { + m_iNumSearches = ENTITYFINDER_MAX_STORED_ENTITIES; + } + } + else +#endif + if (m_iNumSearches == 0) + { + m_iNumSearches = MAX_EDICTS; + } + + const CEntInfo *pInfo = gEntList.FirstEntInfo(); + + for ( ;pInfo; pInfo = pInfo->m_pNext ) + { + CBaseEntity *ent = (CBaseEntity *)pInfo->m_pEntity; + if ( !ent ) + { + DevWarning( "NULL entity in global entity list!\n" ); + continue; + } + + if (iNumResults >= m_iNumSearches) + break; + + if ( m_hSearchFilter && !m_hSearchFilter->PassesFilter( this, ent ) ) + continue; + + if (flRadius > 0 && (ent->GetAbsOrigin() - GetSearchOrigin()).LengthSqr() > flRadius) + continue; + + if ( bShouldStoreEntities ) + { + // Fire it later +#if ENTITYFINDER_UTLVECTOR + m_StoredEntities.AddToTail(ent); +#else + m_StoredEntities[ iNumResults ] = ent; +#endif + } + else + { + // Fire it now + FoundEntity(ent, inputdata); + } + + iNumResults++; + } + + if (iNumResults > 0) + { + if (bShouldStoreEntities) + { + if (m_iFiringMethod == FIRINGMETHOD_NEAREST || m_iFiringMethod == FIRINGMETHOD_FARTHEST) + { + bool bNotFarthest = m_iFiringMethod != FIRINGMETHOD_FARTHEST; + float flMaxDist = m_flRadius; + float flMinDist = 0; + + if (flMaxDist == 0) + flMaxDist = MAX_TRACE_LENGTH; + + for (int iCur = 0; iCur < iNumResults; iCur++) + { + float flClosest = bNotFarthest ? flMaxDist : 0; + float flDistance = 0; + CBaseEntity *pClosest = NULL; + for (int i = 0; i < iNumResults; i++) + { + if (!m_StoredEntities[i]) + continue; + + flDistance = (m_StoredEntities[i]->GetAbsOrigin() - GetSearchOrigin()).Length(); + if (flDistance < flMaxDist && flDistance > flMinDist) + { + if (bNotFarthest ? (flDistance < flClosest) : (flDistance > flClosest)) + { + pClosest = m_StoredEntities[i]; + flClosest = flDistance; + } + } + } + + if (pClosest) + { + bNotFarthest ? flMinDist = flClosest : flMaxDist = flClosest; + FoundEntity(pClosest, inputdata); + } + else + { + DevWarning("%s (%s): NO CLOSEST!!!\n", GetClassname(), GetDebugName()); + } + } + } + else if (m_iFiringMethod == FIRINGMETHOD_RANDOM) + { + // This could probaly be better... + CUtlVector iResultIndices; + for (int i = 0; i < iNumResults; i++) + iResultIndices.AddToTail(i); + + while (iResultIndices.Count() > 0) + { + int index = iResultIndices[RandomInt(0, iResultIndices.Count() - 1)]; + + if (m_StoredEntities[index]) + { + FoundEntity(m_StoredEntities[index], inputdata); + } + else + { + DevWarning("%s (%s): Found entity is null: %i\n", GetClassname(), GetDebugName(), index); + } + + iResultIndices.FindAndRemove(index); + } + } + else if (m_iFiringMethod == FIRINGMETHOD_NONE) + { + for (int i = 0; i < iNumResults; i++) + { + if (m_StoredEntities[i]) + { + FoundEntity(m_StoredEntities[i], inputdata); + } + else + { + DevWarning("%s (%s): Found entity is null: %i\n", GetClassname(), GetDebugName(), i); + } + } + } + + m_StoredEntities.RemoveAll(); + } + return true; + } + else + { + m_OnSearchFailed.FireOutput(inputdata.pActivator, inputdata.pCaller); + return false; + } + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPointAdvancedFinder::FoundEntity(CBaseEntity *pEntity, inputdata_t &inputdata) +{ +#ifdef ENTITYFINDER_OUTPUT_DELAY + variant_t variant; + variant.SetEntity(pEntity); + + DevMsg("%s (%s): Firing entity output in %f, added from %f\n", GetClassname(), GetDebugName(), m_flLastOutputDelay, m_flOutputDelay); + m_OnFoundEntity.FireOutput(variant, pEntity, this, m_flLastOutputDelay); +#else + if (m_flOutputDelay == 0) + { + // Just fire it now + m_OnFoundEntity.Set(pEntity, pEntity, inputdata.pCaller); + DevMsg("%s (%s): Delay 0, firing now\n", GetClassname(), GetDebugName()); + return; + } + + //if (m_flLastOutputDelay == 0) + //m_flLastOutputDelay = gpGlobals->curtime; + + variant_t variant; + variant.SetEntity(pEntity); + + DevMsg("%s (%s): Firing entity output in %f, added from %f\n", GetClassname(), GetDebugName(), m_flLastOutputDelay, m_flOutputDelay); + g_EventQueue.AddEvent(this, "FoundEntity", variant, m_flLastOutputDelay, inputdata.pActivator, inputdata.pCaller); +#endif + + m_flLastOutputDelay += m_flOutputDelay; +} diff --git a/src/game/server/mapbase/point_copy_size.cpp b/src/game/server/mapbase/point_copy_size.cpp new file mode 100644 index 00000000..379a6579 --- /dev/null +++ b/src/game/server/mapbase/point_copy_size.cpp @@ -0,0 +1,143 @@ +//========= Mapbase - https://github.com/mapbase-source/source-sdk-2013 ============// +// +// Purpose: Copies size. +// +//============================================================================= + +#include "cbase.h" + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class CPointCopySize : public CLogicalEntity +{ + DECLARE_CLASS( CPointCopySize, CLogicalEntity ); +private: + // The entity to copy the size from. + // m_target is the target that receives it. + string_t m_iszSizeSource; + EHANDLE m_hSizeSource; + EHANDLE m_hTarget; + + float m_flScale; + + void CopySize(CBaseEntity *pSource, CBaseEntity *pTarget); + + // Inputs + void InputCopySize( inputdata_t &inputdata ); + void InputCopySizeToEntity( inputdata_t &inputdata ); + void InputCopySizeFromEntity( inputdata_t &inputdata ); + + void InputSetTarget( inputdata_t &inputdata ) { BaseClass::InputSetTarget(inputdata); m_hTarget = NULL; } + void InputSetSource( inputdata_t &inputdata ) { m_iszSizeSource = inputdata.value.StringID(); m_hSizeSource = NULL; } + + // Outputs + COutputEvent m_OnCopy; + + DECLARE_DATADESC(); +}; + +LINK_ENTITY_TO_CLASS(point_copy_size, CPointCopySize); + + +BEGIN_DATADESC( CPointCopySize ) + + // Keys + DEFINE_KEYFIELD(m_iszSizeSource, FIELD_STRING, "source"), + DEFINE_FIELD(m_hSizeSource, FIELD_EHANDLE), + DEFINE_FIELD(m_hTarget, FIELD_EHANDLE), + + DEFINE_INPUT(m_flScale, FIELD_FLOAT, "SetScale"), + + // Inputs + DEFINE_INPUTFUNC( FIELD_VOID, "CopySize", InputCopySize ), + DEFINE_INPUTFUNC( FIELD_EHANDLE, "CopySizeToEntity", InputCopySizeToEntity ), + DEFINE_INPUTFUNC( FIELD_EHANDLE, "CopySizeFromEntity", InputCopySizeFromEntity ), + + DEFINE_INPUTFUNC( FIELD_STRING, "SetSource", InputSetSource ), + + // Outputs + DEFINE_OUTPUT(m_OnCopy, "OnCopy"), + +END_DATADESC() + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPointCopySize::CopySize(CBaseEntity *pSource, CBaseEntity *pTarget) +{ + const Vector cvecAlignMins = pSource->WorldAlignMins(); + const Vector cvecAlignMaxs = pSource->WorldAlignMaxs(); + + Vector vecAlignMins = Vector(cvecAlignMins); + Vector vecAlignMaxs = Vector(cvecAlignMaxs); + + if (m_flScale != 0.0f && m_flScale != 1.0f) + { + vecAlignMins *= m_flScale; + vecAlignMaxs *= m_flScale; + } + + pTarget->SetCollisionBounds(vecAlignMins, vecAlignMins); + m_OnCopy.FireOutput(pTarget, this); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPointCopySize::InputCopySize( inputdata_t &inputdata ) +{ + if (!m_hSizeSource) + m_hSizeSource = gEntList.FindEntityByName(NULL, STRING(m_iszSizeSource), this, inputdata.pActivator, inputdata.pCaller); + if (!m_hTarget) + m_hTarget = gEntList.FindEntityByName(NULL, STRING(m_target), this, inputdata.pActivator, inputdata.pCaller); + + if (!m_hSizeSource || !m_hTarget) + { + Warning("%s (%s): Could not find %s\n", GetClassname(), GetDebugName(), !m_hSizeSource ? "size source" : "target to copy size to"); + return; + } + + CopySize(m_hSizeSource, m_hTarget); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPointCopySize::InputCopySizeToEntity( inputdata_t &inputdata ) +{ + if (!m_hSizeSource) + m_hSizeSource = gEntList.FindEntityByName(NULL, STRING(m_iszSizeSource), this, inputdata.pActivator, inputdata.pCaller); + + if (!m_hSizeSource) + { + Warning("%s (%s): Could not find size source\n", GetClassname(), GetDebugName()); + return; + } + + if (!inputdata.value.Entity()) + return; + + CopySize(m_hSizeSource, inputdata.value.Entity()); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPointCopySize::InputCopySizeFromEntity( inputdata_t &inputdata ) +{ + if (!m_hTarget) + m_hTarget = gEntList.FindEntityByName(NULL, STRING(m_target), this, inputdata.pActivator, inputdata.pCaller); + + if (!m_hTarget) + { + Warning("%s (%s): Could not find target to copy size to\n", GetClassname(), GetDebugName()); + return; + } + + if (!inputdata.value.Entity()) + return; + + CopySize(inputdata.value.Entity(), m_hTarget); +} diff --git a/src/game/server/mapbase/point_damageinfo.cpp b/src/game/server/mapbase/point_damageinfo.cpp new file mode 100644 index 00000000..74e7b406 --- /dev/null +++ b/src/game/server/mapbase/point_damageinfo.cpp @@ -0,0 +1,394 @@ +//========= Mapbase - https://github.com/mapbase-source/source-sdk-2013 ============// +// +// Purpose: A special entity for afflicting damage as specific as possible. +// +//============================================================================= + +#include "cbase.h" + + +//----------------------------------------------------------------------------- +// Purpose: Advanced damage information afflicting. +//----------------------------------------------------------------------------- +class CPointDamageInfo : public CLogicalEntity +{ + DECLARE_CLASS( CPointDamageInfo, CLogicalEntity ); + DECLARE_DATADESC(); + + virtual bool KeyValue( const char *szKeyName, const char *szValue ); + virtual bool KeyValue( const char *szKeyName, const Vector &vecValue ); + virtual bool GetKeyValue( const char *szKeyName, char *szValue, int iMaxLen ); + +public: + CPointDamageInfo(); + + CTakeDamageInfo m_info; + + // The actual inflictor, attacker, and weapon in CTakeDamageInfo are direct entity pointers. + // This is needed to ensure !activator functionality, entities that might not exist yet, etc. + string_t m_iszInflictor; + string_t m_iszAttacker; + string_t m_iszWeapon; + + // The maximum number of entities to be damaged if they share m_target's targetname or classname. + int m_iMaxEnts; + + // Suppresses death sounds in the best way we possibly can. + bool m_bSuppressDeathSound; + + //bool m_bDisabled; + + // Inputs + void InputSetInflictor( inputdata_t &inputdata ) { m_iszInflictor = inputdata.value.StringID(); } //{ m_info.SetInflictor(inputdata.value.Entity()); } + void InputSetAttacker( inputdata_t &inputdata ) { m_iszAttacker = inputdata.value.StringID(); } //{ m_info.SetAttacker(inputdata.value.Entity()); } + void InputSetWeapon( inputdata_t &inputdata ) { m_iszWeapon = inputdata.value.StringID(); } //{ m_info.SetWeapon(inputdata.value.Entity()); } + + void InputSetDamage( inputdata_t &inputdata ) { m_info.SetDamage(inputdata.value.Float()); } + void InputSetMaxDamage( inputdata_t &inputdata ) { m_info.SetMaxDamage(inputdata.value.Float()); } + void InputSetDamageBonus( inputdata_t &inputdata ) { m_info.SetDamageBonus(inputdata.value.Float()); } + + void InputSetDamageType( inputdata_t &inputdata ) { m_info.SetDamageType(inputdata.value.Int()); } + void InputSetDamageCustom( inputdata_t &inputdata ) { m_info.SetDamageCustom(inputdata.value.Int()); } + void InputSetDamageStats( inputdata_t &inputdata ) { m_info.SetDamageStats(inputdata.value.Int()); } + void InputSetForceFriendlyFire( inputdata_t &inputdata ) { m_info.SetForceFriendlyFire(inputdata.value.Bool()); } + + void InputSetAmmoType( inputdata_t &inputdata ) { m_info.SetAmmoType(inputdata.value.Int()); } + + void InputSetPlayerPenetrationCount( inputdata_t &inputdata ) { m_info.SetPlayerPenetrationCount( inputdata.value.Int() ); } + void InputSetDamagedOtherPlayers( inputdata_t &inputdata ) { m_info.SetDamagedOtherPlayers( inputdata.value.Int() ); } + + void InputSetDamageForce( inputdata_t &inputdata ) { Vector vec; inputdata.value.Vector3D(vec); m_info.SetDamageForce(vec); } + void InputSetDamagePosition( inputdata_t &inputdata ) { Vector vec; inputdata.value.Vector3D(vec); m_info.SetDamagePosition(vec); } + void InputSetReportedPosition( inputdata_t &inputdata ) { Vector vec; inputdata.value.Vector3D(vec); m_info.SetReportedPosition(vec); } + + void HandleDamage(CBaseEntity *pTarget); + + void ApplyDamage( const char *target, inputdata_t &inputdata ); + void ApplyDamage( CBaseEntity *target, inputdata_t &inputdata ); + + void InputApplyDamage( inputdata_t &inputdata ); + void InputApplyDamageToEntity( inputdata_t &inputdata ); + + // Outputs + COutputEvent m_OnApplyDamage; + COutputEvent m_OnApplyDeath; +}; + +LINK_ENTITY_TO_CLASS(point_damageinfo, CPointDamageInfo); + + +BEGIN_DATADESC( CPointDamageInfo ) + + DEFINE_EMBEDDED( m_info ), + + // Keys + DEFINE_KEYFIELD( m_iszInflictor, FIELD_STRING, "Inflictor" ), + DEFINE_KEYFIELD( m_iszAttacker, FIELD_STRING, "Attacker" ), + DEFINE_KEYFIELD( m_iszWeapon, FIELD_STRING, "Weapon" ), + + DEFINE_KEYFIELD( m_iMaxEnts, FIELD_INTEGER, "MaxEnts" ), + DEFINE_KEYFIELD( m_bSuppressDeathSound, FIELD_BOOLEAN, "SuppressDeathSound" ), + + // Inputs + DEFINE_INPUTFUNC( FIELD_STRING, "SetInflictor", InputSetInflictor ), + DEFINE_INPUTFUNC( FIELD_STRING, "SetAttacker", InputSetAttacker ), + DEFINE_INPUTFUNC( FIELD_STRING, "SetWeapon", InputSetWeapon ), + DEFINE_INPUTFUNC( FIELD_FLOAT, "SetDamage", InputSetDamage ), + DEFINE_INPUTFUNC( FIELD_FLOAT, "SetMaxDamage", InputSetMaxDamage ), + DEFINE_INPUTFUNC( FIELD_FLOAT, "SetDamageBonus", InputSetDamageBonus ), + DEFINE_INPUTFUNC( FIELD_INTEGER, "SetDamageType", InputSetDamageType ), + DEFINE_INPUTFUNC( FIELD_INTEGER, "SetDamageCustom", InputSetDamageCustom ), + DEFINE_INPUTFUNC( FIELD_INTEGER, "SetDamageStats", InputSetDamageStats ), + DEFINE_INPUTFUNC( FIELD_BOOLEAN, "SetForceFriendlyFire", InputSetForceFriendlyFire ), + DEFINE_INPUTFUNC( FIELD_INTEGER, "SetAmmoType", InputSetAmmoType ), + DEFINE_INPUTFUNC( FIELD_INTEGER, "SetPlayerPenetrationCount", InputSetPlayerPenetrationCount ), + DEFINE_INPUTFUNC( FIELD_INTEGER, "SetDamagedOtherPlayers", InputSetDamagedOtherPlayers ), + DEFINE_INPUTFUNC( FIELD_VECTOR, "SetDamageForce", InputSetDamageForce ), + DEFINE_INPUTFUNC( FIELD_VECTOR, "SetDamagePosition", InputSetDamagePosition ), + DEFINE_INPUTFUNC( FIELD_VECTOR, "SetReportedPosition", InputSetReportedPosition ), + + DEFINE_INPUTFUNC( FIELD_VOID, "ApplyDamage", InputApplyDamage ), + DEFINE_INPUTFUNC( FIELD_EHANDLE, "ApplyDamageToEntity", InputApplyDamageToEntity ), + + // Outputs + DEFINE_OUTPUT(m_OnApplyDamage, "OnApplyDamage"), + DEFINE_OUTPUT(m_OnApplyDeath, "OnApplyDeath"), + +END_DATADESC() + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CPointDamageInfo::CPointDamageInfo() +{ + m_info = CTakeDamageInfo(this, this, 24, DMG_GENERIC); + m_iMaxEnts = 1; +} + +//----------------------------------------------------------------------------- +// Purpose: Cache user entity field values until spawn is called. +// Input : szKeyName - Key to handle. +// szValue - Value for key. +// Output : Returns true if the key was handled, false if not. +//----------------------------------------------------------------------------- +bool CPointDamageInfo::KeyValue( const char *szKeyName, const char *szValue ) +{ + /*if (FStrEq(szKeyName, "Inflictor")) + m_info.SetInflictor(gEntList.FindEntityByName(NULL, szValue, this, NULL, NULL)); + else if (FStrEq(szKeyName, "Attacker")) + m_info.SetAttacker(gEntList.FindEntityByName(NULL, szValue, this, NULL, NULL)); + else if (FStrEq(szKeyName, "Weapon")) + m_info.SetWeapon(gEntList.FindEntityByName(NULL, szValue, this, NULL, NULL)); + + else*/ if (FStrEq(szKeyName, "Damage")) + m_info.SetDamage(atof(szValue)); + else if (FStrEq(szKeyName, "MaxDamage")) + m_info.SetMaxDamage(atof(szValue)); + else if (FStrEq(szKeyName, "DamageBonus")) + m_info.SetDamageBonus(atof(szValue)); + + else if (FStrEq(szKeyName, "DamageType") || FStrEq(szKeyName, "DamagePresets")) + m_info.AddDamageType(atoi(szValue)); + else if (FStrEq(szKeyName, "DamageOr")) + m_info.AddDamageType(atoi(szValue)); + else if (FStrEq(szKeyName, "DamageCustom")) + m_info.SetDamageCustom(atoi(szValue)); + else if (FStrEq(szKeyName, "DamageStats")) + m_info.SetDamageStats(atoi(szValue)); + else if (FStrEq(szKeyName, "ForceFriendlyFire")) + m_info.SetForceFriendlyFire(FStrEq(szValue, "1")); + + else if (FStrEq(szKeyName, "AmmoType")) + m_info.SetAmmoType(atoi(szValue)); + + else if (FStrEq(szKeyName, "PlayerPenetrationCount")) + m_info.SetPlayerPenetrationCount(atoi(szValue)); + else if (FStrEq(szKeyName, "DamagedOtherPlayers")) + m_info.SetDamagedOtherPlayers(atoi(szValue)); + + else + { + if (!BaseClass::KeyValue(szKeyName, szValue)) + { + // Ripped from variant_t::Convert()... + Vector tmpVec = vec3_origin; + if (sscanf(szValue, "[%f %f %f]", &tmpVec[0], &tmpVec[1], &tmpVec[2]) == 0) + { + // Try sucking out 3 floats with no []s + sscanf(szValue, "%f %f %f", &tmpVec[0], &tmpVec[1], &tmpVec[2]); + } + return KeyValue(szKeyName, tmpVec); + } + } + + return true; +} + +bool CPointDamageInfo::KeyValue( const char *szKeyName, const Vector &vecValue ) +{ + if (FStrEq(szKeyName, "DamageForce")) + m_info.SetDamageForce(vecValue); + else if (FStrEq(szKeyName, "DamagePosition")) + m_info.SetDamagePosition(vecValue); + else if (FStrEq(szKeyName, "ReportedPosition")) + m_info.SetReportedPosition(vecValue); + else + return CBaseEntity::KeyValue( szKeyName, vecValue ); + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : +// Output : +//----------------------------------------------------------------------------- +bool CPointDamageInfo::GetKeyValue( const char *szKeyName, char *szValue, int iMaxLen ) +{ + /* + if (FStrEq(szKeyName, "Inflictor")) + Q_snprintf(szValue, iMaxLen, "%s", m_info.GetInflictor() ? "" : m_info.GetInflictor()->GetDebugName()); + else if (FStrEq(szKeyName, "Attacker")) + Q_snprintf(szValue, iMaxLen, "%s", m_info.GetAttacker() ? "" : m_info.GetAttacker()->GetDebugName()); + else if (FStrEq(szKeyName, "Weapon")) + Q_snprintf(szValue, iMaxLen, "%s", m_info.GetWeapon() ? "" : m_info.GetWeapon()->GetDebugName()); + + else*/ if (FStrEq(szKeyName, "Damage")) + Q_snprintf(szValue, iMaxLen, "%f", m_info.GetDamage()); + else if (FStrEq(szKeyName, "MaxDamage")) + Q_snprintf(szValue, iMaxLen, "%f", m_info.GetMaxDamage()); + else if (FStrEq(szKeyName, "DamageBonus")) + Q_snprintf(szValue, iMaxLen, "%f", m_info.GetDamageBonus()); + + else if (FStrEq(szKeyName, "DamageType")) + Q_snprintf(szValue, iMaxLen, "%i", m_info.GetDamageType()); + else if (FStrEq(szKeyName, "DamageCustom")) + Q_snprintf(szValue, iMaxLen, "%i", m_info.GetDamageCustom()); + else if (FStrEq(szKeyName, "DamageStats")) + Q_snprintf(szValue, iMaxLen, "%i", m_info.GetDamageStats()); + else if (FStrEq(szKeyName, "ForceFriendlyFire")) + Q_snprintf(szValue, iMaxLen, "%s", m_info.IsForceFriendlyFire() ? "1" : "0"); + + else if (FStrEq(szKeyName, "AmmoType")) + Q_snprintf(szValue, iMaxLen, "%i", m_info.GetAmmoType()); + + else if (FStrEq(szKeyName, "PlayerPenetrationCount")) + Q_snprintf(szValue, iMaxLen, "%i", m_info.GetPlayerPenetrationCount()); + else if (FStrEq(szKeyName, "DamagedOtherPlayers")) + Q_snprintf(szValue, iMaxLen, "%i", m_info.GetDamagedOtherPlayers()); + + else if (FStrEq(szKeyName, "DamageForce")) + Q_snprintf(szValue, iMaxLen, "%f %f %f", m_info.GetDamageForce().x, m_info.GetDamageForce().y, m_info.GetDamageForce().z); + else if (FStrEq(szKeyName, "DamagePosition")) + Q_snprintf(szValue, iMaxLen, "%f %f %f", m_info.GetDamagePosition().x, m_info.GetDamagePosition().y, m_info.GetDamagePosition().z); + else if (FStrEq(szKeyName, "ReportedPosition")) + Q_snprintf(szValue, iMaxLen, "%f %f %f", m_info.GetReportedPosition().x, m_info.GetReportedPosition().y, m_info.GetReportedPosition().z); + else + return BaseClass::GetKeyValue(szKeyName, szValue, iMaxLen); + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPointDamageInfo::HandleDamage( CBaseEntity *pTarget ) +{ + pTarget->TakeDamage(m_info); + m_OnApplyDamage.FireOutput(pTarget, this); + if (pTarget->m_lifeState == LIFE_DYING) + m_OnApplyDeath.FireOutput(pTarget, this); + + // This is the best we could do, as nodeathsound is exclusive to response system NPCs + if (m_bSuppressDeathSound) + pTarget->EmitSound("AI_BaseNPC.SentenceStop"); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : +// Output : +//----------------------------------------------------------------------------- +void CPointDamageInfo::ApplyDamage( const char *target, inputdata_t &inputdata ) +{ + if (m_iszAttacker != NULL_STRING) + m_info.SetAttacker( gEntList.FindEntityByName(NULL, STRING(m_iszAttacker), this, inputdata.pActivator, inputdata.pCaller) ); + + if (m_iszInflictor != NULL_STRING) + m_info.SetInflictor( gEntList.FindEntityByName(NULL, STRING(m_iszInflictor), this, inputdata.pActivator, inputdata.pCaller) ); + else + { + CBaseCombatCharacter *pBCC = ToBaseCombatCharacter(m_info.GetAttacker()); + if (pBCC != NULL && pBCC->GetActiveWeapon()) + { + m_info.SetInflictor(pBCC->GetActiveWeapon()); + } + } + + if (m_iszWeapon != NULL_STRING) + m_info.SetWeapon( gEntList.FindEntityByName(NULL, STRING(m_iszWeapon), this, inputdata.pActivator, inputdata.pCaller) ); + else + { + CBaseCombatCharacter *pBCC = ToBaseCombatCharacter(m_info.GetAttacker()); + if (pBCC != NULL && pBCC->GetActiveWeapon()) + { + m_info.SetWeapon(pBCC->GetActiveWeapon()); + } + } + + if (!m_info.GetAttacker()) + m_info.SetAttacker( this ); + + if (!m_info.GetInflictor()) + m_info.SetInflictor( this ); + + if (!m_info.GetWeapon()) + m_info.SetWeapon( this ); + + CBaseEntity *pTarget = NULL; + if (m_iMaxEnts > 0) + { + for (int i = 0; i < m_iMaxEnts; i++) + { + pTarget = gEntList.FindEntityGeneric(pTarget, target, this, inputdata.pActivator, inputdata.pCaller); + if (pTarget) + { + HandleDamage( pTarget ); + } + } + } + else + { + pTarget = gEntList.FindEntityGeneric(NULL, target, this, inputdata.pActivator, inputdata.pCaller); + while (pTarget) + { + HandleDamage( pTarget ); + pTarget = gEntList.FindEntityGeneric(pTarget, target, this, inputdata.pActivator, inputdata.pCaller); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Applies damage to a specific entity +// Input : +// Output : +//----------------------------------------------------------------------------- +void CPointDamageInfo::ApplyDamage( CBaseEntity *target, inputdata_t &inputdata ) +{ + if (!target) + return; + + if (m_iszAttacker != NULL_STRING) + m_info.SetAttacker( gEntList.FindEntityByName(NULL, STRING(m_iszAttacker), this, inputdata.pActivator, inputdata.pCaller) ); + else + m_info.SetAttacker( this ); + + if (m_iszInflictor != NULL_STRING) + m_info.SetInflictor( gEntList.FindEntityByName(NULL, STRING(m_iszInflictor), this, inputdata.pActivator, inputdata.pCaller) ); + else + { + CBaseCombatCharacter *pBCC = ToBaseCombatCharacter(m_info.GetAttacker()); + if (pBCC != NULL && pBCC->GetActiveWeapon()) + { + m_info.SetInflictor(pBCC->GetActiveWeapon()); + } + else + m_info.SetInflictor(this); + } + + if (m_iszWeapon != NULL_STRING) + m_info.SetWeapon( gEntList.FindEntityByName(NULL, STRING(m_iszWeapon), this, inputdata.pActivator, inputdata.pCaller) ); + else + { + CBaseCombatCharacter *pBCC = ToBaseCombatCharacter(m_info.GetAttacker()); + if (pBCC != NULL && pBCC->GetActiveWeapon()) + { + m_info.SetWeapon(pBCC->GetActiveWeapon()); + } + else + m_info.SetWeapon(this); + } + + target->TakeDamage(m_info); + m_OnApplyDamage.FireOutput(target, this); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : +// Output : +//----------------------------------------------------------------------------- +void CPointDamageInfo::InputApplyDamage( inputdata_t &inputdata ) +{ + ApplyDamage(STRING(m_target), inputdata); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : +// Output : +//----------------------------------------------------------------------------- +void CPointDamageInfo::InputApplyDamageToEntity( inputdata_t &inputdata ) +{ + ApplyDamage(inputdata.value.Entity(), inputdata); +} diff --git a/src/game/server/mapbase/point_entity_replace.cpp b/src/game/server/mapbase/point_entity_replace.cpp new file mode 100644 index 00000000..6152d24c --- /dev/null +++ b/src/game/server/mapbase/point_entity_replace.cpp @@ -0,0 +1,496 @@ +//========= Mapbase - https://github.com/mapbase-source/source-sdk-2013 ============// +// +// Purpose: Replaces a thing with another thing. +// +//============================================================================= + +#include "cbase.h" +#include "TemplateEntities.h" +#include "point_template.h" +#include "saverestore_utlvector.h" +#include "datadesc_mod.h" + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class CPointEntityReplace : public CLogicalEntity +{ + DECLARE_CLASS( CPointEntityReplace, CLogicalEntity ); + DECLARE_DATADESC(); + +public: + + // The entity that will replace the target. + string_t m_iszReplacementEntity; + + // Only used with certain replacement types + EHANDLE m_hReplacementEntity; + + // How we should get the replacement entity. + int m_iReplacementType; + enum + { + REPLACEMENTTYPE_TARGET_TELEPORT, // Replace with target entity + REPLACEMENTTYPE_CLASSNAME, // Replace with entity of specified classname + REPLACEMENTTYPE_TEMPLATE, // Replace with a point_template's contents + REPLACEMENTTYPE_TEMPLATE_RELATIVE, // Replace with a point_template's contents, maintaining relative position + REPLACEMENTTYPE_RANDOM_TEMPLATE, // Replace with one of a point_template's templates, randomly selected + REPLACEMENTTYPE_RANDOM_TEMPLATE_RELATIVE, // Replace with one of a point_template's templates, randomly selected, maintaining relative position + }; + + // Where the replacement entit(ies) should replace at. + int m_iReplacementLocation; + enum + { + REPLACEMENTLOC_ABSOLUTEORIGIN, + REPLACEMENTLOC_WORLDSPACECENTER, + }; + + // Do we actually replace the target entity or just function as a glorified point_teleport? + bool m_bRemoveOriginalEntity; + + // What stuff we should take. + int m_iTakeStuff; + enum + { + REPLACEMENTTAKE_NAME = (1 << 0), // Takes the target's name + REPLACEMENTTAKE_PARENT = (1 << 1), // Takes parent and transfers children + REPLACEMENTTAKE_OWNER = (1 << 2), // Takes owner entity + REPLACEMENTTAKE_MODELSTUFF = (1 << 3), // Takes model stuff + }; + + // Used to cause it to take other fields, most of that has been moved to m_bTakeModelStuff and m_iszOtherTakes + //bool m_bTakeStuff; + + // Other keyvalues to copy. + CUtlVector m_iszOtherTakes; + + // Fire OnReplace with the original entity as the caller. + bool m_bFireOriginalEntityAsCaller; + + bool KeyValue(const char *szKeyName, const char *szValue); + + void HandleReplacement(CBaseEntity *pEntity, CBaseEntity *pReplacement); + + CBaseEntity *GetReplacementEntity(inputdata_t *inputdata); + + void ReplaceEntity(CBaseEntity *pEntity, inputdata_t &inputdata); + + // Inputs + void InputReplace( inputdata_t &inputdata ); + void InputReplaceEntity( inputdata_t &inputdata ); + + void InputSetReplacementEntity( inputdata_t &inputdata ); + + COutputEHANDLE m_OnReplace; // Passes the entity we replaced the target with, fired multiple times if REPLACEMENTTYPE_TEMPLATE created multiple entities +}; + +LINK_ENTITY_TO_CLASS(point_entity_replace, CPointEntityReplace); + +BEGIN_DATADESC( CPointEntityReplace ) + + // Keys + DEFINE_KEYFIELD( m_iszReplacementEntity, FIELD_STRING, "ReplacementEntity" ), + DEFINE_FIELD( m_hReplacementEntity, FIELD_EHANDLE ), + DEFINE_KEYFIELD( m_iReplacementType, FIELD_INTEGER, "ReplacementType" ), + DEFINE_KEYFIELD( m_iReplacementLocation, FIELD_INTEGER, "ReplacementLocation" ), + DEFINE_KEYFIELD( m_bRemoveOriginalEntity, FIELD_BOOLEAN, "RemoveOriginalEntity" ), + DEFINE_FIELD( m_iTakeStuff, FIELD_INTEGER ), + DEFINE_UTLVECTOR( m_iszOtherTakes, FIELD_STRING ), + DEFINE_KEYFIELD( m_bFireOriginalEntityAsCaller, FIELD_BOOLEAN, "TargetIsCaller" ), + + // Inputs + DEFINE_INPUTFUNC( FIELD_VOID, "Replace", InputReplace ), + DEFINE_INPUTFUNC( FIELD_EHANDLE, "ReplaceEntity", InputReplaceEntity ), + DEFINE_INPUTFUNC( FIELD_STRING, "SetReplacementEntity", InputSetReplacementEntity ), + + // Outputs + DEFINE_OUTPUT(m_OnReplace, "OnReplace"), + +END_DATADESC() + +//----------------------------------------------------------------------------- +// Purpose: Handles key values from the BSP before spawn is called. +//----------------------------------------------------------------------------- +bool CPointEntityReplace::KeyValue( const char *szKeyName, const char *szValue ) +{ + if (!stricmp(szKeyName, "OtherStuff")) + { + if (szValue && szValue[0] != '\0') + { + CUtlStringList vecKeyList; + Q_SplitString(szValue, ",", vecKeyList); + FOR_EACH_VEC(vecKeyList, i) + { + m_iszOtherTakes.AddToTail(AllocPooledString(vecKeyList[i])); + } + } + return true; + } + else if (strnicmp(szKeyName, "Take", 4) == 0) + { + const char *subject = (szKeyName + 4); + int flag = 0; + if (FStrEq(subject, "Targetname")) + flag |= REPLACEMENTTAKE_NAME; + else if (FStrEq(subject, "Parent")) + flag |= REPLACEMENTTAKE_PARENT; + else if (FStrEq(subject, "Owner")) + flag |= REPLACEMENTTAKE_OWNER; + else if (FStrEq(subject, "ModelStuff")) + flag |= REPLACEMENTTAKE_MODELSTUFF; + else + return BaseClass::KeyValue(szKeyName, szValue); + + // Remove the flag if 0, add the flag if not 0 + !FStrEq(szValue, "0") ? (m_iTakeStuff |= flag) : (m_iTakeStuff &= ~flag); + + return true; + } + + return BaseClass::KeyValue(szKeyName, szValue); +} + +//----------------------------------------------------------------------------- +// Purpose: Takes targetname, fields, etc. Keep in mind neither are checked for null! +//----------------------------------------------------------------------------- +void CPointEntityReplace::HandleReplacement(CBaseEntity *pEntity, CBaseEntity *pReplacement) +{ + if (m_iTakeStuff & REPLACEMENTTAKE_NAME) + pReplacement->SetName(pEntity->GetEntityName()); + + if (m_iTakeStuff & REPLACEMENTTAKE_PARENT) + { + if (pEntity->GetParent()) + pReplacement->SetParent(pEntity->GetParent(), pEntity->GetParentAttachment()); + + TransferChildren(pEntity, pReplacement); + } + + if (m_iTakeStuff & REPLACEMENTTAKE_OWNER) + { + if (pEntity->GetOwnerEntity()) + pReplacement->SetOwnerEntity(pEntity->GetOwnerEntity()); + } + + if (m_iTakeStuff & REPLACEMENTTAKE_MODELSTUFF) + { + pReplacement->m_nRenderMode = pEntity->m_nRenderMode; + pReplacement->m_nRenderFX = pEntity->m_nRenderFX; + pReplacement->m_clrRender = pEntity->m_clrRender; + if (pEntity->GetBaseAnimating() && pReplacement->GetBaseAnimating()) + { + CBaseAnimating *pEntityAnimating = pEntity->GetBaseAnimating(); + CBaseAnimating *pReplacementAnimating = pReplacement->GetBaseAnimating(); + + pReplacementAnimating->CopyAnimationDataFrom(pEntityAnimating); + + for ( int iPose = 0; iPose < MAXSTUDIOPOSEPARAM; ++iPose ) + { + pReplacementAnimating->SetPoseParameter( iPose, pEntityAnimating->GetPoseParameter( iPose ) ); + } + } + } + + if (m_iszOtherTakes.Count() > 0) + { + CUtlVector szValues; + szValues.AddMultipleToTail( m_iszOtherTakes.Count() ); + + for ( datamap_t *dmap = pEntity->GetDataDescMap(); dmap != NULL; dmap = dmap->baseMap ) + { + // search through all the readable fields in the data description, looking for a match + for ( int i = 0; i < dmap->dataNumFields; i++ ) + { + if ( dmap->dataDesc[i].flags & (FTYPEDESC_SAVE | FTYPEDESC_KEY) && dmap->dataDesc[i].fieldName ) + { + DevMsg("Target Field Name: %s,\n", dmap->dataDesc[i].fieldName); + for (int i2 = 0; i2 < m_iszOtherTakes.Count(); i2++) + { + if ( FStrEq(dmap->dataDesc[i].fieldName, STRING(m_iszOtherTakes[i2])) ) + { + //szValues[i2] = (((char *)pEntity) + dmap->dataDesc[i].fieldOffset[ TD_OFFSET_NORMAL ]); + szValues[i2].Set( dmap->dataDesc[i].fieldType, ((char*)pEntity) + dmap->dataDesc[i].fieldOffset[TD_OFFSET_NORMAL] ); + break; + } + } + } + } + } + + for ( datamap_t *dmap = pReplacement->GetDataDescMap(); dmap != NULL; dmap = dmap->baseMap ) + { + // search through all the readable fields in the data description, looking for a match + for ( int i = 0; i < dmap->dataNumFields; i++ ) + { + if ( dmap->dataDesc[i].flags & (FTYPEDESC_SAVE | FTYPEDESC_KEY) && dmap->dataDesc[i].fieldName ) + { + DevMsg("Replacement Field Name: %s,\n", dmap->dataDesc[i].fieldName); + for (int i2 = 0; i2 < m_iszOtherTakes.Count(); i2++) + { + if ( FStrEq(dmap->dataDesc[i].fieldName, STRING(m_iszOtherTakes[i2])) ) + { + //(void*)(((char *)pReplacement) + dmap->dataDesc[i].fieldOffset[ TD_OFFSET_NORMAL ]) = szValues[i2]; + + //char *data = (((char *)pReplacement) + dmap->dataDesc[i].fieldOffset[TD_OFFSET_NORMAL]); + //memcpy(data, szValues[i2], sizeof(*data)); + + //Datadesc_SetFieldString( szValues[i2], pReplacement, &dmap->dataDesc[i] ); + + szValues[i2].SetOther( (((char *)pReplacement) + dmap->dataDesc[i].fieldOffset[TD_OFFSET_NORMAL]) ); + break; + } + } + } + } + } + } + + /* + // This is largely duplicated from phys_convert + if (m_bTakeStuff) + { + pReplacement->m_nRenderMode = pEntity->m_nRenderMode; + pReplacement->m_nRenderFX = pEntity->m_nRenderFX; + const color32 rclr = pEntity->GetRenderColor(); + pReplacement->SetRenderColor(rclr.r, rclr.g, rclr.b, rclr.a); + if (pEntity->GetBaseAnimating() && pReplacement->GetBaseAnimating()) + { + CBaseAnimating *pEntityAnimating = pEntity->GetBaseAnimating(); + CBaseAnimating *pReplacementAnimating = pReplacement->GetBaseAnimating(); + + pReplacementAnimating->CopyAnimationDataFrom(pEntityAnimating); + + //pReplacementAnimating->SetCycle(pEntityAnimating->GetCycle()); + //pReplacementAnimating->IncrementInterpolationFrame(); + //pReplacementAnimating->SetSequence(pEntityAnimating->GetSequence()); + //pReplacementAnimating->m_flAnimTime = pEntityAnimating->m_flAnimTime; + //pReplacementAnimating->m_nBody = pEntityAnimating->m_nBody; + //pReplacementAnimating->m_nSkin = pEntityAnimating->m_nSkin; + //pReplacementAnimating->SetModelScale(pEntityAnimating->GetModelScale()); + } + + UTIL_TransferPoseParameters(pEntity, pReplacement); + TransferChildren(pEntity, pReplacement); + } + */ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +inline CBaseEntity *CPointEntityReplace::GetReplacementEntity(inputdata_t *inputdata) +{ + if (!m_hReplacementEntity) + m_hReplacementEntity.Set(gEntList.FindEntityByName(NULL, STRING(m_iszReplacementEntity), this, inputdata ? inputdata->pActivator : NULL, inputdata ? inputdata->pCaller : NULL)); + return m_hReplacementEntity; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPointEntityReplace::ReplaceEntity(CBaseEntity *pEntity, inputdata_t &inputdata) +{ + Vector vecOrigin; + if (m_iReplacementLocation == REPLACEMENTLOC_WORLDSPACECENTER) + vecOrigin = pEntity->WorldSpaceCenter(); + else + vecOrigin = pEntity->GetAbsOrigin(); + + QAngle angAngles = pEntity->GetAbsAngles(); + Vector vecVelocity; + QAngle angVelocity; + if (pEntity->VPhysicsGetObject()) + { + AngularImpulse angImpulse; + pEntity->VPhysicsGetObject()->GetVelocity(&vecVelocity, &angImpulse); + AngularImpulseToQAngle(angImpulse, angVelocity); + } + else + { + vecVelocity = pEntity->GetAbsVelocity(); + angVelocity = pEntity->GetLocalAngularVelocity(); + } + + // No conflicts with the replacement entity until we're finished + if (m_bRemoveOriginalEntity && !(m_iTakeStuff & REPLACEMENTTAKE_MODELSTUFF)) + { + pEntity->AddSolidFlags( FSOLID_NOT_SOLID ); + pEntity->AddEffects( EF_NODRAW ); + } + + CBaseEntity *pCaller = m_bFireOriginalEntityAsCaller ? pEntity : this; + + switch (m_iReplacementType) + { + case REPLACEMENTTYPE_TARGET_TELEPORT: + { + CBaseEntity *pReplacementEntity = GetReplacementEntity(&inputdata); + if (pReplacementEntity) + { + HandleReplacement(pEntity, pReplacementEntity); + pReplacementEntity->Teleport(&vecOrigin, &angAngles, &vecVelocity); + + if (pReplacementEntity->VPhysicsGetObject()) + { + AngularImpulse angImpulse; + QAngleToAngularImpulse(angAngles, angImpulse); + pReplacementEntity->VPhysicsGetObject()->SetVelocityInstantaneous(&vecVelocity, &angImpulse); + } + else + { + pReplacementEntity->SetAbsVelocity(vecVelocity); + pReplacementEntity->SetBaseVelocity( vec3_origin ); + pReplacementEntity->SetLocalAngularVelocity(angVelocity); + } + + m_OnReplace.Set(pReplacementEntity, pReplacementEntity, pCaller); + } + } break; + case REPLACEMENTTYPE_CLASSNAME: + { + CBaseEntity *pReplacementEntity = CreateNoSpawn(STRING(m_iszReplacementEntity), vecOrigin, angAngles); + if (pReplacementEntity) + { + HandleReplacement(pEntity, pReplacementEntity); + + DispatchSpawn(pReplacementEntity); + + if (pReplacementEntity->VPhysicsGetObject()) + { + IPhysicsObject *pPhys = pReplacementEntity->VPhysicsGetObject(); + AngularImpulse angImpulse; + QAngleToAngularImpulse(angAngles, angImpulse); + pPhys->SetVelocityInstantaneous(&vecVelocity, &angImpulse); + } + else + { + pReplacementEntity->SetAbsVelocity(vecVelocity); + pReplacementEntity->SetBaseVelocity( vec3_origin ); + pReplacementEntity->SetLocalAngularVelocity(angVelocity); + } + + m_OnReplace.Set(pReplacementEntity, pReplacementEntity, pCaller); + } + } break; + case REPLACEMENTTYPE_TEMPLATE: + case REPLACEMENTTYPE_TEMPLATE_RELATIVE: + { + CPointTemplate *pTemplate = dynamic_cast(GetReplacementEntity(&inputdata)); + if (!pTemplate) + { + Warning("%s unable to retrieve entity %s. It either does not exist or is not a point_template.\n", GetDebugName(), STRING(m_iszReplacementEntity)); + return; + } + + CUtlVector hNewEntities; + if ( !pTemplate->CreateInstance( vecOrigin, angAngles, &hNewEntities ) || hNewEntities.Count() == 0 ) + return; + + CBaseEntity *pTemplateEntity = NULL; + for (int i = 0; i < hNewEntities.Count(); i++) + { + pTemplateEntity = hNewEntities[i]; + if (pTemplateEntity) + { + HandleReplacement(pEntity, pTemplateEntity); + + if (m_iReplacementType != REPLACEMENTTYPE_TEMPLATE_RELATIVE) + { + // We have to do this again. + pTemplateEntity->Teleport( &vecOrigin, &angAngles, &vecVelocity ); + } + + if (pTemplateEntity->VPhysicsGetObject()) + { + AngularImpulse angImpulse; + QAngleToAngularImpulse(angAngles, angImpulse); + pTemplateEntity->VPhysicsGetObject()->SetVelocityInstantaneous(&vecVelocity, &angImpulse); + } + else + { + pTemplateEntity->SetAbsVelocity(vecVelocity); + pTemplateEntity->SetBaseVelocity( vec3_origin ); + pTemplateEntity->SetLocalAngularVelocity(angVelocity); + } + + m_OnReplace.Set(pTemplateEntity, pTemplateEntity, pCaller); + } + } + } break; + case REPLACEMENTTYPE_RANDOM_TEMPLATE: + case REPLACEMENTTYPE_RANDOM_TEMPLATE_RELATIVE: + { + CPointTemplate *pTemplate = dynamic_cast(GetReplacementEntity(&inputdata)); + if (!pTemplate) + { + Warning("%s unable to retrieve entity %s. It either does not exist or is not a point_template.\n", GetDebugName(), STRING(m_iszReplacementEntity)); + return; + } + + CBaseEntity *pTemplateEntity = NULL; + if ( !pTemplate->CreateSpecificInstance( RandomInt(0, pTemplate->GetNumTemplates() - 1), vecOrigin, angAngles, &pTemplateEntity ) ) + return; + + if (pTemplateEntity) + { + HandleReplacement(pEntity, pTemplateEntity); + + if (m_iReplacementType != REPLACEMENTTYPE_RANDOM_TEMPLATE_RELATIVE) + { + // We have to do this again. + pTemplateEntity->Teleport( &vecOrigin, &angAngles, &vecVelocity ); + } + + if (pTemplateEntity->VPhysicsGetObject()) + { + AngularImpulse angImpulse; + QAngleToAngularImpulse(angAngles, angImpulse); + pTemplateEntity->VPhysicsGetObject()->SetVelocityInstantaneous(&vecVelocity, &angImpulse); + } + else + { + pTemplateEntity->SetAbsVelocity(vecVelocity); + pTemplateEntity->SetBaseVelocity( vec3_origin ); + pTemplateEntity->SetLocalAngularVelocity(angVelocity); + } + + m_OnReplace.Set(pTemplateEntity, pTemplateEntity, pCaller); + } + } break; + } + + if (m_bRemoveOriginalEntity) + UTIL_Remove(pEntity); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPointEntityReplace::InputReplace( inputdata_t &inputdata ) +{ + CBaseEntity *pEntity = gEntList.FindEntityByName(NULL, STRING(m_target), this, inputdata.pActivator, inputdata.pCaller); + if (pEntity) + ReplaceEntity(pEntity, inputdata); + else + Warning("%s unable to find replacement target %s.\n", GetDebugName(), STRING(m_target)); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPointEntityReplace::InputReplaceEntity( inputdata_t &inputdata ) +{ + if (inputdata.value.Entity()) + ReplaceEntity(inputdata.value.Entity(), inputdata); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPointEntityReplace::InputSetReplacementEntity( inputdata_t &inputdata ) +{ + m_iszReplacementEntity = inputdata.value.StringID(); + m_hReplacementEntity = NULL; +} diff --git a/src/game/server/mapbase/point_glow.cpp b/src/game/server/mapbase/point_glow.cpp new file mode 100644 index 00000000..877aec83 --- /dev/null +++ b/src/game/server/mapbase/point_glow.cpp @@ -0,0 +1,73 @@ +//========= Mapbase - https://github.com/mapbase-source/source-sdk-2013 ============// +// +// Purpose: Mapbase off-shoot of tf_glow (created using SDK code only) +// +//============================================================================= + +#include "cbase.h" + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class CPointGlow : public CPointEntity +{ + DECLARE_CLASS( CPointGlow, CPointEntity ); +public: + + int UpdateTransmitState( void ) { return SetTransmitState( FL_EDICT_ALWAYS ); } + + void Spawn( void ); + + void SetGlowTarget( CBaseEntity *pActivator, CBaseEntity *pCaller ) { m_hGlowTarget = gEntList.FindEntityByName(NULL, m_target, this, pActivator, pCaller); } + + // Inputs + void InputSetTarget( inputdata_t &inputdata ) { BaseClass::InputSetTarget(inputdata); SetGlowTarget( inputdata.pActivator, inputdata.pCaller ); } + + void InputEnable( inputdata_t &inputdata ) { m_bGlowDisabled = false; SetGlowTarget( inputdata.pActivator, inputdata.pCaller ); } + void InputDisable( inputdata_t &inputdata ) { m_bGlowDisabled = true; } + void InputToggle( inputdata_t &inputdata ) { m_bGlowDisabled ? InputEnable(inputdata) : InputDisable(inputdata); } + + void InputSetGlowColor( inputdata_t &inputdata ) { m_GlowColor = inputdata.value.Color32(); } + + CNetworkHandle( CBaseEntity, m_hGlowTarget ); + CNetworkColor32( m_GlowColor ); + CNetworkVar( bool, m_bGlowDisabled ); + + DECLARE_DATADESC(); + DECLARE_SERVERCLASS(); +}; + +LINK_ENTITY_TO_CLASS( point_glow, CPointGlow ); + + +BEGIN_DATADESC( CPointGlow ) + + // Keys + DEFINE_KEYFIELD( m_GlowColor, FIELD_COLOR32, "GlowColor" ), + DEFINE_FIELD( m_hGlowTarget, FIELD_EHANDLE ), + DEFINE_KEYFIELD( m_bGlowDisabled, FIELD_BOOLEAN, "StartDisabled" ), + + // Inputs + DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ), + DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ), + DEFINE_INPUTFUNC( FIELD_VOID, "Toggle", InputToggle ), + DEFINE_INPUTFUNC( FIELD_COLOR32, "SetGlowColor", InputSetGlowColor ), + +END_DATADESC() + +IMPLEMENT_SERVERCLASS_ST( CPointGlow, DT_PointGlow ) + SendPropEHandle( SENDINFO( m_hGlowTarget ) ), + SendPropInt( SENDINFO( m_GlowColor ), 32, SPROP_UNSIGNED, SendProxy_Color32ToInt ), + SendPropBool( SENDINFO( m_bGlowDisabled ) ), +END_SEND_TABLE() + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPointGlow::Spawn() +{ + m_hGlowTarget = gEntList.FindEntityByName( NULL, m_target, this ); + + BaseClass::Spawn(); +} diff --git a/src/game/server/mapbase/point_projectile.cpp b/src/game/server/mapbase/point_projectile.cpp new file mode 100644 index 00000000..7909c55d --- /dev/null +++ b/src/game/server/mapbase/point_projectile.cpp @@ -0,0 +1,197 @@ +//========= Mapbase - https://github.com/mapbase-source/source-sdk-2013 ============// +// +// Purpose: Fires projectiles. What else is there to say? +// +//============================================================================= + +#include "cbase.h" + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class CPointProjectile : public CBaseEntity +{ + DECLARE_CLASS( CPointProjectile, CBaseEntity ); + DECLARE_DATADESC(); + +public: + + void Precache(); + void Spawn(); + + // m_target is projectile class + + // Owner + // Handle is m_hOwnerEntity + string_t m_iszOwner; + + // Damage + float m_flDamage; + + // Speed + float m_flSpeed; + + bool m_bFireProjectilesFromOwner; + + CBaseEntity *CalculateOwner( CBaseEntity *pActivator, CBaseEntity *pCaller ); + + CBaseEntity *CreateProjectile( Vector &vecOrigin, QAngle &angAngles, Vector &vecDir, CBaseEntity *pOwner ); + + // Inputs + void InputFire( inputdata_t &inputdata ); + void InputFireAtEntity( inputdata_t &inputdata ); + void InputFireAtPosition( inputdata_t &inputdata ); + + void InputSetDamage( inputdata_t &inputdata ) { m_flDamage = inputdata.value.Float(); } + void InputSetOwner( inputdata_t &inputdata ) { m_iszOwner = inputdata.value.StringID(); SetOwnerEntity(NULL); } + void InputSetSpeed( inputdata_t &inputdata ) { m_flSpeed = inputdata.value.Float(); } + + void InputSetTarget( inputdata_t &inputdata ) { BaseClass::InputSetTarget(inputdata); UTIL_PrecacheOther(inputdata.value.String()); } + + COutputEHANDLE m_OnFire; +}; + +LINK_ENTITY_TO_CLASS(point_projectile, CPointProjectile); + +BEGIN_DATADESC( CPointProjectile ) + + // Keys + DEFINE_KEYFIELD( m_iszOwner, FIELD_STRING, "Owner" ), + DEFINE_KEYFIELD( m_flDamage, FIELD_FLOAT, "Damage" ), + DEFINE_KEYFIELD( m_flSpeed, FIELD_FLOAT, "Speed" ), + DEFINE_KEYFIELD( m_bFireProjectilesFromOwner, FIELD_BOOLEAN, "FireFromOwner" ), + + // Inputs + DEFINE_INPUTFUNC( FIELD_VOID, "Fire", InputFire ), + DEFINE_INPUTFUNC( FIELD_EHANDLE, "FireAtEntity", InputFireAtEntity ), + DEFINE_INPUTFUNC( FIELD_VECTOR, "FireAtPosition", InputFireAtPosition ), + + DEFINE_INPUTFUNC( FIELD_FLOAT, "SetDamage", InputSetDamage ), + DEFINE_INPUTFUNC( FIELD_STRING, "SetOwnerEntity", InputSetOwner ), + DEFINE_INPUTFUNC( FIELD_FLOAT, "SetSpeed", InputSetSpeed ), + DEFINE_INPUTFUNC( FIELD_STRING, "SetProjectileClass", InputSetTarget ), + + // Outputs + DEFINE_OUTPUT(m_OnFire, "OnFire"), + +END_DATADESC() + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPointProjectile::Precache() +{ + UTIL_PrecacheOther(STRING(m_target)); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPointProjectile::Spawn() +{ + Precache(); + + BaseClass::Spawn(); +} + +//----------------------------------------------------------------------------- +// Purpose: Calculates owner entity +//----------------------------------------------------------------------------- +inline CBaseEntity *CPointProjectile::CalculateOwner( CBaseEntity *pActivator, CBaseEntity *pCaller ) +{ + if (m_iszOwner != NULL_STRING && !GetOwnerEntity()) + { + CBaseEntity *pOwner = gEntList.FindEntityByName(NULL, STRING(m_iszOwner), this, pActivator, pCaller); + if (pOwner) + SetOwnerEntity(pOwner); + } + + return GetOwnerEntity() ? GetOwnerEntity() : this; +} + +//----------------------------------------------------------------------------- +// Purpose: Fires projectile and output +//----------------------------------------------------------------------------- +inline CBaseEntity *CPointProjectile::CreateProjectile( Vector &vecOrigin, QAngle &angAngles, Vector &vecDir, CBaseEntity *pOwner ) +{ + CBaseEntity *pProjectile = CreateNoSpawn(STRING(m_target), vecOrigin, angAngles, pOwner); + if (!pProjectile) + { + Warning("WARNING: %s unable to create projectile class %s!\n", GetDebugName(), STRING(m_target)); + return NULL; + } + + pProjectile->SetAbsVelocity(vecDir * m_flSpeed); + DispatchSpawn(pProjectile); + + pProjectile->SetDamage(m_flDamage); + + m_OnFire.Set(pProjectile, pProjectile, pOwner); + + return pProjectile; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPointProjectile::InputFire( inputdata_t &inputdata ) +{ + Vector vecOrigin = GetAbsOrigin(); + QAngle angAngles = GetAbsAngles(); + + CBaseEntity *pOwner = CalculateOwner(inputdata.pActivator, inputdata.pCaller); + if (pOwner && m_bFireProjectilesFromOwner) + vecOrigin = pOwner->GetAbsOrigin(); + + Vector vecDir; + AngleVectors(angAngles, &vecDir); + + CreateProjectile(vecOrigin, angAngles, vecDir, pOwner); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPointProjectile::InputFireAtEntity( inputdata_t &inputdata ) +{ + CBaseEntity *pTarget = inputdata.value.Entity(); + if (!pTarget) + return; + + Vector vecOrigin = GetAbsOrigin(); + + CBaseEntity *pOwner = CalculateOwner(inputdata.pActivator, inputdata.pCaller); + if (pOwner && m_bFireProjectilesFromOwner) + vecOrigin = pOwner->GetAbsOrigin(); + + Vector vecDir = (pTarget->WorldSpaceCenter() - vecOrigin); + VectorNormalize(vecDir); + + QAngle angAngles; + VectorAngles(vecDir, angAngles); + + CreateProjectile(vecOrigin, angAngles, vecDir, pOwner); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPointProjectile::InputFireAtPosition( inputdata_t &inputdata ) +{ + Vector vecInput; + inputdata.value.Vector3D(vecInput); + + Vector vecOrigin = GetAbsOrigin(); + + CBaseEntity *pOwner = CalculateOwner(inputdata.pActivator, inputdata.pCaller); + if (pOwner && m_bFireProjectilesFromOwner) + vecOrigin = pOwner->GetAbsOrigin(); + + Vector vecDir = (vecInput - vecOrigin); + VectorNormalize(vecDir); + + QAngle angAngles; + VectorAngles(vecDir, angAngles); + + CreateProjectile(vecOrigin, angAngles, vecDir, pOwner); +} diff --git a/src/game/server/mapbase/point_radiation_source.cpp b/src/game/server/mapbase/point_radiation_source.cpp new file mode 100644 index 00000000..febe0c1a --- /dev/null +++ b/src/game/server/mapbase/point_radiation_source.cpp @@ -0,0 +1,140 @@ +//========= Mapbase - https://github.com/mapbase-source/source-sdk-2013 ==== +// +// An entity that triggers the player's geiger counter. +// +// Doesn't cause any actual damage. Should be parentable. +// +//============================================================================= + +#include "cbase.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + + + +class CPointRadiationSource : public CPointEntity +{ +public: + DECLARE_CLASS( CPointRadiationSource, CPointEntity ); + DECLARE_DATADESC(); + + void Spawn(); + + void RadiationThink(); + + void InputEnable( inputdata_t &inputdata ); + void InputDisable( inputdata_t &inputdata ); + + bool m_bTestPVS; + float m_flRadius; + float m_flIntensity = 1.0f; + + bool m_bDisabled; +}; + +BEGIN_DATADESC( CPointRadiationSource ) + + // Function Pointers + DEFINE_FUNCTION( RadiationThink ), + + // Fields + DEFINE_KEYFIELD( m_bTestPVS, FIELD_BOOLEAN, "TestPVS" ), + DEFINE_INPUT( m_flRadius, FIELD_FLOAT, "SetRadius" ), + DEFINE_INPUT( m_flIntensity, FIELD_FLOAT, "SetIntensity" ), + DEFINE_KEYFIELD( m_bDisabled, FIELD_BOOLEAN, "StartDisabled" ), + + // Inputs + DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ), + DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ), + +END_DATADESC() + + +LINK_ENTITY_TO_CLASS( point_radiation_source, CPointRadiationSource ); + + +//----------------------------------------------------------------------------- +// Purpose: Called when spawning, after keyvalues have been handled. +//----------------------------------------------------------------------------- +void CPointRadiationSource::Spawn( void ) +{ + BaseClass::Spawn(); + + if (!m_bDisabled) + { + SetThink( &CPointRadiationSource::RadiationThink ); + SetNextThink( gpGlobals->curtime + random->RandomFloat(0.0, 0.5) ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPointRadiationSource::InputEnable( inputdata_t &inputdata ) +{ + m_bDisabled = false; + + SetThink( &CPointRadiationSource::RadiationThink ); + SetNextThink( gpGlobals->curtime ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPointRadiationSource::InputDisable( inputdata_t &inputdata ) +{ + m_bDisabled = true; + + SetThink( NULL ); + SetNextThink( TICK_NEVER_THINK ); + + // Must update player + //CBasePlayer *pPlayer = UTIL_GetLocalPlayer(); + //if (pPlayer) + //{ + // pPlayer->NotifyNearbyRadiationSource(1000); + //} +} + +//----------------------------------------------------------------------------- +// Purpose: Trigger hurt that causes radiation will do a radius check and set +// the player's geiger counter level according to distance from center +// of trigger. +//----------------------------------------------------------------------------- +void CPointRadiationSource::RadiationThink( void ) +{ + CBasePlayer *pPlayer = NULL; + if (m_bTestPVS) + { + CBaseEntity *pPVSPlayer = CBaseEntity::Instance(UTIL_FindClientInPVS(edict())); + if (pPVSPlayer) + { + pPlayer = static_cast(pPVSPlayer); + } + } + else + { + pPlayer = UTIL_GetLocalPlayer(); + } + + if (pPlayer) + { + // get range to player; + float flRange = pPlayer->GetAbsOrigin().DistTo((GetAbsOrigin())); + if (m_flRadius <= 0 || flRange < m_flRadius) + { + if (m_flIntensity == 0) + { + Warning("%s: INTENSITY IS ZERO!!! Can't notify of radiation\n", GetDebugName()); + return; + } + + //flRange *= 3.0f; + flRange /= m_flIntensity; + pPlayer->NotifyNearbyRadiationSource(flRange); + } + } + + SetNextThink( gpGlobals->curtime + 0.25 ); +} diff --git a/src/game/server/mapbase/variant_tools.h b/src/game/server/mapbase/variant_tools.h new file mode 100644 index 00000000..21e631fb --- /dev/null +++ b/src/game/server/mapbase/variant_tools.h @@ -0,0 +1,43 @@ +//========= Mapbase - https://github.com/mapbase-source/source-sdk-2013 ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef VARIANT_TOOLS_H +#define VARIANT_TOOLS_H +#ifdef _WIN32 +#pragma once +#endif + +#include "cbase.h" + +// Quick way to define variants in a datadesc. +extern ISaveRestoreOps *variantFuncs; +#define DEFINE_VARIANT(name) { FIELD_CUSTOM, #name, { offsetof(classNameTypedef, name), 0 }, 1, FTYPEDESC_SAVE, NULL, variantFuncs, NULL } +#define DEFINE_KEYVARIANT(name,mapname) { FIELD_CUSTOM, #name, { offsetof(classNameTypedef, name), 0 }, 1, FTYPEDESC_SAVE | FTYPEDESC_KEY, mapname, variantFuncs, NULL } + +// Most of these are defined in variant_t.cpp. + +// Creates a variant_t from the given string. +// It could return as a String or a Float. +variant_t Variant_Parse(const char *szValue); + +// Intended to convert FIELD_INPUT I/O parameters to other values, like integers, floats, or even entities. +// This only changes FIELD_STRING variants. Other data like FIELD_EHANDLE or FIELD_INTEGER are not affected. +variant_t Variant_ParseInput(inputdata_t inputdata); + +// A simpler version of Variant_ParseInput that does not allow FIELD_EHANDLE. +variant_t Variant_ParseString(variant_t value); + +// val1 == val2 +bool Variant_Equal(variant_t val1, variant_t val2, bool bLenAllowed = true); + +// val1 > val2 +bool Variant_Greater(variant_t val1, variant_t val2, bool bLenAllowed = true); + +// val1 >= val2 +bool Variant_GreaterOrEqual(variant_t val1, variant_t val2, bool bLenAllowed = true); + +#endif \ No newline at end of file diff --git a/src/game/server/mapbase/vgui_text_display.cpp b/src/game/server/mapbase/vgui_text_display.cpp new file mode 100644 index 00000000..84e0ac63 --- /dev/null +++ b/src/game/server/mapbase/vgui_text_display.cpp @@ -0,0 +1,437 @@ +//========= Mapbase - https://github.com/mapbase-source/source-sdk-2013 ============// +// +// Purpose: Displays easy, flexible VGui text. Mapbase equivalent of point_worldtext. +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" +#include "vguiscreen.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +#define SF_TESTDISPLAY_START_DISABLED (1 << 0) + +//----------------------------------------------------------------------------- +// vgui_text_display +//----------------------------------------------------------------------------- +class CVGuiTextDisplay : public CBaseEntity +{ +public: + + DECLARE_CLASS( CVGuiTextDisplay, CBaseEntity ); + DECLARE_DATADESC(); + DECLARE_SERVERCLASS(); + + CVGuiTextDisplay(); + virtual ~CVGuiTextDisplay(); + + virtual bool KeyValue( const char *szKeyName, const char *szValue ); + + virtual int UpdateTransmitState(); + virtual void SetTransmit( CCheckTransmitInfo *pInfo, bool bAlways ); + + virtual void Spawn( void ); + virtual void Precache( void ); + virtual void OnRestore( void ); + + void ScreenVisible( bool bVisible ); + + void Disable( void ); + void Enable( void ); + + void InputDisable( inputdata_t &inputdata ); + void InputEnable( inputdata_t &inputdata ); + void InputToggle( inputdata_t &inputdata ); + + void InputSetMessage( inputdata_t &inputdata ); + void InputSetTextAlignment( inputdata_t &inputdata ); + void InputSetFont( inputdata_t &inputdata ); + void InputSetResolution( inputdata_t &inputdata ); + void InputSetTextSize( inputdata_t &inputdata ); + +private: + + // Control panel + void GetControlPanelInfo( int nPanelIndex, const char *&pPanelName ); + void GetControlPanelClassName( int nPanelIndex, const char *&pPanelName ); + void SpawnControlPanels( void ); + void RestoreControlPanels( void ); + +private: + CNetworkVar( bool, m_bEnabled ); + + CNetworkString( m_szDisplayText, 256 ); + CNetworkVar( int, m_iContentAlignment ); + CNetworkString( m_szFont, 64 ); + CNetworkVar( int, m_iResolution ); + float m_flTextSize; + + //CNetworkColor32( m_DisplayColor ); // Use render color + + bool m_bDoFullTransmit; + + CHandle m_hScreen; +}; + +LINK_ENTITY_TO_CLASS( vgui_text_display, CVGuiTextDisplay ); + +//----------------------------------------------------------------------------- +// Save/load +//----------------------------------------------------------------------------- +BEGIN_DATADESC( CVGuiTextDisplay ) + + DEFINE_FIELD( m_bEnabled, FIELD_BOOLEAN ), + + DEFINE_AUTO_ARRAY_KEYFIELD( m_szDisplayText, FIELD_CHARACTER, "message" ), + DEFINE_KEYFIELD( m_iContentAlignment, FIELD_INTEGER, "alignment" ), + DEFINE_AUTO_ARRAY_KEYFIELD( m_szFont, FIELD_CHARACTER, "font" ), + DEFINE_KEYFIELD( m_iResolution, FIELD_INTEGER, "resolution" ), + DEFINE_KEYFIELD( m_flTextSize, FIELD_FLOAT, "textsize" ), + + DEFINE_FIELD( m_bDoFullTransmit, FIELD_BOOLEAN ), + + DEFINE_FIELD( m_hScreen, FIELD_EHANDLE ), + + DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ), + DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ), + DEFINE_INPUTFUNC( FIELD_VOID, "Toggle", InputToggle ), + + DEFINE_INPUTFUNC( FIELD_STRING, "SetMessage", InputSetMessage ), + DEFINE_INPUTFUNC( FIELD_INTEGER, "SetTextAlignment", InputSetTextAlignment ), + DEFINE_INPUTFUNC( FIELD_STRING, "SetFont", InputSetFont ), + DEFINE_INPUTFUNC( FIELD_INTEGER, "SetResolution", InputSetResolution ), + DEFINE_INPUTFUNC( FIELD_FLOAT, "SetPanelSize", InputSetTextSize ), + +END_DATADESC() + +IMPLEMENT_SERVERCLASS_ST( CVGuiTextDisplay, DT_VGuiTextDisplay ) + SendPropBool( SENDINFO( m_bEnabled ) ), + SendPropString( SENDINFO( m_szDisplayText ) ), + SendPropInt( SENDINFO( m_iContentAlignment ) ), + SendPropString( SENDINFO( m_szFont ) ), + SendPropInt( SENDINFO( m_iResolution ) ), +END_SEND_TABLE() + +CVGuiTextDisplay::CVGuiTextDisplay() +{ + m_flTextSize = 100.0f; + m_iResolution = 200; + m_iContentAlignment = 7; // a_south +} + +CVGuiTextDisplay::~CVGuiTextDisplay() +{ + DestroyVGuiScreen( m_hScreen.Get() ); +} + +//----------------------------------------------------------------------------- +// Read in Hammer data +//----------------------------------------------------------------------------- +bool CVGuiTextDisplay::KeyValue( const char *szKeyName, const char *szValue ) +{ + // NOTE: Have to do these separate because they set two values instead of one + if( FStrEq( szKeyName, "angles" ) ) + { + Assert( GetMoveParent() == NULL ); + QAngle angles; + UTIL_StringToVector( angles.Base(), szValue ); + + // Because the vgui screen basis is strange (z is front, y is up, x is right) + // we need to rotate the typical basis before applying it + VMatrix mat, rotation, tmp; + MatrixFromAngles( angles, mat ); + MatrixBuildRotationAboutAxis( rotation, Vector( 0, 1, 0 ), 90 ); + MatrixMultiply( mat, rotation, tmp ); + MatrixBuildRotateZ( rotation, 90 ); + MatrixMultiply( tmp, rotation, mat ); + MatrixToAngles( mat, angles ); + SetAbsAngles( angles ); + } + else if( FStrEq( szKeyName, "message" ) ) + { + Q_strcpy( m_szDisplayText.GetForModify(), szValue ); + } + else if( FStrEq( szKeyName, "font" ) ) + { + Q_strcpy( m_szFont.GetForModify(), szValue ); + } + else if( FStrEq( szKeyName, "color" ) ) + { + // Use render color + return BaseClass::KeyValue( "rendercolor", szValue ); + } + else + return BaseClass::KeyValue( szKeyName, szValue ); + + return true; +} + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +int CVGuiTextDisplay::UpdateTransmitState() +{ + if ( m_bDoFullTransmit ) + { + m_bDoFullTransmit = false; + return SetTransmitState( FL_EDICT_ALWAYS ); + } + + return SetTransmitState( FL_EDICT_FULLCHECK ); +} + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CVGuiTextDisplay::SetTransmit( CCheckTransmitInfo *pInfo, bool bAlways ) +{ + // Are we already marked for transmission? + if ( pInfo->m_pTransmitEdict->Get( entindex() ) ) + return; + + BaseClass::SetTransmit( pInfo, bAlways ); + + // Force our screen to be sent too. + m_hScreen->SetTransmit( pInfo, bAlways ); +} + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CVGuiTextDisplay::Spawn( void ) +{ + Precache(); + + BaseClass::Spawn(); + + m_bEnabled = !HasSpawnFlags( SF_TESTDISPLAY_START_DISABLED ); + + SpawnControlPanels(); + + ScreenVisible( m_bEnabled ); + + m_bDoFullTransmit = true; +} + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CVGuiTextDisplay::Precache( void ) +{ + BaseClass::Precache(); + + PrecacheVGuiScreen( "text_display_panel" ); +} + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CVGuiTextDisplay::OnRestore( void ) +{ + BaseClass::OnRestore(); + + RestoreControlPanels(); + + ScreenVisible( m_bEnabled ); +} + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CVGuiTextDisplay::ScreenVisible( bool bVisible ) +{ + // Set its active state + m_hScreen->SetActive( bVisible ); + + if ( bVisible ) + { + m_hScreen->RemoveEffects( EF_NODRAW ); + } + else + { + m_hScreen->AddEffects( EF_NODRAW ); + } +} + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CVGuiTextDisplay::Disable( void ) +{ + if ( !m_bEnabled ) + return; + + m_bEnabled = false; + + ScreenVisible( false ); +} + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CVGuiTextDisplay::Enable( void ) +{ + if ( m_bEnabled ) + return; + + m_bEnabled = true; + + ScreenVisible( true ); +} + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CVGuiTextDisplay::InputDisable( inputdata_t &inputdata ) +{ + Disable(); +} + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CVGuiTextDisplay::InputEnable( inputdata_t &inputdata ) +{ + Enable(); +} + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CVGuiTextDisplay::InputToggle( inputdata_t &inputdata ) +{ + m_bEnabled ? Disable() : Enable(); +} + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CVGuiTextDisplay::InputSetMessage( inputdata_t &inputdata ) +{ + Q_strcpy( m_szDisplayText.GetForModify(), inputdata.value.String() ); +} + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CVGuiTextDisplay::InputSetTextAlignment( inputdata_t &inputdata ) +{ + m_iContentAlignment = inputdata.value.Int(); +} + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CVGuiTextDisplay::InputSetFont( inputdata_t &inputdata ) +{ + Q_strcpy( m_szFont.GetForModify(), inputdata.value.String() ); +} + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CVGuiTextDisplay::InputSetResolution( inputdata_t &inputdata ) +{ + m_iResolution = inputdata.value.Int(); +} + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CVGuiTextDisplay::InputSetTextSize( inputdata_t &inputdata ) +{ + m_flTextSize = inputdata.value.Float(); + + if (m_hScreen) + { + m_hScreen->SetActualSize( m_flTextSize, m_flTextSize ); + m_hScreen->SetLocalOrigin( m_hScreen->CollisionProp()->OBBCenter() * -1.0f ); + } +} + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CVGuiTextDisplay::GetControlPanelInfo( int nPanelIndex, const char *&pPanelName ) +{ + pPanelName = "text_display_panel"; +} + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CVGuiTextDisplay::GetControlPanelClassName( int nPanelIndex, const char *&pPanelName ) +{ + pPanelName = "vgui_screen"; +} + +//----------------------------------------------------------------------------- +// This is called by the base object when it's time to spawn the control panels +//----------------------------------------------------------------------------- +void CVGuiTextDisplay::SpawnControlPanels() +{ + int nPanel; + for ( nPanel = 0; true; ++nPanel ) + { + const char *pScreenName; + GetControlPanelInfo( nPanel, pScreenName ); + if (!pScreenName) + continue; + + const char *pScreenClassname; + GetControlPanelClassName( nPanel, pScreenClassname ); + if ( !pScreenClassname ) + continue; + + float flWidth = m_flTextSize; + float flHeight = m_flTextSize; + + CVGuiScreen *pScreen = CreateVGuiScreen( pScreenClassname, pScreenName, this, this, 0 ); + pScreen->ChangeTeam( GetTeamNumber() ); + pScreen->SetActualSize( flWidth, flHeight ); + pScreen->SetLocalOrigin( pScreen->CollisionProp()->OBBCenter() * -1.0f ); + pScreen->SetActive( true ); + pScreen->MakeVisibleOnlyToTeammates( false ); + pScreen->SetTransparency( true ); + m_hScreen = pScreen; + + return; + } +} + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CVGuiTextDisplay::RestoreControlPanels( void ) +{ + int nPanel; + for ( nPanel = 0; true; ++nPanel ) + { + const char *pScreenName; + GetControlPanelInfo( nPanel, pScreenName ); + if (!pScreenName) + continue; + + const char *pScreenClassname; + GetControlPanelClassName( nPanel, pScreenClassname ); + if ( !pScreenClassname ) + continue; + + CVGuiScreen *pScreen = (CVGuiScreen *)gEntList.FindEntityByClassname( NULL, pScreenClassname ); + + while ( ( pScreen && pScreen->GetOwnerEntity() != this ) || Q_strcmp( pScreen->GetPanelName(), pScreenName ) != 0 ) + { + pScreen = (CVGuiScreen *)gEntList.FindEntityByClassname( pScreen, pScreenClassname ); + } + + if ( pScreen ) + { + m_hScreen = pScreen; + m_hScreen->SetActive( true ); + } + + return; + } +} diff --git a/src/game/server/mapbase/weapon_custom_hl2.cpp b/src/game/server/mapbase/weapon_custom_hl2.cpp new file mode 100644 index 00000000..7c8241bc --- /dev/null +++ b/src/game/server/mapbase/weapon_custom_hl2.cpp @@ -0,0 +1,1546 @@ +//========= Mapbase - https://github.com/mapbase-source/source-sdk-2013 ============// +// +// Purpose: Custom weapon classes for Half-Life 2 based weapons. +// +// Author: Peter Covington (petercov@outlook.com) +// +//==================================================================================// + +#include "cbase.h" +#include "custom_weapon_factory.h" +#include "basebludgeonweapon.h" +#include "ai_basenpc.h" +#include "player.h" +#include "npcevent.h" +#include "in_buttons.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +// Acttables +extern acttable_t* GetSMG1Acttable(); +extern int GetSMG1ActtableCount(); +extern acttable_t* GetPistolActtable(); +extern int GetPistolActtableCount(); +extern acttable_t* GetShotgunActtable(); +extern int GetShotgunActtableCount(); +extern acttable_t* Get357Acttable(); +extern int Get357ActtableCount(); +extern acttable_t* GetAR2Acttable(); +extern int GetAR2ActtableCount(); +extern acttable_t* GetCrossbowActtable(); +extern int GetCrossbowActtableCount(); +extern acttable_t* GetAnnabelleActtable(); +extern int GetAnnabelleActtableCount(); + + + +const char* g_ppszDamageClasses[] = { + "BLUNT", + "SLASH", + "STUN", + "BURN", +}; + +int g_nDamageClassTypeBits[ARRAYSIZE(g_ppszDamageClasses)] = { + DMG_CLUB, + DMG_SLASH, + DMG_CLUB|DMG_SHOCK, + DMG_CLUB|DMG_BURN, +}; + +typedef struct HL2CustomMeleeData_s +{ + float m_flMeleeRange; + float m_flRefireRate; + float m_flDamage; + float m_flNPCDamage; + float m_flHitDelay; + byte m_nDamageClass; + bool m_bHitUsesMissAnim; + + bool Parse(KeyValues*); +} HL2CustomMeleeData_t; + +class CHLCustomWeaponMelee : public CBaseHLBludgeonWeapon, public ICustomWeapon +{ +public: + DECLARE_CLASS(CHLCustomWeaponMelee, CBaseHLBludgeonWeapon); + + DECLARE_SERVERCLASS(); + DECLARE_ACTTABLE(); + + CHLCustomWeaponMelee(); + + float GetRange(void) { return m_CustomData.m_flMeleeRange; } + float GetFireRate(void) { return m_CustomData.m_flRefireRate; } + float GetHitDelay() { return m_CustomData.m_flHitDelay; } + + void AddViewKick(void); + float GetDamageForActivity(Activity hitActivity); + + virtual int WeaponMeleeAttack1Condition(float flDot, float flDist); + void SecondaryAttack(void) { return; } + + // Animation event + virtual void Operator_HandleAnimEvent(animevent_t* pEvent, CBaseCombatCharacter* pOperator); + + // Don't use backup activities + acttable_t* GetBackupActivityList() { return NULL; } + int GetBackupActivityListCount() { return 0; } + + //Functions to select animation sequences + virtual Activity GetPrimaryAttackActivity(void) { return m_CustomData.m_bHitUsesMissAnim ? ACT_VM_MISSCENTER : BaseClass::GetPrimaryAttackActivity(); } + + const char* GetWeaponScriptName() { return m_iszWeaponScriptName.Get(); } + virtual int GetDamageType() { return g_nDamageClassTypeBits[m_CustomData.m_nDamageClass]; } + + virtual void InitCustomWeaponFromData(const void* pData, const char* pszWeaponScript); + +private: + // Animation event handlers + void HandleAnimEventMeleeHit(animevent_t* pEvent, CBaseCombatCharacter* pOperator); + +private: + HL2CustomMeleeData_t m_CustomData; + + CNetworkString(m_iszWeaponScriptName, 128); +}; + +IMPLEMENT_SERVERCLASS_ST(CHLCustomWeaponMelee, DT_HLCustomWeaponMelee) +SendPropString(SENDINFO(m_iszWeaponScriptName)), +END_SEND_TABLE(); + +DEFINE_CUSTOM_WEAPON_FACTORY(hl2_melee, CHLCustomWeaponMelee, HL2CustomMeleeData_t); + +bool HL2CustomMeleeData_s::Parse(KeyValues* pKVWeapon) +{ + KeyValues* pkvData = pKVWeapon->FindKey("CustomData"); + if (pkvData) + { + m_flDamage = pkvData->GetFloat("damage"); + m_flNPCDamage = pkvData->GetFloat("damage_npc", m_flDamage); + m_flMeleeRange = pkvData->GetFloat("range", 70.f); + m_flRefireRate = pkvData->GetFloat("rate", 0.7f); + m_flHitDelay = pkvData->GetFloat("hitdelay"); + m_bHitUsesMissAnim = pkvData->GetBool("hit_uses_miss_anim"); + + const char* pszDamageClass = pkvData->GetString("damage_type", nullptr); + if (pszDamageClass) + { + for (byte i = 0; i < ARRAYSIZE(g_ppszDamageClasses); i++) + { + if (V_stricmp(pszDamageClass, g_ppszDamageClasses[i]) == 0) + { + m_nDamageClass = i; + break; + } + } + } + return true; + } + + return false; +} + +void CHLCustomWeaponMelee::InitCustomWeaponFromData(const void* pData, const char* pszWeaponScript) +{ + Q_FileBase(pszWeaponScript, m_iszWeaponScriptName.GetForModify(), 128); + V_memcpy(&m_CustomData, pData, sizeof(HL2CustomMeleeData_t)); +} + +acttable_t CHLCustomWeaponMelee::m_acttable[] = +{ + { ACT_MELEE_ATTACK1, ACT_MELEE_ATTACK_SWING, true }, + { ACT_GESTURE_MELEE_ATTACK1, ACT_GESTURE_MELEE_ATTACK_SWING, false}, + + { ACT_IDLE_ANGRY, ACT_IDLE_ANGRY_MELEE, false }, +#if EXPANDED_HL2_WEAPON_ACTIVITIES + { ACT_IDLE, ACT_IDLE_MELEE, false }, + { ACT_RUN, ACT_RUN_MELEE, false }, + { ACT_WALK, ACT_WALK_MELEE, false }, + + { ACT_ARM, ACT_ARM_MELEE, false }, + { ACT_DISARM, ACT_DISARM_MELEE, false }, + + // Readiness activities (not aiming) + { ACT_IDLE_RELAXED, ACT_IDLE_MELEE, false },//never aims + { ACT_IDLE_STIMULATED, ACT_IDLE_MELEE, false }, + { ACT_IDLE_AGITATED, ACT_IDLE_ANGRY_MELEE, false },//always aims + + { ACT_WALK_RELAXED, ACT_WALK_MELEE, false },//never aims + { ACT_WALK_STIMULATED, ACT_WALK_MELEE, false }, + { ACT_WALK_AGITATED, ACT_WALK_MELEE, false },//always aims + + { ACT_RUN_RELAXED, ACT_RUN_MELEE, false },//never aims + { ACT_RUN_STIMULATED, ACT_RUN_MELEE, false }, + { ACT_RUN_AGITATED, ACT_RUN_MELEE, false },//always aims + + // Readiness activities (aiming) + { ACT_IDLE_AIM_RELAXED, ACT_IDLE_MELEE, false },//never aims + { ACT_IDLE_AIM_STIMULATED, ACT_IDLE_ANGRY_MELEE, false }, + { ACT_IDLE_AIM_AGITATED, ACT_IDLE_ANGRY_MELEE, false },//always aims + + { ACT_WALK_AIM_RELAXED, ACT_WALK_MELEE, false },//never aims + { ACT_WALK_AIM_STIMULATED, ACT_WALK_MELEE, false }, + { ACT_WALK_AIM_AGITATED, ACT_WALK_MELEE, false },//always aims + + { ACT_RUN_AIM_RELAXED, ACT_RUN_MELEE, false },//never aims + { ACT_RUN_AIM_STIMULATED, ACT_RUN_MELEE, false }, + { ACT_RUN_AIM_AGITATED, ACT_RUN_MELEE, false },//always aims + //End readiness activities +#else + { ACT_IDLE, ACT_IDLE_ANGRY_MELEE, false }, +#endif + +#ifdef MAPBASE + // HL2:DM activities (for third-person animations in SP) + { ACT_RANGE_ATTACK1, ACT_RANGE_ATTACK_SLAM, true }, + { ACT_HL2MP_IDLE, ACT_HL2MP_IDLE_MELEE, false }, + { ACT_HL2MP_RUN, ACT_HL2MP_RUN_MELEE, false }, + { ACT_HL2MP_IDLE_CROUCH, ACT_HL2MP_IDLE_CROUCH_MELEE, false }, + { ACT_HL2MP_WALK_CROUCH, ACT_HL2MP_WALK_CROUCH_MELEE, false }, + { ACT_HL2MP_GESTURE_RANGE_ATTACK, ACT_HL2MP_GESTURE_RANGE_ATTACK_MELEE, false }, + { ACT_HL2MP_GESTURE_RELOAD, ACT_HL2MP_GESTURE_RELOAD_MELEE, false }, + { ACT_HL2MP_JUMP, ACT_HL2MP_JUMP_MELEE, false }, +#if EXPANDED_HL2DM_ACTIVITIES + { ACT_HL2MP_GESTURE_RANGE_ATTACK2, ACT_HL2MP_GESTURE_RANGE_ATTACK2_MELEE, false }, + { ACT_HL2MP_WALK, ACT_HL2MP_WALK_MELEE, false }, +#endif +#endif +}; + +IMPLEMENT_ACTTABLE(CHLCustomWeaponMelee); + +CHLCustomWeaponMelee::CHLCustomWeaponMelee() +{ +} + +//----------------------------------------------------------------------------- +// Purpose: Get the damage amount for the animation we're doing +// Input : hitActivity - currently played activity +// Output : Damage amount +//----------------------------------------------------------------------------- +float CHLCustomWeaponMelee::GetDamageForActivity(Activity hitActivity) +{ + if ((GetOwner() != NULL) && (GetOwner()->IsPlayer())) + return m_CustomData.m_flDamage; + + return m_CustomData.m_flNPCDamage; +} + +//----------------------------------------------------------------------------- +// Purpose: Add in a view kick for this weapon +//----------------------------------------------------------------------------- +void CHLCustomWeaponMelee::AddViewKick(void) +{ + CBasePlayer* pPlayer = ToBasePlayer(GetOwner()); + + if (pPlayer == NULL) + return; + + QAngle punchAng; + + punchAng.x = random->RandomFloat(1.0f, 2.0f); + punchAng.y = random->RandomFloat(-2.0f, -1.0f); + punchAng.z = 0.0f; + + pPlayer->ViewPunch(punchAng); +} + + +//----------------------------------------------------------------------------- +// Attempt to lead the target (needed because citizens can't hit manhacks with the crowbar!) +//----------------------------------------------------------------------------- +extern ConVar sk_crowbar_lead_time; + +int CHLCustomWeaponMelee::WeaponMeleeAttack1Condition(float flDot, float flDist) +{ + // Attempt to lead the target (needed because citizens can't hit manhacks with the crowbar!) + CAI_BaseNPC* pNPC = GetOwner()->MyNPCPointer(); + CBaseEntity* pEnemy = pNPC->GetEnemy(); + if (!pEnemy) + return COND_NONE; + + Vector vecVelocity; + vecVelocity = pEnemy->GetSmoothedVelocity(); + + // Project where the enemy will be in a little while + float dt = sk_crowbar_lead_time.GetFloat(); + dt += random->RandomFloat(-0.3f, 0.2f); + if (dt < 0.0f) + dt = 0.0f; + + Vector vecExtrapolatedPos; + VectorMA(pEnemy->WorldSpaceCenter(), dt, vecVelocity, vecExtrapolatedPos); + + Vector vecDelta; + VectorSubtract(vecExtrapolatedPos, pNPC->WorldSpaceCenter(), vecDelta); + + if (fabs(vecDelta.z) > 70) + { + return COND_TOO_FAR_TO_ATTACK; + } + + Vector vecForward = pNPC->BodyDirection2D(); + vecDelta.z = 0.0f; + float flExtrapolatedDist = Vector2DNormalize(vecDelta.AsVector2D()); + if ((flDist > 64) && (flExtrapolatedDist > 64)) + { + return COND_TOO_FAR_TO_ATTACK; + } + + float flExtrapolatedDot = DotProduct2D(vecDelta.AsVector2D(), vecForward.AsVector2D()); + if ((flDot < 0.7) && (flExtrapolatedDot < 0.7)) + { + return COND_NOT_FACING_ATTACK; + } + + return COND_CAN_MELEE_ATTACK1; +} + + +//----------------------------------------------------------------------------- +// Animation event handlers +//----------------------------------------------------------------------------- +void CHLCustomWeaponMelee::HandleAnimEventMeleeHit(animevent_t* pEvent, CBaseCombatCharacter* pOperator) +{ + // Trace up or down based on where the enemy is... + // But only if we're basically facing that direction + Vector vecDirection; + AngleVectors(GetAbsAngles(), &vecDirection); + + CBaseEntity* pEnemy = pOperator->MyNPCPointer() ? pOperator->MyNPCPointer()->GetEnemy() : NULL; + if (pEnemy) + { + Vector vecDelta; + VectorSubtract(pEnemy->WorldSpaceCenter(), pOperator->Weapon_ShootPosition(), vecDelta); + VectorNormalize(vecDelta); + + Vector2D vecDelta2D = vecDelta.AsVector2D(); + Vector2DNormalize(vecDelta2D); + if (DotProduct2D(vecDelta2D, vecDirection.AsVector2D()) > 0.8f) + { + vecDirection = vecDelta; + } + } + + Vector vecEnd; + VectorMA(pOperator->Weapon_ShootPosition(), 50, vecDirection, vecEnd); + CBaseEntity* pHurt = pOperator->CheckTraceHullAttack(pOperator->Weapon_ShootPosition(), vecEnd, + Vector(-16, -16, -16), Vector(36, 36, 36), m_CustomData.m_flNPCDamage, GetDamageType(), 0.75); + + // did I hit someone? + if (pHurt) + { + // play sound + WeaponSound(MELEE_HIT); + + // Fake a trace impact, so the effects work out like a player's crowbaw + trace_t traceHit; + UTIL_TraceLine(pOperator->Weapon_ShootPosition(), pHurt->GetAbsOrigin(), MASK_SHOT_HULL, pOperator, COLLISION_GROUP_NONE, &traceHit); + ImpactEffect(traceHit); + } + else + { + WeaponSound(MELEE_MISS); + } +} + + +//----------------------------------------------------------------------------- +// Animation event +//----------------------------------------------------------------------------- +void CHLCustomWeaponMelee::Operator_HandleAnimEvent(animevent_t* pEvent, CBaseCombatCharacter* pOperator) +{ + switch (pEvent->event) + { + case EVENT_WEAPON_MELEE_HIT: + HandleAnimEventMeleeHit(pEvent, pOperator); + break; + + default: + BaseClass::Operator_HandleAnimEvent(pEvent, pOperator); + break; + } +} + +//-------------------------------------------------------------------------- +// +// Custom ranged weapon +// +//-------------------------------------------------------------------------- + +class CHLCustomWeaponGun : public CBaseHLCombatWeapon, public ICustomWeapon +{ +public: + DECLARE_CLASS(CHLCustomWeaponGun, CBaseHLCombatWeapon); + DECLARE_SERVERCLASS(); + DECLARE_DATADESC(); + + CHLCustomWeaponGun(); + virtual void InitCustomWeaponFromData(const void* pData, const char* pszWeaponScript); + const char* GetWeaponScriptName() { return m_iszWeaponScriptName.Get(); } + + // Weapon behaviour + virtual void ItemPostFrame(void); // called each frame by the player PostThink + virtual void ItemBusyFrame(void); // called each frame by the player PostThink, if the player's not ready to attack yet + virtual bool ReloadOrSwitchWeapons(void); + virtual bool Holster(CBaseCombatWeapon* pSwitchingTo = NULL); + + // Bullet launch information + virtual const Vector& GetBulletSpread(void); + virtual float GetFireRate(void) { return m_CustomData.m_flFireRate; } + virtual int GetMinBurst() { return m_CustomData.m_nMinBurst; } + virtual int GetMaxBurst() { return m_CustomData.m_nMaxBurst; } + virtual float GetMinRestTime() { return m_CustomData.m_RestInterval.start; } + virtual float GetMaxRestTime() { return m_CustomData.m_RestInterval.start + m_CustomData.m_RestInterval.range; } + + // Autoaim + virtual float GetMaxAutoAimDeflection() { return 0.99f; } + virtual float WeaponAutoAimScale() { return m_CustomData.m_flAutoAimScale; } // allows a weapon to influence the perceived size of the target's autoaim radius. + + virtual void AddViewKick(void); + int WeaponSoundRealtime(WeaponSound_t shoot_type); + + bool StartReload(void); + bool Reload(void); + void FillClip(void); + void FinishReload(void); + void Pump(void); + + void PrimaryAttack(); + + void FireNPCPrimaryAttack(CBaseCombatCharacter* pOperator, bool bUseWeaponAngles); + void FireNPCSecondaryAttack(CBaseCombatCharacter* pOperator, bool bUseWeaponAngles); + void Operator_ForceNPCFire(CBaseCombatCharacter* pOperator, bool bSecondary); + void Operator_HandleAnimEvent(animevent_t* pEvent, CBaseCombatCharacter* pOperator); + int CapabilitiesGet(void) { return bits_CAP_WEAPON_RANGE_ATTACK1; } + + Activity GetPrimaryAttackActivity(void); + + virtual acttable_t* ActivityList(void); + virtual int ActivityListCount(void); + + virtual acttable_t* GetBackupActivityList(); + virtual int GetBackupActivityListCount(); +private: + void CheckZoomToggle(void); + void ToggleZoom(void); + +public: + typedef struct Data_s + { + float m_flFireRate; + int m_nMinBurst; + int m_nMaxBurst; + interval_t m_RestInterval; + + float m_flAutoAimScale; + + Vector m_vPlayerSpread; + Vector m_vAllySpread; + Vector m_vNPCSpread; + int m_nBulletsPerShot; // For shotguns + + // Viewkick + float m_flMaxVerticalKick; + float m_flSlideLimit; + interval_t m_VerticalPunchRange; + + int m_nActTableIndex; + + bool m_bUseRecoilAnims; + bool m_bFullAuto; // True for machine gun, false for semi-auto + bool m_bNextAttackFromSequence; + bool m_bUsePumpAnimation; + bool m_bHasSecondaryFire; + bool m_bHasZoom; + bool m_bZoomDuringReload; + } Data_t; + + struct Cache_s : public Data_s + { + bool m_bFiresUnderwater; // true if this weapon can fire underwater + bool m_bAltFiresUnderwater; // true if this weapon can fire underwater + float m_fMinRange1; // What's the closest this weapon can be used? + float m_fMinRange2; // What's the closest this weapon can be used? + float m_fMaxRange1; // What's the furthest this weapon can be used? + float m_fMaxRange2; // What's the furthest this weapon can be used? + bool m_bReloadsSingly; // True if this weapon reloads 1 round at a time + + bool Parse(KeyValues*); + }; + +private: + CNetworkString(m_iszWeaponScriptName, 128); + + Data_t m_CustomData; + + bool m_bNeedPump; // When emptied completely + bool m_bDelayedFire1; // Fire primary when finished reloading + bool m_bDelayedFire2; // Fire secondary when finished reloading + bool m_bInZoom; + bool m_bMustReload; + + int m_nShotsFired; // Number of consecutive shots fired + float m_flNextSoundTime; // real-time clock of when to make next sound +public: + enum WeaponActTable_e + { + ACTTABLE_SMG1 = 0, + ACTTABLE_PISTOL, + ACTTABLE_REVOLVER, + ACTTABLE_SHOTGUN, + ACTTABLE_AR2, + ACTTABLE_CROSSBOW, + ACTTABLE_ANNABELLE, + + NUM_GUN_ACT_TABLES + }; +}; + +IMPLEMENT_SERVERCLASS_ST(CHLCustomWeaponGun, DT_HLCustomWeaponGun) +SendPropString(SENDINFO(m_iszWeaponScriptName)), +END_SEND_TABLE(); + +BEGIN_DATADESC(CHLCustomWeaponGun) +DEFINE_FIELD(m_nShotsFired, FIELD_INTEGER), +DEFINE_FIELD(m_flNextSoundTime, FIELD_TIME), +DEFINE_FIELD(m_bNeedPump, FIELD_BOOLEAN), +DEFINE_FIELD(m_bDelayedFire1, FIELD_BOOLEAN), +DEFINE_FIELD(m_bDelayedFire2, FIELD_BOOLEAN), +DEFINE_FIELD(m_bInZoom, FIELD_BOOLEAN), +DEFINE_FIELD(m_bMustReload, FIELD_BOOLEAN), +END_DATADESC(); + +DEFINE_CUSTOM_WEAPON_FACTORY(hl2_gun, CHLCustomWeaponGun, CHLCustomWeaponGun::Cache_s); + +CHLCustomWeaponGun::CHLCustomWeaponGun() +{ + m_bNeedPump = false; + m_bDelayedFire1 = false; + m_bDelayedFire2 = false; + m_bInZoom = false; + m_bMustReload = false; + m_nShotsFired = 0; +} + +acttable_t* CHLCustomWeaponGun::ActivityList(void) +{ + switch (m_CustomData.m_nActTableIndex) + { + default: + case ACTTABLE_SMG1: + return GetSMG1Acttable(); + break; + case ACTTABLE_PISTOL: + return GetPistolActtable(); + break; + case ACTTABLE_REVOLVER: + return Get357Acttable(); + break; + case ACTTABLE_SHOTGUN: + return GetShotgunActtable(); + break; + case ACTTABLE_AR2: + return GetAR2Acttable(); + break; + case ACTTABLE_CROSSBOW: + return GetCrossbowActtable(); + break; + case ACTTABLE_ANNABELLE: + return GetAnnabelleActtable(); + break; + } +} + +int CHLCustomWeaponGun::ActivityListCount(void) +{ + switch (m_CustomData.m_nActTableIndex) + { + default: + case ACTTABLE_SMG1: + return GetSMG1ActtableCount(); + break; + case ACTTABLE_PISTOL: + return GetPistolActtableCount(); + break; + case ACTTABLE_REVOLVER: + return Get357ActtableCount(); + break; + case ACTTABLE_SHOTGUN: + return GetShotgunActtableCount(); + break; + case ACTTABLE_AR2: + return GetAR2ActtableCount(); + break; + case ACTTABLE_CROSSBOW: + return GetCrossbowActtableCount(); + break; + case ACTTABLE_ANNABELLE: + return GetAnnabelleActtableCount(); + break; + } +} + +acttable_t* CHLCustomWeaponGun::GetBackupActivityList(void) +{ + switch (m_CustomData.m_nActTableIndex) + { + default: + case ACTTABLE_SMG1: + case ACTTABLE_CROSSBOW: + case ACTTABLE_AR2: + return GetSMG1Acttable(); + break; + case ACTTABLE_PISTOL: + case ACTTABLE_REVOLVER: + return GetPistolActtable(); + break; + case ACTTABLE_SHOTGUN: + case ACTTABLE_ANNABELLE: + return GetShotgunActtable(); + break; + } +} + +int CHLCustomWeaponGun::GetBackupActivityListCount(void) +{ + switch (m_CustomData.m_nActTableIndex) + { + default: + case ACTTABLE_SMG1: + case ACTTABLE_CROSSBOW: + case ACTTABLE_AR2: + return GetSMG1ActtableCount(); + break; + case ACTTABLE_PISTOL: + case ACTTABLE_REVOLVER: + return GetPistolActtableCount(); + break; + case ACTTABLE_SHOTGUN: + case ACTTABLE_ANNABELLE: + return GetShotgunActtableCount(); + break; + } +} + +void ReadIntervalInt(const char* pString, int &iMin, int &iMax) +{ + char tempString[128]; + Q_strncpy(tempString, pString, sizeof(tempString)); + + char* token = strtok(tempString, ","); + if (token) + { + iMin = atoi(token); + token = strtok(NULL, ","); + if (token) + { + iMax = atoi(token); + } + else + { + iMax = iMin; + } + } +} + +bool CHLCustomWeaponGun::Cache_s::Parse(KeyValues* pKVWeapon) +{ + static const char* ppszCustomGunAnimTypes[NUM_GUN_ACT_TABLES] = { + "smg", + "pistol", + "revolver", + "shotgun", + "ar2", + "crossbow", + "annabelle", + }; + + KeyValues* pkvData = pKVWeapon->FindKey("CustomData"); + if (pkvData) + { + m_flFireRate = pkvData->GetFloat("fire_rate", 0.5f); + ReadIntervalInt(pkvData->GetString("npc_burst", "1"), m_nMinBurst, m_nMaxBurst); + m_RestInterval = ReadInterval(pkvData->GetString("npc_rest_time", "0.3,0.6")); + m_flAutoAimScale = pkvData->GetFloat("autoaim_scale", 1.f); + m_bFullAuto = pkvData->GetBool("auto_fire"); + m_nBulletsPerShot = pkvData->GetInt("bullets", 1); + m_bUseRecoilAnims = pkvData->GetBool("recoil_anims", true); + m_bReloadsSingly = pkvData->GetBool("reload_singly"); + m_bFiresUnderwater = pkvData->GetBool("fires_underwater"); + m_bHasZoom = pkvData->GetBool("zoom_enable"); + m_bZoomDuringReload = m_bHasZoom && pkvData->GetBool("zoom_in_reload"); + + m_fMinRange1 = pkvData->GetFloat("range1_min", 65.f); + m_fMinRange2 = pkvData->GetFloat("range2_min", 65.f); + m_fMaxRange1 = pkvData->GetFloat("range1_max", 1024.f); + m_fMaxRange2 = pkvData->GetFloat("range2_max", 1024.f); + + if (m_bFullAuto) + { + m_flMaxVerticalKick = pkvData->GetFloat("viewkick_vertical_max", 1.f); + m_flSlideLimit = pkvData->GetFloat("viewkick_slide_limit", 2.f); + } + else + { + m_flSlideLimit = pkvData->GetFloat("viewpunch_side_max", .6f); + m_VerticalPunchRange = ReadInterval(pkvData->GetString("viewpunch_vertical", "0.25,0.5")); + + m_bNextAttackFromSequence = pkvData->GetBool("next_attack_time_from_sequence"); + m_bUsePumpAnimation = pkvData->GetBool("use_pump_anim"); + } + + // NOTE: The way these are calculated is that each component == sin (degrees/2) + float flSpread = pkvData->GetFloat("spread", 5.f); + float flNPCSpread = pkvData->GetFloat("spread_npc", flSpread); + float flAllySperad = pkvData->GetFloat("spread_ally", flNPCSpread); + m_vPlayerSpread = Vector(sin(DEG2RAD(flSpread * 0.5f))); + m_vNPCSpread = Vector(sin(DEG2RAD(flNPCSpread * 0.5f))); + m_vAllySpread = Vector(sin(DEG2RAD(flAllySperad * 0.5f))); + + const char* pszAnimType = pkvData->GetString("anim_type", nullptr); + if (pszAnimType) + { + for (int i = 0; i < NUM_GUN_ACT_TABLES; i++) + { + if (V_stricmp(pszAnimType, ppszCustomGunAnimTypes[i]) == 0) + { + m_nActTableIndex = i; + break; + } + } + } + + return true; + } + + return false; +} + +void CHLCustomWeaponGun::InitCustomWeaponFromData(const void* pData, const char* pszWeaponScript) +{ + Q_FileBase(pszWeaponScript, m_iszWeaponScriptName.GetForModify(), 128); + const auto* pCache = static_cast (pData); + m_CustomData = *pCache; + m_bFiresUnderwater = pCache->m_bFiresUnderwater; + m_bAltFiresUnderwater = pCache->m_bAltFiresUnderwater; + m_fMinRange1 = pCache->m_fMinRange1; + m_fMinRange2 = pCache->m_fMinRange2; + m_fMaxRange1 = pCache->m_fMaxRange1; + m_fMaxRange2 = pCache->m_fMaxRange2; + m_bReloadsSingly = pCache->m_bReloadsSingly; +} + +const Vector& CHLCustomWeaponGun::GetBulletSpread() +{ + if (!GetOwner() || !GetOwner()->IsNPC()) + return m_CustomData.m_vPlayerSpread; + + if (GetOwner()->MyNPCPointer()->IsPlayerAlly()) + { + // 357 allies should be cooler + return m_CustomData.m_vAllySpread; + } + + return m_CustomData.m_vNPCSpread; +} + +void CHLCustomWeaponGun::AddViewKick(void) +{ + //Get the view kick + CBasePlayer* pPlayer = ToBasePlayer(GetOwner()); + + if (!pPlayer) + return; + + if (m_CustomData.m_bFullAuto) + { + float flDuration = m_fFireDuration; + + if (g_pGameRules->GetAutoAimMode() == AUTOAIM_ON_CONSOLE) + { + // On the 360 (or in any configuration using the 360 aiming scheme), don't let the + // AR2 progressive into the late, highly inaccurate stages of its kick. Just + // spoof the time to make it look (to the kicking code) like we haven't been + // firing for very long. + flDuration = MIN(flDuration, 0.75f); + } + + CHLMachineGun::DoMachineGunKick(pPlayer, 0.5f, m_CustomData.m_flMaxVerticalKick, flDuration, m_CustomData.m_flSlideLimit); + } + else + { + QAngle viewPunch; + viewPunch.x = RandomInterval(m_CustomData.m_VerticalPunchRange); + viewPunch.y = RandomFloat(-m_CustomData.m_flSlideLimit, m_CustomData.m_flSlideLimit); + viewPunch.z = 0.0f; + + //Add it to the view punch + pPlayer->ViewPunch(viewPunch); + } +} + +bool CHLCustomWeaponGun::Holster(CBaseCombatWeapon* pSwitchingTo) +{ + // Stop zooming + if (m_bInZoom) + { + ToggleZoom(); + } + + return BaseClass::Holster(pSwitchingTo); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CHLCustomWeaponGun::CheckZoomToggle(void) +{ + if (!m_CustomData.m_bHasZoom) + return; + + CBasePlayer* pPlayer = ToBasePlayer(GetOwner()); + + int iButtonsTest = IN_ATTACK3; + if (!m_CustomData.m_bHasSecondaryFire) + iButtonsTest |= IN_ATTACK2; + + if (pPlayer->m_afButtonPressed & iButtonsTest) + { + ToggleZoom(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CHLCustomWeaponGun::ToggleZoom(void) +{ + CBasePlayer* pPlayer = ToBasePlayer(GetOwner()); + + if (pPlayer == NULL) + return; + + if (m_bInZoom) + { + if (pPlayer->SetFOV(this, 0, 0.2f)) + { + m_bInZoom = false; + } + } + else + { + if (pPlayer->SetFOV(this, 20, 0.1f)) + { + m_bInZoom = true; + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Override so only reload one shell at a time +// Input : +// Output : +//----------------------------------------------------------------------------- +bool CHLCustomWeaponGun::StartReload(void) +{ + CBaseCombatCharacter* pOwner = GetOwner(); + + if (pOwner == NULL) + return false; + + if (pOwner->GetAmmoCount(m_iPrimaryAmmoType) <= 0) + return false; + + if (m_iClip1 >= GetMaxClip1()) + return false; + + // If shotgun totally emptied then a pump animation is needed + + //NOTENOTE: This is kinda lame because the player doesn't get strong feedback on when the reload has finished, + // without the pump. Technically, it's incorrect, but it's good for feedback... + + if (m_CustomData.m_bUsePumpAnimation && m_iClip1 <= 0) + { + m_bNeedPump = true; + } + + int j = MIN(1, pOwner->GetAmmoCount(m_iPrimaryAmmoType)); + + if (j <= 0) + return false; + + SendWeaponAnim(ACT_SHOTGUN_RELOAD_START); + + // Make shotgun shell visible + SetBodygroup(1, 0); + + pOwner->m_flNextAttack = gpGlobals->curtime; + m_flNextPrimaryAttack = gpGlobals->curtime + SequenceDuration(); + +#ifdef MAPBASE + if (pOwner->IsPlayer()) + { + static_cast(pOwner)->SetAnimation(PLAYER_RELOAD); + } +#endif + + if (m_bInZoom && !m_CustomData.m_bZoomDuringReload) + { + ToggleZoom(); + } + + m_bInReload = true; + m_bMustReload = false; + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Override so only reload one shell at a time +// Input : +// Output : +//----------------------------------------------------------------------------- +bool CHLCustomWeaponGun::Reload(void) +{ + if (m_bReloadsSingly) + { + // Check that StartReload was called first + if (!m_bInReload) + { + Warning("ERROR: Shotgun Reload called incorrectly!\n"); + } + + CBaseCombatCharacter* pOwner = GetOwner(); + + if (pOwner == NULL) + return false; + + if (pOwner->GetAmmoCount(m_iPrimaryAmmoType) <= 0) + return false; + + if (m_iClip1 >= GetMaxClip1()) + return false; + + int j = MIN(1, pOwner->GetAmmoCount(m_iPrimaryAmmoType)); + + if (j <= 0) + return false; + + FillClip(); + // Play reload on different channel as otherwise steals channel away from fire sound + WeaponSound(RELOAD); + SendWeaponAnim(ACT_VM_RELOAD); + + pOwner->m_flNextAttack = gpGlobals->curtime; + m_flNextPrimaryAttack = gpGlobals->curtime + SequenceDuration(); + + return true; + } + else if (BaseClass::Reload()) + { + if (m_bInZoom && !m_CustomData.m_bZoomDuringReload) + { + ToggleZoom(); + } + + m_bMustReload = false; + return true; + } + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: Play finish reload anim and fill clip +// Input : +// Output : +//----------------------------------------------------------------------------- +void CHLCustomWeaponGun::FinishReload(void) +{ + if (m_bReloadsSingly) + { + // Make shotgun shell invisible + SetBodygroup(1, 1); + + CBaseCombatCharacter* pOwner = GetOwner(); + + if (pOwner == NULL) + return; + + m_bInReload = false; + + // Finish reload animation + SendWeaponAnim(ACT_SHOTGUN_RELOAD_FINISH); + + pOwner->m_flNextAttack = gpGlobals->curtime; + m_flNextPrimaryAttack = gpGlobals->curtime + SequenceDuration(); + } + else + { + BaseClass::FinishReload(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Play finish reload anim and fill clip +// Input : +// Output : +//----------------------------------------------------------------------------- +void CHLCustomWeaponGun::FillClip(void) +{ + CBaseCombatCharacter* pOwner = GetOwner(); + + if (pOwner == NULL) + return; + + // Add them to the clip + if (pOwner->GetAmmoCount(m_iPrimaryAmmoType) > 0) + { + if (Clip1() < GetMaxClip1()) + { + m_iClip1++; + pOwner->RemoveAmmo(1, m_iPrimaryAmmoType); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Play weapon pump anim +// Input : +// Output : +//----------------------------------------------------------------------------- +void CHLCustomWeaponGun::Pump(void) +{ + CBaseCombatCharacter* pOwner = GetOwner(); + + if (pOwner == NULL) + return; + + m_bNeedPump = false; + + WeaponSound(SPECIAL1); + + // Finish reload animation + SendWeaponAnim(ACT_SHOTGUN_PUMP); + + pOwner->m_flNextAttack = gpGlobals->curtime + SequenceDuration(); + m_flNextPrimaryAttack = gpGlobals->curtime + SequenceDuration(); +} + +//----------------------------------------------------------------------------- +// Purpose: If the current weapon has more ammo, reload it. Otherwise, switch +// to the next best weapon we've got. Returns true if it took any action. +//----------------------------------------------------------------------------- +bool CHLCustomWeaponGun::ReloadOrSwitchWeapons(void) +{ + CBasePlayer* pOwner = ToBasePlayer(GetOwner()); + Assert(pOwner); + + m_bFireOnEmpty = false; + + // If we don't have any ammo, switch to the next best weapon + if (!HasAnyAmmo() && m_flNextPrimaryAttack < gpGlobals->curtime && m_flNextSecondaryAttack < gpGlobals->curtime) + { + // weapon isn't useable, switch. + // Ammo might be overridden to 0, in which case we shouldn't do this + if (((GetWeaponFlags() & ITEM_FLAG_NOAUTOSWITCHEMPTY) == false) && !HasSpawnFlags(SF_WEAPON_NO_AUTO_SWITCH_WHEN_EMPTY) && (g_pGameRules->SwitchToNextBestWeapon(pOwner, this))) + { + m_flNextPrimaryAttack = gpGlobals->curtime + 0.3; + return true; + } + } + else + { + // Weapon is useable. Reload if empty and weapon has waited as long as it has to after firing + if (UsesClipsForAmmo1() && !AutoFiresFullClip() && + (m_iClip1 == 0) && + (GetWeaponFlags() & ITEM_FLAG_NOAUTORELOAD) == false && + m_flNextPrimaryAttack < gpGlobals->curtime && + m_flNextSecondaryAttack < gpGlobals->curtime) + { + // if we're successfully reloading, we're done + if (m_bReloadsSingly) + return StartReload(); + else + return Reload(); + } + } + + return false; +} + +void CHLCustomWeaponGun::ItemBusyFrame(void) +{ + BaseClass::ItemBusyFrame(); + + if (m_CustomData.m_bZoomDuringReload) + CheckZoomToggle(); +} + +void CHLCustomWeaponGun::ItemPostFrame(void) +{ + CBasePlayer* pOwner = ToBasePlayer(GetOwner()); + if (!pOwner) + return; + + // Debounce the recoiling counter + if ((pOwner->m_nButtons & IN_ATTACK) == false) + { + m_nShotsFired = 0; + } + + UpdateAutoFire(); + + if (m_CustomData.m_bZoomDuringReload || !m_bInReload) + CheckZoomToggle(); + + if (m_bReloadsSingly) + { + if (m_bInReload) + { + m_fFireDuration = 0.f; + + // If I'm primary firing and have one round stop reloading and fire + if ((pOwner->m_nButtons & IN_ATTACK) && (m_iClip1 >= 1)) + { + m_bInReload = false; + m_bNeedPump = false; + m_bDelayedFire1 = true; + } + // If I'm secondary firing and have one round stop reloading and fire + else if (m_CustomData.m_bHasSecondaryFire && (pOwner->m_nButtons & IN_ATTACK2) && (m_iClip1 >= 2)) + { + m_bInReload = false; + m_bNeedPump = false; + m_bDelayedFire2 = true; + } + else if (m_flNextPrimaryAttack <= gpGlobals->curtime) + { + // If out of ammo end reload + if (pOwner->GetAmmoCount(m_iPrimaryAmmoType) <= 0) + { + FinishReload(); + return; + } + // If clip not full reload again + if (m_iClip1 < GetMaxClip1()) + { + Reload(); + return; + } + // Clip full, stop reloading + else + { + FinishReload(); + return; + } + } + } + else + { + // Make shotgun shell invisible + SetBodygroup(1, 1); + } + } + else if (UsesClipsForAmmo1()) + { + CheckReload(); + } + + if (m_CustomData.m_bUsePumpAnimation && (m_bNeedPump) && (m_flNextPrimaryAttack <= gpGlobals->curtime)) + { + m_fFireDuration = 0.f; + Pump(); + return; + } + + //Track the duration of the fire + //FIXME: Check for IN_ATTACK2 as well? + //FIXME: What if we're calling ItemBusyFrame? + m_fFireDuration = (pOwner->m_nButtons & IN_ATTACK) ? (m_fFireDuration + gpGlobals->frametime) : 0.0f; + + bool bFired = false; + + // Secondary attack has priority + if (m_CustomData.m_bHasSecondaryFire && !m_bMustReload && (m_bDelayedFire2 || pOwner->m_nButtons & IN_ATTACK2) && (m_flNextSecondaryAttack <= gpGlobals->curtime)) + { + m_bDelayedFire2 = false; + + if (pOwner->HasSpawnFlags(SF_PLAYER_SUPPRESS_FIRING)) + { + // Don't do anything, just cancel the whole function + return; + } + else if (UsesSecondaryAmmo() && pOwner->GetAmmoCount(m_iSecondaryAmmoType) <= 0) + { + if (m_flNextEmptySoundTime < gpGlobals->curtime) + { + WeaponSound(EMPTY); + m_flNextSecondaryAttack = m_flNextEmptySoundTime = gpGlobals->curtime + 0.5; + } + } + else if (pOwner->GetWaterLevel() == 3 && m_bAltFiresUnderwater == false) + { + // This weapon doesn't fire underwater + WeaponSound(EMPTY); + m_flNextPrimaryAttack = gpGlobals->curtime + 0.2; + return; + } + else + { + // FIXME: This isn't necessarily true if the weapon doesn't have a secondary fire! + // For instance, the crossbow doesn't have a 'real' secondary fire, but it still + // stops the crossbow from firing on the 360 if the player chooses to hold down their + // zoom button. (sjb) Orange Box 7/25/2007 +#if !defined(CLIENT_DLL) + if (!IsX360() || !ClassMatches("weapon_crossbow")) +#endif + { + bFired = ShouldBlockPrimaryFire(); + } + + SecondaryAttack(); + + // Secondary ammo doesn't have a reload animation + if (UsesClipsForAmmo2()) + { + // reload clip2 if empty + if (m_iClip2 < 1) + { + pOwner->RemoveAmmo(1, m_iSecondaryAmmoType); + m_iClip2 = m_iClip2 + 1; + } + } + } + } + + if (!bFired && !m_bMustReload && (m_bDelayedFire1 || pOwner->m_nButtons & IN_ATTACK) && (m_flNextPrimaryAttack <= gpGlobals->curtime)) + { + m_bDelayedFire1 = false; + + if (pOwner->HasSpawnFlags(SF_PLAYER_SUPPRESS_FIRING)) + { + // Don't do anything, just cancel the whole function + return; + } + // Clip empty? Or out of ammo on a no-clip weapon? + else if ((UsesClipsForAmmo1() && m_iClip1 <= 0) || (!UsesClipsForAmmo1() && pOwner->GetAmmoCount(m_iPrimaryAmmoType) <= 0)) + { + HandleFireOnEmpty(); + } + else if (pOwner->GetWaterLevel() == 3 && m_bFiresUnderwater == false) + { + // This weapon doesn't fire underwater + WeaponSound(EMPTY); + m_flNextPrimaryAttack = gpGlobals->curtime + 0.2; + return; + } + else + { + //NOTENOTE: There is a bug with this code with regards to the way machine guns catch the leading edge trigger + // on the player hitting the attack key. It relies on the gun catching that case in the same frame. + // However, because the player can also be doing a secondary attack, the edge trigger may be missed. + // We really need to hold onto the edge trigger and only clear the condition when the gun has fired its + // first shot. Right now that's too much of an architecture change -- jdw + + // If the firing button was just pressed, or the alt-fire just released, reset the firing time + if ((pOwner->m_afButtonPressed & IN_ATTACK) || (pOwner->m_afButtonReleased & IN_ATTACK2)) + { + m_flNextPrimaryAttack = gpGlobals->curtime; + } + + PrimaryAttack(); + + if (AutoFiresFullClip()) + { + m_bFiringWholeClip = true; + } + +#ifdef CLIENT_DLL + pOwner->SetFiredWeapon(true); +#endif + } + } + + // ----------------------- + // Reload pressed / Clip Empty + // ----------------------- + if ((pOwner->m_nButtons & IN_RELOAD || m_bMustReload) && UsesClipsForAmmo1() && !m_bInReload) + { + // reload when reload is pressed, or if no buttons are down and weapon is empty. + if (m_bReloadsSingly) + StartReload(); + else + Reload(); + + m_fFireDuration = 0.0f; + } + + // ----------------------- + // No buttons down + // ----------------------- + else if (!((pOwner->m_nButtons & IN_ATTACK) || (pOwner->m_nButtons & IN_ATTACK2) || (CanReload() && pOwner->m_nButtons & IN_RELOAD))) + { + // no fire buttons down or reloading + if (!ReloadOrSwitchWeapons() && (m_bInReload == false)) + { + WeaponIdle(); + } + } +} + +void CHLCustomWeaponGun::PrimaryAttack() +{ + // Only the player fires this way so we can cast + CBasePlayer* pPlayer = ToBasePlayer(GetOwner()); + if (!pPlayer) + return; + + // Abort here to handle burst and auto fire modes + if ((UsesClipsForAmmo1() && m_iClip1 == 0) || (!UsesClipsForAmmo1() && !pPlayer->GetAmmoCount(m_iPrimaryAmmoType))) + return; + + if (m_CustomData.m_bFullAuto) + { + m_nShotsFired++; + + pPlayer->DoMuzzleFlash(); + + // To make the firing framerate independent, we may have to fire more than one bullet here on low-framerate systems, + // especially if the weapon we're firing has a really fast rate of fire. + int iBulletsToFire = 0; + float fireRate = GetFireRate(); + + // MUST call sound before removing a round from the clip of a CHLMachineGun + while (m_flNextPrimaryAttack <= gpGlobals->curtime) + { + WeaponSound(SINGLE, m_flNextPrimaryAttack); + m_flNextPrimaryAttack = m_flNextPrimaryAttack + fireRate; + iBulletsToFire++; + } + + // Make sure we don't fire more than the amount in the clip, if this weapon uses clips + if (UsesClipsForAmmo1()) + { + if (iBulletsToFire > m_iClip1) + iBulletsToFire = m_iClip1; + m_iClip1 -= iBulletsToFire; + } + + // Fire the bullets + FireBulletsInfo_t info; + info.m_iShots = iBulletsToFire * m_CustomData.m_nBulletsPerShot; + info.m_vecSrc = pPlayer->Weapon_ShootPosition(); + info.m_vecDirShooting = pPlayer->GetAutoaimVector(AUTOAIM_SCALE_DEFAULT); + info.m_vecSpread = pPlayer->GetAttackSpread(this); + info.m_flDistance = MAX_TRACE_LENGTH; + info.m_iAmmoType = m_iPrimaryAmmoType; + info.m_iTracerFreq = 2; + FireBullets(info); + + SendWeaponAnim(GetPrimaryAttackActivity()); + } + else + { + if (!m_CustomData.m_bNextAttackFromSequence && !m_CustomData.m_bUsePumpAnimation && !(pPlayer->m_afButtonPressed & IN_ATTACK)) + return; + + m_nShotsFired++; + + // MUST call sound before removing a round from the clip of a CMachineGun + WeaponSound(SINGLE); + pPlayer->DoMuzzleFlash(); + SendWeaponAnim(GetPrimaryAttackActivity()); + + m_flNextPrimaryAttack = gpGlobals->curtime + ((m_CustomData.m_bNextAttackFromSequence || m_CustomData.m_bUsePumpAnimation) ? GetViewModelSequenceDuration() : GetFireRate()); + m_iClip1 -= 1; + + Vector vecSrc = pPlayer->Weapon_ShootPosition(); + Vector vecAiming = pPlayer->GetAutoaimVector(AUTOAIM_SCALE_DEFAULT); + + pPlayer->SetMuzzleFlashTime(gpGlobals->curtime + 1.0); + + // Fire the bullets, and force the first shot to be perfectly accuracy + pPlayer->FireBullets(m_CustomData.m_nBulletsPerShot, vecSrc, vecAiming, GetBulletSpread(), MAX_TRACE_LENGTH, m_iPrimaryAmmoType, 0, -1, -1, 0, NULL, (m_CustomData.m_nBulletsPerShot > 1), true); + + if (m_CustomData.m_bUsePumpAnimation && m_iClip1) + { + // pump so long as some rounds are left. + m_bNeedPump = true; + } + } + + m_iPrimaryAttacks++; + + //Factor in the view kick + AddViewKick(); + + CSoundEnt::InsertSound(SOUND_COMBAT, GetAbsOrigin(), SOUNDENT_VOLUME_MACHINEGUN, 0.2, pPlayer); + + if (!m_iClip1 && pPlayer->GetAmmoCount(m_iPrimaryAmmoType) <= 0) + { + // HEV suit - indicate out of ammo condition + pPlayer->SetSuitUpdate("!HEV_AMO0", FALSE, 0); + } + + pPlayer->SetAnimation(PLAYER_ATTACK1); + + // Register a muzzleflash for the AI + pPlayer->SetMuzzleFlashTime(gpGlobals->curtime + 0.5); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Activity +//----------------------------------------------------------------------------- +Activity CHLCustomWeaponGun::GetPrimaryAttackActivity(void) +{ + if (!m_CustomData.m_bUseRecoilAnims || m_nShotsFired < 2) + return ACT_VM_PRIMARYATTACK; + + if (m_nShotsFired < 3) + return ACT_VM_RECOIL1; + + if (m_nShotsFired < 4) + return ACT_VM_RECOIL2; + + return ACT_VM_RECOIL3; +} + +//----------------------------------------------------------------------------- +// Purpose: Make enough sound events to fill the estimated think interval +// returns: number of shots needed +//----------------------------------------------------------------------------- +int CHLCustomWeaponGun::WeaponSoundRealtime(WeaponSound_t shoot_type) +{ + int numBullets = 0; + + // ran out of time, clamp to current + if (m_flNextSoundTime < gpGlobals->curtime) + { + m_flNextSoundTime = gpGlobals->curtime; + } + + // make enough sound events to fill up the next estimated think interval + float dt = Clamp(m_flAnimTime - m_flPrevAnimTime, 0.f, 0.2f); + if (m_flNextSoundTime < gpGlobals->curtime + dt) + { + WeaponSound(SINGLE_NPC, m_flNextSoundTime); + m_flNextSoundTime += GetFireRate(); + numBullets++; + } + if (m_flNextSoundTime < gpGlobals->curtime + dt) + { + WeaponSound(SINGLE_NPC, m_flNextSoundTime); + m_flNextSoundTime += GetFireRate(); + numBullets++; + } + + return numBullets; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pOperator - +//----------------------------------------------------------------------------- +void CHLCustomWeaponGun::FireNPCPrimaryAttack(CBaseCombatCharacter* pOperator, bool bUseWeaponAngles) +{ + Vector vecShootOrigin, vecShootDir; + CAI_BaseNPC* npc = pOperator->MyNPCPointer(); + int iMuzzle = LookupAttachment("muzzle"); + + ASSERT(npc != NULL); + + if (bUseWeaponAngles) + { + QAngle angShootDir; + GetAttachment(iMuzzle, vecShootOrigin, angShootDir); + AngleVectors(angShootDir, &vecShootDir); + } + else + { + vecShootOrigin = pOperator->Weapon_ShootPosition(); + vecShootDir = npc->GetActualShootTrajectory(vecShootOrigin); + } + + CSoundEnt::InsertSound(SOUND_COMBAT | SOUND_CONTEXT_GUNFIRE, pOperator->GetAbsOrigin(), SOUNDENT_VOLUME_MACHINEGUN, 0.2f, pOperator, SOUNDENT_CHANNEL_WEAPON, pOperator->GetEnemy()); + + const Vector& vecSpread = (bUseWeaponAngles || m_CustomData.m_nBulletsPerShot > 1) ? GetBulletSpread() : VECTOR_CONE_PRECALCULATED; + if (m_CustomData.m_bFullAuto) + { + int nShots = WeaponSoundRealtime(SINGLE_NPC); + pOperator->FireBullets(nShots * m_CustomData.m_nBulletsPerShot, vecShootOrigin, vecShootDir, vecSpread, MAX_TRACE_LENGTH, m_iPrimaryAmmoType, 2, entindex(), iMuzzle); + pOperator->DoMuzzleFlash(); + m_iClip1 = m_iClip1 - nShots; + } + else + { + WeaponSound(SINGLE_NPC); + pOperator->FireBullets(m_CustomData.m_nBulletsPerShot, vecShootOrigin, vecShootDir, vecSpread, MAX_TRACE_LENGTH, m_iPrimaryAmmoType, 2, entindex(), iMuzzle); + pOperator->DoMuzzleFlash(); + m_iClip1 = m_iClip1 - 1; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CHLCustomWeaponGun::FireNPCSecondaryAttack(CBaseCombatCharacter* pOperator, bool bUseWeaponAngles) +{ + +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CHLCustomWeaponGun::Operator_ForceNPCFire(CBaseCombatCharacter* pOperator, bool bSecondary) +{ + if (bSecondary) + { + FireNPCSecondaryAttack(pOperator, true); + } + else + { + // Ensure we have enough rounds in the clip + m_iClip1++; + + FireNPCPrimaryAttack(pOperator, true); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pEvent - +// *pOperator - +//----------------------------------------------------------------------------- +void CHLCustomWeaponGun::Operator_HandleAnimEvent(animevent_t* pEvent, CBaseCombatCharacter* pOperator) +{ + switch (pEvent->event) + { + case EVENT_WEAPON_SMG1: + case EVENT_WEAPON_SHOTGUN_FIRE: + case EVENT_WEAPON_AR1: + case EVENT_WEAPON_AR2: + case EVENT_WEAPON_HMG1: + case EVENT_WEAPON_SMG2: + case EVENT_WEAPON_SNIPER_RIFLE_FIRE: + case EVENT_WEAPON_PISTOL_FIRE: + { + FireNPCPrimaryAttack(pOperator, false); + } + break; + + case EVENT_WEAPON_AR2_ALTFIRE: + { + FireNPCSecondaryAttack(pOperator, false); + } + break; + + default: + CBaseCombatWeapon::Operator_HandleAnimEvent(pEvent, pOperator); + break; + } +} diff --git a/src/game/server/maprules.cpp b/src/game/server/maprules.cpp index fd2db950..e4b86aa1 100644 --- a/src/game/server/maprules.cpp +++ b/src/game/server/maprules.cpp @@ -12,6 +12,9 @@ #include "entitylist.h" #include "ai_hull.h" #include "entityoutput.h" +#ifdef MAPBASE +#include "eventqueue.h" +#endif // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" @@ -224,6 +227,9 @@ public: DECLARE_DATADESC(); void InputGameEnd( inputdata_t &inputdata ); +#ifdef MAPBASE + void InputGameEndSP( inputdata_t &inputdata ); +#endif void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); private: }; @@ -232,6 +238,9 @@ BEGIN_DATADESC( CGameEnd ) // inputs DEFINE_INPUTFUNC( FIELD_VOID, "EndGame", InputGameEnd ), +#ifdef MAPBASE + DEFINE_INPUTFUNC( FIELD_STRING, "EndGameSP", InputGameEndSP ), +#endif END_DATADESC() @@ -243,6 +252,17 @@ void CGameEnd::InputGameEnd( inputdata_t &inputdata ) g_pGameRules->EndMultiplayerGame(); } +#ifdef MAPBASE +void CGameEnd::InputGameEndSP( inputdata_t &inputdata ) +{ + // This basically just acts as a shortcut for the "startupmenu force"/disconnection command. + // Things like mapping competitions could change this code based on given strings for specific endings (e.g. background maps). + CBasePlayer *pPlayer = AI_GetSinglePlayer(); + if (pPlayer) + engine->ClientCommand(pPlayer->edict(), "startupmenu force"); +} +#endif + void CGameEnd::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) { if ( !CanFireForActivator( pActivator ) ) @@ -274,6 +294,12 @@ public: void InputDisplay( inputdata_t &inputdata ); void Display( CBaseEntity *pActivator ); +#ifdef MAPBASE + void InputSetText ( inputdata_t &inputdata ); + void SetText( const char* pszStr ); + + void InputSetFont( inputdata_t &inputdata ) { m_strFont = inputdata.value.StringID(); } +#endif void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) { @@ -284,6 +310,11 @@ private: string_t m_iszMessage; hudtextparms_t m_textParms; + +#ifdef MAPBASE + string_t m_strFont; + bool m_bAutobreak; +#endif }; LINK_ENTITY_TO_CLASS( game_text, CGameText ); @@ -303,10 +334,19 @@ BEGIN_DATADESC( CGameText ) DEFINE_KEYFIELD( m_textParms.holdTime, FIELD_FLOAT, "holdtime" ), DEFINE_KEYFIELD( m_textParms.fxTime, FIELD_FLOAT, "fxtime" ), +#ifdef MAPBASE + DEFINE_KEYFIELD( m_strFont, FIELD_STRING, "font" ), + DEFINE_KEYFIELD( m_bAutobreak, FIELD_BOOLEAN, "autobreak" ), +#endif + DEFINE_ARRAY( m_textParms, FIELD_CHARACTER, sizeof(hudtextparms_t) ), // Inputs DEFINE_INPUTFUNC( FIELD_VOID, "Display", InputDisplay ), +#ifdef MAPBASE + DEFINE_INPUTFUNC( FIELD_STRING, "SetText", InputSetText ), + DEFINE_INPUTFUNC( FIELD_STRING, "SetFont", InputSetFont ), +#endif END_DATADESC() @@ -332,6 +372,13 @@ bool CGameText::KeyValue( const char *szKeyName, const char *szValue ) m_textParms.b2 = color[2]; m_textParms.a2 = color[3]; } +#ifdef MAPBASE + else if (FStrEq( szKeyName, "message" )) + { + // Needed for newline support + SetText( szValue ); + } +#endif else return BaseClass::KeyValue( szKeyName, szValue ); @@ -351,7 +398,11 @@ void CGameText::Display( CBaseEntity *pActivator ) if ( MessageToAll() ) { +#ifdef MAPBASE + UTIL_HudMessageAll( m_textParms, MessageGet(), STRING(m_strFont), m_bAutobreak ); +#else UTIL_HudMessageAll( m_textParms, MessageGet() ); +#endif } else { @@ -359,16 +410,55 @@ void CGameText::Display( CBaseEntity *pActivator ) if ( gpGlobals->maxClients == 1 ) { CBasePlayer *pPlayer = UTIL_GetLocalPlayer(); +#ifdef MAPBASE + UTIL_HudMessage( pPlayer, m_textParms, MessageGet(), STRING(m_strFont), m_bAutobreak ); +#else UTIL_HudMessage( pPlayer, m_textParms, MessageGet() ); +#endif } // Otherwise show the message to the player that triggered us. else if ( pActivator && pActivator->IsNetClient() ) { +#ifdef MAPBASE + UTIL_HudMessage( ToBasePlayer( pActivator ), m_textParms, MessageGet(), STRING(m_strFont), m_bAutobreak ); +#else UTIL_HudMessage( ToBasePlayer( pActivator ), m_textParms, MessageGet() ); +#endif } } } +#ifdef MAPBASE +void CGameText::InputSetText( inputdata_t &inputdata ) +{ + SetText( inputdata.value.String() ); +} + +void CGameText::SetText( const char* pszStr ) +{ + // Replace /n with \n + if (Q_strstr( pszStr, "/n" )) + { + CUtlStringList vecLines; + Q_SplitString( pszStr, "/n", vecLines ); + + char szMsg[512]; + Q_strncpy( szMsg, vecLines[0], sizeof( szMsg ) ); + + for (int i = 1; i < vecLines.Count(); i++) + { + Q_strncat( szMsg, "\n", sizeof( szMsg ) ); + Q_strncat( szMsg, vecLines[i], sizeof( szMsg ) ); + } + m_iszMessage = AllocPooledString( szMsg ); + } + else + { + m_iszMessage = AllocPooledString( pszStr ); + } +} +#endif + /* TODO: Replace with an entity I/O version // diff --git a/src/game/server/message_entity.cpp b/src/game/server/message_entity.cpp index e5bb114b..903947c1 100644 --- a/src/game/server/message_entity.cpp +++ b/src/game/server/message_entity.cpp @@ -17,6 +17,12 @@ #include "vstdlib/random.h" #include "engine/IEngineSound.h" #include "game.h" +#ifdef MAPBASE +#include +#include +#include "utlbuffer.h" +#include "saverestore_utlvector.h" +#endif #include "player.h" #include "entitylist.h" @@ -38,12 +44,18 @@ public: void Spawn( void ); void Activate( void ); void Think( void ); +#ifdef MAPBASE + virtual +#endif void DrawOverlays(void); virtual void UpdateOnRemove(); void InputEnable( inputdata_t &inputdata ); void InputDisable( inputdata_t &inputdata ); +#ifdef MAPBASE + virtual void InputSetMessage( inputdata_t &inputdata ); +#endif DECLARE_DATADESC(); @@ -68,6 +80,9 @@ BEGIN_DATADESC( CMessageEntity ) // Inputs DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ), DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ), +#ifdef MAPBASE + DEFINE_INPUTFUNC( FIELD_STRING, "SetMessage", InputSetMessage ), +#endif END_DATADESC() @@ -172,6 +187,19 @@ void CMessageEntity::InputDisable( inputdata_t &inputdata ) m_bEnabled = false; } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CMessageEntity::InputSetMessage( inputdata_t &inputdata ) +{ + char newmessage[256]; + Q_strncpy(newmessage, inputdata.value.String(), sizeof(newmessage)); + + m_messageText = AllocPooledString(newmessage); +} +#endif + // This is a hack to make point_message stuff appear in developer 0 release builds // for now void DrawMessageEntities() @@ -189,3 +217,124 @@ void DrawMessageEntities() me->DrawOverlays(); } } + +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class CMessageEntityLocalized : public CMessageEntity +{ + DECLARE_CLASS( CMessageEntityLocalized, CMessageEntity ); + +public: + bool KeyValue(const char *szKeyName, const char *szValue); + void SetMessage(const char *szValue); + void DrawOverlays(void); + void InputSetMessage( inputdata_t &inputdata ); + + CUtlVector m_messageLines; + + DECLARE_DATADESC(); +}; + +LINK_ENTITY_TO_CLASS( point_message_localized, CMessageEntityLocalized ); + +BEGIN_DATADESC( CMessageEntityLocalized ) + + DEFINE_UTLVECTOR( m_messageLines, FIELD_STRING ), + + //DEFINE_INPUTFUNC( FIELD_STRING, "SetMessage", InputSetMessage ), + +END_DATADESC() + +//----------------------------------------------------------------------------- +// Purpose: Handles key values from the BSP before spawn is called. +//----------------------------------------------------------------------------- +bool CMessageEntityLocalized::KeyValue(const char *szKeyName, const char *szValue) +{ + if (FStrEq(szKeyName, "message")) + { + SetMessage(szValue); + return true; + } + + return BaseClass::KeyValue(szKeyName, szValue); +} + +// I would use "\\n", but Hammer doesn't let you use back slashes. +#define CONVERSION_CHAR "/n" + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CMessageEntityLocalized::SetMessage(const char *szValue) +{ + // Find a localization token matching this string + wchar_t *pszMessage = g_pVGuiLocalize->Find(szValue); + + // If this is a localized string, convert it back to char. + // If it isn't, just copy it right into this. + char szBackToChar[256]; + if (pszMessage) + g_pVGuiLocalize->ConvertUnicodeToANSI(pszMessage, szBackToChar, sizeof(szBackToChar)); + else + Q_strncpy(szBackToChar, szValue, sizeof(szBackToChar)); + + // szTemp is used to turn \n from localized strings into /n. + char szTemp[256]; + if (Q_strstr(szBackToChar, "\n")) + { + char *token = strtok(szBackToChar, "\n"); + while (token) + { + Q_snprintf(szTemp, sizeof(szTemp), "%s%s%s", szTemp, token, CONVERSION_CHAR); + token = strtok(NULL, "\n"); + } + } + else + { + Q_strncpy(szTemp, szBackToChar, sizeof(szTemp)); + } + + m_messageLines.RemoveAll(); + + CUtlStringList vecLines; + Q_SplitString(szTemp, CONVERSION_CHAR, vecLines); + FOR_EACH_VEC( vecLines, i ) + { + m_messageLines.AddToTail( AllocPooledString(vecLines[i]) ); + } + + vecLines.PurgeAndDeleteElements(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CMessageEntityLocalized::InputSetMessage( inputdata_t &inputdata ) +{ + SetMessage(inputdata.value.String()); +} + +//------------------------------------------- +//------------------------------------------- +void CMessageEntityLocalized::DrawOverlays(void) +{ + if ( !m_drawText ) + return; + + if ( m_bDeveloperOnly && !g_pDeveloper->GetInt() ) + return; + + if ( !m_bEnabled ) + return; + + // display text if they are within range + int offset = 0; + FOR_EACH_VEC( m_messageLines, i ) + { + EntityText( offset, STRING(m_messageLines[i]), 0 ); + offset++; + } +} +#endif diff --git a/src/game/server/monstermaker.cpp b/src/game/server/monstermaker.cpp index 98f1e02f..5d56eabf 100644 --- a/src/game/server/monstermaker.cpp +++ b/src/game/server/monstermaker.cpp @@ -18,6 +18,8 @@ #include "IEffects.h" #include "props.h" +#include "point_template.h" + // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" @@ -114,6 +116,8 @@ END_DATADESC() //----------------------------------------------------------------------------- void CBaseNPCMaker::Spawn( void ) { + ScriptInstallPreSpawnHook(); + SetSolid( SOLID_NONE ); m_nLiveChildren = 0; Precache(); @@ -830,6 +834,12 @@ void CTemplateNPCMaker::MakeNPC( void ) pent->SetAbsAngles( angles ); } + if ( !ScriptPreInstanceSpawn( &m_ScriptScope, pEntity, m_iszTemplateData ) ) + { + UTIL_RemoveImmediate( pEntity ); + return; + } + m_OnSpawnNPC.Set( pEntity, pEntity, this ); if ( m_spawnflags & SF_NPCMAKER_FADE ) @@ -867,6 +877,8 @@ void CTemplateNPCMaker::MakeNPC( void ) SetUse( NULL ); } } + + ScriptPostSpawn( &m_ScriptScope, &pEntity, 1 ); } //----------------------------------------------------------------------------- diff --git a/src/game/server/movie_display.cpp b/src/game/server/movie_display.cpp new file mode 100644 index 00000000..b5a4476c --- /dev/null +++ b/src/game/server/movie_display.cpp @@ -0,0 +1,378 @@ +//========= Copyright © 1996-2009, Valve Corporation, All rights reserved. ============// +// +// Purpose: Allows movies to be played as a VGUI screen in the world +// +//=====================================================================================// + +#include "cbase.h" +#include "EnvMessage.h" +#include "fmtstr.h" +#include "vguiscreen.h" +#include "filesystem.h" + +// NOTE: This has to be the last file included! +#include "tier0/memdbgon.h" + +class CMovieDisplay : public CBaseEntity +{ +public: + + DECLARE_CLASS( CMovieDisplay, CBaseEntity ); + DECLARE_DATADESC(); + DECLARE_SERVERCLASS(); + + CMovieDisplay() { m_bMuted = true; } + + virtual ~CMovieDisplay(); + + virtual bool KeyValue( const char *szKeyName, const char *szValue ); + + virtual int UpdateTransmitState(); + virtual void SetTransmit( CCheckTransmitInfo *pInfo, bool bAlways ); + + virtual void Spawn( void ); + virtual void Precache( void ); + virtual void OnRestore( void ); + + void ScreenVisible( bool bVisible ); + + void Disable( void ); + void Enable( void ); + + void InputDisable( inputdata_t &inputdata ); + void InputEnable( inputdata_t &inputdata ); + + void InputSetDisplayText( inputdata_t &inputdata ); + +private: + + // Control panel + void GetControlPanelInfo( int nPanelIndex, const char *&pPanelName ); + void GetControlPanelClassName( int nPanelIndex, const char *&pPanelName ); + void SpawnControlPanels( void ); + void RestoreControlPanels( void ); + +private: + CNetworkVar( bool, m_bEnabled ); + CNetworkVar( bool, m_bLooping ); + CNetworkVar( bool, m_bMuted); + + CNetworkString( m_szDisplayText, 128 ); + + // Filename of the movie to play + CNetworkString( m_szMovieFilename, 128 ); + string_t m_strMovieFilename; + + // "Group" name. Screens of the same group name will play the same movie at the same time + // Effectively this lets multiple screens tune to the same "channel" in the world + CNetworkString( m_szGroupName, 128 ); + string_t m_strGroupName; + + int m_iScreenWidth; + int m_iScreenHeight; + + bool m_bDoFullTransmit; + + CHandle m_hScreen; +}; + +LINK_ENTITY_TO_CLASS( vgui_movie_display, CMovieDisplay ); + +//----------------------------------------------------------------------------- +// Save/load +//----------------------------------------------------------------------------- +BEGIN_DATADESC( CMovieDisplay ) + + DEFINE_FIELD( m_bEnabled, FIELD_BOOLEAN ), + + DEFINE_AUTO_ARRAY_KEYFIELD( m_szDisplayText, FIELD_CHARACTER, "displaytext" ), + + DEFINE_AUTO_ARRAY( m_szMovieFilename, FIELD_CHARACTER ), + DEFINE_KEYFIELD( m_strMovieFilename, FIELD_STRING, "moviefilename" ), + + DEFINE_AUTO_ARRAY( m_szGroupName, FIELD_CHARACTER ), + DEFINE_KEYFIELD( m_strGroupName, FIELD_STRING, "groupname" ), + + DEFINE_KEYFIELD( m_iScreenWidth, FIELD_INTEGER, "width" ), + DEFINE_KEYFIELD( m_iScreenHeight, FIELD_INTEGER, "height" ), + DEFINE_KEYFIELD( m_bLooping, FIELD_BOOLEAN, "looping" ), + DEFINE_KEYFIELD( m_bMuted, FIELD_BOOLEAN, "muted"), + + DEFINE_FIELD( m_bDoFullTransmit, FIELD_BOOLEAN ), + + DEFINE_FIELD( m_hScreen, FIELD_EHANDLE ), + + DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ), + DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ), + + DEFINE_INPUTFUNC( FIELD_STRING, "SetDisplayText", InputSetDisplayText ), + +END_DATADESC() + +IMPLEMENT_SERVERCLASS_ST( CMovieDisplay, DT_MovieDisplay ) + SendPropBool( SENDINFO( m_bEnabled ) ), + SendPropBool( SENDINFO( m_bLooping ) ), + SendPropBool( SENDINFO( m_bMuted ) ), + SendPropString( SENDINFO( m_szMovieFilename ) ), + SendPropString( SENDINFO( m_szGroupName ) ), +END_SEND_TABLE() + +CMovieDisplay::~CMovieDisplay() +{ + DestroyVGuiScreen( m_hScreen.Get() ); +} + +//----------------------------------------------------------------------------- +// Read in Hammer data +//----------------------------------------------------------------------------- + +bool CMovieDisplay::KeyValue( const char *szKeyName, const char *szValue ) +{ + // NOTE: Have to do these separate because they set two values instead of one + if( FStrEq( szKeyName, "angles" ) ) + { + Assert( GetMoveParent() == NULL ); + QAngle angles; + UTIL_StringToVector( angles.Base(), szValue ); + + // Because the vgui screen basis is strange (z is front, y is up, x is right) + // we need to rotate the typical basis before applying it + VMatrix mat, rotation, tmp; + MatrixFromAngles( angles, mat ); + MatrixBuildRotationAboutAxis( rotation, Vector( 0, 1, 0 ), 90 ); + MatrixMultiply( mat, rotation, tmp ); + MatrixBuildRotateZ( rotation, 90 ); + MatrixMultiply( tmp, rotation, mat ); + MatrixToAngles( mat, angles ); + SetAbsAngles( angles ); + + return true; + } + + return BaseClass::KeyValue( szKeyName, szValue ); +} + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +int CMovieDisplay::UpdateTransmitState() +{ + if ( m_bDoFullTransmit ) + { + m_bDoFullTransmit = false; + return SetTransmitState( FL_EDICT_ALWAYS ); + } + + return SetTransmitState( FL_EDICT_FULLCHECK ); +} + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CMovieDisplay::SetTransmit( CCheckTransmitInfo *pInfo, bool bAlways ) +{ + // Are we already marked for transmission? + if ( pInfo->m_pTransmitEdict->Get( entindex() ) ) + return; + + BaseClass::SetTransmit( pInfo, bAlways ); + + // Force our screen to be sent too. + m_hScreen->SetTransmit( pInfo, bAlways ); +} + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CMovieDisplay::Spawn( void ) +{ + // Move the strings into a networkable form + Q_strcpy( m_szMovieFilename.GetForModify(), m_strMovieFilename.ToCStr() ); + Q_strcpy( m_szGroupName.GetForModify(), m_strGroupName.ToCStr() ); + + Precache(); + + BaseClass::Spawn(); + + m_bEnabled = false; + + SpawnControlPanels(); + + ScreenVisible( m_bEnabled ); + + m_bDoFullTransmit = true; +} + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CMovieDisplay::Precache( void ) +{ + BaseClass::Precache(); + + PrecacheVGuiScreen( "video_display_screen" ); +} + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CMovieDisplay::OnRestore( void ) +{ + BaseClass::OnRestore(); + + RestoreControlPanels(); + + ScreenVisible( m_bEnabled ); +} + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CMovieDisplay::ScreenVisible( bool bVisible ) +{ + // Set its active state + m_hScreen->SetActive( bVisible ); + + if ( bVisible ) + { + m_hScreen->RemoveEffects( EF_NODRAW ); + } + else + { + m_hScreen->AddEffects( EF_NODRAW ); + } +} + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CMovieDisplay::Disable( void ) +{ + if ( !m_bEnabled ) + return; + + m_bEnabled = false; + + ScreenVisible( false ); +} + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CMovieDisplay::Enable( void ) +{ + if ( m_bEnabled ) + return; + + m_bEnabled = true; + + ScreenVisible( true ); +} + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CMovieDisplay::InputDisable( inputdata_t &inputdata ) +{ + Disable(); +} + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CMovieDisplay::InputEnable( inputdata_t &inputdata ) +{ + Enable(); +} + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CMovieDisplay::InputSetDisplayText( inputdata_t &inputdata ) +{ + Q_strcpy( m_szDisplayText.GetForModify(), inputdata.value.String() ); +} + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CMovieDisplay::GetControlPanelInfo( int nPanelIndex, const char *&pPanelName ) +{ + pPanelName = "movie_display_screen"; +} + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CMovieDisplay::GetControlPanelClassName( int nPanelIndex, const char *&pPanelName ) +{ + pPanelName = "vgui_screen"; +} + +//----------------------------------------------------------------------------- +// This is called by the base object when it's time to spawn the control panels +//----------------------------------------------------------------------------- +void CMovieDisplay::SpawnControlPanels() +{ + int nPanel; + for ( nPanel = 0; true; ++nPanel ) + { + const char *pScreenName; + GetControlPanelInfo( nPanel, pScreenName ); + if (!pScreenName) + continue; + + const char *pScreenClassname; + GetControlPanelClassName( nPanel, pScreenClassname ); + if ( !pScreenClassname ) + continue; + + float flWidth = m_iScreenWidth; + float flHeight = m_iScreenHeight; + + CVGuiScreen *pScreen = CreateVGuiScreen( pScreenClassname, pScreenName, this, this, 0 ); + pScreen->ChangeTeam( GetTeamNumber() ); + pScreen->SetActualSize( flWidth, flHeight ); + pScreen->SetActive( true ); + pScreen->MakeVisibleOnlyToTeammates( false ); + pScreen->SetTransparency( true ); + m_hScreen = pScreen; + + return; + } +} + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CMovieDisplay::RestoreControlPanels( void ) +{ + int nPanel; + for ( nPanel = 0; true; ++nPanel ) + { + const char *pScreenName; + GetControlPanelInfo( nPanel, pScreenName ); + if (!pScreenName) + continue; + + const char *pScreenClassname; + GetControlPanelClassName( nPanel, pScreenClassname ); + if ( !pScreenClassname ) + continue; + + CVGuiScreen *pScreen = (CVGuiScreen *)gEntList.FindEntityByClassname( NULL, pScreenClassname ); + + while ( ( pScreen && pScreen->GetOwnerEntity() != this ) || Q_strcmp( pScreen->GetPanelName(), pScreenName ) != 0 ) + { + pScreen = (CVGuiScreen *)gEntList.FindEntityByClassname( pScreen, pScreenClassname ); + } + + if ( pScreen ) + { + m_hScreen = pScreen; + m_hScreen->SetActive( true ); + } + + return; + } +} diff --git a/src/game/server/nav_merge.cpp b/src/game/server/nav_merge.cpp index d62764ab..1d5330ca 100644 --- a/src/game/server/nav_merge.cpp +++ b/src/game/server/nav_merge.cpp @@ -303,7 +303,7 @@ void CNavMesh::CommandNavMergeMesh( const CCommand &args ) //-------------------------------------------------------------------------------------------------------- int NavMeshMergeAutocomplete( char const *partial, char commands[ COMMAND_COMPLETION_MAXITEMS ][ COMMAND_COMPLETION_ITEM_LENGTH ] ) { - char *commandName = "nav_merge_mesh"; + const char *commandName = "nav_merge_mesh"; int numMatches = 0; partial += Q_strlen( commandName ) + 1; int partialLength = Q_strlen( partial ); diff --git a/src/game/server/netpropmanager.cpp b/src/game/server/netpropmanager.cpp index 103f2f3d..69b976d8 100644 --- a/src/game/server/netpropmanager.cpp +++ b/src/game/server/netpropmanager.cpp @@ -16,6 +16,7 @@ // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" +#ifndef MAPBASE_VSCRIPT extern void SendProxy_StringT_To_String( const SendProp *pProp, const void *pStruct, const void *pVarData, DVariant *pOut, int iElement, int objectID ); extern ISaveRestoreOps* ActivityDataOps(); @@ -1388,4 +1389,5 @@ void CNetPropManager::GetTable( HSCRIPT hEnt, int iPropType, HSCRIPT hTable ) datamap_t *pDataMap = pBaseEntity->GetDataDescMap(); CollectNestedDataMaps( pDataMap, pBaseEntity, 0, hTable ); } -} \ No newline at end of file +} +#endif diff --git a/src/game/server/netpropmanager.h b/src/game/server/netpropmanager.h index 312ccba2..cf9843e3 100644 --- a/src/game/server/netpropmanager.h +++ b/src/game/server/netpropmanager.h @@ -13,6 +13,7 @@ #pragma once #endif +#ifndef MAPBASE_VSCRIPT #include "dt_send.h" #include "datamap.h" @@ -184,6 +185,7 @@ public: // Fills in a passed table with property info for the provided entity bool GetPropInfo( HSCRIPT hEnt, const char *pstrProperty, int element, HSCRIPT hTable ); }; +#endif #endif // NETPROPMANAGER_H diff --git a/src/game/server/npc_talker.cpp b/src/game/server/npc_talker.cpp index a04c9ff3..ebb94583 100644 --- a/src/game/server/npc_talker.cpp +++ b/src/game/server/npc_talker.cpp @@ -373,7 +373,11 @@ void CNPCSimpleTalker::AlertFriends( CBaseEntity *pKiller ) } else { +#ifdef MAPBASE + if( IRelationType(pKiller) <= D_FR) +#else if( IRelationType(pKiller) == D_HT) +#endif { // Killed by an enemy!!! CNPCSimpleTalker *pAlly = (CNPCSimpleTalker *)pNPC; diff --git a/src/game/server/npc_vehicledriver.cpp b/src/game/server/npc_vehicledriver.cpp index 45da78dd..8e38d769 100644 --- a/src/game/server/npc_vehicledriver.cpp +++ b/src/game/server/npc_vehicledriver.cpp @@ -721,7 +721,13 @@ bool CNPC_VehicleDriver::OverridePathMove( float flInterval ) // Have we reached our target? See if we've passed the current waypoint's plane. Vector vecAbsMins, vecAbsMaxs; +#ifdef MAPBASE + vecAbsMins = m_hVehicleEntity->CollisionProp()->OBBMins(); + vecAbsMaxs = m_hVehicleEntity->CollisionProp()->OBBMaxs(); + m_hVehicleEntity->CollisionProp()->WorldSpaceAABB( &vecAbsMins, &vecAbsMaxs ); +#else CollisionProp()->WorldSpaceAABB( &vecAbsMins, &vecAbsMaxs ); +#endif if ( BoxOnPlaneSide( vecAbsMins, vecAbsMaxs, &m_pCurrentWaypoint->planeWaypoint ) == 3 ) { if ( WaypointReached() ) diff --git a/src/game/server/particle_system.cpp b/src/game/server/particle_system.cpp index 33fcd322..23e98eaf 100644 --- a/src/game/server/particle_system.cpp +++ b/src/game/server/particle_system.cpp @@ -25,9 +25,13 @@ IMPLEMENT_SERVERCLASS_ST_NOBASE(CParticleSystem, DT_ParticleSystem) SendPropInt( SENDINFO(m_iEffectIndex), MAX_PARTICLESYSTEMS_STRING_BITS, SPROP_UNSIGNED ), SendPropBool( SENDINFO(m_bActive) ), +#ifdef MAPBASE + SendPropBool( SENDINFO(m_bDestroyImmediately) ), +#endif SendPropFloat( SENDINFO(m_flStartTime) ), SendPropArray3( SENDINFO_ARRAY3(m_hControlPointEnts), SendPropEHandle( SENDINFO_ARRAY(m_hControlPointEnts) ) ), + SendPropArray3( SENDINFO_ARRAY3(m_vControlPointVecs), SendPropVector( SENDINFO_ARRAY(m_vControlPointVecs) ) ), SendPropArray3( SENDINFO_ARRAY3(m_iControlPointParents), SendPropInt( SENDINFO_ARRAY(m_iControlPointParents), 3, SPROP_UNSIGNED ) ), SendPropBool( SENDINFO(m_bWeatherEffect) ), END_SEND_TABLE() @@ -36,6 +40,9 @@ BEGIN_DATADESC( CParticleSystem ) DEFINE_KEYFIELD( m_bStartActive, FIELD_BOOLEAN, "start_active" ), DEFINE_KEYFIELD( m_bWeatherEffect, FIELD_BOOLEAN, "flag_as_weather" ), DEFINE_FIELD( m_bActive, FIELD_BOOLEAN ), +#ifdef MAPBASE + DEFINE_FIELD( m_bDestroyImmediately, FIELD_BOOLEAN ), +#endif DEFINE_FIELD( m_flStartTime, FIELD_TIME ), DEFINE_KEYFIELD( m_iszEffectName, FIELD_STRING, "effect_name" ), //DEFINE_FIELD( m_iEffectIndex, FIELD_INTEGER ), // Don't save. Refind after loading. @@ -116,12 +123,16 @@ BEGIN_DATADESC( CParticleSystem ) DEFINE_INPUTFUNC( FIELD_VOID, "Start", InputStart ), DEFINE_INPUTFUNC( FIELD_VOID, "Stop", InputStop ), +#ifdef MAPBASE + DEFINE_INPUTFUNC( FIELD_VOID, "DestroyImmediately", InputDestroyImmediately ), +#endif DEFINE_THINKFUNC( StartParticleSystemThink ), END_DATADESC() LINK_ENTITY_TO_CLASS( info_particle_system, CParticleSystem ); +LINK_ENTITY_TO_CLASS( info_particle_system_coordinate, CParticleSystemCoordinate ); //----------------------------------------------------------------------------- // Purpose: @@ -199,6 +210,9 @@ void CParticleSystem::StartParticleSystem( void ) { m_flStartTime = gpGlobals->curtime; m_bActive = true; +#ifdef MAPBASE + m_bDestroyImmediately = false; +#endif // Setup our control points at this time (in case our targets weren't around at spawn time) ReadControlPointEnts(); @@ -229,6 +243,17 @@ void CParticleSystem::InputStop( inputdata_t &inputdata ) StopParticleSystem(); } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CParticleSystem::InputDestroyImmediately( inputdata_t &inputdata ) +{ + m_bDestroyImmediately = true; + StopParticleSystem(); +} +#endif + //----------------------------------------------------------------------------- // Purpose: Find each entity referred to by m_iszControlPointNames and // resolve it into the corresponding slot in m_hControlPointEnts @@ -237,17 +262,28 @@ void CParticleSystem::ReadControlPointEnts( void ) { for ( int i = 0 ; i < kMAXCONTROLPOINTS; ++i ) { - if ( m_iszControlPointNames[i] == NULL_STRING ) - continue; - - CBaseEntity *pPointEnt = gEntList.FindEntityGeneric( NULL, STRING( m_iszControlPointNames[i] ), this ); - Assert( pPointEnt != NULL ); - if ( pPointEnt == NULL ) + if (UsesCoordinates()) { - Warning("Particle system %s could not find control point entity (%s)\n", GetEntityName().ToCStr(), m_iszControlPointNames[i].ToCStr() ); - continue; + Vector vecCoords; + // cast str to vector, add vector to array + const char* pszVector = STRING(m_iszControlPointNames[i]); + UTIL_StringToVector(vecCoords.Base(), pszVector); + m_vControlPointVecs.Set(i, vecCoords); } + else + { + if ( m_iszControlPointNames[i] == NULL_STRING ) + continue; - m_hControlPointEnts.Set( i, pPointEnt ); + CBaseEntity *pPointEnt = gEntList.FindEntityGeneric( NULL, STRING( m_iszControlPointNames[i] ), this ); + Assert( pPointEnt != NULL ); + if ( pPointEnt == NULL ) + { + Warning("Particle system %s could not find control point entity (%s)\n", GetEntityName().ToCStr(), m_iszControlPointNames[i].ToCStr() ); + continue; + } + + m_hControlPointEnts.Set( i, pPointEnt ); + } } } diff --git a/src/game/server/particle_system.h b/src/game/server/particle_system.h index ecf758c2..42e213f5 100644 --- a/src/game/server/particle_system.h +++ b/src/game/server/particle_system.h @@ -34,10 +34,15 @@ public: void InputStart( inputdata_t &inputdata ); void InputStop( inputdata_t &inputdata ); +#ifdef MAPBASE + void InputDestroyImmediately( inputdata_t &inputdata ); +#endif void StartParticleSystemThink( void ); enum { kMAXCONTROLPOINTS = 63 }; ///< actually one less than the total number of cpoints since 0 is assumed to be me + virtual bool UsesCoordinates( void ) { return false; } + protected: /// Load up and resolve the entities that are supposed to be the control points @@ -47,13 +52,27 @@ protected: string_t m_iszEffectName; CNetworkVar( bool, m_bActive ); +#ifdef MAPBASE + CNetworkVar( bool, m_bDestroyImmediately ); +#endif CNetworkVar( int, m_iEffectIndex ) CNetworkVar( float, m_flStartTime ); // Time at which this effect was started. This is used after restoring an active effect. string_t m_iszControlPointNames[kMAXCONTROLPOINTS]; CNetworkArray( EHANDLE, m_hControlPointEnts, kMAXCONTROLPOINTS ); + CNetworkArray( Vector, m_vControlPointVecs, kMAXCONTROLPOINTS ); CNetworkArray( unsigned char, m_iControlPointParents, kMAXCONTROLPOINTS ); CNetworkVar( bool, m_bWeatherEffect ); }; +//----------------------------------------------------------------------------- +// Purpose: An entity that spawns and controls a particle system using coordinates. +//----------------------------------------------------------------------------- +class CParticleSystemCoordinate : public CParticleSystem +{ + DECLARE_CLASS( CParticleSystemCoordinate, CParticleSystem ); +public: + virtual bool UsesCoordinates( void ) { return true; } +}; + #endif // PARTICLE_SYSTEM_H diff --git a/src/game/server/physconstraint.cpp b/src/game/server/physconstraint.cpp index 8fa25cdd..467c7cfb 100644 --- a/src/game/server/physconstraint.cpp +++ b/src/game/server/physconstraint.cpp @@ -569,6 +569,8 @@ void CPhysConstraint::GetConstraintObjects( hl_constraint_info_t &info ) // Missing one object, assume the world instead if ( info.pObjects[0] == NULL && info.pObjects[1] ) { + // This brokens hanging lamps in hl2mp +#if !defined ( HL2MP ) if ( Q_strlen(STRING(m_nameAttach1)) ) { Warning("Bogus constraint %s (attaches ENTITY NOT FOUND:%s to %s)\n", GetDebugName(), STRING(m_nameAttach1), STRING(m_nameAttach2)); @@ -577,11 +579,15 @@ void CPhysConstraint::GetConstraintObjects( hl_constraint_info_t &info ) return; #endif // HL2_EPISODIC } +#endif info.pObjects[0] = g_PhysWorldObject; info.massScale[0] = info.massScale[1] = 1.0f; // no mass scale on world constraint + } else if ( info.pObjects[0] && !info.pObjects[1] ) { + // This brokens hanging lamps in hl2mp +#if !defined ( HL2MP ) if ( Q_strlen(STRING(m_nameAttach2)) ) { Warning("Bogus constraint %s (attaches %s to ENTITY NOT FOUND:%s)\n", GetDebugName(), STRING(m_nameAttach1), STRING(m_nameAttach2)); @@ -590,6 +596,7 @@ void CPhysConstraint::GetConstraintObjects( hl_constraint_info_t &info ) return; #endif // HL2_EPISODIC } +#endif info.pObjects[1] = info.pObjects[0]; info.pObjects[0] = g_PhysWorldObject; // Try to make the world object consistently object0 for ease of implementation info.massScale[0] = info.massScale[1] = 1.0f; // no mass scale on world constraint @@ -1038,6 +1045,16 @@ public: for ( int i = 0; i < 2; i++ ) { info.pObjects[i]->WorldToLocal( &ballsocket.constraintPosition[i], GetAbsOrigin() ); + // HACKHACK - the mapper forgot to put in some sane physics damping + float damping, adamping; + info.pObjects[i]->GetDamping(&damping, &adamping); + if (damping < .2f) { + damping = .2f; + } + if (adamping < .2f) { + adamping = .2f; + } + info.pObjects[i]->SetDamping(&damping, &adamping); } GetBreakParams( ballsocket.constraint, info ); ballsocket.constraint.torqueLimit = 0; diff --git a/src/game/server/physics.cpp b/src/game/server/physics.cpp index 279fd507..6fe206d5 100644 --- a/src/game/server/physics.cpp +++ b/src/game/server/physics.cpp @@ -514,7 +514,12 @@ int CCollisionEvent::ShouldCollide_2( IPhysicsObject *pObj0, IPhysicsObject *pOb if ( pEntity0->edict() && pEntity1->edict() ) { // don't collide with your owner +#ifdef MAPBASE + if ( (pEntity0->GetOwnerEntity() == pEntity1 && !pEntity0->IsSolidFlagSet(FSOLID_COLLIDE_WITH_OWNER)) + || (pEntity1->GetOwnerEntity() == pEntity0 && !pEntity1->IsSolidFlagSet(FSOLID_COLLIDE_WITH_OWNER)) ) +#else if ( pEntity0->GetOwnerEntity() == pEntity1 || pEntity1->GetOwnerEntity() == pEntity0 ) +#endif return 0; } @@ -719,7 +724,7 @@ bool CCollisionEvent::ShouldFreezeContacts( IPhysicsObject **pObjectList, int ob { if ( m_lastTickFrictionError > gpGlobals->tickcount || m_lastTickFrictionError < (gpGlobals->tickcount-1) ) { - DevWarning("Performance Warning: large friction system (%d objects)!!!\n", objectCount ); + CGWarning( 1, CON_GROUP_PHYSICS, "Performance Warning: large friction system (%d objects)!!!\n", objectCount ); #if _DEBUG for ( int i = 0; i < objectCount; i++ ) { @@ -992,7 +997,7 @@ int CCollisionEvent::ShouldSolvePenetration( IPhysicsObject *pObj0, IPhysicsObje { if ( pObj0->GetGameFlags() & FVPHYSICS_PART_OF_RAGDOLL ) { - DevMsg(2, "Solving ragdoll self penetration! %s (%s) (%d v %d)\n", pObj0->GetName(), pEntity0->GetDebugName(), pObj0->GetGameIndex(), pObj1->GetGameIndex() ); + CGMsg( 2, CON_GROUP_PHYSICS, "Solving ragdoll self penetration! %s (%s) (%d v %d)\n", pObj0->GetName(), pEntity0->GetDebugName(), pObj0->GetGameIndex(), pObj1->GetGameIndex() ); ragdoll_t *pRagdoll = Ragdoll_GetRagdoll( pEntity0 ); pRagdoll->pGroup->SolvePenetration( pObj0, pObj1 ); return false; @@ -1025,11 +1030,11 @@ int CCollisionEvent::ShouldSolvePenetration( IPhysicsObject *pObj0, IPhysicsObje { int index0 = physcollision->CollideIndex( pObj0->GetCollide() ); int index1 = physcollision->CollideIndex( pObj1->GetCollide() ); - DevMsg(1, "***Inter-penetration on %s (%d & %d) (%.0f, %.0f)\n", pName1?pName1:"(null)", index0, index1, gpGlobals->curtime, eventTime ); + CGMsg( 1, CON_GROUP_PHYSICS, "***Inter-penetration on %s (%d & %d) (%.0f, %.0f)\n", pName1?pName1:"(null)", index0, index1, gpGlobals->curtime, eventTime ); } else { - DevMsg(1, "***Inter-penetration between %s(%s) AND %s(%s) (%.0f, %.0f)\n", pName1?pName1:"(null)", pEntity0->GetDebugName(), pName2?pName2:"(null)", pEntity1->GetDebugName(), gpGlobals->curtime, eventTime ); + CGMsg( 1, CON_GROUP_PHYSICS, "***Inter-penetration between %s(%s) AND %s(%s) (%.0f, %.0f)\n", pName1?pName1:"(null)", pEntity0->GetDebugName(), pName2?pName2:"(null)", pEntity1->GetDebugName(), gpGlobals->curtime, eventTime ); } } #endif @@ -1328,8 +1333,8 @@ CON_COMMAND_F(surfaceprop, "Reports the surface properties at the cursor", FCVAR Vector vecVelocity = tr.startpos - tr.endpos; int length = vecVelocity.Length(); - Msg("Hit surface \"%s\" (entity %s, model \"%s\" %s), texture \"%s\"\n", physprops->GetPropName( tr.surface.surfaceProps ), tr.m_pEnt->GetClassname(), pModelName, modelStuff.Access(), tr.surface.name); - Msg("Distance to surface: %d\n", length ); + CGMsg( 0, CON_GROUP_PHYSICS, "Hit surface \"%s\" (entity %s, model \"%s\" %s), texture \"%s\"\n", physprops->GetPropName( tr.surface.surfaceProps ), tr.m_pEnt->GetClassname(), pModelName, modelStuff.Access(), tr.surface.name ); + CGMsg( 0, CON_GROUP_PHYSICS, "Distance to surface: %d\n", length ); } } @@ -1337,12 +1342,12 @@ static void OutputVPhysicsDebugInfo( CBaseEntity *pEntity ) { if ( pEntity ) { - Msg("Entity %s (%s) %s Collision Group %d\n", pEntity->GetClassname(), pEntity->GetDebugName(), pEntity->IsNavIgnored() ? "NAV IGNORE" : "", pEntity->GetCollisionGroup() ); + CGMsg( 0, CON_GROUP_PHYSICS, "Entity %s (%s) %s Collision Group %d\n", pEntity->GetClassname(), pEntity->GetDebugName(), pEntity->IsNavIgnored() ? "NAV IGNORE" : "", pEntity->GetCollisionGroup() ); CUtlVector list; g_Collisions.GetListOfPenetratingEntities( pEntity, list ); for ( int i = 0; i < list.Count(); i++ ) { - Msg(" penetration with entity %s (%s)\n", list[i]->GetDebugName(), STRING(list[i]->GetModelName()) ); + CGMsg( 0, CON_GROUP_PHYSICS, " penetration with entity %s (%s)\n", list[i]->GetDebugName(), STRING( list[i]->GetModelName() ) ); } IPhysicsObject *pList[VPHYSICS_MAX_OBJECT_LIST_COUNT]; @@ -1353,7 +1358,7 @@ static void OutputVPhysicsDebugInfo( CBaseEntity *pEntity ) { for ( int i = 0; i < physCount; i++ ) { - Msg("Object %d (of %d) =========================\n", i+1, physCount ); + CGMsg( 0, CON_GROUP_PHYSICS, "Object %d (of %d) =========================\n", i + 1, physCount ); pList[i]->OutputDebugInfo(); } } @@ -1491,7 +1496,7 @@ static void DebugConstraints( CBaseEntity *pEntity ) pModel1 = STRING(pAttach[1]->GetModelName()); index1 = pAttachVPhysics[1]->GetGameIndex(); } - Msg("**********************\n%s connects %s(%s:%d) to %s(%s:%d)\n", constraints[i]->GetClassname(), pName0, pModel0, index0, pName1, pModel1, index1 ); + CGMsg( 0, CON_GROUP_PHYSICS, "**********************\n%s connects %s(%s:%d) to %s(%s:%d)\n", constraints[i]->GetClassname(), pName0, pModel0, index0, pName1, pModel1, index1 ); DebugConstraint(constraints[i]); constraints[i]->m_debugOverlays |= OVERLAY_BBOX_BIT | OVERLAY_TEXT_BIT; } @@ -1637,7 +1642,7 @@ CON_COMMAND( physics_budget, "Times the cost of each active object" ) for ( i = 0; i < ents.Count(); i++ ) { float fraction = times[i] / totalTime; - Msg( "%s (%s): %.3fms (%.3f%%) @ %s\n", ents[i]->GetClassname(), ents[i]->GetDebugName(), fraction * totalTime * 1000.0f, fraction * 100.0f, VecToString(ents[i]->GetAbsOrigin()) ); + CGMsg( 0, CON_GROUP_PHYSICS, "%s (%s): %.3fms (%.3f%%) @ %s\n", ents[i]->GetClassname(), ents[i]->GetDebugName(), fraction * totalTime * 1000.0f, fraction * 100.0f, VecToString( ents[i]->GetAbsOrigin() ) ); } g_Collisions.BufferTouchEvents( false ); } @@ -1680,7 +1685,7 @@ void PhysFrame( float deltaTime ) if ( deltaTime > 1.0f || deltaTime < 0.0f ) { deltaTime = 0; - Msg( "Reset physics clock\n" ); + CGMsg( 0, CON_GROUP_PHYSICS, "Reset physics clock\n" ); } else if ( deltaTime > 0.1f ) // limit incoming time to 100ms { @@ -1738,7 +1743,7 @@ void PhysFrame( float deltaTime ) CBaseEntity *pEntity = pItem->hEnt.Get(); if ( !pEntity ) { - Msg( "Dangling pointer to physics entity!!!\n" ); + CGMsg( 0, CON_GROUP_PHYSICS, "Dangling pointer to physics entity!!!\n" ); continue; } @@ -1760,7 +1765,7 @@ void PhysFrame( float deltaTime ) g_PhysAverageSimTime += (simRealTime * 0.2); if ( lastObjectCount != 0 || activeCount != 0 ) { - Msg( "Physics: %3d objects, %4.1fms / AVG: %4.1fms\n", activeCount, simRealTime * 1000, g_PhysAverageSimTime * 1000 ); + CGMsg( 0, CON_GROUP_PHYSICS, "Physics: %3d objects, %4.1fms / AVG: %4.1fms\n", activeCount, simRealTime * 1000, g_PhysAverageSimTime * 1000 ); } lastObjectCount = activeCount; @@ -1924,7 +1929,7 @@ void PhysForceEntityToSleep( CBaseEntity *pEntity, IPhysicsObject *pObject ) if ( !pObject || !pObject->IsMoveable() ) return; - DevMsg(2, "Putting entity to sleep: %s\n", pEntity->GetClassname() ); + CGMsg( 2, CON_GROUP_PHYSICS, "Putting entity to sleep: %s\n", pEntity->GetClassname() ); MEM_ALLOC_CREDIT(); IPhysicsObject *pList[VPHYSICS_MAX_OBJECT_LIST_COUNT]; int physCount = pEntity->VPhysicsGetObjectList( pList, ARRAYSIZE(pList) ); @@ -2019,7 +2024,7 @@ void CCollisionEvent::FlushQueuedOperations() // testing, if this assert fires it proves we've fixed the crash // after that the assert + warning can safely be removed Assert(0); - Warning("Physics queue not empty, error!\n"); + CGWarning( 0, CON_GROUP_PHYSICS, "Physics queue not empty, error!\n" ); loopCount++; UpdateTouchEvents(); UpdateDamageEvents(); @@ -2768,7 +2773,7 @@ void PhysCallbackDamage( CBaseEntity *pEntity, const CTakeDamageInfo &info ) g_Collisions.AddDamageEvent( pEntity, info, pInflictorPhysics, false, vec3_origin, vec3_origin ); if ( pEntity && info.GetInflictor() ) { - DevMsg( 2, "Warning: Physics damage event with no recovery info!\nObjects: %s, %s\n", pEntity->GetClassname(), info.GetInflictor()->GetClassname() ); + CGMsg( 2, CON_GROUP_PHYSICS, "Warning: Physics damage event with no recovery info!\nObjects: %s, %s\n", pEntity->GetClassname(), info.GetInflictor()->GetClassname() ); } } else @@ -2827,10 +2832,10 @@ IPhysicsObject *FindPhysicsObjectByName( const char *pName, CBaseEntity *pErrorE { const char *pErrorName = pErrorEntity ? pErrorEntity->GetClassname() : "Unknown"; Vector origin = pErrorEntity ? pErrorEntity->GetAbsOrigin() : vec3_origin; - DevWarning("entity %s at %s has physics attachment to more than one entity with the name %s!!!\n", pErrorName, VecToString(origin), pName ); + CGWarning( 1, CON_GROUP_PHYSICS, "entity %s at %s has physics attachment to more than one entity with the name %s!!!\n", pErrorName, VecToString( origin ), pName ); while ( ( pEntity = gEntList.FindEntityByName( pEntity, pName ) ) != NULL ) { - DevWarning("Found %s\n", pEntity->GetClassname() ); + CGWarning( 1, CON_GROUP_PHYSICS, "Found %s\n", pEntity->GetClassname() ); } break; @@ -2848,7 +2853,7 @@ void CC_AirDensity( const CCommand &args ) if ( args.ArgC() < 2 ) { - Msg( "air_density \nCurrent air density is %.2f\n", physenv->GetAirDensity() ); + CGMsg( 0, CON_GROUP_PHYSICS, "air_density \nCurrent air density is %.2f\n", physenv->GetAirDensity() ); } else { diff --git a/src/game/server/physics_impact_damage.cpp b/src/game/server/physics_impact_damage.cpp index 164568e2..312b8a1e 100644 --- a/src/game/server/physics_impact_damage.cpp +++ b/src/game/server/physics_impact_damage.cpp @@ -335,15 +335,32 @@ float CalculatePhysicsImpactDamage( int index, gamevcollisionevent_t *pEvent, co if ( pEvent->pObjects[otherIndex]->GetGameFlags() & FVPHYSICS_PLAYER_HELD ) { - if ( gpGlobals->maxClients == 1 ) + // if the player is holding the object, use its real mass (player holding reduced the mass) + CBasePlayer *pPlayer = NULL; + + if ( 1 == gpGlobals->maxClients ) { - // if the player is holding the object, use it's real mass (player holding reduced the mass) - CBasePlayer *pPlayer = UTIL_GetLocalPlayer(); - if ( pPlayer ) + pPlayer = UTIL_GetLocalPlayer(); + } + else + { + // See which MP player is holding the physics object and then use that player to get the real mass of the object. + // This is ugly but better than having linkage between an object and its "holding" player. + for ( int i = 1; i <= gpGlobals->maxClients; i++ ) { - otherMass = pPlayer->GetHeldObjectMass( pEvent->pObjects[otherIndex] ); + CBasePlayer *tempPlayer = UTIL_PlayerByIndex( i ); + if ( tempPlayer && pEvent->pEntities[index] == tempPlayer->GetHeldObject() ) + { + pPlayer = tempPlayer; + break; + } } } + + if ( pPlayer ) + { + otherMass = pPlayer->GetHeldObjectMass( pEvent->pObjects[otherIndex] ); + } } // NOTE: sum the mass of each object in this system for the purpose of damage @@ -438,16 +455,23 @@ float CalculatePhysicsImpactDamage( int index, gamevcollisionevent_t *pEvent, co } else if ( pEvent->pObjects[index]->GetGameFlags() & FVPHYSICS_PLAYER_HELD ) { - if ( gpGlobals->maxClients == 1 ) + // if the player is holding the object, use it's real mass (player holding reduced the mass) + CBasePlayer *pPlayer = NULL; + if ( 1 == gpGlobals->maxClients ) { - // if the player is holding the object, use it's real mass (player holding reduced the mass) - CBasePlayer *pPlayer = UTIL_GetLocalPlayer(); - if ( pPlayer ) + pPlayer = UTIL_GetLocalPlayer(); + } + else + { + // See which MP player is holding the physics object and then use that player to get the real mass of the object. + // This is ugly but better than having linkage between an object and its "holding" player. + for ( int i = 1; i <= gpGlobals->maxClients; i++ ) { - float mass = pPlayer->GetHeldObjectMass( pEvent->pObjects[index] ); - if ( mass > 0 ) + CBasePlayer *tempPlayer = UTIL_PlayerByIndex( i ); + if ( tempPlayer && pEvent->pEntities[index] == tempPlayer->GetHeldObject() ) { - invMass = 1.0f / mass; + pPlayer = tempPlayer; + break; } } } diff --git a/src/game/server/physics_main.cpp b/src/game/server/physics_main.cpp index 7a2eaff9..777f8b2f 100644 --- a/src/game/server/physics_main.cpp +++ b/src/game/server/physics_main.cpp @@ -949,8 +949,8 @@ void CBaseEntity::PhysicsDispatchThink( BASEPTR thinkFunc ) if ( thinkLimit ) { // calculate running time of the AI in milliseconds - float time = ( engine->Time() - startTime ) * 1000.0f; - if ( time > thinkLimit ) + float flTime = ( engine->Time() - startTime ) * 1000.0f; + if ( flTime > thinkLimit ) { #if defined( _XBOX ) && !defined( _RETAIL ) if ( vprof_think_limit.GetBool() ) @@ -963,14 +963,14 @@ void CBaseEntity::PhysicsDispatchThink( BASEPTR thinkFunc ) CAI_BaseNPC *pNPC = MyNPCPointer(); if (pNPC && pNPC->GetCurSchedule()) { - pNPC->ReportOverThinkLimit( time ); + pNPC->ReportOverThinkLimit( flTime ); } else { #ifdef _WIN32 - Msg( "%s(%s) thinking for %.02f ms!!!\n", GetClassname(), typeid(this).raw_name(), time ); + Msg( "%s(%s) thinking for %.02f ms!!!\n", GetClassname(), typeid(this).raw_name(), flTime ); #elif POSIX - Msg( "%s(%s) thinking for %.02f ms!!!\n", GetClassname(), typeid(this).name(), time ); + Msg( "%s(%s) thinking for %.02f ms!!!\n", GetClassname(), typeid(this).name(), flTime ); #else #error "typeinfo" #endif diff --git a/src/game/server/physics_prop_ragdoll.cpp b/src/game/server/physics_prop_ragdoll.cpp index 72635ea8..1fb43ffc 100644 --- a/src/game/server/physics_prop_ragdoll.cpp +++ b/src/game/server/physics_prop_ragdoll.cpp @@ -20,10 +20,20 @@ #include "AI_Criteria.h" #include "ragdoll_shared.h" #include "hierarchy.h" +#ifdef MAPBASE +#include "decals.h" +#endif // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" +#ifdef MAPBASE +ConVar ragdoll_autointeractions("ragdoll_autointeractions", "1", FCVAR_NONE, "Controls whether we should rely on hardcoded keyvalues or automatic flesh checks for ragdoll physgun interactions."); +#define IsBody() VPhysicsIsFlesh() + +ConVar ragdoll_always_allow_use( "ragdoll_always_allow_use", "0", FCVAR_NONE, "Allows all ragdolls to be used and, if they aren't explicitly set to prevent pickup, picked up." ); +#endif + //----------------------------------------------------------------------------- // Forward declarations //----------------------------------------------------------------------------- @@ -48,6 +58,11 @@ const float ATTACHED_DAMPING_SCALE = 50.0f; #define SF_RAGDOLLPROP_MOTIONDISABLED 0x4000 #define SF_RAGDOLLPROP_ALLOW_STRETCH 0x8000 #define SF_RAGDOLLPROP_STARTASLEEP 0x10000 +#ifdef MAPBASE +#define SF_RAGDOLLPROP_FIXED_CONSTRAINTS 0x20000 +#define SF_RAGDOLLPROP_ALLOW_USE 0x40000 +#define SF_RAGDOLLPROP_PREVENT_PICKUP 0x80000 +#endif //----------------------------------------------------------------------------- // Networking @@ -83,9 +98,19 @@ BEGIN_DATADESC(CRagdollProp) DEFINE_KEYFIELD( m_bStartDisabled, FIELD_BOOLEAN, "StartDisabled" ), +#ifdef MAPBASE + DEFINE_INPUTFUNC( FIELD_FLOAT, "StartRagdollBoogie", InputStartRadgollBoogie ), +#else DEFINE_INPUTFUNC( FIELD_VOID, "StartRagdollBoogie", InputStartRadgollBoogie ), +#endif DEFINE_INPUTFUNC( FIELD_VOID, "EnableMotion", InputEnableMotion ), DEFINE_INPUTFUNC( FIELD_VOID, "DisableMotion", InputDisableMotion ), +#ifdef MAPBASE + DEFINE_INPUTFUNC( FIELD_VOID, "Wake", InputWake ), + DEFINE_INPUTFUNC( FIELD_VOID, "Sleep", InputSleep ), + DEFINE_INPUTFUNC( FIELD_VOID, "AddToLRU", InputAddToLRU ), + DEFINE_INPUTFUNC( FIELD_VOID, "RemoveFromLRU", InputRemoveFromLRU ), +#endif DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputTurnOn ), DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputTurnOff ), DEFINE_INPUTFUNC( FIELD_FLOAT, "FadeAndRemove", InputFadeAndRemove ), @@ -106,6 +131,10 @@ BEGIN_DATADESC(CRagdollProp) DEFINE_FIELD( m_strSourceClassName, FIELD_STRING ), DEFINE_FIELD( m_bHasBeenPhysgunned, FIELD_BOOLEAN ), +#ifdef MAPBASE + DEFINE_OUTPUT( m_OnPlayerUse, "OnPlayerUse" ), +#endif + // think functions DEFINE_THINKFUNC( SetDebrisThink ), DEFINE_THINKFUNC( ClearFlagsThink ), @@ -140,9 +169,33 @@ BEGIN_DATADESC(CRagdollProp) DEFINE_RAGDOLL_ELEMENT( 21 ), DEFINE_RAGDOLL_ELEMENT( 22 ), DEFINE_RAGDOLL_ELEMENT( 23 ), +#ifdef MAPBASE + DEFINE_RAGDOLL_ELEMENT( 24 ), + DEFINE_RAGDOLL_ELEMENT( 25 ), + DEFINE_RAGDOLL_ELEMENT( 26 ), + DEFINE_RAGDOLL_ELEMENT( 27 ), + DEFINE_RAGDOLL_ELEMENT( 28 ), + DEFINE_RAGDOLL_ELEMENT( 29 ), + DEFINE_RAGDOLL_ELEMENT( 30 ), + DEFINE_RAGDOLL_ELEMENT( 31 ), +#endif END_DATADESC() +#ifdef MAPBASE_VSCRIPT +BEGIN_ENT_SCRIPTDESC( CRagdollProp, CBaseAnimating, "Ragdoll physics prop." ) + + DEFINE_SCRIPTFUNC_NAMED( GetSourceClassNameAsCStr, "GetSourceClassName", "Gets the ragdoll's source classname." ) + DEFINE_SCRIPTFUNC( SetSourceClassName, "Sets the ragdoll's source classname." ) + DEFINE_SCRIPTFUNC( HasPhysgunInteraction, "Checks if the ragdoll has the specified interaction." ) + + // TODO: Proper shared ragdoll funcs? + DEFINE_SCRIPTFUNC_NAMED( ScriptGetRagdollObject, "GetRagdollObject", "Gets the ragdoll object of the specified index." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptGetRagdollObjectCount, "GetRagdollObjectCount", "Gets the number of ragdoll objects on this ragdoll." ) + +END_SCRIPTDESC() +#endif + //----------------------------------------------------------------------------- // Disable auto fading under dx7 or when level fades are specified //----------------------------------------------------------------------------- @@ -158,8 +211,10 @@ void CRagdollProp::Spawn( void ) // Starts out as the default fade scale value m_flDefaultFadeScale = m_flFadeScale; +#ifndef MAPBASE // NOTE: If this fires, then the assert or the datadesc is wrong! (see DEFINE_RAGDOLL_ELEMENT above) Assert( RAGDOLL_MAX_ELEMENTS == 24 ); +#endif Precache(); SetModel( STRING( GetModelName() ) ); @@ -289,9 +344,39 @@ void CRagdollProp::Precache( void ) int CRagdollProp::ObjectCaps() { - return BaseClass::ObjectCaps() | FCAP_WCEDIT_POSITION; + int caps = FCAP_WCEDIT_POSITION; + +#ifdef MAPBASE + if (HasSpawnFlags( SF_RAGDOLLPROP_ALLOW_USE ) || ragdoll_always_allow_use.GetBool()) + caps |= FCAP_IMPULSE_USE; +#endif + + return BaseClass::ObjectCaps() | caps; } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pActivator - +// *pCaller - +// useType - +// value - +//----------------------------------------------------------------------------- +void CRagdollProp::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + CBasePlayer *pPlayer = ToBasePlayer( pActivator ); + if (pPlayer) + { + m_OnPlayerUse.FireOutput( pActivator, this ); + + if (!HasSpawnFlags( SF_RAGDOLLPROP_PREVENT_PICKUP )) + { + pPlayer->PickupObject( this, false ); + } + } +} +#endif + //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- @@ -359,7 +444,11 @@ void CRagdollProp::OnPhysGunPickup( CBasePlayer *pPhysGunUser, PhysGunPickup_t r } m_bHasBeenPhysgunned = true; +#ifdef MAPBASE + if( ((ragdoll_autointeractions.GetBool() == true && IsBody()) || HasPhysgunInteraction( "onpickup", "boogie" )) && reason != PICKED_UP_BY_PLAYER ) +#else if( HasPhysgunInteraction( "onpickup", "boogie" ) ) +#endif { if ( reason == PUNTED_BY_CANNON ) { @@ -397,7 +486,11 @@ void CRagdollProp::OnPhysGunDrop( CBasePlayer *pPhysGunUser, PhysGunDrop_t Reaso m_hPhysicsAttacker = pPhysGunUser; m_flLastPhysicsInfluenceTime = gpGlobals->curtime; +#ifdef MAPBASE + if( ((ragdoll_autointeractions.GetBool() == true && IsBody()) || HasPhysgunInteraction( "onpickup", "boogie" )) && (Reason != DROPPED_BY_PLAYER && Reason != THROWN_BY_PLAYER) ) +#else if( HasPhysgunInteraction( "onpickup", "boogie" ) ) +#endif { CRagdollBoogie::Create( this, 150, gpGlobals->curtime, 3.0f, SF_RAGDOLL_BOOGIE_ELECTRICAL ); } @@ -416,7 +509,11 @@ void CRagdollProp::OnPhysGunDrop( CBasePlayer *pPhysGunUser, PhysGunDrop_t Reaso if ( Reason != LAUNCHED_BY_CANNON ) return; +#ifdef MAPBASE + if( (ragdoll_autointeractions.GetBool() == true && IsBody()) || HasPhysgunInteraction( "onlaunch", "spin_zaxis" ) ) +#else if( HasPhysgunInteraction( "onlaunch", "spin_zaxis" ) ) +#endif { Vector vecAverageCenter( 0, 0, 0 ); @@ -615,8 +712,21 @@ void CRagdollProp::HandleFirstCollisionInteractions( int index, gamevcollisionev } } +#ifdef MAPBASE + int iVPhysicsFlesh = VPhysicsGetFlesh(); + bool bRagdollAutoInt = (ragdoll_autointeractions.GetBool() == true && iVPhysicsFlesh); + bool bAlienBloodSplat = HasPhysgunInteraction( "onfirstimpact", "alienbloodsplat" ); + if (bRagdollAutoInt && !bAlienBloodSplat) + { + // Alien blood? + bAlienBloodSplat = (iVPhysicsFlesh == CHAR_TEX_ALIENFLESH || iVPhysicsFlesh == CHAR_TEX_ANTLION); + } + + if( bRagdollAutoInt || bAlienBloodSplat || HasPhysgunInteraction( "onfirstimpact", "bloodsplat" ) ) +#else bool bAlienBloodSplat = HasPhysgunInteraction( "onfirstimpact", "alienbloodsplat" ); if( bAlienBloodSplat || HasPhysgunInteraction( "onfirstimpact", "bloodsplat" ) ) +#endif { IPhysicsObject *pObj = VPhysicsGetObject(); @@ -646,7 +756,11 @@ void CRagdollProp::ClearFlagsThink( void ) //----------------------------------------------------------------------------- AngularImpulse CRagdollProp::PhysGunLaunchAngularImpulse() { +#ifdef MAPBASE + if( (ragdoll_autointeractions.GetBool() == true && IsBody()) || HasPhysgunInteraction( "onlaunch", "spin_zaxis" ) ) +#else if( HasPhysgunInteraction( "onlaunch", "spin_zaxis" ) ) +#endif { // Don't add in random angular impulse if this object is supposed to spin in a specific way. AngularImpulse ang( 0, 0, 0 ); @@ -700,7 +814,11 @@ void CRagdollProp::InitRagdoll( const Vector &forceVector, int forceBone, const params.pCurrentBones = pBoneToWorld; params.jointFrictionScale = 1.0; params.allowStretch = HasSpawnFlags(SF_RAGDOLLPROP_ALLOW_STRETCH); +#ifdef MAPBASE + params.fixedConstraints = HasSpawnFlags(SF_RAGDOLLPROP_FIXED_CONSTRAINTS); +#else params.fixedConstraints = false; +#endif RagdollCreate( m_ragdoll, params, physenv ); RagdollApplyAnimationAsVelocity( m_ragdoll, pPrevBones, pBoneToWorld, dt ); if ( m_anglesOverrideString != NULL_STRING && Q_strlen(m_anglesOverrideString.ToCStr()) > 0 ) @@ -1069,6 +1187,23 @@ int CRagdollProp::VPhysicsGetObjectList( IPhysicsObject **pList, int listMax ) return m_ragdoll.listCount; } +#ifdef MAPBASE +int CRagdollProp::VPhysicsGetFlesh() +{ + IPhysicsObject *pList[VPHYSICS_MAX_OBJECT_LIST_COUNT]; + int count = VPhysicsGetObjectList( pList, ARRAYSIZE(pList) ); + for ( int i = 0; i < count; i++ ) + { + int material = pList[i]->GetMaterialIndex(); + const surfacedata_t *pSurfaceData = physprops->GetSurfaceData( material ); + // Is flesh ?, don't allow pickup + if ( pSurfaceData->game.material == CHAR_TEX_ANTLION || pSurfaceData->game.material == CHAR_TEX_FLESH || pSurfaceData->game.material == CHAR_TEX_BLOODYFLESH || pSurfaceData->game.material == CHAR_TEX_ALIENFLESH ) + return pSurfaceData->game.material; + } + return 0; +} +#endif + void CRagdollProp::UpdateNetworkDataFromVPhysics( IPhysicsObject *pPhysics, int index ) { Assert(index < m_ragdoll.listCount); @@ -1278,6 +1413,16 @@ CBaseAnimating *CreateServerRagdollSubmodel( CBaseAnimating *pOwner, const char matrix3x4_t pBoneToWorld[MAXSTUDIOBONES], pBoneToWorldNext[MAXSTUDIOBONES]; pRagdoll->ResetSequence( 0 ); +#ifdef MAPBASE_VSCRIPT + // Hook for pre-spawn ragdolling + if (pOwner && pOwner->m_ScriptScope.IsInitialized() && CBaseAnimating::g_Hook_OnServerRagdoll.CanRunInScope( pOwner->m_ScriptScope )) + { + // ragdoll, submodel + ScriptVariant_t args[] = { ScriptVariant_t( pRagdoll->GetScriptInstance() ), true }; + CBaseAnimating::g_Hook_OnServerRagdoll.Call( pOwner->m_ScriptScope, NULL, args ); + } +#endif + // let bone merging do the work of copying everything over for us pRagdoll->SetParent( pOwner ); pRagdoll->SetupBones( pBoneToWorld, BONE_USED_BY_ANYTHING ); @@ -1302,6 +1447,16 @@ CBaseEntity *CreateServerRagdoll( CBaseAnimating *pAnimating, int forceBone, con pRagdoll->CopyAnimationDataFrom( pAnimating ); pRagdoll->SetOwnerEntity( pAnimating ); +#ifdef MAPBASE_VSCRIPT + // Hook for pre-spawn ragdolling + if (pAnimating->m_ScriptScope.IsInitialized() && CBaseAnimating::g_Hook_OnServerRagdoll.CanRunInScope( pAnimating->m_ScriptScope )) + { + // ragdoll, submodel + ScriptVariant_t args[] = { ScriptVariant_t( pRagdoll->GetScriptInstance() ), false }; + CBaseAnimating::g_Hook_OnServerRagdoll.Call( pAnimating->m_ScriptScope, NULL, args ); + } +#endif + pRagdoll->InitRagdollAnimation(); matrix3x4_t pBoneToWorld[MAXSTUDIOBONES], pBoneToWorldNext[MAXSTUDIOBONES]; @@ -1441,6 +1596,22 @@ CBaseEntity *CreateServerRagdoll( CBaseAnimating *pAnimating, int forceBone, con maxs = pAnimating->CollisionProp()->OBBMaxs(); pRagdoll->CollisionProp()->SetCollisionBounds( mins, maxs ); +#ifdef MAPBASE + // If this was a NPC running a dynamic interaction, disable collisions with the interaction partner + if (pAnimating->IsNPC() /*&& pAnimating->MyNPCPointer()->IsRunningDynamicInteraction()*/) + { + CAI_BaseNPC *pNPC = pAnimating->MyNPCPointer(); + if (pNPC->GetInteractionPartner() && pNPC->GetInteractionPartner()->VPhysicsGetObject()) + { + PhysDisableEntityCollisions( pRagdoll, pNPC->GetInteractionPartner() ); + } + } + + variant_t variant; + variant.SetEntity(pRagdoll); + pAnimating->FireNamedOutput("OnServerRagdoll", variant, pRagdoll, pAnimating); +#endif + return pRagdoll; } @@ -1683,6 +1854,56 @@ void CRagdollProp::InputDisableMotion( inputdata_t &inputdata ) DisableMotion(); } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: Input handler to start the physics prop simulating. +//----------------------------------------------------------------------------- +void CRagdollProp::InputWake( inputdata_t &inputdata ) +{ + for ( int iRagdoll = 0; iRagdoll < m_ragdoll.listCount; ++iRagdoll ) + { + IPhysicsObject *pPhysicsObject = m_ragdoll.list[ iRagdoll ].pObject; + if ( pPhysicsObject != NULL ) + { + pPhysicsObject->Wake(); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Input handler to stop the physics prop simulating. +//----------------------------------------------------------------------------- +void CRagdollProp::InputSleep( inputdata_t &inputdata ) +{ + for ( int iRagdoll = 0; iRagdoll < m_ragdoll.listCount; ++iRagdoll ) + { + IPhysicsObject *pPhysicsObject = m_ragdoll.list[ iRagdoll ].pObject; + if ( pPhysicsObject != NULL ) + { + pPhysicsObject->Sleep(); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Adds ragdoll to LRU. +//----------------------------------------------------------------------------- +void CRagdollProp::InputAddToLRU( inputdata_t &inputdata ) +{ + AddSpawnFlags( SF_RAGDOLLPROP_USE_LRU_RETIREMENT ); + s_RagdollLRU.MoveToTopOfLRU( this ); +} + +//----------------------------------------------------------------------------- +// Purpose: Removes ragdoll from LRU. +//----------------------------------------------------------------------------- +void CRagdollProp::InputRemoveFromLRU( inputdata_t &inputdata ) +{ + RemoveSpawnFlags( SF_RAGDOLLPROP_USE_LRU_RETIREMENT ); + s_RagdollLRU.RemoveFromLRU( this ); +} +#endif + void CRagdollProp::InputTurnOn( inputdata_t &inputdata ) { RemoveEffects( EF_NODRAW ); @@ -1703,6 +1924,24 @@ void CRagdollProp::InputFadeAndRemove( inputdata_t &inputdata ) FadeOut( 0.0f, flFadeDuration ); } +#ifdef MAPBASE_VSCRIPT +HSCRIPT CRagdollProp::ScriptGetRagdollObject( int iIndex ) +{ + if (iIndex < 0 || iIndex > m_ragdoll.listCount) + { + Warning("%s GetRagdollObject: Index %i not valid (%i objects)\n", GetDebugName(), iIndex, m_ragdoll.listCount); + return NULL; + } + + return g_pScriptVM->RegisterInstance( m_ragdoll.list[iIndex].pObject ); +} + +int CRagdollProp::ScriptGetRagdollObjectCount() +{ + return m_ragdoll.listCount; +} +#endif + void Ragdoll_GetAngleOverrideString( char *pOut, int size, CBaseEntity *pEntity ) { CRagdollProp *pRagdoll = dynamic_cast(pEntity); diff --git a/src/game/server/physics_prop_ragdoll.h b/src/game/server/physics_prop_ragdoll.h index 7c24b47e..82a3d77a 100644 --- a/src/game/server/physics_prop_ragdoll.h +++ b/src/game/server/physics_prop_ragdoll.h @@ -22,6 +22,9 @@ class CRagdollProp : public CBaseAnimating, public CDefaultPlayerPickupVPhysics { DECLARE_CLASS( CRagdollProp, CBaseAnimating ); +#ifdef MAPBASE_VSCRIPT + DECLARE_ENT_SCRIPTDESC(); +#endif public: CRagdollProp( void ); @@ -39,6 +42,10 @@ public: int ObjectCaps(); +#ifdef MAPBASE + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); +#endif + DECLARE_SERVERCLASS(); // Don't treat as a live target virtual bool IsAlive( void ) { return false; } @@ -49,6 +56,9 @@ public: virtual void SetupBones( matrix3x4_t *pBoneToWorld, int boneMask ); virtual void VPhysicsUpdate( IPhysicsObject *pPhysics ); virtual int VPhysicsGetObjectList( IPhysicsObject **pList, int listMax ); +#ifdef MAPBASE + int VPhysicsGetFlesh(); +#endif virtual int DrawDebugTextOverlays(void); @@ -56,6 +66,9 @@ public: virtual IResponseSystem *GetResponseSystem(); virtual void ModifyOrAppendCriteria( AI_CriteriaSet& set ); void SetSourceClassName( const char *pClassname ); +#ifdef MAPBASE + const char *GetSourceClassNameAsCStr() { return STRING( m_strSourceClassName ); } +#endif // Physics attacker virtual CBasePlayer *HasPhysicsAttacker( float dt ); @@ -101,10 +114,21 @@ public: void InputStartRadgollBoogie( inputdata_t &inputdata ); void InputEnableMotion( inputdata_t &inputdata ); void InputDisableMotion( inputdata_t &inputdata ); +#ifdef MAPBASE + void InputWake( inputdata_t &inputdata ); + void InputSleep( inputdata_t &inputdata ); + void InputAddToLRU( inputdata_t &inputdata ); + void InputRemoveFromLRU( inputdata_t &inputdata ); +#endif void InputTurnOn( inputdata_t &inputdata ); void InputTurnOff( inputdata_t &inputdata ); void InputFadeAndRemove( inputdata_t &inputdata ); +#ifdef MAPBASE_VSCRIPT + HSCRIPT ScriptGetRagdollObject( int iIndex ); + int ScriptGetRagdollObjectCount(); +#endif + DECLARE_DATADESC(); protected: @@ -140,6 +164,10 @@ private: string_t m_strSourceClassName; bool m_bHasBeenPhysgunned; +#ifdef MAPBASE + COutputEvent m_OnPlayerUse; +#endif + // If not 1, then allow underlying sequence to blend in with simulated bone positions CNetworkVar( float, m_flBlendWeight ); CNetworkVar( int, m_nOverlaySequence ); diff --git a/src/game/server/physobj.cpp b/src/game/server/physobj.cpp index efaffd07..c486fa80 100644 --- a/src/game/server/physobj.cpp +++ b/src/game/server/physobj.cpp @@ -387,6 +387,9 @@ BEGIN_DATADESC( CPhysBox ) DEFINE_INPUTFUNC( FIELD_VOID, "DisableMotion", InputDisableMotion ), DEFINE_INPUTFUNC( FIELD_VOID, "ForceDrop", InputForceDrop ), DEFINE_INPUTFUNC( FIELD_VOID, "DisableFloating", InputDisableFloating ), +#ifdef MAPBASE + DEFINE_INPUTFUNC( FIELD_BOOLEAN, "SetDebris", InputSetDebris ), +#endif // Function pointers DEFINE_ENTITYFUNC( BreakTouch ), @@ -562,6 +565,13 @@ int CPhysBox::ObjectCaps() } } +#ifdef MAPBASE + if ( HasSpawnFlags( SF_PHYSBOX_RADIUS_PICKUP ) ) + { + caps |= FCAP_USE_IN_RADIUS; + } +#endif + return caps; } @@ -689,6 +699,25 @@ void CPhysBox::InputDisableFloating( inputdata_t &inputdata ) PhysEnableFloating( VPhysicsGetObject(), false ); } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: Adds or removes the debris spawnflag. +//----------------------------------------------------------------------------- +void CPhysBox::InputSetDebris( inputdata_t &inputdata ) +{ + if (inputdata.value.Bool()) + { + AddSpawnFlags(SF_PHYSBOX_DEBRIS); + SetCollisionGroup(COLLISION_GROUP_DEBRIS); + } + else + { + RemoveSpawnFlags(SF_PHYSBOX_DEBRIS); + SetCollisionGroup(COLLISION_GROUP_INTERACTIVE); // Is this the default collision group? + } +} +#endif + //----------------------------------------------------------------------------- // Purpose: If we're being held by the player's hand/physgun, force it to drop us //----------------------------------------------------------------------------- @@ -871,6 +900,9 @@ BEGIN_DATADESC( CPhysExplosion ) // Inputs DEFINE_INPUTFUNC( FIELD_VOID, "Explode", InputExplode ), +#ifdef MAPBASE + DEFINE_INPUTFUNC( FIELD_VOID, "ExplodeAndRemove", InputExplodeAndRemove ), +#endif // Outputs DEFINE_OUTPUT( m_OnPushedPlayer, "OnPushedPlayer" ), @@ -888,10 +920,19 @@ void CPhysExplosion::Spawn( void ) float CPhysExplosion::GetRadius( void ) { float radius = m_radius; +#ifdef MAPBASE + if ( radius == 0 ) +#else if ( radius <= 0 ) +#endif { // Use the same radius as combat radius = m_damage * 2.5; + +#ifdef MAPBASE + if (radius < 0) + radius *= -1; +#endif } return radius; @@ -924,6 +965,17 @@ void CPhysExplosion::InputExplode( inputdata_t &inputdata ) Explode( inputdata.pActivator, inputdata.pCaller ); } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPhysExplosion::InputExplodeAndRemove( inputdata_t &inputdata ) +{ + Explode( inputdata.pActivator, inputdata.pCaller ); + UTIL_Remove(this); +} +#endif + //----------------------------------------------------------------------------- // Purpose: @@ -936,6 +988,13 @@ void CPhysExplosion::Explode( CBaseEntity *pActivator, CBaseEntity *pCaller ) falloff = 1.0 / 2.5; +#ifdef MAPBASE + // For negative damage handling + float damage = m_damage; + if (damage < 0) + damage *= -1.0f; +#endif + // iterate on all entities in the vicinity. // I've removed the traceline heuristic from phys explosions. SO right now they will // affect entities through walls. (sjb) @@ -986,7 +1045,11 @@ void CPhysExplosion::Explode( CBaseEntity *pActivator, CBaseEntity *pCaller ) } adjustedDamage = flDist * falloff; +#ifdef MAPBASE + adjustedDamage = damage - adjustedDamage; +#else adjustedDamage = m_damage - adjustedDamage; +#endif if ( adjustedDamage < 1 ) { @@ -994,7 +1057,19 @@ void CPhysExplosion::Explode( CBaseEntity *pActivator, CBaseEntity *pCaller ) } CTakeDamageInfo info( this, this, adjustedDamage, DMG_BLAST ); +#ifdef MAPBASE + // Negative damage handling + Vector vecDir = (vecSpot - vecOrigin); + if (m_damage < 0) + { + vecDir *= -1.0f; + vecOrigin += vecDir; + NDebugOverlay::Cross3D(vecOrigin, 2.0f, 255, 255, 0, true, 1.0f); + } + CalculateExplosiveDamageForce( &info, vecDir, vecOrigin ); +#else CalculateExplosiveDamageForce( &info, (vecSpot - vecOrigin), vecOrigin ); +#endif if ( HasSpawnFlags( SF_PHYSEXPLOSION_PUSH_PLAYER ) ) { @@ -1019,7 +1094,11 @@ void CPhysExplosion::Explode( CBaseEntity *pActivator, CBaseEntity *pCaller ) pEntity->ViewPunch( vecDeltaAngles ); } +#ifdef MAPBASE + Vector vecPush = (vecPushDir*damage*flFalloff*2.0f); +#else Vector vecPush = (vecPushDir*m_damage*flFalloff*2.0f); +#endif if ( pEntity->GetFlags() & FL_BASEVELOCITY ) { vecPush = vecPush + pEntity->GetBaseVelocity(); @@ -1242,6 +1321,16 @@ public: SetMoveType( MOVETYPE_VPHYSICS ); SetSolid( SOLID_VPHYSICS ); m_takedamage = DAMAGE_EVENTS_ONLY; + +#ifdef MAPBASE + // If we don't have an owner entity, it means this wasn't spawned by a phys_convert and this is safe. + if ( !GetOwnerEntity() ) + { + VPhysicsInitNormal( SOLID_VPHYSICS, 0, HasSpawnFlags(SF_PHYSBOX_DEBRIS) ); + if ( HasSpawnFlags(SF_PHYSBOX_ASLEEP) ) + SetCollisionGroup( COLLISION_GROUP_DEBRIS ); + } +#endif } }; @@ -1258,6 +1347,16 @@ public: SetMoveType( MOVETYPE_VPHYSICS ); SetSolid( SOLID_VPHYSICS ); m_takedamage = DAMAGE_EVENTS_ONLY; + +#ifdef MAPBASE + // If we don't have an owner entity, it means this wasn't spawned by a phys_convert and this is safe. + if ( !GetOwnerEntity() ) + { + VPhysicsInitNormal( SOLID_VPHYSICS, 0, HasSpawnFlags(SF_PHYSPROP_DEBRIS) ); + if ( HasSpawnFlags(SF_PHYSPROP_START_ASLEEP) ) + SetCollisionGroup( COLLISION_GROUP_DEBRIS ); + } +#endif } int ObjectCaps() @@ -1333,6 +1432,10 @@ static CBaseEntity *CreateSimplePhysicsObject( CBaseEntity *pEntity, bool create pPhysEntity->KeyValue( "model", STRING(pEntity->GetModelName()) ); pPhysEntity->SetAbsOrigin( pEntity->GetAbsOrigin() ); pPhysEntity->SetAbsAngles( pEntity->GetAbsAngles() ); +#ifdef MAPBASE + // So the entity knows it's being spawned by a phys_convert + pPhysEntity->SetOwnerEntity( pEntity ); +#endif pPhysEntity->Spawn(); if ( !TransferPhysicsObject( pEntity, pPhysEntity, !createAsleep ) ) { @@ -1343,6 +1446,36 @@ static CBaseEntity *CreateSimplePhysicsObject( CBaseEntity *pEntity, bool create return pPhysEntity; } +#ifdef MAPBASE +// Creates func_brush and prop_physics instead, because why not? +static CBaseEntity *CreateConventionalPhysicsObject( CBaseEntity *pEntity, bool createAsleep, bool createAsDebris ) +{ + CBaseEntity *pPhysEntity = NULL; + int modelindex = pEntity->GetModelIndex(); + const model_t *model = modelinfo->GetModel( modelindex ); + if ( model && modelinfo->GetModelType(model) == mod_brush ) + { + pPhysEntity = CreateEntityByName( "func_physbox" ); + } + else + { + pPhysEntity = CreateEntityByName( "prop_physics_override" ); + } + + pPhysEntity->KeyValue( "model", STRING(pEntity->GetModelName()) ); + pPhysEntity->SetAbsOrigin( pEntity->GetAbsOrigin() ); + pPhysEntity->SetAbsAngles( pEntity->GetAbsAngles() ); + pPhysEntity->Spawn(); + if ( !TransferPhysicsObject( pEntity, pPhysEntity, !createAsleep ) ) + { + pPhysEntity->VPhysicsInitNormal( SOLID_VPHYSICS, 0, createAsleep ); + if ( createAsDebris ) + pPhysEntity->SetCollisionGroup( COLLISION_GROUP_DEBRIS ); + } + return pPhysEntity; +} +#endif + #define SF_CONVERT_ASLEEP 0x0001 #define SF_CONVERT_AS_DEBRIS 0x0002 @@ -1357,11 +1490,22 @@ public: // Input handlers void InputConvertTarget( inputdata_t &inputdata ); +#ifdef MAPBASE + enum + { + CONVERT_ENTITYTYPE_SIMPLE, // simple_physics_prop, simple_physics_brush, etc. + CONVERT_ENTITYTYPE_CONVENTIONAL, // prop_physics, func_physbox, etc. + }; +#endif + DECLARE_DATADESC(); private: string_t m_swapModel; float m_flMassOverride; +#ifdef MAPBASE + int m_iPhysicsEntityType = CONVERT_ENTITYTYPE_SIMPLE; +#endif }; LINK_ENTITY_TO_CLASS( phys_convert, CPhysConvert ); @@ -1370,6 +1514,9 @@ BEGIN_DATADESC( CPhysConvert ) DEFINE_KEYFIELD( m_swapModel, FIELD_STRING, "swapmodel" ), DEFINE_KEYFIELD( m_flMassOverride, FIELD_FLOAT, "massoverride" ), +#ifdef MAPBASE + DEFINE_INPUT( m_iPhysicsEntityType, FIELD_INTEGER, "SetConversionType" ), +#endif // Inputs DEFINE_INPUTFUNC( FIELD_VOID, "ConvertTarget", InputConvertTarget ), @@ -1432,7 +1579,16 @@ void CPhysConvert::InputConvertTarget( inputdata_t &inputdata ) } // created phys object, now move hierarchy over +#ifdef MAPBASE + CBaseEntity *pPhys; + switch (m_iPhysicsEntityType) + { + case CONVERT_ENTITYTYPE_CONVENTIONAL: pPhys = CreateConventionalPhysicsObject( pEntity, createAsleep, createAsDebris ); break; + default: pPhys = CreateSimplePhysicsObject( pEntity, createAsleep, createAsDebris ); break; + } +#else CBaseEntity *pPhys = CreateSimplePhysicsObject( pEntity, createAsleep, createAsDebris ); +#endif if ( pPhys ) { // Override the mass if specified @@ -1445,6 +1601,22 @@ void CPhysConvert::InputConvertTarget( inputdata_t &inputdata ) } } +#ifdef MAPBASE + pPhys->m_nRenderMode = pEntity->m_nRenderMode; + pPhys->m_nRenderFX = pEntity->m_nRenderFX; + const color32 rclr = pEntity->GetRenderColor(); + pPhys->SetRenderColor(rclr.r, rclr.g, rclr.b, rclr.a); + if (pEntity->GetBaseAnimating() /*&& pPhys->GetBaseAnimating()*/) + { + CBaseAnimating *pEntityAnimating = pEntity->GetBaseAnimating(); + CBaseAnimating *pPhysAnimating = pPhys->GetBaseAnimating(); + + pPhysAnimating->m_nSkin = pEntityAnimating->m_nSkin; + pPhysAnimating->m_nBody = pEntityAnimating->m_nBody; + pPhysAnimating->SetModelScale(pEntityAnimating->GetModelScale()); + } +#endif + pPhys->SetName( pEntity->GetEntityName() ); UTIL_TransferPoseParameters( pEntity, pPhys ); TransferChildren( pEntity, pPhys ); @@ -1463,6 +1635,9 @@ void CPhysConvert::InputConvertTarget( inputdata_t &inputdata ) #define SF_MAGNET_SUCK 0x0004 #define SF_MAGNET_ALLOWROTATION 0x0008 #define SF_MAGNET_COAST_HACK 0x0010 +#ifdef MAPBASE +#define SF_MAGNET_PREVENT_PICKUP 0x0020 +#endif LINK_ENTITY_TO_CLASS( phys_magnet, CPhysMagnet ); @@ -1559,6 +1734,16 @@ CPhysMagnet::~CPhysMagnet( void ) //----------------------------------------------------------------------------- void CPhysMagnet::Spawn( void ) { +#ifdef MAPBASE + // Crashes otherwise + if (GetModelName() == NULL_STRING) + { + Warning("WARNING: %s spawned with no model name\n", GetDebugName()); + UTIL_Remove(this); + return; + } +#endif + Precache(); SetMoveType( MOVETYPE_NONE ); @@ -1587,6 +1772,13 @@ void CPhysMagnet::Spawn( void ) VPhysicsGetObject()->EnableMotion( false ); } +#ifdef MAPBASE + if ( HasSpawnFlags(SF_MAGNET_PREVENT_PICKUP) ) + { + PhysSetGameFlags(VPhysicsGetObject(), FVPHYSICS_NO_PLAYER_PICKUP); + } +#endif + m_bActive = true; m_pConstraintGroup = NULL; m_flTotalMass = 0; @@ -1741,6 +1933,20 @@ void CPhysMagnet::VPhysicsCollision( int index, gamevcollisionevent_t *pEvent ) BaseClass::VPhysicsCollision( index, pEvent ); } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CPhysMagnet::CanBePickedUpByPhyscannon( void ) +{ + if ( HasSpawnFlags( SF_MAGNET_PREVENT_PICKUP ) ) + return false; + + return true; +} +#endif + //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- diff --git a/src/game/server/physobj.h b/src/game/server/physobj.h index a6e9e5a5..09d333c5 100644 --- a/src/game/server/physobj.h +++ b/src/game/server/physobj.h @@ -37,6 +37,9 @@ #define SF_PHYSBOX_NEVER_PICK_UP 0x200000 // Physcannon will never be able to pick this up. #define SF_PHYSBOX_NEVER_PUNT 0x400000 // Physcannon will never be able to punt this object. #define SF_PHYSBOX_PREVENT_PLAYER_TOUCH_ENABLE 0x800000 // If set, the player will not cause the object to enable its motion when bumped into +#ifdef MAPBASE +#define SF_PHYSBOX_RADIUS_PICKUP 0x1000000 // Allows this object to be picked up in a radius, useful for smaller objects. Based on the prop_physics input +#endif // UNDONE: Hook collisions into the physics system to generate touch functions and take damage on falls // UNDONE: Base class PhysBrush @@ -76,6 +79,9 @@ public: void InputDisableMotion( inputdata_t &inputdata ); void InputForceDrop( inputdata_t &inputdata ); void InputDisableFloating( inputdata_t &inputdata ); +#ifdef MAPBASE + void InputSetDebris( inputdata_t &inputdata ); +#endif DECLARE_DATADESC(); @@ -120,6 +126,9 @@ public: // Input handlers void InputExplode( inputdata_t &inputdata ); +#ifdef MAPBASE + void InputExplodeAndRemove( inputdata_t &inputdata ); +#endif DECLARE_DATADESC(); private: @@ -187,6 +196,9 @@ public: void Precache( void ); void Touch( CBaseEntity *pOther ); void VPhysicsCollision( int index, gamevcollisionevent_t *pEvent ); +#ifdef MAPBASE + bool CanBePickedUpByPhyscannon( void ); +#endif void DoMagnetSuck( CBaseEntity *pOther ); void SetConstraintGroup( IPhysicsConstraintGroup *pGroup ); diff --git a/src/game/server/player.cpp b/src/game/server/player.cpp index 8df710cc..179c5892 100644 --- a/src/game/server/player.cpp +++ b/src/game/server/player.cpp @@ -59,6 +59,10 @@ #include "env_zoom.h" #include "rumble_shared.h" #include "gamestats.h" +#ifdef MAPBASE // From Alien Swarm SDK +#include "env_tonemap_controller.h" +#include "fogvolume.h" +#endif #include "npcevent.h" #include "datacache/imdlcache.h" #include "hintsystem.h" @@ -80,6 +84,14 @@ #ifdef HL2_DLL #include "combine_mine.h" #include "weapon_physcannon.h" +#ifdef MAPBASE +#include "mapbase/GlobalStrings.h" +#include "mapbase/matchers.h" +#endif +#endif + +#ifdef MAPBASE_VSCRIPT +#include "mapbase/vscript_funcs_shared.h" #endif ConVar autoaim_max_dist( "autoaim_max_dist", "2160" ); // 2160 = 180 feet @@ -196,6 +208,10 @@ ConVar sv_player_display_usercommand_errors( "sv_player_display_usercommand_err ConVar player_debug_print_damage( "player_debug_print_damage", "0", FCVAR_CHEAT, "When true, print amount and type of all damage received by player to console." ); +#ifdef MAPBASE +ConVar player_use_visibility_cache( "player_use_visibility_cache", "0", FCVAR_NONE, "Allows the player to use the visibility cache." ); +#endif + void CC_GiveCurrentAmmo( void ) { @@ -443,14 +459,29 @@ BEGIN_DATADESC( CBasePlayer ) DEFINE_FIELD( m_autoKickDisabled, FIELD_BOOLEAN ), +#ifdef MAPBASE + DEFINE_FIELD( m_bInTriggerFall, FIELD_BOOLEAN ), + + DEFINE_FIELD( m_bDrawPlayerModelExternally, FIELD_BOOLEAN ), +#endif + // Function Pointers DEFINE_FUNCTION( PlayerDeathThink ), // Inputs DEFINE_INPUTFUNC( FIELD_INTEGER, "SetHealth", InputSetHealth ), DEFINE_INPUTFUNC( FIELD_BOOLEAN, "SetHUDVisibility", InputSetHUDVisibility ), +#ifdef MAPBASE // From Alien Swarm SDK (kind of) + DEFINE_INPUTFUNC( FIELD_INPUT, "SetFogController", InputSetFogController ), + DEFINE_INPUTFUNC( FIELD_INPUT, "SetPostProcessController", InputSetPostProcessController ), + DEFINE_INPUTFUNC( FIELD_INPUT, "SetColorCorrectionController", InputSetColorCorrectionController ), +#else DEFINE_INPUTFUNC( FIELD_STRING, "SetFogController", InputSetFogController ), +#endif DEFINE_INPUTFUNC( FIELD_STRING, "HandleMapEvent", InputHandleMapEvent ), +#ifdef MAPBASE + DEFINE_INPUTFUNC( FIELD_BOOLEAN, "SetSuppressAttacks", InputSetSuppressAttacks ), +#endif DEFINE_FIELD( m_nNumCrouches, FIELD_INTEGER ), DEFINE_FIELD( m_bDuckToggled, FIELD_BOOLEAN ), @@ -461,6 +492,10 @@ BEGIN_DATADESC( CBasePlayer ) DEFINE_FIELD( m_nNumCrateHudHints, FIELD_INTEGER ), DEFINE_INPUTFUNC( FIELD_STRING, "SetScriptOverlayMaterial", InputSetScriptOverlayMaterial ), +#ifdef MAPBASE // From Alien Swarm SDK + DEFINE_FIELD( m_hPostProcessCtrl, FIELD_EHANDLE ), + DEFINE_FIELD( m_hColorCorrectionCtrl, FIELD_EHANDLE ), +#endif // DEFINE_FIELD( m_nBodyPitchPoseParam, FIELD_INTEGER ), // DEFINE_ARRAY( m_StepSoundCache, StepSoundCache_t, 2 ), @@ -469,6 +504,83 @@ BEGIN_DATADESC( CBasePlayer ) // DEFINE_UTLVECTOR( m_vecPlayerSimInfo ), END_DATADESC() +#ifdef MAPBASE_VSCRIPT +// TODO: Better placement? +ScriptHook_t g_Hook_PlayerRunCommand; +ScriptHook_t g_Hook_FindUseEntity; + +BEGIN_ENT_SCRIPTDESC( CBasePlayer, CBaseCombatCharacter, "The player entity." ) + + DEFINE_SCRIPTFUNC_NAMED( ScriptIsPlayerNoclipping, "IsNoclipping", "Returns true if the player is in noclip mode." ) + + DEFINE_SCRIPTFUNC_NAMED( VScriptGetExpresser, "GetExpresser", "Gets a handle for this player's expresser." ) + + DEFINE_SCRIPTFUNC( GetPlayerName, "Gets the player's name." ) + DEFINE_SCRIPTFUNC( GetUserID, "Gets the player's user ID." ) + DEFINE_SCRIPTFUNC_NAMED( GetUserID, "GetPlayerUserId", SCRIPT_HIDE ) + DEFINE_SCRIPTFUNC( GetNetworkIDString, "Gets the player's network (i.e. Steam) ID." ) + + DEFINE_SCRIPTFUNC( FragCount, "Gets the number of frags (kills) this player has in a multiplayer game." ) + DEFINE_SCRIPTFUNC( DeathCount, "Gets the number of deaths this player has had in a multiplayer game." ) + DEFINE_SCRIPTFUNC( IsConnected, "Returns true if this player is connected." ) + DEFINE_SCRIPTFUNC( IsDisconnecting, "Returns true if this player is disconnecting." ) + DEFINE_SCRIPTFUNC( IsSuitEquipped, "Returns true if this player had the HEV suit equipped." ) + + DEFINE_SCRIPTFUNC_NAMED( ArmorValue, "GetArmor", "Gets the player's armor." ) + DEFINE_SCRIPTFUNC_NAMED( SetArmorValue, "SetArmor", "Sets the player's armor." ) + + DEFINE_SCRIPTFUNC( FlashlightIsOn, "Returns true if the flashlight is on." ) + DEFINE_SCRIPTFUNC( FlashlightTurnOn, "Turns on the flashlight." ) + DEFINE_SCRIPTFUNC( FlashlightTurnOff, "Turns off the flashlight." ) + + DEFINE_SCRIPTFUNC( DisableButtons, "Disables the specified button mask." ) + DEFINE_SCRIPTFUNC( EnableButtons, "Enables the specified button mask if it was disabled before." ) + DEFINE_SCRIPTFUNC( ForceButtons, "Forces the specified button mask." ) + DEFINE_SCRIPTFUNC( UnforceButtons, "Unforces the specified button mask if it was forced before." ) + + DEFINE_SCRIPTFUNC( GetButtons, "Gets the player's active buttons." ) + DEFINE_SCRIPTFUNC( GetButtonPressed, "Gets the player's currently pressed buttons." ) + DEFINE_SCRIPTFUNC( GetButtonReleased, "Gets the player's just-released buttons." ) + DEFINE_SCRIPTFUNC( GetButtonLast, "Gets the player's previously active buttons." ) + DEFINE_SCRIPTFUNC( GetButtonDisabled, "Gets the player's currently unusable buttons." ) + DEFINE_SCRIPTFUNC( GetButtonForced, "Gets the player's currently forced buttons." ) + + DEFINE_SCRIPTFUNC( GetFOV, "" ) + DEFINE_SCRIPTFUNC_NAMED( ScriptGetFOVOwner, "GetFOVOwner", "Gets current view owner." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptSetFOV, "SetFOV", "Sets player FOV regardless of view owner." ) + + DEFINE_SCRIPTFUNC( ViewPunch, "Punches the player's view with the specified vector." ) + DEFINE_SCRIPTFUNC( SetMuzzleFlashTime, "Sets the player's muzzle flash time for AI." ) + DEFINE_SCRIPTFUNC( SetSuitUpdate, "Sets an update for the player's HEV suit." ) + + DEFINE_SCRIPTFUNC_NAMED( ScriptGetAutoaimVector, "GetAutoaimVector", "Gets the player's autoaim shooting direction with the specified scale." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptGetAutoaimVectorCustomMaxDist, "GetAutoaimVectorCustomMaxDist", "Gets the player's autoaim shooting direction with the specified scale and a custom max distance." ) + DEFINE_SCRIPTFUNC( ShouldAutoaim, "Returns true if the player should be autoaiming." ) + + DEFINE_SCRIPTFUNC_NAMED( ScriptGetEyeForward, "GetEyeForward", "Gets the player's forward eye vector." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptGetEyeRight, "GetEyeRight", "Gets the player's right eye vector." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptGetEyeUp, "GetEyeUp", "Gets the player's up eye vector." ) + + DEFINE_SCRIPTFUNC_NAMED( ScriptGetViewModel, "GetViewModel", "Returns the viewmodel of the specified index." ) + + DEFINE_SCRIPTFUNC_NAMED( ScriptGetUseEntity, "GetUseEntity", "Gets the player's current use entity." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptGetHeldObject, "GetHeldObject", "Gets the player's currently held object IF it is being held by a gravity gun. To check for the player's held +USE object, use the standalone GetPlayerHeldEntity function." ) + + // + // Hooks + // + BEGIN_SCRIPTHOOK( g_Hook_PlayerRunCommand, "PlayerRunCommand", FIELD_VOID, "Called when running a player command on the server." ) + DEFINE_SCRIPTHOOK_PARAM( "command", FIELD_HSCRIPT ) + END_SCRIPTHOOK() + + BEGIN_SCRIPTHOOK( g_Hook_FindUseEntity, "FindUseEntity", FIELD_HSCRIPT, "Called when finding an entity to use. The 'entity' parameter is for the entity found by the default function. If 'is_radius' is true, then this entity was found by searching in a radius around the cursor, rather than being directly used. Return a different entity to use something else." ) + DEFINE_SCRIPTHOOK_PARAM( "entity", FIELD_HSCRIPT ) + DEFINE_SCRIPTHOOK_PARAM( "is_radius", FIELD_BOOLEAN ) + END_SCRIPTHOOK() + +END_SCRIPTDESC(); +#endif + int giPrecacheGrunt = 0; edict_t *CBasePlayer::s_PlayerEdict = NULL; @@ -535,6 +647,46 @@ void CBasePlayer::DestroyViewModels( void ) } } +#ifdef MAPBASE +extern char g_szDefaultHandsModel[MAX_PATH]; +extern int g_iDefaultHandsSkin; +extern int g_iDefaultHandsBody; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBasePlayer::CreateHandModel(int index, int iOtherVm) +{ + Assert(index >= 0 && index < MAX_VIEWMODELS && iOtherVm >= 0 && iOtherVm < MAX_VIEWMODELS ); + + if (GetViewModel( index )) + { + // This can happen if the player respawns + // Don't draw unless we're already using a hands weapon + if ( !GetActiveWeapon() || !GetActiveWeapon()->UsesHands() ) + GetViewModel( index )->AddEffects( EF_NODRAW ); + return; + } + + CBaseViewModel *vm = (CBaseViewModel *)CreateEntityByName("hand_viewmodel"); + if (vm) + { + vm->SetAbsOrigin(GetAbsOrigin()); + vm->SetOwner(this); + vm->SetIndex(index); + + vm->SetModel( g_szDefaultHandsModel ); + vm->m_nSkin = g_iDefaultHandsSkin; + vm->m_nBody = g_iDefaultHandsBody; + + DispatchSpawn(vm); + vm->FollowEntity(GetViewModel(iOtherVm), true); + m_hViewModel.Set(index, vm); + vm->AddEffects( EF_NODRAW ); + } +} +#endif + //----------------------------------------------------------------------------- // Purpose: Static member function to create a player of the specified class // Input : *className - @@ -652,6 +804,7 @@ CBasePlayer::CBasePlayer( ) m_nMovementTicksForUserCmdProcessingRemaining = 0; m_flLastObjectiveTime = -1.f; + m_hPostProcessCtrl.Set( NULL ); } CBasePlayer::~CBasePlayer( ) @@ -749,9 +902,13 @@ int CBasePlayer::ShouldTransmit( const CCheckTransmitInfo *pInfo ) bool CBasePlayer::WantsLagCompensationOnEntity( const CBasePlayer *pPlayer, const CUserCmd *pCmd, const CBitVec *pEntityTransmitBits ) const { - // Team members shouldn't be adjusted unless friendly fire is on. - if ( !friendlyfire.GetInt() && pPlayer->GetTeamNumber() == GetTeamNumber() ) - return false; + //Tony; only check teams in teamplay + if ( gpGlobals->teamplay ) + { + // Team members shouldn't be adjusted unless friendly fire is on. + if ( !friendlyfire.GetInt() && pPlayer->GetTeamNumber() == GetTeamNumber() ) + return false; + } // If this entity hasn't been transmitted to us and acked, then don't bother lag compensating it. if ( pEntityTransmitBits && !pEntityTransmitBits->Get( pPlayer->entindex() ) ) @@ -928,7 +1085,11 @@ void CBasePlayer::TraceAttack( const CTakeDamageInfo &inputInfo, const Vector &v // If an NPC check if friendly fire is disallowed // -------------------------------------------------- CAI_BaseNPC *pNPC = info.GetAttacker()->MyNPCPointer(); +#ifdef MAPBASE + if ( pNPC && (pNPC->CapabilitiesGet() & bits_CAP_NO_HIT_PLAYER) && pNPC->IRelationType( this ) > D_FR ) +#else if ( pNPC && (pNPC->CapabilitiesGet() & bits_CAP_NO_HIT_PLAYER) && pNPC->IRelationType( this ) != D_HT ) +#endif return; // Prevent team damage here so blood doesn't appear @@ -967,10 +1128,22 @@ void CBasePlayer::TraceAttack( const CTakeDamageInfo &inputInfo, const Vector &v break; } +#ifdef MAPBASE + + // Damage filter bleed control needs to exist on all DLLs + bool bShouldBleed = +#ifdef HL2_EPISODIC + !g_pGameRules->Damage_ShouldNotBleed( info.GetDamageType() ) && +#endif + DamageFilterAllowsBlood( info ); + + if ( bShouldBleed ) +#else #ifdef HL2_EPISODIC // If this damage type makes us bleed, then do so bool bShouldBleed = !g_pGameRules->Damage_ShouldNotBleed( info.GetDamageType() ); if ( bShouldBleed ) +#endif #endif { SpawnBlood(ptr->endpos, vecDir, BloodColor(), info.GetDamage());// a little surface blood. @@ -1574,6 +1747,15 @@ void CBasePlayer::RemoveAllItems( bool removeSuit ) RemoveAllWeapons(); RemoveAllAmmo(); +#ifdef MAPBASE + // Hide hand viewmodel + CBaseViewModel *vm = GetViewModel( 1 ); + if ( vm ) + { + vm->AddEffects( EF_NODRAW ); + } +#endif + if ( removeSuit ) { RemoveSuit(); @@ -1582,9 +1764,10 @@ void CBasePlayer::RemoveAllItems( bool removeSuit ) UpdateClientData(); } +//Tony; correct this for base code so that IsDead will be correct accross all games. bool CBasePlayer::IsDead() const { - return m_lifeState == LIFE_DEAD; + return m_lifeState != LIFE_ALIVE; } static float DamageForce( const Vector &size, float damage ) @@ -2890,6 +3073,10 @@ float CBasePlayer::GetHeldObjectMass( IPhysicsObject *pHeldObject ) return 0; } +CBaseEntity *CBasePlayer::GetHeldObject( void ) +{ + return NULL; +} //----------------------------------------------------------------------------- // Purpose: Server side of jumping rules. Most jumping logic is already @@ -3817,6 +4004,20 @@ void CBasePlayer::PlayerRunCommand(CUserCmd *ucmd, IMoveHelper *moveHelper) } } } + +#ifdef MAPBASE_VSCRIPT + // Movement hook for VScript + if (m_ScriptScope.IsInitialized() && g_Hook_PlayerRunCommand.CanRunInScope(m_ScriptScope)) + { + HSCRIPT hCmd = g_pScriptVM->RegisterInstance( reinterpret_cast(ucmd) ); + + // command + ScriptVariant_t args[] = { hCmd }; + g_Hook_PlayerRunCommand.Call( m_ScriptScope, NULL, args ); + + g_pScriptVM->RemoveInstance( hCmd ); + } +#endif PlayerMove()->RunCommand(this, ucmd, moveHelper); } @@ -4167,6 +4368,12 @@ void CBasePlayer::CheckTimeBasedDamage() case itbd_Acid: // OnTakeDamage(pev, pev, ACID_DAMAGE, DMG_GENERIC); bDuration = ACID_DURATION; +#ifdef MAPBASE + // Prevents ant workers from inducing the Flash Plague, flashing the player's screen every time they take damage henceforth. + // I think people came up with a different name, but I can't bother to look for it right now. + // This fix might prevent other acid damage stuff as well, so it's not episodic-exclusive. + m_bitsDamageType &= ~(DMG_ACID); +#endif break; case itbd_SlowBurn: // OnTakeDamage(pev, pev, SLOWBURN_DAMAGE, DMG_GENERIC); @@ -4287,6 +4494,12 @@ void CBasePlayer::UpdateGeigerCounter( void ) range = clamp( (int)range * 4, 0, 255 ); } +#ifdef MAPBASE + // If the geiger is disabled, just use 255 + if (HasSpawnFlags(SF_PLAYER_NO_GEIGER)) + range = 255; +#endif + if (range != m_igeigerRangePrev) { m_igeigerRangePrev = range; @@ -4633,6 +4846,55 @@ void CBasePlayer::ForceOrigin( const Vector &vecOrigin ) m_vForcedOrigin = vecOrigin; } +#ifdef MAPBASE // From Alien Swarm SDK +//-------------------------------------------------------------------------------------------------------- +void CBasePlayer::OnTonemapTriggerStartTouch( CTonemapTrigger *pTonemapTrigger ) +{ + m_hTriggerTonemapList.FindAndRemove( pTonemapTrigger ); + m_hTriggerTonemapList.AddToTail( pTonemapTrigger ); +} + + +//-------------------------------------------------------------------------------------------------------- +void CBasePlayer::OnTonemapTriggerEndTouch( CTonemapTrigger *pTonemapTrigger ) +{ + m_hTriggerTonemapList.FindAndRemove( pTonemapTrigger ); +} + + +//-------------------------------------------------------------------------------------------------------- +void CBasePlayer::UpdateTonemapController( void ) +{ + // For now, Mapbase uses Tony Sergi's Source 2007 tonemap fixes. + // Alien Swarm SDK tonemap controller code copies the parameters instead. + + CEnvTonemapController *pController = NULL; + + if (m_hTriggerTonemapList.Count() > 0) + { + pController = static_cast(m_hTriggerTonemapList.Tail()->GetTonemapController()); + } + else if (TheTonemapSystem()->GetMasterTonemapController()) + { + pController = static_cast(TheTonemapSystem()->GetMasterTonemapController()); + } + + if (pController) + { + //m_hTonemapController = TheTonemapSystem()->GetMasterTonemapController(); + + if (pController->m_bUseCustomAutoExposureMax) + m_Local.m_TonemapParams.m_flAutoExposureMax = pController->m_flCustomAutoExposureMax; + + if (pController->m_bUseCustomAutoExposureMin) + m_Local.m_TonemapParams.m_flAutoExposureMin = pController->m_flCustomAutoExposureMin; + + if (pController->m_bUseCustomBloomScale) + m_Local.m_TonemapParams.m_flBloomScale = pController->m_flCustomBloomScale; + } +} +#endif + //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- @@ -4646,6 +4908,11 @@ void CBasePlayer::PostThink() m_vecSmoothedVelocity = m_vecSmoothedVelocity * SMOOTHING_FACTOR + GetAbsVelocity() * ( 1 - SMOOTHING_FACTOR ); +#ifdef MAPBASE // From Alien Swarm SDK + UpdateTonemapController(); + UpdateFXVolume(); +#endif + if ( !g_fGameOver && !m_iPlayerLocked ) { if ( IsAlive() ) @@ -5022,6 +5289,55 @@ void CBasePlayer::InitialSpawn( void ) gamestats->Event_PlayerConnected( this ); } +//----------------------------------------------------------------------------- +// Purpose: clear our m_Local.m_TonemapParams to -1. +//----------------------------------------------------------------------------- +void CBasePlayer::ClearTonemapParams( void ) +{ + //Tony; clear all the variables to -1.0 + m_Local.m_TonemapParams.m_flAutoExposureMin = -1.0f; + m_Local.m_TonemapParams.m_flAutoExposureMax = -1.0f; + m_Local.m_TonemapParams.m_flTonemapScale = -1.0f; + m_Local.m_TonemapParams.m_flBloomScale = -1.0f; + m_Local.m_TonemapParams.m_flTonemapRate = -1.0f; +} +void CBasePlayer::InputSetTonemapScale( inputdata_t &inputdata ) +{ + m_Local.m_TonemapParams.m_flTonemapScale = inputdata.value.Float(); +} + +void CBasePlayer::InputSetTonemapRate( inputdata_t &inputdata ) +{ + m_Local.m_TonemapParams.m_flTonemapRate = inputdata.value.Float(); +} +void CBasePlayer::InputSetAutoExposureMin( inputdata_t &inputdata ) +{ + m_Local.m_TonemapParams.m_flAutoExposureMin = inputdata.value.Float(); +} + +void CBasePlayer::InputSetAutoExposureMax( inputdata_t &inputdata ) +{ + m_Local.m_TonemapParams.m_flAutoExposureMax = inputdata.value.Float(); +} + +void CBasePlayer::InputSetBloomScale( inputdata_t &inputdata ) +{ + m_Local.m_TonemapParams.m_flBloomScale = inputdata.value.Float(); +} + +//Tony; restore defaults (set min/max to -1.0 so nothing gets overridden) +void CBasePlayer::InputUseDefaultAutoExposure( inputdata_t &inputdata ) +{ + m_Local.m_TonemapParams.m_flAutoExposureMin = -1.0f; + m_Local.m_TonemapParams.m_flAutoExposureMax = -1.0f; + m_Local.m_TonemapParams.m_flTonemapRate = -1.0f; +} +void CBasePlayer::InputUseDefaultBloomScale( inputdata_t &inputdata ) +{ + m_Local.m_TonemapParams.m_flBloomScale = -1.0f; +} +// void InputSetBloomScaleRange( inputdata_t &inputdata ); + //----------------------------------------------------------------------------- // Purpose: Called everytime the player respawns //----------------------------------------------------------------------------- @@ -5033,6 +5349,9 @@ void CBasePlayer::Spawn( void ) Hints()->ResetHints(); } + //Tony; make sure tonemap params is cleared. + ClearTonemapParams(); + SetClassname( "player" ); // Shared spawning code.. @@ -5070,6 +5389,9 @@ void CBasePlayer::Spawn( void ) // Initialize the fog and postprocess controllers. InitFogController(); +#ifdef MAPBASE // From Alien Swarm SDK + InitPostProcessController(); +#endif m_DmgTake = 0; m_DmgSave = 0; @@ -5094,7 +5416,12 @@ void CBasePlayer::Spawn( void ) if ( !m_fGameHUDInitialized ) g_pGameRules->SetDefaultPlayerTeam( this ); +#ifdef MAPBASE + CBaseEntity *pSpawnPoint = g_pGameRules->GetPlayerSpawnSpot( this ); + SpawnedAtPoint( pSpawnPoint ); +#else g_pGameRules->GetPlayerSpawnSpot( this ); +#endif m_Local.m_bDucked = false;// This will persist over round restart if you hold duck otherwise. m_Local.m_bDucking = false; @@ -5130,6 +5457,9 @@ void CBasePlayer::Spawn( void ) enginesound->SetPlayerDSP( user, 0, false ); CreateViewModel(); +#ifdef MAPBASE + CreateHandModel(); +#endif SetCollisionGroup( COLLISION_GROUP_PLAYER ); @@ -5161,6 +5491,11 @@ void CBasePlayer::Spawn( void ) m_vecSmoothedVelocity = vec3_origin; InitVCollision( GetAbsOrigin(), GetAbsVelocity() ); + if ( !g_pGameRules->IsMultiplayer() && g_pScriptVM ) + { + g_pScriptVM->SetValue( "player", GetScriptInstance() ); + } + #if !defined( TF_DLL ) IGameEvent *event = gameeventmanager->CreateEvent( "player_spawn" ); @@ -5262,6 +5597,10 @@ void CBasePlayer::Precache( void ) m_iTrain = TRAIN_NEW; #endif +#ifdef MAPBASE + PrecacheModel( g_szDefaultHandsModel ); +#endif + m_iClientBattery = -1; m_iUpdateTime = 5; // won't update for 1/2 a second @@ -5381,7 +5720,10 @@ void CBasePlayer::OnRestore( void ) m_nBodyPitchPoseParam = LookupPoseParameter( "body_pitch" ); - if ( gpGlobals->eLoadType == MapLoad_Transition ) + // HACK: (03/25/09) Then the player goes across a transition it doesn't spawn and register + // it's instance. We're hacking around this for now, but this will go away when we get around to + // having entities cross transitions and keep their script state. + if ( !g_pGameRules->IsMultiplayer() && g_pScriptVM && (gpGlobals->eLoadType == MapLoad_Transition) ) { g_pScriptVM->SetValue( "player", GetScriptInstance() ); } @@ -5865,6 +6207,25 @@ CBaseEntity *CBasePlayer::GiveNamedItem( const char *pszName, int iSubType ) DispatchSpawn( pent ); +#ifdef MAPBASE + if ( pWeapon ) + { + for (int i=0;iGetSlot() == pWeapon->GetSlot() && m_hMyWeapons[i]->GetPosition() == pWeapon->GetPosition() ) + { + // Make sure it matches the subtype + if ( m_hMyWeapons[i]->GetSubType() == iSubType ) + { + // Don't use this weapon if the slot is already occupied + UTIL_Remove( pWeapon ); + return NULL; + } + } + } + } +#endif + if ( pent != NULL && !(pent->IsMarkedForDeletion()) ) { #ifdef HL2MP @@ -6088,6 +6449,10 @@ void CBasePlayer::ImpulseCommands( ) CBaseCombatWeapon *pWeapon; pWeapon = GetActiveWeapon(); +#ifdef MAPBASE + if (!pWeapon) + return; +#endif if( pWeapon->IsEffectActive( EF_NODRAW ) ) { @@ -6215,7 +6580,12 @@ static void CreateJeep( CBasePlayer *pPlayer ) // Cheat to create a jeep in front of the player Vector vecForward; AngleVectors( pPlayer->EyeAngles(), &vecForward ); + //Tony; in sp sdk, we have prop_vehicle_hl2buggy; because episode 2 modified the jeep code to turn it into the jalopy instead of the regular buggy +#if defined ( HL2_EPISODIC ) + CBaseEntity *pJeep = (CBaseEntity *)CreateEntityByName( "prop_vehicle_hl2buggy" ); +#else CBaseEntity *pJeep = (CBaseEntity *)CreateEntityByName( "prop_vehicle_jeep" ); +#endif if ( pJeep ) { Vector vecOrigin = pPlayer->GetAbsOrigin() + vecForward * 256 + Vector(0,0,64); @@ -6224,7 +6594,11 @@ static void CreateJeep( CBasePlayer *pPlayer ) pJeep->SetAbsAngles( vecAngles ); pJeep->KeyValue( "model", "models/buggy.mdl" ); pJeep->KeyValue( "solid", "6" ); +#if defined ( HL2_EPISODIC ) + pJeep->KeyValue( "targetname", "hl2buggy" ); +#else pJeep->KeyValue( "targetname", "jeep" ); +#endif pJeep->KeyValue( "vehiclescript", "scripts/vehicles/jeep_test.txt" ); DispatchSpawn( pJeep ); pJeep->Activate(); @@ -6714,7 +7088,22 @@ bool CBasePlayer::ClientCommand( const CCommand &args ) VectorMax( origin, worldExtent.lo, origin ); VectorMin( origin, worldExtent.hi, origin ); +#ifdef MAPBASE + #define SPECGOTO_MAX_VALUE 0xFFFF/2.0f + + // This could crash the game somehow if not checked.. Thanks to Nairda. + if (abs(angle.x) <= 360.0f && abs(angle.y) <= 360.0f && abs(origin.x) < SPECGOTO_MAX_VALUE && + abs(origin.y) < SPECGOTO_MAX_VALUE && abs(origin.z) < SPECGOTO_MAX_VALUE) + { + JumptoPosition(origin, angle); + } + else + { + engine->ClientPrintf(edict(), "spec_goto: Out-of-bounds"); + } +#else JumptoPosition( origin, angle ); +#endif } } @@ -6778,7 +7167,11 @@ bool CBasePlayer::BumpWeapon( CBaseCombatWeapon *pWeapon ) else { // Don't let the player fetch weapons through walls (use MASK_SOLID so that you can't pickup through windows) +#ifdef MAPBASE + if( (pWeapon->FVisible( this, MASK_SOLID ) == false && !(GetFlags() & FL_NOTARGET)) && !HasSpawnFlags(SF_WEAPON_ALWAYS_TOUCHABLE) ) +#else if( pWeapon->FVisible( this, MASK_SOLID ) == false && !(GetFlags() & FL_NOTARGET) ) +#endif return false; } @@ -6790,7 +7183,11 @@ bool CBasePlayer::BumpWeapon( CBaseCombatWeapon *pWeapon ) if( Weapon_EquipAmmoOnly( pWeapon ) ) { // Only remove me if I have no ammo left +#ifdef MAPBASE + if ( pWeapon->HasPrimaryAmmo() || pWeapon->HasSecondaryAmmo() ) +#else if ( pWeapon->HasPrimaryAmmo() ) +#endif return false; UTIL_Remove( pWeapon ); @@ -6801,10 +7198,56 @@ bool CBasePlayer::BumpWeapon( CBaseCombatWeapon *pWeapon ) return false; } } +#ifdef MAPBASE + // -------------------------------------------------------------------------------- + // If we own a weapon in the same position take the ammo but leave the weapon behind + // -------------------------------------------------------------------------------- + if (!pWeapon->HasSpawnFlags(SF_WEAPON_USED)) // Make sure we're being used and not being bumped + { + for (int i=0;iGetSlot() == m_hMyWeapons[i]->GetSlot() && + pWeapon->GetPosition() == m_hMyWeapons[i]->GetPosition()) + { + //Weapon_EquipAmmoOnly( pWeapon ); + + // Weapon_EquipAmmoOnly checks the array again, which isn't necessary here + int primaryGiven = (pWeapon->UsesClipsForAmmo1()) ? pWeapon->m_iClip1 : pWeapon->GetPrimaryAmmoCount(); + int secondaryGiven = (pWeapon->UsesClipsForAmmo2()) ? pWeapon->m_iClip2 : pWeapon->GetSecondaryAmmoCount(); + + int takenPrimary = GiveAmmo( primaryGiven, pWeapon->m_iPrimaryAmmoType); + int takenSecondary = GiveAmmo( secondaryGiven, pWeapon->m_iSecondaryAmmoType); + + if( pWeapon->UsesClipsForAmmo1() ) + { + pWeapon->m_iClip1 -= takenPrimary; + } + else + { + pWeapon->SetPrimaryAmmoCount( pWeapon->GetPrimaryAmmoCount() - takenPrimary ); + } + + if( pWeapon->UsesClipsForAmmo2() ) + { + pWeapon->m_iClip2 -= takenSecondary; + } + else + { + pWeapon->SetSecondaryAmmoCount( pWeapon->GetSecondaryAmmoCount() - takenSecondary ); + } + + return false; + } + } + } +#endif // ------------------------- // Otherwise take the weapon // ------------------------- +#ifndef MAPBASE else +#endif { pWeapon->CheckRespawn(); @@ -6827,6 +7270,7 @@ bool CBasePlayer::BumpWeapon( CBaseCombatWeapon *pWeapon ) UTIL_HudHintText( this, hint.Access() ); } +#ifndef MAPBASE // See CBasePlayer::Weapon_Equip. // Always switch to a newly-picked up weapon if ( !PlayerHasMegaPhysCannon() ) { @@ -6838,6 +7282,7 @@ bool CBasePlayer::BumpWeapon( CBaseCombatWeapon *pWeapon ) Weapon_Switch( pWeapon ); } +#endif #endif } return true; @@ -6890,6 +7335,35 @@ void CBasePlayer::ShowCrosshair( bool bShow ) } } +#ifdef MAPBASE_VSCRIPT +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +HSCRIPT CBasePlayer::VScriptGetExpresser() +{ + HSCRIPT hScript = NULL; + CAI_Expresser *pExpresser = GetExpresser(); + if (pExpresser) + { + hScript = g_pScriptVM->RegisterInstance( pExpresser ); + } + + return hScript; +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +HSCRIPT CBasePlayer::ScriptGetViewModel( int viewmodelindex ) +{ + if (viewmodelindex < 0 || viewmodelindex >= MAX_VIEWMODELS) + { + Warning( "GetViewModel: Invalid index '%i'\n", viewmodelindex ); + return NULL; + } + + return ToHScript( GetViewModel( viewmodelindex ) ); +} +#endif + //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- @@ -7443,6 +7917,93 @@ void CBasePlayer::ResetAutoaim( void ) m_fOnTarget = false; } +#ifdef MAPBASE +ConVar player_debug_probable_aim_target( "player_debug_probable_aim_target", "0", FCVAR_CHEAT, "" ); + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CBaseEntity *CBasePlayer::GetProbableAimTarget( const Vector &vecSrc, const Vector &vecDir ) +{ + trace_t tr; + CBaseEntity *pIgnore = NULL; + if (IsInAVehicle()) + pIgnore = GetVehicleEntity(); + + CTraceFilterSkipTwoEntities traceFilter( this, pIgnore, COLLISION_GROUP_NONE ); + + // Based on dot product and distance + // If we aim directly at something, only return it if there's not a larger entity slightly off-center + // Should be weighted based on whether an entity is a NPC, etc. + CBaseEntity *pBestEnt = NULL; + float flBestWeight = 0.0f; + for (CBaseEntity *pEntity = UTIL_EntitiesInPVS( this, NULL ); pEntity; pEntity = UTIL_EntitiesInPVS( this, pEntity )) + { + // Combat characters can be unviewable if they just died + if (!pEntity->IsViewable() && !pEntity->IsCombatCharacter()) + continue; + + if (pEntity == this || pEntity->GetMoveParent() == this || pEntity == GetVehicleEntity()) + continue; + + Vector vecEntDir = (pEntity->EyePosition() - vecSrc); + float flDot = DotProduct( vecEntDir.Normalized(), vecDir); + + if (flDot < m_flFieldOfView) + continue; + + // Make sure we can see it + UTIL_TraceLine( vecSrc, pEntity->EyePosition(), MASK_SHOT, &traceFilter, &tr ); + if (tr.m_pEnt != pEntity) + { + if (pEntity->IsCombatCharacter()) + { + // Trace between centers as well just in case our eyes are blocked + UTIL_TraceLine( WorldSpaceCenter(), pEntity->WorldSpaceCenter(), MASK_SHOT, &traceFilter, &tr ); + if (tr.m_pEnt != pEntity) + continue; + } + else + continue; + } + + float flWeight = flDot - (vecEntDir.LengthSqr() / Square( 2048.0f )); + + if (pEntity->IsCombatCharacter()) + { + // Hostile NPCs are more likely targets + if (IRelationType( pEntity ) <= D_FR) + flWeight += 0.5f; + } + else if (pEntity->GetFlags() & FL_AIMTARGET) + { + // FL_AIMTARGET is often used for props like explosive barrels + flWeight += 0.25f; + } + + if (player_debug_probable_aim_target.GetBool()) + { + float flWeightClamped = 1.0f - RemapValClamped( flWeight, -2.0f, 2.0f, 0.0f, 1.0f ); + pEntity->EntityText( 0, UTIL_VarArgs( "%f", flWeight ), 2.0f, flWeightClamped * 255.0f, 255.0f, flWeightClamped * 255.0f, 255 ); + } + + if (flWeight > flBestWeight) + { + pBestEnt = pEntity; + flBestWeight = flWeight; + } + } + + if (player_debug_probable_aim_target.GetBool()) + { + Msg( "Best probable aim target is %s\n", pBestEnt->GetDebugName() ); + NDebugOverlay::EntityBounds( pBestEnt, 255, 100, 0, 0, 2.0f ); + } + + return pBestEnt; +} +#endif + // ========================================================================== // > Weapon stuff // ========================================================================== @@ -7517,6 +8078,10 @@ void CBasePlayer::Weapon_DropSlot( int weaponSlot ) } } +#ifdef MAPBASE +ConVar player_autoswitch_on_first_pickup("player_autoswitch_on_pickup", "1", FCVAR_NONE, "Determines how the player should autoswitch when picking up a new weapon. 0 = no autoswitch, 1 = always (default), 2 = use unused weighting system"); +#endif + //----------------------------------------------------------------------------- // Purpose: Override to add weapon to the hud //----------------------------------------------------------------------------- @@ -7524,7 +8089,26 @@ void CBasePlayer::Weapon_Equip( CBaseCombatWeapon *pWeapon ) { BaseClass::Weapon_Equip( pWeapon ); +#ifdef MAPBASE + // BumpWeapon's code appeared to be deprecated; The same operation is already handled here, but with much more code involved. + // There's also an unused weighting system which was overridden by that deprecated code. The unused weighting code can be enabled + // via player_autoswitch_on_first_pickup. + bool bShouldSwitch = false; + switch (player_autoswitch_on_first_pickup.GetInt()) + { + // Unused Weighting + case 2: + bShouldSwitch = g_pGameRules->FShouldSwitchWeapon( this, pWeapon ); + break; + + // Always (old behavior) + case 1: + bShouldSwitch = true; + break; + } +#else bool bShouldSwitch = g_pGameRules->FShouldSwitchWeapon( this, pWeapon ); +#endif #ifdef HL2_DLL if ( bShouldSwitch == false && PhysCannonGetHeldEntity( GetActiveWeapon() ) == pWeapon && @@ -7541,6 +8125,33 @@ void CBasePlayer::Weapon_Equip( CBaseCombatWeapon *pWeapon ) } } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +Activity CBasePlayer::Weapon_TranslateActivity( Activity baseAct, bool *pRequired ) +{ + Activity weaponTranslation = BaseClass::Weapon_TranslateActivity( baseAct, pRequired ); + + if ( GetActiveWeapon() && GetActiveWeapon()->IsEffectActive(EF_NODRAW) && baseAct != ACT_ARM ) + { + // Our weapon is holstered. Use the base activity. + return baseAct; + } + if ( GetModelPtr() && (!GetModelPtr()->HaveSequenceForActivity(weaponTranslation) || baseAct == weaponTranslation) ) + { + // This is used so players can fall back to backup activities in the same way NPCs in Mapbase can + Activity backupActivity = Weapon_BackupActivity(baseAct, pRequired ? *pRequired : false); + if ( baseAct != backupActivity && GetModelPtr()->HaveSequenceForActivity(backupActivity) ) + return backupActivity; + + return baseAct; + } + + return weaponTranslation; +} +#endif + //========================================================= // HasNamedPlayerItem Does the player already have this item? @@ -7639,6 +8250,23 @@ void CBasePlayer::PlayWearableAnimsForPlaybackEvent( wearableanimplayback_t iPla } #endif // USES_ECON_ITEMS +#ifdef MAPBASE +bool CBasePlayer::ShouldUseVisibilityCache( CBaseEntity *pEntity ) +{ + // In CBaseEntity::FVisible(), players are allowed to see through CONTENTS_BLOCKLOS, which is used for + // nodraw, block LOS brushes, etc. This is so some code doesn't erronesouly assume the player can't see + // an entity (when the player can, in fact, see it) and therefore do something the player is not supposed to see. + // + // However, to reduce the number of traces FVisible() runs, CBaseCombatCharacter uses a "visibility cache" shared + // by all entities derived from it. The player is normally a part of this visibility cache, so when it runs a trace + // through a CONTENTS_BLOCKLOS surface, the visibility cache assumes entities can now see through it and therefore + // NPCs to see through the brush which should normally block their LOS. + // + // This solution stops the player from using the visibility cache altogether, toggled by a convar. + return player_use_visibility_cache.GetBool(); +} +#endif + //================================================================================ // TEAM HANDLING //================================================================================ @@ -7771,6 +8399,7 @@ BEGIN_DATADESC( CStripWeapons ) DEFINE_INPUTFUNC( FIELD_VOID, "StripWeaponsAndSuit", InputStripWeaponsAndSuit ), END_DATADESC() +#ifndef MAPBASE_VSCRIPT BEGIN_ENT_SCRIPTDESC( CBasePlayer, CBaseCombatCharacter, "The player entity." ) DEFINE_SCRIPTFUNC_NAMED( ScriptIsPlayerNoclipping, "IsNoclipping", "Returns true if the player is in noclip mode." ) DEFINE_SCRIPTFUNC( ViewPunch, "Ow! Punches the player's view" ) @@ -7783,6 +8412,7 @@ BEGIN_ENT_SCRIPTDESC( CBasePlayer, CBaseCombatCharacter, "The player entity." ) DEFINE_SCRIPTFUNC( GetScriptOverlayMaterial, "Gets the current view overlay material" ) DEFINE_SCRIPTFUNC( SetScriptOverlayMaterial, "Sets a view overlay material" ) END_SCRIPTDESC(); +#endif void CStripWeapons::InputStripWeapons(inputdata_t &data) { @@ -7972,6 +8602,11 @@ void CRevertSaved::LoadThink( void ) #define SF_SPEED_MOD_SUPPRESS_SPEED (1<<5) #define SF_SPEED_MOD_SUPPRESS_ATTACK (1<<6) #define SF_SPEED_MOD_SUPPRESS_ZOOM (1<<7) +#ifdef MAPBASE +// Needs to be inverse because suppressing the flashlight is already default behavior +// and we don't want to break compatibility for existing speedmods +#define SF_SPEED_MOD_DONT_SUPPRESS_FLASHLIGHT (1<<8) +#endif class CMovementSpeedMod : public CPointEntity { @@ -7979,9 +8614,20 @@ class CMovementSpeedMod : public CPointEntity public: void InputSpeedMod(inputdata_t &data); +#ifdef MAPBASE + void InputEnable(inputdata_t &data); + void InputDisable(inputdata_t &data); + + void InputSetAdditionalButtons(inputdata_t &data); +#endif + private: int GetDisabledButtonMask( void ); +#ifdef MAPBASE + int m_iAdditionalButtons; +#endif + DECLARE_DATADESC(); }; @@ -7989,6 +8635,13 @@ LINK_ENTITY_TO_CLASS( player_speedmod, CMovementSpeedMod ); BEGIN_DATADESC( CMovementSpeedMod ) DEFINE_INPUTFUNC( FIELD_FLOAT, "ModifySpeed", InputSpeedMod ), +#ifdef MAPBASE + DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ), + DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ), + + DEFINE_KEYFIELD( m_iAdditionalButtons, FIELD_INTEGER, "AdditionalButtons" ), + DEFINE_INPUTFUNC( FIELD_INTEGER, "SetAdditionalButtons", InputSetAdditionalButtons ), +#endif END_DATADESC() int CMovementSpeedMod::GetDisabledButtonMask( void ) @@ -8025,6 +8678,13 @@ int CMovementSpeedMod::GetDisabledButtonMask( void ) nMask |= IN_ZOOM; } +#ifdef MAPBASE + if ( m_iAdditionalButtons != 0 ) + { + nMask |= m_iAdditionalButtons; + } +#endif + return nMask; } @@ -8058,6 +8718,10 @@ void CMovementSpeedMod::InputSpeedMod(inputdata_t &data) pPlayer->HideViewModels(); } +#ifdef MAPBASE + if ( !HasSpawnFlags( SF_SPEED_MOD_DONT_SUPPRESS_FLASHLIGHT ) ) + { +#endif // Turn off the flashlight if ( pPlayer->FlashlightIsOn() ) { @@ -8066,6 +8730,9 @@ void CMovementSpeedMod::InputSpeedMod(inputdata_t &data) // Disable the flashlight's further use pPlayer->SetFlashlightEnabled( false ); +#ifdef MAPBASE + } +#endif pPlayer->DisableButtons( GetDisabledButtonMask() ); // Hide the HUD @@ -8086,8 +8753,15 @@ void CMovementSpeedMod::InputSpeedMod(inputdata_t &data) } } +#ifdef MAPBASE + if ( !HasSpawnFlags( SF_SPEED_MOD_DONT_SUPPRESS_FLASHLIGHT ) ) + { +#endif // Allow the flashlight again pPlayer->SetFlashlightEnabled( true ); +#ifdef MAPBASE + } +#endif pPlayer->EnableButtons( GetDisabledButtonMask() ); // Restore the HUD @@ -8101,6 +8775,205 @@ void CMovementSpeedMod::InputSpeedMod(inputdata_t &data) } } +#ifdef MAPBASE +void CMovementSpeedMod::InputEnable(inputdata_t &data) +{ + CBasePlayer *pPlayer = NULL; + + if ( data.pActivator && data.pActivator->IsPlayer() ) + { + pPlayer = (CBasePlayer *)data.pActivator; + } + else if ( !g_pGameRules->IsDeathmatch() ) + { + pPlayer = UTIL_GetLocalPlayer(); + } + + if ( pPlayer ) + { + // Holster weapon immediately, to allow it to cleanup + if ( HasSpawnFlags( SF_SPEED_MOD_SUPPRESS_WEAPONS ) ) + { + if ( pPlayer->GetActiveWeapon() ) + { + pPlayer->Weapon_SetLast( pPlayer->GetActiveWeapon() ); + pPlayer->GetActiveWeapon()->Holster(); + pPlayer->ClearActiveWeapon(); + } + + pPlayer->HideViewModels(); + } + + // Turn off the flashlight + if ( pPlayer->FlashlightIsOn() ) + { + pPlayer->FlashlightTurnOff(); + } + + // Disable the flashlight's further use + pPlayer->SetFlashlightEnabled( false ); + pPlayer->DisableButtons( GetDisabledButtonMask() ); + + // Hide the HUD + if ( HasSpawnFlags( SF_SPEED_MOD_SUPPRESS_HUD ) ) + { + pPlayer->m_Local.m_iHideHUD |= HIDEHUD_ALL; + } + } +} + +void CMovementSpeedMod::InputDisable(inputdata_t &data) +{ + CBasePlayer *pPlayer = NULL; + + if ( data.pActivator && data.pActivator->IsPlayer() ) + { + pPlayer = (CBasePlayer *)data.pActivator; + } + else if ( !g_pGameRules->IsDeathmatch() ) + { + pPlayer = UTIL_GetLocalPlayer(); + } + + if ( pPlayer ) + { + // Bring the weapon back + if ( HasSpawnFlags( SF_SPEED_MOD_SUPPRESS_WEAPONS ) && pPlayer->GetActiveWeapon() == NULL ) + { + pPlayer->SetActiveWeapon( pPlayer->GetLastWeapon() ); + if ( pPlayer->GetActiveWeapon() ) + { + pPlayer->GetActiveWeapon()->Deploy(); + } + } + + // Allow the flashlight again + pPlayer->SetFlashlightEnabled( true ); + pPlayer->EnableButtons( GetDisabledButtonMask() ); + + // Restore the HUD + if ( HasSpawnFlags( SF_SPEED_MOD_SUPPRESS_HUD ) ) + { + pPlayer->m_Local.m_iHideHUD &= ~HIDEHUD_ALL; + } + } +} + +void CMovementSpeedMod::InputSetAdditionalButtons(inputdata_t &data) +{ + CBasePlayer *pPlayer = NULL; + + if ( data.pActivator && data.pActivator->IsPlayer() ) + { + pPlayer = (CBasePlayer *)data.pActivator; + } + else if ( !g_pGameRules->IsDeathmatch() ) + { + pPlayer = UTIL_GetLocalPlayer(); + } + + bool bAlreadyDisabled = false; + if ( pPlayer ) + { + bAlreadyDisabled = (pPlayer->m_afButtonDisabled & GetDisabledButtonMask()) != 0; + } + + m_iAdditionalButtons = data.value.Int(); + + // If we were already disabling buttons, re-disable them + if ( bAlreadyDisabled ) + { + // We should probably do something better than this. + pPlayer->m_afButtonForced = GetDisabledButtonMask(); + } +} +#endif + +#ifdef MAPBASE +class CLogicPlayerInfo : public CPointEntity +{ + DECLARE_CLASS( CLogicPlayerInfo, CPointEntity ); +public: + void InputGetPlayerInfo( inputdata_t &inputdata ); + void InputGetPlayerByID( inputdata_t &inputdata ); + void InputGetPlayerByName( inputdata_t &inputdata ); + + void GetPlayerInfo( CBasePlayer *pPlayer ); + + COutputInt m_OutUserID; + COutputString m_OutPlayerName; + COutputEHANDLE m_OutPlayerEntity; + + DECLARE_DATADESC(); +}; + +LINK_ENTITY_TO_CLASS( logic_playerinfo, CLogicPlayerInfo ); + +BEGIN_DATADESC( CLogicPlayerInfo ) + DEFINE_INPUTFUNC( FIELD_EHANDLE, "GetPlayerInfo", InputGetPlayerInfo ), + DEFINE_INPUTFUNC( FIELD_STRING, "GetPlayerByID", InputGetPlayerByID ), + DEFINE_INPUTFUNC( FIELD_STRING, "GetPlayerByName", InputGetPlayerByName ), + + DEFINE_OUTPUT( m_OutUserID, "OutUserID" ), + DEFINE_OUTPUT( m_OutPlayerName, "OutPlayerName" ), + DEFINE_OUTPUT( m_OutPlayerEntity, "OutPlayerEntity" ), +END_DATADESC() + + +void CLogicPlayerInfo::InputGetPlayerInfo( inputdata_t &inputdata ) +{ + CBasePlayer *pPlayer = ToBasePlayer(inputdata.value.Entity()); + + // If there was no entity to begin with, try the local player + if (!pPlayer && !inputdata.value.Entity()) + pPlayer = UTIL_GetLocalPlayer(); + + if (pPlayer) + GetPlayerInfo( pPlayer ); +} + +void CLogicPlayerInfo::InputGetPlayerByID( inputdata_t &inputdata ) +{ + for (int i = 1; i < gpGlobals->maxClients; i++) + { + CBasePlayer *pPlayer = UTIL_PlayerByIndex( i ); + if (pPlayer) + { + if (Matcher_NamesMatch( inputdata.value.String(), UTIL_VarArgs("%i", pPlayer->GetUserID()) )) + { + GetPlayerInfo( pPlayer ); + return; + } + } + } +} + +void CLogicPlayerInfo::InputGetPlayerByName( inputdata_t &inputdata ) +{ + for (int i = 1; i < gpGlobals->maxClients; i++) + { + CBasePlayer *pPlayer = UTIL_PlayerByIndex( i ); + if (pPlayer) + { + if (Matcher_NamesMatch( inputdata.value.String(), pPlayer->GetPlayerName() )) + { + GetPlayerInfo( pPlayer ); + return; + } + } + } +} + +void CLogicPlayerInfo::GetPlayerInfo( CBasePlayer *pPlayer ) +{ + m_OutUserID.Set( pPlayer->GetUserID(), pPlayer, this ); + + m_OutPlayerName.Set( AllocPooledString(pPlayer->GetPlayerName()), pPlayer, this ); + + m_OutPlayerEntity.Set( pPlayer, pPlayer, this ); +} +#endif + void SendProxy_CropFlagsToPlayerFlagBitsLength( const SendProp *pProp, const void *pStruct, const void *pVarData, DVariant *pOut, int iElement, int objectID) { @@ -8109,6 +8982,17 @@ void SendProxy_CropFlagsToPlayerFlagBitsLength( const SendProp *pProp, const voi pOut->m_Int = ( data & mask ); } + +#ifdef MAPBASE +// Needs to shift bits since network table only sends the player ones +void SendProxy_ShiftPlayerSpawnflags( const SendProp *pProp, const void *pStruct, const void *pVarData, DVariant *pOut, int iElement, int objectID ) +{ + int *pInt = (int *)pVarData; + + pOut->m_Int = (*pInt) >> 16; +} +#endif + // -------------------------------------------------------------------------------- // // SendTable for CPlayerState. // -------------------------------------------------------------------------------- // @@ -8161,6 +9045,15 @@ void SendProxy_CropFlagsToPlayerFlagBitsLength( const SendProp *pProp, const voi SendPropInt ( SENDINFO( m_nWaterLevel ), 2, SPROP_UNSIGNED ), SendPropFloat ( SENDINFO( m_flLaggedMovementValue ), 0, SPROP_NOSCALE ), +#ifdef MAPBASE + // Transmitted from the server for internal player spawnflags. + // See baseplayer_shared.h for more details. + SendPropInt ( SENDINFO( m_spawnflags ), 3, SPROP_UNSIGNED, SendProxy_ShiftPlayerSpawnflags ), + + SendPropBool ( SENDINFO( m_bDrawPlayerModelExternally ) ), + SendPropBool ( SENDINFO( m_bInTriggerFall ) ), +#endif + END_SEND_TABLE() @@ -8198,6 +9091,12 @@ void SendProxy_CropFlagsToPlayerFlagBitsLength( const SendProp *pProp, const voi SendPropArray ( SendPropEHandle( SENDINFO_ARRAY( m_hViewModel ) ), m_hViewModel ), SendPropString (SENDINFO(m_szLastPlaceName) ), +#ifdef MAPBASE // From Alien Swarm SDK + // Postprocess data + SendPropEHandle ( SENDINFO(m_hPostProcessCtrl) ), + SendPropEHandle ( SENDINFO(m_hColorCorrectionCtrl) ), +#endif + #if defined USES_ECON_ITEMS SendPropUtlVector( SENDINFO_UTLVECTOR( m_hMyWearables ), MAX_WEARABLES_SENT_FROM_SERVER, SendPropEHandle( NULL, 0 ) ), #endif // USES_ECON_ITEMS @@ -8689,6 +9588,18 @@ void CBasePlayer::SetDefaultFOV( int FOV ) m_iDefaultFOV = ( FOV == 0 ) ? g_pGameRules->DefaultFOV() : FOV; } +#ifdef MAPBASE_VSCRIPT +void CBasePlayer::ScriptSetFOV(int iFOV, float flRate) +{ + m_iFOVStart = GetFOV(); + + m_flFOVTime = gpGlobals->curtime; + m_iFOV = iFOV; + + m_Local.m_flFOVRate = flRate; +} +#endif + //----------------------------------------------------------------------------- // Purpose: // static func // Input : set - @@ -8932,6 +9843,19 @@ void CBasePlayer::InputSetHUDVisibility( inputdata_t &inputdata ) } } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: +// Input : &inputdata - +//----------------------------------------------------------------------------- +void CBasePlayer::InputSetSuppressAttacks( inputdata_t &inputdata ) +{ + inputdata.value.Bool() ? + AddSpawnFlags( SF_PLAYER_SUPPRESS_FIRING ) : + RemoveSpawnFlags( SF_PLAYER_SUPPRESS_FIRING ); +} +#endif + //----------------------------------------------------------------------------- // Purpose: Set the fog controller data per player. // Input : &inputdata - @@ -8939,7 +9863,19 @@ void CBasePlayer::InputSetHUDVisibility( inputdata_t &inputdata ) void CBasePlayer::InputSetFogController( inputdata_t &inputdata ) { // Find the fog controller with the given name. +#ifdef MAPBASE // From Alien Swarm SDK + CFogController *pFogController = NULL; + if ( inputdata.value.FieldType() == FIELD_EHANDLE ) + { + pFogController = dynamic_cast( inputdata.value.Entity().Get() ); + } + else + { + pFogController = dynamic_cast( gEntList.FindEntityByName( NULL, inputdata.value.String() ) ); + } +#else CFogController *pFogController = dynamic_cast( gEntList.FindEntityByName( NULL, inputdata.value.String() ) ); +#endif if ( pFogController ) { m_Local.m_PlayerFog.m_hCtrl.Set( pFogController ); @@ -8955,6 +9891,70 @@ void CBasePlayer::InitFogController( void ) m_Local.m_PlayerFog.m_hCtrl = FogSystem()->GetMasterFogController(); } +#ifdef MAPBASE // From Alien Swarm SDK +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CBasePlayer::InitPostProcessController( void ) +{ + // Setup with the default master controller. + m_hPostProcessCtrl = PostProcessSystem()->GetMasterPostProcessController(); +} + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CBasePlayer::InitColorCorrectionController( void ) +{ + m_hColorCorrectionCtrl = ColorCorrectionSystem()->GetMasterColorCorrection(); +} + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CBasePlayer::InputSetPostProcessController( inputdata_t &inputdata ) +{ + // Find the fog controller with the given name. + CPostProcessController *pController = NULL; + if ( inputdata.value.FieldType() == FIELD_EHANDLE ) + { + pController = dynamic_cast( inputdata.value.Entity().Get() ); + } + else + { + pController = dynamic_cast( gEntList.FindEntityByName( NULL, inputdata.value.String() ) ); + } + + if ( pController ) + { + m_hPostProcessCtrl.Set( pController ); + } +} + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CBasePlayer::InputSetColorCorrectionController( inputdata_t &inputdata ) +{ + // Find the fog controller with the given name. + CColorCorrection *pController = NULL; + if ( inputdata.value.FieldType() == FIELD_EHANDLE ) + { + pController = dynamic_cast( inputdata.value.Entity().Get() ); + } + else + { + pController = dynamic_cast( gEntList.FindEntityByName( NULL, inputdata.value.String() ) ); + } + + if ( pController ) + { + m_hColorCorrectionCtrl.Set( pController ); + } + +} +#endif + //----------------------------------------------------------------------------- // Purpose: // Input : *pEntity - @@ -9122,6 +10122,14 @@ bool CBasePlayer::HandleVoteCommands( const CCommand &args ) //----------------------------------------------------------------------------- const char *CBasePlayer::GetNetworkIDString() { + //Tony; bots don't have network id's, and this can potentially crash, especially with plugins creating them. + if (IsBot()) + return "__BOT__"; + + //Tony; if networkidstring is null for any reason, the strncpy will crash! + if (!m_szNetworkIDString) + return "NULLID"; + const char *pStr = engine->GetPlayerNetworkIDString( edict() ); Q_strncpy( m_szNetworkIDString, pStr ? pStr : "", sizeof(m_szNetworkIDString) ); return m_szNetworkIDString; @@ -9686,3 +10694,69 @@ void* SendProxy_SendNonLocalDataTable( const SendProp *pProp, const void *pStruc } REGISTER_SEND_PROXY_NON_MODIFIED_POINTER( SendProxy_SendNonLocalDataTable ); +#ifdef MAPBASE // From Alien Swarm SDK +//-------------------------------------------------------------------------------------------------------- +void CBasePlayer::UpdateFXVolume( void ) +{ + CFogController *pFogController = NULL; + CPostProcessController *pPostProcessController = NULL; + CColorCorrection* pColorCorrectionEnt = NULL; + + Vector eyePos; + CBaseEntity *pViewEntity = GetViewEntity(); + if ( pViewEntity ) + { + eyePos = pViewEntity->GetAbsOrigin(); + } + else + { + eyePos = EyePosition(); + } + + CFogVolume *pFogVolume = CFogVolume::FindFogVolumeForPosition( eyePos ); + if ( pFogVolume ) + { + pFogController = pFogVolume->GetFogController(); + pPostProcessController = pFogVolume->GetPostProcessController(); + pColorCorrectionEnt = pFogVolume->GetColorCorrectionController(); + + if ( !pFogController ) + { + pFogController = FogSystem()->GetMasterFogController(); + } + + if ( !pPostProcessController ) + { + pPostProcessController = PostProcessSystem()->GetMasterPostProcessController(); + } + + if ( !pColorCorrectionEnt ) + { + pColorCorrectionEnt = ColorCorrectionSystem()->GetMasterColorCorrection(); + } + } + else if ( TheFogVolumes.Count() > 0 ) + { + // If we're not in a fog volume, clear our fog volume, if the map has any. + // This will get us back to using the master fog controller. + pFogController = FogSystem()->GetMasterFogController(); + pPostProcessController = PostProcessSystem()->GetMasterPostProcessController(); + pColorCorrectionEnt = ColorCorrectionSystem()->GetMasterColorCorrection(); + } + + if ( pFogController && m_Local.m_PlayerFog.m_hCtrl.Get() != pFogController ) + { + m_Local.m_PlayerFog.m_hCtrl.Set( pFogController ); + } + + if ( pPostProcessController ) + { + m_hPostProcessCtrl.Set( pPostProcessController ); + } + + if ( pColorCorrectionEnt ) + { + m_hColorCorrectionCtrl.Set( pColorCorrectionEnt ); + } +} +#endif diff --git a/src/game/server/player.h b/src/game/server/player.h index 902e0e62..dbdaa16c 100644 --- a/src/game/server/player.h +++ b/src/game/server/player.h @@ -89,6 +89,10 @@ class CHintSystem; class CAI_Expresser; class CVoteController; +#ifdef MAPBASE // From Alien Swarm SDK +class CTonemapTrigger; +#endif + #if defined USES_ECON_ITEMS class CEconWearable; #endif // USES_ECON_ITEMS @@ -234,7 +238,6 @@ private: CBasePlayer *m_pParent; }; - class CBasePlayer : public CBaseCombatCharacter { public: @@ -268,6 +271,10 @@ public: void HideViewModels( void ); void DestroyViewModels( void ); +#ifdef MAPBASE + virtual void CreateHandModel( int viewmodelindex = 1, int iOtherVm = 0 ); +#endif + CPlayerState *PlayerData( void ) { return &pl; } int RequiredEdictIndex( void ) { return ENTINDEX(edict()); } @@ -292,6 +299,11 @@ public: virtual void SharedSpawn(); // Shared between client and server. virtual void ForceRespawn( void ); +#ifdef MAPBASE + // For the logic_playerproxy output + virtual void SpawnedAtPoint( CBaseEntity *pSpawnPoint ) {} +#endif + virtual void InitialSpawn( void ); virtual void InitHUD( void ) {} virtual void ShowViewPortPanel( const char * name, bool bShow = true, KeyValues *data = NULL ); @@ -399,6 +411,26 @@ public: return m_Local.m_bForceLocalPlayerDraw; } +#ifdef MAPBASE_VSCRIPT + HSCRIPT VScriptGetExpresser(); + + int GetButtons() { return m_nButtons; } + int GetButtonPressed() { return m_afButtonPressed; } + int GetButtonReleased() { return m_afButtonReleased; } + int GetButtonLast() { return m_afButtonLast; } + int GetButtonDisabled() { return m_afButtonDisabled; } + int GetButtonForced() { return m_afButtonForced; } + + const Vector& ScriptGetEyeForward() { static Vector vecForward; EyeVectors( &vecForward, NULL, NULL ); return vecForward; } + const Vector& ScriptGetEyeRight() { static Vector vecRight; EyeVectors( NULL, &vecRight, NULL ); return vecRight; } + const Vector& ScriptGetEyeUp() { static Vector vecUp; EyeVectors( NULL, NULL, &vecUp ); return vecUp; } + + HSCRIPT ScriptGetViewModel( int viewmodelindex ); + + HSCRIPT ScriptGetUseEntity() { return ToHScript( GetUseEntity() ); } + HSCRIPT ScriptGetHeldObject() { return ToHScript( GetHeldObject() ); } +#endif + // View model prediction setup void CalcView( Vector &eyeOrigin, QAngle &eyeAngles, float &zNear, float &zFar, float &fov ); @@ -432,6 +464,9 @@ public: virtual bool Weapon_ShouldSelectItem( CBaseCombatWeapon *pWeapon ); void Weapon_DropSlot( int weaponSlot ); CBaseCombatWeapon *GetLastWeapon( void ) { return m_hLastWeapon.Get(); } +#ifdef MAPBASE + virtual Activity Weapon_TranslateActivity( Activity baseAct, bool *pRequired = NULL ); +#endif virtual void OnMyWeaponFired( CBaseCombatWeapon *weapon ); // call this when this player fires a weapon to allow other systems to react virtual float GetTimeSinceWeaponFired( void ) const; // returns the time, in seconds, since this player fired a weapon @@ -568,8 +603,9 @@ public: virtual void PickupObject( CBaseEntity *pObject, bool bLimitMassAndSize = true ) {} virtual void ForceDropOfCarriedPhysObjects( CBaseEntity *pOnlyIfHoldindThis = NULL ) {} virtual float GetHeldObjectMass( IPhysicsObject *pHeldObject ); + virtual CBaseEntity *GetHeldObject( void ); - void CheckSuitUpdate(); + virtual void CheckSuitUpdate(); void SetSuitUpdate(const char *name, int fgroup, int iNoRepeat); virtual void UpdateGeigerCounter( void ); void CheckTimeBasedDamage( void ); @@ -579,12 +615,21 @@ public: virtual Vector GetAutoaimVector( float flScale ); virtual Vector GetAutoaimVector( float flScale, float flMaxDist ); virtual void GetAutoaimVector( autoaim_params_t ¶ms ); +#ifdef MAPBASE_VSCRIPT + Vector ScriptGetAutoaimVector( float flScale ) { return GetAutoaimVector( flScale ); } + Vector ScriptGetAutoaimVectorCustomMaxDist( float flScale, float flMaxDist ) { return GetAutoaimVector( flScale, flMaxDist ); } +#endif float GetAutoaimScore( const Vector &eyePosition, const Vector &viewDir, const Vector &vecTarget, CBaseEntity *pTarget, float fScale, CBaseCombatWeapon *pActiveWeapon ); QAngle AutoaimDeflection( Vector &vecSrc, autoaim_params_t ¶ms ); virtual bool ShouldAutoaim( void ); void SetTargetInfo( Vector &vecSrc, float flDist ); +#ifdef MAPBASE + // Tries to figure out what the player is trying to aim at + CBaseEntity *GetProbableAimTarget( const Vector &vecSrc, const Vector &vecDir ); +#endif + void SetViewEntity( CBaseEntity *pEntity ); CBaseEntity *GetViewEntity( void ) { return m_hViewEntity; } @@ -639,6 +684,12 @@ public: void PlayWearableAnimsForPlaybackEvent( wearableanimplayback_t iPlayback ); #endif +#ifdef MAPBASE + bool ShouldUseVisibilityCache( CBaseEntity *pEntity ); + + void UpdateFXVolume( void ); // From Alien Swarm SDK +#endif + public: // Player Physics Shadow void SetupVPhysicsShadow( const Vector &vecAbsOrigin, const Vector &vecAbsVelocity, CPhysCollide *pStandModel, const char *pStandHullName, CPhysCollide *pCrouchModel, const char *pCrouchHullName ); @@ -680,7 +731,7 @@ public: bool IsConnected() const { return m_iConnected != PlayerDisconnected; } bool IsDisconnecting() const { return m_iConnected == PlayerDisconnecting; } bool IsSuitEquipped() const { return m_Local.m_bWearingSuit; } - int ArmorValue() const { return m_ArmorValue; } + virtual int ArmorValue() const { return m_ArmorValue; } bool HUDNeedsRestart() const { return m_fInitHUD; } float MaxSpeed() const { return m_flMaxspeed; } Activity GetActivity( ) const { return m_Activity; } @@ -761,6 +812,10 @@ public: int GetDefaultFOV( void ) const; // Default FOV if not specified otherwise int GetFOVForNetworking( void ); // Get the current FOV used for network computations bool SetFOV( CBaseEntity *pRequester, int FOV, float zoomRate = 0.0f, int iZoomStart = 0 ); // Alters the base FOV of the player (must have a valid requester) +#ifdef MAPBASE_VSCRIPT + void ScriptSetFOV(int iFOV, float flSpeed); // Overrides player FOV, ignores zoom owner + HSCRIPT ScriptGetFOVOwner() { return ToHScript(m_hZoomOwner); } +#endif void SetDefaultFOV( int FOV ); // Sets the base FOV if nothing else is affecting it by zooming CBaseEntity *GetFOVOwner( void ) { return m_hZoomOwner; } float GetFOVDistanceAdjustFactor(); // shared between client and server @@ -789,6 +844,9 @@ public: void InputSetHealth( inputdata_t &inputdata ); void InputSetHUDVisibility( inputdata_t &inputdata ); void InputHandleMapEvent( inputdata_t &inputdata ); +#ifdef MAPBASE + void InputSetSuppressAttacks( inputdata_t &inputdata ); +#endif surfacedata_t *GetSurfaceData( void ) { return m_pSurfaceData; } void SetLadderNormal( Vector vecLadderNormal ) { m_vecLadderNormal = vecLadderNormal; } @@ -861,6 +919,19 @@ public: void InitFogController( void ); void InputSetFogController( inputdata_t &inputdata ); +#ifdef MAPBASE // From Alien Swarm SDK + void OnTonemapTriggerStartTouch( CTonemapTrigger *pTonemapTrigger ); + void OnTonemapTriggerEndTouch( CTonemapTrigger *pTonemapTrigger ); + CUtlVector< CHandle< CTonemapTrigger > > m_hTriggerTonemapList; + + CNetworkHandle( CPostProcessController, m_hPostProcessCtrl ); // active postprocessing controller + CNetworkHandle( CColorCorrection, m_hColorCorrectionCtrl ); // active FXVolume color correction + void InitPostProcessController( void ); + void InputSetPostProcessController( inputdata_t &inputdata ); + void InitColorCorrectionController( void ); + void InputSetColorCorrectionController( inputdata_t &inputdata ); +#endif + // Used by env_soundscape_triggerable to manage when the player is touching multiple // soundscape triggers simultaneously. // The one at the HEAD of the list is always the current soundscape for the player. @@ -919,6 +990,10 @@ public: int GetNumWearables( void ) const { return m_hMyWearables.Count(); } #endif +#ifdef MAPBASE + CNetworkVar( bool, m_bInTriggerFall ); +#endif + private: Activity m_Activity; @@ -1005,6 +1080,13 @@ protected: float m_fReplayEnd; // time to stop replay mode int m_iReplayEntity; // follow this entity in replay +#ifdef MAPBASE // From Alien Swarm SDK + // For now, Mapbase uses Tony Sergi's Source 2007 tonemap fixes. + // Alien Swarm SDK tonemap controller code copies the parameters instead. + virtual void UpdateTonemapController( void ); + //CNetworkHandle( CBaseEntity, m_hTonemapController ); +#endif + private: void HandleFuncTrain(); @@ -1122,6 +1204,11 @@ public: float m_flSideMove; int m_nNumCrateHudHints; +#ifdef MAPBASE + bool GetDrawPlayerModelExternally( void ) { return m_bDrawPlayerModelExternally; } + void SetDrawPlayerModelExternally( bool bToggle ) { m_bDrawPlayerModelExternally.Set( bToggle ); } +#endif + private: // Used in test code to teleport the player to random locations in the map. @@ -1163,6 +1250,10 @@ private: // Player name char m_szNetname[MAX_PLAYER_NAME_LENGTH]; +#ifdef MAPBASE + CNetworkVar( bool, m_bDrawPlayerModelExternally ); +#endif + protected: // HACK FOR TF2 Prediction friend class CTFGameMovementRecon; @@ -1266,6 +1357,23 @@ private: public: virtual unsigned int PlayerSolidMask( bool brushOnly = false ) const; // returns the solid mask for the given player, so bots can have a more-restrictive set +private: + // + //Tony; new tonemap controller changes, specifically for multiplayer. + // + void ClearTonemapParams(); //Tony; we need to clear our tonemap params every time we spawn to -1, if we trigger an input, the values will be set again. +public: + void InputSetTonemapScale( inputdata_t &inputdata ); //Set m_Local. +// void InputBlendTonemapScale( inputdata_t &inputdata ); //TODO; this should be calculated on the client, if we use it; perhaps an entity message would suffice? .. hmm.. + void InputSetTonemapRate( inputdata_t &inputdata ); + void InputSetAutoExposureMin( inputdata_t &inputdata ); + void InputSetAutoExposureMax( inputdata_t &inputdata ); + void InputSetBloomScale( inputdata_t &inputdata ); + + //Tony; restore defaults (set min/max to -1.0 so nothing gets overridden) + void InputUseDefaultAutoExposure( inputdata_t &inputdata ); + void InputUseDefaultBloomScale( inputdata_t &inputdata ); + virtual bool BHaveChatSuspensionInCurrentMatch() { return false; } // A voice packet from this client was received by the server diff --git a/src/game/server/player_command.cpp b/src/game/server/player_command.cpp index a8469d6f..e7b907c3 100644 --- a/src/game/server/player_command.cpp +++ b/src/game/server/player_command.cpp @@ -18,6 +18,13 @@ // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" +#ifdef MAPBASE +// This turned out to be causing major issues with VPhysics collision. +// It's deactivated until a fix is found. +// See prediction.cpp as well. +//#define PLAYER_COMMAND_FIX 1 +#endif + extern IGameMovement *g_pGameMovement; extern CMoveData *g_pMoveData; // This is a global because it is subclassed by each game. extern ConVar sv_noclipduringpause; @@ -53,21 +60,26 @@ void CPlayerMove::StartCommand( CBasePlayer *player, CUserCmd *cmd ) #if defined (HL2_DLL) // pull out backchannel data and move this out - int i; - for (i = 0; i < cmd->entitygroundcontact.Count(); i++) + // Let's not bother with IK Ground Contact Info in MP games -- the system needs to be re-worked, every client sends down the same info for each entity, so how would it determine which to use? + if ( 1 == gpGlobals->maxClients ) { - int entindex = cmd->entitygroundcontact[i].entindex; - CBaseEntity *pEntity = CBaseEntity::Instance( engine->PEntityOfEntIndex( entindex) ); - if (pEntity) + int i; + for (i = 0; i < cmd->entitygroundcontact.Count(); i++) { - CBaseAnimating *pAnimating = pEntity->GetBaseAnimating(); - if (pAnimating) + int entindex = cmd->entitygroundcontact[i].entindex; + CBaseEntity *pEntity = CBaseEntity::Instance( engine->PEntityOfEntIndex( entindex) ); + if (pEntity) { - pAnimating->SetIKGroundContactInfo( cmd->entitygroundcontact[i].minheight, cmd->entitygroundcontact[i].maxheight ); + CBaseAnimating *pAnimating = pEntity->GetBaseAnimating(); + if (pAnimating) + { + pAnimating->SetIKGroundContactInfo( cmd->entitygroundcontact[i].minheight, cmd->entitygroundcontact[i].maxheight ); + } } } } + #endif } @@ -375,7 +387,7 @@ void CPlayerMove::RunCommand ( CBasePlayer *player, CUserCmd *ucmd, IMoveHelper if ( weapon ) { VPROF( "player->SelectItem()" ); - player->SelectItem( weapon->GetName(), ucmd->weaponsubtype ); + player->SelectItem( weapon->GetClassname(), ucmd->weaponsubtype ); } } @@ -411,6 +423,13 @@ void CPlayerMove::RunCommand ( CBasePlayer *player, CUserCmd *ucmd, IMoveHelper player->pl.v_angle = ucmd->viewangles + player->pl.anglechange; } +#ifdef PLAYER_COMMAND_FIX + // Let server invoke any needed impact functions + VPROF_SCOPE_BEGIN( "moveHelper->ProcessImpacts" ); + moveHelper->ProcessImpacts(); + VPROF_SCOPE_END(); +#endif + // Call standard client pre-think RunPreThink( player ); @@ -432,6 +451,10 @@ void CPlayerMove::RunCommand ( CBasePlayer *player, CUserCmd *ucmd, IMoveHelper VPROF( "pVehicle->ProcessMovement()" ); pVehicle->ProcessMovement( player, g_pMoveData ); } + +#ifdef PLAYER_COMMAND_FIX + RunPostThink( player ); +#endif // Copy output FinishMove( player, ucmd, g_pMoveData ); @@ -442,12 +465,14 @@ void CPlayerMove::RunCommand ( CBasePlayer *player, CUserCmd *ucmd, IMoveHelper player->pl.v_angle = player->GetLockViewanglesData(); } +#ifndef PLAYER_COMMAND_FIX // Let server invoke any needed impact functions VPROF_SCOPE_BEGIN( "moveHelper->ProcessImpacts" ); moveHelper->ProcessImpacts(); VPROF_SCOPE_END(); RunPostThink( player ); +#endif g_pGameMovement->FinishTrackPredictionErrors( player ); diff --git a/src/game/server/playerinfomanager.cpp b/src/game/server/playerinfomanager.cpp index 903efb6f..96ba0c95 100644 --- a/src/game/server/playerinfomanager.cpp +++ b/src/game/server/playerinfomanager.cpp @@ -9,6 +9,16 @@ #include "playerinfomanager.h" #include "edict.h" +#if defined( TF_DLL ) +#include "tf_shareddefs.h" +#elif defined( CSTRIKE_DLL ) +#include "weapon_csbase.h" +#elif defined( DOD_DLL ) +#include "weapon_dodbase.h" +#elif defined( SDK_DLL ) +#include "weapon_sdkbase.h" +#endif + extern CGlobalVars *gpGlobals; static CPlayerInfoManager s_PlayerInfoManager; static CPluginBotManager s_BotManager; @@ -19,74 +29,43 @@ namespace // // Old version support // - abstract_class IPlayerInfo_V1 - { - public: - // returns the players name (UTF-8 encoded) - virtual const char *GetName() = 0; - // returns the userid (slot number) - virtual int GetUserID() = 0; - // returns the string of their network (i.e Steam) ID - virtual const char *GetNetworkIDString() = 0; - // returns the team the player is on - virtual int GetTeamIndex() = 0; - // changes the player to a new team (if the game dll logic allows it) - virtual void ChangeTeam( int iTeamNum ) = 0; - // returns the number of kills this player has (exact meaning is mod dependent) - virtual int GetFragCount() = 0; - // returns the number of deaths this player has (exact meaning is mod dependent) - virtual int GetDeathCount() = 0; - // returns if this player slot is actually valid - virtual bool IsConnected() = 0; - // returns the armor/health of the player (exact meaning is mod dependent) - virtual int GetArmorValue() = 0; - }; - - abstract_class IPlayerInfoManager_V1 - { - public: - virtual IPlayerInfo_V1 *GetPlayerInfo( edict_t *pEdict ) = 0; - }; - - class CPlayerInfoManager_V1: public IPlayerInfoManager_V1 - { - public: - virtual IPlayerInfo_V1 *GetPlayerInfo( edict_t *pEdict ); - }; - - static CPlayerInfoManager_V1 s_PlayerInfoManager_V1; - - - IPlayerInfo_V1 *CPlayerInfoManager_V1::GetPlayerInfo( edict_t *pEdict ) - { - CBasePlayer *pPlayer = ( ( CBasePlayer * )CBaseEntity::Instance( pEdict )); - if ( pPlayer ) - { - return (IPlayerInfo_V1 *)pPlayer->GetPlayerInfo(); - } - else - { - return NULL; - } - } - - EXPOSE_SINGLE_INTERFACE_GLOBALVAR(CPlayerInfoManager_V1, IPlayerInfoManager_V1, "PlayerInfoManager001", s_PlayerInfoManager_V1); + //Tony; pulled out version 1 and 2 support for orange box, we're starting fresh now with v3 player and v2 of the bot interface. } IPlayerInfo *CPlayerInfoManager::GetPlayerInfo( edict_t *pEdict ) { CBasePlayer *pPlayer = ( ( CBasePlayer * )CBaseEntity::Instance( pEdict )); if ( pPlayer ) - { return pPlayer->GetPlayerInfo(); - } else - { return NULL; - } +} +IPlayerInfo *CPlayerInfoManager::GetPlayerInfo( int index ) +{ + return GetPlayerInfo( engine->PEntityOfEntIndex( index ) ); } +// Games implementing advanced bot support should override this. +int CPlayerInfoManager::AliasToWeaponId(const char *weaponName) +{ + //Tony; TF doesn't support this. Should it? +#if defined ( CSTRIKE_DLL ) || defined ( DOD_DLL ) || defined ( SDK_DLL ) + return AliasToWeaponID(weaponName); +#endif + return -1; +} + +// Games implementing advanced bot support should override this. +const char *CPlayerInfoManager::WeaponIdToAlias(int weaponId) +{ +#if defined( TF_DLL ) + return WeaponIdToAlias(weaponId); +#elif defined ( CSTRIKE_DLL ) || defined ( DOD_DLL ) || defined ( SDK_DLL ) + return WeaponIDToAlias(weaponId); +#endif + return "MOD_DIDNT_IMPLEMENT_ME"; +} CGlobalVars *CPlayerInfoManager::GetGlobalVars() { return gpGlobals; @@ -123,6 +102,7 @@ edict_t *CPluginBotManager::CreateBot( const char *botname ) pPlayer->AddFlag( FL_CLIENT | FL_FAKECLIENT ); pPlayer->ChangeTeam( TEAM_UNASSIGNED ); + //pPlayer->AddEFlags( EFL_PLUGIN_BASED_BOT ); // Mark it as a plugin based bot pPlayer->RemoveAllItems( true ); pPlayer->Spawn(); diff --git a/src/game/server/playerinfomanager.h b/src/game/server/playerinfomanager.h index e9c3f510..85dc22b2 100644 --- a/src/game/server/playerinfomanager.h +++ b/src/game/server/playerinfomanager.h @@ -19,7 +19,13 @@ class CPlayerInfoManager: public IPlayerInfoManager { public: virtual IPlayerInfo *GetPlayerInfo( edict_t *pEdict ); + virtual IPlayerInfo *GetPlayerInfo( int index ); virtual CGlobalVars *GetGlobalVars(); + // accessor to hook into aliastoweaponid + virtual int AliasToWeaponId(const char *weaponName); + // accessor to hook into weaponidtoalias + virtual const char *WeaponIdToAlias(int weaponId); + }; class CPluginBotManager: public IBotManager diff --git a/src/game/server/playerlocaldata.cpp b/src/game/server/playerlocaldata.cpp index 71fd4a5b..2a273d82 100644 --- a/src/game/server/playerlocaldata.cpp +++ b/src/game/server/playerlocaldata.cpp @@ -61,6 +61,11 @@ BEGIN_SEND_TABLE_NOBASE( CPlayerLocalData, DT_Local ) // 3d skybox data SendPropInt(SENDINFO_STRUCTELEM(m_skybox3d.scale), 12), SendPropVector (SENDINFO_STRUCTELEM(m_skybox3d.origin), -1, SPROP_COORD), +#ifdef MAPBASE + SendPropVector (SENDINFO_STRUCTELEM(m_skybox3d.angles), -1, SPROP_COORD), + SendPropEHandle (SENDINFO_STRUCTELEM(m_skybox3d.skycamera)), + SendPropInt (SENDINFO_STRUCTELEM(m_skybox3d.skycolor), 32, (SPROP_COORD|SPROP_UNSIGNED), SendProxy_Color32ToInt), +#endif SendPropInt (SENDINFO_STRUCTELEM(m_skybox3d.area), 8, SPROP_UNSIGNED ), SendPropInt( SENDINFO_STRUCTELEM( m_skybox3d.fog.enable ), 1, SPROP_UNSIGNED ), SendPropInt( SENDINFO_STRUCTELEM( m_skybox3d.fog.blend ), 1, SPROP_UNSIGNED ), @@ -71,6 +76,9 @@ BEGIN_SEND_TABLE_NOBASE( CPlayerLocalData, DT_Local ) SendPropFloat( SENDINFO_STRUCTELEM( m_skybox3d.fog.start ), 0, SPROP_NOSCALE ), SendPropFloat( SENDINFO_STRUCTELEM( m_skybox3d.fog.end ), 0, SPROP_NOSCALE ), SendPropFloat( SENDINFO_STRUCTELEM( m_skybox3d.fog.maxdensity ), 0, SPROP_NOSCALE ), +#ifdef MAPBASE + SendPropFloat( SENDINFO_STRUCTELEM( m_skybox3d.fog.farz ), 0, SPROP_NOSCALE ), +#endif SendPropEHandle( SENDINFO_STRUCTELEM( m_PlayerFog.m_hCtrl ) ), @@ -88,6 +96,12 @@ BEGIN_SEND_TABLE_NOBASE( CPlayerLocalData, DT_Local ) SendPropInt( SENDINFO_STRUCTELEM( m_audio.entIndex ) ), SendPropString( SENDINFO( m_szScriptOverlayMaterial ) ), + SendPropFloat ( SENDINFO_STRUCTELEM( m_TonemapParams.m_flTonemapScale ), 0, SPROP_NOSCALE ), + SendPropFloat ( SENDINFO_STRUCTELEM( m_TonemapParams.m_flTonemapRate ), 0, SPROP_NOSCALE ), + SendPropFloat ( SENDINFO_STRUCTELEM( m_TonemapParams.m_flBloomScale ), 0, SPROP_NOSCALE ), + + SendPropFloat ( SENDINFO_STRUCTELEM( m_TonemapParams.m_flAutoExposureMin ), 0, SPROP_NOSCALE ), + SendPropFloat ( SENDINFO_STRUCTELEM( m_TonemapParams.m_flAutoExposureMax ), 0, SPROP_NOSCALE ), END_SEND_TABLE() BEGIN_SIMPLE_DATADESC( fogplayerparams_t ) @@ -125,6 +139,11 @@ BEGIN_SIMPLE_DATADESC( sky3dparams_t ) DEFINE_FIELD( scale, FIELD_INTEGER ), DEFINE_FIELD( origin, FIELD_VECTOR ), +#ifdef MAPBASE + DEFINE_FIELD( angles, FIELD_VECTOR ), + DEFINE_FIELD( skycamera, FIELD_EHANDLE ), + DEFINE_FIELD( skycolor, FIELD_COLOR32 ), +#endif DEFINE_FIELD( area, FIELD_INTEGER ), DEFINE_EMBEDDED( fog ), @@ -139,6 +158,16 @@ BEGIN_SIMPLE_DATADESC( audioparams_t ) END_DATADESC() +//Tony; tonepam params!! +BEGIN_SIMPLE_DATADESC ( tonemap_params_t ) + DEFINE_FIELD( m_flTonemapScale, FIELD_FLOAT ), + DEFINE_FIELD( m_flTonemapRate, FIELD_FLOAT ), + DEFINE_FIELD( m_flBloomScale, FIELD_FLOAT ), + DEFINE_FIELD( m_flAutoExposureMin, FIELD_FLOAT ), + DEFINE_FIELD( m_flAutoExposureMax, FIELD_FLOAT ), +END_DATADESC() + + BEGIN_SIMPLE_DATADESC( CPlayerLocalData ) DEFINE_AUTO_ARRAY( m_chAreaBits, FIELD_CHARACTER ), DEFINE_AUTO_ARRAY( m_chAreaPortalBits, FIELD_CHARACTER ), @@ -167,6 +196,9 @@ BEGIN_SIMPLE_DATADESC( CPlayerLocalData ) DEFINE_EMBEDDED( m_PlayerFog ), DEFINE_EMBEDDED( m_fog ), DEFINE_EMBEDDED( m_audio ), + + //Tony; added + DEFINE_EMBEDDED( m_TonemapParams ), // "Why don't we save this field, grandpa?" // @@ -233,6 +265,18 @@ void ClientData_Update( CBasePlayer *pl ) // HACKHACK: for 3d skybox // UNDONE: Support multiple sky cameras? CSkyCamera *pSkyCamera = GetCurrentSkyCamera(); +#ifdef MAPBASE + // Needs null protection now that the sky can go from valid to null + if ( !pSkyCamera ) + { + pl->m_Local.m_skybox3d.area = 255; + } + else if ( pSkyCamera != pl->m_Local.m_pOldSkyCamera ) + { + pl->m_Local.m_pOldSkyCamera = pSkyCamera; + pl->m_Local.m_skybox3d.CopyFrom(pSkyCamera->m_skyboxData); + } +#else if ( pSkyCamera != pl->m_Local.m_pOldSkyCamera ) { pl->m_Local.m_pOldSkyCamera = pSkyCamera; @@ -242,6 +286,7 @@ void ClientData_Update( CBasePlayer *pl ) { pl->m_Local.m_skybox3d.area = 255; } +#endif } diff --git a/src/game/server/playerlocaldata.h b/src/game/server/playerlocaldata.h index 573d56c5..08638fec 100644 --- a/src/game/server/playerlocaldata.h +++ b/src/game/server/playerlocaldata.h @@ -15,6 +15,10 @@ #include "playernet_vars.h" #include "networkvar.h" #include "fogcontroller.h" +#ifdef MAPBASE // From Alien Swarm SDK +#include "postprocesscontroller.h" +#include "colorcorrection.h" +#endif //----------------------------------------------------------------------------- // Purpose: Player specific data ( sent only to local player, too ) @@ -85,6 +89,9 @@ public: // audio environment CNetworkVarEmbedded( audioparams_t, m_audio ); + //Tony; added so tonemap controller can work in multiplayer with inputs. + CNetworkVarEmbedded( tonemap_params_t, m_TonemapParams ); + CNetworkVar( bool, m_bSlowMovement ); CNetworkString( m_szScriptOverlayMaterial, MAX_PATH ); diff --git a/src/game/server/point_camera.cpp b/src/game/server/point_camera.cpp index ebbd6fcd..49b6d335 100644 --- a/src/game/server/point_camera.cpp +++ b/src/game/server/point_camera.cpp @@ -28,6 +28,58 @@ CPointCamera* GetPointCameraList() return g_PointCameraList.m_pClassList; } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: Returns true if a camera is in the PVS of the specified entity +//----------------------------------------------------------------------------- +edict_t *UTIL_FindRTCameraInEntityPVS( edict_t *pEdict ) +{ + CBaseEntity *pe = GetContainingEntity( pEdict ); + if ( !pe ) + return NULL; + + bool bGotPVS = false; + Vector org; + static byte pvs[ MAX_MAP_CLUSTERS/8 ]; + static int pvssize = sizeof( pvs ); + + for ( CPointCamera *pCameraEnt = GetPointCameraList(); pCameraEnt != NULL; pCameraEnt = pCameraEnt->m_pNext ) + { + if (!pCameraEnt->IsActive()) + continue; + + if (!bGotPVS) + { + // Getting the PVS during the loop like this makes sure we only get the PVS if there's actually an active camera in the level + org = pe->EyePosition(); + int clusterIndex = engine->GetClusterForOrigin( org ); + Assert( clusterIndex >= 0 ); + engine->GetPVSForCluster( clusterIndex, pvssize, pvs ); + bGotPVS = true; + } + + Vector vecCameraEye = pCameraEnt->EyePosition(); + + Vector vecCameraDirection; + pCameraEnt->GetVectors( &vecCameraDirection, NULL, NULL ); + + Vector los = (org - vecCameraEye); + float flDot = DotProduct( los, vecCameraDirection ); + + // Make sure we're in the camera's FOV before checking PVS + if ( flDot <= cos( DEG2RAD( pCameraEnt->GetFOV() / 2 ) ) ) + continue; + + if ( engine->CheckOriginInPVS( vecCameraEye, pvs, pvssize ) ) + { + return pCameraEnt->edict(); + } + } + + return NULL; +} +#endif + // These are already built into CBaseEntity // DEFINE_KEYFIELD( m_iName, FIELD_STRING, "targetname" ), // DEFINE_KEYFIELD( m_iParent, FIELD_STRING, "parentname" ), @@ -52,6 +104,13 @@ CPointCamera::CPointCamera() m_bFogEnable = false; m_bFogRadial = false; +#ifdef MAPBASE + // Equivalent to SKYBOX_2DSKYBOX_VISIBLE, the original sky setting + m_iSkyMode = 2; + + m_iszRenderTarget = AllocPooledString( "_rt_Camera" ); +#endif + g_PointCameraList.Insert( this ); } @@ -190,6 +249,13 @@ void CPointCamera::InputSetOnAndTurnOthersOff( inputdata_t &inputdata ) while ((pEntity = gEntList.FindEntityByClassname( pEntity, "point_camera" )) != NULL) { CPointCamera *pCamera = (CPointCamera*)pEntity; + +#ifdef MAPBASE + // Do not turn off cameras which use different render targets + if (pCamera->m_iszRenderTarget.Get() != m_iszRenderTarget.Get()) + continue; +#endif + pCamera->InputSetOff( inputdata ); } @@ -227,6 +293,10 @@ BEGIN_DATADESC( CPointCamera ) DEFINE_KEYFIELD( m_flFogMaxDensity, FIELD_FLOAT, "fogMaxDensity" ), DEFINE_KEYFIELD( m_bFogRadial, FIELD_BOOLEAN, "fogRadial" ), DEFINE_KEYFIELD( m_bUseScreenAspectRatio, FIELD_BOOLEAN, "UseScreenAspectRatio" ), +#ifdef MAPBASE + DEFINE_KEYFIELD( m_iSkyMode, FIELD_INTEGER, "SkyMode" ), + DEFINE_KEYFIELD( m_iszRenderTarget, FIELD_STRING, "RenderTarget" ), +#endif DEFINE_FIELD( m_bActive, FIELD_BOOLEAN ), DEFINE_FIELD( m_bIsOn, FIELD_BOOLEAN ), @@ -242,6 +312,10 @@ BEGIN_DATADESC( CPointCamera ) DEFINE_INPUTFUNC( FIELD_VOID, "SetOnAndTurnOthersOff", InputSetOnAndTurnOthersOff ), DEFINE_INPUTFUNC( FIELD_VOID, "SetOn", InputSetOn ), DEFINE_INPUTFUNC( FIELD_VOID, "SetOff", InputSetOff ), +#ifdef MAPBASE + DEFINE_INPUTFUNC( FIELD_INTEGER, "SetSkyMode", InputSetSkyMode ), + DEFINE_INPUTFUNC( FIELD_STRING, "SetRenderTarget", InputSetRenderTarget ), +#endif END_DATADESC() @@ -256,4 +330,215 @@ IMPLEMENT_SERVERCLASS_ST( CPointCamera, DT_PointCamera ) SendPropInt( SENDINFO( m_bFogRadial ), 1, SPROP_UNSIGNED ), SendPropInt( SENDINFO( m_bActive ), 1, SPROP_UNSIGNED ), SendPropInt( SENDINFO( m_bUseScreenAspectRatio ), 1, SPROP_UNSIGNED ), +#ifdef MAPBASE + SendPropInt( SENDINFO( m_iSkyMode ) ), + SendPropStringT( SENDINFO( m_iszRenderTarget ) ), +#endif END_SEND_TABLE() + +#ifdef MAPBASE + +//============================================================================= +// Orthographic point_camera +//============================================================================= + +BEGIN_DATADESC( CPointCameraOrtho ) + + DEFINE_KEYFIELD( m_bOrtho, FIELD_BOOLEAN, "IsOrtho" ), + DEFINE_ARRAY( m_OrthoDimensions, FIELD_FLOAT, CPointCameraOrtho::NUM_ORTHO_DIMENSIONS ), + + DEFINE_ARRAY( m_TargetOrtho, FIELD_FLOAT, CPointCameraOrtho::NUM_ORTHO_DIMENSIONS ), + DEFINE_FIELD( m_TargetOrthoDPS, FIELD_FLOAT ), + + DEFINE_FUNCTION( ChangeOrthoThink ), + + // Input + DEFINE_INPUTFUNC( FIELD_BOOLEAN, "SetOrthoEnabled", InputSetOrthoEnabled ), + DEFINE_INPUTFUNC( FIELD_STRING, "ScaleOrtho", InputScaleOrtho ), + DEFINE_INPUTFUNC( FIELD_STRING, "SetOrthoTop", InputSetOrthoTop ), + DEFINE_INPUTFUNC( FIELD_STRING, "SetOrthoBottom", InputSetOrthoBottom ), + DEFINE_INPUTFUNC( FIELD_STRING, "SetOrthoLeft", InputSetOrthoLeft ), + DEFINE_INPUTFUNC( FIELD_STRING, "SetOrthoRight", InputSetOrthoRight ), + +END_DATADESC() + +IMPLEMENT_SERVERCLASS_ST( CPointCameraOrtho, DT_PointCameraOrtho ) + SendPropInt( SENDINFO( m_bOrtho ), 1, SPROP_UNSIGNED ), + SendPropArray( SendPropFloat(SENDINFO_ARRAY(m_OrthoDimensions), CPointCameraOrtho::NUM_ORTHO_DIMENSIONS, SPROP_NOSCALE ), m_OrthoDimensions ), +END_SEND_TABLE() + +LINK_ENTITY_TO_CLASS( point_camera_ortho, CPointCameraOrtho ); + +CPointCameraOrtho::~CPointCameraOrtho() +{ +} + +CPointCameraOrtho::CPointCameraOrtho() +{ +} + +void CPointCameraOrtho::Spawn( void ) +{ + BaseClass::Spawn(); + + // If 0, get the FOV + if (m_OrthoDimensions[ORTHO_TOP] == 0.0f) + m_OrthoDimensions.Set( ORTHO_TOP, GetFOV() ); + + // If 0, get the negative top ortho + if (m_OrthoDimensions[ORTHO_BOTTOM] == 0.0f) + m_OrthoDimensions.Set( ORTHO_BOTTOM, -m_OrthoDimensions[ORTHO_TOP] ); + + // If 0, get the top ortho + if (m_OrthoDimensions[ORTHO_LEFT] == 0.0f) + m_OrthoDimensions.Set( ORTHO_LEFT, m_OrthoDimensions[ORTHO_TOP] ); + + // If 0, get the negative left ortho + if (m_OrthoDimensions[ORTHO_RIGHT] == 0.0f) + m_OrthoDimensions.Set( ORTHO_RIGHT, -m_OrthoDimensions[ORTHO_LEFT] ); +} + +bool CPointCameraOrtho::KeyValue( const char *szKeyName, const char *szValue ) +{ + if ( strncmp( szKeyName, "Ortho", 5 ) == 0 ) + { + int iOrtho = atoi(szKeyName + 5); + m_OrthoDimensions.Set( iOrtho, atof( szValue ) ); + } + else + return BaseClass::KeyValue( szKeyName, szValue ); + + return true; +} + +bool CPointCameraOrtho::GetKeyValue( const char *szKeyName, char *szValue, int iMaxLen ) +{ + if ( strncmp( szKeyName, "Ortho", 5 ) ) + { + int iOrtho = atoi(szKeyName + 5); + Q_snprintf( szValue, iMaxLen, "%f", m_OrthoDimensions[iOrtho] ); + } + else + return BaseClass::GetKeyValue( szKeyName, szValue, iMaxLen ); + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPointCameraOrtho::ChangeOrtho( int iType, const char *szChange ) +{ + // Parse the keyvalue data + char parseString[255]; + Q_strncpy( parseString, szChange, sizeof( parseString ) ); + + // Get Ortho + char *pszParam = strtok( parseString, " " ); + if (pszParam) + { + m_TargetOrtho[iType] = atof( pszParam ); + } + else + { + // Assume no change + m_TargetOrtho[iType] = m_OrthoDimensions[iType]; + } + + // Get Time + float flChangeTime; + pszParam = strtok( NULL, " " ); + if (pszParam) + { + flChangeTime = atof( pszParam ); + } + else + { + // Assume 1 second + flChangeTime = 1.0; + } + + m_TargetOrthoDPS = ( m_TargetOrtho[iType] - m_OrthoDimensions[iType] ) / flChangeTime; + + SetThink( &CPointCameraOrtho::ChangeOrthoThink ); + SetNextThink( gpGlobals->curtime ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPointCameraOrtho::InputScaleOrtho( inputdata_t &inputdata ) +{ + // Parse the keyvalue data + char parseString[255]; + Q_strncpy( parseString, inputdata.value.String(), sizeof( parseString ) ); + + // Get Scale + float flScale = 1.0f; + char *pszParam = strtok( parseString, " " ); + if (pszParam) + { + flScale = atof( pszParam ); + } + + // Get Time + float flChangeTime = 1.0f; + pszParam = strtok( NULL, " " ); + if (pszParam) + { + flChangeTime = atof( pszParam ); + } + + int iLargest = 0; + for (int i = 0; i < NUM_ORTHO_DIMENSIONS; i++) + { + m_TargetOrtho[i] = flScale * m_OrthoDimensions[i]; + + if (m_TargetOrtho[iLargest] <= m_TargetOrtho[i]) + iLargest = i; + } + + m_TargetOrthoDPS = (m_TargetOrtho[iLargest] - m_OrthoDimensions[iLargest]) / flChangeTime; + + SetThink( &CPointCameraOrtho::ChangeOrthoThink ); + SetNextThink( gpGlobals->curtime ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPointCameraOrtho::ChangeOrthoThink( void ) +{ + SetNextThink( gpGlobals->curtime + CAM_THINK_INTERVAL ); + + int iChanging = 0; + for (int i = 0; i < NUM_ORTHO_DIMENSIONS; i++) + { + float newDim = m_OrthoDimensions[i]; + if (newDim == m_TargetOrtho[i]) + continue; + + newDim += m_TargetOrthoDPS * CAM_THINK_INTERVAL; + + if (m_TargetOrthoDPS < 0) + { + if (newDim <= m_TargetOrtho[i]) + { + newDim = m_TargetOrtho[i]; + } + } + else + { + if (newDim >= m_TargetOrtho[i]) + { + newDim = m_TargetOrtho[i]; + } + } + + m_OrthoDimensions.Set(i, newDim); + } + + if (iChanging == 0) + SetThink( NULL ); +} +#endif diff --git a/src/game/server/point_camera.h b/src/game/server/point_camera.h index f0c0567d..d492cca4 100644 --- a/src/game/server/point_camera.h +++ b/src/game/server/point_camera.h @@ -37,6 +37,13 @@ public: void InputSetOnAndTurnOthersOff( inputdata_t &inputdata ); void InputSetOn( inputdata_t &inputdata ); void InputSetOff( inputdata_t &inputdata ); +#ifdef MAPBASE + void InputSetSkyMode( inputdata_t &inputdata ) { m_iSkyMode = inputdata.value.Int(); } + void InputSetRenderTarget( inputdata_t &inputdata ) { m_iszRenderTarget = inputdata.value.StringID(); } + + float GetFOV() const { return m_FOV; } + bool IsActive() const { return m_bIsOn; } +#endif private: float m_TargetFOV; @@ -52,6 +59,10 @@ private: CNetworkVar( bool, m_bFogRadial ); CNetworkVar( bool, m_bActive ); CNetworkVar( bool, m_bUseScreenAspectRatio ); +#ifdef MAPBASE + CNetworkVar( int, m_iSkyMode ); + CNetworkVar( string_t, m_iszRenderTarget ); +#endif // Allows the mapmaker to control whether a camera is active or not bool m_bIsOn; @@ -60,5 +71,56 @@ public: CPointCamera *m_pNext; }; +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class CPointCameraOrtho : public CPointCamera +{ +public: + DECLARE_CLASS( CPointCameraOrtho, CPointCamera ); + DECLARE_SERVERCLASS(); + DECLARE_DATADESC(); + CPointCameraOrtho(); + ~CPointCameraOrtho(); + + enum + { + ORTHO_TOP, + ORTHO_BOTTOM, + ORTHO_LEFT, + ORTHO_RIGHT, + + NUM_ORTHO_DIMENSIONS + }; + + void Spawn( void ); + + bool KeyValue( const char *szKeyName, const char *szValue ); + bool GetKeyValue( const char *szKeyName, char *szValue, int iMaxLen ); + + void ChangeOrtho( int iType, const char *szChange ); + void ChangeOrthoThink( void ); + + void InputSetOrthoEnabled( inputdata_t &inputdata ) { m_bOrtho = inputdata.value.Bool(); } + void InputScaleOrtho( inputdata_t &inputdata ); + void InputSetOrthoTop( inputdata_t &inputdata ) { ChangeOrtho(ORTHO_TOP, inputdata.value.String()); } + void InputSetOrthoBottom( inputdata_t &inputdata ) { ChangeOrtho( ORTHO_BOTTOM, inputdata.value.String() ); } + void InputSetOrthoLeft( inputdata_t &inputdata ) { ChangeOrtho( ORTHO_LEFT, inputdata.value.String() ); } + void InputSetOrthoRight( inputdata_t &inputdata ) { ChangeOrtho( ORTHO_RIGHT, inputdata.value.String() ); } + +private: + float m_TargetOrtho[NUM_ORTHO_DIMENSIONS]; + float m_TargetOrthoDPS; + + CNetworkVar( bool, m_bOrtho ); + CNetworkArray( float, m_OrthoDimensions, NUM_ORTHO_DIMENSIONS ); +}; +#endif + CPointCamera *GetPointCameraList(); + +#ifdef MAPBASE +edict_t *UTIL_FindRTCameraInEntityPVS( edict_t *pEdict ); +#endif #endif // CAMERA_H diff --git a/src/game/server/point_devshot_camera.cpp b/src/game/server/point_devshot_camera.cpp index 3f327ba4..4d476664 100644 --- a/src/game/server/point_devshot_camera.cpp +++ b/src/game/server/point_devshot_camera.cpp @@ -227,6 +227,10 @@ public: } } +#ifdef MAPBASE // VDC Memory Leak Fixes + pkvMapCameras->deleteThis(); +#endif + if ( !g_iDevShotCameraCount ) { Warning( "Devshots: No point_devshot_camera in %s. Moving to next map.\n", STRING( gpGlobals->mapname ) ); diff --git a/src/game/server/point_entity_finder.cpp b/src/game/server/point_entity_finder.cpp new file mode 100644 index 00000000..d26b71f4 --- /dev/null +++ b/src/game/server/point_entity_finder.cpp @@ -0,0 +1,207 @@ +//----------------------------------------------------------------------------- +// class CPointEntityFinder +// +// Purpose: Finds an entity using a specified heuristic and outputs it as !caller +// with the OnFoundEntity output. +//----------------------------------------------------------------------------- + +#include "cbase.h" +#include "filters.h" + +// NOTE: This has to be the last file included! +#include "tier0/memdbgon.h" + + +enum EntFinderMethod_t +{ + ENT_FIND_METHOD_NEAREST = 0, + ENT_FIND_METHOD_FARTHEST, + ENT_FIND_METHOD_RANDOM, +}; + +class CPointEntityFinder : public CBaseEntity +{ + void Activate( void ); + + DECLARE_CLASS( CPointEntityFinder, CBaseEntity ); + +private: + + EHANDLE m_hEntity; + string_t m_iFilterName; + CHandle m_hFilter; + string_t m_iRefName; + EHANDLE m_hReference; + + EntFinderMethod_t m_FindMethod; + + void FindEntity( void ); + void FindByDistance( void ); + void FindByRandom( void ); + + // Input handlers + void InputFindEntity( inputdata_t &inputdata ); + + // Output handlers + COutputEvent m_OnFoundEntity; + + DECLARE_DATADESC(); +}; + +LINK_ENTITY_TO_CLASS( point_entity_finder, CPointEntityFinder ); + +BEGIN_DATADESC( CPointEntityFinder ) + + DEFINE_KEYFIELD( m_FindMethod, FIELD_INTEGER, "method" ), + DEFINE_KEYFIELD( m_iFilterName, FIELD_STRING, "filtername" ), + DEFINE_FIELD( m_hFilter, FIELD_EHANDLE ), + DEFINE_KEYFIELD( m_iRefName, FIELD_STRING, "referencename" ), + DEFINE_FIELD( m_hReference, FIELD_EHANDLE ), + + DEFINE_OUTPUT( m_OnFoundEntity, "OnFoundEntity" ), + + //--------------------------------- + + DEFINE_INPUTFUNC( FIELD_VOID, "FindEntity", InputFindEntity ), + +END_DATADESC() + + +void CPointEntityFinder::Activate( void ) +{ + // Get the filter, if it exists. + if (m_iFilterName != NULL_STRING) + { + m_hFilter = dynamic_cast(gEntList.FindEntityByName( NULL, m_iFilterName )); + } + + BaseClass::Activate(); +} + + +void CPointEntityFinder::FindEntity( void ) +{ + // Get the reference entity, if it exists. + if (m_iRefName != NULL_STRING) + { + m_hReference = gEntList.FindEntityByName( NULL, m_iRefName ); + } + + switch ( m_FindMethod ) + { + + case ( ENT_FIND_METHOD_NEAREST ): + FindByDistance(); + break; + case ( ENT_FIND_METHOD_FARTHEST ): + FindByDistance(); + break; + case ( ENT_FIND_METHOD_RANDOM ): + FindByRandom(); + break; + } +} + +void CPointEntityFinder::FindByDistance( void ) +{ + m_hEntity = NULL; + CBaseFilter *pFilter = m_hFilter.Get(); + +// go through each entity and determine whether it's closer or farther from the current entity. Pick according to Method selected. + + float flBestDist = 0; + CBaseEntity *pEntity = gEntList.FirstEnt(); + while ( pEntity ) + { + if ( FStrEq( STRING( pEntity->m_iClassname ), "worldspawn" ) + || FStrEq( STRING( pEntity->m_iClassname ), "soundent" ) + || FStrEq( STRING( pEntity->m_iClassname ), "player_manager" ) + || FStrEq( STRING( pEntity->m_iClassname ), "bodyque" ) + || FStrEq( STRING( pEntity->m_iClassname ), "ai_network" ) + || pEntity == this + || ( pFilter && !( pFilter->PassesFilter( this, pEntity ) ) ) ) + { + pEntity = gEntList.NextEnt( pEntity ); + continue; + } + + // if we have a reference entity, use that, otherwise, check against 'this' + Vector vecStart; + if ( m_hReference ) + { + vecStart = m_hReference->GetAbsOrigin(); + } + else + { + vecStart = GetAbsOrigin(); + } + + // init m_hEntity with a valid entity. + if (m_hEntity == NULL ) + { + m_hEntity = pEntity; + flBestDist = ( pEntity->GetAbsOrigin() - vecStart ).LengthSqr(); + } + + float flNewDist = ( pEntity->GetAbsOrigin() - vecStart ).LengthSqr(); + + switch ( m_FindMethod ) + { + + case ( ENT_FIND_METHOD_NEAREST ): + if ( flNewDist < flBestDist ) + { + m_hEntity = pEntity; + flBestDist = flNewDist; + } + break; + + case ( ENT_FIND_METHOD_FARTHEST ): + if ( flNewDist > flBestDist ) + { + m_hEntity = pEntity; + flBestDist = flNewDist; + } + break; + + default: + Assert( false ); + break; + } + + pEntity = gEntList.NextEnt( pEntity ); + } +} + +void CPointEntityFinder::FindByRandom( void ) +{ + // TODO: optimize the case where there is no filter + m_hEntity = NULL; + CBaseFilter *pFilter = m_hFilter.Get(); + CUtlVector ValidEnts; + + CBaseEntity *pEntity = gEntList.FirstEnt(); + do // note all valid entities. + { + if ( pFilter && pFilter->PassesFilter( this, pEntity ) ) + { + ValidEnts.AddToTail( pEntity ); + } + + pEntity = gEntList.NextEnt( pEntity ); + + } while ( pEntity ); + + // pick one at random + if ( ValidEnts.Count() != 0 ) + { + m_hEntity = ValidEnts[ RandomInt( 0, ValidEnts.Count() - 1 )]; + } +} + +void CPointEntityFinder::InputFindEntity( inputdata_t &inputdata ) +{ + FindEntity(); + + m_OnFoundEntity.FireOutput( inputdata.pActivator, m_hEntity ); +} \ No newline at end of file diff --git a/src/game/server/point_spotlight.cpp b/src/game/server/point_spotlight.cpp index 41913b35..0f7458dd 100644 --- a/src/game/server/point_spotlight.cpp +++ b/src/game/server/point_spotlight.cpp @@ -26,6 +26,9 @@ public: DECLARE_DATADESC(); CPointSpotlight(); +#ifdef MAPBASE + ~CPointSpotlight(); +#endif void Precache(void); void Spawn(void); @@ -48,6 +51,9 @@ private: // ------------------------------ void InputLightOn( inputdata_t &inputdata ); void InputLightOff( inputdata_t &inputdata ); +#ifdef MAPBASE + void InputLightToggle( inputdata_t &inputdata ) { m_bSpotlightOn ? InputLightOff(inputdata) : InputLightOn(inputdata); } +#endif // Creates the efficient spotlight void CreateEfficientSpotlight(); @@ -74,6 +80,12 @@ private: float m_flHDRColorScale; int m_nMinDXLevel; +#ifdef MAPBASE + float m_flHaloScale; + string_t m_iszHaloMaterial; + string_t m_iszSpotlightMaterial; +#endif + public: COutputEvent m_OnOn, m_OnOff; ///< output fires when turned on, off }; @@ -98,10 +110,18 @@ BEGIN_DATADESC( CPointSpotlight ) DEFINE_KEYFIELD( m_flSpotlightGoalWidth,FIELD_FLOAT, "SpotlightWidth"), DEFINE_KEYFIELD( m_flHDRColorScale, FIELD_FLOAT, "HDRColorScale" ), DEFINE_KEYFIELD( m_nMinDXLevel, FIELD_INTEGER, "mindxlevel" ), +#ifdef MAPBASE + DEFINE_KEYFIELD( m_flHaloScale, FIELD_FLOAT, "HaloScale" ), + DEFINE_KEYFIELD( m_iszHaloMaterial, FIELD_STRING, "HaloMaterial" ), + DEFINE_KEYFIELD( m_iszSpotlightMaterial, FIELD_STRING, "SpotlightMaterial" ), +#endif // Inputs DEFINE_INPUTFUNC( FIELD_VOID, "LightOn", InputLightOn ), DEFINE_INPUTFUNC( FIELD_VOID, "LightOff", InputLightOff ), +#ifdef MAPBASE + DEFINE_INPUTFUNC( FIELD_VOID, "LightToggle", InputLightToggle ), +#endif DEFINE_OUTPUT( m_OnOn, "OnLightOn" ), DEFINE_OUTPUT( m_OnOff, "OnLightOff" ), @@ -127,8 +147,21 @@ CPointSpotlight::CPointSpotlight() m_bIgnoreSolid = false; AddEFlags( EFL_FORCE_ALLOW_MOVEPARENT ); +#ifdef MAPBASE + m_flHaloScale = 60.0f; +#endif } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CPointSpotlight::~CPointSpotlight() +{ + SpotlightDestroy(); +} +#endif + //----------------------------------------------------------------------------- // Purpose: @@ -138,8 +171,23 @@ void CPointSpotlight::Precache(void) BaseClass::Precache(); // Sprites. +#ifdef MAPBASE + if (m_iszHaloMaterial == NULL_STRING) + { + m_iszHaloMaterial = AllocPooledString( "sprites/light_glow03.vmt" ); + } + + if (m_iszSpotlightMaterial == NULL_STRING) + { + m_iszSpotlightMaterial = AllocPooledString( "sprites/glow_test02.vmt" ); + } + + m_nHaloSprite = PrecacheModel( STRING( m_iszHaloMaterial ) ); + PrecacheModel( STRING( m_iszSpotlightMaterial ) ); +#else m_nHaloSprite = PrecacheModel("sprites/light_glow03.vmt"); PrecacheModel( "sprites/glow_test02.vmt" ); +#endif } @@ -387,13 +435,21 @@ void CPointSpotlight::SpotlightCreate(void) } //m_hSpotlight = CBeam::BeamCreate( "sprites/spotlight.vmt", m_flSpotlightGoalWidth ); +#ifdef MAPBASE + m_hSpotlight = CBeam::BeamCreate( STRING(m_iszSpotlightMaterial), m_flSpotlightGoalWidth ); +#else m_hSpotlight = CBeam::BeamCreate( "sprites/glow_test02.vmt", m_flSpotlightGoalWidth ); +#endif // Set the temporary spawnflag on the beam so it doesn't save (we'll recreate it on restore) m_hSpotlight->SetHDRColorScale( m_flHDRColorScale ); m_hSpotlight->AddSpawnFlags( SF_BEAM_TEMPORARY ); m_hSpotlight->SetColor( m_clrRender->r, m_clrRender->g, m_clrRender->b ); m_hSpotlight->SetHaloTexture(m_nHaloSprite); +#ifdef MAPBASE + m_hSpotlight->SetHaloScale(m_flHaloScale); +#else m_hSpotlight->SetHaloScale(60); +#endif m_hSpotlight->SetEndWidth(m_flSpotlightGoalWidth); m_hSpotlight->SetBeamFlags( (FBEAM_SHADEOUT|FBEAM_NOTILE) ); m_hSpotlight->SetBrightness( 64 ); diff --git a/src/game/server/point_template.cpp b/src/game/server/point_template.cpp index 7c53eeeb..836b8370 100644 --- a/src/game/server/point_template.cpp +++ b/src/game/server/point_template.cpp @@ -56,14 +56,23 @@ BEGIN_DATADESC( CPointTemplate ) DEFINE_KEYFIELD( m_iszTemplateEntityNames[14], FIELD_STRING, "Template15"), DEFINE_KEYFIELD( m_iszTemplateEntityNames[15], FIELD_STRING, "Template16"), DEFINE_UTLVECTOR( m_hTemplateEntities, FIELD_CLASSPTR ), +#ifdef MAPBASE + DEFINE_KEYFIELD( m_bFixupExpanded, FIELD_BOOLEAN, "FixupMode" ), +#endif DEFINE_UTLVECTOR( m_hTemplates, FIELD_EMBEDDED ), // Inputs DEFINE_INPUTFUNC( FIELD_VOID, "ForceSpawn", InputForceSpawn ), +#ifdef MAPBASE + DEFINE_INPUTFUNC( FIELD_VOID, "ForceSpawnRandomTemplate", InputForceSpawnRandomTemplate ), +#endif // Outputs DEFINE_OUTPUT( m_pOutputOnSpawned, "OnEntitySpawned" ), +#ifdef MAPBASE + DEFINE_OUTPUT( m_pOutputOutEntity, "OutSpawnedEntity" ), +#endif END_DATADESC() @@ -129,6 +138,7 @@ void CPointTemplate::Spawn( void ) { Precache(); ScriptInstallPreSpawnHook(); + ValidateScriptScope(); } void CPointTemplate::Precache() @@ -391,6 +401,7 @@ bool CPointTemplate::CreateInstance( const Vector &vecOrigin, const QAngle &vecA pSpawnList[i].m_hEntity = NULL; UTIL_RemoveImmediate( pEntity ); } + pSpawnList[i].m_nDepth = 0; pSpawnList[i].m_pDeferredParent = NULL; } @@ -408,6 +419,81 @@ bool CPointTemplate::CreateInstance( const Vector &vecOrigin, const QAngle &vecA return true; } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: Spawn one of the entities I contain +// Input : &vecOrigin - +// &vecAngles - +// pEntities - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CPointTemplate::CreateSpecificInstance( int iTemplate, const Vector &vecOrigin, const QAngle &vecAngles, CBaseEntity **pOutEntity ) +{ + // Go through all our templated map data and spawn all the entities in it + int iTemplates = m_hTemplates.Count(); + if ( !iTemplates ) + { + Msg("CreateInstance called on a point_template that has no templates: %s\n", STRING(GetEntityName()) ); + return false; + } + + // Tell the template system we're about to start a new template + Templates_StartUniqueInstance(); + + CBaseEntity *pEntity = NULL; + char *pMapData; + int iTemplateIndex = m_hTemplates[iTemplate].iTemplateIndex; + + // Some templates have Entity I/O connecting the entities within the template. + // Unique versions of these templates need to be created whenever they're instanced. + if ( AllowNameFixup() && ( Templates_IndexRequiresEntityIOFixup( iTemplateIndex ) || m_ScriptScope.IsInitialized() ) ) + { + // This template requires instancing. + // Create a new mapdata block and ask the template system to fill it in with + // a unique version (with fixed Entity I/O connections). + pMapData = Templates_GetEntityIOFixedMapData( iTemplateIndex ); + } + else + { + // Use the unmodified mapdata + pMapData = (char*)STRING( Templates_FindByIndex( iTemplateIndex ) ); + } + + // Create the entity from the mapdata + MapEntity_ParseEntity( pEntity, pMapData, NULL ); + if ( pEntity == NULL ) + { + Msg("Failed to initialize templated entity with mapdata: %s\n", pMapData ); + return false; + } + + // Get a matrix that'll convert from world to the new local space + VMatrix matNewTemplateToWorld, matStoredLocalToWorld; + matNewTemplateToWorld.SetupMatrixOrgAngles( vecOrigin, vecAngles ); + MatrixMultiply( matNewTemplateToWorld, m_hTemplates[iTemplate].matEntityToTemplate, matStoredLocalToWorld ); + + // Get the world origin & angles from the stored local coordinates + Vector vecNewOrigin; + QAngle vecNewAngles; + vecNewOrigin = matStoredLocalToWorld.GetTranslation(); + MatrixToAngles( matStoredLocalToWorld, vecNewAngles ); + + // Set its origin & angles + pEntity->SetAbsOrigin( vecNewOrigin ); + pEntity->SetAbsAngles( vecNewAngles ); + + // Spawn it + DispatchSpawn( pEntity ); + + if (pOutEntity) + { + *pOutEntity = pEntity; + } + + return true; +} +#endif + //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- @@ -513,8 +599,10 @@ void ScriptPostSpawn( CScriptScope *pScriptScope, CBaseEntity **ppEntities, int // Entity for template spawning entities from script //----------------------------------------------------------------------------- BEGIN_ENT_SCRIPTDESC( CPointScriptTemplate, CBaseEntity, "point_script_template" ) +#ifndef MAPBASE_VSCRIPT DEFINE_SCRIPTFUNC( AddTemplate, "Add an entity to the template spawner" ) DEFINE_SCRIPTFUNC( SetGroupSpawnTables, "Cache the group spawn tables" ) +#endif END_SCRIPTDESC() LINK_ENTITY_TO_CLASS( point_script_template, CPointScriptTemplate ); @@ -580,8 +668,10 @@ void CPointScriptTemplate::Spawn( void ) //----------------------------------------------------------------------------- void CPointScriptTemplate::SetGroupSpawnTables( HSCRIPT templateSpawnTable, HSCRIPT groupSpawnTables ) { +#ifndef MAPBASE_VSCRIPT m_hTemplateSpawnTable = g_pScriptVM->ReferenceScope( templateSpawnTable ); m_hGroupSpawnTables = g_pScriptVM->ReferenceScope( groupSpawnTables ); +#endif } //----------------------------------------------------------------------------- @@ -589,11 +679,13 @@ void CPointScriptTemplate::SetGroupSpawnTables( HSCRIPT templateSpawnTable, HSCR //----------------------------------------------------------------------------- void CPointScriptTemplate::AddTemplate( const char *pClassname, HSCRIPT spawnTable ) { +#ifndef MAPBASE_VSCRIPT scriptTemplate_t newTemplate; newTemplate.szClassname = MAKE_STRING( pClassname ); newTemplate.hSpawnTable = g_pScriptVM->ReferenceScope( spawnTable ); m_hTemplates.AddToTail( newTemplate ); +#endif } //----------------------------------------------------------------------------- @@ -715,5 +807,30 @@ void CPointScriptTemplate::InputForceSpawn( inputdata_t &inputdata ) // Fire our output m_pOutputOnSpawned.FireOutput( this, this ); + +#ifdef MAPBASE + for ( int i = 0; i < hNewEntities.Count(); i++ ) + { + m_pOutputOutEntity.Set(hNewEntities[i], hNewEntities[i], this); + } +#endif } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: Randomly spawns one of our templates. +// This is copied from CreateInstance(). +// Input : &inputdata - +//----------------------------------------------------------------------------- +void CPointTemplate::InputForceSpawnRandomTemplate( inputdata_t &inputdata ) +{ + // Spawn our template + CBaseEntity *pEntity = NULL; + if ( !CreateSpecificInstance( RandomInt(0, GetNumTemplates() - 1), GetAbsOrigin(), GetAbsAngles(), &pEntity ) ) + return; + + // Fire our output + m_pOutputOnSpawned.FireOutput( this, this ); + m_pOutputOutEntity.Set(pEntity, pEntity, this); +} +#endif diff --git a/src/game/server/point_template.h b/src/game/server/point_template.h index f3fadc4b..c1af9385 100644 --- a/src/game/server/point_template.h +++ b/src/game/server/point_template.h @@ -46,6 +46,9 @@ public: void AddTemplate( CBaseEntity *pEntity, const char *pszMapData, int nLen ); bool ShouldRemoveTemplateEntities( void ); bool AllowNameFixup(); +#ifdef MAPBASE + bool NameFixupExpanded() { return m_bFixupExpanded; } +#endif // Templates accessors int GetNumTemplates( void ); @@ -54,9 +57,15 @@ public: // Template instancing bool CreateInstance( const Vector &vecOrigin, const QAngle &vecAngles, CUtlVector *pEntities ); void CreationComplete( const CUtlVector &entities ); +#ifdef MAPBASE + bool CreateSpecificInstance( int iTemplate, const Vector &vecOrigin, const QAngle &vecAngles, CBaseEntity **pOutEntity ); +#endif // Inputs void InputForceSpawn( inputdata_t &inputdata ); +#ifdef MAPBASE + void InputForceSpawnRandomTemplate( inputdata_t &inputdata ); +#endif virtual void PerformPrecache(); @@ -68,10 +77,20 @@ private: // code removes all the entities in it once it finishes turning them into templates. CUtlVector< CBaseEntity * > m_hTemplateEntities; +#ifdef MAPBASE + // Allows name fixup to target all instances of a name in a keyvalue, including output parameters. + // TODO: Support for multiple fixup modes? + bool m_bFixupExpanded; +#endif + // List of templates, generated from our template entities. CUtlVector< template_t > m_hTemplates; COutputEvent m_pOutputOnSpawned; +#ifdef MAPBASE +public: + COutputEHANDLE m_pOutputOutEntity; +#endif }; diff --git a/src/game/server/pointhurt.cpp b/src/game/server/pointhurt.cpp index b1650acd..9698e335 100644 --- a/src/game/server/pointhurt.cpp +++ b/src/game/server/pointhurt.cpp @@ -15,6 +15,7 @@ // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" + BEGIN_DATADESC( CPointHurt ) DEFINE_KEYFIELD( m_flRadius, FIELD_FLOAT, "DamageRadius" ), @@ -154,3 +155,21 @@ void CPointHurt::InputHurt( inputdata_t &data ) HurtThink(); } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CPointHurt::KeyValue( const char *szKeyName, const char *szValue ) +{ + // Additional OR flags + if (FStrEq( szKeyName, "damageor" ) || FStrEq( szKeyName, "damagepresets" )) + { + m_bitsDamageType |= atoi(szValue); + } + else + return BaseClass::KeyValue( szKeyName, szValue ); + + return true; +} +#endif + diff --git a/src/game/server/pointhurt.h b/src/game/server/pointhurt.h index e82cad85..eb591346 100644 --- a/src/game/server/pointhurt.h +++ b/src/game/server/pointhurt.h @@ -18,6 +18,10 @@ public: void InputTurnOff(inputdata_t &inputdata); void InputToggle(inputdata_t &inputdata); void InputHurt(inputdata_t &inputdata); + +#ifdef MAPBASE + bool KeyValue( const char *szKeyName, const char *szValue ); +#endif DECLARE_DATADESC(); diff --git a/src/game/server/pointteleport.cpp b/src/game/server/pointteleport.cpp index 171ee780..e79f6fc3 100644 --- a/src/game/server/pointteleport.cpp +++ b/src/game/server/pointteleport.cpp @@ -23,7 +23,15 @@ class CPointTeleport : public CBaseEntity public: void Activate( void ); +#ifdef MAPBASE + void TeleportEntity( CBaseEntity *pTarget, const Vector &vecPosition, const QAngle &angAngles ); +#endif + void InputTeleport( inputdata_t &inputdata ); +#ifdef MAPBASE + void InputTeleportEntity( inputdata_t &inputdata ); + void InputTeleportToCurrentPos( inputdata_t &inputdata ); +#endif private: @@ -45,6 +53,10 @@ BEGIN_DATADESC( CPointTeleport ) DEFINE_FIELD( m_vSaveAngles, FIELD_VECTOR ), DEFINE_INPUTFUNC( FIELD_VOID, "Teleport", InputTeleport ), +#ifdef MAPBASE + DEFINE_INPUTFUNC( FIELD_EHANDLE, "TeleportEntity", InputTeleportEntity ), + DEFINE_INPUTFUNC( FIELD_VOID, "TeleportToCurrentPos", InputTeleportToCurrentPos ), +#endif END_DATADESC() @@ -107,6 +119,34 @@ void CPointTeleport::Activate( void ) BaseClass::Activate(); } +#ifdef MAPBASE +//------------------------------------------------------------------------------ +// Purpose: +//------------------------------------------------------------------------------ +void CPointTeleport::TeleportEntity( CBaseEntity *pTarget, const Vector &vecPosition, const QAngle &angAngles ) +{ + // in episodic, we have a special spawn flag that forces Gordon into a duck +#ifdef HL2_EPISODIC + if ( (m_spawnflags & SF_TELEPORT_INTO_DUCK) && pTarget->IsPlayer() ) + { + CBasePlayer *pPlayer = ToBasePlayer( pTarget ); + if ( pPlayer != NULL ) + { + pPlayer->m_nButtons |= IN_DUCK; + pPlayer->AddFlag( FL_DUCKING ); + pPlayer->m_Local.m_bDucked = true; + pPlayer->m_Local.m_bDucking = true; + pPlayer->m_Local.m_flDucktime = 0.0f; + pPlayer->SetViewOffset( VEC_DUCK_VIEW_SCALED( pPlayer ) ); + pPlayer->SetCollisionBounds( VEC_DUCK_HULL_MIN, VEC_DUCK_HULL_MAX ); + } + } +#endif + + pTarget->Teleport( &vecPosition, &angAngles, NULL ); +} +#endif + //------------------------------------------------------------------------------ // Purpose: //------------------------------------------------------------------------------ @@ -124,6 +164,9 @@ void CPointTeleport::InputTeleport( inputdata_t &inputdata ) return; } +#ifdef MAPBASE + TeleportEntity( pTarget, m_vSaveOrigin, m_vSaveAngles ); +#else // in episodic, we have a special spawn flag that forces Gordon into a duck #ifdef HL2_EPISODIC if ( (m_spawnflags & SF_TELEPORT_INTO_DUCK) && pTarget->IsPlayer() ) @@ -143,5 +186,49 @@ void CPointTeleport::InputTeleport( inputdata_t &inputdata ) #endif pTarget->Teleport( &m_vSaveOrigin, &m_vSaveAngles, NULL ); +#endif } +#ifdef MAPBASE +//------------------------------------------------------------------------------ +// Purpose: +//------------------------------------------------------------------------------ +void CPointTeleport::InputTeleportEntity( inputdata_t &inputdata ) +{ + if ( !inputdata.value.Entity() ) + { + Warning( "%s unable to find entity from TeleportEntity\n", GetDebugName() ); + return; + } + + // If teleport object is in a movement hierarchy, remove it first + if ( EntityMayTeleport( inputdata.value.Entity() ) == false ) + { + Warning("ERROR: (%s) can't teleport object (%s) as it has a parent (%s)!\n",GetDebugName(),inputdata.value.Entity()->GetDebugName(),inputdata.value.Entity()->GetMoveParent()->GetDebugName()); + return; + } + + TeleportEntity( inputdata.value.Entity(), m_vSaveOrigin, m_vSaveAngles ); +} + +//------------------------------------------------------------------------------ +// Purpose: +//------------------------------------------------------------------------------ +void CPointTeleport::InputTeleportToCurrentPos( inputdata_t &inputdata ) +{ + // Attempt to find the entity in question + CBaseEntity *pTarget = gEntList.FindEntityByName( NULL, m_target, this, inputdata.pActivator, inputdata.pCaller ); + if ( pTarget == NULL ) + return; + + // If teleport object is in a movement hierarchy, remove it first + if ( EntityMayTeleport( pTarget ) == false ) + { + Warning("ERROR: (%s) can't teleport object (%s) as it has a parent (%s)!\n",GetDebugName(),pTarget->GetDebugName(),pTarget->GetMoveParent()->GetDebugName()); + return; + } + + TeleportEntity( pTarget, GetAbsOrigin(), GetAbsAngles() ); +} +#endif + diff --git a/src/game/server/postprocesscontroller.cpp b/src/game/server/postprocesscontroller.cpp new file mode 100644 index 00000000..3e3fd6b5 --- /dev/null +++ b/src/game/server/postprocesscontroller.cpp @@ -0,0 +1,207 @@ +//========= Copyright © 1996-2007, Valve Corporation, All rights reserved. ========== +// +// An entity that allows level designer control over the post-processing parameters. +// +//=================================================================================== + +#include "cbase.h" +#include "postprocesscontroller.h" +#include "entityinput.h" +#include "entityoutput.h" +#include "eventqueue.h" +#include "player.h" +#include "world.h" +#include "ndebugoverlay.h" +#include "triggers.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +CPostProcessSystem s_PostProcessSystem( "PostProcessSystem" ); + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CPostProcessSystem *PostProcessSystem() +{ + return &s_PostProcessSystem; +} + + +LINK_ENTITY_TO_CLASS( postprocess_controller, CPostProcessController ); + +BEGIN_DATADESC( CPostProcessController ) + DEFINE_KEYFIELD( m_flPostProcessParameters[ PPPN_FADE_TIME ], FIELD_FLOAT, "fadetime" ), + DEFINE_KEYFIELD( m_flPostProcessParameters[ PPPN_LOCAL_CONTRAST_STRENGTH ], FIELD_FLOAT, "localcontraststrength" ), + DEFINE_KEYFIELD( m_flPostProcessParameters[ PPPN_LOCAL_CONTRAST_EDGE_STRENGTH ], FIELD_FLOAT, "localcontrastedgestrength" ), + DEFINE_KEYFIELD( m_flPostProcessParameters[ PPPN_VIGNETTE_START ], FIELD_TIME, "vignettestart" ), + DEFINE_KEYFIELD( m_flPostProcessParameters[ PPPN_VIGNETTE_END ], FIELD_TIME, "vignetteend" ), + DEFINE_KEYFIELD( m_flPostProcessParameters[ PPPN_VIGNETTE_BLUR_STRENGTH ], FIELD_FLOAT, "vignetteblurstrength" ), + DEFINE_KEYFIELD( m_flPostProcessParameters[ PPPN_FADE_TO_BLACK_STRENGTH ], FIELD_FLOAT, "fadetoblackstrength" ), + DEFINE_KEYFIELD( m_flPostProcessParameters[ PPPN_DEPTH_BLUR_FOCAL_DISTANCE ], FIELD_FLOAT, "depthblurfocaldistance" ), + DEFINE_KEYFIELD( m_flPostProcessParameters[ PPPN_DEPTH_BLUR_STRENGTH ], FIELD_FLOAT, "depthblurstrength" ), + DEFINE_KEYFIELD( m_flPostProcessParameters[ PPPN_SCREEN_BLUR_STRENGTH ], FIELD_FLOAT, "screenblurstrength" ), + DEFINE_KEYFIELD( m_flPostProcessParameters[ PPPN_FILM_GRAIN_STRENGTH ], FIELD_FLOAT, "filmgrainstrength" ), + + // Inputs + DEFINE_INPUTFUNC( FIELD_FLOAT, "SetFadeTime", InputSetFadeTime ), + DEFINE_INPUTFUNC( FIELD_FLOAT, "SetLocalContrastStrength", InputSetLocalContrastStrength ), + DEFINE_INPUTFUNC( FIELD_FLOAT, "SetLocalContrastEdgeStrength", InputSetLocalContrastEdgeStrength ), + DEFINE_INPUTFUNC( FIELD_FLOAT, "SetVignetteStart", InputSetVignetteStart ), + DEFINE_INPUTFUNC( FIELD_FLOAT, "SetVignetteEnd", InputSetVignetteEnd ), + DEFINE_INPUTFUNC( FIELD_FLOAT, "SetVignetteBlurStrength", InputSetVignetteBlurStrength ), + DEFINE_INPUTFUNC( FIELD_FLOAT, "SetFadeToBlackStrength", InputSetFadeToBlackStrength ), + DEFINE_INPUTFUNC( FIELD_FLOAT, "SetDepthBlurFocalDistance", InputSetDepthBlurFocalDistance), + DEFINE_INPUTFUNC( FIELD_FLOAT, "SetDepthBlurStrength", InputSetDepthBlurStrength ), + DEFINE_INPUTFUNC( FIELD_FLOAT, "SetScreenBlurStrength", InputSetScreenBlurStrength ), + DEFINE_INPUTFUNC( FIELD_FLOAT, "SetFilmGrainStrength", InputSetFilmGrainStrength ), +END_DATADESC() + +IMPLEMENT_SERVERCLASS_ST( CPostProcessController, DT_PostProcessController ) + SendPropArray3( SENDINFO_ARRAY3( m_flPostProcessParameters ), SendPropFloat( SENDINFO_ARRAY( m_flPostProcessParameters ) ) ), + SendPropBool( SENDINFO(m_bMaster) ), +END_SEND_TABLE() + + +CPostProcessController::CPostProcessController() +{ + m_bMaster = false; +} + +CPostProcessController::~CPostProcessController() +{ +} + +void CPostProcessController::Spawn() +{ + BaseClass::Spawn(); + + m_bMaster = IsMaster(); +} + +int CPostProcessController::UpdateTransmitState() +{ + return SetTransmitState( FL_EDICT_ALWAYS ); +} + +void CPostProcessController::InputSetFadeTime( inputdata_t &inputdata ) +{ + m_flPostProcessParameters.Set( PPPN_FADE_TIME, inputdata.value.Float() ); +} + +void CPostProcessController::InputSetLocalContrastStrength( inputdata_t &inputdata ) +{ + m_flPostProcessParameters.Set( PPPN_LOCAL_CONTRAST_STRENGTH, inputdata.value.Float() ); +} + +void CPostProcessController::InputSetLocalContrastEdgeStrength( inputdata_t &inputdata ) +{ + m_flPostProcessParameters.Set( PPPN_LOCAL_CONTRAST_EDGE_STRENGTH, inputdata.value.Float() ); +} + +void CPostProcessController::InputSetVignetteStart( inputdata_t &inputdata ) +{ + m_flPostProcessParameters.Set( PPPN_VIGNETTE_START, inputdata.value.Float() ); +} + +void CPostProcessController::InputSetVignetteEnd( inputdata_t &inputdata ) +{ + m_flPostProcessParameters.Set( PPPN_VIGNETTE_END, inputdata.value.Float() ); +} + +void CPostProcessController::InputSetVignetteBlurStrength( inputdata_t &inputdata ) +{ + m_flPostProcessParameters.Set( PPPN_VIGNETTE_BLUR_STRENGTH, inputdata.value.Float() ); +} + +void CPostProcessController::InputSetFadeToBlackStrength( inputdata_t &inputdata ) +{ + m_flPostProcessParameters.Set( PPPN_FADE_TO_BLACK_STRENGTH, inputdata.value.Float() ); +} + +void CPostProcessController::InputSetDepthBlurFocalDistance( inputdata_t &inputdata ) +{ + m_flPostProcessParameters.Set( PPPN_DEPTH_BLUR_FOCAL_DISTANCE, inputdata.value.Float() ); +} + +void CPostProcessController::InputSetDepthBlurStrength( inputdata_t &inputdata ) +{ + m_flPostProcessParameters.Set( PPPN_DEPTH_BLUR_STRENGTH, inputdata.value.Float() ); +} + +void CPostProcessController::InputSetScreenBlurStrength( inputdata_t &inputdata ) +{ + m_flPostProcessParameters.Set( PPPN_SCREEN_BLUR_STRENGTH, inputdata.value.Float() ); +} + +void CPostProcessController::InputSetFilmGrainStrength( inputdata_t &inputdata ) +{ + m_flPostProcessParameters.Set( PPPN_FILM_GRAIN_STRENGTH, inputdata.value.Float() ); +} + +//----------------------------------------------------------------------------- +// Purpose: Clear out the PostProcess controller. +//----------------------------------------------------------------------------- +void CPostProcessSystem::LevelInitPreEntity() +{ + m_hMasterController = nullptr; + ListenForGameEvent( "round_start" ); +} + +//----------------------------------------------------------------------------- +// Purpose: Find the master controller. If no controller is +// set as Master, use the first controller found. +//----------------------------------------------------------------------------- +void CPostProcessSystem::InitMasterController() +{ + CPostProcessController *pPostProcessController = nullptr; + + do + { + pPostProcessController = dynamic_cast( gEntList.FindEntityByClassname( pPostProcessController, "postprocess_controller" ) ); + if ( pPostProcessController ) + { + if ( m_hMasterController.Get() == nullptr ) + { + m_hMasterController = pPostProcessController; + } + else + { + if ( pPostProcessController->IsMaster() ) + { + m_hMasterController = pPostProcessController; + } + } + } + } while ( pPostProcessController ); +} + +//----------------------------------------------------------------------------- +// Purpose: On a multiplayer map restart, re-find the master controller. +//----------------------------------------------------------------------------- +void CPostProcessSystem::FireGameEvent( IGameEvent *pEvent ) +{ + InitMasterController(); +} + +//----------------------------------------------------------------------------- +// Purpose: On level load find the master PostProcess controller. If no controller is +// set as Master, use the first PostProcess controller found. +//----------------------------------------------------------------------------- +void CPostProcessSystem::LevelInitPostEntity() +{ + InitMasterController(); + + // HACK: Singleplayer games don't get a call to CBasePlayer::Spawn on level transitions. + // CBasePlayer::Activate is called before this is called so that's too soon to set up the PostProcess controller. + // We don't have a hook similar to Activate that happens after LevelInitPostEntity + // is called, or we could just do this in the player itself. + if ( gpGlobals->maxClients == 1 ) + { + CBasePlayer *pPlayer = UTIL_GetLocalPlayer(); + if ( pPlayer && ( pPlayer->m_hPostProcessCtrl.Get() == nullptr ) ) + { + pPlayer->InitPostProcessController(); + } + } +} diff --git a/src/game/server/postprocesscontroller.h b/src/game/server/postprocesscontroller.h new file mode 100644 index 00000000..5608394a --- /dev/null +++ b/src/game/server/postprocesscontroller.h @@ -0,0 +1,80 @@ +#pragma once + +#include "GameEventListener.h" +#include "postprocess_shared.h" + +// Spawn Flags +#define SF_POSTPROCESS_MASTER 0x0001 + +//============================================================================= +// Class Postprocess Controller: +//============================================================================= +class CPostProcessController : public CBaseEntity +{ +public: + DECLARE_SERVERCLASS(); + DECLARE_DATADESC(); + DECLARE_CLASS( CPostProcessController, CBaseEntity ); + + CPostProcessController(); + virtual ~CPostProcessController(); + + virtual int UpdateTransmitState(); + + // Input handlers + void InputSetFadeTime(inputdata_t &data); + void InputSetLocalContrastStrength(inputdata_t &data); + void InputSetLocalContrastEdgeStrength(inputdata_t &data); + void InputSetVignetteStart(inputdata_t &data); + void InputSetVignetteEnd(inputdata_t &data); + void InputSetVignetteBlurStrength(inputdata_t &data); + void InputSetFadeToBlackStrength(inputdata_t &data); + void InputSetDepthBlurFocalDistance(inputdata_t &data); + void InputSetDepthBlurStrength(inputdata_t &data); + void InputSetScreenBlurStrength(inputdata_t &data); + void InputSetFilmGrainStrength(inputdata_t &data); + + void InputTurnOn(inputdata_t &data); + void InputTurnOff(inputdata_t &data); + + void Spawn(); + + bool IsMaster() const { return HasSpawnFlags( SF_FOG_MASTER ); } + +public: + CNetworkArray( float, m_flPostProcessParameters, POST_PROCESS_PARAMETER_COUNT ); + + CNetworkVar( bool, m_bMaster ); +}; + +//============================================================================= +// +// Postprocess Controller System. +// +class CPostProcessSystem : public CAutoGameSystem, public CGameEventListener +{ +public: + + // Creation/Init. + CPostProcessSystem( char const *name ) : CAutoGameSystem( name ) + { + m_hMasterController = nullptr; + } + + ~CPostProcessSystem() + { + m_hMasterController = nullptr; + } + + virtual void LevelInitPreEntity(); + virtual void LevelInitPostEntity(); + virtual void FireGameEvent( IGameEvent *pEvent ); + CPostProcessController *GetMasterPostProcessController() { return m_hMasterController; } + +private: + + void InitMasterController(); + CHandle< CPostProcessController > m_hMasterController; +}; + +CPostProcessSystem *PostProcessSystem(); diff --git a/src/game/server/props.cpp b/src/game/server/props.cpp index dec14e42..b05aee2c 100644 --- a/src/game/server/props.cpp +++ b/src/game/server/props.cpp @@ -41,6 +41,11 @@ #include "physics_collisionevent.h" #include "gamestats.h" #include "vehicle_base.h" +#ifdef MAPBASE +#include "mapbase/GlobalStrings.h" +#include "collisionutils.h" +#include "vstdlib/IKeyValuesSystem.h" // From Alien Swarm SDK +#endif #ifdef TF_DLL #include "nav_mesh/tf_nav_mesh.h" @@ -83,8 +88,17 @@ ConVar func_breakdmg_explosive( "func_breakdmg_explosive", "1.25" ); ConVar sv_turbophysics( "sv_turbophysics", "0", FCVAR_REPLICATED, "Turns on turbo physics" ); +#ifdef MAPBASE +ConVar mapbase_prop_consistency_noremove("mapbase_prop_consistency_noremove", "1", FCVAR_NONE, "Prevents the removal of props when their classes do not match up with their models' propdata."); +#endif + #ifdef HL2_EPISODIC +#ifdef MAPBASE + #define PROP_FLARE_LIFETIME GetFlareLifetime() + float GetEnvFlareLifetime( CBaseEntity *pEntity ); +#else #define PROP_FLARE_LIFETIME 30.0f +#endif #define PROP_FLARE_IGNITE_SUBSTRACT 5.0f CBaseEntity *CreateFlare( Vector vOrigin, QAngle Angles, CBaseEntity *pOwner, float flDuration ); void KillFlare( CBaseEntity *pOwnerEntity, CBaseEntity *pEntity, float flKillTime ); @@ -197,6 +211,35 @@ void CBaseProp::Spawn( void ) int iResult = ParsePropData(); if ( !OverridePropdata() ) { +#ifdef MAPBASE + if (mapbase_prop_consistency_noremove.GetBool()) + { + switch (iResult) + { + case PARSE_FAILED_BAD_DATA: + Warning("%s at %.0f %.0f %0.f uses model %s, which has an invalid prop_data type. Not deleted due to mapbase_prop_consistency_noremove.\n", GetClassname(), GetAbsOrigin().x, GetAbsOrigin().y, GetAbsOrigin().z, szModel); + break; + case PARSE_FAILED_NO_DATA: + { + if ( FClassnameIs( this, "prop_physics" ) ) + { + Warning("%s at %.0f %.0f %0.f uses model %s, which has no propdata which means it should be used on a prop_static. Not deleted due to mapbase_prop_consistency_noremove.\n", GetClassname(), GetAbsOrigin().x, GetAbsOrigin().y, GetAbsOrigin().z, szModel); + } + } break; + case PARSE_SUCCEEDED: + { + if (!IsPropPhysics()) + { + Warning( "%s at %.0f %.0f %0.f uses model %s, which has propdata which means that it should be used on a prop_physics. Not deleted due to mapbase_prop_consistency_noremove.\n", GetClassname(), GetAbsOrigin().x, GetAbsOrigin().y, GetAbsOrigin().z, szModel ); + } + } + } + } + else + { + // No comment. + #define DevWarning Warning +#endif if ( iResult == PARSE_FAILED_BAD_DATA ) { DevWarning( "%s at %.0f %.0f %0.f uses model %s, which has an invalid prop_data type. DELETED.\n", GetClassname(), GetAbsOrigin().x, GetAbsOrigin().y, GetAbsOrigin().z, szModel ); @@ -216,13 +259,21 @@ void CBaseProp::Spawn( void ) else if ( iResult == PARSE_SUCCEEDED ) { // If we have data, and we're not a physics prop, fail +#ifdef MAPBASE + if ( !IsPropPhysics() ) +#else if ( !dynamic_cast(this) ) +#endif { DevWarning( "%s at %.0f %.0f %0.f uses model %s, which has propdata which means that it be used on a prop_physics. DELETED.\n", GetClassname(), GetAbsOrigin().x, GetAbsOrigin().y, GetAbsOrigin().z, szModel ); UTIL_Remove( this ); return; } } +#ifdef MAPBASE + #undef DevWarning + } +#endif } SetMoveType( MOVETYPE_PUSH ); @@ -279,7 +330,11 @@ bool CBaseProp::KeyValue( const char *szKeyName, const char *szValue ) if ( FStrEq(szKeyName, "health") ) { // Only override props are allowed to override health. +#ifdef MAPBASE + if ( OverridePropdata() && !FStrEq(szValue, "-1") ) +#else if ( FClassnameIs( this, "prop_physics_override" ) || FClassnameIs( this, "prop_dynamic_override" ) ) +#endif return BaseClass::KeyValue( szKeyName, szValue ); return true; @@ -337,8 +392,15 @@ int CBaseProp::ParsePropData( void ) return PARSE_FAILED_NO_DATA; } +#ifdef MAPBASE // From Alien Swarm SDK + static int keyPropData = KeyValuesSystem()->GetSymbolForString( "prop_data" ); + + // Do we have a props section? + KeyValues *pkvPropData = modelKeyValues->FindKey( keyPropData ); +#else // Do we have a props section? KeyValues *pkvPropData = modelKeyValues->FindKey("prop_data"); +#endif if ( !pkvPropData ) { modelKeyValues->deleteThis(); @@ -706,6 +768,41 @@ void CBreakableProp::HandleInteractionStick( int index, gamevcollisionevent_t *p } } +#ifdef MAPBASE +extern int g_interactionBarnacleVictimBite; +extern ConVar npc_barnacle_ignite; +//----------------------------------------------------------------------------- +// Purpose: Uses the new CBaseEntity interaction implementation +// Input : The type of interaction, extra info pointer, and who started it +// Output : true - if sub-class has a response for the interaction +// false - if sub-class has no response +//----------------------------------------------------------------------------- +bool CBreakableProp::HandleInteraction( int interactionType, void *data, CBaseCombatCharacter* sourceEnt ) +{ +#ifdef HL2_EPISODIC + // Allows flares to ignite barnacles. + if ( interactionType == g_interactionBarnacleVictimBite ) + { + if ( npc_barnacle_ignite.GetBool() && sourceEnt->IsOnFire() == false ) + { + sourceEnt->Ignite( 25.0f ); + KillFlare( this, m_hFlareEnt, PROP_FLARE_IGNITE_SUBSTRACT ); + IGameEvent *event = gameeventmanager->CreateEvent( "flare_ignite_npc" ); + if ( event ) + { + event->SetInt( "entindex", sourceEnt->entindex() ); + gameeventmanager->FireEvent( event ); + } + } + + return true; + } +#endif + + return BaseClass::HandleInteraction(interactionType, data, sourceEnt); +} +#endif + //----------------------------------------------------------------------------- // Purpose: Turn on prop debugging mode //----------------------------------------------------------------------------- @@ -763,6 +860,9 @@ BEGIN_DATADESC( CBreakableProp ) DEFINE_KEYFIELD( m_flPressureDelay, FIELD_FLOAT, "PressureDelay" ), DEFINE_FIELD( m_preferredCarryAngles, FIELD_VECTOR ), +#ifdef MAPBASE + DEFINE_FIELD( m_bUsesCustomCarryAngles, FIELD_BOOLEAN ), +#endif DEFINE_FIELD( m_flDefaultFadeScale, FIELD_FLOAT ), DEFINE_FIELD( m_bUsePuntSound, FIELD_BOOLEAN ), // DEFINE_FIELD( m_mpBreakMode, mp_break_t ), @@ -772,6 +872,10 @@ BEGIN_DATADESC( CBreakableProp ) DEFINE_INPUTFUNC( FIELD_INTEGER, "SetHealth", InputSetHealth ), DEFINE_INPUTFUNC( FIELD_INTEGER, "AddHealth", InputAddHealth ), DEFINE_INPUTFUNC( FIELD_INTEGER, "RemoveHealth", InputRemoveHealth ), +#ifdef MAPBASE + DEFINE_INPUTFUNC( FIELD_INTEGER, "SetInteraction", InputSetInteraction ), + DEFINE_INPUTFUNC( FIELD_INTEGER, "RemoveInteraction", InputRemoveInteraction ), +#endif DEFINE_INPUT( m_impactEnergyScale, FIELD_FLOAT, "physdamagescale" ), DEFINE_INPUTFUNC( FIELD_VOID, "EnablePhyscannonPickup", InputEnablePhyscannonPickup ), DEFINE_INPUTFUNC( FIELD_VOID, "DisablePhyscannonPickup", InputDisablePhyscannonPickup ), @@ -903,6 +1007,9 @@ void CBreakableProp::Spawn() m_impactEnergyScale = 0.1f; } +#ifdef MAPBASE + if (!m_bUsesCustomCarryAngles) +#endif m_preferredCarryAngles = QAngle( -5, 0, 0 ); // The presence of this activity causes us to have to detach it before it can be grabbed. @@ -926,6 +1033,57 @@ void CBreakableProp::Spawn() SetTouch( &CBreakableProp::BreakablePropTouch ); } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: Handles keyvalues from the BSP. Called before spawning. +//----------------------------------------------------------------------------- +bool CBreakableProp::KeyValue( const char *szKeyName, const char *szValue ) +{ + if ( OverridePropdata() ) + { + if ( FStrEq(szKeyName, "InitialInteractions") ) + { + // Only override props are allowed to override interactions. + if (strchr(szValue, ' ')) + { + // How many interactions could there possibly be? + char szInteractions[64]; + Q_strncpy(szInteractions, szValue, sizeof(szInteractions)); + + char *token = strtok(szInteractions, " ,"); + while (token) + { + SetInteraction((propdata_interactions_t)atoi(token)); + token = strtok(token, " ,"); + } + } + else + SetInteraction((propdata_interactions_t)atoi(szValue)); + } + else if ( FStrEq(szKeyName, "preferredcarryangles") ) + { + // Only detect as custom if it's non-zero + if (!FStrEq( szValue, "0" )) + { + QAngle angCarryAngles; + UTIL_StringToVector( angCarryAngles.Base(), szValue ); + + m_preferredCarryAngles = angCarryAngles; + m_bUsesCustomCarryAngles = true; + } + } + else + return BaseClass::KeyValue( szKeyName, szValue ); + } + else + { + return BaseClass::KeyValue( szKeyName, szValue ); + } + + return true; +} +#endif + //----------------------------------------------------------------------------- // Disable auto fading under dx7 or when level fades are specified @@ -1054,6 +1212,17 @@ int CBreakableProp::OnTakeDamage( const CTakeDamageInfo &inputInfo ) { m_hLastAttacker.Set( info.GetAttacker() ); } +#ifdef MAPBASE // From Alien Swarm SDK + else if ( info.GetAttacker() ) + { + CBaseEntity *attacker = info.GetAttacker(); + CBaseEntity *attackerOwner = attacker->GetOwnerEntity(); + if ( attackerOwner && attackerOwner->MyCombatCharacterPointer() ) + { + m_hLastAttacker.Set( attackerOwner ); + } + } +#endif float flPropDamage = GetBreakableDamage( info, assert_cast(this) ); info.SetDamage( flPropDamage ); @@ -1173,6 +1342,12 @@ int CBreakableProp::OnTakeDamage( const CTakeDamageInfo &inputInfo ) //----------------------------------------------------------------------------- void CBreakableProp::Event_Killed( const CTakeDamageInfo &info ) { +#ifdef MAPBASE_VSCRIPT + // False = Cheat death + if (ScriptDeathHook( const_cast(&info) ) == false) + return; +#endif + IPhysicsObject *pPhysics = VPhysicsGetObject(); if ( pPhysics && !pPhysics->IsMoveable() ) { @@ -1222,6 +1397,25 @@ void CBreakableProp::InputSetHealth( inputdata_t &inputdata ) } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: Input handler for setting interactions. +//----------------------------------------------------------------------------- +void CBreakableProp::InputSetInteraction( inputdata_t &inputdata ) +{ + SetInteraction( (propdata_interactions_t)inputdata.value.Int() ); +} + +//----------------------------------------------------------------------------- +// Purpose: Input handler for Adding interactions. +//----------------------------------------------------------------------------- +void CBreakableProp::InputRemoveInteraction( inputdata_t &inputdata ) +{ + RemoveInteraction( (propdata_interactions_t)inputdata.value.Int() ); +} +#endif + + //----------------------------------------------------------------------------- // Purpose: Choke point for changes to breakable health. Ensures outputs are fired. // Input : iNewHealth - @@ -1489,7 +1683,12 @@ void CBreakableProp::CreateFlare( float flLifetime ) int iAttachment = LookupAttachment( "fuse" ); Vector vOrigin; +#ifdef MAPBASE + if (!GetAttachment( iAttachment, vOrigin )) + vOrigin = GetLocalOrigin(); +#else GetAttachment( iAttachment, vOrigin ); +#endif pFlare->SetMoveType( MOVETYPE_NONE ); pFlare->SetSolid( SOLID_NONE ); @@ -1831,9 +2030,16 @@ BEGIN_DATADESC( CDynamicProp ) DEFINE_KEYFIELD( m_bDisableBoneFollowers, FIELD_BOOLEAN, "DisableBoneFollowers" ), DEFINE_FIELD( m_bUseHitboxesForRenderBox, FIELD_BOOLEAN ), DEFINE_FIELD( m_nPendingSequence, FIELD_SHORT ), +#ifdef MAPBASE // From Alien Swarm SDK + DEFINE_KEYFIELD( m_bUpdateAttachedChildren, FIELD_BOOLEAN, "updatechildren" ), + DEFINE_KEYFIELD( m_bHoldAnimation, FIELD_BOOLEAN, "HoldAnimation" ), +#endif // Inputs DEFINE_INPUTFUNC( FIELD_STRING, "SetAnimation", InputSetAnimation ), +#ifdef MAPBASE // From Alien Swarm SDK + DEFINE_INPUTFUNC( FIELD_STRING, "SetAnimationNoReset", InputSetAnimationNoReset ), +#endif DEFINE_INPUTFUNC( FIELD_STRING, "SetDefaultAnimation", InputSetDefaultAnimation ), DEFINE_INPUTFUNC( FIELD_VOID, "TurnOn", InputTurnOn ), DEFINE_INPUTFUNC( FIELD_VOID, "TurnOff", InputTurnOff ), @@ -2057,9 +2263,8 @@ void CDynamicProp::CreateBoneFollowers() pBone = pBone->GetNextKey(); } } - - modelKeyValues->deleteThis(); } + modelKeyValues->deleteThis(); // if we got here, we don't have a bone follower section, but if we have a ragdoll // go ahead and create default bone followers for it @@ -2197,10 +2402,23 @@ void CDynamicProp::AnimThink( void ) } else { +#ifdef MAPBASE // From Alien Swarm SDK + if ( m_iszDefaultAnim != NULL_STRING && m_bHoldAnimation == false ) + { + PropSetAnim( STRING( m_iszDefaultAnim ) ); + } + + // We need to wait for an animation change to come in + if ( m_bHoldAnimation ) + { + SetNextThink( gpGlobals->curtime + 0.1f ); + } +#else if (m_iszDefaultAnim != NULL_STRING) { PropSetAnim( STRING( m_iszDefaultAnim ) ); } +#endif } } } @@ -2212,6 +2430,17 @@ void CDynamicProp::AnimThink( void ) StudioFrameAdvance(); DispatchAnimEvents(this); m_BoneFollowerManager.UpdateBoneFollowers(this); + +#ifdef MAPBASE // From Alien Swarm SDK + // Update any SetParentAttached children + if ( m_bUpdateAttachedChildren ) + { + for ( CBaseEntity *pChild = FirstMoveChild(); pChild; pChild = pChild->NextMovePeer() ) + { + pChild->PhysicsTouchTriggers(); + } + } +#endif } @@ -2250,6 +2479,19 @@ void CDynamicProp::InputSetAnimation( inputdata_t &inputdata ) PropSetAnim( inputdata.value.String() ); } +#ifdef MAPBASE // From Alien Swarm SDK +//------------------------------------------------------------------------------ +// Purpose: Set the animation unless the prop is already set to this particular animation +//------------------------------------------------------------------------------ +void CDynamicProp::InputSetAnimationNoReset( inputdata_t &inputdata ) +{ + if ( GetSequence() != LookupSequence( inputdata.value.String() ) ) + { + PropSetAnim( inputdata.value.String() ); + } +} +#endif + //------------------------------------------------------------------------------ // Purpose: //------------------------------------------------------------------------------ @@ -2398,6 +2640,358 @@ void COrnamentProp::InputDetach( inputdata_t &inputdata ) DetachFromOwner(); } +#ifdef MAPBASE +#define SF_INTERACTABLE_USE_INTERACTS 512 // Allows +USE interaction. +#define SF_INTERACTABLE_TOUCH_INTERACTS 1024 // Allows touch interaction. +#define SF_INTERACTABLE_IGNORE_COMMANDS_WHEN_LOCKED 2048 // Completely ignores player commands when locked. +#define SF_INTERACTABLE_RADIUS_USE 4096 // Uses radius +USE + +//----------------------------------------------------------------------------- +// Purpose: Button prop for +USEable dynamic props +//----------------------------------------------------------------------------- +class CInteractableProp : public CDynamicProp +{ + DECLARE_CLASS( CInteractableProp, CDynamicProp ); +public: + DECLARE_DATADESC(); + + void Spawn(); + void Precache(); + //void Activate(); + + int ObjectCaps() + { + int caps = BaseClass::ObjectCaps(); + + if (HasSpawnFlags(SF_INTERACTABLE_USE_INTERACTS) && (!HasSpawnFlags( SF_INTERACTABLE_IGNORE_COMMANDS_WHEN_LOCKED ) || !m_bLocked)) + { + caps |= FCAP_IMPULSE_USE; + + if (HasSpawnFlags(SF_INTERACTABLE_RADIUS_USE)) + caps |= FCAP_USE_IN_RADIUS; + } + + return caps; + }; + + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void InteractablePropTouch( CBaseEntity *pOther ); + + void SetPushSequence(int iSequence); + void PushThink(); + + // Input handlers + void InputLock( inputdata_t &inputdata ); + void InputUnlock( inputdata_t &inputdata ); + void InputPress( inputdata_t &inputdata ); + + void InputEnableUseInteraction( inputdata_t &inputdata ) { AddSpawnFlags(SF_INTERACTABLE_USE_INTERACTS); } + void InputDisableUseInteraction( inputdata_t &inputdata ) { RemoveSpawnFlags(SF_INTERACTABLE_USE_INTERACTS); } + void InputEnableTouchInteraction( inputdata_t &inputdata ) { AddSpawnFlags( SF_INTERACTABLE_TOUCH_INTERACTS ); } + void InputDisableTouchInteraction( inputdata_t &inputdata ) { RemoveSpawnFlags( SF_INTERACTABLE_TOUCH_INTERACTS ); } + void InputStartIgnoringCommandsWhenLocked( inputdata_t &inputdata ) { AddSpawnFlags( SF_INTERACTABLE_IGNORE_COMMANDS_WHEN_LOCKED ); } + void InputStopIgnoringCommandsWhenLocked( inputdata_t &inputdata ) { RemoveSpawnFlags( SF_INTERACTABLE_IGNORE_COMMANDS_WHEN_LOCKED ); } + void InputEnableRadiusInteract( inputdata_t &inputdata ) { AddSpawnFlags( SF_INTERACTABLE_RADIUS_USE ); } + void InputDisableRadiusInteract( inputdata_t &inputdata ) { RemoveSpawnFlags( SF_INTERACTABLE_RADIUS_USE ); } + + COutputEvent m_OnPressed; + COutputEvent m_OnLockedUse; + COutputEvent m_OnIn; + COutputEvent m_OnOut; + + bool m_bLocked; + + float m_flCooldown; + +private: + float m_flCooldownTime; + + int m_iCurSequence = INTERACTSEQ_NONE; // Currently in a sequence + enum + { + INTERACTSEQ_NONE = -1, + INTERACTSEQ_IN, + INTERACTSEQ_OUT, + INTERACTSEQ_LOCKED, + }; + + string_t m_iszPressedSound; + string_t m_iszLockedSound; + + string_t m_iszInSequence; + string_t m_iszOutSequence; + string_t m_iszLockedSequence; + + Vector m_vecUseMins; + Vector m_vecUseMaxs; +}; + +LINK_ENTITY_TO_CLASS( prop_interactable, CInteractableProp ); + +BEGIN_DATADESC( CInteractableProp ) + + DEFINE_KEYFIELD( m_bLocked, FIELD_BOOLEAN, "Locked" ), + DEFINE_INPUT( m_flCooldown, FIELD_FLOAT, "SetCooldown" ), + DEFINE_FIELD( m_flCooldownTime, FIELD_TIME ), + DEFINE_FIELD( m_iCurSequence, FIELD_INTEGER ), + + DEFINE_KEYFIELD( m_iszPressedSound, FIELD_STRING, "PressedSound" ), + DEFINE_KEYFIELD( m_iszLockedSound, FIELD_STRING, "LockedSound" ), + DEFINE_KEYFIELD( m_iszInSequence, FIELD_STRING, "InSequence" ), + DEFINE_KEYFIELD( m_iszOutSequence, FIELD_STRING, "OutSequence" ), + DEFINE_KEYFIELD( m_iszLockedSequence, FIELD_STRING, "LockedSequence" ), + + DEFINE_KEYFIELD( m_vecUseMins, FIELD_VECTOR, "use_mins" ), + DEFINE_KEYFIELD( m_vecUseMaxs, FIELD_VECTOR, "use_maxs" ), + + // Inputs + DEFINE_INPUTFUNC( FIELD_VOID, "Lock", InputLock ), + DEFINE_INPUTFUNC( FIELD_VOID, "Unlock", InputUnlock ), + DEFINE_INPUTFUNC( FIELD_VOID, "Press", InputPress ), + + DEFINE_INPUTFUNC( FIELD_VOID, "EnableUseInteraction", InputEnableUseInteraction ), + DEFINE_INPUTFUNC( FIELD_VOID, "DisableUseInteraction", InputDisableUseInteraction ), + DEFINE_INPUTFUNC( FIELD_VOID, "EnableTouchInteraction", InputEnableTouchInteraction ), + DEFINE_INPUTFUNC( FIELD_VOID, "DisableTouchInteraction", InputDisableTouchInteraction ), + DEFINE_INPUTFUNC( FIELD_VOID, "StartIgnoringCommandsWhenLocked", InputStartIgnoringCommandsWhenLocked ), + DEFINE_INPUTFUNC( FIELD_VOID, "StopIgnoringCommandsWhenLocked", InputStopIgnoringCommandsWhenLocked ), + DEFINE_INPUTFUNC( FIELD_VOID, "EnableRadiusInteract", InputEnableRadiusInteract ), + DEFINE_INPUTFUNC( FIELD_VOID, "DisableRadiusInteract", InputDisableRadiusInteract ), + + // Outputs + DEFINE_OUTPUT( m_OnPressed, "OnPressed" ), + DEFINE_OUTPUT( m_OnLockedUse, "OnLockedUse" ), + DEFINE_OUTPUT( m_OnIn, "OnIn" ), + DEFINE_OUTPUT( m_OnOut, "OnOut" ), + + DEFINE_THINKFUNC( PushThink ), + DEFINE_ENTITYFUNC( InteractablePropTouch ), + +END_DATADESC() + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CInteractableProp::Spawn( void ) +{ + BaseClass::Spawn(); + + SetTouch( &CInteractableProp::InteractablePropTouch ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CInteractableProp::Precache( void ) +{ + BaseClass::Precache(); + + if (m_iszPressedSound != NULL_STRING) + PrecacheScriptSound( STRING(m_iszPressedSound) ); + if (m_iszLockedSound != NULL_STRING) + PrecacheScriptSound( STRING(m_iszLockedSound) ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pActivator - +// *pCaller - +// useType - +// value - +//----------------------------------------------------------------------------- +void CInteractableProp::Use(CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value) +{ + if (m_flCooldownTime > gpGlobals->curtime) + return; + + // If we're using +USE mins/maxs, make sure this is being +USE'd from the right place + if (m_vecUseMins.LengthSqr() != 0.0f && m_vecUseMaxs.LengthSqr() != 0.0f) + { + CBasePlayer *pPlayer = ToBasePlayer(pActivator); + if (pPlayer) + { + Vector forward; + pPlayer->EyeVectors(&forward, NULL, NULL); + + // This might be a little convoluted and/or seem needlessly expensive, but I couldn't figure out any better way to do this. + // TOOD: Can we calculate a box in local space instead of world space? + Vector vecWorldMins, vecWorldMaxs; + RotateAABB(EntityToWorldTransform(), m_vecUseMins, m_vecUseMaxs, vecWorldMins, vecWorldMaxs); + TransformAABB(EntityToWorldTransform(), vecWorldMins, vecWorldMaxs, vecWorldMins, vecWorldMaxs); + if (!IsBoxIntersectingRay(vecWorldMins, vecWorldMaxs, pPlayer->EyePosition(), forward * 1024)) + { + // Reject this +USE if it's not in our box + DevMsg("Outside of +USE box\n"); + return; + } + } + } + + int nSequence = -1; + + if (m_bLocked) + { + m_OnLockedUse.FireOutput(pActivator, this); + EmitSound(STRING(m_iszLockedSound)); + nSequence = LookupSequence(STRING(m_iszLockedSequence)); + m_iCurSequence = INTERACTSEQ_LOCKED; + } + else + { + m_OnPressed.FireOutput(pActivator, this); + EmitSound(STRING(m_iszPressedSound)); + nSequence = LookupSequence(STRING(m_iszInSequence)); + m_iCurSequence = INTERACTSEQ_IN; + } + + if (nSequence > ACTIVITY_NOT_AVAILABLE) + { + SetPushSequence(nSequence); + + // We still fire our inherited animation outputs + m_pOutputAnimBegun.FireOutput(pActivator, this); + } + + if (m_flCooldown == -1 && !m_bLocked){ + m_flCooldownTime = FLT_MAX; // yeah we're not going to hit this any time soon + } + else if (m_flCooldown == -1){ + m_flCooldownTime = gpGlobals->curtime + 1.0f; // 1s cooldown if locked + } + else{ + m_flCooldownTime = gpGlobals->curtime + m_flCooldown; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pOther - +//----------------------------------------------------------------------------- +void CInteractableProp::InteractablePropTouch( CBaseEntity *pOther ) +{ + // Do base touch function first + BreakablePropTouch( pOther ); + + if ( HasSpawnFlags(SF_INTERACTABLE_TOUCH_INTERACTS) && (!HasSpawnFlags(SF_INTERACTABLE_IGNORE_COMMANDS_WHEN_LOCKED) || !m_bLocked) && pOther->IsPlayer() ) + { + Use( pOther, pOther, USE_ON, 0 ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CInteractableProp::InputLock( inputdata_t &inputdata ) +{ + m_bLocked = true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CInteractableProp::InputUnlock( inputdata_t &inputdata ) +{ + m_bLocked = false; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CInteractableProp::InputPress( inputdata_t &inputdata ) +{ + Use( inputdata.pActivator, inputdata.pCaller, USE_ON, 0 ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CInteractableProp::SetPushSequence( int iSequence ) +{ + m_iGoalSequence = iSequence; + + int nNextSequence; + float nextCycle; + float flInterval = 0.1f; + + if (GotoSequence( GetSequence(), GetCycle(), GetPlaybackRate(), m_iGoalSequence, nNextSequence, nextCycle, m_iTransitionDirection )) + { + FinishSetSequence( nNextSequence ); + } + + SetThink( &CInteractableProp::PushThink ); + if ( GetNextThink() <= gpGlobals->curtime ) + SetNextThink( gpGlobals->curtime + flInterval ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CInteractableProp::PushThink() +{ + if ( m_nPendingSequence != -1 ) + { + FinishSetSequence( m_nPendingSequence ); + m_nPendingSequence = -1; + } + + SetNextThink( gpGlobals->curtime + 0.1f ); + + if ( ((m_iTransitionDirection > 0 && GetCycle() >= 0.999f) || (m_iTransitionDirection < 0 && GetCycle() <= 0.0f)) && !SequenceLoops() ) + { + if (!SequenceLoops()) + { + // We still fire our inherited animation outputs + m_pOutputAnimOver.FireOutput(NULL, this); + } + + if (m_iCurSequence == INTERACTSEQ_OUT) + { + m_OnOut.FireOutput( NULL, this ); + + m_iCurSequence = INTERACTSEQ_NONE; + } + else + { + m_OnIn.FireOutput( NULL, this ); + } + } + + StudioFrameAdvance(); + DispatchAnimEvents(this); + m_BoneFollowerManager.UpdateBoneFollowers(this); + + if (m_flCooldownTime < gpGlobals->curtime) + { + if (m_iCurSequence == INTERACTSEQ_IN) + { + int nSequence = LookupSequence( STRING(m_iszOutSequence) ); + if ( m_iszOutSequence != NULL_STRING && nSequence > ACTIVITY_NOT_AVAILABLE ) + { + m_iCurSequence = INTERACTSEQ_OUT; + SetPushSequence(nSequence); + + // We still fire our inherited animation outputs + m_pOutputAnimBegun.FireOutput( NULL, this ); + } + else + { + m_iCurSequence = INTERACTSEQ_NONE; + } + } + + if (m_iCurSequence == INTERACTSEQ_NONE) + { + if (m_iszDefaultAnim != NULL_STRING) + { + PropSetAnim( STRING( m_iszDefaultAnim ) ); + } + + SetNextThink( TICK_NEVER_THINK ); + } + } +} +#endif + //============================================================================= // PHYSICS PROPS @@ -2413,6 +3007,9 @@ BEGIN_DATADESC( CPhysicsProp ) DEFINE_INPUTFUNC( FIELD_VOID, "Wake", InputWake ), DEFINE_INPUTFUNC( FIELD_VOID, "Sleep", InputSleep ), DEFINE_INPUTFUNC( FIELD_VOID, "DisableFloating", InputDisableFloating ), +#ifdef MAPBASE + DEFINE_INPUTFUNC( FIELD_BOOLEAN, "SetDebris", InputSetDebris ), +#endif DEFINE_FIELD( m_bAwake, FIELD_BOOLEAN ), @@ -2427,6 +3024,7 @@ BEGIN_DATADESC( CPhysicsProp ) DEFINE_OUTPUT( m_MotionEnabled, "OnMotionEnabled" ), DEFINE_OUTPUT( m_OnPhysGunPickup, "OnPhysGunPickup" ), DEFINE_OUTPUT( m_OnPhysGunOnlyPickup, "OnPhysGunOnlyPickup" ), + DEFINE_OUTPUT( m_OnPhysGunPull, "OnPhysGunPull" ), DEFINE_OUTPUT( m_OnPhysGunPunt, "OnPhysGunPunt" ), DEFINE_OUTPUT( m_OnPhysGunDrop, "OnPhysGunDrop" ), DEFINE_OUTPUT( m_OnPlayerUse, "OnPlayerUse" ), @@ -2477,10 +3075,15 @@ void CPhysicsProp::Spawn( ) { g_ActiveGibCount++; } + // Condense classname's to one, except for "prop_physics_override" if ( FClassnameIs( this, "physics_prop" ) ) { +#ifdef MAPBASE + m_iClassname = gm_isz_class_PropPhysics; +#else SetClassname( "prop_physics" ); +#endif } BaseClass::Spawn(); @@ -2489,10 +3092,17 @@ void CPhysicsProp::Spawn( ) return; // Now condense all classnames to one +#ifdef MAPBASE + if ( EntIsClass( this, gm_isz_class_PropPhysicsOverride ) ) + { + m_iClassname = gm_isz_class_PropPhysics; + } +#else if ( FClassnameIs( this, "prop_physics_override") ) { SetClassname( "prop_physics" ); } +#endif if ( HasSpawnFlags( SF_PHYSPROP_DEBRIS ) || HasInteraction( PROPINTER_PHYSGUN_CREATE_FLARE ) ) { @@ -2642,7 +3252,11 @@ bool CPhysicsProp::CanBePickedUpByPhyscannon( void ) //----------------------------------------------------------------------------- bool CPhysicsProp::OverridePropdata( void ) { +#ifdef MAPBASE + return EntIsClass(this, gm_isz_class_PropPhysicsOverride); +#else return ( FClassnameIs(this, "prop_physics_override" ) ); +#endif } //----------------------------------------------------------------------------- @@ -2695,6 +3309,25 @@ void CPhysicsProp::InputDisableFloating( inputdata_t &inputdata ) PhysEnableFloating( VPhysicsGetObject(), false ); } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: Adds or removes the debris spawnflag. +//----------------------------------------------------------------------------- +void CPhysicsProp::InputSetDebris( inputdata_t &inputdata ) +{ + if (inputdata.value.Bool()) + { + AddSpawnFlags(SF_PHYSPROP_DEBRIS); + SetCollisionGroup(HasSpawnFlags(SF_PHYSPROP_FORCE_TOUCH_TRIGGERS) ? COLLISION_GROUP_DEBRIS_TRIGGER : COLLISION_GROUP_DEBRIS); + } + else + { + RemoveSpawnFlags(SF_PHYSPROP_DEBRIS); + SetCollisionGroup(COLLISION_GROUP_INTERACTIVE); // Is this the default collision group? + } +} +#endif + //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- @@ -2762,6 +3395,13 @@ void CPhysicsProp::OnPhysGunPickup( CBasePlayer *pPhysGunUser, PhysGunPickup_t r CheckRemoveRagdolls(); } +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPhysicsProp::OnPhysGunPull( CBasePlayer* pPhysGunUser ) { + m_OnPhysGunPull.FireOutput(pPhysGunUser, this); +} + //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- @@ -3514,6 +4154,11 @@ enum void PlayLockSounds(CBaseEntity *pEdict, locksound_t *pls, int flocked, int fbutton); +#ifdef MAPBASE +ConVar ai_door_enable_acts( "ai_door_enable_acts", "0", FCVAR_NONE, "Enables the new door-opening activities by default. Override keyvalues will override this cvar." ); +ConVar ai_door_open_dist_override( "ai_door_open_dist_override", "-1", FCVAR_NONE, "Overrides the distance from a door a NPC has to navigate to in order to open a door." ); +#endif + BEGIN_DATADESC_NO_BASE(locksound_t) DEFINE_FIELD( sLockedSound, FIELD_STRING), @@ -3540,6 +4185,11 @@ BEGIN_DATADESC(CBasePropDoor) DEFINE_KEYFIELD(m_SoundClose, FIELD_SOUNDNAME, "soundcloseoverride"), DEFINE_KEYFIELD(m_ls.sLockedSound, FIELD_SOUNDNAME, "soundlockedoverride"), DEFINE_KEYFIELD(m_ls.sUnlockedSound, FIELD_SOUNDNAME, "soundunlockedoverride"), +#ifdef MAPBASE + DEFINE_KEYFIELD(m_flNPCOpenDistance, FIELD_FLOAT, "opendistoverride"), + DEFINE_KEYFIELD(m_eNPCOpenFrontActivity, FIELD_INTEGER, "openfrontactivityoverride"), + DEFINE_KEYFIELD(m_eNPCOpenBackActivity, FIELD_INTEGER, "openbackactivityoverride"), +#endif DEFINE_KEYFIELD(m_SlaveName, FIELD_STRING, "slavename" ), DEFINE_FIELD(m_bLocked, FIELD_BOOLEAN), //DEFINE_KEYFIELD(m_flBlockDamage, FIELD_FLOAT, "dmg"), @@ -3556,6 +4206,16 @@ BEGIN_DATADESC(CBasePropDoor) DEFINE_INPUTFUNC(FIELD_VOID, "Toggle", InputToggle), DEFINE_INPUTFUNC(FIELD_VOID, "Lock", InputLock), DEFINE_INPUTFUNC(FIELD_VOID, "Unlock", InputUnlock), +#ifdef MAPBASE + DEFINE_INPUTFUNC(FIELD_VOID, "AllowPlayerUse", InputAllowPlayerUse), + DEFINE_INPUTFUNC(FIELD_VOID, "DisallowPlayerUse", InputDisallowPlayerUse), + + DEFINE_INPUTFUNC( FIELD_STRING, "SetFullyOpenSound", InputSetFullyOpenSound ), + DEFINE_INPUTFUNC( FIELD_STRING, "SetFullyClosedSound", InputSetFullyClosedSound ), + DEFINE_INPUTFUNC( FIELD_STRING, "SetMovingSound", InputSetMovingSound ), + DEFINE_INPUTFUNC( FIELD_STRING, "SetLockedSound", InputSetLockedSound ), + DEFINE_INPUTFUNC( FIELD_STRING, "SetUnlockedSound", InputSetUnlockedSound ), +#endif DEFINE_OUTPUT(m_OnBlockedOpening, "OnBlockedOpening"), DEFINE_OUTPUT(m_OnBlockedClosing, "OnBlockedClosing"), @@ -3577,9 +4237,42 @@ END_DATADESC() IMPLEMENT_SERVERCLASS_ST(CBasePropDoor, DT_BasePropDoor) END_SEND_TABLE() +#ifdef MAPBASE_VSCRIPT +BEGIN_ENT_SCRIPTDESC( CBasePropDoor, CBaseAnimating, "The base class used by prop doors, such as prop_door_rotating." ) + + DEFINE_SCRIPTFUNC_NAMED( ScriptIsDoorOpen, "IsDoorOpen", "" ) + DEFINE_SCRIPTFUNC_NAMED( ScriptIsDoorAjar, "IsDoorAjar", "" ) + DEFINE_SCRIPTFUNC_NAMED( ScriptIsDoorOpening, "IsDoorOpening", "" ) + DEFINE_SCRIPTFUNC_NAMED( ScriptIsDoorClosed, "IsDoorClosed", "" ) + DEFINE_SCRIPTFUNC_NAMED( ScriptIsDoorClosing, "IsDoorClosing", "" ) + DEFINE_SCRIPTFUNC_NAMED( ScriptIsDoorLocked, "IsDoorLocked", "" ) + DEFINE_SCRIPTFUNC_NAMED( ScriptIsDoorBlocked, "IsDoorBlocked", "" ) + + DEFINE_SCRIPTFUNC_NAMED( ScriptGetActivator, "GetActivator", "" ) + DEFINE_SCRIPTFUNC_NAMED( ScriptGetDoorList, "GetDoorList", "Get connected door entity by index." ) + DEFINE_SCRIPTFUNC( GetDoorListCount, "Get number of connected doors." ) + + DEFINE_SCRIPTFUNC_NAMED( ScriptGetFullyOpenSound, "GetFullyOpenSound", "" ) + DEFINE_SCRIPTFUNC_NAMED( ScriptGetFullyClosedSound, "GetFullyClosedSound", "" ) + DEFINE_SCRIPTFUNC_NAMED( ScriptGetMovingSound, "GetMovingSound", "" ) + DEFINE_SCRIPTFUNC_NAMED( ScriptGetLockedSound, "GetLockedSound", "" ) + DEFINE_SCRIPTFUNC_NAMED( ScriptGetUnlockedSound, "GetUnlockedSound", "" ) + + DEFINE_SCRIPTFUNC( DoorCanClose, "Return true if the door has room to close. Boolean is for whether or not this is an automatic close and not manually triggered by someone." ) + DEFINE_SCRIPTFUNC( DoorCanOpen, "Return true if there are other doors connected to this one." ) + DEFINE_SCRIPTFUNC( HasSlaves, "" ) + +END_SCRIPTDESC(); +#endif + CBasePropDoor::CBasePropDoor( void ) { m_hMaster = NULL; +#ifdef MAPBASE + m_flNPCOpenDistance = -1; + m_eNPCOpenFrontActivity = ACT_INVALID; + m_eNPCOpenBackActivity = ACT_INVALID; +#endif } //----------------------------------------------------------------------------- @@ -3651,6 +4344,32 @@ void CBasePropDoor::Precache(void) } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: Handles keyvalues from the BSP. Called before spawning. +//----------------------------------------------------------------------------- +bool CBasePropDoor::KeyValue( const char *szKeyName, const char *szValue ) +{ + if ( FStrEq(szKeyName, "openfrontactivityoverride") ) + { + m_eNPCOpenFrontActivity = (Activity)CAI_BaseNPC::GetActivityID( szValue ); + if (m_eNPCOpenFrontActivity == ACT_INVALID) + m_eNPCOpenFrontActivity = ActivityList_RegisterPrivateActivity( szValue ); + } + else if ( FStrEq(szKeyName, "openbackactivityoverride") ) + { + m_eNPCOpenBackActivity = (Activity)CAI_BaseNPC::GetActivityID( szValue ); + if (m_eNPCOpenBackActivity == ACT_INVALID) + m_eNPCOpenBackActivity = ActivityList_RegisterPrivateActivity( szValue ); + } + else + return BaseClass::KeyValue( szKeyName, szValue ); + + return true; +} +#endif + + //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- @@ -3777,8 +4496,44 @@ void CBasePropDoor::CalcDoorSounds() { strSoundLocked = AllocPooledString( pkvHardwareData->GetString( "locked" ) ); strSoundUnlocked = AllocPooledString( pkvHardwareData->GetString( "unlocked" ) ); + +#ifdef MAPBASE + if (ai_door_enable_acts.GetBool()) + { + if (m_eNPCOpenFrontActivity == ACT_INVALID) + { + const char *pszActivity = pkvHardwareData->GetString( "activity_front" ); + if (pszActivity[0] != '\0') + { + m_eNPCOpenFrontActivity = (Activity)CAI_BaseNPC::GetActivityID( pszActivity ); + if (m_eNPCOpenFrontActivity == ACT_INVALID) + m_eNPCOpenFrontActivity = ActivityList_RegisterPrivateActivity( pszActivity ); + } + } + if (m_eNPCOpenBackActivity == ACT_INVALID) + { + const char *pszActivity = pkvHardwareData->GetString( "activity_back" ); + if (pszActivity[0] != '\0') + { + m_eNPCOpenBackActivity = (Activity)CAI_BaseNPC::GetActivityID( pszActivity ); + if (m_eNPCOpenBackActivity == ACT_INVALID) + m_eNPCOpenBackActivity = ActivityList_RegisterPrivateActivity( pszActivity ); + } + } + } + + if (m_flNPCOpenDistance == -1) + m_flNPCOpenDistance = pkvHardwareData->GetFloat( "npc_distance", ai_door_enable_acts.GetBool() ? 32.0 : 64.0 ); +#endif } +#ifdef MAPBASE + // This would still be -1 if the hardware wasn't valid + if (m_flNPCOpenDistance == -1) + m_flNPCOpenDistance = ai_door_enable_acts.GetBool() ? 32.0 : 64.0; +#endif + + // If any sounds were missing, try the "defaults" block. if ( ( strSoundOpen == NULL_STRING ) || ( strSoundClose == NULL_STRING ) || ( strSoundMoving == NULL_STRING ) || ( strSoundLocked == NULL_STRING ) || ( strSoundUnlocked == NULL_STRING ) ) @@ -3838,7 +4593,12 @@ void CBasePropDoor::UpdateAreaPortals(bool isOpen) return; CBaseEntity *pPortal = NULL; +#ifdef MAPBASE + // For func_areaportal_oneway. + while ((pPortal = gEntList.FindEntityByClassname(pPortal, "func_areaportal*")) != NULL) +#else while ((pPortal = gEntList.FindEntityByClassname(pPortal, "func_areaportal")) != NULL) +#endif { if (pPortal->HasTarget(name)) { @@ -3978,6 +4738,54 @@ void CBasePropDoor::InputOpenAwayFrom(inputdata_t &inputdata) } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBasePropDoor::InputSetFullyOpenSound( inputdata_t &inputdata ) +{ + m_SoundOpen = inputdata.value.StringID(); + PrecacheScriptSound( STRING( m_SoundOpen ) ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBasePropDoor::InputSetFullyClosedSound( inputdata_t &inputdata ) +{ + m_SoundClose = inputdata.value.StringID(); + PrecacheScriptSound( STRING( m_SoundClose ) ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBasePropDoor::InputSetMovingSound( inputdata_t &inputdata ) +{ + m_SoundMoving = inputdata.value.StringID(); + PrecacheScriptSound( STRING( m_SoundMoving ) ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBasePropDoor::InputSetLockedSound( inputdata_t &inputdata ) +{ + m_ls.sLockedSound = inputdata.value.StringID(); + PrecacheScriptSound( STRING( m_ls.sLockedSound ) ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBasePropDoor::InputSetUnlockedSound( inputdata_t &inputdata ) +{ + m_ls.sUnlockedSound = inputdata.value.StringID(); + PrecacheScriptSound( STRING( m_ls.sUnlockedSound ) ); +} +#endif + + //----------------------------------------------------------------------------- // Purpose: // @@ -4031,6 +4839,25 @@ void CBasePropDoor::InputUnlock(inputdata_t &inputdata) } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: Input handler that makes the door usable for players. +//----------------------------------------------------------------------------- +void CBasePropDoor::InputAllowPlayerUse(inputdata_t &inputdata) +{ + RemoveSpawnFlags(SF_DOOR_IGNORE_USE); +} + +//----------------------------------------------------------------------------- +// Purpose: Input handler that makes the door unusable for players. +//----------------------------------------------------------------------------- +void CBasePropDoor::InputDisallowPlayerUse(inputdata_t &inputdata) +{ + AddSpawnFlags(SF_DOOR_IGNORE_USE); +} +#endif + + //----------------------------------------------------------------------------- // Purpose: Locks the door so that it cannot be opened. //----------------------------------------------------------------------------- @@ -4619,6 +5446,12 @@ public: if ( pPhysics->IsMoveable() && pPhysics->GetMass() < 32 ) return false; } + +#ifdef MAPBASE + // They're children, for goodness sake! + if (pEntity->GetParent() == EntityFromEntityHandle(m_pDoor)) + return false; +#endif } return true; @@ -4641,6 +5474,11 @@ inline void TraceHull_Door( const CBasePropDoor *pDoor, const Vector &vecAbsStar enginetrace->TraceRay( ray, mask, &traceFilter, ptr ); } +#ifdef MAPBASE +// This was still broken when it was scrapped. +//#define DOOR_BREAKING_STUFF 1 +#endif + // Check directions for door movement enum doorCheck_e { @@ -4665,6 +5503,15 @@ enum PropDoorRotatingOpenDirection_e DOOR_ROTATING_OPEN_BACKWARD, }; +#ifdef DOOR_BREAKING_STUFF +enum PropDoorRotatingBreakType_e +{ + DOOR_ROTATING_BREAK_NORMAL = 0, // Base behavior. + DOOR_ROTATING_BREAK_PHYS, // Turn into a physics prop via phys_conversion. + DOOR_ROTATING_BREAK_PHYS_HINGE, // Same as above, but use a phys_hinge. +}; +#endif + //=============================================== // Rotating prop door //=============================================== @@ -4707,6 +5554,15 @@ public: virtual void ComputeDoorExtent( Extent *extent, unsigned int extentType ); // extent contains the volume encompassing open + closed states +#ifdef DOOR_BREAKING_STUFF + void Break( CBaseEntity *pBreaker, const CTakeDamageInfo &info ); +#endif + +#ifdef MAPBASE + // Filters don't work well with the way doors are considered obstructions, so it's just a spawnflag that stops all NPCs for now. + virtual bool PassesDoorFilter(CBaseEntity *pEntity) { return !HasSpawnFlags(SF_DOOR_NONPCS); } +#endif + DECLARE_DATADESC(); private: @@ -4729,6 +5585,9 @@ private: PropDoorRotatingSpawnPos_t m_eSpawnPosition; PropDoorRotatingOpenDirection_e m_eOpenDirection; +#ifdef DOOR_BREAKING_STUFF + PropDoorRotatingBreakType_e m_eBreakType; +#endif QAngle m_angRotationAjar; // Angles to spawn at if we are set to spawn ajar. QAngle m_angRotationClosed; // Our angles when we are fully closed. @@ -4749,6 +5608,9 @@ private: BEGIN_DATADESC(CPropDoorRotating) DEFINE_KEYFIELD(m_eSpawnPosition, FIELD_INTEGER, "spawnpos"), DEFINE_KEYFIELD(m_eOpenDirection, FIELD_INTEGER, "opendir" ), +#ifdef DOOR_BREAKING_STUFF + DEFINE_KEYFIELD(m_eBreakType, FIELD_INTEGER, "breaktype" ), +#endif DEFINE_KEYFIELD(m_vecAxis, FIELD_VECTOR, "axis"), DEFINE_KEYFIELD(m_flDistance, FIELD_FLOAT, "distance"), DEFINE_KEYFIELD( m_angRotationAjar, FIELD_VECTOR, "ajarangles" ), @@ -5372,20 +6234,37 @@ void CPropDoorRotating::GetNPCOpenData(CAI_BaseNPC *pNPC, opendata_t &opendata) Vector vecNPCOrigin = pNPC->GetAbsOrigin(); +#ifdef MAPBASE + float flPosOffset = ai_door_open_dist_override.GetFloat() >= 0.0f ? ai_door_open_dist_override.GetFloat() : GetNPCOpenDistance(); +#else + float flPosOffset = 64; +#endif + if (pNPC->GetAbsOrigin().Dot(vecForward) > GetAbsOrigin().Dot(vecForward)) { // In front of the door relative to the door's forward vector. - opendata.vecStandPos += vecForward * 64; + opendata.vecStandPos += vecForward * flPosOffset; opendata.vecFaceDir = -vecForward; +#ifdef MAPBASE + opendata.eActivity = GetNPCOpenFrontActivity(); +#endif } else { // Behind the door relative to the door's forward vector. - opendata.vecStandPos -= vecForward * 64; + opendata.vecStandPos -= vecForward * flPosOffset; opendata.vecFaceDir = vecForward; +#ifdef MAPBASE + opendata.eActivity = GetNPCOpenBackActivity(); +#endif } +#ifdef MAPBASE + if (opendata.eActivity == ACT_INVALID) + opendata.eActivity = ACT_OPEN_DOOR; +#else opendata.eActivity = ACT_OPEN_DOOR; +#endif } @@ -5417,6 +6296,14 @@ int CPropDoorRotating::DrawDebugTextOverlays(void) EntityText( text_offset, tempstr, 0); text_offset++; +#ifdef MAPBASE // From Alien Swarm SDK + if ( IsDoorLocked() ) + { + EntityText( text_offset, "LOCKED", 0); + text_offset++; + } +#endif + if ( IsDoorOpen() ) { Q_strncpy(tempstr, "DOOR STATE: OPEN", sizeof(tempstr)); @@ -5457,19 +6344,131 @@ void CPropDoorRotating::InputSetRotationDistance( inputdata_t &inputdata ) CalculateDoorVolume( GetLocalAngles(), m_angRotationOpenBack, &m_vecBackBoundsMin, &m_vecBackBoundsMax ); } +#ifdef DOOR_BREAKING_STUFF +//extern bool TransferPhysicsObject( CBaseEntity *pFrom, CBaseEntity *pTo, bool wakeUp ); +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPropDoorRotating::Break( CBaseEntity *pBreaker, const CTakeDamageInfo &info ) +{ + if (m_eBreakType == DOOR_ROTATING_BREAK_NORMAL) + return BaseClass::Break( pBreaker, info ); + + if (m_eBreakType == DOOR_ROTATING_BREAK_PHYS || m_eBreakType == DOOR_ROTATING_BREAK_PHYS_HINGE) + { + DevMsg("Should break into physics\n"); + UnlinkFromParent( this ); + + CBaseEntity *pPhys = CreateNoSpawn( "prop_physics", GetLocalOrigin(), GetLocalAngles() ); + if ( pPhys ) + { + pPhys->SetModelName( GetModelName() ); + + pPhys->m_nRenderMode = m_nRenderMode; + pPhys->m_nRenderFX = m_nRenderFX; + const color32 rclr = GetRenderColor(); + pPhys->SetRenderColor(rclr.r, rclr.g, rclr.b, rclr.a); + + CBaseAnimating *pPhysAnimating = pPhys->GetBaseAnimating(); + + pPhysAnimating->m_nSkin = m_nSkin; + pPhysAnimating->m_nBody = m_nBody; + pPhysAnimating->SetModelScale(GetModelScale()); + + pPhys->SetName( GetEntityName() ); + + UTIL_TransferPoseParameters( this, pPhys ); + TransferChildren( this, pPhys ); + + AddSolidFlags( FSOLID_NOT_SOLID ); + AddEffects( EF_NODRAW ); + + + PhysBreakSound( this, VPhysicsGetObject(), WorldSpaceCenter() ); + + DispatchSpawn(pPhys); + + // Transferring the physics object in this case has proven to be buggy. + if (pPhys->VPhysicsGetObject()) //if ( !TransferPhysicsObject( this, pPhys, true ) ) + { + //pPhys->VPhysicsInitNormal( SOLID_VPHYSICS, 0, false ); + + pPhys->VPhysicsGetObject()->SetMaterialIndex( VPhysicsGetObject()->GetMaterialIndex() ); + } + + if (m_eBreakType == DOOR_ROTATING_BREAK_PHYS_HINGE) + { + DevMsg("Should break with hinge\n"); + // This is the point where names get a little weird. + if (GetEntityName() != NULL_STRING) + SetName(NULL_STRING); + else + { + // Since we don't have a name, but the designer wants a hinge, give the new prop a name for the hinge to target. + pPhys->SetName(AllocPooledString(UTIL_VarArgs("_physdoor%i", entindex()))); + } + + CBaseEntity *pHinge = CreateNoSpawn("phys_hinge", GetLocalOrigin(), GetLocalAngles()); + pHinge->SetName(AllocPooledString(UTIL_VarArgs("%s_createdhinge", STRING(pPhys->GetEntityName())))); + pHinge->KeyValue("attach1", STRING(pPhys->GetEntityName())); + pHinge->KeyValue("hingeaxis", m_vecAxis); + pHinge->KeyValue("breaksound", "Metal_Box.BulletImpact"); + + DispatchSpawn(pHinge); + } + + BaseClass::Break( pBreaker, info ); + + //UTIL_Remove( this ); + + pPhys->VPhysicsTakeDamage(info); + + //if (pPhys->VPhysicsGetObject()) + // pPhys->VPhysicsGetObject()->ApplyForceOffset(info.GetDamageForce(), info.GetDamagePosition()); + } + else + { + BaseClass::Break( pBreaker, info ); + } + } +} +#endif + +void CPropDoorRotating::InputSetSpeed(inputdata_t &inputdata) +{ + AssertMsg1(inputdata.value.Float() > 0.0f, "InputSetSpeed on %s called with negative parameter!", GetDebugName() ); + m_flSpeed = inputdata.value.Float(); + DoorResume(); +} + // Debug sphere class CPhysSphere : public CPhysicsProp { DECLARE_CLASS( CPhysSphere, CPhysicsProp ); +#ifdef MAPBASE + DECLARE_DATADESC(); +#endif public: +#ifdef MAPBASE + float m_fRadius; +#else virtual bool OverridePropdata() { return true; } +#endif bool CreateVPhysics() { SetSolid( SOLID_BBOX ); +#ifdef MAPBASE + SetCollisionBounds( -Vector(m_fRadius), Vector(m_fRadius) ); +#else SetCollisionBounds( -Vector(12,12,12), Vector(12,12,12) ); +#endif objectparams_t params = g_PhysDefaultObjectParams; params.pGameData = static_cast(this); +#ifdef MAPBASE + IPhysicsObject *pPhysicsObject = physenv->CreateSphereObject( m_fRadius, GetModelPtr()->GetRenderHdr()->textureindex, GetAbsOrigin(), GetAbsAngles(), ¶ms, false ); +#else IPhysicsObject *pPhysicsObject = physenv->CreateSphereObject( 12, 0, GetAbsOrigin(), GetAbsAngles(), ¶ms, false ); +#endif if ( pPhysicsObject ) { VPhysicsSetObject( pPhysicsObject ); @@ -5481,16 +6480,135 @@ public: } }; -void CPropDoorRotating::InputSetSpeed(inputdata_t &inputdata) -{ - AssertMsg1(inputdata.value.Float() > 0.0f, "InputSetSpeed on %s called with negative parameter!", GetDebugName() ); - m_flSpeed = inputdata.value.Float(); - DoorResume(); -} +#ifdef MAPBASE +BEGIN_DATADESC( CPhysSphere ) + DEFINE_KEYFIELD( m_fRadius, FIELD_FLOAT, "radius"), +END_DATADESC() +#endif LINK_ENTITY_TO_CLASS( prop_sphere, CPhysSphere ); +#if defined(MAPBASE) && defined(HL2_EPISODIC) +// ------------------------------------------------------------------------------------------ // +// Flare class for higher interaction possibilities, inspired by Black Mesa +// ------------------------------------------------------------------------------------------ // +class CPropFlare : public CPhysicsProp +{ + DECLARE_CLASS( CPropFlare, CPhysicsProp ); + DECLARE_DATADESC(); +public: + + void Precache() + { + BaseClass::Precache(); + + if (GetModelName() != NULL_STRING) + { + PrecacheModel(STRING(GetModelName())); + } + else + { + PrecacheModel("models/props_junk/flare.mdl"); + } + } + + void Spawn() + { + if (GetModelName() == NULL_STRING) + { + // Must've been spawned with ent_create or something + SetModelName(AllocPooledString("models/props_junk/flare.mdl")); + //SetModel("models/props_junk/flare.mdl"); + } + + if (!HasInteraction(PROPINTER_PHYSGUN_CREATE_FLARE)) + { + SetInteraction(PROPINTER_PHYSGUN_CREATE_FLARE); + } + + SetClassname( "prop_physics" ); + + return BaseClass::Spawn(); + } + + bool OverridePropdata( void ) { return true; } + + virtual float GetFlareLifetime() { return m_flFlareLifetime; } + + void InputStartFlare( inputdata_t &inputdata ) + { + CreateFlare( PROP_FLARE_LIFETIME ); + } + void InputStopFlare( inputdata_t &inputdata ) + { + KillFlare( this, m_hFlareEnt, PROP_FLARE_IGNITE_SUBSTRACT ); + } + + void InputAddFlareLifetime( inputdata_t &inputdata ) + { + if (m_hFlareEnt) + { + KillFlare( this, m_hFlareEnt, (inputdata.value.Float() * -1) ); + } + else + { + CreateFlare( inputdata.value.Float() ); + } + } + + void InputRemoveFlare( inputdata_t &inputdata ) + { + UTIL_Remove(m_hFlareEnt); + m_nSkin = 1; + } + + void InputRestoreFlare( inputdata_t &inputdata ) + { + if (!HasInteraction(PROPINTER_PHYSGUN_CREATE_FLARE) && !m_hFlareEnt) + { + SetInteraction(PROPINTER_PHYSGUN_CREATE_FLARE); + m_OnRestored.FireOutput(inputdata.pActivator, inputdata.pCaller); + m_nSkin = 0; + } + } + + int DrawDebugTextOverlays(void) + { + int text_offset = BaseClass::DrawDebugTextOverlays(); + + if (m_debugOverlays & OVERLAY_TEXT_BIT) + { + char tempstr[512]; + Q_snprintf(tempstr, sizeof(tempstr), "Flare Duration: %f", GetEnvFlareLifetime(m_hFlareEnt)); + EntityText(text_offset, tempstr, 0); + text_offset++; + } + + return text_offset; + } + + COutputEvent m_OnRestored; + float m_flFlareLifetime = 30.0f; +}; + +LINK_ENTITY_TO_CLASS( prop_flare, CPropFlare ); + +BEGIN_DATADESC( CPropFlare ) + + DEFINE_KEYFIELD( m_flFlareLifetime, FIELD_FLOAT, "FlareLifetime" ), + + DEFINE_INPUTFUNC( FIELD_VOID, "StartFlare", InputStartFlare ), + DEFINE_INPUTFUNC( FIELD_VOID, "StopFlare", InputStopFlare ), + DEFINE_INPUTFUNC( FIELD_FLOAT, "AddFlareLifetime", InputAddFlareLifetime ), + DEFINE_INPUTFUNC( FIELD_VOID, "RemoveFlare", InputRemoveFlare ), + DEFINE_INPUTFUNC( FIELD_VOID, "RestoreFlare", InputRestoreFlare ), + DEFINE_OUTPUT( m_OnRestored, "OnRestored" ), + +END_DATADESC() +#endif + + // ------------------------------------------------------------------------------------------ // // Special version of func_physbox. // ------------------------------------------------------------------------------------------ // diff --git a/src/game/server/props.h b/src/game/server/props.h index 11ed2118..a528d6ce 100644 --- a/src/game/server/props.h +++ b/src/game/server/props.h @@ -39,6 +39,11 @@ public: // Don't treat as a live target virtual bool IsAlive( void ) { return false; } virtual bool OverridePropdata() { return true; } + +#ifdef MAPBASE + // Attempt to replace a dynamic_cast + virtual bool IsPropPhysics() { return false; } +#endif }; @@ -58,10 +63,18 @@ public: virtual void Precache(); virtual float GetAutoAimRadius() { return 24.0f; } +#ifdef MAPBASE + virtual bool KeyValue( const char *szKeyName, const char *szValue ); +#endif + void BreakablePropTouch( CBaseEntity *pOther ); virtual int OnTakeDamage( const CTakeDamageInfo &info ); void Event_Killed( const CTakeDamageInfo &info ); +#ifdef MAPBASE + // Marks Break() as virtual + virtual +#endif void Break( CBaseEntity *pBreaker, const CTakeDamageInfo &info ); void BreakThink( void ); void AnimateThink( void ); @@ -72,6 +85,10 @@ public: void InputAddHealth( inputdata_t &inputdata ); void InputRemoveHealth( inputdata_t &inputdata ); void InputSetHealth( inputdata_t &inputdata ); +#ifdef MAPBASE + void InputSetInteraction( inputdata_t &inputdata ); + void InputRemoveInteraction( inputdata_t &inputdata ); +#endif int GetNumBreakableChunks( void ) { return m_iNumBreakableChunks; } @@ -86,6 +103,11 @@ public: if ( HasInteraction( PROPINTER_PHYSGUN_LAUNCH_SPIN_Z ) ) return true; +#ifdef MAPBASE + if ( m_bUsesCustomCarryAngles ) + return true; +#endif + return false; } @@ -97,6 +119,11 @@ public: void HandleFirstCollisionInteractions( int index, gamevcollisionevent_t *pEvent ); void HandleInteractionStick( int index, gamevcollisionevent_t *pEvent ); void StickAtPosition( const Vector &stickPosition, const Vector &savePosition, const QAngle &saveAngles ); + +#ifdef MAPBASE + // Uses the new CBaseEntity interaction implementation + bool HandleInteraction( int interactionType, void *data, CBaseCombatCharacter* sourceEnt ); +#endif // Disable auto fading under dx7 or when level fades are specified void DisableAutoFade(); @@ -111,6 +138,10 @@ public: int m_iMinHealthDmg; QAngle m_preferredCarryAngles; +#ifdef MAPBASE + // Indicates whether the prop is using the keyvalue carry angles. + bool m_bUsesCustomCarryAngles; +#endif public: // IBreakableWithPropData @@ -196,6 +227,9 @@ public: virtual CBasePlayer *HasPhysicsAttacker( float dt ); #ifdef HL2_EPISODIC +#ifdef MAPBASE + virtual float GetFlareLifetime() { return 30.0f; } +#endif void CreateFlare( float flLifetime ); #endif //HL2_EPISODIC @@ -243,7 +277,14 @@ private: mp_break_t m_mpBreakMode; EHANDLE m_hLastAttacker; // Last attacker that harmed me. +#ifdef MAPBASE +protected: + // Needs to be protected for prop_flare entity usage EHANDLE m_hFlareEnt; +private: +#else + EHANDLE m_hFlareEnt; +#endif string_t m_iszPuntSound; bool m_bUsePuntSound; }; @@ -288,6 +329,9 @@ public: // Input handlers void InputSetAnimation( inputdata_t &inputdata ); +#ifdef MAPBASE // From Alien Swarm SDK + void InputSetAnimationNoReset( inputdata_t &inputdata ); +#endif void InputSetDefaultAnimation( inputdata_t &inputdata ); void InputTurnOn( inputdata_t &inputdata ); void InputTurnOff( inputdata_t &inputdata ); @@ -303,6 +347,9 @@ public: int m_iTransitionDirection; // Random animations +#ifdef MAPBASE // From Alien Swarm SDK + bool m_bHoldAnimation; +#endif bool m_bRandomAnimator; float m_flNextRandAnim; float m_flMinRandAnimTime; @@ -311,6 +358,9 @@ public: bool m_bStartDisabled; bool m_bDisableBoneFollowers; +#ifdef MAPBASE // From Alien Swarm SDK + bool m_bUpdateAttachedChildren; // For props with children on attachment points, update their child touches as we animate +#endif CNetworkVar( bool, m_bUseHitboxesForRenderBox ); @@ -343,6 +393,11 @@ public: bool CreateVPhysics( void ); bool OverridePropdata( void ); +#ifdef MAPBASE + // Attempt to replace a dynamic_cast + virtual bool IsPropPhysics() { return true; } +#endif + virtual void VPhysicsUpdate( IPhysicsObject *pPhysics ); virtual void VPhysicsCollision( int index, gamevcollisionevent_t *pEvent ); @@ -351,10 +406,14 @@ public: void InputEnableMotion( inputdata_t &inputdata ); void InputDisableMotion( inputdata_t &inputdata ); void InputDisableFloating( inputdata_t &inputdata ); +#ifdef MAPBASE + void InputSetDebris( inputdata_t &inputdata ); +#endif void EnableMotion( void ); bool CanBePickedUpByPhyscannon( void ); void OnPhysGunPickup( CBasePlayer *pPhysGunUser, PhysGunPickup_t reason ); + void OnPhysGunPull( CBasePlayer *pPhysGunUser ); void OnPhysGunDrop( CBasePlayer *pPhysGunUser, PhysGunDrop_t reason ); bool GetPropDataAngles( const char *pKeyName, QAngle &vecAngles ); @@ -388,6 +447,7 @@ private: COutputEvent m_OnPhysGunPickup; COutputEvent m_OnPhysGunPunt; COutputEvent m_OnPhysGunOnlyPickup; + COutputEvent m_OnPhysGunPull; COutputEvent m_OnPhysGunDrop; COutputEvent m_OnPlayerUse; COutputEvent m_OnPlayerPickup; diff --git a/src/game/server/rope.cpp b/src/game/server/rope.cpp index 7ca53298..23f44c39 100644 --- a/src/game/server/rope.cpp +++ b/src/game/server/rope.cpp @@ -37,6 +37,9 @@ IMPLEMENT_SERVERCLASS_ST_NOBASE( CRopeKeyframe, DT_RopeKeyframe ) SendPropInt( SENDINFO(m_Slack), 12 ), SendPropInt( SENDINFO(m_RopeLength), 15 ), SendPropInt( SENDINFO(m_fLockedPoints), 4, SPROP_UNSIGNED ), +#ifdef MAPBASE + SendPropInt( SENDINFO(m_nChangeCount), 8, SPROP_UNSIGNED ), +#endif SendPropInt( SENDINFO(m_RopeFlags), ROPE_NUMFLAGS, SPROP_UNSIGNED ), SendPropInt( SENDINFO(m_nSegments), 4, SPROP_UNSIGNED ), SendPropBool( SENDINFO(m_bConstrainBetweenEndpoints) ), @@ -86,6 +89,15 @@ BEGIN_DATADESC( CRopeKeyframe ) DEFINE_INPUTFUNC( FIELD_VECTOR, "SetForce", InputSetForce ), DEFINE_INPUTFUNC( FIELD_VOID, "Break", InputBreak ), +#ifdef MAPBASE + DEFINE_INPUTFUNC( FIELD_INTEGER, "SetSlack", InputSetSlack ), + DEFINE_INPUTFUNC( FIELD_FLOAT, "SetWidth", InputSetWidth ), + DEFINE_INPUTFUNC( FIELD_INTEGER, "SetSubdivision", InputSetSubdivision ), + + // Outputs + DEFINE_OUTPUT( m_OnBreak, "OnBreak" ), +#endif + END_DATADESC() @@ -109,7 +121,12 @@ CRopeKeyframe::CRopeKeyframe() m_RopeLength = 20; m_fLockedPoints = (int) (ROPE_LOCK_START_POINT | ROPE_LOCK_END_POINT); // by default, both points are locked m_flScrollSpeed = 0; +#ifdef MAPBASE + // Because of the keyvalue switch + m_RopeFlags = ROPE_SIMULATE | ROPE_INITIAL_HANG | ROPE_USE_WIND; +#else m_RopeFlags = ROPE_SIMULATE | ROPE_INITIAL_HANG; +#endif m_iRopeMaterialModelIndex = -1; m_Subdiv = 2; @@ -201,10 +218,19 @@ CRopeKeyframe* CRopeKeyframe::Create( int iEndAttachment, int ropeWidth, const char *pMaterialName, +#ifdef MAPBASE + int numSegments, + const char *pClassName +#else int numSegments +#endif ) { +#ifdef MAPBASE + CRopeKeyframe *pRet = (CRopeKeyframe*)CreateEntityByName( pClassName ); +#else CRopeKeyframe *pRet = (CRopeKeyframe*)CreateEntityByName( "keyframe_rope" ); +#endif if( !pRet ) return NULL; @@ -218,6 +244,9 @@ CRopeKeyframe* CRopeKeyframe::Create( pRet->SetMaterial( pMaterialName ); pRet->m_Width = ropeWidth; pRet->m_nSegments = clamp( numSegments, 2, ROPE_MAX_SEGMENTS ); +#ifdef MAPBASE + pRet->Spawn(); +#endif return pRet; } @@ -230,10 +259,19 @@ CRopeKeyframe* CRopeKeyframe::CreateWithSecondPointDetached( int ropeWidth, const char *pMaterialName, int numSegments, +#ifdef MAPBASE + bool bInitialHang, + const char *pClassName +#else bool bInitialHang +#endif ) { +#ifdef MAPBASE + CRopeKeyframe *pRet = (CRopeKeyframe*)CreateEntityByName( pClassName ); +#else CRopeKeyframe *pRet = (CRopeKeyframe*)CreateEntityByName( "keyframe_rope" ); +#endif if( !pRet ) return NULL; @@ -329,6 +367,26 @@ void CRopeKeyframe::Init() m_bStartPointValid = (m_hStartPoint.Get() != NULL); m_bEndPointValid = (m_hEndPoint.Get() != NULL); + +#ifdef MAPBASE + // Sanity-check the rope texture scale before it goes over the wire + if ( m_TextureScale < 0.1f ) + { + Vector origin = GetAbsOrigin(); + GetEndPointPos( 0, origin ); + DevMsg( "move_rope has TextureScale less than 0.1 at (%2.2f, %2.2f, %2.2f)\n", + origin.x, origin.y, origin.z ); + m_TextureScale = 0.1f; + } + else if ( m_TextureScale > 10.0f ) + { + Vector origin = GetAbsOrigin(); + GetEndPointPos( 0, origin ); + DevMsg( "move_rope has TextureScale greater than 10 at (%2.2f, %2.2f, %2.2f)\n", + origin.x, origin.y, origin.z ); + m_TextureScale = 10.0f; + } +#endif } @@ -552,17 +610,74 @@ void CRopeKeyframe::InputSetForce( inputdata_t &inputdata ) void CRopeKeyframe::InputBreak( inputdata_t &inputdata ) { //Route through the damage code +#ifdef MAPBASE + Break(inputdata.pActivator); +#else Break(); +#endif } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: Sets the slack +// Input : &inputdata - +//----------------------------------------------------------------------------- +void CRopeKeyframe::InputSetSlack( inputdata_t &inputdata ) +{ + m_Slack = inputdata.value.Int(); + + // Must resize in order for changes to occur + m_RopeFlags |= ROPE_RESIZE; + + if (!(m_RopeFlags & ROPE_USE_WIND)) + { + Warning( "WARNING: SetSlack on %s may need wind enabled in order to function\n", GetDebugName() ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Sets the width +// Input : &inputdata - +//----------------------------------------------------------------------------- +void CRopeKeyframe::InputSetWidth( inputdata_t &inputdata ) +{ + m_Width = inputdata.value.Float(); +} + +//----------------------------------------------------------------------------- +// Purpose: Sets the subdivision +// Input : &inputdata - +//----------------------------------------------------------------------------- +void CRopeKeyframe::InputSetSubdivision( inputdata_t &inputdata ) +{ + m_Subdiv = inputdata.value.Int(); + + // Must resize in order for changes to occur + m_RopeFlags |= ROPE_RESIZE; + + if (!(m_RopeFlags & ROPE_USE_WIND)) + { + Warning( "WARNING: SetSubdivision on %s may need wind enabled in order to function\n", GetDebugName() ); + } +} +#endif + //----------------------------------------------------------------------------- // Purpose: Breaks the rope // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- +#ifdef MAPBASE +bool CRopeKeyframe::Break( CBaseEntity *pActivator ) +#else bool CRopeKeyframe::Break( void ) +#endif { DetachPoint( 0 ); +#ifdef MAPBASE + m_OnBreak.FireOutput(pActivator ? pActivator : this, this); +#endif + // Find whoever references us and detach us from them. // UNDONE: PERFORMANCE: This is very slow!!! CRopeKeyframe *pTest = NULL; @@ -585,6 +700,10 @@ bool CRopeKeyframe::Break( void ) //----------------------------------------------------------------------------- void CRopeKeyframe::NotifyPositionChanged( CBaseEntity *pEntity ) { +#ifdef MAPBASE + ++m_nChangeCount; +#endif + // Update our bbox? UpdateBBox( false ); @@ -619,7 +738,11 @@ int CRopeKeyframe::OnTakeDamage( const CTakeDamageInfo &info ) if( !(m_RopeFlags & ROPE_BREAKABLE) ) return false; +#ifdef MAPBASE + Break(info.GetAttacker()); +#else Break(); +#endif return 0; } @@ -630,6 +753,14 @@ void CRopeKeyframe::Precache() BaseClass::Precache(); } +#ifdef MAPBASE +void CRopeKeyframe::Spawn( void ) +{ + BaseClass::Spawn(); + Precache(); +} +#endif + void CRopeKeyframe::DetachPoint( int iPoint ) { @@ -650,10 +781,17 @@ void CRopeKeyframe::EnableCollision() void CRopeKeyframe::EnableWind( bool bEnable ) { int flag = 0; +#ifdef MAPBASE + if ( bEnable ) + flag |= ROPE_USE_WIND; + + if ( (m_RopeFlags & ROPE_USE_WIND) != flag ) +#else if ( !bEnable ) flag |= ROPE_NO_WIND; if ( (m_RopeFlags & ROPE_NO_WIND) != flag ) +#endif { m_RopeFlags |= flag; } @@ -698,6 +836,20 @@ bool CRopeKeyframe::KeyValue( const char *szKeyName, const char *szValue ) { // Legacy support for the RopeShader parameter. int iShader = atoi( szValue ); +#ifdef MAPBASE + if ( iShader == 0 ) + { + m_strRopeMaterialModel = AllocPooledString( "cable/cable.vmt" ); + } + else if ( iShader == 1 ) + { + m_strRopeMaterialModel = AllocPooledString( "cable/rope.vmt" ); + } + else + { + m_strRopeMaterialModel = AllocPooledString( "cable/chain.vmt" ); + } +#else if ( iShader == 0 ) { m_iRopeMaterialModelIndex = PrecacheModel( "cable/cable.vmt" ); @@ -710,6 +862,7 @@ bool CRopeKeyframe::KeyValue( const char *szKeyName, const char *szValue ) { m_iRopeMaterialModelIndex = PrecacheModel( "cable/chain.vmt" ); } +#endif } else if ( stricmp( szKeyName, "RopeMaterial" ) == 0 ) { @@ -725,12 +878,28 @@ bool CRopeKeyframe::KeyValue( const char *szKeyName, const char *szValue ) SetMaterial( str ); } } +#ifdef MAPBASE + else if( stricmp( szKeyName, "UseWind" ) == 0 ) + { + if( atoi( szValue ) == 1 ) + m_RopeFlags |= ROPE_USE_WIND; + else + m_RopeFlags &= ~ROPE_USE_WIND; + } +#endif else if ( stricmp( szKeyName, "NoWind" ) == 0 ) { +#ifdef MAPBASE + if ( atoi( szValue ) != 1 ) + m_RopeFlags |= ROPE_USE_WIND; + else + m_RopeFlags &= ~ROPE_USE_WIND; +#else if ( atoi( szValue ) == 1 ) { m_RopeFlags |= ROPE_NO_WIND; } +#endif } return BaseClass::KeyValue( szKeyName, szValue ); @@ -749,7 +918,9 @@ void CRopeKeyframe::InputSetScrollSpeed( inputdata_t &inputdata ) void CRopeKeyframe::SetMaterial( const char *pName ) { m_strRopeMaterialModel = AllocPooledString( pName ); +#ifndef MAPBASE m_iRopeMaterialModelIndex = PrecacheModel( STRING( m_strRopeMaterialModel ) ); +#endif } int CRopeKeyframe::UpdateTransmitState() diff --git a/src/game/server/rope.h b/src/game/server/rope.h index 7ab96044..44fc7102 100644 --- a/src/game/server/rope.h +++ b/src/game/server/rope.h @@ -37,7 +37,12 @@ public: const char *pMaterialName = "cable/cable.vmt", // Note: whoever creates the rope must // use PrecacheModel for whatever material // it specifies here. +#ifdef MAPBASE + int numSegments = 5, + const char *pClassName = "keyframe_rope" +#else int numSegments = 5 +#endif ); static CRopeKeyframe* CreateWithSecondPointDetached( @@ -49,7 +54,12 @@ public: // use PrecacheModel for whatever material // it specifies here. int numSegments = 5, +#ifdef MAPBASE + bool bInitialHang = false, + const char *pClassName = "keyframe_rope" +#else bool bInitialHang = false +#endif ); bool SetupHangDistance( float flHangDist ); @@ -68,6 +78,9 @@ public: virtual int ObjectCaps( void ) { return BaseClass::ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } virtual void Activate(); virtual void Precache(); +#ifdef MAPBASE + virtual void Spawn(); +#endif virtual int OnTakeDamage( const CTakeDamageInfo &info ); virtual bool KeyValue( const char *szKeyName, const char *szValue ); @@ -89,10 +102,19 @@ public: void InputSetScrollSpeed( inputdata_t &inputdata ); void InputSetForce( inputdata_t &inputdata ); void InputBreak( inputdata_t &inputdata ); +#ifdef MAPBASE + void InputSetSlack( inputdata_t &inputdata ); + void InputSetWidth( inputdata_t &inputdata ); + void InputSetSubdivision( inputdata_t &inputdata ); +#endif public: +#ifdef MAPBASE + bool Break( CBaseEntity *pActivator = NULL ); +#else bool Break( void ); +#endif void DetachPoint( int iPoint ); void EndpointsChanged(); @@ -103,12 +125,21 @@ public: // Toggle wind. void EnableWind( bool bEnable ); +#ifdef MAPBASE + CBaseEntity* GetStartPoint() { return m_hStartPoint.Get(); } + int GetStartAttachment() { return m_iStartAttachment; }; +#else // Unless this is called during initialization, the caller should have done // PrecacheModel on whatever material they specify in here. void SetMaterial( const char *pName ); +#endif CBaseEntity* GetEndPoint() { return m_hEndPoint.Get(); } +#ifdef MAPBASE + int GetEndAttachment() { return m_iEndAttachment; }; +#else int GetEndAttachment() { return m_iStartAttachment; }; +#endif void SetStartPoint( CBaseEntity *pStartPoint, int attachment = 0 ); void SetEndPoint( CBaseEntity *pEndPoint, int attachment = 0 ); @@ -122,11 +153,25 @@ public: private: +#ifdef MAPBASE + // Unless this is called during initialization, the caller should have done + // PrecacheModel on whatever material they specify in here. + void SetMaterial( const char *pName ); + +protected: +#endif + void SetAttachmentPoint( CBaseHandle &hOutEnt, short &iOutAttachment, CBaseEntity *pEnt, int iAttachment ); // This is normally called by Activate but if you create the rope at runtime, // you must call it after you have setup its variables. +#ifdef MAPBASE + virtual void Init(); + +private: +#else void Init(); +#endif // These work just like the client-side versions. bool GetEndPointPos2( CBaseEntity *pEnt, int iAttachment, Vector &v ); @@ -151,6 +196,11 @@ public: // Number of subdivisions in between segments. CNetworkVar( int, m_Subdiv ); + +#ifdef MAPBASE + // Used simply to wake up rope on the client side if it has gone to sleep + CNetworkVar( unsigned char, m_nChangeCount ); +#endif //EHANDLE m_hNextLink; @@ -171,6 +221,10 @@ private: CNetworkHandle( CBaseEntity, m_hEndPoint ); CNetworkVar( short, m_iStartAttachment ); // StartAttachment/EndAttachment are attachment points. CNetworkVar( short, m_iEndAttachment ); + +#ifdef MAPBASE + COutputEvent m_OnBreak; +#endif }; diff --git a/src/game/server/saverestore_gamedll.cpp b/src/game/server/saverestore_gamedll.cpp index 768262e1..a1f9505d 100644 --- a/src/game/server/saverestore_gamedll.cpp +++ b/src/game/server/saverestore_gamedll.cpp @@ -93,6 +93,17 @@ bool ParseKeyvalue( void *pObject, typedescription_t *pFields, int iNumFields, c UTIL_StringToColor32( (color32 *) ((char *)pObject + fieldOffset), szValue ); return true; +#ifdef MAPBASE + case FIELD_EHANDLE: + ((CBaseHandle*)((char*)pObject + fieldOffset))->Set(gEntList.FindEntityByName(NULL, szValue)); + return true; + + case FIELD_INTERVAL: + extern interval_t ReadInterval( const char *pString ); + (*(interval_t*)((char *)pObject + fieldOffset)) = ReadInterval( szValue ); + return true; +#endif + case FIELD_CUSTOM: { SaveRestoreFieldInfo_t fieldInfo = @@ -106,7 +117,9 @@ bool ParseKeyvalue( void *pObject, typedescription_t *pFields, int iNumFields, c } default: +#ifndef MAPBASE case FIELD_INTERVAL: // Fixme, could write this if needed +#endif case FIELD_CLASSPTR: case FIELD_MODELINDEX: case FIELD_MATERIALINDEX: diff --git a/src/game/server/sceneentity.cpp b/src/game/server/sceneentity.cpp index f5c037e4..ca4ef565 100644 --- a/src/game/server/sceneentity.cpp +++ b/src/game/server/sceneentity.cpp @@ -32,8 +32,10 @@ #include "scenefilecache/ISceneFileCache.h" #include "SceneCache.h" #include "scripted.h" +#include "basemultiplayerplayer.h" #include "env_debughistory.h" #include "team.h" +#include "triggers.h" #ifdef HL2_EPISODIC #include "npc_alyx_episodic.h" @@ -71,6 +73,17 @@ static int speechListIndex = 0; #define SCENE_MIN_PITCH 0.25f #define SCENE_MAX_PITCH 2.5f +// New macros introduced for Mapbase's console message color changes. +#ifdef MAPBASE +#define ChoreoMsg( lvl, msg ) CGMsg( lvl, CON_GROUP_CHOREO, msg ) +#define ChoreoMsg1( lvl, msg, a ) CGMsg( lvl, CON_GROUP_CHOREO, msg, a ) +#define ChoreoMsg2( lvl, msg, a, b ) CGMsg( lvl, CON_GROUP_CHOREO, msg, a, b ) +#else +#define ChoreoMsg( lvl, msg ) DevMsg( lvl, msg ) +#define ChoreoMsg1( lvl, msg, a ) DevMsg( lvl, msg, a ) +#define ChoreoMsg2( lvl, msg, a, b ) DevMsg( lvl, msg, a, b ) +#endif + //=========================================================================================================== // SCENE LIST MANAGER //=========================================================================================================== @@ -84,6 +97,11 @@ class CSceneListManager : public CLogicalEntity DECLARE_CLASS( CSceneListManager, CLogicalEntity ); public: DECLARE_DATADESC(); +#ifdef MAPBASE_VSCRIPT + DECLARE_ENT_SCRIPTDESC(); + + HSCRIPT ScriptGetScene( int iIndex ); +#endif virtual void Activate( void ); @@ -140,6 +158,13 @@ public: bool IsRunningScriptedSceneWithSpeech( CBaseFlex *pActor, bool bIgnoreInstancedScenes ); bool IsRunningScriptedSceneWithSpeechAndNotPaused( CBaseFlex *pActor, bool bIgnoreInstancedScenes ); +#ifdef MAPBASE + bool IsRunningScriptedSceneWithFlexAndNotPaused( CBaseFlex *pActor, bool bIgnoreInstancedScenes, const char *pszNotThisScene = NULL ); + bool IsTalkingInAScriptedScene( CBaseFlex *pActor, bool bIgnoreInstancedScenes = false ); + + CUtlVector< CHandle< CSceneEntity > > *GetActiveSceneList(); +#endif + private: @@ -461,6 +486,9 @@ public: bool HasUnplayedSpeech( void ); bool HasFlexAnimation( void ); +#ifdef MAPBASE + bool IsPlayingSpeech( void ); +#endif void SetCurrentTime( float t, bool forceClientSync ); @@ -539,7 +567,6 @@ public: virtual CBaseEntity *FindNamedEntity( const char *name, CBaseEntity *pActor = NULL, bool bBaseFlexOnly = false, bool bUseClear = false ); CBaseEntity *FindNamedTarget( string_t iszTarget, bool bBaseFlexOnly = false ); virtual CBaseEntity *FindNamedEntityClosest( const char *name, CBaseEntity *pActor = NULL, bool bBaseFlexOnly = false, bool bUseClear = false, const char *pszSecondary = NULL ); - HSCRIPT ScriptFindNamedEntity( const char *name ); bool ScriptLoadSceneFromString( const char * pszFilename, const char *pszData ); private: @@ -770,6 +797,7 @@ BEGIN_ENT_SCRIPTDESC( CSceneEntity, CBaseEntity, "Choreographed scene which cont DEFINE_SCRIPTFUNC_NAMED( ScriptLoadSceneFromString, "LoadSceneFromString", "given a dummy scene name and a vcd string, load the scene" ) END_SCRIPTDESC(); + const ConVar *CSceneEntity::m_pcvSndMixahead = NULL; @@ -952,6 +980,68 @@ float CSceneEntity::GetSoundSystemLatency( void ) // Assume 100 msec sound system latency return SOUND_SYSTEM_LATENCY_DEFAULT; } + +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// I copied CSceneEntity's PrecacheScene to a unique static function so PrecacheInstancedScene() +// can precache loose scene files without having to use a CSceneEntity. +//----------------------------------------------------------------------------- +void PrecacheChoreoScene( CChoreoScene *scene ) +{ + Assert( scene ); + + // Iterate events and precache necessary resources + for ( int i = 0; i < scene->GetNumEvents(); i++ ) + { + CChoreoEvent *event = scene->GetEvent( i ); + if ( !event ) + continue; + + // load any necessary data + switch (event->GetType() ) + { + default: + break; + case CChoreoEvent::SPEAK: + { + // Defined in SoundEmitterSystem.cpp + // NOTE: The script entries associated with .vcds are forced to preload to avoid + // loading hitches during triggering + CBaseEntity::PrecacheScriptSound( event->GetParameters() ); + + if ( event->GetCloseCaptionType() == CChoreoEvent::CC_MASTER && + event->GetNumSlaves() > 0 ) + { + char tok[ CChoreoEvent::MAX_CCTOKEN_STRING ]; + if ( event->GetPlaybackCloseCaptionToken( tok, sizeof( tok ) ) ) + { + CBaseEntity::PrecacheScriptSound( tok ); + } + } + } + break; + case CChoreoEvent::SUBSCENE: + { + // Only allow a single level of subscenes for now + if ( !scene->IsSubScene() ) + { + CChoreoScene *subscene = event->GetSubScene(); + if ( !subscene ) + { + subscene = ChoreoLoadScene( event->GetParameters(), NULL, &g_TokenProcessor, LocalScene_Printf ); + subscene->SetSubScene( true ); + event->SetSubScene( subscene ); + + // Now precache it's resources, if any + PrecacheChoreoScene( subscene ); + } + } + } + break; + } + } +} +#endif //----------------------------------------------------------------------------- // Purpose: @@ -1067,6 +1157,31 @@ bool CSceneEntity::HasFlexAnimation( void ) return false; } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CSceneEntity::IsPlayingSpeech( void ) +{ + if ( m_pScene ) + { + float flTime = m_pScene->GetTime(); + for ( int i = 0; i < m_pScene->GetNumEvents(); i++ ) + { + CChoreoEvent *e = m_pScene->GetEvent( i ); + if ( e->GetType() == CChoreoEvent::SPEAK ) + { + if ( /*flTime >= e->GetStartTime() &&*/ flTime <= e->GetEndTime() ) + return true; + } + } + } + + return false; +} +#endif + //----------------------------------------------------------------------------- // Purpose: @@ -1527,7 +1642,11 @@ void CSceneEntity::DispatchEndGesture( CChoreoScene *scene, CBaseFlex *actor, CC void CSceneEntity::DispatchStartGeneric( CChoreoScene *scene, CBaseFlex *actor, CChoreoEvent *event ) { CBaseEntity *pTarget = FindNamedEntity( event->GetParameters2( ) ); +#ifdef MAPBASE + actor->AddSceneEvent( scene, event, pTarget, this ); +#else actor->AddSceneEvent( scene, event, pTarget ); +#endif } @@ -2214,7 +2333,11 @@ void CSceneEntity::InputPitchShiftPlayback( inputdata_t &inputdata ) void CSceneEntity::InputTriggerEvent( inputdata_t &inputdata ) { +#ifdef MAPBASE + CBaseEntity *pActivator = inputdata.pActivator; +#else CBaseEntity *pActivator = this; // at some point, find this from the inputdata +#endif switch ( inputdata.value.Int() ) { case 1: @@ -2317,15 +2440,19 @@ void CSceneEntity::InputInterjectResponse( inputdata_t &inputdata ) // Try to find the response for this slot. AI_Response response; - bool result = npc->SpeakFindResponse( response, inputdata.value.String(), modifiers.Get() ); + CAI_Concept concept(inputdata.value.String()); + concept.SetSpeaker(npc); + AI_CriteriaSet set; + npc->GatherCriteria(&set, concept, modifiers.Get()); + bool result = npc->FindResponse( response, concept, &set); if ( result ) { - float duration = npc->GetResponseDuration( response ); + float duration = npc->GetResponseDuration( &response ); if ( ( duration > 0.0f ) && npc->PermitResponse( duration ) ) { // If we could look it up, dispatch it and bail. - npc->SpeakDispatchResponse( inputdata.value.String(), response ); + npc->SpeakDispatchResponse( concept, &response, &set); return; } } @@ -2409,6 +2536,30 @@ bool CSceneEntity::CheckActors() return false; } } +#ifdef MAPBASE + else if ( pTestActor->IsPlayer() ) + { + // Blixibon - Player speech handling + CBasePlayer *pPlayer = static_cast(pTestActor); + bool bShouldWait = false; + if ( pPlayer->GetExpresser() && pPlayer->GetExpresser()->IsSpeaking() ) + { + bShouldWait = true; + } + else if ( IsTalkingInAScriptedScene( pPlayer ) ) + { + bShouldWait = true; + } + + if ( bShouldWait ) + { + // One of the actors for this scene is talking already. + // Try again next think. + m_bWaitingForActor = true; + return false; + } + } +#endif } else if ( m_BusyActor == SCENE_BUSYACTOR_INTERRUPT || m_BusyActor == SCENE_BUSYACTOR_INTERRUPT_CANCEL ) { @@ -2569,7 +2720,7 @@ void CSceneEntity::StartPlayback( void ) m_pScene = LoadScene( STRING( m_iszSceneFile ), this ); if ( !m_pScene ) { - DevMsg( "%s missing from scenes.image\n", STRING( m_iszSceneFile ) ); + ChoreoMsg1( 1, "%s missing from scenes.image\n", STRING( m_iszSceneFile ) ); m_bSceneMissing = true; return; } @@ -2912,6 +3063,12 @@ void CSceneEntity::DispatchStartSubScene( CChoreoScene *scene, CBaseFlex *pActor if ( subscene ) { +#ifdef MAPBASE + // Somes may not be created with a CSceneEntity as the event callback + if (!scene->GetEventCallbackInterface()) + scene->SetEventCallbackInterface( this ); +#endif + subscene->ResetSimulation(); } } @@ -2935,7 +3092,7 @@ void CSceneEntity::StartEvent( float currenttime, CChoreoScene *scene, CChoreoEv CBaseFlex *pActor = NULL; CChoreoActor *actor = event->GetActor(); - if ( actor ) + if ( actor && (event->GetType() != CChoreoEvent::SCRIPT) && (event->GetType() != CChoreoEvent::CAMERA) ) { pActor = FindNamedActor( actor ); if (pActor == NULL) @@ -3098,6 +3255,80 @@ void CSceneEntity::StartEvent( float currenttime, CChoreoScene *scene, CChoreoEv } } break; + + case CChoreoEvent::CAMERA: + { + // begin the camera shot + const char *pszShotType = event->GetParameters(); + + CBaseEntity *pActor1 = FindNamedEntity( event->GetParameters2( ), pActor ); + CBaseEntity *pActor2 = FindNamedEntity( event->GetParameters3( ), pActor ); + float duration = event->GetDuration(); + + // grab any camera we find in the map + // TODO: find camera that is nearest this scene entity? + CTriggerCamera *pCamera = (CTriggerCamera *)gEntList.FindEntityByClassname( NULL, "point_viewcontrol" ); + + if ( !pCamera ) + { + Warning( "CSceneEntity %s unable to find a camera (point_viewcontrol) in this map!\n", STRING(GetEntityName()) ); + } + else + { + pCamera->StartCameraShot( pszShotType, this, pActor1, pActor2, duration ); + } + } + break; + + case CChoreoEvent::SCRIPT: + { + // NOTE: this is only used by auto-generated vcds to embed script commands to map entities. + + // vscript call - param1 is entity name, param2 is function name, param3 is function parameter string + // calls a vscript function defined on the scope of the named CBaseEntity object/actor. + // script call is of the format FunctionName(pActor, pThisSceneEntity, pszScriptParameters, duration) + const char *pszActorName = event->GetParameters(); + const char *pszFunctionName = event->GetParameters2(); + const char *pszScriptParameters = event->GetParameters3(); + + float duration = event->GetDuration(); + + // TODO: should be new method CBaseEntity::CallScriptFunctionParams() + CBaseEntity *pEntity = (CBaseEntity *)gEntList.FindEntityByName( NULL, pszActorName ); + + //START_VMPROFILE + if ( !pEntity ) + { + Warning( "CSceneEntity::SCRIPT event - unable to find entity named '%s' in this map!\n", pszActorName ); + } + else + { + + if( !pEntity->ValidateScriptScope() ) + { + ChoreoMsg(1, "\n***\nCChoreoEvent::SCRIPT - FAILED to create private ScriptScope. ABORTING script call\n***\n"); + break; + } + + HSCRIPT hFunc = pEntity->m_ScriptScope.LookupFunction( pszFunctionName ); + + if( hFunc ) + { + pEntity->m_ScriptScope.Call( hFunc, NULL, ToHScript(this), pszScriptParameters, duration ); + pEntity->m_ScriptScope.ReleaseFunction( hFunc ); + + //UPDATE_VMPROFILE + } + else + { + Warning("CSceneEntity::SCRIPT event - '%s' entity has no script function '%s' defined!\n", pszActorName,pszFunctionName); + } + } + + } + break; + + case CChoreoEvent::FIRETRIGGER: { if ( IsMultiplayer() ) @@ -3214,7 +3445,10 @@ void CSceneEntity::StartEvent( float currenttime, CChoreoScene *scene, CChoreoEv if ( IsMultiplayer() ) break; - DispatchStartPermitResponses( scene, pActor, event ); + if ( pActor ) + { + DispatchStartPermitResponses( scene, pActor, event ); + } } break; default: @@ -3306,6 +3540,19 @@ void CSceneEntity::EndEvent( float currenttime, CChoreoScene *scene, CChoreoEven } } break; + + case CChoreoEvent::CAMERA: + { + // call the end of camera or call a dispatch function + } + break; + + case CChoreoEvent::SCRIPT: + { + // call the end of script or call a dispatch function + } + break; + case CChoreoEvent::SEQUENCE: { if ( pActor ) @@ -3359,7 +3606,10 @@ void CSceneEntity::EndEvent( float currenttime, CChoreoScene *scene, CChoreoEven if ( IsMultiplayer() ) break; - DispatchEndPermitResponses( scene, pActor, event ); + if ( pActor ) + { + DispatchEndPermitResponses( scene, pActor, event ); + } } break; default: @@ -3414,7 +3664,7 @@ bool CSceneEntity::ShouldNetwork() const CChoreoScene *CSceneEntity::LoadScene( const char *filename, IChoreoEventCallback *pCallback ) { - DevMsg( 2, "Blocking load of scene from '%s'\n", filename ); + ChoreoMsg1( 2, "Blocking load of scene from '%s'\n", filename ); char loadfile[MAX_PATH]; Q_strncpy( loadfile, filename, sizeof( loadfile ) ); @@ -3422,7 +3672,46 @@ CChoreoScene *CSceneEntity::LoadScene( const char *filename, IChoreoEventCallbac Q_FixSlashes( loadfile ); // binary compiled vcd - void *pBuffer; + void *pBuffer = NULL; +#ifdef MAPBASE + // + // Raw scene file support + // + CChoreoScene *pScene; + int fileSize; + + // First, check if it's in scenes.image... + if ( CopySceneFileIntoMemory( loadfile, &pBuffer, &fileSize ) ) + { + pScene = new CChoreoScene( NULL ); + CUtlBuffer buf( pBuffer, fileSize, CUtlBuffer::READ_ONLY ); + if ( !pScene->RestoreFromBinaryBuffer( buf, loadfile, &g_ChoreoStringPool ) ) + { + Warning( "CSceneEntity::LoadScene: Unable to load binary scene '%s'\n", loadfile ); + delete pScene; + pScene = NULL; + } + } + // Next, check if it's a loose file... + else if (filesystem->ReadFileEx( loadfile, "MOD", &pBuffer, true )) + { + g_TokenProcessor.SetBuffer((char*)pBuffer); + pScene = ChoreoLoadScene( loadfile, NULL, &g_TokenProcessor, LocalScene_Printf ); + g_TokenProcessor.SetBuffer(NULL); + } + // Okay, it's definitely missing. + else + { + MissingSceneWarning( loadfile ); + pScene = NULL; + } + + if (pScene) + { + pScene->SetPrintFunc( LocalScene_Printf ); + pScene->SetEventCallbackInterface( pCallback ); + } +#else int fileSize; if ( !CopySceneFileIntoMemory( loadfile, &pBuffer, &fileSize ) ) { @@ -3443,6 +3732,7 @@ CChoreoScene *CSceneEntity::LoadScene( const char *filename, IChoreoEventCallbac pScene->SetPrintFunc( LocalScene_Printf ); pScene->SetEventCallbackInterface( pCallback ); } +#endif FreeSceneFileMemory( pBuffer ); return pScene; @@ -3973,6 +4263,78 @@ CBaseEntity *CSceneEntity::FindNamedEntity( const char *name, CBaseEntity *pActo return entity; } +#ifdef MAPBASE +const char *GetFirstSoundInScene(const char *pszScene) +{ + const char *soundName = NULL; + SceneCachedData_t sceneData; + if ( scenefilecache->GetSceneCachedData( pszScene, &sceneData ) ) + { + if ( sceneData.numSounds > 0 ) + { + // 0 is the first index...right? + short stringId = scenefilecache->GetSceneCachedSound( sceneData.sceneId, 0 ); + + // Trust that it's been precached + soundName = scenefilecache->GetSceneString( stringId ); + } + } + else + { + void *pBuffer = NULL; + if (filesystem->ReadFileEx( pszScene, "MOD", &pBuffer, true )) + { + g_TokenProcessor.SetBuffer((char*)pBuffer); + CChoreoScene *pScene = ChoreoLoadScene( pszScene, NULL, &g_TokenProcessor, LocalScene_Printf ); + g_TokenProcessor.SetBuffer(NULL); + if (pScene) + { + for (int i = 0; i < pScene->GetNumEvents(); i++) + { + CChoreoEvent *pEvent = pScene->GetEvent(i); + + if (pEvent->GetType() == CChoreoEvent::SPEAK) + { + soundName = pEvent->GetParameters(); + break; + } + } + } + } + FreeSceneFileMemory( pBuffer ); + } + + return soundName; +} + +const char *GetFirstSoundInScene(CChoreoScene *scene) +{ + for ( int i = 0; i < scene->GetNumEvents(); i++ ) + { + CChoreoEvent *pEvent = scene->GetEvent( i ); + + if (pEvent->GetType() == CChoreoEvent::SPEAK) + return pEvent->GetParameters(); + } + + return NULL; +} + +CBaseEntity *UTIL_FindNamedSceneEntity(const char *name, CBaseEntity *pActor, CSceneEntity *scene, bool bBaseFlexOnly, bool bUseClear) +{ + if (scene) + { + CBaseEntity *pEnt = scene->FindNamedEntity(name, pActor, bBaseFlexOnly, bUseClear); + return pEnt; + } + else + { + //Warning("SCENE NOT FOUND!\n"); + return NULL; + } +} +#endif + //----------------------------------------------------------------------------- // Purpose: Search for an actor by name, make sure it can do face poses @@ -4416,6 +4778,33 @@ void CSceneEntity::OnSceneFinished( bool canceled, bool fireoutput ) m_OnCompletion.FireOutput( this, this, 0 ); } +#ifdef NEW_RESPONSE_SYSTEM + { + CBaseFlex *pFlex = FindNamedActor( 0 ) ; + if ( pFlex ) + { +#ifdef MAPBASE + CBasePlayer *pAsPlayer = ToBasePlayer(pFlex); +#else + CBaseMultiplayerPlayer *pAsPlayer = dynamic_cast(pFlex); +#endif + if (pAsPlayer) + { + CAI_Expresser *pExpresser = pAsPlayer->GetExpresser(); +#ifdef MAPBASE + if (pExpresser) +#endif + pExpresser->OnSpeechFinished(); + } + else if ( CAI_BaseActor *pActor = dynamic_cast( pFlex ) ) + { + CAI_Expresser *pExpresser = pActor->GetExpresser(); + pExpresser->OnSpeechFinished(); + } + } + } +#endif + // Put face back in neutral pose ClearSceneEvents( m_pScene, canceled ); @@ -4538,6 +4927,7 @@ void CSceneEntity::RemoveBroadcastTeamTarget( int nTeamIndex ) m_pRecipientFilter->RemoveRecipientsByTeam( pTeam ); } + //----------------------------------------------------------------------------- // Purpose: // Input : @@ -4696,7 +5086,13 @@ float InstancedScriptedScene( CBaseFlex *pActor, const char *pszScene, EHANDLE * // *phSceneEnt - // Output : float //----------------------------------------------------------------------------- +#ifdef MAPBASE +float InstancedAutoGeneratedSoundScene( CBaseFlex *pActor, const char *soundname, EHANDLE *phSceneEnt, + float flPostDelay, bool bIsBackground, AI_Response *response, + bool bMultiplayer, IRecipientFilter *filter /* = NULL */ ) +#else float InstancedAutoGeneratedSoundScene( CBaseFlex *pActor, char const *soundname, EHANDLE *phSceneEnt /*= NULL*/ ) +#endif { if ( !pActor ) { @@ -4714,10 +5110,38 @@ float InstancedAutoGeneratedSoundScene( CBaseFlex *pActor, char const *soundname pScene->GenerateSoundScene( pActor, soundname ); +#ifdef MAPBASE + pScene->m_bMultiplayer = bMultiplayer; + pScene->SetPostSpeakDelay( flPostDelay ); + DispatchSpawn( pScene ); + pScene->Activate(); + pScene->m_bIsBackground = bIsBackground; + + pScene->SetBackground( bIsBackground ); + pScene->SetRecipientFilter( filter ); + + if ( response ) + { + float flPreDelay = response->GetPreDelay(); + if ( flPreDelay ) + { + pScene->SetPreDelay( flPreDelay ); + } + } +#else pScene->Spawn(); pScene->Activate(); +#endif pScene->StartPlayback(); +#ifdef MAPBASE + if ( response ) + { + // If the response wants us to abort on NPC state switch, remember that + pScene->SetBreakOnNonIdle( response->ShouldBreakOnNonIdle() ); + } +#endif + if ( phSceneEnt ) { *phSceneEnt = pScene; @@ -4754,10 +5178,55 @@ float GetSceneDuration( char const *pszScene ) { msecs = cachedData.msecs; } +#ifdef MAPBASE + else + { + // Raw scene file support + void *pBuffer = NULL; + if (filesystem->ReadFileEx( pszScene, "MOD", &pBuffer, true )) + { + g_TokenProcessor.SetBuffer((char*)pBuffer); + CChoreoScene *pScene = ChoreoLoadScene( pszScene, NULL, &g_TokenProcessor, LocalScene_Printf ); + g_TokenProcessor.SetBuffer(NULL); + + float flDuration = pScene->GetDuration(); + delete pScene; + return flDuration; + } + } +#endif return (float)msecs * 0.001f; } +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pszScene - +// Output : float +//----------------------------------------------------------------------------- +float GetSceneSpeechDuration( char const* pszScene ) +{ + float flSecs = 0.0f; + + CChoreoScene* pScene = CSceneEntity::LoadScene( pszScene, nullptr ); + if (pScene) + { + for (int i = pScene->GetNumEvents() - 1; i >= 0; i--) + { + CChoreoEvent* pEvent = pScene->GetEvent( i ); + + if (pEvent->GetType() == CChoreoEvent::SPEAK) + { + flSecs = pEvent->GetStartTime() + pEvent->GetDuration(); + break; + } + } + delete pScene; + } + + return flSecs; +} + //----------------------------------------------------------------------------- // Purpose: // Input : *pszScene - @@ -4770,6 +5239,33 @@ int GetSceneSpeechCount( char const *pszScene ) { return cachedData.numSounds; } +#ifdef MAPBASE + else + { + void *pBuffer = NULL; + int iNumSounds = 0; + if (filesystem->ReadFileEx( pszScene, "MOD", &pBuffer, true )) + { + g_TokenProcessor.SetBuffer((char*)pBuffer); + CChoreoScene *pScene = ChoreoLoadScene( pszScene, NULL, &g_TokenProcessor, LocalScene_Printf ); + g_TokenProcessor.SetBuffer(NULL); + if (pScene) + { + for (int i = 0; i < pScene->GetNumEvents(); i++) + { + CChoreoEvent *pEvent = pScene->GetEvent(i); + + if (pEvent->GetType() == CChoreoEvent::SPEAK) + iNumSounds++; + } + } + } + + FreeSceneFileMemory( pBuffer ); + + return iNumSounds; + } +#endif return 0; } @@ -4796,12 +5292,33 @@ void PrecacheInstancedScene( char const *pszScene ) SceneCachedData_t sceneData; if ( !scenefilecache->GetSceneCachedData( pszScene, &sceneData ) ) { +#ifdef MAPBASE + char loadfile[MAX_PATH]; + Q_strncpy( loadfile, pszScene, sizeof( loadfile ) ); + Q_SetExtension( loadfile, ".vcd", sizeof( loadfile ) ); + Q_FixSlashes( loadfile ); + + // Attempt to precache manually + void *pBuffer = NULL; + if (filesystem->ReadFileEx( loadfile, "MOD", &pBuffer, true )) + { + g_TokenProcessor.SetBuffer((char*)pBuffer); + CChoreoScene *pScene = ChoreoLoadScene( loadfile, NULL, &g_TokenProcessor, LocalScene_Printf ); + if (pScene) + { + PrecacheChoreoScene(pScene); + } + g_TokenProcessor.SetBuffer(NULL); + } + FreeSceneFileMemory( pBuffer ); +#else // Scenes are sloppy and don't always exist. // A scene that is not in the pre-built cache image, but on disk, is a true error. if ( developer.GetInt() && ( IsX360() && ( g_pFullFileSystem->GetDVDMode() != DVDMODE_STRICT ) && g_pFullFileSystem->FileExists( pszScene, "GAME" ) ) ) { Warning( "PrecacheInstancedScene: Missing scene '%s' from scene image cache.\nRebuild scene image cache!\n", pszScene ); } +#endif } else { @@ -5019,6 +5536,15 @@ void CInstancedSceneEntity::OnLoaded() { BaseClass::OnLoaded(); SetBackground( m_bIsBackground ); + +#ifdef MAPBASE + // It looks like !Target1 in those default NPC scenes was a freaking lie. + if (m_hOwner) + { + m_hTarget1 = m_hOwner; + m_iszTarget1 = m_hOwner->GetEntityName(); + } +#endif } bool g_bClientFlex = true; @@ -5450,6 +5976,65 @@ bool CSceneManager::IsRunningScriptedSceneWithSpeechAndNotPaused( CBaseFlex *pAc } +#ifdef MAPBASE +bool CSceneManager::IsRunningScriptedSceneWithFlexAndNotPaused( CBaseFlex *pActor, bool bIgnoreInstancedScenes, const char *pszNotThisScene ) +{ + int c = m_ActiveScenes.Count(); + for ( int i = 0; i < c; i++ ) + { + CSceneEntity *pScene = m_ActiveScenes[ i ].Get(); + if ( !pScene || + !pScene->IsPlayingBack() || + pScene->IsPaused() || + ( bIgnoreInstancedScenes && dynamic_cast(pScene) != NULL ) || + ( pszNotThisScene == NULL || Q_strcmp( pszNotThisScene, STRING(pScene->m_iszSceneFile) ) == 0 ) + ) + { + continue; + } + + if ( pScene->InvolvesActor( pActor ) ) + { + if ( pScene->HasFlexAnimation() ) + return true; + } + } + return false; +} + + +bool CSceneManager::IsTalkingInAScriptedScene( CBaseFlex *pActor, bool bIgnoreInstancedScenes ) +{ + int c = m_ActiveScenes.Count(); + for ( int i = 0; i < c; i++ ) + { + CSceneEntity *pScene = m_ActiveScenes[ i ].Get(); + if ( !pScene || + !pScene->IsPlayingBack() || + pScene->IsPaused() || + ( bIgnoreInstancedScenes && dynamic_cast(pScene) != NULL ) + ) + { + continue; + } + + if ( pScene->InvolvesActor( pActor ) ) + { + if ( pScene->IsPlayingSpeech() ) + return true; + } + } + return false; +} + + +CUtlVector< CHandle< CSceneEntity > > *CSceneManager::GetActiveSceneList() +{ + return &m_ActiveScenes; +} +#endif + + //----------------------------------------------------------------------------- // Purpose: // Input : *actor - @@ -5535,6 +6120,23 @@ bool IsRunningScriptedSceneWithSpeechAndNotPaused( CBaseFlex *pActor, bool bIgno return GetSceneManager()->IsRunningScriptedSceneWithSpeechAndNotPaused( pActor, bIgnoreInstancedScenes ); } +#ifdef MAPBASE +bool IsRunningScriptedSceneWithFlexAndNotPaused( CBaseFlex *pActor, bool bIgnoreInstancedScenes, const char *pszNotThisScene ) +{ + return GetSceneManager()->IsRunningScriptedSceneWithFlexAndNotPaused( pActor, bIgnoreInstancedScenes, pszNotThisScene ); +} + +bool IsTalkingInAScriptedScene( CBaseFlex *pActor, bool bIgnoreInstancedScenes ) +{ + return GetSceneManager()->IsTalkingInAScriptedScene( pActor, bIgnoreInstancedScenes ); +} + +CUtlVector< CHandle< CSceneEntity > > *GetActiveSceneList() +{ + return GetSceneManager()->GetActiveSceneList(); +} +#endif + //=========================================================================================================== // SCENE LIST MANAGER @@ -5583,6 +6185,14 @@ BEGIN_DATADESC( CSceneListManager ) DEFINE_INPUTFUNC( FIELD_VOID, "Shutdown", InputShutdown ), END_DATADESC() +#ifdef MAPBASE_VSCRIPT +BEGIN_ENT_SCRIPTDESC( CSceneListManager, CBaseEntity, "Stores choreo scenes and cleans them up when a later scene in the list begins playing." ) + + DEFINE_SCRIPTFUNC_NAMED( ScriptGetScene, "GetScene", "Gets the specified scene index from this manager." ) + +END_SCRIPTDESC(); +#endif + //----------------------------------------------------------------------------- // Purpose: @@ -5725,6 +6335,19 @@ void CSceneListManager::RemoveScene( int iIndex ) } } +#ifdef MAPBASE_VSCRIPT +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +HSCRIPT CSceneListManager::ScriptGetScene( int iIndex ) +{ + if ( iIndex < 0 || iIndex >= SCENE_LIST_MANAGER_MAX_SCENES ) + return NULL; + + return ToHScript( m_hScenes[iIndex] ); +} +#endif + void ReloadSceneFromDisk( CBaseEntity *ent ) { CSceneEntity *scene = dynamic_cast< CSceneEntity * >( ent ); diff --git a/src/game/server/sceneentity.h b/src/game/server/sceneentity.h index f5289ba5..ea286fe9 100644 --- a/src/game/server/sceneentity.h +++ b/src/game/server/sceneentity.h @@ -13,7 +13,9 @@ // List of the last 5 lines of speech from NPCs for bug reports #define SPEECH_LIST_MAX_SOUNDS 5 +#ifndef NEW_RESPONSE_SYSTEM class AI_Response; +#endif struct recentNPCSpeech_t { @@ -24,7 +26,11 @@ struct recentNPCSpeech_t int GetRecentNPCSpeech( recentNPCSpeech_t speech[ SPEECH_LIST_MAX_SOUNDS ] ); float InstancedScriptedScene( CBaseFlex *pActor, const char *pszScene, EHANDLE *phSceneEnt = NULL, float flPostDelay = 0.0f, bool bIsBackground = false, AI_Response *response = NULL, bool bMultiplayer = false, IRecipientFilter *filter = NULL ); +#ifdef MAPBASE +float InstancedAutoGeneratedSoundScene( CBaseFlex *pActor, char const *soundname, EHANDLE *phSceneEnt = NULL, float flPostDelay = 0.0f, bool bIsBackground = false, AI_Response *response = NULL, bool bMultiplayer = false, IRecipientFilter *filter = NULL ); +#else float InstancedAutoGeneratedSoundScene( CBaseFlex *pActor, char const *soundname, EHANDLE *phSceneEnt = NULL ); +#endif void StopScriptedScene( CBaseFlex *pActor, EHANDLE hSceneEnt ); void RemoveActorFromScriptedScenes( CBaseFlex *pActor, bool instancedscenesonly, bool nonidlescenesonly = false, const char *pszThisSceneOnly = NULL ); void RemoveAllScenesInvolvingActor( CBaseFlex *pActor ); @@ -35,7 +41,13 @@ bool IsRunningScriptedScene( CBaseFlex *pActor, bool bIgnoreInstancedScenes = tr bool IsRunningScriptedSceneAndNotPaused( CBaseFlex *pActor, bool bIgnoreInstancedScenes = true ); bool IsRunningScriptedSceneWithSpeech( CBaseFlex *pActor, bool bIgnoreInstancedScenes = false ); bool IsRunningScriptedSceneWithSpeechAndNotPaused( CBaseFlex *pActor, bool bIgnoreInstancedScenes = false ); +#ifdef MAPBASE +bool IsRunningScriptedSceneWithFlexAndNotPaused( CBaseFlex *pActor, bool bIgnoreInstancedScenes = false, const char *pszNotThisScene = NULL ); +bool IsTalkingInAScriptedScene( CBaseFlex *pActor, bool bIgnoreInstancedScenes = false ); +CUtlVector< CHandle< CSceneEntity > > *GetActiveSceneList(); +#endif float GetSceneDuration( char const *pszScene ); +float GetSceneSpeechDuration( char const* pszScene ); int GetSceneSpeechCount( char const *pszScene ); bool IsInInterruptableScenes( CBaseFlex *pActor ); @@ -45,5 +57,12 @@ HSCRIPT ScriptCreateSceneEntity( char const *pszScene ); char const *GetSceneFilename( CBaseEntity *ent ); void ReloadSceneFromDisk( CBaseEntity *ent ); +#ifdef MAPBASE +const char *GetFirstSoundInScene(const char *pszScene); +const char *GetFirstSoundInScene(CChoreoScene *scene); + +CBaseEntity *UTIL_FindNamedSceneEntity(const char *name, CBaseEntity *pActor, CSceneEntity *scene, bool bBaseFlexOnly = false, bool bUseClear = false); +#endif + #endif // SCENEENTITY_H diff --git a/src/game/server/scripted.cpp b/src/game/server/scripted.cpp index 6a492708..1cc14d77 100644 --- a/src/game/server/scripted.cpp +++ b/src/game/server/scripted.cpp @@ -31,6 +31,23 @@ ConVar ai_task_pre_script( "ai_task_pre_script", "0", FCVAR_NONE ); +// New macros introduced for Mapbase's console message color changes. +#ifdef MAPBASE +#define ScriptMsg( lvl, msg ) CGMsg( lvl, CON_GROUP_NPC_SCRIPTS, msg ) +#define ScriptMsg1( lvl, msg, a ) CGMsg( lvl, CON_GROUP_NPC_SCRIPTS, msg, a ) +#define ScriptMsg2( lvl, msg, a, b ) CGMsg( lvl, CON_GROUP_NPC_SCRIPTS, msg, a, b ) +#define ScriptMsg3( lvl, msg, a, b, c ) CGMsg( lvl, CON_GROUP_NPC_SCRIPTS, msg, a, b, c ) +#define ScriptMsg4( lvl, msg, a, b, c, d ) CGMsg( lvl, CON_GROUP_NPC_SCRIPTS, msg, a, b, c, d ) +#define ScriptMsg5( lvl, msg, a, b, c, d, e ) CGMsg( lvl, CON_GROUP_NPC_SCRIPTS, msg, a, b, c, d, e ) +#else +#define ScriptMsg( lvl, msg ) DevMsg( lvl, msg ) +#define ScriptMsg1( lvl, msg, a ) DevMsg( lvl, msg, a ) +#define ScriptMsg2( lvl, msg, a, b ) DevMsg( lvl, msg, a, b ) +#define ScriptMsg3( lvl, msg, a, b, c ) DevMsg( lvl, msg, a, b, c ) +#define ScriptMsg4( lvl, msg, a, b, c, d ) DevMsg( lvl, msg, a, b, c, d ) +#define ScriptMsg5( lvl, msg, a, b, c, d, e ) DevMsg( lvl, msg, a, b, c, d, e ) +#endif + // // targetname "me" - there can be more than one with the same name, and they act in concert @@ -96,10 +113,17 @@ BEGIN_DATADESC( CAI_ScriptedSequence ) DEFINE_INPUTFUNC( FIELD_VOID, "MoveToPosition", InputMoveToPosition ), DEFINE_INPUTFUNC( FIELD_VOID, "BeginSequence", InputBeginSequence ), DEFINE_INPUTFUNC( FIELD_VOID, "CancelSequence", InputCancelSequence ), +#ifdef MAPBASE + DEFINE_INPUTFUNC( FIELD_VOID, "StopActionLoop", InputStopActionLoop ), +#endif DEFINE_KEYFIELD( m_iPlayerDeathBehavior, FIELD_INTEGER, "onplayerdeath" ), DEFINE_INPUTFUNC( FIELD_VOID, "ScriptPlayerDeath", InputScriptPlayerDeath ), +#ifdef MAPBASE + DEFINE_FIELD( m_hActivator, FIELD_EHANDLE ), +#endif + // Outputs DEFINE_OUTPUT(m_OnBeginSequence, "OnBeginSequence"), DEFINE_OUTPUT(m_OnEndSequence, "OnEndSequence"), @@ -114,6 +138,12 @@ BEGIN_DATADESC( CAI_ScriptedSequence ) DEFINE_OUTPUT(m_OnScriptEvent[5], "OnScriptEvent06"), DEFINE_OUTPUT(m_OnScriptEvent[6], "OnScriptEvent07"), DEFINE_OUTPUT(m_OnScriptEvent[7], "OnScriptEvent08"), +#ifdef MAPBASE + DEFINE_OUTPUT(m_OnEntrySequence, "OnEntrySequence"), + DEFINE_OUTPUT(m_OnActionSequence, "OnActionSequence"), + DEFINE_OUTPUT(m_OnPreIdleSequence, "OnPreIdleSequence"), + DEFINE_OUTPUT(m_OnFoundNPC, "OnFoundNPC"), +#endif END_DATADESC() @@ -179,18 +209,31 @@ void CAI_ScriptedSequence::ScriptEntityCancel( CBaseEntity *pentCine, bool bPret if ( bPretendSuccess ) { // We need to pretend that this sequence actually finished fully +#ifdef MAPBASE + pCineTarget->m_OnEndSequence.FireOutput(pEntity, pCineTarget); + pCineTarget->m_OnPostIdleEndSequence.FireOutput(pEntity, pCineTarget); +#else pCineTarget->m_OnEndSequence.FireOutput(NULL, pCineTarget); pCineTarget->m_OnPostIdleEndSequence.FireOutput(NULL, pCineTarget); +#endif } else { // Fire the cancel +#ifdef MAPBASE + pCineTarget->m_OnCancelSequence.FireOutput(pEntity, pCineTarget); +#else pCineTarget->m_OnCancelSequence.FireOutput(NULL, pCineTarget); +#endif if ( pCineTarget->m_startTime == 0 ) { // If start time is 0, this sequence never actually ran. Fire the failed output. +#ifdef MAPBASE + pCineTarget->m_OnCancelFailedSequence.FireOutput(pEntity, pCineTarget); +#else pCineTarget->m_OnCancelFailedSequence.FireOutput(NULL, pCineTarget); +#endif } } } @@ -332,6 +375,26 @@ void CAI_ScriptedSequence::InputMoveToPosition( inputdata_t &inputdata ) } } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: Sets our target NPC with the generic SetTarget input. +//----------------------------------------------------------------------------- +void CAI_ScriptedSequence::InputSetTarget( inputdata_t &inputdata ) +{ + m_hActivator = inputdata.pActivator; + m_iszEntity = AllocPooledString(inputdata.value.String()); + m_hTargetEnt = NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CAI_ScriptedSequence::InputStopActionLoop( inputdata_t &inputdata ) +{ + StopActionLoop( false ); +} +#endif + //----------------------------------------------------------------------------- // Purpose: Input handler that activates the scripted sequence. @@ -343,6 +406,14 @@ void CAI_ScriptedSequence::InputBeginSequence( inputdata_t &inputdata ) // Start the script as soon as possible. m_bWaitForBeginSequence = false; + +#ifdef MAPBASE + m_hActivator = inputdata.pActivator; + + // TODO: Investigate whether this is necessary + //if (FStrEq(STRING(m_iszEntity), "!activator")) + // SetTarget(NULL); +#endif // do I already know who I should use? CBaseEntity *pEntity = GetTarget(); @@ -407,7 +478,7 @@ void CAI_ScriptedSequence::InputCancelSequence( inputdata_t &inputdata ) // We don't call CancelScript because entity I/O will handle dispatching // this input to all other scripts with our same name. // - DevMsg( 2, "InputCancelScript: Cancelling script '%s'\n", STRING( m_iszPlay )); + ScriptMsg1( 2, "InputCancelScript: Cancelling script '%s'\n", STRING( m_iszPlay )); StopThink(); ScriptEntityCancel( this ); } @@ -423,7 +494,7 @@ void CAI_ScriptedSequence::InputScriptPlayerDeath( inputdata_t &inputdata ) // We don't call CancelScript because entity I/O will handle dispatching // this input to all other scripts with our same name. // - DevMsg( 2, "InputCancelScript: Cancelling script '%s'\n", STRING( m_iszPlay )); + ScriptMsg1( 2, "InputCancelScript: Cancelling script '%s'\n", STRING( m_iszPlay )); StopThink(); ScriptEntityCancel( this ); } @@ -472,7 +543,7 @@ void CAI_ScriptedSequence::Blocked( CBaseEntity *pOther ) void CAI_ScriptedSequence::Touch( CBaseEntity *pOther ) { /* - DevMsg( 2, "Cine Touch\n" ); + ScriptMsg( 2, "Cine Touch\n" ); if (m_pentTarget && OFFSET(pOther->pev) == OFFSET(m_pentTarget)) { CAI_BaseNPC *pTarget = GetClassPtr((CAI_BaseNPC *)VARS(m_pentTarget)); @@ -521,7 +592,11 @@ CAI_BaseNPC *CAI_ScriptedSequence::FindScriptEntity( ) { interrupt = SS_INTERRUPT_BY_NAME; +#ifdef MAPBASE + pEntity = gEntList.FindEntityByNameWithin( m_hLastFoundEntity, STRING( m_iszEntity ), GetAbsOrigin(), m_flRadius, this, m_hActivator ); +#else pEntity = gEntList.FindEntityByNameWithin( m_hLastFoundEntity, STRING( m_iszEntity ), GetAbsOrigin(), m_flRadius ); +#endif if (!pEntity) { pEntity = gEntList.FindEntityByClassnameWithin( m_hLastFoundEntity, STRING( m_iszEntity ), GetAbsOrigin(), m_flRadius ); @@ -551,7 +626,7 @@ CAI_BaseNPC *CAI_ScriptedSequence::FindScriptEntity( ) else if (!(m_spawnflags & SF_SCRIPT_NO_COMPLAINTS)) { // They cannot play the script. - DevMsg( "Found %s, but can't play!\n", STRING( m_iszEntity )); + ScriptMsg1( 1, "Found %s, but can't play!\n", STRING( m_iszEntity )); } } @@ -638,7 +713,7 @@ void CAI_ScriptedSequence::StartScript( void ) // Don't clear the currently playing script's target! pCine->SetTarget( NULL ); } - DevMsg( 2, "script \"%s\" kicking script \"%s\" out of the queue\n", GetDebugName(), pCine->GetDebugName() ); + ScriptMsg2( 2, "script \"%s\" kicking script \"%s\" out of the queue\n", GetDebugName(), pCine->GetDebugName() ); } pTarget->m_hCine->m_hNextCine = this; @@ -744,7 +819,7 @@ void CAI_ScriptedSequence::StartScript( void ) //pTarget->SetGroundEntity( NULL ); break; } - //DevMsg( 2, "\"%s\" found and used (INT: %s)\n", STRING( pTarget->m_iName ), FBitSet(m_spawnflags, SF_SCRIPT_NOINTERRUPT)?"No":"Yes" ); + //ScriptMsg2( 2, "\"%s\" found and used (INT: %s)\n", STRING( pTarget->m_iName ), FBitSet(m_spawnflags, SF_SCRIPT_NOINTERRUPT)?"No":"Yes" ); // Wait until all scripts of the same name are ready to play. @@ -759,6 +834,10 @@ void CAI_ScriptedSequence::StartScript( void ) DevWarning( "scripted_sequence %d:%s - restarting dormant entity %d:%s : %.1f:%.1f\n", entindex(), GetDebugName(), pTarget->entindex(), pTarget->GetDebugName(), gpGlobals->curtime, pTarget->GetNextThink() ); pTarget->SetNextThink( gpGlobals->curtime ); } + +#ifdef MAPBASE + m_OnFoundNPC.FireOutput( pTarget, this ); +#endif } } @@ -775,12 +854,12 @@ void CAI_ScriptedSequence::ScriptThink( void ) else if (FindEntity()) { StartScript( ); - DevMsg( 2, "scripted_sequence %d:\"%s\" using NPC %d:\"%s\"(%s)\n", entindex(), GetDebugName(), GetTarget()->entindex(), GetTarget()->GetEntityName().ToCStr(), STRING( m_iszEntity ) ); + ScriptMsg5( 2, "scripted_sequence %d:\"%s\" using NPC %d:\"%s\"(%s)\n", entindex(), GetDebugName(), GetTarget()->entindex(), GetTarget()->GetEntityName().ToCStr(), STRING( m_iszEntity ) ); } else { CancelScript( ); - DevMsg( 2, "scripted_sequence %d:\"%s\" can't find NPC \"%s\"\n", entindex(), GetDebugName(), STRING( m_iszEntity ) ); + ScriptMsg3( 2, "scripted_sequence %d:\"%s\" can't find NPC \"%s\"\n", entindex(), GetDebugName(), STRING( m_iszEntity ) ); // FIXME: just trying again is bad. This should fire an output instead. // FIXME: Think about puting output triggers in both StartScript() and CancelScript(). SetNextThink( gpGlobals->curtime + 1.0f ); @@ -792,10 +871,32 @@ void CAI_ScriptedSequence::ScriptThink( void ) // Purpose: Callback for firing the begin sequence output. Called by the NPC that // is running the script as it starts the action seqeunce. //----------------------------------------------------------------------------- +#ifdef MAPBASE +void CAI_ScriptedSequence::OnBeginSequence( CBaseEntity *pActor ) +{ + m_OnBeginSequence.FireOutput( pActor, this ); +} + +void CAI_ScriptedSequence::OnEntrySequence( CBaseEntity *pActor ) +{ + m_OnEntrySequence.FireOutput( pActor, this ); +} + +void CAI_ScriptedSequence::OnActionSequence( CBaseEntity *pActor ) +{ + m_OnActionSequence.FireOutput( pActor, this ); +} + +void CAI_ScriptedSequence::OnPreIdleSequence( CBaseEntity *pActor ) +{ + m_OnPreIdleSequence.FireOutput( pActor, this ); +} +#else void CAI_ScriptedSequence::OnBeginSequence( void ) { m_OnBeginSequence.FireOutput( this, this ); } +#endif //----------------------------------------------------------------------------- @@ -841,7 +942,7 @@ bool CAI_ScriptedSequence::StartSequence( CAI_BaseNPC *pTarget, string_t iszSeq, // Don't blend... pTarget->IncrementInterpolationFrame(); } - //DevMsg( 2, "%s (%s): started \"%s\":INT:%s\n", STRING( pTarget->m_iName ), pTarget->GetClassname(), STRING( iszSeq), (m_spawnflags & SF_SCRIPT_NOINTERRUPT) ? "No" : "Yes" ); + //ScriptMsg4( 2, "%s (%s): started \"%s\":INT:%s\n", STRING( pTarget->m_iName ), pTarget->GetClassname(), STRING( iszSeq), (m_spawnflags & SF_SCRIPT_NOINTERRUPT) ? "No" : "Yes" ); return true; } @@ -921,7 +1022,7 @@ bool CAI_ScriptedSequence::FinishedActionSequence( CAI_BaseNPC *pNPC ) //----------------------------------------------------------------------------- void CAI_ScriptedSequence::SequenceDone( CAI_BaseNPC *pNPC ) { - //DevMsg( 2, "Sequence %s finished\n", STRING( pNPC->m_hCine->m_iszPlay ) ); + //ScriptMsg1( 2, "Sequence %s finished\n", STRING( pNPC->m_hCine->m_iszPlay ) ); //Msg("%s SequenceDone() at %0.2f\n", pNPC->GetDebugName(), gpGlobals->curtime ); @@ -964,7 +1065,11 @@ void CAI_ScriptedSequence::SequenceDone( CAI_BaseNPC *pNPC ) } } +#ifdef MAPBASE + m_OnEndSequence.FireOutput(pNPC, this); +#else m_OnEndSequence.FireOutput(NULL, this); +#endif } //----------------------------------------------------------------------------- @@ -1027,7 +1132,7 @@ void CAI_ScriptedSequence::PostIdleDone( CAI_BaseNPC *pNPC ) // Only do so if we're selected, to prevent spam if ( pNPC->m_debugOverlays & OVERLAY_NPC_SELECTED_BIT ) { - DevMsg( 2, "Post Idle %s finished for %s\n", STRING( pNPC->m_hCine->m_iszPostIdle ), pNPC->GetDebugName() ); + ScriptMsg2( 2, "Post Idle %s finished for %s\n", STRING( pNPC->m_hCine->m_iszPostIdle ), pNPC->GetDebugName() ); } pNPC->m_scriptState = CAI_BaseNPC::SCRIPT_POST_IDLE; @@ -1073,7 +1178,11 @@ void CAI_ScriptedSequence::PostIdleDone( CAI_BaseNPC *pNPC ) } //Msg("%s finished post idle at %0.2f\n", pNPC->GetDebugName(), gpGlobals->curtime ); +#ifdef MAPBASE + m_OnPostIdleEndSequence.FireOutput(pNPC, this); +#else m_OnPostIdleEndSequence.FireOutput(NULL, this); +#endif } @@ -1189,7 +1298,7 @@ bool CAI_ScriptedSequence::CanEnqueueAfter( void ) if ( m_iszNextScript != NULL_STRING ) { - DevMsg( 2, "%s is specified as the 'Next Script' and cannot be kicked out of the queue\n", m_hNextCine->GetDebugName() ); + ScriptMsg1( 2, "%s is specified as the 'Next Script' and cannot be kicked out of the queue\n", m_hNextCine->GetDebugName() ); return false; } @@ -1198,7 +1307,7 @@ bool CAI_ScriptedSequence::CanEnqueueAfter( void ) return true; } - DevMsg( 2, "%s is a priority script and cannot be kicked out of the queue\n", m_hNextCine->GetDebugName() ); + ScriptMsg1( 2, "%s is a priority script and cannot be kicked out of the queue\n", m_hNextCine->GetDebugName() ); return false; } @@ -1298,11 +1407,31 @@ void CAI_ScriptedSequence::ModifyScriptedAutoMovement( Vector *vecNewPos ) } } + VMatrix matInteractionPosition = m_matInteractionPosition; + +#ifdef MAPBASE + // Account for our own sequence movement + pAnimating = m_hTargetEnt->GetBaseAnimating(); + if (pAnimating) + { + Vector vecDeltaPos; + QAngle angDeltaAngles; + + pAnimating->GetSequenceMovement( pAnimating->GetSequence(), 0.0f, pAnimating->GetCycle(), vecDeltaPos, angDeltaAngles ); + if (!vecDeltaPos.IsZero()) + { + VMatrix matLocalMovement; + matLocalMovement.SetupMatrixOrgAngles( vecDeltaPos, angDeltaAngles ); + MatrixMultiply( m_matInteractionPosition, matLocalMovement, matInteractionPosition ); + } + } +#endif + // We've been asked to maintain a specific position relative to the other NPC // we're interacting with. Lerp towards the relative position. VMatrix matMeToWorld, matLocalToWorld; matMeToWorld.SetupMatrixOrgAngles( vecRelativeOrigin, angRelativeAngles ); - MatrixMultiply( matMeToWorld, m_matInteractionPosition, matLocalToWorld ); + MatrixMultiply( matMeToWorld, matInteractionPosition, matLocalToWorld ); // Get the desired NPC position in worldspace Vector vecOrigin; @@ -1333,7 +1462,7 @@ void CAI_ScriptedSequence::ModifyScriptedAutoMovement( Vector *vecNewPos ) //----------------------------------------------------------------------------- void CAI_ScriptedSequence::CancelScript( void ) { - DevMsg( 2, "Cancelling script: %s\n", STRING( m_iszPlay )); + ScriptMsg1( 2, "Cancelling script: %s\n", STRING( m_iszPlay )); // Don't cancel matching sequences if we're asked not to, unless we didn't actually // succeed in starting, in which case we should always cancel. This fixes @@ -1576,6 +1705,9 @@ private: // Input handlers void InputStartSchedule( inputdata_t &inputdata ); void InputStopSchedule( inputdata_t &inputdata ); +#ifdef MAPBASE + void InputSetTarget( inputdata_t &inputdata ); +#endif CAI_BaseNPC *FindScriptEntity( bool bCyclic ); @@ -1660,7 +1792,7 @@ void CAI_ScriptedSchedule::ScriptThink( void ) pTarget = FindScriptEntity( (m_spawnflags & SF_SCRIPT_SEARCH_CYCLICALLY) != 0 ); if ( pTarget ) { - DevMsg( 2, "scripted_schedule \"%s\" using NPC \"%s\"(%s)\n", GetDebugName(), STRING( m_iszEntity ), pTarget->GetEntityName().ToCStr() ); + ScriptMsg3( 2, "scripted_schedule \"%s\" using NPC \"%s\"(%s)\n", GetDebugName(), STRING( m_iszEntity ), pTarget->GetEntityName().ToCStr() ); StartSchedule( pTarget ); success = true; } @@ -1670,7 +1802,7 @@ void CAI_ScriptedSchedule::ScriptThink( void ) m_hLastFoundEntity = NULL; while ( ( pTarget = FindScriptEntity( true ) ) != NULL ) { - DevMsg( 2, "scripted_schedule \"%s\" using NPC \"%s\"(%s)\n", GetDebugName(), pTarget->GetEntityName().ToCStr(), STRING( m_iszEntity ) ); + ScriptMsg3( 2, "scripted_schedule \"%s\" using NPC \"%s\"(%s)\n", GetDebugName(), pTarget->GetEntityName().ToCStr(), STRING( m_iszEntity ) ); StartSchedule( pTarget ); success = true; } @@ -1678,7 +1810,7 @@ void CAI_ScriptedSchedule::ScriptThink( void ) if ( !success ) { - DevMsg( 2, "scripted_schedule \"%s\" can't find NPC \"%s\"\n", GetDebugName(), STRING( m_iszEntity ) ); + ScriptMsg2( 2, "scripted_schedule \"%s\" can't find NPC \"%s\"\n", GetDebugName(), STRING( m_iszEntity ) ); // FIXME: just trying again is bad. This should fire an output instead. // FIXME: Think about puting output triggers on success true and sucess false // FIXME: also needs to check the result of StartSchedule(), which can fail and not complain @@ -1739,7 +1871,7 @@ void CAI_ScriptedSchedule::StartSchedule( CAI_BaseNPC *pTarget ) CAI_Hint *pHint = CAI_HintManager::FindHint( pTarget->GetAbsOrigin(), hintCriteria ); if ( !pHint ) { - DevMsg( 1, "Can't find goal entity %s\nCan't execute script %s\n", STRING(m_sGoalEnt), GetDebugName() ); + ScriptMsg2( 1, "Can't find goal entity %s\nCan't execute script %s\n", STRING(m_sGoalEnt), GetDebugName() ); return; } pGoalEnt = pHint; @@ -1780,7 +1912,7 @@ void CAI_ScriptedSchedule::StartSchedule( CAI_BaseNPC *pTarget ) pTarget->SetCondition( COND_SCHEDULE_DONE ); } else - DevMsg( "Scripted schedule %s specified an invalid enemy %s\n", STRING( GetEntityName() ), STRING( m_sGoalEnt ) ); + ScriptMsg2( 1, "Scripted schedule %s specified an invalid enemy %s\n", STRING( GetEntityName() ), STRING( m_sGoalEnt ) ); } bool bDidSetSchedule = false; @@ -1805,7 +1937,7 @@ void CAI_ScriptedSchedule::StartSchedule( CAI_BaseNPC *pTarget ) { if (!(m_spawnflags & SF_SCRIPT_NO_COMPLAINTS)) { - DevMsg( 1, "ScheduledMoveToGoalEntity to goal entity %s failed\nCan't execute script %s\n", STRING(m_sGoalEnt), GetDebugName() ); + ScriptMsg2( 1, "ScheduledMoveToGoalEntity to goal entity %s failed\nCan't execute script %s\n", STRING(m_sGoalEnt), GetDebugName() ); } return; } @@ -1827,7 +1959,7 @@ void CAI_ScriptedSchedule::StartSchedule( CAI_BaseNPC *pTarget ) { if (!(m_spawnflags & SF_SCRIPT_NO_COMPLAINTS)) { - DevMsg( 1, "ScheduledFollowPath to goal entity %s failed\nCan't execute script %s\n", STRING(m_sGoalEnt), GetDebugName() ); + ScriptMsg2( 1, "ScheduledFollowPath to goal entity %s failed\nCan't execute script %s\n", STRING(m_sGoalEnt), GetDebugName() ); } return; } @@ -1852,7 +1984,7 @@ void CAI_ScriptedSchedule::InputStartSchedule( inputdata_t &inputdata ) { if (( m_nForceState == 0 ) && ( m_nSchedule == 0 )) { - DevMsg( 2, "aiscripted_schedule - no schedule or state has been set!\n" ); + ScriptMsg( 2, "aiscripted_schedule - no schedule or state has been set!\n" ); } if ( !m_bDidFireOnce || ( m_spawnflags & SF_SCRIPT_REPEATABLE ) ) @@ -1864,7 +1996,7 @@ void CAI_ScriptedSchedule::InputStartSchedule( inputdata_t &inputdata ) } else { - DevMsg( 2, "aiscripted_schedule - not playing schedule again: not flagged to repeat\n" ); + ScriptMsg( 2, "aiscripted_schedule - not playing schedule again: not flagged to repeat\n" ); } } @@ -1875,7 +2007,7 @@ void CAI_ScriptedSchedule::InputStopSchedule( inputdata_t &inputdata ) { if ( !m_bDidFireOnce ) { - DevMsg( 2, "aiscripted_schedule - StopSchedule called, but schedule's never started.\n" ); + ScriptMsg( 2, "aiscripted_schedule - StopSchedule called, but schedule's never started.\n" ); return; } @@ -1898,6 +2030,17 @@ void CAI_ScriptedSchedule::InputStopSchedule( inputdata_t &inputdata ) } } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: Sets our target NPC with the generic SetTarget input. +//----------------------------------------------------------------------------- +void CAI_ScriptedSchedule::InputSetTarget( inputdata_t &inputdata ) +{ + m_hActivator = inputdata.pActivator; + m_iszEntity = AllocPooledString( inputdata.value.String() ); +} +#endif + //----------------------------------------------------------------------------- // Purpose: If the target entity appears to be running this scripted schedule break it //----------------------------------------------------------------------------- @@ -1905,7 +2048,7 @@ void CAI_ScriptedSchedule::StopSchedule( CAI_BaseNPC *pTarget ) { if ( pTarget->IsCurSchedule( SCHED_IDLE_WALK ) ) { - DevMsg( 2, "%s (%s): StopSchedule called on NPC %s.\n", GetClassname(), GetDebugName(), pTarget->GetDebugName() ); + ScriptMsg3( 2, "%s (%s): StopSchedule called on NPC %s.\n", GetClassname(), GetDebugName(), pTarget->GetDebugName() ); pTarget->ClearSchedule( "Stopping scripted schedule" ); } } @@ -2189,7 +2332,7 @@ int CAI_ScriptedSentence::StartSentence( CAI_BaseNPC *pTarget ) { if ( !pTarget ) { - DevMsg( 2, "Not Playing sentence %s\n", STRING(m_iszSentence) ); + ScriptMsg1( 2, "Not Playing sentence %s\n", STRING(m_iszSentence) ); return -1; } @@ -2214,13 +2357,179 @@ int CAI_ScriptedSentence::StartSentence( CAI_BaseNPC *pTarget ) } int sentenceIndex = pTarget->PlayScriptedSentence( STRING(m_iszSentence), m_flDelay, m_flVolume, m_iSoundLevel, bConcurrent, pListener ); - DevMsg( 2, "Playing sentence %s\n", STRING(m_iszSentence) ); + ScriptMsg1( 2, "Playing sentence %s\n", STRING(m_iszSentence) ); m_OnBeginSentence.FireOutput(NULL, this); return sentenceIndex; } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// This isn't exclusive to NPCs, so it could be moved if needed. +//----------------------------------------------------------------------------- +class CScriptedSound : public CPointEntity +{ +public: + DECLARE_CLASS( CScriptedSound, CPointEntity ); + DECLARE_DATADESC(); + + void Precache(); + + CBaseEntity *GetTarget(inputdata_t &inputdata); + + // Input handlers + void InputPlaySound( inputdata_t &inputdata ); + void InputPlaySoundOnEntity( inputdata_t &inputdata ); + void InputStopSound( inputdata_t &inputdata ); + void InputSetSound( inputdata_t &inputdata ); + +private: + string_t m_message; + + bool m_bGrabAll; +}; + + +BEGIN_DATADESC( CScriptedSound ) + + DEFINE_KEYFIELD( m_message, FIELD_STRING, "message" ), + DEFINE_KEYFIELD( m_bGrabAll, FIELD_BOOLEAN, "GrabAll" ), + + // Inputs + DEFINE_INPUTFUNC(FIELD_VOID, "PlaySound", InputPlaySound), + DEFINE_INPUTFUNC(FIELD_EHANDLE, "PlaySoundOnEntity", InputPlaySoundOnEntity), + DEFINE_INPUTFUNC(FIELD_VOID, "StopSound", InputStopSound), + DEFINE_INPUTFUNC(FIELD_STRING, "SetSound", InputSetSound), + +END_DATADESC() + + + +LINK_ENTITY_TO_CLASS( scripted_sound, CScriptedSound ); + +//----------------------------------------------------------------------------- +// Purpose: +// Input : +// Output : +//----------------------------------------------------------------------------- +void CScriptedSound::Precache() +{ + //PrecacheScriptSound(STRING(m_message)); + + BaseClass::Precache(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : +// Output : +//----------------------------------------------------------------------------- +CBaseEntity *CScriptedSound::GetTarget(inputdata_t &inputdata) +{ + CBaseEntity *pEntity = NULL; + if (m_target == NULL_STRING) + { + // Use this as the default source entity + pEntity = this; + m_bGrabAll = false; + } + else + { + pEntity = gEntList.FindEntityGeneric(NULL, STRING(m_target), this, inputdata.pActivator, inputdata.pCaller); + } + + return pEntity; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : +// Output : +//----------------------------------------------------------------------------- +void CScriptedSound::InputPlaySound( inputdata_t &inputdata ) +{ + PrecacheScriptSound(STRING(m_message)); + + CBaseEntity *pEntity = GetTarget(inputdata); + const char *sound = STRING(m_message); + if (m_bGrabAll) + { + //if (pEntity) + //{ + // pEntity->PrecacheScriptSound(sound); + //} + + while (pEntity) + { + pEntity->EmitSound(sound); + pEntity = gEntList.FindEntityGeneric(pEntity, STRING(m_target), this, inputdata.pActivator, inputdata.pCaller); + } + } + else if (pEntity) + { + //pEntity->PrecacheScriptSound(sound); + pEntity->EmitSound(sound); + } + else + { + Warning("%s unable to find target entity %s!\n", GetDebugName(), STRING(m_target)); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : +// Output : +//----------------------------------------------------------------------------- +void CScriptedSound::InputPlaySoundOnEntity( inputdata_t &inputdata ) +{ + if (inputdata.value.Entity()) + { + inputdata.value.Entity()->PrecacheScriptSound(STRING(m_message)); + inputdata.value.Entity()->EmitSound(STRING(m_message)); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : +// Output : +//----------------------------------------------------------------------------- +void CScriptedSound::InputStopSound( inputdata_t &inputdata ) +{ + CBaseEntity *pEntity = GetTarget(inputdata); + const char *sound = STRING(m_message); + if (m_bGrabAll) + { + while (pEntity) + { + pEntity->StopSound(sound); + pEntity = gEntList.FindEntityGeneric(pEntity, STRING(m_target), this, inputdata.pActivator, inputdata.pCaller); + } + } + else if (pEntity) + { + pEntity->StopSound(sound); + } + else + { + Warning("%s unable to find target entity %s!\n", GetDebugName(), STRING(m_target)); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : +// Output : +//----------------------------------------------------------------------------- +void CScriptedSound::InputSetSound( inputdata_t &inputdata ) +{ + PrecacheScriptSound(inputdata.value.String()); + m_message = inputdata.value.StringID(); +} +#endif + // HACKHACK: This is a little expensive with the dynamic_cast<> and all, but it lets us solve diff --git a/src/game/server/scripted.h b/src/game/server/scripted.h index e35d57be..1f77b7e5 100644 --- a/src/game/server/scripted.h +++ b/src/game/server/scripted.h @@ -95,7 +95,14 @@ public: bool FindEntity( void ); void StartScript( void ); void FireScriptEvent( int nEvent ); +#ifdef MAPBASE + void OnBeginSequence( CBaseEntity *pActor ); + void OnEntrySequence( CBaseEntity *pActor ); + void OnActionSequence( CBaseEntity *pActor ); + void OnPreIdleSequence( CBaseEntity *pActor ); +#else void OnBeginSequence( void ); +#endif void SetTarget( CBaseEntity *pTarget ) { m_hTargetEnt = pTarget; }; CBaseEntity *GetTarget( void ) { return m_hTargetEnt; }; @@ -104,6 +111,10 @@ public: void InputBeginSequence( inputdata_t &inputdata ); void InputCancelSequence( inputdata_t &inputdata ); void InputMoveToPosition( inputdata_t &inputdata ); +#ifdef MAPBASE + void InputStopActionLoop( inputdata_t &inputdata ); + void InputSetTarget( inputdata_t &inputdata ); +#endif bool IsTimeToStart( void ); bool IsWaitingForBegin( void ); @@ -211,6 +222,12 @@ private: COutputEvent m_OnCancelSequence; COutputEvent m_OnCancelFailedSequence; // Fired when a scene is cancelled before it's ever run COutputEvent m_OnScriptEvent[MAX_SCRIPT_EVENTS]; +#ifdef MAPBASE + COutputEvent m_OnEntrySequence; + COutputEvent m_OnActionSequence; + COutputEvent m_OnPreIdleSequence; + COutputEvent m_OnFoundNPC; +#endif static void ScriptEntityCancel( CBaseEntity *pentCine, bool bPretendSuccess = false ); @@ -223,6 +240,11 @@ private: EHANDLE m_hInteractionRelativeEntity; int m_iPlayerDeathBehavior; + +#ifdef MAPBASE + // !activator functionality + EHANDLE m_hActivator; +#endif }; diff --git a/src/game/server/server_base.vpc b/src/game/server/server_base.vpc index 32567c5d..981505df 100644 --- a/src/game/server/server_base.vpc +++ b/src/game/server/server_base.vpc @@ -18,6 +18,9 @@ $include "$SRCDIR\vpc_scripts\protobuf_builder.vpc" $Include "$SRCDIR\vpc_scripts\source_replay.vpc" [$TF] $Include "$SRCDIR\game\protobuf_include.vpc" +// Mapbase stuff +$Include "$SRCDIR\game\server\server_mapbase.vpc" [$MAPBASE] + $Configuration "Debug" { $General @@ -138,8 +141,10 @@ $Project $File "ai_concommands.cpp" $File "ai_condition.cpp" $File "ai_condition.h" - $File "AI_Criteria.cpp" + $File "AI_Criteria.cpp" [!$NEW_RESPONSE_SYSTEM] $File "AI_Criteria.h" + $File "$SRCDIR\game\shared\ai_criteria_new.cpp" [$NEW_RESPONSE_SYSTEM] + $File "$SRCDIR\game\shared\ai_criteria_new.h" [$NEW_RESPONSE_SYSTEM] $File "ai_debug.h" $File "$SRCDIR\game\shared\ai_debug_shared.h" $File "ai_default.cpp" @@ -194,8 +199,10 @@ $Project $File "ai_planesolver.h" $File "ai_playerally.cpp" $File "ai_playerally.h" - $File "AI_ResponseSystem.cpp" + $File "AI_ResponseSystem.cpp" [!$NEW_RESPONSE_SYSTEM] $File "AI_ResponseSystem.h" + $File "$SRCDIR\game\shared\ai_responsesystem_new.cpp" [$NEW_RESPONSE_SYSTEM] + $File "$SRCDIR\game\shared\ai_responsesystem_new.h" [$NEW_RESPONSE_SYSTEM] $File "ai_route.cpp" $File "ai_route.h" $File "ai_routedist.h" @@ -209,8 +216,10 @@ $Project $File "ai_senses.h" $File "ai_sentence.cpp" $File "ai_sentence.h" - $File "ai_speech.cpp" + $File "ai_speech.cpp" [!$NEW_RESPONSE_SYSTEM] $File "ai_speech.h" + $File "ai_speech_new.cpp" [$NEW_RESPONSE_SYSTEM] + $File "ai_speech_new.h" [$NEW_RESPONSE_SYSTEM] $File "ai_speechfilter.cpp" $File "ai_speechfilter.h" $File "ai_squad.cpp" @@ -440,7 +449,6 @@ $Project $File "init_factory.h" $File "intermission.cpp" $File "$SRCDIR\public\interpolatortypes.h" - $File "$SRCDIR\game\shared\interval.h" $File "$SRCDIR\public\iregistry.h" $File "$SRCDIR\game\shared\iscenetokenprocessor.h" $File "iservervehicle.h" @@ -671,6 +679,11 @@ $Project $File "$SRCDIR\game\shared\voice_common.h" $File "$SRCDIR\game\shared\voice_gamemgr.cpp" $File "$SRCDIR\game\shared\voice_gamemgr.h" + $File "vscript_server.cpp" + $File "vscript_server.h" + $File "vscript_server.nut" + $File "$SRCDIR\game\shared\vscript_shared.cpp" + $File "$SRCDIR\game\shared\vscript_shared.h" $File "waterbullet.cpp" $File "waterbullet.h" $File "WaterLODControl.cpp" @@ -706,7 +719,6 @@ $Project "h_export.cpp" \ "init_factory.cpp" \ "$SRCDIR\public\interpolatortypes.cpp" \ - "$SRCDIR\game\shared\interval.cpp" \ "$SRCDIR\public\keyframe\keyframe.cpp" \ "$SRCDIR\common\language.cpp" \ "$SRCDIR\common\steamid.cpp" \ @@ -1022,6 +1034,7 @@ $Project $File "$SRCDIR\public\winlite.h" $File "$SRCDIR\public\worldsize.h" $File "$SRCDIR\public\zip_uncompressed.h" + $File "$SRCDIR\public\tier1\interval.h" $File "$SRCDIR\game\shared\mp_shareddefs.h" $File "$SRCDIR\game\shared\econ\ihasowner.h" //Haptics diff --git a/src/game/server/server_episodic.vpc b/src/game/server/server_episodic.vpc index 81c9297b..7c7ca5da 100644 --- a/src/game/server/server_episodic.vpc +++ b/src/game/server/server_episodic.vpc @@ -264,8 +264,8 @@ $Project "Server (Episodic)" $File "hl2\weapon_smg1.cpp" $File "episodic\weapon_striderbuster.cpp" $File "episodic\weapon_striderbuster.h" - $File "hl2\weapon_stunstick.cpp" - $File "hl2\weapon_stunstick.h" + $File "hl2\weapon_stunstick.cpp" [!$MAPBASE] // See server_mapbase.vpc + $File "hl2\weapon_stunstick.h" [!$MAPBASE] // See server_mapbase.vpc $File "episodic\ep1_gamestats.h" $File "episodic\ep2_gamestats.h" $File "episodic\ep1_gamestats.cpp" \ diff --git a/src/game/server/server_hl2.vpc b/src/game/server/server_hl2.vpc index 0fc2e64b..d5b83f45 100644 --- a/src/game/server/server_hl2.vpc +++ b/src/game/server/server_hl2.vpc @@ -247,8 +247,8 @@ $Project "Server (HL2)" $File "hl2\weapon_rpg.h" $File "hl2\weapon_shotgun.cpp" $File "hl2\weapon_smg1.cpp" - $File "hl2\weapon_stunstick.cpp" - $File "hl2\weapon_stunstick.h" + $File "hl2\weapon_stunstick.cpp" [!$MAPBASE] // See server_mapbase.vpc + $File "hl2\weapon_stunstick.h" [!$MAPBASE] // See server_mapbase.vpc $Folder "Unused" { diff --git a/src/game/server/server_mapbase.vpc b/src/game/server/server_mapbase.vpc new file mode 100644 index 00000000..7d74847e --- /dev/null +++ b/src/game/server/server_mapbase.vpc @@ -0,0 +1,124 @@ +//----------------------------------------------------------------------------- +// SERVER_MAPBASE.VPC +// +// Project Base Script +//----------------------------------------------------------------------------- + +$Configuration +{ + $Compiler + { + $PreprocessorDefinitions "$BASE;ASW_PROJECTED_TEXTURES;DYNAMIC_RTT_SHADOWS;GLOWS_ENABLE" + $PreprocessorDefinitions "$BASE;MAPBASE_VSCRIPT" [$MAPBASE_VSCRIPT] + $PreprocessorDefinitions "$BASE;NEW_RESPONSE_SYSTEM" [$NEW_RESPONSE_SYSTEM] + } +} + +$Project +{ + $Folder "Source Files" + { + $File "logic_random_outputs.cpp" + $File "point_entity_finder.cpp" + $File "env_projectedtexture.h" + $File "env_global_light.cpp" + $File "skyboxswapper.cpp" + $File "env_instructor_hint.cpp" + $File "postprocesscontroller.cpp" + $File "postprocesscontroller.h" + $File "env_dof_controller.cpp" + $File "env_dof_controller.h" + $File "logic_playmovie.cpp" + $File "movie_display.cpp" + $File "fogvolume.cpp" + $File "fogvolume.h" + $File "ai_expresserfollowup.cpp" [$NEW_RESPONSE_SYSTEM] + $File "ai_speechqueue.cpp" [$NEW_RESPONSE_SYSTEM] + $File "ai_speechqueue.h" [$NEW_RESPONSE_SYSTEM] + + $Folder "Mapbase" + { + $File "$SRCDIR\game\shared\mapbase\mapbase_shared.cpp" + $File "$SRCDIR\game\shared\mapbase\mapbase_usermessages.cpp" + $File "$SRCDIR\game\shared\mapbase\mapbase_rpc.cpp" + $File "$SRCDIR\game\shared\mapbase\mapbase_game_log.cpp" + $File "$SRCDIR\game\shared\mapbase\MapEdit.cpp" + $File "$SRCDIR\game\shared\mapbase\MapEdit.h" + $File "$SRCDIR\game\shared\mapbase\matchers.cpp" + $File "$SRCDIR\game\shared\mapbase\matchers.h" + $File "$SRCDIR\game\shared\mapbase\singleplayer_animstate.cpp" + $File "$SRCDIR\game\shared\mapbase\singleplayer_animstate.h" + $File "$SRCDIR\game\shared\mapbase\vscript_funcs_shared.cpp" [$MAPBASE_VSCRIPT] + $File "$SRCDIR\game\shared\mapbase\vscript_funcs_shared.h" [$MAPBASE_VSCRIPT] + $File "$SRCDIR\game\shared\mapbase\vscript_singletons.cpp" [$MAPBASE_VSCRIPT] + $File "$SRCDIR\game\shared\mapbase\vscript_singletons.h" [$MAPBASE_VSCRIPT] + $File "$SRCDIR\game\shared\mapbase\vscript_funcs_hl2.cpp" [$MAPBASE_VSCRIPT] + $File "$SRCDIR\game\shared\mapbase\vscript_consts_shared.cpp" [$MAPBASE_VSCRIPT] + $File "$SRCDIR\game\shared\mapbase\vscript_consts_weapons.cpp" [$MAPBASE_VSCRIPT] + $File "$SRCDIR\game\shared\mapbase\weapon_custom_scripted.cpp" [$MAPBASE_VSCRIPT] + $File "$SRCDIR\game\shared\mapbase\weapon_custom_scripted.h" [$MAPBASE_VSCRIPT] + $File "$SRCDIR\game\shared\mapbase\logic_script_client.cpp" [$MAPBASE_VSCRIPT] + + $File "mapbase\ai_grenade.cpp" + $File "mapbase\ai_grenade.h" + $File "mapbase\ai_monitor.cpp" + $File "mapbase\ai_weaponmodifier.cpp" + $File "mapbase\custom_weapon_factory.cpp" + $File "mapbase\custom_weapon_factory.h" + $File "mapbase\closecaption_entity.cpp" + $File "mapbase\datadesc_mod.cpp" + $File "mapbase\datadesc_mod.h" + $File "mapbase\expandedrs_combine.h" + $File "mapbase\func_clientclip.cpp" + $File "mapbase\func_fake_worldportal.cpp" + $File "mapbase\GlobalStrings.cpp" + $File "mapbase\GlobalStrings.h" + $File "mapbase\logic_externaldata.cpp" + $File "mapbase\logic_skill.cpp" + $File "mapbase\logic_substring.cpp" + $File "mapbase\point_advanced_finder.cpp" + $File "mapbase\point_copy_size.cpp" + $File "mapbase\point_damageinfo.cpp" + $File "mapbase\point_entity_replace.cpp" + //$File "mapbase\point_physics_control.cpp" // Backlogged + $File "mapbase\point_projectile.cpp" + $File "mapbase\point_radiation_source.cpp" + $File "mapbase\point_glow.cpp" + $File "mapbase\SystemConvarMod.cpp" + $File "mapbase\SystemConvarMod.h" + $File "mapbase\variant_tools.h" + $File "mapbase\vgui_text_display.cpp" + $File "mapbase\weapon_custom_hl2.cpp" + + $File "mapbase\logic_eventlistener.cpp" + $File "mapbase\logic_register_activator.cpp" + } + + $Folder "HL2 DLL" + { + // Original stunstick files are conditional'd out in the HL2 VPCs + $File "$SRCDIR\game\shared\hl2mp\weapon_stunstick.cpp" + $File "$SRCDIR\game\shared\hl2mp\weapon_stunstick.h" + } + + $Folder "HL2MP" + { + $Folder "Weapons" + { + $File "hl2mp\grenade_satchel.cpp" + $File "hl2mp\grenade_satchel.h" + $File "hl2mp\grenade_tripmine.cpp" + $File "hl2mp\grenade_tripmine.h" + + $File "$SRCDIR\game\shared\hl2mp\weapon_slam.cpp" + $File "$SRCDIR\game\shared\hl2mp\weapon_slam.h" + } + } + } + + $Folder "Link Libraries" + { + $Lib "vscript" [$MAPBASE_VSCRIPT] + $Lib "responserules" [$NEW_RESPONSE_SYSTEM] + } +} diff --git a/src/game/server/shadowcontrol.cpp b/src/game/server/shadowcontrol.cpp index 36c23c28..8db9665d 100644 --- a/src/game/server/shadowcontrol.cpp +++ b/src/game/server/shadowcontrol.cpp @@ -40,6 +40,9 @@ private: CNetworkColor32( m_shadowColor ); CNetworkVar( float, m_flShadowMaxDist ); CNetworkVar( bool, m_bDisableShadows ); +#ifdef MAPBASE + CNetworkVar( bool, m_bEnableLocalLightShadows ); +#endif }; LINK_ENTITY_TO_CLASS(shadow_control, CShadowControl); @@ -48,12 +51,18 @@ BEGIN_DATADESC( CShadowControl ) DEFINE_KEYFIELD( m_flShadowMaxDist, FIELD_FLOAT, "distance" ), DEFINE_KEYFIELD( m_bDisableShadows, FIELD_BOOLEAN, "disableallshadows" ), +#ifdef MAPBASE + DEFINE_KEYFIELD( m_bEnableLocalLightShadows, FIELD_BOOLEAN, "enableshadowsfromlocallights" ), +#endif // Inputs DEFINE_INPUT( m_shadowColor, FIELD_COLOR32, "color" ), DEFINE_INPUT( m_shadowDirection, FIELD_VECTOR, "direction" ), DEFINE_INPUT( m_flShadowMaxDist, FIELD_FLOAT, "SetDistance" ), DEFINE_INPUT( m_bDisableShadows, FIELD_BOOLEAN, "SetShadowsDisabled" ), +#ifdef MAPBASE + DEFINE_INPUT( m_bEnableLocalLightShadows, FIELD_BOOLEAN, "SetShadowsFromLocalLightsEnabled" ), +#endif DEFINE_INPUTFUNC( FIELD_STRING, "SetAngles", InputSetAngles ), @@ -62,9 +71,17 @@ END_DATADESC() IMPLEMENT_SERVERCLASS_ST_NOBASE(CShadowControl, DT_ShadowControl) SendPropVector(SENDINFO(m_shadowDirection), -1, SPROP_NOSCALE ), +#ifdef MAPBASE + /*SendPropInt(SENDINFO(m_shadowColor), 32, SPROP_UNSIGNED, SendProxy_Color32ToInt32 ),*/ + SendPropInt(SENDINFO(m_shadowColor), 32, SPROP_UNSIGNED, SendProxy_Color32ToInt ), +#else SendPropInt(SENDINFO(m_shadowColor), 32, SPROP_UNSIGNED), +#endif SendPropFloat(SENDINFO(m_flShadowMaxDist), 0, SPROP_NOSCALE ), SendPropBool(SENDINFO(m_bDisableShadows)), +#ifdef MAPBASE + SendPropBool(SENDINFO(m_bEnableLocalLightShadows)), +#endif END_SEND_TABLE() @@ -74,6 +91,9 @@ CShadowControl::CShadowControl() m_flShadowMaxDist = 50.0f; m_shadowColor.Init( 64, 64, 64, 0 ); m_bDisableShadows = false; +#ifdef MAPBASE + m_bEnableLocalLightShadows = false; +#endif } diff --git a/src/game/server/skyboxswapper.cpp b/src/game/server/skyboxswapper.cpp index 1eaa3489..40d1f2ba 100644 --- a/src/game/server/skyboxswapper.cpp +++ b/src/game/server/skyboxswapper.cpp @@ -52,7 +52,7 @@ void CSkyboxSwapper::Precache( void ) { if ( Q_strlen( m_iszSkyboxName.ToCStr() ) == 0 ) { - Warning( "skybox_swapper (%s) has no skybox specified!\n", GetEntityName().ToCStr() ); + Warning( "skybox_swapper (%s) has no skybox specified!\n", STRING(GetEntityName()) ); return; } @@ -73,7 +73,7 @@ void CSkyboxSwapper::InputTrigger( inputdata_t &inputdata ) static ConVarRef skyname( "sv_skyname", false ); if ( !skyname.IsValid() ) { - Warning( "skybox_swapper (%s) trigger input failed - cannot find 'sv_skyname' convar!\n", GetEntityName().ToCStr() ); + Warning( "skybox_swapper (%s) trigger input failed - cannot find 'sv_skyname' convar!\n", STRING(GetEntityName()) ); return; } skyname.SetValue( m_iszSkyboxName.ToCStr() ); diff --git a/src/game/server/sound.cpp b/src/game/server/sound.cpp index 4769fb3b..59b6881c 100644 --- a/src/game/server/sound.cpp +++ b/src/game/server/sound.cpp @@ -175,6 +175,9 @@ public: void ToggleSound(); void SendSound( SoundFlags_t flags ); +#ifdef MAPBASE + void SoundEnd(); +#endif // Input handlers void InputPlaySound( inputdata_t &inputdata ); @@ -184,6 +187,9 @@ public: void InputVolume( inputdata_t &inputdata ); void InputFadeIn( inputdata_t &inputdata ); void InputFadeOut( inputdata_t &inputdata ); +#ifdef MAPBASE + void InputSetSound( inputdata_t &inputdata ); +#endif DECLARE_DATADESC(); @@ -200,6 +206,12 @@ public: EHANDLE m_hSoundSource; // entity from which the sound comes int m_nSoundSourceEntIndex; // In case the entity goes away before we finish stopping the sound... +#ifdef MAPBASE + int m_iSoundFlags; + + COutputEvent m_OnSoundFinished; +#endif + private: void ValidateSoundFile( void ); string_t m_iszSound; @@ -217,6 +229,10 @@ BEGIN_DATADESC( CAmbientGeneric ) // DEFINE_FIELD( m_hSoundSource, EHANDLE ), // DEFINE_FIELD( m_nSoundSourceEntIndex, FIELD_INTERGER ), +#ifdef MAPBASE + DEFINE_KEYFIELD( m_iSoundFlags, FIELD_INTEGER, "soundflags" ), +#endif + DEFINE_FIELD( m_flMaxRadius, FIELD_FLOAT ), DEFINE_FIELD( m_fActive, FIELD_BOOLEAN ), DEFINE_FIELD( m_fLooping, FIELD_BOOLEAN ), @@ -231,6 +247,9 @@ BEGIN_DATADESC( CAmbientGeneric ) // Function Pointers DEFINE_FUNCTION( RampThink ), +#ifdef MAPBASE + DEFINE_THINKFUNC( SoundEnd ), +#endif // Inputs DEFINE_INPUTFUNC(FIELD_VOID, "PlaySound", InputPlaySound ), @@ -240,6 +259,11 @@ BEGIN_DATADESC( CAmbientGeneric ) DEFINE_INPUTFUNC(FIELD_FLOAT, "Volume", InputVolume ), DEFINE_INPUTFUNC(FIELD_FLOAT, "FadeIn", InputFadeIn ), DEFINE_INPUTFUNC(FIELD_FLOAT, "FadeOut", InputFadeOut ), +#ifdef MAPBASE + DEFINE_INPUTFUNC(FIELD_STRING, "SetSound", InputSetSound ), + + DEFINE_OUTPUT( m_OnSoundFinished, "OnSoundFinished" ), +#endif END_DATADESC() @@ -248,6 +272,10 @@ END_DATADESC() #define SF_AMBIENT_SOUND_START_SILENT 16 #define SF_AMBIENT_SOUND_NOT_LOOPING 32 +#ifdef MAPBASE +static const char *g_SoundEndContext = "SoundEnd"; +#endif + //----------------------------------------------------------------------------- // @@ -437,6 +465,24 @@ void CAmbientGeneric::InputFadeOut( inputdata_t &inputdata ) SetNextThink( gpGlobals->curtime + 0.1f ); } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CAmbientGeneric::InputSetSound( inputdata_t &inputdata ) +{ + m_iszSound = inputdata.value.StringID(); + if (STRING(m_iszSound)[0] != '!') + { + PrecacheScriptSound(STRING(m_iszSound)); + } + + // Try to refresh the sound + if (m_fActive) + SendSound(SND_NOFLAGS); +} +#endif + void CAmbientGeneric::ValidateSoundFile( void ) { @@ -922,6 +968,19 @@ void CAmbientGeneric::InputPlaySound( inputdata_t &inputdata ) { //Adrian: Stop our current sound before starting a new one! SendSound( SND_STOP ); + +#ifdef MAPBASE + // Procedural handling like !activator + if (STRING(m_sSourceEntName)[0] == '!') + { + CBaseEntity *pEntity = gEntList.FindEntityProcedural(STRING(m_sSourceEntName), this, inputdata.pActivator, inputdata.pCaller); + if (pEntity) + { + m_hSoundSource = pEntity; + m_nSoundSourceEntIndex = pEntity->entindex(); + } + } +#endif ToggleSound(); } @@ -943,6 +1002,52 @@ void CAmbientGeneric::SendSound( SoundFlags_t flags) { ValidateSoundFile(); +#ifdef MAPBASE + int iFlags = flags != SND_STOP ? ((int)flags | m_iSoundFlags) : flags; + const char *szSoundFile = STRING( m_iszSound ); + CBaseEntity* pSoundSource = m_hSoundSource; + if ( pSoundSource ) + { + if ( iFlags & SND_STOP ) + { + UTIL_EmitAmbientSound(pSoundSource->GetSoundSourceIndex(), pSoundSource->GetAbsOrigin(), szSoundFile, + 0, SNDLVL_NONE, iFlags, 0); + + SetContextThink( NULL, TICK_NEVER_THINK, g_SoundEndContext ); + + m_fActive = false; + } + else + { + float duration = 0.0f; + UTIL_EmitAmbientSound(pSoundSource->GetSoundSourceIndex(), pSoundSource->GetAbsOrigin(), szSoundFile, + (m_dpv.vol * 0.01), m_iSoundLevel, iFlags, m_dpv.pitch, 0.0f, &duration); + + SetContextThink( &CAmbientGeneric::SoundEnd, gpGlobals->curtime + duration, g_SoundEndContext ); + + // Only mark active if this is a looping sound. If not looping, each + // trigger will cause the sound to play. If the sound is still + // playing from a previous trigger press, it will be shut off + // and then restarted. + + if (m_fLooping) + m_fActive = true; + } + } + else + { + if ( ( iFlags & SND_STOP ) && + ( m_nSoundSourceEntIndex != -1 ) ) + { + UTIL_EmitAmbientSound(m_nSoundSourceEntIndex, GetAbsOrigin(), szSoundFile, + 0, SNDLVL_NONE, iFlags, 0); + + SetContextThink( NULL, TICK_NEVER_THINK, g_SoundEndContext ); + + m_fActive = false; + } + } +#else CBaseEntity* pSoundSource = m_hSoundSource; if ( pSoundSource ) { @@ -966,14 +1071,35 @@ void CAmbientGeneric::SendSound( SoundFlags_t flags) 0, SNDLVL_NONE, flags, 0); } } +#endif } +#ifdef MAPBASE +void CAmbientGeneric::SoundEnd() +{ + m_OnSoundFinished.FireOutput(this, this); +} +#endif + //----------------------------------------------------------------------------- // Purpose: Input handler that stops playing the sound. //----------------------------------------------------------------------------- void CAmbientGeneric::InputToggleSound( inputdata_t &inputdata ) { +#ifdef MAPBASE + // Procedural handling like !activator + if (STRING(m_sSourceEntName)[0] == '!') + { + CBaseEntity *pEntity = gEntList.FindEntityProcedural(STRING(m_sSourceEntName), this, inputdata.pActivator, inputdata.pCaller); + if (pEntity) + { + m_hSoundSource = pEntity; + m_nSoundSourceEntIndex = pEntity->entindex(); + } + } +#endif + ToggleSound(); } diff --git a/src/game/server/soundent.cpp b/src/game/server/soundent.cpp index 4a735637..59273206 100644 --- a/src/game/server/soundent.cpp +++ b/src/game/server/soundent.cpp @@ -45,6 +45,31 @@ BEGIN_SIMPLE_DATADESC( CSound ) END_DATADESC() +#ifdef MAPBASE_VSCRIPT +BEGIN_SCRIPTDESC_ROOT( CSound, "A sound NPCs can hear." ) + + DEFINE_SCRIPTFUNC( DoesSoundExpire, "Returns true if the sound expires." ) + DEFINE_SCRIPTFUNC( SoundExpirationTime, "Gets the sound's expiration time." ) + DEFINE_SCRIPTFUNC( SetSoundOrigin, "Sets the sound's origin." ) + DEFINE_SCRIPTFUNC( GetSoundOrigin, "Gets the sound's origin." ) + DEFINE_SCRIPTFUNC( GetSoundReactOrigin, "Gets the sound's react origin." ) + DEFINE_SCRIPTFUNC_NAMED( FIsSound, "IsSound", "Returns true if this is a type of sound (as opposed to a scent)." ) + DEFINE_SCRIPTFUNC_NAMED( FIsScent, "IsScent", "Returns true if this is a type of scent (as opposed to a sound)." ) + DEFINE_SCRIPTFUNC( IsSoundType, "Returns true if the sound type is the specified type." ) + DEFINE_SCRIPTFUNC( SoundType, "Gets the raw sound type." ) + DEFINE_SCRIPTFUNC( SoundContext, "Gets the sound type with contexts only." ) + DEFINE_SCRIPTFUNC( SoundTypeNoContext, "Gets the sound type with contexts excluded." ) + DEFINE_SCRIPTFUNC( Volume, "Gets the sound's volume." ) + DEFINE_SCRIPTFUNC( OccludedVolume, "Gets the sound's occluded volume." ) + DEFINE_SCRIPTFUNC( Reset, "Clears the volume, type, and origin for the sound without actually removing it." ) + DEFINE_SCRIPTFUNC( SoundChannel, "Gets the sound's channel." ) + DEFINE_SCRIPTFUNC( ValidateOwner, "Returns true if the sound's owner is still valid or if the sound never had an owner in the first place." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptGetOwner, "GetOwner", "Gets the sound's owner." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptGetTarget, "GetTarget", "Gets the sound's target." ) + +END_SCRIPTDESC(); +#endif + //========================================================= // CSound - Clear - zeros all fields for a sound @@ -769,7 +794,11 @@ void CAISound::InputInsertSound( inputdata_t &inputdata ) if( m_iszProxyEntityName != NULL_STRING ) { +#ifdef MAPBASE + CBaseEntity *pProxy = gEntList.FindEntityByName( NULL, m_iszProxyEntityName, this, inputdata.pActivator, inputdata.pCaller ); +#else CBaseEntity *pProxy = gEntList.FindEntityByName( NULL, m_iszProxyEntityName ); +#endif if( pProxy ) { @@ -781,7 +810,26 @@ void CAISound::InputInsertSound( inputdata_t &inputdata ) } } +#ifdef MAPBASE + EHANDLE hOwner = this; + if (m_target != NULL_STRING) + { + CBaseEntity *pProxy = gEntList.FindEntityByName( NULL, m_target, this, inputdata.pActivator, inputdata.pCaller ); + + if( pProxy ) + { + hOwner = pProxy; + } + else + { + DevWarning("Warning- ai_sound cannot find owner entity named '%s'. Using self.\n", STRING(m_target) ); + } + } + + g_pSoundEnt->InsertSound( m_iSoundType | m_iSoundContext, vecLocation, iVolume, m_flDuration, hOwner ); +#else g_pSoundEnt->InsertSound( m_iSoundType, vecLocation, iVolume, m_flDuration, this ); +#endif } void CAISound::InputEmitAISound( inputdata_t &inputdata ) @@ -790,7 +838,11 @@ void CAISound::InputEmitAISound( inputdata_t &inputdata ) if( m_iszProxyEntityName != NULL_STRING ) { +#ifdef MAPBASE + CBaseEntity *pProxy = gEntList.FindEntityByName( NULL, m_iszProxyEntityName, this, inputdata.pActivator, inputdata.pCaller ); +#else CBaseEntity *pProxy = gEntList.FindEntityByName( NULL, m_iszProxyEntityName ); +#endif if( pProxy ) { @@ -802,7 +854,26 @@ void CAISound::InputEmitAISound( inputdata_t &inputdata ) } } +#ifdef MAPBASE + EHANDLE hOwner = this; + if (m_target != NULL_STRING) + { + CBaseEntity *pProxy = gEntList.FindEntityByName( NULL, m_target, this, inputdata.pActivator, inputdata.pCaller ); + + if( pProxy ) + { + hOwner = pProxy; + } + else + { + DevWarning("Warning- ai_sound cannot find owner entity named '%s'. Using self.\n", STRING(m_target) ); + } + } + + g_pSoundEnt->InsertSound( m_iSoundType | m_iSoundContext, vecLocation, m_iVolume, m_flDuration, hOwner ); +#else g_pSoundEnt->InsertSound( m_iSoundType | m_iSoundContext, vecLocation, m_iVolume, m_flDuration, this ); +#endif } diff --git a/src/game/server/soundent.h b/src/game/server/soundent.h index 4de733f4..7ce3d401 100644 --- a/src/game/server/soundent.h +++ b/src/game/server/soundent.h @@ -57,6 +57,16 @@ enum SOUND_CONTEXT_ALLIES_ONLY = 0x10000000, // Only player allies can hear this sound SOUND_CONTEXT_PLAYER_VEHICLE = 0x20000000, // HACK: need this because we're not treating the SOUND_xxx values as true bit values! See switch in OnListened. +#ifdef MAPBASE + // You know, I wouldn't mind this approach of leaving types and contexts on the same int + // since it was important in the GoldSrc era with how many CSounds there can be at any given time. + // I'm just frustrated that this system was retained in Source with very specific and/or useless contexts with very little room to expand. + // If this doesn't work, replace SOUND_CONTEXT_PLAYER_VEHICLE with owner server vehicle checks. + + // Only heard by NPCs the owner likes. Needed for shared grenade code. + SOUND_CONTEXT_OWNER_ALLIES = 0x40000000, +#endif + ALL_CONTEXTS = 0xFFF00000, ALL_SCENTS = SOUND_CARCASS | SOUND_MEAT | SOUND_GARBAGE, @@ -126,6 +136,12 @@ public: int SoundChannel( void ) const; bool ValidateOwner() const; +#ifdef MAPBASE_VSCRIPT + // For VScript functions + HSCRIPT ScriptGetOwner() const { return ToHScript( m_hOwner ); } + HSCRIPT ScriptGetTarget() const { return ToHScript( m_hTarget ); } +#endif + EHANDLE m_hOwner; // sound's owner EHANDLE m_hTarget; // Sounds's target - an odd concept. For a gunfire sound, the target is the entity being fired at int m_iVolume; // how loud the sound is diff --git a/src/game/server/soundscape_system.cpp b/src/game/server/soundscape_system.cpp index 15793bfa..b5678b2c 100644 --- a/src/game/server/soundscape_system.cpp +++ b/src/game/server/soundscape_system.cpp @@ -139,6 +139,16 @@ bool CSoundscapeSystem::Init() mapSoundscapeFilename = UTIL_VarArgs( "scripts/soundscapes_%s.txt", mapname ); } +#ifdef MAPBASE + if (filesystem->FileExists(UTIL_VarArgs("maps/%s_soundscapes.txt", mapname))) + { + // A Mapbase-specific file exists. Load that instead. + // Any additional soundscape files, like the original scripts/soundscapes version, + // could be loaded through #include and/or #base. + mapSoundscapeFilename = UTIL_VarArgs("maps/%s_soundscapes.txt", mapname); + } +#endif + KeyValues *manifest = new KeyValues( SOUNDSCAPE_MANIFEST_FILE ); if ( filesystem->LoadKeyValues( *manifest, IFileSystem::TYPE_SOUNDSCAPE, SOUNDSCAPE_MANIFEST_FILE, "GAME" ) ) { diff --git a/src/game/server/subs.cpp b/src/game/server/subs.cpp index b2bf003f..53ba8f06 100644 --- a/src/game/server/subs.cpp +++ b/src/game/server/subs.cpp @@ -22,10 +22,10 @@ void CPointEntity::Spawn( void ) } -class CNullEntity : public CBaseEntity +class CNullEntity : public CServerOnlyEntity { public: - DECLARE_CLASS( CNullEntity, CBaseEntity ); + DECLARE_CLASS( CNullEntity, CServerOnlyEntity ); void Spawn( void ); }; @@ -38,6 +38,11 @@ void CNullEntity::Spawn( void ) } LINK_ENTITY_TO_CLASS(info_null,CNullEntity); +#ifdef MAPBASE +// Eh, good enough. +LINK_ENTITY_TO_CLASS(func_null,CNullEntity); +#endif + class CBaseDMStart : public CPointEntity { public: diff --git a/src/game/server/triggers.cpp b/src/game/server/triggers.cpp index bc50b00d..af732713 100644 --- a/src/game/server/triggers.cpp +++ b/src/game/server/triggers.cpp @@ -39,11 +39,16 @@ #include "hl2_player.h" #endif +#ifdef MAPBASE +#include "ai_hint.h" +#endif + // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" #define DEBUG_TRANSITIONS_VERBOSE 2 ConVar g_debug_transitions( "g_debug_transitions", "0", FCVAR_NONE, "Set to 1 and restart the map to be warned if the map has no trigger_transition volumes. Set to 2 to see a dump of all entities & associated results during a transition." ); +ConVar noclip_changelevel("noclip_changelevel", "0", FCVAR_CHEAT); // Global list of triggers that care about weapon fire // Doesn't need saving, the triggers re-add themselves on restore. @@ -103,6 +108,12 @@ BEGIN_DATADESC( CBaseTrigger ) DEFINE_KEYFIELD( m_bDisabled, FIELD_BOOLEAN, "StartDisabled" ), DEFINE_UTLVECTOR( m_hTouchingEntities, FIELD_EHANDLE ), +#ifdef MAPBASE + DEFINE_KEYFIELD( m_flWait, FIELD_FLOAT, "wait" ), + DEFINE_FIELD( m_hActivator, FIELD_EHANDLE ), + DEFINE_KEYFIELD( m_sMaster, FIELD_STRING, "master" ), +#endif + // Inputs DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ), DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ), @@ -123,6 +134,24 @@ BEGIN_DATADESC( CBaseTrigger ) END_DATADESC() +#ifdef MAPBASE_VSCRIPT + +BEGIN_ENT_SCRIPTDESC( CBaseTrigger, CBaseEntity, "Trigger entity" ) + DEFINE_SCRIPTFUNC( Enable, "" ) + DEFINE_SCRIPTFUNC( Disable, "" ) + DEFINE_SCRIPTFUNC( TouchTest, "" ) + DEFINE_SCRIPTFUNC_NAMED( ScriptIsTouching, "IsTouching", "Checks whether the passed entity is touching the trigger." ) + + DEFINE_SCRIPTFUNC( UsesFilter, "Returns true if this trigger uses a filter." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptPassesTriggerFilters, "PassesTriggerFilters", "Returns whether a target entity satisfies the trigger's spawnflags, filter, etc." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptGetTouchedEntityOfType, "GetTouchedEntityOfType", "Gets the first touching entity which matches the specified class." ) + + DEFINE_SCRIPTFUNC( PointIsWithin, "Checks if the given vector is within the trigger's volume." ) + + DEFINE_SCRIPTFUNC_NAMED( ScriptGetTouchingEntities, "GetTouchingEntities", "Gets all entities touching this trigger (and satisfying its criteria). This function copies them to a table with a maximum number of elements." ) +END_SCRIPTDESC(); + +#endif // MAPBASE_VSCRIPT LINK_ENTITY_TO_CLASS( trigger, CBaseTrigger ); @@ -290,8 +319,11 @@ void CBaseTrigger::TouchTest( void ) { if ( m_hTouchingEntities.Count() !=0 ) { - +#ifdef MAPBASE + m_OnTouching.FireOutput( m_hTouchingEntities[0], this ); +#else m_OnTouching.FireOutput( this, this ); +#endif } else { @@ -386,6 +418,10 @@ bool CBaseTrigger::PassesTriggerFilters(CBaseEntity *pOther) (HasSpawnFlags(SF_TRIGGER_ALLOW_NPCS) && (pOther->GetFlags() & FL_NPC)) || (HasSpawnFlags(SF_TRIGGER_ALLOW_PUSHABLES) && FClassnameIs(pOther, "func_pushable")) || (HasSpawnFlags(SF_TRIGGER_ALLOW_PHYSICS) && pOther->GetMoveType() == MOVETYPE_VPHYSICS) +#ifdef MAPBASE + || + (HasSpawnFlags(SF_TRIGGER_ALLOW_ITEMS) && pOther->GetMoveType() == MOVETYPE_FLYGRAVITY) +#endif #if defined( HL2_EPISODIC ) || defined( TF_DLL ) || ( HasSpawnFlags(SF_TRIG_TOUCH_DEBRIS) && @@ -566,6 +602,19 @@ bool CBaseTrigger::IsTouching( const CBaseEntity *pOther ) const return ( m_hTouchingEntities.Find( hOther ) != m_hTouchingEntities.InvalidIndex() ); } +#ifdef MAPBASE_VSCRIPT +bool CBaseTrigger::ScriptIsTouching( HSCRIPT hOther ) +{ + CBaseEntity *pOther = ToEnt(hOther); + if ( !pOther ) + return false; + + EHANDLE eOther; + eOther = pOther; + return ( m_hTouchingEntities.Find( eOther ) != m_hTouchingEntities.InvalidIndex() ); +} +#endif // MAPBASE_VSCRIPT + //----------------------------------------------------------------------------- // Purpose: Return a pointer to the first entity of the specified type being touched by this trigger //----------------------------------------------------------------------------- @@ -599,6 +648,19 @@ void CBaseTrigger::InputToggle( inputdata_t &inputdata ) PhysicsTouchTriggers(); } +#ifdef MAPBASE_VSCRIPT +//----------------------------------------------------------------------------- +// Purpose: Copies touching entities to a script table +//----------------------------------------------------------------------------- +void CBaseTrigger::ScriptGetTouchingEntities( HSCRIPT hTable ) +{ + for (int i = 0; i < m_hTouchingEntities.Count(); i++) + { + g_pScriptVM->ArrayAppend( hTable, ToHScript( m_hTouchingEntities[i] ) ); + } +} +#endif + //----------------------------------------------------------------------------- // Purpose: Removes anything that touches it. If the trigger has a targetname, @@ -666,6 +728,9 @@ BEGIN_DATADESC( CTriggerHurt ) DEFINE_KEYFIELD( m_bitsDamageInflict, FIELD_INTEGER, "damagetype" ), DEFINE_KEYFIELD( m_damageModel, FIELD_INTEGER, "damagemodel" ), DEFINE_KEYFIELD( m_bNoDmgForce, FIELD_BOOLEAN, "nodmgforce" ), +#ifdef MAPBASE + DEFINE_KEYFIELD( m_flHurtRate, FIELD_FLOAT, "hurtrate" ), +#endif DEFINE_FIELD( m_flLastDmgTime, FIELD_TIME ), DEFINE_FIELD( m_flDmgResetTime, FIELD_TIME ), @@ -728,7 +793,11 @@ void CTriggerHurt::RadiationThink( void ) } float dt = gpGlobals->curtime - m_flLastDmgTime; +#ifdef MAPBASE + if ( dt >= m_flHurtRate ) +#else if ( dt >= 0.5 ) +#endif { HurtAllTouchers( dt ); } @@ -804,7 +873,11 @@ void CTriggerHurt::HurtThink() } else { +#ifdef MAPBASE + SetNextThink( gpGlobals->curtime + m_flHurtRate ); +#else SetNextThink( gpGlobals->curtime + 0.5f ); +#endif } } @@ -917,6 +990,24 @@ bool IsTakingTriggerHurtDamageAtPoint( const Vector &vecPoint ) return false; } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CTriggerHurt::KeyValue( const char *szKeyName, const char *szValue ) +{ + // Additional OR flags + if (FStrEq( szKeyName, "damageor" ) || FStrEq( szKeyName, "damagepresets" )) + { + m_bitsDamageInflict |= atoi(szValue); + } + else + return BaseClass::KeyValue( szKeyName, szValue ); + + return true; +} +#endif + // ################################################################################## // >> TriggerMultiple @@ -1041,7 +1132,11 @@ class CTriggerLook : public CTriggerOnce DECLARE_CLASS( CTriggerLook, CTriggerOnce ); public: +#ifdef MAPBASE + CUtlVector m_hLookTargets; +#else EHANDLE m_hLookTarget; +#endif float m_flFieldOfView; float m_flLookTime; // How long must I look for float m_flLookTimeTotal; // How long have I looked @@ -1049,6 +1144,10 @@ public: float m_flTimeoutDuration; // Number of seconds after start touch to fire anyway bool m_bTimeoutFired; // True if the OnTimeout output fired since the last StartTouch. EHANDLE m_hActivator; // The entity that triggered us. +#ifdef MAPBASE + bool m_bUseLOS; // Makes lookers use LOS calculations in addition to viewcone calculations + bool m_bUseLookEntityAsCaller; // Fires OnTrigger with the seen entity +#endif void Spawn( void ); void Touch( CBaseEntity *pOther ); @@ -1060,7 +1159,11 @@ public: private: +#ifdef MAPBASE + void Trigger(CBaseEntity *pActivator, bool bTimeout, CBaseEntity *pCaller = NULL); +#else void Trigger(CBaseEntity *pActivator, bool bTimeout); +#endif void TimeoutThink(); COutputEvent m_OnTimeout; @@ -1069,12 +1172,20 @@ private: LINK_ENTITY_TO_CLASS( trigger_look, CTriggerLook ); BEGIN_DATADESC( CTriggerLook ) +#ifdef MAPBASE + DEFINE_UTLVECTOR( m_hLookTargets, FIELD_EHANDLE ), +#else DEFINE_FIELD( m_hLookTarget, FIELD_EHANDLE ), +#endif DEFINE_FIELD( m_flLookTimeTotal, FIELD_FLOAT ), DEFINE_FIELD( m_flLookTimeLast, FIELD_TIME ), DEFINE_KEYFIELD( m_flTimeoutDuration, FIELD_FLOAT, "timeout" ), DEFINE_FIELD( m_bTimeoutFired, FIELD_BOOLEAN ), DEFINE_FIELD( m_hActivator, FIELD_EHANDLE ), +#ifdef MAPBASE + DEFINE_KEYFIELD( m_bUseLOS, FIELD_BOOLEAN, "UseLOS" ), + DEFINE_KEYFIELD( m_bUseLookEntityAsCaller, FIELD_BOOLEAN, "LookEntityCaller" ), +#endif DEFINE_OUTPUT( m_OnTimeout, "OnTimeout" ), @@ -1092,7 +1203,9 @@ END_DATADESC() //------------------------------------------------------------------------------ void CTriggerLook::Spawn( void ) { +#ifndef MAPBASE m_hLookTarget = NULL; +#endif m_flLookTimeTotal = -1; m_bTimeoutFired = false; @@ -1157,6 +1270,17 @@ void CTriggerLook::Touch(CBaseEntity *pOther) // -------------------------------- // Make sure we have a look target // -------------------------------- +#ifdef MAPBASE + if (m_hLookTargets.Count() <= 0) + { + CBaseEntity *pEntity = gEntList.FindEntityByName( NULL, m_target, this, m_hActivator, this ); + while (pEntity) + { + m_hLookTargets.AddToTail(pEntity); + pEntity = gEntList.FindEntityByName( pEntity, m_target, this, m_hActivator, this ); + } + } +#else if (m_hLookTarget == NULL) { m_hLookTarget = GetNextTarget(); @@ -1165,6 +1289,7 @@ void CTriggerLook::Touch(CBaseEntity *pOther) return; } } +#endif // This is designed for single player only // so we'll always have the same player @@ -1193,6 +1318,50 @@ void CTriggerLook::Touch(CBaseEntity *pOther) vLookDir = ((CBaseCombatCharacter*)pOther)->EyeDirection3D( ); } +#ifdef MAPBASE + // Check if the player is looking at any of the entities, even if they turn to look at another entity candidate. + // This is how we're doing support for multiple entities without redesigning trigger_look. + EHANDLE hLookingAtEntity = NULL; + for (int i = 0; i < m_hLookTargets.Count(); i++) + { + if (!m_hLookTargets[i]) + continue; + + Vector vTargetDir = m_hLookTargets[i]->GetAbsOrigin() - pOther->EyePosition(); + VectorNormalize(vTargetDir); + + float fDotPr = DotProduct(vLookDir,vTargetDir); + if (fDotPr > m_flFieldOfView && (!m_bUseLOS || pOther->FVisible(m_hLookTargets[i]))) + { + hLookingAtEntity = m_hLookTargets[i]; + break; + } + } + + if (hLookingAtEntity != NULL) + { + // Is it the first time I'm looking? + if (m_flLookTimeTotal == -1) + { + m_flLookTimeLast = gpGlobals->curtime; + m_flLookTimeTotal = 0; + } + else + { + m_flLookTimeTotal += gpGlobals->curtime - m_flLookTimeLast; + m_flLookTimeLast = gpGlobals->curtime; + } + + if (m_flLookTimeTotal >= m_flLookTime) + { + Trigger(pOther, false, hLookingAtEntity); + } + } + else + { + m_flLookTimeTotal = -1; + } +#else Vector vTargetDir = m_hLookTarget->GetAbsOrigin() - pOther->EyePosition(); VectorNormalize(vTargetDir); @@ -1220,6 +1389,7 @@ void CTriggerLook::Touch(CBaseEntity *pOther) { m_flLookTimeTotal = -1; } +#endif } } @@ -1227,7 +1397,11 @@ void CTriggerLook::Touch(CBaseEntity *pOther) //----------------------------------------------------------------------------- // Purpose: Called when the trigger is fired by look logic or timeout. //----------------------------------------------------------------------------- +#ifdef MAPBASE +void CTriggerLook::Trigger(CBaseEntity *pActivator, bool bTimeout, CBaseEntity *pCaller) +#else void CTriggerLook::Trigger(CBaseEntity *pActivator, bool bTimeout) +#endif { if (bTimeout) { @@ -1240,7 +1414,11 @@ void CTriggerLook::Trigger(CBaseEntity *pActivator, bool bTimeout) else { // Fire because the player looked at the target. +#ifdef MAPBASE + m_OnTrigger.FireOutput(pActivator, m_bUseLookEntityAsCaller ? pCaller : this); +#else m_OnTrigger.FireOutput(pActivator, this); +#endif m_flLookTimeTotal = -1; // Cancel the timeout think. @@ -1722,7 +1900,8 @@ void CChangeLevel::TouchChangeLevel( CBaseEntity *pOther ) return; } - if ( !pPlayer->IsInAVehicle() && pPlayer->GetMoveType() == MOVETYPE_NOCLIP ) + + if ( !pPlayer->IsInAVehicle() && pPlayer->GetMoveType() == MOVETYPE_NOCLIP && !noclip_changelevel.GetBool()) { DevMsg("In level transition: %s %s\n", st_szNextMap, st_szNextSpot ); return; @@ -1770,7 +1949,11 @@ int BuildChangeList( levellist_t *pLevelList, int maxList ) // NOTE: This routine is relatively slow. If you need to use it for per-frame work, consider that fact. // UNDONE: Expand this to the full matrix of solid types on each side and move into enginetrace +#ifdef MAPBASE // Other files may use this +bool TestEntityTriggerIntersection_Accurate( CBaseEntity *pTrigger, CBaseEntity *pEntity ) +#else static bool TestEntityTriggerIntersection_Accurate( CBaseEntity *pTrigger, CBaseEntity *pEntity ) +#endif { Assert( pTrigger->GetSolid() == SOLID_BSP ); @@ -2184,6 +2367,11 @@ public: void Touch( CBaseEntity *pOther ); void Untouch( CBaseEntity *pOther ); +#ifdef MAPBASE + void InputSetSpeed( inputdata_t &inputdata ); + void InputSetPushDir( inputdata_t &inputdata ); +#endif + Vector m_vecPushDir; DECLARE_DATADESC(); @@ -2196,6 +2384,10 @@ BEGIN_DATADESC( CTriggerPush ) DEFINE_KEYFIELD( m_vecPushDir, FIELD_VECTOR, "pushdir" ), DEFINE_KEYFIELD( m_flAlternateTicksFix, FIELD_FLOAT, "alternateticksfix" ), //DEFINE_FIELD( m_flPushSpeed, FIELD_FLOAT ), +#ifdef MAPBASE + DEFINE_INPUTFUNC( FIELD_FLOAT, "SetSpeed", InputSetSpeed ), + DEFINE_INPUTFUNC( FIELD_VECTOR, "SetPushDir", InputSetPushDir ), +#endif END_DATADESC() LINK_ENTITY_TO_CLASS( trigger_push, CTriggerPush ); @@ -2342,6 +2534,35 @@ void CTriggerPush::Touch( CBaseEntity *pOther ) } } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTriggerPush::InputSetSpeed( inputdata_t &inputdata ) +{ + m_flSpeed = inputdata.value.Float(); + + // Need to update push speed/alternative ticks + Activate(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTriggerPush::InputSetPushDir( inputdata_t &inputdata ) +{ + inputdata.value.Vector3D( m_vecPushDir ); + + // Convert pushdir from angles to a vector + Vector vecAbsDir; + QAngle angPushDir = QAngle( m_vecPushDir.x, m_vecPushDir.y, m_vecPushDir.z ); + AngleVectors( angPushDir, &vecAbsDir ); + + // Transform the vector into entity space + VectorIRotate( vecAbsDir, EntityToWorldTransform(), m_vecPushDir ); +} +#endif + //----------------------------------------------------------------------------- // Teleport trigger @@ -2766,6 +2987,9 @@ private: string_t m_strNewHintGroup; float m_flRadius; bool m_bHintGroupNavLimiting; +#ifdef MAPBASE + bool m_bChangeHints; +#endif }; LINK_ENTITY_TO_CLASS( ai_changehintgroup, CAI_ChangeHintGroup ); @@ -2776,6 +3000,9 @@ BEGIN_DATADESC( CAI_ChangeHintGroup ) DEFINE_KEYFIELD( m_strNewHintGroup, FIELD_STRING, "NewHintGroup" ), DEFINE_KEYFIELD( m_flRadius, FIELD_FLOAT, "Radius" ), DEFINE_KEYFIELD( m_bHintGroupNavLimiting, FIELD_BOOLEAN, "hintlimiting" ), +#ifdef MAPBASE + DEFINE_KEYFIELD( m_bChangeHints, FIELD_BOOLEAN, "changehints" ), +#endif DEFINE_INPUTFUNC( FIELD_VOID, "Activate", InputActivate ), @@ -2832,6 +3059,35 @@ void CAI_ChangeHintGroup::InputActivate( inputdata_t &inputdata ) { pTarget->SetHintGroup( m_strNewHintGroup, m_bHintGroupNavLimiting ); } + +#ifdef MAPBASE + if (m_bChangeHints) + { + AIHintIter_t iter; + CAI_Hint *pHint = CAI_HintManager::GetFirstHint( &iter ); + while ( pHint != NULL ) + { + if ((GetAbsOrigin() - pHint->GetAbsOrigin()).Length() < m_flRadius) + { + bool bValid = false; + switch (m_iSearchType) + { + case 0: { bValid = pHint->NameMatches(STRING(m_strSearchName)); } break; + case 1: { bValid = pHint->ClassMatches(STRING(m_strSearchName)); } break; + + // These should be pooled, so if they're the same hintgroup, they should point to the same string... + case 2: { bValid = pHint->GetGroup() == m_strSearchName; } break; + } + + if (bValid) + pHint->SetGroup(m_strNewHintGroup); + } + + // Move to the next + pHint = CAI_HintManager::GetNextHint( &iter ); + } + } +#endif } @@ -2844,72 +3100,11 @@ void CAI_ChangeHintGroup::InputActivate( inputdata_t &inputdata ) #define SF_CAMERA_PLAYER_SNAP_TO 16 #define SF_CAMERA_PLAYER_NOT_SOLID 32 #define SF_CAMERA_PLAYER_INTERRUPT 64 - - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -class CTriggerCamera : public CBaseEntity -{ -public: - DECLARE_CLASS( CTriggerCamera, CBaseEntity ); - - void Spawn( void ); - bool KeyValue( const char *szKeyName, const char *szValue ); - void Enable( void ); - void Disable( void ); - void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); - void FollowTarget( void ); - void Move(void); - - // Always transmit to clients so they know where to move the view to - virtual int UpdateTransmitState(); - - DECLARE_DATADESC(); - - // Input handlers - void InputEnable( inputdata_t &inputdata ); - void InputDisable( inputdata_t &inputdata ); - -private: - EHANDLE m_hPlayer; - EHANDLE m_hTarget; - - // used for moving the camera along a path (rail rides) - CBaseEntity *m_pPath; - string_t m_sPath; - float m_flWait; - float m_flReturnTime; - float m_flStopTime; - float m_moveDistance; - float m_targetSpeed; - float m_initialSpeed; - float m_acceleration; - float m_deceleration; - int m_state; - Vector m_vecMoveDir; - - - string_t m_iszTargetAttachment; - int m_iAttachmentIndex; - bool m_bSnapToGoal; - -#if HL2_EPISODIC - bool m_bInterpolatePosition; - - // these are interpolation vars used for interpolating the camera over time - Vector m_vStartPos, m_vEndPos; - float m_flInterpStartTime; - - const static float kflPosInterpTime; // seconds +#ifdef MAPBASE +#define SF_CAMERA_PLAYER_SETFOV 128 +#define SF_CAMERA_PLAYER_NEW_BEHAVIOR 256 // In case anyone or anything relied on the broken features #endif - int m_nPlayerButtons; - int m_nOldTakeDamage; - -private: - COutputEvent m_OnEndFollow; -}; #if HL2_EPISODIC const float CTriggerCamera::kflPosInterpTime = 2.0f; @@ -2945,16 +3140,69 @@ BEGIN_DATADESC( CTriggerCamera ) DEFINE_FIELD( m_nPlayerButtons, FIELD_INTEGER ), DEFINE_FIELD( m_nOldTakeDamage, FIELD_INTEGER ), +#ifdef MAPBASE + DEFINE_KEYFIELD( m_fov, FIELD_FLOAT, "fov" ), + DEFINE_KEYFIELD( m_fovSpeed, FIELD_FLOAT, "fov_rate" ), + DEFINE_KEYFIELD( m_flTrackSpeed, FIELD_FLOAT, "trackspeed" ), + + DEFINE_KEYFIELD( m_bDontSetPlayerView, FIELD_BOOLEAN, "DontSetPlayerView" ), +#endif + // Inputs DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ), DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ), +#ifdef MAPBASE + DEFINE_INPUTFUNC( FIELD_FLOAT, "SetFOV", InputSetFOV ), + DEFINE_INPUTFUNC( FIELD_FLOAT, "SetFOVRate", InputSetFOVRate ), + + //DEFINE_INPUTFUNC( FIELD_STRING, "SetTarget", InputSetTarget ), // Defined by base class + DEFINE_INPUTFUNC( FIELD_STRING, "SetTargetAttachment", InputSetTargetAttachment ), + DEFINE_INPUTFUNC( FIELD_FLOAT, "SetTrackSpeed", InputSetTrackSpeed ), +#endif // Function Pointers +#ifdef MAPBASE + DEFINE_FUNCTION( MoveThink ), +#endif DEFINE_FUNCTION( FollowTarget ), DEFINE_OUTPUT( m_OnEndFollow, "OnEndFollow" ), +#ifdef MAPBASE + DEFINE_OUTPUT( m_OnStartFollow, "OnStartFollow" ), +#endif END_DATADESC() +// VScript: publish class and select members to script language +BEGIN_ENT_SCRIPTDESC( CTriggerCamera, CBaseEntity, "Server-side camera entity" ) + DEFINE_SCRIPTFUNC_NAMED( ScriptGetFov, "GetFov", "get camera's current fov setting as integer" ) + DEFINE_SCRIPTFUNC_NAMED( ScriptSetFov, "SetFov", "set camera's current fov in integer degrees and fov change rate as float" ) +END_SCRIPTDESC(); + +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CTriggerCamera::CTriggerCamera() +{ + m_fov = 90; + m_fovSpeed = 1; + m_flTrackSpeed = 40.0f; +} + +//------------------------------------------------------------------------------ +// Cleanup +//------------------------------------------------------------------------------ +void CTriggerCamera::UpdateOnRemove() +{ + if (m_state == USE_ON && HasSpawnFlags(SF_CAMERA_PLAYER_NEW_BEHAVIOR)) + { + Disable(); + } + + BaseClass::UpdateOnRemove(); +} +#endif + //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- @@ -3039,6 +3287,84 @@ void CTriggerCamera::InputDisable( inputdata_t &inputdata ) Disable(); } +#ifdef MAPBASE +//------------------------------------------------------------------------------ +// Purpose: Input handler to set FOV. +//------------------------------------------------------------------------------ +void CTriggerCamera::InputSetFOV( inputdata_t &inputdata ) +{ + m_fov = inputdata.value.Float(); + + if ( m_state == USE_ON && m_hPlayer ) + { + ((CBasePlayer*)m_hPlayer.Get())->SetFOV( this, m_fov, m_fovSpeed ); + } +} + +//------------------------------------------------------------------------------ +// Purpose: Input handler to set FOV rate. +//------------------------------------------------------------------------------ +void CTriggerCamera::InputSetFOVRate( inputdata_t &inputdata ) +{ + m_fovSpeed = inputdata.value.Float(); +} + +//------------------------------------------------------------------------------ +// Purpose: +//------------------------------------------------------------------------------ +void CTriggerCamera::InputSetTarget( inputdata_t &inputdata ) +{ + BaseClass::InputSetTarget( inputdata ); + + if ( FStrEq(STRING(m_target), "!player") ) + { + AddSpawnFlags( SF_CAMERA_PLAYER_TARGET ); + m_hTarget = m_hPlayer; + } + else + { + RemoveSpawnFlags( SF_CAMERA_PLAYER_TARGET ); + m_hTarget = GetNextTarget(); + } +} + +//------------------------------------------------------------------------------ +// Purpose: +//------------------------------------------------------------------------------ +void CTriggerCamera::InputSetTargetAttachment( inputdata_t &inputdata ) +{ + m_iszTargetAttachment = inputdata.value.StringID(); + m_iAttachmentIndex = 0; + + if (m_hTarget) + { + if ( m_iszTargetAttachment != NULL_STRING ) + { + if ( !m_hTarget->GetBaseAnimating() ) + { + Warning("%s tried to target an attachment (%s) on target %s, which has no model.\n", GetClassname(), STRING(m_iszTargetAttachment), STRING(m_hTarget->GetEntityName()) ); + } + else + { + m_iAttachmentIndex = m_hTarget->GetBaseAnimating()->LookupAttachment( STRING(m_iszTargetAttachment) ); + if ( m_iAttachmentIndex <= 0 ) + { + Warning("%s could not find attachment %s on target %s.\n", GetClassname(), STRING(m_iszTargetAttachment), STRING(m_hTarget->GetEntityName()) ); + } + } + } + } +} + +//------------------------------------------------------------------------------ +// Purpose: +//------------------------------------------------------------------------------ +void CTriggerCamera::InputSetTrackSpeed( inputdata_t &inputdata ) +{ + m_flTrackSpeed = inputdata.value.Float(); +} +#endif + //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- @@ -3115,6 +3441,20 @@ void CTriggerCamera::Enable( void ) m_bSnapToGoal = true; } +#ifdef MAPBASE + if ( HasSpawnFlags( SF_CAMERA_PLAYER_SETFOV ) ) + { + if ( pPlayer ) + { + if ( pPlayer->GetFOVOwner() && (/*FClassnameIs( pPlayer->GetFOVOwner(), "point_viewcontrol_multiplayer" ) ||*/ FClassnameIs( pPlayer->GetFOVOwner(), "point_viewcontrol" )) ) + { + pPlayer->ClearZoomOwner(); + } + pPlayer->SetFOV( this, m_fov, m_fovSpeed ); + } + } +#endif + if ( HasSpawnFlags(SF_CAMERA_PLAYER_TARGET ) ) { m_hTarget = m_hPlayer; @@ -3197,12 +3537,17 @@ void CTriggerCamera::Enable( void ) } - pPlayer->SetViewEntity( this ); - - // Hide the player's viewmodel - if ( pPlayer->GetActiveWeapon() ) +#ifdef MAPBASE + if (!m_bDontSetPlayerView) +#endif { - pPlayer->GetActiveWeapon()->AddEffects( EF_NODRAW ); + pPlayer->SetViewEntity( this ); + + // Hide the player's viewmodel + if ( pPlayer->GetActiveWeapon() ) + { + pPlayer->GetActiveWeapon()->AddEffects( EF_NODRAW ); + } } // Only track if we have a target @@ -3212,6 +3557,18 @@ void CTriggerCamera::Enable( void ) SetThink( &CTriggerCamera::FollowTarget ); SetNextThink( gpGlobals->curtime ); } +#ifdef MAPBASE + else if (m_pPath && HasSpawnFlags(SF_CAMERA_PLAYER_NEW_BEHAVIOR)) + { + // Move if we have a path + SetThink( &CTriggerCamera::MoveThink ); + SetNextThink( gpGlobals->curtime ); + } +#endif + +#ifdef MAPBASE + m_OnStartFollow.FireOutput( pPlayer, this ); +#endif m_moveDistance = 0; Move(); @@ -3224,6 +3581,39 @@ void CTriggerCamera::Enable( void ) //----------------------------------------------------------------------------- void CTriggerCamera::Disable( void ) { +#ifdef MAPBASE + if ( m_hPlayer ) + { + CBasePlayer *pBasePlayer = (CBasePlayer*)m_hPlayer.Get(); + + if ( pBasePlayer->IsAlive() ) + { + if ( HasSpawnFlags( SF_CAMERA_PLAYER_NOT_SOLID ) ) + { + pBasePlayer->RemoveSolidFlags( FSOLID_NOT_SOLID ); + } + + if ( HasSpawnFlags( SF_CAMERA_PLAYER_TAKECONTROL ) ) + { + pBasePlayer->EnableControl( TRUE ); + } + + if (!m_bDontSetPlayerView) + { + pBasePlayer->SetViewEntity( NULL ); + pBasePlayer->m_Local.m_bDrawViewmodel = true; + } + } + + if ( HasSpawnFlags( SF_CAMERA_PLAYER_SETFOV ) ) + { + pBasePlayer->SetFOV( this, 0, m_fovSpeed ); + } + + //return the player to previous takedamage state + m_hPlayer->m_takedamage = m_nOldTakeDamage; + } +#else if ( m_hPlayer && m_hPlayer->IsAlive() ) { if ( HasSpawnFlags( SF_CAMERA_PLAYER_NOT_SOLID ) ) @@ -3242,6 +3632,7 @@ void CTriggerCamera::Disable( void ) //return the player to previous takedamage state m_hPlayer->m_takedamage = m_nOldTakeDamage; } +#endif m_state = USE_OFF; m_flReturnTime = gpGlobals->curtime; @@ -3346,7 +3737,11 @@ void CTriggerCamera::FollowTarget( ) dy = dy - 360; QAngle vecAngVel; +#ifdef MAPBASE + vecAngVel.Init( dx * m_flTrackSpeed * gpGlobals->frametime, dy * m_flTrackSpeed * gpGlobals->frametime, GetLocalAngularVelocity().z ); +#else vecAngVel.Init( dx * 40 * gpGlobals->frametime, dy * 40 * gpGlobals->frametime, GetLocalAngularVelocity().z ); +#endif SetLocalAngularVelocity(vecAngVel); } @@ -3364,6 +3759,75 @@ void CTriggerCamera::FollowTarget( ) Move(); } +void CTriggerCamera::StartCameraShot( const char *pszShotType, CBaseEntity *pSceneEntity, CBaseEntity *pActor1, CBaseEntity *pActor2, float duration ) +{ + // called from SceneEntity in response to a CChoreoEvent::CAMERA sent from a VCD. + // talk to vscript, start a camera move + + HSCRIPT hStartCameraShot = NULL; + + // switch to this camera + // Enable(); + + // get script module associated with this ent, lookup function in module + if( m_iszVScripts != NULL_STRING ) + { + hStartCameraShot = m_ScriptScope.LookupFunction( "ScriptStartCameraShot" ); + } + + // call the script function to begin the camera move + if ( hStartCameraShot ) + { + g_pScriptVM->Call( hStartCameraShot, m_ScriptScope, true, NULL, pszShotType, ToHScript(pSceneEntity), ToHScript(pActor1), ToHScript(pActor2), duration ); + g_pScriptVM->ReleaseFunction( hStartCameraShot ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: vscript callback to get the player's fov +//----------------------------------------------------------------------------- +int CTriggerCamera::ScriptGetFov(void) +{ + if (m_hPlayer) + { + CBasePlayer* pBasePlayer = (CBasePlayer*)m_hPlayer.Get(); + int iFOV = pBasePlayer->GetFOV(); + return iFOV; + } + return 0; +} + +//----------------------------------------------------------------------------- +// Purpose: vscript callback to slam the player's fov +//----------------------------------------------------------------------------- +void CTriggerCamera::ScriptSetFov(int iFOV, float fovSpeed) +{ +#ifdef MAPBASE + m_fov = iFOV; + m_fovSpeed = fovSpeed; + + if ( m_state == USE_ON && m_hPlayer ) + { +#else + if ( m_hPlayer ) + { + m_fov = iFOV; + m_fovSpeed = fovSpeed; +#endif + + CBasePlayer* pBasePlayer = (CBasePlayer*)m_hPlayer.Get(); + pBasePlayer->SetFOV(this, iFOV, fovSpeed); + } +} + +#ifdef MAPBASE +void CTriggerCamera::MoveThink() +{ + Move(); + SetNextThink( gpGlobals->curtime ); +} +#endif + void CTriggerCamera::Move() { if ( HasSpawnFlags( SF_CAMERA_PLAYER_INTERRUPT ) ) @@ -4227,6 +4691,10 @@ int CTriggerImpact::DrawDebugTextOverlays(void) const int SF_TRIGGER_MOVE_AUTODISABLE = 0x80; // disable auto movement const int SF_TRIGGER_AUTO_DUCK = 0x800; // Duck automatically +#ifdef MAPBASE +const int SF_TRIGGER_AUTO_WALK = 0x1000; +const int SF_TRIGGER_DISABLE_JUMP = 0x2000; +#endif class CTriggerPlayerMovement : public CBaseTrigger { @@ -4284,6 +4752,18 @@ void CTriggerPlayerMovement::StartTouch( CBaseEntity *pOther ) pPlayer->ForceButtons( IN_DUCK ); } +#ifdef MAPBASE + if ( HasSpawnFlags( SF_TRIGGER_AUTO_WALK ) ) + { + pPlayer->ForceButtons( IN_WALK ); + } + + if ( HasSpawnFlags( SF_TRIGGER_DISABLE_JUMP ) ) + { + pPlayer->DisableButtons( IN_JUMP ); + } +#endif + // UNDONE: Currently this is the only operation this trigger can do if ( HasSpawnFlags(SF_TRIGGER_MOVE_AUTODISABLE) ) { @@ -4306,6 +4786,18 @@ void CTriggerPlayerMovement::EndTouch( CBaseEntity *pOther ) pPlayer->UnforceButtons( IN_DUCK ); } +#ifdef MAPBASE + if ( HasSpawnFlags( SF_TRIGGER_AUTO_WALK ) ) + { + pPlayer->UnforceButtons( IN_WALK ); + } + + if ( HasSpawnFlags( SF_TRIGGER_DISABLE_JUMP ) ) + { + pPlayer->EnableButtons( IN_JUMP ); + } +#endif + if ( HasSpawnFlags(SF_TRIGGER_MOVE_AUTODISABLE) ) { pPlayer->m_Local.m_bAllowAutoMovement = true; @@ -4462,9 +4954,89 @@ void CBaseVPhysicsTrigger::EndTouch( CBaseEntity *pOther ) //----------------------------------------------------------------------------- bool CBaseVPhysicsTrigger::PassesTriggerFilters( CBaseEntity *pOther ) { +#ifdef MAPBASE + if ( !pOther->VPhysicsGetObject() ) +#else if ( pOther->GetMoveType() != MOVETYPE_VPHYSICS && !pOther->IsPlayer() ) +#endif return false; +#ifdef MAPBASE + // Copied and pasted code from CBaseTrigger::PassesTriggerFilters(). + if ( HasSpawnFlags(SF_TRIGGER_ALLOW_ALL) || + (HasSpawnFlags(SF_TRIGGER_ALLOW_CLIENTS) && (pOther->GetFlags() & FL_CLIENT)) || + (HasSpawnFlags(SF_TRIGGER_ALLOW_NPCS) && (pOther->GetFlags() & FL_NPC)) || + (HasSpawnFlags(SF_TRIGGER_ALLOW_PUSHABLES) && FClassnameIs(pOther, "func_pushable")) || + (HasSpawnFlags(SF_TRIGGER_ALLOW_PHYSICS) && pOther->GetMoveType() == MOVETYPE_VPHYSICS) || + (HasSpawnFlags(SF_TRIGGER_ALLOW_ITEMS) && pOther->GetMoveType() == MOVETYPE_FLYGRAVITY) +#if defined( HL2_EPISODIC ) || defined( TF_DLL ) + || + ( HasSpawnFlags(SF_TRIG_TOUCH_DEBRIS) && + (pOther->GetCollisionGroup() == COLLISION_GROUP_DEBRIS || + pOther->GetCollisionGroup() == COLLISION_GROUP_DEBRIS_TRIGGER || + pOther->GetCollisionGroup() == COLLISION_GROUP_INTERACTIVE_DEBRIS) + ) +#endif + ) + { + if ( pOther->GetFlags() & FL_NPC ) + { + CAI_BaseNPC *pNPC = pOther->MyNPCPointer(); + + if ( HasSpawnFlags( SF_TRIGGER_ONLY_PLAYER_ALLY_NPCS ) ) + { + if ( !pNPC || !pNPC->IsPlayerAlly() ) + { + return false; + } + } + + if ( HasSpawnFlags( SF_TRIGGER_ONLY_NPCS_IN_VEHICLES ) ) + { + if ( !pNPC || !pNPC->IsInAVehicle() ) + return false; + } + } + + bool bOtherIsPlayer = pOther->IsPlayer(); + + if ( bOtherIsPlayer ) + { + CBasePlayer *pPlayer = (CBasePlayer*)pOther; + if ( !pPlayer->IsAlive() ) + return false; + + if ( HasSpawnFlags(SF_TRIGGER_ONLY_CLIENTS_IN_VEHICLES) ) + { + if ( !pPlayer->IsInAVehicle() ) + return false; + + // Make sure we're also not exiting the vehicle at the moment + IServerVehicle *pVehicleServer = pPlayer->GetVehicle(); + if ( pVehicleServer == NULL ) + return false; + + if ( pVehicleServer->IsPassengerExiting() ) + return false; + } + + if ( HasSpawnFlags(SF_TRIGGER_ONLY_CLIENTS_OUT_OF_VEHICLES) ) + { + if ( pPlayer->IsInAVehicle() ) + return false; + } + + if ( HasSpawnFlags( SF_TRIGGER_DISALLOW_BOTS ) ) + { + if ( pPlayer->IsFakeClient() ) + return false; + } + } + + CBaseFilter *pFilter = m_hFilter.Get(); + return (!pFilter) ? true : pFilter->PassesFilter( this, pOther ); + } +#else // First test spawn flag filters if ( HasSpawnFlags(SF_TRIGGER_ALLOW_ALL) || (HasSpawnFlags(SF_TRIGGER_ALLOW_CLIENTS) && (pOther->GetFlags() & FL_CLIENT)) || @@ -4498,6 +5070,7 @@ bool CBaseVPhysicsTrigger::PassesTriggerFilters( CBaseEntity *pOther ) CBaseFilter *pFilter = m_hFilter.Get(); return (!pFilter) ? true : pFilter->PassesFilter( this, pOther ); } +#endif return false; } @@ -4869,6 +5442,13 @@ void CServerRagdollTrigger::Spawn( void ) { BaseClass::Spawn(); +#ifdef MAPBASE + // This didn't use PassesTriggerFilters() before, so a trigger_serverragdoll could work regardless of flags. + // Because of this, using trigger filters now will break existing trigger_serverragdolls that functioned without the right flags ticked. + // NPCs are going to be using this in almost all circumstances, so SF_TRIGGER_ALLOW_NPCS is pretty much the main flag of concern. + AddSpawnFlags(SF_TRIGGER_ALLOW_NPCS); +#endif + InitTrigger(); } @@ -4879,10 +5459,25 @@ void CServerRagdollTrigger::StartTouch(CBaseEntity *pOther) if ( pOther->IsPlayer() ) return; +#ifdef MAPBASE + // This means base class didn't accept it (trigger filters) + if (m_hTouchingEntities.Find(pOther) == m_hTouchingEntities.InvalidIndex()) + return; +#endif + CBaseCombatCharacter *pCombatChar = pOther->MyCombatCharacterPointer(); if ( pCombatChar ) { +#ifdef MAPBASE + // The mapper or some other force might've changed it themselves. + // Pretend it never touched us... + if (pCombatChar->m_bForceServerRagdoll == true) + { + BaseClass::EndTouch(pOther); + return; + } +#endif pCombatChar->m_bForceServerRagdoll = true; } } @@ -4974,6 +5569,76 @@ void CTriggerApplyImpulse::InputApplyImpulse( inputdata_t& ) } } +#ifdef MAPBASE +class CTriggerFall : public CBaseTrigger +{ + DECLARE_CLASS( CTriggerFall, CBaseTrigger ); + +public: + + virtual void StartTouch( CBaseEntity *pOther ); + virtual void EndTouch( CBaseEntity *pOther ); + virtual void Spawn( void ); + + bool m_bStayLethal; + + DECLARE_DATADESC(); +}; + +LINK_ENTITY_TO_CLASS( trigger_fall, CTriggerFall ); + +BEGIN_DATADESC( CTriggerFall ) + + DEFINE_KEYFIELD( m_bStayLethal, FIELD_BOOLEAN, "StayLethal" ), + +END_DATADESC() + +void CTriggerFall::Spawn( void ) +{ + BaseClass::Spawn(); + + InitTrigger(); +} + +void CTriggerFall::StartTouch(CBaseEntity *pOther) +{ + BaseClass::StartTouch( pOther ); + + if ( !pOther->IsPlayer() ) + return; + + static_cast(pOther)->m_bInTriggerFall = true; +} + +void CTriggerFall::EndTouch(CBaseEntity *pOther) +{ + BaseClass::EndTouch( pOther ); + + if ( !pOther->IsPlayer() || m_bStayLethal ) + return; + + static_cast(pOther)->m_bInTriggerFall = false; +} + + + +class CTriggerWorld : public CTriggerMultiple +{ + DECLARE_CLASS( CTriggerWorld, CTriggerMultiple ); + +public: + + virtual bool PassesTriggerFilters(CBaseEntity *pOther); +}; + +LINK_ENTITY_TO_CLASS( trigger_world, CTriggerWorld ); + +bool CTriggerWorld::PassesTriggerFilters( CBaseEntity *pOther ) +{ + return pOther->IsWorld(); +} +#endif + #ifdef HL1_DLL //---------------------------------------------------------------------------------- // func_friction diff --git a/src/game/server/triggers.h b/src/game/server/triggers.h index a591be7e..7ec52fed 100644 --- a/src/game/server/triggers.h +++ b/src/game/server/triggers.h @@ -11,7 +11,11 @@ #pragma once #endif +#ifdef MAPBASE +#include "baseentity.h" +#else #include "basetoggle.h" +#endif #include "entityoutput.h" // @@ -33,15 +37,24 @@ enum SF_TRIG_TOUCH_DEBRIS = 0x400, // Will touch physics debris objects SF_TRIGGER_ONLY_NPCS_IN_VEHICLES = 0X800, // *if* NPCs can fire this trigger, only NPCs in vehicles do so (respects player ally flag too) SF_TRIGGER_DISALLOW_BOTS = 0x1000, // Bots are not allowed to fire this trigger +#ifdef MAPBASE + SF_TRIGGER_ALLOW_ITEMS = 0x2000, // MOVETYPE_FLYGRAVITY (Weapons, items, flares, etc.) can fire this trigger +#endif }; // DVS TODO: get rid of CBaseToggle //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- +#ifdef MAPBASE +#define CBaseToggle CBaseEntity +#endif class CBaseTrigger : public CBaseToggle { DECLARE_CLASS( CBaseTrigger, CBaseToggle ); +#ifdef MAPBASE +#undef CBaseToggle +#endif public: CBaseTrigger(); @@ -72,6 +85,9 @@ public: virtual void StartTouchAll() {} virtual void EndTouchAll() {} virtual bool IsTouching( const CBaseEntity *pOther ) const; +#ifdef MAPBASE_VSCRIPT + bool ScriptIsTouching( HSCRIPT hOther ); +#endif CBaseEntity *GetTouchedEntityOfType( const char *sClassName ); @@ -82,6 +98,13 @@ public: bool PointIsWithin( const Vector &vecPoint ); +#ifdef MAPBASE_VSCRIPT + bool ScriptPassesTriggerFilters( HSCRIPT hOther ) { return ToEnt(hOther) ? PassesTriggerFilters( ToEnt(hOther) ) : false; } + HSCRIPT ScriptGetTouchedEntityOfType( const char *sClassName ) { return ToHScript( GetTouchedEntityOfType(sClassName) ); } + + void ScriptGetTouchingEntities( HSCRIPT hTable ); +#endif + bool m_bDisabled; string_t m_iFilterName; CHandle m_hFilter; @@ -99,7 +122,23 @@ protected: // Entities currently being touched by this trigger CUtlVector< EHANDLE > m_hTouchingEntities; +#ifdef MAPBASE + // We don't descend from CBaseToggle anymore. These have to be defined here now. + EHANDLE m_hActivator; + float m_flWait; + string_t m_sMaster; // If this button has a master switch, this is the targetname. + // A master switch must be of the multisource type. If all + // of the switches in the multisource have been triggered, then + // the button will be allowed to operate. Otherwise, it will be + // deactivated. + + virtual float GetDelay( void ) { return m_flWait; } +#endif + DECLARE_DATADESC(); +#ifdef MAPBASE_VSCRIPT + DECLARE_ENT_SCRIPTDESC(); +#endif }; //----------------------------------------------------------------------------- @@ -188,6 +227,10 @@ public: { // This field came along after levels were built so the field defaults to 20 here in the constructor. m_flDamageCap = 20.0f; +#ifdef MAPBASE + // Uh, same here. + m_flHurtRate = 0.5f; +#endif } DECLARE_CLASS( CTriggerHurt, CTriggerHurtShim ); @@ -200,6 +243,10 @@ public: bool HurtEntity( CBaseEntity *pOther, float damage ); int HurtAllTouchers( float dt ); +#ifdef MAPBASE + bool KeyValue( const char *szKeyName, const char *szValue ); +#endif + DECLARE_DATADESC(); float m_flOriginalDamage; // Damage as specified by the level designer. @@ -210,6 +257,9 @@ public: int m_bitsDamageInflict; // DMG_ damage type that the door or tigger does int m_damageModel; bool m_bNoDmgForce; // Should damage from this trigger impart force on what it's hurting +#ifdef MAPBASE + float m_flHurtRate; +#endif enum { @@ -224,6 +274,105 @@ public: CUtlVector m_hurtEntities; }; +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class CTriggerCamera : public CBaseEntity +{ +public: + DECLARE_CLASS( CTriggerCamera, CBaseEntity ); + // script description + DECLARE_ENT_SCRIPTDESC(); + +#ifdef MAPBASE + CTriggerCamera(); + + void UpdateOnRemove(); +#endif + + void Spawn( void ); + bool KeyValue( const char *szKeyName, const char *szValue ); + void Enable( void ); + void Disable( void ); + + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void FollowTarget( void ); + void StartCameraShot( const char *pszShotType, CBaseEntity *pSceneEntity, CBaseEntity *pActor1, CBaseEntity *pActor2, float duration ); + int ScriptGetFov(void); + void ScriptSetFov(int iFOV, float rate); +#ifdef MAPBASE + void MoveThink( void ); +#endif + void Move(void); + + // Always transmit to clients so they know where to move the view to + virtual int UpdateTransmitState(); + + DECLARE_DATADESC(); + + // Input handlers + void InputEnable( inputdata_t &inputdata ); + void InputDisable( inputdata_t &inputdata ); + +#ifdef MAPBASE + void InputSetFOV( inputdata_t &inputdata ); + void InputSetFOVRate( inputdata_t &inputdata ); + + void InputSetTarget( inputdata_t &inputdata ); + void InputSetTargetAttachment( inputdata_t &inputdata ); + void InputSetTrackSpeed( inputdata_t &inputdata ); +#endif + +private: + EHANDLE m_hPlayer; + EHANDLE m_hTarget; + + // used for moving the camera along a path (rail rides) + CBaseEntity *m_pPath; + string_t m_sPath; + float m_flWait; + float m_flReturnTime; + float m_flStopTime; + float m_moveDistance; + float m_targetSpeed; + float m_initialSpeed; + float m_acceleration; + float m_deceleration; + int m_state; + Vector m_vecMoveDir; + +#ifdef MAPBASE + float m_fov; + float m_fovSpeed; + float m_flTrackSpeed; + + bool m_bDontSetPlayerView; +#endif + + string_t m_iszTargetAttachment; + int m_iAttachmentIndex; + bool m_bSnapToGoal; + +#if HL2_EPISODIC + bool m_bInterpolatePosition; + + // these are interpolation vars used for interpolating the camera over time + Vector m_vStartPos, m_vEndPos; + float m_flInterpStartTime; + + const static float kflPosInterpTime; // seconds +#endif + + int m_nPlayerButtons; + int m_nOldTakeDamage; + +private: + COutputEvent m_OnEndFollow; +#ifdef MAPBASE + COutputEvent m_OnStartFollow; +#endif +}; + bool IsTakingTriggerHurtDamageAtPoint( const Vector &vecPoint ); #endif // TRIGGERS_H diff --git a/src/game/server/util.cpp b/src/game/server/util.cpp index 71e0f734..2e9985f6 100644 --- a/src/game/server/util.cpp +++ b/src/game/server/util.cpp @@ -37,6 +37,9 @@ #include "util.h" #include "cdll_int.h" #include "vscript_server.h" +#ifdef MAPBASE +#include "fmtstr.h" +#endif #ifdef PORTAL #include "PortalSimulation.h" @@ -55,12 +58,7 @@ void DBG_AssertFunction( bool fExpr, const char *szExpr, const char *szFile, int { if (fExpr) return; - char szOut[512]; - if (szMessage != NULL) - Q_snprintf(szOut,sizeof(szOut), "ASSERT FAILED:\n %s \n(%s@%d)\n%s", szExpr, szFile, szLine, szMessage); - else - Q_snprintf(szOut,sizeof(szOut), "ASSERT FAILED:\n %s \n(%s@%d)\n", szExpr, szFile, szLine); - Warning( "%s", szOut); + Warning("ASSERT FAILED:\n %s \n(%s@%d)\n%s", szExpr, szFile, szLine, szMessage ? szMessage : ""); } #endif // DEBUG @@ -79,6 +77,10 @@ public: virtual const char *GetCannonicalName( const char *pClassName ); void ReportEntitySizes(); +#ifdef MAPBASE + virtual void UninstallFactory(const char* pClassName); +#endif // MAPBASE + private: IEntityFactory *FindFactory( const char *pClassName ); public: @@ -205,6 +207,51 @@ void CEntityFactoryDictionary::ReportEntitySizes() } } +#ifdef MAPBASE +void CEntityFactoryDictionary::UninstallFactory(const char* pClassName) +{ + m_Factories.Remove(pClassName); +} + +int EntityFactory_AutoComplete( const char *cmdname, CUtlVector< CUtlString > &commands, CUtlRBTree< CUtlString > &symbols, char *substring, int checklen = 0 ) +{ + CEntityFactoryDictionary *pFactoryDict = (CEntityFactoryDictionary*)EntityFactoryDictionary(); + for ( int i = pFactoryDict->m_Factories.First(); i != pFactoryDict->m_Factories.InvalidIndex(); i = pFactoryDict->m_Factories.Next( i ) ) + { + const char *name = pFactoryDict->m_Factories.GetElementName( i ); + if (Q_strnicmp(name, substring, checklen)) + continue; + + CUtlString sym = name; + int idx = symbols.Find(sym); + if (idx == symbols.InvalidIndex()) + { + symbols.Insert(sym); + } + + // Too many + if (symbols.Count() >= COMMAND_COMPLETION_MAXITEMS) + break; + } + + // Now fill in the results + for (int i = symbols.FirstInorder(); i != symbols.InvalidIndex(); i = symbols.NextInorder(i)) + { + const char *name = symbols[i].String(); + + char buf[512]; + Q_strncpy(buf, name, sizeof(buf)); + Q_strlower(buf); + + CUtlString command; + command = CFmtStr("%s %s", cmdname, buf); + commands.AddToTail(command); + } + + return symbols.Count(); +} +#endif + //----------------------------------------------------------------------------- // class CFlaggedEntitiesEnum @@ -296,6 +343,14 @@ int UTIL_EntitiesInSphere( const Vector ¢er, float radius, CFlaggedEntitiesE return pEnum->GetCount(); } +#ifdef MAPBASE +int UTIL_EntitiesAtPoint( const Vector &point, CFlaggedEntitiesEnum *pEnum ) +{ + partition->EnumerateElementsAtPoint( PARTITION_ENGINE_NON_STATIC_EDICTS, point, false, pEnum ); + return pEnum->GetCount(); +} +#endif + CEntitySphereQuery::CEntitySphereQuery( const Vector ¢er, float radius, int flagMask ) { m_listIndex = 0; @@ -987,7 +1042,7 @@ void UTIL_ScreenFade( CBaseEntity *pEntity, const color32 &color, float fadeTime } -void UTIL_HudMessage( CBasePlayer *pToPlayer, const hudtextparms_t &textparms, const char *pMessage ) +void UTIL_HudMessage( CBasePlayer *pToPlayer, const hudtextparms_t &textparms, const char *pMessage, const char *pszFont, bool bAutobreak ) { CRecipientFilter filter; @@ -1020,12 +1075,19 @@ void UTIL_HudMessage( CBasePlayer *pToPlayer, const hudtextparms_t &textparms, c WRITE_FLOAT( textparms.holdTime ); WRITE_FLOAT( textparms.fxTime ); WRITE_STRING( pMessage ); +#ifdef MAPBASE + WRITE_STRING( pszFont ); + if (bAutobreak) + { + WRITE_BYTE ( Q_strlen( pMessage ) ); + } +#endif MessageEnd(); } -void UTIL_HudMessageAll( const hudtextparms_t &textparms, const char *pMessage ) +void UTIL_HudMessageAll( const hudtextparms_t &textparms, const char *pMessage, const char *pszFont, bool bAutobreak ) { - UTIL_HudMessage( NULL, textparms, pMessage ); + UTIL_HudMessage( NULL, textparms, pMessage, pszFont, bAutobreak ); } void UTIL_HudHintText( CBaseEntity *pEntity, const char *pMessage ) @@ -1251,9 +1313,19 @@ void UTIL_SetModel( CBaseEntity *pEntity, const char *pModelName ) int i = modelinfo->GetModelIndex( pModelName ); if ( i == -1 ) { +#if defined(MAPBASE) && !defined(_DEBUG) + // Throwing a program-terminating error might be a little too much since we could just precache it here. + // If we're not in debug mode, just let it off with a nice warning. + if (int newi = CBaseEntity::PrecacheModel(pModelName)) + { + i = newi; + Warning("%s was not precached\n", pModelName); + } +#else Error("%i/%s - %s: UTIL_SetModel: not precached: %s\n", pEntity->entindex(), STRING( pEntity->GetEntityName() ), pEntity->GetClassname(), pModelName); +#endif } CBaseAnimating *pAnimating = pEntity->GetBaseAnimating(); @@ -1745,6 +1817,40 @@ void UTIL_PrecacheOther( const char *szClassname, const char *modelName ) UTIL_RemoveImmediate( pEntity ); } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: Tests whether this entity exists in the dictionary and if it does, precaches it. (as opposed to complaining when it's missing) +// Input : *szClassname - +// *modelName - +//----------------------------------------------------------------------------- +bool UTIL_TestPrecacheOther( const char *szClassname, const char *modelName ) +{ +#if defined( PRECACHE_OTHER_ONCE ) + // already done this one?, if not, mark as done + if ( !g_PrecacheOtherList.AddOrMarkPrecached( szClassname ) ) + return true; +#endif + + // If we can't create it, it probably does not exist + CBaseEntity *pEntity = CreateEntityByName( szClassname ); + if (!pEntity) + return false; + + // If we have a specified model, set it before calling precache + if ( modelName && modelName[0] ) + { + pEntity->SetModelName( AllocPooledString( modelName ) ); + } + + if (pEntity) + pEntity->Precache( ); + + UTIL_RemoveImmediate( pEntity ); + + return true; +} +#endif + //========================================================= // UTIL_LogPrintf - Prints a logged message to console. // Preceded by LOG: ( timestamp ) < message > @@ -2376,6 +2482,10 @@ void UTIL_PredictedPosition( CBaseEntity *pTarget, float flTimeDelta, Vector *ve if ( pAnimating != NULL ) { vecPredictedVel = pAnimating->GetGroundSpeedVelocity(); +#ifdef MAPBASE + if (vecPredictedVel.IsZero()) + vecPredictedVel = pAnimating->GetSmoothedVelocity(); +#endif } else { @@ -2389,6 +2499,80 @@ void UTIL_PredictedPosition( CBaseEntity *pTarget, float flTimeDelta, Vector *ve (*vecPredictedPosition) = pTarget->GetAbsOrigin() + ( vecPredictedVel * flTimeDelta ); } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: Same as above, except you don't have to use the absolute origin and can use your own position to predict from. +//----------------------------------------------------------------------------- +void UTIL_PredictedPosition( CBaseEntity *pTarget, const Vector &vecActualPosition, float flTimeDelta, Vector *vecPredictedPosition ) +{ + if ( ( pTarget == NULL ) || ( vecPredictedPosition == NULL ) ) + return; + + Vector vecPredictedVel; + CBasePlayer *pPlayer = ToBasePlayer( pTarget ); + if ( pPlayer != NULL ) + { + if ( pPlayer->IsInAVehicle() ) + vecPredictedVel = pPlayer->GetVehicleEntity()->GetSmoothedVelocity(); + else + vecPredictedVel = pPlayer->GetSmoothedVelocity(); + } + else + { + CBaseCombatCharacter *pCCTarget = pTarget->MyCombatCharacterPointer(); + if ( pCCTarget != NULL && pCCTarget->IsInAVehicle() ) + vecPredictedVel = pCCTarget->GetVehicleEntity()->GetSmoothedVelocity(); + else + { + CBaseAnimating *pAnimating = dynamic_cast(pTarget); + if ( pAnimating != NULL ) + { + vecPredictedVel = pAnimating->GetGroundSpeedVelocity(); + if (vecPredictedVel.IsZero()) + vecPredictedVel = pAnimating->GetSmoothedVelocity(); + } + else + vecPredictedVel = pTarget->GetSmoothedVelocity(); + } + } + + // Get the result + (*vecPredictedPosition) = vecActualPosition + ( vecPredictedVel * flTimeDelta ); +} + +//----------------------------------------------------------------------------- +// Purpose: Predicts angles through angular velocity instead of predicting origin through regular velocity. +//----------------------------------------------------------------------------- +void UTIL_PredictedAngles( CBaseEntity *pTarget, const QAngle &angActualAngles, float flTimeDelta, QAngle *angPredictedAngles ) +{ + if ( ( pTarget == NULL ) || ( angPredictedAngles == NULL ) ) + return; + + QAngle angPredictedVel; + CBasePlayer *pPlayer = ToBasePlayer( pTarget ); + if ( pPlayer != NULL ) + { + if ( pPlayer->IsInAVehicle() ) + angPredictedVel = pPlayer->GetVehicleEntity()->GetLocalAngularVelocity(); + else + angPredictedVel = pPlayer->GetLocalAngularVelocity(); + } + else + { + CBaseCombatCharacter *pCCTarget = pTarget->MyCombatCharacterPointer(); + if ( pCCTarget != NULL && pCCTarget->IsInAVehicle() ) + angPredictedVel = pCCTarget->GetVehicleEntity()->GetLocalAngularVelocity(); + else + { + angPredictedVel = pTarget->GetLocalAngularVelocity(); + } + } + + // Get the result + (*angPredictedAngles) = angActualAngles + ( angPredictedVel * flTimeDelta ); +} +#endif + //----------------------------------------------------------------------------- // Purpose: Points the destination entity at the target entity // Input : *pDest - entity to be pointed at the target diff --git a/src/game/server/util.h b/src/game/server/util.h index a3b9bf70..38fbbfab 100644 --- a/src/game/server/util.h +++ b/src/game/server/util.h @@ -100,6 +100,9 @@ public: virtual void Destroy( const char *pClassName, IServerNetworkable *pNetworkable ) = 0; virtual IEntityFactory *FindFactory( const char *pClassName ) = 0; virtual const char *GetCannonicalName( const char *pClassName ) = 0; +#ifdef MAPBASE + virtual void UninstallFactory(const char* pClassName) = 0; +#endif // MAPBASE }; IEntityFactoryDictionary *EntityFactoryDictionary(); @@ -187,7 +190,12 @@ extern CGlobalVars *gpGlobals; // Misc useful inline bool FStrEq(const char *sz1, const char *sz2) { +#ifdef MAPBASE + // V_stricmp() already checks if the pointers are equal, so having a pointer comparison here is unnecessary. + return ( V_stricmp(sz1, sz2) == 0 ); +#else return ( sz1 == sz2 || V_stricmp(sz1, sz2) == 0 ); +#endif } #if 0 @@ -230,8 +238,10 @@ CBasePlayer* UTIL_GetLocalPlayer( void ); // get the local player on a listen server CBasePlayer *UTIL_GetListenServerHost( void ); -// Convenience function so we don't have to make this check all over -inline CBasePlayer *UTIL_GetLocalPlayerOrListenServerHost( void ) +//----------------------------------------------------------------------------- +// Purpose: Convenience function so we don't have to make this check all over +//----------------------------------------------------------------------------- +static CBasePlayer * UTIL_GetLocalPlayerOrListenServerHost( void ) { if ( gpGlobals->maxClients > 1 ) { @@ -292,6 +302,9 @@ private: int UTIL_EntitiesInBox( const Vector &mins, const Vector &maxs, CFlaggedEntitiesEnum *pEnum ); int UTIL_EntitiesAlongRay( const Ray_t &ray, CFlaggedEntitiesEnum *pEnum ); int UTIL_EntitiesInSphere( const Vector ¢er, float radius, CFlaggedEntitiesEnum *pEnum ); +#ifdef MAPBASE +int UTIL_EntitiesAtPoint( const Vector &point, CFlaggedEntitiesEnum *pEnum ); +#endif inline int UTIL_EntitiesInBox( CBaseEntity **pList, int listMax, const Vector &mins, const Vector &maxs, int flagMask ) { @@ -311,6 +324,14 @@ inline int UTIL_EntitiesInSphere( CBaseEntity **pList, int listMax, const Vector return UTIL_EntitiesInSphere( center, radius, &sphereEnum ); } +#ifdef MAPBASE +inline int UTIL_EntitiesAtPoint( CBaseEntity **pList, int listMax, const Vector &point, int flagMask ) +{ + CFlaggedEntitiesEnum pointEnum( pList, listMax, flagMask ); + return UTIL_EntitiesAtPoint( point, &pointEnum ); +} +#endif + // marks the entity for deletion so it will get removed next frame void UTIL_Remove( IServerNetworkable *oldObj ); void UTIL_Remove( CBaseEntity *oldObj ); @@ -373,6 +394,10 @@ void UTIL_AxisStringToPointPoint( Vector &start, Vector &end, const char *pStri void UTIL_AxisStringToUnitDir( Vector &dir, const char *pString ); void UTIL_ClipPunchAngleOffset( QAngle &in, const QAngle &punch, const QAngle &clip ); void UTIL_PredictedPosition( CBaseEntity *pTarget, float flTimeDelta, Vector *vecPredictedPosition ); +#ifdef MAPBASE +void UTIL_PredictedPosition( CBaseEntity *pTarget, const Vector &vecActualPosition, float flTimeDelta, Vector *vecPredictedPosition ); +void UTIL_PredictedAngles( CBaseEntity *pTarget, const QAngle &angActualAngles, float flTimeDelta, QAngle *angPredictedAngles ); +#endif void UTIL_Beam( Vector &Start, Vector &End, int nModelIndex, int nHaloIndex, unsigned char FrameStart, unsigned char FrameRate, float Life, unsigned char Width, unsigned char EndWidth, unsigned char FadeLength, unsigned char Noise, unsigned char Red, unsigned char Green, unsigned char Blue, unsigned char Brightness, unsigned char Speed); @@ -404,6 +429,11 @@ void UTIL_BubbleTrail( const Vector& from, const Vector& to, int count ); // allows precacheing of other entities void UTIL_PrecacheOther( const char *szClassname, const char *modelName = NULL ); +#ifdef MAPBASE +// Tests whether this entity exists in the dictionary and if it does, precaches it. (as opposed to complaining when it's missing) +bool UTIL_TestPrecacheOther( const char *szClassname, const char *modelName = NULL ); +#endif + // prints a message to each client void UTIL_ClientPrintAll( int msg_dest, const char *msg_name, const char *param1 = NULL, const char *param2 = NULL, const char *param3 = NULL, const char *param4 = NULL ); inline void UTIL_CenterPrintAll( const char *msg_name, const char *param1 = NULL, const char *param2 = NULL, const char *param3 = NULL, const char *param4 = NULL ) @@ -480,8 +510,8 @@ void UTIL_SetModel( CBaseEntity *pEntity, const char *pModelName ); // prints as transparent 'title' to the HUD -void UTIL_HudMessageAll( const hudtextparms_t &textparms, const char *pMessage ); -void UTIL_HudMessage( CBasePlayer *pToPlayer, const hudtextparms_t &textparms, const char *pMessage ); +void UTIL_HudMessageAll( const hudtextparms_t &textparms, const char *pMessage, const char *pszFont = NULL, bool bAutobreak = false ); +void UTIL_HudMessage( CBasePlayer *pToPlayer, const hudtextparms_t &textparms, const char *pMessage, const char *pszFont = NULL, bool bAutobreak = false ); // brings up hud keyboard hints display void UTIL_HudHintText( CBaseEntity *pEntity, const char *pMessage ); @@ -520,12 +550,17 @@ float UTIL_ScaleForGravity( float desiredGravity ); #define SF_BRUSH_ROTATE_BACKWARDS 2 #define SF_BRUSH_ROTATE_Z_AXIS 4 #define SF_BRUSH_ROTATE_X_AXIS 8 -#define SF_BRUSH_ROTATE_CLIENTSIDE 16 +// brought over from bmodels.cpp +#define SF_BRUSH_ACCDCC 16 // brush should accelerate and decelerate when toggled +#define SF_BRUSH_HURT 32 // rotating brush that inflicts pain based on rotation speed +#define SF_ROTATING_NOT_SOLID 64 // some special rotating objects are not solid. #define SF_BRUSH_ROTATE_SMALLRADIUS 128 #define SF_BRUSH_ROTATE_MEDIUMRADIUS 256 #define SF_BRUSH_ROTATE_LARGERADIUS 512 +// changed bit to not conflict with much older flag SF_BRUSH_ACCDCC +#define SF_BRUSH_ROTATE_CLIENTSIDE 1024 #define PUSH_BLOCK_ONLY_X 1 #define PUSH_BLOCK_ONLY_Y 2 diff --git a/src/game/server/variant_t.cpp b/src/game/server/variant_t.cpp index 82edcfd4..3b45275b 100644 --- a/src/game/server/variant_t.cpp +++ b/src/game/server/variant_t.cpp @@ -6,6 +6,10 @@ #include "cbase.h" #include "variant_t.h" +#ifdef MAPBASE +#include "mapbase/variant_tools.h" +#include "mapbase/matchers.h" +#endif // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" @@ -16,4 +20,265 @@ void variant_t::SetEntity( CBaseEntity *val ) fieldType = FIELD_EHANDLE; } +#ifdef MAPBASE +const char *variant_t::GetDebug() +{ + /* + case FIELD_BOOLEAN: *((bool *)data) = bVal != 0; break; + case FIELD_CHARACTER: *((char *)data) = iVal; break; + case FIELD_SHORT: *((short *)data) = iVal; break; + case FIELD_INTEGER: *((int *)data) = iVal; break; + case FIELD_STRING: *((string_t *)data) = iszVal; break; + case FIELD_FLOAT: *((float *)data) = flVal; break; + case FIELD_COLOR32: *((color32 *)data) = rgbaVal; break; + + case FIELD_VECTOR: + case FIELD_POSITION_VECTOR: + { + ((float *)data)[0] = vecVal[0]; + ((float *)data)[1] = vecVal[1]; + ((float *)data)[2] = vecVal[2]; + break; + } + + case FIELD_EHANDLE: *((EHANDLE *)data) = eVal; break; + case FIELD_CLASSPTR: *((CBaseEntity **)data) = eVal; break; + */ + + const char *fieldtype = "unknown"; + switch (FieldType()) + { + case FIELD_VOID: fieldtype = "Void"; break; + case FIELD_FLOAT: fieldtype = "Float"; break; + case FIELD_STRING: fieldtype = "String"; break; + case FIELD_INTEGER: fieldtype = "Integer"; break; + case FIELD_BOOLEAN: fieldtype = "Boolean"; break; + case FIELD_EHANDLE: fieldtype = "Entity"; break; + case FIELD_CLASSPTR: fieldtype = "EntityPtr"; break; + case FIELD_POSITION_VECTOR: + case FIELD_VECTOR: fieldtype = "Vector"; break; + case FIELD_CHARACTER: fieldtype = "Character"; break; + case FIELD_SHORT: fieldtype = "Short"; break; + case FIELD_COLOR32: fieldtype = "Color32"; break; + default: fieldtype = UTIL_VarArgs("unknown: %i", FieldType()); + } + return UTIL_VarArgs("%s (%s)", String(), fieldtype); +} + +#ifdef MAPBASE_VSCRIPT +void variant_t::SetScriptVariant( ScriptVariant_t &var ) +{ + switch (FieldType()) + { + case FIELD_VOID: var = NULL; break; + case FIELD_INTEGER: var = iVal; break; + case FIELD_FLOAT: var = flVal; break; + case FIELD_STRING: var = STRING(iszVal); break; + case FIELD_POSITION_VECTOR: + case FIELD_VECTOR: var = reinterpret_cast(&flVal); break; // HACKHACK + case FIELD_BOOLEAN: var = bVal; break; + case FIELD_EHANDLE: var = ToHScript( eVal ); break; + case FIELD_CLASSPTR: var = ToHScript( eVal ); break; + case FIELD_SHORT: var = (short)iVal; break; + case FIELD_CHARACTER: var = (char)iVal; break; + case FIELD_COLOR32: + { + Color *clr = new Color( rgbaVal.r, rgbaVal.g, rgbaVal.b, rgbaVal.a ); + var = g_pScriptVM->RegisterInstance( clr, true ); + break; + } + default: var = ToString(); break; + } +} +#endif + +// cmp1 = val1 float +// cmp2 = val2 float +#define VariantToFloat(val1, val2, lenallowed) \ + float cmp1 = val1.Float() ? val1.Float() : val1.Int(); \ + float cmp2 = val2.Float() ? val2.Float() : val2.Int(); \ + if (lenallowed && val2.FieldType() == FIELD_STRING) \ + cmp2 = strlen(val2.String()); + +// Integer parsing has been deactivated for consistency's sake. They now become floats only. +#define INTEGER_PARSING_DEACTIVATED 1 + +// "intchar" is the result of me not knowing where to find a version of isdigit that applies to negative numbers and floats. +#define intchar(c) (c >= '-' && c <= '9') + +// Attempts to determine the field type from whatever is in the string and creates a variant_t with the converted value and resulting field type. +// Right now, Int/Float, String, and Vector are the only fields likely to be used by entities in variant_t parsing, so they're the only ones supported. +// Expand to other fields when necessary. +variant_t Variant_Parse(const char *szValue) +{ +#ifdef INTEGER_PARSING_DEACTIVATED + bool isint = true; + bool isvector = false; + for (size_t i = 0; i < strlen(szValue); i++) + { + if (!intchar(szValue[i])) + { + isint = false; + + if (szValue[i] == ' ') + isvector = true; + else + isvector = false; + } + } + + variant_t var; + + if (isint) + var.SetFloat(atof(szValue)); + else if (isvector) + { + var.SetString(MAKE_STRING(szValue)); + var.Convert(FIELD_VECTOR); + } + else + var.SetString(MAKE_STRING(szValue)); +#else + bool isint = true; + bool isfloat = false; + for (size_t i = 0; i < strlen(szValue); i++) + { + if (szValue[i] == '.') + isfloat = true; + else if (!intchar(szValue[i])) + isint = false; + } + + variant_t var = variant_t(); + + if (isint) + { + if (isfloat) + var.SetFloat(atof(szValue)); + else + var.SetInt(atoi(szValue)); + } + else + var.SetString(MAKE_STRING(szValue)); +#endif + + return var; +} + +// Passes strings to Variant_Parse, uses the other input data for finding procedural entities. +variant_t Variant_ParseInput(inputdata_t inputdata) +{ + if (inputdata.value.FieldType() == FIELD_STRING) + { + if (inputdata.value.String()[0] == '!') + { + variant_t var = variant_t(); + var.SetEntity(gEntList.FindEntityProcedural(inputdata.value.String(), inputdata.pCaller, inputdata.pActivator, inputdata.pCaller)); + if (var.Entity()) + return var; + } + } + + return Variant_Parse(inputdata.value.String()); +} + +// Passes string variants to Variant_Parse +variant_t Variant_ParseString(variant_t value) +{ + if (value.FieldType() != FIELD_STRING) + return value; + + return Variant_Parse(value.String()); +} + +bool Variant_Equal(variant_t val1, variant_t val2, bool bLenAllowed) +{ + //if (!val2.Convert(val1.FieldType())) + // return false; + + // Add more fields if they become necessary + switch (val1.FieldType()) + { + case FIELD_INTEGER: + case FIELD_FLOAT: + { + VariantToFloat(val1, val2, bLenAllowed); + return cmp1 == cmp2; + } + case FIELD_BOOLEAN: return val1.Bool() == val2.Bool(); + case FIELD_EHANDLE: return val1.Entity() == val2.Entity(); + case FIELD_VECTOR: + { + Vector vec1; val1.Vector3D(vec1); + Vector vec2; val2.Vector3D(vec2); + return vec1 == vec2; + } +#ifdef MAPBASE_MATCHERS + // logic_compare allows wildcards on either string + default: return Matcher_NamesMatch_MutualWildcard(val1.String(), val2.String()); +#else + default: return FStrEq(val1.String(), val2.String()); +#endif + } + + return false; +} + +// val1 > val2 +bool Variant_Greater(variant_t val1, variant_t val2, bool bLenAllowed) +{ + //if (!val2.Convert(val1.FieldType())) + // return false; + + // Add more fields if they become necessary + switch (val1.FieldType()) + { + case FIELD_INTEGER: + case FIELD_FLOAT: + { + VariantToFloat(val1, val2, bLenAllowed); + return cmp1 > cmp2; + } + case FIELD_BOOLEAN: return val1.Bool() && !val2.Bool(); + case FIELD_VECTOR: + { + Vector vec1; val1.Vector3D(vec1); + Vector vec2; val2.Vector3D(vec2); + return (vec1.x > vec2.x) && (vec1.y > vec2.y) && (vec1.z > vec2.z); + } + default: return strlen(val1.String()) > strlen(val2.String()); + } + + return false; +} + +// val1 >= val2 +bool Variant_GreaterOrEqual(variant_t val1, variant_t val2, bool bLenAllowed) +{ + //if (!val2.Convert(val1.FieldType())) + // return false; + + // Add more fields if they become necessary + switch (val1.FieldType()) + { + case FIELD_INTEGER: + case FIELD_FLOAT: + { + VariantToFloat(val1, val2, bLenAllowed); + return cmp1 >= cmp2; + } + case FIELD_BOOLEAN: return val1.Bool() >= val2.Bool(); + case FIELD_VECTOR: + { + Vector vec1; val1.Vector3D(vec1); + Vector vec2; val2.Vector3D(vec2); + return (vec1.x >= vec2.x) && (vec1.y >= vec2.y) && (vec1.z >= vec2.z); + } + default: return strlen(val1.String()) >= strlen(val2.String()); + } + + return false; +} +#endif + diff --git a/src/game/server/variant_t.h b/src/game/server/variant_t.h index d5e93ea4..7ec43e94 100644 --- a/src/game/server/variant_t.h +++ b/src/game/server/variant_t.h @@ -14,9 +14,16 @@ #include "ehandle.h" #include "mathlib/vmatrix.h" +#ifdef MAPBASE_VSCRIPT +#include "vscript/variant.h" +#endif class CBaseEntity; +#ifdef MAPBASE_VSCRIPT +typedef CVariant ScriptVariant_t; +#endif + // // A variant class for passing data in entity input/output connections. @@ -49,6 +56,10 @@ public: inline const CHandle &Entity(void) const; inline color32 Color32(void) const { return rgbaVal; } inline void Vector3D(Vector &vec) const; +#ifdef MAPBASE + // Gets angles from a vector + inline void Angle3D(QAngle &ang) const; +#endif fieldtype_t FieldType( void ) { return fieldType; } @@ -59,11 +70,26 @@ public: void SetEntity( CBaseEntity *val ); void SetVector3D( const Vector &val ) { vecVal[0] = val[0]; vecVal[1] = val[1]; vecVal[2] = val[2]; fieldType = FIELD_VECTOR; } void SetPositionVector3D( const Vector &val ) { vecVal[0] = val[0]; vecVal[1] = val[1]; vecVal[2] = val[2]; fieldType = FIELD_POSITION_VECTOR; } +#ifdef MAPBASE + // Passes in angles as a vector + void SetAngle3D( const QAngle &val ) { vecVal[0] = val[0]; vecVal[1] = val[1]; vecVal[2] = val[2]; fieldType = FIELD_VECTOR; } +#endif void SetColor32( color32 val ) { rgbaVal = val; fieldType = FIELD_COLOR32; } void SetColor32( int r, int g, int b, int a ) { rgbaVal.r = r; rgbaVal.g = g; rgbaVal.b = b; rgbaVal.a = a; fieldType = FIELD_COLOR32; } void Set( fieldtype_t ftype, void *data ); void SetOther( void *data ); bool Convert( fieldtype_t newType ); +#ifdef MAPBASE + // Special conversion specifically for FIELD_EHANDLE with !activator, etc. + bool Convert( fieldtype_t newType, CBaseEntity *pSelf, CBaseEntity *pActivator, CBaseEntity *pCaller ); + // Hands over the value + the field type. + // ex: "Otis (String)", "3 (Integer)", or "npc_combine_s (Entity)" + const char *GetDebug(); +#endif + +#ifdef MAPBASE_VSCRIPT + void SetScriptVariant( ScriptVariant_t &var ); +#endif static typedescription_t m_SaveBool[]; static typedescription_t m_SaveInt[]; @@ -105,6 +131,25 @@ inline void variant_t::Vector3D(Vector &vec) const } } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: Returns this variant as angles. +//----------------------------------------------------------------------------- +inline void variant_t::Angle3D(QAngle &ang) const +{ + if (( fieldType == FIELD_VECTOR ) || ( fieldType == FIELD_POSITION_VECTOR )) + { + ang[0] = vecVal[0]; + ang[1] = vecVal[1]; + ang[2] = vecVal[2]; + } + else + { + ang = vec3_angle; + } +} +#endif + //----------------------------------------------------------------------------- // Purpose: Returns this variant as an EHANDLE. //----------------------------------------------------------------------------- diff --git a/src/game/server/vehicle_base.cpp b/src/game/server/vehicle_base.cpp index f916ba69..5e348ee9 100644 --- a/src/game/server/vehicle_base.cpp +++ b/src/game/server/vehicle_base.cpp @@ -66,6 +66,15 @@ BEGIN_DATADESC( CPropVehicle ) END_DATADESC() +#ifdef MAPBASE_VSCRIPT +BEGIN_ENT_SCRIPTDESC( CPropVehicle, CBaseAnimating, "The base class for four-wheel physics vehicles." ) + + DEFINE_SCRIPTFUNC_NAMED( ScriptGetVehicleType, "GetVehicleType", "Get a vehicle's type." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptGetPhysics, "GetPhysics", "Get a vehicle's physics." ) + +END_SCRIPTDESC(); +#endif + LINK_ENTITY_TO_CLASS( prop_vehicle, CPropVehicle ); //----------------------------------------------------------------------------- @@ -226,6 +235,23 @@ void CPropVehicle::InputHandBrakeOff( inputdata_t &inputdata ) m_VehiclePhysics.ReleaseHandbrake(); } +#ifdef MAPBASE_VSCRIPT +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +HSCRIPT CPropVehicle::ScriptGetPhysics() +{ + HSCRIPT hScript = NULL; + CFourWheelVehiclePhysics *pPhysics = GetPhysics(); + if (pPhysics) + { + hScript = g_pScriptVM->RegisterInstance( pPhysics ); + } + + return hScript; +} +#endif + //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- @@ -334,6 +360,12 @@ BEGIN_DATADESC( CPropVehicleDriveable ) DEFINE_INPUTFUNC( FIELD_VOID, "Unlock", InputUnlock ), DEFINE_INPUTFUNC( FIELD_VOID, "TurnOn", InputTurnOn ), DEFINE_INPUTFUNC( FIELD_VOID, "TurnOff", InputTurnOff ), +#ifdef MAPBASE + DEFINE_INPUTFUNC( FIELD_VOID, "EnterVehicle", InputEnterVehicle ), + DEFINE_INPUTFUNC( FIELD_VOID, "EnterVehicleImmediate", InputEnterVehicleImmediate ), + DEFINE_INPUTFUNC( FIELD_VOID, "ExitVehicle", InputExitVehicle ), + DEFINE_INPUTFUNC( FIELD_VOID, "ExitVehicleImmediate", InputExitVehicleImmediate ), +#endif DEFINE_INPUT( m_bHasGun, FIELD_BOOLEAN, "EnableGun" ), // Outputs @@ -343,6 +375,9 @@ BEGIN_DATADESC( CPropVehicleDriveable ) DEFINE_OUTPUT( m_pressedAttack2, "PressedAttack2" ), DEFINE_OUTPUT( m_attackaxis, "AttackAxis" ), DEFINE_OUTPUT( m_attack2axis, "Attack2Axis" ), +#ifdef MAPBASE + DEFINE_OUTPUT( m_OnPlayerUse, "OnPlayerUse" ), +#endif DEFINE_FIELD( m_hPlayer, FIELD_EHANDLE ), DEFINE_EMBEDDEDBYREF( m_pServerVehicle ), @@ -370,6 +405,19 @@ BEGIN_DATADESC( CPropVehicleDriveable ) END_DATADESC() +#ifdef MAPBASE_VSCRIPT +BEGIN_ENT_SCRIPTDESC( CPropVehicleDriveable, CPropVehicle, "The base class for driveable vehicles." ) + + DEFINE_SCRIPTFUNC( IsOverturned, "Check if the vehicle is overturned." ) + DEFINE_SCRIPTFUNC( IsVehicleBodyInWater, "Check if the vehicle's body is submerged in water." ) + DEFINE_SCRIPTFUNC( StartEngine, "Start the engine." ) + DEFINE_SCRIPTFUNC( StopEngine, "Stop the engine." ) + DEFINE_SCRIPTFUNC( IsEngineOn, "Check if the engine is on." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptGetDriver, "GetDriver", "Get a vehicle's driver, which could be either a player or a npc_vehicledriver." ) + +END_SCRIPTDESC(); +#endif + LINK_ENTITY_TO_CLASS( prop_vehicle_driveable, CPropVehicleDriveable ); @@ -557,6 +605,10 @@ void CPropVehicleDriveable::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, ResetUseKey( pPlayer ); +#ifdef MAPBASE + m_OnPlayerUse.FireOutput(pActivator, this); +#endif + m_pServerVehicle->HandlePassengerEntry( pPlayer, (value>0) ); } @@ -847,6 +899,99 @@ void CPropVehicleDriveable::InputTurnOff( inputdata_t &inputdata ) m_VehiclePhysics.SetDisableEngine( true ); } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPropVehicleDriveable::InputEnterVehicle( inputdata_t &inputdata ) +{ + if ( m_bEnterAnimOn ) + return; + + // Try the activator first & use them if they are a player. + CBaseCombatCharacter *pPassenger = ToBaseCombatCharacter( inputdata.pActivator ); + if ( pPassenger == NULL ) + { + // Activator was not a player, just grab the singleplayer player. + pPassenger = UTIL_PlayerByIndex( 1 ); + if ( pPassenger == NULL ) + return; + } + + // FIXME: I hate code like this. I should really add a parameter to HandlePassengerEntry + // to allow entry into locked vehicles + bool bWasLocked = m_bLocked; + m_bLocked = false; + GetServerVehicle()->HandlePassengerEntry( pPassenger, true ); + m_bLocked = bWasLocked; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : &inputdata - +//----------------------------------------------------------------------------- +void CPropVehicleDriveable::InputEnterVehicleImmediate( inputdata_t &inputdata ) +{ + if ( m_bEnterAnimOn ) + return; + + // Try the activator first & use them if they are a player. + CBaseCombatCharacter *pPassenger = ToBaseCombatCharacter( inputdata.pActivator ); + if ( pPassenger == NULL ) + { + // Activator was not a player, just grab the singleplayer player. + pPassenger = UTIL_PlayerByIndex( 1 ); + if ( pPassenger == NULL ) + return; + } + + CBasePlayer *pPlayer = ToBasePlayer( pPassenger ); + if ( pPlayer != NULL ) + { + if ( pPlayer->IsInAVehicle() ) + { + // Force the player out of whatever vehicle they are in. + pPlayer->LeaveVehicle(); + } + + pPlayer->GetInVehicle( GetServerVehicle(), VEHICLE_ROLE_DRIVER ); + } + else + { + // NPCs are not currently supported - jdw + Assert( 0 ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPropVehicleDriveable::InputExitVehicle( inputdata_t &inputdata ) +{ + if (!GetDriver()) + return; + + if ( CanExitVehicle(GetDriver()) ) + { + GetServerVehicle()->HandlePassengerExit(GetDriver()->MyCombatCharacterPointer()); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CPropVehicleDriveable::InputExitVehicleImmediate( inputdata_t &inputdata ) +{ + if (!GetDriver()) + return; + + if (GetDriver()->IsPlayer()) + { + static_cast(GetDriver())->LeaveVehicle(); + } +} +#endif + //----------------------------------------------------------------------------- // Purpose: Check to see if the engine is on. //----------------------------------------------------------------------------- diff --git a/src/game/server/vehicle_base.h b/src/game/server/vehicle_base.h index ab63005a..831ea6b4 100644 --- a/src/game/server/vehicle_base.h +++ b/src/game/server/vehicle_base.h @@ -109,6 +109,12 @@ public: void InputHandBrakeOff( inputdata_t &inputdata ); DECLARE_DATADESC(); +#ifdef MAPBASE_VSCRIPT + DECLARE_ENT_SCRIPTDESC(); + + HSCRIPT ScriptGetPhysics(); + int ScriptGetVehicleType() { return GetVehicleType(); } +#endif #ifdef HL2_EPISODIC void AddPhysicsChild( CBaseEntity *pChild ); @@ -166,6 +172,9 @@ class CPropVehicleDriveable : public CPropVehicle, public IDrivableVehicle, publ DECLARE_CLASS( CPropVehicleDriveable, CPropVehicle ); DECLARE_SERVERCLASS(); DECLARE_DATADESC(); +#ifdef MAPBASE_VSCRIPT + DECLARE_ENT_SCRIPTDESC(); +#endif public: CPropVehicleDriveable( void ); ~CPropVehicleDriveable( void ); @@ -192,6 +201,12 @@ public: void InputUnlock( inputdata_t &inputdata ); void InputTurnOn( inputdata_t &inputdata ); void InputTurnOff( inputdata_t &inputdata ); +#ifdef MAPBASE + virtual void InputEnterVehicle( inputdata_t &inputdata ); + virtual void InputEnterVehicleImmediate( inputdata_t &inputdata ); + virtual void InputExitVehicle( inputdata_t &inputdata ); + virtual void InputExitVehicleImmediate( inputdata_t &inputdata ); +#endif // Locals void ResetUseKey( CBasePlayer *pPlayer ); @@ -232,6 +247,10 @@ public: // If this is a vehicle, returns the vehicle interface virtual IServerVehicle *GetServerVehicle() { return m_pServerVehicle; } +#ifdef MAPBASE_VSCRIPT + HSCRIPT ScriptGetDriver() { return ToHScript( GetDriver() ); } +#endif + protected: virtual bool ShouldThink() { return ( GetDriver() != NULL ); } @@ -251,6 +270,10 @@ protected: COutputFloat m_attackaxis; COutputFloat m_attack2axis; +#ifdef MAPBASE + COutputEvent m_OnPlayerUse; +#endif + CNetworkHandle( CBasePlayer, m_hPlayer ); public: diff --git a/src/game/server/vehicle_choreo_generic.cpp b/src/game/server/vehicle_choreo_generic.cpp index b644a7a4..3b3a53f9 100644 --- a/src/game/server/vehicle_choreo_generic.cpp +++ b/src/game/server/vehicle_choreo_generic.cpp @@ -197,6 +197,11 @@ public: // If this is a vehicle, returns the vehicle interface virtual IServerVehicle *GetServerVehicle() { return &m_ServerVehicle; } +#ifdef MAPBASE + virtual bool IsPassengerUsingStandardWeapons( int nRole = VEHICLE_ROLE_DRIVER ) { return m_bAllowStandardWeapons; } + CNetworkVar( bool, m_bAllowStandardWeapons ); +#endif + bool ShouldCollide( int collisionGroup, int contentsMask ) const; bool m_bForcePlayerEyePoint; // Uses player's eyepoint instead of 'vehicle_driver_eyes' attachment @@ -256,6 +261,10 @@ BEGIN_DATADESC( CPropVehicleChoreoGeneric ) DEFINE_KEYFIELD( m_bIgnorePlayerCollisions, FIELD_BOOLEAN, "ignoreplayer" ), DEFINE_KEYFIELD( m_bForcePlayerEyePoint, FIELD_BOOLEAN, "useplayereyes" ), +#ifdef MAPBASE + DEFINE_KEYFIELD( m_bAllowStandardWeapons, FIELD_BOOLEAN, "AllowStandardWeapons" ), +#endif + DEFINE_OUTPUT( m_playerOn, "PlayerOn" ), DEFINE_OUTPUT( m_playerOff, "PlayerOff" ), DEFINE_OUTPUT( m_OnOpen, "OnOpen" ), @@ -281,6 +290,9 @@ IMPLEMENT_SERVERCLASS_ST(CPropVehicleChoreoGeneric, DT_PropVehicleChoreoGeneric) SendPropFloat( SENDINFO_STRUCTELEM( m_vehicleView.flYawMax ) ), SendPropFloat( SENDINFO_STRUCTELEM( m_vehicleView.flPitchMin ) ), SendPropFloat( SENDINFO_STRUCTELEM( m_vehicleView.flPitchMax ) ), +#ifdef MAPBASE + SendPropBool( SENDINFO( m_bAllowStandardWeapons ) ), +#endif END_SEND_TABLE(); diff --git a/src/game/server/vguiscreen.cpp b/src/game/server/vguiscreen.cpp index 5d4cba48..3c0890df 100644 --- a/src/game/server/vguiscreen.cpp +++ b/src/game/server/vguiscreen.cpp @@ -35,21 +35,21 @@ PRECACHE_REGISTER( vgui_screen ); //----------------------------------------------------------------------------- // Save/load //----------------------------------------------------------------------------- -BEGIN_DATADESC( CVGuiScreen ) +BEGIN_DATADESC(CVGuiScreen) - DEFINE_CUSTOM_FIELD( m_nPanelName, &g_VguiScreenStringOps ), - DEFINE_FIELD( m_nAttachmentIndex, FIELD_INTEGER ), +DEFINE_CUSTOM_FIELD(m_nPanelName, &g_VguiScreenStringOps), +DEFINE_FIELD(m_nAttachmentIndex, FIELD_INTEGER), // DEFINE_FIELD( m_nOverlayMaterial, FIELD_INTEGER ), - DEFINE_FIELD( m_fScreenFlags, FIELD_INTEGER ), - DEFINE_KEYFIELD( m_flWidth, FIELD_FLOAT, "width" ), - DEFINE_KEYFIELD( m_flHeight, FIELD_FLOAT, "height" ), - DEFINE_KEYFIELD( m_strOverlayMaterial, FIELD_STRING, "overlaymaterial" ), - DEFINE_FIELD( m_hPlayerOwner, FIELD_EHANDLE ), +DEFINE_FIELD(m_fScreenFlags, FIELD_INTEGER), +DEFINE_KEYFIELD(m_flWidth, FIELD_FLOAT, "width"), +DEFINE_KEYFIELD(m_flHeight, FIELD_FLOAT, "height"), +DEFINE_KEYFIELD(m_strOverlayMaterial, FIELD_STRING, "overlaymaterial"), +DEFINE_FIELD(m_hPlayerOwner, FIELD_EHANDLE), - DEFINE_INPUTFUNC( FIELD_VOID, "SetActive", InputSetActive ), - DEFINE_INPUTFUNC( FIELD_VOID, "SetInactive", InputSetInactive ), +DEFINE_INPUTFUNC(FIELD_VOID, "SetActive", InputSetActive), +DEFINE_INPUTFUNC(FIELD_VOID, "SetInactive", InputSetInactive), -END_DATADESC() +END_DATADESC(); //----------------------------------------------------------------------------- @@ -75,6 +75,24 @@ bool CVGuiScreen::KeyValue( const char *szKeyName, const char *szValue ) *s = '\0'; } +#ifdef MAPBASE + // Named command outputs + if (szKeyName[0] == '~' && szKeyName[1]) + { + const char* pszOutputName = szKeyName + 1; + int i = m_PanelOutputs.Find(pszOutputName); + if (!m_PanelOutputs.IsValidIndex(i)) + { + auto pMem = new COutputEvent; + V_memset(pMem, 0, sizeof(COutputEvent)); + i = m_PanelOutputs.Insert(pszOutputName, pMem); + } + + m_PanelOutputs[i]->ParseEventAction(szValue); + return true; + } +#endif // MAPBASE + if ( FStrEq( szKeyName, "panelname" )) { SetPanelName( szValue ); @@ -158,6 +176,106 @@ void CVGuiScreen::OnRestore() BaseClass::OnRestore(); } +#ifdef MAPBASE +CVGuiScreen::~CVGuiScreen() +{ + m_PanelOutputs.PurgeAndDeleteElements(); +} + +int CVGuiScreen::Save(ISave& save) +{ +#if MAPBASE_VER_INT < 8000 + // HACKHACK: Until v8.0, mark this screen as using the new save system to prevent existing saves with vgui_screen from crashing + AddContext( "uses_new_save", "1" ); +#endif + + int status = BaseClass::Save(save); + if (!status) + return 0; + + const int iCount = m_PanelOutputs.Count(); + save.WriteInt(&iCount); + for (int i = 0; i < iCount; i++) + { + CBaseEntityOutput* output = m_PanelOutputs[i]; + const int nElems = output->NumberOfElements(); + save.WriteString(m_PanelOutputs.GetElementName(i)); + save.WriteInt(&nElems); + if (!output->Save(save)) + return 0; + } + + return status; +} + +int CVGuiScreen::Restore(IRestore& restore) +{ + int status = BaseClass::Restore(restore); + if (!status) + return 0; + +#if MAPBASE_VER_INT < 8000 + // HACKHACK: Until v8.0, mark this screen as using the new save system to prevent existing saves with vgui_screen from crashing + if (!HasContext( "uses_new_save", "1" )) + return status; +#endif + + const int iCount = restore.ReadInt(); + m_PanelOutputs.EnsureCapacity(iCount); + for (int i = 0; i < iCount; i++) + { + char cName[MAX_KEY]; + restore.ReadString(cName, MAX_KEY, 0); + const int iIndex = m_PanelOutputs.Insert(cName, new COutputEvent); + const int nElems = restore.ReadInt(); + if (!m_PanelOutputs[iIndex]->Restore(restore, nElems)) + return 0; + } + + return status; +} + +// Handle a command from the client-side vgui panel. +bool CVGuiScreen::HandleEntityCommand(CBasePlayer* pClient, KeyValues* pKeyValues) +{ +#if defined(HL2MP) // Enable this in multiplayer. + // Restrict to commands from our owning player. + if ((m_fScreenFlags & VGUI_SCREEN_ONLY_USABLE_BY_OWNER) && pClient != m_hPlayerOwner.Get()) + return false; +#endif + + // Give the owning entity a chance to handle the command. + if (GetOwnerEntity() && GetOwnerEntity()->HandleEntityCommand(pClient, pKeyValues)) + return true; + + // See if we have an output for this command. + const int i = m_PanelOutputs.Find(pKeyValues->GetString()); + if (m_PanelOutputs.IsValidIndex(i)) + { + variant_t Val; + Val.Set(FIELD_VOID, NULL); + m_PanelOutputs[i]->FireOutput(Val, pClient, this); + return true; + } + + return false; +} + +CBaseEntityOutput* CVGuiScreen::FindNamedOutput(const char* pszOutput) +{ + if (pszOutput && pszOutput[0] == '~' && pszOutput[1]) + { + const int i = m_PanelOutputs.Find(pszOutput + 1); + if (m_PanelOutputs.IsValidIndex(i)) + return m_PanelOutputs[i]; + + return NULL; + } + + return BaseClass::FindNamedOutput(pszOutput); +} +#endif // MAPBASE + void CVGuiScreen::SetAttachmentIndex( int nIndex ) { m_nAttachmentIndex = nIndex; diff --git a/src/game/server/vguiscreen.h b/src/game/server/vguiscreen.h index cf720916..557cacd7 100644 --- a/src/game/server/vguiscreen.h +++ b/src/game/server/vguiscreen.h @@ -32,6 +32,15 @@ public: virtual void Activate(); virtual void OnRestore(); +#ifdef MAPBASE + ~CVGuiScreen(); + virtual int Save(ISave& save); + virtual int Restore(IRestore& restore); + + virtual bool HandleEntityCommand(CBasePlayer* pClient, KeyValues* pKeyValues); + virtual CBaseEntityOutput* FindNamedOutput(const char* pszOutput); +#endif // MAPBASE + const char *GetPanelName() const; // Sets the screen size + resolution @@ -75,6 +84,10 @@ private: CNetworkVar( int, m_fScreenFlags ); CNetworkVar( EHANDLE, m_hPlayerOwner ); +#ifdef MAPBASE + CUtlDict m_PanelOutputs; +#endif // MAPBASE + friend CVGuiScreen *CreateVGuiScreen( const char *pScreenClassname, const char *pScreenType, CBaseEntity *pAttachedTo, CBaseEntity *pOwner, int nAttachmentIndex ); }; diff --git a/src/game/server/vscript_server.cpp b/src/game/server/vscript_server.cpp index 43ff4f53..21fc36e6 100644 --- a/src/game/server/vscript_server.cpp +++ b/src/game/server/vscript_server.cpp @@ -1,4 +1,4 @@ -//========== Copyright � 2008, Valve Corporation, All rights reserved. ======== +//========== Copyright © 2008, Valve Corporation, All rights reserved. ======== // // Purpose: // @@ -51,6 +51,11 @@ #include "dota_animation.h" #endif +#ifdef MAPBASE_VSCRIPT +#include "world.h" +#include "mapbase/vscript_singletons.h" +#endif + extern ScriptClassDesc_t * GetScriptDesc( CBaseEntity * ); extern CServerGameDLL g_ServerGameDLL; @@ -76,6 +81,7 @@ ConVar script_break_in_native_debugger_on_error( "script_break_in_native_debugge #define VSCRIPT_CONVAR_ALLOWLIST_NAME "cfg/vscript_convar_allowlist.txt" +#ifndef MAPBASE_VSCRIPT /// Exposes convars to script class CScriptConvarAccessor : public CAutoGameSystem { @@ -262,6 +268,7 @@ DEFINE_SCRIPTFUNC( GetClientConvarValue, "GetClientConvarValue(name) : returns t DEFINE_SCRIPTFUNC( SetValue, "SetValue(name, value) : sets the value of the convar. The convar must be in " VSCRIPT_CONVAR_ALLOWLIST_NAME " to be set. Supported types are bool, int, float, string." ) DEFINE_SCRIPTFUNC( IsConVarOnAllowList, "IsConVarOnAllowList(name) : checks if the convar is allowed to be used and is in " VSCRIPT_CONVAR_ALLOWLIST_NAME ". Please be nice with this and use it for *compatibility* if you need check support and NOT to force server owners to allow hostname to be set... or else this will simply lie and return true in future. ;-) You have been warned!" ) END_SCRIPTDESC() +#endif //----------------------------------------------------------------------------- @@ -567,12 +574,24 @@ bool VScriptServerScriptErrorFunc( ScriptErrorLevel_t /*eLevel*/, const char *ps return true; } +#ifdef MAPBASE_VSCRIPT +static ScriptHook_t g_Hook_OnEntityCreated; +static ScriptHook_t g_Hook_OnEntitySpawned; +static ScriptHook_t g_Hook_OnEntityDeleted; +#endif + //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- -class CScriptEntityIterator : public IEntityFindFilter +class CScriptEntityIterator : public IEntityFindFilter, public IEntityListener { public: +#ifdef MAPBASE_VSCRIPT + HSCRIPT GetLocalPlayer() + { + return ToHScript( UTIL_GetLocalPlayerOrListenServerHost() ); + } +#endif HSCRIPT First() { return Next(NULL); } HSCRIPT Next( HSCRIPT hStartEntity ) @@ -655,10 +674,84 @@ public: return NULL; } +#ifdef MAPBASE_VSCRIPT + HSCRIPT FindByClassnameWithinBox( HSCRIPT hStartEntity , const char *szName, const Vector &vecMins, const Vector &vecMaxs ) + { + return ToHScript( gEntList.FindEntityByClassnameWithin( ToEnt( hStartEntity ), szName, vecMins, vecMaxs ) ); + } + + HSCRIPT FindByClassNearestFacing( const Vector &origin, const Vector &facing, float threshold, const char *classname ) + { + return ToHScript( gEntList.FindEntityClassNearestFacing( origin, facing, threshold, const_cast(classname) ) ); + } + + HSCRIPT FindByClassnameNearest2D( const char *szName, const Vector &vecSrc, float flRadius ) + { + return ToHScript( gEntList.FindEntityByClassnameNearest2D( szName, vecSrc, flRadius ) ); + } + + // + // Custom Procedurals + // + void AddCustomProcedural( const char *pszName, HSCRIPT hFunc, bool bCanReturnMultiple ) + { + gEntList.AddCustomProcedural( pszName, hFunc, bCanReturnMultiple ); + } + + void RemoveCustomProcedural( const char *pszName ) + { + gEntList.RemoveCustomProcedural( pszName ); + } + + void EnableEntityListening() + { + // Start getting entity updates! + gEntList.AddListenerEntity( this ); + } + + void DisableEntityListening() + { + // Stop getting entity updates! + gEntList.RemoveListenerEntity( this ); + } + + void OnEntityCreated( CBaseEntity *pEntity ) + { + if ( g_pScriptVM && GetScriptHookManager().IsEventHooked( "OnEntityCreated" ) ) + { + // entity + ScriptVariant_t args[] = { ScriptVariant_t( pEntity->GetScriptInstance() ) }; + g_Hook_OnEntityCreated.Call( NULL, NULL, args ); + } + }; + + void OnEntitySpawned( CBaseEntity *pEntity ) + { + if ( g_pScriptVM && GetScriptHookManager().IsEventHooked( "OnEntitySpawned" ) ) + { + // entity + ScriptVariant_t args[] = { ScriptVariant_t( pEntity->GetScriptInstance() ) }; + g_Hook_OnEntitySpawned.Call( NULL, NULL, args ); + } + }; + + void OnEntityDeleted( CBaseEntity *pEntity ) + { + if ( g_pScriptVM && GetScriptHookManager().IsEventHooked( "OnEntityDeleted" ) ) + { + // entity + ScriptVariant_t args[] = { ScriptVariant_t( pEntity->GetScriptInstance() ) }; + g_Hook_OnEntityDeleted.Call( NULL, NULL, args ); + } + }; +#endif private: } g_ScriptEntityIterator; BEGIN_SCRIPTDESC_ROOT_NAMED( CScriptEntityIterator, "CEntities", SCRIPT_SINGLETON "The global list of entities" ) +#ifdef MAPBASE_VSCRIPT + DEFINE_SCRIPTFUNC( GetLocalPlayer, "Get local player or listen server host" ) +#endif DEFINE_SCRIPTFUNC( First, "Begin an iteration over the list of entities" ) DEFINE_SCRIPTFUNC( Next, "Continue an iteration over the list of entities, providing reference to a previously found entity" ) DEFINE_SCRIPTFUNC( CreateByClassname, "Creates an entity by classname" ) @@ -672,6 +765,29 @@ BEGIN_SCRIPTDESC_ROOT_NAMED( CScriptEntityIterator, "CEntities", SCRIPT_SINGLETO DEFINE_SCRIPTFUNC( FindByClassnameNearest, "Find entities by class name nearest to a point." ) DEFINE_SCRIPTFUNC( FindByClassnameWithin, "Find entities by class name within a radius. Pass 'null' to start an iteration, or reference to a previously found entity to continue a search" ) DEFINE_SCRIPTFUNC( DispatchSpawn, "Dispatches spawn of an entity!" ) +#ifdef MAPBASE_VSCRIPT + DEFINE_SCRIPTFUNC( FindByClassnameWithinBox, "Find entities by class name within an AABB. Pass 'null' to start an iteration, or reference to a previously found entity to continue a search" ) + DEFINE_SCRIPTFUNC( FindByClassNearestFacing, "Find the nearest entity along the facing direction from the given origin within the angular threshold with the given classname." ) + DEFINE_SCRIPTFUNC( FindByClassnameNearest2D, "Find entities by class name nearest to a point in 2D space." ) + + DEFINE_SCRIPTFUNC( AddCustomProcedural, "Adds a custom '!' target name. The first parameter is the name of the procedural (which should NOT include the '!'), the second parameter is a function which should support 5 arguments (name, startEntity, searchingEntity, activator, caller), and the third parameter is whether or not this procedural can return multiple entities. Note that these are NOT saved and must be redeclared on restore!" ) + DEFINE_SCRIPTFUNC( RemoveCustomProcedural, "Removes a custom '!' target name previously defined with AddCustomProcedural." ) + + DEFINE_SCRIPTFUNC( EnableEntityListening, "Enables the 'OnEntity' hooks. This function must be called before using them." ) + DEFINE_SCRIPTFUNC( DisableEntityListening, "Disables the 'OnEntity' hooks." ) + + BEGIN_SCRIPTHOOK( g_Hook_OnEntityCreated, "OnEntityCreated", FIELD_VOID, "Called when an entity is created. Requires EnableEntityListening() to be fired beforehand." ) + DEFINE_SCRIPTHOOK_PARAM( "entity", FIELD_HSCRIPT ) + END_SCRIPTHOOK() + + BEGIN_SCRIPTHOOK( g_Hook_OnEntitySpawned, "OnEntitySpawned", FIELD_VOID, "Called when an entity spawns. Requires EnableEntityListening() to be fired beforehand." ) + DEFINE_SCRIPTHOOK_PARAM( "entity", FIELD_HSCRIPT ) + END_SCRIPTHOOK() + + BEGIN_SCRIPTHOOK( g_Hook_OnEntityDeleted, "OnEntityDeleted", FIELD_VOID, "Called when an entity is deleted. Requires EnableEntityListening() to be fired beforehand." ) + DEFINE_SCRIPTHOOK_PARAM( "entity", FIELD_HSCRIPT ) + END_SCRIPTHOOK() +#endif END_SCRIPTDESC(); CVScriptGameEventListener g_VScriptGameEventListener; @@ -824,6 +940,7 @@ bool RunScriptHook( const char *pszHookName, HSCRIPT params ) return g_VScriptGameEventListener.FireScriptHook( pszHookName, params ); } +#ifndef MAPBASE_VSCRIPT CNetPropManager g_ScriptNetPropManager; BEGIN_SCRIPTDESC_ROOT_NAMED( CNetPropManager, "CNetPropManager", SCRIPT_SINGLETON "Used to get/set entity network fields" ) @@ -857,6 +974,7 @@ BEGIN_SCRIPTDESC_ROOT_NAMED( CNetPropManager, "CNetPropManager", SCRIPT_SINGLETO DEFINE_SCRIPTFUNC( GetPropInfo, "Arguments: ( entity, propertyName, arrayElement, table ) - Fills in a passed table with property info for the provided entity" ) DEFINE_SCRIPTFUNC( GetTable, "Arguments: ( entity, iPropType, table ) - Fills in a passed table with all props of a specified type for the provided entity (set iPropType to 0 for SendTable or 1 for DataMap)" ) END_SCRIPTDESC() +#endif //----------------------------------------------------------------------------- // @@ -886,6 +1004,7 @@ BEGIN_SCRIPTDESC_ROOT_NAMED( CScriptPanorama, "CPanorama", SCRIPT_SINGLETON "Pan END_SCRIPTDESC(); #endif +#ifndef MAPBASE_VSCRIPT // ---------------------------------------------------------------------------- // KeyValues access - CBaseEntity::ScriptGetKeyFromModel returns root KeyValues // ---------------------------------------------------------------------------- @@ -980,7 +1099,7 @@ void CScriptKeyValues::ScriptReleaseKeyValues( ) // constructors -CScriptKeyValues::CScriptKeyValues( KeyValues *pKeyValues ) +CScriptKeyValues::CScriptKeyValues( KeyValues *pKeyValues = NULL ) { m_pKeyValues = pKeyValues; } @@ -994,6 +1113,7 @@ CScriptKeyValues::~CScriptKeyValues( ) } m_pKeyValues = NULL; } +#endif @@ -1016,6 +1136,18 @@ static float FrameTime() return gpGlobals->frametime; } +#ifdef MAPBASE_VSCRIPT +static int MaxPlayers() +{ + return gpGlobals->maxClients; +} + +static int GetLoadType() +{ + return gpGlobals->eLoadType; +} +#endif + static void SendToConsole( const char *pszCommand ) { if ( !pszCommand ) @@ -1024,7 +1156,11 @@ static void SendToConsole( const char *pszCommand ) CBasePlayer *pPlayer = UTIL_GetLocalPlayerOrListenServerHost(); if ( !pPlayer ) { +#ifdef MAPBASE + CGMsg( 1, CON_GROUP_VSCRIPT, "Cannot execute \"%s\", no player\n", pszCommand ); +#else DevMsg ("Cannot execute \"%s\", no player\n", pszCommand ); +#endif return; } @@ -1064,7 +1200,11 @@ static const char *DoUniqueString( const char *pszBase ) return szBuf; } +#ifdef MAPBASE_VSCRIPT +static int DoEntFire( const char *pszTarget, const char *pszAction, const char *pszValue, float delay, HSCRIPT hActivator, HSCRIPT hCaller ) +#else static void DoEntFire( const char *pszTarget, const char *pszAction, const char *pszValue, float delay, HSCRIPT hActivator, HSCRIPT hCaller ) +#endif { const char *target = "", *action = "Use"; variant_t value; @@ -1079,7 +1219,11 @@ static void DoEntFire( const char *pszTarget, const char *pszAction, const char // ent_fire point_servercommand command "rcon_password mynewpassword" if ( gpGlobals->maxClients > 1 && V_stricmp( target, "point_servercommand" ) == 0 ) { +#ifdef MAPBASE_VSCRIPT + return 0; +#else return; +#endif } if ( *pszAction ) @@ -1095,15 +1239,20 @@ static void DoEntFire( const char *pszTarget, const char *pszAction, const char delay = 0; } +#ifdef MAPBASE_VSCRIPT + return +#endif g_EventQueue.AddEvent( target, action, value, delay, ToEnt(hActivator), ToEnt(hCaller) ); } +#ifndef MAPBASE_VSCRIPT // See vscript_funcs_shared.cpp // Some game events pass entity's by their entindex. This lets scripts translate that // into a handle to the entity's script instance. HSCRIPT EntIndexToHScript( int entityIndex ) { return ToHScript( UTIL_EntityByIndex( entityIndex ) ); } +#endif HSCRIPT PlayerInstanceFromIndex( int idx ) { @@ -2225,7 +2374,11 @@ HSCRIPT CreateProp( const char *pszEntityName, const Vector &vOrigin, const char //-------------------------------------------------------------------------------------------------- // Use an entity's script instance to add an entity IO event (used for firing events on unnamed entities from vscript) //-------------------------------------------------------------------------------------------------- +#ifdef MAPBASE_VSCRIPT +static int DoEntFireByInstanceHandle( HSCRIPT hTarget, const char *pszAction, const char *pszValue, float delay, HSCRIPT hActivator, HSCRIPT hCaller ) +#else static void DoEntFireByInstanceHandle( HSCRIPT hTarget, const char *pszAction, const char *pszValue, float delay, HSCRIPT hActivator, HSCRIPT hCaller ) +#endif { const char *action = "Use"; variant_t value; @@ -2247,10 +2400,18 @@ static void DoEntFireByInstanceHandle( HSCRIPT hTarget, const char *pszAction, c if ( !pTarget ) { +#ifdef MAPBASE_VSCRIPT + CGWarning( 0, CON_GROUP_VSCRIPT, "VScript error: DoEntFire was passed an invalid entity instance.\n" ); + return 0; +#else Warning( "VScript error: DoEntFire was passed an invalid entity instance.\n" ); return; +#endif } +#ifdef MAPBASE_VSCRIPT + return +#endif g_EventQueue.AddEvent( pTarget, action, value, delay, ToEnt(hActivator), ToEnt(hCaller) ); } @@ -2270,6 +2431,18 @@ static float ScriptTraceLine( const Vector &vecStart, const Vector &vecEnd, HSCR } } +#ifdef MAPBASE_VSCRIPT +static bool CancelEntityIOEvent( int event ) +{ + return g_EventQueue.RemoveEvent(event); +} + +static float GetEntityIOEventTimeLeft( int event ) +{ + return g_EventQueue.GetTimeLeft(event); +} +#endif // MAPBASE_VSCRIPT + static float ScriptTraceLinePlayersIncluded( const Vector &vecStart, const Vector &vecEnd, HSCRIPT entIgnore ) { // UTIL_TraceLine( vecAbsStart, vecAbsEnd, MASK_BLOCKLOS, pLooker, COLLISION_GROUP_NONE, ptr ); @@ -2463,6 +2636,18 @@ bool VScriptServerInit() ScriptLanguage_t scriptLanguage = SL_DEFAULT; char const *pszScriptLanguage; +#ifdef MAPBASE_VSCRIPT + if (GetWorldEntity()->GetScriptLanguage() != SL_NONE) + { + // Allow world entity to override script language + scriptLanguage = GetWorldEntity()->GetScriptLanguage(); + + // Less than SL_NONE means the script language should literally be none + if (scriptLanguage < SL_NONE) + scriptLanguage = SL_NONE; + } + else +#endif if ( CommandLine()->CheckParm( "-scriptlang", &pszScriptLanguage ) ) { if( !Q_stricmp(pszScriptLanguage, "gamemonkey") ) @@ -2477,9 +2662,19 @@ bool VScriptServerInit() { scriptLanguage = SL_PYTHON; } +#ifdef MAPBASE_VSCRIPT + else if( !Q_stricmp(pszScriptLanguage, "lua") ) + { + scriptLanguage = SL_LUA; + } +#endif else { +#ifdef MAPBASE_VSCRIPT + CGWarning( 1, CON_GROUP_VSCRIPT, "-server_script does not recognize a language named '%s'. virtual machine did NOT start.\n", pszScriptLanguage ); +#else DevWarning("-server_script does not recognize a language named '%s'. virtual machine did NOT start.\n", pszScriptLanguage ); +#endif scriptLanguage = SL_NONE; } @@ -2496,10 +2691,27 @@ bool VScriptServerInit() if( g_pScriptVM ) { +#ifdef MAPBASE_VSCRIPT + CGMsg( 0, CON_GROUP_VSCRIPT, "VSCRIPT SERVER: Started VScript virtual machine using script language '%s'\n", g_pScriptVM->GetLanguageName() ); +#else Log_Msg( LOG_VScript, "VSCRIPT: Started VScript virtual machine using script language '%s'\n", g_pScriptVM->GetLanguageName() ); +#endif g_pScriptVM->SetErrorCallback( &VScriptServerScriptErrorFunc ); +#ifdef MAPBASE_VSCRIPT + GetScriptHookManager().OnInit(); +#endif + +#ifdef MAPBASE_VSCRIPT + // MULTIPLAYER + // NOTE: 'PlayerInstanceFromIndex' and 'GetPlayerFromUserID' are used in L4D2 and Source 2, + // but the GetPlayerBy* names are more consistent. + // ScriptRegisterFunctionNamed( g_pScriptVM, UTIL_PlayerByIndex, "GetPlayerByIndex", "PlayerInstanceFromIndex" ); + // ScriptRegisterFunctionNamed( g_pScriptVM, UTIL_PlayerByUserId, "GetPlayerByUserID", "GetPlayerFromUserID" ); + ScriptRegisterFunctionNamed( g_pScriptVM, UTIL_ShowMessageAll, "ShowMessage", "Print a hud message on all clients" ); +#endif + ScriptRegisterFunction( g_pScriptVM, SendToConsole, "Send a string to the console as a command" ); ScriptRegisterFunction( g_pScriptVM, SendToServerConsole, "Send a string that gets executed on the server as a ServerCommand. Respects sv_allow_point_servercommand." ); ScriptRegisterFunctionNamed( g_pScriptVM, SendToServerConsole, "SendToConsoleServer", "Copy of SendToServerConsole with another name for compat." ); @@ -2509,7 +2721,9 @@ bool VScriptServerInit() ScriptRegisterFunction( g_pScriptVM, DoIncludeScript, "Execute a script (internal)" ); ScriptRegisterFunction( g_pScriptVM, RegisterScriptGameEventListener, "Register as a listener for a game event from script." ); ScriptRegisterFunction( g_pScriptVM, RegisterScriptHookListener, "Register as a listener for a script hook from script." ); +#ifndef MAPBASE_VSCRIPT ScriptRegisterFunction( g_pScriptVM, EntIndexToHScript, "Turn an entity index integer to an HScript representing that entity's script instance." ); +#endif ScriptRegisterFunction( g_pScriptVM, PlayerInstanceFromIndex, "Get a script instance of a player by index." ); ScriptRegisterFunctionNamed( g_pScriptVM, ScriptFireGameEvent, "FireGameEvent", "Fire a game event to a listening callback function in script. Parameters are passed in a squirrel table." ); ScriptRegisterFunctionNamed( g_pScriptVM, ScriptFireScriptHook, "FireScriptHook", "Fire a script hoook to a listening callback function in script. Parameters are passed in a squirrel table." ); @@ -2590,10 +2804,29 @@ bool VScriptServerInit() ScriptRegisterFunctionNamed( g_pScriptVM, ScriptTraceLine, "TraceLine", "given 2 points & ent to ignore, return fraction along line that hits world or models" ); ScriptRegisterFunctionNamed( g_pScriptVM, ScriptTraceLinePlayersIncluded, "TraceLinePlayersIncluded", "given 2 points & ent to ignore, return fraction along line that hits world, models, players or npcs" ); + ScriptRegisterFunction( g_pScriptVM, Time, "Get the current server time" ); ScriptRegisterFunction( g_pScriptVM, FrameTime, "Get the time spent on the server in the last frame" ); ScriptRegisterFunction( g_pScriptVM, MaxClients, "Get the current number of max clients set by the maxplayers command." ); +#ifdef MAPBASE_VSCRIPT + ScriptRegisterFunction( g_pScriptVM, MaxPlayers, "Get the maximum number of players allowed on this server" ); + ScriptRegisterFunction( g_pScriptVM, GetLoadType, "Get the way the current game was loaded (corresponds to the MapLoad enum)" ); + ScriptRegisterFunction( g_pScriptVM, DoEntFire, SCRIPT_ALIAS( "EntFire", "Generate an entity i/o event" ) ); + ScriptRegisterFunction( g_pScriptVM, DoEntFireByInstanceHandle, SCRIPT_ALIAS( "EntFireByHandle", "Generate an entity i/o event. First parameter is an entity instance." ) ); + // ScriptRegisterFunction( g_pScriptVM, IsValidEntity, "Returns true if the entity is valid." ); + + ScriptRegisterFunction( g_pScriptVM, CancelEntityIOEvent, "Remove entity I/O event." ); + ScriptRegisterFunction( g_pScriptVM, GetEntityIOEventTimeLeft, "Get time left on entity I/O event." ); +#else ScriptRegisterFunctionNamed( g_pScriptVM, DoEntFireByInstanceHandle, "EntFireByHandle", "Generate and entity i/o event. First parameter is an entity instance." ); ScriptRegisterFunctionNamed( g_pScriptVM, ScriptCreateSceneEntity, "CreateSceneEntity", "Create a scene entity to play the specified scene." ); +#endif + ScriptRegisterFunction( g_pScriptVM, DoUniqueString, SCRIPT_ALIAS( "UniqueString", "Generate a string guaranteed to be unique across the life of the script VM, with an optional root string. Useful for adding data to tables when not sure what keys are already in use in that table." ) ); + ScriptRegisterFunctionNamed( g_pScriptVM, ScriptCreateSceneEntity, "CreateSceneEntity", "Create a scene entity to play the specified scene." ); +#ifndef MAPBASE_VSCRIPT + ScriptRegisterFunctionNamed( g_pScriptVM, NDebugOverlay::Box, "DebugDrawBox", "Draw a debug overlay box" ); + ScriptRegisterFunctionNamed( g_pScriptVM, NDebugOverlay::Line, "DebugDrawLine", "Draw a debug overlay box" ); +#endif + ScriptRegisterFunction( g_pScriptVM, DoIncludeScript, "Execute a script (internal)" ); ScriptRegisterFunction( g_pScriptVM, CreateProp, "Create a physics prop" ); //ScriptRegisterFunctionNamed( g_pScriptVM, DoRecordAchievementEvent, "RecordAchievementEvent", "Records achievement event or progress" ); ScriptRegisterFunction( g_pScriptVM, GetDeveloperLevel, "Gets the level of 'developer'" ); @@ -2611,6 +2844,14 @@ bool VScriptServerInit() #endif g_pScriptVM->RegisterAllClasses(); +#ifdef MAPBASE_VSCRIPT + g_pScriptVM->RegisterAllEnums(); + + IGameSystem::RegisterVScriptAllSystems(); + + RegisterSharedScriptConstants(); + RegisterSharedScriptFunctions(); +#endif if ( GameRules() ) { @@ -2624,9 +2865,12 @@ bool VScriptServerInit() #if 0 g_pScriptVM->RegisterInstance( &g_ScriptPanorama, "Panorama" ); #endif - g_pScriptVM->RegisterInstance( &g_ScriptConvars, "Convars" ) ; + g_pScriptVM->RegisterInstance( &g_ScriptEntityOutputs, "EntityOutputs" ); +#ifndef MAPBASE_VSCRIPT + g_pScriptVM->RegisterInstance( &g_ScriptConvars, "Convars" ) ; g_pScriptVM->RegisterInstance( &g_ScriptNetPropManager, "NetProps" ); +#endif ScriptVariant_t vConstantsTable; g_pScriptVM->CreateTable( vConstantsTable ); @@ -3495,6 +3739,7 @@ DECLARE_SCRIPT_CONST( Server, DIST_EPSILON ) DECLARE_SCRIPT_CONST_NAMED( Server, "ConstantNamingConvention", "Constants are named as follows: F -> flags, E -> enums, (nothing) -> random values/constants" ) REGISTER_SCRIPT_CONST_TABLE( Server ) #endif + g_pScriptVM->SetValue( "Constants", vConstantsTable ); if ( scriptLanguage == SL_SQUIRREL ) @@ -3503,8 +3748,17 @@ REGISTER_SCRIPT_CONST_TABLE( Server ) } g_VScriptGameEventListener.Init(); + VScriptRunScript( "vscript_server", true ); VScriptRunScript( "mapspawn", false ); +#ifdef MAPBASE_VSCRIPT + RunAddonScripts(); + + // Since the world entity spawns before VScript is initted, RunVScripts() is called before the VM has started, so no scripts are run. + // This gets around that by calling the same function right after the VM is initted. + GetWorldEntity()->RunVScripts(); +#endif + if ( script_connect_debugger_on_mapspawn.GetBool() ) { g_pScriptVM->ConnectDebugger(); @@ -3516,13 +3770,27 @@ REGISTER_SCRIPT_CONST_TABLE( Server ) } else { +#ifdef MAPBASE_VSCRIPT DevWarning("VM Did not start!\n"); +#else + CGWarning( 1, CON_GROUP_VSCRIPT, "VM Did not start!\n" ); +#endif } } +#ifdef MAPBASE_VSCRIPT + else + { + CGMsg( 0, CON_GROUP_VSCRIPT, "VSCRIPT SERVER: Not starting because language is set to 'none'\n" ); + } +#endif } else { +#ifdef MAPBASE_VSCRIPT + CGMsg( 0, CON_GROUP_VSCRIPT, "\nVSCRIPT: Scripting is disabled.\n" ); +#else Log_Msg( LOG_VScript, "\nVSCRIPT: Scripting is disabled.\n" ); +#endif } g_pScriptVM = NULL; return false; @@ -3866,13 +4134,21 @@ CON_COMMAND( script_reload_code, "Execute a vscript file, replacing existing fun if ( !*args[1] ) { +#ifdef MAPBASE_VSCRIPT + CGWarning( 0, CON_GROUP_VSCRIPT, "No script specified\n" ); +#else Log_Warning( LOG_VScript, "No script specified\n" ); +#endif return; } if ( !g_pScriptVM ) { +#ifdef MAPBASE_VSCRIPT + CGWarning( 0, CON_GROUP_VSCRIPT, "No script specified\n" ); +#else Log_Warning( LOG_VScript, "Scripting disabled or no server running\n" ); +#endif return; } @@ -3894,7 +4170,11 @@ CON_COMMAND( script_reload_entity_code, "Execute all of this entity's VScripts, if ( !g_pScriptVM ) { +#ifdef MAPBASE_VSCRIPT + CGWarning( 0, CON_GROUP_VSCRIPT, "No script specified\n" ); +#else Log_Warning( LOG_VScript, "Scripting disabled or no server running\n" ); +#endif return; } @@ -3935,7 +4215,11 @@ CON_COMMAND( script_reload_think, "Execute an activation script, replacing exist if ( !g_pScriptVM ) { +#ifdef MAPBASE_VSCRIPT + CGWarning( 0, CON_GROUP_VSCRIPT, "No script specified\n" ); +#else Log_Warning( LOG_VScript, "Scripting disabled or no server running\n" ); +#endif return; } @@ -3979,6 +4263,13 @@ public: virtual void LevelShutdownPostEntity( void ) { ClearScriptGameEventListeners(); +#ifdef MAPBASE_VSCRIPT + g_ScriptEntityIterator.DisableEntityListening(); + + g_ScriptNetMsg->LevelShutdownPreVM(); + + GetScriptHookManager().OnShutdown(); +#endif VScriptServerTerm(); } @@ -3995,176 +4286,20 @@ public: CVScriptGameSystem g_VScriptGameSystem; +#ifdef MAPBASE_VSCRIPT +ConVar script_allow_entity_creation_midgame( "script_allow_entity_creation_midgame", "1", FCVAR_NOT_CONNECTED, "Allows VScript files to create entities mid-game, as opposed to only creating entities on startup." ); +#endif + bool IsEntityCreationAllowedInScripts( void ) { +#ifdef MAPBASE_VSCRIPT + if (script_allow_entity_creation_midgame.GetBool()) + return true; +#endif + return g_VScriptGameSystem.m_bAllowEntityCreationInScripts; } -static short VSCRIPT_SERVER_SAVE_RESTORE_VERSION = 2; - - -//----------------------------------------------------------------------------- - -class CVScriptSaveRestoreBlockHandler : public CDefSaveRestoreBlockHandler -{ -public: - CVScriptSaveRestoreBlockHandler() : - m_InstanceMap( DefLessFunc(const char *) ) - { - } - const char *GetBlockName() - { - return "VScriptServer"; - } - - //--------------------------------- - - void Save( ISave *pSave ) - { - pSave->StartBlock(); - - int temp = g_pScriptVM != NULL; - pSave->WriteInt( &temp ); - if ( g_pScriptVM ) - { - temp = g_pScriptVM->GetLanguage(); - pSave->WriteInt( &temp ); - CUtlBuffer buffer; - g_pScriptVM->WriteState( &buffer ); - temp = buffer.TellPut(); - pSave->WriteInt( &temp ); - if ( temp > 0 ) - { - pSave->WriteData( (const char *)buffer.Base(), temp ); - } - } - - pSave->EndBlock(); - } - - //--------------------------------- - - void WriteSaveHeaders( ISave *pSave ) - { - pSave->WriteShort( &VSCRIPT_SERVER_SAVE_RESTORE_VERSION ); - } - - //--------------------------------- - - void ReadRestoreHeaders( IRestore *pRestore ) - { - // No reason why any future version shouldn't try to retain backward compatability. The default here is to not do so. - short version; - pRestore->ReadShort( &version ); - m_fDoLoad = ( version == VSCRIPT_SERVER_SAVE_RESTORE_VERSION ); - } - - //--------------------------------- - - void Restore( IRestore *pRestore, bool createPlayers ) - { - if ( !m_fDoLoad && g_pScriptVM ) - { - return; - } - CBaseEntity *pEnt = gEntList.FirstEnt(); - while ( pEnt ) - { - if ( pEnt->m_iszScriptId != NULL_STRING ) - { - g_pScriptVM->RegisterClass( pEnt->GetScriptDesc() ); - m_InstanceMap.Insert( STRING( pEnt->m_iszScriptId ), pEnt ); - } - pEnt = gEntList.NextEnt( pEnt ); - } - - pRestore->StartBlock(); - if ( pRestore->ReadInt() && pRestore->ReadInt() == g_pScriptVM->GetLanguage() ) - { - int nBytes = pRestore->ReadInt(); - if ( nBytes > 0 ) - { - CUtlBuffer buffer; - buffer.EnsureCapacity( nBytes ); - pRestore->ReadData( (char *)buffer.AccessForDirectRead( nBytes ), nBytes, 0 ); - g_pScriptVM->ReadState( &buffer ); - } - } - pRestore->EndBlock(); - } - - void PostRestore( void ) - { - for ( int i = m_InstanceMap.FirstInorder(); i != m_InstanceMap.InvalidIndex(); i = m_InstanceMap.NextInorder( i ) ) - { - CBaseEntity *pEnt = m_InstanceMap[i]; - if ( pEnt->m_hScriptInstance ) - { - ScriptVariant_t variant; - if ( g_pScriptVM->GetValue( STRING(pEnt->m_iszScriptId), &variant ) && variant.GetType() == FIELD_HSCRIPT) - { - pEnt->m_ScriptScope.Init( variant, false ); - pEnt->RunPrecacheScripts(); - } - } - else - { - // Script system probably has no internal references - pEnt->m_iszScriptId = NULL_STRING; - } - } - m_InstanceMap.Purge(); - } - - - CUtlMap m_InstanceMap; - -private: - bool m_fDoLoad; -}; - -//----------------------------------------------------------------------------- - -CVScriptSaveRestoreBlockHandler g_VScriptSaveRestoreBlockHandler; - -//------------------------------------- - -ISaveRestoreBlockHandler *GetVScriptSaveRestoreBlockHandler() -{ - return &g_VScriptSaveRestoreBlockHandler; -} - -//----------------------------------------------------------------------------- - -bool CBaseEntityScriptInstanceHelper::ToString( void *p, char *pBuf, int bufSize ) -{ - CBaseEntity *pEntity = (CBaseEntity *)p; - if ( pEntity->GetEntityName() != NULL_STRING ) - { - V_snprintf( pBuf, bufSize, "([%d] %s: %s)", pEntity->entindex(), STRING(pEntity->m_iClassname), STRING( pEntity->GetEntityName() ) ); - } - else - { - V_snprintf( pBuf, bufSize, "([%d] %s)", pEntity->entindex(), STRING(pEntity->m_iClassname) ); - } - return true; -} - -void *CBaseEntityScriptInstanceHelper::BindOnRead( HSCRIPT hInstance, void *pOld, const char *pszId ) -{ - int iEntity = g_VScriptSaveRestoreBlockHandler.m_InstanceMap.Find( pszId ); - if ( iEntity != g_VScriptSaveRestoreBlockHandler.m_InstanceMap.InvalidIndex() ) - { - CBaseEntity *pEnt = g_VScriptSaveRestoreBlockHandler.m_InstanceMap[iEntity]; - pEnt->m_hScriptInstance = hInstance; - return pEnt; - } - return NULL; -} - - -CBaseEntityScriptInstanceHelper g_BaseEntityScriptInstanceHelper; - #ifdef TF_DLL bool CNavAreaScriptInstanceHelper::ToString( void *p, char *pBuf, int bufSize ) { diff --git a/src/game/server/vscript_server.h b/src/game/server/vscript_server.h index cc74ea79..97000061 100644 --- a/src/game/server/vscript_server.h +++ b/src/game/server/vscript_server.h @@ -17,19 +17,7 @@ #pragma once #endif -class ISaveRestoreBlockHandler; - bool VScriptServerReplaceClosures( const char *pszScriptName, HSCRIPT hScope, bool bWarnMissing = false ); -ISaveRestoreBlockHandler *GetVScriptSaveRestoreBlockHandler(); - - -class CBaseEntityScriptInstanceHelper : public IScriptInstanceHelper -{ - bool ToString( void *p, char *pBuf, int bufSize ); - void *BindOnRead( HSCRIPT hInstance, void *pOld, const char *pszId ); -}; - -extern CBaseEntityScriptInstanceHelper g_BaseEntityScriptInstanceHelper; #ifdef TF_DLL class CNavAreaScriptInstanceHelper : public IScriptInstanceHelper @@ -50,6 +38,7 @@ extern INextBotComponentScriptInstanceHelper g_NextBotComponentScriptInstanceHel // Only allow scripts to create entities during map initialization bool IsEntityCreationAllowedInScripts( void ); +#ifndef MAPBASE_VSCRIPT // Mapbase adds this to the base library so that CScriptKeyValues can be accessed anywhere, like VBSP. // ---------------------------------------------------------------------------- // KeyValues access // ---------------------------------------------------------------------------- @@ -71,6 +60,7 @@ public: KeyValues *m_pKeyValues; // actual KeyValue entity }; +#endif class CVScriptGameEventListener : public CGameEventListener { diff --git a/src/game/server/vscript_server.nut b/src/game/server/vscript_server.nut index ece2c9ff..12b54a93 100644 --- a/src/game/server/vscript_server.nut +++ b/src/game/server/vscript_server.nut @@ -1,32 +1,87 @@ -//========== Copyright © 2008, Valve Corporation, All rights reserved. ======== +static char g_Script_vscript_client[] = R"vscript( +//========== Copyright © 2008, Valve Corporation, All rights reserved. ======== // // Purpose: // //============================================================================= +local DoEntFire = DoEntFire +local DoEntFireByInstanceHandle = DoEntFireByInstanceHandle +local DoDispatchParticleEffect = DoDispatchParticleEffect +local DoUniqueString = DoUniqueString + function UniqueString( string = "" ) { return DoUniqueString( string.tostring() ); } -function EntFire( target, action, value = null, delay = 0.0, activator = null ) +function EntFire( target, action, value = null, delay = 0.0, activator = null, caller = null ) { if ( !value ) { value = ""; } - - local caller = null; + if ( "self" in this ) { - caller = self; + if ( !caller ) + { + caller = self; + } + if ( !activator ) { activator = self; } } - - DoEntFire( target.tostring(), action.tostring(), value.tostring(), delay, activator, caller ); + + return DoEntFire( "" + target, "" + action, "" + value, delay, activator, caller ); +} + +function EntFireByHandle( target, action, value = null, delay = 0.0, activator = null, caller = null ) +{ + if ( !value ) + { + value = ""; + } + + if ( "self" in this ) + { + if ( !caller ) + { + caller = self; + } + + if ( !activator ) + { + activator = self; + } + } + + return DoEntFireByInstanceHandle( target, "" + action, "" + value, delay, activator, caller ); +} + +function DispatchParticleEffect( particleName, origin, angles, entity = null ) +{ + return DoDispatchParticleEffect( particleName, origin, angles, entity ); +} + +function ImpulseScale( flTargetMass, flDesiredSpeed ) +{ + return flTargetMass * flDesiredSpeed; +} +__Documentation.RegisterHelp( "ImpulseScale", "float ImpulseScale(float, float)", "Returns an impulse scale required to push an object." ); + +local PrecacheModel = PrecacheModel +function PrecacheModel( a, b = true ) +{ + return PrecacheModel( a, b ) +} + +local PrecacheOther = PrecacheOther +function PrecacheOther( a, b = "" ) +{ + PrecacheOther( a, b ) } function __ReplaceClosures( script, scope ) @@ -35,11 +90,11 @@ function __ReplaceClosures( script, scope ) { scope = getroottable(); } - + local tempParent = { getroottable = function() { return null; } }; local temp = { runscript = script }; - temp.setdelegate( tempParent ); - + temp.setdelegate(tempParent); + temp.runscript() foreach( key,val in temp ) { @@ -57,22 +112,24 @@ We're not suing the auto-connecting of outputs, always calling ConnectOuput expl The regexp object doesn't save/load properly and causes a crash when used to match after a save/load. Instead of fixing this, we're disabling the feature. If this class of problem comes up more we might revisit, otherwise we'll leave if off and broken. - -__OutputsPattern <- regexp("^On.*Output$"); - -function ConnectOutputs( table ) +*/ +if (!VSCRIPT_PRIORITIZE_TF2_SYNTAX) { - const nCharsToStrip = 6; - foreach( key, val in table ) + local __OutputsPattern = regexp("^On.*Output$"); + + function ConnectOutputs( table ) { - if ( typeof( val ) == "function" && __OutputsPattern.match( key ) ) + local nCharsToStrip = 6; + foreach( key, val in table ) { - //printl(key.slice( 0, nCharsToStrip ) ); - table.self.ConnectOutput( key.slice( 0, key.len() - nCharsToStrip ), key ); + if ( typeof( val ) == "function" && __OutputsPattern.match( key ) ) + { + //printl(key.slice( 0, nCharsToStrip ) ); + table.self.ConnectOutput( key.slice( 0, key.len() - nCharsToStrip ), key ); + } } } } -*/ function IncludeScript( name, scope = null ) { @@ -721,3 +778,4 @@ function EndScriptDebug() } //----------------------------------------------------------------------------- +)vscript"; diff --git a/src/game/server/wcedit.cpp b/src/game/server/wcedit.cpp index 22eb5259..99219cc6 100644 --- a/src/game/server/wcedit.cpp +++ b/src/game/server/wcedit.cpp @@ -450,6 +450,19 @@ Vector *g_EntityPositions = NULL; QAngle *g_EntityOrientations = NULL; string_t *g_EntityClassnames = NULL; +#ifdef MAPBASE // VDC Memory Leak Fixes +class GlobalCleanUp : public CAutoGameSystem +{ + void Shutdown() + { + delete [] g_EntityPositions; + delete [] g_EntityOrientations; + delete [] g_EntityClassnames; + delete this; + } +}; +#endif + //----------------------------------------------------------------------------- // Purpose: Saves the entity's position for future communication with Hammer //----------------------------------------------------------------------------- @@ -460,6 +473,9 @@ void NWCEdit::RememberEntityPosition( CBaseEntity *pEntity ) if ( !g_EntityPositions ) { +#ifdef MAPBASE // VDC Memory Leak Fixes + new GlobalCleanUp(); +#endif g_EntityPositions = new Vector[NUM_ENT_ENTRIES]; g_EntityOrientations = new QAngle[NUM_ENT_ENTRIES]; // have to save these too because some entities change the classname on spawn (e.g. prop_physics_override, physics_prop) diff --git a/src/game/server/world.cpp b/src/game/server/world.cpp index 55c1e7b5..fc3d59ae 100644 --- a/src/game/server/world.cpp +++ b/src/game/server/world.cpp @@ -376,6 +376,9 @@ BEGIN_DATADESC( CWorld ) // keyvalues are parsed from map, but not saved/loaded DEFINE_KEYFIELD( m_iszChapterTitle, FIELD_STRING, "chaptertitle" ), +#ifdef MAPBASE + DEFINE_KEYFIELD( m_bChapterTitleNoMessage, FIELD_BOOLEAN, "chaptertitlenomessage" ), +#endif DEFINE_KEYFIELD( m_bStartDark, FIELD_BOOLEAN, "startdark" ), DEFINE_KEYFIELD( m_bDisplayTitle, FIELD_BOOLEAN, "gametitle" ), DEFINE_FIELD( m_WorldMins, FIELD_VECTOR ), @@ -390,8 +393,16 @@ BEGIN_DATADESC( CWorld ) DEFINE_KEYFIELD( m_flMaxPropScreenSpaceWidth, FIELD_FLOAT, "maxpropscreenwidth" ), DEFINE_KEYFIELD( m_flMinPropScreenSpaceWidth, FIELD_FLOAT, "minpropscreenwidth" ), DEFINE_KEYFIELD( m_iszDetailSpriteMaterial, FIELD_STRING, "detailmaterial" ), +#ifdef MAPBASE_VSCRIPT + DEFINE_KEYFIELD( m_iScriptLanguage, FIELD_INTEGER, "vscriptlanguage" ), + //DEFINE_KEYFIELD( m_iScriptLanguageClient, FIELD_INTEGER, "vscriptlanguage_client" ), +#endif DEFINE_KEYFIELD( m_bColdWorld, FIELD_BOOLEAN, "coldworld" ), +#ifdef MAPBASE + DEFINE_INPUTFUNC( FIELD_STRING, "SetChapterTitle", InputSetChapterTitle ), +#endif + END_DATADESC() @@ -407,6 +418,9 @@ IMPLEMENT_SERVERCLASS_ST(CWorld, DT_WORLD) SendPropFloat (SENDINFO(m_flMinPropScreenSpaceWidth), 0, SPROP_NOSCALE ), SendPropStringT (SENDINFO(m_iszDetailSpriteMaterial) ), SendPropInt (SENDINFO(m_bColdWorld), 1, SPROP_UNSIGNED ), +#ifdef MAPBASE + SendPropStringT (SENDINFO(m_iszChapterTitle) ), +#endif END_SEND_TABLE() // @@ -449,7 +463,7 @@ bool CWorld::KeyValue( const char *szKeyName, const char *szValue ) extern bool g_fGameOver; -static CWorld *g_WorldEntity = NULL; +CWorld *g_WorldEntity = NULL; CWorld* GetWorldEntity() { @@ -466,6 +480,11 @@ CWorld::CWorld( ) SetSolid( SOLID_BSP ); SetMoveType( MOVETYPE_NONE ); +#ifdef MAPBASE_VSCRIPT + m_iScriptLanguage = SL_NONE; + //m_iScriptLanguageClient = -2; +#endif + m_bColdWorld = false; } @@ -678,6 +697,23 @@ void CWorld::Precache( void ) // Call all registered precachers. CPrecacheRegister::Precache(); +#ifdef MAPBASE + if ( m_iszChapterTitle.Get() != NULL_STRING && !m_bChapterTitleNoMessage ) + { + DevMsg( 2, "Chapter title: %s\n", STRING(m_iszChapterTitle.Get()) ); + CMessage *pMessage = (CMessage *)CBaseEntity::Create( "env_message", vec3_origin, vec3_angle, NULL ); + if ( pMessage ) + { + pMessage->SetMessage( m_iszChapterTitle.Get() ); + m_iszChapterTitle.Set( NULL_STRING ); + + // send the message entity a play message command, delayed by 1 second + pMessage->AddSpawnFlags( SF_MESSAGE_ONCE ); + pMessage->SetThink( &CMessage::SUB_CallUseToggle ); + pMessage->SetNextThink( gpGlobals->curtime + 1.0f ); + } + } +#else if ( m_iszChapterTitle != NULL_STRING ) { DevMsg( 2, "Chapter title: %s\n", STRING(m_iszChapterTitle) ); @@ -693,6 +729,7 @@ void CWorld::Precache( void ) pMessage->SetNextThink( gpGlobals->curtime + 1.0f ); } } +#endif g_iszFuncBrushClassname = AllocPooledString("func_brush"); } @@ -731,3 +768,10 @@ bool CWorld::IsColdWorld( void ) { return m_bColdWorld; } + +#ifdef MAPBASE +void CWorld::InputSetChapterTitle( inputdata_t &inputdata ) +{ + m_iszChapterTitle.Set( inputdata.value.StringID() ); +} +#endif diff --git a/src/game/server/world.h b/src/game/server/world.h index a8547d3c..5d75a64b 100644 --- a/src/game/server/world.h +++ b/src/game/server/world.h @@ -52,10 +52,32 @@ public: bool IsColdWorld( void ); +#ifdef MAPBASE + inline const char *GetChapterTitle() + { + return STRING(m_iszChapterTitle.Get()); + } + + void InputSetChapterTitle( inputdata_t &inputdata ); +#endif + +#ifdef MAPBASE_VSCRIPT + ScriptLanguage_t GetScriptLanguage() { return (ScriptLanguage_t)(m_iScriptLanguage); } +#endif + private: DECLARE_DATADESC(); +#ifdef MAPBASE + // Now needs to show up on the client for RPC + CNetworkVar( string_t, m_iszChapterTitle ); + + // Suppresses m_iszChapterTitle's env_message creation, + // allowing it to only be used for saves and RPC + bool m_bChapterTitleNoMessage; +#else string_t m_iszChapterTitle; +#endif CNetworkVar( float, m_flWaveHeight ); CNetworkVector( m_WorldMins ); @@ -66,6 +88,11 @@ private: CNetworkVar( float, m_flMaxPropScreenSpaceWidth ); CNetworkVar( string_t, m_iszDetailSpriteMaterial ); +#ifdef MAPBASE_VSCRIPT + int m_iScriptLanguage; + //CNetworkVar( int, m_iScriptLanguageClient ); // Now entirely on client +#endif + // start flags CNetworkVar( bool, m_bStartDark ); CNetworkVar( bool, m_bColdWorld ); diff --git a/src/game/shared/GameEventListener.h b/src/game/shared/GameEventListener.h index d77b8098..9b0b41bc 100644 --- a/src/game/shared/GameEventListener.h +++ b/src/game/shared/GameEventListener.h @@ -25,7 +25,7 @@ public: { } - ~CGameEventListener() + virtual ~CGameEventListener() { StopListeningForAllEvents(); } diff --git a/src/game/shared/Multiplayer/multiplayer_animstate.h b/src/game/shared/Multiplayer/multiplayer_animstate.h index b932d8a1..e3f5c42b 100644 --- a/src/game/shared/Multiplayer/multiplayer_animstate.h +++ b/src/game/shared/Multiplayer/multiplayer_animstate.h @@ -299,7 +299,6 @@ protected: // Pose parameters. bool m_bPoseParameterInit; MultiPlayerPoseData_t m_PoseParameterData; - DebugPlayerAnimData_t m_DebugAnimData; bool m_bCurrentFeetYawInitialized; float m_flLastAnimationStateClearTime; @@ -344,6 +343,14 @@ protected: // movement playback options int m_nMovementSequence; LegAnimType_t m_LegAnimType; + + //Tony; moved debuganim data to a private block and made the 2 sdk animstates friendly. I override the base classes + //but want complete functionality. +private: + friend class CSDKPlayerAnimState; + friend class CHL2MPPlayerAnimState; + DebugPlayerAnimData_t m_DebugAnimData; + }; // If this is set, then the game code needs to make sure to send player animation events diff --git a/src/game/shared/SoundEmitterSystem.cpp b/src/game/shared/SoundEmitterSystem.cpp index 2c93c491..0dc0c7e0 100644 --- a/src/game/shared/SoundEmitterSystem.cpp +++ b/src/game/shared/SoundEmitterSystem.cpp @@ -128,6 +128,25 @@ void Hack_FixEscapeChars( char *str ) Q_strncpy( str, osave, len ); } +#ifdef MAPBASE +static const ConVar *pHostTimescale; +static ConVar host_pitchscale( "host_pitchscale", "-1", FCVAR_REPLICATED, "If greater than 0, controls the pitch scale of sounds instead of host_timescale." ); + +static float GetSoundPitchScale() +{ + static ConVarRef sv_cheats( "sv_cheats" ); + if (sv_cheats.GetBool()) + { + if (host_pitchscale.GetFloat() > 0.0f) + return host_pitchscale.GetFloat(); + else + return pHostTimescale->GetFloat(); + } + + return 1.0f; +} +#endif + //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- @@ -324,6 +343,10 @@ public: } } #endif + +#ifdef MAPBASE + pHostTimescale = cvar->FindVar( "host_timescale" ); +#endif } virtual void LevelInitPostEntity() @@ -520,7 +543,11 @@ public: params.volume, (soundlevel_t)params.soundlevel, ep.m_nFlags, +#ifdef MAPBASE + params.pitch * GetSoundPitchScale(), +#else params.pitch, +#endif ep.m_nSpecialDSP, ep.m_pOrigin, NULL, @@ -593,7 +620,11 @@ public: ep.m_flVolume, ep.m_SoundLevel, ep.m_nFlags, +#ifdef MAPBASE + ep.m_nPitch * GetSoundPitchScale(), +#else ep.m_nPitch, +#endif ep.m_nSpecialDSP, ep.m_pOrigin, NULL, @@ -809,6 +840,10 @@ public: params.volume = flVolume; } +#ifdef MAPBASE + params.pitch *= GetSoundPitchScale(); +#endif + #if defined( CLIENT_DLL ) enginesound->EmitAmbientSound( params.soundname, params.volume, params.pitch, iFlags, soundtime ); #else @@ -931,6 +966,10 @@ public: if ( pSample && ( Q_stristr( pSample, ".wav" ) || Q_stristr( pSample, ".mp3" )) ) { +#ifdef MAPBASE + pitch *= GetSoundPitchScale(); +#endif + #if defined( CLIENT_DLL ) enginesound->EmitAmbientSound( pSample, volume, pitch, flags, soundtime ); #else @@ -939,7 +978,11 @@ public: if ( duration ) { - *duration = enginesound->GetSoundDuration( pSample ); + if ( Q_stristr( pSample, ".mp3" ) ) { + *duration = 0; + } else { + *duration = enginesound->GetSoundDuration( pSample ); + } } TraceEmitSound( "EmitAmbientSound: Raw wave emitted '%s' (ent %i)\n", @@ -1141,7 +1184,7 @@ void CBaseEntity::EmitSound( const char *soundname, float soundtime /*= 0.0f*/, EmitSound( filter, entindex(), params ); } -#if !defined ( CLIENT_DLL ) +#if !defined ( CLIENT_DLL ) || defined( MAPBASE_VSCRIPT ) void CBaseEntity::ScriptEmitSound( const char *soundname ) { EmitSound( soundname ); @@ -1359,6 +1402,52 @@ int SENTENCEG_Lookup(const char *sample) } #endif +#if defined(MAPBASE) && defined(GAME_DLL) +//----------------------------------------------------------------------------- +// Purpose: Wrapper to emit a sentence and also a close caption token for the sentence as appropriate. +// Input : filter - +// iEntIndex - +// iChannel - +// iSentenceIndex - +// flVolume - +// iSoundlevel - +// iFlags - +// iPitch - +// bUpdatePositions - +// soundtime - +//----------------------------------------------------------------------------- +void CBaseEntity::EmitSentenceByIndex( IRecipientFilter& filter, int iEntIndex, int iChannel, int iSentenceIndex, + float flVolume, soundlevel_t iSoundlevel, int iFlags /*= 0*/, int iPitch /*=PITCH_NORM*/, + const Vector *pOrigin /*=NULL*/, const Vector *pDirection /*=NULL*/, + bool bUpdatePositions /*=true*/, float soundtime /*=0.0f*/, int iSpecialDSP /*= 0*/, int iSpeakerIndex /*= 0*/ ) +{ + CUtlVector< Vector > soundOrigins; + + bool bSwallowed = CEnvMicrophone::OnSentencePlayed( + iEntIndex, + iSentenceIndex, + iSoundlevel, + flVolume, + iFlags, + iPitch, + pOrigin, + soundtime, + soundOrigins ); + if ( bSwallowed ) + return; + + CBaseEntity *pEntity = UTIL_EntityByIndex( iEntIndex ); + if ( pEntity ) + { + pEntity->ModifySentenceParams( iSentenceIndex, iChannel, flVolume, iSoundlevel, iFlags, iPitch, + &pOrigin, &pDirection, bUpdatePositions, soundtime, iSpecialDSP, iSpeakerIndex ); + } + + enginesound->EmitSentenceByIndex( filter, iEntIndex, iChannel, iSentenceIndex, + flVolume, iSoundlevel, iFlags, iPitch * GetSoundPitchScale(), iSpecialDSP, pOrigin, pDirection, &soundOrigins, bUpdatePositions, soundtime, iSpeakerIndex ); +} +#endif + void UTIL_EmitAmbientSound( int entindex, const Vector &vecOrigin, const char *samp, float vol, soundlevel_t soundlevel, int fFlags, int pitch, float soundtime /*= 0.0f*/, float *duration /*=NULL*/ ) { @@ -1407,7 +1496,17 @@ static const char *UTIL_TranslateSoundName( const char *soundname, const char *a void CBaseEntity::GenderExpandString( char const *in, char *out, int maxlen ) { +#ifdef MAPBASE + // This is so models without globalactors.txt declarations can still play scenes. + gender_t gender = soundemitterbase->GetActorGender(STRING(GetModelName())); + + if (gender == GENDER_NONE) + gender = GENDER_MALE; + + soundemitterbase->GenderExpandString(gender, in, out, maxlen); +#else soundemitterbase->GenderExpandString( STRING( GetModelName() ), in, out, maxlen ); +#endif } bool CBaseEntity::GetParametersForSound( const char *soundname, CSoundParameters ¶ms, const char *actormodel ) @@ -1433,7 +1532,7 @@ HSOUNDSCRIPTHANDLE CBaseEntity::PrecacheScriptSound( const char *soundname ) #endif } -#if !defined ( CLIENT_DLL ) +#if !defined ( CLIENT_DLL ) || defined( MAPBASE_VSCRIPT ) // Same as server version of above, but signiture changed so it can be deduced by the macros void CBaseEntity::VScriptPrecacheScriptSound( const char *soundname ) { diff --git a/src/game/shared/achievementmgr.cpp b/src/game/shared/achievementmgr.cpp index 66c5e9c3..9547b4a4 100644 --- a/src/game/shared/achievementmgr.cpp +++ b/src/game/shared/achievementmgr.cpp @@ -1529,7 +1529,11 @@ void CAchievementMgr::OnKillEvent( CBaseEntity *pVictim, CBaseEntity *pAttacker, } CBaseCombatCharacter *pBCC = dynamic_cast( pVictim ); +#ifdef MAPBASE + if ( pBCC && ( D_FR >= pBCC->IRelationType( pLocalPlayer ) ) ) +#else if ( pBCC && ( D_HT == pBCC->IRelationType( pLocalPlayer ) ) ) +#endif { bVictimIsPlayerEnemy = true; } @@ -1639,6 +1643,13 @@ void CAchievementMgr::OnMapEvent( const char *pchEventName ) CBaseAchievement *pAchievement = m_vecMapEventListeners[iAchievement]; pAchievement->OnMapEvent( pchEventName ); } + +#ifdef MAPBASE + if (cc_achievement_debug.GetBool()) + { + Msg( "CAchievementMgr::OnMapEvent: Achievement \"%s\" not found\n", pchEventName ); + } +#endif } //----------------------------------------------------------------------------- diff --git a/src/game/shared/activitylist.cpp b/src/game/shared/activitylist.cpp index a57513fd..b887148e 100644 --- a/src/game/shared/activitylist.cpp +++ b/src/game/shared/activitylist.cpp @@ -2522,6 +2522,522 @@ void ActivityList_RegisterSharedActivities( void ) REGISTER_SHARED_ACTIVITY( ACT_MP_CYOA_PDA_IDLE ); REGISTER_SHARED_ACTIVITY( ACT_MP_CYOA_PDA_OUTRO ); +#if AR2_ACTIVITY_FIX == 1 + REGISTER_SHARED_ACTIVITY( ACT_IDLE_AR2 ); + REGISTER_SHARED_ACTIVITY( ACT_IDLE_ANGRY_AR2 ); + + REGISTER_SHARED_ACTIVITY( ACT_IDLE_AR2_RELAXED ); + REGISTER_SHARED_ACTIVITY( ACT_IDLE_AR2_STIMULATED ); + REGISTER_SHARED_ACTIVITY( ACT_WALK_AR2_RELAXED ); + REGISTER_SHARED_ACTIVITY( ACT_RUN_AR2_RELAXED ); + REGISTER_SHARED_ACTIVITY( ACT_WALK_AR2_STIMULATED ); + REGISTER_SHARED_ACTIVITY( ACT_RUN_AR2_STIMULATED ); + + REGISTER_SHARED_ACTIVITY( ACT_IDLE_AIM_AR2_STIMULATED ); + REGISTER_SHARED_ACTIVITY( ACT_WALK_AIM_AR2_STIMULATED ); + REGISTER_SHARED_ACTIVITY( ACT_RUN_AIM_AR2_STIMULATED ); + + REGISTER_SHARED_ACTIVITY( ACT_WALK_AR2 ); + REGISTER_SHARED_ACTIVITY( ACT_WALK_AIM_AR2 ); + REGISTER_SHARED_ACTIVITY( ACT_RUN_AR2 ); + REGISTER_SHARED_ACTIVITY( ACT_RUN_AIM_AR2 ); + + REGISTER_SHARED_ACTIVITY( ACT_RELOAD_AR2 ); + REGISTER_SHARED_ACTIVITY( ACT_RELOAD_AR2_LOW ); + + REGISTER_SHARED_ACTIVITY( ACT_GESTURE_RELOAD_AR2 ); + + REGISTER_SHARED_ACTIVITY( ACT_COVER_AR2_LOW ); +#endif + +#if SHARED_COMBINE_ACTIVITIES + REGISTER_SHARED_ACTIVITY( ACT_COMBINE_THROW_GRENADE ); + REGISTER_SHARED_ACTIVITY( ACT_COMBINE_AR2_ALTFIRE ); + + REGISTER_SHARED_ACTIVITY( ACT_GESTURE_COMBINE_THROW_GRENADE ); + REGISTER_SHARED_ACTIVITY( ACT_GESTURE_COMBINE_AR2_ALTFIRE ); + REGISTER_SHARED_ACTIVITY( ACT_GESTURE_SPECIAL_ATTACK1 ); + REGISTER_SHARED_ACTIVITY( ACT_GESTURE_SPECIAL_ATTACK2 ); + + REGISTER_SHARED_ACTIVITY( ACT_GESTURE_SIGNAL_ADVANCE ); + REGISTER_SHARED_ACTIVITY( ACT_GESTURE_SIGNAL_FORWARD ); + REGISTER_SHARED_ACTIVITY( ACT_GESTURE_SIGNAL_GROUP ); + REGISTER_SHARED_ACTIVITY( ACT_GESTURE_SIGNAL_HALT ); + REGISTER_SHARED_ACTIVITY( ACT_GESTURE_SIGNAL_LEFT ); + REGISTER_SHARED_ACTIVITY( ACT_GESTURE_SIGNAL_RIGHT ); + REGISTER_SHARED_ACTIVITY( ACT_GESTURE_SIGNAL_TAKECOVER ); +#endif + +#if EXPANDED_HL2_WEAPON_ACTIVITIES + REGISTER_SHARED_ACTIVITY( ACT_IDLE_REVOLVER ); + REGISTER_SHARED_ACTIVITY( ACT_IDLE_ANGRY_REVOLVER ); + REGISTER_SHARED_ACTIVITY( ACT_WALK_REVOLVER ); + REGISTER_SHARED_ACTIVITY( ACT_RUN_REVOLVER ); + REGISTER_SHARED_ACTIVITY( ACT_WALK_AIM_REVOLVER ); + REGISTER_SHARED_ACTIVITY( ACT_RUN_AIM_REVOLVER ); + REGISTER_SHARED_ACTIVITY( ACT_RANGE_ATTACK_REVOLVER ); + REGISTER_SHARED_ACTIVITY( ACT_RELOAD_REVOLVER ); + REGISTER_SHARED_ACTIVITY( ACT_RANGE_ATTACK_REVOLVER_LOW ); + REGISTER_SHARED_ACTIVITY( ACT_RELOAD_REVOLVER_LOW ); + REGISTER_SHARED_ACTIVITY( ACT_COVER_REVOLVER_LOW ); + REGISTER_SHARED_ACTIVITY( ACT_RANGE_AIM_REVOLVER_LOW ); + REGISTER_SHARED_ACTIVITY( ACT_GESTURE_RANGE_ATTACK_REVOLVER ); + REGISTER_SHARED_ACTIVITY( ACT_GESTURE_RELOAD_REVOLVER ); + + REGISTER_SHARED_ACTIVITY( ACT_IDLE_CROSSBOW ); + REGISTER_SHARED_ACTIVITY( ACT_IDLE_ANGRY_CROSSBOW ); + REGISTER_SHARED_ACTIVITY( ACT_WALK_CROSSBOW ); + REGISTER_SHARED_ACTIVITY( ACT_RUN_CROSSBOW ); + REGISTER_SHARED_ACTIVITY( ACT_WALK_AIM_CROSSBOW ); + REGISTER_SHARED_ACTIVITY( ACT_RUN_AIM_CROSSBOW ); + REGISTER_SHARED_ACTIVITY( ACT_RANGE_ATTACK_CROSSBOW ); + REGISTER_SHARED_ACTIVITY( ACT_RELOAD_CROSSBOW ); + REGISTER_SHARED_ACTIVITY( ACT_RANGE_ATTACK_CROSSBOW_LOW ); + REGISTER_SHARED_ACTIVITY( ACT_RELOAD_CROSSBOW_LOW ); + REGISTER_SHARED_ACTIVITY( ACT_COVER_CROSSBOW_LOW ); + REGISTER_SHARED_ACTIVITY( ACT_RANGE_AIM_CROSSBOW_LOW ); + REGISTER_SHARED_ACTIVITY( ACT_GESTURE_RANGE_ATTACK_CROSSBOW ); + REGISTER_SHARED_ACTIVITY( ACT_GESTURE_RELOAD_CROSSBOW ); + + REGISTER_SHARED_ACTIVITY( ACT_IDLE_CROSSBOW_RELAXED ); + REGISTER_SHARED_ACTIVITY( ACT_IDLE_CROSSBOW_STIMULATED ); + REGISTER_SHARED_ACTIVITY( ACT_WALK_CROSSBOW_RELAXED ); + REGISTER_SHARED_ACTIVITY( ACT_RUN_CROSSBOW_RELAXED ); + REGISTER_SHARED_ACTIVITY( ACT_WALK_CROSSBOW_STIMULATED ); + REGISTER_SHARED_ACTIVITY( ACT_RUN_CROSSBOW_STIMULATED ); + + REGISTER_SHARED_ACTIVITY( ACT_IDLE_AIM_CROSSBOW_STIMULATED ); + REGISTER_SHARED_ACTIVITY( ACT_WALK_AIM_CROSSBOW_STIMULATED ); + REGISTER_SHARED_ACTIVITY( ACT_RUN_AIM_CROSSBOW_STIMULATED ); + + REGISTER_SHARED_ACTIVITY( ACT_IDLE_PISTOL_RELAXED ); + REGISTER_SHARED_ACTIVITY( ACT_IDLE_PISTOL_STIMULATED ); + REGISTER_SHARED_ACTIVITY( ACT_WALK_PISTOL_RELAXED ); + REGISTER_SHARED_ACTIVITY( ACT_WALK_PISTOL_STIMULATED ); + REGISTER_SHARED_ACTIVITY( ACT_RUN_PISTOL_RELAXED ); + REGISTER_SHARED_ACTIVITY( ACT_RUN_PISTOL_STIMULATED ); + + REGISTER_SHARED_ACTIVITY( ACT_IDLE_AIM_PISTOL_STIMULATED ); + REGISTER_SHARED_ACTIVITY( ACT_WALK_AIM_PISTOL_STIMULATED ); + REGISTER_SHARED_ACTIVITY( ACT_RUN_AIM_PISTOL_STIMULATED ); + + REGISTER_SHARED_ACTIVITY( ACT_WALK_CROUCH_PISTOL ); + REGISTER_SHARED_ACTIVITY( ACT_WALK_CROUCH_AIM_PISTOL ); + REGISTER_SHARED_ACTIVITY( ACT_RUN_CROUCH_PISTOL ); + REGISTER_SHARED_ACTIVITY( ACT_RUN_CROUCH_AIM_PISTOL ); + + REGISTER_SHARED_ACTIVITY( ACT_IDLE_SHOTGUN ); + REGISTER_SHARED_ACTIVITY( ACT_WALK_SHOTGUN ); + REGISTER_SHARED_ACTIVITY( ACT_RUN_SHOTGUN ); + + REGISTER_SHARED_ACTIVITY( ACT_COVER_SHOTGUN_LOW ); + REGISTER_SHARED_ACTIVITY( ACT_RANGE_AIM_SHOTGUN_LOW ); + + REGISTER_SHARED_ACTIVITY( ACT_WALK_SHOTGUN_RELAXED ); + REGISTER_SHARED_ACTIVITY( ACT_WALK_SHOTGUN_STIMULATED ); + REGISTER_SHARED_ACTIVITY( ACT_RUN_SHOTGUN_RELAXED ); + REGISTER_SHARED_ACTIVITY( ACT_RUN_SHOTGUN_STIMULATED ); + + REGISTER_SHARED_ACTIVITY( ACT_IDLE_AIM_SHOTGUN_STIMULATED ); + REGISTER_SHARED_ACTIVITY( ACT_WALK_AIM_SHOTGUN_STIMULATED ); + REGISTER_SHARED_ACTIVITY( ACT_RUN_AIM_SHOTGUN_STIMULATED ); + + REGISTER_SHARED_ACTIVITY( ACT_RANGE_AIM_RPG_LOW ); + REGISTER_SHARED_ACTIVITY( ACT_RANGE_ATTACK_RPG_LOW ); + REGISTER_SHARED_ACTIVITY( ACT_GESTURE_RANGE_ATTACK_RPG ); + + REGISTER_SHARED_ACTIVITY( ACT_RANGE_ATTACK_ANNABELLE ); + REGISTER_SHARED_ACTIVITY( ACT_RANGE_ATTACK_ANNABELLE_LOW ); + REGISTER_SHARED_ACTIVITY( ACT_GESTURE_RANGE_ATTACK_ANNABELLE ); + REGISTER_SHARED_ACTIVITY( ACT_RELOAD_ANNABELLE ); + REGISTER_SHARED_ACTIVITY( ACT_RELOAD_ANNABELLE_LOW ); + REGISTER_SHARED_ACTIVITY( ACT_GESTURE_RELOAD_ANNABELLE ); + + REGISTER_SHARED_ACTIVITY( ACT_WALK_MELEE ); + REGISTER_SHARED_ACTIVITY( ACT_RUN_MELEE ); + + REGISTER_SHARED_ACTIVITY( ACT_RUN_PACKAGE ); + REGISTER_SHARED_ACTIVITY( ACT_RUN_SUITCASE ); + + REGISTER_SHARED_ACTIVITY( ACT_ARM_RIFLE ); + REGISTER_SHARED_ACTIVITY( ACT_ARM_SHOTGUN ); + REGISTER_SHARED_ACTIVITY( ACT_ARM_RPG ); + REGISTER_SHARED_ACTIVITY( ACT_ARM_MELEE ); + REGISTER_SHARED_ACTIVITY( ACT_DISARM_RIFLE ); + REGISTER_SHARED_ACTIVITY( ACT_DISARM_SHOTGUN ); + REGISTER_SHARED_ACTIVITY( ACT_DISARM_RPG ); + REGISTER_SHARED_ACTIVITY( ACT_DISARM_MELEE ); +#endif + +#if EXPANDED_HL2_UNUSED_WEAPON_ACTIVITIES + REGISTER_SHARED_ACTIVITY( ACT_IDLE_AR1 ); + REGISTER_SHARED_ACTIVITY( ACT_IDLE_ANGRY_AR1 ); + REGISTER_SHARED_ACTIVITY( ACT_WALK_AR1 ); + REGISTER_SHARED_ACTIVITY( ACT_RUN_AR1 ); + REGISTER_SHARED_ACTIVITY( ACT_WALK_AIM_AR1 ); + REGISTER_SHARED_ACTIVITY( ACT_RUN_AIM_AR1 ); + //REGISTER_SHARED_ACTIVITY( ACT_RANGE_ATTACK_AR1 ); + REGISTER_SHARED_ACTIVITY( ACT_RELOAD_AR1 ); + REGISTER_SHARED_ACTIVITY( ACT_RANGE_ATTACK_AR1_LOW ); + REGISTER_SHARED_ACTIVITY( ACT_RELOAD_AR1_LOW ); + REGISTER_SHARED_ACTIVITY( ACT_COVER_AR1_LOW ); + REGISTER_SHARED_ACTIVITY( ACT_RANGE_AIM_AR1_LOW ); + //REGISTER_SHARED_ACTIVITY( ACT_GESTURE_RANGE_ATTACK_AR1 ); + REGISTER_SHARED_ACTIVITY( ACT_GESTURE_RELOAD_AR1 ); + + REGISTER_SHARED_ACTIVITY( ACT_IDLE_AR1_RELAXED ); + REGISTER_SHARED_ACTIVITY( ACT_IDLE_AR1_STIMULATED ); + REGISTER_SHARED_ACTIVITY( ACT_WALK_AR1_RELAXED ); + REGISTER_SHARED_ACTIVITY( ACT_RUN_AR1_RELAXED ); + REGISTER_SHARED_ACTIVITY( ACT_WALK_AR1_STIMULATED ); + REGISTER_SHARED_ACTIVITY( ACT_RUN_AR1_STIMULATED ); + + REGISTER_SHARED_ACTIVITY( ACT_IDLE_AIM_AR1_STIMULATED ); + REGISTER_SHARED_ACTIVITY( ACT_WALK_AIM_AR1_STIMULATED ); + REGISTER_SHARED_ACTIVITY( ACT_RUN_AIM_AR1_STIMULATED ); + + REGISTER_SHARED_ACTIVITY( ACT_IDLE_AR3 ); + REGISTER_SHARED_ACTIVITY( ACT_IDLE_ANGRY_AR3 ); + REGISTER_SHARED_ACTIVITY( ACT_WALK_AR3 ); + REGISTER_SHARED_ACTIVITY( ACT_RUN_AR3 ); + REGISTER_SHARED_ACTIVITY( ACT_WALK_AIM_AR3 ); + REGISTER_SHARED_ACTIVITY( ACT_RUN_AIM_AR3 ); + REGISTER_SHARED_ACTIVITY( ACT_RANGE_ATTACK_AR3 ); + REGISTER_SHARED_ACTIVITY( ACT_RELOAD_AR3 ); + REGISTER_SHARED_ACTIVITY( ACT_RANGE_ATTACK_AR3_LOW ); + REGISTER_SHARED_ACTIVITY( ACT_RELOAD_AR3_LOW ); + REGISTER_SHARED_ACTIVITY( ACT_COVER_AR3_LOW ); + REGISTER_SHARED_ACTIVITY( ACT_RANGE_AIM_AR3_LOW ); + REGISTER_SHARED_ACTIVITY( ACT_GESTURE_RANGE_ATTACK_AR3 ); + REGISTER_SHARED_ACTIVITY( ACT_GESTURE_RELOAD_AR3 ); + + REGISTER_SHARED_ACTIVITY( ACT_IDLE_AR3_RELAXED ); + REGISTER_SHARED_ACTIVITY( ACT_IDLE_AR3_STIMULATED ); + REGISTER_SHARED_ACTIVITY( ACT_WALK_AR3_RELAXED ); + REGISTER_SHARED_ACTIVITY( ACT_RUN_AR3_RELAXED ); + REGISTER_SHARED_ACTIVITY( ACT_WALK_AR3_STIMULATED ); + REGISTER_SHARED_ACTIVITY( ACT_RUN_AR3_STIMULATED ); + + REGISTER_SHARED_ACTIVITY( ACT_IDLE_AIM_AR3_STIMULATED ); + REGISTER_SHARED_ACTIVITY( ACT_WALK_AIM_AR3_STIMULATED ); + REGISTER_SHARED_ACTIVITY( ACT_RUN_AIM_AR3_STIMULATED ); + + REGISTER_SHARED_ACTIVITY( ACT_IDLE_SMG2 ); + REGISTER_SHARED_ACTIVITY( ACT_IDLE_ANGRY_SMG2 ); + REGISTER_SHARED_ACTIVITY( ACT_WALK_SMG2 ); + REGISTER_SHARED_ACTIVITY( ACT_RUN_SMG2 ); + REGISTER_SHARED_ACTIVITY( ACT_WALK_AIM_SMG2 ); + REGISTER_SHARED_ACTIVITY( ACT_RUN_AIM_SMG2 ); + //REGISTER_SHARED_ACTIVITY( ACT_RANGE_ATTACK_SMG2 ); + REGISTER_SHARED_ACTIVITY( ACT_RELOAD_SMG2 ); + REGISTER_SHARED_ACTIVITY( ACT_RANGE_ATTACK_SMG2_LOW ); + REGISTER_SHARED_ACTIVITY( ACT_RELOAD_SMG2_LOW ); + REGISTER_SHARED_ACTIVITY( ACT_COVER_SMG2_LOW ); + REGISTER_SHARED_ACTIVITY( ACT_RANGE_AIM_SMG2_LOW ); + //REGISTER_SHARED_ACTIVITY( ACT_GESTURE_RANGE_ATTACK_SMG2 ); + REGISTER_SHARED_ACTIVITY( ACT_GESTURE_RELOAD_SMG2 ); + + REGISTER_SHARED_ACTIVITY( ACT_IDLE_SMG2_RELAXED ); + REGISTER_SHARED_ACTIVITY( ACT_IDLE_SMG2_STIMULATED ); + REGISTER_SHARED_ACTIVITY( ACT_WALK_SMG2_RELAXED ); + REGISTER_SHARED_ACTIVITY( ACT_RUN_SMG2_RELAXED ); + REGISTER_SHARED_ACTIVITY( ACT_WALK_SMG2_STIMULATED ); + REGISTER_SHARED_ACTIVITY( ACT_RUN_SMG2_STIMULATED ); + + REGISTER_SHARED_ACTIVITY( ACT_IDLE_AIM_SMG2_STIMULATED ); + REGISTER_SHARED_ACTIVITY( ACT_WALK_AIM_SMG2_STIMULATED ); + REGISTER_SHARED_ACTIVITY( ACT_RUN_AIM_SMG2_STIMULATED ); + + REGISTER_SHARED_ACTIVITY( ACT_IDLE_SMG3 ); + REGISTER_SHARED_ACTIVITY( ACT_IDLE_ANGRY_SMG3 ); + REGISTER_SHARED_ACTIVITY( ACT_WALK_SMG3 ); + REGISTER_SHARED_ACTIVITY( ACT_RUN_SMG3 ); + REGISTER_SHARED_ACTIVITY( ACT_WALK_AIM_SMG3 ); + REGISTER_SHARED_ACTIVITY( ACT_RUN_AIM_SMG3 ); + REGISTER_SHARED_ACTIVITY( ACT_RANGE_ATTACK_SMG3 ); + REGISTER_SHARED_ACTIVITY( ACT_RELOAD_SMG3 ); + REGISTER_SHARED_ACTIVITY( ACT_RANGE_ATTACK_SMG3_LOW ); + REGISTER_SHARED_ACTIVITY( ACT_RELOAD_SMG3_LOW ); + REGISTER_SHARED_ACTIVITY( ACT_COVER_SMG3_LOW ); + REGISTER_SHARED_ACTIVITY( ACT_RANGE_AIM_SMG3_LOW ); + REGISTER_SHARED_ACTIVITY( ACT_GESTURE_RANGE_ATTACK_SMG3 ); + REGISTER_SHARED_ACTIVITY( ACT_GESTURE_RELOAD_SMG3 ); + + REGISTER_SHARED_ACTIVITY( ACT_IDLE_SMG3_RELAXED ); + REGISTER_SHARED_ACTIVITY( ACT_IDLE_SMG3_STIMULATED ); + REGISTER_SHARED_ACTIVITY( ACT_WALK_SMG3_RELAXED ); + REGISTER_SHARED_ACTIVITY( ACT_RUN_SMG3_RELAXED ); + REGISTER_SHARED_ACTIVITY( ACT_WALK_SMG3_STIMULATED ); + REGISTER_SHARED_ACTIVITY( ACT_RUN_SMG3_STIMULATED ); + + REGISTER_SHARED_ACTIVITY( ACT_IDLE_AIM_SMG3_STIMULATED ); + REGISTER_SHARED_ACTIVITY( ACT_WALK_AIM_SMG3_STIMULATED ); + REGISTER_SHARED_ACTIVITY( ACT_RUN_AIM_SMG3_STIMULATED ); + + REGISTER_SHARED_ACTIVITY( ACT_IDLE_HMG1 ); + REGISTER_SHARED_ACTIVITY( ACT_IDLE_ANGRY_HMG1 ); + REGISTER_SHARED_ACTIVITY( ACT_WALK_HMG1 ); + REGISTER_SHARED_ACTIVITY( ACT_RUN_HMG1 ); + REGISTER_SHARED_ACTIVITY( ACT_WALK_AIM_HMG1 ); + REGISTER_SHARED_ACTIVITY( ACT_RUN_AIM_HMG1 ); + //REGISTER_SHARED_ACTIVITY( ACT_RANGE_ATTACK_HMG1 ); + REGISTER_SHARED_ACTIVITY( ACT_RELOAD_HMG1 ); + REGISTER_SHARED_ACTIVITY( ACT_RANGE_ATTACK_HMG1_LOW ); + REGISTER_SHARED_ACTIVITY( ACT_RELOAD_HMG1_LOW ); + REGISTER_SHARED_ACTIVITY( ACT_COVER_HMG1_LOW ); + REGISTER_SHARED_ACTIVITY( ACT_RANGE_AIM_HMG1_LOW ); + //REGISTER_SHARED_ACTIVITY( ACT_GESTURE_RANGE_ATTACK_HMG1 ); + REGISTER_SHARED_ACTIVITY( ACT_GESTURE_RELOAD_HMG1 ); + + REGISTER_SHARED_ACTIVITY( ACT_IDLE_HMG1_RELAXED ); + REGISTER_SHARED_ACTIVITY( ACT_IDLE_HMG1_STIMULATED ); + REGISTER_SHARED_ACTIVITY( ACT_WALK_HMG1_RELAXED ); + REGISTER_SHARED_ACTIVITY( ACT_RUN_HMG1_RELAXED ); + REGISTER_SHARED_ACTIVITY( ACT_WALK_HMG1_STIMULATED ); + REGISTER_SHARED_ACTIVITY( ACT_RUN_HMG1_STIMULATED ); + + REGISTER_SHARED_ACTIVITY( ACT_IDLE_AIM_HMG1_STIMULATED ); + REGISTER_SHARED_ACTIVITY( ACT_WALK_AIM_HMG1_STIMULATED ); + REGISTER_SHARED_ACTIVITY( ACT_RUN_AIM_HMG1_STIMULATED ); + + REGISTER_SHARED_ACTIVITY( ACT_IDLE_SNIPER_RIFLE ); + REGISTER_SHARED_ACTIVITY( ACT_IDLE_ANGRY_SNIPER_RIFLE ); + REGISTER_SHARED_ACTIVITY( ACT_WALK_SNIPER_RIFLE ); + REGISTER_SHARED_ACTIVITY( ACT_RUN_SNIPER_RIFLE ); + REGISTER_SHARED_ACTIVITY( ACT_WALK_AIM_SNIPER_RIFLE ); + REGISTER_SHARED_ACTIVITY( ACT_RUN_AIM_SNIPER_RIFLE ); + //REGISTER_SHARED_ACTIVITY( ACT_RANGE_ATTACK_SNIPER_RIFLE ); + REGISTER_SHARED_ACTIVITY( ACT_RELOAD_SNIPER_RIFLE ); + REGISTER_SHARED_ACTIVITY( ACT_RANGE_ATTACK_SNIPER_RIFLE_LOW ); + REGISTER_SHARED_ACTIVITY( ACT_RELOAD_SNIPER_RIFLE_LOW ); + REGISTER_SHARED_ACTIVITY( ACT_COVER_SNIPER_RIFLE_LOW ); + REGISTER_SHARED_ACTIVITY( ACT_RANGE_AIM_SNIPER_RIFLE_LOW ); + //REGISTER_SHARED_ACTIVITY( ACT_GESTURE_RANGE_ATTACK_SNIPER_RIFLE ); + REGISTER_SHARED_ACTIVITY( ACT_GESTURE_RELOAD_SNIPER_RIFLE ); + + REGISTER_SHARED_ACTIVITY( ACT_IDLE_SNIPER_RIFLE_RELAXED ); + REGISTER_SHARED_ACTIVITY( ACT_IDLE_SNIPER_RIFLE_STIMULATED ); + REGISTER_SHARED_ACTIVITY( ACT_WALK_SNIPER_RIFLE_RELAXED ); + REGISTER_SHARED_ACTIVITY( ACT_RUN_SNIPER_RIFLE_RELAXED ); + REGISTER_SHARED_ACTIVITY( ACT_WALK_SNIPER_RIFLE_STIMULATED ); + REGISTER_SHARED_ACTIVITY( ACT_RUN_SNIPER_RIFLE_STIMULATED ); + + REGISTER_SHARED_ACTIVITY( ACT_IDLE_AIM_SNIPER_RIFLE_STIMULATED ); + REGISTER_SHARED_ACTIVITY( ACT_WALK_AIM_SNIPER_RIFLE_STIMULATED ); + REGISTER_SHARED_ACTIVITY( ACT_RUN_AIM_SNIPER_RIFLE_STIMULATED ); + + REGISTER_SHARED_ACTIVITY( ACT_IDLE_DUAL_PISTOLS ); + REGISTER_SHARED_ACTIVITY( ACT_IDLE_ANGRY_DUAL_PISTOLS ); + REGISTER_SHARED_ACTIVITY( ACT_WALK_DUAL_PISTOLS ); + REGISTER_SHARED_ACTIVITY( ACT_RUN_DUAL_PISTOLS ); + REGISTER_SHARED_ACTIVITY( ACT_WALK_AIM_DUAL_PISTOLS ); + REGISTER_SHARED_ACTIVITY( ACT_RUN_AIM_DUAL_PISTOLS ); + REGISTER_SHARED_ACTIVITY( ACT_RANGE_ATTACK_DUAL_PISTOLS ); + REGISTER_SHARED_ACTIVITY( ACT_RELOAD_DUAL_PISTOLS ); + REGISTER_SHARED_ACTIVITY( ACT_RANGE_ATTACK_DUAL_PISTOLS_LOW ); + REGISTER_SHARED_ACTIVITY( ACT_RELOAD_DUAL_PISTOLS_LOW ); + REGISTER_SHARED_ACTIVITY( ACT_COVER_DUAL_PISTOLS_LOW ); + REGISTER_SHARED_ACTIVITY( ACT_RANGE_AIM_DUAL_PISTOLS_LOW ); + REGISTER_SHARED_ACTIVITY( ACT_GESTURE_RANGE_ATTACK_DUAL_PISTOLS ); + REGISTER_SHARED_ACTIVITY( ACT_GESTURE_RELOAD_DUAL_PISTOLS ); + + REGISTER_SHARED_ACTIVITY( ACT_IDLE_DUAL_PISTOLS_RELAXED ); + REGISTER_SHARED_ACTIVITY( ACT_IDLE_DUAL_PISTOLS_STIMULATED ); + REGISTER_SHARED_ACTIVITY( ACT_WALK_DUAL_PISTOLS_RELAXED ); + REGISTER_SHARED_ACTIVITY( ACT_RUN_DUAL_PISTOLS_RELAXED ); + REGISTER_SHARED_ACTIVITY( ACT_WALK_DUAL_PISTOLS_STIMULATED ); + REGISTER_SHARED_ACTIVITY( ACT_RUN_DUAL_PISTOLS_STIMULATED ); + + REGISTER_SHARED_ACTIVITY( ACT_IDLE_AIM_DUAL_PISTOLS_STIMULATED ); + REGISTER_SHARED_ACTIVITY( ACT_WALK_AIM_DUAL_PISTOLS_STIMULATED ); + REGISTER_SHARED_ACTIVITY( ACT_RUN_AIM_DUAL_PISTOLS_STIMULATED ); +#endif + +#if EXPANDED_NAVIGATION_ACTIVITIES + REGISTER_SHARED_ACTIVITY( ACT_CLIMB_ALL ); + REGISTER_SHARED_ACTIVITY( ACT_CLIMB_IDLE ); + + REGISTER_SHARED_ACTIVITY( ACT_CLIMB_MOUNT_TOP ); + REGISTER_SHARED_ACTIVITY( ACT_CLIMB_MOUNT_BOTTOM ); + REGISTER_SHARED_ACTIVITY( ACT_CLIMB_DISMOUNT_BOTTOM ); +#endif + +#if EXPANDED_HL2_COVER_ACTIVITIES + REGISTER_SHARED_ACTIVITY( ACT_RANGE_ATTACK1_MED ); + REGISTER_SHARED_ACTIVITY( ACT_RANGE_ATTACK2_MED ); + REGISTER_SHARED_ACTIVITY( ACT_RANGE_AIM_MED ); + + REGISTER_SHARED_ACTIVITY( ACT_RANGE_ATTACK_AR2_MED ); + REGISTER_SHARED_ACTIVITY( ACT_RANGE_ATTACK_SMG1_MED ); + REGISTER_SHARED_ACTIVITY( ACT_RANGE_ATTACK_SHOTGUN_MED ); + REGISTER_SHARED_ACTIVITY( ACT_RANGE_ATTACK_PISTOL_MED ); + REGISTER_SHARED_ACTIVITY( ACT_RANGE_ATTACK_RPG_MED ); + REGISTER_SHARED_ACTIVITY( ACT_RANGE_ATTACK_REVOLVER_MED ); + REGISTER_SHARED_ACTIVITY( ACT_RANGE_ATTACK_CROSSBOW_MED ); + + REGISTER_SHARED_ACTIVITY( ACT_RANGE_AIM_AR2_MED ); + REGISTER_SHARED_ACTIVITY( ACT_RANGE_AIM_SMG1_MED ); + REGISTER_SHARED_ACTIVITY( ACT_RANGE_AIM_SHOTGUN_MED ); + REGISTER_SHARED_ACTIVITY( ACT_RANGE_AIM_PISTOL_MED ); + REGISTER_SHARED_ACTIVITY( ACT_RANGE_AIM_RPG_MED ); + REGISTER_SHARED_ACTIVITY( ACT_RANGE_AIM_REVOLVER_MED ); + REGISTER_SHARED_ACTIVITY( ACT_RANGE_AIM_CROSSBOW_MED ); + +#if EXPANDED_HL2_UNUSED_WEAPON_ACTIVITIES + REGISTER_SHARED_ACTIVITY( ACT_RANGE_AIM_AR1_MED ); + REGISTER_SHARED_ACTIVITY( ACT_RANGE_ATTACK_AR1_MED ); + REGISTER_SHARED_ACTIVITY( ACT_RANGE_AIM_AR3_MED ); + REGISTER_SHARED_ACTIVITY( ACT_RANGE_ATTACK_AR3_MED ); + REGISTER_SHARED_ACTIVITY( ACT_RANGE_AIM_SMG2_MED ); + REGISTER_SHARED_ACTIVITY( ACT_RANGE_ATTACK_SMG2_MED ); + REGISTER_SHARED_ACTIVITY( ACT_RANGE_AIM_SMG3_MED ); + REGISTER_SHARED_ACTIVITY( ACT_RANGE_ATTACK_SMG3_MED ); + REGISTER_SHARED_ACTIVITY( ACT_RANGE_AIM_HMG1_MED ); + REGISTER_SHARED_ACTIVITY( ACT_RANGE_ATTACK_HMG1_MED ); + REGISTER_SHARED_ACTIVITY( ACT_RANGE_AIM_SNIPER_RIFLE_MED ); + REGISTER_SHARED_ACTIVITY( ACT_RANGE_ATTACK_SNIPER_RIFLE_MED ); + REGISTER_SHARED_ACTIVITY( ACT_RANGE_AIM_DUAL_PISTOLS_MED ); + REGISTER_SHARED_ACTIVITY( ACT_RANGE_ATTACK_DUAL_PISTOLS_MED ); +#endif + + REGISTER_SHARED_ACTIVITY( ACT_COVER_WALL_R ); + REGISTER_SHARED_ACTIVITY( ACT_COVER_WALL_L ); + REGISTER_SHARED_ACTIVITY( ACT_COVER_WALL_LOW_R ); + REGISTER_SHARED_ACTIVITY( ACT_COVER_WALL_LOW_L ); + + REGISTER_SHARED_ACTIVITY( ACT_COVER_WALL_R_RIFLE ); + REGISTER_SHARED_ACTIVITY( ACT_COVER_WALL_L_RIFLE ); + REGISTER_SHARED_ACTIVITY( ACT_COVER_WALL_LOW_R_RIFLE ); + REGISTER_SHARED_ACTIVITY( ACT_COVER_WALL_LOW_L_RIFLE ); + + REGISTER_SHARED_ACTIVITY( ACT_COVER_WALL_R_PISTOL ); + REGISTER_SHARED_ACTIVITY( ACT_COVER_WALL_L_PISTOL ); + REGISTER_SHARED_ACTIVITY( ACT_COVER_WALL_LOW_R_PISTOL ); + REGISTER_SHARED_ACTIVITY( ACT_COVER_WALL_LOW_L_PISTOL ); +#endif + +#if EXPANDED_HL2DM_ACTIVITIES + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_WALK ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_WALK_PISTOL ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_WALK_SHOTGUN ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_WALK_SMG1 ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_WALK_AR2 ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_WALK_PHYSGUN ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_WALK_GRENADE ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_WALK_RPG ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_WALK_CROSSBOW ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_WALK_MELEE ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_WALK_SLAM ); + + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_GESTURE_RANGE_ATTACK2 ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_GESTURE_RANGE_ATTACK2_PISTOL ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_GESTURE_RANGE_ATTACK2_SHOTGUN ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_GESTURE_RANGE_ATTACK2_SMG1 ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_GESTURE_RANGE_ATTACK2_AR2 ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_GESTURE_RANGE_ATTACK2_PHYSGUN ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_GESTURE_RANGE_ATTACK2_GRENADE ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_GESTURE_RANGE_ATTACK2_RPG ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_GESTURE_RANGE_ATTACK2_CROSSBOW ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_GESTURE_RANGE_ATTACK2_MELEE ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_GESTURE_RANGE_ATTACK2_SLAM ); + + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_IDLE_REVOLVER ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_RUN_REVOLVER ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_WALK_REVOLVER ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_IDLE_CROUCH_REVOLVER ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_WALK_CROUCH_REVOLVER ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_GESTURE_RANGE_ATTACK_REVOLVER ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_GESTURE_RANGE_ATTACK2_REVOLVER ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_GESTURE_RELOAD_REVOLVER ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_JUMP_REVOLVER ); + +#if EXPANDED_HL2_UNUSED_WEAPON_ACTIVITIES + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_IDLE_AR1 ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_RUN_AR1 ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_WALK_AR1 ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_IDLE_CROUCH_AR1 ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_WALK_CROUCH_AR1 ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_GESTURE_RANGE_ATTACK_AR1 ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_GESTURE_RANGE_ATTACK2_AR1 ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_GESTURE_RELOAD_AR1 ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_JUMP_AR1 ); + + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_IDLE_AR3 ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_RUN_AR3 ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_WALK_AR3 ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_IDLE_CROUCH_AR3 ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_WALK_CROUCH_AR3 ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_GESTURE_RANGE_ATTACK_AR3 ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_GESTURE_RANGE_ATTACK2_AR3 ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_GESTURE_RELOAD_AR3 ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_JUMP_AR3 ); + + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_IDLE_SMG2 ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_RUN_SMG2 ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_WALK_SMG2 ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_IDLE_CROUCH_SMG2 ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_WALK_CROUCH_SMG2 ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_GESTURE_RANGE_ATTACK_SMG2 ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_GESTURE_RANGE_ATTACK2_SMG2 ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_GESTURE_RELOAD_SMG2 ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_JUMP_SMG2 ); + + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_IDLE_SMG3 ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_RUN_SMG3 ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_WALK_SMG3 ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_IDLE_CROUCH_SMG3 ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_WALK_CROUCH_SMG3 ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_GESTURE_RANGE_ATTACK_SMG3 ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_GESTURE_RANGE_ATTACK2_SMG3 ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_GESTURE_RELOAD_SMG3 ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_JUMP_SMG3 ); + + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_IDLE_HMG1 ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_RUN_HMG1 ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_WALK_HMG1 ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_IDLE_CROUCH_HMG1 ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_WALK_CROUCH_HMG1 ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_GESTURE_RANGE_ATTACK_HMG1 ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_GESTURE_RANGE_ATTACK2_HMG1 ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_GESTURE_RELOAD_HMG1 ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_JUMP_HMG1 ); + + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_IDLE_SNIPER_RIFLE ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_RUN_SNIPER_RIFLE ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_WALK_SNIPER_RIFLE ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_IDLE_CROUCH_SNIPER_RIFLE ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_WALK_CROUCH_SNIPER_RIFLE ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_GESTURE_RANGE_ATTACK_SNIPER_RIFLE ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_GESTURE_RANGE_ATTACK2_SNIPER_RIFLE ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_GESTURE_RELOAD_SNIPER_RIFLE ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_JUMP_SNIPER_RIFLE ); + + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_IDLE_DUAL_PISTOLS ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_RUN_DUAL_PISTOLS ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_WALK_DUAL_PISTOLS ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_IDLE_CROUCH_DUAL_PISTOLS ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_WALK_CROUCH_DUAL_PISTOLS ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_GESTURE_RANGE_ATTACK_DUAL_PISTOLS ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_GESTURE_RANGE_ATTACK2_DUAL_PISTOLS ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_GESTURE_RELOAD_DUAL_PISTOLS ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_JUMP_DUAL_PISTOLS ); +#endif + + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_IDLE_USE ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_RUN_USE ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_WALK_USE ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_IDLE_CROUCH_USE ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_WALK_CROUCH_USE ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_JUMP_USE ); + + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_IDLE_USE_HEAVY ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_RUN_USE_HEAVY ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_WALK_USE_HEAVY ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_IDLE_CROUCH_USE_HEAVY ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_WALK_CROUCH_USE_HEAVY ); + REGISTER_SHARED_ACTIVITY( ACT_HL2MP_JUMP_USE_HEAVY ); +#endif + AssertMsg( g_HighestActivity == LAST_SHARED_ACTIVITY - 1, "Not all activities from ai_activity.h registered in activitylist.cpp" ); } diff --git a/src/game/shared/ai_activity.h b/src/game/shared/ai_activity.h index 3702cb83..7ee23114 100644 --- a/src/game/shared/ai_activity.h +++ b/src/game/shared/ai_activity.h @@ -11,6 +11,57 @@ #pragma once #endif +#ifdef MAPBASE + +// +// Mapbase adds many new shared activities, primarily for NPCs. +// +// These are at the bottom of the enum to prevent disruptions in the order of existing activities. +// + +// AR2 ACTIVITY FIX +// Citizens and Combine soldiers have several activities for the SMG1 and AR2 which differ from each other. +// Across both NPCs, there are around 20-40 different AR2 animations. Most of these animations are similar to the +// SMG1 animations, except their hand positions are adjusted to use the AR2 instead. +// +// Unfortunately, the vast majority of the AR2 animations in those models are not declared as real activities in +// code. The AR2 instead falls back to its SMG1 animation counterparts. +// This is thought to be an oversight which dates back to late in Half-Life 2's development. +// +// This preprocessor declares those activities and implements them on the AR2. In-game, they work surprisingly well +// despite presumably never being tested in-game during HL2's development. +#define AR2_ACTIVITY_FIX 1 + +// SHARED COMBINE ACTIVITIES +// This turns ACT_COMBINE_AR2_ALTFIRE, ACT_COMBINE_THROW_GRENADE, and their new gesture counterparts into shared activities. +// This is necessary for other NPCs to use them without having to rely on private custom activities declared through the AI definition system. +#define SHARED_COMBINE_ACTIVITIES 1 + +// EXPANDED HL2 WEAPON ACTIVITIES +// This enables a bunch of new activities for Half-Life 2 weapons, including new 357 animations and readiness activities for pistols. +#define EXPANDED_HL2_WEAPON_ACTIVITIES 1 + +// EXPANDED HL2 UNUSED WEAPON ACTIVITIES +// This enables a bunch of new activities for unused Half-Life 2 weapons, particularly those which exist in the SDK, but are deactivated by default. +// This essentially just means mods which restore those weapons have the option of using custom activities for them. +// Mapbase's backup activity system would allow them to fall back to other weapons if the relevant activities do not exist. +// Also includes activity names for "AR3", "SMG3", and "DUAL_PISTOLS", which were never used in HL2, but may be useful when additional animation sets are needed. +#define EXPANDED_HL2_UNUSED_WEAPON_ACTIVITIES 0 + +// EXPANDED NAVIGATION ACTIVITIES +// This enables some new navigation-related activities. +#define EXPANDED_NAVIGATION_ACTIVITIES 1 + +// EXPANDED HL2 COVER ACTIVITIES +// This enables some new cover-related activities. +#define EXPANDED_HL2_COVER_ACTIVITIES 1 + +// EXPANDED HL2DM ACTIVITIES +// This enables some new activities for the HL2:DM set. +#define EXPANDED_HL2DM_ACTIVITIES 1 + +#endif + #define ACTIVITY_NOT_AVAILABLE -1 typedef enum @@ -2363,6 +2414,543 @@ typedef enum ACT_MP_CYOA_PDA_IDLE, ACT_MP_CYOA_PDA_OUTRO, +#if AR2_ACTIVITY_FIX == 1 + ACT_IDLE_AR2, + ACT_IDLE_ANGRY_AR2, + + ACT_IDLE_AR2_RELAXED, + ACT_IDLE_AR2_STIMULATED, + + ACT_WALK_AR2_RELAXED, + ACT_RUN_AR2_RELAXED, + ACT_WALK_AR2_STIMULATED, + ACT_RUN_AR2_STIMULATED, + + ACT_IDLE_AIM_AR2_STIMULATED, + ACT_WALK_AIM_AR2_STIMULATED, + ACT_RUN_AIM_AR2_STIMULATED, + + ACT_WALK_AR2, + ACT_WALK_AIM_AR2, + ACT_RUN_AR2, + ACT_RUN_AIM_AR2, + + ACT_RELOAD_AR2, + ACT_RELOAD_AR2_LOW, + + ACT_GESTURE_RELOAD_AR2, + + ACT_COVER_AR2_LOW, +#endif + +#if SHARED_COMBINE_ACTIVITIES + ACT_COMBINE_THROW_GRENADE, + ACT_COMBINE_AR2_ALTFIRE, + + // Gesture versions for existing Combine signal and grenade activities + ACT_GESTURE_COMBINE_THROW_GRENADE, + ACT_GESTURE_COMBINE_AR2_ALTFIRE, + ACT_GESTURE_SPECIAL_ATTACK1, + ACT_GESTURE_SPECIAL_ATTACK2, + + ACT_GESTURE_SIGNAL_ADVANCE, + ACT_GESTURE_SIGNAL_FORWARD, + ACT_GESTURE_SIGNAL_GROUP, + ACT_GESTURE_SIGNAL_HALT, + ACT_GESTURE_SIGNAL_LEFT, + ACT_GESTURE_SIGNAL_RIGHT, + ACT_GESTURE_SIGNAL_TAKECOVER, +#endif + +#if EXPANDED_HL2_WEAPON_ACTIVITIES + // Revolver (357) + ACT_IDLE_REVOLVER, + ACT_IDLE_ANGRY_REVOLVER, + ACT_WALK_REVOLVER, + ACT_RUN_REVOLVER, + ACT_WALK_AIM_REVOLVER, + ACT_RUN_AIM_REVOLVER, + ACT_RANGE_ATTACK_REVOLVER, + ACT_RELOAD_REVOLVER, + ACT_RANGE_ATTACK_REVOLVER_LOW, + ACT_RELOAD_REVOLVER_LOW, + ACT_COVER_REVOLVER_LOW, + ACT_RANGE_AIM_REVOLVER_LOW, + ACT_GESTURE_RANGE_ATTACK_REVOLVER, + ACT_GESTURE_RELOAD_REVOLVER, + + // Crossbow + ACT_IDLE_CROSSBOW, + ACT_IDLE_ANGRY_CROSSBOW, + ACT_WALK_CROSSBOW, + ACT_RUN_CROSSBOW, + ACT_WALK_AIM_CROSSBOW, + ACT_RUN_AIM_CROSSBOW, + ACT_RANGE_ATTACK_CROSSBOW, + ACT_RELOAD_CROSSBOW, + ACT_RANGE_ATTACK_CROSSBOW_LOW, + ACT_RELOAD_CROSSBOW_LOW, + ACT_COVER_CROSSBOW_LOW, + ACT_RANGE_AIM_CROSSBOW_LOW, + ACT_GESTURE_RANGE_ATTACK_CROSSBOW, + ACT_GESTURE_RELOAD_CROSSBOW, + + ACT_IDLE_CROSSBOW_RELAXED, + ACT_IDLE_CROSSBOW_STIMULATED, + ACT_WALK_CROSSBOW_RELAXED, + ACT_RUN_CROSSBOW_RELAXED, + ACT_WALK_CROSSBOW_STIMULATED, + ACT_RUN_CROSSBOW_STIMULATED, + + ACT_IDLE_AIM_CROSSBOW_STIMULATED, + ACT_WALK_AIM_CROSSBOW_STIMULATED, + ACT_RUN_AIM_CROSSBOW_STIMULATED, + + // Pistol + ACT_IDLE_PISTOL_RELAXED, + ACT_IDLE_PISTOL_STIMULATED, + ACT_WALK_PISTOL_RELAXED, + ACT_WALK_PISTOL_STIMULATED, + ACT_RUN_PISTOL_RELAXED, + ACT_RUN_PISTOL_STIMULATED, + + ACT_IDLE_AIM_PISTOL_STIMULATED, + ACT_WALK_AIM_PISTOL_STIMULATED, + ACT_RUN_AIM_PISTOL_STIMULATED, + + ACT_WALK_CROUCH_PISTOL, + ACT_WALK_CROUCH_AIM_PISTOL, + ACT_RUN_CROUCH_PISTOL, + ACT_RUN_CROUCH_AIM_PISTOL, + + // Shotgun + ACT_IDLE_SHOTGUN, + ACT_WALK_SHOTGUN, + ACT_RUN_SHOTGUN, + + ACT_COVER_SHOTGUN_LOW, + ACT_RANGE_AIM_SHOTGUN_LOW, + + ACT_WALK_SHOTGUN_RELAXED, + ACT_WALK_SHOTGUN_STIMULATED, + ACT_RUN_SHOTGUN_RELAXED, + ACT_RUN_SHOTGUN_STIMULATED, + + ACT_IDLE_AIM_SHOTGUN_STIMULATED, + ACT_WALK_AIM_SHOTGUN_STIMULATED, + ACT_RUN_AIM_SHOTGUN_STIMULATED, + + // RPG + ACT_RANGE_AIM_RPG_LOW, + ACT_RANGE_ATTACK_RPG_LOW, + ACT_GESTURE_RANGE_ATTACK_RPG, + + // Annabelle + ACT_RANGE_ATTACK_ANNABELLE, + ACT_RANGE_ATTACK_ANNABELLE_LOW, + ACT_GESTURE_RANGE_ATTACK_ANNABELLE, + ACT_RELOAD_ANNABELLE, + ACT_RELOAD_ANNABELLE_LOW, + ACT_GESTURE_RELOAD_ANNABELLE, + + // Melee + ACT_WALK_MELEE, + ACT_RUN_MELEE, + + // Citizen accessories + ACT_RUN_PACKAGE, + ACT_RUN_SUITCASE, + + // Holster/Unholster + ACT_ARM_RIFLE, + ACT_ARM_SHOTGUN, + ACT_ARM_RPG, + ACT_ARM_MELEE, + ACT_DISARM_RIFLE, + ACT_DISARM_SHOTGUN, + ACT_DISARM_RPG, + ACT_DISARM_MELEE, +#endif + +#if EXPANDED_HL2_UNUSED_WEAPON_ACTIVITIES + // AR1 + ACT_IDLE_AR1, + ACT_IDLE_ANGRY_AR1, + ACT_WALK_AR1, + ACT_RUN_AR1, + ACT_WALK_AIM_AR1, + ACT_RUN_AIM_AR1, + //ACT_RANGE_ATTACK_AR1, + ACT_RELOAD_AR1, + ACT_RANGE_ATTACK_AR1_LOW, + ACT_RELOAD_AR1_LOW, + ACT_COVER_AR1_LOW, + ACT_RANGE_AIM_AR1_LOW, + //ACT_GESTURE_RANGE_ATTACK_AR1, + ACT_GESTURE_RELOAD_AR1, + + ACT_IDLE_AR1_RELAXED, + ACT_IDLE_AR1_STIMULATED, + ACT_WALK_AR1_RELAXED, + ACT_RUN_AR1_RELAXED, + ACT_WALK_AR1_STIMULATED, + ACT_RUN_AR1_STIMULATED, + + ACT_IDLE_AIM_AR1_STIMULATED, + ACT_WALK_AIM_AR1_STIMULATED, + ACT_RUN_AIM_AR1_STIMULATED, + + // AR3 (new) + ACT_IDLE_AR3, + ACT_IDLE_ANGRY_AR3, + ACT_WALK_AR3, + ACT_RUN_AR3, + ACT_WALK_AIM_AR3, + ACT_RUN_AIM_AR3, + ACT_RANGE_ATTACK_AR3, + ACT_RELOAD_AR3, + ACT_RANGE_ATTACK_AR3_LOW, + ACT_RELOAD_AR3_LOW, + ACT_COVER_AR3_LOW, + ACT_RANGE_AIM_AR3_LOW, + ACT_GESTURE_RANGE_ATTACK_AR3, + ACT_GESTURE_RELOAD_AR3, + + ACT_IDLE_AR3_RELAXED, + ACT_IDLE_AR3_STIMULATED, + ACT_WALK_AR3_RELAXED, + ACT_RUN_AR3_RELAXED, + ACT_WALK_AR3_STIMULATED, + ACT_RUN_AR3_STIMULATED, + + ACT_IDLE_AIM_AR3_STIMULATED, + ACT_WALK_AIM_AR3_STIMULATED, + ACT_RUN_AIM_AR3_STIMULATED, + + // SMG2 + ACT_IDLE_SMG2, + ACT_IDLE_ANGRY_SMG2, + ACT_WALK_SMG2, + ACT_RUN_SMG2, + ACT_WALK_AIM_SMG2, + ACT_RUN_AIM_SMG2, + //ACT_RANGE_ATTACK_SMG2, + ACT_RELOAD_SMG2, + ACT_RANGE_ATTACK_SMG2_LOW, + ACT_RELOAD_SMG2_LOW, + ACT_COVER_SMG2_LOW, + ACT_RANGE_AIM_SMG2_LOW, + //ACT_GESTURE_RANGE_ATTACK_SMG2, + ACT_GESTURE_RELOAD_SMG2, + + ACT_IDLE_SMG2_RELAXED, + ACT_IDLE_SMG2_STIMULATED, + ACT_WALK_SMG2_RELAXED, + ACT_RUN_SMG2_RELAXED, + ACT_WALK_SMG2_STIMULATED, + ACT_RUN_SMG2_STIMULATED, + + ACT_IDLE_AIM_SMG2_STIMULATED, + ACT_WALK_AIM_SMG2_STIMULATED, + ACT_RUN_AIM_SMG2_STIMULATED, + + // SMG3 (new) + ACT_IDLE_SMG3, + ACT_IDLE_ANGRY_SMG3, + ACT_WALK_SMG3, + ACT_RUN_SMG3, + ACT_WALK_AIM_SMG3, + ACT_RUN_AIM_SMG3, + ACT_RANGE_ATTACK_SMG3, + ACT_RELOAD_SMG3, + ACT_RANGE_ATTACK_SMG3_LOW, + ACT_RELOAD_SMG3_LOW, + ACT_COVER_SMG3_LOW, + ACT_RANGE_AIM_SMG3_LOW, + ACT_GESTURE_RANGE_ATTACK_SMG3, + ACT_GESTURE_RELOAD_SMG3, + + ACT_IDLE_SMG3_RELAXED, + ACT_IDLE_SMG3_STIMULATED, + ACT_WALK_SMG3_RELAXED, + ACT_RUN_SMG3_RELAXED, + ACT_WALK_SMG3_STIMULATED, + ACT_RUN_SMG3_STIMULATED, + + ACT_IDLE_AIM_SMG3_STIMULATED, + ACT_WALK_AIM_SMG3_STIMULATED, + ACT_RUN_AIM_SMG3_STIMULATED, + + // HMG1 + ACT_IDLE_HMG1, + ACT_IDLE_ANGRY_HMG1, + ACT_WALK_HMG1, + ACT_RUN_HMG1, + ACT_WALK_AIM_HMG1, + ACT_RUN_AIM_HMG1, + //ACT_RANGE_ATTACK_HMG1, + ACT_RELOAD_HMG1, + ACT_RANGE_ATTACK_HMG1_LOW, + ACT_RELOAD_HMG1_LOW, + ACT_COVER_HMG1_LOW, + ACT_RANGE_AIM_HMG1_LOW, + //ACT_GESTURE_RANGE_ATTACK_HMG1, + ACT_GESTURE_RELOAD_HMG1, + + ACT_IDLE_HMG1_RELAXED, + ACT_IDLE_HMG1_STIMULATED, + ACT_WALK_HMG1_RELAXED, + ACT_RUN_HMG1_RELAXED, + ACT_WALK_HMG1_STIMULATED, + ACT_RUN_HMG1_STIMULATED, + + ACT_IDLE_AIM_HMG1_STIMULATED, + ACT_WALK_AIM_HMG1_STIMULATED, + ACT_RUN_AIM_HMG1_STIMULATED, + + // Sniper Rifle + ACT_IDLE_SNIPER_RIFLE, + ACT_IDLE_ANGRY_SNIPER_RIFLE, + ACT_WALK_SNIPER_RIFLE, + ACT_RUN_SNIPER_RIFLE, + ACT_WALK_AIM_SNIPER_RIFLE, + ACT_RUN_AIM_SNIPER_RIFLE, + //ACT_RANGE_ATTACK_SNIPER_RIFLE, + ACT_RELOAD_SNIPER_RIFLE, + ACT_RANGE_ATTACK_SNIPER_RIFLE_LOW, + ACT_RELOAD_SNIPER_RIFLE_LOW, + ACT_COVER_SNIPER_RIFLE_LOW, + ACT_RANGE_AIM_SNIPER_RIFLE_LOW, + //ACT_GESTURE_RANGE_ATTACK_SNIPER_RIFLE, + ACT_GESTURE_RELOAD_SNIPER_RIFLE, + + ACT_IDLE_SNIPER_RIFLE_RELAXED, + ACT_IDLE_SNIPER_RIFLE_STIMULATED, + ACT_WALK_SNIPER_RIFLE_RELAXED, + ACT_RUN_SNIPER_RIFLE_RELAXED, + ACT_WALK_SNIPER_RIFLE_STIMULATED, + ACT_RUN_SNIPER_RIFLE_STIMULATED, + + ACT_IDLE_AIM_SNIPER_RIFLE_STIMULATED, + ACT_WALK_AIM_SNIPER_RIFLE_STIMULATED, + ACT_RUN_AIM_SNIPER_RIFLE_STIMULATED, + + // Dual Pistols + ACT_IDLE_DUAL_PISTOLS, + ACT_IDLE_ANGRY_DUAL_PISTOLS, + ACT_WALK_DUAL_PISTOLS, + ACT_RUN_DUAL_PISTOLS, + ACT_WALK_AIM_DUAL_PISTOLS, + ACT_RUN_AIM_DUAL_PISTOLS, + ACT_RANGE_ATTACK_DUAL_PISTOLS, + ACT_RELOAD_DUAL_PISTOLS, + ACT_RANGE_ATTACK_DUAL_PISTOLS_LOW, + ACT_RELOAD_DUAL_PISTOLS_LOW, + ACT_COVER_DUAL_PISTOLS_LOW, + ACT_RANGE_AIM_DUAL_PISTOLS_LOW, + ACT_GESTURE_RANGE_ATTACK_DUAL_PISTOLS, + ACT_GESTURE_RELOAD_DUAL_PISTOLS, + + ACT_IDLE_DUAL_PISTOLS_RELAXED, + ACT_IDLE_DUAL_PISTOLS_STIMULATED, + ACT_WALK_DUAL_PISTOLS_RELAXED, + ACT_RUN_DUAL_PISTOLS_RELAXED, + ACT_WALK_DUAL_PISTOLS_STIMULATED, + ACT_RUN_DUAL_PISTOLS_STIMULATED, + + ACT_IDLE_AIM_DUAL_PISTOLS_STIMULATED, + ACT_WALK_AIM_DUAL_PISTOLS_STIMULATED, + ACT_RUN_AIM_DUAL_PISTOLS_STIMULATED, +#endif + +#if EXPANDED_NAVIGATION_ACTIVITIES + ACT_CLIMB_ALL, // An actual blend animation which uses pose parameters for direction + ACT_CLIMB_IDLE, + + ACT_CLIMB_MOUNT_TOP, + ACT_CLIMB_MOUNT_BOTTOM, + ACT_CLIMB_DISMOUNT_BOTTOM, +#endif + +#if EXPANDED_HL2_COVER_ACTIVITIES + // Crouch Cover Medium + ACT_RANGE_ATTACK1_MED, + ACT_RANGE_ATTACK2_MED, + ACT_RANGE_AIM_MED, + + ACT_RANGE_ATTACK_AR2_MED, + ACT_RANGE_ATTACK_SMG1_MED, + ACT_RANGE_ATTACK_SHOTGUN_MED, + ACT_RANGE_ATTACK_PISTOL_MED, + ACT_RANGE_ATTACK_RPG_MED, + ACT_RANGE_ATTACK_REVOLVER_MED, + ACT_RANGE_ATTACK_CROSSBOW_MED, + + ACT_RANGE_AIM_AR2_MED, + ACT_RANGE_AIM_SMG1_MED, + ACT_RANGE_AIM_SHOTGUN_MED, + ACT_RANGE_AIM_PISTOL_MED, + ACT_RANGE_AIM_RPG_MED, + ACT_RANGE_AIM_REVOLVER_MED, + ACT_RANGE_AIM_CROSSBOW_MED, + +#if EXPANDED_HL2_UNUSED_WEAPON_ACTIVITIES + // MED activities for unused weapons + ACT_RANGE_AIM_AR1_MED, + ACT_RANGE_ATTACK_AR1_MED, + ACT_RANGE_AIM_AR3_MED, + ACT_RANGE_ATTACK_AR3_MED, + ACT_RANGE_AIM_SMG2_MED, + ACT_RANGE_ATTACK_SMG2_MED, + ACT_RANGE_AIM_SMG3_MED, + ACT_RANGE_ATTACK_SMG3_MED, + ACT_RANGE_AIM_HMG1_MED, + ACT_RANGE_ATTACK_HMG1_MED, + ACT_RANGE_AIM_SNIPER_RIFLE_MED, + ACT_RANGE_ATTACK_SNIPER_RIFLE_MED, + ACT_RANGE_AIM_DUAL_PISTOLS_MED, + ACT_RANGE_ATTACK_DUAL_PISTOLS_MED, +#endif + + // Wall Cover (for use in custom cover hints) + ACT_COVER_WALL_R, + ACT_COVER_WALL_L, + ACT_COVER_WALL_LOW_R, + ACT_COVER_WALL_LOW_L, + + ACT_COVER_WALL_R_RIFLE, + ACT_COVER_WALL_L_RIFLE, + ACT_COVER_WALL_LOW_R_RIFLE, + ACT_COVER_WALL_LOW_L_RIFLE, + + ACT_COVER_WALL_R_PISTOL, + ACT_COVER_WALL_L_PISTOL, + ACT_COVER_WALL_LOW_R_PISTOL, + ACT_COVER_WALL_LOW_L_PISTOL, +#endif + +#if EXPANDED_HL2DM_ACTIVITIES + ACT_HL2MP_WALK, + ACT_HL2MP_WALK_PISTOL, + ACT_HL2MP_WALK_SHOTGUN, + ACT_HL2MP_WALK_SMG1, + ACT_HL2MP_WALK_AR2, + ACT_HL2MP_WALK_PHYSGUN, + ACT_HL2MP_WALK_GRENADE, + ACT_HL2MP_WALK_RPG, + ACT_HL2MP_WALK_CROSSBOW, + ACT_HL2MP_WALK_MELEE, + ACT_HL2MP_WALK_SLAM, + + ACT_HL2MP_GESTURE_RANGE_ATTACK2, + ACT_HL2MP_GESTURE_RANGE_ATTACK2_PISTOL, + ACT_HL2MP_GESTURE_RANGE_ATTACK2_SHOTGUN, + ACT_HL2MP_GESTURE_RANGE_ATTACK2_SMG1, + ACT_HL2MP_GESTURE_RANGE_ATTACK2_AR2, + ACT_HL2MP_GESTURE_RANGE_ATTACK2_PHYSGUN, + ACT_HL2MP_GESTURE_RANGE_ATTACK2_GRENADE, + ACT_HL2MP_GESTURE_RANGE_ATTACK2_RPG, + ACT_HL2MP_GESTURE_RANGE_ATTACK2_CROSSBOW, + ACT_HL2MP_GESTURE_RANGE_ATTACK2_MELEE, + ACT_HL2MP_GESTURE_RANGE_ATTACK2_SLAM, + + ACT_HL2MP_IDLE_REVOLVER, + ACT_HL2MP_RUN_REVOLVER, + ACT_HL2MP_WALK_REVOLVER, + ACT_HL2MP_IDLE_CROUCH_REVOLVER, + ACT_HL2MP_WALK_CROUCH_REVOLVER, + ACT_HL2MP_GESTURE_RANGE_ATTACK_REVOLVER, + ACT_HL2MP_GESTURE_RANGE_ATTACK2_REVOLVER, + ACT_HL2MP_GESTURE_RELOAD_REVOLVER, + ACT_HL2MP_JUMP_REVOLVER, + +#if EXPANDED_HL2_UNUSED_WEAPON_ACTIVITIES + // Player activities for unused weapons + ACT_HL2MP_IDLE_AR1, + ACT_HL2MP_RUN_AR1, + ACT_HL2MP_WALK_AR1, + ACT_HL2MP_IDLE_CROUCH_AR1, + ACT_HL2MP_WALK_CROUCH_AR1, + ACT_HL2MP_GESTURE_RANGE_ATTACK_AR1, + ACT_HL2MP_GESTURE_RANGE_ATTACK2_AR1, + ACT_HL2MP_GESTURE_RELOAD_AR1, + ACT_HL2MP_JUMP_AR1, + + ACT_HL2MP_IDLE_AR3, + ACT_HL2MP_RUN_AR3, + ACT_HL2MP_WALK_AR3, + ACT_HL2MP_IDLE_CROUCH_AR3, + ACT_HL2MP_WALK_CROUCH_AR3, + ACT_HL2MP_GESTURE_RANGE_ATTACK_AR3, + ACT_HL2MP_GESTURE_RANGE_ATTACK2_AR3, + ACT_HL2MP_GESTURE_RELOAD_AR3, + ACT_HL2MP_JUMP_AR3, + + ACT_HL2MP_IDLE_SMG2, + ACT_HL2MP_RUN_SMG2, + ACT_HL2MP_WALK_SMG2, + ACT_HL2MP_IDLE_CROUCH_SMG2, + ACT_HL2MP_WALK_CROUCH_SMG2, + ACT_HL2MP_GESTURE_RANGE_ATTACK_SMG2, + ACT_HL2MP_GESTURE_RANGE_ATTACK2_SMG2, + ACT_HL2MP_GESTURE_RELOAD_SMG2, + ACT_HL2MP_JUMP_SMG2, + + ACT_HL2MP_IDLE_SMG3, + ACT_HL2MP_RUN_SMG3, + ACT_HL2MP_WALK_SMG3, + ACT_HL2MP_IDLE_CROUCH_SMG3, + ACT_HL2MP_WALK_CROUCH_SMG3, + ACT_HL2MP_GESTURE_RANGE_ATTACK_SMG3, + ACT_HL2MP_GESTURE_RANGE_ATTACK2_SMG3, + ACT_HL2MP_GESTURE_RELOAD_SMG3, + ACT_HL2MP_JUMP_SMG3, + + ACT_HL2MP_IDLE_HMG1, + ACT_HL2MP_RUN_HMG1, + ACT_HL2MP_WALK_HMG1, + ACT_HL2MP_IDLE_CROUCH_HMG1, + ACT_HL2MP_WALK_CROUCH_HMG1, + ACT_HL2MP_GESTURE_RANGE_ATTACK_HMG1, + ACT_HL2MP_GESTURE_RANGE_ATTACK2_HMG1, + ACT_HL2MP_GESTURE_RELOAD_HMG1, + ACT_HL2MP_JUMP_HMG1, + + ACT_HL2MP_IDLE_SNIPER_RIFLE, + ACT_HL2MP_RUN_SNIPER_RIFLE, + ACT_HL2MP_WALK_SNIPER_RIFLE, + ACT_HL2MP_IDLE_CROUCH_SNIPER_RIFLE, + ACT_HL2MP_WALK_CROUCH_SNIPER_RIFLE, + ACT_HL2MP_GESTURE_RANGE_ATTACK_SNIPER_RIFLE, + ACT_HL2MP_GESTURE_RANGE_ATTACK2_SNIPER_RIFLE, + ACT_HL2MP_GESTURE_RELOAD_SNIPER_RIFLE, + ACT_HL2MP_JUMP_SNIPER_RIFLE, + + ACT_HL2MP_IDLE_DUAL_PISTOLS, + ACT_HL2MP_RUN_DUAL_PISTOLS, + ACT_HL2MP_WALK_DUAL_PISTOLS, + ACT_HL2MP_IDLE_CROUCH_DUAL_PISTOLS, + ACT_HL2MP_WALK_CROUCH_DUAL_PISTOLS, + ACT_HL2MP_GESTURE_RANGE_ATTACK_DUAL_PISTOLS, + ACT_HL2MP_GESTURE_RANGE_ATTACK2_DUAL_PISTOLS, + ACT_HL2MP_GESTURE_RELOAD_DUAL_PISTOLS, + ACT_HL2MP_JUMP_DUAL_PISTOLS, +#endif + + ACT_HL2MP_IDLE_USE, + ACT_HL2MP_RUN_USE, + ACT_HL2MP_WALK_USE, + ACT_HL2MP_IDLE_CROUCH_USE, + ACT_HL2MP_WALK_CROUCH_USE, + ACT_HL2MP_JUMP_USE, + + ACT_HL2MP_IDLE_USE_HEAVY, + ACT_HL2MP_RUN_USE_HEAVY, + ACT_HL2MP_WALK_USE_HEAVY, + ACT_HL2MP_IDLE_CROUCH_USE_HEAVY, + ACT_HL2MP_WALK_CROUCH_USE_HEAVY, + ACT_HL2MP_JUMP_USE_HEAVY, +#endif // this is the end of the global activities, private per-monster activities start here. LAST_SHARED_ACTIVITY, diff --git a/src/game/shared/ai_criteria_new.cpp b/src/game/shared/ai_criteria_new.cpp new file mode 100644 index 00000000..837c61aa --- /dev/null +++ b/src/game/shared/ai_criteria_new.cpp @@ -0,0 +1,38 @@ +//===== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======// +// +// Purpose: +// +// $NoKeywords: $ +// +//===========================================================================// +#include "cbase.h" +#include "AI_Criteria.h" + +#ifdef GAME_DLL +#include "ai_speech.h" +#endif + +#include +#include "engine/IEngineSound.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include + + + +BEGIN_SIMPLE_DATADESC( AI_ResponseParams ) + DEFINE_FIELD( flags, FIELD_SHORT ), + DEFINE_FIELD( odds, FIELD_SHORT ), + DEFINE_FIELD( soundlevel, FIELD_CHARACTER ), + DEFINE_FIELD( delay, FIELD_INTEGER ), // These are compressed down to two float16s, so treat as an INT for saverestore + DEFINE_FIELD( respeakdelay, FIELD_INTEGER ), // +END_DATADESC() + +BEGIN_SIMPLE_DATADESC( AI_Response ) + DEFINE_FIELD( m_Type, FIELD_CHARACTER ), + DEFINE_ARRAY( m_szResponseName, FIELD_CHARACTER, AI_Response::MAX_RESPONSE_NAME ), + DEFINE_ARRAY( m_szMatchingRule, FIELD_CHARACTER, AI_Response::MAX_RULE_NAME ), + // DEFINE_FIELD( m_pCriteria, FIELD_??? ), // Don't need to save this probably + DEFINE_EMBEDDED( m_Params ), +END_DATADESC() + diff --git a/src/game/shared/ai_criteria_new.h b/src/game/shared/ai_criteria_new.h new file mode 100644 index 00000000..b5d2c4fd --- /dev/null +++ b/src/game/shared/ai_criteria_new.h @@ -0,0 +1,41 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#ifndef AI_CRITERIA_H +#define AI_CRITERIA_H +#ifdef _WIN32 +#pragma once +#endif + +#include "tier1/utlrbtree.h" +#include "tier1/utlsymbol.h" +#include "tier1/interval.h" +#include "mathlib/compressed_vector.h" +#include "../../public/responserules/response_types.h" + + +using ResponseRules::ResponseType_t; + +extern const char *SplitContext( const char *raw, char *key, int keylen, char *value, int valuelen, float *duration, const char *entireContext ); + +#ifndef AI_CriteriaSet +#define AI_CriteriaSet ResponseRules::CriteriaSet +#endif + +typedef ResponseRules::ResponseParams AI_ResponseParams ; +typedef ResponseRules::CRR_Response AI_Response; + + + +/* +// An AI response that is dynamically new'ed up and returned from SpeakFindResponse. +class AI_ResponseReturnValue : AI_Response +{ + +}; +*/ + +#endif // AI_CRITERIA_H diff --git a/src/game/shared/ai_responsesystem_new.cpp b/src/game/shared/ai_responsesystem_new.cpp new file mode 100644 index 00000000..b639f270 --- /dev/null +++ b/src/game/shared/ai_responsesystem_new.cpp @@ -0,0 +1,1388 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + + +#include "cbase.h" +#include "SoundEmitterSystem/isoundemittersystembase.h" +#include "AI_ResponseSystem.h" +#include "igamesystem.h" +#include "AI_Criteria.h" +#include +#include "filesystem.h" +#include "utldict.h" +#ifdef GAME_DLL +#include "ai_speech.h" +#endif +#include "tier0/icommandline.h" +#include +#include "isaverestore.h" +#include "utlbuffer.h" +#include "stringpool.h" +#include "fmtstr.h" +#include "multiplay_gamerules.h" +#include "characterset.h" +#include "responserules/response_host_interface.h" +#include "../../responserules/runtime/response_types_internal.h" + +#include "scenefilecache/ISceneFileCache.h" + +#ifdef GAME_DLL +#include "sceneentity.h" +#endif + +#include "networkstringtabledefs.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +#undef IResponseSystem +using namespace ResponseRules; + +extern ConVar rr_debugresponses; // ( "rr_debugresponses", "0", FCVAR_NONE, "Show verbose matching output (1 for simple, 2 for rule scoring, 3 for noisy). If set to 4, it will only show response success/failure for npc_selected NPCs." ); +extern ConVar rr_debugrule; // ( "rr_debugrule", "", FCVAR_NONE, "If set to the name of the rule, that rule's score will be shown whenever a concept is passed into the response rules system."); +extern ConVar rr_dumpresponses; // ( "rr_dumpresponses", "0", FCVAR_NONE, "Dump all response_rules.txt and rules (requires restart)" ); +extern ConVar rr_debugresponseconcept; // ( "rr_debugresponseconcept", "", FCVAR_NONE, "If set, rr_debugresponses will print only responses testing for the specified concept" ); + +static void CC_RR_DumpHashInfo( const CCommand &args ); + +#ifdef MAPBASE +ConVar rr_enhanced_saverestore( "rr_enhanced_saverestore", "0", FCVAR_NONE, "Enables enhanced save/restore capabilities for the Response System." ); +#endif + +extern ISceneFileCache *scenefilecache; +extern INetworkStringTable *g_pStringTableClientSideChoreoScenes; + +static characterset_t g_BreakSetIncludingColons; + +// Simple class to initialize breakset +class CBreakInit +{ +public: + CBreakInit() + { + CharacterSetBuild( &g_BreakSetIncludingColons, "{}()':" ); + } +} g_BreakInit; + +inline char rr_tolower( char c ) +{ + if ( c >= 'A' && c <= 'Z' ) + return c - 'A' + 'a'; + return c; +} +// BUG BUG: Note that this function doesn't check for data overruns!!! +// Also, this function lowercases the token as it parses!!! +inline const char *RR_Parse(const char *data, char *token ) +{ + unsigned char c; + int len; + characterset_t *breaks = &g_BreakSetIncludingColons; + len = 0; + token[0] = 0; + + if (!data) + return NULL; + + // skip whitespace +skipwhite: + while ( (c = *data) <= ' ') + { + if (c == 0) + return NULL; // end of file; + data++; + } + + // skip // comments + if (c=='/' && data[1] == '/') + { + while (*data && *data != '\n') + data++; + goto skipwhite; + } + + + // handle quoted strings specially + if (c == '\"') + { + data++; + while (1) + { + c = rr_tolower( *data++ ); + if (c=='\"' || !c) + { + token[len] = 0; + return data; + } + token[len] = c; + len++; + } + } + + // parse single characters + if ( IN_CHARACTERSET( *breaks, c ) ) + { + token[len] = c; + len++; + token[len] = 0; + return data+1; + } + + // parse a regular word + do + { + token[len] = rr_tolower( c ); + data++; + len++; + c = rr_tolower( *data ); + if ( IN_CHARACTERSET( *breaks, c ) ) + break; + } while (c>32); + + token[len] = 0; + return data; +} + +#ifdef MAPBASE +// A version of the above which preserves casing and supports escaped quotes +inline const char *RR_Parse_Preserve(const char *data, char *token ) +{ + unsigned char c; + int len; + characterset_t *breaks = &g_BreakSetIncludingColons; + len = 0; + token[0] = 0; + + if (!data) + return NULL; + + // skip whitespace +skipwhite: + while ( (c = *data) <= ' ') + { + if (c == 0) + return NULL; // end of file; + data++; + } + + // skip // comments + if (c=='/' && data[1] == '/') + { + while (*data && *data != '\n') + data++; + goto skipwhite; + } + + // handle quoted strings specially + if (c == '\"') + { + bool escaped = false; + data++; + while (1) + { + c = *data++; + if ((c=='\"' && !escaped) || !c) + { + token[len] = 0; + return data; + } + else if (c != '\"' && escaped) + { + // Not an escape character, just a back slash + token[len] = '\\'; + len++; + } + + escaped = (c == '\\'); + if (!escaped) + { + token[len] = c; + len++; + } + } + } + + // parse single characters + if ( IN_CHARACTERSET( *breaks, c ) ) + { + token[len] = c; + len++; + token[len] = 0; + return data+1; + } + + // parse a regular word + do + { + token[len] = c; + data++; + len++; + c = *data; + if ( IN_CHARACTERSET( *breaks, c ) ) + break; + } while (c>32); + + token[len] = 0; + return data; +} +#endif + +namespace ResponseRules +{ + extern const char *ResponseCopyString( const char *in ); +} + +// Host functions required by the ResponseRules::IEngineEmulator interface +class CResponseRulesToEngineInterface : public ResponseRules::IEngineEmulator +{ + /// Given an input text buffer data pointer, parses a single token into the variable token and returns the new + /// reading position + virtual const char *ParseFile( const char *data, char *token, int maxlen ) + { + NOTE_UNUSED( maxlen ); + return RR_Parse( data, token ); + } + +#ifdef MAPBASE + /// (Optional) Same as ParseFile, but with casing preserved and escaped quotes supported + virtual const char *ParseFilePreserve( const char *data, char *token, int maxlen ) + { + NOTE_UNUSED( maxlen ); + return RR_Parse_Preserve( data, token ); + } +#endif + + /// Return a pointer to an IFileSystem we can use to read and process scripts. + virtual IFileSystem *GetFilesystem() + { + return filesystem; + } + + /// Return a pointer to an instance of an IUniformRandomStream + virtual IUniformRandomStream *GetRandomStream() + { + return random; + } + + /// Return a pointer to a tier0 ICommandLine + virtual ICommandLine *GetCommandLine() + { + return CommandLine(); + } + + /// Emulates the server's UTIL_LoadFileForMe + virtual byte *LoadFileForMe( const char *filename, int *pLength ) + { + return UTIL_LoadFileForMe( filename, pLength ); + } + + /// Emulates the server's UTIL_FreeFile + virtual void FreeFile( byte *buffer ) + { + return UTIL_FreeFile( buffer ); + } + +}; + +CResponseRulesToEngineInterface g_ResponseRulesEngineWrapper; +IEngineEmulator *IEngineEmulator::s_pSingleton = &g_ResponseRulesEngineWrapper; + + +BEGIN_SIMPLE_DATADESC( ParserResponse ) + // DEFINE_FIELD( type, FIELD_INTEGER ), + // DEFINE_ARRAY( value, FIELD_CHARACTER ), + // DEFINE_FIELD( weight, FIELD_FLOAT ), + DEFINE_FIELD( depletioncount, FIELD_CHARACTER ), + // DEFINE_FIELD( first, FIELD_BOOLEAN ), + // DEFINE_FIELD( last, FIELD_BOOLEAN ), +END_DATADESC() + + +BEGIN_SIMPLE_DATADESC( ResponseGroup ) + // DEFINE_FIELD( group, FIELD_UTLVECTOR ), + // DEFINE_FIELD( rp, FIELD_EMBEDDED ), + // DEFINE_FIELD( m_bDepleteBeforeRepeat, FIELD_BOOLEAN ), + DEFINE_FIELD( m_nDepletionCount, FIELD_CHARACTER ), + // DEFINE_FIELD( m_bHasFirst, FIELD_BOOLEAN ), + // DEFINE_FIELD( m_bHasLast, FIELD_BOOLEAN ), + // DEFINE_FIELD( m_bSequential, FIELD_BOOLEAN ), + // DEFINE_FIELD( m_bNoRepeat, FIELD_BOOLEAN ), + DEFINE_FIELD( m_bEnabled, FIELD_BOOLEAN ), + DEFINE_FIELD( m_nCurrentIndex, FIELD_CHARACTER ), +END_DATADESC() + + +/// Add some game-specific code to the basic response system +/// (eg, the scene precacher, which requires the client and server +/// to work) + +class CGameResponseSystem : public CResponseSystem +{ +public: + CGameResponseSystem(); + + virtual void Precache(); + virtual void PrecacheResponses( bool bEnable ) + { + m_bPrecache = bEnable; + } + bool ShouldPrecache() { return m_bPrecache; } + +protected: + bool m_bPrecache; +}; + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CGameResponseSystem::CGameResponseSystem() : m_bPrecache(true) +{}; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- + +#if 0 +class CScenePrecacheSystem : public CAutoGameSystem +{ +public: + CScenePrecacheSystem() : CAutoGameSystem( "CScenePrecacheSystem" ), m_RepeatCounts( 0, 0, DefLessFunc( int ) ) + { + } + + // Level init, shutdown + virtual void LevelShutdownPreEntity() + { + m_RepeatCounts.Purge(); + } + + bool ShouldPrecache( char const *pszScene ) + { + int hash = HashStringCaselessConventional( pszScene ); + + int slot = m_RepeatCounts.Find( hash ); + if ( slot != m_RepeatCounts.InvalidIndex() ) + { + m_RepeatCounts[ slot ]++; + return false; + } + + m_RepeatCounts.Insert( hash, 0 ); + return true; + } + +private: + + CUtlMap< int, int > m_RepeatCounts; +}; + +static CScenePrecacheSystem g_ScenePrecacheSystem; +//----------------------------------------------------------------------------- +// Purpose: Used for precaching instanced scenes +// Input : *pszScene - +//----------------------------------------------------------------------------- +void PrecacheInstancedScene( char const *pszScene ) +{ + static int nMakingReslists = -1; + + if ( !g_ScenePrecacheSystem.ShouldPrecache( pszScene ) ) + return; + + if ( nMakingReslists == -1 ) + { + nMakingReslists = CommandLine()->FindParm( "-makereslists" ) > 0 ? 1 : 0; + } + + if ( nMakingReslists == 1 ) + { + // Just stat the file to add to reslist + g_pFullFileSystem->Size( pszScene ); + } + + // verify existence, cache is pre-populated, should be there + SceneCachedData_t sceneData; + if ( !scenefilecache->GetSceneCachedData( pszScene, &sceneData ) ) + { + // Scenes are sloppy and don't always exist. + // A scene that is not in the pre-built cache image, but on disk, is a true error. + if ( IsX360() && ( g_pFullFileSystem->GetDVDMode() != DVDMODE_STRICT ) && g_pFullFileSystem->FileExists( pszScene, "GAME" ) ) + { + Warning( "PrecacheInstancedScene: Missing scene '%s' from scene image cache.\nRebuild scene image cache!\n", pszScene ); + } + } + else + { + for ( int i = 0; i < sceneData.numSounds; ++i ) + { + short stringId = scenefilecache->GetSceneCachedSound( sceneData.sceneId, i ); + CBaseEntity::PrecacheScriptSound( scenefilecache->GetSceneString( stringId ) ); + } + } + + g_pStringTableClientSideChoreoScenes->AddString( CBaseEntity::IsServer(), pszScene ); +} +#endif + +static void TouchFile( char const *pchFileName ) +{ + IEngineEmulator::Get()->GetFilesystem()->Size( pchFileName ); +} + +void CGameResponseSystem::Precache() +{ + bool bTouchFiles = CommandLine()->FindParm( "-makereslists" ) != 0; + + // enumerate and mark all the scripts so we know they're referenced + for ( int i = 0; i < (int)m_Responses.Count(); i++ ) + { + ResponseGroup &group = m_Responses[i]; + + for ( int j = 0; j < group.group.Count(); j++) + { + ParserResponse &response = group.group[j]; + + switch ( response.type ) + { + default: + break; + case RESPONSE_SCENE: + { + // fixup $gender references + char file[_MAX_PATH]; + Q_strncpy( file, response.value, sizeof(file) ); + char *gender = strstr( file, "$gender" ); + if ( gender ) + { + // replace with male & female + const char *postGender = gender + strlen("$gender"); + *gender = 0; + char genderFile[_MAX_PATH]; + // male + Q_snprintf( genderFile, sizeof(genderFile), "%smale%s", file, postGender); + + PrecacheInstancedScene( genderFile ); + if ( bTouchFiles ) + { + TouchFile( genderFile ); + } + + Q_snprintf( genderFile, sizeof(genderFile), "%sfemale%s", file, postGender); + + PrecacheInstancedScene( genderFile ); + if ( bTouchFiles ) + { + TouchFile( genderFile ); + } + } + else + { + PrecacheInstancedScene( file ); + if ( bTouchFiles ) + { + TouchFile( file ); + } + } + } + break; + case RESPONSE_SPEAK: + { + CBaseEntity::PrecacheScriptSound( response.value ); + } + break; + } + } + } +} + + +//----------------------------------------------------------------------------- +// Purpose: A special purpose response system associated with a custom entity +//----------------------------------------------------------------------------- +class CInstancedResponseSystem : public CGameResponseSystem +{ + typedef CGameResponseSystem BaseClass; + +public: + CInstancedResponseSystem( const char *scriptfile ) : + m_pszScriptFile( 0 ) + { + Assert( scriptfile ); + + int len = Q_strlen( scriptfile ) + 1; + m_pszScriptFile = new char[ len ]; + Assert( m_pszScriptFile ); + Q_strncpy( m_pszScriptFile, scriptfile, len ); + } + + ~CInstancedResponseSystem() + { + delete[] m_pszScriptFile; + } + virtual const char *GetScriptFile( void ) + { + Assert( m_pszScriptFile ); + return m_pszScriptFile; + } + + // CAutoGameSystem + virtual bool Init() + { + const char *basescript = GetScriptFile(); + LoadRuleSet( basescript ); + return true; + } + + virtual void LevelInitPostEntity() + { +#ifdef MAPBASE + if (!rr_enhanced_saverestore.GetBool() || gpGlobals->eLoadType != MapLoad_Transition) +#endif + ResetResponseGroups(); + } + + virtual void Release() + { + Clear(); + delete this; + } +private: + + char *m_pszScriptFile; +}; + +//----------------------------------------------------------------------------- +// Purpose: The default response system for expressive AIs +//----------------------------------------------------------------------------- +class CDefaultResponseSystem : public CGameResponseSystem, public CAutoGameSystem +{ + typedef CAutoGameSystem BaseClass; + +public: + CDefaultResponseSystem() : CAutoGameSystem( "CDefaultResponseSystem" ) + { + } + + virtual const char *GetScriptFile( void ) + { + return "scripts/talker/response_rules.txt"; + } + + // CAutoServerSystem + virtual bool Init(); + virtual void Shutdown(); + + virtual void LevelInitPostEntity() + { +#ifdef MAPBASE + // CInstancedResponseSystem is not a CAutoGameSystem, so this needs to be called manually. + // The same could've been accomplished by making CInstancedResponseSystem derive from CAutoGameSystem, + // but their instanced nature would've complicated things a lot. + int c = m_InstancedSystems.Count(); + for ( int i = c - 1 ; i >= 0; i-- ) + { + m_InstancedSystems[i]->LevelInitPostEntity(); + } +#endif + } + + virtual void Release() + { + Assert( 0 ); + } + + void AddInstancedResponseSystem( const char *scriptfile, CInstancedResponseSystem *sys ) + { + m_InstancedSystems.Insert( scriptfile, sys ); + } + + CInstancedResponseSystem *FindResponseSystem( const char *scriptfile ) + { + int idx = m_InstancedSystems.Find( scriptfile ); + if ( idx == m_InstancedSystems.InvalidIndex() ) + return NULL; + return m_InstancedSystems[ idx ]; + } + + IResponseSystem *PrecacheCustomResponseSystem( const char *scriptfile ) + { + COM_TimestampedLog( "PrecacheCustomResponseSystem %s - Start", scriptfile ); + CInstancedResponseSystem *sys = ( CInstancedResponseSystem * )FindResponseSystem( scriptfile ); + if ( !sys ) + { + sys = new CInstancedResponseSystem( scriptfile ); + if ( !sys ) + { + Error( "Failed to load response system data from %s", scriptfile ); + } + + if ( !sys->Init() ) + { + Error( "CInstancedResponseSystem: Failed to init response system from %s!", scriptfile ); + } + + AddInstancedResponseSystem( scriptfile, sys ); + } + + sys->Precache(); + + COM_TimestampedLog( "PrecacheCustomResponseSystem %s - Finish", scriptfile ); + + return ( IResponseSystem * )sys; + } + + IResponseSystem *BuildCustomResponseSystemGivenCriteria( const char *pszBaseFile, const char *pszCustomName, AI_CriteriaSet &criteriaSet, float flCriteriaScore ); + void DestroyCustomResponseSystems(); + + virtual void LevelInitPreEntity() + { + // This will precache the default system + // All user installed systems are init'd by PrecacheCustomResponseSystem which will call sys->Precache() on the ones being used + + // FIXME: This is SLOW the first time you run the engine (can take 3 - 10 seconds!!!) + if ( ShouldPrecache() ) + { + Precache(); + } + +#ifdef MAPBASE + if (!rr_enhanced_saverestore.GetBool() || gpGlobals->eLoadType != MapLoad_Transition) +#endif + ResetResponseGroups(); + } + + void ReloadAllResponseSystems() + { + Clear(); + Init(); + + int c = m_InstancedSystems.Count(); + for ( int i = c - 1 ; i >= 0; i-- ) + { + CInstancedResponseSystem *sys = m_InstancedSystems[ i ]; + if ( !IsCustomManagable() ) + { + sys->Clear(); + sys->Init(); + } + else + { + // Custom reponse rules will manage/reload themselves - remove them. + m_InstancedSystems.RemoveAt( i ); + } + } + + // precache sounds in case we added new ones + Precache(); + + } + +private: + + void ClearInstanced() + { + int c = m_InstancedSystems.Count(); + for ( int i = c - 1 ; i >= 0; i-- ) + { + CInstancedResponseSystem *sys = m_InstancedSystems[ i ]; + sys->Release(); + } + m_InstancedSystems.RemoveAll(); + } + + CUtlDict< CInstancedResponseSystem *, int > m_InstancedSystems; + friend void CC_RR_DumpHashInfo( const CCommand &args ); +}; + +IResponseSystem *CDefaultResponseSystem::BuildCustomResponseSystemGivenCriteria( const char *pszBaseFile, const char *pszCustomName, AI_CriteriaSet &criteriaSet, float flCriteriaScore ) +{ + // Create a instanced response system. + CInstancedResponseSystem *pCustomSystem = new CInstancedResponseSystem( pszCustomName ); + if ( !pCustomSystem ) + { + Error( "BuildCustomResponseSystemGivenCriterea: Failed to create custom response system %s!", pszCustomName ); + } + + pCustomSystem->Clear(); + + // Copy the relevant rules and data. + /* + int nRuleCount = m_Rules.Count(); + for ( int iRule = 0; iRule < nRuleCount; ++iRule ) + */ + for ( ResponseRulePartition::tIndex iIdx = m_RulePartitions.First() ; + m_RulePartitions.IsValid(iIdx) ; + iIdx = m_RulePartitions.Next( iIdx ) ) + { + Rule *pRule = &m_RulePartitions[iIdx]; + if ( pRule ) + { + float flScore = 0.0f; + + int nCriteriaCount = pRule->m_Criteria.Count(); + for ( int iCriteria = 0; iCriteria < nCriteriaCount; ++iCriteria ) + { + int iRuleCriteria = pRule->m_Criteria[iCriteria]; + + flScore += LookForCriteria( criteriaSet, iRuleCriteria ); + if ( flScore >= flCriteriaScore ) + { + CopyRuleFrom( pRule, iIdx, pCustomSystem ); + break; + } + } + } + } + + // Set as a custom response system. + m_bCustomManagable = true; + AddInstancedResponseSystem( pszCustomName, pCustomSystem ); + + // pCustomSystem->DumpDictionary( pszCustomName ); + + return pCustomSystem; +} + +void CDefaultResponseSystem::DestroyCustomResponseSystems() +{ + ClearInstanced(); +} + + +static CDefaultResponseSystem defaultresponsesytem; +IResponseSystem *g_pResponseSystem = &defaultresponsesytem; + +CON_COMMAND( rr_reloadresponsesystems, "Reload all response system scripts." ) +{ +#ifdef GAME_DLL + if ( !UTIL_IsCommandIssuedByServerAdmin() ) + return; +#endif + + defaultresponsesytem.ReloadAllResponseSystems(); +} + +#if RR_DUMPHASHINFO_ENABLED +static void CC_RR_DumpHashInfo( const CCommand &args ) +{ + defaultresponsesytem.m_RulePartitions.PrintBucketInfo( &defaultresponsesytem ); +} +static ConCommand rr_dumphashinfo( "rr_dumphashinfo", CC_RR_DumpHashInfo, "Statistics on primary hash bucketing of response rule partitions"); +#endif + +#ifdef MAPBASE +// Designed for extern magic, this gives the <, >, etc. of response system criteria to the outside world. +// Mostly just used for Matcher_Match in matchers.h. +bool ResponseSystemCompare( const char *criterion, const char *value ) +{ + Criteria criteria; + criteria.value = criterion; + defaultresponsesytem.ComputeMatcher( &criteria, criteria.matcher ); + return defaultresponsesytem.CompareUsingMatcher( value, criteria.matcher, true ); + + return false; +} + +//----------------------------------------------------------------------------- +// CResponseFilePrecacher +// +// Purpose: Precaches a single talker file. That's it. +// +// It copies from a bunch of the original Response System class and therefore it's really messy. +// Despite the horrors a normal programmer might find in here, I think it performs better than anything else I could've come up with. +//----------------------------------------------------------------------------- +/* +class CResponseFilePrecacher +{ +public: + + // Stuff copied from the Response System. + // Direct copy-pastes are very compact, to say the least. + inline bool ParseToken( void ) + { + if ( m_bUnget ) + { m_bUnget = false; return true; } + if ( m_ScriptStack.Count() <= 0 ) + { return false; } + + m_ScriptStack[ 0 ].currenttoken = engine->ParseFile( m_ScriptStack[ 0 ].currenttoken, token, sizeof( token ) ); + m_ScriptStack[ 0 ].tokencount++; + return m_ScriptStack[ 0 ].currenttoken != NULL ? true : false; + } + + CUtlVector< CResponseSystem::ScriptEntry > m_ScriptStack; + bool m_bUnget; + char token[ 1204 ]; + + + void PrecacheResponse( const char *response, byte type ) + { + switch ( type ) + { + default: + break; + case RESPONSE_SCENE: + { + DevMsg("Precaching scene %s...\n", response); + + // fixup $gender references + char file[_MAX_PATH]; + Q_strncpy( file, response, sizeof(file) ); + char *gender = strstr( file, "$gender" ); + if ( gender ) + { + // replace with male & female + const char *postGender = gender + strlen("$gender"); + *gender = 0; + char genderFile[_MAX_PATH]; + + Q_snprintf( genderFile, sizeof(genderFile), "%smale%s", file, postGender); + PrecacheInstancedScene( genderFile ); + + Q_snprintf( genderFile, sizeof(genderFile), "%sfemale%s", file, postGender); + PrecacheInstancedScene( genderFile ); + } + else + { + PrecacheInstancedScene( file ); + } + } + break; + case RESPONSE_SPEAK: + { + DevMsg("Precaching sound %s...\n", response); + CBaseEntity::PrecacheScriptSound( response ); + } + break; + } + } + + bool IsRootCommand() + { + if (!Q_stricmp( token, "#include" ) || !Q_stricmp( token, "response" ) + || !Q_stricmp( token, "enumeration" ) || !Q_stricmp( token, "criteria" ) + || !Q_stricmp( token, "criterion" ) || !Q_stricmp( token, "rule" )) + return true; + return false; + } + + void ParseResponse( void ) + { + // Must go to response group name + ParseToken(); + + while ( 1 ) + { + ParseToken(); + + if ( !Q_stricmp( token, "{" ) ) + { + while ( 1 ) + { + ParseToken(); + if ( !Q_stricmp( token, "}" ) ) + break; + + byte type = ComputeResponseType( token ); + if (type == RESPONSE_NONE) + continue; + + ParseToken(); + char *value = CopyString( token ); + + PrecacheResponse(value, type); + } + break; + } + + byte type = ComputeResponseType( token ); + if (type == RESPONSE_NONE) + break; + + ParseToken(); + char *value = CopyString( token ); + + PrecacheResponse(value, type); + + break; + } + } + + bool LoadFromBuffer(const char *scriptfile, unsigned char *buffer, CStringPool &includedFiles) + { + includedFiles.Allocate( scriptfile ); + + CResponseSystem::ScriptEntry e; + e.name = filesystem->FindOrAddFileName( scriptfile ); + e.buffer = buffer; + e.currenttoken = (char *)e.buffer; + e.tokencount = 0; + m_ScriptStack.AddToHead( e ); + + while ( 1 ) + { + ParseToken(); + if ( !token[0] ) + { + break; + } + + if ( !Q_stricmp( token, "response" ) ) + { + ParseResponse(); + } + else if ( !Q_stricmp( token, "#include" ) || !Q_stricmp( token, "#base" ) ) + { + // Compacted version of ParseInclude(), including new changes. + // Look at that if you want to read. + char includefile[ 256 ]; + ParseToken(); + if (scriptfile) { size_t len = strlen(scriptfile)-1; + for (size_t i = 0; i < len; i++) + { if (scriptfile[i] == CORRECT_PATH_SEPARATOR || scriptfile[i] == INCORRECT_PATH_SEPARATOR) + { len = i; } + } Q_strncpy(includefile, scriptfile, len+1); + if (len+1 != strlen(scriptfile)) + { Q_snprintf(includefile, sizeof(includefile), "%s/%s", includefile, token); } + else includefile[0] = '\0'; + } if (!includefile[0]) Q_snprintf( includefile, sizeof( includefile ), "scripts/%s", token ); + + if ( includedFiles.Find( includefile ) == NULL ) + { + MEM_ALLOC_CREDIT(); + + // Try and load it + CUtlBuffer buf; + if ( filesystem->ReadFile( includefile, "GAME", buf ) ) + { + LoadFromBuffer( includefile, (unsigned char *)buf.PeekGet(), includedFiles ); + } + } + } + } + + if ( m_ScriptStack.Count() > 0 ) + m_ScriptStack.Remove( 0 ); + + return true; + } +}; +*/ + +// Loads a file directly to the main response system +bool LoadResponseSystemFile(const char *scriptfile) +{ + CUtlBuffer buf; + if ( !filesystem->ReadFile( scriptfile, "GAME", buf ) ) + { + return false; + } + + // This is a really messy and specialized system that precaches the responses and only the responses of a talker file. + /* + CStringPool includedFiles; + CResponseFilePrecacher *rs = new CResponseFilePrecacher(); + if (!rs || !rs->LoadFromBuffer(scriptfile, (unsigned char *)buf.PeekGet(), includedFiles)) + { + Warning( "Failed to load response system data from %s", scriptfile ); + delete rs; + return false; + } + delete rs; + */ + + // HACKHACK: This is not very efficient + /* + CInstancedResponseSystem *tempSys = new CInstancedResponseSystem( scriptfile ); + if ( tempSys && tempSys->Init() ) + { + tempSys->Precache(); + + for ( ResponseRulePartition::tIndex idx = tempSys->m_RulePartitions.First() ; + tempSys->m_RulePartitions.IsValid(idx) ; + idx = tempSys->m_RulePartitions.Next(idx) ) + { + Rule &rule = tempSys->m_RulePartitions[idx]; + tempSys->CopyRuleFrom( &rule, idx, &defaultresponsesytem ); + } + + tempSys->Release(); + } + */ + + // HACKHACK: This is even less efficient + defaultresponsesytem.LoadFromBuffer( scriptfile, (const char *)buf.PeekGet() ); + defaultresponsesytem.Precache(); + + return true; +} + +// Called from Mapbase manifests to flush +void ReloadResponseSystem() +{ + defaultresponsesytem.ReloadAllResponseSystems(); +} +#endif + +static short RESPONSESYSTEM_SAVE_RESTORE_VERSION = 1; + +// note: this won't save/restore settings from instanced response systems. Could add that with a CDefSaveRestoreOps implementation if needed +// +class CDefaultResponseSystemSaveRestoreBlockHandler : public CDefSaveRestoreBlockHandler +{ +public: + const char *GetBlockName() + { + return "ResponseSystem"; + } + + void WriteSaveHeaders( ISave *pSave ) + { + pSave->WriteShort( &RESPONSESYSTEM_SAVE_RESTORE_VERSION ); + } + + void ReadRestoreHeaders( IRestore *pRestore ) + { + // No reason why any future version shouldn't try to retain backward compatability. The default here is to not do so. + short version; + pRestore->ReadShort( &version ); + m_fDoLoad = ( version == RESPONSESYSTEM_SAVE_RESTORE_VERSION ); + } + + void Save( ISave *pSave ) + { + CDefaultResponseSystem& rs = defaultresponsesytem; + + int count = rs.m_Responses.Count(); + pSave->WriteInt( &count ); + for ( int i = 0; i < count; ++i ) + { + pSave->StartBlock( "ResponseGroup" ); + + pSave->WriteString( rs.m_Responses.GetElementName( i ) ); + const ResponseGroup *group = &rs.m_Responses[ i ]; + pSave->WriteAll( group ); + + short groupCount = group->group.Count(); + pSave->WriteShort( &groupCount ); + for ( int j = 0; j < groupCount; ++j ) + { + const ParserResponse *response = &group->group[ j ]; + pSave->StartBlock( "Response" ); + pSave->WriteString( response->value ); + pSave->WriteAll( response ); + pSave->EndBlock(); + } + + pSave->EndBlock(); + } + +#ifdef MAPBASE + // Enhanced Response System save/restore + int count2 = 0; + if (rr_enhanced_saverestore.GetBool()) + { + // Rule state save/load + count2 = rs.m_RulePartitions.Count(); + pSave->WriteInt( &count2 ); + for ( ResponseRulePartition::tIndex idx = rs.m_RulePartitions.First() ; + rs.m_RulePartitions.IsValid(idx) ; + idx = rs.m_RulePartitions.Next(idx) ) + { + pSave->StartBlock( "Rule" ); + + pSave->WriteString( rs.m_RulePartitions.GetElementName( idx ) ); + const Rule &rule = rs.m_RulePartitions[ idx ]; + + bool bEnabled = rule.m_bEnabled; + pSave->WriteBool( &bEnabled ); + + pSave->EndBlock(); + } + } + else + { + // Indicate this isn't using enhanced save/restore + pSave->WriteInt( &count2 ); + } +#endif + } + + void Restore( IRestore *pRestore, bool createPlayers ) + { + if ( !m_fDoLoad ) + return; + + CDefaultResponseSystem& rs = defaultresponsesytem; + + int count = pRestore->ReadInt(); + for ( int i = 0; i < count; ++i ) + { + char szResponseGroupBlockName[SIZE_BLOCK_NAME_BUF]; + pRestore->StartBlock( szResponseGroupBlockName ); + if ( !Q_stricmp( szResponseGroupBlockName, "ResponseGroup" ) ) + { + + char groupname[ 256 ]; + pRestore->ReadString( groupname, sizeof( groupname ), 0 ); + + // Try and find it + int idx = rs.m_Responses.Find( groupname ); + if ( idx != rs.m_Responses.InvalidIndex() ) + { + ResponseGroup *group = &rs.m_Responses[ idx ]; + pRestore->ReadAll( group ); + + short groupCount = pRestore->ReadShort(); + for ( int j = 0; j < groupCount; ++j ) + { + char szResponseBlockName[SIZE_BLOCK_NAME_BUF]; + + char responsename[ 256 ]; + pRestore->StartBlock( szResponseBlockName ); + if ( !Q_stricmp( szResponseBlockName, "Response" ) ) + { + pRestore->ReadString( responsename, sizeof( responsename ), 0 ); + + // Find it by name + int ri; + for ( ri = 0; ri < group->group.Count(); ++ri ) + { + ParserResponse *response = &group->group[ ri ]; + if ( !Q_stricmp( response->value, responsename ) ) + { + break; + } + } + + if ( ri < group->group.Count() ) + { + ParserResponse *response = &group->group[ ri ]; + pRestore->ReadAll( response ); + } + } + + pRestore->EndBlock(); + } + } + } + + pRestore->EndBlock(); + } + +#ifdef MAPBASE + // Enhanced Response System save/restore + count = pRestore->ReadInt(); + for ( int i = 0; i < count; ++i ) + { + char szRuleBlockName[SIZE_BLOCK_NAME_BUF]; + pRestore->StartBlock( szRuleBlockName ); + if ( !Q_stricmp( szRuleBlockName, "Rule" ) ) + { + char groupname[ 256 ]; + pRestore->ReadString( groupname, sizeof( groupname ), 0 ); + + // Try and find it + Rule *rule = rs.m_RulePartitions.FindByName( groupname ); + if ( rule ) + { + bool bEnabled; + pRestore->ReadBool( &bEnabled ); + rule->m_bEnabled = bEnabled; + } + else + { + Warning("Warning: Can't find rule %s\n", groupname); + } + } + + pRestore->EndBlock(); + } +#endif + } +private: + + bool m_fDoLoad; + +} g_DefaultResponseSystemSaveRestoreBlockHandler; + +ISaveRestoreBlockHandler *GetDefaultResponseSystemSaveRestoreBlockHandler() +{ + return &g_DefaultResponseSystemSaveRestoreBlockHandler; +} + +//----------------------------------------------------------------------------- +// CResponseSystemSaveRestoreOps +// +// Purpose: Handles save and load for instanced response systems... +// +// BUGBUG: This will save the same response system to file multiple times for "shared" response systems and +// therefore it'll restore the same data onto the same pointer N times on reload (probably benign for now, but we could +// write code to save/restore the instanced ones by filename in the block handler above maybe? +//----------------------------------------------------------------------------- + +class CResponseSystemSaveRestoreOps : public CDefSaveRestoreOps +{ +public: + + virtual void Save( const SaveRestoreFieldInfo_t &fieldInfo, ISave *pSave ) + { + CResponseSystem *pRS = *(CResponseSystem **)fieldInfo.pField; + if ( !pRS || pRS == &defaultresponsesytem ) + return; + + int count = pRS->m_Responses.Count(); + pSave->WriteInt( &count ); + for ( int i = 0; i < count; ++i ) + { + pSave->StartBlock( "ResponseGroup" ); + + pSave->WriteString( pRS->m_Responses.GetElementName( i ) ); + const ResponseGroup *group = &pRS->m_Responses[ i ]; + pSave->WriteAll( group ); + + short groupCount = group->group.Count(); + pSave->WriteShort( &groupCount ); + for ( int j = 0; j < groupCount; ++j ) + { + const ParserResponse *response = &group->group[ j ]; + pSave->StartBlock( "Response" ); + pSave->WriteString( response->value ); + pSave->WriteAll( response ); + pSave->EndBlock(); + } + + pSave->EndBlock(); + } + } + + virtual void Restore( const SaveRestoreFieldInfo_t &fieldInfo, IRestore *pRestore ) + { + CResponseSystem *pRS = *(CResponseSystem **)fieldInfo.pField; + if ( !pRS || pRS == &defaultresponsesytem ) + return; + + int count = pRestore->ReadInt(); + for ( int i = 0; i < count; ++i ) + { + char szResponseGroupBlockName[SIZE_BLOCK_NAME_BUF]; + pRestore->StartBlock( szResponseGroupBlockName ); + if ( !Q_stricmp( szResponseGroupBlockName, "ResponseGroup" ) ) + { + + char groupname[ 256 ]; + pRestore->ReadString( groupname, sizeof( groupname ), 0 ); + + // Try and find it + int idx = pRS->m_Responses.Find( groupname ); + if ( idx != pRS->m_Responses.InvalidIndex() ) + { + ResponseGroup *group = &pRS->m_Responses[ idx ]; + pRestore->ReadAll( group ); + + short groupCount = pRestore->ReadShort(); + for ( int j = 0; j < groupCount; ++j ) + { + char szResponseBlockName[SIZE_BLOCK_NAME_BUF]; + + char responsename[ 256 ]; + pRestore->StartBlock( szResponseBlockName ); + if ( !Q_stricmp( szResponseBlockName, "Response" ) ) + { + pRestore->ReadString( responsename, sizeof( responsename ), 0 ); + + // Find it by name + int ri; + for ( ri = 0; ri < group->group.Count(); ++ri ) + { + ParserResponse *response = &group->group[ ri ]; + if ( !Q_stricmp( response->value, responsename ) ) + { + break; + } + } + + if ( ri < group->group.Count() ) + { + ParserResponse *response = &group->group[ ri ]; + pRestore->ReadAll( response ); + } + } + + pRestore->EndBlock(); + } + } + } + + pRestore->EndBlock(); + } + } + +} g_ResponseSystemSaveRestoreOps; + +ISaveRestoreOps *responseSystemSaveRestoreOps = &g_ResponseSystemSaveRestoreOps; + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CDefaultResponseSystem::Init() +{ + /* + Warning( "sizeof( Response ) == %d\n", sizeof( Response ) ); + Warning( "sizeof( ResponseGroup ) == %d\n", sizeof( ResponseGroup ) ); + Warning( "sizeof( Criteria ) == %d\n", sizeof( Criteria ) ); + Warning( "sizeof( AI_ResponseParams ) == %d\n", sizeof( AI_ResponseParams ) ); + */ + const char *basescript = GetScriptFile(); + + LoadRuleSet( basescript ); + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CDefaultResponseSystem::Shutdown() +{ + // Wipe instanced versions + ClearInstanced(); + + // Clear outselves + Clear(); + // IServerSystem chain + BaseClass::Shutdown(); +} + + +//----------------------------------------------------------------------------- +// Purpose: Instance a custom response system +// Input : *scriptfile - +// Output : IResponseSystem +//----------------------------------------------------------------------------- +IResponseSystem *PrecacheCustomResponseSystem( const char *scriptfile ) +{ + return defaultresponsesytem.PrecacheCustomResponseSystem( scriptfile ); +} + +//----------------------------------------------------------------------------- +// Purpose: Instance a custom response system +// Input : *scriptfile - +// set - +// Output : IResponseSystem +//----------------------------------------------------------------------------- +IResponseSystem *BuildCustomResponseSystemGivenCriteria( const char *pszBaseFile, const char *pszCustomName, AI_CriteriaSet &criteriaSet, float flCriteriaScore ) +{ + return defaultresponsesytem.BuildCustomResponseSystemGivenCriteria( pszBaseFile, pszCustomName, criteriaSet, flCriteriaScore ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void DestroyCustomResponseSystems() +{ + defaultresponsesytem.DestroyCustomResponseSystems(); +} diff --git a/src/game/shared/ai_responsesystem_new.h b/src/game/shared/ai_responsesystem_new.h new file mode 100644 index 00000000..9d2fff6b --- /dev/null +++ b/src/game/shared/ai_responsesystem_new.h @@ -0,0 +1,29 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#ifndef AI_RESPONSESYSTEM_H +#define AI_RESPONSESYSTEM_H + +#include "utlvector.h" + +#ifdef _WIN32 +#pragma once +#endif + +#include "AI_Criteria.h" +#include "../../public/responserules/response_types.h" + +// using ResponseRules::IResponseFilter; +// using ResponseRules::IResponseSystem; + +ResponseRules::IResponseSystem *PrecacheCustomResponseSystem( const char *scriptfile ); +ResponseRules::IResponseSystem *BuildCustomResponseSystemGivenCriteria( const char *pszBaseFile, const char *pszCustomName, AI_CriteriaSet &criteriaSet, float flCriteriaScore ); +void DestroyCustomResponseSystems(); + +class ISaveRestoreBlockHandler *GetDefaultResponseSystemSaveRestoreBlockHandler(); +class ISaveRestoreOps *GetResponseSystemSaveRestoreOps(); + +#endif // AI_RESPONSESYSTEM_H diff --git a/src/game/shared/ai_speechconcept.cpp b/src/game/shared/ai_speechconcept.cpp new file mode 100644 index 00000000..c0ae8e36 --- /dev/null +++ b/src/game/shared/ai_speechconcept.cpp @@ -0,0 +1,28 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" + +#include "ai_speechconcept.h" + +#ifdef GAME_DLL +#include "game.h" +#include "ai_basenpc.h" +#include "sceneentity.h" +#endif + +#include "engine/ienginesound.h" +#include "keyvalues.h" +#include "ai_criteria.h" +#include "isaverestore.h" + + +// memdbgon must be the last include file in a .cpp file!!! +#include + + +// empty \ No newline at end of file diff --git a/src/game/shared/ai_speechconcept.h b/src/game/shared/ai_speechconcept.h new file mode 100644 index 00000000..3e375a0a --- /dev/null +++ b/src/game/shared/ai_speechconcept.h @@ -0,0 +1,45 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: Class data for an AI Concept, an atom of response-driven dialog. +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef AI_SPEECHCONCEPT_H +#define AI_SPEECHCONCEPT_H + +#if defined( _WIN32 ) +#pragma once +#endif + +#include "responserules/response_types.h" + +class CAI_Concept : public ResponseRules::CRR_Concept +{ +public: + CAI_Concept() {}; + // construct concept from a string. + CAI_Concept(const char *fromString) : CRR_Concept(fromString) {} ; + + // get/set BS + inline EHANDLE GetSpeaker() const { return m_hSpeaker; } + inline void SetSpeaker(EHANDLE val) { m_hSpeaker = val; } + + /* + inline EHANDLE GetTarget() const { return m_hTarget; } + inline void SetTarget(EHANDLE val) { m_hTarget = val; } + inline EHANDLE GetTopic() const { return m_hTopic; } + inline void SetTopic(EHANDLE val) { m_hTopic = val; } + */ + +protected: + EHANDLE m_hSpeaker; + + /* + EHANDLE m_hTarget; + EHANDLE m_hTopic; + */ +}; + + +#endif diff --git a/src/game/shared/ammodef.cpp b/src/game/shared/ammodef.cpp index 257e8569..cf3ad3e8 100644 --- a/src/game/shared/ammodef.cpp +++ b/src/game/shared/ammodef.cpp @@ -24,6 +24,21 @@ Ammo_t *CAmmoDef::GetAmmoOfIndex(int nAmmoIndex) return &m_AmmoType[ nAmmoIndex ]; } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: +// Input : +// Output : +//----------------------------------------------------------------------------- +const char* CAmmoDef::Name(int nAmmoIndex) +{ + if ( nAmmoIndex < 1 || nAmmoIndex >= m_nAmmoIndex ) + return NULL; + + return m_AmmoType[nAmmoIndex].pName; +} +#endif + //----------------------------------------------------------------------------- // Purpose: // Input : @@ -292,4 +307,24 @@ CAmmoDef::~CAmmoDef( void ) } } +#ifdef MAPBASE_VSCRIPT +BEGIN_SCRIPTDESC_ROOT( CAmmoDef, SCRIPT_SINGLETON "The ammo type definition manager." ) + + DEFINE_SCRIPTFUNC( Name, "Gets the name of the specified ammo type index." ) + DEFINE_SCRIPTFUNC( Index, "Gets the index of the specified ammo type name." ) + DEFINE_SCRIPTFUNC( PlrDamage, "Gets the damage players deal for the specified ammo type." ) + DEFINE_SCRIPTFUNC( NPCDamage, "Gets the damage NPCs deal for the specified ammo type." ) + DEFINE_SCRIPTFUNC( MaxCarry, "Gets the maximum amount of this ammo type which players should be able to carry." ) + DEFINE_SCRIPTFUNC( DamageType, "Gets the type of damage this ammo type deals." ) + DEFINE_SCRIPTFUNC( TracerType, "Gets the type of tracer this ammo type uses." ) + DEFINE_SCRIPTFUNC( DamageForce, "Gets the amount of force this ammo type deals." ) + DEFINE_SCRIPTFUNC( MinSplashSize, "Gets the minimum size of water splashes caused by impacts from this ammo type." ) + DEFINE_SCRIPTFUNC( MaxSplashSize, "Gets the maximum size of water splashes caused by impacts from this ammo type." ) + DEFINE_SCRIPTFUNC( Flags, "Gets the flags this ammo type uses." ) + + DEFINE_SCRIPTFUNC( GetNumAmmoTypes, "Gets the number of ammo types which currently exist." ) + +END_SCRIPTDESC(); +#endif + diff --git a/src/game/shared/ammodef.h b/src/game/shared/ammodef.h index 71c1707f..d5607e02 100644 --- a/src/game/shared/ammodef.h +++ b/src/game/shared/ammodef.h @@ -72,6 +72,9 @@ public: Ammo_t m_AmmoType[MAX_AMMO_TYPES]; Ammo_t *GetAmmoOfIndex(int nAmmoIndex); +#ifdef MAPBASE + const char* Name(int nAmmoIndex); +#endif int Index(const char *psz); int PlrDamage(int nAmmoIndex); int NPCDamage(int nAmmoIndex); @@ -91,6 +94,11 @@ public: private: bool AddAmmoType(char const* name, int damageType, int tracerType, int nFlags, int minSplashSize, int maxSplashSize ); + +#ifdef MAPBASE_VSCRIPT + ALLOW_SCRIPT_ACCESS(); + int GetNumAmmoTypes() { return m_nAmmoIndex; } +#endif }; diff --git a/src/game/shared/base_playeranimstate.cpp b/src/game/shared/base_playeranimstate.cpp index 1153faa4..3be1d5fe 100644 --- a/src/game/shared/base_playeranimstate.cpp +++ b/src/game/shared/base_playeranimstate.cpp @@ -542,7 +542,26 @@ bool CBasePlayerAnimState::CanThePlayerMove() void CBasePlayerAnimState::ComputePlaybackRate() { VPROF( "CBasePlayerAnimState::ComputePlaybackRate" ); +#ifdef MAPBASE + if ( m_AnimConfig.m_LegAnimType == LEGANIM_9WAY ) + { + // If the movement would be greater than the pose range, set playback rate anyway + if ( abs(m_vLastMovePose.x) > 1.0f || abs(m_vLastMovePose.y) > 1.0f ) + { + bool bIsMoving; + float flRate = CalcMovementPlaybackRate( &bIsMoving ); + if ( bIsMoving ) + GetOuter()->SetPlaybackRate( flRate ); + else + GetOuter()->SetPlaybackRate( 1 ); + } + else + GetOuter()->SetPlaybackRate( 1 ); + } + else // Allow LEGANIM_8WAY to change playback rate +#else if ( m_AnimConfig.m_LegAnimType != LEGANIM_9WAY && m_AnimConfig.m_LegAnimType != LEGANIM_8WAY ) +#endif { // When using a 9-way blend, playback rate is always 1 and we just scale the pose params // to speed up or slow down the animation. diff --git a/src/game/shared/basecombatcharacter_shared.cpp b/src/game/shared/basecombatcharacter_shared.cpp index 32e823fa..d937e59a 100644 --- a/src/game/shared/basecombatcharacter_shared.cpp +++ b/src/game/shared/basecombatcharacter_shared.cpp @@ -82,7 +82,11 @@ bool CBaseCombatCharacter::Weapon_CanSwitchTo( CBaseCombatWeapon *pWeapon ) return false; } +#ifdef MAPBASE + if ( !pWeapon->HasAnyAmmo() && !GetAmmoCount( pWeapon->m_iPrimaryAmmoType ) && !pWeapon->HasSpawnFlags(SF_WEAPON_NO_AUTO_SWITCH_WHEN_EMPTY) ) +#else if ( !pWeapon->HasAnyAmmo() && !GetAmmoCount( pWeapon->m_iPrimaryAmmoType ) ) +#endif return false; if ( !pWeapon->CanDeploy() ) @@ -223,6 +227,16 @@ void CBaseCombatCharacter::SetBloodColor( int nBloodColor ) m_bloodColor = nBloodColor; } +#if defined(MAPBASE) && defined(GAME_DLL) +//----------------------------------------------------------------------------- +// Purpose: Sets blood color +//----------------------------------------------------------------------------- +void CBaseCombatCharacter::InputSetBloodColor( inputdata_t &inputdata ) +{ + SetBloodColor(inputdata.value.Int()); +} +#endif + //----------------------------------------------------------------------------- /** The main visibility check. Checks all the entity specific reasons that could diff --git a/src/game/shared/basecombatweapon_shared.cpp b/src/game/shared/basecombatweapon_shared.cpp index 59ecfdf3..913f8671 100644 --- a/src/game/shared/basecombatweapon_shared.cpp +++ b/src/game/shared/basecombatweapon_shared.cpp @@ -195,11 +195,19 @@ void CBaseCombatWeapon::Spawn( void ) // Assume m_nViewModelIndex = 0; +#ifdef MAPBASE + // Don't reset to default ammo if we're supposed to use the keyvalue + if (!HasSpawnFlags( SF_WEAPON_PRESERVE_AMMO )) +#endif GiveDefaultAmmo(); if ( GetWorldModel() ) { +#ifdef MAPBASE + SetModel( (GetDroppedModel() && GetDroppedModel()[0]) ? GetDroppedModel() : GetWorldModel() ); +#else SetModel( GetWorldModel() ); +#endif } #if !defined( CLIENT_DLL ) @@ -248,14 +256,20 @@ const unsigned char *CBaseCombatWeapon::GetEncryptionKey( void ) void CBaseCombatWeapon::Precache( void ) { #if defined( CLIENT_DLL ) - Assert( Q_strlen( GetClassname() ) > 0 ); + Assert( Q_strlen(GetWeaponScriptName() ) > 0 ); // Msg( "Client got %s\n", GetClassname() ); #endif m_iPrimaryAmmoType = m_iSecondaryAmmoType = -1; // Add this weapon to the weapon registry, and get our index into it // Get weapon data from script file +#ifdef MAPBASE + // Allow custom scripts to be loaded on a map-by-map basis + if ( ReadCustomWeaponDataFromFileForSlot( filesystem, GetWeaponScriptName(), &m_hWeaponFileInfo, GetEncryptionKey() ) || + ReadWeaponDataFromFileForSlot( filesystem, GetWeaponScriptName(), &m_hWeaponFileInfo, GetEncryptionKey() ) ) +#else if ( ReadWeaponDataFromFileForSlot( filesystem, GetClassname(), &m_hWeaponFileInfo, GetEncryptionKey() ) ) +#endif { // Get the ammo indexes for the ammo's specified in the data file if ( GetWpnData().szAmmo1[0] ) @@ -298,6 +312,18 @@ void CBaseCombatWeapon::Precache( void ) { m_iWorldModelIndex = CBaseEntity::PrecacheModel( GetWorldModel() ); } +#ifdef MAPBASE + m_iDroppedModelIndex = 0; + if ( GetDroppedModel() && GetDroppedModel()[0] ) + { + m_iDroppedModelIndex = CBaseEntity::PrecacheModel( GetDroppedModel() ); + } + else + { + // Use the world model index + m_iDroppedModelIndex = m_iWorldModelIndex; + } +#endif // Precache sounds, too for ( int i = 0; i < NUM_SHOOT_SOUND_TYPES; ++i ) @@ -312,11 +338,69 @@ void CBaseCombatWeapon::Precache( void ) else { // Couldn't read data file, remove myself - Warning( "Error reading weapon data file for: %s\n", GetClassname() ); + Warning( "Error reading weapon data file for: %s\n", GetWeaponScriptName() ); // Remove( ); //don't remove, this gets released soon! } } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: Sets ammo based on mapper value +//----------------------------------------------------------------------------- +void CBaseCombatWeapon::SetAmmoFromMapper( float flAmmo, bool bSecondary ) +{ + int iFinalAmmo; + if (flAmmo > 0.0f && flAmmo < 1.0f) + { + // Ratio from max ammo + iFinalAmmo = ((float)(!bSecondary ? GetMaxClip1() : GetMaxClip2()) * flAmmo); + } + else + { + // Actual ammo value + iFinalAmmo = (int)flAmmo; + } + + !bSecondary ? + m_iClip1 = iFinalAmmo : + m_iClip2 = iFinalAmmo; +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +bool CBaseCombatWeapon::KeyValue( const char *szKeyName, const char *szValue ) +{ + if ( FStrEq(szKeyName, "SetAmmo1") ) + { + SetAmmoFromMapper(atof(szValue)); + } + if ( FStrEq(szKeyName, "SetAmmo2") ) + { + SetAmmoFromMapper(atof(szValue), true); + } + else if ( FStrEq(szKeyName, "spawnflags") ) + { + m_spawnflags = atoi(szValue); +#ifndef CLIENT_DLL + // Some spawnflags have to be on the client right now + if (m_spawnflags != 0) + DispatchUpdateTransmitState(); +#endif + } + else + return BaseClass::KeyValue( szKeyName, szValue ); + + return true; +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +bool CBaseCombatWeapon::GetKeyValue( const char *szKeyName, char *szValue, int iMaxLen ) +{ + return BaseClass::GetKeyValue(szKeyName, szValue, iMaxLen); +} +#endif + //----------------------------------------------------------------------------- // Purpose: Get my data in the file weapon info array //----------------------------------------------------------------------------- @@ -410,6 +494,38 @@ bool CBaseCombatWeapon::IsMeleeWeapon() const return GetWpnData().m_bMeleeWeapon; } +#ifdef MAPBASE +float CBaseCombatWeapon::GetViewmodelFOVOverride() const +{ + return GetWpnData().m_flViewmodelFOV; +} + +float CBaseCombatWeapon::GetBobScale() const +{ + return GetWpnData().m_flBobScale; +} + +float CBaseCombatWeapon::GetSwayScale() const +{ + return GetWpnData().m_flSwayScale; +} + +float CBaseCombatWeapon::GetSwaySpeedScale() const +{ + return GetWpnData().m_flSwaySpeedScale; +} + +const char *CBaseCombatWeapon::GetDroppedModel() const +{ + return GetWpnData().szDroppedModel; +} + +bool CBaseCombatWeapon::UsesHands() const +{ + return GetWpnData().m_bUsesHands; +} +#endif + //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- @@ -718,6 +834,12 @@ void CBaseCombatWeapon::Drop( const Vector &vecVelocity ) SetOwnerEntity( NULL ); SetOwner( NULL ); +#ifdef MAPBASE + m_bInReload = false; + + m_OnDropped.FireOutput(pOwner, this); +#endif + // If we're not allowing to spawn due to the gamerules, // remove myself when I'm dropped by an NPC. if ( pOwner && pOwner->IsNPC() ) @@ -763,6 +885,10 @@ void CBaseCombatWeapon::OnPickedUp( CBaseCombatCharacter *pNewOwner ) // Robin: We don't want to delete weapons the player has picked up, so // clear the name of the weapon. This prevents wildcards that are meant // to find NPCs finding weapons dropped by the NPCs as well. +#ifdef MAPBASE + // Level designers might want some weapons to preserve their original names, however. + if ( !HasSpawnFlags(SF_WEAPON_PRESERVE_NAME) ) +#endif SetName( NULL_STRING ); } else @@ -974,6 +1100,161 @@ void CBaseCombatWeapon::SetPickupTouch( void ) } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +WeaponClass_t CBaseCombatWeapon::WeaponClassify() +{ + // For now, check how we map our "angry idle" activity. + // The function is virtual, so derived weapons can override this. + Activity idleact = ActivityOverride(ACT_IDLE_ANGRY, NULL); + switch (idleact) + { +#if EXPANDED_HL2_WEAPON_ACTIVITIES + case ACT_IDLE_ANGRY_REVOLVER: +#endif + case ACT_IDLE_ANGRY_PISTOL: return WEPCLASS_HANDGUN; +#if EXPANDED_HL2_WEAPON_ACTIVITIES + case ACT_IDLE_ANGRY_CROSSBOW: // For now, crossbows are rifles +#endif +#if EXPANDED_HL2_UNUSED_WEAPON_ACTIVITIES + case ACT_IDLE_ANGRY_AR1: + case ACT_IDLE_ANGRY_SMG2: + case ACT_IDLE_ANGRY_SNIPER_RIFLE: +#endif + case ACT_IDLE_ANGRY_SMG1: + case ACT_IDLE_ANGRY_AR2: return WEPCLASS_RIFLE; + case ACT_IDLE_ANGRY_SHOTGUN: return WEPCLASS_SHOTGUN; + case ACT_IDLE_ANGRY_RPG: return WEPCLASS_HEAVY; + + case ACT_IDLE_ANGRY_MELEE: return WEPCLASS_MELEE; + } + return WEPCLASS_INVALID; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +WeaponClass_t CBaseCombatWeapon::WeaponClassFromString(const char *str) +{ + if (FStrEq(str, "WEPCLASS_HANDGUN")) + return WEPCLASS_HANDGUN; + else if (FStrEq(str, "WEPCLASS_RIFLE")) + return WEPCLASS_RIFLE; + else if (FStrEq(str, "WEPCLASS_SHOTGUN")) + return WEPCLASS_SHOTGUN; + else if (FStrEq(str, "WEPCLASS_HEAY")) + return WEPCLASS_HEAVY; + + else if (FStrEq(str, "WEPCLASS_MELEE")) + return WEPCLASS_MELEE; + + return WEPCLASS_INVALID; +} + +#ifdef HL2_DLL +extern acttable_t *GetSMG1Acttable(); +extern int GetSMG1ActtableCount(); + +extern acttable_t *GetAR2Acttable(); +extern int GetAR2ActtableCount(); + +extern acttable_t *GetShotgunActtable(); +extern int GetShotgunActtableCount(); + +extern acttable_t *GetPistolActtable(); +extern int GetPistolActtableCount(); + +extern acttable_t *Get357Acttable(); +extern int Get357ActtableCount(); +#endif + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CBaseCombatWeapon::SupportsBackupActivity(Activity activity) +{ + // Derived classes should override this. + +#ifdef HL2_DLL + // Melee users should not use SMG animations for missing activities. + if (IsMeleeWeapon() && GetBackupActivityList() == GetSMG1Acttable()) + return false; +#endif + + return true; +} + +acttable_t *CBaseCombatWeapon::GetBackupActivityList() +{ + return NULL; +} + +int CBaseCombatWeapon::GetBackupActivityListCount() +{ + return 0; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +acttable_t *CBaseCombatWeapon::GetDefaultBackupActivityList( acttable_t *pTable, int &actCount ) +{ +#ifdef HL2_DLL + // Ensure this isn't already a default backup activity list + if (pTable == GetSMG1Acttable() || pTable == GetPistolActtable()) + return NULL; + + // Use a backup table based on what ACT_IDLE_ANGRY is translated to + Activity actTranslated = ACT_INVALID; + for ( int i = 0; i < actCount; i++, pTable++ ) + { + if ( pTable->baseAct == ACT_IDLE_ANGRY ) + { + actTranslated = (Activity)pTable->weaponAct; + break; + } + } + + if (actTranslated == ACT_INVALID) + return NULL; + + switch (actTranslated) + { +#if EXPANDED_HL2_WEAPON_ACTIVITIES + case ACT_IDLE_ANGRY_REVOLVER: +#endif + case ACT_IDLE_ANGRY_PISTOL: + { + actCount = GetPistolActtableCount(); + return GetPistolActtable(); + } +#if EXPANDED_HL2_WEAPON_ACTIVITIES + case ACT_IDLE_ANGRY_CROSSBOW: // For now, crossbows are rifles +#endif +#if EXPANDED_HL2_UNUSED_WEAPON_ACTIVITIES + case ACT_IDLE_ANGRY_AR1: + case ACT_IDLE_ANGRY_SMG2: + case ACT_IDLE_ANGRY_SNIPER_RIFLE: +#endif + case ACT_IDLE_ANGRY_SMG1: + case ACT_IDLE_ANGRY_AR2: + case ACT_IDLE_ANGRY_SHOTGUN: + case ACT_IDLE_ANGRY_RPG: + { + actCount = GetSMG1ActtableCount(); + return GetSMG1Acttable(); + } + } +#endif + + actCount = 0; + return NULL; +} +#endif + + //----------------------------------------------------------------------------- // Purpose: Become a child of the owner (MOVETYPE_FOLLOW) // disables collisions, touch functions, thinking @@ -1000,6 +1281,12 @@ void CBaseCombatWeapon::Equip( CBaseCombatCharacter *pOwner ) } #endif +#ifdef MAPBASE + // Ammo may be overridden to 0, in which case we shouldn't autoswitch + if (m_iClip1 <= 0 && m_iClip2 <= 0) + AddSpawnFlags(SF_WEAPON_NO_AUTO_SWITCH_WHEN_EMPTY); +#endif + m_flNextPrimaryAttack = gpGlobals->curtime; m_flNextSecondaryAttack = gpGlobals->curtime; @@ -1384,7 +1671,12 @@ bool CBaseCombatWeapon::ReloadOrSwitchWeapons( void ) if ( !HasAnyAmmo() && m_flNextPrimaryAttack < gpGlobals->curtime && m_flNextSecondaryAttack < gpGlobals->curtime ) { // weapon isn't useable, switch. +#ifdef MAPBASE + // Ammo might be overridden to 0, in which case we shouldn't do this + if ( ( (GetWeaponFlags() & ITEM_FLAG_NOAUTOSWITCHEMPTY) == false ) && !HasSpawnFlags(SF_WEAPON_NO_AUTO_SWITCH_WHEN_EMPTY) && ( g_pGameRules->SwitchToNextBestWeapon( pOwner, this ) ) ) +#else if ( ( (GetWeaponFlags() & ITEM_FLAG_NOAUTOSWITCHEMPTY) == false ) && ( g_pGameRules->SwitchToNextBestWeapon( pOwner, this ) ) ) +#endif { m_flNextPrimaryAttack = gpGlobals->curtime + 0.3; return true; @@ -1436,6 +1728,10 @@ bool CBaseCombatWeapon::DefaultDeploy( char *szViewModel, char *szWeaponModel, i SetViewModel(); SendWeaponAnim( iActivity ); + +#ifdef MAPBASE + pOwner->SetAnimation( PLAYER_UNHOLSTER ); +#endif pOwner->SetNextAttack( gpGlobals->curtime + SequenceDuration() ); } @@ -1509,6 +1805,11 @@ bool CBaseCombatWeapon::Holster( CBaseCombatWeapon *pSwitchingTo ) if (pOwner) { pOwner->SetNextAttack( gpGlobals->curtime + flSequenceDuration ); + +#ifdef MAPBASE + if (IsWeaponVisible() && pOwner->IsPlayer()) + static_cast(pOwner)->SetAnimation( PLAYER_HOLSTER ); +#endif } // If we don't have a holster anim, hide immediately to avoid timing issues @@ -1532,6 +1833,11 @@ bool CBaseCombatWeapon::Holster( CBaseCombatWeapon *pSwitchingTo ) RescindReloadHudHint(); } +#ifdef MAPBASE + if (HasSpawnFlags(SF_WEAPON_NO_AUTO_SWITCH_WHEN_EMPTY)) + RemoveSpawnFlags(SF_WEAPON_NO_AUTO_SWITCH_WHEN_EMPTY); +#endif + return true; } @@ -1551,6 +1857,134 @@ bool CBaseCombatWeapon::Holster( CBaseCombatWeapon *pSwitchingTo ) } #else + +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseCombatWeapon::InputSetAmmo1( inputdata_t &inputdata ) +{ + SetAmmoFromMapper(inputdata.value.Float()); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseCombatWeapon::InputSetAmmo2( inputdata_t &inputdata ) +{ + SetAmmoFromMapper(inputdata.value.Float(), true); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseCombatWeapon::InputGiveDefaultAmmo( inputdata_t &inputdata ) +{ + GiveDefaultAmmo(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseCombatWeapon::InputEnablePlayerPickup( inputdata_t &inputdata ) +{ + RemoveSpawnFlags(SF_WEAPON_NO_PLAYER_PICKUP); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseCombatWeapon::InputDisablePlayerPickup( inputdata_t &inputdata ) +{ + AddSpawnFlags(SF_WEAPON_NO_PLAYER_PICKUP); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseCombatWeapon::InputEnableNPCPickup( inputdata_t &inputdata ) +{ + RemoveSpawnFlags(SF_WEAPON_NO_NPC_PICKUP); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseCombatWeapon::InputDisableNPCPickup( inputdata_t &inputdata ) +{ + AddSpawnFlags(SF_WEAPON_NO_NPC_PICKUP); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseCombatWeapon::InputBreakConstraint( inputdata_t &inputdata ) +{ + if ( m_pConstraint != NULL ) + { + physenv->DestroyConstraint( m_pConstraint ); + m_pConstraint = NULL; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseCombatWeapon::InputForceFire( inputdata_t &inputdata, bool bSecondary ) +{ + CBaseCombatCharacter *pOperator = GetOwner(); + + if (!pOperator) + { + // No owner. This means they want us to fire while possibly on the floor independent of any NPC...the madmapper! + pOperator = ToBaseCombatCharacter(inputdata.pActivator); + if (pOperator && pOperator->IsNPC()) + { + // Use this guy, I guess + Operator_ForceNPCFire(pOperator, bSecondary); + } + else + { + // Well...I learned this trick from ent_info. If you have any better ideas, be my guest. + pOperator = CreateEntityByName("generic_actor")->MyCombatCharacterPointer(); + pOperator->SetAbsOrigin(GetAbsOrigin()); + pOperator->SetAbsAngles(GetAbsAngles()); + SetOwnerEntity(pOperator); + + Operator_ForceNPCFire(pOperator, bSecondary); + + UTIL_RemoveImmediate(pOperator); + } + } + else if (pOperator->IsPlayer()) + { + // Owner exists and is a player. + bSecondary ? SecondaryAttack() : PrimaryAttack(); + } + else + { + // Owner exists and is a NPC. + Operator_ForceNPCFire(pOperator, bSecondary); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseCombatWeapon::InputForcePrimaryFire( inputdata_t &inputdata ) +{ + InputForceFire(inputdata, false); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseCombatWeapon::InputForceSecondaryFire( inputdata_t &inputdata ) +{ + InputForceFire(inputdata, true); +} +#endif + //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- @@ -1712,6 +2146,14 @@ void CBaseCombatWeapon::ItemPostFrame( void ) // Secondary attack has priority if ( ( pOwner->m_nButtons & IN_ATTACK2 ) ) { +#ifdef MAPBASE + if (pOwner->HasSpawnFlags(SF_PLAYER_SUPPRESS_FIRING)) + { + // Don't do anything, just cancel the whole function + return; + } + else +#endif if ( UsesSecondaryAmmo() && pOwner->GetAmmoCount(m_iSecondaryAmmoType) <= 0 ) { if (m_flNextEmptySoundTime < gpGlobals->curtime) @@ -1757,6 +2199,14 @@ void CBaseCombatWeapon::ItemPostFrame( void ) if ( !bFired && (pOwner->m_nButtons & IN_ATTACK) && (m_flNextPrimaryAttack <= gpGlobals->curtime)) { +#ifdef MAPBASE + if (pOwner->HasSpawnFlags( SF_PLAYER_SUPPRESS_FIRING )) + { + // Don't do anything, just cancel the whole function + return; + } + else +#endif // Clip empty? Or out of ammo on a no-clip weapon? if ( !IsMeleeWeapon() && (( UsesClipsForAmmo1() && m_iClip1 <= 0) || ( !UsesClipsForAmmo1() && pOwner->GetAmmoCount(m_iPrimaryAmmoType)<=0 )) ) @@ -2091,6 +2541,28 @@ bool CBaseCombatWeapon::Reload( void ) return DefaultReload( GetMaxClip1(), GetMaxClip2(), ACT_VM_RELOAD ); } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseCombatWeapon::Reload_NPC( bool bPlaySound ) +{ + if (bPlaySound) + WeaponSound( RELOAD_NPC ); + + if (UsesClipsForAmmo1()) + { + m_iClip1 = GetMaxClip1(); + } + else + { + // For weapons which don't use clips, give the owner ammo. + if (GetOwner()) + GetOwner()->SetAmmoCount( GetDefaultClip1(), m_iPrimaryAmmoType ); + } +} +#endif + //========================================================= void CBaseCombatWeapon::WeaponIdle( void ) { @@ -2462,6 +2934,15 @@ bool CBaseCombatWeapon::IsLocked( CBaseEntity *pAsker ) return ( m_flUnlockTime > gpGlobals->curtime && m_hLocker != pAsker ); } +bool CBaseCombatWeapon::CanBePickedUpByNPCs(void) +{ +#ifdef MAPBASE + return GetWpnData().m_nWeaponRestriction != WPNRESTRICT_PLAYER_ONLY; +#else + return true; +#endif // MAPBASE +} + //----------------------------------------------------------------------------- // Purpose: // Input : @@ -2487,6 +2968,23 @@ Activity CBaseCombatWeapon::ActivityOverride( Activity baseAct, bool *pRequired return baseAct; } +#ifdef MAPBASE_VSCRIPT +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +HSCRIPT CBaseCombatWeapon::ScriptGetOwner() +{ + return ToHScript( GetOwner() ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseCombatWeapon::ScriptSetOwner( HSCRIPT owner ) +{ + return SetOwner( ToEnt( owner ) ? ToEnt( owner )->MyCombatCharacterPointer() : NULL ); +} +#endif //----------------------------------------------------------------------------- // Purpose: @@ -2621,6 +3119,111 @@ END_PREDICTION_DATA() // Special hack since we're aliasing the name C_BaseCombatWeapon with a macro on the client IMPLEMENT_NETWORKCLASS_ALIASED( BaseCombatWeapon, DT_BaseCombatWeapon ) +#ifdef MAPBASE_VSCRIPT + +// Don't allow client to use Set functions. +// They will only cause visual discrepancies, +// and will be reverted on the next update from the server. +#ifdef GAME_DLL +#define DEFINE_SCRIPTFUNC_SV( p1, p2 ) DEFINE_SCRIPTFUNC( p1, p2 ) +#define DEFINE_SCRIPTFUNC_NAMED_SV( p1, p2, p3 ) DEFINE_SCRIPTFUNC_NAMED( p1, p2, p3 ) + +#define DEFINE_SCRIPTFUNC_CL( p1, p2 ) +#define DEFINE_SCRIPTFUNC_NAMED_CL( p1, p2, p3 ) +#else +#define DEFINE_SCRIPTFUNC_SV( p1, p2 ) +#define DEFINE_SCRIPTFUNC_NAMED_SV( p1, p2, p3 ) + +#define DEFINE_SCRIPTFUNC_CL( p1, p2 ) DEFINE_SCRIPTFUNC( p1, p2 ) +#define DEFINE_SCRIPTFUNC_NAMED_CL( p1, p2, p3 ) DEFINE_SCRIPTFUNC_NAMED( p1, p2, p3 ) +#endif + +BEGIN_ENT_SCRIPTDESC( CBaseCombatWeapon, CBaseAnimating, "The base class for all equippable weapons." ) + + DEFINE_SCRIPTFUNC_NAMED( ScriptGetOwner, "GetOwner", "Get the weapon's owner." ) + DEFINE_SCRIPTFUNC_NAMED_SV( ScriptSetOwner, "SetOwner", "Set the weapon's owner." ) + + DEFINE_SCRIPTFUNC( Clip1, "Get the weapon's current primary ammo." ) + DEFINE_SCRIPTFUNC( Clip2, "Get the weapon's current secondary ammo." ) + DEFINE_SCRIPTFUNC_NAMED_SV( ScriptSetClip1, "SetClip1", "Set the weapon's current primary ammo." ) + DEFINE_SCRIPTFUNC_NAMED_SV( ScriptSetClip2, "SetClip2", "Set the weapon's current secondary ammo." ) + DEFINE_SCRIPTFUNC( GetMaxClip1, "Get the weapon's maximum primary ammo." ) + DEFINE_SCRIPTFUNC( GetMaxClip2, "Get the weapon's maximum secondary ammo." ) + DEFINE_SCRIPTFUNC( GetDefaultClip1, "Get the weapon's default primary ammo." ) + DEFINE_SCRIPTFUNC( GetDefaultClip2, "Get the weapon's default secondary ammo." ) + + DEFINE_SCRIPTFUNC( HasAnyAmmo, "Check if the weapon currently has ammo or doesn't need ammo." ) + DEFINE_SCRIPTFUNC( HasPrimaryAmmo, "Check if the weapon currently has ammo or doesn't need primary ammo." ) + DEFINE_SCRIPTFUNC( HasSecondaryAmmo, "Check if the weapon currently has ammo or doesn't need secondary ammo." ) + DEFINE_SCRIPTFUNC( UsesPrimaryAmmo, "Check if the weapon uses primary ammo." ) + DEFINE_SCRIPTFUNC( UsesSecondaryAmmo, "Check if the weapon uses secondary ammo." ) + DEFINE_SCRIPTFUNC_SV( GiveDefaultAmmo, "Fill the weapon back up to default ammo." ) + + DEFINE_SCRIPTFUNC( UsesClipsForAmmo1, "Check if the weapon uses clips for primary ammo." ) + DEFINE_SCRIPTFUNC( UsesClipsForAmmo2, "Check if the weapon uses clips for secondary ammo." ) + + DEFINE_SCRIPTFUNC( GetPrimaryAmmoType, "Get the weapon's primary ammo type." ) + DEFINE_SCRIPTFUNC( GetSecondaryAmmoType, "Get the weapon's secondary ammo type." ) + + DEFINE_SCRIPTFUNC( GetSubType, "Get the weapon's subtype." ) + DEFINE_SCRIPTFUNC_SV( SetSubType, "Set the weapon's subtype." ) + + DEFINE_SCRIPTFUNC( GetFireRate, "Get the weapon's firing rate." ) + DEFINE_SCRIPTFUNC( AddViewKick, "Applies the weapon's view kick." ) + + DEFINE_SCRIPTFUNC( GetWorldModel, "Get the weapon's world model." ) + DEFINE_SCRIPTFUNC( GetViewModel, "Get the weapon's view model." ) + DEFINE_SCRIPTFUNC( GetDroppedModel, "Get the weapon's unique dropped model if it has one." ) + + DEFINE_SCRIPTFUNC( GetWeight, "Get the weapon's weight." ) + DEFINE_SCRIPTFUNC( GetPrintName, "" ) + + DEFINE_SCRIPTFUNC_CL( GetSlot, "" ) + DEFINE_SCRIPTFUNC_CL( GetPosition, "" ) + + DEFINE_SCRIPTFUNC( CanBePickedUpByNPCs, "Check if the weapon can be picked up by NPCs." ) + + DEFINE_SCRIPTFUNC_SV( CapabilitiesGet, "Get the capabilities the weapon currently possesses." ) + + DEFINE_SCRIPTFUNC( HasWeaponIdleTimeElapsed, "Returns true if the idle time has elapsed." ) + DEFINE_SCRIPTFUNC( GetWeaponIdleTime, "Returns the next time WeaponIdle() will run." ) + DEFINE_SCRIPTFUNC_SV( SetWeaponIdleTime, "Sets the next time WeaponIdle() will run." ) + + DEFINE_SCRIPTFUNC_NAMED( ScriptWeaponClassify, "WeaponClassify", "Returns the weapon's classify class from the WEPCLASS_ constant group" ) + DEFINE_SCRIPTFUNC_NAMED( ScriptWeaponSound, "WeaponSound", "Plays one of the weapon's sounds." ) + + DEFINE_SCRIPTFUNC_NAMED( ScriptGetBulletSpread, "GetBulletSpread", "Returns the weapon's default bullet spread." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptGetBulletSpreadForProficiency, "GetBulletSpreadForProficiency", "Returns the weapon's bullet spread for the specified proficiency level." ) + + DEFINE_SCRIPTFUNC_NAMED( ScriptGetPrimaryAttackActivity, "GetPrimaryAttackActivity", "Returns the weapon's primary attack activity." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptGetSecondaryAttackActivity, "GetSecondaryAttackActivity", "Returns the weapon's secondary attack activity." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptGetDrawActivity, "GetDrawActivity", "Returns the weapon's draw activity." ) + DEFINE_SCRIPTFUNC( GetDefaultAnimSpeed, "Returns the weapon's default animation speed." ) + DEFINE_SCRIPTFUNC( SendWeaponAnim, "Sends a weapon animation." ) + DEFINE_SCRIPTFUNC( GetViewModelSequenceDuration, "Gets the sequence duration of the current view model animation." ) + DEFINE_SCRIPTFUNC( IsViewModelSequenceFinished, "Returns true if the current view model animation is finished." ) + + DEFINE_SCRIPTFUNC( FiresUnderwater, "Returns true if this weapon can fire underwater." ) + DEFINE_SCRIPTFUNC_SV( SetFiresUnderwater, "Sets whether this weapon can fire underwater." ) + DEFINE_SCRIPTFUNC( AltFiresUnderwater, "Returns true if this weapon can alt-fire underwater." ) + DEFINE_SCRIPTFUNC_SV( SetAltFiresUnderwater, "Sets whether this weapon can alt-fire underwater." ) + DEFINE_SCRIPTFUNC( MinRange1, "Returns the closest this weapon can be used." ) + DEFINE_SCRIPTFUNC_SV( SetMinRange1, "Sets the closest this weapon can be used." ) + DEFINE_SCRIPTFUNC( MinRange2, "Returns the closest this weapon can be used." ) + DEFINE_SCRIPTFUNC_SV( SetMinRange2, "Sets the closest this weapon can be used." ) + DEFINE_SCRIPTFUNC( ReloadsSingly, "Returns true if this weapon reloads 1 round at a time." ) + DEFINE_SCRIPTFUNC_SV( SetReloadsSingly, "Sets whether this weapon reloads 1 round at a time." ) + DEFINE_SCRIPTFUNC( FireDuration, "Returns the amount of time that the weapon has sustained firing." ) + DEFINE_SCRIPTFUNC_SV( SetFireDuration, "Sets the amount of time that the weapon has sustained firing." ) + + DEFINE_SCRIPTFUNC( NextPrimaryAttack, "Returns the next time PrimaryAttack() will run when the player is pressing +ATTACK." ) + DEFINE_SCRIPTFUNC_SV( SetNextPrimaryAttack, "Sets the next time PrimaryAttack() will run when the player is pressing +ATTACK." ) + DEFINE_SCRIPTFUNC( NextSecondaryAttack, "Returns the next time SecondaryAttack() will run when the player is pressing +ATTACK2." ) + DEFINE_SCRIPTFUNC_SV( SetNextSecondaryAttack, "Sets the next time SecondaryAttack() will run when the player is pressing +ATTACK2." ) + +END_SCRIPTDESC(); +#endif + #if !defined( CLIENT_DLL ) //----------------------------------------------------------------------------- // Purpose: Save Data for Base Weapon object @@ -2698,11 +3301,27 @@ BEGIN_DATADESC( CBaseCombatWeapon ) DEFINE_THINKFUNC( HideThink ), DEFINE_INPUTFUNC( FIELD_VOID, "HideWeapon", InputHideWeapon ), +#ifdef MAPBASE + DEFINE_INPUTFUNC( FIELD_FLOAT, "SetAmmo1", InputSetAmmo1 ), + DEFINE_INPUTFUNC( FIELD_FLOAT, "SetAmmo2", InputSetAmmo2 ), + DEFINE_INPUTFUNC( FIELD_VOID, "GiveDefaultAmmo", InputGiveDefaultAmmo ), + DEFINE_INPUTFUNC( FIELD_VOID, "EnablePlayerPickup", InputEnablePlayerPickup ), + DEFINE_INPUTFUNC( FIELD_VOID, "DisablePlayerPickup", InputDisablePlayerPickup ), + DEFINE_INPUTFUNC( FIELD_VOID, "EnableNPCPickup", InputEnableNPCPickup ), + DEFINE_INPUTFUNC( FIELD_VOID, "DisableNPCPickup", InputDisableNPCPickup ), + DEFINE_INPUTFUNC( FIELD_VOID, "BreakConstraint", InputBreakConstraint ), + DEFINE_INPUTFUNC( FIELD_VOID, "ForcePrimaryFire", InputForcePrimaryFire ), + DEFINE_INPUTFUNC( FIELD_VOID, "ForceSecondaryFire", InputForceSecondaryFire ), +#endif + // Outputs DEFINE_OUTPUT( m_OnPlayerUse, "OnPlayerUse"), DEFINE_OUTPUT( m_OnPlayerPickup, "OnPlayerPickup"), DEFINE_OUTPUT( m_OnNPCPickup, "OnNPCPickup"), DEFINE_OUTPUT( m_OnCacheInteraction, "OnCacheInteraction" ), +#ifdef MAPBASE + DEFINE_OUTPUT( m_OnDropped, "OnDropped" ), +#endif END_DATADESC() @@ -2749,6 +3368,16 @@ void* SendProxy_SendLocalWeaponDataTable( const SendProp *pProp, const void *pSt pRecipients->SetOnly( pPlayer->GetClientIndex() ); return (void*)pVarData; } +#ifdef MAPBASE + else if (pWeapon->HasSpawnFlags( SF_WEAPON_PRESERVE_AMMO )) + { + // Ammo values are sent to the client using this proxy. + // Preserved ammo values from the server need to be sent to the client ASAP to avoid HUD issues, etc. + // I've tried many nasty hacks, but this is the one that works well enough and there's not much else we could do. + pRecipients->SetAllRecipients(); + return (void*)pVarData; + } +#endif } return NULL; @@ -2868,14 +3497,29 @@ BEGIN_NETWORK_TABLE(CBaseCombatWeapon, DT_BaseCombatWeapon) SendPropDataTable("LocalActiveWeaponData", 0, &REFERENCE_SEND_TABLE(DT_LocalActiveWeaponData), SendProxy_SendActiveLocalWeaponDataTable ), SendPropModelIndex( SENDINFO(m_iViewModelIndex) ), SendPropModelIndex( SENDINFO(m_iWorldModelIndex) ), +#ifdef MAPBASE + SendPropModelIndex( SENDINFO(m_iDroppedModelIndex) ), +#endif SendPropInt( SENDINFO(m_iState ), 8, SPROP_UNSIGNED ), SendPropEHandle( SENDINFO(m_hOwner) ), + +#ifdef MAPBASE + SendPropInt( SENDINFO(m_spawnflags), 8, SPROP_UNSIGNED ), +#endif + #else RecvPropDataTable("LocalWeaponData", 0, 0, &REFERENCE_RECV_TABLE(DT_LocalWeaponData)), RecvPropDataTable("LocalActiveWeaponData", 0, 0, &REFERENCE_RECV_TABLE(DT_LocalActiveWeaponData)), RecvPropInt( RECVINFO(m_iViewModelIndex)), RecvPropInt( RECVINFO(m_iWorldModelIndex)), +#ifdef MAPBASE + RecvPropInt( RECVINFO(m_iDroppedModelIndex) ), +#endif RecvPropInt( RECVINFO(m_iState), 0, &CBaseCombatWeapon::RecvProxy_WeaponState ), RecvPropEHandle( RECVINFO(m_hOwner ), RecvProxy_WeaponOwner ), +#ifdef MAPBASE + RecvPropInt( RECVINFO( m_spawnflags ) ), +#endif + #endif END_NETWORK_TABLE() diff --git a/src/game/shared/basecombatweapon_shared.h b/src/game/shared/basecombatweapon_shared.h index 4ca7cd03..f8fcb748 100644 --- a/src/game/shared/basecombatweapon_shared.h +++ b/src/game/shared/basecombatweapon_shared.h @@ -48,6 +48,23 @@ class CUserCmd; #define SF_WEAPON_START_CONSTRAINED (1<<0) #define SF_WEAPON_NO_PLAYER_PICKUP (1<<1) #define SF_WEAPON_NO_PHYSCANNON_PUNT (1<<2) +#ifdef MAPBASE +// I really, REALLY hope no weapon uses their own spawnflags. +// If you want yours to use spawnflags, start at 16 just to be safe. + +#define SF_WEAPON_NO_NPC_PICKUP (1<<3) // Prevents NPCs from picking up the weapon. +#define SF_WEAPON_PRESERVE_AMMO (1<<4) // Prevents the weapon from filling up to max automatically when dropped or picked up by players. +#define SF_WEAPON_PRESERVE_NAME (1<<5) // Prevents the weapon's name from being cleared upon being picked up by a player. +#define SF_WEAPON_ALWAYS_TOUCHABLE (1<<6) // Makes a weapon always touchable/pickupable, even through walls. + +// ---------------------------------------------- +// These spawnflags are not supposed to be used by level designers. +// They're just my way of trying to avoid adding new variables +// that have to stay in memory and save/load. +// ---------------------------------------------- +#define SF_WEAPON_NO_AUTO_SWITCH_WHEN_EMPTY (1<<6) // So weapons with ammo preserved at 0 don't switch. +#define SF_WEAPON_USED (1<<7) // Weapon is being +USE'd, not bumped +#endif //Percent #define CLIP_PERC_THRESHOLD 0.75f @@ -89,6 +106,26 @@ namespace vgui2 typedef unsigned long HFont; } +#ifdef MAPBASE +// ------------------ +// Weapon classes +// ------------------ +// I found myself in situations where this is useful. +// Their purpose is similar to Class_T on NPCs. + +enum WeaponClass_t +{ + WEPCLASS_INVALID = 0, + + WEPCLASS_HANDGUN, + WEPCLASS_RIFLE, + WEPCLASS_SHOTGUN, + WEPCLASS_HEAVY, + + WEPCLASS_MELEE, +}; +#endif + // ----------------------------------------- // Vector cones // ----------------------------------------- @@ -157,9 +194,7 @@ public: DECLARE_CLASS( CBaseCombatWeapon, BASECOMBATWEAPON_DERIVED_FROM ); DECLARE_NETWORKCLASS(); DECLARE_PREDICTABLE(); -#ifdef GAME_DLL DECLARE_ENT_SCRIPTDESC(); -#endif CBaseCombatWeapon(); virtual ~CBaseCombatWeapon(); @@ -174,12 +209,28 @@ public: virtual void Spawn( void ); virtual void Precache( void ); +#ifdef MAPBASE + void SetAmmoFromMapper( float flAmmo, bool bSecondary = false ); + virtual bool KeyValue( const char *szKeyName, const char *szValue ); + virtual bool GetKeyValue( const char *szKeyName, char *szValue, int iMaxLen ); +#endif + void MakeTracer( const Vector &vecTracerSrc, const trace_t &tr, int iTracerType ); // Subtypes are used to manage multiple weapons of the same type on the player. virtual int GetSubType( void ) { return m_iSubType; } virtual void SetSubType( int iType ) { m_iSubType = iType; } +#ifdef MAPBASE + virtual WeaponClass_t WeaponClassify(); + static WeaponClass_t WeaponClassFromString(const char *str); + + virtual bool SupportsBackupActivity(Activity activity); + virtual acttable_t *GetBackupActivityList(); + virtual int GetBackupActivityListCount(); + static acttable_t *GetDefaultBackupActivityList( acttable_t *pTable, int &actCount ); +#endif + virtual void Equip( CBaseCombatCharacter *pOwner ); virtual void Drop( const Vector &vecVelocity ); @@ -269,6 +320,10 @@ public: virtual bool Reload( void ); bool DefaultReload( int iClipSize1, int iClipSize2, int iActivity ); bool ReloadsSingly( void ) const; +#ifdef MAPBASE + // Originally created for the crossbow, can be used to add special NPC reloading behavior + virtual void Reload_NPC( bool bPlaySound = true ); +#endif virtual bool AutoFiresFullClip( void ) const { return false; } virtual void UpdateAutoFire( void ); @@ -334,7 +389,7 @@ public: bool IsLocked( CBaseEntity *pAsker ); //All weapons can be picked up by NPCs by default - virtual bool CanBePickedUpByNPCs( void ) { return true; } + virtual bool CanBePickedUpByNPCs(void); virtual int GetSkinOverride() const { return -1; } @@ -363,6 +418,14 @@ public: virtual bool UsesClipsForAmmo1( void ) const; virtual bool UsesClipsForAmmo2( void ) const; bool IsMeleeWeapon() const; +#ifdef MAPBASE + float GetViewmodelFOVOverride() const; + float GetBobScale() const; + float GetSwayScale() const; + float GetSwaySpeedScale() const; + virtual const char *GetDroppedModel( void ) const; + bool UsesHands( void ) const; +#endif // derive this function if you mod uses encrypted weapon info files virtual const unsigned char *GetEncryptionKey( void ); @@ -396,6 +459,52 @@ public: virtual void Activate( void ); virtual bool ShouldUseLargeViewModelVROverride() { return false; } + +#ifdef MAPBASE + // Gets the weapon script name to load. + virtual const char* GetWeaponScriptName() { return GetClassname(); } +#endif + +#ifdef MAPBASE_VSCRIPT + void ScriptSetClip1( int ammo ) { m_iClip1 = ammo; } + void ScriptSetClip2( int ammo ) { m_iClip2 = ammo; } + + HSCRIPT ScriptGetOwner(); + void ScriptSetOwner( HSCRIPT owner ); + + int ScriptWeaponClassify() { return WeaponClassify(); } + void ScriptWeaponSound( int sound_type, float soundtime = 0.0f ) { WeaponSound( (WeaponSound_t)sound_type, soundtime ); } + + const Vector& ScriptGetBulletSpread( void ) { return GetBulletSpread(); } + Vector ScriptGetBulletSpreadForProficiency( int proficiency ) { return GetBulletSpread( (WeaponProficiency_t)proficiency ); } + + int ScriptGetPrimaryAttackActivity( void ) { return GetPrimaryAttackActivity(); } + int ScriptGetSecondaryAttackActivity( void ) { return GetSecondaryAttackActivity(); } + int ScriptGetDrawActivity( void ) { return GetDrawActivity(); } + + bool FiresUnderwater() { return m_bFiresUnderwater; } + void SetFiresUnderwater( bool bVal ) { m_bFiresUnderwater = bVal; } + bool AltFiresUnderwater() { return m_bAltFiresUnderwater; } + void SetAltFiresUnderwater( bool bVal ) { m_bAltFiresUnderwater = bVal; } + float MinRange1() { return m_fMinRange1; } + void SetMinRange1( float flVal ) { m_fMinRange1 = flVal; } + float MinRange2() { return m_fMinRange2; } + void SetMinRange2( float flVal ) { m_fMinRange2 = flVal; } + float MaxRange1() { return m_fMaxRange1; } + void SetMaxRange1( float flVal ) { m_fMaxRange1 = flVal; } + float MaxRange2() { return m_fMaxRange2; } + void SetMaxRange2( float flVal ) { m_fMaxRange2 = flVal; } + //bool ReloadsSingly() { return m_bReloadsSingly; } + void SetReloadsSingly( bool bVal ) { m_bReloadsSingly = bVal; } + float FireDuration() { return m_fFireDuration; } + void SetFireDuration( float flVal ) { m_fFireDuration = flVal; } + + float NextPrimaryAttack() { return m_flNextPrimaryAttack; } + void SetNextPrimaryAttack( float flVal ) { m_flNextPrimaryAttack = flVal; } + float NextSecondaryAttack() { return m_flNextSecondaryAttack; } + void SetNextSecondaryAttack( float flVal ) { m_flNextSecondaryAttack = flVal; } +#endif + public: // Server Only Methods #if !defined( CLIENT_DLL ) @@ -442,6 +551,19 @@ public: virtual int UpdateTransmitState( void ); +#ifdef MAPBASE + void InputSetAmmo1( inputdata_t &inputdata ); + void InputSetAmmo2( inputdata_t &inputdata ); + void InputGiveDefaultAmmo( inputdata_t &inputdata ); + void InputEnablePlayerPickup( inputdata_t &inputdata ); + void InputDisablePlayerPickup( inputdata_t &inputdata ); + void InputEnableNPCPickup( inputdata_t &inputdata ); + void InputDisableNPCPickup( inputdata_t &inputdata ); + void InputBreakConstraint( inputdata_t &inputdata ); + void InputForceFire( inputdata_t &inputdata, bool bSecondary = false ); + void InputForcePrimaryFire( inputdata_t &inputdata ); + void InputForceSecondaryFire( inputdata_t &inputdata ); +#endif void InputHideWeapon( inputdata_t &inputdata ); void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); @@ -509,6 +631,7 @@ public: virtual int GetWorldModelIndex( void ); virtual void GetToolRecordingState( KeyValues *msg ); + void EnsureCorrectRenderingModel(); virtual void GetWeaponCrosshairScale( float &flScale ) { flScale = 1.f; } @@ -521,6 +644,9 @@ public: bool WantsToOverrideViewmodelAttachments( void ) { return false; } #endif + //Tony; notifications of any third person switches. + virtual void ThirdPersonSwitch( bool bThirdPerson ) {}; + #endif // End client-only methods virtual bool CanLower( void ) { return false; } @@ -589,6 +715,9 @@ public: // Weapon art CNetworkVar( int, m_iViewModelIndex ); CNetworkVar( int, m_iWorldModelIndex ); +#ifdef MAPBASE + CNetworkVar( int, m_iDroppedModelIndex ); +#endif // Sounds float m_flNextEmptySoundTime; // delay on empty sound playing @@ -645,6 +774,18 @@ public: CNetworkVar( bool, m_bFlipViewModel ); +#ifdef MAPBASE +#ifdef CLIENT_DLL + int m_spawnflags; + + inline bool HasSpawnFlags( int flags ) { return (m_spawnflags & flags) != 0; } + inline void RemoveSpawnFlags( int flags ) { m_spawnflags &= ~flags; } + inline void AddSpawnFlags( int flags ) { m_spawnflags |= flags; } +#else + //IMPLEMENT_NETWORK_VAR_FOR_DERIVED(m_spawnflags); +#endif +#endif + IPhysicsConstraint *GetConstraint() { return m_pConstraint; } private: @@ -671,6 +812,9 @@ protected: COutputEvent m_OnPlayerPickup; // Fired when the player picks up the weapon. COutputEvent m_OnNPCPickup; // Fired when an NPC picks up the weapon. COutputEvent m_OnCacheInteraction; // For awarding lambda cache achievements in HL2 on 360. See .FGD file for details +#ifdef MAPBASE + COutputEvent m_OnDropped; +#endif #else // Client .dll only bool m_bJustRestored; diff --git a/src/game/shared/baseentity_shared.cpp b/src/game/shared/baseentity_shared.cpp index bf44dc19..75792409 100644 --- a/src/game/shared/baseentity_shared.cpp +++ b/src/game/shared/baseentity_shared.cpp @@ -53,6 +53,10 @@ ConVar hl2_episodic( "hl2_episodic", "0", FCVAR_REPLICATED ); #include "tf_weaponbase.h" #endif // TF_DLL +#ifdef MAPBASE_VSCRIPT +#include "mapbase/vscript_funcs_shared.h" +#endif + #include "rumble_shared.h" // memdbgon must be the last include file in a .cpp file!!! @@ -79,6 +83,10 @@ ConVar ai_shot_bias_min( "ai_shot_bias_min", "-1.0", FCVAR_REPLICATED ); ConVar ai_shot_bias_max( "ai_shot_bias_max", "1.0", FCVAR_REPLICATED ); ConVar ai_debug_shoot_positions( "ai_debug_shoot_positions", "0", FCVAR_REPLICATED | FCVAR_CHEAT ); +#if defined(MAPBASE) && defined(GAME_DLL) +ConVar ai_shot_notify_targets( "ai_shot_notify_targets", "0", FCVAR_NONE, "Allows fired bullets to notify the NPCs and players they are targeting, regardless of whether they hit them or not. Can be used for custom AI and speech." ); +#endif + // Utility func to throttle rate at which the "reasonable position" spew goes out static double s_LastEntityReasonableEmitTime; bool CheckEmitReasonablePhysicsSpew() @@ -408,7 +416,11 @@ bool CBaseEntity::KeyValue( const char *szKeyName, const char *szValue ) } // Do this so inherited classes looking for 'angles' don't have to bother with 'angle' +#ifdef MAPBASE + return KeyValue( "angles", szBuf ); +#else return KeyValue( szKeyName, szBuf ); +#endif } // NOTE: Have to do these separate because they set two values instead of one @@ -436,6 +448,15 @@ bool CBaseEntity::KeyValue( const char *szKeyName, const char *szValue ) return true; } +#ifdef MAPBASE + if ( FStrEq( szKeyName, "eflags" ) ) + { + // Can't use DEFINE_KEYFIELD since eflags might be set before KV are parsed + AddEFlags( atoi( szValue ) ); + return true; + } +#endif + #ifdef GAME_DLL if ( FStrEq( szKeyName, "targetname" ) ) @@ -1596,6 +1617,23 @@ typedef CTraceFilterSimpleList CBulletsTraceFilter; void CBaseEntity::FireBullets( const FireBulletsInfo_t &info ) { +#if defined(MAPBASE_VSCRIPT) && defined(GAME_DLL) + if ( m_ScriptScope.IsInitialized() && g_Hook_FireBullets.CanRunInScope( m_ScriptScope ) ) + { + HSCRIPT hInfo = g_pScriptVM->RegisterInstance( const_cast(&info) ); + + ScriptVariant_t functionReturn; + ScriptVariant_t args[] = { hInfo }; + if (g_Hook_FireBullets.Call( m_ScriptScope, &functionReturn, args )) + { + if (!functionReturn.Get()) + return; + } + + g_pScriptVM->RemoveInstance( hInfo ); + } +#endif + static int tracerCount; trace_t tr; CAmmoDef* pAmmoDef = GetAmmoDef(); @@ -1669,6 +1707,19 @@ void CBaseEntity::FireBullets( const FireBulletsInfo_t &info ) } #endif // SERVER_DLL +#ifdef MAPBASE + if (info.m_pIgnoreEntList != NULL) + { + for (int i = 0; i < info.m_pIgnoreEntList->Count(); i++) + { + if (info.m_pIgnoreEntList->Element(i)) + { + traceFilter.AddEntityToIgnore(info.m_pIgnoreEntList->Element(i)); + } + } + } +#endif + bool bUnderwaterBullets = ShouldDrawUnderwaterBulletBubbles(); bool bStartedInWater = false; if ( bUnderwaterBullets ) @@ -1885,7 +1936,11 @@ void CBaseEntity::FireBullets( const FireBulletsInfo_t &info ) { flActualDamage = g_pGameRules->GetAmmoDamage( pAttacker, tr.m_pEnt, info.m_iAmmoType ); } +#ifdef MAPBASE + else if ((info.m_nFlags & FIRE_BULLETS_NO_AUTO_GIB_TYPE) == 0) +#else else +#endif { nActualDamageType = nDamageType | ((flActualDamage > 16) ? DMG_ALWAYSGIB : DMG_NEVERGIB ); } @@ -2031,6 +2086,25 @@ void CBaseEntity::FireBullets( const FireBulletsInfo_t &info ) CTakeDamageInfo dmgInfo( this, pAttacker, flCumulativeDamage, nDamageType ); gamestats->Event_WeaponHit( pPlayer, info.m_bPrimaryAttack, pPlayer->GetActiveWeapon()->GetClassname(), dmgInfo ); } + +#ifdef MAPBASE + if ( ai_shot_notify_targets.GetBool() ) + { + if ( IsPlayer() ) + { + // Look for probable target to notify of attack + CBaseEntity *pAimTarget = static_cast(this)->GetProbableAimTarget( info.m_vecSrc, info.m_vecDirShooting ); + if ( pAimTarget && pAimTarget->IsCombatCharacter() ) + { + pAimTarget->MyCombatCharacterPointer()->OnEnemyRangeAttackedMe( this, vecDir, vecEnd ); + } + } + else if ( GetEnemy() && GetEnemy()->IsCombatCharacter() ) + { + GetEnemy()->MyCombatCharacterPointer()->OnEnemyRangeAttackedMe( this, vecDir, vecEnd ); + } + } +#endif #endif } @@ -2139,7 +2213,11 @@ void CBaseEntity::TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir int blood = BloodColor(); +#if defined(MAPBASE) && defined(GAME_DLL) + if ( blood != DONT_BLEED && DamageFilterAllowsBlood( info ) ) +#else if ( blood != DONT_BLEED ) +#endif { SpawnBlood( vecOrigin, vecDir, blood, info.GetDamage() );// a little surface blood. TraceBleed( info.GetDamage(), vecDir, ptr, info.GetDamageType() ); @@ -2352,8 +2430,67 @@ void CBaseEntity::ModifyEmitSoundParams( EmitSound_t ¶ms ) params.m_pSoundName = GameRules()->TranslateEffectForVisionFilter( "sounds", params.m_pSoundName ); } #endif + +#ifdef MAPBASE_VSCRIPT + if (m_ScriptScope.IsInitialized() && g_Hook_ModifyEmitSoundParams.CanRunInScope( m_ScriptScope )) + { + HSCRIPT hParams = g_pScriptVM->RegisterInstance( reinterpret_cast(¶ms) ); + + // params + ScriptVariant_t functionReturn; + ScriptVariant_t args[] = { ScriptVariant_t( hParams ) }; + g_Hook_ModifyEmitSoundParams.Call( m_ScriptScope, &functionReturn, args ); + + g_pScriptVM->RemoveInstance( hParams ); + } +#endif } +#if defined(MAPBASE) && defined(GAME_DLL) +void CBaseEntity::ModifySentenceParams( int &iSentenceIndex, int &iChannel, float &flVolume, soundlevel_t &iSoundlevel, int &iFlags, int &iPitch, + const Vector **pOrigin, const Vector **pDirection, bool &bUpdatePositions, float &soundtime, int &iSpecialDSP, int &iSpeakerIndex ) +{ +#ifdef MAPBASE_VSCRIPT + if (m_ScriptScope.IsInitialized() && g_Hook_ModifySentenceParams.CanRunInScope( m_ScriptScope )) + { + // This is a bit of a hack, but for consistency with ModifyEmitSoundParams, put them into an EmitSound_t params + ScriptEmitSound_t params; + params.m_pSoundName = engine->SentenceNameFromIndex( iSentenceIndex ); + params.m_nChannel = iChannel; + params.m_flVolume = flVolume; + params.m_SoundLevel = iSoundlevel; + params.m_nFlags = iFlags; + params.m_nPitch = iPitch; + params.m_pOrigin = *pOrigin; + params.m_flSoundTime = soundtime; + params.m_nSpecialDSP = iSpecialDSP; + params.m_nSpeakerEntity = iSpeakerIndex; + + HSCRIPT hParams = g_pScriptVM->RegisterInstance( ¶ms ); + + // params + ScriptVariant_t functionReturn; + ScriptVariant_t args[] = { ScriptVariant_t( hParams ) }; + if (g_Hook_ModifySentenceParams.Call( m_ScriptScope, &functionReturn, args )) + { + iSentenceIndex = engine->SentenceIndexFromName( params.m_pSoundName ); + iChannel = params.m_nChannel; + flVolume = params.m_flVolume; + iSoundlevel = params.m_SoundLevel; + iFlags = params.m_nFlags; + iPitch = params.m_nPitch; + *pOrigin = params.m_pOrigin; + soundtime = params.m_flSoundTime; + iSpecialDSP = params.m_nSpecialDSP; + iSpeakerIndex = params.m_nSpeakerEntity; + } + + g_pScriptVM->RemoveInstance( hParams ); + } +#endif +} +#endif + //----------------------------------------------------------------------------- // These methods encapsulate MOVETYPE_FOLLOW, which became obsolete //----------------------------------------------------------------------------- @@ -2377,6 +2514,18 @@ void CBaseEntity::FollowEntity( CBaseEntity *pBaseEntity, bool bBoneMerge ) } } +#ifdef MAPBASE_VSCRIPT +void CBaseEntity::ScriptFollowEntity( HSCRIPT hBaseEntity, bool bBoneMerge ) +{ + FollowEntity( ToEnt( hBaseEntity ), bBoneMerge ); +} + +HSCRIPT CBaseEntity::ScriptGetFollowedEntity() +{ + return ToHScript( GetFollowedEntity() ); +} +#endif + void CBaseEntity::SetEffectEntity( CBaseEntity *pEffectEnt ) { if ( m_hEffectEntity.Get() != pEffectEnt ) @@ -2566,3 +2715,377 @@ bool CBaseEntity::IsToolRecording() const #endif } #endif + +#ifdef MAPBASE_VSCRIPT +HSCRIPT CBaseEntity::GetOrCreatePrivateScriptScope() +{ + ValidateScriptScope(); + return m_ScriptScope; +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CBaseEntity::ScriptSetParent(HSCRIPT hParent, const char *szAttachment) +{ + CBaseEntity *pParent = ToEnt(hParent); + if ( !pParent ) + { + SetParent(NULL); + return; + } + + // if an attachment is specified, the parent needs to be CBaseAnimating + if ( szAttachment && szAttachment[0] != '\0' ) + { + CBaseAnimating *pAnimating = pParent->GetBaseAnimating(); + if ( !pAnimating ) + { + Warning("ERROR: Tried to set parent for entity %s (%s), but its parent has no model.\n", GetClassname(), GetDebugName()); + return; + } + + int iAttachment = pAnimating->LookupAttachment(szAttachment); + if ( iAttachment <= 0 ) + { + Warning("ERROR: Tried to set parent for entity %s (%s), but it has no attachment named %s.\n", GetClassname(), GetDebugName(), szAttachment); + return; + } + + SetParent(pParent, iAttachment); + SetMoveType(MOVETYPE_NONE); + return; + } + + SetParent(pParent); +} + +HSCRIPT CBaseEntity::GetScriptOwnerEntity() +{ + return ToHScript(GetOwnerEntity()); +} + +void CBaseEntity::SetScriptOwnerEntity(HSCRIPT pOwner) +{ + SetOwnerEntity(ToEnt(pOwner)); +} + +#ifdef MAPBASE_VSCRIPT +HSCRIPT CBaseEntity::ScriptGetGroundEntity() +{ + return ToHScript( m_hGroundEntity.Get() ); +} + +void CBaseEntity::ScriptSetGroundEntity( HSCRIPT hGroundEnt ) +{ + SetGroundEntity( ToEnt( hGroundEnt ) ); +} +#endif + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +const Vector& CBaseEntity::ScriptGetColorVector() +{ + static Vector vecColor; + vecColor.Init( m_clrRender.GetR(), m_clrRender.GetG(), m_clrRender.GetB() ); + return vecColor; +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CBaseEntity::ScriptSetColorVector( const Vector& vecColor ) +{ + SetRenderColor( vecColor.x, vecColor.y, vecColor.z ); +} + +void CBaseEntity::ScriptSetColor( int r, int g, int b ) +{ + SetRenderColor( r, g, b ); +} + +//----------------------------------------------------------------------------- +// Vscript: Gets the entity matrix transform +//----------------------------------------------------------------------------- +HSCRIPT CBaseEntity::ScriptEntityToWorldTransform( void ) +{ + return g_pScriptVM->RegisterInstance( &EntityToWorldTransform() ); +} + +//----------------------------------------------------------------------------- +// Vscript: Gets the entity's physics object if it has one +//----------------------------------------------------------------------------- +HSCRIPT CBaseEntity::ScriptGetPhysicsObject( void ) +{ + if (VPhysicsGetObject()) + return g_pScriptVM->RegisterInstance( VPhysicsGetObject() ); + else + return NULL; +} + +//----------------------------------------------------------------------------- +// Vscript: Gets the entity's physics object if it has one +//----------------------------------------------------------------------------- +void CBaseEntity::ScriptPhysicsInitNormal( int nSolidType, int nSolidFlags, bool createAsleep ) +{ + VPhysicsInitNormal( (SolidType_t)nSolidType, nSolidFlags, createAsleep ); +} + + +#ifdef GAME_DLL +#define SCRIPT_NEVER_THINK TICK_NEVER_THINK +#else +#define SCRIPT_NEVER_THINK CLIENT_THINK_NEVER +#endif + +static inline void ScriptStopContextThink( scriptthinkfunc_t *context ) +{ + Assert( context->m_hfnThink ); + Assert( context->m_flNextThink == SCRIPT_NEVER_THINK ); + + g_pScriptVM->ReleaseScript( context->m_hfnThink ); + context->m_hfnThink = NULL; +} + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CBaseEntity::ScriptContextThink() +{ + float flNextThink = FLT_MAX; + float flScheduled = 0.0f; + + ScriptVariant_t arg = m_hScriptInstance; + + for ( int i = 0; i < m_ScriptThinkFuncs.Count(); ++i ) + { + scriptthinkfunc_t *cur = m_ScriptThinkFuncs[i]; + + if ( cur->m_flNextThink == SCRIPT_NEVER_THINK ) + { + continue; + } + + if ( cur->m_flNextThink > gpGlobals->curtime ) + { + if ( ( flScheduled == 0.0f ) || ( flScheduled > cur->m_flNextThink ) ) + { + flScheduled = cur->m_flNextThink; + } + continue; + } + +#ifdef _DEBUG + // going to run the script func + cur->m_flNextThink = 0; +#endif + + ScriptVariant_t varReturn; + +#ifndef CLIENT_DLL + if ( !cur->m_bNoParam ) + { +#endif + g_pScriptVM->ExecuteFunction( cur->m_hfnThink, &arg, 1, &varReturn, NULL, true ); +#ifndef CLIENT_DLL + } + else + { + g_pScriptVM->ExecuteFunction( cur->m_hfnThink, NULL, 0, &varReturn, NULL, true ); + } +#endif + + if ( cur->m_flNextThink == SCRIPT_NEVER_THINK ) + { + // stopped from script while thinking + continue; + } + + float flReturn; + if ( !varReturn.AssignTo( &flReturn ) ) + { + varReturn.Free(); + cur->m_flNextThink = SCRIPT_NEVER_THINK; + continue; + } + + if ( flReturn < 0.0f ) + { + cur->m_flNextThink = SCRIPT_NEVER_THINK; + continue; + } + + if ( flReturn < flNextThink ) + { + flNextThink = flReturn; + } + + cur->m_flNextThink = gpGlobals->curtime + flReturn - 0.001f; + } + + // deferred safe removal + for ( int i = 0; i < m_ScriptThinkFuncs.Count(); ) + { + scriptthinkfunc_t *cur = m_ScriptThinkFuncs[i]; + if ( cur->m_flNextThink == SCRIPT_NEVER_THINK ) + { + ScriptStopContextThink( cur ); + delete cur; + m_ScriptThinkFuncs.Remove(i); + } + else ++i; + } + + if ( flNextThink < FLT_MAX ) + { + if ( flScheduled > 0.0f ) + { + flNextThink = min( gpGlobals->curtime + flNextThink, flScheduled ); + } + else + { + flNextThink = gpGlobals->curtime + flNextThink; + } + } + else + { + if ( flScheduled > 0.0f ) + { + flNextThink = flScheduled; + } + else + { + flNextThink = SCRIPT_NEVER_THINK; + } + } + +#ifdef _DEBUG +#ifdef GAME_DLL + int nNextThinkTick = GetNextThinkTick("ScriptContextThink"); + float flNextThinkTime = TICKS_TO_TIME(nNextThinkTick); + + // If internal next think tick is earlier than what we have here with flNextThink, + // whoever set that think may fail. In worst case scenario the entity may stop thinking. + if ( nNextThinkTick > gpGlobals->tickcount ) + { + if ( flNextThink == SCRIPT_NEVER_THINK ) + Assert(0); + if ( flNextThinkTime < flNextThink ) + Assert(0); + } +#endif +#endif + +#ifdef GAME_DLL + SetNextThink( flNextThink, "ScriptContextThink" ); +#else + SetNextClientThink( flNextThink ); +#endif +} + +#ifndef CLIENT_DLL +// see ScriptSetThink +static bool s_bScriptContextThinkNoParam = false; +#endif + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CBaseEntity::ScriptSetContextThink( const char* szContext, HSCRIPT hFunc, float flTime ) +{ +#ifdef CLIENT_DLL + // Context thinking is not yet supported on client, entities can only have 1 think function. + // C_World does not have one by default, so it is safe to set its. + if ( !IsWorld() ) + { + g_pScriptVM->RaiseException("SetContextThink is only supported on C_World"); + return; + } +#endif + + scriptthinkfunc_t *pf = NULL; + unsigned hash = szContext ? HashString( szContext ) : 0; + + FOR_EACH_VEC( m_ScriptThinkFuncs, i ) + { + scriptthinkfunc_t *f = m_ScriptThinkFuncs[i]; + if ( hash == f->m_iContextHash ) + { + pf = f; + break; + } + } + + if ( hFunc ) + { + // add new + if ( !pf ) + { + pf = new scriptthinkfunc_t; + + m_ScriptThinkFuncs.SetGrowSize(1); + m_ScriptThinkFuncs.AddToTail( pf ); + + pf->m_iContextHash = hash; +#ifndef CLIENT_DLL + pf->m_bNoParam = s_bScriptContextThinkNoParam; +#endif + } + // update existing + else + { +#ifdef _DEBUG + if ( pf->m_flNextThink == 0 ) + { + Warning("Script think ('%s') was changed while it was thinking!\n", szContext); + } +#endif + g_pScriptVM->ReleaseScript( pf->m_hfnThink ); + } + + float nextthink = gpGlobals->curtime + flTime; + + pf->m_hfnThink = hFunc; + pf->m_flNextThink = nextthink; + +#ifdef GAME_DLL + int nexttick = GetNextThinkTick( RegisterThinkContext( "ScriptContextThink" ) ); + if ( nexttick <= 0 || TICKS_TO_TIME(nexttick) > nextthink ) + { + SetContextThink( &CBaseEntity::ScriptContextThink, nextthink, "ScriptContextThink" ); + } +#else + { + // let it self adjust + SetNextClientThink( gpGlobals->curtime ); + } +#endif + } + // null func input, think exists + else if ( pf ) + { + pf->m_flNextThink = SCRIPT_NEVER_THINK; + } +} + +#ifndef CLIENT_DLL +//----------------------------------------------------------------------------- +// m_bNoParam and s_bScriptContextThinkNoParam exist only to keep backwards compatibility +// and are an alternative to this script closure: +// +// function CBaseEntity::SetThink( func, time ) +// { +// SetContextThink( "", function(_){ return func() }, time ) +// } +//----------------------------------------------------------------------------- +void CBaseEntity::ScriptSetThink( HSCRIPT hFunc, float time ) +{ + s_bScriptContextThinkNoParam = true; + ScriptSetContextThink( NULL, hFunc, time ); + s_bScriptContextThinkNoParam = false; +} + +void CBaseEntity::ScriptStopThink() +{ + ScriptSetContextThink( NULL, NULL, 0.0f ); +} +#endif +#endif diff --git a/src/game/shared/baseentity_shared.h b/src/game/shared/baseentity_shared.h index bdb0fb36..3e3a064c 100644 --- a/src/game/shared/baseentity_shared.h +++ b/src/game/shared/baseentity_shared.h @@ -67,6 +67,9 @@ enum InvalidatePhysicsBits_t #endif +#include "vscript/ivscript.h" +#include "vscript_shared.h" + #if !defined( NO_ENTITY_PREDICTION ) // CBaseEntity inlines inline bool CBaseEntity::IsPlayerSimulated( void ) const @@ -250,19 +253,19 @@ inline bool CBaseEntity::IsEffectActive( int nEffects ) const return (m_fEffects & nEffects) != 0; } -#ifdef GAME_DLL -inline HSCRIPT ToHScript( CBaseEntity *pEnt ) +inline HSCRIPT ToHScript(CBaseEntity* pEnt) { return ( pEnt ) ? pEnt->GetScriptInstance() : NULL; } -template <> ScriptClassDesc_t *GetScriptDesc( CBaseEntity * ); -inline CBaseEntity *ToEnt( HSCRIPT hScript ) +template <> ScriptClassDesc_t* GetScriptDesc(CBaseEntity*); +inline CBaseEntity* ToEnt(HSCRIPT hScript) { - return ( hScript ) ? (CBaseEntity *)g_pScriptVM->GetInstanceValue( hScript, GetScriptDescForClass(CBaseEntity) ) : NULL; } +#ifdef GAME_DLL +// TODO: Mapbase has its own version of this (HScriptToEntClass). How to handle differences? template inline T* ScriptToEntClass( HSCRIPT hScript ) { diff --git a/src/game/shared/basegrenade_shared.cpp b/src/game/shared/basegrenade_shared.cpp index b981d078..0d616099 100644 --- a/src/game/shared/basegrenade_shared.cpp +++ b/src/game/shared/basegrenade_shared.cpp @@ -34,13 +34,29 @@ BEGIN_DATADESC( CBaseGrenade ) DEFINE_FIELD( m_hThrower, FIELD_EHANDLE ), // m_fRegisteredSound ??? DEFINE_FIELD( m_bIsLive, FIELD_BOOLEAN ), +#ifdef MAPBASE + DEFINE_KEYFIELD( m_DmgRadius, FIELD_FLOAT, "Radius" ), +#else DEFINE_FIELD( m_DmgRadius, FIELD_FLOAT ), +#endif DEFINE_FIELD( m_flDetonateTime, FIELD_TIME ), DEFINE_FIELD( m_flWarnAITime, FIELD_TIME ), +#ifdef MAPBASE + DEFINE_KEYFIELD( m_flDamage, FIELD_FLOAT, "Damage" ), +#else DEFINE_FIELD( m_flDamage, FIELD_FLOAT ), +#endif DEFINE_FIELD( m_iszBounceSound, FIELD_STRING ), DEFINE_FIELD( m_bHasWarnedAI, FIELD_BOOLEAN ), +#ifdef MAPBASE + DEFINE_INPUTFUNC( FIELD_FLOAT, "SetDamage", InputSetDamage ), + DEFINE_INPUTFUNC( FIELD_VOID, "Detonate", InputDetonate ), + + DEFINE_OUTPUT( m_OnDetonate, "OnDetonate" ), + DEFINE_OUTPUT( m_OnDetonate_OutPosition, "OnDetonate_OutPosition" ), +#endif + // Function Pointers DEFINE_THINKFUNC( Smoke ), DEFINE_ENTITYFUNC( BounceTouch ), @@ -58,6 +74,28 @@ void SendProxy_CropFlagsToPlayerFlagBitsLength( const SendProp *pProp, const voi #endif +#ifdef MAPBASE_VSCRIPT +BEGIN_ENT_SCRIPTDESC( CBaseGrenade, CBaseAnimating, "The base class for grenades." ) + + DEFINE_SCRIPTFUNC( GetBlastForce, "Gets the grenade's blast force override. Grenades which use base damage force calculations return 0,0,0" ) + + DEFINE_SCRIPTFUNC( GetDamage, "Gets the grenade's blast damage." ) + DEFINE_SCRIPTFUNC( GetDamageRadius, "Gets the grenade's blast damage radius." ) + DEFINE_SCRIPTFUNC( SetDamage, "Sets the grenade's blast damage." ) + DEFINE_SCRIPTFUNC( SetDamageRadius, "Sets the grenade's blast damage radius." ) + + DEFINE_SCRIPTFUNC_NAMED( ScriptGetThrower, "GetThrower", "Gets the grenade's thrower." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptSetThrower, "SetThrower", "Sets the grenade's thrower." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptGetOriginalThrower, "GetOriginalThrower", "Gets the grenade's original thrower after the thrower was changed due to being picked up by a gravity gun or something." ) + + DEFINE_SCRIPTFUNC_NAMED( GetDetonateTime, "GetTimer", "Gets the grenade's detonate time if it has one." ) + DEFINE_SCRIPTFUNC( HasWarnedAI, "Whether or not the grenade has issued its DANGER sound to the world sound list yet." ) + DEFINE_SCRIPTFUNC( IsLive, "Whether or not the grenade has issued its DANGER sound to the world sound list yet." ) + DEFINE_SCRIPTFUNC( GetWarnAITime, "Gets the time at which the grenade will warn/has warned AI." ) + +END_SCRIPTDESC(); +#endif + IMPLEMENT_NETWORKCLASS_ALIASED( BaseGrenade, DT_BaseGrenade ) BEGIN_NETWORK_TABLE( CBaseGrenade, DT_BaseGrenade ) @@ -180,6 +218,11 @@ void CBaseGrenade::Explode( trace_t *pTrace, int bitsDamageType ) EmitSound( "BaseGrenade.Explode" ); +#ifdef MAPBASE + m_OnDetonate.FireOutput(GetThrower(), this); + m_OnDetonate_OutPosition.Set(GetAbsOrigin(), GetThrower(), this); +#endif + SetThink( &CBaseGrenade::SUB_Remove ); SetTouch( NULL ); SetSolid( SOLID_NONE ); @@ -258,6 +301,25 @@ void CBaseGrenade::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE // Pass up so we still call any custom Use function BaseClass::Use( pActivator, pCaller, useType, value ); } + +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseGrenade::InputSetDamage( inputdata_t &inputdata ) +{ + SetDamage( inputdata.value.Float() ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBaseGrenade::InputDetonate( inputdata_t &inputdata ) +{ + Detonate(); +} +#endif + #endif //----------------------------------------------------------------------------- diff --git a/src/game/shared/basegrenade_shared.h b/src/game/shared/basegrenade_shared.h index 38bb684d..47840dc4 100644 --- a/src/game/shared/basegrenade_shared.h +++ b/src/game/shared/basegrenade_shared.h @@ -49,6 +49,9 @@ public: #if !defined( CLIENT_DLL ) DECLARE_DATADESC(); #endif +#ifdef MAPBASE_VSCRIPT + DECLARE_ENT_SCRIPTDESC(); +#endif virtual void Precache( void ); @@ -103,6 +106,17 @@ public: void SetThrower( CBaseCombatCharacter *pThrower ); CBaseEntity *GetOriginalThrower() { return m_hOriginalThrower; } +#ifdef MAPBASE_VSCRIPT + HSCRIPT ScriptGetThrower( void ) { return ToHScript( GetThrower() ); } + void ScriptSetThrower( HSCRIPT hThrower ) { SetThrower( ToEnt(hThrower) ? ToEnt(hThrower)->MyCombatCharacterPointer() : NULL ); } + HSCRIPT ScriptGetOriginalThrower() { return ToHScript( GetOriginalThrower() ); } + + float GetDetonateTime() { return m_flDetonateTime; } + bool HasWarnedAI() { return m_bHasWarnedAI; } + bool IsLive() { return m_bIsLive; } + float GetWarnAITime() { return m_flWarnAITime; } +#endif + #if !defined( CLIENT_DLL ) // Allow +USE pickup int ObjectCaps() @@ -111,6 +125,12 @@ public: } void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + +#ifdef MAPBASE + void InputSetDamage( inputdata_t &inputdata ); + void InputDetonate( inputdata_t &inputdata ); +#endif + #endif public: @@ -129,6 +149,11 @@ protected: CNetworkVar( float, m_flDamage ); // Damage to inflict. string_t m_iszBounceSound; // The sound to make on bouncing. If not NULL, overrides the BounceSound() function. +#if defined(MAPBASE) && !defined(CLIENT_DLL) + COutputEvent m_OnDetonate; + COutputVector m_OnDetonate_OutPosition; +#endif + private: CNetworkHandle( CBaseEntity, m_hThrower ); // Who threw this grenade EHANDLE m_hOriginalThrower; // Who was the original thrower of this grenade diff --git a/src/game/shared/baseplayer_shared.cpp b/src/game/shared/baseplayer_shared.cpp index abbd3196..ba0294d4 100644 --- a/src/game/shared/baseplayer_shared.cpp +++ b/src/game/shared/baseplayer_shared.cpp @@ -163,12 +163,30 @@ void CBasePlayer::ItemPreFrame() // Handle use events PlayerUse(); - CBaseCombatWeapon *pActive = GetActiveWeapon(); + //Tony; re-ordered this for efficiency and to make sure that certain things happen in the correct order! + if ( gpGlobals->curtime < m_flNextAttack ) + { + return; + } + if (!GetActiveWeapon()) + return; + +#if defined( CLIENT_DLL ) + // Not predicting this weapon + if ( !GetActiveWeapon()->IsPredicted() ) + return; +#endif + + GetActiveWeapon()->ItemPreFrame(); + + CBaseCombatWeapon *pWeapon; + + CBaseCombatWeapon *pActive = GetActiveWeapon(); // Allow all the holstered weapons to update for ( int i = 0; i < WeaponCount(); ++i ) { - CBaseCombatWeapon *pWeapon = GetWeapon( i ); + pWeapon = GetWeapon( i ); if ( pWeapon == NULL ) continue; @@ -178,20 +196,6 @@ void CBasePlayer::ItemPreFrame() pWeapon->ItemHolsterFrame(); } - - if ( gpGlobals->curtime < m_flNextAttack ) - return; - - if (!pActive) - return; - -#if defined( CLIENT_DLL ) - // Not predicting this weapon - if ( !pActive->IsPredicted() ) - return; -#endif - - pActive->ItemPreFrame(); } //----------------------------------------------------------------------------- @@ -1064,6 +1068,10 @@ float IntervalDistance( float x, float x0, float x1 ) return 0; } +#if !defined(CLIENT_DLL) && defined(MAPBASE_VSCRIPT) +extern ScriptHook_t g_Hook_FindUseEntity; +#endif + CBaseEntity *CBasePlayer::FindUseEntity() { Vector forward, up; @@ -1155,7 +1163,24 @@ CBaseEntity *CBasePlayer::FindUseEntity() // if this is directly under the cursor just return it now if ( i == 0 ) + { +#if !defined(CLIENT_DLL) && defined(MAPBASE_VSCRIPT) + if (m_ScriptScope.IsInitialized() && g_Hook_FindUseEntity.CanRunInScope( m_ScriptScope )) + { + // entity, is_radius + ScriptVariant_t functionReturn; + ScriptVariant_t args[] = { ToHScript( pNearest ), false }; + if (g_Hook_FindUseEntity.Call( m_ScriptScope, &functionReturn, args )) + { + pObject = ToEnt( functionReturn.m_hScript ); + pNearest = pObject; + } + } + + if (pObject) +#endif return pObject; + } } } } @@ -1240,6 +1265,19 @@ CBaseEntity *CBasePlayer::FindUseEntity() { pNearest = DoubleCheckUseNPC( pNearest, searchCenter, forward ); } + +#ifdef MAPBASE_VSCRIPT + if (m_ScriptScope.IsInitialized() && g_Hook_FindUseEntity.CanRunInScope(m_ScriptScope)) + { + // entity, is_radius + ScriptVariant_t functionReturn; + ScriptVariant_t args[] = { ToHScript( pNearest ), true }; + if (g_Hook_FindUseEntity.Call( m_ScriptScope, &functionReturn, args )) + { + pNearest = ToEnt( functionReturn.m_hScript ); + } + } +#endif if ( sv_debug_player_use.GetBool() ) { diff --git a/src/game/shared/baseplayer_shared.h b/src/game/shared/baseplayer_shared.h index 8bdc0950..fc5fdf0f 100644 --- a/src/game/shared/baseplayer_shared.h +++ b/src/game/shared/baseplayer_shared.h @@ -53,6 +53,20 @@ enum stepsoundtimes_t void CopySoundNameWithModifierToken( char *pchDest, const char *pchSource, int nMaxLenInChars, const char *pchToken ); +#ifdef MAPBASE +// Internal player spawnflags. +// These are only meant to be used internally or accessed via logic_playerproxy. +// I'm sure this isn't a bad idea whatsoever... +// +// They start at 16 because some NPC spawnflags (e.g. Wait Till Seen) +// used in places with both NPCs and players don't check whether the target is a NPC or a player. +// Spawnflags are also transmitted to the client and use a special network proxy to get around this without having to transmit unused bits. +// Be sure to update the SendPropInt() entry for m_spawnflags in player.cpp when you add any new spawnflags! +#define SF_PLAYER_NO_GEIGER (1 << 16) +#define SF_PLAYER_HIDE_SQUAD_HUD (1 << 17) +#define SF_PLAYER_SUPPRESS_FIRING (1 << 18) +#endif + // Shared header file for players #if defined( CLIENT_DLL ) #define CBasePlayer C_BasePlayer diff --git a/src/game/shared/baseviewmodel_shared.cpp b/src/game/shared/baseviewmodel_shared.cpp index 936f26d6..3339baec 100644 --- a/src/game/shared/baseviewmodel_shared.cpp +++ b/src/game/shared/baseviewmodel_shared.cpp @@ -289,6 +289,19 @@ void CBaseViewModel::AddEffects( int nEffects ) SetControlPanelsActive( false ); } +#ifdef MAPBASE + if (GetOwningWeapon() && GetOwningWeapon()->UsesHands()) + { + // If using hands, apply effect changes to any viewmodel children as well + // (fixes hand models) + for (CBaseEntity *pChild = FirstMoveChild(); pChild != NULL; pChild = pChild->NextMovePeer()) + { + if (pChild->GetClassname()[0] == 'h') + pChild->AddEffects( nEffects ); + } + } +#endif + BaseClass::AddEffects( nEffects ); } @@ -302,6 +315,19 @@ void CBaseViewModel::RemoveEffects( int nEffects ) SetControlPanelsActive( true ); } +#ifdef MAPBASE + if (GetOwningWeapon() && GetOwningWeapon()->UsesHands()) + { + // If using hands, apply effect changes to any viewmodel children as well + // (fixes hand models) + for (CBaseEntity *pChild = FirstMoveChild(); pChild != NULL; pChild = pChild->NextMovePeer()) + { + if (pChild->GetClassname()[0] == 'h') + pChild->RemoveEffects( nEffects ); + } + } +#endif + BaseClass::RemoveEffects( nEffects ); } @@ -339,6 +365,18 @@ void CBaseViewModel::SetWeaponModel( const char *modelname, CBaseCombatWeapon *w SetControlPanelsActive( showControlPanels ); } #endif + +#ifdef MAPBASE + // If our owning weapon doesn't support hands, disable the hands viewmodel(s) + bool bSupportsHands = weapon != NULL ? weapon->UsesHands() : false; + for (CBaseEntity *pChild = FirstMoveChild(); pChild != NULL; pChild = pChild->NextMovePeer()) + { + if (pChild->GetClassname()[0] == 'h') + { + bSupportsHands ? pChild->RemoveEffects( EF_NODRAW ) : pChild->AddEffects( EF_NODRAW ); + } + } +#endif } //----------------------------------------------------------------------------- @@ -410,6 +448,9 @@ void CBaseViewModel::CalcViewModelView( CBasePlayer *owner, const Vector& eyePos #if defined( CLIENT_DLL ) if ( !prediction->InPrediction() ) { + // Add lag + CalcViewModelLag( vmorigin, vmangles, vmangoriginal ); + // Let the viewmodel shake at about 10% of the amplitude of the player's view vieweffects->ApplyShake( vmorigin, vmangles, 0.1 ); } @@ -420,6 +461,21 @@ void CBaseViewModel::CalcViewModelView( CBasePlayer *owner, const Vector& eyePos g_ClientVirtualReality.OverrideViewModelTransform( vmorigin, vmangles, pWeapon && pWeapon->ShouldUseLargeViewModelVROverride() ); } +#ifdef MAPBASE + // Flip the view if we should be flipping + if (ShouldFlipViewModel()) + { + Vector vecOriginDiff = (eyePosition - vmorigin); + QAngle angAnglesDiff = (eyeAngles - vmangles); + + vmorigin.x = (eyePosition.x + vecOriginDiff.x); + vmorigin.y = (eyePosition.y + vecOriginDiff.y); + + vmangles.y = (eyeAngles.y + angAnglesDiff.y); + vmangles.z = (eyeAngles.z + angAnglesDiff.z); + } +#endif + SetLocalOrigin( vmorigin ); SetLocalAngles( vmangles ); @@ -476,6 +532,23 @@ void CBaseViewModel::CalcViewModelLag( Vector& origin, QAngle& angles, QAngle& o float flSpeed = 5.0f; +#ifdef MAPBASE + CBaseCombatWeapon *pWeapon = m_hWeapon.Get(); + if (pWeapon) + { + const FileWeaponInfo_t *pInfo = &pWeapon->GetWpnData(); + if (pInfo->m_flSwayScale != 1.0f) + { + vDifference *= pInfo->m_flSwayScale; + pInfo->m_flSwayScale != 0.0f ? flSpeed /= pInfo->m_flSwayScale : flSpeed = 0.0f; + } + if (pInfo->m_flSwaySpeedScale != 1.0f) + { + flSpeed *= pInfo->m_flSwaySpeedScale; + } + } +#endif + // If we start to lag too far behind, we'll increase the "catch up" speed. Solves the problem with fast cl_yawspeed, m_yaw or joysticks // rotating quickly. The old code would slam lastfacing with origin causing the viewmodel to pop to a new position float flDiff = vDifference.Length(); @@ -688,3 +761,63 @@ bool CBaseViewModel::GetAttachmentVelocity( int number, Vector &originVel, Quate } #endif + +#ifdef MAPBASE +#if defined( CLIENT_DLL ) +#define CHandViewModel C_HandViewModel +#endif + +// --------------------------------------- +// OzxyBox's hand viewmodel code. +// All credit goes to him. +// --------------------------------------- +class CHandViewModel : public CBaseViewModel +{ + DECLARE_CLASS( CHandViewModel, CBaseViewModel ); +public: + DECLARE_NETWORKCLASS(); + + CBaseViewModel *GetVMOwner(); + + CBaseCombatWeapon *GetOwningWeapon( void ); + +private: + CHandle m_hVMOwner; +}; + +LINK_ENTITY_TO_CLASS(hand_viewmodel, CHandViewModel); +IMPLEMENT_NETWORKCLASS_ALIASED(HandViewModel, DT_HandViewModel) + +// for whatever reason the parent doesn't get sent +// I don't really want to mess with the baseviewmodel +// so now it does +BEGIN_NETWORK_TABLE(CHandViewModel, DT_HandViewModel) +#ifndef CLIENT_DLL + SendPropEHandle(SENDINFO_NAME(m_hMoveParent, moveparent)), +#else + RecvPropInt(RECVINFO_NAME(m_hNetworkMoveParent, moveparent), 0, RecvProxy_IntToMoveParent), +#endif +END_NETWORK_TABLE() + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CBaseViewModel *CHandViewModel::GetVMOwner() +{ + if (!m_hVMOwner) + m_hVMOwner = assert_cast(GetMoveParent()); + return m_hVMOwner; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CBaseCombatWeapon *CHandViewModel::GetOwningWeapon() +{ + CBaseViewModel *pVM = GetVMOwner(); + if (pVM) + return pVM->GetOwningWeapon(); + else + return NULL; +} +#endif diff --git a/src/game/shared/baseviewmodel_shared.h b/src/game/shared/baseviewmodel_shared.h index 94e0ad83..5f1450e6 100644 --- a/src/game/shared/baseviewmodel_shared.h +++ b/src/game/shared/baseviewmodel_shared.h @@ -145,7 +145,11 @@ public: // Should this object receive shadows? virtual bool ShouldReceiveProjectedTextures( int flags ) { +#ifdef MAPBASE + return true; +#else return false; +#endif } // Add entity to visible view models list? diff --git a/src/game/shared/beam_shared.cpp b/src/game/shared/beam_shared.cpp index 36eb8def..b92ec4f9 100644 --- a/src/game/shared/beam_shared.cpp +++ b/src/game/shared/beam_shared.cpp @@ -42,6 +42,9 @@ public: DECLARE_CLASS( CInfoTarget, CPointEntity ); void Spawn( void ); +#ifdef MAPBASE + virtual int UpdateTransmitState(); +#endif }; //info targets are like point entities except you can force them to spawn on the client @@ -55,6 +58,19 @@ void CInfoTarget::Spawn( void ) } } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: Always transmitted to clients +//----------------------------------------------------------------------------- +int CInfoTarget::UpdateTransmitState() +{ + // Spawn flags 2 means we always transmit + if ( HasSpawnFlags(0x02) ) + return SetTransmitState( FL_EDICT_ALWAYS ); + return BaseClass::UpdateTransmitState(); +} +#endif + LINK_ENTITY_TO_CLASS( info_target, CInfoTarget ); #endif @@ -773,7 +789,9 @@ void CBeam::BeamDamage( trace_t *ptr ) if ( ptr->fraction != 1.0 && ptr->m_pEnt != NULL ) { CBaseEntity *pHit = ptr->m_pEnt; +#ifndef MAPBASE if ( pHit ) +#endif { ClearMultiDamage(); Vector dir = ptr->endpos - GetAbsOrigin(); diff --git a/src/game/shared/choreoevent.cpp b/src/game/shared/choreoevent.cpp index bf6c998b..d7b992e5 100644 --- a/src/game/shared/choreoevent.cpp +++ b/src/game/shared/choreoevent.cpp @@ -2064,6 +2064,8 @@ static EventNameMap_t g_NameMap[] = { CChoreoEvent::STOPPOINT, "stoppoint" }, { CChoreoEvent::PERMIT_RESPONSES, "permitresponses" }, { CChoreoEvent::GENERIC, "generic" }, + { CChoreoEvent::CAMERA, "camera" }, + { CChoreoEvent::SCRIPT, "script" }, }; //----------------------------------------------------------------------------- diff --git a/src/game/shared/choreoevent.h b/src/game/shared/choreoevent.h index ad4828d3..64db52af 100644 --- a/src/game/shared/choreoevent.h +++ b/src/game/shared/choreoevent.h @@ -307,6 +307,12 @@ public: // A string passed to the game code for interpretation GENERIC, + // Camera control + CAMERA, + + // Script function call + SCRIPT, + // THIS MUST BE LAST!!! NUM_TYPES, } EVENTTYPE; diff --git a/src/game/shared/choreoscene.h b/src/game/shared/choreoscene.h index d8203c14..b4a5679d 100644 --- a/src/game/shared/choreoscene.h +++ b/src/game/shared/choreoscene.h @@ -84,6 +84,9 @@ public: // Event callback handler void SetEventCallbackInterface( IChoreoEventCallback *callback ); +#ifdef MAPBASE + IChoreoEventCallback *GetEventCallbackInterface() { return m_pIChoreoEventCallback; } +#endif // Loading bool ParseFromBuffer( char const *pFilename, ISceneTokenProcessor *tokenizer ); diff --git a/src/game/shared/env_wind_shared.cpp b/src/game/shared/env_wind_shared.cpp index 67ace652..27854deb 100644 --- a/src/game/shared/env_wind_shared.cpp +++ b/src/game/shared/env_wind_shared.cpp @@ -79,14 +79,16 @@ //----------------------------------------------------------------------------- // globals //----------------------------------------------------------------------------- -static Vector s_vecWindVelocity( 0, 0, 0 ); - static CUtlLinkedList< CEnvWindShared * > s_windControllers; CEnvWindShared::CEnvWindShared() : m_WindAveQueue(10), m_WindVariationQueue(10) { m_pWindSound = NULL; s_windControllers.AddToTail( this ); +#ifdef MAPBASE + m_windRadius = -1.0f; + m_flTreeSwayScale = 1.0f; +#endif } CEnvWindShared::~CEnvWindShared() @@ -110,6 +112,10 @@ void CEnvWindShared::Init( int nEntIndex, int iRandomSeed, float flTime, m_iWindDir = m_iInitialWindDir = iInitialWindYaw; // Bound it for networking as a postive integer m_iInitialWindDir = (int)( anglemod( m_iInitialWindDir ) ); +#ifdef MAPBASE + if (m_windRadiusInner == 0.0f) + m_windRadiusInner = m_windRadius; +#endif m_flAveWindSpeed = m_flWindSpeed = m_flInitialWindSpeed = flInitialWindSpeed; @@ -211,8 +217,13 @@ float CEnvWindShared::WindThink( float flTime ) ComputeWindVariation( flTime ); - // Update Tree Sway - UpdateTreeSway( flTime ); +#if defined(MAPBASE) + if (m_flTreeSwayScale != 0.0f) +#endif + { + // Update Tree Sway + UpdateTreeSway( flTime ); + } while (true) { @@ -323,13 +334,54 @@ void ResetWindspeed() } } +#ifdef MAPBASE //----------------------------------------------------------------------------- // GetWindspeedAtTime was never finished to actually take time in to consideration. We don't need // features that aren't written, but we do need to have multiple wind controllers on a map, so // we need to find the one that is affecting the given location and return its speed. +// +// NEW WITH MAPBASE: Inner-radius! +// You can now choose an inner-radius for your wind, which allows for varying intensities at different distances. +// This can mix in with a global wind controller or even other wind controllers. //----------------------------------------------------------------------------- Vector GetWindspeedAtLocation( const Vector &location ) { + Vector wind = Vector( 0, 0, 0 ); + +#ifdef MAPBASE + FOR_EACH_LL( s_windControllers, it ) + { + CEnvWindShared *thisWindController = s_windControllers[it]; + float distance = (thisWindController->m_location - location).Length(); + + if( distance < thisWindController->m_windRadius ) + { + if (distance > thisWindController->m_windRadiusInner) + { + // New with Mapbase: Inner-radius! + wind += thisWindController->m_currentWindVector * + ((distance - thisWindController->m_windRadiusInner) / (thisWindController->m_windRadius - thisWindController->m_windRadiusInner)); + } + else + { + // This location is within our area of influence, so return our computer wind vector + return thisWindController->m_currentWindVector; + } + } + } + + FOR_EACH_LL( s_windControllers, it ) + { + CEnvWindShared *thisWindController = s_windControllers[it]; + + if( thisWindController->m_windRadius == -1.0f ) + { + // We do a second search for a global controller so you don't have to worry about order in the list. + //wind += thisWindController->m_currentWindVector; + wind = VectorLerp( wind, thisWindController->m_currentWindVector, 1.0f ); + } + } +#else FOR_EACH_LL( s_windControllers, it ) { CEnvWindShared *thisWindController = s_windControllers[it]; @@ -352,10 +404,11 @@ Vector GetWindspeedAtLocation( const Vector &location ) return thisWindController->m_currentWindVector; } } +#endif - return Vector(0,0,0);// No wind + return wind;// No wind } - +#endif //----------------------------------------------------------------------------- // Method to sample the windspeed at a particular time diff --git a/src/game/shared/env_wind_shared.h b/src/game/shared/env_wind_shared.h index ca911fca..6494084a 100644 --- a/src/game/shared/env_wind_shared.h +++ b/src/game/shared/env_wind_shared.h @@ -160,7 +160,10 @@ public: CNetworkVar( int, m_iMinWind ); // the slowest the wind can normally blow CNetworkVar( int, m_iMaxWind ); // the fastest the wind can normally blow - CNetworkVar( int, m_windRadius ); // the radius this entity affects with its windiness, so a map can have multiple +#ifdef MAPBASE + CNetworkVar( float, m_windRadius ); // the radius this entity affects with its windiness, so a map can have multiple + CNetworkVar( float, m_windRadiusInner ); // the inner-radius for noticable distance fading +#endif CNetworkVar( int, m_iMinGust ); // the slowest that a gust can be CNetworkVar( int, m_iMaxGust ); // the fastest that a gust can be @@ -181,6 +184,8 @@ public: Vector m_CurrentSwayVector; Vector m_PrevSwayVector; + CNetworkVar( float, m_flTreeSwayScale ); + CNetworkVar( int, m_iInitialWindDir ); CNetworkVar( float, m_flInitialWindSpeed ); diff --git a/src/game/shared/eventlist.cpp b/src/game/shared/eventlist.cpp index 4218ff56..8678e666 100644 --- a/src/game/shared/eventlist.cpp +++ b/src/game/shared/eventlist.cpp @@ -233,6 +233,11 @@ void EventList_RegisterSharedEvents( void ) REGISTER_SHARED_ANIMEVENT( AE_SV_DUSTTRAIL, AE_TYPE_SERVER ); REGISTER_SHARED_ANIMEVENT( AE_CL_CREATE_PARTICLE_EFFECT, AE_TYPE_CLIENT ); +#ifdef MAPBASE // From Alien Swarm SDK + REGISTER_SHARED_ANIMEVENT( AE_CL_STOP_PARTICLE_EFFECT, AE_TYPE_CLIENT ); + REGISTER_SHARED_ANIMEVENT( AE_CL_ADD_PARTICLE_EFFECT_CP, AE_TYPE_CLIENT ); + //REGISTER_SHARED_ANIMEVENT( AE_CL_CREATE_PARTICLE_BRASS, AE_TYPE_CLIENT ); +#endif REGISTER_SHARED_ANIMEVENT( AE_RAGDOLL, AE_TYPE_SERVER ); @@ -248,6 +253,15 @@ void EventList_RegisterSharedEvents( void ) REGISTER_SHARED_ANIMEVENT( AE_WPN_UNHIDE, AE_TYPE_CLIENT | AE_TYPE_SERVER ); REGISTER_SHARED_ANIMEVENT( AE_WPN_PLAYWPNSOUND, AE_TYPE_CLIENT | AE_TYPE_SERVER ); + +#ifdef MAPBASE + REGISTER_SHARED_ANIMEVENT( AE_NPC_RESPONSE, AE_TYPE_SERVER ); + REGISTER_SHARED_ANIMEVENT( AE_NPC_RESPONSE_FORCED, AE_TYPE_SERVER ); + + REGISTER_SHARED_ANIMEVENT( AE_VSCRIPT_RUN, AE_TYPE_CLIENT | AE_TYPE_SERVER ); + REGISTER_SHARED_ANIMEVENT( AE_VSCRIPT_RUN_FILE, AE_TYPE_CLIENT | AE_TYPE_SERVER ); +#endif + REGISTER_SHARED_ANIMEVENT( AE_RD_ROBOT_POP_PANELS_OFF, AE_TYPE_CLIENT | AE_TYPE_SERVER ); REGISTER_SHARED_ANIMEVENT( AE_TAUNT_ENABLE_MOVE, AE_TYPE_CLIENT | AE_TYPE_SERVER ); diff --git a/src/game/shared/eventlist.h b/src/game/shared/eventlist.h index eac5ca6b..579046d7 100644 --- a/src/game/shared/eventlist.h +++ b/src/game/shared/eventlist.h @@ -69,6 +69,11 @@ typedef enum AE_SV_DUSTTRAIL, AE_CL_CREATE_PARTICLE_EFFECT, +#ifdef MAPBASE // From Alien Swarm SDK + AE_CL_STOP_PARTICLE_EFFECT, + AE_CL_ADD_PARTICLE_EFFECT_CP, + //AE_CL_CREATE_PARTICLE_BRASS, +#endif AE_RAGDOLL, @@ -85,6 +90,14 @@ typedef enum AE_WPN_PLAYWPNSOUND, // Play a weapon sound from the weapon script file +#ifdef MAPBASE + AE_NPC_RESPONSE, // Play a response system concept if we're not speaking + AE_NPC_RESPONSE_FORCED, // Always play a response system concept + + AE_VSCRIPT_RUN, // Run vscript code (server + client) + AE_VSCRIPT_RUN_FILE, // Run vscript file (server + client) +#endif + AE_RD_ROBOT_POP_PANELS_OFF, AE_TAUNT_ENABLE_MOVE, diff --git a/src/game/shared/func_ladder.cpp b/src/game/shared/func_ladder.cpp index 959424c5..f3852cc5 100644 --- a/src/game/shared/func_ladder.cpp +++ b/src/game/shared/func_ladder.cpp @@ -5,6 +5,9 @@ //=============================================================================// #include "cbase.h" #include "func_ladder.h" +#ifdef MAPBASE +#include "hl_gamemovement.h" +#endif // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" @@ -13,6 +16,10 @@ /*static*/ ConVar sv_showladders( "sv_showladders", "0", 0, "Show bbox and dismount points for all ladders (must be set before level load.)\n" ); #endif +#if MAPBASE +extern IGameMovement *g_pGameMovement; +#endif + CUtlVector< CFuncLadder * > CFuncLadder::s_Ladders; CUtlVector< CInfoLadderDismount* > CInfoLadderDismount::s_Dismounts; @@ -107,7 +114,11 @@ void CFuncLadder::Spawn() } // Force geometry overlays on, but only if developer 2 is set... +#ifdef MAPBASE + if ( sv_showladders.GetBool() ) +#else if ( developer.GetInt() > 1 ) +#endif { m_debugOverlays |= OVERLAY_TEXT_BIT; } @@ -397,6 +408,26 @@ void CFuncLadder::InputDisable( inputdata_t &inputdata ) m_bDisabled = true; } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: +// Input : &inputdata - +//----------------------------------------------------------------------------- +void CFuncLadder::InputForcePlayerOn( inputdata_t &inputdata ) +{ + static_cast(g_pGameMovement)->ForcePlayerOntoLadder(this); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : &inputdata - +//----------------------------------------------------------------------------- +void CFuncLadder::InputCheckPlayerOn( inputdata_t &inputdata ) +{ + static_cast(g_pGameMovement)->MountPlayerOntoLadder(this); +} +#endif + //----------------------------------------------------------------------------- // Purpose: // Input : *pPlayer - @@ -474,6 +505,10 @@ BEGIN_DATADESC( CFuncLadder ) DEFINE_KEYFIELD( m_surfacePropName,FIELD_STRING, "ladderSurfaceProperties" ), DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ), DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ), +#ifdef MAPBASE + DEFINE_INPUTFUNC( FIELD_VOID, "ForcePlayerOn", InputForcePlayerOn ), + DEFINE_INPUTFUNC( FIELD_VOID, "CheckPlayerOn", InputCheckPlayerOn ), +#endif DEFINE_OUTPUT( m_OnPlayerGotOnLadder, "OnPlayerGotOnLadder" ), DEFINE_OUTPUT( m_OnPlayerGotOffLadder, "OnPlayerGotOffLadder" ), diff --git a/src/game/shared/func_ladder.h b/src/game/shared/func_ladder.h index 9be7211c..7c583d8b 100644 --- a/src/game/shared/func_ladder.h +++ b/src/game/shared/func_ladder.h @@ -72,6 +72,11 @@ public: void InputEnable( inputdata_t &inputdata ); void InputDisable( inputdata_t &inputdata ); +#ifdef MAPBASE + void InputForcePlayerOn( inputdata_t &inputdata ); + void InputCheckPlayerOn( inputdata_t &inputdata ); +#endif + bool IsEnabled() const; void PlayerGotOn( CBasePlayer *pPlayer ); diff --git a/src/game/shared/gamemovement.cpp b/src/game/shared/gamemovement.cpp index ad1e4709..cf597711 100644 --- a/src/game/shared/gamemovement.cpp +++ b/src/game/shared/gamemovement.cpp @@ -58,6 +58,16 @@ ConVar player_limit_jump_speed( "player_limit_jump_speed", "1", FCVAR_REPLICATED // duck controls. Its value is meaningless anytime we don't have the options window open. ConVar option_duck_method("option_duck_method", "1", FCVAR_REPLICATED|FCVAR_ARCHIVE );// 0 = HOLD to duck, 1 = Duck is a toggle +#ifdef MAPBASE +ConVar player_crouch_multiplier( "player_crouch_multiplier", "0.33333333", FCVAR_NONE ); +#endif + +#ifdef STAGING_ONLY +#ifdef CLIENT_DLL +ConVar debug_latch_reset_onduck( "debug_latch_reset_onduck", "1", FCVAR_CHEAT ); +#endif +#endif + // [MD] I'll remove this eventually. For now, I want the ability to A/B the optimizations. bool g_bMovementOptimizations = true; @@ -2839,7 +2849,7 @@ inline bool CGameMovement::OnLadder( trace_t &trace ) // HPE_BEGIN // [sbodenbender] make ladders easier to climb in cstrike //============================================================================= -#if defined (CSTRIKE_DLL) +#if defined (CSTRIKE_DLL) || defined(HL2_USES_FUNC_LADDER_CODE) ConVar sv_ladder_dampen ( "sv_ladder_dampen", "0.2", FCVAR_REPLICATED, "Amount to dampen perpendicular movement on a ladder", true, 0.0f, true, 1.0f ); ConVar sv_ladder_angle( "sv_ladder_angle", "-0.707", FCVAR_REPLICATED, "Cos of angle of incidence to ladder perpendicular for applying ladder_dampen", true, -1.0f, true, 1.0f ); #endif @@ -3922,12 +3932,24 @@ void CGameMovement::CheckFalling( void ) if ( player->GetGroundEntity() == NULL || player->m_Local.m_flFallVelocity <= 0 ) return; +#ifdef MAPBASE + if ( player->m_bInTriggerFall ) + { + // This value lets the existing fall damage functions ensure a fatal fall. + player->m_Local.m_flFallVelocity += (PLAYER_FATAL_FALL_SPEED + PLAYER_LAND_ON_FLOATING_OBJECT); + } +#endif + if ( !IsDead() && player->m_Local.m_flFallVelocity >= PLAYER_FALL_PUNCH_THRESHOLD ) { bool bAlive = true; float fvol = 0.5; +#ifdef MAPBASE + if ( player->GetWaterLevel() > 0 && !player->m_bInTriggerFall ) +#else if ( player->GetWaterLevel() > 0 ) +#endif { // They landed in water. } @@ -4299,7 +4321,8 @@ void CGameMovement::HandleDuckingSpeedCrop( void ) { if ( !( m_iSpeedCropped & SPEED_CROPPED_DUCK ) && ( player->GetFlags() & FL_DUCKING ) && ( player->GetGroundEntity() != NULL ) ) { - float frac = 0.33333333f; + // Mapbase makes this an adjustable convar + float frac = player_crouch_multiplier.GetFloat(); mv->m_flForwardMove *= frac; mv->m_flSideMove *= frac; mv->m_flUpMove *= frac; diff --git a/src/game/shared/gamemovement.h b/src/game/shared/gamemovement.h index 1cb3fa52..b04479e5 100644 --- a/src/game/shared/gamemovement.h +++ b/src/game/shared/gamemovement.h @@ -25,6 +25,13 @@ #define GAMEMOVEMENT_TIME_TO_UNDUCK ( TIME_TO_UNDUCK * 1000.0f ) // ms #define GAMEMOVEMENT_TIME_TO_UNDUCK_INV ( GAMEMOVEMENT_DUCK_TIME - GAMEMOVEMENT_TIME_TO_UNDUCK ) +#ifdef MAPBASE +// reddit.com/r/SourceEngine/comments/8vx53b/how_to_get_brush_ladders_working/ +// +// Implements code that allows func_ladder to be used in HL2. +#define HL2_USES_FUNC_LADDER_CODE 1 +#endif + enum { SPEED_CROPPED_RESET = 0, diff --git a/src/game/shared/gamerules.cpp b/src/game/shared/gamerules.cpp index 069dc162..b0510d41 100644 --- a/src/game/shared/gamerules.cpp +++ b/src/game/shared/gamerules.cpp @@ -73,6 +73,67 @@ IMPLEMENT_NETWORKCLASS_ALIASED( GameRulesProxy, DT_GameRulesProxy ) BEGIN_NETWORK_TABLE_NOBASE( CGameRulesProxy, DT_GameRulesProxy ) END_NETWORK_TABLE() +#ifdef MAPBASE_VSCRIPT +BEGIN_SCRIPTDESC_ROOT( CGameRules, SCRIPT_SINGLETON "The container of the game's rules, handling behavior which could be different on a game-by-game basis." ) + + DEFINE_SCRIPTFUNC( Name, "Gets the name of these rules." ) + + DEFINE_SCRIPTFUNC( Damage_IsTimeBased, "Damage types that are time-based." ) + DEFINE_SCRIPTFUNC( Damage_ShouldGibCorpse, "Damage types that gib the corpse." ) + DEFINE_SCRIPTFUNC( Damage_ShowOnHUD, "Damage types that have client HUD art." ) + DEFINE_SCRIPTFUNC( Damage_NoPhysicsForce, "Damage types that don't have to supply a physics force & position." ) + DEFINE_SCRIPTFUNC( Damage_ShouldNotBleed, "Damage types that don't make the player bleed." ) + + DEFINE_SCRIPTFUNC( ShouldCollide, "Returns whether two collision groups collide with each other in this game." ) + + DEFINE_SCRIPTFUNC( DefaultFOV, "Default player FOV in this game." ) + + DEFINE_SCRIPTFUNC( GetDamageMultiplier, "Ammo type damage multiplier." ) + + DEFINE_SCRIPTFUNC( IsMultiplayer, "Returns true if this is a multiplayer game (like co-op or deathmatch)." ) + + DEFINE_SCRIPTFUNC( InRoundRestart, "Returns true if the round is restarting." ) + + DEFINE_SCRIPTFUNC( AllowThirdPersonCamera, "Returns true if third-person camera is allowed." ) + +#ifdef CLIENT_DLL + + DEFINE_SCRIPTFUNC( IsBonusChallengeTimeBased, "" ) + DEFINE_SCRIPTFUNC( AllowMapParticleEffect, "" ) + DEFINE_SCRIPTFUNC( AllowWeatherParticles, "" ) + DEFINE_SCRIPTFUNC( AllowMapVisionFilterShaders, "" ) + DEFINE_SCRIPTFUNC( TranslateEffectForVisionFilter, "" ) + DEFINE_SCRIPTFUNC( IsLocalPlayer, "" ) + //DEFINE_SCRIPTFUNC( ShouldWarnOfAbandonOnQuit, "" ) + +#else + + DEFINE_SCRIPTFUNC( RefreshSkillData, "" ) + + DEFINE_SCRIPTFUNC( IsSkillLevel, "Returns true if the game is set to the specified difficulty/skill level." ) + DEFINE_SCRIPTFUNC( GetSkillLevel, "Returns the game's difficulty/skill level." ) + DEFINE_SCRIPTFUNC( SetSkillLevel, "Sets the game's difficulty/skill level." ) + + DEFINE_SCRIPTFUNC_NAMED( FAllowFlashlight, "AllowFlashlight", "Returns true if players are allowed to switch on their flashlight." ) + + DEFINE_SCRIPTFUNC( IsDeathmatch, "" ) + DEFINE_SCRIPTFUNC( IsTeamplay, "" ) + DEFINE_SCRIPTFUNC( IsCoOp, "" ) + + DEFINE_SCRIPTFUNC( GetGameDescription, "This is the game description that gets seen in server browsers." ) + + DEFINE_SCRIPTFUNC( AllowSPRespawn, "" ) + + DEFINE_SCRIPTFUNC_NAMED( FAllowNPCs, "AllowNPCs", "Returns true if NPCs are allowed." ) + +#endif + + DEFINE_SCRIPTFUNC( GetGameTypeName, "" ) + DEFINE_SCRIPTFUNC( GetGameType, "" ) + +END_SCRIPTDESC() +#endif + CGameRulesProxy::CGameRulesProxy() { @@ -609,6 +670,30 @@ void CGameRules::EndGameFrame( void ) } } +#ifdef MAPBASE +void CGameRules::OnSkillLevelChanged( int iNewLevel ) +{ + variant_t varNewLevel; + varNewLevel.SetInt(iNewLevel); + + // Iterate through all logic_skill entities and fire them + CBaseEntity *pEntity = gEntList.FindEntityByClassname(NULL, "logic_skill"); + while (pEntity) + { + pEntity->AcceptInput("SkillLevelChanged", UTIL_GetLocalPlayer(), NULL, varNewLevel, 0); + pEntity = gEntList.FindEntityByClassname(pEntity, "logic_skill"); + } + + // Fire game event for difficulty level changed + IGameEvent *event = gameeventmanager->CreateEvent("skill_changed"); + if (event) + { + event->SetInt("skill_level", iNewLevel); + gameeventmanager->FireEvent(event); + } +} +#endif + //----------------------------------------------------------------------------- // trace line rules //----------------------------------------------------------------------------- @@ -958,3 +1043,28 @@ void __MsgFunc_SavedConvar( bf_read &msg ) } USER_MESSAGE_REGISTER( SavedConvar ); #endif + +#ifdef MAPBASE +void CGameRules::ClientCommandKeyValues(edict_t* pEntity, KeyValues* pKeyValues) +{ +#ifndef CLIENT_DLL + static int s_nEntityCommandSymbol = KeyValues::CallGetSymbolForString("EntityCommand"); + static int s_nEntIndexSymbol = KeyValues::CallGetSymbolForString("entindex"); + static int s_nCommandDataSymbol = KeyValues::CallGetSymbolForString("command_data"); + + CBasePlayer* pPlayer = (CBasePlayer*)GetContainingEntity(pEntity); + if (!pPlayer) + return; + + if (pKeyValues->GetNameSymbol() == s_nEntityCommandSymbol) + { + CBaseEntity* pEntity = CBaseEntity::Instance(pKeyValues->GetInt(s_nEntIndexSymbol)); + KeyValues* pkvCommand = pKeyValues->FindKey(s_nCommandDataSymbol); + if (pEntity && pkvCommand) + { + pEntity->HandleEntityCommand(pPlayer, pkvCommand); + } + } +#endif // GAME_DLL +} +#endif // MAPBASE diff --git a/src/game/shared/gamerules.h b/src/game/shared/gamerules.h index d24dabe9..f226c8a6 100644 --- a/src/game/shared/gamerules.h +++ b/src/game/shared/gamerules.h @@ -178,7 +178,12 @@ public: //Allow thirdperson camera. virtual bool AllowThirdPersonCamera( void ) { return false; } - virtual void ClientCommandKeyValues( edict_t *pEntity, KeyValues *pKeyValues ) {} +#ifdef MAPBASE + virtual void ClientCommandKeyValues(edict_t* pEntity, KeyValues* pKeyValues); +#else + virtual void ClientCommandKeyValues(edict_t* pEntity, KeyValues* pKeyValues) {} +#endif // MAPBASE + // IsConnectedUserInfoChangeAllowed allows the clients to change // cvars with the FCVAR_NOT_CONNECTED rule if it returns true @@ -237,7 +242,11 @@ public: virtual bool IsSkillLevel( int iLevel ) { return GetSkillLevel() == iLevel; } virtual int GetSkillLevel() { return g_iSkillLevel; } +#ifdef MAPBASE + virtual void OnSkillLevelChanged( int iNewLevel ); +#else virtual void OnSkillLevelChanged( int iNewLevel ) {}; +#endif virtual void SetSkillLevel( int iLevel ) { int oldLevel = g_iSkillLevel; @@ -296,6 +305,10 @@ public: virtual CBaseEntity *GetPlayerSpawnSpot( CBasePlayer *pPlayer );// Place this player on their spawnspot and face them the proper direction. virtual bool IsSpawnPointValid( CBaseEntity *pSpot, CBasePlayer *pPlayer ); +#ifdef MAPBASE + virtual bool AllowSPRespawn() { return false; } +#endif + virtual bool AllowAutoTargetCrosshair( void ) { return TRUE; }; virtual bool ClientCommand( CBaseEntity *pEdict, const CCommand &args ); // handles the user commands; returns TRUE if command handled properly virtual void ClientSettingsChanged( CBasePlayer *pPlayer ); // the player has changed cvars diff --git a/src/game/shared/gamestringpool.cpp b/src/game/shared/gamestringpool.cpp index 58676fa7..105f991a 100644 --- a/src/game/shared/gamestringpool.cpp +++ b/src/game/shared/gamestringpool.cpp @@ -10,6 +10,9 @@ #include "utlhashtable.h" #include "igamesystem.h" #include "gamestringpool.h" +#if defined(MAPBASE) && defined(GAME_DLL) +#include "mapbase/GlobalStrings.h" +#endif #include "tier1/stringpool.h" @@ -23,6 +26,10 @@ class CGameStringPool : public CStringPool, public CBaseGameSystem { virtual char const *Name() { return "CGameStringPool"; } +#if defined(MAPBASE) && defined(GAME_DLL) + virtual void LevelInitPreEntity() { InitGlobalStrings(); } +#endif + virtual void LevelShutdownPostEntity() { Cleanup(); diff --git a/src/game/shared/hl2/basehlcombatweapon_shared.cpp b/src/game/shared/hl2/basehlcombatweapon_shared.cpp index c5798a1f..ebff323e 100644 --- a/src/game/shared/hl2/basehlcombatweapon_shared.cpp +++ b/src/game/shared/hl2/basehlcombatweapon_shared.cpp @@ -317,6 +317,14 @@ float CBaseHLCombatWeapon::CalcViewmodelBob( void ) g_lateralBob = speed*0.005f; g_lateralBob = g_lateralBob*0.3 + g_lateralBob*0.7*sin(cycle); g_lateralBob = clamp( g_lateralBob, -7.0f, 4.0f ); + +#ifdef MAPBASE + if (GetBobScale() != 1.0f) + { + //g_verticalBob *= GetBobScale(); + g_lateralBob *= GetBobScale(); + } +#endif //NOTENOTE: We don't use this return value in our case (need to restructure the calculation function setup!) return 0.0f; diff --git a/src/game/shared/hl2/hl2_gamerules.cpp b/src/game/shared/hl2/hl2_gamerules.cpp index bdd5abec..d805c8f8 100644 --- a/src/game/shared/hl2/hl2_gamerules.cpp +++ b/src/game/shared/hl2/hl2_gamerules.cpp @@ -38,10 +38,217 @@ BEGIN_NETWORK_TABLE_NOBASE( CHalfLife2, DT_HL2GameRules ) #endif END_NETWORK_TABLE() +#if MAPBASE && GAME_DLL +extern bool g_bUseLegacyFlashlight; +extern bool g_bCacheLegacyFlashlightStatus; + +BEGIN_DATADESC( CHalfLife2Proxy ) + + // These get the gamerules values on save and write to them on restore + DEFINE_FIELD( m_save_DefaultCitizenType, FIELD_INTEGER ), + DEFINE_FIELD( m_save_LegacyFlashlight, FIELD_CHARACTER ), + DEFINE_FIELD( m_save_PlayerSquadAutosummonDisabled, FIELD_BOOLEAN ), + DEFINE_FIELD( m_save_StunstickPickupBehavior, FIELD_INTEGER ), + DEFINE_FIELD( m_save_AllowSPRespawn, FIELD_BOOLEAN ), + + // Inputs + DEFINE_INPUTFUNC( FIELD_VOID, "EpisodicOn", InputEpisodicOn ), + DEFINE_INPUTFUNC( FIELD_VOID, "EpisodicOff", InputEpisodicOff ), + + // These are FIELD_STRING because they call KeyValue() directly + DEFINE_INPUTFUNC( FIELD_STRING, "SetFriendlyFire", InputSetFriendlyFire ), + DEFINE_INPUTFUNC( FIELD_STRING, "DefaultCitizenType", InputSetDefaultCitizenType ), + DEFINE_INPUTFUNC( FIELD_STRING, "SetLegacyFlashlight", InputSetLegacyFlashlight ), + DEFINE_INPUTFUNC( FIELD_STRING, "SetPlayerSquadAutosummon", InputSetPlayerSquadAutosummon ), + DEFINE_INPUTFUNC( FIELD_STRING, "SetStunstickPickupBehavior", InputSetStunstickPickupBehavior ), + DEFINE_INPUTFUNC( FIELD_STRING, "SetAllowSPRespawn", InputSetAllowSPRespawn ), + +END_DATADESC() +#endif LINK_ENTITY_TO_CLASS( hl2_gamerules, CHalfLife2Proxy ); IMPLEMENT_NETWORKCLASS_ALIASED( HalfLife2Proxy, DT_HalfLife2Proxy ) +#if defined(MAPBASE) && defined(GAME_DLL) +void CHalfLife2Proxy::InputEpisodicOn( inputdata_t &inputdata ) { KeyValue("SetEpisodic", "1"); } +void CHalfLife2Proxy::InputEpisodicOff( inputdata_t &inputdata ) { KeyValue("SetEpisodic", "0"); } +void CHalfLife2Proxy::InputSetFriendlyFire( inputdata_t &inputdata ) { KeyValue("GlobalFriendlyFire", inputdata.value.String()); } +void CHalfLife2Proxy::InputSetDefaultCitizenType( inputdata_t &inputdata ) { KeyValue("DefaultCitizenType", inputdata.value.String()); } +void CHalfLife2Proxy::InputSetLegacyFlashlight( inputdata_t &inputdata ) { KeyValue("SetLegacyFlashlight", inputdata.value.String()); } +void CHalfLife2Proxy::InputSetPlayerSquadAutosummon( inputdata_t &inputdata ) { KeyValue("SetPlayerSquadAutosummon", inputdata.value.String()); } +void CHalfLife2Proxy::InputSetStunstickPickupBehavior( inputdata_t &inputdata ) { KeyValue("SetStunstickPickupBehavior", inputdata.value.String()); } +void CHalfLife2Proxy::InputSetAllowSPRespawn( inputdata_t &inputdata ) { KeyValue( "SetAllowSPRespawn", inputdata.value.String() ); } + +//----------------------------------------------------------------------------- +// Purpose: Cache user entity field values until spawn is called. +// Input : szKeyName - Key to handle. +// szValue - Value for key. +// Output : Returns true if the key was handled, false if not. +//----------------------------------------------------------------------------- +bool CHalfLife2Proxy::KeyValue( const char *szKeyName, const char *szValue ) +{ + if (FStrEq(szKeyName, "DefaultCitizenType")) + { + HL2GameRules()->SetDefaultCitizenType(atoi(szValue)); + } + else if (FStrEq(szKeyName, "GlobalFriendlyFire")) + { + HL2GameRules()->SetGlobalFriendlyFire(TO_THREESTATE(atoi(szValue))); + } + else if (FStrEq(szKeyName, "SetEpisodic") && !FStrEq(szValue, "2")) + { + hl2_episodic.SetValue(!FStrEq(szValue, "0")); + } + else if (FStrEq(szKeyName, "SetLegacyFlashlight")) + { + // Turn off flashlights first + for ( int i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBasePlayer *pPlayer = UTIL_PlayerByIndex( i ); + + if ( pPlayer ) + { + if (pPlayer->FlashlightIsOn()) + pPlayer->FlashlightTurnOff(); + } + } + + g_bUseLegacyFlashlight = !FStrEq(szValue, "0"); + + // We have overridden it, don't test directory + g_bCacheLegacyFlashlightStatus = false; + + // Tell our save/load we've modified it + // 1 = modified, 2 = legacy enabled + m_save_LegacyFlashlight |= 1; + if (g_bUseLegacyFlashlight) + m_save_LegacyFlashlight |= 2; + } + else if (FStrEq(szKeyName, "SetPlayerSquadAutosummon")) + { + HL2GameRules()->SetAutosummonDisabled(FStrEq(szValue, "0")); + } + else if (FStrEq(szKeyName, "SetStunstickPickupBehavior")) + { + HL2GameRules()->SetStunstickPickupBehavior(atoi(szValue)); + } + else if (FStrEq(szKeyName, "SetAllowSPRespawn")) + { + HL2GameRules()->SetAllowSPRespawn(!FStrEq(szValue, "0")); + } + else + { + return BaseClass::KeyValue( szKeyName, szValue ); + } + + return true; +} + +bool CHalfLife2Proxy::GetKeyValue( const char *szKeyName, char *szValue, int iMaxLen ) +{ + if (FStrEq(szKeyName, "DefaultCitizenType")) + { + Q_snprintf( szValue, iMaxLen, "%i", HL2GameRules()->GetDefaultCitizenType() ); + } + else if (FStrEq(szKeyName, "GlobalFriendlyFire")) + { + Q_snprintf( szValue, iMaxLen, "%i", HL2GameRules()->GlobalFriendlyFire() ); + } + else if (FStrEq(szKeyName, "SetEpisodic") && !FStrEq(szValue, "2")) + { + Q_snprintf( szValue, iMaxLen, "%s", hl2_episodic.GetString() ); + } + else if (FStrEq(szKeyName, "SetLegacyFlashlight")) + { + Q_snprintf( szValue, iMaxLen, "%d", g_bUseLegacyFlashlight ); + } + else if (FStrEq(szKeyName, "SetPlayerSquadAutosummon")) + { + Q_snprintf( szValue, iMaxLen, "%d", HL2GameRules()->AutosummonDisabled() ); + } + else if (FStrEq(szKeyName, "SetStunstickPickupBehavior")) + { + Q_snprintf( szValue, iMaxLen, "%i", HL2GameRules()->GetStunstickPickupBehavior() ); + } + else if (FStrEq(szKeyName, "SetAllowSPRespawn")) + { + Q_snprintf( szValue, iMaxLen, "%d", HL2GameRules()->AllowSPRespawn() ); + } + else + { + return BaseClass::GetKeyValue( szKeyName, szValue, iMaxLen ); + } + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Saves the current object out to disk, by iterating through the objects +// data description hierarchy +// Input : &save - save buffer which the class data is written to +// Output : int - 0 if the save failed, 1 on success +//----------------------------------------------------------------------------- +int CHalfLife2Proxy::Save( ISave &save ) +{ + m_save_DefaultCitizenType = HL2GameRules()->GetDefaultCitizenType(); + m_save_PlayerSquadAutosummonDisabled = HL2GameRules()->AutosummonDisabled(); + + // As a static variable, this is actually kept across save games, but lost when the game exits. + // NOTE: Now set in KeyValue() directly + //m_save_LegacyFlashlight = (g_bUseLegacyFlashlight); + + m_save_StunstickPickupBehavior = HL2GameRules()->GetStunstickPickupBehavior(); + + m_save_AllowSPRespawn = HL2GameRules()->AllowSPRespawn(); + + return BaseClass::Save(save); +} + +//----------------------------------------------------------------------------- +// Purpose: Restores the current object from disk, by iterating through the objects +// data description hierarchy +// Input : &restore - restore buffer which the class data is read from +// Output : int - 0 if the restore failed, 1 on success +//----------------------------------------------------------------------------- +int CHalfLife2Proxy::Restore( IRestore &restore ) +{ + int base = BaseClass::Restore(restore); + + HL2GameRules()->SetDefaultCitizenType(m_save_DefaultCitizenType); + HL2GameRules()->SetAutosummonDisabled(m_save_PlayerSquadAutosummonDisabled); + + // Are we modding the legacy flashlight? + if (m_save_LegacyFlashlight & 1) + { + g_bUseLegacyFlashlight = (m_save_LegacyFlashlight & 2) != 0; + + // If we've got the desired legacy flashlight state saved, don't bother caching. + g_bCacheLegacyFlashlightStatus = false; + } + + HL2GameRules()->SetStunstickPickupBehavior(m_save_StunstickPickupBehavior); + + HL2GameRules()->SetAllowSPRespawn(m_save_AllowSPRespawn); + + return base; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CHalfLife2Proxy::UpdateOnRemove() +{ + // Were we modding the legacy flashlight? + if (m_save_LegacyFlashlight & 1) + { + // Restore the default state. + g_bCacheLegacyFlashlightStatus = true; + } + + BaseClass::UpdateOnRemove(); +} +#endif + #ifdef CLIENT_DLL void RecvProxy_HL2GameRules( const RecvProp *pProp, void **pOut, void *pData, int objectID ) @@ -128,6 +335,10 @@ ConVar sk_plr_dmg_buckshot ( "sk_plr_dmg_buckshot","0", FCVAR_REPLICATED); ConVar sk_npc_dmg_buckshot ( "sk_npc_dmg_buckshot","0", FCVAR_REPLICATED); ConVar sk_max_buckshot ( "sk_max_buckshot","0", FCVAR_REPLICATED); ConVar sk_plr_num_shotgun_pellets( "sk_plr_num_shotgun_pellets","7", FCVAR_REPLICATED); +#ifdef MAPBASE +ConVar sk_plr_num_shotgun_pellets_double( "sk_plr_num_shotgun_pellets_double","12", FCVAR_REPLICATED); +ConVar sk_npc_num_shotgun_pellets( "sk_npc_num_shotgun_pellets","8", FCVAR_REPLICATED); +#endif ConVar sk_plr_dmg_rpg_round ( "sk_plr_dmg_rpg_round","0", FCVAR_REPLICATED); ConVar sk_npc_dmg_rpg_round ( "sk_npc_dmg_rpg_round","0", FCVAR_REPLICATED); @@ -206,7 +417,11 @@ bool CHalfLife2::Damage_IsTimeBased( int iDmgType ) // Damage types that are time-based. #ifdef HL2_EPISODIC // This makes me think EP2 should have its own rules, but they are #ifdef all over in here. +#ifdef MAPBASE + return ( ( iDmgType & ( DMG_PARALYZE | DMG_NERVEGAS | DMG_POISON | DMG_RADIATION | DMG_DROWNRECOVER | DMG_ACID | DMG_SLOWBURN ) ) != 0 ); +#else return ( ( iDmgType & ( DMG_PARALYZE | DMG_NERVEGAS | DMG_POISON | DMG_RADIATION | DMG_DROWNRECOVER | DMG_SLOWBURN ) ) != 0 ); +#endif #else return BaseClass::Damage_IsTimeBased( iDmgType ); #endif @@ -253,6 +468,12 @@ ConVar alyx_darkness_force( "alyx_darkness_force", "0", FCVAR_CHEAT | FCVAR_REP m_flLastHealthDropTime = 0.0f; m_flLastGrenadeDropTime = 0.0f; + +#ifdef MAPBASE + m_DefaultCitizenType = 0; + m_bPlayerSquadAutosummonDisabled = false; + m_bAllowSPRespawn = false; +#endif } //----------------------------------------------------------------------------- @@ -1291,6 +1512,10 @@ ConVar alyx_darkness_force( "alyx_darkness_force", "0", FCVAR_CHEAT | FCVAR_REP { case CLASS_NONE: return "CLASS_NONE"; case CLASS_PLAYER: return "CLASS_PLAYER"; +#ifdef MAPBASE + case CLASS_PLAYER_ALLY: return "CLASS_PLAYER_ALLY"; + case CLASS_PLAYER_ALLY_VITAL: return "CLASS_PLAYER_ALLY_VITAL"; +#endif case CLASS_ANTLION: return "CLASS_ANTLION"; case CLASS_BARNACLE: return "CLASS_BARNACLE"; case CLASS_BULLSEYE: return "CLASS_BULLSEYE"; @@ -1314,6 +1539,9 @@ ConVar alyx_darkness_force( "alyx_darkness_force", "0", FCVAR_CHEAT | FCVAR_REP case CLASS_MISSILE: return "CLASS_MISSILE"; case CLASS_FLARE: return "CLASS_FLARE"; case CLASS_EARTH_FAUNA: return "CLASS_EARTH_FAUNA"; +#ifdef MAPBASE + case CLASS_HACKED_ROLLERMINE: return "CLASS_HACKED_ROLLERMINE"; +#endif default: return "MISSING CLASS in ClassifyText()"; } @@ -1400,7 +1628,12 @@ ConVar alyx_darkness_force( "alyx_darkness_force", "0", FCVAR_CHEAT | FCVAR_REP // came from the player's physcannon. CBasePlayer *pPlayer = UTIL_PlayerByIndex(1); +#ifdef MAPBASE + // Friendly fire needs to be handled here. + if ( pPlayer && !pVictim->MyNPCPointer()->FriendlyFireEnabled() ) +#else if( pPlayer ) +#endif { CBaseEntity *pWeapon = pPlayer->HasNamedPlayerItem("weapon_physcannon"); @@ -1769,6 +2002,96 @@ bool CHalfLife2::ShouldBurningPropsEmitLight() } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Gets the default citizen type. +//----------------------------------------------------------------------------- +int CHalfLife2::GetDefaultCitizenType() +{ + return m_DefaultCitizenType; +} + +//----------------------------------------------------------------------------- +// Sets the default citizen type. +//----------------------------------------------------------------------------- +void CHalfLife2::SetDefaultCitizenType(int val) +{ + m_DefaultCitizenType = val; +} + +//----------------------------------------------------------------------------- +// Gets our global friendly fire override. +//----------------------------------------------------------------------------- +ThreeState_t CHalfLife2::GlobalFriendlyFire() +{ + return GlobalEntity_IsInTable(FRIENDLY_FIRE_GLOBALNAME) ? TO_THREESTATE(GlobalEntity_GetState(FRIENDLY_FIRE_GLOBALNAME)) : TRS_NONE; +} + +//----------------------------------------------------------------------------- +// Sets our global friendly fire override. +//----------------------------------------------------------------------------- +void CHalfLife2::SetGlobalFriendlyFire(ThreeState_t val) +{ + GlobalEntity_Add(MAKE_STRING(FRIENDLY_FIRE_GLOBALNAME), gpGlobals->mapname, (GLOBALESTATE)val); +} + +//----------------------------------------------------------------------------- +// Gets our autosummon setting. +//----------------------------------------------------------------------------- +bool CHalfLife2::AutosummonDisabled() +{ + return m_bPlayerSquadAutosummonDisabled; +} + +//----------------------------------------------------------------------------- +// Sets our autosummon setting. +//----------------------------------------------------------------------------- +void CHalfLife2::SetAutosummonDisabled(bool toggle) +{ + m_bPlayerSquadAutosummonDisabled = toggle; +} + +//----------------------------------------------------------------------------- +// Gets our stunstick pickup setting. +//----------------------------------------------------------------------------- +int CHalfLife2::GetStunstickPickupBehavior() +{ + return m_StunstickPickupBehavior; +} + +//----------------------------------------------------------------------------- +// Sets our stunstick pickup setting. +//----------------------------------------------------------------------------- +void CHalfLife2::SetStunstickPickupBehavior(int val) +{ + m_StunstickPickupBehavior = val; +} + +//----------------------------------------------------------------------------- +// Gets our SP respawn setting. +//----------------------------------------------------------------------------- +bool CHalfLife2::AllowSPRespawn() +{ + return m_bAllowSPRespawn; +} + +//----------------------------------------------------------------------------- +// Sets our SP respawn setting. +//----------------------------------------------------------------------------- +void CHalfLife2::SetAllowSPRespawn( bool toggle ) +{ + m_bAllowSPRespawn = toggle; +} + +//BEGIN_SIMPLE_DATADESC( CHalfLife2 ) +// +// DEFINE_FIELD( m_DefaultCitizenType, FIELD_INTEGER ), +// DEFINE_FIELD( m_bPlayerSquadAutosummonDisabled, FIELD_BOOLEAN ), +// +//END_DATADESC() +#endif + + #endif//CLIENT_DLL // ------------------------------------------------------------------------------------ // @@ -1868,9 +2191,20 @@ CAmmoDef *GetAmmoDef() def.AddAmmoType("Grenade", DMG_BURN, TRACER_NONE, "sk_plr_dmg_grenade", "sk_npc_dmg_grenade", "sk_max_grenade", 0, 0); #ifdef HL2_EPISODIC def.AddAmmoType("Hopwire", DMG_BLAST, TRACER_NONE, "sk_plr_dmg_grenade", "sk_npc_dmg_grenade", "sk_max_hopwire", 0, 0); +#ifdef MAPBASE + // + // Always gibbing would make it look better on antlions, etc. + // Hopefully this is very good and isn't bad. + // + def.AddAmmoType("CombineHeavyCannon", DMG_BULLET | DMG_ALWAYSGIB, TRACER_LINE, 40, 40, NULL, 10 * 750 * 12, AMMO_FORCE_DROP_IF_CARRIED ); // hit like a 10 kg weight at 750 ft/s +#else def.AddAmmoType("CombineHeavyCannon", DMG_BULLET, TRACER_LINE, 40, 40, NULL, 10 * 750 * 12, AMMO_FORCE_DROP_IF_CARRIED ); // hit like a 10 kg weight at 750 ft/s +#endif def.AddAmmoType("ammo_proto1", DMG_BULLET, TRACER_LINE, 0, 0, 10, 0, 0 ); #endif // HL2_EPISODIC +#ifdef MAPBASE + def.AddAmmoType("slam", DMG_BURN, TRACER_NONE, 0, 0, 5, 0, 0 ); +#endif } return &def; diff --git a/src/game/shared/hl2/hl2_gamerules.h b/src/game/shared/hl2/hl2_gamerules.h index 4f64793f..a8eace12 100644 --- a/src/game/shared/hl2/hl2_gamerules.h +++ b/src/game/shared/hl2/hl2_gamerules.h @@ -19,12 +19,46 @@ #define CHalfLife2Proxy C_HalfLife2Proxy #endif +#if MAPBASE && GAME_DLL +#define FRIENDLY_FIRE_GLOBALNAME "friendly_fire_override" +#endif + class CHalfLife2Proxy : public CGameRulesProxy { public: DECLARE_CLASS( CHalfLife2Proxy, CGameRulesProxy ); DECLARE_NETWORKCLASS(); + +#if defined(MAPBASE) && defined(GAME_DLL) + bool KeyValue( const char *szKeyName, const char *szValue ); + bool GetKeyValue( const char *szKeyName, char *szValue, int iMaxLen ); + + virtual int Save( ISave &save ); + virtual int Restore( IRestore &restore ); + virtual void UpdateOnRemove(); + + // Inputs + void InputEpisodicOn( inputdata_t &inputdata ); + void InputEpisodicOff( inputdata_t &inputdata ); + void InputSetFriendlyFire( inputdata_t &inputdata ); + void InputSetDefaultCitizenType( inputdata_t &inputdata ); + void InputSetLegacyFlashlight( inputdata_t &inputdata ); + void InputSetPlayerSquadAutosummon( inputdata_t &inputdata ); + void InputSetStunstickPickupBehavior( inputdata_t &inputdata ); + void InputSetAllowSPRespawn( inputdata_t &inputdata ); + + // Gamerules classes don't seem to support datadescs, so the hl2_gamerules entity takes the current values + // from the actual gamerules and saves them in the entity itself, where they're saved via the entity's own datadesc. + // When the save is loaded, the entity sets the main gamerules values according to what was saved. + int m_save_DefaultCitizenType; + char m_save_LegacyFlashlight; + bool m_save_PlayerSquadAutosummonDisabled; + int m_save_StunstickPickupBehavior; + bool m_save_AllowSPRespawn; + + DECLARE_DATADESC(); +#endif }; @@ -47,6 +81,10 @@ public: virtual void LevelInitPreEntity(); #endif +#ifdef MAPBASE_VSCRIPT + virtual void RegisterScriptFunctions( void ); +#endif + private: // Rules change for the mega physgun CNetworkVar( bool, m_bMegaPhysgun ); @@ -88,11 +126,35 @@ public: virtual bool IsAlyxInDarknessMode(); +#ifdef MAPBASE + int GetDefaultCitizenType(); + void SetDefaultCitizenType(int val); + + ThreeState_t GlobalFriendlyFire(); + void SetGlobalFriendlyFire(ThreeState_t val); + + bool AutosummonDisabled(); + void SetAutosummonDisabled(bool toggle); + + int GetStunstickPickupBehavior(); + void SetStunstickPickupBehavior(int val); + + virtual bool AllowSPRespawn(); + void SetAllowSPRespawn( bool toggle ); +#endif + private: float m_flLastHealthDropTime; float m_flLastGrenadeDropTime; +#ifdef MAPBASE + int m_DefaultCitizenType; + bool m_bPlayerSquadAutosummonDisabled; + int m_StunstickPickupBehavior; + bool m_bAllowSPRespawn; +#endif + void AdjustPlayerDamageTaken( CTakeDamageInfo *pInfo ); float AdjustPlayerDamageInflicted( float damage ); diff --git a/src/game/shared/hl2/hl2_usermessages.cpp b/src/game/shared/hl2/hl2_usermessages.cpp index 8b535e56..41f4a64a 100644 --- a/src/game/shared/hl2/hl2_usermessages.cpp +++ b/src/game/shared/hl2/hl2_usermessages.cpp @@ -41,8 +41,14 @@ void RegisterUserMessages( void ) usermessages->Register( "KeyHintText", -1 ); // Displays hint text display usermessages->Register( "SquadMemberDied", 0 ); usermessages->Register( "AmmoDenied", 2 ); +#ifdef MAPBASE + // This sends the credits file now + usermessages->Register( "CreditsMsg", -1 ); + usermessages->Register( "LogoTimeMsg", -1 ); +#else usermessages->Register( "CreditsMsg", 1 ); usermessages->Register( "LogoTimeMsg", 4 ); +#endif usermessages->Register( "AchievementEvent", -1 ); usermessages->Register( "UpdateJalopyRadar", -1 ); diff --git a/src/game/shared/hl2/hl_gamemovement.cpp b/src/game/shared/hl2/hl_gamemovement.cpp index e54f63d0..16762541 100644 --- a/src/game/shared/hl2/hl_gamemovement.cpp +++ b/src/game/shared/hl2/hl_gamemovement.cpp @@ -291,7 +291,11 @@ bool CHL2GameMovement::ContinueForcedMove() //----------------------------------------------------------------------------- bool CHL2GameMovement::OnLadder( trace_t &trace ) { +#ifdef HL2_USES_FUNC_LADDER_CODE + return ( GetLadder() != NULL ) ? true : BaseClass::OnLadder(trace); +#else return ( GetLadder() != NULL ) ? true : false; +#endif } //----------------------------------------------------------------------------- @@ -386,6 +390,104 @@ void CHL2GameMovement::Findladder( float maxdist, CFuncLadder **ppLadder, Vector } +#ifdef MAPBASE +bool CHL2GameMovement::ForcePlayerOntoLadder(CFuncLadder *ladder) +{ + Vector topPosition; + Vector bottomPosition; + + ladder->GetTopPosition(topPosition); + ladder->GetBottomPosition(bottomPosition); + + Vector closest; + CalcClosestPointOnLineSegment(mv->GetAbsOrigin(), bottomPosition, topPosition, closest, NULL); + + StartForcedMove( true, player->MaxSpeed(), closest, ladder ); + + return true; +} + +bool CHL2GameMovement::MountPlayerOntoLadder(CFuncLadder *ladder) +{ + Vector topPosition; + Vector bottomPosition; + + ladder->GetTopPosition(topPosition); + ladder->GetBottomPosition(bottomPosition); + + Vector closest; + CalcClosestPointOnLineSegment(mv->GetAbsOrigin(), bottomPosition, topPosition, closest, NULL); + +#if 0 + // Compute parametric distance along ladder vector... + float oldt; + CalcDistanceSqrToLine( mv->GetAbsOrigin(), topPosition, bottomPosition, &oldt ); + + // Perform the move accounting for any base velocity. + VectorAdd (mv->m_vecVelocity, player->GetBaseVelocity(), mv->m_vecVelocity); + TryPlayerMove(); + VectorSubtract (mv->m_vecVelocity, player->GetBaseVelocity(), mv->m_vecVelocity); + + // Pressed buttons are "changed(xor)" and'ed with the mask of currently held buttons + bool pressing_forward_or_side = mv->m_flForwardMove != 0.0f || mv->m_flSideMove != 0.0f; + + Vector ladderVec = topPosition - bottomPosition; + float LadderLength = VectorNormalize( ladderVec ); + // This test is not perfect by any means, but should help a bit + bool moving_along_ladder = false; + if ( pressing_forward_or_side ) + { + float fwdDot = m_vecForward.Dot( ladderVec ); + if ( fabs( fwdDot ) > 0.9f ) + { + moving_along_ladder = true; + } + } + + // Compute parametric distance along ladder vector... + float newt; + CalcDistanceSqrToLine( mv->GetAbsOrigin(), topPosition, bottomPosition, &newt ); + + // Fudge of 2 units + float tolerance = 1.0f / LadderLength; + + bool wouldleaveladder = false; + // Moving pPast top or bottom? + if ( newt < -tolerance ) + { + wouldleaveladder = newt < oldt; + } + else if ( newt > ( 1.0f + tolerance ) ) + { + wouldleaveladder = newt > oldt; + } + + // See if we are near the top or bottom but not moving + float dist1sqr, dist2sqr; + + dist1sqr = ( topPosition - mv->GetAbsOrigin() ).LengthSqr(); + dist2sqr = ( bottomPosition - mv->GetAbsOrigin() ).LengthSqr(); + + float dist = MIN( dist1sqr, dist2sqr ); + bool neardismountnode = ( dist < 16.0f * 16.0f ) ? true : false; + float ladderUnitsPerTick = ( MAX_CLIMB_SPEED * gpGlobals->interval_per_tick ); + bool neardismountnode2 = ( dist < ladderUnitsPerTick * ladderUnitsPerTick ) ? true : false; + + // Really close to node, cvar is set, and pressing a key, then simulate a +USE + bool auto_dismount_use = ( neardismountnode2 && + sv_autoladderdismount.GetBool() && + pressing_forward_or_side && + !moving_along_ladder ); + + bool fully_underwater = ( player->GetWaterLevel() == WL_Eyes ) ? true : false; +#endif + + StartForcedMove( true, player->MaxSpeed(), closest, ladder ); + + return true; +} +#endif + static bool NearbyDismountLessFunc( const NearbyDismount_t& lhs, const NearbyDismount_t& rhs ) { return lhs.distSqr < rhs.distSqr; @@ -569,6 +671,9 @@ void CHL2GameMovement::FullLadderMove() Assert( ladder ); if ( !ladder ) { +#ifdef HL2_USES_FUNC_LADDER_CODE + BaseClass::FullLadderMove(); +#endif return; } @@ -938,7 +1043,11 @@ bool CHL2GameMovement::LadderMove( void ) if ( player->GetMoveType() == MOVETYPE_NOCLIP ) { SetLadder( NULL ); +#ifdef HL2_USES_FUNC_LADDER_CODE + return BaseClass::LadderMove(); +#else return false; +#endif } // If being forced to mount/dismount continue to act like we are on the ladder @@ -1005,7 +1114,11 @@ bool CHL2GameMovement::LadderMove( void ) } } +#ifdef HL2_USES_FUNC_LADDER_CODE + return BaseClass::LadderMove(); +#else return false; +#endif } if ( !ladder && @@ -1019,7 +1132,11 @@ bool CHL2GameMovement::LadderMove( void ) ladder = GetLadder(); if ( !ladder ) { +#ifdef HL2_USES_FUNC_LADDER_CODE + return BaseClass::LadderMove(); +#else return false; +#endif } // Don't play the deny sound @@ -1083,7 +1200,11 @@ bool CHL2GameMovement::LadderMove( void ) { mv->m_vecVelocity.z = mv->m_vecVelocity.z + 50; } +#ifdef HL2_USES_FUNC_LADDER_CODE + return BaseClass::LadderMove(); +#else return false; +#endif } if ( forwardSpeed != 0 || rightSpeed != 0 ) @@ -1115,7 +1236,11 @@ bool CHL2GameMovement::LadderMove( void ) player->SetMoveType( MOVETYPE_WALK ); // Remove from ladder SetLadder( NULL ); +#ifdef HL2_USES_FUNC_LADDER_CODE + return BaseClass::LadderMove(); +#else return false; +#endif } bool ishorizontal = fabs( topPosition.z - bottomPosition.z ) < 64.0f ? true : false; @@ -1193,6 +1318,29 @@ bool CHL2GameMovement::CanAccelerate() return true; } +//----------------------------------------------------------------------------- +// Purpose: Allow bots etc to use slightly different solid masks +//----------------------------------------------------------------------------- +unsigned int CHL2GameMovement::PlayerSolidMask( bool brushOnly ) +{ + int mask = 0; +#ifdef HL2MP + if ( HL2MPRules()->IsTeamplay() ) + { + switch ( player->GetTeamNumber() ) + { + case TEAM_REBELS: + mask = CONTENTS_TEAM1; + break; + + case TEAM_COMBINE: + mask = CONTENTS_TEAM2; + break; + } + } +#endif + return ( mask | BaseClass::PlayerSolidMask( brushOnly ) ); +} #ifndef PORTAL // Portal inherits from this but needs to declare it's own global interface // Expose our interface. diff --git a/src/game/shared/hl2/hl_gamemovement.h b/src/game/shared/hl2/hl_gamemovement.h index fe50736d..4af11de5 100644 --- a/src/game/shared/hl2/hl_gamemovement.h +++ b/src/game/shared/hl2/hl_gamemovement.h @@ -49,6 +49,15 @@ public: virtual float MaxSpeed(); + virtual unsigned int PlayerSolidMask( bool brushOnly = false ); + +#ifdef MAPBASE + // Called by mappers who need a player to be on a ladder. + bool ForcePlayerOntoLadder(CFuncLadder *ladder); + // Called by mappers who want a player to be on a ladder. + bool MountPlayerOntoLadder(CFuncLadder *ladder); +#endif + private: // See if we are pressing use near a ladder "mount" point and if so, latch us onto the ladder diff --git a/src/game/shared/hl2mp/weapon_ar2.cpp b/src/game/shared/hl2mp/weapon_ar2.cpp index eb63086d..cb04e4e1 100644 --- a/src/game/shared/hl2mp/weapon_ar2.cpp +++ b/src/game/shared/hl2mp/weapon_ar2.cpp @@ -223,7 +223,7 @@ void CWeaponAR2::DelayedAttack( void ) // pOwner->SnapEyeAngles( angles ); - pOwner->ViewPunch( QAngle( SharedRandomInt( "ar2pax", -8, -12 ), SharedRandomInt( "ar2pay", 1, 2 ), 0 ) ); + pOwner->ViewPunch( QAngle( SharedRandomInt( "ar2pax", -12, -8 ), SharedRandomInt( "ar2pay", 1, 2 ), 0 ) ); // Decrease ammo pOwner->RemoveAmmo( 1, m_iSecondaryAmmoType ); diff --git a/src/game/shared/hl2mp/weapon_slam.cpp b/src/game/shared/hl2mp/weapon_slam.cpp index bc1cb8b4..2499b51a 100644 --- a/src/game/shared/hl2mp/weapon_slam.cpp +++ b/src/game/shared/hl2mp/weapon_slam.cpp @@ -11,9 +11,17 @@ #include "engine/IEngineSound.h" #if defined( CLIENT_DLL ) +#ifdef HL2MP #include "c_hl2mp_player.h" #else + #include "hl2_player_shared.h" +#endif +#else +#ifdef HL2MP #include "hl2mp_player.h" +#else + #include "hl2_player.h" +#endif #include "grenade_tripmine.h" #include "grenade_satchel.h" #include "entitylist.h" @@ -25,6 +33,11 @@ // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" +#ifndef HL2MP +#define ToHL2MPPlayer(ent) dynamic_cast(ent) +#define CHL2MP_Player CHL2_Player +#endif + #define SLAM_PRIMARY_VOLUME 450 IMPLEMENT_NETWORKCLASS_ALIASED( Weapon_SLAM, DT_Weapon_SLAM ) @@ -102,6 +115,10 @@ acttable_t CWeapon_SLAM::m_acttable[] = { ACT_HL2MP_GESTURE_RANGE_ATTACK, ACT_HL2MP_GESTURE_RANGE_ATTACK_SLAM, false }, { ACT_HL2MP_GESTURE_RELOAD, ACT_HL2MP_GESTURE_RELOAD_SLAM, false }, { ACT_HL2MP_JUMP, ACT_HL2MP_JUMP_SLAM, false }, +#if EXPANDED_HL2DM_ACTIVITIES + { ACT_HL2MP_WALK, ACT_HL2MP_WALK_SLAM, false }, + { ACT_HL2MP_GESTURE_RANGE_ATTACK2, ACT_HL2MP_GESTURE_RANGE_ATTACK2_SLAM, false }, +#endif }; IMPLEMENT_ACTTABLE(CWeapon_SLAM); @@ -114,7 +131,9 @@ void CWeapon_SLAM::Spawn( ) Precache( ); +#if defined(HL2MP) || !defined(CLIENT_DLL) FallInit();// get ready to fall down +#endif m_tSlamState = (int)SLAM_SATCHEL_THROW; m_flWallSwitchTime = 0; @@ -775,6 +794,14 @@ void CWeapon_SLAM::ItemPostFrame( void ) SLAMThink(); +#ifdef MAPBASE + if (pOwner->HasSpawnFlags( SF_PLAYER_SUPPRESS_FIRING )) + { + WeaponIdle(); + return; + } +#endif + if ((pOwner->m_nButtons & IN_ATTACK2) && (m_flNextSecondaryAttack <= gpGlobals->curtime)) { SecondaryAttack(); diff --git a/src/game/shared/hl2mp/weapon_slam.h b/src/game/shared/hl2mp/weapon_slam.h index 11310f6b..bf728612 100644 --- a/src/game/shared/hl2mp/weapon_slam.h +++ b/src/game/shared/hl2mp/weapon_slam.h @@ -15,7 +15,11 @@ #define WEAPONSLAM_H #include "basegrenade_shared.h" +#ifdef HL2MP #include "weapon_hl2mpbasehlmpcombatweapon.h" +#else +#include "basehlcombatweapon_shared.h" +#endif enum { @@ -28,6 +32,10 @@ enum #define CWeapon_SLAM C_Weapon_SLAM #endif +#ifndef HL2MP +#define CBaseHL2MPCombatWeapon CBaseHLCombatWeapon +#endif + class CWeapon_SLAM : public CBaseHL2MPCombatWeapon { public: diff --git a/src/game/shared/hl2mp/weapon_stunstick.cpp b/src/game/shared/hl2mp/weapon_stunstick.cpp index 0d813be2..7f63c3ed 100644 --- a/src/game/shared/hl2mp/weapon_stunstick.cpp +++ b/src/game/shared/hl2mp/weapon_stunstick.cpp @@ -7,7 +7,10 @@ #include "cbase.h" #include "npcevent.h" +#include "weapon_stunstick.h" +#ifdef HL2MP #include "weapon_hl2mpbasebasebludgeon.h" +#endif #include "IEffects.h" #include "debugoverlay_shared.h" @@ -26,7 +29,7 @@ #include "fx_quad.h" #include "fx.h" - extern void DrawHalo( IMaterial* pMaterial, const Vector &source, float scale, float const *color, float flHDRColorScale ); + extern void DrawHalo( IMaterial* pMaterial, const Vector &source, float scale, float const *color, float flHDRColorScale = 1.0f ); extern void FormatViewModelAttachment( Vector &vOrigin, bool bInverse ); #endif @@ -36,100 +39,12 @@ extern ConVar metropolice_move_and_melee; -#define STUNSTICK_RANGE 75.0f -#define STUNSTICK_REFIRE 0.8f -#define STUNSTICK_BEAM_MATERIAL "sprites/lgtning.vmt" -#define STUNSTICK_GLOW_MATERIAL "sprites/light_glow02_add" -#define STUNSTICK_GLOW_MATERIAL2 "effects/blueflare1" -#define STUNSTICK_GLOW_MATERIAL_NOZ "sprites/light_glow02_add_noz" - -#ifdef CLIENT_DLL -#define CWeaponStunStick C_WeaponStunStick -#endif - -class CWeaponStunStick : public CBaseHL2MPBludgeonWeapon -{ - DECLARE_CLASS( CWeaponStunStick, CBaseHL2MPBludgeonWeapon ); - -public: - - CWeaponStunStick(); - - DECLARE_NETWORKCLASS(); - DECLARE_PREDICTABLE(); - -#ifndef CLIENT_DLL - DECLARE_ACTTABLE(); -#endif - -#ifdef CLIENT_DLL - virtual int DrawModel( int flags ); - virtual void ClientThink( void ); - virtual void OnDataChanged( DataUpdateType_t updateType ); - virtual RenderGroup_t GetRenderGroup( void ); - virtual void ViewModelDrawn( C_BaseViewModel *pBaseViewModel ); - -#endif - - virtual void Precache(); - - void Spawn(); - - float GetRange( void ) { return STUNSTICK_RANGE; } - float GetFireRate( void ) { return STUNSTICK_REFIRE; } - - - bool Deploy( void ); - bool Holster( CBaseCombatWeapon *pSwitchingTo = NULL ); - - void Drop( const Vector &vecVelocity ); - void ImpactEffect( trace_t &traceHit ); - void SecondaryAttack( void ) {} - void SetStunState( bool state ); - bool GetStunState( void ); - -#ifndef CLIENT_DLL - void Operator_HandleAnimEvent( animevent_t *pEvent, CBaseCombatCharacter *pOperator ); - int WeaponMeleeAttack1Condition( float flDot, float flDist ); -#endif - - float GetDamageForActivity( Activity hitActivity ); - - virtual bool PlayFleshyHittySoundOnHit() const { return true; } - - CWeaponStunStick( const CWeaponStunStick & ); - -private: - -#ifdef CLIENT_DLL - - #define NUM_BEAM_ATTACHMENTS 9 - - struct stunstickBeamInfo_t - { - int IDs[2]; // 0 - top, 1 - bottom - }; - - stunstickBeamInfo_t m_BeamAttachments[NUM_BEAM_ATTACHMENTS]; // Lookup for arc attachment points on the head of the stick - int m_BeamCenterAttachment; // "Core" of the effect (center of the head) - - void SetupAttachmentPoints( void ); - void DrawFirstPersonEffects( void ); - void DrawThirdPersonEffects( void ); - void DrawEffects( void ); - bool InSwing( void ); - - bool m_bSwungLastFrame; - - #define FADE_DURATION 0.25f - - float m_flFadeTime; +#ifdef MAPBASE +ConVar sk_plr_dmg_stunstick ( "sk_plr_dmg_stunstick","0"); +ConVar sk_npc_dmg_stunstick ( "sk_npc_dmg_stunstick","0"); #endif - CNetworkVar( bool, m_bActive ); -}; - //----------------------------------------------------------------------------- // CWeaponStunStick //----------------------------------------------------------------------------- @@ -155,6 +70,7 @@ PRECACHE_WEAPON_REGISTER( weapon_stunstick ); acttable_t CWeaponStunStick::m_acttable[] = { +#ifdef HL2MP { ACT_RANGE_ATTACK1, ACT_RANGE_ATTACK_SLAM, true }, { ACT_HL2MP_IDLE, ACT_HL2MP_IDLE_MELEE, false }, { ACT_HL2MP_RUN, ACT_HL2MP_RUN_MELEE, false }, @@ -163,6 +79,30 @@ acttable_t CWeaponStunStick::m_acttable[] = { ACT_HL2MP_GESTURE_RANGE_ATTACK, ACT_HL2MP_GESTURE_RANGE_ATTACK_MELEE, false }, { ACT_HL2MP_GESTURE_RELOAD, ACT_HL2MP_GESTURE_RELOAD_MELEE, false }, { ACT_HL2MP_JUMP, ACT_HL2MP_JUMP_MELEE, false }, +#endif + { ACT_MELEE_ATTACK1, ACT_MELEE_ATTACK_SWING, true }, + { ACT_IDLE_ANGRY, ACT_IDLE_ANGRY_MELEE, true }, +#if EXPANDED_HL2_WEAPON_ACTIVITIES + { ACT_IDLE, ACT_IDLE_MELEE, false }, + { ACT_RUN, ACT_RUN_MELEE, false }, + { ACT_WALK, ACT_WALK_MELEE, false }, +#endif + +#ifdef MAPBASE + // HL2:DM activities (for third-person animations in SP) + { ACT_RANGE_ATTACK1, ACT_RANGE_ATTACK_SLAM, true }, + { ACT_HL2MP_IDLE, ACT_HL2MP_IDLE_MELEE, false }, + { ACT_HL2MP_RUN, ACT_HL2MP_RUN_MELEE, false }, + { ACT_HL2MP_IDLE_CROUCH, ACT_HL2MP_IDLE_CROUCH_MELEE, false }, + { ACT_HL2MP_WALK_CROUCH, ACT_HL2MP_WALK_CROUCH_MELEE, false }, + { ACT_HL2MP_GESTURE_RANGE_ATTACK, ACT_HL2MP_GESTURE_RANGE_ATTACK_MELEE, false }, + { ACT_HL2MP_GESTURE_RELOAD, ACT_HL2MP_GESTURE_RELOAD_MELEE, false }, + { ACT_HL2MP_JUMP, ACT_HL2MP_JUMP_MELEE, false }, +#if EXPANDED_HL2DM_ACTIVITIES + { ACT_HL2MP_GESTURE_RANGE_ATTACK2, ACT_HL2MP_GESTURE_RANGE_ATTACK2_MELEE, false }, + { ACT_HL2MP_WALK, ACT_HL2MP_WALK_MELEE, false }, +#endif +#endif }; IMPLEMENT_ACTTABLE(CWeaponStunStick); @@ -215,7 +155,14 @@ void CWeaponStunStick::Precache() //----------------------------------------------------------------------------- float CWeaponStunStick::GetDamageForActivity( Activity hitActivity ) { +#ifdef MAPBASE + if ( ( GetOwner() != NULL ) && ( GetOwner()->IsPlayer() ) ) + return sk_plr_dmg_stunstick.GetFloat(); + + return sk_npc_dmg_stunstick.GetFloat(); +#else return 40.0f; +#endif } //----------------------------------------------------------------------------- @@ -360,6 +307,35 @@ void CWeaponStunStick::Operator_HandleAnimEvent( animevent_t *pEvent, CBaseComba CBasePlayer *pPlayer = ToBasePlayer( pHurt ); bool bFlashed = false; + +#ifdef MAPBASE + CNPC_MetroPolice *pCop = dynamic_cast(pOperator); + + if ( pCop != NULL && pPlayer != NULL ) + { + // See if we need to knock out this target + if ( pCop->ShouldKnockOutTarget( pHurt ) ) + { + float yawKick = random->RandomFloat( -48, -24 ); + + //Kick the player angles + pPlayer->ViewPunch( QAngle( -16, yawKick, 2 ) ); + + color32 white = {255,255,255,255}; + UTIL_ScreenFade( pPlayer, white, 0.2f, 1.0f, FFADE_OUT|FFADE_PURGE|FFADE_STAYOUT ); + bFlashed = true; + + pCop->KnockOutTarget( pHurt ); + + break; + } + else + { + // Notify that we've stunned a target + pCop->StunnedTarget( pHurt ); + } + } +#endif // Punch angles if ( pPlayer != NULL && !(pPlayer->GetFlags() & FL_GODMODE) ) @@ -476,8 +452,19 @@ void CWeaponStunStick::Drop( const Vector &vecVelocity ) SetStunState( false ); #ifndef CLIENT_DLL +#ifdef MAPBASE +#ifdef HL2MP + if (!GetOwner() || GetOwner()->IsNPC()) + BaseClass::Drop(vecVelocity); + else + UTIL_Remove( this ); +#else + BaseClass::Drop(vecVelocity); +#endif +#else UTIL_Remove( this ); #endif +#endif } @@ -568,7 +555,11 @@ int C_WeaponStunStick::DrawModel( int flags ) return 0; // Only render these on the transparent pass +#ifdef MAPBASE + if ( m_bActive && flags & STUDIO_TRANSPARENCY ) +#else if ( flags & STUDIO_TRANSPARENCY ) +#endif { DrawEffects(); return 1; @@ -844,6 +835,40 @@ void C_WeaponStunStick::DrawFirstPersonEffects( void ) } } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: Draw our special effects +//----------------------------------------------------------------------------- +void C_WeaponStunStick::DrawNPCEffects( void ) +{ + if ( m_bActive ) + { + Vector vecOrigin; + QAngle vecAngles; + float color[3]; + + color[0] = color[1] = color[2] = random->RandomFloat( 0.1f, 0.2f ); + + GetAttachment( 1, vecOrigin, vecAngles ); + + Vector vForward; + AngleVectors( vecAngles, &vForward ); + + Vector vEnd = vecOrigin - vForward * 1.0f; + + IMaterial *pMaterial = materials->FindMaterial( "effects/stunstick", NULL, false ); + + CMatRenderContextPtr pRenderContext( materials ); + pRenderContext->Bind( pMaterial ); + DrawHalo( pMaterial, vEnd, random->RandomFloat( 4.0f, 6.0f ), color ); + + color[0] = color[1] = color[2] = random->RandomFloat( 0.9f, 1.0f ); + + DrawHalo( pMaterial, vEnd, random->RandomFloat( 2.0f, 3.0f ), color ); + } +} +#endif + //----------------------------------------------------------------------------- // Purpose: Draw our special effects //----------------------------------------------------------------------------- @@ -853,6 +878,13 @@ void C_WeaponStunStick::DrawEffects( void ) { DrawFirstPersonEffects(); } +#ifdef MAPBASE + else if ( GetOwner() && GetOwner()->IsNPC() ) + { + // Original HL2 stunstick FX + DrawNPCEffects(); + } +#endif else { DrawThirdPersonEffects(); diff --git a/src/game/shared/hl2mp/weapon_stunstick.h b/src/game/shared/hl2mp/weapon_stunstick.h new file mode 100644 index 00000000..b0553783 --- /dev/null +++ b/src/game/shared/hl2mp/weapon_stunstick.h @@ -0,0 +1,130 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: This is technically a Mapbase addition, but it's just weapon_stunstick's class declaration. +// All actual changes are still nested in #ifdef MAPBASE. +// +//=============================================================================// + +#ifndef WEAPON_STUNSTICK_H +#define WEAPON_STUNSTICK_H +#ifdef _WIN32 +#pragma once +#endif + +#ifdef CLIENT_DLL +#include "c_basehlcombatweapon.h" +#else +#include "basebludgeonweapon.h" +#endif + +#define STUNSTICK_RANGE 75.0f +#define STUNSTICK_REFIRE 0.8f +#define STUNSTICK_BEAM_MATERIAL "sprites/lgtning.vmt" +#define STUNSTICK_GLOW_MATERIAL "sprites/light_glow02_add" +#define STUNSTICK_GLOW_MATERIAL2 "effects/blueflare1" +#define STUNSTICK_GLOW_MATERIAL_NOZ "sprites/light_glow02_add_noz" + +#ifdef CLIENT_DLL +#define CWeaponStunStick C_WeaponStunStick +#define CBaseHLBludgeonWeapon C_BaseHLBludgeonWeapon +#endif + +#ifndef HL2MP +class CWeaponStunStick : public CBaseHLBludgeonWeapon +{ + DECLARE_CLASS( CWeaponStunStick, CBaseHLBludgeonWeapon ); +#else +class CWeaponStunStick : public CBaseHL2MPBludgeonWeapon +{ + DECLARE_CLASS( CWeaponStunStick, CBaseHL2MPBludgeonWeapon ); +#endif + +public: + + CWeaponStunStick(); + + DECLARE_NETWORKCLASS(); + DECLARE_PREDICTABLE(); + +#ifndef CLIENT_DLL + DECLARE_ACTTABLE(); +#endif + +#ifdef CLIENT_DLL + virtual int DrawModel( int flags ); + virtual void ClientThink( void ); + virtual void OnDataChanged( DataUpdateType_t updateType ); + virtual RenderGroup_t GetRenderGroup( void ); + virtual void ViewModelDrawn( C_BaseViewModel *pBaseViewModel ); + +#endif + + virtual void Precache(); + + void Spawn(); + + float GetRange( void ) { return STUNSTICK_RANGE; } + float GetFireRate( void ) { return STUNSTICK_REFIRE; } + + + bool Deploy( void ); + bool Holster( CBaseCombatWeapon *pSwitchingTo = NULL ); + + void Drop( const Vector &vecVelocity ); + void ImpactEffect( trace_t &traceHit ); + void SecondaryAttack( void ) {} + void SetStunState( bool state ); + bool GetStunState( void ); + +#ifndef CLIENT_DLL + void Operator_HandleAnimEvent( animevent_t *pEvent, CBaseCombatCharacter *pOperator ); + int WeaponMeleeAttack1Condition( float flDot, float flDist ); +#endif + + float GetDamageForActivity( Activity hitActivity ); + + virtual bool PlayFleshyHittySoundOnHit() const { return true; } + +#ifdef MAPBASE + // Don't use backup activities + acttable_t *GetBackupActivityList() { return NULL; } + int GetBackupActivityListCount() { return 0; } +#endif + + CWeaponStunStick( const CWeaponStunStick & ); + +private: + +#ifdef CLIENT_DLL + + #define NUM_BEAM_ATTACHMENTS 9 + + struct stunstickBeamInfo_t + { + int IDs[2]; // 0 - top, 1 - bottom + }; + + stunstickBeamInfo_t m_BeamAttachments[NUM_BEAM_ATTACHMENTS]; // Lookup for arc attachment points on the head of the stick + int m_BeamCenterAttachment; // "Core" of the effect (center of the head) + + void SetupAttachmentPoints( void ); + void DrawFirstPersonEffects( void ); + void DrawThirdPersonEffects( void ); +#ifdef MAPBASE + void DrawNPCEffects( void ); +#endif + void DrawEffects( void ); + bool InSwing( void ); + + bool m_bSwungLastFrame; + + #define FADE_DURATION 0.25f + + float m_flFadeTime; + +#endif + + CNetworkVar( bool, m_bActive ); +}; + +#endif // WEAPON_STUNSTICK_H diff --git a/src/game/shared/igamesystem.cpp b/src/game/shared/igamesystem.cpp index 5749b980..d5eddcdb 100644 --- a/src/game/shared/igamesystem.cpp +++ b/src/game/shared/igamesystem.cpp @@ -345,6 +345,15 @@ void IGameSystem::PreClientUpdateAllSystems() #endif +#ifdef MAPBASE_VSCRIPT + +void IGameSystem::RegisterVScriptAllSystems() +{ + InvokeMethod( &IGameSystem::RegisterVScript ); +} + +#endif + //----------------------------------------------------------------------------- // Invokes a method on all installed game systems in proper order diff --git a/src/game/shared/igamesystem.h b/src/game/shared/igamesystem.h index 6dc98350..85621924 100644 --- a/src/game/shared/igamesystem.h +++ b/src/game/shared/igamesystem.h @@ -98,6 +98,13 @@ public: static CBasePlayer *RunCommandPlayer(); static CUserCmd *RunCommandUserCmd(); #endif + +#ifdef MAPBASE_VSCRIPT + // This should be abstract, but there's a lot of systems which derive from + // this interface that would need to have this declared + virtual void RegisterVScript() { ; } + static void RegisterVScriptAllSystems(); +#endif }; class IGameSystemPerFrame : public IGameSystem diff --git a/src/game/shared/in_buttons.h b/src/game/shared/in_buttons.h index 32486853..74e2ca5f 100644 --- a/src/game/shared/in_buttons.h +++ b/src/game/shared/in_buttons.h @@ -11,6 +11,11 @@ #pragma once #endif +#ifdef MAPBASE +// That one article on the VDC. +//#define VGUI_SCREEN_FIX 1 +#endif + #define IN_ATTACK (1 << 0) #define IN_JUMP (1 << 1) #define IN_DUCK (1 << 2) @@ -38,4 +43,12 @@ #define IN_GRENADE2 (1 << 24) // grenade 2 #define IN_ATTACK3 (1 << 25) +#ifdef MAPBASE +#define IN_VGUIMODE (1 << 26) +#endif // MAPBASE + +#ifdef VGUI_SCREEN_FIX +#define IN_VALIDVGUIINPUT (1 << 23) //bitflag for vgui fix +#endif + #endif // IN_BUTTONS_H diff --git a/src/game/shared/mapbase/MapEdit.cpp b/src/game/shared/mapbase/MapEdit.cpp new file mode 100644 index 00000000..0891c29e --- /dev/null +++ b/src/game/shared/mapbase/MapEdit.cpp @@ -0,0 +1,893 @@ +//========= Mapbase - https://github.com/mapbase-source/source-sdk-2013 ============// +// +// Purpose: The flimsy MapEdit system that was +// heavily inspired by Synergy's MapEdit, completely based on the Commentary System +// and originally used for Lambda Fortress. +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" + +#include "MapEdit.h" +#include "filesystem.h" + +#ifndef CLIENT_DLL +#include +#include "utldict.h" +#include "isaverestore.h" +#include "eventqueue.h" +#include "saverestore_utlvector.h" +#include "ai_basenpc.h" +#include "triggers.h" +#include "mapbase/SystemConvarMod.h" +#endif + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +#ifndef CLIENT_DLL + +#define MAPEDIT_SPAWNED_SEMAPHORE "mapedit_semaphore" +#define MAPEDIT_DEFAULT_FILE UTIL_VarArgs("maps/%s_auto.txt", STRING(gpGlobals->mapname)) + +ConVar mapedit_enabled("mapedit_enabled", "1", FCVAR_ARCHIVE, "Is automatic MapEdit enabled?"); +ConVar mapedit_stack("mapedit_stack", "1", FCVAR_ARCHIVE, "If multiple MapEdit scripts are loaded, should they stack or replace each other?"); +ConVar mapedit_debug("mapedit_debug", "0", FCVAR_NONE, "Should MapEdit give debug messages?"); + +inline void DebugMsg(const tchar *pMsg, ...) +{ + if (mapedit_debug.GetBool() == true) + { + Msg("%s", pMsg); + } +} + +//bool g_bMapEditAvailable; +bool g_bMapEditLoaded = false; +bool IsMapEditLoaded( void ) +{ + return g_bMapEditLoaded; +} + +bool IsAutoMapEditAllowed(void) +{ + return mapedit_enabled.GetBool(); +} + +void CV_GlobalChange_MapEdit( IConVar *var, const char *pOldString, float flOldValue ); + +//----------------------------------------------------------------------------- +// Purpose: Game system for MapEdit stuff +//----------------------------------------------------------------------------- +class CMapEdit : public CAutoGameSystemPerFrame +{ +public: + DECLARE_DATADESC(); + + virtual void LevelInitPreEntity() + { + m_bMapEditConvarsChanging = false; + CalculateAvailableState(); + } + + void CalculateAvailableState( void ) + { + // Set the available cvar if we can find commentary data for this level + char szFullName[512]; + Q_snprintf(szFullName,sizeof(szFullName), "maps/%s_auto.txt", STRING( gpGlobals->mapname) ); + if ( filesystem->FileExists( szFullName ) ) + { + bool bAllowed = IsAutoMapEditAllowed(); + g_bMapEditLoaded = bAllowed; + //if (bAllowed) + // gEntList.AddListenerEntity( this ); + } + else + { + g_bMapEditLoaded = false; + //gEntList.RemoveListenerEntity( this ); + } + } + + virtual void LevelShutdownPreEntity() + { + ShutDownMapEdit(); + } + + void ParseEntKVBlock( CBaseEntity *pNode, KeyValues *pkvNode ) + { + KeyValues *pkvNodeData = pkvNode->GetFirstSubKey(); + while ( pkvNodeData ) + { + // Handle the connections block + if ( !Q_strcmp(pkvNodeData->GetName(), "connections") ) + { + ParseEntKVBlock( pNode, pkvNodeData ); + } + else + { + #define MAPEDIT_STRING_LENGTH_MAX 1024 + + const char *pszValue = pkvNodeData->GetString(); + Assert( Q_strlen(pszValue) < MAPEDIT_STRING_LENGTH_MAX ); + if ( Q_strnchr(pszValue, '^', MAPEDIT_STRING_LENGTH_MAX) ) + { + // We want to support quotes in our strings so that we can specify multiple parameters in + // an output inside our commentary files. We convert ^s to "s here. + char szTmp[MAPEDIT_STRING_LENGTH_MAX]; + Q_strncpy( szTmp, pszValue, MAPEDIT_STRING_LENGTH_MAX ); + int len = Q_strlen( szTmp ); + for ( int i = 0; i < len; i++ ) + { + if ( szTmp[i] == '^' ) + { + szTmp[i] = '"'; + } + } + + pszValue = szTmp; + } + + char cOperatorChar = pszValue[0]; + if (cOperatorChar == '+') + { + pszValue++; + char szExistingValue[MAPEDIT_STRING_LENGTH_MAX]; + if (pNode->GetKeyValue(pkvNodeData->GetName(), szExistingValue, sizeof(szExistingValue) )) + { + // Right now, this only supports adding floats/integers. + // Add Vector support later. + float flResult = atof(szExistingValue) + atof(pszValue); + pszValue = UTIL_VarArgs("%f", flResult); + } + } + else if (cOperatorChar == '-') + { + pszValue++; + char szExistingValue[MAPEDIT_STRING_LENGTH_MAX]; + if (pNode->GetKeyValue(pkvNodeData->GetName(), szExistingValue, sizeof(szExistingValue) )) + { + // Right now, this only supports subtracting floats/integers. + // Add Vector support later. + float flResult = atof(szExistingValue) - atof(pszValue); + pszValue = UTIL_VarArgs("%f", flResult); + } + } + else if (cOperatorChar == '|' /*&& pszValue[1] == '='*/) + { + pszValue++; + char szExistingValue[MAPEDIT_STRING_LENGTH_MAX]; + if (pNode->GetKeyValue(pkvNodeData->GetName(), szExistingValue, sizeof(szExistingValue))) + { + int iResult = atoi(szExistingValue) | atoi(pszValue); + pszValue = UTIL_VarArgs("%i", iResult); + } + } + + pNode->KeyValue(pkvNodeData->GetName(), pszValue); + } + + pkvNodeData = pkvNodeData->GetNextKey(); + } + } + + virtual void LevelInitPostEntity( void ) + { + if ( !IsMapEditLoaded() ) + { + return; + } + + if ( gpGlobals->eLoadType == MapLoad_LoadGame || gpGlobals->eLoadType == MapLoad_Background ) + { + return; + } + + m_bMapEditLoadedMidGame = false; + InitMapEdit(); + + //IGameEvent *event = gameeventmanager->CreateEvent( "playing_mapedit" ); + //gameeventmanager->FireEventClientSide( event ); + } + + bool MapEditConvarsChanging( void ) + { + return m_bMapEditConvarsChanging; + } + + void SetMapEditConvarsChanging( bool bChanging ) + { + m_bMapEditConvarsChanging = bChanging; + } + + void ConvarChanged( IConVar *pConVar, const char *pOldString, float flOldValue ) + { + ConVarRef var( pConVar ); + + // A convar has been changed by a commentary node. We need to store + // the old state. If the engine shuts down, we need to restore any + // convars that the commentary changed to their previous values. + for ( int i = 0; i < m_ModifiedConvars.Count(); i++ ) + { + // If we find it, just update the current value + if ( !Q_strncmp( var.GetName(), m_ModifiedConvars[i].pszConvar, MAX_MODIFIED_CONVAR_STRING ) ) + { + Q_strncpy( m_ModifiedConvars[i].pszCurrentValue, var.GetString(), MAX_MODIFIED_CONVAR_STRING ); + //Msg(" Updating Convar %s: value %s (org %s)\n", m_ModifiedConvars[i].pszConvar, m_ModifiedConvars[i].pszCurrentValue, m_ModifiedConvars[i].pszOrgValue ); + return; + } + } + + // We didn't find it in our list, so add it + modifiedconvars_t newConvar; + Q_strncpy( newConvar.pszConvar, var.GetName(), MAX_MODIFIED_CONVAR_STRING ); + Q_strncpy( newConvar.pszCurrentValue, var.GetString(), MAX_MODIFIED_CONVAR_STRING ); + Q_strncpy( newConvar.pszOrgValue, pOldString, MAX_MODIFIED_CONVAR_STRING ); + m_ModifiedConvars.AddToTail( newConvar ); + + /* + Msg(" Commentary changed '%s' to '%s' (was '%s')\n", var->GetName(), var->GetString(), pOldString ); + Msg(" Convars stored: %d\n", m_ModifiedConvars.Count() ); + for ( int i = 0; i < m_ModifiedConvars.Count(); i++ ) + { + Msg(" Convar %d: %s, value %s (org %s)\n", i, m_ModifiedConvars[i].pszConvar, m_ModifiedConvars[i].pszCurrentValue, m_ModifiedConvars[i].pszOrgValue ); + } + */ + } + + CBaseEntity *FindMapEditEntity( CBaseEntity *pStartEntity, const char *szName, const char *szValue = NULL ) + { + CBaseEntity *pEntity = NULL; + DebugMsg("MapEdit Find Debug: Starting Search, Name: %s, Value: %s\n", szName, szValue); + + // First, find by targetname/classname + pEntity = gEntList.FindEntityGeneric(pStartEntity, szName); + + if (!pEntity) + { + DebugMsg("MapEdit Find Debug: \"%s\" not found as targetname or classname\n", szName); + + if (szValue) + { + if (!Q_strnicmp(szName, "#find_", 6)) + { + const char *pName = szName + 6; + if (!Q_stricmp(pName, "by_keyfield")) + { + char key[64]; + char value[64]; + + // Separate key from value + char *delimiter = Q_strstr(szValue, " "); + if (delimiter) + { + Q_strncpy(key, szValue, MIN((delimiter - szValue) + 1, sizeof(key))); + Q_strncpy(value, delimiter + 1, sizeof(value)); + } + else + { + // Assume the value doesn't matter and we're just looking for the key + Q_strncpy(key, szValue, sizeof(key)); + } + + if (!key) + { + Warning("MapEdit: Possible find_by_keyfield syntax error: key not detected in \"%s\"\n", szValue); + } + + // Find entities with matching keyfield + variant_t variant; + const CEntInfo *pInfo = pStartEntity ? gEntList.GetEntInfoPtr(pStartEntity->GetRefEHandle())->m_pNext : gEntList.FirstEntInfo(); + for (; pInfo; pInfo = pInfo->m_pNext) + { + CBaseEntity *ent = (CBaseEntity *)pInfo->m_pEntity; + if (!ent) + { + DevWarning("NULL entity in global entity list!\n"); + continue; + } + + if (!ent->edict()) + continue; + + if (ent->ReadKeyField(key, &variant)) + { + // Does the value matter? + if (value) + { + if (Q_stricmp(variant.String(), value) == 0) + { + // The entity has the keyfield and it matches the value. + return ent; + } + } + else + { + // The value doesn't matter and the entity has the keyfield. + return ent; + } + } + } + } + else if (!Q_stricmp(pName, "by_origin")) + { + // Find entities at this origin + Vector vecOrigin; + UTIL_StringToVector(vecOrigin.Base(), szValue); + if (vecOrigin.IsValid()) + { + DebugMsg("MapEdit Find Debug: \"%s\" is valid vector\n", szValue); + const CEntInfo *pInfo = pStartEntity ? gEntList.GetEntInfoPtr(pStartEntity->GetRefEHandle())->m_pNext : gEntList.FirstEntInfo(); + for (; pInfo; pInfo = pInfo->m_pNext) + { + CBaseEntity *ent = (CBaseEntity *)pInfo->m_pEntity; + if (!ent) + { + DevWarning("NULL entity in global entity list!\n"); + continue; + } + + if (!ent->edict()) + continue; + + if (ent->GetLocalOrigin() == vecOrigin) + return ent; + } + } + } + } + else if (!pStartEntity) + { + // Try the entity index + int iEntIndex = atoi(szName); + if (!pStartEntity && UTIL_EntityByIndex(iEntIndex)) + { + DebugMsg("MapEdit Find Debug: \"%s\" is valid index\n", szName); + return CBaseEntity::Instance(iEntIndex); + } + } + } + + DebugMsg("MapEdit Find Debug: \"%s\" not found\n", szName); + return NULL; + } + + DebugMsg("MapEdit Find Debug: \"%s\" found as targetname or classname\n", szName); + return pEntity; + } + + void InitMapEdit( const char* pFile = MAPEDIT_DEFAULT_FILE ) + { + // Install the global cvar callback + cvar->InstallGlobalChangeCallback( CV_GlobalChange_MapEdit ); + + + // If we find the commentary semaphore, the commentary entities already exist. + // This occurs when you transition back to a map that has saved commentary nodes in it. + if ( gEntList.FindEntityByName( NULL, MAPEDIT_SPAWNED_SEMAPHORE ) ) + return; + + // Spawn the commentary semaphore entity + CBaseEntity *pSemaphore = CreateEntityByName( "info_target" ); + pSemaphore->SetName( MAKE_STRING(MAPEDIT_SPAWNED_SEMAPHORE) ); + + bool oldLock = engine->LockNetworkStringTables( false ); + + SpawnMapEdit(pFile); + + engine->LockNetworkStringTables( oldLock ); + } + + void LoadFromFormat_Original(KeyValues *pkvFile) + { + KeyValues *pkvNode = pkvFile->GetFirstSubKey(); + while ( pkvNode ) + { + const char *pNodeName = pkvNode->GetName(); + if (FStrEq(pNodeName, "create")) + { + KeyValues *pkvClassname = pkvNode->GetFirstSubKey(); + while (pkvClassname) + { + pNodeName = pkvClassname->GetName(); + + CBaseEntity *pNode = CreateEntityByName(pNodeName); + if (pNode) + { + ParseEntKVBlock(pNode, pkvClassname); + + EHANDLE hHandle; + hHandle = pNode; + m_hSpawnedEntities.AddToTail(hHandle); + DebugMsg("MapEdit Debug: Spawned entity %s\n", pNodeName); + } + else + { + Warning("MapEdit: Failed to spawn mapedit entity, type: '%s'\n", pNodeName); + } + + pkvClassname = pkvClassname->GetNextKey(); + } + } + else if (FStrEq(pNodeName, "edit")) + { + KeyValues *pName = pkvNode->GetFirstSubKey(); + while (pName) + { + pNodeName = pName->GetName(); + + CBaseEntity *pNode = NULL; + + pNode = FindMapEditEntity(NULL, pNodeName, pName->GetString()); + + while (pNode) + { + DebugMsg("MapEdit Debug: Editing %s (%s)\n", pNodeName, pNode->GetDebugName()); + + ParseEntKVBlock(pNode, pName); + pNode = FindMapEditEntity(pNode, pNodeName, pName->GetString()); + } + + pName = pName->GetNextKey(); + } + } + else if (FStrEq(pNodeName, "delete")) + { + KeyValues *pName = pkvNode->GetFirstSubKey(); + while (pName) + { + pNodeName = pName->GetName(); + + CBaseEntity *pNode = NULL; + + pNode = FindMapEditEntity(NULL, pNodeName, pName->GetString()); + + while (pNode) + { + DebugMsg("MapEdit Debug: Deleting %s (%s)\n", pNodeName, pNode->GetDebugName()); + + UTIL_Remove(pNode); + pNode = FindMapEditEntity(pNode, pNodeName, pName->GetString()); + } + + pName = pName->GetNextKey(); + } + } + else if (FStrEq(pNodeName, "fire")) + { + KeyValues *pName = pkvNode->GetFirstSubKey(); + while (pName) + { + pNodeName = pName->GetName(); + + string_t pInputName = NULL_STRING; + variant_t varInputParam; + float flInputDelay = 0.0f; + CBaseEntity *pActivator = NULL; + CBaseEntity *pCaller = NULL; + int iOutputID = 0; + + char *pszValue = strdup(pName->GetString()); + int iter = 0; + char *inputparams = strtok(pszValue, ","); + while (inputparams) + { + switch (iter) + { + // Input name + case 0: + pInputName = AllocPooledString(inputparams); break; + // Input parameter + case 1: + varInputParam.SetString(AllocPooledString(inputparams)); break; + // Input delay + case 2: + flInputDelay = atof(inputparams); break; + // Activator + case 3: + pActivator = gEntList.FindEntityByName(NULL, inputparams); break; + // Caller + case 4: + pCaller = gEntList.FindEntityByName(NULL, inputparams); break; + // Output ID + case 5: + iOutputID = atoi(inputparams); break; + } + iter++; + inputparams = strtok(NULL, ","); + } + free(pszValue); + + DebugMsg("MapEdit Debug: Firing input %s on %s\n", pInputName, pNodeName); + g_EventQueue.AddEvent(pNodeName, STRING(pInputName), varInputParam, flInputDelay, pActivator, pCaller, iOutputID); + + pName = pName->GetNextKey(); + } + } + else if (FStrEq(pNodeName, "console")) + { + KeyValues *pkvNodeData = pkvNode->GetFirstSubKey(); + const char *pKey; + const char *pValue; + while (pkvNodeData) + { + SetMapEditConvarsChanging(true); + + pKey = pkvNodeData->GetName(); + pValue = pkvNodeData->GetString(); + + engine->ServerCommand(UTIL_VarArgs("%s %s", pKey, pValue)); + engine->ServerCommand("mapedit_cvarsnotchanging\n"); + + pkvNodeData = pkvNodeData->GetNextKey(); + } + } + + pkvNode = pkvNode->GetNextKey(); + } + } + + void SpawnMapEdit(const char *pFile = NULL) + { + // Find the commentary file + char szFullName[512]; + if (pFile == NULL) + { + DebugMsg("MapEdit Debug: NULL file, loading default\n"); + Q_snprintf(szFullName,sizeof(szFullName), "maps/%s_auto.txt", STRING( gpGlobals->mapname )); + } + else + { + DebugMsg("MapEdit Debug: File not NULL, loading %s\n", pFile); + Q_strncpy(szFullName, pFile, sizeof(szFullName)); + } + KeyValues *pkvFile = new KeyValues( "MapEdit" ); + if ( pkvFile->LoadFromFile( filesystem, szFullName, "MOD" ) ) + { + Msg( "MapEdit: Loading MapEdit data from %s. \n", szFullName ); + + if (gpGlobals->eLoadType != MapLoad_LoadGame) + { + // Support for multiple formats + const char *szVersion = pkvFile->GetString("version", "original"); + if (FStrEq(szVersion, "original")) + LoadFromFormat_Original(pkvFile); + // TODO: More formats + } + + // Then activate all the entities + for ( int i = 0; i < m_hSpawnedEntities.Count(); i++ ) + { + DispatchSpawn(m_hSpawnedEntities[i]); + } + } + else + { + Msg( "MapEdit: Could not find MapEdit data file '%s'. \n", szFullName ); + } + + pkvFile->deleteThis(); + } + + void ShutDownMapEdit( void ) + { + // Destroy all the entities created by commentary + for ( int i = m_hSpawnedEntities.Count()-1; i >= 0; i-- ) + { + if ( m_hSpawnedEntities[i] ) + { + UTIL_Remove( m_hSpawnedEntities[i] ); + } + } + m_hSpawnedEntities.Purge(); + + // Remove the semaphore + CBaseEntity *pSemaphore = gEntList.FindEntityByName( NULL, MAPEDIT_SPAWNED_SEMAPHORE ); + if ( pSemaphore ) + { + UTIL_Remove( pSemaphore ); + } + + // Remove our global convar callback + cvar->RemoveGlobalChangeCallback( CV_GlobalChange_MapEdit ); + + // Reset any convars that have been changed by the commentary + for ( int i = 0; i < m_ModifiedConvars.Count(); i++ ) + { + ConVar *pConVar = (ConVar *)cvar->FindVar( m_ModifiedConvars[i].pszConvar ); + if ( pConVar ) + { + pConVar->SetValue( m_ModifiedConvars[i].pszOrgValue ); + } + } + m_ModifiedConvars.Purge(); + } + + void SetMapEdit( bool bMapEdit, const char *pFile = NULL ) + { + //g_bMapEditLoaded = bMapEdit; + //CalculateAvailableState(); + + // If we're turning on commentary, create all the entities. + if ( bMapEdit ) + { + if (filesystem->FileExists(pFile) || pFile == NULL) + { + g_bMapEditLoaded = true; + m_bMapEditLoadedMidGame = true; + InitMapEdit(pFile); + } + else + { + Warning("MapEdit: No such file \"%s\"!\n", pFile); + } + } + else + { + ShutDownMapEdit(); + } + } + + void OnRestore( void ) + { + cvar->RemoveGlobalChangeCallback( CV_GlobalChange_MapEdit ); + + if ( !IsMapEditLoaded() ) + return; + + // Set any convars that have already been changed by the commentary before the save + for ( int i = 0; i < m_ModifiedConvars.Count(); i++ ) + { + ConVar *pConVar = (ConVar *)cvar->FindVar( m_ModifiedConvars[i].pszConvar ); + if ( pConVar ) + { + //Msg(" Restoring Convar %s: value %s (org %s)\n", m_ModifiedConvars[i].pszConvar, m_ModifiedConvars[i].pszCurrentValue, m_ModifiedConvars[i].pszOrgValue ); + pConVar->SetValue( m_ModifiedConvars[i].pszCurrentValue ); + } + } + + // Install the global cvar callback + cvar->InstallGlobalChangeCallback( CV_GlobalChange_MapEdit ); + } + + bool MapEditLoadedMidGame( void ) + { + return m_bMapEditLoadedMidGame; + } + +private: + bool m_bMapEditConvarsChanging; + bool m_bMapEditLoadedMidGame; + + CUtlVector< modifiedconvars_t > m_ModifiedConvars; + CUtlVector m_hSpawnedEntities; +}; + +CMapEdit g_MapEdit; + +BEGIN_DATADESC_NO_BASE( CMapEdit ) + + DEFINE_FIELD( m_bMapEditLoadedMidGame, FIELD_BOOLEAN ), + + DEFINE_UTLVECTOR( m_ModifiedConvars, FIELD_EMBEDDED ), + DEFINE_UTLVECTOR( m_hSpawnedEntities, FIELD_EHANDLE ), + + //DEFINE_UTLVECTOR( m_SpawnEditLookup, FIELD_EMBEDDED ), + +END_DATADESC() + +//----------------------------------------------------------------------------- +// Purpose: We need to revert back any convar changes that are made by the +// commentary system during commentary. This code stores convar changes +// made by the commentary system, and reverts them when finished. +//----------------------------------------------------------------------------- +void CV_GlobalChange_MapEdit( IConVar *var, const char *pOldString, float flOldValue ) +{ + if ( !g_MapEdit.MapEditConvarsChanging() ) + { + // A convar has changed, but not due to mapedit. Ignore it. + return; + } + + g_MapEdit.ConvarChanged( var, pOldString, flOldValue ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CC_MapEditNotChanging( void ) +{ + g_MapEdit.SetMapEditConvarsChanging( false ); +} +static ConCommand mapedit_cvarsnotchanging( "mapedit_cvarsnotchanging", CC_MapEditNotChanging, 0 ); + +// ======================================================== +// Static functions that can be accessed from outside +// ======================================================== + +//----------------------------------------------------------------------------- +// Purpose: Reloads automatic MapEdit after cleanup +//----------------------------------------------------------------------------- +void MapEdit_MapReload( void ) +{ + Msg("MapEdit: Map reloading\n"); + + g_MapEdit.ShutDownMapEdit(); + + g_MapEdit.LevelInitPreEntity(); + + g_MapEdit.LevelInitPostEntity(); +} + +//----------------------------------------------------------------------------- +// Purpose: Loads a specific MapEdit file. +//----------------------------------------------------------------------------- +void MapEdit_LoadFile(const char *pFile, bool bStack) +{ + if (!filesystem->FileExists(pFile)) + { + Warning("MapEdit: No such file \"%s\"!\n", pFile); + return; + } + + if (IsMapEditLoaded()) + { + if (bStack) + { + g_MapEdit.SpawnMapEdit(pFile); + return; + } + else + { + g_MapEdit.SetMapEdit(false); + } + } + + g_MapEdit.SetMapEdit(true, pFile); +} + + +//----------------------------------------------------------------------------- +// Purpose: MapEdit specific logic_auto replacement. +// Fires outputs based upon how MapEdit has been activated. +//----------------------------------------------------------------------------- +class CMapEditAuto : public CBaseEntity +{ + DECLARE_CLASS( CMapEditAuto, CBaseEntity ); +public: + DECLARE_DATADESC(); + + void Spawn(void); + void Think(void); + +private: + // fired if loaded due to new map + COutputEvent m_OnMapEditNewGame; + + // fired if loaded in the middle of a map + COutputEvent m_OnMapEditMidGame; +}; + +LINK_ENTITY_TO_CLASS(mapedit_auto, CMapEditAuto); + +BEGIN_DATADESC( CMapEditAuto ) + // Outputs + DEFINE_OUTPUT(m_OnMapEditNewGame, "OnMapEditNewGame"), + DEFINE_OUTPUT(m_OnMapEditMidGame, "OnMapEditMidGame"), +END_DATADESC() + +//------------------------------------------------------------------------------ +// Purpose : Fire my outputs here if I fire on map reload +//------------------------------------------------------------------------------ +void CMapEditAuto::Spawn(void) +{ + BaseClass::Spawn(); + SetNextThink( gpGlobals->curtime + 0.1 ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CMapEditAuto::Think(void) +{ + if ( g_MapEdit.MapEditLoadedMidGame() ) + { + m_OnMapEditMidGame.FireOutput(NULL, this); + } + else + { + m_OnMapEditNewGame.FireOutput(NULL, this); + } +} + +//------------------------------------------------------------------------------ +// Purpose : +// Input : +// Output : +//------------------------------------------------------------------------------ +void CC_MapEdit_Load( const CCommand& args ) +{ + const char *sFile = args[1] ? args[1] : NULL; + + MapEdit_LoadFile(sFile, mapedit_stack.GetBool()); +} +static ConCommand mapedit_load("mapedit_load", CC_MapEdit_Load, "Forces mapedit to load a specific file. If there is no input value, it will load the map's default file.", FCVAR_CHEAT); + +//------------------------------------------------------------------------------ +// Purpose : Unloads all MapEdit entities. Does not undo modifications or deletions. +// Input : +// Output : +//------------------------------------------------------------------------------ +void CC_MapEdit_Unload( const CCommand& args ) +{ + g_MapEdit.SetMapEdit(false); +} +static ConCommand mapedit_unload("mapedit_unload", CC_MapEdit_Unload, "Forces mapedit to unload.", FCVAR_CHEAT); + +#else + +//------------------------------------------------------------------------------ +// Purpose : Prints MapEdit data from a specific file to the console. +// Input : The file to print +// Output : The file's data +//------------------------------------------------------------------------------ +void CC_MapEdit_Print( const CCommand& args ) +{ + const char *szFullName = args[1]; + if (szFullName && filesystem->FileExists(szFullName)) + { + KeyValues *pkvFile = new KeyValues( "MapEdit" ); + if ( pkvFile->LoadFromFile( filesystem, szFullName, "MOD" ) ) + { + Msg( "MapEdit: Printing MapEdit data from %s. \n", szFullName ); + + // Load each commentary block, and spawn the entities + KeyValues *pkvNode = pkvFile->GetFirstSubKey(); + while ( pkvNode ) + { + // Get node name + const char *pNodeName = pkvNode->GetName(); + Msg("- Section Name: %s\n", pNodeName); + + // Skip the trackinfo + if ( !Q_strncmp( pNodeName, "trackinfo", 9 ) ) + { + pkvNode = pkvNode->GetNextKey(); + continue; + } + + KeyValues *pClassname = pkvNode->GetFirstSubKey(); + while (pClassname) + { + // Use the classname instead + pNodeName = pClassname->GetName(); + + Msg(" %s\n", pNodeName); + for ( KeyValues *sub = pClassname->GetFirstSubKey(); sub; sub = sub->GetNextKey() ) + { + if (!Q_strcmp(sub->GetName(), "connections")) + { + Msg("- connections\n"); + for ( KeyValues *sub2 = sub->GetFirstSubKey(); sub2; sub2 = sub2->GetNextKey() ) + { + Msg("- \"%s\", \"%s\"\n", sub2->GetName(), sub2->GetString()); + } + continue; + } + Msg("- %s, %s\n", sub->GetName(), sub->GetString()); + } + + pClassname = pClassname->GetNextKey(); + } + + pkvNode = pkvNode->GetNextKey(); + } + + } + pkvFile->deleteThis(); + } +} +static ConCommand mapedit_print("mapedit_print", CC_MapEdit_Print, "Prints a mapedit file in the console."); + +#endif diff --git a/src/game/shared/mapbase/MapEdit.h b/src/game/shared/mapbase/MapEdit.h new file mode 100644 index 00000000..899f6712 --- /dev/null +++ b/src/game/shared/mapbase/MapEdit.h @@ -0,0 +1,14 @@ +//========= Mapbase - https://github.com/mapbase-source/source-sdk-2013 ============// +// +// Purpose: Accessing MapEdit +// +// $NoKeywords: $ +//=============================================================================// + +extern ConVar mapedit_enabled; +extern ConVar mapedit_stack; +extern ConVar mapedit_debug; + +void MapEdit_MapReload( void ); + +void MapEdit_LoadFile( const char *pFile, bool bStack = mapedit_stack.GetBool() ); diff --git a/src/game/shared/mapbase/logic_script_client.cpp b/src/game/shared/mapbase/logic_script_client.cpp new file mode 100644 index 00000000..97edfe30 --- /dev/null +++ b/src/game/shared/mapbase/logic_script_client.cpp @@ -0,0 +1,276 @@ +//========= Mapbase - https://github.com/mapbase-source/source-sdk-2013 ============// +// +// Purpose: Custom client-side equivalent of logic_script. +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" +#include "vscript_shared.h" +#include "tier1/fmtstr.h" + +#ifdef CLIENT_DLL +ConVar cl_script_think_interval( "cl_script_think_interval", "0.1" ); +#endif + +//----------------------------------------------------------------------------- +// Purpose: An entity that acts as a container for client-side game scripts. +//----------------------------------------------------------------------------- + +#define MAX_SCRIPT_GROUP_CLIENT 8 + +class CLogicScriptClient : public CBaseEntity +{ +public: + DECLARE_CLASS( CLogicScriptClient, CBaseEntity ); + DECLARE_DATADESC(); + DECLARE_NETWORKCLASS(); + +#ifdef CLIENT_DLL + void OnDataChanged( DataUpdateType_t type ) + { + BaseClass::OnDataChanged( type ); + + if ( !m_ScriptScope.IsInitialized() ) + { + RunVScripts(); + } + } +#else + int UpdateTransmitState() { return SetTransmitState( FL_EDICT_ALWAYS ); } +#endif + + bool KeyValue( const char *szKeyName, const char *szValue ) + { + if ( FStrEq( szKeyName, "vscripts" ) ) + { + Q_strcpy( m_iszClientScripts.GetForModify(), szValue ); + } + + return BaseClass::KeyValue( szKeyName, szValue ); + } + + void RunVScripts() + { +#ifdef CLIENT_DLL + if (m_iszClientScripts == NULL_STRING) + { + CGMsg( 0, CON_GROUP_VSCRIPT, "%s has no client scripts", GetDebugName() ); + return; + } + + if (g_pScriptVM == NULL) + { + return; + } + + ValidateScriptScope(); + + // All functions we want to have call chained instead of overwritten + // by other scripts in this entities list. + static const char* sCallChainFunctions[] = + { + "OnPostSpawn", + "Precache" + }; + + ScriptLanguage_t language = g_pScriptVM->GetLanguage(); + + // Make a call chainer for each in this entities scope + for (int j = 0; j < ARRAYSIZE( sCallChainFunctions ); ++j) + { + + if (language == SL_PYTHON) + { + // UNDONE - handle call chaining in python + ; + } + else if (language == SL_SQUIRREL) + { + //TODO: For perf, this should be precompiled and the %s should be passed as a parameter + HSCRIPT hCreateChainScript = g_pScriptVM->CompileScript( CFmtStr( "%sCallChain <- CSimpleCallChainer(\"%s\", self.GetScriptScope(), true)", sCallChainFunctions[j], sCallChainFunctions[j] ) ); + g_pScriptVM->Run( hCreateChainScript, (HSCRIPT)m_ScriptScope ); + } + } + + char szScriptsList[255]; + Q_strcpy( szScriptsList, m_iszClientScripts.Get() ); + CUtlStringList szScripts; + + V_SplitString( szScriptsList, " ", szScripts ); + + for (int i = 0; i < szScripts.Count(); i++) + { + CGMsg( 0, CON_GROUP_VSCRIPT, "%s executing script: %s\n", GetDebugName(), szScripts[i] ); + + RunScriptFile( szScripts[i], IsWorld() ); + + for (int j = 0; j < ARRAYSIZE( sCallChainFunctions ); ++j) + { + if (language == SL_PYTHON) + { + // UNDONE - handle call chaining in python + ; + } + else if (language == SL_SQUIRREL) + { + //TODO: For perf, this should be precompiled and the %s should be passed as a parameter. + HSCRIPT hRunPostScriptExecute = g_pScriptVM->CompileScript( CFmtStr( "%sCallChain.PostScriptExecute()", sCallChainFunctions[j] ) ); + g_pScriptVM->Run( hRunPostScriptExecute, (HSCRIPT)m_ScriptScope ); + } + } + } + + if (m_bClientThink) + { + SetNextClientThink( CLIENT_THINK_ALWAYS ); + } +#else + // Avoids issues from having m_iszVScripts set without actually having a script scope + ValidateScriptScope(); + + if (m_bRunOnServer) + { + BaseClass::RunVScripts(); + } +#endif + } + +#ifdef CLIENT_DLL + void ClientThink() + { + ScriptVariant_t varThinkRetVal; + if (CallScriptFunction("ClientThink", &varThinkRetVal)) + { + float flThinkFrequency = 0.0f; + if (!varThinkRetVal.AssignTo(&flThinkFrequency)) + { + // use default think interval if script think function doesn't provide one + flThinkFrequency = cl_script_think_interval.GetFloat(); + } + + if (flThinkFrequency == CLIENT_THINK_ALWAYS) + SetNextClientThink( CLIENT_THINK_ALWAYS ); + else + SetNextClientThink( gpGlobals->curtime + flThinkFrequency ); + } + else + { + DevWarning("%s FAILED to call client script think function!\n", GetDebugName()); + } + + BaseClass::ClientThink(); + } + + void OnSave() + { + // HACKHACK: Save the next think in the VM since the VM is saved + if (m_bClientThink) + { + g_pScriptVM->SetValue( m_ScriptScope, "__c_think", GetNextThink() ); + } + + BaseClass::OnSave(); + } + + void OnRestore() + { + // HACKHACK: See OnSave() + if (m_bClientThink) + { + ScriptVariant_t flNextThink; + if (g_pScriptVM->GetValue( m_ScriptScope, "__c_think", &flNextThink )) + { + SetNextClientThink( flNextThink ); + } + } + + BaseClass::OnRestore(); + } + + void ReceiveMessage( int classID, bf_read &msg ) + { + if ( classID != GetClientClass()->m_ClassID ) + { + BaseClass::ReceiveMessage( classID, msg ); + return; + } + + char szFunction[64]; + msg.ReadString( szFunction, sizeof( szFunction ) ); + + if ( m_ScriptScope.IsInitialized() ) + { + CallScriptFunction( szFunction, NULL ); + } + else + { + CGMsg( 0, CON_GROUP_VSCRIPT, "%s script scope not initialized!\n", GetDebugName() ); + } + } +#endif + +#ifdef GAME_DLL + void InputCallScriptFunctionClient( inputdata_t &inputdata ) + { + const char *pszFunction = inputdata.value.String(); + if ( V_strlen( pszFunction ) >= 64 ) + { + Msg( "%s CallScriptFunctionClient: \"%s\" is too long at %i characters, must be 64 or less\n", GetDebugName(), pszFunction, V_strlen(pszFunction)+1 ); + return; + } + + EntityMessageBegin( this, true ); + WRITE_STRING( pszFunction ); + MessageEnd(); + } +#endif + + //CNetworkArray( string_t, m_iszGroupMembers, MAX_SCRIPT_GROUP_CLIENT ); + CNetworkString( m_iszClientScripts, 128 ); + CNetworkVar( bool, m_bClientThink ); + +#ifndef CLIENT_DLL + bool m_bRunOnServer; +#endif +}; + +LINK_ENTITY_TO_CLASS( logic_script_client, CLogicScriptClient ); + +BEGIN_DATADESC( CLogicScriptClient ) + + // TODO: Does this need to be saved? + //DEFINE_AUTO_ARRAY( m_iszClientScripts, FIELD_CHARACTER ), + + //DEFINE_KEYFIELD( m_iszGroupMembers[0], FIELD_STRING, "Group00"), + //DEFINE_KEYFIELD( m_iszGroupMembers[1], FIELD_STRING, "Group01"), + //DEFINE_KEYFIELD( m_iszGroupMembers[2], FIELD_STRING, "Group02"), + //DEFINE_KEYFIELD( m_iszGroupMembers[3], FIELD_STRING, "Group03"), + //DEFINE_KEYFIELD( m_iszGroupMembers[4], FIELD_STRING, "Group04"), + //DEFINE_KEYFIELD( m_iszGroupMembers[5], FIELD_STRING, "Group05"), + //DEFINE_KEYFIELD( m_iszGroupMembers[6], FIELD_STRING, "Group06"), + //DEFINE_KEYFIELD( m_iszGroupMembers[7], FIELD_STRING, "Group07"), + + DEFINE_KEYFIELD( m_bClientThink, FIELD_BOOLEAN, "ClientThink" ), + +#ifndef CLIENT_DLL + DEFINE_KEYFIELD( m_bRunOnServer, FIELD_BOOLEAN, "RunOnServer" ), + + DEFINE_INPUTFUNC( FIELD_STRING, "CallScriptFunctionClient", InputCallScriptFunctionClient ), +#endif + +END_DATADESC() + +IMPLEMENT_NETWORKCLASS_DT( CLogicScriptClient, DT_LogicScriptClient ) + +#ifdef CLIENT_DLL + //RecvPropArray( RecvPropString( RECVINFO( m_iszGroupMembers[0] ) ), m_iszGroupMembers ), + RecvPropString( RECVINFO( m_iszClientScripts ) ), + RecvPropBool( RECVINFO( m_bClientThink ) ), +#else + //SendPropArray( SendPropStringT( SENDINFO_ARRAY( m_iszGroupMembers ) ), m_iszGroupMembers ), + SendPropString( SENDINFO( m_iszClientScripts ) ), + SendPropBool( SENDINFO( m_bClientThink ) ), +#endif + +END_NETWORK_TABLE() diff --git a/src/game/shared/mapbase/mapbase_game_log.cpp b/src/game/shared/mapbase/mapbase_game_log.cpp new file mode 100644 index 00000000..c51ab0c4 --- /dev/null +++ b/src/game/shared/mapbase/mapbase_game_log.cpp @@ -0,0 +1,243 @@ +//========= Mapbase - https://github.com/mapbase-source/source-sdk-2013 ============// +// +// Purpose: A special system designed to record game information for map testing. +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" + +#include "tier0/icommandline.h" +#include "igamesystem.h" +#include "filesystem.h" +#include "utlbuffer.h" +#ifdef CLIENT_DLL +#else +#include "ammodef.h" +#include "ai_basenpc.h" +#include "ai_squad.h" +#include "fmtstr.h" +#include "GameEventListener.h" +#include "saverestore_utlvector.h" +#endif + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +#ifdef GAME_DLL +// ------------------------------------------------------------------------------------ +// ------------------------------------------------------------------------------------ + +class CMapbaseGameLogger : public CLogicalEntity, public CGameEventListener +{ +public: + DECLARE_DATADESC(); + DECLARE_CLASS( CMapbaseGameLogger, CLogicalEntity ); + + CMapbaseGameLogger() + { + pGameLoggerEnt = this; + } + + void Activate() + { + BaseClass::Activate(); + + ListenForGameEvent("skill_changed"); + } + + void FireGameEvent( IGameEvent *event ) + { + if (FStrEq(event->GetName(), "skill_changed")) + { + m_ListSkillChanged.AddToTail(event->GetInt("skill_level")); + m_ListSkillChangedTime.AddToTail(gpGlobals->curtime); + } + } + + float m_flLastLogTime; + int m_iSaveID; + + CUtlVector m_ListSkillChanged; + CUtlVector m_ListSkillChangedTime; + + static CMapbaseGameLogger *GetGameLoggerEnt() + { + if (!pGameLoggerEnt) + pGameLoggerEnt = static_cast(CBaseEntity::Create("mapbase_game_logger", vec3_origin, vec3_angle)); + + return pGameLoggerEnt; + } + +private: + static CHandle pGameLoggerEnt; +}; + +LINK_ENTITY_TO_CLASS( mapbase_game_logger, CMapbaseGameLogger ); + +BEGIN_DATADESC( CMapbaseGameLogger ) + + DEFINE_FIELD( m_flLastLogTime, FIELD_TIME ), + DEFINE_FIELD( m_iSaveID, FIELD_INTEGER ), + + DEFINE_UTLVECTOR( m_ListSkillChanged, FIELD_INTEGER ), + DEFINE_UTLVECTOR( m_ListSkillChangedTime, FIELD_TIME ), + +END_DATADESC() + +CHandle CMapbaseGameLogger::pGameLoggerEnt; + +void MapbaseGameLog_CVarToggle( IConVar *var, const char *pOldString, float flOldValue ); +ConVar mapbase_game_log_on_autosave( "mapbase_game_log_on_autosave", "0", FCVAR_NONE, "Logs information to %mapname%_log_%number%.txt on each autosave", MapbaseGameLog_CVarToggle ); + +void MapbaseGameLog_Init() +{ + if (mapbase_game_log_on_autosave.GetBool()) + { + // Create the game logger ent + CMapbaseGameLogger::GetGameLoggerEnt(); + } +} + +void MapbaseGameLog_Record( const char *szContext ) +{ + CMapbaseGameLogger *pGameLoggerEnt = CMapbaseGameLogger::GetGameLoggerEnt(); + if (!pGameLoggerEnt) + { + Warning("Failed to get game logger ent\n"); + return; + } + + KeyValues *pKV = new KeyValues( "Log" ); + + KeyValues *pKVLogInfo = pKV->FindKey( "logging_info", true ); + if ( pKVLogInfo ) + { + pKVLogInfo->SetString("context", szContext); + pKVLogInfo->SetFloat("last_log", pGameLoggerEnt->m_flLastLogTime > 0.0f ? gpGlobals->curtime - pGameLoggerEnt->m_flLastLogTime : -1.0f); + } + + KeyValues *pKVGameInfo = pKV->FindKey( "game_info", true ); + if ( pKVGameInfo ) + { + pKVGameInfo->SetInt("skill", g_pGameRules->GetSkillLevel()); + + if (pGameLoggerEnt->m_ListSkillChanged.Count() > 0) + { + KeyValues *pKVSkill = pKVGameInfo->FindKey("skill_changes", true); + for (int i = 0; i < pGameLoggerEnt->m_ListSkillChanged.Count(); i++) + { + float flTime = pGameLoggerEnt->m_ListSkillChangedTime[i]; + switch (pGameLoggerEnt->m_ListSkillChanged[i]) + { + case SKILL_EASY: pKVSkill->SetString(CNumStr(flTime), "easy"); break; + case SKILL_MEDIUM: pKVSkill->SetString(CNumStr(flTime), "normal"); break; + case SKILL_HARD: pKVSkill->SetString(CNumStr(flTime), "hard"); break; + } + } + } + } + + KeyValues *pKVPlayer = pKV->FindKey( "player", true ); + if ( pKVPlayer ) + { + CBasePlayer *pPlayer = UTIL_GetLocalPlayer(); + + if ( pPlayer ) + { + pKVPlayer->SetInt("health", pPlayer->GetHealth()); + pKVPlayer->SetInt("armor", pPlayer->ArmorValue()); + + pKVPlayer->SetString("position", CFmtStrN<128>("[%f %f %f]", pPlayer->GetAbsOrigin().x, pPlayer->GetAbsOrigin().y, pPlayer->GetAbsOrigin().z)); + pKVPlayer->SetString("angles", CFmtStrN<128>("[%f %f %f]", pPlayer->EyeAngles().x, pPlayer->EyeAngles().y, pPlayer->EyeAngles().z)); + + KeyValues *pKVWeapons = pKVPlayer->FindKey( "weapons", true ); + if ( pKVWeapons ) + { + // Cycle through all of the player's weapons + for ( int i = 0; i < pPlayer->WeaponCount(); i++ ) + { + CBaseCombatWeapon *pWeapon = pPlayer->GetWeapon(i); + if ( !pWeapon ) + continue; + + if ( pPlayer->GetActiveWeapon() == pWeapon ) + pKVWeapons->SetString(pWeapon->GetClassname(), CFmtStrN<32>("%i; %i (active)", pWeapon->m_iClip1.Get(), pWeapon->m_iClip2.Get())); + else + pKVWeapons->SetString(pWeapon->GetClassname(), CFmtStrN<32>("%i; %i", pWeapon->m_iClip1.Get(), pWeapon->m_iClip2.Get())); + } + } + + KeyValues *pKVAmmo = pKVPlayer->FindKey( "ammo", true ); + if ( pKVAmmo ) + { + // Cycle through all of the player's ammo + for ( int i = 0; i < GetAmmoDef()->m_nAmmoIndex; i++ ) + { + int iAmmo = pPlayer->GetAmmoCount( i ); + if ( iAmmo > 0 ) + pKVAmmo->SetInt( GetAmmoDef()->m_AmmoType[i].pName, iAmmo ); + } + } + } + } + + CAI_BaseNPC **ppAIs = g_AI_Manager.AccessAIs(); + int nAIs = g_AI_Manager.NumAIs(); + for (int i = 0; i < nAIs; i++) + { + CAI_BaseNPC *pNPC = ppAIs[i]; + + if (!pNPC->IsAlive() || pNPC->GetSleepState() != AISS_AWAKE) + continue; + + KeyValues *pKVNPC = pKV->FindKey( CNumStr( pNPC->entindex() ), true ); + if (pKVNPC) + { + pKVNPC->SetString("classname", pNPC->GetClassname()); + pKVNPC->SetString("name", STRING(pNPC->GetEntityName())); + + pKVNPC->SetString("position", CFmtStrN<128>("[%f %f %f]", pNPC->GetAbsOrigin().x, pNPC->GetAbsOrigin().y, pNPC->GetAbsOrigin().z)); + + pKVNPC->SetInt("health", pNPC->GetHealth()); + + if (pNPC->GetActiveWeapon()) + pKVNPC->SetString("weapon", pNPC->GetActiveWeapon()->GetClassname()); + + if (pNPC->GetSquad()) + pKVNPC->SetString("squad", pNPC->GetSquad()->GetName()); + } + } + + CFmtStrN pathfmt("map_logs/%s_log_%i.txt", STRING(gpGlobals->mapname), pGameLoggerEnt->m_iSaveID); + + pGameLoggerEnt->m_flLastLogTime = gpGlobals->curtime; + pGameLoggerEnt->m_iSaveID++; + + // Create the folder first, since "map_logs" is not standard and is unlikely to exist + g_pFullFileSystem->CreateDirHierarchy( "map_logs", "MOD" ); + + if (pKV->SaveToFile( g_pFullFileSystem, pathfmt, "MOD" )) + { + Msg("Saved game log file to \"%s\"\n", pathfmt.Get()); + } + + pKV->deleteThis(); +} + +static void CC_Mapbase_GameLogRecord( const CCommand& args ) +{ + MapbaseGameLog_Record( "command" ); +} + +static ConCommand mapbase_game_log_record("mapbase_game_log_record", CC_Mapbase_GameLogRecord, "Records game data to %mapname%_log_%number%." ); + +void MapbaseGameLog_CVarToggle( IConVar *var, const char *pOldString, float flOldValue ) +{ + if (mapbase_game_log_on_autosave.GetBool()) + { + // Create the game logger ent + CMapbaseGameLogger::GetGameLoggerEnt(); + } +} +#endif diff --git a/src/game/shared/mapbase/mapbase_rpc.cpp b/src/game/shared/mapbase/mapbase_rpc.cpp new file mode 100644 index 00000000..ecbb36ca --- /dev/null +++ b/src/game/shared/mapbase/mapbase_rpc.cpp @@ -0,0 +1,606 @@ +//========= Mapbase - https://github.com/mapbase-source/source-sdk-2013 ============// +// +// Purpose: Mapbase's RPC implementation. +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" + +#ifdef CLIENT_DLL + +#ifdef STEAM_RPC +#include "clientsteamcontext.h" +#include "steam/steamclientpublic.h" +#endif + +#ifdef DISCORD_RPC +#include "discord_rpc.h" +#include +#include "c_world.h" +#endif + +#include "filesystem.h" +#include "c_playerresource.h" +#include +#include + +#endif + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +// From mapbase_shared.cpp +extern const char *g_MapName; + +// The game's name found in gameinfo.txt. Mostly used for Discord RPC. +extern char g_iszGameName[128]; + +#ifdef MAPBASE_RPC +void MapbaseRPC_CVarToggle( IConVar *var, const char *pOldString, float flOldValue ); + +ConVar mapbase_rpc_enabled("mapbase_rpc_enabled", "1", FCVAR_ARCHIVE, "Controls whether Mapbase's RPC stuff is enabled on this client.", MapbaseRPC_CVarToggle); + +//----------------------------------------------------------------------------- +// RPC Stuff +// +// Mapbase has some special "RPC" integration stuff for things like Discord. +// There's a section that goes into more detail below. +//----------------------------------------------------------------------------- + +void MapbaseRPC_Init(); +void MapbaseRPC_Shutdown(); + +void MapbaseRPC_Update( int iType, const char *pMapName ); +void MapbaseRPC_Update( int iRPCMask, int iType, const char *pMapName ); + +#ifdef STEAM_RPC +void MapbaseRPC_UpdateSteam( int iType, const char *pMapName ); +#endif + +#ifdef DISCORD_RPC +void MapbaseRPC_UpdateDiscord( int iType, const char *pMapName ); +void MapbaseRPC_GetDiscordParameters( DiscordRichPresence &discordPresence, int iType, const char *pMapName ); +#endif + +enum RPCClients_t +{ + RPC_STEAM, + RPC_DISCORD, + + NUM_RPCS, +}; + +static const char *g_pszRPCNames[] = { + "Steam", + "Discord", +}; + +// This is a little dodgy, but it stops us from having to add spawnflag definitions for each RPC. +#define RPCFlag(rpc) (1 << rpc) + +// The global game_metadata entity. +// There can be only one...for each RPC. +static EHANDLE g_Metadata[NUM_RPCS]; + +// Don't update constantly +#define RPC_UPDATE_COOLDOWN 5.0f + +// How long to wait before updating in case multiple variables are changing +#define RPC_UPDATE_WAIT 0.25f +#endif + +#ifdef CLIENT_DLL +#define CMapbaseMetadata C_MapbaseMetadata +#endif + +class CMapbaseMetadata : public CBaseEntity +{ +public: +#ifndef CLIENT_DLL + DECLARE_DATADESC(); +#endif + DECLARE_NETWORKCLASS(); + DECLARE_CLASS( CMapbaseMetadata, CBaseEntity ); + +#ifdef MAPBASE_RPC +#ifdef CLIENT_DLL + ~C_MapbaseMetadata() + { + for (int i = 0; i < NUM_RPCS; i++) + { + if (g_Metadata[i] == this) + { + g_Metadata[i] = NULL; + } + } + } + + void OnDataChanged( DataUpdateType_t updateType ) + { + if (updateType == DATA_UPDATE_CREATED) + { + for (int i = 0; i < NUM_RPCS; i++) + { + // See if we're updating this RPC. + if (m_spawnflags & RPCFlag(i)) + { + if (g_Metadata[i]) + { + Warning("Warning: Metadata entity for %s already exists, replacing with new one\n", g_pszRPCNames[i]); + + // Inherit their update timer + m_flRPCUpdateTimer = static_cast(g_Metadata[i].Get())->m_flRPCUpdateTimer; + + g_Metadata[i].Get()->Remove(); + } + + DevMsg("Becoming metadata entity for %s\n", g_pszRPCNames[i]); + g_Metadata[i] = this; + } + } + } + + // Avoid spamming updates + if (gpGlobals->curtime > (m_flRPCUpdateTimer - RPC_UPDATE_WAIT)) + { + // Multiple variables might be changing, wait until they're probably all finished + m_flRPCUpdateTimer = gpGlobals->curtime + RPC_UPDATE_WAIT; + } + + DevMsg("Metadata changed; updating in %f\n", m_flRPCUpdateTimer - gpGlobals->curtime); + + // Update when the cooldown is over + SetNextClientThink( m_flRPCUpdateTimer ); + } + + void ClientThink() + { + // NOTE: Client thinking should be limited by the update timer! + UpdateRPCThink(); + + // Wait until our data is changed again + SetNextClientThink( CLIENT_THINK_NEVER ); + } + + void UpdateRPCThink() + { + DevMsg("Global metadata entity: %s\n", g_Metadata != NULL ? "Valid" : "Invalid!?"); + + MapbaseRPC_Update(m_spawnflags, RPCSTATE_UPDATE, g_MapName); + + m_flRPCUpdateTimer = gpGlobals->curtime + RPC_UPDATE_COOLDOWN; + } +#else + int UpdateTransmitState() // always send to all clients + { + return SetTransmitState( FL_EDICT_ALWAYS ); + } +#endif +#endif + +#ifdef CLIENT_DLL + char m_iszRPCState[128]; + char m_iszRPCDetails[128]; + +#ifdef MAPBASE_RPC + // Built-in update spam limiter + float m_flRPCUpdateTimer = RPC_UPDATE_COOLDOWN; + + int m_spawnflags; +#endif +#else + CNetworkVar( string_t, m_iszRPCState ); + CNetworkVar( string_t, m_iszRPCDetails ); +#endif + + // TODO: Player-specific control + //CNetworkVar( int, m_iLimitingID ); +}; + +LINK_ENTITY_TO_CLASS( game_metadata, CMapbaseMetadata ); + +IMPLEMENT_NETWORKCLASS_ALIASED(MapbaseMetadata, DT_MapbaseMetadata) + +BEGIN_NETWORK_TABLE_NOBASE(CMapbaseMetadata, DT_MapbaseMetadata) + +#ifdef MAPBASE_RPC +#ifdef CLIENT_DLL + RecvPropString(RECVINFO(m_iszRPCState)), + RecvPropString(RECVINFO(m_iszRPCDetails)), + RecvPropInt( RECVINFO( m_spawnflags ) ), +#else + SendPropStringT(SENDINFO(m_iszRPCState) ), + SendPropStringT(SENDINFO(m_iszRPCDetails) ), + SendPropInt( SENDINFO(m_spawnflags), 8, SPROP_UNSIGNED ), +#endif +#endif + +END_NETWORK_TABLE() + +#ifndef CLIENT_DLL +BEGIN_DATADESC( CMapbaseMetadata ) + + // Inputs + DEFINE_INPUT( m_iszRPCState, FIELD_STRING, "SetRPCState" ), + DEFINE_INPUT( m_iszRPCDetails, FIELD_STRING, "SetRPCDetails" ), + +END_DATADESC() +#endif + +#ifdef MAPBASE_RPC +//----------------------------------------------------------------------------- +// Purpose: Mapbase's special integration with rich presence clients, most notably Discord. +// +// This only has Discord and crude groundwork for Steam as of writing, +//----------------------------------------------------------------------------- + +//----------------------------------------- +// !!! FOR MODS !!! +// +// Create your own Discord "application" if you want to change what info/images show up, etc. +// You can change the app ID in "scripts/mapbase_rpc.txt". It's located in the shared content VPK and the mod templates. +// You could override that file in your mod to change it to your own app ID. +// +// This code automatically shows the mod's title in the details, but it's easy to change this code if you want things to be chapter-specific, etc. +// +//----------------------------------------- + +// Changing the default value of the convars below will not work. +// Use "scripts/mapbase_rpc.txt" instead. +static ConVar cl_discord_appid("cl_discord_appid", "582595088719413250", FCVAR_NONE); +static ConVar cl_discord_largeimage("cl_discord_largeimage", "mb_logo_episodic", FCVAR_NONE); +static ConVar cl_discord_largeimage_text("cl_discord_largeimage_text", "Half-Life 2", FCVAR_NONE); +static int64_t startTimestamp = time(0); + +// + +int MapbaseRPC_GetPlayerCount() +{ + int iNumPlayers = 0; + + if (g_PR) + { + for (; iNumPlayers <= gpGlobals->maxClients; iNumPlayers++) + { + if (!g_PR->IsConnected( iNumPlayers )) + break; + } + } + + return iNumPlayers; +} + +//----------------------------------------------------------------------------- +// Discord RPC handlers +//----------------------------------------------------------------------------- +static void HandleDiscordReady(const DiscordUser* connectedUser) +{ + DevMsg("Discord: Connected to user %s#%s - %s\n", + connectedUser->username, + connectedUser->discriminator, + connectedUser->userId); +} + +static void HandleDiscordDisconnected(int errcode, const char* message) +{ + DevMsg("Discord: Disconnected (%d: %s)\n", errcode, message); +} + +static void HandleDiscordError(int errcode, const char* message) +{ + DevMsg("Discord: Error (%d: %s)\n", errcode, message); +} + +static void HandleDiscordJoin(const char* secret) +{ + // Not implemented +} + +static void HandleDiscordSpectate(const char* secret) +{ + // Not implemented +} + +static void HandleDiscordJoinRequest(const DiscordUser* request) +{ + // Not implemented +} + +void MapbaseRPC_Init() +{ + // Only init if RPC is enabled + if (mapbase_rpc_enabled.GetInt() <= 0) + return; + + // First, load the config + // (we need its values immediately) + KeyValues *pKV = new KeyValues( "MapbaseRPC" ); + if (pKV->LoadFromFile( filesystem, "scripts/mapbase_rpc.txt" )) + { + const char *szAppID = pKV->GetString("discord_appid", cl_discord_appid.GetString()); + cl_discord_appid.SetValue(szAppID); + + const char *szLargeImage = pKV->GetString("discord_largeimage", cl_discord_largeimage.GetString()); + cl_discord_largeimage.SetValue(szLargeImage); + + const char *szLargeImageText = pKV->GetString("discord_largeimage_text", cl_discord_largeimage_text.GetString()); + cl_discord_largeimage_text.SetValue( szLargeImageText ); + } + pKV->deleteThis(); + + // Steam RPC + if (steamapicontext) + { + if (steamapicontext->SteamFriends()) + steamapicontext->SteamFriends()->ClearRichPresence(); + } + + // Discord RPC + DiscordEventHandlers handlers; + memset(&handlers, 0, sizeof(handlers)); + + handlers.ready = HandleDiscordReady; + handlers.disconnected = HandleDiscordDisconnected; + handlers.errored = HandleDiscordError; + handlers.joinGame = HandleDiscordJoin; + handlers.spectateGame = HandleDiscordSpectate; + handlers.joinRequest = HandleDiscordJoinRequest; + + char appid[255]; + sprintf(appid, "%d", engine->GetAppID()); + Discord_Initialize(cl_discord_appid.GetString(), &handlers, 1, appid); + + if (!g_bTextMode) + { + DiscordRichPresence discordPresence; + memset(&discordPresence, 0, sizeof(discordPresence)); + + MapbaseRPC_GetDiscordParameters(discordPresence, RPCSTATE_INIT, NULL); + + discordPresence.startTimestamp = startTimestamp; + + Discord_UpdatePresence(&discordPresence); + } +} + +void MapbaseRPC_Shutdown() +{ + // Discord RPC + Discord_ClearPresence(); + Discord_Shutdown(); + + // Steam RPC + if (steamapicontext) + { + if (steamapicontext->SteamFriends()) + steamapicontext->SteamFriends()->ClearRichPresence(); + } +} + +void MapbaseRPC_Update( int iType, const char *pMapName ) +{ + // All RPCs + MapbaseRPC_Update( INT_MAX, iType, pMapName ); +} + +void MapbaseRPC_Update( int iRPCMask, int iType, const char *pMapName ) +{ + // Only update if RPC is enabled + if (mapbase_rpc_enabled.GetInt() <= 0) + return; + + if (iRPCMask & RPCFlag(RPC_STEAM)) + MapbaseRPC_UpdateSteam(iType, pMapName); + if (iRPCMask & RPCFlag(RPC_DISCORD)) + MapbaseRPC_UpdateDiscord(iType, pMapName); +} + +#ifdef STEAM_RPC +void MapbaseRPC_UpdateSteam( int iType, const char *pMapName ) +{ + // No Steam + if (!steamapicontext || !steamapicontext->SteamFriends()) + return; + + const char *pszStatus = NULL; + + if (g_Metadata[RPC_STEAM] != NULL) + { + C_MapbaseMetadata *pMetadata = static_cast(g_Metadata[RPC_STEAM].Get()); + + if (pMetadata->m_iszRPCDetails[0] != NULL) + pszStatus = pMetadata->m_iszRPCDetails; + else if (pMetadata->m_iszRPCState[0] != NULL) + pszStatus = pMetadata->m_iszRPCState; + else + { + if (engine->IsLevelMainMenuBackground()) + pszStatus = VarArgs("Main Menu (%s)", pMapName ? pMapName : "N/A"); + else + pszStatus = VarArgs("Map: %s", pMapName ? pMapName : "N/A"); + } + } + else + { + switch (iType) + { + case RPCSTATE_INIT: + case RPCSTATE_LEVEL_SHUTDOWN: + { + pszStatus = "Main Menu"; + } break; + case RPCSTATE_LEVEL_INIT: + default: + { + // Say we're in the main menu if it's a background map + if (engine->IsLevelMainMenuBackground()) + { + pszStatus = VarArgs("Main Menu (%s)", pMapName ? pMapName : "N/A"); + } + else + { + pszStatus = VarArgs("Map: %s", pMapName ? pMapName : "N/A"); + } + } break; + } + } + + DevMsg( "Updating Steam\n" ); + + if (pszStatus) + { + steamapicontext->SteamFriends()->SetRichPresence( "gamestatus", pszStatus ); + steamapicontext->SteamFriends()->SetRichPresence( "steam_display", "#SteamRPC_Status" ); + + if (gpGlobals->maxClients > 1) + { + // Players in server + const CSteamID *serverID = serverengine->GetGameServerSteamID(); + if (serverID) + { + char szGroupID[32]; + Q_snprintf(szGroupID, sizeof(szGroupID), "%i", serverID->GetAccountID()); + + char szGroupSize[8]; + Q_snprintf(szGroupSize, sizeof(szGroupSize), "%i", MapbaseRPC_GetPlayerCount()); + + steamapicontext->SteamFriends()->SetRichPresence( "steam_player_group", szGroupID ); + steamapicontext->SteamFriends()->SetRichPresence( "steam_player_group_size", szGroupSize ); + } + else + { + DevWarning("Steam RPC cannot update player count (no server ID)\n"); + } + } + } +} +#endif + +#ifdef DISCORD_RPC +void MapbaseRPC_GetDiscordMapInfo( char *pDetails, size_t iSize, const char *pMapName ) +{ + if (!pMapName) + pMapName = "N/A"; + + // Say we're in the main menu if it's a background map + if (engine->IsLevelMainMenuBackground()) + { + Q_snprintf( pDetails, iSize, "Main Menu (%s)", pMapName ); + } + else + { + // Show the chapter title first + const char *szChapterTitle = NULL; + + C_World *pWorld = GetClientWorldEntity(); + if ( pWorld && pWorld->m_iszChapterTitle[0] != '\0' ) + { + szChapterTitle = g_pVGuiLocalize->FindAsUTF8( pWorld->m_iszChapterTitle ); + if (!szChapterTitle || szChapterTitle[0] == '\0') + szChapterTitle = pWorld->m_iszChapterTitle; + } + + if (szChapterTitle) + { + Q_snprintf( pDetails, iSize, "%s (%s)", szChapterTitle, pMapName ); + } + else + { + Q_snprintf( pDetails, iSize, "%s", pMapName ); + } + } +} + +void MapbaseRPC_GetDiscordParameters( DiscordRichPresence &discordPresence, int iType, const char *pMapName ) +{ + static char details[128]; + static char state[128]; + + details[0] = '\0'; + state[0] = '\0'; + + if (g_Metadata[RPC_DISCORD] != NULL) + { + C_MapbaseMetadata *pMetadata = static_cast(g_Metadata[RPC_DISCORD].Get()); + + if (pMetadata->m_iszRPCState[0] != NULL) + Q_strncpy( state, pMetadata->m_iszRPCState, sizeof(state) ); + else + Q_strncpy( state, g_iszGameName, sizeof(state) ); + + if (pMetadata->m_iszRPCDetails[0] != NULL) + Q_strncpy( details, pMetadata->m_iszRPCDetails, sizeof(details) ); + else + { + MapbaseRPC_GetDiscordMapInfo( details, sizeof(details), pMapName ); + } + } + else + { + Q_strncpy( state, g_iszGameName, sizeof(state) ); + + switch (iType) + { + case RPCSTATE_INIT: + case RPCSTATE_LEVEL_SHUTDOWN: + { + Q_strncpy( details, "Main Menu", sizeof(details) ); + } break; + case RPCSTATE_LEVEL_INIT: + default: + { + MapbaseRPC_GetDiscordMapInfo( details, sizeof(details), pMapName ); + } break; + } + } + + if (gpGlobals->maxClients > 1) + { + Q_snprintf( details, sizeof(details), "%s (%i/%i)", details, MapbaseRPC_GetPlayerCount(), gpGlobals->maxClients ); + } + + if (state[0] != '\0') + discordPresence.state = state; + if (details[0] != '\0') + discordPresence.details = details; + + // Generic Mapbase logo. Specific to the Mapbase Discord application. + discordPresence.smallImageKey = "mb_logo_general"; + discordPresence.smallImageText = "Mapbase"; + + discordPresence.largeImageKey = cl_discord_largeimage.GetString(); + discordPresence.largeImageText = cl_discord_largeimage_text.GetString(); +} + +void MapbaseRPC_UpdateDiscord( int iType, const char *pMapName ) +{ + DiscordRichPresence discordPresence; + memset(&discordPresence, 0, sizeof(discordPresence)); + + DevMsg("Updating Discord\n"); + + discordPresence.startTimestamp = startTimestamp; + + MapbaseRPC_GetDiscordParameters( discordPresence, iType, pMapName ); + + Discord_UpdatePresence(&discordPresence); +} + +void MapbaseRPC_CVarToggle( IConVar *var, const char *pOldString, float flOldValue ) +{ + if (flOldValue <= 0 && mapbase_rpc_enabled.GetInt() > 0) + { + // Turning on + MapbaseRPC_Init(); + MapbaseRPC_Update( g_MapName != NULL ? RPCSTATE_UPDATE : RPCSTATE_INIT, g_MapName ); + } + else if (mapbase_rpc_enabled.GetInt() <= 0) + { + // Turning off + MapbaseRPC_Shutdown(); + } +} +#endif + +#endif diff --git a/src/game/shared/mapbase/mapbase_shared.cpp b/src/game/shared/mapbase/mapbase_shared.cpp new file mode 100644 index 00000000..85e8dee3 --- /dev/null +++ b/src/game/shared/mapbase/mapbase_shared.cpp @@ -0,0 +1,837 @@ +//========= Mapbase - https://github.com/mapbase-source/source-sdk-2013 ============// +// +// Purpose: Carries the Mapbase CAutoGameSystem that loads manifest among other things. +// Also includes code that does not fit anywhere else. +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" + +#include "tier0/icommandline.h" +#include "tier1/mapbase_con_groups.h" +#include "igamesystem.h" +#include "filesystem.h" +#include +#include +#include "saverestore_utlvector.h" +#include "props_shared.h" +#include "utlbuffer.h" +#include "usermessages.h" +#ifdef CLIENT_DLL +#include "hud_closecaption.h" +#include "panelmetaclassmgr.h" +#include "c_soundscape.h" +#include "hud_macros.h" +#include "clientmode_shared.h" +#else +#include "soundscape_system.h" +#include "AI_ResponseSystem.h" +#include "mapbase/SystemConvarMod.h" +#include "gameinterface.h" +#endif + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +#define GENERIC_MANIFEST_FILE "scripts/mapbase_default_manifest.txt" + +#ifdef CLIENT_DLL +#define AUTOLOADED_MANIFEST_FILE VarArgs("maps/%s_manifest.txt", g_MapName) +#else +#define AUTOLOADED_MANIFEST_FILE UTIL_VarArgs("maps/%s_manifest.txt", g_MapName) +#endif + +const char *g_MapName; + +extern ISoundEmitterSystemBase *soundemitterbase; + +ConVar mapbase_load_default_manifest("mapbase_load_default_manifest", "1", FCVAR_ARCHIVE, "Should we automatically load our default manifest file? (\"maps/%mapname%_manifest.txt\")"); + +#ifdef GAME_DLL +// This constant should change with each Mapbase update +ConVar mapbase_version( "mapbase_version", MAPBASE_VERSION, FCVAR_NONE, "The version of Mapbase currently being used in this mod's server.dll" ); + +ConVar mapbase_flush_talker("mapbase_flush_talker", "1", FCVAR_NONE, "Normally, when a map with custom talker files is unloaded, the response system resets to rid itself of the custom file(s). Turn this convar off to prevent that from happening."); + +extern void MapbaseGameLog_Init(); + +extern void ParseCustomActbusyFile(const char *file); + +extern bool LoadResponseSystemFile(const char *scriptfile); +extern void ReloadResponseSystem(); + +// Reloads the response system when the map changes to avoid custom talker leaking +static bool g_bMapContainsCustomTalker; +#else +// This constant should change with each Mapbase update +ConVar mapbase_version_client( "mapbase_version_client", MAPBASE_VERSION, FCVAR_NONE, "The version of Mapbase currently being used in this mod's client.dll" ); + +// This is from the vgui_controls library +extern vgui::HScheme g_iCustomClientSchemeOverride; + +extern bool g_bUsingCustomHudAnimations; +bool g_bUsingCustomHudLayout = false; +#endif + +extern void AddSurfacepropFile( const char *pFileName, IPhysicsSurfaceProps *pProps, IFileSystem *pFileSystem ); + +// Indicates this is a core Mapbase mod and not a mod using its code. +static bool g_bMapbaseCore; + +// The game's name found in gameinfo.txt. Mostly used for Discord RPC. +char g_iszGameName[128]; + +#ifdef GAME_DLL +// Default player configuration +char g_szDefaultPlayerModel[MAX_PATH]; +bool g_bDefaultPlayerDrawExternally; + +char g_szDefaultHandsModel[MAX_PATH]; +int g_iDefaultHandsSkin; +int g_iDefaultHandsBody; +#endif + +enum +{ + MANIFEST_SOUNDSCRIPTS, + //MANIFEST_PROPDATA, + //MANIFEST_SOUNDSCAPES, + MANIFEST_LOCALIZATION, + MANIFEST_SURFACEPROPS, +#ifdef CLIENT_DLL + MANIFEST_CLOSECAPTION, + MANIFEST_VGUI, + MANIFEST_CLIENTSCHEME, + MANIFEST_HUDANIMATIONS, + MANIFEST_HUDLAYOUT, +#else + MANIFEST_TALKER, + //MANIFEST_SENTENCES, + MANIFEST_ACTBUSY, +#endif +#ifdef MAPBASE_VSCRIPT + MANIFEST_VSCRIPT, +#endif + + // Must always be kept below + MANIFEST_NUM_TYPES, +}; + +struct ManifestType_t +{ + ManifestType_t( const char *_string, const char *cvarname, const char *cvardesc ) : cvar( cvarname, "1", FCVAR_ARCHIVE, cvardesc ) + { + string = _string; + } + + //int type; + const char *string; + ConVar cvar; +}; + +#define DECLARE_MANIFEST_TYPE(name, cvar, desc) { #name, ConVar(#cvar, "1", FCVAR_ARCHIVE, #desc) } + +// KEEP THS IN SYNC WITH THE ENUM! +static const ManifestType_t gm_szManifestFileStrings[MANIFEST_NUM_TYPES] = { + { "soundscripts", "mapbase_load_soundscripts", "Should we load map-specific soundscripts? e.g. \"maps/_level_sounds.txt\"" }, + //{ "propdata", "mapbase_load_propdata", "Should we load map-specific soundscripts? e.g. \"maps/_level_sounds.txt\"" }, + //{ "soundscapes", "mapbase_load_soundscapes", "Should we load map-specific soundscapes? e.g. \"maps/_soundscapes.txt\"" }, + { "localization", "mapbase_load_localization", "Should we load map-specific localized text files? e.g. \"maps/_english.txt\"" }, + { "surfaceprops", "mapbase_load_surfaceprops", "Should we load map-specific surfaceproperties files? e.g. \"maps/_surfaceproperties.txt\"" }, +#ifdef CLIENT_DLL + { "closecaption", "mapbase_load_closecaption", "Should we load map-specific closed captioning? e.g. \"maps/_closecaption_english.txt\" and \"maps/_closecaption_english.dat\"" }, + { "vgui", "mapbase_load_vgui", "Should we load map-specific VGUI screens? e.g. \"maps/_screens.txt\"" }, + { "clientscheme", "mapbase_load_clientscheme", "Should we load map-specific ClientScheme.res overrides? e.g. \"maps/_clientscheme.res\"" }, + { "hudanimations", "mapbase_load_hudanimations", "Should we load map-specific HUD animation overrides? e.g. \"maps/_hudanimations.txt\"" }, + { "hudlayout", "mapbase_load_hudlayout", "Should we load map-specific HUD layout overrides? e.g. \"maps/_hudlayout.res\"" }, +#else + { "talker", "mapbase_load_talker", "Should we load map-specific talker files? e.g. \"maps/_talker.txt\"" }, + //{ "sentences", "mapbase_load_sentences", "Should we load map-specific sentences? e.g. \"maps/_sentences.txt\"" }, + { "actbusy", "mapbase_load_actbusy", "Should we load map-specific actbusy files? e.g. \"maps/_actbusy.txt\"" }, +#endif +#ifdef MAPBASE_VSCRIPT + { "vscript", "mapbase_load_vscript", "Should we load map-specific VScript map spawn files? e.g. \"maps/_mapspawn.nut\"" }, +#endif +}; + +//----------------------------------------------------------------------------- +// Purpose: System used to load map-specific files, etc. +//----------------------------------------------------------------------------- +class CMapbaseSystem : public CAutoGameSystem +{ +public: + DECLARE_DATADESC(); + + CMapbaseSystem() : CAutoGameSystem( "CMapbaseSystem" ) + { + } + + inline bool GetGameInfoKeyValues(KeyValues *pKeyValues) + { + return pKeyValues->LoadFromFile( filesystem, "gameinfo.txt", "MOD" ); + } + + virtual bool Init() + { + InitConsoleGroups( g_pFullFileSystem ); + + // Checks gameinfo.txt for additional command line options + KeyValues *gameinfo = new KeyValues("GameInfo"); + if (GetGameInfoKeyValues(gameinfo)) + { + KeyValues *pCommandLineList = gameinfo->FindKey("CommandLine", false); + if (pCommandLineList) + { + for (KeyValues *pKey = pCommandLineList->GetFirstSubKey(); pKey; pKey = pKey->GetNextKey()) + { + CommandLine()->AppendParm( pKey->GetName(), pKey->GetString() ); + } + } + } + gameinfo->deleteThis(); + +#ifdef CLIENT_DLL + InitializeRTs(); +#endif + + return true; + } + + void RefreshCustomTalker() + { +#ifdef GAME_DLL + if (g_bMapContainsCustomTalker && mapbase_flush_talker.GetBool()) + { + CGMsg( 1, CON_GROUP_MAPBASE_MISC, "Mapbase: Reloading response system to flush custom talker\n" ); + ReloadResponseSystem(); + g_bMapContainsCustomTalker = false; + } +#endif + } + + virtual void LevelInitPreEntity() + { +#ifdef GAME_DLL + CGMsg( 0, CON_GROUP_MAPBASE_MISC, "Mapbase system loaded\n" ); +#endif + + // Checks gameinfo.txt for Mapbase-specific options + KeyValues *gameinfo = new KeyValues("GameInfo"); + if (GetGameInfoKeyValues(gameinfo)) + { + // Indicates this is a core Mapbase mod and not a mod using its code + g_bMapbaseCore = gameinfo->GetBool("mapbase_core", false); + + if (!gameinfo->GetBool("hide_mod_name", false)) + { + // Store the game's name + const char *pszGameName = gameinfo->GetString("game_rpc", NULL); + if (pszGameName == NULL) + pszGameName = gameinfo->GetString("game"); + + Q_strncpy(g_iszGameName, pszGameName, sizeof(g_iszGameName)); + } + +#ifdef GAME_DLL + Q_strncpy( g_szDefaultPlayerModel, gameinfo->GetString( "player_default_model", "models/player.mdl" ), sizeof( g_szDefaultPlayerModel ) ); + g_bDefaultPlayerDrawExternally = gameinfo->GetBool( "player_default_draw_externally", false ); + + Q_strncpy( g_szDefaultHandsModel, gameinfo->GetString( "player_default_hands", "models/weapons/v_hands.mdl" ), sizeof( g_szDefaultHandsModel ) ); + g_iDefaultHandsSkin = gameinfo->GetInt( "player_default_hands_skin", 0 ); + g_iDefaultHandsBody = gameinfo->GetInt( "player_default_hands_body", 0 ); +#endif + } + gameinfo->deleteThis(); + + RefreshMapName(); + + // Shared Mapbase scripts to avoid overwriting mod files + g_pVGuiLocalize->AddFile( "resource/mapbase_%language%.txt" ); +#ifdef CLIENT_DLL + PanelMetaClassMgr()->LoadMetaClassDefinitionFile( "scripts/vgui_screens_mapbase.txt" ); +#endif + } + + virtual void OnRestore() + { + RefreshMapName(); + } + + virtual void LevelInitPostEntity() + { + // Check for a generic "mapname_manifest.txt" file and load it. + if (filesystem->FileExists( AUTOLOADED_MANIFEST_FILE, "GAME" )) + { + AddManifestFile( AUTOLOADED_MANIFEST_FILE ); + } + else + { + // Load the generic script instead. + ParseGenericManifest(); + } + +#ifdef GAME_DLL + MapbaseGameLog_Init(); +#endif + } + + virtual void LevelShutdownPreEntity() + { + // How would we make sure they don't last between maps? + // TODO: Investigate ReloadLocalizationFiles() + //g_pVGuiLocalize->RemoveAll(); + //g_pVGuiLocalize->ReloadLocalizationFiles(); + } + + virtual void LevelShutdownPostEntity() + { + g_MapName = NULL; + + RefreshCustomTalker(); + +#ifdef CLIENT_DLL + CHudCloseCaption *hudCloseCaption = GET_HUDELEMENT( CHudCloseCaption ); + FOR_EACH_VEC( m_CloseCaptionFileNames, i ) + { + hudCloseCaption->RemoveCaptionDictionary( m_CloseCaptionFileNames[i] ); + } + m_CloseCaptionFileNames.RemoveAll(); + + if (g_iCustomClientSchemeOverride != 0 || g_bUsingCustomHudAnimations || g_bUsingCustomHudLayout) + { + CGMsg( 1, CON_GROUP_MAPBASE_MISC, "Mapbase: Reloading client mode and viewport scheme\n" ); + + // TODO: We currently have no way of actually cleaning up custom schemes upon level unload. + // That may or may not be sustainable if there's a ton of custom schemes loaded at once + g_iCustomClientSchemeOverride = 0; + + g_bUsingCustomHudAnimations = false; + g_bUsingCustomHudLayout = false; + + // Reload scheme + ClientModeShared *mode = ( ClientModeShared * )GetClientModeNormal(); + if ( mode ) + { + mode->ReloadScheme( true ); + + // We need to reload default values, so load a special "hudlayout_mapbase.res" file that only contains + // default Mapbase definitions identical to the defaults in the code + CBaseViewport *pViewport = dynamic_cast(g_pClientMode->GetViewport()); + if (pViewport) + { + KeyValuesAD pConditions( "conditions" ); + g_pClientMode->ComputeVguiResConditions( pConditions ); + + // reload the .res file from disk + pViewport->LoadControlSettings( "scripts/hudlayout_mapbase.res", NULL, NULL, pConditions ); + } + } + } +#endif + } + + bool RefreshMapName() + { +#ifdef GAME_DLL + const char *pszMapName = STRING(gpGlobals->mapname); +#else + //char mapname[128]; + //Q_StripExtension(MapName(), mapname, sizeof(mapname)); + const char *pszMapName = MapName(); +#endif + + if (g_MapName == NULL || !FStrEq(pszMapName, g_MapName)) + { + g_MapName = pszMapName; + return true; + } + + return false; + } + +#ifdef CLIENT_DLL + //----------------------------------------------------------------------------- + // Initialize custom RT textures if necessary + //----------------------------------------------------------------------------- + void InitializeRTs() + { + if (!m_bInitializedRTs) + { + int iNumCameras = CommandLine()->ParmValue( "-numcameratextures", 3 ); + + materials->BeginRenderTargetAllocation(); + + for (int i = 0; i < iNumCameras; i++) + { + char szName[32]; + Q_snprintf( szName, sizeof(szName), "_rt_Camera%i", i ); + + int iRefIndex = m_CameraTextures.AddToTail(); + + //m_CameraTextures[iRefIndex].InitRenderTarget( + // 256, 256, RT_SIZE_DEFAULT, + // g_pMaterialSystem->GetBackBufferFormat(), + // MATERIAL_RT_DEPTH_SHARED, true, szName ); + + m_CameraTextures[iRefIndex].Init( g_pMaterialSystem->CreateNamedRenderTargetTextureEx2( + szName, + 256, 256, RT_SIZE_DEFAULT, + g_pMaterialSystem->GetBackBufferFormat(), + MATERIAL_RT_DEPTH_SHARED, + 0, + CREATERENDERTARGETFLAGS_HDR ) ); + } + + materials->EndRenderTargetAllocation(); + + m_bInitializedRTs = true; + } + } + + void Shutdown() + { + if (m_bInitializedRTs) + { + for (int i = 0; i < m_CameraTextures.Count(); i++) + { + m_CameraTextures[i].Shutdown(); + } + m_bInitializedRTs = false; + } + } +#endif + + // Get a generic, hardcoded manifest with hardcoded names. + void ParseGenericManifest() + { + if (!mapbase_load_default_manifest.GetBool()) + return; + + KeyValues *pKV = new KeyValues("DefaultManifest"); + pKV->LoadFromFile(filesystem, GENERIC_MANIFEST_FILE); + + AddManifestFile(pKV, g_MapName/*, true*/); + + pKV->deleteThis(); + } + + void AddManifestFile( const char *file ) + { + KeyValues *pKV = new KeyValues(file); + if ( !pKV->LoadFromFile( filesystem, file ) ) + { + Warning("Mapbase Manifest: \"%s\" is unreadable or missing (can't load KV, check for syntax errors)\n", file); + pKV->deleteThis(); + return; + } + + CGMsg( 1, CON_GROUP_MAPBASE_MISC, "===== Mapbase Manifest: Loading manifest file %s =====\n", file ); + + AddManifestFile(pKV, g_MapName, false); + + CGMsg( 1, CON_GROUP_MAPBASE_MISC, "==============================================================================\n" ); + + pKV->deleteThis(); + } + + void LoadFromValue( const char *value, int type, bool bDontWarn ) + { + if (type != MANIFEST_VSCRIPT && !filesystem->FileExists(value, "MOD")) + { + if (!bDontWarn) + { + Warning("Mapbase Manifest: WARNING! \"%s\" does not exist!\n", value); + } + return; + } + + switch (type) + { + case MANIFEST_SOUNDSCRIPTS: { soundemitterbase->AddSoundOverrides(value); } break; + //case MANIFEST_PROPDATA: { g_PropDataSystem.ParsePropDataFile(value); } break; + case MANIFEST_LOCALIZATION: { g_pVGuiLocalize->AddFile( value, "MOD", true ); } break; + case MANIFEST_SURFACEPROPS: { AddSurfacepropFile( value, physprops, filesystem ); } break; +#ifdef CLIENT_DLL + case MANIFEST_CLOSECAPTION: { ManifestLoadCustomCloseCaption( value ); } break; + case MANIFEST_VGUI: { PanelMetaClassMgr()->LoadMetaClassDefinitionFile( value ); } break; + case MANIFEST_CLIENTSCHEME: { ManifestLoadCustomScheme( value ); } break; + case MANIFEST_HUDANIMATIONS: { ManifestLoadCustomHudAnimations( value ); } break; + case MANIFEST_HUDLAYOUT: { ManifestLoadCustomHudLayout( value ); } break; + //case MANIFEST_SOUNDSCAPES: { Soundscape_AddFile(value); } break; +#else + case MANIFEST_TALKER: { + g_bMapContainsCustomTalker = true; + LoadResponseSystemFile(value); //PrecacheCustomResponseSystem( value ); + } break; + //case MANIFEST_SOUNDSCAPES: { g_SoundscapeSystem.AddSoundscapeFile(value); } break; + //case MANIFEST_SENTENCES: { engine->PrecacheSentenceFile(value); } break; + case MANIFEST_ACTBUSY: { ParseCustomActbusyFile(value); } break; +#endif +#ifdef MAPBASE_VSCRIPT + case MANIFEST_VSCRIPT: { VScriptRunScript(value, false); } break; +#endif + } + } + + // This doesn't call deleteThis()! + void AddManifestFile(KeyValues *pKV, const char *pszMapName, bool bDontWarn = false) + { + char value[MAX_PATH]; + const char *name; + for (KeyValues *pKey = pKV->GetFirstSubKey(); pKey; pKey = pKey->GetNextKey()) + { + value[0] = '\0'; + name = pKey->GetName(); + + // Parse %mapname%, etc. + bool inparam = false; + CUtlStringList outStrings; + V_SplitString( pKey->GetString(), "%", outStrings ); + FOR_EACH_VEC( outStrings, i ) + { + if (inparam) + { + if (FStrEq( outStrings[i], "mapname" )) + { + Q_strncat( value, pszMapName, sizeof( value ) ); + } + else if (FStrEq( outStrings[i], "language" )) + { +#ifdef CLIENT_DLL + char uilanguage[64]; + engine->GetUILanguage(uilanguage, sizeof(uilanguage)); + Q_strncat( value, uilanguage, sizeof( value ) ); +#else + // Give up, use English + Q_strncat( value, "english", sizeof( value ) ); +#endif + } + } + else + { + Q_strncat( value, outStrings[i], sizeof( value ) ); + } + + inparam = !inparam; + } + + outStrings.PurgeAndDeleteElements(); + + if (FStrEq(name, "NoErrors")) + { + bDontWarn = pKey->GetBool(); + } + + for (int i = 0; i < MANIFEST_NUM_TYPES; i++) + { + if (FStrEq(name, gm_szManifestFileStrings[i].string)) + { + if (gm_szManifestFileStrings[i].cvar.GetBool()) + { + LoadFromValue(value, i, bDontWarn); + } + break; + } + } + } + } + +private: + +#ifdef CLIENT_DLL + void ManifestLoadCustomCloseCaption( const char *pszFile ) + { + if (GET_HUDELEMENT( CHudCloseCaption )) + (GET_HUDELEMENT( CHudCloseCaption ))->AddCustomCaptionFile( pszFile, m_CloseCaptionFileNames ); + } + + // Custom scheme loading + void ManifestLoadCustomScheme( const char *pszFile ) + { + g_iCustomClientSchemeOverride = vgui::scheme()->LoadSchemeFromFile( pszFile, "CustomClientScheme" ); + + // Reload scheme + ClientModeShared *mode = ( ClientModeShared * )GetClientModeNormal(); + if ( mode ) + { + mode->ReloadScheme( true ); + } + } + + void ManifestLoadCustomHudAnimations( const char *pszFile ) + { + CBaseViewport *pViewport = dynamic_cast(g_pClientMode->GetViewport()); + if (pViewport) + { + g_bUsingCustomHudAnimations = true; + if (!pViewport->LoadCustomHudAnimations( pszFile )) + { + g_bUsingCustomHudAnimations = false; + CGWarning( 0, CON_GROUP_MAPBASE_MISC, "Custom HUD animations file \"%s\" failed to load\n", pszFile ); + pViewport->ReloadHudAnimations(); + } + else + { + CGMsg( 1, CON_GROUP_MAPBASE_MISC, "Loaded custom HUD animations file \"%s\"\n", pszFile );; + } + } + } + + void ManifestLoadCustomHudLayout( const char *pszFile ) + { + CBaseViewport *pViewport = dynamic_cast(g_pClientMode->GetViewport()); + if (pViewport) + { + g_bUsingCustomHudLayout = true; + + KeyValuesAD pConditions( "conditions" ); + g_pClientMode->ComputeVguiResConditions( pConditions ); + + // reload the .res file from disk + pViewport->LoadControlSettings( pszFile, NULL, NULL, pConditions ); + + CGMsg( 1, CON_GROUP_MAPBASE_MISC, "Loaded custom HUD layout file \"%s\"\n", pszFile );; + } + } +#endif + +public: + + void LoadCustomSoundscriptFile( const char *szScript ) { LoadFromValue( szScript, MANIFEST_SOUNDSCRIPTS, false ); } + void LoadCustomLocalizationFile( const char *szScript ) { LoadFromValue( szScript, MANIFEST_LOCALIZATION, false ); } + void LoadCustomSurfacePropsFile( const char *szScript ) { LoadFromValue( szScript, MANIFEST_SURFACEPROPS, false ); } +#ifdef CLIENT_DLL + void LoadCustomCloseCaptionFile( const char *szScript ) { LoadFromValue( szScript, MANIFEST_CLOSECAPTION, false ); } + void LoadCustomVGUIFile( const char *szScript ) { LoadFromValue( szScript, MANIFEST_VGUI, false ); } + void LoadCustomClientSchemeFile( const char *szScript ) { LoadFromValue( szScript, MANIFEST_CLIENTSCHEME, false ); } + void LoadCustomHUDAnimationsFile( const char *szScript ) { LoadFromValue( szScript, MANIFEST_HUDANIMATIONS, false ); } + void LoadCustomHUDLayoutFile( const char *szScript ) { LoadFromValue( szScript, MANIFEST_HUDLAYOUT, false ); } +#else + void LoadCustomTalkerFile( const char *szScript ) { LoadFromValue( szScript, MANIFEST_TALKER, false ); } + void LoadCustomActbusyFile( const char *szScript ) { LoadFromValue( szScript, MANIFEST_ACTBUSY, false ); } +#endif + + const char *GetModName() { return g_iszGameName; } + bool IsCoreMapbase() { return g_bMapbaseCore; } + +#ifdef MAPBASE_VSCRIPT + void ScriptAddManifestFile( const char *szScript ) { AddManifestFile( szScript ); } + + virtual void RegisterVScript() + { + g_pScriptVM->RegisterInstance( this, "Mapbase" ); + } +#endif + +private: + +#ifdef CLIENT_DLL + bool m_bInitializedRTs = false; + CUtlVector m_CameraTextures; + + CUtlVector m_CloseCaptionFileNames; +#endif +}; + +CMapbaseSystem g_MapbaseSystem; + +BEGIN_DATADESC_NO_BASE( CMapbaseSystem ) + + //DEFINE_UTLVECTOR( m_StoredManifestFiles, FIELD_STRING ), + +END_DATADESC() + +#ifdef MAPBASE_VSCRIPT +BEGIN_SCRIPTDESC_ROOT( CMapbaseSystem, SCRIPT_SINGLETON "All-purpose Mapbase system primarily used for map-specific files." ) + + DEFINE_SCRIPTFUNC_NAMED( ScriptAddManifestFile, "AddManifestFile", "Loads a manifest file." ) + DEFINE_SCRIPTFUNC( LoadCustomSoundscriptFile, "Loads a custom soundscript file." ) + DEFINE_SCRIPTFUNC( LoadCustomLocalizationFile, "Loads a custom localization file." ) + DEFINE_SCRIPTFUNC( LoadCustomSurfacePropsFile, "Loads a custom surface properties file." ) +#ifdef CLIENT_DLL + DEFINE_SCRIPTFUNC( LoadCustomCloseCaptionFile, "Loads a custom closed captions file." ) + DEFINE_SCRIPTFUNC( LoadCustomVGUIFile, "Loads a custom VGUI definitions file." ) + DEFINE_SCRIPTFUNC( LoadCustomClientSchemeFile, "Loads a custom ClientScheme.res override file." ) + DEFINE_SCRIPTFUNC( LoadCustomHUDAnimationsFile, "Loads a custom HUD animations override file." ) + DEFINE_SCRIPTFUNC( LoadCustomHUDLayoutFile, "Loads a custom HUD layout override file." ) +#else + DEFINE_SCRIPTFUNC( LoadCustomTalkerFile, "Loads a custom talker file." ) + DEFINE_SCRIPTFUNC( LoadCustomActbusyFile, "Loads a custom actbusy file." ) +#endif + + DEFINE_SCRIPTFUNC( GetModName, "Gets the name of the mod. This is the name which shows up on Steam, RPC, etc." ) + DEFINE_SCRIPTFUNC( IsCoreMapbase, "Indicates whether this is one of the original Mapbase mods or just a separate mod using its code." ) + + // Legacy + DEFINE_SCRIPTFUNC_NAMED( LoadCustomSoundscriptFile, "LoadSoundscriptFile", SCRIPT_HIDE ) +#ifndef CLIENT_DLL + DEFINE_SCRIPTFUNC_NAMED( LoadCustomTalkerFile, "LoadTalkerFile", SCRIPT_HIDE ) + DEFINE_SCRIPTFUNC_NAMED( LoadCustomActbusyFile, "LoadActbusyFile", SCRIPT_HIDE ) +#endif + +END_SCRIPTDESC(); +#endif + +static void CC_Mapbase_LoadManifestFile( const CCommand& args ) +{ + g_MapbaseSystem.AddManifestFile(args[1]); +} + +#ifdef CLIENT_DLL +static ConCommand mapbase_loadmanifestfile("mapbase_loadmanifestfile_client", CC_Mapbase_LoadManifestFile, "Loads a Mapbase manifest file on the client. If you don't want this to be saved and found when reloaded, type a '1' after the file path." ); +#else +static ConCommand mapbase_loadmanifestfile("mapbase_loadmanifestfile", CC_Mapbase_LoadManifestFile, "Loads a Mapbase manifest file. If you don't want this to be saved and found when reloaded, type a '1' after the file path." ); +#endif + +#ifdef GAME_DLL +static CUtlVector g_MapbaseChapterMaps; +static CUtlVector g_MapbaseChapterList; +CUtlVector *Mapbase_GetChapterMaps() +{ + if (g_MapbaseChapterMaps.Count() == 0) + { + // Check the chapter list + KeyValues *chapterlist = new KeyValues("ChapterList"); + if (chapterlist->LoadFromFile(filesystem, "scripts/chapters.txt", "MOD")) + { + KeyValues *pKey = chapterlist->GetFirstSubKey(); + if (pKey) + { + if (Q_stricmp( pKey->GetName(), "Chapters" ) == 0) + { + for (KeyValues *pChapters = pKey->GetFirstSubKey(); pChapters; pChapters = pChapters->GetNextKey()) + { + int index = g_MapbaseChapterList.AddToTail(); + g_MapbaseChapterList[index].iChapter = atoi(pChapters->GetName()); + Q_strncpy(g_MapbaseChapterList[index].pChapterName, pChapters->GetString(), sizeof(g_MapbaseChapterList[index])); + } + } + + for (pKey = pKey->GetNextKey(); pKey; pKey = pKey->GetNextKey()) + { + int index = g_MapbaseChapterMaps.AddToTail(); + Q_strncpy(g_MapbaseChapterMaps[index].pBSPName, pKey->GetName(), sizeof(g_MapbaseChapterMaps[index].pBSPName)); + Q_strncpy(g_MapbaseChapterMaps[index].pTitleName, pKey->GetString(), sizeof(g_MapbaseChapterMaps[index].pTitleName)); + + //comment.pBSPName = pKey->GetName(); + //comment.pTitleName = pKey->GetString(); + } + } + } + chapterlist->deleteThis(); + } + + return &g_MapbaseChapterMaps; +} + +CUtlVector *Mapbase_GetChapterList() +{ + return &g_MapbaseChapterList; +} + +int Mapbase_GetChapterCount() +{ + return g_MapbaseChapterList.Count(); +} + +ThreeState_t Flashlight_GetLegacyVersionKey() +{ + KeyValues *gameinfo = new KeyValues( "GameInfo" ); + if (g_MapbaseSystem.GetGameInfoKeyValues( gameinfo )) + { + // -1 = default + int iUseLegacyFlashlight = gameinfo->GetInt( "use_legacy_flashlight", -1 ); + if (iUseLegacyFlashlight > -1) + return iUseLegacyFlashlight != 0 ? TRS_TRUE : TRS_FALSE; + } + gameinfo->deleteThis(); + + return TRS_NONE; +} + +#define SF_MANIFEST_START_ACTIVATED (1 << 0) + +class CMapbaseManifestEntity : public CPointEntity +{ +public: + DECLARE_DATADESC(); + + DECLARE_CLASS( CMapbaseManifestEntity, CPointEntity ); + + void Spawn( void ) + { + BaseClass::Spawn(); + if (HasSpawnFlags(SF_MANIFEST_START_ACTIVATED)) + { + LoadManifestFile(); + } + } + + void LoadManifestFile( void ) + { + const char *scriptfile = STRING(m_target); + if ( filesystem->FileExists( scriptfile, "MOD" ) ) + { + CGMsg(0, CON_GROUP_MAPBASE_MISC, "Mapbase: Adding manifest file \"%s\"\n", scriptfile); + g_MapbaseSystem.AddManifestFile(scriptfile); + } + else + { + Warning("Mapbase: Manifest file \"%s\" does not exist!", scriptfile); + } + } + + void InputActivate(inputdata_t &inputdata) + { + LoadManifestFile(); + } +}; + +LINK_ENTITY_TO_CLASS( mapbase_manifest, CMapbaseManifestEntity ); + +BEGIN_DATADESC( CMapbaseManifestEntity ) + + // Needs to be set up in the Activate methods of derived classes + //DEFINE_CUSTOM_FIELD( m_pInstancedResponseSystem, responseSystemSaveRestoreOps ), + + // Function Pointers + DEFINE_INPUTFUNC( FIELD_VOID, "Activate", InputActivate ), + +END_DATADESC() +#endif + +//----------------------------------------------------------------------------- + +void CV_IncludeNameChanged( IConVar *pConVar, const char *pOldString, float flOldValue ); + +#ifdef CLIENT_DLL +ConVar con_group_include_name_client( "con_group_include_name_client", "0", FCVAR_NONE, "Includes groups when printing on the client.", CV_IncludeNameChanged ); + +void CV_IncludeNameChanged( IConVar *pConVar, const char *pOldString, float flOldValue ) +{ + SetConsoleGroupIncludeNames( con_group_include_name_client.GetBool() ); +} +#else +ConVar con_group_include_name( "con_group_include_name", "0", FCVAR_NONE, "Includes groups when printing.", CV_IncludeNameChanged ); + +void CV_IncludeNameChanged( IConVar *pConVar, const char *pOldString, float flOldValue ) +{ + SetConsoleGroupIncludeNames( con_group_include_name.GetBool() ); +} +#endif + +CON_COMMAND_SHARED( con_group_reload, "Reloads all console groups." ) +{ + InitConsoleGroups( g_pFullFileSystem ); +} + +CON_COMMAND_SHARED( con_group_list, "Prints a list of all console groups." ) +{ + PrintAllConsoleGroups(); +} + +CON_COMMAND_SHARED( con_group_toggle, "Toggles a console group." ) +{ + ToggleConsoleGroups( args.Arg( 1 ) ); +} diff --git a/src/game/shared/mapbase/mapbase_usermessages.cpp b/src/game/shared/mapbase/mapbase_usermessages.cpp new file mode 100644 index 00000000..5af946e6 --- /dev/null +++ b/src/game/shared/mapbase/mapbase_usermessages.cpp @@ -0,0 +1,35 @@ +//========= Mapbase - https://github.com/mapbase-source/source-sdk-2013 ============// +// +// Purpose: Mapbase-specific user messages. +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" + +#include "usermessages.h" +#ifdef CLIENT_DLL +#include "hud_macros.h" +#endif + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +#ifdef CLIENT_DLL +void HookMapbaseUserMessages( void ) +{ + // VScript + //HOOK_MESSAGE( ScriptMsg ); // Hooked in CNetMsgScriptHelper +} +#endif + +void RegisterMapbaseUserMessages( void ) +{ + // VScript + usermessages->Register( "ScriptMsg", -1 ); // CNetMsgScriptHelper + +#ifdef CLIENT_DLL + // TODO: Better placement? + HookMapbaseUserMessages(); +#endif +} diff --git a/src/game/shared/mapbase/matchers.cpp b/src/game/shared/mapbase/matchers.cpp new file mode 100644 index 00000000..b8b915f4 --- /dev/null +++ b/src/game/shared/mapbase/matchers.cpp @@ -0,0 +1,54 @@ +//========= Mapbase - https://github.com/mapbase-source/source-sdk-2013 ================= +// +// Purpose: General matching functions for things like wildcards and !=. +// +// $NoKeywords: $ +//============================================================================= + +#include "cbase.h" + +#include "matchers.h" +#include "fmtstr.h" + +#ifdef CLIENT_DLL +// FIXME: There is no clientside equivalent to the RS code +static bool ResponseSystemCompare(const char *criterion, const char *value) { return Matcher_NamesMatch(criterion, value); } +#else +extern bool ResponseSystemCompare(const char *criterion, const char *value); +#endif + +//============================================================================= +// These are the "matchers" that compare with wildcards ("any*" for text starting with "any") +// and operators (<3 for numbers less than 3). +// +// Matcher_Match - Matching function using RS operators and NamesMatch wildcards/regex. +// Matcher_Regex - Uses regex functions from the std library. +// Matcher_NamesMatch - Based on Valve's original NamesMatch function, using wildcards and regex. +// +// AppearsToBeANumber - Response System-based function which checks if the string might be a number. +//============================================================================= + +bool Matcher_Match(const char *pszQuery, const char *szValue) +{ + // I wasn't kidding when I said all this did was hijack response system matching. + return ResponseSystemCompare(pszQuery, szValue); +} + +bool Matcher_Match(const char *pszQuery, int iValue) { return Matcher_Match(pszQuery, CNumStr(iValue)); } +bool Matcher_Match(const char *pszQuery, float flValue) { return Matcher_Match(pszQuery, CNumStr(flValue)); } + +// Matcher_Compare is a deprecated alias originally used when Matcher_Match didn't support wildcards. +/* +bool Matcher_Compare(const char *pszQuery, const char *szValue) +{ + return Matcher_Match(pszQuery, szValue); +#if 0 + // I have to do this so wildcards could test *before* the response system comparison. + // I know it removes the operators twice, but I won't worry about it. + bool match = Matcher_NamesMatch(Matcher_RemoveOperators(pszQuery), szValue); + if (match) + return Matcher_Match(pszQuery, szValue); + return false; +#endif +} +*/ diff --git a/src/game/shared/mapbase/matchers.h b/src/game/shared/mapbase/matchers.h new file mode 100644 index 00000000..7d86ad63 --- /dev/null +++ b/src/game/shared/mapbase/matchers.h @@ -0,0 +1,27 @@ +//========= Mapbase - https://github.com/mapbase-source/source-sdk-2013 ================= +// +// Purpose: General matching functions for things like wildcards and !=. +// +// $NoKeywords: $ +//============================================================================= + +#ifndef MAPBASE_MATCHERS_H +#define MAPBASE_MATCHERS_H +#ifdef _WIN32 +#pragma once +#endif + +#include "tier1/mapbase_matchers_base.h" + +// Compares with != and the like. Basically hijacks the response system matching. +// This also loops back around to Matcher_NamesMatch. +// pszQuery = The value that should have the operator(s) at the beginning. +// szValue = The value tested against the criterion. +bool Matcher_Match( const char *pszQuery, const char *szValue ); +bool Matcher_Match( const char *pszQuery, int iValue ); +bool Matcher_Match( const char *pszQuery, float flValue ); + +// Deprecated; do not use +//static inline bool Matcher_Compare( const char *pszQuery, const char *szValue ) { return Matcher_Match( pszQuery, szValue ); } + +#endif diff --git a/src/game/shared/mapbase/singleplayer_animstate.cpp b/src/game/shared/mapbase/singleplayer_animstate.cpp new file mode 100644 index 00000000..7c8b92c9 --- /dev/null +++ b/src/game/shared/mapbase/singleplayer_animstate.cpp @@ -0,0 +1,669 @@ +//========= Mapbase - https://github.com/mapbase-source/source-sdk-2013 ============// +// +// Purpose: Single Player animation state 'handler'. This utility is used +// to evaluate the pose parameter value based on the direction +// and speed of the player. +// +// ------------------------------------------------------------------------------ +// +// This was originally based on the following VDC article: +// https://developer.valvesoftware.com/wiki/Fixing_the_player_animation_state_(Single_Player) +// +// It has been modified by Blixibon to derive from CBasePlayerAnimState instead and support 9-way blends. +// Much of the work done to make this derive from CBasePlayerAnimState utilized code from the Alien Swarm SDK. +// +//=============================================================================// + +#include "cbase.h" +#include "singleplayer_animstate.h" +#include "tier0/vprof.h" +#include "animation.h" +#include "studio.h" +#include "apparent_velocity_helper.h" +#include "utldict.h" +#include "filesystem.h" +#include "in_buttons.h" +#include "datacache/imdlcache.h" + +extern ConVar mp_facefronttime, mp_feetyawrate, mp_ik; + +ConVar sv_playeranimstate_animtype( "sv_playeranimstate_animtype", "0", FCVAR_NONE, "The leg animation type used by the singleplayer animation state. 9way = 0, 8way = 1, GoldSrc = 2" ); +ConVar sv_playeranimstate_bodyyaw( "sv_playeranimstate_bodyyaw", "45.0", FCVAR_NONE, "The maximum body yaw used by the singleplayer animation state." ); +ConVar sv_playeranimstate_use_aim_sequences( "sv_playeranimstate_use_aim_sequences", "1", FCVAR_NONE, "Allows the singleplayer animation state to use aim sequences." ); + +#define MIN_TURN_ANGLE_REQUIRING_TURN_ANIMATION 15.0f + +#define FIRESEQUENCE_LAYER (AIMSEQUENCE_LAYER+NUM_AIMSEQUENCE_LAYERS) +#define RELOADSEQUENCE_LAYER (FIRESEQUENCE_LAYER + 1) +#define NUM_LAYERS_WANTED (RELOADSEQUENCE_LAYER + 1) + +CSinglePlayerAnimState *CreatePlayerAnimationState( CBasePlayer *pPlayer ) +{ + MDLCACHE_CRITICAL_SECTION(); + + CSinglePlayerAnimState *pState = new CSinglePlayerAnimState( pPlayer ); + + // Setup the movement data. + CModAnimConfig movementData; + movementData.m_LegAnimType = (LegAnimType_t)sv_playeranimstate_animtype.GetInt(); + movementData.m_flMaxBodyYawDegrees = sv_playeranimstate_bodyyaw.GetFloat(); + movementData.m_bUseAimSequences = sv_playeranimstate_use_aim_sequences.GetBool(); + + pState->Init( pPlayer, movementData ); + + return pState; +} + +// Below this many degrees, slow down turning rate linearly +#define FADE_TURN_DEGREES 45.0f +// After this, need to start turning feet +#define MAX_TORSO_ANGLE 90.0f +// Below this amount, don't play a turning animation/perform IK +#define MIN_TURN_ANGLE_REQUIRING_TURN_ANIMATION 15.0f + +//static ConVar tf2_feetyawrunscale( "tf2_feetyawrunscale", "2", FCVAR_REPLICATED, "Multiplier on tf2_feetyawrate to allow turning faster when running." ); +extern ConVar sv_backspeed; +extern ConVar mp_feetyawrate; +extern ConVar mp_facefronttime; +extern ConVar mp_ik; + +CSinglePlayerAnimState::CSinglePlayerAnimState( CBasePlayer *pPlayer ): m_pPlayer( pPlayer ) +{ +}; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +Activity CSinglePlayerAnimState::CalcMainActivity() +{ +#ifdef CLIENT_DLL + return ACT_IDLE; +#else + float speed = GetOuter()->GetAbsVelocity().Length2D(); + + if ( HandleJumping() ) + { + return ACT_HL2MP_JUMP; + } + else + { + Activity idealActivity = ACT_HL2MP_IDLE; + + if ( GetOuter()->GetFlags() & ( FL_FROZEN | FL_ATCONTROLS ) ) + { + speed = 0; + } + else + { + if ( GetOuter()->GetFlags() & FL_DUCKING ) + { + if ( speed > 0 ) + { + idealActivity = ACT_HL2MP_WALK_CROUCH; + } + else + { + idealActivity = ACT_HL2MP_IDLE_CROUCH; + } + } + else + { + if ( speed > 0 ) + { +#if EXPANDED_HL2DM_ACTIVITIES + if ( m_pPlayer->GetButtons() & IN_WALK ) + { + idealActivity = ACT_HL2MP_WALK; + } + else +#endif + { + idealActivity = ACT_HL2MP_RUN; + } + } + else + { + idealActivity = ACT_HL2MP_IDLE; + } + } + } + + return idealActivity; + } + + //return m_pPlayer->GetActivity(); +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CSinglePlayerAnimState::SetPlayerAnimation( PLAYER_ANIM playerAnim ) +{ + if ( playerAnim == PLAYER_ATTACK1 ) + { + m_iFireSequence = SelectWeightedSequence( TranslateActivity( ACT_HL2MP_GESTURE_RANGE_ATTACK ) ); + m_bFiring = m_iFireSequence != -1; + m_flFireCycle = 0; + } + else if ( playerAnim == PLAYER_ATTACK2 ) + { +#if EXPANDED_HL2DM_ACTIVITIES + m_iFireSequence = SelectWeightedSequence( TranslateActivity( ACT_HL2MP_GESTURE_RANGE_ATTACK2 ) ); +#else + m_iFireSequence = SelectWeightedSequence( TranslateActivity( ACT_HL2MP_GESTURE_RANGE_ATTACK ) ); +#endif + m_bFiring = m_iFireSequence != -1; + m_flFireCycle = 0; + } + else if ( playerAnim == PLAYER_JUMP ) + { + // Play the jump animation. + if (!m_bJumping) + { + m_bJumping = true; + m_bFirstJumpFrame = true; + m_flJumpStartTime = gpGlobals->curtime; + } + } + else if ( playerAnim == PLAYER_RELOAD ) + { + m_iReloadSequence = SelectWeightedSequence( TranslateActivity( ACT_HL2MP_GESTURE_RELOAD ) ); + if (m_iReloadSequence != -1) + { + // clear other events that might be playing in our layer + m_bWeaponSwitching = false; + m_fReloadPlaybackRate = 1.0f; + m_bReloading = true; + m_flReloadCycle = 0; + } + } + else if ( playerAnim == PLAYER_UNHOLSTER || playerAnim == PLAYER_HOLSTER ) + { + m_iWeaponSwitchSequence = SelectWeightedSequence( TranslateActivity( playerAnim == PLAYER_UNHOLSTER ? ACT_ARM : ACT_DISARM ) ); + if (m_iWeaponSwitchSequence != -1) + { + // clear other events that might be playing in our layer + m_bPlayingMisc = false; + m_bReloading = false; + + m_bWeaponSwitching = true; + m_flWeaponSwitchCycle = 0; + m_flMiscBlendOut = 0.1f; + m_flMiscBlendIn = 0.1f; + m_bMiscNoOverride = false; + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +Activity CSinglePlayerAnimState::TranslateActivity( Activity actDesired ) +{ +#ifdef CLIENT_DLL + return actDesired; +#else + return m_pPlayer->Weapon_TranslateActivity( actDesired ); +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CSinglePlayerAnimState::HandleJumping() +{ + if ( m_bJumping ) + { + if ( m_bFirstJumpFrame ) + { + m_bFirstJumpFrame = false; + RestartMainSequence(); // Reset the animation. + } + + // Don't check if he's on the ground for a sec.. sometimes the client still has the + // on-ground flag set right when the message comes in. + if (m_flJumpStartTime > gpGlobals->curtime) + m_flJumpStartTime = gpGlobals->curtime; + if ( gpGlobals->curtime - m_flJumpStartTime > 0.2f) + { + if ( m_pOuter->GetFlags() & FL_ONGROUND || GetOuter()->GetGroundEntity() != NULL) + { + m_bJumping = false; + RestartMainSequence(); // Reset the animation. + } + } + } + + // Are we still jumping? If so, keep playing the jump animation. + return m_bJumping; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CSinglePlayerAnimState::ComputeSequences( CStudioHdr *pStudioHdr ) +{ + CBasePlayerAnimState::ComputeSequences(pStudioHdr); + + ComputeFireSequence(); + ComputeMiscSequence(); + ComputeReloadSequence(); + ComputeWeaponSwitchSequence(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CSinglePlayerAnimState::AddMiscSequence( int iSequence, float flBlendIn, float flBlendOut, float flPlaybackRate, bool bHoldAtEnd, bool bOnlyWhenStill ) +{ + Assert( iSequence != -1 ); + + m_iMiscSequence = iSequence; + m_flMiscBlendIn = flBlendIn; + m_flMiscBlendOut = flBlendOut; + + m_bPlayingMisc = true; + m_bMiscHoldAtEnd = bHoldAtEnd; + m_bReloading = false; + m_flMiscCycle = 0; + m_bMiscOnlyWhenStill = bOnlyWhenStill; + m_bMiscNoOverride = true; + m_fMiscPlaybackRate = flPlaybackRate; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CSinglePlayerAnimState::ClearAnimationState() +{ + m_bJumping = false; + m_bFiring = false; + m_bReloading = false; + m_bWeaponSwitching = false; + m_bPlayingMisc = false; + m_flReloadBlendIn = 0.0f; + m_flReloadBlendOut = 0.0f; + m_flMiscBlendIn = 0.0f; + m_flMiscBlendOut = 0.0f; + CBasePlayerAnimState::ClearAnimationState(); +} + +void CSinglePlayerAnimState::ClearAnimationLayers() +{ + VPROF( "CBasePlayerAnimState::ClearAnimationLayers" ); + if ( !m_pOuter ) + return; + + m_pOuter->SetNumAnimOverlays( NUM_LAYERS_WANTED ); + for ( int i=0; i < m_pOuter->GetNumAnimOverlays(); i++ ) + { + m_pOuter->GetAnimOverlay( i )->SetOrder( CBaseAnimatingOverlay::MAX_OVERLAYS ); +#ifndef CLIENT_DLL + m_pOuter->GetAnimOverlay( i )->m_fFlags = 0; +#endif + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int CSinglePlayerAnimState::CalcAimLayerSequence( float *flCycle, float *flAimSequenceWeight, bool bForceIdle ) +{ + // TODO? + return m_pOuter->LookupSequence( "soldier_Aim_9_directions" ); +} + +void CSinglePlayerAnimState::UpdateLayerSequenceGeneric( int iLayer, bool &bEnabled, + float &flCurCycle, int &iSequence, bool bWaitAtEnd, + float fBlendIn, float fBlendOut, bool bMoveBlend, float fPlaybackRate, bool bUpdateCycle /* = true */ ) +{ + if ( !bEnabled ) + return; + + CStudioHdr *hdr = GetOuter()->GetModelPtr(); + if ( !hdr ) + return; + + if ( iSequence < 0 || iSequence >= hdr->GetNumSeq() ) + return; + + // Increment the fire sequence's cycle. + if ( bUpdateCycle ) + { + flCurCycle += m_pOuter->GetSequenceCycleRate( hdr, iSequence ) * gpGlobals->frametime * fPlaybackRate; + } + + // temp: if the sequence is looping, don't override it - we need better handling of looping anims, + // especially in misc layer from melee (right now the same melee attack is looped manually in asw_melee_system.cpp) + bool bLooping = m_pOuter->IsSequenceLooping( hdr, iSequence ); + + if ( flCurCycle > 1 && !bLooping ) + { + if ( iLayer == RELOADSEQUENCE_LAYER ) + { + m_bReloading = false; + } + if ( bWaitAtEnd ) + { + flCurCycle = 1; + } + else + { + // Not firing anymore. + bEnabled = false; + iSequence = 0; + return; + } + } + + // if this animation should blend out as we move, then check for dropping it completely since we're moving too fast + float speed = 0; + if (bMoveBlend) + { + Vector vel; + GetOuterAbsVelocity( vel ); + + float speed = vel.Length2D(); + + if (speed > 50) + { + bEnabled = false; + iSequence = 0; + return; + } + } + + // Now dump the state into its animation layer. + CAnimationLayer *pLayer = m_pOuter->GetAnimOverlay( iLayer ); + + pLayer->m_flCycle = flCurCycle; + pLayer->m_nSequence = iSequence; + + pLayer->m_flPlaybackRate = fPlaybackRate; + pLayer->m_flWeight = 1.0f; + + if (iLayer == RELOADSEQUENCE_LAYER) + { + // blend this layer in and out for smooth reloading + if (flCurCycle < fBlendIn && fBlendIn>0) + { + pLayer->m_flWeight = ( clamp(flCurCycle / fBlendIn, + 0.001f, 1.0f) ); + } + else if (flCurCycle >= (1.0f - fBlendOut) && fBlendOut>0) + { + pLayer->m_flWeight = ( clamp((1.0f - flCurCycle) / fBlendOut, + 0.001f, 1.0f) ); + } + else + { + pLayer->m_flWeight = 1.0f; + } + } + else + { + pLayer->m_flWeight = 1.0f; + } + if (bMoveBlend) + { + // blend the animation out as we move faster + if (speed <= 50) + pLayer->m_flWeight = ( pLayer->m_flWeight * (50.0f - speed) / 50.0f ); + } + +#ifndef CLIENT_DLL + pLayer->m_fFlags |= ANIM_LAYER_ACTIVE; +#endif + pLayer->SetOrder( iLayer ); +} + +void CSinglePlayerAnimState::ComputeFireSequence() +{ + UpdateLayerSequenceGeneric( FIRESEQUENCE_LAYER, m_bFiring, m_flFireCycle, m_iFireSequence, false ); +} + +void CSinglePlayerAnimState::ComputeReloadSequence() +{ + UpdateLayerSequenceGeneric( RELOADSEQUENCE_LAYER, m_bReloading, m_flReloadCycle, m_iReloadSequence, false, m_flReloadBlendIn, m_flReloadBlendOut, false, m_fReloadPlaybackRate ); +} + +void CSinglePlayerAnimState::ComputeWeaponSwitchSequence() +{ + UpdateLayerSequenceGeneric( RELOADSEQUENCE_LAYER, m_bWeaponSwitching, m_flWeaponSwitchCycle, m_iWeaponSwitchSequence, false, 0, 0.5f ); +} + +// does misc gestures if we're not firing +void CSinglePlayerAnimState::ComputeMiscSequence() +{ + UpdateLayerSequenceGeneric( RELOADSEQUENCE_LAYER, m_bPlayingMisc, m_flMiscCycle, m_iMiscSequence, m_bMiscHoldAtEnd, m_flMiscBlendIn, m_flMiscBlendOut, m_bMiscOnlyWhenStill, m_fMiscPlaybackRate ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : - +// Output : float +//----------------------------------------------------------------------------- +float CSinglePlayerAnimState::GetCurrentMaxGroundSpeed() +{ + CStudioHdr *pStudioHdr = GetOuter()->GetModelPtr(); + + if ( pStudioHdr == NULL ) + return 1.0f; + + int iMoveX = GetOuter()->LookupPoseParameter( "move_x" ); + int iMoveY = GetOuter()->LookupPoseParameter( "move_y" ); + + float prevX = GetOuter()->GetPoseParameter( iMoveX ); + float prevY = GetOuter()->GetPoseParameter( iMoveY ); + + float d = MAX( fabs( prevX ), fabs( prevY ) ); + float newX, newY; + if ( d == 0.0 ) + { + newX = 1.0; + newY = 0.0; + } + else + { + newX = prevX / d; + newY = prevY / d; + } + + GetOuter()->SetPoseParameter( pStudioHdr, iMoveX, newX ); + GetOuter()->SetPoseParameter( pStudioHdr, iMoveY, newY ); + + float speed = GetOuter()->GetSequenceGroundSpeed(GetOuter()->GetSequence() ); + + GetOuter()->SetPoseParameter( pStudioHdr, iMoveX, prevX ); + GetOuter()->SetPoseParameter( pStudioHdr, iMoveY, prevY ); + + return speed; +} + +//----------------------------------------------------------------------------- +// Purpose: Override for backpeddling +// Input : dt - +//----------------------------------------------------------------------------- +void CSinglePlayerAnimState::ComputePoseParam_BodyYaw( void ) +{ + CBasePlayerAnimState::ComputePoseParam_BodyYaw(); + + //ComputePoseParam_BodyLookYaw(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CSinglePlayerAnimState::ComputePoseParam_BodyLookYaw( void ) +{ + // See if we even have a blender for pitch + int upper_body_yaw = GetOuter()->LookupPoseParameter( "aim_yaw" ); + if ( upper_body_yaw < 0 ) + { + return; + } + + // Assume upper and lower bodies are aligned and that we're not turning + float flGoalTorsoYaw = 0.0f; + int turning = TURN_NONE; + float turnrate = 360.0f; + + Vector vel; + + GetOuterAbsVelocity( vel ); + + bool isMoving = ( vel.Length() > 1.0f ) ? true : false; + + if ( !isMoving ) + { + // Just stopped moving, try and clamp feet + if ( m_flLastTurnTime <= 0.0f ) + { + m_flLastTurnTime = gpGlobals->curtime; + m_flLastYaw = GetOuter()->EyeAngles().y; + // Snap feet to be perfectly aligned with torso/eyes + m_flGoalFeetYaw = GetOuter()->EyeAngles().y; + m_flCurrentFeetYaw = m_flGoalFeetYaw; + m_nTurningInPlace = TURN_NONE; + } + + // If rotating in place, update stasis timer + + if ( m_flLastYaw != GetOuter()->EyeAngles().y ) + { + m_flLastTurnTime = gpGlobals->curtime; + m_flLastYaw = GetOuter()->EyeAngles().y; + } + + if ( m_flGoalFeetYaw != m_flCurrentFeetYaw ) + { + m_flLastTurnTime = gpGlobals->curtime; + } + + turning = ConvergeAngles( m_flGoalFeetYaw, turnrate, m_AnimConfig.m_flMaxBodyYawDegrees, gpGlobals->frametime, m_flCurrentFeetYaw ); + + QAngle eyeAngles = GetOuter()->EyeAngles(); + QAngle vAngle = GetOuter()->GetLocalAngles(); + + // See how far off current feetyaw is from true yaw + float yawdelta = GetOuter()->EyeAngles().y - m_flCurrentFeetYaw; + yawdelta = AngleNormalize( yawdelta ); + + bool rotated_too_far = false; + + float yawmagnitude = fabs( yawdelta ); + + // If too far, then need to turn in place + if ( yawmagnitude > 45 ) + { + rotated_too_far = true; + } + + // Standing still for a while, rotate feet around to face forward + // Or rotated too far + // FIXME: Play an in place turning animation + if ( rotated_too_far || + ( gpGlobals->curtime > m_flLastTurnTime + mp_facefronttime.GetFloat() ) ) + { + m_flGoalFeetYaw = GetOuter()->EyeAngles().y; + m_flLastTurnTime = gpGlobals->curtime; + + /* float yd = m_flCurrentFeetYaw - m_flGoalFeetYaw; + if ( yd > 0 ) + { + m_nTurningInPlace = TURN_RIGHT; + } + else if ( yd < 0 ) + { + m_nTurningInPlace = TURN_LEFT; + } + else + { + m_nTurningInPlace = TURN_NONE; + } + + turning = ConvergeAngles( m_flGoalFeetYaw, turnrate, gpGlobals->frametime, m_flCurrentFeetYaw ); + yawdelta = GetOuter()->EyeAngles().y - m_flCurrentFeetYaw;*/ + + } + + // Snap upper body into position since the delta is already smoothed for the feet + flGoalTorsoYaw = yawdelta; + m_flCurrentTorsoYaw = flGoalTorsoYaw; + } + else + { + m_flLastTurnTime = 0.0f; + m_nTurningInPlace = TURN_NONE; + m_flCurrentFeetYaw = m_flGoalFeetYaw = GetOuter()->EyeAngles().y; + flGoalTorsoYaw = 0.0f; + m_flCurrentTorsoYaw = GetOuter()->EyeAngles().y - m_flCurrentFeetYaw; + } + + if ( turning == TURN_NONE ) + { + m_nTurningInPlace = turning; + } + + if ( m_nTurningInPlace != TURN_NONE ) + { + // If we're close to finishing the turn, then turn off the turning animation + if ( fabs( m_flCurrentFeetYaw - m_flGoalFeetYaw ) < MIN_TURN_ANGLE_REQUIRING_TURN_ANIMATION ) + { + m_nTurningInPlace = TURN_NONE; + } + } + + GetOuter()->SetPoseParameter( upper_body_yaw, clamp( m_flCurrentTorsoYaw, -60.0f, 60.0f ) ); + + /* + // FIXME: Adrian, what is this? + int body_yaw = GetOuter()->LookupPoseParameter( "body_yaw" ); + + if ( body_yaw >= 0 ) + { + GetOuter()->SetPoseParameter( body_yaw, 30 ); + } + */ + +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CSinglePlayerAnimState::ComputePoseParam_BodyPitch( CStudioHdr *pStudioHdr ) +{ + // Get pitch from v_angle + float flPitch = m_flEyePitch; + + if ( flPitch > 180.0f ) + { + flPitch -= 360.0f; + } + flPitch = clamp( flPitch, -90, 90 ); + + // See if we have a blender for pitch + GetOuter()->SetPoseParameter( pStudioHdr, "aim_pitch", flPitch ); + + ComputePoseParam_HeadPitch( pStudioHdr ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CSinglePlayerAnimState::ComputePoseParam_HeadPitch( CStudioHdr *pStudioHdr ) +{ + // Get pitch from v_angle + int iHeadPitch = GetOuter()->LookupPoseParameter("head_pitch"); + + float flPitch = m_flEyePitch; + + if ( flPitch > 180.0f ) + { + flPitch -= 360.0f; + } + flPitch = clamp( flPitch, -90, 90 ); + + GetOuter()->SetPoseParameter( pStudioHdr, iHeadPitch, flPitch ); +} diff --git a/src/game/shared/mapbase/singleplayer_animstate.h b/src/game/shared/mapbase/singleplayer_animstate.h new file mode 100644 index 00000000..a805dffc --- /dev/null +++ b/src/game/shared/mapbase/singleplayer_animstate.h @@ -0,0 +1,110 @@ +//========= Mapbase - https://github.com/mapbase-source/source-sdk-2013 ============// +// +// Purpose: Single Player animation state 'handler'. This utility is used +// to evaluate the pose parameter value based on the direction +// and speed of the player. +// +// ------------------------------------------------------------------------------ +// +// This was originally based on the following VDC article: +// https://developer.valvesoftware.com/wiki/Fixing_the_player_animation_state_(Single_Player) +// +// It has been modified by Blixibon to derive from CBasePlayerAnimState instead and support 9-way blends. +// Much of the work done to make this derive from CBasePlayerAnimState utilized code from the Alien Swarm SDK. +// +//=============================================================================// + +#ifndef SINGLEPLAYER_ANIMSTATE_H +#define SINGLEPLAYER_ANIMSTATE_H +#ifdef _WIN32 +#pragma once +#endif + +#include "cbase.h" +#include "base_playeranimstate.h" + +#ifdef CLIENT_DLL +#include "c_baseplayer.h" +#else +#include "player.h" +#endif + +#ifdef MAPBASE +// Special definition for differentiating between SP and HL2:DM anim states +#define SP_ANIM_STATE 1 +#endif + +class CSinglePlayerAnimState : public CBasePlayerAnimState +{ +public: + CSinglePlayerAnimState( CBasePlayer *pPlayer ); + + Activity CalcMainActivity(); + int CalcAimLayerSequence( float *flCycle, float *flAimSequenceWeight, bool bForceIdle ); + float GetCurrentMaxGroundSpeed(); + + void SetPlayerAnimation( PLAYER_ANIM playerAnim ); + Activity TranslateActivity( Activity actDesired ); + + void ComputeSequences( CStudioHdr *pStudioHdr ); + + void AddMiscSequence( int iSequence, float flBlendIn = 0.0f, float flBlendOut = 0.0f, float flPlaybackRate = 1.0f, bool bHoldAtEnd = false, bool bOnlyWhenStill = false ); + + void ClearAnimationState(); + void ClearAnimationLayers(); + +private: + + bool HandleJumping(); + + void ComputeFireSequence(); + void ComputeReloadSequence(); + void ComputeWeaponSwitchSequence(); + void ComputeMiscSequence(); + + void UpdateLayerSequenceGeneric( int iLayer, bool &bEnabled, float &flCurCycle, + int &iSequence, bool bWaitAtEnd, + float fBlendIn=0.15f, float fBlendOut=0.15f, bool bMoveBlend = false, + float fPlaybackRate=1.0f, bool bUpdateCycle = true ); + + void ComputePoseParam_BodyYaw( void ); + void ComputePoseParam_BodyPitch( CStudioHdr *pStudioHdr ); + void ComputePoseParam_BodyLookYaw( void ); + void ComputePoseParam_HeadPitch( CStudioHdr *pStudioHdr ); + + CBasePlayer* m_pPlayer; + + // Current state variables. + bool m_bJumping; // Set on a jump event. + float m_flJumpStartTime; + bool m_bFirstJumpFrame; + + // Aim sequence plays reload while this is on. + bool m_bReloading; + float m_flReloadCycle; + int m_iReloadSequence; + float m_flReloadBlendOut, m_flReloadBlendIn; + float m_fReloadPlaybackRate; + + bool m_bWeaponSwitching; + float m_flWeaponSwitchCycle; + int m_iWeaponSwitchSequence; + + bool m_bPlayingMisc; + float m_flMiscCycle, m_flMiscBlendOut, m_flMiscBlendIn; + int m_iMiscSequence; + bool m_bMiscOnlyWhenStill, m_bMiscHoldAtEnd; + bool m_bMiscNoOverride; + float m_fMiscPlaybackRate; + bool m_bMiscCycleRewound; + float m_flMiscRewindCycle; + // This is set to true if ANY animation is being played in the fire layer. + bool m_bFiring; // If this is on, then it'll continue the fire animation in the fire layer + // until it completes. + int m_iFireSequence; // (For any sequences in the fire layer, including grenade throw). + float m_flFireCycle; +}; + +CSinglePlayerAnimState *CreatePlayerAnimationState( CBasePlayer *pPlayer ); + +#endif // SINGLEPLAYER_ANIMSTATE_H diff --git a/src/game/shared/mapbase/vscript_consts_shared.cpp b/src/game/shared/mapbase/vscript_consts_shared.cpp new file mode 100644 index 00000000..dbc7b2ee --- /dev/null +++ b/src/game/shared/mapbase/vscript_consts_shared.cpp @@ -0,0 +1,641 @@ +//========= Mapbase - https://github.com/mapbase-source/source-sdk-2013 ============// +// +// Purpose: VScript constants and enums shared between the server and client. +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" +#include "activitylist.h" +#include "in_buttons.h" +#include "rope_shared.h" +#include "eventlist.h" +#ifdef CLIENT_DLL +#include "c_ai_basenpc.h" +#else +#include "ai_basenpc.h" +#include "globalstate.h" +#endif + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + + +//============================================================================= +//============================================================================= + +BEGIN_SCRIPTENUM( IN, "Button mask bindings" ) + + DEFINE_ENUMCONST_NAMED( IN_ATTACK, "ATTACK", "Button for +attack" ) + DEFINE_ENUMCONST_NAMED( IN_JUMP, "JUMP", "Button for +jump" ) + DEFINE_ENUMCONST_NAMED( IN_DUCK, "DUCK", "Button for +duck" ) + DEFINE_ENUMCONST_NAMED( IN_FORWARD, "FORWARD", "Button for +forward" ) + DEFINE_ENUMCONST_NAMED( IN_BACK, "BACK", "Button for +back" ) + DEFINE_ENUMCONST_NAMED( IN_USE, "USE", "Button for +use" ) + DEFINE_ENUMCONST_NAMED( IN_CANCEL, "CANCEL", "Special button flag for attack cancel" ) + DEFINE_ENUMCONST_NAMED( IN_LEFT, "LEFT", "Button for +left" ) + DEFINE_ENUMCONST_NAMED( IN_RIGHT, "RIGHT", "Button for +right" ) + DEFINE_ENUMCONST_NAMED( IN_MOVELEFT, "MOVELEFT", "Button for +moveleft" ) + DEFINE_ENUMCONST_NAMED( IN_MOVERIGHT, "MOVERIGHT", "Button for +moveright" ) + DEFINE_ENUMCONST_NAMED( IN_ATTACK2, "ATTACK2", "Button for +attack2" ) + DEFINE_ENUMCONST_NAMED( IN_RUN, "RUN", "Unused button (see IN.SPEED for sprint)" ) + DEFINE_ENUMCONST_NAMED( IN_RELOAD, "RELOAD", "Button for +reload" ) + DEFINE_ENUMCONST_NAMED( IN_ALT1, "ALT1", "Button for +alt1" ) + DEFINE_ENUMCONST_NAMED( IN_ALT2, "ALT2", "Button for +alt2" ) + DEFINE_ENUMCONST_NAMED( IN_SCORE, "SCORE", "Button for +score" ) + DEFINE_ENUMCONST_NAMED( IN_SPEED, "SPEED", "Button for +speed" ) + DEFINE_ENUMCONST_NAMED( IN_WALK, "WALK", "Button for +walk" ) + DEFINE_ENUMCONST_NAMED( IN_ZOOM, "ZOOM", "Button for +zoom" ) + DEFINE_ENUMCONST_NAMED( IN_WEAPON1, "WEAPON1", "Special button used by weapons themselves" ) + DEFINE_ENUMCONST_NAMED( IN_WEAPON2, "WEAPON2", "Special button used by weapons themselves" ) + DEFINE_ENUMCONST_NAMED( IN_BULLRUSH, "BULLRUSH", "Unused button" ) + DEFINE_ENUMCONST_NAMED( IN_GRENADE1, "GRENADE1", "Button for +grenade1" ) + DEFINE_ENUMCONST_NAMED( IN_GRENADE2, "GRENADE2", "Button for +grenade2" ) + DEFINE_ENUMCONST_NAMED( IN_ATTACK3, "ATTACK3", "Button for +attack3" ) + +END_SCRIPTENUM(); + +//============================================================================= +//============================================================================= + +BEGIN_SCRIPTENUM( RenderMode, "Render modes used by Get/SetRenderMode" ) + + DEFINE_ENUMCONST_NAMED( kRenderNormal, "Normal", "" ) + DEFINE_ENUMCONST_NAMED( kRenderTransColor, "Color", "" ) + DEFINE_ENUMCONST_NAMED( kRenderTransTexture, "Texture", "" ) + DEFINE_ENUMCONST_NAMED( kRenderGlow, "Glow", "" ) + DEFINE_ENUMCONST_NAMED( kRenderTransAlpha, "Solid", "" ) + DEFINE_ENUMCONST_NAMED( kRenderTransAdd, "Additive", "" ) + DEFINE_ENUMCONST_NAMED( kRenderEnvironmental, "Environmental", "" ) + DEFINE_ENUMCONST_NAMED( kRenderTransAddFrameBlend, "AdditiveFractionalFrame", "" ) + DEFINE_ENUMCONST_NAMED( kRenderTransAlphaAdd, "AlphaAdd", "" ) + DEFINE_ENUMCONST_NAMED( kRenderWorldGlow, "WorldSpaceGlow", "" ) + DEFINE_ENUMCONST_NAMED( kRenderNone, "None", "" ) + +END_SCRIPTENUM(); + +//============================================================================= +//============================================================================= + +BEGIN_SCRIPTENUM( Hitgroup, "Hit groups from traces" ) + + DEFINE_ENUMCONST_NAMED( HITGROUP_GENERIC, "Generic", "" ) + DEFINE_ENUMCONST_NAMED( HITGROUP_HEAD, "Head", "" ) + DEFINE_ENUMCONST_NAMED( HITGROUP_CHEST, "Chest", "" ) + DEFINE_ENUMCONST_NAMED( HITGROUP_STOMACH, "Stomach", "" ) + DEFINE_ENUMCONST_NAMED( HITGROUP_LEFTARM, "LeftArm", "" ) + DEFINE_ENUMCONST_NAMED( HITGROUP_RIGHTARM, "RightArm", "" ) + DEFINE_ENUMCONST_NAMED( HITGROUP_LEFTLEG, "LeftLeg", "" ) + DEFINE_ENUMCONST_NAMED( HITGROUP_RIGHTLEG, "RightLeg", "" ) + DEFINE_ENUMCONST_NAMED( HITGROUP_GEAR, "Gear", "" ) + +END_SCRIPTENUM(); + +//============================================================================= +//============================================================================= + +BEGIN_SCRIPTENUM( MapLoad, "Map load enum for GetLoadType()" ) + + DEFINE_ENUMCONST_NAMED( MapLoad_NewGame, "NewGame", "Map was loaded from a new game" ) + DEFINE_ENUMCONST_NAMED( MapLoad_LoadGame, "LoadGame", "Map was loaded from a save file" ) + DEFINE_ENUMCONST_NAMED( MapLoad_Transition, "Transition", "Map was loaded from a level transition" ) + DEFINE_ENUMCONST_NAMED( MapLoad_Background, "Background", "Map was loaded as a background map" ) + +END_SCRIPTENUM(); + +//============================================================================= +//============================================================================= + +void RegisterActivityConstants() +{ + // Make sure there are no activities declared yet + if (g_pScriptVM->ValueExists( "ACT_RESET" )) + return; + + // Register activity constants by just iterating through the entire activity list + for (int i = 0; i < ActivityList_HighestIndex(); i++) + { + ScriptRegisterConstantNamed( g_pScriptVM, i, ActivityList_NameForIndex(i), "" ); + } +} + +//============================================================================= +//============================================================================= + +extern void RegisterWeaponScriptConstants(); + +void RegisterSharedScriptConstants() +{ + // "SERVER_DLL" is used in Source 2. +#ifdef GAME_DLL + ScriptRegisterConstantNamed( g_pScriptVM, 1, "SERVER_DLL", "" ); + ScriptRegisterConstantNamed( g_pScriptVM, 0, "CLIENT_DLL", "" ); +#else + ScriptRegisterConstantNamed( g_pScriptVM, 0, "SERVER_DLL", "" ); + ScriptRegisterConstantNamed( g_pScriptVM, 1, "CLIENT_DLL", "" ); +#endif + + // TODO: Add to non-Mapbase VScript +#ifdef VSCRIPT_PRIORITIZE_TF2_SYNTAX + ScriptRegisterConstantNamed( g_pScriptVM, 1, "VSCRIPT_PRIORITIZE_TF2_SYNTAX", "Whether this mod prioritizes TF2 VScript syntax over Mapbase VScript syntax." ); +#else + ScriptRegisterConstantNamed( g_pScriptVM, 0, "VSCRIPT_PRIORITIZE_TF2_SYNTAX", "Whether this mod prioritizes TF2 VScript syntax over Mapbase VScript syntax." ); +#endif + + // + // Activities + // + + // Scripts have to use this function before using any activity constants. + // This is because initializing 1,700+ constants every time a level loads and letting them lay around + // usually doing nothing sounds like a bad idea. + ScriptRegisterFunction( g_pScriptVM, RegisterActivityConstants, "Registers all activity IDs as usable constants." ); + + + // + // Damage Types + // + ScriptRegisterConstant( g_pScriptVM, DMG_GENERIC, "Damage type used in damage information." ); + ScriptRegisterConstant( g_pScriptVM, DMG_CRUSH, "Damage type used in damage information." ); + ScriptRegisterConstant( g_pScriptVM, DMG_BULLET, "Damage type used in damage information." ); + ScriptRegisterConstant( g_pScriptVM, DMG_SLASH, "Damage type used in damage information." ); + ScriptRegisterConstant( g_pScriptVM, DMG_BURN, "Damage type used in damage information." ); + ScriptRegisterConstant( g_pScriptVM, DMG_VEHICLE, "Damage type used in damage information." ); + ScriptRegisterConstant( g_pScriptVM, DMG_FALL, "Damage type used in damage information." ); + ScriptRegisterConstant( g_pScriptVM, DMG_BLAST, "Damage type used in damage information." ); + ScriptRegisterConstant( g_pScriptVM, DMG_CLUB, "Damage type used in damage information." ); + ScriptRegisterConstant( g_pScriptVM, DMG_SHOCK, "Damage type used in damage information." ); + ScriptRegisterConstant( g_pScriptVM, DMG_SONIC, "Damage type used in damage information." ); + ScriptRegisterConstant( g_pScriptVM, DMG_ENERGYBEAM, "Damage type used in damage information." ); + ScriptRegisterConstant( g_pScriptVM, DMG_PREVENT_PHYSICS_FORCE, "Damage type used in damage information." ); + ScriptRegisterConstant( g_pScriptVM, DMG_NEVERGIB, "Damage type used in damage information." ); + ScriptRegisterConstant( g_pScriptVM, DMG_ALWAYSGIB, "Damage type used in damage information." ); + ScriptRegisterConstant( g_pScriptVM, DMG_DROWN, "Damage type used in damage information." ); + ScriptRegisterConstant( g_pScriptVM, DMG_PARALYZE, "Damage type used in damage information." ); + ScriptRegisterConstant( g_pScriptVM, DMG_NERVEGAS, "Damage type used in damage information." ); + ScriptRegisterConstant( g_pScriptVM, DMG_POISON, "Damage type used in damage information." ); + ScriptRegisterConstant( g_pScriptVM, DMG_RADIATION, "Damage type used in damage information." ); + ScriptRegisterConstant( g_pScriptVM, DMG_DROWNRECOVER, "Damage type used in damage information." ); + ScriptRegisterConstant( g_pScriptVM, DMG_ACID, "Damage type used in damage information." ); + ScriptRegisterConstant( g_pScriptVM, DMG_SLOWBURN, "Damage type used in damage information." ); + ScriptRegisterConstant( g_pScriptVM, DMG_REMOVENORAGDOLL, "Damage type used in damage information." ); + ScriptRegisterConstant( g_pScriptVM, DMG_PHYSGUN, "Damage type used in damage information." ); + ScriptRegisterConstant( g_pScriptVM, DMG_PLASMA, "Damage type used in damage information." ); + ScriptRegisterConstant( g_pScriptVM, DMG_AIRBOAT, "Damage type used in damage information." ); + ScriptRegisterConstant( g_pScriptVM, DMG_DISSOLVE, "Damage type used in damage information." ); + ScriptRegisterConstant( g_pScriptVM, DMG_BLAST_SURFACE, "Damage type used in damage information." ); + ScriptRegisterConstant( g_pScriptVM, DMG_DIRECT, "Damage type used in damage information." ); + ScriptRegisterConstant( g_pScriptVM, DMG_BUCKSHOT, "Damage type used in damage information." ); + + // + // Collision Groups + // + ScriptRegisterConstant( g_pScriptVM, COLLISION_GROUP_NONE, "Collision group used in GetCollisionGroup(), etc." ); + ScriptRegisterConstant( g_pScriptVM, COLLISION_GROUP_DEBRIS, "Collision group used in GetCollisionGroup(), etc." ); + ScriptRegisterConstant( g_pScriptVM, COLLISION_GROUP_DEBRIS_TRIGGER, "Collision group used in GetCollisionGroup(), etc." ); + ScriptRegisterConstant( g_pScriptVM, COLLISION_GROUP_INTERACTIVE_DEBRIS, "Collision group used in GetCollisionGroup(), etc." ); + ScriptRegisterConstant( g_pScriptVM, COLLISION_GROUP_INTERACTIVE, "Collision group used in GetCollisionGroup(), etc." ); + ScriptRegisterConstant( g_pScriptVM, COLLISION_GROUP_PLAYER, "Collision group used in GetCollisionGroup(), etc." ); + ScriptRegisterConstant( g_pScriptVM, COLLISION_GROUP_BREAKABLE_GLASS, "Collision group used in GetCollisionGroup(), etc." ); + ScriptRegisterConstant( g_pScriptVM, COLLISION_GROUP_VEHICLE, "Collision group used in GetCollisionGroup(), etc." ); + ScriptRegisterConstant( g_pScriptVM, COLLISION_GROUP_PLAYER_MOVEMENT, "Collision group used in GetCollisionGroup(), etc." ); + ScriptRegisterConstant( g_pScriptVM, COLLISION_GROUP_NPC, "Collision group used in GetCollisionGroup(), etc." ); + ScriptRegisterConstant( g_pScriptVM, COLLISION_GROUP_IN_VEHICLE, "Collision group used in GetCollisionGroup(), etc." ); + ScriptRegisterConstant( g_pScriptVM, COLLISION_GROUP_WEAPON, "Collision group used in GetCollisionGroup(), etc." ); + ScriptRegisterConstant( g_pScriptVM, COLLISION_GROUP_VEHICLE_CLIP, "Collision group used in GetCollisionGroup(), etc." ); + ScriptRegisterConstant( g_pScriptVM, COLLISION_GROUP_PROJECTILE, "Collision group used in GetCollisionGroup(), etc." ); + ScriptRegisterConstant( g_pScriptVM, COLLISION_GROUP_DOOR_BLOCKER, "Collision group used in GetCollisionGroup(), etc." ); + ScriptRegisterConstant( g_pScriptVM, COLLISION_GROUP_PASSABLE_DOOR, "Collision group used in GetCollisionGroup(), etc." ); + ScriptRegisterConstant( g_pScriptVM, COLLISION_GROUP_DISSOLVING, "Collision group used in GetCollisionGroup(), etc." ); + ScriptRegisterConstant( g_pScriptVM, COLLISION_GROUP_PUSHAWAY, "Collision group used in GetCollisionGroup(), etc." ); + ScriptRegisterConstant( g_pScriptVM, COLLISION_GROUP_NPC_ACTOR, "Collision group used in GetCollisionGroup(), etc." ); + ScriptRegisterConstant( g_pScriptVM, COLLISION_GROUP_NPC_SCRIPTED, "Collision group used in GetCollisionGroup(), etc." ); + + // + // Flags + // + ScriptRegisterConstant( g_pScriptVM, FL_ONGROUND, "Flag used in GetFlags(), etc." ); + ScriptRegisterConstant( g_pScriptVM, FL_DUCKING, "Flag used in GetFlags(), etc." ); + ScriptRegisterConstant( g_pScriptVM, FL_WATERJUMP, "Flag used in GetFlags(), etc." ); + ScriptRegisterConstant( g_pScriptVM, FL_ONTRAIN, "Flag used in GetFlags(), etc." ); + ScriptRegisterConstant( g_pScriptVM, FL_INRAIN, "Flag used in GetFlags(), etc." ); + ScriptRegisterConstant( g_pScriptVM, FL_FROZEN, "Flag used in GetFlags(), etc." ); + ScriptRegisterConstant( g_pScriptVM, FL_ATCONTROLS, "Flag used in GetFlags(), etc." ); + ScriptRegisterConstant( g_pScriptVM, FL_CLIENT, "Flag used in GetFlags(), etc." ); + ScriptRegisterConstant( g_pScriptVM, FL_FAKECLIENT, "Flag used in GetFlags(), etc." ); + ScriptRegisterConstant( g_pScriptVM, FL_INWATER, "Flag used in GetFlags(), etc." ); + ScriptRegisterConstant( g_pScriptVM, FL_FLY, "Flag used in GetFlags(), etc." ); + ScriptRegisterConstant( g_pScriptVM, FL_SWIM, "Flag used in GetFlags(), etc." ); + ScriptRegisterConstant( g_pScriptVM, FL_CONVEYOR, "Flag used in GetFlags(), etc." ); + ScriptRegisterConstant( g_pScriptVM, FL_NPC, "Flag used in GetFlags(), etc." ); + ScriptRegisterConstant( g_pScriptVM, FL_GODMODE, "Flag used in GetFlags(), etc." ); + ScriptRegisterConstant( g_pScriptVM, FL_NOTARGET, "Flag used in GetFlags(), etc." ); + ScriptRegisterConstant( g_pScriptVM, FL_AIMTARGET, "Flag used in GetFlags(), etc." ); + ScriptRegisterConstant( g_pScriptVM, FL_PARTIALGROUND, "Flag used in GetFlags(), etc." ); + ScriptRegisterConstant( g_pScriptVM, FL_STATICPROP, "Flag used in GetFlags(), etc." ); + ScriptRegisterConstant( g_pScriptVM, FL_GRAPHED, "Flag used in GetFlags(), etc." ); + ScriptRegisterConstant( g_pScriptVM, FL_GRENADE, "Flag used in GetFlags(), etc." ); + ScriptRegisterConstant( g_pScriptVM, FL_STEPMOVEMENT, "Flag used in GetFlags(), etc." ); + ScriptRegisterConstant( g_pScriptVM, FL_DONTTOUCH, "Flag used in GetFlags(), etc." ); + ScriptRegisterConstant( g_pScriptVM, FL_BASEVELOCITY, "Flag used in GetFlags(), etc." ); + ScriptRegisterConstant( g_pScriptVM, FL_WORLDBRUSH, "Flag used in GetFlags(), etc." ); + ScriptRegisterConstant( g_pScriptVM, FL_OBJECT, "Flag used in GetFlags(), etc." ); + ScriptRegisterConstant( g_pScriptVM, FL_KILLME, "Flag used in GetFlags(), etc." ); + ScriptRegisterConstant( g_pScriptVM, FL_ONFIRE, "Flag used in GetFlags(), etc." ); + ScriptRegisterConstant( g_pScriptVM, FL_DISSOLVING, "Flag used in GetFlags(), etc." ); + ScriptRegisterConstant( g_pScriptVM, FL_TRANSRAGDOLL, "Flag used in GetFlags(), etc." ); + ScriptRegisterConstant( g_pScriptVM, FL_UNBLOCKABLE_BY_PLAYER, "Flag used in GetFlags(), etc." ); + + // + // Entity Flags + // + ScriptRegisterConstant( g_pScriptVM, EFL_KILLME, "Entity flag used in GetEFlags(), etc." ); + ScriptRegisterConstant( g_pScriptVM, EFL_DORMANT, "Entity flag used in GetEFlags(), etc." ); + ScriptRegisterConstant( g_pScriptVM, EFL_NOCLIP_ACTIVE, "Entity flag used in GetEFlags(), etc." ); + ScriptRegisterConstant( g_pScriptVM, EFL_SETTING_UP_BONES, "Entity flag used in GetEFlags(), etc." ); + ScriptRegisterConstant( g_pScriptVM, EFL_KEEP_ON_RECREATE_ENTITIES, "Entity flag used in GetEFlags(), etc." ); + ScriptRegisterConstant( g_pScriptVM, EFL_HAS_PLAYER_CHILD, "Entity flag used in GetEFlags(), etc." ); + ScriptRegisterConstant( g_pScriptVM, EFL_DIRTY_SHADOWUPDATE, "Entity flag used in GetEFlags(), etc." ); + ScriptRegisterConstant( g_pScriptVM, EFL_NOTIFY, "Entity flag used in GetEFlags(), etc." ); + ScriptRegisterConstant( g_pScriptVM, EFL_FORCE_CHECK_TRANSMIT, "Entity flag used in GetEFlags(), etc." ); + ScriptRegisterConstant( g_pScriptVM, EFL_BOT_FROZEN, "Entity flag used in GetEFlags(), etc." ); + ScriptRegisterConstant( g_pScriptVM, EFL_SERVER_ONLY, "Entity flag used in GetEFlags(), etc." ); + ScriptRegisterConstant( g_pScriptVM, EFL_NO_AUTO_EDICT_ATTACH, "Entity flag used in GetEFlags(), etc." ); + ScriptRegisterConstant( g_pScriptVM, EFL_DIRTY_ABSTRANSFORM, "Entity flag used in GetEFlags(), etc." ); + ScriptRegisterConstant( g_pScriptVM, EFL_DIRTY_ABSVELOCITY, "Entity flag used in GetEFlags(), etc." ); + ScriptRegisterConstant( g_pScriptVM, EFL_DIRTY_ABSANGVELOCITY, "Entity flag used in GetEFlags(), etc." ); + ScriptRegisterConstant( g_pScriptVM, EFL_DIRTY_SURROUNDING_COLLISION_BOUNDS, "Entity flag used in GetEFlags(), etc." ); + ScriptRegisterConstant( g_pScriptVM, EFL_DIRTY_SPATIAL_PARTITION, "Entity flag used in GetEFlags(), etc." ); + ScriptRegisterConstant( g_pScriptVM, EFL_FORCE_ALLOW_MOVEPARENT, "Entity flag used in GetEFlags(), etc." ); + //ScriptRegisterConstant( g_pScriptVM, EFL_PLUGIN_BASED_BOT, "Entity flag used in GetEFlags(), etc." ); + ScriptRegisterConstant( g_pScriptVM, EFL_IN_SKYBOX, "Entity flag used in GetEFlags(), etc." ); + ScriptRegisterConstant( g_pScriptVM, EFL_USE_PARTITION_WHEN_NOT_SOLID, "Entity flag used in GetEFlags(), etc." ); + ScriptRegisterConstant( g_pScriptVM, EFL_TOUCHING_FLUID, "Entity flag used in GetEFlags(), etc." ); + ScriptRegisterConstant( g_pScriptVM, EFL_IS_BEING_LIFTED_BY_BARNACLE, "Entity flag used in GetEFlags(), etc." ); + ScriptRegisterConstant( g_pScriptVM, EFL_NO_ROTORWASH_PUSH, "Entity flag used in GetEFlags(), etc." ); + ScriptRegisterConstant( g_pScriptVM, EFL_NO_THINK_FUNCTION, "Entity flag used in GetEFlags(), etc." ); + ScriptRegisterConstant( g_pScriptVM, EFL_NO_GAME_PHYSICS_SIMULATION, "Entity flag used in GetEFlags(), etc." ); + ScriptRegisterConstant( g_pScriptVM, EFL_CHECK_UNTOUCH, "Entity flag used in GetEFlags(), etc." ); + ScriptRegisterConstant( g_pScriptVM, EFL_DONTBLOCKLOS, "Entity flag used in GetEFlags(), etc." ); + ScriptRegisterConstant( g_pScriptVM, EFL_DONTWALKON, "Entity flag used in GetEFlags(), etc." ); + ScriptRegisterConstant( g_pScriptVM, EFL_NO_DISSOLVE, "Entity flag used in GetEFlags(), etc." ); + ScriptRegisterConstant( g_pScriptVM, EFL_NO_MEGAPHYSCANNON_RAGDOLL, "Entity flag used in GetEFlags(), etc." ); + ScriptRegisterConstant( g_pScriptVM, EFL_NO_WATER_VELOCITY_CHANGE, "Entity flag used in GetEFlags(), etc." ); + ScriptRegisterConstant( g_pScriptVM, EFL_NO_PHYSCANNON_INTERACTION, "Entity flag used in GetEFlags(), etc." ); + ScriptRegisterConstant( g_pScriptVM, EFL_NO_DAMAGE_FORCES, "Entity flag used in GetEFlags(), etc." ); + + // + // Effects + // + ScriptRegisterConstant( g_pScriptVM, EF_BONEMERGE, "Effect flag used in GetEffects(), etc." ); + ScriptRegisterConstant( g_pScriptVM, EF_BRIGHTLIGHT, "Effect flag used in GetEffects(), etc." ); + ScriptRegisterConstant( g_pScriptVM, EF_DIMLIGHT, "Effect flag used in GetEffects(), etc." ); + ScriptRegisterConstant( g_pScriptVM, EF_NOINTERP, "Effect flag used in GetEffects(), etc." ); + ScriptRegisterConstant( g_pScriptVM, EF_NOSHADOW, "Effect flag used in GetEffects(), etc." ); + ScriptRegisterConstant( g_pScriptVM, EF_NODRAW, "Effect flag used in GetEffects(), etc." ); + ScriptRegisterConstant( g_pScriptVM, EF_NORECEIVESHADOW, "Effect flag used in GetEffects(), etc." ); + ScriptRegisterConstant( g_pScriptVM, EF_BONEMERGE_FASTCULL, "Effect flag used in GetEffects(), etc." ); + ScriptRegisterConstant( g_pScriptVM, EF_ITEM_BLINK, "Effect flag used in GetEffects(), etc." ); + ScriptRegisterConstant( g_pScriptVM, EF_PARENT_ANIMATES, "Effect flag used in GetEffects(), etc." ); + + // + // Solid Types + // + ScriptRegisterConstant( g_pScriptVM, SOLID_NONE, "Solid type used by VPhysics" ); + ScriptRegisterConstant( g_pScriptVM, SOLID_BSP, "Solid type used by VPhysics" ); + ScriptRegisterConstant( g_pScriptVM, SOLID_BBOX, "Solid type used by VPhysics" ); + ScriptRegisterConstant( g_pScriptVM, SOLID_OBB, "Solid type used by VPhysics" ); + ScriptRegisterConstant( g_pScriptVM, SOLID_OBB_YAW, "Solid type used by VPhysics" ); + ScriptRegisterConstant( g_pScriptVM, SOLID_CUSTOM, "Solid type used by VPhysics" ); + ScriptRegisterConstant( g_pScriptVM, SOLID_VPHYSICS, "Solid type used by VPhysics" ); + + // + // Solid Flags + // + ScriptRegisterConstant( g_pScriptVM, FSOLID_CUSTOMRAYTEST, "Solid flag used in GetSolidFlags(), etc." ); + ScriptRegisterConstant( g_pScriptVM, FSOLID_CUSTOMBOXTEST, "Solid flag used in GetSolidFlags(), etc." ); + ScriptRegisterConstant( g_pScriptVM, FSOLID_NOT_SOLID, "Solid flag used in GetSolidFlags(), etc." ); + ScriptRegisterConstant( g_pScriptVM, FSOLID_TRIGGER, "Solid flag used in GetSolidFlags(), etc." ); + ScriptRegisterConstant( g_pScriptVM, FSOLID_NOT_STANDABLE, "Solid flag used in GetSolidFlags(), etc." ); + ScriptRegisterConstant( g_pScriptVM, FSOLID_VOLUME_CONTENTS, "Solid flag used in GetSolidFlags(), etc." ); + ScriptRegisterConstant( g_pScriptVM, FSOLID_FORCE_WORLD_ALIGNED, "Solid flag used in GetSolidFlags(), etc." ); + ScriptRegisterConstant( g_pScriptVM, FSOLID_USE_TRIGGER_BOUNDS, "Solid flag used in GetSolidFlags(), etc." ); + ScriptRegisterConstant( g_pScriptVM, FSOLID_ROOT_PARENT_ALIGNED, "Solid flag used in GetSolidFlags(), etc." ); + ScriptRegisterConstant( g_pScriptVM, FSOLID_TRIGGER_TOUCH_DEBRIS, "Solid flag used in GetSolidFlags(), etc." ); + ScriptRegisterConstant( g_pScriptVM, FSOLID_COLLIDE_WITH_OWNER, "Solid flag used in GetSolidFlags(), etc." ); + + // + // Movetypes + // + ScriptRegisterConstant( g_pScriptVM, MOVETYPE_NONE, "Move type used in GetMoveType(), etc." ); + ScriptRegisterConstant( g_pScriptVM, MOVETYPE_ISOMETRIC, "Move type used in GetMoveType(), etc." ); + ScriptRegisterConstant( g_pScriptVM, MOVETYPE_WALK, "Move type used in GetMoveType(), etc." ); + ScriptRegisterConstant( g_pScriptVM, MOVETYPE_STEP, "Move type used in GetMoveType(), etc." ); + ScriptRegisterConstant( g_pScriptVM, MOVETYPE_FLY, "Move type used in GetMoveType(), etc." ); + ScriptRegisterConstant( g_pScriptVM, MOVETYPE_FLYGRAVITY, "Move type used in GetMoveType(), etc." ); + ScriptRegisterConstant( g_pScriptVM, MOVETYPE_VPHYSICS, "Move type used in GetMoveType(), etc." ); + ScriptRegisterConstant( g_pScriptVM, MOVETYPE_PUSH, "Move type used in GetMoveType(), etc." ); + ScriptRegisterConstant( g_pScriptVM, MOVETYPE_NOCLIP, "Move type used in GetMoveType(), etc." ); + ScriptRegisterConstant( g_pScriptVM, MOVETYPE_LADDER, "Move type used in GetMoveType(), etc." ); + ScriptRegisterConstant( g_pScriptVM, MOVETYPE_OBSERVER, "Move type used in GetMoveType(), etc." ); + ScriptRegisterConstant( g_pScriptVM, MOVETYPE_CUSTOM, "Move type used in GetMoveType(), etc." ); + + // + // Animation Stuff + // + ScriptRegisterConstant( g_pScriptVM, AE_TYPE_SERVER, "Animation event flag which indicates an event is supposed to be serverside only." ); + ScriptRegisterConstant( g_pScriptVM, AE_TYPE_SCRIPTED, "Animation event flag with an unknown purpose." ); + ScriptRegisterConstant( g_pScriptVM, AE_TYPE_SHARED, "Animation event flag which indicates an event is supposed to be shared between the server and client." ); + ScriptRegisterConstant( g_pScriptVM, AE_TYPE_WEAPON, "Animation event flag which indicates an event is part of a weapon." ); + ScriptRegisterConstant( g_pScriptVM, AE_TYPE_CLIENT, "Animation event flag which indicates an event is supposed to be clientside only." ); + ScriptRegisterConstant( g_pScriptVM, AE_TYPE_FACEPOSER, "Animation event flag with an unknown purpose. Presumably related to Faceposer." ); + ScriptRegisterConstant( g_pScriptVM, AE_TYPE_NEWEVENTSYSTEM, "Animation event flag which indicates an event is using the new system. This is often used by class-specific events from NPCs." ); + + // + // Ropes + // + ScriptRegisterConstant( g_pScriptVM, ROPE_RESIZE, "Try to keep the rope dangling the same amount even as the rope length changes. (for use in rope flags)" ); + ScriptRegisterConstant( g_pScriptVM, ROPE_BARBED, "Hack option to draw like a barbed wire. (for use in rope flags)" ); + ScriptRegisterConstant( g_pScriptVM, ROPE_COLLIDE, "Collide with the world. (for use in rope flags)" ); + ScriptRegisterConstant( g_pScriptVM, ROPE_SIMULATE, "Is the rope valid? (for use in rope flags)" ); + ScriptRegisterConstant( g_pScriptVM, ROPE_BREAKABLE, "Can the endpoints detach? (for use in rope flags)" ); + ScriptRegisterConstant( g_pScriptVM, ROPE_USE_WIND, "Wind simulation on this rope. (for use in rope flags)" ); + ScriptRegisterConstant( g_pScriptVM, ROPE_INITIAL_HANG, "By default, ropes will simulate for a bit internally when they are created so they sag, but dynamically created ropes for things like harpoons don't want this. (for use in rope flags)" ); + ScriptRegisterConstant( g_pScriptVM, ROPE_PLAYER_WPN_ATTACH, "If this flag is set, then the second attachment must be a player. The rope will attach to \"buff_attach\" on the player's active weapon. This is a flag because it requires special code on the client to find the weapon. (for use in rope flags)" ); + ScriptRegisterConstant( g_pScriptVM, ROPE_NO_GRAVITY, "Disable gravity on this rope. (for use in rope flags)" ); + ScriptRegisterConstant( g_pScriptVM, ROPE_NUMFLAGS, "The number of rope flags recognized by the game." ); + + ScriptRegisterConstantNamed( g_pScriptVM, Vector( ROPE_GRAVITY ), "ROPE_GRAVITY", "Default rope gravity vector." ); + + // + // Sounds + // + ScriptRegisterConstant( g_pScriptVM, CHAN_REPLACE, "The sound channel used when playing sounds through console commands." ); + ScriptRegisterConstant( g_pScriptVM, CHAN_AUTO, "The default generic sound channel." ); + ScriptRegisterConstant( g_pScriptVM, CHAN_WEAPON, "The sound channel for player and NPC weapons." ); + ScriptRegisterConstant( g_pScriptVM, CHAN_VOICE, "The sound channel used for dialogue, voice lines, etc." ); + ScriptRegisterConstant( g_pScriptVM, CHAN_ITEM, "The sound channel used for generic physics impact sounds, health/suit chargers, +use sounds." ); + ScriptRegisterConstant( g_pScriptVM, CHAN_BODY, "The sound channel used for clothing, ragdoll impacts, footsteps, knocking/pounding/punching etc." ); + ScriptRegisterConstant( g_pScriptVM, CHAN_STREAM, "The sound channel for sounds that can be delayed by an async load, i.e. aren't responses to particular events." ); + ScriptRegisterConstant( g_pScriptVM, CHAN_STATIC, "The sound channel for constant/background sound that doesn't require any reaction." ); + ScriptRegisterConstant( g_pScriptVM, CHAN_VOICE2, "An additional sound channel for voices. Used in TF2 for the announcer." ); + ScriptRegisterConstant( g_pScriptVM, CHAN_VOICE_BASE, "The sound channel used for network voice data (online voice communications)." ); + + ScriptRegisterConstant( g_pScriptVM, VOL_NORM, "The standard volume value." ); + ScriptRegisterConstant( g_pScriptVM, PITCH_NORM, "The standard pitch value." ); + ScriptRegisterConstant( g_pScriptVM, PITCH_LOW, "The standard low pitch value." ); + ScriptRegisterConstant( g_pScriptVM, PITCH_HIGH, "The standard high pitch value." ); + + ScriptRegisterConstant( g_pScriptVM, SNDLVL_NONE, "A standard value used for a sound's sound level." ); + ScriptRegisterConstant( g_pScriptVM, SNDLVL_20dB, "A standard value used for a sound's sound level." ); + ScriptRegisterConstant( g_pScriptVM, SNDLVL_25dB, "A standard value used for a sound's sound level." ); + ScriptRegisterConstant( g_pScriptVM, SNDLVL_30dB, "A standard value used for a sound's sound level." ); + ScriptRegisterConstant( g_pScriptVM, SNDLVL_35dB, "A standard value used for a sound's sound level." ); + ScriptRegisterConstant( g_pScriptVM, SNDLVL_40dB, "A standard value used for a sound's sound level." ); + ScriptRegisterConstant( g_pScriptVM, SNDLVL_45dB, "A standard value used for a sound's sound level." ); + ScriptRegisterConstant( g_pScriptVM, SNDLVL_50dB, "A standard value used for a sound's sound level." ); + ScriptRegisterConstant( g_pScriptVM, SNDLVL_55dB, "A standard value used for a sound's sound level." ); + ScriptRegisterConstant( g_pScriptVM, SNDLVL_IDLE, "A standard value used for a sound's sound level." ); + ScriptRegisterConstant( g_pScriptVM, SNDLVL_60dB, "A standard value used for a sound's sound level." ); + ScriptRegisterConstant( g_pScriptVM, SNDLVL_65dB, "A standard value used for a sound's sound level." ); + ScriptRegisterConstant( g_pScriptVM, SNDLVL_STATIC, "A standard value used for a sound's sound level." ); + ScriptRegisterConstant( g_pScriptVM, SNDLVL_70dB, "A standard value used for a sound's sound level." ); + ScriptRegisterConstant( g_pScriptVM, SNDLVL_NORM, "A standard value used for a sound's sound level." ); + ScriptRegisterConstant( g_pScriptVM, SNDLVL_75dB, "A standard value used for a sound's sound level." ); + ScriptRegisterConstant( g_pScriptVM, SNDLVL_80dB, "A standard value used for a sound's sound level." ); + ScriptRegisterConstant( g_pScriptVM, SNDLVL_TALKING, "A standard value used for a sound's sound level." ); + ScriptRegisterConstant( g_pScriptVM, SNDLVL_85dB, "A standard value used for a sound's sound level." ); + ScriptRegisterConstant( g_pScriptVM, SNDLVL_90dB, "A standard value used for a sound's sound level." ); + ScriptRegisterConstant( g_pScriptVM, SNDLVL_95dB, "A standard value used for a sound's sound level." ); + ScriptRegisterConstant( g_pScriptVM, SNDLVL_100dB, "A standard value used for a sound's sound level." ); + ScriptRegisterConstant( g_pScriptVM, SNDLVL_105dB, "A standard value used for a sound's sound level." ); + ScriptRegisterConstant( g_pScriptVM, SNDLVL_110dB, "A standard value used for a sound's sound level." ); + ScriptRegisterConstant( g_pScriptVM, SNDLVL_120dB, "A standard value used for a sound's sound level." ); + ScriptRegisterConstant( g_pScriptVM, SNDLVL_130dB, "A standard value used for a sound's sound level." ); + ScriptRegisterConstant( g_pScriptVM, SNDLVL_GUNFIRE, "A standard value used for a sound's sound level." ); + ScriptRegisterConstant( g_pScriptVM, SNDLVL_140dB, "A standard value used for a sound's sound level." ); + ScriptRegisterConstant( g_pScriptVM, SNDLVL_150dB, "A standard value used for a sound's sound level." ); + ScriptRegisterConstant( g_pScriptVM, SNDLVL_180dB, "A standard value used for a sound's sound level." ); + + ScriptRegisterConstant( g_pScriptVM, SND_CHANGE_VOL, "Indicates a sound is a volume change to an already-playing sound." ); + ScriptRegisterConstant( g_pScriptVM, SND_CHANGE_PITCH, "Indicates a sound is a pitch change to an already-playing sound." ); + ScriptRegisterConstant( g_pScriptVM, SND_STOP, "Indicates a sound is stopping an already-playing sound." ); + ScriptRegisterConstant( g_pScriptVM, SND_SPAWNING, "Indicates a sound is spawning, used in some cases for ambients. Not networked." ); + ScriptRegisterConstant( g_pScriptVM, SND_DELAY, "Indicates a sound has an initial delay." ); + ScriptRegisterConstant( g_pScriptVM, SND_STOP_LOOPING, "Stops all looping sounds on an entity." ); + ScriptRegisterConstant( g_pScriptVM, SND_SPEAKER, "Indicates a sound is being played again by a microphone through a speaker." ); + ScriptRegisterConstant( g_pScriptVM, SND_SHOULDPAUSE, "Forces a sound to pause if the game is paused." ); + ScriptRegisterConstant( g_pScriptVM, SND_IGNORE_PHONEMES, "Prevents the entity emitting this sound from using its phonemes (no lip-syncing)." ); + ScriptRegisterConstant( g_pScriptVM, SND_IGNORE_NAME, "Used to change all sounds emitted by an entity, regardless of name." ); + ScriptRegisterConstant( g_pScriptVM, SND_DO_NOT_OVERWRITE_EXISTING_ON_CHANNEL, "Prevents a sound from interrupting other sounds on a channel (if the channel supports interruption)." ); + + ScriptRegisterConstant( g_pScriptVM, GENDER_NONE, "A standard value used to represent no specific gender. Usually used for sounds." ); + ScriptRegisterConstant( g_pScriptVM, GENDER_MALE, "A standard value used to represent male gender. Usually used for sounds." ); + ScriptRegisterConstant( g_pScriptVM, GENDER_FEMALE, "A standard value used to represent female gender. Usually used for sounds." ); + +#ifdef GAME_DLL + // + // AI Sounds + // (QueryHearSound hook can use these) + // + ScriptRegisterConstant( g_pScriptVM, SOUND_NONE, "Sound type used in QueryHearSound hooks, etc." ); + ScriptRegisterConstant( g_pScriptVM, SOUND_COMBAT, "Sound type used in QueryHearSound hooks, etc." ); + ScriptRegisterConstant( g_pScriptVM, SOUND_WORLD, "Sound type used in QueryHearSound hooks, etc." ); + ScriptRegisterConstant( g_pScriptVM, SOUND_PLAYER, "Sound type used in QueryHearSound hooks, etc." ); + ScriptRegisterConstant( g_pScriptVM, SOUND_DANGER, "Sound type used in QueryHearSound hooks, etc." ); + ScriptRegisterConstant( g_pScriptVM, SOUND_BULLET_IMPACT, "Sound type used in QueryHearSound hooks, etc." ); + ScriptRegisterConstant( g_pScriptVM, SOUND_CARCASS, "Sound type used in QueryHearSound hooks, etc." ); + ScriptRegisterConstant( g_pScriptVM, SOUND_MEAT, "Sound type used in QueryHearSound hooks, etc." ); + ScriptRegisterConstant( g_pScriptVM, SOUND_GARBAGE, "Sound type used in QueryHearSound hooks, etc." ); + ScriptRegisterConstant( g_pScriptVM, SOUND_THUMPER, "Sound type used in QueryHearSound hooks, etc." ); + ScriptRegisterConstant( g_pScriptVM, SOUND_BUGBAIT, "Sound type used in QueryHearSound hooks, etc." ); + ScriptRegisterConstant( g_pScriptVM, SOUND_PHYSICS_DANGER, "Sound type used in QueryHearSound hooks, etc." ); + ScriptRegisterConstant( g_pScriptVM, SOUND_DANGER_SNIPERONLY, "Sound type used in QueryHearSound hooks, etc." ); + ScriptRegisterConstant( g_pScriptVM, SOUND_MOVE_AWAY, "Sound type used in QueryHearSound hooks, etc." ); + ScriptRegisterConstant( g_pScriptVM, SOUND_PLAYER_VEHICLE, "Sound type used in QueryHearSound hooks, etc." ); + ScriptRegisterConstant( g_pScriptVM, SOUND_READINESS_LOW, "Sound type used in QueryHearSound hooks, etc." ); + ScriptRegisterConstant( g_pScriptVM, SOUND_READINESS_MEDIUM, "Sound type used in QueryHearSound hooks, etc." ); + ScriptRegisterConstant( g_pScriptVM, SOUND_READINESS_HIGH, "Sound type used in QueryHearSound hooks, etc." ); + + ScriptRegisterConstant( g_pScriptVM, SOUND_CONTEXT_FROM_SNIPER, "Sound context used in QueryHearSound hooks, etc." ); + ScriptRegisterConstant( g_pScriptVM, SOUND_CONTEXT_GUNFIRE, "Sound context used in QueryHearSound hooks, etc." ); + ScriptRegisterConstant( g_pScriptVM, SOUND_CONTEXT_MORTAR, "Sound context used in QueryHearSound hooks, etc." ); + ScriptRegisterConstant( g_pScriptVM, SOUND_CONTEXT_COMBINE_ONLY, "Sound context used in QueryHearSound hooks, etc." ); + ScriptRegisterConstant( g_pScriptVM, SOUND_CONTEXT_REACT_TO_SOURCE, "Sound context used in QueryHearSound hooks, etc." ); + ScriptRegisterConstant( g_pScriptVM, SOUND_CONTEXT_EXPLOSION, "Sound context used in QueryHearSound hooks, etc." ); + ScriptRegisterConstant( g_pScriptVM, SOUND_CONTEXT_EXCLUDE_COMBINE, "Sound context used in QueryHearSound hooks, etc." ); + ScriptRegisterConstant( g_pScriptVM, SOUND_CONTEXT_DANGER_APPROACH, "Sound context used in QueryHearSound hooks, etc." ); + ScriptRegisterConstant( g_pScriptVM, SOUND_CONTEXT_ALLIES_ONLY, "Sound context used in QueryHearSound hooks, etc." ); + ScriptRegisterConstant( g_pScriptVM, SOUND_CONTEXT_PLAYER_VEHICLE, "Sound context used in QueryHearSound hooks, etc." ); + ScriptRegisterConstant( g_pScriptVM, SOUND_CONTEXT_OWNER_ALLIES, "Sound context used in QueryHearSound hooks, etc." ); + + ScriptRegisterConstant( g_pScriptVM, ALL_CONTEXTS, "All sound contexts useable in QueryHearSound hooks, etc." ); + ScriptRegisterConstant( g_pScriptVM, ALL_SCENTS, "All \"scent\" sound types useable in QueryHearSound hooks, etc." ); + ScriptRegisterConstant( g_pScriptVM, ALL_SOUNDS, "All sound types useable in QueryHearSound hooks, etc." ); + + ScriptRegisterConstant( g_pScriptVM, SOUNDENT_CHANNEL_UNSPECIFIED, "Sound channel used in QueryHearSound hooks, etc." ); + ScriptRegisterConstant( g_pScriptVM, SOUNDENT_CHANNEL_REPEATING, "Sound channel used in QueryHearSound hooks, etc." ); + ScriptRegisterConstant( g_pScriptVM, SOUNDENT_CHANNEL_REPEATED_DANGER, "Sound channel used in QueryHearSound hooks, etc." ); + ScriptRegisterConstant( g_pScriptVM, SOUNDENT_CHANNEL_REPEATED_PHYSICS_DANGER, "Sound channel used in QueryHearSound hooks, etc." ); + ScriptRegisterConstant( g_pScriptVM, SOUNDENT_CHANNEL_WEAPON, "Sound channel used in QueryHearSound hooks, etc." ); + ScriptRegisterConstant( g_pScriptVM, SOUNDENT_CHANNEL_INJURY, "Sound channel used in QueryHearSound hooks, etc." ); + ScriptRegisterConstant( g_pScriptVM, SOUNDENT_CHANNEL_BULLET_IMPACT, "Sound channel used in QueryHearSound hooks, etc." ); + ScriptRegisterConstant( g_pScriptVM, SOUNDENT_CHANNEL_NPC_FOOTSTEP, "Sound channel used in QueryHearSound hooks, etc." ); + ScriptRegisterConstant( g_pScriptVM, SOUNDENT_CHANNEL_SPOOKY_NOISE, "Sound channel used in QueryHearSound hooks, etc." ); + ScriptRegisterConstant( g_pScriptVM, SOUNDENT_CHANNEL_ZOMBINE_GRENADE, "Sound channel used in QueryHearSound hooks, etc." ); + + ScriptRegisterConstantNamed( g_pScriptVM, (int)SOUNDENT_VOLUME_MACHINEGUN, "SOUNDENT_VOLUME_MACHINEGUN", "Sound volume preset for use in InsertAISound, etc." ); + ScriptRegisterConstantNamed( g_pScriptVM, (int)SOUNDENT_VOLUME_SHOTGUN, "SOUNDENT_VOLUME_SHOTGUN", "Sound volume preset for use in InsertAISound, etc." ); + ScriptRegisterConstantNamed( g_pScriptVM, (int)SOUNDENT_VOLUME_PISTOL, "SOUNDENT_VOLUME_PISTOL", "Sound volume preset for use in InsertAISound, etc." ); + ScriptRegisterConstantNamed( g_pScriptVM, (int)SOUNDENT_VOLUME_EMPTY, "SOUNDENT_VOLUME_PISTOL", "Sound volume preset for use in InsertAISound, etc." ); + + // + // Capabilities + // + ScriptRegisterConstant( g_pScriptVM, bits_CAP_MOVE_GROUND, "NPC/player/weapon capability used in GetCapabilities(), etc." ); + ScriptRegisterConstant( g_pScriptVM, bits_CAP_MOVE_JUMP, "NPC/player/weapon capability used in GetCapabilities(), etc." ); + ScriptRegisterConstant( g_pScriptVM, bits_CAP_MOVE_FLY, "NPC/player/weapon capability used in GetCapabilities(), etc." ); + ScriptRegisterConstant( g_pScriptVM, bits_CAP_MOVE_CLIMB, "NPC/player/weapon capability used in GetCapabilities(), etc." ); + ScriptRegisterConstant( g_pScriptVM, bits_CAP_MOVE_SWIM, "NPC/player/weapon capability used in GetCapabilities(), etc." ); + ScriptRegisterConstant( g_pScriptVM, bits_CAP_MOVE_CRAWL, "NPC/player/weapon capability used in GetCapabilities(), etc." ); + ScriptRegisterConstant( g_pScriptVM, bits_CAP_MOVE_SHOOT, "NPC/player/weapon capability used in GetCapabilities(), etc." ); + ScriptRegisterConstant( g_pScriptVM, bits_CAP_SKIP_NAV_GROUND_CHECK, "NPC/player/weapon capability used in GetCapabilities(), etc." ); + ScriptRegisterConstant( g_pScriptVM, bits_CAP_USE, "NPC/player/weapon capability used in GetCapabilities(), etc." ); + //ScriptRegisterConstant( g_pScriptVM, bits_CAP_HEAR, "NPC/player/weapon capability used in GetCapabilities(), etc." ); + ScriptRegisterConstant( g_pScriptVM, bits_CAP_AUTO_DOORS, "NPC/player/weapon capability used in GetCapabilities(), etc." ); + ScriptRegisterConstant( g_pScriptVM, bits_CAP_OPEN_DOORS, "NPC/player/weapon capability used in GetCapabilities(), etc." ); + ScriptRegisterConstant( g_pScriptVM, bits_CAP_TURN_HEAD, "NPC/player/weapon capability used in GetCapabilities(), etc." ); + ScriptRegisterConstant( g_pScriptVM, bits_CAP_WEAPON_RANGE_ATTACK1, "NPC/player/weapon capability used in GetCapabilities(), etc." ); + ScriptRegisterConstant( g_pScriptVM, bits_CAP_WEAPON_RANGE_ATTACK2, "NPC/player/weapon capability used in GetCapabilities(), etc." ); + ScriptRegisterConstant( g_pScriptVM, bits_CAP_WEAPON_MELEE_ATTACK1, "NPC/player/weapon capability used in GetCapabilities(), etc." ); + ScriptRegisterConstant( g_pScriptVM, bits_CAP_WEAPON_MELEE_ATTACK2, "NPC/player/weapon capability used in GetCapabilities(), etc." ); + ScriptRegisterConstant( g_pScriptVM, bits_CAP_INNATE_RANGE_ATTACK1, "NPC/player/weapon capability used in GetCapabilities(), etc." ); + ScriptRegisterConstant( g_pScriptVM, bits_CAP_INNATE_RANGE_ATTACK2, "NPC/player/weapon capability used in GetCapabilities(), etc." ); + ScriptRegisterConstant( g_pScriptVM, bits_CAP_INNATE_MELEE_ATTACK1, "NPC/player/weapon capability used in GetCapabilities(), etc." ); + ScriptRegisterConstant( g_pScriptVM, bits_CAP_INNATE_MELEE_ATTACK2, "NPC/player/weapon capability used in GetCapabilities(), etc." ); + ScriptRegisterConstant( g_pScriptVM, bits_CAP_USE_WEAPONS, "NPC/player/weapon capability used in GetCapabilities(), etc." ); + //ScriptRegisterConstant( g_pScriptVM, bits_CAP_STRAFE, "NPC/player/weapon capability used in GetCapabilities(), etc." ); + ScriptRegisterConstant( g_pScriptVM, bits_CAP_ANIMATEDFACE, "NPC/player/weapon capability used in GetCapabilities(), etc." ); + ScriptRegisterConstant( g_pScriptVM, bits_CAP_USE_SHOT_REGULATOR, "NPC/player/weapon capability used in GetCapabilities(), etc." ); + ScriptRegisterConstant( g_pScriptVM, bits_CAP_FRIENDLY_DMG_IMMUNE, "NPC/player/weapon capability used in GetCapabilities(), etc." ); + ScriptRegisterConstant( g_pScriptVM, bits_CAP_SQUAD, "NPC/player/weapon capability used in GetCapabilities(), etc." ); + ScriptRegisterConstant( g_pScriptVM, bits_CAP_DUCK, "NPC/player/weapon capability used in GetCapabilities(), etc." ); + ScriptRegisterConstant( g_pScriptVM, bits_CAP_NO_HIT_PLAYER, "NPC/player/weapon capability used in GetCapabilities(), etc." ); + ScriptRegisterConstant( g_pScriptVM, bits_CAP_AIM_GUN, "NPC/player/weapon capability used in GetCapabilities(), etc." ); + ScriptRegisterConstant( g_pScriptVM, bits_CAP_NO_HIT_SQUADMATES, "NPC/player/weapon capability used in GetCapabilities(), etc." ); + ScriptRegisterConstant( g_pScriptVM, bits_CAP_SIMPLE_RADIUS_DAMAGE, "NPC/player/weapon capability used in GetCapabilities(), etc." ); + + ScriptRegisterConstant( g_pScriptVM, bits_CAP_DOORS_GROUP, "NPC/player/weapon capability used in GetCapabilities(), etc." ); + ScriptRegisterConstant( g_pScriptVM, bits_CAP_RANGE_ATTACK_GROUP, "NPC/player/weapon capability used in GetCapabilities(), etc." ); + ScriptRegisterConstant( g_pScriptVM, bits_CAP_MELEE_ATTACK_GROUP, "NPC/player/weapon capability used in GetCapabilities(), etc." ); + + // + // Class_T classes + // + ScriptRegisterConstant( g_pScriptVM, CLASS_NONE, "No class." ); + ScriptRegisterConstant( g_pScriptVM, CLASS_PLAYER, "Used by players." ); + +#ifdef HL2_DLL + + ScriptRegisterConstant( g_pScriptVM, CLASS_PLAYER_ALLY, "Used by citizens, hacked manhacks, and other misc. allies." ); + ScriptRegisterConstant( g_pScriptVM, CLASS_PLAYER_ALLY_VITAL, "Used by Alyx, Barney, and other allies vital to HL2." ); + ScriptRegisterConstant( g_pScriptVM, CLASS_ANTLION, "Used by antlions, antlion guards, etc." ); + ScriptRegisterConstant( g_pScriptVM, CLASS_BARNACLE, "Used by barnacles." ); + ScriptRegisterConstant( g_pScriptVM, CLASS_BULLSEYE, "Used by npc_bullseye." ); + //ScriptRegisterConstant( g_pScriptVM, CLASS_BULLSQUID, "Used by bullsquids." ); + ScriptRegisterConstant( g_pScriptVM, CLASS_CITIZEN_PASSIVE, "Used by citizens when the \"gordon_precriminal\" or \"citizens_passive\" states are enabled." ); + ScriptRegisterConstant( g_pScriptVM, CLASS_CITIZEN_REBEL, "UNUSED IN HL2. Rebels normally use CLASS_PLAYER_ALLY." ); + ScriptRegisterConstant( g_pScriptVM, CLASS_COMBINE, "Used by Combine soldiers, Combine turrets, and other misc. Combine NPCs." ); + ScriptRegisterConstant( g_pScriptVM, CLASS_COMBINE_GUNSHIP, "Used by Combine gunships, helicopters, etc." ); + ScriptRegisterConstant( g_pScriptVM, CLASS_CONSCRIPT, "UNUSED IN HL2. Would've been used by conscripts." ); + ScriptRegisterConstant( g_pScriptVM, CLASS_HEADCRAB, "Used by headcrabs." ); + //ScriptRegisterConstant( g_pScriptVM, CLASS_HOUNDEYE, "Used by houndeyes." ); + ScriptRegisterConstant( g_pScriptVM, CLASS_MANHACK, "Used by Combine manhacks." ); + ScriptRegisterConstant( g_pScriptVM, CLASS_METROPOLICE, "Used by Combine metrocops." ); + ScriptRegisterConstant( g_pScriptVM, CLASS_MILITARY, "In HL2, this is only used by npc_combinecamera and func_guntarget. This appears to be recognized as a Combine class." ); + ScriptRegisterConstant( g_pScriptVM, CLASS_SCANNER, "Used by Combine city scanners and claw scanners." ); + ScriptRegisterConstant( g_pScriptVM, CLASS_STALKER, "Used by Combine stalkers." ); + ScriptRegisterConstant( g_pScriptVM, CLASS_VORTIGAUNT, "Used by vortigaunts." ); + ScriptRegisterConstant( g_pScriptVM, CLASS_ZOMBIE, "Used by zombies." ); + ScriptRegisterConstant( g_pScriptVM, CLASS_PROTOSNIPER, "Used by Combine snipers." ); + ScriptRegisterConstant( g_pScriptVM, CLASS_MISSILE, "Used by RPG and APC missiles." ); + ScriptRegisterConstant( g_pScriptVM, CLASS_FLARE, "Used by env_flares." ); + ScriptRegisterConstant( g_pScriptVM, CLASS_EARTH_FAUNA, "Used by birds and other terrestrial animals." ); + ScriptRegisterConstant( g_pScriptVM, CLASS_HACKED_ROLLERMINE, "Used by rollermines which were hacked by Alyx." ); + ScriptRegisterConstant( g_pScriptVM, CLASS_COMBINE_HUNTER, "Used by Combine hunters." ); + +#elif defined( HL1_DLL ) + + ScriptRegisterConstant( g_pScriptVM, CLASS_HUMAN_PASSIVE, "Used by scientists." ); + ScriptRegisterConstant( g_pScriptVM, CLASS_HUMAN_MILITARY, "Used by HECU marines, etc." ); + ScriptRegisterConstant( g_pScriptVM, CLASS_ALIEN_MILITARY, "Used by alien grunts, alien slaves/vortigaunts, etc." ); + ScriptRegisterConstant( g_pScriptVM, CLASS_ALIEN_MONSTER, "Used by zombies, houndeyes, barnacles, and other misc. monsters." ); + ScriptRegisterConstant( g_pScriptVM, CLASS_ALIEN_PREY, "Used by headcrabs, etc." ); + ScriptRegisterConstant( g_pScriptVM, CLASS_ALIEN_PREDATOR, "Used by bullsquids, etc." ); + ScriptRegisterConstant( g_pScriptVM, CLASS_INSECT, "Used by cockroaches." ); + ScriptRegisterConstant( g_pScriptVM, CLASS_PLAYER_ALLY, "Used by security guards/Barneys." ); + ScriptRegisterConstant( g_pScriptVM, CLASS_PLAYER_BIOWEAPON, "Used by a player's hivehand hornets." ); + ScriptRegisterConstant( g_pScriptVM, CLASS_ALIEN_BIOWEAPON, "Used by an alien grunt's hivehand hornets." ); + +#else + + ScriptRegisterConstant( g_pScriptVM, CLASS_PLAYER_ALLY, "Used by player allies." ); + +#endif + + ScriptRegisterConstant( g_pScriptVM, NUM_AI_CLASSES, "Number of AI classes." ); + + // + // Misc. AI + // + ScriptRegisterConstant( g_pScriptVM, NPC_STATE_INVALID, "NPC state type used in GetNPCState(), etc." ); + ScriptRegisterConstant( g_pScriptVM, NPC_STATE_NONE, "NPC state type used in GetNPCState(), etc." ); + ScriptRegisterConstant( g_pScriptVM, NPC_STATE_IDLE, "NPC state type used in GetNPCState(), etc." ); + ScriptRegisterConstant( g_pScriptVM, NPC_STATE_ALERT, "NPC state type used in GetNPCState(), etc." ); + ScriptRegisterConstant( g_pScriptVM, NPC_STATE_COMBAT, "NPC state type used in GetNPCState(), etc." ); + ScriptRegisterConstant( g_pScriptVM, NPC_STATE_SCRIPT, "NPC state type used in GetNPCState(), etc." ); + ScriptRegisterConstant( g_pScriptVM, NPC_STATE_PLAYDEAD, "NPC state type used in GetNPCState(), etc." ); + ScriptRegisterConstant( g_pScriptVM, NPC_STATE_PRONE, "When in clutches of barnacle (NPC state type used in GetNPCState(), etc.)" ); + ScriptRegisterConstant( g_pScriptVM, NPC_STATE_DEAD, "NPC state type used in GetNPCState(), etc." ); + + ScriptRegisterConstant( g_pScriptVM, AISS_AWAKE, "NPC is awake. (NPC sleep state used in Get/SetSleepState())" ); + ScriptRegisterConstant( g_pScriptVM, AISS_WAITING_FOR_THREAT, "NPC is asleep and will awaken upon seeing an enemy. (NPC sleep state used in Get/SetSleepState())" ); + ScriptRegisterConstant( g_pScriptVM, AISS_WAITING_FOR_PVS, "NPC is asleep and will awaken upon entering a player's PVS. (NPC sleep state used in Get/SetSleepState())" ); + ScriptRegisterConstant( g_pScriptVM, AISS_WAITING_FOR_INPUT, "NPC is asleep and will only awaken upon receiving the Wake input. (NPC sleep state used in Get/SetSleepState())" ); + //ScriptRegisterConstant( g_pScriptVM, AISS_AUTO_PVS, "" ); + //ScriptRegisterConstant( g_pScriptVM, AISS_AUTO_PVS_AFTER_PVS, "" ); + ScriptRegisterConstant( g_pScriptVM, AI_SLEEP_FLAGS_NONE, "No sleep flags. (NPC sleep flag used in Add/Remove/HasSleepFlags())" ); + ScriptRegisterConstant( g_pScriptVM, AI_SLEEP_FLAG_AUTO_PVS, "Indicates a NPC will sleep upon exiting PVS. (NPC sleep flag used in Add/Remove/HasSleepFlags())" ); + ScriptRegisterConstant( g_pScriptVM, AI_SLEEP_FLAG_AUTO_PVS_AFTER_PVS, "Indicates a NPC will sleep upon exiting PVS after entering PVS for the first time(?) (NPC sleep flag used in Add/Remove/HasSleepFlags())" ); + + ScriptRegisterConstantNamed( g_pScriptVM, CAI_BaseNPC::SCRIPT_PLAYING, "SCRIPT_PLAYING", "Playing the action animation." ); + ScriptRegisterConstantNamed( g_pScriptVM, CAI_BaseNPC::SCRIPT_WAIT, "SCRIPT_WAIT", "Waiting on everyone in the script to be ready. Plays the pre idle animation if there is one." ); + ScriptRegisterConstantNamed( g_pScriptVM, CAI_BaseNPC::SCRIPT_POST_IDLE, "SCRIPT_POST_IDLE", "Playing the post idle animation after playing the action animation." ); + ScriptRegisterConstantNamed( g_pScriptVM, CAI_BaseNPC::SCRIPT_CLEANUP, "SCRIPT_CLEANUP", "Cancelling the script / cleaning up." ); + ScriptRegisterConstantNamed( g_pScriptVM, CAI_BaseNPC::SCRIPT_WALK_TO_MARK, "SCRIPT_WALK_TO_MARK", "Walking to the scripted sequence position." ); + ScriptRegisterConstantNamed( g_pScriptVM, CAI_BaseNPC::SCRIPT_RUN_TO_MARK, "SCRIPT_RUN_TO_MARK", "Running to the scripted sequence position." ); + ScriptRegisterConstantNamed( g_pScriptVM, CAI_BaseNPC::SCRIPT_PLAYING, "SCRIPT_PLAYING", "Moving to the scripted sequence position while playing a custom movement animation." ); + + ScriptRegisterConstant( g_pScriptVM, D_ER, "'Error' relationship definition. Used by NPCs and players for relationship disposition." ); + ScriptRegisterConstant( g_pScriptVM, D_HT, "Denotes a 'Hate' relationship. Used by NPCs and players for relationship disposition." ); + ScriptRegisterConstant( g_pScriptVM, D_FR, "Denotes a 'Fear' relationship. Used by NPCs and players for relationship disposition." ); + ScriptRegisterConstant( g_pScriptVM, D_LI, "Denotes a 'Like' relationship. Used by NPCs and players for relationship disposition." ); + ScriptRegisterConstant( g_pScriptVM, D_NU, "Denotes a 'Neutral' relationship. Used by NPCs and players for relationship disposition." ); +#endif + + // + // Misc. General + // + ScriptRegisterConstant( g_pScriptVM, DAMAGE_NO, "Don't take damage (Use with GetTakeDamage/SetTakeDamage)" ); + ScriptRegisterConstant( g_pScriptVM, DAMAGE_EVENTS_ONLY, "Call damage functions, but don't modify health (Use with GetTakeDamage/SetTakeDamage)" ); + ScriptRegisterConstant( g_pScriptVM, DAMAGE_YES, "Allow damage to be taken (Use with GetTakeDamage/SetTakeDamage)" ); + ScriptRegisterConstant( g_pScriptVM, DAMAGE_AIM, "(Use with GetTakeDamage/SetTakeDamage)" ); + +#ifdef GAME_DLL + ScriptRegisterConstant( g_pScriptVM, GLOBAL_OFF, "Global state used by the Globals singleton." ); + ScriptRegisterConstant( g_pScriptVM, GLOBAL_ON, "Global state used by the Globals singleton." ); + ScriptRegisterConstant( g_pScriptVM, GLOBAL_DEAD, "Global state used by the Globals singleton." ); +#endif + + RegisterWeaponScriptConstants(); +} diff --git a/src/game/shared/mapbase/vscript_consts_weapons.cpp b/src/game/shared/mapbase/vscript_consts_weapons.cpp new file mode 100644 index 00000000..ad63efb7 --- /dev/null +++ b/src/game/shared/mapbase/vscript_consts_weapons.cpp @@ -0,0 +1,98 @@ +//========= Mapbase - https://github.com/mapbase-source/source-sdk-2013 ============// +// +// Purpose: VScript constants and enums shared between the server and client. +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" +#include "basecombatweapon_shared.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + + +//============================================================================= +//============================================================================= + +BEGIN_SCRIPTENUM( WeaponSound, "Weapon sounds." ) + + DEFINE_ENUMCONST( EMPTY, "" ) + DEFINE_ENUMCONST( SINGLE, "" ) + DEFINE_ENUMCONST( SINGLE_NPC, "" ) + DEFINE_ENUMCONST( WPN_DOUBLE, "" ) + DEFINE_ENUMCONST( DOUBLE_NPC, "" ) + DEFINE_ENUMCONST( BURST, "" ) + DEFINE_ENUMCONST( RELOAD, "" ) + DEFINE_ENUMCONST( RELOAD_NPC, "" ) + DEFINE_ENUMCONST( MELEE_MISS, "" ) + DEFINE_ENUMCONST( MELEE_HIT, "" ) + DEFINE_ENUMCONST( MELEE_HIT_WORLD, "" ) + DEFINE_ENUMCONST( SPECIAL1, "" ) + DEFINE_ENUMCONST( SPECIAL2, "" ) + DEFINE_ENUMCONST( SPECIAL3, "" ) + DEFINE_ENUMCONST( TAUNT, "" ) + DEFINE_ENUMCONST( DEPLOY, "" ) + + DEFINE_ENUMCONST( NUM_SHOOT_SOUND_TYPES, "" ) + +END_SCRIPTENUM(); + +//============================================================================= +//============================================================================= + +void RegisterWeaponScriptConstants() +{ + // + // Weapon classify + // + ScriptRegisterConstant( g_pScriptVM, WEPCLASS_INVALID, "Invalid weapon class." ); + ScriptRegisterConstant( g_pScriptVM, WEPCLASS_HANDGUN, "Weapon class for pistols, revolvers, etc." ); + ScriptRegisterConstant( g_pScriptVM, WEPCLASS_RIFLE, "Weapon class for (assault) rifles, SMGs, etc." ); + ScriptRegisterConstant( g_pScriptVM, WEPCLASS_SHOTGUN, "Weapon class for shotguns." ); + ScriptRegisterConstant( g_pScriptVM, WEPCLASS_HEAVY, "Weapon class for RPGs, etc." ); + + ScriptRegisterConstant( g_pScriptVM, WEPCLASS_MELEE, "Weapon class for melee weapons." ); + + // + // Vector cones + // + ScriptRegisterConstantFromTemp( g_pScriptVM, VECTOR_CONE_PRECALCULATED, "This is just a zero vector, but it adds some context indicating that the person writing the code is not allowing " + "FireBullets() to modify the direction of the shot because the shot direction " + "being passed into the function has already been modified by another piece of " + "code and should be fired as specified." ); + + ScriptRegisterConstantFromTemp( g_pScriptVM, VECTOR_CONE_1DEGREES, "1-degree weapon vector cone." ); + ScriptRegisterConstantFromTemp( g_pScriptVM, VECTOR_CONE_2DEGREES, "2-degree weapon vector cone." ); + ScriptRegisterConstantFromTemp( g_pScriptVM, VECTOR_CONE_3DEGREES, "3-degree weapon vector cone." ); + ScriptRegisterConstantFromTemp( g_pScriptVM, VECTOR_CONE_4DEGREES, "4-degree weapon vector cone." ); + ScriptRegisterConstantFromTemp( g_pScriptVM, VECTOR_CONE_5DEGREES, "5-degree weapon vector cone." ); + ScriptRegisterConstantFromTemp( g_pScriptVM, VECTOR_CONE_6DEGREES, "6-degree weapon vector cone." ); + ScriptRegisterConstantFromTemp( g_pScriptVM, VECTOR_CONE_7DEGREES, "7-degree weapon vector cone." ); + ScriptRegisterConstantFromTemp( g_pScriptVM, VECTOR_CONE_8DEGREES, "8-degree weapon vector cone." ); + ScriptRegisterConstantFromTemp( g_pScriptVM, VECTOR_CONE_9DEGREES, "9-degree weapon vector cone." ); + ScriptRegisterConstantFromTemp( g_pScriptVM, VECTOR_CONE_10DEGREES, "10-degree weapon vector cone." ); + ScriptRegisterConstantFromTemp( g_pScriptVM, VECTOR_CONE_15DEGREES, "15-degree weapon vector cone." ); + ScriptRegisterConstantFromTemp( g_pScriptVM, VECTOR_CONE_20DEGREES, "20-degree weapon vector cone." ); + + // + // Weapon proficiency + // + ScriptRegisterConstant( g_pScriptVM, WEAPON_PROFICIENCY_INVALID, "Invalid weapon proficiency." ); + ScriptRegisterConstant( g_pScriptVM, WEAPON_PROFICIENCY_POOR, "Poor weapon proficiency. Causes low accuracy." ); + ScriptRegisterConstant( g_pScriptVM, WEAPON_PROFICIENCY_AVERAGE, "Average weapon proficiency. Causes average accuracy." ); + ScriptRegisterConstant( g_pScriptVM, WEAPON_PROFICIENCY_GOOD, "Good weapon proficiency. Causes good accuracy." ); + ScriptRegisterConstant( g_pScriptVM, WEAPON_PROFICIENCY_VERY_GOOD, "Very good weapon proficiency. Causes very good accuracy." ); + ScriptRegisterConstant( g_pScriptVM, WEAPON_PROFICIENCY_PERFECT, "Perfect weapon proficiency. Causes perfect accuracy." ); + + // + // Autoaim + // + ScriptRegisterConstant( g_pScriptVM, AUTOAIM_2DEGREES, "2-degree autoaim cone." ); + ScriptRegisterConstant( g_pScriptVM, AUTOAIM_5DEGREES, "5-degree autoaim cone." ); + ScriptRegisterConstant( g_pScriptVM, AUTOAIM_8DEGREES, "8-degree autoaim cone." ); + ScriptRegisterConstant( g_pScriptVM, AUTOAIM_10DEGREES, "10-degree autoaim cone." ); + ScriptRegisterConstant( g_pScriptVM, AUTOAIM_20DEGREES, "20-degree autoaim cone." ); + ScriptRegisterConstant( g_pScriptVM, AUTOAIM_SCALE_DEFAULT, "Indicates default auto aim scale." ); + ScriptRegisterConstant( g_pScriptVM, AUTOAIM_SCALE_DIRECT_ONLY, "Indicates auto aim should not be used except for direct hits." ); +} diff --git a/src/game/shared/mapbase/vscript_funcs_hl2.cpp b/src/game/shared/mapbase/vscript_funcs_hl2.cpp new file mode 100644 index 00000000..529d0291 --- /dev/null +++ b/src/game/shared/mapbase/vscript_funcs_hl2.cpp @@ -0,0 +1,112 @@ +//========= Mapbase - https://github.com/mapbase-source/source-sdk-2013 ============// +// +// Purpose: VScript functions for Half-Life 2. +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" + +#include "hl2_gamerules.h" +#ifndef CLIENT_DLL +#include "eventqueue.h" +#include "weapon_physcannon.h" +#include "player_pickup.h" +#endif + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + + +#ifndef CLIENT_DLL +extern CBaseEntity *CreatePlayerLoadSave( Vector vOrigin, float flDuration, float flHoldTime, float flLoadTime ); + +HSCRIPT ScriptGameOver( const char *pszMessage, float flDelay, float flFadeTime, float flLoadTime, int r, int g, int b ) +{ + CBaseEntity *pPlayer = AI_GetSinglePlayer(); + if (pPlayer) + { + UTIL_ShowMessage( pszMessage, ToBasePlayer( pPlayer ) ); + ToBasePlayer( pPlayer )->NotifySinglePlayerGameEnding(); + } + else + { + // TODO: How should MP handle this? + return NULL; + } + + CBaseEntity *pReload = CreatePlayerLoadSave( vec3_origin, flFadeTime, flLoadTime + 1.0f, flLoadTime ); + if (pReload) + { + pReload->SetRenderColor( r, g, b, 255 ); + g_EventQueue.AddEvent( pReload, "Reload", flDelay, pReload, pReload ); + } + + return ToHScript( pReload ); +} + +bool ScriptMegaPhyscannonActive() +{ + return HL2GameRules()->MegaPhyscannonActive(); +} + +void ScriptPickup_ForcePlayerToDropThisObject( HSCRIPT hTarget ) +{ + Pickup_ForcePlayerToDropThisObject( ToEnt( hTarget ) ); +} + +float ScriptPlayerPickupGetHeldObjectMass( HSCRIPT hPickupControllerEntity, HSCRIPT hHeldObject ) +{ + IPhysicsObject *pPhysObj = HScriptToClass( hHeldObject ); + if (!pPhysObj) + { + CBaseEntity *pEnt = ToEnt( hHeldObject ); + if (pEnt) + pPhysObj = pEnt->VPhysicsGetObject(); + } + + if (!pPhysObj) + { + Warning( "PlayerPickupGetHeldObjectMass: Invalid physics object\n" ); + return 0.0f; + } + + return PlayerPickupGetHeldObjectMass( ToEnt( hPickupControllerEntity ), pPhysObj ); +} + +HSCRIPT ScriptGetPlayerHeldEntity( HSCRIPT hPlayer ) +{ + CBasePlayer *pPlayer = ToBasePlayer( ToEnt( hPlayer ) ); + if (!pPlayer) + return NULL; + + return ToHScript( GetPlayerHeldEntity( pPlayer ) ); +} + +HSCRIPT ScriptPhysCannonGetHeldEntity( HSCRIPT hWeapon ) +{ + CBaseEntity *pEnt = ToEnt( hWeapon ); + if (!pEnt) + return NULL; + + return ToHScript( PhysCannonGetHeldEntity( pEnt->MyCombatWeaponPointer() ) ); +} +#endif + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CHalfLife2::RegisterScriptFunctions( void ) +{ + BaseClass::RegisterScriptFunctions(); + +#ifndef CLIENT_DLL + ScriptRegisterFunctionNamed( g_pScriptVM, ScriptGameOver, "GameOver", "Ends the game and reloads the last save." ); + ScriptRegisterFunctionNamed( g_pScriptVM, ScriptMegaPhyscannonActive, "MegaPhyscannonActive", "Checks if supercharged gravity gun mode is enabled." ); + + ScriptRegisterFunctionNamed( g_pScriptVM, ScriptPickup_ForcePlayerToDropThisObject, "Pickup_ForcePlayerToDropThisObject", "If the specified entity is being carried, instantly drops it." ); + ScriptRegisterFunctionNamed( g_pScriptVM, ScriptPlayerPickupGetHeldObjectMass, "PlayerPickupGetHeldObjectMass", "Gets the mass of the specified player_pickup controller, with the second parameter the held object's physics object." ); + ScriptRegisterFunctionNamed( g_pScriptVM, ScriptGetPlayerHeldEntity, "GetPlayerHeldEntity", "Gets the specified player's currently held entity." ); + ScriptRegisterFunctionNamed( g_pScriptVM, ScriptPhysCannonGetHeldEntity, "PhysCannonGetHeldEntity", "Gets the specified gravity gun's currently held entity." ); +#endif +} diff --git a/src/game/shared/mapbase/vscript_funcs_shared.cpp b/src/game/shared/mapbase/vscript_funcs_shared.cpp new file mode 100644 index 00000000..9e0f0942 --- /dev/null +++ b/src/game/shared/mapbase/vscript_funcs_shared.cpp @@ -0,0 +1,1128 @@ +//========= Mapbase - https://github.com/mapbase-source/source-sdk-2013 ============// +// +// Purpose: This file contains general shared VScript bindings which Mapbase adds onto +// what was ported from Alien Swarm instead of cluttering the existing files. +// +// This includes various functions, classes, etc. which were either created from +// scratch or were based on/inspired by things documented in APIs from L4D2 or even +// Source 2 games like Dota 2 or Half-Life: Alyx. +// +// Other VScript bindings can be found in files like vscript_singletons.cpp and +// things not exclusive to the game DLLs are embedded/recreated in the library itself +// via vscript_bindings_base.cpp. +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" +#include "matchers.h" +#include "takedamageinfo.h" + +#ifndef CLIENT_DLL +#include "globalstate.h" +#include "vscript_server.h" +#include "soundent.h" +#include "rope.h" +#include "ai_basenpc.h" +#else +#include "c_rope.h" +#endif // CLIENT_DLL + +#include "con_nprint.h" +#include "particle_parse.h" +#include "npcevent.h" + +#include "vscript_funcs_shared.h" +#include "vscript_singletons.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +extern IScriptManager *scriptmanager; + +#ifndef CLIENT_DLL +void EmitSoundOn( const char *pszSound, HSCRIPT hEnt ) +{ + CBaseEntity *pEnt = ToEnt( hEnt ); + if (!pEnt) + return; + + pEnt->EmitSound( pszSound ); +} + +void EmitSoundOnClient( const char *pszSound, HSCRIPT hEnt, HSCRIPT hPlayer ) +{ + CBaseEntity *pEnt = ToEnt( hEnt ); + CBasePlayer *pPlayer = ToBasePlayer( ToEnt( hPlayer ) ); + if (!pEnt || !pPlayer) + return; + + CSingleUserRecipientFilter filter( pPlayer ); + + EmitSound_t params; + params.m_pSoundName = pszSound; + params.m_flSoundTime = 0.0f; + params.m_pflSoundDuration = NULL; + params.m_bWarnOnDirectWaveReference = true; + + pEnt->EmitSound( filter, pEnt->entindex(), params ); +} + +void AddThinkToEnt( HSCRIPT entity, const char *pszFuncName ) +{ + CBaseEntity *pEntity = ToEnt( entity ); + if (!pEntity) + return; + + pEntity->ScriptSetThinkFunction(pszFuncName, TICK_INTERVAL); +} + +void ParseScriptTableKeyValues( CBaseEntity *pEntity, HSCRIPT hKV ) +{ + int nIterator = -1; + ScriptVariant_t varKey, varValue; + while ((nIterator = g_pScriptVM->GetKeyValue( hKV, nIterator, &varKey, &varValue )) != -1) + { + switch (varValue.GetType()) + { + case FIELD_CSTRING: pEntity->KeyValue( varKey.Get(), varValue.Get()); break; + case FIELD_INTEGER: pEntity->KeyValueFromInt( varKey.Get(), varValue.Get() ); break; + case FIELD_FLOAT: pEntity->KeyValue( varKey.Get(), varValue.Get() ); break; + case FIELD_VECTOR: pEntity->KeyValue( varKey.Get(), varValue.Get() ); break; + case FIELD_HSCRIPT: + { + if ( varValue.Get() ) + { + // Entity + if (ToEnt( varValue.Get() )) + { + pEntity->KeyValue( varKey.Get(), STRING( ToEnt( varValue.Get() )->GetEntityName() ) ); + } + + // Color + else if (Color *color = HScriptToClass( varValue.Get() )) + { + char szTemp[64]; + Q_snprintf( szTemp, sizeof( szTemp ), "%i %i %i %i", color->r(), color->g(), color->b(), color->a() ); + pEntity->KeyValue( varKey.Get(), szTemp ); + } + } + break; + } + } + + g_pScriptVM->ReleaseValue( varKey ); + g_pScriptVM->ReleaseValue( varValue ); + } +} + +void PrecacheEntityFromTable( const char *pszClassname, HSCRIPT hKV ) +{ + if ( IsEntityCreationAllowedInScripts() == false ) + { + CGWarning( 0, CON_GROUP_VSCRIPT, "VScript error: A script attempted to create an entity mid-game. Due to the server's settings, entity creation from scripts is only allowed during map init.\n" ); + return; + } + + // This is similar to UTIL_PrecacheOther(), but we can't check if we can only precache it once. + // Probably for the best anyway, as similar classes can still have different precachable properties. + CBaseEntity *pEntity = CreateEntityByName( pszClassname ); + if (!pEntity) + { + Assert( !"PrecacheEntityFromTable: only works for CBaseEntities" ); + return; + } + + ParseScriptTableKeyValues( pEntity, hKV ); + + pEntity->Precache(); + + UTIL_RemoveImmediate( pEntity ); +} + +HSCRIPT SpawnEntityFromTable( const char *pszClassname, HSCRIPT hKV ) +{ + if ( IsEntityCreationAllowedInScripts() == false ) + { + CGWarning( 0, CON_GROUP_VSCRIPT, "VScript error: A script attempted to create an entity mid-game. Due to the server's settings, entity creation from scripts is only allowed during map init.\n" ); + return NULL; + } + + CBaseEntity *pEntity = CreateEntityByName( pszClassname ); + if ( !pEntity ) + { + Assert( !"SpawnEntityFromTable: only works for CBaseEntities" ); + return NULL; + } + + gEntList.NotifyCreateEntity( pEntity ); + + ParseScriptTableKeyValues( pEntity, hKV ); + + DispatchSpawn( pEntity ); + pEntity->Activate(); + + return ToHScript( pEntity ); +} +#endif + +HSCRIPT EntIndexToHScript( int index ) +{ +#ifdef GAME_DLL + edict_t *e = INDEXENT(index); + if ( e && !e->IsFree() ) + { + return ToHScript( GetContainingEntity( e ) ); + } +#else // CLIENT_DLL + if ( index < NUM_ENT_ENTRIES ) + { + return ToHScript( CBaseEntity::Instance( index ) ); + } +#endif + return NULL; +} + +//----------------------------------------------------------------------------- +// Mapbase-specific functions start here +//----------------------------------------------------------------------------- + +#ifndef CLIENT_DLL +void SaveEntityKVToTable( HSCRIPT hEnt, HSCRIPT hTable ) +{ + CBaseEntity *pEnt = ToEnt( hEnt ); + if (pEnt == NULL) + return; + + variant_t var; // For Set() + ScriptVariant_t varScript, varTable = hTable; + + // loop through the data description list, reading each data desc block + for ( datamap_t *dmap = pEnt->GetDataDescMap(); dmap != NULL; dmap = dmap->baseMap ) + { + // search through all the readable fields in the data description, looking for a match + for ( int i = 0; i < dmap->dataNumFields; i++ ) + { + if ( dmap->dataDesc[i].flags & (FTYPEDESC_KEY) ) + { + var.Set( dmap->dataDesc[i].fieldType, ((char*)pEnt) + dmap->dataDesc[i].fieldOffset[ TD_OFFSET_NORMAL ] ); + var.SetScriptVariant( varScript ); + g_pScriptVM->SetValue( varTable, dmap->dataDesc[i].externalName, varScript ); + } + } + } +} + +HSCRIPT SpawnEntityFromKeyValues( const char *pszClassname, HSCRIPT hKV ) +{ + if ( IsEntityCreationAllowedInScripts() == false ) + { + Warning( "VScript error: A script attempted to create an entity mid-game. Due to the server's settings, entity creation from scripts is only allowed during map init.\n" ); + return NULL; + } + + CBaseEntity *pEntity = CreateEntityByName( pszClassname ); + if ( !pEntity ) + { + Assert( !"SpawnEntityFromKeyValues: only works for CBaseEntities" ); + return NULL; + } + + gEntList.NotifyCreateEntity( pEntity ); + + KeyValues *pKV = scriptmanager->GetKeyValuesFromScriptKV( g_pScriptVM, hKV ); + for (pKV = pKV->GetFirstSubKey(); pKV != NULL; pKV = pKV->GetNextKey()) + { + pEntity->KeyValue( pKV->GetName(), pKV->GetString() ); + } + + DispatchSpawn( pEntity ); + pEntity->Activate(); + + return ToHScript( pEntity ); +} + +void ScriptDispatchSpawn( HSCRIPT hEntity ) +{ + CBaseEntity *pEntity = ToEnt( hEntity ); + if (pEntity) + { + DispatchSpawn( pEntity ); + } +} +#endif // !CLIENT_DLL + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +static HSCRIPT CreateDamageInfo( HSCRIPT hInflictor, HSCRIPT hAttacker, const Vector &vecForce, const Vector &vecDamagePos, float flDamage, int iDamageType ) +{ + // The script is responsible for deleting this via DestroyDamageInfo(). + CTakeDamageInfo *damageInfo = new CTakeDamageInfo( ToEnt(hInflictor), ToEnt(hAttacker), flDamage, iDamageType ); + HSCRIPT hScript = g_pScriptVM->RegisterInstance( damageInfo ); + + damageInfo->SetDamagePosition( vecDamagePos ); + damageInfo->SetDamageForce( vecForce ); + + return hScript; +} + +static void DestroyDamageInfo( HSCRIPT hDamageInfo ) +{ + CTakeDamageInfo *pInfo = HScriptToClass< CTakeDamageInfo >( hDamageInfo ); + if ( pInfo ) + { + g_pScriptVM->RemoveInstance( hDamageInfo ); + delete pInfo; + } +} + +void ScriptCalculateExplosiveDamageForce( HSCRIPT info, const Vector &vecDir, const Vector &vecForceOrigin, float flScale ) +{ + CTakeDamageInfo *pInfo = HScriptToClass< CTakeDamageInfo >( info ); + if ( pInfo ) + { + CalculateExplosiveDamageForce( pInfo, vecDir, vecForceOrigin, flScale ); + } +} + +void ScriptCalculateBulletDamageForce( HSCRIPT info, int iBulletType, const Vector &vecBulletDir, const Vector &vecForceOrigin, float flScale ) +{ + CTakeDamageInfo *pInfo = HScriptToClass< CTakeDamageInfo >( info ); + if ( pInfo ) + { + CalculateBulletDamageForce( pInfo, iBulletType, vecBulletDir, vecForceOrigin, flScale ); + } +} + +void ScriptCalculateMeleeDamageForce( HSCRIPT info, const Vector &vecMeleeDir, const Vector &vecForceOrigin, float flScale ) +{ + CTakeDamageInfo *pInfo = HScriptToClass< CTakeDamageInfo >( info ); + if ( pInfo ) + { + CalculateMeleeDamageForce( pInfo, vecMeleeDir, vecForceOrigin, flScale ); + } +} + +void ScriptGuessDamageForce( HSCRIPT info, const Vector &vecForceDir, const Vector &vecForceOrigin, float flScale ) +{ + CTakeDamageInfo *pInfo = HScriptToClass< CTakeDamageInfo >( info ); + if ( pInfo ) + { + GuessDamageForce( pInfo, vecForceDir, vecForceOrigin, flScale ); + } +} + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +BEGIN_SCRIPTDESC_ROOT_NAMED( CScriptGameTrace, "CGameTrace", "trace_t" ) + DEFINE_SCRIPTFUNC( DidHitWorld, "Returns whether the trace hit the world entity or not." ) + DEFINE_SCRIPTFUNC( DidHitNonWorldEntity, "Returns whether the trace hit something other than the world entity." ) + DEFINE_SCRIPTFUNC( GetEntityIndex, "Returns the index of whatever entity this trace hit." ) + DEFINE_SCRIPTFUNC( DidHit, "Returns whether the trace hit anything." ) + + DEFINE_SCRIPTFUNC( FractionLeftSolid, "If this trace started within a solid, this is the point in the trace's fraction at which it left that solid." ) + DEFINE_SCRIPTFUNC( HitGroup, "Returns the specific hit group this trace hit if it hit an entity." ) + DEFINE_SCRIPTFUNC( PhysicsBone, "Returns the physics bone this trace hit if it hit an entity." ) + DEFINE_SCRIPTFUNC( Entity, "Returns the entity this trace has hit." ) + DEFINE_SCRIPTFUNC( HitBox, "Returns the hitbox of the entity this trace has hit. If it hit the world entity, this returns the static prop index." ) + + DEFINE_SCRIPTFUNC( IsDispSurface, "Returns whether this trace hit a displacement." ) + DEFINE_SCRIPTFUNC( IsDispSurfaceWalkable, "Returns whether DISPSURF_FLAG_WALKABLE is ticked on the displacement this trace hit." ) + DEFINE_SCRIPTFUNC( IsDispSurfaceBuildable, "Returns whether DISPSURF_FLAG_BUILDABLE is ticked on the displacement this trace hit." ) + DEFINE_SCRIPTFUNC( IsDispSurfaceProp1, "Returns whether DISPSURF_FLAG_SURFPROP1 is ticked on the displacement this trace hit." ) + DEFINE_SCRIPTFUNC( IsDispSurfaceProp2, "Returns whether DISPSURF_FLAG_SURFPROP2 is ticked on the displacement this trace hit." ) + + DEFINE_SCRIPTFUNC( StartPos, "Gets the trace's start position." ) + DEFINE_SCRIPTFUNC( EndPos, "Gets the trace's end position." ) + + DEFINE_SCRIPTFUNC( Fraction, "Gets the fraction of the trace completed. For example, if the trace stopped exactly halfway to the end position, this would be 0.5." ) + DEFINE_SCRIPTFUNC( Contents, "Gets the contents of the surface the trace has hit." ) + DEFINE_SCRIPTFUNC( DispFlags, "Gets the displacement flags of the surface the trace has hit." ) + + DEFINE_SCRIPTFUNC( AllSolid, "Returns whether the trace is completely within a solid." ) + DEFINE_SCRIPTFUNC( StartSolid, "Returns whether the trace started within a solid." ) + + DEFINE_SCRIPTFUNC( Surface, "" ) + DEFINE_SCRIPTFUNC( Plane, "" ) + + DEFINE_SCRIPTFUNC( Destroy, "Deletes this instance. Important for preventing memory leaks." ) +END_SCRIPTDESC(); + +BEGIN_SCRIPTDESC_ROOT_NAMED( scriptsurfacedata_t, "surfacedata_t", "" ) + DEFINE_SCRIPTFUNC( GetFriction, "" ) + DEFINE_SCRIPTFUNC( GetThickness, "" ) + + DEFINE_SCRIPTFUNC( GetJumpFactor, "" ) + DEFINE_SCRIPTFUNC( GetMaterialChar, "" ) + + DEFINE_SCRIPTFUNC( GetSoundStepLeft, "" ) + DEFINE_SCRIPTFUNC( GetSoundStepRight, "" ) + DEFINE_SCRIPTFUNC( GetSoundImpactSoft, "" ) + DEFINE_SCRIPTFUNC( GetSoundImpactHard, "" ) + DEFINE_SCRIPTFUNC( GetSoundScrapeSmooth, "" ) + DEFINE_SCRIPTFUNC( GetSoundScrapeRough, "" ) + DEFINE_SCRIPTFUNC( GetSoundBulletImpact, "" ) + DEFINE_SCRIPTFUNC( GetSoundRolling, "" ) + DEFINE_SCRIPTFUNC( GetSoundBreak, "" ) + DEFINE_SCRIPTFUNC( GetSoundStrain, "" ) +END_SCRIPTDESC(); + +BEGIN_SCRIPTDESC_ROOT_NAMED( CSurfaceScriptHelper, "csurface_t", "" ) + DEFINE_SCRIPTFUNC( Name, "" ) + DEFINE_SCRIPTFUNC( SurfaceProps, "The surface's properties." ) +END_SCRIPTDESC(); + +CPlaneTInstanceHelper g_PlaneTInstanceHelper; + +BEGIN_SCRIPTDESC_ROOT( cplane_t, "" ) + DEFINE_SCRIPT_INSTANCE_HELPER( &g_PlaneTInstanceHelper ) +END_SCRIPTDESC(); + +static HSCRIPT ScriptTraceLineComplex( const Vector &vecStart, const Vector &vecEnd, HSCRIPT entIgnore, int iMask, int iCollisionGroup ) +{ + // The script is responsible for deleting this via Destroy(). + CScriptGameTrace *tr = new CScriptGameTrace(); + + CBaseEntity *pIgnore = ToEnt( entIgnore ); + UTIL_TraceLine( vecStart, vecEnd, iMask, pIgnore, iCollisionGroup, tr ); + + tr->RegisterSurface(); + tr->RegisterPlane(); + + return tr->GetScriptInstance(); +} + +static HSCRIPT ScriptTraceHullComplex( const Vector &vecStart, const Vector &vecEnd, const Vector &hullMin, const Vector &hullMax, + HSCRIPT entIgnore, int iMask, int iCollisionGroup ) +{ + // The script is responsible for deleting this via Destroy(). + CScriptGameTrace *tr = new CScriptGameTrace(); + + CBaseEntity *pIgnore = ToEnt( entIgnore ); + UTIL_TraceHull( vecStart, vecEnd, hullMin, hullMax, iMask, pIgnore, iCollisionGroup, tr ); + + tr->RegisterSurface(); + tr->RegisterPlane(); + + return tr->GetScriptInstance(); +} + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +BEGIN_SCRIPTDESC_ROOT( FireBulletsInfo_t, "" ) + DEFINE_SCRIPT_CONSTRUCTOR() + + DEFINE_SCRIPTFUNC( GetShots, "Gets the number of shots which should be fired." ) + DEFINE_SCRIPTFUNC( SetShots, "Sets the number of shots which should be fired." ) + + DEFINE_SCRIPTFUNC( GetSource, "" ) + DEFINE_SCRIPTFUNC( SetSource, "" ) + DEFINE_SCRIPTFUNC( GetDirShooting, "" ) + DEFINE_SCRIPTFUNC( SetDirShooting, "" ) + DEFINE_SCRIPTFUNC( GetSpread, "" ) + DEFINE_SCRIPTFUNC( SetSpread, "" ) + + DEFINE_SCRIPTFUNC( GetDistance, "Gets the distance the bullets should travel." ) + DEFINE_SCRIPTFUNC( SetDistance, "Sets the distance the bullets should travel." ) + + DEFINE_SCRIPTFUNC( GetAmmoType, "" ) + DEFINE_SCRIPTFUNC( SetAmmoType, "" ) + + DEFINE_SCRIPTFUNC( GetTracerFreq, "" ) + DEFINE_SCRIPTFUNC( SetTracerFreq, "" ) + + DEFINE_SCRIPTFUNC( GetDamage, "Gets the damage the bullets should deal. 0 = use ammo type" ) + DEFINE_SCRIPTFUNC( SetDamage, "Sets the damage the bullets should deal. 0 = use ammo type" ) + DEFINE_SCRIPTFUNC( GetPlayerDamage, "Gets the damage the bullets should deal when hitting the player. 0 = use regular damage" ) + DEFINE_SCRIPTFUNC( SetPlayerDamage, "Sets the damage the bullets should deal when hitting the player. 0 = use regular damage" ) + + DEFINE_SCRIPTFUNC( GetFlags, "Gets the flags the bullets should use." ) + DEFINE_SCRIPTFUNC( SetFlags, "Sets the flags the bullets should use." ) + + DEFINE_SCRIPTFUNC( GetDamageForceScale, "" ) + DEFINE_SCRIPTFUNC( SetDamageForceScale, "" ) + + DEFINE_SCRIPTFUNC_NAMED( ScriptGetAttacker, "GetAttacker", "" ) + DEFINE_SCRIPTFUNC_NAMED( ScriptSetAttacker, "SetAttacker", "" ) + DEFINE_SCRIPTFUNC_NAMED( ScriptGetAdditionalIgnoreEnt, "GetAdditionalIgnoreEnt", "" ) + DEFINE_SCRIPTFUNC_NAMED( ScriptSetAdditionalIgnoreEnt, "SetAdditionalIgnoreEnt", "" ) + + DEFINE_SCRIPTFUNC( GetPrimaryAttack, "Gets whether the bullets came from a primary attack." ) + DEFINE_SCRIPTFUNC( SetPrimaryAttack, "Sets whether the bullets came from a primary attack." ) +END_SCRIPTDESC(); + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +HSCRIPT FireBulletsInfo_t::ScriptGetAttacker() +{ + return ToHScript( m_pAttacker ); +} + +void FireBulletsInfo_t::ScriptSetAttacker( HSCRIPT value ) +{ + m_pAttacker = ToEnt( value ); +} + +HSCRIPT FireBulletsInfo_t::ScriptGetAdditionalIgnoreEnt() +{ + return ToHScript( m_pAdditionalIgnoreEnt ); +} + +void FireBulletsInfo_t::ScriptSetAdditionalIgnoreEnt( HSCRIPT value ) +{ + m_pAdditionalIgnoreEnt = ToEnt( value ); +} + +static HSCRIPT CreateFireBulletsInfo( int cShots, const Vector &vecSrc, const Vector &vecDirShooting, + const Vector &vecSpread, float iDamage, HSCRIPT pAttacker ) +{ + // The script is responsible for deleting this via DestroyFireBulletsInfo(). + FireBulletsInfo_t *info = new FireBulletsInfo_t(); + HSCRIPT hScript = g_pScriptVM->RegisterInstance( info ); + + info->SetShots( cShots ); + info->SetSource( vecSrc ); + info->SetDirShooting( vecDirShooting ); + info->SetSpread( vecSpread ); + info->SetDamage( iDamage ); + info->ScriptSetAttacker( pAttacker ); + + return hScript; +} + +static void DestroyFireBulletsInfo( HSCRIPT hBulletsInfo ) +{ + FireBulletsInfo_t *pInfo = HScriptToClass< FireBulletsInfo_t >( hBulletsInfo ); + if ( pInfo ) + { + g_pScriptVM->RemoveInstance( hBulletsInfo ); + delete pInfo; + } +} + +//----------------------------------------------------------------------------- +// animevent_t +//----------------------------------------------------------------------------- +CAnimEventTInstanceHelper g_AnimEventTInstanceHelper; + +BEGIN_SCRIPTDESC_ROOT( scriptanimevent_t, "" ) + DEFINE_SCRIPT_INSTANCE_HELPER( &g_AnimEventTInstanceHelper ) + + DEFINE_SCRIPTFUNC( GetEvent, "" ) + DEFINE_SCRIPTFUNC( SetEvent, "" ) + + DEFINE_SCRIPTFUNC( GetOptions, "" ) + DEFINE_SCRIPTFUNC( SetOptions, "" ) + + DEFINE_SCRIPTFUNC( GetCycle, "" ) + DEFINE_SCRIPTFUNC( SetCycle, "" ) + + DEFINE_SCRIPTFUNC( GetEventTime, "" ) + DEFINE_SCRIPTFUNC( SetEventTime, "" ) + + DEFINE_SCRIPTFUNC( GetType, "Gets the event's type flags. See the 'AE_TYPE_' set of constants for valid flags." ) + DEFINE_SCRIPTFUNC( SetType, "Sets the event's type flags. See the 'AE_TYPE_' set of constants for valid flags." ) + + DEFINE_SCRIPTFUNC( GetSource, "Gets the event's source entity." ) + DEFINE_SCRIPTFUNC( SetSource, "Sets the event's source entity." ) +END_SCRIPTDESC(); + +bool CAnimEventTInstanceHelper::Get( void *p, const char *pszKey, ScriptVariant_t &variant ) +{ + DevWarning( "VScript animevent_t.%s: animevent_t metamethod members are deprecated! Use 'script_help animevent_t' to see the correct functions.\n", pszKey ); + + animevent_t *ani = ((animevent_t *)p); + if (FStrEq( pszKey, "event" )) + variant = ani->event; + else if (FStrEq( pszKey, "options" )) + variant = ani->options; + else if (FStrEq( pszKey, "cycle" )) + variant = ani->cycle; + else if (FStrEq( pszKey, "eventtime" )) + variant = ani->eventtime; + else if (FStrEq( pszKey, "type" )) + variant = ani->type; + else if (FStrEq( pszKey, "source" )) + variant = ToHScript(ani->pSource); + else + return false; + + return true; +} + +bool CAnimEventTInstanceHelper::Set( void *p, const char *pszKey, ScriptVariant_t &variant ) +{ + DevWarning( "VScript animevent_t.%s: animevent_t metamethod members are deprecated! Use 'script_help animevent_t' to see the correct functions.\n", pszKey ); + + animevent_t *ani = ((animevent_t *)p); + if (FStrEq( pszKey, "event" )) + ani->event = variant; + else if (FStrEq( pszKey, "options" )) + ani->options = variant; + else if (FStrEq( pszKey, "cycle" )) + ani->cycle = variant; + else if (FStrEq( pszKey, "eventtime" )) + ani->eventtime = variant; + else if (FStrEq( pszKey, "type" )) + ani->type = variant; + else if (FStrEq( pszKey, "source" )) + { + CBaseEntity *pEnt = ToEnt( variant.Get() ); + if (pEnt) + ani->pSource = pEnt->GetBaseAnimating(); + } + else + return false; + + return true; +} + +//----------------------------------------------------------------------------- +// EmitSound_t +//----------------------------------------------------------------------------- +BEGIN_SCRIPTDESC_ROOT_NAMED( ScriptEmitSound_t, "EmitSound_t", "" ) + DEFINE_SCRIPT_CONSTRUCTOR() + + DEFINE_SCRIPTFUNC( GetChannel, "" ) + DEFINE_SCRIPTFUNC( SetChannel, "" ) + + DEFINE_SCRIPTFUNC( GetSoundName, "Gets the sound's file path or soundscript name." ) + DEFINE_SCRIPTFUNC( SetSoundName, "Sets the sound's file path or soundscript name." ) + + DEFINE_SCRIPTFUNC( GetVolume, "(Note that this may not apply to soundscripts)" ) + DEFINE_SCRIPTFUNC( SetVolume, "(Note that this may not apply to soundscripts)" ) + + DEFINE_SCRIPTFUNC( GetSoundLevel, "Gets the sound's level in decibels. (Note that this may not apply to soundscripts)" ) + DEFINE_SCRIPTFUNC( SetSoundLevel, "Sets the sound's level in decibels. (Note that this may not apply to soundscripts)" ) + + DEFINE_SCRIPTFUNC( GetFlags, "Gets the sound's flags. See the 'SND_' set of constants." ) + DEFINE_SCRIPTFUNC( SetFlags, "Sets the sound's flags. See the 'SND_' set of constants." ) + + DEFINE_SCRIPTFUNC( GetSpecialDSP, "" ) + DEFINE_SCRIPTFUNC( SetSpecialDSP, "" ) + + DEFINE_SCRIPTFUNC( HasOrigin, "Returns true if the sound has an origin override." ) + DEFINE_SCRIPTFUNC( GetOrigin, "Gets the sound's origin override." ) + DEFINE_SCRIPTFUNC( SetOrigin, "Sets the sound's origin override." ) + DEFINE_SCRIPTFUNC( ClearOrigin, "Clears the sound's origin override if it has one." ) + + DEFINE_SCRIPTFUNC( GetSoundTime, "Gets the time the sound will begin, relative to Time()." ) + DEFINE_SCRIPTFUNC( SetSoundTime, "Sets the time the sound will begin, relative to Time()." ) + + DEFINE_SCRIPTFUNC( GetEmitCloseCaption, "Gets whether or not the sound will emit closed captioning/subtitles." ) + DEFINE_SCRIPTFUNC( SetEmitCloseCaption, "Sets whether or not the sound will emit closed captioning/subtitles." ) + + DEFINE_SCRIPTFUNC( GetWarnOnMissingCloseCaption, "Gets whether or not the sound will send a message to the console if there is no corresponding closed captioning token." ) + DEFINE_SCRIPTFUNC( SetWarnOnMissingCloseCaption, "Sets whether or not the sound will send a message to the console if there is no corresponding closed captioning token." ) + + DEFINE_SCRIPTFUNC( GetWarnOnDirectWaveReference, "Gets whether or not the sound will send a message to the console if it references a direct sound file instead of a soundscript." ) + DEFINE_SCRIPTFUNC( SetWarnOnDirectWaveReference, "Sets whether or not the sound will send a message to the console if it references a direct sound file instead of a soundscript." ) + + DEFINE_SCRIPTFUNC( GetSpeakerEntity, "Gets the sound's original source if it is being transmitted by a microphone." ) + DEFINE_SCRIPTFUNC( SetSpeakerEntity, "Sets the sound's original source if it is being transmitted by a microphone." ) + + DEFINE_SCRIPTFUNC( GetSoundScriptHandle, "" ) + DEFINE_SCRIPTFUNC( SetSoundScriptHandle, "" ) +END_SCRIPTDESC(); + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +BEGIN_SCRIPTDESC_ROOT_NAMED( CScriptUserCmd, "CUserCmd", "" ) + DEFINE_SCRIPTFUNC( GetCommandNumber, "For matching server and client commands for debugging." ) + + DEFINE_SCRIPTFUNC_NAMED( ScriptGetTickCount, "GetTickCount", "The tick the client created this command." ) + + DEFINE_SCRIPTFUNC( GetViewAngles, "Player instantaneous view angles." ) + DEFINE_SCRIPTFUNC( SetViewAngles, "Sets player instantaneous view angles." ) + + DEFINE_SCRIPTFUNC( GetForwardMove, "" ) + DEFINE_SCRIPTFUNC( SetForwardMove, "" ) + DEFINE_SCRIPTFUNC( GetSideMove, "" ) + DEFINE_SCRIPTFUNC( SetSideMove, "" ) + DEFINE_SCRIPTFUNC( GetUpMove, "" ) + DEFINE_SCRIPTFUNC( SetUpMove, "" ) + + DEFINE_SCRIPTFUNC( GetButtons, "Input button state." ) + DEFINE_SCRIPTFUNC( SetButtons, "Sets input button state." ) + DEFINE_SCRIPTFUNC( GetImpulse, "Impulse command issued." ) + DEFINE_SCRIPTFUNC( SetImpulse, "Sets impulse command issued." ) + + DEFINE_SCRIPTFUNC( GetWeaponSelect, "Current weapon id." ) + DEFINE_SCRIPTFUNC( SetWeaponSelect, "Sets current weapon id." ) + DEFINE_SCRIPTFUNC( GetWeaponSubtype, "Current weapon subtype id." ) + DEFINE_SCRIPTFUNC( SetWeaponSubtype, "Sets current weapon subtype id." ) + + DEFINE_SCRIPTFUNC( GetRandomSeed, "For shared random functions." ) + + DEFINE_SCRIPTFUNC( GetMouseX, "Mouse accum in x from create move." ) + DEFINE_SCRIPTFUNC( SetMouseX, "Sets mouse accum in x from create move." ) + DEFINE_SCRIPTFUNC( GetMouseY, "Mouse accum in y from create move." ) + DEFINE_SCRIPTFUNC( SetMouseY, "Sets mouse accum in y from create move." ) +END_SCRIPTDESC(); + +#ifdef GAME_DLL +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +#define DEFINE_ENEMY_INFO_SCRIPTFUNCS(name, desc) \ + DEFINE_SCRIPTFUNC_NAMED( Get##name, #name, "Get " desc ) \ + DEFINE_SCRIPTFUNC( Set##name, "Set " desc ) + +BEGIN_SCRIPTDESC_ROOT_NAMED( Script_AI_EnemyInfo_t, "AI_EnemyInfo_t", "Accessor for information about an enemy." ) + DEFINE_SCRIPTFUNC( Enemy, "" ) + DEFINE_SCRIPTFUNC( SetEnemy, "" ) + DEFINE_ENEMY_INFO_SCRIPTFUNCS( LastKnownLocation, "" ) + DEFINE_ENEMY_INFO_SCRIPTFUNCS( LastSeenLocation, "" ) + DEFINE_ENEMY_INFO_SCRIPTFUNCS( TimeLastSeen, "" ) + DEFINE_ENEMY_INFO_SCRIPTFUNCS( TimeFirstSeen, "" ) + DEFINE_ENEMY_INFO_SCRIPTFUNCS( TimeLastReacquired, "" ) + DEFINE_ENEMY_INFO_SCRIPTFUNCS( TimeValidEnemy, "the time at which the enemy can be selected (reaction delay)." ) + DEFINE_ENEMY_INFO_SCRIPTFUNCS( TimeLastReceivedDamageFrom, "the last time damage was received from this enemy." ) + DEFINE_ENEMY_INFO_SCRIPTFUNCS( TimeAtFirstHand, "the time at which the enemy was seen firsthand." ) + DEFINE_ENEMY_INFO_SCRIPTFUNCS( DangerMemory, "the memory of danger position w/o enemy pointer." ) + DEFINE_ENEMY_INFO_SCRIPTFUNCS( EludedMe, "whether the enemy is not at the last known location." ) + DEFINE_ENEMY_INFO_SCRIPTFUNCS( Unforgettable, "" ) + DEFINE_ENEMY_INFO_SCRIPTFUNCS( MobbedMe, "whether the enemy was part of a mob at some point." ) +END_SCRIPTDESC(); +#endif + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +BEGIN_SCRIPTDESC_ROOT( IPhysicsObject, "VPhysics object class." ) + + DEFINE_SCRIPTFUNC( IsStatic, "" ) + DEFINE_SCRIPTFUNC( IsAsleep, "" ) + DEFINE_SCRIPTFUNC( IsTrigger, "" ) + DEFINE_SCRIPTFUNC( IsFluid, "" ) + DEFINE_SCRIPTFUNC( IsHinged, "" ) + DEFINE_SCRIPTFUNC( IsCollisionEnabled, "" ) + DEFINE_SCRIPTFUNC( IsGravityEnabled, "" ) + DEFINE_SCRIPTFUNC( IsDragEnabled, "" ) + DEFINE_SCRIPTFUNC( IsMotionEnabled, "" ) + DEFINE_SCRIPTFUNC( IsMoveable, "" ) + DEFINE_SCRIPTFUNC( IsAttachedToConstraint, "" ) + + DEFINE_SCRIPTFUNC( EnableCollisions, "" ) + DEFINE_SCRIPTFUNC( EnableGravity, "" ) + DEFINE_SCRIPTFUNC( EnableDrag, "" ) + DEFINE_SCRIPTFUNC( EnableMotion, "" ) + + DEFINE_SCRIPTFUNC( Wake, "" ) + DEFINE_SCRIPTFUNC( Sleep, "" ) + + DEFINE_SCRIPTFUNC( SetMass, "" ) + DEFINE_SCRIPTFUNC( GetMass, "" ) + DEFINE_SCRIPTFUNC( GetInvMass, "" ) + DEFINE_SCRIPTFUNC( GetInertia, "" ) + DEFINE_SCRIPTFUNC( GetInvInertia, "" ) + DEFINE_SCRIPTFUNC( SetInertia, "" ) + + DEFINE_SCRIPTFUNC( ApplyForceCenter, "" ) + DEFINE_SCRIPTFUNC( ApplyForceOffset, "" ) + DEFINE_SCRIPTFUNC( ApplyTorqueCenter, "" ) + + DEFINE_SCRIPTFUNC( GetName, "" ) + +END_SCRIPTDESC(); + +static const Vector &GetPhysVelocity( HSCRIPT hPhys ) +{ + IPhysicsObject *pPhys = HScriptToClass( hPhys ); + if (!pPhys) + return vec3_origin; + + static Vector vecVelocity; + pPhys->GetVelocity( &vecVelocity, NULL ); + return vecVelocity; +} + +static const Vector &GetPhysAngVelocity( HSCRIPT hPhys ) +{ + IPhysicsObject *pPhys = HScriptToClass( hPhys ); + if (!pPhys) + return vec3_origin; + + static Vector vecAngVelocity; + pPhys->GetVelocity( NULL, &vecAngVelocity ); + return vecAngVelocity; +} + +static void SetPhysVelocity( HSCRIPT hPhys, const Vector& vecVelocity, const Vector& vecAngVelocity ) +{ + IPhysicsObject *pPhys = HScriptToClass( hPhys ); + if (!pPhys) + return; + + pPhys->SetVelocity( &vecVelocity, &vecAngVelocity ); +} + +static void AddPhysVelocity( HSCRIPT hPhys, const Vector& vecVelocity, const Vector& vecAngVelocity ) +{ + IPhysicsObject *pPhys = HScriptToClass( hPhys ); + if (!pPhys) + return; + + pPhys->AddVelocity( &vecVelocity, &vecAngVelocity ); +} + +static void ScriptPhysEnableEntityCollisions( HSCRIPT hPhys1, HSCRIPT hPhys2 ) +{ + IPhysicsObject *pPhys1 = HScriptToClass( hPhys1 ); + IPhysicsObject *pPhys2 = HScriptToClass( hPhys2 ); + if (!pPhys1 || !pPhys2) + return; + + PhysEnableEntityCollisions( pPhys1, pPhys2 ); +} + +static void ScriptPhysDisableEntityCollisions( HSCRIPT hPhys1, HSCRIPT hPhys2 ) +{ + IPhysicsObject *pPhys1 = HScriptToClass( hPhys1 ); + IPhysicsObject *pPhys2 = HScriptToClass( hPhys2 ); + if (!pPhys1 || !pPhys2) + return; + + PhysDisableEntityCollisions( pPhys1, pPhys2 ); +} + +//============================================================================= +//============================================================================= + +#ifdef CLIENT_DLL +static int ScriptPrecacheModel( const char *modelname ) +{ + return CBaseEntity::PrecacheModel( modelname ); +} + +static void ScriptPrecacheOther( const char *classname ) +{ + UTIL_PrecacheOther( classname ); +} +#else +static int ScriptPrecacheModel( const char *modelname, bool bPreload ) +{ + return CBaseEntity::PrecacheModel( modelname, bPreload ); +} + +static void ScriptPrecacheOther( const char *classname, const char *modelName ) +{ + UTIL_PrecacheOther( classname, modelName ); +} + +// TODO: Move this? +static void ScriptInsertSound( int iType, const Vector &vecOrigin, int iVolume, float flDuration, HSCRIPT hOwner, int soundChannelIndex, HSCRIPT hSoundTarget ) +{ + CSoundEnt::InsertSound( iType, vecOrigin, iVolume, flDuration, ToEnt(hOwner), soundChannelIndex, ToEnt(hSoundTarget) ); +} +#endif + +//============================================================================= +//============================================================================= + +static void ScriptEntitiesInBox( HSCRIPT hTable, int listMax, const Vector &hullMin, const Vector &hullMax, int iMask ) +{ + CBaseEntity *list[1024]; + int count = UTIL_EntitiesInBox( list, listMax, hullMin, hullMax, iMask ); + + for ( int i = 0; i < count; i++ ) + { + g_pScriptVM->ArrayAppend( hTable, ToHScript(list[i]) ); + } +} + +static void ScriptEntitiesAtPoint( HSCRIPT hTable, int listMax, const Vector &point, int iMask ) +{ + CBaseEntity *list[1024]; + int count = UTIL_EntitiesAtPoint( list, listMax, point, iMask ); + + for ( int i = 0; i < count; i++ ) + { + g_pScriptVM->ArrayAppend( hTable, ToHScript(list[i]) ); + } +} + +static void ScriptEntitiesInSphere( HSCRIPT hTable, int listMax, const Vector ¢er, float radius, int iMask ) +{ + CBaseEntity *list[1024]; + int count = UTIL_EntitiesInSphere( list, listMax, center, radius, iMask ); + + for ( int i = 0; i < count; i++ ) + { + g_pScriptVM->ArrayAppend( hTable, ToHScript(list[i]) ); + } +} + +//----------------------------------------------------------------------------- + +static void ScriptDecalTrace( HSCRIPT hTrace, const char *decalName ) +{ + CScriptGameTrace *tr = HScriptToClass< CScriptGameTrace >( hTrace ); + if ( tr ) + { + UTIL_DecalTrace( tr, decalName ); + } +} + +static HSCRIPT ScriptCreateRope( HSCRIPT hStart, HSCRIPT hEnd, int iStartAttachment, int iEndAttachment, float ropeWidth, const char *pMaterialName, int numSegments, int ropeFlags ) +{ +#ifdef CLIENT_DLL + C_RopeKeyframe *pRope = C_RopeKeyframe::Create( ToEnt( hStart ), ToEnt( hEnd ), iStartAttachment, iEndAttachment, ropeWidth, pMaterialName, numSegments, ropeFlags ); +#else + CRopeKeyframe *pRope = CRopeKeyframe::Create( ToEnt( hStart ), ToEnt( hEnd ), iStartAttachment, iEndAttachment, ropeWidth, pMaterialName, numSegments ); + if (pRope) + pRope->m_RopeFlags |= ropeFlags; // HACKHACK +#endif + + return ToHScript( pRope ); +} + +#ifndef CLIENT_DLL +static HSCRIPT ScriptCreateRopeWithSecondPointDetached( HSCRIPT hStart, int iStartAttachment, int ropeLength, float ropeWidth, const char *pMaterialName, int numSegments, bool initialHang, int ropeFlags ) +{ + CRopeKeyframe *pRope = CRopeKeyframe::CreateWithSecondPointDetached( ToEnt( hStart ), iStartAttachment, ropeLength, ropeWidth, pMaterialName, numSegments, initialHang ); + if (pRope) + pRope->m_RopeFlags |= ropeFlags; // HACKHACK + + return ToHScript( pRope ); +} +#endif + +static void EmitSoundParamsOn( HSCRIPT hParams, HSCRIPT hEnt ) +{ + CBaseEntity *pEnt = ToEnt( hEnt ); + if (!pEnt) + return; + + ScriptEmitSound_t *pParams = (ScriptEmitSound_t*)g_pScriptVM->GetInstanceValue( hParams, GetScriptDescForClass( ScriptEmitSound_t ) ); + if (!pParams) + return; + + CPASAttenuationFilter filter( pEnt, pParams->m_pSoundName ); + + CBaseEntity::EmitSound( filter, pEnt->entindex(), *pParams ); +} + +//----------------------------------------------------------------------------- +// Simple particle effect dispatch +//----------------------------------------------------------------------------- +static void ScriptDispatchParticleEffect( const char *pszParticleName, const Vector &vecOrigin, const QAngle &vecAngles, HSCRIPT hEntity ) +{ + DispatchParticleEffect( pszParticleName, vecOrigin, vecAngles, ToEnt(hEntity) ); +} + +#ifndef CLIENT_DLL +const Vector& ScriptPredictedPosition( HSCRIPT hTarget, float flTimeDelta ) +{ + static Vector predicted; + UTIL_PredictedPosition( ToEnt(hTarget), flTimeDelta, &predicted ); + return predicted; +} +#endif + +//============================================================================= +//============================================================================= + +bool ScriptMatcherMatch( const char *pszQuery, const char *szValue ) { return Matcher_Match( pszQuery, szValue ); } + +//============================================================================= +//============================================================================= + +#ifndef CLIENT_DLL +bool IsDedicatedServer() +{ + return engine->IsDedicatedServer(); +} +#endif + +bool ScriptIsServer() +{ +#ifdef GAME_DLL + return true; +#else + return false; +#endif +} + +bool ScriptIsClient() +{ +#ifdef CLIENT_DLL + return true; +#else + return false; +#endif +} + +bool ScriptIsWindows() +{ + return IsWindows(); +} + +bool ScriptIsLinux() +{ + return IsLinux(); +} + +bool ScriptIsOSX() +{ + return IsOSX(); +} + +bool ScriptIsPosix() +{ + return IsPosix(); +} + +// Notification printing on the right edge of the screen +void NPrint( int pos, const char* fmt ) +{ + engine->Con_NPrintf( pos, "%s", fmt ); +} + +void NXPrint( int pos, int r, int g, int b, bool fixed, float ftime, const char* fmt ) +{ + con_nprint_t info; + + info.index = pos; + info.time_to_live = ftime; + info.color[0] = r / 255.f; + info.color[1] = g / 255.f; + info.color[2] = b / 255.f; + info.fixed_width_font = fixed; + + engine->Con_NXPrintf( &info, "%s", fmt ); +} + +static float IntervalPerTick() +{ + return gpGlobals->interval_per_tick; +} + +static int GetFrameCount() +{ + return gpGlobals->framecount; +} + + +//============================================================================= +//============================================================================= + +void RegisterSharedScriptFunctions() +{ + // + // Due to this being a custom integration of VScript based on the Alien Swarm SDK, we don't have access to + // some of the code normally available in games like L4D2 or Valve's original VScript DLL. + // Instead, that code is recreated here, shared between server and client. + // + +#ifndef CLIENT_DLL + ScriptRegisterFunction( g_pScriptVM, EmitSoundOn, "Play named sound on an entity." ); + ScriptRegisterFunction( g_pScriptVM, EmitSoundOnClient, "Play named sound only on the client for the specified player." ); + + ScriptRegisterFunction( g_pScriptVM, AddThinkToEnt, "This will put a think function onto an entity, or pass null to remove it. This is NOT chained, so be careful." ); + ScriptRegisterFunction( g_pScriptVM, PrecacheEntityFromTable, "Precache an entity from KeyValues in a table." ); + ScriptRegisterFunction( g_pScriptVM, SpawnEntityFromTable, "Native function for entity spawning." ); +#endif // !CLIENT_DLL + ScriptRegisterFunction( g_pScriptVM, EntIndexToHScript, "Returns the script handle for the given entity index." ); + + //----------------------------------------------------------------------------- + // Functions, etc. unique to Mapbase + //----------------------------------------------------------------------------- + + //----------------------------------------------------------------------------- + + ScriptRegisterFunction( g_pScriptVM, NPrint, "Notification print" ); + ScriptRegisterFunction( g_pScriptVM, NXPrint, "Notification print, customised" ); + +#ifndef CLIENT_DLL + ScriptRegisterFunction( g_pScriptVM, SaveEntityKVToTable, "Saves an entity's keyvalues to a table." ); + ScriptRegisterFunction( g_pScriptVM, SpawnEntityFromKeyValues, "Spawns an entity with the keyvalues in a CScriptKeyValues handle." ); + ScriptRegisterFunctionNamed( g_pScriptVM, ScriptDispatchSpawn, "DispatchSpawn", "Spawns an unspawned entity." ); +#endif + + ScriptRegisterFunction( g_pScriptVM, CreateDamageInfo, "" ); + ScriptRegisterFunction( g_pScriptVM, DestroyDamageInfo, "" ); + ScriptRegisterFunctionNamed( g_pScriptVM, ScriptCalculateExplosiveDamageForce, "CalculateExplosiveDamageForce", "Fill out a damage info handle with a damage force for an explosive." ); + ScriptRegisterFunctionNamed( g_pScriptVM, ScriptCalculateBulletDamageForce, "CalculateBulletDamageForce", "Fill out a damage info handle with a damage force for a bullet impact." ); + ScriptRegisterFunctionNamed( g_pScriptVM, ScriptCalculateMeleeDamageForce, "CalculateMeleeDamageForce", "Fill out a damage info handle with a damage force for a melee impact." ); + ScriptRegisterFunctionNamed( g_pScriptVM, ScriptGuessDamageForce, "GuessDamageForce", "Try and guess the physics force to use." ); + + ScriptRegisterFunction( g_pScriptVM, CreateFireBulletsInfo, "" ); + ScriptRegisterFunction( g_pScriptVM, DestroyFireBulletsInfo, "" ); + + ScriptRegisterFunctionNamed( g_pScriptVM, ScriptTraceLineComplex, "TraceLineComplex", "Complex version of TraceLine which takes 2 points, an ent to ignore, a trace mask, and a collision group. Returns a handle which can access all trace info." ); + ScriptRegisterFunctionNamed( g_pScriptVM, ScriptTraceHullComplex, "TraceHullComplex", "Takes 2 points, min/max hull bounds, an ent to ignore, a trace mask, and a collision group to trace to a point using a hull. Returns a handle which can access all trace info." ); + + // + // VPhysics + // + ScriptRegisterFunction( g_pScriptVM, GetPhysVelocity, "Gets physics velocity for the given VPhysics object" ); + ScriptRegisterFunction( g_pScriptVM, GetPhysAngVelocity, "Gets physics angular velocity for the given VPhysics object" ); + ScriptRegisterFunction( g_pScriptVM, SetPhysVelocity, "Sets physics velocity for the given VPhysics object" ); + ScriptRegisterFunction( g_pScriptVM, AddPhysVelocity, "Adds physics velocity for the given VPhysics object" ); + ScriptRegisterFunctionNamed( g_pScriptVM, ScriptPhysEnableEntityCollisions, "PhysEnableEntityCollisions", "Enables collisions between two VPhysics objects"); + ScriptRegisterFunctionNamed( g_pScriptVM, ScriptPhysDisableEntityCollisions, "PhysDisableEntityCollisions", "Disables collisions between two VPhysics objects"); + + // + // Precaching + // + ScriptRegisterFunctionNamed( g_pScriptVM, ScriptPrecacheModel, "PrecacheModel", "Precaches a model for later usage." ); + ScriptRegisterFunction( g_pScriptVM, PrecacheMaterial, "Precaches a material for later usage." ); + ScriptRegisterFunction( g_pScriptVM, PrecacheParticleSystem, "Precaches a particle system for later usage." ); + ScriptRegisterFunctionNamed( g_pScriptVM, ScriptPrecacheOther, "PrecacheOther", "Precaches an entity class for later usage." ); + + // + // NPCs + // +#ifndef CLIENT_DLL + ScriptRegisterFunctionNamed( g_pScriptVM, ScriptInsertSound, "InsertAISound", "Inserts an AI sound." ); + + ScriptRegisterFunctionNamed( g_pScriptVM, CAI_BaseNPC::GetActivityName, "GetActivityName", "Gets the name of the specified activity index." ); +#endif + + // + // Misc. Utility + // + ScriptRegisterFunctionNamed( g_pScriptVM, ScriptEntitiesInBox, "EntitiesInBox", "Gets all entities which are within a worldspace box. This function copies them to an array with a maximum number of elements." ); + ScriptRegisterFunctionNamed( g_pScriptVM, ScriptEntitiesAtPoint, "EntitiesAtPoint", "Gets all entities which are intersecting a point in space. This function copies them to an array with a maximum number of elements." ); + ScriptRegisterFunctionNamed( g_pScriptVM, ScriptEntitiesInSphere, "EntitiesInSphere", "Gets all entities which are within a sphere. This function copies them to an array with a maximum number of elements." ); + + ScriptRegisterFunctionNamed( g_pScriptVM, ScriptDecalTrace, "DecalTrace", "Creates a dynamic decal based on the given trace info. The trace information can be generated by TraceLineComplex() and the decal name must be from decals_subrect.txt." ); + ScriptRegisterFunctionNamed( g_pScriptVM, ScriptDispatchParticleEffect, "DoDispatchParticleEffect", SCRIPT_ALIAS( "DispatchParticleEffect", "Dispatches a one-off particle system" ) ); + + ScriptRegisterFunctionNamed( g_pScriptVM, ScriptCreateRope, "CreateRope", "Creates a single rope between two entities. Can optionally follow specific attachments." ); +#ifndef CLIENT_DLL + ScriptRegisterFunctionNamed( g_pScriptVM, ScriptCreateRopeWithSecondPointDetached, "CreateRopeWithSecondPointDetached", "Creates a single detached rope hanging from a point. Can optionally follow a specific start attachment." ); +#endif + + ScriptRegisterFunction( g_pScriptVM, EmitSoundParamsOn, "Play EmitSound_t params on an entity." ); + + ScriptRegisterFunctionNamed( g_pScriptVM, ScriptMatcherMatch, "Matcher_Match", "Compares a string to a query using Mapbase's matcher system, supporting wildcards, RS matchers, etc." ); + ScriptRegisterFunction( g_pScriptVM, Matcher_NamesMatch, "Compares a string to a query using Mapbase's matcher system using wildcards only." ); + ScriptRegisterFunction( g_pScriptVM, AppearsToBeANumber, "Checks if the given string appears to be a number." ); + +#ifndef CLIENT_DLL + ScriptRegisterFunctionNamed( g_pScriptVM, ScriptPredictedPosition, "PredictedPosition", "Predicts what an entity's position will be in a given amount of time." ); +#endif + +#ifndef CLIENT_DLL + ScriptRegisterFunction( g_pScriptVM, IsDedicatedServer, "Is this a dedicated server?" ); +#endif + ScriptRegisterFunctionNamed( g_pScriptVM, ScriptIsServer, "IsServer", "Returns true if the script is being run on the server." ); + ScriptRegisterFunctionNamed( g_pScriptVM, ScriptIsClient, "IsClient", "Returns true if the script is being run on the client." ); + ScriptRegisterFunction( g_pScriptVM, IntervalPerTick, "Simulation tick interval" ); + ScriptRegisterFunction( g_pScriptVM, GetFrameCount, "Absolute frame counter" ); + //ScriptRegisterFunction( g_pScriptVM, GetTickCount, "Simulation ticks" ); + + ScriptRegisterFunctionNamed( g_pScriptVM, ScriptIsWindows, "IsWindows", "Returns true if the game is being run on a Windows machine." ); + ScriptRegisterFunctionNamed( g_pScriptVM, ScriptIsLinux, "IsLinux", "Returns true if the game is being run on a Linux machine." ); + ScriptRegisterFunctionNamed( g_pScriptVM, ScriptIsOSX, "IsOSX", "Returns true if the game is being run on an OSX machine." ); + ScriptRegisterFunctionNamed( g_pScriptVM, ScriptIsPosix, "IsPosix", "Returns true if the game is being run on a Posix machine." ); + + RegisterScriptSingletons(); +} diff --git a/src/game/shared/mapbase/vscript_funcs_shared.h b/src/game/shared/mapbase/vscript_funcs_shared.h new file mode 100644 index 00000000..bcf91741 --- /dev/null +++ b/src/game/shared/mapbase/vscript_funcs_shared.h @@ -0,0 +1,320 @@ +//========= Mapbase - https://github.com/mapbase-source/source-sdk-2013 ================= +// +// Purpose: See vscript_funcs_shared.cpp +// +// $NoKeywords: $ +//============================================================================= + +#ifndef VSCRIPT_FUNCS_SHARED +#define VSCRIPT_FUNCS_SHARED +#ifdef _WIN32 +#pragma once +#endif + +#include "npcevent.h" +#ifdef GAME_DLL +#include "ai_memory.h" +#endif + +//----------------------------------------------------------------------------- +// Exposes surfacedata_t to VScript +//----------------------------------------------------------------------------- +struct scriptsurfacedata_t : public surfacedata_t +{ +public: + float GetFriction() const { return physics.friction; } + float GetThickness() const { return physics.thickness; } + + float GetJumpFactor() const { return game.jumpFactor; } + char GetMaterialChar() const { return game.material; } + + const char* GetSoundStepLeft() const { return physprops->GetString( sounds.stepleft ); } + const char* GetSoundStepRight() const { return physprops->GetString( sounds.stepright ); } + const char* GetSoundImpactSoft() const { return physprops->GetString( sounds.impactSoft ); } + const char* GetSoundImpactHard() const { return physprops->GetString( sounds.impactHard ); } + const char* GetSoundScrapeSmooth() const { return physprops->GetString( sounds.scrapeSmooth ); } + const char* GetSoundScrapeRough() const { return physprops->GetString( sounds.scrapeRough ); } + const char* GetSoundBulletImpact() const { return physprops->GetString( sounds.bulletImpact ); } + const char* GetSoundRolling() const { return physprops->GetString( sounds.rolling ); } + const char* GetSoundBreak() const { return physprops->GetString( sounds.breakSound ); } + const char* GetSoundStrain() const { return physprops->GetString( sounds.strainSound ); } +}; + +//----------------------------------------------------------------------------- +// Exposes csurface_t to VScript +//----------------------------------------------------------------------------- +class CSurfaceScriptHelper +{ +public: + // This class is owned by CScriptGameTrace, and cannot be accessed without being initialised in CScriptGameTrace::RegisterSurface() + //CSurfaceScriptHelper() : m_pSurface(NULL), m_hSurfaceData(NULL) {} + + ~CSurfaceScriptHelper() + { + g_pScriptVM->RemoveInstance( m_hSurfaceData ); + } + + void Init( csurface_t *surf ) + { + m_pSurface = surf; + m_hSurfaceData = g_pScriptVM->RegisterInstance( + reinterpret_cast< scriptsurfacedata_t* >( physprops->GetSurfaceData( m_pSurface->surfaceProps ) ) ); + } + + const char* Name() const { return m_pSurface->name; } + HSCRIPT SurfaceProps() const { return m_hSurfaceData; } + +private: + csurface_t *m_pSurface; + HSCRIPT m_hSurfaceData; +}; + +//----------------------------------------------------------------------------- +// Exposes cplane_t to VScript +//----------------------------------------------------------------------------- +class CPlaneTInstanceHelper : public IScriptInstanceHelper +{ + bool Get( void *p, const char *pszKey, ScriptVariant_t &variant ) + { + cplane_t *pi = ((cplane_t *)p); + if (FStrEq(pszKey, "normal")) + variant = pi->normal; + else if (FStrEq(pszKey, "dist")) + variant = pi->dist; + else + return false; + + return true; + } + + //bool Set( void *p, const char *pszKey, ScriptVariant_t &variant ); +}; + +//----------------------------------------------------------------------------- +// Exposes trace_t to VScript +//----------------------------------------------------------------------------- +class CScriptGameTrace : public CGameTrace +{ +public: + CScriptGameTrace() : m_surfaceAccessor(NULL), m_planeAccessor(NULL) + { + m_hScriptInstance = g_pScriptVM->RegisterInstance( this ); + } + + ~CScriptGameTrace() + { + if ( m_hScriptInstance ) + { + g_pScriptVM->RemoveInstance( m_hScriptInstance ); + } + + if ( m_surfaceAccessor ) + { + g_pScriptVM->RemoveInstance( m_surfaceAccessor ); + } + + if ( m_planeAccessor ) + { + g_pScriptVM->RemoveInstance( m_planeAccessor ); + } + } + + void RegisterSurface() + { + m_surfaceHelper.Init( &surface ); + m_surfaceAccessor = g_pScriptVM->RegisterInstance( &m_surfaceHelper ); + } + + void RegisterPlane() + { + m_planeAccessor = g_pScriptVM->RegisterInstance( &plane ); + } + + HSCRIPT GetScriptInstance() const + { + return m_hScriptInstance; + } + +public: + float FractionLeftSolid() const { return fractionleftsolid; } + int HitGroup() const { return hitgroup; } + int PhysicsBone() const { return physicsbone; } + + HSCRIPT Entity() const { return ToHScript( m_pEnt ); } + int HitBox() const { return hitbox; } + + const Vector& StartPos() const { return startpos; } + const Vector& EndPos() const { return endpos; } + + float Fraction() const { return fraction; } + + int Contents() const { return contents; } + int DispFlags() const { return dispFlags; } + + bool AllSolid() const { return allsolid; } + bool StartSolid() const { return startsolid; } + + HSCRIPT Surface() const { return m_surfaceAccessor; } + HSCRIPT Plane() const { return m_planeAccessor; } + + void Destroy() { delete this; } + +private: + HSCRIPT m_surfaceAccessor; + HSCRIPT m_planeAccessor; + HSCRIPT m_hScriptInstance; + + CSurfaceScriptHelper m_surfaceHelper; + + CScriptGameTrace( const CScriptGameTrace& v ); +}; + +//----------------------------------------------------------------------------- +// Exposes animevent_t to VScript +//----------------------------------------------------------------------------- +struct scriptanimevent_t : public animevent_t +{ + int GetEvent() { return event; } + void SetEvent( int nEvent ) { event = nEvent; } + + const char *GetOptions() { return options; } + void SetOptions( const char *pOptions ) { options = pOptions; } + + float GetCycle() { return cycle; } + void SetCycle( float flCycle ) { cycle = flCycle; } + + float GetEventTime() { return eventtime; } + void SetEventTime( float flEventTime ) { eventtime = flEventTime; } + + int GetType() { return type; } + void SetType( int nType ) { eventtime = type; } + + HSCRIPT GetSource() { return ToHScript( pSource ); } + void SetSource( HSCRIPT hSource ) + { + CBaseEntity *pEnt = ToEnt( hSource ); + if (pEnt) + pSource = pEnt->GetBaseAnimating(); + } +}; + +class CAnimEventTInstanceHelper : public IScriptInstanceHelper +{ + bool Get( void *p, const char *pszKey, ScriptVariant_t &variant ); + bool Set( void *p, const char *pszKey, ScriptVariant_t &variant ); +}; + +//----------------------------------------------------------------------------- +// Exposes EmitSound_t to VScript +//----------------------------------------------------------------------------- +struct ScriptEmitSound_t : public EmitSound_t +{ + int GetChannel() { return m_nChannel; } + void SetChannel( int nChannel ) { m_nChannel = nChannel; } + + const char *GetSoundName() { return m_pSoundName; } + void SetSoundName( const char *pSoundName ) { m_pSoundName = pSoundName; } + + float GetVolume() { return m_flVolume; } + void SetVolume( float flVolume ) { m_flVolume = flVolume; } + + int GetSoundLevel() { return m_SoundLevel; } + void SetSoundLevel( int iSoundLevel ) { m_SoundLevel = (soundlevel_t)iSoundLevel; } + + int GetFlags() { return m_nFlags; } + void SetFlags( int nFlags ) { m_nFlags = nFlags; } + + int GetSpecialDSP() { return m_nSpecialDSP; } + void SetSpecialDSP( int nSpecialDSP ) { m_nSpecialDSP = nSpecialDSP; } + + bool HasOrigin() { return m_pOrigin ? true : false; } + const Vector &GetOrigin() { return m_pOrigin ? *m_pOrigin : vec3_origin; } + void SetOrigin( const Vector &origin ) { static Vector tempOrigin; tempOrigin = origin; m_pOrigin = &tempOrigin; } + void ClearOrigin() { m_pOrigin = NULL; } + + float GetSoundTime() { return m_flSoundTime; } + void SetSoundTime( float flSoundTime ) { m_flSoundTime = flSoundTime; } + + float GetEmitCloseCaption() { return m_bEmitCloseCaption; } + void SetEmitCloseCaption( bool bEmitCloseCaption ) { m_bEmitCloseCaption = bEmitCloseCaption; } + + float GetWarnOnMissingCloseCaption() { return m_bWarnOnMissingCloseCaption; } + void SetWarnOnMissingCloseCaption( bool bWarnOnMissingCloseCaption ) { m_bWarnOnMissingCloseCaption = bWarnOnMissingCloseCaption; } + + float GetWarnOnDirectWaveReference() { return m_bWarnOnDirectWaveReference; } + void SetWarnOnDirectWaveReference( bool bWarnOnDirectWaveReference ) { m_bWarnOnDirectWaveReference = bWarnOnDirectWaveReference; } + + int GetSpeakerEntity() { return m_nSpeakerEntity; } + void SetSpeakerEntity( int nSpeakerEntity ) { m_nSpeakerEntity = nSpeakerEntity; } + + int GetSoundScriptHandle() { return m_hSoundScriptHandle; } + void SetSoundScriptHandle( int hSoundScriptHandle ) { m_hSoundScriptHandle = hSoundScriptHandle; } +}; + +//----------------------------------------------------------------------------- +// Exposes CUserCmd to VScript +//----------------------------------------------------------------------------- +class CScriptUserCmd : public CUserCmd +{ +public: + int GetCommandNumber() { return command_number; } + + int ScriptGetTickCount() { return tick_count; } + + const QAngle& GetViewAngles() { return viewangles; } + void SetViewAngles( const QAngle& val ) { viewangles = val; } + + float GetForwardMove() { return forwardmove; } + void SetForwardMove( float val ) { forwardmove = val; } + float GetSideMove() { return sidemove; } + void SetSideMove( float val ) { sidemove = val; } + float GetUpMove() { return upmove; } + void SetUpMove( float val ) { upmove = val; } + + int GetButtons() { return buttons; } + void SetButtons( int val ) { buttons = val; } + int GetImpulse() { return impulse; } + void SetImpulse( int val ) { impulse = val; } + + int GetWeaponSelect() { return weaponselect; } + void SetWeaponSelect( int val ) { weaponselect = val; } + int GetWeaponSubtype() { return weaponsubtype; } + void SetWeaponSubtype( int val ) { weaponsubtype = val; } + + int GetRandomSeed() { return random_seed; } + + int GetMouseX() { return mousedx; } + void SetMouseX( int val ) { mousedx = val; } + int GetMouseY() { return mousedy; } + void SetMouseY( int val ) { mousedy = val; } +}; + +#ifdef GAME_DLL +//----------------------------------------------------------------------------- +// Exposes AI_EnemyInfo_t to VScript +//----------------------------------------------------------------------------- +struct Script_AI_EnemyInfo_t : public AI_EnemyInfo_t +{ + #define ENEMY_INFO_SCRIPT_FUNCS(type, name, var) \ + type Get##name() { return var; } \ + void Set##name( type v ) { var = v; } + + HSCRIPT Enemy() { return ToHScript(hEnemy); } + void SetEnemy( HSCRIPT ent ) { hEnemy = ToEnt(ent); } + + ENEMY_INFO_SCRIPT_FUNCS( Vector, LastKnownLocation, vLastKnownLocation ); + ENEMY_INFO_SCRIPT_FUNCS( Vector, LastSeenLocation, vLastSeenLocation ); + ENEMY_INFO_SCRIPT_FUNCS( float, TimeLastSeen, timeLastSeen ); + ENEMY_INFO_SCRIPT_FUNCS( float, TimeFirstSeen, timeFirstSeen ); + ENEMY_INFO_SCRIPT_FUNCS( float, TimeLastReacquired, timeLastReacquired ); + ENEMY_INFO_SCRIPT_FUNCS( float, TimeValidEnemy, timeValidEnemy ); + ENEMY_INFO_SCRIPT_FUNCS( float, TimeLastReceivedDamageFrom, timeLastReceivedDamageFrom ); + ENEMY_INFO_SCRIPT_FUNCS( float, TimeAtFirstHand, timeAtFirstHand ); + ENEMY_INFO_SCRIPT_FUNCS( bool, DangerMemory, bDangerMemory ); + ENEMY_INFO_SCRIPT_FUNCS( bool, EludedMe, bEludedMe ); + ENEMY_INFO_SCRIPT_FUNCS( bool, Unforgettable, bUnforgettable ); + ENEMY_INFO_SCRIPT_FUNCS( bool, MobbedMe, bMobbedMe ); +}; +#endif + +#endif diff --git a/src/game/shared/mapbase/vscript_singletons.cpp b/src/game/shared/mapbase/vscript_singletons.cpp new file mode 100644 index 00000000..2f4239c7 --- /dev/null +++ b/src/game/shared/mapbase/vscript_singletons.cpp @@ -0,0 +1,5554 @@ +//========= Mapbase - https://github.com/mapbase-source/source-sdk-2013 ============// +// +// Purpose: This file contains brand new VScript singletons and singletons replicated from API +// documentation in other games. +// +// See vscript_funcs_shared.cpp for more information. +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" +#include +#include +#include "ammodef.h" +#include "tier1/utlcommon.h" + +#include "soundenvelope.h" +#include "saverestore_utlvector.h" +#include "stdstring.h" + +#ifndef CLIENT_DLL +#include "ai_speech.h" +#include "ai_memory.h" +#include "ai_squad.h" +#endif // !CLIENT_DLL + +#include "usermessages.h" +#include "filesystem.h" +#include "igameevents.h" +#include "engine/ivdebugoverlay.h" +#include "icommandline.h" + +#ifdef CLIENT_DLL +#include "IEffects.h" +#include "fx.h" +#include "itempents.h" +#include "c_te_legacytempents.h" +#include "iefx.h" +#include "dlight.h" + +#if !defined(NO_STEAM) +#include "steam/steam_api.h" +#endif +#endif + +#include "vscript_singletons.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +extern IScriptManager *scriptmanager; + + +#ifdef GAME_DLL + extern void SendProxy_StringT_To_String(const SendProp*, const void*, const void*, DVariant*, int, int); + extern void SendProxy_UtlVectorLength(const SendProp*, const void*, const void*, DVariant*, int, int); + class CSendProxyRecipients; + extern void* SendProxy_LengthTable(const SendProp*, const void*, const void* pData, CSendProxyRecipients*, int); + #define DataTableProxy_EHandle SendProxy_EHandleToInt + #define DataTableProxy_String SendProxy_StringToString + #define DataTableProxy_TableLength SendProxy_LengthTable + #define DataTableProxy_UtlVectorLength SendProxy_UtlVectorLength +#else + extern void RecvProxy_UtlVectorLength(const CRecvProxyData*, void*, void*); + extern void DataTableRecvProxy_LengthProxy(const RecvProp*, void**, void*, int); + #define DataTableProxy_EHandle RecvProxy_IntToEHandle + #define DataTableProxy_String RecvProxy_StringToString + #define DataTableProxy_TableLength DataTableRecvProxy_LengthProxy + #define DataTableProxy_UtlVectorLength RecvProxy_UtlVectorLength +#endif +extern ISaveRestoreOps* GetPhysObjSaveRestoreOps( PhysInterfaceId_t ); +extern ISaveRestoreOps* ActivityDataOps(); +extern ISaveRestoreOps* GetSoundSaveRestoreOps(); +extern ISaveRestoreOps* GetStdStringDataOps(); +#ifdef GAME_DLL + #define UTLVECTOR_DATAOPS( fieldType, dataType )\ + CUtlVectorDataopsInstantiator< fieldType >::GetDataOps( (CUtlVector< dataType >*)0 ) + #define IS_EHANDLE_UTLVECTOR( td )\ + td->pSaveRestoreOps == UTLVECTOR_DATAOPS( FIELD_EHANDLE, CHandle< CBaseEntity > ) ||\ + td->pSaveRestoreOps == UTLVECTOR_DATAOPS( FIELD_EHANDLE, CHandle< CBaseFlex > ) ||\ + td->pSaveRestoreOps == UTLVECTOR_DATAOPS( FIELD_EHANDLE, CHandle< CBaseAnimating > ) ||\ + td->pSaveRestoreOps == UTLVECTOR_DATAOPS( FIELD_EHANDLE, CHandle< CBaseCombatWeapon > ) ||\ + td->pSaveRestoreOps == UTLVECTOR_DATAOPS( FIELD_EHANDLE, CHandle< CBasePlayer > ) ||\ + td->pSaveRestoreOps == UTLVECTOR_DATAOPS( FIELD_EHANDLE, CHandle< CAI_BaseNPC > ) ||\ + td->pSaveRestoreOps == UTLVECTOR_DATAOPS( FIELD_EHANDLE, CHandle< CSceneEntity > ) ||\ + td->pSaveRestoreOps == UTLVECTOR_DATAOPS( FIELD_EHANDLE, CHandle< CSceneListManager > ) ||\ + td->pSaveRestoreOps == UTLVECTOR_DATAOPS( FIELD_EHANDLE, CHandle< CRagdollBoogie > ) ||\ + td->pSaveRestoreOps == UTLVECTOR_DATAOPS( FIELD_EHANDLE, CHandle< CFish > ) ||\ + td->pSaveRestoreOps == UTLVECTOR_DATAOPS( FIELD_EHANDLE, CHandle< CVGuiScreen > ) + + class CSceneListManager; + class CRagdollBoogie; + class CFish; + #ifdef _DEBUG + class CStringTableSaveRestoreOps; + extern CStringTableSaveRestoreOps g_VguiScreenStringOps; + extern INetworkStringTable *g_pStringTableVguiScreen; + extern ISaveRestoreOps *thinkcontextFuncs; + class CAI_EnemiesListSaveRestoreOps; + extern CAI_EnemiesListSaveRestoreOps g_AI_MemoryListSaveRestoreOps; + class CConceptHistoriesDataOps; + extern CConceptHistoriesDataOps g_ConceptHistoriesSaveDataOps; + #endif +#endif + +//============================================================================= +// Net Prop Manager +// Based on L4D2 API +//============================================================================= +class CScriptNetPropManager +{ +private: +#if GAME_DLL + typedef SendProp NetProp; + typedef SendTable NetTable; + typedef ServerClass NetworkClass; + + NetworkClass *GetNetworkClass( CBaseEntity* p ) { return p->GetServerClass(); } + NetTable *GetNetTable( NetworkClass* p ) { return p->m_pTable; } + + void NetworkStateChanged( CBaseEntity* p, int o ) { p->NetworkProp()->NetworkStateChanged( o ); } +#else + typedef RecvProp NetProp; + typedef RecvTable NetTable; + typedef ClientClass NetworkClass; + + NetworkClass *GetNetworkClass( CBaseEntity* p ) { return p->GetClientClass(); } + NetTable *GetNetTable( NetworkClass* p ) { return p->m_pRecvTable; } + + void NetworkStateChanged( CBaseEntity*, int ) {} +#endif + + int GetClassID( CBaseEntity *p ) + { + return GetNetworkClass( p )->m_ClassID; + } + + int GetIntPropSize( NetProp *pProp ) + { + Assert( pProp->GetType() == DPT_Int ); + +#ifdef GAME_DLL + extern void SendProxy_UInt8ToInt32( const SendProp*, const void*, const void*, DVariant*, int, int ); + extern void SendProxy_UInt16ToInt32( const SendProp*, const void*, const void*, DVariant*, int, int ); + extern void SendProxy_UInt32ToInt32( const SendProp*, const void*, const void*, DVariant*, int, int ); + + SendVarProxyFn proxy = pProp->GetProxyFn(); + + if ( proxy == SendProxy_Int8ToInt32 || proxy == SendProxy_UInt8ToInt32 ) + return 8; + if ( proxy == SendProxy_Int16ToInt32 || proxy == SendProxy_UInt16ToInt32 ) + return 16; + if ( proxy == SendProxy_Int32ToInt32 || proxy == SendProxy_UInt32ToInt32 ) + return 32; + + return pProp->m_nBits; +#else + RecvVarProxyFn proxy = pProp->GetProxyFn(); + + if ( proxy == RecvProxy_Int32ToInt8 ) + return 8; + if ( proxy == RecvProxy_Int32ToInt16 ) + return 16; + if ( proxy == RecvProxy_Int32ToInt32 ) + return 32; + + return 0; +#endif + } + + bool IsEHandle( NetProp *pProp ) + { + return ( pProp->GetProxyFn() == DataTableProxy_EHandle ); + } + + bool IsUtlVector( NetProp *pProp ) + { +#ifdef GAME_DLL + SendVarProxyFn proxy = pProp->GetProxyFn(); +#else + RecvVarProxyFn proxy = pProp->GetProxyFn(); +#endif + + return ( proxy == DataTableProxy_UtlVectorLength ); + } + +private: + enum types + { + _INT1 = ( 1 << 0 ), + _INT8 = ( 1 << 1 ), + _INT16 = ( 1 << 2 ), + _INT32 = ( 1 << 3 ), + _FLOAT = ( 1 << 4 ), + _VEC3 = ( 1 << 5 ), + _VEC2 = ( 1 << 6 ), + _EHANDLE = ( 1 << 7 ), + _CLASSPTR = ( 1 << 8 ), + _EDICT = ( 1 << 9 ), + _CSTRING = ( 1 << 10 ), + _STRING_T = ( 1 << 11 ), + _ARRAY = ( 1 << 12 ), + _DATATABLE = ( 1 << 13 ), + + _PHYS = ( 1 << 14 ), + _STDSTRING = _CSTRING | _STRING_T, + + _DAR_EHANDLE = _EHANDLE | _ARRAY, + _DAR_CLASSPTR = _CLASSPTR | _ARRAY, + _DAR_INT = _INT32 | _ARRAY, + _DAR_FLOAT = _FLOAT | _ARRAY, + + //_MAX = ( 1 << 15 ) + }; + + // UNDONE: Special case for GetPropType() to be able to return the table/array itself + #define INDEX_GET_TYPE 0 + + #define MASK_INT_SIZE( _size ) ( ( 1 << (_size - 1) ) | ( (1 << (_size - 1)) - 1 ) ) + #define MASK_NEAREST_BYTE( _bits ) ( ( (1 << ALIGN_TO_NEAREST_BYTE(_bits)) - 1 ) & ~((1 << _bits) - 1) ) + #define ALIGN_TO_NEAREST_BYTE( _bits ) ( (_bits + 7) & ~7 ) + #define VARINFO_ARRAYSIZE_BITS 12 + + struct varinfo_t + { + int offset : 32; // actually a short + + union + { + int mask : 32; + int stringsize : 32; + }; + + enum types datatype : 16; + + // element size in bytes + unsigned int elemsize : 8; + unsigned int arraysize : VARINFO_ARRAYSIZE_BITS; + + // Following are only used in integer netprops to handle unsigned and size casting + bool isUnsigned : 1; + bool isNotNetworked : 1; + + int GetOffset( int index ) + { + return offset + index * elemsize; + } + }; + + // Wrapper to be able to set case sensitive comparator in node insertion + class vardict_t : public CUtlDict< varinfo_t > + { + public: + vardict_t() : CUtlDict< varinfo_t >( k_eDictCompareTypeCaseSensitive ) {} + }; + + // NOTE: This is lazy and inefficient. + // Simply map highest level class id to unique caches. + CUtlVector< int > m_EntMap; + CUtlVector< vardict_t > m_VarDicts; + + varinfo_t* CacheNew( CBaseEntity *pEnt, const char *szProp ) + { + int idx = m_EntMap.Find( GetClassID( pEnt ) ); + if ( idx == m_EntMap.InvalidIndex() ) + { + // Vector indices are kept in parallel as a workaround for encapsulating maps + idx = m_EntMap.AddToTail( GetClassID( pEnt ) ); + m_VarDicts.AddToTail(); + } + + vardict_t &dict = m_VarDicts.Element( idx ); + + idx = dict.Find( szProp ); + if ( idx == dict.InvalidIndex() ) + idx = dict.Insert( szProp ); + + varinfo_t *pInfo = &dict.Element( idx ); + V_memset( pInfo, 0, sizeof( varinfo_t ) ); + return pInfo; + } + + varinfo_t* CacheFetch( CBaseEntity *pEnt, const char *szProp ) + { + int idx = m_EntMap.Find( GetClassID( pEnt ) ); + if ( idx == m_EntMap.InvalidIndex() ) + return NULL; + + vardict_t &dict = m_VarDicts.Element( idx ); + idx = dict.Find( szProp ); + if ( idx == dict.InvalidIndex() ) + return NULL; + + varinfo_t *pInfo = &dict.Element( idx ); + return pInfo; + } + +public: + ~CScriptNetPropManager() + { + PurgeCache(); + } + + void PurgeCache() + { + m_EntMap.Purge(); + m_VarDicts.Purge(); + } + +private: + typedescription_t *FindField( char *pBase, datamap_t *map, const char *szName, int *offset ) + { + if ( map->baseMap ) + { + typedescription_t* p = FindField( pBase, map->baseMap, szName, offset ); + if ( p ) + return p; + } + + typedescription_t *pFields = map->dataDesc; + int numFields = map->dataNumFields; + + for ( int i = 0; i < numFields; i++ ) + { + typedescription_t* td = &pFields[i]; + int fieldType = td->fieldType; + int fieldOffset = td->fieldOffset[ TD_OFFSET_NORMAL ]; + + if ( td->flags & (FTYPEDESC_FUNCTIONTABLE | FTYPEDESC_INPUT | FTYPEDESC_OUTPUT) ) + continue; + + if ( fieldType == FIELD_VOID || fieldType == FIELD_FUNCTION ) + continue; + + if ( !V_strcmp( td->fieldName, szName ) ) + { + *offset += fieldOffset; + + if ( td->flags & FTYPEDESC_PTR ) + { + // Follow the pointer + char * const pRef = *(char**)( pBase + *offset ); + Assert( pRef ); + *offset = pRef - pBase; + } + + return td; + } + } + + return NULL; + } + + NetProp *FindProp( char *pBase, NetTable *pTable, const char *szName, int *offset ) + { + int numProps = pTable->GetNumProps(); + + for ( int i = 0; i < numProps; i++ ) + { + NetProp* pProp = pTable->GetProp(i); + + if ( pProp->IsInsideArray() ) + continue; + + if ( !V_strcmp( pProp->GetName(), szName ) ) + { + *offset += pProp->GetOffset(); + return pProp; + } + + // Go into inherited fields but not member tables, they are looked up explicitly + // This is only a problem with m_AnimOverlay + if ( ( pProp->GetFlags() & SPROP_COLLAPSIBLE ) || + ( pProp->GetType() == DPT_DataTable && pProp->GetOffset() == 0 ) ) + { + // Don't go into lengthproxy + if ( pProp->GetDataTableProxyFn() == DataTableProxy_TableLength ) + continue; + + NetProp *p = FindProp( pBase + pProp->GetOffset(), pProp->GetDataTable(), szName, offset ); + if ( p ) + { + *offset += pProp->GetOffset(); + return p; + } + } + } + + return NULL; + } + + typedescription_t *FindInDataMap( char * const pBase, datamap_t *map, const char *szFullProp, int *offset ) + { + *offset = 0; + + // Look for exact match + typedescription_t *pField = FindField( pBase, map, szFullProp, offset ); + if ( pField ) + return pField; + + // Look for members + const char *pszProp = szFullProp; + const char *pszPropEnd = V_strnchr( pszProp, '.', 512 ); + if ( !pszPropEnd ) + return NULL; + do + { + // this string comes from squirrel stringtable, it can be modified + *((char*)pszPropEnd) = 0; + pField = FindField( pBase, map, pszProp, offset ); + *((char*)pszPropEnd) = '.'; + pszProp = pszPropEnd + 1; + + if ( !pField || ( map = pField->td ) == NULL ) + return NULL; + + // Look for exact match again, just in case + pField = FindField( pBase, map, pszProp, offset ); + if ( pField ) + return pField; + } while ( ( pszPropEnd = V_strnchr( pszProp, '.', 512 ) ) != NULL ); + + return FindField( pBase, map, pszProp, offset ); + } + + NetProp *FindInNetTable( char * const pBase, NetTable *pTable, const char *szFullProp, int *offset ) + { + *offset = 0; + + // Look for exact match + NetProp *pProp = FindProp( pBase, pTable, szFullProp, offset ); + if ( pProp ) + return pProp; + + // Look for members + const char *pszProp = szFullProp; + const char *pszPropEnd = V_strnchr( pszProp, '.', 512 ); + if ( !pszPropEnd ) + return NULL; + do + { + // this string comes from squirrel stringtable, it can be modified + *((char*)pszPropEnd) = 0; + pProp = FindProp( pBase, pTable, pszProp, offset ); + *((char*)pszPropEnd) = '.'; + pszProp = pszPropEnd + 1; + + if ( !pProp || ( pTable = pProp->GetDataTable() ) == NULL ) + return NULL; + + // Look for exact match again for fields such as m_Local{m_skybox3d.scale} + pProp = FindProp( pBase, pTable, pszProp, offset ); + if ( pProp ) + return pProp; + } while ( ( pszPropEnd = V_strnchr( pszProp, '.', 512 ) ) != NULL ); + + return FindProp( pBase, pTable, pszProp, offset ); + } + + // Searches NetTable first to handle overwritten member network variables - see + // CPlayerResource::m_iHealth and CBaseEntity::m_iHealth + varinfo_t *GetVarInfo( CBaseEntity *pEnt, const char *szProp, int index ) + { + int offset = 0; + NetTable *pTable = GetNetTable( GetNetworkClass( pEnt ) ); + NetProp *pProp = FindInNetTable( (char*)pEnt, pTable, szProp, &offset ); + if ( pProp ) + { + +#define SetVarInfo()\ + varinfo_t *pInfo = CacheNew( pEnt, szProp );\ + pInfo->isNotNetworked = 0;\ + pInfo->elemsize = pProp->GetElementStride();\ + pInfo->arraysize = pProp->GetNumElements();\ + pInfo->offset = offset; + + switch ( pProp->GetType() ) + { + case DPT_Int: + { + if ( IsUtlVector( pProp ) ) + { + return NULL; + } + + if ( index < 0 || index >= pProp->GetNumElements() ) + { + Warning( "NetProp element index out of range! %s[%d]\n", szProp, index ); + return NULL; + } + + Assert( index == 0 || pProp->GetElementStride() > 0 ); + + if ( IsEHandle( pProp ) ) + { + Assert( pProp->GetElementStride() == sizeof(int) || pProp->GetElementStride() < 0 ); + + SetVarInfo(); + pInfo->datatype = types::_EHANDLE; + return pInfo; + } + else + { + const int size = GetIntPropSize( pProp ); +#ifdef CLIENT_DLL + // Client might be reading any amount of bits in a custom RecvProxy + // Break and check the datamaps + if ( size == 0 ) + break; +#endif + Assert( size <= pProp->GetElementStride() || pProp->GetElementStride() < 0 ); + + SetVarInfo(); + pInfo->mask = MASK_INT_SIZE( size ); + pInfo->datatype = types::_INT32; + return pInfo; + } + } + case DPT_Float: + { + if ( index < 0 || index >= pProp->GetNumElements() ) + { + Warning( "NetProp element index out of range! %s[%d]\n", szProp, index ); + return NULL; + } + + Assert( index == 0 || pProp->GetElementStride() > 0 ); + Assert( pProp->GetElementStride() == sizeof(float) || pProp->GetElementStride() < 0 ); + + SetVarInfo(); + pInfo->datatype = types::_FLOAT; + return pInfo; + } + case DPT_Vector: + { + if ( index < 0 || index >= pProp->GetNumElements() ) + { + Warning( "NetProp element index out of range! %s[%d]\n", szProp, index ); + return NULL; + } + + Assert( index == 0 || pProp->GetElementStride() > 0 ); + Assert( pProp->GetElementStride() == sizeof(float)*3 || pProp->GetElementStride() < 0 ); + + SetVarInfo(); + pInfo->datatype = types::_VEC3; + return pInfo; + } + case DPT_VectorXY: + { + if ( index < 0 || index >= pProp->GetNumElements() ) + { + Warning( "NetProp element index out of range! %s[%d]\n", szProp, index ); + return NULL; + } + + Assert( index == 0 || pProp->GetElementStride() > 0 ); + Assert( pProp->GetElementStride() == sizeof(float)*2 || pProp->GetElementStride() < 0 ); + + SetVarInfo(); + pInfo->datatype = types::_VEC2; + return pInfo; + } + case DPT_String: + { + if ( index < 0 || index >= pProp->GetNumElements() ) + { + Warning( "NetProp element index out of range! %s[%d]\n", szProp, index ); + return NULL; + } + + Assert( index == 0 || pProp->GetElementStride() > 0 ); + + SetVarInfo(); +#ifdef GAME_DLL + pInfo->stringsize = 0; +#else + pInfo->stringsize = pProp->m_StringBufferSize; +#endif +#ifdef GAME_DLL + if ( pProp->GetProxyFn() == SendProxy_StringT_To_String ) + { + pInfo->datatype = types::_STRING_T; + } + else +#endif + { + Assert( pProp->GetProxyFn() == DataTableProxy_String ); + pInfo->datatype = types::_CSTRING; + } + return pInfo; + } + case DPT_DataTable: + { + NetTable* pArray = pProp->GetDataTable(); + + if ( V_strcmp( pProp->GetName(), pArray->GetName() ) != 0 ) + { + Warning( "DT is not an array! %s(%s)\n", pProp->GetName(), pArray->GetName() ); + return NULL; + } + + if ( index < 0 || index >= pArray->GetNumProps() ) + { + Warning( "NetProp element index out of range! %s[%d]\n", szProp, index ); + return NULL; + } + + pProp = pArray->GetProp( index ); + + switch ( pProp->GetType() ) + { + case DPT_Int: + { + if ( IsEHandle( pProp ) ) + { + varinfo_t *pInfo = CacheNew( pEnt, szProp ); + pInfo->elemsize = sizeof(int); + pInfo->arraysize = pArray->GetNumProps(); + pInfo->offset = offset; + pInfo->datatype = types::_EHANDLE; + return pInfo; + } + else + { + const int size = GetIntPropSize( pProp ); +#ifdef CLIENT_DLL + // Client might be reading any amount of bits in a custom RecvProxy + // Break and check the datamaps + if ( size == 0 ) + break; +#endif + varinfo_t *pInfo = CacheNew( pEnt, szProp ); + + if ( pArray->GetNumProps() > 1 ) + { + pInfo->elemsize = pArray->GetProp(1)->GetOffset() - pArray->GetProp(0)->GetOffset(); + } + else + { + // Doesn't matter for an array of a single element + pInfo->elemsize = 0; + } + + pInfo->arraysize = pArray->GetNumProps(); + pInfo->offset = offset; + pInfo->mask = MASK_INT_SIZE( size ); + pInfo->datatype = types::_INT32; + return pInfo; + } + } + case DPT_Float: + { + varinfo_t *pInfo = CacheNew( pEnt, szProp ); + pInfo->elemsize = sizeof(float); + pInfo->arraysize = pArray->GetNumProps(); + pInfo->offset = offset; + pInfo->datatype = types::_FLOAT; + return pInfo; + } + case DPT_Vector: + { + varinfo_t *pInfo = CacheNew( pEnt, szProp ); + pInfo->elemsize = sizeof(float)*3; + pInfo->arraysize = pArray->GetNumProps(); + pInfo->offset = offset; + pInfo->datatype = types::_VEC3; + return pInfo; + } + case DPT_VectorXY: + { + varinfo_t *pInfo = CacheNew( pEnt, szProp ); + pInfo->elemsize = sizeof(float)*2; + pInfo->arraysize = pArray->GetNumProps(); + pInfo->offset = offset; + pInfo->datatype = types::_VEC2; + return pInfo; + } + case DPT_DataTable: + { + AssertMsg( 0, "DT in DT" ); + return NULL; + } + case DPT_Array: + { + AssertMsg( 0, "Array in DT" ); + return NULL; + } + case DPT_String: + { + AssertMsg( 0, "String in DT" ); + return NULL; + } + default: UNREACHABLE(); + } +#ifdef CLIENT_DLL + // DPT_Int can break into here for datamap fallback + break; +#else + UNREACHABLE(); +#endif + } // DPT_DataTable + case DPT_Array: + { + Assert( pProp->GetArrayProp() ); + + NetProp *pArray = pProp->GetArrayProp(); + offset += pArray->GetOffset(); + + if ( index < 0 || index >= pProp->GetNumElements() ) + { + Warning( "NetProp element index out of range! %s[%d]\n", szProp, index ); + return NULL; + } + + switch ( pArray->GetType() ) + { + case DPT_Int: + { + Assert( index == 0 || pProp->GetElementStride() > 0 ); + + if ( IsEHandle( pArray ) ) + { + SetVarInfo(); + pInfo->datatype = types::_EHANDLE; + return pInfo; + } + else + { + const int size = GetIntPropSize( pArray ); +#ifdef CLIENT_DLL + // Client might be reading any amount of bits in a custom RecvProxy + // Break and check the datamaps + if ( size == 0 ) + break; +#endif + SetVarInfo(); + pInfo->mask = MASK_INT_SIZE( size ); + pInfo->datatype = types::_INT32; + return pInfo; + } + } + case DPT_Float: + { + SetVarInfo(); + pInfo->datatype = types::_FLOAT; + return pInfo; + } + case DPT_Vector: + { + SetVarInfo(); + pInfo->datatype = types::_VEC3; + return pInfo; + } + case DPT_VectorXY: + { + SetVarInfo(); + pInfo->datatype = types::_VEC2; + return pInfo; + } + case DPT_String: + { + AssertMsg( 0, "String array not implemented" ); + return NULL; + } + case DPT_Array: + case DPT_DataTable: AssertMsg( 0, "DT in array" ); + default: UNREACHABLE(); + } +#ifdef CLIENT_DLL + // DPT_Int can break into here for datamap fallback + break; +#else + UNREACHABLE(); +#endif + } // DPT_Array + default: UNREACHABLE(); + } + // ambigious int size on client, check the datamaps +#undef SetVarInfo + } + + datamap_t *map = pEnt->GetDataDescMap(); + typedescription_t *pField = FindInDataMap( (char*)pEnt, map, szProp, &offset ); + if ( pField ) + { +#ifdef CLIENT_DLL +find_field: +#endif + if ( index < 0 || index >= pField->fieldSize ) + { + Warning( "NetProp element index out of range! %s[%d]\n", szProp, index ); + return NULL; + } + +#define SetVarInfo()\ + varinfo_t *pInfo = CacheNew( pEnt, szProp );\ + pInfo->isNotNetworked = 1;\ + pInfo->elemsize = pField->fieldSizeInBytes / pField->fieldSize;\ + pInfo->arraysize = pField->fieldSize;\ + pInfo->offset = offset; + + switch ( pField->fieldType ) + { + case FIELD_INTEGER: + case FIELD_MATERIALINDEX: + case FIELD_MODELINDEX: + case FIELD_COLOR32: + case FIELD_TICK: + case FIELD_BOOLEAN: + case FIELD_CHARACTER: + case FIELD_SHORT: + { + SetVarInfo(); + pInfo->isUnsigned = ( pField->flags & SPROP_UNSIGNED ) != 0; + pInfo->isNotNetworked = 1; + switch ( pField->fieldType ) + { + case FIELD_INTEGER: + case FIELD_MATERIALINDEX: + case FIELD_MODELINDEX: + case FIELD_COLOR32: + case FIELD_TICK: + pInfo->datatype = types::_INT32; break; + case FIELD_BOOLEAN: + pInfo->datatype = types::_INT1; break; + case FIELD_CHARACTER: + Assert( pField->fieldSizeInBytes == pField->fieldSize ); + pInfo->stringsize = pField->fieldSizeInBytes; + pInfo->datatype = types::_INT8; break; + case FIELD_SHORT: + pInfo->datatype = types::_INT16; break; + default: UNREACHABLE(); + } + return pInfo; + } + case FIELD_FLOAT: + case FIELD_TIME: + { + Assert( sizeof(float) == pField->fieldSizeInBytes / pField->fieldSize ); + + SetVarInfo(); + pInfo->datatype = types::_FLOAT; + return pInfo; + } + case FIELD_EHANDLE: + { + Assert( sizeof(int) == pField->fieldSizeInBytes / pField->fieldSize ); + + SetVarInfo(); + pInfo->datatype = types::_EHANDLE; + return pInfo; + } +#ifdef GAME_DLL + case FIELD_CLASSPTR: + { + Assert( sizeof(int*) == pField->fieldSizeInBytes / pField->fieldSize ); + + SetVarInfo(); + pInfo->datatype = types::_CLASSPTR; + return pInfo; + } + case FIELD_EDICT: + { + Assert( sizeof(int*) == pField->fieldSizeInBytes / pField->fieldSize ); + + SetVarInfo(); + pInfo->datatype = types::_EDICT; + return pInfo; + } +#endif + case FIELD_VECTOR: + case FIELD_POSITION_VECTOR: + { + Assert( sizeof(float)*3 == pField->fieldSizeInBytes / pField->fieldSize ); + + SetVarInfo(); + pInfo->datatype = types::_VEC3; + return pInfo; + } + case FIELD_STRING: + case FIELD_MODELNAME: + case FIELD_SOUNDNAME: + { + SetVarInfo(); + pInfo->stringsize = 0; + pInfo->datatype = types::_STRING_T; + return pInfo; + } + case FIELD_CUSTOM: + { + if ( pField->pSaveRestoreOps == GetPhysObjSaveRestoreOps( PIID_IPHYSICSOBJECT ) ) + { + SetVarInfo(); + pInfo->datatype = types::_PHYS; + return pInfo; + } + else if ( pField->pSaveRestoreOps == ActivityDataOps() ) + { + SetVarInfo(); + pInfo->datatype = types::_INT32; + return pInfo; + } +#ifdef GAME_DLL + else if ( IS_EHANDLE_UTLVECTOR( pField ) ) + { + SetVarInfo(); + pInfo->arraysize = ( 1 << VARINFO_ARRAYSIZE_BITS ) - 1; // dynamic, check on get + pInfo->datatype = types::_DAR_EHANDLE; + } + else if ( pField->pSaveRestoreOps == UTLVECTOR_DATAOPS( FIELD_CLASSPTR, CBaseEntity* ) ) + { + SetVarInfo(); + pInfo->arraysize = ( 1 << VARINFO_ARRAYSIZE_BITS ) - 1; // dynamic, check on get + pInfo->datatype = types::_DAR_CLASSPTR; + } + else if ( pField->pSaveRestoreOps == UTLVECTOR_DATAOPS( FIELD_INTEGER, int ) ) + { + SetVarInfo(); + pInfo->arraysize = ( 1 << VARINFO_ARRAYSIZE_BITS ) - 1; // dynamic, check on get + pInfo->datatype = types::_DAR_INT; + } + else if ( pField->pSaveRestoreOps == UTLVECTOR_DATAOPS( FIELD_FLOAT, float ) || + pField->pSaveRestoreOps == UTLVECTOR_DATAOPS( FIELD_TIME, float ) ) + { + SetVarInfo(); + pInfo->arraysize = ( 1 << VARINFO_ARRAYSIZE_BITS ) - 1; // dynamic, check on get + pInfo->datatype = types::_DAR_FLOAT; + } + // Only used by CAI_PlayerAlly::m_PendingConcept + else if ( pField->pSaveRestoreOps == GetStdStringDataOps() ) + { + SetVarInfo(); + pInfo->datatype = types::_STDSTRING; + return pInfo; + } +#endif + return NULL; + } + case FIELD_EMBEDDED: + return NULL; + default: + AssertMsg( 0, "Unknown type %d\n", pField->fieldType ); + return NULL; + } + UNREACHABLE(); +#undef SetVarInfo + } +#ifdef CLIENT_DLL + else + { + map = pEnt->GetPredDescMap(); + pField = FindInDataMap( (char*)pEnt, map, szProp, &offset ); + if ( pField ) + { + goto find_field; + } + } +#endif + return NULL; + } + +public: + // FIXME: Cannot get datatable/arrays at the moment + bool HasProp( HSCRIPT hEnt, const char *szProp ) + { + CBaseEntity *pEnt = ToEnt( hEnt ); + if ( !pEnt ) + return false; + + varinfo_t *pInfo = CacheFetch( pEnt, szProp ); + if ( !pInfo ) + { + pInfo = GetVarInfo( pEnt, szProp, INDEX_GET_TYPE ); + + if ( !pInfo ) + return false; + } + + return true; + } + + // FIXME: Cannot get datatable/arrays at the moment + const char *GetPropType( HSCRIPT hEnt, const char *szProp ) + { + CBaseEntity *pEnt = ToEnt( hEnt ); + if ( !pEnt ) + return NULL; + + varinfo_t *pInfo = CacheFetch( pEnt, szProp ); + if ( !pInfo ) + { + pInfo = GetVarInfo( pEnt, szProp, INDEX_GET_TYPE ); + + if ( !pInfo ) + return NULL; + } + + switch ( pInfo->datatype ) + { + case types::_INT1: + case types::_INT8: + case types::_INT16: + case types::_INT32: + return "integer"; + case types::_FLOAT: + return "float"; + case types::_VEC3: + return "vector"; + case types::_VEC2: + return "vector2d"; + case types::_CSTRING: + case types::_STRING_T: + case types::_STDSTRING: + return "string"; + case types::_EHANDLE: + case types::_CLASSPTR: + case types::_EDICT: + return "entity"; + case types::_PHYS: + return "phys"; + case types::_ARRAY: + return "array"; + case types::_DATATABLE: + return "datatable"; + } + + if ( pInfo->arraysize > 1 ) + return "array"; + + return ""; + } + + int GetPropArraySize( HSCRIPT hEnt, const char *szProp ) + { + CBaseEntity *pEnt = ToEnt( hEnt ); + if ( !pEnt ) + return -1; + + varinfo_t *pInfo = CacheFetch( pEnt, szProp ); + if ( !pInfo ) + { + pInfo = GetVarInfo( pEnt, szProp, INDEX_GET_TYPE ); + + if ( !pInfo ) + return -1; + } +#ifdef GAME_DLL + switch ( pInfo->datatype ) + { + case types::_DAR_EHANDLE: + { + CUtlVector< EHANDLE > &vec = *(CUtlVector< EHANDLE >*)((char*)pEnt + pInfo->offset); + if ( !vec.Base() ) + return -1; + return vec.Count(); + } + case types::_DAR_CLASSPTR: + { + CUtlVector< CBaseEntity* > &vec = *(CUtlVector< CBaseEntity* >*)((char*)pEnt + pInfo->offset); + if ( !vec.Base() ) + return -1; + return vec.Count(); + } + case types::_DAR_INT: + { + CUtlVector< int > &vec = *(CUtlVector< int >*)((char*)pEnt + pInfo->offset); + if ( !vec.Base() ) + return -1; + return vec.Count(); + } + case types::_DAR_FLOAT: + { + CUtlVector< float > &vec = *(CUtlVector< float >*)((char*)pEnt + pInfo->offset); + if ( !vec.Base() ) + return -1; + return vec.Count(); + } + } +#endif + return pInfo->arraysize; + } + +public: + int GetPropIntArray( HSCRIPT hEnt, const char *szProp, int index ) + { + CBaseEntity *pEnt = ToEnt( hEnt ); + if ( !pEnt ) + return -1; + + varinfo_t *pInfo = CacheFetch( pEnt, szProp ); + if ( !pInfo ) + { + pInfo = GetVarInfo( pEnt, szProp, index ); + + if ( !pInfo ) + return -1; + } + + if ( index < 0 || (unsigned int)index >= pInfo->arraysize ) + return -1; + + if ( pInfo->isNotNetworked ) + { + switch ( pInfo->datatype ) + { + case types::_INT32: + if ( pInfo->isUnsigned ) + return *(unsigned int*)((char*)pEnt + pInfo->GetOffset( index )); + return *(int*)((char*)pEnt + pInfo->GetOffset( index )); + case types::_INT1: + return *(bool*)((char*)pEnt + pInfo->GetOffset( index )); + case types::_INT8: + if ( pInfo->isUnsigned ) + return *(unsigned char*)((char*)pEnt + pInfo->GetOffset( index )); + return *(char*)((char*)pEnt + pInfo->GetOffset( index )); + case types::_INT16: + if ( pInfo->isUnsigned ) + return *(unsigned short*)((char*)pEnt + pInfo->GetOffset( index )); + return *(short*)((char*)pEnt + pInfo->GetOffset( index )); +#ifdef GAME_DLL + case types::_DAR_INT: + { + CUtlVector< int > &vec = *(CUtlVector< int >*)((char*)pEnt + pInfo->offset); + if ( !vec.Base() ) + return -1; + if ( index >= vec.Count() ) + return -1; + return vec[ index ]; + } +#endif + } + } + else + { + switch ( pInfo->datatype ) + { + case types::_INT32: + return (*(int*)((char*)pEnt + pInfo->GetOffset( index ))) & pInfo->mask; + } + } + + return -1; + } + + void SetPropIntArray( HSCRIPT hEnt, const char *szProp, int value, int index ) + { + CBaseEntity *pEnt = ToEnt( hEnt ); + if ( !pEnt ) + return; + + varinfo_t *pInfo = CacheFetch( pEnt, szProp ); + if ( !pInfo ) + { + pInfo = GetVarInfo( pEnt, szProp, index ); + + if ( !pInfo ) + return; + } + + if ( index < 0 || (unsigned int)index >= pInfo->arraysize ) + return; + + if ( pInfo->isNotNetworked ) + { + switch ( pInfo->datatype ) + { + case types::_INT32: + if ( pInfo->isUnsigned ) + { + *(unsigned int*)((char*)pEnt + pInfo->GetOffset( index )) = value; + NetworkStateChanged( pEnt, pInfo->GetOffset( index ) ); + break; + } + *(int*)((char*)pEnt + pInfo->GetOffset( index )) = value; + NetworkStateChanged( pEnt, pInfo->GetOffset( index ) ); + break; + case types::_INT1: + *(bool*)((char*)pEnt + pInfo->GetOffset( index )) = value; + NetworkStateChanged( pEnt, pInfo->GetOffset( index ) ); + break; + case types::_INT8: + if ( pInfo->isUnsigned ) + { + *(unsigned char*)((char*)pEnt + pInfo->GetOffset( index )) = value; + NetworkStateChanged( pEnt, pInfo->GetOffset( index ) ); + break; + } + *(char*)((char*)pEnt + pInfo->GetOffset( index )) = value; + NetworkStateChanged( pEnt, pInfo->GetOffset( index ) ); + break; + case types::_INT16: + if ( pInfo->isUnsigned ) + { + *(unsigned short*)((char*)pEnt + pInfo->GetOffset( index )) = value; + NetworkStateChanged( pEnt, pInfo->GetOffset( index ) ); + break; + } + *(short*)((char*)pEnt + pInfo->GetOffset( index )) = value; + NetworkStateChanged( pEnt, pInfo->GetOffset( index ) ); + break; +#ifdef GAME_DLL + case types::_DAR_INT: + { + CUtlVector< int > &vec = *(CUtlVector< int >*)((char*)pEnt + pInfo->offset); + if ( !vec.Base() ) + return; + if ( index >= vec.Count() ) + return; + vec[ index ] = value; + NetworkStateChanged( pEnt, pInfo->offset ); + break; + } +#endif + } + } + else + { + switch ( pInfo->datatype ) + { + case types::_INT32: + { + int *dest = (int*)((char*)pEnt + pInfo->GetOffset( index )); + *dest = (*dest & ~pInfo->mask) | (value & pInfo->mask); + NetworkStateChanged( pEnt, pInfo->GetOffset( index ) ); + break; + } + } + } + } + + float GetPropFloatArray( HSCRIPT hEnt, const char *szProp, int index ) + { + CBaseEntity *pEnt = ToEnt( hEnt ); + if ( !pEnt ) + return -1; + + varinfo_t *pInfo = CacheFetch( pEnt, szProp ); + if ( !pInfo ) + { + pInfo = GetVarInfo( pEnt, szProp, index ); + + if ( !pInfo ) + return -1; + } + + if ( pInfo->datatype == types::_VEC3 ) + index /= 3; + + if ( index < 0 || (unsigned int)index >= pInfo->arraysize ) + return -1; + + switch ( pInfo->datatype ) + { + case types::_VEC3: + case types::_FLOAT: + return *(float*)((char*)pEnt + pInfo->GetOffset( index )); +#ifdef GAME_DLL + case types::_DAR_FLOAT: + { + CUtlVector< float > &vec = *(CUtlVector< float >*)((char*)pEnt + pInfo->offset); + if ( !vec.Base() ) + return -1; + if ( index >= vec.Count() ) + return -1; + return vec[ index ]; + } +#endif + } + + return -1; + } + + void SetPropFloatArray( HSCRIPT hEnt, const char *szProp, float value, int index ) + { + CBaseEntity *pEnt = ToEnt( hEnt ); + if ( !pEnt ) + return; + + varinfo_t *pInfo = CacheFetch( pEnt, szProp ); + if ( !pInfo ) + { + pInfo = GetVarInfo( pEnt, szProp, index ); + + if ( !pInfo ) + return; + } + + if ( pInfo->datatype == types::_VEC3 ) + index /= 3; + + if ( index < 0 || (unsigned int)index >= pInfo->arraysize ) + return; + + switch ( pInfo->datatype ) + { + case types::_VEC3: + case types::_FLOAT: + *(float*)((char*)pEnt + pInfo->GetOffset( index )) = value; + NetworkStateChanged( pEnt, pInfo->GetOffset( index ) ); + break; +#ifdef GAME_DLL + case types::_DAR_FLOAT: + { + CUtlVector< float > &vec = *(CUtlVector< float >*)((char*)pEnt + pInfo->offset); + if ( !vec.Base() ) + return; + if ( index >= vec.Count() ) + return; + vec[ index ] = value; + NetworkStateChanged( pEnt, pInfo->offset ); + break; + } +#endif + } + } + + HSCRIPT GetPropEntityArray( HSCRIPT hEnt, const char *szProp, int index ) + { + CBaseEntity *pEnt = ToEnt( hEnt ); + if ( !pEnt ) + return NULL; + + varinfo_t *pInfo = CacheFetch( pEnt, szProp ); + if ( !pInfo ) + { + pInfo = GetVarInfo( pEnt, szProp, index ); + + if ( !pInfo ) + return NULL; + } + + if ( index < 0 || (unsigned int)index >= pInfo->arraysize ) + return NULL; + + switch ( pInfo->datatype ) + { + case types::_EHANDLE: + { + EHANDLE &iEHandle = *(EHANDLE*)((char*)pEnt + pInfo->GetOffset( index )); + return ToHScript( iEHandle ); + } +#ifdef GAME_DLL + case types::_CLASSPTR: + { + CBaseEntity* ptr = *(CBaseEntity**)((char*)pEnt + pInfo->GetOffset( index )); + return ToHScript( ptr ); + } + case types::_EDICT: + { + edict_t* ptr = *(edict_t**)((char*)pEnt + pInfo->GetOffset( index )); + return ToHScript( GetContainingEntity( ptr ) ); + } + case types::_DAR_EHANDLE: + { + CUtlVector< EHANDLE > &vec = *(CUtlVector< EHANDLE >*)((char*)pEnt + pInfo->offset); + if ( !vec.Base() ) + return NULL; + if ( index >= vec.Count() ) + return NULL; + return ToHScript( vec[ index ] ); + } + case types::_DAR_CLASSPTR: + { + CUtlVector< CBaseEntity* > &vec = *(CUtlVector< CBaseEntity* >*)((char*)pEnt + pInfo->offset); + if ( !vec.Base() ) + return NULL; + if ( index >= vec.Count() ) + return NULL; + return ToHScript( vec[ index ] ); + } +#endif + case types::_PHYS: + { + IPhysicsObject* ptr = *(IPhysicsObject**)((char*)pEnt + pInfo->GetOffset( index )); + return ptr ? g_pScriptVM->RegisterInstance( ptr ) : NULL; + } + } + + return NULL; + } + + void SetPropEntityArray( HSCRIPT hEnt, const char *szProp, HSCRIPT value, int index ) + { + CBaseEntity *pEnt = ToEnt( hEnt ); + if ( !pEnt ) + return; + + varinfo_t *pInfo = CacheFetch( pEnt, szProp ); + if ( !pInfo ) + { + pInfo = GetVarInfo( pEnt, szProp, index ); + + if ( !pInfo ) + return; + } + + if ( index < 0 || (unsigned int)index >= pInfo->arraysize ) + return; + + switch ( pInfo->datatype ) + { + case types::_EHANDLE: + *(EHANDLE*)((char*)pEnt + pInfo->GetOffset( index )) = ToEnt( value ); + NetworkStateChanged( pEnt, pInfo->GetOffset( index ) ); + break; +#ifdef GAME_DLL + case types::_CLASSPTR: + *(CBaseEntity**)((char*)pEnt + pInfo->GetOffset( index )) = ToEnt( value ); + NetworkStateChanged( pEnt, pInfo->GetOffset( index ) ); + break; + case types::_EDICT: + { + CBaseEntity* ptr = ToEnt( value ); + *(edict_t**)((char*)pEnt + pInfo->GetOffset( index )) = ptr ? ptr->edict() : NULL; + NetworkStateChanged( pEnt, pInfo->GetOffset( index ) ); + break; + } + case types::_DAR_EHANDLE: + { + CUtlVector< EHANDLE > &vec = *(CUtlVector< EHANDLE >*)((char*)pEnt + pInfo->offset); + if ( !vec.Base() ) + return; + if ( index >= vec.Count() ) + return; + vec[ index ] = ToEnt( value ); + NetworkStateChanged( pEnt, pInfo->offset ); + break; + } + case types::_DAR_CLASSPTR: + { + CUtlVector< CBaseEntity* > &vec = *(CUtlVector< CBaseEntity* >*)((char*)pEnt + pInfo->offset); + if ( !vec.Base() ) + return; + if ( index >= vec.Count() ) + return; + vec[ index ] = ToEnt( value ); + NetworkStateChanged( pEnt, pInfo->offset ); + break; + } +#endif + } + } + + const Vector &GetPropVectorArray( HSCRIPT hEnt, const char *szProp, int index ) + { + CBaseEntity *pEnt = ToEnt( hEnt ); + if ( !pEnt ) + return vec3_invalid; + + varinfo_t *pInfo = CacheFetch( pEnt, szProp ); + if ( !pInfo ) + { + pInfo = GetVarInfo( pEnt, szProp, index ); + + if ( !pInfo ) + return vec3_invalid; + } + + if ( index < 0 || (unsigned int)index >= pInfo->arraysize ) + return vec3_invalid; + + switch ( pInfo->datatype ) + { + case types::_VEC3: + return *(Vector*)((char*)pEnt + pInfo->GetOffset( index )); + } + + return vec3_invalid; + } + + void SetPropVectorArray( HSCRIPT hEnt, const char *szProp, const Vector &value, int index ) + { + CBaseEntity *pEnt = ToEnt( hEnt ); + if ( !pEnt ) + return; + + varinfo_t *pInfo = CacheFetch( pEnt, szProp ); + if ( !pInfo ) + { + pInfo = GetVarInfo( pEnt, szProp, index ); + + if ( !pInfo ) + return; + } + + if ( index < 0 || (unsigned int)index >= pInfo->arraysize ) + return; + + switch ( pInfo->datatype ) + { + case types::_VEC3: + *(Vector*)((char*)pEnt + pInfo->GetOffset( index )) = value; + NetworkStateChanged( pEnt, pInfo->GetOffset( index ) ); + break; + } + } + + const char *GetPropStringArray( HSCRIPT hEnt, const char *szProp, int index ) + { + CBaseEntity *pEnt = ToEnt( hEnt ); + if ( !pEnt ) + return NULL; + + varinfo_t *pInfo = CacheFetch( pEnt, szProp ); + if ( !pInfo ) + { + pInfo = GetVarInfo( pEnt, szProp, index ); + + if ( !pInfo ) + return NULL; + } + + if ( index < 0 || (unsigned int)index >= pInfo->arraysize ) + return NULL; + + switch ( pInfo->datatype ) + { + case types::_CSTRING: + return (const char*)((char*)pEnt + pInfo->GetOffset( index )); + case types::_STRING_T: // Identical to _CSTRING on client + return STRING( *(string_t*)((char*)pEnt + pInfo->GetOffset( index )) ); + case types::_INT8: + { + if ( !pInfo->stringsize ) + return NULL; + + char * const pVar = ((char*)pEnt + pInfo->GetOffset( index )); + + // Is this null terminated? + int i = 0; + char *c = pVar; + while ( *(c++) && i++ < pInfo->stringsize ); + + if ( i >= pInfo->stringsize ) + { + // Not a null terminated string, don't talk to me ever again + pInfo->stringsize = 0; + return NULL; + } + + return pVar; + } +#ifdef GAME_DLL + case types::_STDSTRING: + return ( (std::string*)((char*)pEnt + pInfo->GetOffset( index )) )->c_str(); +#endif + } + + return NULL; + } + + void SetPropStringArray( HSCRIPT hEnt, const char *szProp, const char *value, int index ) + { + CBaseEntity *pEnt = ToEnt( hEnt ); + if ( !pEnt ) + return; + + varinfo_t *pInfo = CacheFetch( pEnt, szProp ); + if ( !pInfo ) + { + pInfo = GetVarInfo( pEnt, szProp, index ); + + if ( !pInfo ) + return; + } + + if ( index < 0 || (unsigned int)index >= pInfo->arraysize ) + return; + + switch ( pInfo->datatype ) + { + case types::_CSTRING: + case types::_INT8: + { + if ( pInfo->stringsize ) + { + V_strncpy( (char*)pEnt + pInfo->GetOffset( index ), value, pInfo->stringsize ); + NetworkStateChanged( pEnt, pInfo->GetOffset( index ) ); + break; + } + } + case types::_STRING_T: + { + extern string_t FindPooledString( const char* ); + extern string_t AllocPooledString( const char* ); + + string_t src = FindPooledString( value ); + if ( src == NULL_STRING ) + src = AllocPooledString( value ); +#ifdef GAME_DLL + *(string_t*)((char*)pEnt + pInfo->GetOffset( index )) = src; +#else + V_strcpy( (char*)pEnt + pInfo->GetOffset( index ), src ); +#endif + NetworkStateChanged( pEnt, pInfo->GetOffset( index ) ); + break; + } +#ifdef GAME_DLL + case types::_STDSTRING: + { + ( (std::string*)((char*)pEnt + pInfo->GetOffset( index )) )->assign( value, V_strlen(value) ); + NetworkStateChanged( pEnt, pInfo->GetOffset( index ) ); + break; + } +#endif + } + } + +#define GetProp( type, name )\ + type GetProp##name( HSCRIPT hEnt, const char* szProp )\ + {\ + return GetProp##name##Array( hEnt, szProp, 0 );\ + } + +#define SetProp( type, name )\ + void SetProp##name( HSCRIPT hEnt, const char* szProp, type value )\ + {\ + SetProp##name##Array( hEnt, szProp, value, 0 );\ + } + + GetProp( int, Int ); + SetProp( int, Int ); + GetProp( float, Float ); + SetProp( float, Float ); + GetProp( HSCRIPT, Entity ); + SetProp( HSCRIPT, Entity ); + GetProp( const Vector&, Vector ); + SetProp( const Vector&, Vector ); + GetProp( const char*, String ); + SetProp( const char*, String ); + +#undef GetProp +#undef SetProp + +#ifdef _DEBUG +private: + CUtlBuffer m_output; + CUtlString m_indent; + int m_indent_level; + + void IndentStart() + { + m_indent = ""; + m_indent_level = 0; + } + + void Indent1() + { + m_indent_level++; + m_indent.Append("\t"); + } + + void Indent0() + { + m_indent_level--; + m_indent = m_indent.Slice( 0, m_indent_level ); + } + + void PrintVec3( float *pVar ) + { + if ( *(Vector*)pVar != vec3_invalid ) + { + Print( "[%f %f %f]", pVar[0], pVar[1], pVar[2] ); + } + else + { + Print("vec3_invalid"); + } + } + + void PrintVec2( float *pVar ) + { + Print( "[%f %f]", pVar[0], pVar[1] ); + } + + void PrintEntity( EHANDLE* pVar ) + { + CBaseEntity* ent = *pVar; + if ( ent ) + { + Print("[%d]%s", ent->entindex(), ent->GetDebugName()); + } + else + { + Print("null"); + } + } +#ifdef GAME_DLL + void PrintEntity( CBaseEntity* pVar ) + { + CBaseEntity* ent = pVar; + if ( ent ) + { + Print("[%d]%s", ent->entindex(), ent->GetDebugName()); + } + else + { + Print("null"); + } + } + + void PrintEntity( edict_t* pVar ) + { + CBaseEntity* ent = GetContainingEntity( pVar ); + if ( ent ) + { + Print("[%d]%s", ent->entindex(), ent->GetDebugName()); + } + else + { + Print("null"); + } + } +#endif +#ifdef GAME_DLL + void PrintString( string_t pVar ) + { + if ( STRING(pVar) ) + { + Print("\"%s\"", STRING(pVar)); + } + else + { + Print("null"); + } + } +#endif + void PrintString( const char *pVar ) + { + if ( pVar ) + { + Print("\"%s\"", pVar); + } + else + { + Print("null"); + } + } + + void PrintPropType( NetProp *pProp ) + { + switch ( pProp->GetType() ) + { + case DPT_Int: + if ( IsUtlVector( pProp ) ) + { + Print("UtlVector"); + } + else if ( IsEHandle( pProp ) ) + { + Print( "entity" ); + } + else + { + Print( "int" ); + } + break; +#ifdef SUPPORTS_INT64 + case DPT_Int64: + AssertMsg( 0, "not implemented" ); + Print( "int64" ); + break; +#endif + case DPT_Float: + Print( "float" ); + break; + case DPT_Vector: + Print( "vec3" ); + break; + case DPT_VectorXY: + Print( "vec2" ); + break; + case DPT_String: + { +#ifdef GAME_DLL + if ( pProp->GetProxyFn() == SendProxy_StringT_To_String ) + { + Print("string_t"); + } + else +#endif + { +#ifdef CLIENT_DLL + Print("string[%d]", pProp->m_StringBufferSize); +#else + Print("string"); +#endif + } + break; + } + case DPT_Array: + case DPT_DataTable: + break; + default: UNREACHABLE(); + } + } + + void PrintProp_r( char *pVar, NetProp *pProp ) + { + switch ( pProp->GetType() ) + { + case DPT_Int: + { + if ( IsUtlVector( pProp ) ) + { + } + else if ( IsEHandle( pProp ) ) + { + PrintEntity( (EHANDLE*)pVar ); + } + else + { +#ifdef GAME_DLL + // Is this value larger than networked size? + AssertMsg( (*(int*)pVar & MASK_NEAREST_BYTE( pProp->m_nBits )) == 0, + "%s(%i) %d bits doesn't fit networked %d bits", + pProp->GetName(), *(int*)pVar & MASK_NEAREST_BYTE( pProp->m_nBits ), ALIGN_TO_NEAREST_BYTE(pProp->m_nBits), pProp->m_nBits ); +#endif + int size = GetIntPropSize( pProp ); + if ( size ) + { + Print( "%i", *(int*)pVar & MASK_INT_SIZE( size ) ); + } + else + { + Print( " 0x%08x", *(int*)pVar ); + } + } + break; + } +#ifdef SUPPORTS_INT64 + case DPT_Int64: + { + Print( "%lli", *(int64*)pVar ); + break; + } +#endif + case DPT_Float: + { + Assert( pProp->GetElementStride() == sizeof(float) || pProp->GetElementStride() < 0 ); + if ( *(float*)pVar == FLT_MAX ) + { + Print("FLT_MAX"); + } + else + { + Print("%f", *(float*)pVar); + } + break; + } + case DPT_Vector: + { + PrintVec3( (float*)pVar ); + break; + } + case DPT_VectorXY: + { + PrintVec2( (float*)pVar ); + break; + } + case DPT_String: + { +#ifdef GAME_DLL + if ( pProp->GetProxyFn() == SendProxy_StringT_To_String ) + { + PrintString( *(string_t*)pVar ); + } + else +#endif + { + Assert( pProp->GetProxyFn() == DataTableProxy_String ); + PrintString( (char*)pVar ); + } + break; + } + case DPT_DataTable: + { + NetTable* pArray = pProp->GetDataTable(); + Assert( pArray->GetNumProps() ); + + if ( V_strcmp( pProp->GetName(), pArray->GetName() ) != 0 ) + { + Print( " -> (%s)\n", pArray->GetName() ); + DumpNetTable_r( pVar, pArray ); + break; + } + + // Double check that each element is the same size + // Array indexing ints gets element size from this + int diff1 = pArray->GetProp(1)->GetOffset() - pArray->GetProp(0)->GetOffset(); + for ( int k = 0; k < pArray->GetNumProps()-1; k++ ) + { + int diff2 = pArray->GetProp(k+1)->GetOffset() - pArray->GetProp(k)->GetOffset(); + Assert( diff1 == diff2 ); + } + + Print(" <"); + PrintPropType( pArray->GetProp(0) ); + Print(" array> #%d", pArray->GetNumProps()); + Print("\n%s[", m_indent.Get()); + Indent1(); + + for ( int j = 0; j < pArray->GetNumProps(); j++ ) + { + Print("\n%s", m_indent.Get()); + PrintProp_r( pVar + pArray->GetProp(j)->GetOffset(), pArray->GetProp(j) ); + } + + Indent0(); + Print( "\n%s]", m_indent.Get() ); + + break; + } + case DPT_Array: + { + Assert( pProp->GetArrayProp() ); + NetProp *pArray = pProp->GetArrayProp(); + pVar += pArray->GetOffset(); + + int numElements = pProp->GetNumElements(); + int elementStride = pProp->GetElementStride(); + + Print(" <"); + PrintPropType( pArray ); + Print(" array> #%d", numElements); + Print("\n%s[", m_indent.Get()); + Indent1(); + + for ( int j = 0; j < numElements; j++ ) + { + Print("\n%s", m_indent.Get()); + PrintProp_r( pVar + j * elementStride, pArray ); + } + + Indent0(); + Print( "\n%s]", m_indent.Get() ); + + break; + } + default: UNREACHABLE(); + } + } + + void DumpNetTable_r( void *pEnt, NetTable *pTable ) + { + Print("%s{\n", m_indent.Get()); + Indent1(); + + int numProps = pTable->GetNumProps(); + + for ( int i = 0; i < numProps; i++ ) + { + NetProp* pProp = pTable->GetProp(i); + char* pVar = (char*)pEnt + pProp->GetOffset(); + + if ( pProp->IsInsideArray() ) + continue; + + Print( "%s%s", m_indent.Get(), pProp->GetName() ); + + if ( pProp->GetOffset() == 0 ) + Print("<0>"); + + if ( pProp->GetType() != DPT_DataTable ) + Print(" <"); + PrintPropType( pProp ); + if ( pProp->GetType() != DPT_DataTable ) + Print("> "); + PrintProp_r( pVar, pProp ); + Print("\n"); + } + + Indent0(); + Print("%s}", m_indent.Get()); + } + + void PrintFieldType( char *pVar, typedescription_t *td ) + { + switch ( td->fieldType ) + { + case FIELD_INTEGER: + case FIELD_MATERIALINDEX: + case FIELD_MODELINDEX: + case FIELD_TICK: + Print( "int" ); + break; + case FIELD_SHORT: + Print( "short" ); + break; + case FIELD_CHARACTER: + Print( "char" ); + break; + case FIELD_BOOLEAN: + Print( "bool" ); + break; + case FIELD_COLOR32: + Print( "clr32" ); + break; + case FIELD_FLOAT: + case FIELD_TIME: + Print( "float" ); + break; + case FIELD_VECTOR: + case FIELD_POSITION_VECTOR: + Print( "vec3" ); + break; + case FIELD_VECTOR2D: + Print( "vec2" ); + break; + case FIELD_STRING: + case FIELD_MODELNAME: + case FIELD_SOUNDNAME: + Print( "string" ); + break; + case FIELD_EHANDLE: +#ifdef GAME_DLL + case FIELD_CLASSPTR: + case FIELD_EDICT: +#endif + Print( "entity" ); + break; + case FIELD_VMATRIX: + Print( "VMatrix" ); + break; + case FIELD_VMATRIX_WORLDSPACE: + Print( "VMatrix WORLDSPACE" ); + break; + case FIELD_MATRIX3X4_WORLDSPACE: + Print( "matrix3x4 WORLDSPACE" ); + break; + case FIELD_INTERVAL: + Print( "interval_t" ); + break; + case FIELD_CUSTOM: + PrintCustomFieldType( pVar, td ); + break; + case FIELD_EMBEDDED: + if ( td->fieldSize > 1 ) + Print( "DT" ); + break; + default: + Print( "unknown %d", td->fieldType ); + } + } + + void PrintCustomFieldType( char *pVar, typedescription_t *td ) + { + Assert( td->fieldType == FIELD_CUSTOM ); + + const char *g_ppszPhysTypeNames[PIID_NUM_TYPES] = + { + "Unknown Phys", + "IPhysicsObject", + "IPhysicsFluidController", + "IPhysicsSpring", + "IPhysicsConstraintGroup", + "IPhysicsConstraint", + "IPhysicsShadowController", + "IPhysicsPlayerController", + "IPhysicsMotionController", + "IPhysicsVehicleController", + }; + + for ( int i = 0; i < PIID_NUM_TYPES; i++ ) + { + if ( td->pSaveRestoreOps == GetPhysObjSaveRestoreOps( (PhysInterfaceId_t)i ) ) + { + Print("%s", g_ppszPhysTypeNames[i]); + return; + } + } + + if ( td->pSaveRestoreOps == ActivityDataOps() ) + { + Print("int"); + } + else if ( td->pSaveRestoreOps == GetSoundSaveRestoreOps() ) + { + Print("CSoundPatch"); + } + else if ( td->pSaveRestoreOps == GetStdStringDataOps() ) + { + Print("stdstring"); + } +#ifdef GAME_DLL + else if ( IS_EHANDLE_UTLVECTOR( td ) ) + { + CUtlVector< EHANDLE > &vec = *(CUtlVector< EHANDLE >*)pVar; + if ( vec.Base() ) + Print("entity utlvector #%d", vec.Count()); + else + Print("entity utlvector"); + } + else if ( td->pSaveRestoreOps == UTLVECTOR_DATAOPS( FIELD_INTEGER, int ) ) + { + CUtlVector< int > &vec = *(CUtlVector< int >*)pVar; + if ( vec.Base() ) + Print("int utlvector #%d", vec.Count()); + else + Print("int utlvector"); + } + else if ( td->pSaveRestoreOps == UTLVECTOR_DATAOPS( FIELD_FLOAT, float ) || + td->pSaveRestoreOps == UTLVECTOR_DATAOPS( FIELD_TIME, float ) ) + { + CUtlVector< float > &vec = *(CUtlVector< float >*)pVar; + if ( vec.Base() ) + Print("float utlvector #%d", vec.Count()); + else + Print("float utlvector"); + } + else if ( td->pSaveRestoreOps == UTLVECTOR_DATAOPS( FIELD_STRING, string_t ) ) + { + CUtlVector< string_t > &vec = *(CUtlVector< string_t >*)pVar; + if ( vec.Base() ) + Print("string utlvector #%d", vec.Count()); + else + Print("string utlvector"); + } + else if ( td->pSaveRestoreOps == UTLVECTOR_DATAOPS( FIELD_CLASSPTR, CBaseEntity* ) ) + { + CUtlVector< CBaseEntity* > &vec = *(CUtlVector< CBaseEntity* >*)pVar; + if ( vec.Base() ) + Print("entity utlvector #%d", vec.Count()); + else + Print("entity utlvector"); + } + else if ( td->pSaveRestoreOps == UTLVECTOR_DATAOPS( FIELD_VECTOR, Vector ) ) + { + AssertMsg( 0, "Implement me" ); + CUtlVector< Vector > &vec = *(CUtlVector< Vector >*)pVar; + if ( vec.Base() ) + Print("Vector utlvector #%d", vec.Count()); + else + Print("Vector utlvector"); + } + else if ( !V_strcmp( td->fieldName, "m_pIk" ) ) + { + Print("IK"); + } + else if ( td->pSaveRestoreOps == thinkcontextFuncs ) + { + Print("thinkfunc"); + } + else if ( td->pSaveRestoreOps == (ISaveRestoreOps*)(&g_AI_MemoryListSaveRestoreOps) ) + { + Print("AI memory map"); + } + else if ( td->pSaveRestoreOps == (ISaveRestoreOps*)(&g_VguiScreenStringOps)) + { + Print("string (vgui screen)"); + } + else if ( td->pSaveRestoreOps == (ISaveRestoreOps*)(&g_ConceptHistoriesSaveDataOps) ) + { + Print("concept histories"); + } +#endif // GAME_DLL + else + { + Print("custom"); + } + } + + void PrintCustomField( char *pVar, typedescription_t *td ) + { + Assert( td->fieldType == FIELD_CUSTOM ); + + for ( int i = 0; i < PIID_NUM_TYPES; i++ ) + { + if ( td->pSaveRestoreOps == GetPhysObjSaveRestoreOps( (PhysInterfaceId_t)i ) ) + { + Print("0x%x", pVar); + return; + } + } + + if ( td->pSaveRestoreOps == ActivityDataOps() ) + { + Print("%i", *(int*)pVar); + } + else if ( td->pSaveRestoreOps == GetSoundSaveRestoreOps() ) + { + if ( *pVar ) + { + CSoundPatch *pSound = *(CSoundPatch**)pVar; + PrintString( CSoundEnvelopeController::GetController().SoundGetName( pSound ) ); + } + else + { + Print( "null" ); + } + } + else if ( td->pSaveRestoreOps == GetStdStringDataOps() ) + { + Print("%s", ((std::string*)pVar)->c_str()); + } +#ifdef GAME_DLL + else if ( IS_EHANDLE_UTLVECTOR( td ) ) + { + CUtlVector< EHANDLE > &vec = *(CUtlVector< EHANDLE >*)pVar; + if ( !vec.Base() ) + { + Print("null"); + return; + } + Print("\n%s[", m_indent.Get()); + Indent1(); + FOR_EACH_VEC( vec, i ) + { + Print("\n%s", m_indent.Get()); + PrintEntity( vec[i] ); + } + Indent0(); + Print("\n%s]", m_indent.Get()); + } + else if ( td->pSaveRestoreOps == UTLVECTOR_DATAOPS( FIELD_INTEGER, int ) ) + { + CUtlVector< int > &vec = *(CUtlVector< int >*)pVar; + if ( !vec.Base() ) + { + Print("null"); + return; + } + Print("\n%s[", m_indent.Get()); + Indent1(); + FOR_EACH_VEC( vec, i ) + { + Print("\n%s", m_indent.Get()); + Print( "%i", vec[i] ); + } + Indent0(); + Print("\n%s]", m_indent.Get()); + } + else if ( td->pSaveRestoreOps == UTLVECTOR_DATAOPS( FIELD_FLOAT, float ) || + td->pSaveRestoreOps == UTLVECTOR_DATAOPS( FIELD_TIME, float ) ) + { + CUtlVector< float > &vec = *(CUtlVector< float >*)pVar; + if ( !vec.Base() ) + { + Print("null"); + return; + } + Print("\n%s[", m_indent.Get()); + Indent1(); + FOR_EACH_VEC( vec, i ) + { + Print("\n%s", m_indent.Get()); + Print( "%f", vec[i] ); + } + Indent0(); + Print("\n%s]", m_indent.Get()); + } + else if ( td->pSaveRestoreOps == UTLVECTOR_DATAOPS( FIELD_STRING, string_t ) ) + { + CUtlVector< string_t > &vec = *(CUtlVector< string_t >*)pVar; + if ( !vec.Base() ) + { + Print("null"); + return; + } + Print("\n%s[", m_indent.Get()); + Indent1(); + FOR_EACH_VEC( vec, i ) + { + Print("\n%s", m_indent.Get()); + PrintString( vec[i] ); + } + Indent0(); + Print("\n%s]", m_indent.Get()); + } + else if ( td->pSaveRestoreOps == UTLVECTOR_DATAOPS( FIELD_CLASSPTR, CBaseEntity* ) ) + { + CUtlVector< CBaseEntity* > &vec = *(CUtlVector< CBaseEntity* >*)pVar; + if ( !vec.Base() ) + { + Print("null"); + return; + } + Print("\n%s[", m_indent.Get()); + Indent1(); + FOR_EACH_VEC( vec, i ) + { + Print("\n%s", m_indent.Get()); + PrintEntity( vec[i] ); + } + Indent0(); + Print("\n%s]", m_indent.Get()); + } + else if ( td->pSaveRestoreOps == (ISaveRestoreOps*)(&g_VguiScreenStringOps) ) + { + const char *pString = g_pStringTableVguiScreen->GetString( *(int*)pVar ); + PrintString( (char*)pString ); + } +#endif // GAME_DLL + else + { + Print("0x%x", pVar); + } + } + + void PrintField_r( char *pVar, typedescription_t *td ) + { + switch ( td->fieldType ) + { + case FIELD_INTEGER: + case FIELD_MATERIALINDEX: + case FIELD_MODELINDEX: + case FIELD_TICK: + if ( td->flags & SPROP_UNSIGNED ) + { + Print("%u", *(unsigned int*)pVar); + } + else + { + Print("%i", *(int*)pVar); + } + break; + case FIELD_COLOR32: + Print("0x%08x", *(int*)pVar); + break; + case FIELD_BOOLEAN: + Print("%i", *(bool*)pVar & 1); + break; + case FIELD_CHARACTER: + if ( *pVar < 0x20 ) + { + Print("%i (0x%x)", *pVar, *pVar); + } + else + { + Print("%i '%c'", *pVar, *pVar); + } + break; + case FIELD_SHORT: + if ( td->flags & SPROP_UNSIGNED ) + { + Print("%u", *(unsigned short*)pVar); + } + else + { + Print("%i", *(short*)pVar); + } + break; + case FIELD_FLOAT: + case FIELD_TIME: + if ( *(float*)pVar == FLT_MAX ) + { + Print("FLT_MAX"); + } + else + { + Print("%f", *(float*)pVar); + } + break; + case FIELD_VECTOR: + case FIELD_POSITION_VECTOR: + PrintVec3( (float*)pVar ); + break; + case FIELD_VECTOR2D: + PrintVec2( (float*)pVar ); + break; + case FIELD_STRING: + case FIELD_MODELNAME: + case FIELD_SOUNDNAME: +#ifdef GAME_DLL + PrintString( *(string_t*)pVar ); +#else + PrintString( *(char**)pVar ); +#endif + break; + case FIELD_EHANDLE: + PrintEntity( (EHANDLE*)pVar ); + break; +#ifdef GAME_DLL + case FIELD_CLASSPTR: + PrintEntity( *(CBaseEntity**)pVar ); + break; + case FIELD_EDICT: + PrintEntity( *(edict_t**)pVar ); + break; +#endif + case FIELD_EMBEDDED: + Print(" -> (%s)\n", td->td->dataClassName); + DumpDataFields_r( pVar, td->td ); + break; + case FIELD_CUSTOM: + PrintCustomField( pVar, td ); + break; + default: + Print( "", td->fieldType ); + } + } + + void DumpDataFields_r( void *pEnt, datamap_t *map ) + { + Print("%s{\n", m_indent.Get()); + Indent1(); + + if ( map->baseMap ) + { + Print("%sbaseclass -> (%s)\n", m_indent.Get(), map->baseMap->dataClassName); + DumpDataFields_r( pEnt, map->baseMap ); + Print("\n"); + } + + typedescription_t *pFields = map->dataDesc; + int numFields = map->dataNumFields; + + for ( int i = 0; i < numFields; i++ ) + { + typedescription_t* td = &pFields[i]; + + if ( td->flags & (FTYPEDESC_FUNCTIONTABLE | FTYPEDESC_INPUT | FTYPEDESC_OUTPUT) ) + continue; + + if ( td->fieldType == FIELD_VOID || td->fieldType == FIELD_FUNCTION ) + continue; + + char *pVar = (char*)pEnt + td->fieldOffset[ TD_OFFSET_NORMAL ]; + + if ( td->flags & FTYPEDESC_PTR ) + { + AssertMsg( *(char**)pVar, "NULL ptr ref" ); + pVar = *(char**)pVar; + } + + Print( "%s%s", m_indent.Get(), td->fieldName ); + + if ( td->fieldSize == 1 ) + { + if ( td->fieldType != FIELD_EMBEDDED ) + Print(" <"); + PrintFieldType( pVar, td ); + if ( td->fieldType != FIELD_EMBEDDED ) + Print("> "); + PrintField_r( pVar, td ); + } + else + { + Print(" <"); + PrintFieldType( pVar, td ); + Print(" array> #%d", td->fieldSize); + + Print("\n%s[", m_indent.Get()); + Indent1(); + + for ( int j = 0; j < td->fieldSize; j++ ) + { + Print("\n%s", m_indent.Get()); + PrintField_r( pVar + j * td->fieldSizeInBytes / td->fieldSize, td ); + } + + Indent0(); + Print("\n%s]", m_indent.Get()); + } + + Print("\n"); + } + + Indent0(); + Print("%s}", m_indent.Get()); + } + + void Print( const char *fmt, ... ) + { + char buf[2048]; + va_list va; + va_start( va, fmt ); + V_vsnprintf( buf, sizeof(buf) - 1, fmt, va ); + va_end( va ); + + m_output.PutString( buf ); + } + +public: + void Dump( HSCRIPT hEnt, const char* filename ) + { + CBaseEntity *pEnt = ToEnt( hEnt ); + if ( !pEnt ) + return; + + if ( !filename || !*filename ) + return; + + m_output.SetBufferType( true, false ); + IndentStart(); + + Print( "\n" ); + Print( "(%s)\n", GetNetTable( GetNetworkClass(pEnt) )->GetName() ); + DumpNetTable_r( pEnt, GetNetTable( GetNetworkClass(pEnt) ) ); + Print( "\n\n" ); + + Print( "\n" ); + Print( "(%s)\n", pEnt->GetDataDescMap()->dataClassName ); + DumpDataFields_r( pEnt, pEnt->GetDataDescMap() ); + Print( "\n\n" ); +#ifdef CLIENT_DLL + Print( "\n" ); + Print( "(%s)\n", pEnt->GetPredDescMap()->dataClassName ); + DumpDataFields_r( pEnt, pEnt->GetPredDescMap() ); + Print( "\n\n" ); +#endif + const char *pszFile = V_GetFileName( filename ); + filesystem->WriteFile( pszFile, "MOD", m_output ); + + m_indent.Purge(); + m_output.Purge(); + } +#endif // _DEBUG +} g_ScriptNetPropManager; + +BEGIN_SCRIPTDESC_ROOT_NAMED( CScriptNetPropManager, "CNetPropManager", SCRIPT_SINGLETON "Allows reading and updating the network properties and data fields of an entity." ) + DEFINE_SCRIPTFUNC( GetPropArraySize, "Returns the size of an array." ) + DEFINE_SCRIPTFUNC( GetPropEntity, "Reads an entity." ) + DEFINE_SCRIPTFUNC( GetPropEntityArray, "Reads an entity from an array." ) + DEFINE_SCRIPTFUNC( GetPropFloat, "Reads a float." ) + DEFINE_SCRIPTFUNC( GetPropFloatArray, "Reads a float from an array." ) + DEFINE_SCRIPTFUNC( GetPropInt, "Reads an integer." ) + DEFINE_SCRIPTFUNC( GetPropIntArray, "Reads an integer from an array." ) + DEFINE_SCRIPTFUNC( GetPropString, "Reads a string." ) + DEFINE_SCRIPTFUNC( GetPropStringArray, "Reads a string from an array." ) + DEFINE_SCRIPTFUNC( GetPropVector, "Reads a 3D vector." ) + DEFINE_SCRIPTFUNC( GetPropVectorArray, "Reads a 3D vector from an array." ) + DEFINE_SCRIPTFUNC( GetPropType, "Returns the netprop type as a string." ) + DEFINE_SCRIPTFUNC( HasProp, "Checks if netprop/datafield exists." ) + DEFINE_SCRIPTFUNC( SetPropEntity, "Sets an entity." ) + DEFINE_SCRIPTFUNC( SetPropEntityArray, "Sets an entity in an array." ) + DEFINE_SCRIPTFUNC( SetPropFloat, "Sets to the specified float." ) + DEFINE_SCRIPTFUNC( SetPropFloatArray, "Sets a float in an array." ) + DEFINE_SCRIPTFUNC( SetPropInt, "Sets to the specified integer." ) + DEFINE_SCRIPTFUNC( SetPropIntArray, "Sets an integer in an array." ) + DEFINE_SCRIPTFUNC( SetPropString, "Sets to the specified string." ) + DEFINE_SCRIPTFUNC( SetPropStringArray, "Sets a string in an array." ) + DEFINE_SCRIPTFUNC( SetPropVector, "Sets to the specified vector." ) + DEFINE_SCRIPTFUNC( SetPropVectorArray, "Sets a 3D vector in an array." ) +#ifdef _DEBUG + DEFINE_SCRIPTFUNC( Dump, "Dump all readable netprop and datafield values of this entity. Pass in file name to write into." ); +#endif +END_SCRIPTDESC(); + +//============================================================================= +// Localization Interface +// Unique to Mapbase +//============================================================================= +class CScriptLocalize +{ +public: + + const char *GetTokenAsUTF8( const char *pszToken ) + { + const char *pText = g_pVGuiLocalize->FindAsUTF8( pszToken ); + if ( pText ) + { + return pText; + } + + return NULL; + } + + void AddStringAsUTF8( const char *pszToken, const char *pszString ) + { + wchar_t wpszString[256]; + g_pVGuiLocalize->ConvertANSIToUnicode( pszString, wpszString, sizeof(wpszString) ); + + // TODO: This is a fake file name! Should "fileName" mean anything? + g_pVGuiLocalize->AddString( pszToken, wpszString, "resource/vscript_localization.txt" ); + } + +private: +} g_ScriptLocalize; + +BEGIN_SCRIPTDESC_ROOT_NAMED( CScriptLocalize, "CLocalize", SCRIPT_SINGLETON "Accesses functions related to localization strings." ) + + DEFINE_SCRIPTFUNC( GetTokenAsUTF8, "Gets the current language's token as a UTF-8 string (not Unicode)." ) + + DEFINE_SCRIPTFUNC( AddStringAsUTF8, "Adds a new localized token as a UTF-8 string (not Unicode)." ) + +END_SCRIPTDESC(); + +//============================================================================= +// Game Event Listener +// Based on Source 2 API +// +// NOTE: In Source 2 vscript (Lua) event listener contexts are tables that are +// passed to the callback function as the call environment. +// In mapbase implementation these are string identifiers because unlike Lua, +// Squirrel has closure methods such as 'bindenv' which can bind functions to specified environments. +//============================================================================= + +// Define to use the older code that loads all events manually independent from the game event manager. +// Otherwise access event descriptors directly from engine. +//#define USE_OLD_EVENT_DESCRIPTORS 1 + +class CScriptGameEventListener : public IGameEventListener2, public CAutoGameSystem +{ +public: + CScriptGameEventListener() : m_bActive(false) + { +#ifdef _DEBUG + m_nEventTick = 0; +#endif + } + + ~CScriptGameEventListener() + { + StopListeningForEvent(); + } + + int ListenToGameEvent( const char* szEvent, HSCRIPT hFunc, const char* szContext ); + void StopListeningForEvent(); + +public: + static bool StopListeningToGameEvent( int listener ); + static void StopListeningToAllGameEvents( const char* szContext ); + +public: + void FireGameEvent( IGameEvent *event ); + void LevelShutdownPreEntity(); + +private: + //int m_index; + HSCRIPT m_hCallback; + unsigned int m_iContextHash; + bool m_bActive; +#ifdef _DEBUG + int m_nEventTick; +#endif + + static StringHashFunctor Hash; + static inline unsigned int HashContext( const char* c ) { return c ? Hash(c) : 0; } + + inline int GetIndex() + { + Assert( sizeof(CScriptGameEventListener*) == sizeof(int) ); + return reinterpret_cast(this); + } + +public: + enum // event data types, dependant on engine definitions + { + TYPE_LOCAL = 0, + TYPE_STRING = 1, + TYPE_FLOAT = 2, + TYPE_LONG = 3, + TYPE_SHORT = 4, + TYPE_BYTE = 5, + TYPE_BOOL = 6 + }; + static void WriteEventData( IGameEvent *event, HSCRIPT hTable ); + +#ifdef USE_OLD_EVENT_DESCRIPTORS + static void LoadAllEvents(); + static void LoadEventsFromFile( const char *filename, const char *pathID = NULL ); + static CUtlMap< unsigned int, KeyValues* > s_GameEvents; + static CUtlVector< KeyValues* > s_LoadedFiles; +#endif + +public: + //static int g_nIndexCounter; + static CUtlVectorAutoPurge< CScriptGameEventListener* > s_Listeners; +#if _DEBUG + static void DumpEventListeners(); +#endif + +}; + +CUtlVectorAutoPurge< CScriptGameEventListener* > CScriptGameEventListener::s_Listeners; +StringHashFunctor CScriptGameEventListener::Hash; + +#ifdef USE_OLD_EVENT_DESCRIPTORS +CUtlMap< unsigned int, KeyValues* > CScriptGameEventListener::s_GameEvents( DefLessFunc(unsigned int) ); +CUtlVector< KeyValues* > CScriptGameEventListener::s_LoadedFiles; +#endif + + +#if _DEBUG +#ifdef CLIENT_DLL +CON_COMMAND_F( cl_dump_script_game_event_listeners, "Dump all game event listeners created from script.", FCVAR_CHEAT ) +{ + CScriptGameEventListener::DumpEventListeners(); +} +#else +CON_COMMAND_F( dump_script_game_event_listeners, "Dump all game event listeners created from script.", FCVAR_CHEAT ) +{ + CScriptGameEventListener::DumpEventListeners(); +} +#endif +#endif + + +#ifdef USE_OLD_EVENT_DESCRIPTORS +//----------------------------------------------------------------------------- +// Executed in LevelInitPreEntity +//----------------------------------------------------------------------------- +void CScriptGameEventListener::LoadAllEvents() +{ + // Listed in the same order they are loaded in GameEventManager + const char *filenames[] = + { + "resource/serverevents.res", + "resource/gameevents.res", + "resource/mapbaseevents.res", + "resource/modevents.res" + }; + + const char *pathlist[] = + { + "GAME", + "MOD" + }; + + // Destroy old KeyValues + if ( s_LoadedFiles.Count() ) + { + for ( int i = s_LoadedFiles.Count(); i--; ) + s_LoadedFiles[i]->deleteThis(); + s_LoadedFiles.Purge(); + s_GameEvents.Purge(); + } + + for ( int j = 0; j < ARRAYSIZE(pathlist); ++j ) + for ( int i = 0; i < ARRAYSIZE(filenames); ++i ) + { + LoadEventsFromFile( filenames[i], pathlist[j] ); + } +} + +//----------------------------------------------------------------------------- +// Load event files into a lookup array to be able to return the event data to the VM. +//----------------------------------------------------------------------------- +void CScriptGameEventListener::LoadEventsFromFile( const char *filename, const char *pathID ) +{ + KeyValues *pKV = new KeyValues("GameEvents"); + + if ( !pKV->LoadFromFile( filesystem, filename, pathID ) ) + { + // CGMsg( 1, CON_GROUP_VSCRIPT, "CScriptGameEventListener::LoadEventsFromFile: Failed to load file [%s]%s\n", pathID, filename ); + pKV->deleteThis(); + return; + } + + int count = 0; + + for ( KeyValues *key = pKV->GetFirstSubKey(); key; key = key->GetNextKey() ) + { + for ( KeyValues *sub = key->GetFirstSubKey(); sub; sub = sub->GetNextKey() ) + { + if ( sub->GetDataType() == KeyValues::TYPE_STRING ) + { + const char *szVal = sub->GetString(); + if ( !V_stricmp( szVal, "string" ) ) + { + sub->SetInt( NULL, TYPE_STRING ); + } + else if ( !V_stricmp( szVal, "bool" ) ) + { + sub->SetInt( NULL, TYPE_BOOL ); + } + else if ( !V_stricmp( szVal, "byte" ) ) + { + sub->SetInt( NULL, TYPE_BYTE ); + } + else if ( !V_stricmp( szVal, "short" ) ) + { + sub->SetInt( NULL, TYPE_SHORT ); + } + else if ( !V_stricmp( szVal, "long" ) ) + { + sub->SetInt( NULL, TYPE_LONG ); + } + else if ( !V_stricmp( szVal, "float" ) ) + { + sub->SetInt( NULL, TYPE_FLOAT ); + } + } + // none : value is not networked + // string : a zero terminated string + // bool : unsigned int, 1 bit + // byte : unsigned int, 8 bit + // short : signed int, 16 bit + // long : signed int, 32 bit + // float : float, 32 bit + } + + // Store event subkeys + // Replace key so modevents can overwrite gameevents. + // It does not check for hash collisions, however. + s_GameEvents.InsertOrReplace( Hash( key->GetName() ), key ); + ++count; + } + + // Store files (allocated KV) + s_LoadedFiles.AddToTail( pKV ); + + CGMsg( 2, CON_GROUP_VSCRIPT, "CScriptGameEventListener::LoadEventsFromFile: Loaded [%s]%s (%i)\n", pathID, filename, count ); +} +#endif + +#if _DEBUG +void CScriptGameEventListener::DumpEventListeners() +{ + CGMsg( 0, CON_GROUP_VSCRIPT, "--- Script game event listener dump start\n" ); + CGMsg( 0, CON_GROUP_VSCRIPT, "# ID CONTEXT\n" ); + FOR_EACH_VEC( s_Listeners, i ) + { + CGMsg( 0, CON_GROUP_VSCRIPT, " %d : %d : %u\n", i, + s_Listeners[i]->GetIndex(), + s_Listeners[i]->m_iContextHash ); + } + CGMsg( 0, CON_GROUP_VSCRIPT, "--- Script game event listener dump end\n" ); +} +#endif + +void CScriptGameEventListener::LevelShutdownPreEntity() +{ + s_Listeners.FindAndFastRemove(this); + delete this; +} + +void CScriptGameEventListener::FireGameEvent( IGameEvent *event ) +{ +#ifdef _DEBUG + m_nEventTick = gpGlobals->tickcount; +#endif + ScriptVariant_t hTable; + g_pScriptVM->CreateTable( hTable ); + WriteEventData( event, hTable ); + g_pScriptVM->SetValue( hTable, "game_event_listener", GetIndex() ); + // g_pScriptVM->SetValue( hTable, "game_event_name", event->GetName() ); + g_pScriptVM->ExecuteFunction( m_hCallback, &hTable, 1, NULL, NULL, true ); + g_pScriptVM->ReleaseScript( hTable ); +} + +struct CGameEventDescriptor +{ + byte _0[36]; + KeyValues *m_pEventKeys; + //byte _1[22]; +}; + +class CGameEvent__// : public IGameEvent +{ +public: + virtual ~CGameEvent__(); // [0] + CGameEventDescriptor *m_pDescriptor; // 0x04 + //KeyValues *m_pEventData; // 0x08 +}; + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CScriptGameEventListener::WriteEventData( IGameEvent *event, HSCRIPT hTable ) +{ +#ifdef USE_OLD_EVENT_DESCRIPTORS + int i = s_GameEvents.Find( Hash( event->GetName() ) ); + if ( i == s_GameEvents.InvalidIndex() ) + return; + KeyValues *pKV = s_GameEvents[i]; +#endif + +#if defined(_DEBUG) && !defined(USE_OLD_EVENT_DESCRIPTORS) + try + { +#endif + +#if !defined(USE_OLD_EVENT_DESCRIPTORS) + KeyValues *pKV = reinterpret_cast< CGameEvent__* >(event)->m_pDescriptor->m_pEventKeys; +#endif + + for ( KeyValues *sub = pKV->GetFirstSubKey(); sub; sub = sub->GetNextKey() ) + { + const char *szKey = sub->GetName(); + switch ( sub->GetInt() ) + { + case TYPE_LOCAL: + case TYPE_STRING: g_pScriptVM->SetValue( hTable, szKey, event->GetString( szKey ) ); break; + case TYPE_FLOAT: g_pScriptVM->SetValue( hTable, szKey, event->GetFloat ( szKey ) ); break; + case TYPE_BOOL: g_pScriptVM->SetValue( hTable, szKey, event->GetBool ( szKey ) ); break; + default: g_pScriptVM->SetValue( hTable, szKey, event->GetInt ( szKey ) ); + } + } + +#if defined(_DEBUG) && !defined(USE_OLD_EVENT_DESCRIPTORS) + // Access a bunch of KeyValues functions to validate it is the correct address. + // This may not always throw an exception when it is incorrect, but eventually it will. + } + catch (...) + { + // CGameEvent or CGameEventDescriptor offsets did not match! + // This should mean these were modified in engine.dll. + // + // Implement this utility yourself by adding a function to get event descriptor keys + // either on CGameEventManager or on CGameEvent interfaces. + // On CGameEventManager downcast IGameEvent input to CGameEvent, then return event->descriptor->keys + // On CGameEvent return (member) descriptor->keys + // + // Finally assign it to pKV above. + + Warning("CScriptGameEventListener::WriteEventData internal error\n"); + Assert(0); + } +#endif +} + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +int CScriptGameEventListener::ListenToGameEvent( const char* szEvent, HSCRIPT hFunc, const char* szContext ) +{ + bool bValid; + + if ( gameeventmanager && hFunc ) +#ifdef CLIENT_DLL + bValid = gameeventmanager->AddListener( this, szEvent, false ); +#else + bValid = gameeventmanager->AddListener( this, szEvent, true ); +#endif + else bValid = false; + + if ( bValid ) + { + m_iContextHash = HashContext( szContext ); + m_hCallback = hFunc; + m_bActive = true; + + s_Listeners.AddToTail( this ); + + return GetIndex(); + } + else + { + delete this; + return 0; + } +} + +//----------------------------------------------------------------------------- +// Free stuff. Called from the destructor, does not remove itself from the listener list. +//----------------------------------------------------------------------------- +void CScriptGameEventListener::StopListeningForEvent() +{ + if ( !m_bActive ) + return; + + if ( g_pScriptVM ) + g_pScriptVM->ReleaseScript( m_hCallback ); + + m_hCallback = NULL; + m_bActive = false; + + if ( gameeventmanager ) + gameeventmanager->RemoveListener( this ); + +#ifdef _DEBUG + // Event listeners are iterated forwards in the game event manager, + // removing while iterating will cause it to skip one listener. + // + // Fix this in engine without altering any behaviour by + // changing event exeuction order to tail->head, + // changing listener removal to tail->head, + // changing listener addition to head + if ( m_nEventTick == gpGlobals->tickcount ) + { + Warning("CScriptGameEventListener stopped in the same frame it was fired. This will break other event listeners!\n"); + } +#endif +} + +//----------------------------------------------------------------------------- +// Stop the specified event listener. +//----------------------------------------------------------------------------- +bool CScriptGameEventListener::StopListeningToGameEvent( int listener ) +{ + CScriptGameEventListener *p = reinterpret_cast(listener); // INT_TO_POINTER + + bool bRemoved = s_Listeners.FindAndFastRemove(p); + if ( bRemoved ) + { + delete p; + } + + return bRemoved; +} + +//----------------------------------------------------------------------------- +// Stops listening to all events within a context. +//----------------------------------------------------------------------------- +void CScriptGameEventListener::StopListeningToAllGameEvents( const char* szContext ) +{ + unsigned int hash = HashContext( szContext ); + for ( int i = s_Listeners.Count(); i--; ) + { + CScriptGameEventListener *pCur = s_Listeners[i]; + if ( pCur->m_iContextHash == hash ) + { + s_Listeners.FastRemove(i); + delete pCur; + } + } +} + +//============================================================================= +//============================================================================= + +static int ListenToGameEvent( const char* szEvent, HSCRIPT hFunc, const char* szContext ) +{ + CScriptGameEventListener *p = new CScriptGameEventListener(); + return p->ListenToGameEvent( szEvent, hFunc, szContext ); +} + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +static void FireGameEvent( const char* szEvent, HSCRIPT hTable ) +{ + IGameEvent *event = gameeventmanager->CreateEvent( szEvent ); + if ( event ) + { + ScriptVariant_t key, val; + int nIterator = -1; + while ( ( nIterator = g_pScriptVM->GetKeyValue( hTable, nIterator, &key, &val ) ) != -1 ) + { + switch ( val.GetType() ) + { + case FIELD_FLOAT: event->SetFloat ( key.Get(), val.Get() ); break; + case FIELD_INTEGER: event->SetInt ( key.Get(), val.Get() ); break; + case FIELD_BOOLEAN: event->SetBool ( key.Get(), val.Get() ); break; + case FIELD_CSTRING: event->SetString( key.Get(), val.Get() ); break; + } + + g_pScriptVM->ReleaseValue(key); + g_pScriptVM->ReleaseValue(val); + } + +#ifdef CLIENT_DLL + gameeventmanager->FireEventClientSide(event); +#else + gameeventmanager->FireEvent(event); +#endif + } +} + +#ifndef CLIENT_DLL +//----------------------------------------------------------------------------- +// Copy of FireGameEvent, server only with no broadcast to clients. +//----------------------------------------------------------------------------- +static void FireGameEventLocal( const char* szEvent, HSCRIPT hTable ) +{ + IGameEvent *event = gameeventmanager->CreateEvent( szEvent ); + if ( event ) + { + ScriptVariant_t key, val; + int nIterator = -1; + while ( ( nIterator = g_pScriptVM->GetKeyValue( hTable, nIterator, &key, &val ) ) != -1 ) + { + switch ( val.GetType() ) + { + case FIELD_FLOAT: event->SetFloat ( key.Get(), val.Get() ); break; + case FIELD_INTEGER: event->SetInt ( key.Get(), val.Get() ); break; + case FIELD_BOOLEAN: event->SetBool ( key.Get(), val.Get() ); break; + case FIELD_CSTRING: event->SetString( key.Get(), val.Get() ); break; + } + + g_pScriptVM->ReleaseValue(key); + g_pScriptVM->ReleaseValue(val); + } + + gameeventmanager->FireEvent(event,true); + } +} +#endif // !CLIENT_DLL + +static ScriptHook_t g_Hook_OnSave; +static ScriptHook_t g_Hook_OnRestore; + +//============================================================================= +// Save/Restore Utility +// Based on L4D2 API +//============================================================================= +class CScriptSaveRestoreUtil : public CAutoGameSystem +{ +public: + static void SaveTable( const char *szId, HSCRIPT hTable ); + static void RestoreTable( const char *szId, HSCRIPT hTable ); + static void ClearSavedTable( const char *szId ); + +public: // IGameSystem + + void OnSave() + { + if ( g_pScriptVM ) + { + if ( GetScriptHookManager().IsEventHooked( "OnSave" ) ) + g_Hook_OnSave.Call( NULL, NULL, NULL ); + + // Legacy hook + HSCRIPT hFunc = g_pScriptVM->LookupFunction( "OnSave" ); + if ( hFunc ) + { + g_pScriptVM->Call( hFunc ); + g_pScriptVM->ReleaseScript( hFunc ); + } + } + } + +#ifdef CLIENT_DLL + // On the client, OnRestore() is called before VScript is actually restored, so this has to be called manually from VScript save/restore instead + void OnVMRestore() +#else + void OnRestore() +#endif + { + if ( g_pScriptVM ) + { + if ( GetScriptHookManager().IsEventHooked( "OnRestore" ) ) + g_Hook_OnRestore.Call( NULL, NULL, NULL ); + + // Legacy hook + HSCRIPT hFunc = g_pScriptVM->LookupFunction( "OnRestore" ); + if ( hFunc ) + { + g_pScriptVM->Call( hFunc ); + g_pScriptVM->ReleaseScript( hFunc ); + } + } + } + + void Shutdown() + { + FOR_EACH_MAP_FAST( m_Lookup, i ) + m_Lookup[i]->deleteThis(); + m_Lookup.Purge(); + } + +private: + static StringHashFunctor Hash; + static CUtlMap< unsigned int, KeyValues* > m_Lookup; + +} g_ScriptSaveRestoreUtil; + +#ifdef CLIENT_DLL +void VScriptSaveRestoreUtil_OnVMRestore() +{ + g_ScriptSaveRestoreUtil.OnVMRestore(); +} +#endif + +CUtlMap< unsigned int, KeyValues* > CScriptSaveRestoreUtil::m_Lookup( DefLessFunc(unsigned int) ); +StringHashFunctor CScriptSaveRestoreUtil::Hash; + +//----------------------------------------------------------------------------- +// Store a table with primitive values that will persist across level transitions and save loads. +// Case sensitive +//----------------------------------------------------------------------------- +void CScriptSaveRestoreUtil::SaveTable( const char *szId, HSCRIPT hTable ) +{ + KeyValues *pKV; + unsigned int hash = Hash(szId); + + int idx = m_Lookup.Find( hash ); + if ( idx == m_Lookup.InvalidIndex() ) + { + pKV = new KeyValues("ScriptSavedTable"); + m_Lookup.Insert( hash, pKV ); + } + else + { + pKV = m_Lookup[idx]; + pKV->Clear(); + } + + ScriptVariant_t key, val; + int nIterator = -1; + while ( ( nIterator = g_pScriptVM->GetKeyValue( hTable, nIterator, &key, &val ) ) != -1 ) + { + switch ( val.GetType() ) + { + case FIELD_FLOAT: pKV->SetFloat ( key.Get(), val.Get() ); break; + case FIELD_INTEGER: pKV->SetInt ( key.Get(), val.Get() ); break; + case FIELD_BOOLEAN: pKV->SetBool ( key.Get(), val.Get() ); break; + case FIELD_CSTRING: pKV->SetString( key.Get(), val.Get() ); break; + } + + g_pScriptVM->ReleaseValue(key); + g_pScriptVM->ReleaseValue(val); + } +} + +//----------------------------------------------------------------------------- +// Retrieves a table from storage. Write into input table. +//----------------------------------------------------------------------------- +void CScriptSaveRestoreUtil::RestoreTable( const char *szId, HSCRIPT hTable ) +{ + int idx = m_Lookup.Find( Hash(szId) ); + if ( idx == m_Lookup.InvalidIndex() ) + { + // DevWarning( 2, "RestoreTable could not find saved table with context '%s'\n", szId ); + return; + } + + KeyValues *pKV = m_Lookup[idx]; + FOR_EACH_SUBKEY( pKV, key ) + { + switch ( key->GetDataType() ) + { + case KeyValues::TYPE_STRING: g_pScriptVM->SetValue( hTable, key->GetName(), key->GetString() ); break; + case KeyValues::TYPE_INT: g_pScriptVM->SetValue( hTable, key->GetName(), key->GetInt() ); break; + case KeyValues::TYPE_FLOAT: g_pScriptVM->SetValue( hTable, key->GetName(), key->GetFloat() ); break; + } + } +} + +//----------------------------------------------------------------------------- +// Remove a saved table. +//----------------------------------------------------------------------------- +void CScriptSaveRestoreUtil::ClearSavedTable( const char *szId ) +{ + int idx = m_Lookup.Find( Hash(szId) ); + if ( idx != m_Lookup.InvalidIndex() ) + { + m_Lookup[idx]->deleteThis(); + m_Lookup.RemoveAt( idx ); + } + else + { + // DevWarning( 2, "ClearSavedTable could not find saved table with context '%s'\n", szId ); + } +} + +//============================================================================= +// Read/Write to File +// Based on L4D2/Source 2 API +//============================================================================= +#define SCRIPT_MAX_FILE_READ_SIZE (64 * 1024 * 1024) // 64MB +#define SCRIPT_MAX_FILE_WRITE_SIZE (64 * 1024 * 1024) // 64MB +#define SCRIPT_RW_PATH_ID "MOD" +#define SCRIPT_RW_FULL_PATH_FMT "vscript_io/%s" + +class CScriptReadWriteFile : public CAutoGameSystem +{ + // A singleton class with all static members is used to be able to free the read string on level shutdown, + // and register script funcs directly. Same reason applies to CScriptSaveRestoreUtil +public: + static bool FileWrite( const char *szFile, const char *szInput ); + static const char *FileRead( const char *szFile ); + static bool FileExists( const char *szFile ); + + // NOTE: These two functions are new with Mapbase and have no Valve equivalent + static bool KeyValuesWrite( const char *szFile, HSCRIPT hInput ); + static HSCRIPT KeyValuesRead( const char *szFile ); + + void LevelShutdownPostEntity() + { + if ( m_pszReturnReadFile ) + { + delete[] m_pszReturnReadFile; + m_pszReturnReadFile = NULL; + } + } + +private: + static char *m_pszReturnReadFile; + +} g_ScriptReadWrite; + +char *CScriptReadWriteFile::m_pszReturnReadFile = NULL; + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +bool CScriptReadWriteFile::FileWrite( const char *szFile, const char *szInput ) +{ + size_t len = strlen(szInput); + if ( len > SCRIPT_MAX_FILE_WRITE_SIZE ) + { + DevWarning( 2, "Input is too large for a ScriptFileWrite ( %s / %d MB )\n", V_pretifymem(len,2,true), (SCRIPT_MAX_FILE_WRITE_SIZE >> 20) ); + return false; + } + + char pszFullName[MAX_PATH]; + V_snprintf( pszFullName, sizeof(pszFullName), SCRIPT_RW_FULL_PATH_FMT, szFile ); + + if ( !V_RemoveDotSlashes( pszFullName, CORRECT_PATH_SEPARATOR, true ) ) + { + DevWarning( 2, "Invalid file location : %s\n", szFile ); + return false; + } + + CUtlBuffer buf( 0, 0, CUtlBuffer::TEXT_BUFFER ); + buf.PutString(szInput); + + int nSize = V_strlen(pszFullName) + 1; + char *pszDir = (char*)stackalloc(nSize); + V_memcpy( pszDir, pszFullName, nSize ); + V_StripFilename( pszDir ); + + g_pFullFileSystem->CreateDirHierarchy( pszDir, SCRIPT_RW_PATH_ID ); + bool res = g_pFullFileSystem->WriteFile( pszFullName, SCRIPT_RW_PATH_ID, buf ); + buf.Purge(); + return res; +} + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +const char *CScriptReadWriteFile::FileRead( const char *szFile ) +{ + char pszFullName[MAX_PATH]; + V_snprintf( pszFullName, sizeof(pszFullName), SCRIPT_RW_FULL_PATH_FMT, szFile ); + + if ( !CommandLine()->FindParm( "-script_dotslash_read" ) && !V_RemoveDotSlashes( pszFullName, CORRECT_PATH_SEPARATOR, true ) ) + { + DevWarning( 2, "Invalid file location : %s\n", szFile ); + return NULL; + } + + unsigned int size = g_pFullFileSystem->Size( pszFullName, SCRIPT_RW_PATH_ID ); + if ( size >= SCRIPT_MAX_FILE_READ_SIZE ) + { + DevWarning( 2, "File '%s' (from '%s') is too large for a ScriptFileRead ( %s / %u bytes )\n", pszFullName, szFile, V_pretifymem(size,2,true), SCRIPT_MAX_FILE_READ_SIZE ); + return NULL; + } + + FileHandle_t file = g_pFullFileSystem->Open( pszFullName, "rb", SCRIPT_RW_PATH_ID ); + if ( !file ) + { + return NULL; + } + + // Close the previous buffer + if (m_pszReturnReadFile) + g_pFullFileSystem->FreeOptimalReadBuffer( m_pszReturnReadFile ); + + unsigned bufSize = g_pFullFileSystem->GetOptimalReadSize( file, size + 2 ); + m_pszReturnReadFile = (char*)g_pFullFileSystem->AllocOptimalReadBuffer( file, bufSize ); + + bool bRetOK = ( g_pFullFileSystem->ReadEx( m_pszReturnReadFile, bufSize, size, file ) != 0 ); + g_pFullFileSystem->Close( file ); // close file after reading + + if ( bRetOK ) + { + m_pszReturnReadFile[size] = 0; // null terminate file as EOF + //buffer[size+1] = 0; // double NULL terminating in case this is a unicode file + return m_pszReturnReadFile; + } + else + { + return NULL; + } +} + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +bool CScriptReadWriteFile::FileExists( const char *szFile ) +{ + char pszFullName[MAX_PATH]; + V_snprintf( pszFullName, sizeof(pszFullName), SCRIPT_RW_FULL_PATH_FMT, szFile ); + + if ( !CommandLine()->FindParm( "-script_dotslash_read" ) && !V_RemoveDotSlashes( pszFullName, CORRECT_PATH_SEPARATOR, true ) ) + { + DevWarning( 2, "Invalid file location : %s\n", szFile ); + return NULL; + } + + return g_pFullFileSystem->FileExists( pszFullName, SCRIPT_RW_PATH_ID ); +} + +//----------------------------------------------------------------------------- +// Get the checksum of any file. Can be used to check the existence or validity of a file. +// Returns unsigned int as hex string. +//----------------------------------------------------------------------------- +/* +const char *CScriptReadWriteFile::CRC32_Checksum( const char *szFilename ) +{ + CUtlBuffer buf( 0, 0, CUtlBuffer::READ_ONLY ); + if ( !g_pFullFileSystem->ReadFile( szFilename, NULL, buf ) ) + return NULL; + + // first time calling, allocate + if ( !m_pszReturnCRC32 ) + m_pszReturnCRC32 = new char[9]; // 'FFFFFFFF\0' + + V_snprintf( const_cast(m_pszReturnCRC32), 9, "%X", CRC32_ProcessSingleBuffer( buf.Base(), buf.Size()-1 ) ); + buf.Purge(); + + return m_pszReturnCRC32; +} +*/ + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +bool CScriptReadWriteFile::KeyValuesWrite( const char *szFile, HSCRIPT hInput ) +{ + KeyValues *pKV = scriptmanager->GetKeyValuesFromScriptKV( g_pScriptVM, hInput ); + if (!pKV) + { + return false; + } + + CUtlBuffer buf( 0, 0, CUtlBuffer::TEXT_BUFFER ); + pKV->RecursiveSaveToFile( buf, 0 ); + + if ( buf.Size() > SCRIPT_MAX_FILE_WRITE_SIZE ) + { + DevWarning( 2, "Input is too large for a ScriptKeyValuesWrite ( %s / %d MB )\n", V_pretifymem(buf.Size(),2,true), (SCRIPT_MAX_FILE_WRITE_SIZE >> 20) ); + buf.Purge(); + return false; + } + + char pszFullName[MAX_PATH]; + V_snprintf( pszFullName, sizeof(pszFullName), SCRIPT_RW_FULL_PATH_FMT, szFile ); + + if ( !V_RemoveDotSlashes( pszFullName, CORRECT_PATH_SEPARATOR, true ) ) + { + DevWarning( 2, "Invalid file location : %s\n", szFile ); + buf.Purge(); + return false; + } + + int nSize = V_strlen(pszFullName) + 1; + char *pszDir = (char*)stackalloc(nSize); + V_memcpy( pszDir, pszFullName, nSize ); + V_StripFilename( pszDir ); + + g_pFullFileSystem->CreateDirHierarchy( pszDir, SCRIPT_RW_PATH_ID ); + bool res = g_pFullFileSystem->WriteFile( pszFullName, SCRIPT_RW_PATH_ID, buf ); + buf.Purge(); + return res; +} + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +HSCRIPT CScriptReadWriteFile::KeyValuesRead( const char *szFile ) +{ + char pszFullName[MAX_PATH]; + V_snprintf( pszFullName, sizeof(pszFullName), SCRIPT_RW_FULL_PATH_FMT, szFile ); + + if ( !CommandLine()->FindParm( "-script_dotslash_read" ) && !V_RemoveDotSlashes( pszFullName, CORRECT_PATH_SEPARATOR, true ) ) + { + DevWarning( 2, "Invalid file location : %s\n", szFile ); + return NULL; + } + + unsigned int size = g_pFullFileSystem->Size( pszFullName, SCRIPT_RW_PATH_ID ); + if ( size >= SCRIPT_MAX_FILE_READ_SIZE ) + { + DevWarning( 2, "File '%s' (from '%s') is too large for a ScriptKeyValuesRead ( %s / %u bytes )\n", pszFullName, szFile, V_pretifymem(size,2,true), SCRIPT_MAX_FILE_READ_SIZE ); + return NULL; + } + + KeyValues *pKV = new KeyValues( szFile ); + if ( !pKV->LoadFromFile( g_pFullFileSystem, pszFullName, SCRIPT_RW_PATH_ID ) ) + { + pKV->deleteThis(); + return NULL; + } + + HSCRIPT hScript = scriptmanager->CreateScriptKeyValues( g_pScriptVM, pKV, true ); // bAllowDestruct is supposed to automatically remove the involved KV + + return hScript; +} + + +//============================================================================= +// Network message helper +// (Unique to mapbase) +// +// Uses usermessages for server to client, UserCmd for client to server communication. +// The custom message name is hashed and sent as word with the message. +//============================================================================= + +static CNetMsgScriptHelper scriptnetmsg; +CNetMsgScriptHelper *g_ScriptNetMsg = &scriptnetmsg; + +#ifdef GAME_DLL +#define m_MsgIn_() m_MsgIn-> +#define DLL_LOC_STR "[Server]" +#else +#define m_MsgIn_() m_MsgIn. +#define DLL_LOC_STR "[Client]" +#endif + +#ifdef GAME_DLL +#define SCRIPT_NETMSG_WRITE_FUNC +#else +#define SCRIPT_NETMSG_WRITE_FUNC if ( m_bWriteIgnore ) { return; } +#endif + +#ifdef _DEBUG +#ifdef GAME_DLL +ConVar script_net_debug("script_net_debug", "0"); +#define DebugNetMsg( l, ... ) do { if (script_net_debug.GetInt() >= l) ConColorMsg( Color(100, 225, 255, 255), __VA_ARGS__ ); } while (0); +#else +ConVar script_net_debug("script_net_debug_client", "0"); +#define DebugNetMsg( l, ... ) do { if (script_net_debug.GetInt() >= l) ConColorMsg( Color(100, 225, 175, 255), __VA_ARGS__ ); } while (0); +#endif +#define DebugWarning(...) Warning( __VA_ARGS__ ) +#else +#define DebugNetMsg(...) (void)(0) +#define DebugWarning(...) (void)(0) +#endif + + +// Keep track of message names to print on failure +#ifdef _DEBUG +struct NetMsgHook_t +{ + void Set( const char *s ) + { + hash = CNetMsgScriptHelper::Hash( s ); + name = strdup(s); + } + + ~NetMsgHook_t() + { + free( name ); + } + + int hash; + char *name; +}; + +CUtlVector< NetMsgHook_t > g_NetMsgHooks; + +static const char *GetNetMsgName( int hash ) +{ + FOR_EACH_VEC( g_NetMsgHooks, i ) + { + if ( g_NetMsgHooks[i].hash == hash ) + return g_NetMsgHooks[i].name; + } + return 0; +} + +static const char *HasNetMsgCollision( int hash, const char *ignore ) +{ + FOR_EACH_VEC( g_NetMsgHooks, i ) + { + if ( g_NetMsgHooks[i].hash == hash && V_strcmp( g_NetMsgHooks[i].name, ignore ) != 0 ) + { + return g_NetMsgHooks[i].name; + } + } + return 0; +} +#endif // _DEBUG + + + +inline int CNetMsgScriptHelper::Hash( const char *key ) +{ + int hash = CaselessStringHashFunctor()( key ); + return hash; +} + +void CNetMsgScriptHelper::WriteToBuffer( bf_write *bf ) +{ +#ifdef CLIENT_DLL + Assert( m_nQueueCount < ( 1 << SCRIPT_NETMSG_QUEUE_BITS ) ); + bf->WriteUBitLong( m_nQueueCount, SCRIPT_NETMSG_QUEUE_BITS ); + + DebugNetMsg( 2, DLL_LOC_STR " CNetMsgScriptHelper::WriteToBuffer() count(%d) size(%d)\n", + m_nQueueCount, m_MsgOut.GetNumBitsWritten() + SCRIPT_NETMSG_QUEUE_BITS ); +#endif + + bf->WriteBits( m_MsgOut.GetData(), m_MsgOut.GetNumBitsWritten() ); +} + +//----------------------------------------------------------------------------- +// Reset the current network message buffer +//----------------------------------------------------------------------------- +void CNetMsgScriptHelper::Reset() +{ + m_MsgOut.StartWriting( m_MsgData, sizeof(m_MsgData), 0 ); +#ifdef GAME_DLL + m_filter.Reset(); +#else + m_iLastBit = 0; +#endif +} + +//----------------------------------------------------------------------------- +// Create the storage for the receiver callback functions. +// Functions are handled in the VM, the storage table is here. +//----------------------------------------------------------------------------- +void CNetMsgScriptHelper::InitPostVM() +{ + ScriptVariant_t hHooks; + g_pScriptVM->CreateTable( hHooks ); + m_Hooks = (HSCRIPT)hHooks; +} + +void CNetMsgScriptHelper::LevelShutdownPreVM() +{ + Reset(); + if ( m_Hooks ) + g_pScriptVM->ReleaseScript( m_Hooks ); + m_Hooks = NULL; + +#ifdef CLIENT_DLL + m_bWriteReady = m_bWriteIgnore = false; + m_MsgIn.Reset(); +#else + m_MsgIn = NULL; +#endif + +#ifdef _DEBUG + g_NetMsgHooks.Purge(); +#endif +} + +#ifdef CLIENT_DLL + +bool CNetMsgScriptHelper::Init() // IGameSystem +{ + usermessages->HookMessage( "ScriptMsg", __MsgFunc_ScriptMsg ); + return true; +} + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CNetMsgScriptHelper::__MsgFunc_ScriptMsg( bf_read &msg ) +{ + g_ScriptNetMsg->ReceiveMessage( msg ); +} + +#endif // CLIENT_DLL + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +#ifdef GAME_DLL +void CNetMsgScriptHelper::ReceiveMessage( bf_read *msg, CBaseEntity *pPlayer ) +{ + m_MsgIn = msg; +#else +void CNetMsgScriptHelper::ReceiveMessage( bf_read &msg ) +{ + m_MsgIn.StartReading( msg.m_pData, msg.m_nDataBytes ); +#endif + + DebugNetMsg( 2, DLL_LOC_STR " %s()\n", __FUNCTION__ ); + + // Don't do anything if there's no VM here. This can happen if a message from the server goes to a VM-less client, or vice versa. + if ( !g_pScriptVM ) + { + CGWarning( 0, CON_GROUP_VSCRIPT, DLL_LOC_STR " CNetMsgScriptHelper: No VM on receiving side\n" ); + return; + } + +#ifdef GAME_DLL + int count = m_MsgIn_()ReadUBitLong( SCRIPT_NETMSG_QUEUE_BITS ); + DebugNetMsg( 2, " msg count %d\n", count ); + while ( count-- ) +#endif + { + int hash = m_MsgIn_()ReadUBitLong( SCRIPT_NETMSG_HEADER_BITS ); + +#ifdef _DEBUG + const char *msgName = GetNetMsgName( hash ); + DebugNetMsg( 2, " -- begin msg [%d]%s\n", hash, msgName ); +#endif + + ScriptVariant_t hfn; + if ( g_pScriptVM->GetValue( m_Hooks, hash, &hfn ) ) + { +#ifdef GAME_DLL + if ( g_pScriptVM->Call( hfn, NULL, true, NULL, pPlayer->m_hScriptInstance ) == SCRIPT_ERROR ) +#else + if ( g_pScriptVM->ExecuteFunction( hfn, NULL, 0, NULL, NULL, true ) == SCRIPT_ERROR ) +#endif + { +#ifdef _DEBUG + DevWarning( 1, DLL_LOC_STR " NetMsg: invalid callback '%s'\n", GetNetMsgName( hash ) ); +#else + DevWarning( 1, DLL_LOC_STR " NetMsg: invalid callback [%d]\n", hash ); +#endif + } + g_pScriptVM->ReleaseValue( hfn ); + } + else + { + DevWarning( 1, DLL_LOC_STR " NetMsg hook not found [%d]\n", hash ); + } + + DebugNetMsg( 2, " -- end msg\n" ); + } +} + +//----------------------------------------------------------------------------- +// Start writing new custom network message +//----------------------------------------------------------------------------- +void CNetMsgScriptHelper::Start( const char *msg ) +{ + if ( !msg || !msg[0] ) + { + g_pScriptVM->RaiseException( DLL_LOC_STR "NetMsg: invalid message name" ); + return; + } + + DebugNetMsg( 1, DLL_LOC_STR " %s() [%d]%s\n", __FUNCTION__, Hash( msg ), msg ); + +#ifdef CLIENT_DLL + // Client can write multiple messages in a frame before the usercmd is sent, + // this queue system ensures client messages are written to the cmd all at once. + // NOTE: All messages share the same buffer. + if ( !m_bWriteReady ) + { + Reset(); + m_nQueueCount = 0; + m_bWriteIgnore = false; + } + else if ( m_nQueueCount == ((1< client +// +// Sends an exclusive usermessage. +//----------------------------------------------------------------------------- +void CNetMsgScriptHelper::Send( HSCRIPT player, bool bReliable ) +{ + DebugNetMsg( 1, DLL_LOC_STR " %s() size(%d)\n", __FUNCTION__, GetNumBitsWritten() ); + + CBaseEntity *pPlayer = ToEnt(player); + if ( pPlayer ) + { + m_filter.AddRecipient( (CBasePlayer*)pPlayer ); + } + + if ( bReliable ) + { + m_filter.MakeReliable(); + } + + Assert( usermessages->LookupUserMessage( "ScriptMsg" ) != -1 ); + + DoSendUserMsg( &m_filter, usermessages->LookupUserMessage( "ScriptMsg" ) ); +} +#else // CLIENT_DLL +//----------------------------------------------------------------------------- +// client -> server +// +// Mark UserCmd delta ready. +//----------------------------------------------------------------------------- +void CNetMsgScriptHelper::Send() +{ + DebugNetMsg( 1, DLL_LOC_STR " %s() size(%d)\n", __FUNCTION__, m_bWriteIgnore ? 0 : GetNumBitsWritten() ); + + m_bWriteReady = true; +} +#endif + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CNetMsgScriptHelper::Receive( const char *msg, HSCRIPT func ) +{ + if ( !msg || !msg[0] ) + { + g_pScriptVM->RaiseException( DLL_LOC_STR "NetMsg: invalid message name" ); + return; + } + +#ifdef _DEBUG + int hash = Hash( msg ); + + const char *psz = HasNetMsgCollision( hash, msg ); + AssertMsg3( !psz, DLL_LOC_STR " NetMsg hash collision! [%d] '%s', '%s'\n", hash, msg, psz ); + + NetMsgHook_t &hook = g_NetMsgHooks[ g_NetMsgHooks.AddToTail() ]; + hook.Set( msg ); +#endif + + if ( func ) + { + g_pScriptVM->SetValue( m_Hooks, Hash( msg ), func ); + } + else + { + g_pScriptVM->ClearValue( m_Hooks, Hash( msg ) ); + } +} + +#ifdef GAME_DLL +void CNetMsgScriptHelper::DoSendUserMsg( CRecipientFilter *filter, int type ) +{ + WriteToBuffer( engine->UserMessageBegin( filter, type ) ); + engine->MessageEnd(); +} + +void CNetMsgScriptHelper::DoSendEntityMsg( CBaseEntity *entity, bool reliable ) +{ + WriteToBuffer( engine->EntityMessageBegin( entity->entindex(), entity->GetServerClass(), reliable ) ); + engine->MessageEnd(); +} + +//----------------------------------------------------------------------------- +// Send a usermessage from the server to the client +//----------------------------------------------------------------------------- +void CNetMsgScriptHelper::SendUserMessage( HSCRIPT hPlayer, const char *msg, bool bReliable ) +{ + int msg_type = usermessages->LookupUserMessage(msg); + if ( msg_type == -1 ) + { + g_pScriptVM->RaiseException( UTIL_VarArgs("SendUserMessage: Unregistered message '%s'", msg) ); + return; + } + + CBaseEntity *pPlayer = ToEnt(hPlayer); + if ( pPlayer ) + { + m_filter.AddRecipient( (CBasePlayer*)pPlayer ); + } + + if ( bReliable ) + { + m_filter.MakeReliable(); + } + + DoSendUserMsg( &m_filter, msg_type ); +} + +//----------------------------------------------------------------------------- +// Send a message from a server side entity to its client side counterpart +//----------------------------------------------------------------------------- +void CNetMsgScriptHelper::SendEntityMessage( HSCRIPT hEnt, bool bReliable ) +{ + CBaseEntity *entity = ToEnt(hEnt); + if ( !entity ) + { + g_pScriptVM->RaiseException("SendEntityMessage: invalid entity"); + return; + } + + DoSendEntityMsg( entity, bReliable ); +} +#else +//----------------------------------------------------------------------------- +// Dispatch a usermessage on client +//----------------------------------------------------------------------------- +void CNetMsgScriptHelper::DispatchUserMessage( const char *msg ) +{ + bf_read buffer( m_MsgOut.GetData(), m_MsgOut.GetNumBytesWritten() ); + usermessages->DispatchUserMessage( usermessages->LookupUserMessage(msg), buffer ); +} +#endif // GAME_DLL + +void CNetMsgScriptHelper::WriteInt( int iValue, int bits ) +{ + SCRIPT_NETMSG_WRITE_FUNC + m_MsgOut.WriteSBitLong( iValue, bits ); +} + +void CNetMsgScriptHelper::WriteUInt( int iValue, int bits ) +{ + SCRIPT_NETMSG_WRITE_FUNC + m_MsgOut.WriteUBitLong( iValue, bits ); +} + +void CNetMsgScriptHelper::WriteByte( int iValue ) +{ + SCRIPT_NETMSG_WRITE_FUNC + m_MsgOut.WriteByte( iValue ); +} + +void CNetMsgScriptHelper::WriteChar( int iValue ) +{ + SCRIPT_NETMSG_WRITE_FUNC + m_MsgOut.WriteChar( iValue ); +} + +void CNetMsgScriptHelper::WriteShort( int iValue ) +{ + SCRIPT_NETMSG_WRITE_FUNC + m_MsgOut.WriteShort( iValue ); +} + +void CNetMsgScriptHelper::WriteWord( int iValue ) +{ + SCRIPT_NETMSG_WRITE_FUNC + m_MsgOut.WriteWord( iValue ); +} + +void CNetMsgScriptHelper::WriteLong( int iValue ) +{ + SCRIPT_NETMSG_WRITE_FUNC + m_MsgOut.WriteLong( iValue ); +} + +void CNetMsgScriptHelper::WriteFloat( float flValue ) +{ + SCRIPT_NETMSG_WRITE_FUNC + m_MsgOut.WriteFloat( flValue ); +} + +void CNetMsgScriptHelper::WriteNormal( float flValue ) +{ + SCRIPT_NETMSG_WRITE_FUNC + m_MsgOut.WriteBitNormal( flValue ); +} + +void CNetMsgScriptHelper::WriteAngle( float flValue ) +{ + SCRIPT_NETMSG_WRITE_FUNC + m_MsgOut.WriteBitAngle( flValue, 8 ); +} + +void CNetMsgScriptHelper::WriteCoord( float flValue ) +{ + SCRIPT_NETMSG_WRITE_FUNC + m_MsgOut.WriteBitCoord( flValue ); +} + +void CNetMsgScriptHelper::WriteVec3Coord( const Vector& rgflValue ) +{ + SCRIPT_NETMSG_WRITE_FUNC + m_MsgOut.WriteBitVec3Coord( rgflValue ); +} + +void CNetMsgScriptHelper::WriteVec3Normal( const Vector& rgflValue ) +{ + SCRIPT_NETMSG_WRITE_FUNC + m_MsgOut.WriteBitVec3Normal( rgflValue ); +} + +void CNetMsgScriptHelper::WriteAngles( const QAngle& rgflValue ) +{ + SCRIPT_NETMSG_WRITE_FUNC + m_MsgOut.WriteBitAngles( rgflValue ); +} + +void CNetMsgScriptHelper::WriteString( const char *sz ) +{ + SCRIPT_NETMSG_WRITE_FUNC + + // Larger strings can be written but cannot be read + Assert( V_strlen(sz) < SCRIPT_NETMSG_STRING_SIZE ); + + m_MsgOut.WriteString( sz ); +} + +void CNetMsgScriptHelper::WriteBool( bool bValue ) +{ + SCRIPT_NETMSG_WRITE_FUNC + m_MsgOut.WriteOneBit( bValue ? 1 : 0 ); +} + +void CNetMsgScriptHelper::WriteEntity( HSCRIPT hEnt ) +{ + SCRIPT_NETMSG_WRITE_FUNC + CBaseEntity *p = ToEnt(hEnt); + int i = p ? p->entindex() : 0; + m_MsgOut.WriteUBitLong( i, MAX_EDICT_BITS ); +} + +void CNetMsgScriptHelper::WriteEHandle( HSCRIPT hEnt ) +{ + SCRIPT_NETMSG_WRITE_FUNC + CBaseEntity *pEnt = ToEnt( hEnt ); + long iEncodedEHandle; + if ( pEnt ) + { + EHANDLE hEnt = pEnt; + int iSerialNum = hEnt.GetSerialNumber() & (1 << NUM_NETWORKED_EHANDLE_SERIAL_NUMBER_BITS) - 1; + iEncodedEHandle = hEnt.GetEntryIndex() | (iSerialNum << MAX_EDICT_BITS); + } + else + { + iEncodedEHandle = INVALID_NETWORKED_EHANDLE_VALUE; + } + m_MsgOut.WriteLong( iEncodedEHandle ); +} + +int CNetMsgScriptHelper::ReadInt( int bits ) +{ + return m_MsgIn_()ReadSBitLong(bits); +} + +int CNetMsgScriptHelper::ReadUInt( int bits ) +{ + return m_MsgIn_()ReadUBitLong(bits); +} + +int CNetMsgScriptHelper::ReadByte() +{ + return m_MsgIn_()ReadByte(); +} + +int CNetMsgScriptHelper::ReadChar() +{ + return m_MsgIn_()ReadChar(); +} + +int CNetMsgScriptHelper::ReadShort() +{ + return m_MsgIn_()ReadShort(); +} + +int CNetMsgScriptHelper::ReadWord() +{ + return m_MsgIn_()ReadWord(); +} + +int CNetMsgScriptHelper::ReadLong() +{ + return m_MsgIn_()ReadLong(); +} + +float CNetMsgScriptHelper::ReadFloat() +{ + return m_MsgIn_()ReadFloat(); +} + +float CNetMsgScriptHelper::ReadNormal() +{ + return m_MsgIn_()ReadBitNormal(); +} + +float CNetMsgScriptHelper::ReadAngle() +{ + return m_MsgIn_()ReadBitAngle( 8 ); +} + +float CNetMsgScriptHelper::ReadCoord() +{ + return m_MsgIn_()ReadBitCoord(); +} + +const Vector& CNetMsgScriptHelper::ReadVec3Coord() +{ + static Vector vec3; + m_MsgIn_()ReadBitVec3Coord(vec3); + return vec3; +} + +const Vector& CNetMsgScriptHelper::ReadVec3Normal() +{ + static Vector vec3; + m_MsgIn_()ReadBitVec3Normal(vec3); + return vec3; +} + +const QAngle& CNetMsgScriptHelper::ReadAngles() +{ + static QAngle vec3; + m_MsgIn_()ReadBitAngles(vec3); + return vec3; +} + +const char* CNetMsgScriptHelper::ReadString() +{ + static char buf[ SCRIPT_NETMSG_STRING_SIZE ]; + m_MsgIn_()ReadString( buf, sizeof(buf) ); + return buf; +} + +bool CNetMsgScriptHelper::ReadBool() +{ + return m_MsgIn_()ReadOneBit(); +} + +HSCRIPT CNetMsgScriptHelper::ReadEntity() +{ + int index = m_MsgIn_()ReadUBitLong( MAX_EDICT_BITS ); + + if ( !index ) + return NULL; + +#ifdef GAME_DLL + edict_t *e = INDEXENT(index); + if ( e && !e->IsFree() ) + { + return ToHScript( GetContainingEntity(e) ); + } +#else // CLIENT_DLL + if ( index < NUM_ENT_ENTRIES ) + { + return ToHScript( CBaseEntity::Instance(index) ); + } +#endif + return NULL; +} + +HSCRIPT CNetMsgScriptHelper::ReadEHandle() +{ + int iEncodedEHandle = m_MsgIn_()ReadLong(); + if ( iEncodedEHandle == INVALID_NETWORKED_EHANDLE_VALUE ) + return NULL; + int iEntry = iEncodedEHandle & ( (1 << MAX_EDICT_BITS) - 1 ); + int iSerialNum = iEncodedEHandle >> MAX_EDICT_BITS; + return ToHScript( EHANDLE( iEntry, iSerialNum ) ); +} + +inline int CNetMsgScriptHelper::GetNumBitsWritten() +{ +#ifdef GAME_DLL + return m_MsgOut.GetNumBitsWritten() - SCRIPT_NETMSG_HEADER_BITS; +#else + return m_MsgOut.m_iCurBit - m_iLastBit - SCRIPT_NETMSG_HEADER_BITS; +#endif +} + + +BEGIN_SCRIPTDESC_ROOT_NAMED( CNetMsgScriptHelper, "CNetMsg", SCRIPT_SINGLETON "Network messages" ) + +#ifdef GAME_DLL + DEFINE_SCRIPTFUNC( SendUserMessage, "Send a usermessage from the server to the client" ) + DEFINE_SCRIPTFUNC( SendEntityMessage, "Send a message from a server side entity to its client side counterpart" ) + + // TODO: multiplayer +#else + DEFINE_SCRIPTFUNC( DispatchUserMessage, "Dispatch a usermessage on client" ) +#endif + + DEFINE_SCRIPTFUNC( Reset, "Reset the current network message buffer" ) + DEFINE_SCRIPTFUNC( Start, "Start writing new custom network message" ) + DEFINE_SCRIPTFUNC( Receive, "Set custom network message callback" ) + DEFINE_SCRIPTFUNC_NAMED( Receive, "Recieve", SCRIPT_HIDE ) // This was a typo until v6.3 +#ifdef GAME_DLL + DEFINE_SCRIPTFUNC( Send, "Send a custom network message from the server to the client (max 251 bytes)" ) +#else + DEFINE_SCRIPTFUNC( Send, "Send a custom network message from the client to the server (max 2044 bytes)" ) +#endif + + DEFINE_SCRIPTFUNC( WriteInt, "variable bit signed int" ) + DEFINE_SCRIPTFUNC( WriteUInt, "variable bit unsigned int" ) + DEFINE_SCRIPTFUNC( WriteByte, "8 bit unsigned char" ) + DEFINE_SCRIPTFUNC( WriteChar, "8 bit char" ) + DEFINE_SCRIPTFUNC( WriteShort, "16 bit short" ) + DEFINE_SCRIPTFUNC( WriteWord, "16 bit unsigned short" ) + DEFINE_SCRIPTFUNC( WriteLong, "32 bit long" ) + DEFINE_SCRIPTFUNC( WriteFloat, "32 bit float" ) + DEFINE_SCRIPTFUNC( WriteNormal, "12 bit" ) + DEFINE_SCRIPTFUNC( WriteAngle, "8 bit unsigned char" ) + DEFINE_SCRIPTFUNC( WriteCoord, "" ) + DEFINE_SCRIPTFUNC( WriteVec3Coord, "" ) + DEFINE_SCRIPTFUNC( WriteVec3Normal, "27 bit" ) + DEFINE_SCRIPTFUNC( WriteAngles, "" ) + DEFINE_SCRIPTFUNC( WriteString, "max 512 bytes at once" ) + DEFINE_SCRIPTFUNC( WriteBool, "1 bit" ) + DEFINE_SCRIPTFUNC( WriteEntity, "11 bit (entindex)" ) + DEFINE_SCRIPTFUNC( WriteEHandle, "32 bit long" ) + + DEFINE_SCRIPTFUNC( ReadInt, "" ) + DEFINE_SCRIPTFUNC( ReadUInt, "" ) + DEFINE_SCRIPTFUNC( ReadByte, "" ) + DEFINE_SCRIPTFUNC( ReadChar, "" ) + DEFINE_SCRIPTFUNC( ReadShort, "" ) + DEFINE_SCRIPTFUNC( ReadWord, "" ) + DEFINE_SCRIPTFUNC( ReadLong, "" ) + DEFINE_SCRIPTFUNC( ReadFloat, "" ) + DEFINE_SCRIPTFUNC( ReadNormal, "" ) + DEFINE_SCRIPTFUNC( ReadAngle, "" ) + DEFINE_SCRIPTFUNC( ReadCoord, "" ) + DEFINE_SCRIPTFUNC( ReadVec3Coord, "" ) + DEFINE_SCRIPTFUNC( ReadVec3Normal, "" ) + DEFINE_SCRIPTFUNC( ReadAngles, "" ) + DEFINE_SCRIPTFUNC( ReadString, "" ) + DEFINE_SCRIPTFUNC( ReadBool, "" ) + DEFINE_SCRIPTFUNC( ReadEntity, "" ) + DEFINE_SCRIPTFUNC( ReadEHandle, "" ) + + DEFINE_SCRIPTFUNC( GetNumBitsWritten, "" ) + +END_SCRIPTDESC(); + + + +#define RETURN_IF_CANNOT_DRAW_OVERLAY\ + if (engine->IsPaused())\ + return; +class CDebugOverlayScriptHelper +{ +public: + + void Box( const Vector &origin, const Vector &mins, const Vector &maxs, int r, int g, int b, int a, float flDuration ) + { + RETURN_IF_CANNOT_DRAW_OVERLAY + + debugoverlay->AddBoxOverlay(origin, mins, maxs, vec3_angle, r, g, b, a, flDuration); + } + void BoxDirection( const Vector &origin, const Vector &mins, const Vector &maxs, const Vector &forward, int r, int g, int b, int a, float flDuration ) + { + RETURN_IF_CANNOT_DRAW_OVERLAY + + QAngle f_angles = vec3_angle; + f_angles.y = UTIL_VecToYaw(forward); + + debugoverlay->AddBoxOverlay(origin, mins, maxs, f_angles, r, g, b, a, flDuration); + } + void BoxAngles( const Vector &origin, const Vector &mins, const Vector &maxs, const QAngle &angles, int r, int g, int b, int a, float flDuration ) + { + RETURN_IF_CANNOT_DRAW_OVERLAY + + debugoverlay->AddBoxOverlay(origin, mins, maxs, angles, r, g, b, a, flDuration); + } + void SweptBox( const Vector& start, const Vector& end, const Vector& mins, const Vector& maxs, const QAngle & angles, int r, int g, int b, int a, float flDuration ) + { + RETURN_IF_CANNOT_DRAW_OVERLAY + + debugoverlay->AddSweptBoxOverlay(start, end, mins, maxs, angles, r, g, b, a, flDuration); + } + void EntityBounds( HSCRIPT pEntity, int r, int g, int b, int a, float flDuration ) + { + RETURN_IF_CANNOT_DRAW_OVERLAY + + CBaseEntity *pEnt = ToEnt(pEntity); + if (!pEnt) + return; + + const CCollisionProperty *pCollide = pEnt->CollisionProp(); + debugoverlay->AddBoxOverlay(pCollide->GetCollisionOrigin(), pCollide->OBBMins(), pCollide->OBBMaxs(), pCollide->GetCollisionAngles(), r, g, b, a, flDuration); + } + void Line( const Vector &origin, const Vector &target, int r, int g, int b, bool noDepthTest, float flDuration ) + { + RETURN_IF_CANNOT_DRAW_OVERLAY + + debugoverlay->AddLineOverlay(origin, target, r, g, b, noDepthTest, flDuration); + } + void Triangle( const Vector &p1, const Vector &p2, const Vector &p3, int r, int g, int b, int a, bool noDepthTest, float duration ) + { + RETURN_IF_CANNOT_DRAW_OVERLAY + + debugoverlay->AddTriangleOverlay(p1, p2, p3, r, g, b, a, noDepthTest, duration); + } + void EntityText( int entityID, int text_offset, const char *text, float flDuration, int r, int g, int b, int a ) + { + RETURN_IF_CANNOT_DRAW_OVERLAY + + debugoverlay->AddEntityTextOverlay(entityID, text_offset, flDuration, + (int)clamp(r * 255.f, 0.f, 255.f), (int)clamp(g * 255.f, 0.f, 255.f), (int)clamp(b * 255.f, 0.f, 255.f), + (int)clamp(a * 255.f, 0.f, 255.f), text); + } + void EntityTextAtPosition( const Vector &origin, int text_offset, const char *text, float flDuration, int r, int g, int b, int a ) + { + RETURN_IF_CANNOT_DRAW_OVERLAY + + debugoverlay->AddTextOverlayRGB(origin, text_offset, flDuration, r, g, b, a, "%s", text); + } + void Grid( const Vector &vPosition ) + { + RETURN_IF_CANNOT_DRAW_OVERLAY + + debugoverlay->AddGridOverlay(vPosition); + } + void Text( const Vector &origin, const char *text, float flDuration ) + { + RETURN_IF_CANNOT_DRAW_OVERLAY + + debugoverlay->AddTextOverlay(origin, flDuration, "%s", text); + } + void ScreenText( float fXpos, float fYpos, const char *text, int r, int g, int b, int a, float flDuration ) + { + RETURN_IF_CANNOT_DRAW_OVERLAY + + debugoverlay->AddScreenTextOverlay(fXpos, fYpos, flDuration, r, g, b, a, text); + } + void Cross3D( const Vector &position, float size, int r, int g, int b, bool noDepthTest, float flDuration ) + { + RETURN_IF_CANNOT_DRAW_OVERLAY + + Line( position + Vector(size,0,0), position - Vector(size,0,0), r, g, b, noDepthTest, flDuration ); + Line( position + Vector(0,size,0), position - Vector(0,size,0), r, g, b, noDepthTest, flDuration ); + Line( position + Vector(0,0,size), position - Vector(0,0,size), r, g, b, noDepthTest, flDuration ); + } + void Cross3DOriented( const Vector &position, const QAngle &angles, float size, int r, int g, int b, bool noDepthTest, float flDuration ) + { + RETURN_IF_CANNOT_DRAW_OVERLAY + + Vector forward, right, up; + AngleVectors( angles, &forward, &right, &up ); + + forward *= size; + right *= size; + up *= size; + + Line( position + right, position - right, r, g, b, noDepthTest, flDuration ); + Line( position + forward, position - forward, r, g, b, noDepthTest, flDuration ); + Line( position + up, position - up, r, g, b, noDepthTest, flDuration ); + } + void DrawTickMarkedLine( const Vector &startPos, const Vector &endPos, float tickDist, int tickTextDist, int r, int g, int b, bool noDepthTest, float flDuration ) + { + RETURN_IF_CANNOT_DRAW_OVERLAY + + Vector lineDir = (endPos - startPos); + float lineDist = VectorNormalize(lineDir); + int numTicks = lineDist / tickDist; + + Vector upVec = Vector(0,0,4); + Vector sideDir; + Vector tickPos = startPos; + int tickTextCnt = 0; + + CrossProduct(lineDir, upVec, sideDir); + + Line(startPos, endPos, r, g, b, noDepthTest, flDuration); + + for (int i = 0; i 0 ) + { + Triangle( p5, p4, p3, r, g, b, a, noDepthTest, flDuration ); + Triangle( p1, p7, p6, r, g, b, a, noDepthTest, flDuration ); + Triangle( p6, p2, p1, r, g, b, a, noDepthTest, flDuration ); + + Triangle( p3, p4, p5, r, g, b, a, noDepthTest, flDuration ); + Triangle( p6, p7, p1, r, g, b, a, noDepthTest, flDuration ); + Triangle( p1, p2, p6, r, g, b, a, noDepthTest, flDuration ); + } + } + void YawArrow( const Vector &startPos, float yaw, float length, float width, int r, int g, int b, int a, bool noDepthTest, float flDuration ) + { + RETURN_IF_CANNOT_DRAW_OVERLAY + + Vector forward = UTIL_YawToVector( yaw ); + HorzArrow( startPos, startPos + forward * length, width, r, g, b, a, noDepthTest, flDuration ); + } + void VertArrow( const Vector &startPos, const Vector &endPos, float width, int r, int g, int b, int a, bool noDepthTest, float flDuration ) + { + RETURN_IF_CANNOT_DRAW_OVERLAY + + Vector lineDir = (endPos - startPos); + VectorNormalize( lineDir ); + Vector upVec; + Vector sideDir; + float radius = width / 2.0; + + VectorVectors( lineDir, sideDir, upVec ); + + Vector p1 = startPos - upVec * radius; + Vector p2 = endPos - lineDir * width - upVec * radius; + Vector p3 = endPos - lineDir * width - upVec * width; + Vector p4 = endPos; + Vector p5 = endPos - lineDir * width + upVec * width; + Vector p6 = endPos - lineDir * width + upVec * radius; + Vector p7 = startPos + upVec * radius; + + Line(p1, p2, r,g,b,noDepthTest,flDuration); + Line(p2, p3, r,g,b,noDepthTest,flDuration); + Line(p3, p4, r,g,b,noDepthTest,flDuration); + Line(p4, p5, r,g,b,noDepthTest,flDuration); + Line(p5, p6, r,g,b,noDepthTest,flDuration); + Line(p6, p7, r,g,b,noDepthTest,flDuration); + + if ( a > 0 ) + { + Triangle( p5, p4, p3, r, g, b, a, noDepthTest, flDuration ); + Triangle( p1, p7, p6, r, g, b, a, noDepthTest, flDuration ); + Triangle( p6, p2, p1, r, g, b, a, noDepthTest, flDuration ); + + Triangle( p3, p4, p5, r, g, b, a, noDepthTest, flDuration ); + Triangle( p6, p7, p1, r, g, b, a, noDepthTest, flDuration ); + Triangle( p1, p2, p6, r, g, b, a, noDepthTest, flDuration ); + } + } + void Axis( const Vector &position, const QAngle &angles, float size, bool noDepthTest, float flDuration ) + { + RETURN_IF_CANNOT_DRAW_OVERLAY + + Vector xvec, yvec, zvec; + AngleVectors( angles, &xvec, &yvec, &zvec ); + + xvec = position + (size * xvec); + yvec = position - (size * yvec); + zvec = position + (size * zvec); + + Line( position, xvec, 255, 0, 0, noDepthTest, flDuration ); + Line( position, yvec, 0, 255, 0, noDepthTest, flDuration ); + Line( position, zvec, 0, 0, 255, noDepthTest, flDuration ); + } + void Sphere( const Vector ¢er, float radius, int r, int g, int b, bool noDepthTest, float flDuration ) + { + RETURN_IF_CANNOT_DRAW_OVERLAY + + Vector edge, lastEdge; + + float axisSize = radius; + Line( center + Vector( 0, 0, -axisSize ), center + Vector( 0, 0, axisSize ), r, g, b, noDepthTest, flDuration ); + Line( center + Vector( 0, -axisSize, 0 ), center + Vector( 0, axisSize, 0 ), r, g, b, noDepthTest, flDuration ); + Line( center + Vector( -axisSize, 0, 0 ), center + Vector( axisSize, 0, 0 ), r, g, b, noDepthTest, flDuration ); + + lastEdge = Vector( radius + center.x, center.y, center.z ); + float angle; + for( angle=0.0f; angle <= 360.0f; angle += 22.5f ) + { + edge.x = radius * cosf( angle / 180.0f * M_PI ) + center.x; + edge.y = center.y; + edge.z = radius * sinf( angle / 180.0f * M_PI ) + center.z; + + Line( edge, lastEdge, r, g, b, noDepthTest, flDuration ); + + lastEdge = edge; + } + + lastEdge = Vector( center.x, radius + center.y, center.z ); + for( angle=0.0f; angle <= 360.0f; angle += 22.5f ) + { + edge.x = center.x; + edge.y = radius * cosf( angle / 180.0f * M_PI ) + center.y; + edge.z = radius * sinf( angle / 180.0f * M_PI ) + center.z; + + Line( edge, lastEdge, r, g, b, noDepthTest, flDuration ); + + lastEdge = edge; + } + + lastEdge = Vector( center.x, radius + center.y, center.z ); + for( angle=0.0f; angle <= 360.0f; angle += 22.5f ) + { + edge.x = radius * cosf( angle / 180.0f * M_PI ) + center.x; + edge.y = radius * sinf( angle / 180.0f * M_PI ) + center.y; + edge.z = center.z; + + Line( edge, lastEdge, r, g, b, noDepthTest, flDuration ); + + lastEdge = edge; + } + } + void CircleOriented( const Vector &position, const QAngle &angles, float radius, int r, int g, int b, int a, bool bNoDepthTest, float flDuration ) + { + RETURN_IF_CANNOT_DRAW_OVERLAY + + matrix3x4_t xform; + AngleMatrix(angles, position, xform); + Vector xAxis, yAxis; + MatrixGetColumn(xform, 2, xAxis); + MatrixGetColumn(xform, 1, yAxis); + Circle(position, xAxis, yAxis, radius, r, g, b, a, bNoDepthTest, flDuration); + } + void Circle( const Vector &position, const Vector &xAxis, const Vector &yAxis, float radius, int r, int g, int b, int a, bool bNoDepthTest, float flDuration ) + { + RETURN_IF_CANNOT_DRAW_OVERLAY + + const unsigned int nSegments = 16; + const float flRadStep = (M_PI*2.0f) / (float) nSegments; + + Vector vecLastPosition; + Vector vecStart = position + xAxis * radius; + Vector vecPosition = vecStart; + + for ( int i = 1; i <= nSegments; i++ ) + { + vecLastPosition = vecPosition; + + float flSin, flCos; + SinCos( flRadStep*i, &flSin, &flCos ); + vecPosition = position + (xAxis * flCos * radius) + (yAxis * flSin * radius); + + Line( vecLastPosition, vecPosition, r, g, b, bNoDepthTest, flDuration ); + + if ( a && i > 1 ) + { + debugoverlay->AddTriangleOverlay( vecStart, vecLastPosition, vecPosition, r, g, b, a, bNoDepthTest, flDuration ); + } + } + } +#ifndef CLIENT_DLL + void SetDebugBits( HSCRIPT hEntity, int bit ) // DebugOverlayBits_t + { + CBaseEntity *pEnt = ToEnt(hEntity); + if (!pEnt) + return; + + if (pEnt->m_debugOverlays & bit) + { + pEnt->m_debugOverlays &= ~bit; + } + else + { + pEnt->m_debugOverlays |= bit; + +#ifdef AI_MONITOR_FOR_OSCILLATION + if (pEnt->IsNPC()) + { + pEnt->MyNPCPointer()->m_ScheduleHistory.RemoveAll(); + } +#endif//AI_MONITOR_FOR_OSCILLATION + } + } +#endif + void ClearAllOverlays() + { +#ifndef CLIENT_DLL + // Clear all entities of their debug overlays + for (CBaseEntity *pEntity = gEntList.FirstEnt(); pEntity; pEntity = gEntList.NextEnt(pEntity)) + { + pEntity->m_debugOverlays = 0; + } +#endif + + debugoverlay->ClearAllOverlays(); + } + +private: +} g_ScriptDebugOverlay; + +BEGIN_SCRIPTDESC_ROOT( CDebugOverlayScriptHelper, SCRIPT_SINGLETON "CDebugOverlayScriptHelper" ) + DEFINE_SCRIPTFUNC( Box, "Draws a world-space axis-aligned box. Specify bounds in world space." ) + DEFINE_SCRIPTFUNC( BoxDirection, "Draw box oriented to a Vector direction" ) + DEFINE_SCRIPTFUNC( BoxAngles, "Draws an oriented box at the origin. Specify bounds in local space." ) + DEFINE_SCRIPTFUNC( SweptBox, "Draws a swept box. Specify endpoints in world space and the bounds in local space." ) + DEFINE_SCRIPTFUNC( EntityBounds, "Draws bounds of an entity" ) + DEFINE_SCRIPTFUNC( Line, "Draws a line between two points" ) + DEFINE_SCRIPTFUNC( Triangle, "Draws a filled triangle. Specify vertices in world space." ) + DEFINE_SCRIPTFUNC( EntityText, "Draws text on an entity" ) + DEFINE_SCRIPTFUNC( EntityTextAtPosition, "Draw entity text overlay at a specific position" ) + DEFINE_SCRIPTFUNC( Grid, "Add grid overlay" ) + DEFINE_SCRIPTFUNC( Text, "Draws 2D text. Specify origin in world space." ) + DEFINE_SCRIPTFUNC( ScreenText, "Draws 2D text. Specify coordinates in screen space." ) + DEFINE_SCRIPTFUNC( Cross3D, "Draws a world-aligned cross. Specify origin in world space." ) + DEFINE_SCRIPTFUNC( Cross3DOriented, "Draws an oriented cross. Specify origin in world space." ) + DEFINE_SCRIPTFUNC( DrawTickMarkedLine, "Draws a dashed line. Specify endpoints in world space." ) + DEFINE_SCRIPTFUNC( HorzArrow, "Draws a horizontal arrow. Specify endpoints in world space." ) + DEFINE_SCRIPTFUNC( YawArrow, "Draws a arrow associated with a specific yaw. Specify endpoints in world space." ) + DEFINE_SCRIPTFUNC( VertArrow, "Draws a vertical arrow. Specify endpoints in world space." ) + DEFINE_SCRIPTFUNC( Axis, "Draws an axis. Specify origin + orientation in world space." ) + DEFINE_SCRIPTFUNC( Sphere, "Draws a wireframe sphere. Specify center in world space." ) + DEFINE_SCRIPTFUNC( CircleOriented, "Draws a circle oriented. Specify center in world space." ) + DEFINE_SCRIPTFUNC( Circle, "Draws a circle. Specify center in world space." ) +#ifndef CLIENT_DLL + DEFINE_SCRIPTFUNC( SetDebugBits, "Set debug bits on entity" ) +#endif + DEFINE_SCRIPTFUNC( ClearAllOverlays, "Clear all debug overlays at once" ) +END_SCRIPTDESC(); + + + +//============================================================================= +// ConVars +//============================================================================= +class CScriptConCommand : public ConCommand, public ICommandCallback, public ICommandCompletionCallback +{ + typedef ConCommand BaseClass; + +public: + ~CScriptConCommand() + { + Unregister(); + } + + CScriptConCommand( const char *name, HSCRIPT fn, const char *helpString, int flags, ConCommand *pLinked = NULL ) + : BaseClass( name, this, helpString, flags, 0 ), + m_pLinked(pLinked), + m_hCallback(fn), + m_hCompletionCallback(NULL) + { + m_nCmdNameLen = V_strlen(name) + 1; + Assert( m_nCmdNameLen - 1 <= 128 ); + } + + void CommandCallback( const CCommand &command ) + { + int count = command.ArgC(); + ScriptVariant_t *vArgv = (ScriptVariant_t*)stackalloc( sizeof(ScriptVariant_t) * count ); + for ( int i = 0; i < count; ++i ) + { + vArgv[i] = command[i]; + } + ScriptVariant_t ret; + if ( g_pScriptVM->ExecuteFunction( m_hCallback, vArgv, count, &ret, NULL, true ) == SCRIPT_ERROR ) + { + DevWarning( 1, "CScriptConCommand: invalid callback for '%s'\n", command[0] ); + } + if ( m_pLinked && (ret.GetType() == FIELD_BOOLEAN) && ret.Get()) + { + m_pLinked->Dispatch( command ); + } + } + + int CommandCompletionCallback( const char *partial, CUtlVector< CUtlString > &commands ) + { + Assert( g_pScriptVM ); + Assert( m_hCompletionCallback ); + + ScriptVariant_t hArray; + g_pScriptVM->CreateArray( hArray ); + + // split command name from partial, pass both separately to the script function + char *cmdname = (char*)stackalloc( m_nCmdNameLen ); + V_memcpy( cmdname, partial, m_nCmdNameLen - 1 ); + cmdname[ m_nCmdNameLen - 1 ] = 0; + + char argPartial[256]; + V_StrRight( partial, V_strlen(partial) - m_nCmdNameLen, argPartial, sizeof(argPartial) ); + + ScriptVariant_t args[3] = { cmdname, argPartial, hArray }; + if ( g_pScriptVM->ExecuteFunction( m_hCompletionCallback, args, 3, NULL, NULL, true ) == SCRIPT_ERROR ) + { + DevWarning( 1, "CScriptConCommand: invalid command completion callback for '%s'\n", cmdname ); + g_pScriptVM->ReleaseScript( hArray ); + return 0; + } + + int count = 0; + ScriptVariant_t val; + int it = -1; + while ( ( it = g_pScriptVM->GetKeyValue( hArray, it, NULL, &val ) ) != -1 ) + { + if ( val.GetType() == FIELD_CSTRING) + { + CUtlString &s = commands.Element( commands.AddToTail() ); + int len = V_strlen( val.Get() ); + + if ( len <= COMMAND_COMPLETION_ITEM_LENGTH - 1 ) + { + s.Set( val.Get() ); + } + else + { + s.SetDirect( val.Get(), COMMAND_COMPLETION_ITEM_LENGTH - 1 ); + } + + ++count; + } + g_pScriptVM->ReleaseValue(val); + + if ( count == COMMAND_COMPLETION_MAXITEMS ) + break; + } + g_pScriptVM->ReleaseScript( hArray ); + return count; + } + + void SetCompletionCallback( HSCRIPT fn ) + { + if ( m_hCompletionCallback ) + g_pScriptVM->ReleaseScript( m_hCompletionCallback ); + + if (fn) + { + if ( !BaseClass::IsRegistered() ) + return; + + BaseClass::m_pCommandCompletionCallback = this; + BaseClass::m_bHasCompletionCallback = true; + m_hCompletionCallback = fn; + } + else + { + BaseClass::m_pCommandCompletionCallback = NULL; + BaseClass::m_bHasCompletionCallback = false; + m_hCompletionCallback = NULL; + } + } + + void SetCallback( HSCRIPT fn ) + { + if (fn) + { + if ( !BaseClass::IsRegistered() ) + Register(); + + if ( m_hCallback ) + g_pScriptVM->ReleaseScript( m_hCallback ); + m_hCallback = fn; + } + else + { + Unregister(); + } + } + + inline void Unregister() + { + if ( g_pCVar && BaseClass::IsRegistered() ) + g_pCVar->UnregisterConCommand( this ); + + if ( g_pScriptVM ) + { + if ( m_hCallback ) + { + g_pScriptVM->ReleaseScript( m_hCallback ); + m_hCallback = NULL; + } + + SetCompletionCallback( NULL ); + } + } + + inline void Register() + { + if ( g_pCVar ) + g_pCVar->RegisterConCommand( this ); + } + + HSCRIPT m_hCallback; + ConCommand *m_pLinked; + HSCRIPT m_hCompletionCallback; + int m_nCmdNameLen; +}; + +class CScriptConVar : public ConVar +{ + typedef ConVar BaseClass; + +public: + ~CScriptConVar() + { + Unregister(); + } + + CScriptConVar( const char *pName, const char *pDefaultValue, const char *pHelpString, int flags/*, float fMin, float fMax*/ ) + : BaseClass( pName, pDefaultValue, flags, pHelpString ), + m_hCallback(NULL) + {} + + void SetChangeCallback( HSCRIPT fn ) + { + void ScriptConVarCallback( IConVar*, const char*, float ); + + if ( m_hCallback ) + g_pScriptVM->ReleaseScript( m_hCallback ); + + if (fn) + { + m_hCallback = fn; + BaseClass::InstallChangeCallback( (FnChangeCallback_t)ScriptConVarCallback ); + } + else + { + m_hCallback = NULL; + BaseClass::InstallChangeCallback( NULL ); + } + } + + inline void Unregister() + { + if ( g_pCVar && BaseClass::IsRegistered() ) + g_pCVar->UnregisterConCommand( this ); + + if ( g_pScriptVM ) + { + SetChangeCallback( NULL ); + } + } + + HSCRIPT m_hCallback; +}; + +static CUtlMap< unsigned int, bool > g_ConVarsBlocked( DefLessFunc(unsigned int) ); +static CUtlMap< unsigned int, bool > g_ConCommandsOverridable( DefLessFunc(unsigned int) ); +static CUtlMap< unsigned int, CScriptConCommand* > g_ScriptConCommands( DefLessFunc(unsigned int) ); +static CUtlMap< unsigned int, CScriptConVar* > g_ScriptConVars( DefLessFunc(unsigned int) ); + + +class CScriptConvarAccessor : public CAutoGameSystem +{ +public: + static inline unsigned int Hash( const char*sz ){ return HashStringCaseless(sz); } + +public: + inline void AddOverridable( const char *name ) + { + g_ConCommandsOverridable.InsertOrReplace( Hash(name), true ); + } + + inline bool IsOverridable( unsigned int hash ) + { + int idx = g_ConCommandsOverridable.Find( hash ); + return ( idx != g_ConCommandsOverridable.InvalidIndex() ); + } + + inline void AddBlockedConVar( const char *name ) + { + g_ConVarsBlocked.InsertOrReplace( Hash(name), true ); + } + + inline bool IsBlockedConvar( const char *name ) + { + int idx = g_ConVarsBlocked.Find( Hash(name) ); + return ( idx != g_ConVarsBlocked.InvalidIndex() ); + } + +public: + void RegisterCommand( const char *name, HSCRIPT fn, const char *helpString, int flags ); + void SetCompletionCallback( const char *name, HSCRIPT fn ); + void UnregisterCommand( const char *name ); + void RegisterConvar( const char *name, const char *pDefaultValue, const char *helpString, int flags ); + void SetChangeCallback( const char *name, HSCRIPT fn ); + + HSCRIPT GetCommandClient() + { +#ifdef GAME_DLL + return ToHScript( UTIL_GetCommandClient() ); +#else + return ToHScript( C_BasePlayer::GetLocalPlayer() ); +#endif + } +#ifdef GAME_DLL + const char *GetClientConvarValue( int index, const char* cvar ) + { + return engine->GetClientConVarValue( index, cvar ); + } +#endif +public: + bool Init(); + + void LevelShutdownPostEntity() + { + g_ScriptConCommands.PurgeAndDeleteElements(); + g_ScriptConVars.PurgeAndDeleteElements(); + } + +public: + float GetFloat( const char *pszConVar ) + { + ConVarRef cvar( pszConVar ); + if ( cvar.IsFlagSet( FCVAR_SERVER_CANNOT_QUERY ) ) + return NULL; + return cvar.GetFloat(); + } + + int GetInt( const char *pszConVar ) + { + ConVarRef cvar( pszConVar ); + if ( cvar.IsFlagSet( FCVAR_SERVER_CANNOT_QUERY ) ) + return NULL; + return cvar.GetInt(); + } + + bool GetBool( const char *pszConVar ) + { + ConVarRef cvar( pszConVar ); + if ( cvar.IsFlagSet( FCVAR_SERVER_CANNOT_QUERY ) ) + return NULL; + return cvar.GetBool(); + } + + const char *GetStr( const char *pszConVar ) + { + ConVarRef cvar( pszConVar ); + if ( cvar.IsFlagSet( FCVAR_SERVER_CANNOT_QUERY ) ) + return NULL; + return cvar.GetString(); + } + + const char *GetDefaultValue( const char *pszConVar ) + { + ConVarRef cvar( pszConVar ); + return cvar.GetDefault(); + } + + bool IsFlagSet( const char *pszConVar, int nFlags ) + { + ConVarRef cvar( pszConVar ); + return cvar.IsFlagSet( nFlags ); + } + + void SetFloat( const char *pszConVar, float value ) + { + SetValue( pszConVar, value ); + } + + void SetInt( const char *pszConVar, int value ) + { + SetValue( pszConVar, value ); + } + + void SetBool( const char *pszConVar, bool value ) + { + SetValue( pszConVar, value ); + } + + void SetStr( const char *pszConVar, const char *value ) + { + SetValue( pszConVar, value ); + } + + template + void SetValue( const char *pszConVar, T value ) + { + ConVarRef cvar( pszConVar ); + if ( !cvar.IsValid() ) + return; + + if ( cvar.IsFlagSet( FCVAR_NOT_CONNECTED | FCVAR_SERVER_CANNOT_QUERY ) ) + return; + + if ( IsBlockedConvar( pszConVar ) ) + return; + + cvar.SetValue( value ); + } + +} g_ScriptConvarAccessor; + + +void CScriptConvarAccessor::RegisterCommand( const char *name, HSCRIPT fn, const char *helpString, int flags ) +{ +#if CLIENT_DLL + // FIXME: This crashes in engine when used as a hook (dispatched from CScriptConCommand::CommandCallback()) + Assert( V_stricmp( name, "load" ) != 0 ); +#endif + + unsigned int hash = Hash(name); + int idx = g_ScriptConCommands.Find(hash); + if ( idx == g_ScriptConCommands.InvalidIndex() ) + { + ConCommandBase *pBase = g_pCVar->FindCommandBase(name); + if ( pBase && ( !pBase->IsCommand() || !IsOverridable(hash) ) ) + { + DevWarning( 1, "CScriptConvarAccessor::RegisterCommand unable to register blocked ConCommand: %s\n", name ); + return; + } + + if ( !fn ) + return; + + CScriptConCommand *p = new CScriptConCommand( name, fn, helpString, flags, static_cast< ConCommand* >(pBase) ); + g_ScriptConCommands.Insert( hash, p ); + } + else + { + CScriptConCommand *pCmd = g_ScriptConCommands[idx]; + pCmd->SetCallback( fn ); + //CGMsg( 1, CON_GROUP_VSCRIPT, "CScriptConvarAccessor::RegisterCommand replacing command already registered: %s\n", name ); + } +} + +void CScriptConvarAccessor::SetCompletionCallback( const char *name, HSCRIPT fn ) +{ + unsigned int hash = Hash(name); + int idx = g_ScriptConCommands.Find(hash); + if ( idx != g_ScriptConCommands.InvalidIndex() ) + { + g_ScriptConCommands[idx]->SetCompletionCallback( fn ); + } +} + +void CScriptConvarAccessor::UnregisterCommand( const char *name ) +{ + unsigned int hash = Hash(name); + int idx = g_ScriptConCommands.Find(hash); + if ( idx != g_ScriptConCommands.InvalidIndex() ) + { + g_ScriptConCommands[idx]->Unregister(); + } +} + +void CScriptConvarAccessor::RegisterConvar( const char *name, const char *pDefaultValue, const char *helpString, int flags ) +{ + Assert( g_pCVar ); + unsigned int hash = Hash(name); + int idx = g_ScriptConVars.Find(hash); + if ( idx == g_ScriptConVars.InvalidIndex() ) + { + if ( g_pCVar->FindCommandBase(name) ) + { + DevWarning( 1, "CScriptConvarAccessor::RegisterConvar unable to register blocked ConCommand: %s\n", name ); + return; + } + + CScriptConVar *p = new CScriptConVar( name, pDefaultValue, helpString, flags ); + g_ScriptConVars.Insert( hash, p ); + } + else + { + //CGMsg( 1, CON_GROUP_VSCRIPT, "CScriptConvarAccessor::RegisterConvar convar %s already registered\n", name ); + } +} + +void CScriptConvarAccessor::SetChangeCallback( const char *name, HSCRIPT fn ) +{ + unsigned int hash = Hash(name); + int idx = g_ScriptConVars.Find(hash); + if ( idx != g_ScriptConVars.InvalidIndex() ) + { + g_ScriptConVars[idx]->SetChangeCallback( fn ); + } +} + +void ScriptConVarCallback( IConVar *var, const char* pszOldValue, float flOldValue ) +{ + ConVar *cvar = (ConVar*)var; + const char *name = cvar->GetName(); + unsigned int hash = CScriptConvarAccessor::Hash( name ); + int idx = g_ScriptConVars.Find(hash); + if ( idx != g_ScriptConVars.InvalidIndex() ) + { + Assert( g_ScriptConVars[idx]->m_hCallback ); + + ScriptVariant_t args[5] = { name, pszOldValue, flOldValue, cvar->GetString(), cvar->GetFloat() }; + if ( g_pScriptVM->ExecuteFunction( g_ScriptConVars[idx]->m_hCallback, args, 5, NULL, NULL, true ) == SCRIPT_ERROR ) + { + DevWarning( 1, "CScriptConVar: invalid change callback for '%s'\n", name ); + } + } +} + + +bool CScriptConvarAccessor::Init() +{ + static bool bExecOnce = false; + if ( bExecOnce ) + return true; + bExecOnce = true; + + AddOverridable( "+attack" ); + AddOverridable( "+attack2" ); + AddOverridable( "+attack3" ); + AddOverridable( "+forward" ); + AddOverridable( "+back" ); + AddOverridable( "+moveleft" ); + AddOverridable( "+moveright" ); + AddOverridable( "+use" ); + AddOverridable( "+jump" ); + AddOverridable( "+zoom" ); + AddOverridable( "+reload" ); + AddOverridable( "+speed" ); + AddOverridable( "+walk" ); + AddOverridable( "+duck" ); + AddOverridable( "+strafe" ); + AddOverridable( "+alt1" ); + AddOverridable( "+alt2" ); + AddOverridable( "+grenade1" ); + AddOverridable( "+grenade2" ); + AddOverridable( "+showscores" ); + AddOverridable( "+voicerecord" ); + + AddOverridable( "-attack" ); + AddOverridable( "-attack2" ); + AddOverridable( "-attack3" ); + AddOverridable( "-forward" ); + AddOverridable( "-back" ); + AddOverridable( "-moveleft" ); + AddOverridable( "-moveright" ); + AddOverridable( "-use" ); + AddOverridable( "-jump" ); + AddOverridable( "-zoom" ); + AddOverridable( "-reload" ); + AddOverridable( "-speed" ); + AddOverridable( "-walk" ); + AddOverridable( "-duck" ); + AddOverridable( "-strafe" ); + AddOverridable( "-alt1" ); + AddOverridable( "-alt2" ); + AddOverridable( "-grenade1" ); + AddOverridable( "-grenade2" ); + AddOverridable( "-showscores" ); + AddOverridable( "-voicerecord" ); + + AddOverridable( "toggle_duck" ); + AddOverridable( "impulse" ); + AddOverridable( "use" ); + AddOverridable( "lastinv" ); + AddOverridable( "invnext" ); + AddOverridable( "invprev" ); + AddOverridable( "phys_swap" ); + AddOverridable( "slot0" ); + AddOverridable( "slot1" ); + AddOverridable( "slot2" ); + AddOverridable( "slot3" ); + AddOverridable( "slot4" ); + AddOverridable( "slot5" ); + AddOverridable( "slot6" ); + AddOverridable( "slot7" ); + AddOverridable( "slot8" ); + AddOverridable( "slot9" ); + AddOverridable( "slot10" ); + + AddOverridable( "save" ); + AddOverridable( "load" ); + + AddOverridable( "say" ); + AddOverridable( "say_team" ); + + + AddBlockedConVar( "con_enable" ); + AddBlockedConVar( "cl_allowdownload" ); + AddBlockedConVar( "cl_allowupload" ); + AddBlockedConVar( "cl_downloadfilter" ); + + return true; +} + +BEGIN_SCRIPTDESC_ROOT_NAMED( CScriptConvarAccessor, "CConvars", SCRIPT_SINGLETON "Provides an interface to convars." ) + DEFINE_SCRIPTFUNC( RegisterConvar, "register a new console variable." ) + DEFINE_SCRIPTFUNC( RegisterCommand, "register a console command." ) + DEFINE_SCRIPTFUNC( SetCompletionCallback, "callback is called with 3 parameters (cmd, partial, commands), user strings must be appended to 'commands' array" ) + DEFINE_SCRIPTFUNC( SetChangeCallback, "callback is called with 5 parameters (var, szOldValue, flOldValue, szNewValue, flNewValue)" ) + DEFINE_SCRIPTFUNC( UnregisterCommand, "unregister a console command." ) + DEFINE_SCRIPTFUNC( GetCommandClient, "returns the player who issued this console command." ) +#ifdef GAME_DLL + DEFINE_SCRIPTFUNC( GetClientConvarValue, "Get a convar keyvalue for a specified client" ) +#endif + DEFINE_SCRIPTFUNC( GetFloat, "Returns the convar as a float. May return null if no such convar." ) + DEFINE_SCRIPTFUNC( GetInt, "Returns the convar as an int. May return null if no such convar." ) + DEFINE_SCRIPTFUNC( GetBool, "Returns the convar as a bool. May return null if no such convar." ) + DEFINE_SCRIPTFUNC( GetStr, "Returns the convar as a string. May return null if no such convar." ) + DEFINE_SCRIPTFUNC( GetDefaultValue, "Returns the convar's default value as a string. May return null if no such convar." ) + DEFINE_SCRIPTFUNC( IsFlagSet, "Returns the convar's flags. May return null if no such convar." ) + DEFINE_SCRIPTFUNC( SetFloat, "Sets the value of the convar as a float." ) + DEFINE_SCRIPTFUNC( SetInt, "Sets the value of the convar as an int." ) + DEFINE_SCRIPTFUNC( SetBool, "Sets the value of the convar as a bool." ) + DEFINE_SCRIPTFUNC( SetStr, "Sets the value of the convar as a string." ) +END_SCRIPTDESC(); + + +//============================================================================= +// Effects +// (Unique to mapbase) +// +// At the moment only clientside until a filtering method on server is finalised. +// +// TEs most of the time call IEffects (g_pEffects) or ITempEnts (tempents) on client, +// but they also record for tools recording mode. +// +// On client no TE is suppressed. +// TE flags are found at tempent.h +// +// TODO: +//============================================================================= +#ifdef CLIENT_DLL + +class CEffectsScriptHelper +{ +public: + void DynamicLight( int index, const Vector& origin, int r, int g, int b, int exponent, + float radius, float die, float decay, int style = 0, int flags = 0 ) + { + //te->DynamicLight( filter, delay, &origin, r, g, b, exponent, radius, die, decay ); + dlight_t *dl = effects->CL_AllocDlight( index ); + dl->origin = origin; + dl->color.r = r; + dl->color.g = g; + dl->color.b = b; + dl->color.exponent = exponent; + dl->radius = radius; + dl->die = gpGlobals->curtime + die; + dl->decay = decay; + dl->style = style; + dl->flags = flags; + } + + void Explosion( const Vector& pos, float scale, int radius, int magnitude, int flags ) + { + C_RecipientFilter filter; + filter.AddAllPlayers(); + // framerate, modelindex, normal and materialtype are unused + // radius for ragdolls + extern short g_sModelIndexFireball; + te->Explosion( filter, 0.0f, &pos, g_sModelIndexFireball, scale, 15, flags, radius, magnitude, &vec3_origin ); + } + +// void FXExplosion( const Vector& pos, const Vector& normal, int materialType = 'C' ) +// { +// // just the particles +// // materialtype only for debris. can be 'C','W' or anything else. +// FX_Explosion( const_cast(pos), const_cast(normal), materialType ); +// } + +// void ConcussiveExplosion( const Vector& pos, const Vector& normal ) +// { +// FX_ConcussiveExplosion( const_cast(pos), const_cast(normal) ); +// } + +// void MicroExplosion( const Vector& pos, const Vector& normal ) +// { +// FX_MicroExplosion( const_cast(pos), const_cast(normal) ); +// } + +// void MuzzleFlash( int type, HSCRIPT hEntity, int attachment, bool firstPerson ) +// { +// C_BaseEntity *p = ToEnt(hEntity); +// ClientEntityHandle_t ent = p ? (ClientEntityList().EntIndexToHandle)( p->entindex() ) : NULL;; +// tempents->MuzzleFlash( type, ent, attachment, firstPerson ); +// } + + void Sparks( const Vector& pos, int nMagnitude, int nTrailLength, const Vector& pDir ) + { + //te->Sparks( filter, delay, &pos, nMagnitude, nTrailLength, &pDir ); + //g_pEffects->Sparks( pos, nMagnitude, nTrailLength, &pDir ); + FX_ElectricSpark( pos, nMagnitude, nTrailLength, &pDir ); + } + + void MetalSparks( const Vector& pos, const Vector& dir ) + { + //g_pEffects->MetalSparks( pos, dir ); + FX_MetalSpark( pos, dir, dir ); + } + +// void Smoke( const Vector& pos, float scale, int framerate) +// { +// extern short g_sModelIndexSmoke; +// //te->Smoke( filter, 0.0, &pos, g_sModelIndexSmoke, scale * 10.0f, framerate ); +// g_pEffects->Smoke( pos, g_sModelIndexSmoke, scale, framerate ); +// } + + void Dust( const Vector &pos, const Vector &dir, float size, float speed ) + { + //te->Dust( filter, delay, pos, dir, size, speed ); + //g_pEffects->Dust( pos, dir, size, speed ); + FX_Dust( pos, dir, size, speed ); + } + + void Bubbles( const Vector &mins, const Vector &maxs, float height, int modelindex, int count, float speed ) + { + //int bubbles = modelinfo->GetModelIndex( "sprites/bubble.vmt" ); + //te->Bubbles( filter, delay, &mins, &maxs, height, modelindex, count, speed ); + tempents->Bubbles( mins, maxs, height, modelindex, count, speed ); + } + +// void Fizz( const Vector& mins, const Vector& maxs, int modelIndex, int density, int current/*, int flags*/ ) +// { +// //te->Fizz( filter, delay, ent, modelindex, density, current ); +// //tempents->FizzEffect( ToEnt(ent), modelindex, density, current ); +// } + + void Sprite( const Vector &pos, const Vector &dir, float scale, int modelIndex, int rendermode, + int renderfx, int brightness, float life, int flags ) + { + //te->Sprite( filter, delay, &pos, modelindex, size, brightness ); + float a = (1.0 / 255.0) * brightness; + tempents->TempSprite( pos, dir, scale, modelIndex, rendermode, renderfx, a, life, flags ); + } + +// void PhysicsProp( float delay, int modelindex, int skin, const Vector& pos, const QAngle &angles, +// const Vector& vel, int flags, int effects ) +// { +// //te->PhysicsProp( filter, delay, modelindex, skin, pos, angles, vel, flags, effects ); +// tempents->PhysicsProp( modelindex, skin, pos, angles, vel, flags, effects ); +// } + + void ClientProjectile( const Vector& vecOrigin, const Vector& vecVelocity, const Vector& vecAccel, int modelindex, + int lifetime, HSCRIPT pOwner, const char *pszImpactEffect = NULL, const char *pszParticleEffect = NULL ) + { + //te->ClientProjectile( filter, delay, &vecOrigin, &vecVelocity, modelindex, lifetime, ToEnt(pOwner) ); + if ( pszImpactEffect && !(*pszImpactEffect) ) + pszImpactEffect = NULL; + if ( pszParticleEffect && !(*pszParticleEffect) ) + pszParticleEffect = NULL; + tempents->ClientProjectile( vecOrigin, vecVelocity, vecAccel, modelindex, lifetime, ToEnt(pOwner), pszImpactEffect, pszParticleEffect ); + } + +} g_ScriptEffectsHelper; + +BEGIN_SCRIPTDESC_ROOT_NAMED( CEffectsScriptHelper, "CEffects", SCRIPT_SINGLETON "" ) + DEFINE_SCRIPTFUNC( DynamicLight, "" ) + DEFINE_SCRIPTFUNC( Explosion, "" ) + DEFINE_SCRIPTFUNC( Sparks, "" ) + DEFINE_SCRIPTFUNC( MetalSparks, "" ) + DEFINE_SCRIPTFUNC( Dust, "" ) + DEFINE_SCRIPTFUNC( Bubbles, "" ) + DEFINE_SCRIPTFUNC( Sprite, "" ) + DEFINE_SCRIPTFUNC( ClientProjectile, "" ) +END_SCRIPTDESC(); + + + +//============================================================================= +//============================================================================= + +extern CGlowObjectManager g_GlowObjectManager; + +class CScriptGlowObjectManager : public CAutoGameSystem +{ +public: + CUtlVector m_RegisteredObjects; + + void LevelShutdownPostEntity() + { + FOR_EACH_VEC( m_RegisteredObjects, i ) + g_GlowObjectManager.UnregisterGlowObject( m_RegisteredObjects[i] ); + m_RegisteredObjects.Purge(); + } + +public: + int Register( HSCRIPT hEntity, int r, int g, int b, int a, bool bRenderWhenOccluded, bool bRenderWhenUnoccluded ) + { + Vector vGlowColor; + vGlowColor.x = r * ( 1.0f / 255.0f ); + vGlowColor.y = g * ( 1.0f / 255.0f ); + vGlowColor.z = b * ( 1.0f / 255.0f ); + float flGlowAlpha = a * ( 1.0f / 255.0f ); + int idx = g_GlowObjectManager.RegisterGlowObject( ToEnt(hEntity), vGlowColor, flGlowAlpha, bRenderWhenOccluded, bRenderWhenUnoccluded, -1 ); + m_RegisteredObjects.AddToTail( idx ); + return idx; + } + + void Unregister( int nGlowObjectHandle ) + { + if ( (nGlowObjectHandle < 0) || (nGlowObjectHandle >= g_GlowObjectManager.m_GlowObjectDefinitions.Count()) ) + return; + g_GlowObjectManager.UnregisterGlowObject( nGlowObjectHandle ); + m_RegisteredObjects.FindAndFastRemove( nGlowObjectHandle ); + } + + void SetEntity( int nGlowObjectHandle, HSCRIPT hEntity ) + { + g_GlowObjectManager.SetEntity( nGlowObjectHandle, ToEnt(hEntity) ); + } + + void SetColor( int nGlowObjectHandle, int r, int g, int b ) + { + Vector vGlowColor; + vGlowColor.x = r * ( 1.0f / 255.0f ); + vGlowColor.y = g * ( 1.0f / 255.0f ); + vGlowColor.z = b * ( 1.0f / 255.0f ); + g_GlowObjectManager.SetColor( nGlowObjectHandle, vGlowColor ); + } + + void SetAlpha( int nGlowObjectHandle, int a ) + { + float flGlowAlpha = a * ( 1.0f / 255.0f ); + g_GlowObjectManager.SetAlpha( nGlowObjectHandle, flGlowAlpha ); + } + + void SetRenderFlags( int nGlowObjectHandle, bool bRenderWhenOccluded, bool bRenderWhenUnoccluded ) + { + g_GlowObjectManager.SetRenderFlags( nGlowObjectHandle, bRenderWhenOccluded, bRenderWhenUnoccluded ); + } + +} g_ScriptGlowObjectManager; + +BEGIN_SCRIPTDESC_ROOT_NAMED( CScriptGlowObjectManager, "CGlowObjectManager", SCRIPT_SINGLETON "" ) + DEFINE_SCRIPTFUNC( Register, "( HSCRIPT hEntity, int r, int g, int b, int a, bool bRenderWhenOccluded, bool bRenderWhenUnoccluded )" ) + DEFINE_SCRIPTFUNC( Unregister, "" ) + DEFINE_SCRIPTFUNC( SetEntity, "" ) + DEFINE_SCRIPTFUNC( SetColor, "" ) + DEFINE_SCRIPTFUNC( SetAlpha, "" ) + DEFINE_SCRIPTFUNC( SetRenderFlags, "" ) +END_SCRIPTDESC(); + + +//============================================================================= +//============================================================================= + + +#if !defined(NO_STEAM) +class CScriptSteamAPI +{ +public: + const char *GetSteam2ID() + { + if ( !steamapicontext || !steamapicontext->SteamUser() ) + return NULL; + + CSteamID id = steamapicontext->SteamUser()->GetSteamID(); + + uint32 accountID = id.GetAccountID(); + uint32 steamInstanceID = 0; + uint32 high32bits = accountID % 2; + uint32 low32bits = accountID / 2; + + static char ret[48]; + V_snprintf( ret, sizeof(ret), "STEAM_%u:%u:%u", steamInstanceID, high32bits, low32bits ); + return ret; + } + + int GetSecondsSinceComputerActive() + { + if ( !steamapicontext || !steamapicontext->SteamUtils() ) + return 0; + + return steamapicontext->SteamUtils()->GetSecondsSinceComputerActive(); + } + + int GetCurrentBatteryPower() + { + if ( !steamapicontext || !steamapicontext->SteamUtils() ) + return 0; + + return steamapicontext->SteamUtils()->GetCurrentBatteryPower(); + } +#if 0 + const char *GetIPCountry() + { + if ( !steamapicontext || !steamapicontext->SteamUtils() ) + return NULL; + + const char *get = steamapicontext->SteamUtils()->GetIPCountry(); + if ( !get ) + return NULL; + + static char ret[3]; + V_strncpy( ret, get, 3 ); + + return ret; + } +#endif + const char *GetCurrentGameLanguage() + { + if ( !steamapicontext || !steamapicontext->SteamApps() ) + return NULL; + + const char *lang = steamapicontext->SteamApps()->GetCurrentGameLanguage(); + if ( !lang ) + return NULL; + + static char ret[16]; + V_strncpy( ret, lang, sizeof(ret) ); + + return ret; + } + const char *GetCurrentBetaName() + { + if ( !steamapicontext || !steamapicontext->SteamApps() ) + return NULL; + + static char ret[16]; + steamapicontext->SteamApps()->GetCurrentBetaName( ret, sizeof( ret ) ); + return ret; + } +#if 0 + bool IsSubscribedApp( int nAppID ) + { + if ( !steamapicontext || !steamapicontext->SteamApps() ) + return false; + + return steamapicontext->SteamApps()->BIsSubscribedApp( nAppID ); + } +#endif + bool IsAppInstalled( int nAppID ) + { + if ( !steamapicontext || !steamapicontext->SteamApps() ) + return false; + + return steamapicontext->SteamApps()->BIsAppInstalled( nAppID ); + } + +} g_ScriptSteamAPI; + +BEGIN_SCRIPTDESC_ROOT_NAMED( CScriptSteamAPI, "CSteamAPI", SCRIPT_SINGLETON "" ) + DEFINE_SCRIPTFUNC( GetSteam2ID, "" ) + //DEFINE_SCRIPTFUNC( IsVACBanned, "" ) + DEFINE_SCRIPTFUNC( GetSecondsSinceComputerActive, "Returns the number of seconds since the user last moved the mouse." ) + DEFINE_SCRIPTFUNC( GetCurrentBatteryPower, "Return the amount of battery power left in the current system in % [0..100], 255 for being on AC power" ) + //DEFINE_SCRIPTFUNC( GetIPCountry, "Returns the 2 digit ISO 3166-1-alpha-2 format country code this client is running in (as looked up via an IP-to-location database)" ) + DEFINE_SCRIPTFUNC( GetCurrentGameLanguage, "Gets the current language that the user has set as API language code. This falls back to the Steam UI language if the user hasn't explicitly picked a language for the title." ) + DEFINE_SCRIPTFUNC( GetCurrentBetaName, "Gets the name of the user's current beta branch. In Source SDK Base 2013 Singleplayer, this will usually return 'upcoming'." ) + //DEFINE_SCRIPTFUNC( IsSubscribedApp, "Returns true if the user is subscribed to the specified app ID." ) + DEFINE_SCRIPTFUNC( IsAppInstalled, "Returns true if the user has the specified app ID installed on their computer." ) +END_SCRIPTDESC(); +#endif // !NO_STEAM + +#endif // CLIENT_DLL + + +void RegisterScriptSingletons() +{ + ScriptRegisterFunctionNamed( g_pScriptVM, CScriptSaveRestoreUtil::SaveTable, "SaveTable", "Store a table with primitive values that will persist across level transitions and save loads." ); + ScriptRegisterFunctionNamed( g_pScriptVM, CScriptSaveRestoreUtil::RestoreTable, "RestoreTable", "Retrieves a table from storage. Write into input table." ); + ScriptRegisterFunctionNamed( g_pScriptVM, CScriptSaveRestoreUtil::ClearSavedTable, "ClearSavedTable", "Removes the table with the given context." ); + ScriptRegisterSimpleHook( g_pScriptVM, g_Hook_OnSave, "OnSave", FIELD_VOID, "Called when the game is saved." ); + ScriptRegisterSimpleHook( g_pScriptVM, g_Hook_OnRestore, "OnRestore", FIELD_VOID, "Called when the game is restored." ); + ScriptRegisterFunctionNamed( g_pScriptVM, CScriptReadWriteFile::FileWrite, "StringToFile", "Stores the string into the file" ); + ScriptRegisterFunctionNamed( g_pScriptVM, CScriptReadWriteFile::FileRead, "FileToString", "Returns the string from the file, null if no file or file is too big." ); + ScriptRegisterFunctionNamed( g_pScriptVM, CScriptReadWriteFile::FileExists, "FileExists", "Returns true if the file exists." ); + ScriptRegisterFunctionNamed( g_pScriptVM, CScriptReadWriteFile::KeyValuesWrite, "KeyValuesToFile", "Stores the CScriptKeyValues into the file" ); + ScriptRegisterFunctionNamed( g_pScriptVM, CScriptReadWriteFile::KeyValuesRead, "FileToKeyValues", "Returns the CScriptKeyValues from the file, null if no file or file is too big." ); + + ScriptRegisterFunction( g_pScriptVM, ListenToGameEvent, "Register as a listener for a game event from script." ); + ScriptRegisterFunctionNamed( g_pScriptVM, CScriptGameEventListener::StopListeningToGameEvent, "StopListeningToGameEvent", "Stop the specified event listener." ); + ScriptRegisterFunctionNamed( g_pScriptVM, CScriptGameEventListener::StopListeningToAllGameEvents, "StopListeningToAllGameEvents", "Stop listening to all game events within a specific context." ); + ScriptRegisterFunction( g_pScriptVM, FireGameEvent, "Fire a game event." ); +#ifndef CLIENT_DLL + ScriptRegisterFunction( g_pScriptVM, FireGameEventLocal, "Fire a game event without broadcasting to the client." ); +#endif + + g_pScriptVM->RegisterInstance( &g_ScriptNetPropManager, "NetProps" ); + g_pScriptVM->RegisterInstance( &g_ScriptLocalize, "Localize" ); + g_pScriptVM->RegisterInstance( g_ScriptNetMsg, "NetMsg" ); + g_pScriptVM->RegisterInstance( &g_ScriptDebugOverlay, "debugoverlay" ); + g_pScriptVM->RegisterInstance( &g_ScriptConvarAccessor, "Convars" ); +#ifdef CLIENT_DLL + g_pScriptVM->RegisterInstance( &g_ScriptEffectsHelper, "effects" ); + g_pScriptVM->RegisterInstance( &g_ScriptGlowObjectManager, "GlowObjectManager" ); + +#if !defined(NO_STEAM) + g_pScriptVM->RegisterInstance( &g_ScriptSteamAPI, "steam" ); +#endif +#endif + + // Singletons not unique to VScript (not declared or defined here) + g_pScriptVM->RegisterInstance( GameRules(), "GameRules" ); + g_pScriptVM->RegisterInstance( GetAmmoDef(), "AmmoDef" ); +#ifndef CLIENT_DLL + g_pScriptVM->RegisterInstance( &g_AI_SquadManager, "Squads" ); +#endif + +#ifdef USE_OLD_EVENT_DESCRIPTORS + CScriptGameEventListener::LoadAllEvents(); +#endif + + g_ScriptNetMsg->InitPostVM(); +} diff --git a/src/game/shared/mapbase/vscript_singletons.h b/src/game/shared/mapbase/vscript_singletons.h new file mode 100644 index 00000000..b20cc67c --- /dev/null +++ b/src/game/shared/mapbase/vscript_singletons.h @@ -0,0 +1,155 @@ +//========= Mapbase - https://github.com/mapbase-source/source-sdk-2013 ================= +// +// Purpose: See vscript_singletons.cpp +// +// $NoKeywords: $ +//============================================================================= + +#ifndef VSCRIPT_SINGLETONS_H +#define VSCRIPT_SINGLETONS_H +#ifdef _WIN32 +#pragma once +#endif + +void RegisterScriptSingletons(); + + + +#ifdef CLIENT_DLL +// usercmd +#define SCRIPT_NETMSG_DATA_SIZE ( ( 1 << 11 ) - 1 ) +#else +// usermsg +#define SCRIPT_NETMSG_DATA_SIZE MAX_USER_MSG_DATA +#endif + +#define SCRIPT_NETMSG_QUEUE_BITS 3 // determines the number of custom messages client can write to a usercmd +#define SCRIPT_NETMSG_HEADER_BITS (sizeof(unsigned int) << 3) +#define SCRIPT_NETMSG_STRING_SIZE 512 + + +#ifdef CLIENT_DLL +class CNetMsgScriptHelper : public CAutoGameSystem +#else +class CNetMsgScriptHelper +#endif +{ +#ifdef CLIENT_DLL +public: + bool m_bWriteReady; // dt ready to send +#endif + +private: +#ifdef GAME_DLL + bf_read *m_MsgIn; + CRecipientFilter m_filter; +#else + bf_read m_MsgIn; + unsigned int m_nQueueCount; + bool m_bWriteIgnore; +#endif + HSCRIPT m_Hooks; + bf_write m_MsgOut; + byte m_MsgData[ PAD_NUMBER( SCRIPT_NETMSG_DATA_SIZE, 4 ) ]; + +#ifdef CLIENT_DLL + int m_iLastBit; +#endif + +public: + CNetMsgScriptHelper() : m_Hooks(NULL) + +#ifdef CLIENT_DLL + , m_bWriteReady(0), m_bWriteIgnore(0), m_nQueueCount(0), m_iLastBit(0) +#else + , m_MsgIn(0) +#endif + + {} + +public: +#ifdef CLIENT_DLL + bool Init(); // IGameSystem + static void __MsgFunc_ScriptMsg( bf_read &msg ); +#endif + void LevelShutdownPreVM(); // Executed in CVScriptGameSystem + void InitPostVM(); + +#ifdef GAME_DLL + void ReceiveMessage( bf_read *msg, CBaseEntity *pPlayer ); +#else + void ReceiveMessage( bf_read &msg ); +#endif + void WriteToBuffer( bf_write *bf ); + +public: + inline void Reset(); + void Start( const char *msg ); +#ifdef GAME_DLL + void Send( HSCRIPT player, bool bReliable ); +#else + void Send(); +#endif + void Receive( const char *msg, HSCRIPT func ); + +#ifdef GAME_DLL + inline void DoSendUserMsg( CRecipientFilter *filter, int type ); + inline void DoSendEntityMsg( CBaseEntity *entity, bool reliable ); + + void SendUserMessage( HSCRIPT hPlayer, const char *msg, bool bReliable ); + void SendEntityMessage( HSCRIPT hEnt, bool bReliable ); +#else // CLIENT_DLL + void DispatchUserMessage( const char *msg ); +#endif + +public: + void WriteInt( int iValue, int bits ); + void WriteUInt( int iValue, int bits ); + void WriteByte( int iValue ); // 8 bit unsigned char + void WriteChar( int iValue ); // 8 bit char + void WriteShort( int iValue ); // 16 bit short + void WriteWord( int iValue ); // 16 bit unsigned short + void WriteLong( int iValue ); // 32 bit long + void WriteFloat( float flValue ); // 32 bit float + void WriteNormal( float flValue ); // 12 bit (1 + NORMAL_FRACTIONAL_BITS) + void WriteAngle( float flValue ); // 8 bit unsigned char + void WriteCoord( float flValue ); + void WriteVec3Coord( const Vector& rgflValue ); + void WriteVec3Normal( const Vector& rgflValue ); // 27 bit ( 3 + 2 * (1 + NORMAL_FRACTIONAL_BITS) ) + void WriteAngles( const QAngle& rgflValue ); + void WriteString( const char *sz ); + void WriteBool( bool bValue ); // 1 bit + void WriteEntity( HSCRIPT hEnt ); // 11 bit (entindex) + void WriteEHandle( HSCRIPT hEnt ); // 32 bit long + int ReadInt( int bits ); + int ReadUInt( int bits ); + int ReadByte(); + int ReadChar(); + int ReadShort(); + int ReadWord(); + int ReadLong(); + float ReadFloat(); + float ReadNormal(); + float ReadAngle(); + float ReadCoord(); + const Vector& ReadVec3Coord(); + const Vector& ReadVec3Normal(); + const QAngle& ReadAngles(); + const char* ReadString(); + bool ReadBool(); + HSCRIPT ReadEntity(); + HSCRIPT ReadEHandle(); + //int GetNumBitsLeft(); // unreliable on server because of usercmds. so just do away with it + int GetNumBitsWritten(); + +public: + static inline int Hash( const char *key ); +}; + +extern CNetMsgScriptHelper *g_ScriptNetMsg; + +#ifdef CLIENT_DLL +void VScriptSaveRestoreUtil_OnVMRestore(); +#endif + +#endif diff --git a/src/game/shared/mapbase/weapon_custom_scripted.cpp b/src/game/shared/mapbase/weapon_custom_scripted.cpp new file mode 100644 index 00000000..d0df0654 --- /dev/null +++ b/src/game/shared/mapbase/weapon_custom_scripted.cpp @@ -0,0 +1,715 @@ +//========= Mapbase - https://github.com/mapbase-source/source-sdk-2013 ============// +// +// Purpose: VScript-driven custom weapon class. +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" +#include "tier1/fmtstr.h" +#include "tier1/utlvector.h" +#include "weapon_custom_scripted.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//========================================================= +//========================================================= + +BEGIN_DATADESC( CWeaponCustomScripted ) + + DEFINE_AUTO_ARRAY( m_iszClientScripts, FIELD_CHARACTER ), + DEFINE_AUTO_ARRAY( m_iszWeaponScriptName, FIELD_CHARACTER ), + +END_DATADESC() + +IMPLEMENT_NETWORKCLASS_ALIASED( WeaponCustomScripted, DT_WeaponCustomScripted ) + +BEGIN_NETWORK_TABLE( CWeaponCustomScripted, DT_WeaponCustomScripted ) + +#ifdef CLIENT_DLL + RecvPropString( RECVINFO(m_iszClientScripts) ), + RecvPropString( RECVINFO(m_iszWeaponScriptName) ), +#else + SendPropString( SENDINFO(m_iszClientScripts) ), + SendPropString( SENDINFO(m_iszWeaponScriptName) ), +#endif + +END_NETWORK_TABLE() + +BEGIN_PREDICTION_DATA( CWeaponCustomScripted ) +END_PREDICTION_DATA() + +LINK_ENTITY_TO_CLASS( weapon_custom_scripted1, CWeaponCustomScripted ); + +// Only need one of the names +PRECACHE_WEAPON_REGISTER( weapon_custom_scripted1 ); + +//IMPLEMENT_ACTTABLE( CWeaponCustomScripted ); + +#define DEFINE_STATIC_HOOK( name ) ScriptHook_t CWeaponCustomScripted::g_Hook_##name + +DEFINE_STATIC_HOOK( HasAnyAmmo ); +DEFINE_STATIC_HOOK( HasPrimaryAmmo ); +DEFINE_STATIC_HOOK( HasSecondaryAmmo ); + +DEFINE_STATIC_HOOK( CanHolster ); +DEFINE_STATIC_HOOK( CanDeploy ); +DEFINE_STATIC_HOOK( Deploy ); +DEFINE_STATIC_HOOK( Holster ); + +DEFINE_STATIC_HOOK( ItemPreFrame ); +DEFINE_STATIC_HOOK( ItemPostFrame ); +DEFINE_STATIC_HOOK( ItemBusyFrame ); +DEFINE_STATIC_HOOK( ItemHolsterFrame ); +DEFINE_STATIC_HOOK( WeaponIdle ); +DEFINE_STATIC_HOOK( HandleFireOnEmpty ); + +DEFINE_STATIC_HOOK( CheckReload ); +DEFINE_STATIC_HOOK( FinishReload ); +DEFINE_STATIC_HOOK( AbortReload ); +DEFINE_STATIC_HOOK( Reload ); +DEFINE_STATIC_HOOK( Reload_NPC ); + +DEFINE_STATIC_HOOK( PrimaryAttack ); +DEFINE_STATIC_HOOK( SecondaryAttack ); + +DEFINE_STATIC_HOOK( GetPrimaryAttackActivity ); +DEFINE_STATIC_HOOK( GetSecondaryAttackActivity ); +DEFINE_STATIC_HOOK( GetDrawActivity ); +DEFINE_STATIC_HOOK( GetDefaultAnimSpeed ); + +DEFINE_STATIC_HOOK( GetBulletSpread ); +DEFINE_STATIC_HOOK( GetBulletSpreadForProficiency ); +DEFINE_STATIC_HOOK( GetFireRate ); +DEFINE_STATIC_HOOK( GetMinBurst ); +DEFINE_STATIC_HOOK( GetMaxBurst ); +DEFINE_STATIC_HOOK( GetMinRestTime ); +DEFINE_STATIC_HOOK( GetMaxRestTime ); + +DEFINE_STATIC_HOOK( AddViewKick ); + +#ifndef CLIENT_DLL +DEFINE_STATIC_HOOK( WeaponLOSCondition ); +DEFINE_STATIC_HOOK( WeaponRangeAttack1Condition ); +DEFINE_STATIC_HOOK( WeaponRangeAttack2Condition ); +DEFINE_STATIC_HOOK( WeaponMeleeAttack1Condition ); +DEFINE_STATIC_HOOK( WeaponMeleeAttack2Condition ); +#endif + +DEFINE_STATIC_HOOK( ActivityList ); + +#define DEFINE_SIMPLE_WEAPON_HOOK( name, returnType, description ) DEFINE_SIMPLE_SCRIPTHOOK( CWeaponCustomScripted::g_Hook_##name, #name, returnType, description ) +#define BEGIN_WEAPON_HOOK( name, returnType, description ) BEGIN_SCRIPTHOOK( CWeaponCustomScripted::g_Hook_##name, #name, returnType, description ) + +BEGIN_ENT_SCRIPTDESC( CWeaponCustomScripted, CBaseCombatWeapon, "Special weapon class with tons of hooks" ) + + DEFINE_SIMPLE_WEAPON_HOOK( HasAnyAmmo, FIELD_BOOLEAN, "Should return true if weapon has ammo" ) + DEFINE_SIMPLE_WEAPON_HOOK( HasPrimaryAmmo, FIELD_BOOLEAN, "Should return true if weapon has primary ammo" ) + DEFINE_SIMPLE_WEAPON_HOOK( HasSecondaryAmmo, FIELD_BOOLEAN, "Should return true if weapon has secondary ammo" ) + + DEFINE_SIMPLE_WEAPON_HOOK( CanHolster, FIELD_BOOLEAN, "Should return true if weapon can be holstered" ) + DEFINE_SIMPLE_WEAPON_HOOK( CanDeploy, FIELD_BOOLEAN, "Should return true if weapon can be deployed" ) + DEFINE_SIMPLE_WEAPON_HOOK( Deploy, FIELD_BOOLEAN, "Called when weapon is being deployed" ) + BEGIN_WEAPON_HOOK( Holster, FIELD_BOOLEAN, "Called when weapon is being holstered" ) + DEFINE_SCRIPTHOOK_PARAM( "switchingto", FIELD_HSCRIPT ) + END_SCRIPTHOOK() + + DEFINE_SIMPLE_WEAPON_HOOK( ItemPreFrame, FIELD_VOID, "Called each frame by the player PreThink" ) + DEFINE_SIMPLE_WEAPON_HOOK( ItemPostFrame, FIELD_VOID, "Called each frame by the player PostThink" ) + DEFINE_SIMPLE_WEAPON_HOOK( ItemBusyFrame, FIELD_VOID, "Called each frame by the player PostThink, if the player's not ready to attack yet" ) + DEFINE_SIMPLE_WEAPON_HOOK( ItemHolsterFrame, FIELD_VOID, "Called each frame by the player PreThink, if the weapon is holstered" ) + DEFINE_SIMPLE_WEAPON_HOOK( WeaponIdle, FIELD_VOID, "Called when no buttons pressed" ) + DEFINE_SIMPLE_WEAPON_HOOK( HandleFireOnEmpty, FIELD_VOID, "Called when they have the attack button down but they are out of ammo. The default implementation either reloads, switches weapons, or plays an empty sound." ) + + DEFINE_SIMPLE_WEAPON_HOOK( CheckReload, FIELD_VOID, "" ) + DEFINE_SIMPLE_WEAPON_HOOK( FinishReload, FIELD_VOID, "" ) + DEFINE_SIMPLE_WEAPON_HOOK( AbortReload, FIELD_VOID, "" ) + DEFINE_SIMPLE_WEAPON_HOOK( Reload, FIELD_BOOLEAN, "" ) + DEFINE_SIMPLE_WEAPON_HOOK( Reload_NPC, FIELD_VOID, "" ) + + DEFINE_SIMPLE_WEAPON_HOOK( PrimaryAttack, FIELD_VOID, "" ) + DEFINE_SIMPLE_WEAPON_HOOK( SecondaryAttack, FIELD_VOID, "" ) + + DEFINE_SIMPLE_WEAPON_HOOK( GetPrimaryAttackActivity, FIELD_VARIANT, "" ) + DEFINE_SIMPLE_WEAPON_HOOK( GetSecondaryAttackActivity, FIELD_VARIANT, "" ) + DEFINE_SIMPLE_WEAPON_HOOK( GetDrawActivity, FIELD_VARIANT, "" ) + DEFINE_SIMPLE_WEAPON_HOOK( GetDefaultAnimSpeed, FIELD_FLOAT, "" ) + + DEFINE_SIMPLE_WEAPON_HOOK( GetBulletSpread, FIELD_VECTOR, "" ) + BEGIN_WEAPON_HOOK( GetBulletSpreadForProficiency, FIELD_VECTOR, "Returns the bullet spread of a specific proficiency level. If this isn't defined, it will fall back to GetBulletSpread." ) + DEFINE_SCRIPTHOOK_PARAM( "proficiency", FIELD_INTEGER ) + END_SCRIPTHOOK() + DEFINE_SIMPLE_WEAPON_HOOK( GetFireRate, FIELD_FLOAT, "" ) + DEFINE_SIMPLE_WEAPON_HOOK( GetMinBurst, FIELD_INTEGER, "" ) + DEFINE_SIMPLE_WEAPON_HOOK( GetMaxBurst, FIELD_INTEGER, "" ) + DEFINE_SIMPLE_WEAPON_HOOK( GetMinRestTime, FIELD_FLOAT, "" ) + DEFINE_SIMPLE_WEAPON_HOOK( GetMaxRestTime, FIELD_FLOAT, "" ) + + DEFINE_SIMPLE_WEAPON_HOOK( AddViewKick, FIELD_VOID, "" ) + +#ifndef CLIENT_DLL + DEFINE_SIMPLE_WEAPON_HOOK( WeaponLOSCondition, FIELD_BOOLEAN, "" ) + DEFINE_SIMPLE_WEAPON_HOOK( WeaponRangeAttack1Condition, FIELD_INTEGER, "" ) + DEFINE_SIMPLE_WEAPON_HOOK( WeaponRangeAttack2Condition, FIELD_INTEGER, "" ) + DEFINE_SIMPLE_WEAPON_HOOK( WeaponMeleeAttack1Condition, FIELD_INTEGER, "" ) + DEFINE_SIMPLE_WEAPON_HOOK( WeaponMeleeAttack2Condition, FIELD_INTEGER, "" ) +#endif + + DEFINE_SIMPLE_WEAPON_HOOK( ActivityList, FIELD_HSCRIPT, "" ) + +END_SCRIPTDESC(); + +CWeaponCustomScripted::CWeaponCustomScripted() +{ + //m_fMinRange1 = 65; + //m_fMaxRange1 = 2048; + // + //m_fMinRange2 = 256; + //m_fMaxRange2 = 1024; + // + //m_nShotsFired = 0; + //m_nVentPose = -1; + // + //m_bAltFiresUnderwater = false; +} + +bool CWeaponCustomScripted::RunWeaponHook( ScriptHook_t &hook, HSCRIPT &cached, ScriptVariant_t *retVal, ScriptVariant_t *pArgs ) +{ + if ( !cached ) + { + if ( m_ScriptScope.IsInitialized() && hook.CanRunInScope( m_ScriptScope ) ) + { + cached = hook.m_hFunc; + } + } + + if (cached) + { + hook.m_hFunc = cached; + return hook.Call( m_ScriptScope, retVal, pArgs, false ); + } + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CWeaponCustomScripted::Spawn( void ) +{ + BaseClass::Spawn(); +} + +bool CWeaponCustomScripted::KeyValue( const char *szKeyName, const char *szValue ) +{ + if ( FStrEq( szKeyName, "vscripts_client" ) ) + { + Q_strcpy( m_iszClientScripts.GetForModify(), szValue ); + } + else if ( FStrEq( szKeyName, "weapondatascript_name" ) ) + { + Q_strcpy( m_iszWeaponScriptName.GetForModify(), szValue ); + } + else + { + return BaseClass::KeyValue( szKeyName, szValue ); + } + + return true; +} + +bool CWeaponCustomScripted::GetKeyValue( const char *szKeyName, char *szValue, int iMaxLen ) +{ + if ( FStrEq( szKeyName, "vscripts_client" ) ) + { + Q_snprintf( szValue, iMaxLen, "%s", m_iszClientScripts.Get() ); + return true; + } + else if ( FStrEq( szKeyName, "weapondatascript_name" ) ) + { + Q_snprintf( szValue, iMaxLen, "%s", m_iszWeaponScriptName.Get() ); + return true; + } + return BaseClass::GetKeyValue( szKeyName, szValue, iMaxLen ); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +#define SIMPLE_VOID_OVERRIDE( name, pArgs ) ScriptVariant_t retVal; \ + if (RunWeaponHook( g_Hook_##name, m_Func_##name, &retVal, pArgs ) && retVal.m_bool == false) \ + return; + +#define SIMPLE_BOOL_OVERRIDE( name, pArgs ) ScriptVariant_t retVal; \ + if (RunWeaponHook( g_Hook_##name, m_Func_##name, &retVal, pArgs ) && retVal.m_type == FIELD_BOOLEAN) \ + return retVal.m_bool; + +#define SIMPLE_FLOAT_OVERRIDE( name, pArgs ) ScriptVariant_t retVal; \ + if (RunWeaponHook( g_Hook_##name, m_Func_##name, &retVal, pArgs ) && retVal.m_type == FIELD_FLOAT) \ + return retVal.m_float; + +#define SIMPLE_INT_OVERRIDE( name, pArgs ) ScriptVariant_t retVal; \ + if (RunWeaponHook( g_Hook_##name, m_Func_##name, &retVal, pArgs ) && retVal.m_type == FIELD_INTEGER) \ + return retVal.m_int; + +#define SIMPLE_VECTOR_OVERRIDE( name, pArgs ) ScriptVariant_t retVal; \ + if (RunWeaponHook( g_Hook_##name, m_Func_##name, &retVal, pArgs ) && retVal.m_type == FIELD_VECTOR) \ + return *retVal.m_pVector; + +#define SIMPLE_VECTOR_REF_OVERRIDE( name, pArgs ) ScriptVariant_t retVal; \ + if (RunWeaponHook( g_Hook_##name, m_Func_##name, &retVal, pArgs ) && retVal.m_type == FIELD_VECTOR) \ + { \ + static Vector vec = *retVal.m_pVector; \ + return vec; \ + } + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CWeaponCustomScripted::HasAnyAmmo( void ) +{ + SIMPLE_BOOL_OVERRIDE( HasAnyAmmo, NULL ); + + return BaseClass::HasAnyAmmo(); +} + +bool CWeaponCustomScripted::HasPrimaryAmmo( void ) +{ + SIMPLE_BOOL_OVERRIDE( HasPrimaryAmmo, NULL ); + + return BaseClass::HasPrimaryAmmo(); +} + +bool CWeaponCustomScripted::HasSecondaryAmmo( void ) +{ + SIMPLE_BOOL_OVERRIDE( HasSecondaryAmmo, NULL ); + + return BaseClass::HasSecondaryAmmo(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CWeaponCustomScripted::CanHolster( void ) +{ + SIMPLE_BOOL_OVERRIDE( CanHolster, NULL ); + + return BaseClass::CanHolster(); +} + +bool CWeaponCustomScripted::CanDeploy( void ) +{ + SIMPLE_BOOL_OVERRIDE( CanDeploy, NULL ); + + return BaseClass::CanDeploy(); +} + +bool CWeaponCustomScripted::Deploy( void ) +{ + SIMPLE_BOOL_OVERRIDE( Deploy, NULL ); + + return BaseClass::Deploy(); +} + +bool CWeaponCustomScripted::Holster( CBaseCombatWeapon *pSwitchingTo ) +{ + ScriptVariant_t pArgs[] = { ToHScript( pSwitchingTo ) }; + SIMPLE_BOOL_OVERRIDE( Holster, pArgs ); + + return BaseClass::Holster( pSwitchingTo ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CWeaponCustomScripted::ItemPreFrame( void ) +{ + SIMPLE_VOID_OVERRIDE( ItemPreFrame, NULL ); + + BaseClass::ItemPreFrame(); +} + +void CWeaponCustomScripted::ItemPostFrame( void ) +{ + SIMPLE_VOID_OVERRIDE( ItemPostFrame, NULL ); + + BaseClass::ItemPostFrame(); +} + +void CWeaponCustomScripted::ItemBusyFrame( void ) +{ + SIMPLE_VOID_OVERRIDE( ItemBusyFrame, NULL ); + + BaseClass::ItemBusyFrame(); +} + +void CWeaponCustomScripted::ItemHolsterFrame( void ) +{ + SIMPLE_VOID_OVERRIDE( ItemHolsterFrame, NULL ); + + BaseClass::ItemHolsterFrame(); +} + +void CWeaponCustomScripted::WeaponIdle( void ) +{ + SIMPLE_VOID_OVERRIDE( WeaponIdle, NULL ); + + BaseClass::WeaponIdle(); +} + +void CWeaponCustomScripted::HandleFireOnEmpty( void ) +{ + SIMPLE_VOID_OVERRIDE( HandleFireOnEmpty, NULL ); + + BaseClass::HandleFireOnEmpty(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CWeaponCustomScripted::CheckReload( void ) +{ + SIMPLE_VOID_OVERRIDE( CheckReload, NULL ); + + BaseClass::CheckReload(); +} + +void CWeaponCustomScripted::FinishReload( void ) +{ + SIMPLE_VOID_OVERRIDE( FinishReload, NULL ); + + BaseClass::FinishReload(); +} + +void CWeaponCustomScripted::AbortReload( void ) +{ + SIMPLE_VOID_OVERRIDE( AbortReload, NULL ); + + BaseClass::AbortReload(); +} + +bool CWeaponCustomScripted::Reload( void ) +{ + SIMPLE_BOOL_OVERRIDE( Reload, NULL ); + + return BaseClass::Reload(); +} + +void CWeaponCustomScripted::Reload_NPC( bool bPlaySound ) +{ + ScriptVariant_t pArgs[] = { bPlaySound }; + SIMPLE_VOID_OVERRIDE( Reload_NPC, pArgs ); + + BaseClass::Reload_NPC(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CWeaponCustomScripted::PrimaryAttack( void ) +{ + SIMPLE_VOID_OVERRIDE( PrimaryAttack, NULL ); + + BaseClass::PrimaryAttack(); +} + +void CWeaponCustomScripted::SecondaryAttack( void ) +{ + SIMPLE_VOID_OVERRIDE( SecondaryAttack, NULL ); + + BaseClass::SecondaryAttack(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +#define ACTIVITY_FUNC_OVERRIDE( name ) ScriptVariant_t retVal; \ + if (RunWeaponHook( g_Hook_##name, m_Func_##name, &retVal ) && !retVal.IsNull()) \ + { \ + if (retVal.m_type == FIELD_INTEGER) \ + { \ + Activity activity = (Activity)retVal.m_int; \ + if (activity != ACT_INVALID) \ + return (Activity)retVal.m_int; \ + } \ + else \ + { \ + Activity activity = (Activity)LookupActivity( retVal.m_pszString ); \ + if (activity != ACT_INVALID) \ + return activity; \ + } \ + } + +Activity CWeaponCustomScripted::GetPrimaryAttackActivity( void ) +{ + ACTIVITY_FUNC_OVERRIDE( GetPrimaryAttackActivity ); + + return BaseClass::GetPrimaryAttackActivity(); +} + +Activity CWeaponCustomScripted::GetSecondaryAttackActivity( void ) +{ + ACTIVITY_FUNC_OVERRIDE( GetSecondaryAttackActivity ); + + return BaseClass::GetSecondaryAttackActivity(); +} + +Activity CWeaponCustomScripted::GetDrawActivity( void ) +{ + ACTIVITY_FUNC_OVERRIDE( GetDrawActivity ); + + return BaseClass::GetDrawActivity(); +} + +float CWeaponCustomScripted::GetDefaultAnimSpeed( void ) +{ + SIMPLE_FLOAT_OVERRIDE( GetDefaultAnimSpeed, NULL ); + + return BaseClass::GetDefaultAnimSpeed(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +const Vector& CWeaponCustomScripted::GetBulletSpread( void ) +{ + SIMPLE_VECTOR_REF_OVERRIDE( GetBulletSpread, NULL ); + + // HACKHACK: Need to skip CBaseHLCombatWeapon here to recognize this overload for some reason + return CBaseCombatWeapon::GetBulletSpread(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +Vector CWeaponCustomScripted::GetBulletSpread( WeaponProficiency_t proficiency ) +{ + ScriptVariant_t pArgs[] = { (int)proficiency }; + SIMPLE_VECTOR_OVERRIDE( GetBulletSpreadForProficiency, pArgs ); + + return BaseClass::GetBulletSpread( proficiency ); +} + +float CWeaponCustomScripted::GetFireRate( void ) +{ + SIMPLE_FLOAT_OVERRIDE( GetFireRate, NULL ); + + return BaseClass::GetFireRate(); +} + +int CWeaponCustomScripted::GetMinBurst( void ) +{ + SIMPLE_INT_OVERRIDE( GetMinBurst, NULL ); + + return BaseClass::GetMinBurst(); +} + +int CWeaponCustomScripted::GetMaxBurst( void ) +{ + SIMPLE_INT_OVERRIDE( GetMaxBurst, NULL ); + + return BaseClass::GetMaxBurst(); +} + +float CWeaponCustomScripted::GetMinRestTime( void ) +{ + SIMPLE_FLOAT_OVERRIDE( GetMinRestTime, NULL ); + + return BaseClass::GetMinRestTime(); +} + +float CWeaponCustomScripted::GetMaxRestTime( void ) +{ + SIMPLE_FLOAT_OVERRIDE( GetMaxRestTime, NULL ); + + return BaseClass::GetMaxRestTime(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CWeaponCustomScripted::AddViewKick( void ) +{ + SIMPLE_VOID_OVERRIDE( AddViewKick, NULL ); + + return BaseClass::AddViewKick(); +} + +#ifndef CLIENT_DLL +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CWeaponCustomScripted::WeaponLOSCondition( const Vector &ownerPos, const Vector &targetPos, bool bSetConditions ) +{ + ScriptVariant_t pArgs[] = { ownerPos, targetPos, bSetConditions }; + SIMPLE_BOOL_OVERRIDE( WeaponLOSCondition, pArgs ); + + return BaseClass::WeaponLOSCondition( ownerPos, targetPos, bSetConditions ); +} + +int CWeaponCustomScripted::WeaponRangeAttack1Condition( float flDot, float flDist ) +{ + ScriptVariant_t pArgs[] = { flDot, flDist }; + SIMPLE_INT_OVERRIDE( WeaponRangeAttack1Condition, pArgs ); + + return BaseClass::WeaponRangeAttack1Condition( flDot, flDist ); +} + +int CWeaponCustomScripted::WeaponRangeAttack2Condition( float flDot, float flDist ) +{ + ScriptVariant_t pArgs[] = { flDot, flDist }; + SIMPLE_INT_OVERRIDE( WeaponRangeAttack2Condition, pArgs ); + + return BaseClass::WeaponRangeAttack2Condition( flDot, flDist ); +} + +int CWeaponCustomScripted::WeaponMeleeAttack1Condition( float flDot, float flDist ) +{ + ScriptVariant_t pArgs[] = { flDot, flDist }; + SIMPLE_INT_OVERRIDE( WeaponMeleeAttack1Condition, pArgs ); + + return BaseClass::WeaponMeleeAttack1Condition( flDot, flDist ); +} + +int CWeaponCustomScripted::WeaponMeleeAttack2Condition( float flDot, float flDist ) +{ + ScriptVariant_t pArgs[] = { flDot, flDist }; + SIMPLE_INT_OVERRIDE( WeaponMeleeAttack2Condition, pArgs ); + + return BaseClass::WeaponMeleeAttack2Condition( flDot, flDist ); +} + +struct VScriptWeaponCustomData_s +{ + char cScripts[256]; + + bool Parse(KeyValues* pKVWeapon) + { + Q_strncpy(cScripts, pKVWeapon->GetString("vscript_file"), 256); + return true; + } +}; + +DEFINE_CUSTOM_WEAPON_FACTORY(vscript, CWeaponCustomScripted, VScriptWeaponCustomData_s); +void CWeaponCustomScripted::InitCustomWeaponFromData(const void* pData, const char* pszWeaponScript) +{ + Q_FileBase(pszWeaponScript, m_iszWeaponScriptName.GetForModify(), 256); + Q_strncpy(m_iszClientScripts.GetForModify(), static_cast (pData)->cScripts, 256); +} + +extern ConVar sv_script_think_interval; +#else +void CWeaponCustomScripted::OnDataChanged(DataUpdateType_t type) +{ + BaseClass::OnDataChanged(type); + + if (!m_ScriptScope.IsInitialized()) + { + RunVScripts(); + } +} +#endif + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +acttable_t *CWeaponCustomScripted::ActivityList( int &iActivityCount ) +{ + // TODO + + return BaseClass::ActivityList( iActivityCount ); +} + +void CWeaponCustomScripted::RunVScripts() +{ +#ifdef CLIENT_DLL + if (m_iszClientScripts[0] != '\0' && ValidateScriptScope()) + { + RunScriptFile(m_iszClientScripts); + } +#else + if (m_iszVScripts == NULL_STRING && m_iszClientScripts[0] == '\0') + { + return; + } + +#ifdef MAPBASE_VSCRIPT + if (g_pScriptVM == NULL) + { + return; + } +#endif + + ValidateScriptScope(); + + // All functions we want to have call chained instead of overwritten + // by other scripts in this entities list. + static const char* sCallChainFunctions[] = + { + "OnPostSpawn", + "Precache" + }; + + ScriptLanguage_t language = g_pScriptVM->GetLanguage(); + + // Make a call chainer for each in this entities scope + for (int j = 0; j < ARRAYSIZE(sCallChainFunctions); ++j) + { + + if (language == SL_PYTHON) + { + // UNDONE - handle call chaining in python + ; + } + else if (language == SL_SQUIRREL) + { + //TODO: For perf, this should be precompiled and the %s should be passed as a parameter + HSCRIPT hCreateChainScript = g_pScriptVM->CompileScript(CFmtStr("%sCallChain <- CSimpleCallChainer(\"%s\", self.GetScriptScope(), true)", sCallChainFunctions[j], sCallChainFunctions[j])); + g_pScriptVM->Run(hCreateChainScript, (HSCRIPT)m_ScriptScope); + } + } + + CUtlStringList szScripts; + if (m_iszVScripts != NULL_STRING) + { + V_SplitString(STRING(m_iszVScripts), " ", szScripts); + } + + if (m_iszClientScripts[0] != '\0') + { + szScripts.AddToHead(strdup(m_iszClientScripts.Get())); + } + + for (int i = 0; i < szScripts.Count(); i++) + { +#ifdef MAPBASE + CGMsg(0, CON_GROUP_VSCRIPT, "%s executing script: %s\n", GetDebugName(), szScripts[i]); +#else + Log("%s executing script: %s\n", GetDebugName(), szScripts[i]); +#endif + + RunScriptFile(szScripts[i], IsWorld()); + + for (int j = 0; j < ARRAYSIZE(sCallChainFunctions); ++j) + { + if (language == SL_PYTHON) + { + // UNDONE - handle call chaining in python + ; + } + else if (language == SL_SQUIRREL) + { + //TODO: For perf, this should be precompiled and the %s should be passed as a parameter. + HSCRIPT hRunPostScriptExecute = g_pScriptVM->CompileScript(CFmtStr("%sCallChain.PostScriptExecute()", sCallChainFunctions[j])); + g_pScriptVM->Run(hRunPostScriptExecute, (HSCRIPT)m_ScriptScope); + } + } + } + + if (m_iszScriptThinkFunction != NULL_STRING) + { + SetContextThink(&CBaseEntity::ScriptThink, gpGlobals->curtime + sv_script_think_interval.GetFloat(), "ScriptThink"); + } +#endif +} \ No newline at end of file diff --git a/src/game/shared/mapbase/weapon_custom_scripted.h b/src/game/shared/mapbase/weapon_custom_scripted.h new file mode 100644 index 00000000..06b5c5be --- /dev/null +++ b/src/game/shared/mapbase/weapon_custom_scripted.h @@ -0,0 +1,213 @@ +//========= Mapbase - https://github.com/mapbase-source/source-sdk-2013 ================= +// +// Purpose: VScript-driven custom weapon class. +// +// $NoKeywords: $ +//============================================================================= + +#ifndef WEAPON_CUSTOM_SCRIPTED_H +#define WEAPON_CUSTOM_SCRIPTED_H +#ifdef _WIN32 +#pragma once +#endif + +#include "basecombatweapon_shared.h" +#ifdef CLIENT_DLL +#include "vscript_client.h" +#else +#include "mapbase/custom_weapon_factory.h" +#endif + +// The base class of the scripted weapon is game-specific. +#if defined(HL2_DLL) || defined(HL2_CLIENT_DLL) +#include "basehlcombatweapon_shared.h" +#define SCRIPTED_WEAPON_DERIVED_FROM CBaseHLCombatWeapon +#else +#define SCRIPTED_WEAPON_DERIVED_FROM CBaseCombatWeapon +#endif + +#ifdef CLIENT_DLL +#define CWeaponCustomScripted C_WeaponCustomScripted +#endif + +#define DECLARE_CACHED_HOOK(name) static ScriptHook_t g_Hook_##name; \ + HSCRIPT m_Func_##name; + +class CWeaponCustomScripted : public SCRIPTED_WEAPON_DERIVED_FROM +#ifndef CLIENT_DLL + , public ICustomWeapon +#endif // !CLIENT_DLL +{ +public: + DECLARE_CLASS( CWeaponCustomScripted, SCRIPTED_WEAPON_DERIVED_FROM ); + DECLARE_NETWORKCLASS(); + DECLARE_PREDICTABLE(); + + CWeaponCustomScripted(); + + bool RunWeaponHook( ScriptHook_t &hook, HSCRIPT &cached, ScriptVariant_t *retVal = NULL, ScriptVariant_t *pArgs = NULL ); + + bool KeyValue( const char *szKeyName, const char *szValue ); + bool GetKeyValue( const char *szKeyName, char *szValue, int iMaxLen ); + + void RunVScripts(); + + // Base script has a function for this + //void Precache( void ); + + void Spawn( void ); + + bool IsPredicted( void ) const { return m_iszClientScripts[0] != '\0'; } + + const char* GetWeaponScriptName() { return m_iszWeaponScriptName[0] != '\0' ? m_iszWeaponScriptName : BaseClass::GetWeaponScriptName(); } + + // Weapon selection + bool HasAnyAmmo( void ); // Returns true is weapon has ammo + bool HasPrimaryAmmo( void ); // Returns true is weapon has ammo + bool HasSecondaryAmmo( void ); // Returns true is weapon has ammo + + bool CanHolster( void ); // returns true if the weapon can be holstered + bool CanDeploy( void ); // return true if the weapon's allowed to deploy + bool Deploy( void ); // returns true is deploy was successful + bool Holster( CBaseCombatWeapon *pSwitchingTo = NULL ); + + // Weapon behaviour + void ItemPreFrame( void ); // called each frame by the player PreThink + void ItemPostFrame( void ); // called each frame by the player PostThink + void ItemBusyFrame( void ); // called each frame by the player PostThink, if the player's not ready to attack yet + void ItemHolsterFrame( void ); // called each frame by the player PreThink, if the weapon is holstered + void WeaponIdle( void ); // called when no buttons pressed + void HandleFireOnEmpty(); // Called when they have the attack button down + + // Reloading + void CheckReload( void ); + void FinishReload( void ); + void AbortReload( void ); + bool Reload( void ); + void Reload_NPC( bool bPlaySound = true ); + + // Weapon firing + void PrimaryAttack( void ); // do "+ATTACK" + void SecondaryAttack( void ); // do "+ATTACK2" + + // Firing animations + Activity GetPrimaryAttackActivity( void ); + Activity GetSecondaryAttackActivity( void ); + Activity GetDrawActivity( void ); + float GetDefaultAnimSpeed( void ); + + // Bullet launch information + const Vector& GetBulletSpread( void ); + Vector GetBulletSpread( WeaponProficiency_t proficiency ); + float GetFireRate( void ); + int GetMinBurst(); + int GetMaxBurst(); + float GetMinRestTime(); + float GetMaxRestTime(); + + void AddViewKick( void ); + +#ifndef CLIENT_DLL + bool WeaponLOSCondition( const Vector &ownerPos, const Vector &targetPos, bool bSetConditions ); + int WeaponRangeAttack1Condition( float flDot, float flDist ); + int WeaponRangeAttack2Condition( float flDot, float flDist ); + int WeaponMeleeAttack1Condition( float flDot, float flDist ); + int WeaponMeleeAttack2Condition( float flDot, float flDist ); + + // Inherited via ICustomWeapon + virtual void InitCustomWeaponFromData(const void* pData, const char* pszWeaponScript); +#else + void OnDataChanged(DataUpdateType_t type); +#endif + + ALLOW_SCRIPT_ACCESS(); +private: + + // Weapon selection + DECLARE_CACHED_HOOK( HasAnyAmmo ); + DECLARE_CACHED_HOOK( HasPrimaryAmmo ); + DECLARE_CACHED_HOOK( HasSecondaryAmmo ); + + DECLARE_CACHED_HOOK( CanHolster ); + DECLARE_CACHED_HOOK( CanDeploy ); + DECLARE_CACHED_HOOK( Deploy ); + DECLARE_CACHED_HOOK( Holster ); + + // Weapon behaviour + DECLARE_CACHED_HOOK( ItemPreFrame ); + DECLARE_CACHED_HOOK( ItemPostFrame ); + DECLARE_CACHED_HOOK( ItemBusyFrame ); + DECLARE_CACHED_HOOK( ItemHolsterFrame ); + DECLARE_CACHED_HOOK( WeaponIdle ); + DECLARE_CACHED_HOOK( HandleFireOnEmpty ); + + // Reloading + DECLARE_CACHED_HOOK( CheckReload ); + DECLARE_CACHED_HOOK( FinishReload ); + DECLARE_CACHED_HOOK( AbortReload ); + DECLARE_CACHED_HOOK( Reload ); + DECLARE_CACHED_HOOK( Reload_NPC ); + + // Weapon firing + DECLARE_CACHED_HOOK( PrimaryAttack ); + DECLARE_CACHED_HOOK( SecondaryAttack ); + + // Firing animations + DECLARE_CACHED_HOOK( GetPrimaryAttackActivity ); + DECLARE_CACHED_HOOK( GetSecondaryAttackActivity ); + DECLARE_CACHED_HOOK( GetDrawActivity ); + DECLARE_CACHED_HOOK( GetDefaultAnimSpeed ); + + // Bullet launch information + DECLARE_CACHED_HOOK( GetBulletSpread ); + DECLARE_CACHED_HOOK( GetBulletSpreadForProficiency ); + DECLARE_CACHED_HOOK( GetFireRate ); + DECLARE_CACHED_HOOK( GetMinBurst ); + DECLARE_CACHED_HOOK( GetMaxBurst ); + DECLARE_CACHED_HOOK( GetMinRestTime ); + DECLARE_CACHED_HOOK( GetMaxRestTime ); + + DECLARE_CACHED_HOOK( AddViewKick ); + +#ifndef CLIENT_DLL + DECLARE_CACHED_HOOK( WeaponLOSCondition ); + DECLARE_CACHED_HOOK( WeaponRangeAttack1Condition ); + DECLARE_CACHED_HOOK( WeaponRangeAttack2Condition ); + DECLARE_CACHED_HOOK( WeaponMeleeAttack1Condition ); + DECLARE_CACHED_HOOK( WeaponMeleeAttack2Condition ); +#endif + + DECLARE_CACHED_HOOK( ActivityList ); + +private: + + CNetworkString( m_iszClientScripts, 256 ); + CNetworkString( m_iszWeaponScriptName, 256 ); + +protected: + + DECLARE_ACTTABLE(); + DECLARE_DATADESC(); + DECLARE_ENT_SCRIPTDESC(); +}; + +/* +class CWeaponCustomScripted1 : public CWeaponCustomScripted +{ + DECLARE_PREDICTABLE(); +}; +class CWeaponCustomScripted2 : public CWeaponCustomScripted +{ + DECLARE_PREDICTABLE(); +}; +class CWeaponCustomScripted3 : public CWeaponCustomScripted +{ + DECLARE_PREDICTABLE(); +}; +class CWeaponCustomScripted4 : public CWeaponCustomScripted +{ + DECLARE_PREDICTABLE(); +}; +*/ + +#endif diff --git a/src/game/shared/movevars_shared.cpp b/src/game/shared/movevars_shared.cpp index df89471f..843e4d96 100644 --- a/src/game/shared/movevars_shared.cpp +++ b/src/game/shared/movevars_shared.cpp @@ -36,7 +36,7 @@ float GetCurrentGravity( void ) ConVar sv_gravity ( "sv_gravity", DEFAULT_GRAVITY_STRING, FCVAR_NOTIFY | FCVAR_REPLICATED, "World gravity." ); -#if defined( DOD_DLL ) || defined( CSTRIKE_DLL ) || defined( HL1MP_DLL ) +#if defined( DOD_DLL ) || defined( CSTRIKE_DLL ) || defined( HL1MP_DLL ) || defined( MAPBASE ) ConVar sv_stopspeed ( "sv_stopspeed","100", FCVAR_NOTIFY | FCVAR_REPLICATED, "Minimum stopping speed when on ground." ); #else ConVar sv_stopspeed ( "sv_stopspeed","100", FCVAR_NOTIFY | FCVAR_REPLICATED, "Minimum stopping speed when on ground." ); @@ -48,7 +48,7 @@ ConVar sv_specaccelerate( "sv_specaccelerate", "5", FCVAR_NOTIFY | FCVAR_ARCHIVE ConVar sv_specspeed ( "sv_specspeed", "3", FCVAR_ARCHIVE | FCVAR_NOTIFY | FCVAR_REPLICATED); ConVar sv_specnoclip ( "sv_specnoclip", "1", FCVAR_ARCHIVE | FCVAR_NOTIFY | FCVAR_REPLICATED); -#if defined( CSTRIKE_DLL ) || defined( HL1MP_DLL ) +#if defined( CSTRIKE_DLL ) || defined( HL1MP_DLL ) || defined( MAPBASE ) ConVar sv_maxspeed ( "sv_maxspeed", "320", FCVAR_NOTIFY | FCVAR_REPLICATED); #else ConVar sv_maxspeed ( "sv_maxspeed", "320", FCVAR_NOTIFY | FCVAR_REPLICATED); @@ -58,7 +58,7 @@ ConVar sv_maxspeed ( "sv_maxspeed", "320", FCVAR_NOTIFY | FCVAR_REPLICATED); ConVar sv_accelerate ( "sv_accelerate", "7", FCVAR_NOTIFY | FCVAR_REPLICATED); #else -#if defined( CSTRIKE_DLL ) || defined( HL1MP_DLL ) +#if defined( CSTRIKE_DLL ) || defined( HL1MP_DLL ) || defined( MAPBASE ) ConVar sv_accelerate ( "sv_accelerate", "10", FCVAR_NOTIFY | FCVAR_REPLICATED); #else ConVar sv_accelerate ( "sv_accelerate", "10", FCVAR_NOTIFY | FCVAR_REPLICATED); @@ -66,7 +66,7 @@ ConVar sv_maxspeed ( "sv_maxspeed", "320", FCVAR_NOTIFY | FCVAR_REPLICATED); #endif//_XBOX -#if defined( CSTRIKE_DLL ) || defined( HL1MP_DLL ) +#if defined( CSTRIKE_DLL ) || defined( HL1MP_DLL ) || defined( MAPBASE ) ConVar sv_airaccelerate( "sv_airaccelerate", "10", FCVAR_NOTIFY | FCVAR_REPLICATED); ConVar sv_wateraccelerate( "sv_wateraccelerate", "10", FCVAR_NOTIFY | FCVAR_REPLICATED); ConVar sv_waterfriction( "sv_waterfriction", "1", FCVAR_NOTIFY | FCVAR_REPLICATED); @@ -82,13 +82,13 @@ ConVar sv_rollspeed ( "sv_rollspeed", "200", FCVAR_NOTIFY | FCVAR_REPLICATED); ConVar sv_rollangle ( "sv_rollangle", "0", FCVAR_NOTIFY | FCVAR_REPLICATED, "Max view roll angle"); #endif // CSTRIKE_DLL -#if defined( DOD_DLL ) || defined( CSTRIKE_DLL ) || defined( HL1MP_DLL ) +#if defined( DOD_DLL ) || defined( CSTRIKE_DLL ) || defined( HL1MP_DLL ) || defined( MAPBASE ) ConVar sv_friction ( "sv_friction","4", FCVAR_NOTIFY | FCVAR_REPLICATED, "World friction." ); #else ConVar sv_friction ( "sv_friction","4", FCVAR_NOTIFY | FCVAR_REPLICATED, "World friction." ); #endif // DOD_DLL || CSTRIKE_DLL -#if defined( CSTRIKE_DLL ) || defined( HL1MP_DLL ) +#if defined( CSTRIKE_DLL ) || defined( HL1MP_DLL ) || defined( MAPBASE ) ConVar sv_bounce ( "sv_bounce","0", FCVAR_NOTIFY | FCVAR_REPLICATED, "Bounce multiplier for when physically simulated objects collide with other objects." ); ConVar sv_maxvelocity ( "sv_maxvelocity","3500", FCVAR_REPLICATED, "Maximum speed any ballistically moving object is allowed to attain per axis." ); ConVar sv_stepsize ( "sv_stepsize","18", FCVAR_NOTIFY | FCVAR_REPLICATED ); diff --git a/src/game/shared/multiplay_gamerules.cpp b/src/game/shared/multiplay_gamerules.cpp index 73af2f69..6ed275b2 100644 --- a/src/game/shared/multiplay_gamerules.cpp +++ b/src/game/shared/multiplay_gamerules.cpp @@ -215,7 +215,8 @@ int CMultiplayRules::Damage_GetShouldNotBleed( void ) bool CMultiplayRules::Damage_IsTimeBased( int iDmgType ) { // Damage types that are time-based. - return ( ( iDmgType & ( DMG_PARALYZE | DMG_NERVEGAS | DMG_POISON | DMG_RADIATION | DMG_DROWNRECOVER | DMG_ACID | DMG_SLOWBURN ) ) != 0 ); + //Tony; fixed. return Damage_GetTimeBased instead of checking them directly. + return ( ( iDmgType & Damage_GetTimeBased() ) != 0 ); } //----------------------------------------------------------------------------- @@ -379,7 +380,9 @@ ConVarRef suitcharger( "sk_suitcharger" ); if ( g_fGameOver ) // someone else quit the game already { - ChangeLevel(); // intermission is over + // Tony; wait for intermission to end + if ( m_flIntermissionEndTime && ( m_flIntermissionEndTime < gpGlobals->curtime ) ) + ChangeLevel(); // intermission is over return; } diff --git a/src/game/shared/physics_shared.cpp b/src/game/shared/physics_shared.cpp index c6539bae..74eaadfb 100644 --- a/src/game/shared/physics_shared.cpp +++ b/src/game/shared/physics_shared.cpp @@ -1003,7 +1003,11 @@ void PhysFrictionSound( CBaseEntity *pEntity, IPhysicsObject *pObject, float ene if ( psurf->sounds.scrapeSmooth && phit->audio.roughnessFactor < psurf->audio.roughThreshold ) { soundName = psurf->sounds.scrapeSmooth; +#ifdef MAPBASE + soundHandle = &psurf->soundhandles.scrapeSmooth; +#else soundHandle = &psurf->soundhandles.scrapeRough; +#endif } const char *pSoundName = physprops->GetString( soundName ); diff --git a/src/game/shared/playernet_vars.h b/src/game/shared/playernet_vars.h index fb93cfe7..e35a5ff7 100644 --- a/src/game/shared/playernet_vars.h +++ b/src/game/shared/playernet_vars.h @@ -99,6 +99,16 @@ struct sky3dparams_t // 3d skybox camera data CNetworkVar( int, scale ); CNetworkVector( origin ); +#ifdef MAPBASE + // Skybox angle support + CNetworkQAngle( angles ); + + // Skybox entity-based option + CNetworkHandle( CBaseEntity, skycamera ); + + // Sky clearcolor + CNetworkColor32( skycolor ); +#endif CNetworkVar( int, area ); // 3d skybox fog data @@ -120,5 +130,50 @@ struct audioparams_t CNetworkVar( int, entIndex ); // the entity setting the soundscape }; +//Tony; new tonemap information. +// In single player the values are coped directly from the single env_tonemap_controller entity. +// This will allow the controller to work as it always did. +// That way nothing in ep2 will break. With these new params, the controller can properly be used in mp. + + +// Map specific objectives, such as blowing out a wall ( and bringing in more light ) +// can still change values on a particular controller as necessary via inputs, but the +// effects will not directly affect any players who are referencing this controller +// unless the option to update on inputs is set. ( otherwise the values are simply cached +// and changes only take effect when the players controller target is changed ) + +struct tonemap_params_t +{ + DECLARE_CLASS_NOBASE( tonemap_params_t ); + DECLARE_EMBEDDED_NETWORKVAR(); + +#ifndef CLIENT_DLL + DECLARE_SIMPLE_DATADESC(); +#endif + tonemap_params_t() + { + m_flAutoExposureMin = -1.0f; + m_flAutoExposureMax = -1.0f; + m_flTonemapScale = -1.0f; + m_flBloomScale = -1.0f; + m_flTonemapRate = -1.0f; + } + //Tony; all of these are initialized to -1! + CNetworkVar( float, m_flTonemapScale ); + CNetworkVar( float, m_flTonemapRate ); + CNetworkVar( float, m_flBloomScale ); + + CNetworkVar( float, m_flAutoExposureMin ); + CNetworkVar( float, m_flAutoExposureMax ); + +// BLEND TODO +// +// //Tony; Time it takes for a blend to finish, default to 0; this is for the the effect of InputBlendTonemapScale. +// //When +// CNetworkVar( float, m_flBlendTime ); + + //Tony; these next 4 variables do not have to be networked; but I want to update them on the client whenever m_flBlendTime changes. + //TODO +}; #endif // PLAYERNET_VARS_H diff --git a/src/game/shared/point_posecontroller.cpp b/src/game/shared/point_posecontroller.cpp index 660d6248..43404fc8 100644 --- a/src/game/shared/point_posecontroller.cpp +++ b/src/game/shared/point_posecontroller.cpp @@ -326,6 +326,13 @@ void CPoseController::InputGetFMod( inputdata_t &inputdata ) m_fFModAmplitude.Get() ); } +#ifdef MAPBASE +void CPoseController::InputSetTarget( inputdata_t &inputdata ) +{ + SetPropName( inputdata.value.String() ); +} +#endif + #else //#ifndef CLIENT_DLL //----------------------------------------------------------------------------- diff --git a/src/game/shared/point_posecontroller.h b/src/game/shared/point_posecontroller.h index 0d53874e..f8adcc01 100644 --- a/src/game/shared/point_posecontroller.h +++ b/src/game/shared/point_posecontroller.h @@ -73,6 +73,10 @@ public: void InputRandomizeFMod( inputdata_t &inputdata ); void InputGetFMod( inputdata_t &inputdata ); +#ifdef MAPBASE + void InputSetTarget( inputdata_t &inputdata ); +#endif + private: CNetworkArray( EHANDLE, m_hProps, MAX_POSE_CONTROLLED_PROPS ); // Handles to controlled models diff --git a/src/game/shared/postprocess_shared.h b/src/game/shared/postprocess_shared.h new file mode 100644 index 00000000..9d3c7df0 --- /dev/null +++ b/src/game/shared/postprocess_shared.h @@ -0,0 +1,54 @@ +//====== Copyright © 1996-2009, Valve Corporation, All rights reserved. ======= +// +// Purpose: common definitions for post-processing effects +// +//============================================================================= + +#ifndef POSTPROCESS_SHARED_H +#define POSTPROCESS_SHARED_H + +#if defined( COMPILER_MSVC ) +#pragma once +#endif + +enum PostProcessParameterNames_t +{ + PPPN_FADE_TIME = 0, + PPPN_LOCAL_CONTRAST_STRENGTH, + PPPN_LOCAL_CONTRAST_EDGE_STRENGTH, + PPPN_VIGNETTE_START, + PPPN_VIGNETTE_END, + PPPN_VIGNETTE_BLUR_STRENGTH, + PPPN_FADE_TO_BLACK_STRENGTH, + PPPN_DEPTH_BLUR_FOCAL_DISTANCE, + PPPN_DEPTH_BLUR_STRENGTH, + PPPN_SCREEN_BLUR_STRENGTH, + PPPN_FILM_GRAIN_STRENGTH, + + POST_PROCESS_PARAMETER_COUNT +}; + +struct PostProcessParameters_t +{ + PostProcessParameters_t() + { + memset( m_flParameters, 0, sizeof( m_flParameters ) ); + m_flParameters[ PPPN_VIGNETTE_START ] = 0.8f; + m_flParameters[ PPPN_VIGNETTE_END ] = 1.1f; + } + + float m_flParameters[ POST_PROCESS_PARAMETER_COUNT ]; + + bool operator !=(PostProcessParameters_t other) + { + for (int i = 0; i < POST_PROCESS_PARAMETER_COUNT; ++i) + { + if (m_flParameters[i] != other.m_flParameters[i]) + return true; + } + + return false; + } +}; + +#endif // POSTPROCESS_SHARED_H \ No newline at end of file diff --git a/src/game/shared/precipitation_shared.h b/src/game/shared/precipitation_shared.h index 791ec29f..cb5f819c 100644 --- a/src/game/shared/precipitation_shared.h +++ b/src/game/shared/precipitation_shared.h @@ -18,8 +18,29 @@ enum PrecipitationType_t PRECIPITATION_TYPE_SNOW, PRECIPITATION_TYPE_ASH, PRECIPITATION_TYPE_SNOWFALL, + PRECIPITATION_TYPE_PARTICLERAIN, + PRECIPITATION_TYPE_PARTICLEASH, + PRECIPITATION_TYPE_PARTICLERAINSTORM, + PRECIPITATION_TYPE_PARTICLESNOW, NUM_PRECIPITATION_TYPES }; +// Returns true if the precipitation type involves the new particle system code +// +// NOTE: We can get away with >= PARTICLERAIN, but if you're adding any new precipitation types +// which DO NOT use the new particle system, please change this code to prevent it from being recognized +// as a particle type. +inline bool IsParticleRainType( PrecipitationType_t type ) +{ + // m_nPrecipType == PRECIPITATION_TYPE_PARTICLERAIN || m_nPrecipType == PRECIPITATION_TYPE_PARTICLEASH + // || m_nPrecipType == PRECIPITATION_TYPE_PARTICLERAINSTORM || m_nPrecipType == PRECIPITATION_TYPE_PARTICLESNOW + return type >= PRECIPITATION_TYPE_PARTICLERAIN; +} + +#ifdef MAPBASE +#define SF_PRECIP_PARTICLE_CLAMP (1 << 0) // Clamps particle types to the precipitation bounds; Mapbase uses this to compensate for the lack of blocker support. +#define SF_PRECIP_PARTICLE_NO_OUTER (1 << 1) // Suppresses the outer particle system. +#endif + #endif // PRECIPITATION_SHARED_H diff --git a/src/game/shared/props_shared.h b/src/game/shared/props_shared.h index 07878d82..470de3b3 100644 --- a/src/game/shared/props_shared.h +++ b/src/game/shared/props_shared.h @@ -32,6 +32,9 @@ #define SF_PHYSPROP_ALWAYS_PICK_UP 0x100000 // Physcannon can always pick this up, no matter what mass or constraints may apply. #define SF_PHYSPROP_NO_COLLISIONS 0x200000 // Don't enable collisions on spawn #define SF_PHYSPROP_IS_GIB 0x400000 // Limit # of active gibs +#ifdef MAPBASE +#define SF_PHYSPROP_NO_ZOMBIE_SWAT 0x800000 // Zombies are not allowed to swat this +#endif // Any barrel farther away than this is ignited rather than exploded. #define PROP_EXPLOSION_IGNITE_RADIUS 32.0f diff --git a/src/game/shared/ragdoll_shared.cpp b/src/game/shared/ragdoll_shared.cpp index 51376d6f..ea93c02e 100644 --- a/src/game/shared/ragdoll_shared.cpp +++ b/src/game/shared/ragdoll_shared.cpp @@ -90,9 +90,9 @@ public: if ( m_bSelfCollisions ) { char szToken[256]; - const char *pStr = nexttoken(szToken, pValue, ','); + const char *pStr = nexttoken(szToken, sizeof(szToken), pValue, ','); int index0 = atoi(szToken); - nexttoken( szToken, pStr, ',' ); + nexttoken( szToken, sizeof( szToken ), pStr, ',' ); int index1 = atoi(szToken); m_pSet->EnableCollisions( index0, index1 ); @@ -174,6 +174,10 @@ void RagdollSetupAnimatedFriction( IPhysicsEnvironment *pPhysEnv, ragdoll_t *rag } } +#ifdef MAPBASE +ConVar g_ragdoll_fixed_constraints_mass( "g_ragdoll_fixed_constraints_mass", "1000", FCVAR_REPLICATED ); +#endif + static void RagdollAddSolid( IPhysicsEnvironment *pPhysEnv, ragdoll_t &ragdoll, const ragdollparams_t ¶ms, solid_t &solid ) { if ( solid.index >= 0 && solid.index < params.pCollide->solidCount) @@ -186,7 +190,12 @@ static void RagdollAddSolid( IPhysicsEnvironment *pPhysEnv, ragdoll_t &ragdoll, { if ( params.fixedConstraints ) { +#ifdef MAPBASE + if (g_ragdoll_fixed_constraints_mass.GetFloat() != -1) + solid.params.mass = g_ragdoll_fixed_constraints_mass.GetFloat(); +#else solid.params.mass = 1000.f; +#endif } solid.params.rotInertiaLimit = 0.1; @@ -831,6 +840,33 @@ void CRagdollLRURetirement::Update( float frametime ) // EPISODIC VERSION m_iRagdollCount = 0; m_iSimulatedRagdollCount = 0; +#ifdef MAPBASE // From Alien Swarm SDK + // remove ragdolls with a forced retire time + for ( i = m_LRU.Head(); i < m_LRU.InvalidIndex(); i = next ) + { + next = m_LRU.Next(i); + + CBaseAnimating *pRagdoll = m_LRU[i].Get(); + + //Just ignore it until we're done burning/dissolving. + if ( pRagdoll && pRagdoll->GetEffectEntity() ) + continue; + + // ignore if it's not time to force retire this ragdoll + if ( m_LRU[i].GetForcedRetireTime() == 0.0f || gpGlobals->curtime < m_LRU[i].GetForcedRetireTime() ) + continue; + + //Msg(" Removing ragdoll %s due to forced retire time of %f (now = %f)\n", pRagdoll->GetModelName(), m_LRU[i].GetForcedRetireTime(), gpGlobals->curtime ); + +#ifdef CLIENT_DLL + pRagdoll->SUB_Remove(); +#else + pRagdoll->SUB_StartFadeOut( 0 ); +#endif + m_LRU.Remove(i); + } +#endif + // First, find ragdolls that are good candidates for deletion because they are not // visible at all, or are in a culled visibility box for ( i = m_LRU.Head(); i < m_LRU.InvalidIndex(); i = next ) @@ -848,12 +884,12 @@ void CRagdollLRURetirement::Update( float frametime ) // EPISODIC VERSION if ( m_LRU.Count() > iMaxRagdollCount ) { //Found one, we're done. - if ( ShouldRemoveThisRagdoll( m_LRU[i] ) == true ) + if ( ShouldRemoveThisRagdoll( pRagdoll ) == true ) { #ifdef CLIENT_DLL - m_LRU[ i ]->SUB_Remove(); + pRagdoll->SUB_Remove(); #else - m_LRU[ i ]->SUB_StartFadeOut( 0 ); + pRagdoll->SUB_StartFadeOut( 0 ); #endif m_LRU.Remove(i); @@ -887,6 +923,27 @@ void CRagdollLRURetirement::Update( float frametime ) // EPISODIC VERSION for ( i = m_LRU.Head(); i < m_LRU.InvalidIndex(); i = next ) { +#ifdef MAPBASE + next = m_LRU.Next(i); + + CBaseAnimating *pRagdoll = m_LRU[i].Get(); + + if ( pRagdoll ) + { + IPhysicsObject *pObject = pRagdoll->VPhysicsGetObject(); + if ( pRagdoll->GetEffectEntity() || ( pObject && !pObject->IsAsleep()) ) + continue; + + // float distToPlayer = (pPlayer->GetAbsOrigin() - pRagdoll->GetAbsOrigin()).LengthSqr(); + float distToPlayer = (PlayerOrigin - pRagdoll->GetAbsOrigin()).LengthSqr(); + + if (distToPlayer > furthestDistSq) + { + furthestOne = i; + furthestDistSq = distToPlayer; + } + } +#else CBaseAnimating *pRagdoll = m_LRU[i].Get(); next = m_LRU.Next(i); @@ -905,6 +962,7 @@ void CRagdollLRURetirement::Update( float frametime ) // EPISODIC VERSION furthestDistSq = distToPlayer; } } +#endif else // delete bad rags first. { furthestOne = i; @@ -912,10 +970,11 @@ void CRagdollLRURetirement::Update( float frametime ) // EPISODIC VERSION } } + CBaseAnimating *pRemoveRagdoll = m_LRU[ furthestOne ].Get(); #ifdef CLIENT_DLL - m_LRU[ furthestOne ]->SUB_Remove(); + pRemoveRagdoll->SUB_Remove(); #else - m_LRU[ furthestOne ]->SUB_StartFadeOut( 0 ); + pRemoveRagdoll->SUB_StartFadeOut( 0 ); #endif } @@ -936,9 +995,9 @@ void CRagdollLRURetirement::Update( float frametime ) // EPISODIC VERSION continue; #ifdef CLIENT_DLL - m_LRU[ i ]->SUB_Remove(); + pRagdoll->SUB_Remove(); #else - m_LRU[ i ]->SUB_StartFadeOut( 0 ); + pRagdoll->SUB_StartFadeOut( 0 ); #endif m_LRU.Remove(i); } @@ -968,6 +1027,33 @@ void CRagdollLRURetirement::Update( float frametime ) // Non-episodic version m_iRagdollCount = 0; m_iSimulatedRagdollCount = 0; +#ifdef MAPBASE // From Alien Swarm SDK + // remove ragdolls with a forced retire time + for ( i = m_LRU.Head(); i < m_LRU.InvalidIndex(); i = next ) + { + next = m_LRU.Next(i); + + CBaseAnimating *pRagdoll = m_LRU[i].Get(); + + //Just ignore it until we're done burning/dissolving. + if ( pRagdoll && pRagdoll->GetEffectEntity() ) + continue; + + // ignore if it's not time to force retire this ragdoll + if ( m_LRU[i].GetForcedRetireTime() == 0.0f || gpGlobals->curtime < m_LRU[i].GetForcedRetireTime() ) + continue; + + //Msg(" Removing ragdoll %s due to forced retire time of %f (now = %f)\n", pRagdoll->GetModelName(), m_LRU[i].GetForcedRetireTime(), gpGlobals->curtime ); + +#ifdef CLIENT_DLL + pRagdoll->SUB_Remove(); +#else + pRagdoll->SUB_StartFadeOut( 0 ); +#endif + m_LRU.Remove(i); + } +#endif + for ( i = m_LRU.Head(); i < m_LRU.InvalidIndex(); i = next ) { next = m_LRU.Next(i); @@ -983,12 +1069,12 @@ void CRagdollLRURetirement::Update( float frametime ) // Non-episodic version if ( m_LRU.Count() > iMaxRagdollCount ) { //Found one, we're done. - if ( ShouldRemoveThisRagdoll( m_LRU[i] ) == true ) + if ( ShouldRemoveThisRagdoll( pRagdoll ) == true ) { #ifdef CLIENT_DLL - m_LRU[ i ]->SUB_Remove(); + pRagdoll->SUB_Remove(); #else - m_LRU[ i ]->SUB_StartFadeOut( 0 ); + pRagdoll->SUB_StartFadeOut( 0 ); #endif m_LRU.Remove(i); @@ -1017,14 +1103,24 @@ void CRagdollLRURetirement::Update( float frametime ) // Non-episodic version CBaseAnimating *pRagdoll = m_LRU[i].Get(); +#ifdef MAPBASE + if ( pRagdoll ) + { + //Just ignore it until we're done burning/dissolving. + IPhysicsObject *pObject = pRagdoll->VPhysicsGetObject(); + if ( pRagdoll->GetEffectEntity() || ( pObject && !pObject->IsAsleep()) ) + continue; + } +#else //Just ignore it until we're done burning/dissolving. if ( pRagdoll && pRagdoll->GetEffectEntity() ) continue; +#endif #ifdef CLIENT_DLL - m_LRU[ i ]->SUB_Remove(); + pRagdoll->SUB_Remove(); #else - m_LRU[ i ]->SUB_StartFadeOut( 0 ); + pRagdoll->SUB_StartFadeOut( 0 ); #endif m_LRU.Remove(i); } @@ -1043,11 +1139,19 @@ ConVar g_ragdoll_important_maxcount( "g_ragdoll_important_maxcount", "2", FCVAR_ //----------------------------------------------------------------------------- // Move it to the top of the LRU //----------------------------------------------------------------------------- +#ifdef MAPBASE // From Alien Swarm SDK +void CRagdollLRURetirement::MoveToTopOfLRU( CBaseAnimating *pRagdoll, bool bImportant, float flForcedRetireTime ) +#else void CRagdollLRURetirement::MoveToTopOfLRU( CBaseAnimating *pRagdoll, bool bImportant ) +#endif { if ( bImportant ) { +#ifdef MAPBASE // From Alien Swarm SDK + m_LRUImportantRagdolls.AddToTail( CRagdollEntry( pRagdoll, flForcedRetireTime ) ); +#else m_LRUImportantRagdolls.AddToTail( pRagdoll ); +#endif if ( m_LRUImportantRagdolls.Count() > g_ragdoll_important_maxcount.GetInt() ) { @@ -1077,9 +1181,39 @@ void CRagdollLRURetirement::MoveToTopOfLRU( CBaseAnimating *pRagdoll, bool bImpo } } +#ifdef MAPBASE // From Alien Swarm SDK + m_LRU.AddToTail( CRagdollEntry( pRagdoll, flForcedRetireTime ) ); +#else m_LRU.AddToTail( pRagdoll ); +#endif } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Remove it from the LRU +//----------------------------------------------------------------------------- +void CRagdollLRURetirement::RemoveFromLRU( CBaseAnimating *pRagdoll ) +{ + for (int i = 0; i < m_LRU.Count(); i++) + { + if (m_LRU[i].Get() == pRagdoll) + { + m_LRU.Remove( i ); + return; + } + } + + for (int i = 0; i < m_LRUImportantRagdolls.Count(); i++) + { + if (m_LRUImportantRagdolls[i].Get() == pRagdoll) + { + m_LRUImportantRagdolls.Remove( i ); + return; + } + } +} +#endif + //EFFECT/ENTITY TRANSFERS diff --git a/src/game/shared/ragdoll_shared.h b/src/game/shared/ragdoll_shared.h index b51cca83..f53f71d5 100644 --- a/src/game/shared/ragdoll_shared.h +++ b/src/game/shared/ragdoll_shared.h @@ -27,7 +27,11 @@ class CBoneAccessor; #include "bone_accessor.h" // UNDONE: Remove and make dynamic? +#ifdef MAPBASE +#define RAGDOLL_MAX_ELEMENTS 32 // Mapbase boosts this limit to the level of later Source games. +#else #define RAGDOLL_MAX_ELEMENTS 24 +#endif #define RAGDOLL_INDEX_BITS 5 // NOTE 1<= RAGDOLL_MAX_ELEMENTS #define CORE_DISSOLVE_FADE_START 0.2f @@ -79,6 +83,22 @@ struct ragdollparams_t bool fixedConstraints; }; +#ifdef MAPBASE // From Alien Swarm SDK +class CRagdollEntry +{ +public: + CRagdollEntry( CBaseAnimating *pRagdoll, float flForcedRetireTime ) : m_hRagdoll( pRagdoll ), m_flForcedRetireTime( flForcedRetireTime ) + { + } + CBaseAnimating* Get() { return m_hRagdoll.Get(); } + float GetForcedRetireTime() { return m_flForcedRetireTime; } + +private: + CHandle m_hRagdoll; + float m_flForcedRetireTime; +}; +#endif + //----------------------------------------------------------------------------- // This hooks the main game systems callbacks to allow the AI system to manage memory //----------------------------------------------------------------------------- @@ -94,7 +114,12 @@ public: virtual void FrameUpdatePostEntityThink( void ); // Move it to the top of the LRU +#ifdef MAPBASE + void MoveToTopOfLRU( CBaseAnimating *pRagdoll, bool bImportant = false, float flForcedRetireTime = 0.0f ); // From Alien Swarm SDK + void RemoveFromLRU( CBaseAnimating *pRagdoll ); +#else void MoveToTopOfLRU( CBaseAnimating *pRagdoll, bool bImportant = false ); +#endif void SetMaxRagdollCount( int iMaxCount ){ m_iMaxRagdolls = iMaxCount; } virtual void LevelInitPreEntity( void ); @@ -102,8 +127,13 @@ public: private: typedef CHandle CRagdollHandle; +#ifdef MAPBASE + CUtlLinkedList< CRagdollEntry > m_LRU; + CUtlLinkedList< CRagdollEntry > m_LRUImportantRagdolls; +#else CUtlLinkedList< CRagdollHandle > m_LRU; CUtlLinkedList< CRagdollHandle > m_LRUImportantRagdolls; +#endif int m_iMaxRagdolls; int m_iSimulatedRagdollCount; diff --git a/src/game/shared/sceneentity_shared.cpp b/src/game/shared/sceneentity_shared.cpp index 80fcdc71..e8cb1905 100644 --- a/src/game/shared/sceneentity_shared.cpp +++ b/src/game/shared/sceneentity_shared.cpp @@ -44,7 +44,11 @@ void Scene_Printf( const char *pFormat, ... ) Q_vsnprintf(msg, sizeof(msg), pFormat, marker); va_end(marker); +#ifdef MAPBASE + CGMsg( 0, CON_GROUP_CHOREO, "%8.3f[%d] %s: %s", gpGlobals->curtime, gpGlobals->tickcount, CBaseEntity::IsServer() ? "sv" : "cl", msg ); +#else Msg( "%8.3f[%d] %s: %s", gpGlobals->curtime, gpGlobals->tickcount, CBaseEntity::IsServer() ? "sv" : "cl", msg ); +#endif } //----------------------------------------------------------------------------- @@ -109,7 +113,7 @@ void CSceneTokenProcessor::Error( const char *fmt, ... ) va_end( argptr ); Warning( "%s", string ); - Assert(0); + AssertMsg(0, "%s", string); } //----------------------------------------------------------------------------- diff --git a/src/game/shared/sceneentity_shared.h b/src/game/shared/sceneentity_shared.h index 6293e23b..a190e53b 100644 --- a/src/game/shared/sceneentity_shared.h +++ b/src/game/shared/sceneentity_shared.h @@ -36,6 +36,7 @@ public: m_pEvent( 0 ), m_pScene( 0 ), m_pActor( 0 ), + m_hSceneEntity(0), m_bStarted( false ), m_iLayer( -1 ), m_iPriority( 0 ), @@ -63,6 +64,8 @@ public: // Current actor CChoreoActor *m_pActor; + CHandle< CSceneEntity > m_hSceneEntity; + // Set after the first time the event has been configured ( allows // bumping markov index only at start of event playback, not every frame ) bool m_bStarted; diff --git a/src/game/shared/shareddefs.h b/src/game/shared/shareddefs.h index dc62e583..0a296a35 100644 --- a/src/game/shared/shareddefs.h +++ b/src/game/shared/shareddefs.h @@ -235,7 +235,14 @@ enum CastVote #define bits_SUIT_DEVICE_FLASHLIGHT 0x00000002 #define bits_SUIT_DEVICE_BREATHER 0x00000004 -#define MAX_SUIT_DEVICES 3 +#ifdef MAPBASE +// Custom suit power devices +#define bits_SUIT_DEVICE_CUSTOM0 0x00000008 +#define bits_SUIT_DEVICE_CUSTOM1 0x00000010 +#define bits_SUIT_DEVICE_CUSTOM2 0x00000020 +#endif + +#define MAX_SUIT_DEVICES 6 // Mapbase boosts this to 6 for the custom devices //=================================================================================================================== @@ -400,6 +407,14 @@ enum PLAYER_ANIM PLAYER_RELOAD, PLAYER_START_AIMING, PLAYER_LEAVE_AIMING, + +#ifdef MAPBASE + // New player animations from Mapbase + PLAYER_ATTACK2, + PLAYER_ATTACK3, + PLAYER_UNHOLSTER, + PLAYER_HOLSTER, +#endif }; #ifdef HL2_DLL @@ -622,6 +637,7 @@ enum EFL_SETTING_UP_BONES = (1<<3), // Set while a model is setting up its bones. EFL_KEEP_ON_RECREATE_ENTITIES = (1<<4), // This is a special entity that should not be deleted when we restart entities only + //Tony; BUG?? I noticed this today while performing stealz on flag 16! look at the definition of the flag above... EFL_HAS_PLAYER_CHILD= (1<<4), // One of the child entities is a player. EFL_DIRTY_SHADOWUPDATE = (1<<5), // Client only- need shadow manager to update the shadow... @@ -644,6 +660,7 @@ enum EFL_DIRTY_SURROUNDING_COLLISION_BOUNDS = (1<<14), EFL_DIRTY_SPATIAL_PARTITION = (1<<15), EFL_FORCE_ALLOW_MOVEPARENT = (1<<16), + //EFL_PLUGIN_BASED_BOT = (1<<16), //this is set on plugin bots, so that if any games include their own bot code, they won't affect plugin bots. EFL_IN_SKYBOX = (1<<17), // This is set if the entity detects that it's in the skybox. // This forces it to pass the "in PVS" for transmission. @@ -699,6 +716,10 @@ class CBaseEntity; // Bullet firing information //----------------------------------------------------------------------------- class CBaseEntity; +#ifdef MAPBASE_VSCRIPT +// For the VScript functions in FireBUlletsInfo_t +FORWARD_DECLARE_HANDLE( HSCRIPT ); +#endif enum FireBulletsFlags_t { @@ -706,6 +727,9 @@ enum FireBulletsFlags_t FIRE_BULLETS_DONT_HIT_UNDERWATER = 0x2, // If the shot hits its target underwater, don't damage it FIRE_BULLETS_ALLOW_WATER_SURFACE_IMPACTS = 0x4, // If the shot hits water surface, still call DoImpactEffect FIRE_BULLETS_TEMPORARY_DANGER_SOUND = 0x8, // Danger sounds added from this impact can be stomped immediately if another is queued +#ifdef MAPBASE + FIRE_BULLETS_NO_AUTO_GIB_TYPE = 0x10, // Don't automatically add DMG_ALWAYSGIB or DMG_NEVERGIB if m_flDamage is set +#endif }; @@ -731,6 +755,9 @@ struct FireBulletsInfo_t #endif m_bPrimaryAttack = true; m_bUseServerRandomSeed = false; +#ifdef MAPBASE + m_pIgnoreEntList = NULL; +#endif } FireBulletsInfo_t( int nShots, const Vector &vecSrc, const Vector &vecDir, const Vector &vecSpread, float flDistance, int nAmmoType, bool bPrimaryAttack = true ) @@ -750,8 +777,15 @@ struct FireBulletsInfo_t m_flDamageForceScale = 1.0f; m_bPrimaryAttack = bPrimaryAttack; m_bUseServerRandomSeed = false; +#ifdef MAPBASE + m_pIgnoreEntList = NULL; +#endif } +#ifdef MAPBASE + ~FireBulletsInfo_t() {} +#endif + int m_iShots; Vector m_vecSrc; Vector m_vecDirShooting; @@ -767,6 +801,54 @@ struct FireBulletsInfo_t CBaseEntity *m_pAdditionalIgnoreEnt; bool m_bPrimaryAttack; bool m_bUseServerRandomSeed; +#ifdef MAPBASE + // This variable is like m_pAdditionalIgnoreEnt, but it's a list of entities instead of just one. + // Since func_tanks already use m_pAdditionalIgnoreEnt for parents, they needed another way to stop hitting their controllers. + // After much trial and error, I decided to just add more excluded entities to the bullet firing info. + // It could've just been a single entity called "m_pAdditionalIgnoreEnt2", but since these are just pointers, + // I planned ahead and made it a CUtlVector instead. + CUtlVector *m_pIgnoreEntList; +#endif + +#ifdef MAPBASE_VSCRIPT // These functions are used by VScript to expose FireBulletsInfo_t to users. + int GetShots() { return m_iShots; } + void SetShots( int value ) { m_iShots = value; } + + Vector GetSource() { return m_vecSrc; } + void SetSource( Vector value ) { m_vecSrc = value; } + Vector GetDirShooting() { return m_vecDirShooting; } + void SetDirShooting( Vector value ) { m_vecDirShooting = value; } + Vector GetSpread() { return m_vecSpread; } + void SetSpread( Vector value ) { m_vecSpread = value; } + + float GetDistance() { return m_flDistance; } + void SetDistance( float value ) { m_flDistance = value; } + + int GetAmmoType() { return m_iAmmoType; } + void SetAmmoType( int value ) { m_iAmmoType = value; } + + int GetTracerFreq() { return m_iTracerFreq; } + void SetTracerFreq( int value ) { m_iTracerFreq = value; } + + float GetDamage() { return m_flDamage; } + void SetDamage( float value ) { m_flDamage = value; } + int GetPlayerDamage() { return m_iPlayerDamage; } + void SetPlayerDamage( float value ) { m_iPlayerDamage = value; } + + int GetFlags() { return m_nFlags; } + void SetFlags( float value ) { m_nFlags = value; } + + float GetDamageForceScale() { return m_flDamageForceScale; } + void SetDamageForceScale( float value ) { m_flDamageForceScale = value; } + + HSCRIPT ScriptGetAttacker(); + void ScriptSetAttacker( HSCRIPT value ); + HSCRIPT ScriptGetAdditionalIgnoreEnt(); + void ScriptSetAdditionalIgnoreEnt( HSCRIPT value ); + + bool GetPrimaryAttack() { return m_bPrimaryAttack; } + void SetPrimaryAttack( bool value ) { m_bPrimaryAttack = value; } +#endif }; //----------------------------------------------------------------------------- @@ -944,6 +1026,39 @@ enum #define COMMENTARY_BUTTONS (IN_USE) #endif +enum tprbGameInfo_e +{ + // Teamplay Roundbased Game rules shared + TPRBGAMEINFO_GAMESTATE = 1, //gets the state of the current game (waiting for players, setup, active, overtime, stalemate, roundreset) + TPRBGAMEINFO_RESERVED1, + TPRBGAMEINFO_RESERVED2, + TPRBGAMEINFO_RESERVED3, + TPRBGAMEINFO_RESERVED4, + TPRBGAMEINFO_RESERVED5, + TPRBGAMEINFO_RESERVED6, + TPRBGAMEINFO_RESERVED7, + TPRBGAMEINFO_RESERVED8, + + TPRBGAMEINFO_LASTGAMEINFO, +}; +// Mark it off so valvegame_plugin_def.h ignores it, if both headers are included in a plugin. +#define TPRBGAMEINFO_x 1 + +//Tony; (t)eam(p)lay(r)ound(b)ased gamerules -- Game Info values +#define TPRB_STATE_WAITING (1<<0) +#define TPRB_STATE_SETUP (1<<1) +#define TPRB_STATE_ACTIVE (1<<2) +#define TPRB_STATE_ROUNDWON (1<<3) +#define TPRB_STATE_OVERTIME (1<<4) +#define TPRB_STATE_STALEMATE (1<<5) +#define TPRB_STATE_ROUNDRESET (1<<6) +#define TPRB_STATE_WAITINGREADYSTART (1<<7) + +//Tony; including sdk_shareddefs.h because I use it in a _lot_ of places that needs to be seen before many other things. +#ifdef SDK_DLL +#include "sdk_shareddefs.h" +#endif + #define TEAM_TRAIN_MAX_TEAMS 4 #define TEAM_TRAIN_MAX_HILLS 5 #define TEAM_TRAIN_FLOATS_PER_HILL 2 @@ -990,6 +1105,18 @@ enum }; #endif // TF_DLL || TF_CLIENT_DLL +#ifdef MAPBASE +// Developer commentary types +enum +{ + COMMENTARY_TYPE_AUDIO, // Play commentary audio (default) + + COMMENTARY_TYPE_TEXT, // Display text data + COMMENTARY_TYPE_IMAGE, // Display an image + COMMENTARY_TYPE_SCENE, // Play a VCD file +}; +#endif + class CPhysCollide; struct collidelist_t { diff --git a/src/game/shared/takedamageinfo.cpp b/src/game/shared/takedamageinfo.cpp index c15b8017..34f45f60 100644 --- a/src/game/shared/takedamageinfo.cpp +++ b/src/game/shared/takedamageinfo.cpp @@ -31,6 +31,60 @@ BEGIN_SIMPLE_DATADESC( CTakeDamageInfo ) DEFINE_FIELD( m_iDamagedOtherPlayers, FIELD_INTEGER), END_DATADESC() +#ifdef MAPBASE_VSCRIPT +BEGIN_SCRIPTDESC_ROOT( CTakeDamageInfo, "Damage information handler." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptGetInflictor, "GetInflictor", "Gets the inflictor." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptSetInflictor, "SetInflictor", "Sets the inflictor." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptGetWeapon, "GetWeapon", "Gets the weapon." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptSetWeapon, "SetWeapon", "Sets the weapon." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptGetAttacker, "GetAttacker", "Gets the attacker." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptSetAttacker, "SetAttacker", "Sets the attacker." ) + + DEFINE_SCRIPTFUNC( GetDamage, "Gets the damage." ) + DEFINE_SCRIPTFUNC( SetDamage, "Sets the damage." ) + DEFINE_SCRIPTFUNC( GetMaxDamage, "Gets the max damage." ) + DEFINE_SCRIPTFUNC( SetMaxDamage, "Sets the max damage." ) + DEFINE_SCRIPTFUNC( ScaleDamage, "Scales the damage." ) + DEFINE_SCRIPTFUNC( AddDamage, "Adds to the damage." ) + DEFINE_SCRIPTFUNC( SubtractDamage, "Removes from the damage." ) + DEFINE_SCRIPTFUNC( GetDamageBonus, "Gets the damage bonus." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptSetDamageBonus, "SetDamageBonus", "Sets the damage bonus." ) + + DEFINE_SCRIPTFUNC( GetBaseDamage, "Gets the base damage." ) + DEFINE_SCRIPTFUNC( BaseDamageIsValid, "Checks if the base damage is valid." ) + + DEFINE_SCRIPTFUNC( GetDamageForce, "Gets the damage force." ) + DEFINE_SCRIPTFUNC( SetDamageForce, "Sets the damage force." ) + DEFINE_SCRIPTFUNC( ScaleDamageForce, "Scales the damage force." ) + + DEFINE_SCRIPTFUNC( GetDamagePosition, "Gets the damage position." ) + DEFINE_SCRIPTFUNC( SetDamagePosition, "Sets the damage position." ) + + DEFINE_SCRIPTFUNC( GetReportedPosition, "Gets the reported damage position." ) + DEFINE_SCRIPTFUNC( SetReportedPosition, "Sets the reported damage position." ) + + DEFINE_SCRIPTFUNC( GetDamageType, "Gets the damage type." ) + DEFINE_SCRIPTFUNC( SetDamageType, "Sets the damage type." ) + DEFINE_SCRIPTFUNC( AddDamageType, "Adds to the damage type." ) + DEFINE_SCRIPTFUNC( GetDamageCustom, "Gets the damage custom." ) + DEFINE_SCRIPTFUNC( SetDamageCustom, "Sets the damage custom." ) + DEFINE_SCRIPTFUNC( GetDamageStats, "Gets the damage stats." ) + DEFINE_SCRIPTFUNC( SetDamageStats, "Sets the damage stats." ) + DEFINE_SCRIPTFUNC( IsForceFriendlyFire, "Gets force friendly fire." ) + DEFINE_SCRIPTFUNC( SetForceFriendlyFire, "Sets force friendly fire." ) + + DEFINE_SCRIPTFUNC( GetAmmoType, "Gets the ammo type." ) + DEFINE_SCRIPTFUNC( SetAmmoType, "Sets the ammo type." ) + DEFINE_SCRIPTFUNC( GetAmmoName, "Gets the ammo type name." ) + + DEFINE_SCRIPTFUNC( GetPlayerPenetrationCount, "Gets the player penetration count." ) + DEFINE_SCRIPTFUNC( SetPlayerPenetrationCount, "Sets the player penetration count." ) + + DEFINE_SCRIPTFUNC( GetDamagedOtherPlayers, "Gets whether other players have been damaged." ) + DEFINE_SCRIPTFUNC( SetDamagedOtherPlayers, "Sets whether other players have been damaged." ) +END_SCRIPTDESC(); +#endif + void CTakeDamageInfo::Init( CBaseEntity *pInflictor, CBaseEntity *pAttacker, CBaseEntity *pWeapon, const Vector &damageForce, const Vector &damagePosition, const Vector &reportedPosition, float flDamage, int bitsDamageType, int iCustomDamage ) { m_hInflictor = pInflictor; @@ -168,6 +222,57 @@ const char *CTakeDamageInfo::GetAmmoName() const return pszAmmoType; } +#ifdef MAPBASE_VSCRIPT +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +HSCRIPT CTakeDamageInfo::ScriptGetInflictor() const +{ + return ToHScript( GetInflictor() ); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CTakeDamageInfo::ScriptSetInflictor( HSCRIPT pInflictor ) +{ + SetInflictor( ToEnt( pInflictor ) ); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +HSCRIPT CTakeDamageInfo::ScriptGetWeapon() const +{ + return ToHScript( GetWeapon() ); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CTakeDamageInfo::ScriptSetWeapon( HSCRIPT pWeapon ) +{ + SetWeapon( ToEnt( pWeapon ) ); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +HSCRIPT CTakeDamageInfo::ScriptGetAttacker() const +{ + return ToHScript( GetAttacker() ); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CTakeDamageInfo::ScriptSetAttacker( HSCRIPT pAttacker ) +{ + SetAttacker( ToEnt( pAttacker ) ); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CTakeDamageInfo::ScriptSetDamageBonus( float flBonus ) +{ + SetDamageBonus( flBonus ); +} +#endif + // -------------------------------------------------------------------------------------------------- // // MultiDamage // Collects multiple small damages into a single damage diff --git a/src/game/shared/takedamageinfo.h b/src/game/shared/takedamageinfo.h index cdb0173e..5cbfb3fd 100644 --- a/src/game/shared/takedamageinfo.h +++ b/src/game/shared/takedamageinfo.h @@ -14,6 +14,10 @@ #include "networkvar.h" // todo: change this when DECLARE_CLASS is moved into a better location. +#ifdef MAPBASE_VSCRIPT +#include "vscript/ivscript.h" +#endif + // Used to initialize m_flBaseDamage to something that we know pretty much for sure // hasn't been modified by a user. #define BASEDAMAGE_NOT_SPECIFIED FLT_MAX @@ -117,6 +121,17 @@ public: void SetCritType( ECritType eType ); ECritType GetCritType() const { return m_eCritType; } +#ifdef MAPBASE_VSCRIPT + HSCRIPT ScriptGetInflictor() const; + void ScriptSetInflictor( HSCRIPT pInflictor ); + HSCRIPT ScriptGetWeapon() const; + void ScriptSetWeapon( HSCRIPT pWeapon ); + HSCRIPT ScriptGetAttacker() const; + void ScriptSetAttacker( HSCRIPT pAttacker ); + + void ScriptSetDamageBonus( float flBonus ); +#endif + //private: void CopyDamageToBaseDamage(); @@ -367,12 +382,12 @@ inline void CTakeDamageInfo::SetDamageCustom( int iDamageCustom ) inline int CTakeDamageInfo::GetDamageStats() const { - return m_iDamageCustom; + return m_iDamageStats; } inline void CTakeDamageInfo::SetDamageStats( int iDamageCustom ) { - m_iDamageCustom = iDamageCustom; + m_iDamageStats = iDamageCustom; } inline int CTakeDamageInfo::GetAmmoType() const diff --git a/src/game/shared/teamplay_round_timer.cpp b/src/game/shared/teamplay_round_timer.cpp index fd555362..74d67249 100644 --- a/src/game/shared/teamplay_round_timer.cpp +++ b/src/game/shared/teamplay_round_timer.cpp @@ -1398,14 +1398,14 @@ void CTeamRoundTimer::InputAddTeamTime( inputdata_t &input ) int nSeconds = 0; // get the team - p = nexttoken( token, p, ' ' ); + p = nexttoken( token, p, ' ', sizeof(token) ); if ( token[0] ) { nTeam = Q_atoi( token ); } // get the time - p = nexttoken( token, p, ' ' ); + p = nexttoken( token, p, ' ', sizeof(token) ); if ( token[0] ) { nSeconds = Q_atoi( token ); diff --git a/src/game/shared/usercmd.cpp b/src/game/shared/usercmd.cpp index 84ffa243..1607eba3 100644 --- a/src/game/shared/usercmd.cpp +++ b/src/game/shared/usercmd.cpp @@ -10,12 +10,20 @@ #include "bitbuf.h" #include "checksum_md5.h" +#ifdef MAPBASE_VSCRIPT +#include "mapbase/vscript_singletons.h" +#endif + // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" // TF2 specific, need enough space for OBJ_LAST items from tf_shareddefs.h #define WEAPON_SUBTYPE_BITS 6 +#ifdef MAPBASE_VSCRIPT +extern CNetMsgScriptHelper *g_ScriptNetMsg; +#endif + //----------------------------------------------------------------------------- // Purpose: Write a delta compressed user command. // Input : *buf - @@ -187,6 +195,22 @@ void WriteUsercmd( bf_write *buf, const CUserCmd *to, const CUserCmd *from ) buf->WriteOneBit( 0 ); } #endif + +#if defined( MAPBASE_VSCRIPT ) && defined( CLIENT_DLL ) + Assert( g_ScriptNetMsg ); + + if ( g_ScriptNetMsg->m_bWriteReady ) + { + buf->WriteOneBit( 1 ); + g_ScriptNetMsg->WriteToBuffer( buf ); + g_ScriptNetMsg->m_bWriteReady = false; + } + else + { + buf->WriteOneBit( 0 ); + } +#endif + } //----------------------------------------------------------------------------- @@ -196,7 +220,11 @@ void WriteUsercmd( bf_write *buf, const CUserCmd *to, const CUserCmd *from ) // *from - // Output : static void ReadUsercmd //----------------------------------------------------------------------------- +#if defined( MAPBASE_VSCRIPT ) && defined( GAME_DLL ) +void ReadUsercmd( bf_read *buf, CUserCmd *move, CUserCmd *from, CBaseEntity *pPlayer ) +#else void ReadUsercmd( bf_read *buf, CUserCmd *move, CUserCmd *from ) +#endif { // Assume no change *move = *from; @@ -303,4 +331,12 @@ void ReadUsercmd( bf_read *buf, CUserCmd *move, CUserCmd *from ) } } #endif + +#if defined( MAPBASE_VSCRIPT ) && defined( GAME_DLL ) + if ( buf->ReadOneBit() ) + { + g_ScriptNetMsg->ReceiveMessage( buf, pPlayer ); + } +#endif + } diff --git a/src/game/shared/usercmd.h b/src/game/shared/usercmd.h index 0005bde9..6f104b1a 100644 --- a/src/game/shared/usercmd.h +++ b/src/game/shared/usercmd.h @@ -174,7 +174,11 @@ public: }; +#if defined( MAPBASE_VSCRIPT ) && defined( GAME_DLL ) +void ReadUsercmd( bf_read *buf, CUserCmd *move, CUserCmd *from, CBaseEntity *pPlayer ); +#else void ReadUsercmd( bf_read *buf, CUserCmd *move, CUserCmd *from ); +#endif void WriteUsercmd( bf_write *buf, const CUserCmd *to, const CUserCmd *from ); #endif // USERCMD_H diff --git a/src/game/shared/usermessages.cpp b/src/game/shared/usermessages.cpp index d640c4e7..1ac626be 100644 --- a/src/game/shared/usermessages.cpp +++ b/src/game/shared/usermessages.cpp @@ -18,6 +18,10 @@ void RegisterScriptMessages( void ) void RegisterUserMessages( void ); +#ifdef MAPBASE +void RegisterMapbaseUserMessages( void ); +#endif + //----------------------------------------------------------------------------- // Purpose: Force registration on .dll load // FIXME: Should this be a client/server system? @@ -199,6 +203,11 @@ void CreateUserMessages() usermessages = new CUserMessages(); // Game specific registration function; RegisterUserMessages(); + +#ifdef MAPBASE + // Mapbase registration function; + RegisterMapbaseUserMessages(); +#endif } } diff --git a/src/game/shared/util_shared.cpp b/src/game/shared/util_shared.cpp index c3cca293..6aee3582 100644 --- a/src/game/shared/util_shared.cpp +++ b/src/game/shared/util_shared.cpp @@ -222,6 +222,15 @@ bool PassServerEntityFilter( const IHandleEntity *pTouch, const IHandleEntity *p if ( !pEntTouch || !pEntPass ) return true; +#ifdef MAPBASE + // don't clip against own missiles + if ( pEntTouch->GetOwnerEntity() == pEntPass && !pEntTouch->IsSolidFlagSet(FSOLID_COLLIDE_WITH_OWNER) ) + return false; + + // don't clip against owner + if ( pEntPass->GetOwnerEntity() == pEntTouch && !pEntPass->IsSolidFlagSet(FSOLID_COLLIDE_WITH_OWNER) ) + return false; +#else // don't clip against own missiles if ( pEntTouch->GetOwnerEntity() == pEntPass ) return false; @@ -229,6 +238,7 @@ bool PassServerEntityFilter( const IHandleEntity *pTouch, const IHandleEntity *p // don't clip against owner if ( pEntPass->GetOwnerEntity() == pEntTouch ) return false; +#endif return true; @@ -989,6 +999,57 @@ void UTIL_StringToColor32( color32 *color, const char *pString ) color->a = tmp[3]; } +#ifdef MAPBASE +void UTIL_StringToFloatArray_PreserveArray( float *pVector, int count, const char *pString ) +{ + char *pstr, *pfront, tempString[128]; + int j; + + Q_strncpy( tempString, pString, sizeof(tempString) ); + pstr = pfront = tempString; + + for ( j = 0; j < count; j++ ) // lifted from pr_edict.c + { + pVector[j] = atof( pfront ); + + // skip any leading whitespace + while ( *pstr && *pstr <= ' ' ) + pstr++; + + // skip to next whitespace + while ( *pstr && *pstr > ' ' ) + pstr++; + + if (!*pstr) + break; + + pstr++; + pfront = pstr; + } +} + +void UTIL_StringToIntArray_PreserveArray( int *pVector, int count, const char *pString ) +{ + char *pstr, *pfront, tempString[128]; + int j; + + Q_strncpy( tempString, pString, sizeof(tempString) ); + pstr = pfront = tempString; + + for ( j = 0; j < count; j++ ) // lifted from pr_edict.c + { + pVector[j] = atoi( pfront ); + + while ( *pstr && *pstr != ' ' ) + pstr++; + if (!*pstr) + break; + pstr++; + pfront = pstr; + } +} +#endif + #ifndef _XBOX void UTIL_DecodeICE( unsigned char * buffer, int size, const unsigned char *key) { @@ -1298,7 +1359,7 @@ char *UTIL_GetFilteredChatText( int iPlayerIndex, char *pszText, int nTextBuffer } #endif // CLIENT_DLL -char* ReadAndAllocStringValue( KeyValues *pSub, const char *pName, const char *pFilename ) +const char* ReadAndAllocStringValue( KeyValues *pSub, const char *pName, const char *pFilename ) { const char *pValue = pSub->GetString( pName, NULL ); if ( !pValue ) @@ -1333,6 +1394,59 @@ int UTIL_StringFieldToInt( const char *szValue, const char **pValueStrings, int } +static char s_NumBitsInNibble[ 16 ] = +{ + 0, // 0000 = 0 + 1, // 0001 = 1 + 1, // 0010 = 2 + 2, // 0011 = 3 + 1, // 0100 = 4 + 2, // 0101 = 5 + 2, // 0110 = 6 + 3, // 0111 = 7 + 1, // 1000 = 8 + 2, // 1001 = 9 + 2, // 1010 = 10 + 3, // 1011 = 11 + 2, // 1100 = 12 + 3, // 1101 = 13 + 3, // 1110 = 14 + 4, // 1111 = 15 +}; + +int UTIL_CountNumBitsSet( unsigned int nVar ) +{ + int nNumBits = 0; + + while ( nVar > 0 ) + { + // Look up and add in bits in the bottom nibble + nNumBits += s_NumBitsInNibble[ nVar & 0x0f ]; + + // Shift one nibble to the right + nVar >>= 4; + } + + return nNumBits; +} + +int UTIL_CountNumBitsSet( uint64 nVar ) +{ + int nNumBits = 0; + + while ( nVar > 0 ) + { + // Look up and add in bits in the bottom nibble + nNumBits += s_NumBitsInNibble[ nVar & 0x0f ]; + + // Shift one nibble to the right + nVar >>= 4; + } + + return nNumBits; +} + + int find_day_of_week( struct tm& found_day, int day_of_week, int step ) { return 0; diff --git a/src/game/shared/util_shared.h b/src/game/shared/util_shared.h index e27ffc50..5add18c5 100644 --- a/src/game/shared/util_shared.h +++ b/src/game/shared/util_shared.h @@ -359,6 +359,14 @@ void UTIL_StringToIntArray( int *pVector, int count, const char *pString ); void UTIL_StringToFloatArray( float *pVector, int count, const char *pString ); void UTIL_StringToColor32( color32 *color, const char *pString ); +#ifdef MAPBASE +// Version of UTIL_StringToIntArray that doesn't set all untouched array elements to 0. +void UTIL_StringToIntArray_PreserveArray( int *pVector, int count, const char *pString ); + +// Version of UTIL_StringToFloatArray that doesn't set all untouched array elements to 0. +void UTIL_StringToFloatArray_PreserveArray( float *pVector, int count, const char *pString ); +#endif + CBasePlayer *UTIL_PlayerByIndex( int entindex ); // Helper for use with console commands and the like. // Returns NULL if not found or if the provided arg would match multiple players. @@ -625,6 +633,22 @@ public: return (m_timestamp > 0.0f) ? m_duration : 0.0f; } + /// 1.0 for newly started, 0.0 for elapsed + float GetRemainingRatio( void ) const + { + if (HasStarted() && m_duration > 0.0f) + { + float left = GetRemainingTime() / m_duration; + if (left < 0.0f) + return 0.0f; + if (left > 1.0f) + return 1.0f; + return left; + } + + return 0.0f; + } + private: float m_duration; float m_timestamp; @@ -639,10 +663,13 @@ class RealTimeCountdownTimer : public CountdownTimer } }; -char* ReadAndAllocStringValue( KeyValues *pSub, const char *pName, const char *pFilename = NULL ); +const char* ReadAndAllocStringValue( KeyValues *pSub, const char *pName, const char *pFilename = NULL ); int UTIL_StringFieldToInt( const char *szValue, const char **pValueStrings, int iNumStrings ); +int UTIL_CountNumBitsSet( unsigned int nVar ); +int UTIL_CountNumBitsSet( uint64 nVar ); + //----------------------------------------------------------------------------- // Holidays //----------------------------------------------------------------------------- diff --git a/src/game/shared/vscript_shared.cpp b/src/game/shared/vscript_shared.cpp index 64088e10..41e3509e 100644 --- a/src/game/shared/vscript_shared.cpp +++ b/src/game/shared/vscript_shared.cpp @@ -1,4 +1,4 @@ -//========== Copyright © 2008, Valve Corporation, All rights reserved. ======== +//========== Copyright ? 2008, Valve Corporation, All rights reserved. ======== // // Purpose: // @@ -13,6 +13,9 @@ #include "characterset.h" #include "isaverestore.h" #include "gamerules.h" +#ifdef MAPBASE_VSCRIPT +#include "mapbase/vscript_singletons.h" +#endif #if defined(CLIENT_DLL) && defined(PANORAMA_ENABLE) #include "panorama/uijsregistration.h" @@ -58,6 +61,23 @@ private: };*/ #endif +#ifdef MAPBASE_VSCRIPT +// This is to ensure a dependency exists between the vscript library and the game DLLs +extern int vscript_token; +int vscript_token_hack = vscript_token; +#endif + +static const char *pszExtensions[] = +{ + "", // SL_NONE + ".gm", // SL_GAMEMONKEY + ".nut", // SL_SQUIRREL + ".lua", // SL_LUA + ".py", // SL_PYTHON +}; + + + HSCRIPT VScriptCompileScript( const char *pszScriptName, bool bWarnMissing ) { if ( !g_pScriptVM ) @@ -65,20 +85,15 @@ HSCRIPT VScriptCompileScript( const char *pszScriptName, bool bWarnMissing ) return NULL; } - static const char *pszExtensions[] = - { - "", // SL_NONE - ".gm", // SL_GAMEMONKEY - ".nut", // SL_SQUIRREL - ".lua", // SL_LUA - ".py", // SL_PYTHON - }; - const char *pszVMExtension = pszExtensions[g_pScriptVM->GetLanguage()]; const char *pszIncomingExtension = V_strrchr( pszScriptName , '.' ); if ( pszIncomingExtension && V_strcmp( pszIncomingExtension, pszVMExtension ) != 0 ) { +#ifdef MAPBASE_VSCRIPT + CGWarning( 0, CON_GROUP_VSCRIPT, "Script file type (\"%s\", from \"%s\") does not match VM type (\"%s\")\n", pszIncomingExtension, pszScriptName, pszVMExtension ); +#else Log_Warning( LOG_VScript, "Script file type does not match VM type\n" ); +#endif return NULL; } @@ -104,9 +119,17 @@ HSCRIPT VScriptCompileScript( const char *pszScriptName, bool bWarnMissing ) { bool bResult = filesystem->ReadFile( scriptPath, "GAME", bufferScript ); +#ifdef MAPBASE_VSCRIPT + if ( !bResult && bWarnMissing ) +#else if( !bResult ) +#endif { +#ifdef MAPBASE_VSCRIPT + CGWarning( 0, CON_GROUP_VSCRIPT, "Script not found (%s) \n", scriptPath.operator const char *() ); +#else Log_Warning( LOG_VScript, "Script not found (%s) \n", scriptPath.operator const char *() ); +#endif Assert( "Error running script" ); } @@ -124,7 +147,11 @@ HSCRIPT VScriptCompileScript( const char *pszScriptName, bool bWarnMissing ) HSCRIPT hScript = g_pScriptVM->CompileScript( pBase, pszFilename ); if ( !hScript ) { +#ifdef MAPBASE_VSCRIPT + CGWarning( 0, CON_GROUP_VSCRIPT, "FAILED to compile and execute script file named %s\n", scriptPath.operator const char *() ); +#else Log_Warning( LOG_VScript, "FAILED to compile and execute script file named %s\n", scriptPath.operator const char *() ); +#endif Assert( "Error running script" ); } return hScript; @@ -141,14 +168,22 @@ bool VScriptRunScript( const char *pszScriptName, HSCRIPT hScope, bool bWarnMiss if ( !pszScriptName || !*pszScriptName ) { +#ifdef MAPBASE_VSCRIPT Log_Warning( LOG_VScript, "Cannot run script: NULL script name\n" ); +#else + CGWarning( 0, CON_GROUP_VSCRIPT, "Cannot run script: NULL script name\n" ); +#endif return false; } // Prevent infinite recursion in VM if ( g_ScriptServerRunScriptDepth > 16 ) { +#ifdef MAPBASE_VSCRIPT + CGWarning( 0, CON_GROUP_VSCRIPT, "IncludeScript stack overflow\n" ); +#else Log_Warning( LOG_VScript, "IncludeScript stack overflow\n" ); +#endif return false; } @@ -157,6 +192,9 @@ bool VScriptRunScript( const char *pszScriptName, HSCRIPT hScope, bool bWarnMiss bool bSuccess = false; if ( hScript ) { + // player is not yet spawned, this block is always skipped. + // It is registered in CBasePlayer instead. +#ifndef MAPBASE #ifdef GAME_DLL if ( gpGlobals->maxClients == 1 ) { @@ -166,11 +204,16 @@ bool VScriptRunScript( const char *pszScriptName, HSCRIPT hScope, bool bWarnMiss g_pScriptVM->SetValue( "player", pPlayer->GetScriptInstance() ); } } +#endif #endif bSuccess = ( g_pScriptVM->Run( hScript, hScope ) != SCRIPT_ERROR ); if ( !bSuccess ) { +#ifdef MAPBASE_VSCRIPT Log_Warning( LOG_VScript, "Error running script named %s\n", pszScriptName ); +#else + Warning( "Error running script named %s\n", pszScriptName ); +#endif Assert( "Error running script" ); } } @@ -178,29 +221,154 @@ bool VScriptRunScript( const char *pszScriptName, HSCRIPT hScope, bool bWarnMiss return bSuccess; } + +#ifdef MAPBASE_VSCRIPT + +// +// These functions are currently only used for "mapspawn_addon" scripts. +// +HSCRIPT VScriptCompileScriptAbsolute( const char *pszScriptName, bool bWarnMissing, const char *pszRootFolderName ) +{ + if ( !g_pScriptVM ) + { + return NULL; + } + + const char *pszVMExtension = pszExtensions[g_pScriptVM->GetLanguage()]; + const char *pszIncomingExtension = V_strrchr( pszScriptName , '.' ); + if ( pszIncomingExtension && V_strcmp( pszIncomingExtension, pszVMExtension ) != 0 ) + { + // Account for cases where there is no extension and the folder names just have dots (e.g. ".local") + if ( strchr( pszIncomingExtension, CORRECT_PATH_SEPARATOR ) ) + { + pszIncomingExtension = NULL; + } + else + { + CGWarning( 0, CON_GROUP_VSCRIPT, "Script file type (\"%s\", from \"%s\") does not match VM type (\"%s\")\n", pszIncomingExtension, pszScriptName, pszVMExtension ); + return NULL; + } + } + + CFmtStr scriptPath; + if ( pszIncomingExtension ) + { + scriptPath = pszScriptName; + } + else + { + scriptPath.sprintf( "%s%s", pszScriptName, pszVMExtension ); + } + + const char *pBase; + CUtlBuffer bufferScript; + + if ( g_pScriptVM->GetLanguage() == SL_PYTHON ) + { + // python auto-loads raw or precompiled modules - don't load data here + pBase = NULL; + } + else + { + bool bResult = filesystem->ReadFile( scriptPath, NULL, bufferScript ); + + if ( !bResult && bWarnMissing ) + { + CGWarning( 0, CON_GROUP_VSCRIPT, "Script not found (%s) \n", scriptPath.operator const char *() ); + Assert( "Error running script" ); + } + + pBase = (const char *) bufferScript.Base(); + + if ( !pBase || !*pBase ) + { + return NULL; + } + } + + // Attach the folder to the script ID + const char *pszFilename = V_strrchr( scriptPath, '/' ); + scriptPath.sprintf( "%s%s", pszRootFolderName, pszFilename ); + + HSCRIPT hScript = g_pScriptVM->CompileScript( pBase, scriptPath ); + if ( !hScript ) + { + CGWarning( 0, CON_GROUP_VSCRIPT, "FAILED to compile and execute script file named %s\n", scriptPath.operator const char *() ); + Assert( "Error running script" ); + } + return hScript; +} + +bool VScriptRunScriptAbsolute( const char *pszScriptName, HSCRIPT hScope, bool bWarnMissing, const char *pszRootFolderName ) +{ + if ( !g_pScriptVM ) + { + return false; + } + + if ( !pszScriptName || !*pszScriptName ) + { + CGWarning( 0, CON_GROUP_VSCRIPT, "Cannot run script: NULL script name\n" ); + return false; + } + + // Prevent infinite recursion in VM + if ( g_ScriptServerRunScriptDepth > 16 ) + { + CGWarning( 0, CON_GROUP_VSCRIPT, "IncludeScript stack overflow\n" ); + return false; + } + + g_ScriptServerRunScriptDepth++; + HSCRIPT hScript = VScriptCompileScriptAbsolute( pszScriptName, bWarnMissing, pszRootFolderName ); + bool bSuccess = false; + if ( hScript ) + { + bSuccess = ( g_pScriptVM->Run( hScript, hScope ) != SCRIPT_ERROR ); + if ( !bSuccess ) + { + Warning( "Error running script named %s\n", pszScriptName ); + Assert( "Error running script" ); + } + } + g_ScriptServerRunScriptDepth--; + return bSuccess; +} +#endif + + +#ifdef GAME_DLL +#define IsCommandIssuedByServerAdmin() UTIL_IsCommandIssuedByServerAdmin() +#else +#define IsCommandIssuedByServerAdmin() true +#endif + #ifdef CLIENT_DLL CON_COMMAND( script_client, "Run the text as a script" ) #else CON_COMMAND( script, "Run the text as a script" ) #endif { -#ifdef CLIENT_DLL - if ( !engine->IsClientLocalToActiveServer() ) + if ( !IsCommandIssuedByServerAdmin() ) return; -#else - if ( !UTIL_IsCommandIssuedByServerAdmin() ) - return; -#endif if ( !*args[1] ) { +#ifdef MAPBASE_VSCRIPT + CGWarning( 0, CON_GROUP_VSCRIPT, "No function name specified\n" ); +#else Log_Warning( LOG_VScript, "No function name specified\n" ); +#endif return; } if ( !g_pScriptVM ) { +#ifdef MAPBASE_VSCRIPT + CGWarning( 0, CON_GROUP_VSCRIPT, "Scripting disabled or no server running\n" ); +#else Log_Warning( LOG_VScript, "Scripting disabled or no server running\n" ); +#endif return; } @@ -244,31 +412,32 @@ CON_COMMAND( script, "Run the text as a script" ) } } - - #ifdef CLIENT_DLL CON_COMMAND( script_execute_client, "Run a vscript file" ) #else CON_COMMAND( script_execute, "Run a vscript file" ) #endif { -#ifdef CLIENT_DLL - if ( !engine->IsClientLocalToActiveServer() ) + if ( !IsCommandIssuedByServerAdmin() ) return; -#else - if ( !UTIL_IsCommandIssuedByServerAdmin() ) - return; -#endif if ( !*args[1] ) { +#ifdef MAPBASE_VSCRIPT + CGWarning( 0, CON_GROUP_VSCRIPT, "No script specified\n" ); +#else Log_Warning( LOG_VScript, "No script specified\n" ); +#endif return; } if ( !g_pScriptVM ) { +#ifdef MAPBASE_VSCRIPT + CGWarning( 0, CON_GROUP_VSCRIPT, "Scripting disabled or no server running\n" ); +#else Log_Warning( LOG_VScript, "Scripting disabled or no server running\n" ); +#endif return; } @@ -281,17 +450,16 @@ CON_COMMAND( script_debug_client, "Connect the vscript VM to the script debugger CON_COMMAND( script_debug, "Connect the vscript VM to the script debugger" ) #endif { -#ifdef CLIENT_DLL - if ( !engine->IsClientLocalToActiveServer() ) + if ( !IsCommandIssuedByServerAdmin() ) return; -#else - if ( !UTIL_IsCommandIssuedByServerAdmin() ) - return; -#endif if ( !g_pScriptVM ) { +#ifdef MAPBASE_VSCRIPT + CGWarning( 0, CON_GROUP_VSCRIPT, "Scripting disabled or no server running\n" ); +#else Log_Warning( LOG_VScript, "Scripting disabled or no server running\n" ); +#endif return; } g_pScriptVM->ConnectDebugger(); @@ -303,17 +471,16 @@ CON_COMMAND( script_help_client, "Output help for script functions, optionally w CON_COMMAND( script_help, "Output help for script functions, optionally with a search string" ) #endif { -#ifdef CLIENT_DLL - if ( !engine->IsClientLocalToActiveServer() ) + if ( !IsCommandIssuedByServerAdmin() ) return; -#else - if ( !UTIL_IsCommandIssuedByServerAdmin() ) - return; -#endif if ( !g_pScriptVM ) { +#ifdef MAPBASE_VSCRIPT + CGWarning( 0, CON_GROUP_VSCRIPT, "Scripting disabled or no server running\n" ); +#else Log_Warning( LOG_VScript, "Scripting disabled or no server running\n" ); +#endif return; } const char *pszArg1 = "*"; @@ -322,7 +489,11 @@ CON_COMMAND( script_help, "Output help for script functions, optionally with a s pszArg1 = args[1]; } +#ifdef MAPBASE_VSCRIPT + g_pScriptVM->Run( CFmtStr( "__Documentation.PrintHelp( \"%s\" );", pszArg1 ) ); +#else g_pScriptVM->Run( CFmtStr( "PrintHelp( \"%s\" );", pszArg1 ) ); +#endif } #ifdef CLIENT_DLL @@ -331,18 +502,281 @@ CON_COMMAND( script_dump_all_client, "Dump the state of the VM to the console" ) CON_COMMAND( script_dump_all, "Dump the state of the VM to the console" ) #endif { -#ifdef CLIENT_DLL - if ( !engine->IsClientLocalToActiveServer() ) + if ( !IsCommandIssuedByServerAdmin() ) return; -#else - if ( !UTIL_IsCommandIssuedByServerAdmin() ) - return; -#endif if ( !g_pScriptVM ) { +#ifdef MAPBASE_VSCRIPT + CGWarning( 0, CON_GROUP_VSCRIPT, "Scripting disabled or no server running\n" ); +#else Log_Warning( LOG_VScript, "Scripting disabled or no server running\n" ); +#endif return; } g_pScriptVM->DumpState(); -} \ No newline at end of file +} + +//----------------------------------------------------------------------------- + +#ifdef MAPBASE_VSCRIPT +void RunAddonScripts() +{ + char searchPaths[4096]; + filesystem->GetSearchPath( "ADDON", true, searchPaths, sizeof( searchPaths ) ); + + for ( char *path = strtok( searchPaths, ";" ); path; path = strtok( NULL, ";" ) ) + { + char folderName[MAX_PATH]; + Q_FileBase( path, folderName, sizeof( folderName ) ); + + // mapspawn_addon + char fullpath[MAX_PATH]; + Q_ComposeFileName( path, "scripts/vscripts/mapspawn_addon", fullpath, sizeof( fullpath ) ); + + VScriptRunScriptAbsolute( fullpath, NULL, false, folderName ); + } +} + +// UNDONE: "autorun" folder +/* +void RunAutorunScripts() +{ + FileFindHandle_t fileHandle; + char szDirectory[MAX_PATH]; + char szFileName[MAX_PATH]; + char szPartialScriptPath[MAX_PATH]; + + // TODO: Scanning for VM extension would make this more efficient + Q_strncpy( szDirectory, "scripts/vscripts/autorun/*", sizeof( szDirectory ) ); + + const char *pszScriptFile = filesystem->FindFirst( szDirectory, &fileHandle ); + while (pszScriptFile && fileHandle != FILESYSTEM_INVALID_FIND_HANDLE) + { + Q_FileBase( pszScriptFile, szFileName, sizeof( szFileName ) ); + Q_snprintf( szPartialScriptPath, sizeof( szPartialScriptPath ), "autorun/%s", szFileName ); + VScriptRunScript( szPartialScriptPath ); + + pszScriptFile = filesystem->FindNext( fileHandle ); + } + + // Non-shared scripts +#ifdef CLIENT_DLL + Q_strncpy( szDirectory, "scripts/vscripts/autorun/client/*", sizeof( szDirectory ) ); +#else + Q_strncpy( szDirectory, "scripts/vscripts/autorun/server/*", sizeof( szDirectory ) ); +#endif + + pszScriptFile = filesystem->FindFirst( szDirectory, &fileHandle ); + while (pszScriptFile && fileHandle != FILESYSTEM_INVALID_FIND_HANDLE) + { + Q_FileBase( pszScriptFile, szFileName, sizeof( szFileName ) ); +#ifdef CLIENT_DLL + Q_snprintf( szPartialScriptPath, sizeof( szPartialScriptPath ), "autorun/client/%s", szFileName ); +#else + Q_snprintf( szPartialScriptPath, sizeof( szPartialScriptPath ), "autorun/server/%s", szFileName ); +#endif + VScriptRunScript( szPartialScriptPath ); + + pszScriptFile = filesystem->FindNext( fileHandle ); + } +} +*/ +#endif + +//----------------------------------------------------------------------------- + +static short VSCRIPT_SERVER_SAVE_RESTORE_VERSION = 2; + +//----------------------------------------------------------------------------- + +class CVScriptSaveRestoreBlockHandler : public CDefSaveRestoreBlockHandler +{ +public: + CVScriptSaveRestoreBlockHandler() : + m_InstanceMap( DefLessFunc(const char *) ) + { + } + const char *GetBlockName() + { +#ifdef CLIENT_DLL + return "VScriptClient"; +#else + return "VScriptServer"; +#endif + } + + //--------------------------------- + + void Save( ISave *pSave ) + { + pSave->StartBlock(); + + int temp = g_pScriptVM != NULL; + pSave->WriteInt( &temp ); + if ( g_pScriptVM ) + { + temp = g_pScriptVM->GetLanguage(); + pSave->WriteInt( &temp ); + CUtlBuffer buffer; + g_pScriptVM->WriteState( &buffer ); + temp = buffer.TellPut(); + pSave->WriteInt( &temp ); + if ( temp > 0 ) + { + pSave->WriteData( (const char *)buffer.Base(), temp ); + } + } + + pSave->EndBlock(); + } + + //--------------------------------- + + void WriteSaveHeaders( ISave *pSave ) + { + pSave->WriteShort( &VSCRIPT_SERVER_SAVE_RESTORE_VERSION ); + } + + //--------------------------------- + + void ReadRestoreHeaders( IRestore *pRestore ) + { + // No reason why any future version shouldn't try to retain backward compatability. The default here is to not do so. + short version; + pRestore->ReadShort( &version ); + m_fDoLoad = ( version == VSCRIPT_SERVER_SAVE_RESTORE_VERSION ); + } + + //--------------------------------- + + void Restore( IRestore *pRestore, bool createPlayers ) + { + if ( !m_fDoLoad && g_pScriptVM ) + { + return; + } +#ifdef CLIENT_DLL + C_BaseEntity *pEnt = ClientEntityList().FirstBaseEntity(); +#else + CBaseEntity *pEnt = gEntList.FirstEnt(); +#endif + while ( pEnt ) + { + if ( pEnt->m_iszScriptId != NULL_STRING ) + { +#ifndef MAPBASE_VSCRIPT + g_pScriptVM->RegisterClass( pEnt->GetScriptDesc() ); +#endif + m_InstanceMap.Insert( STRING( pEnt->m_iszScriptId ), pEnt ); + } +#ifdef CLIENT_DLL + pEnt = ClientEntityList().NextBaseEntity( pEnt ); +#else + pEnt = gEntList.NextEnt( pEnt ); +#endif + } + + pRestore->StartBlock(); + if ( pRestore->ReadInt() && pRestore->ReadInt() == g_pScriptVM->GetLanguage() ) + { + int nBytes = pRestore->ReadInt(); + if ( nBytes > 0 ) + { + CUtlBuffer buffer; + buffer.EnsureCapacity( nBytes ); + pRestore->ReadData( (char *)buffer.AccessForDirectRead( nBytes ), nBytes, 0 ); + g_pScriptVM->ReadState( &buffer ); + } + } + pRestore->EndBlock(); + } + + void PostRestore( void ) + { + for ( int i = m_InstanceMap.FirstInorder(); i != m_InstanceMap.InvalidIndex(); i = m_InstanceMap.NextInorder( i ) ) + { + CBaseEntity *pEnt = m_InstanceMap[i]; + if ( pEnt->m_hScriptInstance ) + { + ScriptVariant_t variant; + if ( g_pScriptVM->GetValue( STRING(pEnt->m_iszScriptId), &variant ) && variant.GetType() == FIELD_HSCRIPT) + { + pEnt->m_ScriptScope.Init( variant.Get(), false); +#ifdef MAPBASE_VSCRIPT + g_pScriptVM->SetValue( pEnt->m_ScriptScope, "self", pEnt->m_hScriptInstance ); +#endif +#ifndef CLIENT_DLL + pEnt->RunPrecacheScripts(); +#endif + } + } + else + { + // Script system probably has no internal references + pEnt->m_iszScriptId = NULL_STRING; + } + } + m_InstanceMap.Purge(); + +#ifdef MAPBASE_VSCRIPT + GetScriptHookManager().OnRestore(); +#endif + +#if defined(MAPBASE_VSCRIPT) && defined(CLIENT_DLL) + VScriptSaveRestoreUtil_OnVMRestore(); +#endif + } + + + CUtlMap m_InstanceMap; + +private: + bool m_fDoLoad; +}; + +//----------------------------------------------------------------------------- + +CVScriptSaveRestoreBlockHandler g_VScriptSaveRestoreBlockHandler; + +//------------------------------------- + +ISaveRestoreBlockHandler *GetVScriptSaveRestoreBlockHandler() +{ + return &g_VScriptSaveRestoreBlockHandler; +} + +//----------------------------------------------------------------------------- + +bool CBaseEntityScriptInstanceHelper::ToString( void *p, char *pBuf, int bufSize ) +{ + CBaseEntity *pEntity = (CBaseEntity *)p; +#ifdef CLIENT_DLL + if ( pEntity->GetEntityName() && pEntity->GetEntityName()[0] ) +#else + if ( pEntity->GetEntityName() != NULL_STRING ) +#endif + { + V_snprintf( pBuf, bufSize, "([%d] %s: %s)", pEntity->entindex(), pEntity->GetClassname(), STRING( pEntity->GetEntityName() ) ); + } + else + { + V_snprintf( pBuf, bufSize, "([%d] %s)", pEntity->entindex(), pEntity->GetClassname() ); + } + return true; +} + +void *CBaseEntityScriptInstanceHelper::BindOnRead( HSCRIPT hInstance, void *pOld, const char *pszId ) +{ + int iEntity = g_VScriptSaveRestoreBlockHandler.m_InstanceMap.Find( pszId ); + if ( iEntity != g_VScriptSaveRestoreBlockHandler.m_InstanceMap.InvalidIndex() ) + { + CBaseEntity *pEnt = g_VScriptSaveRestoreBlockHandler.m_InstanceMap[iEntity]; + pEnt->m_hScriptInstance = hInstance; + return pEnt; + } + return NULL; +} + + +CBaseEntityScriptInstanceHelper g_BaseEntityScriptInstanceHelper; diff --git a/src/game/shared/vscript_shared.h b/src/game/shared/vscript_shared.h index eae12a3b..9a742fb6 100644 --- a/src/game/shared/vscript_shared.h +++ b/src/game/shared/vscript_shared.h @@ -13,6 +13,13 @@ #pragma once #endif +#if defined(MAPBASE_VSCRIPT) && (defined(TF_DLL) || defined(TF_CLIENT_DLL)) +// Mapbase and TF2 VScript have functions with the same names which do different things and/or take different parameters. +// If enabled, the code will prioritize the syntax from TF2 VScript. +// If disabled, it will prioritize the syntax from Mapbase VScript. +#define VSCRIPT_PRIORITIZE_TF2_SYNTAX 1 +#endif + DECLARE_LOGGING_CHANNEL( LOG_VScript ); extern IScriptVM * g_pScriptVM; @@ -37,6 +44,7 @@ bool IsEntityCreationAllowedInScripts( void ); // //----------------------------------------------------------------------------- +#ifndef MAPBASE_VSCRIPT // TODO: Add to Mapbase VScript? This requires ReleaseScope in interface /*! An auto-referenced wrapper for HSCRIPTs to automatically handle refcount bookkeeping and make storing script references in C++ objects safe. @@ -153,5 +161,27 @@ inline CScriptAutoRef & CScriptAutoRef::operator=( const HSCRIPT &other ) Set( other ); return *this; } +#endif + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +class ISaveRestoreBlockHandler; +ISaveRestoreBlockHandler *GetVScriptSaveRestoreBlockHandler(); + +class CBaseEntityScriptInstanceHelper : public IScriptInstanceHelper +{ + bool ToString( void *p, char *pBuf, int bufSize ); + void *BindOnRead( HSCRIPT hInstance, void *pOld, const char *pszId ); +}; + +extern CBaseEntityScriptInstanceHelper g_BaseEntityScriptInstanceHelper; + +#ifdef MAPBASE_VSCRIPT +void RegisterSharedScriptConstants(); +void RegisterSharedScriptFunctions(); + +void RunAddonScripts(); +#endif #endif // VSCRIPT_SHARED_H diff --git a/src/game/shared/weapon_parse.cpp b/src/game/shared/weapon_parse.cpp index 368ba802..9e12db39 100644 --- a/src/game/shared/weapon_parse.cpp +++ b/src/game/shared/weapon_parse.cpp @@ -76,10 +76,12 @@ extern itemFlags_t g_ItemFlags[8]; static CUtlDict< FileWeaponInfo_t*, unsigned short > m_WeaponInfoDatabase; +#ifndef MAPBASE // Mapbase makes weapons in the same slot & position swap each other out, which is a feature mods can intentionally use. #ifdef _DEBUG // used to track whether or not two weapons have been mistakenly assigned the wrong slot bool g_bUsedWeaponSlots[MAX_WEAPON_SLOTS][MAX_WEAPON_POSITIONS] = { { false } }; +#endif #endif //----------------------------------------------------------------------------- @@ -154,9 +156,11 @@ void ResetFileWeaponInfoDatabase( void ) } m_WeaponInfoDatabase.RemoveAll(); +#ifndef MAPBASE // Mapbase makes weapons in the same slot & position swap each other out, which is a feature mods can intentionally use. #ifdef _DEBUG memset(g_bUsedWeaponSlots, 0, sizeof(g_bUsedWeaponSlots)); #endif +#endif } #endif @@ -279,7 +283,11 @@ bool ReadWeaponDataFromFileForSlot( IFileSystem* pFilesystem, const char *szWeap FileWeaponInfo_t *pFileInfo = GetFileWeaponInfoFromHandle( *phandle ); Assert( pFileInfo ); +#ifdef MAPBASE + if ( pFileInfo->bParsedScript && !pFileInfo->bCustom ) +#else if ( pFileInfo->bParsedScript ) +#endif return true; char sz[128]; @@ -296,6 +304,9 @@ bool ReadWeaponDataFromFileForSlot( IFileSystem* pFilesystem, const char *szWeap if ( !pKV ) return false; +#ifdef MAPBASE + pFileInfo->bCustom = false; +#endif pFileInfo->Parse( pKV, szWeaponName ); pKV->deleteThis(); @@ -303,6 +314,49 @@ bool ReadWeaponDataFromFileForSlot( IFileSystem* pFilesystem, const char *szWeap return true; } +#ifdef MAPBASE +extern const char *g_MapName; + +bool ReadCustomWeaponDataFromFileForSlot( IFileSystem* filesystem, const char *szWeaponName, WEAPON_FILE_INFO_HANDLE *phandle, const unsigned char *pICEKey ) +{ + if ( !phandle ) + { + Assert( 0 ); + return false; + } + + *phandle = FindWeaponInfoSlot( szWeaponName ); + FileWeaponInfo_t *pFileInfo = GetFileWeaponInfoFromHandle( *phandle ); + Assert( pFileInfo ); + + // Just parse the custom script anyway even if it was already loaded. This is because after one is loaded, + // there's no way of distinguishing between maps with no custom scripts and maps with their own new custom scripts. + //if ( pFileInfo->bParsedScript && pFileInfo->bCustom ) + // return true; + + char sz[128]; + Q_snprintf( sz, sizeof( sz ), "maps/%s_%s", g_MapName, szWeaponName ); + + KeyValues *pKV = ReadEncryptedKVFile( filesystem, sz, pICEKey, +#if defined( DOD_DLL ) + true // Only read .ctx files! +#else + false +#endif + ); + + if ( !pKV ) + return false; + + pFileInfo->bCustom = true; + pFileInfo->Parse( pKV, szWeaponName ); + + pKV->deleteThis(); + + return true; +} +#endif + //----------------------------------------------------------------------------- // FileWeaponInfo_t implementation. @@ -347,12 +401,29 @@ FileWeaponInfo_t::FileWeaponInfo_t() bShowUsageHint = false; m_bAllowFlipping = true; m_bBuiltRightHanded = true; +#ifdef MAPBASE + m_flViewmodelFOV = 0.0f; + m_flBobScale = 1.0f; + m_flSwayScale = 1.0f; + m_flSwaySpeedScale = 1.0f; + szDroppedModel[0] = 0; + m_bUsesHands = false; + m_nWeaponRestriction = WPNRESTRICT_NONE; +#endif } #ifdef CLIENT_DLL extern ConVar hud_fastswitch; #endif +#ifdef MAPBASE +const char* pWeaponRestrictions[NUM_WEAPON_RESTRICTION_TYPES] = { + "none", + "player_only", + "npc_only", +}; +#endif // MAPBASE + void FileWeaponInfo_t::Parse( KeyValues *pKeyValuesData, const char *szWeaponName ) { // Okay, we tried at least once to look this up... @@ -412,6 +483,31 @@ void FileWeaponInfo_t::Parse( KeyValues *pKeyValuesData, const char *szWeaponNam m_bAllowFlipping = ( pKeyValuesData->GetInt( "AllowFlipping", 1 ) != 0 ) ? true : false; m_bMeleeWeapon = ( pKeyValuesData->GetInt( "MeleeWeapon", 0 ) != 0 ) ? true : false; +#ifdef MAPBASE + m_flViewmodelFOV = pKeyValuesData->GetFloat( "viewmodel_fov", 0.0f ); + m_flBobScale = pKeyValuesData->GetFloat( "bob_scale", 1.0f ); + m_flSwayScale = pKeyValuesData->GetFloat( "sway_scale", 1.0f ); + m_flSwaySpeedScale = pKeyValuesData->GetFloat( "sway_speed_scale", 1.0f ); + + Q_strncpy( szDroppedModel, pKeyValuesData->GetString( "droppedmodel" ), MAX_WEAPON_STRING ); + + m_bUsesHands = ( pKeyValuesData->GetInt( "uses_hands", 0 ) != 0 ) ? true : false; + + const char* pszRestrictString = pKeyValuesData->GetString("usage_restriction", nullptr); + if (pszRestrictString) + { + for (int i = 0; i < NUM_WEAPON_RESTRICTION_TYPES; i++) + { + if (V_stricmp(pszRestrictString, pWeaponRestrictions[i]) == 0) + { + m_nWeaponRestriction = i; + break; + } + } + } +#endif + +#ifndef MAPBASE // Mapbase makes weapons in the same slot & position swap each other out, which is a feature mods can intentionally use. #if defined(_DEBUG) && defined(HL2_CLIENT_DLL) // make sure two weapons aren't in the same slot & position if ( iSlot >= MAX_WEAPON_SLOTS || @@ -428,6 +524,7 @@ void FileWeaponInfo_t::Parse( KeyValues *pKeyValuesData, const char *szWeaponNam } g_bUsedWeaponSlots[iSlot][iPosition] = true; } +#endif #endif // Primary ammo used diff --git a/src/game/shared/weapon_parse.h b/src/game/shared/weapon_parse.h index f1d4c924..4ba7b02c 100644 --- a/src/game/shared/weapon_parse.h +++ b/src/game/shared/weapon_parse.h @@ -58,6 +58,17 @@ int GetWeaponSoundFromString( const char *pszString ); class CHudTexture; class KeyValues; +#ifdef MAPBASE +enum WeaponUsageRestricions_e +{ + WPNRESTRICT_NONE = 0, + WPNRESTRICT_PLAYER_ONLY, + WPNRESTRICT_NPCS_ONLY, + + NUM_WEAPON_RESTRICTION_TYPES +}; +#endif // MAPBASE + //----------------------------------------------------------------------------- // Purpose: Contains the data read from the weapon's script file. // It's cached so we only read each weapon's script file once. @@ -77,6 +88,10 @@ public: public: bool bParsedScript; bool bLoadedHudElements; +#ifdef MAPBASE + // Indicates the currently loaded data is from a map-specific script and should be flushed. + bool bCustom; +#endif // SHARED char szClassName[MAX_WEAPON_STRING]; @@ -112,6 +127,19 @@ public: bool m_bAllowFlipping; // False to disallow flipping the model, regardless of whether // it is built left or right handed. +#ifdef MAPBASE + float m_flViewmodelFOV; + float m_flBobScale; + float m_flSwayScale; + float m_flSwaySpeedScale; + + char szDroppedModel[MAX_WEAPON_STRING]; // Model of this weapon when dropped on the ground + + bool m_bUsesHands; + + int m_nWeaponRestriction; +#endif + // CLIENT DLL // Sprite data, read from the data file int iSpriteCount; @@ -136,6 +164,12 @@ public: bool ReadWeaponDataFromFileForSlot( IFileSystem* filesystem, const char *szWeaponName, WEAPON_FILE_INFO_HANDLE *phandle, const unsigned char *pICEKey = NULL ); +#ifdef MAPBASE +// For map-specific weapon data +bool ReadCustomWeaponDataFromFileForSlot( IFileSystem* filesystem, const char *szWeaponName, + WEAPON_FILE_INFO_HANDLE *phandle, const unsigned char *pICEKey = NULL ); +#endif + // If weapon info has been loaded for the specified class name, this returns it. WEAPON_FILE_INFO_HANDLE LookupWeaponInfoSlot( const char *name ); diff --git a/src/game/shared/weapon_proficiency.h b/src/game/shared/weapon_proficiency.h index 6532b79c..e91f67cf 100644 --- a/src/game/shared/weapon_proficiency.h +++ b/src/game/shared/weapon_proficiency.h @@ -19,6 +19,11 @@ struct WeaponProficiencyInfo_t enum WeaponProficiency_t { +#ifdef MAPBASE + // For the override + WEAPON_PROFICIENCY_INVALID = -1, +#endif + WEAPON_PROFICIENCY_POOR = 0, WEAPON_PROFICIENCY_AVERAGE, WEAPON_PROFICIENCY_GOOD, diff --git a/src/lib/public/discord-rpc.lib b/src/lib/public/discord-rpc.lib new file mode 100644 index 00000000..d8b6689f Binary files /dev/null and b/src/lib/public/discord-rpc.lib differ diff --git a/src/materialsystem/stdshaders/decalmodulate_dx9.cpp b/src/materialsystem/stdshaders/decalmodulate_dx9.cpp new file mode 100644 index 00000000..8e90c056 --- /dev/null +++ b/src/materialsystem/stdshaders/decalmodulate_dx9.cpp @@ -0,0 +1,234 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Header: $ +// $NoKeywords: $ +//===========================================================================// + +#include "BaseVSShader.h" +#include "cpp_shader_constant_register_map.h" + +#include "VertexLit_and_unlit_Generic_vs20.inc" +#include "decalmodulate_ps20.inc" +#include "decalmodulate_ps20b.inc" + +#ifndef _X360 +#include "vertexlit_and_unlit_generic_vs30.inc" +#include "decalmodulate_ps30.inc" +#endif + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +DEFINE_FALLBACK_SHADER( DecalModulate, DecalModulate_DX9 ) + +extern ConVar r_flashlight_version2; + +BEGIN_VS_SHADER( DecalModulate_dx9, + "Help for DecalModulate_dx9" ) + + BEGIN_SHADER_PARAMS + END_SHADER_PARAMS + + SHADER_FALLBACK + { + if (g_pHardwareConfig->GetDXSupportLevel() < 90) + return "DecalModulate_DX6"; + return 0; + } + + SHADER_INIT_PARAMS() + { + SET_FLAGS( MATERIAL_VAR_NO_DEBUG_OVERRIDE ); + +#ifndef _X360 + if ( g_pHardwareConfig->HasFastVertexTextures() ) + { + // The vertex shader uses the vertex id stream + SET_FLAGS2( MATERIAL_VAR2_USES_VERTEXID ); + SET_FLAGS2( MATERIAL_VAR2_SUPPORTS_HW_SKINNING ); + } +#endif + } + + SHADER_INIT + { + LoadTexture( BASETEXTURE ); + } + + SHADER_DRAW + { + SHADOW_STATE + { + pShaderShadow->EnableAlphaTest( true ); + pShaderShadow->AlphaFunc( SHADER_ALPHAFUNC_GREATER, 0.0f ); + pShaderShadow->EnableDepthWrites( false ); + pShaderShadow->EnablePolyOffset( SHADER_POLYOFFSET_DECAL ); + pShaderShadow->EnableTexture( SHADER_SAMPLER0, true ); + + // Be sure not to write to dest alpha + pShaderShadow->EnableAlphaWrites( false ); + + // SRGB conversions hose the blend on some hardware, so keep we everything in gamma space + pShaderShadow->EnableSRGBRead( SHADER_SAMPLER0, false ); + pShaderShadow->EnableSRGBWrite( false ); + + pShaderShadow->EnableBlending( true ); + pShaderShadow->BlendFunc( SHADER_BLEND_DST_COLOR, SHADER_BLEND_SRC_COLOR ); + pShaderShadow->DisableFogGammaCorrection( true ); //fog should stay exactly middle grey + FogToGrey(); + +#ifndef _X360 + if ( !g_pHardwareConfig->HasFastVertexTextures() ) +#endif + { + bool bUseStaticControlFlow = g_pHardwareConfig->SupportsStaticControlFlow(); + + DECLARE_STATIC_VERTEX_SHADER( vertexlit_and_unlit_generic_vs20 ); + SET_STATIC_VERTEX_SHADER_COMBO( VERTEXCOLOR, false ); + SET_STATIC_VERTEX_SHADER_COMBO( CUBEMAP, false ); + SET_STATIC_VERTEX_SHADER_COMBO( HALFLAMBERT, false ); + SET_STATIC_VERTEX_SHADER_COMBO( FLASHLIGHT, false ); + SET_STATIC_VERTEX_SHADER_COMBO( SEAMLESS_BASE, false ); + SET_STATIC_VERTEX_SHADER_COMBO( SEAMLESS_DETAIL, false ); + SET_STATIC_VERTEX_SHADER_COMBO( SEPARATE_DETAIL_UVS, false ); + SET_STATIC_VERTEX_SHADER_COMBO( USE_STATIC_CONTROL_FLOW, bUseStaticControlFlow ); + SET_STATIC_VERTEX_SHADER_COMBO( DONT_GAMMA_CONVERT_VERTEX_COLOR, 0 ); + SET_STATIC_VERTEX_SHADER_COMBO( TREESWAY, 0 ); + SET_STATIC_VERTEX_SHADER( vertexlit_and_unlit_generic_vs20 ); + + if( g_pHardwareConfig->SupportsPixelShaders_2_b() ) + { + DECLARE_STATIC_PIXEL_SHADER( decalmodulate_ps20b ); + SET_STATIC_PIXEL_SHADER( decalmodulate_ps20b ); + } + else + { + DECLARE_STATIC_PIXEL_SHADER( decalmodulate_ps20 ); + SET_STATIC_PIXEL_SHADER( decalmodulate_ps20 ); + } + } +#ifndef _X360 + else + { + DECLARE_STATIC_VERTEX_SHADER( vertexlit_and_unlit_generic_vs30 ); + SET_STATIC_VERTEX_SHADER_COMBO( VERTEXCOLOR, false ); + SET_STATIC_VERTEX_SHADER_COMBO( CUBEMAP, false ); + SET_STATIC_VERTEX_SHADER_COMBO( HALFLAMBERT, false ); + SET_STATIC_VERTEX_SHADER_COMBO( FLASHLIGHT, false ); + SET_STATIC_VERTEX_SHADER_COMBO( SEAMLESS_BASE, false ); + SET_STATIC_VERTEX_SHADER_COMBO( SEAMLESS_DETAIL, false ); + SET_STATIC_VERTEX_SHADER_COMBO( SEPARATE_DETAIL_UVS, false ); + SET_STATIC_VERTEX_SHADER_COMBO( DECAL, true ); + SET_STATIC_VERTEX_SHADER_COMBO( DONT_GAMMA_CONVERT_VERTEX_COLOR, 0 ); + SET_STATIC_VERTEX_SHADER_COMBO( TREESWAY, 0 ); + SET_STATIC_VERTEX_SHADER( vertexlit_and_unlit_generic_vs30 ); + + DECLARE_STATIC_PIXEL_SHADER( decalmodulate_ps30 ); + SET_STATIC_PIXEL_SHADER( decalmodulate_ps30 ); + } +#endif + + // Set stream format (note that this shader supports compression) + unsigned int flags = VERTEX_POSITION | VERTEX_FORMAT_COMPRESSED; +#ifndef _X360 + // The VS30 shader offsets decals along the normal (for morphed geom) + flags |= g_pHardwareConfig->HasFastVertexTextures() ? VERTEX_NORMAL : 0; +#endif + int pTexCoordDim[3] = { 2, 0, 3 }; + int nTexCoordCount = 1; + int userDataSize = 0; + +#ifndef _X360 + if ( g_pHardwareConfig->HasFastVertexTextures() ) + { + nTexCoordCount = 3; + } +#endif + + pShaderShadow->VertexShaderVertexFormat( flags, nTexCoordCount, pTexCoordDim, userDataSize ); + } + DYNAMIC_STATE + { + if ( pShaderAPI->InFlashlightMode() && ( !IsX360() && ( r_flashlight_version2.GetInt() == 0 ) ) ) + { + // Don't draw anything for the flashlight pass + Draw( false ); + return; + } + + BindTexture( SHADER_SAMPLER0, BASETEXTURE, FRAME ); + + // Set an identity base texture transformation + Vector4D transformation[2]; + transformation[0].Init( 1.0f, 0.0f, 0.0f, 0.0f ); + transformation[1].Init( 0.0f, 1.0f, 0.0f, 0.0f ); + pShaderAPI->SetVertexShaderConstant( VERTEX_SHADER_SHADER_SPECIFIC_CONST_0, transformation[0].Base(), 2 ); + + MaterialFogMode_t fogType = s_pShaderAPI->GetSceneFogMode(); + int fogIndex = ( fogType == MATERIAL_FOG_LINEAR_BELOW_FOG_Z ) ? 1 : 0; + + pShaderAPI->SetPixelShaderFogParams( PSREG_FOG_PARAMS ); + + float vEyePos_SpecExponent[4]; + pShaderAPI->GetWorldSpaceCameraPosition( vEyePos_SpecExponent ); + vEyePos_SpecExponent[3] = 0.0f; + pShaderAPI->SetPixelShaderConstant( PSREG_EYEPOS_SPEC_EXPONENT, vEyePos_SpecExponent, 1 ); + +#ifndef _X360 + if ( !g_pHardwareConfig->HasFastVertexTextures() ) +#endif + { + DECLARE_DYNAMIC_VERTEX_SHADER( vertexlit_and_unlit_generic_vs20 ); + SET_DYNAMIC_VERTEX_SHADER_COMBO( DYNAMIC_LIGHT, 0 ); // Use simplest possible vertex lighting, since ps is so simple + SET_DYNAMIC_VERTEX_SHADER_COMBO( STATIC_LIGHT_VERTEX, 0 ); // + SET_DYNAMIC_VERTEX_SHADER_COMBO( STATIC_LIGHT_LIGHTMAP, 0); // + SET_DYNAMIC_VERTEX_SHADER_COMBO( DOWATERFOG, fogIndex ); + SET_DYNAMIC_VERTEX_SHADER_COMBO( SKINNING, 0 ); + SET_DYNAMIC_VERTEX_SHADER_COMBO( LIGHTING_PREVIEW, 0 ); + SET_DYNAMIC_VERTEX_SHADER_COMBO( COMPRESSED_VERTS, (int)vertexCompression ); + SET_DYNAMIC_VERTEX_SHADER_COMBO( NUM_LIGHTS, 0 ); + SET_DYNAMIC_VERTEX_SHADER( vertexlit_and_unlit_generic_vs20 ); + + if( g_pHardwareConfig->SupportsPixelShaders_2_b() ) + { + DECLARE_DYNAMIC_PIXEL_SHADER( decalmodulate_ps20b ); + SET_DYNAMIC_PIXEL_SHADER_COMBO( PIXELFOGTYPE, pShaderAPI->GetPixelFogCombo1( true ) ); + SET_DYNAMIC_PIXEL_SHADER( decalmodulate_ps20b ); + } + else + { + DECLARE_DYNAMIC_PIXEL_SHADER( decalmodulate_ps20 ); + SET_DYNAMIC_PIXEL_SHADER_COMBO( PIXELFOGTYPE, pShaderAPI->GetPixelFogCombo() ); + SET_DYNAMIC_PIXEL_SHADER( decalmodulate_ps20 ); + } + } +#ifndef _X360 + else + { + SetHWMorphVertexShaderState( VERTEX_SHADER_SHADER_SPECIFIC_CONST_6, VERTEX_SHADER_SHADER_SPECIFIC_CONST_7, SHADER_VERTEXTEXTURE_SAMPLER0 ); + + DECLARE_DYNAMIC_VERTEX_SHADER( vertexlit_and_unlit_generic_vs30 ); + SET_DYNAMIC_VERTEX_SHADER_COMBO( DYNAMIC_LIGHT, 0 ); // Use simplest possible vertex lighting, since ps is so simple + SET_DYNAMIC_VERTEX_SHADER_COMBO( STATIC_LIGHT_VERTEX, 0 ); // + SET_DYNAMIC_VERTEX_SHADER_COMBO( STATIC_LIGHT_LIGHTMAP, 0); // + SET_DYNAMIC_VERTEX_SHADER_COMBO( DOWATERFOG, fogIndex ); + SET_DYNAMIC_VERTEX_SHADER_COMBO( SKINNING, pShaderAPI->GetCurrentNumBones() > 0 ); + SET_DYNAMIC_VERTEX_SHADER_COMBO( LIGHTING_PREVIEW, 0 ); + SET_DYNAMIC_VERTEX_SHADER_COMBO( MORPHING, pShaderAPI->IsHWMorphingEnabled() ); + SET_DYNAMIC_VERTEX_SHADER_COMBO( COMPRESSED_VERTS, (int)vertexCompression ); + SET_DYNAMIC_VERTEX_SHADER( vertexlit_and_unlit_generic_vs30 ); + + DECLARE_DYNAMIC_PIXEL_SHADER( decalmodulate_ps30 ); + SET_DYNAMIC_PIXEL_SHADER_COMBO( PIXELFOGTYPE, pShaderAPI->GetPixelFogCombo1( true ) ); + SET_DYNAMIC_PIXEL_SHADER( decalmodulate_ps30 ); + + bool bUnusedTexCoords[3] = { false, false, !pShaderAPI->IsHWMorphingEnabled() }; + pShaderAPI->MarkUnusedVertexFields( 0, 3, bUnusedTexCoords ); + } +#endif + } + Draw( ); + } +END_SHADER diff --git a/src/materialsystem/stdshaders/engine_post_dx9.cpp b/src/materialsystem/stdshaders/engine_post_dx9.cpp new file mode 100644 index 00000000..66a290f7 --- /dev/null +++ b/src/materialsystem/stdshaders/engine_post_dx9.cpp @@ -0,0 +1,227 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#include "BaseVSShader.h" + +#include "screenspaceeffect_vs20.inc" +#include "engine_post_ps20.inc" +#include "engine_post_ps20b.inc" + +#include "../materialsystem_global.h" + + +DEFINE_FALLBACK_SHADER( Engine_Post, Engine_Post_dx9 ) +BEGIN_VS_SHADER_FLAGS( Engine_Post_dx9, "Engine post-processing effects (software anti-aliasing, bloom, color-correction", SHADER_NOT_EDITABLE ) + BEGIN_SHADER_PARAMS + SHADER_PARAM( FBTEXTURE, SHADER_PARAM_TYPE_TEXTURE, "_rt_FullFrameFB", "Full framebuffer texture" ) + SHADER_PARAM( AAENABLE, SHADER_PARAM_TYPE_BOOL, "0", "Enable software anti-aliasing" ) + SHADER_PARAM( AAINTERNAL1, SHADER_PARAM_TYPE_VEC4, "[0 0 0 0]", "Internal anti-aliasing values set via material proxy" ) + SHADER_PARAM( AAINTERNAL2, SHADER_PARAM_TYPE_VEC4, "[0 0 0 0]", "Internal anti-aliasing values set via material proxy" ) + SHADER_PARAM( AAINTERNAL3, SHADER_PARAM_TYPE_VEC4, "[0 0 0 0]", "Internal anti-aliasing values set via material proxy" ) + SHADER_PARAM( BLOOMENABLE, SHADER_PARAM_TYPE_BOOL, "1", "Enable bloom" ) + END_SHADER_PARAMS + + SHADER_INIT_PARAMS() + { + if( !params[ AAENABLE ]->IsDefined() ) + { + params[ AAENABLE ]->SetIntValue( 0 ); + } + if( !params[ AAINTERNAL1 ]->IsDefined() ) + { + params[ AAINTERNAL1 ]->SetVecValue( 0, 0, 0, 0 ); + } + if( !params[ AAINTERNAL2 ]->IsDefined() ) + { + params[ AAINTERNAL2 ]->SetVecValue( 0, 0, 0, 0 ); + } + if( !params[ AAINTERNAL3 ]->IsDefined() ) + { + params[ AAINTERNAL3 ]->SetVecValue( 0, 0, 0, 0 ); + } + if( !params[ BLOOMENABLE ]->IsDefined() ) + { + params[ BLOOMENABLE ]->SetIntValue( 1 ); + } + SET_FLAGS2( MATERIAL_VAR2_NEEDS_FULL_FRAME_BUFFER_TEXTURE ); + } + + SHADER_FALLBACK + { + // This shader should not be *used* unless we're >= DX9 (bloomadd.vmt/screenspace_general_dx8 should be used for DX8) + return 0; + } + + SHADER_INIT + { + if ( params[BASETEXTURE]->IsDefined() ) + { + LoadTexture( BASETEXTURE ); + } + if ( params[FBTEXTURE]->IsDefined() ) + { + LoadTexture( FBTEXTURE ); + } + } + + SHADER_DRAW + { + SHADOW_STATE + { + // This shader uses opaque blending, but needs to match the behaviour of bloom_add/screen_spacegeneral, + // which uses additive blending (and is used when bloom is enabled but col-correction and AA are not). + // BUT! + // Hardware sRGB blending is incorrect (on pre-DX10 cards, sRGB values are added directly). + // SO... + // When doing the bloom addition in the pixel shader, we need to emulate that incorrect + // behaviour - by turning sRGB read OFF for the FB texture and by turning sRGB write OFF + // (which is fine, since the AA process works better on an sRGB framebuffer than a linear + // one; gamma colours more closely match luminance perception. The color-correction process + // has always taken gamma-space values as input anyway). + + // On OpenGL OSX, we MUST do sRGB reads from the bloom and full framebuffer textures AND sRGB + // writes on the way out to the framebuffer. Hence, our colors are linear in the shader. + // Given this, we use the LINEAR_INPUTS combo to convert to sRGB for the purposes of color + // correction, since that is how the color correction textures are authored. + bool bLinearInput = IsOSX() && g_pHardwareConfig->CanDoSRGBReadFromRTs(); + bool bLinearOutput = IsOSX() && !g_pHardwareConfig->FakeSRGBWrite() && g_pHardwareConfig->CanDoSRGBReadFromRTs(); + + bool bForceSRGBReadsAndWrites = IsOSX() && g_pHardwareConfig->CanDoSRGBReadFromRTs(); + + pShaderShadow->EnableBlending( false ); + + // The (sRGB) bloom texture is bound to sampler 0 + pShaderShadow->EnableTexture( SHADER_SAMPLER0, true ); + pShaderShadow->EnableSRGBRead( SHADER_SAMPLER0, bForceSRGBReadsAndWrites ); + pShaderShadow->EnableSRGBWrite( bForceSRGBReadsAndWrites ); + + // The (sRGB) full framebuffer texture is bound to sampler 1: + pShaderShadow->EnableTexture( SHADER_SAMPLER1, true ); + pShaderShadow->EnableSRGBRead( SHADER_SAMPLER1, bForceSRGBReadsAndWrites ); + + // Up to 4 (sRGB) color-correction lookup textures are bound to samplers 2-5: + pShaderShadow->EnableTexture( SHADER_SAMPLER2, true ); + pShaderShadow->EnableTexture( SHADER_SAMPLER3, true ); + pShaderShadow->EnableTexture( SHADER_SAMPLER4, true ); + pShaderShadow->EnableTexture( SHADER_SAMPLER5, true ); + pShaderShadow->EnableSRGBRead( SHADER_SAMPLER2, false ); + pShaderShadow->EnableSRGBRead( SHADER_SAMPLER3, false ); + pShaderShadow->EnableSRGBRead( SHADER_SAMPLER4, false ); + pShaderShadow->EnableSRGBRead( SHADER_SAMPLER5, false ); + + int format = VERTEX_POSITION; + int numTexCoords = 1; + int * pTexCoordDimensions = NULL; + int userDataSize = 0; + pShaderShadow->VertexShaderVertexFormat( format, numTexCoords, pTexCoordDimensions, userDataSize ); + + DECLARE_STATIC_VERTEX_SHADER( screenspaceeffect_vs20 ); + SET_STATIC_VERTEX_SHADER( screenspaceeffect_vs20 ); + + if( g_pHardwareConfig->SupportsPixelShaders_2_b() || g_pHardwareConfig->ShouldAlwaysUseShaderModel2bShaders() ) // GL always goes the ps2b way for this shader, even on "ps20" parts + { + DECLARE_STATIC_PIXEL_SHADER( engine_post_ps20b ); + SET_STATIC_PIXEL_SHADER_COMBO( LINEAR_INPUT, bLinearInput ); + SET_STATIC_PIXEL_SHADER_COMBO( LINEAR_OUTPUT, bLinearOutput ); + SET_STATIC_PIXEL_SHADER( engine_post_ps20b ); + } + else + { + DECLARE_STATIC_PIXEL_SHADER( engine_post_ps20 ); + SET_STATIC_PIXEL_SHADER( engine_post_ps20 ); + } + } + DYNAMIC_STATE + { + BindTexture( SHADER_SAMPLER0, BASETEXTURE, -1 ); + // FIXME: need to set FBTEXTURE to be point-sampled (will speed up this shader significantly on 360) + // and assert that it's set to SHADER_TEXWRAPMODE_CLAMP (since the shader will sample offscreen) + BindTexture( SHADER_SAMPLER1, FBTEXTURE, -1 ); + + ShaderColorCorrectionInfo_t ccInfo; + pShaderAPI->GetCurrentColorCorrection( &ccInfo ); + int colCorrectNumLookups = ccInfo.m_nLookupCount; + for( int i = 0; i < colCorrectNumLookups; i++ ) + { + pShaderAPI->BindStandardTexture( (Sampler_t)(SHADER_SAMPLER2 + i), (StandardTextureId_t)(TEXTURE_COLOR_CORRECTION_VOLUME_0 + i) ); + } + + // Upload 1-pixel X&Y offsets [ (+dX,0,+dY,-dX) is chosen to work with the allowed ps20 swizzles ] + // The shader will sample in a cross (up/down/left/right from the current sample), for 5-tap + // (quality 0) mode and add another 4 samples in a diagonal cross, for 9-tap (quality 1) mode + ITexture * pTarget = params[FBTEXTURE]->GetTextureValue(); + int width = pTarget->GetActualWidth(); + int height = pTarget->GetActualHeight(); + float dX = 1.0f / width; + float dY = 1.0f / height; + float offsets[4] = { +dX, 0, +dY, -dX }; + pShaderAPI->SetPixelShaderConstant( 0, &offsets[0], 1 ); + // Upload AA tweakables: + // x - strength (this can be used to toggle the AA off, or to weaken it where pathological cases are showing) + // y - reduction of 1-pixel-line blurring (blurring of 1-pixel lines causes issues, so it's tunable) + // z - edge threshold multiplier (default 1.0, < 1.0 => more edges softened, > 1.0 => fewer edges softened) + // w - tap offset multiplier (default 1.0, < 1.0 => sharper image, > 1.0 => blurrier image) + float tweakables[4] = { params[ AAINTERNAL1 ]->GetVecValue()[0], + params[ AAINTERNAL1 ]->GetVecValue()[1], + params[ AAINTERNAL3 ]->GetVecValue()[0], + params[ AAINTERNAL3 ]->GetVecValue()[1] }; + pShaderAPI->SetPixelShaderConstant( 1, &tweakables[0], 1 ); + // Upload AA UV transform (converts bloom texture UVs to framebuffer texture UVs) + // NOTE: we swap the order of the z and w components since 'wz' is an allowed ps20 swizzle, but 'zw' is not: + float uvTrans[4] = { params[ AAINTERNAL2 ]->GetVecValue()[0], + params[ AAINTERNAL2 ]->GetVecValue()[1], + params[ AAINTERNAL2 ]->GetVecValue()[3], + params[ AAINTERNAL2 ]->GetVecValue()[2] }; + pShaderAPI->SetPixelShaderConstant( 2, &uvTrans[0], 1 ); + + // Upload color-correction weights: + pShaderAPI->SetPixelShaderConstant( 3, &ccInfo.m_flDefaultWeight ); + pShaderAPI->SetPixelShaderConstant( 4, ccInfo.m_pLookupWeights ); + + int aaEnabled = ( params[ AAINTERNAL1 ]->GetVecValue()[0] == 0.0f ) ? 0 : 1; + int aaReduceOnePixelLineBlur = ( params[ AAINTERNAL1 ]->GetVecValue()[1] == 0.0f ) ? 0 : 1; + int aaQualityMode = (int)params[ AAINTERNAL1 ]->GetVecValue()[2]; +// int aaDebugMode = (int)params[ AAINTERNAL1 ]->GetVecValue()[3]; + int bloomEnabled = ( params[ BLOOMENABLE ]->GetIntValue() == 0 ) ? 0 : 1; + int colCorrectEnabled = ccInfo.m_bIsEnabled; + + float flBloomFactor = bloomEnabled ? 1.0f : 0.0f; + float bloomConstant[4] = { flBloomFactor, flBloomFactor, flBloomFactor, flBloomFactor }; + pShaderAPI->SetPixelShaderConstant( 5, bloomConstant ); + + if ( !colCorrectEnabled ) + { + colCorrectNumLookups = 0; + } + + if( g_pHardwareConfig->SupportsPixelShaders_2_b() || g_pHardwareConfig->ShouldAlwaysUseShaderModel2bShaders() ) // GL always goes the ps2b way for this shader, even on "ps20" parts + { + DECLARE_DYNAMIC_PIXEL_SHADER( engine_post_ps20b ); + SET_DYNAMIC_PIXEL_SHADER_COMBO( AA_ENABLE, aaEnabled ); + SET_DYNAMIC_PIXEL_SHADER_COMBO( AA_QUALITY_MODE, aaQualityMode ); + SET_DYNAMIC_PIXEL_SHADER_COMBO( AA_REDUCE_ONE_PIXEL_LINE_BLUR, aaReduceOnePixelLineBlur ); +// SET_DYNAMIC_PIXEL_SHADER_COMBO( AA_DEBUG_MODE, aaDebugMode ); + SET_DYNAMIC_PIXEL_SHADER_COMBO( COL_CORRECT_NUM_LOOKUPS, colCorrectNumLookups ); + SET_DYNAMIC_PIXEL_SHADER( engine_post_ps20b ); + } + else + { + DECLARE_DYNAMIC_PIXEL_SHADER( engine_post_ps20 ); + SET_DYNAMIC_PIXEL_SHADER_COMBO( AA_ENABLE, aaEnabled ); + SET_DYNAMIC_PIXEL_SHADER_COMBO( AA_QUALITY_MODE, 0 ); // Only enough instruction slots in ps2b + SET_DYNAMIC_PIXEL_SHADER_COMBO( AA_REDUCE_ONE_PIXEL_LINE_BLUR, 0 ); +// SET_DYNAMIC_PIXEL_SHADER_COMBO( AA_DEBUG_MODE, aaDebugMode ); + SET_DYNAMIC_PIXEL_SHADER_COMBO( COL_CORRECT_NUM_LOOKUPS, colCorrectNumLookups ); + SET_DYNAMIC_PIXEL_SHADER( engine_post_ps20 ); + } + + DECLARE_DYNAMIC_VERTEX_SHADER( screenspaceeffect_vs20 ); + SET_DYNAMIC_VERTEX_SHADER( screenspaceeffect_vs20 ); + } + Draw(); + } +END_SHADER diff --git a/src/public/bspfile.h b/src/public/bspfile.h index 9fcc8884..16580dba 100644 --- a/src/public/bspfile.h +++ b/src/public/bspfile.h @@ -59,7 +59,11 @@ // 16 bit short limits #define MAX_MAP_MODELS 1024 #define MAX_MAP_BRUSHES 8192 +#ifdef MAPBASE +#define MAX_MAP_ENTITIES 65536 // According to ficool2, this limit is bogus/not enforced by the engine and can be "safely" raised. +#else #define MAX_MAP_ENTITIES 8192 +#endif #define MAX_MAP_TEXINFO 12288 #define MAX_MAP_TEXDATA 2048 #define MAX_MAP_DISPINFO 2048 @@ -90,9 +94,17 @@ #define MAX_MAP_LIGHTING 0x1000000 #define MAX_MAP_VISIBILITY 0x1000000 // increased BSPVERSION 7 #define MAX_MAP_TEXTURES 1024 +#ifdef MAPBASE +#define MAX_MAP_WORLDLIGHTS 65536 // According to ficool2, this limit is bogus/not enforced by the engine and can be "safely" raised. +#else #define MAX_MAP_WORLDLIGHTS 8192 +#endif #define MAX_MAP_CUBEMAPSAMPLES 1024 +#ifdef MAPBASE +#define MAX_MAP_OVERLAYS 8192 // According to ficool2, this limit is bogus/not enforced by the engine and can be "safely" raised. +#else #define MAX_MAP_OVERLAYS 512 +#endif #define MAX_MAP_WATEROVERLAYS 16384 #define MAX_MAP_TEXDATA_STRING_DATA 256000 #define MAX_MAP_TEXDATA_STRING_TABLE 65536 diff --git a/src/public/const.h b/src/public/const.h index 256dff2d..c239a026 100644 --- a/src/public/const.h +++ b/src/public/const.h @@ -259,7 +259,12 @@ enum SolidFlags_t FSOLID_ROOT_PARENT_ALIGNED = 0x0100, // Collisions are defined in root parent's local coordinate space FSOLID_TRIGGER_TOUCH_DEBRIS = 0x0200, // This trigger will touch debris objects - FSOLID_MAX_BITS = 10 +#ifdef MAPBASE + // From https://developer.valvesoftware.com/wiki/Owner + FSOLID_COLLIDE_WITH_OWNER = 0X0400, +#endif + + FSOLID_MAX_BITS = 11 }; //----------------------------------------------------------------------------- @@ -444,5 +449,17 @@ class CThreadNullMutex; typedef CThreadNullMutex CSourceMutex; #endif +//Tony; added for IPlayerInfo V3. +//Putting all standard possible stances, but GetStance in CBasePlayer will only return standing or ducking by default - +//up to the mod to specify the others, or override what GetStance returns. +enum player_Stance +{ + PINFO_STANCE_STANDING = 0, + PINFO_STANCE_DUCKING, + + PINFO_STANCE_SPRINTING, + PINFO_STANCE_PRONE, +}; + #endif diff --git a/src/public/datamap.h b/src/public/datamap.h index 7a306b34..d57c2271 100644 --- a/src/public/datamap.h +++ b/src/public/datamap.h @@ -320,6 +320,12 @@ struct datamap_t static datamap_t *GetBaseMap(); \ template friend void DataMapAccess(T *, datamap_t **p); \ template friend datamap_t *DataMapInit(T *); + +#define DECLARE_SIMPLE_DATADESC_INSIDE_NAMESPACE() \ + static datamap_t m_DataMap; \ + static datamap_t *GetBaseMap(); \ + template friend void ::DataMapAccess(T *, datamap_t **p); \ + template friend datamap_t *::DataMapInit(T *); #define DECLARE_DATADESC() \ DECLARE_SIMPLE_DATADESC() \ @@ -421,6 +427,8 @@ inline void DataMapAccess(T *ignored, datamap_t **p) *p = &T::m_DataMap; } +template datamap_t* DataMapInit(T*); + //----------------------------------------------------------------------------- class CDatadescGeneratedNameHolder diff --git a/src/public/discord_register.h b/src/public/discord_register.h new file mode 100644 index 00000000..16fb42f3 --- /dev/null +++ b/src/public/discord_register.h @@ -0,0 +1,26 @@ +#pragma once + +#if defined(DISCORD_DYNAMIC_LIB) +#if defined(_WIN32) +#if defined(DISCORD_BUILDING_SDK) +#define DISCORD_EXPORT __declspec(dllexport) +#else +#define DISCORD_EXPORT __declspec(dllimport) +#endif +#else +#define DISCORD_EXPORT __attribute__((visibility("default"))) +#endif +#else +#define DISCORD_EXPORT +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +DISCORD_EXPORT void Discord_Register(const char* applicationId, const char* command); +DISCORD_EXPORT void Discord_RegisterSteamGame(const char* applicationId, const char* steamId); + +#ifdef __cplusplus +} +#endif diff --git a/src/public/discord_rpc.h b/src/public/discord_rpc.h new file mode 100644 index 00000000..3e1441e0 --- /dev/null +++ b/src/public/discord_rpc.h @@ -0,0 +1,87 @@ +#pragma once +#include + +// clang-format off + +#if defined(DISCORD_DYNAMIC_LIB) +# if defined(_WIN32) +# if defined(DISCORD_BUILDING_SDK) +# define DISCORD_EXPORT __declspec(dllexport) +# else +# define DISCORD_EXPORT __declspec(dllimport) +# endif +# else +# define DISCORD_EXPORT __attribute__((visibility("default"))) +# endif +#else +# define DISCORD_EXPORT +#endif + +// clang-format on + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct DiscordRichPresence { + const char* state; /* max 128 bytes */ + const char* details; /* max 128 bytes */ + int64_t startTimestamp; + int64_t endTimestamp; + const char* largeImageKey; /* max 32 bytes */ + const char* largeImageText; /* max 128 bytes */ + const char* smallImageKey; /* max 32 bytes */ + const char* smallImageText; /* max 128 bytes */ + const char* partyId; /* max 128 bytes */ + int partySize; + int partyMax; + const char* matchSecret; /* max 128 bytes */ + const char* joinSecret; /* max 128 bytes */ + const char* spectateSecret; /* max 128 bytes */ + int8_t instance; +} DiscordRichPresence; + +typedef struct DiscordUser { + const char* userId; + const char* username; + const char* discriminator; + const char* avatar; +} DiscordUser; + +typedef struct DiscordEventHandlers { + void (*ready)(const DiscordUser* request); + void (*disconnected)(int errorCode, const char* message); + void (*errored)(int errorCode, const char* message); + void (*joinGame)(const char* joinSecret); + void (*spectateGame)(const char* spectateSecret); + void (*joinRequest)(const DiscordUser* request); +} DiscordEventHandlers; + +#define DISCORD_REPLY_NO 0 +#define DISCORD_REPLY_YES 1 +#define DISCORD_REPLY_IGNORE 2 + +DISCORD_EXPORT void Discord_Initialize(const char* applicationId, + DiscordEventHandlers* handlers, + int autoRegister, + const char* optionalSteamId); +DISCORD_EXPORT void Discord_Shutdown(void); + +/* checks for incoming messages, dispatches callbacks */ +DISCORD_EXPORT void Discord_RunCallbacks(void); + +/* If you disable the lib starting its own io thread, you'll need to call this from your own */ +#ifdef DISCORD_DISABLE_IO_THREAD +DISCORD_EXPORT void Discord_UpdateConnection(void); +#endif + +DISCORD_EXPORT void Discord_UpdatePresence(const DiscordRichPresence* presence); +DISCORD_EXPORT void Discord_ClearPresence(void); + +DISCORD_EXPORT void Discord_Respond(const char* userid, /* DISCORD_REPLY_ */ int reply); + +DISCORD_EXPORT void Discord_UpdateHandlers(DiscordEventHandlers* handlers); + +#ifdef __cplusplus +} /* extern "C" */ +#endif diff --git a/src/public/dt_utlvector_send.cpp b/src/public/dt_utlvector_send.cpp index 2a358be9..0b05d79f 100644 --- a/src/public/dt_utlvector_send.cpp +++ b/src/public/dt_utlvector_send.cpp @@ -131,7 +131,7 @@ void* SendProxy_LengthTable( const SendProp *pProp, const void *pStructBase, con // Note: you have to be DILIGENT about calling NetworkStateChanged whenever an element in your CUtlVector changes // since CUtlVector doesn't do this automatically. SendProp SendPropUtlVector( - char *pVarName, // Use SENDINFO_UTLVECTOR to generate these 4. + const char *pVarName, // Use SENDINFO_UTLVECTOR to generate these 4. int offset, // Used to generate pData in the function specified in varProxy. int sizeofVar, // The size of each element in the utlvector. EnsureCapacityFn ensureFn, // This is the value returned for elements out of the array's current range. diff --git a/src/public/dt_utlvector_send.h b/src/public/dt_utlvector_send.h index f2ba68c3..3684d5b5 100644 --- a/src/public/dt_utlvector_send.h +++ b/src/public/dt_utlvector_send.h @@ -43,7 +43,7 @@ // ) // SendProp SendPropUtlVector( - char *pVarName, // Use SENDINFO_UTLVECTOR to generate these first 4 parameters. + const char *pVarName, // Use SENDINFO_UTLVECTOR to generate these first 4 parameters. int offset, int sizeofVar, EnsureCapacityFn ensureFn, diff --git a/src/public/engine/ishadowmgr.h b/src/public/engine/ishadowmgr.h index babac919..5165a8ab 100644 --- a/src/public/engine/ishadowmgr.h +++ b/src/public/engine/ishadowmgr.h @@ -40,6 +40,7 @@ enum ShadowFlags_t { SHADOW_FLAGS_FLASHLIGHT = (1 << 0), SHADOW_FLAGS_SHADOW = (1 << 1), + // Update this if you add flags SHADOW_FLAGS_LAST_FLAG = SHADOW_FLAGS_SHADOW }; diff --git a/src/public/fgdlib/gamedata.h b/src/public/fgdlib/gamedata.h index cf8b5be1..5d55db2f 100644 --- a/src/public/fgdlib/gamedata.h +++ b/src/public/fgdlib/gamedata.h @@ -88,6 +88,11 @@ class GameData bool RemapNameField( const char *pszInValue, char *pszOutValue, TNameFixup NameFixup ); bool LoadFGDMaterialExclusions( TokenReader &tr ); bool LoadFGDAutoVisGroups( TokenReader &tr ); + +#ifdef MAPBASE + // Sets up for additional instance remap fixes from Mapbase + void SetupInstanceRemapParams( int iStartNodes, int iStartBrushSide, bool bRemapVecLines ); +#endif CUtlVector< FGDMatExlcusions_s > m_FGDMaterialExclusions; @@ -109,6 +114,11 @@ class GameData matrix3x4_t m_InstanceMat; // matrix of the origin and rotation of rendering char m_InstancePrefix[ 128 ]; // the prefix used for the instance name remapping GDclass *m_InstanceClass; // the entity class that is being remapped +#ifdef MAPBASE + int m_InstanceStartAINodes; // the number of AI nodes in the level (for AI node remapping) + int m_InstanceStartSide; // the number of brush sides in the level (for brush side remapping) + bool m_bRemapVecLines; // allows ivVecLine to be remapped +#endif }; diff --git a/src/public/fgdlib/gdvar.h b/src/public/fgdlib/gdvar.h index e8211d1d..55374de6 100644 --- a/src/public/fgdlib/gdvar.h +++ b/src/public/fgdlib/gdvar.h @@ -95,6 +95,14 @@ class GDinputvariable inline GDIV_TYPE GetType() { return m_eType; } const char *GetTypeText(void); + +#ifdef MAPBASE + // The FGD library normally enforces that variable types should always stay the same. + // The new AI node remapping code needs to change the "nodeid" keyvalue on AI nodes so + // it's properly recognized as a node ID which needs to be remapped. + // That's what this hack is for. + inline void ForceSetType( GDIV_TYPE newType ) { m_eType = newType; } +#endif inline void GetDefault(int *pnStore) { diff --git a/src/public/haptics/haptic_utils.cpp b/src/public/haptics/haptic_utils.cpp index b9c72b2f..e6d3288a 100644 --- a/src/public/haptics/haptic_utils.cpp +++ b/src/public/haptics/haptic_utils.cpp @@ -138,6 +138,12 @@ void ConnectHaptics(CreateInterfaceFn appFactory) HookHapticMessages(); } +#if _MSC_VER >= 1925 +// deleting haptics results in a warning about deleting something with a non-virtual destructor +// big yikes but we can't do anything about it as it's accessed via interface +#pragma warning (disable: 5205) +#endif + void DisconnectHaptics() { haptics->ShutdownHaptics(); diff --git a/src/public/keyframe/keyframe.cpp b/src/public/keyframe/keyframe.cpp index df0a7a93..8f0575aa 100644 --- a/src/public/keyframe/keyframe.cpp +++ b/src/public/keyframe/keyframe.cpp @@ -154,7 +154,7 @@ class CPositionInterpolator_Linear : public IPositionInterpolator { public: virtual void Release(); - virtual void GetDetails( char **outName, int *outMinKeyReq, int *outMaxKeyReq ); + virtual void GetDetails( const char **outName, int *outMinKeyReq, int *outMaxKeyReq ); virtual void SetKeyPosition( int keyNum, Vector const &vPos ); virtual void InterpolatePosition( float time, Vector &vOut ); virtual bool ProcessKey( char const *pName, char const *pValue ) { return false; } @@ -171,7 +171,7 @@ void CPositionInterpolator_Linear::Release() { } -void CPositionInterpolator_Linear::GetDetails( char **outName, int *outMinKeyReq, int *outMaxKeyReq ) +void CPositionInterpolator_Linear::GetDetails( const char **outName, int *outMinKeyReq, int *outMaxKeyReq ) { *outName = "Linear"; *outMinKeyReq = 0; @@ -201,7 +201,7 @@ class CPositionInterpolator_CatmullRom : public IPositionInterpolator { public: virtual void Release(); - virtual void GetDetails( char **outName, int *outMinKeyReq, int *outMaxKeyReq ); + virtual void GetDetails( const char **outName, int *outMinKeyReq, int *outMaxKeyReq ); virtual void SetKeyPosition( int keyNum, Vector const &vPos ); virtual void InterpolatePosition( float time, Vector &vOut ); virtual bool ProcessKey( char const *pName, char const *pValue ) { return false; } @@ -218,7 +218,7 @@ void CPositionInterpolator_CatmullRom::Release() { } -void CPositionInterpolator_CatmullRom::GetDetails( char **outName, int *outMinKeyReq, int *outMaxKeyReq ) +void CPositionInterpolator_CatmullRom::GetDetails( const char **outName, int *outMinKeyReq, int *outMaxKeyReq ) { *outName = "Catmull-Rom Spline"; *outMinKeyReq = -1; @@ -282,7 +282,7 @@ public: CPositionInterpolator_Rope(); virtual void Release(); - virtual void GetDetails( char **outName, int *outMinKeyReq, int *outMaxKeyReq ); + virtual void GetDetails( const char **outName, int *outMinKeyReq, int *outMaxKeyReq ); virtual void SetKeyPosition( int keyNum, Vector const &vPos ); virtual void InterpolatePosition( float time, Vector &vOut ); virtual bool ProcessKey( char const *pName, char const *pValue ); @@ -319,7 +319,7 @@ void CPositionInterpolator_Rope::Release() delete this; } -void CPositionInterpolator_Rope::GetDetails( char **outName, int *outMinKeyReq, int *outMaxKeyReq ) +void CPositionInterpolator_Rope::GetDetails( const char **outName, int *outMinKeyReq, int *outMaxKeyReq ) { *outName = "Rope"; *outMinKeyReq = 0; @@ -433,7 +433,7 @@ typedef void (*RotationInterpolatorFunc_t)(float time, Quaternion &outRot); typedef struct { - char *szName; + const char *szName; RotationInterpolatorFunc_t pFunc; // defines the range of keys this interpolator needs to function @@ -458,7 +458,7 @@ int Motion_GetNumberOfRotationInterpolators( void ) return ARRAYSIZE(g_RotationInterpolators); } -bool Motion_GetRotationInterpolatorDetails( int rotInterpNum, char **outName, int *outMinKeyReq, int *outMaxKeyReq ) +bool Motion_GetRotationInterpolatorDetails( int rotInterpNum, const char **outName, int *outMinKeyReq, int *outMaxKeyReq ) { if ( rotInterpNum < 0 || rotInterpNum >= Motion_GetNumberOfRotationInterpolators() ) { diff --git a/src/public/keyframe/keyframe.h b/src/public/keyframe/keyframe.h index b2cfd27c..4ee04824 100644 --- a/src/public/keyframe/keyframe.h +++ b/src/public/keyframe/keyframe.h @@ -14,7 +14,7 @@ class IPositionInterpolator public: virtual void Release() = 0; - virtual void GetDetails( char **outName, int *outMinKeyReq, int *outMaxKeyReq ) = 0; + virtual void GetDetails( const char **outName, int *outMinKeyReq, int *outMaxKeyReq ) = 0; virtual void SetKeyPosition( int keyNum, Vector const &vPos ) = 0; virtual void InterpolatePosition( float time, Vector &vOut ) = 0; @@ -34,7 +34,7 @@ IPositionInterpolator* Motion_GetPositionInterpolator( int interpNum ); // Rotation interpolators. int Motion_GetNumberOfRotationInterpolators( void ); -bool Motion_GetRotationInterpolatorDetails( int rotInterpNum, char **outName, int *outMinKeyReq, int *outMaxKeyReq ); +bool Motion_GetRotationInterpolatorDetails( int rotInterpNum, const char **outName, int *outMinKeyReq, int *outMaxKeyReq ); bool Motion_InterpolateRotation( float time, int interpFuncNum, Quaternion &outQuatRotation ); bool Motion_SetKeyAngles( int keyNum, Quaternion &quatAngles ); diff --git a/src/public/materialsystem/MaterialSystemUtil.h b/src/public/materialsystem/MaterialSystemUtil.h index 3f22e918..ddabcd22 100644 --- a/src/public/materialsystem/MaterialSystemUtil.h +++ b/src/public/materialsystem/MaterialSystemUtil.h @@ -72,6 +72,10 @@ public: void Init( char const* pTexture, const char *pTextureGroupName, bool bComplain = true ); void InitProceduralTexture( const char *pTextureName, const char *pTextureGroupName, int w, int h, ImageFormat fmt, int nFlags ); void InitRenderTarget( int w, int h, RenderTargetSizeMode_t sizeMode, ImageFormat fmt, MaterialRenderTargetDepth_t depth, bool bHDR, char *pStrOptionalName = NULL ); + void InitRenderTarget(int w, int h, RenderTargetSizeMode_t sizeMode, ImageFormat fmt, MaterialRenderTargetDepth_t depth, bool bHDR, const char* pStrOptionalName = NULL) + { + InitRenderTarget(w, h, sizeMode, fmt, depth, bHDR, const_cast(pStrOptionalName)); + } #if defined( _X360 ) // used when RT coupling is disparate (texture is DDR based, surface is EDRAM based) void InitRenderTargetTexture( int width, int height, RenderTargetSizeMode_t sizeMode, ImageFormat fmt, MaterialRenderTargetDepth_t depth, bool bHDR, char *pStrOptionalName = NULL ); diff --git a/src/public/materialsystem/imaterialsystem.h b/src/public/materialsystem/imaterialsystem.h index 6f4a1237..6265547e 100644 --- a/src/public/materialsystem/imaterialsystem.h +++ b/src/public/materialsystem/imaterialsystem.h @@ -438,10 +438,22 @@ struct FlashlightState_t { m_bEnableShadows = false; // Provide reasonable defaults for shadow depth mapping parameters m_bDrawShadowFrustum = false; +#ifdef ASW_PROJECTED_TEXTURES + m_flShadowMapResolution = 2048.0f; + m_flShadowFilterSize = 0.5f; + m_flShadowSlopeScaleDepthBias = 16.0f; + m_flShadowDepthBias = 0.0005f; +#elif defined(MAPBASE) + m_flShadowMapResolution = 2048; + m_flShadowFilterSize = 1.0f; + m_flShadowSlopeScaleDepthBias = 4.0f; + m_flShadowDepthBias = 0.00001f; +#else m_flShadowMapResolution = 1024.0f; m_flShadowFilterSize = 3.0f; m_flShadowSlopeScaleDepthBias = 16.0f; m_flShadowDepthBias = 0.0005f; +#endif m_flShadowJitterSeed = 0.0f; m_flShadowAtten = 0.0f; m_bScissor = false; @@ -450,6 +462,16 @@ struct FlashlightState_t m_nRight = -1; m_nBottom = -1; m_nShadowQuality = 0; +#ifdef ASW_PROJECTED_TEXTURES + m_bOrtho = false; + m_fOrthoLeft = -1.0f; + m_fOrthoRight = 1.0f; + m_fOrthoTop = -1.0f; + m_fOrthoBottom = 1.0f; + + m_fBrightnessScale = 1.0f; + m_pSpotlightTexture = NULL; +#endif } Vector m_vecLightOrigin; @@ -476,6 +498,22 @@ struct FlashlightState_t float m_flShadowAtten; int m_nShadowQuality; +#ifdef ASW_PROJECTED_TEXTURES + bool m_bOrtho; + float m_fOrthoLeft; + float m_fOrthoRight; + float m_fOrthoTop; + float m_fOrthoBottom; + + float m_FarZAtten; + float m_fBrightnessScale; + bool m_bGlobalLight; +#endif + +#ifdef MAPBASE + bool m_bAlwaysDraw; +#endif + // Getters for scissor members bool DoScissor() { return m_bScissor; } int GetLeft() { return m_nLeft; } diff --git a/src/public/mathlib/vector.h b/src/public/mathlib/vector.h index 9507aea5..86eae436 100644 --- a/src/public/mathlib/vector.h +++ b/src/public/mathlib/vector.h @@ -1576,6 +1576,11 @@ public: void Init(vec_t ix=0.0f, vec_t iy=0.0f, vec_t iz=0.0f, vec_t iw=0.0f) { x = ix; y = iy; z = iz; w = iw; } +#ifdef MAPBASE_VSCRIPT + // Needed to get around vec_t recognition and inlining + void ScriptInit( float ix, float iy, float iz, float iw ) { Init( ix, iy, iz, iw ); } +#endif + bool IsValid() const; void Invalidate(); diff --git a/src/public/mathlib/vector4d.h b/src/public/mathlib/vector4d.h index b6ef3c1d..e5a49952 100644 --- a/src/public/mathlib/vector4d.h +++ b/src/public/mathlib/vector4d.h @@ -45,9 +45,15 @@ public: #endif Vector4D(vec_t X, vec_t Y, vec_t Z, vec_t W); Vector4D(const float *pFloat); +#ifdef MAPBASE + Vector4D(const Vector& vec, vec_t W); +#endif // Initialization void Init(vec_t ix=0.0f, vec_t iy=0.0f, vec_t iz=0.0f, vec_t iw=0.0f); +#ifdef MAPBASE + void Init( const Vector& vec, vec_t W ); +#endif // Got any nasty NAN's? bool IsValid() const; @@ -233,6 +239,14 @@ inline Vector4D::Vector4D(const float *pFloat) Assert( IsValid() ); } +#ifdef MAPBASE +inline Vector4D::Vector4D(const Vector& vec, vec_t W ) +{ + x = vec.x; y = vec.y; z = vec.z; w = W; + Assert( IsValid() ); +} +#endif + //----------------------------------------------------------------------------- // copy constructor @@ -254,6 +268,14 @@ inline void Vector4D::Init( vec_t ix, vec_t iy, vec_t iz, vec_t iw ) Assert( IsValid() ); } +#ifdef MAPBASE +inline void Vector4D::Init( const Vector& vec, vec_t iw ) +{ + x = vec.x; y = vec.y; z = vec.z; w = iw; + Assert( IsValid() ); +} +#endif + inline void Vector4D::Random( vec_t minVal, vec_t maxVal ) { x = minVal + ((vec_t)rand() / VALVE_RAND_MAX) * (maxVal - minVal); diff --git a/src/public/renderparm.h b/src/public/renderparm.h index b4fae7ec..0ab821c9 100644 --- a/src/public/renderparm.h +++ b/src/public/renderparm.h @@ -53,6 +53,9 @@ enum RenderParamInt_t INT_RENDERPARM_BACK_BUFFER_INDEX, + INT_FLASHLIGHT_DEPTHTEXTURE_FALLBACK_FIRST, + INT_FLASHLIGHT_DEPTHTEXTURE_FALLBACK_LAST = INT_FLASHLIGHT_DEPTHTEXTURE_FALLBACK_FIRST + 4, + MAX_INT_RENDER_PARMS = 20 }; @@ -73,4 +76,9 @@ enum RenderParamTexture_t #define ENABLE_FIXED_LIGHTING_OUTPUTMRTS_FOR_DEFERRED_LIGHTING 2 #define ENABLE_FIXED_LIGHTING_OUTPUTNORMAL_AND_DEPTH 3 +enum RenderParamFloat_t +{ + FLOAT_RENDERPARM_MINIMUMLIGHTING = 0, +}; + #endif // RENDERPARM_H diff --git a/src/public/responserules/response_host_interface.h b/src/public/responserules/response_host_interface.h new file mode 100644 index 00000000..fa39bd88 --- /dev/null +++ b/src/public/responserules/response_host_interface.h @@ -0,0 +1,66 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: Core types for the response rules -- criteria, responses, rules, and matchers. +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef RESPONSE_HOST_INTERFACE_H +#define RESPONSE_HOST_INTERFACE_H +#ifdef _WIN32 +#pragma once +#endif + +#include "filesystem.h" +class IUniformRandomStream; +class ICommandLine; + +namespace ResponseRules +{ + // FUNCTIONS YOU MUST IMPLEMENT IN THE HOST EXECUTABLE: + // These are functions that are mentioned in the header, but need their bodies implemented + // in the .dll that links against this lib. + // This is to wrap functions that previously came from the engine interface + // back when the response rules were inside the server.dll . Now that the rules + // are included into a standalone editor, we don't necessarily have an engine around, + // so there needs to be some other implementation. + abstract_class IEngineEmulator + { + public: + /// Given an input text buffer data pointer, parses a single token into the variable token and returns the new + /// reading position + virtual const char *ParseFile( const char *data, char *token, int maxlen ) = 0; + +#ifdef MAPBASE + /// (Optional) Same as ParseFile, but with casing preserved and escaped quotes supported + virtual const char *ParseFilePreserve( const char *data, char *token, int maxlen ) { return ParseFile( data, token, maxlen ); } +#endif + + /// Return a pointer to an IFileSystem we can use to read and process scripts. + virtual IFileSystem *GetFilesystem() = 0; + + /// Return a pointer to an instance of an IUniformRandomStream + virtual IUniformRandomStream *GetRandomStream() = 0 ; + + /// Return a pointer to a tier0 ICommandLine + virtual ICommandLine *GetCommandLine() = 0; + + /// Emulates the server's UTIL_LoadFileForMe + virtual byte *LoadFileForMe( const char *filename, int *pLength ) = 0; + + /// Emulates the server's UTIL_FreeFile + virtual void FreeFile( byte *buffer ) = 0; + + + /// Somewhere in the host executable you should define this symbol and + /// point it at a singleton instance. + static IEngineEmulator *s_pSingleton; + + // this is just a function that returns the pointer above -- just in + // case we need to define it differently. And I get asserts this way. + static IEngineEmulator *Get(); + }; +}; + + +#endif \ No newline at end of file diff --git a/src/public/responserules/response_types.h b/src/public/responserules/response_types.h new file mode 100644 index 00000000..2e80cc5a --- /dev/null +++ b/src/public/responserules/response_types.h @@ -0,0 +1,480 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: Core types for the response rules -- criteria, responses, rules, and matchers. +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef RESPONSE_TYPES_H +#define RESPONSE_TYPES_H +#ifdef _WIN32 +#pragma once +#endif + +#include "tier1/utlrbtree.h" +#include "tier1/utlsymbol.h" +#include "tier1/interval.h" +#include "mathlib/compressed_vector.h" +#include "datamap.h" +#include "soundflags.h" +#include "tier1/utlsymbol.h" + +namespace ResponseRules +{ + /// Custom symbol table for the response rules. + extern CUtlSymbolTable g_RS; +}; + +#ifdef _MANAGED +// forward declare some editor types just so we can friend them. +namespace ResponseRulesCLI +{ + ref class ResponseQueryResult; +} +#endif + +namespace ResponseRules +{ + using ::DataMapAccess; + // using ::DataMapInit; + class CResponseSystem; + +#pragma pack(push,1) + template + struct response_interval_t + { + T start; + T range; + + interval_t &ToInterval( interval_t &dest ) const { dest.start = start; dest.range = range; return dest; } + void FromInterval( const interval_t &from ) { start = from.start; range = from.range; } + float Random() const { interval_t temp = { start, range }; return RandomInterval( temp ); } + }; + + typedef response_interval_t responseparams_interval_t; +#pragma pack(pop) + +#pragma pack(push,1) + struct AI_ResponseFollowup + { + + + // TODO: make less wasteful of memory, by using a symbol table. + const char *followup_concept; // 12 -- next response + const char *followup_contexts; // 16 + float followup_delay; // 20 + const char *followup_target; // 24 -- to whom is this despatched? + // AIConceptHandle_t hConcept; + const char *followup_entityiotarget; //< if this rule involves firing entity io + const char *followup_entityioinput; //< if this rule involves firing entity io + float followup_entityiodelay; + bool bFired; + + inline bool IsValid( void ) const { return (followup_concept && followup_contexts); } + inline void Invalidate() { followup_concept = NULL; followup_contexts = NULL; } + inline void SetFired( bool fired ) { bFired = fired; } + inline bool HasBeenFired() { return bFired; } + + AI_ResponseFollowup( void ) : followup_concept(NULL), followup_contexts(NULL), followup_delay(0), followup_target(NULL), followup_entityiotarget(NULL), followup_entityioinput(NULL), followup_entityiodelay(0), bFired(false) + {}; + AI_ResponseFollowup( char *_followup_concept, char *_followup_contexts, float _followup_delay, char *_followup_target, + char *_followup_entityiotarget, char *_followup_entityioinput, float _followup_entityiodelay ) : + followup_concept(_followup_concept), followup_contexts(_followup_contexts), followup_delay(_followup_delay), followup_target(_followup_target), + followup_entityiotarget(_followup_entityiotarget), followup_entityioinput(_followup_entityioinput), followup_entityiodelay(_followup_entityiodelay), + bFired(false) + {}; + }; +#pragma pack(pop) + + + enum ResponseType_t + { + RESPONSE_NONE = 0, + RESPONSE_SPEAK, + RESPONSE_SENTENCE, + RESPONSE_SCENE, + RESPONSE_RESPONSE, // A reference to another response by name + RESPONSE_PRINT, + RESPONSE_ENTITYIO, // poke an input on an entity +#ifdef MAPBASE + RESPONSE_VSCRIPT, // Run VScript code + RESPONSE_VSCRIPT_FILE, // Run a VScript file (bypasses ugliness and character limits when just using IncludeScript() with RESPONSE_VSCRIPT) +#endif + + NUM_RESPONSES, + }; + +#ifdef MAPBASE + // The "apply to world" context option has been replaced with a flag-based integer which can apply contexts to more things. + // + // New ones should be implemented in: + // CResponseSystem::BuildDispatchTables() - AI_ResponseSystem.cpp (with their own funcs for m_RuleDispatch) + // CRR_Response::Describe() - rr_response.cpp + // CAI_Expresser::SpeakDispatchResponse() - ai_speech.cpp + // + // Also mind that this is 8-bit + enum : uint8 + { + APPLYCONTEXT_SELF = (1 << 0), // Included for contexts that apply to both self and something else + APPLYCONTEXT_WORLD = (1 << 1), // Apply to world + + APPLYCONTEXT_SQUAD = (1 << 2), // Apply to squad + APPLYCONTEXT_ENEMY = (1 << 3), // Apply to enemy + }; +#endif + + +#pragma pack(push,1) + struct ResponseParams + { + DECLARE_SIMPLE_DATADESC_INSIDE_NAMESPACE(); + + enum + { + RG_DELAYAFTERSPEAK = (1<<0), + RG_SPEAKONCE = (1<<1), + RG_ODDS = (1<<2), + RG_RESPEAKDELAY = (1<<3), + RG_SOUNDLEVEL = (1<<4), + RG_DONT_USE_SCENE = (1<<5), + RG_STOP_ON_NONIDLE = (1<<6), + RG_WEAPONDELAY = (1<<7), + RG_DELAYBEFORESPEAK = (1<<8), + }; + + ResponseParams() + { + flags = 0; + odds = 100; + delay.start = 0; + delay.range = 0; + respeakdelay.start = 0; + respeakdelay.range = 0; + weapondelay.start = 0; + weapondelay.range = 0; + soundlevel = 0; + predelay.start = 0; + predelay.range = 0; + } + responseparams_interval_t delay; //4 + responseparams_interval_t respeakdelay; //8 + responseparams_interval_t weapondelay; //12 + + short odds; //14 + + short flags; //16 + byte soundlevel; //17 + + responseparams_interval_t predelay; //21 + + ALIGN32 AI_ResponseFollowup *m_pFollowup; + + }; +#pragma pack(pop) + + class CriteriaSet + { + public: + typedef CUtlSymbol CritSymbol_t; ///< just to make it clear that some symbols come out of our special static table + public: + CriteriaSet(); + CriteriaSet( const CriteriaSet& src ); + CriteriaSet( const char *criteria, const char *value ) ; // construct initialized with a key/value pair (convenience) + ~CriteriaSet(); + + static CritSymbol_t ComputeCriteriaSymbol( const char *criteria ); + void AppendCriteria( CritSymbol_t criteria, const char *value = "", float weight = 1.0f ); + void AppendCriteria( const char *criteria, const char *value = "", float weight = 1.0f ); + void AppendCriteria( const char *criteria, float value, float weight = 1.0f ); + void RemoveCriteria( const char *criteria ); + + void Describe() const; + + int GetCount() const; + int FindCriterionIndex( CritSymbol_t criteria ) const; + int FindCriterionIndex( const char *name ) const; + inline bool IsValidIndex( int index ) const; + + CritSymbol_t GetNameSymbol( int nIndex ) const; + inline static const char *SymbolToStr( const CritSymbol_t &symbol ); + const char *GetName( int index ) const; + const char *GetValue( int index ) const; + float GetWeight( int index ) const; + + /// Merge another CriteriaSet into this one. + void Merge( const CriteriaSet *otherCriteria ); + void Merge( const char *modifiers ); // add criteria parsed from a text string + + /// add all of the contexts herein onto an entity. all durations are infinite. + void WriteToEntity( CBaseEntity *pEntity ); + + // Accessors to things that need only be done under unusual circumstances. + inline void EnsureCapacity( int num ); + void Reset(); // clear out this criteria (should not be necessary) + + /// When this is true, calls to AppendCriteria on a criteria that already exists + /// will override the existing value. (This is the default behavior). Can be temporarily + /// set false to prevent such overrides. + inline void OverrideOnAppend( bool bOverride ) { m_bOverrideOnAppend = bOverride; } + + // For iteration from beginning to end (also should not be necessary except in + // save/load) + inline int Head() const; + inline int Next( int i ) const; // use with IsValidIndex above + + const static char kAPPLYTOWORLDPREFIX = '$'; + + /// A last minute l4d2 change: deferred contexts prefixed with a '$' + /// character are actually applied to the world. This matches the + /// related hack in CBaseEntity::AppplyContext. + /// This function works IN-PLACE on the "from" parameter. + /// any $-prefixed criteria in pFrom become prefixed by "world", + /// and are also written into pSetOnWorld. + /// *IF* a response matches using the modified criteria, then and only + /// then should you write back the criteria in pSetOnWorld to the world + /// entity, subsequent to the match but BEFORE the dispatch. + /// Returns the number of contexts modified. If it returns 0, then + /// pSetOnWorld is empty. + static int InterceptWorldSetContexts( CriteriaSet * RESTRICT pFrom, + CriteriaSet * RESTRICT pSetOnWorld ); + + private: + void RemoveCriteria( int idx, bool bTestForPrefix ); + + struct CritEntry_t + { + CritEntry_t() : + criterianame( UTL_INVAL_SYMBOL ), + weight( 0.0f ) + { + value[ 0 ] = 0; + } + + CritEntry_t( const CritEntry_t& src ) + { + criterianame = src.criterianame; + value[ 0 ] = 0; + weight = src.weight; + SetValue( src.value ); + } + + CritEntry_t& operator=( const CritEntry_t& src ) + { + if ( this == &src ) + return *this; + + criterianame = src.criterianame; + weight = src.weight; + SetValue( src.value ); + + return *this; + } + + static bool LessFunc( const CritEntry_t& lhs, const CritEntry_t& rhs ) + { + return lhs.criterianame < rhs.criterianame; + } + + void SetValue( char const *str ) + { + if ( !str ) + { + value[ 0 ] = 0; + } + else + { + Q_strncpy( value, str, sizeof( value ) ); + } + } + + CritSymbol_t criterianame; + char value[ 64 ]; + float weight; + }; + + static CUtlSymbolTable sm_CriteriaSymbols; + typedef CUtlRBTree< CritEntry_t, short > Dict_t; + Dict_t m_Lookup; + int m_nNumPrefixedContexts; // number of contexts prefixed with kAPPLYTOWORLDPREFIX + bool m_bOverrideOnAppend; + }; + + inline void CriteriaSet::EnsureCapacity( int num ) + { + m_Lookup.EnsureCapacity(num); + } + + //----------------------------------------------------------------------------- + // Purpose: Generic container for a response to a match to a criteria set + // This is what searching for a response returns + //----------------------------------------------------------------------------- + + class CRR_Response + { + public: + DECLARE_SIMPLE_DATADESC_INSIDE_NAMESPACE(); + + CRR_Response(); + CRR_Response( const CRR_Response &from ); + CRR_Response &operator=( const CRR_Response &from ); + ~CRR_Response(); + private: + void operator delete(void* p); // please do not new or delete CRR_Responses. + public: + + // void Release(); // we no longer encourage new and delete on these things + + void GetName( char *buf, size_t buflen ) const; + void GetResponse( char *buf, size_t buflen ) const; +#ifdef MAPBASE + void GetRule( char *buf, size_t buflen ) const; +#endif + const char* GetNamePtr() const; + const char* GetResponsePtr() const; + const ResponseParams *GetParams() const { return &m_Params; } + ResponseType_t GetType() const { return (ResponseType_t)m_Type; } + soundlevel_t GetSoundLevel() const; + float GetRespeakDelay() const; + float GetWeaponDelay() const; + bool GetSpeakOnce() const; + bool ShouldntUseScene( ) const; + bool ShouldBreakOnNonIdle( void ) const; + int GetOdds() const; + float GetDelay() const; + float GetPreDelay() const; + + inline bool IsEmpty() const; // true iff my response name is empty + void Invalidate() ; // wipe out my contents, mark me invalid + + // Get/set the contexts we apply to character and world after execution + void SetContext( const char *context ); + const char * GetContext( void ) const { return m_szContext; } + + // Get/set the score I matched with (under certain circumstances) + inline float GetMatchScore( void ) { return m_fMatchScore; } + inline void SetMatchScore( float f ) { m_fMatchScore = f; } + +#ifdef MAPBASE + int GetContextFlags() { return m_iContextFlags; } + bool IsApplyContextToWorld( void ) { return (m_iContextFlags & APPLYCONTEXT_WORLD) != 0; } + + inline short *GetInternalIndices() { return m_InternalIndices; } + inline void SetInternalIndices( short iGroup, short iWithinGroup ) { m_InternalIndices[0] = iGroup; m_InternalIndices[1] = iWithinGroup; } +#else + bool IsApplyContextToWorld( void ) { return m_bApplyContextToWorld; } +#endif + + void Describe( const CriteriaSet *pDebugCriteria = NULL ); + + void Init( ResponseType_t type, + const char *responseName, + const ResponseParams& responseparams, + const char *matchingRule, + const char *applyContext, + bool bApplyContextToWorld ); + +#ifdef MAPBASE + void Init( ResponseType_t type, + const char *responseName, + const ResponseParams& responseparams, + const char *matchingRule, + const char *applyContext, + int iContextFlags ); +#endif + + static const char *DescribeResponse( ResponseType_t type ); + + enum + { + MAX_RESPONSE_NAME = 64, + MAX_RULE_NAME = 64 + }; + + + private: + byte m_Type; + char m_szResponseName[ MAX_RESPONSE_NAME ]; + char m_szMatchingRule[ MAX_RULE_NAME ]; + + ResponseParams m_Params; + float m_fMatchScore; // when instantiated dynamically in SpeakFindResponse, the score of the rule that matched it. + + char * m_szContext; // context data we apply to character after running +#ifdef MAPBASE + int m_iContextFlags; + + // The response's original indices in the system. [0] is the group's index, [1] is the index within the group. + // For now, this is only set in prospecctive mode. It's used to call back to the ParserResponse and mark a prospectively chosen response as used. + short m_InternalIndices[2]; +#else + bool m_bApplyContextToWorld; +#endif + +#ifdef _MANAGED + friend ref class ResponseRulesCLI::ResponseQueryResult; +#endif + }; + + + + abstract_class IResponseFilter + { + public: + virtual bool IsValidResponse( ResponseType_t type, const char *pszValue ) = 0; + }; + + abstract_class IResponseSystem + { + public: + virtual ~IResponseSystem() {} + + virtual bool FindBestResponse( const CriteriaSet& set, CRR_Response& response, IResponseFilter *pFilter = NULL ) = 0; + virtual void GetAllResponses( CUtlVector *pResponses ) = 0; + virtual void PrecacheResponses( bool bEnable ) = 0; + +#ifdef MAPBASE + // (Optional) Call this before and after using FindBestResponse() for a prospective lookup, e.g. a response that might not actually be used + // and should not trigger displayfirst, etc. + virtual void SetProspective( bool bToggle ) {}; + + // (Optional) Marks a prospective response as used + virtual void MarkResponseAsUsed( short iGroup, short iWithinGroup ) {}; +#endif + }; + + + + // INLINE FUNCTIONS + + // Used as a failsafe in finding responses. + bool CRR_Response::IsEmpty() const + { + return m_szResponseName[0] == 0; + } + + inline bool CriteriaSet::IsValidIndex( int index ) const + { + return ( index >= 0 && index < ((int)(m_Lookup.Count())) ); + } + + inline int CriteriaSet::Head() const + { + return m_Lookup.FirstInorder(); + } + + inline int CriteriaSet::Next( int i ) const + { + return m_Lookup.NextInorder(i); + } + + inline const char *CriteriaSet::SymbolToStr( const CritSymbol_t &symbol ) + { + return sm_CriteriaSymbols.String(symbol); + } + +} + +#include "rr_speechconcept.h" +#include "response_host_interface.h" + +#endif diff --git a/src/public/responserules/rr_speechconcept.h b/src/public/responserules/rr_speechconcept.h new file mode 100644 index 00000000..65b1bb6e --- /dev/null +++ b/src/public/responserules/rr_speechconcept.h @@ -0,0 +1,57 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: Class data for an AI Concept, an atom of response-driven dialog. +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef RR_SPEECHCONCEPT_H +#define RR_SPEECHCONCEPT_H + +#if defined( _WIN32 ) +#pragma once +#endif + +#include "utlsymbol.h" + +#define RR_CONCEPTS_ARE_STRINGS 0 + + +typedef CUtlSymbolTable CRR_ConceptSymbolTable; + +namespace ResponseRules +{ +class CRR_Concept +{ +public: // local typedefs + typedef CUtlSymbol tGenericId; // an int-like type that can be used to refer to all concepts of this type + tGenericId m_iConcept; + +public: + CRR_Concept() {}; + // construct concept from a string. + CRR_Concept(const char *fromString); + + // Return as a string + const char *GetStringConcept() const; + static const char *GetStringForGenericId(tGenericId genericId); + + operator tGenericId() const { return m_iConcept; } + operator const char *() const { return GetStringConcept(); } + inline bool operator==(const CRR_Concept &other) // default is compare by concept ids + { + return m_iConcept == other.m_iConcept; + } + bool operator==(const char *pszConcept); + +protected: + +private: + // dupe a concept + // CRR_Concept& operator=(CRR_Concept &other); + CRR_Concept& operator=(const char *fromString); +}; +}; + + +#endif diff --git a/src/public/rope_shared.h b/src/public/rope_shared.h index 54c28829..fe79b6e6 100644 --- a/src/public/rope_shared.h +++ b/src/public/rope_shared.h @@ -28,7 +28,11 @@ #define ROPE_COLLIDE (1<<2) // Collide with the world? #define ROPE_SIMULATE (1<<3) // Is the rope valid? #define ROPE_BREAKABLE (1<<4) // Can the endpoints detach? +#ifdef MAPBASE +#define ROPE_USE_WIND (1<<5) // Wind simulation on this rope. +#else #define ROPE_NO_WIND (1<<5) // No wind simulation on this rope. +#endif #define ROPE_INITIAL_HANG (1<<6) // By default, ropes will simulate for a bit internally when they // are created so they sag, but dynamically created ropes for things // like harpoons don't want this. diff --git a/src/public/saverestoretypes.h b/src/public/saverestoretypes.h index cd9e457f..41e24a82 100644 --- a/src/public/saverestoretypes.h +++ b/src/public/saverestoretypes.h @@ -178,7 +178,7 @@ class CGameSaveRestoreInfo { public: CGameSaveRestoreInfo() - : tableCount( 0 ), pTable( 0 ), m_pCurrentEntity( 0 ), m_EntityToIndex( 1024 ) + : m_iTableCount( 0 ), m_pTable( 0 ), m_pCurrentEntity( 0 ), m_EntityToIndex( 1024 ) { memset( &levelInfo, 0, sizeof( levelInfo ) ); modelSpaceOffset.Init( 0, 0, 0 ); @@ -186,8 +186,8 @@ public: void InitEntityTable( entitytable_t *pNewTable = NULL, int size = 0 ) { - pTable = pNewTable; - tableCount = size; + m_pTable = pNewTable; + m_iTableCount = size; for ( int i = 0; i < NumEntities(); i++ ) { @@ -197,17 +197,17 @@ public: entitytable_t *DetachEntityTable() { - entitytable_t *pReturn = pTable; - pTable = NULL; - tableCount = 0; + entitytable_t *pReturn = m_pTable; + m_pTable = NULL; + m_iTableCount = 0; return pReturn; } CBaseEntity *GetCurrentEntityContext() { return m_pCurrentEntity; } void SetCurrentEntityContext(CBaseEntity *pEntity) { m_pCurrentEntity = pEntity; } - int NumEntities() { return tableCount; } - entitytable_t *GetEntityInfo( int i ) { return (pTable + i); } + int NumEntities() { return m_iTableCount; } + entitytable_t *GetEntityInfo( int i ) { return (m_pTable + i); } float GetBaseTime() const { return levelInfo.time; } Vector GetLandmark() const { return ( levelInfo.fUseLandmark ) ? levelInfo.vecLandmarkOffset : vec3_origin; } @@ -215,13 +215,13 @@ public: { #ifdef GAME_DLL int i; - entitytable_t *pTable; + entitytable_t *m_pTable; int nEntities = NumEntities(); for ( i = 0; i < nEntities; i++ ) { - pTable = GetEntityInfo( i ); - m_EntityToIndex.Insert( CHashElement( pTable->hEnt.Get(), i ) ); + m_pTable = GetEntityInfo( i ); + m_EntityToIndex.Insert( CHashElement( m_pTable->hEnt.Get(), i ) ); } #endif } @@ -266,8 +266,8 @@ public: Vector modelSpaceOffset; // used only for globaly entity brushes modelled in different coordinate systems. private: - int tableCount; // Number of elements in the entity table - entitytable_t *pTable; // Array of entitytable_t elements (1 for each entity) + int m_iTableCount; // Number of elements in the entity table + entitytable_t *m_pTable; // Array of entitytable_t elements (1 for each entity) CBaseEntity *m_pCurrentEntity; // only valid during the save functions of this entity, NULL otherwise diff --git a/src/public/scratchpad3d.h b/src/public/scratchpad3d.h index 7e9aad66..30b2455c 100644 --- a/src/public/scratchpad3d.h +++ b/src/public/scratchpad3d.h @@ -51,7 +51,7 @@ public: m_pCachedRenderData = NULL; } - ~CBaseCommand() + virtual ~CBaseCommand() { ReleaseCachedRenderData(); } diff --git a/src/public/soundflags.h b/src/public/soundflags.h index 71ae1192..2c978c2b 100644 --- a/src/public/soundflags.h +++ b/src/public/soundflags.h @@ -102,8 +102,27 @@ enum soundlevel_t #define MAX_SNDLVL_VALUE ((1< 50) ? (20.0f / (float)(a - 50)) : 4.0 ) +#else +inline soundlevel_t ATTN_TO_SNDLVL(float a) +{ + soundlevel_t sndlvl = soundlevel_t::SNDLVL_NONE; + + if (a >= 0.0f) + { + sndlvl = soundlevel_t(float(soundlevel_t::SNDLVL_50dB) + float(soundlevel_t::SNDLVL_20dB) / a); + } + + return sndlvl; +} + +inline float SNDLVL_TO_ATTN(soundlevel_t s) +{ + return (s > soundlevel_t::SNDLVL_50dB)? (20.0f / float(s - soundlevel_t::SNDLVL_50dB)) : 4.0f; +} +#endif // This is a limit due to network encoding. // It encodes attenuation * 64 in 8 bits, so the maximum is (255 / 64) diff --git a/src/public/stdstring.h b/src/public/stdstring.h index c72320f7..50ed1c29 100644 --- a/src/public/stdstring.h +++ b/src/public/stdstring.h @@ -70,6 +70,15 @@ public: std::string *pString = (std::string *)fieldInfo.pField; return pString->empty(); } + +#ifdef MAPBASE + virtual bool Parse( const SaveRestoreFieldInfo_t &fieldInfo, char const* szValue ) + { + std::string *pString = (std::string *)fieldInfo.pField; + pString->assign(szValue); + return true; + } +#endif }; //------------------------------------- @@ -85,4 +94,9 @@ inline ISaveRestoreOps *GetStdStringDataOps() #define DEFINE_STDSTRING(name) \ { FIELD_CUSTOM, #name, { offsetof(classNameTypedef,name), 0 }, 1, FTYPEDESC_SAVE, NULL, GetStdStringDataOps(), NULL } +#ifdef MAPBASE +#define DEFINE_KEYSTDSTRING(name,mapname) \ + { FIELD_CUSTOM, #name, { offsetof(classNameTypedef, name), 0 }, 1, FTYPEDESC_SAVE | FTYPEDESC_KEY, mapname, GetStdStringDataOps(), NULL } +#endif + #endif // STDSTRING_H diff --git a/src/public/tier0/basetypes.h b/src/public/tier0/basetypes.h index 64aa95ed..7cc3b6e2 100644 --- a/src/public/tier0/basetypes.h +++ b/src/public/tier0/basetypes.h @@ -141,6 +141,10 @@ typedef wchar_t ucs2; // under windows wchar_t is ucs2 typedef unsigned short ucs2; #endif +#ifdef MAPBASE +#define TO_THREESTATE(num) static_cast(num) +#endif + #ifndef FALSE #define FALSE 0 #define TRUE (!FALSE) diff --git a/src/public/tier0/memalloc.h b/src/public/tier0/memalloc.h index 2ef9bd15..7b853b43 100644 --- a/src/public/tier0/memalloc.h +++ b/src/public/tier0/memalloc.h @@ -383,7 +383,12 @@ public: #pragma warning(disable:4290) #pragma warning(push) + +#if _MSC_VER < 1900 #include +#else + #include +#endif // MEM_DEBUG_CLASSNAME is opt-in. // Note: typeid().name() is not threadsafe, so if the project needs to access it in multiple threads diff --git a/src/public/tier0/memdbgon.h b/src/public/tier0/memdbgon.h index 822202aa..a93fb902 100644 --- a/src/public/tier0/memdbgon.h +++ b/src/public/tier0/memdbgon.h @@ -29,7 +29,7 @@ #include "commonmacros.h" #include "memalloc.h" -#if defined(USE_MEM_DEBUG) +#if USE_MEM_DEBUG #if defined( POSIX ) #define _NORMAL_BLOCK 1 @@ -88,7 +88,7 @@ inline void *MemAlloc_InlineCallocMemset( void *pMem, size_t nCount, size_t nEle } #endif -#define calloc(c, s) MemAlloc_InlineCallocMemset(malloc(c*s), c, s) +#define calloc(c, s) MemAlloc_InlineCallocMemset(malloc((c)*(s)), (c), (s)) #define free(p) g_pMemAlloc->Free( p ) #define _msize(p) g_pMemAlloc->GetSize( p ) #define _expand(p, s) _expand_NoLongerSupported(p, s) @@ -96,7 +96,7 @@ inline void *MemAlloc_InlineCallocMemset( void *pMem, size_t nCount, size_t nEle // -------------------------------------------------------- // Debug path -#if defined(USE_MEM_DEBUG) +#if USE_MEM_DEBUG #define malloc(s) g_pMemAlloc->Alloc( s, __FILE__, __LINE__) #define realloc(p, s) g_pMemAlloc->Realloc( p, s, __FILE__, __LINE__ ) @@ -232,7 +232,7 @@ inline wchar_t *MemAlloc_WcStrDup(const wchar_t *pString) #else -#if defined(USE_MEM_DEBUG) +#if USE_MEM_DEBUG #ifndef _STATIC_LINKED #pragma message ("Note: file includes crtdbg.h directly, therefore will cannot use memdbgon.h in non-debug build") #else diff --git a/src/public/tier0/platform.h b/src/public/tier0/platform.h index b0f9c18b..ed50c9e0 100644 --- a/src/public/tier0/platform.h +++ b/src/public/tier0/platform.h @@ -1437,6 +1437,13 @@ inline bool Plat_IsInDebugSession( bool bForceRecheck = false ) { return false; //----------------------------------------------------------------------------- PLATFORM_INTERFACE bool Is64BitOS(); +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// General Mapbase version constants compiled into projects for versioning purposes +//----------------------------------------------------------------------------- +#define MAPBASE_VERSION "7.3" +#define MAPBASE_VER_INT 7300 // For use in #if in a similar fashion to macros like _MSC_VER +#endif //----------------------------------------------------------------------------- // XBOX Components valid in PC compilation space diff --git a/src/public/tier1/UtlSortVector.h b/src/public/tier1/UtlSortVector.h index 9890c607..02756f70 100644 --- a/src/public/tier1/UtlSortVector.h +++ b/src/public/tier1/UtlSortVector.h @@ -244,7 +244,7 @@ void CUtlSortVector::QuickSort( LessFunc& less, int nLo ctx.m_pLessContext = m_pLessContext; ctx.m_pLessFunc = &less; - qsort_s( Base(), Count(), sizeof(T), (QSortCompareFunc_t)&CUtlSortVector::CompareHelper, &ctx ); + qsort_s( this->Base(), this->Count(), sizeof(T), (QSortCompareFunc_t)&CUtlSortVector::CompareHelper, &ctx ); } #else typedef int (__cdecl *QSortCompareFunc_t)( const void *, const void *); diff --git a/src/public/tier1/byteswap.h b/src/public/tier1/byteswap.h index 8dae8363..f66f6fdb 100644 --- a/src/public/tier1/byteswap.h +++ b/src/public/tier1/byteswap.h @@ -186,7 +186,7 @@ public: if( !m_bSwapBytes || ( sizeof(T) == 1 ) ) { // If we were just going to swap in place then return. - if( !inputBuffer ) + if( inputBuffer == outputBuffer ) return; // Otherwise copy the inputBuffer to the outputBuffer: diff --git a/src/public/tier1/convar.h b/src/public/tier1/convar.h index c551a4b7..12508acb 100644 --- a/src/public/tier1/convar.h +++ b/src/public/tier1/convar.h @@ -21,6 +21,7 @@ #include "tier1/utlvector.h" #include "tier1/utlstring.h" #include "icvar.h" +#include "Color.h" #ifdef _WIN32 #define FORCEINLINE_CVAR FORCEINLINE @@ -300,6 +301,10 @@ private: ICommandCallback *m_pCommandCallback; }; +#ifdef MAPBASE_VSCRIPT + // Allow late modification of the completion callback. +public: +#endif union { FnCommandCompletionCallback m_fnCompletionCallback; @@ -307,6 +312,9 @@ private: }; bool m_bHasCompletionCallback : 1; +#ifdef MAPBASE_VSCRIPT +private: +#endif bool m_bUsingNewCommandCallback : 1; bool m_bUsingCommandCallbackInterface : 1; }; @@ -356,6 +364,7 @@ public: // Retrieve value FORCEINLINE_CVAR float GetFloat( void ) const; FORCEINLINE_CVAR int GetInt( void ) const; + FORCEINLINE_CVAR Color GetColor( void ) const; FORCEINLINE_CVAR bool GetBool() const { return !!GetInt(); } FORCEINLINE_CVAR char const *GetString( void ) const; @@ -470,6 +479,16 @@ FORCEINLINE_CVAR int ConVar::GetInt( void ) const return m_pParent->m_nValue; } +//----------------------------------------------------------------------------- +// Purpose: Return ConVar value as a color +// Output : Color +//----------------------------------------------------------------------------- +FORCEINLINE_CVAR Color ConVar::GetColor( void ) const +{ + unsigned char *pColorElement = ((unsigned char *)&m_pParent->m_nValue); + return Color( pColorElement[0], pColorElement[1], pColorElement[2], pColorElement[3] ); +} + //----------------------------------------------------------------------------- // Purpose: Return ConVar value as a string, return "" for bogus string pointer, etc. @@ -517,6 +536,7 @@ public: // Get/Set value float GetFloat( void ) const; int GetInt( void ) const; + Color GetColor( void ) const; bool GetBool() const { return !!GetInt(); } const char *GetString( void ) const; // True if it has a min/max setting @@ -574,6 +594,16 @@ FORCEINLINE_CVAR int ConVarRef::GetInt( void ) const return m_pConVarState->m_nValue; } +//----------------------------------------------------------------------------- +// Purpose: Return ConVar value as a color +// Output : Color +//----------------------------------------------------------------------------- +FORCEINLINE_CVAR Color ConVarRef::GetColor( void ) const +{ + unsigned char *pColorElement = ((unsigned char *)&m_pConVarState->m_nValue); + return Color( pColorElement[0], pColorElement[1], pColorElement[2], pColorElement[3] ); +} + //----------------------------------------------------------------------------- // Purpose: Return ConVar value as a string, return "" for bogus string pointer, etc. //----------------------------------------------------------------------------- @@ -739,6 +769,18 @@ private: static ConCommand name##_command( #name, name, description ); \ static void name( const CCommand &args ) +#ifdef CLIENT_DLL + #define CON_COMMAND_SHARED( name, description ) \ + static void name( const CCommand &args ); \ + static ConCommand name##_command_client( #name "_client", name, description ); \ + static void name( const CCommand &args ) +#else + #define CON_COMMAND_SHARED( name, description ) \ + static void name( const CCommand &args ); \ + static ConCommand name##_command( #name, name, description ); \ + static void name( const CCommand &args ) +#endif + #define CON_COMMAND_F( name, description, flags ) \ static void name( const CCommand &args ); \ static ConCommand name##_command( #name, name, description, flags ); \ diff --git a/src/game/shared/interval.h b/src/public/tier1/interval.h similarity index 100% rename from src/game/shared/interval.h rename to src/public/tier1/interval.h diff --git a/src/public/tier1/mapbase_con_groups.h b/src/public/tier1/mapbase_con_groups.h new file mode 100644 index 00000000..7346092e --- /dev/null +++ b/src/public/tier1/mapbase_con_groups.h @@ -0,0 +1,63 @@ +//========= Mapbase - https://github.com/mapbase-source/source-sdk-2013 ================= +// +// Purpose: See tier1/mapbase_con_groups.cpp for more information +// +// $NoKeywords: $ +//============================================================================= + +#ifndef CON_VERBOSE_COLORS_H +#define CON_VERBOSE_COLORS_H +#ifdef _WIN32 +#pragma once +#endif + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +//static const Color CON_COLOR_DEV_VERBOSE( 192, 128, 192, 255 ); + +enum ConGroupID_t +{ + // General + CON_GROUP_MAPBASE_MISC, // "Mapbase misc." + CON_GROUP_PHYSICS, // "Physics" + CON_GROUP_IO_SYSTEM, // "Entity I/O" + CON_GROUP_RESPONSE_SYSTEM, // "Response System" + + // Game + CON_GROUP_NPC_AI, // "NPC AI" + CON_GROUP_NPC_SCRIPTS, // "NPC scripts" + CON_GROUP_SPEECH_AI, // "Speech AI" + CON_GROUP_CHOREO, // "Choreo" + + // VScript + CON_GROUP_VSCRIPT, // "VScript" + CON_GROUP_VSCRIPT_PRINT, // "VScript print" + + //-------------------------- + + // + // Mod groups can be inserted here + // + + //-------------------------- + + CON_GROUP_MAX, // Keep this at the end +}; + +// Mapbase console group message. +void CGMsg( int level, ConGroupID_t nGroup, PRINTF_FORMAT_STRING const tchar* pMsg, ... ) FMTFUNCTION( 3, 4 ); + +#define CGWarning CGMsg + +//----------------------------------------------------------------------------- + +class IBaseFileSystem; + +void InitConsoleGroups( IBaseFileSystem *filesystem ); + +void PrintAllConsoleGroups(); +void ToggleConsoleGroups( const char *pszQuery ); +void SetConsoleGroupIncludeNames( bool bToggle ); + +#endif diff --git a/src/public/tier1/mapbase_matchers_base.h b/src/public/tier1/mapbase_matchers_base.h new file mode 100644 index 00000000..beb1d869 --- /dev/null +++ b/src/public/tier1/mapbase_matchers_base.h @@ -0,0 +1,61 @@ +//========= Mapbase - https://github.com/mapbase-source/source-sdk-2013 ================= +// +// Purpose: General matching functions for things like wildcards and !=. +// +// $NoKeywords: $ +//============================================================================= + +#ifndef MAPBASE_MATCHERS_BASE_H +#define MAPBASE_MATCHERS_BASE_H +#ifdef _WIN32 +#pragma once +#endif + +#include + +#define MAPBASE_MATCHERS 1 + +// Regular expressions based off of the std library. +// pszQuery = The regex text. +// szValue = The value that should be matched. +bool Matcher_Regex( const char *pszQuery, const char *szValue ); + +// Compares two strings with support for wildcards or regex. This code is an expanded version of baseentity.cpp's NamesMatch(). +// pszQuery = The value that should have the wildcard. +// szValue = The value tested against the query. +// Use Matcher_Match if you want <, !=, etc. as well. +bool Matcher_NamesMatch( const char *pszQuery, const char *szValue ); + +// Identical to baseentity.cpp's original NamesMatch(). +// pszQuery = The value that should have the wildcard. +// szValue = The value tested against the query. +bool Matcher_NamesMatch_Classic( const char *pszQuery, const char *szValue ); + +// Identical to Matcher_NamesMatch_Classic(), but either value could use a wildcard. +// pszQuery = The value that serves as the query. This value can use wildcards. +// szValue = The value tested against the query. This value can use wildcards as well. +bool Matcher_NamesMatch_MutualWildcard( const char *pszQuery, const char *szValue ); + +// Returns true if the specified string contains a wildcard character. +bool Matcher_ContainsWildcard( const char *pszQuery ); + +// Taken from the Response System. +// Checks if the specified string appears to be a number of some sort. +static bool AppearsToBeANumber( char const *token ) +{ + if ( atof( token ) != 0.0f ) + return true; + + char const *p = token; + while ( *p ) + { + if ( *p != '0' ) + return false; + + p++; + } + + return true; +} + +#endif diff --git a/src/public/tier1/strtools.h b/src/public/tier1/strtools.h index aa408853..fc6136fa 100644 --- a/src/public/tier1/strtools.h +++ b/src/public/tier1/strtools.h @@ -851,6 +851,16 @@ inline void V_wcscat( INOUT_Z_CAP(cchDest) wchar_t *dest, const wchar_t *src, in V_wcsncat( dest, src, cchDest, COPY_ALL_CHARACTERS ); } +// Reentrant strtok +inline static char* V_strtok_s( char *str, const char *delimiters, char **context ) +{ +#ifdef _MSC_VER + return strtok_s( str, delimiters, context ); +#elif POSIX + return strtok_r( str, delimiters, context ); +#endif +} + // Encode a string for display as HTML -- this only encodes ' " & < >, which are the important ones to encode for // security and ensuring HTML display doesn't break. Other special chars like the ? sign and so forth will not // be encoded diff --git a/src/public/tier1/utlbuffer.h b/src/public/tier1/utlbuffer.h index f0d69bc1..ea8725c5 100644 --- a/src/public/tier1/utlbuffer.h +++ b/src/public/tier1/utlbuffer.h @@ -608,7 +608,11 @@ inline void CUtlBuffer::GetObject( T *dest ) { if ( CheckGet( sizeof(T) ) ) { +#ifdef MAPBASE + if ( ( sizeof( T ) == 1 ) || !m_Byteswap.IsSwappingBytes() ) +#else if ( !m_Byteswap.IsSwappingBytes() || ( sizeof( T ) == 1 ) ) +#endif { *dest = *(T *)PeekGet(); } @@ -640,7 +644,11 @@ inline void CUtlBuffer::GetTypeBin( T &dest ) { if ( CheckGet( sizeof(T) ) ) { +#ifdef MAPBASE + if ( ( sizeof( T ) == 1 ) || !m_Byteswap.IsSwappingBytes() ) +#else if ( !m_Byteswap.IsSwappingBytes() || ( sizeof( T ) == 1 ) ) +#endif { dest = *(T *)PeekGet(); } @@ -837,7 +845,11 @@ inline void CUtlBuffer::PutObject( T *src ) { if ( CheckPut( sizeof(T) ) ) { +#ifdef MAPBASE + if ( ( sizeof( T ) == 1 ) || !m_Byteswap.IsSwappingBytes() ) +#else if ( !m_Byteswap.IsSwappingBytes() || ( sizeof( T ) == 1 ) ) +#endif { *(T *)PeekPut() = *src; } @@ -866,7 +878,11 @@ inline void CUtlBuffer::PutTypeBin( T src ) { if ( CheckPut( sizeof(T) ) ) { +#ifdef MAPBASE + if ( ( sizeof( T ) == 1 ) || !m_Byteswap.IsSwappingBytes() ) +#else if ( !m_Byteswap.IsSwappingBytes() || ( sizeof( T ) == 1 ) ) +#endif { *(T *)PeekPut() = src; } diff --git a/src/public/tier1/utlsoacontainer.h b/src/public/tier1/utlsoacontainer.h index 071d2d61..86b87309 100644 --- a/src/public/tier1/utlsoacontainer.h +++ b/src/public/tier1/utlsoacontainer.h @@ -109,6 +109,95 @@ public: } }; +class CFltx4StridedPtr +{ +private: + typedef __m128 T; + +protected: + T *m_pData; + size_t m_nStride; + +public: + FORCEINLINE CFltx4StridedPtr( void *pData, size_t nByteStride ) + { + m_pData = reinterpret_cast( pData ); + m_nStride = nByteStride / sizeof( T ); + } + + FORCEINLINE CFltx4StridedPtr( void ) {} + T *operator->(void) const + { + return m_pData; + } + + T & operator*(void) const + { + return *m_pData; + } + + FORCEINLINE operator T *(void) + { + return m_pData; + } + + FORCEINLINE CFltx4StridedPtr& operator++(void) + { + m_pData += m_nStride; + return *this; + } + + FORCEINLINE void operator+=( size_t nNumElements ) + { + m_pData += nNumElements * m_nStride; + } + +}; + +class CFltx4StridedConstPtr +{ +private: + typedef __m128 T; + +protected: + const T *m_pData; + size_t m_nStride; + +public: + FORCEINLINE CFltx4StridedConstPtr( void const *pData, size_t nByteStride ) + { + m_pData = reinterpret_cast( pData ); + m_nStride = nByteStride / sizeof( T ); + } + + FORCEINLINE CFltx4StridedConstPtr( void ) {} + + const T *operator->(void) const + { + return m_pData; + } + + const T & operator*(void) const + { + return *m_pData; + } + + FORCEINLINE operator const T *(void) const + { + return m_pData; + } + + FORCEINLINE CFltx4StridedConstPtr &operator++(void) + { + m_pData += m_nStride; + return *this; + } + FORCEINLINE void operator+=( size_t nNumElements ) + { + m_pData += nNumElements*m_nStride; + } +}; + // allowed field data types. if you change these values, you need to change the tables in the .cpp file enum EAttributeDataType { @@ -311,19 +400,19 @@ public: }; -class CFltX4AttributeIterator : public CStridedConstPtr +class CFltX4AttributeIterator : public CFltx4StridedConstPtr { FORCEINLINE CFltX4AttributeIterator( CSOAContainer const *pContainer, int nAttribute, int nRowNumber = 0 ) - : CStridedConstPtr( pContainer->ConstRowPtr( nAttribute, nRowNumber), + : CFltx4StridedConstPtr( pContainer->ConstRowPtr( nAttribute, nRowNumber), pContainer->ItemByteStride( nAttribute ) ) { } }; -class CFltX4AttributeWriteIterator : public CStridedPtr +class CFltX4AttributeWriteIterator : public CFltx4StridedPtr { FORCEINLINE CFltX4AttributeWriteIterator( CSOAContainer const *pContainer, int nAttribute, int nRowNumber = 0 ) - : CStridedPtr( pContainer->RowPtr( nAttribute, nRowNumber), + : CFltx4StridedPtr( pContainer->RowPtr( nAttribute, nRowNumber), pContainer->ItemByteStride( nAttribute ) ) { } diff --git a/src/public/tier1/utlsymbol.h b/src/public/tier1/utlsymbol.h index d55afdc7..802261a6 100644 --- a/src/public/tier1/utlsymbol.h +++ b/src/public/tier1/utlsymbol.h @@ -16,6 +16,9 @@ #include "tier0/threadtools.h" #include "tier1/utlrbtree.h" #include "tier1/utlvector.h" +#include "tier1/utlbuffer.h" +#include "tier1/utllinkedlist.h" +#include "tier1/stringpool.h" //----------------------------------------------------------------------------- @@ -85,13 +88,17 @@ protected: // of strings to symbols and back. The symbol class itself contains // a static version of this class for creating global strings, but this // class can also be instanced to create local symbol tables. +// +// This class stores the strings in a series of string pools. The first +// two bytes of each string are decorated with a hash to speed up +// comparisons. //----------------------------------------------------------------------------- class CUtlSymbolTable { public: // constructor, destructor - CUtlSymbolTable( int growSize = 0, int initSize = 32, bool caseInsensitive = false ); + CUtlSymbolTable( int growSize = 0, int initSize = 16, bool caseInsensitive = false ); ~CUtlSymbolTable(); // Finds and/or creates a symbol based on the string @@ -116,6 +123,10 @@ public: return m_Lookup.Count(); } + // We store one of these at the beginning of every string to speed + // up comparisons. + typedef unsigned short hashDecoration_t; + protected: class CStringPoolIndex { @@ -125,10 +136,8 @@ protected: } inline CStringPoolIndex( unsigned short iPool, unsigned short iOffset ) - { - m_iPool = iPool; - m_iOffset = iOffset; - } + : m_iPool(iPool), m_iOffset(iOffset) + {} inline bool operator==( const CStringPoolIndex &other ) const { @@ -163,7 +172,9 @@ protected: }; CTree m_Lookup; + bool m_bInsensitive; + mutable unsigned short m_nUserSearchStringHash; mutable const char* m_pUserSearchString; // stores the string data @@ -172,11 +183,14 @@ protected: private: int FindPoolWithSpace( int len ) const; const char* StringFromIndex( const CStringPoolIndex &index ) const; + const char* DecoratedStringFromIndex( const CStringPoolIndex &index ) const; friend class CLess; + friend class CSymbolHash; + }; -class CUtlSymbolTableMT : private CUtlSymbolTable +class CUtlSymbolTableMT : public CUtlSymbolTable { public: CUtlSymbolTableMT( int growSize = 0, int initSize = 32, bool caseInsensitive = false ) @@ -194,9 +208,9 @@ public: CUtlSymbol Find( const char* pString ) const { - m_lock.LockForRead(); + m_lock.LockForWrite(); CUtlSymbol result = CUtlSymbolTable::Find( pString ); - m_lock.UnlockRead(); + m_lock.UnlockWrite(); return result; } @@ -209,11 +223,7 @@ public: } private: -#if defined(WIN32) || defined(_WIN32) mutable CThreadSpinRWLock m_lock; -#else - mutable CThreadRWLock m_lock; -#endif }; @@ -230,7 +240,6 @@ private: // The handle is a CUtlSymbol for the dirname and the same for the filename, the accessor // copies them into a static char buffer for return. typedef void* FileNameHandle_t; -#define FILENAMEHANDLE_INVALID 0 // Symbol table for more efficiently storing filenames by breaking paths and filenames apart. // Refactored from BaseFileSystem.h @@ -274,6 +283,9 @@ public: int PathIndex(const FileNameHandle_t &handle) { return (( const FileNameHandleInternal_t * )&handle)->path; } bool String( const FileNameHandle_t& handle, char *buf, int buflen ); void RemoveAll(); + void SpewStrings(); + bool SaveToBuffer( CUtlBuffer &buffer ); + bool RestoreFromBuffer( CUtlBuffer &buffer ); private: //CCountedStringPool m_StringPool; @@ -281,5 +293,50 @@ private: mutable CThreadSpinRWLock m_lock; }; +// This creates a simple class that includes the underlying CUtlSymbol +// as a private member and then instances a private symbol table to +// manage those symbols. Avoids the possibility of the code polluting the +// 'global'/default symbol table, while letting the code look like +// it's just using = and .String() to look at CUtlSymbol type objects +// +// NOTE: You can't pass these objects between .dlls in an interface (also true of CUtlSymbol of course) +// +#define DECLARE_PRIVATE_SYMBOLTYPE( typename ) \ + class typename \ + { \ + public: \ + typename(); \ + typename( const char* pStr ); \ + typename& operator=( typename const& src ); \ + bool operator==( typename const& src ) const; \ + const char* String( ) const; \ + private: \ + CUtlSymbol m_SymbolId; \ + }; + +// Put this in the .cpp file that uses the above typename +#define IMPLEMENT_PRIVATE_SYMBOLTYPE( typename ) \ + static CUtlSymbolTable g_##typename##SymbolTable; \ + typename::typename() \ + { \ + m_SymbolId = UTL_INVAL_SYMBOL; \ + } \ + typename::typename( const char* pStr ) \ + { \ + m_SymbolId = g_##typename##SymbolTable.AddString( pStr ); \ + } \ + typename& typename::operator=( typename const& src ) \ + { \ + m_SymbolId = src.m_SymbolId; \ + return *this; \ + } \ + bool typename::operator==( typename const& src ) const \ + { \ + return ( m_SymbolId == src.m_SymbolId ); \ + } \ + const char* typename::String( ) const \ + { \ + return g_##typename##SymbolTable.String( m_SymbolId ); \ + } #endif // UTLSYMBOL_H diff --git a/src/public/vgui/MouseCode.h b/src/public/vgui/MouseCode.h index 7ba16214..9b13fc81 100644 --- a/src/public/vgui/MouseCode.h +++ b/src/public/vgui/MouseCode.h @@ -18,6 +18,15 @@ namespace vgui { typedef ButtonCode_t MouseCode; + +static inline int MouseButtonBit(MouseCode code) +{ + if (code < MOUSE_FIRST || code > MOUSE_LAST) { + Assert(false); + return 0; + } + return 1 << (code - MOUSE_FIRST); +} } #endif // MOUSECODE_H diff --git a/src/public/vgui_controls/ImagePanel.h b/src/public/vgui_controls/ImagePanel.h index 0f3d6f47..a4b69668 100644 --- a/src/public/vgui_controls/ImagePanel.h +++ b/src/public/vgui_controls/ImagePanel.h @@ -61,7 +61,11 @@ public: int GetNumFrames(); void SetFrame( int nFrame ); +#ifdef MAPBASE + void SetRotation( int iRotation ); +#else void SetRotation( int iRotation ) { m_iRotation = iRotation; } +#endif protected: virtual void PaintBackground(); diff --git a/src/public/vgui_controls/Panel.h b/src/public/vgui_controls/Panel.h index 810d49e0..c5f6e20b 100644 --- a/src/public/vgui_controls/Panel.h +++ b/src/public/vgui_controls/Panel.h @@ -1030,7 +1030,7 @@ public: void VguiPanelGetSortedChildPanelList( Panel *pParentPanel, void *pSortedPanels ); -void VguiPanelGetSortedChildButtonList( Panel *pParentPanel, void *pSortedPanels, char *pchFilter = NULL, int nFilterType = 0 ); +void VguiPanelGetSortedChildButtonList( Panel *pParentPanel, void *pSortedPanels, const char *pchFilter = NULL, int nFilterType = 0 ); int VguiPanelNavigateSortedChildButtonList( void *pSortedPanels, int nDir ); int ComputeWide(Panel* pPanel, unsigned int& nBuildFlags, KeyValues *inResourceData, int nParentWide, int nParentTall, bool bComputingForTall); int ComputeTall(Panel* pPanel, unsigned int& nBuildFlags, KeyValues *inResourceData, int nParentWide, int nParentTall, bool bComputingForWide); diff --git a/src/public/vgui_controls/TextImage.h b/src/public/vgui_controls/TextImage.h index 20446e7b..97a918d1 100644 --- a/src/public/vgui_controls/TextImage.h +++ b/src/public/vgui_controls/TextImage.h @@ -108,6 +108,11 @@ public: const wchar_t *GetEllipsesPosition( void ) const { return m_pwszEllipsesPosition; } bool IsWrapping() const { return m_LineBreaks.Count() != 0; } +#ifdef MAPBASE + // Gets the relative y coordinates of all new lines created by newline (\n) characters. + void GetNewlinePositions( CUtlVector *pOutCoords, bool bIgnoreEmptyLines = true ); +#endif + protected: // truncate the _text string to fit into the draw width void SizeText(wchar_t *tempText, int stringLength); diff --git a/src/public/vscript/ivscript.h b/src/public/vscript/ivscript.h index d9e82ba7..b5db780c 100644 --- a/src/public/vscript/ivscript.h +++ b/src/public/vscript/ivscript.h @@ -1,4 +1,4 @@ -//========== Copyright � 2008, Valve Corporation, All rights reserved. ======== +//========== Copyright ? 2008, Valve Corporation, All rights reserved. ======== // // Purpose: VScript // @@ -95,6 +95,14 @@ #ifndef IVSCRIPT_H #define IVSCRIPT_H +#ifdef MAPBASE_VSCRIPT +#include +#include + +#include "utlmap.h" +#include "utlvector.h" +#endif + #include "platform.h" #include "datamap.h" #include "appframework/IAppSystem.h" @@ -131,6 +139,15 @@ class CUtlBuffer; //----------------------------------------------------------------------------- class IScriptVM; +#ifdef MAPBASE_VSCRIPT +class KeyValues; + +// This has been moved up a bit for IScriptManager +DECLARE_POINTER_HANDLE( HSCRIPT ); +#define INVALID_HSCRIPT ((HSCRIPT)-1) + +typedef unsigned int HScriptRaw; +#endif enum ScriptLanguage_t { @@ -148,15 +165,17 @@ class IScriptManager : public IAppSystem public: virtual IScriptVM *CreateVM( ScriptLanguage_t language = SL_DEFAULT ) = 0; virtual void DestroyVM( IScriptVM * ) = 0; + +#ifdef MAPBASE_VSCRIPT + virtual HSCRIPT CreateScriptKeyValues( IScriptVM *pVM, KeyValues *pKV, bool bAllowDestruct ) = 0; + virtual KeyValues *GetKeyValuesFromScriptKV( IScriptVM *pVM, HSCRIPT hSKV ) = 0; +#endif }; //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- -DECLARE_POINTER_HANDLE( HSCRIPT ); -#define INVALID_HSCRIPT ((HSCRIPT)-1) - inline bool IsValid( HSCRIPT hScript ) { return ( hScript != NULL && hScript != INVALID_HSCRIPT ); @@ -201,6 +220,26 @@ struct ScriptFuncDescriptor_t CUtlVector m_Parameters; }; +#ifdef MAPBASE_VSCRIPT +//--------------------------------------------------------- +// VScript Member Variables +// +// An odd concept. Because IScriptInstanceHelper now supports +// get/set metamethods, classes are capable of pretending they +// have member variables which VScript can get and set. +// +// There's no default way of documenting these variables, so even though +// these are not actually binding anything, this is here to allow VScript +// to describe these fake member variables in its documentation. +//--------------------------------------------------------- +struct ScriptMemberDesc_t +{ + const char *m_pszScriptName; + const char *m_pszDescription; + ScriptDataType_t m_ReturnType; +}; +#endif + //--------------------------------------------------------- @@ -251,15 +290,35 @@ public: virtual void *GetProxied( void *p, ScriptFunctionBinding_t *pBinding ) { return p; } virtual bool ToString( void *p, char *pBuf, int bufSize ) { return false; } virtual void *BindOnRead( HSCRIPT hInstance, void *pOld, const char *pszId ) { return NULL; } + +#ifdef MAPBASE_VSCRIPT + virtual bool Get( void *p, const char *pszKey, ScriptVariant_t &variant ) { return false; } + virtual bool Set( void *p, const char *pszKey, ScriptVariant_t &variant ) { return false; } + + virtual ScriptVariant_t *Add( void *p, ScriptVariant_t &variant ) { return NULL; } + virtual ScriptVariant_t *Subtract( void *p, ScriptVariant_t &variant ) { return NULL; } + virtual ScriptVariant_t *Multiply( void *p, ScriptVariant_t &variant ) { return NULL; } + virtual ScriptVariant_t *Divide( void *p, ScriptVariant_t &variant ) { return NULL; } +#endif }; //--------------------------------------------------------- +#ifdef MAPBASE_VSCRIPT +struct ScriptHook_t; +#endif + struct ScriptClassDesc_t { +#ifdef MAPBASE_VSCRIPT + ScriptClassDesc_t() : m_pszScriptName( 0 ), m_pszClassname( 0 ), m_pszDescription( 0 ), m_pBaseDesc( 0 ), m_pfnConstruct( 0 ), m_pfnDestruct( 0 ), pHelper(NULL) +#else ScriptClassDesc_t( void (*pfnInitializer)() ) : m_pszScriptName( 0 ), m_pszClassname( 0 ), m_pszDescription( 0 ), m_pBaseDesc( 0 ), m_pfnConstruct( 0 ), m_pfnDestruct( 0 ), pHelper(NULL) +#endif { +#ifndef MAPBASE_VSCRIPT (*pfnInitializer)(); +#endif ScriptClassDesc_t **ppHead = GetDescList(); m_pNextDesc = *ppHead; *ppHead = this; @@ -271,6 +330,11 @@ struct ScriptClassDesc_t ScriptClassDesc_t * m_pBaseDesc; CUtlVector m_FunctionBindings; +#ifdef MAPBASE_VSCRIPT + CUtlVector m_Hooks; + CUtlVector m_Members; +#endif + void *(*m_pfnConstruct)(); void (*m_pfnDestruct)( void *); IScriptInstanceHelper * pHelper; // optional helper @@ -284,6 +348,39 @@ struct ScriptClassDesc_t } }; +#ifdef MAPBASE_VSCRIPT +//--------------------------------------------------------- +struct ScriptConstantBinding_t +{ + const char *m_pszScriptName; + const char *m_pszDescription; + ScriptVariant_t m_data; + unsigned m_flags; +}; + +//--------------------------------------------------------- +struct ScriptEnumDesc_t +{ + ScriptEnumDesc_t() : m_pszScriptName( 0 ), m_pszDescription( 0 ), m_flags( 0 ) + { + AllEnumsDesc().AddToTail(this); + } + + virtual void RegisterDesc() = 0; + + const char *m_pszScriptName; + const char *m_pszDescription; + CUtlVector m_ConstantBindings; + unsigned m_flags; + + static CUtlVector& AllEnumsDesc() + { + static CUtlVector enums; + return enums; + } +}; +#endif + //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- @@ -314,6 +411,65 @@ struct ScriptClassDesc_t #define ScriptRegisterFunction( pVM, func, description ) ScriptRegisterFunctionNamed( pVM, func, #func, description ) #define ScriptRegisterFunctionNamed( pVM, func, scriptName, description ) do { static ScriptFunctionBinding_t binding; binding.m_desc.m_pszDescription = description; binding.m_desc.m_Parameters.RemoveAll(); ScriptInitFunctionBindingNamed( &binding, func, scriptName ); pVM->RegisterFunction( &binding ); } while (0) +#ifdef MAPBASE_VSCRIPT +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- + +// forwards T (and T&) if T is neither enum or an unsigned integer +// the overload for int below captures enums and unsigned integers and "bends" them to int +template +static inline typename std::enable_if::type>::value && !std::is_unsigned::type>::value, T&&>::type ToConstantVariant(T &&value) +{ + return std::forward(value); +} + +static inline int ToConstantVariant(int value) +{ + return value; +} + +#define ScriptRegisterConstant( pVM, constant, description ) ScriptRegisterConstantNamed( pVM, constant, #constant, description ) +#define ScriptRegisterConstantNamed( pVM, constant, scriptName, description ) do { static ScriptConstantBinding_t binding; binding.m_pszScriptName = scriptName; binding.m_pszDescription = description; binding.m_data = ToConstantVariant(constant); pVM->RegisterConstant( &binding ); } while (0) + +// Could probably use a better name. +// This is used for registering variants (particularly vectors) not tied to existing variables. +// The principal difference is that m_data is initted with bCopy set to true. +#define ScriptRegisterConstantFromTemp( pVM, constant, description ) ScriptRegisterConstantFromTempNamed( pVM, constant, #constant, description ) +#define ScriptRegisterConstantFromTempNamed( pVM, constant, scriptName, description ) do { static ScriptConstantBinding_t binding; binding.m_pszScriptName = scriptName; binding.m_pszDescription = description; binding.m_data = ScriptVariant_t( constant, true ); pVM->RegisterConstant( &binding ); } while (0) + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- + +#define BEGIN_SCRIPTENUM( enumName, description ) \ + struct ScriptEnum##enumName##Desc_t : public ScriptEnumDesc_t \ + { \ + void RegisterDesc(); \ + }; \ + ScriptEnum##enumName##Desc_t g_##enumName##_EnumDesc; \ + \ + void ScriptEnum##enumName##Desc_t::RegisterDesc() \ + { \ + static bool bInitialized; \ + if ( bInitialized ) \ + return; \ + \ + bInitialized = true; \ + \ + m_pszScriptName = #enumName; \ + m_pszDescription = description; \ + +#define DEFINE_ENUMCONST( constant, description ) DEFINE_ENUMCONST_NAMED( constant, #constant, description ) +#define DEFINE_ENUMCONST_NAMED( constant, scriptName, description ) do { ScriptConstantBinding_t *pBinding = &(m_ConstantBindings[m_ConstantBindings.AddToTail()]); pBinding->m_pszScriptName = scriptName; pBinding->m_pszDescription = description; pBinding->m_data = constant; pBinding->m_flags = SF_MEMBER_FUNC; } while (0); + +#define END_SCRIPTENUM() \ + } \ + + +#define GetScriptDescForEnum( enumName ) GetScriptDesc( ( className *)NULL ) +#endif + //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- @@ -323,6 +479,38 @@ struct ScriptClassDesc_t #define BEGIN_SCRIPTDESC( className, baseClass, description ) BEGIN_SCRIPTDESC_NAMED( className, baseClass, #className, description ) #define BEGIN_SCRIPTDESC_ROOT( className, description ) BEGIN_SCRIPTDESC_ROOT_NAMED( className, #className, description ) +#ifdef MAPBASE_VSCRIPT +#define BEGIN_SCRIPTDESC_NAMED( className, baseClass, scriptName, description ) \ + template <> ScriptClassDesc_t* GetScriptDesc(baseClass*); \ + template <> ScriptClassDesc_t* GetScriptDesc(className*); \ + ScriptClassDesc_t & g_##className##_ScriptDesc = *GetScriptDesc(nullptr); \ + template <> ScriptClassDesc_t* GetScriptDesc(className*) \ + { \ + static ScriptClassDesc_t g_##className##_ScriptDesc; \ + typedef className _className; \ + ScriptClassDesc_t *pDesc = &g_##className##_ScriptDesc; \ + if (pDesc->m_pszClassname) return pDesc; \ + pDesc->m_pszDescription = description; \ + ScriptInitClassDescNamed( pDesc, className, GetScriptDescForClass( baseClass ), scriptName ); \ + ScriptClassDesc_t *pInstanceHelperBase = pDesc->m_pBaseDesc; \ + while ( pInstanceHelperBase ) \ + { \ + if ( pInstanceHelperBase->pHelper ) \ + { \ + pDesc->pHelper = pInstanceHelperBase->pHelper; \ + break; \ + } \ + pInstanceHelperBase = pInstanceHelperBase->m_pBaseDesc; \ + } + + +#define BEGIN_SCRIPTDESC_ROOT_NAMED( className, scriptName, description ) \ + BEGIN_SCRIPTDESC_NAMED( className, ScriptNoBase_t, scriptName, description ) + +#define END_SCRIPTDESC() \ + return pDesc; \ + } +#else #if defined(_MSC_VER) && (_MSC_VER < 1800) #define DEFINE_SCRIPTDESC_FUNCTION( className, baseClass ) \ ScriptClassDesc_t * GetScriptDesc( className * ) @@ -385,6 +573,7 @@ inline IScriptInstanceHelper *GetScriptInstanceHelper_ScriptNoBase_t() #define END_SCRIPTDESC() \ return; \ } +#endif #define SCRIPTFUNC_CONCAT_(x, y) x##y #define SCRIPTFUNC_CONCAT(x, y) SCRIPTFUNC_CONCAT_(x, y) @@ -393,12 +582,51 @@ inline IScriptInstanceHelper *GetScriptInstanceHelper_ScriptNoBase_t() #define DEFINE_SCRIPTFUNC_WRAPPED( func, description ) DEFINE_SCRIPTFUNC_NAMED( SCRIPTFUNC_CONCAT( Script, func ), #func, description ) #define DEFINE_SCRIPTFUNC_NAMED( func, scriptName, description ) ScriptAddFunctionToClassDescNamed( pDesc, _className, func, scriptName, description ); #define DEFINE_SCRIPT_CONSTRUCTOR() ScriptAddConstructorToClassDesc( pDesc, _className ); -#define DEFINE_SCRIPT_INSTANCE_HELPER( className, p ) template <> IScriptInstanceHelper *GetScriptInstanceHelperOverride< className >( IScriptInstanceHelper * ) { return p; } - +#define DEFINE_SCRIPT_INSTANCE_HELPER( p ) pDesc->pHelper = (p); + +#ifdef MAPBASE_VSCRIPT +// Use this for hooks which have no parameters +#define DEFINE_SIMPLE_SCRIPTHOOK( hook, hookName, returnType, description ) \ + if (!hook.m_bDefined) \ + { \ + ScriptHook_t *pHook = &hook; \ + pHook->m_desc.m_pszScriptName = hookName; pHook->m_desc.m_pszFunction = #hook; pHook->m_desc.m_ReturnType = returnType; pHook->m_desc.m_pszDescription = description; \ + pDesc->m_Hooks.AddToTail(pHook); \ + } + +#define BEGIN_SCRIPTHOOK( hook, hookName, returnType, description ) \ + if (!hook.m_bDefined) \ + { \ + ScriptHook_t *pHook = &hook; \ + pHook->m_desc.m_pszScriptName = hookName; pHook->m_desc.m_pszFunction = #hook; pHook->m_desc.m_ReturnType = returnType; pHook->m_desc.m_pszDescription = description; + +#define DEFINE_SCRIPTHOOK_PARAM( paramName, type ) pHook->AddParameter( paramName, type ); + +#define END_SCRIPTHOOK() \ + pDesc->m_Hooks.AddToTail(pHook); \ + } + +// Static hooks (or "global" hooks) are not tied to specific classes +#define END_SCRIPTHOOK_STATIC( pVM ) \ + pVM->RegisterHook( pHook ); \ + } + +#define ScriptRegisterSimpleHook( pVM, hook, hookName, returnType, description ) \ + if (!hook.m_bDefined) \ + { \ + ScriptHook_t *pHook = &hook; \ + pHook->m_desc.m_pszScriptName = hookName; pHook->m_desc.m_pszFunction = #hook; pHook->m_desc.m_ReturnType = returnType; pHook->m_desc.m_pszDescription = description; \ + pVM->RegisterHook( pHook ); \ + } + +#define DEFINE_MEMBERVAR( varName, returnType, description ) \ + do { ScriptMemberDesc_t *pBinding = &((pDesc)->m_Members[(pDesc)->m_Members.AddToTail()]); pBinding->m_pszScriptName = varName; pBinding->m_pszDescription = description; pBinding->m_ReturnType = returnType; } while (0); +#endif + template ScriptClassDesc_t *GetScriptDesc(T *); -template <> -inline ScriptClassDesc_t *GetScriptDesc( ScriptNoBase_t *) { return NULL; } +struct ScriptNoBase_t; +template <> inline ScriptClassDesc_t *GetScriptDesc( ScriptNoBase_t *) { return NULL; } #define GetScriptDescForClass( className ) GetScriptDesc( ( className *)NULL ) @@ -444,14 +672,13 @@ enum ScriptStatus_t SCRIPT_RUNNING, }; -// forward declarations for the ForwardConsoleCommand() workaround -class CCommandContext; -class CCommand; -class CSquirrelMetamethodDelegateImpl; - class IScriptVM { public: +#ifdef MAPBASE_VSCRIPT + virtual ~IScriptVM() {} +#endif + virtual bool Init() = 0; virtual void Shutdown() = 0; @@ -490,13 +717,19 @@ public: // Scope //-------------------------------------------------------- virtual HSCRIPT CreateScope( const char *pszScope, HSCRIPT hParent = NULL ) = 0; +#ifndef MAPBASE_VSCRIPT virtual HSCRIPT ReferenceScope( HSCRIPT hScript ) = 0; +#endif virtual void ReleaseScope( HSCRIPT hScript ) = 0; //-------------------------------------------------------- // Script functions //-------------------------------------------------------- +#ifdef MAPBASE_VSCRIPT + virtual HSCRIPT LookupFunction( const char *pszFunction, HSCRIPT hScope = NULL ) = 0; +#else virtual HSCRIPT LookupFunction( const char *pszFunction, HSCRIPT hScope = NULL, bool bNoDelegation = false ) = 0; +#endif virtual void ReleaseFunction( HSCRIPT hScript ) = 0; //-------------------------------------------------------- @@ -504,6 +737,15 @@ public: //-------------------------------------------------------- virtual ScriptStatus_t ExecuteFunction( HSCRIPT hFunction, ScriptVariant_t *pArgs, int nArgs, ScriptVariant_t *pReturn, HSCRIPT hScope, bool bWait ) = 0; +#ifdef MAPBASE_VSCRIPT + //-------------------------------------------------------- + // Hooks + //-------------------------------------------------------- + // Persistent unique identifier for an HSCRIPT variable + virtual HScriptRaw HScriptToRaw( HSCRIPT val ) = 0; + virtual ScriptStatus_t ExecuteHookFunction( const char *pszEventName, ScriptVariant_t *pArgs, int nArgs, ScriptVariant_t *pReturn, HSCRIPT hScope, bool bWait ) = 0; +#endif + //-------------------------------------------------------- // External functions //-------------------------------------------------------- @@ -514,27 +756,49 @@ public: //-------------------------------------------------------- virtual bool RegisterClass( ScriptClassDesc_t *pClassDesc ) = 0; - void RegisterAllClasses() - { - ScriptClassDesc_t *pCurrent = *ScriptClassDesc_t::GetDescList(); - while ( pCurrent ) - { - RegisterClass( pCurrent ); - pCurrent = pCurrent->m_pNextDesc; - } - } +#ifdef MAPBASE_VSCRIPT + //-------------------------------------------------------- + // External constants + //-------------------------------------------------------- + virtual void RegisterConstant( ScriptConstantBinding_t *pScriptConstant ) = 0; + + //-------------------------------------------------------- + // External enums + //-------------------------------------------------------- + virtual void RegisterEnum( ScriptEnumDesc_t *pEnumDesc ) = 0; + + //-------------------------------------------------------- + // External hooks + //-------------------------------------------------------- + virtual void RegisterHook( ScriptHook_t *pHookDesc ) = 0; +#endif //-------------------------------------------------------- // External instances. Note class will be auto-registered. //-------------------------------------------------------- +#ifdef MAPBASE_VSCRIPT + // When a RegisterInstance instance is deleted, VScript normally treats it as a strong reference and only deregisters the instance itself, preserving the registered data + // it points to so the game can continue to use it. + // bAllowDestruct is supposed to allow VScript to treat it as a weak reference created by the script, destructing the registered data automatically like any other type. + // This is useful for classes pretending to be primitive types. + virtual HSCRIPT RegisterInstance( ScriptClassDesc_t *pDesc, void *pInstance, bool bAllowDestruct = false ) = 0; + virtual void SetInstanceUniqeId( HSCRIPT hInstance, const char *pszId ) = 0; + template HSCRIPT RegisterInstance( T *pInstance, bool bAllowDestruct = false ) { return RegisterInstance( GetScriptDesc( pInstance ), pInstance, bAllowDestruct ); } + template HSCRIPT RegisterInstance( T *pInstance, const char *pszInstance, HSCRIPT hScope = NULL, bool bAllowDestruct = false) { HSCRIPT hInstance = RegisterInstance( GetScriptDesc( pInstance ), pInstance, bAllowDestruct ); SetValue( hScope, pszInstance, hInstance ); return hInstance; } +#else virtual HSCRIPT RegisterInstance( ScriptClassDesc_t *pDesc, void *pInstance ) = 0; virtual void SetInstanceUniqeId( HSCRIPT hInstance, const char *pszId ) = 0; template HSCRIPT RegisterInstance( T *pInstance ) { return RegisterInstance( GetScriptDesc( pInstance ), pInstance ); } template HSCRIPT RegisterInstance( T *pInstance, const char *pszInstance, HSCRIPT hScope = NULL) { HSCRIPT hInstance = RegisterInstance( GetScriptDesc( pInstance ), pInstance ); SetValue( hScope, pszInstance, hInstance ); return hInstance; } +#endif virtual void RemoveInstance( HSCRIPT ) = 0; void RemoveInstance( HSCRIPT hInstance, const char *pszInstance, HSCRIPT hScope = NULL ) { ClearValue( hScope, pszInstance ); RemoveInstance( hInstance ); } +#ifdef MAPBASE_SCRIPT + void RemoveInstance( const char *pszInstance, HSCRIPT hScope = NULL ) { ScriptVariant_t val; if ( GetValue( hScope, pszInstance, &val ) ) { if ( val.m_type == FIELD_HSCRIPT ) { RemoveInstance( val, pszInstance, hScope ); } ReleaseValue( val ); } } +#else void RemoveInstance( const char *pszInstance, HSCRIPT hScope = NULL ) { ScriptVariant_t val; if ( GetValue( hScope, pszInstance, &val ) ) { if ( val.GetType() == FIELD_HSCRIPT) { RemoveInstance(val, pszInstance, hScope); } ReleaseValue(val); } } +#endif virtual void *GetInstanceValue( HSCRIPT hInstance, ScriptClassDesc_t *pExpectedType = NULL ) = 0; @@ -550,6 +814,9 @@ public: virtual bool SetValue( HSCRIPT hScope, const char *pszKey, const char *pszValue ) = 0; virtual bool SetValue( HSCRIPT hScope, const char *pszKey, const ScriptVariant_t &value ) = 0; bool SetValue( const char *pszKey, const ScriptVariant_t &value ) { return SetValue(NULL, pszKey, value ); } +#ifdef MAPBASE_VSCRIPT + virtual bool SetValue( HSCRIPT hScope, const ScriptVariant_t& key, const ScriptVariant_t& val ) = 0; +#endif virtual void CreateTable( ScriptVariant_t &Table ) = 0; virtual int GetNumTableEntries( HSCRIPT hScope ) = 0; @@ -557,11 +824,21 @@ public: virtual bool GetValue( HSCRIPT hScope, const char *pszKey, ScriptVariant_t *pValue ) = 0; bool GetValue( const char *pszKey, ScriptVariant_t *pValue ) { return GetValue(NULL, pszKey, pValue ); } +#ifdef MAPBASE_VSCRIPT + virtual bool GetValue( HSCRIPT hScope, ScriptVariant_t key, ScriptVariant_t* pValue ) = 0; +#endif virtual void ReleaseValue( ScriptVariant_t &value ) = 0; virtual bool ClearValue( HSCRIPT hScope, const char *pszKey ) = 0; bool ClearValue( const char *pszKey) { return ClearValue( NULL, pszKey ); } +#ifdef MAPBASE_VSCRIPT + virtual bool ClearValue( HSCRIPT hScope, ScriptVariant_t pKey ) = 0; +#endif +#ifdef MAPBASE_VSCRIPT + virtual void CreateArray(ScriptVariant_t &arr, int size = 0) = 0; + virtual bool ArrayAppend(HSCRIPT hArray, const ScriptVariant_t &val) = 0; +#endif //---------------------------------------------------------------------------- // Josh: Some extra helpers here. @@ -724,6 +1001,27 @@ public: return ExecuteFunction( hFunction, args, ARRAYSIZE(args), pReturn, hScope, bWait ); } +#ifdef MAPBASE_VSCRIPT + void RegisterAllClasses() + { + ScriptClassDesc_t *pCurrent = *ScriptClassDesc_t::GetDescList(); + while ( pCurrent ) + { + RegisterClass( pCurrent ); + pCurrent = pCurrent->m_pNextDesc; + } + } + + void RegisterAllEnums() + { + CUtlVector& enumDescs = ScriptEnumDesc_t::AllEnumsDesc(); + FOR_EACH_VEC(enumDescs, i) + { + enumDescs[i]->RegisterDesc(); + RegisterEnum(enumDescs[i]); + } + } +#else //----------------------------------------------------- /// @{ /// Machinery for smuggling a _get() metamethod override @@ -829,8 +1127,23 @@ public: public: virtual int GetKeyValue2( HSCRIPT hScope, int nIterator, ScriptVariant_t *pKey, ScriptVariant_t *pValue ) = 0; +#endif }; +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- + +#ifdef MAPBASE_VSCRIPT +template T *HScriptToClass( HSCRIPT hObj ) +{ + extern IScriptVM *g_pScriptVM; + return (hObj) ? (T*)g_pScriptVM->GetInstanceValue( hObj, GetScriptDesc( (T*)NULL ) ) : NULL; +} +#else +DECLARE_POINTER_HANDLE( HSCRIPT ); +#define INVALID_HSCRIPT ((HSCRIPT)-1) +#endif //----------------------------------------------------------------------------- // Script scope helper class @@ -960,7 +1273,11 @@ public: HSCRIPT LookupFunction( const char *pszFunction, bool bNoDelegation = false ) { +#ifdef MAPBASE_VSCRIPT + return GetVM()->LookupFunction( pszFunction, m_hScope ); +#else return GetVM()->LookupFunction( pszFunction, m_hScope, bNoDelegation ); +#endif } void ReleaseFunction( HSCRIPT hScript ) @@ -1278,6 +1595,395 @@ typedef CScriptScopeT<> CScriptScope; #define VScriptAddEnumToRoot( enumVal ) g_pScriptVM->SetValue( #enumVal, (int)enumVal ) +#ifdef MAPBASE_VSCRIPT + +// +// Map pointer iteration +// +#define FOR_EACH_MAP_PTR( mapName, iteratorName ) \ + for ( int iteratorName = (mapName)->FirstInorder(); (mapName)->IsUtlMap && iteratorName != (mapName)->InvalidIndex(); iteratorName = (mapName)->NextInorder( iteratorName ) ) + +#define FOR_EACH_MAP_PTR_FAST( mapName, iteratorName ) \ + for ( int iteratorName = 0; (mapName)->IsUtlMap && iteratorName < (mapName)->MaxElement(); ++iteratorName ) if ( !(mapName)->IsValidIndex( iteratorName ) ) continue; else + +#define FOR_EACH_VEC_PTR( vecName, iteratorName ) \ + for ( int iteratorName = 0; iteratorName < (vecName)->Count(); iteratorName++ ) + +//----------------------------------------------------------------------------- + +static void __UpdateScriptHooks( HSCRIPT hooksList ); + +//----------------------------------------------------------------------------- +// +// Keeps track of which events and scopes are hooked without polling this from the script VM on each request. +// Local cache is updated each time there is a change to script hooks: on Add, on Remove, on game restore +// +//----------------------------------------------------------------------------- +class CScriptHookManager +{ +private: + typedef CUtlVector< char* > contextmap_t; + typedef CUtlMap< HScriptRaw, contextmap_t* > scopemap_t; + typedef CUtlMap< char*, scopemap_t* > hookmap_t; + + HSCRIPT m_hfnHookFunc; + + // { [string event], { [HSCRIPT scope], { [string context], [HSCRIPT callback] } } } + hookmap_t m_HookList; + +public: + + CScriptHookManager() : m_HookList( DefLessFunc(char*) ), m_hfnHookFunc(NULL) + { + } + + HSCRIPT GetHookFunction() + { + return m_hfnHookFunc; + } + + // For global hooks + bool IsEventHooked( const char *szEvent ) + { + return m_HookList.Find( const_cast< char* >( szEvent ) ) != m_HookList.InvalidIndex(); + } + + bool IsEventHookedInScope( const char *szEvent, HSCRIPT hScope ) + { + extern IScriptVM *g_pScriptVM; + + Assert( hScope ); + + int eventIdx = m_HookList.Find( const_cast< char* >( szEvent ) ); + if ( eventIdx == m_HookList.InvalidIndex() ) + return false; + + scopemap_t *scopeMap = m_HookList.Element( eventIdx ); + return scopeMap->Find( g_pScriptVM->HScriptToRaw( hScope ) ) != scopeMap->InvalidIndex(); + } + + // + // On VM init, registers script func and caches the hook func. + // + void OnInit() + { + extern IScriptVM *g_pScriptVM; + + ScriptRegisterFunctionNamed( g_pScriptVM, __UpdateScriptHooks, "__UpdateScriptHooks", SCRIPT_HIDE ); + + ScriptVariant_t hHooks; + g_pScriptVM->GetValue( "Hooks", &hHooks ); + + Assert( hHooks.GetType() == FIELD_HSCRIPT); + + if ( hHooks.GetType() == FIELD_HSCRIPT ) + { + m_hfnHookFunc = g_pScriptVM->LookupFunction( "Call", hHooks ); + } + + Clear(); + } + + // + // On VM shutdown, clear the cache. + // Not exactly necessary, as the cache will be cleared on VM init next time. + // + void OnShutdown() + { + extern IScriptVM *g_pScriptVM; + + if ( m_hfnHookFunc ) + g_pScriptVM->ReleaseFunction( m_hfnHookFunc ); + + m_hfnHookFunc = NULL; + + Clear(); + } + + // + // On VM restore, update local cache. + // + void OnRestore() + { + extern IScriptVM *g_pScriptVM; + + ScriptVariant_t hHooks; + g_pScriptVM->GetValue( "Hooks", &hHooks ); + + if ( hHooks.GetType() == FIELD_HSCRIPT ) + { + // Existing m_hfnHookFunc is invalid + m_hfnHookFunc = g_pScriptVM->LookupFunction( "Call", hHooks ); + + HSCRIPT func = g_pScriptVM->LookupFunction( "__UpdateHooks", hHooks ); + g_pScriptVM->Call( func ); + g_pScriptVM->ReleaseFunction( func ); + g_pScriptVM->ReleaseValue( hHooks ); + } + } + + // + // Clear local cache. + // + void Clear() + { + if ( m_HookList.Count() ) + { + FOR_EACH_MAP_FAST( m_HookList, i ) + { + scopemap_t *scopeMap = m_HookList.Element(i); + + FOR_EACH_MAP_PTR_FAST( scopeMap, j ) + { + contextmap_t *contextMap = scopeMap->Element(j); + contextMap->PurgeAndDeleteElements(); + } + + char *szEvent = m_HookList.Key(i); + free( szEvent ); + + scopeMap->PurgeAndDeleteElements(); + } + + m_HookList.PurgeAndDeleteElements(); + } + } + + // + // Called from script, update local cache. + // + void Update( HSCRIPT hooksList ) + { + extern IScriptVM *g_pScriptVM; + + // Rebuild from scratch + Clear(); + { + ScriptVariant_t varEvent, varScopeMap; + int it = -1; + while ( ( it = g_pScriptVM->GetKeyValue( hooksList, it, &varEvent, &varScopeMap ) ) != -1 ) + { + // types are checked in script + Assert( varEvent.m_type == FIELD_CSTRING ); + Assert( varScopeMap.m_type == FIELD_HSCRIPT ); + + scopemap_t *scopeMap; + + int eventIdx = m_HookList.Find( const_cast< char* >( varEvent.Get() ) ); + if ( eventIdx != m_HookList.InvalidIndex() ) + { + scopeMap = m_HookList.Element( eventIdx ); + } + else + { + scopeMap = new scopemap_t( DefLessFunc(HScriptRaw) ); + m_HookList.Insert( strdup( varEvent.Get() ), scopeMap ); + } + + ScriptVariant_t varScope, varContextMap; + int it2 = -1; + while ( ( it2 = g_pScriptVM->GetKeyValue( varScopeMap, it2, &varScope, &varContextMap ) ) != -1 ) + { + Assert( varScope.m_type == FIELD_HSCRIPT ); + Assert( varContextMap.m_type == FIELD_HSCRIPT); + + contextmap_t *contextMap; + + int scopeIdx = scopeMap->Find( g_pScriptVM->HScriptToRaw( varScope.Get() ) ); + if ( scopeIdx != scopeMap->InvalidIndex() ) + { + contextMap = scopeMap->Element( scopeIdx ); + } + else + { + contextMap = new contextmap_t(); + scopeMap->Insert( g_pScriptVM->HScriptToRaw( varScope.Get() ), contextMap ); + } + + ScriptVariant_t varContext, varCallback; + int it3 = -1; + while ( ( it3 = g_pScriptVM->GetKeyValue( varContextMap, it3, &varContext, &varCallback ) ) != -1 ) + { + Assert( varContext.m_type == FIELD_CSTRING ); + Assert( varCallback.m_type == FIELD_HSCRIPT ); + + bool skip = false; + + FOR_EACH_VEC_PTR( contextMap, k ) + { + char *szContext = contextMap->Element(k); + if ( V_strcmp( szContext, varContext.Get() ) == 0 ) + { + skip = true; + break; + } + } + + if ( !skip ) + contextMap->AddToTail( strdup( varContext.Get() ) ); + + g_pScriptVM->ReleaseValue( varContext ); + g_pScriptVM->ReleaseValue( varCallback ); + } + + g_pScriptVM->ReleaseValue( varScope ); + g_pScriptVM->ReleaseValue( varContextMap ); + } + + g_pScriptVM->ReleaseValue( varEvent ); + g_pScriptVM->ReleaseValue( varScopeMap ); + } + } + } +#ifdef _DEBUG + void Dump() + { + extern IScriptVM *g_pScriptVM; + + FOR_EACH_MAP( m_HookList, i ) + { + scopemap_t *scopeMap = m_HookList.Element(i); + char *szEvent = m_HookList.Key(i); + + Msg( "%s [%p]\n", szEvent, (void*)scopeMap ); + Msg( "{\n" ); + + FOR_EACH_MAP_PTR( scopeMap, j ) + { + HScriptRaw hScope = scopeMap->Key(j); + contextmap_t *contextMap = scopeMap->Element(j); + + Msg( "\t(0x%X) [%p]\n", hScope, (void*)contextMap ); + Msg( "\t{\n" ); + + FOR_EACH_VEC_PTR( contextMap, k ) + { + char *szContext = contextMap->Element(k); + + Msg( "\t\t%-.50s\n", szContext ); + } + + Msg( "\t}\n" ); + } + + Msg( "}\n" ); + } + } +#endif +}; + +inline CScriptHookManager &GetScriptHookManager() +{ + static CScriptHookManager g_ScriptHookManager; + return g_ScriptHookManager; +} + +static void __UpdateScriptHooks( HSCRIPT hooksList ) +{ + GetScriptHookManager().Update( hooksList ); +} + + +//----------------------------------------------------------------------------- +// Function bindings allow script functions to run C++ functions. +// Hooks allow C++ functions to run script functions. +// +// This was previously done with raw function lookups, but Mapbase adds more and +// it's hard to keep track of them without proper standards or documentation. +//----------------------------------------------------------------------------- +struct ScriptHook_t +{ + ScriptFuncDescriptor_t m_desc; + CUtlVector m_pszParameterNames; + bool m_bDefined; + + void AddParameter( const char *pszName, ScriptDataType_t type ) + { + int iCur = m_desc.m_Parameters.Count(); + m_desc.m_Parameters.SetGrowSize( 1 ); m_desc.m_Parameters.EnsureCapacity( iCur + 1 ); m_desc.m_Parameters.AddToTail( type ); + m_pszParameterNames.SetGrowSize( 1 ); m_pszParameterNames.EnsureCapacity( iCur + 1 ); m_pszParameterNames.AddToTail( pszName ); + } + + // ----------------------------------------------------------------- + + // Only valid between CanRunInScope() and Call() + HSCRIPT m_hFunc; + + ScriptHook_t() : + m_hFunc(NULL) + { + } + +#ifdef _DEBUG + // + // An uninitialised script scope will pass as null scope which is considered a valid hook scope (global hook) + // This should catch CanRunInScope() calls without CScriptScope::IsInitalised() checks first. + // + bool CanRunInScope( CScriptScope &hScope ) + { + Assert( hScope.IsInitialized() ); + return hScope.IsInitialized() && CanRunInScope( (HSCRIPT)hScope ); + } +#endif + + // Checks if there's a function of this name which would run in this scope + bool CanRunInScope( HSCRIPT hScope ) + { + // For now, assume null scope (which is used for global hooks) is always hooked + if ( !hScope || GetScriptHookManager().IsEventHookedInScope( m_desc.m_pszScriptName, hScope ) ) + { + m_hFunc = NULL; + return true; + } + + extern IScriptVM *g_pScriptVM; + + // Legacy support if the new system is not being used + m_hFunc = g_pScriptVM->LookupFunction( m_desc.m_pszScriptName, hScope ); + + return !!m_hFunc; + } + + // Call the function + // NOTE: `bRelease` only exists for weapon_custom_scripted legacy script func caching + bool Call( HSCRIPT hScope, ScriptVariant_t *pReturn, ScriptVariant_t *pArgs, bool bRelease = true ) + { + extern IScriptVM *g_pScriptVM; + + // Call() should not be called without CanRunInScope() check first, it caches m_hFunc for legacy support + Assert( CanRunInScope( hScope ) ); + + // Legacy + if ( m_hFunc ) + { + for (int i = 0; i < m_desc.m_Parameters.Count(); i++) + { + g_pScriptVM->SetValue( m_pszParameterNames[i], pArgs[i] ); + } + + ScriptStatus_t status = g_pScriptVM->ExecuteFunction( m_hFunc, NULL, 0, pReturn, hScope, true ); + + if ( bRelease ) + g_pScriptVM->ReleaseFunction( m_hFunc ); + m_hFunc = NULL; + + for (int i = 0; i < m_desc.m_Parameters.Count(); i++) + { + g_pScriptVM->ClearValue( m_pszParameterNames[i] ); + } + + return status == SCRIPT_DONE; + } + // New Hook System + else + { + ScriptStatus_t status = g_pScriptVM->ExecuteHookFunction( m_desc.m_pszScriptName, pArgs, m_desc.m_Parameters.Count(), pReturn, hScope, true ); + return status == SCRIPT_DONE; + } + } +}; +#endif + //----------------------------------------------------------------------------- // Script function proxy support //----------------------------------------------------------------------------- @@ -1305,6 +2011,7 @@ public: if ( !m_hScriptFunc_##FuncName.IsNull() ) \ { \ ScriptVariant_t returnVal; \ + Assert( N == m_desc.m_Parameters.Count() ); \ ScriptStatus_t result = Call( m_hScriptFunc_##FuncName.hFunction, &returnVal, FUNC_CALL_ARGS_##N ); \ if ( result != SCRIPT_ERROR ) \ { \ @@ -1329,6 +2036,7 @@ public: \ if ( !m_hScriptFunc_##FuncName.IsNull() ) \ { \ + Assert( N == m_desc.m_Parameters.Count() ); \ ScriptStatus_t result = Call( m_hScriptFunc_##FuncName.hFunction, NULL, FUNC_CALL_ARGS_##N ); \ if ( result != SCRIPT_ERROR ) \ { \ diff --git a/src/public/vscript/variant.h b/src/public/vscript/variant.h index dbc2e428..1a94e8eb 100644 --- a/src/public/vscript/variant.h +++ b/src/public/vscript/variant.h @@ -110,6 +110,11 @@ public: CVariantBase( const char *val , bool bCopy = false ); CVariantBase( const Quaternion &val, bool bCopy = false ); +#ifdef MAPBASE_VSCRIPT + CVariantBase( const QAngle &val, bool bCopy = false ); + CVariantBase( const QAngle *val, bool bCopy = false ); +#endif + CVariantBase( const CVariantBase &variant ) :m_flags( 0 ), m_type( FIELD_VOID ) { variant.AssignTo( this ); } void operator=( const CVariantBase &variant ) { variant.AssignTo( this ); } @@ -192,7 +197,9 @@ public: void ConvertToCopiedData(bool silent = false ); +#ifndef MAPBASE_VSCRIPT private: +#endif void *Allocate( uint nSize ); template< typename T > T* Allocate(); void Free( void* pMemory ); @@ -393,6 +400,20 @@ inline CVariantBase::CVariantBase( const Quaternion &val, bool CopyData( val, bCopy ); } +#ifdef MAPBASE_VSCRIPT +template< class CValueAllocator > +inline CVariantBase::CVariantBase( const QAngle &val, bool bCopy ) : m_flags( 0 ), m_type( FIELD_VOID ) +{ + CopyData( val, bCopy ); +} + +template< class CValueAllocator > +inline CVariantBase::CVariantBase( const QAngle *val, bool bCopy ) : m_flags( 0 ), m_type( FIELD_VOID ) +{ + CopyData( *val, bCopy ); +} +#endif + //----------------------------------------------------------------------------- // Data freeing diff --git a/src/responserules/runtime/criteriaset.cpp b/src/responserules/runtime/criteriaset.cpp new file mode 100644 index 00000000..a2682caf --- /dev/null +++ b/src/responserules/runtime/criteriaset.cpp @@ -0,0 +1,479 @@ +//===== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======// +// +// Purpose: +// +// $NoKeywords: $ +// +//===========================================================================// +#include "rrbase.h" + +#include "utlmap.h" + +#include "tier1/mapbase_con_groups.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include + +using namespace ResponseRules; + +//----------------------------------------------------------------------------- +// Case-insensitive criteria symbol table +//----------------------------------------------------------------------------- +CUtlSymbolTable CriteriaSet::sm_CriteriaSymbols( 1024, 1024, true ); + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *raw - +// *key - +// keylen - +// *value - +// valuelen - +// *duration - +// Output : static bool +//----------------------------------------------------------------------------- +const char *SplitContext( const char *raw, char *key, int keylen, char *value, int valuelen, float *duration, const char *entireContext ) +{ + char *colon1 = Q_strstr( raw, ":" ); + if ( !colon1 ) + { + CGMsg( 1, CON_GROUP_RESPONSE_SYSTEM, "SplitContext: warning, ignoring context '%s', missing colon separator!\n", raw ); + *key = *value = 0; + return NULL; + } + + int len = colon1 - raw; + Q_strncpy( key, raw, MIN( len + 1, keylen ) ); + key[ MIN( len, keylen - 1 ) ] = 0; + + bool last = false; + char *end = Q_strstr( colon1 + 1, "," ); + if ( !end ) + { + int remaining = Q_strlen( colon1 + 1 ); + end = colon1 + 1 + remaining; + last = true; + } + + char *colon2 = Q_strstr( colon1 + 1, ":" ); + if ( colon2 && ( colon2 < end ) ) + { + if ( duration ) + *duration = atof( colon2 + 1 ); + + char durationStartChar = *(colon2 + 1); + if ( durationStartChar < '0' || durationStartChar > '9' ) + { + CGMsg( 1, CON_GROUP_RESPONSE_SYSTEM, "SplitContext: warning, ignoring context '%s', missing comma separator! Entire context was '%s'.\n", raw, entireContext ); + *key = *value = 0; + return NULL; + } + + len = MIN( colon2 - ( colon1 + 1 ), valuelen - 1 ); + Q_strncpy( value, colon1 + 1, len + 1 ); + value[ len ] = 0; + } + else + { + if ( duration ) + *duration = 0.0; + + len = MIN( end - ( colon1 + 1 ), valuelen - 1 ); + Q_strncpy( value, colon1 + 1, len + 1 ); + value[ len ] = 0; + } + + return last ? NULL : end + 1; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CriteriaSet::CriteriaSet() : m_Lookup( 0, 0, CritEntry_t::LessFunc ), m_bOverrideOnAppend(true), + m_nNumPrefixedContexts(0) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CriteriaSet::CriteriaSet( const CriteriaSet& src ) : m_Lookup( 0, 0, CritEntry_t::LessFunc ), m_nNumPrefixedContexts(src.m_nNumPrefixedContexts) +{ + m_Lookup.EnsureCapacity( src.m_Lookup.Count() ); + for ( short i = src.m_Lookup.FirstInorder(); + i != src.m_Lookup.InvalidIndex(); + i = src.m_Lookup.NextInorder( i ) ) + { + m_Lookup.Insert( src.m_Lookup[ i ] ); + } +} + +CriteriaSet::CriteriaSet( const char *criteria, const char *value ) : m_Lookup( 0, 0, CritEntry_t::LessFunc ), m_bOverrideOnAppend(true) +{ + AppendCriteria(criteria,value); +} + + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CriteriaSet::~CriteriaSet() +{ +} + +//----------------------------------------------------------------------------- +// Computes a symbol for the criteria +//----------------------------------------------------------------------------- +CriteriaSet::CritSymbol_t CriteriaSet::ComputeCriteriaSymbol( const char *criteria ) +{ + return sm_CriteriaSymbols.AddString( criteria ); +} + + +//----------------------------------------------------------------------------- +// Computes a symbol for the criteria +//----------------------------------------------------------------------------- +void CriteriaSet::AppendCriteria( CriteriaSet::CritSymbol_t criteria, const char *value, float weight ) +{ + int idx = FindCriterionIndex( criteria ); + if ( idx == -1 ) + { + CritEntry_t entry; + entry.criterianame = criteria; + MEM_ALLOC_CREDIT(); + idx = m_Lookup.Insert( entry ); + if ( sm_CriteriaSymbols.String(criteria)[0] == kAPPLYTOWORLDPREFIX ) + { + m_nNumPrefixedContexts += 1; + } + } + else // criteria already existed + { + // bail out if override existing criteria is not allowed + if ( !m_bOverrideOnAppend ) + return; + } + + CritEntry_t *entry = &m_Lookup[ idx ]; + entry->SetValue( value ); + entry->weight = weight; +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *criteria - +// "" - +// 1.0f - +//----------------------------------------------------------------------------- +void CriteriaSet::AppendCriteria( const char *pCriteriaName, const char *value /*= ""*/, float weight /*= 1.0f*/ ) +{ + CUtlSymbol criteria = ComputeCriteriaSymbol( pCriteriaName ); + AppendCriteria( criteria, value, weight ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *criteria - +// "" - +// 1.0f - +//----------------------------------------------------------------------------- +void CriteriaSet::AppendCriteria( const char *criteria, float value, float weight /*= 1.0f*/ ) +{ + char buf[32]; + V_snprintf( buf, 32, "%f", value ); + AppendCriteria( criteria, buf, weight ); +} + + +//----------------------------------------------------------------------------- +// Removes criteria in a set +//----------------------------------------------------------------------------- +void CriteriaSet::RemoveCriteria( const char *criteria ) +{ + const int idx = FindCriterionIndex( criteria ); + if ( idx == -1 ) + return; + + if ( criteria[0] == kAPPLYTOWORLDPREFIX ) + { + Assert( m_nNumPrefixedContexts > 0 ); + m_nNumPrefixedContexts = (((m_nNumPrefixedContexts - 1) >= 0) ? (m_nNumPrefixedContexts - 1) : (0)); + } + RemoveCriteria( idx, false ); +} + +// bTestForIndex tells us whether the calling function has already checked for a +// $ prefix and decremented m_nNumPrefixedContexts appropriately (false), +// or if this function should do that (true). +void CriteriaSet::RemoveCriteria( int idx, bool bTestForPrefix ) +{ + Assert( m_Lookup.IsValidIndex(idx) ); + if ( bTestForPrefix ) + { + if ( sm_CriteriaSymbols.String( m_Lookup[idx].criterianame )[0] == kAPPLYTOWORLDPREFIX ) + { + Assert( m_nNumPrefixedContexts > 0 ); + m_nNumPrefixedContexts = (((m_nNumPrefixedContexts - 1) >= 0) ? (m_nNumPrefixedContexts - 1) : (0)); + } + } + m_Lookup.RemoveAt( idx ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : int +//----------------------------------------------------------------------------- +int CriteriaSet::GetCount() const +{ + return m_Lookup.Count(); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *name - +// Output : int +//----------------------------------------------------------------------------- +int CriteriaSet::FindCriterionIndex( CritSymbol_t criteria ) const +{ + CritEntry_t search; + search.criterianame = criteria; + int idx = m_Lookup.Find( search ); + return ( idx == m_Lookup.InvalidIndex() ) ? -1 : idx; +} + +int CriteriaSet::FindCriterionIndex( const char *name ) const +{ + CUtlSymbol criteria = ComputeCriteriaSymbol( name ); + return FindCriterionIndex( criteria ); +} + + +//----------------------------------------------------------------------------- +// Returns the name symbol +//----------------------------------------------------------------------------- +CriteriaSet::CritSymbol_t CriteriaSet::GetNameSymbol( int nIndex ) const +{ + if ( nIndex < 0 || nIndex >= (int)m_Lookup.Count() ) + return UTL_INVAL_SYMBOL; + + const CritEntry_t *entry = &m_Lookup[ nIndex ]; + return entry->criterianame; +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : index - +// Output : char const +//----------------------------------------------------------------------------- +const char *CriteriaSet::GetName( int index ) const +{ + if ( index < 0 || index >= (int)m_Lookup.Count() ) + return ""; + else + { + const char *pCriteriaName = sm_CriteriaSymbols.String( m_Lookup[ index ].criterianame ); + return pCriteriaName ? pCriteriaName : ""; + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : index - +// Output : char const +//----------------------------------------------------------------------------- +const char *CriteriaSet::GetValue( int index ) const +{ + if ( index < 0 || index >= (int)m_Lookup.Count() ) + return ""; + + const CritEntry_t *entry = &m_Lookup[ index ]; + return entry->value ? entry->value : ""; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : index - +// Output : float +//----------------------------------------------------------------------------- +float CriteriaSet::GetWeight( int index ) const +{ + if ( index < 0 || index >= (int)m_Lookup.Count() ) + return 1.0f; + + const CritEntry_t *entry = &m_Lookup[ index ]; + return entry->weight; +} + + +//----------------------------------------------------------------------------- +// Purpose: Merge another criteria set into this one. +//----------------------------------------------------------------------------- +void CriteriaSet::Merge( const CriteriaSet * RESTRICT otherCriteria ) +{ + Assert(otherCriteria); + if (!otherCriteria) + return; + + // for now, just duplicate everything. + int count = otherCriteria->GetCount(); + EnsureCapacity( count + GetCount() ); + for ( int i = 0 ; i < count ; ++i ) + { + AppendCriteria( otherCriteria->GetNameSymbol(i), otherCriteria->GetValue(i), otherCriteria->GetWeight(i) ); + } +} + +void CriteriaSet::Merge( const char *modifiers ) // add criteria parsed from a text string +{ + // Always include any optional modifiers + if ( modifiers == NULL ) + return; + + char copy_modifiers[ 255 ]; + const char *pCopy; + char key[ 128 ] = { 0 }; + char value[ 128 ] = { 0 }; + + Q_strncpy( copy_modifiers, modifiers, sizeof( copy_modifiers ) ); + pCopy = copy_modifiers; + + while( pCopy ) + { + pCopy = SplitContext( pCopy, key, sizeof( key ), value, sizeof( value ), NULL, modifiers ); + + if( *key && *value ) + { + AppendCriteria( key, value, 1 ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CriteriaSet::Describe() const +{ + // build an alphabetized representation of the set for printing + typedef CUtlMap tMap; + tMap m_TempMap( 0, m_Lookup.Count(), CaselessStringLessThan ); + + for ( short i = m_Lookup.FirstInorder(); i != m_Lookup.InvalidIndex(); i = m_Lookup.NextInorder( i ) ) + { + const CritEntry_t *entry = &m_Lookup[ i ]; + + m_TempMap.Insert( sm_CriteriaSymbols.String( entry->criterianame ), entry ); + } + + for ( tMap::IndexType_t i = m_TempMap.FirstInorder(); i != m_TempMap.InvalidIndex(); i = m_TempMap.NextInorder( i ) ) + { + // const CritEntry_t *entry = &m_TempMap[ i ]; + // const char *pCriteriaName = sm_CriteriaSymbols.String( entry->criterianame ); + + const char *name = m_TempMap.Key( i ); + const CritEntry_t *entry = m_TempMap.Element( i ); + if ( entry->weight != 1.0f ) + { + CGMsg( 1, CON_GROUP_RESPONSE_SYSTEM, " %20s = '%s' (weight %f)\n", name, entry->value ? entry->value : "", entry->weight ); + } + else + { + CGMsg( 1, CON_GROUP_RESPONSE_SYSTEM, " %20s = '%s'\n", name, entry->value ? entry->value : "" ); + } + } + + /* + for ( short i = m_Lookup.FirstInorder(); i != m_Lookup.InvalidIndex(); i = m_Lookup.NextInorder( i ) ) + { + const CritEntry_t *entry = &m_Lookup[ i ]; + + const char *pCriteriaName = sm_CriteriaSymbols.String( entry->criterianame ); + if ( entry->weight != 1.0f ) + { + CGMsg( 1, CON_GROUP_RESPONSE_SYSTEM, " %20s = '%s' (weight %f)\n", pCriteriaName, entry->value ? entry->value : "", entry->weight ); + } + else + { + CGMsg( 1, CON_GROUP_RESPONSE_SYSTEM, " %20s = '%s'\n", pCriteriaName, entry->value ? entry->value : "" ); + } + } + */ +} + + +void CriteriaSet::Reset() +{ + m_Lookup.Purge(); +} + +void CriteriaSet::WriteToEntity( CBaseEntity *pEntity ) +{ +#if 0 + if ( GetCount() < 1 ) + return; + + for ( int i = Head() ; IsValidIndex(i); i = Next(i) ) + { + pEntity->AddContext( GetName(i), GetValue(i), 0 ); + } +#else + AssertMsg( false, "CriteriaSet::WriteToEntity has not been ported from l4d2.\n" ); +#endif +} + +int CriteriaSet::InterceptWorldSetContexts( CriteriaSet * RESTRICT pFrom, CriteriaSet * RESTRICT pSetOnWorld ) +{ + // Assert( pFrom ); Assert( pTo ); Assert( pSetOnWorld ); + Assert( pSetOnWorld != pFrom ); + Assert( pSetOnWorld->GetCount() == 0 ); + + if ( pFrom->m_nNumPrefixedContexts == 0 ) + { + // nothing needs to be done to it. + return 0; + } + +#ifdef DEBUG + // save this off for later error checking. + const int nPrefixedContexts = pFrom->m_nNumPrefixedContexts; +#endif + + // make enough space for the expected output quantity. + pSetOnWorld->EnsureCapacity( pFrom->m_nNumPrefixedContexts ); + + // initialize a buffer with the "world" prefix (so we can use strncpy instead of snprintf and be much faster) + char buf[80] = { 'w', 'o', 'r', 'l', 'd', '\0' }; + const unsigned int PREFIXLEN = 5; // strlen("world") + + // create a second tree that has the appropriately renamed criteria, + // then swap it into pFrom + CriteriaSet rewrite; + rewrite.EnsureCapacity( pFrom->GetCount() + 1 ); + + for ( int i = pFrom->Head(); pFrom->IsValidIndex(i); i = pFrom->Next(i) ) + { + const char *pszName = pFrom->GetName( i ); + if ( pszName[0] == CriteriaSet::kAPPLYTOWORLDPREFIX ) + { // redirect to the world contexts + V_strncpy( buf+PREFIXLEN, pszName+1, sizeof(buf) - PREFIXLEN ); + rewrite.AppendCriteria( buf, pFrom->GetValue(i), pFrom->GetWeight(i) ); + pSetOnWorld->AppendCriteria( pszName+1, pFrom->GetValue(i), pFrom->GetWeight(i) ); + buf[PREFIXLEN] = 0; + } + else + { // does not need to be fiddled; do not write back to world + rewrite.AppendCriteria( pFrom->GetNameSymbol(i), pFrom->GetValue(i), pFrom->GetWeight(i) ); + } + } + AssertMsg2( pSetOnWorld->GetCount() == nPrefixedContexts, "Count of $ persistent RR contexts is inconsistent (%d vs %d)! Call Elan.", + pSetOnWorld->GetCount(), nPrefixedContexts ); + + pFrom->m_nNumPrefixedContexts = 0; + pFrom->m_Lookup.Swap(rewrite.m_Lookup); + return pSetOnWorld->GetCount(); +} diff --git a/src/responserules/runtime/response_rules.vpc b/src/responserules/runtime/response_rules.vpc new file mode 100644 index 00000000..9ab73afa --- /dev/null +++ b/src/responserules/runtime/response_rules.vpc @@ -0,0 +1,41 @@ +//----------------------------------------------------------------------------- +// response_rules.VPC +// +// Project Script +//----------------------------------------------------------------------------- + +$macro SRCDIR "..\.." +$include "$SRCDIR\vpc_scripts\source_lib_base.vpc" + +$Configuration +{ + $Compiler + { + $AdditionalIncludeDirectories "$BASE;..\public\responserules" + $PreprocessorDefinitions "$BASE;RR_RUNTIME" + } +} + +$Project "responserules_runtime" +{ + $Folder "Source Files" + { + $File "criteriaset.cpp" + $File "response_system.cpp" + $File "response_system.h" + $File "response_types.cpp" + $File "response_types_internal.cpp" + $File "response_types_internal.h" + $File "rr_convars.cpp" + $File "rr_response.cpp" + $File "rr_speechconcept.cpp" + $File "rrrlib.cpp" + } + + $Folder "Public Header Files" + { + $File "$SRCDIR\public\responserules\response_host_interface.h" + $File "$SRCDIR\public\responserules\response_types.h" + $File "$SRCDIR\public\responserules\rr_speechconcept.h" + } +} \ No newline at end of file diff --git a/src/responserules/runtime/response_system.cpp b/src/responserules/runtime/response_system.cpp new file mode 100644 index 00000000..7ae9276c --- /dev/null +++ b/src/responserules/runtime/response_system.cpp @@ -0,0 +1,2959 @@ +//========= Copyright © 1996-2010, Valve Corporation, All rights reserved. ============// +// +// Purpose: Core types for the response rules -- criteria, responses, rules, and matchers. +// +// $NoKeywords: $ +//=============================================================================// + +#include "rrbase.h" +#include "vstdlib/random.h" +#include "utlbuffer.h" +#include "tier1/interval.h" +#include "convar.h" +#include "fmtstr.h" +#include "generichash.h" +#include "tier1/mapbase_con_groups.h" +#ifdef MAPBASE +#include "tier1/mapbase_matchers_base.h" +#endif + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +// #pragma optimize( "", off ) + +using namespace ResponseRules; +static void CC_RR_Debug_ResponseConcept_Exclude( const CCommand &args ); +static ConCommand rr_debug_responseconcept_exclude( "rr_debugresponseconcept_exclude", CC_RR_Debug_ResponseConcept_Exclude, "Set a list of concepts to exclude from rr_debugresponseconcept. Separate multiple concepts with spaces. Call with no arguments to see current list. Call 'rr_debug_responseconcept_exclude !' to reset."); + +namespace ResponseRules +{ + // ick + // Wrap string lookup with a hash on the string so that all of the repetitive playerxxx type strings get bucketed out better + #define STRING_BUCKETS_COUNT 64 // Must be power of two + #define STRING_BUCKETS_MASK ( STRING_BUCKETS_COUNT - 1 ) + + CUtlRBTree g_ResponseStrings[ STRING_BUCKETS_COUNT ]; + class CResponseStringBuckets + { + public: + CResponseStringBuckets() + { + for ( int i = 0; i < ARRAYSIZE( g_ResponseStrings ); ++i ) + { + g_ResponseStrings[ i ].SetLessFunc( &StringLessThan ); + } + } + } g_ReponseStringBucketInitializer; + + const char *ResponseCopyString( const char *in ) + { + if ( !in ) + return NULL; + if ( !*in ) + return ""; + + int bucket = ( RR_HASH( in ) & STRING_BUCKETS_MASK ); + + CUtlRBTree &list = g_ResponseStrings[ bucket ]; + + int i = list.Find( in ); + if ( i != list.InvalidIndex() ) +{ + return list[i]; + } + + int len = Q_strlen( in ); + char *out = new char[ len + 1 ]; + Q_memcpy( out, in, len ); + out[ len ] = 0; + list.Insert( out ); + return out; + } +} + +IEngineEmulator *IEngineEmulator::Get() +{ + AssertMsg( IEngineEmulator::s_pSingleton, "Response rules fail: no IEngineEmulator" ); + return IEngineEmulator::s_pSingleton; +} + + +//----------------------------------------------------------------------------- +// Convars +//----------------------------------------------------------------------------- + + +ConVar rr_debugresponses( "rr_debugresponses", "0", FCVAR_NONE, "Show verbose matching output (1 for simple, 2 for rule scoring, 3 for noisy). If set to 4, it will only show response success/failure for npc_selected NPCs." ); +ConVar rr_debugrule( "rr_debugrule", "", FCVAR_NONE, "If set to the name of the rule, that rule's score will be shown whenever a concept is passed into the response rules system."); +ConVar rr_dumpresponses( "rr_dumpresponses", "0", FCVAR_NONE, "Dump all response_rules.txt and rules (requires restart)" ); +ConVar rr_debugresponseconcept( "rr_debugresponseconcept", "", FCVAR_NONE, "If set, rr_debugresponses will print only responses testing for the specified concept" ); +#define RR_DEBUGRESPONSES_SPECIALCASE 4 + +#ifdef MAPBASE +ConVar rr_disableemptyrules( "rr_disableemptyrules", "0", FCVAR_NONE, "Disables rules with no remaining responses, e.g. rules which use norepeat responses." ); +#endif + + + +//----------------------------------------------------------------------------- +// Copied from SoundParametersInternal.cpp +//----------------------------------------------------------------------------- + +#define SNDLVL_PREFIX "SNDLVL_" + +struct SoundLevelLookup +{ + soundlevel_t level; + char const *name; +}; + +// NOTE: Needs to reflect the soundlevel_t enum defined in soundflags.h +static SoundLevelLookup g_pSoundLevels[] = +{ + { SNDLVL_NONE, "SNDLVL_NONE" }, + { SNDLVL_20dB, "SNDLVL_20dB" }, + { SNDLVL_25dB, "SNDLVL_25dB" }, + { SNDLVL_30dB, "SNDLVL_30dB" }, + { SNDLVL_35dB, "SNDLVL_35dB" }, + { SNDLVL_40dB, "SNDLVL_40dB" }, + { SNDLVL_45dB, "SNDLVL_45dB" }, + { SNDLVL_50dB, "SNDLVL_50dB" }, + { SNDLVL_55dB, "SNDLVL_55dB" }, + { SNDLVL_IDLE, "SNDLVL_IDLE" }, + { SNDLVL_TALKING, "SNDLVL_TALKING" }, + { SNDLVL_60dB, "SNDLVL_60dB" }, + { SNDLVL_65dB, "SNDLVL_65dB" }, + { SNDLVL_STATIC, "SNDLVL_STATIC" }, + { SNDLVL_70dB, "SNDLVL_70dB" }, + { SNDLVL_NORM, "SNDLVL_NORM" }, + { SNDLVL_75dB, "SNDLVL_75dB" }, + { SNDLVL_80dB, "SNDLVL_80dB" }, + { SNDLVL_85dB, "SNDLVL_85dB" }, + { SNDLVL_90dB, "SNDLVL_90dB" }, + { SNDLVL_95dB, "SNDLVL_95dB" }, + { SNDLVL_100dB, "SNDLVL_100dB" }, + { SNDLVL_105dB, "SNDLVL_105dB" }, + { SNDLVL_110dB, "SNDLVL_110dB" }, + { SNDLVL_120dB, "SNDLVL_120dB" }, + { SNDLVL_130dB, "SNDLVL_130dB" }, + { SNDLVL_GUNFIRE, "SNDLVL_GUNFIRE" }, + { SNDLVL_140dB, "SNDLVL_140dB" }, + { SNDLVL_150dB, "SNDLVL_150dB" }, + { SNDLVL_180dB, "SNDLVL_180dB" }, +}; + +static soundlevel_t TextToSoundLevel( const char *key ) +{ + if ( !key ) + { + Assert( 0 ); + return SNDLVL_NORM; + } + + int c = ARRAYSIZE( g_pSoundLevels ); + + int i; + + for ( i = 0; i < c; i++ ) + { + SoundLevelLookup *entry = &g_pSoundLevels[ i ]; + if ( !Q_strcasecmp( key, entry->name ) ) + return entry->level; + } + + if ( !Q_strnicmp( key, SNDLVL_PREFIX, Q_strlen( SNDLVL_PREFIX ) ) ) + { + char const *val = key + Q_strlen( SNDLVL_PREFIX ); + int sndlvl = atoi( val ); + if ( sndlvl > 0 && sndlvl <= 180 ) + { + return ( soundlevel_t )sndlvl; + } + } + + DevMsg( "CSoundEmitterSystem: Unknown sound level %s\n", key ); + + return SNDLVL_NORM; +} + +CResponseSystem::ExcludeList_t CResponseSystem::m_DebugExcludeList( 4, 0 ); + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CResponseSystem::CResponseSystem() : + m_RootCommandHashes( 0, 0, DefLessFunc( unsigned int ) ), + m_FileDispatch( 0, 0, DefLessFunc( unsigned int ) ), + m_RuleDispatch( 0, 0, DefLessFunc( unsigned int ) ), + m_ResponseDispatch( 0, 0, DefLessFunc( unsigned int ) ), + m_ResponseGroupDispatch( 0, 0, DefLessFunc( unsigned int ) ) +{ + token[0] = 0; + m_bUnget = false; + m_bCustomManagable = false; +#ifdef MAPBASE + m_bInProspective = false; +#endif + + BuildDispatchTables(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CResponseSystem::~CResponseSystem() +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : char const +//----------------------------------------------------------------------------- +void CResponseSystem::GetCurrentScript( char *buf, size_t buflen ) +{ + Assert( buf ); + buf[ 0 ] = 0; + if ( m_ScriptStack.Count() <= 0 ) + return; + + if ( IEngineEmulator::Get()->GetFilesystem()->String( m_ScriptStack[ 0 ].name, buf, buflen ) ) + { + return; + } + buf[ 0 ] = 0; +} + +void CResponseSystem::PushScript( const char *scriptfile, unsigned char *buffer ) +{ + ScriptEntry e; + e.name = IEngineEmulator::Get()->GetFilesystem()->FindOrAddFileName( scriptfile ); + e.buffer = buffer; + e.currenttoken = (char *)e.buffer; + e.tokencount = 0; + m_ScriptStack.AddToHead( e ); +} + +void CResponseSystem::PopScript(void) +{ + Assert( m_ScriptStack.Count() >= 1 ); + if ( m_ScriptStack.Count() <= 0 ) + return; + + m_ScriptStack.Remove( 0 ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CResponseSystem::Clear() +{ + m_Responses.RemoveAll(); + m_Criteria.RemoveAll(); +#ifdef MAPBASE + // Must purge to avoid issues with reloading the system + m_RulePartitions.PurgeAndDeleteElements(); +#else + m_RulePartitions.RemoveAll(); +#endif + m_Enumerations.RemoveAll(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *name - +// found - +// Output : float +//----------------------------------------------------------------------------- +float CResponseSystem::LookupEnumeration( const char *name, bool& found ) +{ + int idx = m_Enumerations.Find( name ); + if ( idx == m_Enumerations.InvalidIndex() ) + { + found = false; + return 0.0f; + } + + + found = true; + return m_Enumerations[ idx ].value; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : matcher - +//----------------------------------------------------------------------------- +void CResponseSystem::ResolveToken( Matcher& matcher, char *token, size_t bufsize, char const *rawtoken ) +{ + if ( rawtoken[0] != '[' ) + { + Q_strncpy( token, rawtoken, bufsize ); + return; + } + + // Now lookup enumeration + bool found = false; + float f = LookupEnumeration( rawtoken, found ); + if ( !found ) + { + Q_strncpy( token, rawtoken, bufsize ); + ResponseWarning( "No such enumeration '%s'\n", token ); + return; + } + + Q_snprintf( token, bufsize, "%f", f ); +} + + +#ifndef MAPBASE // Already in mapbase_matchers_base +static bool AppearsToBeANumber( char const *token ) +{ + if ( atof( token ) != 0.0f ) + return true; + + char const *p = token; + while ( *p ) + { + if ( *p != '0' ) + return false; + + p++; + } + + return true; +} +#endif + +void CResponseSystem::ComputeMatcher( Criteria *c, Matcher& matcher ) +{ + const char *s = c->value; + if ( !s ) + { + matcher.valid = false; + return; + } + + const char *in = s; + + char token[ 128 ]; + char rawtoken[ 128 ]; + + token[ 0 ] = 0; + rawtoken[ 0 ] = 0; + + int n = 0; + + bool gt = false; + bool lt = false; + bool eq = false; + bool nt = false; +#ifdef MAPBASE + bool bit = false; +#endif + + bool done = false; + while ( !done ) + { + switch( *in ) + { + case '>': + { + gt = true; + Assert( !lt ); // Can't be both + } + break; + case '<': + { + lt = true; + Assert( !gt ); // Can't be both + } + break; + case '=': + { + eq = true; + } + break; + case ',': + case '\0': + { + rawtoken[ n ] = 0; + n = 0; + + // Convert raw token to real token in case token is an enumerated type specifier + ResolveToken( matcher, token, sizeof( token ), rawtoken ); + +#ifdef MAPBASE + // Bits are an entirely different and independent story + if (bit) + { + matcher.isbit = true; + matcher.notequal = nt; + + matcher.isnumeric = true; + } + else +#endif + // Fill in first data set + if ( gt ) + { + matcher.usemin = true; + matcher.minequals = eq; + matcher.minval = (float)atof( token ); + + matcher.isnumeric = true; + } + else if ( lt ) + { + matcher.usemax = true; + matcher.maxequals = eq; + matcher.maxval = (float)atof( token ); + + matcher.isnumeric = true; + } + else + { + if ( *in == ',' ) + { + // If there's a comma, this better have been a less than or a gt key + Assert( 0 ); + } + + matcher.notequal = nt; + + matcher.isnumeric = AppearsToBeANumber( token ); + } + + gt = lt = eq = nt = false; + + if ( !(*in) ) + { + done = true; + } + } + break; + case '!': + nt = true; + break; +#ifdef MAPBASE + case '~': + nt = true; + case '&': + bit = true; + break; +#endif + default: + rawtoken[ n++ ] = *in; + break; + } + + in++; + } + + matcher.SetToken( token ); + matcher.SetRaw( rawtoken ); + matcher.valid = true; +} + +bool CResponseSystem::CompareUsingMatcher( const char *setValue, Matcher& m, bool verbose /*=false*/ ) +{ + if ( !m.valid ) + return false; + + float v = (float)atof( setValue ); + if ( setValue[0] == '[' ) + { + bool found = false; + v = LookupEnumeration( setValue, found ); + } + +#ifdef MAPBASE + // Bits are always a different story + if (m.isbit) + { + int v1 = v; + int v2 = atoi( m.GetToken() ); + if (m.notequal) + return (v1 & v2) == 0; + else + return (v1 & v2) != 0; + } +#endif + + int minmaxcount = 0; + + if ( m.usemin ) + { + if ( m.minequals ) + { + if ( v < m.minval ) + return false; + } + else + { + if ( v <= m.minval ) + return false; + } + + ++minmaxcount; + } + + if ( m.usemax ) + { + if ( m.maxequals ) + { + if ( v > m.maxval ) + return false; + } + else + { + if ( v >= m.maxval ) + return false; + } + + ++minmaxcount; + } + + // Had one or both criteria and met them + if ( minmaxcount >= 1 ) + { + return true; + } + + if ( m.notequal ) + { + if ( m.isnumeric ) + { + if ( v == (float)atof( m.GetToken() ) ) + return false; + } + else + { +#ifdef MAPBASE + if ( Matcher_NamesMatch( m.GetToken(), setValue ) ) +#else + if ( !Q_stricmp( setValue, m.GetToken() ) ) +#endif + return false; + } + + return true; + } + + if ( m.isnumeric ) + { + // If the setValue is "", the NPC doesn't have the key at all, + // in which case we shouldn't match "0". + if ( !setValue || !setValue[0] ) + return false; + + return v == (float)atof( m.GetToken() ); + } + +#ifdef MAPBASE + return Matcher_NamesMatch( m.GetToken(), setValue ); +#else + return !Q_stricmp( setValue, m.GetToken() ) ? true : false; +#endif +} + +bool CResponseSystem::Compare( const char *setValue, Criteria *c, bool verbose /*= false*/ ) +{ + Assert( c ); + Assert( setValue ); + + bool bret = CompareUsingMatcher( setValue, c->matcher, verbose ); + + if ( verbose ) + { + CGMsg( 1, CON_GROUP_RESPONSE_SYSTEM, "'%20s' vs. '%20s' = ", setValue, c->value ); + + { + //DevMsg( "\n" ); + //m.Describe(); + } + } + return bret; +} + +float CResponseSystem::RecursiveScoreSubcriteriaAgainstRule( const CriteriaSet& set, Criteria *parent, bool& exclude, bool verbose /*=false*/ ) +{ + float score = 0.0f; + int subcount = parent->subcriteria.Count(); + for ( int i = 0; i < subcount; i++ ) + { + int icriterion = parent->subcriteria[ i ]; + + bool excludesubrule = false; + if (verbose) + { + DevMsg( "\n" ); + } + score += ScoreCriteriaAgainstRuleCriteria( set, icriterion, excludesubrule, verbose ); + } + + exclude = ( parent->required && score == 0.0f ) ? true : false; + + return score * parent->weight.GetFloat(); +} + +float CResponseSystem::RecursiveLookForCriteria( const CriteriaSet &criteriaSet, Criteria *pParent ) +{ + float flScore = 0.0f; + int nSubCount = pParent->subcriteria.Count(); + for ( int iSub = 0; iSub < nSubCount; ++iSub ) + { + int iCriteria = pParent->subcriteria[iSub]; + flScore += LookForCriteria( criteriaSet, iCriteria ); + } + + return flScore; +} + +float CResponseSystem::LookForCriteria( const CriteriaSet &criteriaSet, int iCriteria ) +{ + Criteria *pCriteria = &m_Criteria[iCriteria]; + if ( pCriteria->IsSubCriteriaType() ) + { + return RecursiveLookForCriteria( criteriaSet, pCriteria ); + } + + int iIndex = criteriaSet.FindCriterionIndex( pCriteria->nameSym ); + if ( iIndex == -1 ) + return 0.0f; + + Assert( criteriaSet.GetValue( iIndex ) ); + if ( Q_stricmp( criteriaSet.GetValue( iIndex ), pCriteria->value ) ) + return 0.0f; + + return 1.0f; +} + +float CResponseSystem::ScoreCriteriaAgainstRuleCriteria( const CriteriaSet& set, int icriterion, bool& exclude, bool verbose /*=false*/ ) +{ + Criteria *c = &m_Criteria[ icriterion ]; + + if ( c->IsSubCriteriaType() ) + { + return RecursiveScoreSubcriteriaAgainstRule( set, c, exclude, verbose ); + } + + if ( verbose ) + { + CGMsg( 1, CON_GROUP_RESPONSE_SYSTEM, " criterion '%25s':'%15s' ", m_Criteria.GetElementName( icriterion ), CriteriaSet::SymbolToStr(c->nameSym) ); + } + + exclude = false; + + float score = 0.0f; + + const char *actualValue = ""; + + /* + const char * RESTRICT critname = c->name; + CUtlSymbol sym(critname); + const char * nameDoubleCheck = sym.String(); + */ + int found = set.FindCriterionIndex( c->nameSym ); + if ( found != -1 ) + { + actualValue = set.GetValue( found ); + if ( !actualValue ) + { + Assert( 0 ); + return score; + } + } + + Assert( actualValue ); + + if ( Compare( actualValue, c, verbose ) ) + { + float w = set.GetWeight( found ); + score = w * c->weight.GetFloat(); + + if ( verbose ) + { + CGMsg( 1, CON_GROUP_RESPONSE_SYSTEM, "matched, weight %4.2f (s %4.2f x c %4.2f)", + score, w, c->weight.GetFloat() ); + } + } + else + { + if ( c->required ) + { + exclude = true; + if ( verbose ) + { + CGMsg( 1, CON_GROUP_RESPONSE_SYSTEM, "failed (+exclude rule)" ); + } + } + else + { + if ( verbose ) + { + CGMsg( 1, CON_GROUP_RESPONSE_SYSTEM, "failed" ); + } + } + } + + return score; +} + +float CResponseSystem::ScoreCriteriaAgainstRule( const CriteriaSet& set, ResponseRulePartition::tRuleDict &dict, int irule, bool verbose /*=false*/ ) +{ + Rule * RESTRICT rule = dict[ irule ]; + float score = 0.0f; + + bool bBeingWatched = false; + + // See if we're trying to debug this rule + const char *pszText = rr_debugrule.GetString(); + if ( pszText && pszText[0] && !Q_stricmp( pszText, dict.GetElementName( irule ) ) ) + { + bBeingWatched = true; + } + + if ( !rule->IsEnabled() ) + { + if ( bBeingWatched ) + { + CGMsg( 1, CON_GROUP_RESPONSE_SYSTEM, "Rule is disabled.\n" ); + } + return 0.0f; + } + + if ( bBeingWatched ) + { + verbose = true; + } + + if ( verbose ) + { + CGMsg( 1, CON_GROUP_RESPONSE_SYSTEM, "Scoring rule '%s' (%i)\n{\n", dict.GetElementName( irule ), irule+1 ); + } + + // Iterate set criteria + int count = rule->m_Criteria.Count(); + int i; + for ( i = 0; i < count; i++ ) + { + int icriterion = rule->m_Criteria[ i ]; + + bool exclude = false; + score += ScoreCriteriaAgainstRuleCriteria( set, icriterion, exclude, verbose ); + + if ( verbose ) + { + CGMsg( 1, CON_GROUP_RESPONSE_SYSTEM, ", score %4.2f\n", score ); + } + + if ( exclude ) + { + score = 0.0f; + break; + } + } + + if ( verbose ) + { + CGMsg( 1, CON_GROUP_RESPONSE_SYSTEM, "}\n" ); + } + + if ( rule->m_nForceWeight > 0 ) + { // this means override the cumulative weight of criteria and just force the rule's total score, + // assuming it matched at all. + return fsel( score - FLT_MIN, rule->m_nForceWeight, 0 ); + } + else + { + return score; +} +} + +void CResponseSystem::DebugPrint( int depth, const char *fmt, ... ) +{ + int indentchars = 3 * depth; + char *indent = (char *) stackalloc( indentchars + 1); + indent[ indentchars ] = 0; + while ( --indentchars >= 0 ) + { + indent[ indentchars ] = ' '; + } + + // Dump text to debugging console. + va_list argptr; + char szText[1024]; + + va_start (argptr, fmt); + Q_vsnprintf (szText, sizeof( szText ), fmt, argptr); + va_end (argptr); + + CGMsg( 1, CON_GROUP_RESPONSE_SYSTEM, "%s%s", indent, szText ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CResponseSystem::ResetResponseGroups() +{ + int i; + int c = m_Responses.Count(); + for ( i = 0; i < c; i++ ) + { + m_Responses[ i ].Reset(); + } + +#ifdef MAPBASE + for ( ResponseRulePartition::tIndex idx = m_RulePartitions.First() ; + m_RulePartitions.IsValid(idx) ; + idx = m_RulePartitions.Next(idx) ) + { + m_RulePartitions[ idx ].m_bEnabled = true; + } +#endif +} + +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CResponseSystem::DisableEmptyRules() +{ + if (rr_disableemptyrules.GetBool() == false) + return; + + for ( ResponseRulePartition::tIndex idx = m_RulePartitions.First() ; + m_RulePartitions.IsValid(idx) ; + idx = m_RulePartitions.Next(idx) ) + { + Rule &rule = m_RulePartitions[ idx ]; + + // Set it as disabled in advance + rule.m_bEnabled = false; + + int c2 = rule.m_Responses.Count(); + for (int s = 0; s < c2; s++) + { + if (m_Responses[rule.m_Responses[s]].IsEnabled()) + { + // Re-enable it if there's any valid responses + rule.m_bEnabled = true; + break; + } + } + } +} +#endif + +//----------------------------------------------------------------------------- +// Purpose: Make certain responses unavailable by marking them as depleted +//----------------------------------------------------------------------------- +void CResponseSystem::FakeDepletes( ResponseGroup *g, IResponseFilter *pFilter ) +{ + m_FakedDepletes.RemoveAll(); + + // Fake depletion of unavailable choices + int c = g->group.Count(); + if ( pFilter && g->ShouldCheckRepeats() ) + { + for ( int i = 0; i < c; i++ ) + { + ParserResponse *r = &g->group[ i ]; + if ( r->depletioncount != g->GetDepletionCount() && !pFilter->IsValidResponse( r->GetType(), r->value ) ) + { + m_FakedDepletes.AddToTail( i ); + g->MarkResponseUsed( i ); + } + } + } + + // Fake depletion of choices that fail the odds check + for ( int i = 0; i < c; i++ ) + { + ParserResponse *r = &g->group[ i ]; + if ( RandomInt( 1, 100 ) > r->params.odds ) + { + m_FakedDepletes.AddToTail( i ); + g->MarkResponseUsed( i ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Restore responses that were faked as being depleted +//----------------------------------------------------------------------------- +void CResponseSystem::RevertFakedDepletes( ResponseGroup *g ) +{ + for ( int i = 0; i < m_FakedDepletes.Count(); i++ ) + { + g->group[ m_FakedDepletes[ i ] ].depletioncount = 0; + } + m_FakedDepletes.RemoveAll(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *g - +// Output : int +//----------------------------------------------------------------------------- +int CResponseSystem::SelectWeightedResponseFromResponseGroup( ResponseGroup *g, IResponseFilter *pFilter ) +{ + int c = g->group.Count(); + if ( !c ) + { + Assert( !"Expecting response group with >= 1 elements" ); + return -1; + } + + FakeDepletes( g, pFilter ); + + if ( !g->HasUndepletedChoices() ) + { + g->ResetDepletionCount(); + + FakeDepletes( g, pFilter ); + + if ( !g->HasUndepletedChoices() ) + return -1; + + // Disable the group if we looped through all the way + if ( g->IsNoRepeat() ) + { + g->SetEnabled( false ); +#ifdef MAPBASE + DisableEmptyRules(); +#endif + return -1; + } + } + + bool checkrepeats = g->ShouldCheckRepeats(); + int depletioncount = g->GetDepletionCount(); + + float totalweight = 0.0f; + int slot = -1; + + if ( checkrepeats ) + { + int check= -1; + // Snag the first slot right away + if ( g->HasUndepletedFirst( check ) && check != -1 ) + { + slot = check; + } + + if ( slot == -1 && g->HasUndepletedLast( check ) && check != -1 ) + { + // If this is the only undepleted one, use it now + int i; + for ( i = 0; i < c; i++ ) + { + ParserResponse *r = &g->group[ i ]; + if ( checkrepeats && + ( r->depletioncount == depletioncount ) ) + { + continue; + } + + if ( r->last ) + { + Assert( i == check ); + continue; + } + + // There's still another undepleted entry + break; + } + + // No more undepleted so use the r->last slot + if ( i >= c ) + { + slot = check; + } + } + } + + if ( slot == -1 ) + { + for ( int i = 0; i < c; i++ ) + { + ParserResponse *r = &g->group[ i ]; + if ( checkrepeats && + ( r->depletioncount == depletioncount ) ) + { + continue; + } + + // Always skip last entry here since we will deal with it above + if ( checkrepeats && r->last ) + continue; + + int prevSlot = slot; + + if ( !totalweight ) + { + slot = i; + } + + // Always assume very first slot will match + totalweight += r->weight.GetFloat(); + if ( !totalweight || IEngineEmulator::Get()->GetRandomStream()->RandomFloat(0,totalweight) < r->weight.GetFloat() ) + { + slot = i; + } + + if ( !checkrepeats && slot != prevSlot && pFilter && !pFilter->IsValidResponse( r->GetType(), r->value ) ) + { + slot = prevSlot; + totalweight -= r->weight.GetFloat(); + } + } + } + + if ( slot != -1 ) + { +#ifdef MAPBASE + // Don't mark responses as used in prospective mode + if (m_bInProspective == false) +#endif + g->MarkResponseUsed( slot ); + } + + // Revert fake depletion of unavailable choices + RevertFakedDepletes( g ); + + return slot; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : searchResult - +// depth - +// *name - +// verbose - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CResponseSystem::ResolveResponse( ResponseSearchResult& searchResult, int depth, const char *name, bool verbose /*= false*/, IResponseFilter *pFilter ) +{ + int responseIndex = m_Responses.Find( name ); + if ( responseIndex == m_Responses.InvalidIndex() ) + return false; + + ResponseGroup *g = &m_Responses[ responseIndex ]; + // Group has been disabled + if ( !g->IsEnabled() ) + return false; + + int c = g->group.Count(); + if ( !c ) + return false; + + int idx = 0; + + if ( g->IsSequential() ) + { + // See if next index is valid + int initialIndex = g->GetCurrentIndex(); + bool bFoundValid = false; + + do + { + idx = g->GetCurrentIndex(); + g->SetCurrentIndex( idx + 1 ); + if ( idx >= c ) + { + if ( g->IsNoRepeat() ) + { + g->SetEnabled( false ); +#ifdef MAPBASE + DisableEmptyRules(); +#endif + return false; + } + idx = 0; + g->SetCurrentIndex( 0 ); + } + + if ( !pFilter || pFilter->IsValidResponse( g->group[idx].GetType(), g->group[idx].value ) ) + { + bFoundValid = true; + break; + } + + } while ( g->GetCurrentIndex() != initialIndex ); + + if ( !bFoundValid ) + return false; + } + else + { + idx = SelectWeightedResponseFromResponseGroup( g, pFilter ); + if ( idx < 0 ) + return false; + } + + if ( verbose ) + { + DebugPrint( depth, "%s\n", m_Responses.GetElementName( responseIndex ) ); + DebugPrint( depth, "{\n" ); + DescribeResponseGroup( g, idx, depth ); + } + + bool bret = true; + + ParserResponse *result = &g->group[ idx ]; + if ( result->type == RESPONSE_RESPONSE ) + { + // Recurse + bret = ResolveResponse( searchResult, depth + 1, result->value, verbose, pFilter ); + } + else + { + searchResult.action = result; + searchResult.group = g; + } + + if( verbose ) + { + DebugPrint( depth, "}\n" ); + } + + return bret; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *group - +// selected - +// depth - +//----------------------------------------------------------------------------- +void CResponseSystem::DescribeResponseGroup( ResponseGroup *group, int selected, int depth ) +{ + int c = group->group.Count(); + + for ( int i = 0; i < c ; i++ ) + { + ParserResponse *r = &group->group[ i ]; + DebugPrint( depth + 1, "%s%20s : %40s %5.3f\n", + i == selected ? "-> " : " ", + CRR_Response::DescribeResponse( r->GetType() ), + r->value, + r->weight.GetFloat() ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *rule - +// Output : CResponseSystem::Response +//----------------------------------------------------------------------------- +bool CResponseSystem::GetBestResponse( ResponseSearchResult& searchResult, Rule *rule, bool verbose /*=false*/, IResponseFilter *pFilter ) +{ + int c = rule->m_Responses.Count(); + if ( !c ) + return false; + + int index = IEngineEmulator::Get()->GetRandomStream()->RandomInt( 0, c - 1 ); + int groupIndex = rule->m_Responses[ index ]; + + ResponseGroup *g = &m_Responses[ groupIndex ]; + + // Group has been disabled + if ( !g->IsEnabled() ) + return false; + + int count = g->group.Count(); + if ( !count ) + return false; + + int responseIndex = 0; + + if ( g->IsSequential() ) + { + // See if next index is valid + int initialIndex = g->GetCurrentIndex(); + bool bFoundValid = false; + + do + { + responseIndex = g->GetCurrentIndex(); + g->SetCurrentIndex( responseIndex + 1 ); + if ( responseIndex >= count ) + { + if ( g->IsNoRepeat() ) + { + g->SetEnabled( false ); +#ifdef MAPBASE + DisableEmptyRules(); +#endif + return false; + } + responseIndex = 0; + g->SetCurrentIndex( 0 ); + } + + if ( !pFilter || pFilter->IsValidResponse( g->group[responseIndex].GetType(), g->group[responseIndex].value ) ) + { + bFoundValid = true; + break; + } + + } while ( g->GetCurrentIndex() != initialIndex ); + + if ( !bFoundValid ) + return false; + } + else + { + responseIndex = SelectWeightedResponseFromResponseGroup( g, pFilter ); + if ( responseIndex < 0 ) + return false; + } + + + ParserResponse *r = &g->group[ responseIndex ]; + + int depth = 0; + + if ( verbose ) + { + DebugPrint( depth, "%s\n", m_Responses.GetElementName( groupIndex ) ); + DebugPrint( depth, "{\n" ); + + DescribeResponseGroup( g, responseIndex, depth ); + } + + bool bret = true; + + if ( r->type == RESPONSE_RESPONSE ) + { + bret = ResolveResponse( searchResult, depth + 1, r->value, verbose, pFilter ); + } + else + { + searchResult.action = r; + searchResult.group = g; + } + + if ( verbose ) + { + DebugPrint( depth, "}\n" ); + } + + return bret; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : set - +// verbose - +// Output : int +// Warning: If you change this, be sure to also change +// ResponseSystemImplementationCLI::FindAllRulesMatchingCriteria(). +//----------------------------------------------------------------------------- +ResponseRulePartition::tIndex CResponseSystem::FindBestMatchingRule( const CriteriaSet& set, bool verbose, float &scoreOfBestMatchingRule ) +{ + CUtlVector< ResponseRulePartition::tIndex > bestrules(16,4); + float bestscore = 0.001f; + scoreOfBestMatchingRule = 0; + + CUtlVectorFixed< ResponseRulePartition::tRuleDict *, 2 > buckets( 0, 2 ); + m_RulePartitions.GetDictsForCriteria( &buckets, set ); + for ( int b = 0 ; b < buckets.Count() ; ++b ) + { + ResponseRulePartition::tRuleDict *prules = buckets[b]; + int c = prules->Count(); + int i; + for ( i = 0; i < c; i++ ) + { + float score = ScoreCriteriaAgainstRule( set, *prules, i, verbose ); + // Check equals so that we keep track of all matching rules + if ( score >= bestscore ) + { + // Reset bucket + if( score != bestscore ) + { + bestscore = score; + bestrules.RemoveAll(); + } + + // Add to bucket + bestrules.AddToTail( m_RulePartitions.IndexFromDictElem( prules, i ) ); + } + } + } + + int bestCount = bestrules.Count(); + if ( bestCount <= 0 ) + return m_RulePartitions.InvalidIdx(); + + scoreOfBestMatchingRule = bestscore ; + if ( bestCount == 1 ) + { + return bestrules[ 0 ] ; + } + else + { + // Randomly pick one of the tied matching rules + int idx = IEngineEmulator::Get()->GetRandomStream()->RandomInt( 0, bestCount - 1 ); + if ( verbose ) + { + CGMsg( 1, CON_GROUP_RESPONSE_SYSTEM, "Found %i matching rules, selecting slot %i\n", bestCount, idx ); + } + return bestrules[ idx ] ; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : set - +// Output : CRR_Response +//----------------------------------------------------------------------------- +bool CResponseSystem::FindBestResponse( const CriteriaSet& set, CRR_Response& response, IResponseFilter *pFilter ) +{ + bool valid = false; + + int iDbgResponse = rr_debugresponses.GetInt(); + bool showRules = ( iDbgResponse >= 2 && iDbgResponse < RR_DEBUGRESPONSES_SPECIALCASE ); + bool showResult = ( iDbgResponse >= 1 && iDbgResponse < RR_DEBUGRESPONSES_SPECIALCASE ); + + // Look for match. verbose mode used to be at level 2, but disabled because the writers don't actually care for that info. + float scoreOfBestRule; + ResponseRulePartition::tIndex bestRule = FindBestMatchingRule( set, + ( iDbgResponse >= 3 && iDbgResponse < RR_DEBUGRESPONSES_SPECIALCASE ), + scoreOfBestRule ); + + ResponseType_t responseType = RESPONSE_NONE; + ResponseParams rp; + + char ruleName[ 128 ]; + char responseName[ 128 ]; + const char *context; +#ifdef MAPBASE + int contextflags; +#else + bool bcontexttoworld; +#endif + ruleName[ 0 ] = 0; + responseName[ 0 ] = 0; + context = NULL; +#ifdef MAPBASE + contextflags = 0; +#else + bcontexttoworld = false; +#endif + if ( m_RulePartitions.IsValid( bestRule ) ) + { + Rule * RESTRICT r = &m_RulePartitions[ bestRule ]; + + ResponseSearchResult result; + if ( GetBestResponse( result, r, showResult, pFilter ) ) + { + Q_strncpy( responseName, result.action->value, sizeof( responseName ) ); + responseType = result.action->GetType(); + rp = result.action->params; + rp.m_pFollowup = &result.action->m_followup; + } + + Q_strncpy( ruleName, m_RulePartitions.GetElementName( bestRule ), sizeof( ruleName ) ); + + // Disable the rule if it only allows for matching one time + if ( r->IsMatchOnce() ) + { + r->Disable(); + } + context = r->GetContext(); +#ifdef MAPBASE + contextflags = r->GetContextFlags(); + + // Sets the internal indices for the response to call back to later for prospective responses + // (NOTE: Performance not tested; Be wary of turning off the m_bInProspective check!) + if (m_bInProspective) + { + for ( int i = 0; i < (int)m_Responses.Count(); i++ ) + { + if (&m_Responses[i] == result.group) + { + ResponseGroup &group = m_Responses[i]; + for ( int j = 0; j < group.group.Count(); j++) + { + if (&group.group[j] == result.action) + { + response.SetInternalIndices( i, j ); + } + } + } + } + } +#else + bcontexttoworld = r->IsApplyContextToWorld(); +#endif + + response.SetMatchScore(scoreOfBestRule); + valid = true; + } + +#ifdef MAPBASE + response.Init( responseType, responseName, rp, ruleName, context, contextflags ); +#else + response.Init( responseType, responseName, rp, ruleName, context, bcontexttoworld ); +#endif + + if ( showResult ) + { + /* + // clipped -- chet doesn't really want this info + if ( valid ) + { + // Rescore the winner and dump to console + ScoreCriteriaAgainstRule( set, bestRule, true ); + } + */ + + + if ( valid || showRules ) + { + const char *pConceptFilter = rr_debugresponseconcept.GetString(); + // Describe the response, too + if ( V_strlen(pConceptFilter) > 0 && !rr_debugresponseconcept.GetBool() ) + { // filter for only one concept + if ( V_stricmp(pConceptFilter, set.GetValue(set.FindCriterionIndex("concept")) ) == 0 ) + { + response.Describe(&set); + } // else don't print + } + else + { + // maybe we need to filter *out* some concepts + if ( m_DebugExcludeList.IsValidIndex( m_DebugExcludeList.Head() ) ) + { + // we are excluding at least one concept + CRR_Concept test( set.GetValue(set.FindCriterionIndex("concept")) ); + if ( ! m_DebugExcludeList.IsValidIndex( m_DebugExcludeList.Find( test ) ) ) + { // if not found in exclude list, then print + response.Describe(&set); + } + } + else + { + // describe everything + response.Describe(&set); + } + } + } + } + + return valid; +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CResponseSystem::GetAllResponses( CUtlVector *pResponses ) +{ + for ( int i = 0; i < (int)m_Responses.Count(); i++ ) + { + ResponseGroup &group = m_Responses[i]; + + for ( int j = 0; j < group.group.Count(); j++) + { + ParserResponse &response = group.group[j]; + if ( response.type != RESPONSE_RESPONSE ) + { + /* + CRR_Response *pResponse = new CRR_Response; + pResponse->Init( response.GetType(), response.value, CriteriaSet(), response.params, NULL, NULL, false ); + pResponses->AddToTail(pResponse); + */ + pResponses->Element(pResponses->AddToTail()).Init( response.GetType(), response.value, response.params, NULL, NULL, false ); + } + } + } +} + +#ifdef MAPBASE +void CResponseSystem::MarkResponseAsUsed( short iGroup, short iWithinGroup ) +{ + if (m_Responses.Count() > (unsigned int)iGroup) + { + ResponseGroup &group = m_Responses[iGroup]; + if (group.group.Count() > (int)iWithinGroup) + { + group.MarkResponseUsed( iWithinGroup ); + + CGMsg( 1, CON_GROUP_RESPONSE_SYSTEM, "Marked response %s (%i) used\n", group.group[iWithinGroup].value, iWithinGroup ); + } + } +} +#endif + +void CResponseSystem::ParseInclude() +{ + char includefile[ 256 ]; + ParseToken(); + +#ifdef MAPBASE + char scriptfile[256]; + GetCurrentScript( scriptfile, sizeof( scriptfile ) ); + + // Gets first path + // (for example, an #include from a file in resource/script/resp will return resource) + size_t len = strlen(scriptfile)-1; + for (size_t i = 0; i < len; i++) + { + if (scriptfile[i] == CORRECT_PATH_SEPARATOR || scriptfile[i] == INCORRECT_PATH_SEPARATOR) + { + len = i; + } + } + Q_strncpy(includefile, scriptfile, len+1); + + if (len+1 != strlen(scriptfile)) + { + Q_strncat( includefile, "/", sizeof( includefile ) ); + Q_strncat( includefile, token, sizeof( includefile ) ); + } + else + includefile[0] = '\0'; + + if (!includefile[0]) + Q_snprintf( includefile, sizeof( includefile ), "scripts/%s", token ); +#else + Q_snprintf( includefile, sizeof( includefile ), "scripts/%s", token ); +#endif + + // check if the file is already included + if ( m_IncludedFiles.Find( includefile ) != NULL ) + { + return; + } + + MEM_ALLOC_CREDIT(); + + // Try and load it + CUtlBuffer buf; + if ( !IEngineEmulator::Get()->GetFilesystem()->ReadFile( includefile, "GAME", buf ) ) + { + CGMsg( 1, CON_GROUP_RESPONSE_SYSTEM, "Unable to load #included script %s\n", includefile ); + return; + } + + LoadFromBuffer( includefile, (const char *)buf.PeekGet() ); +} + +void CResponseSystem::LoadFromBuffer( const char *scriptfile, const char *buffer ) +{ + COM_TimestampedLog( "CResponseSystem::LoadFromBuffer [%s] - Start", scriptfile ); + m_IncludedFiles.Allocate( scriptfile ); + PushScript( scriptfile, (unsigned char * )buffer ); + + if( rr_dumpresponses.GetBool() ) + { + CGMsg( 1, CON_GROUP_RESPONSE_SYSTEM,"Reading: %s\n", scriptfile ); + } + + while ( 1 ) + { + ParseToken(); + if ( !token[0] ) + { + break; + } + + unsigned int hash = RR_HASH( token ); + bool bSuccess = Dispatch( token, hash, m_FileDispatch ); + if ( !bSuccess ) + { + int byteoffset = m_ScriptStack[ 0 ].currenttoken - (const char *)m_ScriptStack[ 0 ].buffer; + + Error( "CResponseSystem::LoadFromBuffer: Unknown entry type '%s', expecting 'response', 'criterion', 'enumeration' or 'rules' in file %s(offset:%i)\n", + token, scriptfile, byteoffset ); + break; + } + } + + if ( m_ScriptStack.Count() == 1 ) + { + char cur[ 256 ]; + GetCurrentScript( cur, sizeof( cur ) ); + CGMsg( 1, CON_GROUP_RESPONSE_SYSTEM, "CResponseSystem: %s (%i rules, %i criteria, and %i responses)\n", + cur, m_RulePartitions.Count(), m_Criteria.Count(), m_Responses.Count() ); + + if( rr_dumpresponses.GetBool() ) + { + DumpRules(); + } + } + + PopScript(); + COM_TimestampedLog( "CResponseSystem::LoadFromBuffer [%s] - Finish", scriptfile ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CResponseSystem::LoadRuleSet( const char *basescript ) +{ + float flStart = Plat_FloatTime(); + int length = 0; + unsigned char *buffer = (unsigned char *)IEngineEmulator::Get()->LoadFileForMe( basescript, &length ); + if ( length <= 0 || !buffer ) + { + CGMsg( 1, CON_GROUP_RESPONSE_SYSTEM, "CResponseSystem: failed to load %s\n", basescript ); + return; + } + + m_IncludedFiles.FreeAll(); + LoadFromBuffer( basescript, (const char *)buffer ); + + IEngineEmulator::Get()->FreeFile( buffer ); + + Assert( m_ScriptStack.Count() == 0 ); + float flEnd = Plat_FloatTime(); + COM_TimestampedLog( "CResponseSystem::LoadRuleSet took %f msec", 1000.0f * ( flEnd - flStart ) ); +} + +inline ResponseType_t ComputeResponseType( const char *s ) +{ + switch ( s[ 0 ] ) + { + default: + break; + case 's': + switch ( s[ 1 ] ) + { + default: + break; + case 'c': + return RESPONSE_SCENE; + case 'e': + return RESPONSE_SENTENCE; + case 'p': + return RESPONSE_SPEAK; + } + break; + case 'r': + return RESPONSE_RESPONSE; + case 'p': + return RESPONSE_PRINT; + case 'e': + return RESPONSE_ENTITYIO; +#ifdef MAPBASE + case 'v': + if (*(s + 7) == '_') + return RESPONSE_VSCRIPT_FILE; + else + return RESPONSE_VSCRIPT; +#endif + } + + return RESPONSE_NONE; +} + +void CResponseSystem::ParseResponse_Weight( ParserResponse &newResponse, ResponseGroup& group, AI_ResponseParams *rp ) +{ + ParseToken(); + newResponse.weight.SetFloat( (float)atof( token ) ); +} + +void CResponseSystem::ParseResponse_PreDelay( ParserResponse &newResponse, ResponseGroup& group, AI_ResponseParams *rp ) +{ + ParseToken(); + rp->flags |= AI_ResponseParams::RG_DELAYBEFORESPEAK; + rp->predelay.FromInterval( ReadInterval( token ) ); +} + +void CResponseSystem::ParseResponse_NoDelay( ParserResponse &newResponse, ResponseGroup& group, AI_ResponseParams *rp ) +{ + ParseToken(); + rp->flags |= AI_ResponseParams::RG_DELAYAFTERSPEAK; + rp->delay.start = 0; + rp->delay.range = 0; +} + +void CResponseSystem::ParseResponse_DefaultDelay( ParserResponse &newResponse, ResponseGroup& group, AI_ResponseParams *rp ) +{ + rp->flags |= AI_ResponseParams::RG_DELAYAFTERSPEAK; + rp->delay.start = AIS_DEF_MIN_DELAY; + rp->delay.range = ( AIS_DEF_MAX_DELAY - AIS_DEF_MIN_DELAY ); +} + +void CResponseSystem::ParseResponse_Delay( ParserResponse &newResponse, ResponseGroup& group, AI_ResponseParams *rp ) +{ + ParseToken(); + rp->flags |= AI_ResponseParams::RG_DELAYAFTERSPEAK; + rp->delay.FromInterval( ReadInterval( token ) ); +} + +void CResponseSystem::ParseResponse_SpeakOnce( ParserResponse &newResponse, ResponseGroup& group, AI_ResponseParams *rp ) +{ + rp->flags |= AI_ResponseParams::RG_SPEAKONCE; +} + +void CResponseSystem::ParseResponse_NoScene( ParserResponse &newResponse, ResponseGroup& group, AI_ResponseParams *rp ) +{ + rp->flags |= AI_ResponseParams::RG_DONT_USE_SCENE; +} + +void CResponseSystem::ParseResponse_StopOnNonIdle( ParserResponse &newResponse, ResponseGroup& group, AI_ResponseParams *rp ) +{ + rp->flags |= AI_ResponseParams::RG_STOP_ON_NONIDLE; +} + +void CResponseSystem::ParseResponse_Odds( ParserResponse &newResponse, ResponseGroup& group, AI_ResponseParams *rp ) +{ + ParseToken(); + rp->flags |= AI_ResponseParams::RG_ODDS; + rp->odds = clamp( atoi( token ), 0, 100 ); +} + +void CResponseSystem::ParseResponse_RespeakDelay( ParserResponse &newResponse, ResponseGroup& group, AI_ResponseParams *rp ) +{ + ParseToken(); + rp->flags |= AI_ResponseParams::RG_RESPEAKDELAY; + rp->respeakdelay.FromInterval( ReadInterval( token ) ); +} + +void CResponseSystem::ParseResponse_WeaponDelay( ParserResponse &newResponse, ResponseGroup& group, AI_ResponseParams *rp ) +{ + ParseToken(); + rp->flags |= AI_ResponseParams::RG_WEAPONDELAY; + rp->weapondelay.FromInterval( ReadInterval( token ) ); +} + +void CResponseSystem::ParseResponse_Soundlevel( ParserResponse &newResponse, ResponseGroup& group, AI_ResponseParams *rp ) +{ + ParseToken(); + rp->flags |= AI_ResponseParams::RG_SOUNDLEVEL; + rp->soundlevel = (soundlevel_t)TextToSoundLevel( token ); +} + +void CResponseSystem::ParseResponse_DisplayFirst( ParserResponse &newResponse, ResponseGroup& group, AI_ResponseParams *rp ) +{ + newResponse.first = true; + group.m_bHasFirst = true; +} + +void CResponseSystem::ParseResponse_DisplayLast( ParserResponse &newResponse, ResponseGroup& group, AI_ResponseParams *rp ) +{ + newResponse.last = true; + group.m_bHasLast= true; +} + +void CResponseSystem::ParseResponse_Fire( ParserResponse &newResponse, ResponseGroup& group, AI_ResponseParams *rp ) +{ + // get target name + bool bSuc = ParseToken(); + if (!bSuc) + { + ResponseWarning( "FIRE token in response needs exactly three parameters." ); + return; + } + newResponse.m_followup.followup_entityiotarget = ResponseCopyString(token); + + bSuc = ParseToken(); + if (!bSuc) + { + ResponseWarning( "FIRE token in response needs exactly three parameters." ); + return; + } + newResponse.m_followup.followup_entityioinput = ResponseCopyString(token); + + bSuc = ParseToken(); + if (!bSuc) + { + ResponseWarning( "FIRE token in response needs exactly three parameters." ); + return; + } + newResponse.m_followup.followup_entityiodelay = atof( token ); + /* + m_followup.followup_entityioinput = ResponseCopyString(src.m_followup.followup_entityioinput); + m_followup.followup_entityiotarget = ResponseCopyString(src.m_followup.followup_entityiotarget); + */ +} + +void CResponseSystem::ParseResponse_Then( ParserResponse &newResponse, ResponseGroup& group, AI_ResponseParams *rp ) +{ + // eg, "subject TALK_ANSWER saidunplant:1 3" + bool bSuc = ParseToken(); + if (!bSuc) + { + AssertMsg(false, "THEN token in response lacked any further info.\n"); + ResponseWarning( "THEN token in response lacked any further info.\n" ); + return; + } + + newResponse.m_followup.followup_target = ResponseCopyString(token); + + bSuc = ParseToken(); // get another token + if (!bSuc) + { + AssertMsg1(false, "THEN token in response had a target '%s', but lacked any further info.\n", newResponse.m_followup.followup_target ); + ResponseWarning( "THEN token in response had a target '%s', but lacked any further info.\n", newResponse.m_followup.followup_target ); + return; + } + + newResponse.m_followup.followup_concept = ResponseCopyString( token ); + + + // Okay, this is totally asinine. + // Because the ParseToken() function will split foo:bar into three tokens + // (which is reasonable), but we have no safe way to parse the file otherwise + // because it's all behind an engine interface, it's necessary to parse all + // the tokens to the end of the line and catenate them, except for the last one + // which is the delay. That's crap. + bSuc = ParseToken(); + if (!bSuc) + { + AssertMsg(false, "THEN token in response lacked contexts.\n"); + ResponseWarning( "THEN token in response lacked contexts.\n" ); + return; + } + + // okay, as long as there is at least one more token, catenate the ones we + // see onto a temporary buffer. When we're down to the last token, that is + // the delay. + char buf[4096]; + buf[0] = '\0'; + while ( TokenWaiting() ) + { + Q_strncat( buf, token, 4096 ); + bSuc = ParseToken(); + AssertMsg(bSuc, "Token parsing mysteriously failed."); + } + + // down here, token is the last token, and buf is everything up to there. + newResponse.m_followup.followup_contexts = ResponseCopyString( buf ); + + newResponse.m_followup.followup_delay = atof( token ); +} + +void CResponseSystem::ParseOneResponse( const char *responseGroupName, ResponseGroup& group, ResponseParams *defaultParams ) +{ + ParserResponse &newResponse = group.group[ group.group.AddToTail() ]; + newResponse.weight.SetFloat( 1.0f ); + // inherit from group if appropriate + if (defaultParams) + { + newResponse.params = *defaultParams; + } + + ResponseParams *rp = &newResponse.params; + + newResponse.type = ComputeResponseType( token ); + if ( RESPONSE_NONE == newResponse.type ) +{ + ResponseWarning( "response entry '%s' with unknown response type '%s'\n", responseGroupName, token ); + return; +} + +#ifdef MAPBASE + // HACKHACK: Some response system usage in the pre-Alien Swarm system require response names to preserve casing or even have escaped quotes. + ParseTokenIntact(); +#else + ParseToken(); +#endif + newResponse.value = ResponseCopyString( token ); + + while ( TokenWaiting() ) + { + ParseToken(); + + unsigned int hash = RR_HASH( token ); + if ( DispatchParseResponse( token, hash, m_ResponseDispatch, newResponse, group, rp ) ) + { + continue; + } + + ResponseWarning( "response entry '%s' with unknown command '%s'\n", responseGroupName, token ); + } + +} + +void CResponseSystem::ParseResponseGroup_Start( char const *responseGroupName, ResponseGroup &newGroup, AI_ResponseParams &groupResponseParams ) + { + while ( 1 ) + { + ParseToken(); + if ( !Q_stricmp( token, "}" ) ) + break; + + if ( !Q_stricmp( token, "permitrepeats" ) ) + { + newGroup.m_bDepleteBeforeRepeat = false; + continue; + } + else if ( !Q_stricmp( token, "sequential" ) ) + { + newGroup.SetSequential( true ); + continue; + } + else if ( !Q_stricmp( token, "norepeat" ) ) + { + newGroup.SetNoRepeat( true ); + continue; + } + + ParseOneResponse( responseGroupName, newGroup, &groupResponseParams ); + } + } + +void CResponseSystem::ParseResponseGroup_PreDelay( char const *responseGroupName, ResponseGroup &newGroup, AI_ResponseParams &groupResponseParams ) + { + ParseToken(); + groupResponseParams.flags |= AI_ResponseParams::RG_DELAYBEFORESPEAK; + groupResponseParams.predelay.FromInterval( ReadInterval( token ) ); + } + +void CResponseSystem::ParseResponseGroup_NoDelay( char const *responseGroupName, ResponseGroup &newGroup, AI_ResponseParams &groupResponseParams ) + { + ParseToken(); + groupResponseParams.flags |= AI_ResponseParams::RG_DELAYAFTERSPEAK; + groupResponseParams.delay.start = 0; + groupResponseParams.delay.range = 0; + } + +void CResponseSystem::ParseResponseGroup_DefaultDelay( char const *responseGroupName, ResponseGroup &newGroup, AI_ResponseParams &groupResponseParams ) + { + groupResponseParams.flags |= AI_ResponseParams::RG_DELAYAFTERSPEAK; + groupResponseParams.delay.start = AIS_DEF_MIN_DELAY; + groupResponseParams.delay.range = ( AIS_DEF_MAX_DELAY - AIS_DEF_MIN_DELAY ); + } + +void CResponseSystem::ParseResponseGroup_Delay( char const *responseGroupName, ResponseGroup &newGroup, AI_ResponseParams &groupResponseParams ) + { + ParseToken(); + groupResponseParams.flags |= AI_ResponseParams::RG_DELAYAFTERSPEAK; + groupResponseParams.delay.FromInterval( ReadInterval( token ) ); + } + +void CResponseSystem::ParseResponseGroup_SpeakOnce( char const *responseGroupName, ResponseGroup &newGroup, AI_ResponseParams &groupResponseParams ) + { + groupResponseParams.flags |= AI_ResponseParams::RG_SPEAKONCE; + } + +void CResponseSystem::ParseResponseGroup_NoScene( char const *responseGroupName, ResponseGroup &newGroup, AI_ResponseParams &groupResponseParams ) + { + groupResponseParams.flags |= AI_ResponseParams::RG_DONT_USE_SCENE; + } + +void CResponseSystem::ParseResponseGroup_StopOnNonIdle( char const *responseGroupName, ResponseGroup &newGroup, AI_ResponseParams &groupResponseParams ) + { + groupResponseParams.flags |= AI_ResponseParams::RG_STOP_ON_NONIDLE; + } + +void CResponseSystem::ParseResponseGroup_Odds( char const *responseGroupName, ResponseGroup &newGroup, AI_ResponseParams &groupResponseParams ) + { + ParseToken(); + groupResponseParams.flags |= AI_ResponseParams::RG_ODDS; + groupResponseParams.odds = clamp( atoi( token ), 0, 100 ); + } + +void CResponseSystem::ParseResponseGroup_RespeakDelay( char const *responseGroupName, ResponseGroup &newGroup, AI_ResponseParams &groupResponseParams ) + { + ParseToken(); + groupResponseParams.flags |= AI_ResponseParams::RG_RESPEAKDELAY; + groupResponseParams.respeakdelay.FromInterval( ReadInterval( token ) ); + } + +void CResponseSystem::ParseResponseGroup_WeaponDelay( char const *responseGroupName, ResponseGroup &newGroup, AI_ResponseParams &groupResponseParams ) + { + ParseToken(); + groupResponseParams.flags |= AI_ResponseParams::RG_WEAPONDELAY; + groupResponseParams.weapondelay.FromInterval( ReadInterval( token ) ); + } + +void CResponseSystem::ParseResponseGroup_Soundlevel( char const *responseGroupName, ResponseGroup &newGroup, AI_ResponseParams &groupResponseParams ) + { + ParseToken(); + groupResponseParams.flags |= AI_ResponseParams::RG_SOUNDLEVEL; + groupResponseParams.soundlevel = (soundlevel_t)TextToSoundLevel( token ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CResponseSystem::ParseResponse( void ) +{ + AI_ResponseParams groupResponseParams; // default response parameters inherited from single line format for group + + // Should have groupname at start + ParseToken(); + char responseGroupName[ 128 ]; + Q_strncpy( responseGroupName, token, sizeof( responseGroupName ) ); + + int slot = m_Responses.Insert( responseGroupName ); + ResponseGroup &newGroup = m_Responses[ slot ]; + + while ( 1 ) + { +#ifdef MAPBASE + if ( !ParseToken() || !Q_stricmp( token, "}" ) ) + { + break; + } +#else + ParseToken(); +#endif + + unsigned int hash = RR_HASH( token ); + + // Oops, part of next definition + if( IsRootCommand( hash ) ) + { + Unget(); + break; + } + + if ( DispatchParseResponseGroup( token, hash, m_ResponseGroupDispatch, responseGroupName, newGroup, groupResponseParams ) ) + { + continue; + } + + ParseOneResponse( responseGroupName, newGroup, &groupResponseParams ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *criterion - +//----------------------------------------------------------------------------- +int CResponseSystem::ParseOneCriterion( const char *criterionName ) +{ + char key[ 128 ]; + char value[ 128 ]; + + Criteria *pNewCriterion = NULL; + + int idx; +#ifdef MAPBASE + short existing = m_Criteria.Find( criterionName ); + if ( existing != m_Criteria.InvalidIndex() ) + { + //ResponseWarning( "Additional definition for criteria '%s', overwriting\n", criterionName ); + m_Criteria[existing] = Criteria(); + m_Criteria.SetElementName(existing, criterionName); + idx = existing; + pNewCriterion = &m_Criteria[ idx ]; + } +#else + if ( m_Criteria.Find( criterionName ) != m_Criteria.InvalidIndex() ) + { + static Criteria dummy; + pNewCriterion = &dummy; + + ResponseWarning( "Multiple definitions for criteria '%s' [%d]\n", criterionName, RR_HASH( criterionName ) ); + idx = m_Criteria.InvalidIndex(); + } +#endif + else + { + idx = m_Criteria.Insert( criterionName ); + pNewCriterion = &m_Criteria[ idx ]; + } + + bool gotbody = false; + + while ( TokenWaiting() || !gotbody ) + { +#ifdef MAPBASE + if ( !ParseToken() ) + { + break; + } +#else + ParseToken(); +#endif + + // Oops, part of next definition + if( IsRootCommand() ) + { + Unget(); + break; + } + + if ( !Q_stricmp( token, "{" ) ) + { + gotbody = true; + + while ( 1 ) + { + ParseToken(); + if ( !Q_stricmp( token, "}" ) ) + break; + + // Look up subcriteria index + int idx = m_Criteria.Find( token ); + if ( idx != m_Criteria.InvalidIndex() ) + { + pNewCriterion->subcriteria.AddToTail( idx ); + } + else + { + ResponseWarning( "Skipping unrecongized subcriterion '%s' in '%s'\n", token, criterionName ); + } + } + continue; + } + else if ( !Q_stricmp( token, "required" ) ) + { + pNewCriterion->required = true; + } + else if ( !Q_stricmp( token, "weight" ) ) + { + ParseToken(); + pNewCriterion->weight.SetFloat( (float)atof( token ) ); + } + else + { + Assert( pNewCriterion->subcriteria.Count() == 0 ); + + // Assume it's the math info for a non-subcriteria resposne + Q_strncpy( key, token, sizeof( key ) ); + ParseToken(); + Q_strncpy( value, token, sizeof( value ) ); + + V_strlower( key ); + pNewCriterion->nameSym = CriteriaSet::ComputeCriteriaSymbol( key ); + pNewCriterion->value = ResponseCopyString( value ); + + gotbody = true; + } + } + + if ( !pNewCriterion->IsSubCriteriaType() ) + { + ComputeMatcher( pNewCriterion, pNewCriterion->matcher ); + } + + return idx; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *kv - +//----------------------------------------------------------------------------- +void CResponseSystem::ParseCriterion( void ) +{ + // Should have groupname at start + char criterionName[ 128 ]; + ParseToken(); + Q_strncpy( criterionName, token, sizeof( criterionName ) ); + + ParseOneCriterion( criterionName ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *kv - +//----------------------------------------------------------------------------- +void CResponseSystem::ParseEnumeration( void ) +{ + char enumerationName[ 128 ]; + ParseToken(); + Q_strncpy( enumerationName, token, sizeof( enumerationName ) ); + + ParseToken(); + if ( Q_stricmp( token, "{" ) ) + { + ResponseWarning( "Expecting '{' in enumeration '%s', got '%s'\n", enumerationName, token ); + return; + } + + while ( 1 ) + { + ParseToken(); + if ( !Q_stricmp( token, "}" ) ) + break; + + if ( Q_strlen( token ) <= 0 ) + { + ResponseWarning( "Expecting more tokens in enumeration '%s'\n", enumerationName ); + break; + } + + char key[ 128 ]; + + Q_strncpy( key, token, sizeof( key ) ); + ParseToken(); + float value = (float)atof( token ); + + char sz[ 128 ]; + Q_snprintf( sz, sizeof( sz ), "[%s::%s]", enumerationName, key ); + Q_strlower( sz ); + + Enumeration newEnum; + newEnum.value = value; + + if ( m_Enumerations.Find( sz ) == m_Enumerations.InvalidIndex() ) + { + m_Enumerations.Insert( sz, newEnum ); + } + /* + else + { + ResponseWarning( "Ignoring duplication enumeration '%s'\n", sz ); + } + */ + } +} + +void CResponseSystem::ParseRule_MatchOnce( Rule &newRule ) + { + newRule.m_bMatchOnce = true; + } + +#ifdef MAPBASE +void CResponseSystem::ParseRule_ApplyContextToWorld( Rule &newRule ) + { + newRule.m_iContextFlags |= APPLYCONTEXT_WORLD; + } + +void CResponseSystem::ParseRule_ApplyContextToSquad( Rule &newRule ) + { + newRule.m_iContextFlags |= APPLYCONTEXT_SQUAD; + } + +void CResponseSystem::ParseRule_ApplyContextToEnemy( Rule &newRule ) + { + newRule.m_iContextFlags |= APPLYCONTEXT_ENEMY; + } +#else +void CResponseSystem::ParseRule_ApplyContextToWorld( Rule &newRule ) + { + newRule.m_bApplyContextToWorld = true; + } +#endif + +void CResponseSystem::ParseRule_ApplyContext( Rule &newRule ) + { + ParseToken(); + if ( newRule.GetContext() == NULL ) + { + newRule.SetContext( token ); + } + else + { + CFmtStrN<1024> newContext( "%s,%s", newRule.GetContext(), token ); + newRule.SetContext( newContext ); + } + } + +void CResponseSystem::ParseRule_Response( Rule &newRule ) + { + // Read them until we run out. + while ( TokenWaiting() ) + { + ParseToken(); + int idx = m_Responses.Find( token ); + if ( idx != m_Responses.InvalidIndex() ) + { + MEM_ALLOC_CREDIT(); + newRule.m_Responses.AddToTail( idx ); + } + else + { + m_bParseRuleValid = false; + ResponseWarning( "No such response '%s' for rule '%s'\n", token, m_pParseRuleName ); + } + } +} + +/* +void CResponseSystem::ParseRule_ForceWeight( Rule &newRule ) +{ + ParseToken(); + if ( token[0] == 0 ) + { + // no token followed forceweight? + ResponseWarning( "Forceweight token in rule '%s' did not specify a numerical weight! Ignoring.\n", m_pParseRuleName ); + } + else + { + newRule.m_nForceWeight = atoi(token); + if ( newRule.m_nForceWeight == 0 ) + { + ResponseWarning( "Rule '%s' had forceweight '%s', which doesn't work out to a nonzero number. Ignoring.\n", + m_pParseRuleName, token ); + } + } + } +*/ + +void CResponseSystem::ParseRule_Criteria( Rule &newRule ) + { + // Read them until we run out. + while ( TokenWaiting() ) + { + ParseToken(); + + int idx = m_Criteria.Find( token ); + if ( idx != m_Criteria.InvalidIndex() ) + { + MEM_ALLOC_CREDIT(); + newRule.m_Criteria.AddToTail( idx ); + } + else + { + m_bParseRuleValid = false; + ResponseWarning( "No such criterion '%s' for rule '%s'\n", token, m_pParseRuleName ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *kv - +//----------------------------------------------------------------------------- +void CResponseSystem::ParseRule( void ) +{ + static int instancedCriteria = 0; + + char ruleName[ 128 ]; + ParseToken(); + Q_strncpy( ruleName, token, sizeof( ruleName ) ); + + ParseToken(); + if ( Q_stricmp( token, "{" ) ) + { + ResponseWarning( "Expecting '{' in rule '%s', got '%s'\n", ruleName, token ); + return; + } + + // entries are "criteria", "response" or an in-line criteria to instance + Rule *newRule = new Rule; + + char sz[ 128 ]; + + m_bParseRuleValid = true; + m_pParseRuleName = ruleName; + while ( 1 ) + { + ParseToken(); + if ( !Q_stricmp( token, "}" ) ) + { + break; + } + + if ( Q_strlen( token ) <= 0 ) + { + ResponseWarning( "Expecting more tokens in rule '%s'\n", ruleName ); + break; + } + + unsigned int hash = RR_HASH( token ); + if ( DispatchParseRule( token, hash, m_RuleDispatch, *newRule ) ) + continue; + + // It's an inline criteria, generate a name and parse it in + Q_snprintf( sz, sizeof( sz ), "[%s%03i]", ruleName, ++instancedCriteria ); + Unget(); + int idx = ParseOneCriterion( sz ); + if ( idx != m_Criteria.InvalidIndex() ) + { + newRule->m_Criteria.AddToTail( idx ); + } + } + + if ( m_bParseRuleValid ) + { + m_RulePartitions.GetDictForRule( this, newRule ).Insert( ruleName, newRule ); + } + else + { + CGMsg( 1, CON_GROUP_RESPONSE_SYSTEM, "Discarded rule %s\n", ruleName ); + delete newRule; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int CResponseSystem::GetCurrentToken() const +{ + if ( m_ScriptStack.Count() <= 0 ) + return -1; + + return m_ScriptStack[ 0 ].tokencount; +} + + +void CResponseSystem::ResponseWarning( const char *fmt, ... ) +{ + va_list argptr; + char string[1024]; + + va_start (argptr, fmt); + Q_vsnprintf(string, sizeof(string), fmt,argptr); + va_end (argptr); + + char cur[ 256 ]; + GetCurrentScript( cur, sizeof( cur ) ); + CGMsg( 1, CON_GROUP_RESPONSE_SYSTEM, "%s(token %i) : %s", cur, GetCurrentToken(), string ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CResponseSystem::CopyCriteriaFrom( Rule *pSrcRule, Rule *pDstRule, CResponseSystem *pCustomSystem ) +{ + // Add criteria from this rule to global list in custom response system. + int nCriteriaCount = pSrcRule->m_Criteria.Count(); + for ( int iCriteria = 0; iCriteria < nCriteriaCount; ++iCriteria ) + { + int iSrcIndex = pSrcRule->m_Criteria[iCriteria]; + Criteria *pSrcCriteria = &m_Criteria[iSrcIndex]; + if ( pSrcCriteria ) + { + int iIndex = pCustomSystem->m_Criteria.Find( m_Criteria.GetElementName( iSrcIndex ) ); + if ( iIndex != pCustomSystem->m_Criteria.InvalidIndex() ) + { + pDstRule->m_Criteria.AddToTail( iIndex ); + continue; + } + + // Add the criteria. + Criteria dstCriteria; + + dstCriteria.nameSym = pSrcCriteria->nameSym ; + dstCriteria.value = ResponseCopyString( pSrcCriteria->value ); + dstCriteria.weight = pSrcCriteria->weight; + dstCriteria.required = pSrcCriteria->required; + dstCriteria.matcher = pSrcCriteria->matcher; + + int nSubCriteriaCount = pSrcCriteria->subcriteria.Count(); + for ( int iSubCriteria = 0; iSubCriteria < nSubCriteriaCount; ++iSubCriteria ) + { + int iSrcSubIndex = pSrcCriteria->subcriteria[iSubCriteria]; + Criteria *pSrcSubCriteria = &m_Criteria[iSrcSubIndex]; + if ( pSrcCriteria ) + { + int iSubIndex = pCustomSystem->m_Criteria.Find( pSrcSubCriteria->value ); + if ( iSubIndex != pCustomSystem->m_Criteria.InvalidIndex() ) + continue; + + // Add the criteria. + Criteria dstSubCriteria; + + dstSubCriteria.nameSym = pSrcSubCriteria->nameSym ; + dstSubCriteria.value = ResponseCopyString( pSrcSubCriteria->value ); + dstSubCriteria.weight = pSrcSubCriteria->weight; + dstSubCriteria.required = pSrcSubCriteria->required; + dstSubCriteria.matcher = pSrcSubCriteria->matcher; + + int iSubInsertIndex = pCustomSystem->m_Criteria.Insert( pSrcSubCriteria->value, dstSubCriteria ); + dstCriteria.subcriteria.AddToTail( iSubInsertIndex ); + } + } + + int iInsertIndex = pCustomSystem->m_Criteria.Insert( m_Criteria.GetElementName( iSrcIndex ), dstCriteria ); + pDstRule->m_Criteria.AddToTail( iInsertIndex ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CResponseSystem::CopyResponsesFrom( Rule *pSrcRule, Rule *pDstRule, CResponseSystem *pCustomSystem ) +{ + // Add responses from this rule to global list in custom response system. + int nResponseGroupCount = pSrcRule->m_Responses.Count(); + for ( int iResponseGroup = 0; iResponseGroup < nResponseGroupCount; ++iResponseGroup ) + { + int iSrcResponseGroup = pSrcRule->m_Responses[iResponseGroup]; + ResponseGroup *pSrcResponseGroup = &m_Responses[iSrcResponseGroup]; + if ( pSrcResponseGroup ) + { + // Add response group. + ResponseGroup dstResponseGroup; + + dstResponseGroup.m_bDepleteBeforeRepeat = pSrcResponseGroup->m_bDepleteBeforeRepeat; + dstResponseGroup.m_nDepletionCount = pSrcResponseGroup->m_nDepletionCount; + dstResponseGroup.m_bHasFirst = pSrcResponseGroup->m_bHasFirst; + dstResponseGroup.m_bHasLast = pSrcResponseGroup->m_bHasLast; + dstResponseGroup.m_bSequential = pSrcResponseGroup->m_bSequential; + dstResponseGroup.m_bNoRepeat = pSrcResponseGroup->m_bNoRepeat; + dstResponseGroup.m_bEnabled = pSrcResponseGroup->m_bEnabled; + dstResponseGroup.m_nCurrentIndex = pSrcResponseGroup->m_nCurrentIndex; + + int nSrcResponseCount = pSrcResponseGroup->group.Count(); + for ( int iResponse = 0; iResponse < nSrcResponseCount; ++iResponse ) + { + ParserResponse *pSrcResponse = &pSrcResponseGroup->group[iResponse]; + if ( pSrcResponse ) + { + // Add Response + ParserResponse dstResponse; + + dstResponse.weight = pSrcResponse->weight; + dstResponse.type = pSrcResponse->type; + dstResponse.value = ResponseCopyString( pSrcResponse->value ); + dstResponse.depletioncount = pSrcResponse->depletioncount; + dstResponse.first = pSrcResponse->first; + dstResponse.last = pSrcResponse->last; + + dstResponseGroup.group.AddToTail( dstResponse ); + } + } + + int iInsertIndex = pCustomSystem->m_Responses.Insert( m_Responses.GetElementName( iSrcResponseGroup ), dstResponseGroup ); + pDstRule->m_Responses.AddToTail( iInsertIndex ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CResponseSystem::CopyEnumerationsFrom( CResponseSystem *pCustomSystem ) +{ + int nEnumerationCount = m_Enumerations.Count(); + for ( int iEnumeration = 0; iEnumeration < nEnumerationCount; ++iEnumeration ) + { + Enumeration *pSrcEnumeration = &m_Enumerations[iEnumeration]; + if ( pSrcEnumeration ) + { + Enumeration dstEnumeration; + dstEnumeration.value = pSrcEnumeration->value; + pCustomSystem->m_Enumerations.Insert( m_Enumerations.GetElementName( iEnumeration ), dstEnumeration ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CResponseSystem::CopyRuleFrom( Rule *pSrcRule, ResponseRulePartition::tIndex iRule, CResponseSystem *pCustomSystem ) +{ + // Verify data. + Assert( pSrcRule ); + Assert( pCustomSystem ); + if ( !pSrcRule || !pCustomSystem ) + return; + + // New rule + Rule *dstRule = new Rule; + + dstRule->SetContext( pSrcRule->GetContext() ); + dstRule->m_bMatchOnce = pSrcRule->m_bMatchOnce; + dstRule->m_bEnabled = pSrcRule->m_bEnabled; +#ifdef MAPBASE + dstRule->m_iContextFlags = pSrcRule->m_iContextFlags; +#else + dstRule->m_bApplyContextToWorld = pSrcRule->m_bApplyContextToWorld; +#endif + + // Copy off criteria. + CopyCriteriaFrom( pSrcRule, dstRule, pCustomSystem ); + + // Copy off responses. + CopyResponsesFrom( pSrcRule, dstRule, pCustomSystem ); + + // Copy off enumerations - Don't think we use these. + // CopyEnumerationsFrom( pCustomSystem ); + + // Add rule. + pCustomSystem->m_RulePartitions.GetDictForRule( this, dstRule ).Insert( m_RulePartitions.GetElementName( iRule ), dstRule ); +} + + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CResponseSystem::DumpRules() +{ + for ( ResponseRulePartition::tIndex idx = m_RulePartitions.First() ; + m_RulePartitions.IsValid(idx) ; + idx = m_RulePartitions.Next(idx) ) + { + CGMsg( 0, CON_GROUP_RESPONSE_SYSTEM, "%s\n", m_RulePartitions.GetElementName( idx ) ); + } +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CResponseSystem::DumpDictionary( const char *pszName ) +{ + CGMsg( 0, CON_GROUP_RESPONSE_SYSTEM, "\nDictionary: %s\n", pszName ); + + // int nRuleCount = m_Rules.Count(); + // for ( int iRule = 0; iRule < nRuleCount; ++iRule ) + for ( ResponseRulePartition::tIndex idx = m_RulePartitions.First() ; + m_RulePartitions.IsValid(idx) ; + idx = m_RulePartitions.Next(idx) ) + { + CGMsg( 0, CON_GROUP_RESPONSE_SYSTEM, " Rule %d/%d: %s\n", m_RulePartitions.BucketFromIdx( idx ), m_RulePartitions.PartFromIdx( idx ), m_RulePartitions.GetElementName( idx ) ); + + Rule *pRule = &m_RulePartitions[idx]; + + int nCriteriaCount = pRule->m_Criteria.Count(); + for( int iCriteria = 0; iCriteria < nCriteriaCount; ++iCriteria ) + { + int iRuleCriteria = pRule->m_Criteria[iCriteria]; + Criteria *pCriteria = &m_Criteria[iRuleCriteria]; + CGMsg( 0, CON_GROUP_RESPONSE_SYSTEM, " Criteria %d: %s %s\n", iCriteria, CriteriaSet::SymbolToStr( pCriteria->nameSym ), pCriteria->value ); + } + + int nResponseGroupCount = pRule->m_Responses.Count(); + for ( int iResponseGroup = 0; iResponseGroup < nResponseGroupCount; ++iResponseGroup ) + { + int iRuleResponse = pRule->m_Responses[iResponseGroup]; + ResponseGroup *pResponseGroup = &m_Responses[iRuleResponse]; + + CGMsg( 0, CON_GROUP_RESPONSE_SYSTEM, " ResponseGroup %d: %s\n", iResponseGroup, m_Responses.GetElementName( iRuleResponse ) ); + + int nResponseCount = pResponseGroup->group.Count(); + for ( int iResponse = 0; iResponse < nResponseCount; ++iResponse ) + { + ParserResponse *pResponse = &pResponseGroup->group[iResponse]; + CGMsg( 0, CON_GROUP_RESPONSE_SYSTEM, " Response %d: %s\n", iResponse, pResponse->value ); + } + } + } +} + +void CResponseSystem::BuildDispatchTables() +{ + m_RootCommandHashes.Insert( RR_HASH( "#include" ) ); + m_RootCommandHashes.Insert( RR_HASH( "response" ) ); + m_RootCommandHashes.Insert( RR_HASH( "enumeration" ) ); + m_RootCommandHashes.Insert( RR_HASH( "criterion" ) ); + m_RootCommandHashes.Insert( RR_HASH( "criteria" ) ); + m_RootCommandHashes.Insert( RR_HASH( "rule" ) ); + + m_FileDispatch.Insert( RR_HASH( "#include" ), &CResponseSystem::ParseInclude ); + m_FileDispatch.Insert( RR_HASH( "response" ), &CResponseSystem::ParseResponse ); + m_FileDispatch.Insert( RR_HASH( "criterion" ), &CResponseSystem::ParseCriterion ); + m_FileDispatch.Insert( RR_HASH( "criteria" ), &CResponseSystem::ParseCriterion ); + m_FileDispatch.Insert( RR_HASH( "rule" ), &CResponseSystem::ParseRule ); + m_FileDispatch.Insert( RR_HASH( "enumeration" ), &CResponseSystem::ParseEnumeration ); + + m_RuleDispatch.Insert( RR_HASH( "matchonce" ), &CResponseSystem::ParseRule_MatchOnce ); + m_RuleDispatch.Insert( RR_HASH( "applycontexttoworld" ), &CResponseSystem::ParseRule_ApplyContextToWorld ); +#ifdef MAPBASE + m_RuleDispatch.Insert( RR_HASH( "applycontexttosquad" ), &CResponseSystem::ParseRule_ApplyContextToSquad ); + m_RuleDispatch.Insert( RR_HASH( "applycontexttoenemy" ), &CResponseSystem::ParseRule_ApplyContextToEnemy ); +#endif + m_RuleDispatch.Insert( RR_HASH( "applycontext" ), &CResponseSystem::ParseRule_ApplyContext ); + m_RuleDispatch.Insert( RR_HASH( "response" ), &CResponseSystem::ParseRule_Response ); +// m_RuleDispatch.Insert( RR_HASH( "forceweight" ), &CResponseSystem::ParseRule_ForceWeight ); + m_RuleDispatch.Insert( RR_HASH( "criteria" ), &CResponseSystem::ParseRule_Criteria ); + m_RuleDispatch.Insert( RR_HASH( "criterion" ), &CResponseSystem::ParseRule_Criteria ); + + + m_ResponseDispatch.Insert( RR_HASH( "weight" ), &CResponseSystem::ParseResponse_Weight ); + m_ResponseDispatch.Insert( RR_HASH( "predelay" ), &CResponseSystem::ParseResponse_PreDelay ); + m_ResponseDispatch.Insert( RR_HASH( "nodelay" ), &CResponseSystem::ParseResponse_NoDelay ); + m_ResponseDispatch.Insert( RR_HASH( "defaultdelay" ), &CResponseSystem::ParseResponse_DefaultDelay ); + m_ResponseDispatch.Insert( RR_HASH( "delay" ), &CResponseSystem::ParseResponse_Delay ); + m_ResponseDispatch.Insert( RR_HASH( "speakonce" ), &CResponseSystem::ParseResponse_SpeakOnce ); + m_ResponseDispatch.Insert( RR_HASH( "noscene" ), &CResponseSystem::ParseResponse_NoScene ); + m_ResponseDispatch.Insert( RR_HASH( "stop_on_nonidle" ), &CResponseSystem::ParseResponse_StopOnNonIdle ); + m_ResponseDispatch.Insert( RR_HASH( "odds" ), &CResponseSystem::ParseResponse_Odds ); + m_ResponseDispatch.Insert( RR_HASH( "respeakdelay" ), &CResponseSystem::ParseResponse_RespeakDelay ); + m_ResponseDispatch.Insert( RR_HASH( "weapondelay" ), &CResponseSystem::ParseResponse_WeaponDelay ); + m_ResponseDispatch.Insert( RR_HASH( "soundlevel" ), &CResponseSystem::ParseResponse_Soundlevel ); + m_ResponseDispatch.Insert( RR_HASH( "displayfirst" ), &CResponseSystem::ParseResponse_DisplayFirst ); + m_ResponseDispatch.Insert( RR_HASH( "displaylast" ), &CResponseSystem::ParseResponse_DisplayLast ); + m_ResponseDispatch.Insert( RR_HASH( "fire" ), &CResponseSystem::ParseResponse_Fire ); + m_ResponseDispatch.Insert( RR_HASH( "then" ), &CResponseSystem::ParseResponse_Then ); + + m_ResponseGroupDispatch.Insert( RR_HASH( "{" ), &CResponseSystem::ParseResponseGroup_Start ); + m_ResponseGroupDispatch.Insert( RR_HASH( "predelay" ), &CResponseSystem::ParseResponseGroup_PreDelay ); + m_ResponseGroupDispatch.Insert( RR_HASH( "nodelay" ), &CResponseSystem::ParseResponseGroup_NoDelay ); + m_ResponseGroupDispatch.Insert( RR_HASH( "defaultdelay" ), &CResponseSystem::ParseResponseGroup_DefaultDelay ); + m_ResponseGroupDispatch.Insert( RR_HASH( "delay" ), &CResponseSystem::ParseResponseGroup_Delay ); + m_ResponseGroupDispatch.Insert( RR_HASH( "speakonce" ), &CResponseSystem::ParseResponseGroup_SpeakOnce ); + m_ResponseGroupDispatch.Insert( RR_HASH( "noscene" ), &CResponseSystem::ParseResponseGroup_NoScene ); + m_ResponseGroupDispatch.Insert( RR_HASH( "stop_on_nonidle" ), &CResponseSystem::ParseResponseGroup_StopOnNonIdle ); + m_ResponseGroupDispatch.Insert( RR_HASH( "odds" ), &CResponseSystem::ParseResponseGroup_Odds ); + m_ResponseGroupDispatch.Insert( RR_HASH( "respeakdelay" ), &CResponseSystem::ParseResponseGroup_RespeakDelay ); + m_ResponseGroupDispatch.Insert( RR_HASH( "weapondelay" ), &CResponseSystem::ParseResponseGroup_WeaponDelay ); + m_ResponseGroupDispatch.Insert( RR_HASH( "soundlevel" ), &CResponseSystem::ParseResponseGroup_Soundlevel ); +} + +bool CResponseSystem::Dispatch( char const *pToken, unsigned int uiHash, CResponseSystem::DispatchMap_t &rMap ) +{ + int slot = rMap.Find( uiHash ); + if ( slot != rMap.InvalidIndex() ) + { + CResponseSystem::pfnResponseDispatch dispatch = rMap[ slot ]; + (this->*dispatch)(); + return true; + } + + return false; +} + +bool CResponseSystem::DispatchParseRule( char const *pToken, unsigned int uiHash, ParseRuleDispatchMap_t &rMap, Rule &newRule ) +{ + int slot = rMap.Find( uiHash ); + if ( slot != rMap.InvalidIndex() ) + { + CResponseSystem::pfnParseRuleDispatch dispatch = rMap[ slot ]; + (this->*dispatch)( newRule ); + return true; + } + + return false; +} + +bool CResponseSystem::DispatchParseResponse( char const *pToken, unsigned int uiHash, ParseResponseDispatchMap_t &rMap, ParserResponse &newResponse, ResponseGroup& group, AI_ResponseParams *rp ) +{ + int slot = rMap.Find( uiHash ); + if ( slot != rMap.InvalidIndex() ) + { + CResponseSystem::pfnParseResponseDispatch dispatch = rMap[ slot ]; + (this->*dispatch)( newResponse, group, rp ); + return true; + } + + return false; +} + +bool CResponseSystem::DispatchParseResponseGroup( char const *pToken, unsigned int uiHash, ParseResponseGroupDispatchMap_t &rMap, char const *responseGroupName, ResponseGroup& newGroup, AI_ResponseParams &groupResponseParams ) +{ + int slot = rMap.Find( uiHash ); + if ( slot != rMap.InvalidIndex() ) + { + CResponseSystem::pfnParseResponseGroupDispatch dispatch = rMap[ slot ]; + (this->*dispatch)( responseGroupName, newGroup, groupResponseParams ); + return true; + } + + return false; +} + +unsigned int ResponseRulePartition::GetBucketForSpeakerAndConcept( const char *pszSpeaker, const char *pszConcept, const char *pszSubject ) +{ + // make sure is a power of two + COMPILE_TIME_ASSERT( ( N_RESPONSE_PARTITIONS & ( N_RESPONSE_PARTITIONS - 1 ) ) == 0 ); + + // hash together the speaker and concept strings, and mask off by the bucket mask + unsigned hashSpeaker = 0; // pszSpeaker ? HashStringCaseless( pszSpeaker ) : 0; + unsigned hashConcept = pszConcept ? HashStringCaseless( pszConcept ) : 0; + unsigned hashSubject = pszSubject ? HashStringCaseless( pszSubject ) : 0; + unsigned hashBrowns = ( ( hashSubject >> 3 ) ^ (hashSpeaker >> 1) ^ hashConcept ) & ( N_RESPONSE_PARTITIONS - 1 ); + return hashBrowns; +} + +const char *Rule::GetValueForRuleCriterionByName( CResponseSystem * RESTRICT pSystem, const CUtlSymbol &pCritNameSym ) +{ + const char * retval = NULL; + // for each rule criterion... + for ( int i = 0 ; i < m_Criteria.Count() ; ++i ) + { + retval = RecursiveGetValueForRuleCriterionByName( pSystem, &pSystem->m_Criteria[m_Criteria[i]], pCritNameSym ); + if ( retval != NULL ) + { + // we found a result, early out + break; + } + } + + return retval; +} + +const Criteria *Rule::GetPointerForRuleCriterionByName( CResponseSystem *pSystem, const CUtlSymbol &pCritNameSym ) +{ + const Criteria * retval = NULL; + // for each rule criterion... + for ( int i = 0 ; i < m_Criteria.Count() ; ++i ) + { + retval = RecursiveGetPointerForRuleCriterionByName( pSystem, &pSystem->m_Criteria[m_Criteria[i]], pCritNameSym ); + if ( retval != NULL ) + { + // we found a result, early out + break; + } + } + + return retval; +} + +const char *Rule::RecursiveGetValueForRuleCriterionByName( CResponseSystem * RESTRICT pSystem, + const Criteria * RESTRICT pCrit, const CUtlSymbol &pCritNameSym ) +{ + Assert( pCrit ); + if ( !pCrit ) return NULL; + if ( pCrit->IsSubCriteriaType() ) + { + // test each of the children (depth first) + const char *pRet = NULL; + for ( int i = 0 ; i < pCrit->subcriteria.Count() ; ++i ) + { + pRet = RecursiveGetValueForRuleCriterionByName( pSystem, &pSystem->m_Criteria[pCrit->subcriteria[i]], pCritNameSym ); + if ( pRet ) // if found something, early out + return pRet; + } + } + else // leaf criterion + { + if ( pCrit->nameSym == pCritNameSym ) + { + return pCrit->value; + } + else + { + return NULL; + } + } + + return NULL; +} + + +const Criteria *Rule::RecursiveGetPointerForRuleCriterionByName( CResponseSystem *pSystem, const Criteria *pCrit, const CUtlSymbol &pCritNameSym ) +{ + Assert( pCrit ); + if ( !pCrit ) return NULL; + if ( pCrit->IsSubCriteriaType() ) + { + // test each of the children (depth first) + const Criteria *pRet = NULL; + for ( int i = 0 ; i < pCrit->subcriteria.Count() ; ++i ) + { + pRet = RecursiveGetPointerForRuleCriterionByName( pSystem, &pSystem->m_Criteria[pCrit->subcriteria[i]], pCritNameSym ); + if ( pRet ) // if found something, early out + return pRet; + } + } + else // leaf criterion + { + if ( pCrit->nameSym == pCritNameSym ) + { + return pCrit; + } + else + { + return NULL; + } + } + + return NULL; +} + + +static void CC_RR_Debug_ResponseConcept_Exclude( const CCommand &args ) +{ + // shouldn't use this extern elsewhere -- it's meant to be a hidden + // implementation detail + extern CRR_ConceptSymbolTable *g_pRRConceptTable; + Assert( g_pRRConceptTable ); + if ( !g_pRRConceptTable ) return; + + + // different things for different argument lengths + switch ( args.ArgC() ) + { + case 0: + { + AssertMsg( args.ArgC() > 0, "WTF error in ccommand parsing: zero arguments!\n" ); + return; + } + case 1: + { + // print usage info + CGMsg( 0, CON_GROUP_RESPONSE_SYSTEM, "Usage: rr_debugresponseconcept_exclude Concept1 Concept2 Concept3...\n"); + CGMsg( 0, CON_GROUP_RESPONSE_SYSTEM, "\tseparate multiple concepts with spaces.\n"); + CGMsg( 0, CON_GROUP_RESPONSE_SYSTEM, "\tcall with no arguments to see this message and a list of current excludes.\n"); + CGMsg( 0, CON_GROUP_RESPONSE_SYSTEM, "\tto reset the exclude list, type \"rr_debugresponseconcept_exclude !\"\n"); + + // print current excludes + CGMsg( 0, CON_GROUP_RESPONSE_SYSTEM, "\nCurrent exclude list:\n" ); + if ( !CResponseSystem::m_DebugExcludeList.IsValidIndex( CResponseSystem::m_DebugExcludeList.Head() ) ) + { + CGMsg( 0, CON_GROUP_RESPONSE_SYSTEM, "\t\n" ); + } + else + { + CResponseSystem::ExcludeList_t::IndexLocalType_t i; + for ( i = CResponseSystem::m_DebugExcludeList.Head() ; + CResponseSystem::m_DebugExcludeList.IsValidIndex(i) ; + i = CResponseSystem::m_DebugExcludeList.Next(i) ) + { + CGMsg( 0, CON_GROUP_RESPONSE_SYSTEM, "\t%s\n", CResponseSystem::m_DebugExcludeList[i].GetStringConcept() ); + } + } + return; + } + case 2: + // deal with the erase operator + if ( args[1][0] == '!' ) + { + CResponseSystem::m_DebugExcludeList.Purge(); + CGMsg( 0, CON_GROUP_RESPONSE_SYSTEM, "Exclude list emptied.\n" ); + return; + } + // else, FALL THROUGH: + default: + // add each arg to the exclude list + for ( int i = 1 ; i < args.ArgC() ; ++i ) + { + if ( !g_pRRConceptTable->Find(args[i]).IsValid() ) + { + CGMsg( 0, CON_GROUP_RESPONSE_SYSTEM, "\t'%s' is not a known concept (adding it anyway)\n", args[i] ); + } + CRR_Concept concept( args[i] ); + CResponseSystem::m_DebugExcludeList.AddToTail( concept ); + } + } +} +#if RR_DUMPHASHINFO_ENABLED +void ResponseRulePartition::PrintBucketInfo( CResponseSystem *pSys ) +{ + struct bucktuple_t + { + int nBucket; + int nCount; + bucktuple_t() : nBucket(-1), nCount(-1) {}; + bucktuple_t( int bucket, int count ) : nBucket(bucket), nCount(count) {}; + + static int __cdecl SortCompare( const bucktuple_t * a, const bucktuple_t * b ) + { + return a->nCount - b->nCount; + } + }; + + CUtlVector infos( N_RESPONSE_PARTITIONS, N_RESPONSE_PARTITIONS ); + + float nAverage = 0; + for ( int i = 0 ; i < N_RESPONSE_PARTITIONS ; ++i ) + { + int count = m_RuleParts[i].Count(); + infos.AddToTail( bucktuple_t( i, count ) ); + nAverage += count; + } + nAverage /= N_RESPONSE_PARTITIONS; + infos.Sort( bucktuple_t::SortCompare ); + CGMsg( 0, CON_GROUP_RESPONSE_SYSTEM, "%d buckets, %d total, %.2f average size\n", N_RESPONSE_PARTITIONS, Count(), nAverage ); + CGMsg( 0, CON_GROUP_RESPONSE_SYSTEM, "8 shortest buckets:\n" ); + for ( int i = 0 ; i < 8 ; ++i ) + { + CGMsg( 0, CON_GROUP_RESPONSE_SYSTEM, "\t%d: %d\n", infos[i].nBucket, infos[i].nCount ); + } + CGMsg( 0, CON_GROUP_RESPONSE_SYSTEM, "8 longest buckets:\n" ); + for ( int i = infos.Count() - 1 ; i >= infos.Count() - 9 ; --i ) + { + CGMsg( 0, CON_GROUP_RESPONSE_SYSTEM, "\t%d: %d\n", infos[i].nBucket, infos[i].nCount ); + } + int nempty = 0; + for ( nempty = 0 ; nempty < infos.Count() ; ++nempty ) + { + if ( infos[nempty].nCount != 0 ) + break; + } + CGMsg( 0, CON_GROUP_RESPONSE_SYSTEM, "%d empty buckets\n", nempty ); + + /* + CGMsg( 0, CON_GROUP_RESPONSE_SYSTEM, " Contents of longest bucket\nwho\tconcept\n" ); + tRuleDict &bucket = m_RuleParts[infos[infos.Count()-1].nBucket]; + for ( tRuleDict::IndexType_t i = bucket.FirstInorder(); bucket.IsValidIndex(i); i = bucket.NextInorder(i) ) + { + Rule &rule = bucket.Element(i) ; + CGMsg( 0, CON_GROUP_RESPONSE_SYSTEM, "%s\t%s\n", rule.GetValueForRuleCriterionByName( pSys, "who" ), rule.GetValueForRuleCriterionByName( pSys, CriteriaSet::ComputeCriteriaSymbol("concept") ) ); + } + */ +} +#endif \ No newline at end of file diff --git a/src/responserules/runtime/response_system.h b/src/responserules/runtime/response_system.h new file mode 100644 index 00000000..a862d761 --- /dev/null +++ b/src/responserules/runtime/response_system.h @@ -0,0 +1,336 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: The CResponseSystem class. Don't include this header; include the response_types +// into which it is transcluded. +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef RESPONSE_SYSTEM_H +#define RESPONSE_SYSTEM_H +#ifdef _WIN32 +#pragma once +#endif + +#include "utldict.h" + +namespace ResponseRules +{ + typedef ResponseParams AI_ResponseParams ; + #define AI_CriteriaSet ResponseRules::CriteriaSet + + //----------------------------------------------------------------------------- + // Purpose: The database of all available responses. + // The Rules are partitioned based on a variety of factors (presently, + // speaker and concept) for faster lookup, basically a seperate-chained hash. + //----------------------------------------------------------------------------- + class CResponseSystem : public IResponseSystem + { + public: + CResponseSystem(); + ~CResponseSystem(); + + typedef void (CResponseSystem::*pfnResponseDispatch)( void ); + typedef void (CResponseSystem::*pfnParseRuleDispatch)( Rule & ); + typedef void (CResponseSystem::*pfnParseResponseDispatch)( ParserResponse &, ResponseGroup&, AI_ResponseParams * ); + typedef void (CResponseSystem::*pfnParseResponseGroupDispatch) ( char const *responseGroupName, ResponseGroup &newGroup, AI_ResponseParams &groupResponseParams ); + + typedef CUtlMap< unsigned,pfnResponseDispatch > DispatchMap_t; + typedef CUtlMap< unsigned,pfnParseRuleDispatch > ParseRuleDispatchMap_t; + typedef CUtlMap< unsigned,pfnParseResponseDispatch > ParseResponseDispatchMap_t; + typedef CUtlMap< unsigned,pfnParseResponseGroupDispatch > ParseResponseGroupDispatchMap_t; + +#pragma region IResponseSystem + // IResponseSystem + virtual bool FindBestResponse( const CriteriaSet& set, CRR_Response& response, IResponseFilter *pFilter = NULL ); + virtual void GetAllResponses( CUtlVector *pResponses ); + +#ifdef MAPBASE + virtual void SetProspective( bool bToggle ) { m_bInProspective = bToggle; } + + virtual void MarkResponseAsUsed( short iGroup, short iWithinGroup ); +#endif +#pragma endregion Implement interface from IResponseSystem + + virtual void Release() = 0; + + virtual void DumpRules(); + + bool IsCustomManagable() { return m_bCustomManagable; } + +#ifdef MAPBASE + virtual +#endif + void Clear(); + + void DumpDictionary( const char *pszName ); + + protected: + + void BuildDispatchTables(); + bool Dispatch( char const *pToken, unsigned int uiHash, DispatchMap_t &rMap ); + bool DispatchParseRule( char const *pToken, unsigned int uiHash, ParseRuleDispatchMap_t &rMap, Rule &newRule ); + bool DispatchParseResponse( char const *pToken, unsigned int uiHash, ParseResponseDispatchMap_t &rMap, ParserResponse &newResponse, ResponseGroup& group, AI_ResponseParams *rp ); + bool DispatchParseResponseGroup( char const *pToken, unsigned int uiHash, ParseResponseGroupDispatchMap_t &rMap, char const *responseGroupName, ResponseGroup& newGroup, AI_ResponseParams &groupResponseParams ); + + virtual const char *GetScriptFile( void ) = 0; + void LoadRuleSet( const char *setname ); + + void ResetResponseGroups(); + + float LookForCriteria( const CriteriaSet &criteriaSet, int iCriteria ); + float RecursiveLookForCriteria( const CriteriaSet &criteriaSet, Criteria *pParent ); + + public: + + void CopyRuleFrom( Rule *pSrcRule, ResponseRulePartition::tIndex iRule, CResponseSystem *pCustomSystem ); + void CopyCriteriaFrom( Rule *pSrcRule, Rule *pDstRule, CResponseSystem *pCustomSystem ); + void CopyResponsesFrom( Rule *pSrcRule, Rule *pDstRule, CResponseSystem *pCustomSystem ); + void CopyEnumerationsFrom( CResponseSystem *pCustomSystem ); + + //private: + + struct Enumeration + { + float value; + }; + + struct ResponseSearchResult + { + ResponseSearchResult() + { + group = NULL; + action = NULL; + } + + ResponseGroup *group; + ParserResponse *action; + }; + + inline bool ParseToken( void ) + { + if ( m_bUnget ) + { + m_bUnget = false; + return true; + } + if ( m_ScriptStack.Count() <= 0 ) + { + Assert( 0 ); + return false; + } + + m_ScriptStack[ 0 ].currenttoken = IEngineEmulator::Get()->ParseFile( m_ScriptStack[ 0 ].currenttoken, token, sizeof( token ) ); + m_ScriptStack[ 0 ].tokencount++; + return m_ScriptStack[ 0 ].currenttoken != NULL ? true : false; + } + +#ifdef MAPBASE + inline bool ParseTokenIntact( void ) + { + if ( m_bUnget ) + { + m_bUnget = false; + return true; + } + if ( m_ScriptStack.Count() <= 0 ) + { + Assert( 0 ); + return false; + } + + m_ScriptStack[ 0 ].currenttoken = IEngineEmulator::Get()->ParseFilePreserve( m_ScriptStack[ 0 ].currenttoken, token, sizeof( token ) ); + m_ScriptStack[ 0 ].tokencount++; + return m_ScriptStack[ 0 ].currenttoken != NULL ? true : false; + } +#endif + + inline void Unget() + { + m_bUnget = true; + } + + inline bool TokenWaiting( void ) + { + if ( m_ScriptStack.Count() <= 0 ) + { + Assert( 0 ); + return false; + } + + const char *p = m_ScriptStack[ 0 ].currenttoken; + + if ( !p ) + { + Error( "AI_ResponseSystem: Unxpected TokenWaiting() with NULL buffer in %s", (char * ) m_ScriptStack[ 0 ].name ); + return false; + } + + + while ( *p && *p!='\n') + { + // Special handler for // comment blocks + if ( *p == '/' && *(p+1) == '/' ) + return false; + + if ( !V_isspace( *p ) || isalnum( *p ) ) + return true; + + p++; + } + + return false; + } + + void ParseOneResponse( const char *responseGroupName, ResponseGroup& group, ResponseParams *defaultParams = NULL ); + + void ParseInclude( void ); + void ParseResponse( void ); + void ParseCriterion( void ); + void ParseRule( void ); + void ParseEnumeration( void ); + + private: + void ParseRule_MatchOnce( Rule &newRule ); + void ParseRule_ApplyContextToWorld( Rule &newRule ); +#ifdef MAPBASE + void ParseRule_ApplyContextToSquad( Rule &newRule ); + void ParseRule_ApplyContextToEnemy( Rule &newRule ); +#endif + void ParseRule_ApplyContext( Rule &newRule ); + void ParseRule_Response( Rule &newRule ); + //void ParseRule_ForceWeight( Rule &newRule ); + void ParseRule_Criteria( Rule &newRule ); + char const *m_pParseRuleName; + bool m_bParseRuleValid; + + void ParseResponse_Weight( ParserResponse &newResponse, ResponseGroup& group, AI_ResponseParams *rp ); + void ParseResponse_PreDelay( ParserResponse &newResponse, ResponseGroup& group, AI_ResponseParams *rp ); + void ParseResponse_NoDelay( ParserResponse &newResponse, ResponseGroup& group, AI_ResponseParams *rp ); + void ParseResponse_DefaultDelay( ParserResponse &newResponse, ResponseGroup& group, AI_ResponseParams *rp ); + void ParseResponse_Delay( ParserResponse &newResponse, ResponseGroup& group, AI_ResponseParams *rp ); + void ParseResponse_SpeakOnce( ParserResponse &newResponse, ResponseGroup& group, AI_ResponseParams *rp ); + void ParseResponse_NoScene( ParserResponse &newResponse, ResponseGroup& group, AI_ResponseParams *rp ); + void ParseResponse_StopOnNonIdle( ParserResponse &newResponse, ResponseGroup& group, AI_ResponseParams *rp ); + void ParseResponse_Odds( ParserResponse &newResponse, ResponseGroup& group, AI_ResponseParams *rp ); + void ParseResponse_RespeakDelay( ParserResponse &newResponse, ResponseGroup& group, AI_ResponseParams *rp ); + void ParseResponse_WeaponDelay( ParserResponse &newResponse, ResponseGroup& group, AI_ResponseParams *rp ); + void ParseResponse_Soundlevel( ParserResponse &newResponse, ResponseGroup& group, AI_ResponseParams *rp ); + void ParseResponse_DisplayFirst( ParserResponse &newResponse, ResponseGroup& group, AI_ResponseParams *rp ); + void ParseResponse_DisplayLast( ParserResponse &newResponse, ResponseGroup& group, AI_ResponseParams *rp ); + void ParseResponse_Fire( ParserResponse &newResponse, ResponseGroup& group, AI_ResponseParams *rp ); + void ParseResponse_Then( ParserResponse &newResponse, ResponseGroup& group, AI_ResponseParams *rp ); + + void ParseResponseGroup_Start( char const *responseGroupName, ResponseGroup &newGroup, AI_ResponseParams &groupResponseParams ); + void ParseResponseGroup_PreDelay( char const *responseGroupName, ResponseGroup &newGroup, AI_ResponseParams &groupResponseParams ); + void ParseResponseGroup_NoDelay( char const *responseGroupName, ResponseGroup &newGroup, AI_ResponseParams &groupResponseParams ); + void ParseResponseGroup_DefaultDelay( char const *responseGroupName, ResponseGroup &newGroup, AI_ResponseParams &groupResponseParams ); + void ParseResponseGroup_Delay( char const *responseGroupName, ResponseGroup &newGroup, AI_ResponseParams &groupResponseParams ); + void ParseResponseGroup_SpeakOnce( char const *responseGroupName, ResponseGroup &newGroup, AI_ResponseParams &groupResponseParams ); + void ParseResponseGroup_NoScene( char const *responseGroupName, ResponseGroup &newGroup, AI_ResponseParams &groupResponseParams ); + void ParseResponseGroup_StopOnNonIdle( char const *responseGroupName, ResponseGroup &newGroup, AI_ResponseParams &groupResponseParams ); + void ParseResponseGroup_Odds( char const *responseGroupName, ResponseGroup &newGroup, AI_ResponseParams &groupResponseParams ); + void ParseResponseGroup_RespeakDelay( char const *responseGroupName, ResponseGroup &newGroup, AI_ResponseParams &groupResponseParams ); + void ParseResponseGroup_WeaponDelay( char const *responseGroupName, ResponseGroup &newGroup, AI_ResponseParams &groupResponseParams ); + void ParseResponseGroup_Soundlevel( char const *responseGroupName, ResponseGroup &newGroup, AI_ResponseParams &groupResponseParams ); + +public: + int ParseOneCriterion( const char *criterionName ); + + bool Compare( const char *setValue, Criteria *c, bool verbose = false ); + bool CompareUsingMatcher( const char *setValue, Matcher& m, bool verbose = false ); + void ComputeMatcher( Criteria *c, Matcher& matcher ); + void ResolveToken( Matcher& matcher, char *token, size_t bufsize, char const *rawtoken ); + float LookupEnumeration( const char *name, bool& found ); + + ResponseRulePartition::tIndex FindBestMatchingRule( const CriteriaSet& set, bool verbose, float &scoreOfBestMatchingRule ); + +#ifdef MAPBASE + void DisableEmptyRules(); +#endif + + float ScoreCriteriaAgainstRule( const CriteriaSet& set, ResponseRulePartition::tRuleDict &dict, int irule, bool verbose = false ); + float RecursiveScoreSubcriteriaAgainstRule( const CriteriaSet& set, Criteria *parent, bool& exclude, bool verbose /*=false*/ ); + float ScoreCriteriaAgainstRuleCriteria( const CriteriaSet& set, int icriterion, bool& exclude, bool verbose = false ); + void FakeDepletes( ResponseGroup *g, IResponseFilter *pFilter ); + void RevertFakedDepletes( ResponseGroup *g ); + bool GetBestResponse( ResponseSearchResult& result, Rule *rule, bool verbose = false, IResponseFilter *pFilter = NULL ); + bool ResolveResponse( ResponseSearchResult& result, int depth, const char *name, bool verbose = false, IResponseFilter *pFilter = NULL ); + int SelectWeightedResponseFromResponseGroup( ResponseGroup *g, IResponseFilter *pFilter ); + void DescribeResponseGroup( ResponseGroup *group, int selected, int depth ); + void DebugPrint( int depth, const char *fmt, ... ); + + void LoadFromBuffer( const char *scriptfile, const char *buffer ); + + void GetCurrentScript( char *buf, size_t buflen ); + int GetCurrentToken() const; + void SetCurrentScript( const char *script ); + + inline bool IsRootCommand( unsigned int hash ) const + { + int slot = m_RootCommandHashes.Find( hash ); + return slot != m_RootCommandHashes.InvalidIndex(); + } + + inline bool IsRootCommand() const + { + return IsRootCommand( RR_HASH( token ) ); + } + + void PushScript( const char *scriptfile, unsigned char *buffer ); + void PopScript(void); + + void ResponseWarning( const char *fmt, ... ); + + CUtlDict< ResponseGroup, short > m_Responses; + CUtlDict< Criteria, short > m_Criteria; + // CUtlDict< Rule, short > m_Rules; + ResponseRulePartition m_RulePartitions; + CUtlDict< Enumeration, short > m_Enumerations; + + CUtlVector m_FakedDepletes; + + char token[ 1204 ]; + + bool m_bUnget; + + bool m_bCustomManagable; + +#ifdef MAPBASE + // This is a hack specifically designed to fix displayfirst, speakonce, etc. in "prospective" response searches, + // especially the prospective lookups in followup responses. + // It works by preventing responses from being marked as "used". + bool m_bInProspective; +#endif + + struct ScriptEntry + { + unsigned char *buffer; + FileNameHandle_t name; + const char *currenttoken; + int tokencount; + }; + + CUtlVector< ScriptEntry > m_ScriptStack; + CStringPool m_IncludedFiles; + + DispatchMap_t m_FileDispatch; + ParseRuleDispatchMap_t m_RuleDispatch; + ParseResponseDispatchMap_t m_ResponseDispatch; + ParseResponseGroupDispatchMap_t m_ResponseGroupDispatch; + CUtlRBTree< unsigned int > m_RootCommandHashes; + + // for debugging purposes only: concepts to be emitted from rr_debugresponses 2 + typedef CUtlLinkedList< CRR_Concept, unsigned short, false, unsigned int > ExcludeList_t; + static ExcludeList_t m_DebugExcludeList; + + friend class CDefaultResponseSystemSaveRestoreBlockHandler; + friend class CResponseSystemSaveRestoreOps; + }; + + // Some globals inherited from AI_Speech.h: + const float AIS_DEF_MIN_DELAY = 2.8; // Minimum amount of time an NPCs will wait after someone has spoken before considering speaking again + const float AIS_DEF_MAX_DELAY = 3.2; // Maximum amount of time an NPCs will wait after someone has spoken before considering speaking again +} + +#endif // RESPONSE_SYSTEM_H \ No newline at end of file diff --git a/src/responserules/runtime/response_types.cpp b/src/responserules/runtime/response_types.cpp new file mode 100644 index 00000000..1d8f6b31 --- /dev/null +++ b/src/responserules/runtime/response_types.cpp @@ -0,0 +1,281 @@ +//========= Copyright © 1996-2010, Valve Corporation, All rights reserved. ============// +// +// Purpose: Core types for the response rules -- criteria, responses, rules, and matchers. +// +// $NoKeywords: $ +//=============================================================================// + +#include "rrbase.h" + +#include "tier1/mapbase_con_groups.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +using namespace ResponseRules; + + +// bizarre function handed down from the misty days of yore +// and the original response system. a lot of stuff uses it +// and I can't be arsed to replace everything with the c stdlib +// stuff +namespace ResponseRules +{ + extern const char *ResponseCopyString( const char *in ); +}; + + +//-------------------- MATCHER ---------------------------------------------- + +Matcher::Matcher() +{ + valid = false; + isnumeric = false; + notequal = false; + usemin = false; + minequals = false; + usemax = false; + maxequals = false; +#ifdef MAPBASE + isbit = false; +#endif + maxval = 0.0f; + minval = 0.0f; + + token = UTL_INVAL_SYMBOL; + rawtoken = UTL_INVAL_SYMBOL; +} + +void Matcher::Describe( void ) +{ + if ( !valid ) + { + CGMsg( 1, CON_GROUP_RESPONSE_SYSTEM, " invalid!\n" ); + return; + } + char sz[ 128 ]; + + sz[ 0] = 0; + int minmaxcount = 0; + if ( usemin ) + { + Q_snprintf( sz, sizeof( sz ), ">%s%.3f", minequals ? "=" : "", minval ); + minmaxcount++; + } + if ( usemax ) + { + char sz2[ 128 ]; + Q_snprintf( sz2, sizeof( sz2 ), "<%s%.3f", maxequals ? "=" : "", maxval ); + + if ( minmaxcount > 0 ) + { + Q_strncat( sz, " and ", sizeof( sz ), COPY_ALL_CHARACTERS ); + } + Q_strncat( sz, sz2, sizeof( sz ), COPY_ALL_CHARACTERS ); + minmaxcount++; + } + + if ( minmaxcount >= 1 ) + { + CGMsg( 1, CON_GROUP_RESPONSE_SYSTEM, " matcher: %s\n", sz ); + return; + } + +#ifdef MAPBASE + if ( isbit ) + { + CGMsg( 1, CON_GROUP_RESPONSE_SYSTEM, " matcher: &%s%s\n", notequal ? "~" : "", GetToken() ); + return; + } +#endif + + if ( notequal ) + { + CGMsg( 1, CON_GROUP_RESPONSE_SYSTEM, " matcher: !=%s\n", GetToken() ); + return; + } + + CGMsg( 1, CON_GROUP_RESPONSE_SYSTEM, " matcher: ==%s\n", GetToken() ); +} + +void Matcher::SetToken( char const *s ) +{ + token = g_RS.AddString( s ); +} + +void Matcher::SetRaw( char const *raw ) +{ + rawtoken = g_RS.AddString( raw ); +} + +char const *Matcher::GetToken() +{ + if ( token.IsValid() ) + { + return g_RS.String( token ); + } + return ""; +} + +char const *Matcher::GetRaw() +{ + if ( rawtoken.IsValid() ) + { + return g_RS.String( rawtoken ); + } + return ""; +} + +//-------------------- CRITERIA ---------------------------------------------- + +Criteria::Criteria() +{ + value = NULL; + weight.SetFloat( 1.0f ); + required = false; +} +Criteria::Criteria(const Criteria& src ) +{ + operator=( src ); +} + +Criteria::~Criteria() +{ + // do nothing because we don't own name and value anymore +} + +Criteria& Criteria::operator =(const Criteria& src ) +{ + if ( this == &src ) + return *this; + + nameSym = src.nameSym; + value = ResponseCopyString( src.value ); + weight = src.weight; + required = src.required; + + matcher = src.matcher; + + int c = src.subcriteria.Count(); + subcriteria.EnsureCapacity( c ); + for ( int i = 0; i < c; i++ ) + { + subcriteria.AddToTail( src.subcriteria[ i ] ); + } + + return *this; +} + + +//-------------------- RESPONSE ---------------------------------------------- + + + +ParserResponse::ParserResponse() : m_followup() +{ + type = RESPONSE_NONE; + value = NULL; + weight.SetFloat( 1.0f ); + depletioncount = 0; + first = false; + last = false; +} + +ParserResponse& ParserResponse::operator =( const ParserResponse& src ) +{ + if ( this == &src ) + return *this; + weight = src.weight; + type = src.type; + value = ResponseCopyString( src.value ); + depletioncount = src.depletioncount; + first = src.first; + last = src.last; + params = src.params; + + m_followup.followup_concept = ResponseCopyString(src.m_followup.followup_concept); + m_followup.followup_contexts = ResponseCopyString(src.m_followup.followup_contexts); + m_followup.followup_target = ResponseCopyString(src.m_followup.followup_target); + m_followup.followup_entityioinput = ResponseCopyString(src.m_followup.followup_entityioinput); + m_followup.followup_entityiotarget = ResponseCopyString(src.m_followup.followup_entityiotarget); + m_followup.followup_delay = src.m_followup.followup_delay; + m_followup.followup_entityiodelay = src.m_followup.followup_entityiodelay; + + return *this; +} + +ParserResponse::ParserResponse( const ParserResponse& src ) +{ + operator=( src ); +} + +ParserResponse::~ParserResponse() +{ + // nothing to do, since we don't own + // the strings anymore +} + +// ------------ RULE --------------- + +Rule::Rule() : m_nForceWeight(0) +{ + m_bMatchOnce = false; + m_bEnabled = true; + m_szContext = NULL; +#ifdef MAPBASE + m_iContextFlags = 0; +#else + m_bApplyContextToWorld = false; +#endif +} + +Rule& Rule::operator =( const Rule& src ) +{ + if ( this == &src ) + return *this; + + int i; + int c; + + c = src.m_Criteria.Count(); + m_Criteria.EnsureCapacity( c ); + for ( i = 0; i < c; i++ ) + { + m_Criteria.AddToTail( src.m_Criteria[ i ] ); + } + + c = src.m_Responses.Count(); + m_Responses.EnsureCapacity( c ); + for ( i = 0; i < c; i++ ) + { + m_Responses.AddToTail( src.m_Responses[ i ] ); + } + + SetContext( src.m_szContext ); + m_bMatchOnce = src.m_bMatchOnce; + m_bEnabled = src.m_bEnabled; +#ifdef MAPBASE + m_iContextFlags = src.m_iContextFlags; +#else + m_bApplyContextToWorld = src.m_bApplyContextToWorld; +#endif + m_nForceWeight = src.m_nForceWeight; + return *this; +} + +Rule::Rule( const Rule& src ) +{ + operator=(src); +} + +Rule::~Rule() +{ +} + +void Rule::SetContext( const char *context ) +{ + // we don't own the data we point to, so just update pointer + m_szContext = ResponseCopyString( context ); +} + + diff --git a/src/responserules/runtime/response_types_internal.cpp b/src/responserules/runtime/response_types_internal.cpp new file mode 100644 index 00000000..873f8759 --- /dev/null +++ b/src/responserules/runtime/response_types_internal.cpp @@ -0,0 +1,172 @@ +//========= Copyright © 1996-2010, Valve Corporation, All rights reserved. ============// +// +// Purpose: Core types for the response rules -- criteria, responses, rules, and matchers. +// +// $NoKeywords: $ +//=============================================================================// + +#include "rrbase.h" +#ifdef MAPBASE +#include "convar.h" +#include "mapbase_matchers_base.h" +#endif + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +using namespace ResponseRules; + +#ifdef MAPBASE +ConVar rr_bucket_name_who( "rr_bucket_name_who", "Classname", FCVAR_NONE, "The name of the criteria to use for the 'Who' bucket." ); // Default changed to "Classname" for HL2 +ConVar rr_bucket_name_concept( "rr_bucket_name_concept", "Concept", FCVAR_NONE, "The name of the criteria to use for the 'Concept' bucket." ); +ConVar rr_bucket_name_subject( "rr_bucket_name_subject", "Subject", FCVAR_NONE, "The name of the criteria to use for the 'Subject' bucket." ); +#endif + + + + +ResponseRulePartition::ResponseRulePartition() +{ + Assert(true); + COMPILE_TIME_ASSERT( kIDX_ELEM_MASK < (1 << 16) ); + COMPILE_TIME_ASSERT( (kIDX_ELEM_MASK & (kIDX_ELEM_MASK + 1)) == 0 ); /// assert is power of two minus one +} + +ResponseRulePartition::~ResponseRulePartition() +{ + RemoveAll(); +} + +ResponseRulePartition::tIndex ResponseRulePartition::IndexFromDictElem( tRuleDict* pDict, int elem ) +{ + Assert( pDict ); + // If this fails, you've tried to build an index for a rule that's not stored + // in this partition + Assert( pDict >= m_RuleParts && pDict < m_RuleParts + N_RESPONSE_PARTITIONS ); + AssertMsg1( elem <= kIDX_ELEM_MASK, "A rule dictionary has %d elements; this exceeds the 255 that can be packed into an index.\n", elem ); + + int bucket = pDict - m_RuleParts; + return ( bucket << 16 ) | ( elem & kIDX_ELEM_MASK ); // this is a native op on PPC +} + + +char const *ResponseRulePartition::GetElementName( const tIndex &i ) const +{ + Assert( IsValid(i) ); + return m_RuleParts[ BucketFromIdx(i) ].GetElementName( PartFromIdx(i) ); +} + + +Rule *ResponseRulePartition::FindByName( char const *name ) const +{ + int count; + + for ( int bukkit = 0 ; bukkit < N_RESPONSE_PARTITIONS ; ++bukkit ) + { + count = m_RuleParts[bukkit].Count(); + for ( int i = 0; i < count; ++i ) + { + if (V_strncmp( m_RuleParts[bukkit].GetElementName(i), name, CRR_Response::MAX_RULE_NAME ) == 0) + return m_RuleParts[bukkit][i]; + } + } + + return NULL; +} + + +int ResponseRulePartition::Count( void ) +{ + int count = 0 ; + for ( int bukkit = 0 ; bukkit < N_RESPONSE_PARTITIONS ; ++bukkit ) + { + count += m_RuleParts[bukkit].Count(); + } + + return count; +} + +void ResponseRulePartition::RemoveAll( void ) +{ + for ( int bukkit = 0 ; bukkit < N_RESPONSE_PARTITIONS ; ++bukkit ) + { + for ( int i = m_RuleParts[bukkit].FirstInorder(); i != m_RuleParts[bukkit].InvalidIndex(); i = m_RuleParts[bukkit].NextInorder( i ) ) + { + delete m_RuleParts[bukkit][ i ]; + } + m_RuleParts[bukkit].RemoveAll(); + } +} + +#ifdef MAPBASE +void ResponseRulePartition::PurgeAndDeleteElements() +{ + for ( int bukkit = 0 ; bukkit < N_RESPONSE_PARTITIONS ; ++bukkit ) + { + for ( int i = m_RuleParts[bukkit].FirstInorder(); i != m_RuleParts[bukkit].InvalidIndex(); i = m_RuleParts[bukkit].NextInorder( i ) ) + { + delete m_RuleParts[bukkit][ i ]; + } + m_RuleParts[bukkit].Purge(); + } +} +#endif + +// don't bucket "subject" criteria that prefix with operators, since stripping all that out again would +// be a big pain, and the most important rules that need subjects are tlk_remarks anyway. +static inline bool CanBucketBySubject( const char * RESTRICT pszSubject ) +{ + return pszSubject && + ( ( pszSubject[0] >= 'A' && pszSubject[0] <= 'Z' ) || + ( pszSubject[0] >= 'a' && pszSubject[0] <= 'z' ) ) +#ifdef MAPBASE + && !Matcher_ContainsWildcard( pszSubject ) +#endif + ; +} + +ResponseRulePartition::tRuleDict &ResponseRulePartition::GetDictForRule( CResponseSystem *pSystem, Rule *pRule ) +{ +#ifdef MAPBASE + const static CUtlSymbol kWHO = CriteriaSet::ComputeCriteriaSymbol( rr_bucket_name_who.GetString() ); + const static CUtlSymbol kCONCEPT = CriteriaSet::ComputeCriteriaSymbol( rr_bucket_name_concept.GetString() ); + const static CUtlSymbol kSUBJECT = CriteriaSet::ComputeCriteriaSymbol( rr_bucket_name_subject.GetString() ); +#else + const static CUtlSymbol kWHO = CriteriaSet::ComputeCriteriaSymbol("Who"); + const static CUtlSymbol kCONCEPT = CriteriaSet::ComputeCriteriaSymbol("Concept"); + const static CUtlSymbol kSUBJECT = CriteriaSet::ComputeCriteriaSymbol("Subject"); +#endif + + const char *pszSpeaker = pRule->GetValueForRuleCriterionByName( pSystem, kWHO ); + const char *pszConcept = pRule->GetValueForRuleCriterionByName( pSystem, kCONCEPT ); + const Criteria *pSubjCrit = pRule->GetPointerForRuleCriterionByName( pSystem, kSUBJECT ); + + return m_RuleParts[ + GetBucketForSpeakerAndConcept( pszSpeaker, pszConcept, + ( pSubjCrit && pSubjCrit->required && CanBucketBySubject(pSubjCrit->value) ) ? + pSubjCrit->value : + NULL ) + ]; +} + + +void ResponseRulePartition::GetDictsForCriteria( CUtlVectorFixed< ResponseRulePartition::tRuleDict *, 2 > *pResult, const CriteriaSet &criteria ) +{ + pResult->RemoveAll(); + pResult->EnsureCapacity( 2 ); + + // get the values for Who and Concept, which are what we bucket on + int speakerIdx = criteria.FindCriterionIndex( "Who" ); + const char *pszSpeaker = speakerIdx != -1 ? criteria.GetValue( speakerIdx ) : NULL ; + + int conceptIdx = criteria.FindCriterionIndex( "Concept" ); + const char *pszConcept = conceptIdx != -1 ? criteria.GetValue( conceptIdx ) : NULL ; + + int subjectIdx = criteria.FindCriterionIndex( "Subject" ); + const char *pszSubject = subjectIdx != -1 ? criteria.GetValue( subjectIdx ) : NULL ; + + pResult->AddToTail( &m_RuleParts[ GetBucketForSpeakerAndConcept(pszSpeaker, pszConcept, pszSubject) ] ); + // also try the rules not specifying subject + pResult->AddToTail( &m_RuleParts[ GetBucketForSpeakerAndConcept(pszSpeaker, pszConcept, NULL) ] ); + +} \ No newline at end of file diff --git a/src/responserules/runtime/response_types_internal.h b/src/responserules/runtime/response_types_internal.h new file mode 100644 index 00000000..08f31185 --- /dev/null +++ b/src/responserules/runtime/response_types_internal.h @@ -0,0 +1,560 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: Core types for the response rules -- criteria, responses, rules, and matchers. +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef RESPONSE_TYPES_INTERNAL_H +#define RESPONSE_TYPES_INTERNAL_H +#ifdef _WIN32 +#pragma once +#endif + +#include "responserules/response_types.h" +#include "utldict.h" + + +namespace ResponseRules +{ + + inline unsigned FASTCALL HashStringConventional( const char *pszKey ) + { + unsigned hash = 0xAAAAAAAA; // Alternating 1's and 0's to maximize the effect of the later multiply and add + + for( ; *pszKey ; pszKey++ ) + { + hash = ( ( hash << 5 ) + hash ) + (uint8)(*pszKey); + } + + return hash; + } + + // Note: HashString causes collisions!!! +#define RR_HASH HashStringConventional + +#pragma pack(push,1) + + class Matcher + { + public: + Matcher(); + + void Describe( void ); + + float maxval; + float minval; + + bool valid : 1; //1 + bool isnumeric : 1; //2 + bool notequal : 1; //3 + bool usemin : 1; //4 + bool minequals : 1; //5 + bool usemax : 1; //6 + bool maxequals : 1; //7 +#ifdef MAPBASE + bool isbit : 1; //8 +#endif + + void SetToken( char const *s ); + + char const *GetToken(); + + void SetRaw( char const *raw ); + + char const *GetRaw(); + + private: + CUtlSymbol token; + CUtlSymbol rawtoken; + }; +#pragma pack(pop) + + struct Criteria + { + Criteria(); + Criteria& operator =(const Criteria& src ); + + Criteria(const Criteria& src ); + ~Criteria(); + + // Does this criterion recursively contain more criteria? + inline bool IsSubCriteriaType() const + { + return ( subcriteria.Count() > 0 ) ? true : false; + } + + // const char *name; + CUtlSymbol nameSym; + const char *value; + float16 weight; + bool required; + + Matcher matcher; + + // Indices into sub criteria + CUtlVectorConservative< unsigned short > subcriteria; + }; + +#pragma pack(push,1) + /// This is a response block as read from the file, + /// different from CRR_Response which is what is handed + /// back to queries. + struct ParserResponse + { + DECLARE_SIMPLE_DATADESC_INSIDE_NAMESPACE(); + + ParserResponse(); + ParserResponse( const ParserResponse& src ); + ParserResponse& operator =( const ParserResponse& src ); + ~ParserResponse(); + + ResponseType_t GetType() { return (ResponseType_t)type; } + + ResponseParams params; + + const char *value; // fixed up value spot // 4 + float16 weight; // 6 + + byte depletioncount; // 7 + byte type : 6; // 8 + byte first : 1; // + byte last : 1; // + + ALIGN32 AI_ResponseFollowup m_followup; // info on whether I should force the other guy to say something + }; +#pragma pack(pop) + +#pragma pack(push,1) + struct ResponseGroup + { + DECLARE_SIMPLE_DATADESC_INSIDE_NAMESPACE(); + + ResponseGroup() + { + // By default visit all nodes before repeating + m_bSequential = false; + m_bNoRepeat = false; + m_bEnabled = true; + m_nCurrentIndex = 0; + m_bDepleteBeforeRepeat = true; + m_nDepletionCount = 1; + m_bHasFirst = false; + m_bHasLast = false; + } + + ResponseGroup( const ResponseGroup& src ) + { + int c = src.group.Count(); + for ( int i = 0; i < c; i++ ) + { + group.AddToTail( src.group[ i ] ); + } + + m_bDepleteBeforeRepeat = src.m_bDepleteBeforeRepeat; + m_nDepletionCount = src.m_nDepletionCount; + m_bHasFirst = src.m_bHasFirst; + m_bHasLast = src.m_bHasLast; + m_bSequential = src.m_bSequential; + m_bNoRepeat = src.m_bNoRepeat; + m_bEnabled = src.m_bEnabled; + m_nCurrentIndex = src.m_nCurrentIndex; + } + + ResponseGroup& operator=( const ResponseGroup& src ) + { + if ( this == &src ) + return *this; + int c = src.group.Count(); + for ( int i = 0; i < c; i++ ) + { + group.AddToTail( src.group[ i ] ); + } + + m_bDepleteBeforeRepeat = src.m_bDepleteBeforeRepeat; + m_nDepletionCount = src.m_nDepletionCount; + m_bHasFirst = src.m_bHasFirst; + m_bHasLast = src.m_bHasLast; + m_bSequential = src.m_bSequential; + m_bNoRepeat = src.m_bNoRepeat; + m_bEnabled = src.m_bEnabled; + m_nCurrentIndex = src.m_nCurrentIndex; + return *this; + } + + bool HasUndepletedChoices() const + { + if ( !m_bDepleteBeforeRepeat ) + return true; + + int c = group.Count(); + for ( int i = 0; i < c; i++ ) + { + if ( group[ i ].depletioncount != m_nDepletionCount ) + return true; + } + + return false; + } + + void MarkResponseUsed( int idx ) + { + if ( !m_bDepleteBeforeRepeat ) + return; + + if ( idx < 0 || idx >= group.Count() ) + { + Assert( 0 ); + return; + } + + group[ idx ].depletioncount = m_nDepletionCount; + } + + void ResetDepletionCount() + { + if ( !m_bDepleteBeforeRepeat ) + return; + ++m_nDepletionCount; + } + + void Reset() + { + ResetDepletionCount(); + SetEnabled( true ); + SetCurrentIndex( 0 ); + m_nDepletionCount = 1; + + for ( int i = 0; i < group.Count(); ++i ) + { + group[ i ].depletioncount = 0; + } + } + + bool HasUndepletedFirst( int& index ) + { + index = -1; + + if ( !m_bDepleteBeforeRepeat ) + return false; + + int c = group.Count(); + for ( int i = 0; i < c; i++ ) + { + ParserResponse *r = &group[ i ]; + + if ( ( r->depletioncount != m_nDepletionCount ) && r->first ) + { + index = i; + return true; + } + } + + return false; + } + + bool HasUndepletedLast( int& index ) + { + index = -1; + + if ( !m_bDepleteBeforeRepeat ) + return false; + + int c = group.Count(); + for ( int i = 0; i < c; i++ ) + { + ParserResponse *r = &group[ i ]; + + if ( ( r->depletioncount != m_nDepletionCount ) && r->last ) + { + index = i; + return true; + } + } + + return false; + } + + bool ShouldCheckRepeats() const { return m_bDepleteBeforeRepeat; } + int GetDepletionCount() const { return m_nDepletionCount; } + + bool IsSequential() const { return m_bSequential; } + void SetSequential( bool seq ) { m_bSequential = seq; } + + bool IsNoRepeat() const { return m_bNoRepeat; } + void SetNoRepeat( bool norepeat ) { m_bNoRepeat = norepeat; } + + bool IsEnabled() const { return m_bEnabled; } + void SetEnabled( bool enabled ) { m_bEnabled = enabled; } + + int GetCurrentIndex() const { return m_nCurrentIndex; } + void SetCurrentIndex( byte idx ) { m_nCurrentIndex = idx; } + + CUtlVector< ParserResponse > group; + + bool m_bEnabled; + + byte m_nCurrentIndex; + // Invalidation counter + byte m_nDepletionCount; + + // Use all slots before repeating any + bool m_bDepleteBeforeRepeat : 1; + bool m_bHasFirst : 1; + bool m_bHasLast : 1; + bool m_bSequential : 1; + bool m_bNoRepeat : 1; + }; +#pragma pack(pop) + +#pragma pack(push,1) + struct Rule + { + Rule(); + Rule( const Rule& src ); + ~Rule(); + Rule& operator =( const Rule& src ); + + void SetContext( const char *context ); + + const char *GetContext( void ) const { return m_szContext; } + + inline bool IsEnabled() const { return m_bEnabled; } + inline void Disable() { m_bEnabled = false; } + inline bool IsMatchOnce() const { return m_bMatchOnce; } +#ifdef MAPBASE + inline int GetContextFlags() const { return m_iContextFlags; } + inline bool IsApplyContextToWorld() const { return (m_iContextFlags & APPLYCONTEXT_WORLD) != 0; } +#else + inline bool IsApplyContextToWorld() const { return m_bApplyContextToWorld; } +#endif + + const char *GetValueForRuleCriterionByName( CResponseSystem *pSystem, const CUtlSymbol &pCritNameSym ); + const Criteria *GetPointerForRuleCriterionByName( CResponseSystem *pSystem, const CUtlSymbol &pCritNameSym ); + + // Indices into underlying criteria and response dictionaries + CUtlVectorConservative< unsigned short > m_Criteria; + CUtlVectorConservative< unsigned short> m_Responses; + + const char *m_szContext; + uint8 m_nForceWeight; + +#ifdef MAPBASE + // TODO: Could this cause any issues with the code optimization? + uint8 m_iContextFlags; +#else + bool m_bApplyContextToWorld : 1; +#endif + + bool m_bMatchOnce : 1; + bool m_bEnabled : 1; + + private: + // what is this, lisp? + const char *RecursiveGetValueForRuleCriterionByName( CResponseSystem *pSystem, const Criteria *pCrit, const CUtlSymbol &pCritNameSym ); + const Criteria *RecursiveGetPointerForRuleCriterionByName( CResponseSystem *pSystem, const Criteria *pCrit, const CUtlSymbol &pCritNameSym ); + }; +#pragma pack(pop) + + template + class CResponseDict : public CUtlMap + { + public: + CResponseDict() : CUtlMap( DefLessFunc( unsigned int ) ), m_ReverseMap( DefLessFunc( unsigned int ) ) + { + } + + I Insert( const char *pName, const T &element ) + { + extern const char *ResponseCopyString( const char *in ); + char const *pString = ResponseCopyString( pName ); + unsigned int hash = RR_HASH( pString ); + m_ReverseMap.Insert( hash, pString ); + return CUtlMap::Insert( hash, element ); + } + + I Insert( const char *pName ) + { + extern const char *ResponseCopyString( const char *in ); + char const *pString = ResponseCopyString( pName ); + unsigned int hash = RR_HASH( pString ); + m_ReverseMap.Insert( hash, pString ); + return CUtlMap::Insert( hash ); + } + + I Find( char const *pName ) const + { + unsigned int hash = RR_HASH( pName ); + return CUtlMap::Find( hash ); + } + + const char *GetElementName( I i ) + { + int k = this->Key( i ); + int slot = m_ReverseMap.Find( k ); + if ( slot == m_ReverseMap.InvalidIndex() ) + return ""; + return m_ReverseMap[ slot ]; + } + + const char *GetElementName( I i ) const + { + int k = this->Key( i ); + int slot = m_ReverseMap.Find( k ); + if ( slot == m_ReverseMap.InvalidIndex() ) + return ""; + return m_ReverseMap[ slot ]; + } + + private: + CUtlMap< unsigned int, const char * > m_ReverseMap; + + }; + + // define this to 1 to enable printing some occupancy + // information on the response system via concommmand + // rr_dumphashinfo + #define RR_DUMPHASHINFO_ENABLED 0 + // The Rules are partitioned based on a variety of factors (presently, + // speaker and concept) for faster lookup, basically a seperate-chained hash. + struct ResponseRulePartition + { + ResponseRulePartition( void ); + ~ResponseRulePartition(); + + typedef CResponseDict< Rule * > tRuleDict; + typedef uint32 tIndex; // an integer that can be used to find any rule in the dict + + /// get the appropriate m_rules dict for the provided rule + tRuleDict &GetDictForRule( CResponseSystem *pSystem, Rule *pRule ); + + /// get all bucket full of rules that might possibly match the given criteria. + /// (right now they are bucketed such that all rules that can possibly match a + /// criteria are in one of two dictionaries) + void GetDictsForCriteria( CUtlVectorFixed< ResponseRulePartition::tRuleDict *, 2 > *pResult, const CriteriaSet &criteria ); + + // dump everything. + void RemoveAll(); +#ifdef MAPBASE + void PurgeAndDeleteElements(); +#endif + + inline Rule &operator[]( tIndex idx ); + int Count( void ); // number of elements inside, but you can't iterate from 0 to this + char const *GetElementName( const tIndex &i ) const; + Rule *FindByName( char const *name ) const; + + /// given a dictionary and an element number inside that dict, + /// return a tIndex + tIndex IndexFromDictElem( tRuleDict* pDict, int elem ); + + // for iteration: + inline tIndex First( void ); + inline tIndex Next( const tIndex &idx ); + inline bool IsValid( const tIndex &idx ) const; + inline static tIndex InvalidIdx( void ) + { + return ((tIndex) -1); + } + + // used only for debug prints, do not rely on them otherwise + inline unsigned int BucketFromIdx( const tIndex &idx ) const ; + inline unsigned int PartFromIdx( const tIndex &idx ) const ; + + enum { + N_RESPONSE_PARTITIONS = 256, + kIDX_ELEM_MASK = 0xFFF, ///< this is used to mask the element number part of a ResponseRulePartition::tIndex + }; + +#if RR_DUMPHASHINFO_ENABLED + void PrintBucketInfo( CResponseSystem *pSys ); +#endif + + private: + tRuleDict m_RuleParts[N_RESPONSE_PARTITIONS]; + unsigned int GetBucketForSpeakerAndConcept( const char *pszSpeaker, const char *pszConcept, const char *pszSubject ); + }; + + // // // // // inline functions + + inline ResponseRulePartition::tIndex ResponseRulePartition::First( void ) + { + // find the first bucket that has anything + for ( int bucket = 0 ; bucket < N_RESPONSE_PARTITIONS; bucket++ ) + { + if ( m_RuleParts[bucket].Count() > 0 ) + return bucket << 16; + } + return InvalidIdx(); + } + + inline ResponseRulePartition::tIndex ResponseRulePartition::Next( const tIndex &idx ) + { + int bucket = BucketFromIdx( idx ); + unsigned int elem = PartFromIdx( idx ); + Assert( IsValid(idx) ); + AssertMsg( elem < kIDX_ELEM_MASK, "Too many response rules! Overflow! Doom!" ); + if ( elem + 1 < m_RuleParts[bucket].Count() ) + { + return idx+1; + } + else + { + // walk through the other buckets, skipping empty ones, until we find one with responses and give up. + while ( ++bucket < N_RESPONSE_PARTITIONS ) + { + if ( m_RuleParts[bucket].Count() > 0 ) + { + // 0th element in nth bucket + return bucket << 16; + } + } + + // out of buckets + return InvalidIdx(); + + } + } + + inline Rule &ResponseRulePartition::operator[]( tIndex idx ) + { + Assert( IsValid(idx) ); + return *m_RuleParts[ BucketFromIdx(idx) ][ PartFromIdx(idx) ] ; + } + + inline unsigned int ResponseRulePartition::BucketFromIdx( const tIndex &idx ) const + { + return idx >> 16; + } + + inline unsigned int ResponseRulePartition::PartFromIdx( const tIndex &idx ) const + { + return idx & kIDX_ELEM_MASK; + } + + inline bool ResponseRulePartition::IsValid( const tIndex & idx ) const + { + // make sure that the idx type for the dicts is still short + COMPILE_TIME_ASSERT( sizeof(m_RuleParts[0].FirstInorder()) == 2 ); + + if ( idx == -1 ) + return false; + + int bucket = idx >> 16; + unsigned int elem = idx & kIDX_ELEM_MASK; + + return ( bucket < N_RESPONSE_PARTITIONS && + elem < m_RuleParts[bucket].Count() ); + } + + //----------------------------------------------------------------------------- + // PARSER TYPES -- these are internal to the response system, and represent + // the objects as loaded from disk. + //----------------------------------------------------------------------------- + + +} + +#include "response_system.h" + +#endif \ No newline at end of file diff --git a/src/responserules/runtime/rr_convars.cpp b/src/responserules/runtime/rr_convars.cpp new file mode 100644 index 00000000..986ae0cd --- /dev/null +++ b/src/responserules/runtime/rr_convars.cpp @@ -0,0 +1,14 @@ +//========= Copyright © 1996-2010, Valve Corporation, All rights reserved. ============// +// +// Purpose: Convars used by the response rule system. +// +// $NoKeywords: $ +//=============================================================================// + +#include "rrbase.h" +#include + + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + diff --git a/src/responserules/runtime/rr_response.cpp b/src/responserules/runtime/rr_response.cpp new file mode 100644 index 00000000..b652e8b3 --- /dev/null +++ b/src/responserules/runtime/rr_response.cpp @@ -0,0 +1,387 @@ +//===== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======// +// +// Purpose: +// +// $NoKeywords: $ +// +//===========================================================================// +#include "rrbase.h" + +#include +#include "tier1/mapbase_con_groups.h" + +/* +#include "AI_Criteria.h" +#include "ai_speech.h" +#include +#include "engine/IEngineSound.h" +*/ + +// memdbgon must be the last include file in a .cpp file!!! +#include + +using namespace ResponseRules; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CRR_Response::CRR_Response() : m_fMatchScore(0) +{ + m_Type = ResponseRules::RESPONSE_NONE; + m_szResponseName[0] = 0; + m_szMatchingRule[0]=0; + m_szContext = NULL; +#ifdef MAPBASE + m_iContextFlags = 0; +#else + m_bApplyContextToWorld = false; +#endif +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +CRR_Response::CRR_Response( const CRR_Response &from ) : m_fMatchScore(0) +{ + // Assert( (void*)(&m_Type) == (void*)this ); + Invalidate(); + memcpy( this, &from, sizeof(*this) ); + m_szContext = NULL; + SetContext( from.m_szContext ); +#ifdef MAPBASE + m_iContextFlags = from.m_iContextFlags; +#else + m_bApplyContextToWorld = from.m_bApplyContextToWorld; +#endif +} + + +//----------------------------------------------------------------------------- +CRR_Response &CRR_Response::operator=( const CRR_Response &from ) +{ + // Assert( (void*)(&m_Type) == (void*)this ); + Invalidate(); + memcpy( this, &from, sizeof(*this) ); + m_szContext = NULL; + SetContext( from.m_szContext ); +#ifdef MAPBASE + m_iContextFlags = from.m_iContextFlags; +#else + m_bApplyContextToWorld = from.m_bApplyContextToWorld; +#endif + return *this; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CRR_Response::~CRR_Response() +{ + if (m_szContext) + delete[] m_szContext; +} + +void CRR_Response::Invalidate() +{ + if (m_szContext) + { + delete[] m_szContext; + m_szContext = NULL; + } + m_Type = ResponseRules::RESPONSE_NONE; + m_szResponseName[0] = 0; + // not really necessary: + /* + m_szMatchingRule[0]=0; + m_szContext = NULL; + m_bApplyContextToWorld = false; + */ +} + +// please do not new or delete CRR_Responses. +void CRR_Response::operator delete(void* p) +{ + AssertMsg(false, "DO NOT new or delete CRR_Response s."); + free(p); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *response - +// *criteria - +//----------------------------------------------------------------------------- +void CRR_Response::Init( ResponseType_t type, const char *responseName, const ResponseParams& responseparams, const char *ruleName, const char *applyContext, bool bApplyContextToWorld ) +{ + m_Type = type; + Q_strncpy( m_szResponseName, responseName, sizeof( m_szResponseName ) ); + // Copy underlying criteria + Q_strncpy( m_szMatchingRule, ruleName ? ruleName : "NULL", sizeof( m_szMatchingRule ) ); + m_Params = responseparams; + SetContext( applyContext ); +#ifdef MAPBASE + bApplyContextToWorld ? m_iContextFlags = APPLYCONTEXT_WORLD : m_iContextFlags = 0; +#else + m_bApplyContextToWorld = bApplyContextToWorld; +#endif +} + +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: +// Input : *response - +// *criteria - +//----------------------------------------------------------------------------- +void CRR_Response::Init( ResponseType_t type, const char *responseName, const ResponseParams& responseparams, const char *ruleName, const char *applyContext, int iContextFlags ) +{ + m_Type = type; + Q_strncpy( m_szResponseName, responseName, sizeof( m_szResponseName ) ); + // Copy underlying criteria + Q_strncpy( m_szMatchingRule, ruleName ? ruleName : "NULL", sizeof( m_szMatchingRule ) ); + m_Params = responseparams; + SetContext( applyContext ); + m_iContextFlags = iContextFlags; +} +#endif + +//----------------------------------------------------------------------------- +// Purpose: Debug-print the response. You can optionally pass in the criteria +// used to come up with this response (usually present in the calling function) +// if you want to print that as well. DO NOT store the entire criteria set in +// CRR_Response just to make this debug print cleaner. +//----------------------------------------------------------------------------- +void CRR_Response::Describe( const CriteriaSet *pDebugCriteria ) +{ + if ( pDebugCriteria ) + { + CGMsg( 1, CON_GROUP_RESPONSE_SYSTEM, "Search criteria:\n" ); + pDebugCriteria->Describe(); + } + + if ( m_szMatchingRule[ 0 ] ) + { + CGMsg( 1, CON_GROUP_RESPONSE_SYSTEM, "Matched rule '%s', ", m_szMatchingRule ); + } + if ( m_szContext ) + { +#ifdef MAPBASE + CGMsg( 1, CON_GROUP_RESPONSE_SYSTEM, "Contexts to set '%s' on ", m_szContext ); + if (m_iContextFlags & APPLYCONTEXT_WORLD) + CGMsg( 1, CON_GROUP_RESPONSE_SYSTEM, "world, " ); + else if (m_iContextFlags & APPLYCONTEXT_SQUAD) + CGMsg( 1, CON_GROUP_RESPONSE_SYSTEM, "squad, " ); + else if (m_iContextFlags & APPLYCONTEXT_ENEMY) + CGMsg( 1, CON_GROUP_RESPONSE_SYSTEM, "enemy, " ); + else + CGMsg( 1, CON_GROUP_RESPONSE_SYSTEM, "speaker, " ); +#else + DevMsg( "Contexts to set '%s' on %s, ", m_szContext, m_bApplyContextToWorld ? "world" : "speaker" ); +#endif + } + + CGMsg( 1, CON_GROUP_RESPONSE_SYSTEM, "response %s = '%s'\n", DescribeResponse( (ResponseType_t)m_Type ), m_szResponseName ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : char const +//----------------------------------------------------------------------------- +void CRR_Response::GetName( char *buf, size_t buflen ) const +{ + Q_strncpy( buf, m_szResponseName, buflen ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Output : char const +//----------------------------------------------------------------------------- +void CRR_Response::GetResponse( char *buf, size_t buflen ) const +{ + GetName( buf, buflen ); +} + + +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: +// Output : char const +//----------------------------------------------------------------------------- +void CRR_Response::GetRule( char *buf, size_t buflen ) const +{ + Q_strncpy( buf, m_szMatchingRule, buflen ); +} +#endif + + +const char* ResponseRules::CRR_Response::GetNamePtr() const +{ + return m_szResponseName; +} +const char* ResponseRules::CRR_Response::GetResponsePtr() const +{ + return m_szResponseName; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : type - +// Output : char const +//----------------------------------------------------------------------------- +const char *CRR_Response::DescribeResponse( ResponseType_t type ) +{ + if ( (int)type < 0 || (int)type >= ResponseRules::NUM_RESPONSES ) + { + Assert( 0 ); + return "???CRR_Response bogus index"; + } + + switch( type ) + { + default: + { + Assert( 0 ); + } + // Fall through + case ResponseRules::RESPONSE_NONE: + return "RESPONSE_NONE"; + case ResponseRules::RESPONSE_SPEAK: + return "RESPONSE_SPEAK"; + case ResponseRules::RESPONSE_SENTENCE: + return "RESPONSE_SENTENCE"; + case ResponseRules::RESPONSE_SCENE: + return "RESPONSE_SCENE"; + case ResponseRules::RESPONSE_RESPONSE: + return "RESPONSE_RESPONSE"; + case ResponseRules::RESPONSE_PRINT: + return "RESPONSE_PRINT"; + case ResponseRules::RESPONSE_ENTITYIO: + return "RESPONSE_ENTITYIO"; +#ifdef MAPBASE + case ResponseRules::RESPONSE_VSCRIPT: + return "RESPONSE_VSCRIPT"; + case ResponseRules::RESPONSE_VSCRIPT_FILE: + return "RESPONSE_VSCRIPT_FILE"; +#endif + } + + return "RESPONSE_NONE"; +} + +/* +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CRR_Response::Release() +{ + delete this; +} +*/ + +//----------------------------------------------------------------------------- +// Purpose: +// Output : soundlevel_t +//----------------------------------------------------------------------------- +soundlevel_t CRR_Response::GetSoundLevel() const +{ + if ( m_Params.flags & ResponseParams::RG_SOUNDLEVEL ) + { + return (soundlevel_t)m_Params.soundlevel; + } + + return SNDLVL_TALKING; +} + +float CRR_Response::GetRespeakDelay( void ) const +{ + if ( m_Params.flags & ResponseParams::RG_RESPEAKDELAY ) + { + interval_t temp; + m_Params.respeakdelay.ToInterval( temp ); + return RandomInterval( temp ); + } + + return 0.0f; +} + +float CRR_Response::GetWeaponDelay( void ) const +{ + if ( m_Params.flags & ResponseParams::RG_WEAPONDELAY ) + { + interval_t temp; + m_Params.weapondelay.ToInterval( temp ); + return RandomInterval( temp ); + } + + return 0.0f; +} + +bool CRR_Response::GetSpeakOnce( void ) const +{ + if ( m_Params.flags & ResponseParams::RG_SPEAKONCE ) + { + return true; + } + + return false; +} + +bool CRR_Response::ShouldntUseScene( void ) const +{ + return ( m_Params.flags & ResponseParams::RG_DONT_USE_SCENE ) != 0; +} + +bool CRR_Response::ShouldBreakOnNonIdle( void ) const +{ + return ( m_Params.flags & ResponseParams::RG_STOP_ON_NONIDLE ) != 0; +} + +int CRR_Response::GetOdds( void ) const +{ + if ( m_Params.flags & ResponseParams::RG_ODDS ) + { + return m_Params.odds; + } + return 100; +} + +float CRR_Response::GetDelay() const +{ + if ( m_Params.flags & ResponseParams::RG_DELAYAFTERSPEAK ) + { + interval_t temp; + m_Params.delay.ToInterval( temp ); + return RandomInterval( temp ); + } + return 0.0f; +} + +float CRR_Response::GetPreDelay() const +{ + if ( m_Params.flags & ResponseParams::RG_DELAYBEFORESPEAK ) + { + interval_t temp; + m_Params.predelay.ToInterval( temp ); + return RandomInterval( temp ); + } + return 0.0f; +} + +//----------------------------------------------------------------------------- +// Purpose: Sets context string +// Output : void +//----------------------------------------------------------------------------- +void CRR_Response::SetContext( const char *context ) +{ + if (m_szContext) + { + delete[] m_szContext; + m_szContext = NULL; + } + + if ( context ) + { + int len = Q_strlen( context ); + m_szContext = new char[ len + 1 ]; + Q_memcpy( m_szContext, context, len ); + m_szContext[ len ] = 0; + } +} diff --git a/src/responserules/runtime/rr_speechconcept.cpp b/src/responserules/runtime/rr_speechconcept.cpp new file mode 100644 index 00000000..7e1e04ab --- /dev/null +++ b/src/responserules/runtime/rr_speechconcept.cpp @@ -0,0 +1,73 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "rrbase.h" + + +// memdbgon must be the last include file in a .cpp file!!! +#include + +#if RR_CONCEPTS_ARE_STRINGS +#pragma error("RR_CONCEPTS_ARE_STRINGS no longer supported") +#else + +using namespace ResponseRules; + +// Used to turn ad-hoc concept from strings into numbers. +CRR_ConceptSymbolTable *g_pRRConceptTable = NULL; + +// Q&D hack to defer initialization of concept table until I can figure out where it +// really needs to come from. +static void InitializeRRConceptTable() +{ + if (g_pRRConceptTable == NULL) + { + g_pRRConceptTable = new CRR_ConceptSymbolTable( 64, 64, true ); + } +} + +// construct from string +CRR_Concept::CRR_Concept(const char *fromString) +{ + InitializeRRConceptTable(); + m_iConcept = g_pRRConceptTable->AddString(fromString); +} + +CRR_Concept &CRR_Concept::operator=(const char *fromString) +{ + InitializeRRConceptTable(); + m_iConcept = g_pRRConceptTable->AddString(fromString); + return *this; +} + +bool CRR_Concept::operator==(const char *pszConcept) +{ + int otherConcept = g_pRRConceptTable->Find(pszConcept); + return ( otherConcept != UTL_INVAL_SYMBOL && otherConcept == m_iConcept ); +} + +const char *CRR_Concept::GetStringConcept() const +{ + InitializeRRConceptTable(); + AssertMsg( m_iConcept.IsValid(), "AI Concept has invalid string symbol.\n" ); + const char * retval = g_pRRConceptTable->String(m_iConcept); + AssertMsg( retval, "An RR_Concept couldn't find its string in the symbol table!\n" ); + if (retval == NULL) + { + Warning( "An RR_Concept couldn't find its string in the symbol table!\n" ); + retval = ""; + } + return retval; +} + +const char *CRR_Concept::GetStringForGenericId(tGenericId genericId) +{ + InitializeRRConceptTable(); + return g_pRRConceptTable->String(genericId); +} + +#endif diff --git a/src/responserules/runtime/rrbase.h b/src/responserules/runtime/rrbase.h new file mode 100644 index 00000000..e7af74de --- /dev/null +++ b/src/responserules/runtime/rrbase.h @@ -0,0 +1,59 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef RRBASE_H +#define RRBASE_H +#ifdef _WIN32 +#pragma once +#endif + +#ifdef _WIN32 +// Silence certain warnings +// #pragma warning(disable : 4244) // int or float down-conversion +// #pragma warning(disable : 4305) // int or float data truncation +// #pragma warning(disable : 4201) // nameless struct/union +// #pragma warning(disable : 4511) // copy constructor could not be generated +// #pragma warning(disable : 4675) // resolved overload was found by argument dependent lookup +#endif + +#ifdef _DEBUG +#define DEBUG 1 +#endif + +// Misc C-runtime library headers +#include +#include +#include + +// tier 0 +#include "tier0/dbg.h" +#include "tier0/platform.h" +#include "basetypes.h" + +// tier 1 +#include "tier1/strtools.h" +#include "utlvector.h" +#include "utlsymbol.h" + +// tier 2 +#include "string_t.h" + +// Shared engine/DLL constants +#include "const.h" +#include "edict.h" + +// app +#if defined(_X360) +#define DISABLE_DEBUG_HISTORY 1 +#endif + +#include "responserules/response_types.h" +#include "response_types_internal.h" +#include "responserules/response_host_interface.h" + + +#endif // CBASE_H diff --git a/src/responserules/runtime/rrrlib.cpp b/src/responserules/runtime/rrrlib.cpp new file mode 100644 index 00000000..0d2c49fd --- /dev/null +++ b/src/responserules/runtime/rrrlib.cpp @@ -0,0 +1,13 @@ +/// PLACEHOLDER FILE FOR RESPONSE RULES RUNTIME LIBRARY + +#include "rrbase.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + + +namespace ResponseRules +{ + /// Custom symbol table for the response rules. + CUtlSymbolTable g_RS; +}; \ No newline at end of file diff --git a/src/responserules/runtime/stdafx.cpp b/src/responserules/runtime/stdafx.cpp new file mode 100644 index 00000000..4199359f --- /dev/null +++ b/src/responserules/runtime/stdafx.cpp @@ -0,0 +1,11 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: Builds the precompiled header for the game DLL +// +// $NoKeywords: $ +//=============================================================================// + + +#include "rrbase.h" + +// NOTE: DO NOT ADD ANY CODE OR HEADERS TO THIS FILE!!! diff --git a/src/tier1/KeyValues.cpp b/src/tier1/KeyValues.cpp index 648b4522..1e4fca43 100644 --- a/src/tier1/KeyValues.cpp +++ b/src/tier1/KeyValues.cpp @@ -30,6 +30,9 @@ #include "utlqueue.h" #include "UtlSortVector.h" #include "convar.h" +#ifdef MAPBASE +#include "icommandline.h" +#endif // memdbgon must be the last include file in a .cpp file!!! #include @@ -2247,6 +2250,28 @@ bool EvaluateConditional( const char *str ) if ( Q_stristr( str, "$POSIX" ) ) return IsPosix() ^ bNot; + +#ifdef MAPBASE + // Custom conditional + switch( str[bNot ? 1 : 0] ) + { + case '%': + { + // Look for a cvar + ConVarRef cvar( str + (bNot ? 2 : 1), true ); + if (cvar.IsValid()) + { + return cvar.GetBool() ^ bNot; + } + } break; + + case '-': + { + // Look for a command line param + return (CommandLine()->CheckParm( bNot ? str+1 : str ) != 0) ^ bNot; + } break; + } +#endif return false; } diff --git a/src/tier1/bitbuf.cpp b/src/tier1/bitbuf.cpp index d501cbe1..92413bed 100644 --- a/src/tier1/bitbuf.cpp +++ b/src/tier1/bitbuf.cpp @@ -1357,13 +1357,13 @@ int64 bf_read::ReadLongLong() float bf_read::ReadFloat() { - float ret; - Assert( sizeof(ret) == 4 ); - ReadBits(&ret, 32); + float retLocl; + Assert( sizeof(retLocl) == 4 ); + ReadBits(&retLocl, 32); // Swap the float, since ReadBits reads raw data - LittleFloat( &ret, &ret ); - return ret; + LittleFloat( &retLocl, &retLocl ); + return retLocl; } bool bf_read::ReadBytes(void *pOut, int nBytes) diff --git a/src/tier1/convar.cpp b/src/tier1/convar.cpp index 9aa66bbf..d2a09b15 100644 --- a/src/tier1/convar.cpp +++ b/src/tier1/convar.cpp @@ -705,7 +705,10 @@ ConVar::~ConVar( void ) //----------------------------------------------------------------------------- void ConVar::InstallChangeCallback( FnChangeCallback_t callback ) { +#ifndef MAPBASE_VSCRIPT Assert( !m_pParent->m_fnChangeCallback || !callback ); +#endif + m_pParent->m_fnChangeCallback = callback; if ( m_pParent->m_fnChangeCallback ) diff --git a/src/game/shared/interval.cpp b/src/tier1/interval.cpp similarity index 100% rename from src/game/shared/interval.cpp rename to src/tier1/interval.cpp diff --git a/src/tier1/mapbase_con_groups.cpp b/src/tier1/mapbase_con_groups.cpp new file mode 100644 index 00000000..5f23b3f8 --- /dev/null +++ b/src/tier1/mapbase_con_groups.cpp @@ -0,0 +1,193 @@ +//========= Mapbase - https://github.com/mapbase-source/source-sdk-2013 ================= +// +// Purpose: Mapbase classifies certain types of console messages into groups with specific colors. +// +// This is inspired by similar groups seen in CS:GO and Source 2 games. +// +// $NoKeywords: $ +//============================================================================= + +#include +#include +#include +#include "basetypes.h" +#include "tier1.h" +#include "utldict.h" +#include "Color.h" +#include "mapbase_con_groups.h" +#include "KeyValues.h" +#include "filesystem.h" +#include "mapbase_matchers_base.h" + +struct ConGroup_t +{ + ConGroup_t( const char *_pszName, const char *_pszDescription ) + { + pszName = _pszName; + pszDescription = _pszDescription; + _clr.SetColor( 224, 224, 224, 255 ); // Default to a shade of gray + } + + const Color &GetColor() + { + return _clr; + } + + const char *pszName; + const char *pszDescription; + Color _clr; + + bool bDisabled; +}; + +// TODO: Something more reliable? +static bool g_bIncludeConGroupNames = false; + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +//#define DEFINE_CON_GROUP(id, name, codename) { name, &con_group_##codename##_color } +#define DEFINE_CON_GROUP(id, name, description) { name, description } + +ConGroup_t g_ConGroups[CON_GROUP_MAX] = { + + // General + DEFINE_CON_GROUP( CON_GROUP_MAPBASE_MISC, "Mapbase misc.", "Messages from misc. Mapbase functions, like map-specific files." ), + DEFINE_CON_GROUP( CON_GROUP_PHYSICS, "Physics", "Messages from physics-related events." ), + DEFINE_CON_GROUP( CON_GROUP_IO_SYSTEM, "Entity IO", "Messages from I/O events. (these display in developer 2)" ), + DEFINE_CON_GROUP( CON_GROUP_RESPONSE_SYSTEM, "Response System", "Messages from the Response System, a library primarily used for NPC speech." ), + + // Game + DEFINE_CON_GROUP( CON_GROUP_NPC_AI, "NPC AI", "Messages from NPC AI, etc. which display at various verbose levels." ), + DEFINE_CON_GROUP( CON_GROUP_NPC_SCRIPTS, "NPC scripts", "Messages from scripted_sequence, etc. (these display in developer 2)" ), + DEFINE_CON_GROUP( CON_GROUP_SPEECH_AI, "Speech AI", "Messages from response expressers. (these display in developer 1, 2, etc.)" ), + DEFINE_CON_GROUP( CON_GROUP_CHOREO, "Choreo", "Messages from choreographed scenes. (these display in developer 1, 2, etc.)" ), + + // VScript + DEFINE_CON_GROUP( CON_GROUP_VSCRIPT, "VScript", "Internal messages from VScript not produced by actual scripts." ), + DEFINE_CON_GROUP( CON_GROUP_VSCRIPT_PRINT, "VScript print", "Messages from VScript's 'print' function." ), + +}; + +int FindConGroup( const char *pszName ) +{ + for (int i = 0; i < CON_GROUP_MAX; i++) + { + if (Q_stricmp( pszName, g_ConGroups[i].pszName ) == 0) + return i; + } + + return -1; +} + +//----------------------------------------------------------------------------- +// Loads console groups +//----------------------------------------------------------------------------- +void LoadConsoleGroupsFromFile( IBaseFileSystem *filesystem, const char *pszFileName, const char *pathID ) +{ + KeyValues *pGroupRoot = new KeyValues( "ConsoleGroups" ); + + pGroupRoot->LoadFromFile( filesystem, pszFileName, pathID ); + + KeyValues *pGroup = NULL; + for ( pGroup = pGroupRoot->GetFirstTrueSubKey(); pGroup; pGroup = pGroup->GetNextTrueSubKey() ) + { + int index = FindConGroup( pGroup->GetName() ); + if (index != -1) + { + Color msgClr = pGroup->GetColor( "MessageColor" ); + + // Make sure the color isn't 0,0,0,0 before assigning + if (msgClr.GetRawColor() != 0) + g_ConGroups[index]._clr = msgClr; + + g_ConGroups[index].bDisabled = pGroup->GetBool( "Disabled", false ); + } + else + { + Warning( "Invalid console group %s (new groups should be defined in the code)\n", pGroup->GetName() ); + } + } + + pGroupRoot->deleteThis(); +} + +void InitConsoleGroups( IBaseFileSystem *filesystem ) +{ + LoadConsoleGroupsFromFile( filesystem, "scripts/mapbase_con_groups.txt", "MOD" ); + LoadConsoleGroupsFromFile( filesystem, "scripts/mod_con_groups.txt", "MOD" ); +} + +void PrintAllConsoleGroups() +{ + Msg( "============================================================\n" ); + for (int i = 0; i < CON_GROUP_MAX; i++) + { + ConColorMsg( g_ConGroups[i].GetColor(), " # %s", g_ConGroups[i].pszName ); + + if (g_ConGroups[i].bDisabled) + Msg(" [DISABLED]"); + + Msg( " - %s ", g_ConGroups[i].pszDescription ); + + Msg("\n"); + } + Msg( "============================================================\n" ); +} + +void ToggleConsoleGroups( const char *pszQuery ) +{ + bool bMatched = false; + + for (int i = 0; i < ARRAYSIZE( g_ConGroups ); i++) + { + if (Matcher_NamesMatch( pszQuery, g_ConGroups[i].pszName )) + { + Msg( "%s is now %s\n", g_ConGroups[i].pszName, g_ConGroups[i].bDisabled ? "enabled" : "disabled" ); + g_ConGroups[i].bDisabled = !g_ConGroups[i].bDisabled; + bMatched = true; + } + } + + if (!bMatched) + Msg( "No groups matching \"%s\"\n", pszQuery ); +} + +void SetConsoleGroupIncludeNames( bool bToggle ) +{ + g_bIncludeConGroupNames = bToggle; +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +void CGMsg( int level, ConGroupID_t nGroup, const tchar* pMsg, ... ) +{ + // Return early if we're not at this level + if (!IsSpewActive("developer", level)) + return; + + char string[ 2048 ]; + va_list argptr; + va_start( argptr, pMsg ); + Q_vsnprintf( string, sizeof(string), pMsg, argptr ); + va_end( argptr ); + + Assert( nGroup >= 0 ); + Assert( nGroup < CON_GROUP_MAX ); + + ConGroup_t *pGroup = &g_ConGroups[nGroup]; + + if (pGroup->bDisabled) + { + // Do nothing + } + else if (g_bIncludeConGroupNames) + { + ConColorMsg(level, pGroup->GetColor(), "[%s] %s", pGroup->pszName, string); + } + else + { + ConColorMsg(level, pGroup->GetColor(), "%s", string); + } +} diff --git a/src/tier1/mapbase_matchers_base.cpp b/src/tier1/mapbase_matchers_base.cpp new file mode 100644 index 00000000..d4036f2e --- /dev/null +++ b/src/tier1/mapbase_matchers_base.cpp @@ -0,0 +1,243 @@ +//========= Mapbase - https://github.com/mapbase-source/source-sdk-2013 ================= +// +// Purpose: General matching functions for things like wildcards and !=. +// +// $NoKeywords: $ +//============================================================================= + +#include "mapbase_matchers_base.h" +#include "convar.h" + +// glibc (Linux) uses these tokens when including , so we must not #define them +#undef max +#undef min +#include +#undef MINMAX_H +//#include "minmax.h" + +ConVar mapbase_wildcards_enabled("mapbase_wildcards_enabled", "1", FCVAR_NONE, "Toggles Mapbase's '?' wildcard and true '*' features. Useful for maps that have '?' in their targetnames."); +ConVar mapbase_wildcards_lazy_hack("mapbase_wildcards_lazy_hack", "1", FCVAR_NONE, "Toggles a hack which prevents Mapbase's lazy '?' wildcards from picking up \"???\", the default instance parameter."); +ConVar mapbase_regex_enabled("mapbase_regex_enabled", "1", FCVAR_NONE, "Toggles Mapbase's regex matching handover."); + +//============================================================================= +// These are the "matchers" that compare with wildcards ("any*" for text starting with "any") +// and operators (<3 for numbers less than 3). +// +// Matcher_Regex - Uses regex functions from the std library. +// Matcher_NamesMatch - Based on Valve's original NamesMatch function, using wildcards and regex. +// +// AppearsToBeANumber - Response System-based function which checks if the string might be a number. +//============================================================================= + +// The recursive part of Mapbase's modified version of Valve's NamesMatch(). +bool Matcher_RunCharCompare(const char *pszQuery, const char *szValue) +{ + // This matching model is based off of the ASW SDK + while ( *szValue && *pszQuery ) + { + char cName = *szValue; + char cQuery = *pszQuery; + if ( cName != cQuery && tolower(cName) != tolower(cQuery) ) // people almost always use lowercase, so assume that first + { + // Now we'll try the new and improved Mapbase wildcards! + switch (*pszQuery) + { + case '*': + { + // Return true at classic trailing * + if ( *(pszQuery+1) == 0 ) + return true; + + if (mapbase_wildcards_enabled.GetBool()) + { + // There's text after this * which we need to test. + // This recursion allows for multiple wildcards + int vlen = Q_strlen(szValue); + ++pszQuery; + for (int i = 0; i < vlen; i++) + { + if (Matcher_RunCharCompare(pszQuery, szValue + i)) + return true; + } + } + return false; + } break; + case '?': + // Just skip if we're capable of lazy wildcards + if (mapbase_wildcards_enabled.GetBool()) + break; + default: + return false; + } + } + ++szValue; + ++pszQuery; + } + + // Include a classic trailing * check for when szValue is something like "value" and pszQuery is "value*" + return ( ( *pszQuery == 0 && *szValue == 0 ) || *pszQuery == '*' ); +} + +// Regular expressions based off of the std library. +// The C++ is strong in this one. +bool Matcher_Regex(const char *pszQuery, const char *szValue) +{ + std::regex regex; + + // Since I can't find any other way to check for valid regex, + // use a try-catch here to see if it throws an exception. + try { regex = std::regex(pszQuery); } + catch (std::regex_error &e) + { + Msg("Invalid regex \"%s\" (%s)\n", pszQuery, e.what()); + return false; + } + + std::match_results results; + bool bMatch = std::regex_match( szValue, results, regex ); + if (!bMatch) + return false; + + // Only match the *whole* string + return Q_strlen(results.str(0).c_str()) == Q_strlen(szValue); +} + +// The entry point for Mapbase's modified version of Valve's NamesMatch(). +bool Matcher_NamesMatch(const char *pszQuery, const char *szValue) +{ + if ( szValue == NULL ) + return (*pszQuery == 0 || *pszQuery == '*'); + + // If the pointers are identical, we're identical + if ( szValue == pszQuery ) + return true; + + // Check for regex + if ( *pszQuery == '@' && mapbase_regex_enabled.GetBool() ) + { + // Make sure it has a forward slash + // (prevents confusion with instance fixup escape) + if (*(pszQuery+1) == '/') + { + return Matcher_Regex( pszQuery+2, szValue ); + } + } + else if (pszQuery[0] == '?' && pszQuery[1] == '?' && pszQuery[2] == '?' && mapbase_wildcards_lazy_hack.GetBool()) + { + // HACKHACK: There's a nasty issue where instances with blank parameters use "???", but Mapbase's lazy wildcard code + // recognizes this as essentially meaning "any name with 3 characters". This is a serious problem when the instance + // specifically expects the game to interpret "???" as a blank space, such as with damage filters, which crash when targeting + // a non-filter entity. + return false; + } + + return Matcher_RunCharCompare( pszQuery, szValue ); +} + +bool Matcher_NamesMatch_Classic(const char *pszQuery, const char *szValue) +{ + if ( szValue == NULL ) + return (!pszQuery || *pszQuery == 0 || *pszQuery == '*'); + + // If the pointers are identical, we're identical + if ( szValue == pszQuery ) + return true; + + while ( *szValue && *pszQuery ) + { + unsigned char cName = *szValue; + unsigned char cQuery = *pszQuery; + // simple ascii case conversion + if ( cName == cQuery ) + ; + else if ( cName - 'A' <= (unsigned char)'Z' - 'A' && cName - 'A' + 'a' == cQuery ) + ; + else if ( cName - 'a' <= (unsigned char)'z' - 'a' && cName - 'a' + 'A' == cQuery ) + ; + else + break; + ++szValue; + ++pszQuery; + } + + if ( *pszQuery == 0 && *szValue == 0 ) + return true; + + // @TODO (toml 03-18-03): Perhaps support real wildcards. Right now, only thing supported is trailing * + if ( *pszQuery == '*' ) + return true; + + return false; +} + +bool Matcher_NamesMatch_MutualWildcard(const char *pszQuery, const char *szValue) +{ + if ( szValue == NULL ) + return (!pszQuery || *pszQuery == 0 || *pszQuery == '*'); + + if ( pszQuery == NULL ) + return (!szValue || *szValue == 0 || *szValue == '*'); + + // If the pointers are identical, we're identical + if ( szValue == pszQuery ) + return true; + + while ( *szValue && *pszQuery ) + { + unsigned char cName = *szValue; + unsigned char cQuery = *pszQuery; + // simple ascii case conversion + if ( cName == cQuery ) + ; + else if ( cName - 'A' <= (unsigned char)'Z' - 'A' && cName - 'A' + 'a' == cQuery ) + ; + else if ( cName - 'a' <= (unsigned char)'z' - 'a' && cName - 'a' + 'A' == cQuery ) + ; + else + break; + ++szValue; + ++pszQuery; + } + + if ( *pszQuery == 0 && *szValue == 0 ) + return true; + + // @TODO (toml 03-18-03): Perhaps support real wildcards. Right now, only thing supported is trailing * + if ( *pszQuery == '*' || *szValue == '*' ) + return true; + + return false; +} + +// Returns true if a string contains a wildcard. +bool Matcher_ContainsWildcard(const char *pszQuery) +{ + if ( pszQuery == NULL ) + return false; + + while ( *pszQuery ) + { + unsigned char cQuery = *pszQuery; + if (cQuery == '*' || cQuery == '?') + return true; + ++pszQuery; + } + + return false; +} + +// Matcher_Compare is a deprecated alias originally used when Matcher_Match didn't support wildcards. +/* +bool Matcher_Compare(const char *pszQuery, const char *szValue) +{ + return Matcher_Match(pszQuery, szValue); +#if 0 + // I have to do this so wildcards could test *before* the response system comparison. + // I know it removes the operators twice, but I won't worry about it. + bool match = Matcher_NamesMatch(Matcher_RemoveOperators(pszQuery), szValue); + if (match) + return Matcher_Match(pszQuery, szValue); + return false; +#endif +} +*/ diff --git a/src/tier1/tier1.vpc b/src/tier1/tier1.vpc index 36b8ec9c..2c7dd366 100644 --- a/src/tier1/tier1.vpc +++ b/src/tier1/tier1.vpc @@ -40,6 +40,7 @@ $Project "tier1" $File "generichash.cpp" $File "ilocalize.cpp" $File "interface.cpp" + $File "interval.cpp" $File "KeyValues.cpp" $File "keyvaluesjson.cpp" $File "kvpacker.cpp" @@ -80,6 +81,8 @@ $Project "tier1" $File "snappy.cpp" $File "snappy-sinksource.cpp" $File "snappy-stubs-internal.cpp" + $File "mapbase_con_groups.cpp" [$MAPBASE] + $File "mapbase_matchers_base.cpp" [$MAPBASE] } // Select bits from the LZMA SDK to support lzmaDecoder.h @@ -164,6 +167,8 @@ $Project "tier1" $File "$SRCDIR\public\tier1\utlvector.h" $File "$SRCDIR\public\tier1\utlrange.h" $File "$SRCDIR\public\tier1\utlbinaryblock.h" + $File "$SRCDIR\public\tier1\mapbase_con_groups.h" [$MAPBASE] + $File "$SRCDIR\public\tier1\mapbase_matchers_base.h" [$MAPBASE] $File "$SRCDIR\common\xbox\xboxstubs.h" [$WINDOWS] } } diff --git a/src/utils/captioncompiler/captioncompiler.vpc b/src/utils/captioncompiler/captioncompiler.vpc index 34cd64e4..91747665 100644 --- a/src/utils/captioncompiler/captioncompiler.vpc +++ b/src/utils/captioncompiler/captioncompiler.vpc @@ -14,7 +14,8 @@ $Configuration $Compiler { $AdditionalIncludeDirectories "$BASE,..\common,$SRCDIR\game\shared,.\" - $PreprocessorDefinitions "$BASE;captioncompiler" + $PreprocessorDefinitions "$BASE;PROTECTED_THINGS_DISABLE;captioncompiler" [($VS2015||$VS2017||$VS2019||$VS2022)] + $PreprocessorDefinitions "$BASE;captioncompiler" [!($VS2015||$VS2017||$VS2019||$VS2022)] } } diff --git a/src/utils/common/threads.cpp b/src/utils/common/threads.cpp index 74e457a9..28f0894c 100644 --- a/src/utils/common/threads.cpp +++ b/src/utils/common/threads.cpp @@ -19,7 +19,13 @@ #include "threads.h" #include "pacifier.h" +#ifdef MAPBASE +// This was suggested in that Source 2013 pull request that fixed Vrad. +// I trust their judgement on this. +#define MAX_THREADS 32 +#else #define MAX_THREADS 16 +#endif class CRunThreadsData diff --git a/src/utils/common/threads.h b/src/utils/common/threads.h index 0908b67a..7d6beb9f 100644 --- a/src/utils/common/threads.h +++ b/src/utils/common/threads.h @@ -16,9 +16,15 @@ #pragma once +#ifdef MAPBASE +// This was suggested in that Source 2013 pull request that fixed Vrad. +// I trust their judgement on this. +#define MAX_TOOL_THREADS 32 +#else // Arrays that are indexed by thread should always be MAX_TOOL_THREADS+1 // large so THREADINDEX_MAIN can be used from the main thread. #define MAX_TOOL_THREADS 16 +#endif #define THREADINDEX_MAIN (MAX_TOOL_THREADS) diff --git a/src/utils/common/utilmatlib.cpp b/src/utils/common/utilmatlib.cpp index f2dc49fa..7e62e000 100644 --- a/src/utils/common/utilmatlib.cpp +++ b/src/utils/common/utilmatlib.cpp @@ -59,6 +59,7 @@ void InitMaterialSystem( const char *materialBaseDirPath, CreateInterfaceFn file LoadMaterialSystemInterface( fileSystemFactory ); MaterialSystem_Config_t config; g_pMaterialSystem->OverrideConfig( config, false ); + g_pMaterialSystem->ModInit(); } void ShutdownMaterialSystem( ) diff --git a/src/utils/vbsp/csg.cpp b/src/utils/vbsp/csg.cpp index 5de1d680..a8c39afb 100644 --- a/src/utils/vbsp/csg.cpp +++ b/src/utils/vbsp/csg.cpp @@ -594,6 +594,13 @@ void PrintBrushContentsToString( int contents, char *pOut, int nMaxChars ) ADD_CONTENTS(CONTENTS_BLOCKLOS) ADD_CONTENTS(CONTENTS_OPAQUE) ADD_CONTENTS(CONTENTS_TESTFOGVOLUME) +#ifdef MAPBASE + ADD_CONTENTS(CONTENTS_UNUSED) + ADD_CONTENTS(CONTENTS_UNUSED6) + ADD_CONTENTS(CONTENTS_TEAM1) + ADD_CONTENTS(CONTENTS_TEAM2) + ADD_CONTENTS(CONTENTS_IGNORE_NODRAW_OPAQUE) +#endif ADD_CONTENTS(CONTENTS_MOVEABLE) ADD_CONTENTS(CONTENTS_AREAPORTAL) ADD_CONTENTS(CONTENTS_PLAYERCLIP) diff --git a/src/utils/vbsp/cubemap.cpp b/src/utils/vbsp/cubemap.cpp index 829efdfb..9b5f4641 100644 --- a/src/utils/vbsp/cubemap.cpp +++ b/src/utils/vbsp/cubemap.cpp @@ -84,9 +84,15 @@ inline bool SideHasCubemapAndWasntManuallyReferenced( int iSide ) return s_aCubemapSideData[iSide].bHasEnvMapInMaterial && !s_aCubemapSideData[iSide].bManuallyPickedByAnEnvCubemap; } - +#ifdef PARALLAX_CORRECTED_CUBEMAPS +char* g_pParallaxObbStrs[MAX_MAP_CUBEMAPSAMPLES]; +void Cubemap_InsertSample( const Vector& origin, int size, char* pParallaxObbStr = "" ) +{ + g_pParallaxObbStrs[g_nCubemapSamples] = pParallaxObbStr; +#else void Cubemap_InsertSample( const Vector& origin, int size ) { +#endif dcubemapsample_t *pSample = &g_CubemapSamples[g_nCubemapSamples]; pSample->origin[0] = ( int )origin[0]; pSample->origin[1] = ( int )origin[1]; @@ -277,7 +283,15 @@ void VTFNameToHDRVTFName( const char *pSrcName, char *pDest, int maxLen, bool bH Q_strncpy( pDot, ".hdr.vtf", maxLen - ( pDot - pDest ) ); } +#ifdef MAPBASE + +extern bool g_bSkyboxCubemaps; +extern int g_iDefaultCubemapSize; +#define DEFAULT_CUBEMAP_SIZE g_iDefaultCubemapSize + +#else #define DEFAULT_CUBEMAP_SIZE 32 +#endif void CreateDefaultCubemaps( bool bHDR ) { @@ -339,9 +353,17 @@ void CreateDefaultCubemaps( bool bHDR ) int iSize = pDstCubemap->ComputeMipSize( iMip ); int iSrcMipSize = pSrcVTFTextures[iFace]->ComputeMipSize( iMip + iMipLevelOffset ); +#ifdef MAPBASE + if (!g_bSkyboxCubemaps) + { + memset( pDstBits, 0, iSize ); + continue; + } +#else // !!! FIXME: Set this to black until HDR cubemaps are built properly! memset( pDstBits, 0, iSize ); continue; +#endif if ( ( pSrcVTFTextures[iFace]->Width() == 4 ) && ( pSrcVTFTextures[iFace]->Height() == 4 ) ) // If texture is 4x4 square { @@ -429,6 +451,14 @@ void CreateDefaultCubemaps( bool bHDR ) pDstCubemap->ConvertImageFormat( originalFormat, false ); } +#ifdef MAPBASE + // Apply a seam fix + pDstCubemap->ConvertImageFormat( IMAGE_FORMAT_RGBA8888, false ); + + pDstCubemap->MatchCubeMapBorders( 1, IMAGE_FORMAT_RGBA8888, false ); + pDstCubemap->MatchCubeMapBorders( 2, originalFormat, false ); +#endif + // Write the puppy out! char dstVTFFileName[1024]; if( bHDR ) @@ -528,7 +558,11 @@ static void GeneratePatchedName( const char *pMaterialName, const PatchInfo_t &i //----------------------------------------------------------------------------- // Patches the $envmap for a material and all its dependents, returns true if any patching happened //----------------------------------------------------------------------------- +#ifdef PARALLAX_CORRECTED_CUBEMAPS +static bool PatchEnvmapForMaterialAndDependents( const char *pMaterialName, const PatchInfo_t &info, const char *pCubemapTexture, const char *pParallaxObbMatrix = "" ) +#else static bool PatchEnvmapForMaterialAndDependents( const char *pMaterialName, const PatchInfo_t &info, const char *pCubemapTexture ) +#endif { // Do *NOT* patch the material if there is an $envmap specified and it's not 'env_cubemap' @@ -546,7 +580,11 @@ static bool PatchEnvmapForMaterialAndDependents( const char *pMaterialName, cons const char *pDependentMaterial = FindDependentMaterial( pMaterialName, &pDependentMaterialVar ); if ( pDependentMaterial ) { +#ifdef PARALLAX_CORRECTED_CUBEMAPS + bDependentMaterialPatched = PatchEnvmapForMaterialAndDependents( pDependentMaterial, info, pCubemapTexture, pParallaxObbMatrix ); +#else bDependentMaterialPatched = PatchEnvmapForMaterialAndDependents( pDependentMaterial, info, pCubemapTexture ); +#endif } // If we have neither to patch, we're done @@ -557,7 +595,11 @@ static bool PatchEnvmapForMaterialAndDependents( const char *pMaterialName, cons char pPatchedMaterialName[1024]; GeneratePatchedName( pMaterialName, info, true, pPatchedMaterialName, 1024 ); +#ifdef PARALLAX_CORRECTED_CUBEMAPS + MaterialPatchInfo_t pPatchInfo[7]; +#else MaterialPatchInfo_t pPatchInfo[2]; +#endif int nPatchCount = 0; if ( bShouldPatchEnvCubemap ) { @@ -567,6 +609,33 @@ static bool PatchEnvmapForMaterialAndDependents( const char *pMaterialName, cons ++nPatchCount; } +#ifdef PARALLAX_CORRECTED_CUBEMAPS + // Parallax cubemap matrix + CUtlVector matRowList; + if (pParallaxObbMatrix[0] != '\0') + { + V_SplitString( pParallaxObbMatrix, ";", matRowList ); + + // Needed for editor + pPatchInfo[nPatchCount].m_pKey = "$envMapParallax"; + pPatchInfo[nPatchCount].m_pValue = "1"; + ++nPatchCount; + + pPatchInfo[nPatchCount].m_pKey = "$envMapParallaxOBB1"; + pPatchInfo[nPatchCount].m_pValue = matRowList[0]; + ++nPatchCount; + pPatchInfo[nPatchCount].m_pKey = "$envMapParallaxOBB2"; + pPatchInfo[nPatchCount].m_pValue = matRowList[1]; + ++nPatchCount; + pPatchInfo[nPatchCount].m_pKey = "$envMapParallaxOBB3"; + pPatchInfo[nPatchCount].m_pValue = matRowList[2]; + ++nPatchCount; + pPatchInfo[nPatchCount].m_pKey = "$envMapOrigin"; + pPatchInfo[nPatchCount].m_pValue = matRowList[3]; + ++nPatchCount; + } +#endif + char pDependentPatchedMaterialName[1024]; if ( bDependentMaterialPatched ) { @@ -578,7 +647,14 @@ static bool PatchEnvmapForMaterialAndDependents( const char *pMaterialName, cons ++nPatchCount; } +#ifdef PARALLAX_CORRECTED_CUBEMAPS + CreateMaterialPatch( pMaterialName, pPatchedMaterialName, nPatchCount, pPatchInfo, PATCH_INSERT ); + + // Clean up parallax stuff + matRowList.PurgeAndDeleteElements(); +#else CreateMaterialPatch( pMaterialName, pPatchedMaterialName, nPatchCount, pPatchInfo, PATCH_REPLACE ); +#endif return true; } @@ -597,7 +673,11 @@ static bool PatchEnvmapForMaterialAndDependents( const char *pMaterialName, cons // default (skybox) cubemap into this file so the cubemap doesn't have the pink checkerboard at // runtime before they run buildcubemaps. //----------------------------------------------------------------------------- +#ifdef PARALLAX_CORRECTED_CUBEMAPS +static int Cubemap_CreateTexInfo( int originalTexInfo, int origin[3], int cubemapIndex ) +#else static int Cubemap_CreateTexInfo( int originalTexInfo, int origin[3] ) +#endif { // Don't make cubemap tex infos for nodes if ( originalTexInfo == TEXINFO_NODE ) @@ -629,6 +709,15 @@ static int Cubemap_CreateTexInfo( int originalTexInfo, int origin[3] ) char pGeneratedTexDataName[1024]; GeneratePatchedName( pMaterialName, info, true, pGeneratedTexDataName, 1024 ); +#ifdef PARALLAX_CORRECTED_CUBEMAPS + // Append origin info if this cubemap has a parallax OBB + char originAppendedString[1024] = ""; + if (g_pParallaxObbStrs[cubemapIndex] && g_pParallaxObbStrs[cubemapIndex][0] != '\0') + { + Q_snprintf(originAppendedString, 1024, "%s;[%d %d %d]", g_pParallaxObbStrs[cubemapIndex], origin[0], origin[1], origin[2]); + } +#endif + // Make sure the texdata doesn't already exist. int nTexDataID = FindTexData( pGeneratedTexDataName ); bool bHasTexData = (nTexDataID != -1); @@ -640,7 +729,11 @@ static int Cubemap_CreateTexInfo( int originalTexInfo, int origin[3] ) // Hook the texture into the material and all dependent materials // but if no hooking was necessary, exit out +#ifdef PARALLAX_CORRECTED_CUBEMAPS + if ( !PatchEnvmapForMaterialAndDependents( pMaterialName, info, pTextureName, originAppendedString ) ) +#else if ( !PatchEnvmapForMaterialAndDependents( pMaterialName, info, pTextureName ) ) +#endif return originalTexInfo; // Store off the name of the cubemap that we need to create since we successfully patched @@ -730,7 +823,11 @@ void Cubemap_FixupBrushSidesMaterials( void ) } #endif +#ifdef PARALLAX_CORRECTED_CUBEMAPS + pSide->texinfo = Cubemap_CreateTexInfo( pSide->texinfo, g_CubemapSamples[cubemapID].origin, cubemapID ); +#else pSide->texinfo = Cubemap_CreateTexInfo( pSide->texinfo, g_CubemapSamples[cubemapID].origin ); +#endif if ( pSide->pMapDisp ) { pSide->pMapDisp->face.texinfo = pSide->texinfo; @@ -946,7 +1043,11 @@ void Cubemap_AttachDefaultCubemapToSpecularSides( void ) Assert( pSide->texinfo == pSide->pMapDisp->face.texinfo ); } #endif +#ifdef PARALLAX_CORRECTED_CUBEMAPS + pSide->texinfo = Cubemap_CreateTexInfo( pSide->texinfo, g_CubemapSamples[iCubemap].origin, iCubemap ); +#else pSide->texinfo = Cubemap_CreateTexInfo( pSide->texinfo, g_CubemapSamples[iCubemap].origin ); +#endif if ( pSide->pMapDisp ) { pSide->pMapDisp->face.texinfo = pSide->texinfo; diff --git a/src/utils/vbsp/detail.cpp b/src/utils/vbsp/detail.cpp index 840068de..b17917bc 100644 --- a/src/utils/vbsp/detail.cpp +++ b/src/utils/vbsp/detail.cpp @@ -440,6 +440,9 @@ face_t *MakeBrushFace( side_t *originalSide, winding_t *winding ) f->split[0] = f->split[1] = NULL; f->w = CopyWinding( winding ); f->originalface = originalSide; +#ifdef MAPBASE + f->smoothingGroups = originalSide->smoothingGroups; +#endif // // save material info // diff --git a/src/utils/vbsp/manifest.cpp b/src/utils/vbsp/manifest.cpp index 7e624836..caa71445 100644 --- a/src/utils/vbsp/manifest.cpp +++ b/src/utils/vbsp/manifest.cpp @@ -5,13 +5,31 @@ #include "manifest.h" #include "windows.h" +#ifdef MAPBASE +entity_t *g_ManifestWorldSpawn = NULL; + +extern char g_MainMapPath[ MAX_PATH ]; +#endif + +//----------------------------------------------------------------------------- +// Purpose: default constructor +//----------------------------------------------------------------------------- +CManifestMapPrefs::CManifestMapPrefs( void ) +{ + m_nInternalId = 0; + m_bIsVisible = true; + //m_bIsPrimary = false; +} + //----------------------------------------------------------------------------- // Purpose: default constructor //----------------------------------------------------------------------------- CManifestMap::CManifestMap( void ) { + m_nInternalId = 0; m_RelativeMapFileName[ 0 ] = 0; m_bTopLevelMap = false; + m_bIsVisible = true; } @@ -35,7 +53,11 @@ CManifest::CManifest( void ) //----------------------------------------------------------------------------- ChunkFileResult_t CManifest::LoadManifestMapKeyCallback( const char *szKey, const char *szValue, CManifestMap *pManifestMap ) { - if ( !stricmp( szKey, "Name" ) ) + if ( !stricmp( szKey, "InternalID" ) ) + { + pManifestMap->m_nInternalId = atoi( szValue ); + } + else if ( !stricmp( szKey, "Name" ) ) { // pManifestMap->m_FriendlyName = szValue; } @@ -260,6 +282,82 @@ ChunkFileResult_t CManifest::LoadManifestCordoningPrefsCallback( CChunkFile *pFi return( eResult ); } +//----------------------------------------------------------------------------- +// Parses the preferences chunk that pertains to specific submaps in the map: +// +// Maps +// { +// VMF +// { +// "InternalID" "1" +// "IsPrimary" "1" +// } +// VMF +// { +// "InternalID" "2" +// } +// VMF +// { +// "InternalID" "3" +// "IsVisible" "0" +// } +// } +// +//----------------------------------------------------------------------------- + +ChunkFileResult_t CManifest::LoadPrefsVmfKeyCallback( const char *szKey, const char *szValue, CManifestMapPrefs *pManifestMapPrefs ) +{ + if ( !stricmp( szKey, "InternalID" ) ) + { + pManifestMapPrefs->m_nInternalId = atoi( szValue ); + } + else if ( !stricmp( szKey, "IsVisible" ) ) + { + pManifestMapPrefs->m_bIsVisible = ( atoi( szValue ) == 1 ); + } + //else if ( !stricmp( szKey, "IsPrimary" ) ) + //{ + // pManifestMapPrefs->m_bIsPrimary = ( atoi( szValue ) == 1 ); + //} + + return ChunkFile_Ok; +} + +//----------------------------------------------------------------------------- +// Parses preferences and applies them to their corresponding submaps +//----------------------------------------------------------------------------- +ChunkFileResult_t CManifest::LoadPrefsVmfCallback( CChunkFile *pFile, CManifest *pManifest ) +{ + CManifestMapPrefs prefs; + ChunkFileResult_t eResult = pFile->ReadChunk( (KeyHandler_t)LoadPrefsVmfKeyCallback, &prefs ); + + if (eResult == ChunkFile_Ok && prefs.m_nInternalId != 0) + { + for( int i = 0; i < pManifest->m_Maps.Count(); i++ ) + { + if ( pManifest->m_Maps[ i ]->m_nInternalId == prefs.m_nInternalId ) + { + pManifest->m_Maps[ i ]->m_bIsVisible = prefs.m_bIsVisible; + break; + } + } + } + + return(eResult); +} +ChunkFileResult_t CManifest::LoadPrefsMapsCallback( CChunkFile *pFile, CManifest *pManifest ) +{ + CChunkHandlerMap Handlers; + Handlers.AddHandler( "VMF", ( ChunkHandler_t )CManifest::LoadPrefsVmfCallback, pManifest ); + pFile->PushHandlers(&Handlers); + + ChunkFileResult_t eResult = pFile->ReadChunk(); + + pFile->PopHandlers(); + + return( eResult ); +} + //----------------------------------------------------------------------------- // Purpose: this function will create a new entity pair @@ -298,12 +396,20 @@ bool CManifest::LoadSubMaps( CMapFile *pMapFile, const char *pszFileName ) memset( InstanceEntity, 0, sizeof( *InstanceEntity ) ); InstanceEntity->origin.Init( 0.0f, 0.0f, 0.0f ); + +#ifdef MAPBASE + g_ManifestWorldSpawn = InstanceEntity; +#else pEPair = CreateEPair( "classname", "worldspawn" ); pEPair->next = InstanceEntity->epairs; InstanceEntity->epairs = pEPair; +#endif for( int i = 0; i < m_Maps.Count(); i++ ) { + if ( g_bNoHiddenManifestMaps && !m_Maps[ i ]->m_bIsVisible ) + continue; + // if ( m_Maps[ i ]->m_bTopLevelMap == false ) { char FileName[ MAX_PATH ]; @@ -360,7 +466,11 @@ bool CManifest::LoadVMFManifestUserPrefs( const char *pszFileName ) UserNameSize = sizeof( UserName ); if ( GetUserName( UserName, &UserNameSize ) == 0 ) { +#ifdef MAPBASE + strcpy( UserName, "default" ); +#else strcpy( UserPrefsFileName, "default" ); +#endif } sprintf( UserPrefsFileName, "\\%s.vmm_prefs", UserName ); @@ -384,6 +494,9 @@ bool CManifest::LoadVMFManifestUserPrefs( const char *pszFileName ) CChunkHandlerMap Handlers; Handlers.AddHandler( "cordoning", ( ChunkHandler_t )CManifest::LoadManifestCordoningPrefsCallback, this ); + if (g_bNoHiddenManifestMaps) + Handlers.AddHandler( "Maps", ( ChunkHandler_t )CManifest::LoadPrefsMapsCallback, this ); + // Handlers.SetErrorHandler( ( ChunkErrorHandler_t )CMapDoc::HandleLoadError, this); File.PushHandlers(&Handlers); @@ -455,11 +568,14 @@ bool CManifest::LoadVMFManifest( const char *pszFileName ) if ( g_MainMap == NULL ) { g_MainMap = g_LoadingMap; +#ifdef MAPBASE + V_ExtractFilePath( pszFileName, g_MainMapPath, sizeof( g_MainMapPath ) ); +#endif } - LoadSubMaps( g_LoadingMap, pszFileName ); - LoadVMFManifestUserPrefs( pszFileName ); + + LoadSubMaps( g_LoadingMap, pszFileName ); } return ( eResult == ChunkFile_Ok ); diff --git a/src/utils/vbsp/manifest.h b/src/utils/vbsp/manifest.h index e7b801e1..3d772117 100644 --- a/src/utils/vbsp/manifest.h +++ b/src/utils/vbsp/manifest.h @@ -32,8 +32,19 @@ class CManifestMap { public: CManifestMap( void ); + int m_nInternalId; char m_RelativeMapFileName[ MAX_PATH ]; bool m_bTopLevelMap; + bool m_bIsVisible; +}; + +class CManifestMapPrefs +{ +public: + CManifestMapPrefs( void ); + int m_nInternalId; + bool m_bIsVisible; + //int m_bIsPrimary; }; class CManifest @@ -51,6 +62,9 @@ public: static ChunkFileResult_t LoadCordonsKeyCallback( const char *pszKey, const char *pszValue, CManifest *pManifest ); static ChunkFileResult_t LoadCordonsCallback( CChunkFile *pFile, CManifest *pManifest ); static ChunkFileResult_t LoadManifestCordoningPrefsCallback( CChunkFile *pFile, CManifest *pManifest ); + static ChunkFileResult_t LoadPrefsVmfKeyCallback( const char *szKey, const char *szValue, CManifestMapPrefs *pManifestMapPrefs ); + static ChunkFileResult_t LoadPrefsVmfCallback( CChunkFile *pFile, CManifest *pManifest ); + static ChunkFileResult_t LoadPrefsMapsCallback( CChunkFile *pFile, CManifest *pManifest ); bool LoadSubMaps( CMapFile *pMapFile, const char *pszFileName ); epair_t *CreateEPair( char *pKey, char *pValue ); diff --git a/src/utils/vbsp/map.cpp b/src/utils/vbsp/map.cpp index d39a2d72..c42452a9 100644 --- a/src/utils/vbsp/map.cpp +++ b/src/utils/vbsp/map.cpp @@ -15,6 +15,12 @@ #include "materialsub.h" #include "fgdlib/fgdlib.h" #include "manifest.h" +#ifdef PARALLAX_CORRECTED_CUBEMAPS +#include "matrixinvert.h" +#endif +#ifdef MAPBASE_VSCRIPT +#include "vscript_vbsp.h" +#endif #ifdef VSVMFIO #include "VmfImport.h" @@ -46,6 +52,15 @@ struct LoadSide_t extern qboolean onlyents; +#ifdef MAPBASE +extern entity_t *g_ManifestWorldSpawn; + +char g_MainMapPath[ MAX_PATH ]; + +// This is done for the instancing fix +bool g_pParallaxObbsDone[MAX_MAP_CUBEMAPSAMPLES]; +#endif + CUtlVector< CMapFile * > g_Maps; CMapFile *g_MainMap = NULL; @@ -1618,9 +1633,16 @@ ChunkFileResult_t CMapFile::LoadEntityCallback(CChunkFile *pFile, int nParam) if( ( g_nDXLevel == 0 ) || ( g_nDXLevel >= 70 ) ) { const char *pSideListStr = ValueForKey( mapent, "sides" ); +#ifdef PARALLAX_CORRECTED_CUBEMAPS + char *pParallaxObbStr = ValueForKey( mapent, "parallaxobb" ); +#endif int size; size = IntForKey( mapent, "cubemapsize" ); +#ifdef PARALLAX_CORRECTED_CUBEMAPS + Cubemap_InsertSample( mapent->origin, size, pParallaxObbStr ); +#else Cubemap_InsertSample( mapent->origin, size ); +#endif Cubemap_SaveBrushSides( pSideListStr ); } // clear out this entity @@ -1628,6 +1650,88 @@ ChunkFileResult_t CMapFile::LoadEntityCallback(CChunkFile *pFile, int nParam) return(ChunkFile_Ok); } +#ifdef PARALLAX_CORRECTED_CUBEMAPS + // + // parallax_obb brushes are removed after the transformation matrix is found and saved into + // the entity's data (ent will be removed after data transferred to patched materials) + // + if (!strcmp("parallax_obb", pClassName)) + { + matrix3x4_t obbMatrix, invObbMatrix; + SetIdentityMatrix(obbMatrix); + SetIdentityMatrix(invObbMatrix); + + // Get corner and its 3 edges (scaled, local x, y, and z axes) + mapbrush_t *brush = &mapbrushes[mapent->firstbrush]; + Vector corner, x, y, z; + + // Find first valid winding (with these whiles, if not enough valid windings then identity matrix is passed through to vmts) + int i = 0; + while (i < brush->numsides) + { + winding_t* wind = brush->original_sides[i].winding; + if (!wind) + { + i++; + continue; + } + + corner = wind->p[0]; + y = wind->p[1] - corner; + z = wind->p[3] - corner; + x = CrossProduct(y, z).Normalized(); + + i++; + break; + } + + // Skip second valid winding (opposite face from first, unusable for finding Z's length) + while (i < brush->numsides) + { + winding_t* wind = brush->original_sides[i].winding; + if (!wind) + { + i++; + continue; + } + i++; + break; + } + + // Find third valid winding + while (i < brush->numsides) + { + winding_t* wind = brush->original_sides[i].winding; + if (!wind) + { + i++; + continue; + } + + // Find length of x + // Start with diagonal, then scale x by the projection of diag onto x + Vector diag = wind->p[0] - wind->p[2]; + x *= abs(DotProduct(diag, x)); + + // Build transformation matrix (what is needed to turn a [0,0,0] - [1,1,1] cube into this brush) + MatrixSetColumn(x, 0, obbMatrix); + MatrixSetColumn(y, 1, obbMatrix); + MatrixSetColumn(z, 2, obbMatrix); + MatrixSetColumn(corner, 3, obbMatrix); + + //find inverse (we need the world to local matrix, "transformationmatrix" is kind of a misnomer) + MatrixInversion(obbMatrix, invObbMatrix); + break; + } + + char szMatrix[1024]; + Q_snprintf(szMatrix, 1024, "[%f %f %f %f];[%f %f %f %f];[%f %f %f %f]", invObbMatrix[0][0], invObbMatrix[0][1], invObbMatrix[0][2], invObbMatrix[0][3], invObbMatrix[1][0], invObbMatrix[1][1], invObbMatrix[1][2], invObbMatrix[1][3], invObbMatrix[2][0], invObbMatrix[2][1], invObbMatrix[2][2], invObbMatrix[2][3]); + SetKeyValue(mapent, "transformationmatrix", szMatrix); + + return (ChunkFile_Ok); + } +#endif + if ( !strcmp( "test_sidelist", pClassName ) ) { ConvertSideList(mapent, "sides"); @@ -2001,7 +2105,12 @@ void CMapFile::CheckForInstances( const char *pszFileName ) } char FDGPath[ MAX_PATH ]; +#ifdef MAPBASE + // Mapbase's FGD would be in a MOD path + if ( !g_pFullFileSystem->RelativePathToFullPath( GameDataFile, "MOD", FDGPath, sizeof( FDGPath ) ) ) +#else if ( !g_pFullFileSystem->RelativePathToFullPath( GameDataFile, "EXECUTABLE_PATH", FDGPath, sizeof( FDGPath ) ) ) +#endif { if ( !g_pFullFileSystem->RelativePathToFullPath( GameDataFile, NULL, FDGPath, sizeof( FDGPath ) ) ) { @@ -2024,7 +2133,11 @@ void CMapFile::CheckForInstances( const char *pszFileName ) char InstancePath[ MAX_PATH ]; bool bLoaded = false; +#ifdef MAPBASE + if ( DeterminePath( pszFileName, pInstanceFile, InstancePath ) || DeterminePath( g_MainMapPath, pInstanceFile, InstancePath ) ) +#else if ( DeterminePath( pszFileName, pInstanceFile, InstancePath ) ) +#endif { if ( LoadMapFile( InstancePath ) ) { @@ -2333,6 +2446,11 @@ void CMapFile::MergeEntities( entity_t *pInstanceEntity, CMapFile *Instance, Vec entity_t *WorldspawnEnt = NULL; GameData::TNameFixup FixupStyle; +#ifdef MAPBASE + // For fixing AI node problems with manifests and instances + int max_ai_node_id = 0; +#endif + char *pTargetName = ValueForKey( pInstanceEntity, "targetname" ); char *pName = ValueForKey( pInstanceEntity, "name" ); if ( pTargetName[ 0 ] ) @@ -2359,6 +2477,20 @@ void CMapFile::MergeEntities( entity_t *pInstanceEntity, CMapFile *Instance, Vec max_entity_id = value; } } + +#ifdef MAPBASE + // If this is a classname starting with "info_node", look for a node ID keyvalue and + // add it to the counter. + if ( strnicmp( ValueForKey( &entities[ i ], "classname" ), "info_node", 9 ) == 0 ) + { + int value = atoi( ValueForKey( &entities[i], "nodeid" ) ); + if ( value > max_ai_node_id ) + { + max_ai_node_id = value; + //Warning( "Max AI nodes is now %i", max_ai_node_id ); + } + } +#endif } FixupStyle = ( GameData::TNameFixup )( IntForKey( pInstanceEntity, "fixup_style" ) ); @@ -2403,6 +2535,11 @@ void CMapFile::MergeEntities( entity_t *pInstanceEntity, CMapFile *Instance, Vec GDclass *EntClass = GD.BeginInstanceRemap( pEntity, NameFixup, InstanceOrigin, InstanceAngle ); if ( EntClass ) { +#ifdef MAPBASE + // Sets up for additional instance remap fixes from Mapbase + GD.SetupInstanceRemapParams( max_ai_node_id, nummapbrushsides - Instance->nummapbrushsides, IntForKey( pInstanceEntity, "remap_vecline" ) > 0 ); +#endif + for( int i = 0; i < EntClass->GetVariableCount(); i++ ) { GDinputvariable *EntVar = EntClass->GetVariableAt( i ); @@ -2442,13 +2579,60 @@ void CMapFile::MergeEntities( entity_t *pInstanceEntity, CMapFile *Instance, Vec Q_snprintf( temp, sizeof( temp ), "%f", vOutNormal.z ); SetKeyValue( entity, "normal.z", temp );*/ } + +#ifdef MAPBASE + else if ( !strcmp( pEntity, "func_instance" ) ) + { + int iNumReplaces = 0; + for ( epair_t *epSubInstance = entity->epairs; epSubInstance != NULL; epSubInstance = epSubInstance->next ) + { + if ( strnicmp( epSubInstance->key, INSTANCE_VARIABLE_KEY, strlen( INSTANCE_VARIABLE_KEY ) ) == 0 ) + { + iNumReplaces++; + } + } + + // Merge this instance's keys + for ( epair_t *epInstance = pInstanceEntity->epairs; epInstance != NULL; epInstance = epInstance->next ) + { + if ( strnicmp( epInstance->key, INSTANCE_VARIABLE_KEY, strlen( INSTANCE_VARIABLE_KEY ) ) == 0 ) + { + iNumReplaces++; + char szKey[32]; + Q_snprintf(szKey, sizeof(szKey), "replace%i", iNumReplaces); + SetKeyValue( entity, szKey, epInstance->value ); + } + } + + // If the parent instance is within a relative path and no file relative to the main map exists, change it to be relative to the parent + char *pParentInstanceFile = ValueForKey( pInstanceEntity, "file" ); + if ( pParentInstanceFile[ 0 ] && (strchr( pParentInstanceFile, '\\' ) || strchr( pParentInstanceFile, '/' )) ) + { + char *pInstanceFile = ValueForKey( entity, "file" ); + if ( pInstanceFile[ 0 ] ) + { + char InstancePath[ MAX_PATH ]; + + if ( !DeterminePath( g_MainMapPath, pInstanceFile, InstancePath ) ) + { + strcpy( InstancePath, pParentInstanceFile ); + V_StripFilename( InstancePath ); + V_strncat( InstancePath, "\\", sizeof( InstancePath ) ); + V_strncat( InstancePath, pInstanceFile, sizeof( InstancePath ) ); + + SetKeyValue( entity, "file", InstancePath ); + } + } + } + } +#endif } #ifdef MERGE_INSTANCE_DEBUG_INFO Msg( "Instance Entity %d remapped to %d\n", i, num_entities + i ); Msg( " FirstBrush: from %d to %d\n", Instance->entities[ i ].firstbrush, entity->firstbrush ); Msg( " KV Pairs:\n" ); - for ( epair_t *ep = entity->epairs; ep->next != NULL; ep = ep->next ) + for ( epair_t *ep = entity->epairs; ep != NULL; ep = ep->next ) { Msg( " %s %s\n", ep->key, ep->value ); } @@ -2505,7 +2689,16 @@ void CMapFile::MergeEntities( entity_t *pInstanceEntity, CMapFile *Instance, Vec MoveBrushesToWorldGeneral( WorldspawnEnt ); WorldspawnEnt->numbrushes = 0; +#ifdef MAPBASE + char *pIsTopLevel = ValueForKey( pInstanceEntity, "toplevel" ); + if ( strcmp( pIsTopLevel, "1" ) == 0 ) + { + g_ManifestWorldSpawn->epairs = WorldspawnEnt->epairs; + } WorldspawnEnt->epairs = NULL; +#else + WorldspawnEnt->epairs = NULL; +#endif } @@ -2524,10 +2717,27 @@ void CMapFile::MergeOverlays( entity_t *pInstanceEntity, CMapFile *Instance, Vec for( int i = Instance->m_StartMapOverlays; i < g_aMapOverlays.Count(); i++ ) { Overlay_Translate( &g_aMapOverlays[ i ], InstanceOrigin, InstanceAngle, InstanceMatrix ); + +#ifdef MAPBASE + int iSides = (nummapbrushsides - Instance->nummapbrushsides); + for (int i2 = 0; i2 < g_aMapOverlays[i].aSideList.Count(); i2++) + { + //Warning( "Remapping overlay side %i to %i\n", g_aMapOverlays[i].aSideList[i2], g_aMapOverlays[i].aSideList[i2] + iSides ); + g_aMapOverlays[i].aSideList[i2] += iSides; + } +#endif } for( int i = Instance->m_StartMapWaterOverlays; i < g_aMapWaterOverlays.Count(); i++ ) { Overlay_Translate( &g_aMapWaterOverlays[ i ], InstanceOrigin, InstanceAngle, InstanceMatrix ); + +#ifdef MAPBASE + int iSides = (nummapbrushsides - Instance->nummapbrushsides); + for (int i2 = 0; i2 < g_aMapWaterOverlays[i].aSideList.Count(); i2++) + { + g_aMapWaterOverlays[i].aSideList[i2] += iSides; + } +#endif } } @@ -2582,6 +2792,9 @@ bool LoadMapFile( const char *pszFileName ) if ( g_MainMap == NULL ) { g_MainMap = g_LoadingMap; +#ifdef MAPBASE + V_ExtractFilePath( pszFileName, g_MainMapPath, sizeof( g_MainMapPath ) ); +#endif } if ( g_MainMap == g_LoadingMap || verbose ) @@ -2620,6 +2833,21 @@ bool LoadMapFile( const char *pszFileName ) if ((eResult == ChunkFile_Ok) || (eResult == ChunkFile_EOF)) { +#ifdef MAPBASE_VSCRIPT + if ( g_pScriptVM ) + { + if (CMapFile::g_Hook_OnMapLoaded.CanRunInScope( NULL )) + { + // Use GetLoadingMap() + //g_pScriptVM->SetValue( "map", g_LoadingMap->GetScriptInstance() ); + + CMapFile::g_Hook_OnMapLoaded.Call( NULL, NULL, NULL ); + + //g_pScriptVM->ClearValue( "map" ); + } + } +#endif + // Update the overlay/side list(s). Overlay_UpdateSideLists( g_LoadingMap->m_StartMapOverlays ); OverlayTransition_UpdateSideLists( g_LoadingMap->m_StartMapWaterOverlays ); @@ -2631,6 +2859,51 @@ bool LoadMapFile( const char *pszFileName ) pMainManifest->CordonWorld(); } +#ifdef PARALLAX_CORRECTED_CUBEMAPS + // Fill out parallax obb matrix array + // "i" is static so this code could account for + // multiple LoadMapFile() calls from instances, etc. + for (int i = 0; i < g_nCubemapSamples; i++) + { + if (g_pParallaxObbStrs[i][0] != '\0' && g_pParallaxObbsDone[i] == false) + { + //Warning( "Testing OBB string %s\n", g_pParallaxObbStrs[i] ); + + entity_t* obbEnt = NULL; + for (int i2 = 0; i2 < g_LoadingMap->num_entities; i2++) + { + if (stricmp( ValueForKey( &g_LoadingMap->entities[i2], "targetname" ), g_pParallaxObbStrs[i] ) != 0) + continue; + + obbEnt = &g_LoadingMap->entities[i2]; + g_pParallaxObbStrs[i] = ValueForKey(obbEnt, "transformationmatrix"); + //Warning( "Using OBB transformation matrix \"%s\"\n", g_pParallaxObbStrs[i] ); + g_pParallaxObbsDone[i] = true; + + break; + } + + if (!obbEnt) + { + Warning( "Cannot find parallax obb \"%s\" (num_entities is %i)\n", g_pParallaxObbStrs[i], g_LoadingMap->num_entities ); + //g_pParallaxObbStrs[i][0] = '\0'; + } + } + } + + // Remove parallax_obb entities (in a nice slow linear search) + for (int i = 0; i < g_LoadingMap->num_entities; i++) + { + entity_t* mapent = &g_LoadingMap->entities[i]; + const char *pClassName = ValueForKey( mapent, "classname" ); + if ( !strcmp( "parallax_obb", pClassName ) ) + { + mapent->numbrushes = 0; + mapent->epairs = NULL; + } + } +#endif + ClearBounds (g_LoadingMap->map_mins, g_LoadingMap->map_maxs); for (int i=0 ; ientities[0].numbrushes ; i++) { @@ -3129,6 +3402,133 @@ ChunkFileResult_t LoadSolidKeyCallback(const char *szKey, const char *szValue, m } +#ifdef MAPBASE_VSCRIPT +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +HSCRIPT CMapFile::GetScriptInstance() +{ + if (!m_hScriptInstance) + m_hScriptInstance = g_pScriptVM->RegisterInstance( this ); + + return m_hScriptInstance; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CMapFile::ScriptGetEntityKeyValues( int idx, HSCRIPT hKeyTable, HSCRIPT hValTable ) +{ + epair_t *curPair = entities[idx].epairs; + while (curPair) + { + g_pScriptVM->ArrayAppend( hKeyTable, curPair->key ); + g_pScriptVM->ArrayAppend( hValTable, curPair->value ); + + curPair = curPair->next; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int CMapFile::ScriptAddSimpleEntityKV( HSCRIPT hKV ) +{ + if (num_entities == MAX_MAP_ENTITIES) + { + // Exits. + g_MapError.ReportError ("num_entities == MAX_MAP_ENTITIES"); + return -1; + } + + entity_t *mapent = NULL; + if (::num_entities > 0) + { + // We're not loading maps anymore. Add this to the central BSP + mapent = &::entities[num_entities]; + ::num_entities++; + } + else + { + mapent = &entities[num_entities]; + num_entities++; + } + + memset(mapent, 0, sizeof(*mapent)); + mapent->firstbrush = nummapbrushes; + mapent->numbrushes = 0; + //mapent->portalareas[0] = -1; + //mapent->portalareas[1] = -1; + + LoadEntity_t LoadEntity; + LoadEntity.pEntity = mapent; + + // No default flags/contents + LoadEntity.nBaseFlags = 0; + LoadEntity.nBaseContents = 0; + + int nIterator = -1; + ScriptVariant_t varKey, varValue; + char szValue[256]; + while ((nIterator = g_pScriptVM->GetKeyValue( hKV, nIterator, &varKey, &varValue )) != -1) + { + switch (varValue.m_type) + { + case FIELD_CSTRING: Q_strncpy( szValue, varValue.m_pszString, sizeof(szValue) ); break; + case FIELD_INTEGER: Q_snprintf( szValue, sizeof(szValue), "%i", varValue.m_int ); break; + case FIELD_FLOAT: Q_snprintf( szValue, sizeof(szValue), "%f", varValue.m_float ); break; + case FIELD_CHARACTER: Q_snprintf( szValue, sizeof( szValue ), "%c", varValue.m_char ); break; + case FIELD_BOOLEAN: Q_snprintf( szValue, sizeof(szValue), "%d", varValue.m_bool ); break; + case FIELD_VECTOR: Q_snprintf( szValue, sizeof(szValue), "%f %f %f", (*varValue.m_pVector).x, (*varValue.m_pVector).y, (*varValue.m_pVector).z ); break; + default: szValue[0] = '\0'; break; + } + + LoadEntityKeyCallback( varKey, szValue, &LoadEntity ); + + g_pScriptVM->ReleaseValue( varKey ); + g_pScriptVM->ReleaseValue( varValue ); + } + + return num_entities - 1; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int CMapFile::ScriptAddInstance( const char *pszVMF, const Vector& vecOrigin, const QAngle& angAngles ) +{ + if (num_entities == MAX_MAP_ENTITIES) + { + // Exits. + g_MapError.ReportError ("num_entities == MAX_MAP_ENTITIES"); + return -1; + } + + entity_t *mapent = &entities[num_entities]; + num_entities++; + memset(mapent, 0, sizeof(*mapent)); + mapent->firstbrush = nummapbrushes; + mapent->numbrushes = 0; + //mapent->portalareas[0] = -1; + //mapent->portalareas[1] = -1; + + SetKeyValue( mapent, "classname", "func_instance" ); + + SetKeyValue( mapent, "file", pszVMF ); + SetKeyValue( mapent, "fixup_style", "2" ); // No fixup + + char szValue[256]; + Q_snprintf( szValue, sizeof(szValue), "%f %f %f", vecOrigin.x, vecOrigin.y, vecOrigin.z ); + SetKeyValue( mapent, "origin", szValue ); + + Q_snprintf( szValue, sizeof(szValue), "%f %f %f", angAngles.x, angAngles.y, angAngles.z ); + SetKeyValue( mapent, "angles", szValue ); + + return num_entities - 1; +} +#endif + + /* ================ TestExpandBrushes diff --git a/src/utils/vbsp/matrixinvert.h b/src/utils/vbsp/matrixinvert.h new file mode 100644 index 00000000..6e676ad3 --- /dev/null +++ b/src/utils/vbsp/matrixinvert.h @@ -0,0 +1,117 @@ +// By Jason Yu-Tseh Chi +// From http://chi3x10.wordpress.com/2008/05/28/calculate-matrix-inversion-in-c/ +// Modified to work with valve's matrix_3x4_t + +#include "mathlib\mathlib.h" + +// Calculate the cofactor of element (row,col) +int GetMatrixMinor(float **src, float **dest, int row, int col, int order) +{ + // Indicate which col and row is being copied to dest + int colCount = 0, rowCount = 0; + + for (int i = 0; i < order; i++) + { + if (i != row) + { + colCount = 0; + for (int j = 0; j < order; j++) + { + // When j is not the element + if (j != col) + { + dest[rowCount][colCount] = src[i][j]; + colCount++; + } + } + rowCount++; + } + } + + return 1; +} + +// Calculate the determinant recursively. +double CalcMatrixDeterminant(float **mat, int order) +{ + // Order must be >= 0 + // Stop the recursion when matrix is a single element + if (order == 1) + return mat[0][0]; + + // The determinant value + float det = 0; + + // Allocate the cofactor matrix + float **minor; + minor = new float*[order - 1]; + for (int i = 0; inodes[s] ); } } + +#ifdef MAPBASE + if (!noleaktest) + { + Warning( ("--- AREAPORTAL LEAK ---\n") ); + exit(0); + } +#endif } diff --git a/src/utils/vbsp/staticprop.cpp b/src/utils/vbsp/staticprop.cpp index 3ba775ea..ecef5011 100644 --- a/src/utils/vbsp/staticprop.cpp +++ b/src/utils/vbsp/staticprop.cpp @@ -104,6 +104,7 @@ isstaticprop_ret IsStaticProp( studiohdr_t* pHdr ) if (!(pHdr->flags & STUDIOHDR_FLAGS_STATIC_PROP)) return RET_FAIL_NOT_MARKED_STATIC_PROP; +#ifndef MAPBASE // If it's got a propdata section in the model's keyvalues, it's not allowed to be a prop_static KeyValues *modelKeyValues = new KeyValues(pHdr->pszName()); if ( StudioKeyValues( pHdr, modelKeyValues ) ) @@ -119,6 +120,7 @@ isstaticprop_ret IsStaticProp( studiohdr_t* pHdr ) } } modelKeyValues->deleteThis(); +#endif return RET_VALID; } @@ -569,6 +571,10 @@ static void SetLumpData( ) void EmitStaticProps() { +#ifdef MAPBASE + Msg("Placing static props...\n"); +#endif + CreateInterfaceFn physicsFactory = GetPhysicsFactory(); if ( physicsFactory ) { @@ -592,13 +598,43 @@ void EmitStaticProps() for ( i = 0; i < num_entities; ++i) { char* pEntity = ValueForKey(&entities[i], "classname"); +#ifdef MAPBASE + const int iInsertAsStatic = IntForKey( &entities[i], "insertasstaticprop" ); // If the key is absent, IntForKey will return 0. + bool bInsertAsStatic = g_bPropperInsertAllAsStatic; + + // 1 = No, 2 = Yes; Any other number will just use what g_bPropperInsertAllAsStatic is set as. + if ( iInsertAsStatic == 1 ) { bInsertAsStatic = false; } + else if ( iInsertAsStatic == 2 ) { bInsertAsStatic = true; } + + if ( !strcmp( pEntity, "static_prop" ) || !strcmp( pEntity, "prop_static" ) || ( !strcmp( pEntity, "propper_model" ) && bInsertAsStatic ) ) +#else if (!strcmp(pEntity, "static_prop") || !strcmp(pEntity, "prop_static")) +#endif { StaticPropBuild_t build; GetVectorForKey( &entities[i], "origin", build.m_Origin ); GetAnglesForKey( &entities[i], "angles", build.m_Angles ); +#ifdef MAPBASE + if ( !strcmp( pEntity, "propper_model" ) ) + { + char* pModelName = ValueForKey( &entities[i], "modelname" ); + + // The modelname keyvalue lacks 'models/' at the start and '.mdl' at the end, so we have to add them. + char modelpath[MAX_VALUE]; + sprintf( modelpath, "models/%s.mdl", pModelName ); + + Msg( "Inserting propper_model (%.0f %.0f %.0f) as prop_static: %s\n", build.m_Origin[0], build.m_Origin[1], build.m_Origin[2], modelpath ); + + build.m_pModelName = modelpath; + } + else // Otherwise we just assume it's a normal prop_static + { + build.m_pModelName = ValueForKey( &entities[i], "model" ); + } +#else build.m_pModelName = ValueForKey( &entities[i], "model" ); +#endif build.m_Solid = IntForKey( &entities[i], "solid" ); build.m_Skin = IntForKey( &entities[i], "skin" ); build.m_FadeMaxDist = FloatForKey( &entities[i], "fademaxdist" ); @@ -667,6 +703,13 @@ void EmitStaticProps() // strip this ent from the .bsp file entities[i].epairs = 0; } +#ifdef MAPBASE + else if ( g_bPropperStripEntities && !strncmp( pEntity, "propper_", 8 ) ) // Strip out any entities with 'propper_' in their classname, as they don't actually exist in-game. + { + Warning( "Not including %s in BSP compile due to it being a propper entity that isn't used in-game.\n", pEntity ); + entities[i].epairs = 0; + } +#endif } // Strip out lighting origins; has to be done here because they are used when diff --git a/src/utils/vbsp/textures.cpp b/src/utils/vbsp/textures.cpp index 4f49c5d4..3a7cffde 100644 --- a/src/utils/vbsp/textures.cpp +++ b/src/utils/vbsp/textures.cpp @@ -153,6 +153,13 @@ int FindMiptex (const char *name) { textureref[i].flags |= SURF_NOLIGHT; } +#ifdef MAPBASE + // handle Slammin-inspired %compileNoShadows% + else if ( ( propVal = GetMaterialVar( matID, "%compileNoShadows" ) ) && StringIsTrue( propVal ) ) + { + textureref[i].flags |= SURF_NOSHADOWS; + } +#endif else { // HANDLE ALL OF THE STUFF THAT IS RENDERED WITH THE MATERIAL THAT IS ON IT. diff --git a/src/utils/vbsp/vbsp.cpp b/src/utils/vbsp/vbsp.cpp index 7b8c0593..f47eb1e0 100644 --- a/src/utils/vbsp/vbsp.cpp +++ b/src/utils/vbsp/vbsp.cpp @@ -20,6 +20,11 @@ #include "byteswap.h" #include "worldvertextransitionfixup.h" +#ifdef MAPBASE_VSCRIPT +#include "vscript/ivscript.h" +#include "vscript_vbsp.h" +#endif + extern float g_maxLightmapDimension; char source[1024]; @@ -43,7 +48,11 @@ qboolean noshare; qboolean nosubdiv; qboolean notjunc; qboolean noopt; +#ifdef MAPBASE +qboolean noleaktest; +#else qboolean leaktest; +#endif qboolean verboseentities; qboolean dumpcollide = false; qboolean g_bLowPriority = false; @@ -56,6 +65,16 @@ bool g_NodrawTriggers = false; bool g_DisableWaterLighting = false; bool g_bAllowDetailCracks = false; bool g_bNoVirtualMesh = false; +#ifdef MAPBASE +bool g_bNoDefaultCubemaps = true; +bool g_bSkyboxCubemaps = false; +bool g_bPropperInsertAllAsStatic = false; +bool g_bPropperStripEntities = false; +int g_iDefaultCubemapSize = 32; +#endif +#ifdef MAPBASE_VSCRIPT +ScriptLanguage_t g_iScripting = SL_NONE; +#endif float g_defaultLuxelSize = DEFAULT_LUXEL_SIZE; float g_luxelScale = 1.0f; @@ -296,7 +315,11 @@ void ProcessWorldModel (void) Warning( ("**** leaked ****\n") ); leaked = true; LeakFile (tree); +#ifdef MAPBASE + if (!noleaktest) +#else if (leaktest) +#endif { Warning( ("--- MAP LEAKED ---\n") ); exit (0); @@ -670,6 +693,7 @@ void SetOccluderArea( int nOccluder, int nArea, int nEntityNum ) { g_OccluderData[nOccluder].area = nArea; } +#ifndef MAPBASE else if ( (nArea != 0) && (g_OccluderData[nOccluder].area != nArea) ) { const char *pTargetName = ValueForKey( &entities[nEntityNum], "targetname" ); @@ -679,6 +703,7 @@ void SetOccluderArea( int nOccluder, int nArea, int nEntityNum ) } Warning("Occluder \"%s\" straddles multiple areas. This is invalid!\n", pTargetName ); } +#endif } @@ -859,7 +884,12 @@ void ProcessModels (void) } // Turn the skybox into a cubemap in case we don't build env_cubemap textures. +#ifdef MAPBASE + if (!g_bNoDefaultCubemaps) + Cubemap_CreateDefaultCubemaps(); +#else Cubemap_CreateDefaultCubemaps(); +#endif EndBSPFile (); } @@ -1000,11 +1030,19 @@ int RunVBSP( int argc, char **argv ) Msg ("microvolume = %f\n", microvolume); i++; } +#ifdef MAPBASE + else if (!Q_stricmp(argv[i], "-noleaktest")) + { + Msg ("noleaktest = true\n"); + noleaktest = true; + } +#else else if (!Q_stricmp(argv[i], "-leaktest")) { Msg ("leaktest = true\n"); leaktest = true; } +#endif else if (!Q_stricmp(argv[i], "-verboseentities")) { Msg ("verboseentities = true\n"); @@ -1150,6 +1188,103 @@ int RunVBSP( int argc, char **argv ) g_pFullFileSystem->AddSearchPath( g_szEmbedDir, "GAME", PATH_ADD_TO_TAIL ); g_pFullFileSystem->AddSearchPath( g_szEmbedDir, "MOD", PATH_ADD_TO_TAIL ); } +#ifdef MAPBASE + // Thanks to Mapbase's shader changes, default all-black cubemaps are no longer needed. + // The command has been switched from "-nodefaultcubemap" to "-defaultcubemap", + // meaning maps are compiled without them by default. + else if ( !Q_stricmp( argv[i], "-defaultcubemap" ) ) + { + g_bNoDefaultCubemaps = false; + } + // Default cubemaps are supposed to show the sky texture, but Valve disabled this + // because they didn't get it working for HDR cubemaps. As a result, all default + // cubemaps appear as all-black textures. However, this parameter has been added to + // re-enable skybox cubemaps for LDR cubemaps. (HDR skybox cubemaps are not supported) + else if ( !Q_stricmp( argv[i], "-skyboxcubemap" ) ) + { + g_bNoDefaultCubemaps = false; + g_bSkyboxCubemaps = true; + } + else if ( !Q_stricmp( argv[i], "-defaultcubemapres" ) ) + { + g_iDefaultCubemapSize = atoi( argv[i + 1] ); + Msg( "Default cubemap size = %i\n", g_iDefaultCubemapSize ); + i++; + } + else if ( !Q_stricmp( argv[i], "-defaultproppermodelsstatic" ) ) + { + g_bPropperInsertAllAsStatic = true; + } + else if ( !Q_stricmp( argv[i], "-strippropperentities" ) ) + { + g_bPropperStripEntities = true; + } +#endif +#ifdef MAPBASE_VSCRIPT + else if ( !Q_stricmp( argv[i], "-scripting" ) ) + { + const char *pszScriptLanguage = argv[i + 1]; + if( pszScriptLanguage[0] == '-') + { + // It's another command. Just use default + g_iScripting = SL_DEFAULT; + } + else + { + // Use a specific language + if( !Q_stricmp(pszScriptLanguage, "gamemonkey") ) + { + g_iScripting = SL_GAMEMONKEY; + } + else if( !Q_stricmp(pszScriptLanguage, "squirrel") ) + { + g_iScripting = SL_SQUIRREL; + } + else if( !Q_stricmp(pszScriptLanguage, "python") ) + { + g_iScripting = SL_PYTHON; + } + else if( !Q_stricmp(pszScriptLanguage, "lua") ) + { + g_iScripting = SL_LUA; + } + else + { + DevWarning("-server_script does not recognize a language named '%s'. virtual machine did NOT start.\n", pszScriptLanguage ); + g_iScripting = SL_NONE; + } + i++; + } + } + else if ( !Q_stricmp( argv[i], "-doc" ) ) + { + // Only print the documentation + + if (g_iScripting) + { + scriptmanager = (IScriptManager*)Sys_GetFactoryThis()(VSCRIPT_INTERFACE_VERSION, NULL); + VScriptVBSPInit(); + + const char *pszArg1 = argv[i + 1]; + if (pszArg1[0] == '-') + { + // It's another command. Just use * + pszArg1 = "*"; + } + + char szCommand[512]; + _snprintf( szCommand, sizeof( szCommand ), "__Documentation.PrintHelp( \"%s\" );", pszArg1 ); + g_pScriptVM->Run( szCommand ); + } + else + { + Warning("Cannot print documentation without scripting enabled!\n"); + } + + DeleteCmdLine( argc, argv ); + CmdLib_Cleanup(); + CmdLib_Exit( 1 ); + } else if (argv[i][0] == '-') { Warning("VBSP: Unknown option \"%s\"\n\n", argv[i]); @@ -1236,6 +1371,7 @@ int RunVBSP( int argc, char **argv ) " -nox360 : Disable generation Xbox360 version of vsp (default)\n" " -replacematerials : Substitute materials according to materialsub.txt in content\\maps\n" " -FullMinidumps : Write large minidumps on crash.\n" + " -nohiddenmaps : Exclude manifest maps if they are currently hidden.\n" ); } @@ -1297,6 +1433,15 @@ int RunVBSP( int argc, char **argv ) InitMaterialSystem( materialPath, CmdLib_GetFileSystemFactory() ); Msg( "materialPath: %s\n", materialPath ); +#ifdef MAPBASE_VSCRIPT + if (g_iScripting) + { + scriptmanager = (IScriptManager*)Sys_GetFactoryThis()(VSCRIPT_INTERFACE_VERSION, NULL); + + VScriptVBSPInit(); + } +#endif + // delete portal and line files sprintf (path, "%s.prt", source); remove (path); @@ -1423,6 +1568,9 @@ int RunVBSP( int argc, char **argv ) ReleasePakFileLumps(); DeleteMaterialReplacementKeys(); ShutdownMaterialSystem(); +#ifdef MAPBASE_VSCRIPT + VScriptVBSPTerm(); +#endif CmdLib_Cleanup(); return 0; } diff --git a/src/utils/vbsp/vbsp.h b/src/utils/vbsp/vbsp.h index 689bbaa8..9147876a 100644 --- a/src/utils/vbsp/vbsp.h +++ b/src/utils/vbsp/vbsp.h @@ -19,6 +19,9 @@ #include "qfiles.h" #include "utilmatlib.h" #include "ChunkFile.h" +#ifdef MAPBASE_VSCRIPT +#include "vscript/ivscript.h" +#endif #ifdef WIN32 #pragma warning( disable: 4706 ) @@ -34,6 +37,12 @@ class CUtlBuffer; // this will output glview files for the given brushmodel. Brushmodel 1 is the world, 2 is the first brush entity, etc. #define DEBUG_BRUSHMODEL 0 +#ifdef MAPBASE +// Activates compiler code for parallax corrected cubemaps +// https://developer.valvesoftware.com/wiki/Parallax_Corrected_Cubemaps +#define PARALLAX_CORRECTED_CUBEMAPS 1 +#endif + struct portal_t; struct node_t; @@ -334,6 +343,34 @@ public: int m_StartMapOverlays; int m_StartMapWaterOverlays; + +#ifdef MAPBASE_VSCRIPT + HSCRIPT GetScriptInstance(); + + static ScriptHook_t g_Hook_OnMapLoaded; + + // VScript functions + ALLOW_SCRIPT_ACCESS(); +private: + + const Vector& GetMins() { return map_mins; } + const Vector& GetMaxs() { return map_maxs; } + + int GetNumMapBrushes() { return nummapbrushes; } + + const Vector& GetEntityOrigin(int idx) { return (idx < num_entities && idx >= 0) ? entities[idx].origin : vec3_origin; } + int GetEntityFirstBrush(int idx) { return (idx < num_entities && idx >= 0) ? entities[idx].firstbrush : 0; } + int GetEntityNumBrushes(int idx) { return (idx < num_entities && idx >= 0) ? entities[idx].numbrushes : 0; } + + void ScriptGetEntityKeyValues(int idx, HSCRIPT hKeyTable, HSCRIPT hValTable); + + int ScriptAddSimpleEntityKV(HSCRIPT hKV/*, const Vector& vecOrigin, int iFirstBrush, int iNumBrushes*/); + int ScriptAddInstance(const char *pszVMF, const Vector& vecOrigin, const QAngle& angAngles); + + int GetNumEntities() { return num_entities; } + + HSCRIPT m_hScriptInstance; +#endif }; extern CMapFile *g_MainMap; @@ -365,6 +402,11 @@ extern bool g_NodrawTriggers; extern bool g_DisableWaterLighting; extern bool g_bAllowDetailCracks; extern bool g_bNoVirtualMesh; +extern bool g_bNoHiddenManifestMaps; +#ifdef MAPBASE +extern bool g_bPropperInsertAllAsStatic; +extern bool g_bPropperStripEntities; +#endif extern char outbase[32]; extern char source[1024]; @@ -607,7 +649,12 @@ void SaveVertexNormals( void ); //============================================================================= // cubemap.cpp +#ifdef PARALLAX_CORRECTED_CUBEMAPS +extern char* g_pParallaxObbStrs[MAX_MAP_CUBEMAPSAMPLES]; +void Cubemap_InsertSample( const Vector& origin, int size, char* pParallaxObbStr ); +#else void Cubemap_InsertSample( const Vector& origin, int size ); +#endif void Cubemap_CreateDefaultCubemaps( void ); void Cubemap_SaveBrushSides( const char *pSideListStr ); void Cubemap_FixupBrushSidesMaterials( void ); diff --git a/src/utils/vbsp/vbsp.vpc b/src/utils/vbsp/vbsp.vpc index d76a844a..3cbdd4fc 100644 --- a/src/utils/vbsp/vbsp.vpc +++ b/src/utils/vbsp/vbsp.vpc @@ -6,6 +6,7 @@ $Macro SRCDIR "..\.." $Macro OUTBINDIR "$SRCDIR\..\game\bin" +$Macro OUTBINNAME "vbsp" $Include "$SRCDIR\vpc_scripts\source_exe_con_base.vpc" @@ -15,6 +16,8 @@ $Configuration { $AdditionalIncludeDirectories "$BASE,..\common,..\vmpi" $PreprocessorDefinitions "$BASE;MACRO_MATHLIB;PROTECTED_THINGS_DISABLE" + + $PreprocessorDefinitions "$BASE;MAPBASE_VSCRIPT" [$MAPBASE_VSCRIPT] } $Linker @@ -66,6 +69,14 @@ $Project "VBSP" $File "worldvertextransitionfixup.cpp" $File "writebsp.cpp" $File "$SRCDIR\public\zip_utils.cpp" + + $File "vscript_vbsp.cpp" [$MAPBASE_VSCRIPT] + $File "vscript_vbsp.h" [$MAPBASE_VSCRIPT] + $File "vscript_vbsp.nut" [$MAPBASE_VSCRIPT] + $File "vscript_funcs_vmfs.cpp" [$MAPBASE_VSCRIPT] + $File "vscript_funcs_vmfs.h" [$MAPBASE_VSCRIPT] + $File "vscript_funcs_vis.cpp" [$MAPBASE_VSCRIPT] + $File "vscript_funcs_vis.h" [$MAPBASE_VSCRIPT] $Folder "Common Files" { @@ -180,6 +191,7 @@ $Project "VBSP" $Lib tier2 $Lib vtf $Lib "$LIBCOMMON/lzma" + $Lib vscript [$MAPBASE_VSCRIPT] } $File "notes.txt" diff --git a/src/utils/vbsp/vscript_funcs_vis.cpp b/src/utils/vbsp/vscript_funcs_vis.cpp new file mode 100644 index 00000000..7a5fbfb6 --- /dev/null +++ b/src/utils/vbsp/vscript_funcs_vis.cpp @@ -0,0 +1,29 @@ +//========= Mapbase - https://github.com/mapbase-source/source-sdk-2013 ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "tier1/KeyValues.h" +#include "tier1/fmtstr.h" + +#include "vbsp.h" +#include "map.h" +#include "fgdlib/fgdlib.h" + +#include "vscript_vbsp.h" +#include "vscript_funcs_vis.h" + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +// There are currently no vis-related functions, but it would be nice to have them in the future. + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +void RegisterVisScriptFunctions() +{ + //ScriptRegisterFunction( g_pScriptVM, VMFKV_CreateBlank, "Creates a CScriptKeyValues instance with VMF formatting." ); +} diff --git a/src/utils/vbsp/vscript_funcs_vis.h b/src/utils/vbsp/vscript_funcs_vis.h new file mode 100644 index 00000000..906b7d7a --- /dev/null +++ b/src/utils/vbsp/vscript_funcs_vis.h @@ -0,0 +1,16 @@ +//========= Mapbase - https://github.com/mapbase-source/source-sdk-2013 ================= +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef VSCRIPT_FUNCS_VIS +#define VSCRIPT_FUNCS_VIS +#ifdef _WIN32 +#pragma once +#endif + +void RegisterVisScriptFunctions(); + +#endif diff --git a/src/utils/vbsp/vscript_funcs_vmfs.cpp b/src/utils/vbsp/vscript_funcs_vmfs.cpp new file mode 100644 index 00000000..8ef38c81 --- /dev/null +++ b/src/utils/vbsp/vscript_funcs_vmfs.cpp @@ -0,0 +1,157 @@ +//========= Mapbase - https://github.com/mapbase-source/source-sdk-2013 ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "tier1/KeyValues.h" +#include "tier1/fmtstr.h" + +#include "vbsp.h" +#include "map.h" +#include "fgdlib/fgdlib.h" + +#include "vscript_vbsp.h" +#include "vscript_funcs_vmfs.h" + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +static HSCRIPT VMFKV_CreateBlank() +{ + KeyValues *pKV = new KeyValues("VMF"); + + KeyValues *pWorld = pKV->FindKey( "world", true ); + if (pWorld) + { + pWorld->SetString( "classname", "worldspawn" ); + } + + return scriptmanager->CreateScriptKeyValues( g_pScriptVM, pKV, true ); +} + +static bool VMFKV_SaveToFile( const char *szFile, HSCRIPT hKV ) +{ + Warning( "Getting keyvalues from thing\n" ); + + KeyValues *pKV = scriptmanager->GetKeyValuesFromScriptKV( g_pScriptVM, hKV ); + if (!pKV) + return false; + + CUtlBuffer buf( 0, 0, CUtlBuffer::TEXT_BUFFER ); + for (KeyValues *pSubKey = pKV->GetFirstSubKey(); pSubKey; pSubKey = pSubKey->GetNextKey()) + { + pSubKey->RecursiveSaveToFile( buf, 0 ); + } + + char pszFullName[MAX_PATH]; + Q_ExtractFilePath( source, pszFullName, sizeof(pszFullName) ); + V_snprintf( pszFullName, sizeof(pszFullName), "%s/vscript_io/%s", pszFullName, szFile ); + + if ( !V_RemoveDotSlashes( pszFullName, CORRECT_PATH_SEPARATOR, true ) ) + { + Warning( "Invalid file location : %s\n", szFile ); + buf.Purge(); + return false; + } + + int nSize = V_strlen(pszFullName) + 1; + char *pszDir = (char*)stackalloc(nSize); + V_memcpy( pszDir, pszFullName, nSize ); + V_StripFilename( pszDir ); + + //g_pFullFileSystem->RelativePathToFullPath( szFile, NULL, pszFullName, sizeof( pszFullName ) ); + Warning( "Full path is %s!\n", pszFullName ); + g_pFullFileSystem->CreateDirHierarchy( pszDir, NULL ); + bool res = g_pFullFileSystem->WriteFile( pszFullName, NULL, buf ); + buf.Purge(); + return res; +} + +static HSCRIPT VMFKV_LoadFromFile( const char *szFile ) +{ + char pszFullName[MAX_PATH]; + V_snprintf( pszFullName, sizeof(pszFullName), NULL, szFile ); + + if ( !V_RemoveDotSlashes( pszFullName, CORRECT_PATH_SEPARATOR, true ) ) + { + DevWarning( 2, "Invalid file location : %s\n", szFile ); + return NULL; + } + + KeyValues *pKV = new KeyValues( szFile ); + if ( !pKV->LoadFromFile( g_pFullFileSystem, pszFullName, NULL ) ) + { + pKV->deleteThis(); + return NULL; + } + + HSCRIPT hScript = scriptmanager->CreateScriptKeyValues( g_pScriptVM, pKV, true ); // bAllowDestruct is supposed to automatically remove the involved KV + + return hScript; +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +inline void ScriptVarsToKV( ScriptVariant_t &varKey, ScriptVariant_t &varValue, KeyValues *pKV ) +{ + switch (varValue.m_type) + { + case FIELD_CSTRING: pKV->SetString( varKey.m_pszString, varValue.m_pszString ); break; + case FIELD_INTEGER: pKV->SetInt( varKey.m_pszString, varValue.m_int ); break; + case FIELD_FLOAT: pKV->SetFloat( varKey.m_pszString, varValue.m_float ); break; + case FIELD_BOOLEAN: pKV->SetBool( varKey.m_pszString, varValue.m_bool ); break; + case FIELD_VECTOR: pKV->SetString( varKey.m_pszString, CFmtStr( "%f %f %f", varValue.m_pVector->x, varValue.m_pVector->y, varValue.m_pVector->z ) ); break; + } +} + +static HSCRIPT VMFKV_AddEntityFromTables( HSCRIPT hVMF, HSCRIPT hKV, HSCRIPT hIO ) +{ + KeyValues *pVMF = scriptmanager->GetKeyValuesFromScriptKV( g_pScriptVM, hVMF ); + if (!pVMF) + return false; + + KeyValues *pEnt = pVMF->CreateNewKey(); + if (!pEnt) + return false; + + pEnt->SetName( "entity" ); + + int nIterator = -1; + ScriptVariant_t varKey, varValue; + while ((nIterator = g_pScriptVM->GetKeyValue( hKV, nIterator, &varKey, &varValue )) != -1) + { + ScriptVarsToKV( varKey, varValue, pEnt ); + + g_pScriptVM->ReleaseValue( varKey ); + g_pScriptVM->ReleaseValue( varValue ); + } + + KeyValues *pConnections = pEnt->FindKey( "connections", true ); + if (hIO && pConnections) + { + nIterator = -1; + while ((nIterator = g_pScriptVM->GetKeyValue( hIO, nIterator, &varKey, &varValue )) != -1) + { + ScriptVarsToKV( varKey, varValue, pEnt ); + + g_pScriptVM->ReleaseValue( varKey ); + g_pScriptVM->ReleaseValue( varValue ); + } + } + + return scriptmanager->CreateScriptKeyValues( g_pScriptVM, pEnt, false ); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +void RegisterVMFScriptFunctions() +{ + ScriptRegisterFunction( g_pScriptVM, VMFKV_CreateBlank, "Creates a CScriptKeyValues instance with VMF formatting." ); + ScriptRegisterFunction( g_pScriptVM, VMFKV_SaveToFile, "Saves a CScriptKeyValues instance with VMF formatting." ); + ScriptRegisterFunction( g_pScriptVM, VMFKV_LoadFromFile, "Loads a VMF as a CScriptKeyValues instance with VMF formatting." ); + ScriptRegisterFunction( g_pScriptVM, VMFKV_AddEntityFromTables, "Adds a VMF-formatted entity to a CScriptKeyValues instance." ); +} diff --git a/src/utils/vbsp/vscript_funcs_vmfs.h b/src/utils/vbsp/vscript_funcs_vmfs.h new file mode 100644 index 00000000..0b3237a7 --- /dev/null +++ b/src/utils/vbsp/vscript_funcs_vmfs.h @@ -0,0 +1,16 @@ +//========= Mapbase - https://github.com/mapbase-source/source-sdk-2013 ================= +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef VSCRIPT_FUNCS_VMFS +#define VSCRIPT_FUNCS_VMFS +#ifdef _WIN32 +#pragma once +#endif + +void RegisterVMFScriptFunctions(); + +#endif diff --git a/src/utils/vbsp/vscript_vbsp.cpp b/src/utils/vbsp/vscript_vbsp.cpp new file mode 100644 index 00000000..bcb94845 --- /dev/null +++ b/src/utils/vbsp/vscript_vbsp.cpp @@ -0,0 +1,369 @@ +//========= Mapbase - https://github.com/mapbase-source/source-sdk-2013 ============// +// +// Purpose: Mapbase's implementation of VScript in VBSP, allowing users to modify map compilation behavior with scripts. +// +// $NoKeywords: $ +//=============================================================================// + +#include "tier1/KeyValues.h" +#include "tier1/fmtstr.h" + +#include "vbsp.h" +#include "map.h" +#include "fgdlib/fgdlib.h" +#include "convar.h" + +#include "vscript_vbsp.h" +#include "vscript_vbsp.nut" +#include "vscript_funcs_vmfs.h" +#include "vscript_funcs_vis.h" + +IScriptVM *g_pScriptVM; +IScriptManager *scriptmanager = NULL; + +extern ScriptLanguage_t g_iScripting; + +extern ScriptClassDesc_t * GetScriptDesc( CBaseEntity * ); + +// #define VMPROFILE 1 + +#ifdef VMPROFILE + +#define VMPROF_START float debugStartTime = Plat_FloatTime(); +#define VMPROF_SHOW( funcname, funcdesc ) DevMsg("***VSCRIPT PROFILE***: %s %s: %6.4f milliseconds\n", (##funcname), (##funcdesc), (Plat_FloatTime() - debugStartTime)*1000.0 ); + +#else // !VMPROFILE + +#define VMPROF_START +#define VMPROF_SHOW + +#endif // VMPROFILE + +// This is to ensure a dependency exists between the vscript library and the game DLLs +extern int vscript_token; +int vscript_token_hack = vscript_token; + +// HACKHACK: VScript library relies on developer convar existing +ConVar developer( "developer", "1", 0, "Set developer message level." ); // developer mode + +HSCRIPT VScriptCompileScript( const char *pszScriptName, bool bWarnMissing ) +{ + if ( !g_pScriptVM ) + { + return NULL; + } + + static const char *pszExtensions[] = + { + "", // SL_NONE + ".gm", // SL_GAMEMONKEY + ".nut", // SL_SQUIRREL + ".lua", // SL_LUA + ".py", // SL_PYTHON + }; + + const char *pszVMExtension = pszExtensions[g_pScriptVM->GetLanguage()]; + const char *pszIncomingExtension = V_strrchr( pszScriptName , '.' ); + if ( pszIncomingExtension && V_strcmp( pszIncomingExtension, pszVMExtension ) != 0 ) + { + Warning( "Script file type does not match VM type\n" ); + return NULL; + } + + CFmtStr scriptPath; + if ( pszIncomingExtension ) + { + scriptPath = pszScriptName; + } + else + { + scriptPath.sprintf( "%s%s", pszScriptName, pszVMExtension ); + } + + const char *pBase; + CUtlBuffer bufferScript; + + if ( g_pScriptVM->GetLanguage() == SL_PYTHON ) + { + // python auto-loads raw or precompiled modules - don't load data here + pBase = NULL; + } + else + { + /* + FileFindHandle_t handle = NULL; + const char *file = g_pFullFileSystem->FindFirst( "*", &handle ); + while (file) + { + Msg( "File in this directory: %s\n", file ); + file = g_pFullFileSystem->FindNext(handle); + } + + Msg( "File exists: %d\n", g_pFullFileSystem->FileExists( scriptPath ) ); + */ + + + bool bResult = g_pFullFileSystem->ReadFile( scriptPath, NULL, bufferScript ); + + if ( !bResult && bWarnMissing ) + { + Warning( "Script not found (%s) \n", scriptPath.operator const char *() ); + Assert( "Error running script" ); + } + + pBase = (const char *) bufferScript.Base(); + + if ( !pBase || !*pBase ) + { + return NULL; + } + } + + + const char *pszFilename = V_strrchr( scriptPath, '\\' ); + pszFilename++; + HSCRIPT hScript = g_pScriptVM->CompileScript( pBase, pszFilename ); + if ( !hScript ) + { + Warning( "FAILED to compile and execute script file named %s\n", scriptPath.operator const char *() ); + Assert( "Error running script" ); + } + return hScript; +} + +static int g_ScriptVBSPRunScriptDepth; + +bool VScriptRunScript( const char *pszScriptName, HSCRIPT hScope, bool bWarnMissing ) +{ + if ( !g_pScriptVM ) + { + return false; + } + + if ( !pszScriptName || !*pszScriptName ) + { + Warning( "Cannot run script: NULL script name\n" ); + return false; + } + + // Prevent infinite recursion in VM + if ( g_ScriptVBSPRunScriptDepth > 16 ) + { + Warning( "IncludeScript stack overflow\n" ); + return false; + } + + g_ScriptVBSPRunScriptDepth++; + HSCRIPT hScript = VScriptCompileScript( pszScriptName, bWarnMissing ); + bool bSuccess = false; + if ( hScript ) + { + bSuccess = ( g_pScriptVM->Run( hScript, hScope ) != SCRIPT_ERROR ); + if ( !bSuccess ) + { + Warning( "Error running script named %s\n", pszScriptName ); + Assert( "Error running script" ); + } + } + g_ScriptVBSPRunScriptDepth--; + return bSuccess; +} + +ScriptHook_t CMapFile::g_Hook_OnMapLoaded; + +BEGIN_SCRIPTDESC_ROOT( CMapFile, "Map file" ) + + DEFINE_SCRIPTFUNC( GetMins, "Get the map's mins." ) + DEFINE_SCRIPTFUNC( GetMaxs, "Get the map's maxs." ) + + DEFINE_SCRIPTFUNC( GetEntityOrigin, "Get the origin of the entity with the specified index." ) + DEFINE_SCRIPTFUNC( GetEntityFirstBrush, "Get the first brush ID of the entity with the specified index." ) + DEFINE_SCRIPTFUNC( GetEntityNumBrushes, "Get the number of brushes in the entity with the specified index." ) + + DEFINE_SCRIPTFUNC_NAMED( ScriptGetEntityKeyValues, "GetEntityKeyValues", "Export an entity's keyvalues to two arrays." ) + + DEFINE_SCRIPTFUNC_NAMED( ScriptAddSimpleEntityKV, "AddSimpleEntityKV", "Add a simple entity from a keyvalue table." ) + DEFINE_SCRIPTFUNC_NAMED( ScriptAddInstance, "AddInstance", "Add an instance to the map." ) + + DEFINE_SCRIPTFUNC( GetNumEntities, "Get the number of entities in the map." ) + + // + // Hooks + // + DEFINE_SIMPLE_SCRIPTHOOK( CMapFile::g_Hook_OnMapLoaded, "OnMapLoaded", FIELD_VOID, "Called when the NPC is deciding whether to hear a CSound or not." ) + +END_SCRIPTDESC(); + + +static float cvar_getf( const char* sz ) +{ + ConVarRef cvar(sz); + if ( cvar.IsFlagSet( FCVAR_SERVER_CANNOT_QUERY ) ) + return NULL; + return cvar.GetFloat(); +} + +static bool cvar_setf( const char* sz, float val ) +{ + ConVarRef cvar(sz); + if ( !cvar.IsValid() ) + return false; + + if ( cvar.IsFlagSet( FCVAR_SERVER_CANNOT_QUERY ) ) + return false; + + cvar.SetValue(val); + return true; +} + +static const char *GetSource() +{ + return source; +} + +static const char *GetMapBase() +{ + return mapbase; +} + +static HSCRIPT GetMainMap() +{ + return g_MainMap ? g_MainMap->GetScriptInstance() : NULL; +} + +static HSCRIPT GetLoadingMap() +{ + return g_LoadingMap ? g_LoadingMap->GetScriptInstance() : NULL; +} + +static const char *DoUniqueString( const char *pszBase ) +{ + static char szBuf[512]; + g_pScriptVM->GenerateUniqueKey( pszBase, szBuf, ARRAYSIZE(szBuf) ); + return szBuf; +} + +bool DoIncludeScript( const char *pszScript, HSCRIPT hScope ) +{ + if ( !VScriptRunScript( pszScript, hScope, true ) ) + { + g_pScriptVM->RaiseException( CFmtStr( "Failed to include script \"%s\"", ( pszScript ) ? pszScript : "unknown" ) ); + return false; + } + return true; +} + +extern qboolean glview; +extern qboolean onlyents; +extern bool onlyprops; +extern qboolean noleaktest; +extern qboolean verboseentities; +extern qboolean g_bLowPriority; +extern bool g_bKeepStaleZip; +extern bool g_bNoDefaultCubemaps; +extern bool g_bSkyboxCubemaps; +extern int g_iDefaultCubemapSize; + +bool VScriptVBSPInit() +{ + VMPROF_START + + if( g_iScripting != SL_NONE && scriptmanager != NULL ) + { + if ( g_pScriptVM == NULL ) + g_pScriptVM = scriptmanager->CreateVM( g_iScripting ); + + if( g_pScriptVM ) + { + Log( "VSCRIPT VBSP: Started VScript virtual machine using script language '%s'\n", g_pScriptVM->GetLanguageName() ); + + ScriptRegisterFunction( g_pScriptVM, cvar_getf, "Gets the value of the given cvar, as a float." ); + ScriptRegisterFunction( g_pScriptVM, cvar_setf, "Sets the value of the given cvar, as a float." ); + + ScriptRegisterFunction( g_pScriptVM, GetSource, "Gets the base directory of the first map loaded." ); + ScriptRegisterFunction( g_pScriptVM, GetMapBase, "Gets the base name of the first map loaded." ); + ScriptRegisterFunction( g_pScriptVM, GetMainMap, "Gets the first map loaded." ); + ScriptRegisterFunction( g_pScriptVM, GetLoadingMap, "Gets the map which is currently loading (e.g. an instance)." ); + + ScriptRegisterFunction( g_pScriptVM, DoUniqueString, SCRIPT_ALIAS( "UniqueString", "Generate a string guaranteed to be unique across the life of the script VM, with an optional root string. Useful for adding data to tables when not sure what keys are already in use in that table." ) ); + ScriptRegisterFunction( g_pScriptVM, DoIncludeScript, "Execute a script (internal)" ); + + ScriptRegisterConstantNamed( g_pScriptVM, GameData::NAME_FIXUP_PREFIX, "NAME_FIXUP_PREFIX", "Prefix name fixup" ); + ScriptRegisterConstantNamed( g_pScriptVM, GameData::NAME_FIXUP_POSTFIX, "NAME_FIXUP_PREFIX", "Postfix name fixup" ); + ScriptRegisterConstantNamed( g_pScriptVM, GameData::NAME_FIXUP_NONE, "NAME_FIXUP_NONE", "No name fixup" ); + + ScriptRegisterConstant( g_pScriptVM, microvolume, "" ); + ScriptRegisterConstant( g_pScriptVM, noprune, "" ); + ScriptRegisterConstant( g_pScriptVM, glview, "" ); + ScriptRegisterConstant( g_pScriptVM, nodetail, "" ); + ScriptRegisterConstant( g_pScriptVM, fulldetail, "" ); + ScriptRegisterConstant( g_pScriptVM, onlyents, "" ); + ScriptRegisterConstant( g_pScriptVM, onlyprops, "" ); + ScriptRegisterConstant( g_pScriptVM, nomerge, "" ); + ScriptRegisterConstant( g_pScriptVM, nomergewater, "" ); + ScriptRegisterConstant( g_pScriptVM, nowater, "" ); + ScriptRegisterConstant( g_pScriptVM, nocsg, "" ); + ScriptRegisterConstant( g_pScriptVM, noweld, "" ); + ScriptRegisterConstant( g_pScriptVM, noshare, "" ); + ScriptRegisterConstant( g_pScriptVM, nosubdiv, "" ); + ScriptRegisterConstant( g_pScriptVM, notjunc, "" ); + ScriptRegisterConstant( g_pScriptVM, noopt, "" ); + ScriptRegisterConstant( g_pScriptVM, noleaktest, "" ); + ScriptRegisterConstant( g_pScriptVM, verboseentities, "" ); + ScriptRegisterConstant( g_pScriptVM, dumpcollide, "" ); + ScriptRegisterConstant( g_pScriptVM, g_bLowPriority, "" ); + ScriptRegisterConstant( g_pScriptVM, g_DumpStaticProps, "" ); + ScriptRegisterConstant( g_pScriptVM, g_bSkyVis, "" ); + ScriptRegisterConstant( g_pScriptVM, g_bLightIfMissing, "" ); + ScriptRegisterConstant( g_pScriptVM, g_snapAxialPlanes, "" ); + ScriptRegisterConstant( g_pScriptVM, g_bKeepStaleZip, "" ); + ScriptRegisterConstant( g_pScriptVM, g_NodrawTriggers, "" ); + ScriptRegisterConstant( g_pScriptVM, g_DisableWaterLighting, "" ); + ScriptRegisterConstant( g_pScriptVM, g_bAllowDetailCracks, "" ); + ScriptRegisterConstant( g_pScriptVM, g_bNoVirtualMesh, "" ); + ScriptRegisterConstant( g_pScriptVM, g_bNoHiddenManifestMaps, "" ); + ScriptRegisterConstant( g_pScriptVM, g_bNoDefaultCubemaps, "" ); + ScriptRegisterConstant( g_pScriptVM, g_bSkyboxCubemaps, "" ); + ScriptRegisterConstant( g_pScriptVM, g_iDefaultCubemapSize, "" ); + + if (g_iScripting == SL_SQUIRREL) + { + g_pScriptVM->Run( g_Script_vscript_vbsp ); + } + + RegisterVMFScriptFunctions(); + + // Run the map's script + char script[96]; + Q_snprintf( script, sizeof(script), "%s_vbsp", source ); + //Msg("VBSP script: \"%s\"\n", script); + VScriptRunScript( script, true ); + + VMPROF_SHOW( g_iScripting, "virtual machine startup" ); + + return true; + } + else + { + DevWarning("VM Did not start!\n"); + } + } + else + { + Log( "\nVSCRIPT: Scripting is disabled.\n" ); + } + g_pScriptVM = NULL; + return false; +} + +void VScriptVBSPTerm() +{ + if( g_pScriptVM != NULL ) + { + if( g_pScriptVM ) + { + scriptmanager->DestroyVM( g_pScriptVM ); + g_pScriptVM = NULL; + } + } +} diff --git a/src/utils/vbsp/vscript_vbsp.h b/src/utils/vbsp/vscript_vbsp.h new file mode 100644 index 00000000..1a6e390a --- /dev/null +++ b/src/utils/vbsp/vscript_vbsp.h @@ -0,0 +1,27 @@ +//========= Mapbase - https://github.com/mapbase-source/source-sdk-2013 ============// +// +// Purpose: Mapbase's implementation of VScript in VBSP, allowing users to modify map compilation behavior with scripts. +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef VSCRIPT_VBSP_H +#define VSCRIPT_VBSP_H + +#include "vscript/ivscript.h" + +#if defined( _WIN32 ) +#pragma once +#endif + +extern IScriptVM *g_pScriptVM; +extern IScriptManager *scriptmanager; + +HSCRIPT VScriptCompileScript( const char *pszScriptName, bool bWarnMissing = false ); +bool VScriptRunScript( const char *pszScriptName, HSCRIPT hScope, bool bWarnMissing = false ); +inline bool VScriptRunScript( const char *pszScriptName, bool bWarnMissing = false ) { return VScriptRunScript( pszScriptName, NULL, bWarnMissing ); } + +bool VScriptVBSPInit(); +void VScriptVBSPTerm(); + +#endif // VSCRIPT_SERVER_H diff --git a/src/utils/vbsp/vscript_vbsp.nut b/src/utils/vbsp/vscript_vbsp.nut new file mode 100644 index 00000000..2aa99060 --- /dev/null +++ b/src/utils/vbsp/vscript_vbsp.nut @@ -0,0 +1,52 @@ +static char g_Script_vscript_vbsp[] = R"vscript( +//========= Mapbase - https://github.com/mapbase-source/source-sdk-2013 ============// +// +// Purpose: +// +//=============================================================================// + +function UniqueString( string = "" ) +{ + return ::DoUniqueString( string.tostring() ); +} + +function __ReplaceClosures( script, scope ) +{ + if ( !scope ) + { + scope = getroottable(); + } + + local tempParent = { getroottable = function() { return null; } }; + local temp = { runscript = script }; + temp.set_delegate(tempParent); + + temp.runscript() + foreach( key,val in temp ) + { + if ( typeof(val) == "function" && key != "runscript" ) + { + printl( " Replacing " + key ); + scope[key] <- val; + } + } +} + +function IncludeScript( name, scope = null ) +{ + if ( !scope ) + { + scope = this; + } + return ::DoIncludeScript( name, scope ); +} + +// VBSP logs don't support ConColorMsg() +print <- Msg + +function printdoc( text ) +{ + return ::print(text + "\n"); +} + +)vscript"; \ No newline at end of file diff --git a/src/utils/vrad/vrad_dll.vpc b/src/utils/vrad/vrad_dll.vpc index 723c7103..6c1a4d00 100644 --- a/src/utils/vrad/vrad_dll.vpc +++ b/src/utils/vrad/vrad_dll.vpc @@ -6,6 +6,7 @@ $Macro SRCDIR "..\.." $Macro OUTBINDIR "$SRCDIR\..\game\bin" +$Macro OUTBINNAME "vrad_dll" $Include "$SRCDIR\vpc_scripts\source_dll_base.vpc" diff --git a/src/utils/vrad_launcher/vrad_launcher.cpp b/src/utils/vrad_launcher/vrad_launcher.cpp index a4d31834..0afb27d7 100644 --- a/src/utils/vrad_launcher/vrad_launcher.cpp +++ b/src/utils/vrad_launcher/vrad_launcher.cpp @@ -103,6 +103,18 @@ int main(int argc, char* argv[]) if (mode && (! both_arg)) continue; +#ifdef MAPBASE + // Coming through! + if ( !pModule ) + { + // With this, we just load the DLL with our filename. + // This allows for custom DLLs without having to bother with the launcher. + char filename[64]; + Q_FileBase(argv[0], filename, sizeof(filename)); + Q_snprintf(dllName, sizeof(dllName), "%s%s", filename, "_dll.dll"); + pModule = Sys_LoadModule( dllName ); + } +#endif // If it didn't load the module above, then use the if ( !pModule ) diff --git a/src/utils/vvis/vvis_dll.vpc b/src/utils/vvis/vvis_dll.vpc index 1618d6cd..dc42ebec 100644 --- a/src/utils/vvis/vvis_dll.vpc +++ b/src/utils/vvis/vvis_dll.vpc @@ -6,6 +6,7 @@ $Macro SRCDIR "..\.." $Macro OUTBINDIR "$SRCDIR\..\game\bin" +$Macro OUTBINNAME "vvis_dll" $Include "$SRCDIR\vpc_scripts\source_dll_base.vpc" diff --git a/src/utils/vvis_launcher/vvis_launcher.cpp b/src/utils/vvis_launcher/vvis_launcher.cpp index edf03d25..ddc2b83f 100644 --- a/src/utils/vvis_launcher/vvis_launcher.cpp +++ b/src/utils/vvis_launcher/vvis_launcher.cpp @@ -45,6 +45,7 @@ char* GetLastErrorString() int main(int argc, char* argv[]) { CommandLine()->CreateCmdLine( argc, argv ); +#ifndef MAPBASE const char *pDLLName = "vvis_dll.dll"; CSysModule *pModule = Sys_LoadModule( pDLLName ); @@ -53,6 +54,31 @@ int main(int argc, char* argv[]) printf( "vvis launcher error: can't load %s\n%s", pDLLName, GetLastErrorString() ); return 1; } +#else + // Coming through! + const char *pDLLName = "vvis_dll.dll"; + + // With this, we just load the DLL with our filename. + // This allows for custom DLLs without having to bother with the launcher. + char filename[128]; + Q_FileBase(argv[0], filename, sizeof(filename)); + Q_snprintf(filename, sizeof(filename), "%s_dll.dll", filename); + pDLLName = filename; + + CSysModule *pModule = Sys_LoadModule( pDLLName ); + if ( !pModule ) + { + // Try loading the default then + pDLLName = "vvis_dll.dll"; + pModule = Sys_LoadModule( pDLLName ); + } + + if ( !pModule ) + { + printf( "vvis launcher error: can't load %s\n%s", pDLLName, GetLastErrorString() ); + return 1; + } +#endif CreateInterfaceFn fn = Sys_GetFactory( pModule ); if( !fn ) diff --git a/src/vgui2/vgui_controls/AnimationController.cpp b/src/vgui2/vgui_controls/AnimationController.cpp index 48f8c4e1..6bb8c94a 100644 --- a/src/vgui2/vgui_controls/AnimationController.cpp +++ b/src/vgui2/vgui_controls/AnimationController.cpp @@ -31,6 +31,11 @@ using namespace vgui; static CUtlSymbolTable g_ScriptSymbols(0, 128, true); +#ifdef MAPBASE +// Allows animation sequences to be overridden by map-specific files +bool g_bUsingCustomHudAnimations = false; +#endif + // singleton accessor for animation controller for use by the vgui controls namespace vgui { @@ -319,11 +324,35 @@ bool AnimationController::ParseScriptFile(char *pMem, int length) return false; } - int seqIndex; + int seqIndex = -1; UtlSymId_t nameIndex = g_ScriptSymbols.AddString(token); - // Create a new sequence - seqIndex = m_Sequences.AddToTail(); +#ifdef MAPBASE + if (g_bUsingCustomHudAnimations) + { + // look through for the sequence + for (seqIndex = 0; seqIndex < m_Sequences.Count(); seqIndex++) + { + if (m_Sequences[seqIndex].name == nameIndex) + break; + } + + if (seqIndex >= m_Sequences.Count()) + seqIndex = -1; + else + { + // Clear some stuff + m_Sequences[seqIndex].cmdList.RemoveAll(); + } + } + + if (seqIndex == -1) +#endif + { + // Create a new sequence + seqIndex = m_Sequences.AddToTail(); + } + AnimSequence_t &seq = m_Sequences[seqIndex]; seq.name = nameIndex; seq.duration = 0.0f; diff --git a/src/vgui2/vgui_controls/ImagePanel.cpp b/src/vgui2/vgui_controls/ImagePanel.cpp index 192e6567..6d7edebc 100644 --- a/src/vgui2/vgui_controls/ImagePanel.cpp +++ b/src/vgui2/vgui_controls/ImagePanel.cpp @@ -73,6 +73,13 @@ void ImagePanel::OnSizeChanged(int newWide, int newTall) //----------------------------------------------------------------------------- void ImagePanel::SetImage(IImage *image) { +#ifdef MAPBASE + if ( image ) + { + image->SetRotation( m_iRotation ); + } +#endif + m_pImage = image; Repaint(); } @@ -481,3 +488,15 @@ void ImagePanel::SetFrame( int nFrame ) return m_pImage->SetFrame( nFrame ); } + +#ifdef MAPBASE +void ImagePanel::SetRotation( int iRotation ) +{ + m_iRotation = iRotation; + + if ( m_pImage ) + { + m_pImage->SetRotation( m_iRotation ); + } +} +#endif diff --git a/src/vgui2/vgui_controls/Panel.cpp b/src/vgui2/vgui_controls/Panel.cpp index 59a4838a..a61e66f3 100644 --- a/src/vgui2/vgui_controls/Panel.cpp +++ b/src/vgui2/vgui_controls/Panel.cpp @@ -83,6 +83,13 @@ static char *CopyString( const char *in ) // Temporary convar to help debug why the MvMVictoryMannUpPanel TabContainer is sometimes way off to the left. ConVar tf_debug_tabcontainer( "tf_debug_tabcontainer", "0", FCVAR_HIDDEN, "Spew TabContainer dimensions." ); +#ifdef MAPBASE +ConVar vgui_mapbase_custom_schemes( "vgui_mapbase_custom_schemes", "1" ); + +// This is used in mapbase_shared.cpp +HScheme g_iCustomClientSchemeOverride; +#endif + #if defined( VGUI_USEDRAGDROP ) //----------------------------------------------------------------------------- // Purpose: @@ -1710,17 +1717,31 @@ void Panel::DeletePanel() //----------------------------------------------------------------------------- HScheme Panel::GetScheme() { + HScheme iScheme; + if (m_iScheme) { - return m_iScheme; // return our internal scheme + iScheme = m_iScheme; // return our internal scheme } - - if (GetVParent()) // recurse down the heirarchy + else if (GetVParent()) // recurse down the heirarchy { - return ipanel()->GetScheme(GetVParent()); + iScheme = ipanel()->GetScheme(GetVParent()); + } + else + { + iScheme = scheme()->GetDefaultScheme(); } - return scheme()->GetDefaultScheme(); +#ifdef MAPBASE + // If a custom client scheme is available, use the custom scheme. + // TODO: Need a better way to detect that this panel actually uses ClientScheme.res + if (g_iCustomClientSchemeOverride != 0 && iScheme == scheme()->GetScheme( "ClientScheme" ) && vgui_mapbase_custom_schemes.GetBool()) + { + return g_iCustomClientSchemeOverride; + } +#endif + + return iScheme; } //----------------------------------------------------------------------------- @@ -8614,7 +8635,7 @@ void VguiPanelGetSortedChildPanelList( Panel *pParentPanel, void *pSortedPanels } } -void VguiPanelGetSortedChildButtonList( Panel *pParentPanel, void *pSortedPanels, char *pchFilter /*= NULL*/, int nFilterType /*= 0*/ ) +void VguiPanelGetSortedChildButtonList( Panel *pParentPanel, void *pSortedPanels, const char *pchFilter /*= NULL*/, int nFilterType /*= 0*/ ) { CUtlSortVector< SortedPanel_t, CSortedPanelYLess > *pList = reinterpret_cast< CUtlSortVector< SortedPanel_t, CSortedPanelYLess >* >( pSortedPanels ); diff --git a/src/vgui2/vgui_controls/ScrollBarSlider.cpp b/src/vgui2/vgui_controls/ScrollBarSlider.cpp index f324eb13..d3df34a8 100644 --- a/src/vgui2/vgui_controls/ScrollBarSlider.cpp +++ b/src/vgui2/vgui_controls/ScrollBarSlider.cpp @@ -18,7 +18,9 @@ #include #include +#if _MSC_VER < 1900 #include +#endif // memdbgon must be the last include file in a .cpp file!!! #include diff --git a/src/vgui2/vgui_controls/TextImage.cpp b/src/vgui2/vgui_controls/TextImage.cpp index c8870b44..8e641ead 100644 --- a/src/vgui2/vgui_controls/TextImage.cpp +++ b/src/vgui2/vgui_controls/TextImage.cpp @@ -984,3 +984,117 @@ void TextImage::SetColorChangeStream( CUtlSortVector *pOutCoords, bool bIgnoreEmptyLines ) +{ + HFont font = GetFont(); + if (!_utext || font == INVALID_FONT ) + return; + + // Early out if there's no newlines in our text + if (wcschr( _utext, L'\n' ) == NULL) + return; + + if (m_bRecalculateTruncation) + { + if ( m_bWrap || m_bWrapCenter ) + { + RecalculateNewLinePositions(); + } + + RecalculateEllipsesPosition(); + } + + int lineHeight = surface()->GetFontTall( GetFont() ); + float x = 0.0f; + int y = 0; + int iIndent = 0; + + int px, py; + GetPos(px, py); + + int currentLineBreak = 0; + + if ( m_bWrapCenter && m_LineXIndent.Count() ) + { + x = m_LineXIndent[0]; + } + + for (wchar_t *wsz = _utext; *wsz != 0; wsz++) + { + wchar_t ch = wsz[0]; + + if ( m_bAllCaps ) + { + ch = towupper( ch ); + } + + // check for special characters + if ( ch == '\r' || ch <= 8 ) + { + // ignore, just use \n for newlines + continue; + } + else if (ch == '\n') + { + // newline + iIndent++; + if ( m_bWrapCenter && iIndent < m_LineXIndent.Count() ) + { + x = m_LineXIndent[iIndent]; + } + else + { + x = 0; + } + y += lineHeight; + + if (!bIgnoreEmptyLines || (*(wsz + 1) != 0 && wsz[1] != '\n')) + { + pOutCoords->AddToTail( y ); + } + + continue; + } + else if (ch == '&') + { + // "&&" means draw a single ampersand, single one is a shortcut character + if (wsz[1] == '&') + { + // just move on and draw the second ampersand + wsz++; + } + } + + // see if we've hit the truncated portion of the string + if (wsz == m_pwszEllipsesPosition) + { + // do nothing + } + + if (currentLineBreak != m_LineBreaks.Count()) + { + if (wsz == m_LineBreaks[currentLineBreak]) + { + // newline + iIndent++; + if ( m_bWrapCenter && iIndent < m_LineXIndent.Count() ) + { + x = m_LineXIndent[iIndent]; + } + else + { + x = 0; + } + + y += lineHeight; + currentLineBreak++; + } + } + + // Underlined text wants to draw the spaces anyway + x += surface()->GetCharacterWidth(font, ch); + } +} +#endif diff --git a/src/vgui2/vgui_controls/Tooltip.cpp b/src/vgui2/vgui_controls/Tooltip.cpp index 5bebf948..d1d6b2df 100644 --- a/src/vgui2/vgui_controls/Tooltip.cpp +++ b/src/vgui2/vgui_controls/Tooltip.cpp @@ -6,8 +6,8 @@ // and implement another button here. //=============================================================================// -#include -#define PROTECTED_THINGS_DISABLE +//#include +//#define PROTECTED_THINGS_DISABLE #include #include diff --git a/src/vgui2/vgui_controls/TreeView.cpp b/src/vgui2/vgui_controls/TreeView.cpp index b7ad4d3b..c244edeb 100644 --- a/src/vgui2/vgui_controls/TreeView.cpp +++ b/src/vgui2/vgui_controls/TreeView.cpp @@ -418,7 +418,7 @@ class TreeNode : public Panel public: TreeNode(Panel *parent, TreeView *pTreeView); - ~TreeNode(); + virtual ~TreeNode(); void SetText(const char *pszText); void SetFont(HFont font); void SetKeyValues(KeyValues *data); diff --git a/src/vpc_scripts/groups.vgc b/src/vpc_scripts/groups.vgc index 0ff57f71..84bb418b 100644 --- a/src/vpc_scripts/groups.vgc +++ b/src/vpc_scripts/groups.vgc @@ -23,6 +23,8 @@ $Group "game" "server" "tier1" "vgui_controls" + "vscript" + "responserules" } $Group "shaders" @@ -42,6 +44,7 @@ $Group "everything" "mathlib" "motionmapper" "phonemeextractor" + "responserules" "qc_eyes" "raytrace" "server" @@ -53,6 +56,7 @@ $Group "everything" "vice" "vrad_dll" "vrad_launcher" + "vscript" "vtf2tga" "vtfdiff" "vvis_dll" @@ -72,3 +76,17 @@ $Group "dedicated" "tier1" } +$Group "maptools" +{ + "vbsp" + "vrad_dll" + "vrad_launcher" + "vvis_dll" + "vvis_launcher" + "fgdlib" + "mathlib" + "raytrace" + "tier1" + "vscript" +} + diff --git a/src/vpc_scripts/newer_vs_toolsets.vpc b/src/vpc_scripts/newer_vs_toolsets.vpc new file mode 100644 index 00000000..950e1677 --- /dev/null +++ b/src/vpc_scripts/newer_vs_toolsets.vpc @@ -0,0 +1,24 @@ +//----------------------------------------------------------------------------- +// NEWER_VS_TOOLSETS.VPC +// +// Additional toolsets added by Mapbase. +// +// NOTE: Enabling any of these makes the solution incompatible with Visual Studio 2013! +// +//----------------------------------------------------------------------------- + +$Conditional VS2015 "0" // Toggles Visual Studio 2015 (v140) toolset +$Conditional VS2017 "0" // Toggles Visual Studio 2017 (v141) toolset +$Conditional VS2019 "0" // Toggles Visual Studio 2019 (v142) toolset +$Conditional VS2022 "0" // Toggles Visual Studio 2022 (v143) toolset + +// +// VPC may still say "Generating for Visual Studio 2013" even when using one of the above toolsets. This message is irrelevant and can be ignored. +// +// The following projects currently do not compile with any of the above toolsets and are not included in their solutions: +// +// - phonemeextractor (may be fixable with modification) +// - qc_eyes (might be fixed by having C++ MFC for v141 build tools and/or C++ ATL for v141 build tools installed) +// + +//----------------------------------------------------------------------------- diff --git a/src/vpc_scripts/projects.vgc b/src/vpc_scripts/projects.vgc index b277b1a2..d438bbcf 100644 --- a/src/vpc_scripts/projects.vgc +++ b/src/vpc_scripts/projects.vgc @@ -17,6 +17,8 @@ $Project "client" { "game\client\client_tf.vpc" [$TF] "game\client\client_hl2mp.vpc" [$HL2MP] + "game\client\client_hl2.vpc" [$HL2] + "game\client\client_episodic.vpc" [$EPISODIC] } $Project "fgdlib" @@ -28,6 +30,7 @@ $Project "game_shader_generic_example" { "materialsystem\stdshaders\game_shader_generic_example_hl2mp.vpc" [$HL2MP] "materialsystem\stdshaders\game_shader_generic_example_tf.vpc" [$TF] + "materialsystem\stdshaders\game_shader_generic_example_hl2.vpc" [$HL2] } // Use this project if you want to override all std shaders with your own. @@ -36,6 +39,7 @@ $Project "game_shader_generic_std" { "materialsystem\stdshaders\game_shader_generic_std_hl2mp.vpc" [$HL2MP] "materialsystem\stdshaders\game_shader_generic_std_tf.vpc" [$TF] + "materialsystem\stdshaders\game_shader_generic_std_hl2.vpc" [$HL2] } $Project "glview" @@ -52,6 +56,8 @@ $Project "server" { "game\server\server_tf.vpc" [$TF] "game\server\server_hl2mp.vpc" [$HL2MP] + "game\server\server_hl2.vpc" [$HL2] + "game\server\server_episodic.vpc" [$EPISODIC] } $Project "launcher_main" @@ -60,6 +66,11 @@ $Project "launcher_main" "launcher_main\launcher_main_mod_tf.vpc" [$TF] } +$Project "responserules" +{ + "responserules\runtime\response_rules.vpc" //[$NEW_RESPONSE_SYSTEM] +} + $Project "mathlib" { "mathlib\mathlib.vpc" @@ -125,6 +136,11 @@ $Project "vrad_launcher" "utils\vrad_launcher\vrad_launcher.vpc" [$WINDOWS] } +$Project "vscript" +{ + "vscript\vscript.vpc" +} + $Project "vtf2tga" { "utils\vtf2tga\vtf2tga.vpc" [$WINDOWS] diff --git a/src/vpc_scripts/source_base.vpc b/src/vpc_scripts/source_base.vpc index dd5ccc23..ea2a5ab9 100644 --- a/src/vpc_scripts/source_base.vpc +++ b/src/vpc_scripts/source_base.vpc @@ -10,6 +10,16 @@ $MacroRequired VPC_TRIVIAL_DEPENDENCY_PATH "$SRCDIR/vpc_scripts/vpc_trivial_depe $Macro "DEFAULT_RUN_ARGS" "-dev -w 1920 -h 1080 -windowed" +//----------------------------------------------------------------------------- + +// Mapbase functionality conditionals +$Conditional MAPBASE "1" // Equivalent to (and required for) our MAPBASE preprocessor defined below +$Conditional MAPBASE_RPC "0" // Toggles Mapbase's Rich Presence Client implementations (requires discord-rpc.dll in game bin) +$Conditional MAPBASE_VSCRIPT "1" // Toggles VScript implementation (note: interfaces still exist, just the provided implementation is not present) +$Conditional NEW_RESPONSE_SYSTEM "1" // Toggles the new Response System library based on the Alien Swarm SDK + +//----------------------------------------------------------------------------- + $Configuration "Debug" { $Compiler @@ -20,6 +30,9 @@ $Configuration "Debug" $PreprocessorDefinitions "$BASE;USE_MEM_DEBUG" [$USE_MEM_DEBUG] $PreprocessorDefinitions "$BASE;SOURCESDK" [$SOURCESDK] $PreprocessorDefinitions "$BASE;SOURCE_HAS_FREETYPE" + + // Mapbase base definitions + $PreprocessorDefinitions "$BASE;MAPBASE" [$MAPBASE] } } @@ -31,5 +44,8 @@ $Configuration "Release" $PreprocessorDefinitions "VPC" $PreprocessorDefinitions "$BASE;SOURCESDK" [$SOURCESDK] $PreprocessorDefinitions "$BASE;SOURCE_HAS_FREETYPE" + + // Mapbase base definitions + $PreprocessorDefinitions "$BASE;MAPBASE" [$MAPBASE] } } diff --git a/src/vpc_scripts/source_lib_win32_base.vpc b/src/vpc_scripts/source_lib_win32_base.vpc index f0c7aff5..c7aae780 100644 --- a/src/vpc_scripts/source_lib_win32_base.vpc +++ b/src/vpc_scripts/source_lib_win32_base.vpc @@ -39,7 +39,8 @@ $Configuration $Compiler [$WIN32] { - $EnableEnhancedInstructionSet "Streaming SIMD Extensions (/arch:SSE)" + $EnableEnhancedInstructionSet "Streaming SIMD Extensions (/arch:SSE)" [!($VS2015||$VS2017||$VS2019||$VS2022)] + $EnableEnhancedInstructionSet "Streaming SIMD Extensions 2 (/arch:SSE2)" [($VS2015||$VS2017||$VS2019||$VS2022)] } $PreBuildEvent diff --git a/src/vscript/sqstdtime.h b/src/vscript/sqstdtime.h new file mode 100644 index 00000000..fbf1bee7 --- /dev/null +++ b/src/vscript/sqstdtime.h @@ -0,0 +1,88 @@ +//----------------------------------------------------------------------- +// see copyright notice in squirrel.h +// +// Purpose : Squirrel time library cropped out from +// the system library as a safe include. +// +//----------------------------------------------------------------------- + +#include "squirrel.h" +#include "time.h" + +static SQInteger _system_clock(HSQUIRRELVM v) +{ + sq_pushfloat(v, ((SQFloat)clock()) / (SQFloat)CLOCKS_PER_SEC); + return 1; +} + +static SQInteger _system_time(HSQUIRRELVM v) +{ + SQInteger t = (SQInteger)time(NULL); + sq_pushinteger(v, t); + return 1; +} + +static void _set_integer_slot(HSQUIRRELVM v, const SQChar *name, SQInteger val) +{ + sq_pushstring(v, name, -1); + sq_pushinteger(v, val); + sq_rawset(v, -3); +} + +static SQInteger _system_date(HSQUIRRELVM v) +{ + time_t t; + SQInteger it; + SQInteger format = 'l'; + if (sq_gettop(v) > 1) { + sq_getinteger(v, 2, &it); + t = it; + if (sq_gettop(v) > 2) { + sq_getinteger(v, 3, (SQInteger*)&format); + } + } + else { + time(&t); + } + tm *date; + if (format == 'u') + date = gmtime(&t); + else + date = localtime(&t); + if (!date) + return sq_throwerror(v, _SC("crt api failure")); + sq_newtable(v); + _set_integer_slot(v, _SC("sec"), date->tm_sec); + _set_integer_slot(v, _SC("min"), date->tm_min); + _set_integer_slot(v, _SC("hour"), date->tm_hour); + _set_integer_slot(v, _SC("day"), date->tm_mday); + _set_integer_slot(v, _SC("month"), date->tm_mon); + _set_integer_slot(v, _SC("year"), date->tm_year + 1900); + _set_integer_slot(v, _SC("wday"), date->tm_wday); + _set_integer_slot(v, _SC("yday"), date->tm_yday); + return 1; +} + +#define _DECL_FUNC(name,nparams,pmask) {_SC(#name),_system_##name,nparams,pmask} +static const SQRegFunction timelib_funcs[] = { + _DECL_FUNC(clock, 0, NULL), + _DECL_FUNC(time, 1, NULL), + _DECL_FUNC(date, -1, _SC(".nn")), + { NULL, (SQFUNCTION)0, 0, NULL } +}; +#undef _DECL_FUNC + +SQInteger sqstd_register_timelib(HSQUIRRELVM v) +{ + SQInteger i = 0; + while (timelib_funcs[i].name != 0) + { + sq_pushstring(v, timelib_funcs[i].name, -1); + sq_newclosure(v, timelib_funcs[i].f, 0); + sq_setparamscheck(v, timelib_funcs[i].nparamscheck, timelib_funcs[i].typemask); + sq_setnativeclosurename(v, -1, timelib_funcs[i].name); + sq_newslot(v, -3, SQFalse); + i++; + } + return 1; +} diff --git a/src/vscript/squirrel/.gitignore b/src/vscript/squirrel/.gitignore new file mode 100644 index 00000000..6e97beb7 --- /dev/null +++ b/src/vscript/squirrel/.gitignore @@ -0,0 +1,6 @@ +# Folders created at compilation +bin/ +lib/ + +# Folders created at documentation generation +doc/build/ diff --git a/src/vscript/squirrel/.travis.yml b/src/vscript/squirrel/.travis.yml new file mode 100644 index 00000000..1e31c1d6 --- /dev/null +++ b/src/vscript/squirrel/.travis.yml @@ -0,0 +1,17 @@ +language: cpp +compiler: + - gcc + - clang + +# Travis VMs are 64-bit but we compile both for 32 and 64 bit. To enable the +# 32-bit builds to work, we need gcc-multilib. +addons: + apt: + packages: + - gcc-multilib + - g++-multilib + +# Enable container-based builds. +sudo: false + +script: mkdir build && cd build && cmake .. && make -j2 diff --git a/src/vscript/squirrel/CMakeLists.txt b/src/vscript/squirrel/CMakeLists.txt new file mode 100644 index 00000000..dc35b6f4 --- /dev/null +++ b/src/vscript/squirrel/CMakeLists.txt @@ -0,0 +1,109 @@ +cmake_minimum_required(VERSION 3.4) +project(squirrel VERSION 3.1 LANGUAGES C CXX) + +option(DISABLE_STATIC "Avoid building/installing static libraries.") +option(LONG_OUTPUT_NAMES "Use longer names for binaries and libraries: squirrel3 (not sq).") + +if (NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE "Release") +endif () + +include(GNUInstallDirs) + +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/${CMAKE_INSTALL_BINDIR}") +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}") +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}") +set(CMAKE_CXX_STANDARD 11) + +if(CMAKE_COMPILER_IS_GNUCXX) + add_compile_options( + "$<$:-fno-rtti;-fno-exceptions>" + -fno-strict-aliasing + -Wall + -Wextra + -pedantic + -Wcast-qual + "$<$:-O3>" + "$<$:-O3;-g>" + "$<$:-Os>" + "$<$:-pg;-pie;-gstabs;-g3;-Og>" + ) +elseif(MSVC) + set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON) + add_definitions(-D_CRT_SECURE_NO_WARNINGS) +endif() + +add_subdirectory(squirrel) +add_subdirectory(sqstdlib) +add_subdirectory(sq) + +if(CMAKE_SIZEOF_VOID_P EQUAL 8) + set(tgts) + if(NOT DISABLE_DYNAMIC) + list(APPEND tgts squirrel sqstdlib sq) + endif() + if(NOT DISABLE_STATIC) + list(APPEND tgts squirrel_static sqstdlib_static sq_static) + endif() + foreach(t ${tgts}) + target_compile_definitions(${t} PUBLIC -D_SQ64) + endforeach() +endif() + +if(NOT DISABLE_DYNAMIC) + set_target_properties(squirrel sqstdlib PROPERTIES SOVERSION 0 VERSION 0.0.0) +endif() + +if(NOT SQ_DISABLE_INSTALLER AND NOT SQ_DISABLE_HEADER_INSTALLER) + install(FILES + include/sqconfig.h + include/squirrel.h + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} + COMPONENT Development + ) + install(FILES + include/sqstdaux.h + include/sqstdblob.h + include/sqstdio.h + include/sqstdmath.h + include/sqstdstring.h + include/sqstdsystem.h + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} + COMPONENT Development + ) +endif() + +include(CMakePackageConfigHelpers) + +write_basic_package_version_file( + "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}/cmake/squirrel/squirrel-config-version.cmake" + VERSION "${squirrel_VERSION}" + COMPATIBILITY AnyNewerVersion + ) + +configure_package_config_file( + "${CMAKE_CURRENT_SOURCE_DIR}/squirrel-config.cmake.in" + "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}/cmake/squirrel/squirrel-config.cmake" + INSTALL_DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/squirrel" + ) + +export(EXPORT squirrel + NAMESPACE squirrel:: + FILE "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}/cmake/squirrel/squirrel-targets.cmake" + ) + +if(NOT SQ_DISABLE_INSTALLER AND NOT SQ_DISABLE_CMAKE_INSTALLER) + install(FILES + "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}/cmake/squirrel/squirrel-config-version.cmake" + "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}/cmake/squirrel/squirrel-config.cmake" + DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/squirrel" + COMPONENT Development + ) + + install(EXPORT squirrel + NAMESPACE squirrel:: + DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/squirrel" + FILE "squirrel-targets.cmake" + COMPONENT Development + ) +endif() diff --git a/src/vscript/squirrel/COMPILE b/src/vscript/squirrel/COMPILE new file mode 100644 index 00000000..375a9209 --- /dev/null +++ b/src/vscript/squirrel/COMPILE @@ -0,0 +1,86 @@ +Squirrel 3.1 stable +-------------------------------------------------------- +What is in this distribution? + +squirrel + static library implementing the compiler and interpreter of the language + +sqstdlib + the standard utility libraries + +sq + stand alone interpreter + +doc + The manual + +etc + a minimalistic embedding sample + +samples + samples programs + + +HOW TO COMPILE +--------------------------------------------------------- +CMAKE USERS +......................................................... +If you want to build the shared libraries under Windows using Visual +Studio, you will have to use CMake version 3.4 or newer. If not, an +earlier version will suffice. For a traditional out-of-source build +under Linux, type something like + + $ mkdir build # Create temporary build directory + $ cd build + $ cmake .. # CMake will determine all the necessary information, + # including the platform (32- vs. 64-bit) + $ make + $ make install + $ cd ..; rm -r build + +The default installation directory will be /usr/local on Unix platforms, +and C:/Program Files/squirrel on Windows. The binaries will go into bin/ +and the libraries into lib/. You can change this behavior by calling CMake like +this: + + $ cmake .. -DCMAKE_INSTALL_PREFIX=/some/path/on/your/system + +With the CMAKE_INSTALL_BINDIR and CMAKE_INSTALL_LIBDIR options, the directories +the binaries & libraries will go in (relative to CMAKE_INSTALL_PREFIX) +can be specified. For instance, + + $ cmake .. -DCMAKE_INSTALL_LIBDIR=lib64 + +will install the libraries into a 'lib64' subdirectory in the top +source directory. The public header files will be installed into the directory +the value of CMAKE_INSTALL_INCLUDEDIR points to. If you want only the +binaries and no headers, just set -DSQ_DISABLE_HEADER_INSTALLER=ON, and no +header files will be installed. + +Under Windows, it is probably easiest to use the CMake GUI interface, +although invoking CMake from the command line as explained above +should work as well. + +GCC USERS +......................................................... +There is a very simple makefile that compiles all libraries and exes +from the root of the project run 'make' + +for 32 bits systems + + $ make + +for 64 bits systems + + $ make sq64 + +VISUAL C++ USERS +......................................................... +Open squirrel.dsw from the root project directory and build(dho!) + +DOCUMENTATION GENERATION +......................................................... +To be able to compile the documentation, make sure that you have Python +installed and the packages sphinx and sphinx_rtd_theme. Browse into doc/ +and use either the Makefile for GCC-based platforms or make.bat for +Windows platforms. diff --git a/src/vscript/squirrel/COPYRIGHT b/src/vscript/squirrel/COPYRIGHT new file mode 100644 index 00000000..17d13ac1 --- /dev/null +++ b/src/vscript/squirrel/COPYRIGHT @@ -0,0 +1,21 @@ +Copyright (c) 2003-2017 Alberto Demichelis + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +----------------------------------------------------- +END OF COPYRIGHT diff --git a/src/vscript/squirrel/HISTORY b/src/vscript/squirrel/HISTORY new file mode 100644 index 00000000..f8c29e6b --- /dev/null +++ b/src/vscript/squirrel/HISTORY @@ -0,0 +1,533 @@ +***version 3.2 stable*** +-added sq_tailcall +-added rawcall keyword +-added post call initializer syntax +-added table.keys() and table.values() +-added table.filter() +-additional parameters in array.map() and array.apply() +-additional optional initializer in array.reduce() +-closure.call() is now a "native tailcall" and the invoked function can now be suspended +-fixed sq_newmember and sq_rawnewmember properly pop parameters +-fixed capturing free variable on for loop counter before a break statement +-fixed \u in lexer +-various bugfixes + +***version 3.1.1 stable*** +-sq_gettypetag doesn't set last error(it's treated as SQBool function but keeps a SQRESULT for backward compatibility) +-fixed _set method in userdata delegates +-fixed some warnings + +***version 3.1 stable*** +-added slice range for tolower and toupper +-added startswith() and endswith() in string lib +-added SQ_EXCLUDE_DEFAULT_MEMFUNCTIONS to exclude default mem fuction from compilation +-added sq_getreleasehook +-added thread.wakeupthrow() +-added sq_pushthread +-added \u and \U escape sequence for UTF8,UTF16 or UCS4 characters +-added CMake scripts(thx Fabian Wolff) +-the escape character \x is based on sizeof(SQChar) +-fixed several warnings(thx Markus Oberhumer) +-fixed optimizer bug in compound arith oprators(+=,-= etc...) +-fixed sq_getrefvmcount() (thx Gerrit) +-fixed sq_getrefcount() when no references were added with sq_addref() (thx Gerrit) +-fixed bug in string.tointeger() (thx Domingo) +-fixed weakref comparison in 32bit builds using doubles(thx Domingo) +-fixed compiler bug(thx Peter) +-fixed some error in the documentation(thx Alexander) +-fixed some error reporting in compiler(thx Alexander) +-fixed incorrect optional semicolon after "if block"(thx Alexander) +-fixed crash bug in compiler related to compound arith operators(+=,-= etc...) (thx Jeff1) + +***2015-01-10 *** +***version 3.1 RC 1*** +-added new header sqconfig.h for all optional type declarations(unicode, 64bits etc..) +-added sq_setsharedforeignptr sq_getsharedforeignptr +-added sq_setsharedreleasehook sq_getsharedreleasehook +-added escape() in sqstd string library +-added __LINE__ and __FILE__ (thx mingodad) +-widechar support on gcc builds +-now boolean can be used in constants +-reduced dependencies on C runtime library +-newthread and sq_newthread() no longer reinitialize the root table on friend VMs(thx Lucas Cardellini) +-exceptions in the _inherited metamethod are propagated(thx Lucas Cardellini) +-'in' operator performance improvement(thx unagipai and mingodad) +-fixes crash in compiler when trying to write 'base' +-fixed bug in switch statement when using locals as case values (thx mingodad) +-fixed bug in print()(thx Lucas Cardellini) + +***2013-08-30 *** +***version 3.1 beta 1*** +-added new scoping rule(root attached to closures) +-added closure.setroot() closure.getroot() +-added sq_setclosureroot() and sq_getclosureroot() +-added sq_setvmreleasehook() and sq_getvmreleasehook() +-added documentaion for sq_getbase() +-now string.tointeger() accepts an optional parameter 'base' +-now format accepts zeroes in the format string (thx mingodad) +-fixed bug in sqstd_createfile() (thx mingodad) +-minor buxfixes + +***2012-11-10 *** +***version 3.0.4 stable*** +-sq_deleteslot slot now pops the key in case of failure +-fixed bug when _get metamethod throws null +-fixed a bug in rstrip +-added some error handling +-minor bugfixes + +***2012-06-19 *** +***version 3.1.0 alpha 1*** +-changed in and instanceof operator precendence +-root object in closures +-added closure.setroot closure.getroot +-added sq_setclosureroot and sq_getclosureroot + +***version 3.0.3 stable*** +-improved error messages for _cmp(when a non integer value is returned) (thx Yexo) +-added class.newmember() built in method (thx Nam) +-added class.rawnewmember() built in method (thx Nam) +-added sq_rawnewmember() (thx Nam) +-added sq_getversion() +-added sq_typeof() +-added sq_getclosurename() +-added file.close() in stdlib +-documented closure.getinfos() built-in method +-fixed string iteration doesn't return negative numbers for characters > 127 +-fixed bug in tofloat() when converting a string with scientific notation without a decimal point (thx wr2) +-fixed potential infinite loop in array.sort() when the _cmp function is inconsistent (thx Yexo) +-fixed obscure bug in the compiler(thx yishin) +-fixed some minor bug + +***2011-11-28 *** +***version 3.0.2 stable*** +-added sq_gethash API +-now array.sort() is implemented with heapsort +-now floats in scientific notation also accept numbers with no '.' (eg. 1e+6 or 1e6) +-fixed some warning +-fixed some documentation +-fixed bug in GC + +***2011-09-08 *** +***version 3.0.1 stable*** +-added # as alternative symbol for "line comment"(mostly useful for shell scripts) +-added sq_throwobject() to throw an arbitrary object from the C API +-added alignement flag for userdata types, SQ_ALIGNMENT (thx Shigemasa) +-added rawset() and rawget() to class and instance default delegate +-changed bytecode format now ensures matching integer size and float size +-now inherited classes also inherit userdatasize +-added SQUIRREL_VERSION_NUMBER in squirrel.h and _versionnumber_ global symbol +-fixed sq_getmemberhandle +-fixed sq_getrefcount +-refactored some sqstdio code +-refactored some clone code +-refactored some stuff in the string lib +-added -s and -fno-exceptions in GCC makefile(better performance when using GCC) + +***2011-03-13 *** +***version 3.0 stable*** +-added sq_getcallee() +-sq_getfreevariable() also works for native closures +-minior optimizations +-removed several warning when compiling with GCC 4.x +-fixed some errors in the documentation +-fixed bug when using SQUSEDOUBLE and 32bits intengers +-fixed bug when invoking generators with closure.call() (thx huntercool) + +***2010-12-19 *** +***version 3.0 release candidate 1(RC 1)*** +-improved metamethods error handling +-added parameter 'isstatic' to _newmember metamethod(thx G.Meyer) +-added sq_getrefcount() to return number of refences from C++(thx G.Meyer) + +***2010-11-07 *** +***version 3.0 beta 3*** +-license changed to "MIT license" +-added sq_resurrectunreachable() and resurrectunreachable() +-added callee() built in function, returns the current running closure +-added thread.getstackinfos() +-added sq_objtouserpointer() +-added sq_newtableex() +-various refactoring and optimizations +-fixed several 64bits issues regarding integer to string conversions +-fixed some bugs when SQUSEDOUBLE is used in 32bits systems + +***2010-08-18 *** +***version 3.0 beta 2.1*** +-fixed bug in class constructor +-fixed bug in compound arith + +***2010-08-12 *** +***version 3.0 beta 2*** +-class methods can be added or replaced after the class as been instantiated +-JSON compliant table syntax, this is currently an experimental feature (thx atai) +-sq_getsize() now returns userdatasize for classes and instances +-now setroottable() and setconsttable() return the previous value of the respective table +-fixed bug in compound arith operators when used on a free variable (thx ellon) +-fixed some x64 minor bugs +-fixed minor bug in the compiler +-refactored some VM internals +-documented sq_getmemberhandle, sq_getbyhandle, sq_setbyhandle to set and get value from classes + +***2009-11-15 *** +***version 3.0 beta 1*** +-various refactoring and optimizations +-fixed bug in free variables (thx mokehehe) +-fixed bug in functions with default parameters (thx ara & Yexo) +-fixed bug in exception handling +-improved error propagation in _set and _get metamethods ( and 'throw null' for clean failure) +-added sq_getmemberhandle, sq_getbyhandle, sq_setbyhandle to set and get value from classes + +***2009-06-30 *** +***version 3.0 alpha 2*** +-added real free variables(thx Paul Ruizendaal) +-added refactored function call implementation and compiler(thx Paul Ruizendaal) +-added sq_getfunctioninfo +-added compile time flag SQUSEDOUBLE to use double precision floats +-added global slot _floatsize_ int the base lib to recognize single precision and double precision builds +-sq_wakeupvm can now resume the vm with an exception +-added sqstd_format +-now blobs can be cloned +-generators can now be instantiated by calling sq_call() or closure.call() +-fixed debughook bug +-fixed cooroutine error propagation + +***2008-07-23 *** +***version 3.0 alpha 1*** +-first branch from 2.x source tree +-added 'base' keyword +-removed 'delegate' keyword +-now compiled scripts are vararg functions +-added setdelegate() and getdelegate() table builtin methods +-added <=> 3 ways compare operator +-added lambda expression @(a,b) a + b +-added local function statement +-added array built-in map(),reduce(),apply(),filter() and find() +-generators hold only a weak reference of the enviroment object +-removed 'vargv' and 'vargc' keywords +-now var args are passed as an array called vargv(as a paramter) +-removed 'parent' keyword +-added class getbase() built in method +-instanceof doesn't throw an exception if the left expression is not a class +-lexical scoping for free variables(free variables are no longer in the second parameter list) +-sq_setprintfunc accept error func +-sq_geterrorfunc() +-added sq_arrayremove() and sq_arrayinsert() +-error() built in function(works like print but prints using the errorfunc) +-added native debug hook + +***2008-02-17 *** +***version 2.2 stable*** +-added _newslot metamethod in classes +-added enums added constants +-added sq_pushconsttable, sq_setconsttable +-added default param +-added octal literals(thx Dinosaur) +-fixed debug hook, 'calls' and 'returns' are properly notified in the same number. +-fixed a coroutine bug + +***2007-07-29 *** +***version 2.1.2 stable*** +-new behaviour for generators iteration using foreach +now when a generator is iterated by foreach the value returned by a 'return val' statement +will terminate the iteration but will not be returned as foreach iteration +-added sq_setclassudsize() +-added sq_clear() +-added table.clear(), array.clear() +-fixed sq_cmp() (thx jyuill) +-fixed minor bugs + +***2006-08-21 *** +***version 2.1.1 stable*** +-vm refactoring +-optimized internal function memory layout +-new global symbol _version_ (is the version string) +-code size optimization for float literals(on 32bits float builts) +-now the raw ref API(sq_addref etc...) is fully reentrant. +-fixed a bug in sq_getdelegate() now pushes null if the object doesn't have a delegate(thx MatzeB) +-improved C reference performances in NO_GARBAGE_COLLECTOR builds +-sq_getlocal() now enumerates also outer values. +-fixed regexp library for GCC users. + +***2006-03-19 *** +***version 2.1 stable*** +-added static class fields, new keyword static +-added 64bits architecture support +-added global slot _intsize_ int the base lib to recognize 32bits and 64bits builds +-added functions with fixed environment, closure.bindenv() built-in function +-all types except userdata and null implement the tostring() method +-string concatenation now invokes metamethod _tostring +-new metamethods for class objects _newmember and _inherited +-sq_call() sq_resume() sq_wakeupvm() have a new signature +-new C referencing implementation(scales more with the amount of references) +-refactored hash table +-new api functions sq_newslot(),sq_tobool(),sq_getbase(), sq_instanceof(), sq_bindenv() +-the api func sq_createslot was deprecated but still supported in form of C macro on top of sq_newslot +-sq_setreleasehook() now also works for classes +-stream.readstr() and stream.writestr() have been deprecated(this affects file and blob) +-fixed squirrel.h undeclared api calls +-fixed few minor bugs +-SQChar is now defined as wchar_t +-removed warning when building with -Wall -pedantic for GCC users +-added new std io function writeclosuretofile() +-added new std string functions strip(),rstrip(),lstrip() and split() +-regular expressions operators (+,*) now have more POSIX greedyness behaviour +-class constructors are now invoked as normal functions + +***2005-10-02 *** +***version 2.0.5 stable*** +-fixed some 64bits incompatibilities (thx sarge) +-fixed minor bug in the stdlib format() function (thx Rick) +-fixed a bug in dofile() that was preventing to compile empty files +-added new API sq_poptop() & sq_getfreevariable() +-some performance improvements + +***2005-08-14 *** +***version 2.0.4 stable*** +-weak references and related API calls +-added sq_objtobool() +-class instances memory policies improved(1 mem allocation for the whole instance) +-typetags are now declared as SQUserPointer instead of unsigned int +-first pass for 64bits compatibility +-fixed minor bug in the stdio stream +-fixed a bug in format() +-fixed bug in string.tointeger() and string.tofloat() + +***2005-06-24 *** +***version 2.0.3 stable*** +-dofile() and loadfile() in the iolib now can decode ASCII, UTF8 files UCS2 big-endian and little-endian +-sq_setparamscheck() : now typemesk can check for null +-added string escape sequence \xhhhh +-fixed some C++ standard incompatibilities + +***2005-05-15 *** +***version 2.0.2 stable*** +-performances improvements (expecially for GCC users) +-removed all dependencies from C++ exception handling +-various bugfixes + +***2005-04-12 *** +***version 2.0.1 stable*** +-various bugfixes +-sq_setparamscheck() now allows spaces in the typemask + +***2005-04-03 *** +***version 2.0 stable*** +-added API sq_gettypetag() +-added built-in function to the bool type(tointeger, tostring etc...) + +***2005-02-27 *** +***version 2.0 release candidate 1(RC 1)*** +-added API sq_reseterror() +-modified sq_release() +-now class instances can be cloned +-various bufixes + +***2005-01-26 *** +***version 2.0 beta 1*** +-added bool type +-class properties can be redefined in a derived class +-added ops *= /= and %= +-new syntax for class attributes declaration instead of ( and ) +-increased the max number of literals per function from 65535 to 16777215 +-now free variables have proper lexical scoping +-added API sq_createinstance(), sq_pushbool(), sq_getbool() +-added built-in function type() +-added built-in function obj.rawin(key) in table,class and instance +-sq_rawget() and sq_rawset() now work also on classes and instances +-the VM no longer uses C++ exception handling (more suitable for embedded devices) +-various bufixes + +***2004-12-21 *** +***version 2.0 alpha 2*** +-globals scoping changed, now if :: is omitted the VM automatically falls back on the root table +-various bufixes +-added class level attributes + +***2004-12-12 *** +***version 2.0 alpha 1*** +-codebase branch from version 1.x +-added classes +-added functions with variable number of parameters(vargc & vargv and the ...) +-0 and 0.0 are now considered 'false' by all conditional statements(if,while,for,?,do-while) +-added new api functions sq_newclass() sq_setinstanceup() sq_getinstanceup() sq_getattributes() sq_setattributes() +-modified api sq_settypetag() + +***2004-11-01 *** +***version 1.0 stable*** +-fixed some minor bug +-improved operator 'delete' performances +-added scientific notation for float numbers( eg. 2.e16 or 2.e-2) + +***2004-08-30 *** +***version 1.0 release candidate 2(RC 2)*** +-fixed bug in the vm(thx Pierre Renaux) +-fixed bug in the optimizer(thx Pierre Renaux) +-fixed some bug in the documentation(thx JD) +-added new api functions for raw object handling +-removed nested multiline comments +-reduced memory footprint in C references + +***2004-08-23 *** +***version 1.0 release candidate 1(RC 1)*** +-fixed division by zero +-the 'in' operator and obj.rawget() do not query the default delegate anymore +-added function sq_getprintfunc() +-added new standard library 'auxlib'(implements default error handlers) + +***2004-07-12 *** +***version 1.0 beta 4*** +-fixed a bug in the integer.tochar() built-in method +-fixed unary minus operator +-fixed bug in dofile() +-fixed inconsistency between != and == operators(on float/integer comparison) +-added javascript style unsigned right shift operator '>>>' +-added array(size) constructor built-in function +-array.resize(size,[fill]) built-in function accepts an optional 'fill' value +-improved debug API, added sq_getclosureinfo() and sq_setnativeclosurename() + +***2004-05-23 *** +***version 1.0 beta 3*** +-minor vm bug fixes +-string allocation is now faster +-tables and array memory usage is now less conservative(they shrink) +-added regular expression routines in the standard library +-The 'c' expression now accepts only 1 character(thx irbrian) +-multiline strings <[ ]> have been substituted with C# style verbatim strings (eg. @"string") +-added new keyword 'parent' for accessing the delegate of tables and unserdata +-The metamethod '_clone' has been renamed '_cloned' +-the _delslot metamethod's behaviour and prototype have been changed +-new default function in the integer and float object 'tochar()' +-the built-in function chcode2string has been removed +-the default method [table].getdelegate() has been removed +-new api sq_rawdeleteslot() +-new table built-in method rawdelete(key) +-the dynamic mudule loading has been removed from the standard distribution +-some optimizations in the VM + +***2004-04-21 *** +***version 1.0 beta 2*** +-minor compiler/parser bug fixes +-sq_newclosure has a different prototype, the "paramscheck" of paramter has been moved to the new function sq_setparamscheck() +-sq_setparamscheck allows to add automatic parameters type checking in native closures +-sq_compile() lost the lineinfo parameter +-new api sq_enabledebuginfo() globally sets compiler's debug info generation +-added consistency check on bytecode serialization +-fixed += operator, now works on strings like + +-added global slot in the base lib _charsize_ to recognize unicode builds from ascii builds runtime +-added registry table +-new api call sq_pushregistrytable() +-added type tag to the userdata type sq_settypetag() +-sq_getuserdata now queries the userdata typetag +-the built in function collect_garbage() as been renamed collectgarbage() for consistency reasons +-new standard libraries(sqlibs are now obsolete) + +***2004-02-20 *** +***version 1.0 beta 1*** +-fixed a bug in the compiler (thanks Martin Kofler) +-fixed bug in the switch case statement +-fixed the _unm metamethod +-fixed minor bugs in the API +-fixed automatic stack resizing +-first beta version + first pass code clean up in the VM and base lib + first pass code coverege test has been done on VM and built-in lib +-new VM creation API sq_open() sq_close() (sq_newvm and sq_releasevm are now obsolete) +-new api allows to specifiy a "print" function to output text(sq_printfunc) +-added some small optimizations +-new cooperative multi-threading capabilities in the base library(coroutines), VMs are now a built in type("thread") +-new built in functions have been added for manipulating the new "thread" type +-friend virtual machines share the same root table, error handler and debug hook by default +-new compile time options + +***2004-01-19 *** +***version 0.9 alpha*** +-fixed a garbage collection bug +-fixed some API bugs(thanks to Joshua Jensen) +-fixed tail calls (in the version 0.8 the tail call optimization was erroneously disabled) +-new function parameters semantic, now passing a wrong number of parameters generates an exception +-native closures have now a built in parameter number checking +-sq_rawget and sq_rawset now work also on arrays +-sq_getsize now woks also on userdata +-the userdata release hook prototype is changed(now passes the size of the userdata) +-the lexer reader function now returns an integer instead of a char that allows better error checking on the input(thx Joshua Jensen) +-faster compiler +-try/catch blocks do not cause any runtime memory allocation anymore + +***2003-12-06 *** +***version 0.8 alpha*** +-fixed a bug that was preventing to have callable userdata throught the metamethod _call +-fixed a garbage collection bug +-fixed == operator now can compare correctly different types +-new built in method getstackinfos(level) +-improved line informations precision for the debug hook +-new api call sq_compilebuffer() +-new built-in api function compilestring() +-new syntactic sugar for function declarations inside tables +-the debug API has been finalized + +***2003-11-17 *** +***version 0.7 alpha*** +-fixed critical bug SQInteger the tail call system +-fixed bug in the continue statement code generation +-fixed func call param issue(thanks to Rewoonenco Andrew) +-added _delslot metamethod(thanks to Rewoonenco Andrew) +-new multiline string expression ( delimited by <[ and ]> ) +-normal strings ("") do not allow embedded new line anymore +-reduced vm memory footprint(C refs are shared between friend VMs) +-new api method sq_deleteslot() +-new debug hook event 'r' is triggered when a function returns + +***2003-11-04 *** +***version 0.6 alpha*** +-fixed switch statement(was executing the default case after a break) +-sq_call() doesn't pop the closure (just the params) +-the vm execution can be suspended from the C API anytime (micro-threads) +-new api calls sq_suspendvm() sq_wakeupvm() sq_getvmstate() and sq_reservestack() + +***2003-10-13 *** +***version 0.5 alpha*** +-fixed some minor bug +-tested with non ASCII identifiers in unicode mode(I've tried chinese chars) +-added built-in function string.find() +-the built-in function array.sort() optionally accepts a cmp(a,b) function +-the debug hook function now has a new prototype debug_hook(event_type,sourcefile,line,functionname) +-fixed some debug info imprecision + +***2003-10-01 *** +***version 0.4 alpha*** +-faster VM +-sq_call will pop arguments and closure also in case of failure +-fixed a bug in sq_remove +-now the VM detects delegation cycles(and throws an exception) +-new operators ++ and -- +-new operator ',' comma operator +-fixed some expression precedence issue +-fixed bug in sq_arraypop + +***2003-09-15 *** +***version 0.3 alpha*** +-fixed a bug in array::insert() +-optional Unicode core(define SQUNICODE or _UNICODE on Win32) +-sq_compiler uses a new reader function SQLEXREADFUNC +-the debug hook passes 'l' instead of 'line' for line callbacks + and 'c' instead of 'call' for call callbacks +-new array.extend() bulit-in function +-new API sq_clone() + +***2003-09-10 *** +***version 0.2 pre-alpha*** +-new completely reentrant VM (sq_open and sq_close are now obsolete) +-sq_newvm() has a new prototype +-allocators are now global and linked in the VM +-_newslot meta method added +-rawset creates a slot if doesn't exists +-the compiler error callback pass the vm handle(thanks Pierre Renaux) +-sq_setforeignptr() sq_getforeingptr() are now public +-sq_resume() now is possible to resume generators from C +-sq_getlasterror() retrieve the last thrown error +-improved docs + +***2003-09-06 *** +***version 0.1 pre-alpha*** +first release diff --git a/src/vscript/squirrel/Makefile b/src/vscript/squirrel/Makefile new file mode 100644 index 00000000..2ed97e26 --- /dev/null +++ b/src/vscript/squirrel/Makefile @@ -0,0 +1,22 @@ + +SQUIRREL=. +MAKE=make + +sq32: folders + cd squirrel; $(MAKE) + cd sqstdlib; $(MAKE) + cd sq; $(MAKE) + +sqprof: folders + cd squirrel; $(MAKE) sqprof + cd sqstdlib; $(MAKE) sqprof + cd sq; $(MAKE) sqprof + +sq64: folders + cd squirrel; $(MAKE) sq64 + cd sqstdlib; $(MAKE) sq64 + cd sq; $(MAKE) sq64 + +folders: + mkdir -p lib + mkdir -p bin diff --git a/src/vscript/squirrel/README b/src/vscript/squirrel/README new file mode 100644 index 00000000..298aec75 --- /dev/null +++ b/src/vscript/squirrel/README @@ -0,0 +1,33 @@ +The programming language SQUIRREL 3.1 stable + +-------------------------------------------------- +This project has successfully been compiled and run on + * Windows (x86 and amd64) + * Linux (x86, amd64 and ARM) + * Illumos (x86 and amd64) + * FreeBSD (x86 and ARM) + +The following compilers have been confirmed to be working: + MS Visual C++ 6.0 (all on x86 and amd64) + 7.0 | + 7.1 v + 8.0 + 9.0 + 10.0 + 12.0 --- + MinGW gcc 3.2 (mingw special 20020817-1) + Cygnus gcc 3.2 + Linux gcc 3.2.3 + 4.0.0 (x86 and amd64) + 5.3.1 (amd64) + Illumos gcc 4.0.0 (x86 and amd64) + ARM Linux gcc 4.6.3 (Raspberry Pi Model B) + + +Feedback and suggestions are appreciated +project page - http://www.squirrel-lang.org +community forums - http://forum.squirrel-lang.org +wiki - http://wiki.squirrel-lang.org +author - alberto@demichelis.net + +END OF README diff --git a/src/vscript/squirrel/appveyor.yml b/src/vscript/squirrel/appveyor.yml new file mode 100644 index 00000000..4da9b37b --- /dev/null +++ b/src/vscript/squirrel/appveyor.yml @@ -0,0 +1,28 @@ +version: 0.0.{build} + +platform: + - x86 + - x64 + +configuration: + - Debug + - Release + +clone_folder: c:\sq + +before_build: + - mkdir build + - cd build + - call "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" %platform% + - echo %platform% + - if %platform%==X64 (cmake .. -G "Visual Studio 14 2015 Win64") + - if %platform%==x86 (cmake .. -G "Visual Studio 14 2015") + +build_script: + - cmake --build . --config %configuration% -- /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll" + +artifacts: + - path: build\*\%configuration%\*.exe + - path: build\*\%configuration%\*.dll + +test: off diff --git a/src/vscript/squirrel/doc/Makefile b/src/vscript/squirrel/doc/Makefile new file mode 100644 index 00000000..b01e98db --- /dev/null +++ b/src/vscript/squirrel/doc/Makefile @@ -0,0 +1,216 @@ +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = +BUILDDIR = build + +# User-friendly check for sphinx-build +ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) +$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) +endif + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source +# the i18n builder cannot share the environment and doctrees with the others +I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source + +.PHONY: help +help: + @echo "Please use \`make ' where is one of" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " singlehtml to make a single large HTML file" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " applehelp to make an Apple Help Book" + @echo " devhelp to make HTML files and a Devhelp project" + @echo " epub to make an epub" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " latexpdf to make LaTeX files and run them through pdflatex" + @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" + @echo " text to make text files" + @echo " man to make manual pages" + @echo " texinfo to make Texinfo files" + @echo " info to make Texinfo files and run them through makeinfo" + @echo " gettext to make PO message catalogs" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " xml to make Docutils-native XML files" + @echo " pseudoxml to make pseudoxml-XML files for display purposes" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + @echo " coverage to run coverage check of the documentation (if enabled)" + +.PHONY: clean +clean: + rm -rf $(BUILDDIR)/* + +.PHONY: html +html: + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + +.PHONY: dirhtml +dirhtml: + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." + +.PHONY: singlehtml +singlehtml: + $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml + @echo + @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." + +.PHONY: pickle +pickle: + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +.PHONY: json +json: + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json + @echo + @echo "Build finished; now you can process the JSON files." + +.PHONY: htmlhelp +htmlhelp: + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in $(BUILDDIR)/htmlhelp." + +.PHONY: qthelp +qthelp: + $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp + @echo + @echo "Build finished; now you can run "qcollectiongenerator" with the" \ + ".qhcp project file in $(BUILDDIR)/qthelp, like this:" + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/testy_sphinxy.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/testy_sphinxy.qhc" + +.PHONY: applehelp +applehelp: + $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp + @echo + @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." + @echo "N.B. You won't be able to view it unless you put it in" \ + "~/Library/Documentation/Help or install it in your application" \ + "bundle." + +.PHONY: devhelp +devhelp: + $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp + @echo + @echo "Build finished." + @echo "To view the help file:" + @echo "# mkdir -p $$HOME/.local/share/devhelp/testy_sphinxy" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/testy_sphinxy" + @echo "# devhelp" + +.PHONY: epub +epub: + $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub + @echo + @echo "Build finished. The epub file is in $(BUILDDIR)/epub." + +.PHONY: latex +latex: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." + @echo "Run \`make' in that directory to run these through (pdf)latex" \ + "(use \`make latexpdf' here to do that automatically)." + +.PHONY: latexpdf +latexpdf: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through pdflatex..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +.PHONY: latexpdfja +latexpdfja: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through platex and dvipdfmx..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +.PHONY: text +text: + $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text + @echo + @echo "Build finished. The text files are in $(BUILDDIR)/text." + +.PHONY: man +man: + $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man + @echo + @echo "Build finished. The manual pages are in $(BUILDDIR)/man." + +.PHONY: texinfo +texinfo: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo + @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." + @echo "Run \`make' in that directory to run these through makeinfo" \ + "(use \`make info' here to do that automatically)." + +.PHONY: info +info: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo "Running Texinfo files through makeinfo..." + make -C $(BUILDDIR)/texinfo info + @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." + +.PHONY: gettext +gettext: + $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale + @echo + @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." + +.PHONY: changes +changes: + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + @echo + @echo "The overview file is in $(BUILDDIR)/changes." + +.PHONY: linkcheck +linkcheck: + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in $(BUILDDIR)/linkcheck/output.txt." + +.PHONY: doctest +doctest: + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + @echo "Testing of doctests in the sources finished, look at the " \ + "results in $(BUILDDIR)/doctest/output.txt." + +.PHONY: coverage +coverage: + $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage + @echo "Testing of coverage in the sources finished, look at the " \ + "results in $(BUILDDIR)/coverage/python.txt." + +.PHONY: xml +xml: + $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml + @echo + @echo "Build finished. The XML files are in $(BUILDDIR)/xml." + +.PHONY: pseudoxml +pseudoxml: + $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml + @echo + @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." diff --git a/src/vscript/squirrel/doc/make.bat b/src/vscript/squirrel/doc/make.bat new file mode 100644 index 00000000..a32fa10a --- /dev/null +++ b/src/vscript/squirrel/doc/make.bat @@ -0,0 +1,263 @@ +@ECHO OFF + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set BUILDDIR=build +set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% source +set I18NSPHINXOPTS=%SPHINXOPTS% source +if NOT "%PAPER%" == "" ( + set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% + set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% +) + +if "%1" == "" goto help + +if "%1" == "help" ( + :help + echo.Please use `make ^` where ^ is one of + echo. html to make standalone HTML files + echo. dirhtml to make HTML files named index.html in directories + echo. singlehtml to make a single large HTML file + echo. pickle to make pickle files + echo. json to make JSON files + echo. htmlhelp to make HTML files and a HTML help project + echo. qthelp to make HTML files and a qthelp project + echo. devhelp to make HTML files and a Devhelp project + echo. epub to make an epub + echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter + echo. text to make text files + echo. man to make manual pages + echo. texinfo to make Texinfo files + echo. gettext to make PO message catalogs + echo. changes to make an overview over all changed/added/deprecated items + echo. xml to make Docutils-native XML files + echo. pseudoxml to make pseudoxml-XML files for display purposes + echo. linkcheck to check all external links for integrity + echo. doctest to run all doctests embedded in the documentation if enabled + echo. coverage to run coverage check of the documentation if enabled + goto end +) + +if "%1" == "clean" ( + for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i + del /q /s %BUILDDIR%\* + goto end +) + + +REM Check if sphinx-build is available and fallback to Python version if any +%SPHINXBUILD% 1>NUL 2>NUL +if errorlevel 9009 goto sphinx_python +goto sphinx_ok + +:sphinx_python + +set SPHINXBUILD=python -m sphinx.__init__ +%SPHINXBUILD% 2> nul +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.http://sphinx-doc.org/ + exit /b 1 +) + +:sphinx_ok + + +if "%1" == "html" ( + %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/html. + goto end +) + +if "%1" == "dirhtml" ( + %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. + goto end +) + +if "%1" == "singlehtml" ( + %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. + goto end +) + +if "%1" == "pickle" ( + %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can process the pickle files. + goto end +) + +if "%1" == "json" ( + %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can process the JSON files. + goto end +) + +if "%1" == "htmlhelp" ( + %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can run HTML Help Workshop with the ^ +.hhp project file in %BUILDDIR%/htmlhelp. + goto end +) + +if "%1" == "qthelp" ( + %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can run "qcollectiongenerator" with the ^ +.qhcp project file in %BUILDDIR%/qthelp, like this: + echo.^> qcollectiongenerator %BUILDDIR%\qthelp\testy_sphinxy.qhcp + echo.To view the help file: + echo.^> assistant -collectionFile %BUILDDIR%\qthelp\testy_sphinxy.ghc + goto end +) + +if "%1" == "devhelp" ( + %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. + goto end +) + +if "%1" == "epub" ( + %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The epub file is in %BUILDDIR%/epub. + goto end +) + +if "%1" == "latex" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "latexpdf" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + cd %BUILDDIR%/latex + make all-pdf + cd %~dp0 + echo. + echo.Build finished; the PDF files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "latexpdfja" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + cd %BUILDDIR%/latex + make all-pdf-ja + cd %~dp0 + echo. + echo.Build finished; the PDF files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "text" ( + %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The text files are in %BUILDDIR%/text. + goto end +) + +if "%1" == "man" ( + %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The manual pages are in %BUILDDIR%/man. + goto end +) + +if "%1" == "texinfo" ( + %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. + goto end +) + +if "%1" == "gettext" ( + %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The message catalogs are in %BUILDDIR%/locale. + goto end +) + +if "%1" == "changes" ( + %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes + if errorlevel 1 exit /b 1 + echo. + echo.The overview file is in %BUILDDIR%/changes. + goto end +) + +if "%1" == "linkcheck" ( + %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck + if errorlevel 1 exit /b 1 + echo. + echo.Link check complete; look for any errors in the above output ^ +or in %BUILDDIR%/linkcheck/output.txt. + goto end +) + +if "%1" == "doctest" ( + %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest + if errorlevel 1 exit /b 1 + echo. + echo.Testing of doctests in the sources finished, look at the ^ +results in %BUILDDIR%/doctest/output.txt. + goto end +) + +if "%1" == "coverage" ( + %SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage + if errorlevel 1 exit /b 1 + echo. + echo.Testing of coverage in the sources finished, look at the ^ +results in %BUILDDIR%/coverage/python.txt. + goto end +) + +if "%1" == "xml" ( + %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The XML files are in %BUILDDIR%/xml. + goto end +) + +if "%1" == "pseudoxml" ( + %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. + goto end +) + +:end diff --git a/src/vscript/squirrel/doc/source/_static/nut.ico b/src/vscript/squirrel/doc/source/_static/nut.ico new file mode 100644 index 00000000..c28977b0 Binary files /dev/null and b/src/vscript/squirrel/doc/source/_static/nut.ico differ diff --git a/src/vscript/squirrel/doc/source/_static/simple_nut.png b/src/vscript/squirrel/doc/source/_static/simple_nut.png new file mode 100644 index 00000000..4c0e8a20 Binary files /dev/null and b/src/vscript/squirrel/doc/source/_static/simple_nut.png differ diff --git a/src/vscript/squirrel/doc/source/conf.py b/src/vscript/squirrel/doc/source/conf.py new file mode 100644 index 00000000..996eafef --- /dev/null +++ b/src/vscript/squirrel/doc/source/conf.py @@ -0,0 +1,288 @@ +# -*- coding: utf-8 -*- +# +# Squirrel documentation build configuration file, created by +# sphinx-quickstart on Sun Jan 31 00:26:52 2016. +# +# This file is execfile()d with the current directory set to its +# containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import sys +import os +import time + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +#sys.path.insert(0, os.path.abspath('.')) + +# -- General configuration ------------------------------------------------ + +# If your documentation needs a minimal Sphinx version, state it here. +#needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ +] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix(es) of source filenames. +# You can specify multiple suffix as a list of string: +# source_suffix = ['.rst', '.md'] +source_suffix = '.rst' + +# The encoding of source files. +#source_encoding = 'utf-8-sig' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = u'Squirrel documentation' +copyright = '2003-%s, Alberto Demichelis' % time.strftime('%Y') +author = u'Alberto Demichelis' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = u'3.1' +# The full version, including alpha/beta/rc tags. +release = u'3.1 stable' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# +# This is also used if you do content translation via gettext catalogs. +# Usually you set "language" from the command line for these cases. +language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +#today = '' +# Else, today_fmt is used as the format for a strftime call. +#today_fmt = '%B %d, %Y' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +exclude_patterns = [] + +# The reST default role (used for this markup: `text`) to use for all +# documents. +#default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +#add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +#add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +#show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# A list of ignored prefixes for module index sorting. +#modindex_common_prefix = [] + +# If true, keep warnings as "system message" paragraphs in the built documents. +#keep_warnings = False + +# If true, `todo` and `todoList` produce output, else they produce nothing. +todo_include_todos = False + + +# -- Options for HTML output ---------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +html_theme = 'sphinx_rtd_theme' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +#html_theme_options = {} + +# Add any paths that contain custom themes here, relative to this directory. +#html_theme_path = [] + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +#html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +#html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +html_logo = 'simple_nut.png' + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +html_favicon = 'nut.ico' + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# Add any extra paths that contain custom files (such as robots.txt or +# .htaccess) here, relative to this directory. These files are copied +# directly to the root of the documentation. +#html_extra_path = [] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +#html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +#html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +#html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +#html_additional_pages = {} + +# If false, no module index is generated. +#html_domain_indices = True + +# If false, no index is generated. +#html_use_index = True + +# If true, the index is split into individual pages for each letter. +#html_split_index = False + +# If true, links to the reST sources are added to the pages. +#html_show_sourcelink = True + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +#html_show_sphinx = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +#html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +#html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +#html_file_suffix = None + +# Language to be used for generating the HTML full-text search index. +# Sphinx supports the following languages: +# 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' +# 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr' +#html_search_language = 'en' + +# A dictionary with options for the search language support, empty by default. +# Now only 'ja' uses this config value +#html_search_options = {'type': 'default'} + +# The name of a javascript file (relative to the configuration directory) that +# implements a search results scorer. If empty, the default will be used. +#html_search_scorer = 'scorer.js' + +# Output file base name for HTML help builder. +htmlhelp_basename = 'squirrel_doc' + +# -- Options for LaTeX output --------------------------------------------- + +latex_elements = { +# The paper size ('letterpaper' or 'a4paper'). +#'papersize': 'letterpaper', + +# The font size ('10pt', '11pt' or '12pt'). +#'pointsize': '10pt', + +# Additional stuff for the LaTeX preamble. +#'preamble': '', + +# Latex figure (float) alignment +#'figure_align': 'htbp', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +_stdauthor = r'Alberto Demichelis' +latex_documents = [ + ('reference/index', 'reference.tex', + 'Squirrel Reference Manual', _stdauthor, 'manual'), + ('stdlib/index', 'stdlib.tex', + 'Squirrel Standard Library', _stdauthor, 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +#latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +#latex_use_parts = False + +# If true, show page references after internal links. +#latex_show_pagerefs = False + +# If true, show URL addresses after external links. +#latex_show_urls = False + +# Documents to append as an appendix to all manuals. +#latex_appendices = [] + +# If false, no module index is generated. +#latex_domain_indices = True + + +# -- Options for manual page output --------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + (master_doc, 'Squirrel', u'Squirrel Documentation', + [author], 1) +] + +# If true, show URL addresses after external links. +#man_show_urls = False + + +# -- Options for Texinfo output ------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + (master_doc, 'Squirrel', u'Squirrel Documentation', + author, 'Squirrel', 'The Programming Language.', + 'Miscellaneous'), +] + +# Documents to append as an appendix to all manuals. +#texinfo_appendices = [] + +# If false, no module index is generated. +#texinfo_domain_indices = True + +# How to display URL addresses: 'footnote', 'no', or 'inline'. +#texinfo_show_urls = 'footnote' + +# If true, do not generate a @detailmenu in the "Top" node's menu. +#texinfo_no_detailmenu = False diff --git a/src/vscript/squirrel/doc/source/index.rst b/src/vscript/squirrel/doc/source/index.rst new file mode 100644 index 00000000..0cc1bb4d --- /dev/null +++ b/src/vscript/squirrel/doc/source/index.rst @@ -0,0 +1,24 @@ +.. Squirrel documentation master file, created by + sphinx-quickstart on Sun Jan 31 00:26:52 2016. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Squirrel's documentation +========================================= + +Contents: + +.. toctree:: + :maxdepth: 1 + + reference/index.rst + stdlib/index.rst + + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`search` + + diff --git a/src/vscript/squirrel/doc/source/reference/api/bytecode_serialization.rst b/src/vscript/squirrel/doc/source/reference/api/bytecode_serialization.rst new file mode 100644 index 00000000..0e1f586a --- /dev/null +++ b/src/vscript/squirrel/doc/source/reference/api/bytecode_serialization.rst @@ -0,0 +1,32 @@ +.. _api_ref_bytecode_serialization: + +====================== +Bytecode serialization +====================== + +.. _sq_readclosure: + +.. c:function:: SQRESULT sq_readclosure(HSQUIRRELVM v, SQREADFUNC readf, SQUserPointer up) + + :param HSQUIRRELVM v: the target VM + :param SQREADFUNC readf: pointer to a read function that will be invoked by the vm during the serialization. + :param SQUserPointer up: pointer that will be passed to each call to the read function + :returns: a SQRESULT + +serialize (read) a closure and pushes it on top of the stack, the source is user defined through a read callback. + + + + + +.. _sq_writeclosure: + +.. c:function:: SQRESULT sq_writeclosure(HSQUIRRELVM v, SQWRITEFUNC writef, SQUserPointer up) + + :param HSQUIRRELVM v: the target VM + :param SQWRITEFUNC writef: pointer to a write function that will be invoked by the vm during the serialization. + :param SQUserPointer up: pointer that will be passed to each call to the write function + :returns: a SQRESULT + :remarks: closures with free variables cannot be serialized + +serializes(writes) the closure on top of the stack, the destination is user defined through a write callback. diff --git a/src/vscript/squirrel/doc/source/reference/api/calls.rst b/src/vscript/squirrel/doc/source/reference/api/calls.rst new file mode 100644 index 00000000..7ba43fb6 --- /dev/null +++ b/src/vscript/squirrel/doc/source/reference/api/calls.rst @@ -0,0 +1,130 @@ +.. _api_ref_calls: + +===== +Calls +===== + +.. _sq_call: + +.. c:function:: SQRESULT sq_call(HSQUIRRELVM v, SQInteger params, SQBool retval, SQBool raiseerror) + + :param HSQUIRRELVM v: the target VM + :param SQInteger params: number of parameters of the function + :param SQBool retval: if true the function will push the return value in the stack + :param SQBool raiseerror: if true, if a runtime error occurs during the execution of the call, the vm will invoke the error handler. + :returns: a SQRESULT + +calls a closure or a native closure. The function pops all the parameters and leave the closure in the stack; if retval is true the return value of the closure is pushed. If the execution of the function is suspended through sq_suspendvm(), the closure and the arguments will not be automatically popped from the stack. + +When using to create an instance, push a dummy parameter to be filled with the newly-created instance for the constructor's 'this' parameter. + + + +.. _sq_getcallee: + +.. c:function:: SQRESULT sq_getcallee(HSQUIRRELVM v) + + :param HSQUIRRELVM v: the target VM + :returns: a SQRESULT + +push in the stack the currently running closure. + + + + + +.. _sq_getlasterror: + +.. c:function:: SQRESULT sq_getlasterror(HSQUIRRELVM v) + + :param HSQUIRRELVM v: the target VM + :returns: a SQRESULT + :remarks: the pushed error descriptor can be any valid squirrel type. + +pushes the last error in the stack. + + + + + +.. _sq_getlocal: + +.. c:function:: const SQChar * sq_getlocal(HSQUIRRELVM v, SQUnsignedInteger level, SQUnsignedInteger nseq) + + :param HSQUIRRELVM v: the target VM + :param SQUnsignedInteger level: the function index in the calls stack, 0 is the current function + :param SQUnsignedInteger nseq: the index of the local variable in the stack frame (0 is 'this') + :returns: the name of the local variable if a variable exists at the given level/seq otherwise NULL. + +Returns the name of a local variable given stackframe and sequence in the stack and pushes is current value. Free variables are treated as local variables, by sq_getlocal(), and will be returned as they would be at the base of the stack, just before the real local variables. + + + + + +.. _sq_reseterror: + +.. c:function:: void sq_reseterror(HSQUIRRELVM v) + + :param HSQUIRRELVM v: the target VM + +reset the last error in the virtual machine to null + + + + + +.. _sq_resume: + +.. c:function:: SQRESULT sq_resume(HSQUIRRELVM v, SQBool retval, SQBool raiseerror) + + :param HSQUIRRELVM v: the target VM + :param SQBool retval: if true the function will push the return value in the stack + :param SQBool raiseerror: if true, if a runtime error occurs during the execution of the call, the vm will invoke the error handler. + :returns: a SQRESULT + :remarks: if retval != 0 the return value of the generator is pushed. + +resumes the generator at the top position of the stack. + + +.. _sq_tailcall: + +.. c:function:: SQRESULT sq_tailcall(HSQUIRRELVM v, SQInteger nparams) + + :param HSQUIRRELVM v: the target VM + :param SQInteger params: number of parameters of the function + + Calls a closure and removes the caller function from the call stack. + This function must be invoke from a native closure and + he return value of sq_tailcall must be returned by the caller function(see example). + +*.eg* + +:: + + SQInteger tailcall_something_example(HSQUIRRELVM v) + { + //push closure and parameters here + ... + return sq_tailcall(v,2); + } + +.. _sq_throwerror: + +.. c:function:: SQRESULT sq_throwerror(HSQUIRRELVM v, const SQChar * err) + + :param HSQUIRRELVM v: the target VM + :param const SQChar * err: the description of the error that has to be thrown + :returns: the value that has to be returned by a native closure in order to throw an exception in the virtual machine. + +sets the last error in the virtual machine and returns the value that has to be returned by a native closure in order to trigger an exception in the virtual machine. + + +.. _sq_throwobject: + +.. c:function:: SQRESULT sq_throwobject(HSQUIRRELVM v) + + :param HSQUIRRELVM v: the target VM + :returns: the value that has to be returned by a native closure in order to throw an exception in the virtual machine. + +pops a value from the stack sets it as the last error in the virtual machine. Returns the value that has to be returned by a native closure in order to trigger an exception in the virtual machine (aka SQ_ERROR). diff --git a/src/vscript/squirrel/doc/source/reference/api/compiler.rst b/src/vscript/squirrel/doc/source/reference/api/compiler.rst new file mode 100644 index 00000000..7fcb933d --- /dev/null +++ b/src/vscript/squirrel/doc/source/reference/api/compiler.rst @@ -0,0 +1,79 @@ +.. _api_ref_compiler: + +======== +Compiler +======== + +.. _sq_compile: + +.. c:function:: SQRESULT sq_compile(HSQUIRRELVM v, HSQLEXREADFUNC read, SQUserPointer p, const SQChar * sourcename, SQBool raiseerror) + + :param HSQUIRRELVM v: the target VM + :param HSQLEXREADFUNC read: a pointer to a read function that will feed the compiler with the program. + :param SQUserPointer p: a user defined pointer that will be passed by the compiler to the read function at each invocation. + :param const SQChar * sourcename: the symbolic name of the program (used only for more meaningful runtime errors) + :param SQBool raiseerror: if this value is true the compiler error handler will be called in case of an error + :returns: a SQRESULT. If the sq_compile fails nothing is pushed in the stack. + :remarks: in case of an error the function will call the function set by sq_setcompilererrorhandler(). + +compiles a squirrel program; if it succeeds, push the compiled script as function in the stack. + + + + + +.. _sq_compilebuffer: + +.. c:function:: SQRESULT sq_compilebuffer(HSQUIRRELVM v, const SQChar* s, SQInteger size, const SQChar * sourcename, SQBool raiseerror) + + :param HSQUIRRELVM v: the target VM + :param const SQChar* s: a pointer to the buffer that has to be compiled. + :param SQInteger size: size in characters of the buffer passed in the parameter 's'. + :param const SQChar * sourcename: the symbolic name of the program (used only for more meaningful runtime errors) + :param SQBool raiseerror: if this value true the compiler error handler will be called in case of an error + :returns: a SQRESULT. If the sq_compilebuffer fails nothing is pushed in the stack. + :remarks: in case of an error the function will call the function set by sq_setcompilererrorhandler(). + +compiles a squirrel program from a memory buffer; if it succeeds, push the compiled script as function in the stack. + + + + + +.. _sq_enabledebuginfo: + +.. c:function:: void sq_enabledebuginfo(HSQUIRRELVM v, SQBool enable) + + :param HSQUIRRELVM v: the target VM + :param SQBool enable: if true enables the debug info generation, if == 0 disables it. + :remarks: The function affects all threads as well. + +enable/disable the debug line information generation at compile time. + + + + + +.. _sq_notifyallexceptions: + +.. c:function:: void sq_notifyallexceptions(HSQUIRRELVM v, SQBool enable) + + :param HSQUIRRELVM v: the target VM + :param SQBool enable: if true enables the error callback notification of handled exceptions. + :remarks: By default the VM will invoke the error callback only if an exception is not handled (no try/catch traps are present in the call stack). If notifyallexceptions is enabled, the VM will call the error callback for any exception even if between try/catch blocks. This feature is useful for implementing debuggers. + +enable/disable the error callback notification of handled exceptions. + + + + + +.. _sq_setcompilererrorhandler: + +.. c:function:: void sq_setcompilererrorhandler(HSQUIRRELVM v, SQCOMPILERERROR f) + + :param HSQUIRRELVM v: the target VM + :param SQCOMPILERERROR f: A pointer to the error handler function + :remarks: if the parameter f is NULL no function will be called when a compiler error occurs. The compiler error handler is shared between friend VMs. + +sets the compiler error handler function diff --git a/src/vscript/squirrel/doc/source/reference/api/debug_interface.rst b/src/vscript/squirrel/doc/source/reference/api/debug_interface.rst new file mode 100644 index 00000000..ac929e14 --- /dev/null +++ b/src/vscript/squirrel/doc/source/reference/api/debug_interface.rst @@ -0,0 +1,72 @@ +.. _api_ref_debug_interface: + +=============== +Debug interface +=============== + +.. _sq_getfunctioninfo: + +.. c:function:: SQRESULT sq_getfunctioninfo(HSQUIRRELVM v, SQInteger level, SQFunctionInfo * fi) + + :param HSQUIRRELVM v: the target VM + :param SQInteger level: calls stack level + :param SQFunctionInfo * fi: pointer to the SQFunctionInfo structure that will store the closure informations + :returns: a SQRESULT. + :remarks: the member 'funcid' of the returned SQFunctionInfo structure is a unique identifier of the function; this can be useful to identify a specific piece of squirrel code in an application like for instance a profiler. this method will fail if the closure in the stack is a native C closure. + + + +*.eg* + +:: + + + typedef struct tagSQFunctionInfo { + SQUserPointer funcid; //unique idetifier for a function (all it's closures will share the same funcid) + const SQChar *name; //function name + const SQChar *source; //function source file name + }SQFunctionInfo; + + + + + + + +.. _sq_setdebughook: + +.. c:function:: void sq_setdebughook(HSQUIRRELVM v) + + :param HSQUIRRELVM v: the target VM + :remarks: In order to receive a 'per line' callback, is necessary to compile the scripts with the line informations. Without line informations activated, only the 'call/return' callbacks will be invoked. + +pops a closure from the stack an sets it as debug hook. When a debug hook is set it overrides any previously set native or non native hooks. if the hook is null the debug hook will be disabled. + + + + + +.. _sq_setnativedebughook: + +.. c:function:: void sq_setnativedebughook(HSQUIRRELVM v, SQDEBUGHOOK hook) + + :param HSQUIRRELVM v: the target VM + :param SQDEBUGHOOK hook: the native hook function + :remarks: In order to receive a 'per line' callback, is necessary to compile the scripts with the line informations. Without line informations activated, only the 'call/return' callbacks will be invoked. + +sets the native debug hook. When a native hook is set it overrides any previously set native or non native hooks. if the hook is NULL the debug hook will be disabled. + + + + + +.. _sq_stackinfos: + +.. c:function:: SQRESULT sq_stackinfos(HSQUIRRELVM v, SQInteger level, SQStackInfos * si) + + :param HSQUIRRELVM v: the target VM + :param SQInteger level: calls stack level + :param SQStackInfos * si: pointer to the SQStackInfos structure that will store the stack informations + :returns: a SQRESULT. + +retrieve the calls stack informations of a ceratain level in the calls stack. diff --git a/src/vscript/squirrel/doc/source/reference/api/garbage_collector.rst b/src/vscript/squirrel/doc/source/reference/api/garbage_collector.rst new file mode 100644 index 00000000..cfe03e68 --- /dev/null +++ b/src/vscript/squirrel/doc/source/reference/api/garbage_collector.rst @@ -0,0 +1,27 @@ +.. _api_ref_garbage_collector: + +================= +Garbage Collector +================= + +.. _sq_collectgarbage: + +.. c:function:: SQInteger sq_collectgarbage(HSQUIRRELVM v) + + :param HSQUIRRELVM v: the target VM + :remarks: this api only works with garbage collector builds (NO_GARBAGE_COLLECTOR is not defined) + +runs the garbage collector and returns the number of reference cycles found (and deleted) + + + + + +.. _sq_resurrectunreachable: + +.. c:function:: SQRESULT sq_resurrectunreachable(HSQUIRRELVM v) + + :param HSQUIRRELVM v: the target VM + :remarks: this api only works with garbage collector builds (NO_GARBAGE_COLLECTOR is not defined) + +runs the garbage collector and pushes an array in the stack containing all unreachable object found. If no unreachable object is found, null is pushed instead. This function is meant to help debug reference cycles. diff --git a/src/vscript/squirrel/doc/source/reference/api/object_creation_and_handling.rst b/src/vscript/squirrel/doc/source/reference/api/object_creation_and_handling.rst new file mode 100644 index 00000000..5345c05c --- /dev/null +++ b/src/vscript/squirrel/doc/source/reference/api/object_creation_and_handling.rst @@ -0,0 +1,695 @@ +.. _api_ref_object_creation_and_handling: + +============================ +Object creation and handling +============================ + +.. _sq_bindenv: + +.. c:function:: SQRESULT sq_bindenv(HSQUIRRELVM v, SQInteger idx) + + :param HSQUIRRELVM v: the target VM + :param SQInteger idx: index of the target closure + :returns: a SQRESULT + :remarks: the cloned closure holds the environment object as weak reference + +pops an object from the stack (must be a table, instance, or class); clones the closure at position idx in the stack and sets the popped object as environment of the cloned closure. Then pushes the new cloned closure on top of the stack. + + + + + +.. _sq_createinstance: + +.. c:function:: SQRESULT sq_createinstance(HSQUIRRELVM v, SQInteger idx) + + :param HSQUIRRELVM v: the target VM + :param SQInteger idx: index of the target class + :returns: a SQRESULT + :remarks: the function doesn't invoke the instance contructor. To create an instance and automatically invoke its contructor, sq_call must be used instead. + +creates an instance of the class at 'idx' position in the stack. The new class instance is pushed on top of the stack. + + + + + +.. _sq_getbool: + +.. c:function:: SQRESULT sq_getbool(HSQUIRRELVM v, SQInteger idx, SQBool * b) + + :param HSQUIRRELVM v: the target VM + :param SQInteger idx: an index in the stack + :param SQBool * b: A pointer to the bool that will store the value + :returns: a SQRESULT + +gets the value of the bool at the idx position in the stack. + + + + + +.. _sq_getbyhandle: + +.. c:function:: SQRESULT sq_getbyhandle(HSQUIRRELVM v, SQInteger idx, HSQMEMBERHANDLE* handle) + + :param HSQUIRRELVM v: the target VM + :param SQInteger idx: an index in the stack pointing to the class or instance + :param HSQMEMBERHANDLE* handle: a pointer to the member handle + :returns: a SQRESULT + +pushes the value of a class or instance member using a member handle (see sq_getmemberhandle) + + + + + +.. _sq_getclosureinfo: + +.. c:function:: SQRESULT sq_getclosureinfo(HSQUIRRELVM v, SQInteger idx, SQInteger * nparams, SQInteger * nfreevars) + + :param HSQUIRRELVM v: the target VM + :param SQInteger idx: index of the target closure + :param SQInteger * nparams: a pointer to an integer that will store the number of parameters + :param SQInteger * nfreevars: a pointer to an integer that will store the number of free variables + :returns: an SQRESULT + +retrieves number of parameters and number of freevariables from a squirrel closure. + + + + + +.. _sq_getclosurename: + +.. c:function:: SQRESULT sq_getclosurename(HSQUIRRELVM v, SQInteger idx) + + :param HSQUIRRELVM v: the target VM + :param SQInteger idx: index of the target closure + :returns: an SQRESULT + +pushes the name of the closure at position idx in the stack. Note that the name can be a string or null if the closure is anonymous or a native closure with no name assigned to it. + + + + + +.. _sq_getclosureroot: + +.. c:function:: SQRESULT sq_getclosureroot(HSQUIRRELVM v, SQInteger idx) + + :param HSQUIRRELVM v: the target VM + :param SQInteger idx: index of the target closure + :returns: an SQRESULT + +pushes the root table of the closure at position idx in the stack + + + + + +.. _sq_getfloat: + +.. c:function:: SQRESULT sq_getfloat(HSQUIRRELVM v, SQInteger idx, SQFloat * f) + + :param HSQUIRRELVM v: the target VM + :param SQInteger idx: an index in the stack + :param SQFloat * f: A pointer to the float that will store the value + :returns: a SQRESULT + +gets the value of the float at the idx position in the stack. + + + + + +.. _sq_gethash: + +.. c:function:: SQHash sq_gethash(HSQUIRRELVM v, SQInteger idx) + + :param HSQUIRRELVM v: the target VM + :param SQInteger idx: an index in the stack + :returns: the hash key of the value at the position idx in the stack + :remarks: the hash value function is the same used by the VM. + +returns the hash key of a value at the idx position in the stack. + + + + + +.. _sq_getinstanceup: + +.. c:function:: SQRESULT sq_getinstanceup(HSQUIRRELVM v, SQInteger idx, SQUserPointer * up, SQUSerPointer typetag) + + :param HSQUIRRELVM v: the target VM + :param SQInteger idx: an index in the stack + :param SQUserPointer * up: a pointer to the userpointer that will store the result + :param SQUSerPointer typetag: the typetag that has to be checked, if this value is set to 0 the typetag is ignored. + :returns: a SQRESULT + +gets the userpointer of the class instance at position idx in the stack. if the parameter 'typetag' is different than 0, the function checks that the class or a base class of the instance is tagged with the specified tag; if not the function fails. If 'typetag' is 0 the function will ignore the tag check. + + + + + +.. _sq_getinteger: + +.. c:function:: SQRESULT sq_getinteger(HSQUIRRELVM v, SQInteger idx, SQInteger * i) + + :param HSQUIRRELVM v: the target VM + :param SQInteger idx: an index in the stack + :param SQInteger * i: A pointer to the integer that will store the value + :returns: a SQRESULT + +gets the value of the integer at the idx position in the stack. + + + + + +.. _sq_getmemberhandle: + +.. c:function:: SQRESULT sq_getmemberhandle(HSQUIRRELVM v, SQInteger idx, HSQMEMBERHANDLE* handle) + + :param HSQUIRRELVM v: the target VM + :param SQInteger idx: an index in the stack pointing to the class + :param HSQMEMBERHANDLE* handle: a pointer to the variable that will store the handle + :returns: a SQRESULT + :remarks: This method works only with classes. A handle retrieved through a class can be later used to set or get values from one of the class instances. Handles retrieved from base classes are still valid in derived classes and respect inheritance rules. + +pops a value from the stack and uses it as index to fetch the handle of a class member. The handle can be later used to set or get the member value using sq_getbyhandle(), sq_setbyhandle(). + + + + + +.. _sq_getreleasehook: + +.. c:function:: SQRELEASEHOOK sq_getreleasehook(HSQUIRRELVM v, SQInteger idx) + + :param HSQUIRRELVM v: the target VM + :param SQInteger idx: an index in the stack + :remarks: if the object that position idx is not an userdata, class instance or class the function returns NULL. + +gets the release hook of the userdata, class instance or class at position idx in the stack. + + + + + +.. _sq_getscratchpad: + +.. c:function:: SQChar * sq_getscratchpad(HSQUIRRELVM v, SQInteger minsize) + + :param HSQUIRRELVM v: the target VM + :param SQInteger minsize: the requested size for the scratchpad buffer + :remarks: the buffer is valid until the next call to sq_getscratchpad + +returns a pointer to a memory buffer that is at least as big as minsize. + + + + + +.. _sq_getsize: + +.. c:function:: SQObjectType sq_getsize(HSQUIRRELVM v, SQInteger idx) + + :param HSQUIRRELVM v: the target VM + :param SQInteger idx: an index in the stack + :returns: the size of the value at the position idx in the stack + :remarks: this function only works with strings, arrays, tables, classes, instances, and userdata if the value is not a valid type, the function will return -1. + +returns the size of a value at the idx position in the stack. If the value is a class or a class instance the size returned is the size of the userdata buffer (see sq_setclassudsize). + + + + + +.. _sq_getstring: + +.. c:function:: SQRESULT sq_getstring(HSQUIRRELVM v, SQInteger idx, const SQChar ** c) + + :param HSQUIRRELVM v: the target VM + :param SQInteger idx: an index in the stack + :param const SQChar ** c: a pointer to the pointer that will point to the string + :returns: a SQRESULT + +gets a pointer to the string at the idx position in the stack. + + + + + +.. _sq_getstringandsize: + +.. c:function:: SQRESULT sq_getstringandsize(HSQUIRRELVM v, SQInteger idx, const SQChar ** c, SQInteger* size) + + :param HSQUIRRELVM v: the target VM + :param SQInteger idx: an index in the stack + :param const SQChar ** c: a pointer to the pointer that will point to the string + :param SQInteger * size: a pointer to a SQInteger which will receive the size of the string + :returns: a SQRESULT + +gets a pointer to the string at the idx position in the stack; additionally retrieves its size. + + + + +.. _sq_getthread: + +.. c:function:: SQRESULT sq_getthread(HSQUIRRELVM v, SQInteger idx, HSQUIRRELVM* v) + + :param HSQUIRRELVM v: the target VM + :param SQInteger idx: an index in the stack + :param HSQUIRRELVM* v: A pointer to the variable that will store the thread pointer + :returns: a SQRESULT + +gets a pointer to the thread the idx position in the stack. + + + + + +.. _sq_gettype: + +.. c:function:: SQObjectType sq_gettype(HSQUIRRELVM v, SQInteger idx) + + :param HSQUIRRELVM v: the target VM + :param SQInteger idx: an index in the stack + :returns: the type of the value at the position idx in the stack + +returns the type of the value at the position idx in the stack + + + + + +.. _sq_gettypetag: + +.. c:function:: SQRESULT sq_gettypetag(HSQUIRRELVM v, SQInteger idx, SQUserPointer * typetag) + + :param HSQUIRRELVM v: the target VM + :param SQInteger idx: an index in the stack + :param SQUserPointer * typetag: a pointer to the variable that will store the tag + :returns: a SQRESULT + :remarks: the function works also with instances. if the taget object is an instance, the typetag of it's base class is fetched. + +gets the typetag of the object (userdata or class) at position idx in the stack. + + + + + +.. _sq_getuserdata: + +.. c:function:: SQRESULT sq_getuserdata(HSQUIRRELVM v, SQInteger idx, SQUserPointer * p, SQUserPointer * typetag) + + :param HSQUIRRELVM v: the target VM + :param SQInteger idx: an index in the stack + :param SQUserPointer * p: A pointer to the userpointer that will point to the userdata's payload + :param SQUserPointer * typetag: A pointer to a SQUserPointer that will store the userdata tag(see sq_settypetag). The parameter can be NULL. + :returns: a SQRESULT + +gets a pointer to the value of the userdata at the idx position in the stack. + + + + + +.. _sq_getuserpointer: + +.. c:function:: SQRESULT sq_getuserpointer(HSQUIRRELVM v, SQInteger idx, SQUserPointer * p) + + :param HSQUIRRELVM v: the target VM + :param SQInteger idx: an index in the stack + :param SQUserPointer * p: A pointer to the userpointer that will store the value + :returns: a SQRESULT + +gets the value of the userpointer at the idx position in the stack. + + + + + +.. _sq_newarray: + +.. c:function:: void sq_newarray(HSQUIRRELVM v, SQInteger size) + + :param HSQUIRRELVM v: the target VM + :param SQInteger size: the size of the array that as to be created + +creates a new array and pushes it in the stack + + + + + +.. _sq_newclass: + +.. c:function:: SQRESULT sq_newclass(HSQUIRRELVM v, SQBool hasbase) + + :param HSQUIRRELVM v: the target VM + :param SQBool hasbase: if the parameter is true the function expects a base class on top of the stack. + :returns: a SQRESULT + +creates a new class object. If the parameter 'hasbase' is different than 0, the function pops a class from the stack and inherits the new created class from it. The new class is pushed in the stack. + + + + + +.. _sq_newclosure: + +.. c:function:: void sq_newclosure(HSQUIRRELVM v, HSQFUNCTION func, SQInteger nfreevars) + + :param HSQUIRRELVM v: the target VM + :param HSQFUNCTION func: a pointer to a native-function + :param SQInteger nfreevars: number of free variables(can be 0) + +create a new native closure, pops n values set those as free variables of the new closure, and push the new closure in the stack. + + + + + +.. _sq_newtable: + +.. c:function:: void sq_newtable(HSQUIRRELVM v) + + :param HSQUIRRELVM v: the target VM + +creates a new table and pushes it in the stack + + + + + +.. _sq_newtableex: + +.. c:function:: void sq_newtableex(HSQUIRRELVM v, SQInteger initialcapacity) + + :param HSQUIRRELVM v: the target VM + :param SQInteger initialcapacity: number of key/value pairs to preallocate + +creates a new table and pushes it in the stack. This function allows you to specify the initial capacity of the table to prevent unnecessary rehashing when the number of slots required is known at creation-time. + + + + + +.. _sq_newuserdata: + +.. c:function:: SQUserPointer sq_newuserdata(HSQUIRRELVM v, SQUnsignedInteger size) + + :param HSQUIRRELVM v: the target VM + :param SQUnsignedInteger size: the size of the userdata that as to be created in bytes + +creates a new userdata and pushes it in the stack + + + + + +.. _sq_pushbool: + +.. c:function:: void sq_pushbool(HSQUIRRELVM v, SQBool b) + + :param HSQUIRRELVM v: the target VM + :param SQBool b: the bool that has to be pushed(SQTrue or SQFalse) + +pushes a bool into the stack + + + + + +.. _sq_pushfloat: + +.. c:function:: void sq_pushfloat(HSQUIRRELVM v, SQFloat f) + + :param HSQUIRRELVM v: the target VM + :param SQFloat f: the float that has to be pushed + +pushes a float into the stack + + + + + +.. _sq_pushinteger: + +.. c:function:: void sq_pushinteger(HSQUIRRELVM v, SQInteger n) + + :param HSQUIRRELVM v: the target VM + :param SQInteger n: the integer that has to be pushed + +pushes an integer into the stack + + + + + +.. _sq_pushnull: + +.. c:function:: void sq_pushnull(HSQUIRRELVM v) + + :param HSQUIRRELVM v: the target VM + +pushes a null value into the stack + + + + + +.. _sq_pushstring: + +.. c:function:: void sq_pushstring(HSQUIRRELVM v, const SQChar * s, SQInteger len) + + :param HSQUIRRELVM v: the target VM + :param const SQChar * s: pointer to the string that has to be pushed + :param SQInteger len: length of the string pointed by s + :remarks: if the parameter len is less than 0 the VM will calculate the length using strlen(s) + +pushes a string in the stack + + + + + +.. _sq_pushuserpointer: + +.. c:function:: void sq_pushuserpointer(HSQUIRRELVM v, SQUserPointer p) + + :param HSQUIRRELVM v: the target VM + :param SQUserPointer p: the pointer that as to be pushed + +pushes a userpointer into the stack + + + + + +.. _sq_setbyhandle: + +.. c:function:: SQRESULT sq_setbyhandle(HSQUIRRELVM v, SQInteger idx, HSQMEMBERHANDLE* handle) + + :param HSQUIRRELVM v: the target VM + :param SQInteger idx: an index in the stack pointing to the class + :param HSQMEMBERHANDLE* handle: a pointer the member handle + :returns: a SQRESULT + +pops a value from the stack and sets it to a class or instance member using a member handle (see sq_getmemberhandle) + + + + + +.. _sq_setclassudsize: + +.. c:function:: SQRESULT sq_setclassudsize(HSQUIRRELVM v, SQInteger idx, SQInteger udsize) + + :param HSQUIRRELVM v: the target VM + :param SQInteger idx: an index in the stack pointing to the class + :param SQInteger udsize: size in bytes reserved for user data + :returns: a SQRESULT + +Sets the user data size of a class. If a class 'user data size' is greater than 0. When an instance of the class is created additional space will be reserved at the end of the memory chunk where the instance is stored. The userpointer of the instance will also be automatically set to this memory area. This allows you to minimize allocations in applications that have to carry data along with the class instance. + + + + + +.. _sq_setclosureroot: + +.. c:function:: SQRESULT sq_setclosureroot(HSQUIRRELVM v, SQInteger idx) + + :param HSQUIRRELVM v: the target VM + :param SQInteger idx: index of the target closure + :returns: an SQRESULT + +pops a table from the stack and sets it as root of the closure at position idx in the stack + + + + + +.. _sq_setinstanceup: + +.. c:function:: SQRESULT sq_setinstanceup(HSQUIRRELVM v, SQInteger idx, SQUserPointer up) + + :param HSQUIRRELVM v: the target VM + :param SQInteger idx: an index in the stack + :param SQUserPointer up: an arbitrary user pointer + :returns: a SQRESULT + +sets the userpointer of the class instance at position idx in the stack. + + + + + +.. _sq_setnativeclosurename: + +.. c:function:: SQRESULT sq_setnativeclosurename(HSQUIRRELVM v, SQInteger idx, const SQChar * name) + + :param HSQUIRRELVM v: the target VM + :param SQInteger idx: index of the target native closure + :param const SQChar * name: the name that has to be set + :returns: an SQRESULT + +sets the name of the native closure at the position idx in the stack. The name of a native closure is purely for debug purposes. The name is retrieved through the function sq_stackinfos() while the closure is in the call stack. + + + + + +.. _sq_setparamscheck: + +.. c:function:: SQRESULT sq_setparamscheck(HSQUIRRELVM v, SQInteger nparamscheck, const SQChar * typemask) + + :param HSQUIRRELVM v: the target VM + :param SQInteger nparamscheck: defines the parameters number check policy (0 disables the param checking). If nparamscheck is greater than 0, the VM ensures that the number of parameters is exactly the number specified in nparamscheck (eg. if nparamscheck == 3 the function can only be called with 3 parameters). If nparamscheck is less than 0 the VM ensures that the closure is called with at least the absolute value of the number specified in nparamcheck (eg. nparamscheck == -3 will check that the function is called with at least 3 parameters). The hidden parameter 'this' is included in this number; free variables aren't. If SQ_MATCHTYPEMASKSTRING is passed instead of the number of parameters, the function will automatically infer the number of parameters to check from the typemask (eg. if the typemask is ".sn", it is like passing 3). + :param const SQChar * typemask: defines a mask to validate the parametes types passed to the function. If the parameter is NULL, no typechecking is applied (default). + :remarks: The typemask consists in a zero terminated string that represent the expected parameter type. The types are expressed as follows: 'o' null, 'i' integer, 'f' float, 'n' integer or float, 's' string, 't' table, 'a' array, 'u' userdata, 'c' closure and nativeclosure, 'g' generator, 'p' userpointer, 'v' thread, 'x' instance(class instance), 'y' class, 'b' bool. and '.' any type. The symbol '|' can be used as 'or' to accept multiple types on the same parameter. There isn't any limit on the number of 'or' that can be used. Spaces are ignored so can be inserted between types to increase readability. For instance to check a function that expect a table as 'this' a string as first parameter and a number or a userpointer as second parameter, the string would be "tsn|p" (table,string,number or userpointer). If the parameters mask is contains fewer parameters than 'nparamscheck', the remaining parameters will not be typechecked. + +Sets the parameter validation scheme for the native closure at the top position in the stack. Allows you to validate the number of parameters accepted by the function and optionally their types. If the function call does not comply with the parameter schema set by sq_setparamscheck, an exception is thrown. + +*.eg* + +:: + + //example + SQInteger testy(HSQUIRRELVM v) + { + SQUserPointer p; + const SQChar *s; + SQInteger i; + //no type checking, if the call complies with the mask + //surely the functions will succeed. + sq_getuserdata(v,1,&p,NULL); + sq_getstring(v,2,&s); + sq_getinteger(v,3,&i); + //... do something + return 0; + } + + //the reg code + + //....stuff + sq_newclosure(v,testy,0); + //expects exactly 3 parameters(userdata,string,number) + sq_setparamscheck(v,3,_SC("usn")); + //....stuff + + + + + + +.. _sq_setreleasehook: + +.. c:function:: void sq_setreleasehook(HSQUIRRELVM v, SQInteger idx, SQRELEASEHOOK hook) + + :param HSQUIRRELVM v: the target VM + :param SQInteger idx: an index in the stack + :param SQRELEASEHOOK hook: a function pointer to the hook(see sample below) + :remarks: the function hook is called by the VM before the userdata memory is deleted. + +sets the release hook of the userdata, class instance, or class at position idx in the stack. + +*.eg* + +:: + + + /* tyedef SQInteger (*SQRELEASEHOOK)(SQUserPointer,SQInteger size); */ + + SQInteger my_release_hook(SQUserPointer p,SQInteger size) + { + /* do something here */ + return 1; + } + + + + + + +.. _sq_settypetag: + +.. c:function:: SQRESULT sq_settypetag(HSQUIRRELVM v, SQInteger idx, SQUserPointer typetag) + + :param HSQUIRRELVM v: the target VM + :param SQInteger idx: an index in the stack + :param SQUserPointer typetag: an arbitrary SQUserPointer + :returns: a SQRESULT + +sets the typetag of the object (userdata or class) at position idx in the stack. + + + + + +.. _sq_tobool: + +.. c:function:: void sq_tobool(HSQUIRRELVM v, SQInteger idx, SQBool * b) + + :param HSQUIRRELVM v: the target VM + :param SQInteger idx: an index in the stack + :param SQBool * b: A pointer to the bool that will store the value + :remarks: if the object is not a bool the function converts the value to bool according to squirrel's rules. For instance the number 1 will result in true, and the number 0 in false. + +gets the value at position idx in the stack as bool. + + + + + +.. _sq_tostring: + +.. c:function:: void sq_tostring(HSQUIRRELVM v, SQInteger idx) + + :param HSQUIRRELVM v: the target VM + :param SQInteger idx: an index in the stack + +converts the object at position idx in the stack to string and pushes the resulting string in the stack. + + + + + +.. _sq_typeof: + +.. c:function:: SQObjectType sq_typeof(HSQUIRRELVM v, SQInteger idx) + + :param HSQUIRRELVM v: the target VM + :param SQInteger idx: an index in the stack + :returns: a SQRESULT + +pushes the type name of the value at the position idx in the stack. It also invokes the _typeof metamethod for tables and class instances that implement it; in that case the pushed object could be something other than a string (is up to the _typeof implementation). + + + diff --git a/src/vscript/squirrel/doc/source/reference/api/object_manipulation.rst b/src/vscript/squirrel/doc/source/reference/api/object_manipulation.rst new file mode 100644 index 00000000..d5fe5336 --- /dev/null +++ b/src/vscript/squirrel/doc/source/reference/api/object_manipulation.rst @@ -0,0 +1,451 @@ +.. _api_ref_object_manipulation: + +==================== +Object manipulation +==================== + +.. _sq_arrayappend: + +.. c:function:: SQRESULT sq_arrayappend(HSQUIRRELVM v, SQInteger idx) + + :param HSQUIRRELVM v: the target VM + :param SQInteger idx: index of the target array in the stack + :returns: a SQRESULT + :remarks: Only works on arrays. + +pops a value from the stack and pushes it in the back of the array at the position idx in the stack. + + + + + +.. _sq_arrayinsert: + +.. c:function:: SQRESULT sq_arrayinsert(HSQUIRRELVM v, SQInteger idx, SQInteger destpos) + + :param HSQUIRRELVM v: the target VM + :param SQInteger idx: index of the target array in the stack + :param SQInteger destpos: the position in the array where the item has to be inserted + :returns: a SQRESULT + :remarks: Only works on arrays. + +pops a value from the stack and inserts it in an array at the specified position + + + + + +.. _sq_arraypop: + +.. c:function:: SQRESULT sq_arraypop(HSQUIRRELVM v, SQInteger idx) + + :param HSQUIRRELVM v: the target VM + :param SQInteger idx: index of the target array in the stack + :returns: a SQRESULT + :remarks: Only works on arrays. + +pops a value from the back of the array at the position idx in the stack. + + + + + +.. _sq_arrayremove: + +.. c:function:: SQRESULT sq_arrayremove(HSQUIRRELVM v, SQInteger idx, SQInteger itemidx) + + :param HSQUIRRELVM v: the target VM + :param SQInteger idx: index of the target array in the stack + :param SQInteger itemidx: the index of the item in the array that has to be removed + :returns: a SQRESULT + :remarks: Only works on arrays. + +removes an item from an array + + + + + +.. _sq_arrayresize: + +.. c:function:: SQRESULT sq_arrayresize(HSQUIRRELVM v, SQInteger idx, SQInteger newsize) + + :param HSQUIRRELVM v: the target VM + :param SQInteger idx: index of the target array in the stack + :param SQInteger newsize: requested size of the array + :returns: a SQRESULT + :remarks: Only works on arrays. If newsize if greater than the current size the new array slots will be filled with nulls. + +resizes the array at the position idx in the stack. + + + + + +.. _sq_arrayreverse: + +.. c:function:: SQRESULT sq_arrayreverse(HSQUIRRELVM v, SQInteger idx) + + :param HSQUIRRELVM v: the target VM + :param SQInteger idx: index of the target array in the stack + :returns: a SQRESULT + :remarks: Only works on arrays. + +reverses an array in place. + + + + + +.. _sq_clear: + +.. c:function:: SQRESULT sq_clear(HSQUIRRELVM v, SQInteger idx) + + :param HSQUIRRELVM v: the target VM + :param SQInteger idx: index of the target object in the stack + :returns: a SQRESULT + :remarks: Only works on tables and arrays. + +clears all the elements of the table/array at position idx in the stack. + + + + + +.. _sq_clone: + +.. c:function:: SQRESULT sq_clone(HSQUIRRELVM v, SQInteger idx) + + :param HSQUIRRELVM v: the target VM + :param SQInteger idx: index of the target object in the stack + :returns: a SQRESULT + +pushes a clone of the table, array, or class instance at the position idx. + + + + + +.. _sq_createslot: + +.. c:function:: SQRESULT sq_createslot(HSQUIRRELVM v, SQInteger idx) + + :param HSQUIRRELVM v: the target VM + :param SQInteger idx: index of the target table in the stack + :returns: a SQRESULT + :remarks: invoke the _newslot metamethod in the table delegate. it only works on tables. [this function is deperecated since version 2.0.5 use sq_newslot() instead] + +pops a key and a value from the stack and performs a set operation on the table or class that is at position idx in the stack; if the slot does not exist, it will be created. + + + + + +.. _sq_deleteslot: + +.. c:function:: SQRESULT sq_deleteslot(HSQUIRRELVM v, SQInteger idx, SQBool pushval) + + :param HSQUIRRELVM v: the target VM + :param SQInteger idx: index of the target table in the stack + :param SQBool pushval: if this param is true the function will push the value of the deleted slot. + :returns: a SQRESULT + :remarks: invoke the _delslot metamethod in the table delegate. it only works on tables. + +pops a key from the stack and delete the slot indexed by it from the table at position idx in the stack; if the slot does not exist, nothing happens. + + + + + +.. _sq_get: + +.. c:function:: SQRESULT sq_get(HSQUIRRELVM v, SQInteger idx) + + :param HSQUIRRELVM v: the target VM + :param SQInteger idx: index of the target object in the stack + :returns: a SQRESULT + :remarks: this call will invokes the delegation system like a normal dereference it only works on tables, arrays, classes, instances and userdata; if the function fails, nothing will be pushed in the stack. + +pops a key from the stack and performs a get operation on the object at the position idx in the stack; and pushes the result in the stack. + + + + + +.. _sq_getattributes: + +.. c:function:: SQRESULT sq_getattributes(HSQUIRRELVM v, SQInteger idx) + + :param HSQUIRRELVM v: the target VM + :param SQInteger idx: index of the target class in the stack + :returns: a SQRESULT + +Gets the attribute of a class member. The function pops a key from the stack and pushes the attribute of the class member indexed by they key from a class at position idx in the stack. If key is null the function gets the class level attribute. + + + + + +.. _sq_getbase: + +.. c:function:: SQRESULT sq_getbase(HSQUIRRELVM v, SQInteger idx) + + :param HSQUIRRELVM v: the target VM + :param SQInteger idx: index of the target class in the stack + :returns: a SQRESULT + +pushes the base class of the 'class' at stored position idx in the stack. + + + + + +.. _sq_getclass: + +.. c:function:: SQRESULT sq_getclass(HSQUIRRELVM v, SQInteger idx) + + :param HSQUIRRELVM v: the target VM + :param SQInteger idx: index of the target class instance in the stack + :returns: a SQRESULT + +pushes the class of the 'class instance' at stored position idx in the stack. + + + + + +.. _sq_getdelegate: + +.. c:function:: SQRESULT sq_getdelegate(HSQUIRRELVM v, SQInteger idx) + + :param HSQUIRRELVM v: the target VM + :param SQInteger idx: index of the target object in the stack + :returns: a SQRESULT + +pushes the current delegate of the object at the position idx in the stack. + + + + + +.. _sq_getfreevariable: + +.. c:function:: const SQChar * sq_getfreevariable(HSQUIRRELVM v, SQInteger idx, SQInteger nval) + + :param HSQUIRRELVM v: the target VM + :param SQInteger idx: index of the target object in the stack(closure) + :param SQInteger nval: 0 based index of the free variable(relative to the closure). + :returns: the name of the free variable for pure squirrel closures. NULL in case of error or if the index of the variable is out of range. In case the target closure is a native closure, the return name is always "@NATIVE". + :remarks: The function works for both squirrel closure and native closure. + +gets the value of the free variable of the closure at the position idx in the stack. + + + + + +.. _sq_getweakrefval: + +.. c:function:: SQRESULT sq_getweakrefval(HSQUIRRELVM v, SQInteger idx) + + :param HSQUIRRELVM v: the target VM + :param SQInteger idx: index of the target weak reference + :returns: a SQRESULT + :remarks: if the function fails, nothing is pushed in the stack. + +pushes the object pointed by the weak reference at position idx in the stack. + + + + + +.. _sq_instanceof: + +.. c:function:: SQBool sq_instanceof(HSQUIRRELVM v) + + :param HSQUIRRELVM v: the target VM + :returns: SQTrue if the instance at position -2 in the stack is an instance of the class object at position -1 in the stack. + :remarks: The function doesn't pop any object from the stack. + +Determines if an object is an instance of a certain class. Expects an instance and a class in the stack. + + + + + +.. _sq_newmember: + +.. c:function:: SQRESULT sq_newmember(HSQUIRRELVM v, SQInteger idx, SQBool bstatic) + + :param HSQUIRRELVM v: the target VM + :param SQInteger idx: index of the target table in the stack + :param SQBool bstatic: if SQTrue creates a static member. + :returns: a SQRESULT + :remarks: Invokes the _newmember metamethod in the class. it only works on classes. + +pops a key, a value and an object (which will be set as attribute of the member) from the stack and performs a new slot operation on the class that is at position idx in the stack; if the slot does not exist, it will be created. + + + + + +.. _sq_newslot: + +.. c:function:: SQRESULT sq_newslot(HSQUIRRELVM v, SQInteger idx, SQBool bstatic) + + :param HSQUIRRELVM v: the target VM + :param SQInteger idx: index of the target table in the stack + :param SQBool bstatic: if SQTrue creates a static member. This parameter is only used if the target object is a class. + :returns: a SQRESULT + :remarks: Invokes the _newslot metamethod in the table delegate. it only works on tables and classes. + +pops a key and a value from the stack and performs a set operation on the table or class that is at position idx in the stack, if the slot does not exist it will be created. + + + + + +.. _sq_next: + +.. c:function:: SQRESULT sq_next(HSQUIRRELVM v, SQInteger idx) + + :param HSQUIRRELVM v: the target VM + :param SQInteger idx: index of the target object in the stack + :returns: a SQRESULT + +Pushes in the stack the next key and value of an array, table, or class slot. To start the iteration this function expects a null value on top of the stack; at every call the function will substitute the null value with an iterator and push key and value of the container slot. Every iteration the application has to pop the previous key and value but leave the iterator(that is used as reference point for the next iteration). The function will fail when all slots have been iterated(see Tables and arrays manipulation). + + + + + +.. _sq_rawdeleteslot: + +.. c:function:: SQRESULT sq_rawdeleteslot(HSQUIRRELVM v, SQInteger idx, SQBool pushval) + + :param HSQUIRRELVM v: the target VM + :param SQInteger idx: index of the target table in the stack + :param SQBool pushval: if this param is true the function will push the value of the deleted slot. + :returns: a SQRESULT + +Deletes a slot from a table without employing the _delslot metamethod. Pops a key from the stack and delete the slot indexed by it from the table at position idx in the stack; if the slot does not exist nothing happens. + + + + + +.. _sq_rawget: + +.. c:function:: SQRESULT sq_rawget(HSQUIRRELVM v, SQInteger idx) + + :param HSQUIRRELVM v: the target VM + :param SQInteger idx: index of the target object in the stack + :returns: a SQRESULT + :remarks: Only works on tables and arrays. + +pops a key from the stack and performs a get operation on the object at position idx in the stack, without employing delegation or metamethods. + + + + + +.. _sq_rawnewmember: + +.. c:function:: SQRESULT sq_rawnewmember(HSQUIRRELVM v, SQInteger idx, SQBool bstatic) + + :param HSQUIRRELVM v: the target VM + :param SQInteger idx: index of the target table in the stack + :param SQBool bstatic: if SQTrue creates a static member. + :returns: a SQRESULT + :remarks: it only works on classes. + +pops a key, a value and an object(that will be set as attribute of the member) from the stack and performs a new slot operation on the class that is at position idx in the stack; if the slot does not exist it will be created. + + + + + +.. _sq_rawset: + +.. c:function:: SQRESULT sq_rawset(HSQUIRRELVM v, SQInteger idx) + + :param HSQUIRRELVM v: the target VM + :param SQInteger idx: index of the target object in the stack + :returns: a SQRESULT + :remarks: it only works on tables and arrays. if the function fails nothing will be pushed in the stack. + +pops a key and a value from the stack and performs a set operation on the object at position idx in the stack, without employing delegation or metamethods. + + + + + +.. _sq_set: + +.. c:function:: SQRESULT sq_set(HSQUIRRELVM v, SQInteger idx) + + :param HSQUIRRELVM v: the target VM + :param SQInteger idx: index of the target object in the stack + :returns: a SQRESULT + :remarks: this call will invoke the delegation system like a normal assignment, it only works on tables, arrays and userdata. + +pops a key and a value from the stack and performs a set operation on the object at position idx in the stack. + + + + + +.. _sq_setattributes: + +.. c:function:: SQRESULT sq_setattributes(HSQUIRRELVM v, SQInteger idx) + + :param HSQUIRRELVM v: the target VM + :param SQInteger idx: index of the target class in the stack. + :returns: a SQRESULT + +Sets the attribute of a class member. The function pops a key and a value from the stack and sets the attribute (indexed by the key) on the class at position idx in the stack. If key is null the function sets the class level attribute. If the function succeed, the old attribute value is pushed in the stack. + + + + + +.. _sq_setdelegate: + +.. c:function:: SQRESULT sq_setdelegate(HSQUIRRELVM v, SQInteger idx) + + :param HSQUIRRELVM v: the target VM + :param SQInteger idx: index of the target object in the stack + :returns: a SQRESULT + :remarks: to remove the delegate from an object, set a null value. + +pops a table from the stack and sets it as the delegate of the object at the position idx in the stack. + + + + + +.. _sq_setfreevariable: + +.. c:function:: SQRESULT sq_setfreevariable(HSQUIRRELVM v, SQInteger idx, SQInteger nval) + + :param HSQUIRRELVM v: the target VM + :param SQInteger idx: index of the target object in the stack + :param SQInteger nval: 0 based index of the free variable(relative to the closure). + :returns: a SQRESULT + +pops a value from the stack and sets it as a free variable of the closure at the position idx in the stack. + + + + + +.. _sq_weakref: + +.. c:function:: void sq_weakref(HSQUIRRELVM v, SQInteger idx) + + :param HSQUIRRELVM v: the target VM + :param SQInteger idx: index to the target object in the stack + :returns: a SQRESULT + :remarks: if the object at idx position is one of (integer, float, bool, null), the object itself is pushed instead of a weak ref. + +pushes a weak reference to the object at position idx in the stack. diff --git a/src/vscript/squirrel/doc/source/reference/api/raw_object_handling.rst b/src/vscript/squirrel/doc/source/reference/api/raw_object_handling.rst new file mode 100644 index 00000000..f655c345 --- /dev/null +++ b/src/vscript/squirrel/doc/source/reference/api/raw_object_handling.rst @@ -0,0 +1,163 @@ +.. _api_ref_raw_object_handling: + +=================== +Raw object handling +=================== + +.. _sq_addref: + +.. c:function:: void sq_addref(HSQUIRRELVM v, HSQOBJECT* po) + + :param HSQUIRRELVM v: the target VM + :param HSQOBJECT* po: pointer to an object handler + +adds a reference to an object handler. + + + + + +.. _sq_getobjtypetag: + +.. c:function:: SQRESULT sq_getobjtypetag(HSQOBJECT* o, SQUserPointer* typetag) + + :param HSQOBJECT* o: pointer to an object handler + :param SQUserPointer* typetag: a pointer to the variable that will store the tag + :returns: a SQRESULT + :remarks: the function works also with instances. if the target object is an instance, the typetag of it's base class is fetched. + +gets the typetag of a raw object reference(userdata or class). + + + + + +.. _sq_getrefcount: + +.. c:function:: SQUnsignedInteger sq_getrefcount(HSQUIRRELVM v, HSQOBJECT* po) + + :param HSQUIRRELVM v: the target VM + :param HSQOBJECT* po: object handler + +returns the number of references of a given object. + + + + + +.. _sq_getstackobj: + +.. c:function:: SQRESULT sq_getstackobj(HSQUIRRELVM v, SQInteger idx, HSQOBJECT* po) + + :param HSQUIRRELVM v: the target VM + :param SQInteger idx: index of the target object in the stack + :param HSQOBJECT* po: pointer to an object handler + :returns: a SQRESULT + +gets an object from the stack and stores it in a object handler. + + + + + +.. _sq_objtobool: + +.. c:function:: SQBool sq_objtobool(HSQOBJECT* po) + + :param HSQOBJECT* po: pointer to an object handler + :remarks: If the object is not a bool will always return false. + +return the bool value of a raw object reference. + + + + + +.. _sq_objtofloat: + +.. c:function:: SQFloat sq_objtofloat(HSQOBJECT* po) + + :param HSQOBJECT* po: pointer to an object handler + :remarks: If the object is an integer will convert it to float. If the object is not a number will always return 0. + +return the float value of a raw object reference. + + + + + +.. _sq_objtointeger: + +.. c:function:: SQInteger sq_objtointeger(HSQOBJECT* po) + + :param HSQOBJECT* po: pointer to an object handler + :remarks: If the object is a float will convert it to integer. If the object is not a number will always return 0. + +return the integer value of a raw object reference. + + + + + +.. _sq_objtostring: + +.. c:function:: const SQChar* sq_objtostring(HSQOBJECT* po) + + :param HSQOBJECT* po: pointer to an object handler + :remarks: If the object doesn't reference a string it returns NULL. + +return the string value of a raw object reference. + + + + + +.. _sq_objtouserpointer: + +.. c:function:: SQUserPointer sq_objtouserpointer(HSQOBJECT* po) + + :param HSQOBJECT* po: pointer to an object handler + :remarks: If the object doesn't reference a userpointer it returns NULL. + +return the userpointer value of a raw object reference. + + + + + +.. _sq_pushobject: + +.. c:function:: void sq_pushobject(HSQUIRRELVM v, HSQOBJECT obj) + + :param HSQUIRRELVM v: the target VM + :param HSQOBJECT obj: object handler + +push an object referenced by an object handler into the stack. + + + + + +.. _sq_release: + +.. c:function:: SQBool sq_release(HSQUIRRELVM v, HSQOBJECT* po) + + :param HSQUIRRELVM v: the target VM + :param HSQOBJECT* po: pointer to an object handler + :returns: SQTrue if the object handler released has lost all is references(the ones added with sq_addref). SQFalse otherwise. + :remarks: the function will reset the object handler to null when it loses all references. + +remove a reference from an object handler. + + + + + +.. _sq_resetobject: + +.. c:function:: void sq_resetobject(HSQOBJECT* po) + + :param HSQOBJECT* po: pointer to an object handler + :remarks: Every object handler has to be initialized with this function. + +resets(initialize) an object handler. diff --git a/src/vscript/squirrel/doc/source/reference/api/stack_operations.rst b/src/vscript/squirrel/doc/source/reference/api/stack_operations.rst new file mode 100644 index 00000000..bcd0e6c2 --- /dev/null +++ b/src/vscript/squirrel/doc/source/reference/api/stack_operations.rst @@ -0,0 +1,107 @@ +.. _api_ref_stack_operations: + +================ +Stack Operations +================ + +.. _sq_cmp: + +.. c:function:: SQInteger sq_cmp(HSQUIRRELVM v) + + :param HSQUIRRELVM v: the target VM + :returns: > 0 if obj1>obj2 + :returns: == 0 if obj1==obj2 + :returns: < 0 if obj1 0. :: + + sq_pushroottable(v); + sq_pushstring(v,"foo",-1); + sq_get(v,-2); //get the function from the root table + sq_pushroottable(v); //'this' (function environment object) + sq_pushinteger(v,1); + sq_pushfloat(v,2.0); + sq_pushstring(v,"three",-1); + sq_call(v,4,SQFalse,SQFalse); + sq_pop(v,2); //pops the roottable and the function + +this is equivalent to the following Squirrel code:: + + foo(1,2.0,"three"); + +If a runtime error occurs (or a exception is thrown) during the squirrel code execution +the sq_call will fail. diff --git a/src/vscript/squirrel/doc/source/reference/embedding/compiling_a_script.rst b/src/vscript/squirrel/doc/source/reference/embedding/compiling_a_script.rst new file mode 100644 index 00000000..88c15c86 --- /dev/null +++ b/src/vscript/squirrel/doc/source/reference/embedding/compiling_a_script.rst @@ -0,0 +1,58 @@ +.. embedding_compiling_a_script: + +================== +Compiling a script +================== + +You can compile a Squirrel script with the function *sq_compile*.:: + + typedef SQInteger (*SQLEXREADFUNC)(SQUserPointer userdata); + + SQRESULT sq_compile(HSQUIRRELVM v,SQLEXREADFUNC read,SQUserPointer p, + const SQChar *sourcename,SQBool raiseerror); + +In order to compile a script is necessary for the host application to implement a reader +function (SQLEXREADFUNC); this function is used to feed the compiler with the script +data. +The function is called every time the compiler needs a character; It has to return a +character code if succeed or 0 if the source is finished. + +If sq_compile succeeds, the compiled script will be pushed as Squirrel function in the +stack. + +.. :note:: + In order to execute the script, the function generated by *sq_compile()* has + to be called through *sq_call()* + +Here an example of a 'read' function that read from a file: :: + + SQInteger file_lexfeedASCII(SQUserPointer file) + { + int ret; + char c; + if( ( ret=fread(&c,sizeof(c),1,(FILE *)file )>0) ) + return c; + return 0; + } + + int compile_file(HSQUIRRELVM v,const char *filename) + { + FILE *f=fopen(filename,"rb"); + if(f) + { + sq_compile(v,file_lexfeedASCII,f,filename,1); + fclose(f); + return 1; + } + return 0; + } + +When the compiler fails for a syntax error it will try to call the 'compiler error handler'; +this function must be declared as follow: :: + + typedef void (*SQCOMPILERERROR)(HSQUIRRELVM /*v*/,const SQChar * /*desc*/,const SQChar * /*source*/, + SQInteger /*line*/,SQInteger /*column*/); + +and can be set with the following API call:: + + void sq_setcompilererrorhandler(HSQUIRRELVM v,SQCOMPILERERROR f); diff --git a/src/vscript/squirrel/doc/source/reference/embedding/creating_a_c_function.rst b/src/vscript/squirrel/doc/source/reference/embedding/creating_a_c_function.rst new file mode 100644 index 00000000..43772f38 --- /dev/null +++ b/src/vscript/squirrel/doc/source/reference/embedding/creating_a_c_function.rst @@ -0,0 +1,106 @@ +.. _embedding_creating_a_c_function: + +=================== +Create a C function +=================== + +A native C function must have the following prototype: :: + + typedef SQInteger (*SQFUNCTION)(HSQUIRRELVM); + +The parameters is an handle to the calling VM and the return value is an integer +respecting the following rules: + +* 1 if the function returns a value +* 0 if the function does not return a value +* SQ_ERROR runtime error is thrown + +In order to obtain a new callable squirrel function from a C function pointer, is necessary +to call sq_newclosure() passing the C function to it; the new Squirrel function will be +pushed in the stack. + +When the function is called, the stackbase is the first parameter of the function and the +top is the last. In order to return a value the function has to push it in the stack and +return 1. + +Function parameters are in the stack from position 1 ('this') to *n*. +*sq_gettop()* can be used to determinate the number of parameters. + +If the function has free variables, those will be in the stack after the explicit parameters +an can be handled as normal parameters. Note also that the value returned by *sq_gettop()* will be +affected by free variables. *sq_gettop()* will return the number of parameters plus +number of free variables. + +Here an example, the following function print the value of each argument and return the +number of arguments. :: + + SQInteger print_args(HSQUIRRELVM v) + { + SQInteger nargs = sq_gettop(v); //number of arguments + for(SQInteger n=1;n<=nargs;n++) + { + printf("arg %d is ",n); + switch(sq_gettype(v,n)) + { + case OT_NULL: + printf("null"); + break; + case OT_INTEGER: + printf("integer"); + break; + case OT_FLOAT: + printf("float"); + break; + case OT_STRING: + printf("string"); + break; + case OT_TABLE: + printf("table"); + break; + case OT_ARRAY: + printf("array"); + break; + case OT_USERDATA: + printf("userdata"); + break; + case OT_CLOSURE: + printf("closure(function)"); + break; + case OT_NATIVECLOSURE: + printf("native closure(C function)"); + break; + case OT_GENERATOR: + printf("generator"); + break; + case OT_USERPOINTER: + printf("userpointer"); + break; + case OT_CLASS: + printf("class"); + break; + case OT_INSTANCE: + printf("instance"); + break; + case OT_WEAKREF: + printf("weak reference"); + break; + default: + return sq_throwerror(v,"invalid param"); //throws an exception + } + } + printf("\n"); + sq_pushinteger(v,nargs); //push the number of arguments as return value + return 1; //1 because 1 value is returned + } + +Here an example of how to register a function:: + + SQInteger register_global_func(HSQUIRRELVM v,SQFUNCTION f,const char *fname) + { + sq_pushroottable(v); + sq_pushstring(v,fname,-1); + sq_newclosure(v,f,0); //create a new function + sq_newslot(v,-3,SQFalse); + sq_pop(v,1); //pops the root table + return 0; + } diff --git a/src/vscript/squirrel/doc/source/reference/embedding/debug_interface.rst b/src/vscript/squirrel/doc/source/reference/embedding/debug_interface.rst new file mode 100644 index 00000000..3d38bf27 --- /dev/null +++ b/src/vscript/squirrel/doc/source/reference/embedding/debug_interface.rst @@ -0,0 +1,58 @@ +.. _embedding_debug_interface: + +=============== +Debug Interface +=============== + +The squirrel VM exposes a very simple debug interface that allows to easily built a full +featured debugger. +Through the functions sq_setdebughook and sq_setnativedebughook is possible in fact to set a callback function that +will be called every time the VM executes an new line of a script or if a function get +called/returns. The callback will pass as argument the current line the current source and the +current function name (if any).:: + + SQUIRREL_API void sq_setdebughook(HSQUIRRELVM v); + +or :: + + SQUIRREL_API void sq_setnativedebughook(HSQUIRRELVM v,SQDEBUGHOOK hook); + +The following code shows how a debug hook could look like(obviously is possible to +implement this function in C as well). :: + + function debughook(event_type,sourcefile,line,funcname) + { + local fname=funcname?funcname:"unknown"; + local srcfile=sourcefile?sourcefile:"unknown" + switch (event_type) { + case 'l': //called every line(that contains some code) + ::print("LINE line [" + line + "] func [" + fname + "]"); + ::print("file [" + srcfile + "]\n"); + break; + case 'c': //called when a function has been called + ::print("LINE line [" + line + "] func [" + fname + "]"); + ::print("file [" + srcfile + "]\n"); + break; + case 'r': //called when a function returns + ::print("LINE line [" + line + "] func [" + fname + "]"); + ::print("file [" + srcfile + "]\n"); + break; + } + } + +The parameter *event_type* can be 'l' ,'c' or 'r' ; a hook with a 'l' event is called for each line that +gets executed, 'c' every time a function gets called and 'r' every time a function returns. + +A full-featured debugger always allows displaying local variables and calls stack. +The call stack information are retrieved through sq_getstackinfos():: + + SQInteger sq_stackinfos(HSQUIRRELVM v,SQInteger level,SQStackInfos *si); + +While the local variables info through sq_getlocal():: + + SQInteger sq_getlocal(HSQUIRRELVM v,SQUnsignedInteger level,SQUnsignedInteger nseq); + +In order to receive line callbacks the scripts have to be compiled with debug infos enabled +this is done through sq_enabledebuginfo(); :: + + void sq_enabledebuginfo(HSQUIRRELVM v, SQInteger debuginfo); diff --git a/src/vscript/squirrel/doc/source/reference/embedding/error_conventions.rst b/src/vscript/squirrel/doc/source/reference/embedding/error_conventions.rst new file mode 100644 index 00000000..f86e7416 --- /dev/null +++ b/src/vscript/squirrel/doc/source/reference/embedding/error_conventions.rst @@ -0,0 +1,16 @@ +.. _embedding_error_convetions: + + +======================== +Error Conventions +======================== + +.. index:: + single: Error Conventions + +Most of the functions in the API return a SQRESULT value; SQRESULT indicates if a +function completed successfully or not. +The macros SQ_SUCCEEDED() and SQ_FAILED() are used to test the result of a function.:: + + if(SQ_FAILED(sq_getstring(v,-1,&s))) + printf("getstring failed"); diff --git a/src/vscript/squirrel/doc/source/reference/embedding/memory_management.rst b/src/vscript/squirrel/doc/source/reference/embedding/memory_management.rst new file mode 100644 index 00000000..ad0fb082 --- /dev/null +++ b/src/vscript/squirrel/doc/source/reference/embedding/memory_management.rst @@ -0,0 +1,28 @@ +.. _embedding_memory_management: + +======================== +Memory Management +======================== + +.. index:: single: Memory Management + +Squirrel uses reference counting (RC) as primary system for memory management; +however, the virtual machine (VM) has an auxiliary +mark and sweep garbage collector that can be invoked on demand. + +There are 2 possible compile time options: + + * The default configuration consists in RC plus a mark and sweep garbage collector. + The host program can call the function sq_collectgarbage() and perform a garbage collection cycle + during the program execution. The garbage collector isn't invoked by the VM and has to + be explicitly called by the host program. + + * The second a situation consists in RC only(define NO_GARBAGE_COLLECTOR); in this case is impossible for + the VM to detect reference cycles, so is the programmer that has to solve them explicitly in order to + avoid memory leaks. + +The only advantage introduced by the second option is that saves 2 additional +pointers that have to be stored for each object in the default configuration with +garbage collector(8 bytes for 32 bits systems). +The types involved are: tables, arrays, functions, threads, userdata and generators; all other +types are untouched. These options do not affect execution speed. diff --git a/src/vscript/squirrel/doc/source/reference/embedding/references_from_c.rst b/src/vscript/squirrel/doc/source/reference/embedding/references_from_c.rst new file mode 100644 index 00000000..ba84042c --- /dev/null +++ b/src/vscript/squirrel/doc/source/reference/embedding/references_from_c.rst @@ -0,0 +1,21 @@ +.. embedding_references_from_c: + +======================================================== +Mantaining references to Squirrel values from the C API +======================================================== + +Squirrel allows to reference values through the C API; the function sq_getstackobj() gets +a handle to a squirrel object(any type). The object handle can be used to control the lifetime +of an object by adding or removing references to it( see sq_addref() and sq_release()). +The object can be also re-pushed in the VM stack using sq_pushobject().:: + + HSQOBJECT obj; + + sq_resetobject(&obj); //initialize the handle + sq_getstackobj(v,-2,&obj); //retrieve an object handle from the pos -2 + sq_addref(v,&obj); //adds a reference to the object + + ... //do stuff + + sq_pushobject(v,obj); //push the object in the stack + sq_release(v,&obj); //relese the object diff --git a/src/vscript/squirrel/doc/source/reference/embedding/runtime_error_handling.rst b/src/vscript/squirrel/doc/source/reference/embedding/runtime_error_handling.rst new file mode 100644 index 00000000..b956e4be --- /dev/null +++ b/src/vscript/squirrel/doc/source/reference/embedding/runtime_error_handling.rst @@ -0,0 +1,17 @@ +.. _embedding_runtime_error_handling: + +====================== +Runtime error handling +====================== + +When an exception is not handled by Squirrel code with a try/catch statement, a runtime +error is raised and the execution of the current program is interrupted. It is possible to +set a call back function to intercept the runtime error from the host program; this is +useful to show meaningful errors to the script writer and for implementing visual +debuggers. +The following API call pops a Squirrel function from the stack and sets it as error handler.:: + + SQUIRREL_API void sq_seterrorhandler(HSQUIRRELVM v); + +The error handler is called with 2 parameters, an environment object (this) and a object. +The object can be any squirrel type. diff --git a/src/vscript/squirrel/doc/source/reference/embedding/tables_and_arrays_manipulation.rst b/src/vscript/squirrel/doc/source/reference/embedding/tables_and_arrays_manipulation.rst new file mode 100644 index 00000000..8378a102 --- /dev/null +++ b/src/vscript/squirrel/doc/source/reference/embedding/tables_and_arrays_manipulation.rst @@ -0,0 +1,70 @@ +.. _embedding_tables_and_arrays_manipulation: + +============================== +Tables and arrays manipulation +============================== + +A new table is created calling sq_newtable, this function pushes a new table in the stack.:: + + void sq_newtable(HSQUIRRELVM v); + +To create a new slot:: + + SQRESULT sq_newslot(HSQUIRRELVM v,SQInteger idx,SQBool bstatic); + +To set or get the table delegate:: + + SQRESULT sq_setdelegate(HSQUIRRELVM v,SQInteger idx); + SQRESULT sq_getdelegate(HSQUIRRELVM v,SQInteger idx); + + +A new array is created calling sq_newarray, the function pushes a new array in the +stack; if the parameters size is bigger than 0 the elements are initialized to null.:: + + void sq_newarray (HSQUIRRELVM v,SQInteger size); + +To append a value to the back of the array:: + + SQRESULT sq_arrayappend(HSQUIRRELVM v,SQInteger idx); + +To remove a value from the back of the array:: + + SQRESULT sq_arraypop(HSQUIRRELVM v,SQInteger idx,SQInteger pushval); + +To resize the array:: + + SQRESULT sq_arrayresize(HSQUIRRELVM v,SQInteger idx,SQInteger newsize); + +To retrieve the size of a table or an array you must use sq_getsize():: + + SQInteger sq_getsize(HSQUIRRELVM v,SQInteger idx); + +To set a value in an array or table:: + + SQRESULT sq_set(HSQUIRRELVM v,SQInteger idx); + +To get a value from an array or table:: + + SQRESULT sq_get(HSQUIRRELVM v,SQInteger idx); + +To get or set a value from a table without employing delegation:: + + SQRESULT sq_rawget(HSQUIRRELVM v,SQInteger idx); + SQRESULT sq_rawset(HSQUIRRELVM v,SQInteger idx); + +To iterate a table or an array:: + + SQRESULT sq_next(HSQUIRRELVM v,SQInteger idx); + +Here an example of how to perform an iteration: :: + + //push your table/array here + sq_pushnull(v) //null iterator + while(SQ_SUCCEEDED(sq_next(v,-2))) + { + //here -1 is the value and -2 is the key + + sq_pop(v,2); //pops key and val before the nex iteration + } + + sq_pop(v,1); //pops the null iterator diff --git a/src/vscript/squirrel/doc/source/reference/embedding/the_registry_table.rst b/src/vscript/squirrel/doc/source/reference/embedding/the_registry_table.rst new file mode 100644 index 00000000..4aa5f782 --- /dev/null +++ b/src/vscript/squirrel/doc/source/reference/embedding/the_registry_table.rst @@ -0,0 +1,14 @@ +.. _embedding_the_registry_table: + +================== +The registry table +================== + +The registry table is an hidden table shared between vm and all his thread(friend vms). +This table is accessible only through the C API and is meant to be an utility structure +for native C library implementation. +For instance the sqstdlib(squirrel standard library)uses it to store configuration and shared objects +delegates. +The registry is accessible through the API call *sq_pushregistrytable()*.:: + + void sq_pushregistrytable(HSQUIRRELVM v); diff --git a/src/vscript/squirrel/doc/source/reference/embedding/the_stack.rst b/src/vscript/squirrel/doc/source/reference/embedding/the_stack.rst new file mode 100644 index 00000000..9c5f9aef --- /dev/null +++ b/src/vscript/squirrel/doc/source/reference/embedding/the_stack.rst @@ -0,0 +1,104 @@ +.. _embedding_the_stack: + + +========== +The Stack +========== + +Squirrel exchanges values with the virtual machine through a stack. This mechanism has +been inherited from the language Lua. +For instance to call a Squirrel function from C it is necessary to push the function and the +arguments in the stack and then invoke the function; also when Squirrel calls a C +function the parameters will be in the stack as well. + +------------- +Stack indexes +------------- + +Many API functions can arbitrarily refer to any element in the stack through an index. +The stack indexes follow those conventions: + +* 1 is the stack base +* Negative indexes are considered an offset from top of the stack. For instance -1 isthe top of the stack. +* 0 is an invalid index + +Here an example (let's pretend that this table is the VM stack) + ++------------+--------------------+--------------------+ +| **STACK** | **positive index** | **negative index** | ++============+====================+====================+ +| "test" | 4 | -1(top) | ++------------+--------------------+--------------------+ +| 1 | 3 | -2 | ++------------+--------------------+--------------------+ +| 0.5 | 2 | -3 | ++------------+--------------------+--------------------+ +| "foo" | 1(base) | -4 | ++------------+--------------------+--------------------+ + +In this case, the function *sq_gettop* would return 4; + +------------------ +Stack manipulation +------------------ + +The API offers several functions to push and retrieve data from the Squirrel stack. + +To push a value that is already present in the stack in the top position:: + + void sq_push(HSQUIRRELVM v,SQInteger idx); + +To pop an arbitrary number of elements:: + + void sq_pop(HSQUIRRELVM v,SQInteger nelemstopop); + +To remove an element from the stack:: + + void sq_remove(HSQUIRRELVM v,SQInteger idx); + +To retrieve the top index (and size) of the current +virtual stack you must call *sq_gettop* :: + + SQInteger sq_gettop(HSQUIRRELVM v); + +To force the stack to a certain size you can call *sq_settop* :: + + void sq_settop(HSQUIRRELVM v,SQInteger newtop); + +If the newtop is bigger than the previous one, the new positions in the stack will be +filled with null values. + +The following function pushes a C value into the stack:: + + void sq_pushstring(HSQUIRRELVM v,const SQChar *s,SQInteger len); + void sq_pushfloat(HSQUIRRELVM v,SQFloat f); + void sq_pushinteger(HSQUIRRELVM v,SQInteger n); + void sq_pushuserpointer(HSQUIRRELVM v,SQUserPointer p); + void sq_pushbool(HSQUIRRELVM v,SQBool b); + +this function pushes a null into the stack:: + + void sq_pushnull(HSQUIRRELVM v); + +returns the type of the value in a arbitrary position in the stack:: + + SQObjectType sq_gettype(HSQUIRRELVM v,SQInteger idx); + +the result can be one of the following values: :: + + OT_NULL,OT_INTEGER,OT_FLOAT,OT_STRING,OT_TABLE,OT_ARRAY,OT_USERDATA, + OT_CLOSURE,OT_NATIVECLOSURE,OT_GENERATOR,OT_USERPOINTER,OT_BOOL,OT_INSTANCE,OT_CLASS,OT_WEAKREF + +The following functions convert a squirrel value in the stack to a C value:: + + SQRESULT sq_getstring(HSQUIRRELVM v,SQInteger idx,const SQChar **c); + SQRESULT sq_getstringandsize(HSQUIRRELVM v,SQInteger idx,const SQChar **c,SQInteger size); + SQRESULT sq_getinteger(HSQUIRRELVM v,SQInteger idx,SQInteger *i); + SQRESULT sq_getfloat(HSQUIRRELVM v,SQInteger idx,SQFloat *f); + SQRESULT sq_getuserpointer(HSQUIRRELVM v,SQInteger idx,SQUserPointer *p); + SQRESULT sq_getuserdata(HSQUIRRELVM v,SQInteger idx,SQUserPointer *p,SQUserPointer *typetag); + SQRESULT sq_getbool(HSQUIRRELVM v,SQInteger idx,SQBool *p); + +The function sq_cmp compares 2 values from the stack and returns their relation (like strcmp() in ANSI C).:: + + SQInteger sq_cmp(HSQUIRRELVM v); diff --git a/src/vscript/squirrel/doc/source/reference/embedding/userdata_and_userpointers.rst b/src/vscript/squirrel/doc/source/reference/embedding/userdata_and_userpointers.rst new file mode 100644 index 00000000..81e14f03 --- /dev/null +++ b/src/vscript/squirrel/doc/source/reference/embedding/userdata_and_userpointers.rst @@ -0,0 +1,33 @@ +.. _embedding_userdata_and_userpointers: + +========================= +Userdata and UserPointers +========================= + +Squirrel allows the host application put arbitrary data chunks into a Squirrel value, this is +possible through the data type userdata.:: + + SQUserPointer sq_newuserdata(HSQUIRRELVM v,SQUnsignedInteger size); + +When the function *sq_newuserdata* is called, Squirrel allocates a new userdata with the +specified size, returns a pointer to his payload buffer and push the object in the stack; at +this point the application can do whatever it want with this memory chunk, the VM will +automatically take cake of the memory deallocation like for every other built-in type. +A userdata can be passed to a function or stored in a table slot. By default Squirrel +cannot manipulate directly userdata; however is possible to assign a delegate to it and +define a behavior like it would be a table. +Because the application would want to do something with the data stored in a userdata +object when it get deleted, is possible to assign a callback that will be called by the VM +just before deleting a certain userdata. +This is done through the API call *sq_setreleasehook*.:: + + typedef SQInteger (*SQRELEASEHOOK)(SQUserPointer,SQInteger size); + + void sq_setreleasehook(HSQUIRRELVM v,SQInteger idx,SQRELEASEHOOK hook); + +Another kind of userdata is the userpointer; this type is not a memory chunk like the +normal userdata, but just a 'void*' pointer. It cannot have a delegate and is passed by +value, so pushing a userpointer doesn't cause any memory allocation.:: + + void sq_pushuserpointer(HSQUIRRELVM v,SQUserPointer p); + diff --git a/src/vscript/squirrel/doc/source/reference/embedding/vm_initialization.rst b/src/vscript/squirrel/doc/source/reference/embedding/vm_initialization.rst new file mode 100644 index 00000000..03fba06c --- /dev/null +++ b/src/vscript/squirrel/doc/source/reference/embedding/vm_initialization.rst @@ -0,0 +1,21 @@ +.. _embedding_vm_initialization: + +============================== +Virtual Machine Initialization +============================== + +The first thing that a host application has to do, is create a virtual machine. +The host application can create any number of virtual machines through the function +*sq_open()*. +Every single VM that was created using *sq_open()* has to be released with the function *sq_close()* when it is no +longer needed.:: + + int main(int argc, char* argv[]) + { + HSQUIRRELVM v; + v = sq_open(1024); //creates a VM with initial stack size 1024 + + //do some stuff with squirrel here + + sq_close(v); + } diff --git a/src/vscript/squirrel/doc/source/reference/embedding_squirrel.rst b/src/vscript/squirrel/doc/source/reference/embedding_squirrel.rst new file mode 100644 index 00000000..dc8d7fb1 --- /dev/null +++ b/src/vscript/squirrel/doc/source/reference/embedding_squirrel.rst @@ -0,0 +1,29 @@ +.. _embedding_squirrel: + +*************************** + Embedding Squirrel +*************************** + +*This section describes how to embed Squirrel in a host application, +C language knowledge is required to understand this part of the manual.* + +Because of his nature of extension language, Squirrel's compiler and virtual machine +are implemented as C library. The library exposes a set of functions to compile scripts, +call functions, manipulate data and extend the virtual machine. +All declarations needed for embedding the language in an application are in the header file 'squirrel.h'. + +.. toctree:: + embedding/memory_management.rst + embedding/build_configuration.rst + embedding/error_conventions.rst + embedding/vm_initialization.rst + embedding/the_stack.rst + embedding/runtime_error_handling.rst + embedding/compiling_a_script.rst + embedding/calling_a_function.rst + embedding/creating_a_c_function.rst + embedding/tables_and_arrays_manipulation.rst + embedding/userdata_and_userpointers.rst + embedding/the_registry_table.rst + embedding/references_from_c.rst + embedding/debug_interface.rst diff --git a/src/vscript/squirrel/doc/source/reference/index.rst b/src/vscript/squirrel/doc/source/reference/index.rst new file mode 100644 index 00000000..e2d38184 --- /dev/null +++ b/src/vscript/squirrel/doc/source/reference/index.rst @@ -0,0 +1,34 @@ +.. _reference: + +################################# + Squirrel 3.1 Reference Manual +################################# + +Copyright (c) 2003-2016 Alberto Demichelis + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +.. toctree:: + :maxdepth: 3 + :numbered: + + introduction.rst + language.rst + embedding_squirrel.rst + api_reference.rst diff --git a/src/vscript/squirrel/doc/source/reference/introduction.rst b/src/vscript/squirrel/doc/source/reference/introduction.rst new file mode 100644 index 00000000..bc83447f --- /dev/null +++ b/src/vscript/squirrel/doc/source/reference/introduction.rst @@ -0,0 +1,16 @@ +.. _introduction: + +************ +Introduction +************ + +.. index:: + single: introduction + +Squirrel is a high-level, imperative-OO programming language, designed to be a powerful +scripting tool that fits within the size, memory bandwidth, and real-time requirements of +applications like games. +Squirrel offers a wide range of features like dynamic typing, delegation, higher +order functions, generators, tail recursion, exception handling, automatic memory +management while fitting both compiler and virtual machine into about 6k lines of C++ +code. diff --git a/src/vscript/squirrel/doc/source/reference/language.rst b/src/vscript/squirrel/doc/source/reference/language.rst new file mode 100644 index 00000000..35621196 --- /dev/null +++ b/src/vscript/squirrel/doc/source/reference/language.rst @@ -0,0 +1,23 @@ +.. _thelanguage: + +*************************** + The language +*************************** + +.. toctree:: + language/lexical_structure.rst + language/datatypes.rst + language/execution_context.rst + language/statements.rst + language/expressions.rst + language/tables.rst + language/arrays.rst + language/functions.rst + language/classes.rst + language/generators.rst + language/constants_and_enumerations.rst + language/threads.rst + language/weak_references.rst + language/delegation.rst + language/metamethods.rst + language/builtin_functions.rst diff --git a/src/vscript/squirrel/doc/source/reference/language/arrays.rst b/src/vscript/squirrel/doc/source/reference/language/arrays.rst new file mode 100644 index 00000000..ddf5114f --- /dev/null +++ b/src/vscript/squirrel/doc/source/reference/language/arrays.rst @@ -0,0 +1,19 @@ +.. _arrays: + + +================= +Arrays +================= + +.. index:: + single: Arrays + +An array is a sequence of values indexed by a integer number from 0 to the size of the +array minus 1. Arrays elements can be obtained through their index.:: + + local a=["I'm a string", 123] + print(typeof a[0]) //prints "string" + print(typeof a[1]) //prints "integer" + +Resizing, insertion, deletion of arrays and arrays elements is done through a set of +standard functions (see :ref:`built-in functions `). diff --git a/src/vscript/squirrel/doc/source/reference/language/builtin_functions.rst b/src/vscript/squirrel/doc/source/reference/language/builtin_functions.rst new file mode 100644 index 00000000..2f64105b --- /dev/null +++ b/src/vscript/squirrel/doc/source/reference/language/builtin_functions.rst @@ -0,0 +1,693 @@ +.. _builtin_functions: + + +================== +Built-in Functions +================== + +.. index:: + single: Built-in Functions + pair: Global Symbols; Built-in Functions + + +^^^^^^^^^^^^^^ +Global Symbols +^^^^^^^^^^^^^^ + +.. js:function:: array(size,[fill]) + +creates and returns array of a specified size. If the optional parameter fill is specified its value will be used to fill the new array's slots. If the fill parameter is omitted, null is used instead. + +.. js:function:: seterrorhandler(func) + + +sets the runtime error handler + +.. js:function:: callee() + + +returns the currently running closure + +.. js:function:: setdebughook(hook_func) + + +sets the debug hook + +.. js:function:: enabledebuginfo(enable) + +enable/disable the debug line information generation at compile time. enable != null enables. enable == null disables. + +.. js:function:: getroottable() + +returns the root table of the VM. + +.. js:function:: setroottable(table) + +sets the root table of the VM. And returns the previous root table. + +.. js:function:: getconsttable() + +returns the const table of the VM. + +.. js:function:: setconsttable(table) + +sets the const table of the VM; returns the previous const table. + +.. js:function:: assert(exp, [message]) + +throws an exception if exp is null or false. Throws "assertion failed" string by default, or message if specified. + +.. js:function:: print(x) + +prints x to the standard output + +.. js:function:: error(x) + +prints x in the standard error output + +.. js:function:: compilestring(string,[buffername]) + +compiles a string containing a squirrel script into a function and returns it:: + + local compiledscript=compilestring("::print(\"ciao\")"); + //run the script + compiledscript(); + +.. js:function:: collectgarbage() + + Runs the garbage collector and returns the number of reference cycles found (and deleted). This function only works on garbage collector builds. + +.. js:function:: resurrectunreachable() + +Runs the garbage collector and returns an array containing all unreachable object found. If no unreachable object is found, null is returned instead. This function is meant to help debugging reference cycles. This function only works on garbage collector builds. + +.. js:function:: type(obj) + +return the 'raw' type of an object without invoking the metamethod '_typeof'. + +.. js:function:: getstackinfos(level) + +returns the stack informations of a given call stack level. returns a table formatted as follow: :: + + { + func="DoStuff", //function name + + src="test.nut", //source file + + line=10, //line number + + locals = { //a table containing the local variables + + a=10, + + testy="I'm a string" + } + } + +level = 0 is getstackinfos() itself! level = 1 is the current function, level = 2 is the caller of the current function, and so on. If the stack level doesn't exist the function returns null. + +.. js:function:: newthread(threadfunc) + +creates a new cooperative thread object(coroutine) and returns it + +.. js:data:: _versionnumber_ + +integer values describing the version of VM and compiler. e.g. for Squirrel 3.0.1 this value will be 301 + +.. js:data:: _version_ + +string values describing the version of VM and compiler. + +.. js:data:: _charsize_ + +size in bytes of the internal VM representation for characters(1 for ASCII builds 2 for UNICODE builds). + +.. js:data:: _intsize_ + +size in bytes of the internal VM representation for integers(4 for 32bits builds 8 for 64bits builds). + +.. js:data:: _floatsize_ + +size in bytes of the internal VM representation for floats(4 for single precision builds 8 for double precision builds). + +----------------- +Default delegates +----------------- + +Except null and userdata every squirrel object has a default delegate containing a set of functions to manipulate and retrieve information from the object itself. + +^^^^^^^^ +Integer +^^^^^^^^ + +.. js:function:: integer.tofloat() + +convert the number to float and returns it + + +.. js:function:: integer.tostring() + +converts the number to string and returns it + + +.. js:function:: integer.tointeger() + +dummy function; returns the value of the integer. + + +.. js:function:: integer.tochar() + +returns a string containing a single character represented by the integer. + + +.. js:function:: integer.weakref() + +dummy function; returns the integer itself. + +^^^^^ +Float +^^^^^ + +.. js:function:: float.tofloat() + +returns the value of the float(dummy function) + + +.. js:function:: float.tointeger() + +converts the number to integer and returns it + + +.. js:function:: float.tostring() + +converts the number to string and returns it + + +.. js:function:: float.tochar() + +returns a string containing a single character represented by the integer part of the float. + + +.. js:function:: float.weakref() + +dummy function; returns the float itself. + +^^^^ +Bool +^^^^ + +.. js:function:: bool.tofloat() + +returns 1.0 for true 0.0 for false + + +.. js:function:: bool.tointeger() + +returns 1 for true 0 for false + + +.. js:function:: bool.tostring() + +returns "true" for true and "false" for false + + +.. js:function:: bool.weakref() + +dummy function; returns the bool itself. + +^^^^^^ +String +^^^^^^ + +.. js:function:: string.len() + +returns the string length + + +.. js:function:: string.tointeger([base]) + +Converts the string to integer and returns it. An optional parameter base can be specified--if a base is not specified, it defaults to base 10. + + +.. js:function:: string.tofloat() + +converts the string to float and returns it + + +.. js:function:: string.tostring() + +returns the string (really, a dummy function) + + +.. js:function:: string.slice(start,[end]) + +returns a section of the string as new string. Copies from start to the end (not included). If start is negative the index is calculated as length + start, if end is negative the index is calculated as length + end. If end is omitted end is equal to the string length. + + +.. js:function:: string.find(substr,[startidx]) + +Searches a sub string (substr) starting from the index startidx and returns the index of its first occurrence. If startidx is omitted the search operation starts from the beginning of the string. The function returns null if substr is not found. + + +.. js:function:: string.tolower() + +returns a lowercase copy of the string. + + +.. js:function:: string.toupper() + +returns a uppercase copy of the string. + + +.. js:function:: string.weakref() + +returns a weak reference to the object. + +^^^^^ +Table +^^^^^ + +.. js:function:: table.len() + +returns the number of slots contained in a table + + +.. js:function:: table.rawget(key) + +tries to get a value from the slot 'key' without employing delegation + + +.. js:function:: table.rawset(key,val) + +Sets the slot 'key' with the value 'val' without employing delegation. If the slot does not exists, it will be created. Returns table itself. + + +.. js:function:: table.rawdelete() + +Deletes the slot key without employing delegation and returns its value. If the slot does not exists, returns null. + + +.. js:function:: table.rawin(key) + +returns true if the slot 'key' exists. the function has the same effect as the operator 'in' but does not employ delegation. + + +.. js:function:: table.weakref() + +returns a weak reference to the object. + + +.. js:function:: table.tostring() + +Tries to invoke the _tostring metamethod. If that fails, it returns "(table : pointer)". + + +.. js:function:: table.clear() + +removes all the slots from the table. Returns table itself. + + +.. js:function:: table.setdelegate(table) + +Sets the delegate of the table. To remove a delegate, 'null' must be passed to the function. The function returns the table itself (e.g. a.setdelegate(b) -- in this case 'a' is the return value). + + +.. js:function:: table.getdelegate() + +returns the table's delegate or null if no delegate was set. + + +.. js:function:: table.filter(func(key,val)) + +Creates a new table with all values that pass the test implemented by the provided function. In detail, it creates a new table, invokes the specified function for each key-value pair in the original table; if the function returns 'true', then the value is added to the newly created table at the same key. + +.. js:function:: table.keys() + +returns an array containing all the keys of the table slots. + +.. js:function:: table.values() + +returns an array containing all the values of the table slots. + +^^^^^^ +Array +^^^^^^ + +.. js:function:: array.len() + +returns the length of the array + + +.. js:function:: array.append(val) + +appends the value 'val' at the end of the array. Returns array itself. + + +.. js:function:: array.push(val) + +appends the value 'val' at the end of the array. Returns array itself. + + +.. js:function:: array.extend(array) + +Extends the array by appending all the items in the given array. Returns array itself. + + +.. js:function:: array.pop() + +removes a value from the back of the array and returns it. + + +.. js:function:: array.top() + +returns the value of the array with the higher index + + +.. js:function:: array.insert(idx,val) + +inserts the value 'val' at the position 'idx' in the array. Returns array itself. + + +.. js:function:: array.remove(idx) + +removes the value at the position 'idx' in the array and returns its value. + + +.. js:function:: array.resize(size,[fill]) + +Resizes the array. If the optional parameter 'fill' is specified, its value will be used to fill the new array's slots when the size specified is bigger than the previous size. If the fill parameter is omitted, null is used instead. Returns array itself. + + +.. js:function:: array.sort([compare_func]) + +Sorts the array in-place. A custom compare function can be optionally passed. The function prototype as to be the following.:: + + function custom_compare(a,b) + { + if(a>b) return 1 + else if(a :: + + arr.sort(@(a,b) a <=> b); + +Returns array itself. + +.. js:function:: array.reverse() + +reverse the elements of the array in place. Returns array itself. + + +.. js:function:: array.slice(start,[end]) + +Returns a section of the array as new array. Copies from start to the end (not included). If start is negative the index is calculated as length + start, if end is negative the index is calculated as length + end. If end is omitted end is equal to the array length. + + +.. js:function:: array.weakref() + +returns a weak reference to the object. + + +.. js:function:: array.tostring() + +returns the string "(array : pointer)". + + +.. js:function:: array.clear() + +removes all the items from the array + + +.. js:function:: array.map(func(item_value, [item_index], [array_ref])) + +Creates a new array of the same size. For each element in the original array invokes the function 'func' and assigns the return value of the function to the corresponding element of the newly created array. +Provided func can accept up to 3 arguments: array item value (required), array item index (optional), reference to array itself (optional). + + +.. js:function:: array.apply(func([item_value, [item_index], [array_ref])) + +for each element in the array invokes the function 'func' and replace the original value of the element with the return value of the function. + + +.. js:function:: array.reduce(func(prevval,curval), [initializer]) + +Reduces an array to a single value. For each element in the array invokes the function 'func' passing +the initial value (or value from the previous callback call) and the value of the current element. +The return value of the function is then used as 'prevval' for the next element. +If the optional initializer is present, it is placed before the items of the array in the calculation, +and serves as a default when the sequence is empty. +If initializer is not given then for sequence contains only one item, reduce() returns the first item, +and for empty sequence returns null. + +Given an sequence with 2 or more elements (including initializer) calls the function with the first two elements as the parameters, +gets that result, then calls the function with that result and the third element, gets that result, +calls the function with that result and the fourth parameter and so on until all element have been processed. +Finally, returns the return value of the last invocation of func. + + +.. js:function:: array.filter(func(index,val)) + +Creates a new array with all elements that pass the test implemented by the provided function. In detail, it creates a new array, for each element in the original array invokes the specified function passing the index of the element and it's value; if the function returns 'true', then the value of the corresponding element is added on the newly created array. + + +.. js:function:: array.find(value) + +Performs a linear search for the value in the array. Returns the index of the value if it was found null otherwise. + +^^^^^^^^ +Function +^^^^^^^^ + +.. js:function:: function.call(_this,args...) + +calls the function with the specified environment object('this') and parameters + + +.. js:function:: function.pcall(_this,args...) + +calls the function with the specified environment object('this') and parameters, this function will not invoke the error callback in case of failure(pcall stays for 'protected call') + + +.. js:function:: function.acall(array_args) + +calls the function with the specified environment object('this') and parameters. The function accepts an array containing the parameters that will be passed to the called function.Where array_args has to contain the required 'this' object at the [0] position. + + +.. js:function:: function.pacall(array_args) + +calls the function with the specified environment object('this') and parameters. The function accepts an array containing the parameters that will be passed to the called function.Where array_args has to contain the required 'this' object at the [0] position. This function will not invoke the error callback in case of failure(pacall stays for 'protected array call') + + +.. js:function:: function.weakref() + +returns a weak reference to the object. + + +.. js:function:: function.tostring() + +returns the string "(closure : pointer)". + + +.. js:function:: function.setroot(table) + +sets the root table of a closure + + +.. js:function:: function.getroot() + +returns the root table of the closure + + +.. js:function:: function.bindenv(env) + +clones the function(aka closure) and bind the environment object to it(table,class or instance). the this parameter of the newly create function will always be set to env. Note that the created function holds a weak reference to its environment object so cannot be used to control its lifetime. + + +.. js:function:: function.getinfos() + +returns a table containing informations about the function, like parameters, name and source name; :: + + //the data is returned as a table is in form + //pure squirrel function + { + native = false + name = "zefuncname" + src = "/somthing/something.nut" + parameters = ["a","b","c"] + defparams = [1,"def"] + varargs = 2 + } + //native C function + { + native = true + name = "zefuncname" + paramscheck = 2 + typecheck = [83886082,83886384] //this is the typemask (see C defines OT_INTEGER,OT_FLOAT etc...) + } + + + +^^^^^ +Class +^^^^^ + +.. js:function:: class.instance() + +returns a new instance of the class. this function does not invoke the instance constructor. The constructor must be explicitly called (eg. class_inst.constructor(class_inst) ). + + +.. js:function:: class.getattributes(membername) + +returns the attributes of the specified member. if the parameter member is null the function returns the class level attributes. + + +.. js:function:: class.setattributes(membername,attr) + +sets the attribute of the specified member and returns the previous attribute value. if the parameter member is null the function sets the class level attributes. + + +.. js:function:: class.rawin(key) + +returns true if the slot 'key' exists. the function has the same effect as the operator 'in' but does not employ delegation. + + +.. js:function:: class.weakref() + +returns a weak reference to the object. + + +.. js:function:: class.tostring() + +returns the string "(class : pointer)". + + +.. js:function:: class.rawget(key) + +tries to get a value from the slot 'key' without employing delegation + + +.. js:function:: class.rawset(key,val) + +sets the slot 'key' with the value 'val' without employing delegation. If the slot does not exists, it will be created. + + +.. js:function:: class.newmember(key,val,[attrs],[bstatic]) + +sets/adds the slot 'key' with the value 'val' and attributes 'attrs' and if present invokes the _newmember metamethod. If bstatic is true the slot will be added as static. If the slot does not exists , it will be created. + + +.. js:function:: class.rawnewmember(key,val,[attrs],[bstatic]) + +sets/adds the slot 'key' with the value 'val' and attributes 'attrs'. If bstatic is true the slot will be added as static. If the slot does not exist, it will be created. It doesn't invoke any metamethod. + +^^^^^^^^^^^^^^ +Class Instance +^^^^^^^^^^^^^^ + +.. js:function:: instance.getclass() + +returns the class that created the instance. + + +.. js:function:: instance.rawin(key) + + :param key: ze key + +returns true if the slot 'key' exists. the function has the same effect as the operator 'in' but does not employ delegation. + + +.. js:function:: instance.weakref() + +returns a weak reference to the object. + + +.. js:function:: instance.tostring() + +tries to invoke the _tostring metamethod, if failed. returns "(instance : pointer)". + + +.. js:function:: instance.rawget(key) + +tries to get a value from the slot 'key' without employing delegation + + +.. js:function:: instance.rawset(key,val) + +sets the slot 'key' with the value 'val' without employing delegation. If the slot does not exists, it will be created. + +^^^^^^^^^^^^^^ +Generator +^^^^^^^^^^^^^^ + + +.. js:function:: generator.getstatus() + +returns the status of the generator as string : "running", "dead" or "suspended". + + +.. js:function:: generator.weakref() + +returns a weak reference to the object. + + +.. js:function:: generator.tostring() + +returns the string "(generator : pointer)". + +^^^^^^^^^^^^^^ +Thread +^^^^^^^^^^^^^^ + +.. js:function:: thread.call(...) + +starts the thread with the specified parameters + + +.. js:function:: thread.wakeup([wakeupval]) + +wakes up a suspended thread, accepts a optional parameter that will be used as return value for the function that suspended the thread(usually suspend()) + + +.. js:function:: thread.wakeupthrow(objtothrow,[propagateerror = true]) + +wakes up a suspended thread, throwing an exception in the awaken thread, throwing the object 'objtothrow'. + + +.. js:function:: thread.getstatus() + +returns the status of the thread ("idle","running","suspended") + + +.. js:function:: thread.weakref() + +returns a weak reference to the object. + + +.. js:function:: thread.tostring() + +returns the string "(thread : pointer)". + + +.. js:function:: thread.getstackinfos(stacklevel) + +returns the stack frame informations at the given stack level (0 is the current function 1 is the caller and so on). + +^^^^^^^^^^^^^^ +Weak Reference +^^^^^^^^^^^^^^ + +.. js:function:: weakreference.ref() + +returns the object that the weak reference is pointing at; null if the object that was point at was destroyed. + + +.. js:function:: weakreference.weakref() + +returns a weak reference to the object. + + +.. js:function:: weakreference.tostring() + +returns the string "(weakref : pointer)". diff --git a/src/vscript/squirrel/doc/source/reference/language/classes.rst b/src/vscript/squirrel/doc/source/reference/language/classes.rst new file mode 100644 index 00000000..81ee4c41 --- /dev/null +++ b/src/vscript/squirrel/doc/source/reference/language/classes.rst @@ -0,0 +1,429 @@ +.. _classes: + + +================= +Classes +================= + +.. index:: + single: Classes + +Squirrel implements a class mechanism similar to languages like Java/C++/etc... +however because of its dynamic nature it differs in several aspects. +Classes are first class objects like integer or strings and can be stored in +table slots local variables, arrays and passed as function parameters. + +----------------- +Class Declaration +----------------- + +.. index:: + pair: declaration; Class + single: Class Declaration + +A class object is created through the keyword 'class' . The class object follows +the same declaration syntax of a table(see :ref:`Tables `) with the only difference +of using ';' as optional separator rather than ','. + +For instance: :: + + class Foo { + //constructor + constructor(a) + { + testy = ["stuff",1,2,3,a]; + } + //member function + function PrintTesty() + { + foreach(i,val in testy) + { + ::print("idx = "+i+" = "+val+" \n"); + } + } + //property + testy = null; + + } + +the previous code example is a syntactic sugar for: :: + + Foo <- class { + //constructor + constructor(a) + { + testy = ["stuff",1,2,3,a]; + } + //member function + function PrintTesty() + { + foreach(i,val in testy) + { + ::print("idx = "+i+" = "+val+" \n"); + } + } + //property + testy = null; + + } + +in order to emulate namespaces, it is also possible to declare something like this:: + + //just 2 regular nested tables + FakeNamespace <- { + Utils = {} + } + + class FakeNamespace.Utils.SuperClass { + constructor() + { + ::print("FakeNamespace.Utils.SuperClass") + } + function DoSomething() + { + ::print("DoSomething()") + } + } + + function FakeNamespace::Utils::SuperClass::DoSomethingElse() + { + ::print("FakeNamespace::Utils::SuperClass::DoSomethingElse()") + } + + local testy = FakeNamespace.Utils.SuperClass(); + testy.DoSomething(); + testy.DoSomethingElse(); + +After its declaration, methods or properties can be added or modified by following +the same rules that apply to a table(operator ``<-``).:: + + //adds a new property + Foo.stuff <- 10; + + //modifies the default value of an existing property + Foo.testy <- "I'm a string"; + + //adds a new method + function Foo::DoSomething(a,b) + { + return a+b; + } + +After a class is instantiated is no longer possible to add new properties however is possible to add or replace methods. + +^^^^^^^^^^^^^^^^ +Static variables +^^^^^^^^^^^^^^^^ + +.. index:: + pair: static variables; Class + single: Static variables + +Squirrel's classes support static member variables. A static variable shares its value +between all instances of the class. Statics are declared by prefixing the variable declaration +with the keyword ``static``; the declaration must be in the class body. + +.. note:: Statics are read-only. + +:: + + class Foo { + constructor() + { + //..stuff + } + name = "normal variable"; + //static variable + static classname = "The class name is foo"; + }; + +^^^^^^^^^^^^^^^^ +Class Attributes +^^^^^^^^^^^^^^^^ + +.. index:: + pair: attributes; Class + single: Class Attributes + +Classes allow to associate attributes to it's members. Attributes are a form of metadata +that can be used to store application specific informations, like documentations +strings, properties for IDEs, code generators etc... +Class attributes are declared in the class body by preceding the member declaration and +are delimited by the symbol ````. +Here an example: :: + + class Foo { + //attributes of PrintTesty + function PrintTesty() + { + foreach(i,val in testy) + { + ::print("idx = "+i+" = "+val+" \n"); + } + } + //attributes of testy + testy = null; + } + +Attributes are, matter of fact, a table. Squirrel uses ```` syntax +instead of curly brackets ``{}`` for the attribute declaration to increase readability. + +This means that all rules that apply to tables apply to attributes. + +Attributes can be retrieved through the built-in function ``classobj.getattributes(membername)`` (see built-in functions). +and can be modified/added through the built-in function ``classobj.setattributes(membername,val)``. + +the following code iterates through the attributes of all Foo members.:: + + foreach(member,val in Foo) + { + ::print(member+"\n"); + local attr; + if((attr = Foo.getattributes(member)) != null) { + foreach(i,v in attr) + { + ::print("\t"+i+" = "+(typeof v)+"\n"); + } + } + else { + ::print("\t\n") + } + } + +----------------- +Class Instances +----------------- + +.. index:: + pair: instances; Class + single: Class Instances + +The class objects inherits several of the table's feature with the difference that multiple instances of the +same class can be created. +A class instance is an object that share the same structure of the table that created it but +holds is own values. +Class *instantiation* uses function notation. +A class instance is created by calling a class object. Can be useful to imagine a class like a function +that returns a class instance.:: + + //creates a new instance of Foo + local inst = Foo(); + +When a class instance is created its member are initialized *with the same value* specified in the +class declaration. The values are copied verbatim, *no cloning is performed* even if the value is a container or a class instances. + +.. note:: FOR C# and Java programmers: + + Squirrel doesn't clone member's default values nor executes the member declaration for each instance(as C# or java). + + So consider this example: :: + + class Foo { + myarray = [1,2,3] + mytable = {} + } + + local a = Foo(); + local b = Foo(); + + In the snippet above both instances will refer to the same array and same table.To achieve what a C# or Java programmer would + expect, the following approach should be taken. :: + + class Foo { + myarray = null + mytable = null + constructor() + { + myarray = [1,2,3] + mytable = {} + } + } + + local a = Foo(); + local b = Foo(); + +When a class defines a method called 'constructor', the class instantiation operation will +automatically invoke it for the newly created instance. +The constructor method can have parameters, this will impact on the number of parameters +that the *instantiation operation* will require. +Constructors, as normal functions, can have variable number of parameters (using the parameter ``...``).:: + + class Rect { + constructor(w,h) + { + width = w; + height = h; + } + x = 0; + y = 0; + width = null; + height = null; + } + + //Rect's constructor has 2 parameters so the class has to be 'called' + //with 2 parameters + local rc = Rect(100,100); + +After an instance is created, its properties can be set or fetched following the +same rules that apply to tables. Methods cannot be set. + +Instance members cannot be removed. + +The class object that created a certain instance can be retrieved through the built-in function +``instance.getclass()`` (see :ref:`built-in functions `) + +The operator ``instanceof`` tests if a class instance is an instance of a certain class.:: + + local rc = Rect(100,100); + if(rc instanceof ::Rect) { + ::print("It's a rect"); + } + else { + ::print("It isn't a rect"); + } + +.. note:: Since Squirrel 3.x instanceof doesn't throw an exception if the left expression is not a class, it simply fails + +-------------- +Inheritance +-------------- + +.. index:: + pair: inheritance; Class + single: Inheritance + +Squirrel's classes support single inheritance by adding the keyword ``extends``, followed +by an expression, in the class declaration. +The syntax for a derived class is the following: :: + + class SuperFoo extends Foo { + function DoSomething() { + ::print("I'm doing something"); + } + } + +When a derived class is declared, Squirrel first copies all base's members in the +new class then proceeds with evaluating the rest of the declaration. + +A derived class inherit all members and properties of it's base, if the derived class +overrides a base function the base implementation is shadowed. +It's possible to access a overridden method of the base class by fetching the method from +through the 'base' keyword. + +Here an example: :: + + class Foo { + function DoSomething() { + ::print("I'm the base"); + } + }; + + class SuperFoo extends Foo { + //overridden method + function DoSomething() { + //calls the base method + base.DoSomething(); + ::print("I'm doing something"); + } + } + +Same rule apply to the constructor. The constructor is a regular function (apart from being automatically invoked on construction).:: + + class BaseClass { + constructor() + { + ::print("Base constructor\n"); + } + } + + class ChildClass extends BaseClass { + constructor() + { + base.constructor(); + ::print("Child constructor\n"); + } + } + + local test = ChildClass(); + +The base class of a derived class can be retrieved through the built-in method ``getbase()``.:: + + local thebaseclass = SuperFoo.getbase(); + +Note that because methods do not have special protection policies when calling methods of the same +objects, a method of a base class that calls a method of the same class can end up calling a overridden method of the derived class. + +A method of a base class can be explicitly invoked by a method of a derived class though the keyword ``base`` (as in base.MyMethod() ).:: + + class Foo { + function DoSomething() { + ::print("I'm the base"); + } + function DoIt() + { + DoSomething(); + } + }; + + class SuperFoo extends Foo { + //overridden method + function DoSomething() { + ::print("I'm the derived"); + + } + function DoIt() { + base.DoIt(); + } + } + + //creates a new instance of SuperFoo + local inst = SuperFoo(); + + //prints "I'm the derived" + inst.DoIt(); + +---------------------- +Metamethods +---------------------- + +.. index:: + pair: metamethods; Class + single: Class metamethods + +Class instances allow the customization of certain aspects of the +their semantics through metamethods(see see :ref:`Metamethods `). +For C++ programmers: "metamethods behave roughly like overloaded operators". +The metamethods supported by classes are ``_add, _sub, _mul, _div, _unm, _modulo, +_set, _get, _typeof, _nexti, _cmp, _call, _delslot, _tostring`` + +Class objects instead support only 2 metamethods : ``_newmember`` and ``_inherited`` + +the following example show how to create a class that implements the metamethod ``_add``.:: + + class Vector3 { + constructor(...) + { + if(vargv.len() >= 3) { + x = vargv[0]; + y = vargv[1]; + z = vargv[2]; + } + } + function _add(other) + { + return ::Vector3(x+other.x,y+other.y,z+other.z); + } + + x = 0; + y = 0; + z = 0; + } + + local v0 = Vector3(1,2,3) + local v1 = Vector3(11,12,13) + local v2 = v0 + v1; + ::print(v2.x+","+v2.y+","+v2.z+"\n"); + +Since version 2.1, classes support 2 metamethods ``_inherited`` and ``_newmember``. +``_inherited`` is invoked when a class inherits from the one that implements ``_inherited``. +``_newmember`` is invoked for each member that is added to the class(at declaration time). diff --git a/src/vscript/squirrel/doc/source/reference/language/constants_and_enumerations.rst b/src/vscript/squirrel/doc/source/reference/language/constants_and_enumerations.rst new file mode 100644 index 00000000..77fd9bc2 --- /dev/null +++ b/src/vscript/squirrel/doc/source/reference/language/constants_and_enumerations.rst @@ -0,0 +1,97 @@ +.. _constants_and_enumerations: + + +======================== +Constants & Enumerations +======================== + +.. index:: + single: Constants & Enumerations + + + +Squirrel allows to bind constant values to an identifier that will be evaluated compile-time. +This is achieved though constants and Enumerations. + +--------------- +Constants +--------------- + +.. index:: + single: Constants + +Constants bind a specific value to an identifier. Constants are similar to +global values, except that they are evaluated compile time and their value cannot be changed. + +constants values can only be integers, floats or string literals. No expression are allowed. +are declared with the following syntax.:: + + const foobar = 100; + const floatbar = 1.2; + const stringbar = "I'm a constant string"; + +constants are always globally scoped, from the moment they are declared, any following code +can reference them. +Constants will shadow any global slot with the same name( the global slot will remain visible by using the ``::`` syntax).:: + + local x = foobar * 2; + +--------------- +Enumerations +--------------- + +.. index:: + single: Enumerations + +As Constants, Enumerations bind a specific value to a name. Enumerations are also evaluated at compile time +and their value cannot be changed. + +An enum declaration introduces a new enumeration into the program. +Enumeration values can only be integers, floats or string literals. No expression are allowed.:: + + enum Stuff { + first, //this will be 0 + second, //this will be 1 + third //this will be 2 + } + +or:: + + enum Stuff { + first = 10 + second = "string" + third = 1.2 + } + +An enum value is accessed in a manner that's similar to accessing a static class member. +The name of the member must be qualified with the name of the enumeration, for example ``Stuff.second`` +Enumerations will shadow any global slot with the same name( the global slot will remain visible by using the ``::`` syntax).:: + + local x = Stuff.first * 2; + +-------------------- +Implementation notes +-------------------- + +Enumerations and Constants are a compile-time feature. Only integers, string and floats can be declared as const/enum; +No expressions are allowed(because they would have to be evaluated compile time). +When a const or an enum is declared, it is added compile time to the ``consttable``. This table is stored in the VM shared state +and is shared by the VM and all its threads. +The ``consttable`` is a regular squirrel table; In the same way as the ``roottable`` +it can be modified runtime. +You can access the ``consttable`` through the built-in function ``getconsttable()`` +and also change it through the built-in function ``setconsttable()`` + +here some example: :: + + //creates a constant + getconsttable()["something"] <- 10" + //creates an enumeration + getconsttable()["somethingelse"] <- { a = "10", c = "20", d = "200"}; + //deletes the constant + delete getconsttable()["something"] + //deletes the enumeration + delete getconsttable()["somethingelse"] + +This system allows to procedurally declare constants and enumerations, it is also possible to assign any squirrel type +to a constant/enumeration(function,classes etc...). However this will make serialization of a code chunk impossible. diff --git a/src/vscript/squirrel/doc/source/reference/language/datatypes.rst b/src/vscript/squirrel/doc/source/reference/language/datatypes.rst new file mode 100644 index 00000000..843ed8b3 --- /dev/null +++ b/src/vscript/squirrel/doc/source/reference/language/datatypes.rst @@ -0,0 +1,162 @@ +.. _datatypes_and_values: + +===================== +Values and Data types +===================== + +While Squirrel is a dynamically typed language and variables do not +have a type, different operations may interpret the variable as +containing a type. Squirrel's basic types are integer, float, string, +null, table, array, function, generator, class, instance, bool, thread +and userdata. + +.. _userdata-index: + +-------- +Integer +-------- + +An Integer represents a 32 bit (or better) signed number.:: + + local a = 123 //decimal + local b = 0x0012 //hexadecimal + local c = 075 //octal + local d = 'w' //char code + +-------- +Float +-------- + +A float represents a 32 bit (or better) floating point number.:: + + local a=1.0 + local b=0.234 + +-------- +String +-------- + +Strings are an immutable sequence of characters. In order to modify a +string is it necessary create a new one. + +Squirrel's strings are similar to strings in C or C++. They are +delimited by quotation marks(``"``) and can contain escape +sequences (``\t``, ``\a``, ``\b``, ``\n``, ``\r``, ``\v``, ``\f``, +``\\``, ``\"``, ``\'``, ``\0``, ``\x``, ``\u`` and +``\U``). + +Verbatim string literals do not interpret escape sequences. They begin +with ``@"`` and end with the matching quote. Verbatim string literals +also can extend over a line break. If they do, they include any white +space characters between the quotes: :: + + local a = "I'm a wonderful string\n" + // has a newline at the end of the string + local x = @"I'm a verbatim string\n" + // the \n is literal, similar to "\\n" in a regular string. + +However, a doubled quotation mark within a verbatim string is replaced +by a single quotation mark: :: + + local multiline = @" + this is a multiline string + it will ""embed"" all the new line + characters + " + +-------- +Null +-------- + +The null value is a primitive value that represents the null, empty, or non-existent +reference. The type Null has exactly one value, called null.:: + + local a = null + +-------- +Bool +-------- + +Bool is a double-valued (Boolean) data type. Its literals are ``true`` +and ``false``. A bool value expresses the validity of a condition +(tells whether the condition is true or false).:: + + local a = true; + +-------- +Table +-------- + +Tables are associative containers implemented as a set of key/value pairs +called slots.:: + + local t={} + local test= + { + a=10 + b=function(a) { return a+1; } + } + +-------- +Array +-------- + +Arrays are simple sequence of objects. Their size is dynamic and their index always starts from 0.:: + + local a = ["I'm","an","array"] + local b = [null] + b[0] = a[2]; + +-------- +Function +-------- + +Functions are similar to those in other C-like languages with a few key differences (see below). + +-------- +Class +-------- + +Classes are associative containers implemented as sets of key/value +pairs. Classes are created through a 'class expression' or a 'class +statement'. class members can be inherited from another class object +at creation time. After creation, members can be added until an +instance of the class is created. + +-------------- +Class Instance +-------------- + +Class instances are created by calling a *class object*. Instances, as +tables, are implemented as sets of key/value pairs. Instance members +cannot be dynamically added or removed; however the value of the +members can be changed. + +--------- +Generator +--------- + +Generators are functions that can be suspended with the statement +'yield' and resumed later (see :ref:`Generators `). + +--------- +Userdata +--------- + +Userdata objects are blobs of memory or pointers defined by the host +application but stored within Squirrel variables (See :ref:`Userdata +and UserPointers `). + +--------- +Thread +--------- + +Threads are objects representing a cooperative thread of execution, +also known as coroutines. + +-------------- +Weak Reference +-------------- + +Weak References are objects that point to another (non-scalar) object but do not own a strong reference to it. +(See :ref:`Weak References `). diff --git a/src/vscript/squirrel/doc/source/reference/language/delegation.rst b/src/vscript/squirrel/doc/source/reference/language/delegation.rst new file mode 100644 index 00000000..63607d5c --- /dev/null +++ b/src/vscript/squirrel/doc/source/reference/language/delegation.rst @@ -0,0 +1,35 @@ +.. _delegation: + + +======================== +Delegation +======================== + +.. index:: + single: Delegation + +Squirrel supports implicit delegation. Every table or userdata can have a parent table +(delegate). A parent table is a normal table that allows the definition of special behaviors +for his child. +When a table (or userdata) is indexed with a key that doesn't correspond to one of its +slots, the interpreter automatically delegates the get (or set) operation to its parent.:: + + Entity <- { + } + + function Entity::DoStuff() + { + ::print(_name); + } + + local newentity = { + _name="I'm the new entity" + } + newentity.setdelegate(Entity) + + newentity.DoStuff(); //prints "I'm the new entity" + +The delegate of a table can be retreived through built-in method ``table.getdelegate()``.:: + + local thedelegate = newentity.getdelegate(); + diff --git a/src/vscript/squirrel/doc/source/reference/language/execution_context.rst b/src/vscript/squirrel/doc/source/reference/language/execution_context.rst new file mode 100644 index 00000000..47ef3209 --- /dev/null +++ b/src/vscript/squirrel/doc/source/reference/language/execution_context.rst @@ -0,0 +1,95 @@ +.. _executioncontext: + +======================= +Execution Context +======================= + +.. index:: + single: execution context + +The execution context is the union of the function stack frame and the function +environment object(this) and the function root(root table). +The stack frame is the portion of stack where the local variables declared in its body are +stored. +The environment object is an implicit parameter that is automatically passed by the +function caller (see see :ref:`functions `). +The root table is a table associated to the function during its creation. +The root table value of a function is the root table of the VM at the function creation. +The root table of function can also be changed after creation with closure.setroot(). +During the execution, the body of a function can only transparently refer to his execution +context. +This mean that a single identifier can refer to a local variable, to an environment object slot +or to the slot of the closure root table; +The environment object can be explicitly accessed by the keyword ``this``. +The closure root table can be explicitly accessed through the operator ``::`` (see :ref:`Variables `). + +.. _variables: + +----------------- +Variables +----------------- + +There are two types of variables in Squirrel, local variables and tables/arrays slots. +Because global variables(variables stored in the root of a closure) are stored in a table, they are table slots. + +A single identifier refers to a local variable or a slot in the environment object.:: + + derefexp := id; + +:: + + _table["foo"] + _array[10] + +with tables we can also use the '.' syntax:: + + derefexp := exp '.' id + +:: + + _table.foo + +Squirrel first checks if an identifier is a local variable (function arguments are local +variables) if not looks up the environment object (this) and finally looks up +to the closure root. + +For instance::: + + function testy(arg) + { + local a=10; + print(a); + return arg; + } + +in this case 'foo' will be equivalent to 'this.foo' or this["foo"]. + +Global variables are stored in a table called the root table. Usually in the global scope the +environment object is the root table, but to explicitly access the closure root of the function from +another scope, the slot name must be prefixed with ``'::'`` (``::foo``). + +For instance::: + + function testy(arg) + { + local a=10; + return arg+::foo; + } + +accesses the variable 'foo' in the closure root table. + +Since Squirrel 3.1 each function has a weak reference to a specific root table, this can differ from the current VM root table.:: + + function test() { + foo = 10; + } + +is equivalent to write:: + + function test() { + if("foo" in this) { + this.foo = 10; + }else { + ::foo = 10; + } + } diff --git a/src/vscript/squirrel/doc/source/reference/language/expressions.rst b/src/vscript/squirrel/doc/source/reference/language/expressions.rst new file mode 100644 index 00000000..9e75c7e7 --- /dev/null +++ b/src/vscript/squirrel/doc/source/reference/language/expressions.rst @@ -0,0 +1,374 @@ +.. _expressions: + + +================= +Expressions +================= + +.. index:: + single: Expressions + +---------------- +Assignment +---------------- + +.. index:: + single: assignment(=) + single: new slot(<-) + +:: + + exp := derefexp '=' exp + exp:= derefexp '<-' exp + +squirrel implements 2 kind of assignment: the normal assignment(=):: + + a = 10; + +and the "new slot" assignment.:: + + a <- 10; + +The new slot expression allows to add a new slot into a table(see :ref:`Tables `). If the slot +already exists in the table it behaves like a normal assignment. + +---------------- +Operators +---------------- + +.. index:: + single: Operators + +^^^^^^^^^^^^^ +?: Operator +^^^^^^^^^^^^^ + +.. index:: + pair: ?: Operator; Operators + +:: + + exp := exp_cond '?' exp1 ':' exp2 + +conditionally evaluate an expression depending on the result of an expression. + +^^^^^^^^^^^^^ +Arithmetic +^^^^^^^^^^^^^ + +.. index:: + pair: Arithmetic Operators; Operators + +:: + + exp:= 'exp' op 'exp' + +Squirrel supports the standard arithmetic operators ``+, -, *, / and %``. +Other than that is also supports compact operators (``+=,-=,*=,/=,%=``) and +increment and decrement operators(++ and --);:: + + a += 2; + //is the same as writing + a = a + 2; + x++ + //is the same as writing + x = x + 1 + +All operators work normally with integers and floats; if one operand is an integer and one +is a float the result of the expression will be float. +The + operator has a special behavior with strings; if one of the operands is a string the +operator + will try to convert the other operand to string as well and concatenate both +together. For instances and tables, ``_tostring`` is invoked. + +^^^^^^^^^^^^^ +Relational +^^^^^^^^^^^^^ + +.. index:: + pair: Relational Operators; Operators + +:: + + exp:= 'exp' op 'exp' + +Relational operators in Squirrel are : ``==, <, <=, <, <=, !=`` + +These operators return true if the expression is false and a value different than true if the +expression is true. Internally the VM uses the integer 1 as true but this could change in +the future. + +^^^^^^^^^^^^^^ +3 ways compare +^^^^^^^^^^^^^^ + +.. index:: + pair: 3 ways compare operator; Operators + +:: + + exp:= 'exp' <=> 'exp' + +the 3 ways compare operator <=> compares 2 values A and B and returns an integer less than 0 +if A < B, 0 if A == B and an integer greater than 0 if A > B. + +^^^^^^^^^^^^^^ +Logical +^^^^^^^^^^^^^^ + +.. index:: + pair: Logical operators; Operators + +:: + + exp := exp op exp + exp := '!' exp + +Logical operators in Squirrel are : ``&&, ||, !`` + +The operator ``&&`` (logical and) returns null if its first argument is null, otherwise returns +its second argument. +The operator ``||`` (logical or) returns its first argument if is different than null, otherwise +returns the second argument. + +The '!' operator will return null if the given value to negate was different than null, or a +value different than null if the given value was null. + +^^^^^^^^^^^^^^^ +in operator +^^^^^^^^^^^^^^^ + +.. index:: + pair: in operator; Operators + +:: + + exp:= keyexp 'in' tableexp + +Tests the existence of a slot in a table. +Returns true if *keyexp* is a valid key in *tableexp* :: + + local t= + { + foo="I'm foo", + [123]="I'm not foo" + } + + if("foo" in t) dostuff("yep"); + if(123 in t) dostuff(); + +^^^^^^^^^^^^^^^^^^^ +instanceof operator +^^^^^^^^^^^^^^^^^^^ + +.. index:: + pair: instanceof operator; Operators + +:: + + exp:= instanceexp 'instanceof' classexp + +Tests if a class instance is an instance of a certain class. +Returns true if *instanceexp* is an instance of *classexp*. + +^^^^^^^^^^^^^^^^^^^ +typeof operator +^^^^^^^^^^^^^^^^^^^ + +.. index:: + pair: typeof operator; Operators + +:: + + exp:= 'typeof' exp + +returns the type name of a value as string.:: + + local a={},b="squirrel" + print(typeof a); //will print "table" + print(typeof b); //will print "string" + +^^^^^^^^^^^^^^^^^^^ +Comma operator +^^^^^^^^^^^^^^^^^^^ + +.. index:: + pair: Comma operator; Operators + +:: + + exp:= exp ',' exp + +The comma operator evaluates two expression left to right, the result of the operator is +the result of the expression on the right; the result of the left expression is discarded.:: + + local j=0,k=0; + for(local i=0; i<10; i++ , j++) + { + k = i + j; + } + local a,k; + a = (k=1,k+2); //a becomes 3 + +^^^^^^^^^^^^^^^^^^^ +Bitwise Operators +^^^^^^^^^^^^^^^^^^^ + +.. index:: + pair: Bitwise Operators; Operators + +:: + + exp:= 'exp' op 'exp' + exp := '~' exp + +Squirrel supports the standard C-like bitwise operators ``&, |, ^, ~, <<, >>`` plus the unsigned +right shift operator ``>>>``. The unsigned right shift works exactly like the normal right shift operator(``>>``) +except for treating the left operand as an unsigned integer, so is not affected by the sign. Those operators +only work on integer values; passing of any other operand type to these operators will +cause an exception. + +^^^^^^^^^^^^^^^^^^^^^ +Operators precedence +^^^^^^^^^^^^^^^^^^^^^ + +.. index:: + pair: Operators precedence; Operators + ++---------------------------------------+-----------+ +| ``-, ~, !, typeof , ++, --`` | highest | ++---------------------------------------+-----------+ +| ``/, *, %`` | ... | ++---------------------------------------+-----------+ +| ``+, -`` | | ++---------------------------------------+-----------+ +| ``<<, >>, >>>`` | | ++---------------------------------------+-----------+ +| ``<, <=, >, >=, instanceof`` | | ++---------------------------------------+-----------+ +| ``==, !=, <=>`` | | ++---------------------------------------+-----------+ +| ``&`` | | ++---------------------------------------+-----------+ +| ``^`` | | ++---------------------------------------+-----------+ +| ``&&, in`` | | ++---------------------------------------+-----------+ +| ``+=, =, -=, /=, *=, %=`` | ... | ++---------------------------------------+-----------+ +| ``, (comma operator)`` | lowest | ++---------------------------------------+-----------+ + +.. _table_contructor: + +----------------- +Table Constructor +----------------- + +.. index:: + single: Table Contructor + +:: + + tslots := ( 'id' '=' exp | '[' exp ']' '=' exp ) [','] + exp := '{' [tslots] '}' + +Creates a new table.:: + + local a = {} //create an empty table + +A table constructor can also contain slots declaration; With the syntax: :: + + local a = { + slot1 = "I'm the slot value" + } + +An alternative syntax can be:: + + '[' exp1 ']' = exp2 [','] + +A new slot with exp1 as key and exp2 as value is created:: + + local a= + { + [1]="I'm the value" + } + +Both syntaxes can be mixed:: + + local table= + { + a=10, + b="string", + [10]={}, + function bau(a,b) + { + return a+b; + } + } + +The comma between slots is optional. + +^^^^^^^^^^^^^^^^^^^^^^ +Table with JSON syntax +^^^^^^^^^^^^^^^^^^^^^^ + +.. index:: + single: Table with JSON syntax + +Since Squirrel 3.0 is possible to declare a table using JSON syntax(see http://www.wikipedia.org/wiki/JSON). + +the following JSON snippet: :: + + local x = { + "id": 1, + "name": "Foo", + "price": 123, + "tags": ["Bar","Eek"] + } + +is equivalent to the following squirrel code: :: + + local x = { + id = 1, + name = "Foo", + price = 123, + tags = ["Bar","Eek"] + } + +----------------- +clone +----------------- + +.. index:: + single: clone + +:: + + exp:= 'clone' exp + +Clone performs shallow copy of a table, array or class instance (copies all slots in the new object without +recursion). If the source table has a delegate, the same delegate will be assigned as +delegate (not copied) to the new table (see :ref:`Delegation `). + +After the new object is ready the "_cloned" meta method is called (see :ref:`Metamethods `). + +When a class instance is cloned the constructor is not invoked(initializations must rely on ```_cloned``` instead + +----------------- +Array contructor +----------------- + +.. index:: + single: Array constructor + +:: + + exp := '[' [explist] ']' + +Creates a new array.:: + + a <- [] //creates an empty array + +Arrays can be initialized with values during the construction:: + + a <- [1,"string!",[],{}] //creates an array with 4 elements diff --git a/src/vscript/squirrel/doc/source/reference/language/functions.rst b/src/vscript/squirrel/doc/source/reference/language/functions.rst new file mode 100644 index 00000000..f3f98c0b --- /dev/null +++ b/src/vscript/squirrel/doc/source/reference/language/functions.rst @@ -0,0 +1,272 @@ +.. _functions: + + +================= +Functions +================= + +.. index:: + single: Functions + +Functions are first class values like integer or strings and can be stored in table slots, +local variables, arrays and passed as function parameters. +Functions can be implemented in Squirrel or in a native language with calling conventions +compatible with ANSI C. + +-------------------- +Function declaration +-------------------- + +.. index:: + single: Function Declaration + +Functions are declared through the function expression:: + + local a = function(a, b, c) { return a + b - c; } + +or with the syntactic sugar:: + + function ciao(a,b,c) + { + return a+b-c; + } + +that is equivalent to:: + + this.ciao <- function(a,b,c) + { + return a+b-c; + } + +a local function can be declared with this syntactic sugar:: + + local function tuna(a,b,c) + { + return a+b-c; + } + +that is equivalent to:: + + local tuna = function(a,b,c) + { + return a+b-c; + } + +is also possible to declare something like:: + + T <- {} + function T::ciao(a,b,c) + { + return a+b-c; + } + + //that is equivalent to write + + T.ciao <- function(a,b,c) + { + return a+b-c; + } + + //or + + T <- { + function ciao(a,b,c) + { + return a+b-c; + } + } + +^^^^^^^^^^^^^^^^^^ +Default Paramaters +^^^^^^^^^^^^^^^^^^ + +.. index:: + single: Function Default Paramaters + +Squirrel's functions can have default parameters. + +A function with default parameters is declared as follows: :: + + function test(a,b,c = 10, d = 20) + { + .... + } + +when the function *test* is invoked and the parameter c or d are not specified, +the VM autometically assigns the default value to the unspecified parameter. A default parameter can be +any valid squirrel expression. The expression is evaluated at runtime. + +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Function with variable number of paramaters +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. index:: + single: Function with variable number of paramaters + +Squirrel's functions can have variable number of parameters(varargs functions). + +A vararg function is declared by adding three dots (`...`) at the end of its parameter list. + +When the function is called all the extra parameters will be accessible through the *array* +called ``vargv``, that is passed as implicit parameter. + +``vargv`` is a regular squirrel array and can be used accordingly.:: + + function test(a,b,...) + { + for(local i = 0; i< vargv.len(); i++) + { + ::print("varparam "+i+" = "+vargv[i]+"\n"); + } + foreach(i,val in vargv) + { + ::print("varparam "+i+" = "+val+"\n"); + } + } + + test("goes in a","goes in b",0,1,2,3,4,5,6,7,8); + +--------------- +Function calls +--------------- + +.. index:: + single: Function calls + +:: + + exp:= derefexp '(' explist ')' + +The expression is evaluated in this order: derefexp after the explist (arguments) and at +the end the call. + +A function call in Squirrel passes the current environment object *this* as a hidden parameter. +But when the function was immediately indexed from an object, *this* shall be the object +which was indexed, instead. + +If we call a function with the syntax:: + + mytable.foo(x,y) + +the environment object passed to 'foo' as *this* will be 'mytable' (since 'foo' was immediately indexed from 'mytable') + +Whereas with the syntax:: + + foo(x,y) // implicitly equivalent to this.foo(x,y) + +the environment object will be the current *this* (that is, propagated from the caller's *this*). + +It may help to remember the rules in the following way: + + foo(x,y) ---> this.foo(x,y) + table.foo(x,y) ---> call foo with (table,x,y) + +It may also help to consider why it works this way: it's designed to assist with object-oriented style. +When calling 'foo(x,y)' it's assumed you're calling another member of the object (or of the file) and +so should operate on the same object. +When calling 'mytable.foo(x,y)' it's written plainly that you're calling a member of a different object. + +--------------------------------------------- +Binding an environment to a function +--------------------------------------------- + +.. index:: + single: Binding an environment to a function + +while by default a squirrel function call passes as environment object 'this', the object +where the function was indexed from. However, is also possible to statically bind an evironment to a +closure using the built-in method ``closure.bindenv(env_obj)``. +The method bindenv() returns a new instance of a closure with the environment bound to it. +When an environment object is bound to a function, every time the function is invoked, its +'this' parameter will always be the previously bound environent. +This mechanism is useful to implement callbacks systems similar to C# delegates. + +.. note:: The closure keeps a weak reference to the bound environmet object, because of this if + the object is deleted, the next call to the closure will result in a ``null`` + environment object. + +--------------------------------------------- +Lambda Expressions +--------------------------------------------- + +.. index:: + single: Lambda Expressions + +:: + + exp := '@' '(' paramlist ')' exp + +Lambda expressions are a syntactic sugar to quickly define a function that consists of a single expression. +This feature comes handy when functional programming patterns are applied, like map/reduce or passing a compare method to +array.sort(). + +here a lambda expression:: + + local myexp = @(a,b) a + b + +that is equivalent to:: + + local myexp = function(a,b) { return a + b; } + +a more useful usage could be:: + + local arr = [2,3,5,8,3,5,1,2,6]; + arr.sort(@(a,b) a <=> b); + arr.sort(@(a,b) -(a <=> b)); + +that could have been written as:: + + local arr = [2,3,5,8,3,5,1,2,6]; + arr.sort(function(a,b) { return a <=> b; } ); + arr.sort(function(a,b) { return -(a <=> b); } ); + +other than being limited to a single expression lambdas support all features of regular functions. +in fact are implemented as a compile time feature. + +--------------------------------------------- +Free Variables +--------------------------------------------- + +.. index:: + single: Free Variables + +A free variable is a variable external from the function scope as is not a local variable +or parameter of the function. +Free variables reference a local variable from a outer scope. +In the following example the variables 'testy', 'x' and 'y' are bound to the function 'foo'.:: + + local x=10,y=20 + local testy="I'm testy" + + function foo(a,b) + { + ::print(testy); + return a+b+x+y; + } + +A program can read or write a free variable. + +--------------------------------------------- +Tail Recursion +--------------------------------------------- + +.. index:: + single: Tail Recursion + +Tail recursion is a method for partially transforming a recursion in a program into an +iteration: it applies when the recursive calls in a function are the last executed +statements in that function (just before the return). +If this happenes the squirrel interpreter collapses the caller stack frame before the +recursive call; because of that very deep recursions are possible without risk of a stack +overflow.:: + + function loopy(n) + { + if(n>0){ + ::print("n="+n+"\n"); + return loopy(n-1); + } + } + + loopy(1000); + diff --git a/src/vscript/squirrel/doc/source/reference/language/generators.rst b/src/vscript/squirrel/doc/source/reference/language/generators.rst new file mode 100644 index 00000000..6c03de9a --- /dev/null +++ b/src/vscript/squirrel/doc/source/reference/language/generators.rst @@ -0,0 +1,51 @@ +.. _generators: + + +================= +Generators +================= + +.. index:: + single: Generators + +A function that contains a ``yield`` statement is called *'generator function'* . +When a generator function is called, it does not execute the function body, instead it +returns a new suspended generator. +The returned generator can be resumed through the resume statement while it is alive. +The yield keyword, suspends the execution of a generator and optionally returns the +result of an expression to the function that resumed the generator. +The generator dies when it returns, this can happen through an explicit return +statement or by exiting the function body; If an unhandled exception (or runtime error) +occurs while a generator is running, the generator will automatically die. A dead +generator cannot be resumed anymore.:: + + function geny(n) + { + for(local i=1;i<=n;i+=1) + yield i; + return null; + } + + local gtor=geny(10); + local x; + while(x=resume gtor) print(x+"\n"); + +the output of this program will be:: + + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + +generators can also be iterated using the foreach statement. When a generator is evaluated +by ``foreach``, the generator will be resumed for each iteration until it returns. The value +returned by the ``return`` statement will be ignored. + +.. note:: A suspended generator will hold a strong reference to all the values stored in it's local variables except the ``this`` + object that is only a weak reference. A running generator hold a strong reference also to the ``this`` object. diff --git a/src/vscript/squirrel/doc/source/reference/language/lexical_structure.rst b/src/vscript/squirrel/doc/source/reference/language/lexical_structure.rst new file mode 100644 index 00000000..72e23f5c --- /dev/null +++ b/src/vscript/squirrel/doc/source/reference/language/lexical_structure.rst @@ -0,0 +1,154 @@ +.. _lexical_structure: + + +================= +Lexical Structure +================= + +.. index:: single: lexical structure + +----------- +Identifiers +----------- + +.. index:: single: identifiers + +Identifiers start with an alphabetic character or the symbol '_' followed by any number +of alphabetic characters, '_' or digits ([0-9]). Squirrel is a case sensitive language +meaning that the lowercase and uppercase representation of the same alphabetic +character are considered different characters. For instance, "foo", "Foo" and "fOo" are +treated as 3 distinct identifiers. + +----------- +Keywords +----------- + +.. index:: single: keywords + +The following words are reserved and cannot be used as identifiers: + ++------------+------------+-----------+------------+------------+-------------+ +| base | break | case | catch | class | clone | ++------------+------------+-----------+------------+------------+-------------+ +| continue | const | default | delete | else | enum | ++------------+------------+-----------+------------+------------+-------------+ +| extends | for | foreach | function | if | in | ++------------+------------+-----------+------------+------------+-------------+ +| local | null | resume | return | switch | this | ++------------+------------+-----------+------------+------------+-------------+ +| throw | try | typeof | while | yield | constructor | ++------------+------------+-----------+------------+------------+-------------+ +| instanceof | true | false | static | __LINE__ | __FILE__ | ++------------+------------+-----------+------------+------------+-------------+ + +Keywords are covered in detail later in this document. + +----------- +Operators +----------- + +.. index:: single: operators + +Squirrel recognizes the following operators: + ++----------+----------+----------+----------+----------+----------+----------+----------+ +| ``!`` | ``!=`` | ``||`` | ``==`` | ``&&`` | ``>=`` | ``<=`` | ``>`` | ++----------+----------+----------+----------+----------+----------+----------+----------+ +| ``<=>`` | ``+`` | ``+=`` | ``-`` | ``-=`` | ``/`` | ``/=`` | ``*`` | ++----------+----------+----------+----------+----------+----------+----------+----------+ +| ``*=`` | ``%`` | ``%=`` | ``++`` | ``--`` | ``<-`` | ``=`` | ``&`` | ++----------+----------+----------+----------+----------+----------+----------+----------+ +| ``^`` | ``|`` | ``~`` | ``>>`` | ``<<`` | ``>>>`` | | | ++----------+----------+----------+----------+----------+----------+----------+----------+ + +------------ +Other tokens +------------ + +.. index:: + single: delimiters + single: other tokens + +Other significant tokens are: + ++----------+----------+----------+----------+----------+----------+ +| ``{`` | ``}`` | ``[`` | ``]`` | ``.`` | ``:`` | ++----------+----------+----------+----------+----------+----------+ +| ``::`` | ``'`` | ``;`` | ``"`` | ``@"`` | | ++----------+----------+----------+----------+----------+----------+ + +----------- +Literals +----------- + +.. index:: + single: literals + single: string literals + single: numeric literals + +Squirrel accepts integer numbers, floating point numbers and string literals. + ++-------------------------------+------------------------------------------+ +| ``34`` | Integer number(base 10) | ++-------------------------------+------------------------------------------+ +| ``0xFF00A120`` | Integer number(base 16) | ++-------------------------------+------------------------------------------+ +| ``0753`` | Integer number(base 8) | ++-------------------------------+------------------------------------------+ +| ``'a'`` | Integer number | ++-------------------------------+------------------------------------------+ +| ``1.52`` | Floating point number | ++-------------------------------+------------------------------------------+ +| ``1.e2`` | Floating point number | ++-------------------------------+------------------------------------------+ +| ``1.e-2`` | Floating point number | ++-------------------------------+------------------------------------------+ +| ``"I'm a string"`` | String | ++-------------------------------+------------------------------------------+ +| ``@"I'm a verbatim string"`` | String | ++-------------------------------+------------------------------------------+ +| ``@" I'm a`` | | +| ``multiline verbatim string`` | | +| ``"`` | String | ++-------------------------------+------------------------------------------+ + +Pesudo BNF + +.. productionlist:: + IntegerLiteral : [1-9][0-9]* | '0x' [0-9A-Fa-f]+ | ''' [.]+ ''' | 0[0-7]+ + FloatLiteral : [0-9]+ '.' [0-9]+ + FloatLiteral : [0-9]+ '.' 'e'|'E' '+'|'-' [0-9]+ + StringLiteral: '"'[.]* '"' + VerbatimStringLiteral: '@''"'[.]* '"' + +----------- +Comments +----------- + +.. index:: single: comments + +A comment is text that the compiler ignores but that is useful for programmers. +Comments are normally used to embed annotations in the code. The compiler +treats them as white space. + +A comment can be ``/*`` (slash, asterisk) characters, followed by any +sequence of characters (including new lines), +followed by the ``*/`` characters. This syntax is the same as ANSI C.:: + + /* + this is + a multiline comment. + this lines will be ignored by the compiler + */ + +A comment can also be ``//`` (two slashes) characters, followed by any sequence of +characters. A new line not immediately preceded by a backslash terminates this form of +comment. It is commonly called a *"single-line comment."*:: + + //this is a single line comment. this line will be ignored by the compiler + +The character ``#`` is an alternative syntax for single line comment.:: + + # this is also a single line comment. + +This to facilitate the use of squirrel in UNIX-style shell scripts. diff --git a/src/vscript/squirrel/doc/source/reference/language/metamethods.rst b/src/vscript/squirrel/doc/source/reference/language/metamethods.rst new file mode 100644 index 00000000..260b0308 --- /dev/null +++ b/src/vscript/squirrel/doc/source/reference/language/metamethods.rst @@ -0,0 +1,270 @@ +.. _metamethods: + +----------- +Metamethods +----------- + +Metamethods are a mechanism that allows the customization of certain aspects of the +language semantics. Those methods are normal functions placed in a table +parent(delegate) or class declaration; It is possible to change many aspects of a table/class instance behavior by just defining +a metamethod. Class objects (not instances) support only 2 metamethods ``_newmember, _inherited`` . + +For example when we use relational operators other than '==' on 2 tables, the VM will +check if the table has a method in his parent called '_cmp'; if so it will call it to determine +the relation between the tables.:: + + local comparable={ + _cmp = function (other) + { + if(nameother.name)return 1; + return 0; + } + } + + local a={ name="Alberto" }.setdelegate(comparable); + local b={ name="Wouter" }.setdelegate(comparable); + + if(a>b) + print("a>b") + else + print("b<=a"); + +for classes the previous code become: :: + + class Comparable { + constructor(n) + { + name = n; + } + function _cmp(other) + { + if(nameother.name) return 1; + return 0; + } + name = null; + } + + local a = Comparable("Alberto"); + local b = Comparable("Wouter"); + + if(a>b) + print("a>b") + else + print("b<=a"); + +^^^^^ +_set +^^^^^ + +:: + + _set(idx,val) + +invoked when the index idx is not present in the object or in its delegate chain. +``_set`` must 'throw null' to notify that a key wasn't found but the there were not runtime errors (clean failure). +This allows the program to differentiate between a runtime error and a 'index not found'. + +^^^^^ +_get +^^^^^ + +:: + + _get(idx) + +invoked when the index idx is not present in the object or in its delegate chain. +_get must 'throw null' to notify that a key wasn't found but the there were not runtime errors (clean failure). +This allows the program to differentiate between a runtime error and a 'index not found'. + +^^^^^^^^^ +_newslot +^^^^^^^^^ + +:: + + _newslot(key,value) + +invoked when a script tries to add a new slot in a table. + +if the slot already exists in the target table the method will not be invoked also if the +"new slot" operator is used. + +^^^^^^^^^ +_delslot +^^^^^^^^^ + +:: + + _delslot(key) + +invoked when a script deletes a slot from a table. +if the method is invoked squirrel will not try to delete the slot himself + +^^^^^^^^ +_add +^^^^^^^^ + +:: + + _add(other) + +the + operator + +returns this + other + +^^^^^^^^^^^^^^^^^^^^^^^^ +_sub +^^^^^^^^^^^^^^^^^^^^^^^^ + +:: + + _sub(other) + +the - operator (like _add) + +^^^^^^^^^^^^^^^^^^^^^^^^ +_mul +^^^^^^^^^^^^^^^^^^^^^^^^ + +:: + + _mul(other) + +the ``*`` operator (like _add) + +^^^^^^^^^^^^^^^^^^^^^^^^ +_div +^^^^^^^^^^^^^^^^^^^^^^^^ + +:: + + _div(other) + +the ``/`` operator (like _add) + +^^^^^^^^^^^^^^^^^^^^^^^^ +_modulo +^^^^^^^^^^^^^^^^^^^^^^^^ + +:: + + _modulo(other) + +the ``%`` operator (like _add) + +^^^^^^^^^ +_unm +^^^^^^^^^ + +:: + + _unm() + +the unary minus operator + +^^^^^^^^^^^^^^^^^^^^^^^^ +_typeof +^^^^^^^^^^^^^^^^^^^^^^^^ + +:: + + _typeof() + +invoked by the typeof operator on tables, userdata, and class instances. + +Returns the type of ``this`` as string + +^^^^^^^^^^^^^^^^^^^^^^^^ +_cmp +^^^^^^^^^^^^^^^^^^^^^^^^ + +:: + + _cmp(other) + +invoked to emulate the ``< > <= >=`` and ``<=>`` operators + +returns an integer as follow: + ++-----------+----------------------------+ +| returns | relationship | ++===========+============================+ +| > 0 | if ``this`` > ``other`` | ++-----------+----------------------------+ +| 0 | if ``this`` == ``other`` | ++-----------+----------------------------+ +| < 0 | if ``this`` < ``other`` | ++-----------+----------------------------+ + +^^^^^^^^^^^^^^^^^^^^^^^^ +_call +^^^^^^^^^^^^^^^^^^^^^^^^ + +:: + + _call(other) + +invoked when a table, userdata, or class instance is called + +^^^^^^^^^^^^^^^^^^^^^^^^ +_cloned +^^^^^^^^^^^^^^^^^^^^^^^^ + +:: + + _cloned(original) + +invoked when a table or class instance is cloned(in the cloned table) + +^^^^^^^^^^^^^^^^^^^^^^^^ +_nexti +^^^^^^^^^^^^^^^^^^^^^^^^ + +:: + + _nexti(previdx) + +invoked when a userdata or class instance is iterated by a foreach loop. + +If previdx==null it means that it is the first iteration. +The function has to return the index of the 'next' value. + +^^^^^^^^^^^^^^^^^^^^^^^^ +_tostring +^^^^^^^^^^^^^^^^^^^^^^^^ + +:: + + _tostring() + +Invoked when during string concatenation or when the ``print`` function prints a table, instance, or userdata. +The method is also invoked by the sq_tostring() API. + +Must return a string representation of the object. + +^^^^^^^^^^^^^^^^^^^^^^^^ +_inherited +^^^^^^^^^^^^^^^^^^^^^^^^ + +:: + + _inherited(attributes) + +invoked when a class object inherits from the class implementing ``_inherited``. +The ``this`` contains the new class. + +Return value is ignored. + +^^^^^^^^^^^^^^^^^^^^^^^^ +_newmember +^^^^^^^^^^^^^^^^^^^^^^^^ + +:: + + _newmember(index,value,attributes,isstatic) + +invoked for each member declared in a class body (at declaration time). + +If the function is implemented, members will not be added to the class. diff --git a/src/vscript/squirrel/doc/source/reference/language/statements.rst b/src/vscript/squirrel/doc/source/reference/language/statements.rst new file mode 100644 index 00000000..febde526 --- /dev/null +++ b/src/vscript/squirrel/doc/source/reference/language/statements.rst @@ -0,0 +1,386 @@ +.. _statements: + + +================= +Statements +================= + +.. index:: + single: statements + +A squirrel program is a simple sequence of statements.:: + + stats := stat [';'|'\n'] stats + +Statements in squirrel are comparable to the C-Family languages (C/C++, Java, C# +etc...): assignment, function calls, program flow control structures etc.. plus some +custom statement like yield, table and array constructors (All those will be covered in detail +later in this document). +Statements can be separated with a new line or ';' (or with the keywords case or default if +inside a switch/case statement), both symbols are not required if the statement is +followed by '}'. + +------ +Block +------ + +.. index:: + pair: block; statement + +:: + + stat := '{' stats '}' + +A sequence of statements delimited by curly brackets ({ }) is called block; +a block is a statement itself. + +----------------------- +Control Flow Statements +----------------------- + +.. index:: + single: control flow statements + +squirrel implements the most common control flow statements: ``if, while, do-while, switch-case, for, foreach`` + +^^^^^^^^^^^^^^ +true and false +^^^^^^^^^^^^^^ + +.. index:: + single: true and false + single: true + single: false + +Squirrel has a boolean type (bool) however like C++ it considers null, 0(integer) and 0.0(float) +as *false*, any other value is considered *true*. + +^^^^^^^^^^^^^^^^^ +if/else statement +^^^^^^^^^^^^^^^^^ + +.. index:: + pair: if/else; statement + pair: if; statement + pair: else; statement + +:: + + stat:= 'if' '(' exp ')' stat ['else' stat] + +Conditionally execute a statement depending on the result of an expression.:: + + if(a>b) + a=b; + else + b=a; + //// + if(a==10) + { + b=a+b; + return a; + } + +^^^^^^^^^^^^^^^^^ +while statement +^^^^^^^^^^^^^^^^^ + +.. index:: + pair: while; statement + +:: + + stat:= 'while' '(' exp ')' stat + +Executes a statement while the condition is true.:: + + function testy(n) + { + local a=0; + while(a100) + +^^^^^^^^^^^^^^^^^ +switch statement +^^^^^^^^^^^^^^^^^ + +.. index:: + pair: switch; statement + +:: + + stat := 'switch' ''( exp ')' '{' + 'case' case_exp ':' + stats + ['default' ':' + stats] + '}' + +Switch is a control statement allows multiple selections of code by passing control to one of the +case statements within its body. +The control is transferred to the case label whose case_exp matches with exp if none of +the case match will jump to the default label (if present). +A switch statement can contain any number if case instances, if 2 case have the same +expression result the first one will be taken in account first. The default label is only +allowed once and must be the last one. +A break statement will jump outside the switch block. + +----- +Loops +----- + +.. index:: + single: Loops + +^^^^^^^^ +for +^^^^^^^^ + +.. index:: + pair: for; statement + +:: + + stat:= 'for' '(' [initexp] ';' [condexp] ';' [incexp] ')' statement + +Executes a statement as long as a condition is different than false.:: + + for(local a=0;a<10;a+=1) + print(a+"\n"); + //or + glob <- null + for(glob=0;glob<10;glob+=1){ + print(glob+"\n"); + } + //or + for(;;){ + print(loops forever+"\n"); + } + +^^^^^^^^ +foreach +^^^^^^^^ + +.. index:: + pair: foreach; statement + +:: + + 'foreach' '(' [index_id','] value_id 'in' exp ')' stat + +Executes a statement for every element contained in an array, table, class, string or generator. +If exp is a generator it will be resumed every iteration as long as it is alive; the value will +be the result of 'resume' and the index the sequence number of the iteration starting +from 0.:: + + local a=[10,23,33,41,589,56] + foreach(idx,val in a) + print("index="+idx+" value="+val+"\n"); + //or + foreach(val in a) + print("value="+val+"\n"); + +------- +break +------- + +.. index:: + pair: break; statement + +:: + + stat := 'break' + +The break statement terminates the execution of a loop (for, foreach, while or do/while) +or jumps out of switch statement; + +--------- +continue +--------- + +.. index:: + pair: continue; statement + +:: + + stat := 'continue' + +The continue operator jumps to the next iteration of the loop skipping the execution of +the following statements. + +--------- +return +--------- + +.. index:: + pair: return; statement + +:: + + stat:= return [exp] + +The return statement terminates the execution of the current function/generator and +optionally returns the result of an expression. If the expression is omitted the function +will return null. If the return statement is used inside a generator, the generator will not +be resumable anymore. + +--------- +yield +--------- + +.. index:: + pair: yield; statement + +:: + + stat := yield [exp] + +(see :ref:`Generators `). + + +--------------------------- +Local variables declaration +--------------------------- + +.. index:: + pair: Local variables declaration; statement + +:: + + initz := id [= exp][',' initz] + stat := 'local' initz + +Local variables can be declared at any point in the program; they exist between their +declaration to the end of the block where they have been declared. +*EXCEPTION:* a local declaration statement is allowed as first expression in a for loop.:: + + for(local a=0;a<10;a+=1) + print(a); + +-------------------- +Function declaration +-------------------- + +.. index:: + pair: Function declaration; statement + +:: + + funcname := id ['::' id] + stat:= 'function' id ['::' id]+ '(' args ')' stat + +creates a new function. + +----------------- +Class declaration +----------------- + +.. index:: + pair: Class declaration; statement + +:: + + memberdecl := id '=' exp [';'] | '[' exp ']' '=' exp [';'] | functionstat | 'constructor' functionexp + stat:= 'class' derefexp ['extends' derefexp] '{' + [memberdecl] + '}' + +creates a new class. + +----------- +try/catch +----------- + +.. index:: + pair: try/catch; statement + +:: + + stat:= 'try' stat 'catch' '(' id ')' stat + +The try statement encloses a block of code in which an exceptional condition can occur, +such as a runtime error or a throw statement. The catch clause provides the exception-handling +code. When a catch clause catches an exception, its id is bound to that +exception. + +----------- +throw +----------- + +.. index:: + pair: throw; statement + +:: + + stat:= 'throw' exp + +Throws an exception. Any value can be thrown. + +-------------- +const +-------------- + +.. index:: + pair: const; statement + +:: + + stat:= 'const' id '=' 'Integer | Float | StringLiteral + +Declares a constant (see :ref:`Constants & Enumerations `). + +-------------- +enum +-------------- + +.. index:: + pair: enum; statement + +:: + + enumerations := ( 'id' '=' Integer | Float | StringLiteral ) [','] + stat:= 'enum' id '{' enumerations '}' + +Declares an enumeration (see :ref:`Constants & Enumerations `). + +-------------------- +Expression statement +-------------------- + +.. index:: + pair: Expression statement; statement + +:: + + stat := exp + +In Squirrel every expression is also allowed as statement, if so, the result of the +expression is thrown away. + diff --git a/src/vscript/squirrel/doc/source/reference/language/tables.rst b/src/vscript/squirrel/doc/source/reference/language/tables.rst new file mode 100644 index 00000000..eb80a688 --- /dev/null +++ b/src/vscript/squirrel/doc/source/reference/language/tables.rst @@ -0,0 +1,71 @@ +.. _tables: + + +================= +Tables +================= + +.. index:: + single: Tables + +Tables are associative containers implemented as pairs of key/value (called slot); values +can be any possible type and keys any type except 'null'. +Tables are squirrel's skeleton, delegation and many other features are all implemented +through this type; even the environment, where "global" variables are stored, is a table +(known as root table). + +------------------ +Construction +------------------ + +Tables are created through the table constructor (see :ref:`Table constructor `) + +------------------ +Slot creation +------------------ + +.. index:: + single: Slot Creation(table) + +Adding a new slot in a existing table is done through the "new slot" operator ``<-``; this +operator behaves like a normal assignment except that if the slot does not exists it will +be created.:: + + local a = {} + +The following line will cause an exception because the slot named 'newslot' does not exist +in the table 'a':: + + a.newslot = 1234 + +this will succeed: :: + + a.newslot <- 1234; + +or:: + + a[1] <- "I'm the value of the new slot"; + +----------------- +Slot deletion +----------------- + +.. index:: + single: Slot Deletion(table) + + +:: + + exp:= delete derefexp + +Deletion of a slot is done through the keyword delete; the result of this expression will be +the value of the deleted slot.:: + + a <- { + test1=1234 + deleteme="now" + } + + delete a.test1 + print(delete a.deleteme); //this will print the string "now" + diff --git a/src/vscript/squirrel/doc/source/reference/language/threads.rst b/src/vscript/squirrel/doc/source/reference/language/threads.rst new file mode 100644 index 00000000..efe114ab --- /dev/null +++ b/src/vscript/squirrel/doc/source/reference/language/threads.rst @@ -0,0 +1,106 @@ +.. _threads: + + +======================== +Threads +======================== + +.. index:: + single: Threads + +Squirrel supports cooperative threads(also known as coroutines). +A cooperative thread is a subroutine that can suspended in mid-execution and provide a value to the +caller without returning program flow, then its execution can be resumed later from the same +point where it was suspended. +At first look a Squirrel thread can be confused with a generator, in fact their behaviour is quite similar. +However while a generator runs in the caller stack and can suspend only the local routine stack a thread +has its own execution stack, global table and error handler; This allows a thread to suspend nested calls and +have it's own error policies. + +------------------ +Using threads +------------------ + +.. index:: + single: Using Threads + +Threads are created through the built-in function 'newthread(func)'; this function +gets as parameter a squirrel function and bind it to the new thread objects (will be the thread body). +The returned thread object is initially in 'idle' state. the thread can be started with the function +'threadobj.call()'; the parameters passed to 'call' are passed to the thread function. + +A thread can be be suspended calling the function suspend(), when this happens the function +that wokeup(or started) the thread returns (If a parameter is passed to suspend() it will +be the return value of the wakeup function , if no parameter is passed the return value will be null). +A suspended thread can be resumed calling the function 'threadobj.wakeup', when this happens +the function that suspended the thread will return(if a parameter is passed to wakeup it will +be the return value of the suspend function, if no parameter is passed the return value will be null). + +A thread terminates when its main function returns or when an unhandled exception occurs during its execution.:: + + function coroutine_test(a,b) + { + ::print(a+" "+b+"\n"); + local ret = ::suspend("suspend 1"); + ::print("the coroutine says "+ret+"\n"); + ret = ::suspend("suspend 2"); + ::print("the coroutine says "+ret+"\n"); + ret = ::suspend("suspend 3"); + ::print("the coroutine says "+ret+"\n"); + return "I'm done" + } + + local coro = ::newthread(coroutine_test); + + local susparam = coro.call("test","coroutine"); //starts the coroutine + + local i = 1; + do + { + ::print("suspend passed ("+susparam+")\n") + susparam = coro.wakeup("ciao "+i); + ++i; + }while(coro.getstatus()=="suspended") + + ::print("return passed ("+susparam+")\n") + +the result of this program will be:: + + test coroutine + suspend passed (suspend 1) + the coroutine says ciao 1 + suspend passed (suspend 2) + the coroutine says ciao 2 + suspend passed (suspend 3) + the coroutine says ciao 3 + return passed (I'm done). + + +the following is an interesting example of how threads and tail recursion +can be combined.:: + + function state1() + { + ::suspend("state1"); + return state2(); //tail call + } + + function state2() + { + ::suspend("state2"); + return state3(); //tail call + } + + function state3() + { + ::suspend("state3"); + return state1(); //tail call + } + + local statethread = ::newthread(state1) + + ::print(statethread.call()+"\n"); + + for(local i = 0; i < 10000; i++) + ::print(statethread.wakeup()+"\n"); + diff --git a/src/vscript/squirrel/doc/source/reference/language/weak_references.rst b/src/vscript/squirrel/doc/source/reference/language/weak_references.rst new file mode 100644 index 00000000..5f87bc82 --- /dev/null +++ b/src/vscript/squirrel/doc/source/reference/language/weak_references.rst @@ -0,0 +1,61 @@ +.. _weak_references: + + +======================== +Weak References +======================== + +.. index:: + single: Weak References + + +The weak references allows the programmers to create references to objects without +influencing the lifetime of the object itself. +In squirrel Weak references are first-class objects created through the built-in method obj.weakref(). +All types except null implement the weakref() method; however in bools, integers, and floats the method +simply returns the object itself(this because this types are always passed by value). +When a weak references is assigned to a container (table slot,array,class or +instance) is treated differently than other objects; When a container slot that hold a weak +reference is fetched, it always returns the value pointed by the weak reference instead of the weak +reference object. This allow the programmer to ignore the fact that the value handled is weak. +When the object pointed by weak reference is destroyed, the weak reference is automatically set to null.:: + + local t = {} + local a = ["first","second","third"] + //creates a weakref to the array and assigns it to a table slot + t.thearray <- a.weakref(); + +The table slot 'thearray' contains a weak reference to an array. +The following line prints "first", because tables(and all other containers) always return +the object pointed by a weak ref:: + + print(t.thearray[0]); + +the only strong reference to the array is owned by the local variable 'a', so +because the following line assigns a integer to 'a' the array is destroyed.:: + + a = 123; + +When an object pointed by a weak ref is destroyed the weak ref is automatically set to null, +so the following line will print "null".:: + + ::print(typeof(t.thearray)) + +----------------------------------- +Handling weak references explicitly +----------------------------------- + +If a weak reference is assigned to a local variable, then is treated as any other value.:: + + local t = {} + local weakobj = t.weakref(); + +the following line prints "weakref".:: + + ::print(typeof(weakobj)) + +the object pointed by the weakref can be obtained through the built-in method weakref.ref(). + +The following line prints "table".:: + + ::print(typeof(weakobj.ref())) diff --git a/src/vscript/squirrel/doc/source/stdlib/index.rst b/src/vscript/squirrel/doc/source/stdlib/index.rst new file mode 100644 index 00000000..7137bdb5 --- /dev/null +++ b/src/vscript/squirrel/doc/source/stdlib/index.rst @@ -0,0 +1,39 @@ +.. _stdlib: + +################################# + Squirrel Standard Library 3.1 +################################# + +Copyright (c) 2003-2016 Alberto Demichelis + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + + +.. toctree:: + :maxdepth: 1 + :numbered: + + introduction.rst + stdiolib.rst + stdbloblib.rst + stdmathlib.rst + stdsystemlib.rst + stdstringlib.rst + stdauxlib.rst + diff --git a/src/vscript/squirrel/doc/source/stdlib/introduction.rst b/src/vscript/squirrel/doc/source/stdlib/introduction.rst new file mode 100644 index 00000000..171e2425 --- /dev/null +++ b/src/vscript/squirrel/doc/source/stdlib/introduction.rst @@ -0,0 +1,22 @@ +.. _stdlib_introduction: + +============ +Introduction +============ + +The squirrel standard libraries consist in a set of modules implemented in C++. +While are not essential for the language, they provide a set of useful services that are +commonly used by a wide range of applications(file I/O, regular expressions, etc...), +plus they offer a foundation for developing additional libraries. + +All libraries are implemented through the squirrel API and the ANSI C runtime library. +The modules are organized in the following way: + +* I/O : input and output +* blob : binary buffers manipilation +* math : basic mathematical routines +* system : system access function +* string : string formatting and manipulation +* aux : auxiliary functions + +The libraries can be registered independently,except for the IO library that depends from the bloblib. diff --git a/src/vscript/squirrel/doc/source/stdlib/stdauxlib.rst b/src/vscript/squirrel/doc/source/stdlib/stdauxlib.rst new file mode 100644 index 00000000..b800c100 --- /dev/null +++ b/src/vscript/squirrel/doc/source/stdlib/stdauxlib.rst @@ -0,0 +1,31 @@ +.. _stdlib_stdauxlib: + +=============== +The Aux library +=============== + +The aux library implements default handlers for compiler and runtime errors and a stack dumping. + ++++++++++++ +C API ++++++++++++ + +.. _sqstd_seterrorhandlers: + +.. c:function:: void sqstd_seterrorhandlers(HSQUIRRELVM v) + + :param HSQUIRRELVM v: the target VM + + initialize compiler and runtime error handlers, the handlers + use the print function set through(:ref:`sq_setprintfunc `) to output + the error. + +.. _sqstd_printcallstack: + +.. c:function:: void sqstd_printcallstack(HSQUIRRELVM v) + + :param HSQUIRRELVM v: the target VM + + prints the call stack and stack contents. the function + uses the print function set through(:ref:`sq_setprintfunc `) to output + the stack dump. diff --git a/src/vscript/squirrel/doc/source/stdlib/stdbloblib.rst b/src/vscript/squirrel/doc/source/stdlib/stdbloblib.rst new file mode 100644 index 00000000..83d34264 --- /dev/null +++ b/src/vscript/squirrel/doc/source/stdlib/stdbloblib.rst @@ -0,0 +1,213 @@ +.. _stdlib_stdbloblib: + +================== +The Blob library +================== +The blob library implements binary data manipulations routines. The library is +based on `blob objects` that represent a buffer of arbitrary +binary data. + +--------------- +Squirrel API +--------------- + ++++++++++++++++ +Global symbols ++++++++++++++++ + +.. js:function:: castf2i(f) + + casts a float to a int + +.. js:function:: casti2f(n) + + casts a int to a float + +.. js:function:: swap2(n) + + swap the byte order of a number (like it would be a 16bits integer) + +.. js:function:: swap4(n) + + swap the byte order of an integer + +.. js:function:: swapfloat(n) + + swaps the byteorder of a float + +++++++++++++++++++ +The blob class +++++++++++++++++++ + +The blob object is a buffer of arbitrary binary data. The object behaves like +a file stream, it has a read/write pointer and it automatically grows if data +is written out of his boundary. +A blob can also be accessed byte by byte through the `[]` operator. + +.. js:class:: blob(size) + + :param int size: initial size of the blob + + returns a new instance of a blob class of the specified size in bytes + +.. js:function:: blob.eos() + + returns a non null value if the read/write pointer is at the end of the stream. + +.. js:function:: blob.flush() + + flushes the stream.return a value != null if succeded, otherwise returns null + +.. js:function:: blob.len() + + returns the length of the stream + +.. js:function:: blob.readblob(size) + + :param int size: number of bytes to read + + read n bytes from the stream and returns them as blob + +.. js:function:: blob.readn(type) + + :param int type: type of the number to read + + reads a number from the stream according to the type parameter. + + `type` can have the following values: + ++--------------+--------------------------------------------------------------------------------+----------------------+ +| parameter | return description | return type | ++==============+================================================================================+======================+ +| 'l' | processor dependent, 32bits on 32bits processors, 64bits on 64bits processors | integer | ++--------------+--------------------------------------------------------------------------------+----------------------+ +| 'i' | 32bits number | integer | ++--------------+--------------------------------------------------------------------------------+----------------------+ +| 's' | 16bits signed integer | integer | ++--------------+--------------------------------------------------------------------------------+----------------------+ +| 'w' | 16bits unsigned integer | integer | ++--------------+--------------------------------------------------------------------------------+----------------------+ +| 'c' | 8bits signed integer | integer | ++--------------+--------------------------------------------------------------------------------+----------------------+ +| 'b' | 8bits unsigned integer | integer | ++--------------+--------------------------------------------------------------------------------+----------------------+ +| 'f' | 32bits float | float | ++--------------+--------------------------------------------------------------------------------+----------------------+ +| 'd' | 64bits float | float | ++--------------+--------------------------------------------------------------------------------+----------------------+ + +.. js:function:: blob.resize(size) + + :param int size: the new size of the blob in bytes + + resizes the blob to the specified `size` + +.. js:function:: blob.seek(offset [,origin]) + + :param int offset: indicates the number of bytes from `origin`. + :param int origin: origin of the seek + + +--------------+-------------------------------------------+ + | 'b' | beginning of the stream | + +--------------+-------------------------------------------+ + | 'c' | current location | + +--------------+-------------------------------------------+ + | 'e' | end of the stream | + +--------------+-------------------------------------------+ + + Moves the read/write pointer to a specified location. + +.. note:: If origin is omitted the parameter is defaulted as 'b'(beginning of the stream). + +.. js:function:: blob.swap2() + + swaps the byte order of the blob content as it would be an array of `16bits integers` + +.. js:function:: blob.swap4() + + swaps the byte order of the blob content as it would be an array of `32bits integers` + +.. js:function:: blob.tell() + + returns the read/write pointer absolute position + +.. js:function:: blob.writeblob(src) + + :param blob src: the source blob containing the data to be written + + writes a blob in the stream + +.. js:function:: blob.writen(n, type) + + :param number n: the value to be written + :param int type: type of the number to write + + writes a number in the stream formatted according to the `type` parameter + + `type` can have the following values: + ++--------------+--------------------------------------------------------------------------------+ +| parameter | return description | ++==============+================================================================================+ +| 'i' | 32bits number | ++--------------+--------------------------------------------------------------------------------+ +| 's' | 16bits signed integer | ++--------------+--------------------------------------------------------------------------------+ +| 'w' | 16bits unsigned integer | ++--------------+--------------------------------------------------------------------------------+ +| 'c' | 8bits signed integer | ++--------------+--------------------------------------------------------------------------------+ +| 'b' | 8bits unsigned integer | ++--------------+--------------------------------------------------------------------------------+ +| 'f' | 32bits float | ++--------------+--------------------------------------------------------------------------------+ +| 'd' | 64bits float | ++--------------+--------------------------------------------------------------------------------+ + + +------ +C API +------ + +.. _sqstd_register_bloblib: + +.. c:function:: SQRESULT sqstd_register_bloblib(HSQUIRRELVM v) + + :param HSQUIRRELVM v: the target VM + :returns: an SQRESULT + :remarks: The function aspects a table on top of the stack where to register the global library functions. + + initializes and registers the blob library in the given VM. + +.. _sqstd_getblob: + +.. c:function:: SQRESULT sqstd_getblob(HSQUIRRELVM v, SQInteger idx, SQUserPointer* ptr) + + :param HSQUIRRELVM v: the target VM + :param SQInteger idx: and index in the stack + :param SQUserPointer* ptr: A pointer to the userpointer that will point to the blob's payload + :returns: an SQRESULT + + retrieve the pointer of a blob's payload from an arbitrary + position in the stack. + +.. _sqstd_getblobsize: + +.. c:function:: SQInteger sqstd_getblobsize(HSQUIRRELVM v, SQInteger idx) + + :param HSQUIRRELVM v: the target VM + :param SQInteger idx: and index in the stack + :returns: the size of the blob at `idx` position + + retrieves the size of a blob's payload from an arbitrary + position in the stack. + +.. _sqstd_createblob: + +.. c:function:: SQUserPointer sqstd_createblob(HSQUIRRELVM v, SQInteger size) + + :param HSQUIRRELVM v: the target VM + :param SQInteger size: the size of the blob payload that has to be created + :returns: a pointer to the newly created blob payload + + creates a blob with the given payload size and pushes it in the stack. diff --git a/src/vscript/squirrel/doc/source/stdlib/stdiolib.rst b/src/vscript/squirrel/doc/source/stdlib/stdiolib.rst new file mode 100644 index 00000000..c7de168e --- /dev/null +++ b/src/vscript/squirrel/doc/source/stdlib/stdiolib.rst @@ -0,0 +1,264 @@ +.. _stdlib_stdiolib: + +======================== +The Input/Output library +======================== + +the i/o library implements basic input/output routines. + +-------------- +Squirrel API +-------------- + +++++++++++++++ +Global Symbols +++++++++++++++ + + +.. js:function:: dofile(path, [raiseerror]) + + compiles a squirrel script or loads a precompiled one and executes it. + returns the value returned by the script or null if no value is returned. + if the optional parameter 'raiseerror' is true, the compiler error handler is invoked + in case of a syntax error. If raiseerror is omitted or set to false, the compiler + error handler is not invoked. + When squirrel is compiled in Unicode mode the function can handle different character encodings, + UTF8 with and without prefix and UCS-2 prefixed(both big endian an little endian). + If the source stream is not prefixed UTF8 encoding is used as default. + +.. js:function:: loadfile(path, [raiseerror]) + + compiles a squirrel script or loads a precompiled one an returns it as as function. + if the optional parameter 'raiseerror' is true, the compiler error handler is invoked + in case of a syntax error. If raiseerror is omitted or set to false, the compiler + error handler is not invoked. + When squirrel is compiled in Unicode mode the function can handle different character encodings, + UTF8 with and without prefix and UCS-2 prefixed(both big endian an little endian). + If the source stream is not prefixed UTF8 encoding is used as default. + +.. js:function:: writeclosuretofile(destpath, closure) + + serializes a closure to a bytecode file (destpath). The serialized file can be loaded + using loadfile() and dofile(). + + +.. js:data:: stderr + + File object bound on the os *standard error* stream + +.. js:data:: stdin + + File object bound on the os *standard input* stream + +.. js:data:: stdout + + File object bound on the os *standard output* stream + + +++++++++++++++ +The file class +++++++++++++++ + + The file object implements a stream on a operating system file. + +.. js:class:: file(path, patten) + + It's constructor imitates the behaviour of the C runtime function fopen for eg. :: + + local myfile = file("test.xxx","wb+"); + + creates a file with read/write access in the current directory. + +.. js:function:: file.close() + + closes the file. + +.. js:function:: file.eos() + + returns a non null value if the read/write pointer is at the end of the stream. + +.. js:function:: file.flush() + + flushes the stream.return a value != null if succeeded, otherwise returns null + +.. js:function:: file.len() + + returns the length of the stream + +.. js:function:: file.readblob(size) + + :param int size: number of bytes to read + + read n bytes from the stream and returns them as blob + +.. js:function:: file.readn(type) + + :param int type: type of the number to read + + reads a number from the stream according to the type parameter. + + `type` can have the following values: + ++--------------+--------------------------------------------------------------------------------+----------------------+ +| parameter | return description | return type | ++==============+================================================================================+======================+ +| 'l' | processor dependent, 32bits on 32bits processors, 64bits on 64bits processors | integer | ++--------------+--------------------------------------------------------------------------------+----------------------+ +| 'i' | 32bits number | integer | ++--------------+--------------------------------------------------------------------------------+----------------------+ +| 's' | 16bits signed integer | integer | ++--------------+--------------------------------------------------------------------------------+----------------------+ +| 'w' | 16bits unsigned integer | integer | ++--------------+--------------------------------------------------------------------------------+----------------------+ +| 'c' | 8bits signed integer | integer | ++--------------+--------------------------------------------------------------------------------+----------------------+ +| 'b' | 8bits unsigned integer | integer | ++--------------+--------------------------------------------------------------------------------+----------------------+ +| 'f' | 32bits float | float | ++--------------+--------------------------------------------------------------------------------+----------------------+ +| 'd' | 64bits float | float | ++--------------+--------------------------------------------------------------------------------+----------------------+ + +.. js:function:: file.resize(size) + + :param int size: the new size of the blob in bytes + + resizes the blob to the specified `size` + +.. js:function:: file.seek(offset [,origin]) + + :param int offset: indicates the number of bytes from `origin`. + :param int origin: origin of the seek + + +--------------+-------------------------------------------+ + | 'b' | beginning of the stream | + +--------------+-------------------------------------------+ + | 'c' | current location | + +--------------+-------------------------------------------+ + | 'e' | end of the stream | + +--------------+-------------------------------------------+ + + Moves the read/write pointer to a specified location. + +.. note:: If origin is omitted the parameter is defaulted as 'b'(beginning of the stream). + +.. js:function:: file.tell() + + returns the read/write pointer absolute position + +.. js:function:: file.writeblob(src) + + :param blob src: the source blob containing the data to be written + + writes a blob in the stream + +.. js:function:: file.writen(n, type) + + :param number n: the value to be written + :param int type: type of the number to write + + writes a number in the stream formatted according to the `type` pamraeter + + `type` can have the following values: + ++--------------+--------------------------------------------------------------------------------+ +| parameter | return description | ++==============+================================================================================+ +| 'i' | 32bits number | ++--------------+--------------------------------------------------------------------------------+ +| 's' | 16bits signed integer | ++--------------+--------------------------------------------------------------------------------+ +| 'w' | 16bits unsigned integer | ++--------------+--------------------------------------------------------------------------------+ +| 'c' | 8bits signed integer | ++--------------+--------------------------------------------------------------------------------+ +| 'b' | 8bits unsigned integer | ++--------------+--------------------------------------------------------------------------------+ +| 'f' | 32bits float | ++--------------+--------------------------------------------------------------------------------+ +| 'd' | 64bits float | ++--------------+--------------------------------------------------------------------------------+ + + +-------------- +C API +-------------- + +.. _sqstd_register_iolib: + +.. c:function:: SQRESULT sqstd_register_iolib(HSQUIRRELVM v) + + :param HSQUIRRELVM v: the target VM + :returns: an SQRESULT + :remarks: The function aspects a table on top of the stack where to register the global library functions. + + initialize and register the io library in the given VM. + +++++++++++++++ +File Object +++++++++++++++ + +.. c:function:: SQRESULT sqstd_createfile(HSQUIRRELVM v, SQFILE file, SQBool owns) + + :param HSQUIRRELVM v: the target VM + :param SQFILE file: the stream that will be rapresented by the file object + :param SQBool owns: if different true the stream will be automatically closed when the newly create file object is destroyed. + :returns: an SQRESULT + + creates a file object bound to the SQFILE passed as parameter + and pushes it in the stack + +.. c:function:: SQRESULT sqstd_getfile(HSQUIRRELVM v, SQInteger idx, SQFILE* file) + + :param HSQUIRRELVM v: the target VM + :param SQInteger idx: and index in the stack + :param SQFILE* file: A pointer to a SQFILE handle that will store the result + :returns: an SQRESULT + + retrieve the pointer of a stream handle from an arbitrary + position in the stack. + +++++++++++++++++++++++++++++++++ +Script loading and serialization +++++++++++++++++++++++++++++++++ + +.. c:function:: SQRESULT sqstd_loadfile(HSQUIRRELVM v, const SQChar* filename, SQBool printerror) + + :param HSQUIRRELVM v: the target VM + :param SQChar* filename: path of the script that has to be loaded + :param SQBool printerror: if true the compiler error handler will be called if a error occurs + :returns: an SQRESULT + + Compiles a squirrel script or loads a precompiled one an pushes it as closure in the stack. + When squirrel is compiled in Unicode mode the function can handle different character encodings, + UTF8 with and without prefix and UCS-2 prefixed(both big endian an little endian). + If the source stream is not prefixed UTF8 encoding is used as default. + +.. c:function:: SQRESULT sqstd_dofile(HSQUIRRELVM v, const SQChar* filename, SQBool retval, SQBool printerror) + + :param HSQUIRRELVM v: the target VM + :param SQChar* filename: path of the script that has to be loaded + :param SQBool retval: if true the function will push the return value of the executed script in the stack. + :param SQBool printerror: if true the compiler error handler will be called if a error occurs + :returns: an SQRESULT + :remarks: the function expects a table on top of the stack that will be used as 'this' for the execution of the script. The 'this' parameter is left untouched in the stack. + + Compiles a squirrel script or loads a precompiled one and executes it. + Optionally pushes the return value of the executed script in the stack. + When squirrel is compiled in unicode mode the function can handle different character encodings, + UTF8 with and without prefix and UCS-2 prefixed(both big endian an little endian). + If the source stream is not prefixed, UTF8 encoding is used as default. :: + + sq_pushroottable(v); //push the root table(were the globals of the script will are stored) + sqstd_dofile(v, _SC("test.nut"), SQFalse, SQTrue);// also prints syntax errors if any + +.. c:function:: SQRESULT sqstd_writeclosuretofile(HSQUIRRELVM v, const SQChar* filename) + + :param HSQUIRRELVM v: the target VM + :param SQChar* filename: destination path of serialized closure + :returns: an SQRESULT + + serializes the closure at the top position in the stack as bytecode in + the file specified by the parameter filename. If a file with the + same name already exists, it will be overwritten. + diff --git a/src/vscript/squirrel/doc/source/stdlib/stdmathlib.rst b/src/vscript/squirrel/doc/source/stdlib/stdmathlib.rst new file mode 100644 index 00000000..5f0b8b49 --- /dev/null +++ b/src/vscript/squirrel/doc/source/stdlib/stdmathlib.rst @@ -0,0 +1,111 @@ +.. _stdlib_stdmathlib: + +================ +The Math library +================ + +the math lib provides basic mathematic routines. The library mimics the +C runtime library implementation. + +------------ +Squirrel API +------------ + ++++++++++++++++ +Global Symbols ++++++++++++++++ + +.. js:function:: abs(x) + + returns the absolute value of `x` as an integer + +.. js:function:: acos(x) + + returns the arccosine of `x` + +.. js:function:: asin(x) + + returns the arcsine of `x` + +.. js:function:: atan(x) + + returns the arctangent of `x` + +.. js:function:: atan2(x,y) + + returns the arctangent of `x/y` + +.. js:function:: ceil(x) + + returns a float value representing the smallest integer that is greater than or equal to `x` + +.. js:function:: cos(x) + + returns the cosine of `x` + +.. js:function:: exp(x) + + returns the exponential value of the float parameter `x` + +.. js:function:: fabs(x) + + returns the absolute value of `x` as a float + +.. js:function:: floor(x) + + returns a float value representing the largest integer that is less than or equal to `x` + +.. js:function:: log(x) + + returns the natural logarithm of `x` + +.. js:function:: log10(x) + + returns the logarithm base-10 of `x` + +.. js:function:: pow(x,y) + + returns `x` raised to the power of `y` + +.. js:function:: rand() + + returns a pseudorandom integer in the range 0 to `RAND_MAX` + +.. js:function:: sin(x) + + rreturns the sine of `x` + +.. js:function:: sqrt(x) + + returns the square root of `x` + +.. js:function:: srand(seed) + + sets the starting point for generating a series of pseudorandom integers + +.. js:function:: tan(x) + + returns the tangent of `x` + +.. js:data:: RAND_MAX + + the maximum value that can be returned by the `rand()` function + +.. js:data:: PI + + The numeric constant pi (3.141592) is the ratio of the circumference of a circle to its diameter + +------------ +C API +------------ + +.. _sqstd_register_mathlib: + +.. c:function:: SQRESULT sqstd_register_mathlib(HSQUIRRELVM v) + + :param HSQUIRRELVM v: the target VM + :returns: an SQRESULT + :remarks: The function aspects a table on top of the stack where to register the global library functions. + + initializes and register the math library in the given VM. + diff --git a/src/vscript/squirrel/doc/source/stdlib/stdstringlib.rst b/src/vscript/squirrel/doc/source/stdlib/stdstringlib.rst new file mode 100644 index 00000000..dd929346 --- /dev/null +++ b/src/vscript/squirrel/doc/source/stdlib/stdstringlib.rst @@ -0,0 +1,323 @@ +.. _stdlib_stdstringlib: + +================== +The String library +================== + +the string lib implements string formatting and regular expression matching routines. + +-------------- +Squirrel API +-------------- + +++++++++++++++ +Global Symbols +++++++++++++++ + +.. js:function:: endswith(str, cmp) + + returns `true` if the end of the string `str` matches a the string `cmp` otherwise returns `false` + +.. js:function:: escape(str) + + Returns a string with backslashes before characters that need to be escaped(`\",\a,\b,\t,\n,\v,\f,\r,\\,\",\',\0,\xnn`). + +.. js:function:: format(formatstr, ...) + + Returns a string formatted according `formatstr` and the optional parameters following it. + The format string follows the same rules as the `printf` family of + standard C functions( the "*" is not supported). :: + + e.g. + sq> print(format("%s %d 0x%02X\n","this is a test :",123,10)); + this is a test : 123 0x0A + +.. js:function:: printf(formatstr, ...) + + Just like calling `print(format(formatstr` as in the example above, but is more convenient AND more efficient. :: + + e.g. + sq> printf("%s %d 0x%02X\n","this is a test :",123,10); + this is a test : 123 0x0A + +.. js:function:: lstrip(str) + + Strips white-space-only characters that might appear at the beginning of the given string + and returns the new stripped string. + +.. js:function:: rstrip(str) + + Strips white-space-only characters that might appear at the end of the given string + and returns the new stripped string. + +.. js:function:: split(str, separators [, skipempty]) + + returns an array of strings split at each point where a separator character occurs in `str`. + The separator is not returned as part of any array element. + The parameter `separators` is a string that specifies the characters as to be used for the splitting. + The parameter `skipempty` is a boolean (default false). If `skipempty` is true, empty strings are not added to array. + + :: + + eg. + local a = split("1.2-3;;4/5",".-/;"); + // the result will be [1,2,3,,4,5] + or + local b = split("1.2-3;;4/5",".-/;",true); + // the result will be [1,2,3,4,5] + + +.. js:function:: startswith(str, cmp) + + returns `true` if the beginning of the string `str` matches the string `cmp`; otherwise returns `false` + +.. js:function:: strip(str) + + Strips white-space-only characters that might appear at the beginning or end of the given string and returns the new stripped string. + +++++++++++++++++++ +The regexp class +++++++++++++++++++ + +.. js:class:: regexp(pattern) + + The regexp object represents a precompiled regular expression pattern. The object is created + through `regexp(pattern)`. + + ++---------------------+--------------------------------------+ +| `\\` | Quote the next metacharacter | ++---------------------+--------------------------------------+ +| `^` | Match the beginning of the string | ++---------------------+--------------------------------------+ +| `.` | Match any character | ++---------------------+--------------------------------------+ +| `$` | Match the end of the string | ++---------------------+--------------------------------------+ +| `|` | Alternation | ++---------------------+--------------------------------------+ +| `(subexp)` | Grouping (creates a capture) | ++---------------------+--------------------------------------+ +| `(?:subexp)` | No Capture Grouping (no capture) | ++---------------------+--------------------------------------+ +| `[]` | Character class | ++---------------------+--------------------------------------+ + +**GREEDY CLOSURES** + ++---------------------+---------------------------------------------+ +| `*` | Match 0 or more times | ++---------------------+---------------------------------------------+ +| `+` | Match 1 or more times | ++---------------------+---------------------------------------------+ +| `?` | Match 1 or 0 times | ++---------------------+---------------------------------------------+ +| `{n}` | Match exactly n times | ++---------------------+---------------------------------------------+ +| `{n,}` | Match at least n times | ++---------------------+---------------------------------------------+ +| `{n,m}` | Match at least n but not more than m times | ++---------------------+---------------------------------------------+ + +**ESCAPE CHARACTERS** + ++---------------------+--------------------------------------+ +| `\\t` | tab (HT, TAB) | ++---------------------+--------------------------------------+ +| `\\n` | newline (LF, NL) | ++---------------------+--------------------------------------+ +| `\\r` | return (CR) | ++---------------------+--------------------------------------+ +| `\\f` | form feed (FF) | ++---------------------+--------------------------------------+ + +**PREDEFINED CLASSES** + ++---------------------+--------------------------------------+ +| `\\l` | lowercase next char | ++---------------------+--------------------------------------+ +| `\\u` | uppercase next char | ++---------------------+--------------------------------------+ +| `\\a` | letters | ++---------------------+--------------------------------------+ +| `\\A` | non letters | ++---------------------+--------------------------------------+ +| `\\w` | alphanumeric `[_0-9a-zA-Z]` | ++---------------------+--------------------------------------+ +| `\\W` | non alphanumeric `[^_0-9a-zA-Z]` | ++---------------------+--------------------------------------+ +| `\\s` | space | ++---------------------+--------------------------------------+ +| `\\S` | non space | ++---------------------+--------------------------------------+ +| `\\d` | digits | ++---------------------+--------------------------------------+ +| `\\D` | non digits | ++---------------------+--------------------------------------+ +| `\\x` | hexadecimal digits | ++---------------------+--------------------------------------+ +| `\\X` | non hexadecimal digits | ++---------------------+--------------------------------------+ +| `\\c` | control characters | ++---------------------+--------------------------------------+ +| `\\C` | non control characters | ++---------------------+--------------------------------------+ +| `\\p` | punctuation | ++---------------------+--------------------------------------+ +| `\\P` | non punctuation | ++---------------------+--------------------------------------+ +| `\\b` | word boundary | ++---------------------+--------------------------------------+ +| `\\B` | non word boundary | ++---------------------+--------------------------------------+ + + +.. js:function:: regexp.capture(str [, start]) + + returns an array of tables containing two indexes ("begin" and "end") of + the first match of the regular expression in the string `str`. + An array entry is created for each captured sub expressions. If no match occurs returns null. + The search starts from the index `start` + of the string; if `start` is omitted the search starts from the beginning of the string. + + The first element of the returned array(index 0) always contains the complete match. + + :: + + local ex = regexp(@"(\d+) ([a-zA-Z]+)(\p)"); + local string = "stuff 123 Test;"; + local res = ex.capture(string); + foreach(i,val in res) + { + print(format("match number[%02d] %s\n", + i,string.slice(val.begin,val.end))); //prints "Test" + } + + ... + will print + match number[00] 123 Test; + match number[01] 123 + match number[02] Test + match number[03] ; + +.. js:function:: regexp.match(str) + + returns a true if the regular expression matches the string + `str`, otherwise returns false. + +.. js:function:: regexp.search(str [, start]) + + returns a table containing two indexes ("begin" and "end") of the first match of the regular expression in + the string `str`, otherwise if no match occurs returns null. The search starts from the index `start` + of the string; if `start` is omitted the search starts from the beginning of the string. + + :: + + local ex = regexp("[a-zA-Z]+"); + local string = "123 Test;"; + local res = ex.search(string); + print(string.slice(res.begin,res.end)); //prints "Test" + +------------- +C API +------------- + +.. _sqstd_register_stringlib: + +.. c:function:: SQRESULT sqstd_register_stringlib(HSQUIRRELVM v) + + :param HSQUIRRELVM v: the target VM + :returns: an SQRESULT + :remarks: The function aspects a table on top of the stack where to register the global library functions. + + initialize and register the string library in the given VM. + ++++++++++++++ +Formatting ++++++++++++++ + +.. c:function:: SQRESULT sqstd_format(HSQUIRRELVM v, SQInteger nformatstringidx, SQInteger* outlen, SQChar** output) + + :param HSQUIRRELVM v: the target VM + :param SQInteger nformatstringidx: index in the stack of the format string + :param SQInteger* outlen: a pointer to an integer that will be filled with the length of the newly created string + :param SQChar** output: a pointer to a string pointer that will receive the newly created string + :returns: an SQRESULT + :remarks: the newly created string is allocated in the scratchpad memory. + + + creates a new string formatted according to the object at position `nformatstringidx` and the optional parameters following it. + The format string follows the same rules as the `printf` family of + standard C functions( the "*" is not supported). + +++++++++++++++++++ +Regular Expessions +++++++++++++++++++ + +.. c:function:: SQRex* sqstd_rex_compile(const SQChar *pattern, const SQChar ** error) + + :param SQChar* pattern: a pointer to a zero terminated string containing the pattern that has to be compiled. + :param SQChar** error: a pointer to a string pointer that will be set with an error string in case of failure. + :returns: a pointer to the compiled pattern + + compiles an expression and returns a pointer to the compiled version. + in case of failure returns NULL.The returned object has to be deleted + through the function sqstd_rex_free(). + +.. c:function:: void sqstd_rex_free(SQRex * exp) + + :param SQRex* exp: the expression structure that has to be deleted. + + deletes a expression structure created with sqstd_rex_compile() + +.. c:function:: SQBool sqstd_rex_match(SQRex * exp,const SQChar * text) + + :param SQRex* exp: a compiled expression + :param SQChar* text: the string that has to be tested + :returns: SQTrue if successful otherwise SQFalse + + returns SQTrue if the string specified in the parameter text is an + exact match of the expression, otherwise returns SQFalse. + +.. c:function:: SQBool sqstd_rex_search(SQRex * exp, const SQChar * text, const SQChar ** out_begin, const SQChar ** out_end) + + :param SQRex* exp: a compiled expression + :param SQChar* text: the string that has to be tested + :param SQChar** out_begin: a pointer to a string pointer that will be set with the beginning of the match + :param SQChar** out_end: a pointer to a string pointer that will be set with the end of the match + :returns: SQTrue if successful otherwise SQFalse + + searches the first match of the expression in the string specified in the parameter text. + if the match is found returns SQTrue and the sets out_begin to the beginning of the + match and out_end at the end of the match; otherwise returns SQFalse. + +.. c:function:: SQBool sqstd_rex_searchrange(SQRex * exp, const SQChar * text_begin, const SQChar * text_end, const SQChar ** out_begin, const SQChar ** out_end) + + :param SQRex* exp: a compiled expression + :param SQChar* text_begin: a pointer to the beginnning of the string that has to be tested + :param SQChar* text_end: a pointer to the end of the string that has to be tested + :param SQChar** out_begin: a pointer to a string pointer that will be set with the beginning of the match + :param SQChar** out_end: a pointer to a string pointer that will be set with the end of the match + :returns: SQTrue if successful otherwise SQFalse + + searches the first match of the expression in the string delimited + by the parameter text_begin and text_end. + if the match is found returns SQTrue and sets out_begin to the beginning of the + match and out_end at the end of the match; otherwise returns SQFalse. + +.. c:function:: SQInteger sqstd_rex_getsubexpcount(SQRex * exp) + + :param SQRex* exp: a compiled expression + :returns: the number of sub expressions matched by the expression + + returns the number of sub expressions matched by the expression + +.. c:function:: SQBool sqstd_rex_getsubexp(SQRex * exp, SQInteger n, SQRexMatch *subexp) + + :param SQRex* exp: a compiled expression + :param SQInteger n: the index of the submatch(0 is the complete match) + :param SQRexMatch* a: pointer to structure that will store the result + :returns: the function returns SQTrue if n is a valid index; otherwise SQFalse. + + retrieve the begin and and pointer to the length of the sub expression indexed + by n. The result is passed through the struct SQRexMatch. diff --git a/src/vscript/squirrel/doc/source/stdlib/stdsystemlib.rst b/src/vscript/squirrel/doc/source/stdlib/stdsystemlib.rst new file mode 100644 index 00000000..5c565fd6 --- /dev/null +++ b/src/vscript/squirrel/doc/source/stdlib/stdsystemlib.rst @@ -0,0 +1,82 @@ +.. _stdlib_stdsystemlib: + +================== +The System library +================== + +The system library exposes operating system facilities like environment variables, +date time manipulation etc.. + +-------------- +Squirrel API +-------------- + +++++++++++++++ +Global Symbols +++++++++++++++ + +.. js:function:: clock() + + returns a float representing the number of seconds elapsed since the start of the process + +.. js:function:: date([time [, format]]) + + returns a table containing a date/time split into the slots: + ++-------------+----------------------------------------+ +| sec | Seconds after minute (0 - 59). | ++-------------+----------------------------------------+ +| min | Minutes after hour (0 - 59). | ++-------------+----------------------------------------+ +| hour | Hours since midnight (0 - 23). | ++-------------+----------------------------------------+ +| day | Day of month (1 - 31). | ++-------------+----------------------------------------+ +| month | Month (0 - 11; January = 0). | ++-------------+----------------------------------------+ +| year | Year (current year). | ++-------------+----------------------------------------+ +| wday | Day of week (0 - 6; Sunday = 0). | ++-------------+----------------------------------------+ +| yday | Day of year (0 - 365; January 1 = 0). | ++-------------+----------------------------------------+ + +if `time` is omitted the current time is used. + +if `format` can be 'l' local time or 'u' UTC time, if omitted is defaulted as 'l'(local time). + +.. js:function:: getenv(varaname) + + Returns a string containing the value of the environment variable `varname` + +.. js:function:: remove(path) + + deletes the file specified by `path` + +.. js:function:: rename(oldname, newname) + + renames the file or directory specified by `oldname` to the name given by `newname` + +.. js:function:: system(cmd) + + xecutes the string `cmd` through the os command interpreter. + +.. js:function:: time() + + returns the number of seconds elapsed since midnight 00:00:00, January 1, 1970. + + the result of this function can be formatted through the function `date()` + +-------------- +C API +-------------- + +.. _sqstd_register_systemlib: + +.. c:function:: SQRESULT sqstd_register_systemlib(HSQUIRRELVM v) + + :param HSQUIRRELVM v: the target VM + :returns: an SQRESULT + :remarks: The function aspects a table on top of the stack where to register the global library functions. + + initialize and register the system library in the given VM. diff --git a/src/vscript/squirrel/etc/minimal.c b/src/vscript/squirrel/etc/minimal.c new file mode 100644 index 00000000..0695768b --- /dev/null +++ b/src/vscript/squirrel/etc/minimal.c @@ -0,0 +1,78 @@ +#include +#include + +#include +#include +#include + +#ifdef _MSC_VER +#pragma comment (lib ,"squirrel.lib") +#pragma comment (lib ,"sqstdlib.lib") +#endif + +#ifdef SQUNICODE + +#define scvprintf vfwprintf +#else + +#define scvprintf vfprintf +#endif + +void printfunc(HSQUIRRELVM v,const SQChar *s,...) +{ + va_list vl; + va_start(vl, s); + scvprintf(stdout, s, vl); + va_end(vl); +} + +void errorfunc(HSQUIRRELVM v,const SQChar *s,...) +{ + va_list vl; + va_start(vl, s); + scvprintf(stderr, s, vl); + va_end(vl); +} + +void call_foo(HSQUIRRELVM v, int n,float f,const SQChar *s) +{ + SQInteger top = sq_gettop(v); //saves the stack size before the call + sq_pushroottable(v); //pushes the global table + sq_pushstring(v,_SC("foo"),-1); + if(SQ_SUCCEEDED(sq_get(v,-2))) { //gets the field 'foo' from the global table + sq_pushroottable(v); //push the 'this' (in this case is the global table) + sq_pushinteger(v,n); + sq_pushfloat(v,f); + sq_pushstring(v,s,-1); + sq_call(v,4,SQFalse,SQTrue); //calls the function + } + sq_settop(v,top); //restores the original stack size +} + +int main(int argc, char* argv[]) +{ + HSQUIRRELVM v; + v = sq_open(1024); // creates a VM with initial stack size 1024 + + //REGISTRATION OF STDLIB + //sq_pushroottable(v); //push the root table where the std function will be registered + //sqstd_register_iolib(v); //registers a library + // ... call here other stdlibs string,math etc... + //sq_pop(v,1); //pops the root table + //END REGISTRATION OF STDLIB + + sqstd_seterrorhandlers(v); //registers the default error handlers + + sq_setprintfunc(v, printfunc,errorfunc); //sets the print function + + sq_pushroottable(v); //push the root table(were the globals of the script will be stored) + if(SQ_SUCCEEDED(sqstd_dofile(v, _SC("test.nut"), SQFalse, SQTrue))) // also prints syntax errors if any + { + call_foo(v,1,2.5,_SC("teststring")); + } + + sq_pop(v,1); //pops the root table + sq_close(v); + + return 0; +} diff --git a/src/vscript/squirrel/etc/test.nut b/src/vscript/squirrel/etc/test.nut new file mode 100644 index 00000000..125df32c --- /dev/null +++ b/src/vscript/squirrel/etc/test.nut @@ -0,0 +1,4 @@ +function foo(i, f, s) +{ + print("Called foo(), i="+i+", f="+f+", s='"+s+"'\n"); +} diff --git a/src/vscript/squirrel/include/sqconfig.h b/src/vscript/squirrel/include/sqconfig.h new file mode 100644 index 00000000..58bc9793 --- /dev/null +++ b/src/vscript/squirrel/include/sqconfig.h @@ -0,0 +1,146 @@ + +#ifdef _SQ64 + +#ifdef _MSC_VER +typedef __int64 SQInteger; +typedef unsigned __int64 SQUnsignedInteger; +typedef unsigned __int64 SQHash; /*should be the same size of a pointer*/ +#else +typedef long long SQInteger; +typedef unsigned long long SQUnsignedInteger; +typedef unsigned long long SQHash; /*should be the same size of a pointer*/ +#endif +typedef int SQInt32; +typedef unsigned int SQUnsignedInteger32; +#else +typedef int SQInteger; +typedef int SQInt32; /*must be 32 bits(also on 64bits processors)*/ +typedef unsigned int SQUnsignedInteger32; /*must be 32 bits(also on 64bits processors)*/ +typedef unsigned int SQUnsignedInteger; +typedef unsigned int SQHash; /*should be the same size of a pointer*/ +#endif + + +#ifdef SQUSEDOUBLE +typedef double SQFloat; +#else +typedef float SQFloat; +#endif + +#if defined(SQUSEDOUBLE) && !defined(_SQ64) || !defined(SQUSEDOUBLE) && defined(_SQ64) +#ifdef _MSC_VER +typedef __int64 SQRawObjectVal; //must be 64bits +#else +typedef long long SQRawObjectVal; //must be 64bits +#endif +#define SQ_OBJECT_RAWINIT() { _unVal.raw = 0; } +#else +typedef SQUnsignedInteger SQRawObjectVal; //is 32 bits on 32 bits builds and 64 bits otherwise +#define SQ_OBJECT_RAWINIT() +#endif + +#ifndef SQ_ALIGNMENT // SQ_ALIGNMENT shall be less than or equal to SQ_MALLOC alignments, and its value shall be power of 2. +#if defined(SQUSEDOUBLE) || defined(_SQ64) +#define SQ_ALIGNMENT 8 +#else +#define SQ_ALIGNMENT 4 +#endif +#endif + +typedef void* SQUserPointer; +typedef SQUnsignedInteger SQBool; +typedef SQInteger SQRESULT; + +#ifdef SQUNICODE +#include +#include + + +typedef wchar_t SQChar; + + +#define scstrcmp wcscmp +#ifdef _WIN32 +#define scsprintf _snwprintf +#else +#define scsprintf swprintf +#endif +#define scstrlen wcslen +#define scstrtod wcstod +#ifdef _SQ64 +#define scstrtol wcstoll +#else +#define scstrtol wcstol +#endif +#define scstrtoul wcstoul +#define scvsprintf vswprintf +#define scstrstr wcsstr +#define scprintf wprintf + +#ifdef _WIN32 +#define WCHAR_SIZE 2 +#define WCHAR_SHIFT_MUL 1 +#define MAX_CHAR 0xFFFF +#else +#define WCHAR_SIZE 4 +#define WCHAR_SHIFT_MUL 2 +#define MAX_CHAR 0xFFFFFFFF +#endif + +#define _SC(a) L##a + + +#define scisspace iswspace +#define scisdigit iswdigit +#define scisprint iswprint +#define scisxdigit iswxdigit +#define scisalpha iswalpha +#define sciscntrl iswcntrl +#define scisalnum iswalnum + + +#define sq_rsl(l) ((l)<=0) + +#ifdef __GNUC__ +# define SQ_UNUSED_ARG(x) x __attribute__((__unused__)) +#else +# define SQ_UNUSED_ARG(x) x +#endif + +#ifdef __cplusplus +} /*extern "C"*/ +#endif + +#endif /*_SQUIRREL_H_*/ diff --git a/src/vscript/squirrel/samples/ackermann.nut b/src/vscript/squirrel/samples/ackermann.nut new file mode 100644 index 00000000..8b18ec21 --- /dev/null +++ b/src/vscript/squirrel/samples/ackermann.nut @@ -0,0 +1,23 @@ +/* +* +* Original Javascript version by David Hedbor(http://www.bagley.org/~doug/shootout/) +* +*/ + +function Ack(M, N) { + if (M == 0) return( N + 1 ); + if (N == 0) return( Ack(M - 1, 1) ); + return( Ack(M - 1, Ack(M, (N - 1))) ); +} + +local n; + +if(vargv.len()!=0) { + n = vargv[0].tointeger(); + if(n < 1) n = 1; +} else { + n = 1; +} +print("n="+n+"\n"); +print("Ack(3,"+ n+ "):"+ Ack(3, n)); + diff --git a/src/vscript/squirrel/samples/array.nut b/src/vscript/squirrel/samples/array.nut new file mode 100644 index 00000000..0102d62f --- /dev/null +++ b/src/vscript/squirrel/samples/array.nut @@ -0,0 +1,29 @@ +/* +* +* Original Javascript version by David Hedbor(http://www.bagley.org/~doug/shootout/) +* +*/ +local n, i, k; + +if(vargv.len()!=0) { + n = vargv[0].tointeger(); + if(n < 1) n = 1; +} else { + n = 1; +} + +local x = []; x.resize(n); +local y = []; y.resize(n); + +for (i = 0; i < n; i+=1) { + x[i] = i + 1; + y[i] = 0; +} + +for (k = 0 ; k < n; k+=1) { + for (i = n-1; i >= 0; i-=1) { + y[i] = y[i]+ x[i]; + } +} +print(y[0].tostring()+" "+y[n-1]); + diff --git a/src/vscript/squirrel/samples/class.nut b/src/vscript/squirrel/samples/class.nut new file mode 100644 index 00000000..2d924602 --- /dev/null +++ b/src/vscript/squirrel/samples/class.nut @@ -0,0 +1,49 @@ +////////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////////// +class BaseVector { + constructor(...) + { + if(vargv.len() >= 3) { + x = vargv[0]; + y = vargv[1]; + z = vargv[2]; + } + } + + + x = 0; + y = 0; + z = 0; +} + +class Vector3 extends BaseVector { + function _add(other) + { + if(other instanceof this.getclass()) + return ::Vector3(x+other.x,y+other.y,z+other.z); + else + throw "wrong parameter"; + } + function Print() + { + ::print(x+","+y+","+z+"\n"); + } +} + +local v0 = Vector3(1,2,3) +local v1 = Vector3(11,12,13) +local v2 = v0 + v1; +v2.Print(); + +FakeNamespace <- { + Utils = {} +} + +class FakeNamespace.Utils.SuperClass { + constructor() + { + ::print("FakeNamespace.Utils.SuperClass") + } +} + +local testy = FakeNamespace.Utils.SuperClass(); diff --git a/src/vscript/squirrel/samples/classattributes.nut b/src/vscript/squirrel/samples/classattributes.nut new file mode 100644 index 00000000..4d2d078d --- /dev/null +++ b/src/vscript/squirrel/samples/classattributes.nut @@ -0,0 +1,35 @@ +class Foo { + //constructor + constructor(a) + { + testy = ["stuff",1,2,3]; + } + //attributes of PrintTesty + + function PrintTesty() + { + foreach(i,val in testy) + { + ::print("idx = "+i+" = "+val+" \n"); + } + } + //attributes of testy + + testy = null; + +} + +foreach(member,val in Foo) +{ + ::print(member+"\n"); + local attr; + if((attr = Foo.getattributes(member)) != null) { + foreach(i,v in attr) + { + ::print("\t"+i+" = "+(typeof v)+"\n"); + } + } + else { + ::print("\t\n") + } +} diff --git a/src/vscript/squirrel/samples/coroutines.nut b/src/vscript/squirrel/samples/coroutines.nut new file mode 100644 index 00000000..0cc19920 --- /dev/null +++ b/src/vscript/squirrel/samples/coroutines.nut @@ -0,0 +1,25 @@ +function coroutine_test(a,b) +{ + ::print(a+" "+b+"\n"); + local ret = ::suspend("suspend 1"); + ::print("the coroutine says "+ret+"\n"); + ret = ::suspend("suspend 2"); + ::print("the coroutine says "+ret+"\n"); + ret = ::suspend("suspend 3"); + ::print("the coroutine says "+ret+"\n"); + return "I'm done" +} + +local coro = ::newthread(coroutine_test); + +local susparam = coro.call("test","coroutine"); //starts the coroutine + +local i = 1; +do +{ + ::print("suspend passed ["+susparam+"]\n") + susparam = coro.wakeup("ciao "+i); + ++i; +}while(coro.getstatus()=="suspended") + +::print("return passed ["+susparam+"]\n") diff --git a/src/vscript/squirrel/samples/delegation.nut b/src/vscript/squirrel/samples/delegation.nut new file mode 100644 index 00000000..9f208dc4 --- /dev/null +++ b/src/vscript/squirrel/samples/delegation.nut @@ -0,0 +1,54 @@ + +PEntity <- { + name="noname" + pos={x=0,y=0,z=0} + type="entity" + //methamethod + _typeof=function() + { + return type; + } +} + +function PEntity::PrintPos() +{ + ::print("x="+pos.x+" y="+pos.y+" z="+pos.z+"\n"); +} + +function PEntity::new(name,pos) +{ + local newentity=clone ::PEntity; + if(name) + newentity.name=name; + if(pos) + newentity.pos=pos; + return newentity; +} + +PPlayer <- { + model="warrior.mdl" + weapon="fist" + health=100 + armor=0 + //overrides the parent type + type="player" +} + +function PPlayer::new(name,pos) +{ + local p = clone ::PPlayer; + local newplayer = ::PEntity.new(name,pos); + newplayer.setdelegate(p); + return newplayer; +} + +local player=PPlayer.new("godzilla",{x=10,y=20,z=30}); + +::print("PLAYER NAME"+player.name+"\n"); +::print("ENTITY TYPE"+typeof player+"\n"); + +player.PrintPos(); + +player.pos.x=123; + +player.PrintPos(); diff --git a/src/vscript/squirrel/samples/fibonacci.nut b/src/vscript/squirrel/samples/fibonacci.nut new file mode 100644 index 00000000..c722d1a1 --- /dev/null +++ b/src/vscript/squirrel/samples/fibonacci.nut @@ -0,0 +1,15 @@ +/* +* +* Original Javascript version by David Hedbor(http://www.bagley.org/~doug/shootout/) +* +*/ + +function fib(n) +{ + if (n < 2) return 1 + return fib(n-2) + fib(n-1) +} + +local n = vargv.len()!=0?vargv[0].tointeger():1 + +print(fib(n)+"\n") diff --git a/src/vscript/squirrel/samples/flow.nut b/src/vscript/squirrel/samples/flow.nut new file mode 100644 index 00000000..635bc89f --- /dev/null +++ b/src/vscript/squirrel/samples/flow.nut @@ -0,0 +1,33 @@ +function min(x,y) + return xy?x:y; + +if(min(100,200)>max(50,20)) + print("I'm useless statement just to show up the if/else\n"); +else + print("squirrel!!\n"); + +print("\n") + +function typy(obj) +{ + switch(typeof obj) + { + case "integer": + case "float": + return "is a number"; + case "table": + case "array": + return "is a container"; + default: + return "is other stuff" + } +} + +local a=1,b={},c=function(a,b){return a+b;} + +print("a "+typy(a)+"\n"); +print("b "+typy(b)+"\n"); +print("c "+typy(c)+"\n"); diff --git a/src/vscript/squirrel/samples/generators.nut b/src/vscript/squirrel/samples/generators.nut new file mode 100644 index 00000000..eb5eef6b --- /dev/null +++ b/src/vscript/squirrel/samples/generators.nut @@ -0,0 +1,42 @@ +/* +*Random number function from The Great Computer Language shootout +*converted to a generator func +*/ + +function gen_random(max) { + local last=42 + local IM = 139968; + local IA = 3877; + local IC = 29573; + for(;;){ //loops forever + yield (max * (last = (last * IA + IC) % IM) / IM); + } +} + +local randtor=gen_random(100); + +print("RAND NUMBERS \n") + +for(local i=0;i<10;i+=1) + print(">"+resume randtor+"\n"); + +print("FIBONACCI \n") +function fiboz(n) +{ + local prev=0; + local curr=1; + yield 1; + + for(local i=0;i"+val+"\n"); +} diff --git a/src/vscript/squirrel/samples/hello.nut b/src/vscript/squirrel/samples/hello.nut new file mode 100644 index 00000000..f301245e --- /dev/null +++ b/src/vscript/squirrel/samples/hello.nut @@ -0,0 +1 @@ +print("Hello World!") diff --git a/src/vscript/squirrel/samples/list.nut b/src/vscript/squirrel/samples/list.nut new file mode 100644 index 00000000..e7cc2218 --- /dev/null +++ b/src/vscript/squirrel/samples/list.nut @@ -0,0 +1,40 @@ +/*translation of the list test from The Great Computer Language Shootout +*/ + +function compare_arr(a1,a2) +{ + foreach(i,val in a1) + if(val!=a2[i])return null; + return 1; +} + +function test() +{ + local size=10000 + local l1=[]; l1.resize(size); + for(local i=0;i0) + l3.append(l2.pop()); + while(l3.len()>0) + l2.append(l3.pop()); + l1.reverse(); + + if(compare_arr(l1,l2)) + return l1.len(); + return null; +} + +local n = vargv.len()!=0?vargv[0].tointeger():1 +for(local i=0;i\n"); +else + print("\n"); diff --git a/src/vscript/squirrel/samples/methcall.nut b/src/vscript/squirrel/samples/methcall.nut new file mode 100644 index 00000000..73bb2e84 --- /dev/null +++ b/src/vscript/squirrel/samples/methcall.nut @@ -0,0 +1,68 @@ +/*translation of the methcall test from The Great Computer Language Shootout +*/ + +class Toggle { + bool=null +} + +function Toggle::constructor(startstate) { + bool = startstate +} + +function Toggle::value() { + return bool; +} + +function Toggle::activate() { + bool = !bool; + return this; +} + +class NthToggle extends Toggle { + count_max=null + count=0 +} + +function NthToggle::constructor(start_state,max_counter) +{ + base.constructor(start_state); + count_max = max_counter +} + +function NthToggle::activate () +{ + ++count; + if (count >= count_max ) { + base.activate(); + count = 0; + } + return this; +} + + +function main() { + local n = vargv.len()!=0?vargv[0].tointeger():1 + + + + local val = 1; + local toggle = Toggle(val); + local i = n; + while(i--) { + val = toggle.activate().value(); + + } + print(toggle.value() ? "true\n" : "false\n"); + + val = 1; + local ntoggle = NthToggle(val, 3); + i = n; + while(i--) { + val = ntoggle.activate().value(); + } + print(ntoggle.value() ? "true\n" : "false\n"); + +} +local start=clock(); +main(); +print("TIME="+(clock()-start)+"\n"); diff --git a/src/vscript/squirrel/samples/regex.nut b/src/vscript/squirrel/samples/regex.nut new file mode 100644 index 00000000..fcd8e59e --- /dev/null +++ b/src/vscript/squirrel/samples/regex.nut @@ -0,0 +1,10 @@ +local ex = regexp("[a-zA-Z]+"); +local string = "123 Test; strlen(str);"; +local res = ex.search(string); +print(string.slice(res.begin,res.end)); //prints "Test" +print("\n"); +ex = regexp(@"\m()"); +string = "123 Test; doSomething(str, getTemp(), (a+(b/c)));"; +res = ex.search(string); +print(string.slice(res.begin,res.end)); //prints "(...)" +print("\n"); diff --git a/src/vscript/squirrel/samples/tailstate.nut b/src/vscript/squirrel/samples/tailstate.nut new file mode 100644 index 00000000..7ea06c69 --- /dev/null +++ b/src/vscript/squirrel/samples/tailstate.nut @@ -0,0 +1,24 @@ +function state1() +{ + ::suspend("state1"); + return state2(); +} + +function state2() +{ + ::suspend("state2"); + return state3(); +} + +function state3() +{ + ::suspend("state3"); + return state1(); +} + +local statethread = ::newthread(state1) + +::print(statethread.call()+"\n"); + +for(local i = 0; i < 10000; i++) + ::print(statethread.wakeup()+"\n"); diff --git a/src/vscript/squirrel/sq/CMakeLists.txt b/src/vscript/squirrel/sq/CMakeLists.txt new file mode 100644 index 00000000..bdea07d0 --- /dev/null +++ b/src/vscript/squirrel/sq/CMakeLists.txt @@ -0,0 +1,38 @@ +set(CMAKE_C_STANDARD 99) +if(NOT DISABLE_DYNAMIC) + add_executable(sq sq.c) + add_executable(squirrel::interpreter ALIAS sq) + set_target_properties(sq PROPERTIES LINKER_LANGUAGE C EXPORT_NAME interpreter) + target_link_libraries(sq squirrel sqstdlib) + if(NOT SQ_DISABLE_INSTALLER) + install(TARGETS sq EXPORT squirrel RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT Runtime) + endif() + target_include_directories(sq PUBLIC + "$" + "$" + ) +endif() + +if(NOT DISABLE_STATIC) + add_executable(sq_static sq.c) + add_executable(squirrel::interpreter_static ALIAS sq) + set_target_properties(sq_static PROPERTIES LINKER_LANGUAGE C EXPORT_NAME interpreter_static) + target_link_libraries(sq_static squirrel_static sqstdlib_static) + if(NOT SQ_DISABLE_INSTALLER) + install(TARGETS sq_static EXPORT squirrel RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT Runtime) + endif() + target_include_directories(sq_static PUBLIC + "$" + "$" + ) +endif() + +if(LONG_OUTPUT_NAMES) + if(NOT DISABLE_DYNAMIC) + set_target_properties(sq PROPERTIES OUTPUT_NAME squirrel3) + endif() + + if(NOT DISABLE_STATIC) + set_target_properties(sq_static PROPERTIES OUTPUT_NAME squirrel3_static) + endif() +endif() diff --git a/src/vscript/squirrel/sq/Makefile b/src/vscript/squirrel/sq/Makefile new file mode 100644 index 00000000..948fd1ea --- /dev/null +++ b/src/vscript/squirrel/sq/Makefile @@ -0,0 +1,21 @@ +SQUIRREL= .. + + +OUT= $(SQUIRREL)/bin/sq +INCZ= -I$(SQUIRREL)/include -I. -I$(SQUIRREL)/sqlibs +LIBZ= -L$(SQUIRREL)/lib +LIB= -lsquirrel -lsqstdlib + +OBJS= sq.o + +SRCS= sq.c + + +sq32: + g++ -O2 -fno-exceptions -fno-rtti -o $(OUT) $(SRCS) $(INCZ) $(LIBZ) $(LIB) + +sqprof: + g++ -O2 -pg -fno-exceptions -fno-rtti -pie -gstabs -g3 -o $(OUT) $(SRCS) $(INCZ) $(LIBZ) $(LIB) + +sq64: + g++ -O2 -m64 -fno-exceptions -fno-rtti -D_SQ64 -o $(OUT) $(SRCS) $(INCZ) $(LIBZ) $(LIB) diff --git a/src/vscript/squirrel/sq/sq.c b/src/vscript/squirrel/sq/sq.c new file mode 100644 index 00000000..ee5eabbb --- /dev/null +++ b/src/vscript/squirrel/sq/sq.c @@ -0,0 +1,349 @@ +/* see copyright notice in squirrel.h */ + +#include +#include +#include +#include + +#if defined(_MSC_VER) && defined(_DEBUG) +#include +#include +#endif +#include +#include +#include +#include +#include +#include +#include + +#ifdef SQUNICODE +#define scfprintf fwprintf +#define scvprintf vfwprintf +#else +#define scfprintf fprintf +#define scvprintf vfprintf +#endif + + +void PrintVersionInfos(); + +#if defined(_MSC_VER) && defined(_DEBUG) +int MemAllocHook( int allocType, void *userData, size_t size, int blockType, + long requestNumber, const unsigned char *filename, int lineNumber) +{ + //if(requestNumber==769)_asm int 3; + return 1; +} +#endif + + +SQInteger quit(HSQUIRRELVM v) +{ + int *done; + sq_getuserpointer(v,-1,(SQUserPointer*)&done); + *done=1; + return 0; +} + +void printfunc(HSQUIRRELVM SQ_UNUSED_ARG(v),const SQChar *s,...) +{ + va_list vl; + va_start(vl, s); + scvprintf(stdout, s, vl); + va_end(vl); +} + +void errorfunc(HSQUIRRELVM SQ_UNUSED_ARG(v),const SQChar *s,...) +{ + va_list vl; + va_start(vl, s); + scvprintf(stderr, s, vl); + va_end(vl); +} + +void PrintVersionInfos() +{ + scfprintf(stdout,_SC("%s %s (%d bits)\n"),SQUIRREL_VERSION,SQUIRREL_COPYRIGHT,((int)(sizeof(SQInteger)*8))); +} + +void PrintUsage() +{ + scfprintf(stderr,_SC("usage: sq .\n") + _SC("Available options are:\n") + _SC(" -c compiles the file to bytecode(default output 'out.cnut')\n") + _SC(" -o specifies output file for the -c option\n") + _SC(" -c compiles only\n") + _SC(" -d generates debug infos\n") + _SC(" -v displays version infos\n") + _SC(" -h prints help\n")); +} + +#define _INTERACTIVE 0 +#define _DONE 2 +#define _ERROR 3 +//<> this func is a mess +int getargs(HSQUIRRELVM v,int argc, char* argv[],SQInteger *retval) +{ + int i; + int compiles_only = 0; +#ifdef SQUNICODE + static SQChar temp[500]; +#endif + char * output = NULL; + *retval = 0; + if(argc>1) + { + int arg=1,exitloop=0; + + while(arg < argc && !exitloop) + { + + if(argv[arg][0]=='-') + { + switch(argv[arg][1]) + { + case 'd': //DEBUG(debug infos) + sq_enabledebuginfo(v,1); + break; + case 'c': + compiles_only = 1; + break; + case 'o': + if(arg < argc) { + arg++; + output = argv[arg]; + } + break; + case 'v': + PrintVersionInfos(); + return _DONE; + + case 'h': + PrintVersionInfos(); + PrintUsage(); + return _DONE; + default: + PrintVersionInfos(); + scprintf(_SC("unknown prameter '-%c'\n"),argv[arg][1]); + PrintUsage(); + *retval = -1; + return _ERROR; + } + }else break; + arg++; + } + + // src file + + if(arg")); + for(;;) { + int c; + if(done)return; + c = getchar(); + if (c == _SC('\n')) { + if (i>0 && buffer[i-1] == _SC('\\')) + { + buffer[i-1] = _SC('\n'); + } + else if(blocks==0)break; + buffer[i++] = _SC('\n'); + } + else if (c==_SC('}')) {blocks--; buffer[i++] = (SQChar)c;} + else if(c==_SC('{') && !string){ + blocks++; + buffer[i++] = (SQChar)c; + } + else if(c==_SC('"') || c==_SC('\'')){ + string=!string; + buffer[i++] = (SQChar)c; + } + else if (i >= MAXINPUT-1) { + scfprintf(stderr, _SC("sq : input line too long\n")); + break; + } + else{ + buffer[i++] = (SQChar)c; + } + } + buffer[i] = _SC('\0'); + + if(buffer[0]==_SC('=')){ + scsprintf(sq_getscratchpad(v,MAXINPUT),(size_t)MAXINPUT,_SC("return (%s)"),&buffer[1]); + memcpy(buffer,sq_getscratchpad(v,-1),(scstrlen(sq_getscratchpad(v,-1))+1)*sizeof(SQChar)); + retval=1; + } + i=scstrlen(buffer); + if(i>0){ + SQInteger oldtop=sq_gettop(v); + if(SQ_SUCCEEDED(sq_compilebuffer(v,buffer,i,_SC("interactive console"),SQTrue))){ + sq_pushroottable(v); + if(SQ_SUCCEEDED(sq_call(v,1,retval,SQTrue)) && retval){ + scprintf(_SC("\n")); + sq_pushroottable(v); + sq_pushstring(v,_SC("print"),-1); + sq_get(v,-2); + sq_pushroottable(v); + sq_push(v,-4); + sq_call(v,2,SQFalse,SQTrue); + retval=0; + scprintf(_SC("\n")); + } + } + + sq_settop(v,oldtop); + } + } +} + +int main(int argc, char* argv[]) +{ + HSQUIRRELVM v; + SQInteger retval = 0; +#if defined(_MSC_VER) && defined(_DEBUG) + _CrtSetAllocHook(MemAllocHook); +#endif + + v=sq_open(1024); + sq_setprintfunc(v,printfunc,errorfunc); + + sq_pushroottable(v); + + sqstd_register_bloblib(v); + sqstd_register_iolib(v); + sqstd_register_systemlib(v); + sqstd_register_mathlib(v); + sqstd_register_stringlib(v); + + //aux library + //sets error handlers + sqstd_seterrorhandlers(v); + + //gets arguments + switch(getargs(v,argc,argv,&retval)) + { + case _INTERACTIVE: + Interactive(v); + break; + case _DONE: + case _ERROR: + default: + break; + } + + sq_close(v); + +#if defined(_MSC_VER) && defined(_DEBUG) + _getch(); + _CrtMemDumpAllObjectsSince( NULL ); +#endif + return retval; +} + diff --git a/src/vscript/squirrel/sq/sq.dsp b/src/vscript/squirrel/sq/sq.dsp new file mode 100644 index 00000000..a632d12b --- /dev/null +++ b/src/vscript/squirrel/sq/sq.dsp @@ -0,0 +1,101 @@ +# Microsoft Developer Studio Project File - Name="sq" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Console Application" 0x0103 + +CFG=sq - Win32 Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "sq.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "sq.mak" CFG="sq - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "sq - Win32 Release" (based on "Win32 (x86) Console Application") +!MESSAGE "sq - Win32 Debug" (based on "Win32 (x86) Console Application") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_LocalPath ".." +CPP=cl.exe +RSC=rc.exe + +!IF "$(CFG)" == "sq - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release" +# PROP Intermediate_Dir "Release" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c +# ADD CPP /nologo /W3 /GX /O2 /I "..\include" /I "..\sqstdlib" /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c +# ADD BASE RSC /l 0x410 /d "NDEBUG" +# ADD RSC /l 0x410 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386 +# ADD LINK32 squirrel.lib sqstdlib.lib /nologo /subsystem:console /machine:I386 /out:"../bin/sq.exe" /libpath:"../lib" + +!ELSEIF "$(CFG)" == "sq - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug" +# PROP Intermediate_Dir "Debug" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /GZ /c +# ADD CPP /nologo /W3 /Gm /GX /ZI /Od /I "..\include" /I "..\sqstdlib" /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /GZ /c +# ADD BASE RSC /l 0x410 /d "_DEBUG" +# ADD RSC /l 0x410 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept +# ADD LINK32 squirrel.lib sqstdlib.lib /nologo /subsystem:console /debug /machine:I386 /out:"../bin/sq.exe" /pdbtype:sept /libpath:"../lib" + +!ENDIF + +# Begin Target + +# Name "sq - Win32 Release" +# Name "sq - Win32 Debug" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" +# Begin Source File + +SOURCE=.\sq.c +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl" +# End Group +# Begin Group "Resource Files" + +# PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe" +# End Group +# End Target +# End Project diff --git a/src/vscript/squirrel/sqstdlib/CMakeLists.txt b/src/vscript/squirrel/sqstdlib/CMakeLists.txt new file mode 100644 index 00000000..34fba82f --- /dev/null +++ b/src/vscript/squirrel/sqstdlib/CMakeLists.txt @@ -0,0 +1,52 @@ +set(SQSTDLIB_SRC sqstdaux.cpp + sqstdblob.cpp + sqstdio.cpp + sqstdmath.cpp + sqstdrex.cpp + sqstdstream.cpp + sqstdstring.cpp + sqstdsystem.cpp) + +if(NOT DISABLE_DYNAMIC) + add_library(sqstdlib SHARED ${SQSTDLIB_SRC}) + add_library(squirrel::sqstdlib ALIAS sqstdlib) + set_property(TARGET sqstdlib PROPERTY EXPORT_NAME sqstdlib) + target_link_libraries(sqstdlib squirrel) + if(NOT SQ_DISABLE_INSTALLER) + install(TARGETS sqstdlib EXPORT squirrel + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT Libraries + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT Libraries NAMELINK_SKIP + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT Libraries + ) + install(TARGETS sqstdlib EXPORT squirrel + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT Development NAMELINK_ONLY + ) + endif() + target_include_directories(sqstdlib PUBLIC + "$" + "$" + ) +endif() + +if(NOT DISABLE_STATIC) + add_library(sqstdlib_static STATIC ${SQSTDLIB_SRC}) + add_library(squirrel::sqstdlib_static ALIAS sqstdlib_static) + set_property(TARGET sqstdlib_static PROPERTY EXPORT_NAME sqstdlib_static) + if(NOT SQ_DISABLE_INSTALLER) + install(TARGETS sqstdlib_static EXPORT squirrel ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT Development) + endif() + target_include_directories(sqstdlib_static PUBLIC + "$" + "$" + ) +endif() + +if(LONG_OUTPUT_NAMES) + if(NOT DISABLE_DYNAMIC) + set_target_properties(sqstdlib PROPERTIES OUTPUT_NAME sqstdlib3) + endif() + + if(NOT DISABLE_STATIC) + set_target_properties(sqstdlib_static PROPERTIES OUTPUT_NAME sqstdlib3_static) + endif() +endif() diff --git a/src/vscript/squirrel/sqstdlib/Makefile b/src/vscript/squirrel/sqstdlib/Makefile new file mode 100644 index 00000000..6ed1c12b --- /dev/null +++ b/src/vscript/squirrel/sqstdlib/Makefile @@ -0,0 +1,44 @@ +SQUIRREL= .. + + +CC?= gcc +OUT?= $(SQUIRREL)/lib/libsqstdlib.a +INCZ?= -I$(SQUIRREL)/include -I. -Iinclude +DEFS= $(CC_EXTRA_FLAGS) +LIB= + +OBJS= \ + sqstdblob.o \ + sqstdio.o \ + sqstdstream.o \ + sqstdmath.o \ + sqstdsystem.o \ + sqstdstring.o \ + sqstdaux.o \ + sqstdrex.o + +SRCS= \ + sqstdblob.cpp \ + sqstdio.cpp \ + sqstdstream.cpp \ + sqstdmath.cpp \ + sqstdsystem.cpp \ + sqstdstring.cpp \ + sqstdaux.cpp \ + sqstdrex.cpp + + +sq32: + $(CC) -O2 -fno-exceptions -fno-rtti -Wall -fno-strict-aliasing -c $(SRCS) $(INCZ) $(DEFS) + ar rc $(OUT) *.o + rm *.o + +sqprof: + $(CC) -O2 -pg -fno-exceptions -fno-rtti -pie -gstabs -g3 -Wall -fno-strict-aliasing -c $(SRCS) $(INCZ) $(DEFS) + ar rc $(OUT) *.o + rm *.o + +sq64: + $(CC) -O2 -m64 -fno-exceptions -D_SQ64 -fno-rtti -Wall -fno-strict-aliasing -c $(SRCS) $(INCZ) $(DEFS) + ar rc $(OUT) *.o + rm *.o diff --git a/src/vscript/squirrel/sqstdlib/sqstdaux.cpp b/src/vscript/squirrel/sqstdlib/sqstdaux.cpp new file mode 100644 index 00000000..75c16533 --- /dev/null +++ b/src/vscript/squirrel/sqstdlib/sqstdaux.cpp @@ -0,0 +1,151 @@ +/* see copyright notice in squirrel.h */ +#include +#include +#include +#include +#include + +void sqstd_printcallstack(HSQUIRRELVM v) +{ + SQPRINTFUNCTION pf = sq_geterrorfunc(v); + if(pf) { + SQStackInfos si; + SQInteger i; + SQFloat f; + const SQChar *s; + SQInteger level=1; //1 is to skip this function that is level 0 + const SQChar *name=0; + SQInteger seq=0; + pf(v,_SC("\nCALLSTACK\n")); + while(SQ_SUCCEEDED(sq_stackinfos(v,level,&si))) + { + const SQChar *fn=_SC("unknown"); + const SQChar *src=_SC("unknown"); + if(si.funcname)fn=si.funcname; + if(si.source)src=si.source; + pf(v,_SC("*FUNCTION [%s()] %s line [%d]\n"),fn,src,si.line); + level++; + } + level=0; + pf(v,_SC("\nLOCALS\n")); + + for(level=0;level<10;level++){ + seq=0; + while((name = sq_getlocal(v,level,seq))) + { + seq++; + switch(sq_gettype(v,-1)) + { + case OT_NULL: + pf(v,_SC("[%s] NULL\n"),name); + break; + case OT_INTEGER: + sq_getinteger(v,-1,&i); + pf(v,_SC("[%s] %d\n"),name,i); + break; + case OT_FLOAT: + sq_getfloat(v,-1,&f); + pf(v,_SC("[%s] %.14g\n"),name,f); + break; + case OT_USERPOINTER: + pf(v,_SC("[%s] USERPOINTER\n"),name); + break; + case OT_STRING: + sq_getstring(v,-1,&s); + pf(v,_SC("[%s] \"%s\"\n"),name,s); + break; + case OT_TABLE: + pf(v,_SC("[%s] TABLE\n"),name); + break; + case OT_ARRAY: + pf(v,_SC("[%s] ARRAY\n"),name); + break; + case OT_CLOSURE: + pf(v,_SC("[%s] CLOSURE\n"),name); + break; + case OT_NATIVECLOSURE: + pf(v,_SC("[%s] NATIVECLOSURE\n"),name); + break; + case OT_GENERATOR: + pf(v,_SC("[%s] GENERATOR\n"),name); + break; + case OT_USERDATA: + pf(v,_SC("[%s] USERDATA\n"),name); + break; + case OT_THREAD: + pf(v,_SC("[%s] THREAD\n"),name); + break; + case OT_CLASS: + pf(v,_SC("[%s] CLASS\n"),name); + break; + case OT_INSTANCE: + pf(v,_SC("[%s] INSTANCE\n"),name); + break; + case OT_WEAKREF: + pf(v,_SC("[%s] WEAKREF\n"),name); + break; + case OT_BOOL:{ + SQBool bval; + sq_getbool(v,-1,&bval); + pf(v,_SC("[%s] %s\n"),name,bval == SQTrue ? _SC("true"):_SC("false")); + } + break; + default: assert(0); break; + } + sq_pop(v,1); + } + } + } +} + +static SQInteger _sqstd_aux_printerror(HSQUIRRELVM v) +{ + SQPRINTFUNCTION pf = sq_geterrorfunc(v); + if(pf) { + const SQChar *sErr = 0; + if(sq_gettop(v)>=1) { + if(SQ_SUCCEEDED(sq_getstring(v,2,&sErr))) { + pf(v,_SC("\nAN ERROR HAS OCCURRED [%s]\n"),sErr); + } + else{ + pf(v,_SC("\nAN ERROR HAS OCCURRED [unknown]\n")); + } + sqstd_printcallstack(v); + } + } + return 0; +} + +void _sqstd_compiler_error(HSQUIRRELVM v,const SQChar *sErr,const SQChar *sSource,SQInteger line,SQInteger column) +{ + SQPRINTFUNCTION pf = sq_geterrorfunc(v); + if(pf) { + pf(v,_SC("%s line = (%d) column = (%d) : error %s\n"),sSource,line,column,sErr); + } +} + +void sqstd_seterrorhandlers(HSQUIRRELVM v) +{ + sq_setcompilererrorhandler(v,_sqstd_compiler_error); + sq_newclosure(v,_sqstd_aux_printerror,0); + sq_seterrorhandler(v); +} + +SQRESULT sqstd_throwerrorf(HSQUIRRELVM v,const SQChar *err,...) +{ + SQInteger n=256; + va_list args; +begin: + va_start(args,err); + SQChar *b=sq_getscratchpad(v,n); + SQInteger r=scvsprintf(b,n,err,args); + va_end(args); + if (r>=n) { + n=r+1;//required+null + goto begin; + } else if (r<0) { + return sq_throwerror(v,_SC("@failed to generate formatted error message")); + } else { + return sq_throwerror(v,b); + } +} diff --git a/src/vscript/squirrel/sqstdlib/sqstdblob.cpp b/src/vscript/squirrel/sqstdlib/sqstdblob.cpp new file mode 100644 index 00000000..776a9680 --- /dev/null +++ b/src/vscript/squirrel/sqstdlib/sqstdblob.cpp @@ -0,0 +1,283 @@ +/* see copyright notice in squirrel.h */ +#include +#include +#include +#include +#include +#include "sqstdstream.h" +#include "sqstdblobimpl.h" + +#define SQSTD_BLOB_TYPE_TAG ((SQUnsignedInteger)(SQSTD_STREAM_TYPE_TAG | 0x00000002)) + +//Blob + + +#define SETUP_BLOB(v) \ + SQBlob *self = NULL; \ + { if(SQ_FAILED(sq_getinstanceup(v,1,(SQUserPointer*)&self,(SQUserPointer)SQSTD_BLOB_TYPE_TAG))) \ + return sq_throwerror(v,_SC("invalid type tag")); } \ + if(!self || !self->IsValid()) \ + return sq_throwerror(v,_SC("the blob is invalid")); + + +static SQInteger _blob_resize(HSQUIRRELVM v) +{ + SETUP_BLOB(v); + SQInteger size; + sq_getinteger(v,2,&size); + if(!self->Resize(size)) + return sq_throwerror(v,_SC("resize failed")); + return 0; +} + +static void __swap_dword(unsigned int *n) +{ + *n=(unsigned int)(((*n&0xFF000000)>>24) | + ((*n&0x00FF0000)>>8) | + ((*n&0x0000FF00)<<8) | + ((*n&0x000000FF)<<24)); +} + +static void __swap_word(unsigned short *n) +{ + *n=(unsigned short)((*n>>8)&0x00FF)| ((*n<<8)&0xFF00); +} + +static SQInteger _blob_swap4(HSQUIRRELVM v) +{ + SETUP_BLOB(v); + SQInteger num=(self->Len()-(self->Len()%4))>>2; + unsigned int *t=(unsigned int *)self->GetBuf(); + for(SQInteger i = 0; i < num; i++) { + __swap_dword(&t[i]); + } + return 0; +} + +static SQInteger _blob_swap2(HSQUIRRELVM v) +{ + SETUP_BLOB(v); + SQInteger num=(self->Len()-(self->Len()%2))>>1; + unsigned short *t = (unsigned short *)self->GetBuf(); + for(SQInteger i = 0; i < num; i++) { + __swap_word(&t[i]); + } + return 0; +} + +static SQInteger _blob__set(HSQUIRRELVM v) +{ + SETUP_BLOB(v); + SQInteger idx,val; + sq_getinteger(v,2,&idx); + sq_getinteger(v,3,&val); + if(idx < 0 || idx >= self->Len()) + return sq_throwerror(v,_SC("index out of range")); + ((unsigned char *)self->GetBuf())[idx] = (unsigned char) val; + sq_push(v,3); + return 1; +} + +static SQInteger _blob__get(HSQUIRRELVM v) +{ + SETUP_BLOB(v); + SQInteger idx; + + if ((sq_gettype(v, 2) & SQOBJECT_NUMERIC) == 0) + { + sq_pushnull(v); + return sq_throwobject(v); + } + sq_getinteger(v,2,&idx); + if(idx < 0 || idx >= self->Len()) + return sq_throwerror(v,_SC("index out of range")); + sq_pushinteger(v,((unsigned char *)self->GetBuf())[idx]); + return 1; +} + +static SQInteger _blob__nexti(HSQUIRRELVM v) +{ + SETUP_BLOB(v); + if(sq_gettype(v,2) == OT_NULL) { + sq_pushinteger(v, 0); + return 1; + } + SQInteger idx; + if(SQ_SUCCEEDED(sq_getinteger(v, 2, &idx))) { + if(idx+1 < self->Len()) { + sq_pushinteger(v, idx+1); + return 1; + } + sq_pushnull(v); + return 1; + } + return sq_throwerror(v,_SC("internal error (_nexti) wrong argument type")); +} + +static SQInteger _blob__typeof(HSQUIRRELVM v) +{ + sq_pushstring(v,_SC("blob"),-1); + return 1; +} + +static SQInteger _blob_releasehook(SQUserPointer p, SQInteger SQ_UNUSED_ARG(size)) +{ + SQBlob *self = (SQBlob*)p; + self->~SQBlob(); + sq_free(self,sizeof(SQBlob)); + return 1; +} + +static SQInteger _blob_constructor(HSQUIRRELVM v) +{ + SQInteger nparam = sq_gettop(v); + SQInteger size = 0; + if(nparam == 2) { + sq_getinteger(v, 2, &size); + } + if(size < 0) return sq_throwerror(v, _SC("cannot create blob with negative size")); + //SQBlob *b = new SQBlob(size); + + SQBlob *b = new (sq_malloc(sizeof(SQBlob)))SQBlob(size); + if(SQ_FAILED(sq_setinstanceup(v,1,b))) { + b->~SQBlob(); + sq_free(b,sizeof(SQBlob)); + return sq_throwerror(v, _SC("cannot create blob")); + } + sq_setreleasehook(v,1,_blob_releasehook); + return 0; +} + +static SQInteger _blob__cloned(HSQUIRRELVM v) +{ + SQBlob *other = NULL; + { + if(SQ_FAILED(sq_getinstanceup(v,2,(SQUserPointer*)&other,(SQUserPointer)SQSTD_BLOB_TYPE_TAG))) + return SQ_ERROR; + } + //SQBlob *thisone = new SQBlob(other->Len()); + SQBlob *thisone = new (sq_malloc(sizeof(SQBlob)))SQBlob(other->Len()); + memcpy(thisone->GetBuf(),other->GetBuf(),thisone->Len()); + if(SQ_FAILED(sq_setinstanceup(v,1,thisone))) { + thisone->~SQBlob(); + sq_free(thisone,sizeof(SQBlob)); + return sq_throwerror(v, _SC("cannot clone blob")); + } + sq_setreleasehook(v,1,_blob_releasehook); + return 0; +} + +#define _DECL_BLOB_FUNC(name,nparams,typecheck) {_SC(#name),_blob_##name,nparams,typecheck} +static const SQRegFunction _blob_methods[] = { + _DECL_BLOB_FUNC(constructor,-1,_SC("xn")), + _DECL_BLOB_FUNC(resize,2,_SC("xn")), + _DECL_BLOB_FUNC(swap2,1,_SC("x")), + _DECL_BLOB_FUNC(swap4,1,_SC("x")), + _DECL_BLOB_FUNC(_set,3,_SC("xnn")), + _DECL_BLOB_FUNC(_get,2,_SC("x.")), + _DECL_BLOB_FUNC(_typeof,1,_SC("x")), + _DECL_BLOB_FUNC(_nexti,2,_SC("x")), + _DECL_BLOB_FUNC(_cloned,2,_SC("xx")), + {NULL,(SQFUNCTION)0,0,NULL} +}; + + + +//GLOBAL FUNCTIONS + +static SQInteger _g_blob_casti2f(HSQUIRRELVM v) +{ + SQInteger i; + sq_getinteger(v,2,&i); + sq_pushfloat(v,*((const SQFloat *)&i)); + return 1; +} + +static SQInteger _g_blob_castf2i(HSQUIRRELVM v) +{ + SQFloat f; + sq_getfloat(v,2,&f); + sq_pushinteger(v,*((const SQInteger *)&f)); + return 1; +} + +static SQInteger _g_blob_swap2(HSQUIRRELVM v) +{ + SQInteger i; + sq_getinteger(v,2,&i); + unsigned short s = (unsigned short)i; + sq_pushinteger(v, ((s << 8) | ((s >> 8) & 0x00FFu)) & 0xFFFFu); + return 1; +} + +static SQInteger _g_blob_swap4(HSQUIRRELVM v) +{ + SQInteger i; + sq_getinteger(v,2,&i); + unsigned int t4 = (unsigned int)i; + __swap_dword(&t4); + sq_pushinteger(v,(SQInteger)t4); + return 1; +} + +static SQInteger _g_blob_swapfloat(HSQUIRRELVM v) +{ + SQFloat f; + sq_getfloat(v,2,&f); + __swap_dword((unsigned int *)&f); + sq_pushfloat(v,f); + return 1; +} + +#define _DECL_GLOBALBLOB_FUNC(name,nparams,typecheck) {_SC(#name),_g_blob_##name,nparams,typecheck} +static const SQRegFunction bloblib_funcs[]={ + _DECL_GLOBALBLOB_FUNC(casti2f,2,_SC(".n")), + _DECL_GLOBALBLOB_FUNC(castf2i,2,_SC(".n")), + _DECL_GLOBALBLOB_FUNC(swap2,2,_SC(".n")), + _DECL_GLOBALBLOB_FUNC(swap4,2,_SC(".n")), + _DECL_GLOBALBLOB_FUNC(swapfloat,2,_SC(".n")), + {NULL,(SQFUNCTION)0,0,NULL} +}; + +SQRESULT sqstd_getblob(HSQUIRRELVM v,SQInteger idx,SQUserPointer *ptr) +{ + SQBlob *blob; + if(SQ_FAILED(sq_getinstanceup(v,idx,(SQUserPointer *)&blob,(SQUserPointer)SQSTD_BLOB_TYPE_TAG))) + return -1; + *ptr = blob->GetBuf(); + return SQ_OK; +} + +SQInteger sqstd_getblobsize(HSQUIRRELVM v,SQInteger idx) +{ + SQBlob *blob; + if(SQ_FAILED(sq_getinstanceup(v,idx,(SQUserPointer *)&blob,(SQUserPointer)SQSTD_BLOB_TYPE_TAG))) + return -1; + return blob->Len(); +} + +SQUserPointer sqstd_createblob(HSQUIRRELVM v, SQInteger size) +{ + SQInteger top = sq_gettop(v); + sq_pushregistrytable(v); + sq_pushstring(v,_SC("std_blob"),-1); + if(SQ_SUCCEEDED(sq_get(v,-2))) { + sq_remove(v,-2); //removes the registry + sq_push(v,1); // push the this + sq_pushinteger(v,size); //size + SQBlob *blob = NULL; + if(SQ_SUCCEEDED(sq_call(v,2,SQTrue,SQFalse)) + && SQ_SUCCEEDED(sq_getinstanceup(v,-1,(SQUserPointer *)&blob,(SQUserPointer)SQSTD_BLOB_TYPE_TAG))) { + sq_remove(v,-2); + return blob->GetBuf(); + } + } + sq_settop(v,top); + return NULL; +} + +SQRESULT sqstd_register_bloblib(HSQUIRRELVM v) +{ + return declare_stream(v,_SC("blob"),(SQUserPointer)SQSTD_BLOB_TYPE_TAG,_SC("std_blob"),_blob_methods,bloblib_funcs); +} + diff --git a/src/vscript/squirrel/sqstdlib/sqstdblobimpl.h b/src/vscript/squirrel/sqstdlib/sqstdblobimpl.h new file mode 100644 index 00000000..bfdaddc2 --- /dev/null +++ b/src/vscript/squirrel/sqstdlib/sqstdblobimpl.h @@ -0,0 +1,108 @@ +/* see copyright notice in squirrel.h */ +#ifndef _SQSTD_BLOBIMPL_H_ +#define _SQSTD_BLOBIMPL_H_ + +struct SQBlob : public SQStream +{ + SQBlob(SQInteger size) { + _size = size; + _allocated = size; + _buf = (unsigned char *)sq_malloc(size); + memset(_buf, 0, _size); + _ptr = 0; + _owns = true; + } + virtual ~SQBlob() { + sq_free(_buf, _allocated); + } + SQInteger Write(void *buffer, SQInteger size) { + if(!CanAdvance(size)) { + GrowBufOf(_ptr + size - _size); + } + memcpy(&_buf[_ptr], buffer, size); + _ptr += size; + return size; + } + SQInteger Read(void *buffer,SQInteger size) { + SQInteger n = size; + if(!CanAdvance(size)) { + if((_size - _ptr) > 0) + n = _size - _ptr; + else return 0; + } + memcpy(buffer, &_buf[_ptr], n); + _ptr += n; + return n; + } + bool Resize(SQInteger n) { + if(!_owns) return false; + if(n != _allocated) { + unsigned char *newbuf = (unsigned char *)sq_malloc(n); + memset(newbuf,0,n); + if(_size > n) + memcpy(newbuf,_buf,n); + else + memcpy(newbuf,_buf,_size); + sq_free(_buf,_allocated); + _buf=newbuf; + _allocated = n; + if(_size > _allocated) + _size = _allocated; + if(_ptr > _allocated) + _ptr = _allocated; + } + return true; + } + bool GrowBufOf(SQInteger n) + { + bool ret = true; + if(_size + n > _allocated) { + if(_size + n > _size * 2) + ret = Resize(_size + n); + else + ret = Resize(_size * 2); + } + _size = _size + n; + return ret; + } + bool CanAdvance(SQInteger n) { + if(_ptr+n>_size)return false; + return true; + } + SQInteger Seek(SQInteger offset, SQInteger origin) { + switch(origin) { + case SQ_SEEK_SET: + if(offset > _size || offset < 0) return -1; + _ptr = offset; + break; + case SQ_SEEK_CUR: + if(_ptr + offset > _size || _ptr + offset < 0) return -1; + _ptr += offset; + break; + case SQ_SEEK_END: + if(_size + offset > _size || _size + offset < 0) return -1; + _ptr = _size + offset; + break; + default: return -1; + } + return 0; + } + bool IsValid() { + return _size == 0 || _buf?true:false; + } + bool EOS() { + return _ptr == _size; + } + SQInteger Flush() { return 0; } + SQInteger Tell() { return _ptr; } + SQInteger Len() { return _size; } + SQUserPointer GetBuf(){ return _buf; } +private: + SQInteger _size; + SQInteger _allocated; + SQInteger _ptr; + unsigned char *_buf; + bool _owns; +}; + +#endif //_SQSTD_BLOBIMPL_H_ diff --git a/src/vscript/squirrel/sqstdlib/sqstdio.cpp b/src/vscript/squirrel/sqstdlib/sqstdio.cpp new file mode 100644 index 00000000..52cd5158 --- /dev/null +++ b/src/vscript/squirrel/sqstdlib/sqstdio.cpp @@ -0,0 +1,489 @@ +/* see copyright notice in squirrel.h */ +#include +#include +#include +#include +#include "sqstdstream.h" + +#define SQSTD_FILE_TYPE_TAG ((SQUnsignedInteger)(SQSTD_STREAM_TYPE_TAG | 0x00000001)) +//basic API +SQFILE sqstd_fopen(const SQChar *filename ,const SQChar *mode) +{ +#ifndef SQUNICODE + return (SQFILE)fopen(filename,mode); +#else + return (SQFILE)_wfopen(filename,mode); +#endif +} + +SQInteger sqstd_fread(void* buffer, SQInteger size, SQInteger count, SQFILE file) +{ + SQInteger ret = (SQInteger)fread(buffer,size,count,(FILE *)file); + return ret; +} + +SQInteger sqstd_fwrite(const SQUserPointer buffer, SQInteger size, SQInteger count, SQFILE file) +{ + return (SQInteger)fwrite(buffer,size,count,(FILE *)file); +} + +SQInteger sqstd_fseek(SQFILE file, SQInteger offset, SQInteger origin) +{ + SQInteger realorigin; + switch(origin) { + case SQ_SEEK_CUR: realorigin = SEEK_CUR; break; + case SQ_SEEK_END: realorigin = SEEK_END; break; + case SQ_SEEK_SET: realorigin = SEEK_SET; break; + default: return -1; //failed + } + return fseek((FILE *)file,(long)offset,(int)realorigin); +} + +SQInteger sqstd_ftell(SQFILE file) +{ + return ftell((FILE *)file); +} + +SQInteger sqstd_fflush(SQFILE file) +{ + return fflush((FILE *)file); +} + +SQInteger sqstd_fclose(SQFILE file) +{ + return fclose((FILE *)file); +} + +SQInteger sqstd_feof(SQFILE file) +{ + return feof((FILE *)file); +} + +//File +struct SQFile : public SQStream { + SQFile() { _handle = NULL; _owns = false;} + SQFile(SQFILE file, bool owns) { _handle = file; _owns = owns;} + virtual ~SQFile() { Close(); } + bool Open(const SQChar *filename ,const SQChar *mode) { + Close(); + if( (_handle = sqstd_fopen(filename,mode)) ) { + _owns = true; + return true; + } + return false; + } + void Close() { + if(_handle && _owns) { + sqstd_fclose(_handle); + _handle = NULL; + _owns = false; + } + } + SQInteger Read(void *buffer,SQInteger size) { + return sqstd_fread(buffer,1,size,_handle); + } + SQInteger Write(void *buffer,SQInteger size) { + return sqstd_fwrite(buffer,1,size,_handle); + } + SQInteger Flush() { + return sqstd_fflush(_handle); + } + SQInteger Tell() { + return sqstd_ftell(_handle); + } + SQInteger Len() { + SQInteger prevpos=Tell(); + Seek(0,SQ_SEEK_END); + SQInteger size=Tell(); + Seek(prevpos,SQ_SEEK_SET); + return size; + } + SQInteger Seek(SQInteger offset, SQInteger origin) { + return sqstd_fseek(_handle,offset,origin); + } + bool IsValid() { return _handle?true:false; } + bool EOS() { return Tell()==Len()?true:false;} + SQFILE GetHandle() {return _handle;} +private: + SQFILE _handle; + bool _owns; +}; + +static SQInteger _file__typeof(HSQUIRRELVM v) +{ + sq_pushstring(v,_SC("file"),-1); + return 1; +} + +static SQInteger _file_releasehook(SQUserPointer p, SQInteger SQ_UNUSED_ARG(size)) +{ + SQFile *self = (SQFile*)p; + self->~SQFile(); + sq_free(self,sizeof(SQFile)); + return 1; +} + +static SQInteger _file_constructor(HSQUIRRELVM v) +{ + const SQChar *filename,*mode; + bool owns = true; + SQFile *f; + SQFILE newf; + if(sq_gettype(v,2) == OT_STRING && sq_gettype(v,3) == OT_STRING) { + sq_getstring(v, 2, &filename); + sq_getstring(v, 3, &mode); + newf = sqstd_fopen(filename, mode); + if(!newf) return sq_throwerror(v, _SC("cannot open file")); + } else if(sq_gettype(v,2) == OT_USERPOINTER) { + owns = !(sq_gettype(v,3) == OT_NULL); + sq_getuserpointer(v,2,&newf); + } else { + return sq_throwerror(v,_SC("wrong parameter")); + } + + f = new (sq_malloc(sizeof(SQFile)))SQFile(newf,owns); + if(SQ_FAILED(sq_setinstanceup(v,1,f))) { + f->~SQFile(); + sq_free(f,sizeof(SQFile)); + return sq_throwerror(v, _SC("cannot create blob with negative size")); + } + sq_setreleasehook(v,1,_file_releasehook); + return 0; +} + +static SQInteger _file_close(HSQUIRRELVM v) +{ + SQFile *self = NULL; + if(SQ_SUCCEEDED(sq_getinstanceup(v,1,(SQUserPointer*)&self,(SQUserPointer)SQSTD_FILE_TYPE_TAG)) + && self != NULL) + { + self->Close(); + } + return 0; +} + +//bindings +#define _DECL_FILE_FUNC(name,nparams,typecheck) {_SC(#name),_file_##name,nparams,typecheck} +static const SQRegFunction _file_methods[] = { + _DECL_FILE_FUNC(constructor,3,_SC("x")), + _DECL_FILE_FUNC(_typeof,1,_SC("x")), + _DECL_FILE_FUNC(close,1,_SC("x")), + {NULL,(SQFUNCTION)0,0,NULL} +}; + + + +SQRESULT sqstd_createfile(HSQUIRRELVM v, SQFILE file,SQBool own) +{ + SQInteger top = sq_gettop(v); + sq_pushregistrytable(v); + sq_pushstring(v,_SC("std_file"),-1); + if(SQ_SUCCEEDED(sq_get(v,-2))) { + sq_remove(v,-2); //removes the registry + sq_pushroottable(v); // push the this + sq_pushuserpointer(v,file); //file + if(own){ + sq_pushinteger(v,1); //true + } + else{ + sq_pushnull(v); //false + } + if(SQ_SUCCEEDED( sq_call(v,3,SQTrue,SQFalse) )) { + sq_remove(v,-2); + return SQ_OK; + } + } + sq_settop(v,top); + return SQ_ERROR; +} + +SQRESULT sqstd_getfile(HSQUIRRELVM v, SQInteger idx, SQFILE *file) +{ + SQFile *fileobj = NULL; + if(SQ_SUCCEEDED(sq_getinstanceup(v,idx,(SQUserPointer*)&fileobj,(SQUserPointer)SQSTD_FILE_TYPE_TAG))) { + *file = fileobj->GetHandle(); + return SQ_OK; + } + return sq_throwerror(v,_SC("not a file")); +} + + + +#define IO_BUFFER_SIZE 2048 +struct IOBuffer { + unsigned char buffer[IO_BUFFER_SIZE]; + SQInteger size; + SQInteger ptr; + SQFILE file; +}; + +SQInteger _read_byte(IOBuffer *iobuffer) +{ + if(iobuffer->ptr < iobuffer->size) { + + SQInteger ret = iobuffer->buffer[iobuffer->ptr]; + iobuffer->ptr++; + return ret; + } + else { + if( (iobuffer->size = sqstd_fread(iobuffer->buffer,1,IO_BUFFER_SIZE,iobuffer->file )) > 0 ) + { + SQInteger ret = iobuffer->buffer[0]; + iobuffer->ptr = 1; + return ret; + } + } + + return 0; +} + +SQInteger _read_two_bytes(IOBuffer *iobuffer) +{ + if(iobuffer->ptr < iobuffer->size) { + if(iobuffer->size < 2) return 0; + SQInteger ret = *((const wchar_t*)&iobuffer->buffer[iobuffer->ptr]); + iobuffer->ptr += 2; + return ret; + } + else { + if( (iobuffer->size = sqstd_fread(iobuffer->buffer,1,IO_BUFFER_SIZE,iobuffer->file )) > 0 ) + { + if(iobuffer->size < 2) return 0; + SQInteger ret = *((const wchar_t*)&iobuffer->buffer[0]); + iobuffer->ptr = 2; + return ret; + } + } + + return 0; +} + +static SQInteger _io_file_lexfeed_PLAIN(SQUserPointer iobuf) +{ + IOBuffer *iobuffer = (IOBuffer *)iobuf; + return _read_byte(iobuffer); + +} + +#ifdef SQUNICODE +static SQInteger _io_file_lexfeed_UTF8(SQUserPointer iobuf) +{ + IOBuffer *iobuffer = (IOBuffer *)iobuf; +#define READ(iobuf) \ + if((inchar = (unsigned char)_read_byte(iobuf)) == 0) \ + return 0; + + static const SQInteger utf8_lengths[16] = + { + 1,1,1,1,1,1,1,1, /* 0000 to 0111 : 1 byte (plain ASCII) */ + 0,0,0,0, /* 1000 to 1011 : not valid */ + 2,2, /* 1100, 1101 : 2 bytes */ + 3, /* 1110 : 3 bytes */ + 4 /* 1111 :4 bytes */ + }; + static const unsigned char byte_masks[5] = {0,0,0x1f,0x0f,0x07}; + unsigned char inchar; + SQInteger c = 0; + READ(iobuffer); + c = inchar; + // + if(c >= 0x80) { + SQInteger tmp; + SQInteger codelen = utf8_lengths[c>>4]; + if(codelen == 0) + return 0; + //"invalid UTF-8 stream"; + tmp = c&byte_masks[codelen]; + for(SQInteger n = 0; n < codelen-1; n++) { + tmp<<=6; + READ(iobuffer); + tmp |= inchar & 0x3F; + } + c = tmp; + } + return c; +} +#endif + +static SQInteger _io_file_lexfeed_UCS2_LE(SQUserPointer iobuf) +{ + SQInteger ret; + IOBuffer *iobuffer = (IOBuffer *)iobuf; + if( (ret = _read_two_bytes(iobuffer)) > 0 ) + return ret; + return 0; +} + +static SQInteger _io_file_lexfeed_UCS2_BE(SQUserPointer iobuf) +{ + SQInteger c; + IOBuffer *iobuffer = (IOBuffer *)iobuf; + if( (c = _read_two_bytes(iobuffer)) > 0 ) { + c = ((c>>8)&0x00FF)| ((c<<8)&0xFF00); + return c; + } + return 0; +} + +SQInteger file_read(SQUserPointer file,SQUserPointer buf,SQInteger size) +{ + SQInteger ret; + if( ( ret = sqstd_fread(buf,1,size,(SQFILE)file ))!=0 )return ret; + return -1; +} + +SQInteger file_write(SQUserPointer file,SQUserPointer p,SQInteger size) +{ + return sqstd_fwrite(p,1,size,(SQFILE)file); +} + +SQRESULT sqstd_loadfile(HSQUIRRELVM v,const SQChar *filename,SQBool printerror) +{ + SQFILE file = sqstd_fopen(filename,_SC("rb")); + + SQInteger ret; + unsigned short us; + unsigned char uc; + SQLEXREADFUNC func = _io_file_lexfeed_PLAIN; + if(file){ + ret = sqstd_fread(&us,1,2,file); + if(ret != 2) { + //probably an empty file + us = 0; + } + if(us == SQ_BYTECODE_STREAM_TAG) { //BYTECODE + sqstd_fseek(file,0,SQ_SEEK_SET); + if(SQ_SUCCEEDED(sq_readclosure(v,file_read,file))) { + sqstd_fclose(file); + return SQ_OK; + } + } + else { //SCRIPT + + switch(us) + { + //gotta swap the next 2 lines on BIG endian machines + case 0xFFFE: func = _io_file_lexfeed_UCS2_BE; break;//UTF-16 little endian; + case 0xFEFF: func = _io_file_lexfeed_UCS2_LE; break;//UTF-16 big endian; + case 0xBBEF: + if(sqstd_fread(&uc,1,sizeof(uc),file) == 0) { + sqstd_fclose(file); + return sq_throwerror(v,_SC("io error")); + } + if(uc != 0xBF) { + sqstd_fclose(file); + return sq_throwerror(v,_SC("Unrecognized encoding")); + } +#ifdef SQUNICODE + func = _io_file_lexfeed_UTF8; +#else + func = _io_file_lexfeed_PLAIN; +#endif + break;//UTF-8 ; + default: sqstd_fseek(file,0,SQ_SEEK_SET); break; // ascii + } + IOBuffer buffer; + buffer.ptr = 0; + buffer.size = 0; + buffer.file = file; + if(SQ_SUCCEEDED(sq_compile(v,func,&buffer,filename,printerror))){ + sqstd_fclose(file); + return SQ_OK; + } + } + sqstd_fclose(file); + return SQ_ERROR; + } + return sq_throwerror(v,_SC("cannot open the file")); +} + +SQRESULT sqstd_dofile(HSQUIRRELVM v,const SQChar *filename,SQBool retval,SQBool printerror) +{ + //at least one entry must exist in order for us to push it as the environment + if(sq_gettop(v) == 0) + return sq_throwerror(v,_SC("environment table expected")); + + if(SQ_SUCCEEDED(sqstd_loadfile(v,filename,printerror))) { + sq_push(v,-2); + if(SQ_SUCCEEDED(sq_call(v,1,retval,SQTrue))) { + sq_remove(v,retval?-2:-1); //removes the closure + return 1; + } + sq_pop(v,1); //removes the closure + } + return SQ_ERROR; +} + +SQRESULT sqstd_writeclosuretofile(HSQUIRRELVM v,const SQChar *filename) +{ + SQFILE file = sqstd_fopen(filename,_SC("wb+")); + if(!file) return sq_throwerror(v,_SC("cannot open the file")); + if(SQ_SUCCEEDED(sq_writeclosure(v,file_write,file))) { + sqstd_fclose(file); + return SQ_OK; + } + sqstd_fclose(file); + return SQ_ERROR; //forward the error +} + +SQInteger _g_io_loadfile(HSQUIRRELVM v) +{ + const SQChar *filename; + SQBool printerror = SQFalse; + sq_getstring(v,2,&filename); + if(sq_gettop(v) >= 3) { + sq_getbool(v,3,&printerror); + } + if(SQ_SUCCEEDED(sqstd_loadfile(v,filename,printerror))) + return 1; + return SQ_ERROR; //propagates the error +} + +SQInteger _g_io_writeclosuretofile(HSQUIRRELVM v) +{ + const SQChar *filename; + sq_getstring(v,2,&filename); + if(SQ_SUCCEEDED(sqstd_writeclosuretofile(v,filename))) + return 1; + return SQ_ERROR; //propagates the error +} + +SQInteger _g_io_dofile(HSQUIRRELVM v) +{ + const SQChar *filename; + SQBool printerror = SQFalse; + sq_getstring(v,2,&filename); + if(sq_gettop(v) >= 3) { + sq_getbool(v,3,&printerror); + } + sq_push(v,1); //repush the this + if(SQ_SUCCEEDED(sqstd_dofile(v,filename,SQTrue,printerror))) + return 1; + return SQ_ERROR; //propagates the error +} + +#define _DECL_GLOBALIO_FUNC(name,nparams,typecheck) {_SC(#name),_g_io_##name,nparams,typecheck} +static const SQRegFunction iolib_funcs[]={ + _DECL_GLOBALIO_FUNC(loadfile,-2,_SC(".sb")), + _DECL_GLOBALIO_FUNC(dofile,-2,_SC(".sb")), + _DECL_GLOBALIO_FUNC(writeclosuretofile,3,_SC(".sc")), + {NULL,(SQFUNCTION)0,0,NULL} +}; + +SQRESULT sqstd_register_iolib(HSQUIRRELVM v) +{ + SQInteger top = sq_gettop(v); + //create delegate + declare_stream(v,_SC("file"),(SQUserPointer)SQSTD_FILE_TYPE_TAG,_SC("std_file"),_file_methods,iolib_funcs); + sq_pushstring(v,_SC("stdout"),-1); + sqstd_createfile(v,stdout,SQFalse); + sq_newslot(v,-3,SQFalse); + sq_pushstring(v,_SC("stdin"),-1); + sqstd_createfile(v,stdin,SQFalse); + sq_newslot(v,-3,SQFalse); + sq_pushstring(v,_SC("stderr"),-1); + sqstd_createfile(v,stderr,SQFalse); + sq_newslot(v,-3,SQFalse); + sq_settop(v,top); + return SQ_OK; +} diff --git a/src/vscript/squirrel/sqstdlib/sqstdlib.dsp b/src/vscript/squirrel/sqstdlib/sqstdlib.dsp new file mode 100644 index 00000000..ef5b7f48 --- /dev/null +++ b/src/vscript/squirrel/sqstdlib/sqstdlib.dsp @@ -0,0 +1,131 @@ +# Microsoft Developer Studio Project File - Name="sqstdlib" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Static Library" 0x0104 + +CFG=sqstdlib - Win32 Release +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "sqstdlib.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "sqstdlib.mak" CFG="sqstdlib - Win32 Release" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "sqstdlib - Win32 Release" (based on "Win32 (x86) Static Library") +!MESSAGE "sqstdlib - Win32 Debug" (based on "Win32 (x86) Static Library") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_LocalPath ".." +CPP=cl.exe +RSC=rc.exe + +!IF "$(CFG)" == "sqstdlib - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release" +# PROP Intermediate_Dir "Release" +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_MBCS" /D "_LIB" /YX /FD /c +# ADD CPP /nologo /W3 /GX /O2 /I "..\include" /D "WIN32" /D "NDEBUG" /D "_MBCS" /D "_LIB" /YX /FD /c +# ADD BASE RSC /l 0x410 /d "NDEBUG" +# ADD RSC /l 0x410 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LIB32=link.exe -lib +# ADD BASE LIB32 /nologo +# ADD LIB32 /nologo /out:"..\lib\sqstdlib.lib" + +!ELSEIF "$(CFG)" == "sqstdlib - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug" +# PROP Intermediate_Dir "Debug" +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_MBCS" /D "_LIB" /YX /FD /GZ /c +# ADD CPP /nologo /W3 /Gm /GX /ZI /Od /I "..\include" /D "WIN32" /D "_DEBUG" /D "_MBCS" /D "_LIB" /YX /FD /GZ /c +# ADD BASE RSC /l 0x410 /d "_DEBUG" +# ADD RSC /l 0x410 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LIB32=link.exe -lib +# ADD BASE LIB32 /nologo +# ADD LIB32 /nologo /out:"..\lib\sqstdlib.lib" + +!ENDIF + +# Begin Target + +# Name "sqstdlib - Win32 Release" +# Name "sqstdlib - Win32 Debug" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" +# Begin Source File + +SOURCE=.\sqstdblob.cpp +# End Source File +# Begin Source File + +SOURCE=.\sqstdio.cpp +# End Source File +# Begin Source File + +SOURCE=.\sqstdmath.cpp +# End Source File +# Begin Source File + +SOURCE=.\sqstdrex.cpp +# End Source File +# Begin Source File + +SOURCE=.\sqstdstream.cpp +# End Source File +# Begin Source File + +SOURCE=.\sqstdstring.cpp +# End Source File +# Begin Source File + +SOURCE=.\sqstdaux.cpp +# End Source File +# Begin Source File + +SOURCE=.\sqstdsystem.cpp +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl" +# Begin Source File + +SOURCE=.\sqstdblobimpl.h +# End Source File +# Begin Source File + +SOURCE=.\sqstdstream.h +# End Source File +# End Group +# End Target +# End Project diff --git a/src/vscript/squirrel/sqstdlib/sqstdmath.cpp b/src/vscript/squirrel/sqstdlib/sqstdmath.cpp new file mode 100644 index 00000000..c41ffd5e --- /dev/null +++ b/src/vscript/squirrel/sqstdlib/sqstdmath.cpp @@ -0,0 +1,107 @@ +/* see copyright notice in squirrel.h */ +#include +#include +#include +#include + +#define SINGLE_ARG_FUNC(_funcname) static SQInteger math_##_funcname(HSQUIRRELVM v){ \ + SQFloat f; \ + sq_getfloat(v,2,&f); \ + sq_pushfloat(v,(SQFloat)_funcname(f)); \ + return 1; \ +} + +#define TWO_ARGS_FUNC(_funcname) static SQInteger math_##_funcname(HSQUIRRELVM v){ \ + SQFloat p1,p2; \ + sq_getfloat(v,2,&p1); \ + sq_getfloat(v,3,&p2); \ + sq_pushfloat(v,(SQFloat)_funcname(p1,p2)); \ + return 1; \ +} + +static SQInteger math_srand(HSQUIRRELVM v) +{ + SQInteger i; + if(SQ_FAILED(sq_getinteger(v,2,&i))) + return sq_throwerror(v,_SC("invalid param")); + srand((unsigned int)i); + return 0; +} + +static SQInteger math_rand(HSQUIRRELVM v) +{ + sq_pushinteger(v,rand()); + return 1; +} + +static SQInteger math_abs(HSQUIRRELVM v) +{ + SQInteger n; + sq_getinteger(v,2,&n); + sq_pushinteger(v,(SQInteger)abs((int)n)); + return 1; +} + +SINGLE_ARG_FUNC(sqrt) +SINGLE_ARG_FUNC(fabs) +SINGLE_ARG_FUNC(sin) +SINGLE_ARG_FUNC(cos) +SINGLE_ARG_FUNC(asin) +SINGLE_ARG_FUNC(acos) +SINGLE_ARG_FUNC(log) +SINGLE_ARG_FUNC(log10) +SINGLE_ARG_FUNC(tan) +SINGLE_ARG_FUNC(atan) +TWO_ARGS_FUNC(atan2) +TWO_ARGS_FUNC(pow) +SINGLE_ARG_FUNC(floor) +SINGLE_ARG_FUNC(ceil) +SINGLE_ARG_FUNC(exp) + +#define _DECL_FUNC(name,nparams,tycheck) {_SC(#name),math_##name,nparams,tycheck} +static const SQRegFunction mathlib_funcs[] = { + _DECL_FUNC(sqrt,2,_SC(".n")), + _DECL_FUNC(sin,2,_SC(".n")), + _DECL_FUNC(cos,2,_SC(".n")), + _DECL_FUNC(asin,2,_SC(".n")), + _DECL_FUNC(acos,2,_SC(".n")), + _DECL_FUNC(log,2,_SC(".n")), + _DECL_FUNC(log10,2,_SC(".n")), + _DECL_FUNC(tan,2,_SC(".n")), + _DECL_FUNC(atan,2,_SC(".n")), + _DECL_FUNC(atan2,3,_SC(".nn")), + _DECL_FUNC(pow,3,_SC(".nn")), + _DECL_FUNC(floor,2,_SC(".n")), + _DECL_FUNC(ceil,2,_SC(".n")), + _DECL_FUNC(exp,2,_SC(".n")), + _DECL_FUNC(srand,2,_SC(".n")), + _DECL_FUNC(rand,1,NULL), + _DECL_FUNC(fabs,2,_SC(".n")), + _DECL_FUNC(abs,2,_SC(".n")), + {NULL,(SQFUNCTION)0,0,NULL} +}; +#undef _DECL_FUNC + +#ifndef M_PI +#define M_PI (3.14159265358979323846) +#endif + +SQRESULT sqstd_register_mathlib(HSQUIRRELVM v) +{ + SQInteger i=0; + while(mathlib_funcs[i].name!=0) { + sq_pushstring(v,mathlib_funcs[i].name,-1); + sq_newclosure(v,mathlib_funcs[i].f,0); + sq_setparamscheck(v,mathlib_funcs[i].nparamscheck,mathlib_funcs[i].typemask); + sq_setnativeclosurename(v,-1,mathlib_funcs[i].name); + sq_newslot(v,-3,SQFalse); + i++; + } + sq_pushstring(v,_SC("RAND_MAX"),-1); + sq_pushinteger(v,RAND_MAX); + sq_newslot(v,-3,SQFalse); + sq_pushstring(v,_SC("PI"),-1); + sq_pushfloat(v,(SQFloat)M_PI); + sq_newslot(v,-3,SQFalse); + return SQ_OK; +} diff --git a/src/vscript/squirrel/sqstdlib/sqstdrex.cpp b/src/vscript/squirrel/sqstdlib/sqstdrex.cpp new file mode 100644 index 00000000..d0583a6b --- /dev/null +++ b/src/vscript/squirrel/sqstdlib/sqstdrex.cpp @@ -0,0 +1,666 @@ +/* see copyright notice in squirrel.h */ +#include +#include +#include +#include +#include + +#ifdef _DEBUG +#include + +static const SQChar *g_nnames[] = +{ + _SC("NONE"),_SC("OP_GREEDY"), _SC("OP_OR"), + _SC("OP_EXPR"),_SC("OP_NOCAPEXPR"),_SC("OP_DOT"), _SC("OP_CLASS"), + _SC("OP_CCLASS"),_SC("OP_NCLASS"),_SC("OP_RANGE"),_SC("OP_CHAR"), + _SC("OP_EOL"),_SC("OP_BOL"),_SC("OP_WB"),_SC("OP_MB") +}; + +#endif + +#define OP_GREEDY (MAX_CHAR+1) // * + ? {n} +#define OP_OR (MAX_CHAR+2) +#define OP_EXPR (MAX_CHAR+3) //parentesis () +#define OP_NOCAPEXPR (MAX_CHAR+4) //parentesis (?:) +#define OP_DOT (MAX_CHAR+5) +#define OP_CLASS (MAX_CHAR+6) +#define OP_CCLASS (MAX_CHAR+7) +#define OP_NCLASS (MAX_CHAR+8) //negates class the [^ +#define OP_RANGE (MAX_CHAR+9) +#define OP_CHAR (MAX_CHAR+10) +#define OP_EOL (MAX_CHAR+11) +#define OP_BOL (MAX_CHAR+12) +#define OP_WB (MAX_CHAR+13) +#define OP_MB (MAX_CHAR+14) //match balanced + +#define SQREX_SYMBOL_ANY_CHAR ('.') +#define SQREX_SYMBOL_GREEDY_ONE_OR_MORE ('+') +#define SQREX_SYMBOL_GREEDY_ZERO_OR_MORE ('*') +#define SQREX_SYMBOL_GREEDY_ZERO_OR_ONE ('?') +#define SQREX_SYMBOL_BRANCH ('|') +#define SQREX_SYMBOL_END_OF_STRING ('$') +#define SQREX_SYMBOL_BEGINNING_OF_STRING ('^') +#define SQREX_SYMBOL_ESCAPE_CHAR ('\\') + + +typedef int SQRexNodeType; + +typedef struct tagSQRexNode{ + SQRexNodeType type; + SQInteger left; + SQInteger right; + SQInteger next; +}SQRexNode; + +struct SQRex{ + const SQChar *_eol; + const SQChar *_bol; + const SQChar *_p; + SQInteger _first; + SQInteger _op; + SQRexNode *_nodes; + SQInteger _nallocated; + SQInteger _nsize; + SQInteger _nsubexpr; + SQRexMatch *_matches; + SQInteger _currsubexp; + void *_jmpbuf; + const SQChar **_error; +}; + +static SQInteger sqstd_rex_list(SQRex *exp); + +static SQInteger sqstd_rex_newnode(SQRex *exp, SQRexNodeType type) +{ + SQRexNode n; + n.type = type; + n.next = n.right = n.left = -1; + if(type == OP_EXPR) + n.right = exp->_nsubexpr++; + if(exp->_nallocated < (exp->_nsize + 1)) { + SQInteger oldsize = exp->_nallocated; + exp->_nallocated *= 2; + exp->_nodes = (SQRexNode *)sq_realloc(exp->_nodes, oldsize * sizeof(SQRexNode) ,exp->_nallocated * sizeof(SQRexNode)); + } + exp->_nodes[exp->_nsize++] = n; + SQInteger newid = exp->_nsize - 1; + return (SQInteger)newid; +} + +static void sqstd_rex_error(SQRex *exp,const SQChar *error) +{ + if(exp->_error) *exp->_error = error; + longjmp(*((jmp_buf*)exp->_jmpbuf),-1); +} + +static void sqstd_rex_expect(SQRex *exp, SQInteger n){ + if((*exp->_p) != n) + sqstd_rex_error(exp, _SC("expected paren")); + exp->_p++; +} + +static SQChar sqstd_rex_escapechar(SQRex *exp) +{ + if(*exp->_p == SQREX_SYMBOL_ESCAPE_CHAR){ + exp->_p++; + switch(*exp->_p) { + case 'v': exp->_p++; return '\v'; + case 'n': exp->_p++; return '\n'; + case 't': exp->_p++; return '\t'; + case 'r': exp->_p++; return '\r'; + case 'f': exp->_p++; return '\f'; + default: return (*exp->_p++); + } + } else if(!scisprint(*exp->_p)) sqstd_rex_error(exp,_SC("letter expected")); + return (*exp->_p++); +} + +static SQInteger sqstd_rex_charclass(SQRex *exp,SQInteger classid) +{ + SQInteger n = sqstd_rex_newnode(exp,OP_CCLASS); + exp->_nodes[n].left = classid; + return n; +} + +static SQInteger sqstd_rex_charnode(SQRex *exp,SQBool isclass) +{ + SQChar t; + if(*exp->_p == SQREX_SYMBOL_ESCAPE_CHAR) { + exp->_p++; + switch(*exp->_p) { + case 'n': exp->_p++; return sqstd_rex_newnode(exp,'\n'); + case 't': exp->_p++; return sqstd_rex_newnode(exp,'\t'); + case 'r': exp->_p++; return sqstd_rex_newnode(exp,'\r'); + case 'f': exp->_p++; return sqstd_rex_newnode(exp,'\f'); + case 'v': exp->_p++; return sqstd_rex_newnode(exp,'\v'); + case 'a': case 'A': case 'w': case 'W': case 's': case 'S': + case 'd': case 'D': case 'x': case 'X': case 'c': case 'C': + case 'p': case 'P': case 'l': case 'u': + { + t = *exp->_p; exp->_p++; + return sqstd_rex_charclass(exp,t); + } + case 'm': + { + SQChar cb, ce; //cb = character begin match ce = character end match + cb = *++exp->_p; //skip 'm' + ce = *++exp->_p; + exp->_p++; //points to the next char to be parsed + if ((!cb) || (!ce)) sqstd_rex_error(exp,_SC("balanced chars expected")); + if ( cb == ce ) sqstd_rex_error(exp,_SC("open/close char can't be the same")); + SQInteger node = sqstd_rex_newnode(exp,OP_MB); + exp->_nodes[node].left = cb; + exp->_nodes[node].right = ce; + return node; + } + case 0: + sqstd_rex_error(exp,_SC("letter expected for argument of escape sequence")); + break; + case 'b': + case 'B': + if(!isclass) { + SQInteger node = sqstd_rex_newnode(exp,OP_WB); + exp->_nodes[node].left = *exp->_p; + exp->_p++; + return node; + } //else default + default: + t = *exp->_p; exp->_p++; + return sqstd_rex_newnode(exp,t); + } + } + else if(!scisprint(*exp->_p)) { + + sqstd_rex_error(exp,_SC("letter expected")); + } + t = *exp->_p; exp->_p++; + return sqstd_rex_newnode(exp,t); +} +static SQInteger sqstd_rex_class(SQRex *exp) +{ + SQInteger ret = -1; + SQInteger first = -1,chain; + if(*exp->_p == SQREX_SYMBOL_BEGINNING_OF_STRING){ + ret = sqstd_rex_newnode(exp,OP_NCLASS); + exp->_p++; + }else ret = sqstd_rex_newnode(exp,OP_CLASS); + + if(*exp->_p == ']') sqstd_rex_error(exp,_SC("empty class")); + chain = ret; + while(*exp->_p != ']' && exp->_p != exp->_eol) { + if(*exp->_p == '-' && first != -1){ + SQInteger r; + if(*exp->_p++ == ']') sqstd_rex_error(exp,_SC("unfinished range")); + r = sqstd_rex_newnode(exp,OP_RANGE); + if(exp->_nodes[first].type>*exp->_p) sqstd_rex_error(exp,_SC("invalid range")); + if(exp->_nodes[first].type == OP_CCLASS) sqstd_rex_error(exp,_SC("cannot use character classes in ranges")); + exp->_nodes[r].left = exp->_nodes[first].type; + SQInteger t = sqstd_rex_escapechar(exp); + exp->_nodes[r].right = t; + exp->_nodes[chain].next = r; + chain = r; + first = -1; + } + else{ + if(first!=-1){ + SQInteger c = first; + exp->_nodes[chain].next = c; + chain = c; + first = sqstd_rex_charnode(exp,SQTrue); + } + else{ + first = sqstd_rex_charnode(exp,SQTrue); + } + } + } + if(first!=-1){ + SQInteger c = first; + exp->_nodes[chain].next = c; + } + /* hack? */ + exp->_nodes[ret].left = exp->_nodes[ret].next; + exp->_nodes[ret].next = -1; + return ret; +} + +static SQInteger sqstd_rex_parsenumber(SQRex *exp) +{ + SQInteger ret = *exp->_p-'0'; + SQInteger positions = 10; + exp->_p++; + while(isdigit(*exp->_p)) { + ret = ret*10+(*exp->_p++-'0'); + if(positions==1000000000) sqstd_rex_error(exp,_SC("overflow in numeric constant")); + positions *= 10; + }; + return ret; +} + +static SQInteger sqstd_rex_element(SQRex *exp) +{ + SQInteger ret = -1; + switch(*exp->_p) + { + case '(': { + SQInteger expr; + exp->_p++; + + + if(*exp->_p =='?') { + exp->_p++; + sqstd_rex_expect(exp,':'); + expr = sqstd_rex_newnode(exp,OP_NOCAPEXPR); + } + else + expr = sqstd_rex_newnode(exp,OP_EXPR); + SQInteger newn = sqstd_rex_list(exp); + exp->_nodes[expr].left = newn; + ret = expr; + sqstd_rex_expect(exp,')'); + } + break; + case '[': + exp->_p++; + ret = sqstd_rex_class(exp); + sqstd_rex_expect(exp,']'); + break; + case SQREX_SYMBOL_END_OF_STRING: exp->_p++; ret = sqstd_rex_newnode(exp,OP_EOL);break; + case SQREX_SYMBOL_ANY_CHAR: exp->_p++; ret = sqstd_rex_newnode(exp,OP_DOT);break; + default: + ret = sqstd_rex_charnode(exp,SQFalse); + break; + } + + + SQBool isgreedy = SQFalse; + unsigned short p0 = 0, p1 = 0; + switch(*exp->_p){ + case SQREX_SYMBOL_GREEDY_ZERO_OR_MORE: p0 = 0; p1 = 0xFFFF; exp->_p++; isgreedy = SQTrue; break; + case SQREX_SYMBOL_GREEDY_ONE_OR_MORE: p0 = 1; p1 = 0xFFFF; exp->_p++; isgreedy = SQTrue; break; + case SQREX_SYMBOL_GREEDY_ZERO_OR_ONE: p0 = 0; p1 = 1; exp->_p++; isgreedy = SQTrue; break; + case '{': + exp->_p++; + if(!isdigit(*exp->_p)) sqstd_rex_error(exp,_SC("number expected")); + p0 = (unsigned short)sqstd_rex_parsenumber(exp); + /*******************************/ + switch(*exp->_p) { + case '}': + p1 = p0; exp->_p++; + break; + case ',': + exp->_p++; + p1 = 0xFFFF; + if(isdigit(*exp->_p)){ + p1 = (unsigned short)sqstd_rex_parsenumber(exp); + } + sqstd_rex_expect(exp,'}'); + break; + default: + sqstd_rex_error(exp,_SC(", or } expected")); + } + /*******************************/ + isgreedy = SQTrue; + break; + + } + if(isgreedy) { + SQInteger nnode = sqstd_rex_newnode(exp,OP_GREEDY); + exp->_nodes[nnode].left = ret; + exp->_nodes[nnode].right = ((p0)<<16)|p1; + ret = nnode; + } + + if((*exp->_p != SQREX_SYMBOL_BRANCH) && (*exp->_p != ')') && (*exp->_p != SQREX_SYMBOL_GREEDY_ZERO_OR_MORE) && (*exp->_p != SQREX_SYMBOL_GREEDY_ONE_OR_MORE) && (*exp->_p != '\0')) { + SQInteger nnode = sqstd_rex_element(exp); + exp->_nodes[ret].next = nnode; + } + + return ret; +} + +static SQInteger sqstd_rex_list(SQRex *exp) +{ + SQInteger ret=-1,e; + if(*exp->_p == SQREX_SYMBOL_BEGINNING_OF_STRING) { + exp->_p++; + ret = sqstd_rex_newnode(exp,OP_BOL); + } + e = sqstd_rex_element(exp); + if(ret != -1) { + exp->_nodes[ret].next = e; + } + else ret = e; + + if(*exp->_p == SQREX_SYMBOL_BRANCH) { + SQInteger temp,tright; + exp->_p++; + temp = sqstd_rex_newnode(exp,OP_OR); + exp->_nodes[temp].left = ret; + tright = sqstd_rex_list(exp); + exp->_nodes[temp].right = tright; + ret = temp; + } + return ret; +} + +static SQBool sqstd_rex_matchcclass(SQInteger cclass,SQChar c) +{ + switch(cclass) { + case 'a': return isalpha(c)?SQTrue:SQFalse; + case 'A': return !isalpha(c)?SQTrue:SQFalse; + case 'w': return (isalnum(c) || c == '_')?SQTrue:SQFalse; + case 'W': return (!isalnum(c) && c != '_')?SQTrue:SQFalse; + case 's': return isspace(c)?SQTrue:SQFalse; + case 'S': return !isspace(c)?SQTrue:SQFalse; + case 'd': return isdigit(c)?SQTrue:SQFalse; + case 'D': return !isdigit(c)?SQTrue:SQFalse; + case 'x': return isxdigit(c)?SQTrue:SQFalse; + case 'X': return !isxdigit(c)?SQTrue:SQFalse; + case 'c': return iscntrl(c)?SQTrue:SQFalse; + case 'C': return !iscntrl(c)?SQTrue:SQFalse; + case 'p': return ispunct(c)?SQTrue:SQFalse; + case 'P': return !ispunct(c)?SQTrue:SQFalse; + case 'l': return islower(c)?SQTrue:SQFalse; + case 'u': return isupper(c)?SQTrue:SQFalse; + } + return SQFalse; /*cannot happen*/ +} + +static SQBool sqstd_rex_matchclass(SQRex* exp,SQRexNode *node,SQChar c) +{ + do { + switch(node->type) { + case OP_RANGE: + if(c >= node->left && c <= node->right) return SQTrue; + break; + case OP_CCLASS: + if(sqstd_rex_matchcclass(node->left,c)) return SQTrue; + break; + default: + if(c == node->type)return SQTrue; + } + } while((node->next != -1) && (node = &exp->_nodes[node->next])); + return SQFalse; +} + +static const SQChar *sqstd_rex_matchnode(SQRex* exp,SQRexNode *node,const SQChar *str,SQRexNode *next) +{ + + SQRexNodeType type = node->type; + switch(type) { + case OP_GREEDY: { + //SQRexNode *greedystop = (node->next != -1) ? &exp->_nodes[node->next] : NULL; + SQRexNode *greedystop = NULL; + SQInteger p0 = (node->right >> 16)&0x0000FFFF, p1 = node->right&0x0000FFFF, nmaches = 0; + const SQChar *s=str, *good = str; + + if(node->next != -1) { + greedystop = &exp->_nodes[node->next]; + } + else { + greedystop = next; + } + + while((nmaches == 0xFFFF || nmaches < p1)) { + + const SQChar *stop; + if(!(s = sqstd_rex_matchnode(exp,&exp->_nodes[node->left],s,greedystop))) + break; + nmaches++; + good=s; + if(greedystop) { + //checks that 0 matches satisfy the expression(if so skips) + //if not would always stop(for instance if is a '?') + if(greedystop->type != OP_GREEDY || + (greedystop->type == OP_GREEDY && ((greedystop->right >> 16)&0x0000FFFF) != 0)) + { + SQRexNode *gnext = NULL; + if(greedystop->next != -1) { + gnext = &exp->_nodes[greedystop->next]; + }else if(next && next->next != -1){ + gnext = &exp->_nodes[next->next]; + } + stop = sqstd_rex_matchnode(exp,greedystop,s,gnext); + if(stop) { + //if satisfied stop it + if(p0 == p1 && p0 == nmaches) break; + else if(nmaches >= p0 && p1 == 0xFFFF) break; + else if(nmaches >= p0 && nmaches <= p1) break; + } + } + } + + if(s >= exp->_eol) + break; + } + if(p0 == p1 && p0 == nmaches) return good; + else if(nmaches >= p0 && p1 == 0xFFFF) return good; + else if(nmaches >= p0 && nmaches <= p1) return good; + return NULL; + } + case OP_OR: { + const SQChar *asd = str; + SQRexNode *temp=&exp->_nodes[node->left]; + while( (asd = sqstd_rex_matchnode(exp,temp,asd,NULL)) ) { + if(temp->next != -1) + temp = &exp->_nodes[temp->next]; + else + return asd; + } + asd = str; + temp = &exp->_nodes[node->right]; + while( (asd = sqstd_rex_matchnode(exp,temp,asd,NULL)) ) { + if(temp->next != -1) + temp = &exp->_nodes[temp->next]; + else + return asd; + } + return NULL; + break; + } + case OP_EXPR: + case OP_NOCAPEXPR:{ + SQRexNode *n = &exp->_nodes[node->left]; + const SQChar *cur = str; + SQInteger capture = -1; + if(node->type != OP_NOCAPEXPR && node->right == exp->_currsubexp) { + capture = exp->_currsubexp; + exp->_matches[capture].begin = cur; + exp->_currsubexp++; + } + SQInteger tempcap = exp->_currsubexp; + do { + SQRexNode *subnext = NULL; + if(n->next != -1) { + subnext = &exp->_nodes[n->next]; + }else { + subnext = next; + } + if(!(cur = sqstd_rex_matchnode(exp,n,cur,subnext))) { + if(capture != -1){ + exp->_matches[capture].begin = 0; + exp->_matches[capture].len = 0; + } + return NULL; + } + } while((n->next != -1) && (n = &exp->_nodes[n->next])); + + exp->_currsubexp = tempcap; + if(capture != -1) + exp->_matches[capture].len = cur - exp->_matches[capture].begin; + return cur; + } + case OP_WB: + if((str == exp->_bol && !isspace(*str)) + || (str == exp->_eol && !isspace(*(str-1))) + || (!isspace(*str) && isspace(*(str+1))) + || (isspace(*str) && !isspace(*(str+1))) ) { + return (node->left == 'b')?str:NULL; + } + return (node->left == 'b')?NULL:str; + case OP_BOL: + if(str == exp->_bol) return str; + return NULL; + case OP_EOL: + if(str == exp->_eol) return str; + return NULL; + case OP_DOT:{ + if (str == exp->_eol) return NULL; + str++; + } + return str; + case OP_NCLASS: + case OP_CLASS: + if (str == exp->_eol) return NULL; + if(sqstd_rex_matchclass(exp,&exp->_nodes[node->left],*str)?(type == OP_CLASS?SQTrue:SQFalse):(type == OP_NCLASS?SQTrue:SQFalse)) { + str++; + return str; + } + return NULL; + case OP_CCLASS: + if (str == exp->_eol) return NULL; + if(sqstd_rex_matchcclass(node->left,*str)) { + str++; + return str; + } + return NULL; + case OP_MB: + { + SQInteger cb = node->left; //char that opens a balanced expression + if(*str != cb) return NULL; // string doesnt start with open char + SQInteger ce = node->right; //char that closes a balanced expression + SQInteger cont = 1; + const SQChar *streol = exp->_eol; + while (++str < streol) { + if (*str == ce) { + if (--cont == 0) { + return ++str; + } + } + else if (*str == cb) cont++; + } + } + return NULL; // string ends out of balance + default: /* char */ + if (str == exp->_eol) return NULL; + if(*str != node->type) return NULL; + str++; + return str; + } + return NULL; +} + +/* public api */ +SQRex *sqstd_rex_compile(const SQChar *pattern,const SQChar **error) +{ + SQRex * volatile exp = (SQRex *)sq_malloc(sizeof(SQRex)); // "volatile" is needed for setjmp() + exp->_eol = exp->_bol = NULL; + exp->_p = pattern; + exp->_nallocated = (SQInteger)scstrlen(pattern) * sizeof(SQChar); + exp->_nodes = (SQRexNode *)sq_malloc(exp->_nallocated * sizeof(SQRexNode)); + exp->_nsize = 0; + exp->_matches = 0; + exp->_nsubexpr = 0; + exp->_first = sqstd_rex_newnode(exp,OP_EXPR); + exp->_error = error; + exp->_jmpbuf = sq_malloc(sizeof(jmp_buf)); + if(setjmp(*((jmp_buf*)exp->_jmpbuf)) == 0) { + SQInteger res = sqstd_rex_list(exp); + exp->_nodes[exp->_first].left = res; + if(*exp->_p!='\0') + sqstd_rex_error(exp,_SC("unexpected character")); +#ifdef _DEBUG + { + SQInteger nsize,i; + SQRexNode *t; + nsize = exp->_nsize; + t = &exp->_nodes[0]; + scprintf(_SC("\n")); + for(i = 0;i < nsize; i++) { + if(exp->_nodes[i].type>MAX_CHAR) + scprintf(_SC("[%02d] %10s "), (SQInt32)i,g_nnames[exp->_nodes[i].type-MAX_CHAR]); + else + scprintf(_SC("[%02d] %10c "), (SQInt32)i,exp->_nodes[i].type); + scprintf(_SC("left %02d right %02d next %02d\n"), (SQInt32)exp->_nodes[i].left, (SQInt32)exp->_nodes[i].right, (SQInt32)exp->_nodes[i].next); + } + scprintf(_SC("\n")); + } +#endif + exp->_matches = (SQRexMatch *) sq_malloc(exp->_nsubexpr * sizeof(SQRexMatch)); + memset(exp->_matches,0,exp->_nsubexpr * sizeof(SQRexMatch)); + } + else{ + sqstd_rex_free(exp); + return NULL; + } + return exp; +} + +void sqstd_rex_free(SQRex *exp) +{ + if(exp) { + if(exp->_nodes) sq_free(exp->_nodes,exp->_nallocated * sizeof(SQRexNode)); + if(exp->_jmpbuf) sq_free(exp->_jmpbuf,sizeof(jmp_buf)); + if(exp->_matches) sq_free(exp->_matches,exp->_nsubexpr * sizeof(SQRexMatch)); + sq_free(exp,sizeof(SQRex)); + } +} + +SQBool sqstd_rex_match(SQRex* exp,const SQChar* text) +{ + const SQChar* res = NULL; + exp->_bol = text; + exp->_eol = text + scstrlen(text); + exp->_currsubexp = 0; + res = sqstd_rex_matchnode(exp,exp->_nodes,text,NULL); + if(res == NULL || res != exp->_eol) + return SQFalse; + return SQTrue; +} + +SQBool sqstd_rex_searchrange(SQRex* exp,const SQChar* text_begin,const SQChar* text_end,const SQChar** out_begin, const SQChar** out_end) +{ + const SQChar *cur = NULL; + SQInteger node = exp->_first; + if(text_begin >= text_end) return SQFalse; + exp->_bol = text_begin; + exp->_eol = text_end; + do { + cur = text_begin; + while(node != -1) { + exp->_currsubexp = 0; + cur = sqstd_rex_matchnode(exp,&exp->_nodes[node],cur,NULL); + if(!cur) + break; + node = exp->_nodes[node].next; + } + text_begin++; + } while(cur == NULL && text_begin != text_end); + + if(cur == NULL) + return SQFalse; + + --text_begin; + + if(out_begin) *out_begin = text_begin; + if(out_end) *out_end = cur; + return SQTrue; +} + +SQBool sqstd_rex_search(SQRex* exp,const SQChar* text, const SQChar** out_begin, const SQChar** out_end) +{ + return sqstd_rex_searchrange(exp,text,text + scstrlen(text),out_begin,out_end); +} + +SQInteger sqstd_rex_getsubexpcount(SQRex* exp) +{ + return exp->_nsubexpr; +} + +SQBool sqstd_rex_getsubexp(SQRex* exp, SQInteger n, SQRexMatch *subexp) +{ + if( n<0 || n >= exp->_nsubexpr) return SQFalse; + *subexp = exp->_matches[n]; + return SQTrue; +} + diff --git a/src/vscript/squirrel/sqstdlib/sqstdstream.cpp b/src/vscript/squirrel/sqstdlib/sqstdstream.cpp new file mode 100644 index 00000000..b5c47cfb --- /dev/null +++ b/src/vscript/squirrel/sqstdlib/sqstdstream.cpp @@ -0,0 +1,336 @@ +/* see copyright notice in squirrel.h */ +#include +#include +#include +#include +#include +#include +#include +#include "sqstdstream.h" +#include "sqstdblobimpl.h" + +#define SETUP_STREAM(v) \ + SQStream *self = NULL; \ + if(SQ_FAILED(sq_getinstanceup(v,1,(SQUserPointer*)&self,(SQUserPointer)((SQUnsignedInteger)SQSTD_STREAM_TYPE_TAG)))) \ + return sq_throwerror(v,_SC("invalid type tag")); \ + if(!self || !self->IsValid()) \ + return sq_throwerror(v,_SC("the stream is invalid")); + +SQInteger _stream_readblob(HSQUIRRELVM v) +{ + SETUP_STREAM(v); + SQUserPointer data,blobp; + SQInteger size,res; + sq_getinteger(v,2,&size); + if(size > self->Len()) { + size = self->Len(); + } + data = sq_getscratchpad(v,size); + res = self->Read(data,size); + if(res <= 0) + return sq_throwerror(v,_SC("no data left to read")); + blobp = sqstd_createblob(v,res); + memcpy(blobp,data,res); + return 1; +} + +#define SAFE_READN(ptr,len) { \ + if(self->Read(ptr,len) != len) return sq_throwerror(v,_SC("io error")); \ + } +SQInteger _stream_readn(HSQUIRRELVM v) +{ + SETUP_STREAM(v); + SQInteger format; + sq_getinteger(v, 2, &format); + switch(format) { + case 'l': { + SQInteger i; + SAFE_READN(&i, sizeof(i)); + sq_pushinteger(v, i); + } + break; + case 'i': { + SQInt32 i; + SAFE_READN(&i, sizeof(i)); + sq_pushinteger(v, i); + } + break; + case 's': { + short s; + SAFE_READN(&s, sizeof(short)); + sq_pushinteger(v, s); + } + break; + case 'w': { + unsigned short w; + SAFE_READN(&w, sizeof(unsigned short)); + sq_pushinteger(v, w); + } + break; + case 'c': { + char c; + SAFE_READN(&c, sizeof(char)); + sq_pushinteger(v, c); + } + break; + case 'b': { + unsigned char c; + SAFE_READN(&c, sizeof(unsigned char)); + sq_pushinteger(v, c); + } + break; + case 'f': { + float f; + SAFE_READN(&f, sizeof(float)); + sq_pushfloat(v, f); + } + break; + case 'd': { + double d; + SAFE_READN(&d, sizeof(double)); + sq_pushfloat(v, (SQFloat)d); + } + break; + default: + return sq_throwerror(v, _SC("invalid format")); + } + return 1; +} + +SQInteger _stream_writeblob(HSQUIRRELVM v) +{ + SQUserPointer data; + SQInteger size; + SETUP_STREAM(v); + if(SQ_FAILED(sqstd_getblob(v,2,&data))) + return sq_throwerror(v,_SC("invalid parameter")); + size = sqstd_getblobsize(v,2); + if(self->Write(data,size) != size) + return sq_throwerror(v,_SC("io error")); + sq_pushinteger(v,size); + return 1; +} + +SQInteger _stream_writen(HSQUIRRELVM v) +{ + SETUP_STREAM(v); + SQInteger format, ti; + SQFloat tf; + sq_getinteger(v, 3, &format); + switch(format) { + case 'l': { + SQInteger i; + sq_getinteger(v, 2, &ti); + i = ti; + self->Write(&i, sizeof(SQInteger)); + } + break; + case 'i': { + SQInt32 i; + sq_getinteger(v, 2, &ti); + i = (SQInt32)ti; + self->Write(&i, sizeof(SQInt32)); + } + break; + case 's': { + short s; + sq_getinteger(v, 2, &ti); + s = (short)ti; + self->Write(&s, sizeof(short)); + } + break; + case 'w': { + unsigned short w; + sq_getinteger(v, 2, &ti); + w = (unsigned short)ti; + self->Write(&w, sizeof(unsigned short)); + } + break; + case 'c': { + char c; + sq_getinteger(v, 2, &ti); + c = (char)ti; + self->Write(&c, sizeof(char)); + } + break; + case 'b': { + unsigned char b; + sq_getinteger(v, 2, &ti); + b = (unsigned char)ti; + self->Write(&b, sizeof(unsigned char)); + } + break; + case 'f': { + float f; + sq_getfloat(v, 2, &tf); + f = (float)tf; + self->Write(&f, sizeof(float)); + } + break; + case 'd': { + double d; + sq_getfloat(v, 2, &tf); + d = tf; + self->Write(&d, sizeof(double)); + } + break; + default: + return sq_throwerror(v, _SC("invalid format")); + } + return 0; +} + +SQInteger _stream_seek(HSQUIRRELVM v) +{ + SETUP_STREAM(v); + SQInteger offset, origin = SQ_SEEK_SET; + sq_getinteger(v, 2, &offset); + if(sq_gettop(v) > 2) { + SQInteger t; + sq_getinteger(v, 3, &t); + switch(t) { + case 'b': origin = SQ_SEEK_SET; break; + case 'c': origin = SQ_SEEK_CUR; break; + case 'e': origin = SQ_SEEK_END; break; + default: return sq_throwerror(v,_SC("invalid origin")); + } + } + sq_pushinteger(v, self->Seek(offset, origin)); + return 1; +} + +SQInteger _stream_tell(HSQUIRRELVM v) +{ + SETUP_STREAM(v); + sq_pushinteger(v, self->Tell()); + return 1; +} + +SQInteger _stream_len(HSQUIRRELVM v) +{ + SETUP_STREAM(v); + sq_pushinteger(v, self->Len()); + return 1; +} + +SQInteger _stream_flush(HSQUIRRELVM v) +{ + SETUP_STREAM(v); + if(!self->Flush()) + sq_pushinteger(v, 1); + else + sq_pushnull(v); + return 1; +} + +SQInteger _stream_eos(HSQUIRRELVM v) +{ + SETUP_STREAM(v); + if(self->EOS()) + sq_pushinteger(v, 1); + else + sq_pushnull(v); + return 1; +} + + SQInteger _stream__cloned(HSQUIRRELVM v) + { + return sq_throwerror(v,_SC("this object cannot be cloned")); + } + +static const SQRegFunction _stream_methods[] = { + _DECL_STREAM_FUNC(readblob,2,_SC("xn")), + _DECL_STREAM_FUNC(readn,2,_SC("xn")), + _DECL_STREAM_FUNC(writeblob,-2,_SC("xx")), + _DECL_STREAM_FUNC(writen,3,_SC("xnn")), + _DECL_STREAM_FUNC(seek,-2,_SC("xnn")), + _DECL_STREAM_FUNC(tell,1,_SC("x")), + _DECL_STREAM_FUNC(len,1,_SC("x")), + _DECL_STREAM_FUNC(eos,1,_SC("x")), + _DECL_STREAM_FUNC(flush,1,_SC("x")), + _DECL_STREAM_FUNC(_cloned,0,NULL), + {NULL,(SQFUNCTION)0,0,NULL} +}; + +void init_streamclass(HSQUIRRELVM v) +{ + sq_pushregistrytable(v); + sq_pushstring(v,_SC("std_stream"),-1); + if(SQ_FAILED(sq_get(v,-2))) { + sq_pushstring(v,_SC("std_stream"),-1); + sq_newclass(v,SQFalse); + sq_settypetag(v,-1,(SQUserPointer)((SQUnsignedInteger)SQSTD_STREAM_TYPE_TAG)); + SQInteger i = 0; + while(_stream_methods[i].name != 0) { + const SQRegFunction &f = _stream_methods[i]; + sq_pushstring(v,f.name,-1); + sq_newclosure(v,f.f,0); + sq_setparamscheck(v,f.nparamscheck,f.typemask); + sq_newslot(v,-3,SQFalse); + i++; + } + sq_newslot(v,-3,SQFalse); + sq_pushroottable(v); + sq_pushstring(v,_SC("stream"),-1); + sq_pushstring(v,_SC("std_stream"),-1); + sq_get(v,-4); + sq_newslot(v,-3,SQFalse); + sq_pop(v,1); + } + else { + sq_pop(v,1); //result + } + sq_pop(v,1); +} + +SQRESULT declare_stream(HSQUIRRELVM v,const SQChar* name,SQUserPointer typetag,const SQChar* reg_name,const SQRegFunction *methods,const SQRegFunction *globals) +{ + if(sq_gettype(v,-1) != OT_TABLE) + return sq_throwerror(v,_SC("table expected")); + SQInteger top = sq_gettop(v); + //create delegate + init_streamclass(v); + sq_pushregistrytable(v); + sq_pushstring(v,reg_name,-1); + sq_pushstring(v,_SC("std_stream"),-1); + if(SQ_SUCCEEDED(sq_get(v,-3))) { + sq_newclass(v,SQTrue); + sq_settypetag(v,-1,typetag); + SQInteger i = 0; + while(methods[i].name != 0) { + const SQRegFunction &f = methods[i]; + sq_pushstring(v,f.name,-1); + sq_newclosure(v,f.f,0); + sq_setparamscheck(v,f.nparamscheck,f.typemask); + sq_setnativeclosurename(v,-1,f.name); + sq_newslot(v,-3,SQFalse); + i++; + } + sq_newslot(v,-3,SQFalse); + sq_pop(v,1); + + i = 0; + while(globals[i].name!=0) + { + const SQRegFunction &f = globals[i]; + sq_pushstring(v,f.name,-1); + sq_newclosure(v,f.f,0); + sq_setparamscheck(v,f.nparamscheck,f.typemask); + sq_setnativeclosurename(v,-1,f.name); + sq_newslot(v,-3,SQFalse); + i++; + } + //register the class in the target table + sq_pushstring(v,name,-1); + sq_pushregistrytable(v); + sq_pushstring(v,reg_name,-1); + sq_get(v,-2); + sq_remove(v,-2); + sq_newslot(v,-3,SQFalse); + + sq_settop(v,top); + return SQ_OK; + } + sq_settop(v,top); + return SQ_ERROR; +} diff --git a/src/vscript/squirrel/sqstdlib/sqstdstream.h b/src/vscript/squirrel/sqstdlib/sqstdstream.h new file mode 100644 index 00000000..867c135f --- /dev/null +++ b/src/vscript/squirrel/sqstdlib/sqstdstream.h @@ -0,0 +1,18 @@ +/* see copyright notice in squirrel.h */ +#ifndef _SQSTD_STREAM_H_ +#define _SQSTD_STREAM_H_ + +SQInteger _stream_readblob(HSQUIRRELVM v); +SQInteger _stream_readline(HSQUIRRELVM v); +SQInteger _stream_readn(HSQUIRRELVM v); +SQInteger _stream_writeblob(HSQUIRRELVM v); +SQInteger _stream_writen(HSQUIRRELVM v); +SQInteger _stream_seek(HSQUIRRELVM v); +SQInteger _stream_tell(HSQUIRRELVM v); +SQInteger _stream_len(HSQUIRRELVM v); +SQInteger _stream_eos(HSQUIRRELVM v); +SQInteger _stream_flush(HSQUIRRELVM v); + +#define _DECL_STREAM_FUNC(name,nparams,typecheck) {_SC(#name),_stream_##name,nparams,typecheck} +SQRESULT declare_stream(HSQUIRRELVM v,const SQChar* name,SQUserPointer typetag,const SQChar* reg_name,const SQRegFunction *methods,const SQRegFunction *globals); +#endif /*_SQSTD_STREAM_H_*/ diff --git a/src/vscript/squirrel/sqstdlib/sqstdstring.cpp b/src/vscript/squirrel/sqstdlib/sqstdstring.cpp new file mode 100644 index 00000000..5747d8ed --- /dev/null +++ b/src/vscript/squirrel/sqstdlib/sqstdstring.cpp @@ -0,0 +1,552 @@ +/* see copyright notice in squirrel.h */ +#include +#include +#include +#include +#include +#include +#include +#include + +#define MAX_FORMAT_LEN 20 +#define MAX_WFORMAT_LEN 3 +#define ADDITIONAL_FORMAT_SPACE (100*sizeof(SQChar)) + +static SQUserPointer rex_typetag = NULL; + +static SQBool isfmtchr(SQChar ch) +{ + switch(ch) { + case '-': case '+': case ' ': case '#': case '0': return SQTrue; + } + return SQFalse; +} + +static SQInteger validate_format(HSQUIRRELVM v, SQChar *fmt, const SQChar *src, SQInteger n,SQInteger &width) +{ + SQChar *dummy; + SQChar swidth[MAX_WFORMAT_LEN]; + SQInteger wc = 0; + SQInteger start = n; + fmt[0] = '%'; + while (isfmtchr(src[n])) n++; + while (scisdigit(src[n])) { + swidth[wc] = src[n]; + n++; + wc++; + if(wc>=MAX_WFORMAT_LEN) + return sq_throwerror(v,_SC("width format too long")); + } + swidth[wc] = '\0'; + if(wc > 0) { + width = scstrtol(swidth,&dummy,10); + } + else + width = 0; + if (src[n] == '.') { + n++; + + wc = 0; + while (scisdigit(src[n])) { + swidth[wc] = src[n]; + n++; + wc++; + if(wc>=MAX_WFORMAT_LEN) + return sq_throwerror(v,_SC("precision format too long")); + } + swidth[wc] = '\0'; + if(wc > 0) { + width += scstrtol(swidth,&dummy,10); + + } + } + if (n-start > MAX_FORMAT_LEN ) + return sq_throwerror(v,_SC("format too long")); + memcpy(&fmt[1],&src[start],((n-start)+1)*sizeof(SQChar)); + fmt[(n-start)+2] = '\0'; + return n; +} + +SQRESULT sqstd_format(HSQUIRRELVM v,SQInteger nformatstringidx,SQInteger *outlen,SQChar **output) +{ + const SQChar *format; + SQChar *dest; + SQChar fmt[MAX_FORMAT_LEN]; + const SQRESULT res = sq_getstring(v,nformatstringidx,&format); + if (SQ_FAILED(res)) { + return res; // propagate the error + } + SQInteger format_size = sq_getsize(v,nformatstringidx); + SQInteger allocated = (format_size+2)*sizeof(SQChar); + dest = sq_getscratchpad(v,allocated); + SQInteger n = 0,i = 0, nparam = nformatstringidx+1, w = 0; + //while(format[n] != '\0') + while(n < format_size) + { + if(format[n] != '%') { + assert(i < allocated); + dest[i++] = format[n]; + n++; + } + else if(format[n+1] == '%') { //handles %% + dest[i++] = '%'; + n += 2; + } + else { + n++; + if( nparam > sq_gettop(v) ) + return sq_throwerror(v,_SC("not enough parameters for the given format string")); + n = validate_format(v,fmt,format,n,w); + if(n < 0) return -1; + SQInteger addlen = 0; + SQInteger valtype = 0; + const SQChar *ts = NULL; + SQInteger ti = 0; + SQFloat tf = 0; + switch(format[n]) { + case 's': + if(SQ_FAILED(sq_getstring(v,nparam,&ts))) + return sq_throwerror(v,_SC("string expected for the specified format")); + addlen = (sq_getsize(v,nparam)*sizeof(SQChar))+((w+1)*sizeof(SQChar)); + valtype = 's'; + break; + case 'i': case 'd': case 'o': case 'u': case 'x': case 'X': +#ifdef _SQ64 + { + size_t flen = scstrlen(fmt); + SQInteger fpos = flen - 1; + SQChar f = fmt[fpos]; + const SQChar *prec = (const SQChar *)_PRINT_INT_PREC; + while(*prec != _SC('\0')) { + fmt[fpos++] = *prec++; + } + fmt[fpos++] = f; + fmt[fpos++] = _SC('\0'); + } +#endif + case 'c': + if(SQ_FAILED(sq_getinteger(v,nparam,&ti))) + return sq_throwerror(v,_SC("integer expected for the specified format")); + addlen = (ADDITIONAL_FORMAT_SPACE)+((w+1)*sizeof(SQChar)); + valtype = 'i'; + break; + case 'f': case 'g': case 'G': case 'e': case 'E': + if(SQ_FAILED(sq_getfloat(v,nparam,&tf))) + return sq_throwerror(v,_SC("float expected for the specified format")); + addlen = (ADDITIONAL_FORMAT_SPACE)+((w+1)*sizeof(SQChar)); + valtype = 'f'; + break; + default: + return sq_throwerror(v,_SC("invalid format")); + } + n++; + allocated += addlen + sizeof(SQChar); + dest = sq_getscratchpad(v,allocated); + switch(valtype) { + case 's': i += scsprintf(&dest[i],allocated,fmt,ts); break; + case 'i': i += scsprintf(&dest[i],allocated,fmt,ti); break; + case 'f': i += scsprintf(&dest[i],allocated,fmt,tf); break; + }; + nparam ++; + } + } + *outlen = i; + dest[i] = '\0'; + *output = dest; + return SQ_OK; +} + +void sqstd_pushstringf(HSQUIRRELVM v,const SQChar *s,...) +{ + SQInteger n=256; + va_list args; +begin: + va_start(args,s); + SQChar *b=sq_getscratchpad(v,n); + SQInteger r=scvsprintf(b,n,s,args); + va_end(args); + if (r>=n) { + n=r+1;//required+null + goto begin; + } else if (r<0) { + sq_pushnull(v); + } else { + sq_pushstring(v,b,r); + } +} + +static SQInteger _string_printf(HSQUIRRELVM v) +{ + SQChar *dest = NULL; + SQInteger length = 0; + if(SQ_FAILED(sqstd_format(v,2,&length,&dest))) + return -1; + + SQPRINTFUNCTION printfunc = sq_getprintfunc(v); + if(printfunc) printfunc(v,_SC("%s"),dest); + + return 0; +} + +static SQInteger _string_format(HSQUIRRELVM v) +{ + SQChar *dest = NULL; + SQInteger length = 0; + if(SQ_FAILED(sqstd_format(v,2,&length,&dest))) + return -1; + sq_pushstring(v,dest,length); + return 1; +} + +static void __strip_l(const SQChar *str,const SQChar **start) +{ + const SQChar *t = str; + while(((*t) != '\0') && scisspace(*t)){ t++; } + *start = t; +} + +static void __strip_r(const SQChar *str,SQInteger len,const SQChar **end) +{ + if(len == 0) { + *end = str; + return; + } + const SQChar *t = &str[len-1]; + while(t >= str && scisspace(*t)) { t--; } + *end = t + 1; +} + +static SQInteger _string_strip(HSQUIRRELVM v) +{ + const SQChar *str,*start,*end; + sq_getstring(v,2,&str); + SQInteger len = sq_getsize(v,2); + __strip_l(str,&start); + __strip_r(str,len,&end); + sq_pushstring(v,start,end - start); + return 1; +} + +static SQInteger _string_lstrip(HSQUIRRELVM v) +{ + const SQChar *str,*start; + sq_getstring(v,2,&str); + __strip_l(str,&start); + sq_pushstring(v,start,-1); + return 1; +} + +static SQInteger _string_rstrip(HSQUIRRELVM v) +{ + const SQChar *str,*end; + sq_getstring(v,2,&str); + SQInteger len = sq_getsize(v,2); + __strip_r(str,len,&end); + sq_pushstring(v,str,end - str); + return 1; +} + +static SQInteger _string_split(HSQUIRRELVM v) +{ + const SQChar *str,*seps; + SQInteger sepsize; + SQBool skipempty = SQFalse; + sq_getstring(v,2,&str); + sq_getstringandsize(v,3,&seps,&sepsize); + if(sepsize == 0) return sq_throwerror(v,_SC("empty separators string")); + if(sq_gettop(v)>3) { + sq_getbool(v,4,&skipempty); + } + const SQChar *start = str; + const SQChar *end = str; + sq_newarray(v,0); + while(*end != '\0') + { + SQChar cur = *end; + for(SQInteger i = 0; i < sepsize; i++) + { + if(cur == seps[i]) + { + if(!skipempty || (end != start)) { + sq_pushstring(v,start,end-start); + sq_arrayappend(v,-2); + } + start = end + 1; + break; + } + } + end++; + } + if(end != start) + { + sq_pushstring(v,start,end-start); + sq_arrayappend(v,-2); + } + return 1; +} + +static SQInteger _string_escape(HSQUIRRELVM v) +{ + const SQChar *str; + SQChar *dest,*resstr; + SQInteger size; + sq_getstring(v,2,&str); + size = sq_getsize(v,2); + if(size == 0) { + sq_push(v,2); + return 1; + } +#ifdef SQUNICODE +#if WCHAR_SIZE == 2 + const SQChar *escpat = _SC("\\x%04x"); + const SQInteger maxescsize = 6; +#else //WCHAR_SIZE == 4 + const SQChar *escpat = _SC("\\x%08x"); + const SQInteger maxescsize = 10; +#endif +#else + const SQChar *escpat = _SC("\\x%02x"); + const SQInteger maxescsize = 4; +#endif + SQInteger destcharsize = (size * maxescsize); //assumes every char could be escaped + resstr = dest = (SQChar *)sq_getscratchpad(v,destcharsize * sizeof(SQChar)); + SQChar c; + SQChar escch; + SQInteger escaped = 0; + for(int n = 0; n < size; n++){ + c = *str++; + escch = 0; + if(scisprint(c) || c == 0) { + switch(c) { + case '\a': escch = 'a'; break; + case '\b': escch = 'b'; break; + case '\t': escch = 't'; break; + case '\n': escch = 'n'; break; + case '\v': escch = 'v'; break; + case '\f': escch = 'f'; break; + case '\r': escch = 'r'; break; + case '\\': escch = '\\'; break; + case '\"': escch = '\"'; break; + case '\'': escch = '\''; break; + case 0: escch = '0'; break; + } + if(escch) { + *dest++ = '\\'; + *dest++ = escch; + escaped++; + } + else { + *dest++ = c; + } + } + else { + + dest += scsprintf(dest, destcharsize, escpat, c); + escaped++; + } + } + + if(escaped) { + sq_pushstring(v,resstr,dest - resstr); + } + else { + sq_push(v,2); //nothing escaped + } + return 1; +} + +static SQInteger _string_startswith(HSQUIRRELVM v) +{ + const SQChar *str,*cmp; + sq_getstring(v,2,&str); + sq_getstring(v,3,&cmp); + SQInteger len = sq_getsize(v,2); + SQInteger cmplen = sq_getsize(v,3); + SQBool ret = SQFalse; + if(cmplen <= len) { + ret = memcmp(str,cmp,sq_rsl(cmplen)) == 0 ? SQTrue : SQFalse; + } + sq_pushbool(v,ret); + return 1; +} + +static SQInteger _string_endswith(HSQUIRRELVM v) +{ + const SQChar *str,*cmp; + sq_getstring(v,2,&str); + sq_getstring(v,3,&cmp); + SQInteger len = sq_getsize(v,2); + SQInteger cmplen = sq_getsize(v,3); + SQBool ret = SQFalse; + if(cmplen <= len) { + ret = memcmp(&str[len - cmplen],cmp,sq_rsl(cmplen)) == 0 ? SQTrue : SQFalse; + } + sq_pushbool(v,ret); + return 1; +} + +#define SETUP_REX(v) \ + SQRex *self = NULL; \ + if(SQ_FAILED(sq_getinstanceup(v,1,(SQUserPointer *)&self,rex_typetag))) { \ + return sq_throwerror(v,_SC("invalid type tag")); \ + } + +static SQInteger _rexobj_releasehook(SQUserPointer p, SQInteger SQ_UNUSED_ARG(size)) +{ + SQRex *self = ((SQRex *)p); + sqstd_rex_free(self); + return 1; +} + +static SQInteger _regexp_match(HSQUIRRELVM v) +{ + SETUP_REX(v); + const SQChar *str; + sq_getstring(v,2,&str); + if(sqstd_rex_match(self,str) == SQTrue) + { + sq_pushbool(v,SQTrue); + return 1; + } + sq_pushbool(v,SQFalse); + return 1; +} + +static void _addrexmatch(HSQUIRRELVM v,const SQChar *str,const SQChar *begin,const SQChar *end) +{ + sq_newtable(v); + sq_pushstring(v,_SC("begin"),-1); + sq_pushinteger(v,begin - str); + sq_rawset(v,-3); + sq_pushstring(v,_SC("end"),-1); + sq_pushinteger(v,end - str); + sq_rawset(v,-3); +} + +static SQInteger _regexp_search(HSQUIRRELVM v) +{ + SETUP_REX(v); + const SQChar *str,*begin,*end; + SQInteger start = 0; + sq_getstring(v,2,&str); + if(sq_gettop(v) > 2) sq_getinteger(v,3,&start); + if(sqstd_rex_search(self,str+start,&begin,&end) == SQTrue) { + _addrexmatch(v,str,begin,end); + return 1; + } + return 0; +} + +static SQInteger _regexp_capture(HSQUIRRELVM v) +{ + SETUP_REX(v); + const SQChar *str,*begin,*end; + SQInteger start = 0; + sq_getstring(v,2,&str); + if(sq_gettop(v) > 2) sq_getinteger(v,3,&start); + if(sqstd_rex_search(self,str+start,&begin,&end) == SQTrue) { + SQInteger n = sqstd_rex_getsubexpcount(self); + SQRexMatch match; + sq_newarray(v,0); + for(SQInteger i = 0;i < n; i++) { + sqstd_rex_getsubexp(self,i,&match); + if(match.len > 0) + _addrexmatch(v,str,match.begin,match.begin+match.len); + else + _addrexmatch(v,str,str,str); //empty match + sq_arrayappend(v,-2); + } + return 1; + } + return 0; +} + +static SQInteger _regexp_subexpcount(HSQUIRRELVM v) +{ + SETUP_REX(v); + sq_pushinteger(v,sqstd_rex_getsubexpcount(self)); + return 1; +} + +static SQInteger _regexp_constructor(HSQUIRRELVM v) +{ + SQRex *self = NULL; + if (SQ_FAILED(sq_getinstanceup(v, 1, (SQUserPointer *)&self, rex_typetag))) { + return sq_throwerror(v, _SC("invalid type tag")); + } + if (self != NULL) { + return sq_throwerror(v, _SC("invalid regexp object")); + } + const SQChar *error,*pattern; + sq_getstring(v,2,&pattern); + SQRex *rex = sqstd_rex_compile(pattern,&error); + if(!rex) return sq_throwerror(v,error); + sq_setinstanceup(v,1,rex); + sq_setreleasehook(v,1,_rexobj_releasehook); + return 0; +} + +static SQInteger _regexp__typeof(HSQUIRRELVM v) +{ + sq_pushstring(v,_SC("regexp"),-1); + return 1; +} + +#define _DECL_REX_FUNC(name,nparams,pmask) {_SC(#name),_regexp_##name,nparams,pmask} +static const SQRegFunction rexobj_funcs[]={ + _DECL_REX_FUNC(constructor,2,_SC(".s")), + _DECL_REX_FUNC(search,-2,_SC("xsn")), + _DECL_REX_FUNC(match,2,_SC("xs")), + _DECL_REX_FUNC(capture,-2,_SC("xsn")), + _DECL_REX_FUNC(subexpcount,1,_SC("x")), + _DECL_REX_FUNC(_typeof,1,_SC("x")), + {NULL,(SQFUNCTION)0,0,NULL} +}; +#undef _DECL_REX_FUNC + +#define _DECL_FUNC(name,nparams,pmask) {_SC(#name),_string_##name,nparams,pmask} +static const SQRegFunction stringlib_funcs[]={ + _DECL_FUNC(format,-2,_SC(".s")), + _DECL_FUNC(printf,-2,_SC(".s")), + _DECL_FUNC(strip,2,_SC(".s")), + _DECL_FUNC(lstrip,2,_SC(".s")), + _DECL_FUNC(rstrip,2,_SC(".s")), + _DECL_FUNC(split,-3,_SC(".ssb")), + _DECL_FUNC(escape,2,_SC(".s")), + _DECL_FUNC(startswith,3,_SC(".ss")), + _DECL_FUNC(endswith,3,_SC(".ss")), + {NULL,(SQFUNCTION)0,0,NULL} +}; +#undef _DECL_FUNC + + +SQInteger sqstd_register_stringlib(HSQUIRRELVM v) +{ + sq_pushstring(v,_SC("regexp"),-1); + sq_newclass(v,SQFalse); + rex_typetag = (SQUserPointer)rexobj_funcs; + sq_settypetag(v, -1, rex_typetag); + SQInteger i = 0; + while(rexobj_funcs[i].name != 0) { + const SQRegFunction &f = rexobj_funcs[i]; + sq_pushstring(v,f.name,-1); + sq_newclosure(v,f.f,0); + sq_setparamscheck(v,f.nparamscheck,f.typemask); + sq_setnativeclosurename(v,-1,f.name); + sq_newslot(v,-3,SQFalse); + i++; + } + sq_newslot(v,-3,SQFalse); + + i = 0; + while(stringlib_funcs[i].name!=0) + { + sq_pushstring(v,stringlib_funcs[i].name,-1); + sq_newclosure(v,stringlib_funcs[i].f,0); + sq_setparamscheck(v,stringlib_funcs[i].nparamscheck,stringlib_funcs[i].typemask); + sq_setnativeclosurename(v,-1,stringlib_funcs[i].name); + sq_newslot(v,-3,SQFalse); + i++; + } + return 1; +} diff --git a/src/vscript/squirrel/sqstdlib/sqstdsystem.cpp b/src/vscript/squirrel/sqstdlib/sqstdsystem.cpp new file mode 100644 index 00000000..d326be1a --- /dev/null +++ b/src/vscript/squirrel/sqstdlib/sqstdsystem.cpp @@ -0,0 +1,154 @@ +/* see copyright notice in squirrel.h */ +#include +#include +#include +#include +#include + +#ifdef SQUNICODE +#include +#define scgetenv _wgetenv +#define scsystem _wsystem +#define scasctime _wasctime +#define scremove _wremove +#define screname _wrename +#else +#define scgetenv getenv +#define scsystem system +#define scasctime asctime +#define scremove remove +#define screname rename +#endif +#ifdef IOS + #include + extern char **environ; +#endif + +static SQInteger _system_getenv(HSQUIRRELVM v) +{ + const SQChar *s; + if(SQ_SUCCEEDED(sq_getstring(v,2,&s))){ + sq_pushstring(v,scgetenv(s),-1); + return 1; + } + return 0; +} + +static SQInteger _system_system(HSQUIRRELVM v) +{ + const SQChar *s; + if(SQ_SUCCEEDED(sq_getstring(v,2,&s))){ + #ifdef IOS + pid_t pid; + posix_spawn(&pid, s, NULL, NULL, NULL, environ); + sq_pushinteger(v, 0); + #else + sq_pushinteger(v,scsystem(s)); + #endif + return 1; + } + return sq_throwerror(v,_SC("wrong param")); +} + +static SQInteger _system_clock(HSQUIRRELVM v) +{ + sq_pushfloat(v,((SQFloat)clock())/(SQFloat)CLOCKS_PER_SEC); + return 1; +} + +static SQInteger _system_time(HSQUIRRELVM v) +{ + SQInteger t = (SQInteger)time(NULL); + sq_pushinteger(v,t); + return 1; +} + +static SQInteger _system_remove(HSQUIRRELVM v) +{ + const SQChar *s; + sq_getstring(v,2,&s); + if(scremove(s)==-1) + return sq_throwerror(v,_SC("remove() failed")); + return 0; +} + +static SQInteger _system_rename(HSQUIRRELVM v) +{ + const SQChar *oldn,*newn; + sq_getstring(v,2,&oldn); + sq_getstring(v,3,&newn); + if(screname(oldn,newn)==-1) + return sq_throwerror(v,_SC("rename() failed")); + return 0; +} + +static void _set_integer_slot(HSQUIRRELVM v,const SQChar *name,SQInteger val) +{ + sq_pushstring(v,name,-1); + sq_pushinteger(v,val); + sq_rawset(v,-3); +} + +static SQInteger _system_date(HSQUIRRELVM v) +{ + time_t t; + SQInteger it; + SQInteger format = 'l'; + if(sq_gettop(v) > 1) { + sq_getinteger(v,2,&it); + t = it; + if(sq_gettop(v) > 2) { + sq_getinteger(v,3,(SQInteger*)&format); + } + } + else { + time(&t); + } + tm *date; + if(format == 'u') + date = gmtime(&t); + else + date = localtime(&t); + if(!date) + return sq_throwerror(v,_SC("crt api failure")); + sq_newtable(v); + _set_integer_slot(v, _SC("sec"), date->tm_sec); + _set_integer_slot(v, _SC("min"), date->tm_min); + _set_integer_slot(v, _SC("hour"), date->tm_hour); + _set_integer_slot(v, _SC("day"), date->tm_mday); + _set_integer_slot(v, _SC("month"), date->tm_mon); + _set_integer_slot(v, _SC("year"), date->tm_year+1900); + _set_integer_slot(v, _SC("wday"), date->tm_wday); + _set_integer_slot(v, _SC("yday"), date->tm_yday); + return 1; +} + + + +#define _DECL_FUNC(name,nparams,pmask) {_SC(#name),_system_##name,nparams,pmask} +static const SQRegFunction systemlib_funcs[]={ + _DECL_FUNC(getenv,2,_SC(".s")), + _DECL_FUNC(system,2,_SC(".s")), + _DECL_FUNC(clock,0,NULL), + _DECL_FUNC(time,1,NULL), + _DECL_FUNC(date,-1,_SC(".nn")), + _DECL_FUNC(remove,2,_SC(".s")), + _DECL_FUNC(rename,3,_SC(".ss")), + {NULL,(SQFUNCTION)0,0,NULL} +}; +#undef _DECL_FUNC + +SQInteger sqstd_register_systemlib(HSQUIRRELVM v) +{ + SQInteger i=0; + while(systemlib_funcs[i].name!=0) + { + sq_pushstring(v,systemlib_funcs[i].name,-1); + sq_newclosure(v,systemlib_funcs[i].f,0); + sq_setparamscheck(v,systemlib_funcs[i].nparamscheck,systemlib_funcs[i].typemask); + sq_setnativeclosurename(v,-1,systemlib_funcs[i].name); + sq_newslot(v,-3,SQFalse); + i++; + } + return 1; +} diff --git a/src/vscript/squirrel/squirrel-config.cmake.in b/src/vscript/squirrel/squirrel-config.cmake.in new file mode 100644 index 00000000..6ed4a8aa --- /dev/null +++ b/src/vscript/squirrel/squirrel-config.cmake.in @@ -0,0 +1,4 @@ +@PACKAGE_INIT@ + +include("${CMAKE_CURRENT_LIST_DIR}/squirrel-config-version.cmake") +include("${CMAKE_CURRENT_LIST_DIR}/squirrel-targets.cmake") diff --git a/src/vscript/squirrel/squirrel.dsw b/src/vscript/squirrel/squirrel.dsw new file mode 100644 index 00000000..72150910 --- /dev/null +++ b/src/vscript/squirrel/squirrel.dsw @@ -0,0 +1,77 @@ +Microsoft Developer Studio Workspace File, Format Version 6.00 +# WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE! + +############################################################################### + +Project: "sq"=.\sq\sq.dsp - Package Owner=<4> + +Package=<5> +{{{ + begin source code control + . + end source code control +}}} + +Package=<4> +{{{ + Begin Project Dependency + Project_Dep_Name sqlibs + End Project Dependency + Begin Project Dependency + Project_Dep_Name squirrel + End Project Dependency + Begin Project Dependency + Project_Dep_Name sqstdlib + End Project Dependency +}}} + +############################################################################### + +Project: "sqstdlib"=.\sqstdlib\sqstdlib.dsp - Package Owner=<4> + +Package=<5> +{{{ + begin source code control + "$/squirrel", HAAAAAAA + . + end source code control +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Project: "squirrel"=.\squirrel\squirrel.dsp - Package Owner=<4> + +Package=<5> +{{{ + begin source code control + "$/squirrel", HAAAAAAA + . + end source code control +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Global: + +Package=<5> +{{{ + begin source code control + "$/squirrel", HAAAAAAA + . + end source code control +}}} + +Package=<3> +{{{ +}}} + +############################################################################### + diff --git a/src/vscript/squirrel/squirrel/CMakeLists.txt b/src/vscript/squirrel/squirrel/CMakeLists.txt new file mode 100644 index 00000000..f96b9584 --- /dev/null +++ b/src/vscript/squirrel/squirrel/CMakeLists.txt @@ -0,0 +1,55 @@ +set(SQUIRREL_SRC sqapi.cpp + sqbaselib.cpp + sqclass.cpp + sqcompiler.cpp + sqdebug.cpp + sqfuncstate.cpp + sqlexer.cpp + sqmem.cpp + sqobject.cpp + sqstate.cpp + sqtable.cpp + sqvm.cpp) + +if(NOT DISABLE_DYNAMIC) + add_library(squirrel SHARED ${SQUIRREL_SRC}) + add_library(squirrel::squirrel ALIAS squirrel) + set_property(TARGET squirrel PROPERTY EXPORT_NAME squirrel) + if(NOT SQ_DISABLE_INSTALLER) + install(TARGETS squirrel EXPORT squirrel + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT Libraries + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT Libraries NAMELINK_SKIP + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT Libraries + ) + install(TARGETS squirrel EXPORT squirrel + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT Development NAMELINK_ONLY + ) + endif() + target_include_directories(squirrel PUBLIC + "$" + "$" + ) +endif() + +if(NOT DISABLE_STATIC) + add_library(squirrel_static STATIC ${SQUIRREL_SRC}) + add_library(squirrel::squirrel_static ALIAS squirrel_static) + set_property(TARGET squirrel_static PROPERTY EXPORT_NAME squirrel_static) + if(NOT SQ_DISABLE_INSTALLER) + install(TARGETS squirrel_static EXPORT squirrel ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT Development) + endif() + target_include_directories(squirrel_static PUBLIC + "$" + "$" + ) +endif() + +if(LONG_OUTPUT_NAMES) + if(NOT DISABLE_DYNAMIC) + set_target_properties(squirrel PROPERTIES OUTPUT_NAME squirrel3) + endif() + + if(NOT DISABLE_STATIC) + set_target_properties(squirrel_static PROPERTIES OUTPUT_NAME squirrel3_static) + endif() +endif() diff --git a/src/vscript/squirrel/squirrel/Makefile b/src/vscript/squirrel/squirrel/Makefile new file mode 100644 index 00000000..2422a834 --- /dev/null +++ b/src/vscript/squirrel/squirrel/Makefile @@ -0,0 +1,53 @@ +SQUIRREL= .. + + +CC?= gcc +OUT?= $(SQUIRREL)/lib/libsquirrel.a +INCZ?= -I$(SQUIRREL)/include -I. -Iinclude +DEFS= $(CC_EXTRA_FLAGS) +LIB= + +OBJS= \ + sqapi.o \ + sqbaselib.o \ + sqfuncstate.o \ + sqdebug.o \ + sqlexer.o \ + sqobject.o \ + sqcompiler.o \ + sqstate.o \ + sqtable.o \ + sqmem.o \ + sqvm.o \ + sqclass.o + +SRCS= \ + sqapi.cpp \ + sqbaselib.cpp \ + sqfuncstate.cpp \ + sqdebug.cpp \ + sqlexer.cpp \ + sqobject.cpp \ + sqcompiler.cpp \ + sqstate.cpp \ + sqtable.cpp \ + sqmem.cpp \ + sqvm.cpp \ + sqclass.cpp + + + +sq32: + $(CC) -O2 -fno-exceptions -fno-rtti -Wall -fno-strict-aliasing -c $(SRCS) $(INCZ) $(DEFS) + ar rc $(OUT) *.o + rm *.o + +sqprof: + $(CC) -O2 -pg -fno-exceptions -fno-rtti -pie -gstabs -g3 -Wall -fno-strict-aliasing -c $(SRCS) $(INCZ) $(DEFS) + ar rc $(OUT) *.o + rm *.o + +sq64: + $(CC) -O2 -m64 -D_SQ64 -fno-exceptions -fno-rtti -Wall -fno-strict-aliasing -c $(SRCS) $(INCZ) $(DEFS) + ar rc $(OUT) *.o + rm *.o diff --git a/src/vscript/squirrel/squirrel/sqapi.cpp b/src/vscript/squirrel/squirrel/sqapi.cpp new file mode 100644 index 00000000..a3221503 --- /dev/null +++ b/src/vscript/squirrel/squirrel/sqapi.cpp @@ -0,0 +1,1631 @@ +/* + see copyright notice in squirrel.h +*/ +#include "sqpcheader.h" +#include "sqvm.h" +#include "sqstring.h" +#include "sqtable.h" +#include "sqarray.h" +#include "sqfuncproto.h" +#include "sqclosure.h" +#include "squserdata.h" +#include "sqcompiler.h" +#include "sqfuncstate.h" +#include "sqclass.h" + +static bool sq_aux_gettypedarg(HSQUIRRELVM v,SQInteger idx,SQObjectType type,SQObjectPtr **o) +{ + *o = &stack_get(v,idx); + if(sq_type(**o) != type){ + SQObjectPtr oval = v->PrintObjVal(**o); + v->Raise_Error(_SC("wrong argument type, expected '%s' got '%.50s'"),IdType2Name(type),_stringval(oval)); + return false; + } + return true; +} + +#define _GETSAFE_OBJ(v,idx,type,o) { if(!sq_aux_gettypedarg(v,idx,type,&o)) return SQ_ERROR; } + +#define sq_aux_paramscheck(v,count) \ +{ \ + if(sq_gettop(v) < count){ v->Raise_Error(_SC("not enough params in the stack")); return SQ_ERROR; }\ +} + + +SQInteger sq_aux_invalidtype(HSQUIRRELVM v,SQObjectType type) +{ + SQUnsignedInteger buf_size = 100 *sizeof(SQChar); + scsprintf(_ss(v)->GetScratchPad(buf_size), buf_size, _SC("unexpected type %s"), IdType2Name(type)); + return sq_throwerror(v, _ss(v)->GetScratchPad(-1)); +} + +HSQUIRRELVM sq_open(SQInteger initialstacksize) +{ + SQSharedState *ss; + SQVM *v; + sq_new(ss, SQSharedState); + ss->Init(); + v = (SQVM *)SQ_MALLOC(sizeof(SQVM)); + new (v) SQVM(ss); + ss->_root_vm = v; + if(v->Init(NULL, initialstacksize)) { + return v; + } else { + sq_delete(v, SQVM); + return NULL; + } + return v; +} + +HSQUIRRELVM sq_newthread(HSQUIRRELVM friendvm, SQInteger initialstacksize) +{ + SQSharedState *ss; + SQVM *v; + ss=_ss(friendvm); + + v= (SQVM *)SQ_MALLOC(sizeof(SQVM)); + new (v) SQVM(ss); + + if(v->Init(friendvm, initialstacksize)) { + friendvm->Push(v); + return v; + } else { + sq_delete(v, SQVM); + return NULL; + } +} + +SQInteger sq_getvmstate(HSQUIRRELVM v) +{ + if(v->_suspended) + return SQ_VMSTATE_SUSPENDED; + else { + if(v->_callsstacksize != 0) return SQ_VMSTATE_RUNNING; + else return SQ_VMSTATE_IDLE; + } +} + +void sq_seterrorhandler(HSQUIRRELVM v) +{ + SQObject o = stack_get(v, -1); + if(sq_isclosure(o) || sq_isnativeclosure(o) || sq_isnull(o)) { + v->_errorhandler = o; + v->Pop(); + } +} + +void sq_setnativedebughook(HSQUIRRELVM v,SQDEBUGHOOK hook) +{ + v->_debughook_native = hook; + v->_debughook_closure.Null(); + v->_debughook = hook?true:false; +} + +void sq_setdebughook(HSQUIRRELVM v) +{ + SQObject o = stack_get(v,-1); + if(sq_isclosure(o) || sq_isnativeclosure(o) || sq_isnull(o)) { + v->_debughook_closure = o; + v->_debughook_native = NULL; + v->_debughook = !sq_isnull(o); + v->Pop(); + } +} + +void sq_close(HSQUIRRELVM v) +{ + SQSharedState *ss = _ss(v); + _thread(ss->_root_vm)->Finalize(); + sq_delete(ss, SQSharedState); +} + +SQInteger sq_getversion() +{ + return SQUIRREL_VERSION_NUMBER; +} + +SQRESULT sq_compile(HSQUIRRELVM v,SQLEXREADFUNC read,SQUserPointer p,const SQChar *sourcename,SQBool raiseerror) +{ + SQObjectPtr o; +#ifndef NO_COMPILER + if(Compile(v, read, p, sourcename, o, raiseerror?true:false, _ss(v)->_debuginfo)) { + v->Push(SQClosure::Create(_ss(v), _funcproto(o), _table(v->_roottable)->GetWeakRef(OT_TABLE))); + return SQ_OK; + } + return SQ_ERROR; +#else + return sq_throwerror(v,_SC("this is a no compiler build")); +#endif +} + +void sq_enabledebuginfo(HSQUIRRELVM v, SQBool enable) +{ + _ss(v)->_debuginfo = enable?true:false; +} + +void sq_notifyallexceptions(HSQUIRRELVM v, SQBool enable) +{ + _ss(v)->_notifyallexceptions = enable?true:false; +} + +void sq_addref(HSQUIRRELVM v,HSQOBJECT *po) +{ + if(!ISREFCOUNTED(sq_type(*po))) return; +#ifdef NO_GARBAGE_COLLECTOR + __AddRef(po->_type,po->_unVal); +#else + _ss(v)->_refs_table.AddRef(*po); +#endif +} + +SQUnsignedInteger sq_getrefcount(HSQUIRRELVM v,HSQOBJECT *po) +{ + if(!ISREFCOUNTED(sq_type(*po))) return 0; +#ifdef NO_GARBAGE_COLLECTOR + return po->_unVal.pRefCounted->_uiRef; +#else + return _ss(v)->_refs_table.GetRefCount(*po); +#endif +} + +SQBool sq_release(HSQUIRRELVM v,HSQOBJECT *po) +{ + if(!ISREFCOUNTED(sq_type(*po))) return SQTrue; +#ifdef NO_GARBAGE_COLLECTOR + bool ret = (po->_unVal.pRefCounted->_uiRef <= 1) ? SQTrue : SQFalse; + __Release(po->_type,po->_unVal); + return ret; //the ret val doesn't work(and cannot be fixed) +#else + return _ss(v)->_refs_table.Release(*po); +#endif +} + +SQUnsignedInteger sq_getvmrefcount(HSQUIRRELVM SQ_UNUSED_ARG(v), const HSQOBJECT *po) +{ + if (!ISREFCOUNTED(sq_type(*po))) return 0; + return po->_unVal.pRefCounted->_uiRef; +} + +const SQChar *sq_objtostring(const HSQOBJECT *o) +{ + if(sq_type(*o) == OT_STRING) { + return _stringval(*o); + } + return NULL; +} + +SQInteger sq_objtointeger(const HSQOBJECT *o) +{ + if(sq_isnumeric(*o)) { + return tointeger(*o); + } + return 0; +} + +SQFloat sq_objtofloat(const HSQOBJECT *o) +{ + if(sq_isnumeric(*o)) { + return tofloat(*o); + } + return 0; +} + +SQBool sq_objtobool(const HSQOBJECT *o) +{ + if(sq_isbool(*o)) { + return _integer(*o); + } + return SQFalse; +} + +SQUserPointer sq_objtouserpointer(const HSQOBJECT *o) +{ + if(sq_isuserpointer(*o)) { + return _userpointer(*o); + } + return 0; +} + +void sq_pushnull(HSQUIRRELVM v) +{ + v->PushNull(); +} + +void sq_pushstring(HSQUIRRELVM v,const SQChar *s,SQInteger len) +{ + if(s) + v->Push(SQObjectPtr(SQString::Create(_ss(v), s, len))); + else v->PushNull(); +} + +void sq_pushinteger(HSQUIRRELVM v,SQInteger n) +{ + v->Push(n); +} + +void sq_pushbool(HSQUIRRELVM v,SQBool b) +{ + v->Push(b?true:false); +} + +void sq_pushfloat(HSQUIRRELVM v,SQFloat n) +{ + v->Push(n); +} + +void sq_pushuserpointer(HSQUIRRELVM v,SQUserPointer p) +{ + v->Push(p); +} + +void sq_pushthread(HSQUIRRELVM v, HSQUIRRELVM thread) +{ + v->Push(thread); +} + +SQUserPointer sq_newuserdata(HSQUIRRELVM v,SQUnsignedInteger size) +{ + SQUserData *ud = SQUserData::Create(_ss(v), size + SQ_ALIGNMENT); + v->Push(ud); + return (SQUserPointer)sq_aligning(ud + 1); +} + +void sq_newtable(HSQUIRRELVM v) +{ + v->Push(SQTable::Create(_ss(v), 0)); +} + +void sq_newtableex(HSQUIRRELVM v,SQInteger initialcapacity) +{ + v->Push(SQTable::Create(_ss(v), initialcapacity)); +} + +void sq_newarray(HSQUIRRELVM v,SQInteger size) +{ + v->Push(SQArray::Create(_ss(v), size)); +} + +SQRESULT sq_newclass(HSQUIRRELVM v,SQBool hasbase) +{ + SQClass *baseclass = NULL; + if(hasbase) { + SQObjectPtr &base = stack_get(v,-1); + if(sq_type(base) != OT_CLASS) + return sq_throwerror(v,_SC("invalid base type")); + baseclass = _class(base); + } + SQClass *newclass = SQClass::Create(_ss(v), baseclass); + if(baseclass) v->Pop(); + v->Push(newclass); + return SQ_OK; +} + +SQBool sq_instanceof(HSQUIRRELVM v) +{ + SQObjectPtr &inst = stack_get(v,-1); + SQObjectPtr &cl = stack_get(v,-2); + if(sq_type(inst) != OT_INSTANCE || sq_type(cl) != OT_CLASS) + return sq_throwerror(v,_SC("invalid param type")); + return _instance(inst)->InstanceOf(_class(cl))?SQTrue:SQFalse; +} + +SQRESULT sq_arrayappend(HSQUIRRELVM v,SQInteger idx) +{ + sq_aux_paramscheck(v,2); + SQObjectPtr *arr; + _GETSAFE_OBJ(v, idx, OT_ARRAY,arr); + _array(*arr)->Append(v->GetUp(-1)); + v->Pop(); + return SQ_OK; +} + +SQRESULT sq_arraypop(HSQUIRRELVM v,SQInteger idx,SQBool pushval) +{ + sq_aux_paramscheck(v, 1); + SQObjectPtr *arr; + _GETSAFE_OBJ(v, idx, OT_ARRAY,arr); + if(_array(*arr)->Size() > 0) { + if(pushval != 0){ v->Push(_array(*arr)->Top()); } + _array(*arr)->Pop(); + return SQ_OK; + } + return sq_throwerror(v, _SC("empty array")); +} + +SQRESULT sq_arrayresize(HSQUIRRELVM v,SQInteger idx,SQInteger newsize) +{ + sq_aux_paramscheck(v,1); + SQObjectPtr *arr; + _GETSAFE_OBJ(v, idx, OT_ARRAY,arr); + if(newsize >= 0) { + _array(*arr)->Resize(newsize); + return SQ_OK; + } + return sq_throwerror(v,_SC("negative size")); +} + + +SQRESULT sq_arrayreverse(HSQUIRRELVM v,SQInteger idx) +{ + sq_aux_paramscheck(v, 1); + SQObjectPtr *o; + _GETSAFE_OBJ(v, idx, OT_ARRAY,o); + SQArray *arr = _array(*o); + if(arr->Size() > 0) { + SQObjectPtr t; + SQInteger size = arr->Size(); + SQInteger n = size >> 1; size -= 1; + for(SQInteger i = 0; i < n; i++) { + t = arr->_values[i]; + arr->_values[i] = arr->_values[size-i]; + arr->_values[size-i] = t; + } + return SQ_OK; + } + return SQ_OK; +} + +SQRESULT sq_arrayremove(HSQUIRRELVM v,SQInteger idx,SQInteger itemidx) +{ + sq_aux_paramscheck(v, 1); + SQObjectPtr *arr; + _GETSAFE_OBJ(v, idx, OT_ARRAY,arr); + return _array(*arr)->Remove(itemidx) ? SQ_OK : sq_throwerror(v,_SC("index out of range")); +} + +SQRESULT sq_arrayinsert(HSQUIRRELVM v,SQInteger idx,SQInteger destpos) +{ + sq_aux_paramscheck(v, 1); + SQObjectPtr *arr; + _GETSAFE_OBJ(v, idx, OT_ARRAY,arr); + SQRESULT ret = _array(*arr)->Insert(destpos, v->GetUp(-1)) ? SQ_OK : sq_throwerror(v,_SC("index out of range")); + v->Pop(); + return ret; +} + +void sq_newclosure(HSQUIRRELVM v,SQFUNCTION func,SQUnsignedInteger nfreevars) +{ + SQNativeClosure *nc = SQNativeClosure::Create(_ss(v), func,nfreevars); + nc->_nparamscheck = 0; + for(SQUnsignedInteger i = 0; i < nfreevars; i++) { + nc->_outervalues[i] = v->Top(); + v->Pop(); + } + v->Push(SQObjectPtr(nc)); +} + +SQRESULT sq_getclosureinfo(HSQUIRRELVM v,SQInteger idx,SQInteger *nparams,SQInteger *nfreevars) +{ + SQObject o = stack_get(v, idx); + if(sq_type(o) == OT_CLOSURE) { + SQClosure *c = _closure(o); + SQFunctionProto *proto = c->_function; + *nparams = proto->_nparameters; + *nfreevars = proto->_noutervalues; + return SQ_OK; + } + else if(sq_type(o) == OT_NATIVECLOSURE) + { + SQNativeClosure *c = _nativeclosure(o); + *nparams = c->_nparamscheck; + *nfreevars = (SQInteger)c->_noutervalues; + return SQ_OK; + } + return sq_throwerror(v,_SC("the object is not a closure")); +} + +SQRESULT sq_setnativeclosurename(HSQUIRRELVM v,SQInteger idx,const SQChar *name) +{ + SQObject o = stack_get(v, idx); + if(sq_isnativeclosure(o)) { + SQNativeClosure *nc = _nativeclosure(o); + nc->_name = SQString::Create(_ss(v),name); + return SQ_OK; + } + return sq_throwerror(v,_SC("the object is not a nativeclosure")); +} + +SQRESULT sq_setparamscheck(HSQUIRRELVM v,SQInteger nparamscheck,const SQChar *typemask) +{ + SQObject o = stack_get(v, -1); + if(!sq_isnativeclosure(o)) + return sq_throwerror(v, _SC("native closure expected")); + SQNativeClosure *nc = _nativeclosure(o); + nc->_nparamscheck = nparamscheck; + if(typemask) { + SQIntVec res; + if(!CompileTypemask(res, typemask)) + return sq_throwerror(v, _SC("invalid typemask")); + nc->_typecheck.copy(res); + } + else { + nc->_typecheck.resize(0); + } + if(nparamscheck == SQ_MATCHTYPEMASKSTRING) { + nc->_nparamscheck = nc->_typecheck.size(); + } + return SQ_OK; +} + +SQRESULT sq_bindenv(HSQUIRRELVM v,SQInteger idx) +{ + SQObjectPtr &o = stack_get(v,idx); + if(!sq_isnativeclosure(o) && + !sq_isclosure(o)) + return sq_throwerror(v,_SC("the target is not a closure")); + SQObjectPtr &env = stack_get(v,-1); + if(!sq_istable(env) && + !sq_isarray(env) && + !sq_isclass(env) && + !sq_isinstance(env)) + return sq_throwerror(v,_SC("invalid environment")); + SQWeakRef *w = _refcounted(env)->GetWeakRef(sq_type(env)); + SQObjectPtr ret; + if(sq_isclosure(o)) { + SQClosure *c = _closure(o)->Clone(); + __ObjRelease(c->_env); + c->_env = w; + __ObjAddRef(c->_env); + if(_closure(o)->_base) { + c->_base = _closure(o)->_base; + __ObjAddRef(c->_base); + } + ret = c; + } + else { //then must be a native closure + SQNativeClosure *c = _nativeclosure(o)->Clone(); + __ObjRelease(c->_env); + c->_env = w; + __ObjAddRef(c->_env); + ret = c; + } + v->Pop(); + v->Push(ret); + return SQ_OK; +} + +SQRESULT sq_getclosurename(HSQUIRRELVM v,SQInteger idx) +{ + SQObjectPtr &o = stack_get(v,idx); + if(!sq_isnativeclosure(o) && + !sq_isclosure(o)) + return sq_throwerror(v,_SC("the target is not a closure")); + if(sq_isnativeclosure(o)) + { + v->Push(_nativeclosure(o)->_name); + } + else { //closure + v->Push(_closure(o)->_function->_name); + } + return SQ_OK; +} + +SQRESULT sq_setclosureroot(HSQUIRRELVM v,SQInteger idx) +{ + SQObjectPtr &c = stack_get(v,idx); + SQObject o = stack_get(v, -1); + if(!sq_isclosure(c)) return sq_throwerror(v, _SC("closure expected")); + if(sq_istable(o)) { + _closure(c)->SetRoot(_table(o)->GetWeakRef(OT_TABLE)); + v->Pop(); + return SQ_OK; + } + return sq_throwerror(v, _SC("invalid type")); +} + +SQRESULT sq_getclosureroot(HSQUIRRELVM v,SQInteger idx) +{ + SQObjectPtr &c = stack_get(v,idx); + if(!sq_isclosure(c)) return sq_throwerror(v, _SC("closure expected")); + v->Push(_closure(c)->_root->_obj); + return SQ_OK; +} + +SQRESULT sq_clear(HSQUIRRELVM v,SQInteger idx) +{ + SQObject &o=stack_get(v,idx); + switch(sq_type(o)) { + case OT_TABLE: _table(o)->Clear(); break; + case OT_ARRAY: _array(o)->Resize(0); break; + default: + return sq_throwerror(v, _SC("clear only works on table and array")); + break; + + } + return SQ_OK; +} + +void sq_pushroottable(HSQUIRRELVM v) +{ + v->Push(v->_roottable); +} + +void sq_pushregistrytable(HSQUIRRELVM v) +{ + v->Push(_ss(v)->_registry); +} + +void sq_pushconsttable(HSQUIRRELVM v) +{ + v->Push(_ss(v)->_consts); +} + +SQRESULT sq_setroottable(HSQUIRRELVM v) +{ + SQObject o = stack_get(v, -1); + if(sq_istable(o) || sq_isnull(o)) { + v->_roottable = o; + v->Pop(); + return SQ_OK; + } + return sq_throwerror(v, _SC("invalid type")); +} + +SQRESULT sq_setconsttable(HSQUIRRELVM v) +{ + SQObject o = stack_get(v, -1); + if(sq_istable(o)) { + _ss(v)->_consts = o; + v->Pop(); + return SQ_OK; + } + return sq_throwerror(v, _SC("invalid type, expected table")); +} + +void sq_setforeignptr(HSQUIRRELVM v,SQUserPointer p) +{ + v->_foreignptr = p; +} + +SQUserPointer sq_getforeignptr(HSQUIRRELVM v) +{ + return v->_foreignptr; +} + +void sq_setsharedforeignptr(HSQUIRRELVM v,SQUserPointer p) +{ + _ss(v)->_foreignptr = p; +} + +SQUserPointer sq_getsharedforeignptr(HSQUIRRELVM v) +{ + return _ss(v)->_foreignptr; +} + +void sq_setvmreleasehook(HSQUIRRELVM v,SQRELEASEHOOK hook) +{ + v->_releasehook = hook; +} + +SQRELEASEHOOK sq_getvmreleasehook(HSQUIRRELVM v) +{ + return v->_releasehook; +} + +void sq_setsharedreleasehook(HSQUIRRELVM v,SQRELEASEHOOK hook) +{ + _ss(v)->_releasehook = hook; +} + +SQRELEASEHOOK sq_getsharedreleasehook(HSQUIRRELVM v) +{ + return _ss(v)->_releasehook; +} + +void sq_push(HSQUIRRELVM v,SQInteger idx) +{ + v->Push(stack_get(v, idx)); +} + +SQObjectType sq_gettype(HSQUIRRELVM v,SQInteger idx) +{ + return sq_type(stack_get(v, idx)); +} + +SQRESULT sq_typeof(HSQUIRRELVM v,SQInteger idx) +{ + SQObjectPtr &o = stack_get(v, idx); + SQObjectPtr res; + if(!v->TypeOf(o,res)) { + return SQ_ERROR; + } + v->Push(res); + return SQ_OK; +} + +SQRESULT sq_tostring(HSQUIRRELVM v,SQInteger idx) +{ + SQObjectPtr &o = stack_get(v, idx); + SQObjectPtr res; + if(!v->ToString(o,res)) { + return SQ_ERROR; + } + v->Push(res); + return SQ_OK; +} + +void sq_tobool(HSQUIRRELVM v, SQInteger idx, SQBool *b) +{ + SQObjectPtr &o = stack_get(v, idx); + *b = SQVM::IsFalse(o)?SQFalse:SQTrue; +} + +SQRESULT sq_getinteger(HSQUIRRELVM v,SQInteger idx,SQInteger *i) +{ + SQObjectPtr &o = stack_get(v, idx); + if(sq_isnumeric(o)) { + *i = tointeger(o); + return SQ_OK; + } + if(sq_isbool(o)) { + *i = SQVM::IsFalse(o)?SQFalse:SQTrue; + return SQ_OK; + } + return SQ_ERROR; +} + +SQRESULT sq_getfloat(HSQUIRRELVM v,SQInteger idx,SQFloat *f) +{ + SQObjectPtr &o = stack_get(v, idx); + if(sq_isnumeric(o)) { + *f = tofloat(o); + return SQ_OK; + } + return SQ_ERROR; +} + +SQRESULT sq_getbool(HSQUIRRELVM v,SQInteger idx,SQBool *b) +{ + SQObjectPtr &o = stack_get(v, idx); + if(sq_isbool(o)) { + *b = _integer(o); + return SQ_OK; + } + return SQ_ERROR; +} + +SQRESULT sq_getstringandsize(HSQUIRRELVM v,SQInteger idx,const SQChar **c,SQInteger *size) +{ + SQObjectPtr *o = NULL; + _GETSAFE_OBJ(v, idx, OT_STRING,o); + *c = _stringval(*o); + *size = _string(*o)->_len; + return SQ_OK; +} + +SQRESULT sq_getstring(HSQUIRRELVM v,SQInteger idx,const SQChar **c) +{ + SQObjectPtr *o = NULL; + _GETSAFE_OBJ(v, idx, OT_STRING,o); + *c = _stringval(*o); + return SQ_OK; +} + +SQRESULT sq_getthread(HSQUIRRELVM v,SQInteger idx,HSQUIRRELVM *thread) +{ + SQObjectPtr *o = NULL; + _GETSAFE_OBJ(v, idx, OT_THREAD,o); + *thread = _thread(*o); + return SQ_OK; +} + +SQRESULT sq_clone(HSQUIRRELVM v,SQInteger idx) +{ + SQObjectPtr &o = stack_get(v,idx); + v->PushNull(); + if(!v->Clone(o, stack_get(v, -1))){ + v->Pop(); + return SQ_ERROR; + } + return SQ_OK; +} + +SQInteger sq_getsize(HSQUIRRELVM v, SQInteger idx) +{ + SQObjectPtr &o = stack_get(v, idx); + SQObjectType type = sq_type(o); + switch(type) { + case OT_STRING: return _string(o)->_len; + case OT_TABLE: return _table(o)->CountUsed(); + case OT_ARRAY: return _array(o)->Size(); + case OT_USERDATA: return _userdata(o)->_size; + case OT_INSTANCE: return _instance(o)->_class->_udsize; + case OT_CLASS: return _class(o)->_udsize; + default: + return sq_aux_invalidtype(v, type); + } +} + +SQHash sq_gethash(HSQUIRRELVM v, SQInteger idx) +{ + SQObjectPtr &o = stack_get(v, idx); + return HashObj(o); +} + +SQRESULT sq_getuserdata(HSQUIRRELVM v,SQInteger idx,SQUserPointer *p,SQUserPointer *typetag) +{ + SQObjectPtr *o = NULL; + _GETSAFE_OBJ(v, idx, OT_USERDATA,o); + (*p) = _userdataval(*o); + if(typetag) *typetag = _userdata(*o)->_typetag; + return SQ_OK; +} + +SQRESULT sq_settypetag(HSQUIRRELVM v,SQInteger idx,SQUserPointer typetag) +{ + SQObjectPtr &o = stack_get(v,idx); + switch(sq_type(o)) { + case OT_USERDATA: _userdata(o)->_typetag = typetag; break; + case OT_CLASS: _class(o)->_typetag = typetag; break; + default: return sq_throwerror(v,_SC("invalid object type")); + } + return SQ_OK; +} + +SQRESULT sq_getobjtypetag(const HSQOBJECT *o,SQUserPointer * typetag) +{ + switch(sq_type(*o)) { + case OT_INSTANCE: *typetag = _instance(*o)->_class->_typetag; break; + case OT_USERDATA: *typetag = _userdata(*o)->_typetag; break; + case OT_CLASS: *typetag = _class(*o)->_typetag; break; + default: return SQ_ERROR; + } + return SQ_OK; +} + +SQRESULT sq_gettypetag(HSQUIRRELVM v,SQInteger idx,SQUserPointer *typetag) +{ + SQObjectPtr &o = stack_get(v,idx); + if (SQ_FAILED(sq_getobjtypetag(&o, typetag))) + return SQ_ERROR;// this is not an error it should be a bool but would break backward compatibility + return SQ_OK; +} + +SQRESULT sq_getuserpointer(HSQUIRRELVM v, SQInteger idx, SQUserPointer *p) +{ + SQObjectPtr *o = NULL; + _GETSAFE_OBJ(v, idx, OT_USERPOINTER,o); + (*p) = _userpointer(*o); + return SQ_OK; +} + +SQRESULT sq_setinstanceup(HSQUIRRELVM v, SQInteger idx, SQUserPointer p) +{ + SQObjectPtr &o = stack_get(v,idx); + if(sq_type(o) != OT_INSTANCE) return sq_throwerror(v,_SC("the object is not a class instance")); + _instance(o)->_userpointer = p; + return SQ_OK; +} + +SQRESULT sq_setclassudsize(HSQUIRRELVM v, SQInteger idx, SQInteger udsize) +{ + SQObjectPtr &o = stack_get(v,idx); + if(sq_type(o) != OT_CLASS) return sq_throwerror(v,_SC("the object is not a class")); + if(_class(o)->_locked) return sq_throwerror(v,_SC("the class is locked")); + _class(o)->_udsize = udsize; + return SQ_OK; +} + + +SQRESULT sq_getinstanceup(HSQUIRRELVM v, SQInteger idx, SQUserPointer *p,SQUserPointer typetag) +{ + SQObjectPtr &o = stack_get(v,idx); + if(sq_type(o) != OT_INSTANCE) return sq_throwerror(v,_SC("the object is not a class instance")); + (*p) = _instance(o)->_userpointer; + if(typetag != 0) { + SQClass *cl = _instance(o)->_class; + do{ + if(cl->_typetag == typetag) + return SQ_OK; + cl = cl->_base; + }while(cl != NULL); + return sq_throwerror(v,_SC("invalid type tag")); + } + return SQ_OK; +} + +SQInteger sq_gettop(HSQUIRRELVM v) +{ + return (v->_top) - v->_stackbase; +} + +void sq_settop(HSQUIRRELVM v, SQInteger newtop) +{ + SQInteger top = sq_gettop(v); + if(top > newtop) + sq_pop(v, top - newtop); + else + while(top++ < newtop) sq_pushnull(v); +} + +void sq_pop(HSQUIRRELVM v, SQInteger nelemstopop) +{ + assert(v->_top >= nelemstopop); + v->Pop(nelemstopop); +} + +void sq_poptop(HSQUIRRELVM v) +{ + assert(v->_top >= 1); + v->Pop(); +} + + +void sq_remove(HSQUIRRELVM v, SQInteger idx) +{ + v->Remove(idx); +} + +SQInteger sq_cmp(HSQUIRRELVM v) +{ + SQInteger res; + v->ObjCmp(stack_get(v, -1), stack_get(v, -2),res); + return res; +} + +SQRESULT sq_newslot(HSQUIRRELVM v, SQInteger idx, SQBool bstatic) +{ + sq_aux_paramscheck(v, 3); + SQObjectPtr &self = stack_get(v, idx); + if(sq_type(self) == OT_TABLE || sq_type(self) == OT_CLASS) { + SQObjectPtr &key = v->GetUp(-2); + if(sq_type(key) == OT_NULL) return sq_throwerror(v, _SC("null is not a valid key")); + v->NewSlot(self, key, v->GetUp(-1),bstatic?true:false); + v->Pop(2); + } + return SQ_OK; +} + +SQRESULT sq_deleteslot(HSQUIRRELVM v,SQInteger idx,SQBool pushval) +{ + sq_aux_paramscheck(v, 2); + SQObjectPtr *self; + _GETSAFE_OBJ(v, idx, OT_TABLE,self); + SQObjectPtr &key = v->GetUp(-1); + if(sq_type(key) == OT_NULL) return sq_throwerror(v, _SC("null is not a valid key")); + SQObjectPtr res; + if(!v->DeleteSlot(*self, key, res)){ + v->Pop(); + return SQ_ERROR; + } + if(pushval) v->GetUp(-1) = res; + else v->Pop(); + return SQ_OK; +} + +SQRESULT sq_set(HSQUIRRELVM v,SQInteger idx) +{ + SQObjectPtr &self = stack_get(v, idx); + if(v->Set(self, v->GetUp(-2), v->GetUp(-1),DONT_FALL_BACK)) { + v->Pop(2); + return SQ_OK; + } + return SQ_ERROR; +} + +SQRESULT sq_rawset(HSQUIRRELVM v,SQInteger idx) +{ + SQObjectPtr &self = stack_get(v, idx); + SQObjectPtr &key = v->GetUp(-2); + if(sq_type(key) == OT_NULL) { + v->Pop(2); + return sq_throwerror(v, _SC("null key")); + } + switch(sq_type(self)) { + case OT_TABLE: + _table(self)->NewSlot(key, v->GetUp(-1)); + v->Pop(2); + return SQ_OK; + break; + case OT_CLASS: + _class(self)->NewSlot(_ss(v), key, v->GetUp(-1),false); + v->Pop(2); + return SQ_OK; + break; + case OT_INSTANCE: + if(_instance(self)->Set(key, v->GetUp(-1))) { + v->Pop(2); + return SQ_OK; + } + break; + case OT_ARRAY: + if(v->Set(self, key, v->GetUp(-1),false)) { + v->Pop(2); + return SQ_OK; + } + break; + default: + v->Pop(2); + return sq_throwerror(v, _SC("rawset works only on array/table/class and instance")); + } + v->Raise_IdxError(v->GetUp(-2));return SQ_ERROR; +} + +SQRESULT sq_newmember(HSQUIRRELVM v,SQInteger idx,SQBool bstatic) +{ + SQObjectPtr &self = stack_get(v, idx); + if(sq_type(self) != OT_CLASS) return sq_throwerror(v, _SC("new member only works with classes")); + SQObjectPtr &key = v->GetUp(-3); + if(sq_type(key) == OT_NULL) return sq_throwerror(v, _SC("null key")); + if(!v->NewSlotA(self,key,v->GetUp(-2),v->GetUp(-1),bstatic?true:false,false)) { + v->Pop(3); + return SQ_ERROR; + } + v->Pop(3); + return SQ_OK; +} + +SQRESULT sq_rawnewmember(HSQUIRRELVM v,SQInteger idx,SQBool bstatic) +{ + SQObjectPtr &self = stack_get(v, idx); + if(sq_type(self) != OT_CLASS) return sq_throwerror(v, _SC("new member only works with classes")); + SQObjectPtr &key = v->GetUp(-3); + if(sq_type(key) == OT_NULL) return sq_throwerror(v, _SC("null key")); + if(!v->NewSlotA(self,key,v->GetUp(-2),v->GetUp(-1),bstatic?true:false,true)) { + v->Pop(3); + return SQ_ERROR; + } + v->Pop(3); + return SQ_OK; +} + +SQRESULT sq_setdelegate(HSQUIRRELVM v,SQInteger idx) +{ + SQObjectPtr &self = stack_get(v, idx); + SQObjectPtr &mt = v->GetUp(-1); + SQObjectType type = sq_type(self); + switch(type) { + case OT_TABLE: + if(sq_type(mt) == OT_TABLE) { + if(!_table(self)->SetDelegate(_table(mt))) { + return sq_throwerror(v, _SC("delegate cycle")); + } + v->Pop(); + } + else if(sq_type(mt)==OT_NULL) { + _table(self)->SetDelegate(NULL); v->Pop(); } + else return sq_aux_invalidtype(v,type); + break; + case OT_USERDATA: + if(sq_type(mt)==OT_TABLE) { + _userdata(self)->SetDelegate(_table(mt)); v->Pop(); } + else if(sq_type(mt)==OT_NULL) { + _userdata(self)->SetDelegate(NULL); v->Pop(); } + else return sq_aux_invalidtype(v, type); + break; + default: + return sq_aux_invalidtype(v, type); + break; + } + return SQ_OK; +} + +SQRESULT sq_rawdeleteslot(HSQUIRRELVM v,SQInteger idx,SQBool pushval) +{ + sq_aux_paramscheck(v, 2); + SQObjectPtr *self; + _GETSAFE_OBJ(v, idx, OT_TABLE,self); + SQObjectPtr &key = v->GetUp(-1); + SQObjectPtr t; + if(_table(*self)->Get(key,t)) { + _table(*self)->Remove(key); + } + if(pushval != 0) + v->GetUp(-1) = t; + else + v->Pop(); + return SQ_OK; +} + +SQRESULT sq_getdelegate(HSQUIRRELVM v,SQInteger idx) +{ + SQObjectPtr &self=stack_get(v,idx); + switch(sq_type(self)){ + case OT_TABLE: + case OT_USERDATA: + if(!_delegable(self)->_delegate){ + v->PushNull(); + break; + } + v->Push(SQObjectPtr(_delegable(self)->_delegate)); + break; + default: return sq_throwerror(v,_SC("wrong type")); break; + } + return SQ_OK; + +} + +SQRESULT sq_get(HSQUIRRELVM v,SQInteger idx) +{ + SQObjectPtr &self=stack_get(v,idx); + SQObjectPtr &obj = v->GetUp(-1); + if(v->Get(self,obj,obj,false,DONT_FALL_BACK)) + return SQ_OK; + v->Pop(); + return SQ_ERROR; +} + +SQRESULT sq_rawget(HSQUIRRELVM v,SQInteger idx) +{ + SQObjectPtr &self=stack_get(v,idx); + SQObjectPtr &obj = v->GetUp(-1); + switch(sq_type(self)) { + case OT_TABLE: + if(_table(self)->Get(obj,obj)) + return SQ_OK; + break; + case OT_CLASS: + if(_class(self)->Get(obj,obj)) + return SQ_OK; + break; + case OT_INSTANCE: + if(_instance(self)->Get(obj,obj)) + return SQ_OK; + break; + case OT_ARRAY:{ + if(sq_isnumeric(obj)){ + if(_array(self)->Get(tointeger(obj),obj)) { + return SQ_OK; + } + } + else { + v->Pop(); + return sq_throwerror(v,_SC("invalid index type for an array")); + } + } + break; + default: + v->Pop(); + return sq_throwerror(v,_SC("rawget works only on array/table/instance and class")); + } + v->Pop(); + return sq_throwerror(v,_SC("the index doesn't exist")); +} + +SQRESULT sq_getstackobj(HSQUIRRELVM v,SQInteger idx,HSQOBJECT *po) +{ + *po=stack_get(v,idx); + return SQ_OK; +} + +const SQChar *sq_getlocal(HSQUIRRELVM v,SQUnsignedInteger level,SQUnsignedInteger idx) +{ + SQUnsignedInteger cstksize=v->_callsstacksize; + SQUnsignedInteger lvl=(cstksize-level)-1; + SQInteger stackbase=v->_stackbase; + if(lvl_callsstack[(cstksize-i)-1]; + stackbase-=ci._prevstkbase; + } + SQVM::CallInfo &ci=v->_callsstack[lvl]; + if(sq_type(ci._closure)!=OT_CLOSURE) + return NULL; + SQClosure *c=_closure(ci._closure); + SQFunctionProto *func=c->_function; + if(func->_noutervalues > (SQInteger)idx) { + v->Push(*_outer(c->_outervalues[idx])->_valptr); + return _stringval(func->_outervalues[idx]._name); + } + idx -= func->_noutervalues; + return func->GetLocal(v,stackbase,idx,(SQInteger)(ci._ip-func->_instructions)-1); + } + return NULL; +} + +void sq_pushobject(HSQUIRRELVM v,HSQOBJECT obj) +{ + v->Push(SQObjectPtr(obj)); +} + +void sq_resetobject(HSQOBJECT *po) +{ + po->_unVal.pUserPointer=NULL;po->_type=OT_NULL; +} + +SQRESULT sq_throwerror(HSQUIRRELVM v,const SQChar *err) +{ + v->_lasterror=SQString::Create(_ss(v),err); + return SQ_ERROR; +} + +SQRESULT sq_throwobject(HSQUIRRELVM v) +{ + v->_lasterror = v->GetUp(-1); + v->Pop(); + return SQ_ERROR; +} + + +void sq_reseterror(HSQUIRRELVM v) +{ + v->_lasterror.Null(); +} + +void sq_getlasterror(HSQUIRRELVM v) +{ + v->Push(v->_lasterror); +} + +SQRESULT sq_reservestack(HSQUIRRELVM v,SQInteger nsize) +{ + if (((SQUnsignedInteger)v->_top + nsize) > v->_stack.size()) { + if(v->_nmetamethodscall) { + return sq_throwerror(v,_SC("cannot resize stack while in a metamethod")); + } + v->_stack.resize(v->_stack.size() + ((v->_top + nsize) - v->_stack.size())); + } + return SQ_OK; +} + +SQRESULT sq_resume(HSQUIRRELVM v,SQBool retval,SQBool raiseerror) +{ + if (sq_type(v->GetUp(-1)) == OT_GENERATOR) + { + v->PushNull(); //retval + if (!v->Execute(v->GetUp(-2), 0, v->_top, v->GetUp(-1), raiseerror, SQVM::ET_RESUME_GENERATOR)) + {v->Raise_Error(v->_lasterror); return SQ_ERROR;} + if(!retval) + v->Pop(); + return SQ_OK; + } + return sq_throwerror(v,_SC("only generators can be resumed")); +} + +SQRESULT sq_call(HSQUIRRELVM v,SQInteger params,SQBool retval,SQBool raiseerror) +{ + SQObjectPtr res; + if(!v->Call(v->GetUp(-(params+1)),params,v->_top-params,res,raiseerror?true:false)){ + v->Pop(params); //pop args + return SQ_ERROR; + } + if(!v->_suspended) + v->Pop(params); //pop args + if(retval) + v->Push(res); // push result + return SQ_OK; +} + +SQRESULT sq_tailcall(HSQUIRRELVM v, SQInteger nparams) +{ + SQObjectPtr &res = v->GetUp(-(nparams + 1)); + if (sq_type(res) != OT_CLOSURE) { + return sq_throwerror(v, _SC("only closure can be tail called")); + } + SQClosure *clo = _closure(res); + if (clo->_function->_bgenerator) + { + return sq_throwerror(v, _SC("generators cannot be tail called")); + } + + SQInteger stackbase = (v->_top - nparams) - v->_stackbase; + if (!v->TailCall(clo, stackbase, nparams)) { + return SQ_ERROR; + } + return SQ_TAILCALL_FLAG; +} + +SQRESULT sq_suspendvm(HSQUIRRELVM v) +{ + return v->Suspend(); +} + +SQRESULT sq_wakeupvm(HSQUIRRELVM v,SQBool wakeupret,SQBool retval,SQBool raiseerror,SQBool throwerror) +{ + SQObjectPtr ret; + if(!v->_suspended) + return sq_throwerror(v,_SC("cannot resume a vm that is not running any code")); + SQInteger target = v->_suspended_target; + if(wakeupret) { + if(target != -1) { + v->GetAt(v->_stackbase+v->_suspended_target)=v->GetUp(-1); //retval + } + v->Pop(); + } else if(target != -1) { v->GetAt(v->_stackbase+v->_suspended_target).Null(); } + SQObjectPtr dummy; + if(!v->Execute(dummy,-1,-1,ret,raiseerror,throwerror?SQVM::ET_RESUME_THROW_VM : SQVM::ET_RESUME_VM)) { + return SQ_ERROR; + } + if(retval) + v->Push(ret); + return SQ_OK; +} + +void sq_setreleasehook(HSQUIRRELVM v,SQInteger idx,SQRELEASEHOOK hook) +{ + SQObjectPtr &ud=stack_get(v,idx); + switch(sq_type(ud) ) { + case OT_USERDATA: _userdata(ud)->_hook = hook; break; + case OT_INSTANCE: _instance(ud)->_hook = hook; break; + case OT_CLASS: _class(ud)->_hook = hook; break; + default: return; + } +} + +SQRELEASEHOOK sq_getreleasehook(HSQUIRRELVM v,SQInteger idx) +{ + SQObjectPtr &ud=stack_get(v,idx); + switch(sq_type(ud) ) { + case OT_USERDATA: return _userdata(ud)->_hook; break; + case OT_INSTANCE: return _instance(ud)->_hook; break; + case OT_CLASS: return _class(ud)->_hook; break; + default: return NULL; + } +} + +void sq_setcompilererrorhandler(HSQUIRRELVM v,SQCOMPILERERROR f) +{ + _ss(v)->_compilererrorhandler = f; +} + +SQRESULT sq_writeclosure(HSQUIRRELVM v,SQWRITEFUNC w,SQUserPointer up) +{ + SQObjectPtr *o = NULL; + _GETSAFE_OBJ(v, -1, OT_CLOSURE,o); + unsigned short tag = SQ_BYTECODE_STREAM_TAG; + if(_closure(*o)->_function->_noutervalues) + return sq_throwerror(v,_SC("a closure with free variables bound cannot be serialized")); + if(w(up,&tag,2) != 2) + return sq_throwerror(v,_SC("io error")); + if(!_closure(*o)->Save(v,up,w)) + return SQ_ERROR; + return SQ_OK; +} + +SQRESULT sq_readclosure(HSQUIRRELVM v,SQREADFUNC r,SQUserPointer up) +{ + SQObjectPtr closure; + + unsigned short tag; + if(r(up,&tag,2) != 2) + return sq_throwerror(v,_SC("io error")); + if(tag != SQ_BYTECODE_STREAM_TAG) + return sq_throwerror(v,_SC("invalid stream")); + if(!SQClosure::Load(v,up,r,closure)) + return SQ_ERROR; + v->Push(closure); + return SQ_OK; +} + +SQChar *sq_getscratchpad(HSQUIRRELVM v,SQInteger minsize) +{ + return _ss(v)->GetScratchPad(minsize); +} + +SQRESULT sq_resurrectunreachable(HSQUIRRELVM v) +{ +#ifndef NO_GARBAGE_COLLECTOR + _ss(v)->ResurrectUnreachable(v); + return SQ_OK; +#else + return sq_throwerror(v,_SC("sq_resurrectunreachable requires a garbage collector build")); +#endif +} + +SQInteger sq_collectgarbage(HSQUIRRELVM v) +{ +#ifndef NO_GARBAGE_COLLECTOR + return _ss(v)->CollectGarbage(v); +#else + return -1; +#endif +} + +SQRESULT sq_getcallee(HSQUIRRELVM v) +{ + if(v->_callsstacksize > 1) + { + v->Push(v->_callsstack[v->_callsstacksize - 2]._closure); + return SQ_OK; + } + return sq_throwerror(v,_SC("no closure in the calls stack")); +} + +const SQChar *sq_getfreevariable(HSQUIRRELVM v,SQInteger idx,SQUnsignedInteger nval) +{ + SQObjectPtr &self=stack_get(v,idx); + const SQChar *name = NULL; + switch(sq_type(self)) + { + case OT_CLOSURE:{ + SQClosure *clo = _closure(self); + SQFunctionProto *fp = clo->_function; + if(((SQUnsignedInteger)fp->_noutervalues) > nval) { + v->Push(*(_outer(clo->_outervalues[nval])->_valptr)); + SQOuterVar &ov = fp->_outervalues[nval]; + name = _stringval(ov._name); + } + } + break; + case OT_NATIVECLOSURE:{ + SQNativeClosure *clo = _nativeclosure(self); + if(clo->_noutervalues > nval) { + v->Push(clo->_outervalues[nval]); + name = _SC("@NATIVE"); + } + } + break; + default: break; //shutup compiler + } + return name; +} + +SQRESULT sq_setfreevariable(HSQUIRRELVM v,SQInteger idx,SQUnsignedInteger nval) +{ + SQObjectPtr &self=stack_get(v,idx); + switch(sq_type(self)) + { + case OT_CLOSURE:{ + SQFunctionProto *fp = _closure(self)->_function; + if(((SQUnsignedInteger)fp->_noutervalues) > nval){ + *(_outer(_closure(self)->_outervalues[nval])->_valptr) = stack_get(v,-1); + } + else return sq_throwerror(v,_SC("invalid free var index")); + } + break; + case OT_NATIVECLOSURE: + if(_nativeclosure(self)->_noutervalues > nval){ + _nativeclosure(self)->_outervalues[nval] = stack_get(v,-1); + } + else return sq_throwerror(v,_SC("invalid free var index")); + break; + default: + return sq_aux_invalidtype(v, sq_type(self)); + } + v->Pop(); + return SQ_OK; +} + +SQRESULT sq_setattributes(HSQUIRRELVM v,SQInteger idx) +{ + SQObjectPtr *o = NULL; + _GETSAFE_OBJ(v, idx, OT_CLASS,o); + SQObjectPtr &key = stack_get(v,-2); + SQObjectPtr &val = stack_get(v,-1); + SQObjectPtr attrs; + if(sq_type(key) == OT_NULL) { + attrs = _class(*o)->_attributes; + _class(*o)->_attributes = val; + v->Pop(2); + v->Push(attrs); + return SQ_OK; + }else if(_class(*o)->GetAttributes(key,attrs)) { + _class(*o)->SetAttributes(key,val); + v->Pop(2); + v->Push(attrs); + return SQ_OK; + } + return sq_throwerror(v,_SC("wrong index")); +} + +SQRESULT sq_getattributes(HSQUIRRELVM v,SQInteger idx) +{ + SQObjectPtr *o = NULL; + _GETSAFE_OBJ(v, idx, OT_CLASS,o); + SQObjectPtr &key = stack_get(v,-1); + SQObjectPtr attrs; + if(sq_type(key) == OT_NULL) { + attrs = _class(*o)->_attributes; + v->Pop(); + v->Push(attrs); + return SQ_OK; + } + else if(_class(*o)->GetAttributes(key,attrs)) { + v->Pop(); + v->Push(attrs); + return SQ_OK; + } + return sq_throwerror(v,_SC("wrong index")); +} + +SQRESULT sq_getmemberhandle(HSQUIRRELVM v,SQInteger idx,HSQMEMBERHANDLE *handle) +{ + SQObjectPtr *o = NULL; + _GETSAFE_OBJ(v, idx, OT_CLASS,o); + SQObjectPtr &key = stack_get(v,-1); + SQTable *m = _class(*o)->_members; + SQObjectPtr val; + if(m->Get(key,val)) { + handle->_static = _isfield(val) ? SQFalse : SQTrue; + handle->_index = _member_idx(val); + v->Pop(); + return SQ_OK; + } + return sq_throwerror(v,_SC("wrong index")); +} + +SQRESULT _getmemberbyhandle(HSQUIRRELVM v,SQObjectPtr &self,const HSQMEMBERHANDLE *handle,SQObjectPtr *&val) +{ + switch(sq_type(self)) { + case OT_INSTANCE: { + SQInstance *i = _instance(self); + if(handle->_static) { + SQClass *c = i->_class; + val = &c->_methods[handle->_index].val; + } + else { + val = &i->_values[handle->_index]; + + } + } + break; + case OT_CLASS: { + SQClass *c = _class(self); + if(handle->_static) { + val = &c->_methods[handle->_index].val; + } + else { + val = &c->_defaultvalues[handle->_index].val; + } + } + break; + default: + return sq_throwerror(v,_SC("wrong type(expected class or instance)")); + } + return SQ_OK; +} + +SQRESULT sq_getbyhandle(HSQUIRRELVM v,SQInteger idx,const HSQMEMBERHANDLE *handle) +{ + SQObjectPtr &self = stack_get(v,idx); + SQObjectPtr *val = NULL; + if(SQ_FAILED(_getmemberbyhandle(v,self,handle,val))) { + return SQ_ERROR; + } + v->Push(_realval(*val)); + return SQ_OK; +} + +SQRESULT sq_setbyhandle(HSQUIRRELVM v,SQInteger idx,const HSQMEMBERHANDLE *handle) +{ + SQObjectPtr &self = stack_get(v,idx); + SQObjectPtr &newval = stack_get(v,-1); + SQObjectPtr *val = NULL; + if(SQ_FAILED(_getmemberbyhandle(v,self,handle,val))) { + return SQ_ERROR; + } + *val = newval; + v->Pop(); + return SQ_OK; +} + +SQRESULT sq_getbase(HSQUIRRELVM v,SQInteger idx) +{ + SQObjectPtr *o = NULL; + _GETSAFE_OBJ(v, idx, OT_CLASS,o); + if(_class(*o)->_base) + v->Push(SQObjectPtr(_class(*o)->_base)); + else + v->PushNull(); + return SQ_OK; +} + +SQRESULT sq_getclass(HSQUIRRELVM v,SQInteger idx) +{ + SQObjectPtr *o = NULL; + _GETSAFE_OBJ(v, idx, OT_INSTANCE,o); + v->Push(SQObjectPtr(_instance(*o)->_class)); + return SQ_OK; +} + +SQRESULT sq_createinstance(HSQUIRRELVM v,SQInteger idx) +{ + SQObjectPtr *o = NULL; + _GETSAFE_OBJ(v, idx, OT_CLASS,o); + v->Push(_class(*o)->CreateInstance()); + return SQ_OK; +} + +void sq_weakref(HSQUIRRELVM v,SQInteger idx) +{ + SQObject &o=stack_get(v,idx); + if(ISREFCOUNTED(sq_type(o))) { + v->Push(_refcounted(o)->GetWeakRef(sq_type(o))); + return; + } + v->Push(o); +} + +SQRESULT sq_getweakrefval(HSQUIRRELVM v,SQInteger idx) +{ + SQObjectPtr &o = stack_get(v,idx); + if(sq_type(o) != OT_WEAKREF) { + return sq_throwerror(v,_SC("the object must be a weakref")); + } + v->Push(_weakref(o)->_obj); + return SQ_OK; +} + +SQRESULT sq_getdefaultdelegate(HSQUIRRELVM v,SQObjectType t) +{ + SQSharedState *ss = _ss(v); + switch(t) { + case OT_TABLE: v->Push(ss->_table_default_delegate); break; + case OT_ARRAY: v->Push(ss->_array_default_delegate); break; + case OT_STRING: v->Push(ss->_string_default_delegate); break; + case OT_INTEGER: case OT_FLOAT: v->Push(ss->_number_default_delegate); break; + case OT_GENERATOR: v->Push(ss->_generator_default_delegate); break; + case OT_CLOSURE: case OT_NATIVECLOSURE: v->Push(ss->_closure_default_delegate); break; + case OT_THREAD: v->Push(ss->_thread_default_delegate); break; + case OT_CLASS: v->Push(ss->_class_default_delegate); break; + case OT_INSTANCE: v->Push(ss->_instance_default_delegate); break; + case OT_WEAKREF: v->Push(ss->_weakref_default_delegate); break; + default: return sq_throwerror(v,_SC("the type doesn't have a default delegate")); + } + return SQ_OK; +} + +SQRESULT sq_next(HSQUIRRELVM v,SQInteger idx) +{ + SQObjectPtr o=stack_get(v,idx),&refpos = stack_get(v,-1),realkey,val; + if(sq_type(o) == OT_GENERATOR) { + return sq_throwerror(v,_SC("cannot iterate a generator")); + } + int faketojump; + if(!v->FOREACH_OP(o,realkey,val,refpos,0,666,faketojump)) + return SQ_ERROR; + if(faketojump != 666) { + v->Push(realkey); + v->Push(val); + return SQ_OK; + } + return SQ_ERROR; +} + +struct BufState{ + const SQChar *buf; + SQInteger ptr; + SQInteger size; +}; + +SQInteger buf_lexfeed(SQUserPointer file) +{ + BufState *buf=(BufState*)file; + if(buf->size<(buf->ptr+1)) + return 0; + return buf->buf[buf->ptr++]; +} + +SQRESULT sq_compilebuffer(HSQUIRRELVM v,const SQChar *s,SQInteger size,const SQChar *sourcename,SQBool raiseerror) { + BufState buf; + buf.buf = s; + buf.size = size; + buf.ptr = 0; + return sq_compile(v, buf_lexfeed, &buf, sourcename, raiseerror); +} + +void sq_move(HSQUIRRELVM dest,HSQUIRRELVM src,SQInteger idx) +{ + dest->Push(stack_get(src,idx)); +} + +void sq_setprintfunc(HSQUIRRELVM v, SQPRINTFUNCTION printfunc,SQPRINTFUNCTION errfunc) +{ + _ss(v)->_printfunc = printfunc; + _ss(v)->_errorfunc = errfunc; +} + +SQPRINTFUNCTION sq_getprintfunc(HSQUIRRELVM v) +{ + return _ss(v)->_printfunc; +} + +SQPRINTFUNCTION sq_geterrorfunc(HSQUIRRELVM v) +{ + return _ss(v)->_errorfunc; +} + +void *sq_malloc(SQUnsignedInteger size) +{ + return SQ_MALLOC(size); +} + +void *sq_realloc(void* p,SQUnsignedInteger oldsize,SQUnsignedInteger newsize) +{ + return SQ_REALLOC(p,oldsize,newsize); +} + +void sq_free(void *p,SQUnsignedInteger size) +{ + SQ_FREE(p,size); +} diff --git a/src/vscript/squirrel/squirrel/sqarray.h b/src/vscript/squirrel/squirrel/sqarray.h new file mode 100644 index 00000000..7c6c2049 --- /dev/null +++ b/src/vscript/squirrel/squirrel/sqarray.h @@ -0,0 +1,94 @@ +/* see copyright notice in squirrel.h */ +#ifndef _SQARRAY_H_ +#define _SQARRAY_H_ + +struct SQArray : public CHAINABLE_OBJ +{ +private: + SQArray(SQSharedState *ss,SQInteger nsize){_values.resize(nsize); INIT_CHAIN();ADD_TO_CHAIN(&_ss(this)->_gc_chain,this);} + ~SQArray() + { + REMOVE_FROM_CHAIN(&_ss(this)->_gc_chain,this); + } +public: + static SQArray* Create(SQSharedState *ss,SQInteger nInitialSize){ + SQArray *newarray=(SQArray*)SQ_MALLOC(sizeof(SQArray)); + new (newarray) SQArray(ss,nInitialSize); + return newarray; + } +#ifndef NO_GARBAGE_COLLECTOR + void Mark(SQCollectable **chain); + SQObjectType GetType() {return OT_ARRAY;} +#endif + void Finalize(){ + _values.resize(0); + } + bool Get(const SQInteger nidx,SQObjectPtr &val) + { + if(nidx>=0 && nidx<(SQInteger)_values.size()){ + SQObjectPtr &o = _values[nidx]; + val = _realval(o); + return true; + } + else return false; + } + bool Set(const SQInteger nidx,const SQObjectPtr &val) + { + if(nidx>=0 && nidx<(SQInteger)_values.size()){ + _values[nidx]=val; + return true; + } + else return false; + } + SQInteger Next(const SQObjectPtr &refpos,SQObjectPtr &outkey,SQObjectPtr &outval) + { + SQUnsignedInteger idx=TranslateIndex(refpos); + while(idx<_values.size()){ + //first found + outkey=(SQInteger)idx; + SQObjectPtr &o = _values[idx]; + outval = _realval(o); + //return idx for the next iteration + return ++idx; + } + //nothing to iterate anymore + return -1; + } + SQArray *Clone(){SQArray *anew=Create(_opt_ss(this),0); anew->_values.copy(_values); return anew; } + SQInteger Size() const {return _values.size();} + void Resize(SQInteger size) + { + SQObjectPtr _null; + Resize(size,_null); + } + void Resize(SQInteger size,SQObjectPtr &fill) { _values.resize(size,fill); ShrinkIfNeeded(); } + void Reserve(SQInteger size) { _values.reserve(size); } + void Append(const SQObject &o){_values.push_back(o);} + void Extend(const SQArray *a); + SQObjectPtr &Top(){return _values.top();} + void Pop(){_values.pop_back(); ShrinkIfNeeded(); } + bool Insert(SQInteger idx,const SQObject &val){ + if(idx < 0 || idx > (SQInteger)_values.size()) + return false; + _values.insert(idx,val); + return true; + } + void ShrinkIfNeeded() { + if(_values.size() <= _values.capacity()>>2) //shrink the array + _values.shrinktofit(); + } + bool Remove(SQInteger idx){ + if(idx < 0 || idx >= (SQInteger)_values.size()) + return false; + _values.remove(idx); + ShrinkIfNeeded(); + return true; + } + void Release() + { + sq_delete(this,SQArray); + } + + SQObjectPtrVec _values; +}; +#endif //_SQARRAY_H_ diff --git a/src/vscript/squirrel/squirrel/sqbaselib.cpp b/src/vscript/squirrel/squirrel/sqbaselib.cpp new file mode 100644 index 00000000..5c03e839 --- /dev/null +++ b/src/vscript/squirrel/squirrel/sqbaselib.cpp @@ -0,0 +1,1376 @@ +/* + see copyright notice in squirrel.h +*/ +#include "sqpcheader.h" +#include "sqvm.h" +#include "sqstring.h" +#include "sqtable.h" +#include "sqarray.h" +#include "sqfuncproto.h" +#include "sqclosure.h" +#include "sqclass.h" +#include +#include +#include + +static bool str2num(const SQChar *s,SQObjectPtr &res,SQInteger base) +{ + SQChar *end; + const SQChar *e = s; + bool iseintbase = base > 13; //to fix error converting hexadecimals with e like 56f0791e + bool isfloat = false; + SQChar c; + while((c = *e) != _SC('\0')) + { + if (c == _SC('.') || (!iseintbase && (c == _SC('E') || c == _SC('e')))) { //e and E is for scientific notation + isfloat = true; + break; + } + e++; + } + if(isfloat){ + SQFloat r = SQFloat(scstrtod(s,&end)); + if(s == end) return false; + res = r; + } + else{ + SQInteger r = SQInteger(scstrtol(s,&end,(int)base)); + if(s == end) return false; + res = r; + } + return true; +} + +static SQInteger base_dummy(HSQUIRRELVM SQ_UNUSED_ARG(v)) +{ + return 0; +} + +#ifndef NO_GARBAGE_COLLECTOR +static SQInteger base_collectgarbage(HSQUIRRELVM v) +{ + sq_pushinteger(v, sq_collectgarbage(v)); + return 1; +} +static SQInteger base_resurectureachable(HSQUIRRELVM v) +{ + sq_resurrectunreachable(v); + return 1; +} +#endif + +static SQInteger base_getroottable(HSQUIRRELVM v) +{ + v->Push(v->_roottable); + return 1; +} + +static SQInteger base_getconsttable(HSQUIRRELVM v) +{ + v->Push(_ss(v)->_consts); + return 1; +} + + +static SQInteger base_setroottable(HSQUIRRELVM v) +{ + SQObjectPtr o = v->_roottable; + if(SQ_FAILED(sq_setroottable(v))) return SQ_ERROR; + v->Push(o); + return 1; +} + +static SQInteger base_setconsttable(HSQUIRRELVM v) +{ + SQObjectPtr o = _ss(v)->_consts; + if(SQ_FAILED(sq_setconsttable(v))) return SQ_ERROR; + v->Push(o); + return 1; +} + +static SQInteger base_seterrorhandler(HSQUIRRELVM v) +{ + sq_seterrorhandler(v); + return 0; +} + +static SQInteger base_setdebughook(HSQUIRRELVM v) +{ + sq_setdebughook(v); + return 0; +} + +static SQInteger base_enabledebuginfo(HSQUIRRELVM v) +{ + SQObjectPtr &o=stack_get(v,2); + + sq_enabledebuginfo(v,SQVM::IsFalse(o)?SQFalse:SQTrue); + return 0; +} + +static SQInteger __getcallstackinfos(HSQUIRRELVM v,SQInteger level) +{ + SQStackInfos si; + SQInteger seq = 0; + const SQChar *name = NULL; + + if (SQ_SUCCEEDED(sq_stackinfos(v, level, &si))) + { + const SQChar *fn = _SC("unknown"); + const SQChar *src = _SC("unknown"); + if(si.funcname)fn = si.funcname; + if(si.source)src = si.source; + sq_newtable(v); + sq_pushstring(v, _SC("func"), -1); + sq_pushstring(v, fn, -1); + sq_newslot(v, -3, SQFalse); + sq_pushstring(v, _SC("src"), -1); + sq_pushstring(v, src, -1); + sq_newslot(v, -3, SQFalse); + sq_pushstring(v, _SC("line"), -1); + sq_pushinteger(v, si.line); + sq_newslot(v, -3, SQFalse); + sq_pushstring(v, _SC("locals"), -1); + sq_newtable(v); + seq=0; + while ((name = sq_getlocal(v, level, seq))) { + sq_pushstring(v, name, -1); + sq_push(v, -2); + sq_newslot(v, -4, SQFalse); + sq_pop(v, 1); + seq++; + } + sq_newslot(v, -3, SQFalse); + return 1; + } + + return 0; +} +static SQInteger base_getstackinfos(HSQUIRRELVM v) +{ + SQInteger level; + sq_getinteger(v, -1, &level); + return __getcallstackinfos(v,level); +} + +static SQInteger base_assert(HSQUIRRELVM v) +{ + if(SQVM::IsFalse(stack_get(v,2))){ + SQInteger top = sq_gettop(v); + if (top>2 && SQ_SUCCEEDED(sq_tostring(v,3))) { + const SQChar *str = 0; + if (SQ_SUCCEEDED(sq_getstring(v,-1,&str))) { + return sq_throwerror(v, str); + } + } + return sq_throwerror(v, _SC("assertion failed")); + } + return 0; +} + +static SQInteger get_slice_params(HSQUIRRELVM v,SQInteger &sidx,SQInteger &eidx,SQObjectPtr &o) +{ + SQInteger top = sq_gettop(v); + sidx=0; + eidx=0; + o=stack_get(v,1); + if(top>1){ + SQObjectPtr &start=stack_get(v,2); + if(sq_type(start)!=OT_NULL && sq_isnumeric(start)){ + sidx=tointeger(start); + } + } + if(top>2){ + SQObjectPtr &end=stack_get(v,3); + if(sq_isnumeric(end)){ + eidx=tointeger(end); + } + } + else { + eidx = sq_getsize(v,1); + } + return 1; +} + +static SQInteger base_print(HSQUIRRELVM v) +{ + const SQChar *str; + if(SQ_SUCCEEDED(sq_tostring(v,2))) + { + if(SQ_SUCCEEDED(sq_getstring(v,-1,&str))) { + if(_ss(v)->_printfunc) _ss(v)->_printfunc(v,_SC("%s"),str); + return 0; + } + } + return SQ_ERROR; +} + +static SQInteger base_error(HSQUIRRELVM v) +{ + const SQChar *str; + if(SQ_SUCCEEDED(sq_tostring(v,2))) + { + if(SQ_SUCCEEDED(sq_getstring(v,-1,&str))) { + if(_ss(v)->_errorfunc) _ss(v)->_errorfunc(v,_SC("%s"),str); + return 0; + } + } + return SQ_ERROR; +} + +static SQInteger base_compilestring(HSQUIRRELVM v) +{ + SQInteger nargs=sq_gettop(v); + const SQChar *src=NULL,*name=_SC("unnamedbuffer"); + SQInteger size; + sq_getstring(v,2,&src); + size=sq_getsize(v,2); + if(nargs>2){ + sq_getstring(v,3,&name); + } + if(SQ_SUCCEEDED(sq_compilebuffer(v,src,size,name,SQFalse))) + return 1; + else + return SQ_ERROR; +} + +static SQInteger base_newthread(HSQUIRRELVM v) +{ + SQObjectPtr &func = stack_get(v,2); + SQInteger stksize = (_closure(func)->_function->_stacksize << 1) +2; + HSQUIRRELVM newv = sq_newthread(v, (stksize < MIN_STACK_OVERHEAD + 2)? MIN_STACK_OVERHEAD + 2 : stksize); + sq_move(newv,v,-2); + return 1; +} + +static SQInteger base_suspend(HSQUIRRELVM v) +{ + return sq_suspendvm(v); +} + +static SQInteger base_array(HSQUIRRELVM v) +{ + SQArray *a; + SQObject &size = stack_get(v,2); + if(sq_gettop(v) > 2) { + a = SQArray::Create(_ss(v),0); + a->Resize(tointeger(size),stack_get(v,3)); + } + else { + a = SQArray::Create(_ss(v),tointeger(size)); + } + v->Push(a); + return 1; +} + +static SQInteger base_type(HSQUIRRELVM v) +{ + SQObjectPtr &o = stack_get(v,2); + v->Push(SQString::Create(_ss(v),GetTypeName(o),-1)); + return 1; +} + +static SQInteger base_callee(HSQUIRRELVM v) +{ + if(v->_callsstacksize > 1) + { + v->Push(v->_callsstack[v->_callsstacksize - 2]._closure); + return 1; + } + return sq_throwerror(v,_SC("no closure in the calls stack")); +} + +static const SQRegFunction base_funcs[]={ + //generic + {_SC("seterrorhandler"),base_seterrorhandler,2, NULL}, + {_SC("setdebughook"),base_setdebughook,2, NULL}, + {_SC("enabledebuginfo"),base_enabledebuginfo,2, NULL}, + {_SC("getstackinfos"),base_getstackinfos,2, _SC(".n")}, + {_SC("getroottable"),base_getroottable,1, NULL}, + {_SC("setroottable"),base_setroottable,2, NULL}, + {_SC("getconsttable"),base_getconsttable,1, NULL}, + {_SC("setconsttable"),base_setconsttable,2, NULL}, + {_SC("assert"),base_assert,-2, NULL}, + {_SC("print"),base_print,2, NULL}, + {_SC("error"),base_error,2, NULL}, + {_SC("compilestring"),base_compilestring,-2, _SC(".ss")}, + {_SC("newthread"),base_newthread,2, _SC(".c")}, + {_SC("suspend"),base_suspend,-1, NULL}, + {_SC("array"),base_array,-2, _SC(".n")}, + {_SC("type"),base_type,2, NULL}, + {_SC("callee"),base_callee,0,NULL}, + {_SC("dummy"),base_dummy,0,NULL}, +#ifndef NO_GARBAGE_COLLECTOR + {_SC("collectgarbage"),base_collectgarbage,0, NULL}, + {_SC("resurrectunreachable"),base_resurectureachable,0, NULL}, +#endif + {NULL,(SQFUNCTION)0,0,NULL} +}; + +void sq_base_register(HSQUIRRELVM v) +{ + SQInteger i=0; + sq_pushroottable(v); + while(base_funcs[i].name!=0) { + sq_pushstring(v,base_funcs[i].name,-1); + sq_newclosure(v,base_funcs[i].f,0); + sq_setnativeclosurename(v,-1,base_funcs[i].name); + sq_setparamscheck(v,base_funcs[i].nparamscheck,base_funcs[i].typemask); + sq_newslot(v,-3, SQFalse); + i++; + } + + sq_pushstring(v,_SC("_versionnumber_"),-1); + sq_pushinteger(v,SQUIRREL_VERSION_NUMBER); + sq_newslot(v,-3, SQFalse); + sq_pushstring(v,_SC("_version_"),-1); + sq_pushstring(v,SQUIRREL_VERSION,-1); + sq_newslot(v,-3, SQFalse); + sq_pushstring(v,_SC("_charsize_"),-1); + sq_pushinteger(v,sizeof(SQChar)); + sq_newslot(v,-3, SQFalse); + sq_pushstring(v,_SC("_intsize_"),-1); + sq_pushinteger(v,sizeof(SQInteger)); + sq_newslot(v,-3, SQFalse); + sq_pushstring(v,_SC("_floatsize_"),-1); + sq_pushinteger(v,sizeof(SQFloat)); + sq_newslot(v,-3, SQFalse); + sq_pop(v,1); +} + +static SQInteger default_delegate_len(HSQUIRRELVM v) +{ + v->Push(SQInteger(sq_getsize(v,1))); + return 1; +} + +static SQInteger default_delegate_tofloat(HSQUIRRELVM v) +{ + SQObjectPtr &o=stack_get(v,1); + switch(sq_type(o)){ + case OT_STRING:{ + SQObjectPtr res; + if(str2num(_stringval(o),res,10)){ + v->Push(SQObjectPtr(tofloat(res))); + break; + }} + return sq_throwerror(v, _SC("cannot convert the string")); + break; + case OT_INTEGER:case OT_FLOAT: + v->Push(SQObjectPtr(tofloat(o))); + break; + case OT_BOOL: + v->Push(SQObjectPtr((SQFloat)(_integer(o)?1:0))); + break; + default: + v->PushNull(); + break; + } + return 1; +} + +static SQInteger default_delegate_tointeger(HSQUIRRELVM v) +{ + SQObjectPtr &o=stack_get(v,1); + SQInteger base = 10; + if(sq_gettop(v) > 1) { + sq_getinteger(v,2,&base); + } + switch(sq_type(o)){ + case OT_STRING:{ + SQObjectPtr res; + if(str2num(_stringval(o),res,base)){ + v->Push(SQObjectPtr(tointeger(res))); + break; + }} + return sq_throwerror(v, _SC("cannot convert the string")); + break; + case OT_INTEGER:case OT_FLOAT: + v->Push(SQObjectPtr(tointeger(o))); + break; + case OT_BOOL: + v->Push(SQObjectPtr(_integer(o)?(SQInteger)1:(SQInteger)0)); + break; + default: + v->PushNull(); + break; + } + return 1; +} + +static SQInteger default_delegate_tostring(HSQUIRRELVM v) +{ + if(SQ_FAILED(sq_tostring(v,1))) + return SQ_ERROR; + return 1; +} + +static SQInteger obj_delegate_weakref(HSQUIRRELVM v) +{ + sq_weakref(v,1); + return 1; +} + +static SQInteger obj_clear(HSQUIRRELVM v) +{ + return SQ_SUCCEEDED(sq_clear(v,-1)) ? 1 : SQ_ERROR; +} + + +static SQInteger number_delegate_tochar(HSQUIRRELVM v) +{ + SQObject &o=stack_get(v,1); + SQChar c = (SQChar)tointeger(o); + v->Push(SQString::Create(_ss(v),(const SQChar *)&c,1)); + return 1; +} + + + +///////////////////////////////////////////////////////////////// +//TABLE DEFAULT DELEGATE + +static SQInteger table_rawdelete(HSQUIRRELVM v) +{ + if(SQ_FAILED(sq_rawdeleteslot(v,1,SQTrue))) + return SQ_ERROR; + return 1; +} + + +static SQInteger container_rawexists(HSQUIRRELVM v) +{ + if(SQ_SUCCEEDED(sq_rawget(v,-2))) { + sq_pushbool(v,SQTrue); + return 1; + } + sq_pushbool(v,SQFalse); + return 1; +} + +static SQInteger container_rawset(HSQUIRRELVM v) +{ + return SQ_SUCCEEDED(sq_rawset(v,-3)) ? 1 : SQ_ERROR; +} + + +static SQInteger container_rawget(HSQUIRRELVM v) +{ + return SQ_SUCCEEDED(sq_rawget(v,-2))?1:SQ_ERROR; +} + +static SQInteger table_setdelegate(HSQUIRRELVM v) +{ + if(SQ_FAILED(sq_setdelegate(v,-2))) + return SQ_ERROR; + sq_push(v,-1); // -1 because sq_setdelegate pops 1 + return 1; +} + +static SQInteger table_getdelegate(HSQUIRRELVM v) +{ + return SQ_SUCCEEDED(sq_getdelegate(v,-1))?1:SQ_ERROR; +} + +static SQInteger table_filter(HSQUIRRELVM v) +{ + SQObject &o = stack_get(v,1); + SQTable *tbl = _table(o); + SQObjectPtr ret = SQTable::Create(_ss(v),0); + + SQObjectPtr itr, key, val; + SQInteger nitr; + while((nitr = tbl->Next(false, itr, key, val)) != -1) { + itr = (SQInteger)nitr; + + v->Push(o); + v->Push(key); + v->Push(val); + if(SQ_FAILED(sq_call(v,3,SQTrue,SQFalse))) { + return SQ_ERROR; + } + if(!SQVM::IsFalse(v->GetUp(-1))) { + _table(ret)->NewSlot(key, val); + } + v->Pop(); + } + + v->Push(ret); + return 1; +} + +#define TABLE_TO_ARRAY_FUNC(_funcname_,_valname_) static SQInteger _funcname_(HSQUIRRELVM v) \ +{ \ + SQObject &o = stack_get(v, 1); \ + SQTable *t = _table(o); \ + SQObjectPtr itr, key, val; \ + SQObjectPtr _null; \ + SQInteger nitr, n = 0; \ + SQInteger nitems = t->CountUsed(); \ + SQArray *a = SQArray::Create(_ss(v), nitems); \ + a->Resize(nitems, _null); \ + if (nitems) { \ + while ((nitr = t->Next(false, itr, key, val)) != -1) { \ + itr = (SQInteger)nitr; \ + a->Set(n, _valname_); \ + n++; \ + } \ + } \ + v->Push(a); \ + return 1; \ +} + +TABLE_TO_ARRAY_FUNC(table_keys, key) +TABLE_TO_ARRAY_FUNC(table_values, val) + + +const SQRegFunction SQSharedState::_table_default_delegate_funcz[]={ + {_SC("len"),default_delegate_len,1, _SC("t")}, + {_SC("rawget"),container_rawget,2, _SC("t")}, + {_SC("rawset"),container_rawset,3, _SC("t")}, + {_SC("rawdelete"),table_rawdelete,2, _SC("t")}, + {_SC("rawin"),container_rawexists,2, _SC("t")}, + {_SC("weakref"),obj_delegate_weakref,1, NULL }, + {_SC("tostring"),default_delegate_tostring,1, _SC(".")}, + {_SC("clear"),obj_clear,1, _SC(".")}, + {_SC("setdelegate"),table_setdelegate,2, _SC(".t|o")}, + {_SC("getdelegate"),table_getdelegate,1, _SC(".")}, + {_SC("filter"),table_filter,2, _SC("tc")}, + {_SC("keys"),table_keys,1, _SC("t") }, + {_SC("values"),table_values,1, _SC("t") }, + {NULL,(SQFUNCTION)0,0,NULL} +}; + +//ARRAY DEFAULT DELEGATE/////////////////////////////////////// + +static SQInteger array_append(HSQUIRRELVM v) +{ + return SQ_SUCCEEDED(sq_arrayappend(v,-2)) ? 1 : SQ_ERROR; +} + +static SQInteger array_extend(HSQUIRRELVM v) +{ + _array(stack_get(v,1))->Extend(_array(stack_get(v,2))); + sq_pop(v,1); + return 1; +} + +static SQInteger array_reverse(HSQUIRRELVM v) +{ + return SQ_SUCCEEDED(sq_arrayreverse(v,-1)) ? 1 : SQ_ERROR; +} + +static SQInteger array_pop(HSQUIRRELVM v) +{ + return SQ_SUCCEEDED(sq_arraypop(v,1,SQTrue))?1:SQ_ERROR; +} + +static SQInteger array_top(HSQUIRRELVM v) +{ + SQObject &o=stack_get(v,1); + if(_array(o)->Size()>0){ + v->Push(_array(o)->Top()); + return 1; + } + else return sq_throwerror(v,_SC("top() on a empty array")); +} + +static SQInteger array_insert(HSQUIRRELVM v) +{ + SQObject &o=stack_get(v,1); + SQObject &idx=stack_get(v,2); + SQObject &val=stack_get(v,3); + if(!_array(o)->Insert(tointeger(idx),val)) + return sq_throwerror(v,_SC("index out of range")); + sq_pop(v,2); + return 1; +} + +static SQInteger array_remove(HSQUIRRELVM v) +{ + SQObject &o = stack_get(v, 1); + SQObject &idx = stack_get(v, 2); + if(!sq_isnumeric(idx)) return sq_throwerror(v, _SC("wrong type")); + SQObjectPtr val; + if(_array(o)->Get(tointeger(idx), val)) { + _array(o)->Remove(tointeger(idx)); + v->Push(val); + return 1; + } + return sq_throwerror(v, _SC("idx out of range")); +} + +static SQInteger array_resize(HSQUIRRELVM v) +{ + SQObject &o = stack_get(v, 1); + SQObject &nsize = stack_get(v, 2); + SQObjectPtr fill; + if(sq_isnumeric(nsize)) { + SQInteger sz = tointeger(nsize); + if (sz<0) + return sq_throwerror(v, _SC("resizing to negative length")); + + if(sq_gettop(v) > 2) + fill = stack_get(v, 3); + _array(o)->Resize(sz,fill); + sq_settop(v, 1); + return 1; + } + return sq_throwerror(v, _SC("size must be a number")); +} + +static SQInteger __map_array(SQArray *dest,SQArray *src,HSQUIRRELVM v) { + SQObjectPtr temp; + SQInteger size = src->Size(); + SQObject &closure = stack_get(v, 2); + v->Push(closure); + + SQInteger nArgs = 0; + if(sq_type(closure) == OT_CLOSURE) { + nArgs = _closure(closure)->_function->_nparameters; + } + else if (sq_type(closure) == OT_NATIVECLOSURE) { + SQInteger nParamsCheck = _nativeclosure(closure)->_nparamscheck; + if (nParamsCheck > 0) + nArgs = nParamsCheck; + else // push all params when there is no check or only minimal count set + nArgs = 4; + } + + for(SQInteger n = 0; n < size; n++) { + src->Get(n,temp); + v->Push(src); + v->Push(temp); + if (nArgs >= 3) + v->Push(SQObjectPtr(n)); + if (nArgs >= 4) + v->Push(src); + if(SQ_FAILED(sq_call(v,nArgs,SQTrue,SQFalse))) { + return SQ_ERROR; + } + dest->Set(n,v->GetUp(-1)); + v->Pop(); + } + v->Pop(); + return 0; +} + +static SQInteger array_map(HSQUIRRELVM v) +{ + SQObject &o = stack_get(v,1); + SQInteger size = _array(o)->Size(); + SQObjectPtr ret = SQArray::Create(_ss(v),size); + if(SQ_FAILED(__map_array(_array(ret),_array(o),v))) + return SQ_ERROR; + v->Push(ret); + return 1; +} + +static SQInteger array_apply(HSQUIRRELVM v) +{ + SQObject &o = stack_get(v,1); + if(SQ_FAILED(__map_array(_array(o),_array(o),v))) + return SQ_ERROR; + sq_pop(v,1); + return 1; +} + +static SQInteger array_reduce(HSQUIRRELVM v) +{ + SQObject &o = stack_get(v,1); + SQArray *a = _array(o); + SQInteger size = a->Size(); + SQObjectPtr res; + SQInteger iterStart; + if (sq_gettop(v)>2) { + res = stack_get(v,3); + iterStart = 0; + } else if (size==0) { + return 0; + } else { + a->Get(0,res); + iterStart = 1; + } + if (size > iterStart) { + SQObjectPtr other; + v->Push(stack_get(v,2)); + for (SQInteger n = iterStart; n < size; n++) { + a->Get(n,other); + v->Push(o); + v->Push(res); + v->Push(other); + if(SQ_FAILED(sq_call(v,3,SQTrue,SQFalse))) { + return SQ_ERROR; + } + res = v->GetUp(-1); + v->Pop(); + } + v->Pop(); + } + v->Push(res); + return 1; +} + +static SQInteger array_filter(HSQUIRRELVM v) +{ + SQObject &o = stack_get(v,1); + SQArray *a = _array(o); + SQObjectPtr ret = SQArray::Create(_ss(v),0); + SQInteger size = a->Size(); + SQObjectPtr val; + for(SQInteger n = 0; n < size; n++) { + a->Get(n,val); + v->Push(o); + v->Push(n); + v->Push(val); + if(SQ_FAILED(sq_call(v,3,SQTrue,SQFalse))) { + return SQ_ERROR; + } + if(!SQVM::IsFalse(v->GetUp(-1))) { + _array(ret)->Append(val); + } + v->Pop(); + } + v->Push(ret); + return 1; +} + +static SQInteger array_find(HSQUIRRELVM v) +{ + SQObject &o = stack_get(v,1); + SQObjectPtr &val = stack_get(v,2); + SQArray *a = _array(o); + SQInteger size = a->Size(); + SQObjectPtr temp; + for(SQInteger n = 0; n < size; n++) { + bool res = false; + a->Get(n,temp); + if(SQVM::IsEqual(temp,val,res) && res) { + v->Push(n); + return 1; + } + } + return 0; +} + + +static bool _sort_compare(HSQUIRRELVM v, SQArray *arr, SQObjectPtr &a,SQObjectPtr &b,SQInteger func,SQInteger &ret) +{ + if(func < 0) { + if(!v->ObjCmp(a,b,ret)) return false; + } + else { + SQInteger top = sq_gettop(v); + sq_push(v, func); + sq_pushroottable(v); + v->Push(a); + v->Push(b); + SQObjectPtr *valptr = arr->_values._vals; + SQUnsignedInteger precallsize = arr->_values.size(); + if(SQ_FAILED(sq_call(v, 3, SQTrue, SQFalse))) { + if(!sq_isstring( v->_lasterror)) + v->Raise_Error(_SC("compare func failed")); + return false; + } + if(SQ_FAILED(sq_getinteger(v, -1, &ret))) { + v->Raise_Error(_SC("numeric value expected as return value of the compare function")); + return false; + } + if (precallsize != arr->_values.size() || valptr != arr->_values._vals) { + v->Raise_Error(_SC("array resized during sort operation")); + return false; + } + sq_settop(v, top); + return true; + } + return true; +} + +static bool _hsort_sift_down(HSQUIRRELVM v,SQArray *arr, SQInteger root, SQInteger bottom, SQInteger func) +{ + SQInteger maxChild; + SQInteger done = 0; + SQInteger ret; + SQInteger root2; + while (((root2 = root * 2) <= bottom) && (!done)) + { + if (root2 == bottom) { + maxChild = root2; + } + else { + if(!_sort_compare(v,arr,arr->_values[root2],arr->_values[root2 + 1],func,ret)) + return false; + if (ret > 0) { + maxChild = root2; + } + else { + maxChild = root2 + 1; + } + } + + if(!_sort_compare(v,arr,arr->_values[root],arr->_values[maxChild],func,ret)) + return false; + if (ret < 0) { + if (root == maxChild) { + v->Raise_Error(_SC("inconsistent compare function")); + return false; // We'd be swapping ourselve. The compare function is incorrect + } + + _Swap(arr->_values[root],arr->_values[maxChild]); + root = maxChild; + } + else { + done = 1; + } + } + return true; +} + +static bool _hsort(HSQUIRRELVM v,SQObjectPtr &arr, SQInteger SQ_UNUSED_ARG(l), SQInteger SQ_UNUSED_ARG(r),SQInteger func) +{ + SQArray *a = _array(arr); + SQInteger i; + SQInteger array_size = a->Size(); + for (i = (array_size / 2); i >= 0; i--) { + if(!_hsort_sift_down(v,a, i, array_size - 1,func)) return false; + } + + for (i = array_size-1; i >= 1; i--) + { + _Swap(a->_values[0],a->_values[i]); + if(!_hsort_sift_down(v,a, 0, i-1,func)) return false; + } + return true; +} + +static SQInteger array_sort(HSQUIRRELVM v) +{ + SQInteger func = -1; + SQObjectPtr &o = stack_get(v,1); + if(_array(o)->Size() > 1) { + if(sq_gettop(v) == 2) func = 2; + if(!_hsort(v, o, 0, _array(o)->Size()-1, func)) + return SQ_ERROR; + + } + sq_settop(v,1); + return 1; +} + +static SQInteger array_slice(HSQUIRRELVM v) +{ + SQInteger sidx,eidx; + SQObjectPtr o; + if(get_slice_params(v,sidx,eidx,o)==-1)return -1; + SQInteger alen = _array(o)->Size(); + if(sidx < 0)sidx = alen + sidx; + if(eidx < 0)eidx = alen + eidx; + if(eidx < sidx)return sq_throwerror(v,_SC("wrong indexes")); + if(eidx > alen || sidx < 0)return sq_throwerror(v, _SC("slice out of range")); + SQArray *arr=SQArray::Create(_ss(v),eidx-sidx); + SQObjectPtr t; + SQInteger count=0; + for(SQInteger i=sidx;iGet(i,t); + arr->Set(count++,t); + } + v->Push(arr); + return 1; + +} + +const SQRegFunction SQSharedState::_array_default_delegate_funcz[]={ + {_SC("len"),default_delegate_len,1, _SC("a")}, + {_SC("append"),array_append,2, _SC("a")}, + {_SC("extend"),array_extend,2, _SC("aa")}, + {_SC("push"),array_append,2, _SC("a")}, + {_SC("pop"),array_pop,1, _SC("a")}, + {_SC("top"),array_top,1, _SC("a")}, + {_SC("insert"),array_insert,3, _SC("an")}, + {_SC("remove"),array_remove,2, _SC("an")}, + {_SC("resize"),array_resize,-2, _SC("an")}, + {_SC("reverse"),array_reverse,1, _SC("a")}, + {_SC("sort"),array_sort,-1, _SC("ac")}, + {_SC("slice"),array_slice,-1, _SC("ann")}, + {_SC("weakref"),obj_delegate_weakref,1, NULL }, + {_SC("tostring"),default_delegate_tostring,1, _SC(".")}, + {_SC("clear"),obj_clear,1, _SC(".")}, + {_SC("map"),array_map,2, _SC("ac")}, + {_SC("apply"),array_apply,2, _SC("ac")}, + {_SC("reduce"),array_reduce,-2, _SC("ac.")}, + {_SC("filter"),array_filter,2, _SC("ac")}, + {_SC("find"),array_find,2, _SC("a.")}, + {NULL,(SQFUNCTION)0,0,NULL} +}; + +//STRING DEFAULT DELEGATE////////////////////////// +static SQInteger string_slice(HSQUIRRELVM v) +{ + SQInteger sidx,eidx; + SQObjectPtr o; + if(SQ_FAILED(get_slice_params(v,sidx,eidx,o)))return -1; + SQInteger slen = _string(o)->_len; + if(sidx < 0)sidx = slen + sidx; + if(eidx < 0)eidx = slen + eidx; + if(eidx < sidx) return sq_throwerror(v,_SC("wrong indexes")); + if(eidx > slen || sidx < 0) return sq_throwerror(v, _SC("slice out of range")); + v->Push(SQString::Create(_ss(v),&_stringval(o)[sidx],eidx-sidx)); + return 1; +} + +static SQInteger string_find(HSQUIRRELVM v) +{ + SQInteger top,start_idx=0; + const SQChar *str,*substr,*ret; + if(((top=sq_gettop(v))>1) && SQ_SUCCEEDED(sq_getstring(v,1,&str)) && SQ_SUCCEEDED(sq_getstring(v,2,&substr))){ + if(top>2)sq_getinteger(v,3,&start_idx); + if((sq_getsize(v,1)>start_idx) && (start_idx>=0)){ + ret=scstrstr(&str[start_idx],substr); + if(ret){ + sq_pushinteger(v,(SQInteger)(ret-str)); + return 1; + } + } + return 0; + } + return sq_throwerror(v,_SC("invalid param")); +} + +#define STRING_TOFUNCZ(func) static SQInteger string_##func(HSQUIRRELVM v) \ +{\ + SQInteger sidx,eidx; \ + SQObjectPtr str; \ + if(SQ_FAILED(get_slice_params(v,sidx,eidx,str)))return -1; \ + SQInteger slen = _string(str)->_len; \ + if(sidx < 0)sidx = slen + sidx; \ + if(eidx < 0)eidx = slen + eidx; \ + if(eidx < sidx) return sq_throwerror(v,_SC("wrong indexes")); \ + if(eidx > slen || sidx < 0) return sq_throwerror(v,_SC("slice out of range")); \ + SQInteger len=_string(str)->_len; \ + const SQChar *sthis=_stringval(str); \ + SQChar *snew=(_ss(v)->GetScratchPad(sq_rsl(len))); \ + memcpy(snew,sthis,sq_rsl(len));\ + for(SQInteger i=sidx;iPush(SQString::Create(_ss(v),snew,len)); \ + return 1; \ +} + + +STRING_TOFUNCZ(tolower) +STRING_TOFUNCZ(toupper) + +const SQRegFunction SQSharedState::_string_default_delegate_funcz[]={ + {_SC("len"),default_delegate_len,1, _SC("s")}, + {_SC("tointeger"),default_delegate_tointeger,-1, _SC("sn")}, + {_SC("tofloat"),default_delegate_tofloat,1, _SC("s")}, + {_SC("tostring"),default_delegate_tostring,1, _SC(".")}, + {_SC("slice"),string_slice,-1, _SC("s n n")}, + {_SC("find"),string_find,-2, _SC("s s n")}, + {_SC("tolower"),string_tolower,-1, _SC("s n n")}, + {_SC("toupper"),string_toupper,-1, _SC("s n n")}, + {_SC("weakref"),obj_delegate_weakref,1, NULL }, + {NULL,(SQFUNCTION)0,0,NULL} +}; + +//INTEGER DEFAULT DELEGATE////////////////////////// +const SQRegFunction SQSharedState::_number_default_delegate_funcz[]={ + {_SC("tointeger"),default_delegate_tointeger,1, _SC("n|b")}, + {_SC("tofloat"),default_delegate_tofloat,1, _SC("n|b")}, + {_SC("tostring"),default_delegate_tostring,1, _SC(".")}, + {_SC("tochar"),number_delegate_tochar,1, _SC("n|b")}, + {_SC("weakref"),obj_delegate_weakref,1, NULL }, + {NULL,(SQFUNCTION)0,0,NULL} +}; + +//CLOSURE DEFAULT DELEGATE////////////////////////// +static SQInteger closure_pcall(HSQUIRRELVM v) +{ + return SQ_SUCCEEDED(sq_call(v,sq_gettop(v)-1,SQTrue,SQFalse))?1:SQ_ERROR; +} + +static SQInteger closure_call(HSQUIRRELVM v) +{ + SQObjectPtr &c = stack_get(v, -1); + if (sq_type(c) == OT_CLOSURE && (_closure(c)->_function->_bgenerator == false)) + { + return sq_tailcall(v, sq_gettop(v) - 1); + } + return SQ_SUCCEEDED(sq_call(v, sq_gettop(v) - 1, SQTrue, SQTrue)) ? 1 : SQ_ERROR; +} + +static SQInteger _closure_acall(HSQUIRRELVM v,SQBool raiseerror) +{ + SQArray *aparams=_array(stack_get(v,2)); + SQInteger nparams=aparams->Size(); + v->Push(stack_get(v,1)); + for(SQInteger i=0;iPush(aparams->_values[i]); + return SQ_SUCCEEDED(sq_call(v,nparams,SQTrue,raiseerror))?1:SQ_ERROR; +} + +static SQInteger closure_acall(HSQUIRRELVM v) +{ + return _closure_acall(v,SQTrue); +} + +static SQInteger closure_pacall(HSQUIRRELVM v) +{ + return _closure_acall(v,SQFalse); +} + +static SQInteger closure_bindenv(HSQUIRRELVM v) +{ + if(SQ_FAILED(sq_bindenv(v,1))) + return SQ_ERROR; + return 1; +} + +static SQInteger closure_getroot(HSQUIRRELVM v) +{ + if(SQ_FAILED(sq_getclosureroot(v,-1))) + return SQ_ERROR; + return 1; +} + +static SQInteger closure_setroot(HSQUIRRELVM v) +{ + if(SQ_FAILED(sq_setclosureroot(v,-2))) + return SQ_ERROR; + return 1; +} + +static SQInteger closure_getinfos(HSQUIRRELVM v) { + SQObject o = stack_get(v,1); + SQTable *res = SQTable::Create(_ss(v),4); + if(sq_type(o) == OT_CLOSURE) { + SQFunctionProto *f = _closure(o)->_function; + SQInteger nparams = f->_nparameters + (f->_varparams?1:0); + SQObjectPtr params = SQArray::Create(_ss(v),nparams); + SQObjectPtr defparams = SQArray::Create(_ss(v),f->_ndefaultparams); + for(SQInteger n = 0; n_nparameters; n++) { + _array(params)->Set((SQInteger)n,f->_parameters[n]); + } + for(SQInteger j = 0; j_ndefaultparams; j++) { + _array(defparams)->Set((SQInteger)j,_closure(o)->_defaultparams[j]); + } + if(f->_varparams) { + _array(params)->Set(nparams-1,SQString::Create(_ss(v),_SC("..."),-1)); + } + res->NewSlot(SQString::Create(_ss(v),_SC("native"),-1),false); + res->NewSlot(SQString::Create(_ss(v),_SC("name"),-1),f->_name); + res->NewSlot(SQString::Create(_ss(v),_SC("src"),-1),f->_sourcename); + res->NewSlot(SQString::Create(_ss(v),_SC("parameters"),-1),params); + res->NewSlot(SQString::Create(_ss(v),_SC("varargs"),-1),f->_varparams); + res->NewSlot(SQString::Create(_ss(v),_SC("defparams"),-1),defparams); + } + else { //OT_NATIVECLOSURE + SQNativeClosure *nc = _nativeclosure(o); + res->NewSlot(SQString::Create(_ss(v),_SC("native"),-1),true); + res->NewSlot(SQString::Create(_ss(v),_SC("name"),-1),nc->_name); + res->NewSlot(SQString::Create(_ss(v),_SC("paramscheck"),-1),nc->_nparamscheck); + SQObjectPtr typecheck; + if(nc->_typecheck.size() > 0) { + typecheck = + SQArray::Create(_ss(v), nc->_typecheck.size()); + for(SQUnsignedInteger n = 0; n_typecheck.size(); n++) { + _array(typecheck)->Set((SQInteger)n,nc->_typecheck[n]); + } + } + res->NewSlot(SQString::Create(_ss(v),_SC("typecheck"),-1),typecheck); + } + v->Push(res); + return 1; +} + + + +const SQRegFunction SQSharedState::_closure_default_delegate_funcz[]={ + {_SC("call"),closure_call,-1, _SC("c")}, + {_SC("pcall"),closure_pcall,-1, _SC("c")}, + {_SC("acall"),closure_acall,2, _SC("ca")}, + {_SC("pacall"),closure_pacall,2, _SC("ca")}, + {_SC("weakref"),obj_delegate_weakref,1, NULL }, + {_SC("tostring"),default_delegate_tostring,1, _SC(".")}, + {_SC("bindenv"),closure_bindenv,2, _SC("c x|y|t")}, + {_SC("getinfos"),closure_getinfos,1, _SC("c")}, + {_SC("getroot"),closure_getroot,1, _SC("c")}, + {_SC("setroot"),closure_setroot,2, _SC("ct")}, + {NULL,(SQFUNCTION)0,0,NULL} +}; + +//GENERATOR DEFAULT DELEGATE +static SQInteger generator_getstatus(HSQUIRRELVM v) +{ + SQObject &o=stack_get(v,1); + switch(_generator(o)->_state){ + case SQGenerator::eSuspended:v->Push(SQString::Create(_ss(v),_SC("suspended")));break; + case SQGenerator::eRunning:v->Push(SQString::Create(_ss(v),_SC("running")));break; + case SQGenerator::eDead:v->Push(SQString::Create(_ss(v),_SC("dead")));break; + } + return 1; +} + +const SQRegFunction SQSharedState::_generator_default_delegate_funcz[]={ + {_SC("getstatus"),generator_getstatus,1, _SC("g")}, + {_SC("weakref"),obj_delegate_weakref,1, NULL }, + {_SC("tostring"),default_delegate_tostring,1, _SC(".")}, + {NULL,(SQFUNCTION)0,0,NULL} +}; + +//THREAD DEFAULT DELEGATE +static SQInteger thread_call(HSQUIRRELVM v) +{ + SQObjectPtr o = stack_get(v,1); + if(sq_type(o) == OT_THREAD) { + SQInteger nparams = sq_gettop(v); + _thread(o)->Push(_thread(o)->_roottable); + for(SQInteger i = 2; i<(nparams+1); i++) + sq_move(_thread(o),v,i); + if(SQ_SUCCEEDED(sq_call(_thread(o),nparams,SQTrue,SQTrue))) { + sq_move(v,_thread(o),-1); + sq_pop(_thread(o),1); + return 1; + } + v->_lasterror = _thread(o)->_lasterror; + return SQ_ERROR; + } + return sq_throwerror(v,_SC("wrong parameter")); +} + +static SQInteger thread_wakeup(HSQUIRRELVM v) +{ + SQObjectPtr o = stack_get(v,1); + if(sq_type(o) == OT_THREAD) { + SQVM *thread = _thread(o); + SQInteger state = sq_getvmstate(thread); + if(state != SQ_VMSTATE_SUSPENDED) { + switch(state) { + case SQ_VMSTATE_IDLE: + return sq_throwerror(v,_SC("cannot wakeup a idle thread")); + break; + case SQ_VMSTATE_RUNNING: + return sq_throwerror(v,_SC("cannot wakeup a running thread")); + break; + } + } + + SQInteger wakeupret = sq_gettop(v)>1?SQTrue:SQFalse; + if(wakeupret) { + sq_move(thread,v,2); + } + if(SQ_SUCCEEDED(sq_wakeupvm(thread,wakeupret,SQTrue,SQTrue,SQFalse))) { + sq_move(v,thread,-1); + sq_pop(thread,1); //pop retval + if(sq_getvmstate(thread) == SQ_VMSTATE_IDLE) { + sq_settop(thread,1); //pop roottable + } + return 1; + } + sq_settop(thread,1); + v->_lasterror = thread->_lasterror; + return SQ_ERROR; + } + return sq_throwerror(v,_SC("wrong parameter")); +} + +static SQInteger thread_wakeupthrow(HSQUIRRELVM v) +{ + SQObjectPtr o = stack_get(v,1); + if(sq_type(o) == OT_THREAD) { + SQVM *thread = _thread(o); + SQInteger state = sq_getvmstate(thread); + if(state != SQ_VMSTATE_SUSPENDED) { + switch(state) { + case SQ_VMSTATE_IDLE: + return sq_throwerror(v,_SC("cannot wakeup a idle thread")); + break; + case SQ_VMSTATE_RUNNING: + return sq_throwerror(v,_SC("cannot wakeup a running thread")); + break; + } + } + + sq_move(thread,v,2); + sq_throwobject(thread); + SQBool rethrow_error = SQTrue; + if(sq_gettop(v) > 2) { + sq_getbool(v,3,&rethrow_error); + } + if(SQ_SUCCEEDED(sq_wakeupvm(thread,SQFalse,SQTrue,SQTrue,SQTrue))) { + sq_move(v,thread,-1); + sq_pop(thread,1); //pop retval + if(sq_getvmstate(thread) == SQ_VMSTATE_IDLE) { + sq_settop(thread,1); //pop roottable + } + return 1; + } + sq_settop(thread,1); + if(rethrow_error) { + v->_lasterror = thread->_lasterror; + return SQ_ERROR; + } + return SQ_OK; + } + return sq_throwerror(v,_SC("wrong parameter")); +} + +static SQInteger thread_getstatus(HSQUIRRELVM v) +{ + SQObjectPtr &o = stack_get(v,1); + switch(sq_getvmstate(_thread(o))) { + case SQ_VMSTATE_IDLE: + sq_pushstring(v,_SC("idle"),-1); + break; + case SQ_VMSTATE_RUNNING: + sq_pushstring(v,_SC("running"),-1); + break; + case SQ_VMSTATE_SUSPENDED: + sq_pushstring(v,_SC("suspended"),-1); + break; + default: + return sq_throwerror(v,_SC("internal VM error")); + } + return 1; +} + +static SQInteger thread_getstackinfos(HSQUIRRELVM v) +{ + SQObjectPtr o = stack_get(v,1); + if(sq_type(o) == OT_THREAD) { + SQVM *thread = _thread(o); + SQInteger threadtop = sq_gettop(thread); + SQInteger level; + sq_getinteger(v,-1,&level); + SQRESULT res = __getcallstackinfos(thread,level); + if(SQ_FAILED(res)) + { + sq_settop(thread,threadtop); + if(sq_type(thread->_lasterror) == OT_STRING) { + sq_throwerror(v,_stringval(thread->_lasterror)); + } + else { + sq_throwerror(v,_SC("unknown error")); + } + } + if(res > 0) { + //some result + sq_move(v,thread,-1); + sq_settop(thread,threadtop); + return 1; + } + //no result + sq_settop(thread,threadtop); + return 0; + + } + return sq_throwerror(v,_SC("wrong parameter")); +} + +const SQRegFunction SQSharedState::_thread_default_delegate_funcz[] = { + {_SC("call"), thread_call, -1, _SC("v")}, + {_SC("wakeup"), thread_wakeup, -1, _SC("v")}, + {_SC("wakeupthrow"), thread_wakeupthrow, -2, _SC("v.b")}, + {_SC("getstatus"), thread_getstatus, 1, _SC("v")}, + {_SC("weakref"),obj_delegate_weakref,1, NULL }, + {_SC("getstackinfos"),thread_getstackinfos,2, _SC("vn")}, + {_SC("tostring"),default_delegate_tostring,1, _SC(".")}, + {NULL,(SQFUNCTION)0,0,NULL} +}; + +static SQInteger class_getattributes(HSQUIRRELVM v) +{ + return SQ_SUCCEEDED(sq_getattributes(v,-2))?1:SQ_ERROR; +} + +static SQInteger class_setattributes(HSQUIRRELVM v) +{ + return SQ_SUCCEEDED(sq_setattributes(v,-3))?1:SQ_ERROR; +} + +static SQInteger class_instance(HSQUIRRELVM v) +{ + return SQ_SUCCEEDED(sq_createinstance(v,-1))?1:SQ_ERROR; +} + +static SQInteger class_getbase(HSQUIRRELVM v) +{ + return SQ_SUCCEEDED(sq_getbase(v,-1))?1:SQ_ERROR; +} + +static SQInteger class_newmember(HSQUIRRELVM v) +{ + SQInteger top = sq_gettop(v); + SQBool bstatic = SQFalse; + if(top == 5) + { + sq_tobool(v,-1,&bstatic); + sq_pop(v,1); + } + + if(top < 4) { + sq_pushnull(v); + } + return SQ_SUCCEEDED(sq_newmember(v,-4,bstatic))?1:SQ_ERROR; +} + +static SQInteger class_rawnewmember(HSQUIRRELVM v) +{ + SQInteger top = sq_gettop(v); + SQBool bstatic = SQFalse; + if(top == 5) + { + sq_tobool(v,-1,&bstatic); + sq_pop(v,1); + } + + if(top < 4) { + sq_pushnull(v); + } + return SQ_SUCCEEDED(sq_rawnewmember(v,-4,bstatic))?1:SQ_ERROR; +} + +const SQRegFunction SQSharedState::_class_default_delegate_funcz[] = { + {_SC("getattributes"), class_getattributes, 2, _SC("y.")}, + {_SC("setattributes"), class_setattributes, 3, _SC("y..")}, + {_SC("rawget"),container_rawget,2, _SC("y")}, + {_SC("rawset"),container_rawset,3, _SC("y")}, + {_SC("rawin"),container_rawexists,2, _SC("y")}, + {_SC("weakref"),obj_delegate_weakref,1, NULL }, + {_SC("tostring"),default_delegate_tostring,1, _SC(".")}, + {_SC("instance"),class_instance,1, _SC("y")}, + {_SC("getbase"),class_getbase,1, _SC("y")}, + {_SC("newmember"),class_newmember,-3, _SC("y")}, + {_SC("rawnewmember"),class_rawnewmember,-3, _SC("y")}, + {NULL,(SQFUNCTION)0,0,NULL} +}; + + +static SQInteger instance_getclass(HSQUIRRELVM v) +{ + if(SQ_SUCCEEDED(sq_getclass(v,1))) + return 1; + return SQ_ERROR; +} + +const SQRegFunction SQSharedState::_instance_default_delegate_funcz[] = { + {_SC("getclass"), instance_getclass, 1, _SC("x")}, + {_SC("rawget"),container_rawget,2, _SC("x")}, + {_SC("rawset"),container_rawset,3, _SC("x")}, + {_SC("rawin"),container_rawexists,2, _SC("x")}, + {_SC("weakref"),obj_delegate_weakref,1, NULL }, + {_SC("tostring"),default_delegate_tostring,1, _SC(".")}, + {NULL,(SQFUNCTION)0,0,NULL} +}; + +static SQInteger weakref_ref(HSQUIRRELVM v) +{ + if(SQ_FAILED(sq_getweakrefval(v,1))) + return SQ_ERROR; + return 1; +} + +const SQRegFunction SQSharedState::_weakref_default_delegate_funcz[] = { + {_SC("ref"),weakref_ref,1, _SC("r")}, + {_SC("weakref"),obj_delegate_weakref,1, NULL }, + {_SC("tostring"),default_delegate_tostring,1, _SC(".")}, + {NULL,(SQFUNCTION)0,0,NULL} +}; diff --git a/src/vscript/squirrel/squirrel/sqclass.cpp b/src/vscript/squirrel/squirrel/sqclass.cpp new file mode 100644 index 00000000..53a29763 --- /dev/null +++ b/src/vscript/squirrel/squirrel/sqclass.cpp @@ -0,0 +1,213 @@ +/* + see copyright notice in squirrel.h +*/ +#include "sqpcheader.h" +#include "sqvm.h" +#include "sqtable.h" +#include "sqclass.h" +#include "sqfuncproto.h" +#include "sqclosure.h" + + + +SQClass::SQClass(SQSharedState *ss,SQClass *base) +{ + _base = base; + _typetag = 0; + _hook = NULL; + _udsize = 0; + _locked = false; + _constructoridx = -1; + if(_base) { + _constructoridx = _base->_constructoridx; + _udsize = _base->_udsize; + _defaultvalues.copy(base->_defaultvalues); + _methods.copy(base->_methods); + _COPY_VECTOR(_metamethods,base->_metamethods,MT_LAST); + __ObjAddRef(_base); + } + _members = base?base->_members->Clone() : SQTable::Create(ss,0); + __ObjAddRef(_members); + + INIT_CHAIN(); + ADD_TO_CHAIN(&_sharedstate->_gc_chain, this); +} + +void SQClass::Finalize() { + _attributes.Null(); + _NULL_SQOBJECT_VECTOR(_defaultvalues,_defaultvalues.size()); + _methods.resize(0); + _NULL_SQOBJECT_VECTOR(_metamethods,MT_LAST); + __ObjRelease(_members); + if(_base) { + __ObjRelease(_base); + } +} + +SQClass::~SQClass() +{ + REMOVE_FROM_CHAIN(&_sharedstate->_gc_chain, this); + Finalize(); +} + +bool SQClass::NewSlot(SQSharedState *ss,const SQObjectPtr &key,const SQObjectPtr &val,bool bstatic) +{ + SQObjectPtr temp; + bool belongs_to_static_table = sq_type(val) == OT_CLOSURE || sq_type(val) == OT_NATIVECLOSURE || bstatic; + if(_locked && !belongs_to_static_table) + return false; //the class already has an instance so cannot be modified + if(_members->Get(key,temp) && _isfield(temp)) //overrides the default value + { + _defaultvalues[_member_idx(temp)].val = val; + return true; + } + if (_members->CountUsed() >= MEMBER_MAX_COUNT) { + return false; + } + if(belongs_to_static_table) { + SQInteger mmidx; + if((sq_type(val) == OT_CLOSURE || sq_type(val) == OT_NATIVECLOSURE) && + (mmidx = ss->GetMetaMethodIdxByName(key)) != -1) { + _metamethods[mmidx] = val; + } + else { + SQObjectPtr theval = val; + if(_base && sq_type(val) == OT_CLOSURE) { + theval = _closure(val)->Clone(); + _closure(theval)->_base = _base; + __ObjAddRef(_base); //ref for the closure + } + if(sq_type(temp) == OT_NULL) { + bool isconstructor; + SQVM::IsEqual(ss->_constructoridx, key, isconstructor); + if(isconstructor) { + _constructoridx = (SQInteger)_methods.size(); + } + SQClassMember m; + m.val = theval; + _members->NewSlot(key,SQObjectPtr(_make_method_idx(_methods.size()))); + _methods.push_back(m); + } + else { + _methods[_member_idx(temp)].val = theval; + } + } + return true; + } + SQClassMember m; + m.val = val; + _members->NewSlot(key,SQObjectPtr(_make_field_idx(_defaultvalues.size()))); + _defaultvalues.push_back(m); + return true; +} + +SQInstance *SQClass::CreateInstance() +{ + if(!_locked) Lock(); + return SQInstance::Create(_opt_ss(this),this); +} + +SQInteger SQClass::Next(const SQObjectPtr &refpos, SQObjectPtr &outkey, SQObjectPtr &outval) +{ + SQObjectPtr oval; + SQInteger idx = _members->Next(false,refpos,outkey,oval); + if(idx != -1) { + if(_ismethod(oval)) { + outval = _methods[_member_idx(oval)].val; + } + else { + SQObjectPtr &o = _defaultvalues[_member_idx(oval)].val; + outval = _realval(o); + } + } + return idx; +} + +bool SQClass::SetAttributes(const SQObjectPtr &key,const SQObjectPtr &val) +{ + SQObjectPtr idx; + if(_members->Get(key,idx)) { + if(_isfield(idx)) + _defaultvalues[_member_idx(idx)].attrs = val; + else + _methods[_member_idx(idx)].attrs = val; + return true; + } + return false; +} + +bool SQClass::GetAttributes(const SQObjectPtr &key,SQObjectPtr &outval) +{ + SQObjectPtr idx; + if(_members->Get(key,idx)) { + outval = (_isfield(idx)?_defaultvalues[_member_idx(idx)].attrs:_methods[_member_idx(idx)].attrs); + return true; + } + return false; +} + +/////////////////////////////////////////////////////////////////////// +void SQInstance::Init(SQSharedState *ss) +{ + _userpointer = NULL; + _hook = NULL; + __ObjAddRef(_class); + _delegate = _class->_members; + INIT_CHAIN(); + ADD_TO_CHAIN(&_sharedstate->_gc_chain, this); +} + +SQInstance::SQInstance(SQSharedState *ss, SQClass *c, SQInteger memsize) +{ + _memsize = memsize; + _class = c; + SQUnsignedInteger nvalues = _class->_defaultvalues.size(); + for(SQUnsignedInteger n = 0; n < nvalues; n++) { + new (&_values[n]) SQObjectPtr(_class->_defaultvalues[n].val); + } + Init(ss); +} + +SQInstance::SQInstance(SQSharedState *ss, SQInstance *i, SQInteger memsize) +{ + _memsize = memsize; + _class = i->_class; + SQUnsignedInteger nvalues = _class->_defaultvalues.size(); + for(SQUnsignedInteger n = 0; n < nvalues; n++) { + new (&_values[n]) SQObjectPtr(i->_values[n]); + } + Init(ss); +} + +void SQInstance::Finalize() +{ + SQUnsignedInteger nvalues = _class->_defaultvalues.size(); + __ObjRelease(_class); + _NULL_SQOBJECT_VECTOR(_values,nvalues); +} + +SQInstance::~SQInstance() +{ + REMOVE_FROM_CHAIN(&_sharedstate->_gc_chain, this); + if(_class){ Finalize(); } //if _class is null it was already finalized by the GC +} + +bool SQInstance::GetMetaMethod(SQVM* SQ_UNUSED_ARG(v),SQMetaMethod mm,SQObjectPtr &res) +{ + if(sq_type(_class->_metamethods[mm]) != OT_NULL) { + res = _class->_metamethods[mm]; + return true; + } + return false; +} + +bool SQInstance::InstanceOf(SQClass *trg) +{ + SQClass *parent = _class; + while(parent != NULL) { + if(parent == trg) + return true; + parent = parent->_base; + } + return false; +} diff --git a/src/vscript/squirrel/squirrel/sqclass.h b/src/vscript/squirrel/squirrel/sqclass.h new file mode 100644 index 00000000..60d3d21b --- /dev/null +++ b/src/vscript/squirrel/squirrel/sqclass.h @@ -0,0 +1,163 @@ +/* see copyright notice in squirrel.h */ +#ifndef _SQCLASS_H_ +#define _SQCLASS_H_ + +struct SQInstance; + +struct SQClassMember { + SQObjectPtr val; + SQObjectPtr attrs; + void Null() { + val.Null(); + attrs.Null(); + } +}; + +typedef sqvector SQClassMemberVec; + +#define MEMBER_TYPE_METHOD 0x01000000 +#define MEMBER_TYPE_FIELD 0x02000000 +#define MEMBER_MAX_COUNT 0x00FFFFFF + +#define _ismethod(o) (_integer(o)&MEMBER_TYPE_METHOD) +#define _isfield(o) (_integer(o)&MEMBER_TYPE_FIELD) +#define _make_method_idx(i) ((SQInteger)(MEMBER_TYPE_METHOD|i)) +#define _make_field_idx(i) ((SQInteger)(MEMBER_TYPE_FIELD|i)) +#define _member_type(o) (_integer(o)&0xFF000000) +#define _member_idx(o) (_integer(o)&0x00FFFFFF) + +struct SQClass : public CHAINABLE_OBJ +{ + SQClass(SQSharedState *ss,SQClass *base); +public: + static SQClass* Create(SQSharedState *ss,SQClass *base) { + SQClass *newclass = (SQClass *)SQ_MALLOC(sizeof(SQClass)); + new (newclass) SQClass(ss, base); + return newclass; + } + ~SQClass(); + bool NewSlot(SQSharedState *ss, const SQObjectPtr &key,const SQObjectPtr &val,bool bstatic); + bool Get(const SQObjectPtr &key,SQObjectPtr &val) { + if(_members->Get(key,val)) { + if(_isfield(val)) { + SQObjectPtr &o = _defaultvalues[_member_idx(val)].val; + val = _realval(o); + } + else { + val = _methods[_member_idx(val)].val; + } + return true; + } + return false; + } + bool GetConstructor(SQObjectPtr &ctor) + { + if(_constructoridx != -1) { + ctor = _methods[_constructoridx].val; + return true; + } + return false; + } + bool SetAttributes(const SQObjectPtr &key,const SQObjectPtr &val); + bool GetAttributes(const SQObjectPtr &key,SQObjectPtr &outval); + void Lock() { _locked = true; if(_base) _base->Lock(); } + void Release() { + if (_hook) { _hook(_typetag,0);} + sq_delete(this, SQClass); + } + void Finalize(); +#ifndef NO_GARBAGE_COLLECTOR + void Mark(SQCollectable ** ); + SQObjectType GetType() {return OT_CLASS;} +#endif + SQInteger Next(const SQObjectPtr &refpos, SQObjectPtr &outkey, SQObjectPtr &outval); + SQInstance *CreateInstance(); + SQTable *_members; + SQClass *_base; + SQClassMemberVec _defaultvalues; + SQClassMemberVec _methods; + SQObjectPtr _metamethods[MT_LAST]; + SQObjectPtr _attributes; + SQUserPointer _typetag; + SQRELEASEHOOK _hook; + bool _locked; + SQInteger _constructoridx; + SQInteger _udsize; +}; + +#define calcinstancesize(_theclass_) \ + (_theclass_->_udsize + sq_aligning(sizeof(SQInstance) + (sizeof(SQObjectPtr)*(_theclass_->_defaultvalues.size()>0?_theclass_->_defaultvalues.size()-1:0)))) + +struct SQInstance : public SQDelegable +{ + void Init(SQSharedState *ss); + SQInstance(SQSharedState *ss, SQClass *c, SQInteger memsize); + SQInstance(SQSharedState *ss, SQInstance *c, SQInteger memsize); +public: + static SQInstance* Create(SQSharedState *ss,SQClass *theclass) { + + SQInteger size = calcinstancesize(theclass); + SQInstance *newinst = (SQInstance *)SQ_MALLOC(size); + new (newinst) SQInstance(ss, theclass,size); + if(theclass->_udsize) { + newinst->_userpointer = ((unsigned char *)newinst) + (size - theclass->_udsize); + } + return newinst; + } + SQInstance *Clone(SQSharedState *ss) + { + SQInteger size = calcinstancesize(_class); + SQInstance *newinst = (SQInstance *)SQ_MALLOC(size); + new (newinst) SQInstance(ss, this,size); + if(_class->_udsize) { + newinst->_userpointer = ((unsigned char *)newinst) + (size - _class->_udsize); + } + return newinst; + } + ~SQInstance(); + bool Get(const SQObjectPtr &key,SQObjectPtr &val) { + if(_class->_members->Get(key,val)) { + if(_isfield(val)) { + SQObjectPtr &o = _values[_member_idx(val)]; + val = _realval(o); + } + else { + val = _class->_methods[_member_idx(val)].val; + } + return true; + } + return false; + } + bool Set(const SQObjectPtr &key,const SQObjectPtr &val) { + SQObjectPtr idx; + if(_class->_members->Get(key,idx) && _isfield(idx)) { + _values[_member_idx(idx)] = val; + return true; + } + return false; + } + void Release() { + _uiRef++; + if (_hook) { _hook(_userpointer,0);} + _uiRef--; + if(_uiRef > 0) return; + SQInteger size = _memsize; + this->~SQInstance(); + SQ_FREE(this, size); + } + void Finalize(); +#ifndef NO_GARBAGE_COLLECTOR + void Mark(SQCollectable ** ); + SQObjectType GetType() {return OT_INSTANCE;} +#endif + bool InstanceOf(SQClass *trg); + bool GetMetaMethod(SQVM *v,SQMetaMethod mm,SQObjectPtr &res); + + SQClass *_class; + SQUserPointer _userpointer; + SQRELEASEHOOK _hook; + SQInteger _memsize; + SQObjectPtr _values[1]; +}; + +#endif //_SQCLASS_H_ diff --git a/src/vscript/squirrel/squirrel/sqclosure.h b/src/vscript/squirrel/squirrel/sqclosure.h new file mode 100644 index 00000000..66495b94 --- /dev/null +++ b/src/vscript/squirrel/squirrel/sqclosure.h @@ -0,0 +1,201 @@ +/* see copyright notice in squirrel.h */ +#ifndef _SQCLOSURE_H_ +#define _SQCLOSURE_H_ + + +#define _CALC_CLOSURE_SIZE(func) (sizeof(SQClosure) + (func->_noutervalues*sizeof(SQObjectPtr)) + (func->_ndefaultparams*sizeof(SQObjectPtr))) + +struct SQFunctionProto; +struct SQClass; +struct SQClosure : public CHAINABLE_OBJ +{ +private: + SQClosure(SQSharedState *ss,SQFunctionProto *func){_function = func; __ObjAddRef(_function); _base = NULL; INIT_CHAIN();ADD_TO_CHAIN(&_ss(this)->_gc_chain,this); _env = NULL; _root=NULL;} +public: + static SQClosure *Create(SQSharedState *ss,SQFunctionProto *func,SQWeakRef *root){ + SQInteger size = _CALC_CLOSURE_SIZE(func); + SQClosure *nc=(SQClosure*)SQ_MALLOC(size); + new (nc) SQClosure(ss,func); + nc->_outervalues = (SQObjectPtr *)(nc + 1); + nc->_defaultparams = &nc->_outervalues[func->_noutervalues]; + nc->_root = root; + __ObjAddRef(nc->_root); + _CONSTRUCT_VECTOR(SQObjectPtr,func->_noutervalues,nc->_outervalues); + _CONSTRUCT_VECTOR(SQObjectPtr,func->_ndefaultparams,nc->_defaultparams); + return nc; + } + void Release(){ + SQFunctionProto *f = _function; + SQInteger size = _CALC_CLOSURE_SIZE(f); + _DESTRUCT_VECTOR(SQObjectPtr,f->_noutervalues,_outervalues); + _DESTRUCT_VECTOR(SQObjectPtr,f->_ndefaultparams,_defaultparams); + __ObjRelease(_function); + this->~SQClosure(); + sq_vm_free(this,size); + } + void SetRoot(SQWeakRef *r) + { + __ObjRelease(_root); + _root = r; + __ObjAddRef(_root); + } + SQClosure *Clone() + { + SQFunctionProto *f = _function; + SQClosure * ret = SQClosure::Create(_opt_ss(this),f,_root); + ret->_env = _env; + if(ret->_env) __ObjAddRef(ret->_env); + _COPY_VECTOR(ret->_outervalues,_outervalues,f->_noutervalues); + _COPY_VECTOR(ret->_defaultparams,_defaultparams,f->_ndefaultparams); + return ret; + } + ~SQClosure(); + + bool Save(SQVM *v,SQUserPointer up,SQWRITEFUNC write); + static bool Load(SQVM *v,SQUserPointer up,SQREADFUNC read,SQObjectPtr &ret); +#ifndef NO_GARBAGE_COLLECTOR + void Mark(SQCollectable **chain); + void Finalize(){ + SQFunctionProto *f = _function; + _NULL_SQOBJECT_VECTOR(_outervalues,f->_noutervalues); + _NULL_SQOBJECT_VECTOR(_defaultparams,f->_ndefaultparams); + } + SQObjectType GetType() {return OT_CLOSURE;} +#endif + SQWeakRef *_env; + SQWeakRef *_root; + SQClass *_base; + SQFunctionProto *_function; + SQObjectPtr *_outervalues; + SQObjectPtr *_defaultparams; +}; + +////////////////////////////////////////////// +struct SQOuter : public CHAINABLE_OBJ +{ + +private: + SQOuter(SQSharedState *ss, SQObjectPtr *outer){_valptr = outer; _next = NULL; INIT_CHAIN(); ADD_TO_CHAIN(&_ss(this)->_gc_chain,this); } + +public: + static SQOuter *Create(SQSharedState *ss, SQObjectPtr *outer) + { + SQOuter *nc = (SQOuter*)SQ_MALLOC(sizeof(SQOuter)); + new (nc) SQOuter(ss, outer); + return nc; + } + ~SQOuter() { REMOVE_FROM_CHAIN(&_ss(this)->_gc_chain,this); } + + void Release() + { + this->~SQOuter(); + sq_vm_free(this,sizeof(SQOuter)); + } + +#ifndef NO_GARBAGE_COLLECTOR + void Mark(SQCollectable **chain); + void Finalize() { _value.Null(); } + SQObjectType GetType() {return OT_OUTER;} +#endif + + SQObjectPtr *_valptr; /* pointer to value on stack, or _value below */ + SQInteger _idx; /* idx in stack array, for relocation */ + SQObjectPtr _value; /* value of outer after stack frame is closed */ + SQOuter *_next; /* pointer to next outer when frame is open */ +}; + +////////////////////////////////////////////// +struct SQGenerator : public CHAINABLE_OBJ +{ + enum SQGeneratorState{eRunning,eSuspended,eDead}; +private: + SQGenerator(SQSharedState *ss,SQClosure *closure){_closure=closure;_state=eRunning;_ci._generator=NULL;INIT_CHAIN();ADD_TO_CHAIN(&_ss(this)->_gc_chain,this);} +public: + static SQGenerator *Create(SQSharedState *ss,SQClosure *closure){ + SQGenerator *nc=(SQGenerator*)SQ_MALLOC(sizeof(SQGenerator)); + new (nc) SQGenerator(ss,closure); + return nc; + } + ~SQGenerator() + { + REMOVE_FROM_CHAIN(&_ss(this)->_gc_chain,this); + } + void Kill(){ + _state=eDead; + _stack.resize(0); + _closure.Null();} + void Release(){ + sq_delete(this,SQGenerator); + } + + bool Yield(SQVM *v,SQInteger target); + bool Resume(SQVM *v,SQObjectPtr &dest); +#ifndef NO_GARBAGE_COLLECTOR + void Mark(SQCollectable **chain); + void Finalize(){_stack.resize(0);_closure.Null();} + SQObjectType GetType() {return OT_GENERATOR;} +#endif + SQObjectPtr _closure; + SQObjectPtrVec _stack; + SQVM::CallInfo _ci; + ExceptionsTraps _etraps; + SQGeneratorState _state; +}; + +#define _CALC_NATVIVECLOSURE_SIZE(noutervalues) (sizeof(SQNativeClosure) + (noutervalues*sizeof(SQObjectPtr))) + +struct SQNativeClosure : public CHAINABLE_OBJ +{ +private: + SQNativeClosure(SQSharedState *ss,SQFUNCTION func){_function=func;INIT_CHAIN();ADD_TO_CHAIN(&_ss(this)->_gc_chain,this); _env = NULL;} +public: + static SQNativeClosure *Create(SQSharedState *ss,SQFUNCTION func,SQInteger nouters) + { + SQInteger size = _CALC_NATVIVECLOSURE_SIZE(nouters); + SQNativeClosure *nc=(SQNativeClosure*)SQ_MALLOC(size); + new (nc) SQNativeClosure(ss,func); + nc->_outervalues = (SQObjectPtr *)(nc + 1); + nc->_noutervalues = nouters; + _CONSTRUCT_VECTOR(SQObjectPtr,nc->_noutervalues,nc->_outervalues); + return nc; + } + SQNativeClosure *Clone() + { + SQNativeClosure * ret = SQNativeClosure::Create(_opt_ss(this),_function,_noutervalues); + ret->_env = _env; + if(ret->_env) __ObjAddRef(ret->_env); + ret->_name = _name; + _COPY_VECTOR(ret->_outervalues,_outervalues,_noutervalues); + ret->_typecheck.copy(_typecheck); + ret->_nparamscheck = _nparamscheck; + return ret; + } + ~SQNativeClosure() + { + __ObjRelease(_env); + REMOVE_FROM_CHAIN(&_ss(this)->_gc_chain,this); + } + void Release(){ + SQInteger size = _CALC_NATVIVECLOSURE_SIZE(_noutervalues); + _DESTRUCT_VECTOR(SQObjectPtr,_noutervalues,_outervalues); + this->~SQNativeClosure(); + sq_free(this,size); + } + +#ifndef NO_GARBAGE_COLLECTOR + void Mark(SQCollectable **chain); + void Finalize() { _NULL_SQOBJECT_VECTOR(_outervalues,_noutervalues); } + SQObjectType GetType() {return OT_NATIVECLOSURE;} +#endif + SQInteger _nparamscheck; + SQIntVec _typecheck; + SQObjectPtr *_outervalues; + SQUnsignedInteger _noutervalues; + SQWeakRef *_env; + SQFUNCTION _function; + SQObjectPtr _name; +}; + + + +#endif //_SQCLOSURE_H_ diff --git a/src/vscript/squirrel/squirrel/sqcompiler.cpp b/src/vscript/squirrel/squirrel/sqcompiler.cpp new file mode 100644 index 00000000..095edd71 --- /dev/null +++ b/src/vscript/squirrel/squirrel/sqcompiler.cpp @@ -0,0 +1,1611 @@ +/* + see copyright notice in squirrel.h +*/ +#include "sqpcheader.h" +#ifndef NO_COMPILER +#include +#include +#include "sqopcodes.h" +#include "sqstring.h" +#include "sqfuncproto.h" +#include "sqcompiler.h" +#include "sqfuncstate.h" +#include "sqlexer.h" +#include "sqvm.h" +#include "sqtable.h" + +#define EXPR 1 +#define OBJECT 2 +#define BASE 3 +#define LOCAL 4 +#define OUTER 5 + +struct SQExpState { + SQInteger etype; /* expr. type; one of EXPR, OBJECT, BASE, OUTER or LOCAL */ + SQInteger epos; /* expr. location on stack; -1 for OBJECT and BASE */ + bool donot_get; /* signal not to deref the next value */ +}; + +#define MAX_COMPILER_ERROR_LEN 256 + +struct SQScope { + SQInteger outers; + SQInteger stacksize; +}; + +#define BEGIN_SCOPE() SQScope __oldscope__ = _scope; \ + _scope.outers = _fs->_outers; \ + _scope.stacksize = _fs->GetStackSize(); + +#define RESOLVE_OUTERS() if(_fs->GetStackSize() != _scope.stacksize) { \ + if(_fs->CountOuters(_scope.stacksize)) { \ + _fs->AddInstruction(_OP_CLOSE,0,_scope.stacksize); \ + } \ + } + +#define END_SCOPE_NO_CLOSE() { if(_fs->GetStackSize() != _scope.stacksize) { \ + _fs->SetStackSize(_scope.stacksize); \ + } \ + _scope = __oldscope__; \ + } + +#define END_SCOPE() { SQInteger oldouters = _fs->_outers;\ + if(_fs->GetStackSize() != _scope.stacksize) { \ + _fs->SetStackSize(_scope.stacksize); \ + if(oldouters != _fs->_outers) { \ + _fs->AddInstruction(_OP_CLOSE,0,_scope.stacksize); \ + } \ + } \ + _scope = __oldscope__; \ + } + +#define BEGIN_BREAKBLE_BLOCK() SQInteger __nbreaks__=_fs->_unresolvedbreaks.size(); \ + SQInteger __ncontinues__=_fs->_unresolvedcontinues.size(); \ + _fs->_breaktargets.push_back(0);_fs->_continuetargets.push_back(0); + +#define END_BREAKBLE_BLOCK(continue_target) {__nbreaks__=_fs->_unresolvedbreaks.size()-__nbreaks__; \ + __ncontinues__=_fs->_unresolvedcontinues.size()-__ncontinues__; \ + if(__ncontinues__>0)ResolveContinues(_fs,__ncontinues__,continue_target); \ + if(__nbreaks__>0)ResolveBreaks(_fs,__nbreaks__); \ + _fs->_breaktargets.pop_back();_fs->_continuetargets.pop_back();} + +class SQCompiler +{ +public: + SQCompiler(SQVM *v, SQLEXREADFUNC rg, SQUserPointer up, const SQChar* sourcename, bool raiseerror, bool lineinfo) + { + _vm=v; + _lex.Init(_ss(v), rg, up,ThrowError,this); + _sourcename = SQString::Create(_ss(v), sourcename); + _lineinfo = lineinfo;_raiseerror = raiseerror; + _scope.outers = 0; + _scope.stacksize = 0; + _compilererror[0] = _SC('\0'); + } + static void ThrowError(void *ud, const SQChar *s) { + SQCompiler *c = (SQCompiler *)ud; + c->Error(s); + } + void Error(const SQChar *s, ...) + { + va_list vl; + va_start(vl, s); + scvsprintf(_compilererror, MAX_COMPILER_ERROR_LEN, s, vl); + va_end(vl); + longjmp(_errorjmp,1); + } + void Lex(){ _token = _lex.Lex();} + SQObject Expect(SQInteger tok) + { + + if(_token != tok) { + if(_token == TK_CONSTRUCTOR && tok == TK_IDENTIFIER) { + //do nothing + } + else { + const SQChar *etypename; + if(tok > 255) { + switch(tok) + { + case TK_IDENTIFIER: + etypename = _SC("IDENTIFIER"); + break; + case TK_STRING_LITERAL: + etypename = _SC("STRING_LITERAL"); + break; + case TK_INTEGER: + etypename = _SC("INTEGER"); + break; + case TK_FLOAT: + etypename = _SC("FLOAT"); + break; + default: + etypename = _lex.Tok2Str(tok); + } + Error(_SC("expected '%s'"), etypename); + } + Error(_SC("expected '%c'"), tok); + } + } + SQObjectPtr ret; + switch(tok) + { + case TK_IDENTIFIER: + ret = _fs->CreateString(_lex._svalue); + break; + case TK_STRING_LITERAL: + ret = _fs->CreateString(_lex._svalue,_lex._longstr.size()-1); + break; + case TK_INTEGER: + ret = SQObjectPtr(_lex._nvalue); + break; + case TK_FLOAT: + ret = SQObjectPtr(_lex._fvalue); + break; + } + Lex(); + return ret; + } + bool IsEndOfStatement() { return ((_lex._prevtoken == _SC('\n')) || (_token == SQUIRREL_EOB) || (_token == _SC('}')) || (_token == _SC(';'))); } + void OptionalSemicolon() + { + if(_token == _SC(';')) { Lex(); return; } + if(!IsEndOfStatement()) { + Error(_SC("end of statement expected (; or lf)")); + } + } + void MoveIfCurrentTargetIsLocal() { + SQInteger trg = _fs->TopTarget(); + if(_fs->IsLocal(trg)) { + trg = _fs->PopTarget(); //pops the target and moves it + _fs->AddInstruction(_OP_MOVE, _fs->PushTarget(), trg); + } + } + bool Compile(SQObjectPtr &o) + { + _debugline = 1; + _debugop = 0; + + SQFuncState funcstate(_ss(_vm), NULL,ThrowError,this); + funcstate._name = SQString::Create(_ss(_vm), _SC("main")); + _fs = &funcstate; + _fs->AddParameter(_fs->CreateString(_SC("this"))); + _fs->AddParameter(_fs->CreateString(_SC("vargv"))); + _fs->_varparams = true; + _fs->_sourcename = _sourcename; + SQInteger stacksize = _fs->GetStackSize(); + if(setjmp(_errorjmp) == 0) { + Lex(); + while(_token > 0){ + Statement(); + if(_lex._prevtoken != _SC('}') && _lex._prevtoken != _SC(';')) OptionalSemicolon(); + } + _fs->SetStackSize(stacksize); + _fs->AddLineInfos(_lex._currentline, _lineinfo, true); + _fs->AddInstruction(_OP_RETURN, 0xFF); + _fs->SetStackSize(0); + o =_fs->BuildProto(); +#ifdef _DEBUG_DUMP + _fs->Dump(_funcproto(o)); +#endif + } + else { + if(_raiseerror && _ss(_vm)->_compilererrorhandler) { + _ss(_vm)->_compilererrorhandler(_vm, _compilererror, sq_type(_sourcename) == OT_STRING?_stringval(_sourcename):_SC("unknown"), + _lex._currentline, _lex._currentcolumn); + } + _vm->_lasterror = SQString::Create(_ss(_vm), _compilererror, -1); + return false; + } + return true; + } + void Statements() + { + while(_token != _SC('}') && _token != TK_DEFAULT && _token != TK_CASE) { + Statement(); + if(_lex._prevtoken != _SC('}') && _lex._prevtoken != _SC(';')) OptionalSemicolon(); + } + } + void Statement(bool closeframe = true) + { + _fs->AddLineInfos(_lex._currentline, _lineinfo); + switch(_token){ + case _SC(';'): Lex(); break; + case TK_IF: IfStatement(); break; + case TK_WHILE: WhileStatement(); break; + case TK_DO: DoWhileStatement(); break; + case TK_FOR: ForStatement(); break; + case TK_FOREACH: ForEachStatement(); break; + case TK_SWITCH: SwitchStatement(); break; + case TK_LOCAL: LocalDeclStatement(); break; + case TK_RETURN: + case TK_YIELD: { + SQOpcode op; + if(_token == TK_RETURN) { + op = _OP_RETURN; + } + else { + op = _OP_YIELD; + _fs->_bgenerator = true; + } + Lex(); + if(!IsEndOfStatement()) { + SQInteger retexp = _fs->GetCurrentPos()+1; + CommaExpr(); + if(op == _OP_RETURN && _fs->_traps > 0) + _fs->AddInstruction(_OP_POPTRAP, _fs->_traps, 0); + _fs->_returnexp = retexp; + _fs->AddInstruction(op, 1, _fs->PopTarget(),_fs->GetStackSize()); + } + else{ + if(op == _OP_RETURN && _fs->_traps > 0) + _fs->AddInstruction(_OP_POPTRAP, _fs->_traps ,0); + _fs->_returnexp = -1; + _fs->AddInstruction(op, 0xFF,0,_fs->GetStackSize()); + } + break;} + case TK_BREAK: + if(_fs->_breaktargets.size() <= 0)Error(_SC("'break' has to be in a loop block")); + if(_fs->_breaktargets.top() > 0){ + _fs->AddInstruction(_OP_POPTRAP, _fs->_breaktargets.top(), 0); + } + RESOLVE_OUTERS(); + _fs->AddInstruction(_OP_JMP, 0, -1234); + _fs->_unresolvedbreaks.push_back(_fs->GetCurrentPos()); + Lex(); + break; + case TK_CONTINUE: + if(_fs->_continuetargets.size() <= 0)Error(_SC("'continue' has to be in a loop block")); + if(_fs->_continuetargets.top() > 0) { + _fs->AddInstruction(_OP_POPTRAP, _fs->_continuetargets.top(), 0); + } + RESOLVE_OUTERS(); + _fs->AddInstruction(_OP_JMP, 0, -1234); + _fs->_unresolvedcontinues.push_back(_fs->GetCurrentPos()); + Lex(); + break; + case TK_FUNCTION: + FunctionStatement(); + break; + case TK_CLASS: + ClassStatement(); + break; + case TK_ENUM: + EnumStatement(); + break; + case _SC('{'):{ + BEGIN_SCOPE(); + Lex(); + Statements(); + Expect(_SC('}')); + if(closeframe) { + END_SCOPE(); + } + else { + END_SCOPE_NO_CLOSE(); + } + } + break; + case TK_TRY: + TryCatchStatement(); + break; + case TK_THROW: + Lex(); + CommaExpr(); + _fs->AddInstruction(_OP_THROW, _fs->PopTarget()); + break; + case TK_CONST: + { + Lex(); + SQObject id = Expect(TK_IDENTIFIER); + Expect('='); + SQObject val = ExpectScalar(); + OptionalSemicolon(); + SQTable *enums = _table(_ss(_vm)->_consts); + SQObjectPtr strongid = id; + enums->NewSlot(strongid,SQObjectPtr(val)); + strongid.Null(); + } + break; + default: + CommaExpr(); + _fs->DiscardTarget(); + //_fs->PopTarget(); + break; + } + _fs->SnoozeOpt(); + } + void EmitDerefOp(SQOpcode op) + { + SQInteger val = _fs->PopTarget(); + SQInteger key = _fs->PopTarget(); + SQInteger src = _fs->PopTarget(); + _fs->AddInstruction(op,_fs->PushTarget(),src,key,val); + } + void Emit2ArgsOP(SQOpcode op, SQInteger p3 = 0) + { + SQInteger p2 = _fs->PopTarget(); //src in OP_GET + SQInteger p1 = _fs->PopTarget(); //key in OP_GET + _fs->AddInstruction(op,_fs->PushTarget(), p1, p2, p3); + } + void EmitCompoundArith(SQInteger tok, SQInteger etype, SQInteger pos) + { + /* Generate code depending on the expression type */ + switch(etype) { + case LOCAL:{ + SQInteger p2 = _fs->PopTarget(); //src in OP_GET + SQInteger p1 = _fs->PopTarget(); //key in OP_GET + _fs->PushTarget(p1); + //EmitCompArithLocal(tok, p1, p1, p2); + _fs->AddInstruction(ChooseArithOpByToken(tok),p1, p2, p1, 0); + _fs->SnoozeOpt(); + } + break; + case OBJECT: + case BASE: + { + SQInteger val = _fs->PopTarget(); + SQInteger key = _fs->PopTarget(); + SQInteger src = _fs->PopTarget(); + /* _OP_COMPARITH mixes dest obj and source val in the arg1 */ + _fs->AddInstruction(_OP_COMPARITH, _fs->PushTarget(), (src<<16)|val, key, ChooseCompArithCharByToken(tok)); + } + break; + case OUTER: + { + SQInteger val = _fs->TopTarget(); + SQInteger tmp = _fs->PushTarget(); + _fs->AddInstruction(_OP_GETOUTER, tmp, pos); + _fs->AddInstruction(ChooseArithOpByToken(tok), tmp, val, tmp, 0); + _fs->PopTarget(); + _fs->PopTarget(); + _fs->AddInstruction(_OP_SETOUTER, _fs->PushTarget(), pos, tmp); + } + break; + } + } + void CommaExpr() + { + for(Expression();_token == ',';_fs->PopTarget(), Lex(), CommaExpr()); + } + void Expression() + { + SQExpState es = _es; + _es.etype = EXPR; + _es.epos = -1; + _es.donot_get = false; + LogicalOrExp(); + switch(_token) { + case _SC('='): + case TK_NEWSLOT: + case TK_MINUSEQ: + case TK_PLUSEQ: + case TK_MULEQ: + case TK_DIVEQ: + case TK_MODEQ:{ + SQInteger op = _token; + SQInteger ds = _es.etype; + SQInteger pos = _es.epos; + if(ds == EXPR) Error(_SC("can't assign expression")); + else if(ds == BASE) Error(_SC("'base' cannot be modified")); + Lex(); Expression(); + + switch(op){ + case TK_NEWSLOT: + if(ds == OBJECT || ds == BASE) + EmitDerefOp(_OP_NEWSLOT); + else //if _derefstate != DEREF_NO_DEREF && DEREF_FIELD so is the index of a local + Error(_SC("can't 'create' a local slot")); + break; + case _SC('='): //ASSIGN + switch(ds) { + case LOCAL: + { + SQInteger src = _fs->PopTarget(); + SQInteger dst = _fs->TopTarget(); + _fs->AddInstruction(_OP_MOVE, dst, src); + } + break; + case OBJECT: + case BASE: + EmitDerefOp(_OP_SET); + break; + case OUTER: + { + SQInteger src = _fs->PopTarget(); + SQInteger dst = _fs->PushTarget(); + _fs->AddInstruction(_OP_SETOUTER, dst, pos, src); + } + } + break; + case TK_MINUSEQ: + case TK_PLUSEQ: + case TK_MULEQ: + case TK_DIVEQ: + case TK_MODEQ: + EmitCompoundArith(op, ds, pos); + break; + } + } + break; + case _SC('?'): { + Lex(); + _fs->AddInstruction(_OP_JZ, _fs->PopTarget()); + SQInteger jzpos = _fs->GetCurrentPos(); + SQInteger trg = _fs->PushTarget(); + Expression(); + SQInteger first_exp = _fs->PopTarget(); + if(trg != first_exp) _fs->AddInstruction(_OP_MOVE, trg, first_exp); + SQInteger endfirstexp = _fs->GetCurrentPos(); + _fs->AddInstruction(_OP_JMP, 0, 0); + Expect(_SC(':')); + SQInteger jmppos = _fs->GetCurrentPos(); + Expression(); + SQInteger second_exp = _fs->PopTarget(); + if(trg != second_exp) _fs->AddInstruction(_OP_MOVE, trg, second_exp); + _fs->SetInstructionParam(jmppos, 1, _fs->GetCurrentPos() - jmppos); + _fs->SetInstructionParam(jzpos, 1, endfirstexp - jzpos + 1); + _fs->SnoozeOpt(); + } + break; + } + _es = es; + } + template void INVOKE_EXP(T f) + { + SQExpState es = _es; + _es.etype = EXPR; + _es.epos = -1; + _es.donot_get = false; + (this->*f)(); + _es = es; + } + template void BIN_EXP(SQOpcode op, T f,SQInteger op3 = 0) + { + Lex(); + INVOKE_EXP(f); + SQInteger op1 = _fs->PopTarget();SQInteger op2 = _fs->PopTarget(); + _fs->AddInstruction(op, _fs->PushTarget(), op1, op2, op3); + _es.etype = EXPR; + } + void LogicalOrExp() + { + LogicalAndExp(); + for(;;) if(_token == TK_OR) { + SQInteger first_exp = _fs->PopTarget(); + SQInteger trg = _fs->PushTarget(); + _fs->AddInstruction(_OP_OR, trg, 0, first_exp, 0); + SQInteger jpos = _fs->GetCurrentPos(); + if(trg != first_exp) _fs->AddInstruction(_OP_MOVE, trg, first_exp); + Lex(); INVOKE_EXP(&SQCompiler::LogicalOrExp); + _fs->SnoozeOpt(); + SQInteger second_exp = _fs->PopTarget(); + if(trg != second_exp) _fs->AddInstruction(_OP_MOVE, trg, second_exp); + _fs->SnoozeOpt(); + _fs->SetInstructionParam(jpos, 1, (_fs->GetCurrentPos() - jpos)); + _es.etype = EXPR; + break; + }else return; + } + void LogicalAndExp() + { + BitwiseOrExp(); + for(;;) switch(_token) { + case TK_AND: { + SQInteger first_exp = _fs->PopTarget(); + SQInteger trg = _fs->PushTarget(); + _fs->AddInstruction(_OP_AND, trg, 0, first_exp, 0); + SQInteger jpos = _fs->GetCurrentPos(); + if(trg != first_exp) _fs->AddInstruction(_OP_MOVE, trg, first_exp); + Lex(); INVOKE_EXP(&SQCompiler::LogicalAndExp); + _fs->SnoozeOpt(); + SQInteger second_exp = _fs->PopTarget(); + if(trg != second_exp) _fs->AddInstruction(_OP_MOVE, trg, second_exp); + _fs->SnoozeOpt(); + _fs->SetInstructionParam(jpos, 1, (_fs->GetCurrentPos() - jpos)); + _es.etype = EXPR; + break; + } + + default: + return; + } + } + void BitwiseOrExp() + { + BitwiseXorExp(); + for(;;) if(_token == _SC('|')) + {BIN_EXP(_OP_BITW, &SQCompiler::BitwiseXorExp,BW_OR); + }else return; + } + void BitwiseXorExp() + { + BitwiseAndExp(); + for(;;) if(_token == _SC('^')) + {BIN_EXP(_OP_BITW, &SQCompiler::BitwiseAndExp,BW_XOR); + }else return; + } + void BitwiseAndExp() + { + EqExp(); + for(;;) if(_token == _SC('&')) + {BIN_EXP(_OP_BITW, &SQCompiler::EqExp,BW_AND); + }else return; + } + void EqExp() + { + CompExp(); + for(;;) switch(_token) { + case TK_EQ: BIN_EXP(_OP_EQ, &SQCompiler::CompExp); break; + case TK_NE: BIN_EXP(_OP_NE, &SQCompiler::CompExp); break; + case TK_3WAYSCMP: BIN_EXP(_OP_CMP, &SQCompiler::CompExp,CMP_3W); break; + default: return; + } + } + void CompExp() + { + ShiftExp(); + for(;;) switch(_token) { + case _SC('>'): BIN_EXP(_OP_CMP, &SQCompiler::ShiftExp,CMP_G); break; + case _SC('<'): BIN_EXP(_OP_CMP, &SQCompiler::ShiftExp,CMP_L); break; + case TK_GE: BIN_EXP(_OP_CMP, &SQCompiler::ShiftExp,CMP_GE); break; + case TK_LE: BIN_EXP(_OP_CMP, &SQCompiler::ShiftExp,CMP_LE); break; + case TK_IN: BIN_EXP(_OP_EXISTS, &SQCompiler::ShiftExp); break; + case TK_INSTANCEOF: BIN_EXP(_OP_INSTANCEOF, &SQCompiler::ShiftExp); break; + default: return; + } + } + void ShiftExp() + { + PlusExp(); + for(;;) switch(_token) { + case TK_USHIFTR: BIN_EXP(_OP_BITW, &SQCompiler::PlusExp,BW_USHIFTR); break; + case TK_SHIFTL: BIN_EXP(_OP_BITW, &SQCompiler::PlusExp,BW_SHIFTL); break; + case TK_SHIFTR: BIN_EXP(_OP_BITW, &SQCompiler::PlusExp,BW_SHIFTR); break; + default: return; + } + } + SQOpcode ChooseArithOpByToken(SQInteger tok) + { + switch(tok) { + case TK_PLUSEQ: case '+': return _OP_ADD; + case TK_MINUSEQ: case '-': return _OP_SUB; + case TK_MULEQ: case '*': return _OP_MUL; + case TK_DIVEQ: case '/': return _OP_DIV; + case TK_MODEQ: case '%': return _OP_MOD; + default: assert(0); + } + return _OP_ADD; + } + SQInteger ChooseCompArithCharByToken(SQInteger tok) + { + SQInteger oper; + switch(tok){ + case TK_MINUSEQ: oper = '-'; break; + case TK_PLUSEQ: oper = '+'; break; + case TK_MULEQ: oper = '*'; break; + case TK_DIVEQ: oper = '/'; break; + case TK_MODEQ: oper = '%'; break; + default: oper = 0; //shut up compiler + assert(0); break; + }; + return oper; + } + void PlusExp() + { + MultExp(); + for(;;) switch(_token) { + case _SC('+'): case _SC('-'): + BIN_EXP(ChooseArithOpByToken(_token), &SQCompiler::MultExp); break; + default: return; + } + } + + void MultExp() + { + PrefixedExpr(); + for(;;) switch(_token) { + case _SC('*'): case _SC('/'): case _SC('%'): + BIN_EXP(ChooseArithOpByToken(_token), &SQCompiler::PrefixedExpr); break; + default: return; + } + } + //if 'pos' != -1 the previous variable is a local variable + void PrefixedExpr() + { + SQInteger pos = Factor(); + for(;;) { + switch(_token) { + case _SC('.'): + pos = -1; + Lex(); + + _fs->AddInstruction(_OP_LOAD, _fs->PushTarget(), _fs->GetConstant(Expect(TK_IDENTIFIER))); + if(_es.etype==BASE) { + Emit2ArgsOP(_OP_GET); + pos = _fs->TopTarget(); + _es.etype = EXPR; + _es.epos = pos; + } + else { + if(NeedGet()) { + Emit2ArgsOP(_OP_GET); + } + _es.etype = OBJECT; + } + break; + case _SC('['): + if(_lex._prevtoken == _SC('\n')) Error(_SC("cannot break deref/or comma needed after [exp]=exp slot declaration")); + Lex(); Expression(); Expect(_SC(']')); + pos = -1; + if(_es.etype==BASE) { + Emit2ArgsOP(_OP_GET); + pos = _fs->TopTarget(); + _es.etype = EXPR; + _es.epos = pos; + } + else { + if(NeedGet()) { + Emit2ArgsOP(_OP_GET); + } + _es.etype = OBJECT; + } + break; + case TK_MINUSMINUS: + case TK_PLUSPLUS: + { + if(IsEndOfStatement()) return; + SQInteger diff = (_token==TK_MINUSMINUS) ? -1 : 1; + Lex(); + switch(_es.etype) + { + case EXPR: Error(_SC("can't '++' or '--' an expression")); break; + case OBJECT: + case BASE: + if(_es.donot_get == true) { Error(_SC("can't '++' or '--' an expression")); break; } //mmh dor this make sense? + Emit2ArgsOP(_OP_PINC, diff); + break; + case LOCAL: { + SQInteger src = _fs->PopTarget(); + _fs->AddInstruction(_OP_PINCL, _fs->PushTarget(), src, 0, diff); + } + break; + case OUTER: { + SQInteger tmp1 = _fs->PushTarget(); + SQInteger tmp2 = _fs->PushTarget(); + _fs->AddInstruction(_OP_GETOUTER, tmp2, _es.epos); + _fs->AddInstruction(_OP_PINCL, tmp1, tmp2, 0, diff); + _fs->AddInstruction(_OP_SETOUTER, tmp2, _es.epos, tmp2); + _fs->PopTarget(); + } + } + } + return; + break; + case _SC('('): + switch(_es.etype) { + case OBJECT: { + SQInteger key = _fs->PopTarget(); /* location of the key */ + SQInteger table = _fs->PopTarget(); /* location of the object */ + SQInteger closure = _fs->PushTarget(); /* location for the closure */ + SQInteger ttarget = _fs->PushTarget(); /* location for 'this' pointer */ + _fs->AddInstruction(_OP_PREPCALL, closure, key, table, ttarget); + } + break; + case BASE: + //Emit2ArgsOP(_OP_GET); + _fs->AddInstruction(_OP_MOVE, _fs->PushTarget(), 0); + break; + case OUTER: + _fs->AddInstruction(_OP_GETOUTER, _fs->PushTarget(), _es.epos); + _fs->AddInstruction(_OP_MOVE, _fs->PushTarget(), 0); + break; + default: + _fs->AddInstruction(_OP_MOVE, _fs->PushTarget(), 0); + } + _es.etype = EXPR; + Lex(); + FunctionCallArgs(); + break; + default: return; + } + } + } + SQInteger Factor() + { + //_es.etype = EXPR; + switch(_token) + { + case TK_STRING_LITERAL: + _fs->AddInstruction(_OP_LOAD, _fs->PushTarget(), _fs->GetConstant(_fs->CreateString(_lex._svalue,_lex._longstr.size()-1))); + Lex(); + break; + case TK_BASE: + Lex(); + _fs->AddInstruction(_OP_GETBASE, _fs->PushTarget()); + _es.etype = BASE; + _es.epos = _fs->TopTarget(); + return (_es.epos); + break; + case TK_IDENTIFIER: + case TK_CONSTRUCTOR: + case TK_THIS:{ + SQObject id; + SQObject constant; + + switch(_token) { + case TK_IDENTIFIER: id = _fs->CreateString(_lex._svalue); break; + case TK_THIS: id = _fs->CreateString(_SC("this"),4); break; + case TK_CONSTRUCTOR: id = _fs->CreateString(_SC("constructor"),11); break; + } + + SQInteger pos = -1; + Lex(); + if((pos = _fs->GetLocalVariable(id)) != -1) { + /* Handle a local variable (includes 'this') */ + _fs->PushTarget(pos); + _es.etype = LOCAL; + _es.epos = pos; + } + + else if((pos = _fs->GetOuterVariable(id)) != -1) { + /* Handle a free var */ + if(NeedGet()) { + _es.epos = _fs->PushTarget(); + _fs->AddInstruction(_OP_GETOUTER, _es.epos, pos); + /* _es.etype = EXPR; already default value */ + } + else { + _es.etype = OUTER; + _es.epos = pos; + } + } + + else if(_fs->IsConstant(id, constant)) { + /* Handle named constant */ + SQObjectPtr constval; + SQObject constid; + if(sq_type(constant) == OT_TABLE) { + Expect('.'); + constid = Expect(TK_IDENTIFIER); + if(!_table(constant)->Get(constid, constval)) { + constval.Null(); + Error(_SC("invalid constant [%s.%s]"), _stringval(id), _stringval(constid)); + } + } + else { + constval = constant; + } + _es.epos = _fs->PushTarget(); + + /* generate direct or literal function depending on size */ + SQObjectType ctype = sq_type(constval); + switch(ctype) { + case OT_INTEGER: EmitLoadConstInt(_integer(constval),_es.epos); break; + case OT_FLOAT: EmitLoadConstFloat(_float(constval),_es.epos); break; + case OT_BOOL: _fs->AddInstruction(_OP_LOADBOOL, _es.epos, _integer(constval)); break; + default: _fs->AddInstruction(_OP_LOAD,_es.epos,_fs->GetConstant(constval)); break; + } + _es.etype = EXPR; + } + else { + /* Handle a non-local variable, aka a field. Push the 'this' pointer on + * the virtual stack (always found in offset 0, so no instruction needs to + * be generated), and push the key next. Generate an _OP_LOAD instruction + * for the latter. If we are not using the variable as a dref expr, generate + * the _OP_GET instruction. + */ + _fs->PushTarget(0); + _fs->AddInstruction(_OP_LOAD, _fs->PushTarget(), _fs->GetConstant(id)); + if(NeedGet()) { + Emit2ArgsOP(_OP_GET); + } + _es.etype = OBJECT; + } + return _es.epos; + } + break; + case TK_DOUBLE_COLON: // "::" + _fs->AddInstruction(_OP_LOADROOT, _fs->PushTarget()); + _es.etype = OBJECT; + _token = _SC('.'); /* hack: drop into PrefixExpr, case '.'*/ + _es.epos = -1; + return _es.epos; + break; + case TK_NULL: + _fs->AddInstruction(_OP_LOADNULLS, _fs->PushTarget(),1); + Lex(); + break; + case TK_INTEGER: EmitLoadConstInt(_lex._nvalue,-1); Lex(); break; + case TK_FLOAT: EmitLoadConstFloat(_lex._fvalue,-1); Lex(); break; + case TK_TRUE: case TK_FALSE: + _fs->AddInstruction(_OP_LOADBOOL, _fs->PushTarget(),_token == TK_TRUE?1:0); + Lex(); + break; + case _SC('['): { + _fs->AddInstruction(_OP_NEWOBJ, _fs->PushTarget(),0,0,NOT_ARRAY); + SQInteger apos = _fs->GetCurrentPos(),key = 0; + Lex(); + while(_token != _SC(']')) { + Expression(); + if(_token == _SC(',')) Lex(); + SQInteger val = _fs->PopTarget(); + SQInteger array = _fs->TopTarget(); + _fs->AddInstruction(_OP_APPENDARRAY, array, val, AAT_STACK); + key++; + } + _fs->SetInstructionParam(apos, 1, key); + Lex(); + } + break; + case _SC('{'): + _fs->AddInstruction(_OP_NEWOBJ, _fs->PushTarget(),0,NOT_TABLE); + Lex();ParseTableOrClass(_SC(','),_SC('}')); + break; + case TK_FUNCTION: FunctionExp(_token);break; + case _SC('@'): FunctionExp(_token,true);break; + case TK_CLASS: Lex(); ClassExp();break; + case _SC('-'): + Lex(); + switch(_token) { + case TK_INTEGER: EmitLoadConstInt(-_lex._nvalue,-1); Lex(); break; + case TK_FLOAT: EmitLoadConstFloat(-_lex._fvalue,-1); Lex(); break; + default: UnaryOP(_OP_NEG); + } + break; + case _SC('!'): Lex(); UnaryOP(_OP_NOT); break; + case _SC('~'): + Lex(); + if(_token == TK_INTEGER) { EmitLoadConstInt(~_lex._nvalue,-1); Lex(); break; } + UnaryOP(_OP_BWNOT); + break; + case TK_TYPEOF : Lex() ;UnaryOP(_OP_TYPEOF); break; + case TK_RESUME : Lex(); UnaryOP(_OP_RESUME); break; + case TK_CLONE : Lex(); UnaryOP(_OP_CLONE); break; + case TK_RAWCALL: Lex(); Expect('('); FunctionCallArgs(true); break; + case TK_MINUSMINUS : + case TK_PLUSPLUS :PrefixIncDec(_token); break; + case TK_DELETE : DeleteExpr(); break; + case _SC('('): Lex(); CommaExpr(); Expect(_SC(')')); + break; + case TK___LINE__: EmitLoadConstInt(_lex._currentline,-1); Lex(); break; + case TK___FILE__: _fs->AddInstruction(_OP_LOAD, _fs->PushTarget(), _fs->GetConstant(_sourcename)); Lex(); break; + default: Error(_SC("expression expected")); + } + _es.etype = EXPR; + return -1; + } + void EmitLoadConstInt(SQInteger value,SQInteger target) + { + if(target < 0) { + target = _fs->PushTarget(); + } + if(value <= INT_MAX && value > INT_MIN) { //does it fit in 32 bits? + _fs->AddInstruction(_OP_LOADINT, target,value); + } + else { + _fs->AddInstruction(_OP_LOAD, target, _fs->GetNumericConstant(value)); + } + } + void EmitLoadConstFloat(SQFloat value,SQInteger target) + { + if(target < 0) { + target = _fs->PushTarget(); + } + if(sizeof(SQFloat) == sizeof(SQInt32)) { + _fs->AddInstruction(_OP_LOADFLOAT, target,*((SQInt32 *)&value)); + } + else { + _fs->AddInstruction(_OP_LOAD, target, _fs->GetNumericConstant(value)); + } + } + void UnaryOP(SQOpcode op) + { + PrefixedExpr(); + SQInteger src = _fs->PopTarget(); + _fs->AddInstruction(op, _fs->PushTarget(), src); + } + bool NeedGet() + { + switch(_token) { + case _SC('='): case _SC('('): case TK_NEWSLOT: case TK_MODEQ: case TK_MULEQ: + case TK_DIVEQ: case TK_MINUSEQ: case TK_PLUSEQ: + return false; + case TK_PLUSPLUS: case TK_MINUSMINUS: + if (!IsEndOfStatement()) { + return false; + } + break; + } + return (!_es.donot_get || ( _es.donot_get && (_token == _SC('.') || _token == _SC('[')))); + } + void FunctionCallArgs(bool rawcall = false) + { + SQInteger nargs = 1;//this + while(_token != _SC(')')) { + Expression(); + MoveIfCurrentTargetIsLocal(); + nargs++; + if(_token == _SC(',')){ + Lex(); + if(_token == ')') Error(_SC("expression expected, found ')'")); + } + } + Lex(); + if (rawcall) { + if (nargs < 3) Error(_SC("rawcall requires at least 2 parameters (callee and this)")); + nargs -= 2; //removes callee and this from count + } + for(SQInteger i = 0; i < (nargs - 1); i++) _fs->PopTarget(); + SQInteger stackbase = _fs->PopTarget(); + SQInteger closure = _fs->PopTarget(); + _fs->AddInstruction(_OP_CALL, _fs->PushTarget(), closure, stackbase, nargs); + if (_token == '{') + { + SQInteger retval = _fs->TopTarget(); + SQInteger nkeys = 0; + Lex(); + while (_token != '}') { + switch (_token) { + case _SC('['): + Lex(); CommaExpr(); Expect(_SC(']')); + Expect(_SC('=')); Expression(); + break; + default: + _fs->AddInstruction(_OP_LOAD, _fs->PushTarget(), _fs->GetConstant(Expect(TK_IDENTIFIER))); + Expect(_SC('=')); Expression(); + break; + } + if (_token == ',') Lex(); + nkeys++; + SQInteger val = _fs->PopTarget(); + SQInteger key = _fs->PopTarget(); + _fs->AddInstruction(_OP_SET, 0xFF, retval, key, val); + } + Lex(); + } + } + void ParseTableOrClass(SQInteger separator,SQInteger terminator) + { + SQInteger tpos = _fs->GetCurrentPos(),nkeys = 0; + while(_token != terminator) { + bool hasattrs = false; + bool isstatic = false; + //check if is an attribute + if(separator == ';') { + if(_token == TK_ATTR_OPEN) { + _fs->AddInstruction(_OP_NEWOBJ, _fs->PushTarget(),0,NOT_TABLE); Lex(); + ParseTableOrClass(',',TK_ATTR_CLOSE); + hasattrs = true; + } + if(_token == TK_STATIC) { + isstatic = true; + Lex(); + } + } + switch(_token) { + case TK_FUNCTION: + case TK_CONSTRUCTOR:{ + SQInteger tk = _token; + Lex(); + SQObject id = tk == TK_FUNCTION ? Expect(TK_IDENTIFIER) : _fs->CreateString(_SC("constructor")); + Expect(_SC('(')); + _fs->AddInstruction(_OP_LOAD, _fs->PushTarget(), _fs->GetConstant(id)); + CreateFunction(id); + _fs->AddInstruction(_OP_CLOSURE, _fs->PushTarget(), _fs->_functions.size() - 1, 0); + } + break; + case _SC('['): + Lex(); CommaExpr(); Expect(_SC(']')); + Expect(_SC('=')); Expression(); + break; + case TK_STRING_LITERAL: //JSON + if(separator == ',') { //only works for tables + _fs->AddInstruction(_OP_LOAD, _fs->PushTarget(), _fs->GetConstant(Expect(TK_STRING_LITERAL))); + Expect(_SC(':')); Expression(); + break; + } + default : + _fs->AddInstruction(_OP_LOAD, _fs->PushTarget(), _fs->GetConstant(Expect(TK_IDENTIFIER))); + Expect(_SC('=')); Expression(); + } + if(_token == separator) Lex();//optional comma/semicolon + nkeys++; + SQInteger val = _fs->PopTarget(); + SQInteger key = _fs->PopTarget(); + SQInteger attrs = hasattrs ? _fs->PopTarget():-1; + ((void)attrs); + assert((hasattrs && (attrs == key-1)) || !hasattrs); + unsigned char flags = (hasattrs?NEW_SLOT_ATTRIBUTES_FLAG:0)|(isstatic?NEW_SLOT_STATIC_FLAG:0); + SQInteger table = _fs->TopTarget(); //<AddInstruction(_OP_NEWSLOT, 0xFF, table, key, val); + } + else { + _fs->AddInstruction(_OP_NEWSLOTA, flags, table, key, val); //this for classes only as it invokes _newmember + } + } + if(separator == _SC(',')) //hack recognizes a table from the separator + _fs->SetInstructionParam(tpos, 1, nkeys); + Lex(); + } + void LocalDeclStatement() + { + SQObject varname; + Lex(); + if( _token == TK_FUNCTION) { + Lex(); + varname = Expect(TK_IDENTIFIER); + Expect(_SC('(')); + CreateFunction(varname,false); + _fs->AddInstruction(_OP_CLOSURE, _fs->PushTarget(), _fs->_functions.size() - 1, 0); + _fs->PopTarget(); + _fs->PushLocalVariable(varname); + return; + } + + do { + varname = Expect(TK_IDENTIFIER); + if(_token == _SC('=')) { + Lex(); Expression(); + SQInteger src = _fs->PopTarget(); + SQInteger dest = _fs->PushTarget(); + if(dest != src) _fs->AddInstruction(_OP_MOVE, dest, src); + } + else{ + _fs->AddInstruction(_OP_LOADNULLS, _fs->PushTarget(),1); + } + _fs->PopTarget(); + _fs->PushLocalVariable(varname); + if(_token == _SC(',')) Lex(); else break; + } while(1); + } + void IfBlock() + { + if (_token == _SC('{')) + { + BEGIN_SCOPE(); + Lex(); + Statements(); + Expect(_SC('}')); + if (true) { + END_SCOPE(); + } + else { + END_SCOPE_NO_CLOSE(); + } + } + else { + //BEGIN_SCOPE(); + Statement(); + if (_lex._prevtoken != _SC('}') && _lex._prevtoken != _SC(';')) OptionalSemicolon(); + //END_SCOPE(); + } + } + void IfStatement() + { + SQInteger jmppos; + bool haselse = false; + Lex(); Expect(_SC('(')); CommaExpr(); Expect(_SC(')')); + _fs->AddInstruction(_OP_JZ, _fs->PopTarget()); + SQInteger jnepos = _fs->GetCurrentPos(); + + + + IfBlock(); + // + /*static int n = 0; + if (_token != _SC('}') && _token != TK_ELSE) { + printf("IF %d-----------------------!!!!!!!!!\n", n); + if (n == 5) + { + printf("asd"); + } + n++; + //OptionalSemicolon(); + }*/ + + + SQInteger endifblock = _fs->GetCurrentPos(); + if(_token == TK_ELSE){ + haselse = true; + //BEGIN_SCOPE(); + _fs->AddInstruction(_OP_JMP); + jmppos = _fs->GetCurrentPos(); + Lex(); + //Statement(); if(_lex._prevtoken != _SC('}')) OptionalSemicolon(); + IfBlock(); + //END_SCOPE(); + _fs->SetInstructionParam(jmppos, 1, _fs->GetCurrentPos() - jmppos); + } + _fs->SetInstructionParam(jnepos, 1, endifblock - jnepos + (haselse?1:0)); + } + void WhileStatement() + { + SQInteger jzpos, jmppos; + jmppos = _fs->GetCurrentPos(); + Lex(); Expect(_SC('(')); CommaExpr(); Expect(_SC(')')); + + BEGIN_BREAKBLE_BLOCK(); + _fs->AddInstruction(_OP_JZ, _fs->PopTarget()); + jzpos = _fs->GetCurrentPos(); + BEGIN_SCOPE(); + + Statement(); + + END_SCOPE(); + _fs->AddInstruction(_OP_JMP, 0, jmppos - _fs->GetCurrentPos() - 1); + _fs->SetInstructionParam(jzpos, 1, _fs->GetCurrentPos() - jzpos); + + END_BREAKBLE_BLOCK(jmppos); + } + void DoWhileStatement() + { + Lex(); + SQInteger jmptrg = _fs->GetCurrentPos(); + BEGIN_BREAKBLE_BLOCK() + BEGIN_SCOPE(); + Statement(); + END_SCOPE(); + Expect(TK_WHILE); + SQInteger continuetrg = _fs->GetCurrentPos(); + Expect(_SC('(')); CommaExpr(); Expect(_SC(')')); + _fs->AddInstruction(_OP_JZ, _fs->PopTarget(), 1); + _fs->AddInstruction(_OP_JMP, 0, jmptrg - _fs->GetCurrentPos() - 1); + END_BREAKBLE_BLOCK(continuetrg); + } + void ForStatement() + { + Lex(); + BEGIN_SCOPE(); + Expect(_SC('(')); + if(_token == TK_LOCAL) LocalDeclStatement(); + else if(_token != _SC(';')){ + CommaExpr(); + _fs->PopTarget(); + } + Expect(_SC(';')); + _fs->SnoozeOpt(); + SQInteger jmppos = _fs->GetCurrentPos(); + SQInteger jzpos = -1; + if(_token != _SC(';')) { CommaExpr(); _fs->AddInstruction(_OP_JZ, _fs->PopTarget()); jzpos = _fs->GetCurrentPos(); } + Expect(_SC(';')); + _fs->SnoozeOpt(); + SQInteger expstart = _fs->GetCurrentPos() + 1; + if(_token != _SC(')')) { + CommaExpr(); + _fs->PopTarget(); + } + Expect(_SC(')')); + _fs->SnoozeOpt(); + SQInteger expend = _fs->GetCurrentPos(); + SQInteger expsize = (expend - expstart) + 1; + SQInstructionVec exp; + if(expsize > 0) { + for(SQInteger i = 0; i < expsize; i++) + exp.push_back(_fs->GetInstruction(expstart + i)); + _fs->PopInstructions(expsize); + } + BEGIN_BREAKBLE_BLOCK() + Statement(); + SQInteger continuetrg = _fs->GetCurrentPos(); + if(expsize > 0) { + for(SQInteger i = 0; i < expsize; i++) + _fs->AddInstruction(exp[i]); + } + _fs->AddInstruction(_OP_JMP, 0, jmppos - _fs->GetCurrentPos() - 1, 0); + if(jzpos> 0) _fs->SetInstructionParam(jzpos, 1, _fs->GetCurrentPos() - jzpos); + + END_BREAKBLE_BLOCK(continuetrg); + + END_SCOPE(); + } + void ForEachStatement() + { + SQObject idxname, valname; + Lex(); Expect(_SC('(')); valname = Expect(TK_IDENTIFIER); + if(_token == _SC(',')) { + idxname = valname; + Lex(); valname = Expect(TK_IDENTIFIER); + } + else{ + idxname = _fs->CreateString(_SC("@INDEX@")); + } + Expect(TK_IN); + + //save the stack size + BEGIN_SCOPE(); + //put the table in the stack(evaluate the table expression) + Expression(); Expect(_SC(')')); + SQInteger container = _fs->TopTarget(); + //push the index local var + SQInteger indexpos = _fs->PushLocalVariable(idxname); + _fs->AddInstruction(_OP_LOADNULLS, indexpos,1); + //push the value local var + SQInteger valuepos = _fs->PushLocalVariable(valname); + _fs->AddInstruction(_OP_LOADNULLS, valuepos,1); + //push reference index + SQInteger itrpos = _fs->PushLocalVariable(_fs->CreateString(_SC("@ITERATOR@"))); //use invalid id to make it inaccessible + _fs->AddInstruction(_OP_LOADNULLS, itrpos,1); + SQInteger jmppos = _fs->GetCurrentPos(); + _fs->AddInstruction(_OP_FOREACH, container, 0, indexpos); + SQInteger foreachpos = _fs->GetCurrentPos(); + _fs->AddInstruction(_OP_POSTFOREACH, container, 0, indexpos); + //generate the statement code + BEGIN_BREAKBLE_BLOCK() + Statement(); + _fs->AddInstruction(_OP_JMP, 0, jmppos - _fs->GetCurrentPos() - 1); + _fs->SetInstructionParam(foreachpos, 1, _fs->GetCurrentPos() - foreachpos); + _fs->SetInstructionParam(foreachpos + 1, 1, _fs->GetCurrentPos() - foreachpos); + END_BREAKBLE_BLOCK(foreachpos - 1); + //restore the local variable stack(remove index,val and ref idx) + _fs->PopTarget(); + END_SCOPE(); + } + void SwitchStatement() + { + Lex(); Expect(_SC('(')); CommaExpr(); Expect(_SC(')')); + Expect(_SC('{')); + SQInteger expr = _fs->TopTarget(); + bool bfirst = true; + SQInteger tonextcondjmp = -1; + SQInteger skipcondjmp = -1; + SQInteger __nbreaks__ = _fs->_unresolvedbreaks.size(); + _fs->_breaktargets.push_back(0); + while(_token == TK_CASE) { + if(!bfirst) { + _fs->AddInstruction(_OP_JMP, 0, 0); + skipcondjmp = _fs->GetCurrentPos(); + _fs->SetInstructionParam(tonextcondjmp, 1, _fs->GetCurrentPos() - tonextcondjmp); + } + //condition + Lex(); Expression(); Expect(_SC(':')); + SQInteger trg = _fs->PopTarget(); + SQInteger eqtarget = trg; + bool local = _fs->IsLocal(trg); + if(local) { + eqtarget = _fs->PushTarget(); //we need to allocate a extra reg + } + _fs->AddInstruction(_OP_EQ, eqtarget, trg, expr); + _fs->AddInstruction(_OP_JZ, eqtarget, 0); + if(local) { + _fs->PopTarget(); + } + + //end condition + if(skipcondjmp != -1) { + _fs->SetInstructionParam(skipcondjmp, 1, (_fs->GetCurrentPos() - skipcondjmp)); + } + tonextcondjmp = _fs->GetCurrentPos(); + BEGIN_SCOPE(); + Statements(); + END_SCOPE(); + bfirst = false; + } + if(tonextcondjmp != -1) + _fs->SetInstructionParam(tonextcondjmp, 1, _fs->GetCurrentPos() - tonextcondjmp); + if(_token == TK_DEFAULT) { + Lex(); Expect(_SC(':')); + BEGIN_SCOPE(); + Statements(); + END_SCOPE(); + } + Expect(_SC('}')); + _fs->PopTarget(); + __nbreaks__ = _fs->_unresolvedbreaks.size() - __nbreaks__; + if(__nbreaks__ > 0)ResolveBreaks(_fs, __nbreaks__); + _fs->_breaktargets.pop_back(); + } + void FunctionStatement() + { + SQObject id; + Lex(); id = Expect(TK_IDENTIFIER); + _fs->PushTarget(0); + _fs->AddInstruction(_OP_LOAD, _fs->PushTarget(), _fs->GetConstant(id)); + if(_token == TK_DOUBLE_COLON) Emit2ArgsOP(_OP_GET); + + while(_token == TK_DOUBLE_COLON) { + Lex(); + id = Expect(TK_IDENTIFIER); + _fs->AddInstruction(_OP_LOAD, _fs->PushTarget(), _fs->GetConstant(id)); + if(_token == TK_DOUBLE_COLON) Emit2ArgsOP(_OP_GET); + } + Expect(_SC('(')); + CreateFunction(id); + _fs->AddInstruction(_OP_CLOSURE, _fs->PushTarget(), _fs->_functions.size() - 1, 0); + EmitDerefOp(_OP_NEWSLOT); + _fs->PopTarget(); + } + void ClassStatement() + { + SQExpState es; + Lex(); + es = _es; + _es.donot_get = true; + PrefixedExpr(); + if(_es.etype == EXPR) { + Error(_SC("invalid class name")); + } + else if(_es.etype == OBJECT || _es.etype == BASE) { + ClassExp(); + EmitDerefOp(_OP_NEWSLOT); + _fs->PopTarget(); + } + else { + Error(_SC("cannot create a class in a local with the syntax(class )")); + } + _es = es; + } + SQObject ExpectScalar() + { + SQObject val; + val._type = OT_NULL; val._unVal.nInteger = 0; //shut up GCC 4.x + switch(_token) { + case TK_INTEGER: + val._type = OT_INTEGER; + val._unVal.nInteger = _lex._nvalue; + break; + case TK_FLOAT: + val._type = OT_FLOAT; + val._unVal.fFloat = _lex._fvalue; + break; + case TK_STRING_LITERAL: + val = _fs->CreateString(_lex._svalue,_lex._longstr.size()-1); + break; + case TK_TRUE: + case TK_FALSE: + val._type = OT_BOOL; + val._unVal.nInteger = _token == TK_TRUE ? 1 : 0; + break; + case '-': + Lex(); + switch(_token) + { + case TK_INTEGER: + val._type = OT_INTEGER; + val._unVal.nInteger = -_lex._nvalue; + break; + case TK_FLOAT: + val._type = OT_FLOAT; + val._unVal.fFloat = -_lex._fvalue; + break; + default: + Error(_SC("scalar expected : integer, float")); + } + break; + default: + Error(_SC("scalar expected : integer, float, or string")); + } + Lex(); + return val; + } + void EnumStatement() + { + Lex(); + SQObject id = Expect(TK_IDENTIFIER); + Expect(_SC('{')); + + SQObject table = _fs->CreateTable(); + SQInteger nval = 0; + while(_token != _SC('}')) { + SQObject key = Expect(TK_IDENTIFIER); + SQObject val; + if(_token == _SC('=')) { + Lex(); + val = ExpectScalar(); + } + else { + val._type = OT_INTEGER; + val._unVal.nInteger = nval++; + } + _table(table)->NewSlot(SQObjectPtr(key),SQObjectPtr(val)); + if(_token == ',') Lex(); + } + SQTable *enums = _table(_ss(_vm)->_consts); + SQObjectPtr strongid = id; + enums->NewSlot(SQObjectPtr(strongid),SQObjectPtr(table)); + strongid.Null(); + Lex(); + } + void TryCatchStatement() + { + SQObject exid; + Lex(); + _fs->AddInstruction(_OP_PUSHTRAP,0,0); + _fs->_traps++; + if(_fs->_breaktargets.size()) _fs->_breaktargets.top()++; + if(_fs->_continuetargets.size()) _fs->_continuetargets.top()++; + SQInteger trappos = _fs->GetCurrentPos(); + { + BEGIN_SCOPE(); + Statement(); + END_SCOPE(); + } + _fs->_traps--; + _fs->AddInstruction(_OP_POPTRAP, 1, 0); + if(_fs->_breaktargets.size()) _fs->_breaktargets.top()--; + if(_fs->_continuetargets.size()) _fs->_continuetargets.top()--; + _fs->AddInstruction(_OP_JMP, 0, 0); + SQInteger jmppos = _fs->GetCurrentPos(); + _fs->SetInstructionParam(trappos, 1, (_fs->GetCurrentPos() - trappos)); + Expect(TK_CATCH); Expect(_SC('(')); exid = Expect(TK_IDENTIFIER); Expect(_SC(')')); + { + BEGIN_SCOPE(); + SQInteger ex_target = _fs->PushLocalVariable(exid); + _fs->SetInstructionParam(trappos, 0, ex_target); + Statement(); + _fs->SetInstructionParams(jmppos, 0, (_fs->GetCurrentPos() - jmppos), 0); + END_SCOPE(); + } + } + void FunctionExp(SQInteger ftype,bool lambda = false) + { + Lex(); Expect(_SC('(')); + SQObjectPtr dummy; + CreateFunction(dummy,lambda); + _fs->AddInstruction(_OP_CLOSURE, _fs->PushTarget(), _fs->_functions.size() - 1, ftype == TK_FUNCTION?0:1); + } + void ClassExp() + { + SQInteger base = -1; + SQInteger attrs = -1; + if(_token == TK_EXTENDS) { + Lex(); Expression(); + base = _fs->TopTarget(); + } + if(_token == TK_ATTR_OPEN) { + Lex(); + _fs->AddInstruction(_OP_NEWOBJ, _fs->PushTarget(),0,NOT_TABLE); + ParseTableOrClass(_SC(','),TK_ATTR_CLOSE); + attrs = _fs->TopTarget(); + } + Expect(_SC('{')); + if(attrs != -1) _fs->PopTarget(); + if(base != -1) _fs->PopTarget(); + _fs->AddInstruction(_OP_NEWOBJ, _fs->PushTarget(), base, attrs,NOT_CLASS); + ParseTableOrClass(_SC(';'),_SC('}')); + } + void DeleteExpr() + { + SQExpState es; + Lex(); + es = _es; + _es.donot_get = true; + PrefixedExpr(); + if(_es.etype==EXPR) Error(_SC("can't delete an expression")); + if(_es.etype==OBJECT || _es.etype==BASE) { + Emit2ArgsOP(_OP_DELETE); + } + else { + Error(_SC("cannot delete an (outer) local")); + } + _es = es; + } + void PrefixIncDec(SQInteger token) + { + SQExpState es; + SQInteger diff = (token==TK_MINUSMINUS) ? -1 : 1; + Lex(); + es = _es; + _es.donot_get = true; + PrefixedExpr(); + if(_es.etype==EXPR) { + Error(_SC("can't '++' or '--' an expression")); + } + else if(_es.etype==OBJECT || _es.etype==BASE) { + Emit2ArgsOP(_OP_INC, diff); + } + else if(_es.etype==LOCAL) { + SQInteger src = _fs->TopTarget(); + _fs->AddInstruction(_OP_INCL, src, src, 0, diff); + + } + else if(_es.etype==OUTER) { + SQInteger tmp = _fs->PushTarget(); + _fs->AddInstruction(_OP_GETOUTER, tmp, _es.epos); + _fs->AddInstruction(_OP_INCL, tmp, tmp, 0, diff); + _fs->AddInstruction(_OP_SETOUTER, tmp, _es.epos, tmp); + } + _es = es; + } + void CreateFunction(SQObject &name,bool lambda = false) + { + SQFuncState *funcstate = _fs->PushChildState(_ss(_vm)); + funcstate->_name = name; + SQObject paramname; + funcstate->AddParameter(_fs->CreateString(_SC("this"))); + funcstate->_sourcename = _sourcename; + SQInteger defparams = 0; + while(_token!=_SC(')')) { + if(_token == TK_VARPARAMS) { + if(defparams > 0) Error(_SC("function with default parameters cannot have variable number of parameters")); + funcstate->AddParameter(_fs->CreateString(_SC("vargv"))); + funcstate->_varparams = true; + Lex(); + if(_token != _SC(')')) Error(_SC("expected ')'")); + break; + } + else { + paramname = Expect(TK_IDENTIFIER); + funcstate->AddParameter(paramname); + if(_token == _SC('=')) { + Lex(); + Expression(); + funcstate->AddDefaultParam(_fs->TopTarget()); + defparams++; + } + else { + if(defparams > 0) Error(_SC("expected '='")); + } + if(_token == _SC(',')) Lex(); + else if(_token != _SC(')')) Error(_SC("expected ')' or ','")); + } + } + Expect(_SC(')')); + for(SQInteger n = 0; n < defparams; n++) { + _fs->PopTarget(); + } + + SQFuncState *currchunk = _fs; + _fs = funcstate; + if(lambda) { + Expression(); + _fs->AddInstruction(_OP_RETURN, 1, _fs->PopTarget());} + else { + Statement(false); + } + funcstate->AddLineInfos(_lex._prevtoken == _SC('\n')?_lex._lasttokenline:_lex._currentline, _lineinfo, true); + funcstate->AddInstruction(_OP_RETURN, -1); + funcstate->SetStackSize(0); + + SQFunctionProto *func = funcstate->BuildProto(); +#ifdef _DEBUG_DUMP + funcstate->Dump(func); +#endif + _fs = currchunk; + _fs->_functions.push_back(func); + _fs->PopChildState(); + } + void ResolveBreaks(SQFuncState *funcstate, SQInteger ntoresolve) + { + while(ntoresolve > 0) { + SQInteger pos = funcstate->_unresolvedbreaks.back(); + funcstate->_unresolvedbreaks.pop_back(); + //set the jmp instruction + funcstate->SetInstructionParams(pos, 0, funcstate->GetCurrentPos() - pos, 0); + ntoresolve--; + } + } + void ResolveContinues(SQFuncState *funcstate, SQInteger ntoresolve, SQInteger targetpos) + { + while(ntoresolve > 0) { + SQInteger pos = funcstate->_unresolvedcontinues.back(); + funcstate->_unresolvedcontinues.pop_back(); + //set the jmp instruction + funcstate->SetInstructionParams(pos, 0, targetpos - pos, 0); + ntoresolve--; + } + } +private: + SQInteger _token; + SQFuncState *_fs; + SQObjectPtr _sourcename; + SQLexer _lex; + bool _lineinfo; + bool _raiseerror; + SQInteger _debugline; + SQInteger _debugop; + SQExpState _es; + SQScope _scope; + SQChar _compilererror[MAX_COMPILER_ERROR_LEN]; + jmp_buf _errorjmp; + SQVM *_vm; +}; + +bool Compile(SQVM *vm,SQLEXREADFUNC rg, SQUserPointer up, const SQChar *sourcename, SQObjectPtr &out, bool raiseerror, bool lineinfo) +{ + SQCompiler p(vm, rg, up, sourcename, raiseerror, lineinfo); + return p.Compile(out); +} + +#endif diff --git a/src/vscript/squirrel/squirrel/sqcompiler.h b/src/vscript/squirrel/squirrel/sqcompiler.h new file mode 100644 index 00000000..e7da6423 --- /dev/null +++ b/src/vscript/squirrel/squirrel/sqcompiler.h @@ -0,0 +1,79 @@ +/* see copyright notice in squirrel.h */ +#ifndef _SQCOMPILER_H_ +#define _SQCOMPILER_H_ + +struct SQVM; + +#define TK_IDENTIFIER 258 +#define TK_STRING_LITERAL 259 +#define TK_INTEGER 260 +#define TK_FLOAT 261 +#define TK_BASE 262 +#define TK_DELETE 263 +#define TK_EQ 264 +#define TK_NE 265 +#define TK_LE 266 +#define TK_GE 267 +#define TK_SWITCH 268 +#define TK_ARROW 269 +#define TK_AND 270 +#define TK_OR 271 +#define TK_IF 272 +#define TK_ELSE 273 +#define TK_WHILE 274 +#define TK_BREAK 275 +#define TK_FOR 276 +#define TK_DO 277 +#define TK_NULL 278 +#define TK_FOREACH 279 +#define TK_IN 280 +#define TK_NEWSLOT 281 +#define TK_MODULO 282 +#define TK_LOCAL 283 +#define TK_CLONE 284 +#define TK_FUNCTION 285 +#define TK_RETURN 286 +#define TK_TYPEOF 287 +#define TK_UMINUS 288 +#define TK_PLUSEQ 289 +#define TK_MINUSEQ 290 +#define TK_CONTINUE 291 +#define TK_YIELD 292 +#define TK_TRY 293 +#define TK_CATCH 294 +#define TK_THROW 295 +#define TK_SHIFTL 296 +#define TK_SHIFTR 297 +#define TK_RESUME 298 +#define TK_DOUBLE_COLON 299 +#define TK_CASE 300 +#define TK_DEFAULT 301 +#define TK_THIS 302 +#define TK_PLUSPLUS 303 +#define TK_MINUSMINUS 304 +#define TK_3WAYSCMP 305 +#define TK_USHIFTR 306 +#define TK_CLASS 307 +#define TK_EXTENDS 308 +#define TK_CONSTRUCTOR 310 +#define TK_INSTANCEOF 311 +#define TK_VARPARAMS 312 +#define TK___LINE__ 313 +#define TK___FILE__ 314 +#define TK_TRUE 315 +#define TK_FALSE 316 +#define TK_MULEQ 317 +#define TK_DIVEQ 318 +#define TK_MODEQ 319 +#define TK_ATTR_OPEN 320 +#define TK_ATTR_CLOSE 321 +#define TK_STATIC 322 +#define TK_ENUM 323 +#define TK_CONST 324 +#define TK_RAWCALL 325 + + + +typedef void(*CompilerErrorFunc)(void *ud, const SQChar *s); +bool Compile(SQVM *vm, SQLEXREADFUNC rg, SQUserPointer up, const SQChar *sourcename, SQObjectPtr &out, bool raiseerror, bool lineinfo); +#endif //_SQCOMPILER_H_ diff --git a/src/vscript/squirrel/squirrel/sqdebug.cpp b/src/vscript/squirrel/squirrel/sqdebug.cpp new file mode 100644 index 00000000..d55b1b2e --- /dev/null +++ b/src/vscript/squirrel/squirrel/sqdebug.cpp @@ -0,0 +1,118 @@ +/* + see copyright notice in squirrel.h +*/ +#include "sqpcheader.h" +#include +#include "sqvm.h" +#include "sqfuncproto.h" +#include "sqclosure.h" +#include "sqstring.h" + +SQRESULT sq_getfunctioninfo(HSQUIRRELVM v,SQInteger level,SQFunctionInfo *fi) +{ + SQInteger cssize = v->_callsstacksize; + if (cssize > level) { + SQVM::CallInfo &ci = v->_callsstack[cssize-level-1]; + if(sq_isclosure(ci._closure)) { + SQClosure *c = _closure(ci._closure); + SQFunctionProto *proto = c->_function; + fi->funcid = proto; + fi->name = sq_type(proto->_name) == OT_STRING?_stringval(proto->_name):_SC("unknown"); + fi->source = sq_type(proto->_sourcename) == OT_STRING?_stringval(proto->_sourcename):_SC("unknown"); + fi->line = proto->_lineinfos[0]._line; + return SQ_OK; + } + } + return sq_throwerror(v,_SC("the object is not a closure")); +} + +SQRESULT sq_stackinfos(HSQUIRRELVM v, SQInteger level, SQStackInfos *si) +{ + SQInteger cssize = v->_callsstacksize; + if (cssize > level) { + memset(si, 0, sizeof(SQStackInfos)); + SQVM::CallInfo &ci = v->_callsstack[cssize-level-1]; + switch (sq_type(ci._closure)) { + case OT_CLOSURE:{ + SQFunctionProto *func = _closure(ci._closure)->_function; + if (sq_type(func->_name) == OT_STRING) + si->funcname = _stringval(func->_name); + if (sq_type(func->_sourcename) == OT_STRING) + si->source = _stringval(func->_sourcename); + si->line = func->GetLine(ci._ip); + } + break; + case OT_NATIVECLOSURE: + si->source = _SC("NATIVE"); + si->funcname = _SC("unknown"); + if(sq_type(_nativeclosure(ci._closure)->_name) == OT_STRING) + si->funcname = _stringval(_nativeclosure(ci._closure)->_name); + si->line = -1; + break; + default: break; //shutup compiler + } + return SQ_OK; + } + return SQ_ERROR; +} + +void SQVM::Raise_Error(const SQChar *s, ...) +{ + va_list vl; + va_start(vl, s); + SQInteger buffersize = (SQInteger)scstrlen(s)+(NUMBER_MAX_CHAR*2); + scvsprintf(_sp(sq_rsl(buffersize)),buffersize, s, vl); + va_end(vl); + _lasterror = SQString::Create(_ss(this),_spval,-1); +} + +void SQVM::Raise_Error(const SQObjectPtr &desc) +{ + _lasterror = desc; +} + +SQString *SQVM::PrintObjVal(const SQObjectPtr &o) +{ + switch(sq_type(o)) { + case OT_STRING: return _string(o); + case OT_INTEGER: + scsprintf(_sp(sq_rsl(NUMBER_MAX_CHAR+1)),sq_rsl(NUMBER_MAX_CHAR), _PRINT_INT_FMT, _integer(o)); + return SQString::Create(_ss(this), _spval); + break; + case OT_FLOAT: + scsprintf(_sp(sq_rsl(NUMBER_MAX_CHAR+1)), sq_rsl(NUMBER_MAX_CHAR), _SC("%.14g"), _float(o)); + return SQString::Create(_ss(this), _spval); + break; + default: + return SQString::Create(_ss(this), GetTypeName(o)); + } +} + +void SQVM::Raise_IdxError(const SQObjectPtr &o) +{ + SQObjectPtr oval = PrintObjVal(o); + Raise_Error(_SC("the index '%.50s' does not exist"), _stringval(oval)); +} + +void SQVM::Raise_CompareError(const SQObject &o1, const SQObject &o2) +{ + SQObjectPtr oval1 = PrintObjVal(o1), oval2 = PrintObjVal(o2); + Raise_Error(_SC("comparison between '%.50s' and '%.50s'"), _stringval(oval1), _stringval(oval2)); +} + + +void SQVM::Raise_ParamTypeError(SQInteger nparam,SQInteger typemask,SQInteger type) +{ + SQObjectPtr exptypes = SQString::Create(_ss(this), _SC(""), -1); + SQInteger found = 0; + for(SQInteger i=0; i<16; i++) + { + SQInteger mask = ((SQInteger)1) << i; + if(typemask & (mask)) { + if(found>0) StringCat(exptypes,SQString::Create(_ss(this), _SC("|"), -1), exptypes); + found ++; + StringCat(exptypes,SQString::Create(_ss(this), IdType2Name((SQObjectType)mask), -1), exptypes); + } + } + Raise_Error(_SC("parameter %d has an invalid type '%s' ; expected: '%s'"), nparam, IdType2Name((SQObjectType)type), _stringval(exptypes)); +} diff --git a/src/vscript/squirrel/squirrel/sqfuncproto.h b/src/vscript/squirrel/squirrel/sqfuncproto.h new file mode 100644 index 00000000..546dbabb --- /dev/null +++ b/src/vscript/squirrel/squirrel/sqfuncproto.h @@ -0,0 +1,154 @@ +/* see copyright notice in squirrel.h */ +#ifndef _SQFUNCTION_H_ +#define _SQFUNCTION_H_ + +#include "sqopcodes.h" + +enum SQOuterType { + otLOCAL = 0, + otOUTER = 1 +}; + +struct SQOuterVar +{ + + SQOuterVar(){} + SQOuterVar(const SQObjectPtr &name,const SQObjectPtr &src,SQOuterType t) + { + _name = name; + _src=src; + _type=t; + } + SQOuterVar(const SQOuterVar &ov) + { + _type=ov._type; + _src=ov._src; + _name=ov._name; + } + SQOuterType _type; + SQObjectPtr _name; + SQObjectPtr _src; +}; + +struct SQLocalVarInfo +{ + SQLocalVarInfo():_start_op(0),_end_op(0),_pos(0){} + SQLocalVarInfo(const SQLocalVarInfo &lvi) + { + _name=lvi._name; + _start_op=lvi._start_op; + _end_op=lvi._end_op; + _pos=lvi._pos; + } + SQObjectPtr _name; + SQUnsignedInteger _start_op; + SQUnsignedInteger _end_op; + SQUnsignedInteger _pos; +}; + +struct SQLineInfo { SQInteger _line;SQInteger _op; }; + +typedef sqvector SQOuterVarVec; +typedef sqvector SQLocalVarInfoVec; +typedef sqvector SQLineInfoVec; + +#define _FUNC_SIZE(ni,nl,nparams,nfuncs,nouters,nlineinf,localinf,defparams) (sizeof(SQFunctionProto) \ + +((ni-1)*sizeof(SQInstruction))+(nl*sizeof(SQObjectPtr)) \ + +(nparams*sizeof(SQObjectPtr))+(nfuncs*sizeof(SQObjectPtr)) \ + +(nouters*sizeof(SQOuterVar))+(nlineinf*sizeof(SQLineInfo)) \ + +(localinf*sizeof(SQLocalVarInfo))+(defparams*sizeof(SQInteger))) + + +struct SQFunctionProto : public CHAINABLE_OBJ +{ +private: + SQFunctionProto(SQSharedState *ss); + ~SQFunctionProto(); + +public: + static SQFunctionProto *Create(SQSharedState *ss,SQInteger ninstructions, + SQInteger nliterals,SQInteger nparameters, + SQInteger nfunctions,SQInteger noutervalues, + SQInteger nlineinfos,SQInteger nlocalvarinfos,SQInteger ndefaultparams) + { + SQFunctionProto *f; + //I compact the whole class and members in a single memory allocation + f = (SQFunctionProto *)sq_vm_malloc(_FUNC_SIZE(ninstructions,nliterals,nparameters,nfunctions,noutervalues,nlineinfos,nlocalvarinfos,ndefaultparams)); + new (f) SQFunctionProto(ss); + f->_ninstructions = ninstructions; + f->_literals = (SQObjectPtr*)&f->_instructions[ninstructions]; + f->_nliterals = nliterals; + f->_parameters = (SQObjectPtr*)&f->_literals[nliterals]; + f->_nparameters = nparameters; + f->_functions = (SQObjectPtr*)&f->_parameters[nparameters]; + f->_nfunctions = nfunctions; + f->_outervalues = (SQOuterVar*)&f->_functions[nfunctions]; + f->_noutervalues = noutervalues; + f->_lineinfos = (SQLineInfo *)&f->_outervalues[noutervalues]; + f->_nlineinfos = nlineinfos; + f->_localvarinfos = (SQLocalVarInfo *)&f->_lineinfos[nlineinfos]; + f->_nlocalvarinfos = nlocalvarinfos; + f->_defaultparams = (SQInteger *)&f->_localvarinfos[nlocalvarinfos]; + f->_ndefaultparams = ndefaultparams; + + _CONSTRUCT_VECTOR(SQObjectPtr,f->_nliterals,f->_literals); + _CONSTRUCT_VECTOR(SQObjectPtr,f->_nparameters,f->_parameters); + _CONSTRUCT_VECTOR(SQObjectPtr,f->_nfunctions,f->_functions); + _CONSTRUCT_VECTOR(SQOuterVar,f->_noutervalues,f->_outervalues); + //_CONSTRUCT_VECTOR(SQLineInfo,f->_nlineinfos,f->_lineinfos); //not required are 2 integers + _CONSTRUCT_VECTOR(SQLocalVarInfo,f->_nlocalvarinfos,f->_localvarinfos); + return f; + } + void Release(){ + _DESTRUCT_VECTOR(SQObjectPtr,_nliterals,_literals); + _DESTRUCT_VECTOR(SQObjectPtr,_nparameters,_parameters); + _DESTRUCT_VECTOR(SQObjectPtr,_nfunctions,_functions); + _DESTRUCT_VECTOR(SQOuterVar,_noutervalues,_outervalues); + //_DESTRUCT_VECTOR(SQLineInfo,_nlineinfos,_lineinfos); //not required are 2 integers + _DESTRUCT_VECTOR(SQLocalVarInfo,_nlocalvarinfos,_localvarinfos); + SQInteger size = _FUNC_SIZE(_ninstructions,_nliterals,_nparameters,_nfunctions,_noutervalues,_nlineinfos,_nlocalvarinfos,_ndefaultparams); + this->~SQFunctionProto(); + sq_vm_free(this,size); + } + + const SQChar* GetLocal(SQVM *v,SQUnsignedInteger stackbase,SQUnsignedInteger nseq,SQUnsignedInteger nop); + SQInteger GetLine(SQInstruction *curr); + bool Save(SQVM *v,SQUserPointer up,SQWRITEFUNC write); + static bool Load(SQVM *v,SQUserPointer up,SQREADFUNC read,SQObjectPtr &ret); +#ifndef NO_GARBAGE_COLLECTOR + void Mark(SQCollectable **chain); + void Finalize(){ _NULL_SQOBJECT_VECTOR(_literals,_nliterals); } + SQObjectType GetType() {return OT_FUNCPROTO;} +#endif + SQObjectPtr _sourcename; + SQObjectPtr _name; + SQInteger _stacksize; + bool _bgenerator; + SQInteger _varparams; + + SQInteger _nlocalvarinfos; + SQLocalVarInfo *_localvarinfos; + + SQInteger _nlineinfos; + SQLineInfo *_lineinfos; + + SQInteger _nliterals; + SQObjectPtr *_literals; + + SQInteger _nparameters; + SQObjectPtr *_parameters; + + SQInteger _nfunctions; + SQObjectPtr *_functions; + + SQInteger _noutervalues; + SQOuterVar *_outervalues; + + SQInteger _ndefaultparams; + SQInteger *_defaultparams; + + SQInteger _ninstructions; + SQInstruction _instructions[1]; +}; + +#endif //_SQFUNCTION_H_ diff --git a/src/vscript/squirrel/squirrel/sqfuncstate.cpp b/src/vscript/squirrel/squirrel/sqfuncstate.cpp new file mode 100644 index 00000000..779b40df --- /dev/null +++ b/src/vscript/squirrel/squirrel/sqfuncstate.cpp @@ -0,0 +1,649 @@ +/* + see copyright notice in squirrel.h +*/ +#include "sqpcheader.h" +#ifndef NO_COMPILER +#include "sqcompiler.h" +#include "sqstring.h" +#include "sqfuncproto.h" +#include "sqtable.h" +#include "sqopcodes.h" +#include "sqfuncstate.h" + +#ifdef _DEBUG_DUMP +SQInstructionDesc g_InstrDesc[]={ + {_SC("_OP_LINE")}, + {_SC("_OP_LOAD")}, + {_SC("_OP_LOADINT")}, + {_SC("_OP_LOADFLOAT")}, + {_SC("_OP_DLOAD")}, + {_SC("_OP_TAILCALL")}, + {_SC("_OP_CALL")}, + {_SC("_OP_PREPCALL")}, + {_SC("_OP_PREPCALLK")}, + {_SC("_OP_GETK")}, + {_SC("_OP_MOVE")}, + {_SC("_OP_NEWSLOT")}, + {_SC("_OP_DELETE")}, + {_SC("_OP_SET")}, + {_SC("_OP_GET")}, + {_SC("_OP_EQ")}, + {_SC("_OP_NE")}, + {_SC("_OP_ADD")}, + {_SC("_OP_SUB")}, + {_SC("_OP_MUL")}, + {_SC("_OP_DIV")}, + {_SC("_OP_MOD")}, + {_SC("_OP_BITW")}, + {_SC("_OP_RETURN")}, + {_SC("_OP_LOADNULLS")}, + {_SC("_OP_LOADROOT")}, + {_SC("_OP_LOADBOOL")}, + {_SC("_OP_DMOVE")}, + {_SC("_OP_JMP")}, + {_SC("_OP_JCMP")}, + {_SC("_OP_JZ")}, + {_SC("_OP_SETOUTER")}, + {_SC("_OP_GETOUTER")}, + {_SC("_OP_NEWOBJ")}, + {_SC("_OP_APPENDARRAY")}, + {_SC("_OP_COMPARITH")}, + {_SC("_OP_INC")}, + {_SC("_OP_INCL")}, + {_SC("_OP_PINC")}, + {_SC("_OP_PINCL")}, + {_SC("_OP_CMP")}, + {_SC("_OP_EXISTS")}, + {_SC("_OP_INSTANCEOF")}, + {_SC("_OP_AND")}, + {_SC("_OP_OR")}, + {_SC("_OP_NEG")}, + {_SC("_OP_NOT")}, + {_SC("_OP_BWNOT")}, + {_SC("_OP_CLOSURE")}, + {_SC("_OP_YIELD")}, + {_SC("_OP_RESUME")}, + {_SC("_OP_FOREACH")}, + {_SC("_OP_POSTFOREACH")}, + {_SC("_OP_CLONE")}, + {_SC("_OP_TYPEOF")}, + {_SC("_OP_PUSHTRAP")}, + {_SC("_OP_POPTRAP")}, + {_SC("_OP_THROW")}, + {_SC("_OP_NEWSLOTA")}, + {_SC("_OP_GETBASE")}, + {_SC("_OP_CLOSE")}, +}; +#endif +void DumpLiteral(SQObjectPtr &o) +{ + switch(sq_type(o)){ + case OT_STRING: scprintf(_SC("\"%s\""),_stringval(o));break; + case OT_FLOAT: scprintf(_SC("{%f}"),_float(o));break; + case OT_INTEGER: scprintf(_SC("{") _PRINT_INT_FMT _SC("}"),_integer(o));break; + case OT_BOOL: scprintf(_SC("%s"),_integer(o)?_SC("true"):_SC("false"));break; + default: scprintf(_SC("(%s %p)"),GetTypeName(o),(void*)_rawval(o));break; break; //shut up compiler + } +} + +SQFuncState::SQFuncState(SQSharedState *ss,SQFuncState *parent,CompilerErrorFunc efunc,void *ed) +{ + _nliterals = 0; + _literals = SQTable::Create(ss,0); + _strings = SQTable::Create(ss,0); + _sharedstate = ss; + _lastline = 0; + _optimization = true; + _parent = parent; + _stacksize = 0; + _traps = 0; + _returnexp = 0; + _varparams = false; + _errfunc = efunc; + _errtarget = ed; + _bgenerator = false; + _outers = 0; + _ss = ss; + +} + +void SQFuncState::Error(const SQChar *err) +{ + _errfunc(_errtarget,err); +} + +#ifdef _DEBUG_DUMP +void SQFuncState::Dump(SQFunctionProto *func) +{ + SQUnsignedInteger n=0,i; + SQInteger si; + scprintf(_SC("SQInstruction sizeof %d\n"),(SQInt32)sizeof(SQInstruction)); + scprintf(_SC("SQObject sizeof %d\n"), (SQInt32)sizeof(SQObject)); + scprintf(_SC("--------------------------------------------------------------------\n")); + scprintf(_SC("*****FUNCTION [%s]\n"),sq_type(func->_name)==OT_STRING?_stringval(func->_name):_SC("unknown")); + scprintf(_SC("-----LITERALS\n")); + SQObjectPtr refidx,key,val; + SQInteger idx; + SQObjectPtrVec templiterals; + templiterals.resize(_nliterals); + while((idx=_table(_literals)->Next(false,refidx,key,val))!=-1) { + refidx=idx; + templiterals[_integer(val)]=key; + } + for(i=0;i>\n")); + n=0; + for(i=0;i<_parameters.size();i++){ + scprintf(_SC("[%d] "), (SQInt32)n); + DumpLiteral(_parameters[i]); + scprintf(_SC("\n")); + n++; + } + scprintf(_SC("-----LOCALS\n")); + for(si=0;si_nlocalvarinfos;si++){ + SQLocalVarInfo lvi=func->_localvarinfos[si]; + scprintf(_SC("[%d] %s \t%d %d\n"), (SQInt32)lvi._pos,_stringval(lvi._name), (SQInt32)lvi._start_op, (SQInt32)lvi._end_op); + n++; + } + scprintf(_SC("-----LINE INFO\n")); + for(i=0;i<_lineinfos.size();i++){ + SQLineInfo li=_lineinfos[i]; + scprintf(_SC("op [%d] line [%d] \n"), (SQInt32)li._op, (SQInt32)li._line); + n++; + } + scprintf(_SC("-----dump\n")); + n=0; + for(i=0;i<_instructions.size();i++){ + SQInstruction &inst=_instructions[i]; + if(inst.op==_OP_LOAD || inst.op==_OP_DLOAD || inst.op==_OP_PREPCALLK || inst.op==_OP_GETK ){ + + SQInteger lidx = inst._arg1; + scprintf(_SC("[%03d] %15s %d "), (SQInt32)n,g_InstrDesc[inst.op].name,inst._arg0); + if(lidx >= 0xFFFFFFFF) + scprintf(_SC("null")); + else { + SQInteger refidx; + SQObjectPtr val,key,refo; + while(((refidx=_table(_literals)->Next(false,refo,key,val))!= -1) && (_integer(val) != lidx)) { + refo = refidx; + } + DumpLiteral(key); + } + if(inst.op != _OP_DLOAD) { + scprintf(_SC(" %d %d \n"),inst._arg2,inst._arg3); + } + else { + scprintf(_SC(" %d "),inst._arg2); + lidx = inst._arg3; + if(lidx >= 0xFFFFFFFF) + scprintf(_SC("null")); + else { + SQInteger refidx; + SQObjectPtr val,key,refo; + while(((refidx=_table(_literals)->Next(false,refo,key,val))!= -1) && (_integer(val) != lidx)) { + refo = refidx; + } + DumpLiteral(key); + scprintf(_SC("\n")); + } + } + } + else if(inst.op==_OP_LOADFLOAT) { + scprintf(_SC("[%03d] %15s %d %f %d %d\n"), (SQInt32)n,g_InstrDesc[inst.op].name,inst._arg0,*((SQFloat*)&inst._arg1),inst._arg2,inst._arg3); + } + /* else if(inst.op==_OP_ARITH){ + scprintf(_SC("[%03d] %15s %d %d %d %c\n"),n,g_InstrDesc[inst.op].name,inst._arg0,inst._arg1,inst._arg2,inst._arg3); + }*/ + else { + scprintf(_SC("[%03d] %15s %d %d %d %d\n"), (SQInt32)n,g_InstrDesc[inst.op].name,inst._arg0,inst._arg1,inst._arg2,inst._arg3); + } + n++; + } + scprintf(_SC("-----\n")); + scprintf(_SC("stack size[%d]\n"), (SQInt32)func->_stacksize); + scprintf(_SC("--------------------------------------------------------------------\n\n")); +} +#endif + +SQInteger SQFuncState::GetNumericConstant(const SQInteger cons) +{ + return GetConstant(SQObjectPtr(cons)); +} + +SQInteger SQFuncState::GetNumericConstant(const SQFloat cons) +{ + return GetConstant(SQObjectPtr(cons)); +} + +SQInteger SQFuncState::GetConstant(const SQObject &cons) +{ + SQObjectPtr val; + if(!_table(_literals)->Get(cons,val)) + { + val = _nliterals; + _table(_literals)->NewSlot(cons,val); + _nliterals++; + if(_nliterals > MAX_LITERALS) { + val.Null(); + Error(_SC("internal compiler error: too many literals")); + } + } + return _integer(val); +} + +void SQFuncState::SetInstructionParams(SQInteger pos,SQInteger arg0,SQInteger arg1,SQInteger arg2,SQInteger arg3) +{ + _instructions[pos]._arg0=(unsigned char)*((SQUnsignedInteger *)&arg0); + _instructions[pos]._arg1=(SQInt32)*((SQUnsignedInteger *)&arg1); + _instructions[pos]._arg2=(unsigned char)*((SQUnsignedInteger *)&arg2); + _instructions[pos]._arg3=(unsigned char)*((SQUnsignedInteger *)&arg3); +} + +void SQFuncState::SetInstructionParam(SQInteger pos,SQInteger arg,SQInteger val) +{ + switch(arg){ + case 0:_instructions[pos]._arg0=(unsigned char)*((SQUnsignedInteger *)&val);break; + case 1:case 4:_instructions[pos]._arg1=(SQInt32)*((SQUnsignedInteger *)&val);break; + case 2:_instructions[pos]._arg2=(unsigned char)*((SQUnsignedInteger *)&val);break; + case 3:_instructions[pos]._arg3=(unsigned char)*((SQUnsignedInteger *)&val);break; + }; +} + +SQInteger SQFuncState::AllocStackPos() +{ + SQInteger npos=_vlocals.size(); + _vlocals.push_back(SQLocalVarInfo()); + if(_vlocals.size()>((SQUnsignedInteger)_stacksize)) { + if(_stacksize>MAX_FUNC_STACKSIZE) Error(_SC("internal compiler error: too many locals")); + _stacksize=_vlocals.size(); + } + return npos; +} + +SQInteger SQFuncState::PushTarget(SQInteger n) +{ + if(n!=-1){ + _targetstack.push_back(n); + return n; + } + n=AllocStackPos(); + _targetstack.push_back(n); + return n; +} + +SQInteger SQFuncState::GetUpTarget(SQInteger n){ + return _targetstack[((_targetstack.size()-1)-n)]; +} + +SQInteger SQFuncState::TopTarget(){ + return _targetstack.back(); +} +SQInteger SQFuncState::PopTarget() +{ + SQUnsignedInteger npos=_targetstack.back(); + assert(npos < _vlocals.size()); + SQLocalVarInfo &t = _vlocals[npos]; + if(sq_type(t._name)==OT_NULL){ + _vlocals.pop_back(); + } + _targetstack.pop_back(); + return npos; +} + +SQInteger SQFuncState::GetStackSize() +{ + return _vlocals.size(); +} + +SQInteger SQFuncState::CountOuters(SQInteger stacksize) +{ + SQInteger outers = 0; + SQInteger k = _vlocals.size() - 1; + while(k >= stacksize) { + SQLocalVarInfo &lvi = _vlocals[k]; + k--; + if(lvi._end_op == UINT_MINUS_ONE) { //this means is an outer + outers++; + } + } + return outers; +} + +void SQFuncState::SetStackSize(SQInteger n) +{ + SQInteger size=_vlocals.size(); + while(size>n){ + size--; + SQLocalVarInfo lvi = _vlocals.back(); + if(sq_type(lvi._name)!=OT_NULL){ + if(lvi._end_op == UINT_MINUS_ONE) { //this means is an outer + _outers--; + } + lvi._end_op = GetCurrentPos(); + _localvarinfos.push_back(lvi); + } + _vlocals.pop_back(); + } +} + +bool SQFuncState::IsConstant(const SQObject &name,SQObject &e) +{ + SQObjectPtr val; + if(_table(_sharedstate->_consts)->Get(name,val)) { + e = val; + return true; + } + return false; +} + +bool SQFuncState::IsLocal(SQUnsignedInteger stkpos) +{ + if(stkpos>=_vlocals.size())return false; + else if(sq_type(_vlocals[stkpos]._name)!=OT_NULL)return true; + return false; +} + +SQInteger SQFuncState::PushLocalVariable(const SQObject &name) +{ + SQInteger pos=_vlocals.size(); + SQLocalVarInfo lvi; + lvi._name=name; + lvi._start_op=GetCurrentPos()+1; + lvi._pos=_vlocals.size(); + _vlocals.push_back(lvi); + if(_vlocals.size()>((SQUnsignedInteger)_stacksize))_stacksize=_vlocals.size(); + return pos; +} + + + +SQInteger SQFuncState::GetLocalVariable(const SQObject &name) +{ + SQInteger locals=_vlocals.size(); + while(locals>=1){ + SQLocalVarInfo &lvi = _vlocals[locals-1]; + if(sq_type(lvi._name)==OT_STRING && _string(lvi._name)==_string(name)){ + return locals-1; + } + locals--; + } + return -1; +} + +void SQFuncState::MarkLocalAsOuter(SQInteger pos) +{ + SQLocalVarInfo &lvi = _vlocals[pos]; + lvi._end_op = UINT_MINUS_ONE; + _outers++; +} + +SQInteger SQFuncState::GetOuterVariable(const SQObject &name) +{ + SQInteger outers = _outervalues.size(); + for(SQInteger i = 0; iGetLocalVariable(name); + if(pos == -1) { + pos = _parent->GetOuterVariable(name); + if(pos != -1) { + _outervalues.push_back(SQOuterVar(name,SQObjectPtr(SQInteger(pos)),otOUTER)); //local + return _outervalues.size() - 1; + } + } + else { + _parent->MarkLocalAsOuter(pos); + _outervalues.push_back(SQOuterVar(name,SQObjectPtr(SQInteger(pos)),otLOCAL)); //local + return _outervalues.size() - 1; + + + } + } + return -1; +} + +void SQFuncState::AddParameter(const SQObject &name) +{ + PushLocalVariable(name); + _parameters.push_back(name); +} + +void SQFuncState::AddLineInfos(SQInteger line,bool lineop,bool force) +{ + if(_lastline!=line || force){ + SQLineInfo li; + li._line=line;li._op=(GetCurrentPos()+1); + if(lineop)AddInstruction(_OP_LINE,0,line); + if(_lastline!=line) { + _lineinfos.push_back(li); + } + _lastline=line; + } +} + +void SQFuncState::DiscardTarget() +{ + SQInteger discardedtarget = PopTarget(); + SQInteger size = _instructions.size(); + if(size > 0 && _optimization){ + SQInstruction &pi = _instructions[size-1];//previous instruction + switch(pi.op) { + case _OP_SET:case _OP_NEWSLOT:case _OP_SETOUTER:case _OP_CALL: + if(pi._arg0 == discardedtarget) { + pi._arg0 = 0xFF; + } + } + } +} + +void SQFuncState::AddInstruction(SQInstruction &i) +{ + SQInteger size = _instructions.size(); + if(size > 0 && _optimization){ //simple optimizer + SQInstruction &pi = _instructions[size-1];//previous instruction + switch(i.op) { + case _OP_JZ: + if( pi.op == _OP_CMP && pi._arg1 < 0xFF) { + pi.op = _OP_JCMP; + pi._arg0 = (unsigned char)pi._arg1; + pi._arg1 = i._arg1; + return; + } + break; + case _OP_SET: + case _OP_NEWSLOT: + if(i._arg0 == i._arg3) { + i._arg0 = 0xFF; + } + break; + case _OP_SETOUTER: + if(i._arg0 == i._arg2) { + i._arg0 = 0xFF; + } + break; + case _OP_RETURN: + if( _parent && i._arg0 != MAX_FUNC_STACKSIZE && pi.op == _OP_CALL && _returnexp < size-1) { + pi.op = _OP_TAILCALL; + } else if(pi.op == _OP_CLOSE){ + pi = i; + return; + } + break; + case _OP_GET: + if( pi.op == _OP_LOAD && pi._arg0 == i._arg2 && (!IsLocal(pi._arg0))){ + pi._arg2 = (unsigned char)i._arg1; + pi.op = _OP_GETK; + pi._arg0 = i._arg0; + + return; + } + break; + case _OP_PREPCALL: + if( pi.op == _OP_LOAD && pi._arg0 == i._arg1 && (!IsLocal(pi._arg0))){ + pi.op = _OP_PREPCALLK; + pi._arg0 = i._arg0; + pi._arg2 = i._arg2; + pi._arg3 = i._arg3; + return; + } + break; + case _OP_APPENDARRAY: { + SQInteger aat = -1; + switch(pi.op) { + case _OP_LOAD: aat = AAT_LITERAL; break; + case _OP_LOADINT: aat = AAT_INT; break; + case _OP_LOADBOOL: aat = AAT_BOOL; break; + case _OP_LOADFLOAT: aat = AAT_FLOAT; break; + default: break; + } + if(aat != -1 && pi._arg0 == i._arg1 && (!IsLocal(pi._arg0))){ + pi.op = _OP_APPENDARRAY; + pi._arg0 = i._arg0; + pi._arg2 = (unsigned char)aat; + pi._arg3 = MAX_FUNC_STACKSIZE; + return; + } + } + break; + case _OP_MOVE: + switch(pi.op) { + case _OP_GET: case _OP_ADD: case _OP_SUB: case _OP_MUL: case _OP_DIV: case _OP_MOD: case _OP_BITW: + case _OP_LOADINT: case _OP_LOADFLOAT: case _OP_LOADBOOL: case _OP_LOAD: + + if(pi._arg0 == i._arg1) + { + pi._arg0 = i._arg0; + _optimization = false; + //_result_elimination = false; + return; + } + } + + if(pi.op == _OP_MOVE) + { + pi.op = _OP_DMOVE; + pi._arg2 = i._arg0; + pi._arg3 = (unsigned char)i._arg1; + return; + } + break; + case _OP_LOAD: + if(pi.op == _OP_LOAD && i._arg1 < 256) { + pi.op = _OP_DLOAD; + pi._arg2 = i._arg0; + pi._arg3 = (unsigned char)i._arg1; + return; + } + break; + case _OP_EQ:case _OP_NE: + if(pi.op == _OP_LOAD && pi._arg0 == i._arg1 && (!IsLocal(pi._arg0) )) + { + pi.op = i.op; + pi._arg0 = i._arg0; + pi._arg2 = i._arg2; + pi._arg3 = MAX_FUNC_STACKSIZE; + return; + } + break; + case _OP_LOADNULLS: + if((pi.op == _OP_LOADNULLS && pi._arg0+pi._arg1 == i._arg0)) { + + pi._arg1 = pi._arg1 + 1; + pi.op = _OP_LOADNULLS; + return; + } + break; + case _OP_LINE: + if(pi.op == _OP_LINE) { + _instructions.pop_back(); + _lineinfos.pop_back(); + } + break; + } + } + _optimization = true; + _instructions.push_back(i); +} + +SQObject SQFuncState::CreateString(const SQChar *s,SQInteger len) +{ + SQObjectPtr ns(SQString::Create(_sharedstate,s,len)); + _table(_strings)->NewSlot(ns,(SQInteger)1); + return ns; +} + +SQObject SQFuncState::CreateTable() +{ + SQObjectPtr nt(SQTable::Create(_sharedstate,0)); + _table(_strings)->NewSlot(nt,(SQInteger)1); + return nt; +} + +SQFunctionProto *SQFuncState::BuildProto() +{ + + SQFunctionProto *f=SQFunctionProto::Create(_ss,_instructions.size(), + _nliterals,_parameters.size(),_functions.size(),_outervalues.size(), + _lineinfos.size(),_localvarinfos.size(),_defaultparams.size()); + + SQObjectPtr refidx,key,val; + SQInteger idx; + + f->_stacksize = _stacksize; + f->_sourcename = _sourcename; + f->_bgenerator = _bgenerator; + f->_name = _name; + + while((idx=_table(_literals)->Next(false,refidx,key,val))!=-1) { + f->_literals[_integer(val)]=key; + refidx=idx; + } + + for(SQUnsignedInteger nf = 0; nf < _functions.size(); nf++) f->_functions[nf] = _functions[nf]; + for(SQUnsignedInteger np = 0; np < _parameters.size(); np++) f->_parameters[np] = _parameters[np]; + for(SQUnsignedInteger no = 0; no < _outervalues.size(); no++) f->_outervalues[no] = _outervalues[no]; + for(SQUnsignedInteger nl = 0; nl < _localvarinfos.size(); nl++) f->_localvarinfos[nl] = _localvarinfos[nl]; + for(SQUnsignedInteger ni = 0; ni < _lineinfos.size(); ni++) f->_lineinfos[ni] = _lineinfos[ni]; + for(SQUnsignedInteger nd = 0; nd < _defaultparams.size(); nd++) f->_defaultparams[nd] = _defaultparams[nd]; + + memcpy(f->_instructions,&_instructions[0],_instructions.size()*sizeof(SQInstruction)); + + f->_varparams = _varparams; + + return f; +} + +SQFuncState *SQFuncState::PushChildState(SQSharedState *ss) +{ + SQFuncState *child = (SQFuncState *)sq_malloc(sizeof(SQFuncState)); + new (child) SQFuncState(ss,this,_errfunc,_errtarget); + _childstates.push_back(child); + return child; +} + +void SQFuncState::PopChildState() +{ + SQFuncState *child = _childstates.back(); + sq_delete(child,SQFuncState); + _childstates.pop_back(); +} + +SQFuncState::~SQFuncState() +{ + while(_childstates.size() > 0) + { + PopChildState(); + } +} + +#endif diff --git a/src/vscript/squirrel/squirrel/sqfuncstate.h b/src/vscript/squirrel/squirrel/sqfuncstate.h new file mode 100644 index 00000000..130aa54f --- /dev/null +++ b/src/vscript/squirrel/squirrel/sqfuncstate.h @@ -0,0 +1,91 @@ +/* see copyright notice in squirrel.h */ +#ifndef _SQFUNCSTATE_H_ +#define _SQFUNCSTATE_H_ +/////////////////////////////////// +#include "squtils.h" + +struct SQFuncState +{ + SQFuncState(SQSharedState *ss,SQFuncState *parent,CompilerErrorFunc efunc,void *ed); + ~SQFuncState(); +#ifdef _DEBUG_DUMP + void Dump(SQFunctionProto *func); +#endif + void Error(const SQChar *err); + SQFuncState *PushChildState(SQSharedState *ss); + void PopChildState(); + void AddInstruction(SQOpcode _op,SQInteger arg0=0,SQInteger arg1=0,SQInteger arg2=0,SQInteger arg3=0){SQInstruction i(_op,arg0,arg1,arg2,arg3);AddInstruction(i);} + void AddInstruction(SQInstruction &i); + void SetInstructionParams(SQInteger pos,SQInteger arg0,SQInteger arg1,SQInteger arg2=0,SQInteger arg3=0); + void SetInstructionParam(SQInteger pos,SQInteger arg,SQInteger val); + SQInstruction &GetInstruction(SQInteger pos){return _instructions[pos];} + void PopInstructions(SQInteger size){for(SQInteger i=0;i _childstates; + SQInteger GetConstant(const SQObject &cons); +private: + CompilerErrorFunc _errfunc; + void *_errtarget; + SQSharedState *_ss; +}; + + +#endif //_SQFUNCSTATE_H_ + diff --git a/src/vscript/squirrel/squirrel/sqlexer.cpp b/src/vscript/squirrel/squirrel/sqlexer.cpp new file mode 100644 index 00000000..6a8e7c4f --- /dev/null +++ b/src/vscript/squirrel/squirrel/sqlexer.cpp @@ -0,0 +1,563 @@ +/* + see copyright notice in squirrel.h +*/ +#include "sqpcheader.h" +#include +#include +#include "sqtable.h" +#include "sqstring.h" +#include "sqcompiler.h" +#include "sqlexer.h" + +#define CUR_CHAR (_currdata) +#define RETURN_TOKEN(t) { _prevtoken = _curtoken; _curtoken = t; return t;} +#define IS_EOB() (CUR_CHAR <= SQUIRREL_EOB) +#define NEXT() {Next();_currentcolumn++;} +#define INIT_TEMP_STRING() { _longstr.resize(0);} +#define APPEND_CHAR(c) { _longstr.push_back(c);} +#define TERMINATE_BUFFER() {_longstr.push_back(_SC('\0'));} +#define ADD_KEYWORD(key,id) _keywords->NewSlot( SQString::Create(ss, _SC(#key)) ,SQInteger(id)) + +SQLexer::SQLexer(){} +SQLexer::~SQLexer() +{ + _keywords->Release(); +} + +void SQLexer::Init(SQSharedState *ss, SQLEXREADFUNC rg, SQUserPointer up,CompilerErrorFunc efunc,void *ed) +{ + _errfunc = efunc; + _errtarget = ed; + _sharedstate = ss; + _keywords = SQTable::Create(ss, 37); + ADD_KEYWORD(while, TK_WHILE); + ADD_KEYWORD(do, TK_DO); + ADD_KEYWORD(if, TK_IF); + ADD_KEYWORD(else, TK_ELSE); + ADD_KEYWORD(break, TK_BREAK); + ADD_KEYWORD(continue, TK_CONTINUE); + ADD_KEYWORD(return, TK_RETURN); + ADD_KEYWORD(null, TK_NULL); + ADD_KEYWORD(function, TK_FUNCTION); + ADD_KEYWORD(local, TK_LOCAL); + ADD_KEYWORD(for, TK_FOR); + ADD_KEYWORD(foreach, TK_FOREACH); + ADD_KEYWORD(in, TK_IN); + ADD_KEYWORD(typeof, TK_TYPEOF); + ADD_KEYWORD(base, TK_BASE); + ADD_KEYWORD(delete, TK_DELETE); + ADD_KEYWORD(try, TK_TRY); + ADD_KEYWORD(catch, TK_CATCH); + ADD_KEYWORD(throw, TK_THROW); + ADD_KEYWORD(clone, TK_CLONE); + ADD_KEYWORD(yield, TK_YIELD); + ADD_KEYWORD(resume, TK_RESUME); + ADD_KEYWORD(switch, TK_SWITCH); + ADD_KEYWORD(case, TK_CASE); + ADD_KEYWORD(default, TK_DEFAULT); + ADD_KEYWORD(this, TK_THIS); + ADD_KEYWORD(class,TK_CLASS); + ADD_KEYWORD(extends,TK_EXTENDS); + ADD_KEYWORD(constructor,TK_CONSTRUCTOR); + ADD_KEYWORD(instanceof,TK_INSTANCEOF); + ADD_KEYWORD(true,TK_TRUE); + ADD_KEYWORD(false,TK_FALSE); + ADD_KEYWORD(static,TK_STATIC); + ADD_KEYWORD(enum,TK_ENUM); + ADD_KEYWORD(const,TK_CONST); + ADD_KEYWORD(__LINE__,TK___LINE__); + ADD_KEYWORD(__FILE__,TK___FILE__); + ADD_KEYWORD(rawcall, TK_RAWCALL); + + + _readf = rg; + _up = up; + _lasttokenline = _currentline = 1; + _currentcolumn = 0; + _prevtoken = -1; + _reached_eof = SQFalse; + Next(); +} + +void SQLexer::Error(const SQChar *err) +{ + _errfunc(_errtarget,err); +} + +void SQLexer::Next() +{ + SQInteger t = _readf(_up); + if(t > MAX_CHAR) Error(_SC("Invalid character")); + if(t != 0) { + _currdata = (LexChar)t; + return; + } + _currdata = SQUIRREL_EOB; + _reached_eof = SQTrue; +} + +const SQChar *SQLexer::Tok2Str(SQInteger tok) +{ + SQObjectPtr itr, key, val; + SQInteger nitr; + while((nitr = _keywords->Next(false,itr, key, val)) != -1) { + itr = (SQInteger)nitr; + if(((SQInteger)_integer(val)) == tok) + return _stringval(key); + } + return NULL; +} + +void SQLexer::LexBlockComment() +{ + bool done = false; + while(!done) { + switch(CUR_CHAR) { + case _SC('*'): { NEXT(); if(CUR_CHAR == _SC('/')) { done = true; NEXT(); }}; continue; + case _SC('\n'): _currentline++; NEXT(); continue; + case SQUIRREL_EOB: Error(_SC("missing \"*/\" in comment")); + default: NEXT(); + } + } +} +void SQLexer::LexLineComment() +{ + do { NEXT(); } while (CUR_CHAR != _SC('\n') && (!IS_EOB())); +} + +SQInteger SQLexer::Lex() +{ + _lasttokenline = _currentline; + while(CUR_CHAR != SQUIRREL_EOB) { + switch(CUR_CHAR){ + case _SC('\t'): case _SC('\r'): case _SC(' '): NEXT(); continue; + case _SC('\n'): + _currentline++; + _prevtoken=_curtoken; + _curtoken=_SC('\n'); + NEXT(); + _currentcolumn=1; + continue; + case _SC('#'): LexLineComment(); continue; + case _SC('/'): + NEXT(); + switch(CUR_CHAR){ + case _SC('*'): + NEXT(); + LexBlockComment(); + continue; + case _SC('/'): + LexLineComment(); + continue; + case _SC('='): + NEXT(); + RETURN_TOKEN(TK_DIVEQ); + continue; + case _SC('>'): + NEXT(); + RETURN_TOKEN(TK_ATTR_CLOSE); + continue; + default: + RETURN_TOKEN('/'); + } + case _SC('='): + NEXT(); + if (CUR_CHAR != _SC('=')){ RETURN_TOKEN('=') } + else { NEXT(); RETURN_TOKEN(TK_EQ); } + case _SC('<'): + NEXT(); + switch(CUR_CHAR) { + case _SC('='): + NEXT(); + if(CUR_CHAR == _SC('>')) { + NEXT(); + RETURN_TOKEN(TK_3WAYSCMP); + } + RETURN_TOKEN(TK_LE) + break; + case _SC('-'): NEXT(); RETURN_TOKEN(TK_NEWSLOT); break; + case _SC('<'): NEXT(); RETURN_TOKEN(TK_SHIFTL); break; + case _SC('/'): NEXT(); RETURN_TOKEN(TK_ATTR_OPEN); break; + } + RETURN_TOKEN('<'); + case _SC('>'): + NEXT(); + if (CUR_CHAR == _SC('=')){ NEXT(); RETURN_TOKEN(TK_GE);} + else if(CUR_CHAR == _SC('>')){ + NEXT(); + if(CUR_CHAR == _SC('>')){ + NEXT(); + RETURN_TOKEN(TK_USHIFTR); + } + RETURN_TOKEN(TK_SHIFTR); + } + else { RETURN_TOKEN('>') } + case _SC('!'): + NEXT(); + if (CUR_CHAR != _SC('=')){ RETURN_TOKEN('!')} + else { NEXT(); RETURN_TOKEN(TK_NE); } + case _SC('@'): { + SQInteger stype; + NEXT(); + if(CUR_CHAR != _SC('"')) { + RETURN_TOKEN('@'); + } + if((stype=ReadString('"',true))!=-1) { + RETURN_TOKEN(stype); + } + Error(_SC("error parsing the string")); + } + case _SC('"'): + case _SC('\''): { + SQInteger stype; + if((stype=ReadString(CUR_CHAR,false))!=-1){ + RETURN_TOKEN(stype); + } + Error(_SC("error parsing the string")); + } + case _SC('{'): case _SC('}'): case _SC('('): case _SC(')'): case _SC('['): case _SC(']'): + case _SC(';'): case _SC(','): case _SC('?'): case _SC('^'): case _SC('~'): + {SQInteger ret = CUR_CHAR; + NEXT(); RETURN_TOKEN(ret); } + case _SC('.'): + NEXT(); + if (CUR_CHAR != _SC('.')){ RETURN_TOKEN('.') } + NEXT(); + if (CUR_CHAR != _SC('.')){ Error(_SC("invalid token '..'")); } + NEXT(); + RETURN_TOKEN(TK_VARPARAMS); + case _SC('&'): + NEXT(); + if (CUR_CHAR != _SC('&')){ RETURN_TOKEN('&') } + else { NEXT(); RETURN_TOKEN(TK_AND); } + case _SC('|'): + NEXT(); + if (CUR_CHAR != _SC('|')){ RETURN_TOKEN('|') } + else { NEXT(); RETURN_TOKEN(TK_OR); } + case _SC(':'): + NEXT(); + if (CUR_CHAR != _SC(':')){ RETURN_TOKEN(':') } + else { NEXT(); RETURN_TOKEN(TK_DOUBLE_COLON); } + case _SC('*'): + NEXT(); + if (CUR_CHAR == _SC('=')){ NEXT(); RETURN_TOKEN(TK_MULEQ);} + else RETURN_TOKEN('*'); + case _SC('%'): + NEXT(); + if (CUR_CHAR == _SC('=')){ NEXT(); RETURN_TOKEN(TK_MODEQ);} + else RETURN_TOKEN('%'); + case _SC('-'): + NEXT(); + if (CUR_CHAR == _SC('=')){ NEXT(); RETURN_TOKEN(TK_MINUSEQ);} + else if (CUR_CHAR == _SC('-')){ NEXT(); RETURN_TOKEN(TK_MINUSMINUS);} + else RETURN_TOKEN('-'); + case _SC('+'): + NEXT(); + if (CUR_CHAR == _SC('=')){ NEXT(); RETURN_TOKEN(TK_PLUSEQ);} + else if (CUR_CHAR == _SC('+')){ NEXT(); RETURN_TOKEN(TK_PLUSPLUS);} + else RETURN_TOKEN('+'); + case SQUIRREL_EOB: + return 0; + default:{ + if (scisdigit(CUR_CHAR)) { + SQInteger ret = ReadNumber(); + RETURN_TOKEN(ret); + } + else if (scisalpha(CUR_CHAR) || CUR_CHAR == _SC('_')) { + SQInteger t = ReadID(); + RETURN_TOKEN(t); + } + else { + SQInteger c = CUR_CHAR; + if (sciscntrl((int)c)) Error(_SC("unexpected character(control)")); + NEXT(); + RETURN_TOKEN(c); + } + RETURN_TOKEN(0); + } + } + } + return 0; +} + +SQInteger SQLexer::GetIDType(const SQChar *s,SQInteger len) +{ + SQObjectPtr t; + if(_keywords->GetStr(s,len, t)) { + return SQInteger(_integer(t)); + } + return TK_IDENTIFIER; +} + +#ifdef SQUNICODE +#if WCHAR_SIZE == 2 +SQInteger SQLexer::AddUTF16(SQUnsignedInteger ch) +{ + if (ch >= 0x10000) + { + SQUnsignedInteger code = (ch - 0x10000); + APPEND_CHAR((SQChar)(0xD800 | (code >> 10))); + APPEND_CHAR((SQChar)(0xDC00 | (code & 0x3FF))); + return 2; + } + else { + APPEND_CHAR((SQChar)ch); + return 1; + } +} +#endif +#else +SQInteger SQLexer::AddUTF8(SQUnsignedInteger ch) +{ + if (ch < 0x80) { + APPEND_CHAR((char)ch); + return 1; + } + if (ch < 0x800) { + APPEND_CHAR((SQChar)((ch >> 6) | 0xC0)); + APPEND_CHAR((SQChar)((ch & 0x3F) | 0x80)); + return 2; + } + if (ch < 0x10000) { + APPEND_CHAR((SQChar)((ch >> 12) | 0xE0)); + APPEND_CHAR((SQChar)(((ch >> 6) & 0x3F) | 0x80)); + APPEND_CHAR((SQChar)((ch & 0x3F) | 0x80)); + return 3; + } + if (ch < 0x110000) { + APPEND_CHAR((SQChar)((ch >> 18) | 0xF0)); + APPEND_CHAR((SQChar)(((ch >> 12) & 0x3F) | 0x80)); + APPEND_CHAR((SQChar)(((ch >> 6) & 0x3F) | 0x80)); + APPEND_CHAR((SQChar)((ch & 0x3F) | 0x80)); + return 4; + } + return 0; +} +#endif + +SQInteger SQLexer::ProcessStringHexEscape(SQChar *dest, SQInteger maxdigits) +{ + NEXT(); + if (!isxdigit(CUR_CHAR)) Error(_SC("hexadecimal number expected")); + SQInteger n = 0; + while (isxdigit(CUR_CHAR) && n < maxdigits) { + dest[n] = CUR_CHAR; + n++; + NEXT(); + } + dest[n] = 0; + return n; +} + +SQInteger SQLexer::ReadString(SQInteger ndelim,bool verbatim) +{ + INIT_TEMP_STRING(); + NEXT(); + if(IS_EOB()) return -1; + for(;;) { + while(CUR_CHAR != ndelim) { + SQInteger x = CUR_CHAR; + switch (x) { + case SQUIRREL_EOB: + Error(_SC("unfinished string")); + return -1; + case _SC('\n'): + if(!verbatim) Error(_SC("newline in a constant")); + APPEND_CHAR(CUR_CHAR); NEXT(); + _currentline++; + break; + case _SC('\\'): + if(verbatim) { + APPEND_CHAR('\\'); NEXT(); + } + else { + NEXT(); + switch(CUR_CHAR) { + case _SC('x'): { + const SQInteger maxdigits = sizeof(SQChar) * 2; + SQChar temp[maxdigits + 1]; + ProcessStringHexEscape(temp, maxdigits); + SQChar *stemp; + APPEND_CHAR((SQChar)scstrtoul(temp, &stemp, 16)); + } + break; + case _SC('U'): + case _SC('u'): { + const SQInteger maxdigits = CUR_CHAR == 'u' ? 4 : 8; + SQChar temp[8 + 1]; + ProcessStringHexEscape(temp, maxdigits); + SQChar *stemp; +#ifdef SQUNICODE +#if WCHAR_SIZE == 2 + AddUTF16(scstrtoul(temp, &stemp, 16)); +#else + APPEND_CHAR((SQChar)scstrtoul(temp, &stemp, 16)); +#endif +#else + AddUTF8(scstrtoul(temp, &stemp, 16)); +#endif + } + break; + case _SC('t'): APPEND_CHAR(_SC('\t')); NEXT(); break; + case _SC('a'): APPEND_CHAR(_SC('\a')); NEXT(); break; + case _SC('b'): APPEND_CHAR(_SC('\b')); NEXT(); break; + case _SC('n'): APPEND_CHAR(_SC('\n')); NEXT(); break; + case _SC('r'): APPEND_CHAR(_SC('\r')); NEXT(); break; + case _SC('v'): APPEND_CHAR(_SC('\v')); NEXT(); break; + case _SC('f'): APPEND_CHAR(_SC('\f')); NEXT(); break; + case _SC('0'): APPEND_CHAR(_SC('\0')); NEXT(); break; + case _SC('\\'): APPEND_CHAR(_SC('\\')); NEXT(); break; + case _SC('"'): APPEND_CHAR(_SC('"')); NEXT(); break; + case _SC('\''): APPEND_CHAR(_SC('\'')); NEXT(); break; + default: + Error(_SC("unrecognised escaper char")); + break; + } + } + break; + default: + APPEND_CHAR(CUR_CHAR); + NEXT(); + } + } + NEXT(); + if(verbatim && CUR_CHAR == '"') { //double quotation + APPEND_CHAR(CUR_CHAR); + NEXT(); + } + else { + break; + } + } + TERMINATE_BUFFER(); + SQInteger len = _longstr.size()-1; + if(ndelim == _SC('\'')) { + if(len == 0) Error(_SC("empty constant")); + if(len > 1) Error(_SC("constant too long")); + _nvalue = _longstr[0]; + return TK_INTEGER; + } + _svalue = &_longstr[0]; + return TK_STRING_LITERAL; +} + +void LexHexadecimal(const SQChar *s,SQUnsignedInteger *res) +{ + *res = 0; + while(*s != 0) + { + if(scisdigit(*s)) *res = (*res)*16+((*s++)-'0'); + else if(scisxdigit(*s)) *res = (*res)*16+(toupper(*s++)-'A'+10); + else { assert(0); } + } +} + +void LexInteger(const SQChar *s,SQUnsignedInteger *res) +{ + *res = 0; + while(*s != 0) + { + *res = (*res)*10+((*s++)-'0'); + } +} + +SQInteger scisodigit(SQInteger c) { return c >= _SC('0') && c <= _SC('7'); } + +void LexOctal(const SQChar *s,SQUnsignedInteger *res) +{ + *res = 0; + while(*s != 0) + { + if(scisodigit(*s)) *res = (*res)*8+((*s++)-'0'); + else { assert(0); } + } +} + +SQInteger isexponent(SQInteger c) { return c == 'e' || c=='E'; } + + +#define MAX_HEX_DIGITS (sizeof(SQInteger)*2) +SQInteger SQLexer::ReadNumber() +{ +#define TINT 1 +#define TFLOAT 2 +#define THEX 3 +#define TSCIENTIFIC 4 +#define TOCTAL 5 + SQInteger type = TINT, firstchar = CUR_CHAR; + SQChar *sTemp; + INIT_TEMP_STRING(); + NEXT(); + if(firstchar == _SC('0') && (toupper(CUR_CHAR) == _SC('X') || scisodigit(CUR_CHAR)) ) { + if(scisodigit(CUR_CHAR)) { + type = TOCTAL; + while(scisodigit(CUR_CHAR)) { + APPEND_CHAR(CUR_CHAR); + NEXT(); + } + if(scisdigit(CUR_CHAR)) Error(_SC("invalid octal number")); + } + else { + NEXT(); + type = THEX; + while(isxdigit(CUR_CHAR)) { + APPEND_CHAR(CUR_CHAR); + NEXT(); + } + if(_longstr.size() > MAX_HEX_DIGITS) Error(_SC("too many digits for an Hex number")); + } + } + else { + APPEND_CHAR((int)firstchar); + while (CUR_CHAR == _SC('.') || scisdigit(CUR_CHAR) || isexponent(CUR_CHAR)) { + if(CUR_CHAR == _SC('.') || isexponent(CUR_CHAR)) type = TFLOAT; + if(isexponent(CUR_CHAR)) { + if(type != TFLOAT) Error(_SC("invalid numeric format")); + type = TSCIENTIFIC; + APPEND_CHAR(CUR_CHAR); + NEXT(); + if(CUR_CHAR == '+' || CUR_CHAR == '-'){ + APPEND_CHAR(CUR_CHAR); + NEXT(); + } + if(!scisdigit(CUR_CHAR)) Error(_SC("exponent expected")); + } + + APPEND_CHAR(CUR_CHAR); + NEXT(); + } + } + TERMINATE_BUFFER(); + switch(type) { + case TSCIENTIFIC: + case TFLOAT: + _fvalue = (SQFloat)scstrtod(&_longstr[0],&sTemp); + return TK_FLOAT; + case TINT: + LexInteger(&_longstr[0],(SQUnsignedInteger *)&_nvalue); + return TK_INTEGER; + case THEX: + LexHexadecimal(&_longstr[0],(SQUnsignedInteger *)&_nvalue); + return TK_INTEGER; + case TOCTAL: + LexOctal(&_longstr[0],(SQUnsignedInteger *)&_nvalue); + return TK_INTEGER; + } + return 0; +} + +SQInteger SQLexer::ReadID() +{ + SQInteger res; + INIT_TEMP_STRING(); + do { + APPEND_CHAR(CUR_CHAR); + NEXT(); + } while(scisalnum(CUR_CHAR) || CUR_CHAR == _SC('_')); + TERMINATE_BUFFER(); + res = GetIDType(&_longstr[0],_longstr.size() - 1); + if(res == TK_IDENTIFIER || res == TK_CONSTRUCTOR) { + _svalue = &_longstr[0]; + } + return res; +} diff --git a/src/vscript/squirrel/squirrel/sqlexer.h b/src/vscript/squirrel/squirrel/sqlexer.h new file mode 100644 index 00000000..d731c20e --- /dev/null +++ b/src/vscript/squirrel/squirrel/sqlexer.h @@ -0,0 +1,55 @@ +/* see copyright notice in squirrel.h */ +#ifndef _SQLEXER_H_ +#define _SQLEXER_H_ + +#ifdef SQUNICODE +typedef SQChar LexChar; +#else +typedef unsigned char LexChar; +#endif + +struct SQLexer +{ + SQLexer(); + ~SQLexer(); + void Init(SQSharedState *ss,SQLEXREADFUNC rg,SQUserPointer up,CompilerErrorFunc efunc,void *ed); + void Error(const SQChar *err); + SQInteger Lex(); + const SQChar *Tok2Str(SQInteger tok); +private: + SQInteger GetIDType(const SQChar *s,SQInteger len); + SQInteger ReadString(SQInteger ndelim,bool verbatim); + SQInteger ReadNumber(); + void LexBlockComment(); + void LexLineComment(); + SQInteger ReadID(); + void Next(); +#ifdef SQUNICODE +#if WCHAR_SIZE == 2 + SQInteger AddUTF16(SQUnsignedInteger ch); +#endif +#else + SQInteger AddUTF8(SQUnsignedInteger ch); +#endif + SQInteger ProcessStringHexEscape(SQChar *dest, SQInteger maxdigits); + SQInteger _curtoken; + SQTable *_keywords; + SQBool _reached_eof; +public: + SQInteger _prevtoken; + SQInteger _currentline; + SQInteger _lasttokenline; + SQInteger _currentcolumn; + const SQChar *_svalue; + SQInteger _nvalue; + SQFloat _fvalue; + SQLEXREADFUNC _readf; + SQUserPointer _up; + LexChar _currdata; + SQSharedState *_sharedstate; + sqvector _longstr; + CompilerErrorFunc _errfunc; + void *_errtarget; +}; + +#endif diff --git a/src/vscript/squirrel/squirrel/sqmem.cpp b/src/vscript/squirrel/squirrel/sqmem.cpp new file mode 100644 index 00000000..378e254b --- /dev/null +++ b/src/vscript/squirrel/squirrel/sqmem.cpp @@ -0,0 +1,11 @@ +/* + see copyright notice in squirrel.h +*/ +#include "sqpcheader.h" +#ifndef SQ_EXCLUDE_DEFAULT_MEMFUNCTIONS +void *sq_vm_malloc(SQUnsignedInteger size){ return malloc(size); } + +void *sq_vm_realloc(void *p, SQUnsignedInteger SQ_UNUSED_ARG(oldsize), SQUnsignedInteger size){ return realloc(p, size); } + +void sq_vm_free(void *p, SQUnsignedInteger SQ_UNUSED_ARG(size)){ free(p); } +#endif diff --git a/src/vscript/squirrel/squirrel/sqobject.cpp b/src/vscript/squirrel/squirrel/sqobject.cpp new file mode 100644 index 00000000..7d7f297f --- /dev/null +++ b/src/vscript/squirrel/squirrel/sqobject.cpp @@ -0,0 +1,677 @@ +/* + see copyright notice in squirrel.h +*/ +#include "sqpcheader.h" +#include "sqvm.h" +#include "sqstring.h" +#include "sqarray.h" +#include "sqtable.h" +#include "squserdata.h" +#include "sqfuncproto.h" +#include "sqclass.h" +#include "sqclosure.h" + + +const SQChar *IdType2Name(SQObjectType type) +{ + switch(_RAW_TYPE(type)) + { + case _RT_NULL:return _SC("null"); + case _RT_INTEGER:return _SC("integer"); + case _RT_FLOAT:return _SC("float"); + case _RT_BOOL:return _SC("bool"); + case _RT_STRING:return _SC("string"); + case _RT_TABLE:return _SC("table"); + case _RT_ARRAY:return _SC("array"); + case _RT_GENERATOR:return _SC("generator"); + case _RT_CLOSURE: + case _RT_NATIVECLOSURE: + return _SC("function"); + case _RT_USERDATA: + case _RT_USERPOINTER: + return _SC("userdata"); + case _RT_THREAD: return _SC("thread"); + case _RT_FUNCPROTO: return _SC("function"); + case _RT_CLASS: return _SC("class"); + case _RT_INSTANCE: return _SC("instance"); + case _RT_WEAKREF: return _SC("weakref"); + case _RT_OUTER: return _SC("outer"); + default: + return NULL; + } +} + +const SQChar *GetTypeName(const SQObjectPtr &obj1) +{ + return IdType2Name(sq_type(obj1)); +} + +SQString *SQString::Create(SQSharedState *ss,const SQChar *s,SQInteger len) +{ + SQString *str=ADD_STRING(ss,s,len); + return str; +} + +void SQString::Release() +{ + REMOVE_STRING(_sharedstate,this); +} + +SQInteger SQString::Next(const SQObjectPtr &refpos, SQObjectPtr &outkey, SQObjectPtr &outval) +{ + SQInteger idx = (SQInteger)TranslateIndex(refpos); + while(idx < _len){ + outkey = (SQInteger)idx; + outval = (SQInteger)((SQUnsignedInteger)_val[idx]); + //return idx for the next iteration + return ++idx; + } + //nothing to iterate anymore + return -1; +} + +SQUnsignedInteger TranslateIndex(const SQObjectPtr &idx) +{ + switch(sq_type(idx)){ + case OT_NULL: + return 0; + case OT_INTEGER: + return (SQUnsignedInteger)_integer(idx); + default: assert(0); break; + } + return 0; +} + +SQWeakRef *SQRefCounted::GetWeakRef(SQObjectType type) +{ + if(!_weakref) { + sq_new(_weakref,SQWeakRef); +#if defined(SQUSEDOUBLE) && !defined(_SQ64) + _weakref->_obj._unVal.raw = 0; //clean the whole union on 32 bits with double +#endif + _weakref->_obj._type = type; + _weakref->_obj._unVal.pRefCounted = this; + } + return _weakref; +} + +SQRefCounted::~SQRefCounted() +{ + if(_weakref) { + _weakref->_obj._type = OT_NULL; + _weakref->_obj._unVal.pRefCounted = NULL; + } +} + +void SQWeakRef::Release() { + if(ISREFCOUNTED(_obj._type)) { + _obj._unVal.pRefCounted->_weakref = NULL; + } + sq_delete(this,SQWeakRef); +} + +bool SQDelegable::GetMetaMethod(SQVM *v,SQMetaMethod mm,SQObjectPtr &res) { + if(_delegate) { + return _delegate->Get((*_ss(v)->_metamethods)[mm],res); + } + return false; +} + +bool SQDelegable::SetDelegate(SQTable *mt) +{ + SQTable *temp = mt; + if(temp == this) return false; + while (temp) { + if (temp->_delegate == this) return false; //cycle detected + temp = temp->_delegate; + } + if (mt) __ObjAddRef(mt); + __ObjRelease(_delegate); + _delegate = mt; + return true; +} + +bool SQGenerator::Yield(SQVM *v,SQInteger target) +{ + if(_state==eSuspended) { v->Raise_Error(_SC("internal vm error, yielding dead generator")); return false;} + if(_state==eDead) { v->Raise_Error(_SC("internal vm error, yielding a dead generator")); return false; } + SQInteger size = v->_top-v->_stackbase; + + _stack.resize(size); + SQObject _this = v->_stack[v->_stackbase]; + _stack._vals[0] = ISREFCOUNTED(sq_type(_this)) ? SQObjectPtr(_refcounted(_this)->GetWeakRef(sq_type(_this))) : _this; + for(SQInteger n =1; n_stack[v->_stackbase+n]; + } + for(SQInteger j =0; j < size; j++) + { + v->_stack[v->_stackbase+j].Null(); + } + + _ci = *v->ci; + _ci._generator=NULL; + for(SQInteger i=0;i<_ci._etraps;i++) { + _etraps.push_back(v->_etraps.top()); + v->_etraps.pop_back(); + // store relative stack base and size in case of resume to other _top + SQExceptionTrap &et = _etraps.back(); + et._stackbase -= v->_stackbase; + et._stacksize -= v->_stackbase; + } + _state=eSuspended; + return true; +} + +bool SQGenerator::Resume(SQVM *v,SQObjectPtr &dest) +{ + if(_state==eDead){ v->Raise_Error(_SC("resuming dead generator")); return false; } + if(_state==eRunning){ v->Raise_Error(_SC("resuming active generator")); return false; } + SQInteger size = _stack.size(); + SQInteger target = &dest - &(v->_stack._vals[v->_stackbase]); + assert(target>=0 && target<=255); + SQInteger newbase = v->_top; + if(!v->EnterFrame(v->_top, v->_top + size, false)) + return false; + v->ci->_generator = this; + v->ci->_target = (SQInt32)target; + v->ci->_closure = _ci._closure; + v->ci->_ip = _ci._ip; + v->ci->_literals = _ci._literals; + v->ci->_ncalls = _ci._ncalls; + v->ci->_etraps = _ci._etraps; + v->ci->_root = _ci._root; + + + for(SQInteger i=0;i<_ci._etraps;i++) { + v->_etraps.push_back(_etraps.top()); + _etraps.pop_back(); + SQExceptionTrap &et = v->_etraps.back(); + // restore absolute stack base and size + et._stackbase += newbase; + et._stacksize += newbase; + } + SQObject _this = _stack._vals[0]; + v->_stack[v->_stackbase] = sq_type(_this) == OT_WEAKREF ? _weakref(_this)->_obj : _this; + + for(SQInteger n = 1; n_stack[v->_stackbase+n] = _stack._vals[n]; + _stack._vals[n].Null(); + } + + _state=eRunning; + if (v->_debughook) + v->CallDebugHook(_SC('c')); + + return true; +} + +void SQArray::Extend(const SQArray *a){ + SQInteger xlen; + if((xlen=a->Size())) + for(SQInteger i=0;i_values[i]); +} + +const SQChar* SQFunctionProto::GetLocal(SQVM *vm,SQUnsignedInteger stackbase,SQUnsignedInteger nseq,SQUnsignedInteger nop) +{ + SQUnsignedInteger nvars=_nlocalvarinfos; + const SQChar *res=NULL; + if(nvars>=nseq){ + for(SQUnsignedInteger i=0;i=nop) + { + if(nseq==0){ + vm->Push(vm->_stack[stackbase+_localvarinfos[i]._pos]); + res=_stringval(_localvarinfos[i]._name); + break; + } + nseq--; + } + } + } + return res; +} + + +SQInteger SQFunctionProto::GetLine(SQInstruction *curr) +{ + SQInteger op = (SQInteger)(curr-_instructions); + SQInteger line=_lineinfos[0]._line; + SQInteger low = 0; + SQInteger high = _nlineinfos - 1; + SQInteger mid = 0; + while(low <= high) + { + mid = low + ((high - low) >> 1); + SQInteger curop = _lineinfos[mid]._op; + if(curop > op) + { + high = mid - 1; + } + else if(curop < op) { + if(mid < (_nlineinfos - 1) + && _lineinfos[mid + 1]._op >= op) { + break; + } + low = mid + 1; + } + else { //equal + break; + } + } + + while(mid > 0 && _lineinfos[mid]._op >= op) mid--; + + line = _lineinfos[mid]._line; + + return line; +} + +SQClosure::~SQClosure() +{ + __ObjRelease(_root); + __ObjRelease(_env); + __ObjRelease(_base); + REMOVE_FROM_CHAIN(&_ss(this)->_gc_chain,this); +} + +#define _CHECK_IO(exp) { if(!exp)return false; } +bool SafeWrite(HSQUIRRELVM v,SQWRITEFUNC write,SQUserPointer up,SQUserPointer dest,SQInteger size) +{ + if(write(up,dest,size) != size) { + v->Raise_Error(_SC("io error (write function failure)")); + return false; + } + return true; +} + +bool SafeRead(HSQUIRRELVM v,SQREADFUNC read,SQUserPointer up,SQUserPointer dest,SQInteger size) +{ + if(size && read(up,dest,size) != size) { + v->Raise_Error(_SC("io error, read function failure, the origin stream could be corrupted/trucated")); + return false; + } + return true; +} + +bool WriteTag(HSQUIRRELVM v,SQWRITEFUNC write,SQUserPointer up,SQUnsignedInteger32 tag) +{ + return SafeWrite(v,write,up,&tag,sizeof(tag)); +} + +bool CheckTag(HSQUIRRELVM v,SQREADFUNC read,SQUserPointer up,SQUnsignedInteger32 tag) +{ + SQUnsignedInteger32 t; + _CHECK_IO(SafeRead(v,read,up,&t,sizeof(t))); + if(t != tag){ + v->Raise_Error(_SC("invalid or corrupted closure stream")); + return false; + } + return true; +} + +bool WriteObject(HSQUIRRELVM v,SQUserPointer up,SQWRITEFUNC write,SQObjectPtr &o) +{ + SQUnsignedInteger32 _type = (SQUnsignedInteger32)sq_type(o); + _CHECK_IO(SafeWrite(v,write,up,&_type,sizeof(_type))); + switch(sq_type(o)){ + case OT_STRING: + _CHECK_IO(SafeWrite(v,write,up,&_string(o)->_len,sizeof(SQInteger))); + _CHECK_IO(SafeWrite(v,write,up,_stringval(o),sq_rsl(_string(o)->_len))); + break; + case OT_BOOL: + case OT_INTEGER: + _CHECK_IO(SafeWrite(v,write,up,&_integer(o),sizeof(SQInteger)));break; + case OT_FLOAT: + _CHECK_IO(SafeWrite(v,write,up,&_float(o),sizeof(SQFloat)));break; + case OT_NULL: + break; + default: + v->Raise_Error(_SC("cannot serialize a %s"),GetTypeName(o)); + return false; + } + return true; +} + +bool ReadObject(HSQUIRRELVM v,SQUserPointer up,SQREADFUNC read,SQObjectPtr &o) +{ + SQUnsignedInteger32 _type; + _CHECK_IO(SafeRead(v,read,up,&_type,sizeof(_type))); + SQObjectType t = (SQObjectType)_type; + switch(t){ + case OT_STRING:{ + SQInteger len; + _CHECK_IO(SafeRead(v,read,up,&len,sizeof(SQInteger))); + _CHECK_IO(SafeRead(v,read,up,_ss(v)->GetScratchPad(sq_rsl(len)),sq_rsl(len))); + o=SQString::Create(_ss(v),_ss(v)->GetScratchPad(-1),len); + } + break; + case OT_INTEGER:{ + SQInteger i; + _CHECK_IO(SafeRead(v,read,up,&i,sizeof(SQInteger))); o = i; break; + } + case OT_BOOL:{ + SQInteger i; + _CHECK_IO(SafeRead(v,read,up,&i,sizeof(SQInteger))); o._type = OT_BOOL; o._unVal.nInteger = i; break; + } + case OT_FLOAT:{ + SQFloat f; + _CHECK_IO(SafeRead(v,read,up,&f,sizeof(SQFloat))); o = f; break; + } + case OT_NULL: + o.Null(); + break; + default: + v->Raise_Error(_SC("cannot serialize a %s"),IdType2Name(t)); + return false; + } + return true; +} + +bool SQClosure::Save(SQVM *v,SQUserPointer up,SQWRITEFUNC write) +{ + _CHECK_IO(WriteTag(v,write,up,SQ_CLOSURESTREAM_HEAD)); + _CHECK_IO(WriteTag(v,write,up,sizeof(SQChar))); + _CHECK_IO(WriteTag(v,write,up,sizeof(SQInteger))); + _CHECK_IO(WriteTag(v,write,up,sizeof(SQFloat))); + _CHECK_IO(_function->Save(v,up,write)); + _CHECK_IO(WriteTag(v,write,up,SQ_CLOSURESTREAM_TAIL)); + return true; +} + +bool SQClosure::Load(SQVM *v,SQUserPointer up,SQREADFUNC read,SQObjectPtr &ret) +{ + _CHECK_IO(CheckTag(v,read,up,SQ_CLOSURESTREAM_HEAD)); + _CHECK_IO(CheckTag(v,read,up,sizeof(SQChar))); + _CHECK_IO(CheckTag(v,read,up,sizeof(SQInteger))); + _CHECK_IO(CheckTag(v,read,up,sizeof(SQFloat))); + SQObjectPtr func; + _CHECK_IO(SQFunctionProto::Load(v,up,read,func)); + _CHECK_IO(CheckTag(v,read,up,SQ_CLOSURESTREAM_TAIL)); + ret = SQClosure::Create(_ss(v),_funcproto(func),_table(v->_roottable)->GetWeakRef(OT_TABLE)); + //FIXME: load an root for this closure + return true; +} + +SQFunctionProto::SQFunctionProto(SQSharedState *ss) +{ + _stacksize=0; + _bgenerator=false; + INIT_CHAIN();ADD_TO_CHAIN(&_ss(this)->_gc_chain,this); +} + +SQFunctionProto::~SQFunctionProto() +{ + REMOVE_FROM_CHAIN(&_ss(this)->_gc_chain,this); +} + +bool SQFunctionProto::Save(SQVM *v,SQUserPointer up,SQWRITEFUNC write) +{ + SQInteger i,nliterals = _nliterals,nparameters = _nparameters; + SQInteger noutervalues = _noutervalues,nlocalvarinfos = _nlocalvarinfos; + SQInteger nlineinfos=_nlineinfos,ninstructions = _ninstructions,nfunctions=_nfunctions; + SQInteger ndefaultparams = _ndefaultparams; + _CHECK_IO(WriteTag(v,write,up,SQ_CLOSURESTREAM_PART)); + _CHECK_IO(WriteObject(v,up,write,_sourcename)); + _CHECK_IO(WriteObject(v,up,write,_name)); + _CHECK_IO(WriteTag(v,write,up,SQ_CLOSURESTREAM_PART)); + _CHECK_IO(SafeWrite(v,write,up,&nliterals,sizeof(nliterals))); + _CHECK_IO(SafeWrite(v,write,up,&nparameters,sizeof(nparameters))); + _CHECK_IO(SafeWrite(v,write,up,&noutervalues,sizeof(noutervalues))); + _CHECK_IO(SafeWrite(v,write,up,&nlocalvarinfos,sizeof(nlocalvarinfos))); + _CHECK_IO(SafeWrite(v,write,up,&nlineinfos,sizeof(nlineinfos))); + _CHECK_IO(SafeWrite(v,write,up,&ndefaultparams,sizeof(ndefaultparams))); + _CHECK_IO(SafeWrite(v,write,up,&ninstructions,sizeof(ninstructions))); + _CHECK_IO(SafeWrite(v,write,up,&nfunctions,sizeof(nfunctions))); + _CHECK_IO(WriteTag(v,write,up,SQ_CLOSURESTREAM_PART)); + for(i=0;iSave(v,up,write)); + } + _CHECK_IO(SafeWrite(v,write,up,&_stacksize,sizeof(_stacksize))); + _CHECK_IO(SafeWrite(v,write,up,&_bgenerator,sizeof(_bgenerator))); + _CHECK_IO(SafeWrite(v,write,up,&_varparams,sizeof(_varparams))); + return true; +} + +bool SQFunctionProto::Load(SQVM *v,SQUserPointer up,SQREADFUNC read,SQObjectPtr &ret) +{ + SQInteger i, nliterals,nparameters; + SQInteger noutervalues ,nlocalvarinfos ; + SQInteger nlineinfos,ninstructions ,nfunctions,ndefaultparams ; + SQObjectPtr sourcename, name; + SQObjectPtr o; + _CHECK_IO(CheckTag(v,read,up,SQ_CLOSURESTREAM_PART)); + _CHECK_IO(ReadObject(v, up, read, sourcename)); + _CHECK_IO(ReadObject(v, up, read, name)); + + _CHECK_IO(CheckTag(v,read,up,SQ_CLOSURESTREAM_PART)); + _CHECK_IO(SafeRead(v,read,up, &nliterals, sizeof(nliterals))); + _CHECK_IO(SafeRead(v,read,up, &nparameters, sizeof(nparameters))); + _CHECK_IO(SafeRead(v,read,up, &noutervalues, sizeof(noutervalues))); + _CHECK_IO(SafeRead(v,read,up, &nlocalvarinfos, sizeof(nlocalvarinfos))); + _CHECK_IO(SafeRead(v,read,up, &nlineinfos, sizeof(nlineinfos))); + _CHECK_IO(SafeRead(v,read,up, &ndefaultparams, sizeof(ndefaultparams))); + _CHECK_IO(SafeRead(v,read,up, &ninstructions, sizeof(ninstructions))); + _CHECK_IO(SafeRead(v,read,up, &nfunctions, sizeof(nfunctions))); + + + SQFunctionProto *f = SQFunctionProto::Create(_opt_ss(v),ninstructions,nliterals,nparameters, + nfunctions,noutervalues,nlineinfos,nlocalvarinfos,ndefaultparams); + SQObjectPtr proto = f; //gets a ref in case of failure + f->_sourcename = sourcename; + f->_name = name; + + _CHECK_IO(CheckTag(v,read,up,SQ_CLOSURESTREAM_PART)); + + for(i = 0;i < nliterals; i++){ + _CHECK_IO(ReadObject(v, up, read, o)); + f->_literals[i] = o; + } + _CHECK_IO(CheckTag(v,read,up,SQ_CLOSURESTREAM_PART)); + + for(i = 0; i < nparameters; i++){ + _CHECK_IO(ReadObject(v, up, read, o)); + f->_parameters[i] = o; + } + _CHECK_IO(CheckTag(v,read,up,SQ_CLOSURESTREAM_PART)); + + for(i = 0; i < noutervalues; i++){ + SQUnsignedInteger type; + SQObjectPtr name; + _CHECK_IO(SafeRead(v,read,up, &type, sizeof(SQUnsignedInteger))); + _CHECK_IO(ReadObject(v, up, read, o)); + _CHECK_IO(ReadObject(v, up, read, name)); + f->_outervalues[i] = SQOuterVar(name,o, (SQOuterType)type); + } + _CHECK_IO(CheckTag(v,read,up,SQ_CLOSURESTREAM_PART)); + + for(i = 0; i < nlocalvarinfos; i++){ + SQLocalVarInfo lvi; + _CHECK_IO(ReadObject(v, up, read, lvi._name)); + _CHECK_IO(SafeRead(v,read,up, &lvi._pos, sizeof(SQUnsignedInteger))); + _CHECK_IO(SafeRead(v,read,up, &lvi._start_op, sizeof(SQUnsignedInteger))); + _CHECK_IO(SafeRead(v,read,up, &lvi._end_op, sizeof(SQUnsignedInteger))); + f->_localvarinfos[i] = lvi; + } + _CHECK_IO(CheckTag(v,read,up,SQ_CLOSURESTREAM_PART)); + _CHECK_IO(SafeRead(v,read,up, f->_lineinfos, sizeof(SQLineInfo)*nlineinfos)); + + _CHECK_IO(CheckTag(v,read,up,SQ_CLOSURESTREAM_PART)); + _CHECK_IO(SafeRead(v,read,up, f->_defaultparams, sizeof(SQInteger)*ndefaultparams)); + + _CHECK_IO(CheckTag(v,read,up,SQ_CLOSURESTREAM_PART)); + _CHECK_IO(SafeRead(v,read,up, f->_instructions, sizeof(SQInstruction)*ninstructions)); + + _CHECK_IO(CheckTag(v,read,up,SQ_CLOSURESTREAM_PART)); + for(i = 0; i < nfunctions; i++){ + _CHECK_IO(_funcproto(o)->Load(v, up, read, o)); + f->_functions[i] = o; + } + _CHECK_IO(SafeRead(v,read,up, &f->_stacksize, sizeof(f->_stacksize))); + _CHECK_IO(SafeRead(v,read,up, &f->_bgenerator, sizeof(f->_bgenerator))); + _CHECK_IO(SafeRead(v,read,up, &f->_varparams, sizeof(f->_varparams))); + + ret = f; + return true; +} + +#ifndef NO_GARBAGE_COLLECTOR + +#define START_MARK() if(!(_uiRef&MARK_FLAG)){ \ + _uiRef|=MARK_FLAG; + +#define END_MARK() RemoveFromChain(&_sharedstate->_gc_chain, this); \ + AddToChain(chain, this); } + +void SQVM::Mark(SQCollectable **chain) +{ + START_MARK() + SQSharedState::MarkObject(_lasterror,chain); + SQSharedState::MarkObject(_errorhandler,chain); + SQSharedState::MarkObject(_debughook_closure,chain); + SQSharedState::MarkObject(_roottable, chain); + SQSharedState::MarkObject(temp_reg, chain); + for(SQUnsignedInteger i = 0; i < _stack.size(); i++) SQSharedState::MarkObject(_stack[i], chain); + for(SQInteger k = 0; k < _callsstacksize; k++) SQSharedState::MarkObject(_callsstack[k]._closure, chain); + END_MARK() +} + +void SQArray::Mark(SQCollectable **chain) +{ + START_MARK() + SQInteger len = _values.size(); + for(SQInteger i = 0;i < len; i++) SQSharedState::MarkObject(_values[i], chain); + END_MARK() +} +void SQTable::Mark(SQCollectable **chain) +{ + START_MARK() + if(_delegate) _delegate->Mark(chain); + SQInteger len = _numofnodes; + for(SQInteger i = 0; i < len; i++){ + SQSharedState::MarkObject(_nodes[i].key, chain); + SQSharedState::MarkObject(_nodes[i].val, chain); + } + END_MARK() +} + +void SQClass::Mark(SQCollectable **chain) +{ + START_MARK() + _members->Mark(chain); + if(_base) _base->Mark(chain); + SQSharedState::MarkObject(_attributes, chain); + for(SQUnsignedInteger i =0; i< _defaultvalues.size(); i++) { + SQSharedState::MarkObject(_defaultvalues[i].val, chain); + SQSharedState::MarkObject(_defaultvalues[i].attrs, chain); + } + for(SQUnsignedInteger j =0; j< _methods.size(); j++) { + SQSharedState::MarkObject(_methods[j].val, chain); + SQSharedState::MarkObject(_methods[j].attrs, chain); + } + for(SQUnsignedInteger k =0; k< MT_LAST; k++) { + SQSharedState::MarkObject(_metamethods[k], chain); + } + END_MARK() +} + +void SQInstance::Mark(SQCollectable **chain) +{ + START_MARK() + _class->Mark(chain); + SQUnsignedInteger nvalues = _class->_defaultvalues.size(); + for(SQUnsignedInteger i =0; i< nvalues; i++) { + SQSharedState::MarkObject(_values[i], chain); + } + END_MARK() +} + +void SQGenerator::Mark(SQCollectable **chain) +{ + START_MARK() + for(SQUnsignedInteger i = 0; i < _stack.size(); i++) SQSharedState::MarkObject(_stack[i], chain); + SQSharedState::MarkObject(_closure, chain); + END_MARK() +} + +void SQFunctionProto::Mark(SQCollectable **chain) +{ + START_MARK() + for(SQInteger i = 0; i < _nliterals; i++) SQSharedState::MarkObject(_literals[i], chain); + for(SQInteger k = 0; k < _nfunctions; k++) SQSharedState::MarkObject(_functions[k], chain); + END_MARK() +} + +void SQClosure::Mark(SQCollectable **chain) +{ + START_MARK() + if(_base) _base->Mark(chain); + SQFunctionProto *fp = _function; + fp->Mark(chain); + for(SQInteger i = 0; i < fp->_noutervalues; i++) SQSharedState::MarkObject(_outervalues[i], chain); + for(SQInteger k = 0; k < fp->_ndefaultparams; k++) SQSharedState::MarkObject(_defaultparams[k], chain); + END_MARK() +} + +void SQNativeClosure::Mark(SQCollectable **chain) +{ + START_MARK() + for(SQUnsignedInteger i = 0; i < _noutervalues; i++) SQSharedState::MarkObject(_outervalues[i], chain); + END_MARK() +} + +void SQOuter::Mark(SQCollectable **chain) +{ + START_MARK() + /* If the valptr points to a closed value, that value is alive */ + if(_valptr == &_value) { + SQSharedState::MarkObject(_value, chain); + } + END_MARK() +} + +void SQUserData::Mark(SQCollectable **chain){ + START_MARK() + if(_delegate) _delegate->Mark(chain); + END_MARK() +} + +void SQCollectable::UnMark() { _uiRef&=~MARK_FLAG; } + +#endif + diff --git a/src/vscript/squirrel/squirrel/sqobject.h b/src/vscript/squirrel/squirrel/sqobject.h new file mode 100644 index 00000000..a2022227 --- /dev/null +++ b/src/vscript/squirrel/squirrel/sqobject.h @@ -0,0 +1,353 @@ +/* see copyright notice in squirrel.h */ +#ifndef _SQOBJECT_H_ +#define _SQOBJECT_H_ + +#include "squtils.h" + +#ifdef _SQ64 +#define UINT_MINUS_ONE (0xFFFFFFFFFFFFFFFF) +#else +#define UINT_MINUS_ONE (0xFFFFFFFF) +#endif + +#define SQ_CLOSURESTREAM_HEAD (('S'<<24)|('Q'<<16)|('I'<<8)|('R')) +#define SQ_CLOSURESTREAM_PART (('P'<<24)|('A'<<16)|('R'<<8)|('T')) +#define SQ_CLOSURESTREAM_TAIL (('T'<<24)|('A'<<16)|('I'<<8)|('L')) + +struct SQSharedState; + +enum SQMetaMethod{ + MT_ADD=0, + MT_SUB=1, + MT_MUL=2, + MT_DIV=3, + MT_UNM=4, + MT_MODULO=5, + MT_SET=6, + MT_GET=7, + MT_TYPEOF=8, + MT_NEXTI=9, + MT_CMP=10, + MT_CALL=11, + MT_CLONED=12, + MT_NEWSLOT=13, + MT_DELSLOT=14, + MT_TOSTRING=15, + MT_NEWMEMBER=16, + MT_INHERITED=17, + MT_LAST = 18 +}; + +#define MM_ADD _SC("_add") +#define MM_SUB _SC("_sub") +#define MM_MUL _SC("_mul") +#define MM_DIV _SC("_div") +#define MM_UNM _SC("_unm") +#define MM_MODULO _SC("_modulo") +#define MM_SET _SC("_set") +#define MM_GET _SC("_get") +#define MM_TYPEOF _SC("_typeof") +#define MM_NEXTI _SC("_nexti") +#define MM_CMP _SC("_cmp") +#define MM_CALL _SC("_call") +#define MM_CLONED _SC("_cloned") +#define MM_NEWSLOT _SC("_newslot") +#define MM_DELSLOT _SC("_delslot") +#define MM_TOSTRING _SC("_tostring") +#define MM_NEWMEMBER _SC("_newmember") +#define MM_INHERITED _SC("_inherited") + + +#define _CONSTRUCT_VECTOR(type,size,ptr) { \ + for(SQInteger n = 0; n < ((SQInteger)size); n++) { \ + new (&ptr[n]) type(); \ + } \ +} + +#define _DESTRUCT_VECTOR(type,size,ptr) { \ + for(SQInteger nl = 0; nl < ((SQInteger)size); nl++) { \ + ptr[nl].~type(); \ + } \ +} + +#define _COPY_VECTOR(dest,src,size) { \ + for(SQInteger _n_ = 0; _n_ < ((SQInteger)size); _n_++) { \ + dest[_n_] = src[_n_]; \ + } \ +} + +#define _NULL_SQOBJECT_VECTOR(vec,size) { \ + for(SQInteger _n_ = 0; _n_ < ((SQInteger)size); _n_++) { \ + vec[_n_].Null(); \ + } \ +} + +#define MINPOWER2 4 + +struct SQRefCounted +{ + SQUnsignedInteger _uiRef; + struct SQWeakRef *_weakref; + SQRefCounted() { _uiRef = 0; _weakref = NULL; } + virtual ~SQRefCounted(); + SQWeakRef *GetWeakRef(SQObjectType type); + virtual void Release()=0; + +}; + +struct SQWeakRef : SQRefCounted +{ + void Release(); + SQObject _obj; +}; + +#define _realval(o) (sq_type((o)) != OT_WEAKREF?(SQObject)o:_weakref(o)->_obj) + +struct SQObjectPtr; + +#define __AddRef(type,unval) if(ISREFCOUNTED(type)) \ + { \ + unval.pRefCounted->_uiRef++; \ + } + +#define __Release(type,unval) if(ISREFCOUNTED(type) && ((--unval.pRefCounted->_uiRef)==0)) \ + { \ + unval.pRefCounted->Release(); \ + } + +#define __ObjRelease(obj) { \ + if((obj)) { \ + (obj)->_uiRef--; \ + if((obj)->_uiRef == 0) \ + (obj)->Release(); \ + (obj) = NULL; \ + } \ +} + +#define __ObjAddRef(obj) { \ + (obj)->_uiRef++; \ +} + +#define is_delegable(t) (sq_type(t)&SQOBJECT_DELEGABLE) +#define raw_type(obj) _RAW_TYPE((obj)._type) + +#define _integer(obj) ((obj)._unVal.nInteger) +#define _float(obj) ((obj)._unVal.fFloat) +#define _string(obj) ((obj)._unVal.pString) +#define _table(obj) ((obj)._unVal.pTable) +#define _array(obj) ((obj)._unVal.pArray) +#define _closure(obj) ((obj)._unVal.pClosure) +#define _generator(obj) ((obj)._unVal.pGenerator) +#define _nativeclosure(obj) ((obj)._unVal.pNativeClosure) +#define _userdata(obj) ((obj)._unVal.pUserData) +#define _userpointer(obj) ((obj)._unVal.pUserPointer) +#define _thread(obj) ((obj)._unVal.pThread) +#define _funcproto(obj) ((obj)._unVal.pFunctionProto) +#define _class(obj) ((obj)._unVal.pClass) +#define _instance(obj) ((obj)._unVal.pInstance) +#define _delegable(obj) ((SQDelegable *)(obj)._unVal.pDelegable) +#define _weakref(obj) ((obj)._unVal.pWeakRef) +#define _outer(obj) ((obj)._unVal.pOuter) +#define _refcounted(obj) ((obj)._unVal.pRefCounted) +#define _rawval(obj) ((obj)._unVal.raw) + +#define _stringval(obj) (obj)._unVal.pString->_val +#define _userdataval(obj) ((SQUserPointer)sq_aligning((obj)._unVal.pUserData + 1)) + +#define tofloat(num) ((sq_type(num)==OT_INTEGER)?(SQFloat)_integer(num):_float(num)) +#define tointeger(num) ((sq_type(num)==OT_FLOAT)?(SQInteger)_float(num):_integer(num)) +///////////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////////////// +#if defined(SQUSEDOUBLE) && !defined(_SQ64) || !defined(SQUSEDOUBLE) && defined(_SQ64) +#define SQ_REFOBJECT_INIT() SQ_OBJECT_RAWINIT() +#else +#define SQ_REFOBJECT_INIT() +#endif + +#define _REF_TYPE_DECL(type,_class,sym) \ + SQObjectPtr(_class * x) \ + { \ + SQ_OBJECT_RAWINIT() \ + _type=type; \ + _unVal.sym = x; \ + assert(_unVal.pTable); \ + _unVal.pRefCounted->_uiRef++; \ + } \ + inline SQObjectPtr& operator=(_class *x) \ + { \ + SQObjectType tOldType; \ + SQObjectValue unOldVal; \ + tOldType=_type; \ + unOldVal=_unVal; \ + _type = type; \ + SQ_REFOBJECT_INIT() \ + _unVal.sym = x; \ + _unVal.pRefCounted->_uiRef++; \ + __Release(tOldType,unOldVal); \ + return *this; \ + } + +#define _SCALAR_TYPE_DECL(type,_class,sym) \ + SQObjectPtr(_class x) \ + { \ + SQ_OBJECT_RAWINIT() \ + _type=type; \ + _unVal.sym = x; \ + } \ + inline SQObjectPtr& operator=(_class x) \ + { \ + __Release(_type,_unVal); \ + _type = type; \ + SQ_OBJECT_RAWINIT() \ + _unVal.sym = x; \ + return *this; \ + } +struct SQObjectPtr : public SQObject +{ + SQObjectPtr() + { + SQ_OBJECT_RAWINIT() + _type=OT_NULL; + _unVal.pUserPointer=NULL; + } + SQObjectPtr(const SQObjectPtr &o) + { + _type = o._type; + _unVal = o._unVal; + __AddRef(_type,_unVal); + } + SQObjectPtr(const SQObject &o) + { + _type = o._type; + _unVal = o._unVal; + __AddRef(_type,_unVal); + } + _REF_TYPE_DECL(OT_TABLE,SQTable,pTable) + _REF_TYPE_DECL(OT_CLASS,SQClass,pClass) + _REF_TYPE_DECL(OT_INSTANCE,SQInstance,pInstance) + _REF_TYPE_DECL(OT_ARRAY,SQArray,pArray) + _REF_TYPE_DECL(OT_CLOSURE,SQClosure,pClosure) + _REF_TYPE_DECL(OT_NATIVECLOSURE,SQNativeClosure,pNativeClosure) + _REF_TYPE_DECL(OT_OUTER,SQOuter,pOuter) + _REF_TYPE_DECL(OT_GENERATOR,SQGenerator,pGenerator) + _REF_TYPE_DECL(OT_STRING,SQString,pString) + _REF_TYPE_DECL(OT_USERDATA,SQUserData,pUserData) + _REF_TYPE_DECL(OT_WEAKREF,SQWeakRef,pWeakRef) + _REF_TYPE_DECL(OT_THREAD,SQVM,pThread) + _REF_TYPE_DECL(OT_FUNCPROTO,SQFunctionProto,pFunctionProto) + + _SCALAR_TYPE_DECL(OT_INTEGER,SQInteger,nInteger) + _SCALAR_TYPE_DECL(OT_FLOAT,SQFloat,fFloat) + _SCALAR_TYPE_DECL(OT_USERPOINTER,SQUserPointer,pUserPointer) + + SQObjectPtr(bool bBool) + { + SQ_OBJECT_RAWINIT() + _type = OT_BOOL; + _unVal.nInteger = bBool?1:0; + } + inline SQObjectPtr& operator=(bool b) + { + __Release(_type,_unVal); + SQ_OBJECT_RAWINIT() + _type = OT_BOOL; + _unVal.nInteger = b?1:0; + return *this; + } + + ~SQObjectPtr() + { + __Release(_type,_unVal); + } + + inline SQObjectPtr& operator=(const SQObjectPtr& obj) + { + SQObjectType tOldType; + SQObjectValue unOldVal; + tOldType=_type; + unOldVal=_unVal; + _unVal = obj._unVal; + _type = obj._type; + __AddRef(_type,_unVal); + __Release(tOldType,unOldVal); + return *this; + } + inline SQObjectPtr& operator=(const SQObject& obj) + { + SQObjectType tOldType; + SQObjectValue unOldVal; + tOldType=_type; + unOldVal=_unVal; + _unVal = obj._unVal; + _type = obj._type; + __AddRef(_type,_unVal); + __Release(tOldType,unOldVal); + return *this; + } + inline void Null() + { + SQObjectType tOldType = _type; + SQObjectValue unOldVal = _unVal; + _type = OT_NULL; + _unVal.raw = (SQRawObjectVal)NULL; + __Release(tOldType ,unOldVal); + } + private: + SQObjectPtr(const SQChar *){} //safety +}; + + +inline void _Swap(SQObject &a,SQObject &b) +{ + SQObjectType tOldType = a._type; + SQObjectValue unOldVal = a._unVal; + a._type = b._type; + a._unVal = b._unVal; + b._type = tOldType; + b._unVal = unOldVal; +} + +///////////////////////////////////////////////////////////////////////////////////// +#ifndef NO_GARBAGE_COLLECTOR +#define MARK_FLAG 0x80000000 +struct SQCollectable : public SQRefCounted { + SQCollectable *_next; + SQCollectable *_prev; + SQSharedState *_sharedstate; + virtual SQObjectType GetType()=0; + virtual void Release()=0; + virtual void Mark(SQCollectable **chain)=0; + void UnMark(); + virtual void Finalize()=0; + static void AddToChain(SQCollectable **chain,SQCollectable *c); + static void RemoveFromChain(SQCollectable **chain,SQCollectable *c); +}; + + +#define ADD_TO_CHAIN(chain,obj) AddToChain(chain,obj) +#define REMOVE_FROM_CHAIN(chain,obj) {if(!(_uiRef&MARK_FLAG))RemoveFromChain(chain,obj);} +#define CHAINABLE_OBJ SQCollectable +#define INIT_CHAIN() {_next=NULL;_prev=NULL;_sharedstate=ss;} +#else + +#define ADD_TO_CHAIN(chain,obj) ((void)0) +#define REMOVE_FROM_CHAIN(chain,obj) ((void)0) +#define CHAINABLE_OBJ SQRefCounted +#define INIT_CHAIN() ((void)0) +#endif + +struct SQDelegable : public CHAINABLE_OBJ { + bool SetDelegate(SQTable *m); + virtual bool GetMetaMethod(SQVM *v,SQMetaMethod mm,SQObjectPtr &res); + SQTable *_delegate; +}; + +SQUnsignedInteger TranslateIndex(const SQObjectPtr &idx); +typedef sqvector SQObjectPtrVec; +typedef sqvector SQIntVec; +const SQChar *GetTypeName(const SQObjectPtr &obj1); +const SQChar *IdType2Name(SQObjectType type); + + + +#endif //_SQOBJECT_H_ diff --git a/src/vscript/squirrel/squirrel/sqopcodes.h b/src/vscript/squirrel/squirrel/sqopcodes.h new file mode 100644 index 00000000..15d80e4b --- /dev/null +++ b/src/vscript/squirrel/squirrel/sqopcodes.h @@ -0,0 +1,132 @@ +/* see copyright notice in squirrel.h */ +#ifndef _SQOPCODES_H_ +#define _SQOPCODES_H_ + +#define MAX_FUNC_STACKSIZE 0xFF +#define MAX_LITERALS ((SQInteger)0x7FFFFFFF) + +enum BitWiseOP { + BW_AND = 0, + BW_OR = 2, + BW_XOR = 3, + BW_SHIFTL = 4, + BW_SHIFTR = 5, + BW_USHIFTR = 6 +}; + +enum CmpOP { + CMP_G = 0, + CMP_GE = 2, + CMP_L = 3, + CMP_LE = 4, + CMP_3W = 5 +}; + +enum NewObjectType { + NOT_TABLE = 0, + NOT_ARRAY = 1, + NOT_CLASS = 2 +}; + +enum AppendArrayType { + AAT_STACK = 0, + AAT_LITERAL = 1, + AAT_INT = 2, + AAT_FLOAT = 3, + AAT_BOOL = 4 +}; + +enum SQOpcode +{ + _OP_LINE= 0x00, + _OP_LOAD= 0x01, + _OP_LOADINT= 0x02, + _OP_LOADFLOAT= 0x03, + _OP_DLOAD= 0x04, + _OP_TAILCALL= 0x05, + _OP_CALL= 0x06, + _OP_PREPCALL= 0x07, + _OP_PREPCALLK= 0x08, + _OP_GETK= 0x09, + _OP_MOVE= 0x0A, + _OP_NEWSLOT= 0x0B, + _OP_DELETE= 0x0C, + _OP_SET= 0x0D, + _OP_GET= 0x0E, + _OP_EQ= 0x0F, + _OP_NE= 0x10, + _OP_ADD= 0x11, + _OP_SUB= 0x12, + _OP_MUL= 0x13, + _OP_DIV= 0x14, + _OP_MOD= 0x15, + _OP_BITW= 0x16, + _OP_RETURN= 0x17, + _OP_LOADNULLS= 0x18, + _OP_LOADROOT= 0x19, + _OP_LOADBOOL= 0x1A, + _OP_DMOVE= 0x1B, + _OP_JMP= 0x1C, + //_OP_JNZ= 0x1D, + _OP_JCMP= 0x1D, + _OP_JZ= 0x1E, + _OP_SETOUTER= 0x1F, + _OP_GETOUTER= 0x20, + _OP_NEWOBJ= 0x21, + _OP_APPENDARRAY= 0x22, + _OP_COMPARITH= 0x23, + _OP_INC= 0x24, + _OP_INCL= 0x25, + _OP_PINC= 0x26, + _OP_PINCL= 0x27, + _OP_CMP= 0x28, + _OP_EXISTS= 0x29, + _OP_INSTANCEOF= 0x2A, + _OP_AND= 0x2B, + _OP_OR= 0x2C, + _OP_NEG= 0x2D, + _OP_NOT= 0x2E, + _OP_BWNOT= 0x2F, + _OP_CLOSURE= 0x30, + _OP_YIELD= 0x31, + _OP_RESUME= 0x32, + _OP_FOREACH= 0x33, + _OP_POSTFOREACH= 0x34, + _OP_CLONE= 0x35, + _OP_TYPEOF= 0x36, + _OP_PUSHTRAP= 0x37, + _OP_POPTRAP= 0x38, + _OP_THROW= 0x39, + _OP_NEWSLOTA= 0x3A, + _OP_GETBASE= 0x3B, + _OP_CLOSE= 0x3C +}; + +struct SQInstructionDesc { + const SQChar *name; +}; + +struct SQInstruction +{ + SQInstruction(){}; + SQInstruction(SQOpcode _op,SQInteger a0=0,SQInteger a1=0,SQInteger a2=0,SQInteger a3=0) + { op = (unsigned char)_op; + _arg0 = (unsigned char)a0;_arg1 = (SQInt32)a1; + _arg2 = (unsigned char)a2;_arg3 = (unsigned char)a3; + } + + + SQInt32 _arg1; + unsigned char op; + unsigned char _arg0; + unsigned char _arg2; + unsigned char _arg3; +}; + +#include "squtils.h" +typedef sqvector SQInstructionVec; + +#define NEW_SLOT_ATTRIBUTES_FLAG 0x01 +#define NEW_SLOT_STATIC_FLAG 0x02 + +#endif // _SQOPCODES_H_ diff --git a/src/vscript/squirrel/squirrel/sqpcheader.h b/src/vscript/squirrel/squirrel/sqpcheader.h new file mode 100644 index 00000000..8df5ef4c --- /dev/null +++ b/src/vscript/squirrel/squirrel/sqpcheader.h @@ -0,0 +1,20 @@ +/* see copyright notice in squirrel.h */ +#ifndef _SQPCHEADER_H_ +#define _SQPCHEADER_H_ + +#if defined(_MSC_VER) && defined(_DEBUG) +#include +#endif + +#include +#include +#include +#include +#include +#include +//squirrel stuff +#include +#include "sqobject.h" +#include "sqstate.h" + +#endif //_SQPCHEADER_H_ diff --git a/src/vscript/squirrel/squirrel/sqstate.cpp b/src/vscript/squirrel/squirrel/sqstate.cpp new file mode 100644 index 00000000..c89bdc4a --- /dev/null +++ b/src/vscript/squirrel/squirrel/sqstate.cpp @@ -0,0 +1,647 @@ +/* + see copyright notice in squirrel.h +*/ +#include "sqpcheader.h" +#include "sqopcodes.h" +#include "sqvm.h" +#include "sqfuncproto.h" +#include "sqclosure.h" +#include "sqstring.h" +#include "sqtable.h" +#include "sqarray.h" +#include "squserdata.h" +#include "sqclass.h" + +SQSharedState::SQSharedState() +{ + _compilererrorhandler = NULL; + _printfunc = NULL; + _errorfunc = NULL; + _debuginfo = false; + _notifyallexceptions = false; + _foreignptr = NULL; + _releasehook = NULL; +} + +#define newsysstring(s) { \ + _systemstrings->push_back(SQString::Create(this,s)); \ + } + +#define newmetamethod(s) { \ + _metamethods->push_back(SQString::Create(this,s)); \ + _table(_metamethodsmap)->NewSlot(_metamethods->back(),(SQInteger)(_metamethods->size()-1)); \ + } + +bool CompileTypemask(SQIntVec &res,const SQChar *typemask) +{ + SQInteger i = 0; + SQInteger mask = 0; + while(typemask[i] != 0) { + switch(typemask[i]) { + case 'o': mask |= _RT_NULL; break; + case 'i': mask |= _RT_INTEGER; break; + case 'f': mask |= _RT_FLOAT; break; + case 'n': mask |= (_RT_FLOAT | _RT_INTEGER); break; + case 's': mask |= _RT_STRING; break; + case 't': mask |= _RT_TABLE; break; + case 'a': mask |= _RT_ARRAY; break; + case 'u': mask |= _RT_USERDATA; break; + case 'c': mask |= (_RT_CLOSURE | _RT_NATIVECLOSURE); break; + case 'b': mask |= _RT_BOOL; break; + case 'g': mask |= _RT_GENERATOR; break; + case 'p': mask |= _RT_USERPOINTER; break; + case 'v': mask |= _RT_THREAD; break; + case 'x': mask |= _RT_INSTANCE; break; + case 'y': mask |= _RT_CLASS; break; + case 'r': mask |= _RT_WEAKREF; break; + case '.': mask = -1; res.push_back(mask); i++; mask = 0; continue; + case ' ': i++; continue; //ignores spaces + default: + return false; + } + i++; + if(typemask[i] == '|') { + i++; + if(typemask[i] == 0) + return false; + continue; + } + res.push_back(mask); + mask = 0; + + } + return true; +} + +SQTable *CreateDefaultDelegate(SQSharedState *ss,const SQRegFunction *funcz) +{ + SQInteger i=0; + SQTable *t=SQTable::Create(ss,0); + while(funcz[i].name!=0){ + SQNativeClosure *nc = SQNativeClosure::Create(ss,funcz[i].f,0); + nc->_nparamscheck = funcz[i].nparamscheck; + nc->_name = SQString::Create(ss,funcz[i].name); + if(funcz[i].typemask && !CompileTypemask(nc->_typecheck,funcz[i].typemask)) + return NULL; + t->NewSlot(SQString::Create(ss,funcz[i].name),nc); + i++; + } + return t; +} + +void SQSharedState::Init() +{ + _scratchpad=NULL; + _scratchpadsize=0; +#ifndef NO_GARBAGE_COLLECTOR + _gc_chain=NULL; +#endif + _stringtable = (SQStringTable*)SQ_MALLOC(sizeof(SQStringTable)); + new (_stringtable) SQStringTable(this); + sq_new(_metamethods,SQObjectPtrVec); + sq_new(_systemstrings,SQObjectPtrVec); + sq_new(_types,SQObjectPtrVec); + _metamethodsmap = SQTable::Create(this,MT_LAST-1); + //adding type strings to avoid memory trashing + //types names + newsysstring(_SC("null")); + newsysstring(_SC("table")); + newsysstring(_SC("array")); + newsysstring(_SC("closure")); + newsysstring(_SC("string")); + newsysstring(_SC("userdata")); + newsysstring(_SC("integer")); + newsysstring(_SC("float")); + newsysstring(_SC("userpointer")); + newsysstring(_SC("function")); + newsysstring(_SC("generator")); + newsysstring(_SC("thread")); + newsysstring(_SC("class")); + newsysstring(_SC("instance")); + newsysstring(_SC("bool")); + //meta methods + newmetamethod(MM_ADD); + newmetamethod(MM_SUB); + newmetamethod(MM_MUL); + newmetamethod(MM_DIV); + newmetamethod(MM_UNM); + newmetamethod(MM_MODULO); + newmetamethod(MM_SET); + newmetamethod(MM_GET); + newmetamethod(MM_TYPEOF); + newmetamethod(MM_NEXTI); + newmetamethod(MM_CMP); + newmetamethod(MM_CALL); + newmetamethod(MM_CLONED); + newmetamethod(MM_NEWSLOT); + newmetamethod(MM_DELSLOT); + newmetamethod(MM_TOSTRING); + newmetamethod(MM_NEWMEMBER); + newmetamethod(MM_INHERITED); + + _constructoridx = SQString::Create(this,_SC("constructor")); + _registry = SQTable::Create(this,0); + _consts = SQTable::Create(this,0); + _table_default_delegate = CreateDefaultDelegate(this,_table_default_delegate_funcz); + _array_default_delegate = CreateDefaultDelegate(this,_array_default_delegate_funcz); + _string_default_delegate = CreateDefaultDelegate(this,_string_default_delegate_funcz); + _number_default_delegate = CreateDefaultDelegate(this,_number_default_delegate_funcz); + _closure_default_delegate = CreateDefaultDelegate(this,_closure_default_delegate_funcz); + _generator_default_delegate = CreateDefaultDelegate(this,_generator_default_delegate_funcz); + _thread_default_delegate = CreateDefaultDelegate(this,_thread_default_delegate_funcz); + _class_default_delegate = CreateDefaultDelegate(this,_class_default_delegate_funcz); + _instance_default_delegate = CreateDefaultDelegate(this,_instance_default_delegate_funcz); + _weakref_default_delegate = CreateDefaultDelegate(this,_weakref_default_delegate_funcz); +} + +SQSharedState::~SQSharedState() +{ + if(_releasehook) { _releasehook(_foreignptr,0); _releasehook = NULL; } + _constructoridx.Null(); + _table(_registry)->Finalize(); + _table(_consts)->Finalize(); + _table(_metamethodsmap)->Finalize(); + _registry.Null(); + _consts.Null(); + _metamethodsmap.Null(); + while(!_systemstrings->empty()) { + _systemstrings->back().Null(); + _systemstrings->pop_back(); + } + _thread(_root_vm)->Finalize(); + _root_vm.Null(); + _table_default_delegate.Null(); + _array_default_delegate.Null(); + _string_default_delegate.Null(); + _number_default_delegate.Null(); + _closure_default_delegate.Null(); + _generator_default_delegate.Null(); + _thread_default_delegate.Null(); + _class_default_delegate.Null(); + _instance_default_delegate.Null(); + _weakref_default_delegate.Null(); + _refs_table.Finalize(); +#ifndef NO_GARBAGE_COLLECTOR + SQCollectable *t = _gc_chain; + SQCollectable *nx = NULL; + if(t) { + t->_uiRef++; + while(t) { + t->Finalize(); + nx = t->_next; + if(nx) nx->_uiRef++; + if(--t->_uiRef == 0) + t->Release(); + t = nx; + } + } + assert(_gc_chain==NULL); //just to proove a theory + while(_gc_chain){ + _gc_chain->_uiRef++; + _gc_chain->Release(); + } +#endif + + sq_delete(_types,SQObjectPtrVec); + sq_delete(_systemstrings,SQObjectPtrVec); + sq_delete(_metamethods,SQObjectPtrVec); + sq_delete(_stringtable,SQStringTable); + if(_scratchpad)SQ_FREE(_scratchpad,_scratchpadsize); +} + + +SQInteger SQSharedState::GetMetaMethodIdxByName(const SQObjectPtr &name) +{ + if(sq_type(name) != OT_STRING) + return -1; + SQObjectPtr ret; + if(_table(_metamethodsmap)->Get(name,ret)) { + return _integer(ret); + } + return -1; +} + +#ifndef NO_GARBAGE_COLLECTOR + +void SQSharedState::MarkObject(SQObjectPtr &o,SQCollectable **chain) +{ + switch(sq_type(o)){ + case OT_TABLE:_table(o)->Mark(chain);break; + case OT_ARRAY:_array(o)->Mark(chain);break; + case OT_USERDATA:_userdata(o)->Mark(chain);break; + case OT_CLOSURE:_closure(o)->Mark(chain);break; + case OT_NATIVECLOSURE:_nativeclosure(o)->Mark(chain);break; + case OT_GENERATOR:_generator(o)->Mark(chain);break; + case OT_THREAD:_thread(o)->Mark(chain);break; + case OT_CLASS:_class(o)->Mark(chain);break; + case OT_INSTANCE:_instance(o)->Mark(chain);break; + case OT_OUTER:_outer(o)->Mark(chain);break; + case OT_FUNCPROTO:_funcproto(o)->Mark(chain);break; + default: break; //shutup compiler + } +} + +void SQSharedState::RunMark(SQVM* SQ_UNUSED_ARG(vm),SQCollectable **tchain) +{ + SQVM *vms = _thread(_root_vm); + + vms->Mark(tchain); + + _refs_table.Mark(tchain); + MarkObject(_registry,tchain); + MarkObject(_consts,tchain); + MarkObject(_metamethodsmap,tchain); + MarkObject(_table_default_delegate,tchain); + MarkObject(_array_default_delegate,tchain); + MarkObject(_string_default_delegate,tchain); + MarkObject(_number_default_delegate,tchain); + MarkObject(_generator_default_delegate,tchain); + MarkObject(_thread_default_delegate,tchain); + MarkObject(_closure_default_delegate,tchain); + MarkObject(_class_default_delegate,tchain); + MarkObject(_instance_default_delegate,tchain); + MarkObject(_weakref_default_delegate,tchain); + +} + +SQInteger SQSharedState::ResurrectUnreachable(SQVM *vm) +{ + SQInteger n=0; + SQCollectable *tchain=NULL; + + RunMark(vm,&tchain); + + SQCollectable *resurrected = _gc_chain; + SQCollectable *t = resurrected; + + _gc_chain = tchain; + + SQArray *ret = NULL; + if(resurrected) { + ret = SQArray::Create(this,0); + SQCollectable *rlast = NULL; + while(t) { + rlast = t; + SQObjectType type = t->GetType(); + if(type != OT_FUNCPROTO && type != OT_OUTER) { + SQObject sqo; + sqo._type = type; + sqo._unVal.pRefCounted = t; + ret->Append(sqo); + } + t = t->_next; + n++; + } + + assert(rlast->_next == NULL); + rlast->_next = _gc_chain; + if(_gc_chain) + { + _gc_chain->_prev = rlast; + } + _gc_chain = resurrected; + } + + t = _gc_chain; + while(t) { + t->UnMark(); + t = t->_next; + } + + if(ret) { + SQObjectPtr temp = ret; + vm->Push(temp); + } + else { + vm->PushNull(); + } + return n; +} + +SQInteger SQSharedState::CollectGarbage(SQVM *vm) +{ + SQInteger n = 0; + SQCollectable *tchain = NULL; + + RunMark(vm,&tchain); + + SQCollectable *t = _gc_chain; + SQCollectable *nx = NULL; + if(t) { + t->_uiRef++; + while(t) { + t->Finalize(); + nx = t->_next; + if(nx) nx->_uiRef++; + if(--t->_uiRef == 0) + t->Release(); + t = nx; + n++; + } + } + + t = tchain; + while(t) { + t->UnMark(); + t = t->_next; + } + _gc_chain = tchain; + + return n; +} +#endif + +#ifndef NO_GARBAGE_COLLECTOR +void SQCollectable::AddToChain(SQCollectable **chain,SQCollectable *c) +{ + c->_prev = NULL; + c->_next = *chain; + if(*chain) (*chain)->_prev = c; + *chain = c; +} + +void SQCollectable::RemoveFromChain(SQCollectable **chain,SQCollectable *c) +{ + if(c->_prev) c->_prev->_next = c->_next; + else *chain = c->_next; + if(c->_next) + c->_next->_prev = c->_prev; + c->_next = NULL; + c->_prev = NULL; +} +#endif + +SQChar* SQSharedState::GetScratchPad(SQInteger size) +{ + SQInteger newsize; + if(size>0) { + if(_scratchpadsize < size) { + newsize = size + (size>>1); + _scratchpad = (SQChar *)SQ_REALLOC(_scratchpad,_scratchpadsize,newsize); + _scratchpadsize = newsize; + + }else if(_scratchpadsize >= (size<<5)) { + newsize = _scratchpadsize >> 1; + _scratchpad = (SQChar *)SQ_REALLOC(_scratchpad,_scratchpadsize,newsize); + _scratchpadsize = newsize; + } + } + return _scratchpad; +} + +RefTable::RefTable() +{ + AllocNodes(4); +} + +void RefTable::Finalize() +{ + RefNode *nodes = _nodes; + for(SQUnsignedInteger n = 0; n < _numofslots; n++) { + nodes->obj.Null(); + nodes++; + } +} + +RefTable::~RefTable() +{ + SQ_FREE(_buckets,(_numofslots * sizeof(RefNode *)) + (_numofslots * sizeof(RefNode))); +} + +#ifndef NO_GARBAGE_COLLECTOR +void RefTable::Mark(SQCollectable **chain) +{ + RefNode *nodes = (RefNode *)_nodes; + for(SQUnsignedInteger n = 0; n < _numofslots; n++) { + if(sq_type(nodes->obj) != OT_NULL) { + SQSharedState::MarkObject(nodes->obj,chain); + } + nodes++; + } +} +#endif + +void RefTable::AddRef(SQObject &obj) +{ + SQHash mainpos; + RefNode *prev; + RefNode *ref = Get(obj,mainpos,&prev,true); + ref->refs++; +} + +SQUnsignedInteger RefTable::GetRefCount(SQObject &obj) +{ + SQHash mainpos; + RefNode *prev; + RefNode *ref = Get(obj,mainpos,&prev,true); + return ref->refs; +} + + +SQBool RefTable::Release(SQObject &obj) +{ + SQHash mainpos; + RefNode *prev; + RefNode *ref = Get(obj,mainpos,&prev,false); + if(ref) { + if(--ref->refs == 0) { + SQObjectPtr o = ref->obj; + if(prev) { + prev->next = ref->next; + } + else { + _buckets[mainpos] = ref->next; + } + ref->next = _freelist; + _freelist = ref; + _slotused--; + ref->obj.Null(); + //<>test for shrink? + return SQTrue; + } + } + else { + assert(0); + } + return SQFalse; +} + +void RefTable::Resize(SQUnsignedInteger size) +{ + RefNode **oldbucks = _buckets; + RefNode *t = _nodes; + SQUnsignedInteger oldnumofslots = _numofslots; + AllocNodes(size); + //rehash + SQUnsignedInteger nfound = 0; + for(SQUnsignedInteger n = 0; n < oldnumofslots; n++) { + if(sq_type(t->obj) != OT_NULL) { + //add back; + assert(t->refs != 0); + RefNode *nn = Add(::HashObj(t->obj)&(_numofslots-1),t->obj); + nn->refs = t->refs; + t->obj.Null(); + nfound++; + } + t++; + } + assert(nfound == oldnumofslots); + SQ_FREE(oldbucks,(oldnumofslots * sizeof(RefNode *)) + (oldnumofslots * sizeof(RefNode))); +} + +RefTable::RefNode *RefTable::Add(SQHash mainpos,SQObject &obj) +{ + RefNode *t = _buckets[mainpos]; + RefNode *newnode = _freelist; + newnode->obj = obj; + _buckets[mainpos] = newnode; + _freelist = _freelist->next; + newnode->next = t; + assert(newnode->refs == 0); + _slotused++; + return newnode; +} + +RefTable::RefNode *RefTable::Get(SQObject &obj,SQHash &mainpos,RefNode **prev,bool add) +{ + RefNode *ref; + mainpos = ::HashObj(obj)&(_numofslots-1); + *prev = NULL; + for (ref = _buckets[mainpos]; ref; ) { + if(_rawval(ref->obj) == _rawval(obj) && sq_type(ref->obj) == sq_type(obj)) + break; + *prev = ref; + ref = ref->next; + } + if(ref == NULL && add) { + if(_numofslots == _slotused) { + assert(_freelist == 0); + Resize(_numofslots*2); + mainpos = ::HashObj(obj)&(_numofslots-1); + } + ref = Add(mainpos,obj); + } + return ref; +} + +void RefTable::AllocNodes(SQUnsignedInteger size) +{ + RefNode **bucks; + RefNode *nodes; + bucks = (RefNode **)SQ_MALLOC((size * sizeof(RefNode *)) + (size * sizeof(RefNode))); + nodes = (RefNode *)&bucks[size]; + RefNode *temp = nodes; + SQUnsignedInteger n; + for(n = 0; n < size - 1; n++) { + bucks[n] = NULL; + temp->refs = 0; + new (&temp->obj) SQObjectPtr; + temp->next = temp+1; + temp++; + } + bucks[n] = NULL; + temp->refs = 0; + new (&temp->obj) SQObjectPtr; + temp->next = NULL; + _freelist = nodes; + _nodes = nodes; + _buckets = bucks; + _slotused = 0; + _numofslots = size; +} +////////////////////////////////////////////////////////////////////////// +//SQStringTable +/* +* The following code is based on Lua 4.0 (Copyright 1994-2002 Tecgraf, PUC-Rio.) +* http://www.lua.org/copyright.html#4 +* http://www.lua.org/source/4.0.1/src_lstring.c.html +*/ + +SQStringTable::SQStringTable(SQSharedState *ss) +{ + _sharedstate = ss; + AllocNodes(4); + _slotused = 0; +} + +SQStringTable::~SQStringTable() +{ + SQ_FREE(_strings,sizeof(SQString*)*_numofslots); + _strings = NULL; +} + +void SQStringTable::AllocNodes(SQInteger size) +{ + _numofslots = size; + _strings = (SQString**)SQ_MALLOC(sizeof(SQString*)*_numofslots); + memset(_strings,0,sizeof(SQString*)*_numofslots); +} + +SQString *SQStringTable::Add(const SQChar *news,SQInteger len) +{ + if(len<0) + len = (SQInteger)scstrlen(news); + SQHash newhash = ::_hashstr(news,len); + SQHash h = newhash&(_numofslots-1); + SQString *s; + for (s = _strings[h]; s; s = s->_next){ + if(s->_len == len && (!memcmp(news,s->_val,sq_rsl(len)))) + return s; //found + } + + SQString *t = (SQString *)SQ_MALLOC(sq_rsl(len)+sizeof(SQString)); + new (t) SQString; + t->_sharedstate = _sharedstate; + memcpy(t->_val,news,sq_rsl(len)); + t->_val[len] = _SC('\0'); + t->_len = len; + t->_hash = newhash; + t->_next = _strings[h]; + _strings[h] = t; + _slotused++; + if (_slotused > _numofslots) /* too crowded? */ + Resize(_numofslots*2); + return t; +} + +void SQStringTable::Resize(SQInteger size) +{ + SQInteger oldsize=_numofslots; + SQString **oldtable=_strings; + AllocNodes(size); + for (SQInteger i=0; i_next; + SQHash h = p->_hash&(_numofslots-1); + p->_next = _strings[h]; + _strings[h] = p; + p = next; + } + } + SQ_FREE(oldtable,oldsize*sizeof(SQString*)); +} + +void SQStringTable::Remove(SQString *bs) +{ + SQString *s; + SQString *prev=NULL; + SQHash h = bs->_hash&(_numofslots - 1); + + for (s = _strings[h]; s; ){ + if(s == bs){ + if(prev) + prev->_next = s->_next; + else + _strings[h] = s->_next; + _slotused--; + SQInteger slen = s->_len; + s->~SQString(); + SQ_FREE(s,sizeof(SQString) + sq_rsl(slen)); + return; + } + prev = s; + s = s->_next; + } + assert(0);//if this fail something is wrong +} diff --git a/src/vscript/squirrel/squirrel/sqstate.h b/src/vscript/squirrel/squirrel/sqstate.h new file mode 100644 index 00000000..2cdc8da4 --- /dev/null +++ b/src/vscript/squirrel/squirrel/sqstate.h @@ -0,0 +1,136 @@ +/* see copyright notice in squirrel.h */ +#ifndef _SQSTATE_H_ +#define _SQSTATE_H_ + +#include "squtils.h" +#include "sqobject.h" +struct SQString; +struct SQTable; +//max number of character for a printed number +#define NUMBER_MAX_CHAR 50 + +struct SQStringTable +{ + SQStringTable(SQSharedState*ss); + ~SQStringTable(); + SQString *Add(const SQChar *,SQInteger len); + void Remove(SQString *); +private: + void Resize(SQInteger size); + void AllocNodes(SQInteger size); + SQString **_strings; + SQUnsignedInteger _numofslots; + SQUnsignedInteger _slotused; + SQSharedState *_sharedstate; +}; + +struct RefTable { + struct RefNode { + SQObjectPtr obj; + SQUnsignedInteger refs; + struct RefNode *next; + }; + RefTable(); + ~RefTable(); + void AddRef(SQObject &obj); + SQBool Release(SQObject &obj); + SQUnsignedInteger GetRefCount(SQObject &obj); +#ifndef NO_GARBAGE_COLLECTOR + void Mark(SQCollectable **chain); +#endif + void Finalize(); +private: + RefNode *Get(SQObject &obj,SQHash &mainpos,RefNode **prev,bool add); + RefNode *Add(SQHash mainpos,SQObject &obj); + void Resize(SQUnsignedInteger size); + void AllocNodes(SQUnsignedInteger size); + SQUnsignedInteger _numofslots; + SQUnsignedInteger _slotused; + RefNode *_nodes; + RefNode *_freelist; + RefNode **_buckets; +}; + +#define ADD_STRING(ss,str,len) ss->_stringtable->Add(str,len) +#define REMOVE_STRING(ss,bstr) ss->_stringtable->Remove(bstr) + +struct SQObjectPtr; + +struct SQSharedState +{ + SQSharedState(); + ~SQSharedState(); + void Init(); +public: + SQChar* GetScratchPad(SQInteger size); + SQInteger GetMetaMethodIdxByName(const SQObjectPtr &name); +#ifndef NO_GARBAGE_COLLECTOR + SQInteger CollectGarbage(SQVM *vm); + void RunMark(SQVM *vm,SQCollectable **tchain); + SQInteger ResurrectUnreachable(SQVM *vm); + static void MarkObject(SQObjectPtr &o,SQCollectable **chain); +#endif + SQObjectPtrVec *_metamethods; + SQObjectPtr _metamethodsmap; + SQObjectPtrVec *_systemstrings; + SQObjectPtrVec *_types; + SQStringTable *_stringtable; + RefTable _refs_table; + SQObjectPtr _registry; + SQObjectPtr _consts; + SQObjectPtr _constructoridx; +#ifndef NO_GARBAGE_COLLECTOR + SQCollectable *_gc_chain; +#endif + SQObjectPtr _root_vm; + SQObjectPtr _table_default_delegate; + static const SQRegFunction _table_default_delegate_funcz[]; + SQObjectPtr _array_default_delegate; + static const SQRegFunction _array_default_delegate_funcz[]; + SQObjectPtr _string_default_delegate; + static const SQRegFunction _string_default_delegate_funcz[]; + SQObjectPtr _number_default_delegate; + static const SQRegFunction _number_default_delegate_funcz[]; + SQObjectPtr _generator_default_delegate; + static const SQRegFunction _generator_default_delegate_funcz[]; + SQObjectPtr _closure_default_delegate; + static const SQRegFunction _closure_default_delegate_funcz[]; + SQObjectPtr _thread_default_delegate; + static const SQRegFunction _thread_default_delegate_funcz[]; + SQObjectPtr _class_default_delegate; + static const SQRegFunction _class_default_delegate_funcz[]; + SQObjectPtr _instance_default_delegate; + static const SQRegFunction _instance_default_delegate_funcz[]; + SQObjectPtr _weakref_default_delegate; + static const SQRegFunction _weakref_default_delegate_funcz[]; + + SQCOMPILERERROR _compilererrorhandler; + SQPRINTFUNCTION _printfunc; + SQPRINTFUNCTION _errorfunc; + bool _debuginfo; + bool _notifyallexceptions; + SQUserPointer _foreignptr; + SQRELEASEHOOK _releasehook; +private: + SQChar *_scratchpad; + SQInteger _scratchpadsize; +}; + +#define _sp(s) (_sharedstate->GetScratchPad(s)) +#define _spval (_sharedstate->GetScratchPad(-1)) + +#define _table_ddel _table(_sharedstate->_table_default_delegate) +#define _array_ddel _table(_sharedstate->_array_default_delegate) +#define _string_ddel _table(_sharedstate->_string_default_delegate) +#define _number_ddel _table(_sharedstate->_number_default_delegate) +#define _generator_ddel _table(_sharedstate->_generator_default_delegate) +#define _closure_ddel _table(_sharedstate->_closure_default_delegate) +#define _thread_ddel _table(_sharedstate->_thread_default_delegate) +#define _class_ddel _table(_sharedstate->_class_default_delegate) +#define _instance_ddel _table(_sharedstate->_instance_default_delegate) +#define _weakref_ddel _table(_sharedstate->_weakref_default_delegate) + +bool CompileTypemask(SQIntVec &res,const SQChar *typemask); + + +#endif //_SQSTATE_H_ diff --git a/src/vscript/squirrel/squirrel/sqstring.h b/src/vscript/squirrel/squirrel/sqstring.h new file mode 100644 index 00000000..82f1cdf4 --- /dev/null +++ b/src/vscript/squirrel/squirrel/sqstring.h @@ -0,0 +1,31 @@ +/* see copyright notice in squirrel.h */ +#ifndef _SQSTRING_H_ +#define _SQSTRING_H_ + +inline SQHash _hashstr (const SQChar *s, size_t l) +{ + SQHash h = (SQHash)l; /* seed */ + size_t step = (l>>5)|1; /* if string is too long, don't hash all its chars */ + for (; l>=step; l-=step) + h = h ^ ((h<<5)+(h>>2)+(unsigned short)*(s++)); + return h; +} + +struct SQString : public SQRefCounted +{ + SQString(){} + ~SQString(){} +public: + static SQString *Create(SQSharedState *ss, const SQChar *, SQInteger len = -1 ); + SQInteger Next(const SQObjectPtr &refpos, SQObjectPtr &outkey, SQObjectPtr &outval); + void Release(); + SQSharedState *_sharedstate; + SQString *_next; //chain for the string table + SQInteger _len; + SQHash _hash; + SQChar _val[1]; +}; + + + +#endif //_SQSTRING_H_ diff --git a/src/vscript/squirrel/squirrel/sqtable.cpp b/src/vscript/squirrel/squirrel/sqtable.cpp new file mode 100644 index 00000000..3a89c459 --- /dev/null +++ b/src/vscript/squirrel/squirrel/sqtable.cpp @@ -0,0 +1,221 @@ +/* +see copyright notice in squirrel.h +*/ +#include "sqpcheader.h" +#include "sqvm.h" +#include "sqtable.h" +#include "sqfuncproto.h" +#include "sqclosure.h" + +SQTable::SQTable(SQSharedState *ss,SQInteger nInitialSize) +{ + SQInteger pow2size=MINPOWER2; + while(nInitialSize>pow2size)pow2size=pow2size<<1; + AllocNodes(pow2size); + _usednodes = 0; + _delegate = NULL; + INIT_CHAIN(); + ADD_TO_CHAIN(&_sharedstate->_gc_chain,this); +} + +void SQTable::Remove(const SQObjectPtr &key) +{ + + _HashNode *n = _Get(key, HashObj(key) & (_numofnodes - 1)); + if (n) { + n->val.Null(); + n->key.Null(); + _usednodes--; + Rehash(false); + } +} + +void SQTable::AllocNodes(SQInteger nSize) +{ + _HashNode *nodes=(_HashNode *)SQ_MALLOC(sizeof(_HashNode)*nSize); + for(SQInteger i=0;i= oldsize-oldsize/4) /* using more than 3/4? */ + AllocNodes(oldsize*2); + else if (nelems <= oldsize/4 && /* less than 1/4? */ + oldsize > MINPOWER2) + AllocNodes(oldsize/2); + else if(force) + AllocNodes(oldsize); + else + return; + _usednodes = 0; + for (SQInteger i=0; ikey) != OT_NULL) + NewSlot(old->key,old->val); + } + for(SQInteger k=0;k_nodes; + _HashNode *src = _nodes; + _HashNode *dst = nt->_nodes; + SQInteger n = 0; + for(n = 0; n < _numofnodes; n++) { + dst->key = src->key; + dst->val = src->val; + if(src->next) { + assert(src->next > basesrc); + dst->next = basedst + (src->next - basesrc); + assert(dst != dst->next); + } + dst++; + src++; + } + assert(_firstfree > basesrc); + assert(_firstfree != NULL); + nt->_firstfree = basedst + (_firstfree - basesrc); + nt->_usednodes = _usednodes; +#else + SQInteger ridx=0; + SQObjectPtr key,val; + while((ridx=Next(true,ridx,key,val))!=-1){ + nt->NewSlot(key,val); + } +#endif + nt->SetDelegate(_delegate); + return nt; +} + +bool SQTable::Get(const SQObjectPtr &key,SQObjectPtr &val) +{ + if(sq_type(key) == OT_NULL) + return false; + _HashNode *n = _Get(key, HashObj(key) & (_numofnodes - 1)); + if (n) { + val = _realval(n->val); + return true; + } + return false; +} +bool SQTable::NewSlot(const SQObjectPtr &key,const SQObjectPtr &val) +{ + assert(sq_type(key) != OT_NULL); + SQHash h = HashObj(key) & (_numofnodes - 1); + _HashNode *n = _Get(key, h); + if (n) { + n->val = val; + return false; + } + _HashNode *mp = &_nodes[h]; + n = mp; + + + //key not found I'll insert it + //main pos is not free + + if(sq_type(mp->key) != OT_NULL) { + n = _firstfree; /* get a free place */ + SQHash mph = HashObj(mp->key) & (_numofnodes - 1); + _HashNode *othern; /* main position of colliding node */ + + if (mp > n && (othern = &_nodes[mph]) != mp){ + /* yes; move colliding node into free position */ + while (othern->next != mp){ + assert(othern->next != NULL); + othern = othern->next; /* find previous */ + } + othern->next = n; /* redo the chain with `n' in place of `mp' */ + n->key = mp->key; + n->val = mp->val;/* copy colliding node into free pos. (mp->next also goes) */ + n->next = mp->next; + mp->key.Null(); + mp->val.Null(); + mp->next = NULL; /* now `mp' is free */ + } + else{ + /* new node will go into free position */ + n->next = mp->next; /* chain new position */ + mp->next = n; + mp = n; + } + } + mp->key = key; + + for (;;) { /* correct `firstfree' */ + if (sq_type(_firstfree->key) == OT_NULL && _firstfree->next == NULL) { + mp->val = val; + _usednodes++; + return true; /* OK; table still has a free place */ + } + else if (_firstfree == _nodes) break; /* cannot decrement from here */ + else (_firstfree)--; + } + Rehash(true); + return NewSlot(key, val); +} + +SQInteger SQTable::Next(bool getweakrefs,const SQObjectPtr &refpos, SQObjectPtr &outkey, SQObjectPtr &outval) +{ + SQInteger idx = (SQInteger)TranslateIndex(refpos); + while (idx < _numofnodes) { + if(sq_type(_nodes[idx].key) != OT_NULL) { + //first found + _HashNode &n = _nodes[idx]; + outkey = n.key; + outval = getweakrefs?(SQObject)n.val:_realval(n.val); + //return idx for the next iteration + return ++idx; + } + ++idx; + } + //nothing to iterate anymore + return -1; +} + + +bool SQTable::Set(const SQObjectPtr &key, const SQObjectPtr &val) +{ + _HashNode *n = _Get(key, HashObj(key) & (_numofnodes - 1)); + if (n) { + n->val = val; + return true; + } + return false; +} + +void SQTable::_ClearNodes() +{ + for(SQInteger i = 0;i < _numofnodes; i++) { _HashNode &n = _nodes[i]; n.key.Null(); n.val.Null(); } +} + +void SQTable::Finalize() +{ + _ClearNodes(); + SetDelegate(NULL); +} + +void SQTable::Clear() +{ + _ClearNodes(); + _usednodes = 0; + Rehash(true); +} diff --git a/src/vscript/squirrel/squirrel/sqtable.h b/src/vscript/squirrel/squirrel/sqtable.h new file mode 100644 index 00000000..8ca3ae7c --- /dev/null +++ b/src/vscript/squirrel/squirrel/sqtable.h @@ -0,0 +1,110 @@ +/* see copyright notice in squirrel.h */ +#ifndef _SQTABLE_H_ +#define _SQTABLE_H_ +/* +* The following code is based on Lua 4.0 (Copyright 1994-2002 Tecgraf, PUC-Rio.) +* http://www.lua.org/copyright.html#4 +* http://www.lua.org/source/4.0.1/src_ltable.c.html +*/ + +#include "sqstring.h" + + +#define hashptr(p) ((SQHash)(((SQInteger)p) >> 3)) + +inline SQHash HashObj(const SQObject &key) +{ + switch(sq_type(key)) { + case OT_STRING: return _string(key)->_hash; + case OT_FLOAT: return (SQHash)((SQInteger)_float(key)); + case OT_BOOL: case OT_INTEGER: return (SQHash)((SQInteger)_integer(key)); + default: return hashptr(key._unVal.pRefCounted); + } +} + +struct SQTable : public SQDelegable +{ +private: + struct _HashNode + { + _HashNode() { next = NULL; } + SQObjectPtr val; + SQObjectPtr key; + _HashNode *next; + }; + _HashNode *_firstfree; + _HashNode *_nodes; + SQInteger _numofnodes; + SQInteger _usednodes; + +/////////////////////////// + void AllocNodes(SQInteger nSize); + void Rehash(bool force); + SQTable(SQSharedState *ss, SQInteger nInitialSize); + void _ClearNodes(); +public: + static SQTable* Create(SQSharedState *ss,SQInteger nInitialSize) + { + SQTable *newtable = (SQTable*)SQ_MALLOC(sizeof(SQTable)); + new (newtable) SQTable(ss, nInitialSize); + newtable->_delegate = NULL; + return newtable; + } + void Finalize(); + SQTable *Clone(); + ~SQTable() + { + SetDelegate(NULL); + REMOVE_FROM_CHAIN(&_sharedstate->_gc_chain, this); + for (SQInteger i = 0; i < _numofnodes; i++) _nodes[i].~_HashNode(); + SQ_FREE(_nodes, _numofnodes * sizeof(_HashNode)); + } +#ifndef NO_GARBAGE_COLLECTOR + void Mark(SQCollectable **chain); + SQObjectType GetType() {return OT_TABLE;} +#endif + inline _HashNode *_Get(const SQObjectPtr &key,SQHash hash) + { + _HashNode *n = &_nodes[hash]; + do{ + if(_rawval(n->key) == _rawval(key) && sq_type(n->key) == sq_type(key)){ + return n; + } + }while((n = n->next)); + return NULL; + } + //for compiler use + inline bool GetStr(const SQChar* key,SQInteger keylen,SQObjectPtr &val) + { + SQHash hash = _hashstr(key,keylen); + _HashNode *n = &_nodes[hash & (_numofnodes - 1)]; + _HashNode *res = NULL; + do{ + if(sq_type(n->key) == OT_STRING && (scstrcmp(_stringval(n->key),key) == 0)){ + res = n; + break; + } + }while((n = n->next)); + if (res) { + val = _realval(res->val); + return true; + } + return false; + } + bool Get(const SQObjectPtr &key,SQObjectPtr &val); + void Remove(const SQObjectPtr &key); + bool Set(const SQObjectPtr &key, const SQObjectPtr &val); + //returns true if a new slot has been created false if it was already present + bool NewSlot(const SQObjectPtr &key,const SQObjectPtr &val); + SQInteger Next(bool getweakrefs,const SQObjectPtr &refpos, SQObjectPtr &outkey, SQObjectPtr &outval); + + SQInteger CountUsed(){ return _usednodes;} + void Clear(); + void Release() + { + sq_delete(this, SQTable); + } + +}; + +#endif //_SQTABLE_H_ diff --git a/src/vscript/squirrel/squirrel/squirrel.dsp b/src/vscript/squirrel/squirrel/squirrel.dsp new file mode 100644 index 00000000..66a84f7d --- /dev/null +++ b/src/vscript/squirrel/squirrel/squirrel.dsp @@ -0,0 +1,302 @@ +# Microsoft Developer Studio Project File - Name="squirrel" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Static Library" 0x0104 + +CFG=squirrel - Win32 Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "squirrel.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "squirrel.mak" CFG="squirrel - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "squirrel - Win32 Release" (based on "Win32 (x86) Static Library") +!MESSAGE "squirrel - Win32 Debug" (based on "Win32 (x86) Static Library") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_LocalPath ".." +CPP=cl.exe +RSC=rc.exe + +!IF "$(CFG)" == "squirrel - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release" +# PROP Intermediate_Dir "Release" +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_MBCS" /D "_LIB" /YX /FD /c +# ADD CPP /nologo /W3 /GX /O2 /I "..\include" /D "WIN32" /D "NDEBUG" /D "_MBCS" /D "_LIB" /D "GARBAGE_COLLECTOR" /YX /FD /c +# ADD BASE RSC /l 0x410 /d "NDEBUG" +# ADD RSC /l 0x410 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LIB32=link.exe -lib +# ADD BASE LIB32 /nologo +# ADD LIB32 /nologo /out:"..\lib\squirrel.lib" + +!ELSEIF "$(CFG)" == "squirrel - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug" +# PROP Intermediate_Dir "Debug" +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_MBCS" /D "_LIB" /YX /FD /GZ /c +# ADD CPP /nologo /W3 /Gm /GX /ZI /Od /I "..\include" /D "WIN32" /D "_DEBUG" /D "_MBCS" /D "_LIB" /YX /FD /GZ /c +# ADD BASE RSC /l 0x410 /d "_DEBUG" +# ADD RSC /l 0x410 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LIB32=link.exe -lib +# ADD BASE LIB32 /nologo +# ADD LIB32 /nologo /out:"..\lib\squirrel.lib" + +!ENDIF + +# Begin Target + +# Name "squirrel - Win32 Release" +# Name "squirrel - Win32 Debug" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" +# Begin Source File + +SOURCE=.\sqapi.cpp + +!IF "$(CFG)" == "squirrel - Win32 Release" + +!ELSEIF "$(CFG)" == "squirrel - Win32 Debug" + +# ADD CPP /YX"stdafx.h" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\sqbaselib.cpp + +!IF "$(CFG)" == "squirrel - Win32 Release" + +!ELSEIF "$(CFG)" == "squirrel - Win32 Debug" + +# ADD CPP /YX"stdafx.h" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\sqcompiler.cpp + +!IF "$(CFG)" == "squirrel - Win32 Release" + +!ELSEIF "$(CFG)" == "squirrel - Win32 Debug" + +# ADD CPP /YX"stdafx.h" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\sqdebug.cpp + +!IF "$(CFG)" == "squirrel - Win32 Release" + +!ELSEIF "$(CFG)" == "squirrel - Win32 Debug" + +# ADD CPP /YX"stdafx.h" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\sqfuncstate.cpp + +!IF "$(CFG)" == "squirrel - Win32 Release" + +!ELSEIF "$(CFG)" == "squirrel - Win32 Debug" + +# ADD CPP /YX"stdafx.h" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\sqlexer.cpp + +!IF "$(CFG)" == "squirrel - Win32 Release" + +!ELSEIF "$(CFG)" == "squirrel - Win32 Debug" + +# ADD CPP /YX"stdafx.h" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\sqmem.cpp +# End Source File +# Begin Source File + +SOURCE=.\sqobject.cpp + +!IF "$(CFG)" == "squirrel - Win32 Release" + +!ELSEIF "$(CFG)" == "squirrel - Win32 Debug" + +# ADD CPP /YX"stdafx.h" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\sqstate.cpp + +!IF "$(CFG)" == "squirrel - Win32 Release" + +!ELSEIF "$(CFG)" == "squirrel - Win32 Debug" + +# ADD CPP /YX"stdafx.h" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\sqtable.cpp + +!IF "$(CFG)" == "squirrel - Win32 Release" + +!ELSEIF "$(CFG)" == "squirrel - Win32 Debug" + +# ADD CPP /YX"stdafx.h" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\sqclass.cpp + +!IF "$(CFG)" == "squirrel - Win32 Release" + +!ELSEIF "$(CFG)" == "squirrel - Win32 Debug" + +# ADD CPP /YX"stdafx.h" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\sqvm.cpp + +!IF "$(CFG)" == "squirrel - Win32 Release" + +!ELSEIF "$(CFG)" == "squirrel - Win32 Debug" + +# ADD CPP /YX"stdafx.h" + +!ENDIF + +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl" +# Begin Source File + +SOURCE=.\sqarray.h +# End Source File +# Begin Source File + +SOURCE=.\sqclosure.h +# End Source File +# Begin Source File + +SOURCE=.\sqcompiler.h +# End Source File +# Begin Source File + +SOURCE=.\sqfuncproto.h +# End Source File +# Begin Source File + +SOURCE=.\sqfuncstate.h +# End Source File +# Begin Source File + +SOURCE=.\sqlexer.h +# End Source File +# Begin Source File + +SOURCE=.\sqobject.h +# End Source File +# Begin Source File + +SOURCE=.\sqopcodes.h +# End Source File +# Begin Source File + +SOURCE=.\sqpcheader.h +# End Source File +# Begin Source File + +SOURCE=.\sqstate.h +# End Source File +# Begin Source File + +SOURCE=.\sqstring.h +# End Source File +# Begin Source File + +SOURCE=.\sqtable.h +# End Source File +# Begin Source File + +SOURCE=.\squserdata.h +# End Source File +# Begin Source File + +SOURCE=.\squtils.h +# End Source File +# Begin Source File + +SOURCE=.\sqclass.h +# End Source File +# Begin Source File + +SOURCE=.\sqvm.h +# End Source File +# End Group +# End Target +# End Project diff --git a/src/vscript/squirrel/squirrel/squserdata.h b/src/vscript/squirrel/squirrel/squserdata.h new file mode 100644 index 00000000..ec217313 --- /dev/null +++ b/src/vscript/squirrel/squirrel/squserdata.h @@ -0,0 +1,40 @@ +/* see copyright notice in squirrel.h */ +#ifndef _SQUSERDATA_H_ +#define _SQUSERDATA_H_ + +struct SQUserData : SQDelegable +{ + SQUserData(SQSharedState *ss){ _delegate = 0; _hook = NULL; INIT_CHAIN(); ADD_TO_CHAIN(&_ss(this)->_gc_chain, this); } + ~SQUserData() + { + REMOVE_FROM_CHAIN(&_ss(this)->_gc_chain, this); + SetDelegate(NULL); + } + static SQUserData* Create(SQSharedState *ss, SQInteger size) + { + SQUserData* ud = (SQUserData*)SQ_MALLOC(sq_aligning(sizeof(SQUserData))+size); + new (ud) SQUserData(ss); + ud->_size = size; + ud->_typetag = 0; + return ud; + } +#ifndef NO_GARBAGE_COLLECTOR + void Mark(SQCollectable **chain); + void Finalize(){SetDelegate(NULL);} + SQObjectType GetType(){ return OT_USERDATA;} +#endif + void Release() { + if (_hook) _hook((SQUserPointer)sq_aligning(this + 1),_size); + SQInteger tsize = _size; + this->~SQUserData(); + SQ_FREE(this, sq_aligning(sizeof(SQUserData)) + tsize); + } + + + SQInteger _size; + SQRELEASEHOOK _hook; + SQUserPointer _typetag; + //SQChar _val[1]; +}; + +#endif //_SQUSERDATA_H_ diff --git a/src/vscript/squirrel/squirrel/squtils.h b/src/vscript/squirrel/squirrel/squtils.h new file mode 100644 index 00000000..f3e819a5 --- /dev/null +++ b/src/vscript/squirrel/squirrel/squtils.h @@ -0,0 +1,116 @@ +/* see copyright notice in squirrel.h */ +#ifndef _SQUTILS_H_ +#define _SQUTILS_H_ + +void *sq_vm_malloc(SQUnsignedInteger size); +void *sq_vm_realloc(void *p,SQUnsignedInteger oldsize,SQUnsignedInteger size); +void sq_vm_free(void *p,SQUnsignedInteger size); + +#define sq_new(__ptr,__type) {__ptr=(__type *)sq_vm_malloc(sizeof(__type));new (__ptr) __type;} +#define sq_delete(__ptr,__type) {__ptr->~__type();sq_vm_free(__ptr,sizeof(__type));} +#define SQ_MALLOC(__size) sq_vm_malloc((__size)); +#define SQ_FREE(__ptr,__size) sq_vm_free((__ptr),(__size)); +#define SQ_REALLOC(__ptr,__oldsize,__size) sq_vm_realloc((__ptr),(__oldsize),(__size)); + +#define sq_aligning(v) (((size_t)(v) + (SQ_ALIGNMENT-1)) & (~(SQ_ALIGNMENT-1))) + +//sqvector mini vector class, supports objects by value +template class sqvector +{ +public: + sqvector() + { + _vals = NULL; + _size = 0; + _allocated = 0; + } + sqvector(const sqvector& v) + { + copy(v); + } + void copy(const sqvector& v) + { + if(_size) { + resize(0); //destroys all previous stuff + } + //resize(v._size); + if(v._size > _allocated) { + _realloc(v._size); + } + for(SQUnsignedInteger i = 0; i < v._size; i++) { + new ((void *)&_vals[i]) T(v._vals[i]); + } + _size = v._size; + } + ~sqvector() + { + if(_allocated) { + for(SQUnsignedInteger i = 0; i < _size; i++) + _vals[i].~T(); + SQ_FREE(_vals, (_allocated * sizeof(T))); + } + } + void reserve(SQUnsignedInteger newsize) { _realloc(newsize); } + void resize(SQUnsignedInteger newsize, const T& fill = T()) + { + if(newsize > _allocated) + _realloc(newsize); + if(newsize > _size) { + while(_size < newsize) { + new ((void *)&_vals[_size]) T(fill); + _size++; + } + } + else{ + for(SQUnsignedInteger i = newsize; i < _size; i++) { + _vals[i].~T(); + } + _size = newsize; + } + } + void shrinktofit() { if(_size > 4) { _realloc(_size); } } + T& top() const { return _vals[_size - 1]; } + inline SQUnsignedInteger size() const { return _size; } + bool empty() const { return (_size <= 0); } + inline T &push_back(const T& val = T()) + { + if(_allocated <= _size) + _realloc(_size * 2); + return *(new ((void *)&_vals[_size++]) T(val)); + } + inline void pop_back() + { + _size--; _vals[_size].~T(); + } + void insert(SQUnsignedInteger idx, const T& val) + { + resize(_size + 1); + for(SQUnsignedInteger i = _size - 1; i > idx; i--) { + _vals[i] = _vals[i - 1]; + } + _vals[idx] = val; + } + void remove(SQUnsignedInteger idx) + { + _vals[idx].~T(); + if(idx < (_size - 1)) { + memmove(&_vals[idx], &_vals[idx+1], sizeof(T) * (_size - idx - 1)); + } + _size--; + } + SQUnsignedInteger capacity() { return _allocated; } + inline T &back() const { return _vals[_size - 1]; } + inline T& operator[](SQUnsignedInteger pos) const{ return _vals[pos]; } + T* _vals; +private: + void _realloc(SQUnsignedInteger newsize) + { + newsize = (newsize > 0)?newsize:4; + _vals = (T*)SQ_REALLOC(_vals, _allocated * sizeof(T), newsize * sizeof(T)); + _allocated = newsize; + } + SQUnsignedInteger _size; + SQUnsignedInteger _allocated; +}; + +#endif //_SQUTILS_H_ diff --git a/src/vscript/squirrel/squirrel/sqvm.cpp b/src/vscript/squirrel/squirrel/sqvm.cpp new file mode 100644 index 00000000..dcf823d7 --- /dev/null +++ b/src/vscript/squirrel/squirrel/sqvm.cpp @@ -0,0 +1,1791 @@ +/* + see copyright notice in squirrel.h +*/ +#include "sqpcheader.h" +#include +#include +#include "sqopcodes.h" +#include "sqvm.h" +#include "sqfuncproto.h" +#include "sqclosure.h" +#include "sqstring.h" +#include "sqtable.h" +#include "squserdata.h" +#include "sqarray.h" +#include "sqclass.h" + +#define TOP() (_stack._vals[_top-1]) +#define TARGET _stack._vals[_stackbase+arg0] +#define STK(a) _stack._vals[_stackbase+(a)] + +bool SQVM::BW_OP(SQUnsignedInteger op,SQObjectPtr &trg,const SQObjectPtr &o1,const SQObjectPtr &o2) +{ + SQInteger res; + if((sq_type(o1)| sq_type(o2)) == OT_INTEGER) + { + SQInteger i1 = _integer(o1), i2 = _integer(o2); + switch(op) { + case BW_AND: res = i1 & i2; break; + case BW_OR: res = i1 | i2; break; + case BW_XOR: res = i1 ^ i2; break; + case BW_SHIFTL: res = i1 << i2; break; + case BW_SHIFTR: res = i1 >> i2; break; + case BW_USHIFTR:res = (SQInteger)(*((SQUnsignedInteger*)&i1) >> i2); break; + default: { Raise_Error(_SC("internal vm error bitwise op failed")); return false; } + } + } + else { Raise_Error(_SC("bitwise op between '%s' and '%s'"),GetTypeName(o1),GetTypeName(o2)); return false;} + trg = res; + return true; +} + +#define _ARITH_(op,trg,o1,o2) \ +{ \ + SQInteger tmask = sq_type(o1)|sq_type(o2); \ + switch(tmask) { \ + case OT_INTEGER: trg = _integer(o1) op _integer(o2);break; \ + case (OT_FLOAT|OT_INTEGER): \ + case (OT_FLOAT): trg = tofloat(o1) op tofloat(o2); break;\ + default: _GUARD(ARITH_OP((#op)[0],trg,o1,o2)); break;\ + } \ +} + +#define _ARITH_NOZERO(op,trg,o1,o2,err) \ +{ \ + SQInteger tmask = sq_type(o1)|sq_type(o2); \ + switch(tmask) { \ + case OT_INTEGER: { SQInteger i2 = _integer(o2); if(i2 == 0) { Raise_Error(err); SQ_THROW(); } trg = _integer(o1) op i2; } break;\ + case (OT_FLOAT|OT_INTEGER): \ + case (OT_FLOAT): trg = tofloat(o1) op tofloat(o2); break;\ + default: _GUARD(ARITH_OP((#op)[0],trg,o1,o2)); break;\ + } \ +} + +bool SQVM::ARITH_OP(SQUnsignedInteger op,SQObjectPtr &trg,const SQObjectPtr &o1,const SQObjectPtr &o2) +{ + SQInteger tmask = sq_type(o1)| sq_type(o2); + switch(tmask) { + case OT_INTEGER:{ + SQInteger res, i1 = _integer(o1), i2 = _integer(o2); + switch(op) { + case '+': res = i1 + i2; break; + case '-': res = i1 - i2; break; + case '/': if (i2 == 0) { Raise_Error(_SC("division by zero")); return false; } + else if (i2 == -1 && i1 == INT_MIN) { Raise_Error(_SC("integer overflow")); return false; } + res = i1 / i2; + break; + case '*': res = i1 * i2; break; + case '%': if (i2 == 0) { Raise_Error(_SC("modulo by zero")); return false; } + else if (i2 == -1 && i1 == INT_MIN) { res = 0; break; } + res = i1 % i2; + break; + default: res = 0xDEADBEEF; + } + trg = res; } + break; + case (OT_FLOAT|OT_INTEGER): + case (OT_FLOAT):{ + SQFloat res, f1 = tofloat(o1), f2 = tofloat(o2); + switch(op) { + case '+': res = f1 + f2; break; + case '-': res = f1 - f2; break; + case '/': res = f1 / f2; break; + case '*': res = f1 * f2; break; + case '%': res = SQFloat(fmod((double)f1,(double)f2)); break; + default: res = 0x0f; + } + trg = res; } + break; + default: + if(op == '+' && (tmask & _RT_STRING)){ + if(!StringCat(o1, o2, trg)) return false; + } + else if(!ArithMetaMethod(op,o1,o2,trg)) { + return false; + } + } + return true; +} + +SQVM::SQVM(SQSharedState *ss) +{ + _sharedstate=ss; + _suspended = SQFalse; + _suspended_target = -1; + _suspended_root = SQFalse; + _suspended_traps = -1; + _foreignptr = NULL; + _nnativecalls = 0; + _nmetamethodscall = 0; + _lasterror.Null(); + _errorhandler.Null(); + _debughook = false; + _debughook_native = NULL; + _debughook_closure.Null(); + _openouters = NULL; + ci = NULL; + _releasehook = NULL; + INIT_CHAIN();ADD_TO_CHAIN(&_ss(this)->_gc_chain,this); +} + +void SQVM::Finalize() +{ + if(_releasehook) { _releasehook(_foreignptr,0); _releasehook = NULL; } + if(_openouters) CloseOuters(&_stack._vals[0]); + _roottable.Null(); + _lasterror.Null(); + _errorhandler.Null(); + _debughook = false; + _debughook_native = NULL; + _debughook_closure.Null(); + temp_reg.Null(); + _callstackdata.resize(0); + SQInteger size=_stack.size(); + for(SQInteger i=0;i_gc_chain,this); +} + +bool SQVM::ArithMetaMethod(SQInteger op,const SQObjectPtr &o1,const SQObjectPtr &o2,SQObjectPtr &dest) +{ + SQMetaMethod mm; + switch(op){ + case _SC('+'): mm=MT_ADD; break; + case _SC('-'): mm=MT_SUB; break; + case _SC('/'): mm=MT_DIV; break; + case _SC('*'): mm=MT_MUL; break; + case _SC('%'): mm=MT_MODULO; break; + default: mm = MT_ADD; assert(0); break; //shutup compiler + } + if(is_delegable(o1) && _delegable(o1)->_delegate) { + + SQObjectPtr closure; + if(_delegable(o1)->GetMetaMethod(this, mm, closure)) { + Push(o1);Push(o2); + return CallMetaMethod(closure,mm,2,dest); + } + } + Raise_Error(_SC("arith op %c on between '%s' and '%s'"),op,GetTypeName(o1),GetTypeName(o2)); + return false; +} + +bool SQVM::NEG_OP(SQObjectPtr &trg,const SQObjectPtr &o) +{ + + switch(sq_type(o)) { + case OT_INTEGER: + trg = -_integer(o); + return true; + case OT_FLOAT: + trg = -_float(o); + return true; + case OT_TABLE: + case OT_USERDATA: + case OT_INSTANCE: + if(_delegable(o)->_delegate) { + SQObjectPtr closure; + if(_delegable(o)->GetMetaMethod(this, MT_UNM, closure)) { + Push(o); + if(!CallMetaMethod(closure, MT_UNM, 1, temp_reg)) return false; + _Swap(trg,temp_reg); + return true; + + } + } + default:break; //shutup compiler + } + Raise_Error(_SC("attempt to negate a %s"), GetTypeName(o)); + return false; +} + +#define _RET_SUCCEED(exp) { result = (exp); return true; } +bool SQVM::ObjCmp(const SQObjectPtr &o1,const SQObjectPtr &o2,SQInteger &result) +{ + SQObjectType t1 = sq_type(o1), t2 = sq_type(o2); + if(t1 == t2) { + if(_rawval(o1) == _rawval(o2))_RET_SUCCEED(0); + SQObjectPtr res; + switch(t1){ + case OT_STRING: + _RET_SUCCEED(scstrcmp(_stringval(o1),_stringval(o2))); + case OT_INTEGER: + _RET_SUCCEED((_integer(o1)<_integer(o2))?-1:1); + case OT_FLOAT: + _RET_SUCCEED((_float(o1)<_float(o2))?-1:1); + case OT_TABLE: + case OT_USERDATA: + case OT_INSTANCE: + if(_delegable(o1)->_delegate) { + SQObjectPtr closure; + if(_delegable(o1)->GetMetaMethod(this, MT_CMP, closure)) { + Push(o1);Push(o2); + if(CallMetaMethod(closure,MT_CMP,2,res)) { + if(sq_type(res) != OT_INTEGER) { + Raise_Error(_SC("_cmp must return an integer")); + return false; + } + _RET_SUCCEED(_integer(res)) + } + return false; + } + } + //continues through (no break needed) + default: + _RET_SUCCEED( _userpointer(o1) < _userpointer(o2)?-1:1 ); + } + assert(0); + //if(type(res)!=OT_INTEGER) { Raise_CompareError(o1,o2); return false; } + // _RET_SUCCEED(_integer(res)); + + } + else{ + if(sq_isnumeric(o1) && sq_isnumeric(o2)){ + if((t1==OT_INTEGER) && (t2==OT_FLOAT)) { + if( _integer(o1)==_float(o2) ) { _RET_SUCCEED(0); } + else if( _integer(o1)<_float(o2) ) { _RET_SUCCEED(-1); } + _RET_SUCCEED(1); + } + else{ + if( _float(o1)==_integer(o2) ) { _RET_SUCCEED(0); } + else if( _float(o1)<_integer(o2) ) { _RET_SUCCEED(-1); } + _RET_SUCCEED(1); + } + } + else if(t1==OT_NULL) {_RET_SUCCEED(-1);} + else if(t2==OT_NULL) {_RET_SUCCEED(1);} + else { Raise_CompareError(o1,o2); return false; } + + } + assert(0); + _RET_SUCCEED(0); //cannot happen +} + +bool SQVM::CMP_OP(CmpOP op, const SQObjectPtr &o1,const SQObjectPtr &o2,SQObjectPtr &res) +{ + SQInteger r; + if(ObjCmp(o1,o2,r)) { + switch(op) { + case CMP_G: res = (r > 0); return true; + case CMP_GE: res = (r >= 0); return true; + case CMP_L: res = (r < 0); return true; + case CMP_LE: res = (r <= 0); return true; + case CMP_3W: res = r; return true; + } + assert(0); + } + return false; +} + +bool SQVM::ToString(const SQObjectPtr &o,SQObjectPtr &res) +{ + switch(sq_type(o)) { + case OT_STRING: + res = o; + return true; + case OT_FLOAT: + scsprintf(_sp(sq_rsl(NUMBER_MAX_CHAR+1)),sq_rsl(NUMBER_MAX_CHAR),_SC("%g"),_float(o)); + break; + case OT_INTEGER: + scsprintf(_sp(sq_rsl(NUMBER_MAX_CHAR+1)),sq_rsl(NUMBER_MAX_CHAR),_PRINT_INT_FMT,_integer(o)); + break; + case OT_BOOL: + scsprintf(_sp(sq_rsl(6)),sq_rsl(6),_integer(o)?_SC("true"):_SC("false")); + break; + case OT_NULL: + scsprintf(_sp(sq_rsl(5)),sq_rsl(5),_SC("null")); + break; + case OT_TABLE: + case OT_USERDATA: + case OT_INSTANCE: + if(_delegable(o)->_delegate) { + SQObjectPtr closure; + if(_delegable(o)->GetMetaMethod(this, MT_TOSTRING, closure)) { + Push(o); + if(CallMetaMethod(closure,MT_TOSTRING,1,res)) { + if(sq_type(res) == OT_STRING) + return true; + } + else { + return false; + } + } + } + default: + scsprintf(_sp(sq_rsl((sizeof(void*)*2)+NUMBER_MAX_CHAR)),sq_rsl((sizeof(void*)*2)+NUMBER_MAX_CHAR),_SC("(%s : 0x%p)"),GetTypeName(o),(void*)_rawval(o)); + } + res = SQString::Create(_ss(this),_spval); + return true; +} + + +bool SQVM::StringCat(const SQObjectPtr &str,const SQObjectPtr &obj,SQObjectPtr &dest) +{ + SQObjectPtr a, b; + if(!ToString(str, a)) return false; + if(!ToString(obj, b)) return false; + SQInteger l = _string(a)->_len , ol = _string(b)->_len; + SQChar *s = _sp(sq_rsl(l + ol + 1)); + memcpy(s, _stringval(a), sq_rsl(l)); + memcpy(s + l, _stringval(b), sq_rsl(ol)); + dest = SQString::Create(_ss(this), _spval, l + ol); + return true; +} + +bool SQVM::TypeOf(const SQObjectPtr &obj1,SQObjectPtr &dest) +{ + if(is_delegable(obj1) && _delegable(obj1)->_delegate) { + SQObjectPtr closure; + if(_delegable(obj1)->GetMetaMethod(this, MT_TYPEOF, closure)) { + Push(obj1); + return CallMetaMethod(closure,MT_TYPEOF,1,dest); + } + } + dest = SQString::Create(_ss(this),GetTypeName(obj1)); + return true; +} + +bool SQVM::Init(SQVM *friendvm, SQInteger stacksize) +{ + _stack.resize(stacksize); + _alloccallsstacksize = 4; + _callstackdata.resize(_alloccallsstacksize); + _callsstacksize = 0; + _callsstack = &_callstackdata[0]; + _stackbase = 0; + _top = 0; + if(!friendvm) { + _roottable = SQTable::Create(_ss(this), 0); + sq_base_register(this); + } + else { + _roottable = friendvm->_roottable; + _errorhandler = friendvm->_errorhandler; + _debughook = friendvm->_debughook; + _debughook_native = friendvm->_debughook_native; + _debughook_closure = friendvm->_debughook_closure; + } + + + return true; +} + + +bool SQVM::StartCall(SQClosure *closure,SQInteger target,SQInteger args,SQInteger stackbase,bool tailcall) +{ + SQFunctionProto *func = closure->_function; + + SQInteger paramssize = func->_nparameters; + const SQInteger newtop = stackbase + func->_stacksize; + SQInteger nargs = args; + if(func->_varparams) + { + paramssize--; + if (nargs < paramssize) { + Raise_Error(_SC("wrong number of parameters (%d passed, at least %d required)"), + (int)nargs, (int)paramssize); + return false; + } + + //dumpstack(stackbase); + SQInteger nvargs = nargs - paramssize; + SQArray *arr = SQArray::Create(_ss(this),nvargs); + SQInteger pbase = stackbase+paramssize; + for(SQInteger n = 0; n < nvargs; n++) { + arr->_values[n] = _stack._vals[pbase]; + _stack._vals[pbase].Null(); + pbase++; + + } + _stack._vals[stackbase+paramssize] = arr; + //dumpstack(stackbase); + } + else if (paramssize != nargs) { + SQInteger ndef = func->_ndefaultparams; + SQInteger diff; + if(ndef && nargs < paramssize && (diff = paramssize - nargs) <= ndef) { + for(SQInteger n = ndef - diff; n < ndef; n++) { + _stack._vals[stackbase + (nargs++)] = closure->_defaultparams[n]; + } + } + else { + Raise_Error(_SC("wrong number of parameters (%d passed, %d required)"), + (int)nargs, (int)paramssize); + return false; + } + } + + if(closure->_env) { + _stack._vals[stackbase] = closure->_env->_obj; + } + + if(!EnterFrame(stackbase, newtop, tailcall)) return false; + + ci->_closure = closure; + ci->_literals = func->_literals; + ci->_ip = func->_instructions; + ci->_target = (SQInt32)target; + + if (_debughook) { + CallDebugHook(_SC('c')); + } + + if (closure->_function->_bgenerator) { + SQFunctionProto *f = closure->_function; + SQGenerator *gen = SQGenerator::Create(_ss(this), closure); + if(!gen->Yield(this,f->_stacksize)) + return false; + SQObjectPtr temp; + Return(1, target, temp); + STK(target) = gen; + } + + + return true; +} + +bool SQVM::Return(SQInteger _arg0, SQInteger _arg1, SQObjectPtr &retval) +{ + SQBool _isroot = ci->_root; + SQInteger callerbase = _stackbase - ci->_prevstkbase; + + if (_debughook) { + for(SQInteger i=0; i_ncalls; i++) { + CallDebugHook(_SC('r')); + } + } + + SQObjectPtr *dest; + if (_isroot) { + dest = &(retval); + } else if (ci->_target == -1) { + dest = NULL; + } else { + dest = &_stack._vals[callerbase + ci->_target]; + } + if (dest) { + if(_arg0 != 0xFF) { + *dest = _stack._vals[_stackbase+_arg1]; + } + else { + dest->Null(); + } + //*dest = (_arg0 != 0xFF) ? _stack._vals[_stackbase+_arg1] : _null_; + } + LeaveFrame(); + return _isroot ? true : false; +} + +#define _RET_ON_FAIL(exp) { if(!exp) return false; } + +bool SQVM::PLOCAL_INC(SQInteger op,SQObjectPtr &target, SQObjectPtr &a, SQObjectPtr &incr) +{ + SQObjectPtr trg; + _RET_ON_FAIL(ARITH_OP( op , trg, a, incr)); + target = a; + a = trg; + return true; +} + +bool SQVM::DerefInc(SQInteger op,SQObjectPtr &target, SQObjectPtr &self, SQObjectPtr &key, SQObjectPtr &incr, bool postfix,SQInteger selfidx) +{ + SQObjectPtr tmp, tself = self, tkey = key; + if (!Get(tself, tkey, tmp, 0, selfidx)) { return false; } + _RET_ON_FAIL(ARITH_OP( op , target, tmp, incr)) + if (!Set(tself, tkey, target,selfidx)) { return false; } + if (postfix) target = tmp; + return true; +} + +#define arg0 (_i_._arg0) +#define sarg0 ((SQInteger)*((const signed char *)&_i_._arg0)) +#define arg1 (_i_._arg1) +#define sarg1 (*((const SQInt32 *)&_i_._arg1)) +#define arg2 (_i_._arg2) +#define arg3 (_i_._arg3) +#define sarg3 ((SQInteger)*((const signed char *)&_i_._arg3)) + +SQRESULT SQVM::Suspend() +{ + if (_suspended) + return sq_throwerror(this, _SC("cannot suspend an already suspended vm")); + if (_nnativecalls!=2) + return sq_throwerror(this, _SC("cannot suspend through native calls/metamethods")); + return SQ_SUSPEND_FLAG; +} + + +#define _FINISH(howmuchtojump) {jump = howmuchtojump; return true; } +bool SQVM::FOREACH_OP(SQObjectPtr &o1,SQObjectPtr &o2,SQObjectPtr +&o3,SQObjectPtr &o4,SQInteger SQ_UNUSED_ARG(arg_2),int exitpos,int &jump) +{ + SQInteger nrefidx; + switch(sq_type(o1)) { + case OT_TABLE: + if((nrefidx = _table(o1)->Next(false,o4, o2, o3)) == -1) _FINISH(exitpos); + o4 = (SQInteger)nrefidx; _FINISH(1); + case OT_ARRAY: + if((nrefidx = _array(o1)->Next(o4, o2, o3)) == -1) _FINISH(exitpos); + o4 = (SQInteger) nrefidx; _FINISH(1); + case OT_STRING: + if((nrefidx = _string(o1)->Next(o4, o2, o3)) == -1)_FINISH(exitpos); + o4 = (SQInteger)nrefidx; _FINISH(1); + case OT_CLASS: + if((nrefidx = _class(o1)->Next(o4, o2, o3)) == -1)_FINISH(exitpos); + o4 = (SQInteger)nrefidx; _FINISH(1); + case OT_USERDATA: + case OT_INSTANCE: + if(_delegable(o1)->_delegate) { + SQObjectPtr itr; + SQObjectPtr closure; + if(_delegable(o1)->GetMetaMethod(this, MT_NEXTI, closure)) { + Push(o1); + Push(o4); + if(CallMetaMethod(closure, MT_NEXTI, 2, itr)) { + o4 = o2 = itr; + if(sq_type(itr) == OT_NULL) _FINISH(exitpos); + if(!Get(o1, itr, o3, 0, DONT_FALL_BACK)) { + Raise_Error(_SC("_nexti returned an invalid idx")); // cloud be changed + return false; + } + _FINISH(1); + } + else { + return false; + } + } + Raise_Error(_SC("_nexti failed")); + return false; + } + break; + case OT_GENERATOR: + if(_generator(o1)->_state == SQGenerator::eDead) _FINISH(exitpos); + if(_generator(o1)->_state == SQGenerator::eSuspended) { + SQInteger idx = 0; + if(sq_type(o4) == OT_INTEGER) { + idx = _integer(o4) + 1; + } + o2 = idx; + o4 = idx; + _generator(o1)->Resume(this, o3); + _FINISH(0); + } + default: + Raise_Error(_SC("cannot iterate %s"), GetTypeName(o1)); + } + return false; //cannot be hit(just to avoid warnings) +} + +#define COND_LITERAL (arg3!=0?ci->_literals[arg1]:STK(arg1)) + +#define SQ_THROW() { goto exception_trap; } + +#define _GUARD(exp) { if(!exp) { SQ_THROW();} } + +bool SQVM::CLOSURE_OP(SQObjectPtr &target, SQFunctionProto *func) +{ + SQInteger nouters; + SQClosure *closure = SQClosure::Create(_ss(this), func,_table(_roottable)->GetWeakRef(OT_TABLE)); + if((nouters = func->_noutervalues)) { + for(SQInteger i = 0; i_outervalues[i]; + switch(v._type){ + case otLOCAL: + FindOuter(closure->_outervalues[i], &STK(_integer(v._src))); + break; + case otOUTER: + closure->_outervalues[i] = _closure(ci->_closure)->_outervalues[_integer(v._src)]; + break; + } + } + } + SQInteger ndefparams; + if((ndefparams = func->_ndefaultparams)) { + for(SQInteger i = 0; i < ndefparams; i++) { + SQInteger spos = func->_defaultparams[i]; + closure->_defaultparams[i] = _stack._vals[_stackbase + spos]; + } + } + target = closure; + return true; + +} + + +bool SQVM::CLASS_OP(SQObjectPtr &target,SQInteger baseclass,SQInteger attributes) +{ + SQClass *base = NULL; + SQObjectPtr attrs; + if(baseclass != -1) { + if(sq_type(_stack._vals[_stackbase+baseclass]) != OT_CLASS) { Raise_Error(_SC("trying to inherit from a %s"),GetTypeName(_stack._vals[_stackbase+baseclass])); return false; } + base = _class(_stack._vals[_stackbase + baseclass]); + } + if(attributes != MAX_FUNC_STACKSIZE) { + attrs = _stack._vals[_stackbase+attributes]; + } + target = SQClass::Create(_ss(this),base); + if(sq_type(_class(target)->_metamethods[MT_INHERITED]) != OT_NULL) { + int nparams = 2; + SQObjectPtr ret; + Push(target); Push(attrs); + if(!Call(_class(target)->_metamethods[MT_INHERITED],nparams,_top - nparams, ret, false)) { + Pop(nparams); + return false; + } + Pop(nparams); + } + _class(target)->_attributes = attrs; + return true; +} + +bool SQVM::IsEqual(const SQObjectPtr &o1,const SQObjectPtr &o2,bool &res) +{ + SQObjectType t1 = sq_type(o1), t2 = sq_type(o2); + if(t1 == t2) { + if (t1 == OT_FLOAT) { + res = (_float(o1) == _float(o2)); + } + else { + res = (_rawval(o1) == _rawval(o2)); + } + } + else { + if(sq_isnumeric(o1) && sq_isnumeric(o2)) { + res = (tofloat(o1) == tofloat(o2)); + } + else { + res = false; + } + } + return true; +} + +bool SQVM::IsFalse(SQObjectPtr &o) +{ + if(((sq_type(o) & SQOBJECT_CANBEFALSE) + && ( ((sq_type(o) == OT_FLOAT) && (_float(o) == SQFloat(0.0))) )) +#if !defined(SQUSEDOUBLE) || (defined(SQUSEDOUBLE) && defined(_SQ64)) + || (_integer(o) == 0) ) //OT_NULL|OT_INTEGER|OT_BOOL +#else + || (((sq_type(o) != OT_FLOAT) && (_integer(o) == 0))) ) //OT_NULL|OT_INTEGER|OT_BOOL +#endif + { + return true; + } + return false; +} +extern SQInstructionDesc g_InstrDesc[]; +bool SQVM::Execute(SQObjectPtr &closure, SQInteger nargs, SQInteger stackbase,SQObjectPtr &outres, SQBool raiseerror,ExecutionType et) +{ + if ((_nnativecalls + 1) > MAX_NATIVE_CALLS) { Raise_Error(_SC("Native stack overflow")); return false; } + _nnativecalls++; + AutoDec ad(&_nnativecalls); + SQInteger traps = 0; + CallInfo *prevci = ci; + + switch(et) { + case ET_CALL: { + temp_reg = closure; + if(!StartCall(_closure(temp_reg), _top - nargs, nargs, stackbase, false)) { + //call the handler if there are no calls in the stack, if not relies on the previous node + if(ci == NULL) CallErrorHandler(_lasterror); + return false; + } + if(ci == prevci) { + outres = STK(_top-nargs); + return true; + } + ci->_root = SQTrue; + } + break; + case ET_RESUME_GENERATOR: _generator(closure)->Resume(this, outres); ci->_root = SQTrue; traps += ci->_etraps; break; + case ET_RESUME_VM: + case ET_RESUME_THROW_VM: + traps = _suspended_traps; + ci->_root = _suspended_root; + _suspended = SQFalse; + if(et == ET_RESUME_THROW_VM) { SQ_THROW(); } + break; + } + +exception_restore: + // + { + for(;;) + { + const SQInstruction &_i_ = *ci->_ip++; + //dumpstack(_stackbase); + //scprintf("\n[%d] %s %d %d %d %d\n",ci->_ip-_closure(ci->_closure)->_function->_instructions,g_InstrDesc[_i_.op].name,arg0,arg1,arg2,arg3); + switch(_i_.op) + { + case _OP_LINE: if (_debughook) CallDebugHook(_SC('l'),arg1); continue; + case _OP_LOAD: TARGET = ci->_literals[arg1]; continue; + case _OP_LOADINT: +#ifndef _SQ64 + TARGET = (SQInteger)arg1; continue; +#else + TARGET = (SQInteger)((SQInt32)arg1); continue; +#endif + case _OP_LOADFLOAT: TARGET = *((const SQFloat *)&arg1); continue; + case _OP_DLOAD: TARGET = ci->_literals[arg1]; STK(arg2) = ci->_literals[arg3];continue; + case _OP_TAILCALL:{ + SQObjectPtr &t = STK(arg1); + if (sq_type(t) == OT_CLOSURE + && (!_closure(t)->_function->_bgenerator)){ + SQObjectPtr clo = t; + SQInteger last_top = _top; + if(_openouters) CloseOuters(&(_stack._vals[_stackbase])); + for (SQInteger i = 0; i < arg3; i++) STK(i) = STK(arg2 + i); + _GUARD(StartCall(_closure(clo), ci->_target, arg3, _stackbase, true)); + if (last_top >= _top) { + _top = last_top; + } + continue; + } + } + case _OP_CALL: { + SQObjectPtr clo = STK(arg1); + switch (sq_type(clo)) { + case OT_CLOSURE: + _GUARD(StartCall(_closure(clo), sarg0, arg3, _stackbase+arg2, false)); + continue; + case OT_NATIVECLOSURE: { + bool suspend; + bool tailcall; + _GUARD(CallNative(_nativeclosure(clo), arg3, _stackbase+arg2, clo, (SQInt32)sarg0, suspend, tailcall)); + if(suspend){ + _suspended = SQTrue; + _suspended_target = sarg0; + _suspended_root = ci->_root; + _suspended_traps = traps; + outres = clo; + return true; + } + if(sarg0 != -1 && !tailcall) { + STK(arg0) = clo; + } + } + continue; + case OT_CLASS:{ + SQObjectPtr inst; + _GUARD(CreateClassInstance(_class(clo),inst,clo)); + if(sarg0 != -1) { + STK(arg0) = inst; + } + SQInteger stkbase; + switch(sq_type(clo)) { + case OT_CLOSURE: + stkbase = _stackbase+arg2; + _stack._vals[stkbase] = inst; + _GUARD(StartCall(_closure(clo), -1, arg3, stkbase, false)); + break; + case OT_NATIVECLOSURE: + bool dummy; + stkbase = _stackbase+arg2; + _stack._vals[stkbase] = inst; + _GUARD(CallNative(_nativeclosure(clo), arg3, stkbase, clo, -1, dummy, dummy)); + break; + default: break; //shutup GCC 4.x + } + } + break; + case OT_TABLE: + case OT_USERDATA: + case OT_INSTANCE:{ + SQObjectPtr closure; + if(_delegable(clo)->_delegate && _delegable(clo)->GetMetaMethod(this,MT_CALL,closure)) { + Push(clo); + for (SQInteger i = 0; i < arg3; i++) Push(STK(arg2 + i)); + if(!CallMetaMethod(closure, MT_CALL, arg3+1, clo)) SQ_THROW(); + if(sarg0 != -1) { + STK(arg0) = clo; + } + break; + } + + //Raise_Error(_SC("attempt to call '%s'"), GetTypeName(clo)); + //SQ_THROW(); + } + default: + Raise_Error(_SC("attempt to call '%s'"), GetTypeName(clo)); + SQ_THROW(); + } + } + continue; + case _OP_PREPCALL: + case _OP_PREPCALLK: { + SQObjectPtr &key = _i_.op == _OP_PREPCALLK?(ci->_literals)[arg1]:STK(arg1); + SQObjectPtr &o = STK(arg2); + if (!Get(o, key, temp_reg,0,arg2)) { + SQ_THROW(); + } + STK(arg3) = o; + _Swap(TARGET,temp_reg);//TARGET = temp_reg; + } + continue; + case _OP_GETK: + if (!Get(STK(arg2), ci->_literals[arg1], temp_reg, 0,arg2)) { SQ_THROW();} + _Swap(TARGET,temp_reg);//TARGET = temp_reg; + continue; + case _OP_MOVE: TARGET = STK(arg1); continue; + case _OP_NEWSLOT: + _GUARD(NewSlot(STK(arg1), STK(arg2), STK(arg3),false)); + if(arg0 != 0xFF) TARGET = STK(arg3); + continue; + case _OP_DELETE: _GUARD(DeleteSlot(STK(arg1), STK(arg2), TARGET)); continue; + case _OP_SET: + if (!Set(STK(arg1), STK(arg2), STK(arg3),arg1)) { SQ_THROW(); } + if (arg0 != 0xFF) TARGET = STK(arg3); + continue; + case _OP_GET: + if (!Get(STK(arg1), STK(arg2), temp_reg, 0,arg1)) { SQ_THROW(); } + _Swap(TARGET,temp_reg);//TARGET = temp_reg; + continue; + case _OP_EQ:{ + bool res; + if(!IsEqual(STK(arg2),COND_LITERAL,res)) { SQ_THROW(); } + TARGET = res?true:false; + }continue; + case _OP_NE:{ + bool res; + if(!IsEqual(STK(arg2),COND_LITERAL,res)) { SQ_THROW(); } + TARGET = (!res)?true:false; + } continue; + case _OP_ADD: _ARITH_(+,TARGET,STK(arg2),STK(arg1)); continue; + case _OP_SUB: _ARITH_(-,TARGET,STK(arg2),STK(arg1)); continue; + case _OP_MUL: _ARITH_(*,TARGET,STK(arg2),STK(arg1)); continue; + case _OP_DIV: _ARITH_NOZERO(/,TARGET,STK(arg2),STK(arg1),_SC("division by zero")); continue; + case _OP_MOD: ARITH_OP('%',TARGET,STK(arg2),STK(arg1)); continue; + case _OP_BITW: _GUARD(BW_OP( arg3,TARGET,STK(arg2),STK(arg1))); continue; + case _OP_RETURN: + if((ci)->_generator) { + (ci)->_generator->Kill(); + } + if(Return(arg0, arg1, temp_reg)){ + assert(traps==0); + //outres = temp_reg; + _Swap(outres,temp_reg); + return true; + } + continue; + case _OP_LOADNULLS:{ for(SQInt32 n=0; n < arg1; n++) STK(arg0+n).Null(); }continue; + case _OP_LOADROOT: { + SQWeakRef *w = _closure(ci->_closure)->_root; + if(sq_type(w->_obj) != OT_NULL) { + TARGET = w->_obj; + } else { + TARGET = _roottable; //shoud this be like this? or null + } + } + continue; + case _OP_LOADBOOL: TARGET = arg1?true:false; continue; + case _OP_DMOVE: STK(arg0) = STK(arg1); STK(arg2) = STK(arg3); continue; + case _OP_JMP: ci->_ip += (sarg1); continue; + //case _OP_JNZ: if(!IsFalse(STK(arg0))) ci->_ip+=(sarg1); continue; + case _OP_JCMP: + _GUARD(CMP_OP((CmpOP)arg3,STK(arg2),STK(arg0),temp_reg)); + if(IsFalse(temp_reg)) ci->_ip+=(sarg1); + continue; + case _OP_JZ: if(IsFalse(STK(arg0))) ci->_ip+=(sarg1); continue; + case _OP_GETOUTER: { + SQClosure *cur_cls = _closure(ci->_closure); + SQOuter *otr = _outer(cur_cls->_outervalues[arg1]); + TARGET = *(otr->_valptr); + } + continue; + case _OP_SETOUTER: { + SQClosure *cur_cls = _closure(ci->_closure); + SQOuter *otr = _outer(cur_cls->_outervalues[arg1]); + *(otr->_valptr) = STK(arg2); + if(arg0 != 0xFF) { + TARGET = STK(arg2); + } + } + continue; + case _OP_NEWOBJ: + switch(arg3) { + case NOT_TABLE: TARGET = SQTable::Create(_ss(this), arg1); continue; + case NOT_ARRAY: TARGET = SQArray::Create(_ss(this), 0); _array(TARGET)->Reserve(arg1); continue; + case NOT_CLASS: _GUARD(CLASS_OP(TARGET,arg1,arg2)); continue; + default: assert(0); continue; + } + case _OP_APPENDARRAY: + { + SQObject val; + val._unVal.raw = 0; + switch(arg2) { + case AAT_STACK: + val = STK(arg1); break; + case AAT_LITERAL: + val = ci->_literals[arg1]; break; + case AAT_INT: + val._type = OT_INTEGER; +#ifndef _SQ64 + val._unVal.nInteger = (SQInteger)arg1; +#else + val._unVal.nInteger = (SQInteger)((SQInt32)arg1); +#endif + break; + case AAT_FLOAT: + val._type = OT_FLOAT; + val._unVal.fFloat = *((const SQFloat *)&arg1); + break; + case AAT_BOOL: + val._type = OT_BOOL; + val._unVal.nInteger = arg1; + break; + default: val._type = OT_INTEGER; assert(0); break; + + } + _array(STK(arg0))->Append(val); continue; + } + case _OP_COMPARITH: { + SQInteger selfidx = (((SQUnsignedInteger)arg1&0xFFFF0000)>>16); + _GUARD(DerefInc(arg3, TARGET, STK(selfidx), STK(arg2), STK(arg1&0x0000FFFF), false, selfidx)); + } + continue; + case _OP_INC: {SQObjectPtr o(sarg3); _GUARD(DerefInc('+',TARGET, STK(arg1), STK(arg2), o, false, arg1));} continue; + case _OP_INCL: { + SQObjectPtr &a = STK(arg1); + if(sq_type(a) == OT_INTEGER) { + a._unVal.nInteger = _integer(a) + sarg3; + } + else { + SQObjectPtr o(sarg3); //_GUARD(LOCAL_INC('+',TARGET, STK(arg1), o)); + _ARITH_(+,a,a,o); + } + } continue; + case _OP_PINC: {SQObjectPtr o(sarg3); _GUARD(DerefInc('+',TARGET, STK(arg1), STK(arg2), o, true, arg1));} continue; + case _OP_PINCL: { + SQObjectPtr &a = STK(arg1); + if(sq_type(a) == OT_INTEGER) { + TARGET = a; + a._unVal.nInteger = _integer(a) + sarg3; + } + else { + SQObjectPtr o(sarg3); _GUARD(PLOCAL_INC('+',TARGET, STK(arg1), o)); + } + + } continue; + case _OP_CMP: _GUARD(CMP_OP((CmpOP)arg3,STK(arg2),STK(arg1),TARGET)) continue; + case _OP_EXISTS: TARGET = Get(STK(arg1), STK(arg2), temp_reg, GET_FLAG_DO_NOT_RAISE_ERROR | GET_FLAG_RAW, DONT_FALL_BACK) ? true : false; continue; + case _OP_INSTANCEOF: + if(sq_type(STK(arg1)) != OT_CLASS) + {Raise_Error(_SC("cannot apply instanceof between a %s and a %s"),GetTypeName(STK(arg1)),GetTypeName(STK(arg2))); SQ_THROW();} + TARGET = (sq_type(STK(arg2)) == OT_INSTANCE) ? (_instance(STK(arg2))->InstanceOf(_class(STK(arg1)))?true:false) : false; + continue; + case _OP_AND: + if(IsFalse(STK(arg2))) { + TARGET = STK(arg2); + ci->_ip += (sarg1); + } + continue; + case _OP_OR: + if(!IsFalse(STK(arg2))) { + TARGET = STK(arg2); + ci->_ip += (sarg1); + } + continue; + case _OP_NEG: _GUARD(NEG_OP(TARGET,STK(arg1))); continue; + case _OP_NOT: TARGET = IsFalse(STK(arg1)); continue; + case _OP_BWNOT: + if(sq_type(STK(arg1)) == OT_INTEGER) { + SQInteger t = _integer(STK(arg1)); + TARGET = SQInteger(~t); + continue; + } + Raise_Error(_SC("attempt to perform a bitwise op on a %s"), GetTypeName(STK(arg1))); + SQ_THROW(); + case _OP_CLOSURE: { + SQClosure *c = ci->_closure._unVal.pClosure; + SQFunctionProto *fp = c->_function; + if(!CLOSURE_OP(TARGET,fp->_functions[arg1]._unVal.pFunctionProto)) { SQ_THROW(); } + continue; + } + case _OP_YIELD:{ + if(ci->_generator) { + if(sarg1 != MAX_FUNC_STACKSIZE) temp_reg = STK(arg1); + if (_openouters) CloseOuters(&_stack._vals[_stackbase]); + _GUARD(ci->_generator->Yield(this,arg2)); + traps -= ci->_etraps; + if(sarg1 != MAX_FUNC_STACKSIZE) _Swap(STK(arg1),temp_reg);//STK(arg1) = temp_reg; + } + else { Raise_Error(_SC("trying to yield a '%s',only genenerator can be yielded"), GetTypeName(ci->_generator)); SQ_THROW();} + if(Return(arg0, arg1, temp_reg)){ + assert(traps == 0); + outres = temp_reg; + return true; + } + + } + continue; + case _OP_RESUME: + if(sq_type(STK(arg1)) != OT_GENERATOR){ Raise_Error(_SC("trying to resume a '%s',only genenerator can be resumed"), GetTypeName(STK(arg1))); SQ_THROW();} + _GUARD(_generator(STK(arg1))->Resume(this, TARGET)); + traps += ci->_etraps; + continue; + case _OP_FOREACH:{ int tojump; + _GUARD(FOREACH_OP(STK(arg0),STK(arg2),STK(arg2+1),STK(arg2+2),arg2,sarg1,tojump)); + ci->_ip += tojump; } + continue; + case _OP_POSTFOREACH: + assert(sq_type(STK(arg0)) == OT_GENERATOR); + if(_generator(STK(arg0))->_state == SQGenerator::eDead) + ci->_ip += (sarg1 - 1); + continue; + case _OP_CLONE: _GUARD(Clone(STK(arg1), TARGET)); continue; + case _OP_TYPEOF: _GUARD(TypeOf(STK(arg1), TARGET)) continue; + case _OP_PUSHTRAP:{ + SQInstruction *_iv = _closure(ci->_closure)->_function->_instructions; + _etraps.push_back(SQExceptionTrap(_top,_stackbase, &_iv[(ci->_ip-_iv)+arg1], arg0)); traps++; + ci->_etraps++; + } + continue; + case _OP_POPTRAP: { + for(SQInteger i = 0; i < arg0; i++) { + _etraps.pop_back(); traps--; + ci->_etraps--; + } + } + continue; + case _OP_THROW: Raise_Error(TARGET); SQ_THROW(); continue; + case _OP_NEWSLOTA: + _GUARD(NewSlotA(STK(arg1),STK(arg2),STK(arg3),(arg0&NEW_SLOT_ATTRIBUTES_FLAG) ? STK(arg2-1) : SQObjectPtr(),(arg0&NEW_SLOT_STATIC_FLAG)?true:false,false)); + continue; + case _OP_GETBASE:{ + SQClosure *clo = _closure(ci->_closure); + if(clo->_base) { + TARGET = clo->_base; + } + else { + TARGET.Null(); + } + continue; + } + case _OP_CLOSE: + if(_openouters) CloseOuters(&(STK(arg1))); + continue; + } + + } + } +exception_trap: + { + SQObjectPtr currerror = _lasterror; +// dumpstack(_stackbase); +// SQInteger n = 0; + SQInteger last_top = _top; + + if(_ss(this)->_notifyallexceptions || (!traps && raiseerror)) CallErrorHandler(currerror); + + while( ci ) { + if(ci->_etraps > 0) { + SQExceptionTrap &et = _etraps.top(); + ci->_ip = et._ip; + _top = et._stacksize; + _stackbase = et._stackbase; + _stack._vals[_stackbase + et._extarget] = currerror; + _etraps.pop_back(); traps--; ci->_etraps--; + while(last_top >= _top) _stack._vals[last_top--].Null(); + goto exception_restore; + } + else if (_debughook) { + //notify debugger of a "return" + //even if it really an exception unwinding the stack + for(SQInteger i = 0; i < ci->_ncalls; i++) { + CallDebugHook(_SC('r')); + } + } + if(ci->_generator) ci->_generator->Kill(); + bool mustbreak = ci && ci->_root; + LeaveFrame(); + if(mustbreak) break; + } + + _lasterror = currerror; + return false; + } + assert(0); +} + +bool SQVM::CreateClassInstance(SQClass *theclass, SQObjectPtr &inst, SQObjectPtr &constructor) +{ + inst = theclass->CreateInstance(); + if(!theclass->GetConstructor(constructor)) { + constructor.Null(); + } + return true; +} + +void SQVM::CallErrorHandler(SQObjectPtr &error) +{ + if(sq_type(_errorhandler) != OT_NULL) { + SQObjectPtr out; + Push(_roottable); Push(error); + Call(_errorhandler, 2, _top-2, out,SQFalse); + Pop(2); + } +} + + +void SQVM::CallDebugHook(SQInteger type,SQInteger forcedline) +{ + _debughook = false; + SQFunctionProto *func=_closure(ci->_closure)->_function; + if(_debughook_native) { + const SQChar *src = sq_type(func->_sourcename) == OT_STRING?_stringval(func->_sourcename):NULL; + const SQChar *fname = sq_type(func->_name) == OT_STRING?_stringval(func->_name):NULL; + SQInteger line = forcedline?forcedline:func->GetLine(ci->_ip); + _debughook_native(this,type,src,line,fname); + } + else { + SQObjectPtr temp_reg; + SQInteger nparams=5; + Push(_roottable); Push(type); Push(func->_sourcename); Push(forcedline?forcedline:func->GetLine(ci->_ip)); Push(func->_name); + Call(_debughook_closure,nparams,_top-nparams,temp_reg,SQFalse); + Pop(nparams); + } + _debughook = true; +} + +bool SQVM::CallNative(SQNativeClosure *nclosure, SQInteger nargs, SQInteger newbase, SQObjectPtr &retval, SQInt32 target,bool &suspend, bool &tailcall) +{ + SQInteger nparamscheck = nclosure->_nparamscheck; + SQInteger newtop = newbase + nargs + nclosure->_noutervalues; + + if (_nnativecalls + 1 > MAX_NATIVE_CALLS) { + Raise_Error(_SC("Native stack overflow")); + return false; + } + + if(nparamscheck && (((nparamscheck > 0) && (nparamscheck != nargs)) || + ((nparamscheck < 0) && (nargs < (-nparamscheck))))) + { + Raise_Error(_SC("wrong number of parameters")); + return false; + } + + SQInteger tcs; + SQIntVec &tc = nclosure->_typecheck; + if((tcs = tc.size())) { + for(SQInteger i = 0; i < nargs && i < tcs; i++) { + if((tc._vals[i] != -1) && !(sq_type(_stack._vals[newbase+i]) & tc._vals[i])) { + Raise_ParamTypeError(i,tc._vals[i], sq_type(_stack._vals[newbase+i])); + return false; + } + } + } + + if(!EnterFrame(newbase, newtop, false)) return false; + ci->_closure = nclosure; + ci->_target = target; + + SQInteger outers = nclosure->_noutervalues; + for (SQInteger i = 0; i < outers; i++) { + _stack._vals[newbase+nargs+i] = nclosure->_outervalues[i]; + } + if(nclosure->_env) { + _stack._vals[newbase] = nclosure->_env->_obj; + } + + _nnativecalls++; + SQInteger ret = (nclosure->_function)(this); + _nnativecalls--; + + suspend = false; + tailcall = false; + if (ret == SQ_TAILCALL_FLAG) { + tailcall = true; + return true; + } + else if (ret == SQ_SUSPEND_FLAG) { + suspend = true; + } + else if (ret < 0) { + LeaveFrame(); + Raise_Error(_lasterror); + return false; + } + if(ret) { + retval = _stack._vals[_top-1]; + } + else { + retval.Null(); + } + //retval = ret ? _stack._vals[_top-1] : _null_; + LeaveFrame(); + return true; +} + +bool SQVM::TailCall(SQClosure *closure, SQInteger parambase,SQInteger nparams) +{ + SQInteger last_top = _top; + SQObjectPtr clo = closure; + if (ci->_root) + { + Raise_Error("root calls cannot invoke tailcalls"); + return false; + } + for (SQInteger i = 0; i < nparams; i++) STK(i) = STK(parambase + i); + bool ret = StartCall(closure, ci->_target, nparams, _stackbase, true); + if (last_top >= _top) { + _top = last_top; + } + return ret; +} + +#define FALLBACK_OK 0 +#define FALLBACK_NO_MATCH 1 +#define FALLBACK_ERROR 2 + +bool SQVM::Get(const SQObjectPtr &self, const SQObjectPtr &key, SQObjectPtr &dest, SQUnsignedInteger getflags, SQInteger selfidx) +{ + switch(sq_type(self)){ + case OT_TABLE: + if(_table(self)->Get(key,dest))return true; + break; + case OT_ARRAY: + if (sq_isnumeric(key)) { if (_array(self)->Get(tointeger(key), dest)) { return true; } if ((getflags & GET_FLAG_DO_NOT_RAISE_ERROR) == 0) Raise_IdxError(key); return false; } + break; + case OT_INSTANCE: + if(_instance(self)->Get(key,dest)) return true; + break; + case OT_CLASS: + if(_class(self)->Get(key,dest)) return true; + break; + case OT_STRING: + if(sq_isnumeric(key)){ + SQInteger n = tointeger(key); + SQInteger len = _string(self)->_len; + if (n < 0) { n += len; } + if (n >= 0 && n < len) { + dest = SQInteger(_stringval(self)[n]); + return true; + } + if ((getflags & GET_FLAG_DO_NOT_RAISE_ERROR) == 0) Raise_IdxError(key); + return false; + } + break; + default:break; //shut up compiler + } + if ((getflags & GET_FLAG_RAW) == 0) { + switch(FallBackGet(self,key,dest)) { + case FALLBACK_OK: return true; //okie + case FALLBACK_NO_MATCH: break; //keep falling back + case FALLBACK_ERROR: return false; // the metamethod failed + } + if(InvokeDefaultDelegate(self,key,dest)) { + return true; + } + } +//#ifdef ROOT_FALLBACK + if(selfidx == 0) { + SQWeakRef *w = _closure(ci->_closure)->_root; + if(sq_type(w->_obj) != OT_NULL) + { + if(Get(*((const SQObjectPtr *)&w->_obj),key,dest,0,DONT_FALL_BACK)) return true; + } + + } +//#endif + if ((getflags & GET_FLAG_DO_NOT_RAISE_ERROR) == 0) Raise_IdxError(key); + return false; +} + +bool SQVM::InvokeDefaultDelegate(const SQObjectPtr &self,const SQObjectPtr &key,SQObjectPtr &dest) +{ + SQTable *ddel = NULL; + switch(sq_type(self)) { + case OT_CLASS: ddel = _class_ddel; break; + case OT_TABLE: ddel = _table_ddel; break; + case OT_ARRAY: ddel = _array_ddel; break; + case OT_STRING: ddel = _string_ddel; break; + case OT_INSTANCE: ddel = _instance_ddel; break; + case OT_INTEGER:case OT_FLOAT:case OT_BOOL: ddel = _number_ddel; break; + case OT_GENERATOR: ddel = _generator_ddel; break; + case OT_CLOSURE: case OT_NATIVECLOSURE: ddel = _closure_ddel; break; + case OT_THREAD: ddel = _thread_ddel; break; + case OT_WEAKREF: ddel = _weakref_ddel; break; + default: return false; + } + return ddel->Get(key,dest); +} + + +SQInteger SQVM::FallBackGet(const SQObjectPtr &self,const SQObjectPtr &key,SQObjectPtr &dest) +{ + switch(sq_type(self)){ + case OT_TABLE: + case OT_USERDATA: + //delegation + if(_delegable(self)->_delegate) { + if(Get(SQObjectPtr(_delegable(self)->_delegate),key,dest,0,DONT_FALL_BACK)) return FALLBACK_OK; + } + else { + return FALLBACK_NO_MATCH; + } + //go through + case OT_INSTANCE: { + SQObjectPtr closure; + if(_delegable(self)->GetMetaMethod(this, MT_GET, closure)) { + Push(self);Push(key); + _nmetamethodscall++; + AutoDec ad(&_nmetamethodscall); + if(Call(closure, 2, _top - 2, dest, SQFalse)) { + Pop(2); + return FALLBACK_OK; + } + else { + Pop(2); + if(sq_type(_lasterror) != OT_NULL) { //NULL means "clean failure" (not found) + return FALLBACK_ERROR; + } + } + } + } + break; + default: break;//shutup GCC 4.x + } + // no metamethod or no fallback type + return FALLBACK_NO_MATCH; +} + +bool SQVM::Set(const SQObjectPtr &self,const SQObjectPtr &key,const SQObjectPtr &val,SQInteger selfidx) +{ + switch(sq_type(self)){ + case OT_TABLE: + if(_table(self)->Set(key,val)) return true; + break; + case OT_INSTANCE: + if(_instance(self)->Set(key,val)) return true; + break; + case OT_ARRAY: + if(!sq_isnumeric(key)) { Raise_Error(_SC("indexing %s with %s"),GetTypeName(self),GetTypeName(key)); return false; } + if(!_array(self)->Set(tointeger(key),val)) { + Raise_IdxError(key); + return false; + } + return true; + case OT_USERDATA: break; // must fall back + default: + Raise_Error(_SC("trying to set '%s'"),GetTypeName(self)); + return false; + } + + switch(FallBackSet(self,key,val)) { + case FALLBACK_OK: return true; //okie + case FALLBACK_NO_MATCH: break; //keep falling back + case FALLBACK_ERROR: return false; // the metamethod failed + } + if(selfidx == 0) { + if(_table(_roottable)->Set(key,val)) + return true; + } + Raise_IdxError(key); + return false; +} + +SQInteger SQVM::FallBackSet(const SQObjectPtr &self,const SQObjectPtr &key,const SQObjectPtr &val) +{ + switch(sq_type(self)) { + case OT_TABLE: + if(_table(self)->_delegate) { + if(Set(_table(self)->_delegate,key,val,DONT_FALL_BACK)) return FALLBACK_OK; + } + //keps on going + case OT_INSTANCE: + case OT_USERDATA:{ + SQObjectPtr closure; + SQObjectPtr t; + if(_delegable(self)->GetMetaMethod(this, MT_SET, closure)) { + Push(self);Push(key);Push(val); + _nmetamethodscall++; + AutoDec ad(&_nmetamethodscall); + if(Call(closure, 3, _top - 3, t, SQFalse)) { + Pop(3); + return FALLBACK_OK; + } + else { + Pop(3); + if(sq_type(_lasterror) != OT_NULL) { //NULL means "clean failure" (not found) + return FALLBACK_ERROR; + } + } + } + } + break; + default: break;//shutup GCC 4.x + } + // no metamethod or no fallback type + return FALLBACK_NO_MATCH; +} + +bool SQVM::Clone(const SQObjectPtr &self,SQObjectPtr &target) +{ + SQObjectPtr temp_reg; + SQObjectPtr newobj; + switch(sq_type(self)){ + case OT_TABLE: + newobj = _table(self)->Clone(); + goto cloned_mt; + case OT_INSTANCE: { + newobj = _instance(self)->Clone(_ss(this)); +cloned_mt: + SQObjectPtr closure; + if(_delegable(newobj)->_delegate && _delegable(newobj)->GetMetaMethod(this,MT_CLONED,closure)) { + Push(newobj); + Push(self); + if(!CallMetaMethod(closure,MT_CLONED,2,temp_reg)) + return false; + } + } + target = newobj; + return true; + case OT_ARRAY: + target = _array(self)->Clone(); + return true; + default: + Raise_Error(_SC("cloning a %s"), GetTypeName(self)); + return false; + } +} + +bool SQVM::NewSlotA(const SQObjectPtr &self,const SQObjectPtr &key,const SQObjectPtr &val,const SQObjectPtr &attrs,bool bstatic,bool raw) +{ + if(sq_type(self) != OT_CLASS) { + Raise_Error(_SC("object must be a class")); + return false; + } + SQClass *c = _class(self); + if(!raw) { + SQObjectPtr &mm = c->_metamethods[MT_NEWMEMBER]; + if(sq_type(mm) != OT_NULL ) { + Push(self); Push(key); Push(val); + Push(attrs); + Push(bstatic); + return CallMetaMethod(mm,MT_NEWMEMBER,5,temp_reg); + } + } + if(!NewSlot(self, key, val,bstatic)) + return false; + if(sq_type(attrs) != OT_NULL) { + c->SetAttributes(key,attrs); + } + return true; +} + +bool SQVM::NewSlot(const SQObjectPtr &self,const SQObjectPtr &key,const SQObjectPtr &val,bool bstatic) +{ + if(sq_type(key) == OT_NULL) { Raise_Error(_SC("null cannot be used as index")); return false; } + switch(sq_type(self)) { + case OT_TABLE: { + bool rawcall = true; + if(_table(self)->_delegate) { + SQObjectPtr res; + if(!_table(self)->Get(key,res)) { + SQObjectPtr closure; + if(_delegable(self)->_delegate && _delegable(self)->GetMetaMethod(this,MT_NEWSLOT,closure)) { + Push(self);Push(key);Push(val); + if(!CallMetaMethod(closure,MT_NEWSLOT,3,res)) { + return false; + } + rawcall = false; + } + else { + rawcall = true; + } + } + } + if(rawcall) _table(self)->NewSlot(key,val); //cannot fail + + break;} + case OT_INSTANCE: { + SQObjectPtr res; + SQObjectPtr closure; + if(_delegable(self)->_delegate && _delegable(self)->GetMetaMethod(this,MT_NEWSLOT,closure)) { + Push(self);Push(key);Push(val); + if(!CallMetaMethod(closure,MT_NEWSLOT,3,res)) { + return false; + } + break; + } + Raise_Error(_SC("class instances do not support the new slot operator")); + return false; + break;} + case OT_CLASS: + if(!_class(self)->NewSlot(_ss(this),key,val,bstatic)) { + if(_class(self)->_locked) { + Raise_Error(_SC("trying to modify a class that has already been instantiated")); + return false; + } + else { + SQObjectPtr oval = PrintObjVal(key); + Raise_Error(_SC("the property '%s' already exists"),_stringval(oval)); + return false; + } + } + break; + default: + Raise_Error(_SC("indexing %s with %s"),GetTypeName(self),GetTypeName(key)); + return false; + break; + } + return true; +} + + + +bool SQVM::DeleteSlot(const SQObjectPtr &self,const SQObjectPtr &key,SQObjectPtr &res) +{ + switch(sq_type(self)) { + case OT_TABLE: + case OT_INSTANCE: + case OT_USERDATA: { + SQObjectPtr t; + //bool handled = false; + SQObjectPtr closure; + if(_delegable(self)->_delegate && _delegable(self)->GetMetaMethod(this,MT_DELSLOT,closure)) { + Push(self);Push(key); + return CallMetaMethod(closure,MT_DELSLOT,2,res); + } + else { + if(sq_type(self) == OT_TABLE) { + if(_table(self)->Get(key,t)) { + _table(self)->Remove(key); + } + else { + Raise_IdxError((const SQObject &)key); + return false; + } + } + else { + Raise_Error(_SC("cannot delete a slot from %s"),GetTypeName(self)); + return false; + } + } + res = t; + } + break; + default: + Raise_Error(_SC("attempt to delete a slot from a %s"),GetTypeName(self)); + return false; + } + return true; +} + +bool SQVM::Call(SQObjectPtr &closure,SQInteger nparams,SQInteger stackbase,SQObjectPtr &outres,SQBool raiseerror) +{ +#ifdef _DEBUG +SQInteger prevstackbase = _stackbase; +#endif + switch(sq_type(closure)) { + case OT_CLOSURE: + return Execute(closure, nparams, stackbase, outres, raiseerror); + break; + case OT_NATIVECLOSURE:{ + bool dummy; + return CallNative(_nativeclosure(closure), nparams, stackbase, outres, -1, dummy, dummy); + + } + break; + case OT_CLASS: { + SQObjectPtr constr; + SQObjectPtr temp; + CreateClassInstance(_class(closure),outres,constr); + SQObjectType ctype = sq_type(constr); + if (ctype == OT_NATIVECLOSURE || ctype == OT_CLOSURE) { + _stack[stackbase] = outres; + return Call(constr,nparams,stackbase,temp,raiseerror); + } + return true; + } + break; + default: + Raise_Error(_SC("attempt to call '%s'"), GetTypeName(closure)); + return false; + } +#ifdef _DEBUG + if(!_suspended) { + assert(_stackbase == prevstackbase); + } +#endif + return true; +} + +bool SQVM::CallMetaMethod(SQObjectPtr &closure,SQMetaMethod SQ_UNUSED_ARG(mm),SQInteger nparams,SQObjectPtr &outres) +{ + //SQObjectPtr closure; + + _nmetamethodscall++; + if(Call(closure, nparams, _top - nparams, outres, SQFalse)) { + _nmetamethodscall--; + Pop(nparams); + return true; + } + _nmetamethodscall--; + //} + Pop(nparams); + return false; +} + +void SQVM::FindOuter(SQObjectPtr &target, SQObjectPtr *stackindex) +{ + SQOuter **pp = &_openouters; + SQOuter *p; + SQOuter *otr; + + while ((p = *pp) != NULL && p->_valptr >= stackindex) { + if (p->_valptr == stackindex) { + target = SQObjectPtr(p); + return; + } + pp = &p->_next; + } + otr = SQOuter::Create(_ss(this), stackindex); + otr->_next = *pp; + otr->_idx = (stackindex - _stack._vals); + __ObjAddRef(otr); + *pp = otr; + target = SQObjectPtr(otr); +} + +bool SQVM::EnterFrame(SQInteger newbase, SQInteger newtop, bool tailcall) +{ + if( !tailcall ) { + if( _callsstacksize == _alloccallsstacksize ) { + GrowCallStack(); + } + ci = &_callsstack[_callsstacksize++]; + ci->_prevstkbase = (SQInt32)(newbase - _stackbase); + ci->_prevtop = (SQInt32)(_top - _stackbase); + ci->_etraps = 0; + ci->_ncalls = 1; + ci->_generator = NULL; + ci->_root = SQFalse; + } + else { + ci->_ncalls++; + } + + _stackbase = newbase; + _top = newtop; + if(newtop + MIN_STACK_OVERHEAD > (SQInteger)_stack.size()) { + if(_nmetamethodscall) { + Raise_Error(_SC("stack overflow, cannot resize stack while in a metamethod")); + return false; + } + _stack.resize(newtop + (MIN_STACK_OVERHEAD << 2)); + RelocateOuters(); + } + return true; +} + +void SQVM::LeaveFrame() { + SQInteger last_top = _top; + SQInteger last_stackbase = _stackbase; + SQInteger css = --_callsstacksize; + + /* First clean out the call stack frame */ + ci->_closure.Null(); + _stackbase -= ci->_prevstkbase; + _top = _stackbase + ci->_prevtop; + ci = (css) ? &_callsstack[css-1] : NULL; + + if(_openouters) CloseOuters(&(_stack._vals[last_stackbase])); + while (last_top >= _top) { + _stack._vals[last_top--].Null(); + } +} + +void SQVM::RelocateOuters() +{ + SQOuter *p = _openouters; + while (p) { + p->_valptr = _stack._vals + p->_idx; + p = p->_next; + } +} + +void SQVM::CloseOuters(SQObjectPtr *stackindex) { + SQOuter *p; + while ((p = _openouters) != NULL && p->_valptr >= stackindex) { + p->_value = *(p->_valptr); + p->_valptr = &p->_value; + _openouters = p->_next; + __ObjRelease(p); + } +} + +void SQVM::Remove(SQInteger n) { + n = (n >= 0)?n + _stackbase - 1:_top + n; + for(SQInteger i = n; i < _top; i++){ + _stack[i] = _stack[i+1]; + } + _stack[_top].Null(); + _top--; +} + +void SQVM::Pop() { + _stack[--_top].Null(); +} + +void SQVM::Pop(SQInteger n) { + for(SQInteger i = 0; i < n; i++){ + _stack[--_top].Null(); + } +} + +void SQVM::PushNull() { _stack[_top++].Null(); } +void SQVM::Push(const SQObjectPtr &o) { _stack[_top++] = o; } +SQObjectPtr &SQVM::Top() { return _stack[_top-1]; } +SQObjectPtr &SQVM::PopGet() { return _stack[--_top]; } +SQObjectPtr &SQVM::GetUp(SQInteger n) { return _stack[_top+n]; } +SQObjectPtr &SQVM::GetAt(SQInteger n) { return _stack[n]; } + +#ifdef _DEBUG_DUMP +void SQVM::dumpstack(SQInteger stackbase,bool dumpall) +{ + SQInteger size=dumpall?_stack.size():_top; + SQInteger n=0; + scprintf(_SC("\n>>>>stack dump<<<<\n")); + CallInfo &ci=_callsstack[_callsstacksize-1]; + scprintf(_SC("IP: %p\n"),ci._ip); + scprintf(_SC("prev stack base: %d\n"),ci._prevstkbase); + scprintf(_SC("prev top: %d\n"),ci._prevtop); + for(SQInteger i=0;i"));else scprintf(_SC(" ")); + scprintf(_SC("[" _PRINT_INT_FMT "]:"),n); + switch(sq_type(obj)){ + case OT_FLOAT: scprintf(_SC("FLOAT %.3f"),_float(obj));break; + case OT_INTEGER: scprintf(_SC("INTEGER " _PRINT_INT_FMT),_integer(obj));break; + case OT_BOOL: scprintf(_SC("BOOL %s"),_integer(obj)?"true":"false");break; + case OT_STRING: scprintf(_SC("STRING %s"),_stringval(obj));break; + case OT_NULL: scprintf(_SC("NULL")); break; + case OT_TABLE: scprintf(_SC("TABLE %p[%p]"),_table(obj),_table(obj)->_delegate);break; + case OT_ARRAY: scprintf(_SC("ARRAY %p"),_array(obj));break; + case OT_CLOSURE: scprintf(_SC("CLOSURE [%p]"),_closure(obj));break; + case OT_NATIVECLOSURE: scprintf(_SC("NATIVECLOSURE"));break; + case OT_USERDATA: scprintf(_SC("USERDATA %p[%p]"),_userdataval(obj),_userdata(obj)->_delegate);break; + case OT_GENERATOR: scprintf(_SC("GENERATOR %p"),_generator(obj));break; + case OT_THREAD: scprintf(_SC("THREAD [%p]"),_thread(obj));break; + case OT_USERPOINTER: scprintf(_SC("USERPOINTER %p"),_userpointer(obj));break; + case OT_CLASS: scprintf(_SC("CLASS %p"),_class(obj));break; + case OT_INSTANCE: scprintf(_SC("INSTANCE %p"),_instance(obj));break; + case OT_WEAKREF: scprintf(_SC("WEAKREF %p"),_weakref(obj));break; + default: + assert(0); + break; + }; + scprintf(_SC("\n")); + ++n; + } +} + + + +#endif diff --git a/src/vscript/squirrel/squirrel/sqvm.h b/src/vscript/squirrel/squirrel/sqvm.h new file mode 100644 index 00000000..a75524da --- /dev/null +++ b/src/vscript/squirrel/squirrel/sqvm.h @@ -0,0 +1,213 @@ +/* see copyright notice in squirrel.h */ +#ifndef _SQVM_H_ +#define _SQVM_H_ + +#include "sqopcodes.h" +#include "sqobject.h" +#define MAX_NATIVE_CALLS 100 +#define MIN_STACK_OVERHEAD 15 + +#define SQ_SUSPEND_FLAG -666 +#define SQ_TAILCALL_FLAG -777 +#define DONT_FALL_BACK 666 +//#define EXISTS_FALL_BACK -1 + +#define GET_FLAG_RAW 0x00000001 +#define GET_FLAG_DO_NOT_RAISE_ERROR 0x00000002 +//base lib +void sq_base_register(HSQUIRRELVM v); + +struct SQExceptionTrap{ + SQExceptionTrap() {} + SQExceptionTrap(SQInteger ss, SQInteger stackbase,SQInstruction *ip, SQInteger ex_target){ _stacksize = ss; _stackbase = stackbase; _ip = ip; _extarget = ex_target;} + SQExceptionTrap(const SQExceptionTrap &et) { (*this) = et; } + SQInteger _stackbase; + SQInteger _stacksize; + SQInstruction *_ip; + SQInteger _extarget; +}; + +#define _INLINE + +typedef sqvector ExceptionsTraps; + +struct SQVM : public CHAINABLE_OBJ +{ + struct CallInfo{ + //CallInfo() { _generator = NULL;} + SQInstruction *_ip; + SQObjectPtr *_literals; + SQObjectPtr _closure; + SQGenerator *_generator; + SQInt32 _etraps; + SQInt32 _prevstkbase; + SQInt32 _prevtop; + SQInt32 _target; + SQInt32 _ncalls; + SQBool _root; + }; + +typedef sqvector CallInfoVec; +public: + void DebugHookProxy(SQInteger type, const SQChar * sourcename, SQInteger line, const SQChar * funcname); + static void _DebugHookProxy(HSQUIRRELVM v, SQInteger type, const SQChar * sourcename, SQInteger line, const SQChar * funcname); + enum ExecutionType { ET_CALL, ET_RESUME_GENERATOR, ET_RESUME_VM,ET_RESUME_THROW_VM }; + SQVM(SQSharedState *ss); + ~SQVM(); + bool Init(SQVM *friendvm, SQInteger stacksize); + bool Execute(SQObjectPtr &func, SQInteger nargs, SQInteger stackbase, SQObjectPtr &outres, SQBool raiseerror, ExecutionType et = ET_CALL); + //starts a native call return when the NATIVE closure returns + bool CallNative(SQNativeClosure *nclosure, SQInteger nargs, SQInteger newbase, SQObjectPtr &retval, SQInt32 target, bool &suspend,bool &tailcall); + bool TailCall(SQClosure *closure, SQInteger firstparam, SQInteger nparams); + //starts a SQUIRREL call in the same "Execution loop" + bool StartCall(SQClosure *closure, SQInteger target, SQInteger nargs, SQInteger stackbase, bool tailcall); + bool CreateClassInstance(SQClass *theclass, SQObjectPtr &inst, SQObjectPtr &constructor); + //call a generic closure pure SQUIRREL or NATIVE + bool Call(SQObjectPtr &closure, SQInteger nparams, SQInteger stackbase, SQObjectPtr &outres,SQBool raiseerror); + SQRESULT Suspend(); + + void CallDebugHook(SQInteger type,SQInteger forcedline=0); + void CallErrorHandler(SQObjectPtr &e); + bool Get(const SQObjectPtr &self, const SQObjectPtr &key, SQObjectPtr &dest, SQUnsignedInteger getflags, SQInteger selfidx); + SQInteger FallBackGet(const SQObjectPtr &self,const SQObjectPtr &key,SQObjectPtr &dest); + bool InvokeDefaultDelegate(const SQObjectPtr &self,const SQObjectPtr &key,SQObjectPtr &dest); + bool Set(const SQObjectPtr &self, const SQObjectPtr &key, const SQObjectPtr &val, SQInteger selfidx); + SQInteger FallBackSet(const SQObjectPtr &self,const SQObjectPtr &key,const SQObjectPtr &val); + bool NewSlot(const SQObjectPtr &self, const SQObjectPtr &key, const SQObjectPtr &val,bool bstatic); + bool NewSlotA(const SQObjectPtr &self,const SQObjectPtr &key,const SQObjectPtr &val,const SQObjectPtr &attrs,bool bstatic,bool raw); + bool DeleteSlot(const SQObjectPtr &self, const SQObjectPtr &key, SQObjectPtr &res); + bool Clone(const SQObjectPtr &self, SQObjectPtr &target); + bool ObjCmp(const SQObjectPtr &o1, const SQObjectPtr &o2,SQInteger &res); + bool StringCat(const SQObjectPtr &str, const SQObjectPtr &obj, SQObjectPtr &dest); + static bool IsEqual(const SQObjectPtr &o1,const SQObjectPtr &o2,bool &res); + bool ToString(const SQObjectPtr &o,SQObjectPtr &res); + SQString *PrintObjVal(const SQObjectPtr &o); + + + void Raise_Error(const SQChar *s, ...); + void Raise_Error(const SQObjectPtr &desc); + void Raise_IdxError(const SQObjectPtr &o); + void Raise_CompareError(const SQObject &o1, const SQObject &o2); + void Raise_ParamTypeError(SQInteger nparam,SQInteger typemask,SQInteger type); + + void FindOuter(SQObjectPtr &target, SQObjectPtr *stackindex); + void RelocateOuters(); + void CloseOuters(SQObjectPtr *stackindex); + + bool TypeOf(const SQObjectPtr &obj1, SQObjectPtr &dest); + bool CallMetaMethod(SQObjectPtr &closure, SQMetaMethod mm, SQInteger nparams, SQObjectPtr &outres); + bool ArithMetaMethod(SQInteger op, const SQObjectPtr &o1, const SQObjectPtr &o2, SQObjectPtr &dest); + bool Return(SQInteger _arg0, SQInteger _arg1, SQObjectPtr &retval); + //new stuff + _INLINE bool ARITH_OP(SQUnsignedInteger op,SQObjectPtr &trg,const SQObjectPtr &o1,const SQObjectPtr &o2); + _INLINE bool BW_OP(SQUnsignedInteger op,SQObjectPtr &trg,const SQObjectPtr &o1,const SQObjectPtr &o2); + _INLINE bool NEG_OP(SQObjectPtr &trg,const SQObjectPtr &o1); + _INLINE bool CMP_OP(CmpOP op, const SQObjectPtr &o1,const SQObjectPtr &o2,SQObjectPtr &res); + bool CLOSURE_OP(SQObjectPtr &target, SQFunctionProto *func); + bool CLASS_OP(SQObjectPtr &target,SQInteger base,SQInteger attrs); + //return true if the loop is finished + bool FOREACH_OP(SQObjectPtr &o1,SQObjectPtr &o2,SQObjectPtr &o3,SQObjectPtr &o4,SQInteger arg_2,int exitpos,int &jump); + //_INLINE bool LOCAL_INC(SQInteger op,SQObjectPtr &target, SQObjectPtr &a, SQObjectPtr &incr); + _INLINE bool PLOCAL_INC(SQInteger op,SQObjectPtr &target, SQObjectPtr &a, SQObjectPtr &incr); + _INLINE bool DerefInc(SQInteger op,SQObjectPtr &target, SQObjectPtr &self, SQObjectPtr &key, SQObjectPtr &incr, bool postfix,SQInteger arg0); +#ifdef _DEBUG_DUMP + void dumpstack(SQInteger stackbase=-1, bool dumpall = false); +#endif + +#ifndef NO_GARBAGE_COLLECTOR + void Mark(SQCollectable **chain); + SQObjectType GetType() {return OT_THREAD;} +#endif + void Finalize(); + void GrowCallStack() { + SQInteger newsize = _alloccallsstacksize*2; + _callstackdata.resize(newsize); + _callsstack = &_callstackdata[0]; + _alloccallsstacksize = newsize; + } + bool EnterFrame(SQInteger newbase, SQInteger newtop, bool tailcall); + void LeaveFrame(); + void Release(){ sq_delete(this,SQVM); } +//////////////////////////////////////////////////////////////////////////// + //stack functions for the api + void Remove(SQInteger n); + + static bool IsFalse(SQObjectPtr &o); + + void Pop(); + void Pop(SQInteger n); + void Push(const SQObjectPtr &o); + void PushNull(); + SQObjectPtr &Top(); + SQObjectPtr &PopGet(); + SQObjectPtr &GetUp(SQInteger n); + SQObjectPtr &GetAt(SQInteger n); + + SQObjectPtrVec _stack; + + SQInteger _top; + SQInteger _stackbase; + SQOuter *_openouters; + SQObjectPtr _roottable; + SQObjectPtr _lasterror; + SQObjectPtr _errorhandler; + + bool _debughook; + SQDEBUGHOOK _debughook_native; + SQObjectPtr _debughook_closure; + + SQObjectPtr temp_reg; + + + CallInfo* _callsstack; + SQInteger _callsstacksize; + SQInteger _alloccallsstacksize; + sqvector _callstackdata; + + ExceptionsTraps _etraps; + CallInfo *ci; + SQUserPointer _foreignptr; + //VMs sharing the same state + SQSharedState *_sharedstate; + SQInteger _nnativecalls; + SQInteger _nmetamethodscall; + SQRELEASEHOOK _releasehook; + //suspend infos + SQBool _suspended; + SQBool _suspended_root; + SQInteger _suspended_target; + SQInteger _suspended_traps; +}; + +struct AutoDec{ + AutoDec(SQInteger *n) { _n = n; } + ~AutoDec() { (*_n)--; } + SQInteger *_n; +}; + +inline SQObjectPtr &stack_get(HSQUIRRELVM v,SQInteger idx){return ((idx>=0)?(v->GetAt(idx+v->_stackbase-1)):(v->GetUp(idx)));} + +#define _ss(_vm_) (_vm_)->_sharedstate + +#ifndef NO_GARBAGE_COLLECTOR +#define _opt_ss(_vm_) (_vm_)->_sharedstate +#else +#define _opt_ss(_vm_) NULL +#endif + +#define PUSH_CALLINFO(v,nci){ \ + SQInteger css = v->_callsstacksize; \ + if(css == v->_alloccallsstacksize) { \ + v->GrowCallStack(); \ + } \ + v->ci = &v->_callsstack[css]; \ + *(v->ci) = nci; \ + v->_callsstacksize++; \ +} + +#define POP_CALLINFO(v){ \ + SQInteger css = --v->_callsstacksize; \ + v->ci->_closure.Null(); \ + v->ci = css?&v->_callsstack[css-1]:NULL; \ +} +#endif //_SQVM_H_ diff --git a/src/vscript/vscript.cpp b/src/vscript/vscript.cpp new file mode 100644 index 00000000..af1570ad --- /dev/null +++ b/src/vscript/vscript.cpp @@ -0,0 +1,82 @@ +//========= Mapbase - https://github.com/mapbase-source/source-sdk-2013 ============// +// +// Purpose: Custom implementation of VScript in Source 2013, created from scratch +// using the Alien Swarm SDK as a reference for Valve's library. +// +// Author(s): ReDucTor (header written by Blixibon) +// +// $NoKeywords: $ +//=============================================================================// + +#include "vscript/ivscript.h" + +#include "vscript_bindings_base.h" + +#include "tier1/tier1.h" + +IScriptVM* makeSquirrelVM(); + +int vscript_token = 0; + +class CScriptManager : public CTier1AppSystem +{ +public: + virtual IScriptVM* CreateVM(ScriptLanguage_t language) override + { + IScriptVM* pScriptVM = nullptr; + if (language == SL_SQUIRREL) + { + pScriptVM = makeSquirrelVM(); + } + else + { + return nullptr; + } + + if (pScriptVM == nullptr) + { + return nullptr; + } + + if (!pScriptVM->Init()) + { + delete pScriptVM; + return nullptr; + } + + // Register base bindings for all VMs + RegisterBaseBindings( pScriptVM ); + + return pScriptVM; + } + + virtual void DestroyVM(IScriptVM * pScriptVM) override + { + if (pScriptVM) + { + pScriptVM->Shutdown(); + delete pScriptVM; + } + } + + // Mapbase moves CScriptKeyValues into the library so it could be used elsewhere + virtual HSCRIPT CreateScriptKeyValues( IScriptVM *pVM, KeyValues *pKV, bool bAllowDestruct ) override + { + CScriptKeyValues *pSKV = new CScriptKeyValues( pKV ); + HSCRIPT hSKV = pVM->RegisterInstance( pSKV, bAllowDestruct ); + return hSKV; + } + + virtual KeyValues *GetKeyValuesFromScriptKV( IScriptVM *pVM, HSCRIPT hSKV ) override + { + CScriptKeyValues *pSKV = (hSKV ? (CScriptKeyValues*)pVM->GetInstanceValue( hSKV, GetScriptDesc( (CScriptKeyValues*)NULL ) ) : nullptr); + if (pSKV) + { + return pSKV->m_pKeyValues; + } + + return nullptr; + } +}; + +EXPOSE_SINGLE_INTERFACE(CScriptManager, IScriptManager, VSCRIPT_INTERFACE_VERSION); \ No newline at end of file diff --git a/src/vscript/vscript.vpc b/src/vscript/vscript.vpc new file mode 100644 index 00000000..425afb8a --- /dev/null +++ b/src/vscript/vscript.vpc @@ -0,0 +1,69 @@ +//----------------------------------------------------------------------------- +// VSCRIPT.VPC +// +// Project Script +//----------------------------------------------------------------------------- + +$macro SRCDIR ".." + +$include "$SRCDIR\vpc_scripts\source_lib_base.vpc" + +$Configuration +{ + $Compiler + { + $AdditionalIncludeDirectories "$BASE;.\squirrel\include" + $PreprocessorDefinitions "$BASE;MAPBASE_VSCRIPT" [$MAPBASE_VSCRIPT] + } +} + +$Project "VScript" +{ + $Folder "Source Files" + { + + $File "vscript.cpp" + $File "vscript_squirrel.cpp" + $File "vscript_squirrel.nut" + + $File "vscript_bindings_base.cpp" + $File "vscript_bindings_base.h" + $File "vscript_bindings_math.cpp" + $File "vscript_bindings_math.h" + + $Folder "squirrel" + { + $File ".\squirrel\sqstdlib\sqstdaux.cpp" \ + ".\squirrel\sqstdlib\sqstdblob.cpp" \ + ".\squirrel\sqstdlib\sqstdio.cpp" \ + ".\squirrel\sqstdlib\sqstdmath.cpp" \ + ".\squirrel\sqstdlib\sqstdrex.cpp" \ + ".\squirrel\sqstdlib\sqstdstream.cpp" \ + ".\squirrel\sqstdlib\sqstdstring.cpp" \ + ".\squirrel\sqstdlib\sqstdsystem.cpp" \ + ".\squirrel\squirrel\sqapi.cpp" \ + ".\squirrel\squirrel\sqbaselib.cpp" \ + ".\squirrel\squirrel\sqclass.cpp" \ + ".\squirrel\squirrel\sqcompiler.cpp" \ + ".\squirrel\squirrel\sqdebug.cpp" \ + ".\squirrel\squirrel\sqfuncstate.cpp" \ + ".\squirrel\squirrel\sqlexer.cpp" \ + ".\squirrel\squirrel\sqmem.cpp" \ + ".\squirrel\squirrel\sqobject.cpp" \ + ".\squirrel\squirrel\sqstate.cpp" \ + ".\squirrel\squirrel\sqtable.cpp" \ + ".\squirrel\squirrel\sqvm.cpp" + { + $Configuration + { + $Compiler + { + // Squirrel third party library is full of warnings + $AdditionalOptions "$BASE /wd4100 /wd4611 /wd4127 /wd4244 /wd4702 /wd4706 /wd4800" + $TreatWarningsAsErrors "No" + } + } + } + } + } +} diff --git a/src/vscript/vscript_bindings_base.cpp b/src/vscript/vscript_bindings_base.cpp new file mode 100644 index 00000000..99b6194a --- /dev/null +++ b/src/vscript/vscript_bindings_base.cpp @@ -0,0 +1,591 @@ +//========= Mapbase - https://github.com/mapbase-source/source-sdk-2013 ============// +// +// Purpose: VScript functions, constants, etc. registered within the library itself. +// +// This is for things which don't have to depend on server/client and can be accessed +// from anywhere. +// +// $NoKeywords: $ +//=============================================================================// + +#include "vscript/ivscript.h" + +#include "tier1/tier1.h" +#include "tier1/fmtstr.h" + +#include +#include "icommandline.h" +#include "worldsize.h" +#include "bspflags.h" + +#include + +#include "vscript_bindings_base.h" +#include "vscript_bindings_math.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +extern IScriptManager *scriptmanager; + +//============================================================================= +// +// Prints +// +//============================================================================= +static void ScriptMsg( const char *msg ) +{ + Msg( "%s", msg ); +} + +static void ScriptColorPrint( int r, int g, int b, const char *pszMsg ) +{ + const Color clr(r, g, b, 255); + ConColorMsg( clr, "%s", pszMsg ); +} + +static void ScriptColorPrintL( int r, int g, int b, const char *pszMsg ) +{ + const Color clr(r, g, b, 255); + ConColorMsg( clr, "%s\n", pszMsg ); +} + + +//============================================================================= +// +// Command Line +// +//============================================================================= +class CGlobalSys +{ +public: + const char* ScriptGetCommandLine() + { + return CommandLine()->GetCmdLine(); + } + + bool CommandLineCheck(const char* name) + { + return !!CommandLine()->FindParm(name); + } + + const char* CommandLineCheckStr(const char* name) + { + return CommandLine()->ParmValue(name); + } + + float CommandLineCheckFloat(const char* name) + { + return CommandLine()->ParmValue(name, 0); + } + + int CommandLineCheckInt(const char* name) + { + return CommandLine()->ParmValue(name, 0); + } +} g_ScriptGlobalSys; + +BEGIN_SCRIPTDESC_ROOT_NAMED( CGlobalSys, "CGlobalSys", SCRIPT_SINGLETON "GlobalSys" ) + DEFINE_SCRIPTFUNC_NAMED( ScriptGetCommandLine, "GetCommandLine", "returns the command line" ) + DEFINE_SCRIPTFUNC( CommandLineCheck, "returns true if the command line param was used, otherwise false." ) + DEFINE_SCRIPTFUNC( CommandLineCheckStr, "returns the command line param as a string." ) + DEFINE_SCRIPTFUNC( CommandLineCheckFloat, "returns the command line param as a float." ) + DEFINE_SCRIPTFUNC( CommandLineCheckInt, "returns the command line param as an int." ) +END_SCRIPTDESC(); + +// ---------------------------------------------------------------------------- +// KeyValues access - CBaseEntity::ScriptGetKeyFromModel returns root KeyValues +// ---------------------------------------------------------------------------- +BEGIN_SCRIPTDESC_ROOT( CScriptKeyValues, "Wrapper class over KeyValues instance" ) + DEFINE_SCRIPT_CONSTRUCTOR() + DEFINE_SCRIPTFUNC_NAMED( ScriptFindKey, "FindKey", "Given a KeyValues object and a key name, find a KeyValues object associated with the key name" ); + DEFINE_SCRIPTFUNC_NAMED( ScriptGetFirstSubKey, "GetFirstSubKey", "Given a KeyValues object, return the first sub key object" ); + DEFINE_SCRIPTFUNC_NAMED( ScriptGetNextKey, "GetNextKey", "Given a KeyValues object, return the next key object in a sub key group" ); + DEFINE_SCRIPTFUNC_NAMED( ScriptGetKeyValueInt, "GetKeyInt", "Given a KeyValues object and a key name, return associated integer value" ); + DEFINE_SCRIPTFUNC_NAMED( ScriptGetKeyValueFloat, "GetKeyFloat", "Given a KeyValues object and a key name, return associated float value" ); + DEFINE_SCRIPTFUNC_NAMED( ScriptGetKeyValueBool, "GetKeyBool", "Given a KeyValues object and a key name, return associated bool value" ); + DEFINE_SCRIPTFUNC_NAMED( ScriptGetKeyValueString, "GetKeyString", "Given a KeyValues object and a key name, return associated string value" ); + DEFINE_SCRIPTFUNC_NAMED( ScriptIsKeyValueEmpty, "IsKeyEmpty", "Given a KeyValues object and a key name, return true if key name has no value" ); + DEFINE_SCRIPTFUNC_NAMED( ScriptReleaseKeyValues, "ReleaseKeyValues", "Given a root KeyValues object, release its contents" ); + + DEFINE_SCRIPTFUNC( TableToSubKeys, "Converts a script table to KeyValues." ); + DEFINE_SCRIPTFUNC( SubKeysToTable, "Converts to script table." ); + + DEFINE_SCRIPTFUNC_NAMED( ScriptFindOrCreateKey, "FindOrCreateKey", "Given a KeyValues object and a key name, find or create a KeyValues object associated with the key name" ); + + DEFINE_SCRIPTFUNC_NAMED( ScriptGetName, "GetName", "Given a KeyValues object, return its name" ); + DEFINE_SCRIPTFUNC_NAMED( ScriptGetInt, "GetInt", "Given a KeyValues object, return its own associated integer value" ); + DEFINE_SCRIPTFUNC_NAMED( ScriptGetFloat, "GetFloat", "Given a KeyValues object, return its own associated float value" ); + DEFINE_SCRIPTFUNC_NAMED( ScriptGetString, "GetString", "Given a KeyValues object, return its own associated string value" ); + DEFINE_SCRIPTFUNC_NAMED( ScriptGetBool, "GetBool", "Given a KeyValues object, return its own associated bool value" ); + + DEFINE_SCRIPTFUNC_NAMED( ScriptSetKeyValueInt, "SetKeyInt", "Given a KeyValues object and a key name, set associated integer value" ); + DEFINE_SCRIPTFUNC_NAMED( ScriptSetKeyValueFloat, "SetKeyFloat", "Given a KeyValues object and a key name, set associated float value" ); + DEFINE_SCRIPTFUNC_NAMED( ScriptSetKeyValueBool, "SetKeyBool", "Given a KeyValues object and a key name, set associated bool value" ); + DEFINE_SCRIPTFUNC_NAMED( ScriptSetKeyValueString, "SetKeyString", "Given a KeyValues object and a key name, set associated string value" ); + + DEFINE_SCRIPTFUNC_NAMED( ScriptSetName, "SetName", "Given a KeyValues object, set its name" ); + DEFINE_SCRIPTFUNC_NAMED( ScriptSetInt, "SetInt", "Given a KeyValues object, set its own associated integer value" ); + DEFINE_SCRIPTFUNC_NAMED( ScriptSetFloat, "SetFloat", "Given a KeyValues object, set its own associated float value" ); + DEFINE_SCRIPTFUNC_NAMED( ScriptSetBool, "SetBool", "Given a KeyValues object, set its own associated bool value" ); + DEFINE_SCRIPTFUNC_NAMED( ScriptSetString, "SetString", "Given a KeyValues object, set its own associated string value" ); +END_SCRIPTDESC(); + +HSCRIPT CScriptKeyValues::ScriptFindKey( const char *pszName ) +{ + KeyValues *pKeyValues = m_pKeyValues->FindKey(pszName); + if ( pKeyValues == NULL ) + return NULL; + + CScriptKeyValues *pScriptKey = new CScriptKeyValues( pKeyValues ); + + // UNDONE: who calls ReleaseInstance on this?? + HSCRIPT hScriptInstance = g_pScriptVM->RegisterInstance( pScriptKey ); + return hScriptInstance; +} + +HSCRIPT CScriptKeyValues::ScriptGetFirstSubKey( void ) +{ + KeyValues *pKeyValues = m_pKeyValues->GetFirstSubKey(); + if ( pKeyValues == NULL ) + return NULL; + + CScriptKeyValues *pScriptKey = new CScriptKeyValues( pKeyValues ); + + // UNDONE: who calls ReleaseInstance on this?? + HSCRIPT hScriptInstance = g_pScriptVM->RegisterInstance( pScriptKey ); + return hScriptInstance; +} + +HSCRIPT CScriptKeyValues::ScriptGetNextKey( void ) +{ + KeyValues *pKeyValues = m_pKeyValues->GetNextKey(); + if ( pKeyValues == NULL ) + return NULL; + + CScriptKeyValues *pScriptKey = new CScriptKeyValues( pKeyValues ); + + // UNDONE: who calls ReleaseInstance on this?? + HSCRIPT hScriptInstance = g_pScriptVM->RegisterInstance( pScriptKey ); + return hScriptInstance; +} + +int CScriptKeyValues::ScriptGetKeyValueInt( const char *pszName ) +{ + int i = m_pKeyValues->GetInt( pszName ); + return i; +} + +float CScriptKeyValues::ScriptGetKeyValueFloat( const char *pszName ) +{ + float f = m_pKeyValues->GetFloat( pszName ); + return f; +} + +const char *CScriptKeyValues::ScriptGetKeyValueString( const char *pszName ) +{ + const char *psz = m_pKeyValues->GetString( pszName ); + return psz; +} + +bool CScriptKeyValues::ScriptIsKeyValueEmpty( const char *pszName ) +{ + bool b = m_pKeyValues->IsEmpty( pszName ); + return b; +} + +bool CScriptKeyValues::ScriptGetKeyValueBool( const char *pszName ) +{ + bool b = m_pKeyValues->GetBool( pszName ); + return b; +} + +void CScriptKeyValues::ScriptReleaseKeyValues( ) +{ + m_pKeyValues->deleteThis(); + m_pKeyValues = NULL; +} + +void KeyValues_TableToSubKeys( HSCRIPT hTable, KeyValues *pKV ) +{ + int nIterator = -1; + ScriptVariant_t varKey, varValue; + while ((nIterator = g_pScriptVM->GetKeyValue( hTable, nIterator, &varKey, &varValue )) != -1) + { + if ( varKey.m_type == FIELD_CSTRING ) + { + switch ( varValue.m_type ) + { + case FIELD_CSTRING: pKV->SetString( varKey.m_pszString, varValue.m_pszString ); break; + case FIELD_INTEGER: pKV->SetInt( varKey.m_pszString, varValue.m_int ); break; + case FIELD_FLOAT: pKV->SetFloat( varKey.m_pszString, varValue.m_float ); break; + case FIELD_BOOLEAN: pKV->SetBool( varKey.m_pszString, varValue.m_bool ); break; + case FIELD_VECTOR: pKV->SetString( varKey.m_pszString, CFmtStr( "%f %f %f", varValue.m_pVector->x, varValue.m_pVector->y, varValue.m_pVector->z ) ); break; + case FIELD_HSCRIPT: + { + KeyValues *subKey = pKV->FindKey( varKey.m_pszString, true ); + KeyValues_TableToSubKeys( varValue, subKey ); + break; + } + } + } + + g_pScriptVM->ReleaseValue( varKey ); + g_pScriptVM->ReleaseValue( varValue ); + } +} + +void KeyValues_SubKeysToTable( KeyValues *pKV, HSCRIPT hTable ) +{ + FOR_EACH_SUBKEY( pKV, key ) + { + switch ( key->GetDataType() ) + { + case KeyValues::TYPE_STRING: g_pScriptVM->SetValue( hTable, key->GetName(), key->GetString() ); break; + case KeyValues::TYPE_INT: g_pScriptVM->SetValue( hTable, key->GetName(), key->GetInt() ); break; + case KeyValues::TYPE_FLOAT: g_pScriptVM->SetValue( hTable, key->GetName(), key->GetFloat() ); break; + case KeyValues::TYPE_NONE: + { + ScriptVariant_t subTable; + g_pScriptVM->CreateTable( subTable ); + g_pScriptVM->SetValue( hTable, key->GetName(), subTable ); + KeyValues_SubKeysToTable( key, subTable ); + g_pScriptVM->ReleaseValue( subTable ); + break; + } + } + } +} + +void CScriptKeyValues::TableToSubKeys( HSCRIPT hTable ) +{ + KeyValues_TableToSubKeys( hTable, m_pKeyValues ); +} + +void CScriptKeyValues::SubKeysToTable( HSCRIPT hTable ) +{ + KeyValues_SubKeysToTable( m_pKeyValues, hTable ); +} + +HSCRIPT CScriptKeyValues::ScriptFindOrCreateKey( const char *pszName ) +{ + KeyValues *pKeyValues = m_pKeyValues->FindKey(pszName, true); + if ( pKeyValues == NULL ) + return NULL; + + CScriptKeyValues *pScriptKey = new CScriptKeyValues( pKeyValues ); + + // UNDONE: who calls ReleaseInstance on this?? + HSCRIPT hScriptInstance = g_pScriptVM->RegisterInstance( pScriptKey ); + return hScriptInstance; +} + +const char *CScriptKeyValues::ScriptGetName() +{ + const char *psz = m_pKeyValues->GetName(); + return psz; +} + +int CScriptKeyValues::ScriptGetInt() +{ + int i = m_pKeyValues->GetInt(); + return i; +} + +float CScriptKeyValues::ScriptGetFloat() +{ + float f = m_pKeyValues->GetFloat(); + return f; +} + +const char *CScriptKeyValues::ScriptGetString() +{ + const char *psz = m_pKeyValues->GetString(); + return psz; +} + +bool CScriptKeyValues::ScriptGetBool() +{ + bool b = m_pKeyValues->GetBool(); + return b; +} + + +void CScriptKeyValues::ScriptSetKeyValueInt( const char *pszName, int iValue ) +{ + m_pKeyValues->SetInt( pszName, iValue ); +} + +void CScriptKeyValues::ScriptSetKeyValueFloat( const char *pszName, float flValue ) +{ + m_pKeyValues->SetFloat( pszName, flValue ); +} + +void CScriptKeyValues::ScriptSetKeyValueString( const char *pszName, const char *pszValue ) +{ + m_pKeyValues->SetString( pszName, pszValue ); +} + +void CScriptKeyValues::ScriptSetKeyValueBool( const char *pszName, bool bValue ) +{ + m_pKeyValues->SetBool( pszName, bValue ); +} + +void CScriptKeyValues::ScriptSetName( const char *pszValue ) +{ + m_pKeyValues->SetName( pszValue ); +} + +void CScriptKeyValues::ScriptSetInt( int iValue ) +{ + m_pKeyValues->SetInt( NULL, iValue ); +} + +void CScriptKeyValues::ScriptSetFloat( float flValue ) +{ + m_pKeyValues->SetFloat( NULL, flValue ); +} + +void CScriptKeyValues::ScriptSetString( const char *pszValue ) +{ + m_pKeyValues->SetString( NULL, pszValue ); +} + +void CScriptKeyValues::ScriptSetBool( bool bValue ) +{ + m_pKeyValues->SetBool( NULL, bValue ); +} + + +// constructors +CScriptKeyValues::CScriptKeyValues( KeyValues *pKeyValues = NULL ) +{ + if (pKeyValues == NULL) + { + m_pKeyValues = new KeyValues("CScriptKeyValues"); + } + else + { + m_pKeyValues = pKeyValues; + } +} + +// destructor +CScriptKeyValues::~CScriptKeyValues( ) +{ + if (m_pKeyValues) + { + m_pKeyValues->deleteThis(); + } + m_pKeyValues = NULL; +} + +//============================================================================= +// +// matrix3x4_t +// +//============================================================================= +CScriptColorInstanceHelper g_ColorScriptInstanceHelper; + +BEGIN_SCRIPTDESC_ROOT( Color, "" ) + + DEFINE_SCRIPT_CONSTRUCTOR() + DEFINE_SCRIPT_INSTANCE_HELPER( &g_ColorScriptInstanceHelper ) + + DEFINE_SCRIPTFUNC( SetColor, "Sets the color." ) + + DEFINE_SCRIPTFUNC( SetRawColor, "Sets the raw color integer." ) + DEFINE_SCRIPTFUNC( GetRawColor, "Gets the raw color integer." ) + + DEFINE_MEMBERVAR( "r", FIELD_CHARACTER, "Member variable for red." ) + DEFINE_MEMBERVAR( "g", FIELD_CHARACTER, "Member variable for green." ) + DEFINE_MEMBERVAR( "b", FIELD_CHARACTER, "Member variable for blue." ) + DEFINE_MEMBERVAR( "a", FIELD_CHARACTER, "Member variable for alpha. (transparency)" ) + +END_SCRIPTDESC(); + +//----------------------------------------------------------------------------- + +bool CScriptColorInstanceHelper::ToString( void *p, char *pBuf, int bufSize ) +{ + Color *pClr = ((Color *)p); + V_snprintf( pBuf, bufSize, "(color: (%i, %i, %i, %i))", pClr->r(), pClr->g(), pClr->b(), pClr->a() ); + return true; +} + +bool CScriptColorInstanceHelper::Get( void *p, const char *pszKey, ScriptVariant_t &variant ) +{ + Color *pClr = ((Color *)p); + if ( strlen(pszKey) == 1 ) + { + switch (pszKey[0]) + { + case 'r': + variant = pClr->r(); + return true; + case 'g': + variant = pClr->g(); + return true; + case 'b': + variant = pClr->b(); + return true; + case 'a': + variant = pClr->a(); + return true; + } + } + return false; +} + +bool CScriptColorInstanceHelper::Set( void *p, const char *pszKey, ScriptVariant_t &variant ) +{ + Color *pClr = ((Color *)p); + if ( strlen(pszKey) == 1 ) + { + int iVal; + variant.AssignTo( &iVal ); + switch (pszKey[0]) + { + // variant.AssignTo( &(*pClr)[0] ); + case 'r': + (*pClr)[0] = iVal; + return true; + case 'g': + (*pClr)[1] = iVal; + return true; + case 'b': + (*pClr)[2] = iVal; + return true; + case 'a': + (*pClr)[3] = iVal; + return true; + } + } + return false; +} + +//============================================================================= +//============================================================================= + +void RegisterBaseBindings( IScriptVM *pVM ) +{ + ScriptRegisterFunctionNamed( pVM, ScriptMsg, "Msg", "" ); + ScriptRegisterFunctionNamed( pVM, ScriptColorPrint, "printc", "Version of print() which takes a color before the message." ); + ScriptRegisterFunctionNamed( pVM, ScriptColorPrintL, "printcl", "Version of printl() which takes a color before the message." ); + + ScriptRegisterFunction( pVM, GetCPUUsage, "Get CPU usage percentage." ); + + //----------------------------------------------------------------------------- + + pVM->RegisterInstance( &g_ScriptGlobalSys, "GlobalSys" ); + + //----------------------------------------------------------------------------- + + pVM->RegisterClass( GetScriptDescForClass( CScriptKeyValues ) ); + + pVM->RegisterClass( GetScriptDescForClass( Color ) ); + + //----------------------------------------------------------------------------- + + ScriptRegisterConstant( pVM, MAPBASE_VERSION, "The current Mapbase version according to when the VScript library was last compiled." ); + ScriptRegisterConstant( pVM, MAPBASE_VER_INT, "The current Mapbase version integer according to when the VScript library was last compiled." ); + + // + // Math/world + // + ScriptRegisterConstant( pVM, MAX_COORD_FLOAT, "Maximum float coordinate." ); + ScriptRegisterConstant( pVM, MAX_TRACE_LENGTH, "Maximum traceable distance (assumes cubic world and trace from one corner to opposite)." ); + + // + // Trace Contents/Masks + // + ScriptRegisterConstant( pVM, CONTENTS_EMPTY, "Spatial content flags." ); + ScriptRegisterConstant( pVM, CONTENTS_SOLID, "Spatial content flags." ); + ScriptRegisterConstant( pVM, CONTENTS_WINDOW, "Spatial content flags." ); + ScriptRegisterConstant( pVM, CONTENTS_AUX, "Spatial content flags." ); + ScriptRegisterConstant( pVM, CONTENTS_GRATE, "Spatial content flags." ); + ScriptRegisterConstant( pVM, CONTENTS_SLIME, "Spatial content flags." ); + ScriptRegisterConstant( pVM, CONTENTS_WATER, "Spatial content flags." ); + ScriptRegisterConstant( pVM, CONTENTS_BLOCKLOS, "Spatial content flags." ); + ScriptRegisterConstant( pVM, CONTENTS_OPAQUE, "Spatial content flags." ); + ScriptRegisterConstant( pVM, CONTENTS_TESTFOGVOLUME, "Spatial content flags." ); + ScriptRegisterConstant( pVM, CONTENTS_TEAM1, "Spatial content flags." ); + ScriptRegisterConstant( pVM, CONTENTS_TEAM2, "Spatial content flags." ); + ScriptRegisterConstant( pVM, CONTENTS_IGNORE_NODRAW_OPAQUE, "Spatial content flags." ); + ScriptRegisterConstant( pVM, CONTENTS_MOVEABLE, "Spatial content flags." ); + ScriptRegisterConstant( pVM, CONTENTS_AREAPORTAL, "Spatial content flags." ); + ScriptRegisterConstant( pVM, CONTENTS_PLAYERCLIP, "Spatial content flags." ); + ScriptRegisterConstant( pVM, CONTENTS_MONSTERCLIP, "Spatial content flags." ); + + ScriptRegisterConstant( pVM, CONTENTS_CURRENT_0, "Spatial content flags." ); + ScriptRegisterConstant( pVM, CONTENTS_CURRENT_90, "Spatial content flags." ); + ScriptRegisterConstant( pVM, CONTENTS_CURRENT_180, "Spatial content flags." ); + ScriptRegisterConstant( pVM, CONTENTS_CURRENT_270, "Spatial content flags." ); + ScriptRegisterConstant( pVM, CONTENTS_CURRENT_UP, "Spatial content flags." ); + ScriptRegisterConstant( pVM, CONTENTS_CURRENT_DOWN, "Spatial content flags." ); + + ScriptRegisterConstant( pVM, CONTENTS_ORIGIN, "Spatial content flags." ); + ScriptRegisterConstant( pVM, CONTENTS_MONSTER, "Spatial content flags." ); + ScriptRegisterConstant( pVM, CONTENTS_DEBRIS, "Spatial content flags." ); + ScriptRegisterConstant( pVM, CONTENTS_DETAIL, "Spatial content flags." ); + ScriptRegisterConstant( pVM, CONTENTS_TRANSLUCENT, "Spatial content flags." ); + ScriptRegisterConstant( pVM, CONTENTS_LADDER, "Spatial content flags." ); + ScriptRegisterConstant( pVM, CONTENTS_HITBOX, "Spatial content flags." ); + + ScriptRegisterConstant( pVM, LAST_VISIBLE_CONTENTS, "Contains last visible spatial content flags." ); + ScriptRegisterConstant( pVM, ALL_VISIBLE_CONTENTS, "Contains all visible spatial content flags." ); + + ScriptRegisterConstant( pVM, MASK_SOLID, "Spatial content mask representing solid objects (CONTENTS_SOLID|CONTENTS_MOVEABLE|CONTENTS_WINDOW|CONTENTS_MONSTER|CONTENTS_GRATE)" ); + ScriptRegisterConstant( pVM, MASK_PLAYERSOLID, "Spatial content mask representing objects solid to the player, including player clips (CONTENTS_SOLID|CONTENTS_MOVEABLE|CONTENTS_PLAYERCLIP|CONTENTS_WINDOW|CONTENTS_MONSTER|CONTENTS_GRATE)" ); + ScriptRegisterConstant( pVM, MASK_NPCSOLID, "Spatial content mask representing objects solid to NPCs, including NPC clips (CONTENTS_SOLID|CONTENTS_MOVEABLE|CONTENTS_MONSTERCLIP|CONTENTS_WINDOW|CONTENTS_MONSTER|CONTENTS_GRATE)" ); + ScriptRegisterConstant( pVM, MASK_WATER, "Spatial content mask representing water and slime solids (CONTENTS_WATER|CONTENTS_MOVEABLE|CONTENTS_SLIME)" ); + ScriptRegisterConstant( pVM, MASK_OPAQUE, "Spatial content mask representing objects which block lighting (CONTENTS_SOLID|CONTENTS_MOVEABLE|CONTENTS_OPAQUE)" ); + ScriptRegisterConstant( pVM, MASK_OPAQUE_AND_NPCS, "Spatial content mask equivalent to MASK_OPAQUE, but also including NPCs (MASK_OPAQUE|CONTENTS_MONSTER)" ); + ScriptRegisterConstant( pVM, MASK_BLOCKLOS, "Spatial content mask representing objects which block LOS for AI (CONTENTS_SOLID|CONTENTS_MOVEABLE|CONTENTS_BLOCKLOS)" ); + ScriptRegisterConstant( pVM, MASK_BLOCKLOS_AND_NPCS, "Spatial content mask equivalent to MASK_BLOCKLOS, but also including NPCs (MASK_BLOCKLOS|CONTENTS_MONSTER)" ); + ScriptRegisterConstant( pVM, MASK_VISIBLE, "Spatial content mask representing objects which block LOS for players (MASK_OPAQUE|CONTENTS_IGNORE_NODRAW_OPAQUE)" ); + ScriptRegisterConstant( pVM, MASK_VISIBLE_AND_NPCS, "Spatial content mask equivalent to MASK_VISIBLE, but also including NPCs (MASK_OPAQUE_AND_NPCS|CONTENTS_IGNORE_NODRAW_OPAQUE)" ); + ScriptRegisterConstant( pVM, MASK_SHOT, "Spatial content mask representing objects solid to bullets (CONTENTS_SOLID|CONTENTS_MOVEABLE|CONTENTS_MONSTER|CONTENTS_WINDOW|CONTENTS_DEBRIS|CONTENTS_HITBOX)" ); + ScriptRegisterConstant( pVM, MASK_SHOT_HULL, "Spatial content mask representing objects solid to non-raycasted weapons, including grates (CONTENTS_SOLID|CONTENTS_MOVEABLE|CONTENTS_MONSTER|CONTENTS_WINDOW|CONTENTS_DEBRIS|CONTENTS_GRATE)" ); + ScriptRegisterConstant( pVM, MASK_SHOT_PORTAL, "Spatial content mask equivalent to MASK_SHOT, but excluding debris and not using expensive hitbox calculations (CONTENTS_SOLID|CONTENTS_MOVEABLE|CONTENTS_WINDOW|CONTENTS_MONSTER)" ); + ScriptRegisterConstant( pVM, MASK_SOLID_BRUSHONLY, "Spatial content mask equivalent to MASK_SOLID, but without NPCs (CONTENTS_SOLID|CONTENTS_MOVEABLE|CONTENTS_WINDOW|CONTENTS_GRATE)" ); + ScriptRegisterConstant( pVM, MASK_PLAYERSOLID_BRUSHONLY, "Spatial content mask equivalent to MASK_PLAYERSOLID, but without NPCs (CONTENTS_SOLID|CONTENTS_MOVEABLE|CONTENTS_WINDOW|CONTENTS_PLAYERCLIP|CONTENTS_GRATE)" ); + ScriptRegisterConstant( pVM, MASK_NPCSOLID_BRUSHONLY, "Spatial content mask equivalent to MASK_NPCSOLID, but without NPCs (CONTENTS_SOLID|CONTENTS_MOVEABLE|CONTENTS_WINDOW|CONTENTS_MONSTERCLIP|CONTENTS_GRATE)" ); + ScriptRegisterConstant( pVM, MASK_NPCWORLDSTATIC, "Spatial content mask representing objects static to NPCs, used for nodegraph rebuilding (CONTENTS_SOLID|CONTENTS_WINDOW|CONTENTS_MONSTERCLIP|CONTENTS_GRATE)" ); + ScriptRegisterConstant( pVM, MASK_SPLITAREAPORTAL, "Spatial content mask representing objects which can split areaportals (CONTENTS_WATER|CONTENTS_SLIME)" ); + + // + // Misc. General + // + ScriptRegisterConstant( pVM, FCVAR_NONE, "Empty convar flag." ); + ScriptRegisterConstant( pVM, FCVAR_UNREGISTERED, "If this convar flag is set, it isn't added to linked list, etc." ); + ScriptRegisterConstant( pVM, FCVAR_DEVELOPMENTONLY, "If this convar flag is set, it's hidden in \"retail\" DLLs." ); + ScriptRegisterConstant( pVM, FCVAR_GAMEDLL, "This convar flag is defined in server DLL convars." ); + ScriptRegisterConstant( pVM, FCVAR_CLIENTDLL, "This convar flag is defined in client DLL convars." ); + ScriptRegisterConstant( pVM, FCVAR_HIDDEN, "If this convar flag is set, it doesn't appear in the console or any searching tools, but it can still be set." ); + ScriptRegisterConstant( pVM, FCVAR_PROTECTED, "This convar flag prevents convars with secure data (e.g. passwords) from sending full data to clients, only sending 1 if non-zero and 0 otherwise." ); + ScriptRegisterConstant( pVM, FCVAR_SPONLY, "If this convar flag is set, it can't be changed by clients connected to a multiplayer server." ); + ScriptRegisterConstant( pVM, FCVAR_ARCHIVE, "If this convar flag is set, its value will be saved when the game is exited." ); + ScriptRegisterConstant( pVM, FCVAR_NOTIFY, "If this convar flag is set, it will notify players when it is changed." ); + ScriptRegisterConstant( pVM, FCVAR_CHEAT, "Only useable in singleplayer / debug / multiplayer & sv_cheats" ); + ScriptRegisterConstant( pVM, FCVAR_USERINFO, "If this convar flag is set, it will be marked as info which plays a part in how the server identifies a client." ); + ScriptRegisterConstant( pVM, FCVAR_PRINTABLEONLY, "If this convar flag is set, it cannot contain unprintable characters. Used for player name cvars, etc." ); + ScriptRegisterConstant( pVM, FCVAR_UNLOGGED, "If this convar flag is set, it will not log its changes if a log is being created." ); + ScriptRegisterConstant( pVM, FCVAR_NEVER_AS_STRING, "If this convar flag is set, it will never be printed as a string." ); + ScriptRegisterConstant( pVM, FCVAR_REPLICATED, "If this convar flag is set, it will enforce a serverside value on any clientside counterparts. (also known as FCVAR_SERVER)" ); + ScriptRegisterConstant( pVM, FCVAR_DEMO, "If this convar flag is set, it will be recorded when starting a demo file." ); + ScriptRegisterConstant( pVM, FCVAR_DONTRECORD, "If this convar flag is set, it will NOT be recorded when starting a demo file." ); + ScriptRegisterConstant( pVM, FCVAR_RELOAD_MATERIALS, "If this convar flag is set, it will force a material reload when it changes." ); + ScriptRegisterConstant( pVM, FCVAR_RELOAD_TEXTURES, "If this convar flag is set, it will force a texture reload when it changes." ); + ScriptRegisterConstant( pVM, FCVAR_NOT_CONNECTED, "If this convar flag is set, it cannot be changed by a client connected to the server." ); + ScriptRegisterConstant( pVM, FCVAR_MATERIAL_SYSTEM_THREAD, "This convar flag indicates it's read from the material system thread." ); + ScriptRegisterConstant( pVM, FCVAR_ARCHIVE_XBOX, "If this convar flag is set, it will be archived on the Xbox config." ); + ScriptRegisterConstant( pVM, FCVAR_ACCESSIBLE_FROM_THREADS, "If this convar flag is set, it will be accessible from the material system thread." ); + ScriptRegisterConstant( pVM, FCVAR_SERVER_CAN_EXECUTE, "If this convar flag is set, the server will be allowed to execute it as a client command." ); + ScriptRegisterConstant( pVM, FCVAR_SERVER_CANNOT_QUERY, "If this convar flag is set, the server will not be allowed to query its value." ); + ScriptRegisterConstant( pVM, FCVAR_CLIENTCMD_CAN_EXECUTE, "If this convar flag is set, any client will be allowed to execute this command." ); + + //----------------------------------------------------------------------------- + + RegisterMathBaseBindings( pVM ); +} diff --git a/src/vscript/vscript_bindings_base.h b/src/vscript/vscript_bindings_base.h new file mode 100644 index 00000000..2629aada --- /dev/null +++ b/src/vscript/vscript_bindings_base.h @@ -0,0 +1,76 @@ +//========= Mapbase - https://github.com/mapbase-source/source-sdk-2013 ================= +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef VSCRIPT_BINDINGS_BASE +#define VSCRIPT_BINDINGS_BASE +#ifdef _WIN32 +#pragma once +#endif + +#include "vscript/ivscript.h" +#include "tier1/KeyValues.h" + +// ---------------------------------------------------------------------------- +// KeyValues access +// ---------------------------------------------------------------------------- +class CScriptKeyValues +{ +public: + CScriptKeyValues( KeyValues *pKeyValues ); + ~CScriptKeyValues( ); + + HSCRIPT ScriptFindKey( const char *pszName ); + HSCRIPT ScriptGetFirstSubKey( void ); + HSCRIPT ScriptGetNextKey( void ); + int ScriptGetKeyValueInt( const char *pszName ); + float ScriptGetKeyValueFloat( const char *pszName ); + const char *ScriptGetKeyValueString( const char *pszName ); + bool ScriptIsKeyValueEmpty( const char *pszName ); + bool ScriptGetKeyValueBool( const char *pszName ); + void ScriptReleaseKeyValues( ); + + // Functions below are new with Mapbase + void TableToSubKeys( HSCRIPT hTable ); + void SubKeysToTable( HSCRIPT hTable ); + + HSCRIPT ScriptFindOrCreateKey( const char *pszName ); + + const char *ScriptGetName(); + int ScriptGetInt(); + float ScriptGetFloat(); + const char *ScriptGetString(); + bool ScriptGetBool(); + + void ScriptSetKeyValueInt( const char *pszName, int iValue ); + void ScriptSetKeyValueFloat( const char *pszName, float flValue ); + void ScriptSetKeyValueString( const char *pszName, const char *pszValue ); + void ScriptSetKeyValueBool( const char *pszName, bool bValue ); + void ScriptSetName( const char *pszValue ); + void ScriptSetInt( int iValue ); + void ScriptSetFloat( float flValue ); + void ScriptSetString( const char *pszValue ); + void ScriptSetBool( bool bValue ); + + KeyValues *GetKeyValues() { return m_pKeyValues; } + + KeyValues *m_pKeyValues; // actual KeyValue entity +}; + +//----------------------------------------------------------------------------- +// Exposes Color to VScript +//----------------------------------------------------------------------------- +class CScriptColorInstanceHelper : public IScriptInstanceHelper +{ + bool ToString( void *p, char *pBuf, int bufSize ); + + bool Get( void *p, const char *pszKey, ScriptVariant_t &variant ); + bool Set( void *p, const char *pszKey, ScriptVariant_t &variant ); +}; + +void RegisterBaseBindings( IScriptVM *pVM ); + +#endif diff --git a/src/vscript/vscript_bindings_math.cpp b/src/vscript/vscript_bindings_math.cpp new file mode 100644 index 00000000..cb1567d5 --- /dev/null +++ b/src/vscript/vscript_bindings_math.cpp @@ -0,0 +1,515 @@ +//========= Mapbase - https://github.com/mapbase-source/source-sdk-2013 ============// +// +// Purpose: VScript functions, constants, etc. registered within the library itself. +// +// This is for things which don't have to depend on server/client and can be accessed +// from anywhere. +// +// $NoKeywords: $ +//=============================================================================// + +#include "vscript/ivscript.h" + +#include "tier1/tier1.h" + +#include +#include "worldsize.h" + +#include + +#include "vscript_bindings_math.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//============================================================================= +// +// matrix3x4_t +// +//============================================================================= +BEGIN_SCRIPTDESC_ROOT_NAMED( matrix3x4_t, "matrix3x4_t", "A 3x4 matrix transform." ) + + DEFINE_SCRIPT_CONSTRUCTOR() + DEFINE_SCRIPTFUNC( Init, "Creates a matrix where the X axis = forward, the Y axis = left, and the Z axis = up." ) + +END_SCRIPTDESC(); + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +void ScriptConcatTransforms( HSCRIPT hMat1, HSCRIPT hMat2, HSCRIPT hOut ) +{ + if (!hMat1 || !hMat2 || !hOut) + return; + + matrix3x4_t *pMat1 = ToMatrix3x4( hMat1 ); + matrix3x4_t *pMat2 = ToMatrix3x4( hMat2 ); + matrix3x4_t *pOut = ToMatrix3x4( hOut ); + + ConcatTransforms( *pMat1, *pMat2, *pOut ); +} + +void ScriptMatrixCopy( HSCRIPT hMat1, HSCRIPT hOut ) +{ + if (!hMat1 || !hOut) + return; + + matrix3x4_t *pMat1 = ToMatrix3x4( hMat1 ); + matrix3x4_t *pOut = ToMatrix3x4( hOut ); + + MatrixCopy( *pMat1, *pOut ); +} + +void ScriptMatrixInvert( HSCRIPT hMat1, HSCRIPT hOut ) +{ + if (!hMat1 || !hOut) + return; + + matrix3x4_t *pMat1 = ToMatrix3x4( hMat1 ); + matrix3x4_t *pOut = ToMatrix3x4( hOut ); + + MatrixInvert( *pMat1, *pOut ); +} + +void ScriptMatricesAreEqual( HSCRIPT hMat1, HSCRIPT hMat2 ) +{ + if (!hMat1 || !hMat2) + return; + + matrix3x4_t *pMat1 = ToMatrix3x4( hMat1 ); + matrix3x4_t *pMat2 = ToMatrix3x4( hMat2 ); + + MatricesAreEqual( *pMat1, *pMat2 ); +} + +const Vector& ScriptMatrixGetColumn( HSCRIPT hMat1, int column ) +{ + static Vector outvec; + outvec.Zero(); + if (!hMat1) + return outvec; + + matrix3x4_t *pMat1 = ToMatrix3x4( hMat1 ); + + MatrixGetColumn( *pMat1, column, outvec ); + return outvec; +} + +void ScriptMatrixSetColumn( const Vector& vecset, int column, HSCRIPT hMat1 ) +{ + if (!hMat1) + return; + + matrix3x4_t *pMat1 = ToMatrix3x4( hMat1 ); + + MatrixSetColumn( vecset, column, *pMat1 ); +} + +void ScriptMatrixAngles( HSCRIPT hMat1, const QAngle& angset, const Vector& vecset ) +{ + if (!hMat1) + return; + + matrix3x4_t *pMat1 = ToMatrix3x4( hMat1 ); + + MatrixAngles( *pMat1, *const_cast(&angset), *const_cast(&vecset) ); +} + +void ScriptAngleMatrix( const QAngle& angset, const Vector& vecset, HSCRIPT hMat1 ) +{ + if (!hMat1) + return; + + matrix3x4_t *pMat1 = ToMatrix3x4( hMat1 ); + + AngleMatrix( angset, vecset, *pMat1 ); +} + +void ScriptAngleIMatrix( const QAngle& angset, const Vector& vecset, HSCRIPT hMat1 ) +{ + if (!hMat1) + return; + + matrix3x4_t *pMat1 = ToMatrix3x4( hMat1 ); + + AngleIMatrix( angset, vecset, *pMat1 ); +} + +void ScriptSetIdentityMatrix( HSCRIPT hMat1 ) +{ + if (!hMat1) + return; + + matrix3x4_t *pMat1 = ToMatrix3x4( hMat1 ); + + SetIdentityMatrix( *pMat1 ); +} + +void ScriptSetScaleMatrix( float x, float y, float z, HSCRIPT hMat1 ) +{ + if (!hMat1) + return; + + matrix3x4_t *pMat1 = ToMatrix3x4( hMat1 ); + + SetScaleMatrix( x, y, z, *pMat1 ); +} + +void ScriptMatrixScaleBy( float flScale, HSCRIPT hMat1 ) +{ + if (!hMat1) + return; + + matrix3x4_t *pMat1 = ToMatrix3x4( hMat1 ); + + MatrixScaleBy( flScale, *pMat1 ); +} + +void ScriptMatrixScaleByZero( HSCRIPT hMat1 ) +{ + if (!hMat1) + return; + + matrix3x4_t *pMat1 = ToMatrix3x4( hMat1 ); + + MatrixScaleByZero( *pMat1 ); +} + +const Vector& ScriptMatrixGetTranslation( HSCRIPT hMat1 ) +{ + static Vector outvec; + outvec.Zero(); + if (!hMat1) + return outvec; + + matrix3x4_t *pMat1 = ToMatrix3x4( hMat1 ); + + MatrixGetTranslation( *pMat1, outvec ); + return outvec; +} + +void ScriptMatrixSetTranslation( const Vector& vecset, HSCRIPT hMat1 ) +{ + if (!hMat1) + return; + + matrix3x4_t *pMat1 = ToMatrix3x4( hMat1 ); + + MatrixSetTranslation( vecset, *pMat1 ); +} + +//============================================================================= +// +// Quaternion +// +//============================================================================= +CScriptQuaternionInstanceHelper g_QuaternionScriptInstanceHelper; + +BEGIN_SCRIPTDESC_ROOT_NAMED( Quaternion, "Quaternion", "A quaternion." ) + + DEFINE_SCRIPT_CONSTRUCTOR() + DEFINE_SCRIPT_INSTANCE_HELPER( &g_QuaternionScriptInstanceHelper ) + DEFINE_SCRIPTFUNC_NAMED( ScriptInit, "Init", "Creates a quaternion with the given values." ) + + DEFINE_MEMBERVAR( "x", FIELD_FLOAT, "The quaternion's i axis component." ) + DEFINE_MEMBERVAR( "y", FIELD_FLOAT, "The quaternion's j axis component." ) + DEFINE_MEMBERVAR( "z", FIELD_FLOAT, "The quaternion's k axis component." ) + DEFINE_MEMBERVAR( "w", FIELD_FLOAT, "The quaternion's scalar component." ) + +END_SCRIPTDESC(); + +//----------------------------------------------------------------------------- + +bool CScriptQuaternionInstanceHelper::ToString( void *p, char *pBuf, int bufSize ) +{ + Quaternion *pQuat = ((Quaternion *)p); + V_snprintf( pBuf, bufSize, "(Quaternion %p [%f %f %f %f])", (void*)pQuat, pQuat->x, pQuat->y, pQuat->z, pQuat->w ); + return true; +} + +bool CScriptQuaternionInstanceHelper::Get( void *p, const char *pszKey, ScriptVariant_t &variant ) +{ + Quaternion *pQuat = ((Quaternion *)p); + if ( strlen(pszKey) == 1 ) + { + switch (pszKey[0]) + { + case 'x': + variant = pQuat->x; + return true; + case 'y': + variant = pQuat->y; + return true; + case 'z': + variant = pQuat->z; + return true; + case 'w': + variant = pQuat->w; + return true; + } + } + return false; +} + +bool CScriptQuaternionInstanceHelper::Set( void *p, const char *pszKey, ScriptVariant_t &variant ) +{ + Quaternion *pQuat = ((Quaternion *)p); + if ( strlen(pszKey) == 1 ) + { + switch (pszKey[0]) + { + case 'x': + variant.AssignTo( &pQuat->x ); + return true; + case 'y': + variant.AssignTo( &pQuat->y ); + return true; + case 'z': + variant.AssignTo( &pQuat->z ); + return true; + case 'w': + variant.AssignTo( &pQuat->w ); + return true; + } + } + return false; +} + +ScriptVariant_t *CScriptQuaternionInstanceHelper::Add( void *p, ScriptVariant_t &variant ) +{ + Quaternion *pQuat = ((Quaternion *)p); + + float flAdd; + variant.AssignTo( &flAdd ); + + (*pQuat)[0] += flAdd; + (*pQuat)[1] += flAdd; + (*pQuat)[2] += flAdd; + (*pQuat)[3] += flAdd; + + static ScriptVariant_t result; + result = (HSCRIPT)p; + return &result; +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +void ScriptQuaternionAdd( HSCRIPT hQuat1, HSCRIPT hQuat2, HSCRIPT hOut ) +{ + if (!hQuat1 || !hQuat2 || !hOut) + return; + + Quaternion *pQuat1 = ToQuaternion( hQuat1 ); + Quaternion *pQuat2 = ToQuaternion( hQuat2 ); + Quaternion *pOut = ToQuaternion( hOut ); + + QuaternionAdd( *pQuat1, *pQuat2, *pOut ); +} + +void ScriptMatrixQuaternion( HSCRIPT hMat1, HSCRIPT hQuat1 ) +{ + if (!hMat1 || !hQuat1) + return; + + matrix3x4_t *pMat1 = ToMatrix3x4( hMat1 ); + Quaternion *pQuat1 = ToQuaternion( hQuat1 ); + + MatrixQuaternion( *pMat1, *pQuat1 ); +} + +void ScriptQuaternionMatrix( HSCRIPT hQuat1, HSCRIPT hMat1 ) +{ + if (!hQuat1 || !hMat1) + return; + + Quaternion *pQuat1 = ToQuaternion( hQuat1 ); + matrix3x4_t *pMat1 = ToMatrix3x4( hMat1 ); + + QuaternionMatrix( *pQuat1, *pMat1 ); +} + +QAngle ScriptQuaternionAngles( HSCRIPT hQuat1 ) +{ + if (!hQuat1) + return QAngle(); + + Quaternion *pQuat1 = ToQuaternion( hQuat1 ); + + QAngle angles; + QuaternionAngles( *pQuat1, angles ); + return angles; +} + +//============================================================================= +// +// Misc. Vector/QAngle functions +// +//============================================================================= + +const Vector& ScriptAngleVectors( const QAngle &angles ) +{ + static Vector forward; + AngleVectors( angles, &forward ); + return forward; +} + +const QAngle& ScriptVectorAngles( const Vector &forward ) +{ + static QAngle angles; + VectorAngles( forward, angles ); + return angles; +} + +const Vector& ScriptVectorRotate( const Vector &in, HSCRIPT hMat ) +{ + if (ToMatrix3x4(hMat) == NULL) + return vec3_origin; + + static Vector out; + VectorRotate( in, *ToMatrix3x4(hMat), out ); + return out; +} + +const Vector& ScriptVectorIRotate( const Vector &in, HSCRIPT hMat ) +{ + if (ToMatrix3x4(hMat) == NULL) + return vec3_origin; + + static Vector out; + VectorIRotate( in, *ToMatrix3x4(hMat), out ); + return out; +} + +const Vector& ScriptVectorTransform( const Vector &in, HSCRIPT hMat ) +{ + if (ToMatrix3x4(hMat) == NULL) + return vec3_origin; + + static Vector out; + VectorTransform( in, *ToMatrix3x4( hMat ), out ); + return out; +} + +const Vector& ScriptVectorITransform( const Vector &in, HSCRIPT hMat ) +{ + if (ToMatrix3x4(hMat) == NULL) + return vec3_origin; + + static Vector out; + VectorITransform( in, *ToMatrix3x4( hMat ), out ); + return out; +} + +const Vector& ScriptCalcClosestPointOnAABB( const Vector &mins, const Vector &maxs, const Vector &point ) +{ + static Vector outvec; + CalcClosestPointOnAABB( mins, maxs, point, outvec ); + return outvec; +} + +const Vector& ScriptCalcClosestPointOnLine( const Vector &point, const Vector &vLineA, const Vector &vLineB ) +{ + static Vector outvec; + CalcClosestPointOnLine( point, vLineA, vLineB, outvec ); + return outvec; +} + +float ScriptCalcDistanceToLine( const Vector &point, const Vector &vLineA, const Vector &vLineB ) +{ + return CalcDistanceToLine( point, vLineA, vLineB ); +} + +const Vector& ScriptCalcClosestPointOnLineSegment( const Vector &point, const Vector &vLineA, const Vector &vLineB ) +{ + static Vector outvec; + CalcClosestPointOnLineSegment( point, vLineA, vLineB, outvec ); + return outvec; +} + +float ScriptCalcDistanceToLineSegment( const Vector &point, const Vector &vLineA, const Vector &vLineB ) +{ + return CalcDistanceToLineSegment( point, vLineA, vLineB ); +} + +inline float ScriptExponentialDecay( float decayTo, float decayTime, float dt ) +{ + return ExponentialDecay( decayTo, decayTime, dt ); +} + +void RegisterMathBaseBindings( IScriptVM *pVM ) +{ + ScriptRegisterConstantNamed( pVM, ((float)(180.f / M_PI_F)), "RAD2DEG", "" ); + ScriptRegisterConstantNamed( pVM, ((float)(M_PI_F / 180.f)), "DEG2RAD", "" ); + + ScriptRegisterFunction( pVM, RandomFloat, "Generate a random floating point number within a range, inclusive." ); + ScriptRegisterFunction( pVM, RandomInt, "Generate a random integer within a range, inclusive." ); + //ScriptRegisterFunction( pVM, Approach, "Returns a value which approaches the target value from the input value with the specified speed." ); + ScriptRegisterFunction( pVM, ApproachAngle, "Returns an angle which approaches the target angle from the input angle with the specified speed." ); + ScriptRegisterFunction( pVM, AngleDiff, "Returns the degrees difference between two yaw angles." ); + //ScriptRegisterFunction( pVM, AngleDistance, "Returns the distance between two angles." ); + ScriptRegisterFunction( pVM, AngleNormalize, "Clamps an angle to be in between -360 and 360." ); + ScriptRegisterFunction( pVM, AngleNormalizePositive, "Clamps an angle to be in between 0 and 360." ); + ScriptRegisterFunction( pVM, AnglesAreEqual, "Checks if two angles are equal based on a given tolerance value." ); + + // + // matrix3x4_t + // + pVM->RegisterClass( GetScriptDescForClass( matrix3x4_t ) ); + + ScriptRegisterFunctionNamed( pVM, ScriptFreeMatrixInstance, "FreeMatrixInstance", "Frees an allocated matrix instance." ); + + ScriptRegisterFunctionNamed( pVM, ScriptConcatTransforms, "ConcatTransforms", "Concatenates two transformation matrices into another matrix." ); + ScriptRegisterFunctionNamed( pVM, ScriptMatrixCopy, "MatrixCopy", "Copies a matrix to another matrix." ); + ScriptRegisterFunctionNamed( pVM, ScriptMatrixInvert, "MatrixInvert", "Inverts a matrix and copies the result to another matrix." ); + ScriptRegisterFunctionNamed( pVM, ScriptMatricesAreEqual, "MatricesAreEqual", "Checks if two matrices are equal." ); + ScriptRegisterFunctionNamed( pVM, ScriptMatrixGetColumn, "MatrixGetColumn", "Gets the column of a matrix." ); + ScriptRegisterFunctionNamed( pVM, ScriptMatrixSetColumn, "MatrixSetColumn", "Sets the column of a matrix." ); + ScriptRegisterFunctionNamed( pVM, ScriptMatrixAngles, "MatrixAngles", "Gets the angles and position of a matrix." ); + ScriptRegisterFunctionNamed( pVM, ScriptAngleMatrix, "AngleMatrix", "Sets the angles and position of a matrix." ); + ScriptRegisterFunctionNamed( pVM, ScriptAngleIMatrix, "AngleIMatrix", "Sets the inverted angles and position of a matrix." ); + ScriptRegisterFunctionNamed( pVM, ScriptSetIdentityMatrix, "SetIdentityMatrix", "Turns a matrix into an identity matrix." ); + ScriptRegisterFunctionNamed( pVM, ScriptSetScaleMatrix, "SetScaleMatrix", "Builds a scale matrix." ); + ScriptRegisterFunctionNamed( pVM, ScriptMatrixScaleBy, "MatrixScaleBy", "Scales a matrix." ); + ScriptRegisterFunctionNamed( pVM, ScriptMatrixScaleByZero, "MatrixScaleByZero", "Scales a matrix by zero." ); + ScriptRegisterFunctionNamed( pVM, ScriptMatrixGetTranslation, "MatrixGetTranslation", "Gets a matrix's translation." ); + ScriptRegisterFunctionNamed( pVM, ScriptMatrixSetTranslation, "MatrixSetTranslation", "Sets a matrix's translation." ); + + // + // Quaternion + // + pVM->RegisterClass( GetScriptDescForClass( Quaternion ) ); + + ScriptRegisterFunctionNamed( pVM, ScriptFreeQuaternionInstance, "FreeQuaternionInstance", "Frees an allocated quaternion instance." ); + + ScriptRegisterFunctionNamed( pVM, ScriptQuaternionAdd, "QuaternionAdd", "Adds two quaternions together into another quaternion." ); + ScriptRegisterFunctionNamed( pVM, ScriptMatrixQuaternion, "MatrixQuaternion", "Converts a matrix to a quaternion." ); + ScriptRegisterFunctionNamed( pVM, ScriptQuaternionMatrix, "QuaternionMatrix", "Converts a quaternion to a matrix." ); + ScriptRegisterFunctionNamed( pVM, ScriptQuaternionAngles, "QuaternionAngles", "Converts a quaternion to angles." ); + + // + // Misc. Vector/QAngle functions + // + ScriptRegisterFunctionNamed( pVM, ScriptAngleVectors, "AngleVectors", "Turns an angle into a direction vector." ); + ScriptRegisterFunctionNamed( pVM, ScriptVectorAngles, "VectorAngles", "Turns a direction vector into an angle." ); + + ScriptRegisterFunctionNamed( pVM, ScriptVectorRotate, "VectorRotate", "Rotates a vector with a matrix." ); + ScriptRegisterFunctionNamed( pVM, ScriptVectorIRotate, "VectorIRotate", "Rotates a vector with the inverse of a matrix." ); + ScriptRegisterFunctionNamed( pVM, ScriptVectorTransform, "VectorTransform", "Transforms a vector with a matrix." ); + ScriptRegisterFunctionNamed( pVM, ScriptVectorITransform, "VectorITransform", "Transforms a vector with the inverse of a matrix." ); + + ScriptRegisterFunction( pVM, CalcSqrDistanceToAABB, "Returns the squared distance to a bounding box." ); + ScriptRegisterFunctionNamed( pVM, ScriptCalcClosestPointOnAABB, "CalcClosestPointOnAABB", "Returns the closest point on a bounding box." ); + ScriptRegisterFunctionNamed( pVM, ScriptCalcDistanceToLine, "CalcDistanceToLine", "Returns the distance to a line." ); + ScriptRegisterFunctionNamed( pVM, ScriptCalcClosestPointOnLine, "CalcClosestPointOnLine", "Returns the closest point on a line." ); + ScriptRegisterFunctionNamed( pVM, ScriptCalcDistanceToLineSegment, "CalcDistanceToLineSegment", "Returns the distance to a line segment." ); + ScriptRegisterFunctionNamed( pVM, ScriptCalcClosestPointOnLineSegment, "CalcClosestPointOnLineSegment", "Returns the closest point on a line segment." ); + + ScriptRegisterFunction( pVM, SimpleSplineRemapVal, "remaps a value in [startInterval, startInterval+rangeInterval] from linear to spline using SimpleSpline" ); + ScriptRegisterFunction( pVM, SimpleSplineRemapValClamped, "remaps a value in [startInterval, startInterval+rangeInterval] from linear to spline using SimpleSpline" ); + ScriptRegisterFunction( pVM, Bias, "The curve is biased towards 0 or 1 based on biasAmt, which is between 0 and 1." ); + ScriptRegisterFunction( pVM, Gain, "Gain is similar to Bias, but biasAmt biases towards or away from 0.5." ); + ScriptRegisterFunction( pVM, SmoothCurve, "SmoothCurve maps a 0-1 value into another 0-1 value based on a cosine wave" ); + ScriptRegisterFunction( pVM, SmoothCurve_Tweak, "SmoothCurve peaks at flPeakPos, flPeakSharpness controls the sharpness of the peak" ); + ScriptRegisterFunctionNamed( pVM, ScriptExponentialDecay, "ExponentialDecay", "decayTo is factor the value should decay to in decayTime" ); +} diff --git a/src/vscript/vscript_bindings_math.h b/src/vscript/vscript_bindings_math.h new file mode 100644 index 00000000..c2d960b5 --- /dev/null +++ b/src/vscript/vscript_bindings_math.h @@ -0,0 +1,62 @@ +//========= Mapbase - https://github.com/mapbase-source/source-sdk-2013 ================= +// +// Purpose: Shared VScript math functions. +// +// $NoKeywords: $ +//============================================================================= + +#ifndef VSCRIPT_BINDINGS_MATH +#define VSCRIPT_BINDINGS_MATH +#ifdef _WIN32 +#pragma once +#endif + +void RegisterMathBaseBindings( IScriptVM *pVM ); + +// Some base bindings require VM functions +extern IScriptVM *g_pScriptVM; + +//----------------------------------------------------------------------------- +// Exposes matrix3x4_t to VScript +//----------------------------------------------------------------------------- +inline matrix3x4_t *ToMatrix3x4( HSCRIPT hMat ) { return HScriptToClass( hMat ); } + +static void ScriptFreeMatrixInstance( HSCRIPT hMat ) +{ + matrix3x4_t *smatrix = HScriptToClass( hMat ); + if (smatrix) + { + g_pScriptVM->RemoveInstance( hMat ); + delete smatrix; + } +} + +//----------------------------------------------------------------------------- +// Exposes Quaternion to VScript +//----------------------------------------------------------------------------- +class CScriptQuaternionInstanceHelper : public IScriptInstanceHelper +{ + bool ToString( void *p, char *pBuf, int bufSize ); + + bool Get( void *p, const char *pszKey, ScriptVariant_t &variant ); + bool Set( void *p, const char *pszKey, ScriptVariant_t &variant ); + + ScriptVariant_t *Add( void *p, ScriptVariant_t &variant ); + //ScriptVariant_t *Subtract( void *p, ScriptVariant_t &variant ); + //ScriptVariant_t *Multiply( void *p, ScriptVariant_t &variant ); + //ScriptVariant_t *Divide( void *p, ScriptVariant_t &variant ); +}; + +inline Quaternion *ToQuaternion( HSCRIPT hQuat ) { return HScriptToClass( hQuat ); } + +static void ScriptFreeQuaternionInstance( HSCRIPT hQuat ) +{ + Quaternion *squat = HScriptToClass( hQuat ); + if (squat) + { + g_pScriptVM->RemoveInstance( hQuat ); + delete squat; + } +} + +#endif diff --git a/src/vscript/vscript_squirrel.cpp b/src/vscript/vscript_squirrel.cpp new file mode 100644 index 00000000..880fb8dd --- /dev/null +++ b/src/vscript/vscript_squirrel.cpp @@ -0,0 +1,4098 @@ +//========= Mapbase - https://github.com/mapbase-source/source-sdk-2013 ============// +// +// Purpose: Custom implementation of VScript in Source 2013, created from scratch +// using the Alien Swarm SDK as a reference for Valve's library. +// +// Author(s): ReDucTor (header written by Blixibon) +// +// $NoKeywords: $ +//=============================================================================// + +#include "vscript/ivscript.h" +#include "tier1/utlbuffer.h" +#include "tier1/utlmap.h" +#include "tier1/utlstring.h" + +#include "squirrel.h" +#include "sqstdaux.h" +//#include "sqstdblob.h" +//#include "sqstdsystem.h" +#include "sqstdtime.h" +//#include "sqstdio.h" +#include "sqstdmath.h" +#include "sqstdstring.h" + +// HACK: Include internal parts of squirrel for serialization +#include "squirrel/squirrel/sqobject.h" +#include "squirrel/squirrel/sqstate.h" +#include "squirrel/squirrel/sqtable.h" +#include "squirrel/squirrel/sqclass.h" +#include "squirrel/squirrel/sqfuncproto.h" +#include "squirrel/squirrel/sqvm.h" +#include "squirrel/squirrel/sqclosure.h" + +#include "tier1/utlbuffer.h" +#include "tier1/mapbase_con_groups.h" +#include "tier1/convar.h" + +#include "vscript_squirrel.nut" + +#include + +extern ConVar developer; + +struct WriteStateMap +{ + CUtlMap cache; + WriteStateMap() : cache(DefLessFunc(void*)) + {} + + bool CheckCache(CUtlBuffer* pBuffer, void* ptr) + { + auto idx = cache.Find(ptr); + if (idx != cache.InvalidIndex()) + { + pBuffer->PutInt(cache[idx]); + return true; + } + else + { + int newIdx = cache.Count(); + cache.Insert(ptr, newIdx); + pBuffer->PutInt(newIdx); + return false; + } + } +}; + +struct ReadStateMap +{ + CUtlMap cache; +#ifdef _DEBUG + CUtlMap allocated; +#endif + HSQUIRRELVM vm_; + ReadStateMap(HSQUIRRELVM vm) : + cache(DefLessFunc(int)), +#ifdef _DEBUG + allocated(DefLessFunc(int)), +#endif + vm_(vm) + {} + + ~ReadStateMap() + { + FOR_EACH_MAP_FAST(cache, i) + { + HSQOBJECT& obj = cache[i]; + sq_release(vm_, &obj); + } + } + + bool CheckCache(CUtlBuffer* pBuffer, HSQUIRRELVM vm, int * outmarker) + { + int marker = pBuffer->GetInt(); + + auto idx = cache.Find(marker); + +#ifdef _DEBUG + auto allocatedIdx = allocated.Find(marker); + bool hasSeen = allocatedIdx != allocated.InvalidIndex(); + if (!hasSeen) + { + allocated.Insert(marker, true); + } +#endif + + if (idx != cache.InvalidIndex()) + { + sq_pushobject(vm, cache[idx]); + return true; + } + else + { +#ifdef _DEBUG + Assert(!hasSeen); +#endif + *outmarker = marker; + return false; + } + } + + void StoreInCache(int marker, HSQOBJECT& obj) + { + cache.Insert(marker, obj); + } + + void StoreTopInCache(int marker) + { + HSQOBJECT obj; + sq_getstackobj(vm_, -1, &obj); + sq_addref(vm_, &obj); + cache.Insert(marker, obj); + } +}; + +class SquirrelVM : public IScriptVM +{ +public: + virtual bool Init() override; + virtual void Shutdown() override; + + virtual bool ConnectDebugger() override; + virtual void DisconnectDebugger() override; + + virtual ScriptLanguage_t GetLanguage() override; + virtual const char* GetLanguageName() override; + + virtual void AddSearchPath(const char* pszSearchPath) override; + + //-------------------------------------------------------- + + virtual bool Frame(float simTime) override; + + //-------------------------------------------------------- + // Simple script usage + //-------------------------------------------------------- + virtual ScriptStatus_t Run(const char* pszScript, bool bWait = true) override; + + //-------------------------------------------------------- + // Compilation + //-------------------------------------------------------- + virtual HSCRIPT CompileScript(const char* pszScript, const char* pszId = NULL) override; + virtual void ReleaseScript(HSCRIPT) override; + + //-------------------------------------------------------- + // Execution of compiled + //-------------------------------------------------------- + virtual ScriptStatus_t Run(HSCRIPT hScript, HSCRIPT hScope = NULL, bool bWait = true) override; + virtual ScriptStatus_t Run(HSCRIPT hScript, bool bWait) override; + + //-------------------------------------------------------- + // Scope + //-------------------------------------------------------- + virtual HSCRIPT CreateScope(const char* pszScope, HSCRIPT hParent = NULL) override; + virtual void ReleaseScope(HSCRIPT hScript) override; + + //-------------------------------------------------------- + // Script functions + //-------------------------------------------------------- + virtual HSCRIPT LookupFunction(const char* pszFunction, HSCRIPT hScope = NULL) override; + virtual void ReleaseFunction(HSCRIPT hScript) override; + + //-------------------------------------------------------- + // Script functions (raw, use Call()) + //-------------------------------------------------------- + virtual ScriptStatus_t ExecuteFunction(HSCRIPT hFunction, ScriptVariant_t* pArgs, int nArgs, ScriptVariant_t* pReturn, HSCRIPT hScope, bool bWait) override; + + //-------------------------------------------------------- + // Hooks + //-------------------------------------------------------- + virtual HScriptRaw HScriptToRaw( HSCRIPT val ) override; + virtual ScriptStatus_t ExecuteHookFunction( const char *pszEventName, ScriptVariant_t *pArgs, int nArgs, ScriptVariant_t *pReturn, HSCRIPT hScope, bool bWait ) override; + + //-------------------------------------------------------- + // External functions + //-------------------------------------------------------- + virtual void RegisterFunction(ScriptFunctionBinding_t* pScriptFunction) override; + + //-------------------------------------------------------- + // External classes + //-------------------------------------------------------- + virtual bool RegisterClass(ScriptClassDesc_t* pClassDesc) override; + + //-------------------------------------------------------- + // External constants + //-------------------------------------------------------- + virtual void RegisterConstant(ScriptConstantBinding_t *pScriptConstant) override; + + //-------------------------------------------------------- + // External enums + //-------------------------------------------------------- + virtual void RegisterEnum(ScriptEnumDesc_t *pEnumDesc) override; + + //-------------------------------------------------------- + // External hooks + //-------------------------------------------------------- + virtual void RegisterHook(ScriptHook_t *pHookDesc) override; + + //-------------------------------------------------------- + // External instances. Note class will be auto-registered. + //-------------------------------------------------------- + + virtual HSCRIPT RegisterInstance(ScriptClassDesc_t* pDesc, void* pInstance, bool bAllowDestruct = false) override; + virtual void SetInstanceUniqeId(HSCRIPT hInstance, const char* pszId) override; + virtual void RemoveInstance(HSCRIPT hInstance) override; + + virtual void* GetInstanceValue(HSCRIPT hInstance, ScriptClassDesc_t* pExpectedType = NULL) override; + + //---------------------------------------------------------------------------- + + virtual bool GenerateUniqueKey(const char* pszRoot, char* pBuf, int nBufSize) override; + + //---------------------------------------------------------------------------- + + virtual bool ValueExists(HSCRIPT hScope, const char* pszKey) override; + + virtual bool SetValue(HSCRIPT hScope, const char* pszKey, const char* pszValue) override; + virtual bool SetValue(HSCRIPT hScope, const char* pszKey, const ScriptVariant_t& value) override; + virtual bool SetValue(HSCRIPT hScope, const ScriptVariant_t& key, const ScriptVariant_t& val) override; + + virtual void CreateTable(ScriptVariant_t& Table) override; + virtual int GetNumTableEntries(HSCRIPT hScope) override; + virtual int GetKeyValue(HSCRIPT hScope, int nIterator, ScriptVariant_t* pKey, ScriptVariant_t* pValue) override; + + virtual bool GetValue(HSCRIPT hScope, const char* pszKey, ScriptVariant_t* pValue) override; + virtual bool GetValue(HSCRIPT hScope, ScriptVariant_t key, ScriptVariant_t* pValue) override; + virtual void ReleaseValue(ScriptVariant_t& value) override; + + virtual bool ClearValue(HSCRIPT hScope, const char* pszKey) override; + virtual bool ClearValue( HSCRIPT hScope, ScriptVariant_t pKey ) override; + + virtual void CreateArray(ScriptVariant_t &arr, int size = 0) override; + virtual bool ArrayAppend(HSCRIPT hArray, const ScriptVariant_t &val) override; + + //---------------------------------------------------------------------------- + + virtual void WriteState(CUtlBuffer* pBuffer) override; + virtual void ReadState(CUtlBuffer* pBuffer) override; + virtual void RemoveOrphanInstances() override; + + virtual void DumpState() override; + + virtual void SetOutputCallback(ScriptOutputFunc_t pFunc) override; + virtual void SetErrorCallback(ScriptErrorFunc_t pFunc) override; + + //---------------------------------------------------------------------------- + + virtual bool RaiseException(const char* pszExceptionText) override; + + + void WriteObject(CUtlBuffer* pBuffer, WriteStateMap& writeState, SQInteger idx); + void ReadObject(CUtlBuffer* pBuffer, ReadStateMap& readState); + HSQUIRRELVM vm_ = nullptr; + HSQOBJECT lastError_; + HSQOBJECT vectorClass_; + HSQOBJECT regexpClass_; +}; + +static char TYPETAG_VECTOR[] = "VectorTypeTag"; + +namespace SQVector +{ + SQInteger Construct(HSQUIRRELVM vm) + { + // TODO: There must be a nicer way to store the data with the actual instance, there are + // default slots but they are really just pointers anyway and you need to hold a member + // pointer which is yet another pointer dereference + int numParams = sq_gettop(vm); + + float x = 0; + float y = 0; + float z = 0; + + if ((numParams >= 2 && SQ_FAILED(sq_getfloat(vm, 2, &x))) || + (numParams >= 3 && SQ_FAILED(sq_getfloat(vm, 3, &y))) || + (numParams >= 4 && SQ_FAILED(sq_getfloat(vm, 4, &z)))) + { + return sq_throwerror(vm, "Expected Vector(float x, float y, float z)"); + } + + SQUserPointer p; + sq_getinstanceup(vm, 1, &p, 0); + new (p) Vector(x, y, z); + + return 0; + } + + SQInteger _get(HSQUIRRELVM vm) + { + const char* key = nullptr; + sq_getstring(vm, 2, &key); + + if (key == nullptr) + { + return sq_throwerror(vm, "Expected Vector._get(string)"); + } + + if (key[0] < 'x' || key['0'] > 'z' || key[1] != '\0') + { + return sqstd_throwerrorf(vm, "the index '%.50s' does not exist", key); + } + + Vector* v = nullptr; + if (SQ_FAILED(sq_getinstanceup(vm, 1, (SQUserPointer*)&v, TYPETAG_VECTOR))) + { + return sq_throwerror(vm, "Unable to get Vector"); + } + + int idx = key[0] - 'x'; + sq_pushfloat(vm, (*v)[idx]); + return 1; + } + + SQInteger _set(HSQUIRRELVM vm) + { + const char* key = nullptr; + sq_getstring(vm, 2, &key); + + if (key == nullptr) + { + return sq_throwerror(vm, "Expected Vector._set(string)"); + } + + if (key[0] < 'x' || key['0'] > 'z' || key[1] != '\0') + { + return sqstd_throwerrorf(vm, "the index '%.50s' does not exist", key); + } + + Vector* v = nullptr; + if (SQ_FAILED(sq_getinstanceup(vm, 1, (SQUserPointer*)&v, TYPETAG_VECTOR))) + { + return sq_throwerror(vm, "Unable to get Vector"); + } + + float val = 0; + if (SQ_FAILED(sq_getfloat(vm, 3, &val))) + { + return sqstd_throwerrorf(vm, "Vector.%s expects float", key); + } + + int idx = key[0] - 'x'; + (*v)[idx] = val; + return 0; + } + + SQInteger _add(HSQUIRRELVM vm) + { + Vector* v1 = nullptr; + Vector* v2 = nullptr; + + if (sq_gettop(vm) != 2 || + SQ_FAILED(sq_getinstanceup(vm, 1, (SQUserPointer*)&v1, TYPETAG_VECTOR)) || + SQ_FAILED(sq_getinstanceup(vm, 2, (SQUserPointer*)&v2, TYPETAG_VECTOR))) + { + return sq_throwerror(vm, "Expected (Vector, Vector)"); + } + + sq_getclass(vm, 1); + sq_createinstance(vm, -1); + SQUserPointer p; + sq_getinstanceup(vm, -1, &p, 0); + new(p) Vector((*v1) + (*v2)); + sq_remove(vm, -2); + + return 1; + } + + SQInteger _sub(HSQUIRRELVM vm) + { + Vector* v1 = nullptr; + Vector* v2 = nullptr; + + if (sq_gettop(vm) != 2 || + SQ_FAILED(sq_getinstanceup(vm, 1, (SQUserPointer*)&v1, TYPETAG_VECTOR)) || + SQ_FAILED(sq_getinstanceup(vm, 2, (SQUserPointer*)&v2, TYPETAG_VECTOR))) + { + return sq_throwerror(vm, "Expected (Vector, Vector)"); + } + + sq_getclass(vm, 1); + sq_createinstance(vm, -1); + SQUserPointer p; + sq_getinstanceup(vm, -1, &p, 0); + new(p) Vector((*v1) - (*v2)); + sq_remove(vm, -2); + + return 1; + } + + SQInteger _multiply(HSQUIRRELVM vm) + { + Vector* v1 = nullptr; + + if (sq_gettop(vm) != 2 || + SQ_FAILED(sq_getinstanceup(vm, 1, (SQUserPointer*)&v1, TYPETAG_VECTOR))) + { + return sq_throwerror(vm, "Expected (Vector, Vector|float)"); + } + + float s = 0.0; + Vector* v2 = nullptr; + + if ( SQ_SUCCEEDED(sq_getfloat(vm, 2, &s)) ) + { + sq_getclass(vm, 1); + sq_createinstance(vm, -1); + SQUserPointer p; + sq_getinstanceup(vm, -1, &p, 0); + new(p) Vector((*v1) * s); + sq_remove(vm, -2); + + return 1; + } + else if ( SQ_SUCCEEDED(sq_getinstanceup(vm, 1, (SQUserPointer*)&v2, TYPETAG_VECTOR)) ) + { + sq_getclass(vm, 1); + sq_createinstance(vm, -1); + SQUserPointer p; + sq_getinstanceup(vm, -1, &p, 0); + new(p) Vector((*v1) * (*v2)); + sq_remove(vm, -2); + + return 1; + } + else + { + return sq_throwerror(vm, "Expected (Vector, Vector|float)"); + } + } + + SQInteger _divide(HSQUIRRELVM vm) + { + Vector* v1 = nullptr; + + if (sq_gettop(vm) != 2 || + SQ_FAILED(sq_getinstanceup(vm, 1, (SQUserPointer*)&v1, TYPETAG_VECTOR))) + { + return sq_throwerror(vm, "Expected (Vector, Vector|float)"); + } + + float s = 0.0; + Vector* v2 = nullptr; + + if ( SQ_SUCCEEDED(sq_getfloat(vm, 2, &s)) ) + { + sq_getclass(vm, 1); + sq_createinstance(vm, -1); + SQUserPointer p; + sq_getinstanceup(vm, -1, &p, 0); + new(p) Vector((*v1) / s); + sq_remove(vm, -2); + + return 1; + } + else if ( SQ_SUCCEEDED(sq_getinstanceup(vm, 1, (SQUserPointer*)&v2, TYPETAG_VECTOR)) ) + { + sq_getclass(vm, 1); + sq_createinstance(vm, -1); + SQUserPointer p; + sq_getinstanceup(vm, -1, &p, 0); + new(p) Vector((*v1) / (*v2)); + sq_remove(vm, -2); + + return 1; + } + else + { + return sq_throwerror(vm, "Expected (Vector, Vector|float)"); + } + } + + SQInteger _unm(HSQUIRRELVM vm) + { + Vector* v1 = nullptr; + + if (sq_gettop(vm) != 1 || + SQ_FAILED(sq_getinstanceup(vm, 1, (SQUserPointer*)&v1, TYPETAG_VECTOR))) + { + return sq_throwerror(vm, "Expected (Vector)"); + } + + sq_getclass(vm, 1); + sq_createinstance(vm, -1); + SQUserPointer p; + sq_getinstanceup(vm, -1, &p, 0); + new(p) Vector(-v1->x, -v1->y, -v1->z); + sq_remove(vm, -2); + + return 1; + } + + SQInteger weakref(HSQUIRRELVM vm) + { + sq_weakref(vm, 1); + return 1; + } + + SQInteger getclass(HSQUIRRELVM vm) + { + sq_getclass(vm, 1); + sq_push(vm, -1); + return 1; + } + + // multi purpose - copy from input vector, or init with 3 float input + SQInteger Set(HSQUIRRELVM vm) + { + SQInteger top = sq_gettop(vm); + Vector* v1 = nullptr; + + if ( top < 2 || SQ_FAILED(sq_getinstanceup(vm, 1, (SQUserPointer*)&v1, TYPETAG_VECTOR)) ) + { + return sq_throwerror(vm, "Expected (Vector, Vector)"); + } + + Vector* v2 = nullptr; + + if ( SQ_SUCCEEDED(sq_getinstanceup(vm, 2, (SQUserPointer*)&v2, TYPETAG_VECTOR)) ) + { + if ( top != 2 ) + return sq_throwerror(vm, "Expected (Vector, Vector)"); + + VectorCopy( *v2, *v1 ); + sq_remove( vm, -1 ); + + return 1; + } + + float x, y, z; + + if ( top == 4 && + SQ_SUCCEEDED(sq_getfloat(vm, 2, &x)) && + SQ_SUCCEEDED(sq_getfloat(vm, 3, &y)) && + SQ_SUCCEEDED(sq_getfloat(vm, 4, &z)) ) + { + v1->Init( x, y, z ); + sq_pop( vm, 3 ); + + return 1; + } + + return sq_throwerror(vm, "Expected (Vector, Vector)"); + } + + SQInteger Add(HSQUIRRELVM vm) + { + Vector* v1 = nullptr; + Vector* v2 = nullptr; + + if (sq_gettop(vm) != 2 || + SQ_FAILED(sq_getinstanceup(vm, 1, (SQUserPointer*)&v1, TYPETAG_VECTOR)) || + SQ_FAILED(sq_getinstanceup(vm, 2, (SQUserPointer*)&v2, TYPETAG_VECTOR))) + { + return sq_throwerror(vm, "Expected (Vector, Vector)"); + } + + VectorAdd( *v1, *v2, *v1 ); + sq_remove( vm, -1 ); + + return 1; + } + + SQInteger Subtract(HSQUIRRELVM vm) + { + Vector* v1 = nullptr; + Vector* v2 = nullptr; + + if (sq_gettop(vm) != 2 || + SQ_FAILED(sq_getinstanceup(vm, 1, (SQUserPointer*)&v1, TYPETAG_VECTOR)) || + SQ_FAILED(sq_getinstanceup(vm, 2, (SQUserPointer*)&v2, TYPETAG_VECTOR))) + { + return sq_throwerror(vm, "Expected (Vector, Vector)"); + } + + VectorSubtract( *v1, *v2, *v1 ); + sq_remove( vm, -1 ); + + return 1; + } + + SQInteger Multiply(HSQUIRRELVM vm) + { + Vector* v1 = nullptr; + + if (sq_gettop(vm) != 2 || + SQ_FAILED(sq_getinstanceup(vm, 1, (SQUserPointer*)&v1, TYPETAG_VECTOR))) + { + return sq_throwerror(vm, "Expected (Vector, Vector|float)"); + } + + Vector* v2 = nullptr; + + if ( SQ_SUCCEEDED(sq_getinstanceup( vm, 2, (SQUserPointer*)&v2, TYPETAG_VECTOR )) ) + { + VectorMultiply( *v1, *v2, *v1 ); + sq_remove( vm, -1 ); + + return 1; + } + + float flInput; + + if ( SQ_SUCCEEDED(sq_getfloat( vm, 2, &flInput )) ) + { + VectorMultiply( *v1, flInput, *v1 ); + sq_remove( vm, -1 ); + + return 1; + } + + return sq_throwerror(vm, "Expected (Vector, Vector|float)"); + } + + SQInteger Divide(HSQUIRRELVM vm) + { + Vector* v1 = nullptr; + + if (sq_gettop(vm) != 2 || + SQ_FAILED(sq_getinstanceup(vm, 1, (SQUserPointer*)&v1, TYPETAG_VECTOR))) + { + return sq_throwerror(vm, "Expected (Vector, Vector|float)"); + } + + Vector* v2 = nullptr; + + if ( SQ_SUCCEEDED(sq_getinstanceup( vm, 2, (SQUserPointer*)&v2, TYPETAG_VECTOR )) ) + { + VectorDivide( *v1, *v2, *v1 ); + sq_remove( vm, -1 ); + + return 1; + } + + float flInput; + + if ( SQ_SUCCEEDED(sq_getfloat( vm, 2, &flInput )) ) + { + VectorDivide( *v1, flInput, *v1 ); + sq_remove( vm, -1 ); + + return 1; + } + + return sq_throwerror(vm, "Expected (Vector, Vector|float)"); + } + + SQInteger DistTo(HSQUIRRELVM vm) + { + Vector* v1 = nullptr; + Vector* v2 = nullptr; + + if (sq_gettop(vm) != 2 || + SQ_FAILED(sq_getinstanceup(vm, 1, (SQUserPointer*)&v1, TYPETAG_VECTOR)) || + SQ_FAILED(sq_getinstanceup(vm, 2, (SQUserPointer*)&v2, TYPETAG_VECTOR))) + { + return sq_throwerror(vm, "Expected (Vector, Vector)"); + } + + sq_pushfloat( vm, v1->DistTo(*v2) ); + + return 1; + } + + SQInteger DistToSqr(HSQUIRRELVM vm) + { + Vector* v1 = nullptr; + Vector* v2 = nullptr; + + if (sq_gettop(vm) != 2 || + SQ_FAILED(sq_getinstanceup(vm, 1, (SQUserPointer*)&v1, TYPETAG_VECTOR)) || + SQ_FAILED(sq_getinstanceup(vm, 2, (SQUserPointer*)&v2, TYPETAG_VECTOR))) + { + return sq_throwerror(vm, "Expected (Vector, Vector)"); + } + + sq_pushfloat( vm, v1->DistToSqr(*v2) ); + + return 1; + } + + SQInteger IsEqualTo(HSQUIRRELVM vm) + { + Vector* v1 = nullptr; + Vector* v2 = nullptr; + + if (sq_gettop(vm) < 2 || // bother checking > 3? + SQ_FAILED(sq_getinstanceup(vm, 1, (SQUserPointer*)&v1, TYPETAG_VECTOR)) || + SQ_FAILED(sq_getinstanceup(vm, 2, (SQUserPointer*)&v2, TYPETAG_VECTOR)) ) + { + return sq_throwerror(vm, "Expected (Vector, Vector, float)"); + } + + float tolerance = 0.0f; + sq_getfloat( vm, 3, &tolerance ); + + sq_pushbool( vm, VectorsAreEqual( *v1, *v2, tolerance ) ); + + return 1; + } + + SQInteger Length(HSQUIRRELVM vm) + { + Vector* v1 = nullptr; + + if (sq_gettop(vm) != 1 || + SQ_FAILED(sq_getinstanceup(vm, 1, (SQUserPointer*)&v1, TYPETAG_VECTOR))) + { + return sq_throwerror(vm, "Expected (Vector)"); + } + + sq_pushfloat(vm, v1->Length()); + return 1; + } + + SQInteger LengthSqr(HSQUIRRELVM vm) + { + Vector* v1 = nullptr; + + if (sq_gettop(vm) != 1 || + SQ_FAILED(sq_getinstanceup(vm, 1, (SQUserPointer*)&v1, TYPETAG_VECTOR))) + { + return sq_throwerror(vm, "Expected (Vector)"); + } + + sq_pushfloat(vm, v1->LengthSqr()); + return 1; + } + + SQInteger Length2D(HSQUIRRELVM vm) + { + Vector* v1 = nullptr; + + if (sq_gettop(vm) != 1 || + SQ_FAILED(sq_getinstanceup(vm, 1, (SQUserPointer*)&v1, TYPETAG_VECTOR))) + { + return sq_throwerror(vm, "Expected (Vector)"); + } + + sq_pushfloat(vm, v1->Length2D()); + return 1; + } + + SQInteger Length2DSqr(HSQUIRRELVM vm) + { + Vector* v1 = nullptr; + + if (sq_gettop(vm) != 1 || + SQ_FAILED(sq_getinstanceup(vm, 1, (SQUserPointer*)&v1, TYPETAG_VECTOR))) + { + return sq_throwerror(vm, "Expected (Vector)"); + } + + sq_pushfloat(vm, v1->Length2DSqr()); + return 1; + } + + SQInteger Normalized(HSQUIRRELVM vm) + { + Vector* v1 = nullptr; + + if (sq_gettop(vm) != 1 || + SQ_FAILED(sq_getinstanceup(vm, 1, (SQUserPointer*)&v1, TYPETAG_VECTOR))) + { + return sq_throwerror(vm, "Expected (Vector)"); + } + + sq_getclass(vm, 1); + sq_createinstance(vm, -1); + SQUserPointer p; + sq_getinstanceup(vm, -1, &p, 0); + new(p) Vector((*v1).Normalized()); + sq_remove(vm, -2); + + return 1; + } + + SQInteger Norm(HSQUIRRELVM vm) + { + Vector* v1 = nullptr; + + if (sq_gettop(vm) != 1 || + SQ_FAILED(sq_getinstanceup(vm, 1, (SQUserPointer*)&v1, TYPETAG_VECTOR))) + { + return sq_throwerror(vm, "Expected (Vector)"); + } + + float len = v1->NormalizeInPlace(); + sq_pushfloat(vm, len); + + return 1; + } + + SQInteger Scale(HSQUIRRELVM vm) + { + Vector* v1 = nullptr; + float s = 0.0f; + + if (sq_gettop(vm) != 2 || + SQ_FAILED(sq_getinstanceup(vm, 1, (SQUserPointer*)&v1, TYPETAG_VECTOR)) || + SQ_SUCCEEDED(sq_getfloat(vm, 2, &s))) + { + return sq_throwerror(vm, "Expected (Vector, float)"); + } + + sq_getclass(vm, 1); + sq_createinstance(vm, -1); + SQUserPointer p; + sq_getinstanceup(vm, -1, &p, 0); + new(p) Vector((*v1) * s); + sq_remove(vm, -2); + + return 1; + } + + SQInteger Dot(HSQUIRRELVM vm) + { + Vector* v1 = nullptr; + Vector* v2 = nullptr; + + if (sq_gettop(vm) != 2 || + SQ_FAILED(sq_getinstanceup(vm, 1, (SQUserPointer*)&v1, TYPETAG_VECTOR)) || + SQ_FAILED(sq_getinstanceup(vm, 2, (SQUserPointer*)&v2, TYPETAG_VECTOR))) + { + return sq_throwerror(vm, "Expected (Vector, Vector)"); + } + + sq_pushfloat(vm, v1->Dot(*v2)); + return 1; + } + + SQInteger ToKVString(HSQUIRRELVM vm) + { + Vector* v1 = nullptr; + + if (sq_gettop(vm) != 1 || + SQ_FAILED(sq_getinstanceup(vm, 1, (SQUserPointer*)&v1, TYPETAG_VECTOR))) + { + return sq_throwerror(vm, "Expected (Vector)"); + } + + sqstd_pushstringf(vm, "%f %f %f", v1->x, v1->y, v1->z); + return 1; + } + + SQInteger FromKVString(HSQUIRRELVM vm) + { + Vector* v1 = nullptr; + const char* szInput; + + if (sq_gettop(vm) != 2 || + SQ_FAILED(sq_getinstanceup(vm, 1, (SQUserPointer*)&v1, TYPETAG_VECTOR)) || + SQ_FAILED(sq_getstring(vm, 2, &szInput)) ) + { + return sq_throwerror(vm, "Expected (Vector, string)"); + } + + float x = 0.0f, y = 0.0f, z = 0.0f; + + if ( sscanf( szInput, "%f %f %f", &x, &y, &z ) < 3 ) + { + // Return null while invalidating the input vector. + // This allows the user to easily check for input errors without halting. + + sq_pushnull(vm); + *v1 = vec3_invalid; + + return 1; + } + + v1->x = x; + v1->y = y; + v1->z = z; + + // return input vector + sq_remove( vm, -1 ); + + return 1; + } + + SQInteger Cross(HSQUIRRELVM vm) + { + Vector* v1 = nullptr; + Vector* v2 = nullptr; + + if (sq_gettop(vm) != 2 || + SQ_FAILED(sq_getinstanceup(vm, 1, (SQUserPointer*)&v1, TYPETAG_VECTOR)) || + SQ_FAILED(sq_getinstanceup(vm, 2, (SQUserPointer*)&v2, TYPETAG_VECTOR))) + { + return sq_throwerror(vm, "Expected (Vector, Vector)"); + } + + sq_getclass(vm, 1); + sq_createinstance(vm, -1); + SQUserPointer p; + sq_getinstanceup(vm, -1, &p, 0); + new(p) Vector((*v1).Cross(*v2)); + sq_remove(vm, -2); + + return 1; + } + + SQInteger WithinAABox(HSQUIRRELVM vm) + { + Vector* v1 = nullptr; + Vector* mins = nullptr; + Vector* maxs = nullptr; + + if (sq_gettop(vm) != 3 || + SQ_FAILED(sq_getinstanceup(vm, 1, (SQUserPointer*)&v1, TYPETAG_VECTOR)) || + SQ_FAILED(sq_getinstanceup(vm, 2, (SQUserPointer*)&mins, TYPETAG_VECTOR)) || + SQ_FAILED(sq_getinstanceup(vm, 3, (SQUserPointer*)&maxs, TYPETAG_VECTOR))) + { + return sq_throwerror(vm, "Expected (Vector, Vector, Vector)"); + } + + sq_pushbool( vm, v1->WithinAABox( *mins, *maxs ) ); + + return 1; + } + + SQInteger ToString(HSQUIRRELVM vm) + { + Vector* v1 = nullptr; + + if (sq_gettop(vm) != 1 || + SQ_FAILED(sq_getinstanceup(vm, 1, (SQUserPointer*)&v1, TYPETAG_VECTOR))) + { + return sq_throwerror(vm, "Expected (Vector)"); + } + + sqstd_pushstringf(vm, "(Vector %p [%f %f %f])", (void*)v1, v1->x, v1->y, v1->z); + return 1; + } + + SQInteger TypeOf(HSQUIRRELVM vm) + { + sq_pushstring(vm, "Vector", -1); + return 1; + } + + SQInteger Nexti(HSQUIRRELVM vm) + { + Vector* v1 = nullptr; + + if (sq_gettop(vm) != 2 || + SQ_FAILED(sq_getinstanceup(vm, 1, (SQUserPointer*)&v1, TYPETAG_VECTOR))) + { + return sq_throwerror(vm, "Expected (Vector)"); + } + + HSQOBJECT obj; + sq_resetobject(&obj); + sq_getstackobj(vm, 2, &obj); + + const char* curkey = nullptr; + + if (sq_isnull(obj)) + { + curkey = "w"; + } + else if (sq_isstring(obj)) + { + curkey = sq_objtostring(&obj); + } + else + { + return sq_throwerror(vm, "Invalid iterator"); + } + + Assert(curkey && curkey[0] >= 'w' && curkey[0] <= 'z'); + + if (curkey[0] == 'z') + { + // Reached the end + sq_pushnull(vm); + return 1; + } + + char newkey = curkey[0] + 1; + sq_pushstring(vm, &newkey, 1); + + return 1; + } + + static const SQRegFunction funcs[] = { + {_SC("constructor"), Construct,0,nullptr}, + {_SC("_get"), _get, 2, _SC(".s")}, + {_SC("_set"), _set, 3, _SC(".sn")}, + {_SC("_add"), _add, 2, _SC("..")}, + {_SC("_sub"), _sub, 2, _SC("..")}, + {_SC("_mul"), _multiply, 2, _SC("..")}, + {_SC("_div"), _divide, 2, _SC("..")}, + {_SC("_unm"), _unm, 1, _SC(".")}, + {_SC("weakref"), weakref, 1, _SC(".")}, + {_SC("getclass"), getclass, 1, _SC(".")}, + {_SC("Set"), Set, -2, _SC("..nn")}, + {_SC("Add"), Add, 2, _SC("..")}, + {_SC("Subtract"), Subtract, 2, _SC("..")}, + {_SC("Multiply"), Multiply, 2, _SC("..")}, + {_SC("Divide"), Divide, 2, _SC("..")}, + {_SC("DistTo"), DistTo, 2, _SC("..")}, + {_SC("DistToSqr"), DistToSqr, 2, _SC("..")}, + {_SC("IsEqualTo"), IsEqualTo, -2, _SC("..n")}, + {_SC("Length"), Length, 1, _SC(".")}, + {_SC("LengthSqr"), LengthSqr, 1, _SC(".")}, + {_SC("Length2D"), Length2D, 1, _SC(".")}, + {_SC("Length2DSqr"), Length2DSqr, 1, _SC(".")}, + {_SC("Normalized"), Normalized, 1, _SC(".")}, + {_SC("Norm"), Norm, 1, _SC(".")}, + {_SC("Scale"), Scale, 2, _SC(".n")}, // identical to _multiply + {_SC("Dot"), Dot, 2, _SC("..")}, + {_SC("Cross"), Cross, 2, _SC("..")}, + {_SC("WithinAABox"), WithinAABox, 3, _SC("...")}, + {_SC("ToKVString"), ToKVString, 1, _SC(".")}, + {_SC("FromKVString"), FromKVString, 2, _SC(".s")}, + {_SC("_tostring"), ToString, 1, _SC(".")}, + {_SC("_typeof"), TypeOf, 1, _SC(".")}, + {_SC("_nexti"), Nexti, 2, _SC("..")}, + + {nullptr,(SQFUNCTION)0,0,nullptr} + }; + + HSQOBJECT register_class(HSQUIRRELVM v) + { + sq_pushstring(v, _SC("Vector"), -1); + sq_newclass(v, SQFalse); + sq_settypetag(v, -1, TYPETAG_VECTOR); + sq_setclassudsize(v, -1, sizeof(Vector)); + SQInteger i = 0; + while (funcs[i].name != 0) { + const SQRegFunction& f = funcs[i]; + sq_pushstring(v, f.name, -1); + sq_newclosure(v, f.f, 0); + sq_setparamscheck(v, f.nparamscheck, f.typemask); + sq_setnativeclosurename(v, -1, f.name); + sq_newslot(v, -3, SQFalse); + i++; + } + HSQOBJECT klass; + sq_resetobject(&klass); + sq_getstackobj(v, -1, &klass); + sq_addref(v, &klass); + sq_newslot(v, -3, SQFalse); + return klass; + } +} // SQVector + +struct ClassInstanceData +{ + ClassInstanceData(void* instance, ScriptClassDesc_t* desc, const char* instanceId = nullptr, bool allowDestruct = false) : + instance(instance), + desc(desc), + instanceId(instanceId), + allowDestruct(allowDestruct) + {} + + void* instance; + ScriptClassDesc_t* desc; + CUtlString instanceId; + + // Indicates this game-created instance is a weak reference and can be destructed (Blixibon) + bool allowDestruct; +}; + +bool CreateParamCheck(const ScriptFunctionBinding_t& func, char* output) +{ + *output++ = '.'; + for (int i = 0; i < func.m_desc.m_Parameters.Count(); ++i) + { + switch (func.m_desc.m_Parameters[i]) + { + case FIELD_FLOAT: + case FIELD_INTEGER: + *output++ = 'n'; + break; + case FIELD_CSTRING: + *output++ = 's'; + break; + case FIELD_VECTOR: + *output++ = 'x'; // Generic instance, we validate on arrival + break; + case FIELD_BOOLEAN: + *output++ = 'b'; + break; + case FIELD_CHARACTER: + *output++ = 's'; + break; + case FIELD_HSCRIPT: + *output++ = '.'; + break; + default: + Assert(!"Unsupported type"); + return false; + }; + } + *output++ = 0; + return true; +} + +void PushVariant(HSQUIRRELVM vm, const ScriptVariant_t& value) +{ + switch (value.m_type) + { + case FIELD_VOID: + sq_pushnull(vm); + break; + case FIELD_FLOAT: + sq_pushfloat(vm, value); + break; + case FIELD_FLOAT64: + sq_pushfloat(vm, (float)value.m_float64); + break; + case FIELD_CSTRING: + if ( value.m_pszString ) + sq_pushstring(vm, value, -1); + else + sq_pushnull(vm); + break; + case FIELD_VECTOR: + { + SquirrelVM* pSquirrelVM = (SquirrelVM*)sq_getforeignptr(vm); + assert(pSquirrelVM); + sq_pushobject(vm, pSquirrelVM->vectorClass_); + sq_createinstance(vm, -1); + SQUserPointer p; + sq_getinstanceup(vm, -1, &p, 0); + new(p) Vector(static_cast(value)); + sq_remove(vm, -2); + break; + } + case FIELD_INTEGER: + sq_pushinteger(vm, value.m_int); + break; + case FIELD_UINT: + sq_pushinteger(vm, (int)value.m_uint); + break; + case FIELD_UINT64: + sq_pushinteger(vm, (int)value.m_uint64); + break; + case FIELD_BOOLEAN: + sq_pushbool(vm, value.m_bool); + break; + case FIELD_CHARACTER: + { + char buf[2] = { value.m_char, 0 }; + sq_pushstring(vm, buf, 1); + break; + } + case FIELD_HSCRIPT: + if (value.m_hScript) + { + sq_pushobject(vm, *((HSQOBJECT*)value.m_hScript)); + } + else + { + sq_pushnull(vm); + } + break; + } +} + +void GetVariantScriptString(const ScriptVariant_t& value, char *szValue, int iSize) +{ + switch (value.m_type) + { + case FIELD_VOID: + V_strncpy( szValue, "null", iSize ); + break; + case FIELD_FLOAT: + V_snprintf( szValue, iSize, "%f", value.m_float ); + break; + case FIELD_FLOAT64: + V_snprintf( szValue, iSize, "%lf", value.m_float64 ); + break; + case FIELD_CSTRING: + V_snprintf( szValue, iSize, "\"%s\"", value.m_pszString ); + break; + case FIELD_VECTOR: + V_snprintf( szValue, iSize, "Vector( %f, %f, %f )", value.m_pVector->x, value.m_pVector->y, value.m_pVector->z ); + break; + case FIELD_INTEGER: + V_snprintf( szValue, iSize, "%i", value.m_int ); + break; + case FIELD_UINT: + V_snprintf( szValue, iSize, "%u", value.m_uint ); + break; + case FIELD_UINT64: + V_snprintf( szValue, iSize, "%llu", value.m_uint64 ); + break; + case FIELD_BOOLEAN: + V_snprintf( szValue, iSize, "%d", value.m_bool ); + break; + case FIELD_CHARACTER: + //char buf[2] = { value.m_char, 0 }; + V_snprintf( szValue, iSize, "\"%c\"", value.m_char ); + break; + } +} + +bool getVariant(HSQUIRRELVM vm, SQInteger idx, ScriptVariant_t& variant) +{ + switch (sq_gettype(vm, idx)) + { + case OT_NULL: + { + // TODO: Should this be (HSCRIPT)nullptr + variant.m_type = FIELD_VOID; + return true; + } + case OT_INTEGER: + { + SQInteger val; + if (SQ_FAILED(sq_getinteger(vm, idx, &val))) + { + return false; + } + variant = (int)val; + return true; + } + case OT_FLOAT: + { + SQFloat val; + if (SQ_FAILED(sq_getfloat(vm, idx, &val))) + { + return false; + } + variant = (float)val; + return true; + } + case OT_BOOL: + { + SQBool val; + if (SQ_FAILED(sq_getbool(vm, idx, &val))) + { + return false; + } + variant = val ? true : false; + return true; + } + case OT_STRING: + { + const char* val; + SQInteger size = 0; + if (SQ_FAILED(sq_getstringandsize(vm, idx, &val, &size))) + { + return false; + } + char* buffer = new char[size + 1]; + V_memcpy(buffer, val, size); + buffer[size] = 0; + variant = buffer; + variant.m_flags |= SV_FREE; + return true; + } + case OT_INSTANCE: + { + Vector* v = nullptr; + SQUserPointer tag; + if (SQ_SUCCEEDED(sq_gettypetag(vm, idx, &tag)) && + tag == TYPETAG_VECTOR && + SQ_SUCCEEDED(sq_getinstanceup(vm, idx, (SQUserPointer*)&v, TYPETAG_VECTOR))) + { + variant = new Vector(*v); + variant.m_flags |= SV_FREE; + return true; + } + // fall-through for non-vector + } + default: + { + HSQOBJECT* obj = new HSQOBJECT; + sq_resetobject(obj); + sq_getstackobj(vm, idx, obj); + sq_addref(vm, obj); + variant = (HSCRIPT)obj; + } + }; + + + return true; +} + +SQInteger function_stub(HSQUIRRELVM vm) +{ + SQInteger top = sq_gettop(vm); + + SQUserPointer userptr = nullptr; + sq_getuserpointer(vm, top, &userptr); + + Assert(userptr); + + ScriptFunctionBinding_t* pFunc = (ScriptFunctionBinding_t*)userptr; + + auto nargs = pFunc->m_desc.m_Parameters.Count(); + + if (nargs > top) + { + // TODO: Handle optional parameters? + return sq_throwerror(vm, "Invalid number of parameters"); + } + + CUtlVector params; + params.SetCount(nargs); + + for (int i = 0; i < nargs; ++i) + { + switch (pFunc->m_desc.m_Parameters[i]) + { + case FIELD_FLOAT: + { + float val = 0.0; + if (SQ_FAILED(sq_getfloat(vm, i + 2, &val))) + return sq_throwerror(vm, "Expected float"); + params[i] = val; + break; + } + case FIELD_CSTRING: + { + const char* val; + if (SQ_FAILED(sq_getstring(vm, i + 2, &val))) + return sq_throwerror(vm, "Expected string"); + params[i] = val; + break; + } + case FIELD_VECTOR: + { + Vector* val; + if (SQ_FAILED(sq_getinstanceup(vm, i + 2, (SQUserPointer*)&val, TYPETAG_VECTOR))) + return sq_throwerror(vm, "Expected Vector"); + params[i] = *val; + break; + } + case FIELD_INTEGER: + { + SQInteger val = 0; + if (SQ_FAILED(sq_getinteger(vm, i + 2, &val))) + return sq_throwerror(vm, "Expected integer"); + params[i] = (int)val; + break; + } + case FIELD_BOOLEAN: + { + SQBool val = 0; + if (SQ_FAILED(sq_getbool(vm, i + 2, &val))) + return sq_throwerror(vm, "Expected bool"); + params[i] = val ? true : false; + break; + } + case FIELD_CHARACTER: + { + const char* val; + if (SQ_FAILED(sq_getstring(vm, i + 2, &val))) + return sq_throwerror(vm, "Expected string"); + params[i] = val[i]; + break; + } + case FIELD_HSCRIPT: + { + HSQOBJECT val; + if (SQ_FAILED(sq_getstackobj(vm, i + 2, &val))) + return sq_throwerror(vm, "Expected handle"); + + if (sq_isnull(val)) + { + params[i] = (HSCRIPT)nullptr; + } + else + { + HSQOBJECT* pObject = new HSQOBJECT; + *pObject = val; + sq_addref(vm, pObject); + params[i] = (HSCRIPT)pObject; + } + break; + } + default: + Assert(!"Unsupported type"); + return false; + } + } + + void* instance = nullptr; + + if (pFunc->m_flags & SF_MEMBER_FUNC) + { + SQUserPointer self; + sq_getinstanceup(vm, 1, &self, nullptr); + + if (!self) + { + return sq_throwerror(vm, "Accessed null instance"); + } + + instance = ((ClassInstanceData*)self)->instance; + } + + ScriptVariant_t retval; + + SquirrelVM* pSquirrelVM = (SquirrelVM*)sq_getforeignptr(vm); + assert(pSquirrelVM); + + sq_resetobject(&pSquirrelVM->lastError_); + + (*pFunc->m_pfnBinding)(pFunc->m_pFunction, instance, params.Base(), nargs, + pFunc->m_desc.m_ReturnType == FIELD_VOID ? nullptr : &retval); + + if (!sq_isnull(pSquirrelVM->lastError_)) + { + sq_pushobject(vm, pSquirrelVM->lastError_); + sq_resetobject(&pSquirrelVM->lastError_); + return sq_throwobject(vm); + } + + PushVariant(vm, retval); + + if (retval.m_type == FIELD_VECTOR) + delete retval.m_pVector; + + return pFunc->m_desc.m_ReturnType != FIELD_VOID; +} + + +SQInteger destructor_stub(SQUserPointer p, SQInteger size) +{ + auto classInstanceData = (ClassInstanceData*)p; + + if (classInstanceData->desc->m_pfnDestruct) + classInstanceData->desc->m_pfnDestruct(classInstanceData->instance); + + classInstanceData->~ClassInstanceData(); + return 0; +} + +SQInteger destructor_stub_instance(SQUserPointer p, SQInteger size) +{ + auto classInstanceData = (ClassInstanceData*)p; + // We don't call destructor here because this is owned by the game + classInstanceData->~ClassInstanceData(); + return 0; +} + +SQInteger constructor_stub(HSQUIRRELVM vm) +{ + ScriptClassDesc_t* pClassDesc = nullptr; + sq_gettypetag(vm, 1, (SQUserPointer*)&pClassDesc); + + if (!pClassDesc->m_pfnConstruct) + { + return sqstd_throwerrorf(vm, "Unable to construct instances of %s", pClassDesc->m_pszScriptName); + } + + SquirrelVM* pSquirrelVM = (SquirrelVM*)sq_getforeignptr(vm); + assert(pSquirrelVM); + + sq_resetobject(&pSquirrelVM->lastError_); + + void* instance = pClassDesc->m_pfnConstruct(); + + if (!sq_isnull(pSquirrelVM->lastError_)) + { + sq_pushobject(vm, pSquirrelVM->lastError_); + sq_resetobject(&pSquirrelVM->lastError_); + return sq_throwobject(vm); + } + + { + SQUserPointer p; + sq_getinstanceup(vm, 1, &p, 0); + new(p) ClassInstanceData(instance, pClassDesc, nullptr, true); + } + + sq_setreleasehook(vm, 1, &destructor_stub); + + return 0; +} + +SQInteger tostring_stub(HSQUIRRELVM vm) +{ + ClassInstanceData* classInstanceData = nullptr; + sq_getinstanceup(vm, 1, (SQUserPointer*)&classInstanceData, 0); + + char buffer[128] = ""; + + if (classInstanceData && + classInstanceData->instance && + classInstanceData->desc->pHelper && + classInstanceData->desc->pHelper->ToString(classInstanceData->instance, buffer, sizeof(buffer))) + { + sq_pushstring(vm, buffer, -1); + } + else if (classInstanceData) + { + sqstd_pushstringf(vm, "(%s : 0x%p)", classInstanceData->desc->m_pszScriptName, classInstanceData->instance); + } + else + { + HSQOBJECT obj; + sq_resetobject(&obj); + sq_getstackobj(vm, 1, &obj); + // Semi-based on SQVM::ToString default case + sqstd_pushstringf(vm, "(%s: 0x%p)", IdType2Name(obj._type), (void*)_rawval(obj)); + } + + return 1; +} + +SQInteger get_stub(HSQUIRRELVM vm) +{ + ClassInstanceData* classInstanceData = nullptr; + sq_getinstanceup(vm, 1, (SQUserPointer*)&classInstanceData, 0); + + const char* key = nullptr; + sq_getstring(vm, 2, &key); + + if (key == nullptr) + { + return sq_throwerror(vm, "Expected _get(string)"); + } + + ScriptVariant_t var; + if (classInstanceData && + classInstanceData->instance && + classInstanceData->desc->pHelper && + classInstanceData->desc->pHelper->Get(classInstanceData->instance, key, var)) + { + PushVariant(vm, var); + } + else + { + return sqstd_throwerrorf(vm, "the index '%.50s' does not exist", key); + } + + return 1; +} + +SQInteger set_stub(HSQUIRRELVM vm) +{ + ClassInstanceData* classInstanceData = nullptr; + sq_getinstanceup(vm, 1, (SQUserPointer*)&classInstanceData, 0); + + const char* key = nullptr; + sq_getstring(vm, 2, &key); + + if (key == nullptr) + { + return sq_throwerror(vm, "Expected _set(string)"); + } + + ScriptVariant_t var; + getVariant( vm, -1, var ); + + if (classInstanceData && + classInstanceData->instance && + classInstanceData->desc->pHelper && + classInstanceData->desc->pHelper->Set(classInstanceData->instance, key, var)) + { + sq_pop(vm, 1); + } + else + { + sq_pop(vm, 1); + return sqstd_throwerrorf(vm, "the index '%.50s' does not exist", key); + } + + return 0; +} + +SQInteger add_stub(HSQUIRRELVM vm) +{ + ClassInstanceData* classInstanceData = nullptr; + sq_getinstanceup(vm, 1, (SQUserPointer*)&classInstanceData, 0); + + ScriptVariant_t var; + getVariant( vm, 1, var ); + + if (classInstanceData && + classInstanceData->instance && + classInstanceData->desc->pHelper) + { + ScriptVariant_t *result = classInstanceData->desc->pHelper->Add( classInstanceData->instance, var ); + if (result != nullptr) + { + PushVariant( vm, *result ); + sq_pop(vm, 1); + return 1; + } + } + + sq_pop(vm, 1); + return sqstd_throwerrorf(vm, "invalid arith op +"); +} + +SQInteger sub_stub(HSQUIRRELVM vm) +{ + ClassInstanceData* classInstanceData = nullptr; + sq_getinstanceup(vm, 1, (SQUserPointer*)&classInstanceData, 0); + + ScriptVariant_t var; + getVariant( vm, 1, var ); + + if (classInstanceData && + classInstanceData->instance && + classInstanceData->desc->pHelper) + { + ScriptVariant_t *result = classInstanceData->desc->pHelper->Subtract( classInstanceData->instance, var ); + if (result != nullptr) + { + PushVariant( vm, *result ); + sq_pop(vm, 1); + return 1; + } + } + + sq_pop(vm, 1); + return sqstd_throwerrorf(vm, "invalid arith op -"); +} + +SQInteger mul_stub(HSQUIRRELVM vm) +{ + ClassInstanceData* classInstanceData = nullptr; + sq_getinstanceup(vm, 1, (SQUserPointer*)&classInstanceData, 0); + + ScriptVariant_t var; + getVariant( vm, 1, var ); + + if (classInstanceData && + classInstanceData->instance && + classInstanceData->desc->pHelper ) + { + ScriptVariant_t *result = classInstanceData->desc->pHelper->Add( classInstanceData->instance, var ); + if (result != nullptr) + { + PushVariant( vm, *result ); + sq_pop(vm, 1); + return 1; + } + } + + sq_pop(vm, 1); + return sqstd_throwerrorf(vm, "invalid arith op *"); +} + +SQInteger div_stub(HSQUIRRELVM vm) +{ + ClassInstanceData* classInstanceData = nullptr; + sq_getinstanceup(vm, 1, (SQUserPointer*)&classInstanceData, 0); + + ScriptVariant_t var; + getVariant( vm, 1, var ); + + if (classInstanceData && + classInstanceData->instance && + classInstanceData->desc->pHelper ) + { + ScriptVariant_t *result = classInstanceData->desc->pHelper->Add( classInstanceData->instance, var ); + if (result != nullptr) + { + PushVariant( vm, *result ); + sq_pop(vm, 1); + return 1; + } + } + + sq_pop(vm, 1); + return sqstd_throwerrorf(vm, "invalid arith op /"); +} + +SQInteger IsValid_stub(HSQUIRRELVM vm) +{ + ClassInstanceData* classInstanceData = nullptr; + sq_getinstanceup(vm, 1, (SQUserPointer*)&classInstanceData, 0); + sq_pushbool(vm, classInstanceData != nullptr); + return 1; +} + +SQInteger weakref_stub(HSQUIRRELVM vm) +{ + sq_weakref(vm, 1); + return 1; +} + +SQInteger getclass_stub(HSQUIRRELVM vm) +{ + sq_getclass(vm, 1); + sq_push(vm, -1); + return 1; +} + +struct SquirrelSafeCheck +{ + SquirrelSafeCheck(HSQUIRRELVM vm, int outputCount = 0) : + vm_{ vm }, + top_{ sq_gettop(vm) }, + outputCount_{ outputCount } + {} + + ~SquirrelSafeCheck() + { + SQInteger curtop = sq_gettop(vm_); + SQInteger diff = curtop - outputCount_; + if ( top_ != diff ) + { + Assert(!"Squirrel VM stack is not consistent"); + Error("Squirrel VM stack is not consistent\n"); + } + + // TODO: Handle error state checks + } + + HSQUIRRELVM vm_; + SQInteger top_; + SQInteger outputCount_; +}; + + +void printfunc(HSQUIRRELVM SQ_UNUSED_ARG(v), const SQChar* format, ...) +{ + va_list args; + char buffer[2048]; + va_start(args, format); + V_vsnprintf(buffer, sizeof(buffer), format, args); + va_end(args); + CGMsg(0, CON_GROUP_VSCRIPT_PRINT, "%s", buffer); +} + +void errorfunc(HSQUIRRELVM SQ_UNUSED_ARG(v), const SQChar* format, ...) +{ + va_list args; + char buffer[2048]; + va_start(args, format); + V_vsnprintf(buffer, sizeof(buffer), format, args); + va_end(args); + Warning("%s", buffer); +} + +const char * ScriptDataTypeToName(ScriptDataType_t datatype) +{ + switch (datatype) + { + case FIELD_VOID: return "void"; + case FIELD_FLOAT: return "float"; + case FIELD_CSTRING: return "string"; + case FIELD_VECTOR: return "Vector"; + case FIELD_INTEGER: return "int"; + case FIELD_BOOLEAN: return "bool"; + case FIELD_CHARACTER: return "char"; + case FIELD_HSCRIPT: return "handle"; + case FIELD_VARIANT: return "variant"; + default: return ""; + } +} + + +#define PushDocumentationRegisterFunction( szName ) \ + sq_pushroottable(vm); \ + sq_pushstring(vm, "__Documentation", -1); \ + sq_get(vm, -2); \ + sq_pushstring(vm, szName, -1); \ + sq_get(vm, -2); \ + sq_push(vm, -2); + +#define CallDocumentationRegisterFunction( paramcount ) \ + sq_call(vm, paramcount+1, SQFalse, SQFalse); \ + sq_pop(vm, 3); + +void RegisterDocumentation(HSQUIRRELVM vm, const ScriptFuncDescriptor_t& pFuncDesc, ScriptClassDesc_t* pClassDesc = nullptr) +{ + if ( !developer.GetInt() ) + return; + + SquirrelSafeCheck safeCheck(vm); + + if (pFuncDesc.m_pszDescription && pFuncDesc.m_pszDescription[0] == SCRIPT_HIDE[0]) + return; + + char name[256] = ""; + + if (pClassDesc) + { + V_strcat_safe(name, pClassDesc->m_pszScriptName); + V_strcat_safe(name, "::"); + } + + V_strcat_safe(name, pFuncDesc.m_pszScriptName); + + + char signature[256] = ""; + V_snprintf(signature, sizeof(signature), "%s %s(", ScriptDataTypeToName(pFuncDesc.m_ReturnType), name); + + for (int i = 0; i < pFuncDesc.m_Parameters.Count(); ++i) + { + if (i != 0) + V_strcat_safe(signature, ", "); + + V_strcat_safe(signature, ScriptDataTypeToName(pFuncDesc.m_Parameters[i])); + } + + V_strcat_safe(signature, ")"); + + // RegisterHelp(name, signature, description) + PushDocumentationRegisterFunction( "RegisterHelp" ); + sq_pushstring(vm, name, -1); + sq_pushstring(vm, signature, -1); + sq_pushstring(vm, pFuncDesc.m_pszDescription ? pFuncDesc.m_pszDescription : "", -1); + CallDocumentationRegisterFunction( 3 ); +} + +void RegisterClassDocumentation(HSQUIRRELVM vm, const ScriptClassDesc_t* pClassDesc) +{ + if ( !developer.GetInt() ) + return; + + SquirrelSafeCheck safeCheck(vm); + + const char *name = pClassDesc->m_pszScriptName; + const char *base = ""; + if (pClassDesc->m_pBaseDesc) + { + base = pClassDesc->m_pBaseDesc->m_pszScriptName; + } + + const char *description = pClassDesc->m_pszDescription; + if (description) + { + if (description[0] == SCRIPT_HIDE[0]) + return; + if (description[0] == SCRIPT_SINGLETON[0]) + description++; + } + else + { + description = ""; + } + + // RegisterClassHelp(name, base, description) + PushDocumentationRegisterFunction( "RegisterClassHelp" ); + sq_pushstring(vm, name, -1); + sq_pushstring(vm, base, -1); + sq_pushstring(vm, description, -1); + CallDocumentationRegisterFunction( 3 ); +} + +void RegisterEnumDocumentation(HSQUIRRELVM vm, const ScriptEnumDesc_t* pClassDesc) +{ + if ( !developer.GetInt() ) + return; + + SquirrelSafeCheck safeCheck(vm); + + if (pClassDesc->m_pszDescription && pClassDesc->m_pszDescription[0] == SCRIPT_HIDE[0]) + return; + + const char *name = pClassDesc->m_pszScriptName; + + // RegisterEnumHelp(name, description) + PushDocumentationRegisterFunction( "RegisterEnumHelp" ); + sq_pushstring(vm, name, -1); + sq_pushinteger(vm, pClassDesc->m_ConstantBindings.Count()); + sq_pushstring(vm, pClassDesc->m_pszDescription ? pClassDesc->m_pszDescription : "", -1); + CallDocumentationRegisterFunction( 3 ); +} + +void RegisterConstantDocumentation( HSQUIRRELVM vm, const ScriptConstantBinding_t* pConstDesc, const char *pszAsString, ScriptEnumDesc_t* pEnumDesc = nullptr ) +{ + if ( !developer.GetInt() ) + return; + + SquirrelSafeCheck safeCheck(vm); + + if (pConstDesc->m_pszDescription && pConstDesc->m_pszDescription[0] == SCRIPT_HIDE[0]) + return; + + char name[256] = ""; + + if (pEnumDesc) + { + V_strcat_safe(name, pEnumDesc->m_pszScriptName); + V_strcat_safe(name, "."); + } + + V_strcat_safe(name, pConstDesc->m_pszScriptName); + + char signature[256] = ""; + V_snprintf(signature, sizeof(signature), "%s (%s)", pszAsString, ScriptDataTypeToName(pConstDesc->m_data.m_type)); + + // RegisterConstHelp(name, signature, description) + PushDocumentationRegisterFunction( "RegisterConstHelp" ); + sq_pushstring(vm, name, -1); + sq_pushstring(vm, signature, -1); + sq_pushstring(vm, pConstDesc->m_pszDescription ? pConstDesc->m_pszDescription : "", -1); + CallDocumentationRegisterFunction( 3 ); +} + +void RegisterHookDocumentation(HSQUIRRELVM vm, const ScriptHook_t* pHook, const ScriptFuncDescriptor_t& pFuncDesc, ScriptClassDesc_t* pClassDesc = nullptr) +{ + if ( !developer.GetInt() ) + return; + + SquirrelSafeCheck safeCheck(vm); + + if (pFuncDesc.m_pszDescription && pFuncDesc.m_pszDescription[0] == SCRIPT_HIDE[0]) + return; + + char name[256] = ""; + + if (pClassDesc) + { + V_strcat_safe(name, pClassDesc->m_pszScriptName); + V_strcat_safe(name, " -> "); + } + + V_strcat_safe(name, pFuncDesc.m_pszScriptName); + + + char signature[256] = ""; + V_snprintf(signature, sizeof(signature), "%s %s(", ScriptDataTypeToName(pFuncDesc.m_ReturnType), name); + + for (int i = 0; i < pFuncDesc.m_Parameters.Count(); ++i) + { + if (i != 0) + V_strcat_safe(signature, ", "); + + V_strcat_safe(signature, ScriptDataTypeToName(pFuncDesc.m_Parameters[i])); + V_strcat_safe(signature, " ["); + V_strcat_safe(signature, pHook->m_pszParameterNames[i]); + V_strcat_safe(signature, "]"); + } + + V_strcat_safe(signature, ")"); + + // RegisterHookHelp(name, signature, description) + PushDocumentationRegisterFunction( "RegisterHookHelp" ); + sq_pushstring(vm, name, -1); + sq_pushstring(vm, signature, -1); + sq_pushstring(vm, pFuncDesc.m_pszDescription ? pFuncDesc.m_pszDescription : "", -1); + CallDocumentationRegisterFunction( 3 ); +} + +void RegisterMemberDocumentation(HSQUIRRELVM vm, const ScriptMemberDesc_t& pDesc, ScriptClassDesc_t* pClassDesc = nullptr) +{ + if ( !developer.GetInt() ) + return; + + SquirrelSafeCheck safeCheck(vm); + + if (pDesc.m_pszDescription && pDesc.m_pszDescription[0] == SCRIPT_HIDE[0]) + return; + + char name[256] = ""; + + if (pClassDesc) + { + V_strcat_safe(name, pClassDesc->m_pszScriptName); + V_strcat_safe(name, "."); + } + + if (pDesc.m_pszScriptName) + V_strcat_safe(name, pDesc.m_pszScriptName); + + char signature[256] = ""; + V_snprintf(signature, sizeof(signature), "%s %s", ScriptDataTypeToName(pDesc.m_ReturnType), name); + + // RegisterMemberHelp(name, signature, description) + PushDocumentationRegisterFunction( "RegisterMemberHelp" ); + sq_pushstring(vm, name, -1); + sq_pushstring(vm, signature, -1); + sq_pushstring(vm, pDesc.m_pszDescription ? pDesc.m_pszDescription : "", -1); + CallDocumentationRegisterFunction( 3 ); +} + +SQInteger GetDeveloperLevel(HSQUIRRELVM vm) +{ + sq_pushinteger( vm, developer.GetInt() ); + return 1; +} + + +bool SquirrelVM::Init() +{ + vm_ = sq_open(1024); //creates a VM with initial stack size 1024 + + if (vm_ == nullptr) + return false; + + sq_setforeignptr(vm_, this); + sq_resetobject(&lastError_); + + sq_setprintfunc(vm_, printfunc, errorfunc); + + + { + sq_pushroottable(vm_); + + sqstd_register_mathlib(vm_); + sqstd_register_stringlib(vm_); + vectorClass_ = SQVector::register_class(vm_); + + // We exclude these libraries as they create a security risk on the client even + // though I'm sure if someone tried hard enough they could achieve all sorts of + // things with the other APIs, this just makes it a little bit harder for a map + // created by someone in the community causing a bunch of security vulnerablilties. + // + // Pretty sure DoIncludeScript() is already a vulnerability vector here, however + // that also depends on compile errors not showing up and relies on IFilesystem with + // a path prefix. + // + //sqstd_register_bloblib(vm_); + //sqstd_register_iolib(vm_); + //sqstd_register_systemlib(vm_); + + // There is no vulnerability in getting time. + sqstd_register_timelib(vm_); + + + sqstd_seterrorhandlers(vm_); + + { + // Unfortunately we can not get the pattern from a regexp instance + // so we need to wrap it with our own to get it. + if (Run(R"script( + class regexp extends regexp + { + constructor(pattern) + { + base.constructor(pattern); + this.pattern_ = pattern; + } + pattern_ = ""; + } + )script") == SCRIPT_ERROR) + { + this->Shutdown(); + return false; + } + + sq_resetobject(®expClass_); + sq_pushstring(vm_, "regexp", -1); + sq_rawget(vm_, -2); + sq_getstackobj(vm_, -1, ®expClass_); + sq_addref(vm_, ®expClass_); + sq_pop(vm_, 1); + } + + sq_pushstring( vm_, "developer", -1 ); + sq_newclosure( vm_, &GetDeveloperLevel, 0 ); + //sq_setnativeclosurename( vm_, -1, "developer" ); + sq_newslot( vm_, -3, SQFalse ); + + sq_pop(vm_, 1); + } + + if (Run(g_Script_vscript_squirrel) != SCRIPT_DONE) + { + this->Shutdown(); + return false; + } + + return true; +} + +void SquirrelVM::Shutdown() +{ + if (vm_) + { + sq_release(vm_, &vectorClass_); + sq_release(vm_, ®expClass_); + + sq_close(vm_); + vm_ = nullptr; + } +} + +bool SquirrelVM::ConnectDebugger() +{ + // TODO: Debugger support + return false; +} + +void SquirrelVM::DisconnectDebugger() +{ + // TODO: Debugger support +} + +ScriptLanguage_t SquirrelVM::GetLanguage() +{ + return SL_SQUIRREL; +} + +const char* SquirrelVM::GetLanguageName() +{ + return "squirrel"; +} + +void SquirrelVM::AddSearchPath(const char* pszSearchPath) +{ + // TODO: Search path support +} + +bool SquirrelVM::Frame(float simTime) +{ + // TODO: Frame support + return false; +} + +ScriptStatus_t SquirrelVM::Run(const char* pszScript, bool bWait) +{ + SquirrelSafeCheck safeCheck(vm_); + if (SQ_FAILED(sq_compilebuffer(vm_, pszScript, strlen(pszScript), "", SQTrue))) + { + return SCRIPT_ERROR; + } + + sq_pushroottable(vm_); + if (SQ_FAILED(sq_call(vm_, 1, SQFalse, SQTrue))) + { + sq_pop(vm_, 1); + return SCRIPT_ERROR; + } + + sq_pop(vm_, 1); + return SCRIPT_DONE; +} + +HSCRIPT SquirrelVM::CompileScript(const char* pszScript, const char* pszId) +{ + SquirrelSafeCheck safeCheck(vm_); + + Assert(vm_); + if (pszId == nullptr) pszId = ""; + if (SQ_FAILED(sq_compilebuffer(vm_, pszScript, strlen(pszScript), pszId, SQTrue))) + { + return nullptr; + } + HSQOBJECT* obj = new HSQOBJECT; + sq_resetobject(obj); + sq_getstackobj(vm_, -1, obj); + sq_addref(vm_, obj); + sq_pop(vm_, 1); + + return (HSCRIPT)obj; +} + +void SquirrelVM::ReleaseScript(HSCRIPT hScript) +{ + SquirrelSafeCheck safeCheck(vm_); + if (!hScript) return; + HSQOBJECT* obj = (HSQOBJECT*)hScript; + sq_release(vm_, obj); + delete obj; +} + +ScriptStatus_t SquirrelVM::Run(HSCRIPT hScript, HSCRIPT hScope, bool bWait) +{ + SquirrelSafeCheck safeCheck(vm_); + HSQOBJECT* obj = (HSQOBJECT*)hScript; + sq_pushobject(vm_, *obj); + + if (hScope) + { + Assert(hScope != INVALID_HSCRIPT); + HSQOBJECT* scope = (HSQOBJECT*)hScope; + sq_pushobject(vm_, *scope); + } + else + { + sq_pushroottable(vm_); + } + + auto result = sq_call(vm_, 1, false, true); + sq_pop(vm_, 1); + if (SQ_FAILED(result)) + { + return SCRIPT_ERROR; + } + return SCRIPT_DONE; +} + +ScriptStatus_t SquirrelVM::Run(HSCRIPT hScript, bool bWait) +{ + SquirrelSafeCheck safeCheck(vm_); + HSQOBJECT* obj = (HSQOBJECT*)hScript; + sq_pushobject(vm_, *obj); + sq_pushroottable(vm_); + auto result = sq_call(vm_, 1, false, true); + sq_pop(vm_, 1); + if (SQ_FAILED(result)) + { + return SCRIPT_ERROR; + } + return SCRIPT_DONE; +} + +HSCRIPT SquirrelVM::CreateScope(const char* pszScope, HSCRIPT hParent) +{ + SquirrelSafeCheck safeCheck(vm_); + + sq_newtable(vm_); + + if (hParent) + { + HSQOBJECT* parent = (HSQOBJECT*)hParent; + Assert(hParent != INVALID_HSCRIPT); + sq_pushobject(vm_, *parent); + } + else + { + sq_pushroottable(vm_); + } + + sq_pushstring(vm_, pszScope, -1); + sq_push(vm_, -3); + sq_rawset(vm_, -3); + + sq_pushstring(vm_, "__vname", -1); + sq_pushstring(vm_, pszScope, -1); + sq_rawset(vm_, -4); + + if (SQ_FAILED(sq_setdelegate(vm_, -2))) + { + sq_pop(vm_, 2); + return nullptr; + } + + HSQOBJECT* obj = new HSQOBJECT; + sq_resetobject(obj); + sq_getstackobj(vm_, -1, obj); + sq_addref(vm_, obj); + sq_pop(vm_, 1); + + return (HSCRIPT)obj; +} + +void SquirrelVM::ReleaseScope(HSCRIPT hScript) +{ + SquirrelSafeCheck safeCheck(vm_); + if (!hScript) return; + HSQOBJECT* obj = (HSQOBJECT*)hScript; + sq_pushobject(vm_, *obj); + + sq_getdelegate(vm_, -1); + + sq_pushstring(vm_, "__vname", -1); + sq_rawdeleteslot(vm_, -2, SQFalse); + + sq_pop(vm_, 2); + + sq_release(vm_, obj); + delete obj; +} + +HSCRIPT SquirrelVM::LookupFunction(const char* pszFunction, HSCRIPT hScope) +{ + SquirrelSafeCheck safeCheck(vm_); + if (hScope) + { + HSQOBJECT* scope = (HSQOBJECT*)hScope; + Assert(hScope != INVALID_HSCRIPT); + sq_pushobject(vm_, *scope); + } + else + { + sq_pushroottable(vm_); + } + sq_pushstring(vm_, _SC(pszFunction), -1); + + HSQOBJECT obj; + sq_resetobject(&obj); + + if (sq_get(vm_, -2) == SQ_OK) + { + sq_getstackobj(vm_, -1, &obj); + sq_addref(vm_, &obj); + sq_pop(vm_, 1); + } + sq_pop(vm_, 1); + + if (!sq_isclosure(obj)) + { + sq_release(vm_, &obj); + return nullptr; + } + + HSQOBJECT* pObj = new HSQOBJECT; + *pObj = obj; + return (HSCRIPT)pObj; +} + +void SquirrelVM::ReleaseFunction(HSCRIPT hScript) +{ + SquirrelSafeCheck safeCheck(vm_); + if (!hScript) return; + HSQOBJECT* obj = (HSQOBJECT*)hScript; + sq_release(vm_, obj); + delete obj; +} + +ScriptStatus_t SquirrelVM::ExecuteFunction(HSCRIPT hFunction, ScriptVariant_t* pArgs, int nArgs, ScriptVariant_t* pReturn, HSCRIPT hScope, bool bWait) +{ + SquirrelSafeCheck safeCheck(vm_); + if (!hFunction) + return SCRIPT_ERROR; + + if (hFunction == INVALID_HSCRIPT) + return SCRIPT_ERROR; + + HSQOBJECT* pFunc = (HSQOBJECT*)hFunction; + sq_pushobject(vm_, *pFunc); + + if (hScope) + { + Assert(hScope != INVALID_HSCRIPT); + HSQOBJECT* scope = (HSQOBJECT*)hScope; + sq_pushobject(vm_, *scope); + } + else + { + sq_pushroottable(vm_); + } + + for (int i = 0; i < nArgs; ++i) + { + PushVariant(vm_, pArgs[i]); + } + + bool hasReturn = pReturn != nullptr; + + if (SQ_FAILED(sq_call(vm_, nArgs + 1, hasReturn, SQTrue))) + { + sq_pop(vm_, 1); + return SCRIPT_ERROR; + } + + if (hasReturn) + { + if (!getVariant(vm_, -1, *pReturn)) + { + sq_pop(vm_, 1); + return SCRIPT_ERROR; + } + + sq_pop(vm_, 1); + } + + sq_pop(vm_, 1); + return SCRIPT_DONE; +} + +HScriptRaw SquirrelVM::HScriptToRaw( HSCRIPT val ) +{ + Assert( val ); + Assert( val != INVALID_HSCRIPT ); + + HSQOBJECT *obj = (HSQOBJECT*)val; +#if 0 + if ( sq_isweakref(*obj) ) + return obj->_unVal.pWeakRef->_obj._unVal.raw; +#endif + return obj->_unVal.raw; +} + +ScriptStatus_t SquirrelVM::ExecuteHookFunction(const char *pszEventName, ScriptVariant_t* pArgs, int nArgs, ScriptVariant_t* pReturn, HSCRIPT hScope, bool bWait) +{ + SquirrelSafeCheck safeCheck(vm_); + + HSQOBJECT* pFunc = (HSQOBJECT*)GetScriptHookManager().GetHookFunction(); + sq_pushobject(vm_, *pFunc); + + // The call environment of the Hooks::Call function does not matter + // as the function does not access any member variables. + sq_pushroottable(vm_); + + sq_pushstring(vm_, pszEventName, -1); + + if (hScope) + sq_pushobject(vm_, *((HSQOBJECT*)hScope)); + else + sq_pushnull(vm_); // global hook + + for (int i = 0; i < nArgs; ++i) + { + PushVariant(vm_, pArgs[i]); + } + + bool hasReturn = pReturn != nullptr; + + if (SQ_FAILED(sq_call(vm_, nArgs + 3, hasReturn, SQTrue))) + { + sq_pop(vm_, 1); + return SCRIPT_ERROR; + } + + if (hasReturn) + { + if (!getVariant(vm_, -1, *pReturn)) + { + sq_pop(vm_, 1); + return SCRIPT_ERROR; + } + + sq_pop(vm_, 1); + } + + sq_pop(vm_, 1); + return SCRIPT_DONE; +} + +void SquirrelVM::RegisterFunction(ScriptFunctionBinding_t* pScriptFunction) +{ + SquirrelSafeCheck safeCheck(vm_); + Assert(pScriptFunction); + + if (!pScriptFunction) + return; + + char typemask[64]; + if (!CreateParamCheck(*pScriptFunction, typemask)) + { + return; + } + + sq_pushroottable(vm_); + + sq_pushstring(vm_, pScriptFunction->m_desc.m_pszScriptName, -1); + + sq_pushuserpointer(vm_, pScriptFunction); + sq_newclosure(vm_, function_stub, 1); + + sq_setnativeclosurename(vm_, -1, pScriptFunction->m_desc.m_pszScriptName); + + sq_setparamscheck(vm_, pScriptFunction->m_desc.m_Parameters.Count() + 1, typemask); + bool isStatic = false; + sq_newslot(vm_, -3, isStatic); + + sq_pop(vm_, 1); + + RegisterDocumentation(vm_, pScriptFunction->m_desc); +} + +bool SquirrelVM::RegisterClass(ScriptClassDesc_t* pClassDesc) +{ + SquirrelSafeCheck safeCheck(vm_); + + sq_pushroottable(vm_); + sq_pushstring(vm_, pClassDesc->m_pszScriptName, -1); + + // Check if class name is already taken + if (sq_get(vm_, -2) == SQ_OK) + { + HSQOBJECT obj; + sq_resetobject(&obj); + sq_getstackobj(vm_, -1, &obj); + if (!sq_isnull(obj)) + { + sq_pop(vm_, 2); + return false; + } + + sq_pop(vm_, 1); + } + + // Register base in case it doesn't exist + if (pClassDesc->m_pBaseDesc) + { + RegisterClass(pClassDesc->m_pBaseDesc); + + // Check if the base class can be + sq_pushstring(vm_, pClassDesc->m_pBaseDesc->m_pszScriptName, -1); + if (sq_get(vm_, -2) != SQ_OK) + { + sq_pop(vm_, 1); + return false; + } + } + + if (SQ_FAILED(sq_newclass(vm_, pClassDesc->m_pBaseDesc ? SQTrue : SQFalse))) + { + sq_pop(vm_, 2 + (pClassDesc->m_pBaseDesc ? 1 : 0)); + return false; + } + + sq_settypetag(vm_, -1, pClassDesc); + + sq_setclassudsize(vm_, -1, sizeof(ClassInstanceData)); + + sq_pushstring(vm_, "constructor", -1); + sq_newclosure(vm_, constructor_stub, 0); + sq_newslot(vm_, -3, SQFalse); + + sq_pushstring(vm_, "_tostring", -1); + sq_newclosure(vm_, tostring_stub, 0); + sq_newslot(vm_, -3, SQFalse); + + sq_pushstring(vm_, "_get", -1); + sq_newclosure(vm_, get_stub, 0); + sq_newslot(vm_, -3, SQFalse); + + sq_pushstring(vm_, "_set", -1); + sq_newclosure(vm_, set_stub, 0); + sq_newslot(vm_, -3, SQFalse); + + sq_pushstring(vm_, "_add", -1); + sq_newclosure(vm_, add_stub, 0); + sq_newslot(vm_, -3, SQFalse); + + sq_pushstring(vm_, "_sub", -1); + sq_newclosure(vm_, sub_stub, 0); + sq_newslot(vm_, -3, SQFalse); + + sq_pushstring(vm_, "_mul", -1); + sq_newclosure(vm_, mul_stub, 0); + sq_newslot(vm_, -3, SQFalse); + + sq_pushstring(vm_, "_div", -1); + sq_newclosure(vm_, div_stub, 0); + sq_newslot(vm_, -3, SQFalse); + + sq_pushstring(vm_, "IsValid", -1); + sq_newclosure(vm_, IsValid_stub, 0); + sq_newslot(vm_, -3, SQFalse); + + sq_pushstring(vm_, "weakref", -1); + sq_newclosure(vm_, weakref_stub, 0); + sq_newslot(vm_, -3, SQFalse); + + sq_pushstring(vm_, "getclass", -1); + sq_newclosure(vm_, getclass_stub, 0); + sq_newslot(vm_, -3, SQFalse); + + + for (int i = 0; i < pClassDesc->m_FunctionBindings.Count(); ++i) + { + auto& scriptFunction = pClassDesc->m_FunctionBindings[i]; + + char typemask[64]; + if (!CreateParamCheck(scriptFunction, typemask)) + { + Warning("Unable to create param check for %s.%s\n", + pClassDesc->m_pszClassname, scriptFunction.m_desc.m_pszFunction); + break; + } + + sq_pushstring(vm_, scriptFunction.m_desc.m_pszScriptName, -1); + + sq_pushuserpointer(vm_, &scriptFunction); + sq_newclosure(vm_, function_stub, 1); + + sq_setnativeclosurename(vm_, -1, scriptFunction.m_desc.m_pszScriptName); + + sq_setparamscheck(vm_, scriptFunction.m_desc.m_Parameters.Count() + 1, typemask); + bool isStatic = false; + sq_newslot(vm_, -3, isStatic); + + RegisterDocumentation(vm_, scriptFunction.m_desc, pClassDesc); + } + + for (int i = 0; i < pClassDesc->m_Hooks.Count(); ++i) + { + auto& scriptHook = pClassDesc->m_Hooks[i]; + + RegisterHookDocumentation(vm_, scriptHook, scriptHook->m_desc, pClassDesc); + } + + for (int i = 0; i < pClassDesc->m_Members.Count(); ++i) + { + auto& member = pClassDesc->m_Members[i]; + + RegisterMemberDocumentation(vm_, member, pClassDesc); + } + + sq_pushstring(vm_, pClassDesc->m_pszScriptName, -1); + sq_push(vm_, -2); + + if (SQ_FAILED(sq_newslot(vm_, -4, SQFalse))) + { + sq_pop(vm_, 4); + return false; + } + + sq_pop(vm_, 2); + + RegisterClassDocumentation(vm_, pClassDesc); + + return true; +} + +void SquirrelVM::RegisterConstant(ScriptConstantBinding_t* pScriptConstant) +{ + SquirrelSafeCheck safeCheck(vm_); + Assert(pScriptConstant); + + if (!pScriptConstant) + return; + + sq_pushconsttable(vm_); + sq_pushstring(vm_, pScriptConstant->m_pszScriptName, -1); + + PushVariant(vm_, pScriptConstant->m_data); + + sq_newslot(vm_, -3, SQFalse); + + sq_pop(vm_, 1); + + char szValue[64]; + GetVariantScriptString( pScriptConstant->m_data, szValue, sizeof(szValue) ); + RegisterConstantDocumentation(vm_, pScriptConstant, szValue); +} + +void SquirrelVM::RegisterEnum(ScriptEnumDesc_t* pEnumDesc) +{ + SquirrelSafeCheck safeCheck(vm_); + Assert(pEnumDesc); + + if (!pEnumDesc) + return; + + sq_newtableex(vm_, pEnumDesc->m_ConstantBindings.Count()); + + sq_pushconsttable(vm_); + + sq_pushstring(vm_, pEnumDesc->m_pszScriptName, -1); + sq_push(vm_, -3); + sq_rawset(vm_, -3); + + for (int i = 0; i < pEnumDesc->m_ConstantBindings.Count(); ++i) + { + auto& scriptConstant = pEnumDesc->m_ConstantBindings[i]; + + sq_pushstring(vm_, scriptConstant.m_pszScriptName, -1); + PushVariant(vm_, scriptConstant.m_data); + sq_rawset(vm_, -4); + + char szValue[64]; + GetVariantScriptString( scriptConstant.m_data, szValue, sizeof(szValue) ); + RegisterConstantDocumentation(vm_, &scriptConstant, szValue, pEnumDesc); + } + + sq_pop(vm_, 2); + + RegisterEnumDocumentation(vm_, pEnumDesc); +} + +void SquirrelVM::RegisterHook(ScriptHook_t* pHookDesc) +{ + SquirrelSafeCheck safeCheck(vm_); + Assert(pHookDesc); + + if (!pHookDesc) + return; + + RegisterHookDocumentation(vm_, pHookDesc, pHookDesc->m_desc, nullptr); +} + +HSCRIPT SquirrelVM::RegisterInstance(ScriptClassDesc_t* pDesc, void* pInstance, bool bAllowDestruct) +{ + SquirrelSafeCheck safeCheck(vm_); + + this->RegisterClass(pDesc); + + sq_pushroottable(vm_); + sq_pushstring(vm_, pDesc->m_pszScriptName, -1); + + if (sq_get(vm_, -2) != SQ_OK) + { + sq_pop(vm_, 1); + return nullptr; + } + + if (SQ_FAILED(sq_createinstance(vm_, -1))) + { + sq_pop(vm_, 2); + return nullptr; + } + + { + SQUserPointer p; + sq_getinstanceup(vm_, -1, &p, 0); + new(p) ClassInstanceData(pInstance, pDesc, nullptr, bAllowDestruct); + } + + sq_setreleasehook(vm_, -1, bAllowDestruct ? &destructor_stub : &destructor_stub_instance); + + HSQOBJECT* obj = new HSQOBJECT; + sq_resetobject(obj); + sq_getstackobj(vm_, -1, obj); + sq_addref(vm_, obj); + sq_pop(vm_, 3); + + return (HSCRIPT)obj; +} + +void SquirrelVM::SetInstanceUniqeId(HSCRIPT hInstance, const char* pszId) +{ + SquirrelSafeCheck safeCheck(vm_); + + if (!hInstance) return; + HSQOBJECT* obj = (HSQOBJECT*)hInstance; + sq_pushobject(vm_, *obj); + + SQUserPointer self; + sq_getinstanceup(vm_, -1, &self, nullptr); + + auto classInstanceData = (ClassInstanceData*)self; + + classInstanceData->instanceId = pszId; + + sq_poptop(vm_); +} + +void SquirrelVM::RemoveInstance(HSCRIPT hInstance) +{ + SquirrelSafeCheck safeCheck(vm_); + + if (!hInstance) return; + HSQOBJECT* obj = (HSQOBJECT*)hInstance; + sq_pushobject(vm_, *obj); + + SQUserPointer self; + sq_getinstanceup(vm_, -1, &self, nullptr); + + ((ClassInstanceData*)self)->~ClassInstanceData(); + + sq_setinstanceup(vm_, -1, nullptr); + sq_setreleasehook(vm_, -1, nullptr); + sq_pop(vm_, 1); + + sq_release(vm_, obj); + delete obj; +} + +void* SquirrelVM::GetInstanceValue(HSCRIPT hInstance, ScriptClassDesc_t* pExpectedType) +{ + SquirrelSafeCheck safeCheck(vm_); + + if (!hInstance) return nullptr; + HSQOBJECT* obj = (HSQOBJECT*)hInstance; + + if (pExpectedType) + { + sq_pushroottable(vm_); + sq_pushstring(vm_, pExpectedType->m_pszScriptName, -1); + + if (sq_get(vm_, -2) != SQ_OK) + { + sq_pop(vm_, 1); + return nullptr; + } + + sq_pushobject(vm_, *obj); + + if (sq_instanceof(vm_) != SQTrue) + { + sq_pop(vm_, 3); + return nullptr; + } + + sq_pop(vm_, 3); + } + + sq_pushobject(vm_, *obj); + SQUserPointer self; + sq_getinstanceup(vm_, -1, &self, nullptr); + sq_pop(vm_, 1); + + auto classInstanceData = (ClassInstanceData*)self; + + if (!classInstanceData) + { + return nullptr; + } + + + return classInstanceData->instance; +} + +bool SquirrelVM::GenerateUniqueKey(const char* pszRoot, char* pBuf, int nBufSize) +{ + static unsigned keyIdx = 0; + // This gets used for script scope, still confused why it needs to be inside IScriptVM + // is it just to be a compatible name for CreateScope? + V_snprintf(pBuf, nBufSize, "%08X_%s", ++keyIdx, pszRoot); + return true; +} + +bool SquirrelVM::ValueExists(HSCRIPT hScope, const char* pszKey) +{ + SquirrelSafeCheck safeCheck(vm_); + if (hScope) + { + HSQOBJECT* scope = (HSQOBJECT*)hScope; + Assert(hScope != INVALID_HSCRIPT); + sq_pushobject(vm_, *scope); + } + else + { + sq_pushroottable(vm_); + } + + sq_pushstring(vm_, pszKey, -1); + + if (sq_get(vm_, -2) != SQ_OK) + { + sq_pop(vm_, 1); + return false; + } + + sq_pop(vm_, 2); + return true; +} + +bool SquirrelVM::SetValue(HSCRIPT hScope, const char* pszKey, const char* pszValue) +{ + SquirrelSafeCheck safeCheck(vm_); + if (hScope) + { + HSQOBJECT* scope = (HSQOBJECT*)hScope; + Assert(hScope != INVALID_HSCRIPT); + sq_pushobject(vm_, *scope); + } + else + { + sq_pushroottable(vm_); + } + + sq_pushstring(vm_, pszKey, -1); + sq_pushstring(vm_, pszValue, -1); + + sq_newslot(vm_, -3, SQFalse); + + sq_pop(vm_, 1); + return true; +} + +bool SquirrelVM::SetValue(HSCRIPT hScope, const char* pszKey, const ScriptVariant_t& value) +{ + SquirrelSafeCheck safeCheck(vm_); + if (hScope) + { + HSQOBJECT* scope = (HSQOBJECT*)hScope; + Assert(hScope != INVALID_HSCRIPT); + sq_pushobject(vm_, *scope); + } + else + { + sq_pushroottable(vm_); + } + + sq_pushstring(vm_, pszKey, -1); + PushVariant(vm_, value); + + sq_newslot(vm_, -3, SQFalse); + + sq_pop(vm_, 1); + return true; +} + +bool SquirrelVM::SetValue( HSCRIPT hScope, const ScriptVariant_t& key, const ScriptVariant_t& val ) +{ + SquirrelSafeCheck safeCheck(vm_); + HSQOBJECT obj = *(HSQOBJECT*)hScope; + if (hScope) + { + Assert(hScope != INVALID_HSCRIPT); + sq_pushobject(vm_, obj); + } + else + { + sq_pushroottable(vm_); + } + + if ( sq_isarray(obj) ) + { + Assert( key.m_type == FIELD_INTEGER ); + + sq_pushinteger(vm_, key.m_int); + PushVariant(vm_, val); + + sq_set(vm_, -3); + } + else + { + PushVariant(vm_, key); + PushVariant(vm_, val); + + sq_newslot(vm_, -3, SQFalse); + } + + sq_pop(vm_, 1); + return true; +} + +void SquirrelVM::CreateTable(ScriptVariant_t& Table) +{ + SquirrelSafeCheck safeCheck(vm_); + + sq_newtable(vm_); + + HSQOBJECT* obj = new HSQOBJECT; + sq_resetobject(obj); + sq_getstackobj(vm_, -1, obj); + sq_addref(vm_, obj); + sq_pop(vm_, 1); + + Table = (HSCRIPT)obj; +} + +// +// input table/array/class/instance/string +// +int SquirrelVM::GetNumTableEntries(HSCRIPT hScope) +{ + SquirrelSafeCheck safeCheck(vm_); + + if (!hScope) + { + // sq_getsize returns -1 on invalid input + return -1; + } + + HSQOBJECT* scope = (HSQOBJECT*)hScope; + Assert(hScope != INVALID_HSCRIPT); + sq_pushobject(vm_, *scope); + + int count = sq_getsize(vm_, -1); + + sq_pop(vm_, 1); + + return count; +} + +int SquirrelVM::GetKeyValue(HSCRIPT hScope, int nIterator, ScriptVariant_t* pKey, ScriptVariant_t* pValue) +{ + SquirrelSafeCheck safeCheck(vm_); + + if (hScope) + { + Assert(hScope != INVALID_HSCRIPT); + HSQOBJECT* scope = (HSQOBJECT*)hScope; + sq_pushobject(vm_, *scope); + } + else + { + sq_pushroottable(vm_); + } + + if (nIterator == -1) + { + sq_pushnull(vm_); + } + else + { + sq_pushinteger(vm_, nIterator); + } + + SQInteger nextiter = -1; + + if (SQ_SUCCEEDED(sq_next(vm_, -2))) + { + if (pKey) getVariant(vm_, -2, *pKey); + if (pValue) getVariant(vm_, -1, *pValue); + + sq_getinteger(vm_, -3, &nextiter); + sq_pop(vm_, 2); + } + sq_pop(vm_, 2); + + return nextiter; +} + +bool SquirrelVM::GetValue(HSCRIPT hScope, const char* pszKey, ScriptVariant_t* pValue) +{ +#ifdef _DEBUG + AssertMsg( pszKey, "FATAL: cannot get NULL" ); + + // Don't crash on debug + if ( !pszKey ) + return GetValue( hScope, ScriptVariant_t(0), pValue ); +#endif + + SquirrelSafeCheck safeCheck(vm_); + + Assert(pValue); + if (!pValue) + { + return false; + } + + if (hScope) + { + HSQOBJECT* scope = (HSQOBJECT*)hScope; + Assert(hScope != INVALID_HSCRIPT); + sq_pushobject(vm_, *scope); + } + else + { + sq_pushroottable(vm_); + } + + sq_pushstring(vm_, pszKey, -1); + + if (sq_get(vm_, -2) != SQ_OK) + { + sq_pop(vm_, 1); + return false; + } + + if (!getVariant(vm_, -1, *pValue)) + { + sq_pop(vm_, 2); + return false; + } + + sq_pop(vm_, 2); + + return true; +} + +bool SquirrelVM::GetValue( HSCRIPT hScope, ScriptVariant_t key, ScriptVariant_t* pValue ) +{ + SquirrelSafeCheck safeCheck(vm_); + + Assert(pValue); + if (!pValue) + { + return false; + } + + if (hScope) + { + HSQOBJECT* scope = (HSQOBJECT*)hScope; + Assert(hScope != INVALID_HSCRIPT); + sq_pushobject(vm_, *scope); + } + else + { + sq_pushroottable(vm_); + } + + PushVariant(vm_, key); + + if (sq_get(vm_, -2) != SQ_OK) + { + sq_pop(vm_, 1); + return false; + } + + if (!getVariant(vm_, -1, *pValue)) + { + sq_pop(vm_, 2); + return false; + } + + sq_pop(vm_, 2); + + return true; +} + + +void SquirrelVM::ReleaseValue(ScriptVariant_t& value) +{ + SquirrelSafeCheck safeCheck(vm_); + if (value.m_type == FIELD_HSCRIPT) + { + HSCRIPT hScript = value; + HSQOBJECT* obj = (HSQOBJECT*)hScript; + sq_release(vm_, obj); + delete obj; + } + else + { + value.Free(); + } + + // Let's prevent this being called again and giving some UB + value.m_type = FIELD_VOID; +} + +bool SquirrelVM::ClearValue(HSCRIPT hScope, const char* pszKey) +{ + SquirrelSafeCheck safeCheck(vm_); + + if (hScope) + { + HSQOBJECT* scope = (HSQOBJECT*)hScope; + Assert(hScope != INVALID_HSCRIPT); + sq_pushobject(vm_, *scope); + } + else + { + sq_pushroottable(vm_); + } + + sq_pushstring(vm_, pszKey, -1); + if (SQ_FAILED(sq_deleteslot(vm_, -2, SQFalse))) + { + sq_pop(vm_, 1); + return false; + } + + sq_pop(vm_, 1); + return true; +} + +bool SquirrelVM::ClearValue(HSCRIPT hScope, ScriptVariant_t pKey) +{ + SquirrelSafeCheck safeCheck(vm_); + + if (hScope) + { + HSQOBJECT* scope = (HSQOBJECT*)hScope; + Assert(hScope != INVALID_HSCRIPT); + sq_pushobject(vm_, *scope); + } + else + { + sq_pushroottable(vm_); + } + + PushVariant(vm_, pKey); + if (SQ_FAILED(sq_deleteslot(vm_, -2, SQFalse))) + { + sq_pop(vm_, 1); + return false; + } + + sq_pop(vm_, 1); + return true; +} + + +void SquirrelVM::CreateArray(ScriptVariant_t &arr, int size) +{ + SquirrelSafeCheck safeCheck(vm_); + + HSQOBJECT *obj = new HSQOBJECT; + sq_resetobject(obj); + + sq_newarray(vm_,size); + sq_getstackobj(vm_, -1, obj); + sq_addref(vm_, obj); + sq_pop(vm_, 1); + + arr = (HSCRIPT)obj; +} + +bool SquirrelVM::ArrayAppend(HSCRIPT hArray, const ScriptVariant_t &val) +{ + SquirrelSafeCheck safeCheck(vm_); + + HSQOBJECT *arr = (HSQOBJECT*)hArray; + + sq_pushobject(vm_, *arr); + PushVariant(vm_, val); + bool ret = sq_arrayappend(vm_, -2) == SQ_OK; + sq_pop(vm_, 1); + + return ret; +} + +enum ClassType +{ + VectorClassType = 0, + NativeClassType = 1, + ScriptClassType = 2 +}; + +SQInteger closure_write(SQUserPointer file, SQUserPointer p, SQInteger size) +{ + ((CUtlBuffer*)file)->Put(p, size); + return size; +} + +void SquirrelVM::WriteObject(CUtlBuffer* pBuffer, WriteStateMap& writeState, SQInteger idx) +{ + SquirrelSafeCheck safeCheck(vm_); + + HSQOBJECT obj; + sq_resetobject(&obj); + sq_getstackobj(vm_, idx, &obj); + + switch (obj._type) + { + case OT_NULL: + { + pBuffer->PutInt(OT_NULL); + break; + } + case OT_INTEGER: + { + pBuffer->PutInt(OT_INTEGER); + pBuffer->PutInt64(sq_objtointeger(&obj)); + break; + } + case OT_FLOAT: + { + pBuffer->PutInt(OT_FLOAT); + pBuffer->PutFloat(sq_objtofloat(&obj)); + break; + } + case OT_BOOL: + { + pBuffer->PutInt(OT_BOOL); + pBuffer->PutChar(sq_objtobool(&obj)); + break; + } + case OT_STRING: + { + pBuffer->PutInt(OT_STRING); + const char* val = nullptr; + SQInteger size = 0; + sq_getstringandsize(vm_, idx, &val, &size); + pBuffer->PutInt(size); + pBuffer->Put(val, size); + break; + } + case OT_TABLE: + { + pBuffer->PutInt(OT_TABLE); + if (writeState.CheckCache(pBuffer, obj._unVal.pTable)) + { + break; + } + sq_getdelegate(vm_, idx); + WriteObject(pBuffer, writeState, -1); + sq_poptop(vm_); + int count = sq_getsize(vm_, idx); + sq_push(vm_, idx); + sq_pushnull(vm_); + pBuffer->PutInt(count); + while (SQ_SUCCEEDED(sq_next(vm_, -2))) + { + WriteObject(pBuffer, writeState, -2); + WriteObject(pBuffer, writeState, -1); + sq_pop(vm_, 2); + --count; + } + sq_pop(vm_, 2); + Assert(count == 0); + break; + } + case OT_ARRAY: + { + pBuffer->PutInt(OT_ARRAY); + if (writeState.CheckCache(pBuffer, obj._unVal.pArray)) + { + break; + } + int count = sq_getsize(vm_, idx); + pBuffer->PutInt(count); + sq_push(vm_, idx); + sq_pushnull(vm_); + while (SQ_SUCCEEDED(sq_next(vm_, -2))) + { + WriteObject(pBuffer, writeState, -1); + sq_pop(vm_, 2); + --count; + } + sq_pop(vm_, 2); + Assert(count == 0); + break; + } + case OT_CLOSURE: + { + pBuffer->PutInt(OT_CLOSURE); + if (writeState.CheckCache(pBuffer, obj._unVal.pClosure)) + { + break; + } + + SQInteger nparams = 0, nfreevars = 0; + sq_getclosureinfo(vm_, idx, &nparams, &nfreevars); + if (nfreevars == 0 && _closure(obj)->_function->_defaultparams == 0) + { + pBuffer->PutChar(0); + + sq_push(vm_, idx); + if (SQ_FAILED(sq_writeclosure(vm_, closure_write, pBuffer))) + { + Error("Failed to write closure"); + } + sq_pop(vm_, 1); + } + else + { + // Unfortunately we can't use sq_writeclosure because it doesn't work well with + // outer variables + + pBuffer->PutChar(1); + + if (!_closure(obj)->Save(vm_, pBuffer, closure_write)) + { + Error("Failed to write closure\n"); + } + + int noutervalues = _closure(obj)->_function->_noutervalues; + for (int i = 0; i < noutervalues; ++i) + { + sq_pushobject(vm_, _closure(obj)->_outervalues[i]); + WriteObject(pBuffer, writeState, -1); + sq_poptop(vm_); + } + + int ndefaultparams = _closure(obj)->_function->_ndefaultparams; + for (int i = 0; i < ndefaultparams; ++i) + { + sq_pushobject(vm_, _closure(obj)->_defaultparams[i]); + WriteObject(pBuffer, writeState, -1); + sq_poptop(vm_); + } + } + + if (_closure(obj)->_env) + { + sq_pushobject(vm_, _closure(obj)->_env->_obj); + } + else + { + sq_pushnull(vm_); + } + WriteObject(pBuffer, writeState, -1); + sq_poptop(vm_); + + break; + } + case OT_NATIVECLOSURE: + { + pBuffer->PutInt(OT_NATIVECLOSURE); + sq_getclosurename(vm_, idx); + + const char* name = nullptr; + sq_getstring(vm_, -1, &name); + pBuffer->PutString(name); + + sq_pop(vm_, 1); + break; + } + case OT_CLASS: + { + pBuffer->PutInt(OT_CLASS); + if (writeState.CheckCache(pBuffer, obj._unVal.pClass)) + { + break; + } + SQUserPointer typetag = nullptr; + sq_gettypetag(vm_, idx, &typetag); + if (typetag == TYPETAG_VECTOR) + { + pBuffer->PutInt(VectorClassType); + } + else if (typetag != nullptr) + { + // Seems so dangerous to treat typetag as ScriptClassDesc_t* + // however we don't really have an option without some sort of tagged + // pointer. + pBuffer->PutInt(NativeClassType); + pBuffer->PutString(((ScriptClassDesc_t*)typetag)->m_pszScriptName); + } + else + { + // HACK: We can't easily identify when the type is a builtin to exclude + // so we just check against the only class we need to deal with at the moment + // which is "regexp" + const char* builtinName = nullptr; + if (_class(obj) == _class(regexpClass_)) + { + builtinName = "regexp"; + } + + if (builtinName) + { + pBuffer->PutInt(NativeClassType); + pBuffer->PutString(builtinName); + break; + } + + pBuffer->PutInt(ScriptClassType); + + sq_getbase(vm_, idx); + WriteObject(pBuffer, writeState, -1); + sq_pop(vm_, 1); + + sq_push(vm_, idx); + sq_pushnull(vm_); + sq_getattributes(vm_, -2); + WriteObject(pBuffer, writeState, -1); + sq_pop(vm_, 1); + + sq_pushnull(vm_); + while (SQ_SUCCEEDED(sq_next(vm_, -2))) + { + pBuffer->PutChar(1); + // TODO: Member Attributes + WriteObject(pBuffer, writeState, -2); + WriteObject(pBuffer, writeState, -1); + sq_pop(vm_, 2); + } + sq_pop(vm_, 2); + + { + // HACK: Meta-methods are not included in an iterator of OT_CLASS + SQObjectPtrVec& metamethods = *(_ss(vm_)->_metamethods); + for (int i = 0; i < MT_LAST; ++i) + { + if (sq_type(_class(obj)->_metamethods[i]) != OT_NULL) + { + pBuffer->PutChar(1); + sq_pushobject(vm_, metamethods[i]); + sq_pushobject(vm_, _class(obj)->_metamethods[i]); + WriteObject(pBuffer, writeState, -2); + WriteObject(pBuffer, writeState, -1); + sq_pop(vm_, 2); + } + } + } + + pBuffer->PutChar(0); + } + break; + } + case OT_INSTANCE: + { + pBuffer->PutInt(OT_INSTANCE); + if (writeState.CheckCache(pBuffer, obj._unVal.pInstance)) + { + break; + } + sq_getclass(vm_, idx); + WriteObject(pBuffer, writeState, -1); + sq_pop(vm_, 1); + + if (_instance(obj)->_class == _class(regexpClass_)) + { + sq_push(vm_, idx); + sq_pushstring(vm_, "pattern_", -1); + sq_rawget(vm_, -2); + WriteObject(pBuffer, writeState, -1); + sq_pop(vm_, 2); + break; + } + + { + // HACK: No way to get the default values part from accessing the class directly + SQUnsignedInteger nvalues = _instance(obj)->_class->_defaultvalues.size(); + for (SQUnsignedInteger n = 0; n < nvalues; n++) { + sq_pushobject(vm_, _instance(obj)->_values[n]); + WriteObject(pBuffer, writeState, -1); + sq_pop(vm_, 1); + } + } + + SQUserPointer typetag; + sq_gettypetag(vm_, idx, &typetag); + + if (typetag == TYPETAG_VECTOR) + { + Vector* v = nullptr; + sq_getinstanceup(vm_, idx, (SQUserPointer*)&v, TYPETAG_VECTOR); + Assert(v); + pBuffer->PutFloat(v->x); + pBuffer->PutFloat(v->y); + pBuffer->PutFloat(v->z); + } + else if (typetag) + { + ClassInstanceData* pClassInstanceData; + sq_getinstanceup(vm_, idx, (SQUserPointer*)&pClassInstanceData, typetag); + + if (pClassInstanceData) + { + if (pClassInstanceData->desc->m_pszDescription[0] == SCRIPT_SINGLETON[0]) + { + // Do nothing, singleton should be created from just the class + } + else if (!pClassInstanceData->instanceId.IsEmpty()) + { + pBuffer->PutString(pClassInstanceData->instanceId); + + pBuffer->PutChar(pClassInstanceData->allowDestruct ? 1 : 0); + } + else + { + DevWarning("SquirrelVM::WriteObject: Unable to find instanceID for object of type %s, unable to serialize\n", + pClassInstanceData->desc->m_pszClassname); + pBuffer->PutString(""); + } + } + else + { + pBuffer->PutString(""); + } + } + + break; + } + case OT_WEAKREF: + { + pBuffer->PutInt(OT_WEAKREF); + sq_getweakrefval(vm_, idx); + WriteObject(pBuffer, writeState, -1); + sq_pop(vm_, 1); + break; + } + case OT_FUNCPROTO: //internal usage only + { + pBuffer->PutInt(OT_FUNCPROTO); + + if (writeState.CheckCache(pBuffer, obj._unVal.pFunctionProto)) + { + break; + } + + _funcproto(obj)->Save(vm_, pBuffer, closure_write); + } + case OT_OUTER: //internal usage only + { + pBuffer->PutInt(OT_OUTER); + + if (writeState.CheckCache(pBuffer, obj._unVal.pOuter)) + { + break; + } + + sq_pushobject(vm_, *_outer(obj)->_valptr); + WriteObject(pBuffer, writeState, -1); + sq_poptop(vm_); + + break; + } + // case OT_USERDATA: + // case OT_GENERATOR: + // case OT_USERPOINTER: + // case OT_THREAD: + // + default: + Warning("SquirrelVM::WriteObject: Unexpected type %d", sq_gettype(vm_, idx)); + // Save a null instead + pBuffer->PutInt(OT_NULL); + } +} + +void SquirrelVM::WriteState(CUtlBuffer* pBuffer) +{ + SquirrelSafeCheck safeCheck(vm_); + + WriteStateMap writeState; + + sq_pushroottable(vm_); + + // Not really a check cache, but adds the root + HSQOBJECT obj; + sq_resetobject(&obj); + sq_getstackobj(vm_, -1, &obj); + writeState.CheckCache(pBuffer, _table(obj)); + + int count = sq_getsize(vm_, 1); + sq_pushnull(vm_); + pBuffer->PutInt(count); + + while (SQ_SUCCEEDED(sq_next(vm_, -2))) + { + WriteObject(pBuffer, writeState, -2); + WriteObject(pBuffer, writeState, -1); + sq_pop(vm_, 2); + --count; + } + sq_pop(vm_, 2); + Assert(count == 0); +} + +SQInteger closure_read(SQUserPointer file, SQUserPointer buf, SQInteger size) +{ + CUtlBuffer* pBuffer = (CUtlBuffer*)file; + pBuffer->Get(buf, size); + return pBuffer->IsValid() ? size : -1; +} + +void SquirrelVM::ReadObject(CUtlBuffer* pBuffer, ReadStateMap& readState) +{ + SquirrelSafeCheck safeCheck(vm_, 1); + + int thisType = pBuffer->GetInt(); + + switch (thisType) + { + case OT_NULL: + { + sq_pushnull(vm_); + break; + } + case OT_INTEGER: + { + sq_pushinteger(vm_, pBuffer->GetInt64()); + break; + } + case OT_FLOAT: + { + sq_pushfloat(vm_, pBuffer->GetFloat()); + break; + } + case OT_BOOL: + { + sq_pushbool(vm_, pBuffer->GetChar()); + break; + } + case OT_STRING: + { + int size = pBuffer->GetInt(); + char* buffer = new char[size + 1]; + pBuffer->Get(buffer, size); + buffer[size] = 0; + sq_pushstring(vm_, buffer, size); + delete[] buffer; + break; + } + case OT_TABLE: + { + int marker = 0; + if (readState.CheckCache(pBuffer, vm_, &marker)) + { + break; + } + + ReadObject(pBuffer, readState); + + int count = pBuffer->GetInt(); + sq_newtableex(vm_, count); + readState.StoreTopInCache(marker); + + sq_push(vm_, -2); + sq_setdelegate(vm_, -2); + + sq_remove(vm_, -2); + + for (int i = 0; i < count; ++i) + { + ReadObject(pBuffer, readState); + ReadObject(pBuffer, readState); + sq_rawset(vm_, -3); + } + + break; + } + case OT_ARRAY: + { + int marker = 0; + if (readState.CheckCache(pBuffer, vm_, &marker)) + { + break; + } + + int count = pBuffer->GetInt(); + sq_newarray(vm_, count); + readState.StoreTopInCache(marker); + + for (int i = 0; i < count; ++i) + { + sq_pushinteger(vm_, i); + ReadObject(pBuffer, readState); + sq_rawset(vm_, -3); + } + break; + } + case OT_CLOSURE: + { + int marker = 0; + if (readState.CheckCache(pBuffer, vm_, &marker)) + { + break; + } + + if (pBuffer->GetChar() == 0) + { + if (SQ_FAILED(sq_readclosure(vm_, closure_read, pBuffer))) + { + Error("Failed to read closure\n"); + sq_pushnull(vm_); + break; + } + + readState.StoreTopInCache(marker); + } + else + { + SQObjectPtr ret; + if (!SQClosure::Load(vm_, pBuffer, closure_read, ret)) + { + Error("Failed to read closure\n"); + sq_pushnull(vm_); + break; + } + + vm_->Push(ret); + readState.StoreTopInCache(marker); + + int noutervalues = _closure(ret)->_function->_noutervalues; + for (int i = 0; i < noutervalues; ++i) + { + ReadObject(pBuffer, readState); + HSQOBJECT obj; + sq_resetobject(&obj); + sq_getstackobj(vm_, -1, &obj); + _closure(ret)->_outervalues[i] = obj; + sq_poptop(vm_); + } + + int ndefaultparams = _closure(ret)->_function->_ndefaultparams; + for (int i = 0; i < ndefaultparams; ++i) + { + ReadObject(pBuffer, readState); + HSQOBJECT obj; + sq_resetobject(&obj); + sq_getstackobj(vm_, -1, &obj); + _closure(ret)->_defaultparams[i] = obj; + sq_poptop(vm_); + } + } + + ReadObject(pBuffer, readState); + HSQOBJECT env; + sq_resetobject(&env); + sq_getstackobj(vm_, -1, &env); + if (!sq_isnull(env)) + { + HSQOBJECT obj; + sq_getstackobj(vm_, -2, &obj); + if (_closure(obj) == nullptr) + Warning("Closure is null\n"); + else + _closure(obj)->_env = _refcounted(env)->GetWeakRef(sq_type(env)); + } + sq_poptop(vm_); + + break; + } + case OT_NATIVECLOSURE: + { + char closureName[128] = ""; + pBuffer->GetString(closureName, sizeof(closureName)); + + sq_pushroottable(vm_); + sq_pushstring(vm_, closureName, -1); + if (SQ_FAILED(sq_get(vm_, -2))) + { + Warning("SquirrelVM::ReadObject: Failed to find native closure\n"); + sq_pop(vm_, 1); + sq_pushnull(vm_); + } + sq_remove(vm_, -2); + + break; + } + case OT_CLASS: + { + int marker = 0; + if (readState.CheckCache(pBuffer, vm_, &marker)) + { + break; + } + + ClassType classType = (ClassType)pBuffer->GetInt(); + + if (classType == VectorClassType) + { + sq_pushobject(vm_, vectorClass_); + readState.StoreTopInCache(marker); + } + else if (classType == NativeClassType) + { + char className[128] = ""; + pBuffer->GetString(className, sizeof(className)); + + sq_pushroottable(vm_); + sq_pushstring(vm_, className, -1); + if (SQ_FAILED(sq_get(vm_, -2))) + { + Warning("SquirrelVM::ReadObject: Failed to find native class: %s\n", className); + sq_pushnull(vm_); + } + sq_remove(vm_, -2); + readState.StoreTopInCache(marker); + } + else if (classType == ScriptClassType) + { + ReadObject(pBuffer, readState); + bool hasBase = sq_gettype(vm_, -1) != OT_NULL; + if (!hasBase) + { + sq_poptop(vm_); + } + + sq_newclass(vm_, hasBase); + readState.StoreTopInCache(marker); + + sq_pushnull(vm_); + ReadObject(pBuffer, readState); + sq_setattributes(vm_, -3); + sq_poptop(vm_); // Returns the old attributes + + while (pBuffer->GetChar()) + { + // TODO: Member Attributes + ReadObject(pBuffer, readState); + ReadObject(pBuffer, readState); + sq_newslot(vm_, -3, false); + } + } + else + { + Error("SquirrelVM::ReadObject: Unknown class type\n"); + sq_pushnull(vm_); + } + break; + } + case OT_INSTANCE: + { + int marker = 0; + if (readState.CheckCache(pBuffer, vm_, &marker)) + { + break; + } + + ReadObject(pBuffer, readState); + + HSQOBJECT klass; + sq_resetobject(&klass); + sq_getstackobj(vm_, -1, &klass); + if (_class(klass) == _class(regexpClass_)) + { + sq_pushnull(vm_); + ReadObject(pBuffer, readState); + sq_call(vm_, 2, SQTrue, SQFalse); + + readState.StoreTopInCache(marker); + + sq_remove(vm_, -2); + + break; + } + + SQUserPointer typetag; + sq_gettypetag(vm_, -1, &typetag); + + if (typetag && typetag != TYPETAG_VECTOR && + ((ScriptClassDesc_t*)typetag)->m_pszDescription[0] == SCRIPT_SINGLETON[0]) + { + sq_poptop(vm_); + + Assert(sq_isclass(klass)); + + // singleton, lets find an equivlent in the root + bool foundSingleton = false; + sq_pushroottable(vm_); + sq_pushnull(vm_); + HSQOBJECT singleton; + sq_resetobject(&singleton); + while (SQ_SUCCEEDED(sq_next(vm_, -2))) + { + sq_getstackobj(vm_, -1, &singleton); + if (sq_isinstance(singleton) && _instance(singleton)->_class == _class(klass)) + { + foundSingleton = true; + + readState.StoreInCache(marker, singleton); + sq_addref(vm_, &singleton); + sq_pop(vm_, 2); + break; + } + sq_pop(vm_, 2); + } + sq_pop(vm_, 2); + + if (!foundSingleton) + { + Warning("SquirrelVM::ReadObject: Failed to find singleton for %s\n", + ((ScriptClassDesc_t*)typetag)->m_pszScriptName); + } + + sq_pushobject(vm_, singleton); + break; + } + + + HSQOBJECT obj; + sq_createinstance(vm_, -1); + sq_getstackobj(vm_, -1, &obj); + sq_addref(vm_, &obj); + readState.StoreInCache(marker, obj); + + sq_remove(vm_, -2); + + { + // HACK: No way to get the default values part from accessing the class directly + SQUnsignedInteger nvalues = _instance(obj)->_class->_defaultvalues.size(); + for (SQUnsignedInteger n = 0; n < nvalues; n++) { + ReadObject(pBuffer, readState); + HSQOBJECT val; + sq_resetobject(&val); + sq_getstackobj(vm_, -1, &val); + _instance(obj)->_values[n] = val; + sq_pop(vm_, 1); + } + } + + if (typetag == TYPETAG_VECTOR) + { + float x = pBuffer->GetFloat(); + float y = pBuffer->GetFloat(); + float z = pBuffer->GetFloat(); + SQUserPointer p; + sq_getinstanceup(vm_, -1, &p, 0); + new(p) Vector(x, y, z); + } + else if (typetag) + { + ScriptClassDesc_t* pClassDesc = (ScriptClassDesc_t*)typetag; + + char instanceName[128] = ""; + pBuffer->GetString(instanceName, sizeof(instanceName)); + + HSQOBJECT* hinstance = new HSQOBJECT; + sq_resetobject(hinstance); + sq_getstackobj(vm_, -1, hinstance); + sq_addref(vm_, hinstance); + + if (*instanceName) + { + bool allowDestruct = (pBuffer->GetChar() == 1); + + auto instance = pClassDesc->pHelper->BindOnRead((HSCRIPT)hinstance, nullptr, instanceName); + if (instance == nullptr) + { + sq_release(vm_, hinstance); + delete hinstance; + sq_poptop(vm_); + sq_pushnull(vm_); + break; + } + + { + SQUserPointer p; + sq_getinstanceup(vm_, -1, &p, 0); + new(p) ClassInstanceData(instance, pClassDesc, instanceName, allowDestruct); + } + sq_setreleasehook(vm_, -1, allowDestruct ? &destructor_stub : &destructor_stub_instance); + } + else + { + sq_setinstanceup(vm_, -1, nullptr); + } + } + + break; + } + case OT_WEAKREF: + { + ReadObject(pBuffer, readState); + sq_weakref(vm_, -1); + break; + } + case OT_FUNCPROTO: //internal usage only + { + int marker = 0; + if (readState.CheckCache(pBuffer, vm_, &marker)) + { + break; + } + + SQObjectPtr ret; + if (!SQFunctionProto::Load(vm_, pBuffer, closure_read, ret)) + { + Error("Failed to deserialize OT_FUNCPROTO\n"); + sq_pushnull(vm_); + break; + } + + vm_->Push(ret); + readState.StoreTopInCache(marker); + } + case OT_OUTER: //internal usage only + { + int marker = 0; + if (readState.CheckCache(pBuffer, vm_, &marker)) + { + break; + } + + SQOuter* outer = SQOuter::Create(_ss(vm_), nullptr); + vm_->Push(outer); + readState.StoreTopInCache(marker); + + ReadObject(pBuffer, readState); + HSQOBJECT inner; + sq_resetobject(&inner); + sq_getstackobj(vm_, -1, &inner); + outer->_value = inner; + outer->_valptr = &(outer->_value); + sq_poptop(vm_); + + break; + } + // case OT_USERDATA: + // case OT_GENERATOR: + // case OT_USERPOINTER: + // case OT_THREAD: + // + default: + Error("SquirrelVM::ReadObject: Unexpected type %d", thisType); + } +} + +void SquirrelVM::ReadState(CUtlBuffer* pBuffer) +{ + SquirrelSafeCheck safeCheck(vm_); + + ReadStateMap readState(vm_); + + sq_pushroottable(vm_); + + HSQOBJECT obj; + int marker = 0; + readState.CheckCache(pBuffer, vm_, &marker); + sq_getstackobj(vm_, -1, &obj); + sq_addref(vm_, &obj); + readState.StoreInCache(marker, obj); + + int count = pBuffer->GetInt(); + + for (int i = 0; i < count; ++i) + { + ReadObject(pBuffer, readState); + ReadObject(pBuffer, readState); + + sq_rawset(vm_, -3); + } + + sq_pop(vm_, 1); +} + +void SquirrelVM::RemoveOrphanInstances() +{ + SquirrelSafeCheck safeCheck(vm_); + // TODO: Is this the right thing to do here? It's not really removing orphan instances + sq_collectgarbage(vm_); +} + +void SquirrelVM::DumpState() +{ + SquirrelSafeCheck safeCheck(vm_); + // TODO: Dump state +} + +void SquirrelVM::SetOutputCallback(ScriptOutputFunc_t pFunc) +{ + SquirrelSafeCheck safeCheck(vm_); + // TODO: Support output callbacks +} + +void SquirrelVM::SetErrorCallback(ScriptErrorFunc_t pFunc) +{ + SquirrelSafeCheck safeCheck(vm_); + // TODO: Support error callbacks +} + +bool SquirrelVM::RaiseException(const char* pszExceptionText) +{ + SquirrelSafeCheck safeCheck(vm_); + sq_pushstring(vm_, pszExceptionText, -1); + sq_resetobject(&lastError_); + sq_getstackobj(vm_, -1, &lastError_); + sq_addref(vm_, &lastError_); + sq_pop(vm_, 1); + return true; +} + + +IScriptVM* makeSquirrelVM() +{ + return new SquirrelVM; +} diff --git a/src/vscript/vscript_squirrel.nut b/src/vscript/vscript_squirrel.nut new file mode 100644 index 00000000..54492423 --- /dev/null +++ b/src/vscript/vscript_squirrel.nut @@ -0,0 +1,571 @@ +static char g_Script_vscript_squirrel[] = R"vscript( +//========= Mapbase - https://github.com/mapbase-source/source-sdk-2013 ============// +// +// Purpose: +// +//=============================================================================// + +Warning <- error; + +function clamp( val, min, max ) +{ + if ( max < min ) + return max; + if ( val < min ) + return min; + if ( val > max ) + return max; + return val; +} + +function max( a, b ) +{ + if ( a > b ) + return a; + return b; +} + +function min( a, b ) +{ + if ( a < b ) + return a; + return b; +} + +function RemapVal( val, A, B, C, D ) +{ + if ( A == B ) + { + if ( val >= B ) + return D; + return C; + }; + return C + (D - C) * (val - A) / (B - A); +} + +function RemapValClamped( val, A, B, C, D ) +{ + if ( A == B ) + { + if ( val >= B ) + return D; + return C; + }; + + local cVal = (val - A) / (B - A); + + if ( cVal <= 0.0 ) + return C; + + if ( cVal >= 1.0 ) + return D; + + return C + (D - C) * cVal; +} + +function Approach( target, value, speed ) +{ + local delta = target - value; + + if ( delta > speed ) + return value + speed; + if ( -speed > delta ) + return value - speed; + return target; +} + +function AngleDistance( next, cur ) +{ + local delta = next - cur + + if ( delta > 180.0 ) + return delta - 360.0; + if ( -180.0 > delta ) + return delta + 360.0; + return delta; +} + +function FLerp( f1, f2, i1, i2, x ) +{ + return f1+(f2-f1)*(x-i1)/(i2-i1); +} + +function Lerp( f, A, B ) +{ + return A + ( B - A ) * f +} + +function SimpleSpline( f ) +{ + local ff = f * f; + return 3.0 * ff - 2.0 * ff * f; +} + +function printl( text ) +{ + return print(text + "\n"); +} + +class CSimpleCallChainer +{ + constructor(prefixString, scopeForThis, exactMatch) + { + prefix = prefixString; + scope = scopeForThis; + chain = []; + scope["Dispatch" + prefixString] <- Call.bindenv(this); + } + + function PostScriptExecute() + { + if ( prefix in scope ) + { + local func = scope[prefix]; + if ( typeof func == "function" ) + { + chain.push(func); + } + } + } + + function Call() + { + foreach (func in chain) + { + func.pcall(scope); + } + } + + prefix = null; + scope = null; + chain = null; +} + +//--------------------------------------------------------- +// Hook handler +//--------------------------------------------------------- +local s_List = {} + +Hooks <- +{ + // table, string, closure, string + function Add( scope, event, callback, context ) + { + switch ( typeof scope ) + { + case "table": + case "instance": + case "class": + break; + default: + throw "invalid scope param"; + } + + if ( typeof event != "string" ) + throw "invalid event param"; + + if ( typeof callback != "function" ) + throw "invalid callback param"; + + if ( typeof context != "string" ) + throw "invalid context param"; + + if ( !(event in s_List) ) + s_List[event] <- {}; + + local t = s_List[event]; + + if ( !(scope in t) ) + t[scope] <- {}; + + t[scope][context] <- callback; + + return __UpdateHooks(); + } + + function Remove( event, context ) + { + local rem; + + if ( event ) + { + if ( event in s_List ) + { + foreach ( scope, ctx in s_List[event] ) + { + if ( context in ctx ) + { + delete ctx[context]; + } + + if ( !ctx.len() ) + { + if ( !rem ) + rem = []; + rem.append( event ); + rem.append( scope ); + } + } + } + } + else + { + foreach ( ev, t in s_List ) + { + foreach ( scope, ctx in t ) + { + if ( context in ctx ) + { + delete ctx[context]; + } + + if ( !ctx.len() ) + { + if ( !rem ) + rem = []; + rem.append( ev ); + rem.append( scope ); + } + } + } + } + + if ( rem ) + { + local c = rem.len() - 1; + for ( local i = 0; i < c; i += 2 ) + { + local ev = rem[i]; + local scope = rem[i+1]; + + if ( !s_List[ev][scope].len() ) + delete s_List[ev][scope]; + + if ( !s_List[ev].len() ) + delete s_List[ev]; + } + } + + return __UpdateHooks(); + } + + function Call( event, scope, ... ) + { + local firstReturn; + + if ( event in s_List ) + { + vargv.insert( 0, scope ); + + local t = s_List[event]; + if ( scope in t ) + { + foreach ( fn in t[scope] ) + { + //printf( "(%.4f) Calling hook %s:%s\n", Time(), event, context ); + + local r = fn.acall( vargv ); + if ( firstReturn == null ) + firstReturn = r; + } + } + else if ( !scope ) // global hook + { + foreach ( sc, ctx in t ) + { + vargv[0] = sc; + + foreach ( context, fn in ctx ) + { + //printf( "(%.4f) Calling hook (g) %s:%s\n", Time(), event, context ); + + local r = fn.acall( vargv ); + if ( firstReturn == null ) + firstReturn = r; + } + } + } + } + + return firstReturn; + } + + function __UpdateHooks() + { + return __UpdateScriptHooks( s_List ); + } +} + +//--------------------------------------------------------- +// Documentation +//--------------------------------------------------------- +__Documentation <- {} + +local developer = (delete developer)() + +if (developer) +{ + local DocumentedFuncs = {} + local DocumentedClasses = {} + local DocumentedEnums = {} + local DocumentedConsts = {} + local DocumentedHooks = {} + local DocumentedMembers = {} + + local function AddAliasedToTable(name, signature, description, table) + { + // This is an alias function, could use split() if we could guarantee + // that ':' would not occur elsewhere in the description and Squirrel had + // a convience join() function -- It has split() + local colon = description.find(":"); + if (colon == null) + colon = description.len(); + local alias = description.slice(1, colon); + description = description.slice(colon + 1); + name = alias; + signature = null; + + table[name] <- [signature, description]; + } + + function __Documentation::RegisterHelp(name, signature, description) + { + if (description.len() && description[0] == '#') + { + AddAliasedToTable(name, signature, description, DocumentedFuncs) + } + else + { + DocumentedFuncs[name] <- [signature, description]; + } + } + + function __Documentation::RegisterClassHelp(name, baseclass, description) + { + DocumentedClasses[name] <- [baseclass, description]; + } + + function __Documentation::RegisterEnumHelp(name, num_elements, description) + { + DocumentedEnums[name] <- [num_elements, description]; + } + + function __Documentation::RegisterConstHelp(name, signature, description) + { + if (description.len() && description[0] == '#') + { + AddAliasedToTable(name, signature, description, DocumentedConsts) + } + else + { + DocumentedConsts[name] <- [signature, description]; + } + } + + function __Documentation::RegisterHookHelp(name, signature, description) + { + DocumentedHooks[name] <- [signature, description]; + } + + function __Documentation::RegisterMemberHelp(name, signature, description) + { + DocumentedMembers[name] <- [signature, description]; + } + + local function printdoc( text ) + { + return ::printc(200,224,255,text); + } + + local function printdocl( text ) + { + return printdoc(text + "\n"); + } + + local function PrintClass(name, doc) + { + local text = "=====================================\n"; + text += ("Class: " + name + "\n"); + text += ("Base: " + doc[0] + "\n"); + if (doc[1].len()) + text += ("Description: " + doc[1] + "\n"); + text += "=====================================\n\n"; + + printdoc(text); + } + + local function PrintFunc(name, doc) + { + local text = "Function: " + name + "\n" + + if (doc[0] == null) + { + // Is an aliased function + text += ("Signature: function " + name + "("); + foreach(k,v in this[name].getinfos().parameters) + { + if (k == 0 && v == "this") continue; + if (k > 1) text += (", "); + text += (v); + } + text += (")\n"); + } + else + { + text += ("Signature: " + doc[0] + "\n"); + } + if (doc[1].len()) + text += ("Description: " + doc[1] + "\n"); + printdocl(text); + } + + local function PrintMember(name, doc) + { + local text = ("Member: " + name + "\n"); + text += ("Signature: " + doc[0] + "\n"); + if (doc[1].len()) + text += ("Description: " + doc[1] + "\n"); + printdocl(text); + } + + local function PrintEnum(name, doc) + { + local text = "=====================================\n"; + text += ("Enum: " + name + "\n"); + text += ("Elements: " + doc[0] + "\n"); + if (doc[1].len()) + text += ("Description: " + doc[1] + "\n"); + text += "=====================================\n\n"; + + printdoc(text); + } + + local function PrintConst(name, doc) + { + local text = ("Constant: " + name + "\n"); + if (doc[0] == null) + { + text += ("Value: null\n"); + } + else + { + text += ("Value: " + doc[0] + "\n"); + } + if (doc[1].len()) + text += ("Description: " + doc[1] + "\n"); + printdocl(text); + } + + local function PrintHook(name, doc) + { + local text = ("Hook: " + name + "\n"); + if (doc[0] == null) + { + // Is an aliased function + text += ("Signature: function " + name + "("); + foreach(k,v in this[name].getinfos().parameters) + { + if (k == 0 && v == "this") continue; + if (k > 1) text += (", "); + text += (v); + } + text += (")\n"); + } + else + { + text += ("Signature: " + doc[0] + "\n"); + } + if (doc[1].len()) + text += ("Description: " + doc[1] + "\n"); + printdocl(text); + } + + local function PrintMatches( pattern, docs, printfunc ) + { + local matches = []; + local always = pattern == "*"; + + foreach( name, doc in docs ) + { + if (always || name.tolower().find(pattern) != null || (doc[1].len() && doc[1].tolower().find(pattern) != null)) + { + matches.append( name ); + } + } + + if ( !matches.len() ) + return 0; + + matches.sort(); + + foreach( name in matches ) + printfunc( name, docs[name] ); + + return 1; + } + + function __Documentation::PrintHelp(pattern = "*") + { + local patternLower = pattern.tolower(); + + // Have a specific order + if (!( + PrintMatches( patternLower, DocumentedEnums, PrintEnum ) | + PrintMatches( patternLower, DocumentedConsts, PrintConst ) | + PrintMatches( patternLower, DocumentedClasses, PrintClass ) | + PrintMatches( patternLower, DocumentedFuncs, PrintFunc ) | + PrintMatches( patternLower, DocumentedMembers, PrintMember ) | + PrintMatches( patternLower, DocumentedHooks, PrintHook ) + )) + { + printdocl("Pattern " + pattern + " not found"); + } + } +} +else +{ + __Documentation.RegisterHelp <- + __Documentation.RegisterClassHelp <- + __Documentation.RegisterEnumHelp <- + __Documentation.RegisterConstHelp <- + __Documentation.RegisterHookHelp <- + __Documentation.RegisterMemberHelp <- dummy + + function __Documentation::PrintHelp( pattern = null ) + { + printcl(200, 224, 255, "Documentation is not enabled. To enable documentation, restart the server with the 'developer' cvar set to 1 or higher."); + } +} + +if (developer) +{ + __Documentation.RegisterClassHelp( "Vector", "", "Basic 3-float Vector class." ); + __Documentation.RegisterHelp( "Vector::Length", "float Vector::Length()", "Return the vector's length." ); + __Documentation.RegisterHelp( "Vector::LengthSqr", "float Vector::LengthSqr()", "Return the vector's squared length." ); + __Documentation.RegisterHelp( "Vector::Length2D", "float Vector::Length2D()", "Return the vector's 2D length." ); + __Documentation.RegisterHelp( "Vector::Length2DSqr", "float Vector::Length2DSqr()", "Return the vector's squared 2D length." ); + + __Documentation.RegisterHelp( "Vector::Normalized", "float Vector::Normalized()", "Return a normalized version of the vector." ); + __Documentation.RegisterHelp( "Vector::Norm", "void Vector::Norm()", "Normalize the vector in place." ); + __Documentation.RegisterHelp( "Vector::Scale", "vector Vector::Scale(float)", "Scale the vector's magnitude and return the result." ); + __Documentation.RegisterHelp( "Vector::Dot", "float Vector::Dot(vector)", "Return the dot/scalar product of two vectors." ); + __Documentation.RegisterHelp( "Vector::Cross", "float Vector::Cross(vector)", "Return the vector product of two vectors." ); + + __Documentation.RegisterHelp( "Vector::ToKVString", "string Vector::ToKVString()", "Return a vector as a string in KeyValue form, without separation commas." ); + + __Documentation.RegisterMemberHelp( "Vector.x", "float Vector.x", "The vector's X coordinate on the cartesian X axis." ); + __Documentation.RegisterMemberHelp( "Vector.y", "float Vector.y", "The vector's Y coordinate on the cartesian Y axis." ); + __Documentation.RegisterMemberHelp( "Vector.z", "float Vector.z", "The vector's Z coordinate on the cartesian Z axis." ); + + __Documentation.RegisterHelp( "clamp", "float clamp(float, float, float)", "" ); + __Documentation.RegisterHelp( "max", "float max(float, float)", "" ); + __Documentation.RegisterHelp( "min", "float min(float, float)", "" ); + __Documentation.RegisterHelp( "RemapVal", "float RemapVal(float, float, float, float, float)", "" ); + __Documentation.RegisterHelp( "RemapValClamped", "float RemapValClamped(float, float, float, float, float)", "" ); + __Documentation.RegisterHelp( "Approach", "float Approach(float, float, float)", "" ); + __Documentation.RegisterHelp( "AngleDistance", "float AngleDistance(float, float)", "" ); + __Documentation.RegisterHelp( "FLerp", "float FLerp(float, float, float, float, float)", "" ); + __Documentation.RegisterHelp( "Lerp", "float Lerp(float, float, float)", "" ); + __Documentation.RegisterHelp( "SimpleSpline", "float SimpleSpline(float)", "" ); +} +)vscript"; diff --git a/thirdpartylegalnotices.txt b/thirdpartylegalnotices.txt index f417b485..0e2a06b1 100644 --- a/thirdpartylegalnotices.txt +++ b/thirdpartylegalnotices.txt @@ -1,6 +1,6 @@ The Source engine and Valve games use Third Party code for certain functions. The required legal notices for using such code are reproduced below in accordance with -Valve�s obligations to provide such notices: +Valve s obligations to provide such notices: ************************************************************************************ Xzip/Xunzip: @@ -498,7 +498,7 @@ Celt Codec: ************************************************************************************ SPEEX Codes: ************************************************************************************ - SPEEX � 2002-2003, Jean-Marc Valin/Xiph.Org Foundation + SPEEX 2002-2003, Jean-Marc Valin/Xiph.Org Foundation Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of @@ -509,7 +509,7 @@ SPEEX Codes: * Neither the name of the Xiph.org Foundation nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. - This software is provided by the copyright holders and contributors �as is� and any express or + This software is provided by the copyright holders and contributors as is and any express or implied warranties, including, but not limited to, the implied warranties of merchantability and fitness for a particular purpose are disclaimed. In no event shall the foundation or contributors be liable for any direct, indirect, incidental, special, exemplary, or consequential damages (including, @@ -671,17 +671,17 @@ Fast Pow Function // THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ************************************************************************************ -Autodesk� FBX�: +Autodesk FBX : ************************************************************************************ - �This software contains Autodesk� FBX� code developed by Autodesk, Inc. Copyright 2012 Autodesk, Inc. - All rights, reserved. Such code is provided �as is� and Autodesk, Inc. disclaims any and all warranties, + This software contains Autodesk FBX code developed by Autodesk, Inc. Copyright 2012 Autodesk, Inc. + All rights, reserved. Such code is provided as is and Autodesk, Inc. disclaims any and all warranties, whether express or implied, including without limitation the implied warranties of merchantability, fitness for a particular purpose or non-infringement of third party rights. In no event shall Autodesk, Inc. be liable for any direct, indirect, incidental, special, exemplary, or consequential damages (including, but not limited to, procurement of substitute goods or services; loss of use, data, or profits; or business interruption) however caused and on any theory of liability, whether in contract, strict liability, or tort (including negligence or - otherwise) arising in any way out of such code.� + otherwise) arising in any way out of such code. ************************************************************************************ OpenSSL: @@ -910,3 +910,34 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +************************************************************************************ +************************************************************************************ + +Mapbase integrates a few additional third-party libraries, all of which are from free and/or +open-source projects operating under MIT licenses. They can be used in any mod or commercial +product as long as the given notices are present here or elsewhere. + +************************************************************************************ +Squirrel programming language (used in VScript) +************************************************************************************ + +Copyright (c) 2003-2017 Alberto Demichelis + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE.