//============================================================================== // NicePack / NiceResources //============================================================================== // Class that: // 1. Contains definitions for resources // formats as they're used in a NicePack; // 2. Manages loading resource sets from config files. //============================================================================== // Class hierarchy: Object > NiceResources //============================================================================== // 'Nice pack' source // Do whatever the fuck you want with it // Author: dkanus // E-mail: dkanus@gmail.com //============================================================================== class NiceResources extends Object dependson(NicePlainData); // Mesh data used in NicePack. struct NiceMeshData{ // ID of this sound; // IDs are aliases necessary to allow us to // reference particular types of resources, // while preserving an ability to swap actual resources on the fly. var string ID; // Reference to the mesh object. var string reference; }; // Mesh instance used in NicePack. struct NiceMesh{ // Sound parameters. var NiceMeshData data; // Variable into which we will load our sound. var Mesh meshObject; }; // Sound data used in NicePack. struct NiceSoundData{ // ID of this sound; // IDs are aliases necessary to allow us to // reference particular types of resources, // while preserving an ability to swap actual resources on the fly. var string ID; // Reference to the sound object. var string reference; // Obvious sound parameters. var float volume; var float radius; var float pitch; }; // Sound instance used in NicePack. struct NiceSound{ // Sound parameters. var NiceSoundData data; // Variable into which we will load our sound. var Sound soundObject; }; // Animation data used in NicePack. struct NiceAnimationData{ // ID of this animation; // IDs are aliases necessary to allow us to // reference particular types of resources, // while preserving an ability to swap actual resources on the fly. var string ID; // String representation of this animation's name. var string nameRef; // Starting point to play animation from. var float start; // End point at which we stop playing the animation; // if 'end' <= 'start' - we consider this animation empty. var float end; // Default rate at which this animation will be played; // '<= 0.0' means '1.0'. var float rate; // Channel on which animation will be played by default. var int defaultChannel; }; // Animation instance used in NicePack. // Doesn't correspond 1-to-1 to Unreal Engine's counterpart as it can // consist of only a part of Unreal's animation. // This is to allow us construction of animation that correspond to // subset of existing ones that have a meaning distinctive from // all animation (example is segmented weapon reloadings, // - magazine extraction, insertion, etc.) struct NiceAnimation{ // Animation parameters. var NiceAnimationData data; // Name of animation from which we'll be playing a segment. var name loadedName; }; // Type to use to load a particular paint when loading 'NicePaint'. enum MaterialType{ MT_Combiner, MT_FinalBlend, MT_Shader, MT_Texture, MT_Material, MT_Unknown }; // Material data used in NicePack; // can represent more complex things that actual material, including Combiners. struct NiceMaterialData{ // ID of this material; // IDs are aliases necessary to allow us to // reference particular types of resources, // while preserving an ability to swap actual resources on the fly. var string ID; // First-person skin textures var string reference; var MaterialType materialType; }; // Material instance used in NicePack; // can represent more complex things that actual material, including Combiners. struct NiceMaterial{ // Material parameters. var NiceMaterialData data; // Variable into which we will load our material. var Material materialObject; }; // Each set is uniquely determined by 'Title': // ~ a combination 'ID' and 'subject'; // ~ further more each such combination can come and // be stored with different versions. // Read from a name of the object it's stored as. struct Title{ // ID (not expected to be human-readable) of this resource set; var string ID; // Name of object to which this set relates // (like weapon's or zed's name/id). var config string subject; }; // We introduce following structures to group loaded resources // via subject and ID: // - Resource sets with same subject and ID struct IDCase{ var string ID; var NiceResourceSet resourceSet; }; // - Resource sets with same subject, grouped by ID; struct SubjectCase{ var string subject; var array cases; }; // NICETODO: have to track updates of these things and sync them with server. // Available resource packs: // - loaded from config or define by user during the game; var protected array localResourceCases; // - received from the server. var protected array remoteResourceCases; // Points at the particular resource set collection (array) // in our cases, made to avoid passing tons of indices and hide details. // Always point at a full (not filtered) resource set. // For internal use only. // Considered invalid if at least one index is pointing at inexistent array. struct CollectionPointer{ // Indices for arrays of cases that lead us to // the particular resource set collection (array). var int subjectIndex; var int idIndex; // Do we point at collection in the local storage? var bool isLocal; }; // As 'CollectionPointer' is a set of pointers into nested arrays, // depending on how much of these indices are valid, // we can distinguish several validity levels: // ~ pointer is completely invalid; // (guaranteed to be lowest in value validity value) var const int ptrCompletelyInvalid; // ~ pointer has valid subject index, but invalid id index; // (guaranteed to be between // 'ptrCompletelyInvalid' and 'ptrCompletelyValid') var const int ptrValidSubject; // ~ pointer is completely valid. // (guaranteed to be highest validity value) var const int ptrCompletelyValid; // Filtered resource packs, - possibly incomplete copies of // 'localResourceCases' and 'remoteResourceCases' // that are used as temporary storages to // facilitate filters for search queues. var protected array localFilteredCases; var protected array remoteFilteredCases; /* // Returns level of pointer's validity // (see below definition of 'CollectionPointer'). private function int GetPointerValidity(CollectionPointer ptr){ local array relevantCases; if(isLocal) relevantCases = localResourceCases; else relevantCases = remoteResourceCases; if(ptr.subjectIndex < 0) return ptrCompletelyInvalid; if(relevantCases.length <= ptr.subjectIndex) return ptrCompletelyInvalid; if(ptr.idIndex < 0) return ptrValidSubject; if(relevantCases[ptr.subjectIndex].cases.length <= ptr.idIndex) return ptrValidSubject; return ptrCompletelyValid; } // Validates given pointer with given set: // - creates new cases in given collection, // allowing 'toValidate' to point at something; // - subject and id for new cases are taken from 'resourceSet'; // - result isn't written in a collection itself, but into a passed array. // Assumes pointers at all cases, not the filtered collections. // Previously invalid indices are overwritten with new ones. private function ValidatePointer( out CollectionPointer toValidate, NiceResourceSet resourceSet, out array relevantCases){ local int pointerValidityLevel; local SubjectCase newSubjectCase; local IDCase newIDCase; pointerValidityLevel = GetPointerValidity(toValidate); if(pointerValidityLevel < ptrValidSubject){ toValidate.subjectIndex = relevantCases.length; newSubjectCase.subject = resourceSet.subject; relevantCases[toValidate.subjectIndex] = newSubjectCase; } if(pointerValidityLevel < ptrCompletelyValid){ toValidate.idIndex = relevantCases[toValidate.subjectIndex].cases.length; newIDCase.ID = resourceSet.ID; relevantCases[toValidate.subjectIndex].cases[toValidate.idIndex] = newIDCase; } } // Updates given pointer to point at given subject in all cases. // Wipes indices for id case, making pointer invalid. private function CollectionPointer UpdateSubjectIndex ( CollectionPointer toUpdate, string subjectName ){ local int i; local array relevantCases; toUpdate.subjectIndex = -1; toUpdate.idIndex = -1; if(toUpdate.isLocal) relevantCases = localResourceCases; else relevantCases = remoteResourceCases; for(i = 0;i < relevantCases.length;i ++){ if(relevantCases[i].subject ~= subjectName){ toUpdate.subjectIndex = i; break; } } return toUpdate; } // Updates given pointer to point at given id in all cases. // Fails if it points at invalid subject. private function CollectionPointer UpdateIDIndex ( CollectionPointer toUpdate, string id ){ local int i; local array relevantCases; toUpdate.idIndex = -1; if(GetPointerValidity(toUpdate) < ptrValidSubject) return toUpdate; if(toUpdate.isLocal) relevantCases = localResourceCases[toUpdate.subjectIndex].cases; else relevantCases = remoteResourceCases[toUpdate.subjectIndex].cases; for(i = 0;i < relevantCases.length;i ++){ if(relevantCases[i].id ~= id){ toUpdate.idIndex = i; break; } } return toUpdate; } // Returns indices for subject cases corresponding to // subject and id names given by 'NiceResourceSet' argument. // Returns invalid pointer if indices weren't found. private function CollectionPointer FindCaseIndicies(NiceResourceSet resourceSet, bool isLocal){ local CollectionPointer ptr; ptr.isLocal = isLocal; ptr.idIndex = -1; ptr = UpdateSubjectIndex(ptr, resourceSet.myTitle.subject); ptr = UpdateIDIndex(ptr, resourceSet.myTitle.id); return ptr; } // Inserts given data set into our storage. // If a set with the same subject and ID already exists, - method replaces it. private function UpdateResourceSet(NiceResourceSet updatedSet, bool isLocal){ local CollectionPointer setPointer; local array relevantCases; if(isLocal) relevantCases = localResourceCases; else relevantCases = remoteResourceCases; setPointer = FindCaseIndicies(updatedSet, isLocal); ValidatePointer(setPointer, updatedSet, relevantCases); relevantCases[setPointer.subjectIndex] .cases[setPointer.idIndex].resourceSet = updatedSet; if(isLocal) localResourceCases = relevantCases; else remoteResourceCases = relevantCases; } // Forms resource set name as '//'. private function string FormResourceSetName(Title myTitle){ local NicePathBuilder pathBuilder; pathBuilder = class'NicePack'.static.GetPathBuilder(); return pathBuilder.Flush().AddElement(myTitle.subject) .AddElement(myTitle.ID) .ToString(); } // Parses resource set name in the form '//'. // If there isn't enough fields, 'version' will contain '-1'. private function ParseResourceSetName ( string resourceSetName, out Title myTitle ){ local array pathLiterals; local NicePathBuilder pathBuilder; pathBuilder = class'Nicepack'.static.GetPathBuilder(); pathBuilder.Parse(resourceSetName); myTitle.subject = ""; myTitle.ID = ""; pathLiterals = pathBuilder.GetElements(); if(pathLiterals.length >= 1) myTitle.subject = pathLiterals[0]; if(pathLiterals.length >= 2) myTitle.ID = pathLiterals[1]; } // Loads resource set with a given name without checking if it already exist. private function LoadNamedResourceSetFromConfig(string setName){ local NicePlainData.Data newData; local NiceResourceSet newSet; newSet = new(none, setName) class'NiceResourceSet'; ParseResourceSetName(setName, newSet.myTitle); newData.pairs = newSet.data; newSet.properties = newData; UpdateResourceSet(newSet, true); } function LoadResourceSetsFromConfig ( optional class resourceClass ){ local int i; local array names; if(localResourceCases.length > 0) return; if(resourceClass == none) resourceClass = class'NiceResourceSet'; names = GetPerObjectNames( resourceClass.default.iniName, string(class'NiceResourceSet'.name)); for(i = 0;i < names.length;i ++) LoadNamedResourceSetFromConfig(names[i]); } // Wraps mesh data into unloaded mesh object. function static NiceMesh MeshFromData(NiceMeshData data){ local NiceMesh newMesh; newMesh.data = data; return newMesh; } // Wraps animation data into unloaded animation object. function static NiceSound SoundFromData(NiceSoundData data){ local NiceSound newSounds; newSounds.data = data; return newSounds; } // Wraps sound data into unloaded sound object. function static NiceAnimation AnimationFromData(NiceAnimationData data){ local NiceAnimation newAnimation; newAnimation.data = data; return newAnimation; } // Wraps material data into unloaded material object. function static NiceMaterial MaterialFromData(NiceMaterialData data){ local NiceMaterial newMaterial; newMaterial.data = data; return newMaterial; } // Starts new search through data sets. // NOTE: adding any new data sets requires you to create new search. function NiceResources NewSearch(){ localFilteredCases = localResourceCases; remoteFilteredCases = remoteResourceCases; return self; } // Leaves only local resource sets in current search. // 'LocalOnly().RemoteOnly()' removes all possible sets from current search. function NiceResources LocalOnly(){ remoteFilteredCases.length = 0; } // Leaves only remote resource sets in current search. // 'LocalOnly().RemoteOnly()' removes all possible sets from current search. function NiceResources RemoteOnly(){ localFilteredCases.length = 0; } // Leaves only resource sets with given subject in the current search. function NiceResources OnlySubject(string subject){ local int i; local bool foundGoodCase; foundGoodCase = false; for(i = 0;i < localFilteredCases.length;i ++){ if(localFilteredCases[i].subject ~= subject){ localFilteredCases[0] = localFilteredCases[i]; localFilteredCases.length = 1; foundGoodCase = true; break; } } if(!foundGoodCase) localFilteredCases.length = 0; foundGoodCase = false; for(i = 0;i < remoteFilteredCases.length;i ++){ if(remoteFilteredCases[i].subject ~= subject){ remoteFilteredCases[0] = remoteFilteredCases[i]; remoteFilteredCases.length = 1; foundGoodCase = true; break; } } if(!foundGoodCase) remoteFilteredCases.length = 0; } // Leaves only resource sets with given subject in the current search. function NiceResources OnlyID(string id){ local int i; for(i = 0;i < localFilteredCases.length;i ++){ localFilteredCases[i].cases = FilterIDCases(localFilteredCases[i].cases, id); } for(i = 0;i < localFilteredCases.length;i ++){ remoteFilteredCases[i].cases = FilterIDCases(remoteFilteredCases[i].cases, id); } } // Filters given array of 'IDCase's by leaving only case with given ID. private function array FilterIDCases( array toFilter, string id){ local int i; local bool foundGoodCase; foundGoodCase = false; for(i = 0;i < toFilter.length;i ++){ if(toFilter[i].id ~= id){ toFilter[0] = toFilter[i]; toFilter.length = 1; foundGoodCase = true; break; } } if(!foundGoodCase) toFilter.length = 0; return toFilter; } function array ToArray(){ local int i, j; local array resultSets; for(i = 0;i < localFilteredCases.length;i ++){ for(j = 0;j < localFilteredCases[i].cases.length;j ++){ resultSets[resultSets.length] = localFilteredCases[i].cases[j].resourceSet; } } for(i = 0;i < remoteFilteredCases.length;i ++){ for(j = 0;j < remoteFilteredCases[i].cases.length;j ++){ resultSets[resultSets.length] = remoteFilteredCases[i].cases[j].resourceSet; } } return resultSets; }*/ private function bool RemoteShouldReplace( NiceResourceSet localSet, NiceResourceSet remoteSet){ if(localSet.localAlwaysOverrides || remoteSet.localAlwaysOverrides) return false; return true; } defaultproperties { ptrCompletelyInvalid = 0 ptrValidSubject = 1 ptrCompletelyValid = 2 }