Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
… and Vinifera-Developers#203

Implements smarter harvester refinery-seeking algorithm. Vinifera-Developers#201

Implements harvester refinery queue jumping. Vinifera-Developers#202

Adds patch to make harvesters consider distance to refinery when selecting tiberium patch to harvest. Vinifera-Developers#203
  • Loading branch information
Rampastring committed Apr 24, 2022
1 parent 556adc2 commit 06decb6
Show file tree
Hide file tree
Showing 11 changed files with 732 additions and 2 deletions.
166 changes: 166 additions & 0 deletions src/extensions/building/buildingext_hooks.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,17 @@
#include "buildingtypeext.h"
#include "technotype.h"
#include "technotypeext.h"
#include "unit.h"
#include "unitext.h"
#include "house.h"
#include "housetype.h"
#include "cargo.h"
#include "bsurface.h"
#include "dsurface.h"
#include "convert.h"
#include "drawshape.h"
#include "rules.h"
#include "rulesext.h"
#include "voc.h"
#include "iomap.h"
#include "spritecollection.h"
Expand Down Expand Up @@ -476,6 +480,165 @@ DECLARE_PATCH(_BuildingClass_Draw_Spied_Cameo_Palette_Patch)
}


/**
* #issue-202
*
* For harvester queue jumping.
* Don't return RADIO_NEGATIVE if a refinery is already in radio contact
* with a unit that is different from the one that sent us the message.
*
* @author: Rampastring
*/
DECLARE_PATCH(_BuildingClass_Receive_Message_Harvester_Queue_Jump_No_Ignoring_If_In_Contact_Patch)
{
GET_REGISTER_STATIC(BuildingClass*, building, esi);

if (building->Class->IsRefinery) {
goto skip_returning_negative;
}

/**
* We're not a refinery, don't skip the check.
*/
goto original_behaviour;


/**
* Skip the (!ScenarioInit && In_Radio_Contact()) and following (Contact_With_Whom() != from)
* condition and related code for the refinery and continue function
* execution from beyond that point.
*/
skip_returning_negative:
JMP(0x0042697B);


/**
* Stolen bytes / code, perform the check.
*/
original_behaviour:
if (ScenarioInit) {
JMP(0x00426962);
}

JMP(0x00426953);
}


/**
* #issue-202
*
* For harvester queue jumping.
* If we're a refinery and the harvester contacting us is closer to us than a
* potentially existing harvester heading towards us, allow the new harvester
* to take priority and tell the old harvester to go do something else.
*
* @author: Rampastring
*/
DECLARE_PATCH(_BuildingClass_Receive_Message_Harvester_Queue_Jump_Allow_Replacing_Old_Harv_If_Closer_Patch)
{
GET_REGISTER_STATIC(BuildingClass*, building, esi);
GET_REGISTER_STATIC(TechnoClass*, message_sender, edi);
static int queue_jump_distance;
static int old_harvester_distance;
static int new_harvester_distance;
static TechnoClass* old_harvester;

if (ScenarioInit) {
/**
* Stolen bytes / code, return RADIO_ROGER if ScenarioInit is set.
*/
return_roger_scenarioinit:
JMP(0x0042707B);
}


if (building->Cargo.Is_Something_Attached()) {

/**
* We're not free (perhaps a harvester is unloading), return RADIO_STATIC.
*/
return_static:
_asm { mov eax, [RADIO_STATIC] }
JMP_REG(ecx, 0x004271CA);
}

/**
* Check if we're already in radio contact with another harvester
* than the one that sent us the message.
* If not, we can simply accept the new harvester.
*/
old_harvester = building->Contact_With_Whom();

if (old_harvester != nullptr && old_harvester != message_sender) {

/**
* Get distance to the old harvester and distance
* to the new harvester and compare the distances.
*/
old_harvester_distance = building->Distance(old_harvester);
new_harvester_distance = building->Distance(message_sender);

queue_jump_distance = 7;
if (RulesExtension) {
queue_jump_distance = RulesExtension->MinHarvesterQueueJumpDistance;
}

if (new_harvester_distance + Cell_To_Lepton(queue_jump_distance) > old_harvester_distance) {

/**
* The new harvester is not significantly closer to us than the old one,
* exit the function and return RADIO_NEGATIVE.
*/
return_negative:
JMP(0x0042696C);
}

/**
* The harvester that sent us the message is significantly closer to us than
* our old assigned harvester. Tell the old harvester to figure out a new
* purpose for their life and accept the new harvester by returning RADIO_ROGER.
*/
building->Transmit_Message(RADIO_OVER_OUT, old_harvester);
}

/**
* Exits the function and returns RADIO_ROGER.
*/
return_roger:
JMP(0x004269BD);
}


