Ashemanu has asked me if Bash could be used to do something like batch edit a bunch of npc files to quickly add spells to them. The answer is yes and no. Bash itself? No. But bash has a command line counterpart (bish.py) which with a little modification could do the job pretty easily.

Unfortunately, I'm only one who knows it that well, I'm too busy to write custom scripts for folks. So instead, I'm going to give a brief primer on what's possible. This will most likely sink into the sands of Oblivion, but here it is...

Overview
While Bash is well known as an end user tool for manipulating mod and save files in various ways, it can also be useful for repetitive modding tasks -- if you don't mind doing a little python programming and working from the command line. When developing my Rational Names mod, I used it quite a bit to write short little functions to do a lot of the work for me. Since I've been asked about using Bash to copy spells to a bunch of new NPCs, I thought that I'd explain how to do this a little bit.

I'll start off with a nice simple program. After that, I'll scare the pants off you with horrible details that will make you love the construction set.

Sample Program
Here's a brief program that you can add to bish.py and run from the command line to print out the formids of all spells of all npcs in a given mod (I actually haven't tested this, but it should work):
CODE
def npcTweak(fileName=None):
    """Tweak npcs in misc. ways."""
    init(3)
    loadFactory= bosh.LoadFactory(True,bosh.NPC)
    modInfo = bosh.modInfos[fileName]
    modFile = bosh.ModFile(modInfo,loadFactory)
    modFile.load(True)
    for npc in modFile.NPC_.records:
        print npc.eid, npc.full
        for spell in npc.spells:
            print ' ', spell
    modFile.safeSave()
callables.add(npcTweak)

Once you've added that to bish.py, open the Windows command line tool, chdir to the mopy directory, and run it like so: bish npcTweak Oblivion.esm

Wasn't that easy? Now onto scary details...

Possibilities
* Change object names in a systematic way.
* Tweak npcs in just about any way you want (change faction, spells, race, face data, etc.)
* Tweak leveled lists in any way.
* Tweak/access string type properties (editor id, full name, model, icon) of most objects.

Difficulties
* You need to be able to program to some degree in python. Python is a great programming language, but it is programming. If you don't know at least a little about programming, this is not for you.
* bosh.py has 4300 lines of code. Granted, you don't have to understand all of it, but it's not nothing.
* Mod files are fairly complex, with a fair amount of cruft in them. Bash hides a fair amount of that mess from you, but it makes code more obscure to understand.
* Bash is pretty well documented, but I'm the only one who has used it so far, so I'm sure there's still plenty of obscurity.
* Bash uses some advanced python techniques in places. These can be obscure to people unfamiliar with the language. E.g. "[x for x in (1,2,3,4,5) if x % 2]" produces "[1,3,5]". If you're not familiar with advanced python, you probably just said "Huh?"
* Bash code is built to skip over most data in mod files. It actually only has a deep understanding of a few types of records (NPCs, leveled lists, Books). More types can be added with moderate ease, but you need to understand the records for that type of data (and apparently, modfiles have a moderate amount of cruft in them -- Oblivion seems to have changed record the record file formats as it developed. And sometimes the seem to have added stuff and then not used it.)
* Bash does not understand structured content (Dialogue and World Cell blocks) at all. Adding support for that would require a fair amount of work. (The problem here is that these types of records are combined in complex block structures which have to be handled correctly.)

Bash Files
Bash's python code comes in a few modules...
* bush.py: Definines miscelleaneous chunks of data. Don't worry about this.
* bosh.py: The "library" does most of heavy lifting of application. No GUI code.
* bish.py: A miscellaneous collection of command line programs/functions.
* basher.py: Almost all of the GUI code. 99% of Bash application is defined here.
* bash.py: A thin wrapper around basher.py. Basically takes stuff defined in basher.py and presses "Start".

