Skip to content

STILL suck at scripting. 3/3 problems SOLVED: (div/0, can't find door, spell can't damage) Solutions

sunxresunxre Member Posts: 23
edited June 2022 in Builders - Scripting
for fun, i'm basically building some of my Elden ring & dark souls chars + some of the npcs in the NWN EE. Adding action combat, etc.

part of that is building abilities, stats & AI for them(All of it is script based). ALL ISSUES SOLVED. putting solutions below, so you can see the errors. really goes to show that simple oversights can create huge problems. even if they are glaringly easy to see and find, and literally laughing at your lack of observance.


(ISSUE 1, 2 & 3) Fire tempest pillars can't find targets. will also be including the process of how it is cast, incase anyone wants to copy/paste to test. I will edit the code to remove all custom checks, like magicka and relationship score.
also, doors can't be found. turns out they actually CAN. but i just had chars on inappropriate map. never found the actual /0 number. just put a custom wrapper around division to avoid it. will be included here aswell.
//========Put this within main() of Ai script or heartbeat) will also 
//add the object searching functions.
  int busy = GetLocalInt(self, "busy");
  object enemy;
  object target = detectobject(self, nth, 90,0); // find objects around self. visioncone distance is 90 metres.
  while(GetIsObjectValid(target))
  {
    if(GetObjectType(target) == OBJECT_TYPE_CREATURE)
    {
      if(!GetIsDead(target))
      {
        if(GetIsReactionTypeHostile(target, self) )
        {
          incombat = TRUE;
          enemycount++;
          if(GetDistanceToObject(target) < 13.0) { nearbyenemycount++; }
          if(!GetIsObjectValid(enemy)) { enemy = target; nearestenemydist = GetDistanceToObject(target); }
        }
      }
    }
    else //if target isn't creature
    {
      if(!GetIsObjectValid(door) && GetIsObjectValid(GetTransitionTarget(target)) && GetDistanceToObject(target) <= 8.0)
      {
        door = target;
        AssignCommand(GetFirstPC(), SpeakString("I Have FOUND THE DOOR!!! it's tag is"+GetTag(door));
      }
    }
    nth++;
    target = detectobject(self, nth, 90.0);
  }
  if(!busy && nearbyenemycount >= 4 && nearestenemydist <= 5.0)  
//casts the iconic firestorm spell from darksouls(tempest is the stronger version)
  {
    SendMessageToPC(GetFirstPC(), GetName(self)+" casting firetempest");
    SetLocalInt(self, "busy", TRUE); busy++;
    ClearAllActions();
    firetempest(self);
  }
//===============end of main additions

//division wrappers. seperated into int & float. as i felt it would be cheaper than 
//making 1 for both
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;
}


//this currently is just a wrapper for perceived that accounts 
//for distance, and allows nth input to find objects.
object detectobject(object self, int nth, float distance)
{
  object spotted = GetNearestObject(OBJECT_TYPE_ALL, self, nth);
  if(objectperceived(self, spotted) && GetDistanceToObject(spotted) <= distance)
  {
    return spotted;
  }
  return OBJECT_INVALID;
}


//checks if character is within X Field of vision of character, or should 
//otherwise be heard. very finicky ATM.
//will add more explanation, if asked about the If statement.
int objectperceived(object self, object target)
{
  float hearingrange = 30.0; //metres
  if((LineOfSightObject(self, target) && conespread(self, target, 120)) ||  ((GetCurrentAction(target) == (ACTION_ATTACKOBJECT||ACTION_CASTSPELL||ACTION_COUNTERSPELL||ACTION_ITEMCASTSPELL||ACTION_SMITEGOOD||ACTION_TAUNT) || (GetLocalInt(target, "incombat") && GetLocalInt(target, "busy")) || GetIsInCombat(target)) && GetDistanceToObject(target) <= hearingrange*3) || (  (GetCurrentAction(target) == (ACTION_MOVETOPOINT||ACTION_RANDOMWALK||ACTION_CLOSEDOOR||ACTION_DISABLETRAP||ACTION_OPENDOOR||ACTION_USEOBJECT) || (GetLocalFloat(target, "health") > 0.0 && !GetLocalInt(target, "dead") && !GetLocalInt(self, "busy")) && GetDistanceToObject(target) <= hearingrange)))
  {
    return TRUE;
  }
  return FALSE;
}

