@@ -1,7 +1,9 @@
using System.Collections.Generic ;
using System.Diagnostics ;
using System.Linq ;
using System.Text.RegularExpressions ;
using System.Xml.Linq ;
using Microsoft.EntityFrameworkCore ;
using Newtonsoft.Json.Linq ;
using Pf2eDataScrapper.Importers.Implementations.Aonprd.DataBuilder.Helpers ;
using Pf2eModel ;
@@ -46,11 +48,10 @@ internal partial class RitualProcessor(AonprdProcessorInput input) : AbstractEnt
ProcessArea ( ) ;
Object . PrimaryCheckSuffix = Input . GetOptionalLocale (
"primary_check_markdown" ,
LocaleType . English
) ;
// ProcessPrimaryCheck();
ProcessPrimaryChecks ( ) ;
ProcessSecondaryChecks ( ) ;
ProcessSecondaryCasters ( ) ;
}
private int CalculateCastTime ( string value )
@@ -100,7 +101,50 @@ internal partial class RitualProcessor(AonprdProcessorInput input) : AbstractEnt
Context . RitualAreas . Add ( ritualArea ) ;
}
private void ProcessPrimaryCheck ( )
private readonly Dictionary < string , string > _castersCleanup = new ( )
{
[", up to a maximum of "] = "-" ,
[" or more"] = "-999" ,
[" (or more)"] = "-999" ,
["up to "] = "0-" ,
[" to "] = "-" ,
["at least 1"] = "1-999" ,
} ;
private void ProcessSecondaryCasters ( )
{
string? input = Input . GetOptional < string > ( "secondary_casters_raw" ) ;
if ( string . IsNullOrWhiteSpace ( input ) )
{
return ;
}
foreach ( KeyValuePair < string , string > pair in _castersCleanup )
{
input = input . Replace ( pair . Key , pair . Value ) ;
}
Match match = CasterRegex ( ) . Match ( input ) ;
if ( ! match . Success )
{
throw new ( $"Can't parse secondary casters: \" { input } \ "" ) ;
}
Object . SecondaryCasters = int . Parse ( M ( 1 ) ) ;
Object . SecondaryCastersMax = ( M ( 2 ) ! = "" ) ? int . Parse ( M ( 2 ) ) : Object . SecondaryCasters ;
if ( M ( 3 ) ! = "" )
{
Object . SecondaryCastersSuffix = new ( ) { [ LocaleType . English ] = M ( 3 ) } ;
}
return ;
string M ( int i ) = > match . Groups [ i ] . Value ;
}
private void ProcessPrimaryChecks ( )
{
string? check = Input . GetOptional < string > ( "primary_check_markdown" ) ;
@@ -109,21 +153,129 @@ internal partial class RitualProcessor(AonprdProcessorInput input) : AbstractEnt
return ;
}
List < string > parts = CheckStringPrepare ( check ) ;
string suffix = "" ;
// Обработка частей
List < RitualCheck > ritualChecks = parts
. Select ( part = > ResolveCheck ( part . Trim ( ) , true , out suffix ) )
. ToList ( ) ;
if (
ritualChecks . Any ( c = > c . Rank = = TrainingRank . Untrained )
& & ritualChecks . Any ( c = > c . Rank ! = TrainingRank . Untrained )
)
{
// Смешанные ранги
TrainingRank [ ] ranks = ritualChecks
. Select ( c = > c . Rank )
. Distinct ( )
. Where ( r = > r ! = TrainingRank . Untrained )
. ToArray ( ) ;
if ( ranks . Length > 1 )
{
throw new ( $"Can't resolve mixed ranks: {string.Join(" , ", ranks)}" ) ;
}
foreach (
RitualCheck ritualCheck in ritualChecks . Where ( r = > r . Rank = = TrainingRank . Untrained )
)
{
ritualCheck . Rank = ranks [ 0 ] ;
}
}
if ( suffix ! = "" )
{
Object . PrimaryCheckSuffix = new ( ) { [ LocaleType . English ] = suffix } ;
}
foreach ( RitualCheck ritualCheck in ritualChecks )
{
Object . Checks . Add ( ritualCheck ) ;
Context . RitualChecks . Add ( ritualCheck ) ;
}
}
private void ProcessSecondaryChecks ( )
{
string? check = Input . GetOptional < string > ( "secondary_check_markdown" ) ;
if ( string . IsNullOrWhiteSpace ( check ) )
{
return ;
}
List < string > parts = CheckStringPrepare ( check ) ;
foreach ( string part in parts )
{
RitualCheck ritualCheck = ResolveCheck ( part . Trim ( ) , false , out string suffix ) ;
if ( suffix ! = "" )
{
if ( ritualCheck . TrainingString is not null )
{
ritualCheck . TrainingString [ LocaleType . English ] + = $" ({suffix})" ;
}
else
{
ritualCheck . TrainingString = new ( ) { [ LocaleType . English ] = suffix } ;
}
}
Object . Checks . Add ( ritualCheck ) ;
Context . RitualChecks . Add ( ritualCheck ) ;
}
}
private readonly Dictionary < string , string > _checksCleanupReplaces = new ( )
{
["DC 35 Arcana"] = "Arcana <DC 35>" ,
[")or"] = ") or" ,
["[Lore] ( / Skills . aspx ? ID = 41 ) ( any ) "] = " [ Any Lore ] ( / Skills . aspx ? ID = 41 ) ",
["[Lore] ( / Skills . aspx ? ID = 8 ) ( any ) "] = " [ Any Lore ] ( / Skills . aspx ? ID = 8 ) ",
} ;
private readonly Dictionary < Regex , string > _checksCleanupRegexes = new ( )
{
[new(@"(\w+) \[Lore\] ", RegexOptions.Compiled | RegexOptions.Multiline)] = " [ $ 1 Lore ] ",
} ;
private List < string > CheckStringPrepare ( string input )
{
// Отчистка проблемных частей
foreach ( KeyValuePair < string , string > pair in _checksCleanupReplaces )
{
input = input . Replace ( pair . Key , pair . Value ) ;
}
foreach ( KeyValuePair < Regex , string > pair in _checksCleanupRegexes )
{
input = pair . Key . Replace ( input , pair . Value ) ;
}
// Замена символа ссылок
check = StringHelper . MarkdownRegex ( ) . Replace ( check , "$1[$2]{$3}" ) ;
check = check . Replace ( "(" , "<" ) . Replace ( ")" , ">" ) . Replace ( "{" , "(" ) . Replace ( "}" , ")" ) ;
input = StringHelper . MarkdownRegex ( ) . Replace ( input , "$1[$2]{$3}" ) ;
input = input . Replace ( "(" , "<" ) . Replace ( ")" , ">" ) . Replace ( "{" , "(" ) . Replace ( "}" , ")" ) ;
if ( input . EndsWith ( ';' ) | | input . EndsWith ( ',' ) )
{
input = input [ . . ^ 1 ] . Trim ( ) ;
}
// Временная замена скобок ранга и дополнения
#pragma warning disable SYSLIB1045
MatchCollection rankMatches = new Regex ( @"<(.*?)>" ) . Matches ( check ) ;
MatchCollection rankMatches = new Regex ( @"<(.*?)>" ) . Matches ( input ) ;
#pragma warning restore SYSLIB1045
for ( int i = 0 ; i < rankMatches . Count ; i + + )
{
check = check . Replace ( rankMatches [ i ] . Value , $"{{rank-{i}}}" ) ;
input = input . Replace ( rankMatches [ i ] . Value , $"{{rank-{i}}}" ) ;
}
// Разбиваем строку на части
List < string > parts = StringHelper . RowSplit ( check ) ;
List < string > parts = StringHelper . RowSplit ( input ) ;
// Возвращаем скобки ранга и дополнения
for ( int i = 0 ; i < rankMatches . Count ; i + + )
@@ -134,71 +286,14 @@ internal partial class RitualProcessor(AonprdProcessorInput input) : AbstractEnt
}
}
// Обработка частей
List < RitualCheck > ritualChecks = parts . Select ( part = > ResolveCheck ( part , false ) ) . ToList ( ) ;
// TODO
// 1. Проверить ritualChecks, если в каких-то Untrained, а в каком-то есть ранг - все к этому рангу
// 2. Поставить primarySuffix
// 3. Добавить в коллекцию
return parts ;
}
private List < RitualCheck > ResolveChecks ( string input , bool isSecondary )
private RitualCheck ResolveCheck ( string input , bool isPrimary , out string suffix )
{
List < RitualCheck > result = [ ] ;
List < List < string > > checkStrings = [ ] ;
foreach ( string part in StringHelper . RowSplit ( input ) )
{
Match match = StringHelper . MarkdownRegex ( ) . Match ( part ) ;
if ( match . Success )
{
string prefix = match . Groups [ 1 ] . Value . Trim ( ) ;
string text = match . Groups [ 2 ] . Value . Trim ( ) ;
checkStrings . Add ( [ prefix + " " + text , text ] ) ;
}
else
{
checkStrings . Add ( [ part . Trim ( ) ] ) ;
}
}
List < string > checkOptions = checkStrings . SelectMany ( a = > a ) . ToList ( ) ;
List < Training > skills = Context
. Trainings . Where ( t = > Pf2eDbContext . LocaleSearchArray ( t . Name , checkOptions ) )
. ToList ( ) ;
foreach ( List < string > checks in checkStrings )
{
Training ? skill = skills . FirstOrDefault ( t = >
checks . Any ( check = > t . Name ! . Contains ( check ) )
) ;
LocaleString ? trainingString =
( skill is null ) ? null : new ( ) { [ LocaleType . English ] = checks [ 0 ] } ;
result . Add (
new ( )
{
Ritual = Object ,
Training = skill ,
IsSecondary = isSecondary ,
TrainingString = trainingString ,
}
) ;
}
return result ;
}
private string primarySuffix = "" ;
private RitualCheck ResolveCheck ( string input , bool isSecondary )
{
RitualCheck result = new ( ) { Ritual = Object , IsSecondary = isSecondary } ;
RitualCheck result = new ( ) { Ritual = Object , IsPrimary = isPrimary } ;
string? skillName = null ;
string s = "" ;
Dictionary < Regex , Action < Match > > regexDictionary = new ( )
{
@@ -207,9 +302,19 @@ internal partial class RitualProcessor(AonprdProcessorInput input) : AbstractEnt
[PrimaryRegex1()] = match = >
{
skillName = match . Groups [ 1 ] . Value ;
result . Rank = Input . Enum < TrainingRank > ( match . Groups [ 4 ] . Value . ToLower ( ) ) ;
primarySuffix + =
match . Groups [ 3 ] . Value + match . Groups [ 5 ] . Value + match . Groups [ 6 ] . Value ;
try
{
result . Rank = Input . Enum < TrainingRank > ( match . Groups [ 4 ] . Value . ToLower ( ) ) ;
s + = match . Groups [ 3 ] . Value + match . Groups [ 5 ] . Value + match . Groups [ 6 ] . Value ;
}
catch
{
s + =
match . Groups [ 3 ] . Value
+ match . Groups [ 4 ] . Value
+ match . Groups [ 5 ] . Value
+ match . Groups [ 6 ] . Value ;
}
} ,
// ? [name](url)
@@ -217,14 +322,21 @@ internal partial class RitualProcessor(AonprdProcessorInput input) : AbstractEnt
{
skillName = match . Groups [ 2 ] . Value ;
result . Rank = TrainingRank . Untrained ;
primarySuffix + = match . Groups [ 1 ] . Value ;
s + = match . Groups [ 1 ] . Value ;
} ,
// name <rank>
[PrimaryRegex3()] = match = >
{
skillName = match . Groups [ 1 ] . Value ;
result . Rank = Input . Enum < TrainingRank > ( match . Groups [ 2 ] . Value . ToLower ( ) ) ;
try
{
result . Rank = Input . Enum < TrainingRank > ( match . Groups [ 2 ] . Value . ToLower ( ) ) ;
}
catch
{
s + = match . Groups [ 2 ] . Value ;
}
} ,
} ;
@@ -232,25 +344,23 @@ internal partial class RitualProcessor(AonprdProcessorInput input) : AbstractEnt
{
Match match = pair . Key . Match ( input ) ;
if ( ! match . Success )
if ( match . Success )
{
continue ;
pair . Value . Invoke ( match ) ;
break ;
}
pair . Value . Invoke ( match ) ;
}
if ( skillName is null )
{
throw new ( $"Can't resolve skill name from \" { input } \ "" ) ;
}
suffix = s ;
skillName ? ? = input ;
result . Training = Context . Trainings . FirstOrDefault ( t = >
Pf2eDbContext . LocaleSearch ( t . Name , skillName )
) ;
result . TrainingString =
( result . Training is null ) ? null : new ( ) { [ LocaleType . English ] = skillName } ;
( result . Training is not null ) ? null : new ( ) { [ LocaleType . English ] = skillName } ;
return result ;
}
@@ -263,14 +373,8 @@ internal partial class RitualProcessor(AonprdProcessorInput input) : AbstractEnt
"secondary_check_markdown",
] ;
[GeneratedRegex(@"\((\/.*?)\)")]
private static partial Regex LinkRemoveRegex ( ) ;
[GeneratedRegex(@"\((\w*)[;, ] ? ( . * ? ) \ ) ")]
private static partial Regex PrimaryCheckSuffixRegex ( ) ;
[GeneratedRegex(
@"^ *\[\s*(.*?)\s*\] \ ( ( . * ? ) \ ) \ s * ( [ a - z ' ] * ? ) < ( . * ? ) ( ? : [ ; , ] \ s * ( . * ? ) ) ? > ( [ a - z ' ] * ? ) $",
@"^ *\[\s*(.*?)\s*\] \ ( ( . * ? ) \ ) \ s * ( [ a - z ' ] * ? ) < ( . * ? ) ( ? : [ ; , ] ? \ s * ( . * ? ) ) ? > ( [ a - z ' ] * ? ) $",
RegexOptions.Multiline
)]
private static partial Regex PrimaryRegex1();
@@ -280,350 +384,7 @@ internal partial class RitualProcessor(AonprdProcessorInput input) : AbstractEnt
[GeneratedRegex(@" ^ * ( . * ? ) \ s * < ( . * ? ) > * $", RegexOptions.Multiline)]
private static partial Regex PrimaryRegex3();
[GeneratedRegex(@" ^ ( \ d + ) ( ? : \ - ( \ d + ) ) ? , ? * \ ( ? ( . * ? ) \ ) ? $ ", RegexOptions.Multiline)]
private static partial Regex CasterRegex ( ) ;
}
/*
TEST DATA FOR PRIMARY RANK
[Arcana](/Skills.aspx?ID=2) <expert>
[Religion](/Skills.aspx?ID=13) <expert; you must be a [daemon](/MonsterFamilies.aspx?ID=26), an [urdefhan](/MonsterFamilies.aspx?ID=158)>
[Arcana](/Skills.aspx?ID=2) <master>
[Occultism](/Skills.aspx?ID=11) <master>
[Religion](/Skills.aspx?ID=13) <master>
[Occultism](/Skills.aspx?ID=11) <master>
[Arcana](/Skills.aspx?ID=2)
[Nature](/Skills.aspx?ID=10)
[Occultism](/Skills.aspx?ID=11)
[Religion](/Skills.aspx?ID=13) <legendary>
[Nature](/Skills.aspx?ID=10)
[Religion](/Skills.aspx?ID=13)
[Occultism](/Skills.aspx?ID=11) <expert>
[Religion](/Skills.aspx?ID=13) <expert>
[Nature](/Skills.aspx?ID=10)
[Religion](/Skills.aspx?ID=13) <master, the check has the [secret](/Traits.aspx?ID=142) trait>
[Arcana](/Skills.aspx?ID=2)
[Occultism](/Skills.aspx?ID=11) <trained>
[Occultism](/Skills.aspx?ID=11) <expert>
[Religion](/Skills.aspx?ID=13) <expert; you must be a [div](/MonsterFamilies.aspx?ID=225)>
[Occultism](/Skills.aspx?ID=11) <expert>
[Arcana](/Skills.aspx?ID=2)
[Occultism](/Skills.aspx?ID=11) <expert>
[Nature](/Skills.aspx?ID=10) <trained>
[Crafting](/Skills.aspx?ID=4) <expert>
[Occultism](/Skills.aspx?ID=11) <expert>
[Arcana](/Skills.aspx?ID=2) <expert>
[Occultism](/Skills.aspx?ID=11) <expert>
[Occultism](/Skills.aspx?ID=11) <expert>
[Occultism](/Skills.aspx?ID=11)
[Arcana](/Skills.aspx?ID=2)
[Occultism](/Skills.aspx?ID=11) <expert>
[Arcana](/Skills.aspx?ID=2) <expert>
[Occultism](/Skills.aspx?ID=11) <expert>
[Nature](/Skills.aspx?ID=10) <expert>
[Occultism](/Skills.aspx?ID=11)
[Religion](/Skills.aspx?ID=13)
[Occultism](/Skills.aspx?ID=11) <expert>
[Occultism](/Skills.aspx?ID=11) <expert>
[Arcana](/Skills.aspx?ID=2) <Expert>
[Occultism](/Skills.aspx?ID=11) <Expert>
[Occultism](/Skills.aspx?ID=11)
[Religion](/Skills.aspx?ID=13) <master>
[Occultism](/Skills.aspx?ID=11) <expert>
[Arcana](/Skills.aspx?ID=2)
[Nature](/Skills.aspx?ID=10)
[Occultism](/Skills.aspx?ID=11)
[Religion](/Skills.aspx?ID=13) matching the ley line's tradition <legendary>
[Arcana](/Skills.aspx?ID=2)
[Nature](/Skills.aspx?ID=10)
[Occultism](/Skills.aspx?ID=11)
[Religion](/Skills.aspx?ID=13) based on the ley line <master>
[Religion](/Skills.aspx?ID=13) <trained>
[Performance](/Skills.aspx?ID=12) <expert>
[Occultism](/Skills.aspx?ID=11)
[Religion](/Skills.aspx?ID=13) <legendary>
DC 40 [Arcana](/Skills.aspx?ID=2)
[Nature](/Skills.aspx?ID=10) <master>
[Occultism](/Skills.aspx?ID=11)
[Religion](/Skills.aspx?ID=13) <legendary>
[Nature](/Skills.aspx?ID=10) <expert; you must be the [Mosquito Witch](/Monsters.aspx?ID=1735)>
[Religion](/Skills.aspx?ID=13) <master>
[Nature](/Skills.aspx?ID=10) <expert>
[Nature](/Skills.aspx?ID=10) <master>
[Occultism](/Skills.aspx?ID=11)
[Religion](/Skills.aspx?ID=13)
[Arcana](/Skills.aspx?ID=2)
[Occultism](/Skills.aspx?ID=11) <expert>
[Occultism](/Skills.aspx?ID=11) <master>
[Religion](/Skills.aspx?ID=13) <master>
[Occultism](/Skills.aspx?ID=11) <master>
[Religion](/Skills.aspx?ID=13) <master>
[Occultism](/Skills.aspx?ID=11) <master>
[Religion](/Skills.aspx?ID=13) <master>
[Occultism](/Skills.aspx?ID=11)
[Religion](/Skills.aspx?ID=13) <legendary>
[Arcana](/Skills.aspx?ID=2)
[Occultism](/Skills.aspx?ID=11) <master>
[Arcana](/Skills.aspx?ID=2)
[Occultism](/Skills.aspx?ID=11) <expert>
[Religion](/Skills.aspx?ID=13) <trained>
[Crafting](/Skills.aspx?ID=4) <master>
[Occultism](/Skills.aspx?ID=11) <expert>
[Religion](/Skills.aspx?ID=13) <expert>
[Arcana](/Skills.aspx?ID=2)
[Nature](/Skills.aspx?ID=10) <expert>
[Arcana](/Skills.aspx?ID=2)
[Occultism](/Skills.aspx?ID=11) <master, the check has the [secret](/Traits.aspx?ID=142) trait>
[Occultism](/Skills.aspx?ID=11) <expert>
[Fortune-Telling Lore](/Skills.aspx?ID=8) <trained>
[Occultism](/Skills.aspx?ID=11) <expert>
[Performance](/Skills.aspx?ID=12) <expert>
[Nature](/Skills.aspx?ID=10) <master>
[Nature](/Skills.aspx?ID=10) <expert>
[Arcana](/Skills.aspx?ID=2)
[Nature](/Skills.aspx?ID=10) <expert>
Occultism <Trained>
Religion <trained>
[Arcana](/Skills.aspx?ID=2)
[Occultism](/Skills.aspx?ID=11) <expert>
[Arcana](/Skills.aspx?ID=2) <expert>
[Nature](/Skills.aspx?ID=10)
[Religion](/Skills.aspx?ID=13) <expert>
[Nature](/Skills.aspx?ID=10) <master>
[Arcana](/Skills.aspx?ID=2) <master>
[Occultism](/Skills.aspx?ID=11) <master>
[Nature](/Skills.aspx?ID=10) <expert>
[Occultism](/Skills.aspx?ID=11) <expert>
[Religion](/Skills.aspx?ID=13) <expert>
[Occultism](/Skills.aspx?ID=11) <master>
[Nature](/Skills.aspx?ID=10) <master>
[Occultism](/Skills.aspx?ID=11) <master>
[Religion](/Skills.aspx?ID=13) <master>
[Religion](/Skills.aspx?ID=13)
[Nature](/Skills.aspx?ID=10) <master>
[Arcana](/Skills.aspx?ID=2) <expert>
[Occultism](/Skills.aspx?ID=11) <expert>
[Religion](/Skills.aspx?ID=13) <expert>
[Arcana](/Skills.aspx?ID=2) <expert>
[Occultism](/Skills.aspx?ID=11) <expert>
[Religion](/Skills.aspx?ID=13) <expert>
[Arcana](/Skills.aspx?ID=2) <master>
[Nature](/Skills.aspx?ID=10) <master>
[Occultism](/Skills.aspx?ID=11) <master>
[Religion](/Skills.aspx?ID=13) <master>
[Religion](/Skills.aspx?ID=13) <expert>
[Nature](/Skills.aspx?ID=10) <expert>
[Nature](/Skills.aspx?ID=10) <master>
[Religion](/Skills.aspx?ID=13) <expert>
[Arcana](/Skills.aspx?ID=2) <expert>
[Nature](/Skills.aspx?ID=10) <expert>
[Occultism](/Skills.aspx?ID=11) <expert>
[Religion](/Skills.aspx?ID=13) <expert> as determined by the tradition of spell cast in the ritual
[Arcana](/Skills.aspx?ID=2) <legendary>
[Nature](/Skills.aspx?ID=10) <legendary>
[Occultism](/Skills.aspx?ID=11) <legendary>
[Religion](/Skills.aspx?ID=13) <legendary>
[Diplomacy ](/Skills.aspx?ID=39)<expert>
[Occultism ](/Skills.aspx?ID=44)<trained>
[Survival](/Skills.aspx?ID=49) <expert>
[Occultism ](/Skills.aspx?ID=44)
[Religion ](/Skills.aspx?ID=46) <expert>
[Religion](/Skills.aspx?ID=46) <expert>
[Performance ](/Skills.aspx?ID=45)<expert>
[Cooking Lore](/Skills.aspx?ID=41) <expert>
[Cooking Lore](/Skills.aspx?ID=41)
[Tea Lore ](/Skills.aspx?ID=41) <expert>
[Crafting](/Skills.aspx?ID=37) <expert>
[Athletics](/Skills.aspx?ID=36) <expert>
[Medicine](/Skills.aspx?ID=42) <expert>
[Perception](/Rules.aspx?ID=2298) <master>
[Diplomacy](/Skills.aspx?ID=39) <expert>
[Religion](/Skills.aspx?ID=46) <expert; you must be an [angel](/Traits.aspx?ID=530)>
[Religion](/Skills.aspx?ID=46) <expert; you must be a [demon](/Traits.aspx?ID=573)>
[Religion](/Skills.aspx?ID=46) <expert; you must be a [devil](/Traits.aspx?ID=575)>
[Arcana](/Skills.aspx?ID=35)
[Occultism](/Skills.aspx?ID=44) <master>
[Crafting](/Skills.aspx?ID=37) <legendary>
[Arcana](/Skills.aspx?ID=35)
[Occultism](/Skills.aspx?ID=44) <legendary>
[Crafting](/Skills.aspx?ID=37)
[Nature](/Skills.aspx?ID=43)
[Society](/Skills.aspx?ID=47) <expert>
[Arcana](/Skills.aspx?ID=35)
[Occultism](/Skills.aspx?ID=44) <expert, the check has the [secret](/Traits.aspx?ID=690) trait>
[Nature](/Skills.aspx?ID=43)
[Religion](/Skills.aspx?ID=46) <expert>
[Arcana](/Skills.aspx?ID=35) <expert>
[Occultism](/Skills.aspx?ID=44) <expert>
[Religion](/Skills.aspx?ID=46) <expert>
[Arcana](/Skills.aspx?ID=35)
[Occultism](/Skills.aspx?ID=44) <expert>
[Nature](/Skills.aspx?ID=43) <expert>
[Religion](/Skills.aspx?ID=46) <expert>
[Arcana](/Skills.aspx?ID=35)
[Occultism](/Skills.aspx?ID=44) <master, the check has the [secret](/Traits.aspx?ID=690) trait>
[Arcana](/Skills.aspx?ID=35)
[Occultism](/Skills.aspx?ID=44) <master>
[Arcana](/Skills.aspx?ID=35)
[Nature](/Skills.aspx?ID=43)
[Occultism](/Skills.aspx?ID=44) <master>
[Cooking Lore](/Skills.aspx?ID=41)
[Crafting](/Skills.aspx?ID=37)
[Performance](/Skills.aspx?ID=45)
[Survival](/Skills.aspx?ID=49) <trained>
[Medicine](/Skills.aspx?ID=42) <trained>
[Cooking Lore](/Skills.aspx?ID=41)
[Crafting](/Skills.aspx?ID=37)
[Performance](/Skills.aspx?ID=45)
[Survival](/Skills.aspx?ID=49) <expert>
skill corresponding to the target’ s casting tradition <trained>
[Arcana](/Skills.aspx?ID=35) <mythic>
[Occultism](/Skills.aspx?ID=44) <mythic>
[Diplomacy](/Skills.aspx?ID=39) <mythic>
[Society](/Skills.aspx?ID=47) <mythic>
[Diplomacy](/Skills.aspx?ID=39) <mythic>
[Society](/Skills.aspx?ID=47) <mythic>
[Arcana](/Skills.aspx?ID=35) <mythic>
[Nature](/Skills.aspx?ID=43) <mythic>
[Occultism](/Skills.aspx?ID=44) <mythic>
[Religion](/Skills.aspx?ID=46) <mythic>
[Occultism](/Skills.aspx?ID=44) <mythic>
[Arcana](/Skills.aspx?ID=35) <mythic>
[Occultism](/Skills.aspx?ID=44) <mythic>
[Arcana](/Skills.aspx?ID=35) <mythic>
[Occultism](/Skills.aspx?ID=44) <mythic>
[Crafting](/Skills.aspx?ID=37) <mythic>
[Nature](/Skills.aspx?ID=43) <mythic>
[Performance](/Skills.aspx?ID=45) <mythic>
[Religion](/Skills.aspx?ID=46) <mythic>
[Nature](/Skills.aspx?ID=43) <mythic>
[Arcana](/Skills.aspx?ID=35) <mythic>
[Nature](/Skills.aspx?ID=43) <mythic>
[Occultism](/Skills.aspx?ID=44) <mythic>
[Religion](/Skills.aspx?ID=46) <mythic>
[Nature](/Skills.aspx?ID=43) <master>
[Occultism](/Skills.aspx?ID=44) <master>
*/