NicePack/sources/Resources/NiceResources.uc
2020-02-16 19:53:59 +07:00

517 lines
18 KiB
Ucode

//==============================================================================
// 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<IDCase> 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<SubjectCase> localResourceCases;
// - received from the server.
var protected array<SubjectCase> remoteResourceCases;
// Points at the particular resource set collection (array<NiceResourceSet>)
// 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<NiceResourceSet>).
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<SubjectCase> localFilteredCases;
var protected array<SubjectCase> remoteFilteredCases;
/*
// Returns level of pointer's validity
// (see below definition of 'CollectionPointer').
private function int GetPointerValidity(CollectionPointer ptr){
local array<SubjectCase> 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<SubjectCase> 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<SubjectCase> 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<SubjectCase> 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<SubjectCase> 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 '<subject>/<ID>/<version>'.
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 '<subject>/<ID>/<version>'.
// If there isn't enough fields, 'version' will contain '-1'.
private function ParseResourceSetName
(
string resourceSetName,
out Title myTitle
){
local array<string> 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<NiceResourceSet> resourceClass
){
local int i;
local array<string> 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<SubjectCase> FilterIDCases( array<IDCase> 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<NiceResourceSet> ToArray(){
local int i, j;
local array<NiceResourceSet> 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
}