Skip to content

[MOD] EEex (v0.10.2-alpha)

1356749

Comments

  • BubbBubb Member Posts: 1,005
    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().
  • The user and all related content has been deleted.
  • kjeronkjeron Member Posts: 2,368
    This probably isn't the best way, but it should work:
    COPY_EXISTING ~UI.MENU~ override
    	REPLACE_TEXTUALLY ~function[ %TAB%%WNL%]+filterContingencyMageSpells()~
    	~function	filterContingencyMageSpellsDisabled()~
    Create M_*.lua file, with contents:
    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
    
  • BubbBubb Member Posts: 1,005
    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.
  • GrammarsaladGrammarsalad Member Posts: 2,582
    I don't know enough about lua to comment. But, I think that you should do whatever works best for you.
  • GrammarsaladGrammarsalad Member Posts: 2,582
    edited November 2018
    Bubb said:

    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
    Post edited by Grammarsalad on
  • GrammarsaladGrammarsalad Member Posts: 2,582
    *has chills*
  • BubbBubb Member Posts: 1,005

    *has chills*

    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)


    And here's what the Engine would normally do:

  • GrammarsaladGrammarsalad Member Posts: 2,582
    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.
  • GrammarsaladGrammarsalad Member Posts: 2,582
    ...oh, that is awesome!
  • Permidion_StarkPermidion_Stark Member Posts: 4,861
    Bubb said:

    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?

  • GrammarsaladGrammarsalad Member Posts: 2,582

    Bubb said:

    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?

    I think that would be doable through dialog
  • FlashburnFlashburn Member Posts: 1,847
    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.
  • kjeronkjeron Member Posts: 2,368
    Flashburn said:

    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.
  • FlashburnFlashburn Member Posts: 1,847
    kjeron said:

    Flashburn said:

    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:
  • GreenerGreener Member Posts: 430
    @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
  • BubbBubb Member Posts: 1,005
    edited November 2018
    Greener said:

    @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 :)
    kjeron said:

    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.
  • kjeronkjeron Member Posts: 2,368
    Bubb said:

    @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.
  • FlashburnFlashburn Member Posts: 1,847
    Bubb said:

    @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.
  • FlashburnFlashburn Member Posts: 1,847
    YESYESYESYESYES

    I just knew you could do it, I've been dying to have something like this for years!
  • BubbBubb Member Posts: 1,005
    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?
  • The user and all related content has been deleted.
  • GrammarsaladGrammarsalad Member Posts: 2,582
    edited November 2018
    Bubb said:



    ....

    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
  • southfla79southfla79 Member Posts: 214
    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.

    https://forums.beamdog.com/discussion/18108/mod-more-style-for-mages-v1-55/p1#famnpc
  • GrammarsaladGrammarsalad Member Posts: 2,582

    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.

    https://forums.beamdog.com/discussion/18108/mod-more-style-for-mages-v1-55/p1#famnpc

    Oh that's right. I remember @swit saying that eet externalizes familiars
Sign In or Register to comment.