Getting Started...
Hello, hello? Okay, I've scared the non-programmers away, but I think there's a few of you left... Anyway, to get started:
* copy bish.py to mybish.py (Don't want next Bash release to overwrite your code!)
* Mess around. Try not to break Oblivion.esm.

More hints:
* Get a decent, code understanding text editor. I use EditPlus, but there are tons of them around. In fact in a pinch, you can use Notepad.
* Have your python docs ready. Note that bash code is fairly documented with doc strings. There are also tools which will read python modules and spit out the docs for them in html format. Might be useful.

Wanted: Bash Coding Experts?
There's a lot of batch stuff that I can do very rapidly, but I'm not going to do it for everyone. I think that large projects might find that ability useful, so having a couple of extra people knowing it well enough to knock little functions out would be good. More hands/eyes would probably also mean that Bash's understanding of Mod file structure would be improved, which would be useful for everyone.
And here's some more pain for you... Going to Ashemanu's case (adding spells) you could do something like this...
CODE
def npcSpellCopy(fileName=None):
    """Copies spells from template npc to other npcs."""
    init(3)
    loadFactory= bosh.LoadFactory(True,bosh.NPC)
    modInfo = bosh.modInfos[fileName]
    modFile = bosh.ModFile(modInfo,loadFactory)
    modFile.load(True)
    #--Get source spells from template npc
    sourceNpc = modFile.NPC_.getRecord(0x1000CAE) #--Formid of template npc
    sourceSpells = sourceNPC.spells
    #--Loop over other npcs
    for npc in modFile.NPC_.records:
        print npc.eid, npc.full
        npcSpells = npc.spells
        for spell in sourceSpells:
            if spell not in npcSpells:
                npcSpells.append(spell)
                print ' ', spell
    modFile.safeSave()
callables.add(npcSpellCopy)

Now, that copies the spells to everyone, which is probably not what is desired. Some sort of filtration would be nice. This is left an an exercise for the student.

The code above is no doubt a bit confusing, so I'll give another example with lots of commenting... For my Rational Names mod, I wanted to rename the sigil stones. There's a bunch of them, so first thing for me to do was to copy the soulgems records from Oblivion.esm to my "Rational Names.esp" file. You'll find the original code for this in bish.py, but heres the same thing with lots of comments...
CODE
def importRecs(fileName=None):
    """Imports records from Oblivion into a mod. Used for Rational Names."""
    #--Initializes some data arrays by scanning mods and savegames.
    init(3)
    #--Mod
    #--Oblvion.esm and "Rational Names.esp" will both be represented as ModFile
    #  objects. But I need to tell the ModFile objects to only break down SGST
    #  records, and for those, just treat them as generic records (i.e. just as
    #  a collection of subrecords, with no real understanding of what those subrecords
    #  mean. Also I want to be able to save file when I'm done. So I create a
    #  LoadFactory object and tell it canSave == True and records to analyze == 'SGST'
    loadFactory= bosh.LoadFactory(True,'SGST')
    #--Now I go to my bosh.modInfos database and get the modInfo for the
    #  "Rational Names.esp" mod. A modinfo is just a chunk of data with a
    #  summary of info about the mod (directory, file name, masters, etc.). The
    #  point of it here is that I don't have to specify the full path, and modFile
    #  needs a modInfo rather than just the filename as it's argument.
    modInfo = bosh.modInfos[fileName]
    #--Create the ModFile representation...
    modFile = bosh.ModFile(modInfo,loadFactory)
    #--And tell it to load (i.e., actually read and analyze the mod file).
    modFile.load(True)
    #--Now modfile is full of data, mostly just large chunks of unanalyzed data,
    #  except for SGST records, which are broken down somewhat.
    #--Source (Oblivion)
    #--Now I do the same thing for Oblivion.esm...
    srcInfo = bosh.modInfos['Oblivion.esm']
    srcFile = bosh.ModFile(srcInfo,loadFactory)
    srcFile.load(True)
    #--Import All Sigil stones
    modSGST = modFile.SGST #--Just a shortcut to save a little typing.
    #--Now, loop over all sigil stone records in Oblivion.esm...
    for record in srcFile.SGST.records:
        #--Now, get the formid from oblivion.esm record and see if that's
        #  already in "Rational Names.esp". If not, then add it.
        if not modSGST.getRecord(record.formid):
            #--Okay, adding it...
            modSGST.setRecord(record.formid,record)
            #--And print a little message to command line showing formid and
            #  in game name of sigil stone.
            print 'importing',hex(record.formid),record.getSubString('FULL')
    #--Finally, save the changes.
    modFile.safeSave()
#--This is just a little glue that allows me to call the function from the
#  command line.
callables.add(importRecs)

To run this command, I open comand line tool, go to the Mopy directory and type: bish importRecs "Rational Names.esp"
QUOTE
This is left an an exercise for the student.

That phrase is the bane of all students. wink.gif
But Python looks like a fun language.
Excellent! I can't wait to get my hands dirty with this. It will probably make a couple things a little easier for me - plus OBSE doesn't know anything about mod files yet (we only deal with the in-memory representations.) I've got some ideas for this... biggrin.gif
Keep in mind that I'd be glad to merge changes back into bash.

Also, for file format record info (e.g. for records that aren't represented strongly in Bash), the place to go would be UESP. But it's having server problems while Dave Humphrey is out of town. I've had some luck pulling stuff from Google's cache (e.g., search for tes4mod:mod_file_format) however that doesn't always work (and right now is definitely having a problem with wiki subpages (which is most of the specific record data. Dang.)
I've taken a look at the format pages in the past - Dave (I believe) suggested they might be useful for decoding purposes.

I've wanted to try adding a new Magic Effect by creating a new entry in an esm and see if it is usable. The idea would be to try and mark it as needing a script effect. If this works we could add multiple new scriptable magic effects which could then have their own icons (say one for each current school at a minimum) so that people weren't stuck with the same icon for all scripted effects. Another use would be to create more conjuration functions. For some reason Bethesda coded multiple conjuration effects and stored the refid of the item to be summoned in the magic effect itself, rather than as a single summon effect with an override on the summoned object. Adding new effects could allow other objects to be summoned without scripting it.

I could probably create a new effect in memory in OBSE, but without getting it into an esm or esp, others couldn't use it.

There is also a long dormant python version of obse that will allow scripting more or less completely in python, which would allow significant improvements in script complexity and power.
QUOTE(Wrye @ Dec 28 2006, 10:54 PM) *

This will most likely sink into the sands of Oblivion, but here it is...


Hmmm... Having just got back into Herbert's 'Duniverse' some sand delving or providing some protection from the elements sits well with me. I don't know Python (only used Java, please forgive me), and had alot of trouble with getting it onto my comp... but this is just the sort of thing to provide an avenue for me to get into it... after Hogmanay of course.

Thanks Wrye.
QUOTE(Malbulga @ Dec 28 2006, 11:09 PM) *

Hmmm... Having just got back into Herbert's 'Duniverse' some sand delving or providing some protection from the elements sits well with me. I don't know Python (only used Java, please forgive me), and had alot of trouble with getting it onto my comp... but this is just the sort of thing to provide an avenue for me to get into it... after Hogmanay of course.

Thanks Wrye.

I've programmed in quite a few languages, but python is easily my favorite -- in fact (with a few exceptions, it's the only language I've been using recently). So, I went back and checked on a couple of python vs... references. Here's a couple:
* Python and Java Side by Side Comparison
* Why Python? by Eric S. Raymond

behippo: New mgef. I'd be surprised, but you never know. (I'd be surprised because they seem to be built in. E.g, the RSWD has a formid of 0, which is a sign of built-in-ness AFAIK.) But, definitely worth a try. Of course, messing with the exe in memory gives you more options.

OBSE and python: I remember reading about that -- I wonder about performance though. (However, I've been surprised by python's performance in the past, so I may be underestimating it again.)
QUOTE(Wrye @ Dec 28 2006, 10:46 PM) *

behippo: New mgef. I'd be surprised, but you never know. (I'd be surprised because they seem to be built in. E.g, the RSWD has a formid of 0, which is a sign of built-in-ness AFAIK.) But, definitely worth a try. Of course, messing with the exe in memory gives you more options.

yeah - and DUMY as well. DUMY is available in the Mehrunes Razor expansion - so I bet that esp has a non-0 formid. The other effects all have calid ids. So, I have hopes.
QUOTE

OBSE and python: I remember reading about that -- I wonder about performance though. (However, I've been surprised by python's performance in the past, so I may be underestimating it again.)

Ian says it shold be pretty fast. I am new to python myself - being a C++ programmer by trade. Looks like fun. Also, plenty of other games use python exclusively for scripting (Civ IV, Temple of Elemental Evil, V:TM Bloodlines) I imagine it will be on par with OBScript. We'll see.
Good stuff! I need to bone up on my python anyway. wink.gif
Since, there has been some interest... I'll bump with some minor notes.

String Translation: One of the things that's been on my to do list for a while (like a year and half) is improved support for non-English speaking users of Bash is better language support. Going through Bash code, you'll see almost all information strings are surrounded by _(). This is actually a setup to access a language string translating facility. Essentially the idea is that _() is supposed to be a function that takes the string argument and returns a equivalent string for the locale. This is actually pretty easy to do -- the main deal is that I just need to make it a little easier for foreign speakers to build up the string translation libraries. I know what I want to do and it should be too hard, just gotta find the time.

Path Class: A related issue is better path handling on non-English systems. Also something I'm overdue to fix is case-insensitive file name comparisons. To deal with both problems, I'm adding a Path class to represent all paths/file names. The problem here is that it's a pretty substantial refactoring of the code (paths and filenames are pervasive in Bash, not to mention stuff like directory listings, mtime access, file copy, etc.) So, I've done a fair amount of work on that, but there's still a fair amount to go. This refactoring will be included in next release of Bash.

Customizability: I kept customizability in mind while designing Bash (and Mash), however there wasn't a lot of interest, so it could probably be developed further. Rencently, I heard from Gez, who has customized the bottom launch bar to launch additional utilities. So I'll probably devote some further thought to this when I have time.

One way to customize right now would be: Copy bash.py to mybash.py. In my bash.py: import * from basher. Then define any new menu items, or launcher icons you want. Then define your updated versions of final assembly functions (e.g., InitLinks). Then tweak the final application launch code accordingly.
Note that for Bash's final assembly is done in the final functions.

That could probably be improved. I'll think about it when I get a chance, if there's enough interest.

More info, so it's not really a "bump", is it?? biggrin.gif

The COBL work has bumped me into refactoring code some more. So, next release of bash will be improved in a number of ways:
* Flags fields are handled better now -- you'll be able to define and access flags by name very easily thanks to a new Flags class.
* Run under either python 2.4 or 2.5.
* Better class names.
* And best of all, greater ease in adding support for more record types. I'm trying to do something like Enchanted Editor where you define class through a fairly simple definition, and all the basic read, write, mangle functions come automatically from that. Unlike EE, there isn't an external template file, but of course, python code itself is editable with fairly simple text editors, so it's pretty much the same thing.

