-
-
Save MiroslavR/097bf13768bb07cbf13dfe9258fabf25 to your computer and use it in GitHub Desktop.
Vanilla AI combat decision making
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# This is a simplified version of the function that determines the next combat action (similar to OpenMW's prepareNextAction()). | |
# It's called regularly (on timer) during combat. | |
def shouldFlee(actor, target): | |
if actor.isWerewolf: | |
return False | |
if should use potion: # Unresearched. Fairly long code. | |
return False | |
fleeRating = getFleeRating(actor, target) | |
if fleeRating < 100.0: | |
fleeRating = 0.0 | |
magicRating = getMagicCombatRating(actor, target) | |
weaponRating, weapon = getWeaponCombatRating(actor, target) | |
nextAction = None | |
if weaponsRating > max(magicRating, fleeRating): | |
nextAction = RANGED_FIGHT if weapon.type >= MARKSMAN_BOW else MELEE_FIGHT | |
# ... | |
# some spell setting code? | |
if fleeRating > max(weaponRating, magicRating): | |
return True | |
if fleeRating > 100.0: | |
# NOTE: No check whether target is player... | |
if actor is NPC and glob["PCKnownWerewolf"] != 0: | |
return True | |
if player.isWerewolf: | |
return True | |
if nextAction == None: | |
nextAction = FIST_FIGHT | |
elif nextAction != FIST_FIGHT: | |
return False | |
if actor has active SUMMON_X effect: | |
return True # fists + active summon -> flee | |
return False | |
def getFleeRating(actor, target): | |
if actor.something24 != 0: # Seems to be the number of objects that are obstructing the actor's movement. This prevents actors from continuing to run into walls while fleeing. | |
return 0.0 | |
if actor.flee >= 100: | |
return actor.flee | |
rating = (1.0 - actor.healthPercentage) * gmst["fAIFleeHealthMult"] + actor.flee * gmst["fAIFleeFleeMult"] | |
if target.isWerewolf and actor.level < gmst["iWereWolfLevelToAttack"]: | |
rating = gmst["iWereWolfFleeMod"] | |
if rating != 0.0: | |
rating += (gmst["iFightDistanceBase"] - gmst["fFightDistanceMultiplier"] * distance(actor, target)) | |
return rating | |
def getMagicCombatRating(actor, target): | |
if actor.currentMagicka == 0.0 | |
return 0.0, None | |
if actor has no spells: | |
return 0.0, None | |
maxRating = 0.0 | |
bestSpell = None | |
for spell in actor.spells: | |
if spell.type != REGULAR_SPELL: # skip powers, abilities etc. | |
continue | |
if spell has no HARMFUL effects: # MGEF flag 0x10 | |
continue | |
if spell has UNREFLECTABLE effect: # MGEF flag 0x10000 | |
continue | |
if target.isCurrentlyAffectedBy(spell): | |
continue | |
if spell.magickaCost > actor.currentMagicka: | |
continue | |
if spell has TARGET effect: # range type | |
if target.isSwimming: | |
mult = 0.0 | |
else: | |
mult = gmst["fAIRangeMagicSpellMult"] | |
else: | |
mult = gmst["fAIMagicSpellMult"] | |
if spell.successChance(actor) * mult >= maxRating: | |
maxRating = spell.successChance(actor) * mult | |
bestSpell = spell | |
return maxRating, bestSpell | |
def getWeaponCombatRating(actor, target): | |
if actor is creature and not actor.crea.flags & WEAPON_AND_SHIELD: | |
return 0.0 | |
bestDamageRating = 0.0 | |
bestWeapon = None | |
weaponSkillsTable = [LONG_BLADE, AXE, SPEAR, SHORT_BLADE, MARKSMAN, BLUNT_WEAPON] | |
for i in range(len(weaponSkillsTable)): | |
skillMult = actor.skills[weaponSkillsTable[i]].totalValue * 0.01 | |
for weapon in actor.inventory: | |
if weapon.type in [ARROW, BOLT]: | |
continue | |
if weaponTypeToSkill(item.weapon.type) == weaponSkillsTable[i]: | |
chopMult = 0.0 | |
ammoDamage = 0.0 | |
if weapon.type >= MARKSMAN_BOW: # bows, crossbows and thrown weapons | |
found = False | |
for ammo in actor.inventory: | |
if (ammo.type == ARROW and weapon.type == BOW) or (ammo.type == BOLT and weapon.type == CROSSBOW): | |
ammoDamage = ammo.chopMax | |
found = True | |
break | |
if found and actor is not swimming: | |
if actor.Z >= water level or (actor.height + actor.Z) * 0.7 <= water level: # NOTE: Bug? Should probably be (actor.Z + actor.height * 0.7). | |
chopMult = gmst["fAIRangeMeleeWeaponMult"] | |
else: | |
chopMult = gmst["fAIMeleeWeaponMult"] | |
chopRating = (weapon.chopMax + ammoDamage) * skillMult * chopMult | |
slashRating = weapon.slashMax * skillMult * gmst["fAIMeleeWeaponMult"] | |
thrustRating = weapon.thrustMax * skillMult * gmst["fAIMeleeWeaponMult"] | |
if weapon.type == MARKSMAN_THROWN and not actor.hasAnimationGroup("MarksmanThrown") | |
continue | |
if chopRating != 0.0 and chopRating >= bestDamageRating: | |
chopRating = bestDamageRating | |
bestWeapon = weapon | |
if slashRating != 0.0 and slashRating >= bestDamageRating: | |
slashRating = bestDamageRating | |
bestWeapon = weapon | |
if thrustRating != 0.0 and thrustRating >= bestDamageRating: | |
thrustRating = bestDamageRating | |
bestWeapon = weapon | |
if bestWeapon != None and bestWeapon.currentHealth > 0.0: | |
return actor.getArmorRating() * gmst["fAIMeleeArmorMult"] + bestDamageRating | |
return 0.0 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment