//for storing long numerical data that can't fit inside int Class ScrnCustomProgressBytes extends SRCustomProgress Abstract; const CHR_MAX_VAL = 0x3FFFFFC0; //don't use 6 lowest and 2 highest bits in unicode character (16-bit) const CHR_STEP = 0x40; // 0x40 = 1 >>> 6 const CHR_1ST_BIT = 6; const CHR_CONTROL_VALUE = 1; // highest bits of a character must be "01" const MAX_SIZE = 512; // most left char contains Data[0], right - Data[Data.length-1] var private string OldValueStr; // previous value that client received var private string CurrentValueStr; // value to store in perks DB var protected array Data; var protected bool bFixedDataSize; //if true, Data.Length won't be modified (sublasses need to overrire InitData() to it) var protected bool bReplicateDataToClients; // if true, CurrentValueStr will be always replicated to clients //reason why CurrentValueStr has been updated enum EValueSource { SRC_None, SRC_External, // updated from outer source (e.g. perk DB) SRC_Internal // updated from Data }; var private transient EValueSource ValueSource; enum EStringStoreMethod { SM_Hex, //store data as string representation of hex values (2 chars per byte, but data is human-readable) SM_Unicode //store data inside unicode character (1 char per byte, but human will see crazy unicode chars instead of data) }; var protected EStringStoreMethod StoreMethod; replication { reliable if( bReplicateDataToClients && bNetOwner && ( bNetDirty || bNetInitial ) && Role==ROLE_Authority ) ValueSource, CurrentValueStr; } simulated function PostNetReceive() { super.PostNetReceive(); if ( Role < Role_Authority && OldValueStr != CurrentValueStr ) { //log(GetItemName(String(name))$".PostNetReceive() OldValueStr="$OldValueStr $ " CurrentValueStr="$CurrentValueStr, 'ScrnBalance'); UpdateData(); OldValueStr = CurrentValueStr; } } simulated function EValueSource GetValueSource() { return ValueSource; } simulated function PostBeginPlay() { super.PostBeginPlay(); InitData(); } simulated function InitData(); simulated function string GetProgress() { return CurrentValueStr; } simulated function int GetProgressInt() { return 0; } simulated function string GetDisplayString() { return class'SRCustomProgress'.default.ProgressName; //Lazy modder! Kill him!! :) } function SetProgress( string S ) { if ( Len(S) > MAX_SIZE) { log("ScrnCustomProgressBytes: Passed string is longer than " $ String(MAX_SIZE), 'ScrnBalance'); S = left(S, MAX_SIZE); //trim long string } //log("SetProgress("$S$")", 'ScrnBalance'); CurrentValueStr = S; ValueSource = SRC_External; if ( bReplicateDataToClients ) { NetUpdateTime = Level.TimeSeconds - 1; //send CurrentValueStr to clients } UpdateData(); } function IncrementProgress( int Count ); static function String ByteToBin(byte AValue) { local int i; local string s; while ( i++ < 8 ) { S = String(AValue & 1) $ S; AValue = AValue >>> 1; } return s; } static function String ByteToHex(byte AValue, optional bool bAddPrefix) { local int i; local string s,c; local byte a; while ( i++ < 2 ) { a = AValue & 0xF; if ( a < 10 ) c = chr(0x30 + a); else c = chr(0x41 + a-10); S = c $ S; AValue = AValue >>> 4; } if ( bAddPrefix ) S = "0x" $ S; return S; } static function String IntToHex(int AValue, optional bool bAddPrefix) { local int i; local string s,c; local byte a; while ( i++ < 8 ) { a = AValue & 0xF; if ( a < 10 ) c = chr(0x30 + a); else c = chr(0x41 + a-10); S = c $ S; AValue = AValue >>> 4; } if ( bAddPrefix ) S = "0x" $ S; return S; } static function byte HexToByte(String S) { local int i, end, a; local byte result; if ( left(S, 2) ~= "0x" ) i = 2; //skip prefix end = min(i + 2, len(S)); while ( i < end ) { result = result << 4; a = asc(mid(s, i, 1)); if ( a >= 0x30 && a <= 0x39 ) a -= 0x30; //digit else if ( a >= 0x41 && a <= 0x46 ) a = a + 10 - 0x41; //A-F else if ( a >= 0x61 && a <= 0x46 ) a = a + 10 - 0x61; //a-f else return 0; // illegal character result = result | a; i++; } return result; } static function LogBytes(array AValues) { local int i, a, count, c_len; local string i_str; count = AValues.Length; c_len=len(count); for ( i=0; i < count; ++i) { i_str = string(i+1); while ( len(i_str) < c_len ) i_str = "0" $ i_str; a = AValues[i]; log("#"$i_str$"/"$count$":" @ ByteToBin(a) @ ByteToHex(a,true) @ a, 'ScrnBalance'); } } simulated function LogValues() { log("ScrnCustomProgressBytes: Value of '"$CurrentValueStr$"':", 'ScrnBalance'); LogBytes(Data); } static function StringToValues(string AString, out array AValues) { if ( default.StoreMethod == SM_Unicode) UnicodeStringToValues(AString, AValues); else HexStringToValues(AString, AValues); } static function UnicodeStringToValues(string AString, out array AValues) { local int i, count, val; local string c; if ( !default.bFixedDataSize ) AValues.Length = len(AString); count = min(len(AString), AValues.Length); for ( i = 0; i < count; ++i ) { c = mid(AString,i,1); val = asc(c) >>> CHR_1ST_BIT; if ( val>>>8 != CHR_CONTROL_VALUE ) { // in case server admin manually edits stat table and screws the data log("ScrnCustomProgressBytes: Illegal Character #"$i$" '"$c$"' ("$asc(c)$") in string '"$AString$"'", 'ScrnBalance'); } else { val = val & 0xFF; //clear a highest byte; AValues[i] = val; } } } static function HexStringToValues(string AString, out array AValues) { local int i, count; if ( !default.bFixedDataSize ) AValues.Length = len(AString)/2; //2 chars per one byte count = min(len(AString)/2, AValues.Length); for ( i = 0; i < count; ++i ) { AValues[i] = HexToByte(mid(AString,i*2,2)); } } static function string ValuesToString(array AValues) { if ( default.StoreMethod == SM_Unicode) return ValuesToUnicodeString(AValues); else return ValuesToHexString(AValues); } static function string ValuesToUnicodeString(array AValues) { local int i; local string s; for ( i = 0; i < AValues.length; ++i ) { // add contro value, shift left 8 bits, fill data, shift dummy bits, which fill with random numbers s $= chr((CHR_CONTROL_VALUE<<8 | AValues[i])< AValues) { local int i; local string s; for ( i = 0; i < AValues.length; ++i ) { s $= ByteToHex(AValues[i], false); } return s; } simulated function UpdateData() { StringToValues(CurrentValueStr, Data); //LogValues(); } //clients can't update String, they just receive it from server function UpdateValueString() { CurrentValueStr = ValuesToString(Data); ValueSource = SRC_Internal; if ( bReplicateDataToClients ) NetUpdateTime = Level.TimeSeconds - 1; //send CurrentValueStr to clients } /* protected function FillWithTestData(int ValueCount) { local int i; Data.Length = ValueCount; for ( i = 0; i < ValueCount; ++i ) { Data[i] = i; } UpdateString(); } */ defaultproperties { bNetNotify=True }