Skip to content

Commit

Permalink
Add ai_check_drugs fixes and improvements (#29)
Browse files Browse the repository at this point in the history
  • Loading branch information
alexbatalov committed Dec 27, 2022
1 parent 4b137da commit 0e11569
Showing 1 changed file with 126 additions and 42 deletions.
168 changes: 126 additions & 42 deletions src/combat_ai.cc
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,16 @@ namespace fallout {

#define AI_MESSAGE_SIZE 260

static constexpr int kChemUseStimsWhenHurtLittleHpRatio = 60;
static constexpr int kChemUseStimsWhenHurtLotsHpRatio = 30;
static constexpr int kChemUseStimsHpRatio = 50;

static constexpr int kChemUseSometimesChance = 25;
static constexpr int kChemUseAnytimeChance = 75;
static constexpr int kChemUseAlwaysChance = 100;

static constexpr int kRandomDrugPickingArraySize = 3;

typedef struct AiMessageRange {
int start;
int end;
Expand Down Expand Up @@ -934,31 +944,29 @@ static int _ai_check_drugs(Object* critter)
return 0;
}

int hpRatio = 50;
int hpRatio = kChemUseStimsHpRatio;
int chemUseChance = 0;
switch (ai->chem_use) {
case CHEM_USE_CLEAN:
return 0;
case CHEM_USE_STIMS_WHEN_HURT_LITTLE:
hpRatio = 60;
hpRatio = kChemUseStimsWhenHurtLittleHpRatio;
break;
case CHEM_USE_STIMS_WHEN_HURT_LOTS:
hpRatio = 30;
hpRatio = kChemUseStimsWhenHurtLotsHpRatio;
break;
case CHEM_USE_SOMETIMES:
if ((_combatNumTurns % 3) == 0) {
chemUseChance = 25;
chemUseChance = kChemUseSometimesChance;
}
hpRatio = 50;
break;
case CHEM_USE_ANYTIME:
if ((_combatNumTurns % 3) == 0) {
chemUseChance = 75;
chemUseChance = kChemUseAnytimeChance;
}
hpRatio = 50;
break;
case CHEM_USE_ALWAYS:
chemUseChance = 100;
chemUseChance = kChemUseAlwaysChance;
break;
}

Expand Down Expand Up @@ -996,48 +1004,102 @@ static int _ai_check_drugs(Object* critter)

if (!drugUsed) {
if (chemUseChance > 0 && randomBetween(0, 100) < chemUseChance) {
while (critter->data.critter.combat.ap >= 2) {
// CE: Slightly improve and randomize drug picking.
inventoryItemIndex = -1;
searchCompleted = false;

Object* primaryDrugs[kRandomDrugPickingArraySize];
int primaryDrugsCount = 0;

Object* secondaryDrugs[kRandomDrugPickingArraySize];
int secondaryDrugsCount = 0;

// Collect drugs into buckets - primary desires and everything
// else.
//
// Strictly speaking NPCs can have more drugs than buckets can
// store. Together with `CHEM_USE_ALWAYS` it can lead to
// unexpected behaviour. In vanilla NPCs will eat drugs on their
// turns while they have action points. With this implementation
// NPCs will eat at most 2x `kRandomDrugPickingArraySize` drugs
// every turn (exhausting both primary and secondary buckets)
// and then continue to other activities (assuming they have
// action points to do so). In practice this sitation is very
// rare if even exists in vanilla or popular mods.
//
// The alternative is to use a pair of `std::vector`s and let
// them manage the memory.
while (true) {
Object* drug = _inven_find_type(critter, ITEM_TYPE_DRUG, &inventoryItemIndex);
if (drug == NULL) {
searchCompleted = true;
break;
}

int drugPid = drug->pid;
int index;
for (index = 0; index < AI_PACKET_CHEM_PRIMARY_DESIRE_COUNT; index++) {
// TODO: Find out why it checks for inequality at 0x4286B1.
if (ai->chem_primary_desire[index] != drugPid) {
break;
if (!itemIsHealing(drug->pid)) {
bool isPrimary = false;
for (int index = 0; index < AI_PACKET_CHEM_PRIMARY_DESIRE_COUNT; index++) {
if (ai->chem_primary_desire[index] == drug->pid) {
isPrimary = true;
break;
}
}
}

if (index < AI_PACKET_CHEM_PRIMARY_DESIRE_COUNT) {
if (!itemIsHealing(drugPid)) {
if (itemRemove(critter, drug, 1) == 0) {
if (_item_d_take_drug(critter, drug) == -1) {
itemAdd(critter, drug, 1);
} else {
_ai_magic_hands(critter, drug, 5000);
_obj_connect(drug, critter->tile, critter->elevation, NULL);
_obj_destroy(drug);
drugUsed = true;
drugCount += 1;
}

if (critter->data.critter.combat.ap < 2) {
critter->data.critter.combat.ap = 0;
} else {
critter->data.critter.combat.ap -= 2;
}

if (ai->chem_use == CHEM_USE_SOMETIMES || (ai->chem_use == CHEM_USE_ANYTIME && drugCount >= 2)) {
break;
}
if (isPrimary) {
if (primaryDrugsCount < kRandomDrugPickingArraySize) {
primaryDrugs[primaryDrugsCount++] = drug;
}
} else {
if (secondaryDrugsCount < kRandomDrugPickingArraySize) {
secondaryDrugs[secondaryDrugsCount++] = drug;
}
}
}
}

// Consume drugs one-by-one in random order with primary desires
// first.
while (critter->data.critter.combat.ap >= 2) {
Object** availableDrugs;
int* availableDrugsCountPtr;

if (primaryDrugsCount > 0) {
availableDrugs = primaryDrugs;
availableDrugsCountPtr = &primaryDrugsCount;
} else if (secondaryDrugsCount > 0) {
availableDrugs = secondaryDrugs;
availableDrugsCountPtr = &secondaryDrugsCount;
} else {
break;
}

int index = randomBetween(0, *availableDrugsCountPtr - 1);
Object* drug = availableDrugs[index];
availableDrugs[index] = availableDrugs[*availableDrugsCountPtr - 1];
*availableDrugsCountPtr -= 1;

if (itemRemove(critter, drug, 1) == 0) {
if (_item_d_take_drug(critter, drug) == -1) {
itemAdd(critter, drug, 1);
} else {
_ai_magic_hands(critter, drug, 5000);
_obj_connect(drug, critter->tile, critter->elevation, NULL);
_obj_destroy(drug);
drugUsed = true;
drugCount += 1;
}

if (critter->data.critter.combat.ap < 2) {
critter->data.critter.combat.ap = 0;
} else {
critter->data.critter.combat.ap -= 2;
}

if (ai->chem_use == CHEM_USE_SOMETIMES || (ai->chem_use == CHEM_USE_ANYTIME && drugCount >= 2)) {
break;
}
}
}
}
}
}
Expand Down Expand Up @@ -1991,10 +2053,32 @@ static bool aiCanUseItem(Object* critter, Object* item)
return false;
}

int itemPid = item->pid;
if (itemPid != PROTO_ID_STIMPACK
&& itemPid != PROTO_ID_SUPER_STIMPACK
&& itemPid != PROTO_ID_HEALING_POWDER) {
// SFALL: Check healing items.
if (!itemIsHealing(item->pid)) {
return false;
}

// CE: Make sure critter actually need healing item.
//
// Sfall has similar fix implemented differently in `ai_check_drugs`. It
// does so after healing item is returned from `ai_search_environ`, so it
// does not a have a chance to look for other items.
int hpRatio = kChemUseStimsHpRatio;
switch (aiGetChemUse(critter)) {
case CHEM_USE_CLEAN:
hpRatio = 0;
break;
case CHEM_USE_STIMS_WHEN_HURT_LITTLE:
hpRatio = kChemUseStimsWhenHurtLittleHpRatio;
break;
case CHEM_USE_STIMS_WHEN_HURT_LOTS:
hpRatio = kChemUseStimsWhenHurtLotsHpRatio;
break;
}

int currentHp = critterGetStat(critter, STAT_CURRENT_HIT_POINTS);
int stimsHp = critterGetStat(critter, STAT_MAXIMUM_HIT_POINTS) * hpRatio / 100;
if (currentHp > stimsHp) {
return false;
}

Expand Down

0 comments on commit 0e11569

Please sign in to comment.