/**
* #issue-203
*
* Assigns the last docked building of a spawned free unit on
* building placement complete (the "grand opening").
* This allows harvesters to know which refinery they spawned from.
*
* @author: Rampastring
*/
DECLARE_PATCH(_BuildingClass_Grand_Opening_Assign_FreeUnit_LastDockedBuilding_Patch)
{
GET_REGISTER_STATIC(BuildingClass*, this_ptr, esi);
GET_REGISTER_STATIC(UnitClass*, unit, edi);
static UnitClassExtension* unitext_ptr;

unitext_ptr = UnitClassExtensions.find(unit);
if (unitext_ptr) {
unitext_ptr->LastDockedBuilding = this_ptr;
}


/**
* Continue the FreeUnit down-placing process.
*/
original_code:
_asm { movsx eax, bp }
_asm { movsx ecx, bx }
JMP_REG(edx, 0x0042E5FB);
}

/**
* Main function for patching the hooks.
*/
Expand All @@ -493,4 +656,7 @@ void BuildingClassExtension_Hooks()
Patch_Jump(0x00429A96, &_BuildingClass_AI_ProduceCash_Patch);
Patch_Jump(0x0042F67D, &_BuildingClass_Captured_ProduceCash_Patch);
Patch_Jump(0x0042E179, &_BuildingClass_Grand_Opening_ProduceCash_Patch);
Patch_Jump(0x0042694A, &_BuildingClass_Receive_Message_Harvester_Queue_Jump_No_Ignoring_If_In_Contact_Patch);
Patch_Jump(0x00426A71, &_BuildingClass_Receive_Message_Harvester_Queue_Jump_Allow_Replacing_Old_Harv_If_Closer_Patch);
Patch_Jump(0x0042E5F5, &_BuildingClass_Grand_Opening_Assign_FreeUnit_LastDockedBuilding_Patch);
}
149 changes: 149 additions & 0 deletions src/extensions/foot/footext_functions.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
/*******************************************************************************
/* O P E N S O U R C E -- V I N I F E R A **
/*******************************************************************************
*
* @project Vinifera
*
* @file FOOTEXT_FUNCTIONS.CPP
*
* @author Rampastring
*
* @brief Contains the supporting functions for the extended FootClass.
*
* @license Vinifera is free software: you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version
* 3 of the License, or (at your option) any later version.
*
* Vinifera is distributed in the hope that it will be
* useful, but WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program.
* If not, see <http://www.gnu.org/licenses/>.
*
******************************************************************************/
#include "footext_functions.h"
#include "unitext_functions.h"
#include "unit.h"
#include "unittype.h"
#include "unitext.h"
#include "building.h"
#include "buildingtype.h"
#include "cell.h"
#include "map.h"
#include "mouse.h"
#include "house.h"
#include "technotype.h"
#include "rulesext.h"
#include "session.h"
#include "tibsun_inline.h"
#include "vinifera_globals.h"
#include "tibsun_globals.h"
#include "tibsun_functions.h"
#include "tibsun_defines.h"
#include "debughandler.h"
#include "asserthandler.h"



void _Vinifera_FootClass_Search_For_Tiberium_Check_Tiberium_Value_Of_Cell(FootClass* this_ptr, Cell& cell_coords, Cell* besttiberiumcell, int* besttiberiumvalue, UnitClassExtension* unitext)
{
if (this_ptr->Tiberium_Check(cell_coords)) {

CellClass* cell = &Map[cell_coords];
int tiberiumvalue = cell->Get_Tiberium_Value();

/**
* #issue-203
*
* Consider distance to refinery when selecting the next tiberium patch to harvest.
* Prefer the most resourceful tiberium patch, but if there's a tie, prefer one that's
* closer to our refinery.
*
* @author: Rampastring
*/
if (unitext && unitext->LastDockedBuilding && unitext->LastDockedBuilding->IsActive && !unitext->LastDockedBuilding->IsInLimbo) {
tiberiumvalue *= 100;
tiberiumvalue -= ::Distance(cell_coords, unitext->LastDockedBuilding->Get_Cell());
}

if (tiberiumvalue > * besttiberiumvalue)
{
*besttiberiumvalue = tiberiumvalue;
*besttiberiumcell = cell_coords;
}
}
}


