Yeah, the amount of familiars was never the problem, as a quick MakeGlobal(); AddFamiliar() script call can make any creature a familiar.
More news regarding familiars: I fixed the crashing on picking up items from the ground / containers. Also, I got the inventory screen working for familiars...
At this point they are pretty much fully fledged party members. Here's the functionality that I've added to familiars: 1. Can use doors / containers 2. Can transfer items off the ground / containers 2. Can transition to areas without having to be with true party members 3. Can use character sheet / inventory / spell books
The only thing I haven't done yet is have the engine distribute party XP to familiars. -------------------------------
Anyone else have ideas they would like to see? I'm getting close to releasing the first version of this craziness - let's say within a week's time.
The following are the currently implemented features... and that's only what I've done. Once this is released, anyone with a knack of poking around the assembly could make new functions and share them just like I have.
(Hardcoded LUA Functions)
call(address, functionArgs, ecx, popSize) - Calls an internal function.
rdword(address) - Reads a dword at the given address.
rstring(address) - Reads a null-terminated string at the given address.
ruserdata(userdata) - Returns the internal address associated with the given userdata.
wbyte(address, byte) - Writes a byte to the given address.
wstring(address, string) - Writes a null-terminated string to the given address.
(Hardcoded Engine Behavior)
Engine calls keyPressed(key) every time a key is pressed.
Engine calls keyReleased(key) every time a key is released.
(Softcoded LUA Functions)
checkActorSpellState(actorID, splstateID) - Returns true if the given actor is currently under the given splstateID.
getActorDataAddress(actorID) - Returns the address where all the actor's data is held.
getActorEffectResrefs(actorID) - Returns all the .SPL files the given actorID is under.
getActorIDUnderCursor(actorID) - Returns the actorID of the creature under the cursor.
getActorLocation(actorID) - Returns the x,y pair of the given actor.
getActorName(actorID) - Returns the given actor's name.
getActorsAreaSize(actorID) - Returns the width,height pair of the given actor's area.
getActorScriptName(actorID) - Returns the given actor's script name.
getActorStat(actorID, statID) - Returns the value of the given actor's statID.
getAlignment(actorID) - Returns the given actor's Alignment value.
getAllLoadedActorIDs() - Returns all actorIDs of all currently loaded creatures.
getClass(actorID) - Returns the given actor's Class value.
getEnemyAlly(actorID) - Returns the given actor's EA value.
getGender(actorID) - Returns the given actor's Gender value.
getGeneral(actorID) - Returns the given actor's General value.
getListScroll(listName) - Returns the scroll-level of the given list.
getMaximumMemorizableClericSpells(actorID, level) - Returns the maximum memorizable cleric spells for the given actor at the given level.
getMaximumMemorizableInnateSpells(actorID, level) - Returns the maximum memorizable innate spells for the given actor at the given level.
getMaximumMemorizableWizardSpells(actorID, level) - Returns the maximum memorizable wizard spells for the given actor at the given level.
getMenuAddressFromItem(menuItemName) - Returns the address of the menu containing the menuItemName.
getMenuItemAddress(menuItemName) - Returns the internal address of the menuItemName.
getPCIdentifier(slot) - Returns the actorID of the party member in the given portrait slot.
getRace(actorID) - Returns the given actor's Race value.
getSelectedActorID() - Returns the currently selected actorID.
getSpecific(actorID) - Returns the given actor's Specific value.
getSpellDescription(resrefLocation) - Fetches a spell description based on the given resrefLocation.
getSpellIcon(resrefLocation) - Fetches a spell icon based on the given resrefLocation.
getSpellName(resrefLocation) - Fetches a spell name based on the given resrefLocation.
getTrueMousePos() - Returns the mouse position. (vanilla function is broken)
learnClericSpell(actorID, level, resref) - Adds the given resref to the actor's cleric spellbook.
learnInnateSpell(actorID, level, resref) - Adds the given resref to the actor's innate spellbook.
learnWizardSpell(actorID, level, resref) - Adds the given resref to the actor's wizard spellbook.
memorizeClericSpell(actorID, level, resref) - Memorizes the given cleric spell in the actor's spellbook.
memorizeInnateSpell(actorID, level, resref) - Memorizes the given innate spell in the actor's spellbook.
memorizeWizardSpell(actorID, level, resref) - Memorizes the given wizard spell in the actor's spellbook.
processClericMemorization(actorID, func) - Call func(level, splAddress) for every memorized cleric entry.
processInnateMemorization(actorID, func) - Call func(level, splAddress) for every memorized innate entry.
processKnownClericSpells - Call func(splAddress) for every known cleric entry.
processKnownInnateSpells - Call func(splAddress) for every known innate entry.
processKnownWizardSpells - Call func(splAddress) for every known wizard entry.
processWizardMemorization(actorID, func) - Call func(level, splAddress) for every memorized wizard entry.
setListScroll(listName, scroll) - Sets the given list's scroll amount.
unlearnClericSpell(actorID, level, resref) - Removes the given resref from the actor's cleric spellbook.
unlearnInnateSpell(actorID, level, resref) - Removes the given resref from the actor's innate spellbook.
unlearnWizardSpell(actorID, level, resref) - Removes the given resref from the actor's wizard spellbook.
unmemorizeClericSpell(actorID, level, index) - Unmemorizes the given cleric spell from the given index from the actor's spellbook.
unmemorizeInnateSpell(actorID, level, index) - Unmemorizes the given innate spell from the given index from the actor's spellbook.
unmemorizeWizardSpell(actorID, level, index) - Unmemorizes the given wizard spell from the given index from the actor's spellbook.
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):
"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.
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):
"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.
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.
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?
@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.
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.
And I think I can actually understand this! OMG, dude!
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.
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.
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.
As I'm sure all of you can tell, I am terrible at giving time estimates. Heh...
I'm on Christmas break now, so there's no excuse for me not to release the initial version soon™.
To share some more progress, I've fully implemented and got the B3Lua script action up and running. (That's the one that lets you run LUA code via AI scripts). The one major change I've made is that it no longer runs LUA fragments; you supply it a LUA function name, and it goes and runs that.
Also, I've finalized the "Dynamic Script Arguments" system. Instead of setting arbitrary override variables, you now create a hook function to be run through B3Lua. It's quite easy to make one of these... here's an example:
The above LUA code will force the next script action, (which should be one of the Spell variants), to target the target's location, and not the target itself.
To use this, you simply put a B3Lua("B3SpellToPoint") call directly before you execute the Spell action. So, like this:
That Fireball will not follow the target as if it was a heat-seeking missile. It will instead work exactly how the player's fireballs work, (targeting a point).
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?
@Grammarsalad: Good point. It appears that some of the buttons have actions that are hard-coded to be dependent on the character's class. My scrubber reports that CAST_SPELL accesses the character's class 4 times, THIEVING accesses it 1 time, and BARD_SONG accesses it 1 time.
It's quite obvious what the THIEVING access is deciding. 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. More testing is needed....
In response to your example of a bard's gimped thieving button, it's quite easy to spoof the character's class via a LUA hook. I've actually made some internal utility functions that makes it much easier to create hooks / insert new assembly code into memory. So, here is all the code that it takes to hook into the bard check, and allow Blades to use the thieving button as if they were a fully fledged thief.
Granted, you would only ever care about the _B3HookBardThieving function. The code above that is just to show how I set up the hook behind the scenes.
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.
The loader is finished... at least for the Windows version. Had to figure out how to kick-start all of my edits with only 340 bytes of space. After contemplating every-single optimization possible... I got it down to 300 ¯\_(ツ)_/¯
Also, before any of you say it - I am going to be using a wrapper program in the future. For now I'm writing the loader directly into the exe when you install.
I've attached a highly alpha version of this project... which (should?) keep from exploding... uh, most of the time. I've included the binary patches for BG2EE v2.5.16.6 on Windows.
The new noteworthy files include: M__EEex.lua EEex_Act.lua EEex_AHo.lua EEex_Bar.lua EEex_Brd.lua EEex_Key.lua EEex_Tip.lua
There is very little documentation as of yet, that will come later. If you have any questions, or want to know how to do a thing, feel free to post here. Looking at the file contents listed above should be your starting point if you are curious.
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
@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: 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)
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
-- Increase creature struct size by 0x4 bytes (in memory) for _, address in ipairs(creatureAllocations) do _B3WriteAssembly(address + 1, {{0x3B1C, 4}}) end
_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.
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):
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
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:
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:
@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:
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.
@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.
@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.
@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.
@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.
Comments
More news regarding familiars: I fixed the crashing on picking up items from the ground / containers. Also, I got the inventory screen working for familiars...
At this point they are pretty much fully fledged party members. Here's the functionality that I've added to familiars:
1. Can use doors / containers
2. Can transfer items off the ground / containers
2. Can transition to areas without having to be with true party members
3. Can use character sheet / inventory / spell books
The only thing I haven't done yet is have the engine distribute party XP to familiars.
-------------------------------
Anyone else have ideas they would like to see? I'm getting close to releasing the first version of this craziness - let's say within a week's time.
The following are the currently implemented features... and that's only what I've done. Once this is released, anyone with a knack of poking around the assembly could make new functions and share them just like I have.
(Hardcoded LUA Functions)
- call(address, functionArgs, ecx, popSize) - Calls an internal function.
- rdword(address) - Reads a dword at the given address.
- rstring(address) - Reads a null-terminated string at the given address.
- ruserdata(userdata) - Returns the internal address associated with the given userdata.
- wbyte(address, byte) - Writes a byte to the given address.
- wstring(address, string) - Writes a null-terminated string to the given address.
(Hardcoded Engine Behavior)- Engine calls keyPressed(key) every time a key is pressed.
- Engine calls keyReleased(key) every time a key is released.
(Softcoded LUA Functions)- (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.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.
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: 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.
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.
And I think I can actually understand this! OMG, dude!
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.
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.
I'm on Christmas break now, so there's no excuse for me not to release the initial version soon™.
To share some more progress, I've fully implemented and got the B3Lua script action up and running. (That's the one that lets you run LUA code via AI scripts). The one major change I've made is that it no longer runs LUA fragments; you supply it a LUA function name, and it goes and runs that.
Also, I've finalized the "Dynamic Script Arguments" system. Instead of setting arbitrary override variables, you now create a hook function to be run through B3Lua. It's quite easy to make one of these... here's an example:
The above LUA code will force the next script action, (which should be one of the Spell variants), to target the target's location, and not the target itself.
To use this, you simply put a B3Lua("B3SpellToPoint") call directly before you execute the Spell action. So, like this:
That Fireball will not follow the target as if it was a heat-seeking missile. It will instead work exactly how the player's fireballs work, (targeting a point).
It's quite obvious what the THIEVING access is deciding. 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. More testing is needed....
In response to your example of a bard's gimped thieving button, it's quite easy to spoof the character's class via a LUA hook. I've actually made some internal utility functions that makes it much easier to create hooks / insert new assembly code into memory. So, here is all the code that it takes to hook into the bard check, and allow Blades to use the thieving button as if they were a fully fledged thief.
Granted, you would only ever care about the _B3HookBardThieving function. The code above that is just to show how I set up the hook behind the scenes.
And here's a GIF to prove it works:
BARD_SONG: Might be to detect Bard/Shaman, as the Shaman Dance is just another form of Bardsong.
Also, before any of you say it - I am going to be using a wrapper program in the future. For now I'm writing the loader directly into the exe when you install.
I've attached a highly alpha version of this project... which (should?) keep from exploding... uh, most of the time. I've included the binary patches for BG2EE v2.5.16.6 on Windows.
The new noteworthy files include:
M__EEex.lua
EEex_Act.lua
EEex_AHo.lua
EEex_Bar.lua
EEex_Brd.lua
EEex_Key.lua
EEex_Tip.lua
There is very little documentation as of yet, that will come later. If you have any questions, or want to know how to do a thing, feel free to post here. Looking at the file contents listed above should be your starting point if you are curious.
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:
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): When I create an inquisitor, I see no change in the action bar
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
Btw, happy New year!
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.
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
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
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
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:
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:
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.
@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.
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.
Tried calling each one manually, it's "setActionbarButton()" that is crashing.
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:
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.
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.