diff --git a/.pylintrc b/.pylintrc index 65626e0..21e1e15 100644 --- a/.pylintrc +++ b/.pylintrc @@ -142,7 +142,8 @@ disable=print-statement, too-few-public-methods, too-many-lines, duplicate-code, # can't get it to shut up about @pytest.mark.skipif's :-/ - no-else-return + no-else-return, + len-as-condition # Enable the message, report, category or checker with the given id(s). You can # either give multiple identifier separated by comma (,) or put this option diff --git a/vasl_templates/main_window.py b/vasl_templates/main_window.py index 2919aff..9652abe 100644 --- a/vasl_templates/main_window.py +++ b/vasl_templates/main_window.py @@ -86,8 +86,8 @@ class MainWindow( QWidget ): if val : self.restoreGeometry( val ) else : - self.resize( 1000, 600 ) - self.setMinimumSize( 1000, 520 ) + self.resize( 1050, 650 ) + self.setMinimumSize( 1000, 595 ) # initialize the layout layout = QVBoxLayout( self ) diff --git a/vasl_templates/webapp/__init__.py b/vasl_templates/webapp/__init__.py index a0ba415..47ac733 100644 --- a/vasl_templates/webapp/__init__.py +++ b/vasl_templates/webapp/__init__.py @@ -97,6 +97,7 @@ import vasl_templates.webapp.snippets #pylint: disable=cyclic-import import vasl_templates.webapp.files #pylint: disable=cyclic-import import vasl_templates.webapp.vassal #pylint: disable=cyclic-import import vasl_templates.webapp.vo_notes #pylint: disable=cyclic-import +import vasl_templates.webapp.nat_caps #pylint: disable=cyclic-import import vasl_templates.webapp.roar #pylint: disable=cyclic-import if app.config.get( "ENABLE_REMOTE_TEST_CONTROL" ): print( "*** WARNING: Remote test control enabled! ***" ) diff --git a/vasl_templates/webapp/data/default-template-pack/nat_caps.j2 b/vasl_templates/webapp/data/default-template-pack/nat_caps.j2 new file mode 100644 index 0000000..af5fd7d --- /dev/null +++ b/vasl_templates/webapp/data/default-template-pack/nat_caps.j2 @@ -0,0 +1,55 @@ + + + + + + + + + +
+ {{INCLUDE:player_flag}}{{PLAYER_NAME|nbsp}} Capabilities + +
+ +{%if NAT_CAPS%} + +
    +{%if NAT_CAPS.GRENADES%}
  • {{NAT_CAPS.GRENADES}} {%endif%} +{%if NAT_CAPS.HOB_DRM%}
  • Heat of Battle: {{NAT_CAPS.HOB_DRM}} {%endif%} +{%if NAT_CAPS.TH_COLOR%}
  • {{NAT_CAPS.TH_COLOR}} {%endif%} +{%if NAT_CAPS.OBA_BLACK%} +
  • OBA: {{NAT_CAPS.OBA_BLACK}} {{NAT_CAPS.OBA_RED}} + {%if NAT_CAPS.OBA_ACCESS%} (access: {{NAT_CAPS.OBA_ACCESS}}) {%endif%} + + {%if NAT_CAPS.OBA_COMMENTS%} +
      {%for cmt in NAT_CAPS.OBA_COMMENTS%}
    • {{cmt}} {%endfor%}
    + {%endif%} +{%endif%} +
+ +{%if NAT_CAPS.NOTES%} +
    {%for note in NAT_CAPS.NOTES%} +
  • {{note}} +{%endfor%}