//checks if target is within a set angle from the direction origin is facing
int conespread(object origin, object target, int coneangle)
{
  float angleoffset = VectorToAngle(GetPosition(target) - (GetPosition(origin))-GetFacing(origin);
  //coneangle = 180.0;
  return(abs(FloatToInt(angleoffset)) < (coneangle/2));
}

//================End of utility functions


//==============================================The SPELL!

//------------------------------------------------------\
//                                                       \
//                                                        \
//=============Fire Tempest==============================  \
//                                                         /
//                                                        /
//-------------------------------------------------------/

// the functions are used in Ascending order. meaning firecolumndamage runs last. 
//so read it from bottom to top.


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(FloatToInt(damage), DAMAGE_TYPE_FIRE), collisiontarget);
    }
    nth++;
    collisiontarget = GetNearestObjectToLocation(OBJECT_TYPE_ALL, flamelocation, nth);
  }
}

/*columns is the maximum amount of columns that can be created. 
failed is the max amount of retries if columns are attempted to be made in walls. 
in the actual spell. the mana cost is = to the total created pillars, rather than 
just a cast deduction*/
void createfirecolumns(object self, int columns = 20, int failed = 20)
{

//this below section is all finding the position to place pillars. too messy for me to break down.
//just know that i don't understand angles, trigonometry, etc. and that this is my best attempt
  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); //this is where the pillar 
//wants to spawn and this is a check to make sure it isn't in a wall or building. 
//checks 10 metres above, to see if LOS.
//may have trouble in roofed caves, etc. or with REALLY tall buildings.
  if(LineOfSightVector(columnpos, Vector(columnpos.x, columnpos.y, columnpos.z+10.0)) && columns)
  {
    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));
//===========and between these 2 bars was the solution to the inability to find 
//damage targets. the location was 3.3 metres in the air! but the size is only 2 metres.

    flamelocation = Location(GetArea(self), Vector(columnpos.x, columnpos.y, columnpos.z), 0.0);
//===========I set the Z axis position back to the actual location pos. rather than with +3.3
    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));
  }
// And this else statement was the 1 causing the stack overflow. i didn't check 
//to make sure there was still columns left to place. so as soon as columns hit 0. 
//it'd retry 20 times, as quickly as my poor cpu could run it. 
  else if(columns) 
  {
    createfirecolumns(self, columns, failed-1);
  }

}

void firetempest(object self)
{
  PlayAnimation(ANIMATION_LOOPING_GET_LOW, 0.4, 3.4);
  SetCommandable(FALSE, self);
  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)); //runs function to create pillars
  DelayCommand(3.7, SetCommandable(TRUE, self));  //character finally finished casting spell
  DelayCommand(3.7, SetLocalInt(self, "busy", FALSE));  //character isn't locked into the action 
//anymore, and can freely choose other actions
}


