Skip to content

[MOD] EEex (v0.10.2-alpha)

1141517192048

Comments

  • OlvynChuruOlvynChuru Member Posts: 3,075
    Bubb wrote: »
    @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.

    n3xx39jugef5.png

    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.
    Bubb
  • switswit Member, Translator (NDA) Posts: 495
    edited May 2019
    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)
    Post edited by swit on
    FlashburnGrammarsaladBubb
  • MyragMyrag Member Posts: 328
    @Bubb is BGEE with SOD not supported? I've installed BGEE + SOD and used modmerge and I'm getting game not supported message.
  • BubbBubb Member Posts: 998
    @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?
  • MyragMyrag Member Posts: 328
    @Bubb my bad, I forgot I switched back to 0.3.X when I had some crashes. Installed 0.5.3 and works just fine :smile:.
    Bubb
  • OlvynChuruOlvynChuru Member Posts: 3,075
    @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.
    Bubb
  • switswit Member, Translator (NDA) Posts: 495
    no problems with new NI on my system (win10, java 64bit)
    Bubb
  • BubbBubb Member Posts: 998
    @Myrag: Glad it's all working... or working to the extent it's supposed to. :)

    @OlvynChuru: Odd, my labels really shouldn't affect anything. Try starting the NearInfinity.jar from the cmd using:
    java -jar NearInfinity.jar
    

    (note that the cmd should be in the same directory). Do you see any stack traces being printed out?
  • OlvynChuruOlvynChuru Member Posts: 3,075
    edited May 2019
    @Bubb I got this problem.

    1p1qxffqmzjc.png

    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.
  • BubbBubb Member Posts: 998
    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.

    Does the attached build work?
    OlvynChuru
  • OlvynChuruOlvynChuru Member Posts: 3,075
    @Bubb Yes it does! Thank you! :)
  • switswit Member, Translator (NDA) Posts: 495
    I think EEex_Lua no longer works. Test code:
    - bcs:
    IF
      Global("Marked","GLOBAL",0)
    THEN
      RESPONSE #100
        SetGlobal("Marked","GLOBAL",1)
        EEex_Lua("testMe()")
    END
    
    - lua:
    function testMe()
    	Infinity_DisplayString("testMe")
    end
    
    String doesn't show up, infinite "INFO: 1: attempt to call a nil value" printed in the batch console.
  • MyragMyrag Member Posts: 328
    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.

    Thanks a bunch!
    switBubb
  • switswit Member, Translator (NDA) Posts: 495
    edited May 2019
    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:
    EEex_ApplyEffectToActor(id, {
    	["opcode"] = 123, --Remove inventory item
    	["target"] = 2, --Preset target
    	["timing"] = 1, --Instant/Permanent until death
    	["resource"] = characters[id].equipment[selectedSlot].item.res,
    	["source_target"] = id,
    	["source_id"] = id,
    })
    
    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
    
    Post edited by swit on
  • OlvynChuruOlvynChuru Member Posts: 3,075
    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?
  • BubbBubb Member Posts: 998
    edited May 2019
    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.
    OlvynChuru
  • RaduzielRaduziel Member Posts: 4,714
    edited May 2019
    @OlvynChuru

    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.
  • OlvynChuruOlvynChuru Member Posts: 3,075
    edited May 2019
    @Raduziel
    Raduziel wrote: »
    @OlvynChuru

    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.
  • RaduzielRaduziel Member Posts: 4,714
    @OlvynChuru

    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.
    OlvynChuru
  • switswit Member, Translator (NDA) Posts: 495
    edited May 2019
    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.
    Post edited by swit on
    BubbGrammarsalad
  • BubbBubb Member Posts: 998
    @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.
    swit
  • The user and all related content has been deleted.
    Raduziel
  • BubbBubb Member Posts: 998
    edited May 2019

    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...
    RaduzielTimbo0o0o0
  • The user and all related content has been deleted.
  • OlvynChuruOlvynChuru Member Posts: 3,075
    edited May 2019
    @Bubb

    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:
    EEex_AlterActorEffect(actorID, {{"opcode",232},{"parameter2",0},{"resource","SPWI304"}}, {{"resource","SPWI502"}}, 2)
    
    equals
    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.
    switGrammarsaladBubb
  • BubbBubb Member Posts: 998
    @OlvynChuru: Merged!

    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. :)
    OlvynChuruGrammarsaladRaduziel
  • GreenerGreener Member Posts: 430
    @Bubb

    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.
  • OlvynChuruOlvynChuru Member Posts: 3,075
    Greener wrote: »
    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
    Flashburn
  • kjeronkjeron Member Posts: 2,367
    edited May 2019
    OlvynChuru wrote: »
    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.
Sign In or Register to comment.