To give an example, the MISC record type is defined as:
CODE
class MreMisc(MelRecord):
    """MISC (miscellaneous item) record."""
    type = 'MISC' #--Used by LoadFactory
    melSet = MelSet(
        MelString('EDID','eid'),
        MelString('FULL','full'),
        MelString('MODL','model'),
        MelBase('MODB','modb'),
        MelBase('MODT','modt'),
        MelString('ICON','icon'),
        MelFormid('SCRI','script'),
        MelStruct('DATA','if',('value','weight'),(0,0)),
        )

That's 13 lines. The prefactor version is 50 lines. Granted MISC is a pretty simple structure, but most of the others should be similarly reducible.

You may notice that this class actually doesn't have a single member function. Instead the work is done by the parent MelRecord class working in combination with the class constant "melSet" and its MelXXX element subrecords.

To decode a little... "MelSet" is an ordered set of Morrowind Record Elements. The elements of the set define the type of record, what object variable it maps to, and what the corresponding file subrecords are. Strings are for string subrecords, Base is for raw (usually unknown subrecords). The pick the complicated case the last one is a structure in which the 'DATA' subrecord is read as an intger and float ('if') which map respectively to instance members value and weight, which have respectively default values of 0 and 0.
New Version 0.41. Major Refactoring, Support for Many New Types
After a heck of a lot of work and testing, I've got the new version out. For the general user, it doesn't do much different then before, but the new refactoring of the code supports the relatively easy addition of most record types. (Note: Version bumped to 0.41 after fixing a Python 2.5 compatibility problem.)

With this release, the following classes provide definition for their corresponding types:
* MreActi: Activator
* MreAppa: Alchemical Apparatus
* MreBook: Book
* MreBsgn: Birthsign
* MreCont: Container
* MreFact: Faction
* MreGlob: Global
* MreGmst: GMST
* MreHair: Hair
* MreIngr: Ingredient
* MreLvlc: Leveled Creature List
* MreLvli: Leveled Item List
* MreLvsp: Leveled Spell List
* MreMisc: Miscellaneous Item
* MreKeym: Key
* MreNpc: NPC
* MreSlgm: Soulgem
* MreStat: Static

The other record types should be fairly easy to add since all types are now defined through structure elements (similar to Enchanted Editor templates, but much more flexible and structured). Note that though several types are not explained at UESP, I've found it quite simple to guess/test/model them using the bash's new structures/tools. If you're interested in seeing a particular type, just let me know and I'll take a shot when I have time.

NOTE: I don't yet have support for complex block types (cells, world, dialog). Dialogs probably wouldn't be too bad, but cell/world grouping is pretty complex and will take a bit of figuring.

To give you another taste, here's the full class definition for a book record:
CODE
class MreBook(MelRecord):
    """BOOK record."""
    type = 'BOOK'
    flags = Flags(0,Flags.getNames('isScroll','isFixed'))
    melSet = MelSet(
        MelString('EDID','eid'),
        MelString('FULL','full'),
        MelModel(),
        MelString('ICON','icon'),
        MelString('DESC','text'),
        MelFormid('SCRI','script'),
        MelFormid('ENAM','enchantment'),
        MelStruct('ANAM','H','enchantPoints'),
        MelStruct('DATA', '=BBif',(flags,'flags',0L),('teaches',0xFF),'value','weight'),
        )

