Skip to content

Commit

Permalink
Allow refreshing buildables after losing buildings, allow removing MC…
Browse files Browse the repository at this point in the history
…V ActLike check
  • Loading branch information
ZivDero committed Nov 27, 2024
1 parent 8cb8c19 commit 0a8e674
Show file tree
Hide file tree
Showing 10 changed files with 167 additions and 47 deletions.
2 changes: 2 additions & 0 deletions CREDITS.md
Original file line number Diff line number Diff line change
Expand Up @@ -204,4 +204,6 @@ This page lists all the individual contributions to the project by their author.
- Fix a bug where AI players would send teams to attack their allies.
- Allow customizing minimum damage per warhead.
- Implement required and forbidden houses.
- Allow turning off "sticky" technologies.
- Allow disabling the ActLike check on construction yards to allow for faction-specific MCVs.

22 changes: 22 additions & 0 deletions docs/Miscellaneous.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,28 @@ PrePlacedConYards=no ; boolean, should pre-place construction yards instead of
; NOTE: This option has priority over AutoDeployMCV.
```

## Prerequisites

### Multi-MCV

- Vinifera allows turning off the check for the house that built the MCV to allow giving each faction their own MCV (instead of a shared MCV).

In `RULES.INI`:
```ini
[General]
MultiMCV=no ; boolean, should MCVs allow the construction of buildings of any house, not only the house that built them?
```

### Sticky Technologies

- In vanilla, technologies are "sticky", that is, for example, if you lose a tech center, you will not lose access to objects that require a tech center until you lose all factories of the type. Vinifera allows turning off this behavior.

In `RULES.INI`:
```ini
[General]
RecheckPrerequisites=no ; boolean, should prerequisites be rechecked, and unavailable items removed from the sidebar, when buildings are lost?
```

## Multi-Engineer

- Vinifera fixes `EngineerDamage` and `EngineerCaptureLevel` to be considered by the game, like they were in Tiberian Dawn and Red Alert.
Expand Down
2 changes: 2 additions & 0 deletions docs/Whats-New.md
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,8 @@ New:
- Implement support for a Saved Games subdirectory (by ZivDero)
- Allow customizing minimum damage per warhead (by ZivDero)
- Implement required and forbidden houses (by ZivDero)
- Allow turning off "sticky" technologies (by ZivDero)
- Allow disabling the ActLike check on construction yards to allow for faction-specific MCVs (by ZivDero)


Vanilla fixes:
Expand Down
9 changes: 5 additions & 4 deletions src/extensions/building/buildingext_hooks.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@

#include "hooker.h"
#include "hooker_macros.h"
#include "rulesext.h"


/**
Expand Down Expand Up @@ -124,7 +125,7 @@ void BuildingClassExt::_Update_Buildables()
case RTTI_AIRCRAFTTYPE:
for (int i = 0; i < AircraftTypes.Count(); i++)
{
if (PlayerPtr->Can_Build(AircraftTypes[i], false, true) && AircraftTypes[i]->Who_Can_Build_Me(true, false, false, PlayerPtr) != nullptr)
if (PlayerPtr->Can_Build(AircraftTypes[i], false, true) && AircraftTypes[i]->Who_Can_Build_Me(true, false, RuleExtension->IsRecheckPrerequisites, PlayerPtr) != nullptr)
{
Map.Add(RTTI_AIRCRAFTTYPE, i);
}
Expand All @@ -134,7 +135,7 @@ void BuildingClassExt::_Update_Buildables()
case RTTI_BUILDINGTYPE:
for (int i = 0; i < BuildingTypes.Count(); i++)
{
if (PlayerPtr->Can_Build(BuildingTypes[i], false, true) && BuildingTypes[i]->Who_Can_Build_Me(true, false, false, PlayerPtr) != nullptr)
if (PlayerPtr->Can_Build(BuildingTypes[i], false, true) && BuildingTypes[i]->Who_Can_Build_Me(true, false, RuleExtension->IsRecheckPrerequisites, PlayerPtr) != nullptr)
{
Map.Add(RTTI_BUILDINGTYPE, i);
}
Expand All @@ -144,7 +145,7 @@ void BuildingClassExt::_Update_Buildables()
case RTTI_INFANTRYTYPE:
for (int i = 0; i < InfantryTypes.Count(); i++)
{
if (PlayerPtr->Can_Build(InfantryTypes[i], false, true) && InfantryTypes[i]->Who_Can_Build_Me(true, false, false, PlayerPtr) != nullptr)
if (PlayerPtr->Can_Build(InfantryTypes[i], false, true) && InfantryTypes[i]->Who_Can_Build_Me(true, false, RuleExtension->IsRecheckPrerequisites, PlayerPtr) != nullptr)
{
Map.Add(RTTI_INFANTRYTYPE, i);
}
Expand All @@ -154,7 +155,7 @@ void BuildingClassExt::_Update_Buildables()
case RTTI_UNITTYPE:
for (int i = 0; i < UnitTypes.Count(); i++)
{
if (PlayerPtr->Can_Build(UnitTypes[i], false, true) && UnitTypes[i]->Who_Can_Build_Me(true, false, false, PlayerPtr) != nullptr)
if (PlayerPtr->Can_Build(UnitTypes[i], false, true) && UnitTypes[i]->Who_Can_Build_Me(true, false, RuleExtension->IsRecheckPrerequisites, PlayerPtr) != nullptr)
{
Map.Add(RTTI_UNITTYPE, i);
}
Expand Down
72 changes: 32 additions & 40 deletions src/extensions/factory/factoryext_hooks.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,13 @@
#include "asserthandler.h"
#include "extension_globals.h"
#include "factoryext_init.h"
#include "techno.h"
#include "technotype.h"

#include "hooker.h"
#include "hooker_macros.h"
#include "mouse.h"
#include "rulesext.h"
#include "sidebarext.h"
#include "techno.h"


/**
Expand Down Expand Up @@ -70,8 +70,9 @@ void FactoryClassExt::_Sanitize_Queue()
{
const TechnoClass* producing_object = Get_Object();

if (producing_object == nullptr)
if (producing_object == nullptr) {
return;
}

const TechnoTypeClass* producing_type = producing_object->Techno_Type_Class();
const RTTIType type = producing_type->Kind_Of();
Expand All @@ -80,14 +81,12 @@ void FactoryClassExt::_Sanitize_Queue()
bool need_update = false;

// Check the thing we're currently building
if (!House->Can_Build(producing_type, false, true))
{
if (!House->Can_Build(producing_type, false, true)) {
Abandon();
need_update = true;

// Cancel map placement
if (is_building && House == PlayerPtr)
{
if (is_building && House == PlayerPtr) {
Map.PendingObject = nullptr;
Map.PendingObjectPtr = nullptr;
Map.PendingHouse = HOUSE_NONE;
Expand All @@ -96,20 +95,18 @@ void FactoryClassExt::_Sanitize_Queue()
}

// Make sure there are no unavailable objects in the queue
for (int i = 0; i < QueuedObjects.Count(); i++)
{
if (!House->Can_Build(QueuedObjects[i], false, true))
{
for (int i = 0; i < QueuedObjects.Count(); i++) {
if (!House->Can_Build(QueuedObjects[i], false, true)) {
Remove_From_Queue(*QueuedObjects[i]);
need_update = true;
i--;
}
}

if (need_update)
{
if (House == PlayerPtr)
SidebarExtension->Get_Tab(type).Flag_To_Redraw();
if (need_update) {
if (House == PlayerPtr) {
SidebarExtension->Flag_Strip_To_Redraw(type);
}

House->Update_Factories(type);
Resume_Queue();
Expand All @@ -126,25 +123,24 @@ void FactoryClassExt::_Sanitize_Queue()
*/
bool FactoryClassExt::_Start(bool suspend)
{
if ((Object || SpecialItem) && IsSuspended && !Has_Completed())
{
if ((Object || SpecialItem) && IsSuspended && !Has_Completed()) {
const int time = Object ? Object->Time_To_Build() : 0;
int rate = time / STEP_COUNT;
rate = std::clamp(rate, 1, 255);

Set_Rate(rate);
IsSuspended = false;

if (House->Available_Money() >= Cost_Per_Tick())
{
if (House->Available_Money() >= Cost_Per_Tick()) {
IsPlayerSuspended = true;

if (suspend)
if (suspend) {
Suspend(true);
}

return true;
}
}

return false;
}

Expand All @@ -156,12 +152,15 @@ bool FactoryClassExt::_Start(bool suspend)
*/
void FactoryClassExt::_AI()
{
//_Sanitize_Queue();
/**
* If sticky techs are disabled, clear anything that's no longer available from the build queue.
*/
if (RuleExtension->IsRecheckPrerequisites) {
_Sanitize_Queue();
}

if (!IsSuspended && (Object != nullptr || SpecialItem))
{
if (!Has_Completed() && Graphic_Logic())
{
if (!IsSuspended && (Object != nullptr || SpecialItem)) {
if (!Has_Completed() && Graphic_Logic()) {
IsDifferent = true;

int cost = Cost_Per_Tick();
Expand All @@ -173,12 +172,10 @@ void FactoryClassExt::_AI()
** continue the countdown. The idea being that by the time the next
** production step occurs, there may be sufficient funds available.
*/
if (cost > House->Available_Money())
{
if (cost > House->Available_Money()) {
Set_Stage(Fetch_Stage() - 1);
}
else
{
else {
House->Spend_Money(cost);
Balance -= cost;
}
Expand All @@ -188,32 +185,28 @@ void FactoryClassExt::_AI()
*
* @author: CCHyper
*/
if (Vinifera_DeveloperMode)
{
if (Vinifera_DeveloperMode) {
/*
** If AIInstantBuild is toggled on, make sure this is a non-human AI house.
*/
if (Vinifera_Developer_AIInstantBuild
&& !House->Is_Human_Control() && House != PlayerPtr)
{
&& !House->Is_Human_Control() && House != PlayerPtr) {
Set_Stage(STEP_COUNT);
}

/*
** If InstantBuild is toggled on, make sure the local player is a human house.
*/
if (Vinifera_Developer_InstantBuild
&& House->Is_Human_Control() && House == PlayerPtr)
{
&& House->Is_Human_Control() && House == PlayerPtr) {
Set_Stage(STEP_COUNT);
}

/*
** If the AI has taken control of the player house, it needs a special
** case to handle the "player" instant build mode.
*/
if (Vinifera_Developer_InstantBuild)
{
if (Vinifera_Developer_InstantBuild) {
if (Vinifera_Developer_AIControl && House == PlayerPtr)
Set_Stage(STEP_COUNT);
}
Expand All @@ -223,8 +216,7 @@ void FactoryClassExt::_AI()
/*
** If the production has completed, then suspend further production.
*/
if (Fetch_Stage() == STEP_COUNT)
{
if (Fetch_Stage() == STEP_COUNT) {
IsSuspended = true;
Set_Rate(0);
House->Spend_Money(Balance);
Expand Down
24 changes: 23 additions & 1 deletion src/extensions/house/houseext_hooks.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@

#include "hooker.h"
#include "hooker_macros.h"
#include "rulesext.h"
#include "tibsun_functions.h"


Expand Down Expand Up @@ -941,6 +942,27 @@ DECLARE_PATCH(_Can_Build_Required_Forbidden_Houses_Patch)
}


/**
* Allow to skip the check for the MCV's ActLike.
*
* Author: ZivDero
*/
DECLARE_PATCH(_HouseClass_Can_Build_Multi_MCV_Patch)
{
GET_REGISTER_STATIC(BuildingClass*, building, esi);

if (RuleExtension->IsMultiMCV) {
JMP(0x004BC102);
}

static HousesType act_like;
act_like = building->ActLike;

_asm mov ecx, act_like
JMP(0x004BC0BD);
}


/**
* Main function for patching the hooks.
*/
Expand All @@ -959,12 +981,12 @@ void HouseClassExtension_Hooks()

Patch_Jump(0x004CB777, &_HouseClass_ShouldDisableCameo_BuildLimit_Fix);
Patch_Jump(0x004BC187, &_HouseClass_Can_Build_BuildLimit_Handle_Vehicle_Transform);

Patch_Jump(0x004CB6C1, &_HouseClass_Enable_SWs_Check_For_Building_Power);

Patch_Jump(0x004C10E0, &HouseClassExt::_AI_Building);
Patch_Jump(0x004C0630, &HouseClassExt::_Expert_AI);
Patch_Jump(0x004BBC74, &_Can_Build_Required_Forbidden_Houses_Patch);

Patch_Jump(0x004BAC2C, 0x004BAC39); // Patch a jump in the constructor to always allocate unit trackers
Patch_Jump(0x004BC0B7, &_HouseClass_Can_Build_Multi_MCV_Patch);
}
18 changes: 18 additions & 0 deletions src/extensions/objecttype/objecttypeext_hooks.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
#include "hooker.h"
#include "hooker_macros.h"
#include "miscutil.h"
#include "rulesext.h"


/**
Expand Down Expand Up @@ -222,6 +223,22 @@ void ObjectTypeClassExt::_Clear_Voxel_Indexes()
}


/**
* Allow to skip the check for the MCV's ActLike.
*
* Author: ZivDero
*/
DECLARE_PATCH(_ObjectTypeClass_Who_Can_Build_Me_Multi_MCV_Patch)
{
if (RuleExtension->IsMultiMCV) {
JMP(0x00587C0B);
}

_asm mov ecx, [esi+0x9C]
JMP_REG(edx, 0x00587C00);
}


/**
* Main function for patching the hooks.
*/
Expand All @@ -232,4 +249,5 @@ void ObjectTypeClassExtension_Hooks()
Patch_Jump(0x0058891D, &_ObjectTypeClass_Load_Theater_Art_Assign_Theater_Name_Theater_Patch);
Patch_Jump(0x00587C80, &ObjectTypeClassExt::_Fetch_Voxel_Image);
Patch_Jump(0x00589030, &ObjectTypeClassExt::_Clear_Voxel_Indexes);
Patch_Jump(0x00587BFA, &_ObjectTypeClass_Who_Can_Build_Me_Multi_MCV_Patch);
}
11 changes: 9 additions & 2 deletions src/extensions/rules/rulesext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,9 @@ RulesClassExtension::RulesClassExtension(const RulesClass *this_ptr) :
IsShowSuperWeaponTimers(true),
IceStrength(0),
WeedPipIndex(1),
MaxFreeRefineryDistanceBias(16)
MaxFreeRefineryDistanceBias(16),
IsRecheckPrerequisites(false),
IsMultiMCV(false)
{
//if (this_ptr) EXT_DEBUG_TRACE("RulesClassExtension::RulesClassExtension - 0x%08X\n", (uintptr_t)(ThisPtr));

Expand Down Expand Up @@ -214,6 +216,8 @@ void RulesClassExtension::Compute_CRC(WWCRCEngine &crc) const
crc(IsShowSuperWeaponTimers);
crc(IceStrength);
crc(MaxFreeRefineryDistanceBias);
crc(IsRecheckPrerequisites);
crc(IsMultiMCV);
}


Expand Down Expand Up @@ -605,12 +609,15 @@ bool RulesClassExtension::General(CCINIClass &ini)
* #issue-632
*
* "EngineerDamage" was incorrectly loaded with "EngineerCaptureLevel", so
* the value the value correctly.
* load the value correctly.
*
* @author: CCHyper
*/
This()->EngineerDamage = ini.Get_Float(GENERAL, "EngineerDamage", This()->EngineerDamage);

MaxFreeRefineryDistanceBias = ini.Get_Int(GENERAL, "MaxFreeRefineryDistanceBias", MaxFreeRefineryDistanceBias);
IsRecheckPrerequisites = ini.Get_Bool(GENERAL, "RecheckPrerequisites", IsRecheckPrerequisites);
IsMultiMCV = ini.Get_Bool(GENERAL, "MultiMCV", IsMultiMCV);

return true;
}
Expand Down
Loading

0 comments on commit 0a8e674

Please sign in to comment.