As kjeron has said, it is possible to do this with the normal UI modding capabilities. To be more specific, the function you would have to use to add unmemorized spells to contingencies / sequencers would be mageScreen:SequenceSpell().
function filterContingencyMageSpells()
local out = {}
if characters[id].mageSpells ~= nil and characters[id].mageSpells[currentSpellLevel] ~= nil then
for k,v in pairs(characters[id].mageSpells[currentSpellLevel]) do
if mageScreen:SpellAllowedForContingency(v.level, v.resref) then
if v.castableCount > 0
tableInsert(out, v)
elseif contingencyResRef == "resource" then -- custom sequencer/contingency spell resref, with quotes
tableInsert(out, v)
elseif contingencyResRef == "resource" then -- custom sequencer/contingency spell resref, with quotes
tableInsert(out, v)
elseif contingencyResRef == "resource" then -- custom sequencer/contingency spell resref, with quotes
tableInsert(out, v)
elseif contingencyResRef == "resource" then -- custom sequencer/contingency spell resref, with quotes
tableInsert(out, v)
elseif contingencyResRef == "resource" then -- custom sequencer/contingency spell resref, with quotes
tableInsert(out, v)
end
end
end
end
if characters[id].priestSpells ~= nil and characters[id].priestSpells[currentSpellLevel] ~= nil then
for k,v in pairs(characters[id].priestSpells[currentSpellLevel]) do
if v.castableCount > 0 and mageScreen:SpellAllowedForContingency(v.level, v.resref) then
tableInsert(out, v)
end
end
end
return out
end
--These add the correct labels/descriptions when setting the sequencer/contingency, the game crashes without them:
mageBookStrings['resource'] = mageBookStrings['SPWI420'] -- Minor Sequencer clone, replace resource, keep quotes
mageBookStrings['resource'] = mageBookStrings['SPWI710'] -- Spell Sequencer
mageBookStrings['resource'] = mageBookStrings['SPWI809'] -- Spell Trigger
mageBookStrings['resource'] = mageBookStrings['SPWI617'] -- Contingency
mageBookStrings['resource'] = mageBookStrings['SPWI908'] -- Chain Contingency
As I'm sure everyone can tell from my relative absence, school started back up and I haven't had much time to play around with this project.
Anyways - I had a thought while working on this today: why am I hard coding all of these functions? It's a literal hell to work with the assembly directly. I can easily externalize the ability to read arbitrary memory addresses to the LUA environment... so wouldn't it make more sense to deal with most everything there, where I don't cry out in agony every time I attempt to implement something?
While I understand it can crash the game quite easily if the implementer is careless with the memory accesses, I think it is a much better system to allow anyone with some know-how to create new functions that tie into the engine.
Here's an example of some LUA code I wrote that mimics an otherwise internal function that gets the currently selected creature's ID:
function getSelectedCharacter() local eax = Infinity_ReadDWORD(0x93FDBC) local ecx = Infinity_ReadDWORD(eax + 0xD14) ecx = ecx + 0x3E48 if Infinity_ReadDWORD(ecx + 0xD14) >= 0x0 then eax = Infinity_ReadDWORD(ecx + 0xC) eax = Infinity_ReadDWORD(eax + 0x8) else eax = bit.bor(eax, 0xFFFFFFFF) end return eax end
Of course with enough research into where the engine stores various things in memory, any value can be accessed in this fashion.
Long time no see! I've been screwing around with this project for the last two days, and, well, I made a minimap? It's not very extravagant, but it works just the same.
I've hardcoded 3 new LUA functions into the exe which allow me to read arbitrary memory addresses and call internal functions - leaving the actual implementation of exciting functions to UI.MENU. Here's the new functions I used to make the minimap, if anyone is curious:
function getSelectedActorID() return rdword(rdword(rdword(rdword(0x93FDBC) + 0xD14) + 0x3E54) + 0x8) end
function getActorDataAddress(actorID) local resultBlock = malloc(0x4) call(0x625C00, {resultBlock, actorID}, nil, 0x8) local result = rdword(resultBlock) free(resultBlock) return result end
function getAllLoadedActorIDs() local ids = {} local eax = getActorDataAddress(0x0) local ecx = eax eax = rdword(eax) eax = call(rdword(eax + 0x1C), {}, ecx, 0x0) ebx = rdword(eax) repeat local actorID = rdword(ebx + 0x8) -- Check if it's actually an actor... -- apparently animations are also stored in this list. if rdword(getActorDataAddress(actorID)) == 0x8A86D0 then table.insert(ids, actorID) end ebx = rdword(ebx) until ebx == 0x0 return ids end
function getActorLocation(actorID) local dataAddress = getActorDataAddress(actorID) local x = rdword(dataAddress + 0x8) local y = rdword(dataAddress + 0xC) return x, y end
function getActorsAreaSize(actorID) local address = rdword(getActorDataAddress(actorID) + 0x14) local width = rword(address + 0x4BC) * 64 local height = rword(address + 0x4C0) * 64 return width, height end
-- Temporary soft-coded function to simulate -- reading a word... function rword(address) return bit32.extract(rdword(address), 0, 16) end
function malloc(size) return call(0x886FD0, {size}, nil, 0x4) end
function free(address) return call(0x7BF980, {address}, nil, 0x4) end
Long time no see! I've been screwing around with this project for the last two days, and, well, I made a minimap? It's not very extravagant, but it works just the same.
I've hardcoded 3 new LUA functions into the exe which allow me to read arbitrary memory addresses and call internal functions - leaving the actual implementation of exciting functions to UI.MENU. Here's the new functions I used to make the minimap, if anyone is curious:
function getSelectedActorID() return rdword(rdword(rdword(rdword(0x93FDBC) + 0xD14) + 0x3E54) + 0x8) end
function getActorDataAddress(actorID) local resultBlock = malloc(0x4) call(0x625C00, {resultBlock, actorID}, nil, 0x8) local result = rdword(resultBlock) free(resultBlock) return result end
function getAllLoadedActorIDs() local ids = {} local eax = getActorDataAddress(0x0) local ecx = eax eax = rdword(eax) eax = call(rdword(eax + 0x1C), {}, ecx, 0x0) ebx = rdword(eax) repeat local actorID = rdword(ebx + 0x8) -- Check if it's actually an actor... -- apparently animations are also stored in this list. if rdword(getActorDataAddress(actorID)) == 0x8A86D0 then table.insert(ids, actorID) end ebx = rdword(ebx) until ebx == 0x0 return ids end
function getActorLocation(actorID) local dataAddress = getActorDataAddress(actorID) local x = rdword(dataAddress + 0x8) local y = rdword(dataAddress + 0xC) return x, y end
function getActorsAreaSize(actorID) local address = rdword(getActorDataAddress(actorID) + 0x14) local width = rword(address + 0x4BC) * 64 local height = rword(address + 0x4C0) * 64 return width, height end
-- Temporary soft-coded function to simulate -- reading a word... function rword(address) return bit32.extract(rdword(address), 0, 16) end
function malloc(size) return call(0x886FD0, {size}, nil, 0x4) end
function free(address) return call(0x7BF980, {address}, nil, 0x4) end
Good to see you again. And you bring awesomeness!
Is it possible to interact with the minimap, like say click on it to jump to that location, or maybe do that with the clairvoyance spell? Edit: Er, I mean the farsight spell
Is it possible to interact with the minimap, like say click on it to jump to that location, or maybe do that with the clairvoyance spell? Edit: Er, I mean the farsight spell
It very well could, but I only programmed in the visuals; my laziness has no bounds, heh. -------------------------------------------------------------------
I've been grinding away at this for the past couple of days, and I've got some progress to share with you guys Wall of text incoming!
I think that externalizing the amount of Fatigue would open a whole new world... *snip* ...to see the current amount of Luck stacked would be nice too.
Done:
New code used:
-- Pulled from where the engine decides -- which one player is the currently -- selected leader - such as when the -- character screens are opened, or when -- determining the actionbar buttons. function getSelectedActorID() return rdword(rdword(rdword(rdword(0x93FDBC) + 0xD14) + 0x3E54) + 0x8) end
-- Copied directly from the CheckStat() trigger. function getActorStat(actorID, statID) local ecx = call(0x4FE120, {}, getActorDataAddress(actorID), 0x0) return call(0x52CC40, {statID}, ecx, 0x0) end
-- Pulled from where the engine decides -- which one player is the currently -- selected leader - such as when the -- character screens are opened, or when -- determining the actionbar buttons. function getSelectedActorID() return rdword(rdword(rdword(rdword(0x93FDBC) + 0xD14) + 0x3E54) + 0x8) end
-- Copied directly from the CheckStat() trigger. function getActorStat(actorID, statID) local ecx = call(0x4FE120, {}, getActorDataAddress(actorID), 0x0) return call(0x52CC40, {statID}, ecx, 0x0) end
function dumpStats() local actorID = getSelectedActorID() for i = 1, 202, 1 do Infinity_DisplayString(i.."=>"..getActorStat(actorID, i)) end end
- Get is set of characters spellstate (from SPLSTATE.IDS)
Done:
New code used:
-- Pulled from where the engine decides -- which one player is the currently -- selected leader - such as when the -- character screens are opened, or when -- determining the actionbar buttons. function getSelectedActorID() return rdword(rdword(rdword(rdword(0x93FDBC) + 0xD14) + 0x3E54) + 0x8) end
-- Copied directly from the CheckSpellState() trigger. function checkActorSpellState(actorID, splstateID) return call(0x52DF30, {splstateID}, getActorDataAddress(actorID) + 0xB30, 0x0) end
function dumpSpellStates() local actorID = getSelectedActorID() for i = 0, 126, 1 do Infinity_DisplayString(i.."=>"..checkActorSpellState(actorID, i)) end for i = 249, 255, 1 do Infinity_DisplayString(i.."=>"..checkActorSpellState(actorID, i)) end end
- Get characters EA/GENERAL/CLASS/RACE/ALIGNMENT/GENDER/ALIGNMENT IDS index (as opposed to their current string references).
Done:
New code used:
-- Pulled from where the engine decides -- which one player is the currently -- selected leader - such as when the -- character screens are opened, or when -- determining the actionbar buttons. function getSelectedActorID() return rdword(rdword(rdword(rdword(0x93FDBC) + 0xD14) + 0x3E54) + 0x8) end
-- Copied directly from the CTRL-M DEBUG DUMP: CGameSprite function getEnemyAlly(actorID) return rbyte(getActorDataAddress(actorID) + 0x24, 0x0) end
-- Copied directly from the CTRL-M DEBUG DUMP: CGameSprite function getGeneral(actorID) return rbyte(getActorDataAddress(actorID) + 0x24, 0x1) end
-- Copied directly from the CTRL-M DEBUG DUMP: CGameSprite function getRace(actorID) return rbyte(getActorDataAddress(actorID) + 0x24, 0x2) end
-- Copied directly from the CTRL-M DEBUG DUMP: CGameSprite function getClass(actorID) return rbyte(getActorDataAddress(actorID) + 0x24, 0x3) end
-- Copied directly from the CTRL-M DEBUG DUMP: CGameSprite function getSpecific(actorID) return rbyte(getActorDataAddress(actorID) + 0x30, 0x1) end
-- Copied directly from the CTRL-M DEBUG DUMP: CGameSprite function getGender(actorID) return rbyte(getActorDataAddress(actorID) + 0x30, 0x2) end
-- Copied directly from the CTRL-M DEBUG DUMP: CGameSprite function getAlignment(actorID) return rbyte(getActorDataAddress(actorID) + 0x30, 0x3) end
function dumpSelectorData() local actorID = getSelectedActorID() Infinity_DisplayString("["..getEnemyAlly(actorID).."."..getGeneral(actorID).."."..getRace(actorID).."." ..getClass(actorID).."."..getSpecific(actorID).."."..getGender(actorID).."." ..getAlignment(actorID).."]") end
-- Pulled from where the engine decides -- which one player is the currently -- selected leader - such as when the -- character screens are opened, or when -- determining the actionbar buttons. function getSelectedActorID() return rdword(rdword(rdword(rdword(0x93FDBC) + 0xD14) + 0x3E54) + 0x8) end
-- This code was pulled out of opcode #261 - -- very nonsensical, but it directly mimics -- how the engine accesses the memorization -- tables. function getWizardMemorizationInfo(actorID) local info = {} local infoIndex = nil local maxLevel = 0x9 local ebx = maxLevel local esi = ebx local ecx = ebx local edi = getActorDataAddress(actorID) local edx = rdword(edi + 0x124A) ecx = ecx * 0x16 edx = edx + ecx ecx = ebx * 0x8 ecx = ecx - ebx ecx = ecx + 0x254 local eax = 0x0 ecx = edi + ecx * 0x4 ::_0:: if eax >= maxLevel then goto _3 end
table.insert(info, 1, {})
eax = rdword(ecx) if eax == 0x0 then goto _2 end ::_1:: esi = rdword(eax + 0x8)
eax = rdword(eax) if eax ~= 0x0 then goto _1 end ::_2:: ebx = ebx - 1 ecx = ecx - 0x1C edx = edx - 0x10 if ebx > 0 then goto _0 end ::_3:: return info end
-- This code was pulled out of opcode #261 - -- very nonsensical, but it directly mimics -- how the engine accesses the memorization -- tables. function getClericMemorizationInfo(actorID) local info = {} local infoIndex = nil local maxLevel = 0x7 local ebx = maxLevel local esi = ebx local ecx = ebx local edi = getActorDataAddress(actorID) local edx = rdword(edi + 0x12DA) ecx = ecx * 0x16 edx = edx + ecx ecx = ebx * 0x8 ecx = ecx - ebx ecx = ecx + 0x223 local eax = 0x0 ecx = edi + ecx * 0x4 ::_0:: if eax >= maxLevel then goto _3 end
table.insert(info, 1, {})
eax = rdword(ecx) if eax == 0x0 then goto _2 end ::_1:: esi = rdword(eax + 0x8)
eax = rdword(eax) if eax ~= 0x0 then goto _1 end ::_2:: ebx = ebx - 1 ecx = ecx - 0x1C edx = edx - 0x10 if ebx > 0 then goto _0 end ::_3:: return info end
-- Couldn't find where the engine accesses -- innates in the correct fashion, so I came -- up with this. It seems innates don't store -- "spell" level - everything is treated as -- level 1 at runtime, at least when it comes -- to how they are stored in memory. function getInnateMemorizationInfo(actorID) local info = {} local resrefLocation = nil local infoIndex = nil local currentAddress = rdword(getActorDataAddress(actorID) + 0xA68)
function dumpMemorization() local actorID = getSelectedActorID() Infinity_DisplayString(getActorName(actorID).."("..getActorScriptName(actorID).."):") local wizardSpellInfo = getWizardMemorizationInfo(getSelectedActorID()) Infinity_DisplayString("Wizard Spells:") for i, level in ipairs(wizardSpellInfo) do Infinity_DisplayString("\t["..i.."]:") for j, info in ipairs(level) do Infinity_DisplayString("\t\t["..j.."]:") Infinity_DisplayString("\t\t\tResref: "..info.resref) Infinity_DisplayString("\t\t\tCastable: "..tostring(info.castable)) end end local clericSpellInfo = getClericMemorizationInfo(getSelectedActorID()) Infinity_DisplayString("Cleric Spells:") for i, level in ipairs(clericSpellInfo) do Infinity_DisplayString("\t["..i.."]:") for j, info in ipairs(level) do Infinity_DisplayString("\t\t["..j.."]:") Infinity_DisplayString("\t\t\tResref: "..info.resref) Infinity_DisplayString("\t\t\tCastable: "..tostring(info.castable)) end end local innateSpellInfo = getInnateMemorizationInfo(getSelectedActorID()) Infinity_DisplayString("Innates:") for i, info in ipairs(innateSpellInfo) do Infinity_DisplayString("\t["..i.."]:") Infinity_DisplayString("\t\tResref: "..info.resref) Infinity_DisplayString("\t\tCastable: "..tostring(info.castable)) end end
- MemorizeSpell(level,"resref"), as opposed to the current MemorizeSpell(level,index), same for Unmemorize.
Done:
New code used:
-- Pulled from where the engine decides -- which one player is the currently -- selected leader - such as when the -- character screens are opened, or when -- determining the actionbar buttons. function getSelectedActorID() return rdword(rdword(rdword(rdword(0x93FDBC) + 0xD14) + 0x3E54) + 0x8) end
function memorizeSpell(actorID, level, resref) local esi = getActorDataAddress(actorID) local ecx = esi + 0xB30 if rdword(esi + 0x3748) ~= 0x0 then goto _0 end ecx = esi + 0x1454 ::_0:: local edx = level local eax = edx eax = eax * 0x16 eax = eax + 0x7B8 eax = eax + ecx local upperBoundPointer = eax local incrementMemorizedPointer = esi + edx * 4 + 0x860 ecx = edx + 0x4F eax = ecx * 8 eax = eax - ecx ecx = esi eax = esi + eax * 4 local currentlyMemorizedPointer = eax eax = edx * 8 eax = eax - edx eax = eax + 0x1A1 eax = esi + eax * 4
I must be doing the right things then! ------------------------------------------
A quick question to all of you:
While figuring out how to access the known spells table, I realized that the EE engine sorts the entries alphabetically whenever it accesses it. This completely destroys any persistent state of the order in which the player learned the spells to begin with.
I figured out how to disable this sorting, (only a 14 byte change!). Would this be a desired? You could always sort the spells at run time instead of having the engine screw with the data structures themselves... though I'm not sure if there would be any use to changing the behavior.
Here's an example of the change, spells learned in this order: Armor -> Blindness -> Chill Touch -> Chromatic Orb. (Most recently learned spells go on top... oldest on bottom)
My guess is the reason they did that was so a player could more quickly find the spell they wanted (e.g. when looking for descriptions or for memorization). You might not remember the order that you learned your spells in, but its pretty easy to find your desired spell by alphabetical order.
Well, I've just finished rebuilding the Mage Book from the ground up to use my custom functions instead of the hardcoded ones...
Fun Fact: Now that everything is detached from the internal functions, you can open the mage book for any creature in the game, even those that are not in your party.
Here I am accessing the mage book of the Pseudo Dragon Familar:
It's fully functional too, meaning that you can learn, unlearn, memorize, and unmemorize spells from the familar. I think persistent allies just got more interesting...
It's fully functional too, meaning that you can learn, unlearn, memorize, and unmemorize spells from the familar. I think persistent allies just got more interesting...
Does this mean you can also teach your familiar new spells?
It's fully functional too, meaning that you can learn, unlearn, memorize, and unmemorize spells from the familar. I think persistent allies just got more interesting...
Does this mean you can also teach your familiar new spells?
1. To be able to know what spells enemies are under, without combing through the dialog box. Something like a pop-up on mouse hover? Just imagine, 5 cowled enforcers start their contingencies all at once. Then you have to comb through the dialog box to know which spell is whose.
And here's the result, (only made to be proof-of-concept, mind you):
When the player presses LShift Spell-Inspection mode is enabled, which then pops up on hover all the named spells which have put effects on the creature.
In order to implement the suggestion I had to hook directly into the SDL event queue to receive keyboard presses and releases from the LUA environment; in my implementation, every key-press calls keyPressed(key), and every key-release calls keyReleased(key).
In fact, any sort of input can be processed here, even mouse movement / clicks. I only send the keyboard presses / releases to the LUA environment, though I could send everything if other modders could think of a use.
And here's a small rant: For some reason Infinity_GetMousePosition() relies on the menu that was opened last, and doesn't always report the correct mouse position, (it's literally your ONLY JOB!). I had to use my new memory-reading capabilities to grab the mouse position straight from the source, because something is seriously wacky with the vanilla function.
That is incredible, @Bubb! Mage fencing will become so much easier!
I have a suggestion, too, if I may: Could you make a countdown timer for when modal skills proc, like Bard Song/Detect Traps/Turn Undead?
I find that when I'm trying to use Detect Illusions in combat once the skill is high enough, it's usually a gamble to see if my thief will dispel a mage's illusions in time before the mage casts another spell that my thief could get caught in. With the timer, I could see if it would be wiser to have my thief escape or stay a little longer so other party members can capitalize on the mage's illusions being shattered.
I have a suggestion, too, if I may: Could you make a countdown timer for when modal skills proc, like Bard Song/Detect Traps/Turn Undead?
You don't need any UI editing for this. Modal Abilties run on the same repeating interval as opcode 232 (cast spell on condition), so you can have the spell it casts display the countdown each second.
I have a suggestion, too, if I may: Could you make a countdown timer for when modal skills proc, like Bard Song/Detect Traps/Turn Undead?
You don't need any UI editing for this. Modal Abilties run on the same repeating interval as opcode 232 (cast spell on condition), so you can have the spell it casts display the countdown each second.
I guess what I'm asking for is a little more ambitious than that. I don't want it clogging the combat log or displaying overhead text, and I don't think doing that will take into account if the mode is turned off before the ability actually activates.
I want a small little countdown timer to the left of the quickloot diamond that is synced with the game's paused/unpaused state, starts and stops when the modal skill is turned on/off, and preferably with tenths of a second also displayed. Like this:
@Bubb Incredible work! Out of curiosity, how would one incorporate these functions? For example Fatigue, luck, intoxication is this something that could be displayed on the character sheet as a value? Could it be updated in realtime? @lefreut
@Bubb Incredible work! Out of curiosity, how would one incorporate these functions? For example Fatigue, luck, intoxication is this something that could be displayed on the character sheet as a value? Could it be updated in realtime? @lefreut
@Greener: Sorry for the delayed response, school has kept me busy for the past few days. To answer your question, yes, these values can easily be added to the character sheet and be updated in real time using my functions
Modal Abilties run on the same repeating interval as opcode 232 (cast spell on condition), so you can have the spell it casts display the countdown each second.
@kjeron: After investigating the modal timer, I don't believe this is 100% true. While modal abilities and opcode 232 usually run very close together, they do actually use different internal timers.
While opcode 232 uses a simple 100 tick countdown, modal abilities actually display some intentional randomness in their execution. Here's some pseudocode that controls the modal timer:
idRemainder = actorID % 0x64 //Divide actorID by 100 and store the remainder modalRemainder = modalTimer % 0x64 //Divide modalTimer by 100 and store the remainder if idRemainder == modalRemainder then runModalAbility() //Actually execute the modal action end
In normal people speak: the modal ability will randomly execute somewhere on the interval [0, 100] of the timer.
Every actor in the game will evaluate a different number for the idRemainder, and thus the modal abilities of most creatures will be processed at different times. Perhaps the developers did this to spread out the operations to avoid lag, but it's an interesting finding regardless.
@Flashburn: Would you also want the GUI to show the opcode 232 counter? This would allow the player to keep track of when any opcode 232 spells would execute, such as Globe of Blades.
@kjeron: After investigating the modal timer, I don't believe this is 100% true. While modal abilities and opcode 232 usually run very close together, they do actually use different internal timers.
So it is, how odd. I know I've had the two run parallel before, must have been coincidence.
@Flashburn: Would you also want the GUI to show the opcode 232 counter? This would allow the player to keep track of when any opcode 232 spells would execute, such as Globe of Blades.
Yes, as long as you can minimize its UI element. It will still be helpful to know when spells that use opcode 232 proc, but they're not quite as important as knowing when modal abilities will proc.
So, I'm going to go crazy for a second and try to contemplate the impossible: would it be possible to unhardcode the 6 party-member cap?
My findings suggest that while possible, it would be an extraordinary task even with the original source code. If my exe scrubber is correct, there are around ~250 hardcoded instances where the engine assumes that or checks against there being 6 party members. Every one of these cases would have to be looked at individually, and a unique fix applied. I am sorry, but I'm not going to go insane trying to do that...
Here's what I am thinking: while a "correct" removal of the cap would be hell, would it be possible to fake it? Hear me out,
I have already shown that the mage book can be completely detached from the engine's hardcoded implementation. I'm thinking it would be possible to do this with every GUI element that pertains to party members, such as the inventory and the character sheet.
The ideal candidate for fake party members would be familiars, because the engine already treats them as fully-fledged characters, just with some artificial restrictions imposed.
I've already removed the door-opening and area-transition restrictions from familiars. Interacting with containers crashes the game right now, but I'm pretty sure I can fix that.
If you're still reading this, can you think of anything I've missed? Do familiars have any non-GUI restrictions beyond area transitions, door opening, and inventory management?
I've already removed the door-opening and area-transition restrictions from familiars. Interacting with containers crashes the game right now, but I'm pretty sure I can fix that.
If you're still reading this, can you think of anything I've missed? Do familiars have any non-GUI restrictions beyond area transitions, door opening, and inventory management?
Removed restrictions: Wait, what!?! Awesome! What about restrictions to regular summons (e.g. area transitions?)
Anything else: there actually may be a way around this that I don't know about but what about the limit of one familiar? And (in bg(2)ee at least) only allowing the protagonist to have a familiar
Not sure if this is helpful but this mod has allowed NPC familiars for a while. Granted haven't used it much since I mainly play EET and the familiar stuff isn't compatible.
Not sure if this is helpful but this mod has allowed NPC familiars for a while. Granted haven't used it much since I mainly play EET and the familiar stuff isn't compatible.
Comments
Anyways - I had a thought while working on this today: why am I hard coding all of these functions? It's a literal hell to work with the assembly directly. I can easily externalize the ability to read arbitrary memory addresses to the LUA environment... so wouldn't it make more sense to deal with most everything there, where I don't cry out in agony every time I attempt to implement something?
While I understand it can crash the game quite easily if the implementer is careless with the memory accesses, I think it is a much better system to allow anyone with some know-how to create new functions that tie into the engine.
Here's an example of some LUA code I wrote that mimics an otherwise internal function that gets the currently selected creature's ID:
Of course with enough research into where the engine stores various things in memory, any value can be accessed in this fashion.
I've hardcoded 3 new LUA functions into the exe which allow me to read arbitrary memory addresses and call internal functions - leaving the actual implementation of exciting functions to UI.MENU. Here's the new functions I used to make the minimap, if anyone is curious:
Is it possible to interact with the minimap, like say click on it to jump to that location, or maybe do that with the
clairvoyancespell?Edit: Er, I mean the farsight spell
-------------------------------------------------------------------
I've been grinding away at this for the past couple of days, and I've got some progress to share with you guys
Wall of text incoming!
@Raduziel: Done:
New code used:
@kjeron: Done:
New code used:
New code used:
New code used:
New code used:
Done:
New code used:
------------------------------------------
A quick question to all of you:
While figuring out how to access the known spells table, I realized that the EE engine sorts the entries alphabetically whenever it accesses it. This completely destroys any persistent state of the order in which the player learned the spells to begin with.
I figured out how to disable this sorting, (only a 14 byte change!). Would this be a desired? You could always sort the spells at run time instead of having the engine screw with the data structures themselves... though I'm not sure if there would be any use to changing the behavior.
Here's an example of the change, spells learned in this order: Armor -> Blindness -> Chill Touch -> Chromatic Orb. (Most recently learned spells go on top... oldest on bottom)
And here's what the Engine would normally do:
Fun Fact: Now that everything is detached from the internal functions, you can open the mage book for any creature in the game, even those that are not in your party.
Here I am accessing the mage book of the Pseudo Dragon Familar:
It's fully functional too, meaning that you can learn, unlearn, memorize, and unmemorize spells from the familar. I think persistent allies just got more interesting...
When the player presses LShift Spell-Inspection mode is enabled, which then pops up on hover all the named spells which have put effects on the creature.
In order to implement the suggestion I had to hook directly into the SDL event queue to receive keyboard presses and releases from the LUA environment; in my implementation, every key-press calls keyPressed(key), and every key-release calls keyReleased(key).
In fact, any sort of input can be processed here, even mouse movement / clicks. I only send the keyboard presses / releases to the LUA environment, though I could send everything if other modders could think of a use.
And here's a small rant: For some reason Infinity_GetMousePosition() relies on the menu that was opened last, and doesn't always report the correct mouse position, (it's literally your ONLY JOB!). I had to use my new memory-reading capabilities to grab the mouse position straight from the source, because something is seriously wacky with the vanilla function.
I have a suggestion, too, if I may:
Could you make a countdown timer for when modal skills proc, like Bard Song/Detect Traps/Turn Undead?
I find that when I'm trying to use Detect Illusions in combat once the skill is high enough, it's usually a gamble to see if my thief will dispel a mage's illusions in time before the mage casts another spell that my thief could get caught in. With the timer, I could see if it would be wiser to have my thief escape or stay a little longer so other party members can capitalize on the mage's illusions being shattered.
I want a small little countdown timer to the left of the quickloot diamond that is synced with the game's paused/unpaused state, starts and stops when the modal skill is turned on/off, and preferably with tenths of a second also displayed. Like this:
While opcode 232 uses a simple 100 tick countdown, modal abilities actually display some intentional randomness in their execution. Here's some pseudocode that controls the modal timer: In normal people speak: the modal ability will randomly execute somewhere on the interval [0, 100] of the timer.
Every actor in the game will evaluate a different number for the idRemainder, and thus the modal abilities of most creatures will be processed at different times. Perhaps the developers did this to spread out the operations to avoid lag, but it's an interesting finding regardless.
@Flashburn: Would you also want the GUI to show the opcode 232 counter? This would allow the player to keep track of when any opcode 232 spells would execute, such as Globe of Blades.
I just knew you could do it, I've been dying to have something like this for years!
My findings suggest that while possible, it would be an extraordinary task even with the original source code. If my exe scrubber is correct, there are around ~250 hardcoded instances where the engine assumes that or checks against there being 6 party members. Every one of these cases would have to be looked at individually, and a unique fix applied. I am sorry, but I'm not going to go insane trying to do that...
Here's what I am thinking: while a "correct" removal of the cap would be hell, would it be possible to fake it? Hear me out,
I have already shown that the mage book can be completely detached from the engine's hardcoded implementation. I'm thinking it would be possible to do this with every GUI element that pertains to party members, such as the inventory and the character sheet.
The ideal candidate for fake party members would be familiars, because the engine already treats them as fully-fledged characters, just with some artificial restrictions imposed.
I've already removed the door-opening and area-transition restrictions from familiars. Interacting with containers crashes the game right now, but I'm pretty sure I can fix that.
If you're still reading this, can you think of anything I've missed? Do familiars have any non-GUI restrictions beyond area transitions, door opening, and inventory management?
Anything else: there actually may be a way around this that I don't know about but what about the limit of one familiar? And (in bg(2)ee at least) only allowing the protagonist to have a familiar
https://forums.beamdog.com/discussion/18108/mod-more-style-for-mages-v1-55/p1#famnpc