now i just have to fix the other million issues i have. like my newest projectile spell flying through the ground, into the endless void!!!! and the annoying glitchiness of SetObjectVisualTransform for 10/th second recurring functions(like with an object that's flying through the AIR!!!), on said projectiles(works better with just ApplyEffectAtLocation(), but each effect hangs in previous location for a solid 1+ seconds, leaving a messy, ugly trail.).

anyways. thank you to neverwinter wights for reading and answering. as i found out on the discord. nobody could actually understand what i had written. making it impossible for people to help me. due to the very awkward way i've written the code. and the huge lack of comments. leaving people scratching their heads with the lack of context of what it is even supposed to be doing.

hopefully the above is a bit more readable.(still severely low in comments.)


QUICK UPDATE: wights just pointed out another incredibly dumb issue in my script. the while loop at the top of main breaks if nothing is detected. whereas it should realistically cycle through all objects within the detectable range, and then have a conditional for whether they are actually perceived or not.
Post edited by sunxre on

Comments

  • sunxresunxre Member Posts: 23
    edited June 2022
    Comment not necessary. Figured out how to edit OP. for those confused. i didn't know about
    Code
    
    Post edited by sunxre on
  • TarotRedhandTarotRedhand Member Posts: 1,481
    RE the title of this thread - Don't Panic, we all sucked at coding at some point. It's just a matter of practice. Although I think you may be a bit ambitious to tackle AI this early in your coding. Try downloading and reading the things in this thread -

    Tutorial List and Essential Downloads (especially if you are new to scripting)

    You might find something useful there. FWIW, I have no idea about creating AI scripts I just know how to code after a lot of practice.

    TR
  • sunxresunxre Member Posts: 23
    RE the title of this thread - Don't Panic, we all sucked at coding at some point. It's just a matter of practice. Although I think you may be a bit ambitious to tackle AI this early in your coding. Try downloading and reading the things in this thread -

    Tutorial List and Essential Downloads (especially if you are new to scripting)

    You might find something useful there. FWIW, I have no idea about creating AI scripts I just know how to code after a lot of practice.

    TR

    Thankyou for the reply. I will look through that post, although I'm not sure how much it will help with my current issues.

    Since you have experience, as you say. Can you explain why I'm getting a stack overflow with the casting of fire tempest from queelana?(current npc in working on). (No other functions within the AI Script,but are main are running while spells and actions are performed.)

    I've experienced this error in the past. But not in this current project.
    I have 2 other almost completely functional npcs. 1 has an action of some kind causing a /0 error, and the inconsistency in restoring cerulean tears(issues with inventory functions). The other is wholly functional, bar the door bug mentioned. Although they only create bonfires, restore and drink estus, and box/fight npcs with their halberd.

    And also, can you help me figure out why its failing to find and hurt targets. As all my other abilities & attacking action all do damage in the correct areas consistently.

    I might put up a video showing them in action with the chat box showing actions and debugging, if it helps with figuring out these issues.
  • NeverwinterWightsNeverwinterWights Member Posts: 339
    edited June 2022
    As for the doors not being found, it might be partly due to the exclamation in "!GetIsObjectValid(door)". If the door is not valid?
    In testing and bypassing your functions, you can easily get all nearest doors simply testing for OBJECT_TYPE_DOOR with GetObjectType();
    Also if any object fails a test in the "detectobject" function it will return an invalid object thus breaking the while loop in the main? (if i'm reading things right)
  • sunxresunxre Member Posts: 23
    edited June 2022
    Thankyou for the reply wights. I found the door issue. The problem is, my char paths around obstacles and terrain. Which happens to be what doors are stuck to (I was testing it on a square map, with 2 doors on the edges. So my chars never looked at the doors.


    I'm going to throw up an updated version of the code on here. All 3 problems have been fully resolved

    The div by 0 was an easy to fix problem, but tedious(had to scrounge through, and replace all variable a/variable b with a custom wrapper that checks if either is zero, and spits out a result of zero.

    I will also explain the door 1 in post update. But to briefly explain. The "door" variable starts empty. And once a door is found, and the variable is filled. It stops searching for anything to replace said variable.

    This makes sure they are grabbing the closest door. Specifically a transition/teleporting door. And not replacing it with a further 1. The variable being examined is actually "target".

    edit: this post was writhe with errors... and half spelt words?
    Post edited by sunxre on
  • sunxresunxre Member Posts: 23
    Also if any object fails a test in the "detectobject" function it will return an invalid object thus breaking the while loop in the main? (if i'm reading things right)

    lol. you might be right. that is a BIG mistake, and oversight. thank you very much. that is legitimately the the best. and only actually helpful feedback i have gotten so far. as it A) understood what was going on(i can understand why no one did.)
    and B) found an actual ERROR.

    now i just have to come up with a solution... will probably just turn detectobject(or just use objectperceived lol) into a simple int, and do generic nearestobject.

    thank you a tonne! it explains why sometimes the AI is super observant. and and at others, absolutely blind
  • sunxresunxre Member Posts: 23

    thank you for the reply. although honestly. it wasn't very helpful.

    the reason for the divide by 0. is because in my script, i use alot of Variable A ÷ variable B.
    in cases where variable b happens to be 0. such as when 2 vectors are actually on top of eachother, equating a distance of 0.0; it causes a divide by 0 error.

    the issue was, i couldn't figure out if one of the variables was bugged(out of a massive 5000+ line script), or if it was just because in some cases it was natural. i ended up replacing all variable a/variable be with a wrapper. so float dividefloat(variable a, variable b)

    dividefloat(float a, float b)
    if A or b is 0.0
    variable = 0
    otherwise
    variable = a ÷ b

    the main thing i was asking. is if there was an easy, or efficient way of finding the variable that is becoming zero, for those familiar with coding/scripting, who are used to finding/easily avoiding such obvious errors because i'm terrible at scripting lol.
Sign In or Register to comment.