Add ai_check_drugs fixes and improvements (#29)

This commit is contained in:
Alexander Batalov 2022-12-28 02:19:43 +03:00
parent 4b137dac5f
commit 0e11569397
1 changed files with 124 additions and 40 deletions

View File

@ -41,6 +41,16 @@ namespace fallout {
#define AI_MESSAGE_SIZE 260 #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 { typedef struct AiMessageRange {
int start; int start;
int end; int end;
@ -934,31 +944,29 @@ static int _ai_check_drugs(Object* critter)
return 0; return 0;
} }
int hpRatio = 50; int hpRatio = kChemUseStimsHpRatio;
int chemUseChance = 0; int chemUseChance = 0;
switch (ai->chem_use) { switch (ai->chem_use) {
case CHEM_USE_CLEAN: case CHEM_USE_CLEAN:
return 0; return 0;
case CHEM_USE_STIMS_WHEN_HURT_LITTLE: case CHEM_USE_STIMS_WHEN_HURT_LITTLE:
hpRatio = 60; hpRatio = kChemUseStimsWhenHurtLittleHpRatio;
break; break;
case CHEM_USE_STIMS_WHEN_HURT_LOTS: case CHEM_USE_STIMS_WHEN_HURT_LOTS:
hpRatio = 30; hpRatio = kChemUseStimsWhenHurtLotsHpRatio;
break; break;
case CHEM_USE_SOMETIMES: case CHEM_USE_SOMETIMES:
if ((_combatNumTurns % 3) == 0) { if ((_combatNumTurns % 3) == 0) {
chemUseChance = 25; chemUseChance = kChemUseSometimesChance;
} }
hpRatio = 50;
break; break;
case CHEM_USE_ANYTIME: case CHEM_USE_ANYTIME:
if ((_combatNumTurns % 3) == 0) { if ((_combatNumTurns % 3) == 0) {
chemUseChance = 75; chemUseChance = kChemUseAnytimeChance;
} }
hpRatio = 50;
break; break;
case CHEM_USE_ALWAYS: case CHEM_USE_ALWAYS:
chemUseChance = 100; chemUseChance = kChemUseAlwaysChance;
break; break;
} }
@ -996,24 +1004,80 @@ static int _ai_check_drugs(Object* critter)
if (!drugUsed) { if (!drugUsed) {
if (chemUseChance > 0 && randomBetween(0, 100) < chemUseChance) { 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); Object* drug = _inven_find_type(critter, ITEM_TYPE_DRUG, &inventoryItemIndex);
if (drug == NULL) { if (drug == NULL) {
searchCompleted = true; searchCompleted = true;
break; break;
} }
int drugPid = drug->pid; if (!itemIsHealing(drug->pid)) {
int index; bool isPrimary = false;
for (index = 0; index < AI_PACKET_CHEM_PRIMARY_DESIRE_COUNT; index++) { for (int 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] == drug->pid) {
if (ai->chem_primary_desire[index] != drugPid) { isPrimary = true;
break; break;
} }
} }
if (index < AI_PACKET_CHEM_PRIMARY_DESIRE_COUNT) { if (isPrimary) {
if (!itemIsHealing(drugPid)) { 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 (itemRemove(critter, drug, 1) == 0) {
if (_item_d_take_drug(critter, drug) == -1) { if (_item_d_take_drug(critter, drug) == -1) {
itemAdd(critter, drug, 1); itemAdd(critter, drug, 1);
@ -1039,8 +1103,6 @@ static int _ai_check_drugs(Object* critter)
} }
} }
} }
}
}
if (lastItem != NULL || (!drugUsed && searchCompleted)) { if (lastItem != NULL || (!drugUsed && searchCompleted)) {
do { do {
@ -1991,10 +2053,32 @@ static bool aiCanUseItem(Object* critter, Object* item)
return false; return false;
} }
int itemPid = item->pid; // SFALL: Check healing items.
if (itemPid != PROTO_ID_STIMPACK if (!itemIsHealing(item->pid)) {
&& itemPid != PROTO_ID_SUPER_STIMPACK return false;
&& itemPid != PROTO_ID_HEALING_POWDER) { }
// 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; return false;
} }