Howdy, Stranger!

It looks like you're new here. If you want to get involved, click one of these buttons!

Categories

New Premium Module: Tyrants of the Moonsea! Read More
Attention, new and old users! Please read the new rules of conduct for the forums, and we hope you enjoy your stay!

Customising SCS's Ease of Use scripts, looking for assistance

FringewareFringeware Member Posts: 29
I love the Ease of Use script. It was the first script I found that matches my philosophy, to wit, that AI scripts should manage minor tasks, but not use consumable resources or override player commands. The special hotkey functions are also really great (I never used the Paladins ProE ability, because serial casting it on the party was such a pain, but now I just press 'Z.' Awesome!).

That being said, there are thing I'd like to change. I've altered the autohealing to use lower level cure spells (I am playing BG1 and the script was written for BG2), removed the portions that prevent your party from attacking helpless foes (tactically sound, but creates annoying task management when you make heavy use of sleep, hold person, and the like) , and a few other things.

There are three issues remaining, and they're beyond my current abilities.

1) Duplicate casting of healing spells. The autohealer is great, but the problem is that if you have more than one healer, they can sometimes gang up on one person and waste a lot of magic. The BP series had a solution for this, where they made a series of variant scripts that were almost identical to each other, but the later variants had a time delay built in. The idea is your primary healer goes first, and then you give the variant scripts to your secondary healers, who wait a little bit before going. It's a clever solution, and one I'd like to port over to Ease of Use, but oh man is that part of the script complicated. I haven't been able to get it to work.

2) Healers should choose spells based on absolute values, not percentages. I don't want my cleric to cast Cure Light Wounds when someone is at 90% health, I want them to cast it when that person is missing 8 hit points. Script writers aren't idiots, and I am assuming that they did it the way that they did because they had no choice. Still, if there's a way to detect absolute damage values, I'm interested.

3) I'd like archers to close the range a bit more aggressively. In Ease-of-Use, archers don't try to run away, they just stand their ground and fire. This is good. I like this. Autokiting just leads to your archers never firing at all. I actually want the opposite behavior, where archers automatically try to decrease the range. A problem I keep having is that the enemies are spread out a bit, the closest ones die, and then my archers just stand there because they can't see any active enemies, even though they're just a few feet out of LoS and are actively engaging my melee line. It ends up creating an extra task where I have to check checking on my archers and scooting them forward. A simple solution would be to have them try to close the range to maybe 2/3 of visual range before firing. That should solve about 90% of the problems, but I have no idea how to script that.


Thanks in advance for any help!

