I've been doing some reading/research on game AI and there are some really complex structures that could be used, but I think I will stick with the more simple method of decision trees. I started detailing out a bit of the General Caster:
//if rand(100) > 30 and not silenced then cast spell
// if rand(100) > 30 use defense spell
// if does not have a defense effect active
// if has a major defense spell available to use then use that
// else if has a minor defense spell available to use then use that
// else don't use a defense spell and move on to next
// if rand(100) > 30 use a responsive spell
// if some allies (maybe 1/3) have a debuff effect and caster has the counter spell, then 50% chance to remove the debuff
// else if an ally have low HP (<50%) and caster has a heal spell, then 50% chance to heal them
// pick random spell
// if heal spell, but no one needs healing then try again
// if buff spell, but allies (maybe 1/3) already have a similar buff then try again
// if debuff spell, but PCs already have the debuff effect or are immune (after learned) then try again
// if damage spell, but not enough PC can be hit then try again
// or rank each spell available based on how effective it would be on this round (need a weighting system)
//else attack/move
I am not sure about whether to use the 'pick random spell' (the current system used) or a 'ranking' system for the fall back option (no defense or responsive spell used this round). I kind of like the idea of going through each spell that is still available to the caster and ranking them based on how effective they may be on that round. I would using weighting systems to make that determination. Could be randomized a bit by selecting one of the top three ranked spells instead of the top spell each time. Thoughts?
Yes, I like the idea of a weighted system with randomisation on the last three options. It's a good balance and will keep things unpredictable but with a degree of intelligence.
Here is what I have so far for scoring a damage type spell with weighting. I still think I could add different weighting for for PCs with high HP vs. low HP. I removed some code for easier reading here.
public int ScoreOfDamageSpell(Creature crt, Spell spl, int casterWeight, int crtWeight, int pcWeight)
{
//test numbers below, remove once this method is used somewhere
casterWeight = -4;
crtWeight = -1;
pcWeight = 2;
for (int y = spl.range; y > -spl.range; y--)
{
for (int x = spl.range; x > -spl.range; x--)
{
utility = 0; //reset utility for each point tested
selectedPoint = new Coordinate(crt.combatLocX + x, crt.combatLocY + y);
//check if selected point is a valid location on combat map
if (!isSquareOnCombatMap(selectedPoint.X, selectedPoint.Y))
//if ((selectedPoint.X < 0) || (selectedPoint.X > gv.mod.currentEncounter.MapSizeX - 1) || (selectedPoint.Y < 0) || (selectedPoint.Y > gv.mod.currentEncounter.MapSizeY - 1))
{
continue;
}
//check if selected point is in LoS, if not skip this point
if (!isVisibleLineOfSight(new Coordinate(endX, endY), new Coordinate(startX, startY)))
{
continue;
}
if (selectedPoint == new Coordinate(crt.combatLocX, crt.combatLocY))
{
utility += casterWeight; //the creature at least attempts to avoid hurting itself, but if surrounded might fireball itself!
if (crt.hp <= crt.hpMax / 4) //caster is wounded, definately avoids itself.
{
utility += casterWeight;
}
}
foreach (Creature crtr in gv.mod.currentEncounter.encounterCreatureList) //if its allies are in the burst subtract a point, or half depending on how evil it is.
{
if (this.CalcDistance(crtr, crtr.combatLocX, crtr.combatLocY, selectedPoint.X, selectedPoint.Y) <= spl.aoeRadius) //if friendly creatures are in the AOE burst, count how many, subtract 0.5 for each, evil is evil
{
utility += crtWeight;
}
}
foreach (Player tgt_pc in gv.mod.playerList)
{
if ((this.CalcDistance(null, tgt_pc.combatLocX, tgt_pc.combatLocY, selectedPoint.X, selectedPoint.Y) <= spl.aoeRadius) && (tgt_pc.hp > 0)) //if players are in the AOE burst, count how many, total count is utility //&& sf.GetLocalInt(tgt_pc.Tag, "StealthModeOn") != 1 <-throws an annoying message if not found!!
{
utility += pcWeight;
if (utility > optimalUtil)
{
//optimal found, choose this point
optimalUtil = utility;
}
}
}
}
}
if (gv.mod.debugMode)
{
gv.cc.addLogText("<yl>" + spl.name + ":" + optimalUtil + "</yl><BR>");
}
return optimalUtil;
}
Hey life stuff has been keeping me occupied for awhile, but checking in and seeing this thread is very cool! Even replaying some IB classics with these mods will be really fun.
cartons wrote: ↑Fri Aug 27, 2021 7:50 pm
Hey life stuff has been keeping me occupied for awhile, but checking in and seeing this thread is very cool! Even replaying some IB classics with these mods will be really fun.
I agree, it will definitely make the casters more challenging and threatening. Leveling up, spell selection, party configuration will be even more critical. Some of the original modules may become too difficult . I'll have a basic General Caster AI that will be slightly more intelligent than the one before (and will be the default for the older modules) and more other AIs that will be very intelligent and deadly.
More progress on the General Caster AI. I have the first two parts completed, the Defensive and Responsive options. Next up is the last option, the Random option.
//if rand(100) > 30 and not silenced then cast spell
// if rand(100) > 30 use Defense spell
// if does not have a defense spell active
// if has a major defense spell available to use then use that
// else if has a minor defense spell available to use then use that
// else don't use a defense spell and move on to next option (Responsive)
// if rand(100) > 30 use a Responsive spell
// if an ally has a hold effect, and the caster has counter spell, then use that
// else if some allies(maybe 1/3) have a debuff effect and caster has the counter spell, then remove the debuff
// else if many allies have low HP and caster has a mass heal spell, then use that
// else if an ally has low HP(<50%) and caster has a heal spell, then heal them
// else, didn't find an appropriate responsive spell so go to the next option (Random)
// pick Random spell rand(4)
// 1 -if has heal spell, but no one needs healing then try again, else use most effective heal spell available (try from major list first)
// 2 -if has buff spell, but allies(maybe 1 / 3) already have a similar buff then try again, else use most effective buff spell available (try
from major list first)
// 3 -if has debuff spell, but PCs already have the debuff effect or are immune(after learned) then try again, else use most effective
debuff spell available (try from major list first)
// 4 -if has damage spell, but not enough PC can be hit then try again, else use most effective damage spell available (try from major list
first)
//else attack/move
Small request, slowdive. If you have time, could you look into the Dimension Door spell that you have packaged with Proving Grounds, and how it works for enemy casters? I gave it to a magic using monster, and it did cast the spell, but the effect did not apply. He did not move to the new point location. The spell does work flawlessly for player characters.
It could be a matter of logistics, the way moving a creature works. Maybe because of the code, it would require a separate spell for enemy casters. However, if it does work, this is a possible way to have teleporting opponents, or phasing out to different places on the battlefield.
I have three of the four parts of the Random option completed. I just have the debuff part left to do. I am hopeful that I will have that completed this weekend and can start setting up the test encounter(s) to debug the new code.
I finished all the code parts for the new General Caster AI. I still need to add a few more features like moving first if needed to cast to the optimal square (that will require some decision memory tricks). Now I'm testing it out in the Proving Grounds module. I created a test wizard that has a lot of spells to choose from (almost all of them). Found a few bugs already and some issues with not showing mirror images for creatures. More testing to follow.
After some more testing, it looks like the AI is working as expected. I added in some more randomness to the spell selection from within a category (heal, damage, buff, debuff) and within a major and minor group. The AI also looks at trying an AoE spell first (only uses it if enough targets are in the best AoE location) and then a single target spell next. I still want to add the moving before casting feature to optimize the use of the best spell on each round. This will take some time, but I think it will be worth the effort.
I made a short video to show where I am at with the new General Caster AI. I walk through the basics of the code first and then do a demonstration of the code in action with the combat test module:
I just had a chance to watch the video, interesting stuff. The most satisfying thing to me is seeing a variety of spells, and less repeating castings of the same buff. Makes magic using enemies so much more dangerous. Also, I think a key to an engaging battle is one where the player has to react to events on the field. The web, hold and high damage fireballs creates a lot of conditions to deal with, in order to win such a fight.
I was able to get the move before casting working. Now the caster will find the best possible location/target for the selected spell and move to the closest location that will work to cast from. It still has a few weird moves every once in awhile that I'll have to figure out, but for the most part it is fairly brutal. I'll do some more play testing and then try adding it to IB for v204. I'll also make another video once everything seems to be working as intended.
I made a short video to show the addition of the "move before casting" feature. There are still some anomalies that need tweaking and some better decision making that needs to be added in, but I think it is going well so far. What do you think?
Hey, slowdive, video looks good. Very cool to see casters move and then take an action.
Just a head's up, I don't think the Silence spell I copied over from Proving Grounds is working properly. I tested on an enemy creature, and it showed the Silence effect, but she still was able to cast a spell. I am going to check the effects data, so see if there is a property that needs to be applied to prevent spell casting.
edit: Ah, under the status type, it says Silenced. But then in the descriptive box at the bottom, indicates Silenced has not been implemented.
Would you prefer that I make the new "GeneralCaster" AI to automatically replace the existing GeneralCaster AI or would you prefer that the new AI be called GeneralCaster2 and the builder would need to manually change caster AI setting to point to the new AI if they want to use it. I could leave the old AI in as "GeneralCasterOld" and builders could point to that as well. I'm fine either way, but if everyone wants to automatically move over to the new AI, it would be easier for the builder to not have to manually change a bunch of AI settings to point to the new AI.