Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- Monster AI:
- So anyway, I've been coding on Incursion, trying to get it back to the spot where it produces a working executable (albeit with a bunch of stuff disabled), and it's going very slowly. I'm really realizing the enormity of the task I set out on here when I decided to refactor everything at once. On the good side, I'm very close (to an executable, not anything playable) at this point, and when the refactoring is truly all finished this will basically be the engine I plan to use for Return of the Forsaken.
- Anyway, as a preview and to elicit feedback, I figured I would post some of the new Behavior resources for the fully customizable resource-driven monster AI. This file doesn't compile at all yet -- I need to make improvements to the script compiler before it will take it -- so it's become kind of a sketchpad for theoretical ideas on monster tactics in Incursion. All the resources listed here that don't have code are just ideas so far -- definitely everything here won't be in the next release!
- Note that you just have to set a flag on any of these to make them player-accessible; they a player will be able to customize whether a follower is subject to a given Behavior or not by giving orders. I'll use a list like the 'pick up multiple objects' list on AutoLoot to let players turn lots of behaviors on an off for their companions at once.
- I don't expect people to really understand my code, and a lot of it is between revisions right now. I'm just posting it for interest's sake, if it amuses anybody. :)
- -------------
- Behaviour "Ooze Eats Metal Items" : BV_GENERIC
- {
- On Event EV_ISTARGET {
- return EActor->HasMFlag(M_METALIVORE) ?
- SHOULD_CAST_IT : CANNOT_CAST_IT;
- },
- EV_RATEITEM {
- if (EItem->isMetallic() &&
- EItem->Material() != MAT_ADAMANT)
- {
- e.targPri = 10 + EItem->Weight(false);
- e.targType = TT_ITEM;
- e.targWhy = TY_FOOD;
- e.targObj = EItem;
- return DONE;
- }
- return NOTHING;
- },
- EV_THINK {
- hObj hi;
- for (hi=EMap->FItemAt(EActor->x,EActor->y);hi;
- hi=EMap->NItemAt(EActor->x,EActor->y))
- if (EItem->isMetallic() &&
- EItem->Material() != MAT_ADAMANT)
- AddAct(ACT_CUSTOM,P_HIGH,hi,0);
- return NOTHING;
- },
- EV_CUSTOM {
- e.chResult = Throw(EV_EAT,EActor,ETarget);
- return DONE;
- };
- }
- Behavior "Attack Mount in Melee" : BF_UNIVERSAL|BF_SETUP
- {
- Requires: BV_HAS_MTARG;
- On Event EV_THINK {
- if (!random(3))
- if (e.mtarg->hasStati(MOUNTED))
- e.mtarg = e.mtarg->getStatiObj(MOUNTED);
- };
- }
- Behavior "Attack Mount at Range" : BF_UNIVERSAL|BF_SETUP
- {
- Requires: BV_HAS_RTARG;
- On Event EV_THINK {
- if (!random(3))
- if (rtarg->hasStati(MOUNTED))
- rtarg = rtarg->getStatiObj(MOUNTED);
- return NOTHING;
- };
- }
- Behavior "Fear From Injuries" : BF_UNIVERSAL|BF_EARLY
- {
- Cancels: BV_ENRAGED | BV_AFRAID;
- On Event EV_THINK {
- if (EActor->cHP*3 < (EActor->mHP+EActor->getAttr(A_THP)) {
- EActor->IDPrint(NULL,"The <hObj> turns to flee!", EActor);
- EActor->GainTempStati(AFRAID, NULL, -2, SS_MONI, FEAR_HP ,0,
- $"Fear From Injuries");
- EActorMB->AIFlags |= BV_AFRAID;
- }
- return NOTHING;
- },
- EV_ISTARGET {
- if (getAttr(A_MOV) == -20)
- return CANNOT_CAST_IT;
- return (hasMFlag(M_PSYCHO) || hasMFlag(M_MINDLESS) || isIllusion
- ()) ?
- CANNOT_CAST_IT : SHOULD_CAST_IT;
- };
- }
- Behavior "Fear From Low Mana" : BF_UNIVERSAL|BF_EARLY
- {
- Cancels: BV_ENRAGED | BV_SESSILE | BV_AFRAID;
- On Event EV_THINK {
- if (EActor->cMana()*2 < EActor->tMana()) {
- EActor->IDPrint(NULL,"The <hObj> turns to flee!", EActor);
- EActor->GainTempStati(AFRAID, NULL, -2, SS_MONI, FEAR_MANA,0,
- $"Fear From Low Mana");
- EActorMB->AIFlags |= BV_AFRAID;
- }
- return NOTHING;
- },
- EV_ISTARGET {
- return (hasMFlag(M_PSYCHO) || hasMFlag(M_MINDLESS) || isIllusion
- ()) ?
- CANNOT_CAST_IT : SHOULD_CAST_IT;
- };
- }
- Behavior "Recover From Fear" : BF_UNIVERSAL|BF_EARLY
- {
- Requires: BV_AFRAID;
- On Event EV_THINK {
- bool b;
- for (b=EActor->FirstStati(e,AFRAID,-1,SS_MONI);b;
- b=EActor->NextStati(e,AFRAID,-1,SS_MONI)) {
- if (e.vVal == FEAR_HP && EActor->cHP*3 > (EActor->mHP+EActor-
- >getAttr(A_THP))*2)
- EActor->RemoveThisStati();
- if (e.vVal == FEAR_MANA && EActor->cMana()*5 > EActor->tMana()
- *4)
- EActor->RemoveThisStati();
- }
- return NOTHING;
- };
- }
- Behavior "Fix Trouble With Effect" : BF_UNIVERSAL
- {
- Requires: BV_HAS_EFFS;
- On Event EV_THINK {
- int16 tr, pri;
- if (EActor->hasEffToFix & EActor->Troubles) {
- for (tr=EActor->FirstTrouble();tr;tr=EActor->NextTrouble())
- EActor->AddAct(ACT_CAST,(tr/256) + ((e.bvFlags &
- BV_AFRAID) ? +1 : -1),
- EP_FIX_TROUBLE + ((tr & 0xFF)<<16));
- }
- return NOTHING;
- };
- }
- /* To support this, we should add back the OBJECTIVE stati
- specifically
- as a "location or creature to move toward" data structure, and give
- it a Val (Priority), Dur (abort if not reached after X turns), eID
- (causing Behavior), hObj (object to move towards) or Mag (x+y*256).
- EActor->addMovementObjective(Target,PRI_HIGH,Dur,eID);
- EActor->addMovementObjective(x,y,PRI_NORMAL,Dur,eID);
- We could do this with a Target instead of a Stati if we desired. We
- also need to add code for processing ACT_CAST to move toward the
- target of a touch spell if necessary, both for healing allies and
- for using hostile touch spells.
- Since we're starting to look at spells as long-term objectives
- here,
- we also need to add a new function, calcCastingDiff(), for a given
- effect that returns a priority value based on how difficult it
- would
- be to cast the spell, taking into account mana, fatigue, risk, etc.
- We don't set movement objectives, and we abort the actual casting,
- if
- the priority returned by this function is higher than the priority
- of
- the ACT_CAST.
- Use EventInfo as place to store disambiguators and such for
- iterators,
- to prevent recursive iteration, letting us do, frex, FirstStati.
- BV_PALLY -- is an ally of the player
- BV_GOOD -- good alignment behavior
- BV_LAWFUL -- lawful alignment behavior
- TO_MOVE_OBJ -- Movement Objective Target
- TO_KEEP_DIST -- Stay at least N squares away
- Movement Rules:
- -- if highest priority movement objective is out of LOS,
- determine movement by pathfinding to that obj alone.
- -- if highest pri MO is pri >= PRI_EXTREME, travel only
- considering that objective
- -- otherwise, use gravity system
- Dependencies Differ for Behaviors:
- BV_SHAPE (recalculate these on shapeshift)
- BV_ABILITY (recalc on levelup)
- BV_EFFECT (recalc with effectlist)
- BV_INVEN (recalc with inventory)
- BV_GROUP (recalc when party changes)
- --
- BV_LEADER
- BV_UNLED (leader or solo)
- BV_FOLLOWER
- --
- When a Behavior returns CAN_CAST_IT, it's not automatic but can be
- issued as an order. This might depend on social stats -- a mage
- will be reluctant to "Take Point" unless the leader's really
- persuasive.
- Allocate an int8* array of hasBehavior flags for each MonsterBrain
- on creation based on calling theGame->TotalBehaviors().
- Favor (Neutrals) / Loyalty (Allies):
- -- gaining XP grants favor points
- -- fixing troubles grants favor points
- -- giving items that get equipped grants favor
- -- taking away items reduces loyalty
- Strain
- -- RHostility sets base strain
- -- Getting critically wounded increases strain
- Animal Emp should use the social framework for recruiting animals,
- ditto species affinity; work out how they stack (full AE ranks
- or
- +1/4 level bonus, whichever is better)
- scanNearbyArea()
- -- support function for monster navigation, when setting movement
- objectives you scan nearby squares until you find the closest
- one which matches some criteria. Used for:
- -- find lower-ceiling area when being shot by unreachable flyer
- -- find nearest route out of bad fields that are hurting you,
- like Insect Plague.
- Pathfinding:
- -- when doing pathfinding, at the start build up two movement
- profiles -- current and elective, with elective including
- all the monster's movement-enhancing effects. Assign the
- effects opportunity costs.
- We only need to calculate both paths for monsters whose
- current and elective profiles differ.
- -- If the elective path is much shorter, activate or prioritize
- the needed elective effects and use the elective path.
- -- When taking a step on an elective path:
- -- if you're beside terrain you need an elective effect to
- cross, activate the elective effect. If you can't, drop
- the path and redo the pathfinding.
- -- otherwise, take the step.
- -- When on an elective path, remember to unmark MS_INV_OKAY
- because betterFor changes based on demand for elective
- effects.
- -- Thus, monsters will put on levitation boots to cross chasms
- and fire boots to walk over magma, but they'll take the lev
- boots back off afterward because of the 50% move penalty.
- setContextActive
- -- Marks a Behavior as active that alters equipment betterFor
- based on context; EV_ISTARGET will be called at the start
- of every action to see if the
- -- So the monster uses the +4 sword usually, but uses the
- +1 flame sword when a target in LOS has flame vulnerability
- -- List InActive effects (equipment that is suboptimal in the
- generic context) in ListEffects() based on unworn inventory.
- -- TT_MEMORY -- specific behaviors demand we remember
- things indefinitely, such as priests and altars,
- but we don't go to grab them right away.
- Monsters need some way to remember the situation they're
- in, so they know to use, for example, wind wall if they've
- been shot with arrows five times in the last two turns.
- Brainstorm on this!
- STR_CASTER
- * Increase Avoid Melee Priority
- * Flee Based On Low Mana
- * Favor Ranged Magic to Inflict Damage, Summon, etc.
- STR_BRUTE
- STR_SKIRMISH
- STR_SUPPORT
- STR_BODYGUARD
- STR_STRIKER // reprioritize, mobile, leave melee, address
- highest threats
- STR_PACK // pack animals, wear down before attack, aid another, etc.
- STR_OUTSIDER // powerful in melee, assorted innate powers
- STR_
- */
- Behavior "Fix Allies' Troubles" : BF_UNIVERSAL
- {
- Requires: BV_HAS_EFFS, BV_HAS_PARTY;
- On Event EV_THINK {
- hObj hAlly, hBAlly;
- int16 high, tr, htr, pri;
- if (EActor->hasEffToFix == 0)
- return NOTHING;
- hBAlly = NULL_OBJ; high = 0;
- for (hAlly=EActor->FirstAlly();hAlly;
- hAlly=EActor->NextAlly())
- if (hAlly->Troubles & EActor->hasEffToFix)
- for (tr=hAlly->FirstTrouble(e);tr;
- tr=hAlly->NextTrouble(e))
- {
- pri = tr / 256;
- if (pri > high) {
- hBAlly = hAlly;
- htr = tr % 256;
- high = pri;
- }
- }
- if (hBAlly == NULL_OBJ)
- return NOTHING;
- /* Don't try to fix Y's troubles when we're already running to X
- to
- fix HIS troubles. */
- if (high <= EActor->getMovementObjectivePri($"Fix Allies'
- Troubles"))
- return NOTHING;
- EActor->AddAct(ACT_CAST,high,EP_FIX_TROUBLE+(htr<<16));
- return NOTHING;
- };
- }
- /* City watch and stuff -- ignore racial hostility and never initiate
- combat, stand in one location unless disturbed, return to that loc
- as soon as you have no targets, do not covet items. */
- Behavior "Guard Location"
- {
- Cancels: BV_TARGVIS, BV_INMELEE, BV_AFRAID;
- List:
- * NEGATE_BEHAVIORS
- $"Explore Dungeon"
- $"Wanderlust";
- On Event EV_THINK {
- int16 gx, gy;
- /* If we haven't already been assigned a specific location on
- the
- map to guard when we were given the behavior, we guard the
- location we were first placed on the map at. */
- if (!EActor->hasEffStati(GENERIC,thID))
- EActor->GainPermStati(GENERIC,NULL_OBJ,SS_MONI,EActor->x
- +EActor->y*256);
- gx = EActor->getEffStatiVal(GENERIC,thID) % 256;
- gy = EActor->getEffStatiVal(GENERIC,thID) / 256)
- if (EActor->x == gx && EActor->y == gy)
- {
- EActor->AddAct(ACT_WAIT, PRI_NORMAL, 10);
- return NOTHING;
- }
- if (EActor->hasMoveObjFrom(thID,gx,gy))
- return NOTHING;
- EActor->addMoveObj(gx,gy,PRI_NORMAL,-2,thID);
- EActor->AddAct(ACT_MOVE,PRI_NORMAL,0);
- return NOTHING;
- };
- }
- /* Westley's Code -- I'm not sure what it's doing */
- #if 0
- Behavior "Coup-de-Grace Helpless Foes"
- {
- Cancels: BV_ENRAGED;
- On Event EV_THINK {
- int16 best; hObj bc, c;
- best = 0;
- for(i=0;i!=8;i++)
- for (c = m->FCreatureAt(EActor->x+DirX(i),EActor->y+DirY
- (i));c;
- c = m->NCreatureAt(EActor->x+DirX(i),EActor->y+DirY(i)))
- if (EActor->getHostility(c) > best &&
- XPercieves(c))
- {
- best = isHostileTo(c);
- bc = c;
- }
- if (bc) {
- if (bc->hasStati(SLEEPING) ||
- bc->hasStati(PARALYSIS,PARA_HELD) ||
- (bc->hasStati(STUCK) && bc->hasStati(PRONE)))
- AddAct(ACT_COUPDEGRACE,P_URGENT,bc);
- if (AttackMode() == S_BRAWL || ((onPlane() != bc->onPlane())
- &&
- (!inSlot(SL_WEAPON) || !inSlot(SL_WEAPON)->isMagic())))
- AddAct(ACT_NATTACK,P_URGENT,bc);
- else if (AttackMode() == S_MELEE)
- AddAct(ACT_WATTACK,P_URGENT,bc);
- }
- }
- #endif
- Behavior "Recognize Unbeatable Foe"
- if (EActor->attackForms & EActor->getKnownImmunes(mtarg) ==
- EActor->attackForms)
- isUnbeatable = true;
- Behavior "Cannot Beat MR"
- If you can't beat a target's MR,
- (1) attack other targets
- (2) buff for melee and fight melee
- (3) buff allies, act as support
- Behavior "Evade Elevated Foe"
- -- foe is flying too high to hit, so go somewhere
- with a lower ceiling.
- Behavior "Placate Unbeatable Foe"
- (1) try resolve conflict
- (2) decide if fleeing is viable
- (3) surrender or flee
- (4) or just avoid getting near and fight other things
- Behavior "List Pseudo-Effects" // Trap EV_LIST_EFFECTS
- --or--
- Behavior "Use Healing Skill" // Done using ACT_CUSTOM
- Behavior "Jump Toward Objective"
- Behavior "Teleport to Objective"
- int16 Creature::getBuffDispelPriority()
- {
- bool isFullCaster =
- (getAbilLev(CA_SPELLCASTING)*110 > getCR()*100) &&
- getAbilLev(CA_SPELLCASTING) > 3;
- int16 activeBuffs = ...
- if (isFullCaster && activeBuffs >= 4 &&
- buffMana >= tMana() / 3)
- return P_EXTREME;
- int16 myCheck, hisCheck, priorTries;
- ...
- }
- Behavior "Dispel Enemy Buffs"
- {
- Requires: BV_EFFECTS;
- Purpose: EP_DISPEL;
- On Event EV_THINK {
- if (mtarg->hMana > 30 || mtarg->isPlayer())
- {
- int16 manaCost, buffCount, sl;
- static int16 slots[] = { SL_AMULET, SL_LRING, SL_RRING,
- SL_BRACERS, SL_READY, SL_WEAPON,
- SL_CLOAK, SL_GAUNTLETS, SL_HELM };
- sl = random(9);
- mtarg->getBuffInfo(&buffCount,&manaCost,NULL);
- if (buffCount && manaCost > 150 && !random(2))
- AddAct(ACT_CAST,P_OVERRIDE,mtarg,EP_DISPEL);
- else if (buffCount && manaCost > 80)
- AddAct(ACT_CAST,P_URGENT,mtarg,EP_DISPEL);
- else if (buffCount || (mtarg->inSlot(sl) &&
- mtarg->inSlot(sl)->isMagic() &&
- !mtarg->inSlot(sl)->hasStati(DISPELLED)))
- AddAct(ACT_CAST,P_MODERATE+random(2),mtarg,EP_DISPEL);
- }
- if (rtarg->hMana > 30 || rtarg->isPlayer())
- {
- int16 manaCost, buffCount, sl;
- static slots[] = { SL_AMULET, SL_LRING, SL_RRING,
- SL_BRACERS, SL_READY, SL_WEAPON,
- SL_CLOAK, SL_GAUNTLETS, SL_HELM };
- sl = random(9);
- rtarg->getBuffInfo(&buffCount,&manaCost,NULL);
- if (buffCount && manaCost > 150 && !random(2))
- AddAct(ACT_CAST,P_OVERRIDE,rtarg,EP_DISPEL);
- else if (buffCount && manaCost > 80)
- AddAct(ACT_CAST,P_URGENT,rtarg,EP_DISPEL);
- else if (buffCount || (rtarg->inSlot(sl) &&
- rtarg->inSlot(sl)->isMagic() &&
- !rtarg->inSlot(sl)->hasStati(DISPELLED)))
- AddAct(ACT_CAST,P_MODERATE+random(2),rtarg,EP_DISPEL);
- }
- };
- }
- Behavior "Use Healing on Allies"
- Behavior "Charge at Enemy"
- Behavior "Tumble when Threatened"
- Behavior "Use Spring Attack"
- Behavior "Use Whirlwind Attack"
- Behavior "Perform Great Blow" // include smite preference
- Behavior "Dip Item in Fountain" // Trap EV_RATEITEM to appraise
- fountain when first seen
- Behavior "Loot Chest" // set move obj, pick lock, try force, get
- items, not from your HOME_REGION
- Behavior "Hide Tactically"
- Behavior "Hide Whenever Possible"
- Behavior "Get Out Of Bad Field" // pathfind out of radius
- Behavior "Mine for Treasure"
- Behavior "Use Turn/Command"
- Behavior "Use Gate Ability"
- Behavior "Buff Until Buffed"
- -- each specialist has a BUFF_LIST constant, scan it (FirstListItem
- ()?)
- and cast each spell we don't have. We might need a getBuffList()
- function hardcoded, and getBuffState(). Maybe a boolean flag isBuff
- in the KnownSpells Array?
- -- some buffs (stoneskin) should be very high priority when not up,
- but
- most others shuold only be cast when not in combat.
- /* Some Metamagic Feats are handled in ListEffects by listing effects
- more than once, with the feats in question applied -- i.e., Warp
- Spell */
- /* The Pathfinding Algorithm should occur at some stage of EV_THINK if
- there are any ACT_MOVE lines in the queue, and it will translate
- the
- ACT_MOVE into ACT_WALK or ACT_JUMP with a specific direction
- attached. */
- /* The Orders Mechanism should have an ability to give behaviors with
- the
- BF_ORDER flag, or take them away. Player preferences should have a
- list
- of each such behavior with ALWAYS/NEVER/SMART for each as Default
- Orders. */
- Behavior "Fight Defensively When Desperate"
- Behavior "Fight Defensively Near Unbeatables"
- Behavior "Bring Items to Leader"
- Behavior "Use Shapeshifting"
- Behavior "Situation Equipment Changes: Magma"
- Behavior "Situation Equipment Changes: Fighting Mages"
- Behavior "Get Useful Items"
- Behavior "Use Breath Weapon"
- Behavior "Use Spit Weapon"
- Behavior "Bull Rush Foe Over Bad Terrain"
- Behavior "Use Disarm" / BV_MANIP -- fine manipulators
- Behavior "Foil Pursuit"
- Behavior "Escape/Subvert Grapple"
- Behavior "Use Roar Attack"
- Behavior "Use Thrown Weapon"
- Behavior "Make Trip Attack"
- Behavior "Sprint When Desperate"
- Behavior "Choose Armor Level"
- // Trap EV_BETTER_FOR, mages don't wear armor, rangers wear light,
- etc.
- Behavior "Masquerade as Human"
- // for dragons and such with a human form
- Behavior "Hide Under Object" // prevent movement out for most move
- priorities
- Behavior "Hide In Water" // ditto
- Behavior "Hide On Ceiling"
- Behavior "Eat When Hungry"
- Behavior "Eat Items" // Oozes
- Behavior "Avoid Conflict to Recover Mana"
- Behavior "Wander Around Home Region"
- Behavior "Return to Home Region When Tired"
- Behavior "Skirmish Tactics"
- Behavior "Pursue But Avoid Combat" // until overmatched, animal packs
- Behavior "Recruit Allies" // NPC's issue enlist requests, poach PC
- allies
- Behavior "Intimidate Enemy"
- Behavior "Resolve Conflict"
- // good vs. good/neutral
- // Animal Empathics vs. Animals
- Behavior "Buy Service Spell to Fix Trouble"
- Behavior "Surrender When Overmatched"
- // maybe just as a tag for the fear behavior to check for
- Behavior "Explore Dungeon"
- // int32 memory flag of investigated panels, so you know
- where you haven't been.
- Behavior "Distract and Hide"
- Behavior "Stay Mounted"
- // summon or recruit mount if you don't have one
- // remount when dismounted
- Behavior "Climb on Ceiling"
- Behavior "Jump over Trap/Obstacle"
- Behavior "Disarm Traps"
- Behavior "Re-Arm/Salvage/Set Traps"
- Behavior "Use Pick Pockets"
- Behavior "Apply Poison to Weapons"
- Behavior "Flank For Rogue Ally"
- Behavior "Bodyguard Mage Ally"
- Behavior "Aid Leader When in Melee"
- Behavior "Attack Most Wounded Targets First"
- Behavior "Adhere to Chivalry"
- Behavior "Adhere to Good Alignment"
- Behavior "Greet Strangers"
- // human neutrals approach you, give favorable/unfavorable gesture
- Behavior "Emote as Animal"
- // animals walk up and nuzzle, lick, or growl/hiss
- Behavior "Chatter Randomly"
- // if monster has periodic messages, show them; some might have
- // associated timeouts ("The myconid tends the fungus garden.")
- Behavior "Betray Weakened Ally"
- // Chaotic Evil monsters will eject badly wounded ally from party
- // and become hostile to it.
- Behavior "Challenge for Leadership"
- Behavior "Rage When Challenged" // Barbarians
- Behavior "Hold Formation"
- Behavior "Travel Near Leader"
- Behavior "Take Point"
- Behavior "Buff Leader"
- /* When leader is player, ask (yn) if player wants the spell cast
- on them, or Yes/No/Always/Never and store the Always and Never
- results in stati. */
- Behavior "Buff Allies"
- Behavior "Buff Weapon" // Greater Magic Weapon, etc.
- Behavior "Buff Armor"
- Behavior "Buff Allies' Weapons"
- Behavior "Buff Allies' Armor"
- Behavior "Cast Specific Spell"
- // pseudo-behavior to get ally to cast a spell they know with a
- target and metamagic chosen by the player.
- Behavior "Devour Corpses" // for orcs
- Behavior "Weaken/Curse Strong Foe" // when group holding back
- Behavior "Use Secondary Weapons"
- {
- Requires: BV_HANDS;
- Cancels: BV_AFRAID;
- }
- Behavior "Favor Defensive Equipment" : BF_CONTEXT
- {
- Requires: BV_HANDS, BV_CONTEXT_EQUIP;
- On Event EV_ISTARGET {
- return EActor->isDefensive();
- },
- EV_EVAL_ITEM<EE_POST> {
- e.eiDefenseVal *= 2;
- return NOTHING;
- },
- EV_WIELD, EV_TAKEOFF {
- EActor->StateFlags &= ~MS_INVEN_OK;
- return NOTHING;
- };
- }
- Behavior "Take War Form" : BF_ABILITY
- {
- Cancels: BV_AFRAID;
- On Event EV_THINK {
- if (hasStati(SPEC_TIMEOUT,A_FORM))
- return NOTHING;
- if (EActor->mID == EActor->getForm(FORM_WAR))
- return NOTHING;
- if ((e.bvFlags & BV_INMELEE) ||
- (EActor->isThreatened() && e.aiDesperation >= P_HIGH))
- EActor->AddAct(ACT_SHIFT,P_HIGH,NULL,EActor->getForm
- (FORM_WAR));
- return NOTHING;
- },
- EV_ISTARGET {
- if (!EActor->hasAttk(A_FORM))
- return CANNOT_CAST_IT;
- if (EActor->getWarForm() == NULL_ID)
- return CANNOT_CAST_IT;
- return SHOULD_CAST_IT;
- };
- }
- Behavior "Take Disguise Form" : BF_ABILITY
- {
- Cancels: BV_TARGVIS, BV_INMELEE, BV_ENRAGED, BV_AFRAID;
- On Event EV_THINK {
- if (hasStati(SPEC_TIMEOUT,A_FORM))
- return NOTHING;
- if (EActor->mID == EActor->getForm(FORM_DISGUISE1) ||
- EActor->mID == EActor->getForm(FORM_DISGUISE2))
- return NOTHING;
- EActor->AddAct(ACT_SHIFT,P_HIGH,NULL,random(3) ?
- EActor->getForm(FORM_DISGUISE1) : EActor->getForm
- (FORM_DISGUISE2));
- return NOTHING;
- },
- EV_ISTARGET {
- if (!EActor->hasAttk(A_FORM))
- return CANNOT_CAST_IT;
- if (EActor->getForm(FORM_DISGUISE1) == NULL_ID)
- return CANNOT_CAST_IT;
- return SHOULD_CAST_IT;
- };
- }
- Behavior "Take Fleeing Form" : BF_ABILITY
- {
- Requires: BV_AFRAID;
- Cancels: BV_INMELEE, BV_ENRAGED;
- On Event EV_THINK {
- if (hasStati(SPEC_TIMEOUT,A_FORM))
- return NOTHING;
- if (EActor->mID == EActor->getForm(FORM_FLEEING))
- return NOTHING;
- EActor->AddAct(ACT_SHIFT,P_HIGH,NULL,EActor->getForm
- (FORM_FLEEING));
- return NOTHING;
- },
- EV_ISTARGET {
- if (!EActor->hasAttk(A_FORM))
- return CANNOT_CAST_IT;
- if (EActor->getForm(FORM_FLEEING) == NULL_ID)
- return CANNOT_CAST_IT;
- return SHOULD_CAST_IT;
- };
- }
- Behavior "Take Social Form" : BF_ABILITY
- {
- Cancels: BV_INMELEE, BV_ENRAGED, BV_AFRAID, BV_TARGVIS;
- On Event EV_THINK {
- if (hasStati(SPEC_TIMEOUT,A_FORM))
- return NOTHING;
- if (EActor->mID == EActor->getForm(FORM_SOCIAL))
- return NOTHING;
- if (there's a neutral or player nearby)
- EActor->AddAct(ACT_SHIFT,P_HIGH,NULL,EActor->getForm
- (FORM_SOCIAL));
- return NOTHING;
- },
- EV_ISTARGET {
- if (!EActor->hasAttk(A_FORM))
- return CANNOT_CAST_IT;
- if (EActor->getForm(FORM_SOCIAL) == NULL_ID)
- return CANNOT_CAST_IT;
- return SHOULD_CAST_IT;
- };
- }
- Behavior "Take Terrain-Capable Form" : BF_ABILITY|BF_MOVEMENT
- {
- Cancels: BV_INMELEE, BV_ENRAGED;
- On Event EV_THINK {
- if (hasStati(SPEC_TIMEOUT,A_FORM))
- return NOTHING;
- if (EActor->isBadTerrainAt(e.EXVal,e.EYVal))
- if (EActor->getBestFormForTerrainAt(e.EXVal, e.EYVal))
- Actor->AddTravelAct(ACT_SHIFT,P_HIGH,NULL,EActor-
- >getBestFormForTerrainAt(e.EXVal, e.EYVal));
- return NOTHING;
- },
- EV_ISTARGET {
- if (!EActor->hasAttk(A_FORM))
- return CANNOT_CAST_IT;
- return SHOULD_CAST_IT;
- };
- }
- Behavior "Phase Before Attacking" : BF_ABILITY|BF_EFFECTS
- {
- On Event EV_PERFORM_ACT {
- int16 sl;
- if (e.vAct != ACT_WATTACK ||
- e.vAct != ACT_NATTACK ||
- e.vAct != ACT_RATTACK)
- return NOTHING;
- if (EActor->onPlane() == EVictim->onPlane())
- return NOTHING;
- if (e.vAct == ACT_WATTACK)
- if (EActor->inSlot(SL_WEAPON) != NULL_OBJ)
- if (EActor->inSlot(SL_WEAPON)->hasQuality(WQ_GHOST_TOUCH))
- return NOTHING;
- if (e.vAct == ACT_RATTACK) {
- sl = EActor->isCharacter() ? SL_WEAPON : SL_ARCHERY;
- if (EActor->inSlot(sl) != NULL_OBJ)
- if (EActor->inSlot(sl)->hasQuality(WQ_GHOST_TOUCH))
- return NOTHING;
- }
- EActor->RedirectAct(ACT_PHASE,NULL,EVictim->onPlane());
- return DONE;
- },
- EV_ISTARGET {
- if (EActor->hasAbility(CA_PHASE))
- return SHOULD_CAST_IT;
- if (EActor->hasEffFor & EP_PHASE)
- return SHOULD_CAST_IT;
- return CANNOT_CAST_IT;
- };
- }
- Behavior "Phase In Before Casting"
- ...
- Behavior "Phase To Flee/Hide"
- {
- Requires: BV_AFRAID, BV_HIDDEN;
- On Event EV_THINK {
- if (EActor->onPlane() == EMap->getBasePlane())
- if (!EActor->hasStati(SPEC_TIMEOUT,CA_PHASE))
- EActor->AddAct(ACT_PHASE,NULL,-1); // any plane but this
- one!
- return NOTHING;
- },
- EV_ISTARGET {
- if (EActor->hasAbility(CA_PHASE))
- return SHOULD_CAST_IT;
- if (EActor->hasEffFor & EP_PHASE)
- return SHOULD_CAST_IT;
- return CANNOT_CAST_IT;
- };
- }
- /* Creatures that live on other planes, like the ethereal filcher,
- should go back there when left alone. Characters with phasing
- spells or gear should hand out on the material unless given an
- order to do otherwise. */
- Behavior "Phase When Alone"
- Behavior "Hide When Alone"
- {
- Cancels: BV_HIDDEN, BV_ILLUSION, BV_CHARGING, BV_TARGVIS;
- On Event EV_THINK {
- /* If we're desperate to get somewhere, we don't
- want to take the Hide movement penalty. */
- if (getMoveObjPri() > P_HIGH ||
- hasStati(SPRINTING))
- return NOTHING;
- if (hasOrder("Hide;Order"))
- AddAct(ACT_HIDE,P_URGENT,NULL,HI_SHADOWS);
- else
- AddAct(ACT_HIDE,P_HIGH,NULL,HI_SHADOWS);
- return NOTHING;
- },
- EV_ISTARGET {
- if (EActor->hasSkill(SK_HIDE))
- return SHOULD_CAST_IT;
- return CANNOT_CAST_IT;
- };
- }
- Behavior "Rate Shoreline Targets"
- /* When evaluating targets, non-amphibious aquatic monsters should
- only move toward targets that are in water, or or within one
- square of their body of water, or within two if they have reach. */
- Behavior "Hide Underwater"
- {
- Prereq: BV_INWATER;
- Cancels: BV_ILLUSION, BV_CHARGING;
- On Event EV_THINK {
- if (hasStati(HIDING,HI_WATER))
- return NOTHING;
- /* Hide only if there's no one around, or we're fleeing,
- possibly due to skirmish tactics -- so we can strike
- from underwater, swim away to hide and strike from
- surprise again and again. */
- if (e.bvFlags & BV_TARGVIS)
- if (!(e.bvFlags & BV_AFRAID))
- return NOTHING;
- /* Check if water is too clear? If it obscures, we can
- hide in it an depth A, otherwise we need depth B. */
- if (!EActor->isWaterTerFlag(TC_OBSCURES))
- return NOTHING;
- if (hasOrder("Hide;Order"))
- AddAct(ACT_HIDE,P_URGENT,NULL,HI_WATER);
- else
- AddAct(ACT_HIDE,P_HIGH,NULL,HI_WATER);
- return NOTHING;
- },
- EV_ISTARGET {
- if (EActor->hasSkill(SK_HIDE))
- return SHOULD_CAST_IT;
- return CANNOT_CAST_IT;
- };
- }
- /* Spiders and such can hide under objects for a +10 circumstance
- bonus to Hide. When we do this, we want to not move unless the
- reason for moving is urgent. This is BF_LATE so we can cancel
- other actions that would disrupt our hiding.*/
- Behavior "Hide Under Objects" : BF_LATE
- {
- Cancels: BV_ILLUSION, BV_CHARGING;
- On Event EV_THINK {
- hObj hItem;
- if (hasStati(HIDING,HI_UNDER)) {
- /* Don't give ourselves away! */
- RemoveActBelowPri(ACT_MOVE,P_HIGH);
- RemoveActBelowPri(ACT_SHOOT,P_HIGH);
- RemoveActBelowPri(ACT_THROW,P_HIGH);
- RemoveActBelowPri(ACT_SATTACK,P_HIGH); // spit, breathe
- RemoveActBelowPri(ACT_CAST,P_MODERATE);
- return NOTHING;
- }
- if (e.bvFlags & BV_TARGVIS)
- return NOTHING;
- /* If this is a good spot to hide, do so. */
- for (hItem=EMap->FItemAt(x,y,li);hItem;
- hItem->EMap->NItemAt(x,y,li))
- if (hItem->getSize() >= getAttr(A_SIZ))
- {
- AddAct(ACT_HIDE,P_HIGH,NULL,HI_UNDER);
- return NOTHING;
- }
- /* If there's a good hiding place within 5 squares,
- go there in order to hide. */
- if (!hasMoveObjFrom(thID))
- if (scanNearbyArea(thID,7))
- addMoveObjFromScan(P_HIGH,10,thID);
- return NOTHING;
- },
- EV_EVALSPOT {
- int16 xyli;
- xyli = EMap->getEquivLI(x,y,li,e.EXVal,e.EYVal);
- for (hItem=EMap->FItemAt(e.EXVal,e.EYVal,xyli);hItem;
- hItem->EMap->NItemAt(x,y,xyli))
- return P_HIGH;
- return -1;
- },
- EV_ISTARGET {
- return hasMFlag(M_HIDE_UNDER) ? SHOULD_CAST_IT : CANNOT_CAST_IT;
- };
- }
- /* Darkmantles and other trappers hide on the ceiling. How
- do they actually get up there? I haven't got a clue, and
- neither does the player. In truth, they just "float" up
- whenever the player can't see them, because anything else
- is too difficult to code. So, only try to hide when TRULY
- unseen, as opposed to "no target visible". */
- Behavior "Hide On Ceiling" : BF_LATE
- {
- Cancels: BV_TARGVIS, BV_AFRAID, BV_CHARGING;
- On Event EV_THINK {
- hObj hItem;
- if (hasStati(HIDING,HI_UNDER)) {
- /* Don't give ourselves away! */
- RemoveActBelowPri(ACT_MOVE,P_HIGH);
- RemoveActBelowPri(ACT_SHOOT,P_HIGH);
- RemoveActBelowPri(ACT_THROW,P_HIGH);
- RemoveActBelowPri(ACT_SATTACK,P_HIGH); // spit, breathe
- RemoveActBelowPri(ACT_CAST,P_MODERATE);
- return NOTHING;
- }
- if (e.bvFlags & BV_TARGVIS)
- return NOTHING;
- if (!isUnseen())
- return NOTHING;
- AddAct(ACT_HIDE,P_HIGH,NULL,HI_CEILING);
- },
- EV_ISTARGET {
- return hasMFlag(M_HIDE_ABOVE) ? SHOULD_CAST_IT : CANNOT_CAST_IT;
- };
- }
- Behavior "Pickup Desired Items"
- {
- Prereq: BV_HANDS;
- On Event EV_THINK {
- int16 pri;
- if (!hasTargOfType(TT_ITEM))
- return NOTHING;
- for (getFTargOfType(e,TT_ITEM);e.tsObj;
- getNTargOfType(e,TT_ITEM)) {
- pri = TargPriToActPri(e.tsPri)
- if (e.tsObj->x == x && e.tsObj->y == y)
- AddAct(ACT_PICKUP,pri,e.tsObj);
- else if (p > P_MODERATE && !hasMoveObjFrom(e.tsObj))
- addMoveObj(e.tsObj->x,e.tsObj->y,p-1,thID);
- }
- return NOTHING;
- };
- }
- /* If our equipment selections aren't optimized, and we don't have
- anything better to do, we should optimize them. */
- Behavior "Optimize Inventory"
- {
- Prereq: BV_HANDS;
- Cancels: BV_INMELEE;
- On Event EV_THINK {
- if (!(StateFlags & MS_INVEN_GOOD))
- AddAct(ACT_EQUIP,P_MODERATE);
- return NOTHING;
- },
- EV_ISTARGET {
- if (hasMFlag(M_NOHANDS) || hasMFlag(M_MINDLESS))
- return CANNOT_CAST_IT;
- return SHOULD_CAST_IT;
- };
- }
- Behavior "Buff Self"
- {
- Prereq: BV_EFFECTS;
- Cancels: BV_ENRAGED;
- Purpose: EP_BUFF;
- On Event EV_THINK {
- if (bvFlags & BV_INMELEE)
- if (random(3)) {
- AddAct(ACT_CAST,P_HIGH,NULL,EP_BUFF);
- return NOTHING;
- }
- if (!longBuffsActive())
- AddAct(ACT_CAST,P_URGENT,NULL,EP_BUFF);
- if (hasShortBuffs() && getDesperation() >= P_HIGH)
- AddAct(ACT_CAST,P_HIGH,NULL,EP_BUFF);
- return NOTHING;
- };
- }
- /* Later: Summon creatures into melee with oncoming people you
- are avoiding melee with. Discern whether you're primarily
- a brawler and shouldn't summon in melee, or of your summon
- is strong enough for it to be worthwhile in melee. */
- Behavior "Summon Aid"
- {
- Prereq: BV_EFFECTS;
- Cancels: BV_ENRAGED;
- Purpose: EP_SUMMON;
- On Event EV_THINK {
- if (getCurrPHD(PHD_MAGIC)*110 > getMaxPHD(PHD_MAGIC))
- return NOTHING;
- if (bvFlags & BV_INMELEE)
- AddAct(ACT_CAST,P_LOW+random(4),mtarg,EP_SUMMON);
- else
- AddAct(ACT_CAST,P_LOW+random(4),rtarg,EP_SUMMON);
- return NOTHING;
- };
- }
- Behavior "Avoid Melee With Effects"
- {
- Prereq: BV_EFFECTS;
- Cancels: BV_ENRAGED;
- Purpose: EP_ESCAPE|EP_FOIL_PURSUIT;
- On Event EV_THINK {
- if ((bvFlags & BV_INMELEE) && (bvFlags & (BV_AFRAID|
- BV_EVADING)))
- AddAct(ACT_CAST,P_URGENT+random(3),mtarg,EP_ESCAPE);
- else if (bvFlags & (BV_AFRAID|BV_EVADING)) {
- for (getFTargOfType(e,TT_ENEMY);e.tsObj;
- getNTargOfType(e,TT_ENEMY)) {
- if (DistFrom(e.tsObj) < 3) {
- AddAct(ACT_CAST,P_URGENT,e.tsObj,EP_FOIL_PURSUIT|
- EP_ESCAPE);
- }
- }
- }
- };
- }
- /* Old Logic, need to update -- cast invis when not using normal
- hide, but boost Hide only when hiding. */
- Behavior "Aid Stealth With Effects" : BF_ORDER
- {
- Prereq: BV_EFFECTS;
- Cancels: BV_ENRAGED, BV_TARGVIS;
- Purpose: EP_STEALTH;
- On Event EV_THINK {
- /* Old:
- if (isHiding && !isAfraid && rtarg && !isEnraged
- && (dist(rtarg->x,rtarg->y,x,y) < 6) && !random(3))
- AddAct(ACT_CAST,P_HIGH,rtarg,EP_STEALTH);
- */
- return NOTHING;
- };
- }
- Behavior "Escape Grapple" : BF_ORDER
- {
- Prereq: BV_GRABBED;
- Cancels: BV_ENRAGED;
- On Event EV_THINK {
- if ((bvFlags & BV_EVADING) || (!random(4)) ||
- (getGrappler()->getAttr(A_STR) > (getAttr(A_STR)+5)))
- AddAct(ACT_SATTACK,P_HIGH,NULL,A_ESCA);
- return NOTHING;
- },
- EV_ISTARGET {
- if (hasFeat(FT_MASTER_GRAPPLE) ||
- hasAbil(CA_NATURAL_GRAB))
- return CAN_CAST_IT;
- return SHOULD_CAST_IT;
- };
- }
- /* This is the logic behind whether a monster will use its
- gate ability or not: it will only do so when facing another
- monster 3 CRs higher then itself, or when facing a player
- character (which, given the magic item christmas tree glow
- effect, it's safe to assume smart monsters like demons and
- celestials recognize). Monsters can only attempt to gate
- once a day, hence the SPEC_TIMEOUT. */
- Behavior "Use Gate Ability" : BF_ORDER
- {
- On Event EV_THINK {
- if (bvDesperation >= PV_MODERATE)
- if (!hasStati(SPEC_TIMEOUT,A_GATE))
- AddAct(ACT_SATTACK,P_HIGH,mtarg ? mtarg : rtarg,A_GATE);
- return NOTHING;
- },
- EV_ISTARGET {
- return hasAttk(A_GATE) ? SHOULD_CAST_IT : CANNOT_CAST_IT;
- };
- }
- bool Creature::canUseAttk(int16 at, Creature *targ)
- {
- // can't breathe when polyed human -- hasAttk checks this?
- if (!hasAttk(at))
- return false;
- if (hasStati(SPEC_TIMEOUT,at))
- return false;
- if (hasStati(CANCELLED))
- return false;
- if (!targ)
- return true;
- if (knownImmune(targ,getAttk(at)->DType)
- return false;
- if (attkHitsAllies(at,targ))
- if (!(isMType(MA_EVIL) && isMType(MA_CHAOTIC)))
- return false;
- // don't keep roaring if victim already frightened
- if (isRedundantAttk(at,targ))
- return;
- if (dist(targ->x,targ->y,x,y) > getAttkRange(at))
- return false;
- return true;
- }
- Behavior "Use Breath/Spit Weapon" : BF_ORDER|BF_FORM
- {
- Requires: BV_TARGVIS;
- On Event EV_THINK {
- if (bvFlags & BV_INMELEE) {
- if (canUseAttk(A_BREA,mtarg))
- AddAct(ACT_SATTACK,P_URGENT,mtarg,A_BREA);
- if (canUseAttk(A_BRE2,mtarg))
- AddAct(ACT_SATTACK,P_URGENT,mtarg,A_BRE2);
- if (canUseAttk(A_SPIT,mtarg))
- AddAct(ACT_SATTACK,P_URGENT,mtarg,A_SPIT);
- }
- else if (rtarg) {
- if (canUseAttk(A_BREA,rtarg))
- AddAct(ACT_SATTACK,P_URGENT,rtarg,A_BREA);
- if (canUseAttk(A_BRE2,rtarg))
- AddAct(ACT_SATTACK,P_URGENT,rtarg,A_BRE2);
- if (canUseAttk(A_SPIT,rtarg))
- AddAct(ACT_SATTACK,P_URGENT,rtarg,A_SPIT);
- }
- return NOTHING;
- },
- EV_ISTARGET {
- if (hasAttk(A_BREA) || hasAttk(A_BRE2) || hasAttk(A_SPIT))
- return SHOULD_CAST_IT;
- return CANNOT_CAST_IT;
- };
- }
- Behavior "Use Sound Weapon" : BF_ORDER|BF_FORM
- {
- Requires: BV_TARGVIS;
- On Event EV_THINK {
- hObh hTarg; bool canUse;
- if (!canUseAttk(A_ROAR))
- return NOTHING;
- canUse = false;
- for (hTarg=getFTargetOfType(TT_ENEMY,e);hTarg;
- hTarg=getNTargetOfType(TT_ENEMY,e))
- if (canUseAttk(A_ROAR,e.tsObj))
- canUse = true;
- // later, consider fatigue, desperation, etc.
- if (canUse)
- AddAct(ACT_SATTACK,P_URGENT,NULL,A_ROAR);
- return NOTHING;
- },
- EV_ISTARGET {
- if (hasAttk(A_ROAR))
- return SHOULD_CAST_IT;
- return CANNOT_CAST_IT;
- };
- }
- // yellow light, gas spore, etc.
- Behavior "Kamakaze Explode Attack" : BF_ORDER|BF_FORM
- {
- Requires: BV_INMELEE;
- On Event EV_THINK {
- if (bvDesperation >= P_HIGH)
- if (canUseAttk(A_EXPL))
- AddAct(ACT_SATTACK,P_HIGH,mtarg,A_EXPL);
- return NOTHING;
- },
- EV_ISTARGET {
- return hasAttk(A_EXPL) ? SHOULD_CAST_IT : CANNOT_CAST_IT;
- };
- }
- Behavior "Use Trip Attack" : BF_ORDER
- {
- Requires: BV_INMELEE, BV_HANDS, !BV_AFRAID, !BV_ENRAGED;
- On Event EV_THINK {
- int16 tripChance;
- tripChance = 2;
- if (inSlot(SL_WEAPON) && inSlot(SL_WEAPON)->HasIFlag
- (WT_SUPER_TRIP))
- tripChance += 20;
- if (hasFeat(FT_MASTER_TRIP))
- tripChance += tripChance + 30;
- if (random(100) < tripChance)
- AddAct(ACT_TRIP,P_HIGH,mtarg);
- };
- }
- Behavior "Use Disarm Attack" : BF_ORDER
- {
- Requires: BV_INMELEE, BV_HANDS, !BV_AFRAID, !BV_ENRAGED;
- On Event EV_THINK {
- int16 disChance;
- disChance = 2;
- if (inSlot(SL_WEAPON) && inSlot(SL_WEAPON)->HasIFlag
- (WT_SUPER_DISARM))
- disChance += 20;
- if (hasFeat(FT_MASTER_DISARM))
- disChance += disChance + 30;
- if (random(100) < disChance)
- AddAct(ACT_DISARM,P_HIGH,mtarg);
- };
- }
- /* Later, archery-based coup-de-grace */
- Behavior "Use Coup de Grace" : BF_ORDER
- {
- Requires: BV_INMELEE, BV_HANDS, !BV_AFRAID, !BV_ENRAGED;
- On Event EV_THINK {
- hObj hTarg;
- if (!inSlot(SL_WEAPON))
- return NOTHING;
- for (hTarg=getFTargetOfType(TT_ENEMY,e);hTarg;
- hTarg=getNTargetOfType(TT_ENEMY,e))
- if (hTarg->isHelpless() && isBeside(hTarg) && !knownImmune
- (hTarg,AD_CRIT))
- AddAct(ACT_COUP, P_URGENT, hTarg);
- return NOTHING;
- };
- }
- int16 MonsterBrain::calcTacticalBlastPoint(rID effID, Creature *targ)
- {
- int16 range = getRange(), rat, best, bhits, hits;
- bool isGood = isMType(MA_GOOD);
- bool isChaoticEvil = isMType(MA_EVIL) && isMType(MA_CHAOTIC);
- OArray<Creature*,20,20> Allies, Enemies;
- MapIterate(m,cr,i)
- if (cr->isCreature() && !m->isSolidAt(cr->x,cr->y,cr->li) &&
- dist(cr->x,cy->y,x,y) < range*2-1) {
- if (cr->getGroupInfo() == getGroupInfo())
- Allies.Add(cr);
- else if (cr->isHostileTo(this))
- Enemies.Add(cr);
- else if (isMType(MA_GOOD) && cr->isMType(MA_SAPIENT))
- Allies.Add(cr);
- }
- brat = -10000000;
- for (xx=targ->x-range;xx<=targ->x+range;xx++)
- for (yy=targ->y-range;yy<=targ->y+range;yy++)
- if (m->InBounds(xx,yy)
- {
- hitEnemy = hitAlly = hitSelf = 0;
- foreach(Allies)
- if (dist(x,y,xx,yy) <= range)
- hitAlly++;
- foreach(Enemies)
- if (dist(x,y,xx,yy) <= range)
- hitEnemy++;
- if (dist(x,y,xx,yy) <= range)
- hitSelf++;
- if (isGood)
- rat = hitEnemy - hitAlly*1000 - hitSelf*2;
- else if (isChaoticEvil)
- rat = hitEnemy - hitAlly - hitSelf*1000;
- else
- rat = hitEnemy - hitAlly*5 - hitSelf*1000;
- if (rat > brat)
- { brat = rat; best = xx+yy*256; }
- }
- return brat > 0 ? best : -1;
- }
- bool isBullRushWise(hObj hMe, hObj hTarg)
- {
- int16 me, targ;
- me = hMe->getAttr(A_STR) + hMe->hasFeat(FT_MASTER_BULL_RUSH)*7 +
- hMe->getAttr(A_SIZ)*5;
- targ = hTarg->getAttr(A_STR) + hTarg->getAttr(A_SIZ)*5+
- hTarg->getAbilLev(CA_STABILITY)*4+
- hTarg->ResistLevel(AD_KNOC)*3;
- return me >= targ;
- }
- /* Later, use force bolt and gust of wind likewise
- /* Later, scan all melee targets */
- Behavior "Bull Rush Enemies Onto Bad Terrain"
- {
- Prereq: !BV_AFRAID, !BV_EVADING, BV_INMELEE, !BV_ENRAGED;
- On Event EV_THINK {
- int16 d, tx, ty, tx2, ty2;
- d = DirTo(mtarg);
- tx = mtarg->x + DirX(d);
- ty = mtarg->y + DirY(d);
- tx2 = mtarg->x + DirX(d)*2;
- ty2 = mtarg->y + DirY(d)*2;
- if (EMap->getWarnAt(tx,ty) || EMap->getWarnAt(tx2,ty2))
- if (EMap->isPBadSquareFor(EActor,mtarg,tx,ty) ||
- EMap->isPBadSquareFor(EActor,mtarg,tx2,ty2))
- if (isBullRushWise(this,mtarg))
- AddAct(ACT_SATTACK,P_URGENT,mtarg,A_BULL);
- },
- EV_ISTARGET {
- return (EActor->getAttr(A_STR) > 14 && !EActor->isSessile()) ?
- SHOULD_CAST_IT : CAN_CAST_IT;
- };
- }
- /* Scan targets for incorporeality. If so:
- * if you've got force damage, use it
- * if you can, use Manifestation
- * if you've got ki strike, everything's good
- * if you've got a holy weapon and they're evil,
- everything's good
- * phase to meet them when in melee, if possible
- * setEquipContext(thID) to equip ghost touch, etc.
- * Drink potions of faerie if you got em
- * If you can't fight, evade these creatures specifically
- with a new movement objective
- */
- Behavior "Cope With Incorporeality"
- Behavior "Make Melee Attacks"
- {
- Prereq: BV_INMELEE, !BV_AFRAID;
- On Event EV_THINK {
- hObj hWep;
- hWep = inVSlot(SL_WEAPON);
- if (bvFlags & BV_ENRAGED)
- if (getStatiObj(ENRAGED) != mtarg) {
- if (isBeside(getStatiObj(ENRAGED)))
- mtarg = getStatiObj(ENRAGED);
- else
- return NOTHING;
- }
- if (hWep && !hWep->hasIFlag(WT_NO_MELEE)) {
- /* When fighting incorporeals, fight with melee weapons by
- preference
- if we have ghost touch, but only hit with magical weapons
- (==5% chance
- to strike true per plus) if we don't have any better
- option.
- TODO: Check for holy weapon vs. evil, etc. */
- if ((onPlane() == mtarg->onPlane()) || hWep->hasQuality
- (IQ_GHOST_TOUCH) ||
- hasStati(MANIFEST))
- AddAct(ACT_WATTACK,P_HIGH,mtarg);
- else if (hWep->getPlus() > 0)
- AddAct(ACT_WATTACK,P_LOW,mtarg);
- }
- if (hWep == NULL_OBJ)
- if ((onPlane() == mtarg->onPlane()) || hasAbility
- (CA_KI_STRIKE) ||
- hasStati(MANIFEST))
- AddAct(ACT_NATTACK,P_HIGH,mtarg);
- /* LATER, tweak the priority here to depend on strategy, among
- other things.
- Maybe we need some functions specifically to adjust
- priorities based on
- strategy. */
- if ((hasEffFor & EP_BLAST) && !(bvFlags & BV_ENRAGED))
- AddAct(ACT_EFFECT,P_URGENT,mtarg,EP_BLAST,CAST_INNATE_ONLY|
- CAST_NO_AOO_ONLY);
- return NOTHING;
- };
- }
- Behavior "Make Archery Attacks" : BF_INVEN
- {
- Prereq: !BV_INMELEE, !BV_AFRAID, BV_TARGVIS, !BV_ENRAGED;
- On Event EV_THINK {
- hObj hBow;
- hBow = isAdvancedInv() ? inVSlot(SL_WEAPON) : inVSlot
- (SL_ARCHERY);
- if (hBow && hBow->isType(T_BOW))
- if (EMap->LineOfFire(this,rtarg)) {
- if (it->needsToBeCocked())
- AddAct(ACT_LOADXBOW,P_MODERATE,hBow);
- else {
- AddAct(ACT_RATTACK,P_HIGH,rtarg);
- }
- }
- return NOTHING;
- },
- EV_ISTARGET {
- hObj hItem;
- if (hasMFlag(M_NOHANDS))
- return CANNOT_CAST_IT;
- for (hItem=FirstInv();hItem;hItem=NextInv())
- if (hItem->isType(T_BOW))
- return SHOULD_CAST_IT;
- return CAN_CAST_IT;
- };
- }
- Behavior "Use Ranged Attack Effects"
- {
- Prereq: !BV_INMELEE, !BV_AFRAID, BV_TARGVIS, !BV_ENRAGED,
- BV_EFFECTS;
- Purpose: EP_BLAST|EP_CURSE;
- On Event EV_THINK {
- /* Later, prioritize by strategy... */
- if (rtarg && EMap->LineOfSight(this,rtarg) &&
- EMap->LineOfFire(this,rtarg))
- AddAct(ACT_EFFECT,P_URGENT+random(2),rtarg,EP_ATTACK|
- EP_CURSE);
- return NOTHING;
- };
- }
- Behavior "Use Thrown Weapons" : BF_INVEN
- {
- Prereq: !BV_AFRAID, !BV_ENRAGED, BV_HANDS, BV_TARGVIS;
- On Event EV_THINK {
- hObj hThrown, hTarg, hBest;
- hTarg = rtarg ? rtarg : mtarg;
- hBest = NULL_OBJ;
- for(hThrown=FirstInv();hThrown;
- hThrown=NextInv())
- if (hThrown->isGroup(WG_THROWN))
- if (hBest == NULL_OBJ ||
- hThrown->isBetterVersus(hTarg,hBest))
- hBest = hThrown;
- if (hBest != NULL_OBJ)
- AddAct(ACT_THROW,P_HIGH,hTarg,hBest);
- return NOTHING;
- },
- EV_ISTARGET {
- hObj hThrown;
- for(hThrown=FirstInv();hThrown;
- hThrown=NextInv())
- if (hThrown->isGroup(WG_THROWN))
- return SHOULD_CAST_IT;
- return CANNOT_CAST_IT;
- };
- }
- Behavior "Enraged Movement"
- {
- Prereq: BV_ENRAGED, !BV_SESSILE;
- On Event EV_THINK {
- if (!isBeside(getStatiObj(ENRAGED)))
- AddAct(ACT_MOVE,P_EXTREME,getStatiObj(ENRAGED));
- return NOTHING;
- };
- }
- Behavior "Move To Melee"
- {
- Prereq: !BV_ENRAGED, !BV_INMELEE, !BV_MOUNT, !BV_EVADING, !
- BV_SESSILE, BV_TARGVIS;
- On Event EV_THINK {
- /* Usually movement into melee is low priority, but sometimes
- (1 in 5) it gets higher, so we always keep moving toward
- the target. */
- AddAct(ACT_MOVE,random(5) ? P_LOW : P_URGENT,rtarg);
- return NOTHING;
- };
- }
- Behavior "Move To Flee"
- {
- Prereq: BV_AFRAID, !BV_SESSILE;
- On Event EV_THINK {
- AddAct(ACT_MOVE,P_HIGH,NULL);
- return NOTHING;
- };
- }
- Behavior "Move To Avoid Melee"
- {
- Prereq: BV_EVADING, !BV_SESSILE, BV_TARGVIS;
- On Event EV_THINK {
- bool hasEff;
- hasEff = (hasEffFor & EP_ESCAPE);
- if (mtarg)
- AddAct(ACT_MOVE,hasEff ? P_MODERATE :
- P_EXTREME,mtarg,MOVE_AWAY);
- else if (rtarg && DistFrom(rtarg) < getIdealRange(this))
- AddAct(ACT_MOVE,hasEff ? P_LOW : P_HIGH,mtarg,MOVE_AWAY);
- return NOTHING;
- };
- }
- Behavior "Break Off Melee by XCR"
- /* If total XCR of melee enemies is 1/5th the monster's,
- and there's a target in sight you either really hate
- or is more than 3x to total XCR of your melee opponents,
- run from melee, suck up the AoOs and pursue the real
- threat.
- This often happens when the PC spams low-level monster
- summoning spells to prevent enemies from moving forward.
- Now they have to spam high-level summons. :)
- */
- Behavior "Leave Melee to Pursue Hated Target"
- Behavior "Leave Summoned Melee to Pursue Summoner"
- /* Animals will panic, and become hostile for a limited number
- of turns, if other creatures step too close to them when they
- are outside their natural environment. They don't do this if
- they've had Animal Empathy (calm) or Calm Animals used on
- them. */
- Behavior "Panic When Approached"
- Behavior "Offer Sacrifice at Altar"
- Behavior "Melt Ice Terrain With Fire"
- -- isTerOnPath($"Ice Wall",5)
- Behavior "Collapse Wall Spell With Effects"
- Behavior "Fly Over Barrier"
- Behavior "Flee Down Stairs/Portal"
- -- use getComparativeStrengthMod() in evaluating whether
- to attack head on, use skirmish tactics, etc.
- Behavior "Use Passwall Smartly"
- Behavior "Counter Earth Meld"
- -- avoid standing near walls, for one thing
- -- prioritize EP_FOIL_ESCAPE strongly
- -- use Rock to Mud, etc.
- -- strike as hard as possible when melder is exposed,
- try for one-hit kills
- Behavior "Smash Altars of Enemy Gods"
- Behavior "Attempt Altar Conversion"
- -- only if Know(Theo) + FavorLev is high enough
- Behavior "Talk To New People"
- -- for aranea, and random others -- approach each person
- you don't have a TRIED stati for this behavior/person
- combo and use the greet action to chat with them, then
- mark them as tried and continue. Move toward them with
- a movement objective before chatting.
- Behavior "Drink From Fountain"
- -- walk toward and drink from a fountain if you have high
- luck, are diseased or poisoned, are seriously XP drained
- or what have you.
- Behavior "Travel Via Fountain"
- -- Oozes - anyone with M_AMORPH, really - can use a special
- teleport-like effect to travel from fountain to fountain
- on EV_DESCEND.
- Behavior "Bodyguard"
- -- Move to intercept, sprinting if necessary, any character
- that approaches the protected creature.
- -- Break off other activities to reach anyone attacking the
- protected in melee, and use great blows, throws, spells
- with knockback and bull rush to get them away.
- /* Flying creatures with ranged attacks in areas with a high
- (or no) ceiling should stay elevated and strike at ground-
- bound creatures from above, where they can't fight back.
- Only fly down to enter melee when all ranged attacks are
- expended or timed out. Also, be reluctant to enter areas
- with a lower ceiling to avoid losing the flight advantage. */
- Behavior "Fly Intelligently"
- /* Behavior for Maeve's creatures -- be hostile to ugly or
- repellent creatures, in order to test the ability of
- behaviors to modify target system hostility rules. */
- Behavior "Hostile to Uglyness"
- Behavior "Pray For Aid"
- Behavior "Seek, Tame and Ride Mount"
- /*
- -- count total enemies in LineOfFire by iterating targets
- -- choose priority by counting enemies
- -- determine how many enemies would still have LOF if the
- wall was cast in each of the four cardinal directions
- -- cast in whichever direction comes closest to dividing
- the enemies exactly in half
- -- if every direction is all-or-nothing, do not cast
- */
- Behavior "Use Wall Spell to Divide Enemies"
- Behavior "Block Casters' Line-of-Sight"
- -- use darkness, obscurement, call light, cause blindness,
- wall of fog, etc.
- Behavior "Use Wind Wall Against Archers"
- Behavior "Use Wall Spell To Gain Buff Time"
- /* When an enemy target has getRecentMoveVector() pointed away
- from you -- either fleeing or avoiding melee, doesn't matter
- -- and he's faster than you:
- (1) cast EP_FOIL_PURSUIT effects
- (2) throw tanglefoot bags
- (3) try to blind or slow him at range
- (4) sprint, if it makes you fast enough
- (5) choose a different target rather than chasing */
- Behavior "Handle Fast Escaping Target"
- AI Targetting:
- Okay, so over the last week I've been putting in a lot
- of work recoding the monster targeting system -- the
- code that decides what they consider hostile or allied
- and how they prioritize those targets. I'm hoping to
- move much farther along on this task -- maybe finish --
- over the weekend.
- There will be three major benefits of this. First and
- foremost, I'll understand the code a lot better. It was
- refactored once by Westley Weimer, and while he did
- a great job I never clearly understood everything. Now
- I know it in and out, so I'll be able to fix persistant bugs;
- my list includes:
- * NPCs hostile to your mount
- * "hostile, formerly peaceful" after Quell
- * Mara's hostile Mourners
- * Your mount hostile to you.
- * Fighting among your allies
- * Monsters neutrals to you attacking your allies
- The recoding should make several of these issues
- irrelevant and obsolete, and the rest easy to fix. I welcome
- reminders from people here for anything else that belongs
- on this list.
- The second major benefit is that the target system now
- fits the overall source standards of the game, and is fully
- accessable from IncursionScript -- the customization and
- modularity stuff you hear me ranting so much about. The
- final, and most interesting benefit, is that parties (groups
- of allied creatures -- both your own and monster warbands)
- are now much more manipulable as distinct entities. This
- should alleviate some wierd situations (like the chieftan
- being generated neutral and his troops hostile), and it will
- also allow me to add a lot of fun flashy new features,
- including:
- * When you kill a group leader, the group decides on a
- new leader, and can sometimes splinter in the process.
- * When a leader is weakened, another monster can lead
- a coup to try and take over a group, possibly splitting
- it into two inimical factions.
- * When you enlist a creature that another creature in your
- group hates, that creature might quit your group over the
- act, or otherwise react.
- * Monsters willl be able to use the Enlist function to recruit
- others to their group just the way the PC can (though they
- can't enlist the PC, who is always the leader of his group).
- * NPC spellcasters will be able to support their allies and
- leader more sensibly, casting healing spells and such.
- * The concept of "strain" will handle creatures snapping and
- turning in a more nuanced manner, letting offenses build
- up over time and making it less of an all or nothing issue.
- * Monster CR and Charisma influences their level of influence
- in their group, and how much "say" they have in what it is
- and isn't hostile to.
- The general design philosophy here is that in normal
- circumstances hostility is determined on a whole-party
- level. Thus, for example, a warband of 6 CE orcs and
- one NG orc will all be hostile to the player, and a band
- of 5 LG dwarves and one LE dwarf will all be neutral.
- These averaged hostilities are calculated mathamatically
- and are based on strength of animosity as well as
- numbers. It's actually quite a fiddly, simulationist system,
- because I like that kind of thing, even if all the detail isn't
- visible at the surface level.
- Creatures who would be neutral to your personally but
- are hostile because of their party leaning (or vice versa)
- will be noted as "reluctantly hostile" or "reluctantly neutral".
- This will be useful for Semirath worshippers to spot the
- good orcs they aren't supposed to kill, for example.
- The question is when this "whole group hostility" system
- should deviate. For example, if a player character charms
- one member of a hostile band, the /charmed/ member
- shouldn't stay hostile, nor should the entire group turn
- friendly. What about Quell? Should it be limited to groups?
- How "bad" are divided groups? I'm not sure it makes sense
- to quell only part of an adventuring party and then have them
- stand idly by while you slaughter the rest. If you Quell them,
- should they be treated as having left the party?
- Another confusing issue is the various "affect all monsters
- of type N" conditions, such as elves' sylvan affinity (making
- all animals neutral), a stone of undead warding, a ring of
- aggrivate monster or Kysul's wrath effect. These things are
- going to be causes of players running into half hostile, half
- neutral. If an elf meets a bloodthirsty grey druid with his
- animal companions, should the animals be hostile? What
- if the druid is the PC and the elf the monster? Should animal
- companions refuse to attack characters that have Animal
- Friendship active? Should it add strain? I'm not sure what
- the best way to deal with this is.
- So I'm interested in any feedback from players about
- anything I'm saying here. Specifically, I'm very enthused
- with all the flash and simulationism of this, but I don't want
- to let that get in the way of practicality. In other words,
- what rules should I implement in the new target system
- to avoid frusteration and keep players' blood pressure
- under control? :)
- ----
- Tangentally, another major addition will be the Behaviour
- system, which is essentially rule-based logic for monsters --
- resources that modify how monsters act in certain situations,
- when conditions are met. I'm brainstorming for different
- Behaviours to add now, and welcome suggestions. A lot of
- things currently hardcoded (like monsters fleeing or casters
- avoiding melee) will become Behaviour resources. Further
- ideas include:
- * When monster has a vial of poison and Poison Use skill,
- poison an unpoisoned, wielded weapon.
- * Switch weapons contextually to attack players with the
- weapons that hit observed "resistance holes". (Monsters
- will have the ability to 'learn' weaknesses, but that has
- nothing to do with behaviors.)
- * Use Jump intelligently in combat if the monster has it as
- a skill. Same with Turn Undead, Sprinting, Whirlwind
- Attack, etc. -- behaviours make this kind of thing much
- easier to "teach" the monsters to use.
- * Kobolds will actally reset traps in the game. Likewise,
- any monster with Handle Device might disarm them.
- * Fleeing humanoid monsters will close and/or lock doors
- to stop those they're fleeing from.
- * Lawful monsters might spontaneously surrender rather
- than fleeing when afraid, as requested.
- * Monsters can initiate a revolt to try to take over their
- group, as discussed above.
- * Monsters can try to enlist other monsters, if they have
- Diplomacy et al. Later same for Cow, Quell, etc. --
- though obviously not against the player.
- * Casters fire /dispel magic/ at heavily buffed creatures,
- /shatter/ at characters with fragile equipment, Will save
- spells at fighter types, Fort saves at spindly types,
- /dimensional anchor/ at people they've seen teleport,
- /dismissal/ at summoned creatures, etc. (Behaviours
- were concieved with the "counter X with Y" mentality
- and will make a lot of the very powerful magic in
- Incursion more balanced).
- * Monsters will flee, avoid melee or use only non-standard
- attacks (wands, etc.) against a creature that is phased
- or immune to their weapons.
- * Monsters with Sneak Attack will move into flanking
- positions, seek to stun the player, etc.
- * Monsters being shot with arrows by creatures they can't
- see will put up missile defense effects, flee away from
- the direction the arrows are coming, etc.
- * When chaotic and/or evil monsters surrender, they will
- sometimes treacherously attack again afterward (at which
- time they can be freely killed no matter what they do).
- * Neutral monsters will react more positively to characters
- who heal their wounds, cure diseases, etc.
- * I'm also going to use behaviors to implement monster
- actions with no game effect, just for flavor (The orc glares
- at the elf. The horse eats grass. The human warrior paces.
- The floating eye scans the vicinity. The gnome mage chats
- with the human druid.)
- * I'm debating adding the hunger mechanic for monsters,
- especially beasts, but I worry about the player starving the
- whole dungeon level to death by resting six or seven times
- in a row. Don't know how I want to deal with this. Even if
- not, monsters could still eat things even if they don't /have/
- to in order to survive.
- * This system will also allow custom plot-related behaviours
- for select groups, like any surviving goblins in the goblin
- camp becoming permanently afraid if you kill Murgash, just
- because that's cinematic and badass.
- I'm not saying I'm going to do all this stuff right away for
- the next release -- I'll build up behavior lists over time, just
- like I have with spells. But the cool part is that adding new
- behaviors is as independant of the rest of the game as
- adding new monsters or spells, which makes it really
- flexible.
- So basically I'm soliciting for suggestions from players for
- things monsters (or a small or large subset thereof) could be
- programmed to do, that people think would make the game
- better.
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement