Hello, and thank you for checking out the mod. The initial release doesn't contain a lot of recipes, I'm mainly posting it for feedback, and to have it as a requirement for some future mods.
However, some recipes for base game items, Skyrim Ingredients, and COBL items are already there.
Hey, this looks neat! I like the idea of crafting, and it could be fun to be able to add crafting recipes for mod items too - especially items that you can only get through shops or something. How does this compare to MOO's crafting system? Because I like MOO a lot, but the Crafting system isn't very easy to work with.
Well guess I'll have to see for myself in any case, but I'm still excited to see where this goes.
I really hope to see this project evolving to fully support all vanilla craftablish assets. As my two cent, I am providing an adapted xedit plugin to convert all static references to CookingPot and Anvil01 to their equivalent active ones OCRAFTStationCookingPot and OCRAFTStationAnvil.
To use this script you need to create an empty plugin named 'Static Replacer - Extended' (you can manually change the name in the script for whatever) and copy the references for the actives elements over to the new plugin. Then select the new plugin and apply the script. This also has the benefit of carrying over all previous form changes so that no conflict resolution patch would be needed:
{ Purpose: Reference Replace Game: The Elder Scrolls V: Skyrim SE Original Author: Sandman53 Adapted by: Darkaxt Version: 1.1 Instructions: 1. Add Editor IDs and Masters 2. Apply Script } unit UserScript; //------------------------------------------------------------------------------- // Global Variables //------------------------------------------------------------------------------- var stReplaceReference, stRefName: TStringList; userFile, iReplaceRef: IInterface; sRefType: string; //------------------------------------------------------------------------------- // Initialize Variables and File //------------------------------------------------------------------------------- function Initialize: integer; var stObjectEID, stSplitText: TStringList; i, n: integer; sModName, sRefName, sRefType: string; iRef, searchFile: IInterface; begin //------------------------------------------------------------------------------- // Setup Variables //------------------------------------------------------------------------------- // Mod Name sModName := 'Static Replacer - Extended.esp'; // Base Objects that will replacing existing object. The delimiter seperates the objects type stObjectEID := TStringList.Create; stObjectEID.Add('OCRAFTStationCookingPot;ACTI'); // 0 stObjectEID.Add('OCRAFTStationAnvil;ACTI'); // 1
// Editor ID of Objects to replace. Include the Type and the corresponding reference above to replace with stReplaceReference := TStringList.Create; stReplaceReference.Add('CookingPot;STAT;0'); stReplaceReference.Add('Anvil01;STAT;1');
//------------------------------------------------------------------------------- // Get Main Form IDs //------------------------------------------------------------------------------- // Save Main Editor FormIDs for later stRefName := TStringList.Create; for i := 0 to Pred(FileCount) do begin searchFile := FileByIndex(i); if (GetFileName(searchFile) = sModName) then begin RemoveNode(GroupBySignature(searchFile, 'CELL')); RemoveNode(GroupBySignature(searchFile, 'WRLD')); userFile := searchFile; for n := 0 to stObjectEID.Count-1 do begin // Split string for Name and Type stSplitText := TStringList.Create; SplitText(';', stObjectEID[n], stSplitText); sRefName := stSplitText[0]; sRefType := stSplitText[1]; iRef := MainRecordByEditorID(GroupBySignature(searchFile, sRefType), sRefName); stRefName.add(GetEditValue(ElementByPath(iRef, 'Record Header\FormID'))); end; end; end; //------------------------------------------------------------------------------- // File Creation //-------------------------------------------------------------------------------
if not Assigned(userFile) then begin AddMessage('Failed to create patch.'); exit; end; // Patch will be an ESL SetFlag(ElementByPath(ElementByIndex(userFile, 0), 'Record Header\Record Flags'), 9, true);
// Add Base Masters CleanMasters(userFile); AddMasterIfMissing(userFile, 'Oblivion.esm'); AddMasterIfMissing(userFile, 'OCRAFT.esp'); //AddMasterIfMissing(userFile, sModName); SortMasters(userFile); end; //------------------------------------------------------------------------------- // Update all Objects //------------------------------------------------------------------------------- function Finalize: Integer; var stProcessedForms, stSplitText: TStringList; baseIndex, i, refIndex, recIndex, baseObject: integer; iSearchRef, iFoundRef, iNewRef, iRef, iCell, iCellCopy, iGroup: IInterface; sFormID, sRefName, sRefType: string; frm: TForm; clb: TCheckListBox; begin AddMessage('-------------------------------------------------------------------------------'); AddMessage( 'Modifying References' ); AddMessage('-------------------------------------------------------------------------------'); // Setup Processed List stProcessedForms := TStringList.Create; // Process each Base Form for baseIndex := 0 to stReplaceReference.Count-1 do begin // Split the current string and extract Name, Type and the replacement object stSplitText := TStringList.Create; SplitText(';', stReplaceReference[baseIndex], stSplitText); sRefName := stSplitText[0]; sRefType := stSplitText[1]; baseObject := stSplitText[2]; // Find the Base Form ID of the object we are working with for i := 0 to Pred(FileCount) do begin iSearchRef := MainRecordByEditorID(GroupBySignature(FileByIndex(i), sRefType), sRefName); if Assigned(iSearchRef) then break; end; // Get all of the references to the Base Form for refIndex := ReferencedByCount(MasterOrSelf(iSearchRef))-1 downto 0 do begin iFoundRef := ReferencedByIndex(MasterOrSelf(iSearchRef), refIndex); sFormID := GetEditValue(ElementByPath(iFoundRef, 'Record Header\FormID')); // If it is not a reference we skip it if Signature(iFoundRef) <> 'REFR' then continue; // Make sure we have not already processed it if (wbStringListInString(stProcessedForms, sFormID) = -1) then begin if not ( IsWinningOverride(iFoundRef)) then begin //AddMessage('Skipping reference ' + sformID); stProcessedForms.add(sFormID); continue; end; AddMessage('Comparing ' + sformId + ' element ' + Name(iSearchRef) + ', name ' + GetElementEditValues(iFoundRef, 'NAME'));
// Copy Cell Data iCell := WinningOverride(LinksTo(ElementByName(iFoundRef, 'Cell'))); UpdateMasters(iCell);
iCellCopy := wbCopyElementToFile(iCell, userFile, false, true); // Add Required masters and copy element to our file UpdateMasters(iFoundRef); iNewRef := wbCopyElementToFile(iFoundRef, userFile, false, true); // Update the reference with the new name SetElementEditValues(iNewRef, 'NAME', stRefName[baseObject]); // Show Processing Message if GetElementEditValues(iCellCopy, 'FULL') = '' then AddMessage('Modified Reference at: ' + GetElementEditValues(iCellCopy, 'Worldspace')) else AddMessage('Modified Reference at: ' + GetElementEditValues(iCellCopy, 'FULL')); // Add the form we just processed stProcessedForms.add(sFormID); end; end;
// Clean Masters CleanMasters(userFile); end; // Clean and Sort Masters CleanMasters(userFile); SortMasters(userFile); // Free Memory stProcessedForms.Free; AddMessage('-------------------------------------------------------------------------------'); AddMessage('Processing Complete'); AddMessage('-------------------------------------------------------------------------------'); end; //------------------------------------------------------------------------------- // Split String //------------------------------------------------------------------------------- procedure SplitText(aDelimiter: Char; const s: String; aList: TStringList); begin aList.Delimiter := aDelimiter; aList.StrictDelimiter := True; // Spaces excluded from being a delimiter aList.DelimitedText := s; end; //------------------------------------------------------------------------------- // Set Flags //------------------------------------------------------------------------------- procedure SetFlag(element: IInterface; index: Integer; state: boolean); var mask: Integer; begin mask := 1 shl index; if state then SetNativeValue(element, GetNativeValue(element) or mask) else SetNativeValue(element, GetNativeValue(element) and not mask); end; //------------------------------------------------------------------------------- // Update Masters //------------------------------------------------------------------------------- procedure UpdateMasters(element: IInterface); var i: Integer; workFile: iwbFile; begin workFile := GetFile(element); for i := 0 to MasterCount(workFile) - 1 do begin AddMasterIfMissing(userFile, GetFileName(MasterByIndex(workFile, i))); end; AddMasterIfMissing(userFile, GetFileName(workFile)); end; end.
There's some conflicts with the Unofficial Oblivion Patch, concerning the placement of some items, and they float in the air a bit because they undo the Z position:
And you should probably add Shivering Isles to the requirements tab, also.
I just booted up good ol' Oblivion from an old DVD and I have the base game only. I even had to patch it up with the official patches to use other mods lol I know it's unusual but here I am, the rare exception :P :D
Most mods doesn't require Shivering Isles and I barely encounter any that dependent on it but it's still good to know if it's needed in order to use a mod.
Anyway good concept and good work! :D Keep it up!
Once I'll get the DLC / the Deluxe edition I'll try your mod too
?Will these crafting actions ever reward XP if you are also using the Ultimate Leveling mod?
I don't use it, but I'm assuming there is some command I can use for compatibility. I'll check the documentation. Also, I'm not sure how much XP such mundane action should give?
Oooh, this is good! I'm currently using that Skyrim Crafting mod (forgot the actual name), but it's got too many esps, the grammar is a bit messy and the interface isn't very comfortable. Can't wait to see what people make of this framework!
41 comments
However, some recipes for base game items, Skyrim Ingredients, and COBL items are already there.
Well guess I'll have to see for myself in any case, but I'm still excited to see where this goes.
I really hope to see this project evolving to fully support all vanilla craftablish assets. As my two cent, I am providing an adapted xedit plugin to convert all static references to CookingPot and Anvil01 to their equivalent active ones OCRAFTStationCookingPot and OCRAFTStationAnvil.
To use this script you need to create an empty plugin named 'Static Replacer - Extended' (you can manually change the name in the script for whatever) and copy the references for the actives elements over to the new plugin. Then select the new plugin and apply the script. This also has the benefit of carrying over all previous form changes so that no conflict resolution patch would be needed:
{
Purpose: Reference Replace
Game: The Elder Scrolls V: Skyrim SE
Original Author: Sandman53
Adapted by: Darkaxt
Version: 1.1
Instructions:
1. Add Editor IDs and Masters
2. Apply Script
}
unit UserScript;
//-------------------------------------------------------------------------------
// Global Variables
//-------------------------------------------------------------------------------
var
stReplaceReference, stRefName: TStringList;
userFile, iReplaceRef: IInterface;
sRefType: string;
//-------------------------------------------------------------------------------
// Initialize Variables and File
//-------------------------------------------------------------------------------
function Initialize: integer;
var
stObjectEID, stSplitText: TStringList;
i, n: integer;
sModName, sRefName, sRefType: string;
iRef, searchFile: IInterface;
begin
//-------------------------------------------------------------------------------
// Setup Variables
//-------------------------------------------------------------------------------
// Mod Name
sModName := 'Static Replacer - Extended.esp';
// Base Objects that will replacing existing object. The delimiter seperates the objects type
stObjectEID := TStringList.Create;
stObjectEID.Add('OCRAFTStationCookingPot;ACTI'); // 0
stObjectEID.Add('OCRAFTStationAnvil;ACTI'); // 1
// Editor ID of Objects to replace. Include the Type and the corresponding reference above to replace with
stReplaceReference := TStringList.Create;
stReplaceReference.Add('CookingPot;STAT;0');
stReplaceReference.Add('Anvil01;STAT;1');
//-------------------------------------------------------------------------------
// Get Main Form IDs
//-------------------------------------------------------------------------------
// Save Main Editor FormIDs for later
stRefName := TStringList.Create;
for i := 0 to Pred(FileCount) do begin
searchFile := FileByIndex(i);
if (GetFileName(searchFile) = sModName) then begin
RemoveNode(GroupBySignature(searchFile, 'CELL'));
RemoveNode(GroupBySignature(searchFile, 'WRLD'));
userFile := searchFile;
for n := 0 to stObjectEID.Count-1 do begin
// Split string for Name and Type
stSplitText := TStringList.Create;
SplitText(';', stObjectEID[n], stSplitText);
sRefName := stSplitText[0];
sRefType := stSplitText[1];
iRef := MainRecordByEditorID(GroupBySignature(searchFile, sRefType), sRefName);
stRefName.add(GetEditValue(ElementByPath(iRef, 'Record Header\FormID')));
end;
end;
end;
//-------------------------------------------------------------------------------
// File Creation
//-------------------------------------------------------------------------------
if not Assigned(userFile) then begin
AddMessage('Failed to create patch.');
exit;
end;
// Patch will be an ESL
SetFlag(ElementByPath(ElementByIndex(userFile, 0), 'Record Header\Record Flags'), 9, true);
// Add Base Masters
CleanMasters(userFile);
AddMasterIfMissing(userFile, 'Oblivion.esm');
AddMasterIfMissing(userFile, 'OCRAFT.esp');
//AddMasterIfMissing(userFile, sModName);
SortMasters(userFile);
end;
//-------------------------------------------------------------------------------
// Update all Objects
//-------------------------------------------------------------------------------
function Finalize: Integer;
var
stProcessedForms, stSplitText: TStringList;
baseIndex, i, refIndex, recIndex, baseObject: integer;
iSearchRef, iFoundRef, iNewRef, iRef, iCell, iCellCopy, iGroup: IInterface;
sFormID, sRefName, sRefType: string;
frm: TForm;
clb: TCheckListBox;
begin
AddMessage('-------------------------------------------------------------------------------');
AddMessage( 'Modifying References' );
AddMessage('-------------------------------------------------------------------------------');
// Setup Processed List
stProcessedForms := TStringList.Create;
// Process each Base Form
for baseIndex := 0 to stReplaceReference.Count-1 do begin
// Split the current string and extract Name, Type and the replacement object
stSplitText := TStringList.Create;
SplitText(';', stReplaceReference[baseIndex], stSplitText);
sRefName := stSplitText[0];
sRefType := stSplitText[1];
baseObject := stSplitText[2];
// Find the Base Form ID of the object we are working with
for i := 0 to Pred(FileCount) do begin
iSearchRef := MainRecordByEditorID(GroupBySignature(FileByIndex(i), sRefType), sRefName);
if Assigned(iSearchRef) then
break;
end;
// Get all of the references to the Base Form
for refIndex := ReferencedByCount(MasterOrSelf(iSearchRef))-1 downto 0 do begin
iFoundRef := ReferencedByIndex(MasterOrSelf(iSearchRef), refIndex);
sFormID := GetEditValue(ElementByPath(iFoundRef, 'Record Header\FormID'));
// If it is not a reference we skip it
if Signature(iFoundRef) <> 'REFR' then
continue;
// Make sure we have not already processed it
if (wbStringListInString(stProcessedForms, sFormID) = -1) then begin
if not ( IsWinningOverride(iFoundRef)) then begin
//AddMessage('Skipping reference ' + sformID);
stProcessedForms.add(sFormID);
continue;
end;
AddMessage('Comparing ' + sformId + ' element ' + Name(iSearchRef) + ', name ' + GetElementEditValues(iFoundRef, 'NAME'));
// Copy Cell Data
iCell := WinningOverride(LinksTo(ElementByName(iFoundRef, 'Cell')));
UpdateMasters(iCell);
iCellCopy := wbCopyElementToFile(iCell, userFile, false, true);
// Add Required masters and copy element to our file
UpdateMasters(iFoundRef);
iNewRef := wbCopyElementToFile(iFoundRef, userFile, false, true);
// Update the reference with the new name
SetElementEditValues(iNewRef, 'NAME', stRefName[baseObject]);
// Show Processing Message
if GetElementEditValues(iCellCopy, 'FULL') = '' then
AddMessage('Modified Reference at: ' + GetElementEditValues(iCellCopy, 'Worldspace'))
else
AddMessage('Modified Reference at: ' + GetElementEditValues(iCellCopy, 'FULL'));
// Add the form we just processed
stProcessedForms.add(sFormID);
end;
end;
// Clean Masters
CleanMasters(userFile);
end;
// Clean and Sort Masters
CleanMasters(userFile);
SortMasters(userFile);
// Free Memory
stProcessedForms.Free;
AddMessage('-------------------------------------------------------------------------------');
AddMessage('Processing Complete');
AddMessage('-------------------------------------------------------------------------------');
end;
//-------------------------------------------------------------------------------
// Split String
//-------------------------------------------------------------------------------
procedure SplitText(aDelimiter: Char; const s: String; aList: TStringList);
begin
aList.Delimiter := aDelimiter;
aList.StrictDelimiter := True; // Spaces excluded from being a delimiter
aList.DelimitedText := s;
end;
//-------------------------------------------------------------------------------
// Set Flags
//-------------------------------------------------------------------------------
procedure SetFlag(element: IInterface; index: Integer; state: boolean);
var
mask: Integer;
begin
mask := 1 shl index;
if state then
SetNativeValue(element, GetNativeValue(element) or mask)
else
SetNativeValue(element, GetNativeValue(element) and not mask);
end;
//-------------------------------------------------------------------------------
// Update Masters
//-------------------------------------------------------------------------------
procedure UpdateMasters(element: IInterface);
var
i: Integer;
workFile: iwbFile;
begin
workFile := GetFile(element);
for i := 0 to MasterCount(workFile) - 1 do begin
AddMasterIfMissing(userFile, GetFileName(MasterByIndex(workFile, i)));
end;
AddMasterIfMissing(userFile, GetFileName(workFile));
end;
end.
And you should probably add Shivering Isles to the requirements tab, also.
Shivering Isles is part of Oblivion.esm. Since you literally can't play without it, I think adding it as a "requirement" is kinda strange.
I know it's unusual but here I am, the rare exception :P :D
Most mods doesn't require Shivering Isles and I barely encounter any that dependent on it but it's still good to know if it's needed in order to use a mod.
Anyway good concept and good work! :D Keep it up!
Once I'll get the DLC / the Deluxe edition I'll try your mod too
Will these crafting actions ever reward XP if you are also using the Ultimate Leveling mod?
Also, I'm not sure how much XP such mundane action should give?
"Ultimate Leveling can automatically support mods using that as long as they call the custom OnXPMessage event handler."
Probably similar to creating a potion, which is 6.5 XP I believe.
I've also noticed some minor conflicts with Better Camps. I will make a patch probably: