This page details some advanced concepts and answers some questions you may have after having followed the Basketweaving tutorial.
Skill Level: Advanced
This is an advanced tutorial and is targeted toward experienced modders.
Important Note
There is a lot of information in this tutorial. Please take your time working through it.
Appendix A: Progression System Code
As mentioned in Part 1, the Campfire Skill System is not the progression system itself; that’s an exercise left up to the reader.
This is a pattern I used for the Camping skill in Campfire. Hopefully this will give you an idea of what you might need to do in your own mod. Fundamentally, you need to:
- Figure out how many actions / events / etc required to get to the next level of your skill. I like to increase the number of actions required for each new point in the skill, which is consistent with other systems in the game. Give the player the first point after only a few actions, and then each additional one requires a little more time / actions / events.
- Update the Skill Progress global so that the player knows how close they are to the next point when they open up the skill.
- If the progress is greater than or equal to 100%, give the player a skill point. Display a message to let them know they gained a point.
- If the progress wasn’t enough to gain a point, let the player know that they advanced the skill instead. Do this only if the action the player would be taking is seldom; we don’t want to spam the player with advancement messages. If the action happens often, maybe display the advancement message every 25% of progress.
Here is an example of this. For Basketweaving, we said that the player should advance the skill when they read special Basketweaving skill books. We can assume that there’s a script attached to the skill books that does this, so, that’s probably a good place to put this code.
Feel free to comment out the debug.trace() calls if you don’t want them in your user’s logs. May be helpful during early testing, but probably not after that.
Info
This code is available in the example plug-in download.scriptname BasketweavingSkillBook extends ObjectReference GlobalVariable property BasketweavingPerkPoints auto GlobalVariable property BasketweavingPerkPointsEarned auto GlobalVariable property BasketweavingPerkPointProgress auto GlobalVariable property BasketweavingPerkPointsTotal auto Message property Basketweaving_PerkEarned auto Message property Basketweaving_PerkAdvancement auto Event OnRead() AdvanceBasketweaving() EndEvent function AdvanceBasketweaving() if BasketweavingPerkPointsEarned.GetValueInt() < BasketweavingPerkPointsTotal.GetValueInt() ;Figure out the number of books needed for the next point. debug.trace("Advancing Basketweaving skill.") int next_level = BasketweavingPerkPointsEarned.GetValueInt() + 1 ; 3, 5, 7, 9, 10, 11, 12, 13... float books_required if next_level <= 4 books_required = (2 * next_level) + 1 else books_required = (9 + (next_level - 4)) endif ; What is the current progress toward the next point? float progress_value = (1.0 / books_required) BasketweavingPerkPointProgress.SetValue(BasketweavingPerkPointProgress.GetValue() + progress_value) debug.trace("Basketweaving perk progress value: " + BasketweavingPerkPointProgress.GetValue()) ; If we have >= 100% progress, give the player a point. if (BasketweavingPerkPointProgress.GetValue() + 0.01) >= 1.0 debug.trace("Granting a Basketweaving perk point.") Basketweaving_PerkEarned.Show() BasketweavingPerkPointsEarned.SetValueInt(BasketweavingPerkPointsEarned.GetValueInt() + 1) BasketweavingPerkPoints.SetValueInt(BasketweavingPerkPoints.GetValueInt() + 1) ; If we've maxed out the skill, leave the progress at 100% so the player knows ; that they can't get any more advancement in this skill. if BasketweavingPerkPointsEarned.GetValueInt() >= BasketweavingPerkPointsTotal.GetValueInt() BasketweavingPerkPointProgress.SetValue(1.0) else BasketweavingPerkPointProgress.SetValue(0.0) endif else ; We didn't have enough progress for a point; show the advancement message instead. Basketweaving_PerkAdvancement.Show() endif endif endFunction
Appendix B: Manual Skill Tree Registration
Registering your skill tree manually may be a better option than the automatic player alias method if you need greater control over the availability of your skill tree, such as making sure that your mod is “running” before you display it.
Here’s an example of that.
Important: If you follow this method, try not to register the tree immediately when the game starts (like in an OnInit or PlayerLoadGame event). You must allow time for Campfire to initialize on start-up. You can register for the Campfire_Loaded mod event in order to detect when Campfire is finished starting up when the game loads.
scriptname MyModStartUpScript extends Whatever Activator property SuperCoolNodeController auto function StartMyAwesomeMod() ; Do a bunch of stuff to start the mod ; And then... ; For safety, make sure to check the API version is at least 4. GlobalVariable CampfireAPIVersion = Game.GetFormFromFile(0x03F1BE, "Campfire.esm") as GlobalVariable if CampfireAPIVersion.GetValueInt() >= 4 CampUtil.RegisterPerkTree(SuperCoolNodeController, "my_cool_mod.esp") endif endFunction function StopMyAwesomeMod() ; Do a bunch of stuff to stop the mod ; And then... ; This controls run-time availability, in case user stops the mod during play. ; Campfire will automatically unregister the tree if your mod ; is uninstalled or removed. GlobalVariable CampfireAPIVersion = Game.GetFormFromFile(0x03F1BE, "Campfire.esm") as GlobalVariable if CampfireAPIVersion.GetValueInt() >= 4 CampUtil.UnregisterPerkTree(SuperCoolNodeController, "my_cool_mod.esp") endif endFunction
Appendix C: SkyUI MCM Skill Respec and Refund
Your user may want easy access to control certain parameters of your skill. They might want to reallocate their earned points. Or, they might have had to perform a clean save of your mod, which would wipe out their skill progress, and they want those points back. Last but not least, they might want to cheat and get all of the points immediately!
The following is a standard set of SkyUI MCM code I like to use in my mods. This will allow your users to respec or “restore” (or cheat) their points.
This is a fully-functional SkyUI MCM script; adapt it into your existing MCM script as needed.
Info
This code is available in the example plug-in download.Scriptname Basketweaving_SkyUIMCMScript extends SKI_ConfigBase ; Be sure to make translator's lives easier and convert ; all string literals below to translation file variables ; and include a translation file with your mod. int BasketweavingSkillRespec_OID int BasketweavingSkillRestore_OID int BasketweavingSkillRestoreSlider_OID Globalvariable property BasketweavingPerkPoints auto Globalvariable property BasketweavingPerkPointsEarned auto Globalvariable property BasketweavingPerkPointProgress auto Globalvariable property BasketweavingPerkPointsTotal auto Globalvariable Property Basket_PerkRank_BasketBeginner auto Globalvariable Property Basket_PerkRank_RedBasketExpert auto Globalvariable Property Basket_PerkRank_HappyBasketCrafter auto Globalvariable Property Basket_PerkRank_TravelBasket auto Globalvariable Property Basket_PerkRank_BasketMaster auto Event OnConfigInit() Pages = new string[1] Pages[0] = "Basketweaving" endEvent Event OnPageReset(string page) if page == "Basketweaving" PageReset_Basketweaving() endif endEvent function PageReset_Basketweaving() SetCursorFillMode(TOP_TO_BOTTOM) AddHeaderOption("Basketweaving Skill") BasketweavingSkillRespec_OID = AddTextOption("Respec Skill Points", "") BasketweavingSkillRestore_OID = AddToggleOption("Restore Skill Progress", false) BasketweavingSkillRestoreSlider_OID = AddSliderOption("Points to Restore", 0, "{0}", OPTION_FLAG_DISABLED) endFunction Event OnOptionHighlight(int option) if option == BasketweavingSkillRespec_OID SetInfoText("Select to have all spent skill points refunded.") elseif option == BasketweavingSkillRestore_OID SetInfoText("Select to restore lost skill point progress.") endif EndEvent Event OnOptionSelect(int option) if option == BasketweavingSkillRespec_OID bool b = ShowMessage("Are you sure you want to refund all earned Basketweaving skill points so you can reallocate them?") if b RefundSkillPoints() ShowMessage("Skill points restored successfully.", false) endif elseif option == BasketweavingSkillRestore_OID bool b = ShowMessage("This option is intended to be used to reclaim Basketweaving skill progress that was lost due to a clean save or mod uninstallation. Do you wish to continue?") if b ShowMessage("Select the total number of skill points to restore. This will replace your current progress.") SetToggleOptionValue(BasketweavingSkillRestore_OID, true, true) SetOptionFlags(BasketweavingSkillRestoreSlider_OID, OPTION_FLAG_NONE) endif endif endEvent event OnOptionSliderOpen(int option) if option == BasketweavingSkillRestoreSlider_OID SetSliderDialogStartValue(0.0) SetSliderDialogDefaultValue(0.0) SetSliderDialogRange(0, BasketweavingPerkPointsTotal.GetValue()) SetSliderDialogInterval(1.0) endif endEvent event OnOptionSliderAccept(int option, float value) if option == BasketweavingSkillRestoreSlider_OID BasketweavingPerkPointProgress.SetValue(0.0) BasketweavingPerkPoints.SetValue(value) BasketweavingPerkPointsEarned.SetValue(value) ClearBasketweavingPerks() ShowMessage("Skill points restored successfully.", false) SetOptionFlags(BasketweavingSkillRestoreSlider_OID, OPTION_FLAG_DISABLED, true) SetToggleOptionValue(BasketweavingSkillRestore_OID, false) endif endEvent function RefundSkillPoints() ClearBasketweavingPerks() BasketweavingPerkPoints.SetValueInt(BasketweavingPerkPointsEarned.GetValueInt()) BasketweavingPerkPointProgress.SetValue(0.0) endFunction function ClearBasketweavingPerks() ; All current perk rank globals go here. Basket_PerkRank_BasketBeginner.SetValueInt(0) Basket_PerkRank_RedBasketExpert.SetValueInt(0) Basket_PerkRank_HappyBasketCrafter.SetValueInt(0) Basket_PerkRank_TravelBasket.SetValueInt(0) Basket_PerkRank_BasketMaster.SetValueInt(0) endFunction
This code will generate a set of controls on your MCM page that look like the following.
Appendix D: Creating Skill Tree Without Campfire.esm Dependency
This involves a few more steps than if Campfire.esm is required, but it is possible. There are two options:
- Option A – Create a plug-in that requires both your main plug-in, and Campfire.esm, and build your skill tree in this secondary optional plug-in. This may make things a bit harder on your end since you may have to test for the presence of both Campfire AND your optional Campfire Skill Tree plugin, and react accordingly if necessary, unless you make your secondary plug-in completely self-contained. This also requires your users to use another plug-in in their load order if they want the perk tree and some may not have the space for it.
- Option B – Copy forms from Campfire.esm into your plug-in and then build your tree in your main plugin file. Doing things this way may be easier to manage but you will need to carefully copy forms using TES5Edit.
Option A – Secondary Optional Plug-In
Complete the following steps:
1. Create a plug-in similar to creating a compatibility patch (using TES5Edit or Wrye Bash) and require both your mod and Campfire.esm as master files.
2. Once that’s done, open your new optional plug-in and continue the tutorial from Part 1, Globals; you will have access to all of the examples and template files that Campfire provides.
Option B – Integration into Main Plug-In
A fully-functional version of the Basketweaving example that doesn’t require Campfire.esm is available here:
Warning
Make a back-up of your main plug-in before you begin! You could lose work if you make a mistake!You can integrate a Campfire Skill Tree into your main plug-in without a hard dependency on Campfire.esm that will work if Campfire is loaded and fails gracefully if it isn’t.
To do this, you will need to copy forms from Campfire.esm into your main plug-in so you can use them.
Complete the following steps:
1. Open TES5Edit, and load Campfire.esm and your main plug-in.
2. Right-click on the following records in Campfire.esm and select Copy as new record into…, and select your plug-in as the destination (ignoring the error about Editor IDs; it’s fine to keep the names the same as you won’t be loading your plug-in and Campfire.esm in the CK at the same time):
Global: CampfireIsPerkEligibleToBuy Activator: _Camp_PerkLine_DefaultForModdersCOPYME Activator: _Camp_PerkNode_DefaultForModdersCOPYME Activator: _Camp_PerkNodeController_DefaultForModdersCOPYME Activator: _Camp_PerkNodePosRefDummy Activator: _Camp_PerkLinePosRefDummy Static: _Camp_CampfireStagingStaticDONOTDELETE Static: _Camp_PerkArtPlane_DefaultForModdersCOPYME TextureSet: _Camp_PerkArt_Example
3. After you’ve copied the records, right-click the CampfireIsPerkEligibleToBuy Global you copied in your plug-in and select Change FormID. Change the FormID to 01CC0D02. This will inject the global into Update.esm and you can now use it to conditionalize your “Choose Perk” button in your perk Message objects.
4. Exit TES5Edit and save your plug-in. Make a back-up. You might need it if something went wrong.
5. You’ll need to stage the position references yourself since you can’t copy the default from Campfire’s modder staging cell. Begin by going to World, Cells…, right click the list, and create a new Cell. Inside the render window, drop a RifRmBgKeepFloorStone01 into the cell positioned at X 0, Y 0, Z 0. Also at 0, 0, 0 drop a copy of _Camp_CampfireStagingStaticDONOTDELETE and an XMarker static. Do not rotate anything.
Turn on Snap to Grid () to make positioning them exactly at 0, 0, 0 a bit easier. Turn it off when you’re done.
6. Drag a copy of _Camp_PerkArtPlane_DefaultForModdersCOPYME into the cell. Position it -47 units Y and +109 units Z from the XMarker. If your XMarker is at 0, 0, 0, that means your art plane will be at X 0, Y -47, and Z 109.
(Note: Your copy of the _Camp_PerkArtPlane_DefaultForModdersCOPYME might be a solid, translucent blue instead of the text below. This is fine, it just means that the TextureSet got disassociated with the static when you copied the records in TES5Edit. It won’t matter in a few minutes when you replace it with your own perk art plane anyway. If it bothers you or makes it difficult to work, open _Camp_PerkArtPlane_DefaultForModdersCOPYME in the Object Window, select Mesh, Edit, and assign it the _Camp_PerkArt_Example TextureSet.)
7. Drop a _Camp_PerkNodePosRefDummy into the cell. Position it -68 units Y from the XMarker.
8. Finally, drop a _Camp_PerkLinePosRefDummy into the cell. Position it at the same coordinates as the node. Set the X Angle to 90 and the Y Angle to 0. (Very important for proper display!) After that, adjust only the Z angle to “spin” the line into the direction you want it to face and scale accordingly.
You’re now ready to set up the rest of your tree position references in your staging cell when the time comes in Part 3. Just duplicate the nodes and lines as necessary.
9. You should now able to continue the tutorial from Part 1, Globals and create the rest of your skill tree. When you reach Part 3 and begin setting up your position references, just edit the references (perk nodes, lines, perk art plane) you created on this page; you don’t need to make another copy of them.