Jump to content

Remove All Spells of a Certain Level


Recommended Posts

Hey,

So I'm working with @Lava in a project where I'll turn a cleric into a cleric/mage.

That means the XP will be split and some cleric levels will be lost - which mean eventually losing access to a whole spell circle.

I do know how to remove spells one by one (quiet easy actually) but doing the way I usually do some mod-added spells may be left behind.

SO...

Is there a tricky to track and remove all the spells from a cleric spellbook based on the spell's level?

Thanks in advance.

Link to comment

I am pretty sure I wrote something like that for you a few years ago when you began to code kits. ;)

afair, you decided to use another process - more suitable for your needs -. Unfortunately, I did not keep it when I updated my computer. The goal was to read all spell files (including those added by mods), check a few values (type, level, restrictions), write an array listing spells to remove, then apply an innate AP_spell that removed those spells from spellbook at level up.

Edited by Gwendolyne
Link to comment

Maybe it was for the Undead Predator, I'll look into it. IIRC I went for the Disable spellcasting button and called it a day but may be a good way to improve it too.

I'll give it a spin, thanks!

And to everyone else, I'm still open to receive some new ideas.

Link to comment

I don’t quite understand the concept (magically get 10 levels of wizard experience?) but I've tried something like that, and the only way I found is to use opcode 172. Which means the mod must be installed after all other mods that add wizard spells. 

The method is in Tome & Blood’s ‘Revised Illusionary Clones’ component... it creates a spell with a bunch of 172 effects to remove all wizard spells above level X.

Link to comment
18 minutes ago, subtledoctor said:

I don’t quite understand the concept (magically get 10 levels of wizard experience?) but I've tried something like that, and the only way I found is to use opcode 172. Which means the mod must be installed after all other mods that add wizard spells. 

The method is in Tome & Blood’s ‘Revised Illusionary Clones’ component... it creates a spell with a bunch of 172 effects to remove all wizard spells above level X.

Let's say the character has 20,000 XP

As a pureclass Cleric it would be a 5th level Cleric with access to spells up to the Third Circle.

As a multiclass Cleric-Mage it would be a 4th level Cleric/4th level Mage with access to spells up to the Second Circle only.

So if the "make this character a CM instead of a C" component is chosen, the Third Circle entry at the spellbook needs to be removed and its slots zeroed.

I'm talking about removing divine spells. Arcane ones will be added by this component but that is quite easy. Removing is not that bad with REMOVE_KNOWS_SPELL but I may skip a mod-added one.

Cheers.

Link to comment
10 minutes ago, subtledoctor said:

Ah, it’s for an NPC. My brain read it  as an in-game ability.

So it’s being done at install-time? That’s much easier. In that case I would just generate an array of all spells of the appropriate levels and spell type, and PHP_EACH -> REMOVE_SPELL %spell%

Seems like an excellent solution and exactly what I'm looking for but IDK if I know how to do it to be honest. I could also use this to do the inverse (add spells) for DoF's Mazzy and Shar-Teel's component (so far the spells are added one by one).

Link to comment

Something like...

COPY_EXISTING_REGEXP GLOB ~[all .spl]~
  PATCH_IF %SOURCE_SIZE% > 0x71 BEGIN
    READ_SHORT 0x1c type
    READ_LONG 0x34 level
    PATCH_IF type = 2 BEGIN
      PATCH_IF level = 1 BEGIN
        SPRINT $level_one_divine(~%SOURCE_RES%~) ~1~
      END
      PATCH_IF level = 2 BEGIN
        SPRINT $level_two_divine(~%SOURCE_RES%~) ~1~
      END
      [etc. levels 3-7]
    END
  END
BUT_ONLY

COPY_EXISTING ~this_npc.cre~ ~override~
  PHP_EACH level_seven_divine AS spl => nul BEGIN
    REMOVE_KNOWN_SPELL ~%spl%~
  END
  [etc. for whatever spell levels]
BUT_ONLY
Edited by subtledoctor
Link to comment
18 minutes ago, subtledoctor said:

Something like (sorry for the formatting, you can’t edit text tagged as code when using a mobile browser)...


