一键生成带图像的开火时刻表
阅读
2026-01-22更新
最新编辑:westseo
阅读:
更新日期:2026-01-22
最新编辑:westseo
[ 把BUFF触发改成了时间和轮次 ]把两个文件(命名相同,如AL_Heatmap_App_Kai_I.m)置于同一根目录下运行(特点是有一个X轴δR,Y轴R的热图,鼠标放上去可以简明地看到“某装填值下需要多少额外/更少的δR能够恰好射出某轮攻击”。同时支持时间轴上任意调整BUFF数量/数值和开始/结束时间。考虑到某些舰船前后摇可能比较特别,所以该项也是可调的。)
(文件1,UI界面)
classdef AL_Heatmap_App_Kai_I < matlab.apps.AppBase
properties (Access = private)
PresetData
Range_R = 50:1:400
Range_dR = -40:1:80
IsLoading = true
end
properties (Access = public)
UIFigure
GridLayout
LeftPanel
RightPanel
TabGroup
TabSettings
TabBuffs
TabAdvanced
TabHelp
TabLog
% Settings Tab
TypeDropDown
CurrentTimeEdit
ShipBaseReloadEdit
CatReloadEdit
PreCastEdit
PostCastEdit
FireDurEdit
SimDurationEdit
HeatmapSwitch
% Buff Tab
BuffTable
AddRowBtn
DelRowBtn
BuffPreviewAx
SkillTotalEdit % Moved permanent here
FinalCDREdit % Moved permanent here
% Advanced Tab
SplitPartsEdit
SplitIntervalEdit
HitDelayEdit
CalcModeSwitch
% Log Tab
LogTextArea
% Main Plot
Ax
end
methods (Access = public)
function app = AL_Heatmap_App_Kai_I
% Initialize Data - Step by Step
keys = {
'战列 (BB) - [Pre 0.17s / Post 0.00s]', ...
'驱逐 (DD) - [Pre 0.16s / Post 0.10s]', ...
'轻巡 (CL) - [Pre 0.18s / Post 0.10s]', ...
'重巡 (CA) - [Pre 0.20s / Post 0.10s]', ...
'超巡 (CB) - [Pre 0.20s / Post 0.10s]', ...
'航母 (CV) - [Pre 0.00s / Post 0.00s]', ...
'自定义'
};
vals = {
[0.17, 0.00], ... % BB
[0.16, 0.10], ... % DD
[0.18, 0.10], ... % CL
[0.20, 0.10], ... % CA
[0.20, 0.10], ... % CB
[0.00, 0.00], ... % CV
[0.00, 0.00] % Custom
};
app.PresetData = containers.Map(keys, vals);
createUI(app);
app.IsLoading = false;
onPresetChanged(app);
end
end
methods (Access = private)
function createUI(app)
try
% Main Window
app.UIFigure = uifigure('Name', 'AL Reload Heatmap Kai I', ...
'Position', [100, 100, 1150, 750], 'Color', 'w');
app.GridLayout = uigridlayout(app.UIFigure, [1, 2]);
app.GridLayout.ColumnWidth = {480, '1x'};
% Left Panel
app.LeftPanel = uipanel(app.GridLayout, 'Title', '配置与说明', ...
'FontSize', 14, 'FontWeight', 'bold', 'BackgroundColor', 'w');
app.LeftPanel.Layout.Column = 1;
GridMain = uigridlayout(app.LeftPanel, [1, 1]);
GridMain.Padding = [5 5 5 5];
app.TabGroup = uitabgroup(GridMain);
% ============================================================
% Tab 1: Settings
% ============================================================
app.TabSettings = uitab(app.TabGroup, 'Title', '参数设定', 'Scrollable', 'on');
GridSettings = uigridlayout(app.TabSettings, [12, 1]);
GridSettings.Padding = [10 10 10 10];
GridSettings.RowSpacing = 8;
GridSettings.RowHeight = {'fit','fit','fit','fit','fit','fit','fit','fit','fit','fit','fit','1x'};
% 1. Preset
GridPreset = uigridlayout(GridSettings, [1, 2]); GridPreset.Padding=[0 0 0 0]; GridPreset.ColumnWidth={100,'1x'};
uilabel(GridPreset, 'Text', '1. 舰种预设:', 'FontWeight','bold');
app.TypeDropDown = uidropdown(GridPreset, 'Items', keys(app.PresetData), 'Value', '航母 (CV) - [Pre 0.00s / Post 0.00s]', 'ValueChangedFcn', @(s,e) onPresetChanged(app));
% 2. Panel Data
uilabel(GridSettings, 'Text', '2. 面板数据 (基准值)', 'FontWeight','bold', 'FontColor', [0.8 0 0]);
GridPanelData = uigridlayout(GridSettings, [3, 2]); GridPanelData.Padding=[10 0 0 0]; GridPanelData.RowHeight={'fit','fit','fit'}; GridPanelData.ColumnWidth={'1x', 100};
uilabel(GridPanelData, 'Text', '面板显示射速 (s):'); app.CurrentTimeEdit = uieditfield(GridPanelData, 'numeric', 'Value', 22.50, 'ValueChangedFcn', @(s,e) refreshPlots(app));
uilabel(GridPanelData, 'Text', '船坞装填 (白值+科技+装备):'); app.ShipBaseReloadEdit = uieditfield(GridPanelData, 'numeric', 'Value', 180, 'ValueChangedFcn', @(s,e) refreshPlots(app));
uilabel(GridPanelData, 'Text', '指挥猫装填:'); app.CatReloadEdit = uieditfield(GridPanelData, 'numeric', 'Value', 0, 'ValueChangedFcn', @(s,e) refreshPlots(app));
% 3. Action Params
uilabel(GridSettings, 'Text', '3. 动作参数 (调轴)', 'FontWeight','bold', 'FontColor', [0 0.4 0.8]);
GridAction = uigridlayout(GridSettings, [2, 4]); GridAction.Padding=[10 0 0 0]; GridAction.RowHeight={'fit','fit'}; GridAction.ColumnWidth={50,'1x',50,'1x'};
uilabel(GridAction, 'Text', '前摇:'); app.PreCastEdit = uieditfield(GridAction, 'numeric', 'ValueChangedFcn', @(s,e) refreshPlots(app));
uilabel(GridAction, 'Text', '后摇:'); app.PostCastEdit = uieditfield(GridAction, 'numeric', 'ValueChangedFcn', @(s,e) refreshPlots(app));
uilabel(GridAction, 'Text', '开火:', 'FontColor','red'); app.FireDurEdit = uieditfield(GridAction, 'numeric', 'Value', 0.00, 'FontColor','red', 'ValueChangedFcn', @(s,e) refreshPlots(app));
% 4. Simulation Control
uilabel(GridSettings, 'Text', '4. 模拟控制', 'FontWeight', 'bold');
GridSim = uigridlayout(GridSettings, [3, 2]); GridSim.Padding=[10 0 0 0]; GridSim.ColumnWidth={100, '1x'};
uilabel(GridSim, 'Text', '模拟时长(s):'); app.SimDurationEdit = uieditfield(GridSim, 'numeric', 'Value', 90, 'ValueChangedFcn', @(s,e) refreshPlots(app));
GridBtns = uigridlayout(GridSim, [1, 3]); GridBtns.Padding=[0 0 0 0]; GridBtns.ColumnSpacing=5;
GridBtns.Layout.Column = [1 2];
uibutton(GridBtns, 'Text', '10s', 'ButtonPushedFcn', @(s,e) setDuration(app, 10));
uibutton(GridBtns, 'Text', '20s', 'ButtonPushedFcn', @(s,e) setDuration(app, 20));
uibutton(GridBtns, 'Text', '90s', 'ButtonPushedFcn', @(s,e) setDuration(app, 90));
uilabel(GridSim, 'Text', '装填机制:');
app.HeatmapSwitch = uiswitch(GridSim, 'slider', 'Items', {'串行', '并行'}, 'ValueChangedFcn', @(s,e) refreshPlots(app));
% ============================================================
% Tab 2: Buff Configuration
% ============================================================
app.TabBuffs = uitab(app.TabGroup, 'Title', '战斗环境配置');
GridBuffs = uigridlayout(app.TabBuffs, [3, 1]);
GridBuffs.RowHeight = {'fit', '1x', 150};
GridBuffs.Padding = [10 10 10 10];
% Permanent Stats
GridPerm = uigridlayout(GridBuffs, [1, 4]); GridPerm.Padding=[0 0 0 0];
uilabel(GridPerm, 'Text', '常驻装填加成(%):'); app.SkillTotalEdit = uieditfield(GridPerm, 'numeric', 'Value', 0, 'ValueChangedFcn', @(s,e) refreshPlots(app));
uilabel(GridPerm, 'Text', '常驻CDR(%):', 'FontColor', '#7E2F8E'); app.FinalCDREdit = uieditfield(GridPerm, 'numeric', 'Value', 0, 'FontColor', '#7E2F8E', 'FontWeight', 'bold', 'ValueChangedFcn', @(s,e) refreshPlots(app));
% Table Controls
GridTbl = uigridlayout(GridBuffs, [2, 1]);
GridTbl.RowHeight = {'1x', 40};
GridTbl.Padding = [0 0 0 0];
% Create BuffTable inside GridTbl
app.BuffTable = uitable(GridTbl);
app.BuffTable.Layout.Row = 1;
app.BuffTable.Layout.Column = 1;
app.BuffTable.ColumnName = {'触发模式', '开始', '结束', 'CDR%', '装填%'};
app.BuffTable.ColumnFormat = {{'时间(s)', '轮次(R)'}, 'numeric', 'numeric', 'numeric', 'numeric'};
app.BuffTable.ColumnEditable = true;
app.BuffTable.ColumnWidth = {80, 50, 50, 50, 50};
% Default: Time mode
app.BuffTable.Data = {'时间(s)', 0, 20, 0, 0; '时间(s)', 20, 100, 0, 0};
app.BuffTable.CellEditCallback = @(s,e) refreshPlots(app);
GridTblBtns = uigridlayout(GridTbl, [1, 2]);
GridTblBtns.ColumnWidth = {'1x', '1x'};
GridTblBtns.Padding = [2 2 2 2]; % Reduced padding
app.AddRowBtn = uibutton(GridTblBtns, 'Text', '+ 添加修正行', ...
'FontSize', 12, 'FontWeight', 'bold', ...
'ButtonPushedFcn', @(s,e) addBuffRow(app));
app.DelRowBtn = uibutton(GridTblBtns, 'Text', '- 删除选中行', ...
'FontSize', 12, 'FontWeight', 'bold', ...
'ButtonPushedFcn', @(s,e) delBuffRow(app));
% Preview Plot
app.BuffPreviewAx = uiaxes(GridBuffs);
app.BuffPreviewAx.Title.String = '装填速率与开火时间预测';
app.BuffPreviewAx.XLabel.String = 'Time (s)';
app.BuffPreviewAx.YLabel.String = '相对速率 (x1 = Base)';
app.BuffPreviewAx.YGrid = 'on';
% ============================================================
% Tab 3: Advanced
% ============================================================
app.TabAdvanced = uitab(app.TabGroup, 'Title', '高级设置', 'Scrollable', 'on');
GridAdv = uigridlayout(app.TabAdvanced, [6, 2]);
GridAdv.Padding = [20 20 20 20];
GridAdv.RowHeight = {'fit', 'fit', 'fit', 'fit', 'fit', '1x'};
GridAdv.ColumnWidth = {'1x', 120};
uilabel(GridAdv, 'Text', '伤害结算模式:', 'FontWeight', 'bold', 'FontSize', 14);
app.CalcModeSwitch = uiswitch(GridAdv, 'slider', 'Items', {'平滑模型', '阶梯模型'});
app.CalcModeSwitch.ValueChangedFcn = @(s,e) refreshPlots(app);
uilabel(GridAdv, 'Text', '注意: 阶梯模型下, 只有完整的一波攻击才会计入收益。', 'FontColor', '#808080', 'FontSize', 10);
uilabel(GridAdv, 'Text', '');
uilabel(GridAdv, 'Text', '攻击波次 / 底座数 (Split Parts):', 'FontSize', 12);
app.SplitPartsEdit = uieditfield(GridAdv, 'numeric', 'Value', 1, 'Limits', [1 10], 'RoundFractionalValues', 'on', 'ValueChangedFcn', @(s,e) refreshPlots(app));
uilabel(GridAdv, 'Text', '波次间隔 (Interval, s):', 'FontSize', 12);
app.SplitIntervalEdit = uieditfield(GridAdv, 'numeric', 'Value', 0, 'ValueChangedFcn', @(s,e) refreshPlots(app));
uilabel(GridAdv, 'Text', '命中延迟 (Hit Delay, s):', 'FontSize', 12);
app.HitDelayEdit = uieditfield(GridAdv, 'numeric', 'Value', 0.5, 'ValueChangedFcn', @(s,e) refreshPlots(app));
% ============================================================
% Tab 4: Help
% ============================================================
app.TabHelp = uitab(app.TabGroup, 'Title', '说明文本', 'Scrollable', 'on');
GridHelp = uigridlayout(app.TabHelp, [1, 1]);
GridHelp.Padding = [10 10 10 10];
HelpLines = {
'【多重 Buff 配置】';
'在“战斗环境配置”页签中,你可以自由定义复杂的时间轴 Buff。';
'表格每一行代表一个生效的时间段。';
'系统会自动计算重叠时间段内的 Buff 叠加效果。';
'';
'【参数说明】';
'CDR修正(%): 减少技能冷却时间,例如 -15 代表减少 15% CD。';
'装填值修正(%): 增加面板装填属性,例如 50 代表装填值 +50%。';
'';
'关于前后摇:https://wiki.biligame.com/blhx/5B5的其他研究合集#舰船的可手动开火武器(含后排卡炮研究)';
'关于武器开火时间:https://wiki.biligame.com/blhx/全武器对护甲补正一览';
};
uitextarea(GridHelp, 'Value', strjoin(HelpLines, '\n'), 'Editable', 'off', ...
'FontName', 'Microsoft YaHei', 'FontSize', 12);
% ============================================================
% Tab 5: Log
% ============================================================
app.TabLog = uitab(app.TabGroup, 'Title', '模拟日志', 'Scrollable', 'on');
GridLog = uigridlayout(app.TabLog, [2, 1]);
GridLog.RowHeight = {40, '1x'};
GridLog.Padding = [10 10 10 10];
uibutton(GridLog, 'Text', '生成当前配置的时间轴报告', ...
'BackgroundColor', [0.9 0.95 1], ...
'FontWeight', 'bold', ...
'ButtonPushedFcn', @(s,e) showTimelineReport(app));
app.LogTextArea = uitextarea(GridLog, 'Value', '...', 'Editable', 'off', 'FontName', 'Consolas', 'FontSize', 11);
% --- Right Panel ---
app.RightPanel = uipanel(app.GridLayout, 'BorderType', 'none');
app.RightPanel.Layout.Column = 2;
GridRight = uigridlayout(app.RightPanel, [1, 1]);
GridRight.Padding = [10 10 10 10];
app.Ax = uiaxes(GridRight);
app.Ax.Title.String = '收益热图 (有效轮次)';
app.Ax.XLabel.String = '新增白值 (\Delta R)';
app.Ax.YLabel.String = '船坞装填 (Base)';
colormap(app.Ax, 'parula');
catch ME
uialert(app.UIFigure, ME.message, 'Init Error');
end
end
function setDuration(app, val)
if app.IsLoading, return; end
app.SimDurationEdit.Value = val;
refreshPlots(app);
end
function addBuffRow(app)
d = app.BuffTable.Data;
% Create a 5-column row matching the new format:
% {Mode, Start, End, CDR, Rld}
newRow = {'时间(s)', 0, app.SimDurationEdit.Value, 0, 0};
if isempty(d)
app.BuffTable.Data = newRow;
elseif iscell(d)
app.BuffTable.Data = [d; newRow];
else
app.BuffTable.Data = [num2cell(d); newRow];
end
refreshPlots(app);
end
function delBuffRow(app)
d = app.BuffTable.Data;
if isempty(d), return; end
% Get selection
sel = app.BuffTable.Selection;
% sel is [row, col] or multiple indices.
if isempty(sel), return; end
rowsToDelete = unique(sel(:,1));
d(rowsToDelete, :) = [];
app.BuffTable.Data = d;
refreshPlots(app);
end
function onPresetChanged(app)
key = app.TypeDropDown.Value;
if isKey(app.PresetData, key)
vals = app.PresetData(key);
app.PreCastEdit.Value = vals(1);
app.PostCastEdit.Value = vals(2);
refreshPlots(app);
end
end
function refreshPlots(app)
if app.IsLoading, return; end
% Gather Inputs
v_CurTime = app.CurrentTimeEdit.Value;
v_ShipBase = app.ShipBaseReloadEdit.Value;
v_CatReload = app.CatReloadEdit.Value;
v_Pre = app.PreCastEdit.Value;
v_Post = app.PostCastEdit.Value;
v_Fire = app.FireDurEdit.Value;
v_Skill = app.SkillTotalEdit.Value;
v_Final = app.FinalCDREdit.Value;
v_Table = app.BuffTable.Data;
v_Dur = app.SimDurationEdit.Value;
v_CalcMode = app.CalcModeSwitch.Value;
v_Split = app.SplitPartsEdit.Value;
v_Interval = app.SplitIntervalEdit.Value;
v_HitDelay = app.HitDelayEdit.Value;
v_RangeR = app.Range_R;
v_RangeDR = app.Range_dR;
% SINGLE LINE CALL - NO CONTINUATION DOTS
[Map_Rounds, Map_Time, Time_S, Time_P, CurveData] = ReloadModel_Heatmap_Kai_I.calculateScaling(v_RangeR, v_RangeDR, v_CurTime, v_ShipBase, v_CatReload, v_Pre, v_Post, v_Fire, v_Skill, v_Final, v_Table, v_Dur, v_CalcMode, v_Split, v_Interval, v_HitDelay);
if strcmp(app.HeatmapSwitch.Value, '串行')
ZData = Map_Rounds.S; TData = Map_Time.S;
else
ZData = Map_Rounds.P; TData = Map_Time.P;
end
[X, Y] = meshgrid(app.Range_dR, app.Range_R);
cla(app.Ax);
% 1. Draw Surface
s = surf(app.Ax, X, Y, ZData);
s.UserData = TData; % Store the timeline data in the object
set(s, 'PickableParts', 'visible', 'HitTest', 'on');
view(app.Ax, 2);
shading(app.Ax, 'flat');
axis(app.Ax, 'tight');
hold(app.Ax, 'on');
% 2. Configure Tooltip (The Safe Way)
% Rename existing rows and apply Bold format (\\bf)
s.DataTipTemplate.DataTipRows(1).Label = '新增装填';
s.DataTipTemplate.DataTipRows(1).Format = '\\bf%g';
s.DataTipTemplate.DataTipRows(2).Label = '船坞装填';
s.DataTipTemplate.DataTipRows(2).Format = '\\bf%g';
s.DataTipTemplate.DataTipRows(3).Label = '有效轮次';
s.DataTipTemplate.DataTipRows(3).Format = '\\bf%.2f';
% Append custom row referencing UserData
% MATLAB automatically maps (i,j) of UserData to the surface point
row4 = dataTipTextRow('理论射速(s)', 'UserData', '\\bf%.3f');
s.DataTipTemplate.DataTipRows(end+1) = row4;
s.DataTipTemplate.Interpreter = 'tex';
% 3. Add Overlays (Ghost Mode)
% Red Star
plot3(app.Ax, 0, app.ShipBaseReloadEdit.Value, max(ZData(:))+10, ...
'p', 'MarkerSize', 14, 'MarkerEdgeColor', 'r', ...
'MarkerFaceColor', 'r', 'LineWidth', 1.5, ...
'PickableParts', 'none', 'HitTest', 'off');
% Contours
[C, h_cont] = contour3(app.Ax, X, Y, ZData + 0.2, ...
'LineColor', 'k', 'ShowText', 'off', ...
'PickableParts', 'none', 'HitTest', 'off');
clabel(C, h_cont, 'Color', 'w', 'FontSize', 9, ...
'FontWeight', 'bold', 'LabelSpacing', 300);
hold(app.Ax, 'off');
% Restore Colorbar
cb = colorbar(app.Ax);
cb.Label.String = '有效总轮次 (Total Rounds)';
% 4. Update Time Domain Plot (BuffPreviewAx)
% Calculate specific fire timings AND visual equivalent curve
v_HitDelay = app.HitDelayEdit.Value;
[FireTimes, VisualCurve] = ReloadModel_Heatmap_Kai_I.runSingleTrace(v_CurTime, v_ShipBase, v_CatReload, v_Pre, v_Post, v_Fire, v_Skill, v_Final, v_Table, v_Dur, app.HeatmapSwitch.Value, v_HitDelay);
cla(app.BuffPreviewAx);
hold(app.BuffPreviewAx, 'on');
% Draw Area (Integration Metaphor: Area = Progress)
% Use VisualCurve (includes Round Buff equivalent speed)
ar = area(app.BuffPreviewAx, VisualCurve.T, VisualCurve.Spd);
ar.FaceColor = [0.0 0.45 0.74]; % MATLAB Blue
ar.FaceAlpha = 0.15;
ar.EdgeColor = [0.0 0.45 0.74];
ar.LineWidth = 1.5;
% Configure Tooltip for the Area Plot
ar.DataTipTemplate.DataTipRows(1).Label = '当前时间';
ar.DataTipTemplate.DataTipRows(1).Format = '%.2fs';
ar.DataTipTemplate.DataTipRows(2).Label = '实时速率';
ar.DataTipTemplate.DataTipRows(2).Format = '%.2f';
% Draw Vertical Stems for Fire Events
for k = 1:length(FireTimes)
ft = FireTimes(k);
if ft <= v_Dur
% Interpolate speed at exact fire time
spd_at_fire = interp1(VisualCurve.T, VisualCurve.Spd, ft, 'linear', 'extrap');
% Draw vertical dashed line up to the curve only
plot(app.BuffPreviewAx, [ft ft], [0 spd_at_fire], 'r--', 'LineWidth', 1, 'PickableParts', 'none', 'HitTest', 'off');
% Draw Marker at intersection
p = plot(app.BuffPreviewAx, ft, spd_at_fire, 'ro', 'MarkerFaceColor', 'r', 'MarkerSize', 5);
% Custom Tooltip for the marker
p.DataTipTemplate.DataTipRows(1).Label = '开火时间';
p.DataTipTemplate.DataTipRows(2).Label = '等效速率';
end
end
grid(app.BuffPreviewAx, 'on');
title(app.BuffPreviewAx, '等效装填速率系数 与 开火时点');
xlabel(app.BuffPreviewAx, '时间 (s)');
ylabel(app.BuffPreviewAx, '等效装填速率');
xlim(app.BuffPreviewAx, [0, v_Dur]);
% Manually set limits (Robust against auto-scaling glitches)
max_spd = max(VisualCurve.Spd);
upper_lim = max(max_spd * 1.15, 1.2);
ylim(app.BuffPreviewAx, [0, upper_lim]);
hold(app.BuffPreviewAx, 'off');
end
function showTimelineReport(app)
% Extract Local Variables
v_CurTime = app.CurrentTimeEdit.Value;
v_ShipBase = app.ShipBaseReloadEdit.Value;
v_CatReload = app.CatReloadEdit.Value;
v_Pre = app.PreCastEdit.Value;
v_Post = app.PostCastEdit.Value;
v_Fire = app.FireDurEdit.Value;
v_Skill = app.SkillTotalEdit.Value;
v_Final = app.FinalCDREdit.Value;
v_Table = app.BuffTable.Data;
v_Dur = app.SimDurationEdit.Value;
v_MapSwitch = app.HeatmapSwitch.Value;
v_CalcMode = app.CalcModeSwitch.Value;
v_Split = app.SplitPartsEdit.Value;
v_Interval = app.SplitIntervalEdit.Value;
v_HitDelay = app.HitDelayEdit.Value;
% SINGLE LINE CALL - NO CONTINUATION DOTS
Report = ReloadModel_Heatmap_Kai_I.generateTimelineReport(v_CurTime, v_ShipBase, v_CatReload, v_Pre, v_Post, v_Fire, v_Skill, v_Final, v_Table, v_Dur, v_MapSwitch, v_CalcMode, v_Split, v_Interval, v_HitDelay);
app.LogTextArea.Value = Report;
end
end
end
(文件2,计算模块)
classdef ReloadModel_Heatmap_Kai_I
methods (Static)
function vec = parseInput(textVal)
try
textVal = strtrim(textVal);
if startsWith(textVal, 'loop:', 'IgnoreCase', true)
patternStr = extractAfter(textVal, 'loop:');
patternVec = str2num(patternStr); %#ok<ST2NM>
if isempty(patternVec), vec = 0; return; end
vec = repmat(patternVec, 1, 500);
else
vec = str2num(textVal); %#ok<ST2NM>
if isempty(vec), vec = 0; end
end
catch
vec = 0;
end
end
% ============================================================
% Main Calculation Entry Point
% ============================================================
function [Map_Rounds, Map_Time, Time_S, Time_P, CurveData] = calculateScaling(Range_BaseR, Range_AddR, CurrentPanelTime, ...
ShipBaseReload, CatReload, PreCast, PostCast, FireDuration, TotalSkillPct, FinalCDR_Pct, ...
BuffTableData, SimDuration, CalcMode, SplitParts, SplitInterval, HitDelay)
% BuffTableData:
% Legacy: Numeric [Start, End, CDR, Rld]
% Modern: Cell {Mode, Start, End, CDR, Rld}
Buffs_Time = [];
Buffs_Round = [];
% 1. Parse Buff Table (Split into Time/Round)
if ~isempty(BuffTableData)
if iscell(BuffTableData)
% New 5-Column Format
Modes = BuffTableData(:,1);
Vals = cell2mat(BuffTableData(:, 2:5)); % [Start, End, CDR, Rld]
% Filter Time Mode
IsTime = contains(Modes, '时间');
if any(IsTime)
Buffs_Time = Vals(IsTime, :);
end
% Filter Round Mode
IsRound = contains(Modes, '轮次');
if any(IsRound)
Buffs_Round = Vals(IsRound, :);
end
else
% Legacy Numeric Format (Assume Time)
Buffs_Time = BuffTableData;
end
end
% 2. Data Cleaning
Range_BaseR = reshape(Range_BaseR, 1, []);
Range_AddR = reshape(Range_AddR, 1, []);
% 3. Reference Data (Dock)
Ref_T_Dock = CurrentPanelTime;
Ref_R_Dock = ShipBaseReload;
Valid_Ref_R_Dock = max(Ref_R_Dock + 100, 0.0001);
% 4. Grid Generation
[X_Add, Y_Base] = meshgrid(Range_AddR, Range_BaseR);
% 5. Calculate R_Target Matrix (Base + Cat + Add)
R_Target_Base = (Y_Base + CatReload + X_Add);
Valid_R_Target_Base = max(R_Target_Base + 100, 0.0001);
% 6. Calculate Raw Paper Time Matrix (No Skills)
T_Paper_Trgt_Raw = Ref_T_Dock .* sqrt(Valid_Ref_R_Dock ./ Valid_R_Target_Base);
% 7. Simulation (Pass separated buffs)
[Rounds_S, AvgTime_S, CurveData] = ReloadModel_Heatmap_Kai_I.simulateTimeline(...
T_Paper_Trgt_Raw, PreCast, PostCast, FireDuration, ...
TotalSkillPct, FinalCDR_Pct, Buffs_Time, Buffs_Round, ...
SimDuration, 'S', CalcMode, SplitParts, SplitInterval, HitDelay);
[Rounds_P, AvgTime_P, ~] = ReloadModel_Heatmap_Kai_I.simulateTimeline(...
T_Paper_Trgt_Raw, PreCast, PostCast, FireDuration, ...
TotalSkillPct, FinalCDR_Pct, Buffs_Time, Buffs_Round, ...
SimDuration, 'P', CalcMode, SplitParts, SplitInterval, HitDelay);
Map_Rounds = struct('S', Rounds_S, 'P', Rounds_P);
Map_Time = struct('S', AvgTime_S, 'P', AvgTime_P);
Time_S = AvgTime_S;
Time_P = AvgTime_P;
end
% ============================================================
% Timeline Simulator (Matrix Interpolation + Dynamic Buffs)
% ============================================================
function [Total_Rounds, Avg_Rate, CurveData] = simulateTimeline(T_Paper_Raw, Pre, Post, Fire, Perm_Rld_Pct, Perm_CDR_Pct, Buffs_Time, Buffs_Round, Duration, Mode, CalcMode, SplitParts, SplitInterval, HitDelay)
[rows, cols] = size(T_Paper_Raw);
Num_Pixels = rows * cols;
T_Raw_Flat = T_Paper_Raw(:);
Max_Shots = 500;
% -- 1. Build Global Speed Function v(t) (Time-based Buffs ONLY) --
T_Axis = 0 : 0.05 : (Duration * 2);
if T_Axis(end) < Duration * 2, T_Axis(end+1) = Duration*2; end
% Base Curves (Permanent Stats)
% CDR Logic: Additive (Negative Input = Reduction)
% e.g. -15% CDR -> Factor 0.85
Rld_Curve = ones(size(T_Axis)) * (1 + Perm_Rld_Pct/100);
CDR_Curve = ones(size(T_Axis)) * (1 + Perm_CDR_Pct/100);
% Apply Time-based Buffs [Start, End, CDR, Rld]
if ~isempty(Buffs_Time)
for i = 1:size(Buffs_Time, 1)
tStart = Buffs_Time(i, 1);
tEnd = Buffs_Time(i, 2);
valCDR = Buffs_Time(i, 3);
valRld = Buffs_Time(i, 4);
mask = (T_Axis >= tStart) & (T_Axis < tEnd);
if valRld ~= 0, Rld_Curve(mask) = Rld_Curve(mask) + (valRld/100); end
% CDR: Add value
if valCDR ~= 0, CDR_Curve(mask) = CDR_Curve(mask) + (valCDR/100); end
end
end
CDR_Curve(CDR_Curve < 0.1) = 0.1;
Rld_Curve(Rld_Curve < 0.1) = 0.1;
Speed_Curve = sqrt(Rld_Curve) ./ CDR_Curve;
% Integral of Speed -> Effective Progress (Global Time Flow)
Progress_Curve = cumtrapz(T_Axis, Speed_Curve);
% Pack Curve Data for Visualization
CurveData = struct('T', T_Axis, 'Spd', Speed_Curve, 'Prg', Progress_Curve);
% -- 2. Simulation Loop (Round-based Buffs applied here) --
Cum_Times = zeros(Num_Pixels, Max_Shots);
Current_Time_Flat = zeros(Num_Pixels, 1);
if strcmp(Mode, 'P') || strcmp(Mode, '并行')
ActionTime = Post + Fire;
else
ActionTime = 0;
end
for k = 1:Max_Shots
% A. Determine Round-based Modifiers for this Shot (k)
Rld_Mod_Round = 0;
CDR_Mod_Round = 0;
if ~isempty(Buffs_Round)
% Filter buffs active for round k
% Col 1: StartR, Col 2: EndR, Col 3: CDR, Col 4: Rld
RoundMask = (Buffs_Round(:,1) <= k) & (Buffs_Round(:,2) >= k);
if any(RoundMask)
Rld_Mod_Round = sum(Buffs_Round(RoundMask, 4));
CDR_Mod_Round = sum(Buffs_Round(RoundMask, 3));
end
end
% B. Calculate Effective Required Time for this specific shot
Base_Rld_Factor = sqrt(1 + Perm_Rld_Pct/100);
Total_Rld_Factor = sqrt(1 + (Perm_Rld_Pct + Rld_Mod_Round)/100);
% Multiplier to shrink T_Raw based on added Rld stats
Rld_Multiplier = Total_Rld_Factor / Base_Rld_Factor;
% Multiplier for CDR
% Additive: 1 + (-15/100) = 0.85
CDR_Multiplier = (1 + CDR_Mod_Round/100);
% Adjusted Requirement for this round
T_Step_Target = T_Raw_Flat ./ Rld_Multiplier .* CDR_Multiplier;
% C. Solve Integral (Standard Logic)
T_start = Current_Time_Flat;
P_start = interp1(T_Axis, Progress_Curve, T_start, 'linear', 'extrap');
P_target = P_start + T_Step_Target;
T_end_load = interp1(Progress_Curve, T_Axis, P_target, 'linear', 'extrap');
Actual_Load_Time = max(T_end_load - T_start, 0);
% D. Cycle
if strcmp(Mode, 'P') || strcmp(Mode, '并行')
Cycle_Time = Pre + max(Actual_Load_Time, ActionTime);
else
Cycle_Time = Pre + Actual_Load_Time + Post + Fire;
end
Current_Time_Flat = Current_Time_Flat + Cycle_Time;
Cum_Times(:, k) = Current_Time_Flat;
end
% -- 3. Results --
Full_Rounds = sum(Cum_Times <= Duration, 2);
Avg_Cycle_Time = zeros(Num_Pixels, 1);
Has_Completed_Shots = Full_Rounds > 0;
if any(Has_Completed_Shots)
idx_last = sub2ind(size(Cum_Times), find(Has_Completed_Shots), Full_Rounds(Has_Completed_Shots));
Time_Used_Last = Cum_Times(idx_last);
Avg_Cycle_Time(Has_Completed_Shots) = Time_Used_Last ./ Full_Rounds(Has_Completed_Shots);
end
if any(~Has_Completed_Shots)
Avg_Cycle_Time(~Has_Completed_Shots) = Cum_Times(~Has_Completed_Shots, 1);
end
if strcmp(CalcMode, '平滑模型')
Has_Shots = Full_Rounds > 0;
Fractional = zeros(Num_Pixels, 1);
if any(Has_Shots)
% Calculate fractional progress for the INCOMPLETE round
% Need to apply NEXT round's buffs to predict T_Cycle_Next
% Vectorized "Next Round" Logic
Row_I = find(Has_Shots);
Col_I = Full_Rounds(Row_I) + 1; % The next round index (k+1)
% Current End Time
idx_curr = sub2ind(size(Cum_Times), Row_I, Col_I-1);
T_End_Curr = Cum_Times(idx_curr);
Rem_Time = Duration - T_End_Curr;
% We need T_Cycle_Next for each pixel.
% Since T_Cycle_Next depends on Round Buffs for (k+1), we can't just take Cum_Times(:, k+1) - Cum_Times(:, k)
% because Cum_Times is calculated up to Max_Shots, which usually covers it.
% Checking if Max_Shots covers k+1
Valid_Next = Col_I <= Max_Shots;
% Filter valid ones
Row_I = Row_I(Valid_Next);
Col_I = Col_I(Valid_Next);
Rem_Time = Rem_Time(Valid_Next);
if ~isempty(Row_I)
idx_next = sub2ind(size(Cum_Times), Row_I, Col_I);
idx_curr_valid = sub2ind(size(Cum_Times), Row_I, Col_I-1);
T_Cycle_Next = Cum_Times(idx_next) - Cum_Times(idx_curr_valid);
T_Cycle_Next(T_Cycle_Next < 0.001) = 0.001;
Fractional(Row_I) = Rem_Time ./ T_Cycle_Next;
end
end
Total_Rounds = Full_Rounds + Fractional;
else
% Staircase
Score_Base = Full_Rounds;
Partial_Score = zeros(Num_Pixels, 1);
Check_Mask = Full_Rounds < Max_Shots;
if any(Check_Mask)
Check_Idx = Full_Rounds(Check_Mask) + 1;
idx_fire = sub2ind(size(Cum_Times), find(Check_Mask), Check_Idx);
T_Fire_End = Cum_Times(idx_fire);
for j = 1:SplitParts
Part_Delay = HitDelay + (j-1) * SplitInterval;
Part_Hit_Time = T_Fire_End + Part_Delay;
Hit_Mask = Part_Hit_Time <= Duration;
Partial_Score(Check_Mask) = Partial_Score(Check_Mask) + Hit_Mask .* (1/SplitParts);
end
end
Total_Rounds = Score_Base + Partial_Score;
end
Total_Rounds = reshape(Total_Rounds, rows, cols);
Avg_Rate = reshape(Avg_Cycle_Time, rows, cols);
end
% ============================================================
% Single Trace Calculator (For Visualization)
% ============================================================
function [FireTimes, VisualCurve] = runSingleTrace(CurrentPanelTime, ShipBaseReload, CatReload, PreCast, PostCast, FireDuration, TotalSkillPct, FinalCDR_Pct, ...
BuffTableData, SimDuration, Mode, HitDelay)
% 1. Parse Buffs
Buffs_Time = []; Buffs_Round = [];
if ~isempty(BuffTableData)
if iscell(BuffTableData)
Modes = BuffTableData(:,1);
Vals = cell2mat(BuffTableData(:, 2:5));
IsTime = contains(Modes, '时间');
IsRound = contains(Modes, '轮次');
if any(IsTime), Buffs_Time = Vals(IsTime, :); end
if any(IsRound), Buffs_Round = Vals(IsRound, :); end
else
Buffs_Time = BuffTableData;
end
end
% 2. Base Calculation
Ref_T_Dock = CurrentPanelTime;
Ref_R_Dock = ShipBaseReload;
R_Current_Base = (ShipBaseReload + CatReload);
Valid_Ref_R = max(Ref_R_Dock + 100, 0.0001);
Valid_R_Cur = max(R_Current_Base + 100, 0.0001);
T_Paper_Raw = Ref_T_Dock .* sqrt(Valid_Ref_R ./ Valid_R_Cur);
% 3. Build Speed Curve (For Integral)
T_Axis = 0 : 0.05 : (SimDuration * 2);
if T_Axis(end) < SimDuration * 2, T_Axis(end+1) = SimDuration*2; end
Rld_Curve = ones(size(T_Axis)) * (1 + TotalSkillPct/100);
CDR_Curve = ones(size(T_Axis)) * (1 + FinalCDR_Pct/100);
if ~isempty(Buffs_Time)
for i = 1:size(Buffs_Time, 1)
tStart = Buffs_Time(i, 1);
tEnd = Buffs_Time(i, 2);
valCDR = Buffs_Time(i, 3);
valRld = Buffs_Time(i, 4);
mask = (T_Axis >= tStart) & (T_Axis < tEnd);
if valRld ~= 0, Rld_Curve(mask) = Rld_Curve(mask) + (valRld/100); end
if valCDR ~= 0, CDR_Curve(mask) = CDR_Curve(mask) + (valCDR/100); end
end
end
CDR_Curve(CDR_Curve < 0.1) = 0.1;
Rld_Curve(Rld_Curve < 0.1) = 0.1;
Speed_Curve = sqrt(Rld_Curve) ./ CDR_Curve;
Progress_Curve = cumtrapz(T_Axis, Speed_Curve);
% Initialize Visual Curve (Copy of Base Speed)
Visual_Speed = Speed_Curve;
% 4. Simulation Loop
if strcmp(Mode, '并行') || strcmp(Mode, 'P'), ActionTime = PostCast+FireDuration;
else, ActionTime = 0; end
Current_Time = 0;
FireTimes = [];
Max_Shots = 200;
for k = 1:Max_Shots
% Round Buffs
Rld_Mod_Round = 0;
CDR_Mod_Round = 0;
if ~isempty(Buffs_Round)
RoundMask = (Buffs_Round(:,1) <= k) & (Buffs_Round(:,2) >= k);
if any(RoundMask)
Rld_Mod_Round = sum(Buffs_Round(RoundMask, 4));
CDR_Mod_Round = sum(Buffs_Round(RoundMask, 3));
end
end
Base_Rld_Factor = sqrt(1 + TotalSkillPct/100);
Total_Rld_Factor = sqrt(1 + (TotalSkillPct + Rld_Mod_Round)/100);
Rld_Multiplier = Total_Rld_Factor / Base_Rld_Factor;
CDR_Multiplier = (1 + CDR_Mod_Round/100);
Req_Time = T_Paper_Raw ./ Rld_Multiplier .* CDR_Multiplier;
% Solve
P_start = interp1(T_Axis, Progress_Curve, Current_Time, 'linear', 'extrap');
P_target = P_start + Req_Time;
T_end_load = interp1(Progress_Curve, T_Axis, P_target, 'linear', 'extrap');
Actual_Load = T_end_load - Current_Time;
% VISUALIZATION: Apply Round Buff Equivalent Speed
% If CDR_Multiplier is 0.5 (fast), Effective Speed is x2.
% Apply ONLY during loading phase [Current_Time, Current_Time + Actual_Load]
if CDR_Multiplier ~= 1
Visual_Factor = 1 / max(CDR_Multiplier, 0.01);
VisMask = (T_Axis >= Current_Time) & (T_Axis <= (Current_Time + Actual_Load));
Visual_Speed(VisMask) = Visual_Speed(VisMask) * Visual_Factor;
end
if strcmp(Mode, '并行') || strcmp(Mode, 'P')
T_Cycle = PreCast + max(Actual_Load, ActionTime);
else
T_Cycle = PreCast + Actual_Load + PostCast + FireDuration;
end
T_Finish = Current_Time + T_Cycle;
if T_Finish > SimDuration + 5, break; end
FireTimes(end+1) = T_Finish;
Current_Time = T_Finish;
end
VisualCurve = struct('T', T_Axis, 'Spd', Visual_Speed);
end
% ============================================================
% Timeline Report Generator
% ============================================================
function ReportStr = generateTimelineReport(CurrentPanelTime, ShipBaseReload, CatReload, PreCast, PostCast, FireDuration, TotalSkillPct, FinalCDR_Pct, ...
BuffTableData, SimDuration, Mode, CalcMode, SplitParts, SplitInterval, HitDelay)
% Split Buffs
Buffs_Time = []; Buffs_Round = [];
if ~isempty(BuffTableData)
if iscell(BuffTableData)
Modes = BuffTableData(:,1);
Vals = cell2mat(BuffTableData(:, 2:5));
IsTime = strcmp(Modes, '时间(s)');
IsRound = strcmp(Modes, '轮次(R)');
if any(IsTime), Buffs_Time = Vals(IsTime, :); end
if any(IsRound), Buffs_Round = Vals(IsRound, :); end
else
Buffs_Time = BuffTableData;
end
end
Ref_T_Dock = CurrentPanelTime;
Ref_R_Dock = ShipBaseReload;
R_Current_Base = (ShipBaseReload + CatReload);
Valid_Ref_R = max(Ref_R_Dock + 100, 0.0001);
Valid_R_Cur = max(R_Current_Base + 100, 0.0001);
T_Paper_Raw = Ref_T_Dock .* sqrt(Valid_Ref_R ./ Valid_R_Cur);
% Build Time Speed Curve
T_Axis = 0 : 0.05 : (SimDuration * 2);
if T_Axis(end) < SimDuration * 2, T_Axis(end+1) = SimDuration*2; end
% CDR Logic: Additive (Negative Input = Reduction)
Rld_Curve = ones(size(T_Axis)) * (1 + TotalSkillPct/100);
CDR_Curve = ones(size(T_Axis)) * (1 + FinalCDR_Pct/100);
if ~isempty(Buffs_Time)
for i = 1:size(Buffs_Time, 1)
tStart = Buffs_Time(i, 1);
tEnd = Buffs_Time(i, 2);
valCDR = Buffs_Time(i, 3);
valRld = Buffs_Time(i, 4);
mask = (T_Axis >= tStart) & (T_Axis < tEnd);
if valRld ~= 0, Rld_Curve(mask) = Rld_Curve(mask) + (valRld/100); end
% CDR: Add value
if valCDR ~= 0, CDR_Curve(mask) = CDR_Curve(mask) + (valCDR/100); end
end
end
CDR_Curve(CDR_Curve < 0.1) = 0.1;
Rld_Curve(Rld_Curve < 0.1) = 0.1;
Speed_Curve = sqrt(Rld_Curve) ./ CDR_Curve;
Progress_Curve = cumtrapz(T_Axis, Speed_Curve);
if strcmp(Mode, '并行') || strcmp(Mode, 'P'), ModeStr = '并行 (P)'; ActionTime = PostCast+FireDuration;
else, ModeStr = '串行 (S)'; ActionTime = 0; end
ReportStr = sprintf('=== 射击时间轴报告 (Hybrid Mode) ===\n');
ReportStr = [ReportStr sprintf('Mode: %s | Calc: %s\n', ModeStr, CalcMode)];
ReportStr = [ReportStr sprintf('RawLoad: %.3fs | SimDur: %.1fs\n', T_Paper_Raw, SimDuration)];
ReportStr = [ReportStr sprintf('PermRld: %+g%% | PermCDR: %+g%%\n', TotalSkillPct, FinalCDR_Pct)];
ReportStr = [ReportStr sprintf('--------------------------------------------------\n')];
ReportStr = [ReportStr sprintf('| R# | Load(s) | Cycle(s) | Finish | Mods | HitTimes\n')];
Current_Time = 0;
Total_Effective = 0;
Max_Shots = 200;
for k = 1:Max_Shots
% Apply Round Buffs
Rld_Mod_Round = 0;
CDR_Mod_Round = 0;
Mods_Str = '';
if ~isempty(Buffs_Round)
RoundMask = (Buffs_Round(:,1) <= k) & (Buffs_Round(:,2) >= k);
if any(RoundMask)
Rld_Mod_Round = sum(Buffs_Round(RoundMask, 4));
CDR_Mod_Round = sum(Buffs_Round(RoundMask, 3));
if Rld_Mod_Round~=0, Mods_Str=[Mods_Str sprintf('Rld%+d%%(R) ', Rld_Mod_Round)]; end
if CDR_Mod_Round~=0, Mods_Str=[Mods_Str sprintf('CDR%+d%%(R) ', CDR_Mod_Round)]; end
end
end
% Apply Time Buffs (Visual Reporting Only)
% Check if Current_Time falls within any Time Buff interval
if ~isempty(Buffs_Time)
for i = 1:size(Buffs_Time, 1)
tStart = Buffs_Time(i, 1);
tEnd = Buffs_Time(i, 2);
if (Current_Time >= tStart) && (Current_Time < tEnd)
valCDR = Buffs_Time(i, 3);
valRld = Buffs_Time(i, 4);
if valRld~=0, Mods_Str=[Mods_Str sprintf('Rld%+d%%(T) ', valRld)]; end
if valCDR~=0, Mods_Str=[Mods_Str sprintf('CDR%+d%%(T) ', valCDR)]; end
end
end
end
% Adjust Req Time
Base_Rld_Factor = sqrt(1 + TotalSkillPct/100);
Total_Rld_Factor = sqrt(1 + (TotalSkillPct + Rld_Mod_Round)/100);
Rld_Multiplier = Total_Rld_Factor / Base_Rld_Factor;
% Multiplier for CDR
% Additive: 1 + (-15/100) = 0.85
CDR_Multiplier = (1 + CDR_Mod_Round/100);
Req_Time = T_Paper_Raw ./ Rld_Multiplier .* CDR_Multiplier;
% Solve Integral
P_start = interp1(T_Axis, Progress_Curve, Current_Time, 'linear', 'extrap');
P_target = P_start + Req_Time;
T_end_load = interp1(Progress_Curve, T_Axis, P_target, 'linear', 'extrap');
Actual_Load = T_end_load - Current_Time;
if strcmp(Mode, '并行') || strcmp(Mode, 'P')
T_Cycle = PreCast + max(Actual_Load, ActionTime);
else
T_Cycle = PreCast + Actual_Load + PostCast + FireDuration;
end
T_Finish = Current_Time + T_Cycle;
Hit_Str = '';
Parts_Hit = 0;
for j = 1:SplitParts
H_Time = T_Finish + HitDelay + (j-1)*SplitInterval;
if H_Time <= SimDuration
Hit_Str = [Hit_Str sprintf('%.1f ', H_Time)];
Parts_Hit = Parts_Hit + 1;
else
Hit_Str = [Hit_Str sprintf('(%.1f) ', H_Time)];
end
end
Current_Time = T_Finish;
if Parts_Hit == 0 && strcmp(CalcMode, '阶梯模型')
ReportStr = [ReportStr sprintf('|%02d | %.3f | %.3f | %.2f | %s | Timeout\n', k, Actual_Load, T_Cycle, T_Finish, Mods_Str)];
break;
elseif T_Finish > SimDuration && strcmp(CalcMode, '平滑模型')
Time_Used = Current_Time - T_Cycle;
Frac = (SimDuration - Time_Used) / T_Cycle;
Total_Effective = (k-1) + Frac;
ReportStr = [ReportStr sprintf('|%02d | %.3f | %.3f | >%.2f | %s | +%.2f (Smooth)\n', k, Actual_Load, T_Cycle, SimDuration, Mods_Str, Frac)];
break;
else
ReportStr = [ReportStr sprintf('|%02d | %.3f | %.3f | %.2f | %s | %s\n', k, Actual_Load, T_Cycle, T_Finish, Mods_Str, Hit_Str)];
Total_Effective = Total_Effective + Parts_Hit/SplitParts;
end
end
ReportStr = [ReportStr sprintf('--------------------------------------------------\n')];
ReportStr = [ReportStr sprintf('Total Effective Rounds: %.2f\n', Total_Effective)];
end
end
end

沪公网安备 31011002002714 号