DavidW Posted September 16, 2020 Posted September 16, 2020 Partly for the sake of trying something new, I've been trying to get my head around how the kit system works on EE, especially with respect to multiclassed kits. Here's a quick summary of what I think is correct (and one proposal): if any of the various people who know this part of the engine better than me wanted to confirm (or deny!) that I'm understanding it correctly, I'd appreciate it. 1) The engine is perfectly happy for you to define a kit with a multiclass parent class. 2) The engine, and the chargen UI, are perfectly happy for you to assign a multiclass or single-class kit to the kit-selection 2da for a multiclass. 3) If you attach a single-class kit to a multiclass character, it overrides the relevant component class. So a Fighter/Mage could become a Berserker/Mage or a Fighter/Invoker, and the character will be listed as Fighter level X... Invoker level Y on the character page. (Indeed, that's how the existing Fighter/Illusionist is defined.) Since the component classes of a multiclass character determine the CLAB table, you end up using the kit's CLAB table for the relevant class. But since the multiclass controls the LUA table, you still use the vanilla multiclass HLAs. 4) Conversely, if you attach a multiclass kit to a multiclass character, it overrides the multiclass. So a Fighter/Mage could become... well, anything you like... though the character will still be listed as Fighter level X... Mage level Y on the character page. Since the component classes are unchanged, you draw the CLAB entries from the vanilla tables for the component classes, and any kit-defined CLAB is ignored (though of course you can do kit-specific abilities using a 318/326-gated AP_ in the component-class CLAB - I assume that's how QDMULTI works, though I haven't looked at the code). Since the multiclass is changed, you can sub in a kit-specific LUA. 5) The engine sulks (i.e. crashes) if you try to use a CLAB file defined in a kitlist.2da entry higher than 255. But it's otherwise happy to use kitlist entries as high as you like: it will cheerfully read names, descriptions, IDS entries etc from high-numbered kitlist rows. That means that the ceiling of 255 kits applies only to single-class kits: you can have as many multiclass kits as you like. BUT the single-class kits need to be first in kitlist.2da, or at least, any spaces after row 255 have to be for multiclass kits. (I appreciate there's also a problem if you use WEIDU's built-in ADD_KIT command, which is hardcoded to complain if you go above 255 kits. SFO's make_kit function doesn't have that problem, and I think argent77 also has a kit-adding function that avoids the problem?) If all that's right, presumably it would be sensible to sort kitlist.ids (and make the necessary adjustments to the k_x_y.2da files) after installing a single-class kit, so as to make sure single-class kits precede multiclass kits? (Otherwise you get quite a strong install-order constraint - install single-class kits first - albeit it only matters on quite heavily modded installs.) Am I missing any problem or subtlety that would arise from doing so? Has someone already written code to do it? (If not, I might write some myself.) Quote
Luke Posted September 16, 2020 Posted September 16, 2020 49 minutes ago, DavidW said: ... and I think argent77 also has a kit-adding function that avoids the problem? Yes, it should. You can find more info here. For instance: 6) "HPCLASS.2DA": this parameter is completely ignored for multiclass kits. Quote
subtledoctor Posted September 16, 2020 Posted September 16, 2020 1) Correct 2) I don't understand what you mean. You mean the "K_F_H.2da" etc. files? In which case, the EE 2.5+ engine is happy to display multiclass kits when generating multiclass characters. I've never tried putting a single-class kit into e.g. K_FM_HE.2da, don't know if that works. The pre-EE engine will not make multiclass kits available in Chargen, AFAIK. 3) Correct. 4) Correct. QDMulti adds a single AP_ spell to the core kitless class tables, and then populates each single spell at each level with opcode 177 effects targeting multiclass kits that have kit effects at that level. The 177 effects reference .EFFs that use opcode 146 or opcode 171, representing the AP_ and GA_ entries of a multiclass kit table, respectively. 5) Correct. 2 hours ago, DavidW said: (I appreciate there's also a problem if you use WEIDU's built-in ADD_KIT command, which is hardcoded to complain if you go above 255 kits. SFO's make_kit function doesn't have that problem, and I think argent77 also has a kit-adding function that avoids the problem?) Yes, ADD_KIT_EX will happily do it, and Weidu's built-in ADD_KIT will happily do it as well if you fool it into thinking ToBEx is installed by adding /tobex_ini/tobexver.txt. (Can delete it afterward to keep the game folder clean.) 2 hours ago, DavidW said: presumably it would be sensible to sort kitlist.ids (and make the necessary adjustments to the k_x_y.2da files) after installing a single-class kit, so as to make sure single-class kits precede multiclass kits? (Otherwise you get quite a strong install-order constraint - install single-class kits first - albeit it only matters on quite heavily modded installs.) Am I missing any problem or subtlety that would arise from doing so? Has someone already written code to do it? (If not, I might write some myself.) There was discussion about doing this - the idea raised was, fill out kitlist.2da with dummy entries when a multiclass kit is installed, so that all multiclass kits are installed at entry #256+; and then jazz up ADD_KIT to automatically replace the dummy entries when installing single-class kits. The concerns that kept it in the realm of the hypothetical were 1) the effort involved, and 2) compatibility with legacy mods. Ultimately the problem of having too many kits is pretty rare, and fairly easily addressed by paying attention to install order. So there hasn't been much motivation to put more effort into the issue. Quote
DavidW Posted September 16, 2020 Author Posted September 16, 2020 5 minutes ago, subtledoctor said: I've never tried putting a single-class kit into e.g. K_FM_HE.2da, don't know if that works. It does, so far as I can see. It's a very quick lightweight way of making a singleclass kit multiclassed, but it leaves out lots of features - the LUA table for one. I think it's mostly a curiosity. 6 minutes ago, subtledoctor said: Correct. QDMulti adds a single AP_ spell to the core kitless class tables, and then populates each single spell at each level with opcode 177 effects targeting multiclass kits that have kit effects at that level. The 177 effects reference .EFFs that use opcode 146 or opcode 171, representing the AP_ and GA_ entries of a multiclass kit table, respectively. Do you know if there's any particular advantage of using 177 and EFFs rather than just 326? 7 minutes ago, subtledoctor said: There was discussion about doing this - the idea raised was, fill out kitlist.2da with dummy entries when a multiclass kit is installed, so that all multiclass kits are installed at entry #256+; and then jazz up ADD_KIT to automatically replace the dummy entries when installing single-class kits. The concerns that kept it in the realm of the hypothetical were 1) the effort involved, and 2) compatibility with legacy mods. That does sound messy. I was thinking of something simpler - just run a function that reorders kitlist.2da and relabels the numbers in the k_x_y.2da files. Then you could call it after installing a single-class kit, and (unless I'm missing something) there wouldn't be any legacy-mod problems. I don't think that would be too hard to do - I might see if I can work something up. Quote
DavidW Posted September 16, 2020 Author Posted September 16, 2020 This works, I think. DEFINE_ACTION_FUNCTION dw_reorder_kitlist BEGIN // parse kitlist to get number=>multiclas map ACTION_CLEAR_ARRAY is_multiclass COPY_EXISTING "kitlist.2da" override COUNT_2DA_COLS colcount READ_2DA_ENTRIES_NOW dw_kitlist_data colcount FOR (row=0;row<dw_kitlist_data;++row) BEGIN READ_2DA_ENTRY_FORMER dw_kitlist_data row 0 number READ_2DA_ENTRY_FORMER dw_kitlist_data row 8 class_id LPF class_is_multiclass STR_VAR class_id RET multiclass END PATCH_IF multiclass BEGIN SET $is_multiclass("%number%")=1 END END BUT_ONLY // destructively parse kitlist to get the data ACTION_CLEAR_ARRAY kitlist_contents COPY_EXISTING "kitlist.2da" override REPLACE_EVALUATE "^\([0-9]+\)[ %TAB%]+\([^ %TAB%].*\)" BEGIN SPRINT $kitlist_contents("%MATCH1%") "%MATCH2%" END "" BUT_ONLY // obviously it will change, this is just for neatness ACTION_CLEAR_ARRAY kit_number_map OUTER_SET number_new=0 OUTER_SPRINT kitlist_write "" // put the single-class data back ACTION_PHP_EACH kitlist_contents AS number=>data BEGIN ACTION_IF !VARIABLE_IS_SET $is_multiclass("%number%") BEGIN // single-class kit OUTER_SET $kit_number_map("%number%")=number_new OUTER_SPRINT kitlist_write "%kitlist_write%%number_new%%TAB%%data%%WNL%" OUTER_SET number_new=number_new+1 END END // put the multi-class data back ACTION_PHP_EACH kitlist_contents AS number=>data BEGIN ACTION_IF VARIABLE_IS_SET $is_multiclass("%number%") BEGIN //multiclass kit OUTER_SET $kit_number_map("%number%")=number_new OUTER_SPRINT kitlist_write "%kitlist_write%%number_new%%TAB%%data%%WNL%" OUTER_SET number_new=number_new+1 END END // write the data to kitlist APPEND "kitlist.2da" "%kitlist_write%" COPY_EXISTING "kitlist.2da" override PRETTY_PRINT_2DA // update the k_x_y entries COPY_EXISTING "kittable.2da" override COUNT_2DA_COLS colcount READ_2DA_ENTRIES_NOW dw_kittable_data colcount FOR (row=0;row<dw_kittable_data;++row) BEGIN FOR (col=1;col<colcount;++col) BEGIN READ_2DA_ENTRY_FORMER dw_kittable_data row col file INNER_ACTION BEGIN LAF remap_k_x_y STR_VAR file END END END END BUT_ONLY END DEFINE_PATCH_FUNCTION class_is_multiclass STR_VAR class_id=0 RET multiclass BEGIN PATCH_MATCH "%class_id%" WITH 7 8 9 10 13 14 15 16 17 18 BEGIN SET multiclass=1 END DEFAULT SET multiclass=0 END END DEFINE_ACTION_FUNCTION remap_k_x_y STR_VAR file="" BEGIN ACTION_IF FILE_EXISTS_IN_GAME "%file%.2da" BEGIN COPY_EXISTING "%file%.2da" override COUNT_2DA_ROWS 2 rowcount FOR (row=0;row<rowcount;++row) BEGIN READ_2DA_ENTRY row 1 2 number PATCH_IF IS_AN_INT number BEGIN SET new_number=$kit_number_map("%number%") SET_2DA_ENTRY row 1 2 new_number END END PRETTY_PRINT_2DA BUT_ONLY END END Quote
argent77 Posted September 16, 2020 Posted September 16, 2020 If you rearrange kit entries in kitlist.2da you might also have to update kit references in game scripts and effect opcodes, maybe even references in saved games (if you're thorough). Quote
DavidW Posted September 16, 2020 Author Posted September 16, 2020 That's one of the things I was interested in advice about. I think that the actual row number in kitlist.2da turns up in game in very few places. CRE files put the KITIDS entry from kitlist.2da into the kit field, not the row number. Likewise, opcodes 177, 318, 324, 326 check the ids number, So do BCS triggers. And of course rearranging the order of entries in kitlist.2da doesn't change which ids entry is associated with which kit. CLAB files appear directly in kitlist.2da and don't need looking up. LUA entries are from LUABBR, which looks up by rowname, not rownumber. Proficiencies are indexed directly via the proficiency entry in kitlist. And so on. The k_x_y 2das that associate kits to race/class combos do a direct lookup via kitlist rownumber. So far as I can tell that's the only place it's used. (And my code above updates them.) But: I admit freely that this is not my main area of expertise. If I'm missing something, I'd be glad to be told. Quote
subtledoctor Posted September 16, 2020 Posted September 16, 2020 32 minutes ago, DavidW said: Proficiencies are indexed directly via the proficiency entry in kitlist. And so on. This probably could stand to be tested. I’m not sure whether the Proficiency value in kitlist determines which column is used by that kit, or whether it is simply for easy lookups. But it’s probably fine. That code will probably work fine. I only don’t have a situation in mind where it would be used. But presumably you do. (Adding kits for enemies in SCS? Doing an actual PC-focused kit mod? If the latter, you might consider just keeping it separate from SCS. This code might solve the kit limit problem, but keeping the hypothetical mod separate would maximize players’ ability to easily control their install order. (Or maybe the goal is to liberalize the options available in the NPC Customization component. I’ve long back-burnered an idea to clone every kit to every valid class, as an option in my NPC customization mod. But that would mostly mean making multiclass copies of single-class kits, so it wouldn’t implicate the kit limit.)) Quote
argent77 Posted September 17, 2020 Posted September 17, 2020 9 hours ago, DavidW said: That's one of the things I was interested in advice about. I think that the actual row number in kitlist.2da turns up in game in very few places. CRE files put the KITIDS entry from kitlist.2da into the kit field, not the row number. Likewise, opcodes 177, 318, 324, 326 check the ids number, So do BCS triggers. And of course rearranging the order of entries in kitlist.2da doesn't change which ids entry is associated with which kit. CLAB files appear directly in kitlist.2da and don't need looking up. LUA entries are from LUABBR, which looks up by rowname, not rownumber. Proficiencies are indexed directly via the proficiency entry in kitlist. And so on. The k_x_y 2das that associate kits to race/class combos do a direct lookup via kitlist rownumber. So far as I can tell that's the only place it's used. (And my code above updates them.) But: I admit freely that this is not my main area of expertise. If I'm missing something, I'd be glad to be told. I think the KITIDS column in kitlist.2da must correspond to the row number, but I have to do some more tests to be certain. A quick test showed that character creation can't be completed if row number and the lowest order byte of the KITIDS value are different. Exceptions are specialist mages and the barbarian kit. If KITIDS is indeed linked to the kitlist row number then you'd have to update a lot of game resources. Quote
DavidW Posted September 17, 2020 Author Posted September 17, 2020 That certainly complicates it, to be sure (not unmanageably so, but the cost/ benefit equation would change). I didn’t find that on my own testing but I may have missed something - will check again. Quote
argent77 Posted September 17, 2020 Posted September 17, 2020 (edited) After more testing it looks like KITIDS values don't have to correspond to the kitlist row number as long as a valid kit entry with the row number equal to the lowest order byte value of KITIDS exists. Otherwise character creation fails to list available alignment entries for the selected kit. So in theory sorting the kitlist table works without unwanted side effects, but it would be a rather fragile solution. Edited September 17, 2020 by argent77 Quote
DavidW Posted September 17, 2020 Author Posted September 17, 2020 That's matching some of what I've just been finding. I got through character creation ok but was getting alignment anomalies. Will test a bit more, if only for my own curiosity. It offends my mathematical sensibilities to have arbitrary rules like this... EDIT: If I understand that correctly, won't it always be satisfied? I'm only permuting table entries, not deleting any. (But then, in that case I shouldn't have got alignment anomalies on my own run. Quote
DavidW Posted September 17, 2020 Author Posted September 17, 2020 Actually @argent77: when you say it 'fails to list available alignment entries' do you mean it doesn't list them at all, or that it gets them wrong? I'm finding that if I permute rows, I get the parent-class alignment table - kit is ignored. Quote
argent77 Posted September 17, 2020 Posted September 17, 2020 17 minutes ago, DavidW said: Actually @argent77: when you say it 'fails to list available alignment entries' do you mean it doesn't list them at all, or that it gets them wrong? Yes, no entries at all. All you can do is go back and select another kit that works. As I said, in theory your idea should work since it only permutes existing rows. But you don't know if mods installed afterwards might get confused because of the mismatching row number / kitids values. There could also be other side effects that we haven't yet noticed. Quote
DavidW Posted September 17, 2020 Author Posted September 17, 2020 I'm finding that the alignment comes back wrong even if you just permute rows. ... I'm basically concluding that this is more trouble than it's worth. The game is doing some weird lookup stuff that doesn't really track the implied logic of its tables, and the risk of breaking something is probably too high. Thanks for your help with it. Quote
Recommended Posts
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.