Toorumifier 1.01

Talk about anything related to Legend of Grimrock 2 here.
minmay
Posts: 2780
Joined: Mon Sep 23, 2013 2:24 am

Toorumifier 1.01

Post by minmay »

UPDATE: With the release of Grimrock 2 2.2.4, this script is basically obsolete. You can get Toorum's movement speed by executing PartyComponent:setMovementSpeed(1.5). You still cannot get Toorum's 1.2x turning speed without editing the save, as far as I know. So, for almost-Toorum mode, just start a solo character in slot 1 with no skills or traits and paste this into the debug console:

Code: Select all

party.party:setMovementSpeed(1.5)
 party.party:getChampion(1):setName("Toorum")
 party.party:getChampion(1):setSex("male")
 party.party:getChampion(1):setRace("human")
 party.party:getChampion(1):setPortrait("assets/textures/portraits/toorum.tga")
 party.party:getChampion(1):setBaseStat("strength",18)
 party.party:getChampion(1):setBaseStat("dexterity",15)
 party.party:getChampion(1):setBaseStat("vitality",16)
 party.party:getChampion(1):setBaseStat("willpower",12)
 party.party:getChampion(1):trainSkill("armors",2,true)
 party.party:getChampion(1):trainSkill("earth_magic",1,true)
 party.party:getChampion(1):trainSkill("heavy_weapons",1,true)
 party.party:getChampion(1):trainSkill("light_weapons",1,true)
 party.party:getChampion(1):addTrait("skilled")
 party.party:getChampion(1):addSkillPoints(-1)
 party.party:getChampion(1):addTrait("tough")
 party.party:getChampion(1):upgradeBaseStat("cooldown_rate",100)
The original post is preserved below in case you want to see my guesses at save game format.
SpoilerShow
Here is a Python 2 script that will edit a Grimrock 2 saved game and give the party Toorum's movement speed (or take it away if they already have it). Proof. At the moment, it only works on saved games from the original dungeon and not custom dungeons, but it should work on any save made at any point in the original dungeon. If you don't know how to run a Python script, I recommend using your favourite Web search engine to find out, instead of asking me.

I've tested it on quite a few different saves and it hasn't done any damage yet, but I still recommend that you keep your original save file instead of overwriting it, just in case.

Download
...or look at the contents of this spoiler. My knowledge of Python is terrible so this code is really bad but it works:

Code: Select all

#!/usr/bin/env python

import struct
import sys
import zlib
import re

"""
python toorumify.py [infile] [outfile]
Example: python toorify quicksave.sav autosave.sav
to Toorumify the quicksave and overwrite the autosave with the result.
Toorumifying increases the party's movement speed, as though playing as Toorum.
If the save file is already Toorumified, it will be Untoorumified, returning
the party's normal movement speed.
NOTE: This script may not work with custom dungeons, failing to find the party
object. This will hopefully change at some point. However, if you have a save
in the original dungeon from 2.1.1.13+, and this script doesn't work on it,
that's a bug and you should tell me at
http://www.grimrock.net/forum/viewtopic.php?f=18&t=7933.

Also, importing the party will not retain the increased movement speed. The
former will hopefully change, the latter cannot be changed.

The new save file will have the same name as the old one in the "Load Game"
menu. You can tell it apart because it will be on top (since it will be the
most recently modified).

This script ONLY changes the party's movement speed. If you want to *really*
play as Toorum, start a solo character without traits or skills and paste the
following into the debug console (you need to keep the spaces):

--BEGIN--
party.party:getChampion(1):setName("Toorum")
 party.party:getChampion(1):setSex("male")
 party.party:getChampion(1):setRace("human")
 party.party:getChampion(1):setPortrait("assets/textures/portraits/toorum.tga")
 party.party:getChampion(1):setBaseStat("strength",18)
 party.party:getChampion(1):setBaseStat("dexterity",15)
 party.party:getChampion(1):setBaseStat("vitality",16)
 party.party:getChampion(1):setBaseStat("willpower",12)
 party.party:getChampion(1):trainSkill("armors",2,true)
 party.party:getChampion(1):trainSkill("earth_magic",1,true)
 party.party:getChampion(1):trainSkill("heavy_weapons",1,true)
 party.party:getChampion(1):trainSkill("light_weapons",1,true)
 party.party:getChampion(1):addTrait("skilled")
 party.party:getChampion(1):addSkillPoints(-1)
 party.party:getChampion(1):addTrait("tough")
 party.party:getChampion(1):upgradeBaseStat("cooldown_rate",100)
--END--

The final line imitates the effect of the "Thunderstruck" trait (halving all
cooldowns). I'll let you decide for yourself which class fits Toorum best;
there's not really anything like Ranger in Grimrock 2.
"""



