Utilizador:Halo112358
WIP things, not sure how relevant they are any more but I don't want to see them vanish before I've found a permanent home for them.
Índice
Distributing useable horse armor retextures legally using bsdiff and bspatch
Introduction
Let's get this out of the way up front:
- redistribution of paid Official plugins or their art assets is against the law
- we are not competing with Bethesda or releasing free clones of their pay-to-play content
- we are giving the end user extra incentive to legally purchase pay-to-play content from Bethesda
Executive Summary
The TES modding community is accustomed to redistributing 'reskinned' or retextured models for TES series games. The Morrowind modding community has been engaging in this practice for approximately four years and this additional user-created content is a large part of Morrowinds longevity. Currently we're in a bit of a bind, with the recent release of pay-to-play content we're no longer able to reskin and redistribute all available art assets. Redistributing copies (even modified versions) of Bethesda's pay-to-play content is piracy, so how do we make reskinned horse armor available in a way that A: is legal and B: doesn't interfere with Bethesda's business?
The simple answer is to require the end user already have the paid content installed and enforce this requirement by distributing an incomplete package that contains none of Bethesda's pay-to-play content.
This tutorial discusses a method for mod authors and users to legally (and morally, if you're so inclined) author and distribute mods that build on the official DLCHorseArmor plugin by using binary patches. A wide variety of content built on top of a paid plugin gives the end user extra incentive to purchase official content. By utilizing this method mod authors are able to distribute patches that leverage existing paid content and provide more value for the users discretionary dollar.
Fair Warning: This method is not for novices, if you're not comfortable using command line utilities you probably don't want to attempt this.
Methodology
There are currently four horse armor texture replacement mods in existence, they all require that the end user have the original paid content in place before they function and they all seem to be acceptable to Bethesda. None of these plugins have added items to the game in a standalone manner, they are all texture replacers, and they're often incompatible as released.
While we're not allowed to redistribute art assets or content from DLCHorseArmor in any form, including meshes or plugin files, fair use doctrine allows us to use our own purchased content as we see fit. Since we can't redistribute reskinned models we have to rely on the user already having these meshes installed (and they do if they've paid for the DLC content). We unpack the asset archive (DLCHorseArmor.bsa), duplicate the original mesh, apply our modifications with a utility such as nifskope and then generate a small patch (generally less than 300 bytes) that the end user can use to apply our work to their purchased plugin quickly and easily. This ensures that users are not able to pirate official content simply by installing a texture replacement mod, after all, our 200 bytes of data certainly does not contain a useable model.
This method is similar to the currently accepted modding practice of reskinning and then redistributing a mesh, except that in this case we aren't redistributing the mesh (as it's a pay-to-play asset).
Required Tools
- bsdiff.exe & bspatch.exe for win32 (source code)
- bzip2 for win32
- tes4bsa by Ghostwheel
- NifSkope (authors only)
For mod authors
workflow overview
- unpack the DLCHorseArmor.bsa archive
- duplicate any meshes you wish to use
- modify your new mesh
- complete any work in the construction set
- generate any model diffs using bsdiff
c:\> bsdiff armorsteel.nif armorblackhand.nif armorsteel_armorblackhand.bpatch
- package your mod
- plugin file (.esp)
- textures
- binary diff files for meshes
For mod consumers
installation overview
- unpack downloaded mod to a temporary directory
- unpack DLCHorseArmor.bsa to a temporary directory
- copy any .bpatch files and any models they correspond to into the same directory
- generate patched models with bspatch
c:\> bspatch armorsteel.nif armorblackhand.nif armorsteel_armorblackhand.bpatch
- copy the newly generated models into the proper place in your oblivion data directory
ie: Oblivion\data\meshes\creatures\horse\armorblackhand.nif
- install the rest of the mod (textures, esp files, etc..)
morrowind script performance
I'm not sure where these should live (or if they should live) on the UESP wiki, they'd be useful to people still scripting for morrowind because they demonstrate best practices for returning early and concurrency.
scripts for elhazan
begin uniqueprefix_sword_script ;; relies on a global named uniqueprefix_doSwordFade ;; global should be 0 before we've done any work short done float timer if ( done == 2 ) return elseif ( done == 1 ) set timer to ( timer + getsecondspassed ) if ( timer > 2.0 ) ;; remove the sword when fully faded disable ;; play teleport noise after full fade playsound "conjuration hit" ;; tell destination script we need to fade-in when we get there set uniqueprefix_doSwordFade to 1 ;; teleport to coords here, this is a sample from the mazed band script ;;Player->PositionCell 12, 219, -501, 0 "Vivec, High Fane" ;; fade in view to normal - this is handled by a script at the destination ;; this way we avoid complicated global timers & buggy CellChange events ;; that don't always fire. endif return endif ;; don't do menumode test when we're in the middle of a fade if ( menumode == 1 ) return endif if ( OnActivate == 1 ) messagebox "As you grasp the sword your vision goes black" "Ok" set done to 1 ;; fade out view to black here, note that fading takes a little time ;; so we don't want to warp immediately - run a timer to warp fadeout 1.5 return endif
begin uniqueprefix_destinationFadeIn ;; relies on a global named uniqueprefix_doSwordFade to determine if ;; we should do our fade-in when the player arrives in the cell with ;; this activator ;;short uniqueprefix_doSwordFade if ( menumode == 1 ) return endif if ( uniqueprefix_doSwordFade == 2 ) return endif if ( uniqueprefix_doSwordFade == 1 ) fadein 1.5 set uniqueprefix_doSwordFade to 2 end
scripts for PirateLord
this is the launcher & cleanup script for any number of concurrent magic-school levelling scripts, cleans up the current state on load and handles starting scripts at the right time (after chargen..).
begin plx__ISM_startupCleanupScript if ( chargenstate != -1 ) return endif if ( scriptrunning, "plx__ISM_Script" ) set plx__ISM_Script.state to 0 else startscript plx__ISM_Script endif ;; ;; set state to 0 and/or start all other scripts here ;; stopscript plx__ISM_startupCleanupScript END
major things to note, notice how the return statements are called right up front - and how the logic puts them there instead of after large blocks of code. MW doesn't skip over chunks of code the way it should, everything in an if/else block is still read at runtime until the program gets to the proper section. floats, notice that all float operations use float values (0.0 instead of 0, etc..).
The fastest way to run this system is to split the leveling script into concurrent scripts, one for each school. Using a monolithic script to check many conditions is going to be slow, 6 smaller parallel scripts with early return calls should work just as well.
begin plx__ISM_Script float SpellTimer short MyCurrentMagic short SkillBonus short TmpVal ; short state short preCastMagicka ;Update the Level Up maximums to most current numbers set plx__LevUpAlt to ( ( player -> GetAlteration ) * plx__LevMaxMult ) set plx__LevUpConj to ( ( player -> GetConjuration ) * plx__LevMaxMult ) set plx__LevUpDest to ( ( player -> GetDestruction ) * plx__LevMaxMult ) set plx__LevUpIll to ( ( player -> GetIllusion ) * plx__LevMaxMult ) set plx__LevUpMyst to ( ( player -> GetMysticism ) * plx__LevMaxMult ) set plx__LevUpRest to ( ( player -> GetRestoration ) * plx__LevMaxMult ) if ( menumode == 1 ) return endif if ( (player->GetSpellReadied) == 0 ) return endif if ( state == 0 ) if ( (player->GetSoundPlaying, "destruction cast") != 1 ) return else ;first frame, allow some time for the players stats to update set SpellTimer to 0.0 ; 2 seconds later, do my magic calculations ;; this is updated after successful calc. as well, see bottom set preCastMagicka to ( player->GetMagicka ) set state to 10 return endif elsif ( state == 10 ) set SpellTimer to ( SpellTimer + GetSecondsPassed ) if ( SpellTimer < 2.0 ) return else ; 2 seconds later, do my magic calculations set MyCurrentMagic to ( player->GetMagicka ) ;messagebox "Original Magic: %.0f Current Magic: %0.f" plx__MagicPreCastValue, MyCurrentMagic, "OK" if ( preCastMagicka > MyCurrentMagic ) set SkillBonus to ( preCastMagicka - MyCurrentMagic ) set SkillBonus to ( SkillBonus / plx__ExpDivider ) ;; did you mean each cast to raise skill by 1 point? Is this for testing? ;;if ( SkillBonus < 1 ) if ( SkillBonus > 1 ) set SkillBonus to 1 endif set plx__CurExPDest to ( plx__CurExpDest + SkillBonus ) if ( plx__CurExpDest >= plx__LevUpDest ) set plx__CurExpDest to ( plx__CurExpDest - plx__LevUpDest ) player -> modDestruction 1 ;; put a GCD hook in here to tell GCD that we've leveled set TmpVal to ( player -> GetDestruction ) messagebox "Your Destruction Skill has increased to %0.f", TmpVal set plx__LevUpDest to ( ( player -> GetDestruction ) * plx__LevMaxMult ) endif endif set SpellTimer to 0.0 ; this doesn't always get updated up top, so do it again to be safe. set preCastMagicka to ( player->GetMagicka ) set state to 0 endif endif end