diff --git a/src/extensions/building/buildingext_hooks.cpp b/src/extensions/building/buildingext_hooks.cpp index 20443ddf..3cf4873a 100644 --- a/src/extensions/building/buildingext_hooks.cpp +++ b/src/extensions/building/buildingext_hooks.cpp @@ -35,6 +35,8 @@ #include "building.h" #include "buildingtype.h" #include "buildingtypeext.h" +#include "unit.h"; +#include "unitext.h" #include "technotype.h" #include "technotypeext.h" #include "house.h" @@ -464,6 +466,34 @@ DECLARE_PATCH(_BuildingClass_Draw_Spied_Cameo_Palette_Patch) } +/** + * #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; + + unitext = Extension::Fetch(unit); + unitext->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. */ @@ -481,4 +511,5 @@ 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(0x0042E5F5, &_BuildingClass_Grand_Opening_Assign_FreeUnit_LastDockedBuilding_Patch); } diff --git a/src/extensions/foot/footext_functions.cpp b/src/extensions/foot/footext_functions.cpp new file mode 100644 index 00000000..40968d2d --- /dev/null +++ b/src/extensions/foot/footext_functions.cpp @@ -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 . + * + ******************************************************************************/ +#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 = Extension::Fetch((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; +} \ No newline at end of file diff --git a/src/extensions/foot/footext_functions.h b/src/extensions/foot/footext_functions.h new file mode 100644 index 00000000..9985d582 --- /dev/null +++ b/src/extensions/foot/footext_functions.h @@ -0,0 +1,34 @@ +/******************************************************************************* +/* 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 . + * + ******************************************************************************/ +#pragma once + +#include "always.h" +#include "foot.h" +#include "tibsun_defines.h" + +Cell Vinifera_FootClass_Search_For_Tiberium(FootClass* this_ptr, int rad, bool a2); \ No newline at end of file diff --git a/src/extensions/foot/footext_hooks.cpp b/src/extensions/foot/footext_hooks.cpp index 7f477198..1b4bb926 100644 --- a/src/extensions/foot/footext_hooks.cpp +++ b/src/extensions/foot/footext_hooks.cpp @@ -26,10 +26,13 @@ * ******************************************************************************/ #include "footext_hooks.h" +#include "footext_functions.h" #include "foot.h" #include "technoext.h" #include "technotype.h" #include "technotypeext.h" +#include "unit.h" +#include "unittype.h" #include "extension.h" #include "fatal.h" #include "asserthandler.h" @@ -39,6 +42,33 @@ #include "hooker_macros.h" + /** + * A fake class for implementing new member functions which allow + * access to the "this" pointer of the intended class. + * + * @note: This must not contain a constructor or deconstructor! + * @note: All functions must be prefixed with "_" to prevent accidental virtualization. + */ +static class FootClassFake final : public FootClass +{ +public: + Cell _Search_For_Tiberium(int rad, bool a2); +}; + + +/** + * #issue-203 + * + * Enables smarter harvester tiberium-seeking algorithm. + * + * Author: Rampastring + */ +Cell FootClassFake::_Search_For_Tiberium(int rad, bool a2) +{ + return Vinifera_FootClass_Search_For_Tiberium(this, rad, a2); +} + + /** * #issue-593 * @@ -283,4 +313,5 @@ void FootClassExtension_Hooks() Patch_Jump(0x004A2BE7, &_FootClass_Mission_Guard_Area_Can_Passive_Acquire_Patch); Patch_Jump(0x004A1AAE, &_FootClass_Mission_Guard_Can_Passive_Acquire_Patch); Patch_Jump(0x004A102F, &_FootClass_Mission_Move_Can_Passive_Acquire_Patch); + Patch_Jump(0x004A76F0, &FootClassFake::_Search_For_Tiberium); } diff --git a/src/extensions/rules/rulesext.cpp b/src/extensions/rules/rulesext.cpp index 05220ebd..41a9269c 100644 --- a/src/extensions/rules/rulesext.cpp +++ b/src/extensions/rules/rulesext.cpp @@ -76,7 +76,8 @@ RulesClassExtension::RulesClassExtension(const RulesClass *this_ptr) : IsMPAutoDeployMCV(false), IsMPPrePlacedConYards(false), IsBuildOffAlly(true), - IsShowSuperWeaponTimers(true) + IsShowSuperWeaponTimers(true), + MaxFreeRefineryDistanceBias(16) { //if (this_ptr) EXT_DEBUG_TRACE("RulesClassExtension::RulesClassExtension - 0x%08X\n", (uintptr_t)(ThisPtr)); @@ -192,6 +193,7 @@ void RulesClassExtension::Compute_CRC(WWCRCEngine &crc) const crc(IsMPPrePlacedConYards); crc(IsBuildOffAlly); crc(IsShowSuperWeaponTimers); + crc(MaxFreeRefineryDistanceBias); } @@ -426,6 +428,7 @@ bool RulesClassExtension::General(CCINIClass &ini) * @author: CCHyper */ This()->EngineerDamage = ini.Get_Float(GENERAL, "EngineerDamage", This()->EngineerDamage); + MaxFreeRefineryDistanceBias = ini.Get_Int(GENERAL, "MaxFreeRefineryDistanceBias", MaxFreeRefineryDistanceBias); return true; } diff --git a/src/extensions/rules/rulesext.h b/src/extensions/rules/rulesext.h index 9b3b27a1..3bd1c64a 100644 --- a/src/extensions/rules/rulesext.h +++ b/src/extensions/rules/rulesext.h @@ -90,4 +90,11 @@ class RulesClassExtension final : public GlobalExtensionClass * on the tactical view? */ bool IsShowSuperWeaponTimers; + + /** + * When looking for refineries, harvesters will prefer a distant free + * refinery over a closer occupied refinery if the refineries' distance + * difference in cells is less than this. + */ + int MaxFreeRefineryDistanceBias; }; diff --git a/src/extensions/unit/unitext.cpp b/src/extensions/unit/unitext.cpp index 7e1621bd..19ba4d56 100644 --- a/src/extensions/unit/unitext.cpp +++ b/src/extensions/unit/unitext.cpp @@ -27,6 +27,8 @@ ******************************************************************************/ #include "unitext.h" #include "unit.h" +#include "building.h" +#include "vinifera_saveload.h" #include "wwcrc.h" #include "extension.h" #include "asserthandler.h" @@ -39,7 +41,8 @@ * @author: CCHyper */ UnitClassExtension::UnitClassExtension(const UnitClass *this_ptr) : - FootClassExtension(this_ptr) + FootClassExtension(this_ptr), + LastDockedBuilding(nullptr) { //if (this_ptr) EXT_DEBUG_TRACE("UnitClassExtension::UnitClassExtension - Name: %s (0x%08X)\n", Name(), (uintptr_t)(This())); @@ -107,6 +110,8 @@ HRESULT UnitClassExtension::Load(IStream *pStm) new (this) UnitClassExtension(NoInitClass()); + VINIFERA_SWIZZLE_REQUEST_POINTER_REMAP(LastDockedBuilding, "LastDockedBuilding"); + return hr; } @@ -150,6 +155,10 @@ int UnitClassExtension::Size_Of() const void UnitClassExtension::Detach(TARGET target, bool all) { //EXT_DEBUG_TRACE("UnitClassExtension::Detach - Name: %s (0x%08X)\n", Name(), (uintptr_t)(This())); + + if (LastDockedBuilding == target) { + LastDockedBuilding = nullptr; + } } @@ -161,4 +170,6 @@ void UnitClassExtension::Detach(TARGET target, bool all) void UnitClassExtension::Compute_CRC(WWCRCEngine &crc) const { //EXT_DEBUG_TRACE("UnitClassExtension::Compute_CRC - Name: %s (0x%08X)\n", Name(), (uintptr_t)(This())); + + crc(LastDockedBuilding != nullptr ? LastDockedBuilding->Fetch_ID() : 0); } diff --git a/src/extensions/unit/unitext.h b/src/extensions/unit/unitext.h index 4142ec10..42f76e39 100644 --- a/src/extensions/unit/unitext.h +++ b/src/extensions/unit/unitext.h @@ -29,6 +29,7 @@ #include "footext.h" #include "unit.h" +#include "building.h" class DECLSPEC_UUID(UUID_UNIT_EXTENSION) @@ -60,4 +61,12 @@ UnitClassExtension final : public FootClassExtension virtual RTTIType What_Am_I() const override { return RTTI_UNIT; } public: + /** + * #issue-203 + * + * The building that this unit last docked with. + * Used by harvesters for considering the distance to their last refinery + * when picking a tiberium cell to harvest from. + */ + BuildingClass *LastDockedBuilding; }; diff --git a/src/extensions/unit/unitext_functions.cpp b/src/extensions/unit/unitext_functions.cpp new file mode 100644 index 00000000..b618f3f4 --- /dev/null +++ b/src/extensions/unit/unitext_functions.cpp @@ -0,0 +1,81 @@ +/******************************************************************************* +/* O P E N S O U R C E -- V I N I F E R A ** +/******************************************************************************* + * + * @project Vinifera + * + * @file UNITEXT_FUNCTIONS.CPP + * + * @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 . + * + ******************************************************************************/ +#include "unitext_functions.h" +#include "unit.h" +#include "unittype.h" +#include "building.h" +#include "buildingtype.h" +#include "technotype.h" +#include "rulesext.h" +#include "tibsun_inline.h" +#include "vinifera_globals.h" +#include "tibsun_globals.h" +#include "tibsun_functions.h" +#include "debughandler.h" +#include "asserthandler.h" + + + /** + * Finds the nearest docking bay for a specific unit. + * + * @author: Rampastring + */ +void UnitClassExtension_Find_Nearest_Refinery(UnitClass* this_ptr, BuildingClass** building_addr, int* distance_addr, bool include_reserved) +{ + int nearest_refinery_distance = INT_MAX; + BuildingClass* nearest_refinery = nullptr; + + /** + * Find_Docking_Bay looks also through occupied docking bays if ScenarioInit is set + */ + if (include_reserved) { + ScenarioInit++; + } + + for (int i = 0; i < this_ptr->Class->Dock.Count(); i++) { + BuildingTypeClass* dockbuildingtype = this_ptr->Class->Dock[i]; + + BuildingClass* dockbuilding = this_ptr->Find_Docking_Bay(dockbuildingtype, false, false); + if (dockbuilding == nullptr) + continue; + + int distance = this_ptr->Distance(dockbuilding); + + if (distance < nearest_refinery_distance) { + nearest_refinery_distance = distance; + nearest_refinery = dockbuilding; + } + } + + if (include_reserved) { + ScenarioInit--; + } + + *building_addr = nearest_refinery; + *distance_addr = nearest_refinery_distance; +} \ No newline at end of file diff --git a/src/extensions/unit/unitext_functions.h b/src/extensions/unit/unitext_functions.h new file mode 100644 index 00000000..9afdd334 --- /dev/null +++ b/src/extensions/unit/unitext_functions.h @@ -0,0 +1,34 @@ +/******************************************************************************* +/* O P E N S O U R C E -- V I N I F E R A ** +/******************************************************************************* + * + * @project Vinifera + * + * @file UNITEXT_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 . + * + ******************************************************************************/ +#pragma once + +#include "always.h" +#include "building.h" +#include "unit.h" + +void UnitClassExtension_Find_Nearest_Refinery(UnitClass* this_ptr, BuildingClass** building_addr, int* distance_addr, bool include_reserved = false); \ No newline at end of file diff --git a/src/extensions/unit/unitext_hooks.cpp b/src/extensions/unit/unitext_hooks.cpp index 0e54d509..f6cff136 100644 --- a/src/extensions/unit/unitext_hooks.cpp +++ b/src/extensions/unit/unitext_hooks.cpp @@ -34,10 +34,13 @@ #include "technotype.h" #include "technotypeext.h" #include "unit.h" +#include "unitext.h" #include "unittype.h" #include "unittypeext.h" +#include "unitext_functions.h" #include "target.h" #include "rules.h" +#include "rulesext.h" #include "iomap.h" #include "voc.h" #include "extension.h" @@ -680,6 +683,132 @@ DECLARE_PATCH(_UnitClass_Per_Cell_Process_AutoHarvest_Assign_Harvest_Mission_Pat } +/** + * #issue-201 + * + * A "quality of life" patch for harvesters so they don't discriminate against dock + * buildings that are not the first on their Dock= list. Also makes harvesters + * smarter by making them prefer queuing for nearby occupied refineries instead + * of wandering to distant free refineries. + * + * @author: Rampastring + */ +DECLARE_PATCH(_UnitClass_Mission_Harvest_FINDHOME_Find_Nearest_Refinery_Patch) +{ + /** + * Enum for MISSION_HARVEST status constants. + */ + enum { + LOOKING, + HARVESTING, + FINDHOME, + HEADINGHOME, + GOINGTOIDLE, + }; + + + GET_REGISTER_STATIC(UnitClass*, harvester, esi); + static RadioMessageType response; + static UnitClassExtension* unitext; + static int free_refinery_distance_bias; + static BuildingClass* nearest_free_refinery; + static int nearest_free_refinery_distance; + static BuildingClass* nearest_possibly_occupied_refinery; + static int nearest_possibly_occupied_refinery_distance; + static bool reserve_free_refinery; + + /** + * Find the nearest refinery that is not occupied. + */ + UnitClassExtension_Find_Nearest_Refinery(harvester, &nearest_free_refinery, &nearest_free_refinery_distance); + + /** + * Find the nearest refinery, regardless of whether it's occupied. + */ + UnitClassExtension_Find_Nearest_Refinery(harvester, &nearest_possibly_occupied_refinery, &nearest_possibly_occupied_refinery_distance, true); + + reserve_free_refinery = true; + + if (nearest_free_refinery == nullptr) { + + /** + * There was no free refinery, check if there was an occupied one. + */ + if (nearest_possibly_occupied_refinery == nullptr) { + + /** + * No refinery existed at all! We have nothing to do here. + */ + goto set_mission_delay_and_return; + } + + /** + * There was an occupied refinery, queue for it instead. + */ + reserve_free_refinery = false; + } + else if (nearest_free_refinery != nearest_possibly_occupied_refinery) { + + /** + * There was a free refinery as well as an occupied one. + * Check if the occupied refinery is significantly closer to us than the free refinery. + */ + + free_refinery_distance_bias = RuleExtension->MaxFreeRefineryDistanceBias; + + if (nearest_free_refinery_distance > + nearest_possibly_occupied_refinery_distance + Cell_To_Lepton(free_refinery_distance_bias)) { + + reserve_free_refinery = false; + } + } + + unitext = Extension::Fetch(harvester); + + if (reserve_free_refinery) { + + /** + * We want to contact the free refinery, send a radio message to it. + */ + response = harvester->Transmit_Message(RADIO_HELLO, nearest_free_refinery); + + /** + * Check if the refinery answered as expected. If not, we'll queue for it instead. + */ + if (response == RADIO_ROGER) { + + /** + * The refinery accepted us! Change mission status to HEADINGHOME and jump to original code. + */ + harvester->Status = HEADINGHOME; + + unitext->LastDockedBuilding = nearest_free_refinery; + + goto set_mission_delay_and_return; + } + } + + + /** + * Re-use the original game's code for queueing to an occupied refinery. + * The game expects the occupied refinery pointer to be in edi. + */ +queue_to_occupied: + + unitext->LastDockedBuilding = nearest_possibly_occupied_refinery; + + _asm { mov edi, [nearest_possibly_occupied_refinery] }; + JMP(0x00654FAA); + + + /** + * Set mission delay and return from function. + */ +set_mission_delay_and_return: + JMP(0x00655226); +} + + /** * Main function for patching the hooks. */ @@ -699,6 +828,7 @@ void UnitClassExtension_Hooks() Patch_Jump(0x00653114, &_UnitClass_Draw_Shape_IdleRate_Patch); Patch_Jump(0x00656623, &_UnitClass_What_Action_ACTION_HARVEST_Block_On_Bridge_Patch); // IsToHarvest Patch_Jump(0x0065665D, &_UnitClass_What_Action_ACTION_HARVEST_Block_On_Bridge_Patch); // IsToVeinHarvest + Patch_Jump(0x00654EEE, &_UnitClass_Mission_Harvest_FINDHOME_Find_Nearest_Refinery_Patch); //Patch_Jump(0x0065054F, &_UnitClass_Enter_Idle_Mode_Block_Harvesting_On_Bridge_Patch); // Removed, keeping code for reference. //Patch_Jump(0x00654AB0, &_UnitClass_Mission_Harvest_Block_Harvesting_On_Bridge_Patch); // Removed, keeping code for reference. }