73 Commits
Author SHA1 Message Date
Marco Antonio J. Costaandmajcosta 3d3e1bbfc6 refactor Translate()
it returns true but no consumers use it. its return type is void, then
2026-07-21 18:39:50 -03:00
Marco Antonio J. Costaandmajcosta 738225d2a2 move ImportStrings to i18n 2026-07-21 18:39:50 -03:00
Marco Antonio J. Costaandmajcosta 4be9b106f4 give more local functions internal linkage 2026-07-21 18:39:50 -03:00
Marco Antonio J. Costaandmajcosta af4eeb09f4 make local-only functions static 2026-07-21 18:39:50 -03:00
Marco Antonio J. Costaandmajcosta 15821498a3 text declarations go into text.h 2026-07-21 18:39:50 -03:00
Marco Antonio J. Costaandmajcosta 54bdce6c46 cleanup: dead code removal 2026-07-21 18:39:50 -03:00
Marco Antonio J. Costaandmajcosta 433de0e67b use exhaustive switch instead of if statements 2026-07-21 18:39:50 -03:00
Marco Antonio J. Costaandmajcosta 30e5d8f91c i18n: we already have one Language enum, no need for two 2026-07-21 18:39:50 -03:00
Marco Antonio J. Costaandmajcosta 506ea842b8 i18n: pull some TUs that do not belong in Utils 2026-07-21 18:39:50 -03:00
Marco Antonio J. Costaandmajcosta 9adc1ad998 node20 is deprecated, clear the warnings
bump action versions and change the MSVC action to a composite one that doesn't use node at all

also remove msbuild which we don't need
2026-07-21 14:45:04 -03:00
Marco Antonio J. Costaandmajcosta 8d1877d26d read language from Language.ini file
to avoid having to ship a whole ja2.ini for languages that
currently don't

command-line /language:german still works, but INI file is
prioritized

format:

```
[Language]
LANGUAGE = ITALIAN
```
2026-07-21 14:42:52 -03:00
Marco Antonio J. Costaandmajcosta d03989051a remove Languages from CI
no need anymore
2026-07-21 11:23:08 -03:00
Marco Antonio J. Costaandmajcosta 5da2ee8bf6 better initialization of language defaults 2026-07-21 09:46:41 -03:00
Marco Antonio J. Costaandmajcosta aa0aac2bfe remove unused includes 2026-07-21 09:46:41 -03:00
ee098125e6 Strip dead per-language .slf entries from gGameLibaries
InitializeFileDatabase, the only reader of gGameLibaries[], is called
nowhere in the tree — VFS replaced this legacy loader. Delete the
7 #ifdef GERMAN/POLISH/DUTCH/ITALIAN/RUSSIAN/FRENCH/CHINESE entries
outright rather than runtime-selecting them.

