86 Commits
Author SHA1 Message Date
Asdow 7b8c2d1ff9 Remove old function call 2026-01-23 23:35:59 +02:00
Asdow caf925b216 Fix Fatima not moving after map traversal 2026-01-23 23:35:03 +02:00
Asdow bd9cbd9944 Migrate +AI features
* Drink canteen
* Jump through window
* Use explosives
* Use wirecutters
2026-01-23 23:19:07 +02:00
Asdow 41945842d5 Migrate features from +AI
Create Scuba fins when in deep water for elites
2026-01-23 18:06:26 +02:00
Asdow 91a1059f87 Migrate features from +AI
Possibly wear gasmask if soldier sees tear or mustard gas
2026-01-23 16:47:34 +02:00
Asdow 9c0fb0b6da Add tactical range defines 2026-01-22 00:32:41 +02:00
Asdow 3d7d7cc88a Reorganize stuff to improve readability 2026-01-22 00:31:31 +02:00
Asdow 737cd848ce Make water and gas checks const 2026-01-22 00:28:37 +02:00
Asdow 1bead3a0fe Improve readability 2026-01-22 00:27:53 +02:00
Asdow b50ed93f39 Remove armed vehicle check from soldier AI 2026-01-22 00:27:18 +02:00
Asdow 75355f6cac Set DangerousSpot to const 2026-01-22 00:26:48 +02:00
Asdow 2448ccb191 Tweak AI screen messages 2026-01-22 00:26:18 +02:00
Asdow 42215e7a51 Remove magic numbers 2026-01-22 00:25:33 +02:00
Asdow 1419cc6925 Avoid overcrowding when looking for spot to move to 2026-01-22 00:24:53 +02:00
Asdow 76af6b7641 Remove duplicate include 2026-01-22 00:23:52 +02:00
Asdow 2b1838dbb0 Remove unused local variable 2026-01-19 23:34:19 +02:00
Asdow 4ad34f80a9 Fix includes 2026-01-19 22:59:55 +02:00
Asdow ff96d653e2 Add skeleton for utilityAI framework 2026-01-19 22:59:30 +02:00
Asdow 58ea3be2fd Remove MercPtrs[]
SoldierID works without it thanks to operator overload
2026-01-19 22:58:58 +02:00
Asdow 4678cab16f Use correct type 2026-01-19 22:58:33 +02:00
Asdow 1b9cb1f75e Add functions to check if soldier is in specific gas 2026-01-19 22:58:17 +02:00
Asdow df8d4bd8a2 Only show message if temperature has changed
This was absolutely clogging Debug build when compressing time
2026-01-19 22:57:34 +02:00
Asdow 52a4959dfb Add optional InOut parameter to ClosestKnownOpponent
distanceInCellCoords will return the distance between pSoldier and closest known opponent. We're often calculating same range after the call using PythSpacesAway and this way it's no longer necessary in most cases.
2026-01-19 22:56:18 +02:00
Asdow e806fd37e7 Add screen messages that display AI decisions 2026-01-19 22:51:38 +02:00
Asdow cd054b549f Split DecideAction into several specialized DecideAction functions
* Removes the need to constantly check for fCivilian, ARMED_VEHICLE, ENEMY_ROBOT etc inside DecideAction
* Move AI decision checks into their own functions, such as DecideActionRadioRedAlert
* Add new entry to ActionType. AI_ACTION_INVALID signals that no valid action was found when a function returns, or no valid action is possible
* Do a weighted random selection for choosing target under DecideActionBlackSoldier
2026-01-19 22:50:27 +02:00
Asdow c3f4ee754a Only log during turn based AI 2026-01-19 22:32:01 +02:00
Asdow a46d2dbb96 Add global to selectively log AI status BLACK decisions 2026-01-19 22:31:34 +02:00
Asdow 16fc2d8177 Cleanup FindBestNearbyCover
* Add optional parameter to ignore search range
2026-01-19 22:29:06 +02:00
Asdow e634edd862 Rename debug draw modes 2026-01-19 22:05:31 +02:00
Asdow f5b705cbe4 Add ingame option to use old AI instead of new 2026-01-19 22:05:10 +02:00
Asdow 4e19816b57 Fix typo 2026-01-19 00:28:19 +02:00
Asdow 20dadec54c Merge branch 'master' into decideaction 2026-01-13 00:38:40 +02:00
Asdow 2e3e4b38d9 Fix compilation 2025-12-25 20:17:02 +02:00
Asdow f075963bca Merge branch 'master' into decideaction 2025-12-25 18:58:41 +02:00
Asdow de91c7a9fd Render debug info in tactical
Functionality for rendering pathfinding and cover values in tactical was behind defunct #ifdefs.
It's now compiled always, but only enabled if cheat level is BEDUG_CHEAT_LEVEL.
Unified rendering the debug info into one function, instead of several nearly identical ones.
Ctrl + Shift + Z cycles through the different debug modes.

Now it should be fairly easy to add more info modes like these two in the future.
2024-02-21 23:24:14 +02:00
Asdow ab2c5fc10d Add BOXER() & ISVIP() macros 2024-02-18 12:03:43 +02:00
AsdowandGitHub 5fe730b075 Merge branch 'master' into decideaction 2024-02-11 17:38:27 +02:00
Asdow 57716e1c07 Revert back to original AI deadlock breaking
For now..
2024-02-11 17:20:10 +02:00
Asdow dfc17c68c6 Cancel action if new situation arises
Lot of actions are liable to deadlock if this is not done
2024-02-11 13:32:17 +02:00
Asdow 67a26e0936 Add more AI logging 2024-02-11 11:21:41 +02:00
Asdow 1a98420160 Remove extraneous check
Allow at least considering throwing smoke for cover even if pSoldier has used some AP from its initial amount.
2024-02-10 18:56:11 +02:00
Asdow 877ec385ee Whitespace changes 2024-02-10 18:50:30 +02:00
Asdow b480a67d36 Reduce code duplication 2024-02-10 18:47:03 +02:00
Asdow adf86380ca Merge branch 'decideaction' of https://github.com/1dot13/source into decideaction 2024-02-10 18:37:11 +02:00
Asdow 93acd19c11 Try to find a cover spot when advancing to attack
Calling InternalGoAsFarAsPossibleTowards() with target's gridno means AI doesn't take into account eg. nearby friendlies resulting in them bunching up and  making a beeline straight at whoever they're trying to attack.
We'll first at least attempt to find a cover spot near said target, if it doesn't work, only then head straight at them.
2024-02-10 18:35:45 +02:00
Asdow f6052f0eff add another AI logging entry 2024-02-10 18:35:25 +02:00
Asdow eaf76957c0 Use correct string array 2024-02-10 18:35:25 +02:00
Asdow 3c2b048367 Attempt to continue moving towards enemy
when in water. If our current or last action was AI_ACTION_SEEK_OPPONENT.

The AI tends to flipflop between jumping into water, swimming a certain distance and then "panic" because they're in the water and then try to reach the closest land tile. Which is often right where they entered the water. Adding a bit of decision momentum with this change should help with that.
2024-02-10 18:35:25 +02:00
Asdow 1aec54f59c add another AI logging entry 2024-02-10 18:14:35 +02:00
Asdow 0240729c11 Use correct string array 2024-02-10 18:13:20 +02:00
Asdow 5c86cf0b07 Attempt to continue moving towards enemy
when in water. If our current or last action was AI_ACTION_SEEK_OPPONENT.

The AI tends to flipflop between jumping into water, swimming a certain distance and then "panic" because they're in the water and then try to reach the closest land tile. Which is often right where they entered the water. Adding a bit of decision momentum with this change should help with that.
2024-02-10 18:12:30 +02:00
Asdow 79372d5d23 Try to find a cover spot when advancing to attack
Calling InternalGoAsFarAsPossibleTowards() with target's gridno means AI doesn't take into account eg. nearby friendlies resulting in them bunching up and  making a beeline straight at whoever they're trying to attack.
We'll first at least attempt to find a cover spot near said target, if it doesn't work, only then head straight at them.
2024-02-04 21:05:54 +02:00
Asdow 4faccb2957 Fix indentation 2024-02-04 20:17:09 +02:00
Asdow 0ed68b6214 Set functions to static 2024-02-04 16:17:36 +02:00
Asdow f646bc2bfa Set correct next action if npc has no AP
With aiData.bAction = AI_ACTION_NONE and aiData.bNextAction = AI_ACTION_END_TURN, the npc turn is correctly ended in the next call to TurnBasedHandleNPCAI()
2024-02-04 16:14:01 +02:00
Asdow 01327265a3 Check if action should stay inprogress state
Several actions tended to get stuck in an invalid state, where it was completed, but aiData.bActionInProgress was still TRUE resulting in an AI deadlock.

If such a situation is found, Actiondone is now called, preventing the deadlock
2024-02-04 16:10:26 +02:00
Asdow cd193c7f5e Call TurnBasedHandleNPCAI() if no action is in progress
Several actions would sometimes get stuck in a forever loop, where they would not be executed, since no proper path for executing the actions is found.

Originally TurnBasedHandleNPCAI() would only be called if aidata.bAction was AI_ACTION_NONE.
Now we'll call it if an action is not already in progress, and will be executed if the action is affordable.
2024-02-04 16:07:03 +02:00
Asdow f249406ba0 Allow specific alert status AI logging
Less clutter in the AI logs if, for example one is only interested in BLACK status decisions
2024-02-04 15:29:05 +02:00
Asdow 7ee8165e02 Prevent AI deadlocking if NEW_SITUATION is encountered 2024-02-04 15:18:43 +02:00
Asdow caf5828153 Remove deadlock breaking from TurnBasedHandleNPCAI
It's already handled before a call to this function, and TurnBasedHandleNPCAI() is only called when pSoldier's action is AI_ACTION_NONE
2024-02-04 12:39:57 +02:00
Asdow ffb95a2418 Remove unused variable from if checks
aiData.bRTPCombat is never initialized and is always 0 when valid values should be

#define RTP_COMBAT_AGGRESSIVE		1
#define RTP_COMBAT_CONSERVE			2
#define RTP_COMBAT_REFRAIN			3
2024-02-03 20:49:43 +02:00
Asdow 22f2c9ff9a Correct AP check
We can't do anything with negative action points either.
2024-02-02 01:00:12 +02:00
Asdow a90e11619d Merge branch 'master' into decideaction 2023-10-10 23:21:12 +03:00
Asdow d6c9cb20b2 Report soldier ID in DebugMsg when interrupt ends 2023-10-02 22:29:47 +03:00
Asdow 1586e8fd2b Allow possibly taking cover without full APs 2023-10-02 22:28:56 +03:00
Asdow 88af94eaaf Use Flugente's AI deadlock break & fix wstring for screenMsg 2023-10-02 22:26:30 +03:00
Asdow 6131ebc689 Comment out temp. fixes to flush out AI deadlocks 2023-10-02 22:20:43 +03:00
Asdow ae709f2d19 Merge branch 'master' into decideaction 2023-10-02 21:19:44 +03:00
Asdow a45f3ae319 Add more AI logging 2023-08-20 23:38:56 +03:00
Asdow 318e4a40cf Improve AI logging 2023-08-19 23:23:59 +03:00
Asdow 1170f82159 Move variable declarations to point of initialization 2023-08-19 21:47:31 +03:00
Asdow d28ed97dff Add section comments do DecideActionRed 2023-08-19 21:45:01 +03:00
Asdow 96a7fa0538 Add section comments to DecideActionBlack 2023-08-19 14:41:16 +03:00
Asdow c5d1615035 Move decision if stuck in water or gas into its own function 2023-08-19 14:39:33 +03:00
Asdow 138ad0656f Prevent AI deadlocks
Canceling current AI actions at the start of soldier's turn prevents a lot of ai deadlocks. The status RED & BLACK actions are supposed re-evaluate the situation every turn anyways
2023-08-19 14:37:49 +03:00
Asdow a0cbe42a01 Create function for decision to wear a gasmask 2023-08-18 23:56:40 +03:00
Asdow 06478266d0 Moved armed vehicle/robot in gas check into function
Originally placed in DecideAction(green/yellow/red/black) functions separately
2023-08-18 23:11:39 +03:00
Asdow 613de1b869 Remove useless code
!AimingGun(pSoldier) would always evaluate to true and there are no hits to CrowDecideAction in the entire solution even in commented out code
2023-08-18 21:05:11 +03:00
Asdow 1c4929c3e1 Improve AI item handling log entry 2023-08-17 19:52:21 +03:00
Asdow 664142c36c Don't log AI info if pSoldier is null 2023-08-17 19:50:59 +03:00
Asdow 1c8d008a66 Move DebugAI() calls inside if blocks
Clutters logs and slows down game less when logging is on.
2023-08-17 19:49:55 +03:00
Asdow 0e529070c4 Allow AI to shoot with lower aim if out of AP for current aim level 2023-08-17 19:42:43 +03:00
Asdow bacad9b4c0 Prevent AI deadlock when they try to shoot and player gets an interrupt 2023-08-17 19:41:41 +03:00
Asdow e502727828 Disable canceling AI actions for escorted mercs
Logging AI decisions shows that this conditional would constantly cancel AI actions during regular gameplay. A special case like this should not affect normal AI routines and I'd rather disable this for now to prevent it from masking other problems with AI decisions.
2023-08-17 19:41:08 +03:00
Asdow d99b12cb5f Allow status black cover advance with full AP 2023-08-16 23:08:03 +03:00
Asdow 070867d965 Check for closest known opponent in status black 2023-08-16 23:07:29 +03:00
150 changed files with 18895 additions and 4702 deletions
+27 -166
View File
@@ -9,6 +9,11 @@ on:
# allows to manually trigger a build # allows to manually trigger a build
workflow_dispatch: workflow_dispatch:
inputs: inputs:
build_all_languages:
description: build all languages
required: false
default: false
type: boolean
assemble_release: assemble_release:
description: create release description: create release
required: false required: false
@@ -22,6 +27,7 @@ jobs:
workflow_setup: workflow_setup:
runs-on: ubuntu-latest runs-on: ubuntu-latest
outputs: outputs:
languages_json_array: ${{ steps.global_vars.outputs.languages_json_array }}
assemble_release: ${{ steps.global_vars.outputs.assemble_release }} assemble_release: ${{ steps.global_vars.outputs.assemble_release }}
steps: steps:
- id: global_vars - id: global_vars
@@ -31,6 +37,15 @@ jobs:
full_release='${{ ( github.repository == '1dot13/source' && github.ref_name == 'master' ) || startsWith(github.ref, 'refs/tags/v') }}' full_release='${{ ( github.repository == '1dot13/source' && github.ref_name == 'master' ) || startsWith(github.ref, 'refs/tags/v') }}'
if [[ '${{ inputs.build_all_languages }}' == 'true' || ( '${{ inputs.build_all_languages }}' == '' && "$full_release" == 'true' ) ]]
then
languages_json_array='["Chinese", "German", "English", "French", "Polish", "Italian", "Dutch", "Russian"]';
else
# English + some other language for compilation testing
languages_json_array='["German", "English"]'
fi
echo "languages_json_array=$languages_json_array" >> $GITHUB_OUTPUT
if [[ '${{ inputs.assemble_release }}' == 'true' || ( '${{ inputs.assemble_release }}' == '' && "$full_release" == 'true' ) ]] if [[ '${{ inputs.assemble_release }}' == 'true' || ( '${{ inputs.assemble_release }}' == '' && "$full_release" == 'true' ) ]]
then then
assemble_release='true' assemble_release='true'
@@ -139,193 +154,39 @@ jobs:
cat dist/versions.env cat dist/versions.env
- name: Upload - name: Upload
uses: actions/upload-artifact@v7 uses: actions/upload-artifact@v4
with: with:
name: versions.env name: versions.env
path: dist/ path: dist/
# every executable contains all languages and picks one at runtime, so one build serves every package build:
compile:
needs: [ workflow_setup ] needs: [ workflow_setup ]
runs-on: windows-latest # required for the MSVC toolchain
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
application: [ja2, ja2mapeditor, ja2ub, ja2ubmapeditor] language: ${{ fromJson(needs.workflow_setup.outputs.languages_json_array) }}
uses: ./.github/workflows/build_language.yml
steps: with:
- name: Checkout source language: ${{ matrix.language }}
uses: actions/checkout@v7 assemble: ${{ needs.workflow_setup.outputs.assemble_release == 'true' }}
# at least English and some other lang have to work
- name: Download versions.env continue-on-error: ${{ matrix.language != 'English' && matrix.language != 'German' }}
uses: actions/download-artifact@v8
with:
name: versions.env
path: artifacts
- name: Restore versions.env
shell: bash
run: cat artifacts/versions.env >> $GITHUB_ENV
# TODO: Move this into CMake, just taking the GitHub Actions parameters as -D variables
- name: Update GameVersion.cpp
shell: bash
run: |
set -eux
GAME_VERSION=$(echo "$GAME_VERSION" | tr -cd '[:print:]' | tr -d '"\\')
GAME_BUILD=$(echo "$GAME_BUILD_INFORMATION" | tr -cd '[:print:]' | tr -d '"\\')
sed -i "s|@Version@|${GAME_VERSION:0:15}|" Ja2/GameVersion.cpp
sed -i "s|@Build@|${GAME_BUILD:0:255}|" Ja2/GameVersion.cpp
cat Ja2/GameVersion.cpp
- name: Prepare build properties
shell: bash
run: |
set -eux
touch CMakePresets.json
JA2Application=$(echo '${{ matrix.application }}' | tr '[:lower:]' '[:upper:]')
echo "
JA2Application=$JA2Application
" >> $GITHUB_ENV
- uses: egor-tensin/vs-shell@v2
with:
arch: x86
- name: Prepare build
run: |
cmake -S . -B build -GNinja -DCMAKE_BUILD_TYPE=RelWithDebInfo -DApplications="$Env:JA2Application"
- name: Build
run: |
cmake --build build/ -- -v
- name: List build artifacts
shell: bash
run: |
find build/
- name: Upload
uses: actions/upload-artifact@v7
with:
name: ${{ matrix.application }}
path: build/*.exe
# only the game data differs per language, every package gets the same executables
assemble:
needs: [ workflow_setup, compile ]
if: needs.workflow_setup.outputs.assemble_release == 'true'
runs-on: windows-latest # required for case-insensitive filesystem handling
# at least English has to work
continue-on-error: ${{ matrix.language != 'English' }}
strategy:
fail-fast: false
matrix:
language: [Chinese, German, English, French, Polish, Italian, Dutch, Russian]
steps:
- name: Download versions.env
uses: actions/download-artifact@v8
with:
name: versions.env
path: artifacts
- name: Restore versions.env
shell: bash
run: cat artifacts/versions.env >> $GITHUB_ENV
- name: Checkout gamedir
uses: actions/checkout@v7
with:
repository: ${{ env.GAMEDIR_REPOSITORY }}
ref: ${{ env.GAMEDIR_COMMIT_SHA }}
path: gamedir
- name: Checkout gamedir-languages
if: matrix.language != 'English'
uses: actions/checkout@v7
with:
repository: ${{ env.GAMEDIR_LANGUAGES_REPOSITORY }}
ref: ${{ env.GAMEDIR_LANGUAGES_COMMIT_SHA }}
path: gamedir-languages
- name: Copy gamedir-languages files to gamedir
if: matrix.language != 'English'
shell: bash
run: |
set -eux
cp -a gamedir-languages/${{ matrix.language }}_Version/* gamedir/
- name: Download applications
uses: actions/download-artifact@v8
with:
pattern: ja2*
path: artifacts
- name: Copy application files to gamedir
shell: bash
run: |
set -eux
for APP in ja2 ja2mapeditor ja2ub ja2ubmapeditor
do
mv artifacts/${APP}/*.exe gamedir/${APP}.exe
done
- name: Create version information file
shell: bash
run: |
set -eux
# "-" separates words, "_" combines words, see double-click behavior
DIST_PREFIX='JA2_113'
DIST_NAME="${DIST_PREFIX}-${GAME_VERSION}-G${GAMEDIR_COMMIT_SHA:0:4}L${GAMEDIR_LANGUAGES_COMMIT_SHA:0:4}-${{ matrix.language }}"
echo "DIST_NAME=$DIST_NAME" >> $GITHUB_ENV
echo "If you encounter problems during gameplay, please provide the following version information:
Distribution Name: $DIST_NAME
Game Version: $GAME_VERSION
Language: ${{ matrix.language }}
Build Information: $GAME_BUILD_INFORMATION
Source Repository: $GITHUB_REPOSITORY
Source Commit SHA: $GITHUB_SHA
Source Commit Date: $SOURCE_COMMIT_DATETIME
Gamedir Repository: $GAMEDIR_REPOSITORY
Gamedir Commit SHA: $GAMEDIR_COMMIT_SHA
Gamedir Commit Date: $GAMEDIR_COMMIT_DATETIME
Gamedir Languages Repository: $GAMEDIR_LANGUAGES_REPOSITORY
Gamedir Languages Commit SHA: $GAMEDIR_LANGUAGES_COMMIT_SHA
Gamedir Languages Commit Date: $GAMEDIR_LANGUAGES_COMMIT_DATETIME
" > "gamedir/${DIST_PREFIX}-Version.txt"
cat "gamedir/${DIST_PREFIX}-Version.txt"
- name: Create release archive
shell: bash
run: |
set -eux
mkdir dist/
cd gamedir/
7z a -bb -xr'!.*' "../dist/${DIST_NAME}.7z" .
- name: Upload
uses: actions/upload-artifact@v7
with:
name: ${{ matrix.language }}_release
path: dist/
compression-level: 0
release: release:
needs: [ workflow_setup, assemble ] needs: [ workflow_setup, build ]
if: needs.workflow_setup.outputs.assemble_release == 'true' if: needs.workflow_setup.outputs.assemble_release == 'true'
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Download artifacts - name: Download artifacts
uses: actions/download-artifact@v8 uses: actions/download-artifact@v4
with: with:
path: dist path: dist
pattern: '*_release' pattern: '*_release'
merge-multiple: true merge-multiple: true
- name: Checkout Repo - name: Checkout Repo
uses: actions/checkout@v7 uses: actions/checkout@v4
with: with:
path: source path: source
fetch-depth: 1 fetch-depth: 1
+206
View File
@@ -0,0 +1,206 @@
name: build language
on:
workflow_call:
inputs:
language:
description: 'any of Chinese German English French Polish Italian Dutch Russian'
required: true
default: 'English'
type: string
assemble:
description: 'assemble full package'
required: true
default: true
type: boolean
continue-on-error:
description: 'allows a language to fail, used when building all languages'
required: false
default: false
type: boolean
jobs:
compile:
runs-on: windows-latest # required for msbuild
continue-on-error: ${{ inputs.continue-on-error }}
strategy:
fail-fast: false
matrix:
application: [ja2, ja2mapeditor, ja2ub, ja2ubmapeditor]
steps:
- name: Checkout source
uses: actions/checkout@v4
- name: Download versions.env
uses: actions/download-artifact@v4
with:
name: versions.env
path: artifacts
- name: Restore versions.env
shell: bash
run: cat artifacts/versions.env >> $GITHUB_ENV
# TODO: Move this into CMake, just taking the GitHub Actions parameters as -D variables
- name: Update GameVersion.cpp
shell: bash
run: |
set -eux
INPUTS_LANGUAGE=${{ inputs.language }}
GAME_VERSION=$(echo "$GAME_VERSION" | tr -cd '[:print:]' | tr -d '"\\')
GAME_BUILD="${INPUTS_LANGUAGE:0:2} $GAME_BUILD_INFORMATION"
GAME_BUILD=$(echo "$GAME_BUILD" | tr -cd '[:print:]' | tr -d '"\\')
sed -i "s|@Version@|${GAME_VERSION:0:15}|" Ja2/GameVersion.cpp
sed -i "s|@Build@|${GAME_BUILD:0:255}|" Ja2/GameVersion.cpp
cat Ja2/GameVersion.cpp
- name: Prepare build properties
shell: bash
run: |
set -eux
touch CMakePresets.json
JA2Language=$(echo '${{ inputs.language }}' | tr '[:lower:]' '[:upper:]')
JA2Application=$(echo '${{ matrix.application }}' | tr '[:lower:]' '[:upper:]')
echo "
JA2Language=$JA2Language
JA2Application=$JA2Application
" >> $GITHUB_ENV
- uses: microsoft/setup-msbuild@v2
with:
msbuild-architecture: x86
- uses: ilammy/msvc-dev-cmd@v1
with:
arch: x86
- name: Prepare build
run: |
cmake -S . -B build -GNinja -DCMAKE_BUILD_TYPE=RelWithDebInfo -DLanguages="$Env:JA2Language" -DApplications="$Env:JA2Application"
- name: Build
run: |
cmake --build build/ -- -v
- name: List build artifacts
shell: bash
run: |
find build/
- name: Upload
uses: actions/upload-artifact@v4
with:
name: ${{ inputs.language }}_${{ matrix.application }}
path: build/*.exe
assemble:
needs: [ compile ]
if: inputs.assemble
runs-on: windows-latest # required for case-insensitive filesystem handling
continue-on-error: ${{ inputs.continue-on-error }}
steps:
- name: Download versions.env
uses: actions/download-artifact@v4
with:
name: versions.env
path: artifacts
- name: Restore versions.env
shell: bash
run: cat artifacts/versions.env >> $GITHUB_ENV
- name: Checkout gamedir
uses: actions/checkout@v4
with:
repository: ${{ env.GAMEDIR_REPOSITORY }}
ref: ${{ env.GAMEDIR_COMMIT_SHA }}
path: gamedir
- name: Checkout gamedir-languages
if: inputs.language != 'English'
uses: actions/checkout@v4
with:
repository: ${{ env.GAMEDIR_LANGUAGES_REPOSITORY }}
ref: ${{ env.GAMEDIR_LANGUAGES_COMMIT_SHA }}
path: gamedir-languages
- name: Copy gamedir-languages files to gamedir
if: inputs.language != 'English'
shell: bash
run: |
set -eux
cp -a gamedir-languages/${{ inputs.language }}_Version/* gamedir/
- name: Download ja2
uses: actions/download-artifact@v4
with:
name: ${{ inputs.language }}_ja2
path: artifacts/ja2
- name: Download ja2mapeditor
uses: actions/download-artifact@v4
with:
name: ${{ inputs.language }}_ja2mapeditor
path: artifacts/ja2mapeditor
- name: Download ja2ub
uses: actions/download-artifact@v4
with:
name: ${{ inputs.language }}_ja2ub
path: artifacts/ja2ub
- name: Download ja2ubmapeditor
uses: actions/download-artifact@v4
with:
name: ${{ inputs.language }}_ja2ubmapeditor
path: artifacts/ja2ubmapeditor
- name: Copy application files to gamedir
shell: bash
run: |
set -eux
for APP in ja2 ja2mapeditor ja2ub ja2ubmapeditor
do
mv artifacts/${APP}/*.exe gamedir/${APP}.exe
done
- name: Create version information file
shell: bash
run: |
set -eux
# "-" separates words, "_" combines words, see double-click behavior
DIST_PREFIX='JA2_113'
DIST_NAME="${DIST_PREFIX}-${GAME_VERSION}-G${GAMEDIR_COMMIT_SHA:0:4}L${GAMEDIR_LANGUAGES_COMMIT_SHA:0:4}-${{ inputs.language }}"
echo "DIST_NAME=$DIST_NAME" >> $GITHUB_ENV
echo "If you encounter problems during gameplay, please provide the following version information:
Distribution Name: $DIST_NAME
Game Version: $GAME_VERSION
Language: ${{ inputs.language }}
Build Information: $GAME_BUILD_INFORMATION
Source Repository: $GITHUB_REPOSITORY
Source Commit SHA: $GITHUB_SHA
Source Commit Date: $SOURCE_COMMIT_DATETIME
Gamedir Repository: $GAMEDIR_REPOSITORY
Gamedir Commit SHA: $GAMEDIR_COMMIT_SHA
Gamedir Commit Date: $GAMEDIR_COMMIT_DATETIME
Gamedir Languages Repository: $GAMEDIR_LANGUAGES_REPOSITORY
Gamedir Languages Commit SHA: $GAMEDIR_LANGUAGES_COMMIT_SHA
Gamedir Languages Commit Date: $GAMEDIR_LANGUAGES_COMMIT_DATETIME
" > "gamedir/${DIST_PREFIX}-Version.txt"
cat "gamedir/${DIST_PREFIX}-Version.txt"
- name: Create release archive
shell: bash
run: |
set -eux
mkdir dist/
cd gamedir/
7z a -bb -xr'!.*' "../dist/${DIST_NAME}.7z" .
- name: Upload
uses: actions/upload-artifact@v4
with:
name: ${{ inputs.language }}_release
path: dist/
compression-level: 0
+27 -29
View File
@@ -27,11 +27,7 @@ if(ADDRESS_SANITIZER)
endif() endif()
if(MSVC) if(MSVC)
# TODO: fix the warnings here and turn them on add_compile_options("/wd4838")
# https://learn.microsoft.com/en-us/cpp/error-messages/compiler-warnings/compiler-warnings-c4000-through-c4199
add_compile_options("/wd4838") # silence implicit narrowing conversion warnings
# add_compile_options("/w14061")
# add_compile_options("/w14062")
endif() endif()
# whether we are using MSBuild as a generator # whether we are using MSBuild as a generator
@@ -106,13 +102,15 @@ foreach(lib IN LISTS Ja2_Libs)
add_subdirectory(${lib}) add_subdirectory(${lib})
endforeach() endforeach()
# language library relies on Application preprocessor definition; compiled once per app # language library relies on Application _and_ Language preprocessor definition. very bad.
# with all 8 languages built in and selected at runtime (docs/plans/language-design.md).
add_subdirectory(i18n) add_subdirectory(i18n)
# simple function to validate the Application choice # simple function to validate Languages and Application choices
include(cmake/ValidateOptions.cmake) include(cmake/ValidateOptions.cmake)
set(ValidLanguages CHINESE DUTCH ENGLISH FRENCH GERMAN ITALIAN POLISH RUSSIAN)
ValidateOptions("${ValidLanguages}" "Languages" "${Languages}" "LangTargets")
set(ValidApplications JA2 JA2MAPEDITOR JA2UB JA2UBMAPEDITOR) set(ValidApplications JA2 JA2MAPEDITOR JA2UB JA2UBMAPEDITOR)
ValidateOptions("${ValidApplications}" "Applications" "${Applications}" "ApplicationTargets") ValidateOptions("${ValidApplications}" "Applications" "${Applications}" "ApplicationTargets")
@@ -138,32 +136,32 @@ foreach(app IN LISTS ApplicationTargets)
target_compile_definitions(${game_library} PRIVATE ${compilationFlags} ${debugFlags}) target_compile_definitions(${game_library} PRIVATE ${compilationFlags} ${debugFlags})
endforeach() endforeach()
# executable for the application, e.g. JA2.exe foreach(lang IN LISTS LangTargets)
set(exe ${app}) # executable for an application/language combination, e.g. JA2_ENGLISH.exe
add_executable(${exe} WIN32 set(exe ${app}_${lang})
sgp/sgp.cpp add_executable(${exe} WIN32
Ja2/Res/ja2.rc sgp/sgp.cpp
) Ja2/Res/ja2.rc
target_link_libraries(${exe} PRIVATE ${Ja2_Libraries} $<IF:${usingMsBuild},legacy_stdio_definitions.lib,>) )
target_link_options(${exe} PRIVATE $<IF:${usingMsBuild},/SAFESEH:NO,>) target_link_libraries(${exe} PRIVATE ${Ja2_Libraries} $<IF:${usingMsBuild},legacy_stdio_definitions.lib,>)
target_compile_definitions(${exe} PRIVATE ${compilationFlags} ${debugFlags}) target_link_options(${exe} PRIVATE $<IF:${usingMsBuild},/SAFESEH:NO,>)
target_compile_definitions(${exe} PRIVATE ${compilationFlags} ${debugFlags})
# language library for the application, e.g. JA2MAPEDITOR_i18n — one per app, all 8 # language library for an application, e.g. JA2MAPEDITOR_ENGLISH_i18n
# languages compiled in, selected at runtime by BindLanguageStrings set(language_library ${exe}_i18n)
set(language_library ${exe}_i18n) add_library(${language_library})
add_library(${language_library}) target_sources(${language_library} PRIVATE ${i18nSrc})
target_sources(${language_library} PRIVATE ${i18nSrc}) target_compile_definitions(${language_library} PRIVATE ${compilationFlags} ${debugFlags} ${lang})
target_compile_definitions(${language_library} PRIVATE ${compilationFlags} ${debugFlags}) target_link_libraries(${exe} PRIVATE ${language_library})
target_compile_options(${language_library} PRIVATE /w14062)
target_link_libraries(${exe} PRIVATE ${language_library})
# go through all game libraries again and link them to the app executable # go through all game libraries again and link them to the app/language executable
foreach(lib IN LISTS Ja2_Libs) foreach(lib IN LISTS Ja2_Libs)
target_link_libraries(${exe} PRIVATE ${app}_${lib}) target_link_libraries(${exe} PRIVATE ${app}_${lib})
endforeach()
endforeach() endforeach()
# for SGP only # for SGP only
target_link_libraries(${app}_sgp PRIVATE "ddraw.lib" "${PROJECT_SOURCE_DIR}/fmodvc.lib") target_link_libraries(${app}_sgp PRIVATE "ddraw.lib" "${PROJECT_SOURCE_DIR}/fmodvc.lib")
target_link_libraries(${app}_sgp PRIVATE libpng) target_link_libraries(${app}_sgp PRIVATE libpng)
target_compile_definitions(${app}_sgp PRIVATE ${compilationFlags} ${debugFlags} NO_ZLIB_COMPRESSION) target_compile_definitions(${app}_sgp PRIVATE NO_ZLIB_COMPRESSION)
endforeach() endforeach()
+2 -2
View File
@@ -29,7 +29,7 @@ void IncreaseSelectionDensity();
void DecreaseSelectionDensity(); void DecreaseSelectionDensity();
void RemoveCursors(); void RemoveCursors();
extern STR16 *wszSelType; extern STR16 wszSelType[6];
extern BOOLEAN gfCurrentSelectionWithRightButton; extern BOOLEAN gfCurrentSelectionWithRightButton;
@@ -40,4 +40,4 @@ extern BOOLEAN gfCurrentSelectionWithRightButton;
#endif #endif
#endif #endif
+1 -1
View File
@@ -325,7 +325,7 @@ extern UINT8 gubCurrMercMode, gubPrevMercMode;
#define NUM_DIFF_LVLS 5 #define NUM_DIFF_LVLS 5
extern STR16 *zDiffNames; extern STR16 zDiffNames[NUM_DIFF_LVLS];
extern INT16 sCurBaseDiff; extern INT16 sCurBaseDiff;
extern INT16 gsSelectedMercID; extern INT16 gsSelectedMercID;
extern INT32 gsSelectedMercGridNo; extern INT32 gsSelectedMercGridNo;
+1 -1
View File
@@ -40,7 +40,7 @@ MOUSE_REGION popupRegion;
UINT16 gusEntryHeight; UINT16 gusEntryHeight;
BOOLEAN fWaitingForLButtonRelease = FALSE; BOOLEAN fWaitingForLButtonRelease = FALSE;
extern CHAR16 (*gszScheduleActions)[20]; // NUM_SCHEDULE_ACTIONS entries extern CHAR16 gszScheduleActions[ NUM_SCHEDULE_ACTIONS ][20];
//Finds the string for any popup menu in JA2 -- the strings are stored //Finds the string for any popup menu in JA2 -- the strings are stored
//in different ways in each instance. //in different ways in each instance.
+3 -1
View File
@@ -332,6 +332,7 @@ BOOLEAN LoadGameSettings()
gGameSettings.fOptions[TOPTION_ALT_START_AIM] = iniReader.ReadBoolean("JA2 Game Settings", "TOPTION_ALT_START_AIM" , TRUE); // Start at max aiming level instead of default no aiming gGameSettings.fOptions[TOPTION_ALT_START_AIM] = iniReader.ReadBoolean("JA2 Game Settings", "TOPTION_ALT_START_AIM" , TRUE); // Start at max aiming level instead of default no aiming
gGameSettings.fOptions[TOPTION_ALT_PATHFINDING] = iniReader.ReadBoolean("JA2 Game Settings", "TOPTION_ALT_PATHFINDING" , FALSE); // A* pathfinding gGameSettings.fOptions[TOPTION_ALT_PATHFINDING] = iniReader.ReadBoolean("JA2 Game Settings", "TOPTION_ALT_PATHFINDING" , FALSE); // A* pathfinding
gGameSettings.fOptions[TOPTION_USE_LEGACY_TACTICALAI] = iniReader.ReadBoolean("JA2 Game Settings", "TOPTION_USE_LEGACY_TACTICALAI", FALSE); // A* pathfinding
gGameSettings.fOptions[TOPTION_MERCENARY_FORMATIONS] = iniReader.ReadBoolean("JA2 Game Settings","TOPTION_MERCENARY_FORMATIONS" , FALSE ); // Flugente: mercenary formations gGameSettings.fOptions[TOPTION_MERCENARY_FORMATIONS] = iniReader.ReadBoolean("JA2 Game Settings","TOPTION_MERCENARY_FORMATIONS" , FALSE ); // Flugente: mercenary formations
gGameSettings.fOptions[TOPTION_SHOW_ENEMY_LOCATION] = iniReader.ReadBoolean("JA2 Game Settings","TOPTION_SHOW_ENEMY_LOCATION" , FALSE); // sevenfm: show locations of known enemies gGameSettings.fOptions[TOPTION_SHOW_ENEMY_LOCATION] = iniReader.ReadBoolean("JA2 Game Settings","TOPTION_SHOW_ENEMY_LOCATION" , FALSE); // sevenfm: show locations of known enemies
gGameSettings.fOptions[TOPTION_REPORT_MISS_MARGIN] = iniReader.ReadBoolean("JA2 Game Settings","TOPTION_REPORT_MISS_MARGIN" , FALSE ); // HEADROCK HAM 4: Shot offset report gGameSettings.fOptions[TOPTION_REPORT_MISS_MARGIN] = iniReader.ReadBoolean("JA2 Game Settings","TOPTION_REPORT_MISS_MARGIN" , FALSE ); // HEADROCK HAM 4: Shot offset report
@@ -616,6 +617,7 @@ BOOLEAN SaveGameSettings()
settings << "TOPTION_SHOW_ENEMY_LOCATION = " << (gGameSettings.fOptions[TOPTION_SHOW_ENEMY_LOCATION] ? "TRUE" : "FALSE" ) << endl; settings << "TOPTION_SHOW_ENEMY_LOCATION = " << (gGameSettings.fOptions[TOPTION_SHOW_ENEMY_LOCATION] ? "TRUE" : "FALSE" ) << endl;
settings << "TOPTION_ALT_START_AIM = " << (gGameSettings.fOptions[TOPTION_ALT_START_AIM] ? "TRUE" : "FALSE") << endl; settings << "TOPTION_ALT_START_AIM = " << (gGameSettings.fOptions[TOPTION_ALT_START_AIM] ? "TRUE" : "FALSE") << endl;
settings << "TOPTION_ALT_PATHFINDING = " << (gGameSettings.fOptions[TOPTION_ALT_PATHFINDING] ? "TRUE" : "FALSE") << endl; settings << "TOPTION_ALT_PATHFINDING = " << (gGameSettings.fOptions[TOPTION_ALT_PATHFINDING] ? "TRUE" : "FALSE") << endl;
settings << "TOPTION_USE_LEGACY_TACTICALAI = " << (gGameSettings.fOptions[TOPTION_USE_LEGACY_TACTICALAI] ? "TRUE" : "FALSE") << endl;
settings << "TOPTION_CHEAT_MODE_OPTIONS_HEADER = " << (gGameSettings.fOptions[TOPTION_CHEAT_MODE_OPTIONS_HEADER] ? "TRUE" : "FALSE" ) << endl; settings << "TOPTION_CHEAT_MODE_OPTIONS_HEADER = " << (gGameSettings.fOptions[TOPTION_CHEAT_MODE_OPTIONS_HEADER] ? "TRUE" : "FALSE" ) << endl;
settings << "TOPTION_FORCE_BOBBY_RAY_SHIPMENTS = " << (gGameSettings.fOptions[TOPTION_FORCE_BOBBY_RAY_SHIPMENTS] ? "TRUE" : "FALSE" ) << endl; settings << "TOPTION_FORCE_BOBBY_RAY_SHIPMENTS = " << (gGameSettings.fOptions[TOPTION_FORCE_BOBBY_RAY_SHIPMENTS] ? "TRUE" : "FALSE" ) << endl;
@@ -846,6 +848,7 @@ void InitGameSettings()
gGameSettings.fOptions[TOPTION_SHOW_ENEMY_LOCATION] = FALSE; // sevenfm: show locations of known enemies gGameSettings.fOptions[TOPTION_SHOW_ENEMY_LOCATION] = FALSE; // sevenfm: show locations of known enemies
gGameSettings.fOptions[TOPTION_ALT_START_AIM] = TRUE; gGameSettings.fOptions[TOPTION_ALT_START_AIM] = TRUE;
gGameSettings.fOptions[TOPTION_ALT_PATHFINDING] = FALSE; gGameSettings.fOptions[TOPTION_ALT_PATHFINDING] = FALSE;
gGameSettings.fOptions[TOPTION_USE_LEGACY_TACTICALAI] = FALSE;
// arynn: Cheat/Debug Menu // arynn: Cheat/Debug Menu
gGameSettings.fOptions[ TOPTION_CHEAT_MODE_OPTIONS_HEADER ] = FALSE; gGameSettings.fOptions[ TOPTION_CHEAT_MODE_OPTIONS_HEADER ] = FALSE;
@@ -2749,7 +2752,6 @@ void LoadSkillTraitsExternalSettings()
gSkillTraitValues.ubTERepairRobotPenaltyReduction = iniReader.ReadInteger("Technician","REPAIR_SPEED_ROBOT_PENALTY_REDUCTION", 30, 0, 100); gSkillTraitValues.ubTERepairRobotPenaltyReduction = iniReader.ReadInteger("Technician","REPAIR_SPEED_ROBOT_PENALTY_REDUCTION", 30, 0, 100);
if (gSkillTraitValues.ubTETraitsNumToRepairRobot == 2) {gSkillTraitValues.ubTERepairRobotPenaltyReduction /= 2; } if (gSkillTraitValues.ubTETraitsNumToRepairRobot == 2) {gSkillTraitValues.ubTERepairRobotPenaltyReduction /= 2; }
gSkillTraitValues.fTETraitsCanRestoreItemThreshold = iniReader.ReadBoolean( "Technician", "MERCS_CAN_DO_ADVANCED_REPAIRS", FALSE ); gSkillTraitValues.fTETraitsCanRestoreItemThreshold = iniReader.ReadBoolean( "Technician", "MERCS_CAN_DO_ADVANCED_REPAIRS", FALSE );
gSkillTraitValues.ubTechLevelNeededForAdvancedRepair = iniReader.ReadInteger("Technician", "TECH_LEVEL_NEEDED_FOR_ADVANCED_REPAIR", 2, 1, 2);
// DOCTOR // DOCTOR
gSkillTraitValues.ubDONumberTraitsNeededForSurgery = iniReader.ReadInteger("Doctor","NUMBER_OF_TRAITS_NEEDED_FOR_SURGERY", 1, 0, 2); gSkillTraitValues.ubDONumberTraitsNeededForSurgery = iniReader.ReadInteger("Doctor","NUMBER_OF_TRAITS_NEEDED_FOR_SURGERY", 1, 0, 2);
+1 -3
View File
@@ -8,10 +8,8 @@
#include "Campaign Types.h" #include "Campaign Types.h"
#include "environment.h" #include "environment.h"
#include <string_view>
#define GAME_INI_FILE "Ja2.ini" #define GAME_INI_FILE "Ja2.ini"
constexpr std::string_view LANGUAGE_INI_FILE{"Language.ini"};
//If you add any options, MAKE sure you add the corresponding string to the Options Screen string array. //If you add any options, MAKE sure you add the corresponding string to the Options Screen string array.
// look up : zOptionsScreenHelpText , zOptionsToggleText // look up : zOptionsScreenHelpText , zOptionsToggleText
//Also, define its initialization and add its load/save to INI lines in : InitGameSettings() , SaveGameSettings() , LoadGameSettings() //Also, define its initialization and add its load/save to INI lines in : InitGameSettings() , SaveGameSettings() , LoadGameSettings()
@@ -107,6 +105,7 @@ enum
TOPTION_SHOW_ENEMY_LOCATION, TOPTION_SHOW_ENEMY_LOCATION,
TOPTION_ALT_START_AIM, TOPTION_ALT_START_AIM,
TOPTION_ALT_PATHFINDING, TOPTION_ALT_PATHFINDING,
TOPTION_USE_LEGACY_TACTICALAI,
// arynn: Debug/Cheat // arynn: Debug/Cheat
TOPTION_CHEAT_MODE_OPTIONS_HEADER, TOPTION_CHEAT_MODE_OPTIONS_HEADER,
@@ -2147,7 +2146,6 @@ typedef struct
UINT8 ubTERepairRobotPenaltyReduction; UINT8 ubTERepairRobotPenaltyReduction;
UINT8 ubTETraitsNumToRepairRobot; UINT8 ubTETraitsNumToRepairRobot;
BOOLEAN fTETraitsCanRestoreItemThreshold; BOOLEAN fTETraitsCanRestoreItemThreshold;
UINT8 ubTechLevelNeededForAdvancedRepair; // if we have a high enough technician level, we can repair items above the normal threshold (1 for technician, 2 for engineer)
// DOCTOR // DOCTOR
UINT8 ubDONumberTraitsNeededForSurgery; UINT8 ubDONumberTraitsNeededForSurgery;
+1 -1
View File
@@ -174,7 +174,7 @@ private:
static VideoPlayer s_VP(VideoPlayer::VT_SMK | VideoPlayer::VT_BINK); static VideoPlayer s_VP(VideoPlayer::VT_SMK | VideoPlayer::VT_BINK);
extern STR16* gzIntroScreen; extern STR16 gzIntroScreen[];
extern HVSURFACE ghFrameBuffer; extern HVSURFACE ghFrameBuffer;
enum enum
+1 -1
View File
@@ -89,7 +89,7 @@ extern BOOLEAN gfDontOverRideSaveBuffer; //this variable can be unset if u
extern void HandleTacticalUILoseCursorFromOtherScreen( ); extern void HandleTacticalUILoseCursorFromOtherScreen( );
extern STR16* pUpdatePanelButtons; extern STR16 pUpdatePanelButtons[];
#define NUM_CHAT_TOGGLES 2 #define NUM_CHAT_TOGGLES 2
+1 -1
View File
@@ -69,7 +69,7 @@ BOOLEAN gfStartedFromMapScreen = FALSE;
BOOLEAN fRestoreBackgroundForMessageBox = FALSE; BOOLEAN fRestoreBackgroundForMessageBox = FALSE;
BOOLEAN gfDontOverRideSaveBuffer = TRUE; //this variable can be unset if ur in a non gamescreen and DONT want the msg box to use the save buffer BOOLEAN gfDontOverRideSaveBuffer = TRUE; //this variable can be unset if ur in a non gamescreen and DONT want the msg box to use the save buffer
extern void HandleTacticalUILoseCursorFromOtherScreen( ); extern void HandleTacticalUILoseCursorFromOtherScreen( );
extern STR16* pUpdatePanelButtons; extern STR16 pUpdatePanelButtons[];
CHAR16 gzUserDefinedButton1[ 128 ]; CHAR16 gzUserDefinedButton1[ 128 ];
CHAR16 gzUserDefinedButton2[ 128 ]; CHAR16 gzUserDefinedButton2[ 128 ];
+1 -1
View File
@@ -1822,7 +1822,7 @@ void HandleHighLightedText( BOOLEAN fHighLight )
bLastRegion = -1; bLastRegion = -1;
} }
if( bHighLight != -1 && toggle_box_array[bHighLight] != -1) if( bHighLight != -1 )
{ {
if( bHighLight < OPT_FIRST_COLUMN_TOGGLE_CUT_OFF ) if( bHighLight < OPT_FIRST_COLUMN_TOGGLE_CUT_OFF )
{ {
+50
View File
@@ -1468,6 +1468,31 @@ BOOLEAN LoadMercBioInfo(UINT8 ubIndex, STR16 pInfoString, STR16 pAddInfo)
//{ //{
// if( pInfoString[i] > 33 ) // if( pInfoString[i] > 33 )
// pInfoString[i] -= 1; // pInfoString[i] -= 1;
// #ifdef POLISH
// switch( pInfoString[ i ] )
// {
// case 260: pInfoString[ i ] = 165; break;
// case 262: pInfoString[ i ] = 198; break;
// case 280: pInfoString[ i ] = 202; break;
// case 321: pInfoString[ i ] = 163; break;
// case 323: pInfoString[ i ] = 209; break;
// case 211: pInfoString[ i ] = 211; break;
// case 346: pInfoString[ i ] = 338; break;
// case 379: pInfoString[ i ] = 175; break;
// case 377: pInfoString[ i ] = 143; break;
// case 261: pInfoString[ i ] = 185; break;
// case 263: pInfoString[ i ] = 230; break;
// case 281: pInfoString[ i ] = 234; break;
// case 322: pInfoString[ i ] = 179; break;
// case 324: pInfoString[ i ] = 241; break;
// case 243: pInfoString[ i ] = 243; break;
// case 347: pInfoString[ i ] = 339; break;
// case 380: pInfoString[ i ] = 191; break;
// case 378: pInfoString[ i ] = 376; break;
// }
// #endif
//} //}
DecodeString(pInfoString, SIZE_MERC_BIO_INFO); DecodeString(pInfoString, SIZE_MERC_BIO_INFO);
@@ -1489,6 +1514,31 @@ BOOLEAN LoadMercBioInfo(UINT8 ubIndex, STR16 pInfoString, STR16 pAddInfo)
//{ //{
// if( pAddInfo[i] > 33 ) // if( pAddInfo[i] > 33 )
// pAddInfo[i] -= 1; // pAddInfo[i] -= 1;
// #ifdef POLISH
// switch( pAddInfo[ i ] )
// {
// case 260: pAddInfo[ i ] = 165; break;
// case 262: pAddInfo[ i ] = 198; break;
// case 280: pAddInfo[ i ] = 202; break;
// case 321: pAddInfo[ i ] = 163; break;
// case 323: pAddInfo[ i ] = 209; break;
// case 211: pAddInfo[ i ] = 211; break;
// case 346: pAddInfo[ i ] = 338; break;
// case 379: pAddInfo[ i ] = 175; break;
// case 377: pAddInfo[ i ] = 143; break;
// case 261: pAddInfo[ i ] = 185; break;
// case 263: pAddInfo[ i ] = 230; break;
// case 281: pAddInfo[ i ] = 234; break;
// case 322: pAddInfo[ i ] = 179; break;
// case 324: pAddInfo[ i ] = 241; break;
// case 243: pAddInfo[ i ] = 243; break;
// case 347: pAddInfo[ i ] = 339; break;
// case 380: pAddInfo[ i ] = 191; break;
// case 378: pAddInfo[ i ] = 376; break;
// }
// #endif
//} //}
DecodeString(pAddInfo, SIZE_MERC_BIO_INFO); DecodeString(pAddInfo, SIZE_MERC_BIO_INFO);
+1 -1
View File
@@ -75,7 +75,7 @@ extern INT32 iAddExplosives ;
extern INT32 iAddMechanical ; extern INT32 iAddMechanical ;
// pop up strings // pop up strings
extern STR16* pImpPopUpStrings; extern STR16 pImpPopUpStrings[];
//extern BOOLEAN fIMPCompletedFlag; //extern BOOLEAN fIMPCompletedFlag;
+2 -2
View File
@@ -7,6 +7,6 @@ void RenderIMPPersonalityEntrance( void );
void ExitIMPPersonalityEntrance( void ); void ExitIMPPersonalityEntrance( void );
void HandleIMPPersonalityEntrance( void ); void HandleIMPPersonalityEntrance( void );
extern STR16* pSkillTraitBeginIMPStrings; STR16 pSkillTraitBeginIMPStrings[];
#endif #endif
+4 -4
View File
@@ -9,13 +9,13 @@ void PrintImpText( void );
void PrintIMPPersonalityQuizQuestionAndAnsers( void ); void PrintIMPPersonalityQuizQuestionAndAnsers( void );
// buttons text // buttons text
extern STR16* pImpButtonText; extern STR16 pImpButtonText[];
// extra strings not found in IMP Text Document // extra strings not found in IMP Text Document
extern STR16* pExtraIMPStrings; extern STR16 pExtraIMPStrings[];
extern STR16* pSkillTraitBeginIMPStrings; // added - SANDRO extern STR16 pSkillTraitBeginIMPStrings[]; // added - SANDRO
enum{ enum{
IMP_HOME_1, IMP_HOME_1,
@@ -145,4 +145,4 @@ enum{
}; };
#endif #endif
+4
View File
@@ -6,6 +6,10 @@ set(ModularizedTacticalAISrc
"${CMAKE_CURRENT_SOURCE_DIR}/src/LegacyAIPlanFactory.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/LegacyAIPlanFactory.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/src/LegacyCreaturePlan.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/LegacyCreaturePlan.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/src/LegacyZombiePlan.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/LegacyZombiePlan.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/src/BoxerPlan.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/src/CivilianPlan.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/src/RobotPlan.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/src/SoldierPlan.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/src/NullPlan.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/NullPlan.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/src/NullPlanFactory.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/NullPlanFactory.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/src/Plan.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/Plan.cpp"
+23
View File
@@ -0,0 +1,23 @@
#pragma once
#include "Plan.h"
namespace AI
{
namespace tactical
{
/**@class LegacyAIPlan
* @brief Component/Concrete Product. Wrapper/Re-Write of DecideAction()
*
* Wrapper around boxer related AI uplifted from original DecideAction() routines
*/
class LegacyAIBoxerPlan: public Plan
{
private:
public:
LegacyAIBoxerPlan(SOLDIERTYPE* npc);
virtual void execute(PlanInputData& environment);
virtual bool done() const {return false;}
};
}
}
@@ -0,0 +1,23 @@
#pragma once
#include "Plan.h"
namespace AI
{
namespace tactical
{
/**@class LegacyAIPlan
* @brief Component/Concrete Product. Wrapper/Re-Write of DecideAction()
*
* Wrapper around civilian/noncombatant related AI uplifted from original DecideAction() routines
*/
class CivilianPlan : public Plan
{
private:
public:
CivilianPlan(SOLDIERTYPE* npc);
virtual void execute(PlanInputData& environment);
virtual bool done() const { return false; }
};
}
}
+23
View File
@@ -0,0 +1,23 @@
#pragma once
#include "Plan.h"
namespace AI
{
namespace tactical
{
/**@class LegacyAIPlan
* @brief Component/Concrete Product. Wrapper/Re-Write of DecideAction()
*
* Wrapper around robot related AI uplifted from original DecideAction() routines
*/
class RobotPlan : public Plan
{
private:
public:
RobotPlan(SOLDIERTYPE* npc);
virtual void execute(PlanInputData& environment);
virtual bool done() const { return false; }
};
}
}
@@ -0,0 +1,23 @@
#pragma once
#include "Plan.h"
namespace AI
{
namespace tactical
{
/**@class LegacyAIPlan
* @brief Component/Concrete Product. Wrapper/Re-Write of DecideAction()
*
* Wrapper around soldier related AI uplifted from original DecideAction() routines
*/
class SoldierPlan : public Plan
{
private:
public:
SoldierPlan(SOLDIERTYPE* npc);
virtual void execute(PlanInputData& environment);
virtual bool done() const { return false; }
};
}
}
+35
View File
@@ -0,0 +1,35 @@
#include "../include/BoxerPlan.h"
#include "../../TacticalAI/ai.h"
namespace AI
{
namespace tactical
{
LegacyAIBoxerPlan::LegacyAIBoxerPlan(SOLDIERTYPE* npc)
: Plan(npc)
{
}
void LegacyAIBoxerPlan::execute(PlanInputData& environment)
{
switch (get_npc()->aiData.bAlertStatus)
{
case STATUS_GREEN:
get_npc()->aiData.bAction = DecideActionGreenBoxer(get_npc());
break;
case STATUS_YELLOW:
get_npc()->aiData.bAction = DecideActionGreenBoxer(get_npc());
break;
case STATUS_RED:
get_npc()->aiData.bAction = DecideActionBlackBoxer(get_npc());
break;
case STATUS_BLACK:
get_npc()->aiData.bAction = DecideActionBlackBoxer(get_npc());
break;
}
}
} // namespace tactical
} // namespace AI
@@ -0,0 +1,51 @@
#include "../include/CivilianPlan.h"
#include "../../TacticalAI/ai.h"
#include "NPC.h"
#include "Soldier Profile.h"
namespace AI
{
namespace tactical
{
CivilianPlan::CivilianPlan(SOLDIERTYPE* npc)
: Plan(npc)
{
}
void CivilianPlan::execute(PlanInputData& environment)
{
if ( !environment.turn_based() )
{
if ( (get_npc()->ubProfile != NO_PROFILE) && (gMercProfiles[get_npc()->ubProfile].ubMiscFlags3 & PROFILE_MISC_FLAG3_HANDLE_DONE_TRAVERSAL) )
{
TriggerNPCWithGivenApproach(get_npc()->ubProfile, APPROACH_DONE_TRAVERSAL, FALSE);
gMercProfiles[get_npc()->ubProfile].ubMiscFlags3 &= (~PROFILE_MISC_FLAG3_HANDLE_DONE_TRAVERSAL);
get_npc()->ubQuoteActionID = 0;
// wait a tiny bit
get_npc()->aiData.usActionData = 100;
get_npc()->aiData.bAction = AI_ACTION_WAIT;
return;
}
}
switch (get_npc()->aiData.bAlertStatus)
{
case STATUS_GREEN:
get_npc()->aiData.bAction = DecideActionGreenCivilian(get_npc());
break;
case STATUS_YELLOW:
get_npc()->aiData.bAction = DecideActionYellowCivilian(get_npc());
break;
case STATUS_RED:
get_npc()->aiData.bAction = DecideActionRedCivilian(get_npc());
break;
case STATUS_BLACK:
get_npc()->aiData.bAction = DecideActionBlackCivilian(get_npc());
break;
}
}
} // namespace tactical
} // namespace AI
@@ -7,6 +7,10 @@
#include "../include/LegacyCreaturePlan.h" #include "../include/LegacyCreaturePlan.h"
#include "../include/LegacyZombiePlan.h" #include "../include/LegacyZombiePlan.h"
#include "../include/LegacyArmedVehiclePlan.h" #include "../include/LegacyArmedVehiclePlan.h"
#include "../include/BoxerPlan.h"
#include "../include/CivilianPlan.h"
#include "../include/RobotPlan.h"
#include "../include/SoldierPlan.h"
#include "../include/CrowPlan.h" #include "../include/CrowPlan.h"
#include "../include/PlanList.h" #include "../include/PlanList.h"
@@ -14,8 +18,7 @@
#include "../../Tactical/Soldier Control.h" // For SOLDIERTYPE definition #include "../../Tactical/Soldier Control.h" // For SOLDIERTYPE definition
#include "../../Tactical/Animation Data.h" // For the definition of, wait for it... BLOODCAT! #include "../../Tactical/Animation Data.h" // For the definition of, wait for it... BLOODCAT!
#include "Soldier macros.h" #include "Soldier macros.h"
#include "ai.h"
#include <stdio.h>
namespace AI namespace AI
@@ -43,6 +46,11 @@ namespace AI
if(npc->IsZombie()) if(npc->IsZombie())
return new LegacyZombiePlan(npc); return new LegacyZombiePlan(npc);
if (BOXER(npc)) { return new LegacyAIBoxerPlan(npc); }
if (IS_CIV_BODY_TYPE(npc)) { return new CivilianPlan(npc); }
if (ENEMYROBOT(npc)) { return new RobotPlan(npc); }
if (SoldierAI(npc)) { return new SoldierPlan(npc); }
return new LegacyAIPlan(npc); // no special plan for other cases yet, return default legacy AI wrapper return new LegacyAIPlan(npc); // no special plan for other cases yet, return default legacy AI wrapper
} }
+34
View File
@@ -0,0 +1,34 @@
#include "../include/RobotPlan.h"
#include "../../TacticalAI/ai.h"
namespace AI
{
namespace tactical
{
RobotPlan::RobotPlan(SOLDIERTYPE* npc)
: Plan(npc)
{
}
void RobotPlan::execute(PlanInputData& environment)
{
switch (get_npc()->aiData.bAlertStatus)
{
case STATUS_GREEN:
get_npc()->aiData.bAction = DecideActionGreenRobot(get_npc());
break;
case STATUS_YELLOW:
get_npc()->aiData.bAction = DecideActionYellowRobot(get_npc());
break;
case STATUS_RED:
get_npc()->aiData.bAction = DecideActionRedRobot(get_npc());
break;
case STATUS_BLACK:
get_npc()->aiData.bAction = DecideActionBlackRobot(get_npc());
break;
}
}
} // namespace tactical
} // namespace AI
+104
View File
@@ -0,0 +1,104 @@
#include "../include/SoldierPlan.h"
#include "../../TacticalAI/ai.h"
#include "../../TacticalAI/AIInternals.h" // ACTING_ON_SCHEDULE
#include "../../TacticalAI/NPC.h" // NPCReachedDestination
#include "../../Tactical/Dialogue Control.h" // DialogueQueueIsEmpty
#include "../../Utils/Font Control.h" // ScreenMsg about deadlock
#include "../../i18n/include/Text.h" // Sniper warning
#include "../../Utils/message.h" // ditto
namespace AI
{
namespace tactical
{
SoldierPlan::SoldierPlan(SOLDIERTYPE* npc)
: Plan(npc)
{
}
void SoldierPlan::execute(PlanInputData& environment)
{
if (!environment.turn_based())
{
if ((get_npc()->ubProfile != NO_PROFILE) && (gMercProfiles[get_npc()->ubProfile].ubMiscFlags3 & PROFILE_MISC_FLAG3_HANDLE_DONE_TRAVERSAL))
{
TriggerNPCWithGivenApproach(get_npc()->ubProfile, APPROACH_DONE_TRAVERSAL, FALSE);
gMercProfiles[get_npc()->ubProfile].ubMiscFlags3 &= (~PROFILE_MISC_FLAG3_HANDLE_DONE_TRAVERSAL);
get_npc()->ubQuoteActionID = 0;
// wait a tiny bit
get_npc()->aiData.usActionData = 100;
get_npc()->aiData.bAction = AI_ACTION_WAIT;
return;
}
if (get_npc()->bTeam == gbPlayerNum)
{
if (environment.get_tactical_status().fAutoBandageMode)
{
get_npc()->aiData.bAction = DecideAutoBandage(get_npc());
return;
}
}
}
if (get_npc()->bTeam != MILITIA_TEAM)
{
if (!sniperwarning && get_npc()->aiData.bOrders == SNIPER)
{
ScreenMsg(FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, New113Message[MSG113_WATHCHOUTFORSNIPERS]);
sniperwarning = TRUE;
// Flugente: additional dialogue
AdditionalTacticalCharacterDialogue_AllInCurrentSector(NO_PROFILE, ADE_SNIPERWARNING);
}
if (!biggunwarning && FindRocketLauncherOrCannon(get_npc()) != NO_SLOT)
{
biggunwarning = TRUE;
//TODO: don't say this again after reloading a savegame
SayQuoteFromAnyBodyInSector(QUOTE_WEARY_SLASH_SUSPUCIOUS);
}
}
get_npc()->aiData.fAIFlags &= (~AI_CAUTIOUS); // turn off cautious flag
// if status override is set, bypass RED/YELLOW and go directly to GREEN!
if ((get_npc()->aiData.bBypassToGreen) && (get_npc()->aiData.bAlertStatus < STATUS_BLACK))
{
get_npc()->aiData.bAction = DecideActionGreenSoldier(get_npc());
if (!gfTurnBasedAI)
{
// reset bypass now
get_npc()->aiData.bBypassToGreen = 0;
}
}
else
{
switch (get_npc()->aiData.bAlertStatus)
{
case STATUS_GREEN:
get_npc()->aiData.bAction = DecideActionGreenSoldier(get_npc());
break;
case STATUS_YELLOW:
get_npc()->aiData.bAction = DecideActionYellowSoldier(get_npc());
break;
case STATUS_RED:
get_npc()->aiData.bAction = DecideActionRedSoldier(get_npc());
break;
case STATUS_BLACK:
//if ( gGameSettings.fOptions[TOPTION_USE_LEGACY_TACTICALAI] ) // Commented out for now since new AI is WIP
{
get_npc()->aiData.bAction = DecideActionBlackSoldier(get_npc());
}
//else
//{
// get_npc()->aiData.bAction = DecideActionBlackSoldierUtilityAI(get_npc());
//}
break;
}
}
DEBUGAIMSG("Deciding for guynum " << (int)get_npc()->ubID << " at gridno " << get_npc()->sGridNo << ", APs " << get_npc()->bActionPoints <<
", decided action: " << (int)get_npc()->aiData.bAction << ", data " << (int)get_npc()->aiData.usActionData);
}
} // namespace tactical
} // namespace AI
+5 -10
View File
@@ -186,7 +186,7 @@ REPAIR_PASS_SLOTS_TYPE gRepairPassSlotList[ NUM_REPAIR_PASS_TYPES ] =
{ /* HEADROCK HAM B2.8: LBE Slot pass */ 0, 5, VESTPOCKPOS, LTHIGHPOCKPOS, RTHIGHPOCKPOS, CPACKPOCKPOS, BPACKPOCKPOS, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { /* HEADROCK HAM B2.8: LBE Slot pass */ 0, 5, VESTPOCKPOS, LTHIGHPOCKPOS, RTHIGHPOCKPOS, CPACKPOCKPOS, BPACKPOCKPOS, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
}; };
extern STR16* sRepairsDoneString; extern STR16 sRepairsDoneString[];
// PopUp Box Handles // PopUp Box Handles
INT32 ghAssignmentBox = -1; INT32 ghAssignmentBox = -1;
@@ -4948,7 +4948,7 @@ static void CollectRepairableItems(SOLDIERTYPE* pRepairSoldier, SOLDIERTYPE* pSo
// Check for attachments (are there stackable items that can take attachments though?) // Check for attachments (are there stackable items that can take attachments though?)
UINT8 attachmentIndex = 0; UINT8 attachmentIndex = 0;
for (attachmentList::const_iterator iter = (*pObj)[stackIndex]->attachments.begin(); iter != (*pObj)[stackIndex]->attachments.end(); ++iter, ++attachmentIndex) { for (attachmentList::const_iterator iter = (*pObj)[stackIndex]->attachments.begin(); iter != (*pObj)[stackIndex]->attachments.end(); ++iter, ++attachmentIndex) {
if ( iter->exists() && IsItemRepairable(pRepairSoldier, iter->usItem, (*iter)[attachmentIndex]->data.objectStatus, (*iter)[attachmentIndex]->data.sRepairThreshold )) { if (IsItemRepairable(pRepairSoldier, iter->usItem, (*iter)[attachmentIndex]->data.objectStatus, (*iter)[attachmentIndex]->data.sRepairThreshold )) {
// Send the main item, not the attachment // Send the main item, not the attachment
RepairItem item(pObj, pSoldier, (INVENTORY_SLOT) pocketIndex); RepairItem item(pObj, pSoldier, (INVENTORY_SLOT) pocketIndex);
itemsToFix.push(item); itemsToFix.push(item);
@@ -5081,7 +5081,7 @@ OBJECTTYPE* FindRepairableItemInSpecificPocket(SOLDIERTYPE * pSoldier, OBJECTTYP
// have to check for attachments after... // have to check for attachments after...
for (attachmentList::iterator iter = (*pObj)[subObject]->attachments.begin(); iter != (*pObj)[subObject]->attachments.end(); ++iter) { for (attachmentList::iterator iter = (*pObj)[subObject]->attachments.begin(); iter != (*pObj)[subObject]->attachments.end(); ++iter) {
// if it's repairable and NEEDS repairing // if it's repairable and NEEDS repairing
if ( iter->exists() && IsItemRepairable( pSoldier, iter->usItem, (*iter)[subObject]->data.objectStatus, (*iter)[subObject]->data.sRepairThreshold ) ) { if ( IsItemRepairable( pSoldier, iter->usItem, (*iter)[subObject]->data.objectStatus, (*iter)[subObject]->data.sRepairThreshold ) && iter->exists() ) {
return( &(*iter) ); return( &(*iter) );
} }
} }
@@ -5171,12 +5171,8 @@ BOOLEAN RepairObject( SOLDIERTYPE * pSoldier, SOLDIERTYPE * pOwner, OBJECTTYPE *
INT16 threshold = 100; INT16 threshold = 100;
if ( gGameExternalOptions.fAdvRepairSystem && (Item[pObj->usItem].usItemClass & (IC_WEAPON|IC_ARMOUR)) ) if ( gGameExternalOptions.fAdvRepairSystem && (Item[pObj->usItem].usItemClass & (IC_WEAPON|IC_ARMOUR)) )
{ {
if (gSkillTraitValues.fTETraitsCanRestoreItemThreshold && NUM_SKILL_TRAITS(pSoldier, TECHNICIAN_NT) >= gSkillTraitValues.ubTechLevelNeededForAdvancedRepair) // Greysa: added skill check for advanced repair. If we have a high enough technician level, we can repair items above the normal threshold (1 for technician, 2 for engineer) if ( !gSkillTraitValues.fTETraitsCanRestoreItemThreshold || !HAS_SKILL_TRAIT( pSoldier, TECHNICIAN_NT ) )
{ {
threshold = 100;
}
else
{
threshold = (*pObj)[ubLoop]->data.sRepairThreshold; threshold = (*pObj)[ubLoop]->data.sRepairThreshold;
} }
} }
@@ -11089,8 +11085,7 @@ void DetermineWhichAssignmentMenusCanBeShown( void )
CreateDestroyMouseRegionForFacilityMenu(); CreateDestroyMouseRegionForFacilityMenu();
CreateDestroyMouseRegionsForFacilityAssignmentMenu(); CreateDestroyMouseRegionsForFacilityAssignmentMenu();
const auto selectedCharacter = gCharactersList[bSelectedInfoChar].usSolID; if( ( ( gCharactersList[ bSelectedInfoChar ].usSolID->stats.bLife == 0 )||( gCharactersList[bSelectedInfoChar].usSolID->bAssignment == ASSIGNMENT_POW ) ) && ( (guiTacticalInterfaceFlags & INTERFACE_MAPSCREEN ) ) )
if( selectedCharacter < NOBODY && ( (selectedCharacter->stats.bLife == 0 )||(selectedCharacter->bAssignment == ASSIGNMENT_POW ) ) && ( (guiTacticalInterfaceFlags & INTERFACE_MAPSCREEN ) ) )
{ {
// show basic assignment menu // show basic assignment menu
ShowBox( ghRemoveMercAssignBox ); ShowBox( ghRemoveMercAssignBox );
+5 -13
View File
@@ -2481,12 +2481,6 @@ DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"Autoresolve2");
DeleteVideoObjectFromIndex( gpAR->iIndent ); DeleteVideoObjectFromIndex( gpAR->iIndent );
DeleteVideoSurfaceFromIndex( gpAR->iInterfaceBuffer ); DeleteVideoSurfaceFromIndex( gpAR->iInterfaceBuffer );
// Load necessary mapinfo to place corpses
CHAR8 bFilename[50];
INT32 worldRows, worldCols;
GetMapFileName(gpAR->ubSectorX, gpAR->ubSectorY, 0, bFilename, TRUE, TRUE);
LoadWorldInfoForAutoResolve(bFilename, worldRows, worldCols);
if( fDeleteForGood ) if( fDeleteForGood )
{ //Delete the soldier instances -- done when we are completely finished. { //Delete the soldier instances -- done when we are completely finished.
@@ -2525,7 +2519,7 @@ DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"Autoresolve2");
RemoveCharacterFromSquads( gpMercs[ i ].pSoldier ); RemoveCharacterFromSquads( gpMercs[ i ].pSoldier );
ChangeSoldiersAssignment( gpMercs[ i ].pSoldier, ASSIGNMENT_DEAD ); ChangeSoldiersAssignment( gpMercs[ i ].pSoldier, ASSIGNMENT_DEAD );
AddDeadSoldierToUnLoadedSector( gpAR->ubSectorX, gpAR->ubSectorY, 0, gpMercs[ i ].pSoldier, RandomGridNoUnloadedSector(worldRows, worldCols), ADD_DEAD_SOLDIER_TO_SWEETSPOT ); AddDeadSoldierToUnLoadedSector( gpAR->ubSectorX, gpAR->ubSectorY, 0, gpMercs[ i ].pSoldier, RandomGridNo(), ADD_DEAD_SOLDIER_TO_SWEETSPOT );
} }
else if( gpAR->ubBattleStatus == BATTLE_SURRENDERED || gpAR->ubBattleStatus == BATTLE_CAPTURED ) else if( gpAR->ubBattleStatus == BATTLE_SURRENDERED || gpAR->ubBattleStatus == BATTLE_CAPTURED )
{ {
@@ -2630,8 +2624,7 @@ DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"Autoresolve2");
} }
// Flugente: drop sector equipment // Flugente: drop sector equipment
const auto gridno = RandomGridNoUnloadedSector(worldRows, worldCols); gpCivs[ i ].pSoldier->DropSectorEquipment();
gpCivs[ i ].pSoldier->DropSectorEquipment(gridno);
if( fDeleteForGood && gpCivs[ i ].pSoldier->stats.bLife < OKLIFE/2 ) if( fDeleteForGood && gpCivs[ i ].pSoldier->stats.bLife < OKLIFE/2 )
{ {
@@ -2656,7 +2649,7 @@ DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"Autoresolve2");
UpdateMilitia( militia ); UpdateMilitia( militia );
} }
AddDeadSoldierToUnLoadedSector( gpAR->ubSectorX, gpAR->ubSectorY, 0, gpCivs[ i ].pSoldier, gridno, ADD_DEAD_SOLDIER_TO_SWEETSPOT ); AddDeadSoldierToUnLoadedSector( gpAR->ubSectorX, gpAR->ubSectorY, 0, gpCivs[ i ].pSoldier, RandomGridNo(), ADD_DEAD_SOLDIER_TO_SWEETSPOT );
StrategicRemoveMilitiaFromSector( gpCivs[ i ].pSoldier->sSectorX, gpCivs[ i ].pSoldier->sSectorY, ubCurrentRank, 1 ); StrategicRemoveMilitiaFromSector( gpCivs[ i ].pSoldier->sSectorX, gpCivs[ i ].pSoldier->sSectorY, ubCurrentRank, 1 );
if( ProcessLoyalty() ) if( ProcessLoyalty() )
@@ -2688,8 +2681,7 @@ DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"Autoresolve2");
TrackEnemiesKilled( ENEMY_KILLED_IN_AUTO_RESOLVE, gpEnemies[ i ].pSoldier->ubSoldierClass ); //add casualty to some statistic TrackEnemiesKilled( ENEMY_KILLED_IN_AUTO_RESOLVE, gpEnemies[ i ].pSoldier->ubSoldierClass ); //add casualty to some statistic
if( ProcessLoyalty() )HandleGlobalLoyaltyEvent( GLOBAL_LOYALTY_ENEMY_KILLED, gpAR->ubSectorX, gpAR->ubSectorY, 0 ); if( ProcessLoyalty() )HandleGlobalLoyaltyEvent( GLOBAL_LOYALTY_ENEMY_KILLED, gpAR->ubSectorX, gpAR->ubSectorY, 0 );
ProcessQueenCmdImplicationsOfDeath( gpEnemies[ i ].pSoldier ); ProcessQueenCmdImplicationsOfDeath( gpEnemies[ i ].pSoldier );
const auto gridno = RandomGridNoUnloadedSector(worldRows, worldCols); AddDeadSoldierToUnLoadedSector( gpAR->ubSectorX, gpAR->ubSectorY, 0, gpEnemies[ i ].pSoldier, RandomGridNo(), ADD_DEAD_SOLDIER_TO_SWEETSPOT );
AddDeadSoldierToUnLoadedSector( gpAR->ubSectorX, gpAR->ubSectorY, 0, gpEnemies[ i ].pSoldier, gridno, ADD_DEAD_SOLDIER_TO_SWEETSPOT);
} }
} }
} }
@@ -6015,7 +6007,7 @@ void AutoResolveMilitiaDropAndPromote()
} }
// Flugente: drop sector equipment // Flugente: drop sector equipment
gpCivs[i].pSoldier->DropSectorEquipment( NOWHERE ); gpCivs[i].pSoldier->DropSectorEquipment( );
if ( gpCivs[i].pSoldier->stats.bLife < OKLIFE / 2 ) if ( gpCivs[i].pSoldier->stats.bLife < OKLIFE / 2 )
{ {
+4
View File
@@ -249,6 +249,10 @@ void AdvanceClock( UINT8 ubWarpCode )
guiDay = ( guiGameClock / NUM_SEC_IN_DAY ); guiDay = ( guiGameClock / NUM_SEC_IN_DAY );
guiHour = ( guiGameClock - ( guiDay * NUM_SEC_IN_DAY ) ) / NUM_SEC_IN_HOUR; guiHour = ( guiGameClock - ( guiDay * NUM_SEC_IN_DAY ) ) / NUM_SEC_IN_HOUR;
guiMin = ( guiGameClock - ( ( guiDay * NUM_SEC_IN_DAY ) + ( guiHour * NUM_SEC_IN_HOUR ) ) ) / NUM_SEC_IN_MIN; guiMin = ( guiGameClock - ( ( guiDay * NUM_SEC_IN_DAY ) + ( guiHour * NUM_SEC_IN_HOUR ) ) ) / NUM_SEC_IN_MIN;
uiHourLua = guiHour;
uiDayLua = guiDay;
uiMinLua = guiMin;
swprintf( WORLDTIMESTR, L"%s %d, %02d:%02d", gpGameClockString[ STR_GAMECLOCK_DAY_NAME ], guiDay, guiHour, guiMin ); swprintf( WORLDTIMESTR, L"%s %d, %02d:%02d", gpGameClockString[ STR_GAMECLOCK_DAY_NAME ], guiDay, guiHour, guiMin );
+1 -4
View File
@@ -158,6 +158,7 @@ CHAR16 zString[128];
HourlyDrugUpdate(); HourlyDrugUpdate();
RebelCommand::HourlyUpdate();
// WANNE: This check should avoid the resaving of a loaded auto-save game, when entering tactical // WANNE: This check should avoid the resaving of a loaded auto-save game, when entering tactical
BOOLEAN doAutoSave = TRUE; BOOLEAN doAutoSave = TRUE;
@@ -168,10 +169,6 @@ CHAR16 zString[128];
if (doAutoSave) if (doAutoSave)
{ {
// We're also using the doAutoSave check to prevent RebelCommand::DailyUpdate() from running a second time when loading an autosave at 00:00
RebelCommand::HourlyUpdate();
if ( AutoSaveToSlot[1] == FALSE && AutoSaveToSlot[2] == FALSE && AutoSaveToSlot[3] == FALSE && AutoSaveToSlot[4] == FALSE ) if ( AutoSaveToSlot[1] == FALSE && AutoSaveToSlot[2] == FALSE && AutoSaveToSlot[3] == FALSE && AutoSaveToSlot[4] == FALSE )
AutoSaveToSlot[0] = TRUE; AutoSaveToSlot[0] = TRUE;
+3 -23
View File
@@ -859,7 +859,6 @@ static int l_PlayerTeamFull(lua_State* L);
static int l_ProfilesStrategicInsertionData(lua_State* L); static int l_ProfilesStrategicInsertionData(lua_State* L);
static int l_HealBoxers(lua_State* L);
static int l_ResetBoxers(lua_State* L); static int l_ResetBoxers(lua_State* L);
static int l_AddVolunteers(lua_State* L); static int l_AddVolunteers(lua_State* L);
@@ -913,6 +912,9 @@ static int l_SetIntelAndQuestMapDataForSector(lua_State* L);
using namespace std; using namespace std;
UINT16 idProfil; UINT16 idProfil;
UINT32 uiHourLua;
UINT32 uiDayLua;
UINT32 uiMinLua;
UINT16 PROFILLUA_sSectorX; UINT16 PROFILLUA_sSectorX;
UINT16 PROFILLUA_sSectorY; UINT16 PROFILLUA_sSectorY;
@@ -1187,7 +1189,6 @@ static void IniFunction(lua_State* L, BOOLEAN bQuests)
lua_register(L, "TacticalCharacterDialogueWithSpecialEvent", l_TacticalCharacterDialogueWithSpecialEvent); lua_register(L, "TacticalCharacterDialogueWithSpecialEvent", l_TacticalCharacterDialogueWithSpecialEvent);
lua_register(L, "SetSalary", l_MercSalary); lua_register(L, "SetSalary", l_MercSalary);
lua_register(L, "SetProfileStrategicInsertionData", l_ProfilesStrategicInsertionData); lua_register(L, "SetProfileStrategicInsertionData", l_ProfilesStrategicInsertionData);
lua_register(L, "HealBoxers", l_HealBoxers);
//Get merc //Get merc
lua_register(L, "GetDirection", l_GetDirection); //new lua_register(L, "GetDirection", l_GetDirection); //new
@@ -9314,27 +9315,6 @@ static int l_ResetBoxers(lua_State* L)
return 0; return 0;
} }
// heal boxers back to max health in case the player stays in sector while the boxers are resting
static int l_HealBoxers(lua_State* L)
{
for (UINT8 i = 0; i < NUM_BOXERS; ++i)
{
// Get breath back
gubBoxerID[i]->bBreath = gubBoxerID[i]->bBreathMax;
gubBoxerID[i]->sBreathRed = 0;
// Get life back
gubBoxerID[i]->stats.bLife = gubBoxerID[i]->stats.bLifeMax;
gubBoxerID[i]->bBleeding = 0;
// erase insta-healable injury
gubBoxerID[i]->iHealableInjury = 0;
DebugQuestInfo(String("Lua: healed gubBoxerID[%d] %d back to full health", i, gubBoxerID[i]));
}
return 0;
}
//Set character to sector Y //Set character to sector Y
static int l_SetCharacterSectorY(lua_State* L) static int l_SetCharacterSectorY(lua_State* L)
{ {
+3
View File
@@ -26,6 +26,9 @@ typedef struct
extern ACTION_ITEM_VALUES ActionItemsValues[500]; extern ACTION_ITEM_VALUES ActionItemsValues[500];
extern UINT32 uiHourLua;
extern UINT32 uiDayLua;
extern UINT32 uiMinLua;
extern UINT16 PROFILLUA_sSectorX; extern UINT16 PROFILLUA_sSectorX;
extern UINT16 PROFILLUA_sSectorY; extern UINT16 PROFILLUA_sSectorY;
extern UINT8 PROFILLUA_bSectorZ; extern UINT8 PROFILLUA_bSectorZ;
+9 -10
View File
@@ -510,6 +510,15 @@ void IniGlobalGameSetting(lua_State *L)
lua_pushinteger(L, gubBoxingMatchesWon); lua_pushinteger(L, gubBoxingMatchesWon);
lua_setglobal(L, "gubBoxingMatchesWon"); lua_setglobal(L, "gubBoxingMatchesWon");
lua_pushinteger(L, uiHourLua);
lua_setglobal(L, "cHour");
lua_pushinteger(L, uiDayLua);
lua_setglobal(L, "cDay");
lua_pushinteger(L, uiMinLua);
lua_setglobal(L, "cMin");
lua_pushinteger(L, gbPlayerNum); lua_pushinteger(L, gbPlayerNum);
lua_setglobal(L, "gbPlayerNum"); lua_setglobal(L, "gbPlayerNum");
@@ -581,16 +590,6 @@ void IniGlobalGameSetting(lua_State *L)
lua_pushinteger(L, guiMin); lua_pushinteger(L, guiMin);
lua_setglobal(L, "guiMin"); lua_setglobal(L, "guiMin");
lua_pushinteger(L, guiDay);
lua_setglobal(L, "cDay");
lua_pushinteger(L, guiHour);
lua_setglobal(L, "cHour");
lua_pushinteger(L, guiMin);
lua_setglobal(L, "cMin");
lua_pushinteger(L, guiCurrentScreen); lua_pushinteger(L, guiCurrentScreen);
lua_setglobal(L, "guiCurrentScreen"); lua_setglobal(L, "guiCurrentScreen");
@@ -1133,12 +1133,8 @@ void SaveSeenAndUnseenItems( void )
if(pipl->fExists && pipl->object.ubNumberOfObjects) if(pipl->fExists && pipl->object.ubNumberOfObjects)
{ {
uiTotalNumberOfVisibleItems += pipl->object.ubNumberOfObjects; uiTotalNumberOfVisibleItems += pipl->object.ubNumberOfObjects;
if (i > uiNumberOfSeenItems) if(i > uiNumberOfSeenItems)
{
pInventoryPoolList[uiNumberOfSeenItems] = *pipl; pInventoryPoolList[uiNumberOfSeenItems] = *pipl;
pipl->fExists = FALSE;
}
uiNumberOfSeenItems++; uiNumberOfSeenItems++;
} }
} }
@@ -6028,7 +6024,10 @@ void HandleItemCooldownFunctions( OBJECTTYPE* itemStack, INT32 deltaSeconds, BOO
FLOAT newguntemperature = max(0.0f, guntemperature - tickspassed * cooldownfactor ); // ... calculate new temperature ... FLOAT newguntemperature = max(0.0f, guntemperature - tickspassed * cooldownfactor ); // ... calculate new temperature ...
#if JA2TESTVERSION #if JA2TESTVERSION
ScreenMsg(FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, L"World: Item temperature lowered from %4.2f to %4.2f", guntemperature, newguntemperature); if (guntemperature != newguntemperature)
{
ScreenMsg(FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, L"World: Item temperature lowered from %4.2f to %4.2f", guntemperature, newguntemperature);
}
#endif #endif
(*itemStack)[i]->data.bTemperature = newguntemperature; // ... set new temperature (*itemStack)[i]->data.bTemperature = newguntemperature; // ... set new temperature
@@ -6048,7 +6047,10 @@ void HandleItemCooldownFunctions( OBJECTTYPE* itemStack, INT32 deltaSeconds, BOO
(*iter)[i]->data.bTemperature = newtemperature; // ... set new temperature (*iter)[i]->data.bTemperature = newtemperature; // ... set new temperature
#if JA2TESTVERSION #if JA2TESTVERSION
ScreenMsg(FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, L"World: Item temperature lowered from %4.2f to %4.2f", temperature, newtemperature); if (temperature != newtemperature)
{
ScreenMsg(FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, L"World: Item temperature lowered from %4.2f to %4.2f", temperature, newtemperature);
}
#endif #endif
// we assume that there can exist only 1 underbarrel weapon per gun // we assume that there can exist only 1 underbarrel weapon per gun
+1 -1
View File
@@ -194,7 +194,7 @@ INT32 iHeightOfInitFastHelpText = 0;
extern INT32 giMapContractButton; extern INT32 giMapContractButton;
extern INT32 giCharInfoButton[]; extern INT32 giCharInfoButton[];
extern STR16* pUpdatePanelButtons; extern STR16 pUpdatePanelButtons[];
// the list of soldiers that are moving // the list of soldiers that are moving
SOLDIERTYPE * pSoldierMovingList[ CODE_MAXIMUM_NUMBER_OF_PLAYER_SLOTS ]; SOLDIERTYPE * pSoldierMovingList[ CODE_MAXIMUM_NUMBER_OF_PLAYER_SLOTS ];
-9
View File
@@ -904,16 +904,7 @@ BOOLEAN BeginStrategicRemoveMerc( SOLDIERTYPE *pSoldier, BOOLEAN fAddRehireButto
else else
{ {
if (!is_networked) if (!is_networked)
{
#ifdef JA2UB
// Leave equipment to merc's current sector by default in UB
HandleLeavingOfEquipmentInCurrentSector(pSoldier->ubID);
StrategicRemoveMerc(pSoldier);
pLeaveSoldier = NULL;
#else
NotifyPlayerOfMercDepartureAndPromptEquipmentPlacement( pSoldier, fAddRehireButton ); NotifyPlayerOfMercDepartureAndPromptEquipmentPlacement( pSoldier, fAddRehireButton );
#endif
}
else else
{ {
// WANNE - MP: Skip all the dialog boxes that appear. Just dismiss the merc // WANNE - MP: Skip all the dialog boxes that appear. Just dismiss the merc
+1 -1
View File
@@ -49,7 +49,7 @@ extern "C" {
extern CHAR16 gzUserDefinedButton1[ 128 ]; extern CHAR16 gzUserDefinedButton1[ 128 ];
extern CHAR16 gzUserDefinedButton2[ 128 ]; extern CHAR16 gzUserDefinedButton2[ 128 ];
extern CHAR16 (*pTownNames)[MAX_TOWN_NAME_LENGHT]; extern CHAR16 pTownNames[MAX_TOWNS][MAX_TOWN_NAME_LENGHT];
static size_t MAX_BUTTON_LENGTH = 60; static size_t MAX_BUTTON_LENGTH = 60;
static size_t MAX_BODY_LENGTH = 450; static size_t MAX_BODY_LENGTH = 450;
+2 -2
View File
@@ -2,8 +2,8 @@
#define _QUEST_TEXT_H_ #define _QUEST_TEXT_H_
extern STR16 *QuestDescText; STR16 QuestDescText[];
extern STR16 *FactDescText; STR16 FactDescText[];
#endif #endif
+1 -1
View File
@@ -31,7 +31,7 @@ class SOLDIERTYPE;
#ifdef JA2EDITOR #ifdef JA2EDITOR
extern CHAR16 (*gszScheduleActions)[20]; // NUM_SCHEDULE_ACTIONS entries extern CHAR16 gszScheduleActions[ NUM_SCHEDULE_ACTIONS ][20];
#endif #endif
BOOLEAN GetEarliestMorningScheduleEvent( SCHEDULENODE *pSchedule, UINT32 * puiTime ); BOOLEAN GetEarliestMorningScheduleEvent( SCHEDULENODE *pSchedule, UINT32 * puiTime );
+3 -3
View File
@@ -4881,14 +4881,14 @@ void ExecuteStrategicAIAction( UINT16 usActionCode, INT16 sSectorX, INT16 sSecto
} }
// depending on which cities the player currently holds, we send out attack on multiple cities. We try to make these attacks occur simultaneously, so the player will have to fend off // depending on which cities the player currently holds, we send out attack on multiple cities. We try to make these attacks occur simultaneously, so the player will have to fend off
// multiple gigantic attacks on different cities. Ideally, the attacks will be timed so well that the player cannot use a squad in both battles, even with use of the helicopter // multiple gigantic attacks on different cities. Ideally, the attacks will be timed so well that the player cannot use a sqaud in both battles, even with use of the helicopter
// we first have to check which cities we have to attack. For that, we simply check wether there are troops in the target sector. If not, we will attack here // we first have to check which cities we have to attack. For that, we simply check wether there are troops in the target sector. If not, we will attack here
BOOLEAN fAttack_Grumm = !(SectorInfo[ SEC_H3 ].ubNumTroops > 0); BOOLEAN fAttack_Grumm = !(SectorInfo[ SEC_H3 ].ubNumTroops > 0);
BOOLEAN fAttack_Cambria = !(SectorInfo[ SEC_H8 ].ubNumTroops > 0); BOOLEAN fAttack_Cambria = !(SectorInfo[ SEC_H8 ].ubNumTroops > 0);
BOOLEAN fCambriaSAMOccupied = !(SectorInfo[ SEC_I8 ].ubNumTroops > 0); BOOLEAN fCambriaSAMOccupied = !(SectorInfo[ SEC_I8 ].ubNumTroops > 0);
BOOLEAN fAttack_Alma = !(SectorInfo[ SEC_I13 ].ubNumTroops > 0); BOOLEAN fAttack_Alma = !(SectorInfo[ SEC_I13 ].ubNumTroops > 0);
BOOLEAN fAttack_Balime = !(SectorInfo[ SEC_L11 ].ubNumTroops > 0); BOOLEAN fAttack_Balime = !(SectorInfo[ SEC_L11 ].ubNumTroops > 0);
BOOLEAN fAttack_Chitzena = !(SectorInfo[ SEC_B2 ].ubNumTroops > 0); BOOLEAN fAttack_Chitzena = !(SectorInfo[ SEC_L11 ].ubNumTroops > 0);
ubNumSoldiers = (UINT8)( gubMinEnemyGroupSize + difficultyMod * 3); ubNumSoldiers = (UINT8)( gubMinEnemyGroupSize + difficultyMod * 3);
+1 -1
View File
@@ -180,7 +180,7 @@ INT16 sWorldSectorLocationOfFirstBattle = 0;
// preprocess sector for mercs in it // preprocess sector for mercs in it
extern BOOLEAN fSectorsWithSoldiers[ MAP_WORLD_X * MAP_WORLD_X ][ 4 ]; extern BOOLEAN fSectorsWithSoldiers[ MAP_WORLD_X * MAP_WORLD_X ][ 4 ];
extern CHAR16 (*pTownNames)[MAX_TOWN_NAME_LENGHT]; extern CHAR16 pTownNames[MAX_TOWNS][MAX_TOWN_NAME_LENGHT];
+2 -16
View File
@@ -613,8 +613,8 @@ extern BOOLEAN gfAutoAIAware;
extern void HandlePreBattleInterfaceStates(); extern void HandlePreBattleInterfaceStates();
// the title for the contract button on the character info panel in the upper left portion of the mapscreen // the title for the contract button on the character info panel in the upper left portion of the mapscreen
extern STR16* pContractButtonString; extern STR16 pContractButtonString[];
extern STR16* pBullseyeStrings; extern STR16 pBullseyeStrings[];
extern OBJECTTYPE *gpItemDescObject; extern OBJECTTYPE *gpItemDescObject;
@@ -8472,12 +8472,6 @@ void GetMapKeyboardInput( UINT32 *puiNewEvent )
if ( HandleNailsVestFetish( pSoldier, i, NOTHING ) ) if ( HandleNailsVestFetish( pSoldier, i, NOTHING ) )
continue; continue;
// tais: never strip Madlab's robot of its installed components (ammo,
// targeting, chassis/armour, utility) - the player can't re-install them
// and the robot would be left defenceless. Only its cargo slot may be emptied.
if ( AM_A_ROBOT( pSoldier ) && i != ROBOT_INVENTORY_SLOT )
continue;
AutoPlaceObjectInInventoryStash(&pSoldier->inv[i], pSoldier->sGridNo, pSoldier->pathing.bLevel); AutoPlaceObjectInInventoryStash(&pSoldier->inv[i], pSoldier->sGridNo, pSoldier->pathing.bLevel);
DeleteObj(&pSoldier->inv[i]); DeleteObj(&pSoldier->inv[i]);
} }
@@ -12748,10 +12742,6 @@ void HandleShadingOfLinesForContractMenu( void )
SOLDIERTYPE* pSoldier = gCharactersList[ bSelectedContractChar ].usSolID; SOLDIERTYPE* pSoldier = gCharactersList[ bSelectedContractChar ].usSolID;
const bool multipleMercsSelected = (gSelectedSoldiers.size() > 0) ? true : false; const bool multipleMercsSelected = (gSelectedSoldiers.size() > 0) ? true : false;
#ifdef JA2UB
// No contract renewal in UB
const bool atLeastOneAIMmerc = false;
#else
bool atLeastOneAIMmerc = false; bool atLeastOneAIMmerc = false;
if (pSoldier->ubWhatKindOfMercAmI == MERC_TYPE__AIM_MERC) if (pSoldier->ubWhatKindOfMercAmI == MERC_TYPE__AIM_MERC)
{ {
@@ -12769,7 +12759,6 @@ void HandleShadingOfLinesForContractMenu( void )
} }
} }
} }
#endif // JA2UB
// is guy in AIM? and well enough to talk and make such decisions? // is guy in AIM? and well enough to talk and make such decisions?
if( (atLeastOneAIMmerc) && ( pSoldier->stats.bLife >= OKLIFE ) ) if( (atLeastOneAIMmerc) && ( pSoldier->stats.bLife >= OKLIFE ) )
@@ -15377,10 +15366,7 @@ BOOLEAN CanExtendContractForCharSlot( INT16 bCharNumber )
// if a vehicle, in transit, or a POW // if a vehicle, in transit, or a POW
if( /*( pSoldier->flags.uiStatusFlags & SOLDIER_VEHICLE ) ||*/ if( /*( pSoldier->flags.uiStatusFlags & SOLDIER_VEHICLE ) ||*/
#ifndef JA2UB
// Allow contract box in UB before helicopter ride, so mercs can be dismissed if player has hired more than 6 mercs
( pSoldier->bAssignment == IN_TRANSIT ) || ( pSoldier->bAssignment == IN_TRANSIT ) ||
#endif
( pSoldier->bAssignment == ASSIGNMENT_POW ) ) ( pSoldier->bAssignment == ASSIGNMENT_POW ) )
{ {
// can't extend contracts at this time // can't extend contracts at this time
+1 -1
View File
@@ -357,7 +357,7 @@ extern HVSURFACE ghFrameBuffer;
extern BOOLEAN gfOverrideSector; extern BOOLEAN gfOverrideSector;
extern BOOLEAN sBadSectorsList[WORLD_MAP_X][WORLD_MAP_X]; extern BOOLEAN sBadSectorsList[WORLD_MAP_X][WORLD_MAP_X];
extern STR16* pBullseyeStrings; extern STR16 pBullseyeStrings[];
extern void HandleRPCDescription( ); extern void HandleRPCDescription( );
+1
View File
@@ -2,3 +2,4 @@
# priority (LOW,HIGH) and description # priority (LOW,HIGH) and description
LOW readd the C4838 (https://learn.microsoft.com/en-us/cpp/error-messages/compiler-warnings/compiler-warning-level-1-c4838) warning to CMakeLists.txt once they're all fixed in the code LOW readd the C4838 (https://learn.microsoft.com/en-us/cpp/error-messages/compiler-warnings/compiler-warning-level-1-c4838) warning to CMakeLists.txt once they're all fixed in the code
HIGH get rid of ENGLISH, GERMAN, etc preprocessor definitions completely. that way i18n can be built just once and language can be changed in the options screen pending game restart (hotloading will likely require more work)
+3 -3
View File
@@ -1423,7 +1423,7 @@ void AddObjectToArmsDealerInventory( UINT8 ubArmsDealer, OBJECTTYPE *pObject )
for (attachmentList::iterator iter = seperateObject[0]->attachments.begin(); iter != seperateObject[0]->attachments.end();) { for (attachmentList::iterator iter = seperateObject[0]->attachments.begin(); iter != seperateObject[0]->attachments.end();) {
// ARM: Note: this is only used for selling, not repairs, so attachmentes are seperated when sold to a dealer // ARM: Note: this is only used for selling, not repairs, so attachmentes are seperated when sold to a dealer
// If the attachment is detachable // If the attachment is detachable
if (! (iter->exists() && Item[ iter->usItem ].inseparable)) if (! (Item[ iter->usItem ].inseparable ) && iter->exists())
{ {
// add this particular attachment (they can't be imprinted, or themselves have attachments!) // add this particular attachment (they can't be imprinted, or themselves have attachments!)
AddObjectToArmsDealerInventory( ubArmsDealer, &(*iter) ); AddObjectToArmsDealerInventory( ubArmsDealer, &(*iter) );
@@ -1931,7 +1931,7 @@ UINT32 CalculateObjectItemRepairTime( UINT8 ubArmsDealer, OBJECTTYPE *pItemObjec
// add time to repair any attachments on it // add time to repair any attachments on it
for (attachmentList::iterator iter = (*pItemObject)[x]->attachments.begin(); iter != (*pItemObject)[x]->attachments.end(); ++iter) { for (attachmentList::iterator iter = (*pItemObject)[x]->attachments.begin(); iter != (*pItemObject)[x]->attachments.end(); ++iter) {
// if damaged and repairable // if damaged and repairable
if ( iter->exists() && ( (*iter)[x]->data.objectStatus < 100 ) && CanDealerRepairItem( ubArmsDealer, iter->usItem ) ) if ( ( (*iter)[x]->data.objectStatus < 100 ) && CanDealerRepairItem( ubArmsDealer, iter->usItem ) && iter->exists())
{ {
//uiRepairTime += CalculateSimpleItemRepairTime( ubArmsDealer, iter->usItem, (*iter)[x]->data.objectStatus ); //uiRepairTime += CalculateSimpleItemRepairTime( ubArmsDealer, iter->usItem, (*iter)[x]->data.objectStatus );
uiRepairTime += CalculateObjectItemRepairTime( ubArmsDealer, &(*iter) ); uiRepairTime += CalculateObjectItemRepairTime( ubArmsDealer, &(*iter) );
@@ -1988,7 +1988,7 @@ UINT32 CalculateObjectItemRepairCost( UINT8 ubArmsDealer, OBJECTTYPE *pItemObjec
// add cost of repairing any attachments on it // add cost of repairing any attachments on it
for (attachmentList::iterator iter = (*pItemObject)[x]->attachments.begin(); iter != (*pItemObject)[x]->attachments.end(); ++iter) { for (attachmentList::iterator iter = (*pItemObject)[x]->attachments.begin(); iter != (*pItemObject)[x]->attachments.end(); ++iter) {
// if damaged and repairable // if damaged and repairable
if ( iter->exists() && ( (*iter)[x]->data.objectStatus < 100 ) && CanDealerRepairItem( ubArmsDealer, iter->usItem ) ) if ( ( (*iter)[x]->data.objectStatus < 100 ) && CanDealerRepairItem( ubArmsDealer, iter->usItem ) && iter->exists())
{ {
uiRepairCost += CalculateObjectItemRepairCost( ubArmsDealer, &(*iter)); uiRepairCost += CalculateObjectItemRepairCost( ubArmsDealer, &(*iter));
} }
+8 -10
View File
@@ -21,6 +21,7 @@
#include "GameSettings.h" // added by SANDRO #include "GameSettings.h" // added by SANDRO
#include <Font Control.h> #include <Font Control.h>
#include <message.h> #include <message.h>
#include "Soldier macros.h"
INT32 gsBoxerGridNo[ NUM_BOXERS ] = { 11393, 11233, 11073 }; INT32 gsBoxerGridNo[ NUM_BOXERS ] = { 11393, 11233, 11073 };
SoldierID gubBoxerID[ NUM_BOXERS ] = { NOBODY, NOBODY, NOBODY }; SoldierID gubBoxerID[ NUM_BOXERS ] = { NOBODY, NOBODY, NOBODY };
@@ -54,7 +55,7 @@ void ExitBoxing( void )
if ( pSoldier != NULL ) if ( pSoldier != NULL )
{ {
if ( ( pSoldier->flags.uiStatusFlags & SOLDIER_BOXER ) && InARoom( pSoldier->sGridNo, &usRoom ) && usRoom == BOXING_RING ) if ( BOXER(pSoldier) && InARoom( pSoldier->sGridNo, &usRoom ) && usRoom == BOXING_RING )
{ {
if ( pSoldier->flags.uiStatusFlags & SOLDIER_PC ) if ( pSoldier->flags.uiStatusFlags & SOLDIER_PC )
{ {
@@ -238,7 +239,7 @@ static void CountPeopleInBoxingRingAndDoActions( void )
{ {
++ubPlayersInRing; ++ubPlayersInRing;
if ( !pNonBoxingPlayer && !(pSoldier->flags.uiStatusFlags & SOLDIER_BOXER) ) if ( !pNonBoxingPlayer && !BOXER(pSoldier) )
{ {
pNonBoxingPlayer = pSoldier; pNonBoxingPlayer = pSoldier;
} }
@@ -290,7 +291,7 @@ static void CountPeopleInBoxingRingAndDoActions( void )
// ladieees and gennleman, we have a fight! // ladieees and gennleman, we have a fight!
for (uiLoop = 0; uiLoop < 2; ++uiLoop) for (uiLoop = 0; uiLoop < 2; ++uiLoop)
{ {
if (!(pInRing[uiLoop]->flags.uiStatusFlags & SOLDIER_BOXER)) if (!BOXER(pInRing[uiLoop]))
{ {
// set as boxer! // set as boxer!
pInRing[uiLoop]->flags.uiStatusFlags |= SOLDIER_BOXER; pInRing[uiLoop]->flags.uiStatusFlags |= SOLDIER_BOXER;
@@ -453,14 +454,11 @@ BOOLEAN BoxerAvailable( void )
return( FALSE ); return( FALSE );
} }
// NOTE THIS IS NOW BROKEN BECAUSE NPC.C ASSUMES THAT BOXERSAVAILABLE < 3 IS A
// SEQUEL FIGHT. Maybe we could check Kingpin's location instead!
UINT8 BoxersAvailable( void ) UINT8 BoxersAvailable( void )
{ {
UINT8 ubCount = 0; UINT8 ubCount = 0;
// Should also run CheckOnBoxers to make sure boxer IDs are set for this function similarly to BoxerAvailable
if (CheckOnBoxers() == FALSE)
{
return(ubCount);
}
for (UINT8 ubLoop = 0; ubLoop < NUM_BOXERS; ++ubLoop) for (UINT8 ubLoop = 0; ubLoop < NUM_BOXERS; ++ubLoop)
{ {
@@ -514,7 +512,7 @@ void BoxingMovementCheck( SOLDIERTYPE * pSoldier )
// someone moving in/into the ring // someone moving in/into the ring
CountPeopleInBoxingRingAndDoActions(); CountPeopleInBoxingRingAndDoActions();
} }
else if ( ( gTacticalStatus.bBoxingState == BOXING ) && ( pSoldier->flags.uiStatusFlags & SOLDIER_BOXER ) ) else if ( ( gTacticalStatus.bBoxingState == BOXING ) && BOXER(pSoldier) )
{ {
// boxer stepped out of the ring! // boxer stepped out of the ring!
BoxingPlayerDisqualified( pSoldier, BOXER_OUT_OF_RING ); BoxingPlayerDisqualified( pSoldier, BOXER_OUT_OF_RING );
@@ -564,7 +562,7 @@ void ClearAllBoxerFlags( void )
{ {
for (UINT32 uiSlot = 0; uiSlot < guiNumMercSlots; ++uiSlot) for (UINT32 uiSlot = 0; uiSlot < guiNumMercSlots; ++uiSlot)
{ {
if ( MercSlots[ uiSlot ] && MercSlots[ uiSlot ]->flags.uiStatusFlags & SOLDIER_BOXER ) if ( MercSlots[ uiSlot ] && BOXER(MercSlots[ uiSlot ]) )
{ {
// Flugente: nuke the entire opponent count, remove boxing flag, reevaluate opponent list // Flugente: nuke the entire opponent count, remove boxing flag, reevaluate opponent list
DecayIndividualOpplist(MercSlots[uiSlot]); DecayIndividualOpplist(MercSlots[uiSlot]);
+557 -413
View File
File diff suppressed because it is too large Load Diff
+23
View File
@@ -758,6 +758,29 @@ void EatFromInventory( SOLDIERTYPE *pSoldier, BOOLEAN fcanteensonly )
} }
} }
void DrinkFromInventory(SOLDIERTYPE* pSoldier)
{
if ( !pSoldier )
return;
INT8 invsize = pSoldier->inv.size();
for ( INT8 bLoop = 0; bLoop < invsize; ++bLoop )
{
if ( pSoldier->inv[bLoop].exists() && ItemIsCanteen(pSoldier->inv[bLoop].usItem) )
{
OBJECTTYPE* pObj = &(pSoldier->inv[bLoop]);
if ( pObj && TotalPoints(pObj) > 1 )
{
//ScreenMsg(FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, Message[STR_DRINKS], pSoldier->GetName());
ApplyConsumable(pSoldier, pObj, false, true);
return;
}
}
}
}
void HourlyFoodUpdate( void ) void HourlyFoodUpdate( void )
{ {
SoldierID bMercID, bLastTeamID; SoldierID bMercID, bLastTeamID;
+1
View File
@@ -108,5 +108,6 @@ void SoldierAutoFillCanteens(SOLDIERTYPE *pSoldier);
BOOLEAN HasFoodInInventory( SOLDIERTYPE *pSoldier, BOOLEAN fCheckFood, BOOLEAN fCheckDrink ); BOOLEAN HasFoodInInventory( SOLDIERTYPE *pSoldier, BOOLEAN fCheckFood, BOOLEAN fCheckDrink );
void DrinkFromWaterTap( SOLDIERTYPE *pSoldier ); void DrinkFromWaterTap( SOLDIERTYPE *pSoldier );
void DrinkFromInventory(SOLDIERTYPE* pSoldier);
#endif #endif
+1 -1
View File
@@ -7035,7 +7035,7 @@ UINT8 StealItems(SOLDIERTYPE* pSoldier,SOLDIERTYPE* pOpponent, UINT8* ubIndexRet
if ((pObject->exists() == true) && !ItemIsUndroppableByDefault(pObject->usItem)) // CHECK! Undroppable items cannot be stolen - SANDRO if ((pObject->exists() == true) && !ItemIsUndroppableByDefault(pObject->usItem)) // CHECK! Undroppable items cannot be stolen - SANDRO
{ {
// Is the enemy collapsed // Is the enemy collapsed
if ( pOpponent->stats.bLife < OKLIFE || pOpponent->bCollapsed || (pOpponent->usSoldierFlagMask & SOLDIER_POW) ) if ( pOpponent->stats.bLife < OKLIFE || pOpponent->bCollapsed )
{ {
// We can steal any of his items in the inventory // We can steal any of his items in the inventory
fStealItem = TRUE; fStealItem = TRUE;
+51 -2
View File
@@ -73,7 +73,7 @@
#include "TeamTurns.h" #include "TeamTurns.h"
#include "Map Screen Interface.h" // added by Flugente for SquadNames #include "Map Screen Interface.h" // added by Flugente for SquadNames
#include "Keys.h" // added by silversurfer for door handling from the side #include "Keys.h" // added by silversurfer for door handling from the side
#include "Cheats.h"
#include "AIInternals.h" #include "AIInternals.h"
extern BOOLEAN gubWorldTileInLight[MAX_ALLOWED_WORLD_MAX]; extern BOOLEAN gubWorldTileInLight[MAX_ALLOWED_WORLD_MAX];
extern BOOLEAN gubIsCorpseThere[MAX_ALLOWED_WORLD_MAX]; extern BOOLEAN gubIsCorpseThere[MAX_ALLOWED_WORLD_MAX];
@@ -372,7 +372,7 @@ BOOLEAN gfDisplayTimerCursor = FALSE;
UINT32 guiTimerCursorID = 0; UINT32 guiTimerCursorID = 0;
UINT32 guiTimerLastUpdate = 0; UINT32 guiTimerLastUpdate = 0;
UINT32 guiTimerCursorDelay = 0; UINT32 guiTimerCursorDelay = 0;
UINT8 gRenderDebugInfoMode = DEBUG_OFF;
CHAR16 gzLocation[ 20 ]; CHAR16 gzLocation[ 20 ];
BOOLEAN gfLocation = FALSE; BOOLEAN gfLocation = FALSE;
@@ -500,6 +500,54 @@ void GetMercOknoDirection( SoldierID ubSoldierID, BOOLEAN *pfGoDown, BOOLEAN *pf
} }
//---------------------------------------------------------------------------------- //----------------------------------------------------------------------------------
void HandleRenderDebugInfoModes()
{
if (DEBUG_CHEAT_LEVEL())
{
switch (gRenderDebugInfoMode)
{
case DEBUG_PATHFINDING:
// Nothing to do here, pathfinding info is filled in the pathing functions.
break;
case DEBUG_THREATVALUE:
break;
case DEBUG_COVERVALUE:
// Calculate cover values for pSoldier under cursor, or for currently selected merc, if nobody is under the cursor.
if (gTacticalStatus.Team[OUR_TEAM].bTeamActive)
{
static SOLDIERTYPE* previousSoldier = nullptr;
static INT32 previousLocation = NOWHERE;
static UINT8 previousStance = 0;
SoldierID usSoldierIndex = NOBODY;
UINT32 uiMercFlags;
FindSoldierFromMouse(&usSoldierIndex, &uiMercFlags);
if (usSoldierIndex == NOBODY)
{
usSoldierIndex = gusSelectedSoldier;
}
if (usSoldierIndex != NOBODY)
{
// Get Soldier
INT32 iPercentBetter;
SOLDIERTYPE* pSoldier;
GetSoldier(&pSoldier, usSoldierIndex);
if (previousSoldier != pSoldier || previousLocation != pSoldier->sGridNo || previousStance != gAnimControl[pSoldier->usAnimState].ubEndHeight)
{
FindBestNearbyCover(pSoldier, pSoldier->aiData.bAIMorale, &iPercentBetter, NOWHERE, false);
previousSoldier = pSoldier;
previousLocation = pSoldier->sGridNo;
previousStance = gAnimControl[pSoldier->usAnimState].ubEndHeight;
}
}
}
break;
default: // off
break;
}
}
}
void PreventFromTheFreezingBug(SOLDIERTYPE* pSoldier) void PreventFromTheFreezingBug(SOLDIERTYPE* pSoldier)
{ {
@@ -673,6 +721,7 @@ UINT32 HandleTacticalUI( void )
} }
} }
HandleRenderDebugInfoModes();
// Check if current event has changed and clear event if so, to prepare it for execution // Check if current event has changed and clear event if so, to prepare it for execution
// Clearing it does things like set first time flag, param variables, etc // Clearing it does things like set first time flag, param variables, etc
if ( uiNewEvent != guiOldEvent ) if ( uiNewEvent != guiOldEvent )
+1
View File
@@ -184,6 +184,7 @@ extern UINT32 guiCurrentEvent;
extern INT16 gsSelectedLevel; extern INT16 gsSelectedLevel;
extern BOOLEAN gfPlotNewMovement; extern BOOLEAN gfPlotNewMovement;
extern UINT32 guiPendingOverrideEvent; extern UINT32 guiPendingOverrideEvent;
extern UINT8 gRenderDebugInfoMode;
// GLOBALS // GLOBALS
+2 -2
View File
@@ -10073,7 +10073,7 @@ void DrawArmorValues( OBJECTTYPE * gpItemDescObject )
UINT16 iFinalProtectionValue = iProtectionValue; UINT16 iFinalProtectionValue = iProtectionValue;
for (attachmentList::iterator iter = (*gpItemDescObject)[0]->attachments.begin(); iter != (*gpItemDescObject)[0]->attachments.end(); ++iter) for (attachmentList::iterator iter = (*gpItemDescObject)[0]->attachments.begin(); iter != (*gpItemDescObject)[0]->attachments.end(); ++iter)
{ {
if ( iter->exists() && Item[ iter->usItem ].usItemClass == IC_ARMOUR) if (Item[ iter->usItem ].usItemClass == IC_ARMOUR)
{ {
iFinalProtectionValue += Armour[Item[iter->usItem].ubClassIndex].ubProtection; iFinalProtectionValue += Armour[Item[iter->usItem].ubClassIndex].ubProtection;
} }
@@ -10099,7 +10099,7 @@ void DrawArmorValues( OBJECTTYPE * gpItemDescObject )
UINT16 iComparedFinalProtectionValue = iComparedProtectionValue; UINT16 iComparedFinalProtectionValue = iComparedProtectionValue;
for (attachmentList::iterator iter = (*gpComparedItemDescObject)[0]->attachments.begin(); iter != (*gpComparedItemDescObject)[0]->attachments.end(); ++iter) for (attachmentList::iterator iter = (*gpComparedItemDescObject)[0]->attachments.begin(); iter != (*gpComparedItemDescObject)[0]->attachments.end(); ++iter)
{ {
if ( iter->exists() && Item[ iter->usItem ].usItemClass == IC_ARMOUR) if (Item[ iter->usItem ].usItemClass == IC_ARMOUR)
{ {
iComparedFinalProtectionValue += Armour[Item[iter->usItem].ubClassIndex].ubProtection; iComparedFinalProtectionValue += Armour[Item[iter->usItem].ubClassIndex].ubProtection;
} }
+4 -22
View File
@@ -643,29 +643,13 @@ void popupCallbackItem(INT16 itemId){
i++;p++; i++;p++;
} }
static OBJECTTYPE pObjTmp; OBJECTTYPE pObjTmp;
pObjTmp.initialize(); pObjTmp.initialize();
if( bestStack->RemoveObjectAtIndex(leastDamagedIndex, &pObjTmp) ){ if( bestStack->RemoveObjectAtIndex(leastDamagedIndex, &pObjTmp) ){
gpItemPointer = &pObjTmp; // pick up the object (or stack) gpItemPointer = &pObjTmp; // pick up the object (or stack)
DoAttachment((UINT8)gubPopupStatusIndex, guiPopupItemPos); // try to attach it DoAttachment((UINT8)gubPopupStatusIndex, guiPopupItemPos); // try to attach it
//gpItemPointer = NULL; // dont drop it!
// If attaching failed, change mouse cursor to item as we're still holding it
if ( gpItemPointer->exists() ) {
// Set mouse
guiExternVo = GetInterfaceGraphicForItem(&(Item[gpItemPointer->usItem]));
gusExternVoSubIndex = Item[gpItemPointer->usItem].ubGraphicNum;
MSYS_ChangeRegionCursor(&gMPanelRegion, EXTERN_CURSOR);
MSYS_SetCurrentCursor(EXTERN_CURSOR);
fMapInventoryItem = TRUE;
fTeamPanelDirty = TRUE;
//Dirty interface
fInterfacePanelDirty = DIRTYLEVEL2;
UpdateItemHatches();
}
gItemDescAttachmentPopups[giActiveAttachmentPopup]->hide(); gItemDescAttachmentPopups[giActiveAttachmentPopup]->hide();
RenderItemDescriptionBox(); RenderItemDescriptionBox();
@@ -6076,8 +6060,7 @@ void ItemDescAmmoCallback(GUI_BUTTON *btn,INT32 reason)
gfItemAmmoDown = FALSE; gfItemAmmoDown = FALSE;
//CHRISL: We dont' want to be able to reload guns using the ammo crate from this function //CHRISL: We dont' want to be able to reload guns using the ammo crate from this function
//Greysa: add check for ammo item if((gpItemPointer != NULL && Magazine[Item[gpItemPointer->usItem].ubClassIndex].ubMagType >= AMMO_BOX) || !EnoughPoints(gpItemDescSoldier, APBPConstants[AP_RELOAD_GUN], 0, TRUE))//dnl ch65 040913
if( gpItemPointer != NULL && ( (Item[gpItemPointer->usItem].usItemClass != IC_AMMO || Magazine[Item[gpItemPointer->usItem].ubClassIndex].ubMagType >= AMMO_BOX) ) || !EnoughPoints(gpItemDescSoldier, APBPConstants[AP_RELOAD_GUN], 0, TRUE) )//dnl ch65 040913
{ {
fInterfacePanelDirty = DIRTYLEVEL2; fInterfacePanelDirty = DIRTYLEVEL2;
btn->uiFlags &= (~BUTTON_CLICKED_ON ); btn->uiFlags &= (~BUTTON_CLICKED_ON );
@@ -6100,8 +6083,7 @@ void ItemDescAmmoCallback(GUI_BUTTON *btn,INT32 reason)
else else
{ {
//holding an item //holding an item
//Greysa: add check for ammo item if(Magazine[Item[gpItemPointer->usItem].ubClassIndex].ubCalibre == Weapon[Item[gpItemDescObject->usItem].ubClassIndex].ubCalibre)
if( Item[gpItemPointer->usItem].usItemClass == IC_AMMO && Magazine[Item[gpItemPointer->usItem].ubClassIndex].ubCalibre == Weapon[Item[gpItemDescObject->usItem].ubClassIndex].ubCalibre )
{ {
ReloadGun(gpItemDescSoldier, gpItemDescObject, gpItemPointer, ubStatusIndex); ReloadGun(gpItemDescSoldier, gpItemDescObject, gpItemPointer, ubStatusIndex);
} }
+1 -1
View File
@@ -6265,7 +6265,7 @@ BOOLEAN ShowSoldierRoleSymbol(SOLDIERTYPE* pSoldier)
if ( pSoldier->usSkillCounter[SOLDIER_COUNTER_ROLE_OBSERVED] >= gGameExternalOptions.usTurnsToUncover ) if ( pSoldier->usSkillCounter[SOLDIER_COUNTER_ROLE_OBSERVED] >= gGameExternalOptions.usTurnsToUncover )
{ {
// are we a VIP? show that only when the player knows a VIP is in this sector. otherwise, don't even show our officer property // are we a VIP? show that only when the player knows a VIP is in this sector. otherwise, don't even show our officer property
if ( pSoldier->usSoldierFlagMask & SOLDIER_VIP && !pSoldier->bSectorZ ) if (ISVIP(pSoldier) && !pSoldier->bSectorZ )
{ {
if ( PlayerKnowsAboutVIP( pSoldier->sSectorX, pSoldier->sSectorY ) ) if ( PlayerKnowsAboutVIP( pSoldier->sSectorX, pSoldier->sSectorY ) )
{ {
+104 -137
View File
@@ -1995,7 +1995,7 @@ OBJECTTYPE* FindAttachment( OBJECTTYPE * pObj, UINT16 usItem, UINT8 subObject )
{ {
if (pObj->exists() == true) { if (pObj->exists() == true) {
for (attachmentList::iterator iter = (*pObj)[subObject]->attachments.begin(); iter != (*pObj)[subObject]->attachments.end(); ++iter) { for (attachmentList::iterator iter = (*pObj)[subObject]->attachments.begin(); iter != (*pObj)[subObject]->attachments.end(); ++iter) {
if ( iter->exists() && iter->usItem == usItem ) if (iter->usItem == usItem && iter->exists())
{ {
return &(*iter); return &(*iter);
} }
@@ -2008,7 +2008,7 @@ OBJECTTYPE* FindAttachmentByClass( OBJECTTYPE * pObj, UINT32 uiItemClass, UINT8
{ {
if (pObj->exists() == true) { if (pObj->exists() == true) {
for (attachmentList::iterator iter = (*pObj)[subObject]->attachments.begin(); iter != (*pObj)[subObject]->attachments.end(); ++iter) { for (attachmentList::iterator iter = (*pObj)[subObject]->attachments.begin(); iter != (*pObj)[subObject]->attachments.end(); ++iter) {
if ( iter->exists() && Item[iter->usItem].usItemClass == uiItemClass ) if (Item[iter->usItem].usItemClass == uiItemClass && iter->exists())
{ {
return &(*iter); return &(*iter);
} }
@@ -2021,7 +2021,7 @@ OBJECTTYPE* FindAttachmentByAttachmentClass( OBJECTTYPE * pObj, UINT32 uiAttachm
{ {
if (pObj->exists() == true) { if (pObj->exists() == true) {
for (attachmentList::iterator iter = (*pObj)[subObject]->attachments.begin(); iter != (*pObj)[subObject]->attachments.end(); ++iter) { for (attachmentList::iterator iter = (*pObj)[subObject]->attachments.begin(); iter != (*pObj)[subObject]->attachments.end(); ++iter) {
if ( iter->exists() && Item[iter->usItem].attachmentclass == uiAttachmentClass ) if (Item[iter->usItem].attachmentclass == uiAttachmentClass && iter->exists())
{ {
return &(*iter); return &(*iter);
} }
@@ -2139,7 +2139,7 @@ OBJECTTYPE* FindNonSmokeLaunchableAttachment( OBJECTTYPE * pObj, UINT16 usWeapon
if (pObj->exists() == true) { if (pObj->exists() == true) {
for (attachmentList::iterator iter = (*pObj)[0]->attachments.begin(); iter != (*pObj)[0]->attachments.end(); ++iter) { for (attachmentList::iterator iter = (*pObj)[0]->attachments.begin(); iter != (*pObj)[0]->attachments.end(); ++iter) {
if ( iter->exists() && ValidLaunchable( iter->usItem, usWeapon) && Explosive[Item[iter->usItem].ubClassIndex].ubType != EXPLOSV_SMOKE ) if (ValidLaunchable( iter->usItem, usWeapon) && Explosive[Item[iter->usItem].ubClassIndex].ubType != EXPLOSV_SMOKE && iter->exists())
{ {
return &(*iter); return &(*iter);
} }
@@ -2162,7 +2162,7 @@ BOOLEAN ItemHasAttachments( OBJECTTYPE * pObj, SOLDIERTYPE * pSoldier, UINT8 ite
{ {
for(attachmentList::iterator att = (*pObj)[iter]->attachments.begin(); att != (*pObj)[iter]->attachments.end(); ++att) for(attachmentList::iterator att = (*pObj)[iter]->attachments.begin(); att != (*pObj)[iter]->attachments.end(); ++att)
{ {
if ( att->exists() && att->usItem != 0 && !ItemIsHiddenAttachment(att->usItem) ) if ( att->usItem != 0 && !ItemIsHiddenAttachment(att->usItem) )
{ {
attachmentHidden = FALSE; attachmentHidden = FALSE;
break; break;
@@ -2178,7 +2178,7 @@ BOOLEAN ItemHasAttachments( OBJECTTYPE * pObj, SOLDIERTYPE * pSoldier, UINT8 ite
{ {
for(attachmentList::iterator att = (*pObj)[iter]->attachments.begin(); att != (*pObj)[iter]->attachments.end(); ++att) for(attachmentList::iterator att = (*pObj)[iter]->attachments.begin(); att != (*pObj)[iter]->attachments.end(); ++att)
{ {
if ( att->exists() && att->usItem != 0 && !ItemIsHiddenAttachment(att->usItem) ) if ( att->usItem != 0 && !ItemIsHiddenAttachment(att->usItem) )
{ {
attachmentHidden = FALSE; attachmentHidden = FALSE;
break; break;
@@ -3872,111 +3872,13 @@ BOOLEAN AutoReload( SOLDIERTYPE * pSoldier, bool aReloadEvenIfNotEmpty )
{ {
OBJECTTYPE *pObj, *pObj2; OBJECTTYPE *pObj, *pObj2;
INT8 bSlot; INT8 bSlot;
INT16 bAPCost; INT16 bAPCost;
BOOLEAN fRet = FALSE; BOOLEAN fRet = FALSE;
CHECKF( pSoldier ); CHECKF( pSoldier );
// Flugente: check for underbarrel weapons and use that object if necessary // Flugente: check for underbarrel weapons and use that object if necessary
pObj = pSoldier->GetUsedWeapon( &(pSoldier->inv[HANDPOS]) ); pObj = pSoldier->GetUsedWeapon( &(pSoldier->inv[HANDPOS]) );
pObj2 = NULL;
if (pSoldier->IsValidSecondHandShotForReloadingPurposes()) //check for valid second hand weapon for reloading purposes (something that doesn't use ammo)
{
pObj2 = pSoldier->GetUsedWeapon( &(pSoldier->inv[SECONDHANDPOS]) );
}
// Greysa: Check if weapon is jammed and unjam it first
if ((*pObj)[0]->data.gun.bGunAmmoStatus < 0 || ((pObj2 != NULL) && (*pObj2)[0]->data.gun.bGunAmmoStatus < 0))
{
if ((*pObj)[0]->data.gun.bGunAmmoStatus < 0)
{
//borrowed from Weapons.cpp
if (EnoughPoints(pSoldier, APBPConstants[AP_UNJAM], APBPConstants[BP_UNJAM], FALSE))
{
DeductPoints(pSoldier, APBPConstants[AP_UNJAM], APBPConstants[BP_UNJAM]);
INT8 bChanceMod;
if (Weapon[pObj->usItem].EasyUnjam)
bChanceMod = 100;
else
bChanceMod = (INT8)(GetReliability(pObj) * 4);
int iResult = SkillCheck(pSoldier, UNJAM_GUN_CHECK, bChanceMod);
if (iResult > 0)
{
// yay! unjammed the gun
(*pObj)[0]->data.gun.bGunAmmoStatus *= -1;
// MECHANICAL/DEXTERITY GAIN: Unjammed a gun
if (bChanceMod < 100) // don't give exp for unjamming an easily unjammable gun
{
StatChange(pSoldier, MECHANAMT, 5, FALSE);
StatChange(pSoldier, DEXTAMT, 5, FALSE);
}
DirtyMercPanelInterface(pSoldier, DIRTYLEVEL2);
PlayJA2Sample(Weapon[Item[pObj->usItem].ubClassIndex].ManualReloadSound, RATE_11025, SoundVolume(HIGHVOLUME, pSoldier->sGridNo), 1, SoundDir(pSoldier->sGridNo));
ScreenMsg(FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, Message[STR_UNJAMMED], pSoldier->GetName(), ItemNames[pObj->usItem]);
// merc voice feedback?
}
else
{
ScreenMsg(FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, Message[STR_FAILED_UNJAM], pSoldier->GetName(), ItemNames[pObj->usItem]);
}
}
else
{
ScreenMsg(FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, Message[STR_NO_AP_NO_UNJAM], pSoldier->GetName(), ItemNames[pObj->usItem]);
}
}
if ((pObj2 != NULL) && (*pObj2)[0]->data.gun.bGunAmmoStatus < 0)
{
if (EnoughPoints(pSoldier, APBPConstants[AP_UNJAM], APBPConstants[BP_UNJAM], FALSE))
{
DeductPoints(pSoldier, APBPConstants[AP_UNJAM], APBPConstants[BP_UNJAM]);
INT8 bChanceMod;
if (Weapon[pObj2->usItem].EasyUnjam)
bChanceMod = 100;
else
bChanceMod = (INT8)(GetReliability(pObj2) * 4);
int iResult = SkillCheck(pSoldier, UNJAM_GUN_CHECK, bChanceMod);
if (iResult > 0)
{
// yay! unjammed the gun
(*pObj2)[0]->data.gun.bGunAmmoStatus *= -1;
// MECHANICAL/DEXTERITY GAIN: Unjammed a gun
if (bChanceMod < 100) // don't give exp for unjamming an easily unjammable gun
{
StatChange(pSoldier, MECHANAMT, 5, FALSE);
StatChange(pSoldier, DEXTAMT, 5, FALSE);
}
DirtyMercPanelInterface(pSoldier, DIRTYLEVEL2);
PlayJA2Sample(Weapon[Item[pObj2->usItem].ubClassIndex].ManualReloadSound, RATE_11025, SoundVolume(HIGHVOLUME, pSoldier->sGridNo), 1, SoundDir(pSoldier->sGridNo));
ScreenMsg(FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, Message[STR_UNJAMMED], pSoldier->GetName(), ItemNames[pObj2->usItem]);
// merc voice feedback?
}
else
{
ScreenMsg(FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, Message[STR_FAILED_UNJAM], pSoldier->GetName(), ItemNames[pObj2->usItem]);
}
}
else
{
ScreenMsg(FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, Message[STR_NO_AP_NO_UNJAM], pSoldier->GetName(), ItemNames[pObj2->usItem]);
}
}
return FALSE; // Greysa: We want to skip reloading if we attempted to unjam, regardless of outcome. Return value doesn't seem to matter as there doesn't seem to be any actual checks on the returned value. I picked FALSE as actual reload hasn't occurred
}
//<SB> manual recharge //<SB> manual recharge
if ((*pObj)[0]->data.gun.ubGunShotsLeft && !((*pObj)[0]->data.gun.ubGunState & GS_CARTRIDGE_IN_CHAMBER) ) if ((*pObj)[0]->data.gun.ubGunShotsLeft && !((*pObj)[0]->data.gun.ubGunState & GS_CARTRIDGE_IN_CHAMBER) )
@@ -4013,8 +3915,10 @@ BOOLEAN AutoReload( SOLDIERTYPE * pSoldier, bool aReloadEvenIfNotEmpty )
PlayJA2Sample( Weapon[ Item[pObj->usItem].ubClassIndex ].ManualReloadSound, RATE_11025, SoundVolume( HIGHVOLUME, pSoldier->sGridNo ), 1, SoundDir( pSoldier->sGridNo ) ); PlayJA2Sample( Weapon[ Item[pObj->usItem].ubClassIndex ].ManualReloadSound, RATE_11025, SoundVolume( HIGHVOLUME, pSoldier->sGridNo ), 1, SoundDir( pSoldier->sGridNo ) );
if (pObj2 != NULL) if ( pSoldier->IsValidSecondHandShot( ) )
{ {
pObj2 = &(pSoldier->inv[SECONDHANDPOS]);
if ((*pObj2)[0]->data.gun.ubGunShotsLeft && !((*pObj2)[0]->data.gun.ubGunState & GS_CARTRIDGE_IN_CHAMBER) ) if ((*pObj2)[0]->data.gun.ubGunShotsLeft && !((*pObj2)[0]->data.gun.ubGunState & GS_CARTRIDGE_IN_CHAMBER) )
{ {
(*pObj2)[0]->data.gun.ubGunState |= GS_CARTRIDGE_IN_CHAMBER; (*pObj2)[0]->data.gun.ubGunState |= GS_CARTRIDGE_IN_CHAMBER;
@@ -4026,8 +3930,10 @@ BOOLEAN AutoReload( SOLDIERTYPE * pSoldier, bool aReloadEvenIfNotEmpty )
} }
else else
{ {
if (pObj2 != NULL) if ( pSoldier->IsValidSecondHandShot( ) )
{ {
pObj2 = &(pSoldier->inv[SECONDHANDPOS]);
if ((*pObj2)[0]->data.gun.ubGunShotsLeft && !((*pObj2)[0]->data.gun.ubGunState & GS_CARTRIDGE_IN_CHAMBER) ) if ((*pObj2)[0]->data.gun.ubGunShotsLeft && !((*pObj2)[0]->data.gun.ubGunState & GS_CARTRIDGE_IN_CHAMBER) )
{ {
(*pObj2)[0]->data.gun.ubGunState |= GS_CARTRIDGE_IN_CHAMBER; (*pObj2)[0]->data.gun.ubGunState |= GS_CARTRIDGE_IN_CHAMBER;
@@ -4083,19 +3989,21 @@ BOOLEAN AutoReload( SOLDIERTYPE * pSoldier, bool aReloadEvenIfNotEmpty )
// if we are valid for two-pistol shooting (reloading) and we have enough APs still // if we are valid for two-pistol shooting (reloading) and we have enough APs still
// then do a reload of both guns! // then do a reload of both guns!
// Flugente: only reload if it's empty, or we really want to // Flugente: only reload if it's empty, or we really want to
if ( pObj2 != NULL && ( aReloadEvenIfNotEmpty || !EnoughAmmo( pSoldier, FALSE, SECONDHANDPOS ) ) ) if ( pSoldier->IsValidSecondHandShotForReloadingPurposes()
&& ( aReloadEvenIfNotEmpty || !EnoughAmmo( pSoldier, FALSE, SECONDHANDPOS ) ) )
{ {
// Flugente: check for underbarrel weapons and use that object if necessary // Flugente: check for underbarrel weapons and use that object if necessary
pObj = pSoldier->GetUsedWeapon( &( pSoldier->inv[SECONDHANDPOS] ) );
bSlot = FindAmmoToReload( pSoldier, SECONDHANDPOS, NO_SLOT ); bSlot = FindAmmoToReload( pSoldier, SECONDHANDPOS, NO_SLOT );
if ( bSlot != NO_SLOT ) if ( bSlot != NO_SLOT )
{ {
// ce would reload using this ammo! // ce would reload using this ammo!
bAPCost = GetAPsToReloadGunWithAmmo( pSoldier, pObj2, &( pSoldier->inv[bSlot] ) ); bAPCost = GetAPsToReloadGunWithAmmo( pSoldier, pObj, &( pSoldier->inv[bSlot] ) );
if ( EnoughPoints( pSoldier, (INT16)bAPCost, 0, FALSE ) ) if ( EnoughPoints( pSoldier, (INT16)bAPCost, 0, FALSE ) )
{ {
// reload the 2nd gun too // reload the 2nd gun too
fRet = ReloadGun( pSoldier, pObj2, &( pSoldier->inv[bSlot] ) ); fRet = ReloadGun( pSoldier, pObj, &( pSoldier->inv[bSlot] ) );
} }
else else
{ {
@@ -5658,7 +5566,7 @@ BOOLEAN OBJECTTYPE::AttachObjectNAS( SOLDIERTYPE * pSoldier, OBJECTTYPE * pAttac
//Madd: if the attaching/merging item had any attachments on it, then try to move them to the first result //Madd: if the attaching/merging item had any attachments on it, then try to move them to the first result
for (attachmentList::iterator iter = (*pAttachment)[0]->attachments.begin(); iter != (*pAttachment)[0]->attachments.end();) { for (attachmentList::iterator iter = (*pAttachment)[0]->attachments.begin(); iter != (*pAttachment)[0]->attachments.end();) {
if( iter->exists() && ValidItemAttachmentSlot(this, iter->usItem, TRUE, FALSE, subObject )){ if( ValidItemAttachmentSlot(this, iter->usItem, TRUE, FALSE, subObject )){
//This seems to be rather valid. Can't be 100% sure though. //This seems to be rather valid. Can't be 100% sure though.
OBJECTTYPE tempAttachment; // Madd: we must recreate the attachments because they may themselves have default inseparable attachments... OBJECTTYPE tempAttachment; // Madd: we must recreate the attachments because they may themselves have default inseparable attachments...
CreateItem(iter->usItem, (*iter)[0]->data.objectStatus, &tempAttachment); CreateItem(iter->usItem, (*iter)[0]->data.objectStatus, &tempAttachment);
@@ -5995,7 +5903,7 @@ void RemoveProhibitedAttachments(SOLDIERTYPE* pSoldier, OBJECTTYPE* pObj, UINT16
//Start by trying to re-attach inseperable items. They take precedence over items that can normally be removed //Start by trying to re-attach inseperable items. They take precedence over items that can normally be removed
for (attachmentList::iterator iter = tempAttachList.begin(); iter != tempAttachList.end();) { for (attachmentList::iterator iter = tempAttachList.begin(); iter != tempAttachList.end();) {
if( iter->exists() && Item[iter->usItem].inseparable && ValidItemAttachmentSlot(pObj, iter->usItem, TRUE, FALSE, 0, -1, 0, NULL, usAttachmentSlotIndexVector)){ if(Item[iter->usItem].inseparable && ValidItemAttachmentSlot(pObj, iter->usItem, TRUE, FALSE, 0, -1, 0, NULL, usAttachmentSlotIndexVector)){
//This seems to be rather valid. Can't be 100% sure though. //This seems to be rather valid. Can't be 100% sure though.
if(pObj->AttachObject(NULL, &(*iter), FALSE, 0, -1, FALSE, usAttachmentSlotIndexVector)){ if(pObj->AttachObject(NULL, &(*iter), FALSE, 0, -1, FALSE, usAttachmentSlotIndexVector)){
//Ok now we can be sure, lets remove this object so we don't try to drop it later. //Ok now we can be sure, lets remove this object so we don't try to drop it later.
@@ -6009,7 +5917,7 @@ void RemoveProhibitedAttachments(SOLDIERTYPE* pSoldier, OBJECTTYPE* pObj, UINT16
} }
//Try to attach all the other attachments that didn't fit their current slot. //Try to attach all the other attachments that didn't fit their current slot.
for (attachmentList::iterator iter = tempAttachList.begin(); iter != tempAttachList.end();) { for (attachmentList::iterator iter = tempAttachList.begin(); iter != tempAttachList.end();) {
if( iter->exists() && ValidItemAttachmentSlot(pObj, iter->usItem, TRUE, FALSE, 0, -1, 0, NULL, usAttachmentSlotIndexVector)){ if(ValidItemAttachmentSlot(pObj, iter->usItem, TRUE, FALSE, 0, -1, 0, NULL, usAttachmentSlotIndexVector)){
//This seems to be rather valid. Can't be 100% sure though. //This seems to be rather valid. Can't be 100% sure though.
if(pObj->AttachObject(NULL, &(*iter), FALSE, 0, -1, FALSE, usAttachmentSlotIndexVector)){ if(pObj->AttachObject(NULL, &(*iter), FALSE, 0, -1, FALSE, usAttachmentSlotIndexVector)){
//Ok now we can be sure, lets remove this object so we don't try to drop it later. //Ok now we can be sure, lets remove this object so we don't try to drop it later.
@@ -6185,7 +6093,7 @@ attachmentList ReInitMergedItem(SOLDIERTYPE* pSoldier, OBJECTTYPE* pObj, UINT16
//First re-attach any slot-changing attachments. //First re-attach any slot-changing attachments.
for (attachmentList::iterator iter = tempSlotChangingAttachList.begin(); iter != tempSlotChangingAttachList.end();) { for (attachmentList::iterator iter = tempSlotChangingAttachList.begin(); iter != tempSlotChangingAttachList.end();) {
if( iter->exists() && ValidItemAttachmentSlot(pObj, iter->usItem, TRUE, FALSE, ubStatusIndex )){ if( ValidItemAttachmentSlot(pObj, iter->usItem, TRUE, FALSE, ubStatusIndex )){
//This seems to be rather valid. Can't be 100% sure though. //This seems to be rather valid. Can't be 100% sure though.
OBJECTTYPE tempAttachment; // Madd: we must recreate the attachments because they may themselves have default inseparable attachments... OBJECTTYPE tempAttachment; // Madd: we must recreate the attachments because they may themselves have default inseparable attachments...
CreateItem(iter->usItem, (*iter)[0]->data.objectStatus, &tempAttachment); CreateItem(iter->usItem, (*iter)[0]->data.objectStatus, &tempAttachment);
@@ -6202,7 +6110,7 @@ attachmentList ReInitMergedItem(SOLDIERTYPE* pSoldier, OBJECTTYPE* pObj, UINT16
//Time to re-attach the other attachments, if we can. I am the king of copy pasta. //Time to re-attach the other attachments, if we can. I am the king of copy pasta.
for (attachmentList::iterator iter = tempAttachList.begin(); iter != tempAttachList.end();) { for (attachmentList::iterator iter = tempAttachList.begin(); iter != tempAttachList.end();) {
if( iter->exists() && ValidItemAttachmentSlot(pObj, iter->usItem, TRUE, FALSE, ubStatusIndex)){ if( ValidItemAttachmentSlot(pObj, iter->usItem, TRUE, FALSE, ubStatusIndex)){
//This seems to be rather valid. Can't be 100% sure though. //This seems to be rather valid. Can't be 100% sure though.
OBJECTTYPE tempAttachment; // Madd: we must recreate the attachments because they may themselves have default inseparable attachments... OBJECTTYPE tempAttachment; // Madd: we must recreate the attachments because they may themselves have default inseparable attachments...
CreateItem(iter->usItem, (*iter)[0]->data.objectStatus, &tempAttachment); CreateItem(iter->usItem, (*iter)[0]->data.objectStatus, &tempAttachment);
@@ -6219,7 +6127,7 @@ attachmentList ReInitMergedItem(SOLDIERTYPE* pSoldier, OBJECTTYPE* pObj, UINT16
//drop all items we couldn't re-attach. //drop all items we couldn't re-attach.
for (attachmentList::iterator iter = tempSlotChangingAttachList.begin(); iter != tempSlotChangingAttachList.end(); ++iter) { for (attachmentList::iterator iter = tempSlotChangingAttachList.begin(); iter != tempSlotChangingAttachList.end(); ++iter) {
if ( iter->exists() && Item[iter->usItem].inseparable != 1) if ( Item[iter->usItem].inseparable != 1)
{//WarmSteel - Couldn't re-attach this item, try to drop it. {//WarmSteel - Couldn't re-attach this item, try to drop it.
if (pSoldier) { if (pSoldier) {
OBJECTTYPE tempAttachment; // Madd: we must recreate the attachments because they may themselves have default inseparable attachments... OBJECTTYPE tempAttachment; // Madd: we must recreate the attachments because they may themselves have default inseparable attachments...
@@ -6237,7 +6145,7 @@ attachmentList ReInitMergedItem(SOLDIERTYPE* pSoldier, OBJECTTYPE* pObj, UINT16
} }
//and the rest too //and the rest too
for (attachmentList::iterator iter = tempAttachList.begin(); iter != tempAttachList.end(); ++iter) { for (attachmentList::iterator iter = tempAttachList.begin(); iter != tempAttachList.end(); ++iter) {
if ( iter->exists() && Item[iter->usItem].inseparable != 1) if ( Item[iter->usItem].inseparable != 1)
{//WarmSteel - Couldn't re-attach this item, try to drop it. {//WarmSteel - Couldn't re-attach this item, try to drop it.
if (pSoldier) { if (pSoldier) {
OBJECTTYPE tempAttachment; // Madd: we must recreate the attachments because they may themselves have default inseparable attachments... OBJECTTYPE tempAttachment; // Madd: we must recreate the attachments because they may themselves have default inseparable attachments...
@@ -10422,7 +10330,7 @@ BOOLEAN NCTHIsScoped( OBJECTTYPE * pObj )
return TRUE; return TRUE;
for (attachmentList::iterator iter = (*pObj)[0]->attachments.begin(); iter != (*pObj)[0]->attachments.end(); ++iter) { for (attachmentList::iterator iter = (*pObj)[0]->attachments.begin(); iter != (*pObj)[0]->attachments.end(); ++iter) {
if ( iter->exists() && Item[iter->usItem].scopemagfactor > 1.0 ) if ( Item[iter->usItem].scopemagfactor > 1.0 && iter->exists() )
return TRUE; return TRUE;
} }
} }
@@ -10441,7 +10349,7 @@ BOOLEAN IsScoped( OBJECTTYPE * pObj )
return TRUE; return TRUE;
for (attachmentList::iterator iter = (*pObj)[0]->attachments.begin(); iter != (*pObj)[0]->attachments.end(); ++iter) { for (attachmentList::iterator iter = (*pObj)[0]->attachments.begin(); iter != (*pObj)[0]->attachments.end(); ++iter) {
if ( iter->exists() && Item[iter->usItem].aimbonus > 0 ) if ( Item[iter->usItem].aimbonus > 0 && iter->exists())
return TRUE; return TRUE;
} }
} }
@@ -10528,7 +10436,7 @@ INT16 GetBaseScopeAimBonus( OBJECTTYPE * pObj, INT32 iRange )
//Search for the most powerful scope we can use. //Search for the most powerful scope we can use.
for(attachmentList::iterator iter = (*pObj)[0]->attachments.begin(); iter != (*pObj)[0]->attachments.end(); iter++) for(attachmentList::iterator iter = (*pObj)[0]->attachments.begin(); iter != (*pObj)[0]->attachments.end(); iter++)
{ {
if( iter->exists() && Item[iter->usItem].aimbonus > bonus && iRange >= Item[iter->usItem].minrangeforaimbonus ) if(Item[iter->usItem].aimbonus > bonus && iRange >= Item[iter->usItem].minrangeforaimbonus && iter->exists())
{ {
bonus = Item[iter->usItem].aimbonus; bonus = Item[iter->usItem].aimbonus;
} }
@@ -10606,7 +10514,7 @@ UINT32 FindRangeBonusAttachment( OBJECTTYPE * pObj )
{ {
if (pObj->exists() == true) { if (pObj->exists() == true) {
for (attachmentList::iterator iter = (*pObj)[0]->attachments.begin(); iter != (*pObj)[0]->attachments.end(); ++iter) { for (attachmentList::iterator iter = (*pObj)[0]->attachments.begin(); iter != (*pObj)[0]->attachments.end(); ++iter) {
if ( iter->exists() && Item[iter->usItem].rangebonus > 0 ) if (Item[iter->usItem].rangebonus > 0 && iter->exists())
{ {
return( Item[iter->usItem].uiIndex ); return( Item[iter->usItem].uiIndex );
} }
@@ -10627,7 +10535,7 @@ INT16 GetRangeBonus( OBJECTTYPE * pObj )
bonus += Item[(*pObj)[0]->data.gun.usGunAmmoItem].rangebonus; bonus += Item[(*pObj)[0]->data.gun.usGunAmmoItem].rangebonus;
for (attachmentList::iterator iter = (*pObj)[0]->attachments.begin(); iter != (*pObj)[0]->attachments.end(); ++iter) { for (attachmentList::iterator iter = (*pObj)[0]->attachments.begin(); iter != (*pObj)[0]->attachments.end(); ++iter) {
if ( iter->exists() && !ItemIsDuckbill(iter->usItem) || (ItemIsDuckbill(iter->usItem) && (*pObj)[0]->data.gun.ubGunAmmoType == AMMO_BUCKSHOT ) ) if ( !ItemIsDuckbill(iter->usItem) || (ItemIsDuckbill(iter->usItem) && (*pObj)[0]->data.gun.ubGunAmmoType == AMMO_BUCKSHOT ) && iter->exists())
bonus += BonusReduce( Item[iter->usItem].rangebonus, (*iter)[0]->data.objectStatus ); bonus += BonusReduce( Item[iter->usItem].rangebonus, (*iter)[0]->data.objectStatus );
} }
} }
@@ -12053,7 +11961,7 @@ BOOLEAN HasThermalOptics( SOLDIERTYPE * pSoldier )
OBJECTTYPE* pObj = &pSoldier->inv[HANDPOS]; OBJECTTYPE* pObj = &pSoldier->inv[HANDPOS];
if (pObj->exists() == true) { if (pObj->exists() == true) {
for (attachmentList::iterator iter = (*pObj)[0]->attachments.begin(); iter != (*pObj)[0]->attachments.end(); ++iter) { for (attachmentList::iterator iter = (*pObj)[0]->attachments.begin(); iter != (*pObj)[0]->attachments.end(); ++iter) {
if ( iter->exists() && ItemIsThermalOptics(iter->usItem) ) if (ItemIsThermalOptics(iter->usItem) && iter->exists() )
return TRUE; return TRUE;
} }
} }
@@ -12110,7 +12018,7 @@ BOOLEAN IsDuckbill( OBJECTTYPE * pObj )
return TRUE; return TRUE;
for (attachmentList::iterator iter = (*pObj)[0]->attachments.begin(); iter != (*pObj)[0]->attachments.end(); ++iter) { for (attachmentList::iterator iter = (*pObj)[0]->attachments.begin(); iter != (*pObj)[0]->attachments.end(); ++iter) {
if ( iter->exists() && ItemIsDuckbill(iter->usItem) ) if (ItemIsDuckbill(iter->usItem) && iter->exists() )
{ {
return( TRUE ); return( TRUE );
} }
@@ -12161,7 +12069,7 @@ BOOLEAN IsDetonatorAttached( OBJECTTYPE * pObj )
// return TRUE; // return TRUE;
for (attachmentList::iterator iter = (*pObj)[0]->attachments.begin(); iter != (*pObj)[0]->attachments.end(); ++iter) { for (attachmentList::iterator iter = (*pObj)[0]->attachments.begin(); iter != (*pObj)[0]->attachments.end(); ++iter) {
if ( iter->exists() && IsAttachmentClass( iter->usItem, AC_DETONATOR ) ) if ( IsAttachmentClass( iter->usItem, AC_DETONATOR ) && iter->exists() )
{ {
return( TRUE ); return( TRUE );
} }
@@ -12177,7 +12085,7 @@ BOOLEAN IsRemoteDetonatorAttached( OBJECTTYPE * pObj )
// return TRUE; // return TRUE;
for (attachmentList::iterator iter = (*pObj)[0]->attachments.begin(); iter != (*pObj)[0]->attachments.end(); ++iter) { for (attachmentList::iterator iter = (*pObj)[0]->attachments.begin(); iter != (*pObj)[0]->attachments.end(); ++iter) {
if ( iter->exists() && IsAttachmentClass( iter->usItem, AC_REMOTEDET ) ) if ( IsAttachmentClass( iter->usItem, AC_REMOTEDET ) && iter->exists() )
{ {
return( TRUE ); return( TRUE );
} }
@@ -12449,7 +12357,7 @@ OBJECTTYPE* FindAttachedBatteries( OBJECTTYPE * pObj )
{ {
if (pObj->exists() == true) { if (pObj->exists() == true) {
for (attachmentList::iterator iter = (*pObj)[0]->attachments.begin(); iter != (*pObj)[0]->attachments.end(); ++iter) { for (attachmentList::iterator iter = (*pObj)[0]->attachments.begin(); iter != (*pObj)[0]->attachments.end(); ++iter) {
if ( iter->exists() && ItemIsBatteries(iter->usItem) ) if (ItemIsBatteries(iter->usItem) && iter->exists())
{ {
return( &(*iter) ); return( &(*iter) );
} }
@@ -12518,6 +12426,51 @@ INT8 FindCamoKit( SOLDIERTYPE * pSoldier )
return( NO_SLOT ); return( NO_SLOT );
} }
INT8 FindCanteen(SOLDIERTYPE* pSoldier)
{
const INT8 invsize = pSoldier->inv.size();
for ( INT8 bLoop = 0; bLoop < invsize; ++bLoop )
{
if ( pSoldier->inv[bLoop].exists() && ItemIsCanteen(pSoldier->inv[bLoop].usItem) )
{
OBJECTTYPE* pObj = &(pSoldier->inv[bLoop]);
if ( pObj && TotalPoints(pObj) > 1 )
{
return(bLoop);
}
}
}
return(NO_SLOT);
}
INT8 FindWirecutters(SOLDIERTYPE* pSoldier)
{
const INT8 invsize = pSoldier->inv.size();
for ( INT8 bLoop = 0; bLoop < invsize; ++bLoop )
{
if ( pSoldier->inv[bLoop].exists() && ItemIsWirecutters(pSoldier->inv[bLoop].usItem) )
{
return(bLoop);
}
}
return NO_SLOT;
}
INT8 FindTNT(SOLDIERTYPE* pSoldier)
{
const INT8 invsize = pSoldier->inv.size();
for ( INT8 bLoop = 0; bLoop < invsize; ++bLoop )
{
if ( pSoldier->inv[bLoop].exists() && ItemIsTNT(pSoldier->inv[bLoop].usItem) )
{
return(bLoop);
}
}
return NO_SLOT;
}
//JMich_SkillModifiers: Adding a function to see if we have an item with disarm bonus //JMich_SkillModifiers: Adding a function to see if we have an item with disarm bonus
INT8 FindDisarmKit( SOLDIERTYPE * pSoldier ) INT8 FindDisarmKit( SOLDIERTYPE * pSoldier )
{ {
@@ -12681,7 +12634,7 @@ INT16 GetCamoBonus( OBJECTTYPE * pObj )
bonus = (Item[pObj->usItem].camobonus);// * (WEAPON_STATUS_MOD((*pObj)[0]->data.objectStatus) / 100)) ; bonus = (Item[pObj->usItem].camobonus);// * (WEAPON_STATUS_MOD((*pObj)[0]->data.objectStatus) / 100)) ;
for (attachmentList::iterator iter = (*pObj)[0]->attachments.begin(); iter != (*pObj)[0]->attachments.end(); ++iter) { for (attachmentList::iterator iter = (*pObj)[0]->attachments.begin(); iter != (*pObj)[0]->attachments.end(); ++iter) {
if ( iter->exists() && !ItemIsCamoKit(iter->usItem) ) if (!ItemIsCamoKit(iter->usItem) && iter->exists())
bonus += (INT16) (Item[iter->usItem].camobonus);// * (WEAPON_STATUS_MOD((*iter)[0]->data.objectStatus) / 100)); bonus += (INT16) (Item[iter->usItem].camobonus);// * (WEAPON_STATUS_MOD((*iter)[0]->data.objectStatus) / 100));
} }
} }
@@ -12694,7 +12647,7 @@ INT16 GetUrbanCamoBonus( OBJECTTYPE * pObj )
bonus = (Item[pObj->usItem].urbanCamobonus);// * (WEAPON_STATUS_MOD((*pObj)[0]->data.objectStatus) / 100)) ; bonus = (Item[pObj->usItem].urbanCamobonus);// * (WEAPON_STATUS_MOD((*pObj)[0]->data.objectStatus) / 100)) ;
for (attachmentList::iterator iter = (*pObj)[0]->attachments.begin(); iter != (*pObj)[0]->attachments.end(); ++iter) { for (attachmentList::iterator iter = (*pObj)[0]->attachments.begin(); iter != (*pObj)[0]->attachments.end(); ++iter) {
if ( iter->exists() && !ItemIsCamoKit(iter->usItem) ) if (!ItemIsCamoKit(iter->usItem) && iter->exists())
bonus += (INT16) (Item[iter->usItem].urbanCamobonus);// * (WEAPON_STATUS_MOD((*iter)[0]->data.objectStatus) / 100)); bonus += (INT16) (Item[iter->usItem].urbanCamobonus);// * (WEAPON_STATUS_MOD((*iter)[0]->data.objectStatus) / 100));
} }
} }
@@ -12707,7 +12660,7 @@ INT16 GetDesertCamoBonus( OBJECTTYPE * pObj )
bonus = (Item[pObj->usItem].desertCamobonus);// * (WEAPON_STATUS_MOD((*pObj)[0]->data.objectStatus) / 100)) ; bonus = (Item[pObj->usItem].desertCamobonus);// * (WEAPON_STATUS_MOD((*pObj)[0]->data.objectStatus) / 100)) ;
for (attachmentList::iterator iter = (*pObj)[0]->attachments.begin(); iter != (*pObj)[0]->attachments.end(); ++iter) { for (attachmentList::iterator iter = (*pObj)[0]->attachments.begin(); iter != (*pObj)[0]->attachments.end(); ++iter) {
if ( iter->exists() && !ItemIsCamoKit(iter->usItem) ) if (!ItemIsCamoKit(iter->usItem) && iter->exists())
bonus += (INT16) (Item[iter->usItem].desertCamobonus);// * (WEAPON_STATUS_MOD((*iter)[0]->data.objectStatus) / 100)); bonus += (INT16) (Item[iter->usItem].desertCamobonus);// * (WEAPON_STATUS_MOD((*iter)[0]->data.objectStatus) / 100));
} }
} }
@@ -12720,7 +12673,7 @@ INT16 GetSnowCamoBonus( OBJECTTYPE * pObj )
bonus = (Item[pObj->usItem].snowCamobonus);// * (WEAPON_STATUS_MOD((*pObj)[0]->data.objectStatus) / 100)) ; bonus = (Item[pObj->usItem].snowCamobonus);// * (WEAPON_STATUS_MOD((*pObj)[0]->data.objectStatus) / 100)) ;
for (attachmentList::iterator iter = (*pObj)[0]->attachments.begin(); iter != (*pObj)[0]->attachments.end(); ++iter) { for (attachmentList::iterator iter = (*pObj)[0]->attachments.begin(); iter != (*pObj)[0]->attachments.end(); ++iter) {
if ( iter->exists() && !ItemIsCamoKit(iter->usItem) ) if (!ItemIsCamoKit(iter->usItem) && iter->exists())
bonus += (INT16) (Item[iter->usItem].snowCamobonus);// * (WEAPON_STATUS_MOD((*iter)[0]->data.objectStatus) / 100)); bonus += (INT16) (Item[iter->usItem].snowCamobonus);// * (WEAPON_STATUS_MOD((*iter)[0]->data.objectStatus) / 100));
} }
} }
@@ -13165,7 +13118,7 @@ OBJECTTYPE* FindNightGogglesInInv( SOLDIERTYPE * pSoldier, INT8 * bSlot, BOOLEAN
if (searchAllInventory) { if (searchAllInventory) {
for (UINT8 loop = 0; loop < pSoldier->inv[bLoop].ubNumberOfObjects; loop ++){ for (UINT8 loop = 0; loop < pSoldier->inv[bLoop].ubNumberOfObjects; loop ++){
for (attachmentList::iterator iter = pSoldier->inv[bLoop][loop]->attachments.begin(); iter != pSoldier->inv[bLoop][loop]->attachments.end(); ++iter) { for (attachmentList::iterator iter = pSoldier->inv[bLoop][loop]->attachments.begin(); iter != pSoldier->inv[bLoop][loop]->attachments.end(); ++iter) {
if ( iter->exists() && Item[ iter->usItem ].cavevisionrangebonus > bonusToBeat && Item[ iter->usItem ].usItemClass == IC_FACE ) { if ( Item[ iter->usItem ].cavevisionrangebonus > bonusToBeat && Item[ iter->usItem ].usItemClass == IC_FACE && iter->exists() ) {
pGoggles = &(*iter); pGoggles = &(*iter);
*bSlot = bLoop; *bSlot = bLoop;
*isAttach = TRUE; *isAttach = TRUE;
@@ -13223,7 +13176,7 @@ INT16 GetMinRangeForAimBonus( SOLDIERTYPE* pSoldier, OBJECTTYPE * pObj )
attachmentList::iterator iterend = (*pObj)[0]->attachments.end(); attachmentList::iterator iterend = (*pObj)[0]->attachments.end();
for (attachmentList::iterator iter = (*pObj)[0]->attachments.begin(); iter != iterend; ++iter) for (attachmentList::iterator iter = (*pObj)[0]->attachments.begin(); iter != iterend; ++iter)
{ {
if ( !gGameExternalOptions.fScopeModes || (iter->exists() && !IsAttachmentClass(iter->usItem, AC_SCOPE|AC_SIGHT|AC_IRONSIGHT )) ) if ( !gGameExternalOptions.fScopeModes || !IsAttachmentClass(iter->usItem, AC_SCOPE|AC_SIGHT|AC_IRONSIGHT ) )
bonus += Item[iter->usItem].minrangeforaimbonus; bonus += Item[iter->usItem].minrangeforaimbonus;
} }
} }
@@ -14141,7 +14094,7 @@ INT16 GetAverageBestLaserRange( OBJECTTYPE * pObj )
} }
for (attachmentList::iterator iter = (*pObj)[0]->attachments.begin(); iter != (*pObj)[0]->attachments.end(); ++iter) for (attachmentList::iterator iter = (*pObj)[0]->attachments.begin(); iter != (*pObj)[0]->attachments.end(); ++iter)
{ {
if ( iter->exists() && Item[iter->usItem].bestlaserrange > 0 ) if (Item[iter->usItem].bestlaserrange > 0 && iter->exists())
{ {
numModifiers++; numModifiers++;
bonus += (FLOAT) Item[iter->usItem].bestlaserrange; bonus += (FLOAT) Item[iter->usItem].bestlaserrange;
@@ -14167,7 +14120,7 @@ INT16 GetBestLaserRange( OBJECTTYPE * pObj )
} }
for (attachmentList::iterator iter = (*pObj)[0]->attachments.begin(); iter != (*pObj)[0]->attachments.end(); ++iter) for (attachmentList::iterator iter = (*pObj)[0]->attachments.begin(); iter != (*pObj)[0]->attachments.end(); ++iter)
{ {
if ( iter->exists() && Item[iter->usItem].bestlaserrange > range ) if (Item[iter->usItem].bestlaserrange > range && iter->exists())
{ {
range = Item[iter->usItem].bestlaserrange; range = Item[iter->usItem].bestlaserrange;
} }
@@ -15180,7 +15133,7 @@ BOOLEAN OBJECTTYPE::TransformObject( SOLDIERTYPE * pSoldier, UINT8 ubStatusIndex
// I am the prince of copy pasta ;) // I am the prince of copy pasta ;)
for (attachmentList::iterator iter = unattachableList.begin(); iter != unattachableList.end();) for (attachmentList::iterator iter = unattachableList.begin(); iter != unattachableList.end();)
{ {
if( iter->exists() && ValidItemAttachmentSlot(&gTempObject, iter->usItem, TRUE, FALSE, ubStatusIndex )) if( ValidItemAttachmentSlot(&gTempObject, iter->usItem, TRUE, FALSE, ubStatusIndex ))
{ {
//This seems to be rather valid. Can't be 100% sure though. //This seems to be rather valid. Can't be 100% sure though.
OBJECTTYPE tempAttachment; // Madd: we must recreate the attachments because they may themselves have default inseparable attachments... OBJECTTYPE tempAttachment; // Madd: we must recreate the attachments because they may themselves have default inseparable attachments...
@@ -15868,7 +15821,7 @@ INT32 GetPercentRangeBonus( OBJECTTYPE * pObj )
bonus = ( bonus * ( 100 + Item[(*pObj)[0]->data.gun.usGunAmmoItem].percentrangebonus ) ) / 100; bonus = ( bonus * ( 100 + Item[(*pObj)[0]->data.gun.usGunAmmoItem].percentrangebonus ) ) / 100;
for (attachmentList::iterator iter = (*pObj)[0]->attachments.begin(); iter != (*pObj)[0]->attachments.end(); ++iter) { for (attachmentList::iterator iter = (*pObj)[0]->attachments.begin(); iter != (*pObj)[0]->attachments.end(); ++iter) {
if ( iter->exists() && !ItemIsDuckbill(iter->usItem) || (ItemIsDuckbill(iter->usItem) && (*pObj)[0]->data.gun.ubGunAmmoType == AMMO_BUCKSHOT )) if ( !ItemIsDuckbill(iter->usItem) || (ItemIsDuckbill(iter->usItem) && (*pObj)[0]->data.gun.ubGunAmmoType == AMMO_BUCKSHOT ))
bonus = ( bonus * ( 100 + BonusReduce( Item[iter->usItem].percentrangebonus, (*iter)[0]->data.objectStatus ) ) ) / 100; bonus = ( bonus * ( 100 + BonusReduce( Item[iter->usItem].percentrangebonus, (*iter)[0]->data.objectStatus ) ) ) / 100;
} }
} }
@@ -16137,6 +16090,20 @@ BOOLEAN FindAttachmentRange(UINT16 usAttachment, UINT32* pStartIndex, UINT32* pE
return result; return result;
} }
BOOLEAN ItemIsTNT(UINT16 usItem)
{
if ( Item[usItem].usItemClass == IC_BOMB &&
!ItemIsMine(usItem) &&
!ItemIsTripwire(usItem) &&
Explosive[Item[usItem].ubClassIndex].ubType == EXPLOSV_NORMAL &&
Explosive[Item[usItem].ubClassIndex].ubDamage > 40 &&
GetLauncherFromLaunchable(usItem) == NOTHING )
return TRUE;
return FALSE;
}
//////////////////////////////////// ////////////////////////////////////
// Item flagmask utility functions // Item flagmask utility functions
// Just to improve readability // Just to improve readability
+5
View File
@@ -249,6 +249,7 @@ BOOLEAN ItemIsOnlyInDisease(UINT16 usItem);
BOOLEAN ItemProvidesRobotCamo(UINT16 usItem); BOOLEAN ItemProvidesRobotCamo(UINT16 usItem);
BOOLEAN ItemProvidesRobotNightvision(UINT16 usItem); BOOLEAN ItemProvidesRobotNightvision(UINT16 usItem);
BOOLEAN ItemProvidesRobotLaserBonus(UINT16 usItem); BOOLEAN ItemProvidesRobotLaserBonus(UINT16 usItem);
BOOLEAN ItemIsTNT(UINT16 usItem);
//Existing functions without header def's, added them here, just incase I'll need to call //Existing functions without header def's, added them here, just incase I'll need to call
//them from the editor. //them from the editor.
@@ -479,9 +480,13 @@ INT8 FindFirstAidKit( SOLDIERTYPE * pSoldier );
INT8 FindDisarmKit( SOLDIERTYPE * pSoldier ); //JMich_SkillsModifiers: Added function to check for disarm bonus INT8 FindDisarmKit( SOLDIERTYPE * pSoldier ); //JMich_SkillsModifiers: Added function to check for disarm bonus
INT8 FindLocksmithKit( SOLDIERTYPE * pSoldier ); INT8 FindLocksmithKit( SOLDIERTYPE * pSoldier );
INT8 FindCamoKit( SOLDIERTYPE * pSoldier ); INT8 FindCamoKit( SOLDIERTYPE * pSoldier );
INT8 FindCanteen(SOLDIERTYPE* pSoldier);
INT8 FindWalkman( SOLDIERTYPE * pSoldier ); INT8 FindWalkman( SOLDIERTYPE * pSoldier );
INT8 FindTrigger( SOLDIERTYPE * pSoldier ); INT8 FindTrigger( SOLDIERTYPE * pSoldier );
INT8 FindRemoteControl( SOLDIERTYPE * pSoldier ); INT8 FindRemoteControl( SOLDIERTYPE * pSoldier );
INT8 FindWirecutters(SOLDIERTYPE* pSoldier);
INT8 FindTNT(SOLDIERTYPE* pSoldier);
INT16 GetWornCamo( SOLDIERTYPE * pSoldier ); INT16 GetWornCamo( SOLDIERTYPE * pSoldier );
INT16 GetCamoBonus( OBJECTTYPE * pObj ); INT16 GetCamoBonus( OBJECTTYPE * pObj );
INT16 GetWornStealth( SOLDIERTYPE * pSoldier ); INT16 GetWornStealth( SOLDIERTYPE * pSoldier );
+2 -1
View File
@@ -167,6 +167,7 @@ extern BOOLEAN KeyExistsInInventory( SOLDIERTYPE *pSoldier, UINT8 ubKeyID );
extern BOOLEAN KeyExistsInKeyRing( SOLDIERTYPE *pSoldier, UINT8 ubKeyID, UINT8 * pubPos ); extern BOOLEAN KeyExistsInKeyRing( SOLDIERTYPE *pSoldier, UINT8 ubKeyID, UINT8 * pubPos );
extern BOOLEAN SoldierHasKey( SOLDIERTYPE *pSoldier, UINT8 ubKeyID ); extern BOOLEAN SoldierHasKey( SOLDIERTYPE *pSoldier, UINT8 ubKeyID );
extern STR16 sKeyDescriptionStrings[];
/********************************** /**********************************
* Door utils add by Kris Morness * * Door utils add by Kris Morness *
**********************************/ **********************************/
@@ -280,4 +281,4 @@ void AttachStringToDoor( INT32 sGridNo );
void DropKeysInKeyRing( SOLDIERTYPE *pSoldier, INT32 sGridNo, INT8 bLevel, INT8 bVisible, BOOLEAN fAddToDropList, INT32 iDropListSlot, BOOLEAN fUseUnLoaded ); void DropKeysInKeyRing( SOLDIERTYPE *pSoldier, INT32 sGridNo, INT8 bLevel, INT8 bVisible, BOOLEAN fAddToDropList, INT32 iDropListSlot, BOOLEAN fUseUnLoaded );
#endif #endif
+1 -1
View File
@@ -171,7 +171,7 @@ INT32 GetSpreadPattern( OBJECTTYPE * pObj )
//If there are attachments, check them. Stop on the first one with something defined. //If there are attachments, check them. Stop on the first one with something defined.
//Dear God, I hate C++ iterators. What a fugly mess. //WarmSteel - I made it even messier ;3 //Dear God, I hate C++ iterators. What a fugly mess. //WarmSteel - I made it even messier ;3
for (attachmentList::iterator iter = pObj[0][0]->attachments.begin(); iter != pObj[0][0]->attachments.end(); ++iter){ for (attachmentList::iterator iter = pObj[0][0]->attachments.begin(); iter != pObj[0][0]->attachments.end(); ++iter){
if( iter->exists() && (n=Item[ iter->usItem ].spreadPattern) ){ if( (n=Item[ iter->usItem ].spreadPattern) && iter->exists()){
//An attachment has it, and it trumps everything, so return it's value. //An attachment has it, and it trumps everything, so return it's value.
return n;} return n;}
} }
+1 -1
View File
@@ -106,7 +106,7 @@ namespace LogicalBodyTypes {
data->filter = FilterDB::Instance().FindFilter(aFilter); data->filter = FilterDB::Instance().FindFilter(aFilter);
if (data->filter == NULL) throw XMLParseException("Unknown filter specified!", name, data->pParser); if (data->filter == NULL) throw XMLParseException("Unknown filter specified!", name, data->pParser);
} }
if (aPalette != NULL && strcmp(aPalette, "") != 0 && strcmp(aPalette, "default") != 0) { if (aPalette != NULL && strcmp(aFilter, "") != 0 && strcmp(aFilter, "default") != 0) {
data->paletteTable = PaletteDB::Instance().FindPaletteTable(aPalette); data->paletteTable = PaletteDB::Instance().FindPaletteTable(aPalette);
if (data->paletteTable == NULL) throw XMLParseException("Unknown palette specified!", name, data->pParser); if (data->paletteTable == NULL) throw XMLParseException("Unknown palette specified!", name, data->pParser);
} }
-1
View File
@@ -20,7 +20,6 @@ FLOAT gdMajorMapVersion = MAJOR_MAP_VERSION;
BOOLEAN gfWorldLoaded; BOOLEAN gfWorldLoaded;
MAPCREATE_STRUCT gMapInformation; MAPCREATE_STRUCT gMapInformation;
MAPCREATE_STRUCT gMapInformationAutoResolve;
//CHRISL: MINOR_MAP_VERSION information moved to worlddef.h by ADB. We're using these values elsewhere and need them //CHRISL: MINOR_MAP_VERSION information moved to worlddef.h by ADB. We're using these values elsewhere and need them
// in the header file // in the header file
-1
View File
@@ -57,7 +57,6 @@ public:
}; };
extern MAPCREATE_STRUCT gMapInformation; extern MAPCREATE_STRUCT gMapInformation;
extern MAPCREATE_STRUCT gMapInformationAutoResolve;
void LoadMapInformation(INT8** hBuffer, FLOAT dMajorMapVersion); void LoadMapInformation(INT8** hBuffer, FLOAT dMajorMapVersion);
void SaveMapInformation(HWFILE hFile, FLOAT dMajorMapVersion, UINT8 ubMinorMapVersion); void SaveMapInformation(HWFILE hFile, FLOAT dMajorMapVersion, UINT8 ubMinorMapVersion);
+44 -16
View File
@@ -3026,7 +3026,7 @@ BOOLEAN HandleAtNewGridNo( SOLDIERTYPE *pSoldier, BOOLEAN *pfKeepMoving )
// sevenfm: check all nearby enemy boxers for opportunity attack // sevenfm: check all nearby enemy boxers for opportunity attack
if (IS_MERC_BODY_TYPE(pSoldier) && if (IS_MERC_BODY_TYPE(pSoldier) &&
(pSoldier->flags.uiStatusFlags & SOLDIER_BOXER) && BOXER(pSoldier) &&
gTacticalStatus.bBoxingState == BOXING && gTacticalStatus.bBoxingState == BOXING &&
pSoldier->aiData.bAlertStatus >= STATUS_RED) pSoldier->aiData.bAlertStatus >= STATUS_RED)
{ {
@@ -3047,7 +3047,7 @@ BOOLEAN HandleAtNewGridNo( SOLDIERTYPE *pSoldier, BOOLEAN *pfKeepMoving )
pOpponent->bCollapsed || pOpponent->bCollapsed ||
pOpponent->bBreathCollapsed || pOpponent->bBreathCollapsed ||
!IS_MERC_BODY_TYPE(pOpponent) || !IS_MERC_BODY_TYPE(pOpponent) ||
!(pOpponent->flags.uiStatusFlags & SOLDIER_BOXER) || !BOXER(pOpponent) ||
gAnimControl[pOpponent->usAnimState].ubEndHeight < ANIM_STAND || gAnimControl[pOpponent->usAnimState].ubEndHeight < ANIM_STAND ||
pOpponent->pathing.bLevel != pSoldier->pathing.bLevel || pOpponent->pathing.bLevel != pSoldier->pathing.bLevel ||
!SoldierToSoldierLineOfSightTest(pOpponent, pSoldier, TRUE, CALC_FROM_WANTED_DIR) || !SoldierToSoldierLineOfSightTest(pOpponent, pSoldier, TRUE, CALC_FROM_WANTED_DIR) ||
@@ -5031,11 +5031,11 @@ BOOLEAN NewOKDestination( SOLDIERTYPE * pCurrSoldier, INT32 sGridNo, BOOLEAN fPe
BOOLEAN fOKCheckStruct; BOOLEAN fOKCheckStruct;
// Allow civilians and NPCs with profile to go off screen, and also enemies if tactical retreat is enabled // Allow civilians and NPCs with profile to go off screen, and also enemies if tactical retreat is enabled
auto destinationOffscreen = !(GridNoOnVisibleWorldTile(sGridNo)); const auto destinationOffscreen = !(GridNoOnVisibleWorldTile(sGridNo));
auto hasProfile = pCurrSoldier->ubProfile != NO_PROFILE; const auto hasProfile = pCurrSoldier->ubProfile != NO_PROFILE;
auto isCivilian = pCurrSoldier->bTeam == CIV_TEAM; const auto isCivilian = pCurrSoldier->bTeam == CIV_TEAM;
auto isEnemy = pCurrSoldier->bTeam == ENEMY_TEAM; const auto isEnemy = pCurrSoldier->bTeam == ENEMY_TEAM;
auto retreatAllowed = gGameExternalOptions.fAITacticalRetreat == true; const auto retreatAllowed = gGameExternalOptions.fAITacticalRetreat == TRUE;
if (destinationOffscreen && !(isCivilian || hasProfile || (isEnemy && retreatAllowed))) if (destinationOffscreen && !(isCivilian || hasProfile || (isEnemy && retreatAllowed)))
{ {
@@ -5400,6 +5400,34 @@ BOOLEAN TeamMemberNear( INT8 bTeam, INT32 sGridNo, INT32 iRange )
return(FALSE); return(FALSE);
} }
BOOLEAN NotDeafTeamMemberNear(INT8 bTeam, INT32 sGridNo, INT32 iRange)
{
for ( SoldierID pSoldier = gTacticalStatus.Team[bTeam].bFirstID; pSoldier <= gTacticalStatus.Team[bTeam].bLastID; ++pSoldier )
{
if ( pSoldier->bActive &&
pSoldier->bInSector &&
pSoldier->stats.bLife >= OKLIFE &&
pSoldier->bDeafenedCounter == 0 )
{
if ( PythSpacesAway(pSoldier->sGridNo, sGridNo) <= iRange )
{
return(TRUE);
}
}
}
return(FALSE);
}
BOOLEAN PlayerCanHearNoise(SOLDIERTYPE* pSoldier)
{
if ( pSoldier &&
(pSoldier->bVisible == TRUE || NotDeafTeamMemberNear(gbPlayerNum, pSoldier->sGridNo, TACTICAL_RANGE / 2) || NightLight() && !(gTacticalStatus.uiFlags & TURNBASED && gTacticalStatus.uiFlags & INCOMBAT) && NotDeafTeamMemberNear(gbPlayerNum, pSoldier->sGridNo, TACTICAL_RANGE)) )
return TRUE;
return FALSE;
}
INT32 FindAdjacentGridEx( SOLDIERTYPE *pSoldier, INT32 sGridNo, UINT8 *pubDirection, INT32 *psAdjustedGridNo, BOOLEAN fForceToPerson, BOOLEAN fDoor, bool allow_diagonal ) INT32 FindAdjacentGridEx( SOLDIERTYPE *pSoldier, INT32 sGridNo, UINT8 *pubDirection, INT32 *psAdjustedGridNo, BOOLEAN fForceToPerson, BOOLEAN fDoor, bool allow_diagonal )
{ {
// psAdjustedGridNo gets the original gridno or the new one if updated // psAdjustedGridNo gets the original gridno or the new one if updated
@@ -6909,7 +6937,7 @@ BOOLEAN CheckForEndOfCombatMode( BOOLEAN fIncrementTurnsNotSeen )
for ( cnt = 0; cnt < guiNumMercSlots; cnt++ ) for ( cnt = 0; cnt < guiNumMercSlots; cnt++ )
{ {
pTeamSoldier = MercSlots[ cnt ]; pTeamSoldier = MercSlots[ cnt ];
if ( pTeamSoldier && pTeamSoldier->stats.bLife >= OKLIFE && !pTeamSoldier->aiData.bNeutral && !(pTeamSoldier->usSoldierFlagMask & SOLDIER_POW) ) if ( pTeamSoldier && pTeamSoldier->stats.bLife >= OKLIFE && !pTeamSoldier->aiData.bNeutral )
{ {
if ( SoldierHasSeenEnemiesLastFewTurns( pTeamSoldier ) ) if ( SoldierHasSeenEnemiesLastFewTurns( pTeamSoldier ) )
{ {
@@ -7175,7 +7203,7 @@ static void RemoveCapturedEnemiesFromSectorInfo( INT16 sMapX, INT16 sMapY, INT8
//if ( pTeamSoldier->stats.bLife >= OKLIFE && pTeamSoldier->stats.bLife != 0 ) //if ( pTeamSoldier->stats.bLife >= OKLIFE && pTeamSoldier->stats.bLife != 0 )
{ {
// officers and generals are 'special' prisoners... // officers and generals are 'special' prisoners...
if ( pTeamSoldier->usSoldierFlagMask & SOLDIER_VIP ) if (ISVIP(pTeamSoldier))
++sNumPrisoner[PRISONER_GENERAL]; ++sNumPrisoner[PRISONER_GENERAL];
// downed pilots count as officers too, even though they are civilians. This makes capturing them more rewarding // downed pilots count as officers too, even though they are civilians. This makes capturing them more rewarding
else if ( (pTeamSoldier->usSoldierFlagMask & SOLDIER_ENEMY_OFFICER) || pTeamSoldier->ubCivilianGroup == DOWNEDPILOT_CIV_GROUP ) else if ( (pTeamSoldier->usSoldierFlagMask & SOLDIER_ENEMY_OFFICER) || pTeamSoldier->ubCivilianGroup == DOWNEDPILOT_CIV_GROUP )
@@ -7204,7 +7232,7 @@ static void RemoveCapturedEnemiesFromSectorInfo( INT16 sMapX, INT16 sMapY, INT8
} }
// Flugente: VIPs // Flugente: VIPs
if ( pTeamSoldier->usSoldierFlagMask & SOLDIER_VIP ) if (ISVIP(pTeamSoldier))
DeleteVIP( pTeamSoldier->sSectorX, pTeamSoldier->sSectorY ); DeleteVIP( pTeamSoldier->sSectorX, pTeamSoldier->sSectorY );
// Flugente: turncoats // Flugente: turncoats
@@ -9251,10 +9279,10 @@ BOOLEAN ProcessImplicationsOfPCAttack( SOLDIERTYPE * pSoldier, SOLDIERTYPE ** pp
if ( gTacticalStatus.bBoxingState == BOXING ) if ( gTacticalStatus.bBoxingState == BOXING )
{ {
// should have a check for "in boxing ring", no? // should have a check for "in boxing ring", no?
if ( ( pSoldier->usAttackingWeapon != NOTHING && !ItemIsBrassKnuckles(pSoldier->usAttackingWeapon)) || !( pSoldier->flags.uiStatusFlags & SOLDIER_BOXER ) || pSoldier->IsRiotShieldEquipped() ) if ( ( pSoldier->usAttackingWeapon != NOTHING && !ItemIsBrassKnuckles(pSoldier->usAttackingWeapon)) || !BOXER(pSoldier) || pSoldier->IsRiotShieldEquipped() )
{ {
// someone's cheating! // someone's cheating!
if ( (Item[ pSoldier->usAttackingWeapon ].usItemClass == IC_BLADE || Item[ pSoldier->usAttackingWeapon ].usItemClass == IC_PUNCH) && (pTarget->flags.uiStatusFlags & SOLDIER_BOXER) ) if ( (Item[ pSoldier->usAttackingWeapon ].usItemClass == IC_BLADE || Item[ pSoldier->usAttackingWeapon ].usItemClass == IC_PUNCH) && BOXER(pTarget) )
{ {
// knife or brass knuckles disqualify the player! // knife or brass knuckles disqualify the player!
BoxingPlayerDisqualified( pSoldier, BAD_ATTACK ); BoxingPlayerDisqualified( pSoldier, BAD_ATTACK );
@@ -9265,7 +9293,7 @@ BOOLEAN ProcessImplicationsOfPCAttack( SOLDIERTYPE * pSoldier, SOLDIERTYPE ** pp
//gTacticalStatus.bBoxingState = NOT_BOXING; //gTacticalStatus.bBoxingState = NOT_BOXING;
SetBoxingState( NOT_BOXING ); SetBoxingState( NOT_BOXING );
// if we are attacking a boxer we should set them to neutral (temporarily) so that the rest of the civgroup code works... // if we are attacking a boxer we should set them to neutral (temporarily) so that the rest of the civgroup code works...
if ( (pTarget->bTeam == CIV_TEAM) && (pTarget->flags.uiStatusFlags & SOLDIER_BOXER) ) if ( (pTarget->bTeam == CIV_TEAM) && BOXER(pTarget) )
{ {
SetSoldierNeutral( pTarget ); SetSoldierNeutral( pTarget );
} }
@@ -9412,7 +9440,7 @@ BOOLEAN ProcessImplicationsOfPCAttack( SOLDIERTYPE * pSoldier, SOLDIERTYPE ** pp
//TriggerNPCWithIHateYouQuote( pTarget->ubProfile ); //TriggerNPCWithIHateYouQuote( pTarget->ubProfile );
} }
} }
else if ( pTarget->ubCivilianGroup != NON_CIV_GROUP && !( pTarget->flags.uiStatusFlags & SOLDIER_BOXER ) ) else if ( pTarget->ubCivilianGroup != NON_CIV_GROUP && !BOXER(pTarget) )
{ {
// Firing at a civ in a civ group who isn't hostile... if anyone in that civ group can see this // Firing at a civ in a civ group who isn't hostile... if anyone in that civ group can see this
// going on they should become hostile. // going on they should become hostile.
@@ -10848,7 +10876,7 @@ static void TurnCoatAttemptMessageBoxCallBack( UINT8 ubExitValue )
UINT8 approachchance = gusSelectedSoldier->GetTurncoatConvinctionChance( prisonerdialoguetargetID, approachselected ); UINT8 approachchance = gusSelectedSoldier->GetTurncoatConvinctionChance( prisonerdialoguetargetID, approachselected );
// you can never turn a VIP (though we don't tell the player if someone is a VIP, lest they have an exploit to find out) // you can never turn a VIP (though we don't tell the player if someone is a VIP, lest they have an exploit to find out)
if ( pSoldier->usSoldierFlagMask & SOLDIER_VIP ) if (ISVIP(pSoldier))
approachchance = 0; approachchance = 0;
// as using random numbers to pass the check would result in players savescumming, use a number based on the soldier's stats // as using random numbers to pass the check would result in players savescumming, use a number based on the soldier's stats
@@ -11403,7 +11431,7 @@ void TeamDropAll(UINT8 bTeam, BOOLEAN fForce)
// if soldier is in the current sector, drop all equipment (that has the TAKEN_BY_MILITIA-flag set) // if soldier is in the current sector, drop all equipment (that has the TAKEN_BY_MILITIA-flag set)
if( pSoldier->bActive && ( pSoldier->sSectorX == gWorldSectorX ) && ( pSoldier->sSectorY == gWorldSectorY ) && ( pSoldier->bSectorZ == gbWorldSectorZ) ) if( pSoldier->bActive && ( pSoldier->sSectorX == gWorldSectorX ) && ( pSoldier->sSectorY == gWorldSectorY ) && ( pSoldier->bSectorZ == gbWorldSectorZ) )
{ {
pSoldier->DropSectorEquipment( NOWHERE ); pSoldier->DropSectorEquipment();
} }
} }
} }
+3 -1
View File
@@ -261,6 +261,9 @@ void SelectNextAvailSoldier( SOLDIERTYPE *pSoldier );
BOOLEAN TeamMemberNear(INT8 bTeam, INT32 sGridNo, INT32 iRange); BOOLEAN TeamMemberNear(INT8 bTeam, INT32 sGridNo, INT32 iRange);
BOOLEAN IsValidTargetMerc( SoldierID ubSoldierID ); BOOLEAN IsValidTargetMerc( SoldierID ubSoldierID );
BOOLEAN NotDeafTeamMemberNear(INT8 bTeam, INT32 sGridNo, INT32 iRange);
BOOLEAN PlayerCanHearNoise(SOLDIERTYPE* pSoldier);
// FUNCTIONS FOR MANIPULATING MERC SLOTS - A LIST OF ALL ACTIVE MERCS // FUNCTIONS FOR MANIPULATING MERC SLOTS - A LIST OF ALL ACTIVE MERCS
INT32 GetFreeMercSlot( ); INT32 GetFreeMercSlot( );
INT32 AddMercSlot( SOLDIERTYPE *pSoldier ); INT32 AddMercSlot( SOLDIERTYPE *pSoldier );
@@ -433,4 +436,3 @@ BOOLEAN IsFreeSlotAvailable( int aTeam );
void AttemptToCapturePlayerSoldiers(); void AttemptToCapturePlayerSoldiers();
#endif #endif
+36 -100
View File
@@ -36,6 +36,9 @@
#include "BinaryHeap.hpp" #include "BinaryHeap.hpp"
#include "opplist.h" #include "opplist.h"
#include "Weapons.h" #include "Weapons.h"
#include "renderworld.h"
#include "Cheats.h"
#include "Handle UI.h"
//forward declarations of common classes to eliminate includes //forward declarations of common classes to eliminate includes
class OBJECTTYPE; class OBJECTTYPE;
@@ -56,19 +59,8 @@ extern BOOLEAN InGasSpot(SOLDIERTYPE *pSoldier, INT32 sGridNo, INT8 bLevel);
// skiplist has extra level of pointers every 4 elements, so a level 5is optimized for // skiplist has extra level of pointers every 4 elements, so a level 5is optimized for
// 4 to the power of 5 elements, or 2 to the power of 10, 1024 // 4 to the power of 5 elements, or 2 to the power of 10, 1024
//#define PATHAI_VISIBLE_DEBUG
//#define PATHAI_SKIPLIST_DEBUG //#define PATHAI_SKIPLIST_DEBUG
#ifdef PATHAI_VISIBLE_DEBUG
#include "video.h"
//extern INT16 gsCoverValue[WORLD_MAX];
extern INT16 * gsCoverValue;
BOOLEAN gfDisplayCoverValues = TRUE;
BOOLEAN gfDrawPathPoints = TRUE;
#endif
BOOLEAN gfPlotPathToExitGrid = FALSE; BOOLEAN gfPlotPathToExitGrid = FALSE;
BOOLEAN gfRecalculatingExistingPathCost = FALSE; BOOLEAN gfRecalculatingExistingPathCost = FALSE;
UINT8 gubGlobalPathFlags = 0; UINT8 gubGlobalPathFlags = 0;
@@ -613,15 +605,12 @@ int AStarPathfinder::GetPath(SOLDIERTYPE *s ,
} }
if (gRenderDebugInfoMode == DEBUG_PATHFINDING && DEBUG_CHEAT_LEVEL())
#if defined( PATHAI_VISIBLE_DEBUG )
if (gfDisplayCoverValues && gfDrawPathPoints)
{ {
memset( gsCoverValue, 0x7F, sizeof( INT16 ) * WORLD_MAX ); ResetDebugInfoValues();
gRenderDebugInfoValues[ StartNode ] = 0;
PATHAI_VISIBLE_DEBUG_Counter = 1;
} }
gsCoverValue[ StartNode ] = 0;
PATHAI_VISIBLE_DEBUG_Counter = 1;
#endif
//init other private data, mostly flags //init other private data, mostly flags
endDir = lastDir = direction = startDir = 0; endDir = lastDir = direction = startDir = 0;
@@ -786,12 +775,10 @@ int AStarPathfinder::GetPath(SOLDIERTYPE *s ,
return 0; return 0;
} }
#if defined( PATHAI_VISIBLE_DEBUG ) if (gRenderDebugInfoMode == DEBUG_PATHFINDING && DEBUG_CHEAT_LEVEL())
if (gfDisplayCoverValues && gfDrawPathPoints)
{ {
SetRenderFlags( RENDER_FLAG_FULL ); SetRenderFlags( RENDER_FLAG_FULL );
} }
#endif
// Count the number of steps, but keep it less than the max path length. // Count the number of steps, but keep it less than the max path length.
// Adjust the parent until it begins at the tail end of the max path length (or the dest if reachable) // Adjust the parent until it begins at the tail end of the max path length (or the dest if reachable)
@@ -874,12 +861,10 @@ int AStarPathfinder::GetPath(SOLDIERTYPE *s ,
sizePath = giPathDataSize; sizePath = giPathDataSize;
} }
#if defined( PATHAI_VISIBLE_DEBUG ) if (gRenderDebugInfoMode == DEBUG_PATHFINDING && DEBUG_CHEAT_LEVEL())
if (gfDisplayCoverValues && gfDrawPathPoints)
{ {
SetRenderFlags( RENDER_FLAG_FULL ); SetRenderFlags( RENDER_FLAG_FULL );
} }
#endif
#ifdef COUNT_PATHS #ifdef COUNT_PATHS
guiSuccessfulPathChecks++; guiSuccessfulPathChecks++;
@@ -991,15 +976,13 @@ void AStarPathfinder::ExecuteAStarLogic()
SetAStarStatus(ParentNode, AStar_Closed); SetAStarStatus(ParentNode, AStar_Closed);
//ClosedList.push_back(ParentNode); //ClosedList.push_back(ParentNode);
#if defined( PATHAI_VISIBLE_DEBUG ) if (gRenderDebugInfoMode == DEBUG_PATHFINDING && DEBUG_CHEAT_LEVEL())
if (gfDisplayCoverValues && gfDrawPathPoints)
{ {
if (gsCoverValue[ ParentNode ] > 0) if (gRenderDebugInfoValues[ParentNode] > 0)
{ {
gsCoverValue[ ParentNode ] *= -1; gRenderDebugInfoValues[ParentNode] *= -1;
} }
} }
#endif
// Shouldn't G and AP be the same thing? // Shouldn't G and AP be the same thing?
INT16 baseGCost = GetAStarG(ParentNode); INT16 baseGCost = GetAStarG(ParentNode);
@@ -1082,7 +1065,7 @@ void AStarPathfinder::ExecuteAStarLogic()
gpWorldLevelData[ CurrentNode ].ubExtFlags[0] |= MAPELEMENT_EXT_CLIMBPOINT; gpWorldLevelData[ CurrentNode ].ubExtFlags[0] |= MAPELEMENT_EXT_CLIMBPOINT;
gpWorldLevelData[ ParentNode ].ubExtFlags[1] |= MAPELEMENT_EXT_CLIMBPOINT; gpWorldLevelData[ ParentNode ].ubExtFlags[1] |= MAPELEMENT_EXT_CLIMBPOINT;
#ifdef ROOF_DEBUG #ifdef ROOF_DEBUG
gsCoverValue[CurrentNode] = 1; gRenderDebugInfoValues[CurrentNode] = 1;
#endif #endif
} }
@@ -1180,26 +1163,13 @@ void AStarPathfinder::ExecuteAStarLogic()
int AStarH = CalcH(); int AStarH = CalcH();
int AStarF = (AStarG + extraGCoverCost) + AStarH; int AStarF = (AStarG + extraGCoverCost) + AStarH;
#if defined( PATHAI_VISIBLE_DEBUG ) if (gRenderDebugInfoMode == DEBUG_PATHFINDING && DEBUG_CHEAT_LEVEL())
if (gfDisplayCoverValues && gfDrawPathPoints)
{ {
if (gsCoverValue[CurrentNode] == 0x7F7F) if (gRenderDebugInfoValues[CurrentNode] == 0x7FFFFFFF)
{ {
//gsCoverValue[CurrentNode] = PATHAI_VISIBLE_DEBUG_Counter++; gRenderDebugInfoValues[CurrentNode] = (INT16) AStarF;
gsCoverValue[CurrentNode] = (INT16) AStarF;
} }
/*
else if (gsCoverValue[CurrentNodeIndex] >= 0)
{
gsCoverValue[CurrentNodeIndex]++;
}
else
{
gsCoverValue[CurrentNodeIndex]--;
}
*/
} }
#endif
//insert this node onto the heap //insert this node onto the heap
if (GetAStarStatus(CurrentNode) == AStar_Init) if (GetAStarStatus(CurrentNode) == AStar_Init)
@@ -1380,7 +1350,7 @@ INT16 AStarPathfinder::CalcAP(int const terrainCost, UINT8 const direction)
} }
// Flugente: dragging someone // Flugente: dragging someone
if ( pSoldier->IsDragging() ) if ( pSoldier->IsDragging( false ) )
{ {
movementAPCost *= gItemSettings.fDragAPCostModifier; movementAPCost *= gItemSettings.fDragAPCostModifier;
} }
@@ -2250,9 +2220,7 @@ INT32 FindBestPath(SOLDIERTYPE *s , INT32 sDestination, INT8 bLevel, INT16 usMov
CHAR8 zTempString[1000], zTS[50]; CHAR8 zTempString[1000], zTS[50];
#endif #endif
#ifdef PATHAI_VISIBLE_DEBUG
UINT16 usCounter = 0; UINT16 usCounter = 0;
#endif
fVehicle = FALSE; fVehicle = FALSE;
iOriginationX = iOriginationY = 0; iOriginationX = iOriginationY = 0;
@@ -2502,12 +2470,10 @@ if(!GridNoOnVisibleWorldTile(iDestination))
memset( pathQ, 0, iMaxPathQ * sizeof( path_t ) ); memset( pathQ, 0, iMaxPathQ * sizeof( path_t ) );
memset( trailTree, 0, iMaxTrailTree * sizeof( trail_t ) ); memset( trailTree, 0, iMaxTrailTree * sizeof( trail_t ) );
#if defined( PATHAI_VISIBLE_DEBUG ) if (gRenderDebugInfoMode == DEBUG_PATHFINDING && DEBUG_CHEAT_LEVEL())
if (gfDisplayCoverValues && gfDrawPathPoints)
{ {
memset( gsCoverValue, 0x7F, sizeof( INT16 ) * WORLD_MAX ); ResetDebugInfoValues();
} }
#endif
bSkipListLevel = 1; bSkipListLevel = 1;
iSkipListSize = 0; iSkipListSize = 0;
@@ -2619,15 +2585,13 @@ if(!GridNoOnVisibleWorldTile(iDestination))
// remember the cost used to get here... // remember the cost used to get here...
prevCost = gubWorldMovementCosts[ trailTree[ sCurPathNdx ].sGridNo ][ trailTree[ sCurPathNdx ].stepDir ][ bLevel ]; prevCost = gubWorldMovementCosts[ trailTree[ sCurPathNdx ].sGridNo ][ trailTree[ sCurPathNdx ].stepDir ][ bLevel ];
#if defined( PATHAI_VISIBLE_DEBUG ) if (gRenderDebugInfoMode == DEBUG_PATHFINDING && DEBUG_CHEAT_LEVEL())
if (gfDisplayCoverValues && gfDrawPathPoints)
{ {
if (gsCoverValue[ curLoc ] > 0) if (gRenderDebugInfoValues[ curLoc ] > 0)
{ {
gsCoverValue[ curLoc ] *= -1; gRenderDebugInfoValues[ curLoc ] *= -1;
} }
} }
#endif
/* /*
if (fTurnSlow) if (fTurnSlow)
@@ -3553,27 +3517,13 @@ if(!GridNoOnVisibleWorldTile(iDestination))
// costs less than the best so far to the same location? // costs less than the best so far to the same location?
if (trailCostUsed[newLoc] != gubGlobalPathCount || newTotCost < trailCost[newLoc]) if (trailCostUsed[newLoc] != gubGlobalPathCount || newTotCost < trailCost[newLoc])
{ {
if (gRenderDebugInfoMode == DEBUG_PATHFINDING && DEBUG_CHEAT_LEVEL())
#if defined( PATHAI_VISIBLE_DEBUG ) {
if (gRenderDebugInfoValues[newLoc] == 0x7FFFFFFF)
if (gfDisplayCoverValues && gfDrawPathPoints)
{
if (gsCoverValue[newLoc] == 0x7F7F)
{ {
gsCoverValue[newLoc] = usCounter++; gRenderDebugInfoValues[newLoc] = usCounter++;
} }
/* }
else if (gsCoverValue[newLoc] >= 0)
{
gsCoverValue[newLoc]++;
}
else
{
gsCoverValue[newLoc]--;
}
*/
}
#endif
//NEWQUENODE; //NEWQUENODE;
{ {
@@ -3816,20 +3766,13 @@ ENDOFLOOP:
while (pathQNotEmpty && pathNotYetFound); while (pathQNotEmpty && pathNotYetFound);
#if defined( PATHAI_VISIBLE_DEBUG ) if (gRenderDebugInfoMode == DEBUG_PATHFINDING && DEBUG_CHEAT_LEVEL())
if (gfDisplayCoverValues && gfDrawPathPoints) {
if ( guiCurrentScreen == GAME_SCREEN )
{ {
SetRenderFlags( RENDER_FLAG_FULL ); InvalidateRegion(gsVIEWPORT_START_X, gsVIEWPORT_START_Y, gsVIEWPORT_END_X, gsVIEWPORT_WINDOW_END_Y);
if ( guiCurrentScreen == GAME_SCREEN )
{
RenderWorld();
RenderCoverDebug( );
InvalidateScreen( );
EndFrameBufferRender();
RefreshScreen( NULL );
}
} }
#endif }
// work finished. Did we find a path? // work finished. Did we find a path?
@@ -3884,17 +3827,10 @@ ENDOFLOOP:
} }
#if defined( PATHAI_VISIBLE_DEBUG ) if (gRenderDebugInfoMode == DEBUG_PATHFINDING && DEBUG_CHEAT_LEVEL())
if (gfDisplayCoverValues && gfDrawPathPoints) {
{ InvalidateRegion(gsVIEWPORT_START_X, gsVIEWPORT_START_Y, gsVIEWPORT_END_X, gsVIEWPORT_WINDOW_END_Y);
SetRenderFlags( RENDER_FLAG_FULL ); }
RenderWorld();
RenderCoverDebug( );
InvalidateScreen( );
EndFrameBufferRender();
RefreshScreen( NULL );
}
#endif
// return path length : serves as a "successful" flag and a path length counter // return path length : serves as a "successful" flag and a path length counter
+7 -9
View File
@@ -713,10 +713,6 @@ void RemoveCorpse( INT32 iCorpseID )
DeleteAniTile( gRottingCorpse[ iCorpseID ].pAniTile ); DeleteAniTile( gRottingCorpse[ iCorpseID ].pAniTile );
FreeCorpsePalettes( &( gRottingCorpse[ iCorpseID ] ) ); FreeCorpsePalettes( &( gRottingCorpse[ iCorpseID ] ) );
const auto sGridNo = gRottingCorpse[iCorpseID].def.sGridNo;
auto pStructure = FindLastStructure(sGridNo, STRUCTURE_CORPSE);
DeleteStructureFromWorld(pStructure);
} }
BOOLEAN CreateCorpsePalette( ROTTING_CORPSE *pCorpse ) BOOLEAN CreateCorpsePalette( ROTTING_CORPSE *pCorpse )
@@ -1066,7 +1062,7 @@ BOOLEAN TurnSoldierIntoCorpse( SOLDIERTYPE *pSoldier, BOOLEAN fRemoveMerc, BOOLE
DropKeysInKeyRing( pSoldier, pSoldier->sGridNo, pSoldier->pathing.bLevel, bVisible, FALSE, 0, FALSE ); DropKeysInKeyRing( pSoldier, pSoldier->sGridNo, pSoldier->pathing.bLevel, bVisible, FALSE, 0, FALSE );
// Flugente: even if we forbid militia from dropping their equipment, they will still drop what they took via sector inventory (this functions only drops what they took) // Flugente: even if we forbid militia from dropping their equipment, they will still drop what they took via sector inventory (this functions only drops what they took)
pSoldier->DropSectorEquipment( NOWHERE ); pSoldier->DropSectorEquipment();
} }
// Make team look for items // Make team look for items
@@ -1112,8 +1108,7 @@ BOOLEAN TurnSoldierIntoCorpse( SOLDIERTYPE *pSoldier, BOOLEAN fRemoveMerc, BOOLE
iCorpseID = AddRottingCorpse( &Corpse ); iCorpseID = AddRottingCorpse( &Corpse );
// If this is our guy......make visible... // If this is our guy......make visible...
//if ( pSoldier->bTeam == gbPlayerNum ) if ( iCorpseID != -1 && pSoldier->bTeam == OUR_TEAM || gbPublicOpplist[OUR_TEAM][pSoldier->ubID] == SEEN_CURRENTLY )
if ( iCorpseID != -1 )
{ {
MakeCorpseVisible( pSoldier, &( gRottingCorpse[ iCorpseID ] ) ); MakeCorpseVisible( pSoldier, &( gRottingCorpse[ iCorpseID ] ) );
} }
@@ -1775,10 +1770,10 @@ BOOLEAN IsValidDecapitationCorpse( ROTTING_CORPSE *pCorpse )
ROTTING_CORPSE *GetCorpseAtGridNo( INT32 sGridNo, INT8 bLevel ) ROTTING_CORPSE *GetCorpseAtGridNo( INT32 sGridNo, INT8 bLevel )
{ {
STRUCTURE *pStructure, *pBaseStructure; STRUCTURE *pStructure, *pBaseStructure;
INT32 sBaseGridNo; INT32 sBaseGridNo;
pStructure = FindLastStructure( sGridNo, STRUCTURE_CORPSE ); pStructure = FindStructure( sGridNo, STRUCTURE_CORPSE );
if ( pStructure != NULL ) if ( pStructure != NULL )
{ {
@@ -2540,6 +2535,9 @@ void LookForAndMayCommentOnSeeingCorpse( SOLDIERTYPE *pSoldier, INT32 sGridNo, U
return; return;
} }
if (pCorpse->fActivated && pCorpse->def.bVisible != 1)
pCorpse->def.bVisible = 1;
if ( pCorpse->def.ubType != ROTTING_STAGE2 ) if ( pCorpse->def.ubType != ROTTING_STAGE2 )
{ {
return; return;
+1 -1
View File
@@ -3031,7 +3031,7 @@ BOOLEAN RepairIsDone(DEALER_SPECIAL_ITEM* pSpecial)
// max condition of all permanent attachments on it // max condition of all permanent attachments on it
for (attachmentList::iterator iter = RepairItem.ItemObject[0]->attachments.begin(); iter != RepairItem.ItemObject[0]->attachments.end(); ++iter) { for (attachmentList::iterator iter = RepairItem.ItemObject[0]->attachments.begin(); iter != RepairItem.ItemObject[0]->attachments.end(); ++iter) {
if ( iter->exists() && CanDealerRepairItem( gbSelectedArmsDealerID, iter->usItem ) ) if ( CanDealerRepairItem( gbSelectedArmsDealerID, iter->usItem ) && iter->exists())
{ {
// fix it up // fix it up
(*iter)[0]->data.objectStatus = 100; (*iter)[0]->data.objectStatus = 100;
+1 -1
View File
@@ -4223,7 +4223,7 @@ BOOLEAN HandleSoldierDeath( SOLDIERTYPE *pSoldier , BOOLEAN *pfMadeCorpse )
} }
// Flugente: VIPs // Flugente: VIPs
if ( pSoldier->usSoldierFlagMask & SOLDIER_VIP ) if (ISVIP(pSoldier))
{ {
DeleteVIP( pSoldier->sSectorX, pSoldier->sSectorY ); DeleteVIP( pSoldier->sSectorX, pSoldier->sSectorY );
} }
+97 -8
View File
@@ -9046,7 +9046,7 @@ void CalculateSoldierAniSpeed( SOLDIERTYPE *pSoldier, SOLDIERTYPE *pStatsSoldier
} }
// Flugente: drag people // Flugente: drag people
if ( pSoldier->IsDragging() ) if ( pSoldier->IsDragging( false ) )
{ {
pSoldier->sAniDelay = gItemSettings.fDragAPCostModifier * pSoldier->sAniDelay; pSoldier->sAniDelay = gItemSettings.fDragAPCostModifier * pSoldier->sAniDelay;
} }
@@ -9700,6 +9700,85 @@ void SOLDIERTYPE::BeginSoldierClimbWallUp( void )
} }
//------------------------------------------------------------------------------------------ //------------------------------------------------------------------------------------------
void SOLDIERTYPE::BeginSoldierJumpWindowAI(void)
{
DebugAI(AI_MSG_INFO, this, String("check if we can jump through window"));
//UINT8 ubDirection = this->aiData.usActionData;
UINT8 ubDirection = this->ubDirection;
INT32 sWindowGridNo = this->sGridNo;
if ( ubDirection == NORTH || ubDirection == WEST )
sWindowGridNo = NewGridNo(this->sGridNo, (UINT16)DirectionInc((UINT8)ubDirection));
DebugAI(AI_MSG_INFO, this, String("sWindowGridNo %d direction %d", sWindowGridNo, ubDirection));
if (//CheckWindow(this->sGridNo, ubDirection, gGameExternalOptions.fCanJumpThroughClosedWindows) &&
IsJumpableWindowPresentAtGridNo(sWindowGridNo, ubDirection, gGameExternalOptions.fCanJumpThroughClosedWindows) &&
//FindWindowJumpDirection(this, this->sGridNo, bDirection, &bDirection) &&
this->pathing.bLevel == 0 &&
(ubDirection == NORTH || ubDirection == EAST || ubDirection == SOUTH || ubDirection == WEST) )
{
// Flugente: if we are jumping through an intact window, smash it during our animation
if ( gGameExternalOptions.fCanJumpThroughClosedWindows )
{
// is there really an intact window that we jump through?
if ( IsJumpableWindowPresentAtGridNo(sWindowGridNo, ubDirection, TRUE) && !IsJumpableWindowPresentAtGridNo(sWindowGridNo, ubDirection, FALSE) )
{
STRUCTURE* pStructure = FindStructure(sWindowGridNo, STRUCTURE_WALLNWINDOW);
if ( pStructure && !(pStructure->fFlags & STRUCTURE_OPEN) )
{
DebugAI(AI_MSG_INFO, this, String("jumping through closed window, damage soldier"));
// intact window found. Smash it!
WindowHit(sWindowGridNo, pStructure->usStructureID, (ubDirection == SOUTH || ubDirection == EAST), TRUE, PlayerCanHearNoise(this));
// we get a bit of damage for jumping through a window
this->SoldierTakeDamage(0, 2 + 2 * Random(4), 0, 1000, TAKE_DAMAGE_ELECTRICITY, NOBODY, sWindowGridNo, 0);
}
}
}
this->sTempNewGridNo = NewGridNo(this->sGridNo, (UINT16)DirectionInc(ubDirection));
this->flags.fDontChargeTurningAPs = TRUE;
EVENT_InternalSetSoldierDesiredDirection(this, ubDirection, FALSE, this->usAnimState);
this->flags.fTurningUntilDone = TRUE;
// ATE: Reset flag to go back to prone...
// Flugente: In case an animation is missing (civilian bodytypes), we TELEPORT instead
if ( IsAnimationValidForBodyType(this, JUMPWINDOWS) == FALSE )
{
DebugAI(AI_MSG_INFO, this, String("teleport soldier to %d", this->sTempNewGridNo));
// sevenfm: deduct APs for jumping
if ( UsingNewInventorySystem() && FindBackpackOnSoldier(this) != ITEM_NOT_FOUND )
DeductPoints(this, GetAPsToJumpThroughWindows(this, TRUE), GetBPsToJumpThroughWindows(this, TRUE), SP_MOVEMENT_INTERRUPT);
else
DeductPoints(this, GetAPsToJumpThroughWindows(this, FALSE), GetBPsToJumpThroughWindows(this, FALSE), SP_MOVEMENT_INTERRUPT);
TeleportSoldier(this, this->sTempNewGridNo, TRUE);
}
else
{
DebugAI(AI_MSG_INFO, this, String("start jumping"));
this->flags.bTurningFromPronePosition = TURNING_FROM_PRONE_OFF;
this->EVENT_InitNewSoldierAnim(JUMPWINDOWS, 0, FALSE);
//this->usPendingAnimation = JUMPWINDOWS;
//EndAIGuysTurn(this);
}
// Flugente: should be fixed now, re-enable if not
// Flugente: if an AI guy, end turn (weird endless clock syndrome)
//if ( this->bTeam != OUR_TEAM )
//EndAIGuysTurn( this);
}
else
{
ScreenMsg(FONT_LTRED, MSG_INTERFACE, L"[%d] %s cannot jump", this->ubID, this->GetName());
DebugAI(AI_MSG_INFO, this, String("CancelAIAction: cannot jump"));
CancelAIAction(this, TRUE);
}
}
UINT32 SleepDartSuccumbChance( SOLDIERTYPE * pSoldier ) UINT32 SleepDartSuccumbChance( SOLDIERTYPE * pSoldier )
{ {
UINT32 uiChance; UINT32 uiChance;
@@ -9853,7 +9932,7 @@ void SOLDIERTYPE::BeginSoldierGetup( void )
else else
{ {
this->bTurnsCollapsed++; this->bTurnsCollapsed++;
if ( (gTacticalStatus.bBoxingState == BOXING) && (this->flags.uiStatusFlags & SOLDIER_BOXER) ) if ( (gTacticalStatus.bBoxingState == BOXING) && (BOXER(this)) )
{ {
if ( this->bTurnsCollapsed > 1 ) if ( this->bTurnsCollapsed > 1 )
{ {
@@ -11516,7 +11595,7 @@ void SOLDIERTYPE::MoveMerc( FLOAT dMovementChange, FLOAT dAngle, BOOLEAN fCheckR
// Flugente: as we move a tile, we would now be too far away to drag someone. // Flugente: as we move a tile, we would now be too far away to drag someone.
// So remember whether we were dragging (we have to set our position now, otherwise the person we drag woul soon occupy our gridno). // So remember whether we were dragging (we have to set our position now, otherwise the person we drag woul soon occupy our gridno).
BOOLEAN currentlydragging = this->IsDragging(true); BOOLEAN currentlydragging = this->IsDragging();
INT32 sOldGridNo = this->sGridNo; INT32 sOldGridNo = this->sGridNo;
// OK, set new position // OK, set new position
@@ -14668,7 +14747,7 @@ void SOLDIERTYPE::SoldierInventoryCoolDown( void )
attachmentList::iterator iterend = (*pObj)[0]->attachments.end( ); attachmentList::iterator iterend = (*pObj)[0]->attachments.end( );
for ( attachmentList::iterator iter = (*pObj)[0]->attachments.begin( ); iter != iterend; ++iter ) for ( attachmentList::iterator iter = (*pObj)[0]->attachments.begin( ); iter != iterend; ++iter )
{ {
if ( iter->exists() && Item[iter->usItem].usFlashLightRange ) if ( Item[iter->usItem].usFlashLightRange )
flashlightfound = TRUE; flashlightfound = TRUE;
} }
} }
@@ -16818,7 +16897,7 @@ BOOLEAN SOLDIERTYPE::UpdateMultiTurnAction( )
return TRUE; return TRUE;
} }
void SOLDIERTYPE::DropSectorEquipment( INT32 UnloadedSectorGridNo) void SOLDIERTYPE::DropSectorEquipment( )
{ {
// not if we already dropped the gear // not if we already dropped the gear
if ( this->usSoldierFlagMask & SOLDIER_EQUIPMENT_DROPPED ) if ( this->usSoldierFlagMask & SOLDIER_EQUIPMENT_DROPPED )
@@ -16887,7 +16966,7 @@ void SOLDIERTYPE::DropSectorEquipment( INT32 UnloadedSectorGridNo)
} }
} }
AddItemsToUnLoadedSector( this->sSectorX, this->sSectorY, this->bSectorZ, UnloadedSectorGridNo, counter, pObject, 0, WORLD_ITEM_REACHABLE, 0, 1, FALSE ); AddItemsToUnLoadedSector( this->sSectorX, this->sSectorY, this->bSectorZ, RandomGridNo( ), counter, pObject, 0, WORLD_ITEM_REACHABLE, 0, 1, FALSE );
} }
} }
@@ -19046,7 +19125,7 @@ BOOLEAN SOLDIERTYPE::IsJamming( )
{ {
if ( CanUseRadio( FALSE ) ) if ( CanUseRadio( FALSE ) )
return TRUE; return TRUE;
// if we cannot use the radio, remove that flag hile we're at it // if we cannot use the radio, remove that flag while we're at it
else else
usSoldierFlagMask &= ~SOLDIER_RADIO_OPERATOR_JAMMING; usSoldierFlagMask &= ~SOLDIER_RADIO_OPERATOR_JAMMING;
} }
@@ -24226,7 +24305,7 @@ BOOLEAN SOLDIERTYPE::CanBreakWindow(void)
BOOLEAN SOLDIERTYPE::CanStartDrag(void) BOOLEAN SOLDIERTYPE::CanStartDrag(void)
{ {
if (!this->IsDragging() && this->CanDragInPrinciple()) if (!this->IsDragging(false) && this->CanDragInPrinciple())
{ {
INT32 sNewGridNo = NewGridNo(this->sGridNo, DirectionInc(this->ubDirection)); INT32 sNewGridNo = NewGridNo(this->sGridNo, DirectionInc(this->ubDirection));
@@ -26297,3 +26376,13 @@ void SOLDIERTYPE::InitializeExtraData(void)
this->delayedDamageFunction = nullptr; this->delayedDamageFunction = nullptr;
} }
UINT8 SOLDIERTYPE::AnimHeight(void) const
{
return gAnimControl[this->usAnimState].ubHeight;
}
UINT8 SOLDIERTYPE::AnimEndHeight(void) const
{
return gAnimControl[this->usAnimState].ubEndHeight;
}
+5 -2
View File
@@ -1788,10 +1788,13 @@ public:
void ChangeToFallbackAnimation( UINT8 fallBackDirection ); void ChangeToFallbackAnimation( UINT8 fallBackDirection );
// sevenfm // sevenfm
void BeginSoldierJumpWindowAI(void);
void BreakWindow(void); void BreakWindow(void);
BOOLEAN CanBreakWindow(void); BOOLEAN CanBreakWindow(void);
BOOLEAN CanStartDrag(void); BOOLEAN CanStartDrag(void);
void StartDrag(void); void StartDrag(void);
UINT8 AnimHeight(void) const;
UINT8 AnimEndHeight(void) const;
void UpdateRobotControllerGivenController( void ); void UpdateRobotControllerGivenController( void );
void UpdateRobotControllerGivenRobot( void ); void UpdateRobotControllerGivenRobot( void );
@@ -1916,7 +1919,7 @@ public:
void CancelMultiTurnAction(BOOLEAN fFinished); void CancelMultiTurnAction(BOOLEAN fFinished);
BOOLEAN UpdateMultiTurnAction(); BOOLEAN UpdateMultiTurnAction();
void DropSectorEquipment( INT32 UnloadedSectorGridNo); void DropSectorEquipment();
// sevenfm: Take new bomb with id = usItem from iventory to HANDPOS // sevenfm: Take new bomb with id = usItem from iventory to HANDPOS
void TakeNewBombFromInventory(UINT16 usItem); void TakeNewBombFromInventory(UINT16 usItem);
@@ -2056,7 +2059,7 @@ public:
BOOLEAN CanDragPerson(SoldierID usID, BOOLEAN fCheckStance = FALSE); BOOLEAN CanDragPerson(SoldierID usID, BOOLEAN fCheckStance = FALSE);
BOOLEAN CanDragCorpse(UINT16 usCorpseNum, BOOLEAN fCheckStance = FALSE); BOOLEAN CanDragCorpse(UINT16 usCorpseNum, BOOLEAN fCheckStance = FALSE);
BOOLEAN CanDragStructure(INT32 sGridNo, BOOLEAN fCheckStance = FALSE); BOOLEAN CanDragStructure(INT32 sGridNo, BOOLEAN fCheckStance = FALSE);
BOOLEAN IsDragging(bool aStopIfConditionNotSatisfied = false); BOOLEAN IsDragging(bool aStopIfConditionNotSatisfied = true);
void SetDragOrderPerson( SoldierID usID ); void SetDragOrderPerson( SoldierID usID );
void SetDragOrderCorpse( UINT32 uiCorpseID ); void SetDragOrderCorpse( UINT32 uiCorpseID );
void SetDragOrderStructure( INT32 sGridNo ); void SetDragOrderStructure( INT32 sGridNo );
+3 -1
View File
@@ -37,8 +37,10 @@
#define TANK( p ) (p->ubBodyType == TANK_NE || p->ubBodyType == TANK_NW ) #define TANK( p ) (p->ubBodyType == TANK_NE || p->ubBodyType == TANK_NW )
#define ENEMYROBOT( p ) (p->ubBodyType == ROBOTNOWEAPON && p->bTeam == ENEMY_TEAM) #define ENEMYROBOT( p ) (p->ubBodyType == ROBOTNOWEAPON && p->bTeam == ENEMY_TEAM)
#define ARMED_VEHICLE( p ) ( TANK( p ) || COMBAT_JEEP(p) ) #define ARMED_VEHICLE( p ) ( TANK( p ) || COMBAT_JEEP(p) )
#define BOXER( p ) ( p->flags.uiStatusFlags & SOLDIER_BOXER )
#define ISVIP( p ) ( p->usSoldierFlagMask & SOLDIER_VIP )
//#define OK_ENTERABLE_VEHICLE( p ) ( ( p->flags.uiStatusFlags & SOLDIER_VEHICLE ) && !TANK( p ) && p->stats.bLife >= OKLIFE ) //#define OK_ENTERABLE_VEHICLE( p ) ( ( p->flags.uiStatusFlags & SOLDIER_VEHICLE ) && !TANK( p ) && p->stats.bLife >= OKLIFE )
#define OK_ENTERABLE_VEHICLE( p ) ( ( p->flags.uiStatusFlags & SOLDIER_VEHICLE ) && (!ARMED_VEHICLE( p ) || !(p->flags.uiStatusFlags & SOLDIER_ENEMY) ) && p->stats.bLife >= OKLIFE ) #define OK_ENTERABLE_VEHICLE( p ) ( ( p->flags.uiStatusFlags & SOLDIER_VEHICLE ) && (!ARMED_VEHICLE( p ) || !(p->flags.uiStatusFlags & SOLDIER_ENEMY) ) && p->stats.bLife >= OKLIFE )
#endif #endif
+1 -1
View File
@@ -147,7 +147,7 @@ void SoldierTooltip( SOLDIERTYPE* pSoldier )
{ {
OBJECTTYPE* pObject = &(gusSelectedSoldier->inv[HANDPOS]); OBJECTTYPE* pObject = &(gusSelectedSoldier->inv[HANDPOS]);
for (attachmentList::iterator iter = (*pObject)[0]->attachments.begin(); iter != (*pObject)[0]->attachments.end(); ++iter) { for (attachmentList::iterator iter = (*pObject)[0]->attachments.begin(); iter != (*pObject)[0]->attachments.end(); ++iter) {
if ( iter->exists() && Item[iter->usItem].visionrangebonus > 0 ) if ( Item[iter->usItem].visionrangebonus > 0 && iter->exists())
{ {
fMercIsUsingScope = TRUE; fMercIsUsingScope = TRUE;
break; break;
+2 -3
View File
@@ -1218,11 +1218,10 @@ void EndInterrupt( BOOLEAN fMarkInterruptOccurred )
else else
{ {
ubInterruptedSoldier = LATEST_INTERRUPT_GUY; ubInterruptedSoldier = LATEST_INTERRUPT_GUY;
DebugMsg( TOPIC_JA2INTERRUPT, DBG_LEVEL_3, String("INTERRUPT: interrupt over, %d's team regains control", ubInterruptedSoldier ) );
pSoldier = ubInterruptedSoldier; pSoldier = ubInterruptedSoldier;
DebugMsg( TOPIC_JA2INTERRUPT, DBG_LEVEL_3, String("INTERRUPT: interrupt over, soldier %d's team %d regains control", ubInterruptedSoldier, pSoldier->bTeam ) );
for ( SoldierID id = 0; id < MAX_NUM_SOLDIERS; ++id) for ( SoldierID id = 0; id < MAX_NUM_SOLDIERS; ++id)
{ {
pTempSoldier = id; pTempSoldier = id;
+38 -17
View File
@@ -1684,6 +1684,24 @@ void ItemCreationCallBack( UINT8 ubResult )
memset(gszMsgBoxInputString,0,sizeof(gszMsgBoxInputString)); memset(gszMsgBoxInputString,0,sizeof(gszMsgBoxInputString));
} }
static void CycleThroughTileDebugInfo()
{
const STR16 modeStrings[] =
{
L"Debug draw mode: Pathfinding",
L"Debug drawmode: Threat values",
L"Debug drawmode: Cover values",
L"Debug drawmode: Off",
};
gRenderDebugInfoMode += 1;
if (gRenderDebugInfoMode > DEBUG_OFF)
{
gRenderDebugInfoMode = 0;
}
ScreenMsg(FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, modeStrings[gRenderDebugInfoMode]);
}
extern BOOLEAN gfDisableRegionActive; extern BOOLEAN gfDisableRegionActive;
extern BOOLEAN gfUserTurnRegionActive; extern BOOLEAN gfUserTurnRegionActive;
@@ -2221,7 +2239,7 @@ void GetKeyboardInput( UINT32 *puiNewEvent )
// sevenfm: also stop dragging // sevenfm: also stop dragging
if (gusSelectedSoldier != NOBODY && if (gusSelectedSoldier != NOBODY &&
gusSelectedSoldier && gusSelectedSoldier &&
gusSelectedSoldier->IsDragging()) gusSelectedSoldier->IsDragging(false))
{ {
gusSelectedSoldier->CancelDrag(); gusSelectedSoldier->CancelDrag();
DirtyMercPanelInterface(gusSelectedSoldier, DIRTYLEVEL2); DirtyMercPanelInterface(gusSelectedSoldier, DIRTYLEVEL2);
@@ -4303,25 +4321,21 @@ void GetKeyboardInput( UINT32 *puiNewEvent )
// Make auto reload with magazines from sector inventory // Make auto reload with magazines from sector inventory
case 'R': case 'R':
if (fAlt && gusSelectedSoldier != NOBODY ) // Greysa: Moved here from 'r' to retain all functionality during testing. HandleTBReloadAll();
{
if (CHEATER_CHEAT_LEVEL())
{
ReloadWeapon(gusSelectedSoldier, gusSelectedSoldier->ubAttackingHand);
}
}
else
{
HandleTBReloadAll();
}
break; break;
case 'r': case 'r':
if( gusSelectedSoldier != NOBODY ) if( gusSelectedSoldier != NOBODY )
{ {
if( fAlt ) if( fAlt ) //reload selected merc's weapon
{ {
HandleTBReload(); if ( CHEATER_CHEAT_LEVEL( ) )
} {
ReloadWeapon( gusSelectedSoldier, gusSelectedSoldier->ubAttackingHand );
}
else
HandleTBReload();
}
else if( fCtrl ) else if( fCtrl )
{ {
if ( INFORMATION_CHEAT_LEVEL( ) ) if ( INFORMATION_CHEAT_LEVEL( ) )
@@ -4756,6 +4770,13 @@ void GetKeyboardInput( UINT32 *puiNewEvent )
break; break;
case 'Z': case 'Z':
if (fCtrl)
{
if (DEBUG_CHEAT_LEVEL())
{
CycleThroughTileDebugInfo();
}
}
break; break;
} }
@@ -6770,7 +6791,7 @@ void SwapGogglesUniformly(SOLDIERTYPE *pTeamSoldier, BOOLEAN fToNightVision)
pObj = &(pTeamSoldier->inv[gear]); pObj = &(pTeamSoldier->inv[gear]);
for (attachmentList::iterator iter = (*pObj)[0]->attachments.begin(); iter != (*pObj)[0]->attachments.end(); ++iter) for (attachmentList::iterator iter = (*pObj)[0]->attachments.begin(); iter != (*pObj)[0]->attachments.end(); ++iter)
{ {
if ( iter->exists() && Item[ iter->usItem ].nightvisionrangebonus > bestBonus && Item[ iter->usItem ].usItemClass == IC_FACE ) if ( Item[ iter->usItem ].nightvisionrangebonus > bestBonus && Item[ iter->usItem ].usItemClass == IC_FACE && iter->exists())
{ {
pGoggles = &(*iter); pGoggles = &(*iter);
bestBonus = Item[ iter->usItem ].nightvisionrangebonus; bestBonus = Item[ iter->usItem ].nightvisionrangebonus;
@@ -6818,7 +6839,7 @@ void SwapGogglesUniformly(SOLDIERTYPE *pTeamSoldier, BOOLEAN fToNightVision)
pObj = &(pTeamSoldier->inv[gear]); pObj = &(pTeamSoldier->inv[gear]);
for (attachmentList::iterator iter = (*pObj)[0]->attachments.begin(); iter != (*pObj)[0]->attachments.end(); ++iter) for (attachmentList::iterator iter = (*pObj)[0]->attachments.begin(); iter != (*pObj)[0]->attachments.end(); ++iter)
{ {
if ( iter->exists() && Item[ iter->usItem ].brightlightvisionrangebonus > bestBonus && Item[ iter->usItem ].usItemClass == IC_FACE ) if ( Item[ iter->usItem ].brightlightvisionrangebonus > bestBonus && Item[ iter->usItem ].usItemClass == IC_FACE && iter->exists())
{ {
pGoggles = &(*iter); pGoggles = &(*iter);
bestBonus = Item[ iter->usItem ].brightlightvisionrangebonus; bestBonus = Item[ iter->usItem ].brightlightvisionrangebonus;
+1 -1
View File
@@ -2081,7 +2081,7 @@ UINT8 HandleNonActivatedTossCursor( SOLDIERTYPE *pSoldier, INT32 sGridNo, BOOLEA
//sevenfm this should be checked only for guns because we can throw items with attached explosives //sevenfm this should be checked only for guns because we can throw items with attached explosives
if(Item[pObj->usItem].usItemClass & IC_WEAPON ) if(Item[pObj->usItem].usItemClass & IC_WEAPON )
for (attachmentList::iterator iter = (*pObj)[0]->attachments.begin(); iter != (*pObj)[0]->attachments.end(); ++iter) { for (attachmentList::iterator iter = (*pObj)[0]->attachments.begin(); iter != (*pObj)[0]->attachments.end(); ++iter) {
if ( iter->exists() && Item[ iter->usItem ].usItemClass & IC_EXPLOSV ) if ( Item[ iter->usItem ].usItemClass & IC_EXPLOSV && iter->exists())
{ {
pObject = &(*iter); pObject = &(*iter);
break; break;
+25 -23
View File
@@ -1036,7 +1036,7 @@ INT32 EffectiveArmour( OBJECTTYPE * pObj )
iValue = iValue * (*pObj)[0]->data.objectStatus * Armour[ Item[pObj->usItem].ubClassIndex ].ubCoverage / 10000; iValue = iValue * (*pObj)[0]->data.objectStatus * Armour[ Item[pObj->usItem].ubClassIndex ].ubCoverage / 10000;
for (attachmentList::iterator iter = (*pObj)[0]->attachments.begin(); iter != (*pObj)[0]->attachments.end(); ++iter) { for (attachmentList::iterator iter = (*pObj)[0]->attachments.begin(); iter != (*pObj)[0]->attachments.end(); ++iter) {
if ( iter->exists() && Item[iter->usItem].usItemClass == IC_ARMOUR && (*iter)[0]->data.objectStatus > 0 ) if (Item[iter->usItem].usItemClass == IC_ARMOUR && (*iter)[0]->data.objectStatus > 0 && iter->exists())
{ {
INT32 iValue2; INT32 iValue2;
@@ -1062,7 +1062,7 @@ INT32 EffectiveArmourLBE( OBJECTTYPE * pObj )
iValue = 0; iValue = 0;
for (attachmentList::iterator iter = (*pObj)[0]->attachments.begin(); iter != (*pObj)[0]->attachments.end(); ++iter) { for (attachmentList::iterator iter = (*pObj)[0]->attachments.begin(); iter != (*pObj)[0]->attachments.end(); ++iter) {
if ( iter->exists() && Item[iter->usItem].usItemClass == IC_ARMOUR && (*iter)[0]->data.objectStatus > 0 ) if (Item[iter->usItem].usItemClass == IC_ARMOUR && (*iter)[0]->data.objectStatus > 0 )
{ {
INT32 iValue2; INT32 iValue2;
@@ -1088,7 +1088,7 @@ INT32 ExplosiveEffectiveArmourLBE( OBJECTTYPE * pObj )
iValue = 0; iValue = 0;
for (attachmentList::iterator iter = (*pObj)[0]->attachments.begin(); iter != (*pObj)[0]->attachments.end(); ++iter) { for (attachmentList::iterator iter = (*pObj)[0]->attachments.begin(); iter != (*pObj)[0]->attachments.end(); ++iter) {
if ( iter->exists() && Item[iter->usItem].usItemClass == IC_ARMOUR && (*iter)[0]->data.objectStatus > 0 ) if (Item[iter->usItem].usItemClass == IC_ARMOUR && (*iter)[0]->data.objectStatus > 0 )
{ {
INT32 iValue2; INT32 iValue2;
@@ -1214,7 +1214,7 @@ INT32 ExplosiveEffectiveArmour( OBJECTTYPE * pObj )
} }
for (attachmentList::iterator iter = (*pObj)[0]->attachments.begin(); iter != (*pObj)[0]->attachments.end(); ++iter) { for (attachmentList::iterator iter = (*pObj)[0]->attachments.begin(); iter != (*pObj)[0]->attachments.end(); ++iter) {
if ( iter->exists() && Item[iter->usItem].usItemClass == IC_ARMOUR && (*iter)[0]->data.objectStatus > 0 ) if (Item[iter->usItem].usItemClass == IC_ARMOUR && (*iter)[0]->data.objectStatus > 0 && iter->exists())
{ {
INT32 iValue2; INT32 iValue2;
@@ -1295,7 +1295,7 @@ INT16 FireEffectiveArmour( OBJECTTYPE * pObj )
for ( attachmentList::iterator iter = ( *pObj )[0]->attachments.begin(); iter != ( *pObj )[0]->attachments.end(); ++iter ) for ( attachmentList::iterator iter = ( *pObj )[0]->attachments.begin(); iter != ( *pObj )[0]->attachments.end(); ++iter )
{ {
if ( iter->exists() && Item[iter->usItem].usItemClass == IC_ARMOUR && ( *iter )[0]->data.objectStatus > 0 ) if ( Item[iter->usItem].usItemClass == IC_ARMOUR && ( *iter )[0]->data.objectStatus > 0 && iter->exists() )
{ {
iValue += Item[iter->usItem].sFireResistance * ( *iter )[0]->data.objectStatus / 100; iValue += Item[iter->usItem].sFireResistance * ( *iter )[0]->data.objectStatus / 100;
} }
@@ -2560,7 +2560,7 @@ BOOLEAN UseGunNCTH( SOLDIERTYPE *pSoldier , INT32 sTargetGridNo )
{ {
for (attachmentList::iterator iter = (*pObjAttHand)[0]->attachments.begin(); iter != (*pObjAttHand)[0]->attachments.end(); ++iter) for (attachmentList::iterator iter = (*pObjAttHand)[0]->attachments.begin(); iter != (*pObjAttHand)[0]->attachments.end(); ++iter)
{ {
if ( iter->exists() && ItemHasHiddenMuzzleFlash(iter->usItem)) if (ItemHasHiddenMuzzleFlash(iter->usItem))
{ {
OBJECTTYPE* pA= &(*iter); OBJECTTYPE* pA= &(*iter);
if ( (*pA)[0]->data.objectStatus >=USABLE) if ( (*pA)[0]->data.objectStatus >=USABLE)
@@ -3346,7 +3346,7 @@ BOOLEAN UseGun( SOLDIERTYPE *pSoldier , INT32 sTargetGridNo )
{ {
for (attachmentList::iterator iter = (*pObjUsed)[0]->attachments.begin(); iter != (*pObjUsed)[0]->attachments.end(); ++iter) for (attachmentList::iterator iter = (*pObjUsed)[0]->attachments.begin(); iter != (*pObjUsed)[0]->attachments.end(); ++iter)
{ {
if ( iter->exists() && ItemHasHiddenMuzzleFlash(iter->usItem)) if (ItemHasHiddenMuzzleFlash(iter->usItem))
{ {
OBJECTTYPE* pA= &(*iter); OBJECTTYPE* pA= &(*iter);
if ( (*pA)[0]->data.objectStatus >=USABLE) if ( (*pA)[0]->data.objectStatus >=USABLE)
@@ -4233,7 +4233,7 @@ BOOLEAN UseHandToHand( SOLDIERTYPE *pSoldier, INT32 sTargetGridNo, BOOLEAN fStea
// give bonus for surprise, but not so much as struggle would still occur // give bonus for surprise, but not so much as struggle would still occur
iHitChance = CalcChanceToSteal( pSoldier, pTargetSoldier, pSoldier->aiData.bAimTime ) + 20; iHitChance = CalcChanceToSteal( pSoldier, pTargetSoldier, pSoldier->aiData.bAimTime ) + 20;
} }
else if (pTargetSoldier->stats.bLife < OKLIFE || pTargetSoldier->bCollapsed || pTargetSoldier->usSoldierFlagMask & SOLDIER_POW) else if ( pTargetSoldier->stats.bLife < OKLIFE || pTargetSoldier->bCollapsed )
{ {
iHitChance = 100; iHitChance = 100;
fSoldierCollapsed = TRUE; fSoldierCollapsed = TRUE;
@@ -4257,10 +4257,10 @@ BOOLEAN UseHandToHand( SOLDIERTYPE *pSoldier, INT32 sTargetGridNo, BOOLEAN fStea
// sevenfm: bonus for boxers for attack from the back // sevenfm: bonus for boxers for attack from the back
if (iHitChance < 100 && if (iHitChance < 100 &&
(pSoldier->flags.uiStatusFlags & SOLDIER_BOXER) && BOXER(pSoldier) &&
!pSoldier->bBlindedCounter && !pSoldier->bBlindedCounter &&
gAnimControl[pTargetSoldier->usAnimState].ubEndHeight > ANIM_PRONE && gAnimControl[pTargetSoldier->usAnimState].ubEndHeight > ANIM_PRONE &&
(pTargetSoldier->flags.uiStatusFlags & SOLDIER_BOXER) && BOXER(pTargetSoldier) &&
pTargetSoldier->usSoldierFlagMask2 & SOLDIER_BACK_ATTACK) pTargetSoldier->usSoldierFlagMask2 & SOLDIER_BACK_ATTACK)
{ {
iHitChance += (100 - iHitChance) / 2; iHitChance += (100 - iHitChance) / 2;
@@ -4876,7 +4876,7 @@ BOOLEAN UseHandToHand( SOLDIERTYPE *pSoldier, INT32 sTargetGridNo, BOOLEAN fStea
if (pTargetSoldier->bActionPoints > 0 && if (pTargetSoldier->bActionPoints > 0 &&
gGameOptions.fNewTraitSystem && gGameOptions.fNewTraitSystem &&
gTacticalStatus.bBoxingState == BOXING && gTacticalStatus.bBoxingState == BOXING &&
(pTargetSoldier->flags.uiStatusFlags & SOLDIER_BOXER) && BOXER(pTargetSoldier) &&
Chance(ubCounterattackChance) && Chance(ubCounterattackChance) &&
IS_MERC_BODY_TYPE(pSoldier) && IS_MERC_BODY_TYPE(pSoldier) &&
IS_MERC_BODY_TYPE(pTargetSoldier) && IS_MERC_BODY_TYPE(pTargetSoldier) &&
@@ -5111,7 +5111,7 @@ BOOLEAN UseLauncher( SOLDIERTYPE *pSoldier, INT32 sTargetGridNo )
pObj = &(pSoldier->inv[HANDPOS]); pObj = &(pSoldier->inv[HANDPOS]);
attachmentList::iterator iter; attachmentList::iterator iter;
for (iter = (*pObj)[0]->attachments.begin(); iter != (*pObj)[0]->attachments.end(); ++iter) { for (iter = (*pObj)[0]->attachments.begin(); iter != (*pObj)[0]->attachments.end(); ++iter) {
if ( iter->exists() && Item[ iter->usItem ].usItemClass & IC_EXPLOSV ) if ( Item[ iter->usItem ].usItemClass & IC_EXPLOSV && iter->exists())
{ {
break; break;
} }
@@ -6089,7 +6089,7 @@ void StructureHit( INT32 iBullet, UINT16 usWeaponIndex, INT16 bWeaponStatus, Sol
} }
} }
void WindowHit( INT32 sGridNo, UINT16 usStructureID, BOOLEAN fBlowWindowSouth, BOOLEAN fLargeForce ) void WindowHit( INT32 sGridNo, UINT16 usStructureID, BOOLEAN fBlowWindowSouth, BOOLEAN fLargeForce, BOOLEAN fSound)
{ {
STRUCTURE * pWallAndWindow; STRUCTURE * pWallAndWindow;
DB_STRUCTURE * pWallAndWindowInDB; DB_STRUCTURE * pWallAndWindowInDB;
@@ -6210,10 +6210,12 @@ void WindowHit( INT32 sGridNo, UINT16 usStructureID, BOOLEAN fBlowWindowSouth, B
pNode = CreateAnimationTile( &AniParams ); pNode = CreateAnimationTile( &AniParams );
//ddd window{ //ddd window{
CompileWorldMovementCosts(); CompileWorldMovementCosts();
//ddd window} //ddd window}
PlayJA2Sample( GLASS_SHATTER1 + Random(2), RATE_11025, MIDVOLUME, 1, SoundDir( sGridNo ) ); if ( fSound )
{
PlayJA2Sample(GLASS_SHATTER1 + Random(2), RATE_11025, MIDVOLUME, 1, SoundDir(sGridNo));
}
} }
@@ -7993,7 +7995,7 @@ INT32 TotalArmourProtection( SOLDIERTYPE * pTarget, UINT8 ubHitLocation, INT32 i
if (iSlot == VESTPOS && pVestPack->exists() == true) if (iSlot == VESTPOS && pVestPack->exists() == true)
{ {
for (attachmentList::iterator iter = (*pVestPack)[0]->attachments.begin(); iter != (*pVestPack)[0]->attachments.end(); ++iter) { for (attachmentList::iterator iter = (*pVestPack)[0]->attachments.begin(); iter != (*pVestPack)[0]->attachments.end(); ++iter) {
if ( iter->exists() && Item[iter->usItem].usItemClass == IC_ARMOUR && (*iter)[0]->data.objectStatus > 0 ) if (Item[iter->usItem].usItemClass == IC_ARMOUR && (*iter)[0]->data.objectStatus > 0 )
{ {
INT32 protection = ArmourProtection( pTarget, Item[iter->usItem].ubClassIndex, &((*iter)[0]->data.objectStatus), iImpact, ubAmmoType, &plateHit ); INT32 protection = ArmourProtection( pTarget, Item[iter->usItem].ubClassIndex, &((*iter)[0]->data.objectStatus), iImpact, ubAmmoType, &plateHit );
@@ -8025,7 +8027,7 @@ INT32 TotalArmourProtection( SOLDIERTYPE * pTarget, UINT8 ubHitLocation, INT32 i
if (pArmour->exists() == true) if (pArmour->exists() == true)
{ {
for (attachmentList::iterator iter = (*pArmour)[0]->attachments.begin(); iter != (*pArmour)[0]->attachments.end(); ++iter) { for (attachmentList::iterator iter = (*pArmour)[0]->attachments.begin(); iter != (*pArmour)[0]->attachments.end(); ++iter) {
if ( iter->exists() && Item[iter->usItem].usItemClass == IC_ARMOUR && (*iter)[0]->data.objectStatus > 0 ) if (Item[iter->usItem].usItemClass == IC_ARMOUR && (*iter)[0]->data.objectStatus > 0 && iter->exists())
{ {
// bullet got through jacket; apply ceramic plate armour // bullet got through jacket; apply ceramic plate armour
INT32 protection = ArmourProtection( pTarget, Item[iter->usItem].ubClassIndex, &((*iter)[0]->data.objectStatus), iImpact, ubAmmoType, &plateHit ); INT32 protection = ArmourProtection( pTarget, Item[iter->usItem].ubClassIndex, &((*iter)[0]->data.objectStatus), iImpact, ubAmmoType, &plateHit );
@@ -8071,7 +8073,7 @@ INT32 TotalArmourProtection( SOLDIERTYPE * pTarget, UINT8 ubHitLocation, INT32 i
{ {
//Madd: put any attachments that someone might have added to the armour in the merc's inventory //Madd: put any attachments that someone might have added to the armour in the merc's inventory
for (attachmentList::iterator iter = (*pArmour)[0]->attachments.begin(); iter != (*pArmour)[0]->attachments.end(); ++iter) { for (attachmentList::iterator iter = (*pArmour)[0]->attachments.begin(); iter != (*pArmour)[0]->attachments.end(); ++iter) {
if ( iter->exists() && !AutoPlaceObject( pTarget, &(*iter), FALSE ) ) if ( !AutoPlaceObject( pTarget, &(*iter), FALSE ) && iter->exists())
{ // put it on the ground { // put it on the ground
AddItemToPool( pTarget->sGridNo, &(*iter), 1, pTarget->pathing.bLevel, 0 , -1 ); AddItemToPool( pTarget->sGridNo, &(*iter), 1, pTarget->pathing.bLevel, 0 , -1 );
} }
@@ -9723,7 +9725,7 @@ UINT32 CalcChanceHTH( SOLDIERTYPE * pAttacker,SOLDIERTYPE *pDefender, INT16 ubAi
{ {
// Changed from DG by CJC to give higher chances of hitting with a stab or punch // Changed from DG by CJC to give higher chances of hitting with a stab or punch
// sevenfm: lowered chance for boxers // sevenfm: lowered chance for boxers
if (pAttacker->flags.uiStatusFlags & SOLDIER_BOXER) if (BOXER(pAttacker))
iChance = 50 + (iAttRating - iDefRating) / 3; iChance = 50 + (iAttRating - iDefRating) / 3;
else else
iChance = 67 + (iAttRating - iDefRating) / 3; iChance = 67 + (iAttRating - iDefRating) / 3;
@@ -9778,8 +9780,8 @@ UINT32 CalcChanceHTH( SOLDIERTYPE * pAttacker,SOLDIERTYPE *pDefender, INT16 ubAi
// sevenfm: bonus for boxers for attacking from the back // sevenfm: bonus for boxers for attacking from the back
if (ubMode == HTH_MODE_PUNCH && if (ubMode == HTH_MODE_PUNCH &&
(pAttacker->flags.uiStatusFlags & SOLDIER_BOXER) && BOXER(pAttacker) &&
(pDefender->flags.uiStatusFlags & SOLDIER_BOXER) && BOXER(pDefender) &&
iChance < 100 && iChance < 100 &&
!pAttacker->bBlindedCounter && !pAttacker->bBlindedCounter &&
gAnimControl[pDefender->usAnimState].ubEndHeight > ANIM_PRONE && gAnimControl[pDefender->usAnimState].ubEndHeight > ANIM_PRONE &&
+1 -1
View File
@@ -456,7 +456,7 @@ INT16 ArmourVersusFirePercent( SOLDIERTYPE * pSoldier );
extern BOOLEAN FireWeapon( SOLDIERTYPE *pSoldier , INT32 sTargetGridNo ); extern BOOLEAN FireWeapon( SOLDIERTYPE *pSoldier , INT32 sTargetGridNo );
extern void WeaponHit( SoldierID usSoldierID, UINT16 usWeaponIndex, INT16 sDamage, INT16 sBreathLoss, UINT16 usDirection, INT16 sXPos, INT16 sYPos, INT16 sZPos, INT16 sRange, SoldierID ubAttackerID, BOOLEAN fHit, UINT8 ubSpecial, UINT8 ubHitLocation ); extern void WeaponHit( SoldierID usSoldierID, UINT16 usWeaponIndex, INT16 sDamage, INT16 sBreathLoss, UINT16 usDirection, INT16 sXPos, INT16 sYPos, INT16 sZPos, INT16 sRange, SoldierID ubAttackerID, BOOLEAN fHit, UINT8 ubSpecial, UINT8 ubHitLocation );
extern void StructureHit( INT32 iBullet, UINT16 usWeaponIndex, INT16 bWeaponStatus, SoldierID ubAttackerID, UINT16 sXPos, INT16 sYPos, INT16 sZPos, UINT16 usStructureID, INT32 iImpact, BOOLEAN fStopped ); extern void StructureHit( INT32 iBullet, UINT16 usWeaponIndex, INT16 bWeaponStatus, SoldierID ubAttackerID, UINT16 sXPos, INT16 sYPos, INT16 sZPos, UINT16 usStructureID, INT32 iImpact, BOOLEAN fStopped );
extern void WindowHit( INT32 sGridNo, UINT16 usStructureID, BOOLEAN fBlowWindowSouth, BOOLEAN fLargeForce ); void WindowHit(INT32 sGridNo, UINT16 usStructureID, BOOLEAN fBlowWindowSouth, BOOLEAN fLargeForce, BOOLEAN fSound = TRUE);
// HEADROCK HAM 5.1: Moved to Bullets.h // HEADROCK HAM 5.1: Moved to Bullets.h
extern BOOLEAN InRange( SOLDIERTYPE *pSoldier, INT32 sGridNo ); extern BOOLEAN InRange( SOLDIERTYPE *pSoldier, INT32 sGridNo );
extern void ShotMiss( SoldierID ubAttackerID, INT32 iBullet ); extern void ShotMiss( SoldierID ubAttackerID, INT32 iBullet );
+1 -1
View File
@@ -1180,7 +1180,7 @@ profileEndElementHandle(void *userData, const XML_Char *name)
#include <vfs/Core/vfs_file_raii.h> // added by Flugente for vfs-stuff #include <vfs/Core/vfs_file_raii.h> // added by Flugente for vfs-stuff
#include <iostream> #include <iostream>
extern STR16* gzMercSkillTextNew; extern STR16 gzMercSkillTextNew[];
void AnalyzeProfiles() void AnalyzeProfiles()
{ {
+5 -4
View File
@@ -4,7 +4,7 @@
#include "Overhead.h" #include "Overhead.h"
#include "random.h" #include "random.h"
#include "Points.h" #include "Points.h"
#include "ai.h"
#include <iostream> #include <iostream>
#include <iomanip> #include <iomanip>
@@ -190,7 +190,6 @@ typedef enum
INT16 AdvanceToFiringRange( SOLDIERTYPE * pSoldier, INT16 sClosestOpponent ); INT16 AdvanceToFiringRange( SOLDIERTYPE * pSoldier, INT16 sClosestOpponent );
BOOLEAN AimingGun(SOLDIERTYPE *pSoldier);
void CalcBestShot(SOLDIERTYPE *pSoldier, ATTACKTYPE *pBestShot); void CalcBestShot(SOLDIERTYPE *pSoldier, ATTACKTYPE *pBestShot);
void CalcBestStab(SOLDIERTYPE *pSoldier, ATTACKTYPE *pBestStab, BOOLEAN fBladeAttack); void CalcBestStab(SOLDIERTYPE *pSoldier, ATTACKTYPE *pBestStab, BOOLEAN fBladeAttack);
void CalcBestThrow(SOLDIERTYPE *pSoldier, ATTACKTYPE *pBestThrow); void CalcBestThrow(SOLDIERTYPE *pSoldier, ATTACKTYPE *pBestThrow);
@@ -223,7 +222,6 @@ INT8 ArmedVehicleDecideAction( SOLDIERTYPE* pSoldier );
// a variant of ClosestSeenOpponent(...), that allows to find enemies on a roof // a variant of ClosestSeenOpponent(...), that allows to find enemies on a roof
INT32 ClosestSeenOpponentWithRoof(SOLDIERTYPE *pSoldier, INT32 * psGridNo, INT8 * pbLevel); INT32 ClosestSeenOpponentWithRoof(SOLDIERTYPE *pSoldier, INT32 * psGridNo, INT8 * pbLevel);
INT8 CrowDecideAction( SOLDIERTYPE * pSoldier );
void DecideAlertStatus( SOLDIERTYPE *pSoldier ); void DecideAlertStatus( SOLDIERTYPE *pSoldier );
INT8 DecideAutoBandage( SOLDIERTYPE * pSoldier ); INT8 DecideAutoBandage( SOLDIERTYPE * pSoldier );
UINT16 DetermineMovementMode( SOLDIERTYPE * pSoldier, INT8 bAction ); UINT16 DetermineMovementMode( SOLDIERTYPE * pSoldier, INT8 bAction );
@@ -241,9 +239,12 @@ INT32 GetInterveningClimbingLocation( SOLDIERTYPE * pSoldier, INT32 sDestGridNo,
UINT8 GetTraversalQuoteActionID( INT8 bDirection ); UINT8 GetTraversalQuoteActionID( INT8 bDirection );
INT32 GoAsFarAsPossibleTowards(SOLDIERTYPE *pSoldier, INT32 sDesGrid, INT8 bAction); INT32 GoAsFarAsPossibleTowards(SOLDIERTYPE *pSoldier, INT32 sDesGrid, INT8 bAction);
INT8 HeadForTheStairCase( SOLDIERTYPE * pSoldier ); ActionType HeadForTheStairCase( SOLDIERTYPE * pSoldier );
BOOLEAN InSmoke(INT32 sGridNo, INT8 bLevel); BOOLEAN InSmoke(INT32 sGridNo, INT8 bLevel);
BOOLEAN InSmoke(SOLDIERTYPE* pSoldier, INT32 sGridNo);
BOOLEAN InTearGas(SOLDIERTYPE* pSoldier, INT32 sGridNo);
BOOLEAN InMustardGas(SOLDIERTYPE* pSoldier, INT32 sGridNo);
BOOLEAN InGas( SOLDIERTYPE *pSoldier, INT32 sGridNo ); BOOLEAN InGas( SOLDIERTYPE *pSoldier, INT32 sGridNo );
BOOLEAN InGasOrSmoke( SOLDIERTYPE *pSoldier, INT32 sGridNo ); BOOLEAN InGasOrSmoke( SOLDIERTYPE *pSoldier, INT32 sGridNo );
BOOLEAN InWaterGasOrSmoke( SOLDIERTYPE *pSoldier, INT32 sGridNo ); BOOLEAN InWaterGasOrSmoke( SOLDIERTYPE *pSoldier, INT32 sGridNo );
+2 -1
View File
@@ -18,6 +18,7 @@
#include "opplist.h" #include "opplist.h"
#include "Interface.h" #include "Interface.h"
#include "Tactical Save.h" #include "Tactical Save.h"
#include <Soldier macros.h>
#define AI_LIST_SIZE TOTAL_SOLDIERS #define AI_LIST_SIZE TOTAL_SOLDIERS
@@ -180,7 +181,7 @@ BOOLEAN InsertIntoAIList( SoldierID ubID, INT8 bPriority )
BOOLEAN SatisfiesAIListConditions( SOLDIERTYPE * pSoldier, UINT16 * pubDoneCount, BOOLEAN fDoRandomChecks ) BOOLEAN SatisfiesAIListConditions( SOLDIERTYPE * pSoldier, UINT16 * pubDoneCount, BOOLEAN fDoRandomChecks )
{ {
if ( (gTacticalStatus.bBoxingState == BOXING) && !(pSoldier->flags.uiStatusFlags & SOLDIER_BOXER) ) if ( (gTacticalStatus.bBoxingState == BOXING) && !BOXER(pSoldier) )
{ {
return( FALSE ); return( FALSE );
} }
+328 -107
View File
@@ -49,6 +49,7 @@
#include "Soldier Functions.h" // added by SANDRO #include "Soldier Functions.h" // added by SANDRO
#include "Text.h" // sevenfm #include "Text.h" // sevenfm
#include "english.h" // sevenfm: for ESC key #include "english.h" // sevenfm: for ESC key
#include "Food.h"
#include "connect.h" #include "connect.h"
// needed to use the modularized tactical AI: // needed to use the modularized tactical AI:
@@ -206,18 +207,96 @@ STR szAction[] = {
"AI_ACTION_STOP_MEDIC" "AI_ACTION_STOP_MEDIC"
}; };
STR16 wszAction[] = {
L"AI_ACTION_NONE",
L"AI_ACTION_RANDOM_PATROL",
L"AI_ACTION_SEEK_FRIEND",
L"AI_ACTION_SEEK_OPPONENT",
L"AI_ACTION_TAKE_COVER",
L"AI_ACTION_GET_CLOSER",
L"AI_ACTION_POINT_PATROL",
L"AI_ACTION_LEAVE_WATER_GAS",
L"AI_ACTION_SEEK_NOISE",
L"AI_ACTION_ESCORTED_MOVE",
L"AI_ACTION_RUN_AWAY",
L"AI_ACTION_KNIFE_MOVE",
L"AI_ACTION_APPROACH_MERC",
L"AI_ACTION_TRACK",
L"AI_ACTION_EAT",
L"AI_ACTION_PICKUP_ITEM",
L"AI_ACTION_SCHEDULE_MOVE",
L"AI_ACTION_WALK",
L"AI_ACTION_RUN",
L"AI_ACTION_WITHDRAW",
L"AI_ACTION_FLANK_LEFT",
L"AI_ACTION_FLANK_RIGHT",
L"AI_ACTION_MOVE_TO_CLIMB",
L"AI_ACTION_CHANGE_FACING",
L"AI_ACTION_CHANGE_STANCE",
L"AI_ACTION_YELLOW_ALERT",
L"AI_ACTION_RED_ALERT",
L"AI_ACTION_CREATURE_CALL",
L"AI_ACTION_PULL_TRIGGER",
L"AI_ACTION_USE_DETONATOR",
L"AI_ACTION_FIRE_GUN",
L"AI_ACTION_TOSS_PROJECTILE",
L"AI_ACTION_KNIFE_STAB",
L"AI_ACTION_THROW_KNIFE",
L"AI_ACTION_GIVE_AID",
L"AI_ACTION_WAIT",
L"AI_ACTION_PENDING_ACTION",
L"AI_ACTION_DROP_ITEM",
L"AI_ACTION_COWER",
L"AI_ACTION_STOP_COWERING",
L"AI_ACTION_OPEN_OR_CLOSE_DOOR",
L"AI_ACTION_UNLOCK_DOOR",
L"AI_ACTION_LOCK_DOOR",
L"AI_ACTION_LOWER_GUN",
L"AI_ACTION_ABSOLUTELY_NONE",
L"AI_ACTION_CLIMB_ROOF",
L"AI_ACTION_END_TURN",
L"AI_ACTION_END_COWER_AND_MOVE",
L"AI_ACTION_TRAVERSE_DOWN",
L"AI_ACTION_OFFER_SURRENDER",
L"AI_ACTION_RAISE_GUN",
L"AI_ACTION_STEAL_MOVE",
L"AI_ACTION_RELOAD_GUN",
L"AI_ACTION_JUMP_WINDOW",
L"AI_ACTION_FREE_PRISONER",
L"AI_ACTION_USE_SKILL",
L"AI_ACTION_DOCTOR",
L"AI_ACTION_DOCTOR_SELF",
L"AI_ACTION_SELFDETONATE",
L"AI_ACTION_STOP_MEDIC"
};
// sevenfm // sevenfm
UINT32 guiAIStartCounter = 0, guiAILastCounter = 0; UINT32 guiAIStartCounter = 0, guiAILastCounter = 0;
//UINT8 gubAISelectedSoldier = NOBODY; //UINT8 gubAISelectedSoldier = NOBODY;
BOOLEAN gfLogsEnabled = TRUE; BOOLEAN gfLogsEnabled = TRUE;
bool gLogDecideActionRed = true;
bool gLogDecideActionBlack = true;
void DebugAI( INT8 bMsgType, SOLDIERTYPE *pSoldier, STR szOutput, INT8 bAction ) void DebugAI( INT8 bMsgType, SOLDIERTYPE *pSoldier, STR szOutput, bool doLog, INT8 bAction)
{ {
FILE* DebugFile; FILE* DebugFile;
CHAR8 msg[1024]; CHAR8 msg[1024];
CHAR8 buf[1024]; CHAR8 buf[1024];
if (!gfLogsEnabled || pSoldier == nullptr) if (!gfTurnBasedAI || !gfLogsEnabled || !doLog || pSoldier == nullptr)
return; return;
memset(buf, 0, 1024 * sizeof(char)); memset(buf, 0, 1024 * sizeof(char));
@@ -258,7 +337,7 @@ void DebugAI( INT8 bMsgType, SOLDIERTYPE *pSoldier, STR szOutput, INT8 bAction )
strcat(msg, buf); strcat(msg, buf);
} }
if (bAction >= AI_ACTION_NONE && bAction <= AI_ACTION_LAST) if (bAction >= AI_ACTION_NONE && bAction < AI_ACTION_INVALID)
{ {
strcat(msg, " "); strcat(msg, " ");
strcat(msg, szAction[bAction]); strcat(msg, szAction[bAction]);
@@ -301,16 +380,19 @@ void DebugAI( INT8 bMsgType, SOLDIERTYPE *pSoldier, STR szOutput, INT8 bAction )
} }
// also log to individual file for selected soldier // also log to individual file for selected soldier
sprintf(buf, "Logs\\AI_Decisions [%d].txt", pSoldier->ubID.i); if (pSoldier)
if ((DebugFile = fopen(buf, "a+t")) != NULL)
{ {
if (bMsgType == AI_MSG_START) sprintf(buf, "Logs\\AI_Decisions [%d].txt", pSoldier->ubID.i);
if ((DebugFile = fopen(buf, "a+t")) != NULL)
{ {
if (bMsgType == AI_MSG_START)
{
fputs("\n", DebugFile);
}
fputs(msg, DebugFile);
fputs("\n", DebugFile); fputs("\n", DebugFile);
fclose(DebugFile);
} }
fputs(msg, DebugFile);
fputs("\n", DebugFile);
fclose(DebugFile);
} }
} }
@@ -354,6 +436,27 @@ void DebugQuestInfo(STR szOutput)
} }
} }
static INT16 ShouldActionStayInProgress(SOLDIERTYPE* pSoldier)
{
// this here should never happen, but it seems to (turns sometimes hang!)
if ((pSoldier->aiData.bAction == AI_ACTION_CHANGE_FACING) && (pSoldier->pathing.bDesiredDirection != pSoldier->aiData.usActionData))
{
// don't try to pay any more APs for this, it was paid for once already!
pSoldier->pathing.bDesiredDirection = (INT8)pSoldier->aiData.usActionData; // turn to face direction in actionData
return(TRUE);
}
else if ((pSoldier->aiData.bAction == AI_ACTION_CHANGE_FACING) && (pSoldier->pathing.bDesiredDirection == pSoldier->aiData.usActionData))
{
return(FALSE);
}
else if (pSoldier->aiData.bAction == AI_ACTION_END_TURN || pSoldier->aiData.bAction == AI_ACTION_NONE)
{
return(FALSE);
}
// needs more time to complete action
return(TRUE);
}
BOOLEAN InitAI( void ) BOOLEAN InitAI( void )
{ {
@@ -361,13 +464,6 @@ BOOLEAN InitAI( void )
FILE * DebugFile; FILE * DebugFile;
#endif #endif
#ifdef _DEBUG
if (gfDisplayCoverValues)
{
//memset( gsCoverValue, 0x7F, sizeof( INT16 ) * WORLD_MAX );
}
#endif
//If we are not loading a saved game ( if we are, this has already been called ) //If we are not loading a saved game ( if we are, this has already been called )
if( !( gTacticalStatus.uiFlags & LOADING_SAVED_GAME ) ) if( !( gTacticalStatus.uiFlags & LOADING_SAVED_GAME ) )
{ {
@@ -399,10 +495,6 @@ BOOLEAN InitAI( void )
return( TRUE ); return( TRUE );
} }
BOOLEAN AimingGun(SOLDIERTYPE *pSoldier)
{
return(FALSE);
}
void HandleSoldierAI( SOLDIERTYPE *pSoldier ) // FIXME - this function is named inappropriately void HandleSoldierAI( SOLDIERTYPE *pSoldier ) // FIXME - this function is named inappropriately
{ {
@@ -645,6 +737,9 @@ void HandleSoldierAI( SOLDIERTYPE *pSoldier ) // FIXME - this function is named
{ {
// traversing offmap, ignore new situations // traversing offmap, ignore new situations
} }
// FIXME: Disabled temporarily to prevent AI actions constantly being cancelled during normal turn based combat.
// Need to find out when this conditional is actually needed.
#if 0
else if ( pSoldier->ubQuoteRecord == 0 && !gTacticalStatus.fAutoBandageMode ) else if ( pSoldier->ubQuoteRecord == 0 && !gTacticalStatus.fAutoBandageMode )
{ {
// don't force, don't want escorted mercs reacting to new opponents, etc. // don't force, don't want escorted mercs reacting to new opponents, etc.
@@ -658,6 +753,7 @@ void HandleSoldierAI( SOLDIERTYPE *pSoldier ) // FIXME - this function is named
} }
DecideAlertStatus( pSoldier ); DecideAlertStatus( pSoldier );
} }
#endif
else else
{ {
if ( pSoldier->ubQuoteRecord ) if ( pSoldier->ubQuoteRecord )
@@ -665,6 +761,15 @@ void HandleSoldierAI( SOLDIERTYPE *pSoldier ) // FIXME - this function is named
// make sure we're not using combat AI // make sure we're not using combat AI
pSoldier->aiData.bAlertStatus = STATUS_GREEN; pSoldier->aiData.bAlertStatus = STATUS_GREEN;
} }
// Prevent AI deadlocking in case enemy is performing an action and player gets an interrupt.
// Without this, if player doesn't move any mercs, the AI soldier will wait until the deadlock is broken.
// By canceling the AI action, the AI can then reconsider actions.
//if (pSoldier->aiData.bAction == AI_ACTION_FIRE_GUN || pSoldier->aiData.bAction == AI_ACTION_KNIFE_MOVE || pSoldier->aiData.bAction == AI_ACTION_STEAL_MOVE || pSoldier->aiData.bAction == AI_ACTION_KNIFE_STAB)
{
DebugAI(AI_MSG_INFO, pSoldier, String("New Situation"));
CancelAIAction(pSoldier, FALSE);
}
pSoldier->aiData.bNewSituation = WAS_NEW_SITUATION; pSoldier->aiData.bNewSituation = WAS_NEW_SITUATION;
} }
} }
@@ -674,7 +779,6 @@ void HandleSoldierAI( SOLDIERTYPE *pSoldier ) // FIXME - this function is named
// might have been in 'was' state; no longer so... // might have been in 'was' state; no longer so...
pSoldier->aiData.bNewSituation = NOT_NEW_SITUATION; pSoldier->aiData.bNewSituation = NOT_NEW_SITUATION;
} }
#ifdef TESTAI #ifdef TESTAI
DebugMsg( TOPIC_JA2AI, DBG_LEVEL_3,String( ".... HANDLING AI FOR %d",pSoldier->ubID)); DebugMsg( TOPIC_JA2AI, DBG_LEVEL_3,String( ".... HANDLING AI FOR %d",pSoldier->ubID));
#endif #endif
@@ -682,42 +786,79 @@ void HandleSoldierAI( SOLDIERTYPE *pSoldier ) // FIXME - this function is named
/********* /*********
Start of new overall AI system Start of new overall AI system
********/ ********/
if (gfTurnBasedAI) if (gfTurnBasedAI)
{ {
time_t tCurrentTime = time(0); #if 0
UINT32 uiShortDelay = 10;
UINT32 uiDelay = (UINT32)gGameExternalOptions.gubDeadLockDelay;
UINT32 uiTime = (UINT32)(tCurrentTime - gtTimeSinceMercAIStart);
BOOLEAN fKeyPressed = _KeyDown(ESC);
if ((uiTime > uiDelay || uiTime > uiShortDelay && fKeyPressed) && !gfUIInDeadlock)
//if ( ( GetJA2Clock() - gTacticalStatus.uiTimeSinceMercAIStart ) > ( (UINT32)gGameExternalOptions.gubDeadLockDelay * 1000 ) && !gfUIInDeadlock )
{ {
// ATE: Display message that deadlock occured... // added by Flugente: static pointers, used to break out of an endless circles
LiveMessage( "Breaking Deadlock" ); static SOLDIERTYPE* pLastDecisionSoldier = NULL;
static INT16 lastdecisioncount = 0;
// simple solution to prevent an endless clock: remember the last soldier that decided an action. If its the same one, increase the counter.
// if counter is high enough, end this guy's turn
if (pSoldier == pLastDecisionSoldier)
{
// we will only end our turn this way if this function was called over 100 times with same soldier without ending a turn.
// so many actions in a single turn smell of an endless clock.
// If we end a turn normally, the counter will be set back to 0, so this wont be a problem if you have a single soldier left for multiple turns
if (lastdecisioncount >= 600)
{
ScreenMsg(FONT_MCOLOR_LTRED, MSG_INTERFACE, L"Aborting AI deadlock for [%d] %s data %d", pSoldier->ubID, wszAction[pSoldier->aiData.bAction], pSoldier->aiData.usActionData);
DebugAI(AI_MSG_INFO, pSoldier, String("Aborting AI deadlock for [%d] %s data %d", pSoldier->ubID, szAction[pSoldier->aiData.bAction], pSoldier->aiData.usActionData));
DebugAI(AI_MSG_INFO, pSoldier, String("Last action was %s ", szAction[pSoldier->aiData.bLastAction]));
EndAIDeadlock();
//EndAIGuysTurn(pSoldier);
lastdecisioncount = 0;
return;
}
else
++lastdecisioncount;
}
else
{
pLastDecisionSoldier = pSoldier;
lastdecisioncount = 0;
}
}
#else
{
time_t tCurrentTime = time(0);
UINT32 uiShortDelay = 10;
UINT32 uiDelay = (UINT32)gGameExternalOptions.gubDeadLockDelay;
UINT32 uiTime = (UINT32)(tCurrentTime - gtTimeSinceMercAIStart);
BOOLEAN fKeyPressed = _KeyDown(ESC);
if ((uiTime > uiDelay || uiTime > uiShortDelay && fKeyPressed) && !gfUIInDeadlock)
//if ( ( GetJA2Clock() - gTacticalStatus.uiTimeSinceMercAIStart ) > ( (UINT32)gGameExternalOptions.gubDeadLockDelay * 1000 ) && !gfUIInDeadlock )
{
// ATE: Display message that deadlock occured...
LiveMessage( "Breaking Deadlock" );
ScreenMsg(FONT_MCOLOR_LTRED, MSG_INTERFACE, L"Aborting AI deadlock for [%d] %s data %d", pSoldier->ubID, wszAction[pSoldier->aiData.bAction], pSoldier->aiData.usActionData);
DebugAI(AI_MSG_INFO, pSoldier, String("Aborting AI deadlock for [%d] %s data %d", pSoldier->ubID, szAction[pSoldier->aiData.bAction], pSoldier->aiData.usActionData));
DebugAI(AI_MSG_INFO, pSoldier, String("Last action was %s ", szAction[pSoldier->aiData.bLastAction]));
ScreenMsg(FONT_MCOLOR_LTRED, MSG_INTERFACE, L"Aborting AI deadlock for [%d] %s %s data %d", pSoldier->ubID.i, pSoldier->GetName(), utf8_to_wstring(std::string(szAction[pSoldier->aiData.bAction])), pSoldier->aiData.usActionData);
DebugAI(String("Aborting AI deadlock for [%d] %s data %d", pSoldier->ubID, szAction[pSoldier->aiData.bAction], pSoldier->aiData.usActionData));
#ifdef JA2TESTVERSION #ifdef JA2TESTVERSION
// display deadlock message // display deadlock message
gfUIInDeadlock = TRUE; gfUIInDeadlock = TRUE;
DebugAI( String("DEADLOCK soldier %d action %s ABC %d", pSoldier->ubID.i, gzActionStr[pSoldier->aiData.bAction], gTacticalStatus.ubAttackBusyCount ) ); DebugAI( String("DEADLOCK soldier %d action %s ABC %d", pSoldier->ubID.i, gzActionStr[pSoldier->aiData.bAction], gTacticalStatus.ubAttackBusyCount ) );
#else #else
// If we are in beta version, also report message!
// If we are in beta version, also report message!
#ifdef JA2BETAVERSION #ifdef JA2BETAVERSION
ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_ERROR, L"Aborting AI deadlock for %d. Please sent DEBUG.TXT file and SAVE.", pSoldier->ubID.i ); ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_ERROR, L"Aborting AI deadlock for %d. Please sent DEBUG.TXT file and SAVE.", pSoldier->ubID.i );
#endif
// just abort
EndAIDeadlock();
if ( !(pSoldier->flags.uiStatusFlags & SOLDIER_UNDERAICONTROL) )
{
return;
}
#endif #endif
// just abort
EndAIDeadlock();
if ( !(pSoldier->flags.uiStatusFlags & SOLDIER_UNDERAICONTROL) )
{
return;
} }
#endif
} }
#endif
} }
// We STILL do not want to issue new orders while an attack busy situation is going on. This can happen, for example, // We STILL do not want to issue new orders while an attack busy situation is going on. This can happen, for example,
@@ -727,7 +868,7 @@ void HandleSoldierAI( SOLDIERTYPE *pSoldier ) // FIXME - this function is named
return; return;
} }
if (pSoldier->aiData.bAction == AI_ACTION_NONE) if (!pSoldier->aiData.bActionInProgress)
{ {
// being handled so turn off muzzle flash // being handled so turn off muzzle flash
if ( pSoldier->flags.fMuzzleFlash ) if ( pSoldier->flags.fMuzzleFlash )
@@ -863,6 +1004,12 @@ void HandleSoldierAI( SOLDIERTYPE *pSoldier ) // FIXME - this function is named
{ {
ActionDone(pSoldier); ActionDone(pSoldier);
} }
if (!ShouldActionStayInProgress(pSoldier))
{
DebugAI(AI_MSG_INFO, pSoldier, String("Action %s was stuck as being in progress. Canceling action", szAction[pSoldier->aiData.bAction]));
ActionDone(pSoldier);
}
} }
/********* /*********
End of new overall AI system End of new overall AI system
@@ -1327,7 +1474,7 @@ void FreeUpNPCFromRoofClimb(SOLDIERTYPE *pSoldier )
void ActionDone(SOLDIERTYPE *pSoldier) void ActionDone(SOLDIERTYPE *pSoldier)
{ {
// if an action is currently selected // if an action is currently selected
if (pSoldier->aiData.bAction != AI_ACTION_NONE) //if (pSoldier->aiData.bAction != AI_ACTION_NONE)
{ {
if (pSoldier->flags.uiStatusFlags & SOLDIER_MONSTER) if (pSoldier->flags.uiStatusFlags & SOLDIER_MONSTER)
{ {
@@ -1547,33 +1694,6 @@ INT16 ActionInProgress(SOLDIERTYPE *pSoldier)
void TurnBasedHandleNPCAI(SOLDIERTYPE *pSoldier) void TurnBasedHandleNPCAI(SOLDIERTYPE *pSoldier)
{ {
// added by Flugente: static pointers, used to break out of an endless circles (currently only used for zombie AI)
static SOLDIERTYPE* pLastDecisionSoldier = NULL;
static INT16 lastdecisioncount = 0;
// simple solution to prevent an endless clock: remember the last soldier that decided an action. If its the same one, increase the counter.
// if counter is high enough, end this guy's turn
if ( pSoldier == pLastDecisionSoldier )
{
// we will only end our turn this way if this function was called over 100 times with same soldier without ending a turn.
// so many actions in a single turn smell of an endless clock.
// If we end a turn normally, the counter will be set back to 0, so this wont be a problem if you have a single soldier left for multiple turns
if ( lastdecisioncount >= 100 )
{
// zombie is done doing harm...
EndAIGuysTurn( pSoldier);
lastdecisioncount = 0;
return ;
}
else
++lastdecisioncount;
}
else
{
pLastDecisionSoldier = pSoldier;
lastdecisioncount = 0;
}
// yikes, this shouldn't occur! we should be trying to finish our move! // yikes, this shouldn't occur! we should be trying to finish our move!
// pSoldier->flags.fNoAPToFinishMove = FALSE; // pSoldier->flags.fNoAPToFinishMove = FALSE;
@@ -1606,6 +1726,8 @@ void TurnBasedHandleNPCAI(SOLDIERTYPE *pSoldier)
#ifdef DEBUGBUSY #ifdef DEBUGBUSY
AINumMessage("Busy with action, skipping guy#",pSoldier->ubID); AINumMessage("Busy with action, skipping guy#",pSoldier->ubID);
#endif #endif
ScreenMsg(FONT_MCOLOR_LTRED, MSG_INTERFACE, L"Busy with action %s, skipping guy [%d]", wszAction[pSoldier->aiData.bAction], pSoldier->ubID);
DebugAI(AI_MSG_INFO, pSoldier, String("Busy with action %s, skipping guy [%d]", wszAction[pSoldier->aiData.bAction], pSoldier->ubID));
// let it continue // let it continue
return; return;
@@ -1731,43 +1853,42 @@ void TurnBasedHandleNPCAI(SOLDIERTYPE *pSoldier)
{ {
pSoldier->aiData.bAction = AI_ACTION_NONE; pSoldier->aiData.bAction = AI_ACTION_NONE;
} }
}
// if he chose to continue doing nothing // if he chose to continue doing nothing
if (pSoldier->aiData.bAction == AI_ACTION_NONE) if (pSoldier->aiData.bAction == AI_ACTION_NONE)
{ {
#ifdef RECORDNET #ifdef RECORDNET
fprintf(NetDebugFile,"\tMOVED BECOMING TRUE: Chose to do nothing, guynum %d\n",pSoldier->ubID); fprintf(NetDebugFile,"\tMOVED BECOMING TRUE: Chose to do nothing, guynum %d\n",pSoldier->ubID);
#endif #endif
DebugMsg (TOPIC_JA2AI,DBG_LEVEL_3,"NPC has no action assigned"); DebugMsg (TOPIC_JA2AI,DBG_LEVEL_3,"NPC has no action assigned");
NPCDoesNothing(pSoldier); // sets pSoldier->moved to TRUE NPCDoesNothing(pSoldier); // sets pSoldier->moved to TRUE
return; return;
} }
// to get here, we MUST have an action selected, but not in progress... // to get here, we MUST have an action selected, but not in progress...
// see if we can afford to do this action // see if we can afford to do this action
if (IsActionAffordable(pSoldier)) if (IsActionAffordable(pSoldier))
{ {
NPCDoesAct(pSoldier); NPCDoesAct(pSoldier);
// perform the chosen action // perform the chosen action
pSoldier->aiData.bActionInProgress = ExecuteAction(pSoldier); // if started, mark us as busy pSoldier->aiData.bActionInProgress = ExecuteAction(pSoldier); // if started, mark us as busy
if ( !pSoldier->aiData.bActionInProgress && !TileIsOutOfBounds(pSoldier->sAbsoluteFinalDestination)) if ( !pSoldier->aiData.bActionInProgress && !TileIsOutOfBounds(pSoldier->sAbsoluteFinalDestination))
{
// turn based... abort this guy's turn
EndAIGuysTurn( pSoldier );
lastdecisioncount = 0;
}
}
else
{ {
#ifdef DEBUGDECISIONS // turn based... abort this guy's turn
AINumMessage("HandleManAI - Not enough APs, skipping guy#",pSoldier->ubID); EndAIGuysTurn( pSoldier );
#endif
HaltMoveForSoldierOutOfPoints( pSoldier);
return;
} }
} }
else
{
#ifdef DEBUGDECISIONS
AINumMessage("HandleManAI - Not enough APs, skipping guy#",pSoldier->ubID);
#endif
HaltMoveForSoldierOutOfPoints( pSoldier);
return;
}
} }
@@ -1799,6 +1920,7 @@ void RefreshAI(SOLDIERTYPE *pSoldier)
if ((pSoldier->aiData.bAlertStatus == STATUS_BLACK) || (pSoldier->aiData.bAlertStatus == STATUS_RED)) if ((pSoldier->aiData.bAlertStatus == STATUS_BLACK) || (pSoldier->aiData.bAlertStatus == STATUS_RED))
{ {
// always freshly rethink things at start of his turn // always freshly rethink things at start of his turn
//CancelAIAction(pSoldier, FALSE);
pSoldier->aiData.bNewSituation = IS_NEW_SITUATION; pSoldier->aiData.bNewSituation = IS_NEW_SITUATION;
} }
else else
@@ -2281,14 +2403,24 @@ INT8 ExecuteAction(SOLDIERTYPE *pSoldier)
usHandItem = GetAttachedGrenadeLauncher(&pSoldier->inv[HANDPOS]); usHandItem = GetAttachedGrenadeLauncher(&pSoldier->inv[HANDPOS]);
iRetCode = HandleItem( pSoldier, pSoldier->aiData.usActionData, pSoldier->bTargetLevel, usHandItem, FALSE ); iRetCode = HandleItem( pSoldier, pSoldier->aiData.usActionData, pSoldier->bTargetLevel, usHandItem, FALSE );
// If AI cannot shoot because of lack of APs, attempt to try again with lower aim.
// Usually happens when they have to turn before shooting. Without this, the game would cancel soldier's whole turn
if (iRetCode == ITEM_HANDLE_NOAPS && pSoldier->aiData.bAimTime > 0)
{
do
{
pSoldier->aiData.bAimTime -= 1;
iRetCode = HandleItem(pSoldier, pSoldier->aiData.usActionData, pSoldier->bTargetLevel, usHandItem, FALSE);
} while (iRetCode == ITEM_HANDLE_NOAPS && pSoldier->aiData.bAimTime > 0);
}
if ( iRetCode != ITEM_HANDLE_OK) if ( iRetCode != ITEM_HANDLE_OK)
{ {
if ( iRetCode != ITEM_HANDLE_BROKEN ) // if the item broke, this is 'legal' and doesn't need reporting if ( iRetCode != ITEM_HANDLE_BROKEN ) // if the item broke, this is 'legal' and doesn't need reporting
{ {
DebugAI( String( "AI %d got error code %ld from HandleItem, doing action %d, has %d APs... aborting deadlock!", pSoldier->ubID, iRetCode, pSoldier->aiData.bAction, pSoldier->bActionPoints ) ); DebugAI(AI_MSG_INFO, pSoldier, String( "AI %d got error code %ld from HandleItem, doing action %d, has %d APs... aborting deadlock!", pSoldier->ubID, iRetCode, pSoldier->aiData.bAction, pSoldier->bActionPoints ) );
ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_BETAVERSION, L"AI %d got error code %ld from HandleItem, doing action %d... aborting deadlock!", pSoldier->ubID, iRetCode, pSoldier->aiData.bAction ); ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_BETAVERSION, L"AI %d got error code %ld from HandleItem, doing action %d... aborting deadlock!", pSoldier->ubID, iRetCode, pSoldier->aiData.bAction );
} }
DebugAI(AI_MSG_INFO, pSoldier, String("CancelAIAction: !ITEM_HANDLE_OK"));
CancelAIAction( pSoldier, FORCE); CancelAIAction( pSoldier, FORCE);
#ifdef TESTAICONTROL #ifdef TESTAICONTROL
if (gfTurnBasedAI) if (gfTurnBasedAI)
@@ -2612,7 +2744,30 @@ INT8 ExecuteAction(SOLDIERTYPE *pSoldier)
case AI_ACTION_JUMP_WINDOW: case AI_ACTION_JUMP_WINDOW:
{ {
pSoldier->BeginSoldierClimbWindow(); pSoldier->BeginSoldierJumpWindowAI();
if ( gfTurnBasedAI )
{
//if (pSoldier->bActionPoints >= GetAPsToJumpThroughWindows(pSoldier, FALSE) + GetAPsToLook(pSoldier))
if ( pSoldier->bActionPoints >= GetAPsToLook(pSoldier) )
{
pSoldier->aiData.bNextAction = AI_ACTION_CHANGE_FACING;
INT32 sClosestOpponent = ClosestKnownOpponent(pSoldier, NULL, NULL);
if ( !TileIsOutOfBounds(sClosestOpponent) )
pSoldier->aiData.usNextActionData = AIDirection(pSoldier->sGridNo, sClosestOpponent);
else
pSoldier->aiData.usNextActionData = PreRandom(8);
}
else
{
pSoldier->aiData.bNextAction = AI_ACTION_END_TURN;
pSoldier->aiData.usNextActionData = 0;
}
}
else
{
pSoldier->aiData.bNextAction = AI_ACTION_WAIT;
pSoldier->aiData.usNextActionData = REALTIME_AI_DELAY / 10;
}
ActionDone( pSoldier ); ActionDone( pSoldier );
} }
break; break;
@@ -2675,7 +2830,73 @@ INT8 ExecuteAction(SOLDIERTYPE *pSoldier)
} }
break; break;
default: case AI_ACTION_DRINK_CANTEEN:
DrinkFromInventory(pSoldier);
ActionDone(pSoldier);
break;
case AI_ACTION_HANDLE_ITEM:
iRetCode = HandleItem(pSoldier, pSoldier->aiData.usActionData, pSoldier->pathing.bLevel, pSoldier->inv[HANDPOS].usItem, FALSE);
if ( iRetCode != ITEM_HANDLE_OK )
{
DebugAI(AI_MSG_INFO, pSoldier, String("CancelAIAction (AI_ACTION_HANDLE_ITEM): HandleItem error code %d", iRetCode));
CancelAIAction(pSoldier, FORCE);
EndAIGuysTurn(pSoldier);
}
break;
case AI_ACTION_PLANT_BOMB:
if ( pSoldier->inv[HANDPOS].exists() &&
(Item[pSoldier->inv[HANDPOS].usItem].usItemClass & IC_BOMB) )
{
OBJECTTYPE bombobj;
INT32 sSpot = pSoldier->sGridNo;
if ( !TileIsOutOfBounds(sSpot) &&
pSoldier->inv[HANDPOS].MoveThisObjectTo(bombobj, 1) == 0 )
{
bombobj.fFlags |= OBJECT_ARMED_BOMB;
bombobj[0]->data.misc.bDetonatorType = BOMB_TIMED;
bombobj[0]->data.misc.usBombItem = bombobj.usItem;
//bombobj[0]->data.misc.ubBombOwner = pSoldier->ubID + 2;
bombobj[0]->data.misc.ubBombOwner = 1;
bombobj[0]->data.misc.bDelay = 1 + Random(2);
//pSoldier->inv[HANDPOS][0]->data.bTrap = EffectiveExplosive(pSoldier) / 20 + EffectiveExpLevel(pSoldier, TRUE) / 2;
pSoldier->inv[HANDPOS][0]->data.bTrap = 6 + SoldierDifficultyLevel(pSoldier);
AddItemToPool(sSpot, &bombobj, INVISIBLE, pSoldier->pathing.bLevel, WORLD_ITEM_ARMED_BOMB, 0);
NotifySoldiersToLookforItems();
DeductPoints(pSoldier, APBPConstants[AP_INVENTORY_ARM] + APBPConstants[AP_DROP_BOMB], APBPConstants[BP_INVENTORY_ARM] + APBPConstants[BP_DROP_BOMB]);
if ( gAnimControl[pSoldier->usAnimState].ubHeight == ANIM_STAND )
{
pSoldier->EVENT_InitNewSoldierAnim(DROP_ITEM, 0, FALSE);
}
else if ( gAnimControl[pSoldier->usAnimState].ubHeight == ANIM_CROUCH )
{
pSoldier->EVENT_InitNewSoldierAnim(CUTTING_FENCE, 0, FALSE);
}
if ( pSoldier->bVisible != -1 )
{
PlayJA2Sample(THROW_IMPACT_2, RATE_11025, SoundVolume(MIDVOLUME, pSoldier->sGridNo), 1, SoundDir(pSoldier->sGridNo));
}
ActionDone(pSoldier);
}
else
{
DebugAI(AI_MSG_INFO, pSoldier, String("CancelAIAction (AI_ACTION_PLANT_BOMB): failed to move object"));
CancelAIAction(pSoldier, FORCE);
EndAIGuysTurn(pSoldier);
}
}
else
{
DebugAI(AI_MSG_INFO, pSoldier, String("CancelAIAction (AI_ACTION_PLANT_BOMB): failed to find bomb in hand"));
CancelAIAction(pSoldier, FORCE);
EndAIGuysTurn(pSoldier);
}
break;
default:
#ifdef BETAVERSION #ifdef BETAVERSION
NumMessage("ExecuteAction - Illegal action type = ",pSoldier->aiData.bAction); NumMessage("ExecuteAction - Illegal action type = ",pSoldier->aiData.bAction);
#endif #endif
+353 -24
View File
@@ -27,6 +27,7 @@
#include "SmokeEffects.h" // sevenfm #include "SmokeEffects.h" // sevenfm
#include "GameInitOptionsScreen.h" #include "GameInitOptionsScreen.h"
#include "Structure Wrap.h"
////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////
// SANDRO - In this file, all APBPConstants[AP_CROUCH] and APBPConstants[AP_PRONE] were changed to GetAPsCrouch() and GetAPsProne() // SANDRO - In this file, all APBPConstants[AP_CROUCH] and APBPConstants[AP_PRONE] were changed to GetAPsCrouch() and GetAPsProne()
@@ -134,18 +135,17 @@ INT8 OKToAttack(SOLDIERTYPE * pSoldier, int target)
BOOLEAN ConsiderProne( SOLDIERTYPE * pSoldier ) BOOLEAN ConsiderProne( SOLDIERTYPE * pSoldier )
{ {
INT32 sOpponentGridNo; INT32 sOpponentGridNo;
INT8 bOpponentLevel; INT8 bOpponentLevel;
INT32 iRange; INT32 iRangeInCellCoords;
if (pSoldier->aiData.bAIMorale >= MORALE_NORMAL) if (pSoldier->aiData.bAIMorale >= MORALE_NORMAL)
{ {
return( FALSE ); return( FALSE );
} }
// We don't want to go prone if there is a nearby enemy // We don't want to go prone if there is a nearby enemy
ClosestKnownOpponent( pSoldier, &sOpponentGridNo, &bOpponentLevel ); ClosestKnownOpponent( pSoldier, &sOpponentGridNo, &bOpponentLevel, nullptr, &iRangeInCellCoords);
iRange = PythSpacesAway( pSoldier->sGridNo, sOpponentGridNo ); if ( iRangeInCellCoords > 10*CELL_X_SIZE)
if (iRange > 10)
{ {
return( TRUE ); return( TRUE );
} }
@@ -397,7 +397,11 @@ UINT16 DetermineMovementMode( SOLDIERTYPE * pSoldier, INT8 bAction )
// sevenfm: movement mode tweaks // sevenfm: movement mode tweaks
if (gGameExternalOptions.fAIMovementMode) if (gGameExternalOptions.fAIMovementMode)
{ {
INT32 sClosestThreat = ClosestKnownOpponent(pSoldier, NULL, NULL); INT32 distanceToThreat;
const INT32 sClosestThreat = ClosestKnownOpponent(pSoldier, NULL, NULL, NULL, &distanceToThreat);
const auto mediumRange = TACTICAL_RANGE_CELL_COORDS / 2;
const auto close = TACTICAL_RANGE_CELL_COORDS / 4;
const auto reallyClose = TACTICAL_RANGE_CELL_COORDS / 8;
// use walking mode if no enemy known // use walking mode if no enemy known
if (pSoldier->aiData.bAlertStatus < STATUS_RED && if (pSoldier->aiData.bAlertStatus < STATUS_RED &&
@@ -418,11 +422,12 @@ UINT16 DetermineMovementMode( SOLDIERTYPE * pSoldier, INT8 bAction )
if (IS_MERC_BODY_TYPE(pSoldier) && if (IS_MERC_BODY_TYPE(pSoldier) &&
pSoldier->aiData.bAlertStatus >= STATUS_YELLOW && pSoldier->aiData.bAlertStatus >= STATUS_YELLOW &&
!InWaterGasOrSmoke(pSoldier, pSoldier->sGridNo) && !InWaterGasOrSmoke(pSoldier, pSoldier->sGridNo) &&
!(pSoldier->flags.uiStatusFlags & SOLDIER_BOXER) && !BOXER(pSoldier) &&
!TileIsOutOfBounds(sClosestThreat) && !TileIsOutOfBounds(sClosestThreat) &&
(pSoldier->bTeam == ENEMY_TEAM || pSoldier->bTeam == MILITIA_TEAM)) (pSoldier->bTeam == ENEMY_TEAM || pSoldier->bTeam == MILITIA_TEAM))
{ {
INT16 sDistanceVisible = VISION_RANGE; INT16 sDistanceVisible = VISION_RANGE;
const auto beyondVisionRange = (CELL_X_SIZE * 3 * sDistanceVisible) / 2;
INT32 iRCD = RangeChangeDesire(pSoldier); INT32 iRCD = RangeChangeDesire(pSoldier);
// use running when in light at night // use running when in light at night
@@ -445,7 +450,7 @@ UINT16 DetermineMovementMode( SOLDIERTYPE * pSoldier, INT8 bAction )
!GuySawEnemy(pSoldier) && !GuySawEnemy(pSoldier) &&
(NightTime() || gAnimControl[pSoldier->usAnimState].ubEndHeight <= ANIM_CROUCH) && (NightTime() || gAnimControl[pSoldier->usAnimState].ubEndHeight <= ANIM_CROUCH) &&
CountNearbyFriends(pSoldier, pSoldier->sGridNo, TACTICAL_RANGE / 4) < 3 && CountNearbyFriends(pSoldier, pSoldier->sGridNo, TACTICAL_RANGE / 4) < 3 &&
PythSpacesAway(pSoldier->sGridNo, sClosestThreat) < 3 * sDistanceVisible / 2 && distanceToThreat < beyondVisionRange &&
CountFriendsBlack(pSoldier) == 0 && CountFriendsBlack(pSoldier) == 0 &&
bAction == AI_ACTION_SEEK_OPPONENT) bAction == AI_ACTION_SEEK_OPPONENT)
{ {
@@ -454,7 +459,7 @@ UINT16 DetermineMovementMode( SOLDIERTYPE * pSoldier, INT8 bAction )
// use swatting for taking cover // use swatting for taking cover
if (pSoldier->aiData.bAlertStatus >= STATUS_RED && if (pSoldier->aiData.bAlertStatus >= STATUS_RED &&
PythSpacesAway(pSoldier->sGridNo, sClosestThreat) > (INT16)TACTICAL_RANGE / 8 && distanceToThreat > reallyClose &&
(pSoldier->aiData.bUnderFire && iRCD < 4 || (pSoldier->aiData.bUnderFire && iRCD < 4 ||
pSoldier->aiData.bShock > 2 * iRCD || pSoldier->aiData.bShock > 2 * iRCD ||
pSoldier->aiData.bShock > 0 && gAnimControl[pSoldier->usAnimState].ubEndHeight == ANIM_PRONE) && pSoldier->aiData.bShock > 0 && gAnimControl[pSoldier->usAnimState].ubEndHeight == ANIM_PRONE) &&
@@ -466,9 +471,9 @@ UINT16 DetermineMovementMode( SOLDIERTYPE * pSoldier, INT8 bAction )
// use SWATTING when under fire // use SWATTING when under fire
if (pSoldier->aiData.bAlertStatus >= STATUS_RED && if (pSoldier->aiData.bAlertStatus >= STATUS_RED &&
(pSoldier->aiData.bShock > iRCD && PythSpacesAway(pSoldier->sGridNo, sClosestThreat) > (INT16)TACTICAL_RANGE / 2 || (pSoldier->aiData.bShock > iRCD && distanceToThreat > mediumRange ||
pSoldier->aiData.bShock > 0 && gAnimControl[pSoldier->usAnimState].ubEndHeight == ANIM_PRONE && PythSpacesAway(pSoldier->sGridNo, sClosestThreat) > (INT16)TACTICAL_RANGE / 4) && pSoldier->aiData.bShock > 0 && gAnimControl[pSoldier->usAnimState].ubEndHeight == ANIM_PRONE && distanceToThreat > close) &&
PythSpacesAway(pSoldier->sGridNo, sClosestThreat) < 3 * sDistanceVisible / 2 && distanceToThreat < beyondVisionRange &&
gAnimControl[pSoldier->usAnimState].ubEndHeight <= ANIM_CROUCH && gAnimControl[pSoldier->usAnimState].ubEndHeight <= ANIM_CROUCH &&
!pSoldier->aiData.bLastAttackHit && !pSoldier->aiData.bLastAttackHit &&
(bAction == AI_ACTION_SEEK_OPPONENT || (bAction == AI_ACTION_SEEK_OPPONENT ||
@@ -485,7 +490,7 @@ UINT16 DetermineMovementMode( SOLDIERTYPE * pSoldier, INT8 bAction )
(pSoldier->aiData.bOrders == SNIPER || (pSoldier->aiData.bOrders == SNIPER ||
pSoldier->aiData.bOrders == STATIONARY || pSoldier->aiData.bOrders == STATIONARY ||
(GuySawEnemy(pSoldier) || pSoldier->aiData.bShock > 0) && iRCD < 4) && (GuySawEnemy(pSoldier) || pSoldier->aiData.bShock > 0) && iRCD < 4) &&
PythSpacesAway(pSoldier->sGridNo, sClosestThreat) > (INT16)TACTICAL_RANGE / 4 && distanceToThreat > close &&
(bAction == AI_ACTION_SEEK_OPPONENT || (bAction == AI_ACTION_SEEK_OPPONENT ||
bAction == AI_ACTION_GET_CLOSER || bAction == AI_ACTION_GET_CLOSER ||
bAction == AI_ACTION_SEEK_FRIEND || bAction == AI_ACTION_SEEK_FRIEND ||
@@ -501,7 +506,7 @@ UINT16 DetermineMovementMode( SOLDIERTYPE * pSoldier, INT8 bAction )
(pSoldier->aiData.bOrders == SNIPER || (pSoldier->aiData.bOrders == SNIPER ||
pSoldier->aiData.bOrders == STATIONARY || pSoldier->aiData.bOrders == STATIONARY ||
pSoldier->aiData.bShock > 0 && iRCD < 4) && pSoldier->aiData.bShock > 0 && iRCD < 4) &&
PythSpacesAway(pSoldier->sGridNo, sClosestThreat) > (INT16)TACTICAL_RANGE / 4 && distanceToThreat > close &&
(bAction == AI_ACTION_SEEK_OPPONENT || (bAction == AI_ACTION_SEEK_OPPONENT ||
bAction == AI_ACTION_GET_CLOSER || bAction == AI_ACTION_GET_CLOSER ||
bAction == AI_ACTION_SEEK_FRIEND || bAction == AI_ACTION_SEEK_FRIEND ||
@@ -515,7 +520,7 @@ UINT16 DetermineMovementMode( SOLDIERTYPE * pSoldier, INT8 bAction )
if (!pSoldier->aiData.bUnderFire && if (!pSoldier->aiData.bUnderFire &&
bAction == AI_ACTION_TAKE_COVER && bAction == AI_ACTION_TAKE_COVER &&
pSoldier->bInitialActionPoints > APBPConstants[AP_MINIMUM] && pSoldier->bInitialActionPoints > APBPConstants[AP_MINIMUM] &&
(!InARoom(pSoldier->sGridNo, NULL) || PythSpacesAway(pSoldier->sGridNo, sClosestThreat) > sDistanceVisible * 2) && (!InARoom(pSoldier->sGridNo, NULL) || distanceToThreat > sDistanceVisible * 20) &&
pSoldier->aiData.bAIMorale >= MORALE_NORMAL && pSoldier->aiData.bAIMorale >= MORALE_NORMAL &&
pSoldier->bBreath > 25 && pSoldier->bBreath > 25 &&
pSoldier->pathing.bLevel == 0 && pSoldier->pathing.bLevel == 0 &&
@@ -539,14 +544,14 @@ UINT16 DetermineMovementMode( SOLDIERTYPE * pSoldier, INT8 bAction )
else if (gAnimControl[pSoldier->usAnimState].ubEndHeight == ANIM_CROUCH) else if (gAnimControl[pSoldier->usAnimState].ubEndHeight == ANIM_CROUCH)
{ {
if (WeaponReady(pSoldier) && !pSoldier->aiData.bUnderFire && pSoldier->aiData.bAlertStatus == STATUS_BLACK || if (WeaponReady(pSoldier) && !pSoldier->aiData.bUnderFire && pSoldier->aiData.bAlertStatus == STATUS_BLACK ||
pSoldier->aiData.bUnderFire && PythSpacesAway(pSoldier->sGridNo, sClosestThreat) > (INT16)TACTICAL_RANGE / 8) pSoldier->aiData.bUnderFire && distanceToThreat > reallyClose )
return SWATTING; return SWATTING;
else else
return RUNNING; return RUNNING;
} }
else if (gAnimControl[pSoldier->usAnimState].ubEndHeight == ANIM_PRONE) else if (gAnimControl[pSoldier->usAnimState].ubEndHeight == ANIM_PRONE)
{ {
if (pSoldier->aiData.bUnderFire && !pSoldier->aiData.bLastAttackHit && PythSpacesAway(pSoldier->sGridNo, sClosestThreat) > (INT16)TACTICAL_RANGE / 8) if (pSoldier->aiData.bUnderFire && !pSoldier->aiData.bLastAttackHit && distanceToThreat > reallyClose )
return SWATTING; return SWATTING;
else else
return RUNNING; return RUNNING;
@@ -808,6 +813,18 @@ BOOLEAN IsActionAffordable(SOLDIERTYPE *pSoldier, INT8 bAction)
bMinPointsNeeded = 20; // TODO bMinPointsNeeded = 20; // TODO
break; break;
case AI_ACTION_DRINK_CANTEEN:
bMinPointsNeeded = APBPConstants[AP_DRINK];
break;
case AI_ACTION_HANDLE_ITEM:
bMinPointsNeeded = 0;
break;
case AI_ACTION_PLANT_BOMB:
bMinPointsNeeded = APBPConstants[AP_INVENTORY_ARM] + APBPConstants[AP_DROP_BOMB];
break;
default: default:
#ifdef BETAVERSION #ifdef BETAVERSION
//NumMessage("AffordableAction - Illegal action type = ",pSoldier->aiData.bAction); //NumMessage("AffordableAction - Illegal action type = ",pSoldier->aiData.bAction);
@@ -1379,7 +1396,7 @@ INT32 ClosestReachableDisturbance(SOLDIERTYPE *pSoldier, BOOLEAN * pfChangeLevel
} }
INT32 ClosestKnownOpponent(SOLDIERTYPE *pSoldier, INT32 * psGridNo, INT8 * pbLevel, SoldierID * pubOpponentID) INT32 ClosestKnownOpponent(SOLDIERTYPE *pSoldier, INT32 * psGridNo, INT8 * pbLevel, SoldierID * pubOpponentID, INT32 * distanceInCellCoords)
{ {
INT32 *psLastLoc,sGridNo, sClosestOpponent = NOWHERE; INT32 *psLastLoc,sGridNo, sClosestOpponent = NOWHERE;
UINT32 uiLoop; UINT32 uiLoop;
@@ -1475,7 +1492,7 @@ INT32 ClosestKnownOpponent(SOLDIERTYPE *pSoldier, INT32 * psGridNo, INT8 * pbLev
if (sClosestOpponent == NOWHERE || if (sClosestOpponent == NOWHERE ||
iRange < iClosestRange || iRange < iClosestRange ||
pClosestOpponent && !pClosestOpponent->IsZombie() && !(pSoldier->flags.uiStatusFlags & SOLDIER_BOXER) && pClosestOpponent->stats.bLife < OKLIFE && pOpponent->stats.bLife >= OKLIFE) pClosestOpponent && !pClosestOpponent->IsZombie() && !BOXER(pSoldier) && pClosestOpponent->stats.bLife < OKLIFE && pOpponent->stats.bLife >= OKLIFE)
{ {
iClosestRange = iRange; iClosestRange = iRange;
sClosestOpponent = sGridNo; sClosestOpponent = sGridNo;
@@ -1503,6 +1520,10 @@ INT32 ClosestKnownOpponent(SOLDIERTYPE *pSoldier, INT32 * psGridNo, INT8 * pbLev
{ {
*pubOpponentID = pClosestOpponent->ubID; *pubOpponentID = pClosestOpponent->ubID;
} }
if ( distanceInCellCoords )
{
*distanceInCellCoords = iClosestRange;
}
return( sClosestOpponent ); return( sClosestOpponent );
} }
@@ -2232,6 +2253,31 @@ INT16 DistanceToClosestFriend( SOLDIERTYPE * pSoldier )
return( sMinDist ); return( sMinDist );
} }
BOOLEAN InSmoke(SOLDIERTYPE* pSoldier, INT32 sGridNo)
{
if ( gpWorldLevelData[sGridNo].ubExtFlags[pSoldier->pathing.bLevel] & (MAPELEMENT_EXT_SMOKE | MAPELEMENT_EXT_SIGNAL_SMOKE | MAPELEMENT_EXT_DEBRIS_SMOKE | MAPELEMENT_EXT_FIRERETARDANT_SMOKE) )
return TRUE;
return FALSE;
}
BOOLEAN InTearGas(SOLDIERTYPE* pSoldier, INT32 sGridNo)
{
if ( (gpWorldLevelData[sGridNo].ubExtFlags[pSoldier->pathing.bLevel] & MAPELEMENT_EXT_TEARGAS))
return TRUE;
return FALSE;
}
BOOLEAN InMustardGas(SOLDIERTYPE* pSoldier, INT32 sGridNo)
{
if ( gpWorldLevelData[sGridNo].ubExtFlags[pSoldier->pathing.bLevel] & (MAPELEMENT_EXT_BURNABLEGAS | MAPELEMENT_EXT_CREATUREGAS | MAPELEMENT_EXT_MUSTARDGAS) )
return TRUE;
return FALSE;
}
BOOLEAN InWaterGasOrSmoke( SOLDIERTYPE *pSoldier, INT32 sGridNo ) BOOLEAN InWaterGasOrSmoke( SOLDIERTYPE *pSoldier, INT32 sGridNo )
{ {
if (WaterTooDeepForAttacks( sGridNo, pSoldier->pathing.bLevel )) if (WaterTooDeepForAttacks( sGridNo, pSoldier->pathing.bLevel ))
@@ -2250,6 +2296,12 @@ BOOLEAN InWaterGasOrSmoke( SOLDIERTYPE *pSoldier, INT32 sGridNo )
BOOLEAN InGasOrSmoke( SOLDIERTYPE *pSoldier, INT32 sGridNo ) BOOLEAN InGasOrSmoke( SOLDIERTYPE *pSoldier, INT32 sGridNo )
{ {
// Armed vehicles and robots do not care about gas or smoke
if (ARMED_VEHICLE(pSoldier) || ENEMYROBOT(pSoldier))
{
return FALSE;
}
// smoke // smoke
if ( gpWorldLevelData[sGridNo].ubExtFlags[pSoldier->pathing.bLevel] & (MAPELEMENT_EXT_SMOKE | MAPELEMENT_EXT_SIGNAL_SMOKE | MAPELEMENT_EXT_DEBRIS_SMOKE | MAPELEMENT_EXT_FIRERETARDANT_SMOKE ) ) if ( gpWorldLevelData[sGridNo].ubExtFlags[pSoldier->pathing.bLevel] & (MAPELEMENT_EXT_SMOKE | MAPELEMENT_EXT_SIGNAL_SMOKE | MAPELEMENT_EXT_DEBRIS_SMOKE | MAPELEMENT_EXT_FIRERETARDANT_SMOKE ) )
return TRUE; return TRUE;
@@ -2297,6 +2349,12 @@ BOOLEAN InGas(SOLDIERTYPE *pSoldier, INT32 sGridNo)
if (TileIsOutOfBounds(sGridNo)) if (TileIsOutOfBounds(sGridNo))
return FALSE; return FALSE;
// Armed vehicles and robots do not care about gas or smoke
if (ARMED_VEHICLE(pSoldier) || ENEMYROBOT(pSoldier))
{
return FALSE;
}
if (InGasSpot(pSoldier, sGridNo, pSoldier->pathing.bLevel)) if (InGasSpot(pSoldier, sGridNo, pSoldier->pathing.bLevel))
{ {
return TRUE; return TRUE;
@@ -2655,7 +2713,7 @@ INT32 CalcManThreatValue( SOLDIERTYPE *pEnemy, INT32 sMyGrid, UINT8 ubReduceForC
} }
// in boxing mode, let only a boxer be considered a threat. // in boxing mode, let only a boxer be considered a threat.
if ( (gTacticalStatus.bBoxingState == BOXING) && !(pEnemy->flags.uiStatusFlags & SOLDIER_BOXER) ) if ( (gTacticalStatus.bBoxingState == BOXING) && !BOXER(pEnemy) )
{ {
iThreatValue = -999; iThreatValue = -999;
return( iThreatValue ); return( iThreatValue );
@@ -3896,8 +3954,6 @@ INT8 CalcMoraleNew(SOLDIERTYPE *pSoldier)
bMoraleCategory ++; bMoraleCategory ++;
} }
INT32 sClosestOpponent = ClosestKnownOpponent(pSoldier, NULL, NULL);
// if last attack of this soldier hit enemy - increase morale // if last attack of this soldier hit enemy - increase morale
if( pSoldier->aiData.bLastAttackHit ) if( pSoldier->aiData.bLastAttackHit )
{ {
@@ -4576,6 +4632,61 @@ UINT8 RedSmokeDanger(INT32 sGridNo, INT8 bLevel)
return ubDangerPercent; return ubDangerPercent;
} }
BOOLEAN FindClosestVisibleSmoke(SOLDIERTYPE* pSoldier, INT32& sSpot, INT8& bLevel, BOOLEAN fOnlyGas)
{
CHECKF(pSoldier);
INT32 sDist;
INT32 sClosestDist = INT32_MAX;
INT32 sCheckSpot;
INT8 bCheckLevel;
sSpot = NOWHERE;
bLevel = 0;
//loop through all smoke effects and find closest visible
for ( UINT32 uiCnt = 0; uiCnt < guiNumSmokeEffects; uiCnt++ )
{
if ( gSmokeEffectData[uiCnt].fAllocated &&
!TileIsOutOfBounds(gSmokeEffectData[uiCnt].sGridNo) )
{
// ignore smoke if not dangerous
if ( fOnlyGas &&
gSmokeEffectData[uiCnt].bType != TEARGAS_SMOKE_EFFECT &&
gSmokeEffectData[uiCnt].bType != MUSTARDGAS_SMOKE_EFFECT &&
gSmokeEffectData[uiCnt].bType != CREATURE_SMOKE_EFFECT )
{
continue;
}
sCheckSpot = gSmokeEffectData[uiCnt].sGridNo;
if ( gSmokeEffectData[uiCnt].bFlags & SMOKE_EFFECT_ON_ROOF )
bCheckLevel = 1;
else
bCheckLevel = 0;
sDist = PythSpacesAway(sCheckSpot, pSoldier->sGridNo);
if ( sDist < DAY_VISION_RANGE &&
SoldierToVirtualSoldierLineOfSightTest(pSoldier, sCheckSpot, bCheckLevel, ANIM_PRONE, 0, CALC_FROM_ALL_DIRS) &&
(sSpot == NOWHERE || sDist < sClosestDist) )
{
sClosestDist = sDist;
sSpot = sCheckSpot;
bLevel = bCheckLevel;
}
}
}
if ( !TileIsOutOfBounds(sSpot) )
{
return TRUE;
}
return FALSE;
}
// check if artillery strike was ordered by any team // check if artillery strike was ordered by any team
BOOLEAN CheckArtilleryStrike(void) BOOLEAN CheckArtilleryStrike(void)
{ {
@@ -4784,7 +4895,7 @@ BOOLEAN ValidOpponent(SOLDIERTYPE* pSoldier, SOLDIERTYPE* pOpponent)
pSoldier->bSide == pOpponent->bSide || pSoldier->bSide == pOpponent->bSide ||
pSoldier->aiData.bAttitude == ATTACKSLAYONLY && pOpponent->ubProfile != SLAY || pSoldier->aiData.bAttitude == ATTACKSLAYONLY && pOpponent->ubProfile != SLAY ||
(pOpponent->ubWhatKindOfMercAmI == MERC_TYPE__VEHICLE && GetNumberInVehicle(pOpponent->bVehicleID) == 0) || (pOpponent->ubWhatKindOfMercAmI == MERC_TYPE__VEHICLE && GetNumberInVehicle(pOpponent->bVehicleID) == 0) ||
gTacticalStatus.bBoxingState == BOXING && (pSoldier->flags.uiStatusFlags & SOLDIER_BOXER) && !(pOpponent->flags.uiStatusFlags & SOLDIER_BOXER) || gTacticalStatus.bBoxingState == BOXING && BOXER(pSoldier) && !BOXER(pOpponent) ||
pOpponent->ubBodyType == CROW) pOpponent->ubBodyType == CROW)
{ {
return FALSE; return FALSE;
@@ -4893,7 +5004,7 @@ BOOLEAN SoldierAI(SOLDIERTYPE *pSoldier)
if (!IS_MERC_BODY_TYPE(pSoldier) || if (!IS_MERC_BODY_TYPE(pSoldier) ||
pSoldier->aiData.bNeutral || pSoldier->aiData.bNeutral ||
fCivilian || fCivilian ||
pSoldier->flags.uiStatusFlags & SOLDIER_BOXER || BOXER(pSoldier) ||
ARMED_VEHICLE(pSoldier) || ARMED_VEHICLE(pSoldier) ||
pSoldier->flags.uiStatusFlags & SOLDIER_VEHICLE || pSoldier->flags.uiStatusFlags & SOLDIER_VEHICLE ||
AM_A_ROBOT(pSoldier) || AM_A_ROBOT(pSoldier) ||
@@ -6229,3 +6340,221 @@ BOOLEAN CheckSuppressionDirection(SOLDIERTYPE *pSoldier, INT32 sTargetGridNo, IN
return TRUE; return TRUE;
} }
UINT8 CountKnownEnemies(SOLDIERTYPE* pSoldier, INT32 sSpot, INT16 sDistance, INT8 bLevel)
{
CHECKF(pSoldier);
SOLDIERTYPE* pOpponent;
INT32 sThreatLoc;
INT8 bThreatLevel;
UINT8 ubNum = 0;
// loop through all the enemies
for ( UINT32 uiLoop = 0; uiLoop < guiNumMercSlots; ++uiLoop )
{
pOpponent = MercSlots[uiLoop];
// if this merc is inactive, at base, on assignment, dead, unconscious
if ( !pOpponent || pOpponent->stats.bLife < OKLIFE )
{
continue;
}
if ( !ValidOpponent(pSoldier, pOpponent) )
{
continue;
}
// check knowledge
if ( Knowledge(pSoldier, pOpponent->ubID) == NOT_HEARD_OR_SEEN )
{
continue;
}
sThreatLoc = KnownLocation(pSoldier, pOpponent->ubID);
bThreatLevel = KnownLevel(pSoldier, pOpponent->ubID);
if ( TileIsOutOfBounds(sThreatLoc) )
{
continue;
}
if ( PythSpacesAway(sSpot, sThreatLoc) > sDistance )
{
continue;
}
if ( bLevel >= 0 && bThreatLevel != bLevel )
{
continue;
}
ubNum++;
}
return ubNum;
}
UINT8 CountKnownEnemiesInRoom(SOLDIERTYPE* pSoldier, UINT16 usRoom)
{
CHECKF(pSoldier);
UINT8 ubNum = 0;
for ( UINT32 uiLoop = 0; uiLoop < guiNumMercSlots; ++uiLoop )
{
SOLDIERTYPE* pOpponent = MercSlots[uiLoop];
// if this merc is inactive, at base, on assignment, dead, unconscious
if ( !pOpponent || pOpponent->stats.bLife < OKLIFE )
{
continue;
}
if ( !ValidOpponent(pSoldier, pOpponent) )
{
continue;
}
// check public knowledge
if ( Knowledge(pSoldier, pOpponent->ubID) == NOT_HEARD_OR_SEEN )
{
continue;
}
INT32 sThreatLoc = KnownLocation(pSoldier, pOpponent->ubID);
if ( TileIsOutOfBounds(sThreatLoc) )
{
continue;
}
// check room
UINT16 usRoomNo;
if ( !InARoom(sThreatLoc, &usRoomNo) )
{
continue;
}
if ( usRoomNo != usRoom )
{
continue;
}
ubNum++;
}
return ubNum;
}
UINT8 CountFriendsInRoom(SOLDIERTYPE* pSoldier, UINT16 usRoom)
{
CHECKF(pSoldier);
SOLDIERTYPE* pFriend;
UINT8 ubFriendCount = 0;
UINT16 usRoomNo;
// Run through each friendly.
for ( SoldierID iCounter = gTacticalStatus.Team[pSoldier->bTeam].bFirstID; iCounter <= gTacticalStatus.Team[pSoldier->bTeam].bLastID; ++iCounter )
{
pFriend = iCounter;
if ( pFriend &&
pFriend != pSoldier &&
pFriend->bActive &&
pFriend->stats.bLife >= OKLIFE &&
InARoom(pFriend->sGridNo, &usRoomNo) &&
usRoomNo == usRoom )
{
ubFriendCount++;
}
}
return ubFriendCount;
}
INT32 CountCorpsesInRoom(SOLDIERTYPE* pSoldier, UINT16 usRoomNo, INT8 bLevel)
{
CHECKF(pSoldier);
ROTTING_CORPSE* pCorpse;
INT32 iCount = 0;
for ( INT32 cnt = 0; cnt < giNumRottingCorpse; ++cnt )
{
pCorpse = &(gRottingCorpse[cnt]);
if ( pCorpse &&
pCorpse->fActivated &&
pCorpse->def.ubType < ROTTING_STAGE2 &&
pCorpse->def.ubBodyType <= REGFEMALE &&
pCorpse->def.ubAIWarningValue > 0 &&
pCorpse->def.bLevel == bLevel &&
!TileIsOutOfBounds(pCorpse->def.sGridNo) &&
RoomNo(pCorpse->def.sGridNo) == usRoomNo &&
(pSoldier->bTeam == ENEMY_TEAM && CorpseEnemyTeam(pCorpse) || pSoldier->bTeam == MILITIA_TEAM && CorpseMilitiaTeam(pCorpse) || pSoldier->bTeam == CIV_TEAM && !pSoldier->aiData.bNeutral) )
{
iCount++;
}
}
return iCount;
}
BOOLEAN FindFenceAroundSpot(INT32 sSpot)
{
if ( TileIsOutOfBounds(sSpot) )
{
return FALSE;
}
INT32 sTempSpot;
// check adjacent locations
for ( UINT8 ubDirection = 0; ubDirection < NUM_WORLD_DIRECTIONS; ubDirection++ )
{
sTempSpot = NewGridNo(sSpot, DirectionInc(ubDirection));
if ( sTempSpot != sSpot && IsCuttableWireFenceAtGridNo(sTempSpot) )
{
return TRUE;
}
}
return FALSE;
}
BOOLEAN SameRoom(INT32 sSpot1, INT32 sSpot2)
{
if ( RoomNo(sSpot1) == RoomNo(sSpot2) && sSpot1 != NO_ROOM )
return TRUE;
return FALSE;
}
UINT16 RoomNo(INT32 sSpot)
{
if ( TileIsOutOfBounds(sSpot) )
return NO_ROOM;
return gusWorldRoomInfo[sSpot];
}
BOOLEAN CheckWindow(INT32 sSpot, UINT8 ubDirection, BOOLEAN fAllowClosed)
{
CHECKF(!TileIsOutOfBounds(sSpot));
// find window spot
INT32 sWindowSpot = sSpot;
if ( ubDirection == NORTH || ubDirection == WEST )
sWindowSpot = NewGridNo(sSpot, (UINT16)DirectionInc(ubDirection));
//if (IsJumpableWindowPresentAtGridNo(sWindowSpot, ubDirection, gGameExternalOptions.fCanJumpThroughClosedWindows))
if ( IsJumpableWindowPresentAtGridNo(sWindowSpot, ubDirection, fAllowClosed) )
{
return TRUE;
}
return FALSE;
}
+685 -1
View File
@@ -2207,7 +2207,7 @@ INT32 EstimateShotDamage(SOLDIERTYPE *pSoldier, SOLDIERTYPE *pOpponent, INT16 ub
// check for ceramic plates; these do affect monster spit // check for ceramic plates; these do affect monster spit
for (attachmentList::iterator iter = pOpponent->inv[VESTPOS][0]->attachments.begin(); iter != pOpponent->inv[VESTPOS][0]->attachments.end(); ++iter) { for (attachmentList::iterator iter = pOpponent->inv[VESTPOS][0]->attachments.begin(); iter != pOpponent->inv[VESTPOS][0]->attachments.end(); ++iter) {
if ( iter->exists() && Item[iter->usItem].usItemClass == IC_ARMOUR && (*iter)[0]->data.objectStatus > 0 ) if (Item[iter->usItem].usItemClass == IC_ARMOUR && (*iter)[0]->data.objectStatus > 0 && iter->exists() )
{ {
iTorsoProt += (INT32) Armour[Item[iter->usItem].ubClassIndex].ubProtection * iTorsoProt += (INT32) Armour[Item[iter->usItem].ubClassIndex].ubProtection *
(INT32) (*iter)[0]->data.objectStatus / 100; (INT32) (*iter)[0]->data.objectStatus / 100;
@@ -4234,3 +4234,687 @@ void CheckTossGrenadeAt(SOLDIERTYPE *pSoldier, ATTACKTYPE *pBestThrow, INT32 sTa
pSoldier->bWeaponMode = WM_NORMAL; pSoldier->bWeaponMode = WM_NORMAL;
} }
static void CalculatePossibleShots(SOLDIERTYPE* pSoldier, INT8 gun, std::vector<ATTACKTYPE> &possibleShots)
{
UINT32 uiLoop;
INT32 iAttackValue, iThreatValue, iHitRate, iBestHitRate, iPercentBetter, iEstDamage, iTrueLastTarget;
UINT16 usTrueState, usTurningCost, usRaiseGunCost;
INT16 sMinAPcost;
INT16 sRawAPCost;
INT16 sAimAPCost;
INT16 sBestAPcost;
INT16 sChanceToHit;
INT16 sAimTime;
INT16 sBestAimTime;
INT16 sMaxPossibleAimTime;
UINT8 ubChanceToGetThrough;
UINT8 ubBestChanceToGetThrough;
UINT8 ubFriendlyFireChance;
UINT8 ubBestFriendlyFireChance;
INT16 sBestChanceToHit;
INT16 sStanceAPcost;
BOOLEAN fAddingTurningCost, fAddingRaiseGunCost;
UINT8 ubStance, ubBestStance, ubChanceToReallyHit;
INT8 bScopeMode;
SOLDIERTYPE* pOpponent;
// sevenfm:
BOOLEAN fSuppression = FALSE;
BOOLEAN fReturnFire = FALSE;
INT32 sTarget = NOWHERE;
INT8 bLevel;
INT8 bKnowledge;
INT8 bPersonalKnowledge;
INT8 bPublicKnowledge;
DebugMsg(TOPIC_JA2, DBG_LEVEL_3, "CalcBestShot");
sBestChanceToHit = sBestAimTime = sChanceToHit = ubBestChanceToGetThrough = ubBestFriendlyFireChance = ubChanceToReallyHit = 0;
// sevenfm: set attacking hand and target
pSoldier->ubAttackingHand = HANDPOS;
pSoldier->ubTargetID = NOBODY;
pSoldier->usAttackingWeapon = pSoldier->inv[HANDPOS].usItem;
pSoldier->bWeaponMode = WM_NORMAL;
std::map<INT8, OBJECTTYPE*> ObjList;
GetScopeLists(pSoldier, &pSoldier->inv[HANDPOS], ObjList);
pSoldier->bScopeMode = USE_BEST_SCOPE;
pSoldier->bDoBurst = 0;
pSoldier->bDoAutofire = 0;
// determine which attack against which target has the greatest attack value
for (uiLoop = 0; uiLoop < guiNumMercSlots; uiLoop++)
{
ATTACKTYPE pBestShot;
pBestShot.ubPossible = FALSE;
pBestShot.ubChanceToReallyHit = 0;
pBestShot.iAttackValue = 0;
pBestShot.ubOpponent = NOBODY;
pBestShot.ubFriendlyFireChance = 0;
pBestShot.bWeaponIn = gun;
pOpponent = MercSlots[uiLoop];
fSuppression = FALSE;
fReturnFire = FALSE;
// if this merc is inactive, at base, on assignment, or dead
if (!pOpponent || !pOpponent->stats.bLife)
continue; // next merc
if (!ValidOpponent(pSoldier, pOpponent))
{
continue;
}
// determine return fire
if (pSoldier->aiData.bUnderFire &&
!pSoldier->bBlindedCounter &&
pSoldier->ubPreviousAttackerID == pOpponent->ubID)
{
fReturnFire = TRUE;
}
bKnowledge = Knowledge(pSoldier, pOpponent->ubID);
bPersonalKnowledge = PersonalKnowledge(pSoldier, pOpponent->ubID);
bPublicKnowledge = PublicKnowledge(pSoldier->bTeam, pOpponent->ubID);
// check knowledge
if (bKnowledge != SEEN_CURRENTLY &&
bKnowledge != SEEN_THIS_TURN &&
bKnowledge != SEEN_LAST_TURN &&
bKnowledge != HEARD_THIS_TURN &&
bKnowledge != HEARD_LAST_TURN &&
!((bKnowledge == SEEN_2_TURNS_AGO || bKnowledge == SEEN_3_TURNS_AGO || bKnowledge == HEARD_2_TURNS_AGO) && Weapon[pSoldier->usAttackingWeapon].ubWeaponType == GUN_LMG))
{
continue; // next opponent
}
// sevenfm: blind soldier can only attack seen/heard personally
if (pSoldier->bBlindedCounter > 0 &&
bPersonalKnowledge != SEEN_THIS_TURN &&
bPersonalKnowledge != HEARD_THIS_TURN)
{
continue; // next opponent
}
// sevenfm: determine if we shoot on unseen target for suppression
if (bPersonalKnowledge != SEEN_CURRENTLY &&
bPublicKnowledge != SEEN_CURRENTLY &&
//!SoldierToSoldierLineOfSightTest(pSoldier, pOpponent, TRUE, CALC_FROM_ALL_DIRS))
!LOS_Raised(pSoldier, pOpponent, CALC_FROM_ALL_DIRS))
{
fSuppression = TRUE;
}
// sevenfm: shooting at unseen opponents is optional
if (fSuppression && !gGameExternalOptions.fAIShootUnseen)
{
continue; // next opponent
}
// determine enemy location
if (fSuppression)
{
// personal/public knowledge
sTarget = KnownLocation(pSoldier, pOpponent->ubID);
bLevel = KnownLevel(pSoldier, pOpponent->ubID);
// try to randomize location
sTarget = RandomizeLocation(sTarget, bLevel, 1, pSoldier);
}
else
{
// we know exact enemy location
sTarget = pOpponent->sGridNo;
bLevel = pOpponent->pathing.bLevel;
}
// safety check
if (TileIsOutOfBounds(sTarget))
{
continue;
}
// skip if we can see location and location is empty
if (SoldierToVirtualSoldierLineOfSightTest(pSoldier, sTarget, bLevel, ANIM_PRONE, TRUE, CALC_FROM_ALL_DIRS) &&
WhoIsThere2(sTarget, bLevel) == NOBODY)
{
continue;
}
// no fire on unseen opponents with throwing knives
if ((Item[pSoldier->usAttackingWeapon].usItemClass & IC_THROWING_KNIFE) &&
bPersonalKnowledge != SEEN_CURRENTLY &&
//!SoldierToSoldierLineOfSightTest(pSoldier, pOpponent, TRUE, CALC_FROM_ALL_DIRS))
!LOS_Raised(pSoldier, pOpponent, CALC_FROM_ALL_DIRS))
{
continue;
}
// sevenfm: only enemy team can use blind suppression fire
if (fSuppression &&
pSoldier->bTeam != ENEMY_TEAM)
{
continue;
}
// sevenfm: only try to suppress alive and conscious human targets
if (fSuppression &&
(pOpponent->stats.bLife < OKLIFE ||
pOpponent->bCollapsed && pOpponent->bBreath == 0 ||
pOpponent->IsCowering() ||
pOpponent->IsZombie() ||
!IS_MERC_BODY_TYPE(pOpponent)))
{
continue;
}
// shoot through wall check
if (bPersonalKnowledge != SEEN_CURRENTLY &&
//!SoldierToSoldierLineOfSightTest(pSoldier, pOpponent, TRUE, CALC_FROM_ALL_DIRS) &&
!LOS_Raised(pSoldier, pOpponent, CALC_FROM_ALL_DIRS) &&
!SoldierToVirtualSoldierLineOfSightTest(pSoldier, sTarget, bLevel, ANIM_STAND, TRUE, NO_DISTANCE_LIMIT) &&
!LocationToLocationLineOfSightTest(pSoldier->sGridNo, pSoldier->pathing.bLevel, sTarget, bLevel, TRUE, NO_DISTANCE_LIMIT) &&
!fReturnFire &&
!(InARoom(sTarget, NULL) && bLevel == 0 && Weapon[pSoldier->usAttackingWeapon].ubWeaponType == GUN_LMG) && // bPublicKnowledge == SEEN_CURRENTLY &&
!(InARoom(sTarget, NULL) && bLevel == 0 && TeamPercentKilled(pSoldier->bTeam) > (100 - 20 * SoldierDifficultyLevel(pSoldier))))
{
continue;
}
#ifdef DEBUGATTACKS
DebugAI(String("%s sees %s at gridno %d\n", pSoldier->GetName(), ExtMen[pOpponent->ubID].GetName(), pOpponent->sGridNo));
#endif
sMinAPcost = MinAPsToAttack(pSoldier, sTarget, DONTADDTURNCOST, 0);
// later will be decide if shoot is possible this here is just best guess so ignore turnover
// if we don't have enough APs left to shoot even a snap-shot at this guy
if (sMinAPcost > pSoldier->bActionPoints)
continue; // next opponent
// sevenfm: check CTGT and friendly fire for each stance instead since they can be different
// calculate chance to get through the opponent's cover (if any)
//dnl ch61 180813
/*gUnderFire.Clear();
gUnderFire.Enable();
ubChanceToGetThrough = AISoldierToSoldierChanceToGetThrough( pSoldier, pOpponent );
ubFriendlyFireChance = gUnderFire.Chance(pSoldier->bTeam, pSoldier->bSide, TRUE);
gUnderFire.Disable();
// if we can't possibly get through all the cover
if (ubChanceToGetThrough == 0)
continue; // next opponent
// sevenfm: ignore opponent if we can hit friend
if (ubFriendlyFireChance > MIN_CHANCE_TO_ACCIDENTALLY_HIT_SOMEONE)
continue;*/
if ((pSoldier->flags.uiStatusFlags & SOLDIER_MONSTER) && (pSoldier->ubBodyType != QUEENMONSTER))
{
STRUCTURE_FILE_REF* pStructureFileRef;
UINT16 usAnimSurface;
usAnimSurface = DetermineSoldierAnimationSurface(pSoldier, pSoldier->usUIMovementMode);
pStructureFileRef = GetAnimationStructureRef(pSoldier->ubID, usAnimSurface, pSoldier->usUIMovementMode);
if (pStructureFileRef)
{
UINT16 usStructureID;
INT8 bDir;
// must make sure that structure data can be added in the direction of the target
bDir = (INT8)GetDirectionToGridNoFromGridNo(pSoldier->sGridNo, sTarget);
// ATE: Only if we have a levelnode...
if (pSoldier->pLevelNode != NULL && pSoldier->pLevelNode->pStructureData != NULL)
{
usStructureID = pSoldier->pLevelNode->pStructureData->usStructureID;
}
else
{
usStructureID = INVALID_STRUCTURE_ID;
}
if (!OkayToAddStructureToWorld(pSoldier->sGridNo, pSoldier->pathing.bLevel, &(pStructureFileRef->pDBStructureRef[gOneCDirection[bDir]]), usStructureID))
{
// can't turn in that dir.... next opponent
continue;
}
}
}
iBestHitRate = 0; // reset best hit rate to minimum
//dnl ch69 130913 Hoping to optimize
// consider alternate holding mode and different scopes
// sevenfm: alt weapon holding scope mode is used only when ubAllowAlternativeWeaponHolding == 3
for (pSoldier->bScopeMode = (gGameExternalOptions.ubAllowAlternativeWeaponHolding == 3 ? USE_ALT_WEAPON_HOLD : USE_BEST_SCOPE);
pSoldier->bScopeMode <= (gGameExternalOptions.fScopeModes ? NUM_SCOPE_MODES - 1 : USE_BEST_SCOPE);
pSoldier->bScopeMode++)
{
if (pSoldier->bScopeMode == USE_ALT_WEAPON_HOLD)
{
//dnl ch71 180913 throwing knives cannot be used in fire from hip
if (Item[pSoldier->usAttackingWeapon].usItemClass & IC_THROWING_KNIFE)
continue;
// sevenfm: hip firing allowed only for human bodytypes
if (!IS_MERC_BODY_TYPE(pSoldier))
continue;
}
if (pSoldier->bScopeMode == USE_ALT_WEAPON_HOLD || (pSoldier->bScopeMode >= USE_BEST_SCOPE && ObjList[pSoldier->bScopeMode] != NULL))
{
usTrueState = pSoldier->usAnimState; // because is used in CalculateRaiseGunCost, CalcAimingLevelsAvailableWithAP, CalculateTurningCost
iTrueLastTarget = pSoldier->sLastTarget; // because is used in MinAPsToShootOrStab
// --------- Standing ---------
ubStance = ANIM_STAND;
// sevenfm: take into account direction when checking stance
// sevenfm: shoot heavy guns in standing stance only when using hip fire
if (pSoldier->InternalIsValidStance(AIDirection(pSoldier->sGridNo, sTarget), ubStance) &&
(pSoldier->bScopeMode == USE_ALT_WEAPON_HOLD || !Weapon[pSoldier->usAttackingWeapon].HeavyGun || !ItemIsTwoHanded(pSoldier->usAttackingWeapon) || !gGameExternalOptions.ubAllowAlternativeWeaponHolding))
{
sStanceAPcost = GetAPsToChangeStance(pSoldier, ubStance);
if (sStanceAPcost)
{
// Going up so first is stance change then turnover, do animation change before APs calculation
pSoldier->usAnimState = STANDING;
pSoldier->sLastTarget = NOWHERE;
}
GetAPChargeForShootOrStabWRTGunRaises(pSoldier, sTarget, TRUE, &fAddingTurningCost, &fAddingRaiseGunCost, 0);
usTurningCost = CalculateTurningCost(pSoldier, pSoldier->usAttackingWeapon, fAddingTurningCost);
usRaiseGunCost = CalculateRaiseGunCost(pSoldier, fAddingRaiseGunCost, sTarget, 0);
if (fAddingTurningCost && fAddingRaiseGunCost)//dnl ch71 180913
{
if (usRaiseGunCost > usTurningCost)
usTurningCost = 0;
else
usRaiseGunCost = 0;
}
sRawAPCost = MinAPsToShootOrStab(pSoldier, sTarget, 0, FALSE, 2);
sMinAPcost = sRawAPCost + usTurningCost + sStanceAPcost + usRaiseGunCost;
if (pSoldier->bActionPoints - sMinAPcost >= 0)
{
// calc next attack's minimum shooting cost (excludes readying & turning & raise gun)
sMaxPossibleAimTime = CalcAimingLevelsAvailableWithAP(pSoldier, sTarget, pSoldier->bActionPoints - sMinAPcost);
// sevenfm: check CTGT and friendly fire chance for every stance
gUnderFire.Clear();
gUnderFire.Enable();
if (fSuppression)
ubChanceToGetThrough = SoldierToLocationChanceToGetThrough(pSoldier, sTarget, bLevel, 3, NOBODY);
else
ubChanceToGetThrough = SoldierToSoldierChanceToGetThrough(pSoldier, pOpponent);
ubFriendlyFireChance = gUnderFire.Chance(pSoldier->bTeam, pSoldier->bSide, TRUE);
gUnderFire.Disable();
// sevenfm: only use this stance if we can hit target and cannot hit friends
if (ubChanceToGetThrough > 0 && ubFriendlyFireChance <= MIN_CHANCE_TO_ACCIDENTALLY_HIT_SOMEONE)
{
for (sAimTime = 0; sAimTime <= sMaxPossibleAimTime; sAimTime++)
{
sChanceToHit = AICalcChanceToHitGun(pSoldier, sTarget, sAimTime, AIM_SHOT_TORSO, bLevel, STANDING);
sAimAPCost = CalcAPCostForAiming(pSoldier, sTarget, (INT8)sAimTime);
iHitRate = sChanceToHit * (pSoldier->bActionPoints - (sMinAPcost - sRawAPCost)) / (sRawAPCost + sAimAPCost);
// sevenfm: take into account CTGT for every stance
if (iHitRate * ubChanceToGetThrough > iBestHitRate * ubBestChanceToGetThrough ||
(Item[pSoldier->usAttackingWeapon].usItemClass & IC_THROWING_KNIFE) && sChanceToHit > sBestChanceToHit)// rather take best chance for throwing knives
{
iBestHitRate = iHitRate;
sBestAimTime = sAimTime;
sBestChanceToHit = sChanceToHit;
ubBestChanceToGetThrough = ubChanceToGetThrough;
ubBestFriendlyFireChance = ubFriendlyFireChance;
bScopeMode = pSoldier->bScopeMode;
sBestAPcost = sMinAPcost;
ubBestStance = ubStance;
}
}
}
}
pSoldier->usAnimState = usTrueState;
pSoldier->sLastTarget = iTrueLastTarget;
}
// no crouched/prone if we are tank/using throwing knife/hip firing
if (pSoldier->bScopeMode == USE_ALT_WEAPON_HOLD || ARMED_VEHICLE(pSoldier) || ENEMYROBOT(pSoldier) || (Item[pSoldier->usAttackingWeapon].usItemClass & IC_THROWING_KNIFE))
continue;
// --------- Crouched ---------
ubStance = ANIM_CROUCH;
// sevenfm: take into account direction
if (pSoldier->InternalIsValidStance(AIDirection(pSoldier->sGridNo, sTarget), ubStance))
{
// change stance then turn
sStanceAPcost = GetAPsToChangeStance(pSoldier, ubStance);
if (sStanceAPcost)
{
pSoldier->usAnimState = CROUCHING;
pSoldier->sLastTarget = NOWHERE;
}
GetAPChargeForShootOrStabWRTGunRaises(pSoldier, sTarget, TRUE, &fAddingTurningCost, &fAddingRaiseGunCost, 0);
usTurningCost = CalculateTurningCost(pSoldier, pSoldier->usAttackingWeapon, fAddingTurningCost);
usRaiseGunCost = CalculateRaiseGunCost(pSoldier, fAddingRaiseGunCost, sTarget, 0);
if (fAddingTurningCost && fAddingRaiseGunCost)//dnl ch71 180913
{
if (usRaiseGunCost > usTurningCost)
usTurningCost = 0;
else
usRaiseGunCost = 0;
}
sRawAPCost = MinAPsToShootOrStab(pSoldier, sTarget, 0, FALSE, 2);
sMinAPcost = sRawAPCost + usTurningCost + sStanceAPcost + usRaiseGunCost;
if (pSoldier->bActionPoints - sMinAPcost >= 0)
{
sMaxPossibleAimTime = CalcAimingLevelsAvailableWithAP(pSoldier, sTarget, pSoldier->bActionPoints - sMinAPcost);
// sevenfm: check CTGT and friendly fire chance for every stance
gUnderFire.Clear();
gUnderFire.Enable();
if (fSuppression)
ubChanceToGetThrough = SoldierToLocationChanceToGetThrough(pSoldier, sTarget, bLevel, 3, NOBODY);
else
ubChanceToGetThrough = SoldierToSoldierChanceToGetThrough(pSoldier, pOpponent);
ubFriendlyFireChance = gUnderFire.Chance(pSoldier->bTeam, pSoldier->bSide, TRUE);
gUnderFire.Disable();
// sevenfm: only use this stance if we can hit target and cannot hit friends
if (ubChanceToGetThrough > 0 && ubFriendlyFireChance <= MIN_CHANCE_TO_ACCIDENTALLY_HIT_SOMEONE)
{
for (sAimTime = 0; sAimTime <= sMaxPossibleAimTime; sAimTime++)
{
sChanceToHit = AICalcChanceToHitGun(pSoldier, sTarget, sAimTime, AIM_SHOT_TORSO, bLevel, CROUCHING);
sAimAPCost = CalcAPCostForAiming(pSoldier, sTarget, (INT8)sAimTime);
iHitRate = sChanceToHit * (pSoldier->bActionPoints - (sMinAPcost - sRawAPCost)) / (sRawAPCost + sAimAPCost);
// sevenfm: take into account CTGT for every stance
if (iHitRate * ubChanceToGetThrough > iBestHitRate * ubBestChanceToGetThrough)
{
iBestHitRate = iHitRate;
sBestAimTime = sAimTime;
sBestChanceToHit = sChanceToHit;
ubBestChanceToGetThrough = ubChanceToGetThrough;
ubBestFriendlyFireChance = ubFriendlyFireChance;
bScopeMode = pSoldier->bScopeMode;
sBestAPcost = sMinAPcost;
ubBestStance = ubStance;
}
}
}
}
pSoldier->usAnimState = usTrueState;
pSoldier->sLastTarget = iTrueLastTarget;
}
// no prone stance if we have to change direction and stance at the same time
if (pSoldier->ubDirection != AIDirection(pSoldier->sGridNo, sTarget) &&
gAnimControl[pSoldier->usAnimState].ubEndHeight > ANIM_PRONE)
{
continue;
}
// --------- Prone ---------
ubStance = ANIM_PRONE;
if (pSoldier->InternalIsValidStance(AIDirection(pSoldier->sGridNo, sTarget), ubStance))
{
sStanceAPcost = GetAPsToChangeStance(pSoldier, ubStance);
if (sStanceAPcost)
{
pSoldier->usAnimState = PRONE;
pSoldier->sLastTarget = NOWHERE;
}
GetAPChargeForShootOrStabWRTGunRaises(pSoldier, sTarget, TRUE, &fAddingTurningCost, &fAddingRaiseGunCost, 0);
usTurningCost = CalculateTurningCost(pSoldier, pSoldier->usAttackingWeapon, fAddingTurningCost);
usRaiseGunCost = CalculateRaiseGunCost(pSoldier, fAddingRaiseGunCost, sTarget, 0);
sRawAPCost = MinAPsToShootOrStab(pSoldier, sTarget, 0, FALSE, 2);
sMinAPcost = sRawAPCost + usTurningCost + sStanceAPcost + usRaiseGunCost;
if (pSoldier->bActionPoints - sMinAPcost >= 0)
{
sMaxPossibleAimTime = CalcAimingLevelsAvailableWithAP(pSoldier, sTarget, pSoldier->bActionPoints - sMinAPcost);
// sevenfm: check CTGT and friendly fire chance for every stance
gUnderFire.Clear();
gUnderFire.Enable();
if (fSuppression)
ubChanceToGetThrough = SoldierToLocationChanceToGetThrough(pSoldier, sTarget, bLevel, 3, NOBODY);
else
ubChanceToGetThrough = SoldierToSoldierChanceToGetThrough(pSoldier, pOpponent);
ubFriendlyFireChance = gUnderFire.Chance(pSoldier->bTeam, pSoldier->bSide, TRUE);
gUnderFire.Disable();
// sevenfm: only use this stance if we can hit target and cannot hit friends
if (ubChanceToGetThrough > 0 && ubFriendlyFireChance <= MIN_CHANCE_TO_ACCIDENTALLY_HIT_SOMEONE)
{
for (sAimTime = 0; sAimTime <= sMaxPossibleAimTime; sAimTime++)
{
sChanceToHit = AICalcChanceToHitGun(pSoldier, sTarget, sAimTime, AIM_SHOT_TORSO, bLevel, PRONE);
sAimAPCost = CalcAPCostForAiming(pSoldier, sTarget, (INT8)sAimTime);
iHitRate = sChanceToHit * (pSoldier->bActionPoints - (sMinAPcost - sRawAPCost)) / (sRawAPCost + sAimAPCost);
// sevenfm: take into account CTGT for every stance
if (iHitRate * ubChanceToGetThrough > iBestHitRate * ubBestChanceToGetThrough)
{
iBestHitRate = iHitRate;
sBestAimTime = sAimTime;
sBestChanceToHit = sChanceToHit;
ubBestChanceToGetThrough = ubChanceToGetThrough;
ubBestFriendlyFireChance = ubFriendlyFireChance;
bScopeMode = pSoldier->bScopeMode;
sBestAPcost = sMinAPcost;
ubBestStance = ubStance;
}
}
}
}
pSoldier->usAnimState = usTrueState;
pSoldier->sLastTarget = iTrueLastTarget;
}
}
}
// if we can't get any kind of hit rate at all
if (iBestHitRate == 0)
continue; // next opponent
// calculate chance to REALLY hit: shoot accurately AND get past cover
ubChanceToReallyHit = (UINT8)ceil((sBestChanceToHit * ubBestChanceToGetThrough) / 100.0f);
// if we can't REALLY hit at all
if (ubChanceToReallyHit == 0)
continue; // next opponent
// really limit knife throwing so it doesn't look wrong
if (Item[pSoldier->usAttackingWeapon].usItemClass == IC_THROWING_KNIFE &&
(ubChanceToReallyHit < 25 || (PythSpacesAway(pSoldier->sGridNo, sTarget) > CalcMaxTossRange(pSoldier, pSoldier->usAttackingWeapon, FALSE))))// Madd / 2 ) ) ) //dnl ch69 160913 was ubChanceToReallyHit < 30
continue; // don't bother... next opponent
// calculate this opponent's threat value (factor in my cover from him)
iThreatValue = CalcManThreatValue(pOpponent, pSoldier->sGridNo, TRUE, pSoldier);
// estimate the damage this shot would do to this opponent
iEstDamage = EstimateShotDamage(pSoldier, pOpponent, sBestChanceToHit);
//NumMessage("SHOT EstDamage = ",iEstDamage);
// calculate the combined "attack value" for this opponent
// highest possible value before division should be about 1.8 billion...
// normal value before division should be about 5 million...
iAttackValue = (iEstDamage * iBestHitRate * ubChanceToReallyHit * iThreatValue) / 1000;
//NumMessage("SHOT AttackValue = ",iAttackValue / 1000);
// sevenfm: penalize suppression fire
if (fSuppression)
{
// 25% penalty for shooting at invisible target
iAttackValue = iAttackValue / 2;
}
// special stuff for assassins to ignore militia more
if (pSoldier->IsAssassin() && pOpponent->bTeam == MILITIA_TEAM)
{
iAttackValue /= 2;
}
// sevenfm: empty vehicles have very low priority
if (pOpponent->ubWhatKindOfMercAmI == MERC_TYPE__VEHICLE && GetNumberInVehicle(pOpponent->bVehicleID) == 0)
{
iAttackValue /= 4;
}
// sevenfm: dying, cowering or unconscious soldiers have very low priority
if (pOpponent->stats.bLife < OKLIFE || pOpponent->bCollapsed || pOpponent->bBreathCollapsed)
{
iAttackValue /= 4;
}
#ifdef DEBUGATTACKS
DebugAI(String("CalcBestShot: best AttackValue vs %d = %d\n", uiLoop, iAttackValue));
#endif
// if we can hurt the guy, OR probably not, but at least it's our best
// chance to actually hit him and maybe scare him, knock him down, etc.
if ((iAttackValue > 0) || (ubChanceToReallyHit > pBestShot.ubChanceToReallyHit))
{
#if 0 // if there already was another viable target
if (pBestShot.ubChanceToReallyHit > 0)
{
// OK, how does our chance to hit him compare to the previous best one?
iPercentBetter = ((ubChanceToReallyHit * 100) / pBestShot.ubChanceToReallyHit) - 100;
//dnl ch62 180813 ignore firing into breathless targets if there are targets in better condition
// sevenfm: check that best opponent exists
if (pBestShot.ubOpponent != NOBODY &&
(Menptr[pBestShot.ubOpponent].bCollapsed || Menptr[pBestShot.ubOpponent].bBreathCollapsed) &&
Menptr[pBestShot.ubOpponent].bBreath < OKBREATH
&& Menptr[pBestShot.ubOpponent].bBreath < pOpponent->bBreath)
{
iPercentBetter = PERCENT_TO_IGNORE_THREAT;
}
// sevenfm: if best opponent is dying and new opponent is ok, use new opponent
if (pBestShot.ubOpponent != NOBODY &&
Menptr[pBestShot.ubOpponent].stats.bLife < OKLIFE &&
pOpponent->stats.bLife >= OKLIFE)
{
iPercentBetter = PERCENT_TO_IGNORE_THREAT;
}
// if this chance to really hit is more than 50% worse, and the other
// guy is conscious at all
if (iPercentBetter < -PERCENT_TO_IGNORE_THREAT &&
pBestShot.ubOpponent != NOBODY &&
Menptr[pBestShot.ubOpponent].stats.bLife >= OKLIFE)
{
// then stick with the older guy as the better target
continue;
}
// if this chance to really hit between 50% worse to 50% better
if (iPercentBetter < PERCENT_TO_IGNORE_THREAT)
{
// then the one with the higher ATTACK VALUE is the better target
if (iAttackValue < pBestShot.iAttackValue)
// the previous guy is more important since he's more dangerous
continue; // next opponent
}
}
// sevenfm: if new opponent is dying and best opponent is ok, ignore new opponent
if (pBestShot.ubOpponent != NOBODY &&
Menptr[pBestShot.ubOpponent].stats.bLife >= OKLIFE &&
pOpponent->stats.bLife < OKLIFE)
{
//DebugShot(pSoldier, String("new opponent is dying, best opponent is ok - skip"));
continue;
}
#endif
// OOOF! That was a lot of work! But we've got a new viable target!
pBestShot.ubPossible = TRUE;
pBestShot.ubOpponent = pOpponent->ubID;
pBestShot.ubAimTime = sBestAimTime;
pBestShot.ubChanceToReallyHit = ubChanceToReallyHit;
pBestShot.sTarget = sTarget;
pBestShot.bTargetLevel = bLevel;
pBestShot.iAttackValue = iAttackValue;
pBestShot.ubAPCost = sBestAPcost;
pBestShot.ubStance = ubBestStance;
pBestShot.bScopeMode = bScopeMode;
pBestShot.ubFriendlyFireChance = ubBestFriendlyFireChance;
possibleShots.push_back(pBestShot);
}
}
pSoldier->bScopeMode = USE_BEST_SCOPE; // better reset this back
}
bool CompareAttacks(const ATTACKTYPE& a, const ATTACKTYPE& b)
{
if (a.iAttackValue > b.iAttackValue)
return true;
else if (a.iAttackValue < b.iAttackValue)
return false;
if (a.ubChanceToReallyHit > b.ubChanceToReallyHit)
return true;
else if (a.ubChanceToReallyHit < b.ubChanceToReallyHit)
return false;
if (a.ubFriendlyFireChance < b.ubFriendlyFireChance)
return true;
else if (a.ubFriendlyFireChance > b.ubFriendlyFireChance)
return false;
if (a.ubAPCost < b.ubAPCost)
return true;
else if (a.ubAPCost > b.ubAPCost)
return false;
return false;
}
void CheckIfShotsPossible(SOLDIERTYPE* pSoldier, std::vector<ATTACKTYPE>& possibleShots)
{
INT8 guns[] = { FindAIUsableObjClass(pSoldier, IC_GUN), FindAIUsableObjClass(pSoldier, IC_GUN, TRUE) };
// Only merc bodytypes can use a sidearm, also don't bother to check for targets if found sidearm is the same as main gun
if (!IS_MERC_BODY_TYPE(pSoldier) || guns[0] == guns[1])
{
guns[1] = NO_SLOT;
}
for (size_t i = 0; i < 2; i++)
{
const auto gun = guns[i];
// if the soldier does have a gun
if (gun != NO_SLOT)
{
// if it's in his holster, swap it into his hand temporarily
if (gun != HANDPOS)
{
RearrangePocket(pSoldier, HANDPOS, gun, TEMPORARILY);
}
// get the minimum cost to attack with this item
INT16 ubMinAPcost = MinAPsToAttack(pSoldier, pSoldier->sLastTarget, ADDTURNCOST, 0);
// if we can afford the minimum AP cost
if (pSoldier->bActionPoints >= ubMinAPcost)
{
// then look around for worthy targets
CalculatePossibleShots(pSoldier, gun, possibleShots);
}
// if it was in his holster, swap it back into his holster for now
if (gun != HANDPOS)
{
RearrangePocket(pSoldier, HANDPOS, gun, TEMPORARILY);
}
}
}
}
+2
View File
@@ -14,4 +14,6 @@ set(TacticalAISrc
"${CMAKE_CURRENT_SOURCE_DIR}/QuestDebug.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/QuestDebug.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/Realtime.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/Realtime.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/ZombieDecideAction.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/ZombieDecideAction.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/UtilityAI.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/UtilityAI_ResponseCurve.cpp"
PARENT_SCOPE) PARENT_SCOPE)
+7 -5
View File
@@ -844,7 +844,8 @@ INT8 CreatureDecideActionRed(SOLDIERTYPE *pSoldier, UINT8 ubUnconsciousOK)
{ {
// determine the location of the known closest opponent // determine the location of the known closest opponent
// (don't care if he's conscious, don't care if he's reachable at all) // (don't care if he's conscious, don't care if he's reachable at all)
sClosestOpponent = ClosestKnownOpponent(pSoldier, NULL, NULL); INT32 distanceToThreat;
sClosestOpponent = ClosestKnownOpponent(pSoldier, NULL, NULL, NULL, &distanceToThreat);
if (!TileIsOutOfBounds(sClosestOpponent)) if (!TileIsOutOfBounds(sClosestOpponent))
{ {
@@ -854,9 +855,9 @@ INT8 CreatureDecideActionRed(SOLDIERTYPE *pSoldier, UINT8 ubUnconsciousOK)
// if soldier is not already facing in that direction, // if soldier is not already facing in that direction,
// and the opponent is close enough that he could possibly be seen // and the opponent is close enough that he could possibly be seen
// note, have to change this to use the level returned from ClosestKnownOpponent // note, have to change this to use the level returned from ClosestKnownOpponent
sDistVisible = pSoldier->GetMaxDistanceVisible(sClosestOpponent, 0 ); sDistVisible = pSoldier->GetMaxDistanceVisible(sClosestOpponent, 0 ) * CELL_X_SIZE;
if ((pSoldier->ubDirection != ubOpponentDir) && (PythSpacesAway(pSoldier->sGridNo,sClosestOpponent) <= sDistVisible)) if ((pSoldier->ubDirection != ubOpponentDir) && (distanceToThreat <= sDistVisible))
{ {
// set base chance according to orders // set base chance according to orders
if ((pSoldier->aiData.bOrders == STATIONARY) || (pSoldier->aiData.bOrders == ONGUARD)) if ((pSoldier->aiData.bOrders == STATIONARY) || (pSoldier->aiData.bOrders == ONGUARD))
@@ -1380,11 +1381,12 @@ INT8 CreatureDecideActionBlack( SOLDIERTYPE * pSoldier )
{ {
// determine the location of the known closest opponent // determine the location of the known closest opponent
// (don't care if he's conscious, don't care if he's reachable at all) // (don't care if he's conscious, don't care if he's reachable at all)
sClosestOpponent = ClosestKnownOpponent(pSoldier, NULL, NULL); INT32 distanceToThreat;
sClosestOpponent = ClosestKnownOpponent(pSoldier, NULL, NULL, NULL, &distanceToThreat);
// if we have a closest reachable opponent // if we have a closest reachable opponent
if (!TileIsOutOfBounds(sClosestOpponent)) if (!TileIsOutOfBounds(sClosestOpponent))
{ {
if ( ubCanMove && PythSpacesAway( pSoldier->sGridNo, sClosestOpponent ) > 2 ) if ( ubCanMove && distanceToThreat > 20 ) // 2 tiles -> 20 in fractional Cell Coordinates
{ {
if ( bSpitIn != NO_SLOT ) if ( bSpitIn != NO_SLOT )
{ {
+12611 -288
View File
File diff suppressed because it is too large Load Diff
+138 -287
View File
@@ -15,10 +15,6 @@
#include "Render Fun.h" #include "Render Fun.h"
#include "Boxing.h" #include "Boxing.h"
#include "Text.h" #include "Text.h"
#ifdef _DEBUG
#include "renderworld.h"
#include "video.h"
#endif
#include "worldman.h" #include "worldman.h"
#include "strategicmap.h" #include "strategicmap.h"
#include "environment.h" #include "environment.h"
@@ -26,7 +22,8 @@
#include "Buildings.h" #include "Buildings.h"
#include "GameSettings.h" #include "GameSettings.h"
#include "Soldier Profile.h" #include "Soldier Profile.h"
#include "Rotting Corpses.h" // sevenfm #include "rotting corpses.h" // sevenfm
#include <Cheats.h>
//////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -34,17 +31,7 @@
// were changed to GetAPsCrouch() and GetAPsProne() // were changed to GetAPsCrouch() and GetAPsProne()
// - also all "APBPConstants[AP_PICKUP_ITEM]" were replaced by GetBasicAPsToPickupItem() // - also all "APBPConstants[AP_PICKUP_ITEM]" were replaced by GetBasicAPsToPickupItem()
//////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////
INT16 gsBestCover;
INT16 * gsCoverValue = NULL;
#ifdef _DEBUG
INT16 gsBestCover;
#ifndef PATHAI_VISIBLE_DEBUG
// NB Change this to true to get visible cover debug -- CJC
BOOLEAN gfDisplayCoverValues = FALSE;
#endif
extern void RenderCoverDebug( void );
#endif
INT16 gubAIPathCosts[19][19]; INT16 gubAIPathCosts[19][19];
@@ -613,42 +600,115 @@ UINT8 NumberOfTeamMatesAdjacent( SOLDIERTYPE * pSoldier, INT32 sGridNo )
return( ubCount ); return( ubCount );
} }
INT32 FindBestNearbyCover(SOLDIERTYPE *pSoldier, INT32 morale, INT32 *piPercentBetter) static void CalculateCoverValue(SOLDIERTYPE* pSoldier, const INT32 sGridNo, const INT32 iPathCost, const INT32 iMyThreatValue, const UINT32 uiThreatCnt, const UINT8 ubDiff, const BOOLEAN fNight, const UINT8 ubBackgroundLightPercent, const INT32 morale, INT32 &iCoverValue, INT32& iCoverScale)
{ {
iCoverValue = 0;
iCoverScale = 0;
// sevenfm: check sight cover
BOOLEAN fProneCover = TRUE;
// for every opponent that threatens, consider this spot's cover vs. him
for (UINT32 uiLoop = 0; uiLoop < uiThreatCnt; uiLoop++)
{
// calculate the range we would be at from this opponent
INT32 iThreatRange = Threat[uiLoop].iOrigRange;
if (sGridNo != pSoldier->sGridNo)
{
iThreatRange = GetRangeInCellCoordsFromGridNoDiff(sGridNo, Threat[uiLoop].sGridNo);
}
// if this threat would be within 20 tiles, count it
if (iThreatRange <= MAX_THREAT_RANGE)
{
iCoverValue += CalcCoverValue(
pSoldier, sGridNo, iMyThreatValue,
(pSoldier->bActionPoints - iPathCost),
uiLoop, iThreatRange, morale, &iCoverScale
);
}
// sevenfm: sight test
if (gGameExternalOptions.fAIBetterCover)
{
if (LocationToLocationLineOfSightTest(Threat[uiLoop].sGridNo, Threat[uiLoop].pOpponent->pathing.bLevel, sGridNo, pSoldier->pathing.bLevel, TRUE, MAX_VISION_RANGE, STANDING_LOS_POS, PRONE_LOS_POS))
//if ( SoldierToVirtualSoldierLineOfSightTest( Threat[uiLoop].pOpponent, sGridNo, pSoldier->pathing.bLevel, ANIM_PRONE, TRUE, -1 ) != 0 )
{
fProneCover = FALSE;
}
}
//sprintf(tempstr,"iCoverValue after opponent %d is now %d",iLoop,iCoverValue);
//PopMessage(tempstr);
}
// reduce cover for each person adjacent to this gridno who is on our team,
// by 10% (so locations next to several people will be very much frowned upon
iCoverValue -= (abs(iCoverValue) / 5) * NumberOfTeamMatesAdjacent(pSoldier, sGridNo);
if (gGameExternalOptions.fAIBetterCover)
{
// sevenfm: when defending (range change <= 3), prefer locations with sight cover
if (RangeChangeDesire(pSoldier) < 4)
{
if (fProneCover) { iCoverValue += abs(iCoverValue) / __max(2, 2 * RangeChangeDesire(pSoldier)); }
}
// sevenfm: check for nearby friends, add bonus/penalty 10%
UINT8 ubNearbyFriends = __max(0, CountNearbyFriends(pSoldier, sGridNo, TACTICAL_RANGE_CLOSE));
iCoverValue -= ubNearbyFriends * abs(iCoverValue) / (6 - ubDiff);
// sevenfm: penalize locations with fresh corpses
if (GetNearestRottingCorpseAIWarning(sGridNo) > 0)
{
iCoverValue -= abs(iCoverValue) / (6 - ubDiff);
}
// sevenfm: penalize locations near red smoke
iCoverValue -= abs(iCoverValue) * RedSmokeDanger(sGridNo, pSoldier->pathing.bLevel) / 100;
}
if (fNight && !(InARoom(sGridNo, NULL))) // ignore in buildings in case placed there
{
// reduce cover at nighttime based on how bright the light is at that location
// using the difference in sighting distance between the background and the
// light for this tile
//ubLightPercentDifference = (gbLightSighting[ 0 ][ LightTrueLevel( sGridNo, pSoldier->pathing.bLevel ) ] - ubBackgroundLightPercent );
UINT8 ubLightPercentDifference = (gGameExternalOptions.ubBrightnessVisionMod[LightTrueLevel(sGridNo, pSoldier->pathing.bLevel)] - ubBackgroundLightPercent);
iCoverValue -= (abs(iCoverValue) / 100) * ubLightPercentDifference;
}
}
INT32 FindBestNearbyCover(SOLDIERTYPE *pSoldier, INT32 morale, INT32 *piPercentBetter, INT32 targetGridNo, bool ignoreSearchRange)
{
if (gRenderDebugInfoMode == DEBUG_COVERVALUE && DEBUG_CHEAT_LEVEL())
{
ResetDebugInfoValues();
}
DebugMsg(TOPIC_JA2AI,DBG_LEVEL_3,String("FindBestNearbyCover")); DebugMsg(TOPIC_JA2AI,DBG_LEVEL_3,String("FindBestNearbyCover"));
// all 32-bit integers for max. speed // all 32-bit integers for max. speed
UINT32 uiLoop;
INT32 iCurrentCoverValue, iCoverValue, iBestCoverValue; INT32 iCurrentCoverValue, iCoverValue, iBestCoverValue;
INT32 iCurrentScale = -1, iCoverScale = -1, iBestCoverScale = -1; INT32 iCurrentScale = -1, iCoverScale = -1, iBestCoverScale = -1;
INT32 iDistFromOrigin, iDistCoverFromOrigin; INT32 iDistFromOrigin, iDistCoverFromOrigin;
//INT32 iThreatCertainty;
INT32 sGridNo, sBestCover = NOWHERE; INT32 sGridNo, sBestCover = NOWHERE;
INT32 iPathCost; INT32 iPathCost;
INT32 iThreatRange, iClosestThreatRange = 1500;
// INT16 sClosestThreatGridno = NOWHERE;
INT32 iMyThreatValue; INT32 iMyThreatValue;
//INT32 sThreatLoc;
//INT32 iMaxThreatRange;
UINT32 uiThreatCnt = 0; UINT32 uiThreatCnt = 0;
INT32 iMaxMoveTilesLeft, iSearchRange, iRoamRange; INT32 iMaxMoveTilesLeft, iSearchRange, iRoamRange;
INT16 sMaxLeft, sMaxRight, sMaxUp, sMaxDown, sXOffset, sYOffset; INT16 sMaxLeft, sMaxRight, sMaxUp, sMaxDown, sXOffset, sYOffset;
INT32 sOrigin; // has to be a short, need a pointer INT32 sOrigin; // has to be a short, need a pointer
INT32 * pusLastLoc;
INT8 * pbPersOL;
INT8 * pbPublOL;
//SOLDIERTYPE *pOpponent;
UINT16 usMovementMode; UINT16 usMovementMode;
UINT8 ubBackgroundLightLevel; UINT8 ubBackgroundLightLevel;
UINT8 ubBackgroundLightPercent = 0; UINT8 ubBackgroundLightPercent = 0;
UINT8 ubLightPercentDifference;
BOOLEAN fNight; BOOLEAN fNight;
// sevenfm const UINT8 ubDiff = SoldierDifficultyLevel( pSoldier );
UINT8 ubNearbyFriends;
BOOLEAN fProneCover; if (targetGridNo == NOWHERE)
UINT8 ubDiff = SoldierDifficultyLevel( pSoldier ); {
targetGridNo = pSoldier->sGridNo;
}
// There's no cover when boxing! // There's no cover when boxing!
if (gTacticalStatus.bBoxingState == BOXING) if (gTacticalStatus.bBoxingState == BOXING)
@@ -676,25 +736,6 @@ INT32 FindBestNearbyCover(SOLDIERTYPE *pSoldier, INT32 morale, INT32 *piPercentB
} }
} }
iBestCoverValue = -1;
#if defined( _DEBUG ) && defined( COVER_DEBUG )
if (gfDisplayCoverValues)
{
memset( gsCoverValue, 0x7F, sizeof( INT16 ) * WORLD_MAX );
}
#endif
//NameMessage(pSoldier,"looking for some cover...");
// BUILD A LIST OF THREATENING GRID #s FROM PERSONAL & PUBLIC opplists
pusLastLoc = &(gsLastKnownOppLoc[pSoldier->ubID][0]);
// hang a pointer into personal opplist
pbPersOL = &(pSoldier->aiData.bOppList[0]);
// hang a pointer into public opplist
pbPublOL = &(gbPublicOpplist[pSoldier->bTeam][0]);
// decide how far we're gonna be looking // decide how far we're gonna be looking
iSearchRange = gbDiff[DIFF_MAX_COVER_RANGE][ SoldierDifficultyLevel( pSoldier ) ]; iSearchRange = gbDiff[DIFF_MAX_COVER_RANGE][ SoldierDifficultyLevel( pSoldier ) ];
@@ -711,9 +752,9 @@ INT32 FindBestNearbyCover(SOLDIERTYPE *pSoldier, INT32 morale, INT32 *piPercentB
}*/ }*/
// maximum search range is 1 tile / 8 pts of wisdom // maximum search range is 1 tile / 8 pts of wisdom
if (iSearchRange > (pSoldier->stats.bWisdom / 8)) if (iSearchRange > (pSoldier->stats.bWisdom / 4))
{ {
iSearchRange = (pSoldier->stats.bWisdom / 8); iSearchRange = (pSoldier->stats.bWisdom / 4);
} }
if (!gfTurnBasedAI) if (!gfTurnBasedAI)
@@ -724,6 +765,7 @@ INT32 FindBestNearbyCover(SOLDIERTYPE *pSoldier, INT32 morale, INT32 *piPercentB
usMovementMode = DetermineMovementMode( pSoldier, AI_ACTION_TAKE_COVER ); usMovementMode = DetermineMovementMode( pSoldier, AI_ACTION_TAKE_COVER );
#if 1
if (pSoldier->aiData.bAlertStatus >= STATUS_RED) // if already in battle if (pSoldier->aiData.bAlertStatus >= STATUS_RED) // if already in battle
{ {
// must be able to reach the cover, so it can't possibly be more than // must be able to reach the cover, so it can't possibly be more than
@@ -744,6 +786,12 @@ INT32 FindBestNearbyCover(SOLDIERTYPE *pSoldier, INT32 morale, INT32 *piPercentB
iSearchRange = iMaxMoveTilesLeft; iSearchRange = iMaxMoveTilesLeft;
} }
} }
#endif
if (ignoreSearchRange)
{
// For debugging
iSearchRange = 16;
}
if (iSearchRange <= 0) if (iSearchRange <= 0)
{ {
@@ -765,95 +813,30 @@ INT32 FindBestNearbyCover(SOLDIERTYPE *pSoldier, INT32 morale, INT32 *piPercentB
// calculate our current cover value in the place we are now, since the // calculate our current cover value in the place we are now, since the
// cover we are searching for must be better than what we have now! // cover we are searching for must be better than what we have now!
iCurrentCoverValue = 0; CalculateCoverValue(pSoldier, targetGridNo, 0, iMyThreatValue, uiThreatCnt, ubDiff, fNight, ubBackgroundLightPercent, morale, iCurrentCoverValue, iCurrentScale);
iCurrentScale = 0; // the initial cover value to beat is our current cover value
iBestCoverValue = iCurrentCoverValue;
// sevenfm: sight cover if (gRenderDebugInfoMode == DEBUG_COVERVALUE && DEBUG_CHEAT_LEVEL())
fProneCover = TRUE;
// for every opponent that threatens, consider this spot's cover vs. him
for (uiLoop = 0; uiLoop < uiThreatCnt; uiLoop++)
{ {
// if this threat is CURRENTLY within 20 tiles gRenderDebugInfoValues[targetGridNo] = (INT32)(iCurrentCoverValue / 100);
if (Threat[uiLoop].iOrigRange <= MAX_THREAT_RANGE)
{
// add this opponent's cover value to our current total cover value
iCurrentCoverValue += CalcCoverValue(pSoldier,pSoldier->sGridNo,iMyThreatValue,pSoldier->bActionPoints,uiLoop,Threat[uiLoop].iOrigRange,morale,&iCurrentScale);
}
// sevenfm: sight test
if( gGameExternalOptions.fAIBetterCover )
{
if ( LocationToLocationLineOfSightTest( Threat[uiLoop].sGridNo, Threat[uiLoop].pOpponent->pathing.bLevel, pSoldier->sGridNo, pSoldier->pathing.bLevel, TRUE, MAX_VISION_RANGE, STANDING_LOS_POS, PRONE_LOS_POS ) )
//if ( SoldierToVirtualSoldierLineOfSightTest( Threat[uiLoop].pOpponent, pSoldier->sGridNo, pSoldier->pathing.bLevel, ANIM_PRONE, TRUE, NO_DISTANCE_LIMIT ) )
{
fProneCover = FALSE;
}
}
//sprintf(tempstr,"iCurrentCoverValue after opponent %d is now %d",iLoop,iCurrentCoverValue);
//PopMessage(tempstr);
} }
// reduce cover for each person adjacent to this gridno who is on our team,
// by 10% (so locations next to several people will be very much frowned upon
if ( iCurrentCoverValue >= 0 )
{
iCurrentCoverValue -= (iCurrentCoverValue / 10) * NumberOfTeamMatesAdjacent( pSoldier, pSoldier->sGridNo );
}
else
{
// when negative, must add a negative to decrease the total
iCurrentCoverValue += (iCurrentCoverValue / 10) * NumberOfTeamMatesAdjacent( pSoldier, pSoldier->sGridNo );
}
if( gGameExternalOptions.fAIBetterCover )
{
// sevenfm: when defending (range change <= 3), prefer locations with sight cover
if( RangeChangeDesire(pSoldier) < 4 )
{
if( fProneCover )
{
iCurrentCoverValue += abs(iCurrentCoverValue) / __max(2, 2*RangeChangeDesire(pSoldier));
}
}
// sevenfm: check for nearby friends, add bonus/penalty
ubNearbyFriends = __min(5, CountNearbyFriends( pSoldier, pSoldier->sGridNo, 5 ));
iCurrentCoverValue -= ubNearbyFriends * abs(iCurrentCoverValue) / (10-ubDiff);
// sevenfm: penalize locations with fresh corpses
if(GetNearestRottingCorpseAIWarning( pSoldier->sGridNo ) > 0)
{
iCurrentCoverValue -= abs(iCurrentCoverValue) / (8-ubDiff);
}
// sevenfm: penalize locations near red smoke
iCurrentCoverValue -= abs(iCurrentCoverValue) * RedSmokeDanger(pSoldier->sGridNo, pSoldier->pathing.bLevel) / 100;
}
#ifdef DEBUGCOVER
// AINumMessage("Search Range = ",iSearchRange);
#endif
// determine maximum horizontal limits // determine maximum horizontal limits
sMaxLeft = min(iSearchRange,(pSoldier->sGridNo % MAXCOL)); sMaxLeft = min(iSearchRange,(targetGridNo % MAXCOL));
//NumMessage("sMaxLeft = ",sMaxLeft); sMaxRight = min(iSearchRange,MAXCOL - ((targetGridNo % MAXCOL) + 1));
sMaxRight = min(iSearchRange,MAXCOL - ((pSoldier->sGridNo % MAXCOL) + 1));
//NumMessage("sMaxRight = ",sMaxRight);
// determine maximum vertical limits // determine maximum vertical limits
sMaxUp = min(iSearchRange,(pSoldier->sGridNo / MAXROW)); sMaxUp = min(iSearchRange,(targetGridNo / MAXROW));
//NumMessage("sMaxUp = ",sMaxUp); sMaxDown = min(iSearchRange,MAXROW - ((targetGridNo / MAXROW) + 1));
sMaxDown = min(iSearchRange,MAXROW - ((pSoldier->sGridNo / MAXROW) + 1));
//NumMessage("sMaxDown = ",sMaxDown);
iRoamRange = RoamingRange(pSoldier,&sOrigin); iRoamRange = RoamingRange(pSoldier,&sOrigin);
// if status isn't black (life & death combat), and roaming range is limited // if status isn't black (life & death combat), and roaming range is limited
if ((pSoldier->aiData.bAlertStatus != STATUS_BLACK) && (iRoamRange < MAX_ROAMING_RANGE) && //if ((pSoldier->aiData.bAlertStatus != STATUS_BLACK) && (iRoamRange < MAX_ROAMING_RANGE) &&
if ((pSoldier->aiData.bAlertStatus < STATUS_RED) && (iRoamRange < MAX_ROAMING_RANGE) &&
(!TileIsOutOfBounds(sOrigin))) (!TileIsOutOfBounds(sOrigin)))
{ {
// must try to stay within or return to the point of origin // must try to stay within or return to the point of origin
iDistFromOrigin = SpacesAway(sOrigin,pSoldier->sGridNo); iDistFromOrigin = SpacesAway(sOrigin,targetGridNo);
} }
else else
{ {
@@ -862,19 +845,6 @@ INT32 FindBestNearbyCover(SOLDIERTYPE *pSoldier, INT32 morale, INT32 *piPercentB
} }
#ifdef DEBUGCOVER
DebugAI( String( "FBNC: iRoamRange %d, sMaxLeft %d, sMaxRight %d, sMaxUp %d, sMaxDown %d\n",iRoamRange,sMaxLeft,sMaxRight,sMaxUp,sMaxDown) );
#endif
// the initial cover value to beat is our current cover value
iBestCoverValue = iCurrentCoverValue;
#ifdef DEBUGDECISIONS
STR tempstr="";
sprintf( tempstr, "FBNC: CURRENT iCoverValue = %d\n",iCurrentCoverValue );
DebugAI( tempstr );
#endif
if (pSoldier->aiData.bAlertStatus >= STATUS_RED) // if already in battle if (pSoldier->aiData.bAlertStatus >= STATUS_RED) // if already in battle
{ {
// to speed this up, tell PathAI to cancel any paths beyond our AP reach! // to speed this up, tell PathAI to cancel any paths beyond our AP reach!
@@ -900,7 +870,7 @@ INT32 FindBestNearbyCover(SOLDIERTYPE *pSoldier, INT32 morale, INT32 *piPercentB
{ {
for (sXOffset = -sMaxLeft; sXOffset <= sMaxRight; sXOffset++) for (sXOffset = -sMaxLeft; sXOffset <= sMaxRight; sXOffset++)
{ {
sGridNo = pSoldier->sGridNo + sXOffset + (MAXCOL * sYOffset); sGridNo = targetGridNo + sXOffset + (MAXCOL * sYOffset);
if ( !(sGridNo >=0 && sGridNo < WORLD_MAX) ) if ( !(sGridNo >=0 && sGridNo < WORLD_MAX) )
{ {
continue; continue;
@@ -908,22 +878,20 @@ INT32 FindBestNearbyCover(SOLDIERTYPE *pSoldier, INT32 morale, INT32 *piPercentB
gpWorldLevelData[sGridNo].uiFlags &= ~(MAPELEMENT_REACHABLE); gpWorldLevelData[sGridNo].uiFlags &= ~(MAPELEMENT_REACHABLE);
} }
} }
FindBestPath( pSoldier, GRIDSIZE, pSoldier->pathing.bLevel, DetermineMovementMode( pSoldier, AI_ACTION_TAKE_COVER ), COPYREACHABLE_AND_APS, 0 );//dnl ch50 071009 FindBestPath( pSoldier, GRIDSIZE, pSoldier->pathing.bLevel, DetermineMovementMode( pSoldier, AI_ACTION_TAKE_COVER ), COPYREACHABLE_AND_APS, 0 );//dnl ch50 071009
// Turn off the "reachable" flag for his current location // Turn off the "reachable" flag for his current location
// so we don't consider it // so we don't consider it
gpWorldLevelData[pSoldier->sGridNo].uiFlags &= ~(MAPELEMENT_REACHABLE); gpWorldLevelData[targetGridNo].uiFlags &= ~(MAPELEMENT_REACHABLE);
// SET UP DOUBLE-LOOP TO STEP THROUGH POTENTIAL GRID #s // SET UP DOUBLE-LOOP TO STEP THROUGH POTENTIAL GRID #s
for (sYOffset = -sMaxUp; sYOffset <= sMaxDown; sYOffset++) for (sYOffset = -sMaxUp; sYOffset <= sMaxDown; sYOffset++)
{ {
for (sXOffset = -sMaxLeft; sXOffset <= sMaxRight; sXOffset++) for (sXOffset = -sMaxLeft; sXOffset <= sMaxRight; sXOffset++)
{ {
//HandleMyMouseCursor(KEYBOARDALSO);
// calculate the next potential gridno // calculate the next potential gridno
sGridNo = pSoldier->sGridNo + sXOffset + (MAXCOL * sYOffset); sGridNo = targetGridNo + sXOffset + (MAXCOL * sYOffset);
if ( !(sGridNo >=0 && sGridNo < WORLD_MAX) ) if ( !(sGridNo >=0 && sGridNo < WORLD_MAX) )
{ {
continue; continue;
@@ -980,7 +948,7 @@ INT32 FindBestNearbyCover(SOLDIERTYPE *pSoldier, INT32 morale, INT32 *piPercentB
// sevenfm: avoid moving into light // sevenfm: avoid moving into light
if (InLightAtNight(sGridNo, pSoldier->pathing.bLevel) && if (InLightAtNight(sGridNo, pSoldier->pathing.bLevel) &&
!InLightAtNight(pSoldier->sGridNo, pSoldier->pathing.bLevel) && !InLightAtNight(targetGridNo, pSoldier->pathing.bLevel) &&
!pSoldier->aiData.bUnderFire) !pSoldier->aiData.bUnderFire)
{ {
continue; continue;
@@ -989,12 +957,19 @@ INT32 FindBestNearbyCover(SOLDIERTYPE *pSoldier, INT32 morale, INT32 *piPercentB
// avoid moving into red smoke // avoid moving into red smoke
if (gGameExternalOptions.fAIBetterCover && if (gGameExternalOptions.fAIBetterCover &&
RedSmokeDanger(sGridNo, pSoldier->pathing.bLevel) && RedSmokeDanger(sGridNo, pSoldier->pathing.bLevel) &&
!RedSmokeDanger(pSoldier->sGridNo, pSoldier->pathing.bLevel)) !RedSmokeDanger(targetGridNo, pSoldier->pathing.bLevel))
{ {
//DebugCover(pSoldier, String("moving into red smoke, skip")); //DebugCover(pSoldier, String("moving into red smoke, skip"));
continue; continue;
} }
// Avoid overcrowding
const auto nearbyFriends = CountNearbyFriends(pSoldier, sGridNo, TACTICAL_RANGE_VERYCLOSE);
if ( nearbyFriends > 1 && !pSoldier->IsZombie() )
{
continue;
}
// zombies only want to hide // zombies only want to hide
if (pSoldier->IsZombie() && !SightCoverAtSpot(pSoldier, sGridNo, TRUE)) if (pSoldier->IsZombie() && !SightCoverAtSpot(pSoldier, sGridNo, TRUE))
{ {
@@ -1027,110 +1002,15 @@ INT32 FindBestNearbyCover(SOLDIERTYPE *pSoldier, INT32 morale, INT32 *piPercentB
// OK, this place shows potential. How useful is it as cover? // OK, this place shows potential. How useful is it as cover?
// EVALUATE EACH GRID #, remembering the BEST PROTECTED ONE // EVALUATE EACH GRID #, remembering the BEST PROTECTED ONE
iCoverValue = 0; CalculateCoverValue(pSoldier, sGridNo, iPathCost, iMyThreatValue, uiThreatCnt, ubDiff, fNight, ubBackgroundLightPercent, morale, iCoverValue, iCoverScale);
iCoverScale = 0;
// sevenfm: check sight cover
fProneCover = TRUE;
// for every opponent that threatens, consider this spot's cover vs. him if (gRenderDebugInfoMode == DEBUG_COVERVALUE && DEBUG_CHEAT_LEVEL())
for (uiLoop = 0; uiLoop < uiThreatCnt; uiLoop++)
{ {
// calculate the range we would be at from this opponent gRenderDebugInfoValues[sGridNo] = (INT32) (iCoverValue / 100);
iThreatRange = GetRangeInCellCoordsFromGridNoDiff( sGridNo, Threat[uiLoop].sGridNo );
// if this threat would be within 20 tiles, count it
if (iThreatRange <= MAX_THREAT_RANGE)
{
iCoverValue += CalcCoverValue(pSoldier,sGridNo,iMyThreatValue,
(pSoldier->bActionPoints - iPathCost),
uiLoop,iThreatRange,morale,&iCoverScale);
}
// sevenfm: sight test
if( gGameExternalOptions.fAIBetterCover )
{
if ( LocationToLocationLineOfSightTest( Threat[uiLoop].sGridNo, Threat[uiLoop].pOpponent->pathing.bLevel, sGridNo, pSoldier->pathing.bLevel, TRUE, MAX_VISION_RANGE, STANDING_LOS_POS, PRONE_LOS_POS ) )
//if ( SoldierToVirtualSoldierLineOfSightTest( Threat[uiLoop].pOpponent, sGridNo, pSoldier->pathing.bLevel, ANIM_PRONE, TRUE, -1 ) != 0 )
{
fProneCover = FALSE;
}
}
//sprintf(tempstr,"iCoverValue after opponent %d is now %d",iLoop,iCoverValue);
//PopMessage(tempstr);
} }
// reduce cover for each person adjacent to this gridno who is on our team,
// by 10% (so locations next to several people will be very much frowned upon
if ( iCoverValue >= 0 )
{
iCoverValue -= (iCoverValue / 10) * NumberOfTeamMatesAdjacent( pSoldier, sGridNo );
}
else
{
// when negative, must add a negative to decrease the total
iCoverValue += (iCoverValue / 10) * NumberOfTeamMatesAdjacent( pSoldier, sGridNo );
}
if( gGameExternalOptions.fAIBetterCover )
{
// sevenfm: when defending (range change <= 3), prefer locations with sight cover
if( RangeChangeDesire(pSoldier) < 4 )
{
if( fProneCover )
iCoverValue += abs(iCoverValue) / __max(2, 2*RangeChangeDesire(pSoldier));
}
// sevenfm: check for nearby friends in 10 radius, add bonus/penalty 10%
ubNearbyFriends = __min(5, CountNearbyFriends( pSoldier, sGridNo, 5 ));
iCoverValue -= ubNearbyFriends * abs(iCoverValue) / (10-ubDiff);
// sevenfm: penalize locations with fresh corpses
if(GetNearestRottingCorpseAIWarning( sGridNo ) > 0)
{
iCoverValue -= abs(iCoverValue) / (8-ubDiff);
}
// sevenfm: penalize locations near red smoke
iCoverValue -= abs(iCoverValue) * RedSmokeDanger(sGridNo, pSoldier->pathing.bLevel) / 100;
}
if ( fNight && !( InARoom( sGridNo, NULL ) ) ) // ignore in buildings in case placed there
{
// reduce cover at nighttime based on how bright the light is at that location
// using the difference in sighting distance between the background and the
// light for this tile
//ubLightPercentDifference = (gbLightSighting[ 0 ][ LightTrueLevel( sGridNo, pSoldier->pathing.bLevel ) ] - ubBackgroundLightPercent );
ubLightPercentDifference = (gGameExternalOptions.ubBrightnessVisionMod[ LightTrueLevel( sGridNo, pSoldier->pathing.bLevel ) ] - ubBackgroundLightPercent );
if ( iCoverValue >= 0 )
{
iCoverValue -= (iCoverValue / 100) * ubLightPercentDifference;
}
else
{
iCoverValue += (iCoverValue / 100) * ubLightPercentDifference;
}
}
#ifdef DEBUGCOVER
// if there ARE multiple opponents
if (uiThreatCnt > 1)
{
DebugAI( String( "FBNC: Total iCoverValue at gridno %d is %d\n\n",sGridNo,iCoverValue ) );
}
#endif
#if defined( _DEBUG ) && defined( COVER_DEBUG )
if (gfDisplayCoverValues)
{
gsCoverValue[sGridNo] = (INT16) (iCoverValue / 100);
}
#endif
// if this is better than the best place found so far // if this is better than the best place found so far
if (iCoverValue > iBestCoverValue) if (iCoverValue > iBestCoverValue)
{ {
// ONLY DO THIS CHECK HERE IF WE'RE WAITING FOR OPPCHANCETODECIDE, // ONLY DO THIS CHECK HERE IF WE'RE WAITING FOR OPPCHANCETODECIDE,
@@ -1148,11 +1028,6 @@ INT32 FindBestNearbyCover(SOLDIERTYPE *pSoldier, INT32 morale, INT32 *piPercentB
} }
*/ */
#ifdef DEBUGDECISIONS
STR tempstr;
sprintf( tempstr,"FBNC: NEW BEST iCoverValue at gridno %d is %d\n",sGridNo,iCoverValue );
DebugAI( tempstr );
#endif
// remember it instead // remember it instead
sBestCover = sGridNo; sBestCover = sGridNo;
iBestCoverValue = iCoverValue; iBestCoverValue = iCoverValue;
@@ -1164,34 +1039,15 @@ INT32 FindBestNearbyCover(SOLDIERTYPE *pSoldier, INT32 morale, INT32 *piPercentB
gubNPCAPBudget = 0; gubNPCAPBudget = 0;
gubNPCDistLimit = 0; gubNPCDistLimit = 0;
#if defined( _DEBUG ) && !defined( PATHAI_VISIBLE_DEBUG ) if (gRenderDebugInfoMode == DEBUG_COVERVALUE && DEBUG_CHEAT_LEVEL())
if (gfDisplayCoverValues)
{ {
// do a locate?
LocateSoldier( pSoldier->ubID, SETLOCATORFAST );
gsBestCover = sBestCover; gsBestCover = sBestCover;
SetRenderFlags( RENDER_FLAG_FULL ); InvalidateRegion(gsVIEWPORT_START_X, gsVIEWPORT_START_Y, gsVIEWPORT_END_X, gsVIEWPORT_WINDOW_END_Y);
RenderWorld();
RenderCoverDebug( );
InvalidateScreen( );
EndFrameBufferRender();
RefreshScreen( NULL );
/*
iLoop = GetJA2Clock();
do
{
} while( ( GetJA2Clock( ) - iLoop ) < 2000 );
*/
} }
#endif
// if a better cover location was found // if a better cover location was found
if (!TileIsOutOfBounds(sBestCover)) if (!TileIsOutOfBounds(sBestCover))
{ {
#if defined( _DEBUG ) && !defined( PATHAI_VISIBLE_DEBUG )
gsBestCover = sBestCover;
#endif
// cover values already take the AP cost of getting there into account in // cover values already take the AP cost of getting there into account in
// a BIG way, so no need to worry about that here, even small improvements // a BIG way, so no need to worry about that here, even small improvements
// are actually very significant once we get our APs back (if we live!) // are actually very significant once we get our APs back (if we live!)
@@ -1200,16 +1056,6 @@ INT32 FindBestNearbyCover(SOLDIERTYPE *pSoldier, INT32 morale, INT32 *piPercentB
// if best cover value found was at least 5% better than our current cover // if best cover value found was at least 5% better than our current cover
if (*piPercentBetter >= MIN_PERCENT_BETTER) if (*piPercentBetter >= MIN_PERCENT_BETTER)
{ {
#ifdef DEBUGDECISIONS
STR tempstr;
sprintf( tempstr,"Found Cover: current %ld, best %ld, %%%%Better %ld\n", iCurrentCoverValue,iBestCoverValue,*piPercentBetter );
DebugAI( tempstr );
#endif
#ifdef BETAVERSION
SnuggleDebug(pSoldier,"Found Cover");
#endif
return(sBestCover); // return the gridno of that cover return(sBestCover); // return the gridno of that cover
} }
} }
@@ -3128,7 +2974,11 @@ INT32 FindAdvanceSpot(SOLDIERTYPE *pSoldier, INT32 sTargetSpot, INT8 bAction, UI
{ {
if (usMovementMode == RUNNING || usMovementMode == WALKING) if (usMovementMode == RUNNING || usMovementMode == WALKING)
{ {
ubReserveAP = APBPConstants[AP_CHANGE_FACING] + GetAPsCrouch(pSoldier, TRUE) + GetAPsProne(pSoldier, TRUE); ubReserveAP = APBPConstants[AP_CHANGE_FACING] + GetAPsCrouch(pSoldier, TRUE) +GetAPsProne(pSoldier, TRUE);
if (pSoldier->bActionPoints <= ubReserveAP)
{
ubReserveAP = APBPConstants[AP_CHANGE_FACING] + GetAPsCrouch(pSoldier, TRUE);
}
} }
else else
{ {
@@ -3279,7 +3129,8 @@ INT32 FindAdvanceSpot(SOLDIERTYPE *pSoldier, INT32 sTargetSpot, INT8 bAction, UI
} }
// avoid overcrowding // avoid overcrowding
if (!pSoldier->IsZombie() && NumberOfTeamMatesAdjacent(pSoldier, sGridNo) > 1) const auto nearbyFriends = CountNearbyFriends(pSoldier, sGridNo, TACTICAL_RANGE_VERYCLOSE);
if ( nearbyFriends > 1 && !pSoldier->IsZombie() )
{ {
continue; continue;
} }
+107 -111
View File
@@ -432,7 +432,7 @@ INT8 RandomPointPatrolAI(SOLDIERTYPE *pSoldier)
INT32 InternalGoAsFarAsPossibleTowards(SOLDIERTYPE *pSoldier, INT32 sDesGrid, INT16 bReserveAPs, INT8 bAction, INT8 fFlags ) INT32 InternalGoAsFarAsPossibleTowards(SOLDIERTYPE *pSoldier, INT32 sDesGrid, INT16 bReserveAPs, INT8 bAction, INT8 fFlags )
{ {
#ifdef DEBUGDECISIONS #ifdef DEBUGDECISIONS
STR16 tempstr; STR16 tempstr;
#endif #endif
INT32 sLoop,sAPCost; INT32 sLoop,sAPCost;
INT32 sTempDest,sGoToGrid; INT32 sTempDest,sGoToGrid;
@@ -588,156 +588,152 @@ INT32 InternalGoAsFarAsPossibleTowards(SOLDIERTYPE *pSoldier, INT32 sDesGrid, IN
} }
} }
// HAVE FOUND AN OK destination AND PLOTTED A VALID BEST PATH TO IT // HAVE FOUND AN OK destination AND PLOTTED A VALID BEST PATH TO IT
#ifdef DEBUGDECISIONS #ifdef DEBUGDECISIONS
AINumMessage("Chosen legal destination is gridno ",sDesGrid); AINumMessage("Chosen legal destination is gridno ",sDesGrid);
AINumMessage("Tracing along path, pathRouteToGo = ",pSoldier->pathing.usPathIndex); AINumMessage("Tracing along path, pathRouteToGo = ",pSoldier->pathing.usPathIndex);
#endif #endif
sGoToGrid = pSoldier->sGridNo; // start back where soldier is standing now sGoToGrid = pSoldier->sGridNo; // start back where soldier is standing now
sAPCost = 0; // initialize path cost counter sAPCost = 0; // initialize path cost counter
// 0verhaul: If the destination is within the patrol route, allow it. This is necessary to allow an errant soldier // 0verhaul: If the destination is within the patrol route, allow it. This is necessary to allow an errant soldier
// to return to his patrol route if flanking or other actions have pulled him beyond his allowed range from origin. // to return to his patrol route if flanking or other actions have pulled him beyond his allowed range from origin.
if (SpacesAway(sOrigin,sDesGrid) <= usMaxDist) if (SpacesAway(sOrigin,sDesGrid) <= usMaxDist)
{ {
fAllowDest = TRUE; fAllowDest = TRUE;
} }
// we'll only go as far along the plotted route as is within our // we'll only go as far along the plotted route as is within our
// permitted roaming range, and we'll stop as soon as we're down to <= 5 APs // permitted roaming range, and we'll stop as soon as we're down to <= 5 APs
sTempDest = pSoldier->sGridNo; sTempDest = pSoldier->sGridNo;
for (sLoop = 0; sLoop < (pSoldier->pathing.usPathDataSize - pSoldier->pathing.usPathIndex); sLoop++) for (sLoop = 0; sLoop < (pSoldier->pathing.usPathDataSize - pSoldier->pathing.usPathIndex); sLoop++)
{ {
// what is the next gridno in the path? // what is the next gridno in the path?
//sTempDest = NewGridNo( sGoToGrid,DirectionInc( (INT16) (pSoldier->pathing.usPathingData[sLoop] + 1) ) ); //sTempDest = NewGridNo( sGoToGrid,DirectionInc( (INT16) (pSoldier->pathing.usPathingData[sLoop] + 1) ) );
//sTempDest = NewGridNo( sGoToGrid,DirectionInc( (UINT8) (pSoldier->pathing.usPathingData[sLoop]) ) ); //sTempDest = NewGridNo( sGoToGrid,DirectionInc( (UINT8) (pSoldier->pathing.usPathingData[sLoop]) ) );
sTempDest = NewGridNo( sTempDest,DirectionInc( (UINT8) (pSoldier->pathing.usPathingData[sLoop]) ) ); sTempDest = NewGridNo( sTempDest,DirectionInc( (UINT8) (pSoldier->pathing.usPathingData[sLoop]) ) );
// this should NEVER be out of bounds // this should NEVER be out of bounds
if (sTempDest == sGoToGrid) if (sTempDest == sGoToGrid)
{ {
#ifdef BETAVERSION #ifdef BETAVERSION
sprintf(tempstr,"GoAsFarAsPossibleTowards: ERROR - gridno along valid route is invalid! guynum %d, sTempDest = %d",pSoldier->ubID,sTempDest); sprintf(tempstr,"GoAsFarAsPossibleTowards: ERROR - gridno along valid route is invalid! guynum %d, sTempDest = %d",pSoldier->ubID,sTempDest);
#ifdef RECORDNET #ifdef RECORDNET
fprintf(NetDebugFile,"\n\t%s\n",tempstr); fprintf(NetDebugFile,"\n\t%s\n",tempstr);
#endif #endif
PopMessage(tempstr); PopMessage(tempstr);
SaveGame(ERROR_SAVE); SaveGame(ERROR_SAVE);
#endif #endif
break; // quit here, sGoToGrid is where we are going
break; // quit here, sGoToGrid is where we are going
}
// if this takes us beyond our permitted "roaming range"
// but if it brings us closer, then allow it!
if (SpacesAway(sOrigin,sTempDest) > usMaxDist && !fAllowDest)
break; // quit here, sGoToGrid is where we are going
if ( usRoomRequired )
{
if ( !( InARoom( sTempDest, &usTempRoom ) && usTempRoom == usRoomRequired ) )
{
// quit here, limited by room!
break;
} }
}
if ( (fFlags & FLAG_STOPSHORT) && SpacesAway( sDesGrid, sTempDest ) <= STOPSHORTDIST ) // if this takes us beyond our permitted "roaming range"
{ // but if it brings us closer, then allow it!
break; // quit here, sGoToGrid is where we are going if (SpacesAway(sOrigin,sTempDest) > usMaxDist && !fAllowDest)
} break; // quit here, sGoToGrid is where we are going
// CAN'T CALL PathCost() HERE! IT CALLS findBestPath() and overwrites
// pathRouteToGo !!! Gotta calculate the cost ourselves - Ian
//
//ubAPsLeft = pSoldier->bActionPoints - PathCost(pSoldier,sTempDest,FALSE,FALSE,FALSE,FALSE,FALSE);
if (gfTurnBasedAI) if ( usRoomRequired )
{
// if we're just starting the "costing" process (first gridno)
if (sLoop == 0)
{
if (pSoldier->usUIMovementMode == RUNNING)
{
sAPCost += GetAPsStartRun( pSoldier ); // changed by SANDRO
}
}
// ATE: Direction here?
sAPCost += EstimateActionPointCost( pSoldier, sTempDest, (INT8) pSoldier->pathing.usPathingData[sLoop], pSoldier->usUIMovementMode, (INT8) sLoop, (INT8) pSoldier->pathing.usPathDataSize );
bAPsLeft = pSoldier->bActionPoints - sAPCost;
}
// if this gridno is NOT a legal NPC destination
// DONT'T test path again - that would replace the traced path! - Ian
// NOTE: It's OK to go *THROUGH* water to try and get to the destination!
// sevenfm: jump over fence code - if current gridno is not legal destination, check next gridno
if (!LegalNPCDestination(pSoldier,sTempDest,IGNORE_PATH,WATEROK,0))
{
// break;
continue;
}
// if after this, we have <= 5 APs remaining, that's far enough, break out
// (the idea is to preserve APs so we can crouch or react if
// necessary, and benefit from the carry-over next turn if not needed)
// This routine is NOT used by any GREEN AI, so such caution is warranted!
if ( gfTurnBasedAI && (bAPsLeft < bReserveAPs) )
break;
else
{ {
sGoToGrid = sTempDest; // we're OK up to here if ( !( InARoom( sTempDest, &usTempRoom ) && usTempRoom == usRoomRequired ) )
{
// quit here, limited by room!
break;
}
}
// if exactly 5 APs left, don't bother checking any further if ( (fFlags & FLAG_STOPSHORT) && SpacesAway( sDesGrid, sTempDest ) <= STOPSHORTDIST )
if ( gfTurnBasedAI && (bAPsLeft == bReserveAPs) ) {
break; // quit here, sGoToGrid is where we are going
}
// CAN'T CALL PathCost() HERE! IT CALLS findBestPath() and overwrites
// pathRouteToGo !!! Gotta calculate the cost ourselves - Ian
//
//ubAPsLeft = pSoldier->bActionPoints - PathCost(pSoldier,sTempDest,FALSE,FALSE,FALSE,FALSE,FALSE);
if (gfTurnBasedAI)
{
// if we're just starting the "costing" process (first gridno)
if (sLoop == 0)
{
if (pSoldier->usUIMovementMode == RUNNING)
{
sAPCost += GetAPsStartRun( pSoldier ); // changed by SANDRO
}
}
// ATE: Direction here?
sAPCost += EstimateActionPointCost( pSoldier, sTempDest, (INT8) pSoldier->pathing.usPathingData[sLoop], pSoldier->usUIMovementMode, (INT8) sLoop, (INT8) pSoldier->pathing.usPathDataSize );
bAPsLeft = pSoldier->bActionPoints - sAPCost;
}
// if this gridno is NOT a legal NPC destination
// DONT'T test path again - that would replace the traced path! - Ian
// NOTE: It's OK to go *THROUGH* water to try and get to the destination!
// sevenfm: jump over fence code - if current gridno is not legal destination, check next gridno
if (!LegalNPCDestination(pSoldier,sTempDest,IGNORE_PATH,WATEROK,0))
{
// break;
continue;
}
// if after this, we have <= 5 APs remaining, that's far enough, break out
// (the idea is to preserve APs so we can crouch or react if
// necessary, and benefit from the carry-over next turn if not needed)
// This routine is NOT used by any GREEN AI, so such caution is warranted!
if ( gfTurnBasedAI && (bAPsLeft < bReserveAPs) )
break; break;
else
{
sGoToGrid = sTempDest; // we're OK up to here
// if exactly 5 APs left, don't bother checking any further
if ( gfTurnBasedAI && (bAPsLeft == bReserveAPs) )
break;
} }
} }
// if it turned out we couldn't go even 1 tile towards the desired gridno // if it turned out we couldn't go even 1 tile towards the desired gridno
if (sGoToGrid == pSoldier->sGridNo) if (sGoToGrid == pSoldier->sGridNo)
{ {
#ifdef DEBUGDECISIONS #ifdef DEBUGDECISIONS
sprintf(tempstr,"%s will go NOWHERE, path doesn't meet criteria",pSoldier->name); sprintf(tempstr,"%s will go NOWHERE, path doesn't meet criteria",pSoldier->name);
AIPopMessage(tempstr); AIPopMessage(tempstr);
#endif #endif
pSoldier->pathing.usPathIndex = pSoldier->pathing.usPathDataSize = 0; pSoldier->pathing.usPathIndex = pSoldier->pathing.usPathDataSize = 0;
return(NOWHERE); // then go nowhere return(NOWHERE); // then go nowhere
} }
else else
{
// possible optimization - stored path IS good if we're going all the way
if (sGoToGrid == sDesGrid)
{ {
pSoldier->pathing.bPathStored = TRUE; // possible optimization - stored path IS good if we're going all the way
pSoldier->pathing.sFinalDestination = sGoToGrid; if (sGoToGrid == sDesGrid)
} {
else if ( pSoldier->pathing.usPathIndex == 0 ) pSoldier->pathing.bPathStored = TRUE;
{ pSoldier->pathing.sFinalDestination = sGoToGrid;
// we can hack this surely! -- CJC }
pSoldier->pathing.bPathStored = TRUE; else if ( pSoldier->pathing.usPathIndex == 0 )
pSoldier->pathing.sFinalDestination = sGoToGrid; {
pSoldier->pathing.usPathDataSize = sLoop + 1; // we can hack this surely! -- CJC
} pSoldier->pathing.bPathStored = TRUE;
pSoldier->pathing.sFinalDestination = sGoToGrid;
pSoldier->pathing.usPathDataSize = sLoop + 1;
}
#ifdef DEBUGDECISIONS #ifdef DEBUGDECISIONS
ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_BETAVERSION, L"%d to %d with %d APs left", pSoldier->ubID, sGoToGrid, pSoldier->bActionPoints ); ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_BETAVERSION, L"%d to %d with %d APs left", pSoldier->ubID, sGoToGrid, pSoldier->bActionPoints );
#endif #endif
return( sGoToGrid ); return( sGoToGrid );
} }
} }
+5
View File
@@ -38,6 +38,9 @@
typedef struct typedef struct
{ {
//SB: unify structure for all versions //SB: unify structure for all versions
// #if defined( CRIPPLED_VERSION ) || defined( RUSSIAN )
// UINT8 ubIdentifier[4];
// #endif
UINT16 fFlags; UINT16 fFlags;
@@ -71,7 +74,9 @@ typedef struct
INT16 sActionData; // special action value INT16 sActionData; // special action value
//SB: unify structure for all versions //SB: unify structure for all versions
//#if !defined( CRIPPLED_VERSION ) && !defined( RUSSIAN )
UINT8 ubUnused[4]; UINT8 ubUnused[4];
//#endif
} _old_NPCQuoteInfo; // 32 bytes } _old_NPCQuoteInfo; // 32 bytes
+4 -4
View File
@@ -244,7 +244,7 @@ void PossiblyMakeThisEnemyChosenOne( SOLDIERTYPE * pSoldier )
} }
INT8 PanicAI(SOLDIERTYPE *pSoldier, UINT8 ubCanMove) ActionType PanicAI(SOLDIERTYPE *pSoldier, UINT8 ubCanMove)
{ {
BOOLEAN fFoundRoute = FALSE; BOOLEAN fFoundRoute = FALSE;
INT8 bSlot; INT8 bSlot;
@@ -307,7 +307,7 @@ INT8 PanicAI(SOLDIERTYPE *pSoldier, UINT8 ubCanMove)
if (bPanicTrigger == -1) if (bPanicTrigger == -1)
{ {
// augh! // augh!
return( -1 ); return( AI_ACTION_INVALID );
} }
sPanicTriggerGridNo = gTacticalStatus.sPanicTriggerGridNo[ bPanicTrigger ]; sPanicTriggerGridNo = gTacticalStatus.sPanicTriggerGridNo[ bPanicTrigger ];
@@ -430,7 +430,7 @@ INT8 PanicAI(SOLDIERTYPE *pSoldier, UINT8 ubCanMove)
} }
// no action decided // no action decided
return(-1); return(AI_ACTION_INVALID);
} }
void InitPanicSystem( void ) void InitPanicSystem( void )
@@ -534,7 +534,7 @@ BOOLEAN NeedToRadioAboutPanicTrigger( void )
#define STAIRCASE_GRIDNO 12067 #define STAIRCASE_GRIDNO 12067
#define STAIRCASE_DIRECTION 0 #define STAIRCASE_DIRECTION 0
INT8 HeadForTheStairCase( SOLDIERTYPE * pSoldier ) ActionType HeadForTheStairCase( SOLDIERTYPE * pSoldier )
{ {
UNDERGROUND_SECTORINFO * pBasementInfo; UNDERGROUND_SECTORINFO * pBasementInfo;
+372
View File
@@ -0,0 +1,372 @@
#include "UtilityAI.h"
#include "UtilityAI_ResponseCurve.h"
#include "Overhead Types.h"
#include "Soldier Control.h"
#include "Isometric Utils.h"
#include "soldier profile type.h"
#include "AIInternals.h"
#include "Render Fun.h"
namespace UtilityAI
{
typedef enum DecisionInput
{
MyHealth,
MyStamina,
MyStance,
MyMorale,
MyCurrentWeaponRange,
DistanceFromMe,
HealthOfTarget,
AllyCount,
EnemyCount,
AllyEnemyRatio,
EnemiesNearTarget,
AlliesNearTarget,
HaveGasmaskInInventory,
HaveNVGInInventory,
HasKnife,
HasThrowingKnife,
HasSmokeGrenade,
IamInSmoke,
IamInTearGas,
IamInMustardGas, // Also fire & creature gas
NearSmoke,
NearTearGas,
NearMustardGas,
InShock,
InsideRoom,
NightTime,
InLightAtNight,
TakenLargeHit,
HasTrait_AutoWeapons,
HasTrait_HeavyWeapons,
HasTrait_Sniper,
HasTrait_Ranger,
HasTrait_Gunslinger,
HasTrait_MartialArts,
HasTrait_SquadLeader,
HasTrait_Technician,
HasTrait_Doctor,
HasTrait_Ambidextrous,
HasTrait_Melee,
HasTrait_Throwing,
HasTrait_NightOps,
HasTrait_Stealthy,
HasTrait_Athletics,
HasTrait_Bodybuilding,
HasTrait_Demolitions,
HasTrait_Teaching,
HasTrait_Scouting,
HasTrait_Covert,
HasTrait_RadioOperator,
HasTrait_Snitch,
HasTrait_Survival,
HasTraitOld_Lockpicking,
HasTraitOld_HandToHand,
HasTraitOld_Electronics,
HasTraitOld_NightOps,
HasTraitOld_Throwing,
HasTraitOld_Teaching,
HasTraitOld_HeavyWeapons,
HasTraitOld_AutoWeapons,
HasTraitOld_Stealthy,
HasTraitOld_Ambidextrous,
HasTraitOld_Thief,
HasTraitOld_MartialArts,
HasTraitOld_Knifing,
HasTraitOld_Sniper,
HasTraitOld_Camouflaged,
};
struct Consideration
{
DecisionInput input;
ResponseCurve curve;
SoldierID target;
INT32 targetLocation;
};
struct DecisionScoreEvaluator
{
ActionType Action;
float priorityWeight;
std::string description;
std::vector<Consideration> considerations;
};
struct DecisionScore
{
float score;
size_t idx; // Index into DM::decisions
bool operator<(const DecisionScore& a) const
{
return score < a.score;
}
bool operator>(const DecisionScore& a) const
{
return score > a.score;
}
bool operator==(const DecisionScore& a) const
{
return score == a.score;
}
};
struct DecisionMaker
{
std::vector<DecisionScoreEvaluator> decisions;
std::vector<DecisionScore> scores;
};
static float GetTrait(DecisionInput input, SOLDIERTYPE* soldier)
{
switch ( input )
{
case HasTrait_AutoWeapons:
return static_cast<float>(HAS_SKILL_TRAIT(soldier, AUTO_WEAPONS_NT));
case HasTrait_HeavyWeapons:
return static_cast<float>(HAS_SKILL_TRAIT(soldier, HEAVY_WEAPONS_NT));
case HasTrait_Sniper:
return static_cast<float>(HAS_SKILL_TRAIT(soldier, SNIPER_NT));
case HasTrait_Ranger:
return static_cast<float>(HAS_SKILL_TRAIT(soldier, RANGER_NT));
case HasTrait_Gunslinger:
return static_cast<float>(HAS_SKILL_TRAIT(soldier, GUNSLINGER_NT));
case HasTrait_MartialArts:
return static_cast<float>(HAS_SKILL_TRAIT(soldier, MARTIAL_ARTS_NT));
case HasTrait_SquadLeader:
return static_cast<float>(HAS_SKILL_TRAIT(soldier, SQUADLEADER_NT));
case HasTrait_Technician:
return static_cast<float>(HAS_SKILL_TRAIT(soldier, TECHNICIAN_NT));
case HasTrait_Doctor:
return static_cast<float>(HAS_SKILL_TRAIT(soldier, DOCTOR_NT));
case HasTrait_Ambidextrous:
return static_cast<float>(HAS_SKILL_TRAIT(soldier, AMBIDEXTROUS_NT));
case HasTrait_Melee:
return static_cast<float>(HAS_SKILL_TRAIT(soldier, MELEE_NT));
case HasTrait_Throwing:
return static_cast<float>(HAS_SKILL_TRAIT(soldier, THROWING_NT));
case HasTrait_NightOps:
return static_cast<float>(HAS_SKILL_TRAIT(soldier, NIGHT_OPS_NT));
case HasTrait_Stealthy:
return static_cast<float>(HAS_SKILL_TRAIT(soldier, STEALTHY_NT));
case HasTrait_Athletics:
return static_cast<float>(HAS_SKILL_TRAIT(soldier, ATHLETICS_NT));
case HasTrait_Bodybuilding:
return static_cast<float>(HAS_SKILL_TRAIT(soldier, BODYBUILDING_NT));
case HasTrait_Demolitions:
return static_cast<float>(HAS_SKILL_TRAIT(soldier, DEMOLITIONS_NT));
case HasTrait_Teaching:
return static_cast<float>(HAS_SKILL_TRAIT(soldier, TEACHING_NT));
case HasTrait_Scouting:
return static_cast<float>(HAS_SKILL_TRAIT(soldier, SCOUTING_NT));
case HasTrait_Covert:
return static_cast<float>(HAS_SKILL_TRAIT(soldier, COVERT_NT));
case HasTrait_RadioOperator:
return static_cast<float>(HAS_SKILL_TRAIT(soldier, RADIO_OPERATOR_NT));
case HasTrait_Snitch:
return static_cast<float>(HAS_SKILL_TRAIT(soldier, SNITCH_NT));
case HasTrait_Survival:
return static_cast<float>(HAS_SKILL_TRAIT(soldier, SURVIVAL_NT));
case HasTraitOld_Lockpicking:
return static_cast<float>(HAS_SKILL_TRAIT(soldier, LOCKPICKING_OT));
case HasTraitOld_HandToHand:
return static_cast<float>(HAS_SKILL_TRAIT(soldier, HANDTOHAND_OT));
case HasTraitOld_Electronics:
return static_cast<float>(HAS_SKILL_TRAIT(soldier, ELECTRONICS_OT));
case HasTraitOld_NightOps:
return static_cast<float>(HAS_SKILL_TRAIT(soldier, NIGHTOPS_OT));
case HasTraitOld_Throwing:
return static_cast<float>(HAS_SKILL_TRAIT(soldier, THROWING_OT));
case HasTraitOld_Teaching:
return static_cast<float>(HAS_SKILL_TRAIT(soldier, TEACHING_OT));
case HasTraitOld_HeavyWeapons:
return static_cast<float>(HAS_SKILL_TRAIT(soldier, HEAVY_WEAPS_OT));
case HasTraitOld_AutoWeapons:
return static_cast<float>(HAS_SKILL_TRAIT(soldier, AUTO_WEAPS_OT));
case HasTraitOld_Stealthy:
return static_cast<float>(HAS_SKILL_TRAIT(soldier, STEALTHY_OT));
case HasTraitOld_Ambidextrous:
return static_cast<float>(HAS_SKILL_TRAIT(soldier, AMBIDEXT_OT));
case HasTraitOld_Thief:
return static_cast<float>(HAS_SKILL_TRAIT(soldier, THIEF_OT));
case HasTraitOld_MartialArts:
return static_cast<float>(HAS_SKILL_TRAIT(soldier, MARTIALARTS_OT));
case HasTraitOld_Knifing:
return static_cast<float>(HAS_SKILL_TRAIT(soldier, KNIFING_OT));
case HasTraitOld_Sniper:
return static_cast<float>(HAS_SKILL_TRAIT(soldier, PROF_SNIPER_OT));
case HasTraitOld_Camouflaged:
return static_cast<float>(HAS_SKILL_TRAIT(soldier, CAMOUFLAGED_OT));
}
return 0.0f;
}
float GetInputValue(DecisionInput input, SOLDIERTYPE* me, SoldierID target, INT32 targetLocation)
{
switch ( input )
{
case MyHealth:
{
const auto health = static_cast<float>(me->stats.bLife);
const auto min = 0.0f;
const auto max = static_cast<float>(me->stats.bLifeMax);
return NormalizeInput(health, min, max);
}
case MyMorale:
{
const auto morale = static_cast<float>(me->aiData.bAIMorale);
const auto min = 0.0f;
const auto max = static_cast<float>(MORALE_FEARLESS);
return NormalizeInput(morale, min, max);
}
case HaveGasmaskInInventory:
{
const auto gasmaskInvSlot = FindGasMask(me);
if ( gasmaskInvSlot != NO_SLOT ) { return 1.0f; }
else { return 0.0f; }
}
case HasKnife:
{
const auto bWeaponIn = FindAIUsableObjClass(me, (IC_BLADE));
if ( bWeaponIn != NO_SLOT ) { return 1.0f; }
else { return 0.0f; }
}
case HasThrowingKnife:
{
const auto bWeaponIn = FindAIUsableObjClass(me, (IC_THROWING_KNIFE));
if ( bWeaponIn != NO_SLOT ) { return 1.0f; }
else { return 0.0f; }
}
case HasSmokeGrenade:
{
const auto bGrenadeIn = FindThrowableGrenade(me, EXPLOSV_SMOKE);
if ( bGrenadeIn != NO_SLOT ) { return 1.0f; }
else { return 0.0f; }
}
case DistanceFromMe:
{
const auto distance = PythSpacesAway(me->sGridNo, targetLocation);
}
case IamInSmoke:
return static_cast<float>(InSmoke(me, me->sGridNo));
case IamInTearGas:
return static_cast<float>(InTearGas(me, me->sGridNo));
case IamInMustardGas:
return static_cast<float>(InMustardGas(me, me->sGridNo));
case NearSmoke:
case NearTearGas:
case NearMustardGas:
case InShock:
case InsideRoom:
return static_cast<float>(InARoom(me->sGridNo, NULL));
case NightTime:
case InLightAtNight:
case TakenLargeHit:
return static_cast<float>(me->TakenLargeHit());
case HasTrait_AutoWeapons:
case HasTrait_HeavyWeapons:
case HasTrait_Sniper:
case HasTrait_Ranger:
case HasTrait_Gunslinger:
case HasTrait_MartialArts:
case HasTrait_SquadLeader:
case HasTrait_Technician:
case HasTrait_Doctor:
case HasTrait_Ambidextrous:
case HasTrait_Melee:
case HasTrait_Throwing:
case HasTrait_NightOps:
case HasTrait_Stealthy:
case HasTrait_Athletics:
case HasTrait_Bodybuilding:
case HasTrait_Demolitions:
case HasTrait_Teaching:
case HasTrait_Scouting:
case HasTrait_Covert:
case HasTrait_RadioOperator:
case HasTrait_Snitch:
case HasTrait_Survival:
case HasTraitOld_Lockpicking:
case HasTraitOld_HandToHand:
case HasTraitOld_Electronics:
case HasTraitOld_NightOps:
case HasTraitOld_Throwing:
case HasTraitOld_Teaching:
case HasTraitOld_HeavyWeapons:
case HasTraitOld_AutoWeapons:
case HasTraitOld_Stealthy:
case HasTraitOld_Ambidextrous:
case HasTraitOld_Thief:
case HasTraitOld_MartialArts:
case HasTraitOld_Knifing:
case HasTraitOld_Sniper:
case HasTraitOld_Camouflaged:
return GetTrait(input, me);
}
}
float ScoreDSE(DecisionScoreEvaluator DSE, SOLDIERTYPE* me)
{
float finalScore = 1.0f;
for ( const auto& consideration : DSE.considerations )
{
if ( finalScore <= 0.0f ) { break; }
float curveInput = GetInputValue(consideration.input, me, consideration.target, consideration.targetLocation);
float considerationScore = CalculateResponse(curveInput, consideration.curve);
finalScore *= considerationScore;
}
// Add consideration compensation
const auto modificationFactor = 1 - 1 / (DSE.considerations.size());
const auto makeUpValue = (1 - finalScore) * modificationFactor;
finalScore += finalScore * makeUpValue;
// Add DSE priority weight
finalScore *= DSE.priorityWeight;
return finalScore;
}
void ScoreAllDecisions(DecisionMaker DM, SOLDIERTYPE* me)
{
size_t idx = 0;
for ( const auto& decision : DM.decisions )
{
DM.scores[idx].score = ScoreDSE(decision, me);
DM.scores[idx].idx = idx;
++idx;
}
// Sort all scores from best to last
std::sort(DM.scores.begin(), DM.scores.end(), std::greater<DecisionScore>());
}
ActionType SelectScoredAction(DecisionMaker DM)
{
// Select highest for now. TODO weighted random choice
const auto idx = DM.scores[0].idx;
const auto action = DM.decisions[idx].Action;
return action;
}
}
+5
View File
@@ -0,0 +1,5 @@
#pragma once
//void ScoreAllDecisions(DecisionMaker DM, SOLDIERTYPE* me);
//ActionType SelectScoredAction(DecisionMaker DM);
+57
View File
@@ -0,0 +1,57 @@
#include "UtilityAI_ResponseCurve.h"
#include <cmath>
#include <algorithm>
namespace UtilityAI
{
float NormalizeInput(float value, float min, float max)
{
const float normalizedValue = (value - min) / (max - min);
return normalizedValue;
}
float CalculateResponse(float x, ResponseCurve curve)
{
const auto min = curve.min;
const auto max = curve.max;
const auto type = curve.type;
const auto m = curve.m;
const auto k = curve.k;
const auto b = curve.b;
const auto c = curve.c;
float input = std::clamp(x, min, max);
input = NormalizeInput(input, min, max);
float y = 0.0f;
// Polynomial
float n;
// Logistic
const float e = 2.71828f; // Euler's number. I think? Lecture slides did not explain if this is what it's supposed to be.
const float exponent = -input + c;
const float denominator = 1.0f + 1000.0f * e * pow(m, exponent);
switch ( type )
{
case Step:
if ( input != 0.0f ) { return 1.0f; }
else { return 0.0f; }
case Linear:
case Polynomial:
n = input - c;
y = m * pow(n, k) + b;
break;
case Logistic:
y = (k / denominator) + b;
break;
//case Logit: // Not implemented yet
//break;
}
y = std::clamp(y, 0.0f, 1.0f);
return y;
}
}
+24
View File
@@ -0,0 +1,24 @@
#pragma once
typedef enum
{
Step,
Linear,
Polynomial,
Logistic,
Logit
} ResponseCurveType;
struct ResponseCurve
{
ResponseCurveType type;
float m;
float k;
float b;
float c;
float min;
float max;
};
float NormalizeInput(float value, float min, float max);
float CalculateResponse(float x, ResponseCurve curve);
+1 -1
View File
@@ -33,7 +33,7 @@
#include "Text.h" #include "Text.h"
extern BOOLEAN gfHiddenInterrupt; extern BOOLEAN gfHiddenInterrupt;
extern void LogDecideInfo(SOLDIERTYPE *pSoldier); extern void LogDecideInfo(SOLDIERTYPE *pSoldier, bool doLog = true);
extern STR8 gStr8AlertStatus[]; extern STR8 gStr8AlertStatus[];
extern STR8 gStr8Attitude[]; extern STR8 gStr8Attitude[];
+39 -12
View File
@@ -12,10 +12,6 @@
extern INT16 gubAIPathCosts[19][19]; extern INT16 gubAIPathCosts[19][19];
#define AI_PATHCOST_RADIUS 9 #define AI_PATHCOST_RADIUS 9
extern BOOLEAN gfDisplayCoverValues;
//extern INT16 gsCoverValue[WORLD_MAX];
extern INT16 * gsCoverValue;
// AI actions // AI actions
enum CreatureCalls enum CreatureCalls
@@ -110,7 +106,10 @@ typedef enum
AI_ACTION_DOCTOR_SELF, // added by Flugente: AI-ONLY! bandage/surgery on self. DO NOT USE THIS FOR MERCS!!! AI_ACTION_DOCTOR_SELF, // added by Flugente: AI-ONLY! bandage/surgery on self. DO NOT USE THIS FOR MERCS!!!
AI_ACTION_SELFDETONATE, // added by Flugente: blow up an explosive in own inventory AI_ACTION_SELFDETONATE, // added by Flugente: blow up an explosive in own inventory
AI_ACTION_STOP_MEDIC, // sevenfm: stop giving aid animation AI_ACTION_STOP_MEDIC, // sevenfm: stop giving aid animation
AI_ACTION_LAST = AI_ACTION_STOP_MEDIC AI_ACTION_DRINK_CANTEEN, // sevenfm: drink from canteen in inventory
AI_ACTION_HANDLE_ITEM, // sevenfm: use item in hand
AI_ACTION_PLANT_BOMB, // sevenfm: plant bomb using item in hand
AI_ACTION_INVALID
} ActionType; } ActionType;
@@ -175,21 +174,36 @@ void CheckForChangingOrders(SOLDIERTYPE *pSoldier );
INT8 ClosestPanicTrigger( SOLDIERTYPE * pSoldier ); INT8 ClosestPanicTrigger( SOLDIERTYPE * pSoldier );
INT32 ClosestKnownOpponent(SOLDIERTYPE *pSoldier, INT32 * psGridNo, INT8 * pbLevel, SoldierID *pubOpponentID = NULL); INT32 ClosestKnownOpponent(SOLDIERTYPE *pSoldier, INT32 * psGridNo, INT8 * pbLevel, SoldierID *pubOpponentID = NULL, INT32 * distanceInCellCoords = NULL);
INT32 ClosestPC( SOLDIERTYPE *pSoldier, INT32 * psDistance ); INT32 ClosestPC( SOLDIERTYPE *pSoldier, INT32 * psDistance );
INT32 ClosestUnDisguisedPC( SOLDIERTYPE *pSoldier, INT32 * psDistance ); // Flugente: like ClosestPC(...), but does not account for covert or not visible mercs INT32 ClosestUnDisguisedPC( SOLDIERTYPE *pSoldier, INT32 * psDistance ); // Flugente: like ClosestPC(...), but does not account for covert or not visible mercs
BOOLEAN CanAutoBandage( BOOLEAN fDoFullCheck ); BOOLEAN CanAutoBandage( BOOLEAN fDoFullCheck );
void DebugAI( STR szOutput ); void DebugAI( STR szOutput );
enum { AI_MSG_START, AI_MSG_DECIDE, AI_MSG_INFO, AI_MSG_TOPIC }; enum { AI_MSG_START, AI_MSG_DECIDE, AI_MSG_INFO, AI_MSG_TOPIC };
void DebugAI(INT8 bMsgType, SOLDIERTYPE *pSoldier, STR szOutput, INT8 bAction = -1); void DebugAI(INT8 bMsgType, SOLDIERTYPE *pSoldier, STR szOutput, bool doLog = true, INT8 bAction = -1);
void DebugQuestInfo(STR szOutput); void DebugQuestInfo(STR szOutput);
INT8 DecideAction(SOLDIERTYPE *pSoldier); INT8 DecideAction(SOLDIERTYPE *pSoldier);
INT8 DecideActionBlack(SOLDIERTYPE *pSoldier);
INT8 DecideActionEscort(SOLDIERTYPE *pSoldier);
INT8 DecideActionGreen(SOLDIERTYPE *pSoldier); INT8 DecideActionGreen(SOLDIERTYPE *pSoldier);
INT8 DecideActionRed(SOLDIERTYPE *pSoldier);
INT8 DecideActionYellow(SOLDIERTYPE *pSoldier); INT8 DecideActionYellow(SOLDIERTYPE *pSoldier);
INT8 DecideActionRed(SOLDIERTYPE *pSoldier);
INT8 DecideActionBlack(SOLDIERTYPE *pSoldier);
INT8 DecideActionGreenBoxer(SOLDIERTYPE* pSoldier);
INT8 DecideActionBlackBoxer(SOLDIERTYPE* pSoldier);
INT8 DecideActionGreenCivilian(SOLDIERTYPE* pSoldier);
INT8 DecideActionYellowCivilian(SOLDIERTYPE* pSoldier);
INT8 DecideActionRedCivilian(SOLDIERTYPE* pSoldier);
INT8 DecideActionBlackCivilian(SOLDIERTYPE* pSoldier);
INT8 DecideActionGreenSoldier(SOLDIERTYPE* pSoldier);
INT8 DecideActionYellowSoldier(SOLDIERTYPE* pSoldier);
INT8 DecideActionRedSoldier(SOLDIERTYPE* pSoldier);
INT8 DecideActionBlackSoldier(SOLDIERTYPE* pSoldier);
INT8 DecideActionGreenRobot(SOLDIERTYPE* pSoldier);
INT8 DecideActionYellowRobot(SOLDIERTYPE* pSoldier);
INT8 DecideActionRedRobot(SOLDIERTYPE* pSoldier);
INT8 DecideActionBlackRobot(SOLDIERTYPE* pSoldier);
INT8 DecideActionBlackSoldierUtilityAI(SOLDIERTYPE* pSoldier);
INT16 DistanceToClosestFriend( SOLDIERTYPE * pSoldier ); INT16 DistanceToClosestFriend( SOLDIERTYPE * pSoldier );
@@ -199,11 +213,12 @@ void EndAIGuysTurn( SOLDIERTYPE *pSoldier );
INT8 ExecuteAction(SOLDIERTYPE *pSoldier); INT8 ExecuteAction(SOLDIERTYPE *pSoldier);
INT32 FindAdjacentSpotBeside(SOLDIERTYPE *pSoldier, INT32 sGridNo); INT32 FindAdjacentSpotBeside(SOLDIERTYPE *pSoldier, INT32 sGridNo);
INT32 FindBestNearbyCover(SOLDIERTYPE *pSoldier, INT32 morale, INT32 *pPercentBetter); INT32 FindBestNearbyCover(SOLDIERTYPE *pSoldier, INT32 morale, INT32 *pPercentBetter, INT32 targetGridNo = NOWHERE, bool ignoreSearchRange = false);
INT32 FindClosestDoor( SOLDIERTYPE * pSoldier ); INT32 FindClosestDoor( SOLDIERTYPE * pSoldier );
INT32 FindNearbyPointOnEdgeOfMap( SOLDIERTYPE * pSoldier, INT8 * pbDirection ); INT32 FindNearbyPointOnEdgeOfMap( SOLDIERTYPE * pSoldier, INT8 * pbDirection );
INT32 FindNearestEdgePoint( INT32 sGridNo ); INT32 FindNearestEdgePoint( INT32 sGridNo );
INT32 FindNearestPassableSpot( INT32 sGridNo, UINT8 usSearchRadius = 5 ); INT32 FindNearestPassableSpot( INT32 sGridNo, UINT8 usSearchRadius = 5 );
BOOLEAN FindFenceAroundSpot(INT32 sSpot);
//Kris: Added these as I need specific searches on certain sides. //Kris: Added these as I need specific searches on certain sides.
enum enum
@@ -244,7 +259,7 @@ void ManChecksOnFriends(SOLDIERTYPE *pSoldier);
void NewDest(SOLDIERTYPE *pSoldier, INT32 sGridNo); void NewDest(SOLDIERTYPE *pSoldier, INT32 sGridNo);
INT32 NextPatrolPoint(SOLDIERTYPE *pSoldier); INT32 NextPatrolPoint(SOLDIERTYPE *pSoldier);
INT8 PanicAI(SOLDIERTYPE *pSoldier, UINT8 ubCanMove); ActionType PanicAI(SOLDIERTYPE *pSoldier, UINT8 ubCanMove);
void HaltMoveForSoldierOutOfPoints(SOLDIERTYPE *pSoldier); void HaltMoveForSoldierOutOfPoints(SOLDIERTYPE *pSoldier);
INT32 RandDestWithinRange(SOLDIERTYPE *pSoldier); INT32 RandDestWithinRange(SOLDIERTYPE *pSoldier);
@@ -288,6 +303,7 @@ SoldierID GetClosestMedicSoldierID( SOLDIERTYPE * pSoldier, INT16 aRange, UINT8
// sevenfm: // sevenfm:
BOOLEAN NightLight(void); BOOLEAN NightLight(void);
BOOLEAN DuskLight(void); BOOLEAN DuskLight(void);
BOOLEAN FindClosestVisibleSmoke(SOLDIERTYPE* pSoldier, INT32& sSpot, INT8& bLevel, BOOLEAN fOnlyGas);
BOOLEAN InSmokeNearby(INT32 sGridNo, INT8 bLevel); BOOLEAN InSmokeNearby(INT32 sGridNo, INT8 bLevel);
INT16 MaxNormalVisionDistance( void ); INT16 MaxNormalVisionDistance( void );
UINT8 MinFlankDirections( SOLDIERTYPE *pSoldier ); UINT8 MinFlankDirections( SOLDIERTYPE *pSoldier );
@@ -300,6 +316,13 @@ UINT8 CountFriendsBlack( SOLDIERTYPE *pSoldier, INT32 sClosestOpponent = NOWHERE
UINT16 CountTeamUnderAttack(INT8 bTeam, INT32 sGridNo, INT16 sDistance); UINT16 CountTeamUnderAttack(INT8 bTeam, INT32 sGridNo, INT16 sDistance);
UINT16 CountPublicKnownEnemies(SOLDIERTYPE *pSoldier, INT32 sGridNo, INT16 sDistance); UINT16 CountPublicKnownEnemies(SOLDIERTYPE *pSoldier, INT32 sGridNo, INT16 sDistance);
UINT16 CountPublicKnownEnemies(SOLDIERTYPE *pSoldier); UINT16 CountPublicKnownEnemies(SOLDIERTYPE *pSoldier);
UINT8 CountKnownEnemies(SOLDIERTYPE* pSoldier, INT32 sSpot, INT16 sDistance, INT8 bLevel = HEARD_THIS_TURN);
UINT8 CountKnownEnemiesInRoom(SOLDIERTYPE* pSoldier, UINT16 usRoom);
UINT8 CountFriendsInRoom(SOLDIERTYPE* pSoldier, UINT16 usRoom);
INT32 CountCorpsesInRoom(SOLDIERTYPE* pSoldier, UINT16 usRoomNo, INT8 bLevel);
UINT16 RoomNo(INT32 sSpot);
BOOLEAN SameRoom(INT32 sSpot1, INT32 sSpot2);
BOOLEAN CheckWindow(INT32 sSpot, UINT8 ubDirection, BOOLEAN fAllowClosed);
UINT8 SectorCurfew(BOOLEAN fNight); UINT8 SectorCurfew(BOOLEAN fNight);
UINT8 TeamPercentKilled(INT8 bTeam); UINT8 TeamPercentKilled(INT8 bTeam);
@@ -420,6 +443,10 @@ INT8 KnownPublicLevel(UINT8 bTeam, SoldierID ubOpponentID);
// sevenfm: distance for tactical AI checks, roughly equal to normal day vision range // sevenfm: distance for tactical AI checks, roughly equal to normal day vision range
#define TACTICAL_RANGE (gGameExternalOptions.ubStraightSightRange * STRAIGHT_RATIO * 2) #define TACTICAL_RANGE (gGameExternalOptions.ubStraightSightRange * STRAIGHT_RATIO * 2)
#define TACTICAL_RANGE_CELL_COORDS TACTICAL_RANGE*CELL_X_SIZE
#define BOMB_DETECTION_RANGE (TACTICAL_RANGE / 4) #define BOMB_DETECTION_RANGE (TACTICAL_RANGE / 4)
#define TACTICAL_RANGE_HALF (TACTICAL_RANGE / 2)
#define TACTICAL_RANGE_CLOSE (TACTICAL_RANGE / 4)
#define TACTICAL_RANGE_VERYCLOSE (TACTICAL_RANGE / 6)
#endif #endif
+1 -1
View File
@@ -22,7 +22,7 @@ UINT32 guiFireAmbientLastUpdate = 0;
extern STR8 pVertStrings[]; extern STR8 pVertStrings[];
extern STR8 pHortStrings[]; extern STR8 pHortStrings[];
extern CHAR16 (*pTownNames)[MAX_TOWN_NAME_LENGHT]; extern CHAR16 pTownNames[MAX_TOWNS][MAX_TOWN_NAME_LENGHT];
UINT8 gubCurrentSteadyStateAmbience = SSA_NONE; UINT8 gubCurrentSteadyStateAmbience = SSA_NONE;
UINT8 gubCurrentSteadyStateSound = 0; UINT8 gubCurrentSteadyStateSound = 0;
+1 -64
View File
@@ -1319,7 +1319,7 @@ BOOLEAN FindFenceJumpDirection( SOLDIERTYPE *pSoldier, INT32 sGridNo, INT8 bStar
return( FALSE ); return( FALSE );
} }
//Simply chooses a random gridno within valid boundaries (for dropping things in loaded sectors) //Simply chooses a random gridno within valid boundaries (for dropping things in unloaded sectors)
INT32 RandomGridNo() INT32 RandomGridNo()
{ {
INT32 iMapXPos, iMapYPos, iMapIndex; INT32 iMapXPos, iMapYPos, iMapIndex;
@@ -1332,69 +1332,6 @@ INT32 RandomGridNo()
return iMapIndex; return iMapIndex;
} }
static bool pointLeftOfEdge(INT32 xp, INT32 yp, INT32 x1, INT32 y1, INT32 x2, INT32 y2)
{
//Test whether the point(xp, yp) lies on the left-hand side of the edge(x1, y1) - (x2, y2)
const auto D = (x2 - x1) * (yp - y1) - (xp - x1) * (y2 - y1);
return (D > 0);
}
INT32 RandomGridNoUnloadedSector(INT32 worldRows, INT32 worldCols)
{
INT32 iMapIndex;
const auto centerHeight = gpWorldLevelDataAutoResolve[gMapInformationAutoResolve.sCenterGridNo].sHeight;
const auto CenterWorldX = (WORLD_ROWS) / 2 * CELL_X_SIZE;
const auto CenterWorldY = (WORLD_COLS) / 2 * CELL_Y_SIZE;
// Sector corners are shifted inwards a bit to limit the area to be slightly smaller than actual sector.
// It fixed a few annyoing edge cases where a tile would evaluate to being inside the map, but it was juuust out of reach in actual gameplay
const auto TLX = 2*CELL_X_SIZE; // Shift X-coordinate by one tile inwards from actual edge.
const auto TLY = (WORLD_ROWS / 2) * CELL_X_SIZE;
const auto TRX = (WORLD_COLS / 2) * CELL_Y_SIZE;
const auto TRY = 2*CELL_X_SIZE; // Shift Y-coordinate by one tile inwards from actual edge
const auto BLX = ((WORLD_COLS / 2) * CELL_Y_SIZE);
const auto BLY = ((WORLD_ROWS-1) * CELL_Y_SIZE); // Shift Y-coordinate by one row inwards from actual edge
const auto BRX = ((WORLD_COLS-1) * CELL_Y_SIZE); // Shift X-coordinate by one row inwards from actual edge
const auto BRY = ((WORLD_ROWS / 2) * CELL_X_SIZE);
UINT8 randomGridHeight = 255;
do
{
randomGridHeight = 255; // Reset height to be tested
INT32 iMapXPos = Random(worldCols);
INT32 iMapYPos = Random(worldRows);
iMapIndex = iMapYPos * worldCols + iMapXPos;
// Discard gridno that is not within map bounds.
// Check point against world area's edges. If any edge test fails, it means point is not inside.
// Edge traverals goes counterclockwise, Top Right -> Top Left -> Bottom Left -> Bottom Right -> Top Right
const auto pX = iMapXPos * CELL_X_SIZE;
const auto pY = iMapYPos * CELL_Y_SIZE;
//TR->TL
if ( !pointLeftOfEdge(pX, pY, TLX, TLY, TRX, TRY) )
continue;
//TL->BL
if ( !pointLeftOfEdge(pX, pY, BLX, BLY, TLX, TLY) )
continue;
//BL->BR
if ( !pointLeftOfEdge(pX, pY, BRX, BRY, BLX, BLY) )
continue;
//BR->TR
if ( !pointLeftOfEdge(pX, pY, TRX, TRY, BRX, BRY) )
continue;
// Compare randomGridHeight with height of the center grid of the map, as long as the center of the map is on walkable height it will work properly
randomGridHeight = gpWorldLevelDataAutoResolve[iMapIndex].sHeight;
} while ( !(randomGridHeight == centerHeight) );
return iMapIndex;
}
// Flugente: is this gridno near a player merc? // Flugente: is this gridno near a player merc?
BOOLEAN GridNoNearPlayerMercs( INT32 sGridNo, INT16 sRadius ) BOOLEAN GridNoNearPlayerMercs( INT32 sGridNo, INT16 sRadius )
{ {
-1
View File
@@ -113,7 +113,6 @@ BOOLEAN FindFenceJumpDirection( SOLDIERTYPE *pSoldier, INT32 sGridNo, INT8 bStar
//Simply chooses a random gridno within valid boundaries (for dropping things in unloaded sectors) //Simply chooses a random gridno within valid boundaries (for dropping things in unloaded sectors)
INT32 RandomGridNo(); INT32 RandomGridNo();
INT32 RandomGridNoUnloadedSector(INT32 worldRows, INT32 worldCols);
extern UINT32 guiForceRefreshMousePositionCalculation; extern UINT32 guiForceRefreshMousePositionCalculation;
+175 -179
View File
@@ -23,6 +23,7 @@
#include "LogicalBodyTypes/BodyTypeDB.h" #include "LogicalBodyTypes/BodyTypeDB.h"
#include "Utilities.h" #include "Utilities.h"
#include <Cheats.h>
UINT32 guiShieldGraphic = 0; UINT32 guiShieldGraphic = 0;
BOOLEAN fShieldGraphicInit = FALSE; BOOLEAN fShieldGraphicInit = FALSE;
@@ -552,22 +553,25 @@ void ResetRenderParameters( );
void RenderRoomInfo( INT16 sStartPointX_M, INT16 sStartPointY_M, INT16 sStartPointX_S, INT16 sStartPointY_S, INT16 sEndXS, INT16 sEndYS ); void RenderRoomInfo( INT16 sStartPointX_M, INT16 sStartPointY_M, INT16 sStartPointX_S, INT16 sStartPointY_S, INT16 sEndXS, INT16 sEndYS );
//extern UINT8 * gubFOVDebugInfoInfo;
#ifdef _DEBUG //extern UINT8 * gubGridNoMarkers;
//extern UINT8 gubFOVDebugInfoInfo[ WORLD_MAX ]; //extern UINT8 gubGridNoValue;
//extern UINT8 gubGridNoMarkers[ WORLD_MAX ]; //extern BOOLEAN gfDisplayGridNoVisibleValues = 0;
extern UINT8 * gubFOVDebugInfoInfo; //void RenderFOVDebugInfo( INT16 sStartPointX_M, INT16 sStartPointY_M, INT16 sStartPointX_S, INT16 sStartPointY_S, INT16 sEndXS, INT16 sEndYS );
extern UINT8 * gubGridNoMarkers; //void RenderGridNoVisibleDebugInfo( INT16 sStartPointX_M, INT16 sStartPointY_M, INT16 sStartPointX_S, INT16 sStartPointY_S, INT16 sEndXS, INT16 sEndYS );
extern UINT8 gubGridNoValue; extern INT16 gsBestCover;
extern BOOLEAN gfDisplayCoverValues; INT32* gRenderDebugInfoValues = nullptr;
extern BOOLEAN gfDisplayGridNoVisibleValues = 0; void RenderDebugInfo(INT16 sStartPointX_M, INT16 sStartPointY_M, INT16 sStartPointX_S, INT16 sStartPointY_S, INT16 sEndXS, INT16 sEndYS);
//extern INT16 gsCoverValue[ WORLD_MAX ]; void ResetDebugInfoValues()
extern INT16 * gsCoverValue; {
extern INT16 gsBestCover; if (gRenderDebugInfoValues)
void RenderFOVDebugInfo( INT16 sStartPointX_M, INT16 sStartPointY_M, INT16 sStartPointX_S, INT16 sStartPointY_S, INT16 sEndXS, INT16 sEndYS ); {
void RenderCoverDebugInfo( INT16 sStartPointX_M, INT16 sStartPointY_M, INT16 sStartPointX_S, INT16 sStartPointY_S, INT16 sEndXS, INT16 sEndYS ); for (size_t i = 0; i < WORLD_MAX; i++)
void RenderGridNoVisibleDebugInfo( INT16 sStartPointX_M, INT16 sStartPointY_M, INT16 sStartPointX_S, INT16 sStartPointY_S, INT16 sEndXS, INT16 sEndYS ); {
#endif gRenderDebugInfoValues[i] = 0x7FFFFFFF;
}
}
}
void DeleteFromWorld( UINT16 usTileIndex, UINT32 uiRenderTiles, UINT16 usIndex ); void DeleteFromWorld( UINT16 usTileIndex, UINT32 uiRenderTiles, UINT16 usIndex );
@@ -3272,23 +3276,10 @@ UINT32 cnt = 0;
RenderRoomInfo( gsStartPointX_M, gsStartPointY_M, gsStartPointX_S, gsStartPointY_S, gsEndXS, gsEndYS ); RenderRoomInfo( gsStartPointX_M, gsStartPointY_M, gsStartPointX_S, gsStartPointY_S, gsEndXS, gsEndYS );
} }
#ifdef _DEBUG if (DEBUG_CHEAT_LEVEL() && gTacticalStatus.Team[OUR_TEAM].bTeamActive)
if( gRenderFlags&RENDER_FLAG_FOVDEBUG )
{ {
RenderFOVDebugInfo( gsStartPointX_M, gsStartPointY_M, gsStartPointX_S, gsStartPointY_S, gsEndXS, gsEndYS ); RenderDebugInfo(gsStartPointX_M, gsStartPointY_M, gsStartPointX_S, gsStartPointY_S, gsEndXS, gsEndYS);
} }
else if (gfDisplayCoverValues)
{
RenderCoverDebugInfo( gsStartPointX_M, gsStartPointY_M, gsStartPointX_S, gsStartPointY_S, gsEndXS, gsEndYS );
}
else if (gfDisplayGridNoVisibleValues)
{
RenderGridNoVisibleDebugInfo( gsStartPointX_M, gsStartPointY_M, gsStartPointX_S, gsStartPointY_S, gsEndXS, gsEndYS );
}
#endif
//#endif
//RenderStaticWorldRect( gsVIEWPORT_START_X, gsVIEWPORT_START_Y, gsVIEWPORT_END_X, gsVIEWPORT_END_Y ); //RenderStaticWorldRect( gsVIEWPORT_START_X, gsVIEWPORT_START_Y, gsVIEWPORT_END_X, gsVIEWPORT_END_Y );
//AddBaseDirtyRect(gsVIEWPORT_START_X, gsVIEWPORT_START_Y, gsVIEWPORT_END_X, gsVIEWPORT_END_Y ); //AddBaseDirtyRect(gsVIEWPORT_START_X, gsVIEWPORT_START_Y, gsVIEWPORT_END_X, gsVIEWPORT_END_Y );
@@ -8014,6 +8005,10 @@ void RenderRoomInfo( INT16 sStartPointX_M, INT16 sStartPointY_M, INT16 sStartPoi
#ifdef _DEBUG #ifdef _DEBUG
// From fov.cpp
extern UINT8* gubGridNoMarkers;
extern UINT8 gubGridNoValue;
extern UINT8* gubFOVDebugInfoInfo;
void RenderFOVDebugInfo( INT16 sStartPointX_M, INT16 sStartPointY_M, INT16 sStartPointX_S, INT16 sStartPointY_S, INT16 sEndXS, INT16 sEndYS ) void RenderFOVDebugInfo( INT16 sStartPointX_M, INT16 sStartPointY_M, INT16 sStartPointX_S, INT16 sStartPointY_S, INT16 sEndXS, INT16 sEndYS )
{ {
@@ -8124,114 +8119,7 @@ void RenderFOVDebugInfo( INT16 sStartPointX_M, INT16 sStartPointY_M, INT16 sStar
} }
void RenderCoverDebugInfo( INT16 sStartPointX_M, INT16 sStartPointY_M, INT16 sStartPointX_S, INT16 sStartPointY_S, INT16 sEndXS, INT16 sEndYS ) // This one just renders tile gridnos. Not much use nowadays
{
INT8 bXOddFlag = 0;
INT16 sAnchorPosX_M, sAnchorPosY_M;
INT16 sAnchorPosX_S, sAnchorPosY_S;
INT16 sTempPosX_M, sTempPosY_M;
INT16 sTempPosX_S, sTempPosY_S;
BOOLEAN fEndRenderRow = FALSE, fEndRenderCol = FALSE;
UINT16 usTileIndex;
INT16 sX, sY;
UINT32 uiDestPitchBYTES;
UINT8 *pDestBuf;
// Begin Render Loop
sAnchorPosX_M = sStartPointX_M;
sAnchorPosY_M = sStartPointY_M;
sAnchorPosX_S = sStartPointX_S;
sAnchorPosY_S = sStartPointY_S;
pDestBuf = LockVideoSurface( FRAME_BUFFER, &uiDestPitchBYTES );
do
{
fEndRenderRow = FALSE;
sTempPosX_M = sAnchorPosX_M;
sTempPosY_M = sAnchorPosY_M;
sTempPosX_S = sAnchorPosX_S;
sTempPosY_S = sAnchorPosY_S;
if(bXOddFlag > 0)
sTempPosX_S += 20;
do
{
usTileIndex=FASTMAPROWCOLTOPOS( sTempPosY_M, sTempPosX_M );
if ( usTileIndex < GRIDSIZE )
{
sX = sTempPosX_S + ( WORLD_TILE_X / 2 ) - 5;
sY = sTempPosY_S + ( WORLD_TILE_Y / 2 ) - 5;
// Adjust for interface level
sY -= gpWorldLevelData[ usTileIndex ].sHeight;
sY += gsRenderHeight;
if (gsCoverValue[ usTileIndex] != 0x7F7F)
{
SetFont( SMALLCOMPFONT );
SetFontDestBuffer( FRAME_BUFFER , 0, 0, SCREEN_WIDTH, gsVIEWPORT_END_Y, FALSE );
if (usTileIndex == gsBestCover)
{
SetFontForeground( FONT_MCOLOR_RED );
}
else if (gsCoverValue[ usTileIndex ] < 0)
{
SetFontForeground( FONT_MCOLOR_WHITE );
}
else
{
SetFontForeground( FONT_GRAY3 );
}
mprintf_buffer( pDestBuf, uiDestPitchBYTES, TINYFONT1, sX, sY , L"%d", gsCoverValue[ usTileIndex ] );
SetFontDestBuffer( FRAME_BUFFER , 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, FALSE );
}
}
sTempPosX_S += 40;
sTempPosX_M ++;
sTempPosY_M --;
if ( sTempPosX_S >= sEndXS )
{
fEndRenderRow = TRUE;
}
} while( !fEndRenderRow );
if ( bXOddFlag > 0 )
{
sAnchorPosY_M ++;
}
else
{
sAnchorPosX_M ++;
}
bXOddFlag = !bXOddFlag;
sAnchorPosY_S += 10;
if ( sAnchorPosY_S >= sEndYS )
{
fEndRenderCol = TRUE;
}
}
while( !fEndRenderCol );
UnLockVideoSurface( FRAME_BUFFER );
}
void RenderGridNoVisibleDebugInfo( INT16 sStartPointX_M, INT16 sStartPointY_M, INT16 sStartPointX_S, INT16 sStartPointY_S, INT16 sEndXS, INT16 sEndYS ) void RenderGridNoVisibleDebugInfo( INT16 sStartPointX_M, INT16 sStartPointY_M, INT16 sStartPointX_S, INT16 sStartPointY_S, INT16 sEndXS, INT16 sEndYS )
{ {
INT8 bXOddFlag = 0; INT8 bXOddFlag = 0;
@@ -8334,9 +8222,148 @@ void RenderGridNoVisibleDebugInfo( INT16 sStartPointX_M, INT16 sStartPointY_M, I
} }
void RenderFOVDebug()
{
RenderFOVDebugInfo(gsStartPointX_M, gsStartPointY_M, gsStartPointX_S, gsStartPointY_S, gsEndXS, gsEndYS);
}
void RenderGridNoVisibleDebug()
{
RenderGridNoVisibleDebugInfo(gsStartPointX_M, gsStartPointY_M, gsStartPointX_S, gsStartPointY_S, gsEndXS, gsEndYS);
}
#endif #endif
void RenderDebugInfo(INT16 sStartPointX_M, INT16 sStartPointY_M, INT16 sStartPointX_S, INT16 sStartPointY_S, INT16 sEndXS, INT16 sEndYS)
{
INT8 bXOddFlag = 0;
INT16 sTempPosX_M, sTempPosY_M;
INT16 sTempPosX_S, sTempPosY_S;
BOOLEAN fEndRenderRow = FALSE, fEndRenderCol = FALSE;
// Begin Render Loop
INT16 sAnchorPosX_M = sStartPointX_M;
INT16 sAnchorPosY_M = sStartPointY_M;
INT16 sAnchorPosX_S = sStartPointX_S;
INT16 sAnchorPosY_S = sStartPointY_S;
UINT32 uiDestPitchBYTES;
UINT8* pDestBuf = LockVideoSurface(FRAME_BUFFER, &uiDestPitchBYTES);
const auto mode = gRenderDebugInfoMode;
do
{
fEndRenderRow = FALSE;
sTempPosX_M = sAnchorPosX_M;
sTempPosY_M = sAnchorPosY_M;
sTempPosX_S = sAnchorPosX_S;
sTempPosY_S = sAnchorPosY_S;
if (bXOddFlag > 0)
sTempPosX_S += 20;
do
{
UINT16 usTileIndex = FASTMAPROWCOLTOPOS(sTempPosY_M, sTempPosX_M);
if (usTileIndex < GRIDSIZE)
{
INT16 sX = sTempPosX_S + (WORLD_TILE_X / 2) - 5;
INT16 sY = sTempPosY_S + (WORLD_TILE_Y / 2) - 5;
// Adjust for interface level
sY -= gpWorldLevelData[usTileIndex].sHeight;
sY += gsRenderHeight;
if (gRenderDebugInfoValues[usTileIndex] != 0x7FFFFFFF)
{
SetFont(SMALLCOMPFONT);
SetFontDestBuffer(FRAME_BUFFER, 0, 0, SCREEN_WIDTH, gsVIEWPORT_END_Y, FALSE);
////////////////////////////
// Debug mode specific setup
switch (mode)
{
case DEBUG_PATHFINDING:
if (gRenderDebugInfoValues[usTileIndex] < 0)
{
SetFontForeground(FONT_LTRED);
}
else
{
SetFontForeground(FONT_LTGREEN);
}
break;
case DEBUG_THREATVALUE:
//TODO implement
goto exit_loop;
break;
case DEBUG_COVERVALUE:
if (usTileIndex == gsBestCover)
{
SetFontForeground(FONT_YELLOW);
}
else if (gRenderDebugInfoValues[usTileIndex] < 0)
{
SetFontForeground(FONT_LTRED);
}
else
{
SetFontForeground(FONT_LTGREEN);
}
break;
default:
goto exit_loop;
break;
}
////////////////////////////
mprintf_buffer(pDestBuf, uiDestPitchBYTES, TINYFONT1, sX, sY, L"%d", gRenderDebugInfoValues[usTileIndex]);
SetFontDestBuffer(FRAME_BUFFER, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, FALSE);
}
}
sTempPosX_S += 40;
sTempPosX_M++;
sTempPosY_M--;
if (sTempPosX_S >= sEndXS)
{
fEndRenderRow = TRUE;
}
} while (!fEndRenderRow);
if (bXOddFlag > 0)
{
sAnchorPosY_M++;
}
else
{
sAnchorPosX_M++;
}
bXOddFlag = !bXOddFlag;
sAnchorPosY_S += 10;
if (sAnchorPosY_S >= sEndYS)
{
fEndRenderCol = TRUE;
}
} while (!fEndRenderCol);
exit_loop:
UnLockVideoSurface(FRAME_BUFFER);
}
void ExamineZBufferRect( INT16 sLeft, INT16 sTop, INT16 sRight, INT16 sBottom) void ExamineZBufferRect( INT16 sLeft, INT16 sTop, INT16 sRight, INT16 sBottom)
{ {
CalcRenderParameters( sLeft, sTop, sRight, sBottom ); CalcRenderParameters( sLeft, sTop, sRight, sBottom );
@@ -8518,7 +8545,8 @@ void CalcRenderParameters(INT16 sLeft, INT16 sTop, INT16 sRight, INT16 sBottom )
gsStartPointY_W = sRenderCenterY_W - sTempPosY_W; gsStartPointY_W = sRenderCenterY_W - sTempPosY_W;
// NOTE: Increase X map value by 1 tile to offset where on screen we are... // NOTE: Increase X map value by 1 tile to offset where on screen we are...
gsStartPointX_W += CELL_X_SIZE; if ( gsStartPointX_W > 0 )
gsStartPointX_W += CELL_X_SIZE;
// d) screen start point is screen distances minus screen center // d) screen start point is screen distances minus screen center
gsStartPointX_S = sLeft - VIEWPORT_XOFFSET_S; gsStartPointX_S = sLeft - VIEWPORT_XOFFSET_S;
@@ -8526,8 +8554,8 @@ void CalcRenderParameters(INT16 sLeft, INT16 sTop, INT16 sRight, INT16 sBottom )
// STEP FOUR - Determine Start block // STEP FOUR - Determine Start block
// a) Find start block // a) Find start block
gsStartPointX_M = floor( DOUBLE(gsStartPointX_W) / DOUBLE(CELL_X_SIZE) ); gsStartPointX_M = ( gsStartPointX_W ) / CELL_X_SIZE;
gsStartPointY_M = floor( DOUBLE(gsStartPointY_W) / DOUBLE(CELL_Y_SIZE) ); gsStartPointY_M = ( gsStartPointY_W ) / CELL_Y_SIZE;
// STEP 5 - Determine Deltas for center and find screen values // STEP 5 - Determine Deltas for center and find screen values
//Make sure these coordinates are multiples of scroll steps //Make sure these coordinates are multiples of scroll steps
@@ -8563,7 +8591,7 @@ void CalcRenderParameters(INT16 sLeft, INT16 sTop, INT16 sRight, INT16 sBottom )
gsLEndXS = sRight + LARGER_VIEWPORT_XOFFSET_S; gsLEndXS = sRight + LARGER_VIEWPORT_XOFFSET_S;
gsLEndYS = sBottom + LARGER_VIEWPORT_YOFFSET_S; gsLEndYS = sBottom + LARGER_VIEWPORT_YOFFSET_S;
// STEP THREE - determine starting point in world coords // STEP THREE - determine starting point in world coords
// a) Determine where in screen coords to start rendering // a) Determine where in screen coords to start rendering
gsLStartPointX_S = ( ( gsVIEWPORT_END_X - gsVIEWPORT_START_X ) /2 ) - (sLeft - LARGER_VIEWPORT_XOFFSET_S); gsLStartPointX_S = ( ( gsVIEWPORT_END_X - gsVIEWPORT_START_X ) /2 ) - (sLeft - LARGER_VIEWPORT_XOFFSET_S);
gsLStartPointY_S = ( ( gsVIEWPORT_END_Y - gsVIEWPORT_START_Y ) /2 ) - (sTop - LARGER_VIEWPORT_YOFFSET_S); gsLStartPointY_S = ( ( gsVIEWPORT_END_Y - gsVIEWPORT_START_Y ) /2 ) - (sTop - LARGER_VIEWPORT_YOFFSET_S);
@@ -8577,7 +8605,8 @@ void CalcRenderParameters(INT16 sLeft, INT16 sTop, INT16 sRight, INT16 sBottom )
gsLStartPointY_W = sRenderCenterY_W - sTempPosY_W; gsLStartPointY_W = sRenderCenterY_W - sTempPosY_W;
// NOTE: Increase X map value by 1 tile to offset where on screen we are... // NOTE: Increase X map value by 1 tile to offset where on screen we are...
gsLStartPointX_W += CELL_X_SIZE; if ( gsLStartPointX_W > 0 )
gsLStartPointX_W += CELL_X_SIZE;
// d) screen start point is screen distances minus screen center // d) screen start point is screen distances minus screen center
gsLStartPointX_S = sLeft - LARGER_VIEWPORT_XOFFSET_S; gsLStartPointX_S = sLeft - LARGER_VIEWPORT_XOFFSET_S;
@@ -8585,23 +8614,8 @@ void CalcRenderParameters(INT16 sLeft, INT16 sTop, INT16 sRight, INT16 sBottom )
// STEP FOUR - Determine Start block // STEP FOUR - Determine Start block
// a) Find start block // a) Find start block
gsLStartPointX_M = floor( DOUBLE(gsLStartPointX_W) / DOUBLE(CELL_X_SIZE) ); gsLStartPointX_M = ( gsLStartPointX_W ) / CELL_X_SIZE;
gsLStartPointY_M = floor( DOUBLE(gsLStartPointY_W) / DOUBLE(CELL_Y_SIZE) ); gsLStartPointY_M = ( gsLStartPointY_W ) / CELL_Y_SIZE;
// Check and correct the difference between smaller and larger start blocks in tile coordinates.
// Regardless of resolution, the difference between the start blocks are constant 10 tiles in X- and 6 tiles in Y-direction.
// If the difference doesn't match, static world is rendered in the wrong place relative to dynamic world.
// Mainly evident in 960x540 resolution.
const auto ratioX_M = gsStartPointX_M - gsLStartPointX_M;
if ( ratioX_M != 10)
{
gsLStartPointX_M -= 10 - ratioX_M;
}
const auto ratioY_M = gsStartPointY_M - gsLStartPointY_M;
if ( ratioY_M != 6 )
{
gsLStartPointY_M -= 6 - ratioY_M;
}
// Adjust starting screen coordinates // Adjust starting screen coordinates
gsLStartPointX_S -= sOffsetX_S; gsLStartPointX_S -= sOffsetX_S;
@@ -9010,21 +9024,3 @@ void SetRenderCenter( INT16 sNewX, INT16 sNewY )
gfScrollInertia = FALSE; gfScrollInertia = FALSE;
} }
#ifdef _DEBUG
void RenderFOVDebug( )
{
RenderFOVDebugInfo( gsStartPointX_M, gsStartPointY_M, gsStartPointX_S, gsStartPointY_S, gsEndXS, gsEndYS );
}
void RenderCoverDebug( )
{
RenderCoverDebugInfo( gsStartPointX_M, gsStartPointY_M, gsStartPointX_S, gsStartPointY_S, gsEndXS, gsEndYS );
}
static void RenderGridNoVisibleDebug( )
{
RenderGridNoVisibleDebugInfo( gsStartPointX_M, gsStartPointY_M, gsStartPointX_S, gsStartPointY_S, gsEndXS, gsEndYS );
}
#endif

Some files were not shown because too many files have changed in this diff Show More