Create attractive VASL scenarios, with loads of useful information embedded to assist with game play.
https://vasl-templates.org
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
160 lines
5.7 KiB
160 lines
5.7 KiB
""" Webapp handlers. """
|
|
|
|
import os
|
|
import time
|
|
import base64
|
|
import logging
|
|
import xml.etree.ElementTree as ET
|
|
|
|
from flask import request, jsonify
|
|
|
|
from vasl_templates.webapp import app
|
|
from vasl_templates.webapp.vassal import VassalShim
|
|
from vasl_templates.webapp.utils import SimpleError, TempFile
|
|
|
|
# weights for each possible roll value
|
|
DEFAULT_LFA_DICE_HOTNESS_WEIGHTS = {
|
|
"DR": { 2: 20, 3: 16, 4: 12, 5: 8, 6: 4, 7: 0, 8: -4, 9: -8, 10: -12, 11: -16, 12: -20 },
|
|
"dr": { 1: 3, 2: 2, 3: 1, 4: -1, 5: -2, 6: -3 }
|
|
}
|
|
|
|
# minimum number of rolls for dice hotness to be considered reasonable
|
|
DEFAULT_LFA_DICE_HOTNESS_THRESHOLDS = {
|
|
"DR": 100, "dr": 50
|
|
}
|
|
|
|
# ---------------------------------------------------------------------
|
|
|
|
@app.route( "/analyze-vlogs", methods=["POST"] )
|
|
def analyze_vlogs(): #pylint: disable=too-many-locals
|
|
"""Analyze VASL log file(s)."""
|
|
|
|
# parse the request
|
|
start_time = time.time()
|
|
vlog_data = request.json
|
|
|
|
# initialize
|
|
logger = logging.getLogger( "analyze_vlogs" )
|
|
temp_files = []
|
|
|
|
try:
|
|
|
|
# save each VLOG file in a temp file
|
|
if not vlog_data:
|
|
raise SimpleError( "No log files were submitted." )
|
|
for vlog_no, vlog in enumerate( vlog_data ):
|
|
fname, data = vlog
|
|
data = base64.b64decode( data )
|
|
logger.info( "Analyzing VLOG (#bytes=%d): %s", len(data), fname )
|
|
temp_file = TempFile()
|
|
temp_file.open()
|
|
temp_file.write( data )
|
|
temp_file.close( delete=False )
|
|
save_fname = app.config.get( "ANALYZE_VLOG_INPUT" )
|
|
if save_fname:
|
|
if len(vlog_data) == 1:
|
|
temp_file.save_copy( save_fname, logger, "VLOG data" )
|
|
else:
|
|
parts = os.path.splitext( save_fname )
|
|
temp_file.save_copy( "{}-{}".format(parts[0],1+vlog_no) + parts[1], logger, "VLOG data" )
|
|
temp_files.append( temp_file )
|
|
|
|
# run the VASSAL shim to analyze the VLOG file(s)
|
|
with TempFile() as report_file:
|
|
report_file.close( delete=False )
|
|
vassal_shim = VassalShim()
|
|
fnames = [ tf.name for tf in temp_files ]
|
|
fnames.append( report_file.name )
|
|
vassal_shim.analyze_logfiles( *fnames )
|
|
report_file.save_copy( app.config.get("ANALYZE_VLOG_REPORT"), logger, "analysis report" )
|
|
report = parse_analysis_report( report_file.name, logger )
|
|
|
|
except Exception as ex: #pylint: disable=broad-except
|
|
|
|
return VassalShim.translate_vassal_shim_exception( ex, logger )
|
|
|
|
finally:
|
|
|
|
# clean up
|
|
for tf in temp_files:
|
|
tf.close( delete=True )
|
|
|
|
# insert the filenames for each log file, as they were passed in to us
|
|
for vlog_no,vlog in enumerate( vlog_data ):
|
|
report["logFiles"][ vlog_no ]["filename"] = vlog[0]
|
|
|
|
# return the results
|
|
logger.info( "Analyzed the VLOG file(s) OK: elapsed=%.3fs", time.time()-start_time )
|
|
return jsonify( report )
|
|
|
|
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
def parse_analysis_report( fname, logger=None ):
|
|
"""Parse the analysis report generated by the VASSAL shim."""
|
|
|
|
# initialize
|
|
doc = ET.parse( fname )
|
|
|
|
# get the complete list of players across all the log files
|
|
players = {}
|
|
for elem in doc.findall( ".//diceEvent" ):
|
|
player_name = elem.attrib[ "player" ]
|
|
if player_name not in players:
|
|
# NOTE: ChartJS (in the frontend Javascript) identifies datasets using a 0-based index,
|
|
# so to avoid accidentally mixing these up with player ID's, we generate non-numeric player ID's.
|
|
player_id = "p:{}".format( len(players) + 1 )
|
|
players[ player_name ] = player_id
|
|
|
|
# generate the results for each log file
|
|
log_files = []
|
|
for logFileElem in doc.findall( ".//logFile" ):
|
|
|
|
# process the events for the next log file
|
|
events, scenario = [], {}
|
|
for elem in logFileElem.find( ".//events" ):
|
|
|
|
if elem.tag == "diceEvent":
|
|
# found a DICE ROLL event
|
|
player_id = players[ elem.attrib["player"] ]
|
|
values = [ int(v) for v in elem.text.split(",") ]
|
|
events.append( {
|
|
"eventType": "roll",
|
|
"playerId": player_id,
|
|
"rollType": elem.attrib[ "rollType" ],
|
|
"rollValue": values[0] if len(values) == 1 else values
|
|
} )
|
|
elif elem.tag == "turnTrackEvent":
|
|
# found a TURN TRACK event
|
|
events.append( {
|
|
"eventType": "turnTrack",
|
|
"side": elem.attrib[ "side" ],
|
|
"turnNo": elem.attrib[ "turnNo" ],
|
|
"phase": elem.attrib[ "phase" ]
|
|
} )
|
|
elif elem.tag == "customLabelEvent":
|
|
# found a CUSTOM label
|
|
events.append( {
|
|
"eventType": "customLabel",
|
|
"caption": elem.text
|
|
} )
|
|
else:
|
|
if logger:
|
|
logger.warn( "Found an unknown analysis event: %s", elem.tag )
|
|
|
|
# extract the scenario details
|
|
elem = logFileElem.find( ".//scenario" )
|
|
if elem is not None:
|
|
scenario[ "scenarioName" ] = elem.text
|
|
if "id" in elem.attrib:
|
|
scenario[ "scenarioId" ] = elem.attrib["id"]
|
|
|
|
log_files.append( {
|
|
"filename": logFileElem.attrib[ "filename" ],
|
|
"scenario": scenario,
|
|
"events": events,
|
|
} )
|
|
|
|
return {
|
|
"players": { v: k for k,v in players.items() },
|
|
"logFiles": log_files
|
|
}
|
|
|