MMExtension v2.2 + MMEditor v2.1 Level Editor [June 4, 2019]

The role-playing games (I-X) that started it all and the various spin-offs (including Dark Messiah).
StormyG7
Leprechaun
Leprechaun
Posts: 24
Joined: 18 Nov 2016

Re: MMExtension v2.2 + MMEditor v2.1 Level Editor [June 4, 2019]

Unread postby StormyG7 » 22 Oct 2021, 21:20

I'm trying to create a way to export/import a party member to get around the 50 player limit by making MMExtension lua scripts in MM8 (specifically the World of Enroth MM 6,7,8 merge; community master branch). Problem is, I don't know if I have access to all of the elements of a party member's data tables needed to make the script I want. I discovered 'Party' is the player sorta/kinda struct that contains from which I can modify or get player data, e.g. "Party[#]" appended with .Name or .Experience, seems to indicate it's the array struct for party members.

In Greyface's help page for MMExtension v2.2, notably "dump(Party[0].Stats)" is given as an example to output player stats. I wish I'd seen that first because I was taking shots in the dark browsing deeper in the help page, forums, and MM8 script files. Anyway, if that contains everything, then I should be able to make it work; unless someone smarter here sees a reason why it's doomed to fail?

dstar
Leprechaun
Leprechaun
Posts: 5
Joined: 17 Oct 2021

Re: MMExtension v2.2 + MMEditor v2.1 Level Editor [June 4, 2019]

Unread postby dstar » 23 Oct 2021, 15:29

DaveHer wrote: 17 Oct 2021, 07:59
dstar wrote: 17 Oct 2021, 03:35 I think I'm missing something. I want to install MM8 choose party, which requires MMExtension, but I'm not sure how to install MMExtension? Where do I find the installation instructions?
Place the folds into the folder where MM8 is installed (where mm8.exe is). You must also put the folders of the MMEditor there.
David
Thanks! I was overthinking it, I guess.

StormyG7
Leprechaun
Leprechaun
Posts: 24
Joined: 18 Nov 2016

Re: MMExtension v2.2 + MMEditor v2.1 Level Editor [June 4, 2019]

Unread postby StormyG7 » 26 Oct 2021, 07:05

I've been working on a script for the MM 6,7,8 merge to get around the 50 party member limit of MM8 by importing and exporting them to text files. So far, this is what I've got. I'm still in my first year of school for software engineering, so it's not great, or completely finished, but as is, it mostly works. The problem is, I have to use an entry on roster.txt in order to import/export. I have no idea how to implement reserving a space on the roster for importing characters from text files.

Code: Select all

