Utilizador:Halo112358

A UESPWiki – Sua fonte de The Elder Scrolls desde 1995

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.

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

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