COPY_EXISTING_REGEXP GLOB ~[all .spl]~
	PATCH_IF %SOURCE_SIZE% > 0x71 BEGIN
	READ_SHORT 0x1c type
	READ_LONG 0x34 level
	PATCH_IF type = 2 BEGIN
	PATCH_IF level = 1 BEGIN
	SPRINT $level_one_divine(~%SOURCE_RES%~) ~1~
	END
	PATCH_IF level = 2 BEGIN
	SPRINT $level_two_divine(~%SOURCE_RES%~) ~1~
	END
	[etc. levels 3-7]
	END
	END
	END
	BUT_ONLY
	COPY_EXISTING ~this_npc.cre~ ~override~
	PHP_EACH level_seven_divine AS spl => nul BEGIN
	REMOVE_SPELL ~%spl%~
	END
	[etc. for whatever spell levels]
	BUT_ONLY

That will save me a lot of time, thanks!

I will try to adapt and use it to ADD_SPELL for two NPCs but I need to add one more filter (other than type and level) that is alignment restriction - so an Evil character wouldn't receive spells like Holy Smite and a Good character wouldn't receive spells like Unholy Smite.

I know I would need to mess with READ_LONG 0x1e (Exclusion Flags) and bits 1 (exclude Evil) and 2 (exclude Good). But... how?

Thanks in advance!

Edited by Raduziel
Link to comment
  READ_BYTE 0x1e good_evil
  PATCH_IF (good_evil BAND 0b00000100 = 0b00000100) BEGIN
    [this spell excludes Good casters]
  END
  PATCH_IF (good_evil BAND 0b00000010 = 0b00000010) BEGIN
    [this spell excludes Good casters]
  END

I think.  That's untested but is basically how you can check specific bits in fields such as spell flags.

Link to comment
5 hours ago, subtledoctor said:

  READ_BYTE 0x1e good_evil
  PATCH_IF (good_evil BAND 0b00000100 = 0b00000100) BEGIN
    [this spell excludes Good casters]
  END
  PATCH_IF (good_evil BAND 0b00000010 = 0b00000010) BEGIN
    [this spell excludes Good casters]
  END

I think.  That's untested but is basically how you can check specific bits in fields such as spell flags.

I'll test it and provide feedback. If it work as I'm planning I'll pay you a beer someday.

Link to comment

@subtledoctor

Apparently it doesn't work (for adding spells at least):

This installs smoothly but the NPC's spellbook is blank.

COPY_EXISTING_REGEXP ~^.+\.spl$~ override
	PATCH_IF %SOURCE_SIZE% > 0x71 BEGIN
	READ_SHORT 0x1c type
	READ_LONG  0x34 level
	READ_LONG  0x1e restriction
	PATCH_IF type  = 2 BEGIN
	PATCH_IF (restriction BAND 0b10000100 = 0b10000100) BEGIN
	PATCH_IF level = 1 BEGIN
	SPRINT $level_one_divine(~%SOURCE_RES%~)   ~1~
	END
	PATCH_IF level = 2 BEGIN
	SPRINT $level_two_divine(~%SOURCE_RES%~)   ~1~
	END
	PATCH_IF level = 3 BEGIN
	SPRINT $level_three_divine(~%SOURCE_RES%~) ~1~
	END
	PATCH_IF level = 4 BEGIN
	SPRINT $level_four_divine(~%SOURCE_RES%~)  ~1~
	END
		END
			END
				END
	BUT_ONLY
			

    COPY_EXISTING ~shartd.cre~ ~override~
      WRITE_SHORT     0x244 0
      WRITE_BYTE      0x246 "%kit%"
      WRITE_BYTE      0x247 0x40
	  WRITE_BYTE	  0x239 0        //Exceptional Strength
	  WRITE_BYTE	  0x23b 9        //Wisdom
	  WRITE_BYTE	  0x23d 11       //Constitution
	  WRITE_BYTE	  0x273 3        //Class
	  WRITE_LONG      0x028 24592    //Animation
	  WRITE_BYTE      0x052 20       //Thac0
	  WRITE_BYTE      0x054 10       //Death
	  WRITE_BYTE      0x055 14       //Wands
	  WRITE_BYTE      0x056 13       //Paralyze
	  WRITE_BYTE      0x057 16       //Breath
	  WRITE_BYTE      0x058 15       //Spells
	  PHP_EACH level_one_divine   AS spl => nul  BEGIN
	ADD_KNOWN_SPELL ~%spl%~ #0 ~priest~
	  END
	  PHP_EACH level_two_divine   AS spl => nul  BEGIN
	ADD_KNOWN_SPELL ~%spl%~ #1 ~priest~
	  END
	  ADD_MEMORIZED_SPELL ~CLERIC_DOOM~        #0 ~priest~ (2)
	  ADD_MEMORIZED_SPELL ~CLERIC_FLAME_BLADE~ #1 ~priest~ (1)
	  READ_LONG       0x2a8 spl_off
	  READ_LONG       0x2ac spl_num
		FOR (i = 0; i < spl_num; ++i) BEGIN
			READ_SHORT (spl_off + i * 0x10 + 0x8) type
			PATCH_IF (type = 0) BEGIN
				READ_SHORT (spl_off + i * 0x10) level
					PATCH_MATCH level WITH
			0 BEGIN	WRITE_SHORT (spl_off + i * 0x10 + 0x2) 2 END
			1 BEGIN	WRITE_SHORT (spl_off + i * 0x10 + 0x2) 1 END
			DEFAULT
			
		END
	END
