DavidW
-
Posts
8,048 -
Joined
-
Last visited
Content Type
Forums
Events
Downloads
Gallery
Mods
News
Store
Posts posted by DavidW
-
-
Doing a quick automated audit in BG2, I think there would indeed need to be about 20 spellstates to implement that method of doing effect blocking:
charm (6)
haste(16)
fear (23,24,106)
unconscious (39,217)
slow (40,210)
stun (45)
death (55,206)
blindness (74)
feeblemind (76)
disease (78)
deafness (80)
fatigue (93)
hold (109,175,185)
confusion (128)
petrification (134)
polymorph (135)
imprisonment (211)
maze (213)
energy drain (216)
disintegrate (238)I am actually tempted - there are significant AI advantages - but it does rather depend on how much mods are using up splstate.ids - cf my other thread.
-
It would be good to know how severe the demands on spellstates actually are. There are 256 slots available, of which the vanilla game uses 133, leaving 123 free. My mods (mostly SCS, but also some upcoming projects) use maybe 30-40 of those. Does anyone have an impression of what other mods do? If actually the extant mods only use 50-60 in total, we could borrow a bunch for FP with impunity (which would simplify immunities); if there's real pressure on splstate.2da, of course we shouldn't.
(It would be *really* nice if BD were willing to extend splstate to 2 bits in 2.7)
-
I think there are two factors here.
(1) What feature of a creature actually codes its immunity to some effect?
(2) Given an answer to (1), how do we implement that immunity?I'll use charm immunity for undead as an example. In BG2, what makes an undead creature immune to Charm is that it has ring95. In IWD, what makes it immune is that it has GENERAL=UNDEAD. In other words, in IWD it's immune simply because it's undead; in BG2 the fact that it's undead only matters through the developer's decision to give the creature ring95. Internal to the logic of BG2, undead aren't charm-proofed; only ring95-holders are.
As I said in the earlier mail thread, these are two fundamentally different choices of immunity architecture, embedded *very* deeply into the respective games. There are defensible arguments for either, but I think it's a really bad idea to try rewriting the architecture of any of the games to this degree. I suspect Cam's compelling example of the Chromatic Demon would just be the tip of the iceberg.
But even holding the architecture fixed, there are still substantive questions about how it is implemented. In the IWd architecture, there's not much choice: there is no way to attach effects to a RACE or GENERAL setting, so the only available implementation is to tell each appropriate spell and item to ignore creatures of that RACE or GENERAL, using 318/324. This is basically what IWDEE already does.
It's more complicated on the BG2 architecture. There are 3 options:
(i) Use 101 on the immunity item (ring95 in our example) to block the core effect, and then add a bunch of string/animation immunities to block associated strings/animations.
(ii) For each spell/effect that implements the blocked effect (e.g. Charm), add that spell to the immunity item via 206. (Or 318; I don't think it matters.)
(iii) Get the immunity item to set a spellstate (say, BLOCKS_CHARM), and then edit all spells/items that inflict the blocked event so as to add a 318/324 block keyed to that spellstate.Importantly, these are not mutually exclusive.
I'm going to argue that we should always *at least* use (i) when it's viable. The absolutely-most-important feature of an immunity-to-charm option is that it blocks the charm opcodes. Secondarily, it needs to block the clearly-visible signatures of charm: the displayed 'Charmed'/'Dominated' strings, the icons, and the SPNWCHRM animation. Blocking other effects (e.g. sounds) is a very distant third. It is way too dangerous to rely on (ii) and (iii) to block the charm opcodes: even in the unmodded game I'd be nervous, but mods add vast numbers of new items and spells and there is no way to enforce that those mod items/spells add 206 to the immunity items *or* 318/324 to themselves. Blocking the relevant opcodes direct is the only safe option.
BUT there is a perfectly good case for ALSO blocking the core spells directly via either (ii) or (iii): it addresses the sound-effect issue and any other stray effects, and it's harmless to double up. In principle I think (iii) (new spellstate set by the immunity item, spells detect the spellstate and self-block) is better: it's more elegant, clutters up the spell less, and is resilient against mods that clone the spell. However, the cost in spellstates is way too high: we'd need 20 or more. So (reluctantly!) I think in practice using 206 on the immunity item is better.
However, for immunity *spells* there is an spellstate available already: the spellstate set by DS. There is no reason why, e.g., Charm Person couldn't just self-block if it detects the CHAOTIC_COMMANDS spellstate. (We can't, unfortunately, add that spellstate to ring95, because both SoD and mods like SCS assume that CHAOTIC_COMMANDS marks the specific - and dispellable/breachable - spell, not just generic immunity.) I think that move is more elegant for spells than adding a bunch of 206s to the spell (though I'm not super-sold on this.)
Cam raises two special cases - Cloudkill and Song of Kaudies - but I think they fit in this general logical framework. The underlying issue is that the game contains no resource to designate a spell as Poison / Sound. So the relevant effect needs to sit on the spell itself: that's much cleaner logic than just calling out the spells to each relevant item. Doing so for Cloudkill, as Cam says, is easy: just do a 318/324 keyed to poison resistance. I agree that Sound needs a new spellstate, IMMUNE_TO_SOUND; then what marks something as a Sound effect is just that it does a 318/324 self-block if it runs into IMMUNE_TO_SOUND. I think this is a satisfactory reason to use up one spellstate slot; in general I think using up the odd one is fine, I just would want to resist a systematic policy that uses up dozens.
Summary:
- For IWDEE creature immunities, I don't think we should do anything
- For BG(2)EE creature immunities, we should maintain the existing 101 blocks but should also add a 206 block to probably any vanilla-game resource; certainly any vanilla-game resource with sound effects.
- For spells that grant immunities, we should use their DS spellstates/stats to directly block any vanilla-game resource (or, again, any vanilla-game resource with a sound effect). That means that we should after all include basic DS in the fixpack.
- We should follow Cam's suggestions for Cloudkill and Song of Kaudies. -
I am very pro following the same framework as the BG2 fixpack. I don't think there is any better standard available for bugs than developer intent, and I think the BG2 fixpack both interpreted what that means sensibly and sets a good precedent.
1 hour ago, Luke said:10 hours ago, CamDawg said:IDS/2DA fixes
- Syntax errors
So this includes fixing things like
KIT.IDS => ASSASIN // should be ASSASSIN (two S) GENDER.IDS => NIETHER // should be NEITHER
...?
But that would require adjusting scripts/dialogs as well...
These aren't syntax errors. Assigning 'ASSASIN' rather than 'ASSASSIN' as the ids name for kit 0x400a is a dumb choice and presumably happened because someone at Bioware couldn't spell 'assassin', but it's perfectly legal syntax and doesn't need changing. (And doing so would break dozens of mods.)
-
Sounds good to me.
-
A return character other than %WNL%, perhaps?
-
It shouldn't - or rather, it should only be adding entries if they're not already present - but I wouldn't swear 100% that there's not some obsolete bit of code somewhere that's doing it more crudely.
-
Unintentional, and I couldn't reproduce it, but it's not as if I tried that hard. I'll have another look next time I update.
-
Siege of Dragonspear, unmodded, I think any recent version. (But actually I reproduced it on a different dialog - AEEXTORT in BG2 - so I think you can try it on any dialog you like.
-
18 minutes ago, subtledoctor said:
I see no problem with simply removing all +1 weapons from the game...
In BG1 I think it serves a purpose to leave a few around, given that there are some monsters that can't be harmed by nonmagical weapons (and given that there are few enough magic weapons that finding one is cool).
-
I assume because someone told me IR already introduced high-quality weapons.
-
SCS does exactly what your * suggests, i.e. the default magic +1 weapons become high-quality, but there's copy made of each of them and the copies are placed in hand-picked locations.
(Well, that's what happens in BG1. In BG2 I don't bother with the hand-picked placements since there are so many +2 and better items anyway.)
-
11 minutes ago, CamDawg said:
This is fixed, kinda. In oBG bastard swords, scimitars, long swords, and two-handed swords all use the same item type. (In BG2, we just use proficiency which avoids this altogether.) Two-handed swords get filtered out by checking if the weapon is one-handed, and (as of v10) long swords and scimitars get filtered out by checking for usability of single-class druids and thieves. This still isn't perfect--if a mod adds a scimitar or long sword that's unusable by thieves and druids, it'll get picked up as a bastard sword. I feel like the other things that we could check--base damage, minimum strength, etc.--are less reliable than usabilities, but I'm open to suggestions.
Can you check the unidentified name? Most magic items will have the name of the nonmagical version in that field. (If they don't have a recognized unidentifiable name, you could use a secondary heuristic.)
-
I guess GOTO doesn't require you to put the name of a dialog file at all, so case-sensitivity doesn't apply.
-
Bizarrely, ALTER_TRANS appears to be case-sensitive.
If I do
<<<<<<<< .../stratagems-inline/test.d ALTER_TRANS bdff1709 BEGIN 2 END BEGIN 1 END BEGIN "EPILOGUE" "EXTERN bdff1709 0" END ALTER_TRANS bdff1709 BEGIN 4 END BEGIN 0 END BEGIN "EPILOGUE" "EXTERN bdff1709 7" END >>>>>>>> COMPILE ".../stratagems-inline/test.d"
then I can reproduce Jastey's bug.
If I do
<<<<<<<< .../stratagems-inline/test.d ALTER_TRANS bdff1709 BEGIN 2 END BEGIN 1 END BEGIN "EPILOGUE" "EXTERN BDFF1709 0" END ALTER_TRANS bdff1709 BEGIN 4 END BEGIN 0 END BEGIN "EPILOGUE" "EXTERN BDFF1709 7" END >>>>>>>> COMPILE ".../stratagems-inline/test.d"
it works fine.
Probably worth reporting to Wisp - it's undocumented as far as I can see.
-
You could debate if that's how it ought to work, but it's not really in SCS's scope to adjust it.
-
If you do
PHP_EACH array AS k=>v
for a multidimensional or variable-dimensional array, it cycles through all elements of the array. v is set to the value; k_0 is set to the first key, k_1 to the second key (if any), k_2 to the third (if any), and so on. k is a synonym for k_0. (This is documented in the WEIDU readme but quite obscurely - I only understood it myself recently and I've hardly ever seen it used in a mod in the wild, though I've started using it myself quite a lot.)
That's often an inefficient way to cycle through a 2da that you've read in, of course; that's indeed why I read in rows and columns. If you want, say, to go through all entries in a particular column, it's easier to do something like
PHP_EACH clastext_rows AS row=>discard BEGIN desc_here=$clastext_array("%row%" "descstr") [do something with row and desc_here] END
-
They’re not automatically immune to Sunray, but it does magical damage (iirc) and they often have Protection from Magic Energy running.
-
14 hours ago, Skye said:
Since there's actually no such thing as a 2-dimensional array in WeiDU (to the best of my knowledge), it can't be a function.
WEIDU is fine with multidimensional (or variable-dimensional) arrays. You can have a function that reads a 2da and spits out a 2D WEIDU array labelled by rows and columns. This is my (probably overcomplicated) version:
Basic use:
COPY_EXISTING - "clastext.2da" nowhere LPF 2da_read RET_ARRAY clastext_array=array clastext_rows=rows clastext_cols=columns END
Code:
////////////////////////////////////////////////////////////////////////////////////// /* document{2da_read} { Read a 2da file (or, in patch context, the current 2da file) into a 2d array. Also return an array of uppercased row headers and column headers, in the format row_label=>row_number. ('case' controls the case of the row and column headers; it's uppercase by default on genuine 2das, mixed by default otherwise) In action context, if the file doesn't exist return 0; otherwise, return 1. Also whine if it doesn't exist, unless silent=1. If the file is a 2da, and 'reflect' is set, reverse rows and columns. If it's a 2da, and 'allow_incomplete_lines' is set, don't require that all lines are complete. If it's a 2da, and "rowname_column" is set, use that column (if it's present) for the row names instead of the usual entries. If 'rowmap' and/or 'colmap' are set, they get applied to the row and column entries before the array is constructed. If 'allow_incomplete_lines' is set, we can handle incomplete lines (filling with the default). } */ ////////////////////////////////////////////////////////////////////////////////////// DEFINE_PATCH_FUNCTION "2da_read" INT_VAR silent=0//boolean reflect=0//boolean allow_incomplete_lines=0//boolean STR_VAR type=""//[2da|ids|table_header|table_no_header] rowmap=""//function colmap=""//function rowname_column="" case=""//[upper|lower|mixed] RET_ARRAY columns rows array BEGIN // initialize CLEAR_ARRAY columns CLEAR_ARRAY rows CLEAR_ARRAY array // try to infer type PATCH_IF "%type%" STR_EQ "" BEGIN PATCH_IF "%rowmap%" STR_CMP "" || "%colmap%" STR_CMP "" BEGIN SPRINT type "2da" END ELSE BEGIN READ_ASCII 0x0 sig (3) PATCH_MATCH "%sig%" WITH "2da" "ids" BEGIN SPRINT type "%sig%" END DEFAULT PATCH_MATCH "%SOURCE_EXT%" WITH "2da" "ids" BEGIN SPRINT type "%SOURCE_EXT%" END DEFAULT SPRINT type "table_header" END END END END // get column width & length COUNT_2DA_COLS colcount COUNT_2DA_ROWS colcount rowcount PATCH_MATCH "%type%" WITH "2da" BEGIN // get the default READ_2DA_ENTRY 1 0 1 default // check if it's empty COUNT_2DA_ROWS 1 empty_check is_empty=(empty_check=3) // get the col headers (& also the lookup column if appropriate) lookup_col_num=0 COUNT_2DA_COLS colcount COUNT_2DA_ROWS colcount rowcount PATCH_MATCH "%colcount%" WITH 2 BEGIN col_row=2 main_row=1 END 3 BEGIN col_row=1 main_row=0 END DEFAULT col_row=0 main_row=0 END READ_2DA_ENTRIES_NOW 2da_coldata (colcount - 1) FOR (col=1;col<colcount;++col) BEGIN READ_2DA_ENTRY_FORMER 2da_coldata col_row (col - 1) value PATCH_MATCH "%value%" WITH "%rowname_column%" BEGIN lookup_col_num=col END DEFAULT END PATCH_MATCH "%case%" WITH lower BEGIN TO_LOWER value END upper BEGIN TO_UPPER value END mixed BEGIN END DEFAULT TO_UPPER value END PATCH_IF !reflect BEGIN SET $columns("%value%")=col END ELSE BEGIN SET $rows("%value%")=col END END PATCH_IF is_empty BEGIN LPF array_map STR_VAR array=columns keymap="%colmap%" RET_ARRAY columns=array END SPRINT $rows("null") discard SPRINT $array("null") discard END ELSE BEGIN // get the rows READ_2DA_ENTRIES_NOW 2da_data colcount FOR (rownum=main_row;rownum<2da_data;++rownum) BEGIN READ_2DA_ENTRY_FORMER 2da_data rownum lookup_col_num value PATCH_MATCH "%case%" WITH lower BEGIN TO_LOWER value END upper BEGIN TO_UPPER value END mixed BEGIN END DEFAULT TO_UPPER value END PATCH_IF !reflect BEGIN SET $rows("%value%")=rownum END ELSE BEGIN SET $columns("%value%")=rownum END END // map rows and columns if needed LPF array_map STR_VAR array=columns keymap="%colmap%" RET_ARRAY columns=array END LPF array_map STR_VAR array=rows keymap="%rowmap%" RET_ARRAY rows=array END PATCH_IF allow_incomplete_lines BEGIN // get the data into a working array READ_ASCII 0x0 data (BUFFER_LENGTH) LPF data_lines STR_VAR data RET_ARRAY lines END PHP_EACH lines AS ind=>line BEGIN PATCH_IF ind>=3 BEGIN LPF array_values_from_string INT_VAR quick=1 STR_VAR string="%line%" RET_ARRAY temparray=array END LPF array_length STR_VAR array=temparray RET length END SPRINT row $temparray(0) row_int=ind - 3 +main_row PATCH_IF !reflect BEGIN SPRINT columns_or_rows columns END ELSE BEGIN SPRINT columns_or_rows rows END PHP_EACH "%columns_or_rows%" AS col=>int BEGIN PATCH_IF int<=length BEGIN SPRINT $working_array("%row_int%" "%int%") $temparray("%int%") END ELSE BEGIN SPRINT $working_array("%row_int%" "%int%") "%default%" END END END END // get it into the final array PHP_EACH rows AS row=>row_int BEGIN PHP_EACH columns AS col=>col_int BEGIN PATCH_IF !reflect BEGIN SPRINT $array("%row%" "%col%") $working_array("%row_int%" "%col_int%") END ELSE BEGIN SPRINT $array("%row%" "%col%") $working_array("%col_int%" "%row_int%") END END END END ELSE BEGIN PHP_EACH rows AS row=>row_int BEGIN PHP_EACH columns AS col=>col_int BEGIN PATCH_IF !reflect BEGIN READ_2DA_ENTRY_FORMER 2da_data row_int col_int entry END ELSE BEGIN READ_2DA_ENTRY_FORMER 2da_data col_int row_int entry END SPRINT $array("%row%" "%col%") "%entry%" END END END END END "ids" BEGIN READ_2DA_ENTRIES_NOW 2da_data 2 // set cols SET $columns("int")=0 SET $columns("sym")=1 // get main data count=0 FOR (row=0;row<2da_data;++row) BEGIN READ_2DA_ENTRY_FORMER 2da_data row 0 int READ_2DA_ENTRY_FORMER 2da_data row 1 sym PATCH_IF IS_AN_INT int BEGIN SET $array("%count%" "int")=int SPRINT $array("%count%" "sym") "%sym%" ++count END END // set rows FOR (row=0;row<count;++row) BEGIN SET $rows("%row%")=row END END "table_header" BEGIN COUNT_2DA_COLS colcount READ_2DA_ENTRIES_NOW 2da_data colcount // get columns FOR (col=0;col<colcount;++col) BEGIN READ_2DA_ENTRY_FORMER 2da_data 0 col value PATCH_MATCH "%case%" WITH lower BEGIN TO_LOWER value END upper BEGIN TO_UPPER value END DEFAULT END SET $columns("%value%")=col END // get data; set rows FOR (ind=1;ind<2da_data;++ind) BEGIN row=ind - 1 SET $rows("%row%")=row PHP_EACH columns AS col=>colnum BEGIN READ_2DA_ENTRY_FORMER 2da_data ind colnum value SPRINT $array("%row%" "%col%") "%value%" END END END "table_noheader" BEGIN COUNT_2DA_COLS colcount READ_2DA_ENTRIES_NOW 2da_data colcount // set columns FOR (col=0;col<colcount;++col) BEGIN SET $columns("%col%")=col END // get data; set rows FOR (ind=1;ind<2da_data;++ind) BEGIN row=ind - 1 SET $rows("%row%")=row PHP_EACH columns AS col=>colnum BEGIN READ_2DA_ENTRY_FORMER 2da_data ind colnum value SPRINT $array("%row%" "%col%") "%value%" END END END DEFAULT PATCH_FAIL "2da_read: unidentified read type %type%" END END
-
On 10/15/2021 at 6:28 PM, subtledoctor said:
the current updated version of the mod leaves the Silver Sword as a non-artifact weapon that hits as +3. But again, while this may sound "weaker," I challenge anyone to write a list of enemies who can't be hit by such weapons. Off the top of my head, I can only think of two. Maybe three.
Four in the unmodded game, I think: Fallen Solars, the Ravager, the Aurumach Rilmani, and the Lesser Demon Lord.
(And any wizard who casts Improved Mantle, of course!)
-
I meant Confusion for Chaos - but yes, I was also forgetting you're using mods that make Spell Turning/Deflection affect AoE. I can see that changes the game a lot. If you let Spell Deflection/Turning block AoE you certainly remove the main distinctive benefit of globes.
-
I think you're all underrating GoI at higher levels. It protects against the contents of Spell Sequencers; even at high levels, 3x Chaos or 3x Skull Trap is non-trivial.
-
1 minute ago, subtledoctor said:
unless you mean in OBG1? (I don’t even really consider that to be a game under discussion - the engine is so primitive, and none of these mods are built to work with it, apart from some stuff in CDTweaks and maybe SCS?)
No, SCS has never worked for oBG1. My embryonic, pre-public, pre-tutu version was written for BG1, but I rapidly decided it was too much of a pain. (Pausing the game can interrupt enemy spellcasting.)
-
25 minutes ago, subtledoctor said:
for some reason using dialogue instead of the UI
The UI is just a button to press to activate the dialog. I think a full-UI version is theoretically possible, but it's a lot of work and I'm not sure it's worth it.
[Discussion] Immunities, aka 101 vs. 318/324
in EE Fixpack
Posted
I was being selective given that splstate slots are an at-least-somewhat-scarce resource.(I did an automated trawl through all the 101 effects in BG2EE.) In this case, only one spell in BG2 gives immunity to op44, and its existing implementation looks fine without a spellstate.
On reflection, they're not as good as I thought. SCS needs to distinguish between protections that are immediately obvious (e.g. don't charm undead) and protections that you can only discover through trial and error (e.g. don't charm people wearing Helms of Charm Protection). So I take back the 'AI advantages' point, although I'm still attracted to the overall elegance of the scheme.
That's incredibly clever. I think I agree that it's too hacky to use in a FP, but the very fact that it can be done takes the pressure of splstate.2da and makes me somewhat more relaxed about grabbing a bunch of spellstates for FP.