NPC.h: two commented-out #if defined(CRIPPLED_VERSION)||defined(RUSSIAN)
triplets. AimMembers.cpp: two commented-out #ifdef POLISH character-remap
switch blocks. All already-dead. Verify grep for language macros outside
i18n/ now returns only unrelated hits (filename literal, dev-tool CLI flag,
stray comment, historical prose) — no #ifdef guards left.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-21 09:46:41 -03:00
Marco Antonio J. Costaandmajcosta bada3ef31a cleanup: remove last remnants of language preprocessor definitions
good riddance
2026-07-21 09:46:41 -03:00
Marco Antonio J. Costaandmajcosta 00a805cc39 remove lying, outdated or superfluous comments
no more guards, no more cpp files included
2026-07-21 09:46:41 -03:00
fd2c74727e Make LANGUAGE switchable in runtime (#653)
squashed into a single commit because doing it in steps that all
compiled involved a lot of boilerplate that is pointless to commit to
the repository. GH still has the PR for reference.

g_lang, MAX_MESSAGES_ON_MAP_BOTTOM, and GetLanguagePrefix() were
compile-time constants selected by the ENGLISH/GERMAN/... build
define. They're now runtime, defaulting to the same per-exe value the
define used to pick, and overridable at startup from [Ja2 Settings]
LANGUAGE in Ja2.ini.

-

XMLTacticalMessages is filled at runtime from NewTacticalMessages.xml,
never from compiled-in per-language data: all 8 per-language definitions
were the identical all-zero { L"" }. Delete them and define one shared
buffer in Utils/XML_Language.cpp (the loader) instead of pointer-rebinding
it like the static tables — this drops 9 dead 800KB zero-buffers (8
namespaced copies in LanguageStrings.cpp plus the standalone one) and
leaves no bind-ordering hazard for the XML load path.

-

ExportStrings.cpp privately recompiled one language's full text table
by #including the raw _<LANG>Text.cpp inside namespace Loc, keyed off
the exe-level ENGLISH/GERMAN/... compile macro (whichever the build
happened to select). That's redundant with the pointer globals every
other subsystem already uses (Text.h / LanguageStrings.cpp).

Drop the private copy; the unqualified table names in Loc::ExportStrings
now resolve to the global pointer externs, which BindLanguageStrings has
already rebound to the runtime g_lang by the time this runs (EXPORT_STRINGS
ini flag, checked after GetRuntimeSettings in sgp.cpp). gs_Lang (used only
by Loc::Translate for the Polish/Russian byte remap on raw .edt exports) is
now derived from g_lang via ToLocLanguage, so the export always matches
whatever language is actually active instead of a compile-time pick.

-

Editor/popupmenu.cpp and Strategic/Scheduling.cpp kept their own
call-site extern of gszScheduleActions as CHAR16[NUM_SCHEDULE_ACTIONS][20]
after the real definition changed into a rebindable pointer
(CHAR16 (*)[20], LanguageStrings.cpp). MSVC decays the outer array
dimension when mangling globals, so both declarations produce the same
symbol (?gszScheduleActions@@3PAY0BE@_WA) and the mismatch linked
silently -- but the array-typed TUs then indexed the 4-byte pointer
slot itself as string data, so the editor schedule popup and the map
schedule message text read garbage.

-

add text.def to be single-source of truth on symbol names and use it
 .much simpler, less error prone if adding more strings

-

add pseudo interface for language state. MAX_SAGES_ON_BOTTOM must always
 change in lockstep with g_lang. this isn't foolproof but better

---------

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-07-21 05:33:03 -03:00
steph-pgandAsdow ba64ed53c6 Items can now be taken from non colapsed POWs 2026-07-02 19:16:55 +03:00
Asdow c61f57bd9b Whitespace adjustment 2026-06-22 00:21:31 +03:00
Asdow 4d5593ebcb Fix render misalignment at 960x540 resolution
Should also work for any custom resolution that suffers from the same issue
2026-06-22 00:21:31 +03:00
Asdow 601fabf401 Fix tile rendering misalignment at 1440x900 and 1600x900 resolutions
Shamelessly stolen from Stracciatella code
2026-06-22 00:21:31 +03:00
steph-pgandAsdow eb8ecb31f0 fix POWs in LOS keeps you in turnbased mode 2026-06-20 20:03:16 +03:00
Asdow 2801af552e Set moved item old entry to not existing 2026-06-20 17:11:37 +03:00
Asdow ef30192b29 Leave merc equipment to current sector in UB 2026-06-20 08:49:56 +03:00
Asdow 392ba66df3 Enable dismissing mercs in UB before game start
Jerry Melo complains about too many mercs if player has hired over 6 mercs and refuses to fly. This allows player to dismiss mercs until the pilot is happy.
2026-06-20 08:49:56 +03:00
Asdow d4dd5f07df Disable contract renewal in UB 2026-06-20 08:49:56 +03:00
db29feed11 Don't strip robot's installed components on mapscreen drop-all
The mapscreen "drop everything" hotkey (Shift+W) looped over every
inventory slot from HELMETPOS to NUM_INV_SLOTS and dumped each one.
Madlab's robot stores its installed gear in those same slots - ammo (14),
targeting (15), chassis/armour (16) and utility (17). Dropping these left
the robot defenceless, and the player cannot re-install the components
through normal inventory, so the loss was permanent.

Skip the robot's component slots in the drop loop; only its general cargo
slot (ROBOT_INVENTORY_SLOT) may still be emptied. Regular mercs are
unaffected.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 23:06:33 +03:00
Asdow 1fc5f939bf Use new RandomGridNoUnloadedSector() for autoresolve
* Load required mapinfo for checking suitable gridnos
* Modify SOLDIERTYPE::DropSectorEquipment( ) to take an external gridno when placing items into an unloaded sector
2026-06-15 22:01:07 +03:00
Asdow 9e0105caec Add function for choosing random gridno in an unloaded sector 2026-06-15 22:01:07 +03:00
Asdow 0cc7fb4e39 Add function for loading world height data 2026-06-15 22:01:07 +03:00
Asdow db15679ec1 Add missing Iter->Exists() checks 2026-06-12 15:52:24 +03:00
Asdow 6a8a22aba2 Move iter->exists() check to first 2026-06-12 15:52:24 +03:00
Asdow 9b654ff3f1 Fix typo 2026-06-12 15:51:03 +03:00
Asdow a67f4b8a8f Formatting 2026-06-12 15:51:03 +03:00
Asdow 673dea01af Use correct sector for Chitzena 2026-06-12 15:51:03 +03:00
Marco Antonio J. Costaandmajcosta b44b6cebb8 Revert PR544
"Corpses are not properly shown even if within mercs' vision due to the check only
running intermittently since we're not constantly running the reveal roofs and items
function. In addition, mercs such as Raven who have the quoteexpheadshotonly set to true,
will NEVER reveal any corpses"

Revert "Remove extra line"

This reverts commit 1e1eb88864.

Revert "Move visibility check after already included pCorpse check"

This reverts commit 72e586a378.

Revert "Make 3rd party corpses invisible till they in fov"

This reverts commit 07dbf9c848.
2026-06-02 19:17:31 -03:00
Asdow 8bc3dbd0aa Prevent RebelCommand::DailyUpdate() from running twice when loading autosave 2026-06-02 23:59:30 +03:00
Marco Antonio J. Costaandmajcosta b4176bb2d9 Fix/drag unconscious softlock
Same logic but cleaned up version of https://github.com/1dot13/source/pull/602 which Tais contributed with Claude Code's help

Most calls to SOLDIERTYPE::IsDragging() aren't supposed to have a side effect of cancelling a drag, being simple queries.
Changing the default to reflect that is right move.

Assisted-by: Claude:
2026-06-01 21:45:55 -03:00
Asdow 8b3e417872 Guard against illegal array access
Hovering mouse over an option long enough that the popup text appears and scrolling to last page with mouse wheel would run into assertion error in gprintfdirty()  Assert(pFontString!=NULL); if the option was one of the last ones that didn't have a text box in the last option page.
2026-05-28 17:44:21 +03:00
AsdowandClaude Opus 4.7 b949189881 Fix palette resolution
The LayerProp palette lookup was gated on the *filter* attribute instead
of the *palette* attribute -- the filter checks from the line above were
copy-pasted verbatim. As a result, any LayerProp with an empty filter but
a real palette never resolved its palette table: paletteTable stayed NULL
and the layer fell back to the merc's shade table.

The most visible victim is the heli fast-rope arrival layer
(filter="" palette="hats"): rendered with the merc palette, the rope's
pixel indices (45-81, 166-172) hit the merc's unreplaced orange/red glow
ramp instead of the rope's own tan/green, painting orange specks along the
rope. Testing the palette attribute restores the intended "hats" palette
and the correct rope colours; it also fixes any other empty-filter layers.

Co-Authored-By: Claude Opus 4.7
2026-05-28 16:33:48 +03:00
JuqiandAsdow cc19490898 update Chinese translations 2026-05-25 08:37:30 +03:00
GreysaandAsdow 9fac0a8ecd Tidy up code 2026-05-23 21:22:28 +03:00
GreysaandAsdow 346e5c6293 fix for issue #580 2026-05-23 21:22:28 +03:00
GreysaandAsdow 0bea84374e fixed some screenmsg strings not changed 2026-05-23 21:22:28 +03:00
GreysaandAsdow 3d589bd78e A little code cleanup and moved debug reload to shift + alt + r 2026-05-23 21:22:28 +03:00
GreysaandAsdow e1c8bee1d1 Message strings added to language files. 2026-05-23 21:22:28 +03:00
GreysaandAsdow ead2932316 Added dual wielding unjamming, tidied up reload and unjam code a little 2026-05-23 21:22:28 +03:00
GreysaandAsdow bb55299ff6 Basic implementaion of unjamming on reload hotkey.
TODO:
text strings
check dual wielding
2026-05-23 21:22:28 +03:00
JuqiandAsdow 2d4f62a5b6 Correct two errors in the Chinese translation 2026-05-13 16:02:28 +03:00
Asdow 03a7b017b6 Change mouse cursor if attachment attempt fails 2026-05-06 23:23:07 +03:00
Asdow edf8f2f086 Fix crash when failing an attachment via item desc popup callback 2026-05-06 23:23:07 +03:00
Asdow 78ed49c73d Remove corpse structure from world when removing corpse
Fixes not being able to take all corpses from one tile, if multiple corpses were stacked on top of each other. With the structure not deleted, the  GetCorpseAtGridNo would not find an existing corpse anymore.
2026-05-06 11:28:11 +03:00
Asdow 5697acabb3 Take most recent corpse from tile
Fixes crash when taking a corpse on roof when multiple corpses are stacked on one tile.
2026-05-06 11:28:11 +03:00
Asdow 09197e421c Add a function for finding last matching structure 2026-05-06 11:28:11 +03:00
JuqiandAsdow 46afd90fdc Add Chinese translation 2026-05-04 12:10:10 +03:00
JuqiandAsdow 80ba0a05d7 Revert "Add Chinese translation"
This reverts commit e7d436d9fa.
2026-05-04 12:10:10 +03:00
JuqiandAsdow aab34bff20 Add Chinese translation 2026-05-04 12:10:10 +03:00
JuqiandAsdow ef325c1cde (Chinese) Add support for line breaks in ITEMS.XML
Added detection of the NEWLINE_CHAR character "±" to enable line breaks not only when text exceeds display width, but also upon encountering NEWLINE_CHAR.
Note: This character has been conventionally adopted in the Chinese community as a line break marker within XML item descriptions. The modification affects Chinese text display only.
2026-05-02 09:39:03 +03:00
Asdow 99a4ff1527 Guard against invalid value 2026-05-02 09:31:09 +03:00
Marco Antonio J. Costaandmajcosta f2d1b09fc6 add comment to CMakeLists.txt
save people a trip to learn.microsoft.com
2026-04-08 15:25:48 -03:00
Marco Antonio J. CostaandAsdow 31970291bd make preprocessor definitions consistent in every single lib
SGP did not seem to make any use of compilationFlags or debugFlags when
translating from msbuild to cmake, but there could be a thrice removed
transitive include I've missed.

there's a small probability this fixes some obscure UB or one-definition rule
violation that's causing the mystery crashes people have reported
2026-04-08 21:06:34 +03:00
GreysaandGitHub 0a758179c2 Advanced repairs skill check (#581)
* Add technician level skill check for advanced repair

* changed readability
2026-03-17 10:35:29 +01:00
noooooo4499andAsdow 481e95d42d Avoid breaking compatibility with gamedirs with old HourlyUpdate scripts
They are mapped to the right variable now
2026-02-25 10:47:47 +02:00
Asdow 8f178a59ab Add filename to error text for pngloader 2026-02-23 18:23:08 +02:00
Asdow a6d96f5f31 Remove compiler warnings 2026-02-21 21:00:38 +02:00
Asdow 67f41d4de6 Remove superfluous variable 2026-02-21 21:00:38 +02:00
Asdow 1d53b92731 Add const keyword 2026-02-21 21:00:38 +02:00
Asdow 42b2cbc7f5 add const keyword 2026-02-21 21:00:38 +02:00
Asdow 04ace5df24 add const keyword 2026-02-21 21:00:38 +02:00
Asdow 323944757d Improve DisplayCover() performance
* Replaced gCoverViewArea[2000][2000][2] with std::vector that is adjusted to the actual viewport size. Size difference for example in 720p resolution is 8 000 000 vs 3969 elements
* FPS improved in merc view mode 6-8 frames and in enemy view mode 10-20 frames
2026-02-21 21:00:38 +02:00
noooooo4499andAsdow 7d5a1c2e9e Remove useless lua gameclock variables, add lua externalized function to heal boxers
- These gameclock variables were not updated properly so it created a discrepancy between it and the proper gameclock variables, and it was causing issues with the lua hourly update script

- Function that heals boxers after they finish resting
2026-02-12 10:24:47 +02:00
noooooo4499andAsdow dc107f5044 Fix Darren sometimes never giving fights if the boxers's IDs aren't assigned 2026-02-07 12:03:03 +02:00
150 changed files with 4701 additions and 18894 deletions
+166 -27
View File
@@ -9,11 +9,6 @@ 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
@@ -27,7 +22,6 @@ 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
@@ -37,15 +31,6 @@ 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'
@@ -154,39 +139,193 @@ jobs:
cat dist/versions.env cat dist/versions.env
- name: Upload - name: Upload
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v7
with: with:
name: versions.env name: versions.env
path: dist/ path: dist/
build: # every executable contains all languages and picks one at runtime, so one build serves every package
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:
language: ${{ fromJson(needs.workflow_setup.outputs.languages_json_array) }} application: [ja2, ja2mapeditor, ja2ub, ja2ubmapeditor]
uses: ./.github/workflows/build_language.yml
with: steps:
language: ${{ matrix.language }} - name: Checkout source
assemble: ${{ needs.workflow_setup.outputs.assemble_release == 'true' }} uses: actions/checkout@v7
# at least English and some other lang have to work
continue-on-error: ${{ matrix.language != 'English' && matrix.language != 'German' }} - 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
# 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, build ] needs: [ workflow_setup, assemble ]
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@v4 uses: actions/download-artifact@v8
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@v4 uses: actions/checkout@v7
with: with:
path: source path: source
fetch-depth: 1 fetch-depth: 1
-206
View File
@@ -1,206 +0,0 @@
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
+29 -27
View File
@@ -27,7 +27,11 @@ if(ADDRESS_SANITIZER)
endif() endif()
if(MSVC) if(MSVC)
add_compile_options("/wd4838") # TODO: fix the warnings here and turn them on
# 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
@@ -102,15 +106,13 @@ foreach(lib IN LISTS Ja2_Libs)
add_subdirectory(${lib}) add_subdirectory(${lib})
endforeach() endforeach()
# language library relies on Application _and_ Language preprocessor definition. very bad. # language library relies on Application preprocessor definition; compiled once per app
# 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 Languages and Application choices # simple function to validate the Application choice
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")
@@ -136,32 +138,32 @@ foreach(app IN LISTS ApplicationTargets)
target_compile_definitions(${game_library} PRIVATE ${compilationFlags} ${debugFlags}) target_compile_definitions(${game_library} PRIVATE ${compilationFlags} ${debugFlags})
endforeach() endforeach()
foreach(lang IN LISTS LangTargets) # executable for the application, e.g. JA2.exe
# executable for an application/language combination, e.g. JA2_ENGLISH.exe set(exe ${app})
set(exe ${app}_${lang}) add_executable(${exe} WIN32
add_executable(${exe} WIN32 sgp/sgp.cpp
sgp/sgp.cpp Ja2/Res/ja2.rc
Ja2/Res/ja2.rc )
) target_link_libraries(${exe} PRIVATE ${Ja2_Libraries} $<IF:${usingMsBuild},legacy_stdio_definitions.lib,>)
target_link_libraries(${exe} PRIVATE ${Ja2_Libraries} $<IF:${usingMsBuild},legacy_stdio_definitions.lib,>) target_link_options(${exe} PRIVATE $<IF:${usingMsBuild},/SAFESEH:NO,>)
target_link_options(${exe} PRIVATE $<IF:${usingMsBuild},/SAFESEH:NO,>) target_compile_definitions(${exe} PRIVATE ${compilationFlags} ${debugFlags})
target_compile_definitions(${exe} PRIVATE ${compilationFlags} ${debugFlags})
# language library for an application, e.g. JA2MAPEDITOR_ENGLISH_i18n # language library for the application, e.g. JA2MAPEDITOR_i18n — one per app, all 8
set(language_library ${exe}_i18n) # languages compiled in, selected at runtime by BindLanguageStrings
add_library(${language_library}) set(language_library ${exe}_i18n)
target_sources(${language_library} PRIVATE ${i18nSrc}) add_library(${language_library})
target_compile_definitions(${language_library} PRIVATE ${compilationFlags} ${debugFlags} ${lang}) target_sources(${language_library} PRIVATE ${i18nSrc})
target_link_libraries(${exe} PRIVATE ${language_library}) target_compile_definitions(${language_library} PRIVATE ${compilationFlags} ${debugFlags})
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/language executable # go through all game libraries again and link them to the app 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 NO_ZLIB_COMPRESSION) target_compile_definitions(${app}_sgp PRIVATE ${compilationFlags} ${debugFlags} 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[6]; extern STR16 *wszSelType;
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[NUM_DIFF_LVLS]; extern STR16 *zDiffNames;
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[ NUM_SCHEDULE_ACTIONS ][20]; extern CHAR16 (*gszScheduleActions)[20]; // NUM_SCHEDULE_ACTIONS entries
//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.
+1 -3
View File
@@ -332,7 +332,6 @@ 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
@@ -617,7 +616,6 @@ 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;
@@ -848,7 +846,6 @@ 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;
@@ -2752,6 +2749,7 @@ 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);
+3 -1
View File
@@ -8,8 +8,10 @@
#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()
@@ -105,7 +107,6 @@ 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,
@@ -2146,6 +2147,7 @@ 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 ) if( bHighLight != -1 && toggle_box_array[bHighLight] != -1)
{ {
if( bHighLight < OPT_FIRST_COLUMN_TOGGLE_CUT_OFF ) if( bHighLight < OPT_FIRST_COLUMN_TOGGLE_CUT_OFF )
{ {
-50
View File
@@ -1468,31 +1468,6 @@ 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);
@@ -1514,31 +1489,6 @@ 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 );
STR16 pSkillTraitBeginIMPStrings[]; extern 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,10 +6,6 @@ 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
@@ -1,23 +0,0 @@
#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;}
};
}
}
@@ -1,23 +0,0 @@
#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
@@ -1,23 +0,0 @@
#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; }
};
}
}
@@ -1,23 +0,0 @@
#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
@@ -1,35 +0,0 @@
#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
@@ -1,51 +0,0 @@
#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,10 +7,6 @@
#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"
@@ -18,7 +14,8 @@
#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
@@ -46,11 +43,6 @@ 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
@@ -1,34 +0,0 @@
#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
@@ -1,104 +0,0 @@
#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
+10 -5
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 (IsItemRepairable(pRepairSoldier, iter->usItem, (*iter)[attachmentIndex]->data.objectStatus, (*iter)[attachmentIndex]->data.sRepairThreshold )) { if ( iter->exists() && 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 ( IsItemRepairable( pSoldier, iter->usItem, (*iter)[subObject]->data.objectStatus, (*iter)[subObject]->data.sRepairThreshold ) && iter->exists() ) { if ( iter->exists() && IsItemRepairable( pSoldier, iter->usItem, (*iter)[subObject]->data.objectStatus, (*iter)[subObject]->data.sRepairThreshold ) ) {
return( &(*iter) ); return( &(*iter) );
} }
} }
@@ -5171,8 +5171,12 @@ 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 || !HAS_SKILL_TRAIT( pSoldier, TECHNICIAN_NT ) ) 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)
{ {
threshold = 100;
}
else
{
threshold = (*pObj)[ubLoop]->data.sRepairThreshold; threshold = (*pObj)[ubLoop]->data.sRepairThreshold;
} }
} }
@@ -11085,7 +11089,8 @@ void DetermineWhichAssignmentMenusCanBeShown( void )
CreateDestroyMouseRegionForFacilityMenu(); CreateDestroyMouseRegionForFacilityMenu();
CreateDestroyMouseRegionsForFacilityAssignmentMenu(); CreateDestroyMouseRegionsForFacilityAssignmentMenu();
if( ( ( gCharactersList[ bSelectedInfoChar ].usSolID->stats.bLife == 0 )||( gCharactersList[bSelectedInfoChar].usSolID->bAssignment == ASSIGNMENT_POW ) ) && ( (guiTacticalInterfaceFlags & INTERFACE_MAPSCREEN ) ) ) const auto selectedCharacter = gCharactersList[bSelectedInfoChar].usSolID;
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 );
+13 -5
View File
@@ -2481,6 +2481,12 @@ 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.
@@ -2519,7 +2525,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, RandomGridNo(), ADD_DEAD_SOLDIER_TO_SWEETSPOT ); AddDeadSoldierToUnLoadedSector( gpAR->ubSectorX, gpAR->ubSectorY, 0, gpMercs[ i ].pSoldier, RandomGridNoUnloadedSector(worldRows, worldCols), 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 )
{ {
@@ -2624,7 +2630,8 @@ DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"Autoresolve2");
} }
// Flugente: drop sector equipment // Flugente: drop sector equipment
gpCivs[ i ].pSoldier->DropSectorEquipment(); const auto gridno = RandomGridNoUnloadedSector(worldRows, worldCols);
gpCivs[ i ].pSoldier->DropSectorEquipment(gridno);
if( fDeleteForGood && gpCivs[ i ].pSoldier->stats.bLife < OKLIFE/2 ) if( fDeleteForGood && gpCivs[ i ].pSoldier->stats.bLife < OKLIFE/2 )
{ {
@@ -2649,7 +2656,7 @@ DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"Autoresolve2");
UpdateMilitia( militia ); UpdateMilitia( militia );
} }
AddDeadSoldierToUnLoadedSector( gpAR->ubSectorX, gpAR->ubSectorY, 0, gpCivs[ i ].pSoldier, RandomGridNo(), ADD_DEAD_SOLDIER_TO_SWEETSPOT ); AddDeadSoldierToUnLoadedSector( gpAR->ubSectorX, gpAR->ubSectorY, 0, gpCivs[ i ].pSoldier, gridno, 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() )
@@ -2681,7 +2688,8 @@ 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 );
AddDeadSoldierToUnLoadedSector( gpAR->ubSectorX, gpAR->ubSectorY, 0, gpEnemies[ i ].pSoldier, RandomGridNo(), ADD_DEAD_SOLDIER_TO_SWEETSPOT ); const auto gridno = RandomGridNoUnloadedSector(worldRows, worldCols);
AddDeadSoldierToUnLoadedSector( gpAR->ubSectorX, gpAR->ubSectorY, 0, gpEnemies[ i ].pSoldier, gridno, ADD_DEAD_SOLDIER_TO_SWEETSPOT);
} }
} }
} }
@@ -6007,7 +6015,7 @@ void AutoResolveMilitiaDropAndPromote()
} }
// Flugente: drop sector equipment // Flugente: drop sector equipment
gpCivs[i].pSoldier->DropSectorEquipment( ); gpCivs[i].pSoldier->DropSectorEquipment( NOWHERE );
if ( gpCivs[i].pSoldier->stats.bLife < OKLIFE / 2 ) if ( gpCivs[i].pSoldier->stats.bLife < OKLIFE / 2 )
{ {
-4
View File
@@ -249,10 +249,6 @@ 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 );
+4 -1
View File
@@ -158,7 +158,6 @@ 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;
@@ -169,6 +168,10 @@ 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;
+23 -3
View File
@@ -859,6 +859,7 @@ 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);
@@ -912,9 +913,6 @@ 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;
@@ -1189,6 +1187,7 @@ 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
@@ -9315,6 +9314,27 @@ 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,9 +26,6 @@ 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;
+10 -9
View File
@@ -510,15 +510,6 @@ 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");
@@ -590,6 +581,16 @@ 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,8 +1133,12 @@ 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++;
} }
} }
@@ -6024,10 +6028,7 @@ 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
if (guntemperature != newguntemperature) ScreenMsg(FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, L"World: Item temperature lowered from %4.2f to %4.2f", 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
@@ -6047,10 +6048,7 @@ 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
if (temperature != newtemperature) ScreenMsg(FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, L"World: Item temperature lowered from %4.2f to %4.2f", 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,7 +904,16 @@ 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_TOWNS][MAX_TOWN_NAME_LENGHT]; extern CHAR16 (*pTownNames)[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_
STR16 QuestDescText[]; extern STR16 *QuestDescText;
STR16 FactDescText[]; extern STR16 *FactDescText;
#endif #endif
+1 -1
View File
@@ -31,7 +31,7 @@ class SOLDIERTYPE;
#ifdef JA2EDITOR #ifdef JA2EDITOR
extern CHAR16 gszScheduleActions[ NUM_SCHEDULE_ACTIONS ][20]; extern CHAR16 (*gszScheduleActions)[20]; // NUM_SCHEDULE_ACTIONS entries
#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 sqaud 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 squad 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_L11 ].ubNumTroops > 0); BOOLEAN fAttack_Chitzena = !(SectorInfo[ SEC_B2 ].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_TOWNS][MAX_TOWN_NAME_LENGHT]; extern CHAR16 (*pTownNames)[MAX_TOWN_NAME_LENGHT];
+16 -2
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,6 +8472,12 @@ 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]);
} }
@@ -12742,6 +12748,10 @@ 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)
{ {
@@ -12759,6 +12769,7 @@ 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 ) )
@@ -15366,7 +15377,10 @@ 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,4 +2,3 @@
# 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 (! (Item[ iter->usItem ].inseparable ) && iter->exists()) if (! (iter->exists() && Item[ iter->usItem ].inseparable))
{ {
// 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)[x]->data.objectStatus < 100 ) && CanDealerRepairItem( ubArmsDealer, iter->usItem ) && iter->exists()) if ( iter->exists() && ( (*iter)[x]->data.objectStatus < 100 ) && CanDealerRepairItem( ubArmsDealer, iter->usItem ) )
{ {
//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)[x]->data.objectStatus < 100 ) && CanDealerRepairItem( ubArmsDealer, iter->usItem ) && iter->exists()) if ( iter->exists() && ( (*iter)[x]->data.objectStatus < 100 ) && CanDealerRepairItem( ubArmsDealer, iter->usItem ) )
{ {
uiRepairCost += CalculateObjectItemRepairCost( ubArmsDealer, &(*iter)); uiRepairCost += CalculateObjectItemRepairCost( ubArmsDealer, &(*iter));
} }
+10 -8
View File
@@ -21,7 +21,6 @@
#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 };
@@ -55,7 +54,7 @@ void ExitBoxing( void )
if ( pSoldier != NULL ) if ( pSoldier != NULL )
{ {
if ( BOXER(pSoldier) && InARoom( pSoldier->sGridNo, &usRoom ) && usRoom == BOXING_RING ) if ( ( pSoldier->flags.uiStatusFlags & SOLDIER_BOXER ) && InARoom( pSoldier->sGridNo, &usRoom ) && usRoom == BOXING_RING )
{ {
if ( pSoldier->flags.uiStatusFlags & SOLDIER_PC ) if ( pSoldier->flags.uiStatusFlags & SOLDIER_PC )
{ {
@@ -239,7 +238,7 @@ static void CountPeopleInBoxingRingAndDoActions( void )
{ {
++ubPlayersInRing; ++ubPlayersInRing;
if ( !pNonBoxingPlayer && !BOXER(pSoldier) ) if ( !pNonBoxingPlayer && !(pSoldier->flags.uiStatusFlags & SOLDIER_BOXER) )
{ {
pNonBoxingPlayer = pSoldier; pNonBoxingPlayer = pSoldier;
} }
@@ -291,7 +290,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 (!BOXER(pInRing[uiLoop])) if (!(pInRing[uiLoop]->flags.uiStatusFlags & SOLDIER_BOXER))
{ {
// set as boxer! // set as boxer!
pInRing[uiLoop]->flags.uiStatusFlags |= SOLDIER_BOXER; pInRing[uiLoop]->flags.uiStatusFlags |= SOLDIER_BOXER;
@@ -454,11 +453,14 @@ 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)
{ {
@@ -512,7 +514,7 @@ void BoxingMovementCheck( SOLDIERTYPE * pSoldier )
// someone moving in/into the ring // someone moving in/into the ring
CountPeopleInBoxingRingAndDoActions(); CountPeopleInBoxingRingAndDoActions();
} }
else if ( ( gTacticalStatus.bBoxingState == BOXING ) && BOXER(pSoldier) ) else if ( ( gTacticalStatus.bBoxingState == BOXING ) && ( pSoldier->flags.uiStatusFlags & SOLDIER_BOXER ) )
{ {
// boxer stepped out of the ring! // boxer stepped out of the ring!
BoxingPlayerDisqualified( pSoldier, BOXER_OUT_OF_RING ); BoxingPlayerDisqualified( pSoldier, BOXER_OUT_OF_RING );
@@ -562,7 +564,7 @@ void ClearAllBoxerFlags( void )
{ {
for (UINT32 uiSlot = 0; uiSlot < guiNumMercSlots; ++uiSlot) for (UINT32 uiSlot = 0; uiSlot < guiNumMercSlots; ++uiSlot)
{ {
if ( MercSlots[ uiSlot ] && BOXER(MercSlots[ uiSlot ]) ) if ( MercSlots[ uiSlot ] && MercSlots[ uiSlot ]->flags.uiStatusFlags & SOLDIER_BOXER )
{ {
// 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]);
+412 -556
View File
File diff suppressed because it is too large Load Diff
-23
View File
@@ -758,29 +758,6 @@ 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,6 +108,5 @@ 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 ) if ( pOpponent->stats.bLife < OKLIFE || pOpponent->bCollapsed || (pOpponent->usSoldierFlagMask & SOLDIER_POW) )
{ {
// We can steal any of his items in the inventory // We can steal any of his items in the inventory
fStealItem = TRUE; fStealItem = TRUE;
+2 -51
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,54 +500,6 @@ 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)
{ {
@@ -721,7 +673,6 @@ 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,7 +184,6 @@ 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 (Item[ iter->usItem ].usItemClass == IC_ARMOUR) if ( iter->exists() && 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 (Item[ iter->usItem ].usItemClass == IC_ARMOUR) if ( iter->exists() && Item[ iter->usItem ].usItemClass == IC_ARMOUR)
{ {
iComparedFinalProtectionValue += Armour[Item[iter->usItem].ubClassIndex].ubProtection; iComparedFinalProtectionValue += Armour[Item[iter->usItem].ubClassIndex].ubProtection;
} }
+22 -4
View File
@@ -643,13 +643,29 @@ void popupCallbackItem(INT16 itemId){
i++;p++; i++;p++;
} }
OBJECTTYPE pObjTmp; static 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();
@@ -6060,7 +6076,8 @@ 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
if((gpItemPointer != NULL && Magazine[Item[gpItemPointer->usItem].ubClassIndex].ubMagType >= AMMO_BOX) || !EnoughPoints(gpItemDescSoldier, APBPConstants[AP_RELOAD_GUN], 0, TRUE))//dnl ch65 040913 //Greysa: add check for ammo item
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 );
@@ -6083,7 +6100,8 @@ void ItemDescAmmoCallback(GUI_BUTTON *btn,INT32 reason)
else else
{ {
//holding an item //holding an item
if(Magazine[Item[gpItemPointer->usItem].ubClassIndex].ubCalibre == Weapon[Item[gpItemDescObject->usItem].ubClassIndex].ubCalibre) //Greysa: add check for ammo item
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 (ISVIP(pSoldier) && !pSoldier->bSectorZ ) if ( pSoldier->usSoldierFlagMask & SOLDIER_VIP && !pSoldier->bSectorZ )
{ {
if ( PlayerKnowsAboutVIP( pSoldier->sSectorX, pSoldier->sSectorY ) ) if ( PlayerKnowsAboutVIP( pSoldier->sSectorX, pSoldier->sSectorY ) )
{ {
+137 -104
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->usItem == usItem && iter->exists()) if ( iter->exists() && iter->usItem == usItem )
{ {
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 (Item[iter->usItem].usItemClass == uiItemClass && iter->exists()) if ( iter->exists() && Item[iter->usItem].usItemClass == uiItemClass )
{ {
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 (Item[iter->usItem].attachmentclass == uiAttachmentClass && iter->exists()) if ( iter->exists() && Item[iter->usItem].attachmentclass == uiAttachmentClass )
{ {
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 (ValidLaunchable( iter->usItem, usWeapon) && Explosive[Item[iter->usItem].ubClassIndex].ubType != EXPLOSV_SMOKE && iter->exists()) if ( iter->exists() && ValidLaunchable( iter->usItem, usWeapon) && Explosive[Item[iter->usItem].ubClassIndex].ubType != EXPLOSV_SMOKE )
{ {
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->usItem != 0 && !ItemIsHiddenAttachment(att->usItem) ) if ( att->exists() && 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->usItem != 0 && !ItemIsHiddenAttachment(att->usItem) ) if ( att->exists() && att->usItem != 0 && !ItemIsHiddenAttachment(att->usItem) )
{ {
attachmentHidden = FALSE; attachmentHidden = FALSE;
break; break;
@@ -3872,13 +3872,111 @@ 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) )
@@ -3915,10 +4013,8 @@ 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 ( pSoldier->IsValidSecondHandShot( ) ) if (pObj2 != NULL)
{ {
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;
@@ -3930,10 +4026,8 @@ BOOLEAN AutoReload( SOLDIERTYPE * pSoldier, bool aReloadEvenIfNotEmpty )
} }
else else
{ {
if ( pSoldier->IsValidSecondHandShot( ) ) if (pObj2 != NULL)
{ {
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;
@@ -3989,21 +4083,19 @@ 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 ( pSoldier->IsValidSecondHandShotForReloadingPurposes() if ( pObj2 != NULL && ( aReloadEvenIfNotEmpty || !EnoughAmmo( pSoldier, FALSE, SECONDHANDPOS ) ) )
&& ( 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, pObj, &( pSoldier->inv[bSlot] ) ); bAPCost = GetAPsToReloadGunWithAmmo( pSoldier, pObj2, &( 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, pObj, &( pSoldier->inv[bSlot] ) ); fRet = ReloadGun( pSoldier, pObj2, &( pSoldier->inv[bSlot] ) );
} }
else else
{ {
@@ -5566,7 +5658,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( ValidItemAttachmentSlot(this, iter->usItem, TRUE, FALSE, subObject )){ if( iter->exists() && 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);
@@ -5903,7 +5995,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(Item[iter->usItem].inseparable && ValidItemAttachmentSlot(pObj, iter->usItem, TRUE, FALSE, 0, -1, 0, NULL, usAttachmentSlotIndexVector)){ if( iter->exists() && 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.
@@ -5917,7 +6009,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(ValidItemAttachmentSlot(pObj, iter->usItem, TRUE, FALSE, 0, -1, 0, NULL, usAttachmentSlotIndexVector)){ if( iter->exists() && 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.
@@ -6093,7 +6185,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( ValidItemAttachmentSlot(pObj, iter->usItem, TRUE, FALSE, ubStatusIndex )){ if( iter->exists() && 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);
@@ -6110,7 +6202,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( ValidItemAttachmentSlot(pObj, iter->usItem, TRUE, FALSE, ubStatusIndex)){ if( iter->exists() && 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);
@@ -6127,7 +6219,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 ( Item[iter->usItem].inseparable != 1) if ( iter->exists() && 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...
@@ -6145,7 +6237,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 ( Item[iter->usItem].inseparable != 1) if ( iter->exists() && 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...
@@ -10330,7 +10422,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 ( Item[iter->usItem].scopemagfactor > 1.0 && iter->exists() ) if ( iter->exists() && Item[iter->usItem].scopemagfactor > 1.0 )
return TRUE; return TRUE;
} }
} }
@@ -10349,7 +10441,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 ( Item[iter->usItem].aimbonus > 0 && iter->exists()) if ( iter->exists() && Item[iter->usItem].aimbonus > 0 )
return TRUE; return TRUE;
} }
} }
@@ -10436,7 +10528,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(Item[iter->usItem].aimbonus > bonus && iRange >= Item[iter->usItem].minrangeforaimbonus && iter->exists()) if( iter->exists() && Item[iter->usItem].aimbonus > bonus && iRange >= Item[iter->usItem].minrangeforaimbonus )
{ {
bonus = Item[iter->usItem].aimbonus; bonus = Item[iter->usItem].aimbonus;
} }
@@ -10514,7 +10606,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 (Item[iter->usItem].rangebonus > 0 && iter->exists()) if ( iter->exists() && Item[iter->usItem].rangebonus > 0 )
{ {
return( Item[iter->usItem].uiIndex ); return( Item[iter->usItem].uiIndex );
} }
@@ -10535,7 +10627,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 ( !ItemIsDuckbill(iter->usItem) || (ItemIsDuckbill(iter->usItem) && (*pObj)[0]->data.gun.ubGunAmmoType == AMMO_BUCKSHOT ) && iter->exists()) if ( iter->exists() && !ItemIsDuckbill(iter->usItem) || (ItemIsDuckbill(iter->usItem) && (*pObj)[0]->data.gun.ubGunAmmoType == AMMO_BUCKSHOT ) )
bonus += BonusReduce( Item[iter->usItem].rangebonus, (*iter)[0]->data.objectStatus ); bonus += BonusReduce( Item[iter->usItem].rangebonus, (*iter)[0]->data.objectStatus );
} }
} }
@@ -11961,7 +12053,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 (ItemIsThermalOptics(iter->usItem) && iter->exists() ) if ( iter->exists() && ItemIsThermalOptics(iter->usItem) )
return TRUE; return TRUE;
} }
} }
@@ -12018,7 +12110,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 (ItemIsDuckbill(iter->usItem) && iter->exists() ) if ( iter->exists() && ItemIsDuckbill(iter->usItem) )
{ {
return( TRUE ); return( TRUE );
} }
@@ -12069,7 +12161,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 ( IsAttachmentClass( iter->usItem, AC_DETONATOR ) && iter->exists() ) if ( iter->exists() && IsAttachmentClass( iter->usItem, AC_DETONATOR ) )
{ {
return( TRUE ); return( TRUE );
} }
@@ -12085,7 +12177,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 ( IsAttachmentClass( iter->usItem, AC_REMOTEDET ) && iter->exists() ) if ( iter->exists() && IsAttachmentClass( iter->usItem, AC_REMOTEDET ) )
{ {
return( TRUE ); return( TRUE );
} }
@@ -12357,7 +12449,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 (ItemIsBatteries(iter->usItem) && iter->exists()) if ( iter->exists() && ItemIsBatteries(iter->usItem) )
{ {
return( &(*iter) ); return( &(*iter) );
} }
@@ -12426,51 +12518,6 @@ 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 )
{ {
@@ -12634,7 +12681,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 (!ItemIsCamoKit(iter->usItem) && iter->exists()) if ( iter->exists() && !ItemIsCamoKit(iter->usItem) )
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));
} }
} }
@@ -12647,7 +12694,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 (!ItemIsCamoKit(iter->usItem) && iter->exists()) if ( iter->exists() && !ItemIsCamoKit(iter->usItem) )
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));
} }
} }
@@ -12660,7 +12707,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 (!ItemIsCamoKit(iter->usItem) && iter->exists()) if ( iter->exists() && !ItemIsCamoKit(iter->usItem) )
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));
} }
} }
@@ -12673,7 +12720,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 (!ItemIsCamoKit(iter->usItem) && iter->exists()) if ( iter->exists() && !ItemIsCamoKit(iter->usItem) )
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));
} }
} }
@@ -13118,7 +13165,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 ( Item[ iter->usItem ].cavevisionrangebonus > bonusToBeat && Item[ iter->usItem ].usItemClass == IC_FACE && iter->exists() ) { if ( iter->exists() && Item[ iter->usItem ].cavevisionrangebonus > bonusToBeat && Item[ iter->usItem ].usItemClass == IC_FACE ) {
pGoggles = &(*iter); pGoggles = &(*iter);
*bSlot = bLoop; *bSlot = bLoop;
*isAttach = TRUE; *isAttach = TRUE;
@@ -13176,7 +13223,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 || !IsAttachmentClass(iter->usItem, AC_SCOPE|AC_SIGHT|AC_IRONSIGHT ) ) if ( !gGameExternalOptions.fScopeModes || (iter->exists() && !IsAttachmentClass(iter->usItem, AC_SCOPE|AC_SIGHT|AC_IRONSIGHT )) )
bonus += Item[iter->usItem].minrangeforaimbonus; bonus += Item[iter->usItem].minrangeforaimbonus;
} }
} }
@@ -14094,7 +14141,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 (Item[iter->usItem].bestlaserrange > 0 && iter->exists()) if ( iter->exists() && Item[iter->usItem].bestlaserrange > 0 )
{ {
numModifiers++; numModifiers++;
bonus += (FLOAT) Item[iter->usItem].bestlaserrange; bonus += (FLOAT) Item[iter->usItem].bestlaserrange;
@@ -14120,7 +14167,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 (Item[iter->usItem].bestlaserrange > range && iter->exists()) if ( iter->exists() && Item[iter->usItem].bestlaserrange > range )
{ {
range = Item[iter->usItem].bestlaserrange; range = Item[iter->usItem].bestlaserrange;
} }
@@ -15133,7 +15180,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( ValidItemAttachmentSlot(&gTempObject, iter->usItem, TRUE, FALSE, ubStatusIndex )) if( iter->exists() && 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...
@@ -15821,7 +15868,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 ( !ItemIsDuckbill(iter->usItem) || (ItemIsDuckbill(iter->usItem) && (*pObj)[0]->data.gun.ubGunAmmoType == AMMO_BUCKSHOT )) if ( iter->exists() && !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;
} }
} }
@@ -16090,20 +16137,6 @@ 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,7 +249,6 @@ 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.
@@ -480,13 +479,9 @@ 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 );
+1 -2
View File
@@ -167,7 +167,6 @@ 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 *
**********************************/ **********************************/
@@ -281,4 +280,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( (n=Item[ iter->usItem ].spreadPattern) && iter->exists()){ if( iter->exists() && (n=Item[ iter->usItem ].spreadPattern) ){
//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(aFilter, "") != 0 && strcmp(aFilter, "default") != 0) { if (aPalette != NULL && strcmp(aPalette, "") != 0 && strcmp(aPalette, "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,6 +20,7 @@ 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,6 +57,7 @@ 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);
+16 -44
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) &&
BOXER(pSoldier) && (pSoldier->flags.uiStatusFlags & SOLDIER_BOXER) &&
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) ||
!BOXER(pOpponent) || !(pOpponent->flags.uiStatusFlags & SOLDIER_BOXER) ||
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
const auto destinationOffscreen = !(GridNoOnVisibleWorldTile(sGridNo)); auto destinationOffscreen = !(GridNoOnVisibleWorldTile(sGridNo));
const auto hasProfile = pCurrSoldier->ubProfile != NO_PROFILE; auto hasProfile = pCurrSoldier->ubProfile != NO_PROFILE;
const auto isCivilian = pCurrSoldier->bTeam == CIV_TEAM; auto isCivilian = pCurrSoldier->bTeam == CIV_TEAM;
const auto isEnemy = pCurrSoldier->bTeam == ENEMY_TEAM; auto isEnemy = pCurrSoldier->bTeam == ENEMY_TEAM;
const auto retreatAllowed = gGameExternalOptions.fAITacticalRetreat == TRUE; auto retreatAllowed = gGameExternalOptions.fAITacticalRetreat == true;
if (destinationOffscreen && !(isCivilian || hasProfile || (isEnemy && retreatAllowed))) if (destinationOffscreen && !(isCivilian || hasProfile || (isEnemy && retreatAllowed)))
{ {
@@ -5400,34 +5400,6 @@ 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
@@ -6937,7 +6909,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 ) if ( pTeamSoldier && pTeamSoldier->stats.bLife >= OKLIFE && !pTeamSoldier->aiData.bNeutral && !(pTeamSoldier->usSoldierFlagMask & SOLDIER_POW) )
{ {
if ( SoldierHasSeenEnemiesLastFewTurns( pTeamSoldier ) ) if ( SoldierHasSeenEnemiesLastFewTurns( pTeamSoldier ) )
{ {
@@ -7203,7 +7175,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 (ISVIP(pTeamSoldier)) if ( pTeamSoldier->usSoldierFlagMask & SOLDIER_VIP )
++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 )
@@ -7232,7 +7204,7 @@ static void RemoveCapturedEnemiesFromSectorInfo( INT16 sMapX, INT16 sMapY, INT8
} }
// Flugente: VIPs // Flugente: VIPs
if (ISVIP(pTeamSoldier)) if ( pTeamSoldier->usSoldierFlagMask & SOLDIER_VIP )
DeleteVIP( pTeamSoldier->sSectorX, pTeamSoldier->sSectorY ); DeleteVIP( pTeamSoldier->sSectorX, pTeamSoldier->sSectorY );
// Flugente: turncoats // Flugente: turncoats
@@ -9279,10 +9251,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)) || !BOXER(pSoldier) || pSoldier->IsRiotShieldEquipped() ) if ( ( pSoldier->usAttackingWeapon != NOTHING && !ItemIsBrassKnuckles(pSoldier->usAttackingWeapon)) || !( pSoldier->flags.uiStatusFlags & SOLDIER_BOXER ) || pSoldier->IsRiotShieldEquipped() )
{ {
// someone's cheating! // someone's cheating!
if ( (Item[ pSoldier->usAttackingWeapon ].usItemClass == IC_BLADE || Item[ pSoldier->usAttackingWeapon ].usItemClass == IC_PUNCH) && BOXER(pTarget) ) if ( (Item[ pSoldier->usAttackingWeapon ].usItemClass == IC_BLADE || Item[ pSoldier->usAttackingWeapon ].usItemClass == IC_PUNCH) && (pTarget->flags.uiStatusFlags & SOLDIER_BOXER) )
{ {
// knife or brass knuckles disqualify the player! // knife or brass knuckles disqualify the player!
BoxingPlayerDisqualified( pSoldier, BAD_ATTACK ); BoxingPlayerDisqualified( pSoldier, BAD_ATTACK );
@@ -9293,7 +9265,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) && BOXER(pTarget) ) if ( (pTarget->bTeam == CIV_TEAM) && (pTarget->flags.uiStatusFlags & SOLDIER_BOXER) )
{ {
SetSoldierNeutral( pTarget ); SetSoldierNeutral( pTarget );
} }
@@ -9440,7 +9412,7 @@ BOOLEAN ProcessImplicationsOfPCAttack( SOLDIERTYPE * pSoldier, SOLDIERTYPE ** pp
//TriggerNPCWithIHateYouQuote( pTarget->ubProfile ); //TriggerNPCWithIHateYouQuote( pTarget->ubProfile );
} }
} }
else if ( pTarget->ubCivilianGroup != NON_CIV_GROUP && !BOXER(pTarget) ) else if ( pTarget->ubCivilianGroup != NON_CIV_GROUP && !( pTarget->flags.uiStatusFlags & SOLDIER_BOXER ) )
{ {
// 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.
@@ -10876,7 +10848,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 (ISVIP(pSoldier)) if ( pSoldier->usSoldierFlagMask & SOLDIER_VIP )
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
@@ -11431,7 +11403,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(); pSoldier->DropSectorEquipment( NOWHERE );
} }
} }
} }
+1 -3
View File
@@ -261,9 +261,6 @@ 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 );
@@ -436,3 +433,4 @@ BOOLEAN IsFreeSlotAvailable( int aTeam );
void AttemptToCapturePlayerSoldiers(); void AttemptToCapturePlayerSoldiers();
#endif #endif
+100 -36
View File
@@ -36,9 +36,6 @@
#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;
@@ -59,8 +56,19 @@ 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;
@@ -605,12 +613,15 @@ int AStarPathfinder::GetPath(SOLDIERTYPE *s ,
} }
if (gRenderDebugInfoMode == DEBUG_PATHFINDING && DEBUG_CHEAT_LEVEL())
#if defined( PATHAI_VISIBLE_DEBUG )
if (gfDisplayCoverValues && gfDrawPathPoints)
{ {
ResetDebugInfoValues(); memset( gsCoverValue, 0x7F, sizeof( INT16 ) * WORLD_MAX );
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;
@@ -775,10 +786,12 @@ int AStarPathfinder::GetPath(SOLDIERTYPE *s ,
return 0; return 0;
} }
if (gRenderDebugInfoMode == DEBUG_PATHFINDING && DEBUG_CHEAT_LEVEL()) #if defined( PATHAI_VISIBLE_DEBUG )
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)
@@ -861,10 +874,12 @@ int AStarPathfinder::GetPath(SOLDIERTYPE *s ,
sizePath = giPathDataSize; sizePath = giPathDataSize;
} }
if (gRenderDebugInfoMode == DEBUG_PATHFINDING && DEBUG_CHEAT_LEVEL()) #if defined( PATHAI_VISIBLE_DEBUG )
if (gfDisplayCoverValues && gfDrawPathPoints)
{ {
SetRenderFlags( RENDER_FLAG_FULL ); SetRenderFlags( RENDER_FLAG_FULL );
} }
#endif
#ifdef COUNT_PATHS #ifdef COUNT_PATHS
guiSuccessfulPathChecks++; guiSuccessfulPathChecks++;
@@ -976,13 +991,15 @@ void AStarPathfinder::ExecuteAStarLogic()
SetAStarStatus(ParentNode, AStar_Closed); SetAStarStatus(ParentNode, AStar_Closed);
//ClosedList.push_back(ParentNode); //ClosedList.push_back(ParentNode);
if (gRenderDebugInfoMode == DEBUG_PATHFINDING && DEBUG_CHEAT_LEVEL()) #if defined( PATHAI_VISIBLE_DEBUG )
if (gfDisplayCoverValues && gfDrawPathPoints)
{ {
if (gRenderDebugInfoValues[ParentNode] > 0) if (gsCoverValue[ ParentNode ] > 0)
{ {
gRenderDebugInfoValues[ParentNode] *= -1; gsCoverValue[ 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);
@@ -1065,7 +1082,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
gRenderDebugInfoValues[CurrentNode] = 1; gsCoverValue[CurrentNode] = 1;
#endif #endif
} }
@@ -1163,13 +1180,26 @@ void AStarPathfinder::ExecuteAStarLogic()
int AStarH = CalcH(); int AStarH = CalcH();
int AStarF = (AStarG + extraGCoverCost) + AStarH; int AStarF = (AStarG + extraGCoverCost) + AStarH;
if (gRenderDebugInfoMode == DEBUG_PATHFINDING && DEBUG_CHEAT_LEVEL()) #if defined( PATHAI_VISIBLE_DEBUG )
if (gfDisplayCoverValues && gfDrawPathPoints)
{ {
if (gRenderDebugInfoValues[CurrentNode] == 0x7FFFFFFF) if (gsCoverValue[CurrentNode] == 0x7F7F)
{ {
gRenderDebugInfoValues[CurrentNode] = (INT16) AStarF; //gsCoverValue[CurrentNode] = PATHAI_VISIBLE_DEBUG_Counter++;
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)
@@ -1350,7 +1380,7 @@ INT16 AStarPathfinder::CalcAP(int const terrainCost, UINT8 const direction)
} }
// Flugente: dragging someone // Flugente: dragging someone
if ( pSoldier->IsDragging( false ) ) if ( pSoldier->IsDragging() )
{ {
movementAPCost *= gItemSettings.fDragAPCostModifier; movementAPCost *= gItemSettings.fDragAPCostModifier;
} }
@@ -2220,7 +2250,9 @@ 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;
@@ -2470,10 +2502,12 @@ 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 (gRenderDebugInfoMode == DEBUG_PATHFINDING && DEBUG_CHEAT_LEVEL()) #if defined( PATHAI_VISIBLE_DEBUG )
if (gfDisplayCoverValues && gfDrawPathPoints)
{ {
ResetDebugInfoValues(); memset( gsCoverValue, 0x7F, sizeof( INT16 ) * WORLD_MAX );
} }
#endif
bSkipListLevel = 1; bSkipListLevel = 1;
iSkipListSize = 0; iSkipListSize = 0;
@@ -2585,13 +2619,15 @@ 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 (gRenderDebugInfoMode == DEBUG_PATHFINDING && DEBUG_CHEAT_LEVEL()) #if defined( PATHAI_VISIBLE_DEBUG )
if (gfDisplayCoverValues && gfDrawPathPoints)
{ {
if (gRenderDebugInfoValues[ curLoc ] > 0) if (gsCoverValue[ curLoc ] > 0)
{ {
gRenderDebugInfoValues[ curLoc ] *= -1; gsCoverValue[ curLoc ] *= -1;
} }
} }
#endif
/* /*
if (fTurnSlow) if (fTurnSlow)
@@ -3517,13 +3553,27 @@ 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)
{ {
gRenderDebugInfoValues[newLoc] = usCounter++; gsCoverValue[newLoc] = usCounter++;
} }
} /*
else if (gsCoverValue[newLoc] >= 0)
{
gsCoverValue[newLoc]++;
}
else
{
gsCoverValue[newLoc]--;
}
*/
}
#endif
//NEWQUENODE; //NEWQUENODE;
{ {
@@ -3766,13 +3816,20 @@ ENDOFLOOP:
while (pathQNotEmpty && pathNotYetFound); while (pathQNotEmpty && pathNotYetFound);
if (gRenderDebugInfoMode == DEBUG_PATHFINDING && DEBUG_CHEAT_LEVEL()) #if defined( PATHAI_VISIBLE_DEBUG )
{ if (gfDisplayCoverValues && gfDrawPathPoints)
if ( guiCurrentScreen == GAME_SCREEN )
{ {
InvalidateRegion(gsVIEWPORT_START_X, gsVIEWPORT_START_Y, gsVIEWPORT_END_X, gsVIEWPORT_WINDOW_END_Y); SetRenderFlags( RENDER_FLAG_FULL );
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?
@@ -3827,10 +3884,17 @@ ENDOFLOOP:
} }
if (gRenderDebugInfoMode == DEBUG_PATHFINDING && DEBUG_CHEAT_LEVEL()) #if defined( PATHAI_VISIBLE_DEBUG )
{ 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
+9 -7
View File
@@ -713,6 +713,10 @@ 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 )
@@ -1062,7 +1066,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(); pSoldier->DropSectorEquipment( NOWHERE );
} }
// Make team look for items // Make team look for items
@@ -1108,7 +1112,8 @@ 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 ( iCorpseID != -1 && pSoldier->bTeam == OUR_TEAM || gbPublicOpplist[OUR_TEAM][pSoldier->ubID] == SEEN_CURRENTLY ) //if ( pSoldier->bTeam == gbPlayerNum )
if ( iCorpseID != -1 )
{ {
MakeCorpseVisible( pSoldier, &( gRottingCorpse[ iCorpseID ] ) ); MakeCorpseVisible( pSoldier, &( gRottingCorpse[ iCorpseID ] ) );
} }
@@ -1770,10 +1775,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 = FindStructure( sGridNo, STRUCTURE_CORPSE ); pStructure = FindLastStructure( sGridNo, STRUCTURE_CORPSE );
if ( pStructure != NULL ) if ( pStructure != NULL )
{ {
@@ -2535,9 +2540,6 @@ 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 ( CanDealerRepairItem( gbSelectedArmsDealerID, iter->usItem ) && iter->exists()) if ( iter->exists() && CanDealerRepairItem( gbSelectedArmsDealerID, iter->usItem ) )
{ {
// 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 (ISVIP(pSoldier)) if ( pSoldier->usSoldierFlagMask & SOLDIER_VIP )
{ {
DeleteVIP( pSoldier->sSectorX, pSoldier->sSectorY ); DeleteVIP( pSoldier->sSectorX, pSoldier->sSectorY );
} }
+8 -97
View File
@@ -9046,7 +9046,7 @@ void CalculateSoldierAniSpeed( SOLDIERTYPE *pSoldier, SOLDIERTYPE *pStatsSoldier
} }
// Flugente: drag people // Flugente: drag people
if ( pSoldier->IsDragging( false ) ) if ( pSoldier->IsDragging() )
{ {
pSoldier->sAniDelay = gItemSettings.fDragAPCostModifier * pSoldier->sAniDelay; pSoldier->sAniDelay = gItemSettings.fDragAPCostModifier * pSoldier->sAniDelay;
} }
@@ -9700,85 +9700,6 @@ 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;
@@ -9932,7 +9853,7 @@ void SOLDIERTYPE::BeginSoldierGetup( void )
else else
{ {
this->bTurnsCollapsed++; this->bTurnsCollapsed++;
if ( (gTacticalStatus.bBoxingState == BOXING) && (BOXER(this)) ) if ( (gTacticalStatus.bBoxingState == BOXING) && (this->flags.uiStatusFlags & SOLDIER_BOXER) )
{ {
if ( this->bTurnsCollapsed > 1 ) if ( this->bTurnsCollapsed > 1 )
{ {
@@ -11595,7 +11516,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(); BOOLEAN currentlydragging = this->IsDragging(true);
INT32 sOldGridNo = this->sGridNo; INT32 sOldGridNo = this->sGridNo;
// OK, set new position // OK, set new position
@@ -14747,7 +14668,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 ( Item[iter->usItem].usFlashLightRange ) if ( iter->exists() && Item[iter->usItem].usFlashLightRange )
flashlightfound = TRUE; flashlightfound = TRUE;
} }
} }
@@ -16897,7 +16818,7 @@ BOOLEAN SOLDIERTYPE::UpdateMultiTurnAction( )
return TRUE; return TRUE;
} }
void SOLDIERTYPE::DropSectorEquipment( ) void SOLDIERTYPE::DropSectorEquipment( INT32 UnloadedSectorGridNo)
{ {
// 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 )
@@ -16966,7 +16887,7 @@ void SOLDIERTYPE::DropSectorEquipment( )
} }
} }
AddItemsToUnLoadedSector( this->sSectorX, this->sSectorY, this->bSectorZ, RandomGridNo( ), counter, pObject, 0, WORLD_ITEM_REACHABLE, 0, 1, FALSE ); AddItemsToUnLoadedSector( this->sSectorX, this->sSectorY, this->bSectorZ, UnloadedSectorGridNo, counter, pObject, 0, WORLD_ITEM_REACHABLE, 0, 1, FALSE );
} }
} }
@@ -19125,7 +19046,7 @@ BOOLEAN SOLDIERTYPE::IsJamming( )
{ {
if ( CanUseRadio( FALSE ) ) if ( CanUseRadio( FALSE ) )
return TRUE; return TRUE;
// if we cannot use the radio, remove that flag while we're at it // if we cannot use the radio, remove that flag hile we're at it
else else
usSoldierFlagMask &= ~SOLDIER_RADIO_OPERATOR_JAMMING; usSoldierFlagMask &= ~SOLDIER_RADIO_OPERATOR_JAMMING;
} }
@@ -24305,7 +24226,7 @@ BOOLEAN SOLDIERTYPE::CanBreakWindow(void)
BOOLEAN SOLDIERTYPE::CanStartDrag(void) BOOLEAN SOLDIERTYPE::CanStartDrag(void)
{ {
if (!this->IsDragging(false) && this->CanDragInPrinciple()) if (!this->IsDragging() && this->CanDragInPrinciple())
{ {
INT32 sNewGridNo = NewGridNo(this->sGridNo, DirectionInc(this->ubDirection)); INT32 sNewGridNo = NewGridNo(this->sGridNo, DirectionInc(this->ubDirection));
@@ -26376,13 +26297,3 @@ 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;
}
+2 -5
View File
@@ -1788,13 +1788,10 @@ 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 );
@@ -1919,7 +1916,7 @@ public:
void CancelMultiTurnAction(BOOLEAN fFinished); void CancelMultiTurnAction(BOOLEAN fFinished);
BOOLEAN UpdateMultiTurnAction(); BOOLEAN UpdateMultiTurnAction();
void DropSectorEquipment(); void DropSectorEquipment( INT32 UnloadedSectorGridNo);
// 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);
@@ -2059,7 +2056,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 = true); BOOLEAN IsDragging(bool aStopIfConditionNotSatisfied = false);
void SetDragOrderPerson( SoldierID usID ); void SetDragOrderPerson( SoldierID usID );
void SetDragOrderCorpse( UINT32 uiCorpseID ); void SetDragOrderCorpse( UINT32 uiCorpseID );
void SetDragOrderStructure( INT32 sGridNo ); void SetDragOrderStructure( INT32 sGridNo );
+1 -3
View File
@@ -37,10 +37,8 @@
#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 ( Item[iter->usItem].visionrangebonus > 0 && iter->exists()) if ( iter->exists() && Item[iter->usItem].visionrangebonus > 0 )
{ {
fMercIsUsingScope = TRUE; fMercIsUsingScope = TRUE;
break; break;
+3 -2
View File
@@ -1218,9 +1218,10 @@ void EndInterrupt( BOOLEAN fMarkInterruptOccurred )
else else
{ {
ubInterruptedSoldier = LATEST_INTERRUPT_GUY; ubInterruptedSoldier = LATEST_INTERRUPT_GUY;
pSoldier = ubInterruptedSoldier;
DebugMsg( TOPIC_JA2INTERRUPT, DBG_LEVEL_3, String("INTERRUPT: interrupt over, soldier %d's team %d regains control", ubInterruptedSoldier, pSoldier->bTeam ) ); DebugMsg( TOPIC_JA2INTERRUPT, DBG_LEVEL_3, String("INTERRUPT: interrupt over, %d's team regains control", ubInterruptedSoldier ) );
pSoldier = ubInterruptedSoldier;
for ( SoldierID id = 0; id < MAX_NUM_SOLDIERS; ++id) for ( SoldierID id = 0; id < MAX_NUM_SOLDIERS; ++id)
{ {
+17 -38
View File
@@ -1684,24 +1684,6 @@ 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;
@@ -2239,7 +2221,7 @@ void GetKeyboardInput( UINT32 *puiNewEvent )
// sevenfm: also stop dragging // sevenfm: also stop dragging
if (gusSelectedSoldier != NOBODY && if (gusSelectedSoldier != NOBODY &&
gusSelectedSoldier && gusSelectedSoldier &&
gusSelectedSoldier->IsDragging(false)) gusSelectedSoldier->IsDragging())
{ {
gusSelectedSoldier->CancelDrag(); gusSelectedSoldier->CancelDrag();
DirtyMercPanelInterface(gusSelectedSoldier, DIRTYLEVEL2); DirtyMercPanelInterface(gusSelectedSoldier, DIRTYLEVEL2);
@@ -4321,21 +4303,25 @@ void GetKeyboardInput( UINT32 *puiNewEvent )
// Make auto reload with magazines from sector inventory // Make auto reload with magazines from sector inventory
case 'R': case 'R':
HandleTBReloadAll(); if (fAlt && gusSelectedSoldier != NOBODY ) // Greysa: Moved here from 'r' to retain all functionality during testing.
{
if (CHEATER_CHEAT_LEVEL())
{
ReloadWeapon(gusSelectedSoldier, gusSelectedSoldier->ubAttackingHand);
}
}
else
{
HandleTBReloadAll();
}
break; break;
case 'r': case 'r':
if( gusSelectedSoldier != NOBODY ) if( gusSelectedSoldier != NOBODY )
{ {
if( fAlt ) //reload selected merc's weapon if( fAlt )
{ {
if ( CHEATER_CHEAT_LEVEL( ) ) HandleTBReload();
{ }
ReloadWeapon( gusSelectedSoldier, gusSelectedSoldier->ubAttackingHand );
}
else
HandleTBReload();
}
else if( fCtrl ) else if( fCtrl )
{ {
if ( INFORMATION_CHEAT_LEVEL( ) ) if ( INFORMATION_CHEAT_LEVEL( ) )
@@ -4770,13 +4756,6 @@ void GetKeyboardInput( UINT32 *puiNewEvent )
break; break;
case 'Z': case 'Z':
if (fCtrl)
{
if (DEBUG_CHEAT_LEVEL())
{
CycleThroughTileDebugInfo();
}
}
break; break;
} }
@@ -6791,7 +6770,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 ( Item[ iter->usItem ].nightvisionrangebonus > bestBonus && Item[ iter->usItem ].usItemClass == IC_FACE && iter->exists()) if ( iter->exists() && Item[ iter->usItem ].nightvisionrangebonus > bestBonus && Item[ iter->usItem ].usItemClass == IC_FACE )
{ {
pGoggles = &(*iter); pGoggles = &(*iter);
bestBonus = Item[ iter->usItem ].nightvisionrangebonus; bestBonus = Item[ iter->usItem ].nightvisionrangebonus;
@@ -6839,7 +6818,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 ( Item[ iter->usItem ].brightlightvisionrangebonus > bestBonus && Item[ iter->usItem ].usItemClass == IC_FACE && iter->exists()) if ( iter->exists() && Item[ iter->usItem ].brightlightvisionrangebonus > bestBonus && Item[ iter->usItem ].usItemClass == IC_FACE )
{ {
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 ( Item[ iter->usItem ].usItemClass & IC_EXPLOSV && iter->exists()) if ( iter->exists() && Item[ iter->usItem ].usItemClass & IC_EXPLOSV )
{ {
pObject = &(*iter); pObject = &(*iter);
break; break;
+23 -25
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 (Item[iter->usItem].usItemClass == IC_ARMOUR && (*iter)[0]->data.objectStatus > 0 && iter->exists()) if ( iter->exists() && Item[iter->usItem].usItemClass == IC_ARMOUR && (*iter)[0]->data.objectStatus > 0 )
{ {
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 (Item[iter->usItem].usItemClass == IC_ARMOUR && (*iter)[0]->data.objectStatus > 0 ) if ( iter->exists() && 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 (Item[iter->usItem].usItemClass == IC_ARMOUR && (*iter)[0]->data.objectStatus > 0 ) if ( iter->exists() && 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 (Item[iter->usItem].usItemClass == IC_ARMOUR && (*iter)[0]->data.objectStatus > 0 && iter->exists()) if ( iter->exists() && Item[iter->usItem].usItemClass == IC_ARMOUR && (*iter)[0]->data.objectStatus > 0 )
{ {
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 ( Item[iter->usItem].usItemClass == IC_ARMOUR && ( *iter )[0]->data.objectStatus > 0 && iter->exists() ) if ( iter->exists() && Item[iter->usItem].usItemClass == IC_ARMOUR && ( *iter )[0]->data.objectStatus > 0 )
{ {
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 (ItemHasHiddenMuzzleFlash(iter->usItem)) if ( iter->exists() && 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 (ItemHasHiddenMuzzleFlash(iter->usItem)) if ( iter->exists() && 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 ) else if (pTargetSoldier->stats.bLife < OKLIFE || pTargetSoldier->bCollapsed || pTargetSoldier->usSoldierFlagMask & SOLDIER_POW)
{ {
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 &&
BOXER(pSoldier) && (pSoldier->flags.uiStatusFlags & SOLDIER_BOXER) &&
!pSoldier->bBlindedCounter && !pSoldier->bBlindedCounter &&
gAnimControl[pTargetSoldier->usAnimState].ubEndHeight > ANIM_PRONE && gAnimControl[pTargetSoldier->usAnimState].ubEndHeight > ANIM_PRONE &&
BOXER(pTargetSoldier) && (pTargetSoldier->flags.uiStatusFlags & SOLDIER_BOXER) &&
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 &&
BOXER(pTargetSoldier) && (pTargetSoldier->flags.uiStatusFlags & SOLDIER_BOXER) &&
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 ( Item[ iter->usItem ].usItemClass & IC_EXPLOSV && iter->exists()) if ( iter->exists() && Item[ iter->usItem ].usItemClass & IC_EXPLOSV )
{ {
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, BOOLEAN fSound) void WindowHit( INT32 sGridNo, UINT16 usStructureID, BOOLEAN fBlowWindowSouth, BOOLEAN fLargeForce )
{ {
STRUCTURE * pWallAndWindow; STRUCTURE * pWallAndWindow;
DB_STRUCTURE * pWallAndWindowInDB; DB_STRUCTURE * pWallAndWindowInDB;
@@ -6210,12 +6210,10 @@ 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}
if ( fSound ) PlayJA2Sample( GLASS_SHATTER1 + Random(2), RATE_11025, MIDVOLUME, 1, SoundDir( sGridNo ) );
{
PlayJA2Sample(GLASS_SHATTER1 + Random(2), RATE_11025, MIDVOLUME, 1, SoundDir(sGridNo));
}
} }
@@ -7995,7 +7993,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 (Item[iter->usItem].usItemClass == IC_ARMOUR && (*iter)[0]->data.objectStatus > 0 ) if ( iter->exists() && 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 );
@@ -8027,7 +8025,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 (Item[iter->usItem].usItemClass == IC_ARMOUR && (*iter)[0]->data.objectStatus > 0 && iter->exists()) if ( iter->exists() && Item[iter->usItem].usItemClass == IC_ARMOUR && (*iter)[0]->data.objectStatus > 0 )
{ {
// 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 );
@@ -8073,7 +8071,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 ( !AutoPlaceObject( pTarget, &(*iter), FALSE ) && iter->exists()) if ( iter->exists() && !AutoPlaceObject( pTarget, &(*iter), FALSE ) )
{ // 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 );
} }
@@ -9725,7 +9723,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 (BOXER(pAttacker)) if (pAttacker->flags.uiStatusFlags & SOLDIER_BOXER)
iChance = 50 + (iAttRating - iDefRating) / 3; iChance = 50 + (iAttRating - iDefRating) / 3;
else else
iChance = 67 + (iAttRating - iDefRating) / 3; iChance = 67 + (iAttRating - iDefRating) / 3;
@@ -9780,8 +9778,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 &&
BOXER(pAttacker) && (pAttacker->flags.uiStatusFlags & SOLDIER_BOXER) &&
BOXER(pDefender) && (pDefender->flags.uiStatusFlags & SOLDIER_BOXER) &&
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 );
void WindowHit(INT32 sGridNo, UINT16 usStructureID, BOOLEAN fBlowWindowSouth, BOOLEAN fLargeForce, BOOLEAN fSound = TRUE); extern void WindowHit( INT32 sGridNo, UINT16 usStructureID, BOOLEAN fBlowWindowSouth, BOOLEAN fLargeForce );
// 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()
{ {
+4 -5
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,6 +190,7 @@ 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);
@@ -222,6 +223,7 @@ 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 );
@@ -239,12 +241,9 @@ 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);
ActionType HeadForTheStairCase( SOLDIERTYPE * pSoldier ); INT8 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 );
+1 -2
View File
@@ -18,7 +18,6 @@
#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
@@ -181,7 +180,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) && !BOXER(pSoldier) ) if ( (gTacticalStatus.bBoxingState == BOXING) && !(pSoldier->flags.uiStatusFlags & SOLDIER_BOXER) )
{ {
return( FALSE ); return( FALSE );
} }
+112 -333
View File
@@ -49,7 +49,6 @@
#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:
@@ -207,96 +206,18 @@ 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, bool doLog, INT8 bAction) void DebugAI( INT8 bMsgType, SOLDIERTYPE *pSoldier, STR szOutput, INT8 bAction )
{ {
FILE* DebugFile; FILE* DebugFile;
CHAR8 msg[1024]; CHAR8 msg[1024];
CHAR8 buf[1024]; CHAR8 buf[1024];
if (!gfTurnBasedAI || !gfLogsEnabled || !doLog || pSoldier == nullptr) if (!gfLogsEnabled || pSoldier == nullptr)
return; return;
memset(buf, 0, 1024 * sizeof(char)); memset(buf, 0, 1024 * sizeof(char));
@@ -337,7 +258,7 @@ void DebugAI( INT8 bMsgType, SOLDIERTYPE *pSoldier, STR szOutput, bool doLog, IN
strcat(msg, buf); strcat(msg, buf);
} }
if (bAction >= AI_ACTION_NONE && bAction < AI_ACTION_INVALID) if (bAction >= AI_ACTION_NONE && bAction <= AI_ACTION_LAST)
{ {
strcat(msg, " "); strcat(msg, " ");
strcat(msg, szAction[bAction]); strcat(msg, szAction[bAction]);
@@ -380,19 +301,16 @@ void DebugAI( INT8 bMsgType, SOLDIERTYPE *pSoldier, STR szOutput, bool doLog, IN
} }
// also log to individual file for selected soldier // also log to individual file for selected soldier
if (pSoldier) sprintf(buf, "Logs\\AI_Decisions [%d].txt", pSoldier->ubID.i);
if ((DebugFile = fopen(buf, "a+t")) != NULL)
{ {
sprintf(buf, "Logs\\AI_Decisions [%d].txt", pSoldier->ubID.i); if (bMsgType == AI_MSG_START)
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);
} }
} }
@@ -436,27 +354,6 @@ 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 )
{ {
@@ -464,6 +361,13 @@ 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 ) )
{ {
@@ -495,6 +399,10 @@ 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
{ {
@@ -737,9 +645,6 @@ 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.
@@ -753,7 +658,6 @@ void HandleSoldierAI( SOLDIERTYPE *pSoldier ) // FIXME - this function is named
} }
DecideAlertStatus( pSoldier ); DecideAlertStatus( pSoldier );
} }
#endif
else else
{ {
if ( pSoldier->ubQuoteRecord ) if ( pSoldier->ubQuoteRecord )
@@ -761,15 +665,6 @@ 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;
} }
} }
@@ -779,6 +674,7 @@ 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
@@ -786,79 +682,42 @@ 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)
{ {
#if 0 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 )
{ {
// added by Flugente: static pointers, used to break out of an endless circles // ATE: Display message that deadlock occured...
static SOLDIERTYPE* pLastDecisionSoldier = NULL; LiveMessage( "Breaking Deadlock" );
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,
@@ -868,7 +727,7 @@ void HandleSoldierAI( SOLDIERTYPE *pSoldier ) // FIXME - this function is named
return; return;
} }
if (!pSoldier->aiData.bActionInProgress) if (pSoldier->aiData.bAction == AI_ACTION_NONE)
{ {
// being handled so turn off muzzle flash // being handled so turn off muzzle flash
if ( pSoldier->flags.fMuzzleFlash ) if ( pSoldier->flags.fMuzzleFlash )
@@ -1004,12 +863,6 @@ 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
@@ -1474,7 +1327,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)
{ {
@@ -1694,6 +1547,33 @@ 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;
@@ -1726,8 +1606,6 @@ 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;
@@ -1853,41 +1731,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
fprintf(NetDebugFile,"\tMOVED BECOMING TRUE: Chose to do nothing, guynum %d\n",pSoldier->ubID);
#endif
DebugMsg (TOPIC_JA2AI,DBG_LEVEL_3,"NPC has no action assigned");
NPCDoesNothing(pSoldier); // sets pSoldier->moved to TRUE
return;
}
// to get here, we MUST have an action selected, but not in progress...
// see if we can afford to do this action
if (IsActionAffordable(pSoldier))
{
NPCDoesAct(pSoldier);
// perform the chosen action
pSoldier->aiData.bActionInProgress = ExecuteAction(pSoldier); // if started, mark us as busy
if ( !pSoldier->aiData.bActionInProgress && !TileIsOutOfBounds(pSoldier->sAbsoluteFinalDestination))
{ {
// turn based... abort this guy's turn #ifdef RECORDNET
EndAIGuysTurn( pSoldier ); fprintf(NetDebugFile,"\tMOVED BECOMING TRUE: Chose to do nothing, guynum %d\n",pSoldier->ubID);
}
}
else
{
#ifdef DEBUGDECISIONS
AINumMessage("HandleManAI - Not enough APs, skipping guy#",pSoldier->ubID);
#endif #endif
HaltMoveForSoldierOutOfPoints( pSoldier);
return; DebugMsg (TOPIC_JA2AI,DBG_LEVEL_3,"NPC has no action assigned");
NPCDoesNothing(pSoldier); // sets pSoldier->moved to TRUE
return;
}
// to get here, we MUST have an action selected, but not in progress...
// see if we can afford to do this action
if (IsActionAffordable(pSoldier))
{
NPCDoesAct(pSoldier);
// perform the chosen action
pSoldier->aiData.bActionInProgress = ExecuteAction(pSoldier); // if started, mark us as busy
if ( !pSoldier->aiData.bActionInProgress && !TileIsOutOfBounds(pSoldier->sAbsoluteFinalDestination))
{
// turn based... abort this guy's turn
EndAIGuysTurn( pSoldier );
lastdecisioncount = 0;
}
}
else
{
#ifdef DEBUGDECISIONS
AINumMessage("HandleManAI - Not enough APs, skipping guy#",pSoldier->ubID);
#endif
HaltMoveForSoldierOutOfPoints( pSoldier);
return;
}
} }
} }
@@ -1920,7 +1799,6 @@ 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
@@ -2403,24 +2281,14 @@ 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(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 ) ); 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 ) );
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)
@@ -2744,30 +2612,7 @@ INT8 ExecuteAction(SOLDIERTYPE *pSoldier)
case AI_ACTION_JUMP_WINDOW: case AI_ACTION_JUMP_WINDOW:
{ {
pSoldier->BeginSoldierJumpWindowAI(); pSoldier->BeginSoldierClimbWindow();
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;
@@ -2830,73 +2675,7 @@ INT8 ExecuteAction(SOLDIERTYPE *pSoldier)
} }
break; break;
case AI_ACTION_DRINK_CANTEEN: default:
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
+24 -353
View File
@@ -27,7 +27,6 @@
#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()
@@ -135,17 +134,18 @@ INT8 OKToAttack(SOLDIERTYPE * pSoldier, int target)
BOOLEAN ConsiderProne( SOLDIERTYPE * pSoldier ) BOOLEAN ConsiderProne( SOLDIERTYPE * pSoldier )
{ {
INT32 sOpponentGridNo; INT32 sOpponentGridNo;
INT8 bOpponentLevel; INT8 bOpponentLevel;
INT32 iRangeInCellCoords; INT32 iRange;
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, nullptr, &iRangeInCellCoords); ClosestKnownOpponent( pSoldier, &sOpponentGridNo, &bOpponentLevel );
if ( iRangeInCellCoords > 10*CELL_X_SIZE) iRange = PythSpacesAway( pSoldier->sGridNo, sOpponentGridNo );
if (iRange > 10)
{ {
return( TRUE ); return( TRUE );
} }
@@ -397,11 +397,7 @@ UINT16 DetermineMovementMode( SOLDIERTYPE * pSoldier, INT8 bAction )
// sevenfm: movement mode tweaks // sevenfm: movement mode tweaks
if (gGameExternalOptions.fAIMovementMode) if (gGameExternalOptions.fAIMovementMode)
{ {
INT32 distanceToThreat; INT32 sClosestThreat = ClosestKnownOpponent(pSoldier, NULL, NULL);
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 &&
@@ -422,12 +418,11 @@ 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) &&
!BOXER(pSoldier) && !(pSoldier->flags.uiStatusFlags & SOLDIER_BOXER) &&
!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
@@ -450,7 +445,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 &&
distanceToThreat < beyondVisionRange && PythSpacesAway(pSoldier->sGridNo, sClosestThreat) < 3 * sDistanceVisible / 2 &&
CountFriendsBlack(pSoldier) == 0 && CountFriendsBlack(pSoldier) == 0 &&
bAction == AI_ACTION_SEEK_OPPONENT) bAction == AI_ACTION_SEEK_OPPONENT)
{ {
@@ -459,7 +454,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 &&
distanceToThreat > reallyClose && PythSpacesAway(pSoldier->sGridNo, sClosestThreat) > (INT16)TACTICAL_RANGE / 8 &&
(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) &&
@@ -471,9 +466,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 && distanceToThreat > mediumRange || (pSoldier->aiData.bShock > iRCD && PythSpacesAway(pSoldier->sGridNo, sClosestThreat) > (INT16)TACTICAL_RANGE / 2 ||
pSoldier->aiData.bShock > 0 && gAnimControl[pSoldier->usAnimState].ubEndHeight == ANIM_PRONE && distanceToThreat > close) && pSoldier->aiData.bShock > 0 && gAnimControl[pSoldier->usAnimState].ubEndHeight == ANIM_PRONE && PythSpacesAway(pSoldier->sGridNo, sClosestThreat) > (INT16)TACTICAL_RANGE / 4) &&
distanceToThreat < beyondVisionRange && PythSpacesAway(pSoldier->sGridNo, sClosestThreat) < 3 * sDistanceVisible / 2 &&
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 ||
@@ -490,7 +485,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) &&
distanceToThreat > close && PythSpacesAway(pSoldier->sGridNo, sClosestThreat) > (INT16)TACTICAL_RANGE / 4 &&
(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 ||
@@ -506,7 +501,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) &&
distanceToThreat > close && PythSpacesAway(pSoldier->sGridNo, sClosestThreat) > (INT16)TACTICAL_RANGE / 4 &&
(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 ||
@@ -520,7 +515,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) || distanceToThreat > sDistanceVisible * 20) && (!InARoom(pSoldier->sGridNo, NULL) || PythSpacesAway(pSoldier->sGridNo, sClosestThreat) > sDistanceVisible * 2) &&
pSoldier->aiData.bAIMorale >= MORALE_NORMAL && pSoldier->aiData.bAIMorale >= MORALE_NORMAL &&
pSoldier->bBreath > 25 && pSoldier->bBreath > 25 &&
pSoldier->pathing.bLevel == 0 && pSoldier->pathing.bLevel == 0 &&
@@ -544,14 +539,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 && distanceToThreat > reallyClose ) pSoldier->aiData.bUnderFire && PythSpacesAway(pSoldier->sGridNo, sClosestThreat) > (INT16)TACTICAL_RANGE / 8)
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 && distanceToThreat > reallyClose ) if (pSoldier->aiData.bUnderFire && !pSoldier->aiData.bLastAttackHit && PythSpacesAway(pSoldier->sGridNo, sClosestThreat) > (INT16)TACTICAL_RANGE / 8)
return SWATTING; return SWATTING;
else else
return RUNNING; return RUNNING;
@@ -813,18 +808,6 @@ 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);
@@ -1396,7 +1379,7 @@ INT32 ClosestReachableDisturbance(SOLDIERTYPE *pSoldier, BOOLEAN * pfChangeLevel
} }
INT32 ClosestKnownOpponent(SOLDIERTYPE *pSoldier, INT32 * psGridNo, INT8 * pbLevel, SoldierID * pubOpponentID, INT32 * distanceInCellCoords) INT32 ClosestKnownOpponent(SOLDIERTYPE *pSoldier, INT32 * psGridNo, INT8 * pbLevel, SoldierID * pubOpponentID)
{ {
INT32 *psLastLoc,sGridNo, sClosestOpponent = NOWHERE; INT32 *psLastLoc,sGridNo, sClosestOpponent = NOWHERE;
UINT32 uiLoop; UINT32 uiLoop;
@@ -1492,7 +1475,7 @@ INT32 ClosestKnownOpponent(SOLDIERTYPE *pSoldier, INT32 * psGridNo, INT8 * pbLev
if (sClosestOpponent == NOWHERE || if (sClosestOpponent == NOWHERE ||
iRange < iClosestRange || iRange < iClosestRange ||
pClosestOpponent && !pClosestOpponent->IsZombie() && !BOXER(pSoldier) && pClosestOpponent->stats.bLife < OKLIFE && pOpponent->stats.bLife >= OKLIFE) pClosestOpponent && !pClosestOpponent->IsZombie() && !(pSoldier->flags.uiStatusFlags & SOLDIER_BOXER) && pClosestOpponent->stats.bLife < OKLIFE && pOpponent->stats.bLife >= OKLIFE)
{ {
iClosestRange = iRange; iClosestRange = iRange;
sClosestOpponent = sGridNo; sClosestOpponent = sGridNo;
@@ -1520,10 +1503,6 @@ INT32 ClosestKnownOpponent(SOLDIERTYPE *pSoldier, INT32 * psGridNo, INT8 * pbLev
{ {
*pubOpponentID = pClosestOpponent->ubID; *pubOpponentID = pClosestOpponent->ubID;
} }
if ( distanceInCellCoords )
{
*distanceInCellCoords = iClosestRange;
}
return( sClosestOpponent ); return( sClosestOpponent );
} }
@@ -2253,31 +2232,6 @@ 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 ))
@@ -2296,12 +2250,6 @@ 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;
@@ -2349,12 +2297,6 @@ 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;
@@ -2713,7 +2655,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) && !BOXER(pEnemy) ) if ( (gTacticalStatus.bBoxingState == BOXING) && !(pEnemy->flags.uiStatusFlags & SOLDIER_BOXER) )
{ {
iThreatValue = -999; iThreatValue = -999;
return( iThreatValue ); return( iThreatValue );
@@ -3954,6 +3896,8 @@ 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 )
{ {
@@ -4632,61 +4576,6 @@ 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)
{ {
@@ -4895,7 +4784,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 && BOXER(pSoldier) && !BOXER(pOpponent) || gTacticalStatus.bBoxingState == BOXING && (pSoldier->flags.uiStatusFlags & SOLDIER_BOXER) && !(pOpponent->flags.uiStatusFlags & SOLDIER_BOXER) ||
pOpponent->ubBodyType == CROW) pOpponent->ubBodyType == CROW)
{ {
return FALSE; return FALSE;
@@ -5004,7 +4893,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 ||
BOXER(pSoldier) || pSoldier->flags.uiStatusFlags & SOLDIER_BOXER ||
ARMED_VEHICLE(pSoldier) || ARMED_VEHICLE(pSoldier) ||
pSoldier->flags.uiStatusFlags & SOLDIER_VEHICLE || pSoldier->flags.uiStatusFlags & SOLDIER_VEHICLE ||
AM_A_ROBOT(pSoldier) || AM_A_ROBOT(pSoldier) ||
@@ -6340,221 +6229,3 @@ 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;
}
+1 -685
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 (Item[iter->usItem].usItemClass == IC_ARMOUR && (*iter)[0]->data.objectStatus > 0 && iter->exists() ) if ( iter->exists() && Item[iter->usItem].usItemClass == IC_ARMOUR && (*iter)[0]->data.objectStatus > 0 )
{ {
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,687 +4234,3 @@ 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,6 +14,4 @@ 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)
+5 -7
View File
@@ -844,8 +844,7 @@ 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)
INT32 distanceToThreat; sClosestOpponent = ClosestKnownOpponent(pSoldier, NULL, NULL);
sClosestOpponent = ClosestKnownOpponent(pSoldier, NULL, NULL, NULL, &distanceToThreat);
if (!TileIsOutOfBounds(sClosestOpponent)) if (!TileIsOutOfBounds(sClosestOpponent))
{ {
@@ -855,9 +854,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 ) * CELL_X_SIZE; sDistVisible = pSoldier->GetMaxDistanceVisible(sClosestOpponent, 0 );
if ((pSoldier->ubDirection != ubOpponentDir) && (distanceToThreat <= sDistVisible)) if ((pSoldier->ubDirection != ubOpponentDir) && (PythSpacesAway(pSoldier->sGridNo,sClosestOpponent) <= 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))
@@ -1381,12 +1380,11 @@ 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)
INT32 distanceToThreat; sClosestOpponent = ClosestKnownOpponent(pSoldier, NULL, NULL);
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 && distanceToThreat > 20 ) // 2 tiles -> 20 in fractional Cell Coordinates if ( ubCanMove && PythSpacesAway( pSoldier->sGridNo, sClosestOpponent ) > 2 )
{ {
if ( bSpitIn != NO_SLOT ) if ( bSpitIn != NO_SLOT )
{ {
+288 -12611
View File
File diff suppressed because it is too large Load Diff
+287 -138
View File
@@ -15,6 +15,10 @@
#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"
@@ -22,8 +26,7 @@
#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>
//////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -31,7 +34,17 @@
// 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];
@@ -600,115 +613,42 @@ UINT8 NumberOfTeamMatesAdjacent( SOLDIERTYPE * pSoldier, INT32 sGridNo )
return( ubCount ); return( ubCount );
} }
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) INT32 FindBestNearbyCover(SOLDIERTYPE *pSoldier, INT32 morale, INT32 *piPercentBetter)
{ {
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;
const UINT8 ubDiff = SoldierDifficultyLevel( pSoldier ); // sevenfm
UINT8 ubNearbyFriends;
if (targetGridNo == NOWHERE) BOOLEAN fProneCover;
{ 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)
@@ -736,6 +676,25 @@ 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 ) ];
@@ -752,9 +711,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 / 4)) if (iSearchRange > (pSoldier->stats.bWisdom / 8))
{ {
iSearchRange = (pSoldier->stats.bWisdom / 4); iSearchRange = (pSoldier->stats.bWisdom / 8);
} }
if (!gfTurnBasedAI) if (!gfTurnBasedAI)
@@ -765,7 +724,6 @@ 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
@@ -786,12 +744,6 @@ 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)
{ {
@@ -813,30 +765,95 @@ 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!
CalculateCoverValue(pSoldier, targetGridNo, 0, iMyThreatValue, uiThreatCnt, ubDiff, fNight, ubBackgroundLightPercent, morale, iCurrentCoverValue, iCurrentScale); iCurrentCoverValue = 0;
// the initial cover value to beat is our current cover value iCurrentScale = 0;
iBestCoverValue = iCurrentCoverValue;
if (gRenderDebugInfoMode == DEBUG_COVERVALUE && DEBUG_CHEAT_LEVEL()) // sevenfm: sight cover
fProneCover = TRUE;
// for every opponent that threatens, consider this spot's cover vs. him
for (uiLoop = 0; uiLoop < uiThreatCnt; uiLoop++)
{ {
gRenderDebugInfoValues[targetGridNo] = (INT32)(iCurrentCoverValue / 100); // if this threat is CURRENTLY within 20 tiles
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,(targetGridNo % MAXCOL)); sMaxLeft = min(iSearchRange,(pSoldier->sGridNo % MAXCOL));
sMaxRight = min(iSearchRange,MAXCOL - ((targetGridNo % MAXCOL) + 1)); //NumMessage("sMaxLeft = ",sMaxLeft);
sMaxRight = min(iSearchRange,MAXCOL - ((pSoldier->sGridNo % MAXCOL) + 1));
//NumMessage("sMaxRight = ",sMaxRight);
// determine maximum vertical limits // determine maximum vertical limits
sMaxUp = min(iSearchRange,(targetGridNo / MAXROW)); sMaxUp = min(iSearchRange,(pSoldier->sGridNo / MAXROW));
sMaxDown = min(iSearchRange,MAXROW - ((targetGridNo / MAXROW) + 1)); //NumMessage("sMaxUp = ",sMaxUp);
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,targetGridNo); iDistFromOrigin = SpacesAway(sOrigin,pSoldier->sGridNo);
} }
else else
{ {
@@ -845,6 +862,19 @@ 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!
@@ -870,7 +900,7 @@ INT32 FindBestNearbyCover(SOLDIERTYPE *pSoldier, INT32 morale, INT32 *piPercentB
{ {
for (sXOffset = -sMaxLeft; sXOffset <= sMaxRight; sXOffset++) for (sXOffset = -sMaxLeft; sXOffset <= sMaxRight; sXOffset++)
{ {
sGridNo = targetGridNo + sXOffset + (MAXCOL * sYOffset); sGridNo = pSoldier->sGridNo + sXOffset + (MAXCOL * sYOffset);
if ( !(sGridNo >=0 && sGridNo < WORLD_MAX) ) if ( !(sGridNo >=0 && sGridNo < WORLD_MAX) )
{ {
continue; continue;
@@ -878,20 +908,22 @@ 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[targetGridNo].uiFlags &= ~(MAPELEMENT_REACHABLE); gpWorldLevelData[pSoldier->sGridNo].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 = targetGridNo + sXOffset + (MAXCOL * sYOffset); sGridNo = pSoldier->sGridNo + sXOffset + (MAXCOL * sYOffset);
if ( !(sGridNo >=0 && sGridNo < WORLD_MAX) ) if ( !(sGridNo >=0 && sGridNo < WORLD_MAX) )
{ {
continue; continue;
@@ -948,7 +980,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(targetGridNo, pSoldier->pathing.bLevel) && !InLightAtNight(pSoldier->sGridNo, pSoldier->pathing.bLevel) &&
!pSoldier->aiData.bUnderFire) !pSoldier->aiData.bUnderFire)
{ {
continue; continue;
@@ -957,19 +989,12 @@ 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(targetGridNo, pSoldier->pathing.bLevel)) !RedSmokeDanger(pSoldier->sGridNo, 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))
{ {
@@ -1002,15 +1027,110 @@ 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
CalculateCoverValue(pSoldier, sGridNo, iPathCost, iMyThreatValue, uiThreatCnt, ubDiff, fNight, ubBackgroundLightPercent, morale, iCoverValue, iCoverScale); iCoverValue = 0;
iCoverScale = 0;
// sevenfm: check sight cover
fProneCover = TRUE;
if (gRenderDebugInfoMode == DEBUG_COVERVALUE && DEBUG_CHEAT_LEVEL()) // for every opponent that threatens, consider this spot's cover vs. him
for (uiLoop = 0; uiLoop < uiThreatCnt; uiLoop++)
{ {
gRenderDebugInfoValues[sGridNo] = (INT32) (iCoverValue / 100); // calculate the range we would be at from this opponent
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,
@@ -1028,6 +1148,11 @@ 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;
@@ -1039,15 +1164,34 @@ INT32 FindBestNearbyCover(SOLDIERTYPE *pSoldier, INT32 morale, INT32 *piPercentB
gubNPCAPBudget = 0; gubNPCAPBudget = 0;
gubNPCDistLimit = 0; gubNPCDistLimit = 0;
if (gRenderDebugInfoMode == DEBUG_COVERVALUE && DEBUG_CHEAT_LEVEL()) #if defined( _DEBUG ) && !defined( PATHAI_VISIBLE_DEBUG )
if (gfDisplayCoverValues)
{ {
// do a locate?
LocateSoldier( pSoldier->ubID, SETLOCATORFAST );
gsBestCover = sBestCover; gsBestCover = sBestCover;
InvalidateRegion(gsVIEWPORT_START_X, gsVIEWPORT_START_Y, gsVIEWPORT_END_X, gsVIEWPORT_WINDOW_END_Y); SetRenderFlags( RENDER_FLAG_FULL );
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!)
@@ -1056,6 +1200,16 @@ 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
} }
} }
@@ -2974,11 +3128,7 @@ 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
{ {
@@ -3129,8 +3279,7 @@ INT32 FindAdvanceSpot(SOLDIERTYPE *pSoldier, INT32 sTargetSpot, INT8 bAction, UI
} }
// avoid overcrowding // avoid overcrowding
const auto nearbyFriends = CountNearbyFriends(pSoldier, sGridNo, TACTICAL_RANGE_VERYCLOSE); if (!pSoldier->IsZombie() && NumberOfTeamMatesAdjacent(pSoldier, sGridNo) > 1)
if ( nearbyFriends > 1 && !pSoldier->IsZombie() )
{ {
continue; continue;
} }
+106 -102
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,152 +588,156 @@ 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
}
// if this takes us beyond our permitted "roaming range" break; // quit here, sGoToGrid is where we are going
// 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 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 ( usRoomRequired )
{
if ( !( InARoom( sTempDest, &usTempRoom ) && usTempRoom == usRoomRequired ) )
{ {
if ( !( InARoom( sTempDest, &usTempRoom ) && usTempRoom == usRoomRequired ) ) // quit here, limited by room!
break;
}
}
if ( (fFlags & FLAG_STOPSHORT) && SpacesAway( sDesGrid, sTempDest ) <= STOPSHORTDIST )
{
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)
{ {
// quit here, limited by room! if (pSoldier->usUIMovementMode == RUNNING)
break; {
sAPCost += GetAPsStartRun( pSoldier ); // changed by SANDRO
} }
}
if ( (fFlags & FLAG_STOPSHORT) && SpacesAway( sDesGrid, sTempDest ) <= STOPSHORTDIST )
{
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? // ATE: Direction here?
sAPCost += EstimateActionPointCost( pSoldier, sTempDest, (INT8) pSoldier->pathing.usPathingData[sLoop], pSoldier->usUIMovementMode, (INT8) sLoop, (INT8) pSoldier->pathing.usPathDataSize ); 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 bAPsLeft = pSoldier->bActionPoints - sAPCost;
// 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 this gridno is NOT a legal NPC destination
if (!LegalNPCDestination(pSoldier,sTempDest,IGNORE_PATH,WATEROK,0)) // 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
{ {
// break; sGoToGrid = sTempDest; // we're OK up to here
continue;
}
// if after this, we have <= 5 APs remaining, that's far enough, break out // if exactly 5 APs left, don't bother checking any further
// (the idea is to preserve APs so we can crouch or react if if ( gfTurnBasedAI && (bAPsLeft == bReserveAPs) )
// 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)
{ {
// possible optimization - stored path IS good if we're going all the way pSoldier->pathing.bPathStored = TRUE;
if (sGoToGrid == sDesGrid) pSoldier->pathing.sFinalDestination = sGoToGrid;
{ }
pSoldier->pathing.bPathStored = TRUE; else if ( pSoldier->pathing.usPathIndex == 0 )
pSoldier->pathing.sFinalDestination = sGoToGrid; {
} // we can hack this surely! -- CJC
else if ( pSoldier->pathing.usPathIndex == 0 ) pSoldier->pathing.bPathStored = TRUE;
{ pSoldier->pathing.sFinalDestination = sGoToGrid;
// we can hack this surely! -- CJC pSoldier->pathing.usPathDataSize = sLoop + 1;
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,9 +38,6 @@
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;
@@ -74,9 +71,7 @@ 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 )
} }
ActionType PanicAI(SOLDIERTYPE *pSoldier, UINT8 ubCanMove) INT8 PanicAI(SOLDIERTYPE *pSoldier, UINT8 ubCanMove)
{ {
BOOLEAN fFoundRoute = FALSE; BOOLEAN fFoundRoute = FALSE;
INT8 bSlot; INT8 bSlot;
@@ -307,7 +307,7 @@ ActionType PanicAI(SOLDIERTYPE *pSoldier, UINT8 ubCanMove)
if (bPanicTrigger == -1) if (bPanicTrigger == -1)
{ {
// augh! // augh!
return( AI_ACTION_INVALID ); return( -1 );
} }
sPanicTriggerGridNo = gTacticalStatus.sPanicTriggerGridNo[ bPanicTrigger ]; sPanicTriggerGridNo = gTacticalStatus.sPanicTriggerGridNo[ bPanicTrigger ];
@@ -430,7 +430,7 @@ ActionType PanicAI(SOLDIERTYPE *pSoldier, UINT8 ubCanMove)
} }
// no action decided // no action decided
return(AI_ACTION_INVALID); return(-1);
} }
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
ActionType HeadForTheStairCase( SOLDIERTYPE * pSoldier ) INT8 HeadForTheStairCase( SOLDIERTYPE * pSoldier )
{ {
UNDERGROUND_SECTORINFO * pBasementInfo; UNDERGROUND_SECTORINFO * pBasementInfo;
-372
View File
@@ -1,372 +0,0 @@
#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
@@ -1,5 +0,0 @@
#pragma once
//void ScoreAllDecisions(DecisionMaker DM, SOLDIERTYPE* me);
//ActionType SelectScoredAction(DecisionMaker DM);
-57
View File
@@ -1,57 +0,0 @@
#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
@@ -1,24 +0,0 @@
#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, bool doLog = true); extern void LogDecideInfo(SOLDIERTYPE *pSoldier);
extern STR8 gStr8AlertStatus[]; extern STR8 gStr8AlertStatus[];
extern STR8 gStr8Attitude[]; extern STR8 gStr8Attitude[];
+13 -40
View File
@@ -12,6 +12,10 @@
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
@@ -106,10 +110,7 @@ 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_DRINK_CANTEEN, // sevenfm: drink from canteen in inventory AI_ACTION_LAST = AI_ACTION_STOP_MEDIC
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;
@@ -174,36 +175,21 @@ void CheckForChangingOrders(SOLDIERTYPE *pSoldier );
INT8 ClosestPanicTrigger( SOLDIERTYPE * pSoldier ); INT8 ClosestPanicTrigger( SOLDIERTYPE * pSoldier );
INT32 ClosestKnownOpponent(SOLDIERTYPE *pSoldier, INT32 * psGridNo, INT8 * pbLevel, SoldierID *pubOpponentID = NULL, INT32 * distanceInCellCoords = NULL); INT32 ClosestKnownOpponent(SOLDIERTYPE *pSoldier, INT32 * psGridNo, INT8 * pbLevel, SoldierID *pubOpponentID = 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, bool doLog = true, INT8 bAction = -1); void DebugAI(INT8 bMsgType, SOLDIERTYPE *pSoldier, STR szOutput, INT8 bAction = -1);
void DebugQuestInfo(STR szOutput); void DebugQuestInfo(STR szOutput);
INT8 DecideAction(SOLDIERTYPE *pSoldier); INT8 DecideAction(SOLDIERTYPE *pSoldier);
INT8 DecideActionGreen(SOLDIERTYPE *pSoldier);
INT8 DecideActionYellow(SOLDIERTYPE *pSoldier);
INT8 DecideActionRed(SOLDIERTYPE *pSoldier);
INT8 DecideActionBlack(SOLDIERTYPE *pSoldier); INT8 DecideActionBlack(SOLDIERTYPE *pSoldier);
INT8 DecideActionGreenBoxer(SOLDIERTYPE* pSoldier); INT8 DecideActionEscort(SOLDIERTYPE *pSoldier);
INT8 DecideActionBlackBoxer(SOLDIERTYPE* pSoldier); INT8 DecideActionGreen(SOLDIERTYPE *pSoldier);
INT8 DecideActionGreenCivilian(SOLDIERTYPE* pSoldier); INT8 DecideActionRed(SOLDIERTYPE *pSoldier);
INT8 DecideActionYellowCivilian(SOLDIERTYPE* pSoldier); INT8 DecideActionYellow(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 );
@@ -213,12 +199,11 @@ 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 targetGridNo = NOWHERE, bool ignoreSearchRange = false); INT32 FindBestNearbyCover(SOLDIERTYPE *pSoldier, INT32 morale, INT32 *pPercentBetter);
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
@@ -259,7 +244,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);
ActionType PanicAI(SOLDIERTYPE *pSoldier, UINT8 ubCanMove); INT8 PanicAI(SOLDIERTYPE *pSoldier, UINT8 ubCanMove);
void HaltMoveForSoldierOutOfPoints(SOLDIERTYPE *pSoldier); void HaltMoveForSoldierOutOfPoints(SOLDIERTYPE *pSoldier);
INT32 RandDestWithinRange(SOLDIERTYPE *pSoldier); INT32 RandDestWithinRange(SOLDIERTYPE *pSoldier);
@@ -303,7 +288,6 @@ 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 );
@@ -316,13 +300,6 @@ 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);
@@ -443,10 +420,6 @@ 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_TOWNS][MAX_TOWN_NAME_LENGHT]; extern CHAR16 (*pTownNames)[MAX_TOWN_NAME_LENGHT];
UINT8 gubCurrentSteadyStateAmbience = SSA_NONE; UINT8 gubCurrentSteadyStateAmbience = SSA_NONE;
UINT8 gubCurrentSteadyStateSound = 0; UINT8 gubCurrentSteadyStateSound = 0;
+64 -1
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 unloaded sectors) //Simply chooses a random gridno within valid boundaries (for dropping things in loaded sectors)
INT32 RandomGridNo() INT32 RandomGridNo()
{ {
INT32 iMapXPos, iMapYPos, iMapIndex; INT32 iMapXPos, iMapYPos, iMapIndex;
@@ -1332,6 +1332,69 @@ 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,6 +113,7 @@ 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;
+179 -175
View File
@@ -23,7 +23,6 @@
#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;
@@ -553,25 +552,22 @@ 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;
//extern UINT8 * gubGridNoMarkers; #ifdef _DEBUG
//extern UINT8 gubGridNoValue; //extern UINT8 gubFOVDebugInfoInfo[ WORLD_MAX ];
//extern BOOLEAN gfDisplayGridNoVisibleValues = 0; //extern UINT8 gubGridNoMarkers[ WORLD_MAX ];
//void RenderFOVDebugInfo( INT16 sStartPointX_M, INT16 sStartPointY_M, INT16 sStartPointX_S, INT16 sStartPointY_S, INT16 sEndXS, INT16 sEndYS ); extern UINT8 * gubFOVDebugInfoInfo;
//void RenderGridNoVisibleDebugInfo( INT16 sStartPointX_M, INT16 sStartPointY_M, INT16 sStartPointX_S, INT16 sStartPointY_S, INT16 sEndXS, INT16 sEndYS ); extern UINT8 * gubGridNoMarkers;
extern INT16 gsBestCover; extern UINT8 gubGridNoValue;
INT32* gRenderDebugInfoValues = nullptr; extern BOOLEAN gfDisplayCoverValues;
void RenderDebugInfo(INT16 sStartPointX_M, INT16 sStartPointY_M, INT16 sStartPointX_S, INT16 sStartPointY_S, INT16 sEndXS, INT16 sEndYS); extern BOOLEAN gfDisplayGridNoVisibleValues = 0;
void ResetDebugInfoValues() //extern INT16 gsCoverValue[ WORLD_MAX ];
{ extern INT16 * gsCoverValue;
if (gRenderDebugInfoValues) extern INT16 gsBestCover;
{ void RenderFOVDebugInfo( 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 RenderCoverDebugInfo( 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 );
gRenderDebugInfoValues[i] = 0x7FFFFFFF; #endif
}
}
}
void DeleteFromWorld( UINT16 usTileIndex, UINT32 uiRenderTiles, UINT16 usIndex ); void DeleteFromWorld( UINT16 usTileIndex, UINT32 uiRenderTiles, UINT16 usIndex );
@@ -3276,10 +3272,23 @@ 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 );
} }
if (DEBUG_CHEAT_LEVEL() && gTacticalStatus.Team[OUR_TEAM].bTeamActive) #ifdef _DEBUG
if( gRenderFlags&RENDER_FLAG_FOVDEBUG )
{ {
RenderDebugInfo(gsStartPointX_M, gsStartPointY_M, gsStartPointX_S, gsStartPointY_S, gsEndXS, gsEndYS); RenderFOVDebugInfo( 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 );
@@ -8005,10 +8014,6 @@ 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 )
{ {
@@ -8119,7 +8124,114 @@ void RenderFOVDebugInfo( INT16 sStartPointX_M, INT16 sStartPointY_M, INT16 sStar
} }
// This one just renders tile gridnos. Not much use nowadays void RenderCoverDebugInfo( INT16 sStartPointX_M, INT16 sStartPointY_M, INT16 sStartPointX_S, INT16 sStartPointY_S, INT16 sEndXS, INT16 sEndYS )
{
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;
@@ -8222,148 +8334,9 @@ 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 );
@@ -8545,8 +8518,7 @@ 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...
if ( gsStartPointX_W > 0 ) gsStartPointX_W += CELL_X_SIZE;
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;
@@ -8554,8 +8526,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 = ( gsStartPointX_W ) / CELL_X_SIZE; gsStartPointX_M = floor( DOUBLE(gsStartPointX_W) / DOUBLE(CELL_X_SIZE) );
gsStartPointY_M = ( gsStartPointY_W ) / CELL_Y_SIZE; gsStartPointY_M = floor( DOUBLE(gsStartPointY_W) / DOUBLE(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
@@ -8591,7 +8563,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);
@@ -8605,8 +8577,7 @@ 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...
if ( gsLStartPointX_W > 0 ) gsLStartPointX_W += CELL_X_SIZE;
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;
@@ -8614,8 +8585,23 @@ 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 = ( gsLStartPointX_W ) / CELL_X_SIZE; gsLStartPointX_M = floor( DOUBLE(gsLStartPointX_W) / DOUBLE(CELL_X_SIZE) );
gsLStartPointY_M = ( gsLStartPointY_W ) / CELL_Y_SIZE; gsLStartPointY_M = floor( DOUBLE(gsLStartPointY_W) / DOUBLE(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;
@@ -9024,3 +9010,21 @@ 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