Comments

  • JebbleJebble Member Posts: 26
    I completely agree with all you points. I hope someone with more knowledge steps in and helps you out with this.

  • FringewareFringeware Member Posts: 29
    edited May 2016
    I've found a way to solve item 2), but oh my God is it horrible. It turns out that BG script doesn't have an else-if function, which makes things awful.

    This is how the default EoU script handles Cure Light Wounds, with some slight modifications (there doesn't seem to be a code tag on these forums, so the formatting is bad, sorry about that):
    IF
    ActionListEmpty()
    CombatCounter(0)
    Global("HEALER","LOCALS",1)
    !Global("DMWWBuff","LOCALS",0)
    !Detect(NearestEnemyOf(Myself))
    !CheckStatGT(Myself,0,SPELLFAILUREPRIEST)
    HaveSpell(CLERIC_CURE_LIGHT_WOUNDS) // SPPR103.SPL (Cure Light Wounds)
    See([PC])
    See([PC])
    HPPercentLT([PC],70)
    LevelGT(Myself,0)
    !Global("DMWWBlockAutoHealing","GLOBAL",1)
    THEN
    RESPONSE #100
    SetGlobalTimer("SpellsBad","GLOBAL",3)
    ApplySpellRES("dw#alac",Myself) // Of course you will. You're doomed.
    Spell([PC],CLERIC_CURE_LIGHT_WOUNDS) // SPPR103.SPL (Cure Light Wounds)
    END



    IF
    ActionListEmpty()
    CombatCounter(0)
    Global("HEALER","LOCALS",1)
    !Global("DMWWBuff","LOCALS",0)
    !Detect(NearestEnemyOf(Myself))
    !CheckStatGT(Myself,0,SPELLFAILUREPRIEST)
    HaveSpell(CLERIC_CURE_LIGHT_WOUNDS) // SPPR103.SPL (Cure Light Wounds)
    See(SecondNearest([PC]))
    See(SecondNearest([PC]))
    HPPercentLT(SecondNearest([PC]),70)
    LevelGT(Myself,0)
    !Global("DMWWBlockAutoHealing","GLOBAL",1)
    THEN
    RESPONSE #100
    SetGlobalTimer("SpellsBad","GLOBAL",3)
    ApplySpellRES("dw#alac",Myself) // Of course you will. You're doomed.
    Spell(SecondNearest([PC]),CLERIC_CURE_LIGHT_WOUNDS) // SPPR103.SPL (Cure Light Wounds)
    END


    And you keep doing this up to SixthNearestPC. For. Each. And. Every. Spell.

    I've also noticed a bug. The healers never seem to notice themselves. I fixed this by using Player1, Player2, etc instead of NearestPC, SecondNearestPC, etc.

    So anyways, to prevent my dingdong clerics from casting valuable spells on barely injured people, I found a way to kind-of-sort-of measure exact hitpoints. It's horrible, but here it is:

    IF
    ActionListEmpty()
    CombatCounter(0)
    Global("HEALER","LOCALS",1)
    !Global("DMWWBuff","LOCALS",0)
    !Detect(NearestEnemyOf(Myself))
    HaveSpell(INNATE_CURE_LIGHT_WOUNDS) // SPIN101.SPL (Healing Touch)
    See(Player1)
    See(Player1)
    HPPercentLT(Player1,12)
    CheckStatGT(Player1,8,MAXHITPOINTS)
    !Global("DMWWBlockAutoHealing","GLOBAL",1)
    THEN
    RESPONSE #100
    ApplySpellRES("dw#alac",Myself) // Of course you will. You're doomed.
    Spell(Player1,INNATE_CURE_LIGHT_WOUNDS) // SPIN101.SPL (Healing Touch)
    END

    IF
    ActionListEmpty()
    CombatCounter(0)
    Global("HEALER","LOCALS",1)
    !Global("DMWWBuff","LOCALS",0)
    !Detect(NearestEnemyOf(Myself))
    HaveSpell(INNATE_CURE_LIGHT_WOUNDS) // SPIN101.SPL (Healing Touch)
    HPPercentLT([PC],53)
    CheckStatGT(Myself,16,MAXHITPOINTS)
    !Global("DMWWBlockAutoHealing","GLOBAL",1)
    THEN
    RESPONSE #100
    ApplySpellRES("dw#alac",Myself) // Of course you will. You're doomed.
    Spell([PC],INNATE_CURE_LIGHT_WOUNDS) // SPIN101.SPL (Healing Touch)
    END


    You keep adding more blocks to extend the accuracy of the script further and further. You have to do this For. Each. Character. And. For. Every. Spell. Oh. My. Fucking. God. But it works.

    So this is for Healing Touch, which normally heals 6 HP, but I've modded it so that it heals 8. The reason I did this is so that I can recycle the code for Cure Light Wounds.

    As for problem 1) (duplicate casting), uh, that one's really hard. The method used in the BP series won't work here, for lots of reasons. I've come up with a crude solution. I added a new hotkey that toggles autohealing on and off for each character. The Global("HEALER","LOCALS",1) check is something I added to see if "healer mode" is on or not. You can turn your healers on and off this way to avoid collisions. It's okay, but it's a pain remembering which people have healing turned on at any given time. I'd like to have a portrait icon appear when the mode is active, but I don't know how to do that.

    EDIT: I'm an idiot and got the LT/GT logic wrong. Right now, it only works in a few fringe cases (which are of course the only ones that I tested). Gotta think one through a bit more.

    Post edited by Fringeware on
  • FringewareFringeware Member Posts: 29
    edited May 2016
    Okay, I got a prototype script working now

    [spoiler]
    IF
    	ActionListEmpty()
    	CombatCounter(0)
    	Global("HEALER","LOCALS",1)
    	!Global("DMWWBuff","LOCALS",0)
    	!Detect(NearestEnemyOf(Myself))
    	HaveSpell(INNATE_CURE_LIGHT_WOUNDS) // SPIN101.SPL (Healing Touch)
    	See(Player1)
    	See(Player1)
    	HPPercentLT(Player1,24)
    	CheckStatGT(Player1,8,MAXHITPOINTS)
    	!Global("DMWWBlockAutoHealing","GLOBAL",1)
    THEN
    	RESPONSE #100
    		ApplySpellRES("dw#alac",Myself) // Of course you will. You're doomed.
    		Spell(Player1,INNATE_CURE_LIGHT_WOUNDS) // SPIN101.SPL (Healing Touch)
    END
    
    IF
    	ActionListEmpty()
    	CombatCounter(0)
    	Global("HEALER","LOCALS",1)
    	!Global("DMWWBuff","LOCALS",0)
    	!Detect(NearestEnemyOf(Myself))
    	HaveSpell(INNATE_CURE_LIGHT_WOUNDS) // SPIN101.SPL (Healing Touch)
    	See(Player1)
    	See(Player1)
    	HPPercentLT(Player1,47)
    	CheckStatGT(Player1,12,MAXHITPOINTS)
    	!Global("DMWWBlockAutoHealing","GLOBAL",1)
    THEN
    	RESPONSE #100
    		ApplySpellRES("dw#alac",Myself) // Of course you will. You're doomed.
    		Spell(Player1,INNATE_CURE_LIGHT_WOUNDS) // SPIN101.SPL (Healing Touch)
    END
    
    IF
    	ActionListEmpty()
    	CombatCounter(0)
    	Global("HEALER","LOCALS",1)
    	!Global("DMWWBuff","LOCALS",0)
    	!Detect(NearestEnemyOf(Myself))
    	HaveSpell(INNATE_CURE_LIGHT_WOUNDS) // SPIN101.SPL (Healing Touch)
    	See(Player1)
    	See(Player1)
    	HPPercentLT(Player1,58)
    	CheckStatGT(Player1,16,MAXHITPOINTS)
    	!Global("DMWWBlockAutoHealing","GLOBAL",1)
    THEN
    	RESPONSE #100
    		ApplySpellRES("dw#alac",Myself) // Of course you will. You're doomed.
    		Spell(Player1,INNATE_CURE_LIGHT_WOUNDS) // SPIN101.SPL (Healing Touch)
    END
    
    IF
    	ActionListEmpty()
    	CombatCounter(0)
    	Global("HEALER","LOCALS",1)
    	!Global("DMWWBuff","LOCALS",0)
    	!Detect(NearestEnemyOf(Myself))
    	HaveSpell(INNATE_CURE_LIGHT_WOUNDS) // SPIN101.SPL (Healing Touch)
    	See(Player1)
    	See(Player1)
    	HPPercentLT(Player1,73)
    	CheckStatGT(Player1,24,MAXHITPOINTS)
    	!Global("DMWWBlockAutoHealing","GLOBAL",1)
    THEN
    	RESPONSE #100
    		ApplySpellRES("dw#alac",Myself) // Of course you will. You're doomed.
    		Spell(Player1,INNATE_CURE_LIGHT_WOUNDS) // SPIN101.SPL (Healing Touch)
    END
    
    IF
    	ActionListEmpty()
    	CombatCounter(0)
    	Global("HEALER","LOCALS",1)
    	!Global("DMWWBuff","LOCALS",0)
    	!Detect(NearestEnemyOf(Myself))
    	HaveSpell(INNATE_CURE_LIGHT_WOUNDS) // SPIN101.SPL (Healing Touch)
    	See(Player1)
    	See(Player1)
    	HPPercentLT(Player1,76)
    	CheckStatGT(Player1,32,MAXHITPOINTS)
    	!Global("DMWWBlockAutoHealing","GLOBAL",1)
    THEN
    	RESPONSE #100
    		ApplySpellRES("dw#alac",Myself) // Of course you will. You're doomed.
    		Spell(Player1,INNATE_CURE_LIGHT_WOUNDS) // SPIN101.SPL (Healing Touch)
    END
    
    IF
    	ActionListEmpty()
    	CombatCounter(0)
    	Global("HEALER","LOCALS",1)
    	!Global("DMWWBuff","LOCALS",0)
    	!Detect(NearestEnemyOf(Myself))
    	HaveSpell(INNATE_CURE_LIGHT_WOUNDS) // SPIN101.SPL (Healing Touch)
    	See(Player1)
    	See(Player1)
    	HPPercentLT(Player1,83)
    	CheckStatGT(Player1,40,MAXHITPOINTS)
    	!Global("DMWWBlockAutoHealing","GLOBAL",1)
    THEN
    	RESPONSE #100
    		ApplySpellRES("dw#alac",Myself) // Of course you will. You're doomed.
    		Spell(Player1,INNATE_CURE_LIGHT_WOUNDS) // SPIN101.SPL (Healing Touch)
    END
    
    IF
    	ActionListEmpty()
    	CombatCounter(0)
    	Global("HEALER","LOCALS",1)
    	!Global("DMWWBuff","LOCALS",0)
    	!Detect(NearestEnemyOf(Myself))
    	HaveSpell(INNATE_CURE_LIGHT_WOUNDS) // SPIN101.SPL (Healing Touch)
    	See(Player1)
    	See(Player1)
    	HPPercentLT(Player1,86)
    	CheckStatGT(Player1,48,MAXHITPOINTS)
    	!Global("DMWWBlockAutoHealing","GLOBAL",1)
    THEN
    	RESPONSE #100
    		ApplySpellRES("dw#alac",Myself) // Of course you will. You're doomed.
    		Spell(Player1,INNATE_CURE_LIGHT_WOUNDS) // SPIN101.SPL (Healing Touch)
    END
    
    IF
    	ActionListEmpty()
    	CombatCounter(0)
    	Global("HEALER","LOCALS",1)
    	!Global("DMWWBuff","LOCALS",0)
    	!Detect(NearestEnemyOf(Myself))
    	HaveSpell(INNATE_CURE_LIGHT_WOUNDS) // SPIN101.SPL (Healing Touch)
    	See(Player1)
    	See(Player1)
    	HPPercentLT(Player1,88)
    	CheckStatGT(Player1,56,MAXHITPOINTS)
    	!Global("DMWWBlockAutoHealing","GLOBAL",1)
    THEN
    	RESPONSE #100
    		ApplySpellRES("dw#alac",Myself) // Of course you will. You're doomed.
    		Spell(Player1,INNATE_CURE_LIGHT_WOUNDS) // SPIN101.SPL (Healing Touch)
    END
    
    IF
    	ActionListEmpty()
    	CombatCounter(0)
    	Global("HEALER","LOCALS",1)
    	!Global("DMWWBuff","LOCALS",0)
    	!Detect(NearestEnemyOf(Myself))
    	HaveSpell(INNATE_CURE_LIGHT_WOUNDS) // SPIN101.SPL (Healing Touch)
    	See(Player1)
    	See(Player1)
    	HPPercentLT(Player1,90)
    	!HPPercentGT(Player1,94)
    	CheckStatGT(Player1,64,MAXHITPOINTS)
    	!Global("DMWWBlockAutoHealing","GLOBAL",1)
    THEN
    	RESPONSE #100
    		ApplySpellRES("dw#alac",Myself) // Of course you will. You're doomed.
    		Spell(Player1,INNATE_CURE_LIGHT_WOUNDS) // SPIN101.SPL (Healing Touch)
    END
    [/spoiler]

    This is one sixth of the healing touch script. Unless you make a separate condition of for and every possible MAXHP value, you have to accept some kind of trade off between over- and underhealing. I chose to accept a single point of overhealing. When MAXHP goes beyond 64, it stops caring about precision anymore and just casts the spell whenever you are at 90-94% health. I had it stop at 95% because I prefer to let the last few points be taken care of by goodberries.

    Sigh, now to do this again for Cure Light, Medium, Serious, and Critical :/

    Post edited by Fringeware on
  • BillyYankBillyYank Member Posts: 2,769
    @Fringeware " (there doesn't seem to be a code tag on these forums, so the formatting is bad, sorry about that)"

    Highlight the text you want to display as code, hit the arrow next to the paragraph sign above the edit window and click "code".

  • FringewareFringeware Member Posts: 29
    Thanks Billy, that worked.

    So, does anyone know how to add/remove portrait icons via script? That would make it so much easier to track what orders I've given to whom.

  • FringewareFringeware Member Posts: 29
    I just found this thread: https://forums.beamdog.com/discussion/comment/363506

    The author apparently wrote a script that does some of the things I am trying to figure out, and has some other feature that I would like, but the download links are all dead. Does anyone have copies of those scripts, or knows where else to find them?

  • FringewareFringeware Member Posts: 29
    So, if anyone cares, I found a crude way to implement goodberry sharing:

    [Spoiler]
    IF
    !HasItem("gberry",Myself)// no berries
    OR(6)
    	HasItem("gberry",Player1)
    	HasItem("gberry",Player2)
    	HasItem("gberry",Player3)
    	HasItem("gberry",Player4)
    	HasItem("gberry",Player5)
    	HasItem("gberry",Player6)
    
    THEN
    RESPONSE #100
    GlobalShout(3015)// shout so everyone in the area hears
    SetGlobal("WANTBERRY","GLOBAL",1)
    END
    
    IF
    Heard([GOODCUTOFF],3015)// i hear the shout
    HasItem("gberry",Myself)// and have the berry
    Global("WANTBERRY","GLOBAL",1)
    THEN
    RESPONSE #100
    MoveToObject(LastHeardBy())// move to whoever shouted
    GiveItem("gberry",LastHeardBy()) and give them the potion
    SetGlobal("WANTBERRY","GLOBAL",0)
    END
    [/spoiler]

    This causes a character with no goodberries to request some from the group. Someone (I'm not sure how the game decides who, but it's always one person) walks up to the requester and hands them a stack.

    It runs into problems when there are less stacks of berries then there are people in the group. It creates a hot potato situation where they keeping passing stacks back and forth. I've found that once you've got a decent stash, this doesn't happen very often, so I haven't looked into fixing it.

    For all the bugs, automated goodberry distribution is pretty nice, and makes the spell practical.

  • FringewareFringeware Member Posts: 29
    edited May 2016
    I've finally got the autohealer part mostly working. The code itself is hideous, but it works fine. I'm using a system where healers call their targets, and avoid healing anyone that has already been called by someone else. This has almost completely eliminated duplicate healing from my game. If the party is very scattered, you might get cases where multiple healers try to gang up on a distant injured person, because their "calls" expire before they can reach the target. This is rare, and it's very easy to spot and correct it manually when it does happen.

    A quick feature summary:

    1) Healers avoid overkill. They won't cast spells on someone if a significant portion of the healing would be wasted. Small amounts of overkill are tolerated.
    2) Healers use their bigger spells first. Some people prefer it the other way around, but I would rather that my clerics use their bigger spells when the opportunity arises. You don't always need a Cure Critical, but a Cure Light is almost always appropriate.
    3) Healers avoid ganging up on one target.
    4) Item 3) does not interfere with with any non-duplicate healing. If you have two healers and two injured parties, both healers will immediately get to work, each one on a separate person.
    5) Healers can heal themselves. This was a missing functionality from Ease-of-Use.
    6) Healer status can be toggled on and off on a per-person basis.
    7) Good Berry is cast automatically (from Ease-of-Use)
    8) Good berries are consumed automatically (also from EoU)
    9) Good berries are distributed automatically (original). I solved the "hot potato" problem that I was talking about earlier in this thread.


    [Spoiler]
    [/spoiler]

    Here's a test case where all five of my characters have Cure Lights Wounds, and Jaheira and Viconia also have Cure Medium Wounds. Eveyone has some degree of damage, except for Isra, who is at full health. This screen shot is taken two seconds after combat ended.


    The moment combat ended, everyone except Isra instantly cast their healing spells, all on unique targets, and with no overkill. Viconia had to use her CLW because all of the other good targets for CMW had already called themselves, but Jaheira was able to use her own CMW on Viconia. Isra had to wait a few beats because she's the fifth healer, and there are only four targets, but once everyone else's calls expired, she targeted Imoen with her CLW.

    Two seconds after this, Jaheira uses a CLW on Viconia, and everyone else begins topping themselves off with good berries. Some people didn't have any berries, but Jaheira had several stacks, so she started passing them out automatically.

    This is very close to how I would have managed this manually, but this all happened in four seconds of real time and with no input at all. I'm very happy with this result.

    This was intended to just be for my own personal use, but I'm starting to think about sharing it. The problem is that this is a highly modified version of Ease of Use, which doesn't belong to me. I'm not sure what the rules are abut this sort of thing. At the very least, I am happy to answer questions.

Sign In or Register to comment.