END
	BUT_ONLY

The extra "1" at 0b00000100 is to keep Druid/Shaman spells away. I also tested without it and the spellbook was empty still. Also using READ_BYTE instead of READ_LONG doesn't do the trick.

Thanks for the help.

Edited by Raduziel
Link to comment

"0b00000100" represents the eight bits (the eight 1s or 0s after the 'b') in each byte.  In NI the "flags" field is 4 bytes large... but you still have to deal with each byte individually.  So for the good/evil flags you do

READ_BYTE 0x1e good_evil

and for the druid/cleric restrictions you do

READ_BYTE 0x21 druid_cleric

and deal with each one separately. 

Honestly, it can get complicated if you are adding these limiters, since for each character there will be a different combination of spells to add or remove.  I would step back:

  1. Make an array of all 1st-level divine spells, an array all 2nd-level divine spells, etc., as outlined above.
  2. Make an array of all spells that exclude Good and an array of spells that exclude Evil.
  3. Make an array of spells that exclude Druids, and an array of spells that exclude Clerics.
DEFINE_ACTION_FUNCTION divine_spell_arrays

COPY_EXISTING_REGEXP ~^.+\.spl$~ override
 PATCH_IF %SOURCE_SIZE% > 0x71 BEGIN
  READ_SHORT 0x1c type
  READ_LONG  0x34 level
  READ_BYTE  0x1e good_evil
  READ_BYTE  0x21 cleric_druid
   PATCH_IF type  = 2 BEGIN
	PATCH_IF level = 1 BEGIN
     SPRINT $level_one_divine(~%SOURCE_RES%~)   ~1~
    END
    PATCH_IF level = 2 BEGIN
     SPRINT $level_two_divine(~%SOURCE_RES%~)   ~1~
    END
    PATCH_IF level = 3 BEGIN
     SPRINT $level_three_divine(~%SOURCE_RES%~) ~1~
    END
    PATCH_IF level = 4 BEGIN
     SPRINT $level_four_divine(~%SOURCE_RES%~)  ~1~
    END
    PATCH_IF level = 5 BEGIN
     SPRINT $level_five_divine(~%SOURCE_RES%~)  ~1~
    END
    PATCH_IF level = 6 BEGIN
     SPRINT $level_six_divine(~%SOURCE_RES%~)  ~1~
    END
    PATCH_IF level = 7 BEGIN
     SPRINT $level_seven_divine(~%SOURCE_RES%~)  ~1~
    END
    PATCH_IF (good_evil BAND 0b00000100 = 0b00000100) BEGIN
     SPRINT $exclude_good_divine(~%SOURCE_RES%~)  ~1~
    END
    PATCH_IF (good_evil BAND 0b00000010 = 0b00000010) BEGIN
     SPRINT $exclude_evil_divine(~%SOURCE_RES%~)  ~1~
    END
    PATCH_IF (cleric_druid BAND 0b01000000 = 0b01000000) BEGIN
     SPRINT $exclude_cleric_divine(~%SOURCE_RES%~)  ~1~
    END
    PATCH_IF (cleric_druid BAND 0b10000000 = 0b10000000) BEGIN
     SPRINT $exclude_druid_divine(~%SOURCE_RES%~)  ~1~
    END
   END
  END
 END
