2018-07-28 21:01:19 -05:00
using System ;
using System.Collections.Generic ;
using System.IO ;
using System.Text.RegularExpressions ;
2019-01-26 10:09:13 -06:00
2018-08-11 16:12:08 -04:00
using ACE.Database.Models.World ;
using ACE.Entity.Enum ;
2018-07-28 21:01:19 -05:00
using ACE.Entity.Enum.Properties ;
namespace ACE.Database.SQLFormatters
{
public abstract class SQLWriter
{
protected static readonly Regex IllegalInFileName = new Regex ( $"[{Regex.Escape(new string(Path.GetInvalidFileNameChars()))}]" , RegexOptions . Compiled ) ;
/// <summary>
/// Set this to enable auto commenting when creating SQL statements.<para />
/// If a weenie id is found in the dictionary, the name will be added in the form of a /* Friendly Weenie Name */
/// </summary>
2019-01-26 10:09:13 -06:00
public IDictionary < uint , string > WeenieNames ;
2018-07-28 21:01:19 -05:00
/// <summary>
/// Set this to enable auto commenting when creating SQL statements.<para />
/// If a spell id is found in the dictionary, the name will be added in the form of a /* Friendly Spell Name */
/// </summary>
2019-01-26 10:09:13 -06:00
public IDictionary < uint , string > SpellNames ;
2018-07-28 21:01:19 -05:00
/// <summary>
/// Set this to enable auto commenting when creating SQL statements.<para />
/// If a opcode is found in the dictionary, the name will be added in the form of a /* Friendly Opcode Name */
/// </summary>
2019-01-26 10:09:13 -06:00
public IDictionary < uint , string > PacketOpCodes ;
2018-07-28 21:01:19 -05:00
2018-08-11 16:12:08 -04:00
/// <summary>
/// Set this to enable auto commenting when creating SQL statements.<para />
/// </summary>
2019-01-26 10:09:13 -06:00
public IDictionary < uint , List < TreasureWielded > > TreasureWielded ;
2018-08-11 16:12:08 -04:00
/// <summary>
/// Set this to enable auto commenting when creating SQL statements.<para />
/// </summary>
2019-01-26 10:09:13 -06:00
public IDictionary < uint , TreasureDeath > TreasureDeath ;
2018-08-11 16:12:08 -04:00
/// <summary>
/// Set this to enable auto commenting when creating SQL statements.<para />
/// </summary>
2019-01-26 10:09:13 -06:00
public IDictionary < uint , Weenie > Weenies ;
2018-08-11 16:12:08 -04:00
2018-07-28 21:01:19 -05:00
/// <summary>
/// lineGenerator should generate the entire line after the first (. It should include the trailing ) and any comments after.<para />
/// It should consist of only a single line.<para />
/// This will automatically call FixNullFields on the output created by lineGenerator()
/// </summary>
protected static void ValuesWriter ( int count , Func < int , string > lineGenerator , StreamWriter writer )
{
for ( int i = 0 ; i < count ; i + + )
{
string output ;
if ( i = = 0 )
output = "VALUES (" ;
else
output = " , (" ;
output + = lineGenerator ( i ) ;
if ( i = = count - 1 )
output + = ";" ;
output = FixNullFields ( output ) ;
writer . WriteLine ( output ) ;
}
}
/// <summary>
/// If input is null, NULL will be returned.<para />
/// If input is not null, a string surrounded in ' will be returned, and any ' found within the string will be replaced wtih ''
/// </summary>
protected static string GetSQLString ( string input )
{
if ( input = = null )
return null ;
return "'" + input . Replace ( "'" , "''" ) + "'" ;
}
/// <summary>
/// This will find values that were not output to a values line, for example, if a property is a (int?), and it has no value, you might see ", ," in the sql.<para />
/// This function will replace that ", ," with ", NULL,".<para />
/// It also removes empty comments like the folliwng: " /* */"
/// </summary>
protected static string FixNullFields ( string input )
{
input = input . Replace ( ", ," , ", NULL," ) ;
input = input . Replace ( ", ," , ", NULL," ) ;
// Fix cases where the last field might be null
input = input . Replace ( ", )" , ", NULL)" ) ;
// Remove empty comments
input = input . Replace ( " /* */" , "" ) ;
return input ;
}
protected string GetValueEnumName ( PropertyInt property , int value )
{
switch ( property )
{
case PropertyInt . ActivationCreateClass :
2019-12-11 12:49:00 -06:00
case PropertyInt . AttackersClass :
case PropertyInt . PetClass :
2018-07-28 21:01:19 -05:00
if ( WeenieNames ! = null )
{
WeenieNames . TryGetValue ( ( uint ) value , out var propertyValueDescription ) ;
return propertyValueDescription ;
}
break ;
2018-10-15 03:51:49 -04:00
case PropertyInt . ActivationResponse :
return ( ( ActivationResponse ) value ) . ToString ( ) ;
2018-08-11 16:12:08 -04:00
case PropertyInt . AetheriaBitfield :
return ( ( AetheriaBitfield ) value ) . ToString ( ) ;
case PropertyInt . AiAllowedCombatStyle :
return ( ( CombatStyle ) value ) . ToString ( ) ;
2020-07-12 18:40:05 -04:00
case PropertyInt . AppraisalLongDescDecoration :
return ( ( AppraisalLongDescDecorations ) value ) . ToString ( ) ;
2018-08-11 16:12:08 -04:00
case PropertyInt . AttackType :
return ( ( AttackType ) value ) . ToString ( ) ;
case PropertyInt . ChannelsActive :
case PropertyInt . ChannelsAllowed :
return ( ( Channel ) value ) . ToString ( ) ;
case PropertyInt . ClothingPriority :
return ( ( CoverageMask ) value ) . ToString ( ) ;
case PropertyInt . CurrentWieldedLocation :
return ( ( EquipMask ) value ) . ToString ( ) ;
case PropertyInt . DamageType :
return ( ( DamageType ) value ) . ToString ( ) ;
case PropertyInt . DefaultCombatStyle :
return ( ( CombatStyle ) value ) . ToString ( ) ;
case PropertyInt . HookType :
return ( ( HookType ) value ) . ToString ( ) ;
case PropertyInt . ImbuedEffect :
case PropertyInt . ImbuedEffect2 :
case PropertyInt . ImbuedEffect3 :
case PropertyInt . ImbuedEffect4 :
return ( ( ImbuedEffectType ) value ) . ToString ( ) ;
case PropertyInt . ItemUseable :
return ( ( Usable ) value ) . ToString ( ) ;
case PropertyInt . MerchandiseItemTypes :
return ( ( ItemType ) value ) . ToString ( ) ;
case PropertyInt . PhysicsState :
return ( ( PhysicsState ) value ) . ToString ( ) ;
case PropertyInt . PortalBitmask :
return ( ( PortalBitmask ) value ) . ToString ( ) ;
case PropertyInt . SlayerCreatureType :
return ( ( CreatureType ) value ) . ToString ( ) ;
case PropertyInt . TargetType :
2018-08-11 18:21:55 -04:00
return ( ( ItemType ) value ) . ToString ( ) ;
2018-08-11 16:12:08 -04:00
case PropertyInt . UiEffects :
return ( ( UiEffects ) value ) . ToString ( ) ;
case PropertyInt . ValidLocations :
return ( ( EquipMask ) value ) . ToString ( ) ;
case PropertyInt . WieldRequirements :
case PropertyInt . WieldRequirements2 :
case PropertyInt . WieldRequirements3 :
case PropertyInt . WieldRequirements4 :
return ( ( WieldRequirement ) value ) . ToString ( ) ;
2019-01-26 19:19:06 -06:00
case PropertyInt . CombatTactic :
case PropertyInt . HomesickTargetingTactic :
case PropertyInt . TargetingTactic :
return ( ( TargetingTactic ) value ) . ToString ( ) ;
case PropertyInt . Tolerance :
return ( ( Tolerance ) value ) . ToString ( ) ;
case PropertyInt . AiOptions :
return ( ( AiOption ) value ) . ToString ( ) ;
2021-08-02 14:24:10 -04:00
case PropertyInt . Faction1Bits :
case PropertyInt . Faction2Bits :
case PropertyInt . Faction3Bits :
case PropertyInt . Hatred1Bits :
case PropertyInt . Hatred2Bits :
case PropertyInt . Hatred3Bits :
return ( ( FactionBits ) value ) . ToString ( ) ;
2021-10-18 21:33:27 -04:00
case PropertyInt . CharacterTitleId :
return ( ( CharacterTitle ) value ) . ToString ( ) ;
2018-07-28 21:01:19 -05:00
}
return property . GetValueEnumName ( value ) ;
}
protected string GetValueEnumName ( PropertyDataId property , uint value )
{
switch ( property )
{
case PropertyDataId . AlternateCurrency :
case PropertyDataId . AugmentationCreateItem :
case PropertyDataId . LastPortal :
case PropertyDataId . LinkedPortalOne :
case PropertyDataId . LinkedPortalTwo :
case PropertyDataId . OriginalPortal :
case PropertyDataId . UseCreateItem :
case PropertyDataId . VendorsClassId :
2020-07-03 23:15:28 -04:00
case PropertyDataId . PCAPPhysicsDIDDataTemplatedFrom :
2018-07-28 21:01:19 -05:00
if ( WeenieNames ! = null )
{
WeenieNames . TryGetValue ( value , out var propertyValueDescription ) ;
return propertyValueDescription ;
}
break ;
case PropertyDataId . BlueSurgeSpell :
case PropertyDataId . DeathSpell :
case PropertyDataId . ProcSpell :
case PropertyDataId . RedSurgeSpell :
case PropertyDataId . Spell :
case PropertyDataId . YellowSurgeSpell :
if ( SpellNames ! = null )
{
SpellNames . TryGetValue ( value , out var propertyValueDescription ) ;
return propertyValueDescription ;
}
break ;
2018-08-11 16:12:08 -04:00
case PropertyDataId . WieldedTreasureType :
2021-10-18 21:33:27 -04:00
if ( TreasureWielded ! = null & & TreasureWielded . ContainsKey ( value ) )
2018-08-11 16:12:08 -04:00
{
2023-04-08 13:34:04 -04:00
return GetValuesForTreasureDID ( TreasureWielded [ value ] ) ;
2018-08-11 16:12:08 -04:00
}
2021-10-18 21:33:27 -04:00
else if ( TreasureDeath ! = null & & TreasureDeath . ContainsKey ( value ) )
2018-08-11 16:12:08 -04:00
{
2021-10-18 21:33:27 -04:00
return $"Loot Tier: {TreasureDeath[value].Tier}" ;
2018-08-11 16:12:08 -04:00
}
break ;
case PropertyDataId . DeathTreasureType :
2021-10-18 21:33:27 -04:00
if ( TreasureDeath ! = null & & TreasureDeath . ContainsKey ( value ) )
2018-08-11 16:12:08 -04:00
{
2021-10-18 21:33:27 -04:00
return $"Loot Tier: {TreasureDeath[value].Tier}" ;
2018-08-11 16:12:08 -04:00
}
2021-10-18 21:33:27 -04:00
else if ( TreasureWielded ! = null & & TreasureWielded . ContainsKey ( value ) )
2018-08-11 16:12:08 -04:00
{
2023-04-08 13:34:04 -04:00
return GetValuesForTreasureDID ( TreasureWielded [ value ] ) ;
2018-08-11 16:12:08 -04:00
}
break ;
2023-04-08 14:43:43 -04:00
case PropertyDataId . InventoryTreasureType :
if ( TreasureWielded ! = null & & TreasureWielded . ContainsKey ( value ) )
{
return GetValuesForTreasureDID ( TreasureWielded [ value ] ) ;
}
else if ( TreasureDeath ! = null & & TreasureDeath . ContainsKey ( value ) )
{
return $"Loot Tier: {TreasureDeath[value].Tier}" ;
}
break ;
case PropertyDataId . ShopTreasureType :
if ( TreasureWielded ! = null & & TreasureWielded . ContainsKey ( value ) )
{
return GetValuesForTreasureDID ( TreasureWielded [ value ] ) ;
}
else if ( TreasureDeath ! = null & & TreasureDeath . ContainsKey ( value ) )
{
return $"Loot Tier: {TreasureDeath[value].Tier}" ;
}
break ;
2018-12-07 13:49:17 -05:00
case PropertyDataId . PCAPRecordedObjectDesc :
return ( ( ObjectDescriptionFlag ) value ) . ToString ( ) ;
case PropertyDataId . PCAPRecordedPhysicsDesc :
return ( ( PhysicsDescriptionFlag ) value ) . ToString ( ) ;
case PropertyDataId . PCAPRecordedWeenieHeader :
return ( ( WeenieHeaderFlag ) value ) . ToString ( ) ;
case PropertyDataId . PCAPRecordedWeenieHeader2 :
return ( ( WeenieHeaderFlag2 ) value ) . ToString ( ) ;
2018-07-28 21:01:19 -05:00
}
return property . GetValueEnumName ( value ) ;
}
2018-08-11 16:12:08 -04:00
2023-04-08 13:34:04 -04:00
protected string GetValuesForTreasureDID ( List < TreasureWielded > treasureWieldedList )
2018-08-11 16:12:08 -04:00
{
string treasure = "" ;
2023-04-08 13:34:04 -04:00
var setNumber = 0 ;
var setTotalProbability = 0.0f ;
var nextItemIsStartOfSubSet = false ;
var nextItemIsPartOfSubSet = false ;
var depth = 0 ;
var subSetTotalProbability = new Dictionary < int , float > ( ) ;
foreach ( var item in treasureWieldedList )
{
var treasureItem = "" ;
if ( item . StackSize > 1 & & item . StackSizeVariance = = 0 )
treasureItem + = $"{item.StackSize}x " ;
if ( item . StackSize > 1 & & item . StackSizeVariance > 0 )
{
var minStack = Math . Max ( 1 , ( int ) Math . Round ( item . StackSize * ( 1.0f - item . StackSizeVariance ) , 0 , MidpointRounding . AwayFromZero ) ) ;
var maxStack = item . StackSize ;
if ( minStack ! = maxStack )
treasureItem + = $"{minStack}x to {maxStack}x " ;
else
treasureItem + = $"{maxStack}x " ;
}
treasureItem + = $"{WeenieNames[item.WeenieClassId]} ({item.WeenieClassId})" ;
if ( item . PaletteId > 0 )
treasureItem + = $" | Palette: {Enum.GetName(typeof(PaletteTemplate), item.PaletteId)} ({item.PaletteId})" ;
if ( item . Shade > 0 )
treasureItem + = $" | Shade: {item.Shade}" ;
if ( item . StackSizeVariance > 0 )
treasureItem + = $" | StackSizeVariance: {item.StackSizeVariance}" ;
if ( item . SetStart | | ( setTotalProbability > = 1.0f & & ! nextItemIsPartOfSubSet ) )
{
if ( ( ! nextItemIsStartOfSubSet & & ! item . ContinuesPreviousSet ) | | ( nextItemIsStartOfSubSet & & item . ContinuesPreviousSet ) | | ( setTotalProbability > = 1.0f & & ! nextItemIsStartOfSubSet ) )
{
if ( setTotalProbability > 0.0f & & setTotalProbability < 1.0f )
{
var setProbabilityLeftover = ( 1.0f - setTotalProbability ) * 100 ;
if ( setProbabilityLeftover < 0.01f )
treasure + = Environment . NewLine + $" | {Math.Ceiling(setProbabilityLeftover),6:#.00}% chance of nothing from this set" ;
else
treasure + = Environment . NewLine + $" | {(1.0f - setTotalProbability) * 100,6:#.00}% chance of nothing from this set" ;
}
if ( depth > 0 & & subSetTotalProbability [ depth ] > 0.0f & & subSetTotalProbability [ depth ] < 1.0f )
{
var setProbabilityLeftover = ( 1.0f - subSetTotalProbability [ depth ] ) * 100 ;
if ( setProbabilityLeftover < 0.01f )
treasure + = Environment . NewLine + $" | {new string(' ', 1 * depth)} {Math.Ceiling(setProbabilityLeftover),6:#.00}% chance of nothing from this subset" ;
else
treasure + = Environment . NewLine + $" | {new string(' ', 1 * depth)} {(1.0f - subSetTotalProbability[depth]) * 100,6:#.00}% chance of nothing from this subset" ;
}
treasure + = Environment . NewLine + $" # Set: {++setNumber}" ;
nextItemIsStartOfSubSet = false ;
nextItemIsPartOfSubSet = false ;
setTotalProbability = 0.0f ;
if ( depth > 0 )
depth - - ;
}
else if ( nextItemIsStartOfSubSet )
{
treasure + = Environment . NewLine + $" | {new string(' ', 1 * depth)} with" ;
nextItemIsStartOfSubSet = false ;
nextItemIsPartOfSubSet = true ;
}
else if ( nextItemIsPartOfSubSet & & item . ContinuesPreviousSet )
{
nextItemIsStartOfSubSet = false ;
nextItemIsPartOfSubSet = false ;
if ( depth > 0 )
depth - - ;
}
}
if ( ! nextItemIsPartOfSubSet )
{
if ( ( setTotalProbability + item . Probability ) > 1.0f )
{
var realProbability = item . Probability - ( setTotalProbability + item . Probability - 1.0f ) ;
if ( $"{realProbability:#.00}" ! = $"{item.Probability:#.00}" )
treasureItem + = $" | Chance adjusted down from {item.Probability * 100:#.00}% due to overage for this set" ;
treasure + = Environment . NewLine + $" | {realProbability * 100,6:#.00}% chance of {treasureItem}" ;
}
else
treasure + = Environment . NewLine + $" | {item.Probability * 100,6:#.00}% chance of {treasureItem}" ;
setTotalProbability + = item . Probability ;
}
else
{
treasure + = Environment . NewLine + $" | {new string(' ', 1 * depth)} {item.Probability * 100,6:#.00}% chance of {treasureItem}" ;
subSetTotalProbability [ depth ] + = item . Probability ;
}
if ( item . HasSubSet )
{
nextItemIsStartOfSubSet = true ;
nextItemIsPartOfSubSet = false ;
depth + + ;
subSetTotalProbability [ depth ] = 0.0f ;
}
}
if ( depth > 0 & & subSetTotalProbability [ depth ] > 0.0f & & subSetTotalProbability [ depth ] < 1.0f )
{
var setProbabilityLeftover = ( 1.0f - subSetTotalProbability [ depth ] ) * 100 ;
if ( setProbabilityLeftover < 0.01f )
treasure + = Environment . NewLine + $" | {new string(' ', 1 * depth)} {Math.Ceiling(setProbabilityLeftover),6:#.00}% chance of nothing from this subset" ;
else
treasure + = Environment . NewLine + $" | {new string(' ', 1 * depth)} {(1.0f - subSetTotalProbability[depth]) * 100,6:#.00}% chance of nothing from this subset" ;
}
if ( setTotalProbability > 0.0f & & setTotalProbability < 1.0f )
{
var setProbabilityLeftover = ( 1.0f - setTotalProbability ) * 100 ;
if ( setProbabilityLeftover < 0.01f )
treasure + = Environment . NewLine + $" | {Math.Ceiling(setProbabilityLeftover),6:#.00}% chance of nothing from this set" ;
else
treasure + = Environment . NewLine + $" | {(1.0f - setTotalProbability) * 100,6:#.00}% chance of nothing from this set" ;
}
2018-08-11 16:12:08 -04:00
return treasure ;
}
protected string GetValueForTreasureData ( uint weenieOrType , bool isWeenieClassID = false )
{
string label = "UNKNOWN RANDOMLY GENERATED TREASURE" ;
uint? deathTreasureType = null ;
uint? wieldedTreasureType = null ;
if ( isWeenieClassID )
{
2019-01-19 19:55:26 -06:00
if ( Weenies ! = null & & Weenies . ContainsKey ( weenieOrType ) )
2018-08-11 16:12:08 -04:00
{
deathTreasureType = Weenies [ weenieOrType ] . GetProperty ( PropertyDataId . DeathTreasureType ) ;
wieldedTreasureType = Weenies [ weenieOrType ] . GetProperty ( PropertyDataId . WieldedTreasureType ) ;
}
}
else
{
deathTreasureType = weenieOrType ;
wieldedTreasureType = weenieOrType ;
}
2019-01-19 19:55:26 -06:00
if ( deathTreasureType . HasValue & & wieldedTreasureType . HasValue )
2018-08-11 16:12:08 -04:00
{
2019-01-19 19:55:26 -06:00
if ( TreasureDeath ! = null & & TreasureDeath . ContainsKey ( deathTreasureType . Value ) )
2018-08-11 16:12:08 -04:00
{
2019-01-19 19:55:26 -06:00
label = $"RANDOMLY GENERATED TREASURE from Loot Tier {TreasureDeath[deathTreasureType.Value].Tier} from Death Treasure Table id: {deathTreasureType}" ;
2018-08-11 16:12:08 -04:00
}
2019-01-19 19:55:26 -06:00
else if ( TreasureWielded ! = null & & TreasureWielded . ContainsKey ( wieldedTreasureType . Value ) )
2018-08-11 16:12:08 -04:00
{
2023-04-08 13:34:04 -04:00
label = $"something from one or more sets from Wielded Treasure Table id: {wieldedTreasureType}" ;
label + = GetValuesForTreasureDID ( TreasureWielded [ wieldedTreasureType . Value ] ) ;
2018-08-11 16:12:08 -04:00
}
}
2019-01-19 19:55:26 -06:00
else
{
label = "nothing" ;
}
2018-08-11 16:12:08 -04:00
return label ;
}
2021-11-08 00:41:17 -05:00
protected static float? TrimNegativeZero ( float? input , int places = 6 )
{
if ( input = = null )
return null ;
var str = input . Value . ToString ( $"0.{new string('#', places)}" ) ;
if ( str = = $"-0.{new string('#', places)}" | | str = = "-0" )
str = str . Substring ( 1 , str . Length - 1 ) ;
var ret = float . Parse ( str ) ;
return ret ;
}
2018-07-28 21:01:19 -05:00
}
}