Wrye Bash Programming

Post » Mon Dec 28, 2009 10:16 am

Wow, I must say this thread has gotten me interested in Python :D

Can you recommend a good IDE for it?
User avatar
Amelia Pritchard
 
Posts: 3445
Joined: Mon Jul 24, 2006 2:40 am

Post » Mon Dec 28, 2009 10:25 pm

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 locations in the files -- which I used to quickjump between different locations.

More generally for Python, Since compilation occurs in the background as needed, you don't need to go through the regular build steps. For Bash after making changes, I usually just relaunch from shortcut and test changes. If there's a problem, it will show up in trace dump which is enough. Occasionally, a bug will cause it to fail to launch, in which case I open a command window in the mopy directory and run "bash 0", which will dump debug to the command shell. Note that the "0" is bash specific -- I just use it set where text dumps to.

For general (non-Bash) python programming, a good text editor may do the job for you (e.g., EditPlus allows me to run commands and dump to an in-editor buffer, and from there go to appropriate space in text files (takes a little tweaking to get there, but works fine). If you're looking for a more advanced IDE, I'd suggest checking at python.org for help -- I'm too rusty in that area to give you any advice.
User avatar
Crystal Clarke
 
Posts: 3410
Joined: Mon Dec 11, 2006 5:55 am

Post » Mon Dec 28, 2009 2:05 pm

Wrye, I'm very interested in this, but I've got one question.

I basically need to change the skeleton of every NPC in the game to SkeletonBeast [http://www.gamesas.com/bgsforums/index.php?s=&showtopic=747389&view=findpost&p=10820374], which I realise can be done quite easily (I just need to learn how :P). The problem is this needs to be done user-end, as every NPC in every mod needs to be changed as well. Is it possible to add menu options to Bash through 'plugins' at all, or would I have to instruct the mod users to use the command line version (which... they might have difficulty with. Along with tying their shoelaces and opening packets of crisps)?
User avatar
dell
 
Posts: 3452
Joined: Sat Mar 24, 2007 2:58 am

Post » Mon Dec 28, 2009 4:00 pm

Thing to do as add a bash patch component that does the conversion. To add a patch component, you need to do three things:
1) Define a patcher class in bosh.py to do most of the work.
2) Define a small GUI wrapper class in basher.py.
3) Add an instance of the new wrapper class to the # Init Patchers extend call in basher.py.

