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)
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])
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)
edit: update to 1.01: removed version assert since it changes every minor game version.