Skip to content

Can I apply a script through a class kit?

paradoxxparadoxx Member Posts: 12
Is it somehow possible to apply a script through a class kit added via weido or any other means? Essentially I would like to have a few different scripts permenantly running on a kit I'm trying to make but I can't find a way to make this possible.

Comments

  • jmerryjmerry Member Posts: 3,881
    There is an opcode that allows you to set creature scripts (#82). Creating a spell that uses that opcode and applying it through the kit's CLAB is certainly possible.

    And then, if you try this with party members, it all falls apart. NPCs already have override scripts you don't want to replace. The protagonist, or other created characters, usually have a blank spot there, but that's a very limited resource and some things (like the Black Pits adventure) may use it already. The "class" slot belongs to combat scripts, which are set through PARTYAI.2DA or in-game menus; that'll overwrite any replacement you try to make. The "area" and "specifics" slots can't be used for party members at all. The "race" and "general" slots get blanked for party members. Finally, the "default" slot gets set any time someone joins the party.
    What about characters that aren't in the party? They don't even run the stuff in the CLAB; they don't get kit benefits unless those are explicitly placed in their CRE files.

    So, if you try to set scripts for party members, the game will undo your hard work. You can experiment with stuff (try a script with visually obvious effects), but I don't think it'll work. That leaves adding to a global script - not elegant, but sometimes it needs to be done.
  • paradoxxparadoxx Member Posts: 12
    edited March 2023
    I like the suggestion with the global script. But I presume that would not work with opcode 82. Any idea how I could implement a global script with a kit?

    Also you say it's not elegant suggesting it could cause issues, but all the effects I'm intending will be internal to the character with the kit. Are there any problems you forsee?
  • jmerryjmerry Member Posts: 3,881
    You don't "implement a global script". You add the script stuff for your kit to a global script that's already running, with blocks that test Player1 through Player6 for the kit and then do stuff to that player. Just a little more overhead every time scripts run...

    What do you want to do with these scripts, anyway? There might be other ways of accomplishing things, depending on the answer.
  • paradoxxparadoxx Member Posts: 12
    I am trying to add a few different things but the main effect I'm after is to apply a spell restore function that happens at certain time intervals while the character is idling, out of combat, not under negative status effects, and with no enemies in LOS. It works nicely as a character script but I would like to keep that free to allow for the assignment of scripts as needed and to make it more permenant.

    Which global script would you recommend to append to for this purpose?
  • morpheus562morpheus562 Member Posts: 302
    edited March 2023
    paradoxx wrote: »
    I am trying to add a few different things but the main effect I'm after is to apply a spell restore function that happens at certain time intervals while the character is idling, out of combat, not under negative status effects, and with no enemies in LOS. It works nicely as a character script but I would like to keep that free to allow for the assignment of scripts as needed and to make it more permenant.

    Which global script would you recommend to append to for this purpose?

    You don't need a script to restore spells on a timed interval. I did something for an early version of Skills and Abilities where every round it would have a % chance of restoring one used spell for a given spell level. You may be able to tweak something like that and add in more restrictions (i.e. no enemies present).

    Basically, quick rundown, is something like this:
    1. Assign a spell in the class clab like AP_SPELL1. SPELL1 will use opcode 177 to apply an effect.
    2. For the EFF File, use opcode 232 to cast spell on condition. For the condition, use condition 20 and special at 500. This means so long as my health is below 500% it will cast SPELL2 every round.
    3. SPELL2 will use opcode 326 to apply effects list where you can list out some requirements. This will cast SPELL3
    4. SPELL3 will restore the spells.
  • paradoxxparadoxx Member Posts: 12
    You don't need a script to restore spells on a timed interval. I did something for an early version of Skills and Abilities where every round it would have a % chance of restoring one used spell for a given spell level. You may be able to tweak something like that and add in more restrictions (i.e. no enemies present).

    Basically, quick rundown, is something like this:
    1. Assign a spell in the class clab like AP_SPELL1. SPELL1 will use opcode 177 to apply an effect.
    2. For the EFF File, use opcode 232 to cast spell on condition. For the condition, use condition 20 and special at 500. This means so long as my health is below 500% it will cast SPELL2 every round.
    3. SPELL2 will use opcode 326 to apply effects list where you can list out some requirements. This will cast SPELL3
    4. SPELL3 will restore the spells.

    This looks like an amazing suggestion but I'm struggling to figure out opcode 326. How would I make it do all the things I can do very simply in a script? I can see there are lots of complicated parameters there but how would I use those to only make SPELL3 be cast out of combat for example and when there is no negative status effect and when the character is idle all at once? Spell restore is a very powerful ability and I only want it to occur under very specific, safe, short rest type circumstances.

    There is another issue as well. Spell restore restores the highest level spells first, which trivialises the use of those spells as they will be the easiest to restore. In a script you can specify the ranking of the action responses, ranking low level restores over high level ones to help balance out this issue. I see no mechanism how this could be done with the method you propose.

    So in summary while opcode 326 sure looks nifty I have the impression it is cumbersome and restricted compared to the freedom of scripting.
  • morpheus562morpheus562 Member Posts: 302
    I used the method above to restore single spells of spell levels 1-3, so hardly high level. You would have to work with splprot.2da. I haven't tried, but you might be able to figure out some out of combat or not see enemy checks.
  • paradoxxparadoxx Member Posts: 12
    Thank you @morpheus I do appreciate your recommendation but I feel it will be too difficultfor me to implement.

    @mjerry can you pinpoint me to a global script I could use in the way you indicated?
  • jmerryjmerry Member Posts: 3,881
    As far as I know, there's exactly one global script at any stage of the game. What that script is called varies - baldur.BCS for the main BG1 and SoA campaigns, bdbaldur.BCS for SoD, baldur25.bcs for ToB. For modifications like these, you'd patch all of these that are present.
  • paradoxxparadoxx Member Posts: 12
    Thank you jmerry. This seems to be working like a treat. One question though. These scripts have thousands of lines. My script is meant to repeat every round. Is there a risk that the script cycle could struggle to manage that due to the length if there is a lot going on on a map? Have only tested it in the bg2 starter dungeon where it's been fine.
  • paradoxxparadoxx Member Posts: 12
    @jmerry as you have been so helpful, I was wondering if you might have an answer to the following question. How can I target a clone of a party member with a script. Clones don't have CRE files so I'm stumped as to how the script system would be able to locate them. Ideallly I would like to use the global scripts you have mentioned above but I am happy to use an assinged script if that is the only way and it is possible to assign scripts to clones.
  • jmerryjmerry Member Posts: 3,881
    Is it possible to assign scripts to clones? I've done it. When clones are created, all scripts are stripped from them, but you can change that after the fact with either script actions or spells. And detecting a clone is easy; their script name becomes "COPY".

    So, here's the code for the component I wrote, assigning "Advanced AI" with the same mode choices as the player to Project Image and Simulacrum clones.
    //
    //
    // Allow clones of party members to use Advanced AI
    // New in 3.0
    //
    
    BEGIN @50000 DESIGNATED 500
    GROUP @4
    	OUTER_SPRINT defaultAI ~BDDEFAI~
    	COPY_EXISTING ~PARTYAI.2DA~ ~override~
    		READ_2DA_ENTRY 1 0 1 defaultAI // I expect this to be the same, but you never know
    	BUT_ONLY // Get default AI
    
    	COMPILE ~jtweaks/resource/baf/j8#aiopt.baf~ EVAL // Change script to default AI
    	COPY ~jtweaks/resource/j8#aisel.SPL~ ~override~ // Pre-built spell that assigns aiopt
    
    	OUTER_SET rows = 0
    	COPY ~jtweaks/resource/2da/j8#aivar.2da~ ~override~
    		READ_2DA_ENTRIES_NOW j8#aivar 2
    		SET rows = j8#aivar
    	BUT_ONLY // List of variable/value combinations needed to control default AI
    
    	SILENT // Repeated block gets spammy
    	OUTER_FOR (n = 0; n < 6; ++n) BEGIN
    		OUTER_SET pnum = n+1
    		OUTER_FOR (row = 0; row < rows; ++row) BEGIN
    			OUTER_SPRINT var $j8#aivar(~%row%~ ~0~)
    			OUTER_SET val = $j8#aivar(~%row%~ ~1~)
    			EXTEND_TOP ~j8#aiopt.bcs~ ~jtweaks/resource/baf/aioptPatch.baf~ EVAL
    		END // Add block to set control variables.
    	END // j8#aiopt now sets AI-control options before assigning default AI
    
    	VERBOSE // Back to displaying messages
    	COPY_EXISTING ~PROJIMAG.SPL~ ~override~
    	              ~SIMULACR.SPL~ ~override~
    		LPF ADD_SPELL_EFFECT INT_VAR opcode=326 target=1 timing=4 parameter2=49 duration=1 STR_VAR resource=j8#aisel END
    	BUT_ONLY // New effect assigns option-setting script to clone after 1 sec, if clone is ally
    
    	COPY_EXISTING ~%defaultAI%.BCS~ ~override~
    		DECOMPILE_AND_PATCH BEGIN
    			REPLACE_TEXTUALLY ~\bInParty(Myself)~ ~Allegiance(Myself,GOODCUTOFF)~
    		END
    	BUT_ONLY // Standard version of script only works for party members
    

    And now, the external resources this calls on. First, j8#aivar.2da:
    2DA
    *
    			Value
    BDAI_ATTACK_MODE	1
    BDAI_ATTACK_MODE	2
    BDAI_DISABLE_DEFENSIVE	1
    BDAI_DISABLE_ITEMS	1
    BDAI_DISABLE_SPECIAL	1
    BDAI_DISABLE_OFFENSIVE	1
    BDAI_SKILL_MODE		1
    BDAI_SKILL_MODE		2
    BDAI_SKILL_MODE		3
    BDAI_SKILL_MODE		4
    BDAI_DISABLE_ATTACK	1
    

    Second, j8#aiopt.baf:
    IF
    	True()
    THEN
    	Response #100
    		ChangeAIScript("%defaultAI%",CLASS)
    END
    

    And aioptPatch.baf:
    IF
    	Name("COPY",Myself) // I think I'm a clone now
    	CheckStat(Myself,%n%,138) // Who am I a clone of? 0-5 matches player 1-6
    	TriggerOverride(Player%pnum%,Global("%var%","LOCALS",%val%)) // pnum = n + 1
    THEN
    	RESPONSE #100
    		SetGlobal("%var%","LOCALS",%val%)
    		Continue()
    END
    
    // Add this block to the beginning of the script for each player/variable/value combination
    

    All right, how does this all work? First, I read a table to get the "default" advanced AI. Personalizing that like the game does with the bd*****c combat scripts might be possible, but it's not easy; I went with the universal option instead.

    Second, I copy over some resources; a script to be assigned to a creature that changes its "class" script to the default AI, and a spell that assigns this new script to a creature.

    Next, I extend that script. For each possible value of a control variable, I add a new block that detects the variable's value for the clone's original and assigns it to the clone.

    Then, to get this actually working, I add an effect casting that new spell to PROJIMAG and SIMULACR, spells which are always applied to clones.

    And finally, I edit the default AI itself; the standard version only works for party members, so I broaden it to function whenever the user is on the party's team.

    What didn't work? Early on, I tried to detect what the player was using and assign that same script, so I could cover the .bs files in the "scripts" folder as well. That failed; I was using the ChangeAIScript script action at the time, and that doesn't allow you to assign a .bs. Also, I initially thought local variables carried over to the clone; they don't, so detecting and copying them by script was the only way.

    The method I used for assigning scripts to clones? Create a spell, filter it through an allegiance check, and cast it in PROJIMAG.spl and SIMULACR.spl which are applied to clones on creation. The allegiance check is so that I don't catch clones of enemies or hostile clones of players. Then that script I assign can check stats to find out who the creature is a clone of, check the original's stuff to set local variables, and finally assign the actual combat script to the clone.
    No global scripts involved.

    You can target a clone from a global script by using its COPY script name, but that'll catch all clones rather than any particular one. Better to piggy-back on the spells which are applied to clones at creation, as script-changing is also available in opcode form.
  • paradoxxparadoxx Member Posts: 12
    edited March 2023
    @jmerry All I can say is wow. I had no idea it would be so complex. Massive kudos for figuring this out! I'm currently away from my rig but will try to implement when I get home. At any rate many thanks so far! If there is a hall of fame of ingenous hacks for infinity I hope your name is listed there.
  • paradoxxparadoxx Member Posts: 12
    Ok one thing I'm not sure on how to do is the allegiance filter. Could you talk me through that? Thanks!
  • jmerryjmerry Member Posts: 3,881
    That's opcode 326. Basically, it casts a spell if a condition is satisfied. What condition? You can test practically anything about the target - race, class, allegiance, any given stat, even some composite tests that check multiple possibilities - and as long as that test has a row in SPLPROT.2DA, you can use it here. That 2DA comes with a ton of useful tests already included, one of which I'm using here: #49 "Allies".
    Then I use a delayed timing mode to have this fire after one second. I was thinking of some SCS fights here, in which clones of the party are created and then made enemies; the delay gives that a chance to work before my spell can assign a script.
    Add this new effect to the spells using the ADD_SPELL_EFFECT function, and we're set.
Sign In or Register to comment.