/**
* Smarter replacement for the Search_For_Tiberium method.
* Makes harvesters consider the distance to their refinery when
* looking for the cell of tiberium to harvest.
*
* @author: Rampastring
*/
Cell Vinifera_FootClass_Search_For_Tiberium(FootClass* this_ptr, int rad, bool a2)
{
if (!this_ptr->Owning_House()->Is_Human_Control() &&
this_ptr->What_Am_I() == RTTI_UNIT &&
((UnitClass*)this_ptr)->Class->IsToHarvest &&
a2 &&
Session.Type != GAME_NORMAL)
{
/**
* Use weighted tiberium-seeking algorithm for AI in multiplayer.
*/

return this_ptr->Search_For_Tiberium_Weighted(rad);
}

Coordinate center_coord = this_ptr->Center_Coord();
Cell cell_coords = Coord_Cell(center_coord);
Cell unit_cell_coords = cell_coords;

if (Map[unit_cell_coords].Land_Type() == LAND_TIBERIUM) {

/**
* If we're already standing on tiberium, then we don't need to move anywhere.
*/

return unit_cell_coords;
}

int besttiberiumvalue = -1;
Cell besttiberiumcell = Cell(0, 0);

UnitClassExtension* unitext = nullptr;
if (this_ptr->What_Am_I() == RTTI_UNIT) {
unitext = UnitClassExtensions.find((UnitClass*)this_ptr);
}

/**
* Perform a ring search outward from the center.
*/
for (int radius = 1; radius < rad; radius++) {
for (int x = -radius; x <= radius; x++) {

cell_coords = Cell(unit_cell_coords.X + x, unit_cell_coords.Y - radius);
_Vinifera_FootClass_Search_For_Tiberium_Check_Tiberium_Value_Of_Cell(this_ptr, cell_coords, &besttiberiumcell, &besttiberiumvalue, unitext);

cell_coords = Cell(unit_cell_coords.X + x, unit_cell_coords.Y + radius);
_Vinifera_FootClass_Search_For_Tiberium_Check_Tiberium_Value_Of_Cell(this_ptr, cell_coords, &besttiberiumcell, &besttiberiumvalue, unitext);

cell_coords = Cell(unit_cell_coords.X - radius, unit_cell_coords.Y + x);
_Vinifera_FootClass_Search_For_Tiberium_Check_Tiberium_Value_Of_Cell(this_ptr, cell_coords, &besttiberiumcell, &besttiberiumvalue, unitext);

cell_coords = Cell(unit_cell_coords.X + radius, unit_cell_coords.Y + x);
_Vinifera_FootClass_Search_For_Tiberium_Check_Tiberium_Value_Of_Cell(this_ptr, cell_coords, &besttiberiumcell, &besttiberiumvalue, unitext);
}

if (besttiberiumvalue != -1)
break;
}

return besttiberiumcell;
}
36 changes: 36 additions & 0 deletions src/extensions/foot/footext_functions.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*******************************************************************************
/* O P E N S O U R C E -- V I N I F E R A **
/*******************************************************************************
*
* @project Vinifera
*
* @file FOOTEXT_FUNCTIONS.H
*
* @author Rampastring
*
* @brief Contains the supporting functions for the extended UnitClass.
*
* @license Vinifera is free software: you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version
* 3 of the License, or (at your option) any later version.
*
* Vinifera is distributed in the hope that it will be
* useful, but WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program.
* If not, see <http://www.gnu.org/licenses/>.
*
******************************************************************************/
#pragma once

#include "always.h"
#include "foot.h"
#include "tibsun_defines.h"

class FootClass;

Cell Vinifera_FootClass_Search_For_Tiberium(FootClass* this_ptr, int rad, bool a2);
Loading

0 comments on commit 06decb6

Please sign in to comment.