+{%endif%} + +{%else%} + +Not available. + +{%endif%} + +
+ + diff --git a/vasl_templates/webapp/data/default-template-pack/national-capabilities.json b/vasl_templates/webapp/data/default-template-pack/national-capabilities.json new file mode 100644 index 0000000..6eaa55a --- /dev/null +++ b/vasl_templates/webapp/data/default-template-pack/national-capabilities.json @@ -0,0 +1,311 @@ +{ + +"german": { + "th_color": "Black", + "oba": [ "8B", "3R" ], "oba_access": "≤ 2", + "hob_drm": "0 DRM", + "grenades": "Smoke", + "notes": [ + "{? 10/1943- | Inherent PF | No Inherent PF | Inherent PF (10/43+) ?}", + "{? 01/1944- | Inherent ATMM | No Inherent ATMM | Inherent ATMM (44+) ?}", + "SS: Disrupt & RtPh Surrender NA vs Russians", + "Massacre OK", + "{? 01/1944- | Squad Assault Fire | No Squad Assault Fire | Squad Assault Fire (44+) ?}" + ] +}, + +"russian": { + "th_color": "Red", + "oba": [ "5B", "2R" ], "oba_access": "≤ 1", + "hob_drm": "+2 DRM", + "grenades": null, + "notes": [ + "Massacre OK", + "Deploy NA", + "Entrench -1 DRM", + "{? 11/1942- | Commissars NA | Commissars | Commissars (pre-11/42) ?}", + "Human Wave", + "{? 01/1942- | Riders OK | Riders NA | Riders (42+) ?}" + ] +}, + +"american": { + "th_color": "{? 01/1944- | Black | Red | Black (44+) ?}", + "oba": [ "10B", "3R", "Plentiful Ammo included" ], + "oba_access": "≤ 2", + "hob_drm": "0 DRM", + "grenades": "SMOKE", + "notes": [ + "U.S.M.C.: " + ] +}, +"kfw-american": { + "th_color": "{! 06/1950-08/1950 = Red | 09/1950- = Black | ??? !}", + "oba": [ "{! 06/1950-08/1950 = 9B | 09/1950- = 10B | ??? !}", "3R", + "{! 09/1950- = Plentiful Ammo included !}" + ], + "oba_access": "≤ 2", + "hob_drm": [ "0 DRM", "+3 for Katusa; NA for TACP" ], + "grenades": "SMOKE", + "notes": [ + "Rangers: 6-6-8 ", + "Airborne: 6-6-7", + "{! 06/1950-08/1950 = Early KW U.S. Army rules: !}", + "Katusa: As U.S. Army MMC ", + "Disruption NA", + "7-6-8 can Self-Deploy", + "Use 5-5-8 when: ", + "Tactical Air Control Party: " + ] +}, + +"british": { + "th_color": "Black", + "oba": [ "8B", "2R" ], "oba_access": "≤ 2", + "hob_drm": "-1 DRM", + "grenades": "{? 01/1944- | SMOKE | Smoke | SMOKE (44+) ?}", + "notes": [ + "Elite & 1st Line: Cowering NA", + "ANZAC: Stealthy (unless Green)", + "Gurkha: " + ] +}, + +"french": { + "th_color": [ "Black", "AFV use Red TH#" ], + "oba": [ "6B", "2R" ], "oba_access": "≤ 1", + "hob_drm": "+1 DRM", + "grenades": "Smoke", + "notes": [] +}, + +"italian": { + "th_color": "Red", + "oba": [ "7B", "3R" ], "oba_access": "≤ 1", + "hob_drm": "+3 DRM", + "grenades": "Smoke", + "notes": [ + "Escape NA", + "1st Line & Conscript: " + ] +}, + +"finnish": { + "th_color": "Red", + "oba": [ + "{! 01/1939-12/1940 = 6B | 01/1941-12/1942 = 7B | 01/1943-09/1944 = 8B | 10/1944- = 7B | ??? !}", + "3R", + "Plentiful Ammo included" + ], + "oba_access": "≤ 1", + "hob_drm": "-1 DRM", + "grenades": null, + "notes": [ + "Deploy (1TC) & Recombine without Leader", + "Self-Rally OK [EXC: Conscript]", + "Cowering NA [EXC: Conscript]", + "Elite & 1st Line: ", + "Ski-trained (don Skis = one MF)", + "Leader Creation NA", + "Captured Use penalties NA for Russian MG [EXC: LMG in 1939; .50-cal]" + ] +}, + +"axis-minor": { + "th_color": "Red", + "oba": [ "6B", "3R" ], "oba_access": "≤ 1", + "hob_drm": "+3 DRM", + "grenades": "Smoke", + "notes": [ + "Escape NA", + "1st Line & Conscript: ", + "Inherent PF in non-Crew MMC (Romanian 3/44+; Hungarian 6/44+): ", + "{? 07/1943- | Inherent ATMM in Romanian non-Crew Elite & 1st Line MMC (-2 CC DRM) | No Inherent ATMM | Inherent ATMM (7/43+) ?}" + ] +}, + +"allied-minor": { + "th_color": "Red", + "oba": [ "6B", "3R" ], "oba_access": "≤ 1", + "hob_drm": "+2 DRM", + "grenades": "Smoke", + "notes": [ + "+1 Broken Morale vs Italians", + "1st Line & Green: 1 PAATC" + ] +}, + +"japanese": { + "th_color": "Black", + "oba": [ "5B", "2R" ], "oba_access": "≤ 1", + "hob_drm": "+4 DRM", + "grenades": "SMOKE", + "notes": [ + "SMC PTC/Pin/Break NA", + "Leaders: ", + "Tank-Hunter Heroes & ATMM", + "Banzai Charge (always Lax)", + "Elite & 1st Line: Always Stealthy", + "Conscript: Always Lax", + "ATR/MMG/HMG Breakdown penalty", + "Always NA: ", + "LLMC → LLTC if unbroken", + "Massacre OK", + "-1 Interrogation DRM", + "-2 Concealment drm", + "Enemy +2 search drm", + "Hand-to-Hand CC & Hara-Kiri" + ] +}, + +"chinese~gmd": { + "th_color": "Red", + "oba": [ "5B", "2R", + "6B/2R if Majority Squad Type is 5-3-7", + "5B/3R if Majority Squad Type is 3-3-7 or 3-3-6" + ], + "oba_access": "≤ 1", + "hob_drm": "0 DRM", + "grenades": "SMOKE", + "notes": [ + "Deploy NA", + "Lax at Night", + "+1 Leader Creation drm", + "1st Line & Conscript: 1 PAATC", + "Human Wave", + "Dare-Death Squads [EXC: 5-3-7]" + ] +}, + +"chinese": { + "th_color": "Red", + "oba": null, + "hob_drm": "+1 DRM", + "grenades": null, + "notes": [ + "Cowering NA", + "Commissars", + "Human Wave", + "Dare-Death Squads" + ] +}, + +"kfw-rok": { + "th_color": "{! -08/1950 = Red | 09/1950-04/1951 = Red (ROK) ; Black (KMC) | 05/1951- = Black | ??? !}", + "oba": [ "{! 06/1950- = 10B | ??? !}", "3R", + "{! 09/1950- = Plentiful Ammo included !}", + "{! 06/1950-08/1950 = ROK: 6B/3R !}" + ], + "oba_access": "≤ 1 (ROK) ; 2 (KMC)", + "hob_drm": "+3/+4 DRM", + "grenades": "SMOKE", + "notes": [ + "Republic of Korea (ROK): ", + "Korean Marine Corps (KMC): " + ] +}, + +"kfw-bcfk": { + "th_color": "Black", + "oba": [ "8B", "2R" ], "oba_access": "≤ 2", + "hob_drm": "-1 DRM", + "grenades": "SMOKE", + "notes": [ + "2nd Line MMC: ELR Replacement → Disrupt", + "{? 01/1952- | Canadian squads have Assault Fire | | Canadian squads have Assault Fire (1/52+) ?}", + "Royal Marines: " + ] +}, + +"kfw-ounc": { + "th_color": "Black", + "oba": [ "9B", "3R" ], "oba_access": "≤ 1", + "hob_drm": [ "0 DRM", "+3 for Turkish" ], + "grenades": "SMOKE", + "notes": [ + "2nd Line MMC: ELR Replacement → Disrupt [EXC: Turkish]", + "Bayonet Charge NTC NA for Ethiopian, French, Turkish leaders" + ] +}, + +"kfw-kpa": { + "th_color": "Red", + "oba": [ "5B", "2R" ], "oba_access": "≤ 1", + "hob_drm": "+2 DRM", + "grenades": null, + "notes": [ + "As Russian ", + "Suicide Heroes", + "Starshell restrictions", + "Assault Engineers: WP grenades", + "Communist Partisans: " + ] +}, + +"kfw-cpva": { + "th_color": "Red", + "oba": [ + "{? 04/1951- | 7B | | 7B (4/51+) ?}", + "{! 04/1951-09/1952 = 3R | 10/1952- = 2R | ??? !}" + ], + "oba_access": "≤ 1", + "hob_drm": "+1 DRM", + "grenades": null, + "notes": [ + "Always Stealthy", + "Starshell restrictions", + "Armored Assault NA", + "Riders NA", + "Assault Engineers: WP grenades", + "{! 10/1950-03/1951 = Early KW CPVA rules !}", + "Leaders & Political Officers increase Morale as if Commissar", + "SW B#/X#/ROF penalty", + "Restricted Fire", + "Infantry Platoon Movement", + "Hand-to-Hand CC (-1 DRM)", + "HS Infantry Overrun", + "Bugles", + "Entrench -1 DRM", + "PAATC NTC NA", + "Infantry Overrun NTC NA", + "Conceal if +2 Hindrance", + "Concealment -1 drm", + "Civilian Interrogation is always in effect" + ] +}, + +"burmese": { + "th_color": "Red", + "oba": null, + "hob_drm": "+2 DRM", + "grenades": null, + "notes": [ + "Dare-Death Squads (as if Chinese)", + "Elite and 1st Line MMC: Always Stealthy", + "Deploy NA [EXC: A20.5 & A21.22]; Recombine OK", + "Leaders: Morale/Berserk/Rally as Commissar" + ] +}, + +"indonesian": { + "th_color": "Red", + "oba": [ "5B", "3R" ], + "hob_drm": "+3 DRM", + "grenades": "Smoke", + "notes": [ + "Tank-Hunter/DC Heroes (as if 1945 Japanese)", + "Hand-to-Hand Combat", + "Massacre OK", + "HoB DR ≥ 12 → Berserk", + "Deploy NA [EXC: A20.5 & A21.22]; Recombine OK" + ] +}, + +"thai": { + "th_color": "Black", + "oba": [ "7B", "3R" ], + "hob_drm": "0 DRM", + "grenades": "Smoke" +} + +} diff --git a/vasl_templates/webapp/nat_caps.py b/vasl_templates/webapp/nat_caps.py new file mode 100644 index 0000000..f504171 --- /dev/null +++ b/vasl_templates/webapp/nat_caps.py @@ -0,0 +1,18 @@ +""" Main webapp handlers. """ + +from flask import render_template + +from vasl_templates.webapp import app + +# --------------------------------------------------------------------- + +@app.route( "/national-capabilities///", defaults={"month":1} ) +@app.route( "/national-capabilities////" ) +def get_national_capabilities( nat, theater, year, month ): + """Get the national capabilities snippet.""" + return render_template( "national-capabilities.html", + NATIONALITY = nat, + THEATER = theater, + YEAR = year, + MONTH = month + ) diff --git a/vasl_templates/webapp/snippets.py b/vasl_templates/webapp/snippets.py index 5ea6799..419e14b 100644 --- a/vasl_templates/webapp/snippets.py +++ b/vasl_templates/webapp/snippets.py @@ -44,9 +44,10 @@ def load_default_template_pack(): #pylint: disable=too-many-locals "default-template-pack/" ) data = { "templates": {} } - fname = os.path.join( base_dir, "nationalities.json" ) - with open(fname,"r") as fp: + with open( os.path.join( base_dir, "nationalities.json" ), "r") as fp: data["nationalities"] = json.load( fp ) + with open( os.path.join( base_dir, "national-capabilities.json" ), "r" ) as fp: + data["national-capabilities"] = json.load( fp ) # NOTE: Similarly, we always load the default extras templates, and user-defined template packs # can add to them, or modify existing ones, but not remove them. diff --git a/vasl_templates/webapp/static/css/tabs-scenario.css b/vasl_templates/webapp/static/css/tabs-scenario.css index bab3952..bae2f7c 100644 --- a/vasl_templates/webapp/static/css/tabs-scenario.css +++ b/vasl_templates/webapp/static/css/tabs-scenario.css @@ -16,6 +16,9 @@ #panel-scenario label.header { font-weight: bold ; width: 3em ; text-align: center ; } #panel-scenario input { margin-bottom: 0.25em ; } +#panel-scenario #oba-info { height: 1em ; } +.oba-info-tooltip { max-width: 500px ; } + #panel-scenario .select2-container { margin: 2px ; } #panel-scenario .select2-selection__rendered { height: 23px ; line-height: 23px ; } #panel-scenario .select2-selection__arrow { margin-top: -2px ; } diff --git a/vasl_templates/webapp/static/css/tabs.css b/vasl_templates/webapp/static/css/tabs.css index 19c3454..4317ba0 100644 --- a/vasl_templates/webapp/static/css/tabs.css +++ b/vasl_templates/webapp/static/css/tabs.css @@ -19,7 +19,7 @@ /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ #tabs-scenario { display: flex ; } -#tabs-scenario .left { width: 33.5em ; min-width: 33.5em ; } +#tabs-scenario .left { width: 32em ; min-width: 32em ; } #tabs-scenario .right { flex-grow: 1 ; min-width: 25em ; } #tabs-scenario .left { display: flex ; flex-direction: column ; } diff --git a/vasl_templates/webapp/static/images/nat-caps.png b/vasl_templates/webapp/static/images/nat-caps.png new file mode 100644 index 0000000..8b0a0ba Binary files /dev/null and b/vasl_templates/webapp/static/images/nat-caps.png differ diff --git a/vasl_templates/webapp/static/images/oba-info.png b/vasl_templates/webapp/static/images/oba-info.png new file mode 100644 index 0000000..643ebe7 Binary files /dev/null and b/vasl_templates/webapp/static/images/oba-info.png differ diff --git a/vasl_templates/webapp/static/main.js b/vasl_templates/webapp/static/main.js index a48a7b7..e0622db 100644 --- a/vasl_templates/webapp/static/main.js +++ b/vasl_templates/webapp/static/main.js @@ -137,6 +137,12 @@ $(document).ready( function () { onClose: on_scenario_date_change, } ) ; + // initialize the OBA INFO tooltip + $( "#oba-info" ).tooltip( { + tooltipClass: "oba-info-tooltip", + content: make_oba_info_tooltip, + } ) ; + // initialize the SSR's $("#ssr-sortable").sortable2( "init", { add: add_ssr, edit: edit_ssr @@ -394,6 +400,8 @@ $(document).ready( function () { template_id = "ob_vehicles" ; else if ( template_id.substring(0,12) === "ob_ordnance_" ) template_id = "ob_ordnance" ; + else if ( template_id.substring(0,9) === "nat_caps_" ) + template_id = "nat_caps" ; $( "" @@ -415,14 +423,16 @@ function init_snippet_button( $btn ) var template_id2 ; if ( template_id.substring(0,9) === "ob_setup_" ) template_id2 = "ob_setup" ; - else if ( template_id.substring(0,21) == "ob_vehicles_ma_notes_" ) + else if ( template_id.substring(0,21) === "ob_vehicles_ma_notes_" ) template_id2 = "ob_vehicles_ma_notes" ; - else if ( template_id.substring(0,21) == "ob_ordnance_ma_notes_" ) + else if ( template_id.substring(0,21) === "ob_ordnance_ma_notes_" ) template_id2 = "ob_ordnance_ma_notes" ; - else if ( template_id.substring(0,12) == "ob_vehicles_" ) + else if ( template_id.substring(0,12) === "ob_vehicles_" ) template_id2 = "ob_vehicles" ; - else if ( template_id.substring(0,12) == "ob_ordnance_" ) + else if ( template_id.substring(0,12) === "ob_ordnance_" ) template_id2 = "ob_ordnance" ; + else if ( template_id.substring(0,9) === "nat_caps_" ) + template_id2 = "nat_caps" ; else template_id2 = template_id ; @@ -435,12 +445,18 @@ function init_snippet_button( $btn ) "" ] ; var $newBtn = $( buf.join("") ) ; - $newBtn.find( "button" ).prepend( - $( "" ) - ).click( function( evt ) { - generate_snippet( $(this), evt, null ) ; - return false ; - } ).attr( "title", GENERATE_SNIPPET_HINT ) ; + var fname="snippet.png", style="" ; + if ( template_id.substring( 0, 9 ) === "nat_caps_" ) { + fname = "nat-caps.png" ; + style = "height:15px;margin-right:0;" ; + } + $newBtn.find( "button" ) + .prepend( $( "" ) ) + .click( function( evt ) { + generate_snippet( $(this), evt, null ) ; + return false ; + } ) + .attr( "title", GENERATE_SNIPPET_HINT ) ; // add in the droplist $newBtn.controlgroup() ; @@ -514,6 +530,10 @@ function update_page_load_status( id ) $("#tabs").tabs({ disabled: [] }) ; $("#loader").fadeOut( 500 ) ; adjust_footer_vspacers() ; + // position the PLAYERS snippet button + var $btn = $( ".snippet-control[data-id='players']" ) ; + var $sel = $( ".select2[name='PLAYER_2_SAN']" ) ; + $btn.offset( { left: $sel.offset().left + $sel.outerWidth() - $btn.outerWidth() } ) ; // NOTE: The watermark image appears briefly in IE when reloading the page, but not even // creating the watermark dynamically and removing it when the page unloads fixes it :-( $("#watermark").fadeIn( 5*1000 ) ; @@ -717,6 +737,49 @@ function on_player_change( player_no ) // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +function make_oba_info_tooltip() +{ + // initialize + var buf = [ "" ] ; + buf.push( "", "" ) ; + var player_nat = $( "select[name='PLAYER_" + player_no + "']" ).val() ; + var display_name = get_nationality_display_name( player_nat ) ; + buf.push( "", "
Off-Board Artillery" ) ; + + // initialize + var params = { + SCENARIO_THEATER: $( "select.param[name='SCENARIO_THEATER']" ).val() + } ; + var scenario_date = get_scenario_date() ; + if ( scenario_date ) { + params.SCENARIO_MONTH = 1 + scenario_date.getMonth() ; + params.SCENARIO_YEAR = scenario_date.getFullYear() ; + } + + // add the OBA info for each player + for ( var player_no=1 ; player_no <= 2 ; ++player_no ) { + buf.push( "
", display_name+":" ) ; + set_nat_caps_params( player_nat, params ) ; + if ( ! params.NAT_CAPS ) + params.NAT_CAPS = { OBA_BLACK: "-", OBA_RED: "-" } ; + buf.push( "" ) ; + var colors = [ "BLACK", "RED" ] ; + for ( var i=0 ; i < colors.length ; ++i ) { + var val = params.NAT_CAPS[ "OBA_"+colors[i] ] || "-" ; + buf.push( "", val, "" ) ; + } + if ( params.NAT_CAPS.OBA_COMMENTS ) { + for ( i=0 ; i < params.NAT_CAPS.OBA_COMMENTS.length ; ++i ) + buf.push( "
", "", params.NAT_CAPS.OBA_COMMENTS[i] ) ; + } + } + + buf.push( "
" ) ; + return buf.join( "" ) ; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + function update_ob_tab_header( player_no ) { // update the OB tab header for the specified player @@ -741,6 +804,7 @@ function update_nationality_specific_buttons( player_no ) var theater = $( "select.param[name='SCENARIO_THEATER']" ).val().toLowerCase() ; // hide/show each nationality-specific button + var $elem ; for ( var button_id in NATIONALITY_SPECIFIC_BUTTONS ) { var show = false ; for ( var i=0 ; i < NATIONALITY_SPECIFIC_BUTTONS[button_id].length ; ++i ) { @@ -755,9 +819,16 @@ function update_nationality_specific_buttons( player_no ) show = nat.substr(0,pos) == player_nat && nat.substr(pos+1) !== theater ; } } - var $elem = $( "#panel-ob_notes_" + player_no + " div.snippet-control[data-id='" + button_id + "']" ) ; + $elem = $( "#panel-ob_notes_" + player_no + " div.snippet-control[data-id='" + button_id + "']" ) ; $elem.css( "display", show ? "inline-block" : "none" ) ; } + + // update the CAPABILITIES button + var $btn = $( "button.generate[data-id='nat_caps_" + player_no + "']" ) ; + if ( get_national_capabilities( player_nat ) ) + $btn.removeClass( "inactive" ) ; + else + $btn.addClass( "inactive" ) ; } // -------------------------------------------------------------------- diff --git a/vasl_templates/webapp/static/nat_caps.js b/vasl_templates/webapp/static/nat_caps.js new file mode 100644 index 0000000..9462232 --- /dev/null +++ b/vasl_templates/webapp/static/nat_caps.js @@ -0,0 +1,117 @@ +// -------------------------------------------------------------------- + +function set_nat_caps_params( player_nat, params ) +{ + // get the national capabilities + var is_kfw = params.SCENARIO_THEATER == "Korea" ; + var nat_caps = get_national_capabilities( player_nat, is_kfw ) ; + if ( ! nat_caps ) + return ; + + // initialize + params.NAT_CAPS = {} ; + var excRegex = new RegExp( /\[EXC: .*?\]/g ) ; + var val ; + + function add_nat_cap( key, val ) { + if ( val !== undefined ) + params.NAT_CAPS[ key ] = val ; + } + function fixup_content( val ) { + val = strReplaceAll( val, "1st", "1st" ) ; + val = strReplaceAll( val, "2nd", "2nd" ) ; + return wrapSubstrings( val, excRegex, "", "" ) ; + } + + // set the TH# color + if ( nat_caps.th_color ) { + if ( $.isArray( nat_caps.th_color ) ) { + add_nat_cap( "TH_COLOR", + make_time_based_comment( nat_caps.th_color[0], params.SCENARIO_MONTH, params.SCENARIO_YEAR ) + " TH#" + + " (" + nat_caps.th_color[1] + ")" + ) ; + } else { + var th_color = make_time_based_comment( nat_caps.th_color, params.SCENARIO_MONTH, params.SCENARIO_YEAR ) ; + var match = th_color.match( /\(.+\)$/ ) ; + if ( match ) + th_color = th_color.substring(0,match.index) + "TH# " + match[0] ; + else + th_color += " TH#" ; + add_nat_cap( "TH_COLOR", th_color ) ; + } + } + + // set the HoB DRM + if ( nat_caps.hob_drm ) { + if ( $.isArray( nat_caps.hob_drm ) ) { + add_nat_cap( "HOB_DRM", + nat_caps.hob_drm[0] + + " (" + nat_caps.hob_drm[1] + ")" + ) ; + } else { + add_nat_cap( "HOB_DRM", nat_caps.hob_drm ) ; + } + } + + // set the type of grenades available + if ( nat_caps.grenades !== undefined ) { + val = (nat_caps.grenades === null) ? "No" : make_time_based_comment( nat_caps.grenades, params.SCENARIO_MONTH, params.SCENARIO_YEAR ) ; + add_nat_cap( "GRENADES", val+" grenades" ) ; + } + + // set the OBA red/black numbers + if ( nat_caps.oba ) { + params.NAT_CAPS.OBA_BLACK = make_time_based_comment( nat_caps.oba[0], params.SCENARIO_MONTH, params.SCENARIO_YEAR ) ; + params.NAT_CAPS.OBA_RED = make_time_based_comment( nat_caps.oba[1], params.SCENARIO_MONTH, params.SCENARIO_YEAR ) ; + if ( nat_caps.oba.length > 2 ) { + var oba_comments = [] ; + for ( i=2 ; i < nat_caps.oba.length ; ++i ) { + val = make_time_based_comment( nat_caps.oba[i], params.SCENARIO_MONTH, params.SCENARIO_YEAR ) ; + if ( val ) + oba_comments.push( val ) ; + } + if ( oba_comments.length > 0 ) + params.NAT_CAPS.OBA_COMMENTS = oba_comments ; + } + } + + // set the OBA access number + add_nat_cap( "OBA_ACCESS", nat_caps.oba_access ) ; + + // add any additional notes + if ( nat_caps.notes ) { + params.NAT_CAPS.NOTES = [] ; + for ( i=0 ; i < nat_caps.notes.length ; ++i ) { + val = make_time_based_comment( nat_caps.notes[i], params.SCENARIO_MONTH, params.SCENARIO_YEAR ) ; + if ( val ) + params.NAT_CAPS.NOTES.push( fixup_content( val ) ) ; + } + } +} + +// -------------------------------------------------------------------- + +function get_national_capabilities( nat, is_kfw ) +{ + // get the capabilities for the specified nationality + if ( ! nat ) + return null ; + if ( is_kfw ) { + if ( nat === "american" ) + nat = "kfw-american" ; + else if ( ["british","british~canadian","british~newzealand"].indexOf( nat ) !== -1 ) + nat = "kfw-bcfk" ; + } + else if ( nat === "anzac" || nat === "free-french" || nat.substring(0,8) === "british~" ) + nat = "british" ; + var nat_caps = gTemplatePack["national-capabilities"][ nat ] ; + if ( nat_caps ) + return nat_caps ; + if ( gTemplatePack.nationalities[ nat ] ) { + var nat_type = gTemplatePack.nationalities[ nat ].type ; + if ( nat_type ) + return gTemplatePack["national-capabilities"][ nat_type ] ; + } + return null ; +} + diff --git a/vasl_templates/webapp/static/snippets.js b/vasl_templates/webapp/static/snippets.js index 062a31f..3d7b9ce 100644 --- a/vasl_templates/webapp/static/snippets.js +++ b/vasl_templates/webapp/static/snippets.js @@ -151,7 +151,11 @@ function make_snippet( $btn, params, extra_params, show_date_warnings ) params.PLAYER_FLAG_SIZE = "width='11' height='11'" ; // set player-specific parameters - var player_no = get_player_no_for_element( $btn ) ; + var player_no ; + if ( template_id.substring( 0, 9 ) === "nat_caps_" ) + player_no = template_id.substring( 9 ) ; + else + player_no = get_player_no_for_element( $btn ) ; var player_nat = get_player_nat( player_no ) ; if ( player_no ) { params.PLAYER_NAME = get_nationality_display_name( params["PLAYER_"+player_no] ) ; @@ -213,6 +217,8 @@ function make_snippet( $btn, params, extra_params, show_date_warnings ) params.OB_VO_WIDTH = params.OB_ORDNANCE_WIDTH_2 ; snippet_save_name = params.PLAYER_2 + " ordnance" ; } + if ( template_id === "nat_caps_1" || template_id === "nat_caps_2" ) + template_id = "nat_caps" ; // adjust comments adjust_vo_comments( params ) ; @@ -242,12 +248,12 @@ function make_snippet( $btn, params, extra_params, show_date_warnings ) set_vo_note( "ordnance" ) ; // generate snippets for multi-applicable vehicle/ordnance notes - var pos ; + var pos, i ; function add_ma_notes( ma_notes, keys, param_name, nat, vo_type ) { if ( ! keys ) return ; params[ param_name ] = [] ; - for ( var i=0 ; i < keys.length ; ++i ) { + for ( i=0 ; i < keys.length ; ++i ) { var ma_note = get_ma_note( nat, vo_type, keys[i] ) ; var key = keys[i] ; var extn_marker = "" ; @@ -301,7 +307,7 @@ function make_snippet( $btn, params, extra_params, show_date_warnings ) template_id = "ob_" + vo_type + "_ma_notes" ; var vo_type_uc = vo_type.toUpperCase() ; var postfixes = [ "MA_NOTES", "MA_NOTES_WIDTH", "EXTRA_MA_NOTES", "EXTRA_MA_NOTES_CAPTION" ] ; - for ( var i=0 ; i < postfixes.length ; ++i ) { + for ( i=0 ; i < postfixes.length ; ++i ) { params[ "OB_" + postfixes[i] ] = params[ "OB_" + vo_type_uc + "_" + postfixes[i] + "_" + player_no ] ; } snippet_save_name = params["PLAYER_"+player_no] + (vo_type === "vehicles" ? " vehicle notes" : " ordnance notes") ; @@ -371,6 +377,9 @@ function make_snippet( $btn, params, extra_params, show_date_warnings ) params.BAZ_RANGE = 4 ; } + // set the national capabilities parameters + set_nat_caps_params( player_nat, params ) ; + // check for mandatory parameters if ( template_id in _MANDATORY_PARAMS ) { var missing_params = [] ; @@ -432,7 +441,7 @@ function make_snippet( $btn, params, extra_params, show_date_warnings ) snippet = func( params, { autoEscape: false, filters: { - join: function( vals, sep ) { return vals.join( sep ) ; }, + join: function( vals, sep ) { return vals ? vals.join(sep) : "" ; }, nbsp: function( val ) { return strReplaceAll( val, " ", " " ) ; }, } , } ) ; @@ -484,20 +493,14 @@ function adjust_vo_comments( params ) } // allow comment EXC's to be styled - var excRegex = new RegExp( /\[EXC: .*?\]/ ) ; + var excRegex = new RegExp( /\[EXC: .*?\]/g ) ; function adjustExc( val ) { - var match = val.match( excRegex ) ; - if ( match ) { - val = val.substring( 0, match.index ) + - "" + match[0] + "" + - val.substring( match.index + match[0].length ) ; - } - return val ; + return wrapSubstrings( val, excRegex, "", "" ) ; } // adjust comments if ( params.OB_VO ) { - for ( var i=0 ; i < params.OB_VO.length ; ++i ) { + for ( i=0 ; i < params.OB_VO.length ; ++i ) { if ( ! params.OB_VO[i].comments ) continue ; for ( var j=0 ; j < params.OB_VO[i].comments.length ; ++j ) { @@ -935,40 +938,10 @@ function get_vo_comments( vo_entry, month, year ) if ( ! vo_entry.comments ) return vo_entry.comments ; - function parseDate( val ) { - if ( ! val ) - return null ; - var match = val.trim().match( /^(\d\d)\/(19\d\d)$/ ) ; - if ( ! match ) - return null ; - return [ match[1], match[2] ] ; - } - // generate the vehicle/ordnance's comments var voComments=[], cmt, i ; for ( i=0 ; i < vo_entry.comments.length ; ++i ) { - cmt = vo_entry.comments[i] ; - if ( cmt.substr(0,2) === "{?" && cmt.substr(cmt.length-2) === "?}" ) { - // this is a time-based comment, check the scenario date - var words = cmt.substring( 2, cmt.length-2 ).split( "|" ) ; - var dates = words[0].split( "-" ) ; - dates = [ parseDate(dates[0]), parseDate(dates[1]) ] ; - if ( words.length != 4 || dates.length != 2 || (!dates[0] && !dates[1]) ) { - showErrorMsg( "Invalid time-based vehicle/ordnance comment: " + cmt ) ; - continue ; - } - if ( !month || !year ) - cmt = words[3] ; - else { - var rc = true ; - if ( dates[0] && ( year < dates[0][1] || ( year == dates[0][1] && month < dates[0][0] ) ) ) - rc = false ; - if ( dates[1] && ( year > dates[1][1] || ( year == dates[1][1] && month > dates[1][0] ) ) ) - rc = false ; - cmt = rc ? words[1] : words[2] ; - } - } - cmt = cmt.trim() ; + cmt = make_time_based_comment( vo_entry.comments[i], month, year ) ; if ( cmt ) voComments.push( cmt ) ; } @@ -996,6 +969,108 @@ function get_vo_comments( vo_entry, month, year ) return voComments ; } +function make_time_based_comment( val, month, year ) +{ + function parseDateControl( val ) { + // parse a date control string + var dates = val.split( "-" ) ; + if ( dates.length != 2 ) + return null ; + for ( var i=0 ; i < 2 ; ++i ) { + var date = dates[i].trim() ; + if ( date !== "" ) { + var match = date.match( /^(\d\d)\/(19\d\d)$/ ) ; + if ( ! match ) + return null ; + dates[i] = [ match[1], match[2] ] ; + } else { + dates[i] = null ; + } + } + return dates ; + } + function checkDateControl( dateControl ) { + // check if the date passed in falls within the date control + if ( dateControl[0] && ( year < dateControl[0][1] || ( year == dateControl[0][1] && month < dateControl[0][0] ) ) ) + return false ; + if ( dateControl[1] && ( year > dateControl[1][1] || ( year == dateControl[1][1] && month > dateControl[1][0] ) ) ) + return false ; + return true ; + } + + // process any time-based values + var words, dateControl ; + for ( ; ; ) { + + // check for a time-based substitution + var parts = findDelimitedSubstring( val, "{?", "?}" ) ; + if ( $.isArray( parts ) ) { + // found one - this form has the following syntax: + // {? DATE CONTROL | within the date control | outside the date control | fallback text ?} + // parse the date control + words = parts[1].split( "|" ) ; + dateControl = parseDateControl( words[0] ) ; + if ( words.length != 4 || dateControl === null ) { + showErrorMsg( "Invalid time-based comment: " + val ) ; + return null ; + } + // figure out which value to use + if ( month && year ) + val = parts[0] + words[ checkDateControl(dateControl) ? 1 : 2 ].trim() + parts[2] ; + else + val = parts[0] + words[3].trim() + parts[2] ; + continue ; + } + + // check for a time-based substitution + parts = findDelimitedSubstring( val, "{!", "!}" ) ; + if ( $.isArray( parts ) ) { + // found one - this form has the following syntax: + // {! DATE CONTROL = text | DATE CONTROL = text | etc... | fallback text !} + var fallbackText = "" ; + choices = parts[1].split( "|" ) ; + for ( var i=0 ; i < choices.length ; ++i ) { + // parse the next choice + var pos = choices[i].indexOf( "=" ) ; + if ( pos !== -1 ) { + dateControl = parseDateControl( choices[i].substring( 0, pos ) ) ; + if ( dateControl !== null ) { + // the choice is valid - save it, and its substitution text + choices[i] = [ dateControl, choices[i].substring(pos+1).trim() ] ; + continue ; + } + } + // the choice is invalid + if ( i === choices.length-1 ) { + // this is the last choice - use it as the fallback text + fallbackText = choices.pop().trim() ; + break ; + } else { + showErrorMsg( "Invalid time-based comment: " + choices[i] ) ; + return null ; + } + } + // check each choice to try find a match + var replaceText = fallbackText ; + if ( month && year ) { + for ( i=0 ; i < choices.length ; ++i ) { + if ( checkDateControl( choices[i][0] ) ) { + // found a match - replace the content with the substitution text + replaceText = choices[i][1] ; + break ; + } + } + } + val = parts[0] + replaceText + parts[2] ; + } + + // NOTE: If we get here, there are no more time-based substitutions to be made. + break ; + } + + return val ; +} + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - function make_capabilities( raw, vo_entry, vo_type, nat, elite, scenario_theater, scenario_year, scenario_month, show_warnings ) @@ -1937,6 +2012,7 @@ function do_load_template_pack( fname, data ) var unknown_template_ids = [] ; var template_pack = { nationalities: $.extend( true, {}, gDefaultTemplatePack.nationalities ), + "national-capabilities": $.extend( true, {}, gDefaultTemplatePack["national-capabilities"] ), templates: {}, css: {}, includes: {}, @@ -1963,6 +2039,17 @@ function do_load_template_pack( fname, data ) $.extend( true, template_pack.nationalities, nationalities ) ; return ; } + if ( fname.toLowerCase() === "national-capabilities.json" ) { + var nat_caps = null ; + try { + nat_caps = JSON.parse( data ) ; + } catch( ex ) { + showWarningMsg( "Can't parse the nationalities JSON data:
" + escapeHTML(ex) + "
" ) ; + return ; + } + $.extend( true, template_pack["national-capabilities"], nat_caps ) ; + return ; + } var extn = getFilenameExtn( fname ) ; if ( [".j2",".css",".include"].indexOf( extn ) === -1 ) { invalid_filename_extns.push( fname ) ; diff --git a/vasl_templates/webapp/static/sortable.js b/vasl_templates/webapp/static/sortable.js index 2d3b52f..815f713 100644 --- a/vasl_templates/webapp/static/sortable.js +++ b/vasl_templates/webapp/static/sortable.js @@ -77,7 +77,7 @@ $.fn.sortable2 = function( action, args ) $sortable2.data( "no_confirm_delete", args.no_confirm_delete ) ; $sortable2.data( "on_edit", args.edit ) ; var $add_btn = find_helper( $sortable2, "add" ) ; - $add_btn.prepend( $( "
Add
" ) ) + $add_btn.prepend( $( "
Add
" ) ) .button( {} ) ; var $add = find_helper( $sortable2, "add" ) ; $add.prop( "title", "Add a new " + display_name[0] ) @@ -85,7 +85,7 @@ $.fn.sortable2 = function( action, args ) if ( args.reset ) { $sortable2.data( "on_reset", args.reset ) ; var $reset_btn = find_helper( $sortable2, "reset" ) ; - $reset_btn.prepend( $( "
Reset
" ) ) + $reset_btn.prepend( $( "
Reset
" ) ) .button( {} ) ; var $reset = find_helper( $sortable2, "reset" ) ; $reset.prop( "title", "Reset the " + display_name[1] ) diff --git a/vasl_templates/webapp/static/user_settings.js b/vasl_templates/webapp/static/user_settings.js index 4a72225..275c15e 100644 --- a/vasl_templates/webapp/static/user_settings.js +++ b/vasl_templates/webapp/static/user_settings.js @@ -9,6 +9,7 @@ USER_SETTINGS = { "date-format": "droplist", "scenario-images-source": "droplist", "hide-unavailable-ma-notes": "checkbox", + "auto-create-national-capabilities-labels": "checkbox", "include-vasl-images-in-snippets": "checkbox", "include-flags-in-snippets": "checkbox", "custom-list-bullets": "checkbox", @@ -76,7 +77,7 @@ function user_settings() dialogClass: "user-settings", modal: true, width: 460, - height: 320, + height: 340, resizable: false, create: function() { init_dialog( $(this), "OK", true ) ; diff --git a/vasl_templates/webapp/static/utils.js b/vasl_templates/webapp/static/utils.js index 72fed09..5e2f4d6 100644 --- a/vasl_templates/webapp/static/utils.js +++ b/vasl_templates/webapp/static/utils.js @@ -4,6 +4,8 @@ function get_nationality_display_name( nat_id ) { // get the nationality's display name + if ( ! gTemplatePack.nationalities[ nat_id ] ) + return null ; return gTemplatePack.nationalities[ nat_id ].display_name ; } @@ -85,6 +87,8 @@ function is_template_available( template_id ) // check if the specified template is available if ( template_id.match( /^ob_(vehicles|ordnance).*_[12]$/ ) ) template_id = template_id.substring( 0, template_id.length-2 ) ; + else if ( template_id === "nat_caps_1" || template_id === "nat_caps_2" ) + template_id = "nat_caps" ; return gTemplatePack.templates[ template_id ] !== undefined ; } @@ -426,6 +430,42 @@ function strReplaceAll( val, searchFor, replaceWith ) } } +function findDelimitedSubstring( val, delim1, delim2 ) +{ + // search for a substring delimited by the 2 specified markers + if ( val === null || val === undefined ) + return null ; + var pos = val.indexOf( delim1 ) ; + if ( pos === -1 ) + return val ; + var pos2 = val.indexOf( delim2, pos ) ; + if ( pos2 === -1 ) + return val ; + // found it - return the prefix/middle/postfix parts + return [ + val.substring( 0, pos ), + val.substring( pos+delim1.length, pos2 ), + val.substring( pos2+delim2.length ) + ] ; +} + +function wrapSubstrings( val, searchFor, delim1, delim2 ) +{ + // search for a substring and wrap it with the specified delimeters + if ( val === null || val === undefined ) + return null ; + // FUDGE! matchAll() isn't available in the PyQt embedded browser :-/ + var matches = [] ; + while ( ( match = searchFor.exec( val ) ) !== null ) + matches.push( match ) ; + for ( var i=matches.length-1 ; i >= 0 ; --i ) { + val = val.substring( 0, matches[i].index ) + + delim1 + matches[i][0] + delim2 + + val.substring( matches[i].index + matches[i][0].length ) ; + } + return val ; +} + function getFilenameExtn( fname ) { // get the filename extension diff --git a/vasl_templates/webapp/static/vassal.js b/vasl_templates/webapp/static/vassal.js index 9d4fc82..4d62962 100644 --- a/vasl_templates/webapp/static/vassal.js +++ b/vasl_templates/webapp/static/vassal.js @@ -100,6 +100,10 @@ function _generate_snippets() } no_autocreate[template_id] = true ; } + if ( ! gUserSettings["auto-create-national-capabilities-labels"] ) { + no_autocreate.nat_caps_1 = true ; + no_autocreate.nat_caps_2 = true ; + } function on_snippet_button( $btn, inactive ) { var template_id = $btn.attr( "data-id" ) ; @@ -114,7 +118,11 @@ function _generate_snippets() var params = unload_snippet_params( true, template_id ) ; var snippet_id = template_id ; var extra_params = {} ; - var player_no = get_player_no_for_element( $btn ) ; + var player_no ; + if ( snippet_id.substring( 0, 9 ) === "nat_caps_" ) + player_no = snippet_id.substring( 9 ) ; + else + player_no = get_player_no_for_element( $btn ) ; var data ; if ( ["scenario_note","ob_setup","ob_note"].indexOf( template_id ) !== -1 ) { data = $btn.parent().parent().data( "sortable2-data" ) ; @@ -225,6 +233,8 @@ function _get_raw_content( snippet_id, $btn, params ) return [ "Bazooka", "Range", "TH#" ] ; if ( snippet_id === "thh" ) return [ "Tank-Hunter Heroes", "Banzai Charge" ] ; + if ( snippet_id.substring( 0, 9 ) === "nat_caps_" ) + return [ "Capabilities" ] ; // handle vehicle/ordnance notes // NOTE: These were implemented after we added snippet ID's, so there's no need to support legacy labels. diff --git a/vasl_templates/webapp/templates/index.html b/vasl_templates/webapp/templates/index.html index 3f434f4..06a36dc 100644 --- a/vasl_templates/webapp/templates/index.html +++ b/vasl_templates/webapp/templates/index.html @@ -110,6 +110,7 @@ gHelpUrl = "{{url_for('show_help')}}" ; + diff --git a/vasl_templates/webapp/templates/national-capabilities.html b/vasl_templates/webapp/templates/national-capabilities.html new file mode 100644 index 0000000..58df26c --- /dev/null +++ b/vasl_templates/webapp/templates/national-capabilities.html @@ -0,0 +1,70 @@ + + + + + + National Capabilities: {{NATIONALITY}} + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vasl_templates/webapp/templates/tabs-scenario.html b/vasl_templates/webapp/templates/tabs-scenario.html index a294121..1146d6d 100644 --- a/vasl_templates/webapp/templates/tabs-scenario.html +++ b/vasl_templates/webapp/templates/tabs-scenario.html @@ -41,16 +41,19 @@ +
+   - - - +
+
+ +
- Hide unavailable multi-applicable notes
+ Auto-create National Capabilities labels
+
+ Hide unavailable multi-applicable notes
+