To clarify a bit: MelModel defines a group (essentially a subobject) which maps to attribute 'model' and which has attributes 'path', 'modb' and 'modt'. The MelStruct at the end is unpacked into two bytes, and int and a float (the '=' is for bit alignment), the first btye is interpreted as a flags object instance (these some trickiness here, which I won't bore you with), and which has a default value of 0L (long integer). The second byte maps to 'teaches', and is given a default value of 0xFF (teaches nothing). The int and the float map to value and weight -- which have no special handling, and have defaults of zero, so I don't need to specify them.

And here's some code where I use that. Note how object fields match to definition above. (This function is used by Bash's merge data list to build the update versions of the alchemical catalogs. It's actually defined within a member function -- that's where the "self" comes from. ("self" is Python's version of "this".))
CODE
def getBook(objectId,eid,full,value,icon,modelPath,modb):
    #--New book and set some management fields for it.
    book = MreBook(('BOOK',0,0,0,0))
    book.longFormIds = True
    book.changed = True
    #--Book
    book.eid = eid
    book.full = full
    book.value = value
    book.weight = 0.2
    book.formid = (Path('Cobl Main.esm'),objectId) #--"Long" formid. Converted to hex later in code.
    book.text = _("Salan's Catalog of %s\r\n\r\n") % (full,) #--More text added later in code.
    book.icon = icon
    book.model = book.getDefault('model')
    book.model.path = modelPath
    book.model.modb = modb
    book.modb = book
    self.BOOK.setRecord(book.formid,book) #--Save book record in BOOK type data block.
    return book

So, that's it for now, if you were waiting until after the refactoring, it's done!
A faction relationship merger would be a good thing, though I don't think I could make one.

I see you have a class for hair, there could be one for eyes too. Then it would be possible to just make a simple code that would add all hair styles and all eyes to all races, allowing unwanted combinations but putting an end to the chore of adapting things like cosmetic compilation to a half-dozen new races...
QUOTE(Gez @ Jan 15 2007, 09:45 AM) *

A faction relationship merger would be a good thing, though I don't think I could make one.

This would actually be very simple to do and could be included in the Bash merge process. I still need to check back on Martigen's objections to it (I still haven't had time to review the COBL factions topic), but implementation is pretty simple.

QUOTE
I see you have a class for hair, there could be one for eyes too. Then it would be possible to just make a simple code that would add all hair styles and all eyes to all races, allowing unwanted combinations but putting an end to the chore of adapting things like cosmetic compilation to a half-dozen new races...

Well I added MreEyes (pretty trivial) but that's actually not relevant. I need an MreRace class to do cosmetics merging. I started analyzing the RACE record last night, but it's fairly complicated and it seems Bethesda programmers found yet another way to encode data.

In general, I have been thinking about more complicated merging. Not as vanilla as TesTool's object merging for Morrowind, but maybe something more directed. No promises on any of thes, but potential merging might include:
* NPC levels (Bash already has this in one form, of course)
* Face data (for the that mod that tweaks lots of faces to add more character)
* Names? (already done to some degree, but something to keep in mind)

Note that there's a limit to what can be automatically merged. For example you can merge new weapons into the leveled lists so that they show up along OOO changes. But OOO changes the nature of weapons too (weight/speed balances, etc.), so if you're really doing a OOO merge, you need to tweak those as well, and that pretty much has to be done manually. Of course, if you could define an algorithm, a programmatic tweak might be possible.

Started a new topic for this: [WIPZ] Based Raced (oops, typo -- was supposd to be "Bashed RaceS". Ah well...
As of version 0.42 (just released) Bash has support for even more record types (including RACE, which was true pain in the butt to do). The inclusion of RACE means that merging in eyes and hair would be pretty trivial with a command line script.
QUOTE
The inclusion of RACE means that merging in eyes and hair would be pretty trivial with a command line script.

That's great news!
QUOTE(scruggsywuggsy the ferret @ Jan 25 2007, 10:27 PM) *
That's great news!

Continued on Bashed Races topic.
A few people (Gez, Abot) have been extending bash a little bit on their own by editing the files as released. This actually wasn't necessary, you can extend the application quite easily without modifying any of them. However, with current release (0.44), I've made that just a bit easier by tweaking menu creation slighly.

So here's an example in which a "Hello World" message is added under the "Version 0.8" menu item for mod list items...

First thing to do is duplicate bash.py to something like "mybash.py". There's very little code it in and it's very rarely updated, so you'll rarely have to adapt it to newer bash releases. Then in mybash.py, you add a new menu item definition, and then you throw in a little code to add the new menu item in under one of the existing menu items. Like so...
CODE
# Imports ---------------------------------------------------------------------
import getopt
import os
import sys
if sys.version[:3] == '2.4':
    import wxversion
    wxversion.select("2.5.3.1")
import bosh, basher

#------------------------------------------------------------------------------
from basher import _, wx
class HelloWorld(basher.Link):
    """Hello World link."""
    def __init__(self,menuText,text):
        basher.Link.__init__(self)
        self.menuText = menuText
        self.text = text
    def AppendToMenu(self,menu,window,data):
        basher.Link.AppendToMenu(self,menu,window,data)
        menu.AppendItem(wx.MenuItem(menu,self.id,self.menuText))
    def Do(self,event):
        basher.Message(self.window,self.text)

# Main ------------------------------------------------------------------------
if __name__ == '__main__':
    #--Parse arguments
    optlist,args = getopt.getopt(sys.argv[1:],'u:')
    optdict = dict(optlist)
    if '-u' in optdict:
        drive,path = os.path.splitdrive(optdict['-u'])
        os.environ['HOMEDRIVE'] = drive
        os.environ['HOMEPATH'] = path
    #--Initialize Directories
    #os.environ['HOMEPATH'] = r'\Documents and Settings\Wrye' #--In case of registry problems.
    bosh.initDirs()
    #--More Initialization
    basher.InitSettings()
    basher.InitLinks()
    basher.InitImages()
    #--My stuff
    point = basher.modsItemMenu.getClassPoint(basher.Mod_SetVersion)
    point.append(HelloWorld('Hello World','Hello Wonderful World!'))
    #--Start application
    if args and args[0] == '0':
        app = basher.BashApp(0)
    else:
        app = basher.BashApp()
    app.MainLoop()

The new parts here are the HelloWold class, and the two lines under "My Stuff" in the main function.

What's been tweaked in 0.44 is:
* menus are now more easily accessible (e.g. basher.modsItemMenu)
* You can use the getClassPoint function to get a point in the current menu, by finding a class in it (most menu items are unique classes). You can then do things like:
point.remove()
point.replace(newItem)
point.append(newItem)

If you want to do something complicated, generally you'll want to do it as a command line function first, and then, if you want, convert that to a menu command. Understanding menu commands is a little complicated, but usually you can do pretty well by following an existing menu item's code.

When testing your new command, you'll almost certainly need to be ready to run from command line (e.g., "mbash 0") for debugging purposes.


Interesting. Well, all I want to do is adding new launch buttons. So, for instance, basher.py contains the following code:

CODE
# App Links -------------------------------------------------------------------
#------------------------------------------------------------------------------
class App_Oblivion(Link):
    """Launch Oblivion."""
    def GetBitmapButton(self,window,style=0):
        if not self.id: self.id = wx.NewId()
        button = wx.BitmapButton(window,self.id,images['oblivion'].GetBitmap(),style=style)
        button.SetToolTip(wx.ToolTip(_("Launch Oblivion")))
        wx.EVT_BUTTON(button,self.id,self.Do)
        return button

    def Do(self,event):
        cwd = os.getcwd()
        os.chdir(bosh.dirs['app'])
        os.spawnl(os.P_NOWAIT,os.path.join(bosh.dirs['app'],'Oblivion.exe'))
        os.chdir(cwd)

#------------------------------------------------------------------------------
class App_ObMM(Link):
    """Launch Oblivion Mod Manager."""
    def GetBitmapButton(self,window,style=0):
        if not self.id: self.id = wx.NewId()
        button = wx.BitmapButton(window,self.id,images['obmm'].GetBitmap(),style=style)
        button.SetToolTip(wx.ToolTip(_("Launch Oblivion Mod Manager")))
        wx.EVT_BUTTON(button,self.id,self.Do)
        return button

    def Do(self,event):
        cwd = os.getcwd()
        os.chdir(bosh.dirs['app'])
        os.spawnl(os.P_NOWAIT,os.path.join(bosh.dirs['app'],'OblivionModManager.exe'))
        os.chdir(cwd)
#------------------------------------------------------------------------------
class App_TESCS(Link):
    """Launch TES Construction Set."""
    def GetBitmapButton(self,window,style=0):
        if not self.id: self.id = wx.NewId()
        button = wx.BitmapButton(window,self.id,images['tescs'].GetBitmap(),style=style)
        button.SetToolTip(wx.ToolTip(_("Launch TES Construction Set")))
        wx.EVT_BUTTON(button,self.id,self.Do)
        return button

    def Do(self,event):
        cwd = os.getcwd()
        os.chdir(bosh.dirs['app'])
        os.spawnl(os.P_NOWAIT,os.path.join(bosh.dirs['app'],'TESConstructionSet.exe'))
        os.chdir(cwd)

#------------------------------------------------------------------------------
class App_Help(Link):
    """Show help browser."""
    def GetBitmapButton(self,window,style=0):
        if not self.id: self.id = wx.NewId()
        button = wx.BitmapButton(window,self.id,images['help'].GetBitmap(),style=style)
        button.SetToolTip(wx.ToolTip(_("Help File")))
        wx.EVT_BUTTON(button,self.id,self.Do)
        return button

    def Do(self,event):
        """Handle menu selection."""
        if not helpBrowser:
            HelpBrowser().Show()
            settings['bash.help.show'] = True
        helpBrowser.Raise()

# Initialization --------------------------------------------------------------
def InitSettings():
    bosh.initSettings()
    global settings
    settings = bosh.settings
    settings.loadDefaults(settingDefaults)

def InitImages():
    #--Standard
    images['save.on'] = Image(r'images\save_on.png',wx.BITMAP_TYPE_PNG)
    images['save.off'] = Image(r'images\save_off.png',wx.BITMAP_TYPE_PNG)
    #--Misc
    images['oblivion'] = Image(r'images\oblivion.png',wx.BITMAP_TYPE_PNG)
    images['obmm'] = Image(r'images\obmm.png',wx.BITMAP_TYPE_PNG)
    images['tescs'] = Image(r'images\tescs.png',wx.BITMAP_TYPE_PNG)
    images['help'] = Image(r'images\help.png',wx.BITMAP_TYPE_PNG)
    #--Tools
    images['doc.on'] = Image(r'images\doc_on.png',wx.BITMAP_TYPE_PNG)
    #--Checkboxes
    images['bash.checkboxes'] = Checkboxes()
    images['checkbox.green.on.32'] = (
        Image(r'images\checkbox_green_on_32.png',wx.BITMAP_TYPE_PNG))
    images['checkbox.blue.on.32'] = (
        Image(r'images\checkbox_blue_on_32.png',wx.BITMAP_TYPE_PNG))
    #--Bash
    images['bash.16'] = Image(r'images\bash_16.png',wx.BITMAP_TYPE_PNG)
    images['bash.32'] = Image(r'images\bash_32.png',wx.BITMAP_TYPE_PNG)
    images['bash.16.blue'] = Image(r'images\bash_16_blue.png',wx.BITMAP_TYPE_PNG)
    images['bash.32.blue'] = Image(r'images\bash_32_blue.png',wx.BITMAP_TYPE_PNG)
    #--Applications Icons
    wryeBashIcons = ImageBundle()
    wryeBashIcons.Add(images['bash.16'])
    wryeBashIcons.Add(images['bash.32'])
    images['bash.icons'] = wryeBashIcons
    #--Application Subwindow Icons
    wryeBashIcons2 = ImageBundle()
    wryeBashIcons2.Add(images['bash.16.blue'])
    wryeBashIcons2.Add(images['bash.32.blue'])
    images['bash.icons2'] = wryeBashIcons2

def InitLinks():
    #--Bash Status/LinkBar
    BashStatusBar.links.append(App_Oblivion())
    BashStatusBar.links.append(App_ObMM())
    BashStatusBar.links.append(App_TESCS())
    BashStatusBar.links.append(App_Help())


I'm not sure how I'd achieve the same result by editing bash instead.

I'm not a python monkey, so I'm just coding by copy/pasting stuff and modifying thingies. Just the most obvious stuff. I'm not sure if it's really simpler to use points and indirections.
Here's how you would do it. So, create your gezbash.py and use this for its code:
CODE
# Imports ---------------------------------------------------------------------
import getopt
import os
import sys
if sys.version[:3] == '2.4':
    import wxversion
    wxversion.select("2.5.3.1")
import bosh, basher

# Gez -------------------------------------------------------------------------
from basher import *

#------------------------------------------------------------------------------
class App_ObMM(Link):
    """Launch Oblivion Mod Manager."""
    def GetBitmapButton(self,window,style=0):
        if not self.id: self.id = wx.NewId()
        button = wx.BitmapButton(window,self.id,images['obmm'].GetBitmap(),style=style)
        button.SetToolTip(wx.ToolTip(_("Launch Oblivion Mod Manager")))
        wx.EVT_BUTTON(button,self.id,self.Do)
        return button

    def Do(self,event):
        cwd = os.getcwd()
        os.chdir(bosh.dirs['app'])
        os.spawnl(os.P_NOWAIT,os.path.join(bosh.dirs['app'],'OblivionModManager.exe'))
        os.chdir(cwd)

#------------------------------------------------------------------------------
class App_TESCS(Link):
    """Launch TES Construction Set."""
    def GetBitmapButton(self,window,style=0):
        if not self.id: self.id = wx.NewId()
        button = wx.BitmapButton(window,self.id,images['tescs'].GetBitmap(),style=style)
        button.SetToolTip(wx.ToolTip(_("Launch TES Construction Set")))
        wx.EVT_BUTTON(button,self.id,self.Do)
        return button

    def Do(self,event):
        cwd = os.getcwd()
        os.chdir(bosh.dirs['app'])
        os.spawnl(os.P_NOWAIT,os.path.join(bosh.dirs['app'],'TESConstructionSet.exe'))
        os.chdir(cwd)

# Initialization --------------------------------------------------------------
def InitGez():
    #--Images
    images['obmm'] = Image(r'images\obmm.png',wx.BITMAP_TYPE_PNG)
    images['tescs'] = Image(r'images\tescs.png',wx.BITMAP_TYPE_PNG)
    #--Bash Status/LinkBar
    point = bashStatusBar.getClassPoint(App_Oblivion)
    point.append(App_ObMM())
    point.append(App_TESCS())

# Main ------------------------------------------------------------------------
if __name__ == '__main__':
    #--Parse arguments
    optlist,args = getopt.getopt(sys.argv[1:],'u:')
    optdict = dict(optlist)
    if '-u' in optdict:
        drive,path = os.path.splitdrive(optdict['-u'])
        os.environ['HOMEDRIVE'] = drive
        os.environ['HOMEPATH'] = path
    #--Initialize Directories
    #os.environ['HOMEPATH'] = r'\Documents and Settings\Wrye' #--In case of registry problems.
    bosh.initDirs()
    #--More Initialization
    basher.InitSettings()
    basher.InitLinks()
    basher.InitImages()
    #--My stuff
    InitGez()
    #--Start application
    if args and args[0] == '0':
        app = basher.BashApp(0)
    else:
        app = basher.BashApp()
    app.MainLoop()

I haven't tested this, so there might be a bug, but it's pretty simple. Essentially the only changes
1) Add the gez section with your two new button classes and the InitGez function.
2) Edit the main bash loop to run InitGez() just after running the regular bash init code.