# All values are little-endian.
#
# Types: 
# 0 - 16-bit string handle?
# 1 - some kind of byte?
# 2 - byte
# 3 - short
# 4 - int
# 5 - double
# 6 - ?
# 7 - ?
# 8 - ?
# 9 - function
# 10 - table
# 11 - vec
# 12 - ?
# 22 - transform
# 23 - sphere
# 24 - box
# 25 - plane
# 26 - ray
    
def save(infile, outfile):
    data = open(infile).read()
    (magic, version) = struct.unpack('<4sI', data[:8])
    # File begins with 'GRIM' and a version number.
    assert(magic == 'GRIM')
    # 1.01: disabling this assert because it changes every minor game version
    # (so far there hasn't been a change to the savefile format I know of).
    #assert(version == 16)

    # The rest of the file is a zlib stream.
    raw = zlib.decompress(data[8:])
    size = len(raw)

    offset = 0
    flagsIndex = -1
    while offset < size:
	# Each chunk starts with a 4-byte description like "STAB" or "PROP"
	# followed by the size of the chunk. Note that chunks can contain other
	# chunks.
        (type,csize) = struct.unpack('<4sI', raw[offset:offset+8])
        offset += 8
        if type == b'STAB': # String table.
          soffset = offset
          sindex = 1 # It's 1-indexed because Lua, I guess.
          while soffset < csize:
	    # Each entry in the string table is the size of a string followed by the string.
            (ssize,) = struct.unpack('<I', raw[soffset:soffset+4])
            soffset +=4
            str = raw[soffset:soffset+ssize]
            if str == b'flags':
              flagsIndex = sindex # Record the index of the 'flags' string so
                                  # we can identify references to it later.
            elif str == b'party':
              partyIndex = sindex # Same for the 'party' string.
            soffset+=ssize
            sindex += 1
          break # remove if you're interested in the rest of the chunks
        offset += csize

    # Find the GOBJ entry for 'party'. (I assume GOBJ stands for "game object")
    pregex = ''.join('\\x'+x.encode('hex') for x in struct.pack('<H',partyIndex))
    # the 7 '.' bytes are the size and a short I don't know the meaning of
    ppat = re.compile(b'GOBJ.......\x00'+pregex,re.DOTALL)
    match = re.search(ppat,raw)

    if not match:
      print 'ERROR: Party object not found. Aborting.'
      sys.exit(1)

    gstart = match.start()
    (gobjsize,) = struct.unpack('<I', raw[gstart+4:gstart+8])

    # Now find the PROP entry inside 'party' corresponding to 'flags'. There are
    # other entities with 'flags' entries so we do have to find the party
    # specifically - my first attempt hit the 'flags' entry for the 'sack' item,
    # which isn't really what we want.
    fregex = ''.join('\\x'+x.encode('hex') for x in struct.pack('<H',flagsIndex))
    fpat = re.compile(b'PROP\x05\x00\x00\x00\x00'+fregex+'\x02\x00',re.DOTALL)
    tpat = re.compile(b'PROP\x05\x00\x00\x00\x00'+fregex+'\x02\x02',re.DOTALL)
    toor = re.search(tpat,raw[gstart:gstart+gobjsize])

    if toor:
      print 'Untoorumifying...'
      raw = raw[:gstart+toor.start()+12] + '\x00' + raw[gstart+toor.start()+13:] # disable bit 1, PlayingAsToorum; other bits are unused in Grimrock 2    
    else:
      flags = re.search(fpat,raw[gstart:gstart+gobjsize])

      if not flags:
        print 'ERROR: Party flags entry not found. Aborting.'
        sys.exit(1)

      print 'Toorumifying...'
      raw = raw[:gstart+flags.start()+12] + '\x02' + raw[gstart+flags.start()+13:] # enable bit 1, PlayingAsToorum; other bits are unused in Grimrock 2    

    # write new save file
    newdata = b''.join([data[:8], zlib.compress(raw)])
    f = open(outfile,'w')
    f.write(newdata)
    f.close()

