🌟現在、鉄壁 鉄壁ヘッドショットには対応済みです。
鉄壁HSは通常HSと同じダメージになります。LMG及びDMR、チャージライフル、ハンマーポイント弾を除き、すべてのダメージ値が一致していることを確認しています。

「モジュール:WeaponInfobox/Duration」の版間の差分

提供:Apex Data
ナビゲーションに移動 検索に移動
(デュアルシェルに対応)
(物資投下の際にホップアップ「デュアルシェル」を自動で有効化)
タグ: 差し戻し済み
146行目: 146行目:
end
end


local function renderDurationWithStock(stat, duration, stock, canModdedLoader, isSniper)
local function renderDurationWithStock(stat, duration, stock, canModdedLoader, isSniper, isSpecial)
local tbl = mw.html.create('table')
local tbl = mw.html.create('table')
:addClass('intable')
:addClass('intable')
local hasCooldown  = proto.validateTypes(stat.time, CooldownProto)
local hasCooldown  = proto.validateTypes(stat.time, CooldownProto)
local hasDualShell = Hopup.dual_shell.enabled and proto.validateTypes(stat, DualShellProto)
local hasDualShell = (Hopup.dual_shell.enabled or isSpecial) and proto.validateTypes(stat, DualShellProto)
local colspan, rootOpts, secondOpts, thirdOpts
local colspan, rootOpts, secondOpts, thirdOpts
if hasCooldown or hasDualShell or canModdedLoader then
if hasCooldown or hasDualShell or canModdedLoader then
1,102行目: 1,102行目:
node = renderDurationWithoutStock(stat, duration, stock, canModdedLoader, isSpecial)
node = renderDurationWithoutStock(stat, duration, stock, canModdedLoader, isSpecial)
else
else
node = renderDurationWithStock(stat, duration, stock, canModdedLoader, isSniper)
node = renderDurationWithStock(stat, duration, stock, canModdedLoader, isSniper, isSpecial)
end
end

2022年5月19日 (木) 12:33時点における版

このモジュールについての説明文ページを モジュール:WeaponInfobox/Duration/doc に作成できます

require('Module:Utility/mw.html Extensions')

local p = {}
local cfg = mw.loadData('Module:WeaponInfobox/configuration')
local attachment = mw.loadData('Module:Stat/Attachment')
local Hopup = mw.loadData('Module:Stat/Hopup')

local aw = require('Module:Utility/Library')
local iu = require('Module:Utility/Image')
local nu = require('Module:Utility/Name')
local proto = require('Module:Utility/Prototypes')

-- Constants
local leftBorder = {
	['border-left-width'] = '1px',
	['border-left-style'] = 'solid',
}
local leftBorderWithoutTopRightBorder = {
	['border-top'] = '0 none transparent',
	['border-right'] = '0 none transparent',
	['border-left-width'] = '1px',
	['border-left-style'] = 'solid',
}

-- Protos
local ZoomProto = {
	zoom_in  = proto.NumberRange(0),
	zoom_out = proto.NumberRange(0),
}
local MagazineProto = {
	proto.NumberRange(0),
	proto.NumberRange(0),
	proto.NumberRange(0),
	proto.NumberRange(0),
}
local SegmentReloadProto = {
	reload_segment_loop = proto.NumberRange(0),
	reload_segment_end  = proto.NumberRange(0),
	reload_segment_one  = proto.NumberRange(0),
}
local CooldownProto = {
	cooldown                = proto.NumberRange(0),
	cooldown_delay          = proto.NumberRange(0),
	overheat_cooldown       = proto.NumberRange(0),
	overheat_cooldown_delay = proto.NumberRange(0),
}
local DualShellProto = {
	dual_shell = {
		ammo_size_segmented_reload = proto.NumberRange(1),
	},
}

-- Duration for multiple value
local function createMultipleRow(name, default, cfg, opts)
	opts = opts or {}
	
	local row = cfg.tbl:tag('tr')
	
	if aw.isNumberAndGreaterThanZero(opts.offset) then
		for i = 1, opts.offset do
			local hasLeftBorder = opts.offsetStyles and opts.offsetStyles[i] and opts.offsetStyles[i].leftBorder
			if hasLeftBorder then
				row:tag('th')
					:css(leftBorderWithoutTopRightBorder)
					:wikitext(' ')
			else
				row:tag('th')
					:css('border', '0 none transparent')
					:wikitext(' ')
			end
		end
	end
	
	row:tag('th')
		:attrIf(aw.isNumber(opts.colspan), { colspan = opts.colspan })
		:cssIf(aw.isNumberAndGreaterThanZero(opts.offset), leftBorder)
		:wikitext(name)
	
	if not cfg.isLegendary then
		row
			:tag('td')
				:addClass('cell-type-number')
				:attr('align', 'right')
					:wikitext(string.format(cfg.fmt, default))
				:done()
		
		if cfg.muls then
			row
				:tag('td')
					:addClass('cell-type-number')
					:attr('align', 'right')
					:tag('span')
						:addClass('text-rarity')
						:addClass('text-rarity-common')
						:wikitext(string.format(cfg.fmt, cfg.muls[1] * default))
						:done()
					:done()
				:tag('td')
					:addClass('cell-type-number')
					:attr('align', 'right')
					:tag('span')
						:addClass('text-rarity')
						:addClass('text-rarity-rare')
						:wikitext(string.format(cfg.fmt, cfg.muls[2] * default))
						:done()
					:done()
				:tag('td')
					:addClass('cell-type-number')
					:attr('align', 'right')
					:tag('span')
						:addClass('text-rarity')
						:addClass('text-rarity-epic')
						:wikitext(string.format(cfg.fmt, cfg.muls[3] * default))
						:done()
					:done()
		else
			row:tag('td')
				:addClass('disabled')
				:attr('align', 'center')
				:attr('colspan', 3)
				:wikitext(cfg.res.na)
		end
	else
		row
			:tag('td')
				:addClass('cell-type-number')
				:attr('align', 'right')
				:attr('colspan', 4)
				:tag('span')
					:addClass('text-rarity')
					:addClass('text-rarity-legendary')
					:wikitext(string.format(cfg.fmt, default))
					:done()
				:done()
	end
	
	return row
end

local function calcSegmentedReloadTime(rounds, time)
	if rounds <= 1 then
		return time.reload_segment_one
	else
		return (rounds - 1) * time.reload_segment_loop + time.reload_segment_end
	end
end

local function renderDurationWithStock(stat, duration, stock, canModdedLoader, isSniper, isSpecial)
	local tbl = mw.html.create('table')
		:addClass('intable')
	
	local hasCooldown  = proto.validateTypes(stat.time, CooldownProto)
	local hasDualShell = (Hopup.dual_shell.enabled or isSpecial) and proto.validateTypes(stat, DualShellProto)
	local colspan, rootOpts, secondOpts, thirdOpts
	if hasCooldown or hasDualShell or canModdedLoader then
		colspan    = 3
		rootOpts   = { colspan = 3 }
		secondOpts = { colspan = 2, offset = 1 }
		thirdOpts  = { offset = 2, offsetStyles = { nil, { leftBorder = true } } }
	else
		colspan    = 2
		rootOpts   = { colspan = 2 }
		secondOpts = { offset = 1 }
	end
	
	-- Header
	local stockName = isSniper and 'スナイパーストック' or '標準ストック'
	tbl:tag('tr')
		:tag('th')
			:attr('colspan', colspan)
			:wikitext(name)
			:done()
		:tag('th'):done()
		:tag('th'):wikitext(iu.attachment(stockName, 1)):done()
		:tag('th'):wikitext(iu.attachment(stockName, 2)):done()
		:tag('th'):wikitext(iu.attachment(stockName, 3))
	
	-- Segmented Reload
	if proto.validateTypes(stat.time, SegmentReloadProto) then
		local reloadSegmentConfig = {
			fmt  = duration.reload.format,
			tbl  = tbl,
			muls = stock.reload_segment,
			res  = duration,
		}
		
		if proto.validateTypes(stat.magazine, MagazineProto) then
			local magName  = nu.extmag(stat.ammo, 'ja')
			local baseTime = calcSegmentedReloadTime(stat.magazine[1], stat.time)
			createMultipleRow(
				duration.reload.name,
				baseTime,
				reloadSegmentConfig,
				rootOpts)
			
			local lvl1Time = calcSegmentedReloadTime(stat.magazine[2], stat.time)
			createMultipleRow(
				iu.attachment(magName, 1),
				lvl1Time,
				reloadSegmentConfig,
				secondOpts)
			
			local lvl2Time = calcSegmentedReloadTime(stat.magazine[3], stat.time)
			createMultipleRow(
				iu.attachment(magName, 2),
				lvl2Time,
				reloadSegmentConfig,
				secondOpts)
			
			local lvl3Time = calcSegmentedReloadTime(stat.magazine[4], stat.time)
			createMultipleRow(
				iu.attachment(magName, 3) .. iu.attachment(magName, 4),
				lvl3Time,
				reloadSegmentConfig,
				secondOpts)
			
			-- [Hop-up] Dual Shell
			if hasDualShell then
				local virtualBaseAmmoSize = math.ceil(stat.magazine[1] / stat.dual_shell.ammo_size_segmented_reload)
				local dualShellBaseTime = calcSegmentedReloadTime(virtualBaseAmmoSize, stat.time)
				createMultipleRow(
					iu.hopup('dual_shell'),
					dualShellBaseTime,
					reloadSegmentConfig,
					secondOpts)
				
				local virtualLvl1AmmoSize = math.ceil(stat.magazine[2] / stat.dual_shell.ammo_size_segmented_reload)
				local dualShellLvl1Time = calcSegmentedReloadTime(virtualLvl1AmmoSize, stat.time)
				createMultipleRow(
					iu.attachment(magName, 1),
					dualShellLvl1Time,
					reloadSegmentConfig,
					thirdOpts)
				
				local virtualLvl2AmmoSize = math.ceil(stat.magazine[3] / stat.dual_shell.ammo_size_segmented_reload)
				local dualShellLvl2Time = calcSegmentedReloadTime(virtualLvl2AmmoSize, stat.time)
				createMultipleRow(
					iu.attachment(magName, 2),
					dualShellLvl2Time,
					reloadSegmentConfig,
					thirdOpts)
				
				local virtualLvl3AmmoSize = math.ceil(stat.magazine[4] / stat.dual_shell.ammo_size_segmented_reload)
				local dualShellLvl3Time = calcSegmentedReloadTime(virtualLvl3AmmoSize, stat.time)
				createMultipleRow(
					iu.attachment(magName, 3) .. iu.attachment(magName, 4),
					dualShellLvl3Time,
					reloadSegmentConfig,
					thirdOpts)
			end
			
			local reloadSegmentConfigOne = {
				fmt  = duration.reloadsegmentone.format,
				tbl  = tbl,
				muls = stock.reload_segment,
				res  = duration,
			}
			local oneTime = calcSegmentedReloadTime(1, stat.time)
			createMultipleRow(
				duration.reloadsegmentone.name,
				oneTime,
				reloadSegmentConfigOne,
				rootOpts)
		end
	
	-- Cooldown
	elseif hasCooldown then
		local cooldownOverheatConfig = {
			fmt  = duration.cooldownoverheat.format,
			tbl  = tbl,
			muls = stock.reload,
			res  = duration,
		}
		local overheatedTime = stat.time.overheat_cooldown + stat.time.overheat_cooldown_delay
		createMultipleRow(
			duration.cooldownoverheat.name,
			overheatedTime,
			cooldownOverheatConfig,
			rootOpts)
		
		-- [Passive] Modded Loader
		if canModdedLoader then
			local moddedLoaderOverheatedTime = stat.time.overheat_cooldown * 0.75 + stat.time.overheat_cooldown_delay
			createMultipleRow(
				iu.passive('modded_loader'),
				moddedLoaderOverheatedTime,
				cooldownOverheatConfig,
				secondOpts)
		end
		
		local cooldownConfig = {
			fmt  = duration.cooldown.format,
			tbl  = tbl,
			muls = stock.reload,
			res  = duration,
		}
		local baseTime = stat.time.cooldown + stat.time.cooldown_delay
		local moddedLoaderTime = stat.time.cooldown * 0.75 + stat.time.cooldown_delay
		for _, rate in ipairs(duration.cooldown.rates) do
			createMultipleRow(
				string.format(duration.cooldown.name, 100 * rate),
				rate * baseTime,
				cooldownConfig,
				secondOpts)
			
			-- [Passive] Modded Loader
			if canModdedLoader then
				createMultipleRow(
					iu.passive('modded_loader'),
					rate * moddedLoaderTime,
					cooldownConfig,
					thirdOpts)
			end
		end
	
	-- Reload
	elseif aw.isNumber(stat.time.reload) then
		local reloadConfig = {
			fmt  = duration.reload.format,
			tbl  = tbl,
			muls = stock.reload,
			res  = duration,
		}
		createMultipleRow(duration.reload.name, stat.time.reload, reloadConfig, rootOpts)
		
		-- [Passive] Modded Loader
		if canModdedLoader then
			createMultipleRow(
				iu.passive('modded_loader'),
				stat.time.reload * 0.75,
				reloadConfig,
				secondOpts)
		end
		
		-- [Hop-up] Boosted Loader
		if stat.boosted_loader and aw.isNumberAndGreaterThanZero(stat.boosted_loader.reloadfast) then
			local reloadFastConfig = {
				fmt  = duration.reload.format,
				tbl  = tbl,
				res  = duration,
				isLegendary = true,
			}
			createMultipleRow(
				iu.hopup('boosted_loader'),
				stat.boosted_loader.reloadfast,
				reloadFastConfig,
				secondOpts)
		end
		
		-- [Hop-up] Splatter Rounds
		local reloadSplatterConfig = {
			fmt  = duration.reload.format,
			tbl  = tbl,
			res  = duration,
			isLegendary = true,
		}
		createMultipleRow(
			iu.hopup('splatter_rounds'),
			stat.time.reload * stock.reload[3] * 0.75,
			reloadSplatterConfig,
			secondOpts)
		
		-- [Passive] Modded Loader & [Hop-up] Splatter Rounds
		if canModdedLoader then
			createMultipleRow(
				iu.passive('modded_loader'),
				stat.time.reload * stock.reload[3] * 0.5625,
				reloadSplatterConfig,
				thirdOpts)
		end
		
		-- Reload (Empty)
		if aw.isNumber(stat.time.reloadempty) and stat.time.reload ~= stat.time.reloadempty then
			local reloadEmptyConfig = {
				fmt  = duration.reloadempty.format,
				tbl  = tbl,
				muls = stock.reload,
				res  = duration,
			}
			createMultipleRow(duration.reloadempty.name, stat.time.reloadempty, reloadEmptyConfig, rootOpts)
			
			-- [Passive] Modded Loader
			if canModdedLoader then
				createMultipleRow(
					iu.passive('modded_loader'),
					stat.time.reloadempty * 0.75,
					reloadEmptyConfig,
					secondOpts)
			end
			
			-- [Hop-up] Splatter Rounds
			local reloadEmptySplatterConfig = {
				fmt  = duration.reloadempty.format,
				tbl  = tbl,
				res  = duration,
				isLegendary = true,
			}
			createMultipleRow(
				iu.hopup('splatter_rounds'),
				stat.time.reloadempty * stock.reload[3] * 0.75,
				reloadEmptySplatterConfig,
				secondOpts)
			
			-- [Passive] Modded Loader & [Hop-up] Splatter Rounds
			if canModdedLoader then
				createMultipleRow(
					iu.passive('modded_loader'),
					stat.time.reloadempty * stock.reload[3] * 0.5625,
					reloadEmptySplatterConfig,
					thirdOpts)
			end
		end
	end
	
	-- Deploy
	if aw.isNumber(stat.time.draw) then
		local deployConfig = {
			fmt  = duration.deploy.format,
			tbl  = tbl,
			muls = stock.change,
			res  = duration,
		}
		createMultipleRow(duration.deploy.name, stat.time.draw, deployConfig, rootOpts)
		
		if aw.isNumber(stat.time.deployfirst) then
			local deployFirstConfig = {
				fmt  = duration.deploy.format,
				tbl  = tbl,
				muls = nil,
				res  = duration,
			}
			createMultipleRow(duration.deployfirst.name, stat.time.deployfirst, deployFirstConfig, secondOpts)
		end
	end
	
	-- Holster
	if aw.isNumber(stat.time.holster) then
		local holsterConfig = {
			fmt  = duration.holster.format,
			tbl  = tbl,
			muls = stock.change,
			res  = duration,
		}
		createMultipleRow(duration.holster.name, stat.time.holster, holsterConfig, rootOpts)
	end
	
	-- Raise
	if aw.isNumber(stat.time.upper) then
		local raiseConfig = {
			fmt  = duration.raise.format,
			tbl  = tbl,
			muls = stock.raise,
			res  = duration,
		}
		createMultipleRow(duration.raise.name, stat.time.upper, raiseConfig, rootOpts)
	end
	
	-- Lower
	if aw.isNumber(stat.time.lower) then
		local lowerConfig = {
			fmt  = duration.lower.format,
			tbl  = tbl,
			muls = stock.lower,
			res  = duration,
		}
		createMultipleRow(duration.lower.name, stat.time.lower, lowerConfig, rootOpts)
	end
	
	-- Zoom In
	if aw.isNumber(stat.time.zoom_in) then
		local zoomInConfig = {
			fmt  = duration.zoomin.format,
			tbl  = tbl,
			muls = stock.zoom,
			res  = duration,
		}
		createMultipleRow(duration.zoomin.name, stat.time.zoom_in, zoomInConfig, rootOpts)
		
		-- HCOG Classic/Bruiser, Holosight, 1x Digital Threat
		if proto.validateTypes(stat.time.hcog_classic, ZoomProto) then
			local label
			if proto.validateTypes(stat.time.threat, ZoomProto) then
				label = iu.scope('1倍HCOG\'クラシック\'') .. iu.scope('2倍HCOG\'ブルーザー\'') .. iu.scope('1倍ホロサイト') .. iu.scope('1~2倍可変式ホロサイト') .. iu.scope('1倍デジタルスレット')
			else
				label = iu.scope('1倍HCOG\'クラシック\'') .. iu.scope('2倍HCOG\'ブルーザー\'') .. '<br>' .. iu.scope('1倍ホロサイト') .. iu.scope('1~2倍可変式ホロサイト')
			end
			
			createMultipleRow(label, stat.time.hcog_classic.zoom_in, zoomInConfig, secondOpts)
		end
		
		-- 3x HCOG 'Ranger', 2x-4x Variable AOG
		if proto.validateTypes(stat.time.hcog_ranged, ZoomProto) then
			if proto.validateTypes(stat.time.aog_variable, ZoomProto) then
				if stat.time.hcog_ranged.zoom_in ~= stat.time.aog_variable.zoom_in then
					createMultipleRow(
						iu.scope('3倍HCOG\'レンジャー\''),
						stat.time.hcog_ranged.zoom_in,
						zoomInConfig,
						secondOpts)
					createMultipleRow(
						iu.scope('2~4倍可変式AOG'),
						stat.time.aog_variable.zoom_in,
						zoomInConfig,
						secondOpts)
				else
					createMultipleRow(
						iu.scope('3倍HCOG\'レンジャー\'') .. iu.scope('2~4倍可変式AOG'),
						stat.time.hcog_ranged.zoom_in,
						zoomInConfig,
						secondOpts)
				end
			else
				createMultipleRow(
					iu.scope('3倍HCOG\'レンジャー\''),
					stat.time.hcog_ranged.zoom_in,
					zoomInConfig,
					secondOpts)
			end
		end
		
		-- 6x Sniper Scope
		if proto.validateTypes(stat.time.sniper, ZoomProto) then
			createMultipleRow(
				iu.scope('6倍スナイパー'),
				stat.time.sniper.zoom_in,
				zoomInConfig,
				secondOpts)
		end
		
		-- 4x-8x Variable Sniper Scope
		if proto.validateTypes(stat.time.sniper_variable, ZoomProto) then
			createMultipleRow(
				iu.scope('4~8倍可変式スナイパー'),
				stat.time.sniper_variable.zoom_in,
				zoomInConfig,
				secondOpts)
		end
		
		-- 4x-10x Digital Sniper Threat
		if proto.validateTypes(stat.time.sniper_threat, ZoomProto) then
			createMultipleRow(
				iu.scope('4~10倍デジタルスレット'),
				stat.time.sniper_threat.zoom_in,
				zoomInConfig,
				secondOpts)
		end
	end
	
	-- Zoom Out
	if aw.isNumber(stat.time.zoom_out) then
		local zoomOutConfig = {
			fmt  = duration.zoomout.format,
			tbl  = tbl,
			muls = stock.zoom,
			res  = duration,
		}
		createMultipleRow(duration.zoomout.name, stat.time.zoom_out, zoomOutConfig, rootOpts)
		
		-- HCOG Classic/Bruiser, Holosight, 1x Digital Threat
		if proto.validateTypes(stat.time.hcog_classic, ZoomProto) then
			local label
			if proto.validateTypes(stat.time.threat, ZoomProto) then
				label = iu.scope('1倍HCOG\'クラシック\'') .. iu.scope('2倍HCOG\'ブルーザー\'') .. iu.scope('1倍ホロサイト') .. iu.scope('1~2倍可変式ホロサイト') .. iu.scope('1倍デジタルスレット')
			else
				label = iu.scope('1倍HCOG\'クラシック\'') .. iu.scope('2倍HCOG\'ブルーザー\'') .. '<br>' .. iu.scope('1倍ホロサイト') .. iu.scope('1~2倍可変式ホロサイト')
			end
			
			createMultipleRow(
				label,
				stat.time.hcog_classic.zoom_out,
				zoomOutConfig,
				secondOpts)
		end
		
		-- 3x HCOG 'Ranger', 2x-4x Variable AOG
		if proto.validateTypes(stat.time.hcog_ranged, ZoomProto) then
			if proto.validateTypes(stat.time.aog_variable, ZoomProto) then
				if stat.time.hcog_ranged.zoom_out ~= stat.time.aog_variable.zoom_out then
					createMultipleRow(
						iu.scope('3倍HCOG\'レンジャー\''),
						stat.time.hcog_ranged.zoom_out,
						zoomOutConfig,
						secondOpts)
					createMultipleRow(
						iu.scope('2~4倍可変式AOG'),
						stat.time.aog_variable.zoom_out,
						zoomOutConfig,
						secondOpts)
				else
					createMultipleRow(
						iu.scope('3倍HCOG\'レンジャー\'') .. iu.scope('2~4倍可変式AOG'),
						stat.time.hcog_ranged.zoom_out,
						zoomOutConfig,
						secondOpts)
				end
			else
				createMultipleRow(
					iu.scope('3倍HCOG\'レンジャー\''),
					stat.time.hcog_ranged.zoom_out,
					zoomOutConfig,
					secondOpts)
			end
		end
		
		-- 6x Sniper Scope
		if proto.validateTypes(stat.time.sniper, ZoomProto) then
			createMultipleRow(
				iu.scope('6倍スナイパー'),
				stat.time.sniper.zoom_out,
				zoomOutConfig,
				secondOpts)
		end
		
		-- 4x-8x Variable Sniper Scope
		if proto.validateTypes(stat.time.sniper_variable, ZoomProto) then
			createMultipleRow(
				iu.scope('4~8倍可変式スナイパー'),
				stat.time.sniper_variable.zoom_out,
				zoomOutConfig,
				secondOpts)
		end
		
		-- 4x-10x Digital Sniper Threat
		if proto.validateTypes(stat.time.sniper_threat, ZoomProto) then
			createMultipleRow(
				iu.scope('4~10倍デジタルスレット'),
				stat.time.sniper_threat.zoom_out,
				zoomOutConfig,
				secondOpts)
		end
	end
	
	return tbl