If you were making a lot of additions, you might want to isolate your own code into a separate file. In that case, just take the whole Gez section and stick in in your own code file (gezzer.py, I guess). Then the only change you need to make to bash.py is add:
CODE
import gezzer

with the rest of the imports. And then do the initialization with
CODE
gezzer.InitGez()

in the same place as above.
QUOTE(Wrye @ Jan 28 2007, 07:38 PM) *

* menus are now more easily accessible (e.g. basher.modsItemMenu)
* You can use the getClassPoint function to get a point in the current menu, by finding a class in it (most menu items are unique classes). You can then do things like:
point.remove()
point.replace(newItem)
point.append(newItem)


Awesome! This looks very useful. Need to start playing with this stuff.
There are two Batch functions I really need for "Lore Dialogue 300" the first one is a double-clickable Windows bats file that will copy a silent.mp3 and create all of the folders used by the .esp so that all of the Dialogue will stay on screen long enough to be read.

Here is an example of ac38 what I mean, but this only includes very little:
CODE
COPY "Silence.mp3" ".\Sound\Voice\300_Lore Dialogue.esp\Argonian\F\LoreDialogueQuest_LoreBackground_00000CE9_2.mp3"
COPY "Silence.mp3" ".\Sound\Voice\300_Lore Dialogue.esp\Argonian\F\LoreDialogueQuest_LoreBackground_00000CE9_3.mp3"
COPY "Silence.mp3" ".\Sound\Voice\300_Lore Dialogue.esp\Argonian\F\LoreDialogueQuest_LoreBackground_00000CE9_4.mp3"
COPY "Silence.mp3" ".\Sound\Voice\300_Lore Dialogue.esp\Argonian\F\LoreDialogueQuest_LoreBackground_00000CEA_1.mp3"
COPY "Silence.mp3" ".\Sound\Voice\300_Lore Dialogue.esp\Argonian\F\LoreDialogueQuest_LoreBackground_00000CEA_2.mp3"
COPY "Silence.mp3" ".\Sound\Voice\300_Lore Dialogue.esp\Argonian\F\LoreDialogueQuest_LoreBackground_00000CEA_3.mp3"
COPY "Silence.mp3" ".\Sound\Voice\300_Lore Dialogue.esp\Argonian\F\LoreDialogueQuest_LoreBackground_00000CEA_4.mp3"
COPY "Silence.mp3" ".\Sound\Voice\300_Lore Dialogue.esp\Argonian\F\LoreDialogueQuest_LoreBackground_00000CEB_1.mp3"
COPY "Silence.mp3" ".\Sound\Voice\300_Lore Dialogue.esp\Argonian\F\LoreDialogueQuest_LoreBackground_00000CEB_2.mp3"
COPY "Silence.mp3" ".\Sound\Voice\300_Lore Dialogue.esp\Argonian\F\LoreDialogueQuest_LoreBackground_00000CEB_3.mp3"
COPY "Silence.mp3" ".\Sound\Voice\300_Lore Dialogue.esp\Argonian\F\LoreDialogueQuest_LoreBackground_00000CEC_1.mp3"
COPY "Silence.mp3" ".\Sound\Voice\300_Lore Dialogue.esp\Argonian\F\LoreDialogueQuest_LoreBackground_00000CED_1.mp3"


I also need a way to batch-rename Dialogue Topics. When they are made the Dialogue Topic's Name is automaitically its FormID which looks very "yucky" in-game. So for the Background Topic, it will show up as LoreBackground, in-game. I jiust need something that can search+replace the Dialogue Topics to remoave the wrod "Lore" form the beginning of Each.
QUOTE(navy_gurl_boyd @ Mar 13 2007, 09:21 AM) *

