2022-08-04 03:14:59 +08:00
const Validator = {
checks : [ ] ,
warnings : [ ] ,
errors : [ ] ,
2022-08-23 23:29:45 +08:00
_timeout : null ,
2022-08-04 03:14:59 +08:00
validate ( trigger ) {
2022-08-23 23:29:45 +08:00
if ( this . _timeout ) {
clearTimeout ( this . _timeout ) ;
this . _timeout = null ;
}
2022-08-04 03:14:59 +08:00
if ( ! Project ) return ;
2022-08-23 23:29:45 +08:00
this . _timeout = setTimeout ( ( ) => {
this . _timeout = null ;
Validator . warnings . empty ( ) ;
Validator . errors . empty ( ) ;
2022-08-04 03:14:59 +08:00
2022-08-23 23:29:45 +08:00
Validator . checks . forEach ( check => {
try {
if ( ! Condition ( check . condition ) ) return ;
if ( ! trigger || check . update _triggers . includes ( trigger ) ) {
check . update ( ) ;
}
Validator . warnings . push ( ... check . warnings ) ;
Validator . errors . push ( ... check . errors ) ;
} catch ( error ) {
console . error ( error ) ;
}
} )
} , 400 )
2022-08-04 03:14:59 +08:00
} ,
openDialog ( ) {
if ( ! Validator . dialog ) {
Validator . dialog = new Dialog ( {
id : 'validator' ,
title : 'action.validator_window' ,
singleButton : true ,
2022-08-23 06:19:57 +08:00
width : 800 ,
2022-08-04 03:14:59 +08:00
component : {
data ( ) { return {
warnings : Validator . warnings ,
errors : Validator . errors ,
} } ,
computed : {
problems ( ) {
this . errors . forEach ( error => error . error = true ) ;
return [ ... this . errors , ... this . warnings ]
}
} ,
methods : {
2022-08-23 06:19:57 +08:00
getIconNode : Blockbench . getIconNode ,
marked
2022-08-04 03:14:59 +08:00
} ,
template : `
< template >
< ul >
< li v - for = "problem in problems" class = "validator_dialog_problem" : class = "problem.error ? 'validator_error' : 'validator_warning'" : key = "problem.message" >
< i class = "material-icons" > { { problem . error ? 'error' : 'warning' } } < / i >
2022-08-23 06:19:57 +08:00
< span class = "markdown" v - html = "marked(problem.message.replace(/\\n/g, '\\n\\n'))" > < / s p a n >
2022-08-04 03:14:59 +08:00
< template v - if = "problem.buttons" >
< div v - for = "button in problem.buttons" class = "tool" : title = "button.name" @ click = "button.click($event)" >
< div class = "icon_wrapper plugin_icon normal" v - html = "getIconNode(button.icon, button.color).outerHTML" > < / d i v >
< / d i v >
< / d i v >
< / l i >
< / u l >
< / t e m p l a t e >
`
}
} ) ;
}
Validator . dialog . show ( ) ;
} ,
triggers : [ ] ,
updateCashedTriggers ( ) {
Validator . triggers . empty ( ) ;
Validator . checks . forEach ( check => {
Validator . triggers . safePush ( ... check . update _triggers ) ;
} )
}
} ;
class ValidatorCheck {
constructor ( id , options ) {
this . id = id ;
this . type = options . type ;
this . update _triggers = options . update _triggers
? ( options . update _triggers instanceof Array ? options . update _triggers : [ options . update _triggers ] )
: [ ] ;
this . condition = options . condition ;
this . run = options . run ;
this . errors = [ ] ;
this . warnings = [ ] ;
Validator . checks . push ( this ) ;
Validator . updateCashedTriggers ( ) ;
}
delete ( ) {
Validator . checks . remove ( this ) ;
Validator . updateCashedTriggers ( ) ;
}
update ( ) {
this . errors . empty ( ) ;
this . warnings . empty ( ) ;
try {
this . run ( ) ;
if ( this . errors . length > 100 ) this . errors . splice ( 100 ) ;
if ( this . warnings . length > 100 ) this . warnings . splice ( 100 ) ;
} catch ( error ) {
console . error ( error ) ;
}
}
warn ( ... warnings ) {
this . warnings . push ( ... warnings ) ;
}
fail ( ... errors ) {
this . errors . push ( ... errors ) ;
}
}
BARS . defineActions ( function ( ) {
new Action ( 'validator_window' , {
icon : 'checklist' ,
category : 'file' ,
condition : ( ) => Project ,
click ( ) {
Validator . openDialog ( ) ;
}
} )
} )
new ValidatorCheck ( 'texture_names' , {
condition : { formats : [ 'java_block' ] } ,
update _triggers : [ 'add_texture' , 'change_texture_path' ] ,
run ( ) {
Texture . all . forEach ( texture => {
let characters = ( texture . folder + texture . name ) . match ( /[^a-z0-9._/\\-]/ )
if ( characters ) {
this . warn ( {
message : ` Texture " ${ texture . name } " contains the following invalid characters: " ${ characters . join ( '' ) } " ` ,
buttons : [
{
name : 'Select Texture' ,
icon : 'mouse' ,
click ( ) {
Validator . dialog . hide ( ) ;
texture . select ( ) ;
}
}
]
} )
}
} )
}
} )
2022-08-10 05:14:03 +08:00
new ValidatorCheck ( 'catmullrom_keyframes' , {
condition : { features : [ 'animation_files' ] } ,
update _triggers : [ 'update_keyframe_selection' ] ,
run ( ) {
Animation . all . forEach ( animation => {
for ( let key in animation . animators ) {
let animator = animation . animators [ key ] ;
if ( animator instanceof BoneAnimator ) {
for ( let channel in animator . channels ) {
if ( ! animator [ channel ] || ! animator [ channel ] . find ( kf => kf . interpolation == 'catmullrom' ) ) continue ;
let keyframes = animator [ channel ] . slice ( ) . sort ( ( a , b ) => a . time - b . time ) ;
keyframes . forEach ( ( kf , i ) => {
if ( kf . interpolation == 'catmullrom' ) {
if ( kf . data _points . find ( dp => isNaN ( dp . x ) || isNaN ( dp . y ) || isNaN ( dp . z ) ) ) {
this . fail ( {
2022-08-23 06:19:57 +08:00
message : ` ${ animator . channels [ channel ] . name } keyframe at ${ kf . time . toFixed ( 2 ) } on " ${ animator . name } " in " ${ animation . name } " contains non-numeric value. Smooth keyframes cannot contain math expressions. ` ,
buttons : [ {
name : 'Reveal Keyframe' ,
icon : 'icon-keyframe' ,
click ( ) {
Dialog . open . close ( ) ;
kf . showInTimeline ( ) ;
}
} ]
2022-08-10 05:14:03 +08:00
} )
}
if ( ( ! keyframes [ i - 1 ] || keyframes [ i - 1 ] . interpolation != 'catmullrom' ) && ( ! keyframes [ i + 1 ] || keyframes [ i + 1 ] . interpolation != 'catmullrom' ) ) {
this . warn ( {
2022-08-23 06:19:57 +08:00
message : ` ${ animator . channels [ channel ] . name } keyframe at ${ kf . time . toFixed ( 2 ) } on " ${ animator . name } " in " ${ animation . name } " is not surrounded by smooth keyframes. Multiple smooth keyframes are required to create a smooth spline. ` ,
buttons : [ {
name : 'Reveal Keyframe' ,
icon : 'icon-keyframe' ,
click ( ) {
Dialog . open . close ( ) ;
kf . showInTimeline ( ) ;
}
} ]
2022-08-10 05:14:03 +08:00
} )
}
}
} )
}
}
}
} )
}
} )
2022-08-23 06:19:57 +08:00
new ValidatorCheck ( 'molang_syntax' , {
condition : { features : [ 'animation_mode' ] } ,
update _triggers : [ 'update_keyframe_selection' , 'edit_animation_properties' ] ,
run ( ) {
let check = this ;
function validateMolang ( string , message , instance ) {
if ( ! string || typeof string !== 'string' ) return ;
2022-08-23 23:29:45 +08:00
let clear _string = string . replace ( /'.*'/g , '0' ) ;
2022-08-23 06:19:57 +08:00
let issues = [ ] ;
2022-08-23 23:29:45 +08:00
if ( clear _string . match ( /([-+*/]\s*[+*/])|(\+\s*-)/ ) ) {
2022-08-23 06:19:57 +08:00
issues . push ( 'Two directly adjacent operators' ) ;
}
2022-08-23 23:29:45 +08:00
if ( clear _string . match ( /^[+*/.,?=&<>|]/ ) ) {
issues . push ( 'Expression starts with an invalid character' ) ;
}
if ( clear _string . match ( /(?!')[a-df-z_]+\s*[-?]+\s*[a-z_]+/i ) ) {
issues . push ( 'Invalid expression "' + clear _string . match ( /(?!')[a-df-z_]+\s*[-?]+\s*[a-z_]+/i ) [ 0 ] + '"' ) ;
}
if ( clear _string . match ( /[\w.]\s+[\w.]/ ) ) {
2022-08-23 06:19:57 +08:00
issues . push ( 'Two expressions with no operator in between' ) ;
}
2022-08-23 23:29:45 +08:00
if ( clear _string . match ( /[^\w\s+\-*/().,;[\]!?=<>&|]/ ) ) {
issues . push ( 'Invalid character: ' + clear _string . match ( /[^\w+\-*/().,;[\]!?=<>&|]+/g ) . join ( ', ' ) ) ;
2022-08-23 06:19:57 +08:00
}
let left = string . match ( /\(/g ) || 0 ;
let right = string . match ( /\)/g ) || 0 ;
if ( left . length !== right . length ) {
issues . push ( 'Brackets do not match' ) ;
}
if ( issues . length ) {
let button ;
if ( instance instanceof Animation ) {
button = {
name : 'Edit Animation' ,
icon : 'movie' ,
click ( ) {
Dialog . open . close ( ) ;
instance . propertiesDialog ( ) ;
}
}
} else {
button = {
name : 'Reveal Keyframe' ,
icon : 'icon-keyframe' ,
click ( ) {
Dialog . open . close ( ) ;
instance . showInTimeline ( ) ;
}
}
}
check . fail ( {
message : ` ${ message } ${ issues . join ( '; ' ) } . Script: \` ${ string } \` ` ,
buttons : [ button ]
} )
}
}
Animation . all . forEach ( animation => {
for ( let key in Animation . properties ) {
let property = Animation . properties [ key ] ;
if ( Condition ( property . condition , animation ) && property . type == 'molang' ) {
let value = animation [ key ] ;
validateMolang ( value , ` Property " ${ key } " on animation " ${ animation . name } " contains invalid molang: ` , animation ) ;
}
}
for ( let key in animation . animators ) {
let animator = animation . animators [ key ] ;
for ( let channel in animator . channels ) {
animator [ channel ] . forEach ( ( kf , i ) => {
kf . data _points . forEach ( data _point => {
for ( let key in KeyframeDataPoint . properties ) {
let property = KeyframeDataPoint . properties [ key ] ;
if ( Condition ( property . condition , data _point ) && property . type == 'molang' ) {
let value = data _point [ key ] ;
validateMolang ( value , ` ${ animator . channels [ channel ] . name } keyframe at ${ kf . time . toFixed ( 2 ) } on " ${ animator . name } " in " ${ animation . name } " contains invalid molang: ` , kf ) ;
}
}
} )
} )
}
}
} )
}
} )