Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CREDITS.md
Original file line number Diff line number Diff line change
Expand Up @@ -742,7 +742,6 @@ This page lists all the individual contributions to the project by their author.
- Fix an issue that the AI would look for the first house in the array as an enemy instead of the nearest one when there were no enemies
- `AllowBerzerkOnAllies`
- Fix an issue that retaliation will make the unit keep switching among multiple targets with the same amount of threat
- Fix an issue where units recruited by a team with `AreTeamMembersRecruitable=false` cannot be recruited even if they have been liberated by that team
- Global default value for `DefaultToGuardArea`
- Weapon range finding in cylinder
- Allow jumpjet climbing ignore building height
Expand All @@ -766,6 +765,7 @@ This page lists all the individual contributions to the project by their author.
- Framework for dynamic sight
- Fix voxel projectile and animation lighting issues
- Export interface for external call
- [Adjust recruitable status on team member discharge](AI-Scripting-and-Mapping.md#adjust-recruitable-status-on-team-member-discharge)
- **solar-III (凤九歌)**
- Target scanning delay customization (documentation)
- Skip target scanning function calling for unarmed technos (documentation)
Expand Down
3 changes: 3 additions & 0 deletions Phobos.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,8 @@
<!-- src\Ext\Team -->
<ClCompile Include="src\Ext\Team\Body.cpp" />
<ClCompile Include="src\Ext\Team\Hooks.cpp" />
<!-- src\Ext\TeamType -->
<ClCompile Include="src\Ext\TeamType\Body.cpp" />
<!-- src\Ext\Techno -->
<ClCompile Include="src\Ext\Techno\Body.cpp" />
<ClCompile Include="src\Ext\Techno\Body.Internal.cpp" />
Expand Down Expand Up @@ -341,6 +343,7 @@
<ClInclude Include="src\Ext\SWType\NewSWType\NewSWType.h" />
<ClInclude Include="src\Ext\TAction\Body.h" />
<ClInclude Include="src\Ext\Team\Body.h" />
<ClInclude Include="src\Ext\TeamType\Body.h" />
<ClInclude Include="src\Ext\Techno\Body.h" />
<ClInclude Include="src\Ext\TechnoType\Body.h" />
<ClInclude Include="src\Ext\TerrainType\Body.h" />
Expand Down
20 changes: 20 additions & 0 deletions docs/AI-Scripting-and-Mapping.md
Original file line number Diff line number Diff line change
Expand Up @@ -965,3 +965,23 @@ In `mycampaign.map`:
ID=EventCount,...,606,2,0,[AttachEffectType],...
...
```

## Teams

### Adjust recruitable status on team member discharge

- In vanilla, when a unit is added to a team, its `RecruitableB` flag is overwritten by the team's `AreTeamMembersRecruitable` setting. When the unit is discharged from the team, the flag is not restored. The following settings allow a team to reset this flag when discharging its members.
- `[General] → IsDischargedMemberAutocreateRecruitable` sets the global default value.
- `[TeamType] → IsDischargedMemberAutocreateRecruitable` overrides the global default for a specific team type.
- If set to a value **greater than 0**, the discharged unit is forcibly marked as recruitable.
- If set to **0**, the discharged unit is forcibly marked as not recruitable.
- If set to a value **less than 0** (default: `-1`), the original game behavior is preserved.

In `rulesmd.ini`:
```ini
[General]
IsDischargedMemberAutocreateRecruitable=-1 ; integer

[SOMETEAMTYPE] ; TeamType
IsDischargedMemberAutocreateRecruitable= ; integer, default to [General] -> IsDischargedMemberAutocreateRecruitable
```
2 changes: 1 addition & 1 deletion docs/Whats-New.md
Original file line number Diff line number Diff line change
Expand Up @@ -584,6 +584,7 @@ HideShakeEffects=false ; boolean
- Introduce weight selection rules for ExtraWarheads (by Noble_Fish)
- [Building turret idle/firing/low power animations](Fixed-or-Improved-Logics.md#building-turret-animations) (by Starkku)
- [Add action `512 Set Follower for Associated Unit...`](AI-Scripting-and-Mapping.md#set-follower-for-associated-unit) (by Noble_Fish)
- [Adjust recruitable status on team member discharge](AI-Scripting-and-Mapping.md#adjust-recruitable-status-on-team-member-discharge) (by TaranDahl)

#### Vanilla fixes:
- Fixed sidebar not updating queued unit numbers when adding or removing units when the production is on hold (by CrimRecya)
Expand Down Expand Up @@ -638,7 +639,6 @@ HideShakeEffects=false ; boolean
- Fixed an issue where mining vehicles could not move after leaving a tank bunker (by FlyStar)
- Fixed the bug where selected technos would lose their selection if their regular mind control was replaced with permanent mind control or with the control from the Psychic Dominator superweapon (by NetsuNegi)
- Fixed an issue that retaliation will make the unit keep switching among multiple targets with the same amount of threat (by TaranDahl)
- Fixed the issue where units recruited by a team with `AreTeamMembersRecruitable=false` cannot be recruited even if they have been liberated by that team (by TaranDahl)
- Fixed the bug that cause technos teleport to cell 0,0 by ChronoSphere superweapon (by NetsuNegi)
- Fixed the bug that techno in attack move will move to target if it cannot attack it (by NetsuNegi)
- Fixed the bug in AI scripts 56 and 57 that forced the launch of superweapons with index numbers 3 and 4 (by FlyStar)
Expand Down
45 changes: 45 additions & 0 deletions docs/locale/zh_CN/LC_MESSAGES/AI-Scripting-and-Mapping.po
Original file line number Diff line number Diff line change
Expand Up @@ -1727,3 +1727,48 @@ msgid ""
"same type of attached effect to the techno."
msgstr "外源 AE:赋予时、对同一单位赋予同一 AE 使其刷新时。"

msgid "Teams"
msgstr "小队"

msgid "Adjust recruitable status on team member discharge"
msgstr "小队成员解散时调整可招募性"

msgid ""
"In vanilla, when a unit is added to a team, its `RecruitableB` flag is "
"overwritten by the team's `AreTeamMembersRecruitable` setting. When the "
"unit is discharged from the team, the flag is not restored. The following "
"settings allow a team to reset this flag when discharging its members."
msgstr ""
"在原版游戏中,当单位被加入到小队中时,其 `RecruitableB` 字段会被小队的设置 "
"`AreTeamMembersRecruitable` 覆写。单位被小队解散时,该字段也不会被恢复。以下设置"
"允许小队在解散时重新设置该字段。"

msgid ""
"`[General] → IsDischargedMemberAutocreateRecruitable` sets the global "
"default value."
msgstr ""
"`[General] → IsDischargedMemberAutocreateRecruitable` 设置全局默认值。"

msgid ""
"`[TeamType] → IsDischargedMemberAutocreateRecruitable` overrides the "
"global default for a specific team type."
msgstr ""
"`[TeamType] → IsDischargedMemberAutocreateRecruitable` 对特定小队类型覆盖全局默认值。"

msgid ""
"If set to a value greater than 0, the discharged unit is forcibly marked "
"as recruitable."
msgstr ""
"如果值大于 0,解散的单位会被强制标记为可招募。"

msgid ""
"If set to 0, the discharged unit is forcibly marked as not recruitable."
msgstr ""
"如果值为 0,解散的单位会被强制标记为不可招募。"

msgid ""
"If set to a value less than 0 (default: -1), the original game behavior "
"is preserved."
msgstr ""
"如果值小于 0(默认值:-1),则保留原版游戏行为。"

7 changes: 2 additions & 5 deletions docs/locale/zh_CN/LC_MESSAGES/CREDITS.po
Original file line number Diff line number Diff line change
Expand Up @@ -2482,11 +2482,8 @@ msgid ""
"multiple targets with the same amount of threat"
msgstr "修复了反击行为会让单位在多个相同威胁值的目标间不断切换的问题"

msgid ""
"Fix an issue where units recruited by a team with "
"`AreTeamMembersRecruitable=false` cannot be recruited even if they have "
"been liberated by that team"
msgstr "修复了单位被 `AreTeamMembersRecruitable=false` 的小队招募过则即便原小队解散也无法再被其他小队招募的 Bug"
msgid "Adjust recruitable status on team member discharge"
msgstr "小队成员解散时调整可招募性"

msgid "Global default value for `DefaultToGuardArea`"
msgstr "`DefaultToGuardArea` 的全局默认值"
Expand Down
10 changes: 5 additions & 5 deletions docs/locale/zh_CN/LC_MESSAGES/Whats-New.po
Original file line number Diff line number Diff line change
Expand Up @@ -2229,12 +2229,12 @@ msgid ""
msgstr "修复了反击行为会让单位在多个相同威胁值的目标间不断切换的问题(by TaranDahl)"

msgid ""
"Fixed the issue where units recruited by a team with "
"`AreTeamMembersRecruitable=false` cannot be recruited even if they have "
"been liberated by that team (by TaranDahl)"
"- [Adjust recruitable status on team member discharge](AI-Scripting-and-"
"Mapping.md#adjust-recruitable-status-on-team-member-discharge) (by "
"TaranDahl)"
msgstr ""
"修复了单位被 `AreTeamMembersRecruitable=false` 的小队招募过则即便原小队解散也无法再被其他小队招募的 "
"Bug(by TaranDahl)"
"- [小队成员解散时调整可招募性](AI-Scripting-and-Mapping.md#adjust-recruitable-status-on-team-member-discharge)(by "
"TaranDahl)"

msgid ""
"Fixed the bug that cause technos teleport to cell 0,0 by ChronoSphere "
Expand Down
2 changes: 2 additions & 0 deletions src/Ext/Rules/Body.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,7 @@ void RulesExt::ExtData::LoadBeforeTypeData(RulesClass* pThis, CCINIClass* pINI)

this->IsVoiceCreatedGlobal.Read(exINI, GameStrings::AudioVisual, "IsVoiceCreatedGlobal");
this->SelectionFlashDuration.Read(exINI, GameStrings::AudioVisual, "SelectionFlashDuration");
this->IsDischargedMemberAutocreateRecruitable.Read(exINI, GameStrings::General, "IsDischargedMemberAutocreateRecruitable");
this->DrawInsignia_OnlyOnSelected.Read(exINI, GameStrings::AudioVisual, "DrawInsignia.OnlyOnSelected");
this->DrawInsignia_AdjustPos_Infantry.Read(exINI, GameStrings::AudioVisual, "DrawInsignia.AdjustPos.Infantry");
this->DrawInsignia_AdjustPos_Buildings.Read(exINI, GameStrings::AudioVisual, "DrawInsignia.AdjustPos.Buildings");
Expand Down Expand Up @@ -609,6 +610,7 @@ void RulesExt::ExtData::Serialize(T& Stm)
.Process(this->DrawTurretShadow)
.Process(this->IsVoiceCreatedGlobal)
.Process(this->SelectionFlashDuration)
.Process(this->IsDischargedMemberAutocreateRecruitable)
.Process(this->DrawInsignia_OnlyOnSelected)
.Process(this->DrawInsignia_AdjustPos_Infantry)
.Process(this->DrawInsignia_AdjustPos_Buildings)
Expand Down
2 changes: 2 additions & 0 deletions src/Ext/Rules/Body.h
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ class RulesExt
Valueable<bool> ShowPowerPlantEnhancerRange;
Valueable<bool> IsVoiceCreatedGlobal;
Valueable<int> SelectionFlashDuration;
Valueable<int> IsDischargedMemberAutocreateRecruitable;
Nullable<AnimTypeClass*> DropPodTrailer;
AnimTypeClass* DropPodDefaultTrailer;
SHPStruct* PodImage;
Expand Down Expand Up @@ -486,6 +487,7 @@ class RulesExt
, DrawTurretShadow { false }
, IsVoiceCreatedGlobal { false }
, SelectionFlashDuration { 0 }
, IsDischargedMemberAutocreateRecruitable { -1 }
, DrawInsignia_OnlyOnSelected { false }
, DrawInsignia_AdjustPos_Infantry { { 5, 2 } }
, DrawInsignia_AdjustPos_Buildings { { 10, 6 } }
Expand Down
17 changes: 17 additions & 0 deletions src/Ext/Team/Hooks.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#include "Body.h"
#include <Utilities/AresHelper.h>
#include <Ext/Techno/Body.h>
#include <Ext/TeamType/Body.h>

// Bugfix: TAction 7,80,107.
DEFINE_HOOK(0x65DF67, TeamTypeClass_CreateMembers_LoadOntoTransport, 0x6)
Expand Down Expand Up @@ -147,3 +148,19 @@ DEFINE_HOOK(0x6EF57F, TeamClass_GetTaskForceMissingMemberTypes_Consideration, 0x

return SkipThisMember;
}

DEFINE_HOOK(0x6EA870, TeamClass_LiberateMember_Start, 0x6)
{
GET_STACK(FootClass*, pMember, 0x4);
GET(TeamClass*, pTeam, ECX);

const auto pTeamTypeExt = TeamTypeExt::ExtMap.Find(pTeam->Type);
const int value = pTeamTypeExt->IsDischargedMemberAutocreateRecruitable.Get(RulesExt::Global()->IsDischargedMemberAutocreateRecruitable);

if (value > 0)
pMember->RecruitableB = true;
else if (value == 0)
pMember->RecruitableB = false;

return 0;
}
99 changes: 99 additions & 0 deletions src/Ext/TeamType/Body.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
#include "Body.h"

TeamTypeExt::ExtContainer TeamTypeExt::ExtMap;

// =============================
// load / save

void TeamTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI)
{
auto pThis = this->OwnerObject();
const char* pSection = pThis->ID;
INI_EX exINI(pINI);

this->IsDischargedMemberAutocreateRecruitable.Read(exINI, pSection, "IsDischargedMemberAutocreateRecruitable");
}

template <typename T>
void TeamTypeExt::ExtData::Serialize(T& Stm)
{
Stm
.Process(this->IsDischargedMemberAutocreateRecruitable)
;
}

void TeamTypeExt::ExtData::LoadFromStream(PhobosStreamReader& Stm)
{
Extension<TeamTypeClass>::LoadFromStream(Stm);
this->Serialize(Stm);
}

void TeamTypeExt::ExtData::SaveToStream(PhobosStreamWriter& Stm)
{
Extension<TeamTypeClass>::SaveToStream(Stm);
this->Serialize(Stm);
}

// =============================
// container

TeamTypeExt::ExtContainer::ExtContainer() : Container("TeamTypeClass") { }

TeamTypeExt::ExtContainer::~ExtContainer() = default;

// =============================
// container hooks

DEFINE_HOOK(0x6F08E6, TeamTypeClass_CTOR, 0x6)
{
GET(TeamTypeClass*, pItem, EAX);

TeamTypeExt::ExtMap.TryAllocate(pItem);

return 0;
}

DEFINE_HOOK(0x6F20D0, TeamTypeClass_DTOR, 0x6)
{
GET(TeamTypeClass*, pItem, ECX);

TeamTypeExt::ExtMap.Remove(pItem);

return 0;
}

DEFINE_HOOK_AGAIN(0x6F1BB0, TeamTypeClass_SaveLoad_Prefix, 0x5)
DEFINE_HOOK(0x6F1B90, TeamTypeClass_SaveLoad_Prefix, 0x8)
{
GET_STACK(TeamTypeClass*, pItem, 0x4);
GET_STACK(IStream*, pStm, 0x8);

TeamTypeExt::ExtMap.PrepareStream(pItem, pStm);

return 0;
}

DEFINE_HOOK(0x6F1C35, TeamTypeClass_Load_Suffix, 0x5)
{
TeamTypeExt::ExtMap.LoadStatic();

return 0;
}

DEFINE_HOOK(0x6F1BAA, TeamTypeClass_Save_Suffix, 0x5)
{
TeamTypeExt::ExtMap.SaveStatic();

return 0;
}

DEFINE_HOOK_AGAIN(0x6F1535, TeamTypeClass_LoadFromINI, 0xA)
DEFINE_HOOK(0x6F1528, TeamTypeClass_LoadFromINI, 0xA)
{
GET(TeamTypeClass*, pItem, ESI);
GET_STACK(CCINIClass*, pINI, 0xCC);

TeamTypeExt::ExtMap.LoadFromINI(pItem, pINI);

return 0;
}
47 changes: 47 additions & 0 deletions src/Ext/TeamType/Body.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
#pragma once
#include <TeamTypeClass.h>

#include <Utilities/Container.h>
#include <Utilities/TemplateDef.h>

class TeamTypeExt
{
public:
using base_type = TeamTypeClass;

static constexpr DWORD Canary = 0xABCDEF01;
static constexpr size_t ExtPointerOffset = 0xBC;

class ExtData final : public Extension<TeamTypeClass>
{
public:
ExtData(TeamTypeClass* OwnerObject) : Extension<TeamTypeClass>(OwnerObject)
, IsDischargedMemberAutocreateRecruitable { }
{ }

virtual ~ExtData() = default;

virtual void LoadFromINIFile(CCINIClass* pINI) override;
// virtual void Initialize() override;

Nullable<int> IsDischargedMemberAutocreateRecruitable;

virtual void InvalidatePointer(void* ptr, bool bRemoved) override { }

virtual void LoadFromStream(PhobosStreamReader& Stm) override;
virtual void SaveToStream(PhobosStreamWriter& Stm) override;

private:
template <typename T>
void Serialize(T& Stm);
};

class ExtContainer final : public Container<TeamTypeExt>
{
public:
ExtContainer();
~ExtContainer();
};

static ExtContainer ExtMap;
};
8 changes: 0 additions & 8 deletions src/Misc/Hooks.BugFixes.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3211,14 +3211,6 @@ DEFINE_HOOK(0x708A81, TechnoClass_CanRetaliate_CheckThreat, 0x5)
return pThis->ThreatCoeffients(pAttacker, &CoordStruct::Empty) <= pThis->ThreatCoeffients((ObjectClass*)(pThis->Target), &CoordStruct::Empty) ? SkipRetaliate : GoOtherChecks;
}

// Fixed the issue where units recruited by a team with `AreTeamMembersRecruitable=false` cannot be recruited even if they have been liberated by that team.
DEFINE_HOOK(0x6EA870, TeamClass_LiberateMember_Start, 0x6)
{
GET_STACK(FootClass*, pMember, 0x4);
pMember->RecruitableB = true;
return 0;
}

DEFINE_HOOK_AGAIN(0x6F845D, TechnoClass_CanAutoTargetObject_Garrisonable, 0x6);
DEFINE_HOOK(0x6F833E, TechnoClass_CanAutoTargetObject_Garrisonable, 0x6)
{
Expand Down
2 changes: 2 additions & 0 deletions src/Phobos.Ext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
#include <Ext/SWType/NewSWType/NewSWType.h>
#include <Ext/TAction/Body.h>
#include <Ext/Team/Body.h>
#include <Ext/TeamType/Body.h>
#include <Ext/Techno/Body.h>
#include <Ext/TechnoType/Body.h>
#include <Ext/TerrainType/Body.h>
Expand Down Expand Up @@ -225,6 +226,7 @@ using PhobosTypeRegistry = TypeRegistry <
SWTypeExt,
TActionExt,
TeamExt,
TeamTypeExt,
TechnoExt,
TechnoTypeExt,
TerrainTypeExt,
Expand Down
Loading