forked from BoomerangAide/GPTP-For-VS2008
[Source] Smart Casting Interface
KYSXD edited this page Oct 1, 2016
·
10 revisions
In StarCraft II, when multiple units are given the same spell order, only one of them will comply, preventing a serious waste of spellcaster energy.
This source tries to implement the same behaviour with GPTP.
Find all the casters, get best target (and set the correct point target) and, for the casters, continue the last order.
hooks/game_hooks.cpp
#include "game_hooks.h"
#include <graphics/graphics.h>
#include <SCBW/api.h>
#include <SCBW/scbwdata.h>
#include <SCBW/ExtendSightLimit.h>
#include "psi_field.h"
#include <cstdio>
//Here we store our helpers for smart casting
namespace smartCasting {
//Here we store the last order for each unit,
//to do not mess with the members of the CUnit structure
static COrder lastOrderArray[UNIT_ARRAY_LENGTH + 1];
const int ORDER_TYPE_COUNT = ((int)(0xBD) + 1);
//Stores the pointer for the next CUnit (like a linked list)
static CUnit *custom_unit_array[UNIT_ARRAY_LENGTH + 1];
//First and last node of the casters lists
static CUnit *firstCaster[ORDER_TYPE_COUNT][PLAYABLE_PLAYER_COUNT];
static CUnit *lastCaster[ORDER_TYPE_COUNT][PLAYABLE_PLAYER_COUNT];
bool isOrderValidForSmartcasting(u16 orderId) {
switch(orderId) {
case OrderId::WarpingArchon:
case OrderId::FireYamatoGun1:
case OrderId::MagnaPulse:
case OrderId::DarkSwarm:
case OrderId::CastParasite:
case OrderId::SummonBroodlings:
case OrderId::EmpShockwave:
case OrderId::NukePaint:
case OrderId::PlaceMine:
case OrderId::DefensiveMatrix:
case OrderId::PsiStorm:
case OrderId::Irradiate:
case OrderId::Plague:
case OrderId::Consume:
case OrderId::Ensnare:
case OrderId::StasisField:
case OrderId::Hallucianation1:
case OrderId::Restoration:
case OrderId::CastDisruptionWeb:
case OrderId::CastMindControl:
case OrderId::WarpingDarkArchon:
case OrderId::CastFeedback:
case OrderId::CastOpticalFlare:
case OrderId::CastMaelstrom:
return true;
break;
default:
return false;
break;
}
}
//Adds a node to the order's stack
void addToOrderList(CUnit *unit)
{
int currentOrder = unit->mainOrderId;
int currentPlayer = unit->playerId;
int index;
if(!firstCaster[currentOrder][currentPlayer])
{
firstCaster[currentOrder][currentPlayer] = unit;
lastCaster[currentOrder][currentPlayer] = unit;
index = lastCaster[currentOrder][currentPlayer]->getIndex();
custom_unit_array[index] = NULL;
}
else
{
index = lastCaster[currentOrder][currentPlayer]->getIndex();
custom_unit_array[index] = unit;
lastCaster[currentOrder][currentPlayer] = unit;
index = lastCaster[currentOrder][currentPlayer]->getIndex();
custom_unit_array[index] = NULL;
}
}
//Get a COrder pointer with a custom order
COrder *createOrder(u16 orderId = OrderId::Nothing2, u16 unitId = 0, CUnit *target = NULL, u16 posX = 0, u16 posY = 0){
static COrder thisOrder;
thisOrder.prev = NULL;
thisOrder.next = NULL;
thisOrder.orderId = orderId;
thisOrder.unitId = unitId;
thisOrder.target.unit = target;
thisOrder.target.pt.x = posX;
thisOrder.target.pt.y = posY;
return &thisOrder;
}
//Get a COrder pointer with the current order
inline COrder *getCurrentOrder(CUnit *unit) {
return createOrder((u16)unit->mainOrderId,
unit->buildQueue[unit->buildQueueSlot],
unit->orderTarget.unit,
unit->orderTarget.pt.x,
unit->orderTarget.pt.y);
}
//Get a COrder pointer with the last order
inline COrder *getLastOrder(CUnit *unit) {
int index = unit->getIndex();
COrder *unitLastOrder = &lastOrderArray[index];
return createOrder(unitLastOrder->orderId,
unitLastOrder->unitId,
unitLastOrder->target.unit,
unitLastOrder->target.pt.x,
unitLastOrder->target.pt.y);
}
//Stores all variables of COrder in the unit using unused members of CUnit
inline void saveAsLastOrder(CUnit *unit, COrder *lastOrder = NULL) {
if(lastOrder) {
int index = unit->getIndex();
COrder *unitLastOrder = &lastOrderArray[index];
unitLastOrder->orderId = (u8)lastOrder->orderId;
unitLastOrder->unitId = lastOrder->unitId;
unitLastOrder->target = lastOrder->target;
}
else {
saveAsLastOrder(unit, getCurrentOrder(unit));
}
}
//Stores all variables of COrder in the variables of the current order
inline void saveAsMainOrder(CUnit *unit, COrder *mainOrder) {
if(mainOrder) {
unit->mainOrderId = (u8)mainOrder->orderId;
unit->buildQueue[unit->buildQueueSlot] = mainOrder->unitId;
unit->orderTarget.unit = mainOrder->target.unit;
unit->orderTarget.pt.x = mainOrder->target.pt.x;
unit->orderTarget.pt.y = mainOrder->target.pt.y;
}
}
//This function does three things:
//1) resets the variables of the current order
//2) issues the COrder newOrder
//3) stores the newOrder's variables in the unit's last order variables
inline void setOrderInUnit(CUnit *unit, COrder *newOrder = NULL) {
if(newOrder) {
//If the order is the same, just change the current targets
if(newOrder->orderId == unit->mainOrderId) {
saveAsMainOrder(unit, newOrder);
}
//If the order is the different, clean and queue...
else {
saveAsMainOrder(unit, createOrder());
unit->order((u8)newOrder->orderId, newOrder->target.pt.x, newOrder->target.pt.y, newOrder->target.unit, newOrder->unitId, true);
}
//Save the variables
saveAsLastOrder(unit, newOrder);
}
//If no order... do the same with a Nothing2 order
else {
setOrderInUnit(unit, createOrder());
}
}
//This function checks three things:
//1) If the user has interacted with the unit
//2) if the unit's mainOrderId is the same as the asked orderId
//3) If the last order is different from the asked orderId or is allowed to overrun orders
inline bool isCasterValidForOrder(CUnit *unit, u8 orderId) {
if(unit
&& unit->userActionFlags == 2
&& unit->mainOrderId == orderId) {
return true;
}
return false;
}
//Checks if the new order is different from the last
inline bool isOverruningLastOrder(CUnit *unit, u8 orderId) {
if(getLastOrder(unit)->orderId == orderId) {
return true;
}
return false;
}
//Checks of the orderId requires two units (warp archon)
inline bool isCouplesOrder(u8 orderId) {
switch(orderId) {
case OrderId::WarpingArchon:
case OrderId::WarpingDarkArchon:
return true;
break;
default:
break;
}
return false;
}
//Checks if two units are partners in a 2-units order
inline bool isPartnerInOrder(CUnit *unit1, CUnit *unit2, u8 orderId) {
if(unit1 && unit2 && isCouplesOrder(orderId)
&& unit1->orderTarget.unit == unit2
&& unit2->orderTarget.unit == unit1) {
return true;
}
else return false;
}
void setCasters() {
for(CUnit *caster = *firstVisibleUnit; caster; caster = caster->link.next) {
if(isOrderValidForSmartcasting(caster->mainOrderId)) {
addToOrderList(caster);
}
}
}
//Returns the best caster running orderId for player playerId
CUnit **getBestCasters(u8 orderId) {
static CUnit *bestCasterClean[8];
static CUnit *bestCasterOverrun[8];
static CUnit *bestCasterArray[8];
for(int i = 0; i < 8; i++) {
bestCasterClean[i] = NULL;
bestCasterOverrun[i] = NULL;
for(CUnit *caster = firstCaster[orderId][i]; caster; caster = custom_unit_array[caster->getIndex()]) {
if(isCasterValidForOrder(caster, orderId)) {
u8 p_id = caster->playerId;
//If is clean order (different than the last one), we have a winner
if(!isOverruningLastOrder(caster, orderId)
&& (!bestCasterClean[p_id]
|| bestCasterClean[p_id]->energy < caster->energy)) {
bestCasterClean[p_id] = caster;
}
//Store in case we don't have a winner (clean caster)
//We're going to need this if we can't find a new caster
if(!bestCasterOverrun[p_id]
|| bestCasterOverrun[p_id]->energy < caster->energy) {
bestCasterOverrun[p_id] = caster;
}
}
}
}
//For each player, store the caster
for(int i = 0; i < 8; i++) {
bestCasterArray[i] = (bestCasterClean[i] == NULL ? bestCasterOverrun[i] : bestCasterClean[i]);
}
return bestCasterArray;
}
//Issued the last order to unit
//If the last order was completed, orders to idle
inline void tryLastOrder(CUnit *unit) {
if(unit->orderSignal == 2) {
setOrderInUnit(unit);
unit->orderSignal = 0;
}
else
setOrderInUnit(unit, getLastOrder(unit));
}
//Smartcast behaviour
inline void smartCastOrder(u8 orderId) {
CUnit **bestCasterArray = getBestCasters(orderId);
long int target_x[8];
long int target_y[8];
u16 totalCasters[8];
//Set if we can find at leat one unit new-casting the order
bool atLeastOneCaster = false;
for(int i = 0; i < 8; i++){
if(bestCasterArray[i]) {
bestCasterArray[i]->userActionFlags = 0;
atLeastOneCaster = true;
}
target_x[i] = 0;
target_y[i] = 0;
totalCasters[i] = 0;
}
if(atLeastOneCaster) {
//For each player
for(int i = 0; i < PLAYABLE_PLAYER_COUNT; i++)
{
//Order other units to continue with the lasts orders
for(CUnit *caster = firstCaster[orderId][i]; caster; caster = custom_unit_array[caster->getIndex()]) {
if(caster->userActionFlags == 2
&& caster->mainOrderId == orderId) {
target_x[caster->playerId] += caster->orderTarget.pt.x;
target_y[caster->playerId] += caster->orderTarget.pt.y;
totalCasters[caster->playerId]++;
//Checks if the unit is the partner of the current best
//This is going to help (eventually) with smart casting archon warp and dark archon meld
if(!isPartnerInOrder(bestCasterArray[caster->playerId], caster, orderId)) {
tryLastOrder(caster);
}
//Unset the user flags (To avoid mistakes in the next frame)
caster->userActionFlags = 0;
}
}
firstCaster[orderId][i] = NULL;
}
//Fix the target point for ground spells
//Ground spells like psi storm, ensnare, lockdown
for(int j = 0; j < 8; j++) {
if(bestCasterArray[j]
&& !bestCasterArray[j]->orderTarget.unit
&& totalCasters[j]) {
target_x[j] /= totalCasters[j];
target_y[j] /= totalCasters[j];
bestCasterArray[j]->orderTarget.pt.x = (u16)target_x[j];
bestCasterArray[j]->orderTarget.pt.y = (u16)target_y[j];
}
}
}
}
//Sets flags and saves last order
inline void prepareUnitsForNextFrame() {
for(CUnit *unit = *firstVisibleUnit; unit; unit = unit->link.next) {
//Stores the last order for smartCast
saveAsLastOrder(unit);
}
}
//Runs smartcast for each order and prepares for next frame
//Archon's orders doesn't work now:
//Archon Merge and Dark Archon Meld buttons doesn't set userActionFlags on the unit
inline void runSmartCast() {
setCasters();
for(int i = 0; i < ORDER_TYPE_COUNT; i++)
{
if(isOrderValidForSmartcasting(i))
{
smartCastOrder(i);
}
}
prepareUnitsForNextFrame();
}
} //namespace smartCasting
namespace hooks {
bool nextFrame() {
if (!scbw::isGamePaused()) { //If the game is not paused
scbw::setInGameLoopState(true); //Needed for scbw::random() to work
graphics::resetAllGraphics();
hooks::updatePsiFieldProviders();
//Runs smart casting!!
smartCasting::runSmarCast();
scbw::setInGameLoopState(false);
}
return true;
}
bool gameOn() {
return true;
}
bool gameEnd() {
return true;
}
} //hooks