--EXPORT-----------------------------------
function EPM()--TO DO: Create a way to export party members
    
    local filename = "test" --TO DO: Rename the file using character specific info
    local filepath = string.format(".\\Adventurers\\%s.txt", filename)

    local file = io.open(filepath,"w")

    file:write("\t++++++++++++++++++++++++++++++++++++++++++++++START Character Info\n")
    file:write( string.format("Name:\t%s\n", Party[Game.CurrentPlayer].Name) )
    file:write( string.format("Class #:\t%d\n", Party[Game.CurrentPlayer].Class) )
    file:write( string.format("Race #:\t%d\n", Party[Game.CurrentPlayer].Attrs.Race) )
    file:write( string.format("Birth Year:\t%d\n", Party[Game.CurrentPlayer].BirthYear) )
    file:write( string.format("Biography:\t%s\n", Party[Game.CurrentPlayer].Biography) )
    file:write( string.format("Level:\t%d\n", Party[Game.CurrentPlayer].LevelBase) )
    file:write( string.format("Experience:\t%d\n", Party[Game.CurrentPlayer].Experience) )
    file:write( string.format("Face #:\t%d\n", Party[Game.CurrentPlayer].Face) )
    file:write( string.format("Voice #:\t%d\n", Party[Game.CurrentPlayer].Voice) )
    
    file:write("\t++++++++++++++++++++++++++++++++++++++++++++++Attributes\t\n")
    file:write( string.format("Might:\t%d\n", Party[Game.CurrentPlayer].MightBase) )
    file:write( string.format("Intellect:\t%d\n", Party[Game.CurrentPlayer].IntellectBase) )
    file:write( string.format("Personality:\t%d\n", Party[Game.CurrentPlayer].PersonalityBase) )
    file:write( string.format("Endurance:\t%d\n", Party[Game.CurrentPlayer].EnduranceBase) )
    file:write( string.format("Accuracy:\t%d\n", Party[Game.CurrentPlayer].AccuracyBase) )
    file:write( string.format("Speed:\t%d\n", Party[Game.CurrentPlayer].SpeedBase) )
    file:write( string.format("Luck:\t%d\n", Party[Game.CurrentPlayer].LuckBase) )
    file:write( string.format("Hit Points:\t%d\n", Party[Game.CurrentPlayer].HP) )
    file:write( string.format("Spell Points:\t%d\n", Party[Game.CurrentPlayer].SP) )

    file:write("\t++++++++++++++++++++++++++++++++++++++++++++++Resistances\n")
    file:write( string.format("Fire Resistance:\t%d\n", Party[Game.CurrentPlayer].FireResistanceBase) )
    file:write( string.format("Air Resistance:\t%d\n", Party[Game.CurrentPlayer].AirResistanceBase) )
    file:write( string.format("Water Resistance:\t%d\n", Party[Game.CurrentPlayer].WaterResistanceBase) )
    file:write( string.format("Earth Resistance:\t%d\n", Party[Game.CurrentPlayer].EarthResistanceBase) )
    file:write( string.format("Mind Resistance:\t%d\n", Party[Game.CurrentPlayer].MindResistanceBase) )
    file:write( string.format("Body Resistance:\t%d\n", Party[Game.CurrentPlayer].BodyResistanceBase) )
    file:write( string.format("Spirit Resistance:\t%d\n", Party[Game.CurrentPlayer].SpiritResistanceBase) )
    local r = 0
    while r<11 do
        file:write( string.format("Resistance #%d:\t%d\n", r+1, Party[Game.CurrentPlayer].Resistances[r].Base) )
        r = r + 1
    end

    file:write("\t++++++++++++++++++++++++++++++++++++++++++++++Skills\n")
    file:write( string.format("Skill Points to Spend:\t%d\n", Party[Game.CurrentPlayer].SkillPoints) )
    local sk = 0
    while sk<39 do
        file:write( string.format("Skill #%d:\t%d\n", sk+1, Party[Game.CurrentPlayer].Skills[sk]) )
        sk = sk + 1
    end

    file:write("\t++++++++++++++++++++++++++++++++++++++++++++++Condition\n")
    file:write( string.format("Additional Years Aged:\t%d\n", Party[Game.CurrentPlayer].AgeBonus) )
    file:write( string.format("Cursed:\t%d\n", Party[Game.CurrentPlayer].Conditions[0]) )
    file:write( string.format("Weak:\t%d\n", Party[Game.CurrentPlayer].Conditions[1]) )
    file:write( string.format("Asleep:\t%d\n", Party[Game.CurrentPlayer].Conditions[2]) )
    file:write( string.format("Afraid:\t%d\n", Party[Game.CurrentPlayer].Conditions[3]) )
    file:write( string.format("Drunk:\t%d\n", Party[Game.CurrentPlayer].Conditions[4]) )
    file:write( string.format("Insane:\t%d\n", Party[Game.CurrentPlayer].Conditions[5]) )
    file:write( string.format("Poison1:\t%d\n", Party[Game.CurrentPlayer].Conditions[6]) )
    file:write( string.format("Disease1:\t%d\n", Party[Game.CurrentPlayer].Conditions[7]) )
    file:write( string.format("Poison2:\t%d\n", Party[Game.CurrentPlayer].Conditions[8]) )
    file:write( string.format("Disease2:\t%d\n", Party[Game.CurrentPlayer].Conditions[9]) )
    file:write( string.format("Poison3:\t%d\n", Party[Game.CurrentPlayer].Conditions[10]) )
    file:write( string.format("Disease3:\t%d\n", Party[Game.CurrentPlayer].Conditions[11]) )
    file:write( string.format("Paralyzed:\t%d\n", Party[Game.CurrentPlayer].Conditions[12]) )
    file:write( string.format("Unconscious:\t%d\n", Party[Game.CurrentPlayer].Conditions[13]) )
    file:write( string.format("Dead:\t%d\n", Party[Game.CurrentPlayer].Conditions[14]) )
    file:write( string.format("Stoned:\t%d\n", Party[Game.CurrentPlayer].Conditions[15]) )
    file:write( string.format("Eradicated:\t%d\n", Party[Game.CurrentPlayer].Conditions[16]) )
    file:write( string.format("Zombie:\t%d\n", Party[Game.CurrentPlayer].Conditions[17]) )
    file:write( string.format("Unkown1:\t%d\n", Party[Game.CurrentPlayer].Conditions[18]) )
    file:write( string.format("Unkown2:\t%d\n", Party[Game.CurrentPlayer].Conditions[19]) )

    file:write("\t++++++++++++++++++++++++++++++++++++++++++++++Equipment\n")
    file:write( string.format("Left Hand:\t%d\n", Party[Game.CurrentPlayer].ItemExtraHand) )
    file:write( string.format("Right Hand:\t%d\n", Party[Game.CurrentPlayer].ItemMainHand) )
    file:write( string.format("Bow:\t%d\n", Party[Game.CurrentPlayer].ItemBow) )
    file:write( string.format("Armor:\t%d\n", Party[Game.CurrentPlayer].ItemArmor) )
    file:write( string.format("Head:\t%d\n", Party[Game.CurrentPlayer].ItemHelm) )
    file:write( string.format("Belt:\t%d\n", Party[Game.CurrentPlayer].ItemBelt) )
    file:write( string.format("Cloak:\t%d\n", Party[Game.CurrentPlayer].ItemCloak) )
    file:write( string.format("Gloves:\t%d\n", Party[Game.CurrentPlayer].ItemGountlets) )
    file:write( string.format("Boots:\t%d\n", Party[Game.CurrentPlayer].ItemBoots) )
    file:write( string.format("Amulet:\t%d\n", Party[Game.CurrentPlayer].ItemAmulet) )
    file:write( string.format("Ring 1:\t%d\n", Party[Game.CurrentPlayer].ItemRing1) )
    file:write( string.format("Ring 2:\t%d\n", Party[Game.CurrentPlayer].ItemRing2) )
    file:write( string.format("Ring 3:\t%d\n", Party[Game.CurrentPlayer].ItemRing3) )
    file:write( string.format("Ring 4:\t%d\n", Party[Game.CurrentPlayer].ItemRing4) )
    file:write( string.format("Ring 5:\t%d\n", Party[Game.CurrentPlayer].ItemRing5) )
    file:write( string.format("Ring 6:\t%d\n", Party[Game.CurrentPlayer].ItemRing6) )

    local i = 0
    local j = 0
    local k = 0
    local l = 0
    local m = 0
    local n = 0
    local o = 0
    local p = 1
    local q = 0

    file:write("\t++++++++++++++++++++++++++++++++++++++++++++++Inventory\n")
    while j<126  do
        file:write( string.format("Row %d/9, Slot %d/14:\t%d\n", math.ceil((j+1) / 14), (j+14) % 14 + 1, Party[Game.CurrentPlayer].Inventory[j]) )
        j = j + 1
    end

    file:write("\t++++++++++++++++++++++++++++++++++++++++++++++Item Data\n")
    while p<138 do
        file:write( string.format("Item %d Body Location:\t%d\n", p, Party[Game.CurrentPlayer].Items[p].BodyLocation) )
        file:write( string.format("Item %d Bonus 1:\t%d\n", p, Party[Game.CurrentPlayer].Items[p].Bonus) )
        file:write( string.format("Item %d Bonus 2:\t%d\n", p, Party[Game.CurrentPlayer].Items[p].Bonus2) )
        file:write( string.format("Item %d Bonus Expiration Time:\t%d\n", p, Party[Game.CurrentPlayer].Items[p].BonusExpireTime) )
        file:write( string.format("Item %d Bonus Strength:\t%d\n", p, Party[Game.CurrentPlayer].Items[p].BonusStrength) )
        file:write( string.format("Item %d Broken:\t%s\n", p, tostring(Party[Game.CurrentPlayer].Items[p].Broken)) )
        file:write( string.format("Item %d Charges:\t%d\n", p, Party[Game.CurrentPlayer].Items[p].Charges) )
        file:write( string.format("Item %d Condition:\t%d\n", p, Party[Game.CurrentPlayer].Items[p].Condition) )
        file:write( string.format("Item %d Hardened:\t%s\n", p, tostring(Party[Game.CurrentPlayer].Items[p].Hardened)) )
        file:write( string.format("Item %d Identified:\t%s\n", p, tostring(Party[Game.CurrentPlayer].Items[p].Identified)) )
        file:write( string.format("Item %d Max Charges:\t%d\n", p, Party[Game.CurrentPlayer].Items[p].MaxCharges) )
        file:write( string.format("Item %d Number:\t%d\n", p, Party[Game.CurrentPlayer].Items[p].Number) )
        file:write( string.format("Item %d Owner:\t%d\n", p, Party[Game.CurrentPlayer].Items[p].Owner) )
        file:write( string.format("Item %d Stolen:\t%s\n", p, tostring(Party[Game.CurrentPlayer].Items[p].Stolen)) )
        file:write( string.format("Item %d Temporary Bonus:\t%s\n", p, tostring(Party[Game.CurrentPlayer].Items[p].TemporaryBonus)) )
        p = p + 1
    end
    
    file:write("\t++++++++++++++++++++++++++++++++++++++++++++++Spell Book\n")
    while i<137 do
        file:write( string.format("Knows Spell %d:\t%s\n", i+1, tostring(Party[Game.CurrentPlayer].Spells[i]) ) )
        i = i + 1
    end
    file:write("\t++++++++++++++++++++++++++++++++++++++++++++++Spell Vars\n")
    file:write( string.format("Current Spell Book Page:\t%d\n", Party[Game.CurrentPlayer].SpellBookPage) )
    file:write( string.format("Current Quick Spell:\t%d\n", Party[Game.CurrentPlayer].QuickSpell) )
    file:write( string.format("Armageddon Casts:\t%d\n", Party[Game.CurrentPlayer].ArmageddonCasts) )
    file:write( string.format("Divine Intervention Casts:\t%d\n", Party[Game.CurrentPlayer].DevineInterventionCasts) )
    file:write( string.format("Fire Spike Casts:\t%d\n", Party[Game.CurrentPlayer].FireSpikeCasts) )

    file:write("\t++++++++++++++++++++++++++++++++++++++++++++++Lloyd's Beacons\n")
    while n<5 do
        file:write( string.format("Beacon %d Active:\t%d\n", n+1, Party[Game.CurrentPlayer].Beacons[n].Active) )
        file:write( string.format("Beacon %d Direction:\t%d\n", n+1, Party[Game.CurrentPlayer].Beacons[n].Direction) )
        file:write( string.format("Beacon %d Expire Time:\t%d\n", n+1, Party[Game.CurrentPlayer].Beacons[n].ExpireTime) )
        file:write( string.format("Beacon %d Look Angle:\t%d\n", n+1, Party[Game.CurrentPlayer].Beacons[n].LookAngle) )
        file:write( string.format("Beacon %d Map:\t%s\n", n+1, tostring(Party[Game.CurrentPlayer].Beacons[n].Map) ) )
        file:write( string.format("Beacon %d Map Index:\t%d\n", n+1, Party[Game.CurrentPlayer].Beacons[n].MapIndex) )
        while o<3 do
            file:write( string.format("Beacon %d Pos %d:\t%d\n", n+1, o+1, Party[Game.CurrentPlayer].Beacons[n].Pos[o]) )
            o = o + 1
        end
        file:write( string.format("Beacon %d X:\t%d\n", n+1, Party[Game.CurrentPlayer].Beacons[n].X) )
        file:write( string.format("Beacon %d Y:\t%d\n", n+1, Party[Game.CurrentPlayer].Beacons[n].Y) )
        file:write( string.format("Beacon %d Z:\t%d\n", n+1, Party[Game.CurrentPlayer].Beacons[n].Z) )
        n = n + 1
        o = 0
    end

    file:write("\t++++++++++++++++++++++++++++++++++++++++++++++Black Potions Used\n")
    while k<7 do
        file:write( string.format("Black Potion %d:\t%s\n", k+1, tostring(Party[Game.CurrentPlayer].UsedBlackPotions[k+264]) ) )
        k = k + 1
    end

    --TO DO: Figure out how to get the number of potential promotion awards there are to make this loop complete the number of iterations needed
    --[[file:write("\nPromotion Awards\n")
    while l< #Party[Game.CurrentPlayer].Attrs.PromoAwards do
        file:write( string.format("Promotion Award d%:\t%s\n", l+1, tostring(Party[Game.CurrentPlayer].Attrs.PromoAwards[l]) ) )
        l = l + 1
    end--]]

    file:write("\t++++++++++++++++++++++++++++++++++++++++++++++General Awards\n")
    while m<104 do
        file:write( string.format("Award %d:\t%s\n", m+1, tostring(Party[Game.CurrentPlayer].Awards[m]) ) )
        m = m + 1
    end

    file:write("\t++++++++++++++++++++++++++++++++++++++++++++++Player Bits\n")
    while q<511 do
        file:write( string.format("Player Bit %d:\t%s\n", q+1, tostring(Party[Game.CurrentPlayer].PlayerBits[q]) ) )
        q = q + 1
    end
    file:write("\t++++++++++++++++++++++++++++++++++++++++++++++END")
    file:close()

