@OlvynChuru: How does an animation change the creature's height? It would be the best bet for a manual change, as the creature's normal "z" position is used by the bounce logic you described earlier. You can set it manually, but the engine will then take over and bounce the character around.
Normally, a creature's height above its circle has to do with its animation BAM files. The selection circle is positioned at the BAM file's center point.
The creature's height is determined by that Center Y value. That also determines the creature's height for the purposes of projectile targeting: projectiles target a height equal to half the target's current Center Y value. But I really just want to be able to change how high up a specific creature appears above its circle in the middle of the game.
Back on-topic: as long as @Bubb is mucking around with hard-coded stuff, it might be working trying to fix enemy facestabbing when Sneak Attack is enabled.
Explanation: the engine does not reqiure enemies to be behind you when they backstab you. This is down to the AI not being good enough for enemy thieves to position themselves properly. If an enemy thief is invisible, their melee attack is a backstab, no matter where they are standing. This works fine because they have to be invisible.
But the '3E Sneak Attack' EE gameplay option removes the invisibility requirement. It is not really a sneak attack/backstab, but really a kind of thief-only flanking attack. This means that for enemy thieves, every single melee attack is a sneak attack, always, period. It's silly - like they have some kind of permanent 'Smite' ability.
So maybe EEex could enforce the positioning/flanking requirement, when the '3E Sneak Attack' option is enabled. Enemies still could not position themselves very well, but it would be on the player not to turn your back on them in the chaos of combat. I surely think it would be better then the current "thieves always Smite" nonsense.
I think new function that can check CRE facing would be enough to implement it in a sane way:
- Invoke Lua on successful hit triggered via 248 (melee hit effect) and 249 (ranged hit effect) applied to weapons, filtered for thieves
- Lua code checks invoker and invoker's target facing (thanks to recently implemented EEex_GetActorTargetID)
- Apply Sneak Attack/Crippled Strike effects only if positioning is valid (determined by comparing facing) or ignoring this condition if stat 192 > 0 (Backstab Every Hit)
- Effect blocked when stat 175 > 0 (immune to backstab), and for the duration of round that the target has been hit (also if hidden in shadows, if we want to use both this and backstabbing at the same time)
- Ranged attacks can count as sneak attacks only if the target is within 30 feet (range calculated via existing EEex_GetActorLocation function)
@Myrag: It is supported. Weird that you are getting that message when I don't. Can you screenshot the error and post it here? Attaching EEex.ini and EEex.log should also help, if they have been generated.
Also, which version of SoD are you using? Since you are using modmerge you should be using the Steam version, but I just want to make sure. You didn't rename or otherwise modify the exe before installing EEex, did you?
@Bubb I downloaded your version of NearInfinity.jar that has the new labels, and when I ran NearInfinity.jar it did not start. It displayed a loading bar and then it disappeared and nothing happened. An older version of NearInfinity.jar I have (from 2018) still works fine.
Thanks, that explains it. Apparently Java 9+ changed some ByteBuffer method signatures and produces incorrect bytecode. I compiled with JDK 11, so users running Java 8 or lower will experience the crash.
By the way @Bubb, just wanted to stop by and say Thank you.
I started playing my forever love Wild Mage again. I always had issues playing it since I play LOB/SCS so with Wild Mage this means sometimes more reloads than usual and as such amount of NRD>spell casts per fight was just insane, especially with Project Image. Being able to cast 6x Dragons Breath via NRD hotkey is such a improvement in QoL.
Does anyone know if there is an existing lua function for removing particular item from inventory? (I need it in order to implement Spell learning based on Spellcraft skill instead of Intelligence) I've tried:
characters[id].equipment[selectedSlot] = {}
but whole characters[id].equipment table is just generated on the fly data, resetting the subtable doesn't really remove the item. Currently I'm using:
but it's not ideal since I can't target the exact slot number and the inventory table is not updataed until re-opened.
btw. here are some general use functions for working with lua tables and strings, in case someone needs them in their mods. Especially printTable comes is handy when working with UI.MENU.
--round to an arbitrary number of digits (0 digits if not defined)
function K4_round(x, n)
n = math.pow(10, n or 0)
x = x * n
if x >= 0 then x = math.floor(x + 0.5) else x = math.ceil(x - 0.5) end
return x / n
end
--prints "t" table content on screen
function K4_printTable(t)
local txt = ''
local print_t_cache = {}
local function sub_print_t(t, indent)
if print_t_cache[tostring(t)] then
txt = txt .. indent .. '*' .. tostring(t) .. '\n'
else
print_t_cache[tostring(t)] = true
if type(t) == 'table' then
for pos, val in pairs(t) do
if type(val) == 'table' then
txt = txt .. indent .. '[' .. pos .. '] => ' .. tostring(t) .. ' {' .. '\n'
sub_print_t(val, indent .. string.rep(' ', string.len(tostring(pos)) + 8))
txt = txt .. indent .. string.rep(' ', string.len(tostring(pos)) + 6) .. '}' .. '\n'
elseif type(val) == 'string' then
txt = txt .. indent .. '[' .. pos .. '] => "' .. val .. '"' .. '\n'
else
txt = txt .. indent .. '[' .. pos .. '] => ' .. tostring(val) ..'\n'
end
end
else
txt = txt .. indent .. tostring(t) .. '\n'
end
end
end
if type(t) == 'table' then
txt = txt .. tostring(t) .. ' {' .. '\n'
sub_print_t(t, ' ')
txt = txt .. '}' .. '\n'
else
sub_print_t(t, ' ')
end
Infinity_DisplayString(txt)
end
--randomizes table content
function K4_shuffleTable(t)
local rand = math.random
local iterations = #t
local j
for i = iterations, 2, -1 do
j = rand(i)
t[i], t[j] = t[j], t[i]
end
end
--return table with reversed keys
function K4_reversedTable(t)
local reversedTable = {}
local itemCount = #t
for k, v in ipairs(t) do
reversedTable[itemCount + 1 - k] = v
end
return reversedTable
end
--iterate over the table in order
-- basic usage, just sort by the keys:
--for k, v in K4_sortKeys(t) do
-- Infinity_DisplayString(k, v)
--end
-- this uses an custom sorting function ordering by score descending
--for k, v in K4_sortKeys(t, function(t, a, b) return t[b] < t[a] end) do
-- Infinity_DisplayString(k, v)
--end
function K4_sortKeys(t, order)
-- collect the keys
local keys = {}
for k in pairs(t) do keys[#keys + 1] = k end
-- if order function given, sort it by passing the table and keys a, b,
-- otherwise just sort the keys
if order then
table.sort(keys, function(a,b) return order(t, a, b) end)
else
table.sort(keys)
end
-- return the iterator function
local i = 0
return function()
i = i + 1
if keys[i] then
return keys[i], t[keys[i]]
end
end
end
--return bool depending on value existing in table
function K4_tableContains(t, value)
for k, v in pairs(t) do
--if v == value then
if v:match(value) then
return true
end
end
return false
end
--merge 2 tables into 1 overwriting values
function K4_tableMerge(t1, t2)
for k, v in pairs(t2) do
if type(v) == "table" then
if type(t1[k] or false) == "table" then
K4_tableMerge(t1[k] or {}, t2[k] or {})
else
t1[k] = v
end
elseif type(t1[k] or false) == "table" then
t1[k][1] = v
else
t1[k] = v
end
end
return t1
end
--split strings
function K4_strsplit(delimiter, text)
local list = {}
local pos = 1
if string.find('', delimiter, 1) then
if string.len(text) == 0 then
table.insert(list, text)
else
for i = 1, string.len(text) do
table.insert(list, string.sub(text, i, i))
end
end
else
while true do
local first, last = string.find(text, delimiter, pos)
if first then
table.insert(list, string.sub(text, pos, first - 1))
pos = last + 1
else
table.insert(list, string.sub(text, pos))
break
end
end
end
return list
end
--ensure that correct data type is set (number, bool, string)
function K4_dataType(arg)
arg = arg:gsub('^%s*(.-)%s*$', '%1') --get rid of whitespace at the start and end of arg
if tonumber(arg) then
arg = tonumber(arg)
elseif arg == 'true' then
arg = true
elseif arg == 'false' then
arg = false
else
arg = tostring(arg)
end
return arg
end
EEex_ScreenToWorldXY(screenX, screenY) => Translates screen / ui x, y position to the corresponding point in the game world. Can be used in conjunction with EEex_GetTrueMousePos() to help work with game world via the cursor.
EEex_GetActorDirection(actorID) => Gets current actor direction, corresponds to DIR.IDS.
EEex_GetActorRequiredDirection(actorID, targetX, targetY) => Gets the direction the actor should be facing to "see" the target point. Note that this is a strict comparison, so only 1 out of the 16 directions will be returned, while in reality the engine allows some variance in implementation.
EEex_IsActorFacing(actorID, targetID) => Shortcut for using EEex_GetActorRequiredDirection() to check if the given actor is facing the target. Note that the strict comparison will make it very difficult for this function to return true for combined directions. (SSW, SWW, NWW, etc.)
EEex_CyclicBound(num, lowerBound, upperBound) => Sanity function to help work with number ranges that are cyclic, (like actor direction). Example:
EEex_CyclicBound(16, 0, 15) -- returns 0
EEex_WithinCyclicRange(num, num2, range, lowerBound, higherBound) => Checks if numbers defined to be in given cyclic range are within a certain amount of places. range = 0 automatically fails, range = 1 only returns true if num == num2, range = 2 allows 1 "place" of variance, etc.
EEex_IsValidBackstabDirection(attackerID, targetID) => Convenience function that uses EEex_WithinCyclicRange() with the correct signature. Good example of how to use EEex_WithinCyclicRange() to facilitate direction matching with some tolerance for difference.
@swit: EEex_Lua(S:Function*) uses Lua chunks again. EEex_LuaActorID global filled with the executing actorID.
Also, do you absolutely need to target a specific slot? The engine doesn't seem to have anything defined to do that, and the inventory code is more complicated than you would think. Forcing an update of the characters table isn't too hard; I'll have that up soonish.
Great! Now I might be able to make gaze attacks that only work when the creatures are facing each other.
A few more requests:
Could you create a priest spells equivalent to the bonus wizard spells opcode modification that lets it work even if the character does not yet have any spell slots at a certain level?
Also, could you make it that the bonus spell slots opcodes only have this functionality if Special = 1? Having extra spell slots at an especially high level could be very overpowered in certain cases (e.g. Edwin's amulet). And currently if an effect gives a character -1 spell slots at all spell levels, they end up with 65535 spell slots.
I also have a question: if a spell uses an Invoke Lua effect on the target, what is the easiest way for that function to get the actor ID of the source of the spell?
Always meant to fix up the bonus spell modification. Don't know why I haven't yet...
Here's an example of getting the effect source's actorID:
function B3PRINT(effectData, creatureData)
local sourceID = EEex_ReadDword(effectData + 0x10C)
if sourceID == 0x0 then return end
Infinity_DisplayString("Applied by "..EEex_GetActorName(sourceID))
end
A word of caution: Effects can also be applied by traps, containers, doors, etc., which all have valid IDs. I'll have to make a function that can check for the correct type.
In DoF, to fix the bonus spell that a kitted cleric/mage wrongly receives I apply a spell through CLAB at level 1 that reduces the amount of slots by 1 per circle and it works fine.
And in I Hate Undead the Dreadful Witch loses 1 spell slot per circle and this disadvantage is also enforced by a spell applied through the CLAB at level 1.
Both cases, arcane and divine, the character loses only 1 spell slot per circle and I never saw this 65535 slots issue present itself.
I think that is what you were requiring at the first half of your post.
In DoF, to fix the bonus spell that a kitted cleric/mage wrongly receives I apply a spell through CLAB at level 1 that reduces the amount of slots by 1 per circle and it works fine.
And in I Hate Undead the Dreadful Witch loses 1 spell slot per circle and this disadvantage is also enforced through a spell applied by the CLAB at level 1.
I think that is what you were requiring at the first half of your post.
Did you have EEex installed? Currently, if you have EEex installed, an effect that reduces a character's wizard spell slots by 1 actually causes them to get 65535 slots for spell levels where they would normally have 0 slots.
Wow, fantastic, I wasn't expecting all those additional functions on top of EEex_GetActorDirection, so in the meantime I've prepared tables that were meant to be looped through in order to get positioning valid for certain actions (starting from particular column depending on the allowed direction tolerance - middle column is direct behind, or direct front). Now they are less useful, but maybe will help to explain how to use Cyclic functions:
--table containing directions valid for frontal attacks (side, front-side, front)
K4_dirFrontTable = {
[0] = {12, 11, 10, 9, 8, 7, 6, 5, 4}, --S = E, NEE, NE, NNE, N, NNW, NW, NWW, W
[1] = {13, 12, 11, 10, 9, 8, 7, 6, 5}, --SSW = SEE, E, NEE, NE, NNE, N, NNW, NW, NWW
[2] = {14, 13, 12, 11, 10, 9, 8, 7, 6}, --SW = SE, SEE, E, NEE, NE, NNE, N, NNW, NW
[3] = {15, 14, 13, 12, 11, 10, 9, 8, 7}, --SWW = SSE, SE, SEE, E, NEE, NE, NNE, N, NNW
[4] = {0, 15, 14, 13, 12, 11, 10, 9, 8}, --W = S, SSE, SE, SEE, E, NEE, NE, NNE, N
[5] = {1, 0, 15, 14, 13, 12, 11, 10, 9}, --NWW = SSW, S, SSE, SE, SEE, E, NEE, NE, NNE
[6] = {2, 1, 0, 15, 14, 13, 12, 11, 10}, --NW = SW, SSW, S, SSE, SE, SEE, E, NEE, NE
[7] = {3, 2, 1, 0, 15, 14, 13, 12, 11}, --NNW = SWW, SW, SSW, S, SSE, SE, SEE, E, NEE
[8] = {4, 3, 2, 1, 0, 15, 14, 13, 12}, --N = W, SWW, SW, SSW, S, SSE, SE, SEE, E
[9] = {5, 4, 3, 2, 1, 0, 15, 14, 13}, --NNE = NWW, W, SWW, SW, SSW, S, SSE, SE, SEE
[10] = {6, 5, 4, 3, 2, 1, 0, 15, 14}, --NE = NW, NWW, W, SWW, SW, SSW, S, SSE, SE
[11] = {7, 6, 5, 4, 3, 2, 1, 0, 15}, --NEE = NNW, NW, NWW, W, SWW, SW, SSW, S, SSE
[12] = {8, 7, 6, 5, 4, 3, 2, 1, 0}, --E = N, NNW, NW, NWW, W, SWW, SW, SSW, S
[13] = {9, 8, 7, 6, 5, 4, 3, 2, 1}, --SEE = NNE, N, NNW, NW, NWW, W, SWW, SW, SSW
[14] = {10, 9, 8, 7, 6, 5, 4, 3, 2}, --SE = NE, NNE, N, NNW, NW, NWW, W, SWW, SW
[15] = {11, 10, 9, 8, 7, 6, 5, 4, 3}, --SSE = NEE, NE, NNE, N, NNW, NW, NWW, W, SWW
}
--table containing directions valid for back attacks (side, back-side, back)
K4_dirFlankingTable = {
[0] = {4, 3, 2, 1, 0, 15, 14, 13, 12}, --S = W, SWW, SW, SSW, S, SSE, SE, SEE, E
[1] = {5, 4, 3, 2, 1, 0, 15, 14, 13}, --SSW = NWW, W, SWW, SW, SSW, S, SSE, SE, SEE
[2] = {6, 5, 4, 3, 2, 1, 0, 15, 14}, --SW = NW, NWW, W, SWW, SW, SSW, S, SSE, SE
[3] = {7, 6, 5, 4, 3, 2, 1, 0, 15}, --SWW = NNW, NW, NWW, W, SWW, SW, SSW, S, SSE
[4] = {8, 7, 6, 5, 4, 3, 2, 1, 0}, --W = N, NNW, NW, NWW, W, SWW, SW, SSW, S
[5] = {9, 8, 7, 6, 5, 4, 3, 2, 1}, --NWW = NNE, N, NNW, NW, NWW, W, SWW, SW, SSW
[6] = {10, 9, 8, 7, 6, 5, 4, 3, 2}, --NW = NE, NNE, N, NNW, NW, NWW, W, SWW, SW
[7] = {11, 10, 9, 8, 7, 6, 5, 4, 3}, --NNW = NEE, NE, NNE, N, NNW, NW, NWW, W, SWW
[8] = {12, 11, 10, 9, 8, 7, 6, 5, 4}, --N = E, NEE, NE, NNE, N, NNW, NW, NWW, W
[9] = {13, 12, 11, 10, 9, 8, 7, 6, 5}, --NNE = SEE, E, NEE, NE, NNE, N, NNW, NW, NWW
[10] = {14, 13, 12, 11, 10, 9, 8, 7, 6}, --NE = SE, SEE, E, NEE, NE, NNE, N, NNW, NW
[11] = {15, 14, 13, 12, 11, 10, 9, 8, 7}, --NEE = SSE, SE, SEE, E, NEE, NE, NNE, N, NNW
[12] = {0, 15, 14, 13, 12, 11, 10, 9, 8}, --E = S, SSE, SE, SEE, E, NEE, NE, NNE, N
[13] = {1, 0, 15, 14, 13, 12, 11, 10, 9}, --SEE = SSW, S, SSE, SE, SEE, E, NEE, NE, NNE
[14] = {2, 1, 0, 15, 14, 13, 12, 11, 10}, --SE = SW, SSW, S, SSE, SE, SEE, E, NEE, NE
[15] = {3, 2, 1, 0, 15, 14, 13, 12, 11}, --SSE = SWW, SW, SSW, S, SSE, SE, SEE, E, NEE
}
EEex_Lua(S:Function*) uses Lua chunks again. EEex_LuaActorID global filled with the executing actorID.
Not sure if I understand. How does actorID being stored in EEex_LuaActorID global variable prevents Infinity_DisplayString("testMe") from showing up in my test code? The error messages shows up even with completly empty function.
Also, do you absolutely need to target a specific slot? The engine doesn't seem to have anything defined to do that, and the inventory code is more complicated than you would think. Forcing an update of the characters table isn't too hard; I'll have that up soonish.
nope, I suspected that such function already exist. If it's a lot of work then definitely it's not worth it. Updating characters table will be useful in this case, though, thanks.
@swit: Sorry, I wasn't very clear. EEex_Lua(S:Function*) was directly running the given function name, not running the string contents, (like what EEex_LuaTrigger was doing before we changed it).
What I was trying to say is that I've updated master so EEex_Lua now runs the string contents, and instead of passing the actorID like it did before, it sets the EEex_LuaActorID global.
The vanilla 2.5 behavior is a bit weird here. Any negative modifier to spell slots that reduced your # of slots to below 0 used to end up giving you ~65535 slots. In 2.5 Beamdog changed this behavior, allowing us to apply a negative modifier.
However, it is not completely predictable. I have a mod that reduces spell slots by 12, via 12 different spells that each apply a -1 modifier. That works fine. But if I apply a -12 modifier in a single spell, it wraps to 65523. Why? I have no idea.
If you're actually wondering why, it's because of how the limit is enforced. If the current slot count is 0 the engine skips over modifying it. Decrementing the count by 1, 12 times allows the engine to detect the zero count and skip the current / subsequent opcodes. A modifier that decrements past 0 jumps over the check, and thus results in an integer underflow.
Not a very well implemented check, but I don't think it was ever its purpose to prevent underflows. The main thing it does is prevent characters that don't have any normal slots from gaining "bonus" slots.
In any case, it's always good to know why the engine does particular things sometimes...
EEex_AlterActorEffect(actorID, match_table, set_table, multi_match) => This function can change parameters of the effects of an actor in the middle of the game. It works basically like the WeiDU ALTER_EFFECT function. For up to multi_match of the actor's effects that satisfy all the conditions in match_table, it makes all of the changes in set_table. For example:
LPF ALTER_EFFECT INT_VAR multi_match=2 match_opcode=232 match_parameter2=0 STR_VAR match_resource=~SPWI304~ resource=~SPWI502~ END
This would alter a contingency effect that would trigger upon getting hit so that it will release a Cloudkill spell rather than a Fireball spell.
EEex_WriteLString(address, toWrite, maxLength) => This can be used to edit text fields that aren't terminated by a NULL (e.g. resrefs). It works very similarly to the WeiDU WRITE ASCII function.
I've had a look over the code; I'll probably go in and change some minor style things to keep EEex consistent, but the code itself looks good. Great work.
Would it be possible to fix the modify proficiencies (233) op code to specifically allow for the incremental setting of proficiencies?
Furthermore, would it be possible to fix the Black Blade of Disaster spell to allow a character to benefit from the 5 stars placed in long sword if they already have the long sword proficiency, as currently this is not the case.
Furthermore, would it be possible to fix the Black Blade of Disaster spell to allow a character to benefit from the 5 stars placed in long sword if they already have the long sword proficiency, as currently this is not the case.
This is already possible. The problem with Black Blade of Disaster is that effects with a higher timing override effects of the same opcode with a lower timing. Thus, the character's normal long sword proficiency effect (with timing = 9) overrides the Black Blade of Disaster equipped proficiency effect (with timing = 2). However, if you give the Black Blade of Disaster spell a proficiency effect granting 5 stats in long swords with timing = 10 (instant, limited, in ticks) with a duration 15 times the normal duration of the spell (15 ticks per second), it will grant the proficiency correctly.
This is already possible. The problem with Black Blade of Disaster is that effects with a higher timing override effects of the same opcode with a lower timing. Thus, the character's normal long sword proficiency effect (with timing = 9) overrides the Black Blade of Disaster equipped proficiency effect (with timing = 2). However, if you give the Black Blade of Disaster spell a proficiency effect granting 5 stats in long swords with timing = 10 (instant, limited, in ticks) with a duration 15 times the normal duration of the spell (15 ticks per second), it will grant the proficiency correctly.
A normal second-based duration works as well. Either way you retain that higher proficiency if you change weapons until the spell expires, which still isn't correct.
Assigning BLAKBLAD.itm it's own item category and using op183 can get around this, such that the effect is only active while the Black Blade is equipped, but there is still another issue with any spell/item that alters proficiency - you can Dual-Class while the item/spell is active to make the proficiency permanent. It's possibly one of the reasons beamdog disabled cancelling the dual-class screen, as you didn't have to complete the process to make it permanent, you could cancel out and retain permanent Grandmastery.
Comments
Normally, a creature's height above its circle has to do with its animation BAM files. The selection circle is positioned at the BAM file's center point.
The creature's height is determined by that Center Y value. That also determines the creature's height for the purposes of projectile targeting: projectiles target a height equal to half the target's current Center Y value. But I really just want to be able to change how high up a specific creature appears above its circle in the middle of the game.
I think new function that can check CRE facing would be enough to implement it in a sane way:
- Invoke Lua on successful hit triggered via 248 (melee hit effect) and 249 (ranged hit effect) applied to weapons, filtered for thieves
- Lua code checks invoker and invoker's target facing (thanks to recently implemented EEex_GetActorTargetID)
- Apply Sneak Attack/Crippled Strike effects only if positioning is valid (determined by comparing facing) or ignoring this condition if stat 192 > 0 (Backstab Every Hit)
- Effect blocked when stat 175 > 0 (immune to backstab), and for the duration of round that the target has been hit (also if hidden in shadows, if we want to use both this and backstabbing at the same time)
- Ranged attacks can count as sneak attacks only if the target is within 30 feet (range calculated via existing EEex_GetActorLocation function)
Also, which version of SoD are you using? Since you are using modmerge you should be using the Steam version, but I just want to make sure. You didn't rename or otherwise modify the exe before installing EEex, did you?
@OlvynChuru: Odd, my labels really shouldn't affect anything. Try starting the NearInfinity.jar from the cmd using:
(note that the cmd should be in the same directory). Do you see any stack traces being printed out?
I'm not sure if it's your fault, though.
I have the latest version of Java installed, and my computer is 64 bit with Windows 10.
Does the attached build work?
- bcs: - lua: String doesn't show up, infinite "INFO: 1: attempt to call a nil value" printed in the batch console.
I started playing my forever love Wild Mage again. I always had issues playing it since I play LOB/SCS so with Wild Mage this means sometimes more reloads than usual and as such amount of NRD>spell casts per fight was just insane, especially with Project Image. Being able to cast 6x Dragons Breath via NRD hotkey is such a improvement in QoL.
Thanks a bunch!
btw. here are some general use functions for working with lua tables and strings, in case someone needs them in their mods. Especially printTable comes is handy when working with UI.MENU.
@swit: EEex_Lua(S:Function*) uses Lua chunks again. EEex_LuaActorID global filled with the executing actorID.
Also, do you absolutely need to target a specific slot? The engine doesn't seem to have anything defined to do that, and the inventory code is more complicated than you would think. Forcing an update of the characters table isn't too hard; I'll have that up soonish.
A few more requests:
Could you create a priest spells equivalent to the bonus wizard spells opcode modification that lets it work even if the character does not yet have any spell slots at a certain level?
Also, could you make it that the bonus spell slots opcodes only have this functionality if Special = 1? Having extra spell slots at an especially high level could be very overpowered in certain cases (e.g. Edwin's amulet). And currently if an effect gives a character -1 spell slots at all spell levels, they end up with 65535 spell slots.
I also have a question: if a spell uses an Invoke Lua effect on the target, what is the easiest way for that function to get the actor ID of the source of the spell?
Here's an example of getting the effect source's actorID:
A word of caution: Effects can also be applied by traps, containers, doors, etc., which all have valid IDs. I'll have to make a function that can check for the correct type.
In DoF, to fix the bonus spell that a kitted cleric/mage wrongly receives I apply a spell through CLAB at level 1 that reduces the amount of slots by 1 per circle and it works fine.
And in I Hate Undead the Dreadful Witch loses 1 spell slot per circle and this disadvantage is also enforced by a spell applied through the CLAB at level 1.
Both cases, arcane and divine, the character loses only 1 spell slot per circle and I never saw this 65535 slots issue present itself.
I think that is what you were requiring at the first half of your post.
Did you have EEex installed? Currently, if you have EEex installed, an effect that reduces a character's wizard spell slots by 1 actually causes them to get 65535 slots for spell levels where they would normally have 0 slots.
No, never tested in EEex. I was describing vanilla-EE behavior. Sorry.
Anyway, looks like the scenario is to fix in EEex the existing Opcode instead of creating a new one - unless I'm failing to see something.
nope, I suspected that such function already exist. If it's a lot of work then definitely it's not worth it. Updating characters table will be useful in this case, though, thanks.
What I was trying to say is that I've updated master so EEex_Lua now runs the string contents, and instead of passing the actorID like it did before, it sets the EEex_LuaActorID global.
If you're actually wondering why, it's because of how the limit is enforced. If the current slot count is 0 the engine skips over modifying it. Decrementing the count by 1, 12 times allows the engine to detect the zero count and skip the current / subsequent opcodes. A modifier that decrements past 0 jumps over the check, and thus results in an integer underflow.
Not a very well implemented check, but I don't think it was ever its purpose to prevent underflows. The main thing it does is prevent characters that don't have any normal slots from gaining "bonus" slots.
In any case, it's always good to know why the engine does particular things sometimes...
You have a new pull request!
I added two new functions.
EEex_AlterActorEffect(actorID, match_table, set_table, multi_match) => This function can change parameters of the effects of an actor in the middle of the game. It works basically like the WeiDU ALTER_EFFECT function. For up to multi_match of the actor's effects that satisfy all the conditions in match_table, it makes all of the changes in set_table. For example:
equals
This would alter a contingency effect that would trigger upon getting hit so that it will release a Cloudkill spell rather than a Fireball spell.
EEex_WriteLString(address, toWrite, maxLength) => This can be used to edit text fields that aren't terminated by a NULL (e.g. resrefs). It works very similarly to the WeiDU WRITE ASCII function.
I've had a look over the code; I'll probably go in and change some minor style things to keep EEex consistent, but the code itself looks good. Great work.
Incredible work mate..
Would it be possible to fix the modify proficiencies (233) op code to specifically allow for the incremental setting of proficiencies?
Furthermore, would it be possible to fix the Black Blade of Disaster spell to allow a character to benefit from the 5 stars placed in long sword if they already have the long sword proficiency, as currently this is not the case.
This is already possible. The problem with Black Blade of Disaster is that effects with a higher timing override effects of the same opcode with a lower timing. Thus, the character's normal long sword proficiency effect (with timing = 9) overrides the Black Blade of Disaster equipped proficiency effect (with timing = 2). However, if you give the Black Blade of Disaster spell a proficiency effect granting 5 stats in long swords with timing = 10 (instant, limited, in ticks) with a duration 15 times the normal duration of the spell (15 ticks per second), it will grant the proficiency correctly.
The WeiDU code would look something like this.
COPY_EXISTING ~spwi915.spl~ ~override~
LPF CLONE_EFFECT INT_VAR multi_match=1 match_opcode=111 opcode=233 timing=10 duration=1620 parameter1=5 parameter=90 END
IF_EXISTS
Assigning BLAKBLAD.itm it's own item category and using op183 can get around this, such that the effect is only active while the Black Blade is equipped, but there is still another issue with any spell/item that alters proficiency - you can Dual-Class while the item/spell is active to make the proficiency permanent. It's possibly one of the reasons beamdog disabled cancelling the dual-class screen, as you didn't have to complete the process to make it permanent, you could cancel out and retain permanent Grandmastery.