Tash Posted February 10, 2015 Share Posted February 10, 2015 Hi everyone, I feel a little bit more comfortable when it comes to coding now; I'd had almost zero experience prior to my first BG modding attempts some time ago. Part of what my two mods currently do, they bring more consistency to spell casting and expiration sound effects. While everything works in its simplicity, I want to improve my code if only slightly and make a separate component-patch. My mod is for BG1. The component in question will patch all the valid spells to use proper spell expiration sound effects according to this scheme: EFF_E01 - Illusion EFF_E02 - Abjuration EFF_E03 - Conjuration EFF_E04 - Divination EFF_E05 - Enchantment EFF_E06 - Invocation EFF_E07 - Necromancy EFF_E08 - Alteration The patch will first check for spell casting school. BG1 uses the same reference for schools and casting graphics. Special cases without any reference here will be handled manually if necessary. Next, it scans the available effects for expiration sound effects and modifies them according to the detected school. Once again, special expiration sound effects outside of EFF_E0[1-8] range will be either respected or changed manually. Here's what it looks like on paper: COPY_EXISTING_REGEXP ~.*\.spl~ ~override~ PATCH_IF (SOURCE_SIZE > 0x72) BEGIN READ_SHORT 0x22 school // spell casting school and graphics READ_LONG 0x6a fx_offset FOR (y = %fx_offset% ; y < %SOURCE_SIZE% ; y = y + 0x30) BEGIN // scan all effects READ_SHORT %y% opcode READ_ASCII (~%y%~ + 0x14) resref PATCH_IF ((%opcode% = 174) AND (~%resref%~ STRING_COMPARE_REGEXP ~EFF_E0[1-8]~ = 0)) BEGIN // only expiration sound effects PATCH_IF (%school% = 13) BEGIN // Illusion WRITE_ASCII (~%y%~ + 0x14) ~EFF_E01~ #8 END PATCH_IF (%school% = 12) BEGIN // Abjuration WRITE_ASCII (~%y%~ + 0x14) ~EFF_E02~ #8 END PATCH_IF (%school% = 14) BEGIN // Conjuration WRITE_ASCII (~%y%~ + 0x14) ~EFF_E03~ #8 END PATCH_IF (%school% = 16) BEGIN // Divination WRITE_ASCII (~%y%~ + 0x14) ~EFF_E04~ #8 END PATCH_IF (%school% = 11) BEGIN // Enchantment WRITE_ASCII (~%y%~ + 0x14) ~EFF_E05~ #8 END PATCH_IF (%school% = 15) BEGIN // Invocation WRITE_ASCII (~%y%~ + 0x14) ~EFF_E06~ #8 END PATCH_IF (%school% = 09) BEGIN // Necromancy WRITE_ASCII (~%y%~ + 0x14) ~EFF_E07~ #8 END PATCH_IF (%school% = 10) BEGIN // Alteration WRITE_ASCII (~%y%~ + 0x14) ~EFF_E08~ #8 END END END END BUT_ONLY Would there be any issues with this? Can I optimize it somehow? One possible issue would be if the given spell has more than one expiration sound effect, and each use different resources. I don't know of any spells like that in BG1, but I do remember a few from BG2. In such case, these spells will be excluded from the patch and handled separately. Also, as I understand it, if I want to scan custom spells added by other mods, it's all just a matter of adding a GLOB. Is that right? I know it's all a goblin cake for you guys, so I'd be grateful for your insights . Link to comment
CrevsDaak Posted February 10, 2015 Share Posted February 10, 2015 I would only add a GLOB after COPY_EXISTING_REGEXP. It's a bit of a nitpick but it won't get files in the override folder otherwise Link to comment
Jarno Mikkola Posted February 10, 2015 Share Posted February 10, 2015 I use this, in the mod of mine: COPY_EXISTING_REGEXP GLOB ~^.+\.spl$~ ~override~ As you probably don't want to edit a impspl.cre file with that thing... Link to comment
Tash Posted February 10, 2015 Author Share Posted February 10, 2015 I would only add a GLOB after COPY_EXISTING_REGEXP. It's a bit of a nitpick but it won't get files in the override folder otherwise Well, reading the docs I understand that GLOB will also patch files which aren't in the key; that means custom spells whose authors have consciously chosen specific sound effects for. But without GLOB, spells modified by other mods that are in the override, and that are in the key, will still be patched <- that's what I want. Do I understand this correctly? Well, in any case, patching mod-introduced spells would probably be a good idea for consistency. Also, I think this should speed up the process: COPY_EXISTING_REGEXP GLOB ~^.+\.spl$~ ~override~ PATCH_IF (SOURCE_SIZE > 0x72) BEGIN // only if there is more than a header READ_SHORT 0x22 school PATCH_IF (%school% != 00) BEGIN // only if a school is actually assigned READ_LONG 0x6a fx_offset FOR (y = %fx_offset% ; y < %SOURCE_SIZE% ; y = y + 0x30) BEGIN (...) Link to comment
Mike1072 Posted February 11, 2015 Share Posted February 11, 2015 I added an associative array and loops that might be safer on files with non-standard orders (effects before abilities). ACTION_DEFINE_ASSOCIATIVE_ARRAY expiresounds BEGIN 13 => EFF_E01 // Illusion 12 => EFF_E02 // Abjuration 14 => EFF_E03 // Conjuration 16 => EFF_E04 // Divination 11 => EFF_E05 // Enchantment 15 => EFF_E06 // Invocation 9 => EFF_E07 // Necromancy 10 => EFF_E08 // Alteration END DEFINE_PATCH_FUNCTION ~UPDATE_EXPIRY_SOUND~ INT_VAR offset = 0 STR_VAR sound = ~~ BEGIN READ_SHORT (offset) opcode READ_ASCII (offset + 0x14) resref PATCH_IF (opcode == 174 && (~%resref%~ STRING_COMPARE_REGEXP ~EFF_E0[1-8]~ == 0)) BEGIN WRITE_ASCIIE (offset + 0x14) ~%sound%~ #8 END END COPY_EXISTING_REGEXP GLOB ~^.+\.spl$~ ~override~ PATCH_IF (SOURCE_SIZE > 0x72) BEGIN READ_SHORT 0x22 school // spell casting school and graphics PATCH_IF (VARIABLE_IS_SET $expiresounds(~%school%~)) BEGIN TEXT_SPRINT sound $expiresounds(~%school%~) READ_LONG 0x64 abilities_off READ_SHORT 0x68 num_abilities READ_LONG 0x6a effects_off READ_SHORT 0x6e effects_ind READ_SHORT 0x70 num_effects FOR (i = 0; i < num_effects; i += 1) BEGIN LPF ~UPDATE_EXPIRY_SOUND~ INT_VAR offset = (effects_off + 0x30*(effects_ind + i)) STR_VAR sound END END FOR (i = 0; i < num_abilities; i += 1) BEGIN READ_SHORT (abilities_off + 0x28*i + 0x1e) num_features READ_SHORT (abilities_off + 0x28*i + 0x20) features_ind FOR (j = 0; j < num_features; j += 1) BEGIN LPF ~UPDATE_EXPIRY_SOUND~ INT_VAR offset = (effects_off + 0x30*(features_ind + j)) STR_VAR sound END END END END END BUT_ONLY Regarding use of GLOB, it would be much weirder if your component behaved differently on vanilla files versus mod-added ones, so I would always use it. Also, I would think mod-added spells would be even more likely to have unintentional mismatches between schools and expiry sounds. I can't imagine what the rationale would be for an intentional mismatch. Link to comment
Tash Posted February 11, 2015 Author Share Posted February 11, 2015 All right! Thanks, CreevsDaak, Imp and Mike1072! Mike, big thanks for the revised code. I'll have to take a look and understand new functions, but it indeed seems like these non-standard spell files could potentially be a problem with my simple code. I'll also use GLOB as you guys suggest. Link to comment
Tash Posted February 11, 2015 Author Share Posted February 11, 2015 A few questions about arrays, which seem very useful. From Mike's code: (...) PATCH_IF (VARIABLE_IS_SET $expiresounds(~%school%~)) BEGIN TEXT_SPRINT sound $expiresounds(~%school%~) (...) Do I understand this correctly that an array can be called twice in the code; the first time it's called it returns the key, the second time it returns the associated value/string? In the above example, "$expiresounds(~%school%~)" is the same in both lines, so how does TEXT_SPRINT know to use the string (eg. "EFF_E07") instead of its defined key ("9")? Is it because TEXT_SPRINT is internally smart, or because key has already been returned in PATCH_IF condition? I'm also having a hard time understanding the last bit of this launched patch function: FOR (i = 0 ; i < %num_effects% ; i += 1) BEGIN LPF %change_exp_sound_fx% INT_VAR offset = (%effects_off% + 0x30 * (%effects_ind% + i)) STR_VAR sound_fx END While "INT_VAR offset = (...)" is clear, at the end of the line, "STR_VAR sound_fx" has no equation mark and no value/string, just plain empty variable, so I've no idea what it does then. Other than that, in Mike's code some of the byte references are differant than at IESDP, so it's hard for me to look things up. For example, 0x70 is normally the number of effects and 0x6e is casting feature offset; these are now swapped around in Mike's sample. I'm a little confused, is this what you call the "non-standard" spell files? Are there really that many of them? Why would anybody want to change the position of headers...? Link to comment
Mike1072 Posted February 11, 2015 Share Posted February 11, 2015 (...) PATCH_IF (VARIABLE_IS_SET $expiresounds(~%school%~)) BEGIN TEXT_SPRINT sound $expiresounds(~%school%~) (...) Do I understand this correctly that an array can be called twice in the code; the first time it's called it returns the key, the second time it returns the associated value/string? The important part of that if statement is the VARIABLE_IS_SET command. It takes a string parameter and returns true if a value has been assigned to a variable with that name. WeiDU's array syntax is just a fancy way of dealing with variables: $expiresounds(~15~) corresponds to the variable expiresounds_15. So, you can compare the above code to this: PATCH_IF (VARIABLE_IS_SET expiresounds_15) BEGIN TEXT_SPRINT sound ~%expiresounds_15%~ I'm also having a hard time understanding the last bit of this launched patch function: FOR (i = 0 ; i < %num_effects% ; i += 1) BEGIN LPF %change_exp_sound_fx% INT_VAR offset = (%effects_off% + 0x30 * (%effects_ind% + i)) STR_VAR sound_fx END While "INT_VAR offset = (...)" is clear, at the end of the line, "STR_VAR sound_fx" has no equation mark and no value/string, just plain empty variable, so I've no idea what it does then. It's a shorthand for STR_VAR sound_fx = ~%sound_fx%~. Other than that, in Mike's code some of the byte references are differant than at IESDP, so it's hard for me to look things up. For example, 0x70 is normally the number of effects and 0x6e is casting feature offset; these are now swapped around in Mike's sample. There was one typo there; fixed now. The naming is not consistent between IESDP and tools like NearInfinity. I settled on the names I use now after some frustration with using the others. I make a clear distinction between offsets (positions in the file) and indices (positions in a list). The code I provided is actually a perfect example of looping over everything in the file. A spell consists of abilities and effects. Each ability can have multiple effects. The spell can also have global effects. So, to iterate over all of the effects, you need to iterate over the global effects and the effects associated with each ability. I refer to the effects associated with abilities as features, to distinguish them from the global effects. I'm a little confused, is this what you call the "non-standard" spell files? Are there really that many of them? Why would anybody want to change the position of headers...? The standard layout would be HEADER + ABILITIES + EFFECTS. However, the SPL format just as easily supports files with the layout HEADER + EFFECTS + ABILITIES. So if you iterate from effects_off to the end of the file, you aren't guaranteed that you will only encounter effects. Link to comment
Tash Posted February 11, 2015 Author Share Posted February 11, 2015 Thanks for your time, Mike! You've been very helpful . I'd like to use your code and give you credit, but first I just want to make sure I understand everything, hence my questions. No blind copy & pasting on my side. I'm no coder, but with IESDP and all the available documentation, it's not that hard. So I study every command, but once in a while things are different in practice, or in other people's codes, and then it's just, "did I miss something?" Link to comment
Jarno Mikkola Posted February 11, 2015 Share Posted February 11, 2015 @Tash, just like I try to do, and with Mike's help, I sometimes get to be really good at it. Link to comment
Recommended Posts
Archived
This topic is now archived and is closed to further replies.