AI scripting examples
Would anyone who has done AI scripting successfully care to post some examples/explanations for us to learn from? I've not seen specific AI scripting examples discussed in much detail here and think there's quite a bit of interest in making interesting monster behavior.
I've tried a lot of stuff with inconsistent success at best. (at least I have fun trying). I've looked at the x2_ai_... options but clearly don't appreciate everything that is being done in those and why.
I've tried a lot of stuff with inconsistent success at best. (at least I have fun trying). I've looked at the x2_ai_... options but clearly don't appreciate everything that is being done in those and why.
0
Comments
1. This is my basic AI script for my Darkener NPC to ensure that it casts darkness when I want (or try to dispel UV/true seeing if they have that up). I've put some comments about parts I don't understand/appreciate what they do. Comments/explanations are welcome.
2. Share a script to show off what you did. (Maybe AI scripts are inherently too long and complicated for this format, I dunno.)
void main() { object oIntruder = GetCreatureOverrideAIScriptTarget(); ClearCreatureOverrideAIScriptTarget(); //If we're already doing something, don't try something else. if(__InCombatRound()) return; object oSelf = OBJECT_SELF; string sTag = GetTag(oSelf); object oEnemy = bkAcquireTarget(); //In case our target isn't valid, there should at minimum be a nearby PC... assuming we can see them. if( !GetIsObjectValid(oEnemy) ) { oEnemy = GetNearestCreature(CREATURE_TYPE_PLAYER_CHAR,PLAYER_CHAR_IS_PC,OBJECT_SELF,1,CREATURE_TYPE_IS_ALIVE,TRUE,CREATURE_TYPE_PERCEPTION,PERCEPTION_SEEN); //if( GetIsObjectValid(oEnemy) ) AssignCommand(GetObjectByTag("SCREAMER"),ActionSpeakString("oEnemy wasn't valid, got a new one.",TALKVOLUME_SHOUT));//DEBUG //else AssignCommand(GetObjectByTag("SCREAMER"),ActionSpeakString("Tried to get a new oEnemy but failed.",TALKVOLUME_SHOUT));//DEBUG } if (GetIsObjectValid(oEnemy)) { //OK, we are officially doing something so let's say so to avoid it being interrupted by other AI calls. __TurnCombatRoundOn(TRUE); //Only clear actions if we have a valid enemy and therefore are going to decide what to do. ClearAllActions(); //I want all NPCs to do this stuff //As far as I can tell, the only things these next few lines make this NPC do are throw up its elemental protection. //Even though the NPC has a number of assigned abilities in the toolset, it doesn't use any of them... //This makes sense because this NPC has no healing capability. if(TalentPersistentAbilities()) // * Will put up things like Auras quickly { __TurnCombatRoundOn(FALSE); return; } else if(TalentHealingSelf()) { __TurnCombatRoundOn(FALSE); return; } else if(TalentHeal()) { __TurnCombatRoundOn(FALSE); return; } //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// ///////////////START NPC SPECIFIC AI ACTIONS//////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// if( sTag == "darkman" ) { if( !GetHasSpellEffect(SPELL_DARKVISION,oEnemy) && !GetHasSpellEffect(SPELL_TRUE_SEEING,oEnemy) && !GetHasSpellEffect(SPELL_DARKNESS,oEnemy) ) { if( GetHasSpell(SPELL_DARKNESS,oSelf) ) { ActionCastSpellAtLocation(SPELL_DARKNESS,GetLocation(oEnemy)); } else { //They don't have any effects but we don't have any Darkness left anyway, so let's quit this special AI altogether. //Since I achieved what I wanted I can get the normal AI to completely take over by just deleting to custom AIScript var. //If I want custom AI actions to resume, I set the AIScript var again in x2_def_userdef when the right conditions are met. //It's the only way I know right now to get specific behavior when I want, and normal AI the rest of the time. ActionAttack(oEnemy); DeleteLocalString(oSelf,"AIScript"); return; } } else if( GetHasSpell(SPELL_LESSER_DISPEL,oSelf) && GetHasSpellEffect(SPELL_DARKVISION,oEnemy) || GetHasSpellEffect(SPELL_TRUE_SEEING,oEnemy) ) { ActionCastSpellAtLocation(SPELL_LESSER_DISPEL,GetLocation(oEnemy)); } else if( GetDistanceBetween(oSelf, oEnemy) > 5.0 ) { bkEquipRanged(oEnemy,FALSE,TRUE); ActionAttack(oEnemy); } else { bkEquipMelee(); //Don't quit the specific AI completely yet if we still can cast darkness. if( GetHasSpell(SPELL_DARKNESS,oSelf) ) { ActionAttack(oEnemy); } else { ActionAttack(oEnemy); DeleteLocalString(oSelf,"AIScript"); return; } } } //We're no longer doing specific stuff so allow new actions to be determined. __TurnCombatRoundOn(FALSE); } //This has to be here because! (I never tested what happens when it isn't, but the ai_demo said so. Explanations welcome.) SetCreatureOverrideAIScriptFinished(OBJECT_SELF); }in the hakpack. all the ai scripts are charactername+ai. although they won't function without all the other scripts, etc.
and. the char ai script... it's long, and broken lol.
#include "sborneweaponmult" //searches surroundings for any visible objects object detectobject(object self, int nth, float distance); //true if object is within sight cone int objectperceived(object self, object target); //true if target within angle of origins facing direction int conespread(object origin, object target, int coneangle); //true if target within angle of origins. checks from facing direction of origin+angle offset int conespreadfromangle(object origin, object target, float coneangle, float angleoffset); //generates a random float between min & max float randomfloat(float min, float max); //returns relationship value. seperate to vanilla NWN reputation system float getrelation(object self, object target); //modifies relationship by value void modrelationvalue(object self, object target, float value); //basically just teleports current character to a connected door void transitionarea(object self, object door, int transitioning = FALSE); //compares self to target to see if the target appears dangerous to the character. basically myscore/enemyscore float intimidationvaluecomp(object self, object target); //simple script to get char to rotate, and look for threats void checkforthreat(object self, int duration = 4); //same as obstacle script, but for fleeing AI. checks from a further distance to help them avoid enemies int foundenemy(object self, object enemy, float offset); //used in simple pathing script used when char has nothing else to do. makes sure they don't walk into stuff int foundobstacle(object self, object enemy, float offset); //causes char to throw strikes. early action combat system. void attack(object self, object target, int hand, int combo); //below are darksouls spells. mostly are designed to function as in darksouls. int divint(int num1, int num2); float divfloat(float num1, float num2); void greatcombustion(object self, object enemy); void firetempest(object self); void greatfireball(object self, object target); //Thank you to the NWN community. especially from nwnLexicon for this + rgbcolours function const string colourtoken = " ##################$%&'()*+,-./0123456789:;;==?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[[]^_`abcdefghijklmnopqrstuvwxyz{|}~~€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ¡¡¢£¤¥¦§¨©ª«¬¬®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþþ"; const string colorend = "</c>"; int roundupfloat(float round) { int r = FloatToInt(round); if((IntToFloat(r) + 0.5) <= round) { return r+=1; } else { return r; } } string rgbcolours(int nRed=255, int nGreen=255, int nBlue=255) { return "<c" + GetSubString(colourtoken, nRed, 1) + GetSubString(colourtoken, nGreen, 1) + GetSubString(colourtoken, nBlue, 1) + ">"; } string levelbar(object self, float current, float max, int red, int green, int blue) { if((current < 0.0 && max > 0.0) || (current > 0.0 && max < 0.0)) { current = 0.0; max = 0.0; } if(current == 0.0 || max == 0.0) { return "("+rgbcolours(90, 90, 90)+"NNNNNNNNNNNN"+colorend+")"; } int percent = roundupfloat((current/max)*12); int remainder; string bar = "("; if(percent > 12) { bar += rgbcolours(red, green, blue); remainder = 12; while(percent > 0) { remainder--; percent--; if(remainder == 0) { bar+= colorend+")"+rgbcolours(red, green, blue); } bar +="N"; } return bar+"|"+colorend; } else if(percent == 12) { return "("+rgbcolours(red, green, blue)+"NNNNNNNNNNNN"+colorend+")"; } else { remainder = 12-percent; bar += rgbcolours(red, green, blue); while(percent > 0) { percent--; bar +="N"; } bar += "|"+colorend+rgbcolours(90, 90, 90); while(remainder > 0) { remainder--; bar +="N"; } } return bar+colorend+")"; } void main() { object self = OBJECT_SELF; object belts = GetLocalObject(self, "belts"); object inventory = GetLocalObject(self, "inventory"); object bonfire = GetLocalObject(self, "bonfire"); object door; int struck = GetLocalInt(self, "struck"); int stuck; int busy = GetLocalInt(self, "busy"); int humanity = GetLocalInt(self, "chaos"); if(humanity < 5) { SetLocalInt(self, "hollowing", TRUE); } else { SetLocalInt(self, "hollowing", FALSE);} int hollowing = GetLocalInt(self, "hollowing"); if(humanity < 1) { SetLocalInt(self, "hollow", TRUE); } else { SetLocalInt(self, "hollow", FALSE); } int hollow = GetLocalInt(self, "hollow"); int incombat = GetLocalInt(self, "incombat"); int emptyestusflasks; int creatureblood; float friendrank = IntToFloat(GetLocalInt(self, "friendrank")); int followtimer = GetLocalInt(self, "followtimer"); object flasks = GetFirstItemInInventory(belts); while(GetIsObjectValid(flasks)) { if(GetTag(flasks) == "ercertearemp") { emptyestusflasks += GetItemStackSize(flasks); } if(GetTag(flasks) == "ds1blood") { creatureblood += GetItemStackSize(flasks); } flasks = GetNextItemInInventory(belts); } float happiness = 100.0; float currenthealth = GetLocalFloat(self, "currenthealth"); float fullhealth = GetLocalFloat(self, "health"); float currentmagicka = GetLocalFloat(self, "currentmagicka"); float fullmagicka = GetLocalFloat(self, "magicka"); float currentadrenaline = GetLocalFloat(self, "currentadrenaline"); float maxadrenaline = GetLocalFloat(self, "maxadrenaline"); float fear; float bravery = IntToFloat(GetLocalInt(self, "bravery")); float comfort = GetLocalFloat(self, "comfort"); float comforttolerance = GetLocalFloat(self, "comforttolerance"); float comfortimportance = GetLocalFloat(self, "comfortimportance"); effect cycleeffect = GetFirstEffect(self); effect poison = cycleeffect; effect invisible; //-------------------Self variables object target = GetNearestObject(OBJECT_TYPE_ALL, self); object friend; object enemy; object injured; object poisoned; object humanitymine; object nearbybonfire; object caution; int hashealth; int nth = 1; int nearbyenemycount; int enemycount; int deadenemies; float nearestenemydist; float enemyforcestrength; float adrenalinemult = currentadrenaline; if(adrenalinemult < 10.0) { adrenalinemult = 10.0; } //-------------------world variables //makes aggressive AI threshold higher if character has gone hollow. they won't have to hate //people as much to attack them. float relrank; switch(hollowing) { case FALSE: relrank = 30.0; break; default: switch(hollow) { case FALSE: relrank = 50.0; break; default: relrank = 90.0; break; } break; } //custom function related to hak. basically just displays hlth/stam/magic bars if(GetLocalInt(GetLocalObject(self, "summoner"), "displaystatusbar")) { SpeakString(" HP "+levelbar(self, GetLocalFloat(self, "currenthealth"), GetLocalFloat(self, "health"), 240, 120, 30)+ "\nMP "+levelbar(self, GetLocalFloat(self, "currentmagicka"), GetLocalFloat(self, "magicka"), 110, 120, 255)+" ["+FloatToString(10*GetLocalFloat(self, "currentmagicka"), 0, 0)+"]\n STM"+levelbar(self, GetLocalFloat(self, "currentstamina"), GetLocalFloat(self, "stamina"), 130, 255, 170)+"\n\n"+rgbcolours(205, 200, 120)+"{} "+IntToString(GetLocalInt(self, "chaos"))+colorend, TALKVOLUME_WHISPER); } //if health has been lowered in any way. we have been hit. very simplistic atm if(GetCurrentHitPoints(self) < GetLocalInt(self, "lasthealthd&d") || currenthealth < GetLocalFloat(self, "lasthealth")) { struck = TRUE; //all mentions of fear below are counted, and then seen if they surpass our courage scores. causes //fleeing behaviours fear += GetLocalInt(self, "lasthealth")-currenthealth; } //Object detection. basically looks around to see if anything is seen, and of interest while(GetIsObjectValid(target)) { if(objectperceived(self, target)) { if(GetObjectType(target) == OBJECT_TYPE_CREATURE) { if(!GetIsDead(target) && !GetLocalInt(target, "dead")) { //if the current detected creature is showing aggression in our direction. blame them all for striking us if(struck && conespread(target, self, 40) && (GetIsInCombat(target) || GetLocalInt(target, "incombat"))) { modrelationvalue(self, target, -18.0*(adrenalinemult/10)); fear += (intimidationvaluecomp(self, target)/3); if(!GetIsObjectValid(caution)) { caution = target; } } //detected creature is acting hostile. but hasn't attacked yet else if(GetLocalObject(target, "followtarget") != self && (conespread(target, self, 25) && (GetIsInCombat(target) || GetLocalInt(target, "incombat"))) || (conespread(target, friend, 25) && (GetIsInCombat(target) || GetLocalInt(target, "incombat")))) { modrelationvalue(self, target, -5.0*(adrenalinemult/10)); fear += (intimidationvaluecomp(self, target)/10); if(!GetIsObjectValid(caution)) { caution = target; } } //if character is disliked enough to surpass our aggression threshold. save them for violence. flat values atm if(getrelation(self, target) <= relrank) { incombat = TRUE; SetLocalInt(self, "incombat", TRUE); enemycount++; //closer objects are more intimidating. if(GetDistanceToObject(target) > 15.0) { fear += (intimidationvaluecomp(self, target)/3)/3; } else { fear += intimidationvaluecomp(self, target)/3; } fear += intimidationvaluecomp(self, target)/3; if(GetDistanceToObject(target) < 13.0) { nearbyenemycount++; } if(!GetIsObjectValid(enemy)) { enemy = target; nearestenemydist = GetDistanceToObject(target); } } else if(getrelation(self, target) >= friendrank) { target = friend; //nearby allies. especially strong ones, make us feel safer if(GetDistanceToObject(target) > 15.0) { fear -= (intimidationvaluecomp(self, target)/3)/3; } else { fear -= intimidationvaluecomp(self, target)/3; //vestigial from other chars script. for curing poisoned friends if(!GetIsObjectValid(poisoned)) { poison = GetFirstEffect(target); while (GetIsEffectValid(poison)) { if((GetEffectType(poison) == EFFECT_TYPE_POISON || GetEffectType(cycleeffect) == EFFECT_TYPE_DISEASE || GetLocalInt(target, "scarletrot")) && !busy) { target = poisoned; poison = cycleeffect; break; } poison = GetNextEffect(target); } } //as above if(!GetIsObjectValid(injured)) { if((FloatToInt(GetMaxHitPoints(target)*0.75) >= GetCurrentHitPoints(target) || (GetLocalFloat(target, "health")*0.75) >= GetLocalFloat(target, "currenthealth")) && !GetLocalInt(target, "regeneratinghealth")) { injured = target; } } } } //character seems to be showing aggression to our current combat target. so we trust them more if((conespread(target, enemy, 25) && (GetIsInCombat(target) || GetLocalInt(target, "incombat")))) { modrelationvalue(self, target, 3.0*(currentadrenaline/10)); } if(conespread(target, humanitymine, 25)) { modrelationvalue(self, target, 3.0*(currentadrenaline/10)+deadenemies); } } else //if target is dead { if(getrelation(self, target) <= relrank) { deadenemies++; } //another vestigial feature. will re-incorporate it. pings dead character as prime sustanence //for some juicing souls if(!GetIsObjectValid(humanitymine) && !GetLocalInt(target, "humanitydrawn")) { humanitymine = target; } } } else //if target isn't creature { //for below door function. character automatically uses any areatransition door within 8m, to allow them to "dynamically" travel through the module. if(!GetIsObjectValid(door) && GetIsObjectValid(GetTransitionTarget(target)) && GetDistanceToObject(target) <= 8.0) { door = target; } } } nth++; target = GetNearestObject(OBJECT_TYPE_ALL, self, nth); } if(fear < 0.0) { fear = 0.0; } //adrenaline system keeps character feeling on edge until it runs out of their system. //simple means of keeping up the fear, even when threats aren't directly observed. //will later have to save "enemies" into short-term memory for them to worry about if((currentadrenaline- (maxadrenaline*0.05)) > 0.0) { SetLocalFloat(self, "adrenaline", currentadrenaline - (maxadrenaline*0.05)); } else { SetLocalFloat(self, "adrenaline", 0.0); } //unfinished. basically a procedural AI system, using bars to determine actions, and preferences for priority. //not used on this char. if((comfort-0.5) > 0.0) { SetLocalFloat(self, "comfort", comfort-0.5); } else { SetLocalFloat(self, "comfort", 0.0); } if(fear < 0.0) { fear = 0.0; } happiness = 100-((1-divfloat(comfort, comforttolerance))*comfortimportance); fear = ((1 - (divfloat(currenthealth, fullhealth)))/100) + (fear/10) + currentadrenaline; if((divfloat(fear, bravery)) >= 0.6) { if((currentadrenaline+fear) < maxadrenaline) { SetLocalFloat(self, "currentadrenaline", currentadrenaline+fear); } else { SetLocalFloat(self, "currentadrenaline", maxadrenaline); } } else if(!GetIsObjectValid(enemy)) { incombat = FALSE; SetLocalInt(self, "incombat", FALSE); SetLocalInt(self, "attackint", FALSE); } //saves whether we are trapped or not. currently used for fleeing ai, to determine whether we fight back while frozen while (GetIsEffectValid(cycleeffect)) { if(GetEffectType(cycleeffect) == EFFECT_TYPE_ENTANGLE || GetEffectType(cycleeffect) == EFFECT_TYPE_SLOW) { stuck = TRUE; break; } cycleeffect = GetNextEffect(self); } //-------------------find objects, and gather info if((GetIsObjectValid(caution) || struck) && !incombat && !busy) { SetLocalInt(self, "busy", TRUE); busy++; SetLocalInt(self, "struck", FALSE); ClearAllActions(); ActionMoveToLocation(Location(GetArea(self), GetPosition(self)+AngleToVector(GetFacing(self)), GetFacing(self)-180.0)); checkforthreat(self, 1); } //-------------------important actions if(incombat && fear <= bravery) { SendMessageToPC(GetFirstPC(), GetName(self)+" in battle"); if(currentmagicka >= 1.449 && !busy && nearbyenemycount >= 4 && nearestenemydist <= 5.0) //casts the iconic firestorm spell(tempest is stronger version) { // SendMessageToPC(GetFirstPC(), GetName(self)+" casting firetempest"); SetLocalInt(self, "busy", TRUE); busy++; ClearAllActions(); firetempest(self); } if(currentmagicka >= 0.772 && !busy && nearestenemydist <= 2.5) //cast combustion { // SendMessageToPC(GetFirstPC(), GetName(self)+" casting great combustion"); SetLocalInt(self, "busy", TRUE); busy++; ClearAllActions(); greatcombustion(self, enemy); } else if(currentmagicka >= 1.545 && !busy && nearestenemydist <= 42.0 && nearestenemydist >= (0.621*GetLocalFloat(self, "magicattack")+1.5)) { // SendMessageToPC(GetFirstPC(), GetName(self)+" casting Great Fireball"); SetLocalInt(self, "busy", TRUE); busy++; ClearAllActions(); greatfireball(self, enemy); }//below is the beginning of the action combat Ai. float distance = (weaponreach(GetItemInSlot(INVENTORY_SLOT_RIGHTHAND, self))+5.0+randomfloat(-0.6, 2.5)); //avoiding striking range, based on own weapon length //timer character will maintain distance and circle character, rather than swinging at them. preset //to add consistency to the AI int attackint = GetLocalInt(self, "attackint"); if(attackint < 0 && !busy) { switch(Random(5)) { default: SetLocalInt(self, "attackint", 5); break; case 1: SetLocalInt(self, "attackint", 2); break; case 2: SetLocalInt(self, "attackint", 6); break; case 3: SetLocalInt(self, "attackint", 10); break; case 4: SetLocalInt(self, "attackint", 15); break; } } else if(attackint > 0 && !busy) { SetLocalInt(self, "attackint", attackint-1); } //move into attacking range if(!attackint && nearestenemydist >= (weaponreach(GetItemInSlot(INVENTORY_SLOT_RIGHTHAND, self))+2.5) && !busy) { ClearAllActions(); if(nearestenemydist >= 15.0 || (GetLocalInt(enemy, "busy") && GetLocalInt(enemy, "incombat")) || (GetIsInCombat(enemy) && (GetCurrentAction(enemy) != ACTION_ATTACKOBJECT || GetCurrentAction(enemy) != ACTION_MOVETOPOINT))) { ActionMoveToObject(enemy, TRUE, 1.0); } else { ActionMoveToObject(enemy, FALSE, 1.0); } } //swing attack if(!busy && !attackint) { SetLocalInt(self, "attackint", -1); ClearAllActions(); SetLocalInt(self, "busy", TRUE); busy++; attack(self, enemy, 1, Random(4)); if(GetLocalInt(self, "invisible")) { SetLocalInt(self, "cancelinvisible", TRUE); } } //below section is just for keeping away from enemy and circling, while waiting for our timer to attack else if(!busy && nearestenemydist > distance) { ClearAllActions(); if(nearestenemydist >= 15.0 || (GetLocalInt(enemy, "busy") && GetLocalInt(enemy, "incombat")) || (GetIsInCombat(enemy) && (GetCurrentAction(enemy) != ACTION_ATTACKOBJECT || GetCurrentAction(enemy) != ACTION_MOVETOPOINT))) { ActionMoveToObject(enemy, TRUE, distance); } else { ActionMoveToObject(enemy, FALSE, distance); } } else if(!busy) { ClearAllActions(); int noenemy; int anglenth = 1; float offset; int circle = GetLocalInt(self, "circle"); int circledirection = GetLocalInt(self, "circledirection"); if(circle > 0) { SetLocalInt(self, "circle", circle-1); } else { if(!circledirection) { offset = randomfloat(-40.0, -80.0); SetLocalInt(self, "circledirection", TRUE); } else { offset = randomfloat(40.0, 80.0); SetLocalInt(self, "circledirection", FALSE); } switch(Random(5)) { default: SetLocalInt(self, "circle", 3); break; case 1: SetLocalInt(self, "circle", 7); break; case 2: SetLocalInt(self, "circle", 2); break; case 3: SetLocalInt(self, "circle", 13); break; } } vector currentpos = GetPosition(enemy); vector targetpos = AngleToVector(offset+VectorToAngle(GetPosition(self)-GetPosition(enemy)))+GetPosition(enemy); float x = targetpos.x-currentpos.x; float y = targetpos.y-currentpos.y; float truex = x; float truey = y; if(x < 0.0) { x = 0-x; } if(y < 0.0) { y = 0-y; } float distanceportion = divfloat(distance, (x+y)); float distancex = truex*distanceportion; float distancey = truey*distanceportion; currentpos.x += distancex; currentpos.y += distancey; ClearAllActions(); ActionMoveToLocation(Location(GetArea(self), currentpos, 0.0), TRUE); DelayCommand(0.4, SetFacing(VectorToAngle(GetPosition(enemy)-GetPosition(self)))); } } //-------------------combat actions else if(fear > bravery) //routing AI { SendMessageToPC(GetFirstPC(), GetName(self)+"'s morales broken"); SetLocalInt(self, "afraid", TRUE); //used by checkforthreats script. once calm, they will look around to make sure they weren't pursued if(!busy) { // SendMessageToPC(GetFirstPC(), GetName(self)+" is getting out of there"); int noenemy; int anglenth = 1; float offset = 0.0; if(GetLocalInt(self, "travelint") == 0) //i have no idea... don't remember this { SetLocalInt(self, "travelint", 1+Random(10)); offset += randomfloat(-110.0, 110.0); } else { SetLocalInt(self, "travelint", GetLocalInt(self, "travelint")-1); } float interval; switch(Random(2)) { default: interval = 30.0; break; case 1: interval = -30.0; break; } while(!noenemy && offset < 360.0) //checking for threats/obstacles in the way of our retreat, and rotating until we find a safe position { if(!foundenemy(self, enemy, offset)) { noenemy++; break; } offset += 0-(offset*2); if(!foundenemy(self, enemy, offset)) { noenemy++; break; } offset +=(offset*2)+interval; } vector currentpos = GetPosition(self); vector targetpos = AngleToVector(offset+GetFacing(self))+GetPosition(self); float x = targetpos.x-currentpos.x; float y = targetpos.y-currentpos.y; float truex = x; float truey = y; if(x < 0.0) { x = 0-x; } if(y < 0.0) { y = 0-y; } float distanceportion = divfloat(5.0, (x+y)); float distancex = truex*distanceportion; float distancey = truey*distanceportion; currentpos.x += distancex; currentpos.y += distancey; ClearAllActions(); ActionMoveToLocation(Location(GetArea(self), currentpos, 0.0), TRUE); } } //--------------------routing actions else //relaxed actions { // SendMessageToPC(GetFirstPC(), GetName(self)+"'s relaxed"); if(IsInConversation(self)) //make sure they aren't doing unecessary stuff while chatting { busy++; } else if(!busy && GetIsObjectValid(door)) { SetLocalInt(self, "busy", TRUE); busy++; ClearAllActions(); transitionarea(self, door); } //LAST TIME SYSTEM WAS BROKEN. MADE MANY CHANGES, BUT IS STILL JUST A CONCEPT. FEEL FREE TO COMMENT OUT FOLLOW SYSTEM if(!busy && ((GetIsObjectValid(friend) && GetLocalObject(friend, "followtarget") != self) || (GetIsObjectValid(GetLocalObject(self, "followtarget")) && GetLocalObject(GetLocalObject(self, "followtarget"), "followtarget") != self ))) { if(!followtimer) { DeleteLocalObject(self, "followtarget"); SetLocalInt(self, "followtimer", 20+Random(50)); } SetLocalObject(self, "followtarget", friend); busy++; vector currentpos = GetPosition(self); vector targetpos = AngleToVector(GetFacing(friend)-180)+GetPosition(friend); float x = targetpos.x-currentpos.x; float y = targetpos.y-currentpos.y; float truex = x; float truey = y; if(x < 0.0) { x = 0-x; } if(y < 0.0) { y = 0-y; } float distanceportion = divfloat(2.5, (x+y)); float distancex = truex*distanceportion; float distancey = truey*distanceportion; currentpos += Vector(distancex, distancey, GetPosition(friend).z); ClearAllActions(); if(GetDistanceToObject(friend) > 5.0) { ActionMoveToLocation(Location(GetArea(self), currentpos, GetFacing(friend)), TRUE); } else { ActionMoveToLocation(Location(GetArea(self), currentpos, GetFacing(friend)), FALSE); } } else if(!busy) { DeleteLocalObject(self, "followtarget"); // SendMessageToPC(GetFirstPC(), "strolling"); int noenemy; int anglenth = 1; float offset = 0.0; if(GetLocalInt(self, "travelint") == 0) { SetLocalInt(self, "travelint", 1+Random(10)); offset += randomfloat(-110.0, 110.0); } else { SetLocalInt(self, "travelint", GetLocalInt(self, "travelint")-1); } float interval; switch(Random(2)) { default: interval = 30.0; break; case 1: interval = -30.0; break; } while(!noenemy && offset < 360.0) { if(!foundobstacle(self, enemy, offset)) { noenemy++; break; } offset += 0-(offset*2); if(!foundobstacle(self, enemy, offset)) { noenemy++; break; } offset +=(offset*2)+interval; } vector currentpos = GetPosition(self); vector targetpos = AngleToVector(offset+GetFacing(self))+GetPosition(self); float x = targetpos.x-currentpos.x; float y = targetpos.y-currentpos.y; float truex = x; float truey = y; if(x < 0.0) { x = 0-x; } if(y < 0.0) { y = 0-y; } float distance = 5.0; if(GetLocalFloat(self, "distance") > 0.0) { distance = GetLocalFloat(self, "distance"); SetLocalFloat(self, "distance", 0.0); } float distanceportion = divfloat(5.0, (x+y)); float distancex = truex*distanceportion; float distancey = truey*distanceportion; currentpos.x += distancex; currentpos.y += distancey; ClearAllActions(); switch(hollowing) { case FALSE: ActionMoveToLocation(Location(GetArea(self), currentpos, 0.0), FALSE); break; default: ActionMoveToLocation(Location(GetArea(self), currentpos, 0.0), TRUE); break; } } } //--------------------routine AI actions if(GetLocalInt(self, "aitick") < 100) //after 100 "ticks". AI deactivates to prevent TMI. being reset by heartbeat { if(!GetIsDead(self)) { SetLocalInt(self, "aitick", GetLocalInt(self, "aitick")+1); SetLocalInt(self, "lasthealthd&d", GetCurrentHitPoints(self)); SetLocalFloat(self, "lasthealth", GetLocalFloat(self, "currenthealth")); DelayCommand(0.5, ExecuteScript("queelanaai")); } else { SetLocalInt(self, "aitick", 500); } } //--------------------AI reset } ////////////////////////////////////////////////////////// //=========================================(END OF MAIN()) //\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ //|______________________________________________________|//WILL NOT BE COMMENTING ALL FUNCTIONS LOL //------------------------------------------------------\ // \ // \ //=============UTILITIES & Miscellaneous================= \ // / // / //-------------------------------------------------------/ int divint(int num1, int num2) { if(num1 || num2 == 0) { return 0; } return num1/num2; } float divfloat(float num1, float num2) { if(num1 == 0.0 || num2 == 0.0) { return 0.0; } return num1/num2; } void modrelationvalue(object self, object target, float value) { string name = GetName(target); float relationshipvalue = getrelation(self, target); if(relationshipvalue == 0.0) { relationshipvalue = 0.0; } if((relationshipvalue+value) <= 0.0) { relationshipvalue = 1.0; } else if((relationshipvalue+value) >= 100.0) { relationshipvalue = 100.0; } else { relationshipvalue += value; } SetLocalFloat(self, name, relationshipvalue); } float factionrelationbonus(object self, object target) { if(GetLocalInt(self, "wayofthewhitecovenant")) { if(GetLocalInt(target, "wayofthewhitecovenant")) { return 35.0; } else if(GetLocalInt(target, "wayofthewhiteassociate")) { return 20.0; } else { return 0.0; } } if(GetLocalInt(self, "wayofthewhiteassociate")) { if(GetLocalInt(target, "wayofthewhitecovenant") || GetLocalInt(target, "wayofthewhiteassociate")) { return 20.0; } else { return 0.0; } } return 0.0; } float getrelation(object self, object target) { string name = GetName(target); if(GetLocalFloat(self, name) == 0.0) { SetLocalFloat(self, name, 50.0+factionrelationbonus(self, target)); return GetLocalFloat(self, name); } return GetLocalFloat(self, name); } float intimidationvaluecomp(object self, object target) { float personalscore; float targetscore; int humanoidknowledge = GetLocalInt(self, "humanoidknowledge"); int animalknowledge = GetLocalInt(self, "animalknowledge"); int medicalknowledge = GetLocalInt(self, "medicalknowledge"); int feyknowledge = GetLocalInt(self, "feyknowledge"); int giantknowledge = GetLocalInt(self, "giantknowledge"); int magicbeastknowledge = GetLocalInt(self, "magicbeastknowledge"); int monstrousknowledge = GetLocalInt(self, "graftingknowledge"); int undeadknowledge = GetLocalInt(self, "necromancyknowledge"); int aberrationknowledge = GetLocalInt(self, "aberrationknowledge"); int dragonknowledge = GetLocalInt(self, "dragonknowledge"); int golemknowledge = GetLocalInt(self, "golemcraft"); int elementalknowledge = GetLocalInt(self, "elementsknowledge"); int outsiderknowledge = GetLocalInt(self, "demonology"); int oozeknowledge = GetLocalInt(self, "oozeknowledge"); switch(GetAppearanceType(target)) { //too long to post } switch(GetAppearanceType(self)) { //too long to post } if(GetIsObjectValid(GetItemInSlot(INVENTORY_SLOT_RIGHTHAND, self))) { personalscore += 2.0; } if(GetIsObjectValid(GetItemInSlot(INVENTORY_SLOT_RIGHTHAND, target))) { targetscore += 2.0; } if(GetIsObjectValid(GetItemInSlot(INVENTORY_SLOT_LEFTHAND, self))) { personalscore += 1.0; } if(GetIsObjectValid(GetItemInSlot(INVENTORY_SLOT_LEFTHAND, target))) { targetscore += 1.0; } if(GetIsObjectValid(GetItemInSlot(INVENTORY_SLOT_HEAD, self))) { personalscore += 0.5; } if(GetIsObjectValid(GetItemInSlot(INVENTORY_SLOT_HEAD, target))) { targetscore += 0.5; } return (divfloat(personalscore, targetscore)); } void checkforthreat(object self, int duration = 4) { object target = detectobject(self, 1, 90.0); int nth; while(GetIsObjectValid(target)) { if(getrelation(self, target) <= 35.0) { break; } nth++; target = detectobject(self, nth, 90.0); } if(!GetIsObjectValid(target) && duration > 0 && !GetIsDead(self)) { SetFacing(GetFacing(self)+randomfloat(15.0, 180.0)); ClearAllActions(); DelayCommand(0.4, checkforthreat(self, duration-1)); } else { SetLocalInt(self, "busy", FALSE); } } void Knockback(object target, float angle, float power, float lift) //haven't bothered making this yet. basically will be used for staggering, and sending targets flying { } int foundenemy(object self, object enemy, float offset) { int nth = 1; enemy = GetNearestObject(OBJECT_TYPE_CREATURE, self); while(GetIsObjectValid(enemy) && GetDistanceToObject(enemy) < 50.0) { if(conespreadfromangle(self, enemy, 30.0, 0.0+offset) && (getrelation(self, enemy) <= 30.0 || (conespread(enemy, self, 25) && (GetIsInCombat(enemy) || GetLocalInt(enemy, "incombat")))) && !GetIsDead(enemy)) { return TRUE; } nth++; enemy = GetNearestObject(OBJECT_TYPE_CREATURE, self, nth); } vector currentpos = GetPosition(self); vector targetpos = AngleToVector(offset+GetFacing(self))+GetPosition(self); float x; float y; float truex; float truey; float distanceportion; x = targetpos.x-currentpos.x; y = targetpos.y-currentpos.y; truex = x; truey = y; if(x < 0.0) { x = 0-x; } if(y < 0.0) { y = 0-y; } distanceportion = divfloat(10.0, (x+y)); float distancex = truex*distanceportion; float distancey = truey*distanceportion; currentpos.x += distancex; currentpos.y += distancey; if(!LineOfSightVector(GetPosition(self), currentpos)) { return TRUE; } return FALSE; } int foundobstacle(object self, object enemy, float offset) { int nth = 1; enemy = GetNearestObject(OBJECT_TYPE_CREATURE, self); while(GetIsObjectValid(enemy) && GetDistanceToObject(enemy) <= 4.0) { if(conespreadfromangle(self, enemy, 70.0, 0.0+offset) && !GetIsDead(enemy)) { return TRUE; } nth++; enemy = GetNearestObject(OBJECT_TYPE_CREATURE, self, nth); } vector currentpos = GetPosition(self); currentpos.z-=0.5; vector targetpos = AngleToVector(offset+GetFacing(self))+GetPosition(self); float x; float y; float truex; float truey; float distanceportion; x = targetpos.x-currentpos.x; y = targetpos.y-currentpos.y; truex = x; truey = y; if(x < 0.0) { x = 0-x; } if(y < 0.0) { y = 0-y; } distanceportion = divfloat(10.0, (x+y)); float distancex = truex*distanceportion; float distancey = truey*distanceportion; targetpos = currentpos+Vector(distancex, distancey, 0.0); if(!LineOfSightVector(GetPosition(self), targetpos)) { distanceportion /= 10; distancex = truex*distanceportion; distancey = truey*distanceportion; currentpos = GetPosition(self)+Vector(distancex, distancey, 0.5); if(LineOfSightVector(GetPosition(self), currentpos) && !LineOfSightVector(currentpos, Vector(currentpos.x, currentpos.y, -0.5))) { currentpos = GetPosition(self)+Vector((distancex*3), (distancey*3), 1.0); if(LineOfSightVector(GetPosition(self), currentpos) && !LineOfSightVector(currentpos, Vector(currentpos.x, currentpos.y, -1.0))) { currentpos = GetPosition(self)+Vector((distancex*5), (distancey*5), 1.5); if(LineOfSightVector(GetPosition(self), currentpos) && !LineOfSightVector(currentpos, Vector(currentpos.x, currentpos.y, -1.5))) { SetLocalFloat(self, "distance", 2.0); return FALSE; } } } return TRUE; } return FALSE; } float randomfloat(float min, float max) { int minnum = FloatToInt(min*100); int randhigh = FloatToInt(max*100); randhigh = (Random(randhigh)+1)+(minnum/2); if(randhigh < minnum) { return min; } else if(randhigh > (FloatToInt(max)*100)) { return max; } return IntToFloat(randhigh)/100.0; } object detectobject(object self, int nth, float distance) { object spotted = GetNearestObject(OBJECT_TYPE_ALL, self, nth); if(objectperceived(self, spotted) || ((GetIsInCombat(spotted) || GetLocalInt(spotted, "incombat")) && GetDistanceToObject(spotted) < 25.0 && (!GetIsDead(spotted) || !GetLocalInt(spotted, "dead"))) || ((GetCurrentAction(spotted) == ACTION_ATTACKOBJECT || GetCurrentAction(spotted) == ACTION_CASTSPELL || GetCurrentAction(spotted) == ACTION_COUNTERSPELL || GetCurrentAction(spotted) == ACTION_ITEMCASTSPELL || GetCurrentAction(spotted) == ACTION_MOVETOPOINT || GetCurrentAction(spotted) == ACTION_RANDOMWALK) && GetDistanceToObject(spotted) < 15.0 && (!GetIsDead(spotted) || !GetLocalInt(spotted, "dead")))) //if(objectperceived(self, spotted)) // only checks if char is in sight cone. which is Getfacing+60 degrees in either direction { return spotted; } return OBJECT_INVALID; } int objectperceived(object self, object target) { if(LineOfSightObject(self, target) && conespread(self, target, 120)) { return TRUE; } return FALSE; } int conespread(object origin, object target, int coneangle) { float angleoffset = VectorToAngle((GetPosition(target)+Vector(GetObjectVisualTransform(target, OBJECT_VISUAL_TRANSFORM_TRANSLATE_X), GetObjectVisualTransform(target, OBJECT_VISUAL_TRANSFORM_TRANSLATE_Y), GetObjectVisualTransform(target, OBJECT_VISUAL_TRANSFORM_TRANSLATE_Z))) - (GetPosition(origin)+Vector(GetObjectVisualTransform(origin, OBJECT_VISUAL_TRANSFORM_TRANSLATE_X), GetObjectVisualTransform(origin, OBJECT_VISUAL_TRANSFORM_TRANSLATE_Y), GetObjectVisualTransform(origin, OBJECT_VISUAL_TRANSFORM_TRANSLATE_Z))))-GetFacing(origin); //coneangle = 180.0; return(abs(FloatToInt(angleoffset)) < coneangle/2); } int conespreadfromangle(object origin, object target, float coneangle, float angleoffset) { float angle = GetFacing(origin)+angleoffset; if(angle < 0.0) { angle = 360.0 + angle; } else if(angle > 360.0) { angle -= 360.0; } float angleoffset = VectorToAngle((GetPosition(target)+Vector(GetObjectVisualTransform(target, OBJECT_VISUAL_TRANSFORM_TRANSLATE_X), GetObjectVisualTransform(target, OBJECT_VISUAL_TRANSFORM_TRANSLATE_Y), GetObjectVisualTransform(target, OBJECT_VISUAL_TRANSFORM_TRANSLATE_Z))) - (GetPosition(origin)+Vector(GetObjectVisualTransform(origin, OBJECT_VISUAL_TRANSFORM_TRANSLATE_X), GetObjectVisualTransform(origin, OBJECT_VISUAL_TRANSFORM_TRANSLATE_Y), GetObjectVisualTransform(origin, OBJECT_VISUAL_TRANSFORM_TRANSLATE_Z))))-angle; //coneangle = 180.0; return(abs(FloatToInt(angleoffset)) < FloatToInt(coneangle/2)); } void invischeck(object target, int duration) { if(GetLocalInt(target, "cancelinvisible")) { duration = -1; SetLocalInt(target, "cancelinvisible", FALSE); effect invisible = GetFirstEffect(target); while (GetIsEffectValid(invisible)) { if(GetEffectType(invisible) == EFFECT_TYPE_INVISIBILITY) { RemoveEffect(target, invisible); break; } invisible = GetNextEffect(target); } SetLocalInt(target, "invisible", FALSE); return; } if(duration > 0) { DelayCommand(1.0, invischeck(target, duration-1)); } else { SetLocalInt(target, "invisible", FALSE); } } void transitionarea(object self, object door, int transitioning = FALSE) { if(GetDistanceToObject(door) > 1.5 && !GetIsDead(self) && !transitioning) { SetCommandable(TRUE, self); ClearAllActions(); ActionMoveToObject(door, FALSE, 0.0); SetCommandable(FALSE, self); DelayCommand(1.0, transitionarea(self, door)); } else if(!GetIsDead(self) && transitioning) { if(GetIsOpen(door)) { SetCommandable(TRUE, self); door = GetTransitionTarget(door); JumpToObject(door); SetCommandable(FALSE, self); DelayCommand(2.0, SetCommandable(TRUE, self)); DelayCommand(2.0, SetLocalInt(self, "busy", FALSE)); } else { SetCommandable(TRUE, self); switch(Random(2)) { default: SetFacing(GetFacing(self)-randomfloat(-90.0, -140.0)); break; case 1: SetFacing(GetFacing(self)-randomfloat(90.0, 140.0)); break; } SetCommandable(FALSE, self); DelayCommand(1.0, SetCommandable(TRUE, self)); DelayCommand(1.0, SetLocalInt(self, "busy", FALSE)); } } else if(!GetIsDead(self)) { SetCommandable(TRUE, self); DoDoorAction(door, DOOR_ACTION_OPEN); SetCommandable(FALSE, self); DelayCommand(1.5, transitionarea(self, door, TRUE)); } else { SetCommandable(TRUE, self); SetLocalInt(self, "busy", FALSE); } }//------------------------------------------------------\ // \ // \ //=============Great Fireball============================ \ // / // / //-------------------------------------------------------/ void greatfiredamage(object self, location flamelocation, float damage, float explosionsize) { if(GetDistanceBetweenLocations(flamelocation, GetLocation(self)) <= (0.621*GetLocalFloat(self, "magicattack")+1.2) && !GetLocalInt(self, "busy")) { ClearAllActions(); ActionMoveAwayFromLocation(flamelocation, TRUE, 0.621*GetLocalFloat(self, "magicattack")+1.0); } object collisiontarget = GetNearestObjectToLocation(OBJECT_TYPE_ALL, flamelocation); int nth = 1; while(GetIsObjectValid(collisiontarget) && GetDistanceBetweenLocations(flamelocation, GetLocation(collisiontarget)) <= explosionsize) { if(LineOfSightVector(GetPosition(collisiontarget), GetPositionFromLocation(flamelocation))) { ApplyEffectToObject(DURATION_TYPE_INSTANT, EffectDamage(0, DAMAGE_TYPE_FIRE), collisiontarget); if(GetLocalFloat(collisiontarget, "health") > 0.0) { float realdamage = (damage - GetLocalInt(collisiontarget, "magicdefence"))*(IntToFloat(100-GetLocalInt(collisiontarget, "fireresist"))/100); if(realdamage > 0.0) { SetLocalFloat(collisiontarget, "currenthealth", GetLocalFloat(collisiontarget, "currenthealth")-realdamage); AssignCommand(collisiontarget, SpeakString(FloatToString(realdamage, 0, 0))); } } else { ApplyEffectToObject(DURATION_TYPE_INSTANT, EffectDamage(FloatToInt(damage), DAMAGE_TYPE_FIRE), collisiontarget); } } nth++; collisiontarget = GetNearestObjectToLocation(OBJECT_TYPE_ALL, flamelocation, nth); } } void greatfireproj(object self, object target, object projectile, float damage, float explosionsize, float fullspeed, float speed, float upspeed, vector targetpos, vector currentpos, vector previouspos, location projloc, float xpos, float ypos, float zpos, int targeted = FALSE) { if(!targeted) { targeted++; vector targetspeed = GetPosition(target); float x = targetspeed.x-targetpos.x; float y = targetspeed.y-targetpos.y; float truex = x; float truey = y; if(x < 0.0) { x=0-x; } if(y < 0.0) { y=0-y; } float distance = sqrt( ((targetpos.x-targetspeed.x)*(targetpos.x-targetspeed.x)) + ((targetpos.y-targetspeed.y)*(targetpos.y-targetspeed.y)) ); float distanceportion = divfloat(distance, (x+y)); float distancex = truex*distancex; float distancey = truey*distancey; float prevdistance = sqrt( ((currentpos.x-targetpos.x)*(currentpos.x-targetpos.x)) + ((currentpos.y-targetpos.y)*(currentpos.y-targetpos.y)) ); targetpos = Vector(targetspeed.x+distancex, targetspeed.y+distancey, targetspeed.z+1.5); upspeed *= divfloat(distance, prevdistance); } float x = targetpos.x-currentpos.x; float y = targetpos.y-currentpos.y; float truex = x; float truey = y; if(x < 0.0) { x = 0-x; } if(y < 0.0) { y = 0-y; } float distanceportion = divfloat((speed*0.1), (x+y)); float distancex = truex*distanceportion; float distancey = truey*distanceportion; currentpos += Vector(distancex, distancey, (upspeed*0.1)); xpos += distancex; ypos += distancey; zpos +=(upspeed*0.1); int nth = 1; object collisiontarget; switch(LineOfSightVector(previouspos, Vector(currentpos.x, currentpos.y, currentpos.z-1.0))) { case TRUE: upspeed -= (9.8*0.1); speed -= (fullspeed*0.01); projloc = Location(GetAreaFromLocation(projloc), currentpos, 0.0); if(GetDistanceBetweenLocations(projloc, GetLocation(self)) <= (0.621*GetLocalFloat(self, "magicattack")+1.2) && !GetLocalInt(self, "busy")) { ClearAllActions(); ActionMoveAwayFromLocation(projloc, TRUE, 0.621*GetLocalFloat(self, "magicattack")+1.0); } SetObjectVisualTransform(projectile, OBJECT_VISUAL_TRANSFORM_TRANSLATE_X, xpos, OBJECT_VISUAL_TRANSFORM_LERP_LINEAR, 0.1); SetObjectVisualTransform(projectile, OBJECT_VISUAL_TRANSFORM_TRANSLATE_Y, ypos, OBJECT_VISUAL_TRANSFORM_LERP_LINEAR, 0.1); SetObjectVisualTransform(projectile, OBJECT_VISUAL_TRANSFORM_TRANSLATE_Z, zpos, OBJECT_VISUAL_TRANSFORM_LERP_LINEAR, 0.1); collisiontarget = GetNearestObjectToLocation(OBJECT_TYPE_ALL, projloc); if(collisiontarget == projectile) { collisiontarget = GetNearestObjectToLocation(OBJECT_TYPE_ALL, projloc, 2); } ApplyEffectToObject(DURATION_TYPE_TEMPORARY, EffectVisualEffect(VFX_IMP_SUNSTRIKE, FALSE, 0.9), projectile, 0.1); if(GetDistanceBetweenLocations(projloc, GetLocation(collisiontarget)) <= 1.0 && collisiontarget != self) { DestroyObject(projectile); ApplyEffectAtLocation(DURATION_TYPE_INSTANT, EffectVisualEffect(VFX_IMP_SUNSTRIKE, FALSE, (explosionsize*0.9)), projloc); ApplyEffectAtLocation(DURATION_TYPE_INSTANT, EffectVisualEffect(VFX_FNF_SUMMON_MONSTER_1, FALSE, (explosionsize*0.1)), projloc); ApplyEffectAtLocation(DURATION_TYPE_INSTANT, EffectVisualEffect(VFX_IMP_FLAME_M, FALSE, (explosionsize*0.8)), projloc); DelayCommand(0.5, greatfiredamage(self, projloc, damage, explosionsize)); collisiontarget = GetNearestObjectToLocation(OBJECT_TYPE_ALL, projloc); while(GetIsObjectValid(collisiontarget) && GetDistanceBetweenLocations(projloc, GetLocation(collisiontarget)) <= 1.0) { if(LineOfSightVector(currentpos, GetPosition(collisiontarget))) { SetLocalInt(collisiontarget, "struck", TRUE); ApplyEffectToObject(DURATION_TYPE_INSTANT, EffectDamage(0, DAMAGE_TYPE_FIRE), collisiontarget); ApplyEffectToObject(DURATION_TYPE_INSTANT, EffectVisualEffect(VFX_IMP_FLAME_S), collisiontarget); if(GetLocalFloat(collisiontarget, "health") < 0.1) { ApplyEffectToObject(DURATION_TYPE_INSTANT, EffectDamage(FloatToInt(damage/10), DAMAGE_TYPE_FIRE), collisiontarget); } else { damage = ((damage/10) - GetLocalInt(collisiontarget, "magicdefence"))*(IntToFloat(100-GetLocalInt(collisiontarget, "fireresist"))/100); if(damage > 0.0) { SetLocalFloat(collisiontarget, "currenthealth", GetLocalFloat(collisiontarget, "currenthealth")-damage); AssignCommand(collisiontarget, SpeakString(FloatToString(damage, 0, 0))); } } } nth++; collisiontarget = GetNearestObject(OBJECT_TYPE_ALL, self, nth); } return; } DelayCommand(0.1, greatfireproj(self, target, projectile, damage, explosionsize, fullspeed, speed, upspeed, targetpos, currentpos, currentpos, projloc, xpos, ypos, zpos, targeted)); break; case FALSE: DestroyObject(projectile); ApplyEffectAtLocation(DURATION_TYPE_INSTANT, EffectVisualEffect(VFX_IMP_SUNSTRIKE, FALSE, (explosionsize*0.9)), projloc); ApplyEffectAtLocation(DURATION_TYPE_INSTANT, EffectVisualEffect(VFX_FNF_SUMMON_MONSTER_1, FALSE, (explosionsize*0.1)), projloc); ApplyEffectAtLocation(DURATION_TYPE_INSTANT, EffectVisualEffect(VFX_IMP_FLAME_M, FALSE, (explosionsize*0.8)), projloc); DelayCommand(0.5, greatfiredamage(self, projloc, damage, explosionsize)); collisiontarget = GetNearestObjectToLocation(OBJECT_TYPE_ALL, projloc); while(GetIsObjectValid(collisiontarget) && GetDistanceBetweenLocations(projloc, GetLocation(collisiontarget)) <= 1.0) { if(LineOfSightVector(currentpos, GetPosition(collisiontarget))) { SetLocalInt(collisiontarget, "struck", TRUE); ApplyEffectToObject(DURATION_TYPE_INSTANT, EffectDamage(0, DAMAGE_TYPE_FIRE), collisiontarget); ApplyEffectToObject(DURATION_TYPE_INSTANT, EffectVisualEffect(VFX_IMP_FLAME_S), collisiontarget); if(GetLocalFloat(collisiontarget, "health") < 0.1) { ApplyEffectToObject(DURATION_TYPE_INSTANT, EffectDamage(FloatToInt(damage/10), DAMAGE_TYPE_FIRE), collisiontarget); } else { damage = ((damage/10) - GetLocalInt(collisiontarget, "magicdefence"))*(IntToFloat(100-GetLocalInt(collisiontarget, "fireresist"))/100); if(damage > 0.0) { SetLocalFloat(collisiontarget, "currenthealth", GetLocalFloat(collisiontarget, "currenthealth")-damage); AssignCommand(collisiontarget, SpeakString(FloatToString(damage, 0, 0))); } } } nth++; collisiontarget = GetNearestObject(OBJECT_TYPE_ALL, self, nth); } break; } } void greatfireball(object self, object target) { PlayAnimation(ANIMATION_LOOPING_TALK_PLEADING, 0.4, 1.5); SetFacing(VectorToAngle(GetPosition(target)-GetPosition(self))); SetCommandable(FALSE, self); float speed = 18.05; float upspeed = 18.05; vector targetpos = GetPosition(target); vector currentpos = GetPosition(self); currentpos.x += 1.5; float x = targetpos.x-currentpos.x; float y = targetpos.y-currentpos.y; float z = targetpos.z-currentpos.z; float truex = x; float truey = y; if(x < 0.0) { x=0-x; } if(y < 0.0) { y=0-y; } if(z < 0.0) { z=0-z; } float distance = sqrt( ((currentpos.x-targetpos.x)*(currentpos.x-targetpos.x)) + ((currentpos.y-targetpos.y)*(currentpos.y-targetpos.y)) ); float hangtime = (((upspeed*0.6)+1.5)/9.8)*2; float maxdistance = speed*hangtime-(0.3*hangtime); if(distance > maxdistance) { distance = maxdistance; } else { upspeed = 18.05*divfloat(distance, maxdistance); } float distanceportion = divfloat(distance, (x+y)); float distancex = truex*distanceportion; float distancey = truey*distanceportion; targetpos += Vector(distancex, distancey, 1.5); currentpos.z += 1.5; if(GetLocalFloat(self, "currentmagicka") < 1.545) { DelayCommand(1.2, SetCommandable(TRUE, self)); DelayCommand(1.2, ClearAllActions()); DelayCommand(1.2, SetLocalInt(self, "busy", FALSE)); SetLocalFloat(self, "currentmagicka", 0.0); return; } SetLocalFloat(self, "currentmagicka", GetLocalFloat(self, "currentmagicka")-1.545); location projloc = Location(GetArea(self), currentpos+AngleToVector(GetFacing(self)), 90.0); object projectile = CreateObject(OBJECT_TYPE_PLACEABLE, "dsprojectile", projloc, FALSE); SetObjectVisualTransform(projectile, OBJECT_VISUAL_TRANSFORM_TRANSLATE_Z, 1.5); DelayCommand(0.8, ApplyEffectAtLocation(DURATION_TYPE_INSTANT, EffectVisualEffect(VFX_IMP_SUNSTRIKE, FALSE, 0.15), projloc)); DelayCommand(1.0, ApplyEffectAtLocation(DURATION_TYPE_INSTANT, EffectVisualEffect(VFX_IMP_SUNSTRIKE, FALSE, 0.2), projloc)); DelayCommand(1.2, ApplyEffectAtLocation(DURATION_TYPE_INSTANT, EffectVisualEffect(VFX_IMP_SUNSTRIKE, FALSE, 0.25), projloc)); DelayCommand(1.6, ApplyEffectAtLocation(DURATION_TYPE_INSTANT, EffectVisualEffect(VFX_FNF_FIREBALL, FALSE, 0.4), projloc)); DelayCommand(1.6, ApplyEffectAtLocation(DURATION_TYPE_INSTANT, EffectVisualEffect(VFX_IMP_SUNSTRIKE, FALSE, 0.75), projloc)); DelayCommand(1.5, SetCommandable(TRUE, self)); DelayCommand(1.6, ClearAllActions()); DelayCommand(1.85, PlayAnimation(ANIMATION_FIREFORGET_VICTORY1, 1.4)); DelayCommand(1.9, SetCommandable(FALSE, self)); DelayCommand(2.3, greatfireproj(self, target, projectile, 3*GetLocalFloat(self, "magicattack"), 0.621*GetLocalFloat(self, "magicattack"), speed, speed, upspeed, targetpos, currentpos, currentpos, projloc, 0.0, 0.0, 1.5)); DelayCommand(2.9, SetCommandable(TRUE, self)); DelayCommand(2.9, SetLocalInt(self, "busy", FALSE)); } //------------------------------------------------------\ // \ // \ //=============Great Combustion========================== \ // / // / //-------------------------------------------------------/ void combustiondamage(object self, object target, float damage, location flamelocation) { object collisiontarget = GetNearestObjectToLocation(OBJECT_TYPE_ALL, flamelocation); int nth = 1; while(GetIsObjectValid(collisiontarget) && GetDistanceToObject(collisiontarget) <= 2.3) { if(LineOfSightVector(GetPosition(collisiontarget), GetPositionFromLocation(flamelocation)) && collisiontarget != self) { ApplyEffectToObject(DURATION_TYPE_INSTANT, EffectDamage(0, DAMAGE_TYPE_FIRE), collisiontarget); if(GetLocalFloat(collisiontarget, "health") > 0.0) { float realdamage = (damage - GetLocalInt(collisiontarget, "magicdefence"))*(IntToFloat(100-GetLocalInt(collisiontarget, "fireresist"))/100); if(realdamage > 0.0) { SetLocalFloat(collisiontarget, "currenthealth", GetLocalFloat(collisiontarget, "currenthealth")-realdamage); AssignCommand(collisiontarget, SpeakString(FloatToString(realdamage, 0, 0))); } } else { ApplyEffectToObject(DURATION_TYPE_INSTANT, EffectDamage(FloatToInt(damage), DAMAGE_TYPE_FIRE), collisiontarget); } } nth++; collisiontarget = GetNearestObjectToLocation(OBJECT_TYPE_ALL, flamelocation, nth); } } void greatcombustion(object self, object target) { PlayAnimation(ANIMATION_LOOPING_TALK_PLEADING, 0.4, 1.5); SetFacing(VectorToAngle(GetPosition(target)-GetPosition(self))); SetCommandable(FALSE, self); if(GetLocalFloat(self, "currentmagicka") < 0.772) { DelayCommand(1.2, SetCommandable(TRUE, self)); DelayCommand(1.2, ClearAllActions()); DelayCommand(1.2, SetLocalInt(self, "busy", FALSE)); SetLocalFloat(self, "currentmagicka", 0.0); return; } SetLocalFloat(self, "currentmagicka", GetLocalFloat(self, "currentmagicka")-0.772); location flamelocation = Location(GetArea(self), GetPosition(self)+AngleToVector(GetFacing(self)), 0.0); DelayCommand(1.2, ApplyEffectAtLocation(DURATION_TYPE_INSTANT, EffectVisualEffect(VFX_IMP_FLAME_S, FALSE, 1.7), flamelocation)); DelayCommand(1.6, ApplyEffectAtLocation(DURATION_TYPE_INSTANT, EffectVisualEffect(VFX_FNF_FIREBALL), flamelocation)); DelayCommand(1.5, combustiondamage(self, target, 2.4*GetLocalFloat(self, "magicattack"), flamelocation)); DelayCommand(1.9, SetCommandable(TRUE, self)); DelayCommand(1.9, SetLocalInt(self, "busy", FALSE)); } //------------------------------------------------------\ // \ // \ //=============Fire Tempest============================== \ // / // / //-------------------------------------------------------/ void firecolumndamage(object self, float damage, location flamelocation) { object collisiontarget = GetNearestObjectToLocation(OBJECT_TYPE_ALL, flamelocation); int nth = 1; vector bangarea = GetPositionFromLocation(flamelocation); while(GetIsObjectValid(collisiontarget) && GetDistanceBetweenLocations(flamelocation, GetLocation(collisiontarget)) <= 2.5) { if(LineOfSightVector(GetPosition(collisiontarget), bangarea) && collisiontarget != self) { ApplyEffectToObject(DURATION_TYPE_INSTANT, EffectDamage(0, DAMAGE_TYPE_FIRE), collisiontarget); if(GetLocalFloat(collisiontarget, "health") > 0.0) { float realdamage = (damage - GetLocalInt(collisiontarget, "magicdefence"))*(IntToFloat(100-GetLocalInt(collisiontarget, "fireresist"))/100); if(realdamage > 0.0) { SetLocalFloat(collisiontarget, "currenthealth", GetLocalFloat(collisiontarget, "currenthealth")-realdamage); AssignCommand(collisiontarget, SpeakString(FloatToString(realdamage, 0, 0))); } } else { ApplyEffectToObject(DURATION_TYPE_INSTANT, EffectDamage(FloatToInt(damage), DAMAGE_TYPE_FIRE), collisiontarget); } } nth++; collisiontarget = GetNearestObjectToLocation(OBJECT_TYPE_ALL, flamelocation, nth); } } void createfirecolumns(object self, int columns = 20, int failed = 20) { vector currentpos = GetPosition(self); vector columnpos = AngleToVector(randomfloat(0.0, 360.0))+currentpos; float x = columnpos.x-currentpos.x; float y = columnpos.y-currentpos.y; float truex = x; float truey = y; if(x < 0.0) { x = 0-x; } if(y < 0.0) { y = 0-y; } float distanceportion = divfloat(randomfloat(4.0, 7.0), (x+y)); float distancex = truex*distanceportion; float distancey = truey*distanceportion; columnpos = currentpos+Vector(distancex, distancey, 0.0); location flamelocation = Location(GetArea(self), columnpos, 0.0); if(LineOfSightVector(columnpos, Vector(columnpos.x, columnpos.y, columnpos.z+10.0)) && columns) { SetLocalFloat(self, "currentmagicka", GetLocalFloat(self, "currentmagicka")-0.07245); ApplyEffectAtLocation(DURATION_TYPE_INSTANT, EffectVisualEffect(VFX_IMP_FLAME_M, FALSE, 1.8), flamelocation); flamelocation = Location(GetArea(self), Vector(columnpos.x, columnpos.y, columnpos.z+1.9), 0.0); DelayCommand(0.14, ApplyEffectAtLocation(DURATION_TYPE_INSTANT, EffectVisualEffect(VFX_IMP_FLAME_M, FALSE, 1.3), flamelocation)); flamelocation = Location(GetArea(self), Vector(columnpos.x, columnpos.y, columnpos.z+3.3), 0.0); DelayCommand(0.2, ApplyEffectAtLocation(DURATION_TYPE_INSTANT, EffectVisualEffect(VFX_IMP_FLAME_M, FALSE, 0.8), flamelocation)); flamelocation = Location(GetArea(self), Vector(columnpos.x, columnpos.y, columnpos.z), 0.0); DelayCommand(0.25, ApplyEffectAtLocation(DURATION_TYPE_INSTANT, EffectVisualEffect(VFX_COM_CHUNK_STONE_SMALL), flamelocation)); firecolumndamage(self, 3.9*GetLocalFloat(self, "magicattack"), flamelocation); DelayCommand(randomfloat(0.1, 0.25), createfirecolumns(self, columns-1, failed)); } else if(columns) { createfirecolumns(self, columns, failed-1); } } void firetempest(object self) { PlayAnimation(ANIMATION_LOOPING_GET_LOW, 0.4, 3.4); SetCommandable(FALSE, self); if(GetLocalFloat(self, "currentmagicka") < 1.449) { DelayCommand(1.2, SetCommandable(TRUE, self)); DelayCommand(1.2, ClearAllActions()); DelayCommand(1.2, SetLocalInt(self, "busy", FALSE)); SetLocalFloat(self, "currentmagicka", 0.0); return; } DelayCommand(1.4, ApplyEffectToObject(DURATION_TYPE_INSTANT, EffectVisualEffect(VFX_IMP_HEAD_FIRE), self)); DelayCommand(1.4, ApplyEffectToObject(DURATION_TYPE_INSTANT, EffectVisualEffect(VFX_DUR_PROTECTION_GOOD_MINOR), self)); DelayCommand(2.0, createfirecolumns(self)); DelayCommand(3.7, SetCommandable(TRUE, self)); DelayCommand(3.7, SetLocalInt(self, "busy", FALSE)); } //------------------------------------------------------\ // \ // \ //=============Attack==================================== \ // / // / //-------------------------------------------------------/ void checkattack(object self, object target, int hand, int combo) { if(combo > 0 && GetDistanceBetween(self, target) < 5.0 && !GetIsDead(self) && !GetIsDead(target)) { attack(self, target, hand, combo-1); } else { vector targetpos = AngleToVector(GetFacing(self)-180.0)+GetPosition(self); vector currentpos = GetPosition(self); float x = targetpos.x - currentpos.x; float y = targetpos.y - currentpos.y; float truex = x; float truey = y; if(x < 0.0) { x = 0-x; } if(y < 0.0) { y = 0-y; } float distance = 1.4; float distanceportion = divfloat(distance, (x+y)); float distancex = truex*distanceportion; float distancey = truey*distanceportion; DelayCommand(0.2, ActionMoveToLocation(Location(GetArea(self), targetpos, VectorToAngle(GetPosition(target)-GetPosition(self))), TRUE)); targetpos = currentpos+Vector(distancex, distancey, 0.0); DelayCommand(0.15, SetCommandable(TRUE, self)); DelayCommand(0.5, SetFacing(VectorToAngle(GetPosition(target)-GetPosition(self)))); DelayCommand(0.6, SetLocalInt(self, "busy", FALSE)); } } void swing1(object self, object target, float damage, int damagetype, float weaponlength, float arcsize = 0.0, float currentarc = -120.0) { if(arcsize < 0.1) { arcsize = 20.0; } else { currentarc += arcsize; } int hit; string resistdamage; object collisiontarget = GetNearestObject(OBJECT_TYPE_ALL, self); int nth = 1; while(GetIsObjectValid(collisiontarget) && GetDistanceToObject(collisiontarget) < 1.0+weaponlength) { if(conespreadfromangle(self, collisiontarget, arcsize+72.0, currentarc) && !GetIsDead(collisiontarget)) { hit = 1; SetLocalInt(collisiontarget, "struck", TRUE); ApplyEffectToObject(DURATION_TYPE_INSTANT, EffectVisualEffect(VFX_COM_BLOOD_SPARK_MEDIUM), collisiontarget); if(GetLocalFloat(collisiontarget, "health") < 0.1) { if(!GetLocalInt(target, "leavescorpse")) { ExecuteScript("dsmakedestroyable", target); } ApplyEffectToObject(DURATION_TYPE_INSTANT, EffectDamage(FloatToInt(damage), damagetype), collisiontarget); } else { if(damagetype == DAMAGE_TYPE_BLUDGEONING) { resistdamage = "resistblunt"; } else if(damagetype == DAMAGE_TYPE_SLASHING) { resistdamage = "resistcut"; } else if(damagetype == DAMAGE_TYPE_PIERCING) { resistdamage = "resistpierce"; } float rdamage = (damage - GetLocalInt(collisiontarget, "defence"))*(IntToFloat(100-GetLocalInt(collisiontarget, resistdamage))/100); if(rdamage > 0.0) { SetLocalFloat(collisiontarget, "currenthealth", GetLocalFloat(collisiontarget, "currenthealth")-rdamage); AssignCommand(collisiontarget, SpeakString(FloatToString(rdamage, 0, 0))); } if(rdamage > 1.0) { switch(Random(6)) { case 0: PlayVoiceChat(VOICE_CHAT_PAIN1, collisiontarget); break; case 1: PlayVoiceChat(VOICE_CHAT_PAIN2, collisiontarget); break; case 2: PlayVoiceChat(VOICE_CHAT_PAIN3, collisiontarget); break; } } } break; } nth++; collisiontarget = GetNearestObject(OBJECT_TYPE_ALL, self, nth); } if(currentarc < 85.0 && !hit) { DelayCommand(0.2, swing1(self, target, damage, damagetype, weaponlength, arcsize*1.15, currentarc)); } } void attack(object self, object target, int hand, int combo) { SetCommandable(TRUE, self); vector targetpos = GetPosition(target); vector currentpos = GetPosition(self); SetFacing(VectorToAngle(targetpos-currentpos)); float x = targetpos.x - currentpos.x; float y = targetpos.y - currentpos.y; float truex = x; float truey = y; if(x < 0.0) { x = 0-x; } if(y < 0.0) { y = 0-y; } float distance; if(GetDistanceToObject(target) < 1.4) { distance = GetDistanceToObject(target)-0.9; } else { distance = 1.4; } float distanceportion = divfloat(distance, (x+y)); float distancex = truex*distanceportion; float distancey = truey*distanceportion; targetpos = currentpos+Vector(distancex, distancey, 0.0); ActionMoveToLocation(Location(GetArea(self), targetpos, VectorToAngle(GetPosition(target)-GetPosition(self))), TRUE); ActionPlayAnimation(ANIMATION_FIREFORGET_STEAL, 1.45, 1.3); SetCommandable(FALSE, self); float weapondamage; int weapontype; float weaponlength; object myweapon; switch(hand) { default: myweapon = GetItemInSlot(INVENTORY_SLOT_RIGHTHAND, self); weaponlength = weaponreach(myweapon); weapondamage = weaponmulti(myweapon)*GetLocalFloat(self, "attack"); weapontype = weapondamagetype(myweapon); break; case 2: myweapon = GetItemInSlot(INVENTORY_SLOT_LEFTHAND, self); weaponlength = weaponreach(myweapon); weapondamage = weaponmulti(myweapon)*GetLocalFloat(self, "attack"); weapontype = weapondamagetype(myweapon); break; case 3: myweapon = GetItemInSlot(INVENTORY_SLOT_RIGHTHAND, self); weaponlength = weaponreach(myweapon); weapondamage = weaponmulti(myweapon)*GetLocalFloat(self, "attack"); weapontype = weapondamagetype(myweapon); myweapon = GetItemInSlot(INVENTORY_SLOT_LEFTHAND, self); weapondamage += weaponmulti(myweapon)*GetLocalFloat(self, "attack"); break; } DelayCommand(0.6, swing1(self, target, weapondamage, weapontype, weaponlength)); DelayCommand(1.1, checkattack(self, target, hand, combo)); }