I also need a way to batch-rename Dialogue Topics. When they are made the Dialogue Topic's Name is automaitically its FormID which looks very "yucky" in-game. So for the Background Topic, it will show up as LoreBackground, in-game. I jiust need something that can search+replace the Dialogue Topics to remoave the wrod "Lore" form the beginning of Each.


Did you already find a way around this problem?
QUOTE(dev_akm @ Mar 13 2007, 01:37 PM) *

Did you already find a way around this problem?


No, the only thing I could do is manually change the name of each topic one by one. Which I did for the race topics.
Sorry, missed this earlier since the topic fell off my watchlist. (If you mention "wrye" in the post, I'll likely see it very quickly.)

Bash doesn't yet support dialogs, cells or worldspaces, but I'm getting to thinking about it. Once dialogs is in, it could do this sort of thing (main problem with dialogs is the formids in their compiled scripts are pretty tweaky).

Maybe sometime this week, if there's a demand.

It's possible (though no guess as to how probable) that beth will fix the silent dialog problem in the upcoming patch. It's probably about five lines of code for them...
Dial renaming is now supported. Here's a brief function I did to test the feature. The code here changes "NN" to "nn". You could instead search for "^Lore" and replace with "" (empty string).

CODE
def temp(fileName=None):
    init(3)
    loadFactory = bosh.LoadFactory(True,'INFO',bosh.MreDial)
    modInfo = bosh.modInfos[Path(fileName)]
    modFile = bosh.ModFile(modInfo,loadFactory)
    modFile.load(True)
    reNN = re.compile('^NN')
    for dial in modFile.DIAL.records:
        dial.full = reNN.sub('nn',dial.full)
        print dial.eid, dial.full
        dial.setChanged()
    modFile.safeSave()
callables.add(temp)


Edit: Oops. Fix to mangle 'full' instead of eid. Take out some reporting stuff.
Thanks for the update Wrye!

Even though your update is a little too late to be used for why I needed it in the first place - I am sure it will come in handy later!

QUOTE(navy_gurl_boyd @ Mar 22 2007, 06:33 AM) *
Even though your update is a little too late to be used for why I needed it in the first place - I am sure it will come in handy later!

Ahh well. Just means that things like dialog import/export and silent mp3 generation are closer. Plus I've got a better idea of how to do cells (similar, but a bit more complex).
QUOTE(Wrye @ Mar 22 2007, 12:06 AM) *
Dial renaming is now supported. Here's a brief function I did to test the feature. The code here changes "NN" to "nn". You could instead search for "^Lore" and replace with "" (empty string).


Very, very cool, man.

This gives a great amount of flexibility with support for regex.

Bash is rapidly becoming an essential tool for mod-makers.

Do you already maintain a list of the record types you support for functions like this?

Might be useful to start accumulating more of this info on the CS Wiki so we can start expanding the usage examples and such with contributions from other folks.
QUOTE(dev_akm @ Mar 22 2007, 01:13 PM) *
Do you already maintain a list of the record types you support for functions like this?

Might be useful to start accumulating more of this info on the CS Wiki so we can start expanding the usage examples and such with contributions from other folks.

I think that about 80% of record types are covered. Basically thing to do is to look in bosh.py in text editor. Look for section starting "# Mod Records 1" -- everything under that is the class definition for the corresponding record type (e.g., MreDial for DIAL records).

Actually, I have the list in the code (for another purpose). It's:
CODE
    MreActi, MreAlch, MreAmmo, MreAnio, MreAppa, MreArmo, MreBook, MreBsgn,
    MreClot, MreCont, MreDoor, MreEnch, MreEyes, MreFact, MreFlor, MreFurn,
    MreGlob, MreGmst, MreHair, MreIngr, MreKeym, MreLigh, MreLvlc, MreLvli,
    MreLvsp, MreMisc, MreNpc, MreRace, MreScpt, MreSgst, MreSlgm, MreSpel,
    MreStat, MreTes4, MreWeap,

You'll notice for instance that MreInfo isn't in there. Records that aren't specifically supported can be handled as unanalyzed chunks of data by MreRecord. BTW, class hierarchy is:
MreRecord: Basic record class
--MelRecord: Record with structure defined by a MelSet.
----MreActi (etc.): Specific record types defined by MelSets.

To understand the data for specific subclass, you'll have understand a little how melsets work and then just look at the MelSet def for the class. For instance for Dials (pretty simple for own structure, complexity comes from also acting as container of INFO records):
CODE
    melSet = MelSet(
        MelString('EDID','eid'),
        MelFormids('QSTI','quests'),
        MelString('FULL','full'),
        MelStruct('DATA','B','dialType'),
    )

So, for an MreDial instance, named 'dial'
dial.eid: editor id as string
dial.quests: list of formids (e.g., dial.quests >> [0x01012345,0x01012346,...]
dial.full: full name as string
dial.dialType: dialog type as integer ('B' == unsigned byte)

For newbies, it may be useful to generate html docs for b*.py files. You can do that by opening command shell and:
chdir [whatever]\Oblivion\Mopy
pydoc -w bosh bush basher wtxt bish

CsWiki. Probably your'e right. I'm a little short of time to do that though. Maybe just a starter, or feel free to start w/o me.
QUOTE(Wrye @ Mar 22 2007, 01:52 PM) *
I think that about 80% of record types are covered. Basically thing to do is to look in bosh.py in text editor. Look for section starting "# Mod Records 1" -- everything under that is the class definition for the corresponding record type (e.g., MreDial for DIAL records).

Actually, I have the list in the code (for another purpose). It's:

...

CsWiki. Probably your'e right. I'm a little short of time to do that though. Maybe just a starter, or feel free to start w/o me.


Awesome! Great info there, man. Thanks for that!

You have more important things to do, for sure. I'll start accumulating some of this stuff in the CS Wiki as I get time and poke you occasionally for review. Sound good?
QUOTE(dev_akm @ Mar 23 2007, 02:24 PM) *
You have more important things to do, for sure. I'll start accumulating some of this stuff in the CS Wiki as I get time and poke you occasionally for review. Sound good?

That'll work.
Did you know that the forum subscription software drops subscriptions when there's no reply after 5 days? And that the only way to get it back in your subscription list is to add a new otherwise useless post? :sigh:
I need a utility or something similar to open a nif, read all of its texture nodes and replace the directory (while preservig the textures' name) to a specific directory.

The purpose if this is to allow mass retextures, for new tilesets etc. Do you think Bash could be used for this?
I have no experience there and bash knows nothing about nif files. However, I've noticed that niftools uses python -- so it MAY be possible to get that niftools library, write some fairly simple python script and be done with it. Maybe. You should check with niftools folks.
QUOTE(Wrye @ Mar 31 2007, 05:48 PM) *
I have no experience there and bash knows nothing about nif files. However, I've noticed that niftools uses python -- so it MAY be possible to get that niftools library, write some fairly simple python script and be done with it. Maybe. You should check with niftools folks.


Alright thanks smile.gif
QUOTE
For example you want to set up an out-door shop, or market area - how could you set the ownership of all of the objects in that cell - without doing it one item at a time?


Which gives me a new idea for a bash function, how about settingf the ownership of everything that can be given an ownership in an entire cell or worldspace.smile.gif
Hmm... Bash doesn't understand cells or worldspaces yet, so that would require a bit of work. Then there's the issue of recognizing whether a given ref CAN be owned -- means you have to know what type of ref it is (e.g., a container, not a static) which would require a bit of work.

And can't you already do this for cells? Justs select everything and set? (I have no idea myself -- that worked for MW TESCS, might not work for Oblivion TESCS.)
QUOTE(Wrye @ Apr 4 2007, 06:02 PM) *
Hmm... Bash doesn't understand cells or worldspaces yet, so that would require a bit of work. Then there's the issue of recognizing whether a given ref CAN be owned -- means you have to know what type of ref it is (e.g., a container, not a static) which would require a bit of work.

And can't you already do this for cells? Justs select everything and set? (I have no idea myself -- that worked for MW TESCS, might not work for Oblivion TESCS.)



In Morrowind, yes - in Oblivion no, which making things in exteriors incredibly tedious.
QUOTE(navy_gurl_boyd @ Apr 4 2007, 05:35 PM) *
In Morrowind, yes - in Oblivion no, which making things in exteriors incredibly tedious.

Wonderful. Okay, I'll put it on my "maybe" list. (Like I said, it would still take a bit of work to implement.)

Have you looked at Dave Humphrey's ObEdit? It has some batch command processing, but I don't know if it handles cell records.
Dammit it seems like I am the best at finding out what bash cant do sad.gif
Another Idea...

How about... somthing like Split Inifinity?

Like.... You make a script which contains specific changes to a set of Form Ids. For example you can make one that will increase the Damage of all of the weapons in Oblivion.esm by +2. Or you could make a duplicate of each Weapon Form and increase that duplicates' Weight by +2 and then add a prefix to the weapons' display name "Heavy". Know what im saying? The same could be done to apply enchantments to weapons and armor, duplicate all cuirasses and apply Enchantment A to all of the dups and then rename the dups to be Enchanted Cuirass.
QUOTE(navy_gurl_boyd @ Apr 11 2007, 02:13 PM) *
Like.... You make a script which contains specific changes to a set of Form Ids. For example you can make one that will increase the Damage of all of the weapons in Oblivion.esm by +2. Or you could make a duplicate of each Weapon Form and increase that duplicates' Weight by +2 and then add a prefix to the weapons' display name "Heavy". Know what im saying? The same could be done to apply enchantments to weapons and armor, duplicate all cuirasses and apply Enchantment A to all of the dups and then rename the dups to be Enchanted Cuirass.

That you can do. Essentially you would:
# load source mod (e.g., Oblivion.esm) and set load factory to analyze weapons:
# load new mod (the one that will contain new/modified weapons, and again set to analyze weapons)
# loop over weapons from source mod and select the ones you want to modify.
# copy the selected weapon
# give the copy a new formid
# Change record.full to new name
# Change damage and/or add enchantment
# Maybe tweak the price (old price * scale or maybe plus extra cost)

(PS: Again, it helps to mention my name in the post if it's been more than 5 days since there was last a post in it.)
WRYE:

Any idea where I should start? Is there any basic tutorials?
QUOTE(navy_gurl_boyd @ Apr 16 2007, 01:12 PM) *
WRYE:

Any idea where I should start? Is there any basic tutorials?

Here's a pretty good start:
CODE
def copyWeapons(fileName=None):
    """Copies weapons from Oblivion.esm into mod fileName."""
    init(3)
    #--Mod
    fileName = Path(fileName)
    loadFactory= bosh.LoadFactory(True,MreWeap)
    modInfo = bosh.modInfos[fileName]
    modFile = bosh.ModFile(modInfo,loadFactory)
    modFile.load(True)
    tes4 = modFile.tes4
    #--Source (Oblivion)
    loadFactory= bosh.LoadFactory(False,MreWeap)
    srcInfo = bosh.modInfos[Path('Oblivion.esm')]
    srcFile = bosh.ModFile(srcInfo,loadFactory)
    srcFile.load(True)
    srcMapper = srcFile.getLongMapper()
    #--Do import, selective copy and modify
    modFile.WEAP.convertFormids((modFile.getLongMapper(),True)
    for weapon in srcFile.WEAP.records:
        #--Add some code to skip over weapons you're not intested in. (See MreWeap)
        #--Assuming you're going to copy...
        weapon = weapon.getTypeCopy(srcMapper)
        weapon.formid = (fileName,tes4.getNextObject())
        modFile.WEAP.setRecord(weapon.formid,weapon)
        #--Add some code to tweak the weapon as desired (again, see MreWeap)
        weapon.weight = 100 #-- Or whatever
    modFile.WEAP.convertFormids(modFile.getShortMapper(),False)
    modFile.safeSave()
callables.add(copyWeapons)

Be aware that I haven't tested this code. There may be some typos or flaws, but that most of the framework of what you need to do. Mostly you need to add code within the loop to select only the weapons that you're interested in and then tweak them as desired.

The code above is most of the heavy lifting, but you still need to understand python well enough and understand the MreWeap record well enough to tweak it. Look back into this topic for earlier tips and explanations and poke around in bosh and bish code to for clarification. (Search is very useful for looking around in bosh.py -- it's pretty big.)

The rest is left as an exercise for the student. biggrin.gif
This is a Very Interesting Topic This should be placed in some type of wiki for oblivion editing of some type.
okay I copied all the information from the bosh.py and created a new one called mybosh.py
I yanked out all of the duplicated informations for the bash patch and created a new field Game Settings2
I cant get it to run but when i go and right click on the bash patch and click update my new menu i named game settings2 does not appear on the list.

It compiles right and bash loads with no problems but it does not show my changes or give a error report

I did a search for bosh and everywhere that there is a import function

CODE
Import bosh

I added
CODE
Import mybosh


Everywheres that I found
CODE
import bosh, basher

I added
CODE
import mybosh, basher


Everywheres that I found
CODE
import bosh, bush
from bosh import _, Path

I added
CODE
import mybosh, bush
from mybosh import _, Path

I am currently rewriting this so this is still unfinsihed but shoudl still be fully functional.
CODE
class GmstTweak(MultiTweakItem):
#--Patch Phase ------------------------------------------------------------
def buildPatch(self,patchFile,keep,log):
"""Build patch."""
eids = ((self.key,),self.key)[isinstance(self.key,tuple)]
for eid,value in zip(eids,self.choiceValues[self.chosen]):
gmst = MreGmst(('GMST',0,0,0,0))
gmst.eid,gmst.value,gmst.longFormids = eid,value,True
formid = gmst.formid = gmst.getOblivionFormid()
patchFile.GMST.setRecord(keep(formid),gmst)
if len(self.choiceLabels) > 1:
log('* %s: %s' % (self.label,self.choiceLabels[self.chosen]))
else:
log('* ' + self.label)

class GmstTweaker(MultiTweaker):
"""Tweaks miscellaneous gmsts in miscellaneous ways."""
group = _('Tweakers')
name = _('Game Settings2')
text = _("Modify miscellaneous game settings.")
tweaks = sorted([
GmstTweak(_('Arrow Litter Count'),
_("Maximum number of spent arrows allowed in cell."),
'iArrowMaxRefCount',
('50',50),
('100',100),
('500',500),
),
GmstTweak(_('Arrow Litter Time'),
_("Time before spent arrows fade away from cells and actors."),
'fArrowAgeMax',
(_('2 Minutes'),120),
(_('3 Minutes'),180),
(_('5 Minutes'),300),
(_('10 Minutes'),600),
(_('30 Minutes'),1800),
(_('1 Hour'),3600),
),
GmstTweak(_('Arrow Recovery from Actor'),
_("Chance that an arrow shot into an actor can be recovered."),
'iArrowInventoryChance',
('70%',70),
('80%',80),
('90%',90),
('100%',100),
),
GmstTweak(_('Arrow Speed'),
_("Speed of full power arrow."),
'fArrowSpeedMult',
(_('x 1.4'),1500*1.4),
(_('x 1.6'),1500*1.6),
(_('x 1.8'),1500*1.8),
(_('x 2.0'),1500*2.0),
(_('x 2.2'),1500*2.2),
(_('x 2.4'),1500*2.4),
(_('x 2.6'),1500*2.6),
(_('x 2.8'),1500*2.8),
(_('x 3.0'),1500*3.0),
),
GmstTweak(_('Chase Camera Tightness'),
_("Tightness of chase camera to player turning."),
('fChase3rdPersonVanityXYMult','fChase3rdPersonXYMult'),
(_('x 2.0'),8,8),
(_('x 3.0'),12,12),
(_('x 5.0'),20,20),
),
GmstTweak(_('Chase Camera Distance'),
_("Distance camera can be moved away from PC using mouse wheel."),
('fVanityModeWheelMax', 'fChase3rdPersonZUnitsPerSecond','fVanityModeWheelMult'),
(_('x 2'), 600*2, 300*2, 0.2),
(_('x 3'), 600*3, 300*3, 0.3),
(_('x 5'), 600*5, 1000, 0.3),
(_('x 10'), 600*10, 2000, 0.3),
),
GmstTweak(_('Compass: POI Recognition'),
_("Distance at which POI markers begin to show on compass."),
'iMapMarkerVisibleDistance',
(_('x 0.50'),6000),
(_('x 0.75'),9000),
),
GmstTweak(_('Essential NPC Unconsciousness'),
_("Time which essential NPCs stay unconscious."),
'fEssentialDeathTime',
(_('30 Seconds'),30),
(_('1 Minute'),60),
(_('5 Minutes'),300),
),
GmstTweak(_('Fatigue from Running/Encumbrance'),
_("Fatigue cost of running and encumbrance."),
('fFatigueRunBase','fFatigueRunMult'),
('x 3',24,12),
('x 4',32,16),
('x 5',40,20),
),
GmstTweak(_('Horse Turning Speed'),
_("Speed at which horses turn."),
'iHorseTurnDegreesPerSecond',
(_('x 2.0'),90),
),
GmstTweak(_('Jump Higher'),
_("Maximum height player can jump to."),
'fJumpHeightMax',
(_('x 1.2'),164*1.2),
(_('x 1.4'),164*1.4),
(_('x 1.6'),164*1.6),
),
GmstTweak(_('PC Death Camera'),
_("Time after player's death before reload menu appears."),
'fPlayerDeathReloadTime',
(_('30 Seconds'),30),
(_('1 Minute'),60),
(_('5 Minute'),300),
(_('Unlimited'),9999999),
),
GmstTweak(_('Cell Respawn Time'),
_("Time before unvisited cell respawns. But longer times increase save sizes."),
'iHoursToRespawnCell',
(_('3 Days'),24*3),
(_('5 Days'),24*5),
(_('10 Days'),24*10),
(_('20 Days'),24*20),
(_('1 Month'),24*30),
(_('6 Months'),24*182),
(_('1 Year'),24*365),
),
#--Magic Bolt Speed
GmstTweak(_('Magic Bolt Speed'),
_("Speed of magic bolt/projectile."),
'fMagicProjectileBaseSpeed',
(_('x 1.4'),1000*1.4),
(_('x 1.6'),1000*1.6),
(_('x 1.8'),1000*1.8),
(_('x 2.0'),1000*2.0),
(_('x 2.2'),1000*2.2),
(_('x 2.4'),1000*2.4),
(_('x 2.6'),1000*2.6),
(_('x 2.8'),1000*2.8),
(_('x 3.0'),1000*3.0),
),
#--Training Max
GmstTweak(_('Training Max'),
_("Maximum number of Training allowed by trainers."),
'iTrainingSkills',
('25',25),
),
GmstTweak(_('Training Max'),
_("Maximum number of Training allowed by trainers."),
'iTrainingSkills',
('50',50),
),
GmstTweak(_('Training Max'),
_("Maximum number of Training allowed by trainers."),
'iTrainingSkills',
('75',75),
),
GmstTweak(_('Training Max'),
_("Maximum number of Training allowed by trainers."),
'iTrainingSkills',
('100',100),
),
],key=lambda a: a.label.lower())
'



I riped this section out so I would not overwrite any changes that might be made.

CODE
class AlchemicalCatalogs(Patcher):
#------------------------------------------------------------------------------
class AliasesPatcher(Patcher):
#------------------------------------------------------------------------------
class BowPatcher(Patcher):
#------------------------------------------------------------------------------
class ClothesTweak(MultiTweakItem):
#------------------------------------------------------------------------------
class ClothesTweak_MaxWeight(ClothesTweak):
#------------------------------------------------------------------------------
class ClothesTweak_Unblock(ClothesTweak):
#------------------------------------------------------------------------------
class ClothesTweaker(MultiTweaker):
#------------------------------------------------------------------------------
class GmstTweak(MultiTweakItem):
#------------------------------------------------------------------------------
class GmstTweaker(MultiTweaker):
#------------------------------------------------------------------------------
class GraphicsPatcher(ListPatcher):
#------------------------------------------------------------------------------
class ListsMerger(ListPatcher):
#------------------------------------------------------------------------------
class NamesPatcher(ListPatcher):
#------------------------------------------------------------------------------
class NamesTweak_Body(MultiTweakItem):
#------------------------------------------------------------------------------
class NamesTweak_Potions(MultiTweakItem):
#------------------------------------------------------------------------------
class NamesTweak_Scrolls(MultiTweakItem):
#------------------------------------------------------------------------------
class NamesTweak_Spells(MultiTweakItem):
#------------------------------------------------------------------------------
class NamesTweak_Weapons(MultiTweakItem):
#------------------------------------------------------------------------------
class NamesTweaker(MultiTweaker):
#------------------------------------------------------------------------------
class NpcFacePatcher(ListPatcher):
#------------------------------------------------------------------------------
class PatchMerger(ListPatcher):
#------------------------------------------------------------------------------
class PowerExhaustion(Patcher):
#------------------------------------------------------------------------------
class RacePatcher(ListPatcher):
#------------------------------------------------------------------------------
class ReweighPotions(Patcher):
What i am trying to do is do a collapsible list allowing you to pick a one of the entries to give more options
IE

Collapsed
CODE
+Training Max


Opened
CODE
-Training Max
|-Training Max[25]
|-Training Max[50]
--Training Max[75]
Error: The idea is to clone bash.py, NOT bosh.py. bosh is the bottom of the layer cake -- you want to leave that alone.

Instead, you clone bash.py (which is the very thin, top most layer of the cake). Then you put your new code into bash.py. In order to do this, you need to: 1) understand python moderately well (i.e., understand lists, tuples, dictionaries, strings, classes and objects), 2) understand bash's object model -- if you understand this, then you'll know how to use code in bash.py to reach down in the lower layers (basher and bosh) and you'll know which elements of the code structure are particularly amenable to extension (typically things that are lists, e.g., menu command items, patcher component list, subcomponents of patchers -- e.g., the particular set of GMST tweaks with the GMST tweaker component).
Wow, I must say this thread has gotten me interested in Python biggrin.gif

Can you recommend a good IDE for it?
Some belated replies (remember semi-retired)...

Training Max is a GMST setting and should be handled in same way as other GMST settings: For those, options are available in a pop-up list.

Inverness: I actually just use a text editor (EditPlus). There's some tweaking that you can do in recognizing file formats, and I use various tricks to navigate around the files. E.g., EditPlus allows you to bookmark various loc