if len(sys.argv) != 3:
  print 'Usage: python ' + sys.argv[0] + ' [infile] [outfile]'
  print '[infile] should be a Legend of Grimrock 2 save game file.'
  print '[outfile] should be the filename to write the changed save to.'
  print 'outfile can be the same as infile to overwrite it - make a backup first!'
  sys.exit(1)
save(sys.argv[1], sys.argv[2])
Also included is a block you can paste into the console to imitate the rest of Toorum mode. Start a solo character with no traits or skills, make sure they're in slot 1, then paste this into the debug console and press enter to give them Toorum's name, portrait, sex, stats, skills, traits, and halved cooldowns (the same as the effect of Thunderstruck in the original game):

Code: Select all

party.party:getChampion(1):setName("Toorum")
 party.party:getChampion(1):setSex("male")
 party.party:getChampion(1):setRace("human")
 party.party:getChampion(1):setPortrait("assets/textures/portraits/toorum.tga")
 party.party:getChampion(1):setBaseStat("strength",18)
 party.party:getChampion(1):setBaseStat("dexterity",15)
 party.party:getChampion(1):setBaseStat("vitality",16)
 party.party:getChampion(1):setBaseStat("willpower",12)
 party.party:getChampion(1):trainSkill("armors",2,true)
 party.party:getChampion(1):trainSkill("earth_magic",1,true)
 party.party:getChampion(1):trainSkill("heavy_weapons",1,true)
 party.party:getChampion(1):trainSkill("light_weapons",1,true)
 party.party:getChampion(1):addTrait("skilled")
 party.party:getChampion(1):addSkillPoints(-1)
 party.party:getChampion(1):addTrait("tough")
 party.party:getChampion(1):upgradeBaseStat("cooldown_rate",100)
Credits: this work was informed by this script for Grimrock 1,

edit: update to 1.01: removed version assert since it changes every minor game version.
Last edited by minmay on Fri Mar 20, 2015 12:36 am, edited 2 times in total.
Grimrock 1 dungeon
Grimrock 2 resources
I no longer answer scripting questions in private messages. Please ask in a forum topic or this Discord server.
User avatar
sapientCrow
Posts: 608
Joined: Sun Apr 22, 2012 10:57 am

Re: Toorumifier 1.00

Post by sapientCrow »

NICE!
thank you for this minmay

do you think I could use a similar command line to change the exp_rate
such as:
party.party:getChampion(1):set exp_rate(400)

really appreciate you doing this.
it gives options to modify more directly.
minmay
Posts: 2780
Joined: Mon Sep 23, 2013 2:24 am

Re: Toorumifier 1.00

Post by minmay »

Sure,

Code: Select all

party.party:getChampion(1):setBaseStat("exp_rate",400)
works.
Grimrock 1 dungeon
Grimrock 2 resources
I no longer answer scripting questions in private messages. Please ask in a forum topic or this Discord server.
Palandus
Posts: 56
Joined: Wed Oct 29, 2014 11:20 am

