Wrye Bash Programming

Post » Mon Dec 28, 2009 11:06 am

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...
User avatar
pinar
 
Posts: 3453
Joined: Thu Apr 19, 2007 1:35 pm

Post » Mon Dec 28, 2009 1:46 pm

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).

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.
User avatar
Ownie Zuliana
 
Posts: 3375
Joined: Thu Jun 15, 2006 4:31 am

Post » Mon Dec 28, 2009 7:23 pm

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!
User avatar
Paula Rose
 
Posts: 3305
Joined: Fri Feb 16, 2007 8:12 am

Post » Mon Dec 28, 2009 5:03 pm

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).
User avatar
Mark
 
Posts: 3341
Joined: Wed May 23, 2007 11:59 am

Post » Mon Dec 28, 2009 8:48 pm

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.
User avatar
clelia vega
 
Posts: 3433
Joined: Wed Mar 21, 2007 6:04 pm

Post » Mon Dec 28, 2009 7:41 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:
	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 unanolyzed 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):
	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.
User avatar
Lewis Morel
 
Posts: 3431
Joined: Thu Aug 16, 2007 7:40 pm

Post » Mon Dec 28, 2009 11:08 am

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?
User avatar
Killer McCracken
 
Posts: 3456
Joined: Wed Feb 14, 2007 9:57 pm

Post » Mon Dec 28, 2009 5:31 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.
User avatar
Etta Hargrave
 
Posts: 3452
Joined: Fri Sep 01, 2006 1:27 am

Post » Mon Dec 28, 2009 7:34 pm

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:
User avatar
Laura-Jayne Lee
 
Posts: 3474
Joined: Sun Jul 02, 2006 4:35 pm

Post » Mon Dec 28, 2009 11:02 pm

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?
User avatar
Harry Leon
 
Posts: 3381
Joined: Tue Jun 12, 2007 3:53 am

Post » Mon Dec 28, 2009 8:36 am

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.
User avatar
Kelsey Anna Farley
 
Posts: 3433
Joined: Fri Jun 30, 2006 10:33 pm

Post » Mon Dec 28, 2009 3:22 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 :)
User avatar
James Wilson
 
Posts: 3457
Joined: Mon Nov 12, 2007 12:51 pm

Post » Tue Dec 29, 2009 12:04 am

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.:)
User avatar
Keeley Stevens
 
Posts: 3398
Joined: Wed Sep 06, 2006 6:04 pm

Post » Mon Dec 28, 2009 11:44 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.)
User avatar
Antony Holdsworth
 
Posts: 3387
Joined: Tue May 29, 2007 4:50 am

Post » Mon Dec 28, 2009 11:47 am

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.
User avatar
Amy Gibson
 
Posts: 3540
Joined: Wed Oct 04, 2006 2:11 pm

Post » Mon Dec 28, 2009 10:37 am

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 http://www.uesp.net/wiki/Tes4Mod:ObEdit? It has some batch command processing, but I don't know if it handles cell records.
User avatar
sam westover
 
Posts: 3420
Joined: Sun Jun 10, 2007 2:00 pm

Post » Mon Dec 28, 2009 5:36 pm

Dammit it seems like I am the best at finding out what bash cant do :(
User avatar
Amy Siebenhaar
 
Posts: 3426
Joined: Fri Aug 10, 2007 1:51 am

Post » Mon Dec 28, 2009 9:51 pm

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.
User avatar
Add Me
 
Posts: 3486
Joined: Thu Jul 05, 2007 8:21 am

Post » Mon Dec 28, 2009 8:42 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 anolyze weapons:
# load new mod (the one that will contain new/modified weapons, and again set to anolyze 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.)
User avatar
Elina
 
Posts: 3411
Joined: Wed Jun 21, 2006 10:09 pm

Post » Mon Dec 28, 2009 7:55 pm

WRYE:

Any idea where I should start? Is there any basic tutorials?
User avatar
Nany Smith
 
Posts: 3419
Joined: Sat Mar 17, 2007 5:36 pm

Post » Mon Dec 28, 2009 4:54 pm

WRYE:

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

Here's a pretty good start:
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. :D
User avatar
Laura Elizabeth
 
Posts: 3454
Joined: Wed Oct 11, 2006 7:34 pm

Post » Mon Dec 28, 2009 5:21 pm

This is a Very Interesting Topic This should be placed in some type of wiki for oblivion editing of some type.
User avatar
Justin Hankins
 
Posts: 3348
Joined: Fri Oct 26, 2007 12:36 pm

Post » Mon Dec 28, 2009 6:41 pm

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

Import bosh

I added
Import mybosh


Everywheres that I found
import bosh, basher

I added
import mybosh, basher


Everywheres that I found
import bosh, bushfrom bosh import _, Path

I added
import mybosh, bushfrom mybosh import _, Path

I am currently rewriting this so this is still unfinsihed but shoudl still be fully functional.
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.

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):

User avatar
lacy lake
 
Posts: 3450
Joined: Sun Dec 31, 2006 12:13 am

Post » Mon Dec 28, 2009 9:58 am

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
+Training Max


Opened
-Training Max |-Training Max[25] |-Training Max[50] --Training Max[75]

User avatar
Chenae Butler
 
Posts: 3485
Joined: Sat Feb 17, 2007 3:54 pm

Post » Mon Dec 28, 2009 9:54 pm

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).
User avatar
Carys
 
Posts: 3369
Joined: Wed Aug 23, 2006 11:15 pm

PreviousNext

Return to IV - Oblivion