From 8bf233d1444681dbad4efb2454c9ed9305b31c7c Mon Sep 17 00:00:00 2001 From: Rampastring Date: Fri, 20 Dec 2024 03:11:13 +0200 Subject: [PATCH 1/2] Implement TargetZoneScanType to allow AI units to properly target objects outside of their movement zone --- src/extensions/techno/technoext_hooks.cpp | 95 +++++++++++++++++++++ src/extensions/technotype/technotypeext.cpp | 35 +++++++- src/extensions/technotype/technotypeext.h | 5 ++ src/vinifera/vinifera_defines.h | 8 ++ 4 files changed, 142 insertions(+), 1 deletion(-) diff --git a/src/extensions/techno/technoext_hooks.cpp b/src/extensions/techno/technoext_hooks.cpp index f52688cfa..c9b110355 100644 --- a/src/extensions/techno/technoext_hooks.cpp +++ b/src/extensions/techno/technoext_hooks.cpp @@ -2550,6 +2550,99 @@ DECLARE_PATCH(_TechnoClass_Fire_At_TargetLaserTimer_Patch) } +/** + * #issue-1161 + * + * Helper function. Checks whether a target is valid considering zone evaluation. + * + * @author: Rampastring + */ +bool _TechnoClass_Evaluate_Object_Zone_Evaluation_Is_Valid_Target(TechnoClass* techno, TARGET target, int ourzone, int targetzone) +{ + auto technotype = techno->Techno_Type_Class(); + auto technotypeext = Extension::Fetch(technotype); + + if (technotypeext->TargetZoneScan == TZST_SAME) { + // Only allow targeting objects in the same zone. + return ourzone == targetzone; + } + + if (technotypeext->TargetZoneScan == TZST_ANY) { + // Any zone is allowed. + return true; + } + + if (technotypeext->TargetZoneScan == TZST_INRANGE) { + // If the zone is different, only allow targeting if we can reach the target from our zone. + + if (ourzone == targetzone) { + return true; + } + + Cell nearbycellcoords = Map.Nearby_Location(Coord_Cell(target->Center_Coord()), + technotype->Speed, + /*Phobos has -1 here*/ ourzone, + technotype->MZone, + false, 1, 1, true, false, false, technotype->Speed != SPEED_FLOAT, Cell()); + + const CellClass& cell = Map[nearbycellcoords]; + + if (&cell == nullptr) { + // We couldn't find a valid cell to reach the target from + return false; + } + + int distance = ::Distance(nearbycellcoords, Coord_Cell(target->Center_Coord())); + + WeaponSlotType weaponslot = techno->What_Weapon_Should_I_Use(target); + auto weaponinfo = techno->Get_Weapon(weaponslot); + if (weaponinfo->Weapon == nullptr) { + return false; + } + + return (distance * CELL_LEPTON_W) < weaponinfo->Weapon->Range; + } + + // For some reason the target zone scan type was invalid. Something is wrong. + Fatal("Invalid TargetZoneScanType for techno type %s", technotype->IniName); + return false; +} + + +/** + * #issue-1161 + * + * Makes Technos consider their TargetZoneScan when potential targets are in a + * different movement zone. Makes the AI smarter when targeting objects on different + * movement zones (for example, ships targeting ground targets). + * Implementation inspired by respective feature for the "Phobos" Yuri's Revenge engine extension. + * + * @author: Rampastring + */ +DECLARE_PATCH(_TechnoClass_Evaluate_Object_Zone_Evaluation_TargetZoneScanType_Patch) +{ + GET_REGISTER_STATIC(int, targetzone, eax); + GET_REGISTER_STATIC(int, ourzone, ebp); + GET_REGISTER_STATIC(TARGET, target, esi); + GET_REGISTER_STATIC(TechnoClass*, this_ptr, edi); + + enum { + Continue = 0x0062D220, + InvalidTarget = 0x0062D8C0 + }; + + if (targetzone == ourzone) { + JMP(Continue); + } + + if (!_TechnoClass_Evaluate_Object_Zone_Evaluation_Is_Valid_Target(this_ptr, target, ourzone, targetzone)) { + JMP(InvalidTarget); + } + + JMP(Continue); +} + + /** * Main function for patching the hooks. */ @@ -2599,4 +2692,6 @@ void TechnoClassExtension_Hooks() Patch_Jump(0x00631FF0, &TechnoClassExt::_Can_Player_Move); Patch_Jump(0x006336F0, &TechnoClassExt::_Record_The_Kill); //Patch_Jump(0x0062A3D0, &TechnoClassExt::_Fire_Coord); // Disabled because it's functionally identical to the vanilla function when there's no secondary coordinate + Patch_Jump(0x00633745, (uintptr_t)0x00633762); // Do not trigger "Discovered by Player" when an object is destroyed + Patch_Jump(0x0062D218, &_TechnoClass_Evaluate_Object_Zone_Evaluation_TargetZoneScanType_Patch); } diff --git a/src/extensions/technotype/technotypeext.cpp b/src/extensions/technotype/technotypeext.cpp index 4ead5f5f9..82e680a90 100644 --- a/src/extensions/technotype/technotypeext.cpp +++ b/src/extensions/technotype/technotypeext.cpp @@ -88,7 +88,8 @@ TechnoTypeClassExtension::TechnoTypeClassExtension(const TechnoTypeClass *this_p IsSpawned(false), BuildTimeCost(0), RequiredHouses(-1), - ForbiddenHouses(-1) + ForbiddenHouses(-1), + TargetZoneScan(TZST_SAME) { //if (this_ptr) EXT_DEBUG_TRACE("TechnoTypeClassExtension::TechnoTypeClassExtension - Name: %s (0x%08X)\n", Name(), (uintptr_t)(This())); } @@ -237,6 +238,36 @@ void TechnoTypeClassExtension::Compute_CRC(WWCRCEngine &crc) const crc(SpawnRegenRate); crc(SpawnReloadRate); crc(SpawnsNumber); + crc(TargetZoneScan); +} + + +/** + * #issue-1161 + * + * Fetches the target zone scan type from the INI database. + * + * @author: Rampastring + */ +TargetZoneScanType _Get_TargetZoneScanType(CCINIClass& ini, const char* section, const char* entry, const TargetZoneScanType defvalue) +{ + char buffer[1024]; + + if (ini.Get_String(section, entry, nullptr, buffer, sizeof(buffer)) > 0) { + if (std::strncmp("Same", buffer, sizeof("Same")) == 0) { + return TZST_SAME; + } + + if (std::strncmp("Any", buffer, sizeof("Any")) == 0) { + return TZST_ANY; + } + + if (std::strncmp("InRange", buffer, sizeof("InRange")) == 0) { + return TZST_INRANGE; + } + } + + return defvalue; } @@ -327,5 +358,7 @@ bool TechnoTypeClassExtension::Read_INI(CCINIClass &ini) RequiredHouses = ini.Get_Owners(ini_name, "RequiredHouses", RequiredHouses); ForbiddenHouses = ini.Get_Owners(ini_name, "ForbiddenHouses", ForbiddenHouses); + TargetZoneScan = _Get_TargetZoneScanType(ini, ini_name, "TargetZoneScan", TargetZoneScan); + return true; } diff --git a/src/extensions/technotype/technotypeext.h b/src/extensions/technotype/technotypeext.h index 21994a600..ce31a96c2 100644 --- a/src/extensions/technotype/technotypeext.h +++ b/src/extensions/technotype/technotypeext.h @@ -262,4 +262,9 @@ class TechnoTypeClassExtension : public ObjectTypeClassExtension * Optional override for the cost that is used for determining the techno's build time. */ int BuildTimeCost; + + /** + * Defines how the techno treats targets outside of its zone when scanning for targets. + */ + TargetZoneScanType TargetZoneScan; }; diff --git a/src/vinifera/vinifera_defines.h b/src/vinifera/vinifera_defines.h index 3c2a335a9..244e6e338 100644 --- a/src/vinifera/vinifera_defines.h +++ b/src/vinifera/vinifera_defines.h @@ -151,3 +151,11 @@ typedef enum ViniferaRTTIType VINIFERA_RTTI_COUNT }; DEFINE_ENUMERATION_OPERATORS(ViniferaRTTIType); + + +typedef enum TargetZoneScanType +{ + TZST_SAME, + TZST_ANY, + TZST_INRANGE +} TargetZoneScanType; \ No newline at end of file From bed8784c28732a86a14e5334fd2967a6ced60685 Mon Sep 17 00:00:00 2001 From: Rampastring Date: Fri, 20 Dec 2024 03:27:17 +0200 Subject: [PATCH 2/2] Update docs --- CREDITS.md | 3 +++ docs/New-Features-and-Enhancements.md | 16 ++++++++++++++++ docs/Whats-New.md | 1 + 3 files changed, 20 insertions(+) diff --git a/CREDITS.md b/CREDITS.md index 5d903a822..f25bf70ad 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -165,8 +165,11 @@ This page lists all the individual contributions to the project by their author. - Add `BuildTimeCost`. - Allow scenarios to have custom score screen bar colors. - Add `Inaccuracy` to RocketTypes. + - Add `TargetZoneScan` to TechnoTypes. - **secsome**: - Add support for up to 32767 waypoints to be used in scenarios. +- **Starkku**: + - Add `TargetZoneScan` to TechnoTypes. - **ZivDero**: - Filling the documentation for previously implemented features. - Add support for up to 32767 waypoints to be used in scenarios. diff --git a/docs/New-Features-and-Enhancements.md b/docs/New-Features-and-Enhancements.md index eb45f1708..fb6c21e98 100644 --- a/docs/New-Features-and-Enhancements.md +++ b/docs/New-Features-and-Enhancements.md @@ -909,6 +909,22 @@ BuildTimeCost=300 ; integer, specifies the object's build time. ; for example, setting this to 300 makes the object build as fast as a 300-cost object, regardless of its actual cost. ``` +### TargetZoneType + +In the original game, when AI units look for targets to attack through (team or individual unit) missions like Hunt or Attack Quarry, the AI only scans for targets within the unit's own movement zone. + +This makes the AI perform poorly in some scenarios, like when it tries to attack coastal targets with naval units. AI naval units can attack land targets if the ships end up idling within guard range of the land target, but if the naval units are farther away from the target than their guard range, they will ignore the land target - even if the naval units could just move closer and then attack the target from near the shoreline, like human players do. + +Vinifera allows customizing this behaviour per TechnoType. With `TargetZoneScan=InRange`, AI units of the type will scan targets outside of their movement zone. Any targets that the unit can reach from its movement zone, considering the unit's weapon range, will be considered valid targets. Note that this option is relatively expensive considering performance - it is recommended to only enable it for specially important long-ranged units. + +With `TargetZoneScan=Any`, the AI considers all targets valid, regardless of zone or movement range. + +In `RULES.INI`: +```ini +[SOMETECHNO] +TargetZoneScan=InRange ; InRange, Any, or Same. Same - matches original game behaviour and is the default. InRange - considers targets in other movement zones that are within weapon range. Any - ignore zone checks altogether. +``` + ## Terrain ### Light Sources diff --git a/docs/Whats-New.md b/docs/Whats-New.md index f7187324e..9231e970f 100644 --- a/docs/Whats-New.md +++ b/docs/Whats-New.md @@ -167,6 +167,7 @@ New: - Allow turning off "sticky" technologies (by ZivDero) - Allow disabling the ActLike check on construction yards to allow for faction-specific MCVs (by ZivDero) - Implement a feature for animations to spawn additional animations (by CCHyper/tomsons26, ZivDero) +- Add `TargetZoneScan` to TechnoTypes (by Rampastring) Vanilla fixes: