Skip to content

[MOD] EEex (v0.10.2-alpha)

1246748

Comments

  • FlashburnFlashburn Member Posts: 1,847
    edited November 2018
    Could you make it so that spell protections display how many spell levels remain when they absorb/reflect a spell? For example, displayed in the combat log (or with overhead text):
    • (get hit with a 7th level spell)
    • "Spell Turning: Reflects 7 spell levels: 4 spell levels remaining"
    or
    • (get hit with 5th level spell)
    • "Shield of the Archons: Absorbs spell 5 levels: 3 spell levels remaining"
    Not that I ever run into this situation much, as SCS mages prefer to dispel spell protections or summon allies if they can't cast against you. But I always liked how, in Neverwinter Nights, Spell Mantles will tell you this information.
    BubbGusindalolien
  • raizoraizo Member Posts: 31
    Flashburn said:

    Could you make it so that spell protections display how many spell levels remain when they absorb/reflect a spell? For example, displayed in the combat log (or with overhead text):

    • (get hit with a 7th level spell)
    • "Spell Turning: Reflects 7 spell levels: 4 spell levels remaining"
    or
    • (get hit with 5th level spell)
    • "Shield of the Archons: Absorbs spell 5 levels: 3 spell levels remaining"
    Not that I ever run into this situation much, as SCS mages prefer to dispel spell protections or summon allies if they can't cast against you. But I always liked how, in Neverwinter Nights, Spell Mantles will tell you this information.
    Yep, that is basically what I suggested earlier in the spell menu thread. That visible counters for active buffs would be very helpful. Especially if you have a ton of them.

    But cloging combat log with more information would not be such a good idea.
    Maybe something like a small popup window near the left or right sidebar for each party member, who has some active buffs - time dependent (Blur) and constant (Stoneskin/Spell Trap/etc). Even better if it could be switched on/off with a keyboard shortcut.
    Just thinking out loud.
    FlashburnBubbGusindalolien
  • GreenerGreener Member Posts: 430
    In theory you could give a class or specific sub kit the pickpocket or stealth skill? If so how would said skill advance? Would it be linked to something like the thiefscl.2da, skillbrd.2da or skillrng.2da files?
    Bubb
  • BubbBubb Member Posts: 998
    @Greener: Just tested this and it works. You have to enable the thief ability via shiefscl.2DA, and then determine the per-level point allocation in thiefskil.2DA.

    I believe the fixed-point system using SKILLBRD.2DA, SKILLRNG.2DA etc. is hardcoded, so that isn't a possibility.

    You might be able to emulate a fixed-point system by using a clever EVAL command from the GUI on level up, so I wouldn't count it completely out if you wanted the class / kit to use that method, instead of the per level-up points.
    GusindaGrammarsaladlolien
  • The user and all related content has been deleted.
    Grammarsalad
  • GrammarsaladGrammarsalad Member Posts: 2,582
    Bubb said:

    As always, great suggestions you two :)

    I'll look into implementing them after I release the first version of this, (which should be very soon)! My code doesn't help anyone if I never release it, eh?
    ----------------------------------------

    Also, as my last addition before release, I properly implemented an Actionbar hook. Modders using my system can easily customize any creature's actionbar by creating a very simple listener; for example:

    function B3ActionbarListener(config)
    local actorID = getActorIDSelected()
    if
    config == 0x5
    and getActorClass(actorID) == 0x6
    and getActorKit(actorID) == 0x4005
    then
    setActionbarButton(0x5, ACTIONBAR_TYPE.BARD_SONG)
    end
    end
    addActionbarListener(B3ActionbarListener)
    Throwing that in a M_*.LUA file is all that is required to swap out the Inquisitor's nonfunctional Turn Undead button for Bard Song instead.
    :open_mouth:

    And I think I can actually understand this! OMG, dude!
    Bubb
  • FlashburnFlashburn Member Posts: 1,847
    Oh yeah, I've got one more suggestion:
    Whenever anyone is affected by Dispel Effects, I'd like to see what was dispelled because of it, if anything. I would also like to see this extended to the specific anti-magic spells such as Breach, Ruby Ray, Pierce Magic, etc. Sometimes it's hard to tell if anything happened when dispelling enemy buffs.
    Bubb
  • BubbBubb Member Posts: 998
    @Flashburn: How would you want this implemented?

    You could tell what was dispelled using the spell inspection mode I showed earlier, right? You would just look for what disappeared from the effects list.

    If that doesn't work, I guess I could dump it in the chatlog - but that seems like a less-than-optimal design.
  • FlashburnFlashburn Member Posts: 1,847
    I'll have to play using the spell inspection mode to see if I still want this, because it is kind of redundant. I bet the anti-magic spells besides Breach and Spellstrike will still benefit from this, since they only dispel one spell protection at a time when mages can often have multiple protections up.
  • GrammarsaladGrammarsalad Member Posts: 2,582
    Hi Bubb. I was just thinking of something. The bard has what looks like a thieving button, but it doesn't function as anything but a button for pick pocket. Will we need to change something in order to give them the ability to pick locks or disable traps other than get them the skill percentage points?
    Bubb
  • kjeronkjeron Member Posts: 2,367
    Bubb said:

    CAST_SPELL might be checking for certain multiclasses to add the "Jump to Mage/Cleric Spells" buttons. Though, I'm lost on what BARD_SONG cares about.

    CAST_SPELL: I would guess the other 3 are to assess CasterLevelMage, CasterLevelPriest, and CasterLevelInnate - to determine which ability header to display (a spell can have a different icon for each ability header (min level), and it will display the one appropriate for their current caster level.)

    BARD_SONG: Might be to detect Bard/Shaman, as the Shaman Dance is just another form of Bardsong.
    BubbRaduzielCrevsDaak
  • GrammarsaladGrammarsalad Member Posts: 2,582
    edited December 2018
    Yay!

    Okay, I tried the sample to give the inquisitor the bardsong. Installed on BG2EE (gog v2.5.16.6)

    After adding the M_*file (M_PAL.lua), which I just copied the code from the sample:

    function B3ActionbarListener(config)
    	local actorID = getActorIDSelected()
        if 
           config == 0x5
           and getActorClass(actorID) == 0x6
           and getActorKit(actorID) == 0x4005
        then
            setActionbarButton(0x5, ACTIONBAR_TYPE.BARD_SONG)
        end
    end
    addActionbarListener(B3ActionbarListener)


    I get this error on startup:


    Note, that I do not get this error unless I add my own M_ file for a custom action bar

    I look at line 27 in EEex_Bar.lua, and I see this (line 27 is the middle line):
    function addActionbarListener(func)
    	table.insert(_actionbarListeners, func)
    end
    When I create an inquisitor, I see no change in the action bar
    Bubb
  • BubbBubb Member Posts: 998
    @Grammarsalad: D'oh! I only tested the system by putting the listener in UI.MENU. The order of execution of certain important blocks was messed up if you put the listener in a M_*.lua file. I've attached an update that fixes the ordering. Please uninstall the old version, (with the game closed), delete the EEex folder, and install the new version like normal.

    Also, I wrote my hooks to be compliant with UI.MENU's convention that any state gets wiped when a F5 reload occurs. A listener that is defined in UI.MENU will get reloaded automatically as part of the process, but the v2.5 update made it so M_*.lua files don't get reloaded like UI.MENU does.

    If you put the listener in a M_*.lua file, you should define a reset listener which reloads the file manually. This doesn't do anything for a normal user - it just makes sure the listener is reinstalled when you, or another modder, reloads the UI. Put this at the top of your M_PAL.lua file:

    EEex_AddResetListener(function() Infinity_DoFile("M_PAL") end)
    Everything should work now :)
    Grammarsalad
  • GrammarsaladGrammarsalad Member Posts: 2,582
    Bubb said:

    @Grammarsalad: D'oh! I only tested the system by putting the listener in UI.MENU. The order of execution of certain important blocks was messed up if you put the listener in a M_*.lua file. I've attached an update that fixes the ordering. Please uninstall the old version, (with the game closed), delete the EEex folder, and install the new version like normal.

    Also, I wrote my hooks to be compliant with UI.MENU's convention that any state gets wiped when a F5 reload occurs. A listener that is defined in UI.MENU will get reloaded automatically as part of the process, but the v2.5 update made it so M_*.lua files don't get reloaded like UI.MENU does.

    If you put the listener in a M_*.lua file, you should define a reset listener which reloads the file manually. This doesn't do anything for a normal user - it just makes sure the listener is reinstalled when you, or another modder, reloads the UI. Put this at the top of your M_PAL.lua file:

    EEex_AddResetListener(function() Infinity_DoFile("M_PAL") end)
    Everything should work now :)

    Cool cool! Looking forward to giving it a try! (After the new year)

    Btw, happy New year!
    Bubb
  • BubbBubb Member Posts: 998
    Fun Fact: It's even possible to extend certain file formats with the capabilities of this system. Granted, it requires directly hooking into the engine like everything else... but it's possible, and that's what's important. Like a so:

    function getExtra(actorID)
    return rdword(_getActorDataAddress(actorID) + 0x3B18)
    end

    function setExtra(actorID, value)
    wdword(_getActorDataAddress(actorID) + 0x3B18, value)
    end

    function EEex_HookSaveCreature(fromStruct, toFile)
    wdword(toFile + 0x2D4, rdword(fromStruct + 0x3B18))
    end

    function EEex_HookLoadCreature(fromFile, toStruct)
    wdword(toStruct + 0x3B18, rdword(fromFile + 0x2D4))
    end

    function _B3InstallCreatureHook()

    local creatureAllocations = {
    0x51C21E,
    0x51E0A4,
    0x521231,
    0x53EE01,
    0x53F6AD,
    0x53FF0D,
    0x54F945,
    0x54FD53,
    0x55C648,
    0x55C6E7,
    0x55C74C,
    0x55C7B9,
    0x560796,
    0x568077,
    0x57C8E4,
    0x57D081,
    0x584C61,
    0x586FDF,
    0x588CB2,
    0x590E7C,
    0x5938AF,
    0x5B31B0,
    0x5B34FF,
    0x5B650A,
    0x5B9DDC,
    0x5BA13C,
    0x5C0906,
    0x5C0CB7,
    0x620D8D,
    0x62D05D,
    0x63E5D4,
    0x63E629,
    0x63E927,
    0x63E96C,
    0x641DB0,
    0x656029,
    0x667462,
    0x66BDC9,
    0x69DE3A,
    0x6E19F7,
    0x6ECB87,
    0x714887
    }

    local hookName = "EEex_HookSaveCreature"
    local hookNameAddress = malloc(#hookName + 1)
    wstring(hookNameAddress, hookName)
    local hookAddress = _B3WriteAssemblyAuto({
    "E8 :85B6A0 "..
    "68", {hookNameAddress, 4},
    "FF 35 0C 01 94 00 "..
    "E8 :4B5C10 "..
    "83 C4 08 53 DB 04 24 83 EC 04 DD 1C 24 FF 35 0C 01 94 00 "..
    "E8 :4B5960 "..
    "83 C4 0C FF 34 24 DB 04 24 83 EC 04 DD 1C 24 FF 35 0C 01 94 00 "..
    "E8 :4B5960 "..
    "83 C4 0C 6A 00 6A 00 6A 00 6A 00 6A 02 FF 35 0C 01 94 00 "..
    "E8 :4B63F0 "..
    "83 C4 18 "..
    "E9 :6F0201"
    })

    local hookNameLoad = "EEex_HookLoadCreature"
    local hookNameLoadAddress = malloc(#hookNameLoad + 1)
    wstring(hookNameLoadAddress, hookNameLoad)
    local hookAddressLoad = _B3WriteAssemblyAuto({
    "E8 :52EEE0 "..
    "68", {hookNameLoadAddress, 4},
    "FF 35 0C 01 94 00 "..
    "E8 :4B5C10 "..
    "83 C4 08 FF 75 08 DB 04 24 83 EC 04 DD 1C 24 FF 35 0C 01 94 00 "..
    "E8 :4B5960 "..
    "83 C4 0C 53 DB 04 24 83 EC 04 DD 1C 24 FF 35 0C 01 94 00 "..
    "E8 :4B5960 "..
    "83 C4 0C 6A 00 6A 00 6A 00 6A 00 6A 02 FF 35 0C 01 94 00 "..
    "E8 :4B63F0 "..
    "83 C4 18 "..
    "E9 :6D40CB"
    })

    _B3DisableCodeProtection()

    -- Increase creature struct size by 0x4 bytes (in memory)
    for _, address in ipairs(creatureAllocations) do
    _B3WriteAssembly(address + 1, {{0x3B1C, 4}})
    end

    -- Install EEex_HookSaveCreature
    _B3WriteAssembly(0x6F01FC, {"E9", {hookAddress, 4, 4}})

    -- Push the start of dynamic structures back 0x4 bytes (in save)
    _B3WriteAssembly(0x6F01E5, {{0x2D8, 4}})
    _B3WriteAssembly(0x6F031B, {{0x2D8, 4}})

    -- Install EEex_HookLoadCreature
    _B3WriteAssembly(0x6D40C6, {"E9", {hookAddressLoad, 4, 4}})

    _B3EnableCodeProtection()
    end
    _B3InstallCreatureHook()

    Throwing the above into a M_*.lua file is all that is required to extend the .CRE header by 4 bytes. getExtra() / setExtra are used to modify this new field. It is even preserved by saving and reset by loading.

    The above example isn't of much use, but it does show that it is possible to store new data without much hassle. I'm actually pretty sure I could make a system in which a modder requests a certain amount of space inside the .CRE file, and they could do whatever they wish with it; it would also theoretically allow for unlimited data to be stored inside creatures. :)

    Side note: The vanilla game safely ignores new data if it is used to load a modified SAV.
    Grammarsalad
  • GrammarsaladGrammarsalad Member Posts: 2,582
    Okay, I found a bit of time before the festivities started. Works like a
    dream so far:

    Here's an example of an actionbar modified inquisitor:



    I'm super giddy right now. This...opens up so many possibilities!

    @subtledoctor @Aquadrizzt
    lolien
  • GrammarsaladGrammarsalad Member Posts: 2,582
    edited January 2019
    Okay, another question. One issue that I've heard complained about is the button placement of cleric/thieves. I think that the thieving button is located in the special abilities section, while the turn undead button is in the normal place for that button. This is an issue of convenience because one has to press two buttons to access the thieving skills, though that button is more often needed than turn undead. As such, a lot of players would prefer to switch it with the turn undead button. How would I do that? I tried adding the following to put the find traps button behind the special abilities button, but it didn't work (note: I'm still tinkering with the inquisitor):

    setActionbarButton(0xc, ACTIONBAR_TYPE.FIND_TRAPS)

    One thing that I would love to be able to do is to place a number of buttons in the special abilities section for classes that don't normally need them, so that they could use them under certain circumstances (e.g. if under the effects of a spell that gave them skill in stealth, for example) Is this possible?

    Edit: another question:

    In the button altering code, what does this represent:

    config == 0x5

    I (think I) understand getActorClass and getActorKit as calling for kit and class ids
  • kjeronkjeron Member Posts: 2,367

    One thing that I would love to be able to do is to place a number of buttons in the special abilities section for classes that don't normally need them, so that they could use them under certain circumstances (e.g. if under the effects of a spell that gave them skill in stealth, for example) Is this possible?

    I would hope to put all of them on the main bar, using left/right clicks to get the alternate function:

    Talk / Defend
    Weapon
    Weapon
    ( Weapon : Quick Spell ) (Depending on class)
    ( Weapon : Quick Spell ) (Depending on class)
    Thieving / Use Item
    Sing / Turn
    Detect / Hide
    Spells / Specials Abilities
    Quick Item
    Quick Item
    Quick Item
    Grammarsalad
  • GrammarsaladGrammarsalad Member Posts: 2,582
    kjeron said:

    One thing that I would love to be able to do is to place a number of buttons in the special abilities section for classes that don't normally need them, so that they could use them under certain circumstances (e.g. if under the effects of a spell that gave them skill in stealth, for example) Is this possible?

    I would hope to put all of them on the main bar, using left/right clicks to get the alternate function:

    Talk / Defend
    Weapon
    Weapon
    ( Weapon : Quick Spell ) (Depending on class)
    ( Weapon : Quick Spell ) (Depending on class)
    Thieving / Use Item
    Sing / Turn
    Detect / Hide
    Spells / Specials Abilities
    Quick Item
    Quick Item
    Quick Item
    Well, yes. This would be ideal. Is this possible?
  • BubbBubb Member Posts: 998
    edited January 2019
    @Grammarsalad: You asked, so you're getting a wall of text. ;)

    The engine only "knows" about the 12 buttons it is displaying. When you press a button that opens up a submenu, the engine is actually setting a new configuration. Configurations are all the possible orders / placements the buttons can be in. Each configuration has a corresponding internal index, though these indexes are highly arbitrary. It appears the devs simply used a first-come-first-serve system when mapping them. The main configurations for player classes is usually the class ids - 1.

    You can drop this in another M_*.lua file to get actionbar debug statements:
    EEex_AddResetListener(function()
    Infinity_DoFile("M_BDebug")
    end)

    function getActionbarButton(buttonIndex)
    if buttonIndex < 0 or buttonIndex > 11 then
    error("buttonIndex out of bounds", 2)
    end
    local ecx = rdword(rdword(0x93FDBC) + 0xD14)
    local actionbarAddress = rdword(rdword(ecx + rbyte(ecx + 0x3DA0, 0) * 4 + 0x3DA4) + 0x204) + 0x2654
    return rdword(actionbarAddress + 0x1440 + buttonIndex * 0x4)
    end

    function B3ActionbarDebug(config)
    Infinity_DisplayString(" ")
    Infinity_DisplayString("DEBUG: [Engine] Actionbar Config => "..config)
    for i = 0, 11, 1 do
    Infinity_DisplayString("DEBUG: [Engine] Actionbar Button["..i.."] => "..getActionbarButton(i))
    end
    end
    addActionbarListener(B3ActionbarDebug)

    That will print out the config id of the actionbar whenever the engine changes it, along with all the new button identifiers.

    The engine hardcodes the thieving button into the Special Abilities menu for Cleric / Thieves. You can switch it out like this:
    if getActorClass(actorID) == 15 then
    -- I'm a Cleric / Thief
    if config == 14 then
    -- Main Config for Cleric / Thieves
    setActionbarButton(0x4, ACTIONBAR_TYPE.THIEVING)
    elseif config == 23 then
    -- Special Abilities (for everyone)
    setActionbarButton(0x0, ACTIONBAR_TYPE.TURN_UNDEAD)
    end
    end

    Curiously, the button appears changed, but now doesn't function. The engine actually goes through a different action jump-table when it is in the Special Abilities menu - one that lacks all of the normal actions, (except thieving). It's possible to redirect the Special Ability buttons to the normal subroutine if it fails - but one thing remains: certain buttons just weren't designed to be in the Special Abilities menu.

    I've got a working hook in place, however, buttons that don't belong in the Special Abilities menu don't highlight the Special Abilities button like it should. I can't figure out how to do this myself. :/

    So it functions, but it's not ideal.

    Regarding a condensed actionbar, it's definitely possible to invoke the actions like kjeron describes. The problem is that the UI might not reflect active abilities if they are merged into a single button. For example, a merged Sing / Turn button would not highlight when you use the "alternate" ability.
    GrammarsaladCrevsDaaklolien
  • kjeronkjeron Member Posts: 2,367
    edited January 2019
    I dont' know, I've been unable to test.

    @bubb
    While the actionbar function (the exact sample you present here and in EEex_bar.lua) is present in either UI.menu or an M_.lua file, my game crashes whenever I select a Paladin class character.

    I'm testing on an otherwise unmodded version, Windows BG2EE v2.5.16.6 from beamdog with v0.1.1, so I'm at a loss for what's wrong.

    edit - just saw above post, would be nice if these forums informed us of new posts while we are posting.

    Bubb
  • BubbBubb Member Posts: 998
    edited January 2019
    @kjeron: Are there any error messages? The only functions that should even have the remotest possibility of crashing are these:
    • getActorIDSelected() - Never observed a crash.
    • getActorClass() - Will crash if given an invalid actorID.
    • getActorKit() - Will crash if given an invalid actorID.
    • setActionbarButton() - Might crash? Never observed this though.

    Since it only happens with paladins, it must be either getActorKit() or setActionbarButton() that's crashing. I just hope the beamdog executable isn't different from the other platforms somehow...

    Hopefully the game saved a crash dump. Could you zip it up and upload it? It should point towards the crashing subroutine.
  • kjeronkjeron Member Posts: 2,367
    No error message, but plenty of crash dumps.
    Tried calling each one manually, it's "setActionbarButton()" that is crashing.
    Bubb
  • BubbBubb Member Posts: 998
    edited January 2019
    @kjeron: Spent way too much time trying to implement SEH handling, just for Windows to complain that I was in dynamically allocated memory. Think we got to do this the old fashioned way...

    The dump didn't say much. It reports the program crashed outside of one of my functions, but that's impossible. The only thing that could cause that is stack corruption, and that either happens or it doesn't; it's not something that would differ between users.

    Right above your actionbar listener, could you paste the following code:
    function EEex_MessageBox(message)
    local caption = "EEex"
    local messageAddress = malloc(#message + 1 + #caption + 1)
    local captionAddress = messageAddress + #message + 1
    wstring(messageAddress, message)
    wstring(captionAddress, caption)
    dllcall("User32", "MessageBoxA", {B3Flags({0x40}), captionAddress, messageAddress, 0x0}, nil, 0x0)
    free(messageAddress)
    end

    function setActionbarButton(buttonIndex, buttonType)
    if buttonIndex < 0 or buttonIndex > 11 then
    error("buttonIndex out of bounds", 2)
    end
    EEex_MessageBox("buttonIndex: "..toHex(buttonIndex))
    EEex_MessageBox("buttonType: "..toHex(buttonType))
    local step1 = rdword(0x93FDBC)
    EEex_MessageBox("step1: "..toHex(step1))
    local ecx = rdword(step1 + 0xD14)
    EEex_MessageBox("step2: "..toHex(ecx))
    local step22 = rbyte(ecx + 0x3DA0, 0)
    EEex_MessageBox("step3: "..toHex(step22))
    local step33 = rdword(ecx + step22 * 4 + 0x3DA4)
    EEex_MessageBox("step4: "..toHex(step33))
    local step44 = rdword(step33 + 0x204)
    EEex_MessageBox("step5: "..toHex(step44))
    local actionbarAddress = step44 + 0x2654
    EEex_MessageBox("step6: "..toHex(actionbarAddress))
    EEex_MessageBox("step7: "..toHex(actionbarAddress + 0x1440 + buttonIndex * 0x4))
    wdword(actionbarAddress + 0x1440 + buttonIndex * 0x4, buttonType)
    end

    When you select your paladin, could you write down each step's message? The game should crash before step7 is completed.

    I went over how the engine does it vs how I do it, and the only difference is a single NULL check. I can't see how that would be it, as it's checking if the area is null, and that definitely is defined if you can select your character.

    Anyways, the above debug stuff should tell me where it's going wrong.
  • kjeronkjeron Member Posts: 2,367
    edited January 2019
    @bubb
    Sorry, I'm not seeing any messages, either in-game or by running game in command prompt. It just immediately pops up the "Error. There was an Error. A crash dump was saved to:" window.
    It doesn't always crash with that extra bit though, but it's also not changing the button when it doesn't crash either, and no messages when it doesn't crash.
Sign In or Register to comment.