The example to follow there is the BowPatcher, which also has a pretty simple interface. The only thing that's a little difficult is the bosh.py code. I've written an (untested) version of that below. (I'm not totally sure about the modb and modt settings. And I'm assuming that I've got the path for the skeleton correct.) So, just add this to bosh.py, and duplicate/tweak uses of BowPatcher in basher.py.

#------------------------------------------------------------------------------class NpcSkeletonPatcher(Patcher):	"""Changes skeletons of all NPCs to SkeletonBeast."""	group = _('Tweakers')	name = _('Beastifier')	text = _("Changes skeletons of all NPCs to SkeletonBeast.")	#--Config Phase -----------------------------------------------------------	#--Patch Phase ------------------------------------------------------------	def getReadClasses(self):		"""Returns load factory classes needed for reading."""		if not self.isActive: return tuple()		return (MreNpc,)	def getWriteClasses(self):		"""Returns load factory classes needed for writing."""		if not self.isActive: return tuple()		return (MreNpc,)	def scanModFile(self,modFile,progress):		"""Scans specified mod file to extract info. May add record to patch mod, 		but won't alter it."""		if not self.isActive: return		patchBlock = self.patchFile.NPC_		recordsById = patchBlock.recordsById		modFile.convertToLongFormIds(('NPC_',))		for record in modFile.NPC_.getActiveRecords:			if record.formid not in recordsById and record.model.path != 'SkeletonBeast':				patchBlock.setRecord(record.formid,record)					def buildPatch(self,log,progress):		"""Edits patch file as desired. Will write to log."""		if not self.isActive: return		count = {}		keep = self.patchFile.getKeeper()		for record in self.patchFile.NPC_.records:			model = record.model			if model.path != 'SkeletonBeast':				model.path = 'SkeletonBeast'				model.modb = None #??				model.modt = None #??				keep(record.formid)				srcMod = record.formid[0]				count[srcMod] = count.get(srcMod,0) + 1		#--Log		log.setHeader('= '+self.__class__.name)		log(_('* %d Skeletons Tweaked') % (sum(count.values()),))		for srcMod in sorted(count.keys()):			log('  * %3d %s' % (count[srcMod],srcMod))

So, do that for you own version of Bash right now, and when you get the rest of the tweaking package put together, let me know and I'll add it into regular release of bash.

PS: One more thing: This won't affect existing cloned npcs in game -- those will still have old SkeletonMale. Vanilla Oblivion adds one (statue) and SI adds one, but it doesn't last long (so not a problem). But, there's also staff of corruption I think, plus various things from mods. Tweaking those would require a separate command on the savegame. (Like the reweigh potions for savegames.)
User avatar
Nicholas
 
Posts: 3454
Joined: Wed Jul 04, 2007 12:05 am

Post » Mon Dec 28, 2009 5:14 pm

Ah, excellent. I'll give that a go now, thanks. I'm afraid I'm not entirely sure what modB and modT do though, I tried looking through Bosh.py but couldn't figure it out :/.
User avatar
Sylvia Luciani
 
Posts: 3380
Joined: Sun Feb 11, 2007 2:31 am

Post » Mon Dec 28, 2009 3:12 pm

Ah, excellent. I'll give that a go now, thanks. I'm afraid I'm not entirely sure what modB and modT do though, I tried looking through Bosh.py but couldn't figure it out :/.

Yeah, that's in the unknown category. Bash treats them as all being part of the model (along with the path). Rough idea is that if you're copying the model from something else, you should copy modb and modt as well. I assume that you're familiar with UESP file format documentation, and there's tes4view as well, which sometimes has additional info (but not on this, I don't think).
User avatar
Tessa Mullins
 
Posts: 3354
Joined: Mon Oct 22, 2007 5:17 am

Post » Mon Dec 28, 2009 3:35 pm

Better keep this one from being munched. Unique info here.
User avatar
Stu Clarke
 
Posts: 3326
Joined: Fri Jun 22, 2007 1:45 pm

Post » Mon Dec 28, 2009 3:49 pm

[bump]
User avatar
MR.BIGG
 
Posts: 3373
Joined: Sat Sep 08, 2007 7:51 am

Post » Mon Dec 28, 2009 9:37 pm

Time to bump the thread. Thanks for all the help so far Wrye, but I've got another question... :P

As part of my ongoing quest to make playable creatures I've just figured out a solution to one of the crazy problems I was having, namely, that you couldn't strip the default attack and cast animations from the player. I've figured out I can 'remove' the animations using archiveinvalidation, and add them as specialanims. This is another thing I'd need bash for (otherwise every NPC in the game would be unable to fight or cast, making it a even easier than vanilla OB), so I was wondering if it can read/edit SpecialAnim entries on NPCs? What I'd like to do is add specialanims\[each of the cast/standard attack anims].kf to each NPC with a skeleton in the _male folder apart from the player. Does that seem feasible?
User avatar
El Goose
 
Posts: 3368
Joined: Sun Dec 02, 2007 12:02 am

Post » Mon Dec 28, 2009 1:43 pm

That would be pretty easy I think. (I could be wrong, but as I recall that wouldn't be a problem.) Pick one npc add the needed entries, save to a an esp and send it to me.
User avatar
Arnold Wet
 
Posts: 3353
Joined: Fri Jul 07, 2006 10:32 am

Post » Mon Dec 28, 2009 2:30 pm

A token bump here...

Python is interesting. After C++, anything having dynamic typing is looked at longingly (unless you're into micromanaging your logic like a lot of C programmers are). C++ regards anything as wild an crazy as a string with mistrust - is isn't even recognised by MS Visual C++ 2008 as an official datatype (because it isn't - well, mater of perspective there I guess). Python just looks less painful.
User avatar
Brandon Wilson
 
Posts: 3487
Joined: Sat Oct 13, 2007 1:31 am

Post » Mon Dec 28, 2009 9:22 pm

Kind of surprised this is still here (advantage of putting it on the slower moving TESCS forum, I guess). However, worth noting that it's archived at http://www.yacoby.net/es/forum/24/6120471167357240.html.
User avatar
Tina Tupou
 
Posts: 3487
Joined: Fri Mar 09, 2007 4:37 pm

Post » Mon Dec 28, 2009 11:23 pm

Kind of surprised this is still here (advantage of putting it on the slower moving TESCS forum, I guess). However, worth noting that it's archived at http://www.yacoby.net/es/forum/24/6120471167357240.html.

So am I - I won't be able to believe the courage it took to scourge it from the pits of this sub-forum ( either that or you used your assistant :P ). Either ways, this thread must live on.
User avatar
Nancy RIP
 
Posts: 3519
Joined: Mon Jan 29, 2007 5:42 am

Post » Mon Dec 28, 2009 5:57 pm

This is a great resource, I will be very upset if this ever gets pruned... in fact, I think a Wiki article on the subject would be appropriate... maybe more appropriate to UESP, since a lot of your stuff is there, Wrye? I mean, really, it would be appropriate to the CSwiki, but it's your work and UESP probably has more obvious places to put it...
User avatar
George PUluse
 
Posts: 3486
Joined: Fri Sep 28, 2007 11:20 pm

Post » Mon Dec 28, 2009 10:47 am

This is a great resource, I will be very upset if this ever gets pruned... in fact, I think a Wiki article on the subject would be appropriate... maybe more appropriate to UESP, since a lot of your stuff is there, Wrye? I mean, really, it would be appropriate to the CSwiki, but it's your work and UESP probably has more obvious places to put it...

There's only so far I can stretch my 5% unretirement. :lol: Just having the thread archived at Yacoby's is a good step. Editing it down to something useful on a wiki would be more work than it's worth (to me, right now). If you want to take that on (either at UESP or CS Wiki) is fine with me, but again, just having the unedited topic archive gets 80% of the value for 5% of the effort.
User avatar
Georgine Lee
 
Posts: 3353
Joined: Wed Oct 04, 2006 11:50 am

Post » Mon Dec 28, 2009 3:58 pm

The CS Wiki has a pretty good set-up for archiving threads now, actually, though some of the CSS changes need to be pushed down into the Common.css so everyone can use it without editing their user CSS, so that's one option.

In theory, I'd be willing to edit it... but I don't have time to. I wish I did though, because I think it is worthwhile to do so, at some point, maybe.

Although really, I doubt it'll ever get much more attention than it does, sadly. I've found it very useful for my tinkering.
User avatar
ImmaTakeYour
 
Posts: 3383
Joined: Mon Sep 03, 2007 12:45 pm

Post » Mon Dec 28, 2009 10:32 pm

Well doing some Bash changes (unnofficially ofc since Wrye hasn't been online for near a month)
and I've hit a few stumbling blocks but one in particular is this:
I cannot seem to get race;
this is what seems to me to be the relevant code (at the bottom is exact but the whole section is reference so...)
class MreNpc(MreActor):    """NPC Record. Non-Player Character."""    classType = 'NPC_'    #--Main flags    _flags = Flags(0L,Flags.getNames(        ( 0,'female'),        ( 1,'essential'),        ( 3,'respawn'),        ( 4,'autoCalc'),        ( 7,'pcLevelOffset'),        ( 9,'noLowLevel'),        (13,'noRumors'),        (14,'summonable'),        (15,'noPersuasion'),        (20,'canCorpseCheck'),))    #--AI Service flags    aiService = Flags(0L,Flags.getNames(        (0,'weapons'),        (1,'armor'),        (2,'clothing'),        (3,'books'),        (4,'ingredients'),        (7,'lights'),        (8,'apparatus'),        (10,'miscItems'),        (11,'spells'),        (12,'magicItems'),        (13,'potions'),        (14,'training'),        (16,'recharge'),        (17,'repair'),))    #--Mel NPC DATA    class MelNpcData(MelStruct):        """Convert npc stats into skills, health, attributes."""        def loadData(self,record,ins,type,size,readId):            unpacked = list(ins.unpack('=21BH2s8B',size,readId))            recordSetAttr = record.__setattr__            recordSetAttr('skills',unpacked[:21])            recordSetAttr('health',unpacked[21])            recordSetAttr('unused1',unpacked[22])            recordSetAttr('attributes',unpacked[23:])            if self._debug: print unpacked[:21],unpacked[21],unpacked[23:]        def dumpData(self,record,out):            """Dumps data from record to outstream."""            recordGetAttr = record.__getattribute__            values = recordGetAttr('skills')+[recordGetAttr('health')]+[recordGetAttr('unused1')]+recordGetAttr('attributes')            out.packSub(self.subType,'=21BH2s8B',*values)    #--Mel Set    melSet = MelSet(        MelString('EDID','eid'),        MelString('FULL','full'),        MelModel(),        MelStruct('ACBS','=I3Hh2H',            (_flags,'flags',0L),'baseSpell','fatigue','barterGold',            ('level',1),'calcMin','calcMax'),        MelStructs('SNAM','=IB3s','factions',            (FID,'faction',None),'rank',('unused1','ODB')),        MelFid('INAM','deathItem'),        MelFid('RNAM','race'),        MelFids('SPLO','spells'),        MelFid('SCRI','script'),        MelStructs('CNTO','Ii','items',(FID,'item',None),('count',1)),        MelStruct('AIDT','=4BIbB2s',            ('aggression',5),('confidence',50),('energyLevel',50),('responsibility',50),            (aiService,'services',0L),'trainSkill','trainLevel',('unused1',null2)),        MelFids('PKID','aiPackages'),        MelStrings('KFFZ','animations'),        MelFid('CNAM','iclass'),        MelNpcData('DATA','',('skills',[0]*21),'health',('unused2',null2),('attributes',[0]*8)),        MelFid('HNAM','hair'),        MelOptStruct('LNAM','f',('hairLength',None)),        MelFid('ENAM','eye'), ####fid Array        MelStruct('HCLR','3Bs','hairRed','hairBlue','hairGreen',('unused3',null1)),        MelFid('ZNAM','combatStyle'),        MelBase('FGGS','fggs_p'), ####FaceGen Geometry-Symmetric        MelBase('FGGA','fgga_p'), ####FaceGen Geometry-Asymmetric        MelBase('FGTS','fgts_p'), ####FaceGen Texture-Symmetric        MelStruct('FNAM','H','fnam'), ####Byte Array        )    __slots__ = MreActor.__slots__ + melSet.getSlotsUsed()    def setRace(self,race):        """Set additional race info."""        self.race = race        #--Model        if not self.model:            self.model = self.getDefault('model')        if race in (0x23fe9,0x223c7):            self.model.modPath = r"Characters\_Male\SkeletonBeast.NIF"        else:            self.model.modPath = r"Characters\_Male\skeleton.nif"        #--FNAM        fnams = {            0x23fe9 : 0x3cdc ,#--Argonian            0x224fc : 0x1d48 ,#--Breton            0x191c1 : 0x5472 ,#--Dark Elf            0x19204 : 0x21e6 ,#--High Elf            0x00907 : 0x358e ,#--Imperial            0x22c37 : 0x5b54 ,#--Khajiit            0x224fd : 0x03b6 ,#--Nord            0x191c0 : 0x0974 ,#--Orc            0x00d43 : 0x61a9 ,#--Redguard            0x00019 : 0x4477 ,#--Vampire            0x223c8 : 0x4a2e ,#--Wood Elf            }        self.fnam = fnams.get(race,0x358e)

my expression that I'm doing is:
       for record in self.patchFile.NPC_.records:            race = record.race            model = record.model            if (model.modPath != 'Characters\_male\SkeletonBeast.nif' and                   model.modPath != 'Characters\_male\SkeletonBeast.NIF'                    ):                if (race == 0x3cdc or                    race == 0x5b54                    ):                    model.modPath = 'Characters\_male\SkeletonBeast.nif'                    model.modb_p = None #??                    model.modt_p = None #??            elif record.model.modPath == 'Characters\_male\SkeletonBeast.nif':                if race in (0x224fc,0x191c1,0x19204,0x00907,0x224fd,0x00d43,0x00019,0x223c8):                    model.modPath = 'Characters\_male\Skeleton.nif'                    model.modb_p = None #??                    model.modt_p = None #??           

where those if race expressions are I've tried a LOT of ways and they always return false; if I comment the if race part I can get the rest to run fine, but that returns false so totally ruins it.
Any suggestions?
Thanks
Pacific Morrowind
User avatar
sarah
 
Posts: 3430
Joined: Wed Jul 05, 2006 1:53 pm

Post » Mon Dec 28, 2009 11:15 am

just a little bump to make sure this doesn't die... [begin voice over[]taying Alive Staying Alive [end Lord Farquad Voice]
Pacific Morrowind
(and in case anyone wonders I figured out what I was a doing wrong in the last post on here - and it is included in a recentish Bash update)
User avatar
Spencey!
 
Posts: 3221
Joined: Thu Aug 17, 2006 12:18 am

Post » Mon Dec 28, 2009 9:08 pm

just a little bump to make sure this doesn't die... [begin voice over[]taying Alive Staying Alive [end Lord Farquad Voice]
Pacific Morrowind
(and in case anyone wonders I figured out what I was a doing wrong in the last post on here - and it is included in a recentish Bash update)
Good on you :thumbsup: But if I recall correctly, Wrye had this thread archived someplace - So there's no fear of death.
User avatar
Jonathan Egan
 
Posts: 3432
Joined: Fri Jun 22, 2007 3:27 pm

Previous

Return to IV - Oblivion