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.