1025 lines
33 KiB
Ruby
Executable File
1025 lines
33 KiB
Ruby
Executable File
#
|
|
|
|
#
|
|
# Author:: Mark Harrison-Ball <Mark.Harrison-Ball@rockstargames.com>
|
|
# Date:: 20 Februray 2013 (AP3)
|
|
# Purpose:
|
|
# Will read through the tracking docs and extract approved data and bugnumbers
|
|
# WIll then updat ethe meta data file used by the game to display if something has been approved
|
|
# Will also generate a email report sent to producrion
|
|
# Is deiged to run as a automated task checking perforce for checked in file
|
|
#
|
|
|
|
|
|
#-----------------------------------------------------------------------------
|
|
# Uses
|
|
#-----------------------------------------------------------------------------
|
|
#include RSG::Base::Logging
|
|
#include RSG::Base::Logging::Universal
|
|
|
|
require 'RSG.Base.dll'
|
|
require 'RSG.Base.Configuration.dll'
|
|
require 'RSG.Base.Windows.dll'
|
|
require 'RSG.SourceControl.Perforce'
|
|
require 'RSG.Metadata'
|
|
|
|
# Core
|
|
include RSG::Base::Configuration
|
|
include RSG::Base::Logging
|
|
include RSG::Base::Logging::Universal
|
|
include RSG::Base::OS
|
|
include RSG::SourceControl::Perforce
|
|
include RSG::Metadata
|
|
|
|
|
|
require 'mscorlib'
|
|
require 'System.Core'
|
|
require 'System.Xml'
|
|
require 'win32ole'
|
|
|
|
include System::Collections::Generic
|
|
include System::IO
|
|
include System::Diagnostics
|
|
include System::Xml
|
|
include System::Runtime::InteropServices
|
|
include System::Net
|
|
include System::Net::Sockets
|
|
include System::Net::Mail
|
|
|
|
|
|
|
|
|
|
|
|
|
|
require 'pipeline/os/options'
|
|
#require 'pipeline/util/email'
|
|
include Pipeline
|
|
|
|
DEBUG = false
|
|
#-----------------------------------------------------------------------------
|
|
# Constants
|
|
#-----------------------------------------------------------------------------
|
|
|
|
OPTIONS = [
|
|
LongOption::new( 'frequency', LongOption::ArgType.Required, 'f',
|
|
'frequency to check perforce for submits(required).' )
|
|
]
|
|
|
|
|
|
#EMAIL = 'mark.harrison-ball@rockstargames.com'
|
|
EMAIL = 'AutoApprovedCutsceneUpdater@rockstargames.com'
|
|
AUTHOR = ['mark.harrison-ball@rockstargames.com']
|
|
#SERVER = 'rsgldnexg1.rockstar.t2.corp'
|
|
@server = 'rsgldnexg1.rockstar.t2.corp'
|
|
|
|
|
|
|
|
#SMTPSERVER.Port = 25
|
|
|
|
PRODUCTION_EMAIL = ['dermot.bailie@rockstarnorth.com',
|
|
'tina.chen@rockstargames.com',
|
|
'sam.henman@rockstargames.com',
|
|
'francesca.howard@rockstarnorth.com',
|
|
'eric.smith@rockstarsandiego.com',
|
|
'sean.letts@rockstarsandiego.com',
|
|
'mark.harrison-ball@rockstargames.com']
|
|
|
|
=begin
|
|
PRODUCTION_EMAIL = ['mark.harrison-ball@rockstargames.com',
|
|
'mark.harrison-ball@rockstargames.com']
|
|
|
|
=end
|
|
|
|
DOF_COUNT = 0
|
|
LIGHTING_COUNT = 0
|
|
FACIAL_COUNT = 0
|
|
ANIMATION_COUNT = 0
|
|
CAMERA_COUNT = 0
|
|
# Total entries
|
|
CUTLIST_COUNT = 0
|
|
FACIALLIST_COUNT = 0
|
|
|
|
#-----------------------------------------------------------------------------
|
|
# Functions
|
|
#-----------------------------------------------------------------------------
|
|
|
|
|
|
#CUTSCENEMETA = "X:/gta5/build/dev/common/non_final/cutscene/ApprovedCutsceneList.meta"
|
|
|
|
|
|
=begin
|
|
Perfore functions
|
|
=end
|
|
|
|
#
|
|
#--------------------------------------------------------
|
|
#def send_email( server, port, subject, body, from, to, reply_to = '',
|
|
# from_alias = '', to_alias = '', reply_to_alias = '' )
|
|
def send_email(from, to_list, subject, message)
|
|
|
|
|
|
#port= 25
|
|
#server= 'rsgnycexg04.rockstar.t2.corp'
|
|
|
|
#subject='test'
|
|
#body= 'test'
|
|
#from= 'mark.harrison-ball@rockstargames.com'
|
|
#to= 'matt.tempest@rockstarlondon.com'
|
|
#to = 'mark.harrison-ball@rockstargames.com'
|
|
|
|
|
|
begin
|
|
Mail = System::Net::Mail::MailMessage.new
|
|
Smptserver = System::Net::Mail::SmtpClient.new(@server)
|
|
Smptserver.Port = 25
|
|
MailAddress = System::Net::Mail::MailAddress.new(from)
|
|
Mail.From = MailAddress
|
|
to_list.each do |to|
|
|
Mail.To.Add(to)
|
|
end
|
|
Mail.IsBodyHtml = true
|
|
Mail.Subject = subject
|
|
|
|
Mail.Body = message
|
|
|
|
Smptserver.Send(Mail);
|
|
|
|
rescue => msg
|
|
puts(msg)
|
|
end
|
|
|
|
end
|
|
|
|
#---------------------------------------------------------------------------------------------------------------
|
|
# REPORT GENERTATION
|
|
#---------------------------------------------------------------------------------------------------------------
|
|
|
|
# Convert bool to symbol
|
|
#---------------------------------------------------
|
|
def returnHtmlBoolSymbol(bFlag)
|
|
htmlString = ""
|
|
if bFlag.class == String.class
|
|
htmlString = "<span>#{bFlag}</span>"
|
|
elsif bFlag.class == true.class
|
|
htmlString = "<span style=\"font-family: wingdings; color=\"#556B2F\"; font-size: 300%;\">ü</span>"
|
|
elsif bFlag.class == false.class
|
|
htmlString = "<span style=\"font-family: wingdings; font-size: 140%; color=\"#FFE4C4\" \">û</span>"
|
|
|
|
end
|
|
htmlString
|
|
end
|
|
|
|
def returnHtmlUrlLink(iValue)
|
|
htmlString = "-"
|
|
if not iValue == "-"
|
|
bNumber = iValue.to_i
|
|
htmlString = "<a href=\"url:bugstar:#{bNumber}\">#{bNumber}</a>"
|
|
end
|
|
htmlString
|
|
end
|
|
|
|
# Create HTML HEader
|
|
#----------------------------------------------
|
|
def HtmlHeader()
|
|
@msgBody << "<div><span style='font-size:20;font-family:\"Arial\",\"sans-serif\";color:windowtext'><b>Cutscene Tracking Changes > ApprovedCutsceneList.meta</b></span></div>"
|
|
|
|
@msgBody << "<div><span style='font-size:9.0pt;line-height:115%;font-family:\"Arial\",\"sans-serif\"'>Date #{Time.now}<o:p></o:p></span></div><br/><br/>"
|
|
|
|
@msgBody << "<table width=\"800\" border=1 cellspacing=\"0\">
|
|
<tr><td style= \"align=\"left\"; colspan=3; bgcolor=\"#B22222\"><font size=\"2\" face=\"verdana\" color=\"white\"><p><b>Report generated from</b></p></td></tr>
|
|
<tr><td style= \"align=\"left\"; bgcolor=\"#FFE4C4\"><span><font size=\"1\" face=\"verdana\">//depot/gta5/docs/production/GTAV_CAMERA_TRACKING.xlsm</span></td></tr>
|
|
<tr><td style= \"align=\"left\"; bgcolor=\"#FFE4C4\"><span><font size=\"1\" face=\"verdana\">//depot/gta5/docs/production/Animation/V_Facial_Animation_Tracking.xlsm</span></td></tr>
|
|
<tr><td style= \"align=\"left\"; bgcolor=\"#FFE4C4\"><span><font size=\"2\" color=\"blue\" face=\"verdana\"><i>changes CL#:#{@lastCL} to #{@currentCL}</i></span></td></tr>
|
|
</table><br/>"
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def HtmlSummary()
|
|
width = (800/100.0)
|
|
|
|
|
|
|
|
|
|
@msgBody << "<div><span style='font-size:20;font-family:\"Arial\",\"sans-serif\";color:windowtext'><b>Cutscene Overall Summary</b></span></div>"
|
|
|
|
# Camera Approved Percentage
|
|
cam_width = ((100.0/CUTLIST_COUNT)*CAMERA_COUNT) * width
|
|
@msgBody << "<table width=\"800\" border=0 cellspacing=\"0\"><tr>
|
|
<tr><td width=\"#{cam_width}\"; style= \"align=\"left\"; bgcolor=\"#007FFF\"><span><font size=\"1\" face=\"verdana\">Camera Approved</span></td>
|
|
<td width=\"#{800-cam_width}\"; style=\"align=\"left\"; bgcolor=\"#EAEAAE\"><font size=\"2\" face=\"verdana\">#{((100.0/CUTLIST_COUNT)*CAMERA_COUNT).round}%</td>
|
|
</tr></table>"
|
|
# DOF Approved Percentage
|
|
dof_width = ((100.0/CUTLIST_COUNT)*DOF_COUNT) * width
|
|
@msgBody << "<table width=\"800\" border=0 cellspacing=\"0\"><tr>
|
|
<tr><td width=\"#{dof_width}\"; style= \"align=\"left\"; bgcolor=\"#FF7F00\"><span><font size=\"1\" face=\"verdana\">DOF Approved</span></td>
|
|
<td width=\"#{800-dof_width}\"; style=\"align=\"left\"; bgcolor=\"#EAEAAE\"><font size=\"2\" face=\"verdana\">#{((100.0/CUTLIST_COUNT)*DOF_COUNT).round}%</td>
|
|
</tr></table>"
|
|
# Lighting Approved Percentage
|
|
lig_width = ((100.0/CUTLIST_COUNT)*LIGHTING_COUNT) * width
|
|
@msgBody << "<table width=\"800\" border=0 cellspacing=\"0\"><tr>
|
|
<tr><td width=\"#{lig_width}\"; style= \"align=\"left\"; bgcolor=\"#5F9F9F\"><span><font size=\"1\" face=\"verdana\">Lighting Approved (2nd pass)</span></td>
|
|
<td width=\"#{800-lig_width}\"; style=\"align=\"left\"; bgcolor=\"#EAEAAE\"><font size=\"2\" face=\"verdana\">#{((100.0/CUTLIST_COUNT)*LIGHTING_COUNT).round}%</td>
|
|
</tr></table>"
|
|
# Animation Approved Percentage
|
|
anm_width = ((100.0/CUTLIST_COUNT)*ANIMATION_COUNT) * width
|
|
@msgBody << "<table width=\"800\" border=0 cellspacing=\"0\"><tr>
|
|
<tr><td width=\"#{anm_width}\"; style= \"align=\"left\"; bgcolor=\"#6B8E23\"><span><font size=\"1\" face=\"verdana\">Animation Approved</span></td>
|
|
<td width=\"#{800-anm_width}\"; style=\"align=\"left\"; bgcolor=\"#EAEAAE\"><font size=\"2\" face=\"verdana\">#{((100.0/CUTLIST_COUNT)*ANIMATION_COUNT).round}%</td>
|
|
</tr></table>"
|
|
# Facial Approved Percentage
|
|
fce_width = ((100.0/FACIALLIST_COUNT)*FACIAL_COUNT) * width
|
|
@msgBody << "<table width=\"800\" border=0 cellspacing=\"0\"><tr>
|
|
<tr><td width=\"#{fce_width}\"; style= \"align=\"left\"; bgcolor=\"#A68064\"><span><font size=\"1\" face=\"verdana\">Facial Approved</span></td>
|
|
<td width=\"#{800-fce_width}\"; style=\"align=\"left\"; bgcolor=\"#EAEAAE\"><font size=\"2\" face=\"verdana\">#{((100.0/FACIALLIST_COUNT)*FACIAL_COUNT).round}%</td>
|
|
</tr>
|
|
</table><br/>"
|
|
|
|
|
|
|
|
|
|
|
|
end
|
|
|
|
# Create Table Info
|
|
#----------------------------------------------
|
|
def generateBaseTable(statusType,headerName, color)
|
|
|
|
|
|
@msgBody << "<table width=\"800\" cellspacing=\"0\">
|
|
<tr><td style= \"align=\"left\"; bgcolor=\"#{color}\"><font size=\"2\" face=\"verdana\" color=\"white\"><p><b>#{headerName}</b></p></td>
|
|
<td style= \"align=\"left\"; bgcolor=\"#{color}\"> </td>
|
|
<td style= \"align=\"left\"; bgcolor=\"#{color}\"> </td>
|
|
<td style= \"align=\"left\"; bgcolor=\"#{color}\"> </td>
|
|
</tr>
|
|
<tr>
|
|
<td width=\"250\"; height=\"20\" style= \"align=\"left\"; bgcolor=\"#FFE4C4\"><span><font size=\"1\" face=\"verdana\"><b>Cutscene Name</span></td>
|
|
<td width=\"300\"; style= \"align=\"left\"; bgcolor=\"#FFE4C4\"><span><font size=\"1\" face=\"verdana\"><b>""</span></td>
|
|
<td width=\"100\"; style= \"align=\"left\"; bgcolor=\"#FFE4C4\"><span><font size=\"1\" face=\"verdana\" ><b>Status</span></td>
|
|
<td width=\"150\"; style= \"align=\"left\"; bgcolor=\"#FFE4C4\"><font size=\"1\" face=\"verdana\"><b>Bug Number</td>
|
|
</tr>
|
|
"
|
|
|
|
@emailLog.each do |key, value |
|
|
# loop through scene Name attributes
|
|
scenename = key
|
|
hasMultipleStatus = false
|
|
# We need to find out how many values are set to true, if more than 1 then we sett key and loop else
|
|
index = 0
|
|
value.each do |attr |
|
|
if attr.has_value?(statusType)
|
|
index+=1
|
|
end
|
|
end
|
|
|
|
# we have multiple approved values set the same
|
|
if index > 1
|
|
hasMultipleStatus = true
|
|
end
|
|
|
|
# We loop each list of attr
|
|
value.each do |attr |
|
|
|
|
if attr.has_value?(statusType)
|
|
approvename = attr[:ApprovedName]
|
|
status = attr[:Status]
|
|
bugnumbers = attr[:Bug]
|
|
|
|
|
|
@msgBody << "<tr>
|
|
<td style=\"align=\"left\"; bgcolor=\"#EAEAAE\"><font size=\"2\" face=\"verdana\"><b>#{scenename}</b></td>
|
|
<td style=\"align=\"left\"; bgcolor=\"#EAEAAE\"><font size=\"2\" face=\"verdana\"><i>#{approvename}</i></td>
|
|
<td style=\"align=\"left\"; bgcolor=\"#EAEAAE\"><font size=\"2\" face=\"verdana\">#{returnHtmlBoolSymbol(status)}</td>
|
|
<td style=\"align=\"left\"; bgcolor=\"#EAEAAE\"><font size=\"2\" face=\"verdana\">#{returnHtmlUrlLink(bugnumbers[0])}</td>
|
|
</tr>"
|
|
|
|
if bugnumbers.size > 1
|
|
(1..bugnumbers.size-1).each do |n|
|
|
@msgBody << "<tr>
|
|
<td width=\"250\"; style=\"align=\"left\"; bgcolor=\"#EAEAAE\"><font size=\"2\" face=\"verdana\"></td>
|
|
<td width=\"300\"; style=\"align=\"left\"; bgcolor=\"#EAEAAE\"><font size=\"2\" face=\"verdana\"></td>
|
|
<td width=\"100\"; style=\"align=\"left\"; bgcolor=\"#EAEAAE\"><font size=\"2\" face=\"verdana\"></td>
|
|
<td width=\"150\"; style=\"align=\"left\"; bgcolor=\"#EAEAAE\"><font size=\"2\" face=\"verdana\">#{returnHtmlUrlLink(bugnumbers[n])}</td>
|
|
</tr>"
|
|
|
|
end
|
|
|
|
end
|
|
|
|
if hasMultipleStatus == true
|
|
scenename = ""
|
|
hasMultipleStatus == false
|
|
end
|
|
|
|
end
|
|
# SPACER
|
|
|
|
end
|
|
if index > 0
|
|
@msgBody << "<tr>
|
|
<td height=\"6\"; style=\"align=\"left\"; bgcolor=\"#EAEAAE\"><font size=\"2\" face=\"verdana\"></td>
|
|
<td height=\"6\"; style=\"align=\"left\"; bgcolor=\"#EAEAAE\"><font size=\"2\" face=\"verdana\"></td>
|
|
<td height=\"6\"; style=\"align=\"left\"; bgcolor=\"#EAEAAE\"><font size=\"2\" face=\"verdana\"></td>
|
|
<td height=\"6\"; style=\"align=\"left\"; bgcolor=\"#EAEAAE\"><font size=\"2\" face=\"verdana\"></td>
|
|
</tr>"
|
|
|
|
|
|
|
|
end
|
|
|
|
|
|
end
|
|
@msgBody << "</table><br/>"
|
|
|
|
end
|
|
|
|
def closeHtmlHeader()
|
|
@msgBody << "<div><span style='font-size:8.0pt;line-height:115%;font-family:\"Arial\",\"sans-serif\" line-height: 0%;'><b>Generated by //depot/gta5/tools/wildwest/script/Ruby/ApprovedCutsceneUpdater/CutsceneApprovedUpdater.rb</b></span></div>"
|
|
|
|
@msgBody << "<div><span style='font-size:8.0pt;line-height:115%;font-family:\"Arial\",\"sans-serif\" line-height: 0%;'>Author: Mark.Harrison-Ball@rockstargames.com</span></div>"
|
|
end
|
|
|
|
|
|
def generateEmailReport()
|
|
|
|
|
|
HtmlHeader()
|
|
|
|
HtmlSummary()
|
|
|
|
generateBaseTable(true,"Approved Changes",'#556B2F')
|
|
|
|
generateBaseTable(false,"Not Approved Changes",'B22222')
|
|
|
|
generateBaseTable("new", "Name Changes",'#FFA500')
|
|
|
|
|
|
|
|
closeHtmlHeader()
|
|
|
|
|
|
|
|
|
|
|
|
if DEBUG
|
|
aFile = File.new("x:/temp/test.html", "w")
|
|
aFile.write(@msgBody)
|
|
aFile.close
|
|
end
|
|
|
|
return @msgBody
|
|
|
|
end
|
|
|
|
#---------------------------------------------------------------------------------------------------------------
|
|
#
|
|
#---------------------------------------------------------------------------------------------------------------
|
|
|
|
|
|
# Return Changelist objects from a set of Change records.
|
|
#---------------------------------------------------------
|
|
def get_changelists_from_changes( log, p4, change_records )
|
|
changelists = []
|
|
change_records.each do |change_record|
|
|
changelist = Changelist::Create( p4, change_record['change'].to_i )
|
|
changelists << changelist[0]
|
|
end
|
|
changelists
|
|
end
|
|
|
|
|
|
# Return the last submitted changelist number in perforce
|
|
#--------------------------------------------------------
|
|
def get_current_changelistnumber(p4, log)
|
|
change_records = p4.Run( 'changes', true, '-m', 1.to_s )
|
|
changes = get_changelists_from_changes( log, p4, change_records )
|
|
changes[0].Number
|
|
end
|
|
|
|
#
|
|
#---------------------------------------------------------------------------------
|
|
def parse_p4_review(p4, depotpath, log)
|
|
log.message( "Scanning for changes since last Changelist = #{@currentCL}.....")
|
|
g_Status = 'submitted'
|
|
bRequireCutsceneUpdate = false
|
|
change_records = p4.Run( 'changes', true, '-s', g_Status, depotpath+'@'+@currentCL.to_s+',#head' )
|
|
changes = get_changelists_from_changes( log, p4, change_records )
|
|
changes.each do |change|
|
|
|
|
log.message( "Changelist: #{change.Number} by #{change.Username}" )
|
|
if change.Number > @currentCL
|
|
@currentCL = (change.Number+1)
|
|
log.message( "Last Changelist = #{@currentCL}")
|
|
|
|
end
|
|
change.Files.each do |filename|
|
|
file_name = File.basename(filename).downcase
|
|
if file_name == 'gtav_camera_tracking.xlsm' or file_name == 'v_facial_animation_aracking.xlsm'
|
|
@p4_infodescription += "\nChangelist: #{change.Number} File: #{file_name} by #{change.Username}"
|
|
bRequireCutsceneUpdate = true
|
|
end
|
|
|
|
|
|
log.Message("\t#{filename}" )
|
|
end
|
|
end
|
|
bRequireCutsceneUpdate
|
|
end
|
|
|
|
# Revert if unchanged and tehn submit
|
|
#---------------------------------------------------------------
|
|
def submit_p4_file(p4, file, changelist, log)
|
|
# revert if unchnaged
|
|
log.message( "Reverting unchanged files for changelist ##{changelist.Number.to_s}")
|
|
reverted = p4.Run( 'revert', true, '-a', '-c', changelist.Number.to_s)
|
|
|
|
if (reverted.count == 0) # means file was NOT reverted
|
|
log.message( "Submitting changelist ##{changelist.Number.to_s}")
|
|
reportmsg = generateEmailReport()
|
|
|
|
if DEBUG
|
|
send_email(@email, AUTHOR, 'Auto Approved Cutscene Updated', reportmsg)
|
|
else
|
|
p4.Run( 'submit', true, '-c', changelist.Number.to_s )
|
|
send_email(@email, PRODUCTION_EMAIL, 'Auto Approved Cutscene Updated', reportmsg)
|
|
end
|
|
|
|
|
|
|
|
else
|
|
log.message( "Deleting empty changelist ##{changelist.Number.to_s}")
|
|
changelist.delete
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
#---------------------------------------------------
|
|
# Generic Excel Parser
|
|
# Input: log / not nessecary
|
|
# i: filename
|
|
# i: rowkey - a column index to look up and the value to seach for
|
|
# i: excelcolumnindexlist - List of column xcel names to collect
|
|
#---------------------------------------------------
|
|
#def ParseExcelDoc(excelfilename, rowkey, excelcolumnindexlist, log)
|
|
def ParseExcelDoc(excelfilename, rowkey, log)
|
|
# Checks
|
|
break if rowkey.length == 0
|
|
|
|
excel = WIN32OLE::new('excel.Application')
|
|
workbook = excel.Workbooks.Open(excelfilename)
|
|
worksheet = workbook.Worksheets(1)
|
|
worksheet.Select
|
|
excel['Visible'] = false #make visible, set to false to make invisible
|
|
|
|
rows = worksheet.UsedRange.Rows.Count;
|
|
cols = worksheet.UsedRange.Columns.Count;
|
|
|
|
aArray = Array.new()
|
|
eArray = Array.new()
|
|
eSubArray = Array.new()
|
|
excelStatus = 0
|
|
|
|
if rowkey[:key]
|
|
key_name = rowkey[:subkey]
|
|
end
|
|
if rowkey[:subkey]
|
|
subkey_name = rowkey[:subkey]
|
|
end
|
|
|
|
#aArray = Array.new()
|
|
# We want to go trhough each row, if we have a key then read the rwo with the key
|
|
count = 0
|
|
rows.times do |count|
|
|
key = rowkey[:row] +(count+1).to_s
|
|
masterLookUpVal = worksheet.range(key).value
|
|
|
|
|
|
if masterLookUpVal != nil and masterLookUpVal.is_a? String
|
|
if masterLookUpVal.downcase == rowkey[:key]
|
|
if eArray.size > 0 # if we had a previous array
|
|
if eSubArray.size > 0
|
|
eArray << eSubArray
|
|
end
|
|
aArray << eArray
|
|
eArray = Array.new()
|
|
eSubArray = Array.new()
|
|
end
|
|
excelStatus = 1
|
|
elsif masterLookUpVal.downcase == rowkey[:subkey] # we say look fo rsub info
|
|
excelStatus = 2
|
|
else
|
|
excelStatus = 0
|
|
end
|
|
else
|
|
excelStatus = 0
|
|
end
|
|
|
|
|
|
# Master Data
|
|
if excelStatus == 1
|
|
rowkey[:vals].each do |indexVal|
|
|
lookupColIndex = indexVal+(count+1).to_s()
|
|
val_toAdd = worksheet.range(lookupColIndex).value
|
|
#puts(val_toAdd)
|
|
eArray << val_toAdd
|
|
end
|
|
end
|
|
|
|
# Subdata
|
|
if excelStatus == 2
|
|
rowkey[:subvals].each do |indexVal|
|
|
lookupColIndex = indexVal+(count+1).to_s()
|
|
val_toAdd = worksheet.range(lookupColIndex).value
|
|
eSubArray << val_toAdd
|
|
end
|
|
end
|
|
|
|
if count == rows.size-1
|
|
# Add last array
|
|
if eArray.size > 0 # if we had a previous array
|
|
if eSubArray.size > 0
|
|
eArray << eSubArray
|
|
end
|
|
aArray << eArray
|
|
end
|
|
end
|
|
end
|
|
|
|
|
|
|
|
workbook.Close(false)
|
|
excel.Quit
|
|
excel = nil
|
|
#GC.start # Invoke
|
|
|
|
return aArray
|
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
=begin
|
|
|
|
masterLookUpVal = testarray[i][rowkey[:row]]
|
|
|
|
datVal = worksheet.range(key).value
|
|
if datVal != nil
|
|
begin
|
|
#puts(datVal)
|
|
if rowkey[:keys].include?(datVal.downcase)
|
|
|
|
if datVal.downcase == rowkey[1].downcase # We found a key
|
|
# Read each Header value into List
|
|
eArray = Array.new()
|
|
|
|
excelcolumnindexlist.each {|colkey|
|
|
lookupColIndex = colkey+(count+1).to_s()
|
|
val = worksheet.range(lookupColIndex).value
|
|
eArray <<(val)
|
|
}
|
|
|
|
aArray << eArray
|
|
end
|
|
rescue
|
|
log.Message("Skipping Value #{datVal}")
|
|
end
|
|
end
|
|
end
|
|
=end
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#---------------------------------------------------
|
|
# Read our Tracking Doc
|
|
# Ok I need to change this so we look up for the actual name than the key
|
|
#---------------------------------------------------
|
|
def ReadTrackingDoc(log, doc)
|
|
log.Message("Opening Doc #{doc}")
|
|
#rowkey = ["B","cs"] # optional row Key
|
|
#rowkey = {:row => "B", :keys =>["cs", "fbx"]} # optional row Key
|
|
#excelcolumnindexlist = ["d", "u", "y", "z","az","ba"]
|
|
|
|
rowkey = {:row => "b",
|
|
:key => "cs",
|
|
:vals => ["d", "t", "x", "y","be","bf","v"],
|
|
:subrow => "b",
|
|
:subkey => "fbx",
|
|
:subvals => ["i"]
|
|
}
|
|
|
|
|
|
# 0|D = Scene Name
|
|
# 1|2 T/U = Edit Approved
|
|
# 2|X = bug number for DOF
|
|
# 3|Y = Dof approved
|
|
# 4|BE = bug number for lighting
|
|
# 5|BF = LIghting approved (2st Pass)
|
|
# 6|V = Animation
|
|
|
|
#ParseExcelDoc(doc, rowkey, excelcolumnindexlist,log)
|
|
ParseExcelDoc(doc, rowkey, log)
|
|
|
|
end
|
|
|
|
#---------------------------------------------------
|
|
# Read our Facial Doc
|
|
#--------------------------------------------------
|
|
def ReadFacialDoc(log, doc)
|
|
log.Message("Opening Doc #{doc}")
|
|
#rowkey = {:row => "f", :keys =>["face"]} # optional row Key
|
|
#excelcolumnindexlist = ["ak","ai", "c"]
|
|
# 0 Scene Name
|
|
# 1 SOFT Approved
|
|
# 2 = bugnumber
|
|
rowkey = {:row => "g",
|
|
:key => "face",
|
|
:vals => ["al","aj","c"]
|
|
}
|
|
|
|
#ParseExcelDoc(doc, rowkey, excelcolumnindexlist, log)
|
|
ParseExcelDoc(doc, rowkey, log)
|
|
|
|
end
|
|
|
|
|
|
#---------------------------------------------------
|
|
# Convert value to Bool
|
|
#---------------------------------------------------
|
|
def convertToBool(value, log, valName)
|
|
log.Message("#{valName} = #{value}")
|
|
if (value.class == System::DateTime)
|
|
return true
|
|
elsif (value == 1.0)
|
|
return true
|
|
elsif (value == 'Test (Verifying)')
|
|
return true
|
|
elsif (value == 'Closed (Fixed)')
|
|
return true
|
|
elsif (value == 'Dev (Fixed, Awaiting Build)')
|
|
return true
|
|
else
|
|
return false
|
|
|
|
end
|
|
end
|
|
|
|
#---------------------------------------------------
|
|
# Convert value to Bool
|
|
#---------------------------------------------------
|
|
def convertDateToBool(value, log, valName)
|
|
log.Message("#{valName} = #{value.to_s}")
|
|
#log.Message("#{valName} = #{value.class}")
|
|
#puts(value.class)
|
|
if (value.class == System::DateTime)
|
|
return true
|
|
else
|
|
return false
|
|
end
|
|
end
|
|
|
|
=begin
|
|
def LogValueHasChanged(scenename, cutlistentryValue, exceldataEntryValue, cutlistentry)
|
|
if cutlistentryValue.value != bcutval2
|
|
if not @emailLog.include?('chees33e')
|
|
end
|
|
else
|
|
@emailLog['cheese'] = ['cutscneParroved = true']
|
|
end
|
|
|
|
|
|
end
|
|
cutentry.value = bcutval2
|
|
|
|
end
|
|
=end
|
|
|
|
|
|
def valueBoolChanged(bInputA, bInputB)
|
|
bHasChanged = false
|
|
if bInputA != bInputB
|
|
bHasChanged = true
|
|
end
|
|
bHasChanged
|
|
end
|
|
|
|
def updateMetaEntry(cutlistentry, approvedName, exceldataValue, log, excelbugnumbersArray = ['-'])
|
|
scenename = cutlistentry["CutsceneName"].value
|
|
bApprovedFlag = cutlistentry[approvedName].value
|
|
bExcelFlag = convertToBool(exceldataValue, log, approvedName)
|
|
|
|
if approvedName == "CameraApproved" and bExcelFlag
|
|
CAMERA_COUNT += 1
|
|
elsif approvedName == "LightingApproved" and bExcelFlag
|
|
LIGHTING_COUNT +=1
|
|
elsif approvedName == "FacialApproved" and bExcelFlag
|
|
FACIAL_COUNT +=1
|
|
elsif approvedName == "AnimationApproved" and bExcelFlag
|
|
ANIMATION_COUNT +=1
|
|
elsif approvedName == "DofApproved" and bExcelFlag
|
|
DOF_COUNT += 1
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
if valueBoolChanged(bApprovedFlag, bExcelFlag)
|
|
cutlistentry[approvedName].value = bExcelFlag
|
|
if not @emailLog.include?(scenename)
|
|
@emailLog[scenename] = [{:ApprovedName => approvedName, :Status => bExcelFlag, :Bug => excelbugnumbersArray}]
|
|
else
|
|
@emailLog[scenename] << {:ApprovedName=> approvedName, :Status => bExcelFlag, :Bug => excelbugnumbersArray}
|
|
end
|
|
end
|
|
end
|
|
|
|
|
|
#------------------------------------
|
|
#
|
|
#-----------------------------------
|
|
# 0 D = Scene Name
|
|
# 1|2 U|V = Edit Approved
|
|
# 2 bug number for lighting
|
|
# 3 Z =Dof approved
|
|
# 4 bug number for lighting
|
|
# 5 LIghting approved (2st Pass)
|
|
|
|
def updateMetaData(scenename, exceldataEntry, cutlistentry, log)
|
|
#updateMetaEntry(cutlistentry, "CutsceneApproved", exceldataEntry[?], log)
|
|
#log.message("CameraApproved = #{exceldataEntry[1]}, DofApproved = #{exceldataEntry[3]}, bug = #{[exceldataEntry[2]].to_s}, LightingApproved = #{exceldataEntry[5].to_s}, bug = #{[exceldataEntry[4]].to_s}")
|
|
#print(exceldataEntry+"\n")
|
|
|
|
updateMetaEntry(cutlistentry, "AnimationApproved", exceldataEntry[6], log )
|
|
updateMetaEntry(cutlistentry, "CameraApproved", exceldataEntry[1], log, exceldataEntry.last )
|
|
updateMetaEntry(cutlistentry, "DofApproved", exceldataEntry[3], log, [exceldataEntry[2]])
|
|
updateMetaEntry(cutlistentry, "LightingApproved", exceldataEntry[5], log, [exceldataEntry[4]])
|
|
|
|
end
|
|
|
|
# 0 = bugnumbers
|
|
# 1 SOFT Approved
|
|
# 2 Scene Name
|
|
def updateFaceMetaData(scenename, exceldataEntry, cutlistentry, log)
|
|
updateMetaEntry(cutlistentry, "FacialApproved", exceldataEntry[0] , log, exceldataEntry[1] )
|
|
end
|
|
|
|
#-------------------------------------------------------
|
|
# Add a new tunable to our metadata file
|
|
#-------------------------------------------------------
|
|
def AddCutsceneEntry(name, memberUtils, cutlist, metaLoad)
|
|
member = memberUtils.FindStructMemberByName(metaLoad.Definition.Members, "ApprovalStatuses")
|
|
structTunable = RSG::Metadata::Data::StructureTunable.new(member, nil)
|
|
structTunable.Item("CutsceneName").value = name
|
|
cutlist.Add(structTunable)
|
|
return structTunable
|
|
|
|
end
|
|
|
|
def UpdateCutscene_osi(p4, branch, g_Log)
|
|
excelTrackfilename = 'X:/gta5/docs/production/GTAV_CAMERA_TRACKING.xlsm'
|
|
excelFacialfilename = 'X:/gta5/docs/production/Animation/V_Facial_Animation_Tracking.xlsm'
|
|
metaCameraFile = 'X:/gta5/build/dev/common/non_final/cutscene/ApprovedCutsceneList.meta'
|
|
definationsPath = 'x:/gta5/assets/metadata/definitions/'
|
|
|
|
#------------------------------------
|
|
# Sync to required Files
|
|
|
|
#args = ['-f', Path.Combine( dest_dir, '...' )
|
|
g_Log.Message('Found changes, syncing Files')
|
|
args = ['-f', excelTrackfilename, excelFacialfilename, metaCameraFile, ( definationsPath+ '...' )]
|
|
p4.Run( 'sync', System::Array[System::String].new(args) )
|
|
|
|
|
|
changelist_description = 'Auto Approved Cutscene Update Submit [AP3]' +@p4_infodescription
|
|
changelist = p4.CreatePendingChangelist(changelist_description)
|
|
main_args = ['-c', changelist.Number.to_s]
|
|
args = main_args + [metaCameraFile]
|
|
args = System::Array[System::String].new(args)
|
|
ret = p4.Run('edit', args)
|
|
#g_Log.Message(args)
|
|
#_Log.Message(ret)
|
|
DOF_COUNT = 0
|
|
LIGHTING_COUNT = 0
|
|
FACIAL_COUNT = 0
|
|
ANIMATION_COUNT = 0
|
|
CAMERA_COUNT = 0
|
|
# Total entries
|
|
CUTLIST_COUNT = 0
|
|
FACIALLIST_COUNT = 0
|
|
|
|
begin
|
|
|
|
#----------------------------------
|
|
trackingData = ReadTrackingDoc(g_Log, excelTrackfilename)
|
|
facialData = ReadFacialDoc(g_Log, excelFacialfilename)
|
|
|
|
structspath = [definationsPath]
|
|
structPathNet = System::Array[System::String].new(structspath)
|
|
structDict = RSG::Metadata::Model::StructureDictionary.new
|
|
structDict.load(branch, structPathNet)
|
|
|
|
# put in beign catch
|
|
g_Log.Message("loaded Definitions sucessfully")
|
|
|
|
metaLoad = RSG::Metadata::Model::MetaFile.new( metaCameraFile, structDict, true)
|
|
|
|
# Static Def calsses
|
|
TunableUtils = RSG::Metadata::Util::Tunable
|
|
MemberUtils = RSG::Metadata::Util::Member
|
|
|
|
|
|
|
|
# Main Dictionary
|
|
cutlist = TunableUtils.FindFirstStuctureNamed("ApprovalStatuses", metaLoad.Members )
|
|
|
|
# Create a array of scene names so we can do quick lookup from the cutlist.
|
|
metasceneNames = []#Hash.new
|
|
cutlist.each {|colkey|
|
|
metasceneNames << colkey["CutsceneName"].value
|
|
}
|
|
g_Log.message("======================================CutNames lookup = #{metasceneNames.length}")
|
|
|
|
|
|
g_Log.Message('Created look up')
|
|
|
|
|
|
#------------ Check data
|
|
CUTLIST_COUNT = trackingData.length
|
|
|
|
#------------------------------
|
|
trackingData.each{|thisdata|
|
|
# Check if cutscene name exists, if does update
|
|
lscenename = thisdata[0].downcase
|
|
|
|
|
|
#g_Log.Message(metasceneNames[lsceneName])
|
|
|
|
if metasceneNames.include?(lscenename)
|
|
#if metasceneNames.has_key?(lSceneName)
|
|
g_Log.Message("Found Existing cutscene #{lscenename}")
|
|
index = metasceneNames.index(lscenename)
|
|
updateMetaData(lscenename, thisdata, cutlist[index], g_Log )
|
|
# If does not exist add new data
|
|
else
|
|
g_Log.Message("Creating New Entry for cutscene #{lscenename}")
|
|
entry = AddCutsceneEntry(lscenename, MemberUtils, cutlist, metaLoad)
|
|
updateMetaData(lscenename, thisdata, entry, g_Log)
|
|
#@emailLog[lscenename] = ["","New entry has been added","-"]
|
|
@emailLog[lscenename] = [{:ApprovedName => "New entry/Name Change", :Status => "new", :Bug => ["-"]}]
|
|
|
|
end
|
|
# don't really need to care about old data
|
|
}
|
|
|
|
#----------------------------------
|
|
# Rebuild our lookup table
|
|
metasceneNames.clear #= []#Hash.new
|
|
cutlist.each {|colkey|
|
|
metasceneNames << colkey["CutsceneName"].value
|
|
}
|
|
|
|
g_Log.message("====================================CutNames lookup= #{metasceneNames.length}")
|
|
|
|
# So we need to do some colection on the arrays to make sure all characters have got facical passses
|
|
g_Log.message("Parsing Facial Doc")
|
|
validfacialData = Hash.new
|
|
|
|
facialData.each{|thisdata|
|
|
sceneName = thisdata[0].downcase
|
|
|
|
|
|
if not validfacialData.include?(sceneName)
|
|
validfacialData[sceneName] = [thisdata[1], [thisdata[2]]]
|
|
else
|
|
if (thisdata[1].class != System::DateTime)
|
|
validfacialData[sceneName][0] = 0.0
|
|
end
|
|
validfacialData[sceneName][1] << thisdata[2] #add bugnumbers
|
|
|
|
end
|
|
|
|
}
|
|
|
|
g_Log.Message("validfacialData = #{validfacialData.length} , facialData = #{facialData.length}")
|
|
|
|
FACIALLIST_COUNT = validfacialData.length
|
|
|
|
# now lets go theough our filtered down Hash
|
|
validfacialData.each do |key, value|
|
|
if metasceneNames.include?(key)
|
|
g_Log.Message("Found Existing cutscene #{key}")
|
|
index = metasceneNames.index(key)
|
|
updateFaceMetaData(key, value, cutlist[index], g_Log)
|
|
|
|
end
|
|
end
|
|
|
|
# Save the new metafile
|
|
metaLoad.SerialiseAlt(metaCameraFile)
|
|
|
|
# revert and submit
|
|
submit_p4_file(p4, metaCameraFile, changelist, g_Log)
|
|
|
|
|
|
g_Log.Message("Sucess!")
|
|
rescue => msg
|
|
puts(msg)
|
|
g_Log.error("Crash Deteted ")
|
|
send_email(EMAIL, AUTHOR, 'Auto Approved Cutscene Updater', msg)
|
|
end
|
|
|
|
|
|
end
|
|
|
|
|
|
#-----------------------------------------------------------------------------
|
|
# Entry-Point
|
|
#-----------------------------------------------------------------------------
|
|
if ( __FILE__ == $0 ) then
|
|
|
|
g_Options = OS::Options::new( OPTIONS )
|
|
LogFactory.CreateApplicationConsoleLogTarget( )
|
|
g_Log = LogFactory.ApplicationLog
|
|
|
|
|
|
|
|
begin
|
|
if ( g_Options.is_enabled?( 'help' ) )
|
|
puts "#{__FILE__}"
|
|
puts "Usage:"
|
|
puts g_Options.usage()
|
|
exit( 1 )
|
|
end
|
|
|
|
|
|
|
|
g_Frequency = g_Options.get?( 'frequency' ) unless g_Options.get( 'frequency' ).nil?
|
|
|
|
# Perforce initialisation.
|
|
g_Config = RSG::Base::Configuration::ConfigFactory::CreateConfig( )
|
|
@email = g_Config.EmailAddress
|
|
@server= g_Config.studios.this_studio.exchange_server # FUCKING MAKES NOT FUCKING DIFFERNCE!@
|
|
@server= 'rsgldnexg1.rockstar.t2.corp'
|
|
|
|
branch = g_Config.Project.DefaultBranch
|
|
p4 = g_Config.Project.SCMConnect()
|
|
|
|
# Statics
|
|
depotpath = '//depot/gta5/docs/production/...'
|
|
|
|
@currentCL = get_current_changelistnumber(p4, g_Log)
|
|
#@currentCL = 3778825
|
|
|
|
@lastCL = @currentCL
|
|
|
|
|
|
|
|
# USed for debug test
|
|
if DEBUG
|
|
@currentCL = 3757695
|
|
|
|
end
|
|
|
|
@currentCL = 3757695
|
|
|
|
send_email(EMAIL, AUTHOR, 'Auto Approved Cutscene Updater', 'Cutscene Approved Updater has started!')
|
|
|
|
repeat = true
|
|
while repeat
|
|
# Globals
|
|
@p4_infodescription = ''
|
|
@emailLog = Hash.new
|
|
@msgBody = ""
|
|
# ---------
|
|
bRunCutsceneUpdate = parse_p4_review(p4, depotpath, g_Log)
|
|
if bRunCutsceneUpdate
|
|
#puts("Updating cutscene")
|
|
UpdateCutscene_osi(p4,branch, g_Log)
|
|
end
|
|
puts("Waiting.........")
|
|
sleep (14400) # seconds opt[:delay]
|
|
end
|
|
|
|
|
|
rescue => msg
|
|
g_Log.error("Crash Deteted ")
|
|
g_Log.error(msg)
|
|
if not DEBUG
|
|
send_email(EMAIL, AUTHOR, 'Auto Approved Cutscene Updater', msg)
|
|
end
|
|
|
|
end
|
|
|
|
|
|
end |