Skip to content

Commit

Permalink
Implement TargetZoneScanType to allow AI units to properly target obj…
Browse files Browse the repository at this point in the history
…ects outside of their movement zone
  • Loading branch information
Rampastring authored Dec 20, 2024
1 parent 1ed3234 commit 1a01961
Show file tree
Hide file tree
Showing 7 changed files with 162 additions and 1 deletion.
3 changes: 3 additions & 0 deletions CREDITS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
16 changes: 16 additions & 0 deletions docs/New-Features-and-Enhancements.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions docs/Whats-New.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
95 changes: 95 additions & 0 deletions src/extensions/techno/technoext_hooks.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<TechnoTypeClassExtension>(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.
*/
Expand Down Expand Up @@ -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);
}
35 changes: 34 additions & 1 deletion src/extensions/technotype/technotypeext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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()));
}
Expand Down Expand Up @@ -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;
}


Expand Down Expand Up @@ -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;
}
5 changes: 5 additions & 0 deletions src/extensions/technotype/technotypeext.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
};
8 changes: 8 additions & 0 deletions src/vinifera/vinifera_defines.h
Original file line number Diff line number Diff line change
Expand Up @@ -151,3 +151,11 @@ typedef enum ViniferaRTTIType
VINIFERA_RTTI_COUNT
};
DEFINE_ENUMERATION_OPERATORS(ViniferaRTTIType);


typedef enum TargetZoneScanType
{
TZST_SAME,
TZST_ANY,
TZST_INRANGE
} TargetZoneScanType;

0 comments on commit 1a01961

Please sign in to comment.