BUT_ONLY

END // end function

So you have 11 arrays.  Put that all in your ALWAYS block, and at the beginning of each NPC component put "LAF cleric_spell_arrays END"  All that does is load into memory the arrays for organizing divine spells along each of those parameters.  Now you can use each one for each NPC.  Pseudocode:
 

LAF cleric_spell_arrays END

COPY_EXISTING ~shar-teel.cre
 PHP_EACH exclude_good_divine AS spl => nul BEGIN
  ADD_CRE_EFFECT INT_VAR opcode=171 target=1 timing=9 power=1 STR_VAR resource=EVAL ~%spl%~ END
// using ADD_CRE_EFFECT so you don't have to know  spell  levels and the other stuff for ADD_KNOWN_SPELL
 END
 PHP_EACH exclude_druid_divine AS spl => nul BEGIN
  REMOVE_KNOWN_SPELL ~%spl%~
 END
 PHP_EACH level_seven_divine AS spl => nul BEGIN
  REMOVE_KNOWN_SPELL ~%spl%~
 END
 PHP_EACH level_six_divine AS spl => nul BEGIN
  REMOVE_KNOWN_SPELL ~%spl%~
 END
BUT_ONLY

So you add all evil-only spells (of every type and level); then you remove all druid spells, and remove all spells of too-high spell levels.

Edited by subtledoctor
Link to comment
3 hours ago, subtledoctor said:

"0b00000100" represents the eight bits (the eight 1s or 0s after the 'b') in each byte.  In NI the "flags" field is 4 bytes large... but you still have to deal with each byte individually.  So for the good/evil flags you do


READ_BYTE 0x1e good_evil

and for the druid/cleric restrictions you do


READ_BYTE 0x21 druid_cleric

and deal with each one separately. 

Like this?

COPY_EXISTING_REGEXP ~^.+\.spl$~ override
	PATCH_IF %SOURCE_SIZE% > 0x71 BEGIN
	READ_SHORT 0x1c type
	READ_LONG  0x34 level
	READ_BYTE  0x1e alignment
	READ_BYTE  0x21 class
	PATCH_IF type  = 2 BEGIN
	PATCH_IF (alignment BAND 0b00000100 = 0b00000100) BEGIN
	PATCH_IF (class     BAND 0b10000000 = 0b10000000) BEGIN
	PATCH_IF level = 1 BEGIN
	SPRINT $level_one_divine(~%SOURCE_RES%~)   ~1~
	END
	PATCH_IF level = 2 BEGIN
	SPRINT $level_two_divine(~%SOURCE_RES%~)   ~1~
	END
	PATCH_IF level = 3 BEGIN
	SPRINT $level_three_divine(~%SOURCE_RES%~) ~1~
	END
	PATCH_IF level = 4 BEGIN
	SPRINT $level_four_divine(~%SOURCE_RES%~)  ~1~
	END
		END
			END
				END
					END
	BUT_ONLY

 

Still doesn't work, though.

This will be done only twice (for Mazzy and Shar-Teel) so the eleven-arrays approach doesn't seem necessary.

Link to comment

By nesting the conditions you are making very small arrays. Your code makes:

- one array of 1st-level cleric-only spells excluded from good casters

- one array of 2nd-level cleric-only spells excluded from good casters

- one array of 3rd-level cleric-only spells excluded from good casters

- one array of 4th-level cleric-only spells excluded from good casters

I don’t think there even are any spells in some of those categories. Maybe running PHP_EACH on an empty array causes an error... I’m not sure how Weidu handles such things. 

You could also try it my way, adding appropriate spells and removing inappropriate spells in distinct steps. The error might be in the changes you made... hard for me to tell, reading the code on a phone.

You know, the simpler alternative might be what I do in NPC_EE: just drop the .CRE to 1st level, set stats appropriately, leave the XP alone, and then as soon as they join the party they can level up to their appropriate level. Priest spells are added automatically upon level-up, so you can just let the engine handle the details for you. Easy-peasy, and it gives the player more control of proficiencies, hit points, and whatnot. Just a thought. 

Link to comment

Join the conversation

You are posting as a guest. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...