end

-- Duration for single value
local function renderRow(tbl, name, num, cfg, opts)
	opts = opts or {}
	
	if cfg.isSpecial and cfg.muls then
		num = num * cfg.muls[3]
	end
	
	local row = tbl:tag('tr')
		:addClassIf(opts.class and type(opts.class) == 'string', opts.class)
		:attr('role', 'listitem')
		:tag('th')
			:attrIf(opts.headerAlign and type(opts.headerAlign) == 'string', { align = opts.headerAlign })
			:wikitext(name)
			:done()
		:tag('td')
			:addClass('cell-type-number')
			:attr('align', 'left')
			:wikitext(string.format(cfg.fmt, num))
			:done()
		:tag('td')
			:attr('align', 'left')
			:wikitext(cfg.unit)
			:done()
	return row
end

local function renderDurationWithoutStock(stat, duration, stock, canModdedLoader, isSpecial)
	local secondOpts = { class = 'no-list-style', headerAlign = 'right' }
	local tbl = mw.html.create('table')
		:addClass('condensedtable')
		:addClass('listtable')
		:attr('role', 'list')
	
	-- Segmented Reload
	if proto.validateTypes(stat.time, SegmentReloadProto) then
		local reloadSegmentConfig = {
			fmt  = duration.reload.format,
			muls = stock.reload_segment,
			unit = duration.reload.unit,
			isSpecial = isSpecial,
		}
		
		-- with Mags
		if proto.validateTypes(stat.magazine, MagazineProto) then
			local lvl3Time = calcSegmentedReloadTime(stat.magazine[4], stat.time)
			renderRow(
				tbl,
				duration.reload.name .. '&nbsp;',
				lvl3Time,
				reloadSegmentConfig)
			
			-- [Hop-up] Dual Shell
			if Hopup.dual_shell.enabled and proto.validateTypes(stat, DualShellProto) then
				local lvl3VirtualAmmoSize = math.ceil(stat.magazine[4] / stat.dual_shell.ammo_size_segmented_reload)
				local lvl3DualShellTime = calcSegmentedReloadTime(lvl3VirtualAmmoSize, stat.time)
				renderRow(
					tbl,
					iu.hopup('dual_shell') .. '&nbsp;',
					lvl3DualShellTime,
					reloadSegmentConfig,
					secondOpts)
			end
			
			local oneTime = calcSegmentedReloadTime(1, stat.time)
			renderRow(
				tbl,
				duration.reloadsegmentone.name .. '&nbsp;',
				oneTime,
				reloadSegmentConfig)
		
		-- without Mags
		elseif aw.isNumber(stat.magazine) then
			local baseTime = calcSegmentedReloadTime(stat.magazine, stat.time)
			renderRow(
				tbl,
				duration.reload.name .. '&nbsp;',
				baseTime,
				reloadSegmentConfig)
			
			-- [Hop-up] Dual Shell
			if Hopup.dual_shell.enabled and proto.validateTypes(stat, DualShellProto) then
				local virtualAmmoSize = math.ceil(stat.magazine / stat.dual_shell.ammo_size_segmented_reload)
				local dualShellTime = calcSegmentedReloadTime(virtualAmmoSize, stat.time)
				renderRow(
					tbl,
					iu.hopup('dual_shell') .. '&nbsp;',
					dualShellTime,
					reloadSegmentConfig,
					secondOpts)
			end
			
			local oneTime = calcSegmentedReloadTime(1, stat.time)
			renderRow(
				tbl,
				duration.reloadsegmentone.name .. '&nbsp;',
				oneTime,
				reloadSegmentConfig)
		end
	
	-- Reload
	elseif aw.isNumber(stat.time.reload) then
		local reloadConfig = {
			fmt  = duration.reload.format,
			muls = stock.reload,
			unit = duration.reload.unit,
			isSpecial = isSpecial,
		}
		renderRow(
			tbl,
			duration.reload.name .. '&nbsp;',
			stat.time.reload,
			reloadConfig)
		if canModdedLoader then
			renderRow(
				tbl,
				iu.passive('modded_loader') .. '&nbsp;',
				stat.time.reload * 0.75,
				reloadConfig,
				secondOpts)
		end
		if stat.boosted_loader and aw.isNumberAndGreaterThanZero(stat.boosted_loader.reloadfast) then
			renderRow(
				tbl,
				iu.hopup('boosted_loader') .. '&nbsp;',
				stat.boosted_loader.reloadfast,
				reloadConfig,
				secondOpts)
		end
		
		if not isSpecial then
			-- [Hop-up] Splatter Rounds
			local reloadSplatterConfig = {
				fmt  = duration.reload.format,
				unit = duration.reload.unit,
			}
			renderRow(
				tbl,
				iu.hopup('splatter_rounds') .. '&nbsp;',
				stat.time.reload * 0.75,
				reloadSplatterConfig,
				secondOpts)
		end
		
		-- Reload (Empty)
		if aw.isNumber(stat.time.reloadempty) and stat.time.reload ~= stat.time.reloadempty then
			local reloademptyConfig = {
				fmt  = duration.reloadempty.format,
				muls = stock.reload,
				unit = duration.reloadempty.unit,
				isSpecial = isSpecial,
			}
			renderRow(
				tbl,
				duration.reloadempty.name .. '&nbsp;',
				stat.time.reloadempty,
				reloademptyConfig)
			if canModdedLoader then
				renderRow(
					tbl,
					iu.passive('modded_loader') .. '&nbsp;',
					stat.time.reloadempty * 0.75,
					reloademptyConfig,
					secondOpts)
			end
			
			if not isSpecial then
				-- [Hop-up] Splatter Rounds
				local reloademptySplatterConfig = {
					fmt  = duration.reloadempty.format,
					unit = duration.reloadempty.unit,
				}
				renderRow(
					tbl,
					iu.hopup('splatter_rounds') .. '&nbsp;',
					stat.time.reloadempty * 0.75,
					reloademptySplatterConfig,
					secondOpts)
			end
		end
	end
	
	-- Deploy
	if aw.isNumber(stat.time.draw) then
		local deployConfig = {
			fmt  = duration.deploy.format,
			muls = stock.change,
			unit = duration.deploy.unit,
			isSpecial = isSpecial,
		}
		renderRow(
			tbl,
			duration.deploy.name .. '&nbsp;',
			stat.time.draw,
			deployConfig)
		
		if aw.isNumber(stat.time.deployfirst) then
			local deployFirstConfig = {
				fmt  = duration.deployfirst.format,
				unit = duration.deployfirst.unit,
				isSpecial = isSpecial,
			}
			renderRow(
				tbl,
				duration.deployfirst.name .. '&nbsp;',
				stat.time.deployfirst,
				deployFirstConfig,
				secondOpts)
		end
	end
	
	-- Holster
	if aw.isNumber(stat.time.holster) then
		local holsterConfig = {
			fmt  = duration.holster.format,
			muls = stock.change,
			unit = duration.holster.unit,
			isSpecial = isSpecial,
		}
		renderRow(
			tbl,
			duration.holster.name .. '&nbsp;',
			stat.time.holster,
			holsterConfig)
	end
	
	-- Raise
	if aw.isNumber(stat.time.upper) then
		local raiseConfig = {
			fmt  = duration.raise.format,
			muls = stock.raise,
			unit = duration.raise.unit,
			isSpecial = isSpecial,
		}
		renderRow(
			tbl,
			duration.raise.name .. '&nbsp;',
			stat.time.upper,
			raiseConfig)
	end
	
	-- Lower
	if aw.isNumber(stat.time.lower) then
		local lowerConfig = {
			fmt  = duration.lower.format,
			muls = stock.lower,
			unit = duration.lower.unit,
			isSpecial = isSpecial,
		}
		renderRow(
			tbl,
			duration.lower.name .. '&nbsp;',
			stat.time.lower,
			lowerConfig)
	end
	
	-- Zoom In
	if aw.isNumber(stat.time.zoom_in) then
		local zoominConfig = {
			fmt  = duration.zoomin.format,
			muls = stock.zoom,
			unit = duration.zoomin.unit,
			isSpecial = isSpecial,
		}
		renderRow(
			tbl,
			duration.zoomin.name .. '&nbsp;',
			stat.time.zoom_in,
			zoominConfig)
		
		-- HCOG Classic/Bruiser, Holosight, 1x Digital Threat
		if proto.validateTypes(stat.time.hcog_classic, ZoomProto) and stat.time.hcog_classic.zoom_in ~= stat.time.zoom_in then
			local label 
			if proto.validateTypes(stat.time.threat, ZoomProto) then
				label = duration.without_standard_scope
			else
				label = iu.scope('1倍HCOG\'クラシック\'') .. iu.scope('2倍HCOG\'ブルーザー\'') .. iu.scope('1倍ホロサイト') .. iu.scope('1~2倍可変式ホロサイト')
			end
			
			renderRow(
				tbl,
				label .. '&nbsp;',
				stat.time.hcog_classic.zoom_in,
				zoominConfig,
				secondOpts)
		end
		
		-- 3x HCOG 'Ranger', 2x-4x Variable AOG
		if proto.validateTypes(stat.time.hcog_ranged, ZoomProto) then
			if proto.validateTypes(stat.time.aog_variable, ZoomProto) then
				if stat.time.hcog_ranged.zoom_in ~= stat.time.aog_variable.zoom_in then
					renderRow(
						tbl,
						iu.scope('3倍HCOG\'レンジャー\'') .. '&nbsp;',
						stat.time.hcog_ranged.zoom_in,
						zoominConfig,
						secondOpts)
					renderRow(
						tbl,
						iu.scope('2~4倍可変式AOG') .. '&nbsp;',
						stat.time.aog_variable.zoom_in,
						zoominConfig,
						secondOpts)
				else
					renderRow(
						tbl,
						iu.scope('3倍HCOG\'レンジャー\'') .. iu.scope('2~4倍可変式AOG') .. '&nbsp;',
						stat.time.hcog_ranged.zoom_in,
						zoominConfig,
						secondOpts)
				end
			elseif stat.time.hcog_ranged.zoom_in ~= stat.time.zoom_in then
				renderRow(
					tbl,
					iu.scope('3倍HCOG\'レンジャー\'') .. '&nbsp;',
					stat.time.hcog_ranged.zoom_in,
					zoominConfig,
					secondOpts)
			end
		end
		
		-- 6x Sniper Scope
		if proto.validateTypes(stat.time.sniper, ZoomProto) then
			renderRow(
				tbl,
				iu.scope('6倍スナイパー') .. '&nbsp;',
				stat.time.sniper.zoom_in,
				zoominConfig,
				secondOpts)
		end
		
		-- 4x-8x Variable Sniper Scope
		if proto.validateTypes(stat.time.sniper_variable, ZoomProto) then
			renderRow(
				tbl,
				iu.scope('4~8倍可変式スナイパー') .. '&nbsp;',
				stat.time.sniper_variable.zoom_in,
				zoominConfig,
				secondOpts)
		end
		
		-- 4x-10x Digital Sniper Threat
		if proto.validateTypes(stat.time.sniper_threat, ZoomProto) then
			renderRow(
				tbl,
				iu.scope('4~10倍デジタルスレット') .. '&nbsp;',
				stat.time.sniper_threat.zoom_in,
				zoominConfig,
				secondOpts)
		end
	end
	
	-- Zoom Out
	if aw.isNumber(stat.time.zoom_out) then
		local zoomoutConfig = {
			fmt  = duration.zoomout.format,
			muls = stock.zoom,
			unit = duration.zoomout.unit,
			isSpecial = isSpecial,
		}
		renderRow(
			tbl,
			duration.zoomout.name .. '&nbsp;',
			stat.time.zoom_out,
			zoomoutConfig)
		
		-- HCOG Classic/Bruiser, Holosight, 1x Digital Threat
		if proto.validateTypes(stat.time.hcog_classic, ZoomProto) and stat.time.hcog_classic.zoom_out ~= stat.time.zoom_out then
			local label
			if proto.validateTypes(stat.time.threat, ZoomProto) then
				label = duration.without_standard_scope
			else
				label = iu.scope('1倍HCOG\'クラシック\'') .. iu.scope('2倍HCOG\'ブルーザー\'') .. iu.scope('1倍ホロサイト') .. iu.scope('1~2倍可変式ホロサイト')
			end
			
			renderRow(
				tbl,
				label .. '&nbsp;',
				stat.time.hcog_classic.zoom_out,
				zoomoutConfig,
				secondOpts)
		end
		
		-- 3x HCOG 'Ranger', 2x-4x Variable AOG
		if proto.validateTypes(stat.time.hcog_ranged, ZoomProto) then
			if proto.validateTypes(stat.time.aog_variable, ZoomProto) then
				if stat.time.hcog_ranged.zoom_out ~= stat.time.aog_variable.zoom_out then
					renderRow(
						tbl,
						iu.scope('3倍HCOG\'レンジャー\'') .. '&nbsp;',
						stat.time.hcog_ranged.zoom_out,
						zoomoutConfig,
						secondOpts)
					renderRow(
						tbl,
						iu.scope('2~4倍可変式AOG') .. '&nbsp;',
						stat.time.aog_variable.zoom_out,
						zoomoutConfig,
						secondOpts)
				else
					renderRow(
						tbl,
						iu.scope('3倍HCOG\'レンジャー\'') .. iu.scope('2~4倍可変式AOG') .. '&nbsp;',
						stat.time.hcog_ranged.zoom_out,
						zoomoutConfig,
						secondOpts)
				end
			elseif stat.time.hcog_ranged.zoom_out ~= stat.time.zoom_out then
				renderRow(
					tbl,
					iu.scope('3倍HCOG\'レンジャー\'') .. '&nbsp;',
					stat.time.hcog_ranged.zoom_out,
					zoomoutConfig,
					secondOpts)
			end
		end
		
		-- 6x Sniper Scope
		if proto.validateTypes(stat.time.sniper, ZoomProto) then
			renderRow(
				tbl,
				iu.scope('6倍スナイパー') .. '&nbsp;',
				stat.time.sniper.zoom_out,
				zoomoutConfig,
				secondOpts)
		end
		
		-- 4x-8x Variable Sniper Scope
		if proto.validateTypes(stat.time.sniper_variable, ZoomProto) then
			renderRow(
				tbl,
				iu.scope('4~8倍可変式スナイパー') .. '&nbsp;',
				stat.time.sniper_variable.zoom_out,
				zoomoutConfig,
				secondOpts)
		end
		
		-- 4x-10x Digital Sniper Threat
		if proto.validateTypes(stat.time.sniper_threat, ZoomProto) then
			renderRow(
				tbl,
				iu.scope('4~10倍デジタルスレット') .. '&nbsp;',
				stat.time.sniper_threat.zoom_out,
				zoomoutConfig,
				secondOpts)
		end
	end
	
	return tbl
end

function p.renderDuration(stat, lang)
	local duration = cfg[lang].duration
	local isSniper = stat.category == 'sniper' or stat.category == 'marksman_weapon'
	local stock
	if isSniper then
		stock = attachment.sniper_stock
	else
		stock = attachment.standard_stock
	end
	local canModdedLoader = stat.category == 'light_machine_gun' or stat.ammo == 'minigun'
	local isSpecial = aw.stringstarts(stat.ammo, 'special_') and not (stat.ammo == 'special_sniper')
	
	local isSimple = not stat.attachments.stock or isSpecial
	local node
	if isSimple then
		node = renderDurationWithoutStock(stat, duration, stock, canModdedLoader, isSpecial)
	else
		node = renderDurationWithStock(stat, duration, stock, canModdedLoader, isSniper, isSpecial)
	end
	
	return node, isSimple
end

function p._main(name, lang)
	lang = lang or 'ja'
	
	local stat = mw.loadData('Module:Stat/Weapon')[name]
	local node, _ = p.renderDuration(stat, lang)
	return tostring(node)
end

return p