Re: Toorumifier 1.00

Post by Palandus »

Couple of Questions:

1) Does it need to be the first character in a party that you Toorumify? If I wanted my 3rd character to be Toorum, would I change all the 1's to 3's. Ex. party.party:getChampion(1):setSex("male") to party.party:getChampion(3):setSex("male")?

2) What about a Toorum that is a different race? If I wanted to be ratling instead of human, would I replace this: party.party:getChampion(1):setRace("human") to party.party:getChampion(1):setRace("ratling")?

3) Can you give a race a different race's special race-specific trait? ie Giving the human Toorum the Mutation ability?

4) In your previous post a few days ago you stated that "cooldown_rate" needed to be set to 200, but here it is set to 100? Why?
User avatar
sapientCrow
Posts: 608
Joined: Sun Apr 22, 2012 10:57 am

Re: Toorumifier 1.00

Post by sapientCrow »

thanks!

I too would like to know if we can potentially add a trait from a different race to any race?

Also where can we pull a list of all the different commands that can be put into this?
minmay
Posts: 2780
Joined: Mon Sep 23, 2013 2:24 am

Re: Toorumifier 1.00

Post by minmay »

Palandus wrote:Couple of Questions:

1) Does it need to be the first character in a party that you Toorumify? If I wanted my 3rd character to be Toorum, would I change all the 1's to 3's. Ex. party.party:getChampion(1):setSex("male") to party.party:getChampion(3):setSex("male")?

2) What about a Toorum that is a different race? If I wanted to be ratling instead of human, would I replace this: party.party:getChampion(1):setRace("human") to party.party:getChampion(1):setRace("ratling")?

3) Can you give a race a different race's special race-specific trait? ie Giving the human Toorum the Mutation ability?
It doesn't need to be the first character. All 3 of those would work.
Palandus wrote:4) In your previous post a few days ago you stated that "cooldown_rate" needed to be set to 200, but here it is set to 100? Why?
In the previous post I used the equivalent of setBaseStat. Here I used upgradeBaseStat, which adds to the existing stat, which is 100. 100+100 = 200.
sapientCrow wrote:Also where can we pull a list of all the different commands that can be put into this?
Either wait for Almost Human to release the scripting reference, or look at this post if you're too impatient for that.
Grimrock 1 dungeon
Grimrock 2 resources
I no longer answer scripting questions in private messages. Please ask in a forum topic or this Discord server.
Palandus
Posts: 56
Joined: Wed Oct 29, 2014 11:20 am

Re: Toorumifier 1.00

Post by Palandus »

So if I desired to change stuff around, would I copy the stuff in the spoiler to a text file and rename its extension to .py afterwards?
minmay
Posts: 2780
Joined: Mon Sep 23, 2013 2:24 am

Re: Toorumifier 1.00

Post by minmay »

The stuff in the first spoiler is the script to change the party's movement speed, which needs to be run with your Python interpreter. The stuff in the second spoiler is for pasting into Grimrock 2's debug console; it's not Python. To enable the debug console in Grimrock you need the line "console = true" in your grimrock.cfg.
Grimrock 1 dungeon
Grimrock 2 resources
I no longer answer scripting questions in private messages. Please ask in a forum topic or this Discord server.
Palandus
Posts: 56
Joined: Wed Oct 29, 2014 11:20 am

Re: Toorumifier 1.00

Post by Palandus »

Okay, but how do you access the debug console? The typical ~ isn't working?
minmay
Posts: 2780
Joined: Mon Sep 23, 2013 2:24 am

Re: Toorumifier 1.00

Post by minmay »

The default key is \
Grimrock 1 dungeon
Grimrock 2 resources
I no longer answer scripting questions in private messages. Please ask in a forum topic or this Discord server.
Post Reply