///////////////////////////////////////////////////////// // // Attract-Mode Frontend - MALA Layout Loader/MaLadaptor // // Copyright (c) 2015 Andrew Mickelson // // This program comes with ABSOLUTELY NO WARRANTY. It is licensed under // the terms of the GNU General Public License, version 3 or later. // // Known Issues: // // - Animation1-4 not implemented // - No image lists // - missing some sounds // - Missing some text substitutions ~etc // ///////////////////////////////////////////////////////// class UserConfig { orient="Horizontal"; animate_list="Yes"; lazy_art="No"; fade="Yes"; } fe.load_module( "conveyor" ); function mll_str( key, tag ) { if ( !mll[key].rawin( tag ) ) return ""; else return mll[key][tag]; } function mll_int( key, tag, default_val=0 ) { if ( !mll[key].rawin( tag ) ) return default_val; else return mll[key][tag].tointeger(); } function hex2int( key, tag, pos ) { if ( !mll[key].rawin( tag ) ) return 255; local s=mll[key][tag]; local val=0; if ( s[pos+1] < 58 ) // 0-9 val = s[pos+1] - 48; else // A-F val = s[pos+1] - 55; if ( s[pos] < 58 ) val += (s[pos] - 48) << 4; else val += (s[pos] - 55) << 4; return val; } function mll_red( key, tag ) { return hex2int( key, tag, 0 ); } function mll_green( key, tag ) { return hex2int( key, tag, 2 ); } function mll_blue( key, tag ) { return hex2int( key, tag, 4 ); } function mll_file( key, tag, prefix="" ) { if ( !mll[key].rawin( tag ) ) return ""; local t=split(mll[key][tag],"\\/"); return prefix + t[t.len()-1]; } function mll_text( key, tag ) { if ( !mll[key].rawin( tag ) ) return ""; local d = date(); local rep_map = { "e_name": "[Emulator]", "g_name": "[Title]", "g_cloneof": "[CloneOf]", "g_genre": "[Category]", "g_year": "[Year]", "g_manufacturer": "[Manufacturer]", "g_control": "[Control]", "g_orientation": "[Rotation]", "g_players": "[Players]", "g_screen": "[DisplayType]", "g_played": "[PlayedCount]", "s_status": "[Status]", "l_name": "[FilterName]", "l_position": "[ListEntry]", "l_count": "[ListSize]", "m_time": "12:00", "m_date": ( d["year"] + "/" + d["month"] * d["day"] ) }; local str = mll[key][tag]; local ex = regexp( "~[_a-z]+" ); local pos=0; local token = ex.search( str, pos ); while ( token ) { local tmp = str.slice( token.begin+1, token.end ); local new_end = str.slice( token.end ); str = str.slice( 0, token.begin ) + ( rep_map.rawin(tmp) ? rep_map[tmp] : "" ); pos = str.len(); str += new_end; token = ex.search( str, pos ); } return str; } function mll_align( key, tag ) { if ( !mll[key].rawin( tag ) ) return Align.Centre; switch ( mll_int( key, tag ) ) { case 1: return Align.Left; case 2: return Align.Right; } return Align.Centre; // 0 } function mll_style( bold, italic ) { local style=0; if ( bold == "1" ) style = Style.Bold; if ( italic == "1" ) style = style | Style.Italic; return style; } function add_bgimage() { local img = fe.add_image( mll_file( lkey, "BGImage", ::mala_subdir ), (vert ? fe.layout.width : 0), 0, (vert? fe.layout.height : fe.layout.width), (vert? fe.layout.width : fe.layout.height) ); if ( img.file_name.len() < 1 ) img.file_name = mll_file( lkey, "BGImage" ); if ( vert ) img.rotation = 90; } ::list_points <- []; class ListEntry extends ConveyorSlot { _t=null; constructor() { _t = fe.add_text( "[Title]", 0, 0, 0, 0 ); base.constructor(); } _pro_attrs = ["x","y","width","height","red","green","blue","charsize"]; function on_progress( progress, var ) { local num = ::list_points.len()-1; local p = progress * num; local s = p.tointeger(); p -= s; // don't show list entries off the edges of the mala list if ( (progress<0) || (progress>( 1.0*(num-1)/num)) ) _t.visible=false; else _t.visible=true; if ( s < 0 ) s=0; else if ( s >= num ) s = num-1; foreach (attr in _pro_attrs ) { local tmp = ::list_points[s+1][attr] - ::list_points[s][attr]; _t[attr] = ::list_points[s][attr] + p * tmp; } } function swap( other ) {} function set_index_offset( io ) { _t.index_offset=io; } function reset_index_offset() { set_index_offset( m_base_io ); } }; function step_color( c, step ) { c += step; if ( c < 0 ) c=0; if ( c > 255 ) c=255; return c; } function add_list() { // we want this to return true if ListVisible doesn't exist if ( mll_int( lkey, "ListVisible", 1 ) != 1 ) return; local slots = []; const LIST="List"; const TFC="TopFontColor"; const CFC="CenterFontColor"; const BFC="BottomFontColor"; const ROUND_DIVIDE=2.4; local x = mll_int( lkey, "ListLeft"); local y = mll_int( lkey, "ListTop"); local w = mll_int( lkey, "ListWidth"); local h = mll_int( lkey, "ListHeight"); local align = mll_align( lkey, "ListAlignment" ); if ( vert ) { // swap w&h, x&y local temp=w; w=h; h=temp; temp=x; x = fe.layout.width - y - w; y = temp; } local dist = mll_int( lkey, "ListDistance"); local s_grad = mll_int( lkey, "ListSizeGradient"); local count_backup = mll_int( LIST, "TopBottomCount" ); local topcount = mll_int( lkey, "ListTopCount", count_backup); local bottomcount = mll_int( lkey, "ListBottomCount", count_backup); local red = mll_red( LIST, TFC ); local green = mll_green( LIST, TFC ); local blue = mll_blue( LIST, TFC ); local red_step = 0; local green_step = 0; local blue_step = 0; enum ColorStyle { Flat =0, Highlight =1, Gradient =2 } switch ( mll_int( LIST, "ColorStyle" ) ) { case ColorStyle.Flat: red = mll_red( LIST, CFC ); green = mll_green( LIST, CFC ); blue = mll_blue( LIST, CFC ); break; case ColorStyle.Highlight: break; case ColorStyle.Gradient: local grad_depth = mll_int( LIST, "GradientDepth" ); red_step = ( mll_red( LIST, CFC ) - red ) / ( topcount + grad_depth ); green_step = ( mll_green( LIST, CFC ) - green ) / ( topcount + grad_depth ); blue_step = ( mll_blue( LIST, CFC ) - blue ) / ( topcount + grad_depth ); break; } local tb_fontsize = mll_int( LIST, "TopBottomFontSize" ); enum Trans { NoTransform =0, ListDiagonal =1, ListArrow =2, ListRound =3 } local trans = mll_int( lkey, "ListTransformation" ); local trans_adj=0; switch ( trans ) { case Trans.ListDiagonal: local diag = mll_int( lkey, "ListDiagonal"); trans_adj = diag/25.0 * w / ( topcount + bottomcount ); local old_w = w; w -= fabs( trans_adj ) * ( topcount + bottomcount ); if ( diag < 0 ) x += old_w - w; break; case Trans.ListArrow: local arrow = mll_int( lkey, "ListArrow"); trans_adj = arrow/25.0 * w / ( topcount + bottomcount ); local old_w = w; w -= fabs( trans_adj ) * ( topcount + bottomcount ) / 2; if ( arrow < 0 ) x += old_w - w; break; case Trans.ListRound: local round = mll_int( lkey, "ListRound" ); trans_adj = round/25.0 * w / ( topcount + bottomcount ); local old_w = w; w -= fabs( trans_adj ) * ( topcount + bottomcount ) / 6; if ( round < 0 ) x += old_w - w; break; case Trans.NoTransform: default: // None }; local ent; local tmp; for ( local i=0; i< topcount; i++ ) { ent = { x=x, y=y, width=w, height=h, red=red, green=green, blue=blue, charsize=tb_fontsize }; ::list_points.append( ent ); tmp = ListEntry(); tmp._t.font = mll_str( LIST, "TopBottomFontName" ); tmp._t.style = mll_style( mll_str( LIST, "TopBottomFontBold" ), mll_str( LIST, "TopBottomFontItalic" ) ); tmp._t.align = align; slots.append( tmp ); y += h + dist; if ( trans == Trans.ListRound ) trans_adj -= trans_adj / ROUND_DIVIDE; x += trans_adj; tb_fontsize += s_grad; red = step_color( red, red_step ); green = step_color( green, green_step ); blue = step_color( blue, blue_step ); } local ch = mll_int( lkey, "ListCenterHeight", h ); local ent = { x=x, y=y, width=w, height=ch, charsize = mll_int( LIST, "CenterFontSize" ), red = mll_red( LIST, CFC ), green = mll_green( LIST, CFC ), blue = mll_blue( LIST, CFC ) } ::list_points.append( ent ); tmp = ListEntry(); tmp._t.font = mll_str( LIST, "CenterFontName" ); tmp._t.style = mll_style( mll_str( LIST, "CenterFontBold" ), mll_str( LIST, "CenterFontItalic" ) ); tmp._t.align = align; if ( mll_int( LIST, "CenterFontUseBGColor" ) == 1 ) { const CFBG = "CenterFontBGColor"; tmp._t.bg_red = mll_red( LIST, CFBG ); tmp._t.bg_green = mll_green( LIST, CFBG ); tmp._t.bg_blue = mll_blue( LIST, CFBG ); tmp._t.bg_alpha = 255; } slots.append( tmp ); y += ch + dist; red = step_color( red, -red_step ); green = step_color( green, -green_step ); blue = step_color( blue, -blue_step ); for ( local i=0; i< bottomcount; i++ ) { if ( trans == Trans.ListDiagonal ) x += trans_adj; else { if ( trans == Trans.ListRound ) trans_adj += 1.25 * trans_adj / ROUND_DIVIDE; x -= trans_adj; } local ent = { x=x, y=y, width=w, height=h, red=red, green=green, blue=blue, charsize=tb_fontsize }; ::list_points.append( ent ); tmp = ListEntry(); tmp._t.font = mll_str( LIST, "TopBottomFontName" ); tmp._t.style = mll_style( mll_str( LIST, "TopBottomFontBold" ), mll_str( LIST, "TopBottomFontItalic" ) ); tmp._t.align = align; slots.append( tmp ); y += h + dist; tb_fontsize -= s_grad; red = step_color( red, -red_step ); green = step_color( green, -green_step ); blue = step_color( blue, -blue_step ); } local ent = { x=x, y=y, width=w, height=h, red=red, green=green, blue=blue, charsize=tb_fontsize }; ::list_points.append( ent ); mala_conveyor <- Conveyor(); mala_conveyor.set_slots( slots, topcount ); mala_conveyor.transition_swap_point = 1.0; mala_conveyor.transition_ms = 80; if ( am_options["animate_list"] != "Yes" ) mala_conveyor.enabled = false; } function add_art( mll_label, am_label="", extra="Image" ) { if ( mll_int( lkey, mll_label + "Visible" ) != 1 ) return; local x = mll_int( lkey, mll_label+extra+"Left" ); local y = mll_int( lkey, mll_label+extra+"Top" ); local w = mll_int( lkey, mll_label+extra+"Width" ); local h = mll_int( lkey, mll_label+extra+"Height" ); if ( vert ) { // swap w&h, x&y local temp=w; w=h; h=temp; temp=x; x = fe.layout.width - y - w; y = temp; } // If there is a default image and attract-mode doesn't have // a label for this artwork then just go and load the default image // local img=null; if ( am_label.len() == 0 ) { if ( mll_str( lkey, mll_label+"Default" ).len() > 0 ) { img = fe.add_image( mll_file( key, mll_label+"Default", ::mala_subdir ), x, y, w, h ); if ( img.file_name.len() < 1 ) img.file_name = mll_file( key, mll_label+"Default" ); } } else { img = fe.add_artwork( am_label, x, y, w, h ); if ( am_options["lazy_art"] == "Yes" ) img.trigger = Transition.EndNavigation; } } function add_custom( key ) { local ex = lkey.slice( 0, 1 ); if ( mll_int( key, "Visible" + ex ) != 1 ) return; local x = mll_int( key, "Left" + ex ); local y = mll_int( key, "Top" + ex ); local w = mll_int( key, "Width" + ex ); local h = mll_int( key, "Height" + ex ); if ( vert ) { // swap w&h, x&y local temp=w; w=h; h=temp; temp=x; x = fe.layout.width - y - w; y = temp; } local do_transparent = mll_int( key, "Transparent" ); local img = fe.add_image( mll_file( key, "Image" + ex, ::mala_subdir ), x, y, w, h ); if ( img.file_name.len() < 1 ) img.file_name = mll_file( key, "Image" + ex ); if ( do_transparent && (img.file_name.len() > 0) ) img.fix_masked_image(); } function add_video2() { if ( mll_int( lkey, "Video2Visible" ) != 1 ) return; local x = mll_int( lkey, "Video2Left" ); local y = mll_int( lkey, "Video2Top" ); local w = mll_int( lkey, "Video2Width" ); local h = mll_int( lkey, "Video2Height" ); local vid = fe.add_image( mll_file( lkey, "Video2File", ::mala_subdir ), x, y, w, h ); if ( vid.file_name.len() < 1 ) vid.file_name = mll_file( lkey, "Video2File" ); } function add_text( num ) { local ex = lkey.slice( 0, 1 ); local k = "Text" + num; if ( mll_int( k, "Visible"+ex ) == 1 ) { local x = mll_int( k, "Left"+ex ); local y = mll_int( k, "Top"+ex ); local w = mll_int( k, "Width"+ex ); local h = mll_int( k, "Height"+ex ); if ( vert ) { // swap w&h, x&y local temp=w; w=h; h=temp; temp=x; x = fe.layout.width - y - w; y = temp; } local text = fe.add_text( mll_text( k, "Pattern" ), x, y, w, h ); text.font = mll_str( k, "FontName" ); text.charsize = mll_int( k, "FontSize" ); text.red = mll_red( k, "FontColor" ); text.green = mll_green( k, "FontColor" ); text.blue = mll_blue( k, "FontColor" ); text.align = mll_align( k, "FontAlignment"+ex ); } } function add_logo() { if ( mll_int( lkey, "LogoVisible" ) != 1 ) return; local x = mll_int( lkey, "LogoLeft" ); local y = mll_int( lkey, "LogoTop" ); local w = mll_int( lkey, "LogoWidth" ); local h = mll_int( lkey, "LogoHeight" ); if ( vert ) { // swap w&h, x&y local temp=w; w=h; h=temp; temp=x; x = fe.layout.width - y - w; y = temp; } local text = fe.add_text( "[DisplayName]", x, y, w, h ); const LOGO="Logo"; text.font = mll_str( LOGO, "FontName" ); text.charsize = mll_int( LOGO, "FontSize" ); text.red = mll_red( LOGO, "FontColor" ); text.green = mll_green( LOGO, "FontColor" ); text.blue = mll_blue( LOGO, "FontColor" ); text.align = mll_align( lkey, "LogoFontAlignment" ); } function add_sound( label ) { if (( mll["Sounds"].rawin( label ) ) && ( mll["Sounds"][label].len() > 0 )) { local snd = fe.add_sound( fe.script_dir + mll_file("Sounds",label,::mala_subdir) ); if ( snd.file_name.len() < 1 ) snd.file_name = fe.script_dir + mll_file("Sounds",label); return snd; } return null; } // // Get the mll file settings // mll <- {}; local entity=null; local f = ReadTextFile( fe.script_dir, ::file_to_load ); while ( !f.eos() ) { local line = f.read_line(); if (( line.len() > 0 ) && ( line[0] == '[' )) { entity = line.slice( 1, line.len()-1 ); mll[ entity ] <- {}; } else { local temp = split( line, "=" ); local v = ( temp.len() > 1 ) ? temp[ 1] : ""; if ( entity ) mll[entity][temp[0]] <- v; else mll[temp[0]] <- v; } } function add_config_attribute( mll_tag, porder ) { if ( mll_int( "Vertical", mll_tag + "Visible" ) || mll_int( "Horizontal", mll_tag + "Visible" ) ) { local layout_tag = mll_tag.tolower(); UserConfig[layout_tag] <- ""; UserConfig.setattributes( layout_tag, { label=mll_tag + " Artwork", help="Name the artwork to put in the " + mll_tag + " slot", order=porder } ); } } // // Load mll-dependent options into the UserConfig table // { local config_attrs = [ "CPanel", "Video", "Definable1", "Definable2", "Definable3", "Definable4", "Definable5" ]; local o=10; foreach ( a in config_attrs ) add_config_attribute( a, o++ ); } // // Execution will end here when the script is run to get config settings // am_options <- fe.get_config(); local temp = split( ::file_to_load, "\\/" ); print( " - Found MALA layout: " + temp[ temp.len()-1 ] + "\n" ); // // lkey <- am_options["orient"]; vert <- ( lkey == "Vertical" ); local tmp = mll_str( "Layout", "Name" ); if ( tmp.len() > 4 ) ::mala_subdir <- tmp.slice( 0, tmp.len()-4 ) + "/"; else ::mala_subdir <- ""; // // Initialize the layout // if ( vert ) { fe.layout.width = mll_int( "Layout", "FixedSizeHeight" ); fe.layout.height = mll_int( "Layout", "FixedSizeWidth" ); fe.layout.base_rotation = RotateScreen.Left; } else { fe.layout.width = mll_int( "Layout", "FixedSizeWidth" ); fe.layout.height = mll_int( "Layout", "FixedSizeHeight" ); } // set the default font to the list center font fe.layout.font = mll_str( "List", "CenterFontName" ); // Draw artworks according to ZOrder for ( local i=0; i<29; i++ ) { switch( mll_int( "ZOrder", i.tostring(), i ) ) { case 0: add_bgimage(); break; // 1-4 (Animation4-Animation1) case 5: add_art( "Video", am_options.rawin("video")?am_options["video"]:"", "" ); break; case 6: local t="Definable4"; local t2=t.tolower(); add_art( t, am_options.rawin(t2)?am_options[t2]:"" ); break; case 7: local t="Definable3"; local t2=t.tolower(); add_art( t, am_options.rawin(t2)?am_options[t2]:"" ); break; case 8: local t="Definable2"; local t2=t.tolower(); add_art( t, am_options.rawin(t2)?am_options[t2]:"" ); break; case 9: local t="Definable1"; local t2=t.tolower(); add_art( t, am_options.rawin(t2)?am_options[t2]:"" ); break; case 10: add_art( "CPanel", am_options.rawin("cpanel")?am_options["cpanel"]:"" ); break; case 11: add_art( "Marquee", "marquee" ); break; case 12: add_art( "Snap", "snap" ); break; case 13: add_custom( "Custom4" ); break; case 14: add_custom( "Custom3" ); break; case 15: add_custom( "Custom2" ); break; case 16: add_custom( "Custom1" ); break; case 17: add_text( 8 ); break; case 18: add_text( 7 ); break; case 19: add_text( 6 ); break; case 20: add_text( 5 ); break; case 21: add_text( 4 ); break; case 22: add_text( 3 ); break; case 23: add_text( 2 ); break; case 24: add_text( 1 ); break; case 25: add_logo(); break; case 26: add_list(); break; case 27: add_video2(); break; case 28: local t="Definable5"; local t2=t.tolower(); add_art( t, am_options.rawin(t2)?am_options[t2]:"" ); break; } } // // Handle layout sounds // local move_sound = add_sound( "Movement" ); local to_game_sound = add_sound( "StartGame" ); local from_game_sound = add_sound( "QuitGame" ); local list_switch_sound = add_sound( "ListSwitch" ); local screen_saver_sound = add_sound( "AttractMode" ); local exit_sound = add_sound( "Exit" ); if ( mll_str( "Sounds", "BGMusic" ).len() > 0 ) { fe.ambient_sound.file_name = fe.script_dir + mll_file( "Sounds", "BGMusic", ::mala_subdir ); if ( fe.ambient_sound.file_name.len() < 1 ) fe.ambient_sound.file_name = fe.script_dir + mll_file( "Sounds", "BGMusic" ); fe.ambient_sound.playing=true; } fe.add_transition_callback( "mala_sound_callback" ); function mala_sound_callback( ttype, var, ttime ) { switch ( ttype ) { case Transition.ToNewSelection: if ( move_sound ) move_sound.playing=true; break; case Transition.ToGame: if ( to_game_sound ) to_game_sound.playing=true; break; case Transition.FromGame: if ( from_game_sound ) from_game_sound.playing=true; break; case Transition.ToNewList: if ( list_switch_sound ) list_switch_sound.playing=true; break; case Transition.EndLayout: if (( var == FromTo.ScreenSaver ) && ( screen_saver_sound )) screen_saver_sound.playing=true; else if (( var == FromTo.Frontend ) && ( exit_sound )) exit_sound.playing=true; break; } return false; } if ( am_options["fade"] == "Yes" ) fe.add_transition_callback( "mala_transition_callback" ); function mala_transition_callback( ttype, var, ttime ) { switch ( ttype ) { case Transition.ToGame: case Transition.EndLayout: if ( ttime < 255 ) { foreach ( o in fe.obj ) o.alpha = 255 - ttime; return true; } if ( !fe.obj[0].alpha == 0 ) { foreach ( o in fe.obj ) o.alpha=0; return true; } break; case Transition.FromGame: case Transition.StartLayout: if ( ttime < 255 ) { foreach ( o in fe.obj ) o.alpha = ttime; return true; } foreach ( o in fe.obj ) o.alpha = 255; break; } return false; } mll=null;