end
--IMPORT-----------------------------------
function IPM()




    
    local lastHired = -1
    for _, pl in Party do
        lastHired = lastHired + 1
    end

    HireCharacter(49)
    
    local filename = "test"
    local filepath = string.format(".\\Adventurers\\%s.txt", filename)

    local file = io.open(filepath,"r")
    if file ~= nil then
        local lines = {}

        for line in io.lines(filepath) do 
            lines[#lines + 1] = line
        end
        file:close()

        --Test to see everything is there
        --for k, v in pairs(lines) do
        --    print(k, v)
        --end

        for k, v in pairs(lines) do
            lines[k] = string.format(string.sub(string.sub(lines[k],string.find( lines[k], "\t"),string.len(lines[k])),2))
        end

        --Test to see everything is STILL there
        --for k, v in pairs(lines) do
        --    print(k, v)
        --end

        --Character Info Section
        Party[lastHired].Name = lines[2]
        Party[lastHired].Class = tonumber(lines[3])
        Party[lastHired].Race = tonumber(lines[4])
        Party[lastHired].BirthYear = tonumber(lines[5])
        Party[lastHired].Biography = lines[6]
        Party[lastHired].LevelBase = tonumber(lines[7])
        Party[lastHired].Experience = tonumber(lines[8])
        Party[lastHired].Face = tonumber(lines[9])
        Party[lastHired].Voice = tonumber(lines[10])

        --Stats
        Party[lastHired].MightBase = tonumber(lines[12])
        Party[lastHired].IntellectBase = tonumber(lines[13])
        Party[lastHired].PersonalityBase = tonumber(lines[14])
        Party[lastHired].EnduranceBase = tonumber(lines[15])
        Party[lastHired].AccuracyBase = tonumber(lines[16])
        Party[lastHired].SpeedBase = tonumber(lines[17])
        Party[lastHired].LuckBase = tonumber(lines[18])
        Party[lastHired].HP = tonumber(lines[19])
        Party[lastHired].SP = tonumber(lines[20])

        --Resistances
        Party[lastHired].FireResistanceBase = tonumber(lines[22])
        Party[lastHired].AirResistanceBase = tonumber(lines[23])
        Party[lastHired].WaterResistanceBase = tonumber(lines[24])
        Party[lastHired].EarthResistanceBase = tonumber(lines[25])
        Party[lastHired].MindResistanceBase = tonumber(lines[26])
        Party[lastHired].BodyResistanceBase = tonumber(lines[27])
        Party[lastHired].SpiritResistanceBase = tonumber(lines[28])
        local r = 0
        while r<11 do
            Party[lastHired].Resistances[r].Base = tonumber(lines[29+r])
            r = r + 1
        end
        
        --Skills
        Party[lastHired].SkillPoints = tonumber(lines[41])
        local i = 0
        while i<39 do
            Party[lastHired].Skills[i] = tonumber(lines[42+i])
            i = i + 1
        end

        --Conditions
        Party[lastHired].AgeBonus = tonumber(lines[82])
        local c = 0
        while c<20 do
            Party[lastHired].Conditions[c] = tonumber(lines[83+c])
            c = c + 1
        end
        
        --Equipment
        Party[lastHired].ItemExtraHand = tonumber(lines[104])
        Party[lastHired].ItemMainHand = tonumber(lines[105])
        Party[lastHired].ItemBow = tonumber(lines[106])
        Party[lastHired].ItemArmor = tonumber(lines[107])
        Party[lastHired].ItemHelm = tonumber(lines[108])
        Party[lastHired].ItemBelt = tonumber(lines[109])
        Party[lastHired].ItemCloak = tonumber(lines[110])
        Party[lastHired].ItemGountlets = tonumber(lines[111])
        Party[lastHired].ItemBoots = tonumber(lines[112])
        Party[lastHired].ItemAmulet = tonumber(lines[113])
        Party[lastHired].ItemRing1 = tonumber(lines[114])
        Party[lastHired].ItemRing2 = tonumber(lines[115])
        Party[lastHired].ItemRing3 = tonumber(lines[116])
        Party[lastHired].ItemRing4 = tonumber(lines[117])
        Party[lastHired].ItemRing5 = tonumber(lines[118])
        Party[lastHired].ItemRing6 = tonumber(lines[119])

        --Inventory
        local j = 0
        while j<126  do
            Party[lastHired].Inventory[j] = tonumber(lines[121+j])
            j = j + 1
        end

        --Item Data
        local p = 0
        local z = 0
        while p<137 do
            Party[lastHired].Items[p+1].BodyLocation = tonumber(lines[248+z])
            Party[lastHired].Items[p+1].Bonus = tonumber(lines[249+z])
            Party[lastHired].Items[p+1].Bonus2 = tonumber(lines[250+z])
            Party[lastHired].Items[p+1].BonusExpireTime = tonumber(lines[251+z])
            Party[lastHired].Items[p+1].BonusStrength = tonumber(lines[252+z])
            if string.match(tostring(lines[253+z]),"false") then
                Party[lastHired].Items[p+1].Broken = false
            else
                Party[lastHired].Items[p+1].Broken = true
            end
            Party[lastHired].Items[p+1].Charges = tonumber(lines[254+z])
            Party[lastHired].Items[p+1].Condition = tonumber(lines[255+z])
            if string.match(tostring(lines[253+z]),"false") then
                Party[lastHired].Items[p+1].Hardened = false
            else
                Party[lastHired].Items[p+1].Hardened = true
            end
            if string.match(tostring(lines[253+z]),"false") then
                Party[lastHired].Items[p+1].Identified = false
            else
                Party[lastHired].Items[p+1].Identified = true
            end
            Party[lastHired].Items[p+1].MaxCharges = tonumber(lines[258+z])
            Party[lastHired].Items[p+1].Number = tonumber(lines[259+z])
            Party[lastHired].Items[p+1].Owner = tonumber(lines[260+z])
            if string.match(tostring(lines[253+z]),"false") then
                Party[lastHired].Items[p+1].Stolen = false
            else
                Party[lastHired].Items[p+1].Stolen = true
            end
            if string.match(tostring(lines[253+z]),"false") then
                Party[lastHired].Items[p+1].TemporaryBonus = false
            else
                Party[lastHired].Items[p+1].TemporaryBonus = true
            end
            p = p + 1
            z = z + 15
        end

        --Spellbook
        i = 0
        while i<137 do
            Party[lastHired].Spells[i] = tonumber(lines[121+i])
            i = i + 1
        end

    else
        file:close()
        MessageBox("%s does not exist", filename)
    end

end

User avatar
GrayFace
Round Table Hero
Round Table Hero
Posts: 1660
Joined: 29 Nov 2005

Re: MMExtension v2.2 + MMEditor v2.1 Level Editor [June 4, 2019]

Unread postby GrayFace » 04 Nov 2021, 00:46

StormyG7 wrote: 22 Oct 2021, 21:20 I'm trying to create a way to export/import a party member to get around the 50 player limit by making MMExtension lua scripts in MM8 (specifically the World of Enroth MM 6,7,8 merge; community master branch).
The problem is lloyd beacon images and also any MMExt scripts that may have some info stored for each player based on index. Code patching would be required to deal with lloyd beacons.
The player structure can be backed up like this:
local pl = Party.PlayersArray[49]
vars.PlayerBackup = mem.string(pl, pl['?size'], true)

And restored like this:
local pl = Party.PlayersArray[49]
mem.copy(pl, vars.PlayerBackup)

[edit] Looks like CODE tag is broken
My patches: MM6 MM7 MM8. MMExtension. Tools. Also, I love Knytt Stories and Knytt Underground. I'm also known as sergroj.

StormyG7
Leprechaun
Leprechaun
Posts: 24
Joined: 18 Nov 2016

Re: MMExtension v2.2 + MMEditor v2.1 Level Editor [June 4, 2019]

Unread postby StormyG7 » 04 Nov 2021, 19:15

GrayFace wrote: 04 Nov 2021, 00:46
StormyG7 wrote: 22 Oct 2021, 21:20 I'm trying to create a way to export/import a party member to get around the 50 player limit by making MMExtension lua scripts in MM8 (specifically the World of Enroth MM 6,7,8 merge; community master branch).
The problem is lloyd beacon images and also any MMExt scripts that may have some info stored for each player based on index. Code patching would be required to deal with lloyd beacons.
The player structure can be backed up like this:
local pl = Party.PlayersArray[49]
vars.PlayerBackup = mem.string(pl, pl['?size'], true)

And restored like this:
local pl = Party.PlayersArray[49]
mem.copy(pl, vars.PlayerBackup)

[edit] Looks like CODE tag is broken
I finished making the script and posted a Google Drive Link in the MM 6 7 8 Merge thread. I would like to see if the Adventurer's Inn could be changed to use my script to view exported party members that I have stored in text files and have the dismiss button also trigger my script to export a player.

I haven't encountered any problems with Lloyd's Beacon; probably because so far, only the main character has Lloyd's Beacon. For my own personal use, if the picture for the location doesn't work, I'm not too concerned. I would imagine that one could create a new script to handle the creation of Lloyd's Beacon Images instead of whatever already exists. Then, all that my export/import functions would need is to throw the images into a file system sorted by each exported player full of .bmp or .tga images.

You mention info stored based on the player index, but I have played for over 40 hours swapping characters in and out of text files with no major issues; the only minor issue is that it seems like the fountains and shrines that have onetime bonuses do not work on repeat encounters after swapping out a character using my script. I think this issue should persist with NPCMercenaries script since new mercenaries are using the same player roster index, but I haven't tested enough to see how the game remembers who's visited what. I'm not sure how to get around that, but for now, I just manually track who's visited shrines so I can add the bonus using the MMExt console.

User avatar
GrayFace
Round Table Hero
Round Table Hero
Posts: 1660
Joined: 29 Nov 2005

Re: MMExtension v2.2 + MMEditor v2.1 Level Editor [June 4, 2019]

Unread postby GrayFace » 08 Nov 2021, 04:09

StormyG7 wrote: 04 Nov 2021, 19:15the only minor issue is that it seems like the fountains and shrines that have onetime bonuses do not work on repeat encounters after swapping out a character using my script. I think this issue should persist with NPCMercenaries script since new mercenaries are using the same player roster index, but I haven't tested enough to see how the game remembers who's visited what. I'm not sure how to get around that, but for now, I just manually track who's visited shrines so I can add the bonus using the MMExt console.
That's because MMExt doesn't expose every aspect of a character and you probably don't save everything that in exposes. I gave you the code to use, you just need to save that string into a binary file instead of vars.PlayerBackup. Why don't you just save them into the save file though?
My patches: MM6 MM7 MM8. MMExtension. Tools. Also, I love Knytt Stories and Knytt Underground. I'm also known as sergroj.

StormyG7
Leprechaun
Leprechaun
Posts: 24
Joined: 18 Nov 2016

Re: MMExtension v2.2 + MMEditor v2.1 Level Editor [June 4, 2019]

Unread postby StormyG7 » 08 Nov 2021, 14:55

GrayFace wrote: 08 Nov 2021, 04:09 That's because MMExt doesn't expose every aspect of a character and you probably don't save everything that in exposes. I gave you the code to use, you just need to save that string into a binary file instead of vars.PlayerBackup. Why don't you just save them into the save file though?
This is all fairly new to me and right now the script I'm using is working fairly well. Now that you've told me MMExt doesn't give me all the character's data, I'm more interested in creating something that does, by implementing those four lines of code you posted. The problem is, I don't think I know how to create a binary file using lua, and I'm unsure why a binary is preferable to a script. About the saving characters into the save file, I am interested, but I like being able to open a character file in text format and change things easily. I don't know how to save them into the save file or how the save file is structured or even accessed.

I'm on a playthrough that might be my last for a long time before I move on to something else. Since you made MMExt, I'm just curious, besides the fountains, do you know what else is left out? Is it anything that matters? As I said, so far my current playthrough is working out great with this script. I import and export characters regularly and; but I would hate for it to be ruined and to have to start all over because I missed something important that won't get exported. It sure seems like I got everything that matters besides the fountains. I know I don't have the honorary promotion awards, because World of Enroth merge stores them in a way that I didn't take time to familiarize myself with and it didn't matter to me for my personal use.

EDIT I was wrong about the fountains and shrines. Somehow it must be in player bits or something I exported, but the game remembers who's visited what fountain from character to character using my export/import script.

Eksekk
Assassin
Assassin
Posts: 259
Joined: 19 Jul 2016

Re: MMExtension v2.2 + MMEditor v2.1 Level Editor [June 4, 2019]

Unread postby Eksekk » 10 Nov 2021, 09:34

StormyG7 wrote: 08 Nov 2021, 14:55 EDIT I was wrong about the fountains and shrines. Somehow it must be in player bits or something I exported, but the game remembers who's visited what fountain from character to character using my export/import script.
Yes, it's stored in player bits. You can check if a bit is set with evt.Cmp("PlayerBits", <number>) (will return true if set) and set a bit with evt.Set("PlayerBits", <number>). Idk how they exactly work though.
Unfinished mod by me: MM7 Rev4 mod, MMMerge version.

cthscr
Swordsman
Swordsman
Posts: 587
Joined: 12 Jan 2020

Re: MMExtension v2.2 + MMEditor v2.1 Level Editor [June 4, 2019]

Unread postby cthscr » 10 Nov 2021, 17:44

Bits are packed into bytes, nothing surprising. So 512 player bits are stored as 64 bytes. There are procedures in mm8.exe that check/set specific bit of specific byte based on given number and offset of the field. Note in MMExtension 2.2 you have to subtract 1 from player bit to get its index in array ('Party[0].PlayerBits[3]' is 'evt.Cmp("PlayerBits", 4)').

User avatar
GrayFace
Round Table Hero
Round Table Hero
Posts: 1660
Joined: 29 Nov 2005

Re: MMExtension v2.2 + MMEditor v2.1 Level Editor [June 4, 2019]

Unread postby GrayFace » 20 Nov 2021, 09:29

StormyG7 wrote: 08 Nov 2021, 14:55The problem is, I don't think I know how to create a binary file using lua, and I'm unsure why a binary is preferable to a script.
Like this: io.SaveString([[c:\file.dat]], s), where s is that binary string: s = mem.string(pl, pl['?size'], true)
Then loading: s = io.LoadString([[c:\file.dat]])
StormyG7 wrote: 08 Nov 2021, 14:55About the saving characters into the save file, I am interested, but I like being able to open a character file in text format and change things easily. I don't know how to save them into the save file or how the save file is structured or even accessed.
"vars" table is saved in save file, so you'd need to create a subtable like vars.SavedPlayers and organize players in it however you want.
StormyG7 wrote: 08 Nov 2021, 14:55Since you made MMExt, I'm just curious, besides the fountains, do you know what else is left out?
No, I don't. Can't tell anything off the back of my head.
StormyG7 wrote: 08 Nov 2021, 14:55EDIT I was wrong about the fountains and shrines. Somehow it must be in player bits or something I exported, but the game remembers who's visited what fountain from character to character using my export/import script.
They too are probably stored outside the player structure for MM6 and MM7. There might be a shortage of PlayerBits to fit all 3 games.
My patches: MM6 MM7 MM8. MMExtension. Tools. Also, I love Knytt Stories and Knytt Underground. I'm also known as sergroj.

cthscr
Swordsman
Swordsman
Posts: 587
Joined: 12 Jan 2020

Re: MMExtension v2.2 + MMEditor v2.1 Level Editor [June 4, 2019]

Unread postby cthscr » 22 Nov 2021, 06:17

GrayFace wrote: 20 Nov 2021, 09:29
StormyG7 wrote: 08 Nov 2021, 14:55The problem is, I don't think I know how to create a binary file using lua, and I'm unsure why a binary is preferable to a script.
Like this: io.SaveString([[c:\file.dat]], s), where s is that binary string: s = mem.string(pl, pl['?size'], true)
Then loading: s = io.LoadString([[c:\file.dat]])
Won't you please add some compressor (LZMA maybe)?
GrayFace wrote: 20 Nov 2021, 09:29
StormyG7 wrote: 08 Nov 2021, 14:55EDIT I was wrong about the fountains and shrines. Somehow it must be in player bits or something I exported, but the game remembers who's visited what fountain from character to character using my export/import script.
They too are probably stored outside the player structure for MM6 and MM7. There might be a shortage of PlayerBits to fit all 3 games.
All three games combined use less than 100 PlayerBits. 512 is much more than that. But one can't speak about fountains in general - not every one is stored into PlayerBits.

User avatar
GrayFace
Round Table Hero
Round Table Hero
Posts: 1660
Joined: 29 Nov 2005

Re: MMExtension v2.2 + MMEditor v2.1 Level Editor [June 4, 2019]

Unread postby GrayFace » 07 Dec 2021, 12:45

cthscr wrote: 22 Nov 2021, 06:17 Won't you please add some compressor (LZMA maybe)?
For Lua data to base MMExt? How much does it weigh in the MMMerge?
My patches: MM6 MM7 MM8. MMExtension. Tools. Also, I love Knytt Stories and Knytt Underground. I'm also known as sergroj.

cthscr
Swordsman
Swordsman
Posts: 587
Joined: 12 Jan 2020

Re: MMExtension v2.2 + MMEditor v2.1 Level Editor [June 4, 2019]

Unread postby cthscr » 10 Dec 2021, 05:20

GrayFace wrote: 07 Dec 2021, 12:45
cthscr wrote: 22 Nov 2021, 06:17 Won't you please add some compressor (LZMA maybe)?
For Lua data to base MMExt? How much does it weigh in the MMMerge?
Right now it is small - up to 300k. But I can easily increase it up to 4M.

User avatar
GrayFace
Round Table Hero
Round Table Hero
Posts: 1660
Joined: 29 Nov 2005

Re: MMExtension v2.2 + MMEditor v2.1 Level Editor [June 4, 2019]

Unread postby GrayFace » 13 Dec 2021, 13:41

cthscr wrote: 10 Dec 2021, 05:20 Right now it is small - up to 300k. But I can easily increase it up to 4M.
Forgot to say I've added compression the day I asked about it. Of course, no LZMA, just ZLib that's already in the game. EnableLuaDataCompression(true) enables it.
My patches: MM6 MM7 MM8. MMExtension. Tools. Also, I love Knytt Stories and Knytt Underground. I'm also known as sergroj.

seregavlg
Leprechaun
Leprechaun
Posts: 7
Joined: 14 Jun 2018

Re: MMExtension v2.2 + MMEditor v2.1 Level Editor [June 4, 2019]

Unread postby seregavlg » 14 Jan 2022, 11:02

Hello! Why this not work?
evt.CastSpell{Spell = 77, Mastery = const.Master, Skill = 64, FromX = 0, FromY = 0, FromZ = 0, ToX = 0, ToY = 0, ToZ = 0} - Power Cure
Where i 'm wrong?

Tomsod
Assassin
Assassin
Posts: 294
Joined: 31 Jul 2020

Re: MMExtension v2.2 + MMEditor v2.1 Level Editor [June 4, 2019]

Unread postby Tomsod » 14 Jan 2022, 11:57

IIRC only some spells are implemented in the evt.CastSpell command, mostly those that are actually used in events (pedestals, traps, etc.) Power Cure is evidently not one of them. To cast Power Cure on the party, ideally you'll need to call the player-casts-spell function, but I don't know where (or even if) it's found in MMExtension. Otherwise, you can just replicate its effects instead, but that's obviously more work.

cthscr
Swordsman
Swordsman
Posts: 587
Joined: 12 Jan 2020

Re: MMExtension v2.2 + MMEditor v2.1 Level Editor [June 4, 2019]

Unread postby cthscr » 15 Jan 2022, 15:18

seregavlg wrote: 14 Jan 2022, 11:02 Hello! Why this not work?
evt.CastSpell{Spell = 77, Mastery = const.Master, Skill = 64, FromX = 0, FromY = 0, FromZ = 0, ToX = 0, ToY = 0, ToZ = 0} - Power Cure
Where i 'm wrong?
Depends on what you're using but 'Skill = 64' will most probably call an error. Try to reduce it to 63.

Kyurem
Leprechaun
Leprechaun
Posts: 5
Joined: 02 Feb 2022

Re: MMExtension v2.2 + MMEditor v2.1 Level Editor [June 4, 2019]

Unread postby Kyurem » 05 Feb 2022, 04:12

Wow you guys have knowledge way beyond my comprehension. I know nothing about how lua scripting works. But I've read somewhere else that you can edit the respawn timer of each zone using the mapstats.txt. Sadly I don't know where is the location of that file. Maybe I need to create it, right? I'm using de mm678 merge so I know that mmextension is installed along with the editor. I just wanted the monsters to respawn every 2 in game months lmao.
Also is it possible to edit the duration of training? It takes 8 days per level trained. I just want the mm6 version, 8 days per training, it doesn't matter how many levels you train.

Kyurem
Leprechaun
Leprechaun
Posts: 5
Joined: 02 Feb 2022

Re: MMExtension v2.2 + MMEditor v2.1 Level Editor [June 4, 2019]

Unread postby Kyurem » 05 Feb 2022, 04:15

Wow you guys have knowledge way beyond my comprehension. I know nothing about how lua scripting works. But I've read somewhere else that you can edit the respawn timer of each zone using the mapstats.txt. Sadly I don't know where is the location of that file. Maybe I need to create it, right? I'm using de mm678 merge so I know that mmextension is installed along with the editor. I just wanted the monsters to respawn every 2 in game months lmao.
Also is it possible to edit the duration of training? It takes 8 days per level trained. I just want the mm6 version, 8 days per training, it doesn't matter how many levels you train.

Tomsod
Assassin
Assassin
Posts: 294
Joined: 31 Jul 2020

Re: MMExtension v2.2 + MMEditor v2.1 Level Editor [June 4, 2019]

Unread postby Tomsod » 05 Feb 2022, 08:06

Table files like mapstats.txt are in lod archives in the Data directory. You'll need MMArchive or a similar tool to open them. Create DataFiles directory next to Data, extract the txt file there, and then you can edit it. The specific archive depends on the version of Merge you're using, usually it's something.T.lod.

Training time is not easily editable. You'd need an assembly patch or perhaps some time-rewinding MMExt hack, and both are evidently not your specialty. Relatedly, I actually intend to make this exact change in the next version of my MM7 mod (I have a very strong opinion that the MM6 way was better here), but I haven't got around to it yet.


Return to “Might and Magic”

Who is online

Users browsing this forum: Google [Bot] and 53 guests