| 🌟 | 現在、 鉄壁HSは通常HSと同じダメージになります。LMG及びDMR、チャージライフル、ハンマーポイント弾を除き、すべてのダメージ値が一致していることを確認しています。  | 
「モジュール:WeaponInfobox/Duration」の版間の差分
		
		
		
		
		
		ナビゲーションに移動
		検索に移動
		
				
		
		
	
 (ブーステッドローダーのファストリロードに対応)  | 
				 (物資投下の際にホップアップ「デュアルシェル」を自動で有効化)  | 
				||
| (同じ利用者による、間の24版が非表示) | |||
| 4行目: | 4行目: | ||
local cfg = mw.loadData('Module:WeaponInfobox/configuration')  | local cfg = mw.loadData('Module:WeaponInfobox/configuration')  | ||
local attachment = mw.loadData('Module:Stat/Attachment')  | local attachment = mw.loadData('Module:Stat/Attachment')  | ||
local Hopup = mw.loadData('Module:Stat/Hopup')  | |||
local aw = require('Module:Utility/Library')  | local aw = require('Module:Utility/Library')  | ||
local iu = require('Module:Utility/Image')  | local iu = require('Module:Utility/Image')  | ||
local nu = require('Module:Utility/Name')  | |||
local proto = require('Module:Utility/Prototypes')  | local proto = require('Module:Utility/Prototypes')  | ||
| 25行目: | 27行目: | ||
	zoom_in  = proto.NumberRange(0),  | 	zoom_in  = proto.NumberRange(0),  | ||
	zoom_out = 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  | -- Duration for multiple value  | ||
local function createMultipleRow(  | local function createMultipleRow(name, default, cfg, opts)  | ||
	opts = opts or {}  | 	opts = opts or {}  | ||
	local row = tbl:tag('tr')  | 	local row = cfg.tbl:tag('tr')  | ||
	if aw.isNumberAndGreaterThanZero(opts.offset) then  | 	if aw.isNumberAndGreaterThanZero(opts.offset) then  | ||
| 53行目: | 77行目: | ||
		:wikitext(name)  | 		:wikitext(name)  | ||
	if   | 	if not cfg.isLegendary then  | ||
		row  | 		row  | ||
			:tag('td')  | 			:tag('td')  | ||
				:addClass('cell-type-number')  | 				:addClass('cell-type-number')  | ||
				:attr('align', 'right')  | 				:attr('align', 'right')  | ||
					:wikitext(string.format(cfg.fmt, default))  | |||
				:done()  | 				:done()  | ||
			:tag('td')  | |||
		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()  | 					: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()  | 					: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()  | 					:done()  | ||
				:  | 		else  | ||
			row:tag('td')  | |||
				:addClass('disabled')  | |||
				:attr('align', 'center')  | |||
				:attr('colspan', 3)  | |||
				:wikitext(cfg.res.na)  | |||
		end  | |||
	else  | 	else  | ||
		row  | 		row  | ||
| 93行目: | 127行目: | ||
				:attr('align', 'right')  | 				:attr('align', 'right')  | ||
				:attr('colspan', 4)  | 				:attr('colspan', 4)  | ||
				:wikitext(default)  | 				:tag('span')  | ||
					:addClass('text-rarity')  | |||
					:addClass('text-rarity-legendary')  | |||
					:wikitext(string.format(cfg.fmt, default))  | |||
					:done()  | |||
				:done()  | 				:done()  | ||
	end  | 	end  | ||
	return row  | 	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  | end  | ||
| 104行目: | 150行目: | ||
		:addClass('intable')  | 		:addClass('intable')  | ||
	local rootOpts = { colspan = 2 }  | 	local hasCooldown  = proto.validateTypes(stat.time, CooldownProto)  | ||
	local hasDualShell = Hopup.dual_shell.enabled 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  | 	-- Header  | ||
| 111行目: | 168行目: | ||
	tbl:tag('tr')  | 	tbl:tag('tr')  | ||
		:tag('th')  | 		:tag('th')  | ||
			:attr('colspan',   | 			:attr('colspan', colspan)  | ||
			:wikitext(name)  | 			:wikitext(name)  | ||
			:done()  | 			:done()  | ||
| 119行目: | 176行目: | ||
		:tag('th'):wikitext(iu.attachment(stockName, 3))  | 		:tag('th'):wikitext(iu.attachment(stockName, 3))  | ||
	-- Reload  | 	-- Segmented Reload  | ||
	if   | 	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(  | 		createMultipleRow(  | ||
			duration.cooldownoverheat.name,  | |||
			duration.  | 			overheatedTime,  | ||
			cooldownOverheatConfig,  | |||
			rootOpts)  | 			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  | 		if canModdedLoader then  | ||
			createMultipleRow(  | 			createMultipleRow(  | ||
				iu.passive('modded_loader'),  | 				iu.passive('modded_loader'),  | ||
				stat.time.reload * 0.75,  | |||
				reloadConfig,  | |||
				secondOpts)  | 				secondOpts)  | ||
		end  | 		end  | ||
		-- [Hop-up] Boosted Loader  | |||
		if stat.boosted_loader and aw.isNumberAndGreaterThanZero(stat.boosted_loader.reloadfast) then  | 		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(  | 			createMultipleRow(  | ||
				iu.hopup('boosted_loader'),  | |||
				iu.hopup('boosted_loader')   | 				stat.boosted_loader.reloadfast,  | ||
				reloadFastConfig,  | |||
				secondOpts)  | 				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  | 		end  | ||
		-- Reload (Empty)  | 		-- Reload (Empty)  | ||
		if aw.isNumber(stat.time.  | 		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(  | 			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  | 			if canModdedLoader then  | ||
				createMultipleRow(  | 				createMultipleRow(  | ||
					iu.passive('modded_loader'),  | 					iu.passive('modded_loader'),  | ||
					stat.time.reloadempty * stock.reload[3] * 0.5625,  | |||
					reloadEmptySplatterConfig,  | |||
					thirdOpts)  | |||
			end  | 			end  | ||
		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  | 	end  | ||
	-- Zoom In  | 	-- Zoom In  | ||
	if aw.isNumber(stat.time.zoom_in) then  | 	if aw.isNumber(stat.time.zoom_in) then  | ||
		local zoomInConfig = {  | |||
			fmt  = duration.zoomin.format,  | |||
			duration.zoomin.  | 			tbl  = tbl,  | ||
			muls = stock.zoom,  | |||
			res  = duration,  | |||
		}  | |||
		createMultipleRow(duration.zoomin.name, stat.time.zoom_in, zoomInConfig, rootOpts)  | |||
		-- HCOG Classic/Bruiser, Holosight, 1x Digital Threat  | 		-- HCOG Classic/Bruiser, Holosight, 1x Digital Threat  | ||
| 193行目: | 486行目: | ||
			end  | 			end  | ||
			createMultipleRow(  | 			createMultipleRow(label, stat.time.hcog_classic.zoom_in, zoomInConfig, secondOpts)  | ||
		end  | 		end  | ||
| 208行目: | 494行目: | ||
				if stat.time.hcog_ranged.zoom_in ~= stat.time.aog_variable.zoom_in then  | 				if stat.time.hcog_ranged.zoom_in ~= stat.time.aog_variable.zoom_in then  | ||
					createMultipleRow(  | 					createMultipleRow(  | ||
						iu.scope('3倍HCOG\'レンジャー\''),  | 						iu.scope('3倍HCOG\'レンジャー\''),  | ||
						stat.time.hcog_ranged.zoom_in,  | |||
						zoomInConfig,  | |||
						secondOpts)  | 						secondOpts)  | ||
					createMultipleRow(  | 					createMultipleRow(  | ||
						iu.scope('2~4倍可変式AOG'),  | 						iu.scope('2~4倍可変式AOG'),  | ||
						stat.time.aog_variable.zoom_in,  | |||
						zoomInConfig,  | |||
						secondOpts)  | 						secondOpts)  | ||
				else  | 				else  | ||
					createMultipleRow(  | 					createMultipleRow(  | ||
						iu.scope('3倍HCOG\'レンジャー\'') .. iu.scope('2~4倍可変式AOG'),  | 						iu.scope('3倍HCOG\'レンジャー\'') .. iu.scope('2~4倍可変式AOG'),  | ||
						stat.time.hcog_ranged.zoom_in,  | |||
						zoomInConfig,  | |||
						secondOpts)  | 						secondOpts)  | ||
				end  | 				end  | ||
			else  | 			else  | ||
				createMultipleRow(  | 				createMultipleRow(  | ||
					iu.scope('3倍HCOG\'レンジャー\''),  | 					iu.scope('3倍HCOG\'レンジャー\''),  | ||
					stat.time.hcog_ranged.zoom_in,  | |||
					zoomInConfig,  | |||
					secondOpts)  | 					secondOpts)  | ||
			end  | 			end  | ||
| 248行目: | 522行目: | ||
		if proto.validateTypes(stat.time.sniper, ZoomProto) then  | 		if proto.validateTypes(stat.time.sniper, ZoomProto) then  | ||
			createMultipleRow(  | 			createMultipleRow(  | ||
				iu.scope('6倍スナイパー'),  | 				iu.scope('6倍スナイパー'),  | ||
				stat.time.sniper.zoom_in,  | |||
				zoomInConfig,  | |||
				secondOpts)  | 				secondOpts)  | ||
		end  | 		end  | ||
| 260行目: | 531行目: | ||
		if proto.validateTypes(stat.time.sniper_variable, ZoomProto) then  | 		if proto.validateTypes(stat.time.sniper_variable, ZoomProto) then  | ||
			createMultipleRow(  | 			createMultipleRow(  | ||
				iu.scope('4~8倍可変式スナイパー'),  | 				iu.scope('4~8倍可変式スナイパー'),  | ||
				stat.time.sniper_variable.zoom_in,  | |||
				zoomInConfig,  | |||
				secondOpts)  | 				secondOpts)  | ||
		end  | 		end  | ||
| 272行目: | 540行目: | ||
		if proto.validateTypes(stat.time.sniper_threat, ZoomProto) then  | 		if proto.validateTypes(stat.time.sniper_threat, ZoomProto) then  | ||
			createMultipleRow(  | 			createMultipleRow(  | ||
				iu.scope('4~10倍デジタルスレット'),  | 				iu.scope('4~10倍デジタルスレット'),  | ||
				stat.time.sniper_threat.zoom_in,  | |||
				zoomInConfig,  | |||
				secondOpts)  | 				secondOpts)  | ||
		end  | 		end  | ||
| 284行目: | 549行目: | ||
	-- Zoom Out  | 	-- Zoom Out  | ||
	if aw.isNumber(stat.time.zoom_out) then  | 	if aw.isNumber(stat.time.zoom_out) then  | ||
		local zoomOutConfig = {  | |||
			fmt  = duration.zoomout.format,  | |||
			duration.zoomout.  | 			tbl  = tbl,  | ||
			muls = stock.zoom,  | |||
			res  = duration,  | |||
		}  | |||
		createMultipleRow(duration.zoomout.name, stat.time.zoom_out, zoomOutConfig, rootOpts)  | |||
		-- HCOG Classic/Bruiser, Holosight, 1x Digital Threat  | 		-- HCOG Classic/Bruiser, Holosight, 1x Digital Threat  | ||
| 303行目: | 567行目: | ||
			createMultipleRow(  | 			createMultipleRow(  | ||
				label,  | 				label,  | ||
				stat.time.hcog_classic.zoom_out,  | |||
				zoomOutConfig,  | |||
				secondOpts)  | 				secondOpts)  | ||
		end  | 		end  | ||
| 317行目: | 578行目: | ||
				if stat.time.hcog_ranged.zoom_out ~= stat.time.aog_variable.zoom_out then  | 				if stat.time.hcog_ranged.zoom_out ~= stat.time.aog_variable.zoom_out then  | ||
					createMultipleRow(  | 					createMultipleRow(  | ||
						iu.scope('3倍HCOG\'レンジャー\''),  | 						iu.scope('3倍HCOG\'レンジャー\''),  | ||
						stat.time.hcog_ranged.zoom_out,  | |||
						zoomOutConfig,  | |||
						secondOpts)  | 						secondOpts)  | ||
					createMultipleRow(  | 					createMultipleRow(  | ||
						iu.scope('2~4倍可変式AOG'),  | 						iu.scope('2~4倍可変式AOG'),  | ||
						stat.time.aog_variable.zoom_out,  | |||
						zoomOutConfig,  | |||
						secondOpts)  | 						secondOpts)  | ||
				else  | 				else  | ||
					createMultipleRow(  | 					createMultipleRow(  | ||
						iu.scope('3倍HCOG\'レンジャー\'') .. iu.scope('2~4倍可変式AOG'),  | 						iu.scope('3倍HCOG\'レンジャー\'') .. iu.scope('2~4倍可変式AOG'),  | ||
						stat.time.hcog_ranged.zoom_out,  | |||
						zoomOutConfig,  | |||
						secondOpts)  | 						secondOpts)  | ||
				end  | 				end  | ||
			else  | 			else  | ||
				createMultipleRow(  | 				createMultipleRow(  | ||
					iu.scope('3倍HCOG\'レンジャー\''),  | 					iu.scope('3倍HCOG\'レンジャー\''),  | ||
					stat.time.hcog_ranged.zoom_out,  | |||
					zoomOutConfig,  | |||
					secondOpts)  | 					secondOpts)  | ||
			end  | 			end  | ||
| 357行目: | 606行目: | ||
		if proto.validateTypes(stat.time.sniper, ZoomProto) then  | 		if proto.validateTypes(stat.time.sniper, ZoomProto) then  | ||
			createMultipleRow(  | 			createMultipleRow(  | ||
				iu.scope('6倍スナイパー'),  | 				iu.scope('6倍スナイパー'),  | ||
				stat.time.sniper.zoom_out,  | |||
				zoomOutConfig,  | |||
				secondOpts)  | 				secondOpts)  | ||
		end  | 		end  | ||
| 369行目: | 615行目: | ||
		if proto.validateTypes(stat.time.sniper_variable, ZoomProto) then  | 		if proto.validateTypes(stat.time.sniper_variable, ZoomProto) then  | ||
			createMultipleRow(  | 			createMultipleRow(  | ||
				iu.scope('4~8倍可変式スナイパー'),  | 				iu.scope('4~8倍可変式スナイパー'),  | ||
				stat.time.sniper_variable.zoom_out,  | |||
				zoomOutConfig,  | |||
				secondOpts)  | 				secondOpts)  | ||
		end  | 		end  | ||
| 381行目: | 624行目: | ||
		if proto.validateTypes(stat.time.sniper_threat, ZoomProto) then  | 		if proto.validateTypes(stat.time.sniper_threat, ZoomProto) then  | ||
			createMultipleRow(  | 			createMultipleRow(  | ||
				iu.scope('4~10倍デジタルスレット'),  | 				iu.scope('4~10倍デジタルスレット'),  | ||
				stat.time.sniper_threat.zoom_out,  | |||
				zoomOutConfig,  | |||
				secondOpts)  | 				secondOpts)  | ||
		end  | 		end  | ||
| 398行目: | 638行目: | ||
	opts = opts or {}  | 	opts = opts or {}  | ||
	if cfg.isSpecial then  | 	if cfg.isSpecial and cfg.muls then  | ||
		num = num * cfg.muls[3]  | 		num = num * cfg.muls[3]  | ||
	end  | 	end  | ||
| 404行目: | 644行目: | ||
	local row = tbl:tag('tr')  | 	local row = tbl:tag('tr')  | ||
		:addClassIf(opts.class and type(opts.class) == 'string', opts.class)  | 		:addClassIf(opts.class and type(opts.class) == 'string', opts.class)  | ||
		:attr('role', 'listitem')  | |||
		:tag('th')  | 		:tag('th')  | ||
			:attrIf(opts.headerAlign and type(opts.headerAlign) == 'string', { align = opts.headerAlign })  | 			:attrIf(opts.headerAlign and type(opts.headerAlign) == 'string', { align = opts.headerAlign })  | ||
| 425行目: | 666行目: | ||
		:addClass('condensedtable')  | 		:addClass('condensedtable')  | ||
		:addClass('listtable')  | 		: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 .. ' ',  | |||
				lvl3Time,  | |||
				reloadSegmentConfig)  | |||
			-- [Hop-up] Dual Shell  | |||
			if (Hopup.dual_shell.enabled or isSpecial) 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') .. ' ',  | |||
					lvl3DualShellTime,  | |||
					reloadSegmentConfig,  | |||
					secondOpts)  | |||
			end  | |||
			local oneTime = calcSegmentedReloadTime(1, stat.time)  | |||
			renderRow(  | |||
				tbl,  | |||
				duration.reloadsegmentone.name .. ' ',  | |||
				oneTime,  | |||
				reloadSegmentConfig)  | |||
		-- without Mags  | |||
		elseif aw.isNumber(stat.magazine) then  | |||
			local baseTime = calcSegmentedReloadTime(stat.magazine, stat.time)  | |||
			renderRow(  | |||
				tbl,  | |||
				duration.reload.name .. ' ',  | |||
				baseTime,  | |||
				reloadSegmentConfig)  | |||
			-- [Hop-up] Dual Shell  | |||
			if (Hopup.dual_shell.enabled or isSpecial) 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') .. ' ',  | |||
					dualShellTime,  | |||
					reloadSegmentConfig,  | |||
					secondOpts)  | |||
			end  | |||
			local oneTime = calcSegmentedReloadTime(1, stat.time)  | |||
			renderRow(  | |||
				tbl,  | |||
				duration.reloadsegmentone.name .. ' ',  | |||
				oneTime,  | |||
				reloadSegmentConfig)  | |||
		end  | |||
	-- Reload  | 	-- Reload  | ||
	elseif aw.isNumber(stat.time.reload) then  | |||
		local reloadConfig = {  | 		local reloadConfig = {  | ||
			fmt  = duration.reload.format,  | 			fmt  = duration.reload.format,  | ||
| 436行目: | 744行目: | ||
		renderRow(  | 		renderRow(  | ||
			tbl,  | 			tbl,  | ||
			duration.reload.name,  | 			duration.reload.name .. ' ',  | ||
			stat.time.reload  | 			stat.time.reload,  | ||
			reloadConfig)  | 			reloadConfig)  | ||
		if canModdedLoader then  | 		if canModdedLoader then  | ||
| 443行目: | 751行目: | ||
				tbl,  | 				tbl,  | ||
				iu.passive('modded_loader') .. ' ',  | 				iu.passive('modded_loader') .. ' ',  | ||
				stat.time.reload  | 				stat.time.reload * 0.75,  | ||
				reloadConfig,  | 				reloadConfig,  | ||
				secondOpts)  | 				secondOpts)  | ||
| 453行目: | 761行目: | ||
				stat.boosted_loader.reloadfast,  | 				stat.boosted_loader.reloadfast,  | ||
				reloadConfig,  | 				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') .. ' ',  | |||
				stat.time.reload * 0.75,  | |||
				reloadSplatterConfig,  | |||
				secondOpts)  | 				secondOpts)  | ||
		end  | 		end  | ||
		-- Reload (Empty)  | 		-- Reload (Empty)  | ||
		if aw.isNumber(stat.time.  | 		if aw.isNumber(stat.time.reloadempty) and stat.time.reload ~= stat.time.reloadempty then  | ||
			local reloademptyConfig = {  | 			local reloademptyConfig = {  | ||
				fmt  = duration.reloadempty.format,  | 				fmt  = duration.reloadempty.format,  | ||
| 466行目: | 788行目: | ||
			renderRow(  | 			renderRow(  | ||
				tbl,  | 				tbl,  | ||
				duration.reloadempty.name,  | 				duration.reloadempty.name .. ' ',  | ||
				stat.time.  | 				stat.time.reloadempty,  | ||
				reloademptyConfig)  | 				reloademptyConfig)  | ||
			if canModdedLoader then  | 			if canModdedLoader then  | ||
| 473行目: | 795行目: | ||
					tbl,  | 					tbl,  | ||
					iu.passive('modded_loader') .. ' ',  | 					iu.passive('modded_loader') .. ' ',  | ||
					stat.time.  | 					stat.time.reloadempty * 0.75,  | ||
					reloademptyConfig,  | 					reloademptyConfig,  | ||
					secondOpts)  | 					secondOpts)  | ||
			end  | 			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') .. ' ',  | |||
					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 .. ' ',  | |||
			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 .. ' ',  | |||
				stat.time.deployfirst,  | |||
				deployFirstConfig,  | |||
				secondOpts)  | |||
		end  | 		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 .. ' ',  | |||
			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 .. ' ',  | |||
			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 .. ' ',  | |||
			stat.time.lower,  | |||
			lowerConfig)  | |||
	end  | 	end  | ||
| 490行目: | 900行目: | ||
		renderRow(  | 		renderRow(  | ||
			tbl,  | 			tbl,  | ||
			duration.zoomin.name,  | 			duration.zoomin.name .. ' ',  | ||
			stat.time.zoom_in,  | 			stat.time.zoom_in,  | ||
			zoominConfig)  | 			zoominConfig)  | ||
| 496行目: | 906行目: | ||
		-- HCOG Classic/Bruiser, Holosight, 1x Digital Threat  | 		-- 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  | 		if proto.validateTypes(stat.time.hcog_classic, ZoomProto) and stat.time.hcog_classic.zoom_in ~= stat.time.zoom_in then  | ||
			local label   | 			local label    | ||
			if proto.validateTypes(stat.time.threat, ZoomProto) then  | 			if proto.validateTypes(stat.time.threat, ZoomProto) then  | ||
				label = label .. iu.scope('  | 				label = duration.without_standard_scope  | ||
			else  | |||
				label = iu.scope('1倍HCOG\'クラシック\'') .. iu.scope('2倍HCOG\'ブルーザー\'') .. iu.scope('1倍ホロサイト') .. iu.scope('1~2倍可変式ホロサイト')  | |||
			end  | 			end  | ||
| 584行目: | 996行目: | ||
		renderRow(  | 		renderRow(  | ||
			tbl,  | 			tbl,  | ||
			duration.zoomout.name,  | 			duration.zoomout.name .. ' ',  | ||
			stat.time.zoom_out,  | 			stat.time.zoom_out,  | ||
			zoomoutConfig)  | 			zoomoutConfig)  | ||
| 590行目: | 1,002行目: | ||
		-- HCOG Classic/Bruiser, Holosight, 1x Digital Threat  | 		-- 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  | 		if proto.validateTypes(stat.time.hcog_classic, ZoomProto) and stat.time.hcog_classic.zoom_out ~= stat.time.zoom_out then  | ||
			local label   | 			local label  | ||
			if proto.validateTypes(stat.time.threat, ZoomProto) then  | 			if proto.validateTypes(stat.time.threat, ZoomProto) then  | ||
				label = label .. iu.scope('  | 				label = duration.without_standard_scope  | ||
			else  | |||
				label = iu.scope('1倍HCOG\'クラシック\'') .. iu.scope('2倍HCOG\'ブルーザー\'') .. iu.scope('1倍ホロサイト') .. iu.scope('1~2倍可変式ホロサイト')  | |||
			end  | 			end  | ||
2022年5月19日 (木) 12:45時点における最新版
このモジュールについての説明文ページを モジュール: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)
	local tbl = mw.html.create('table')
		:addClass('intable')
	
	local hasCooldown  = proto.validateTypes(stat.time, CooldownProto)
	local hasDualShell = Hopup.dual_shell.enabled 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 .. ' ',
				lvl3Time,
				reloadSegmentConfig)
			
			-- [Hop-up] Dual Shell
			if (Hopup.dual_shell.enabled or isSpecial) 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') .. ' ',
					lvl3DualShellTime,
					reloadSegmentConfig,
					secondOpts)
			end
			
			local oneTime = calcSegmentedReloadTime(1, stat.time)
			renderRow(
				tbl,
				duration.reloadsegmentone.name .. ' ',
				oneTime,
				reloadSegmentConfig)
		
		-- without Mags
		elseif aw.isNumber(stat.magazine) then
			local baseTime = calcSegmentedReloadTime(stat.magazine, stat.time)
			renderRow(
				tbl,
				duration.reload.name .. ' ',
				baseTime,
				reloadSegmentConfig)
			
			-- [Hop-up] Dual Shell
			if (Hopup.dual_shell.enabled or isSpecial) 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') .. ' ',
					dualShellTime,
					reloadSegmentConfig,
					secondOpts)
			end
			
			local oneTime = calcSegmentedReloadTime(1, stat.time)
			renderRow(
				tbl,
				duration.reloadsegmentone.name .. ' ',
				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 .. ' ',
			stat.time.reload,
			reloadConfig)
		if canModdedLoader then
			renderRow(
				tbl,
				iu.passive('modded_loader') .. ' ',
				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') .. ' ',
				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') .. ' ',
				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 .. ' ',
				stat.time.reloadempty,
				reloademptyConfig)
			if canModdedLoader then
				renderRow(
					tbl,
					iu.passive('modded_loader') .. ' ',
					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') .. ' ',
					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 .. ' ',
			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 .. ' ',
				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 .. ' ',
			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 .. ' ',
			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 .. ' ',
			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 .. ' ',
			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 .. ' ',
				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\'レンジャー\'') .. ' ',
						stat.time.hcog_ranged.zoom_in,
						zoominConfig,
						secondOpts)
					renderRow(
						tbl,
						iu.scope('2~4倍可変式AOG') .. ' ',
						stat.time.aog_variable.zoom_in,
						zoominConfig,
						secondOpts)
				else
					renderRow(
						tbl,
						iu.scope('3倍HCOG\'レンジャー\'') .. iu.scope('2~4倍可変式AOG') .. ' ',
						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\'レンジャー\'') .. ' ',
					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倍スナイパー') .. ' ',
				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倍可変式スナイパー') .. ' ',
				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倍デジタルスレット') .. ' ',
				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 .. ' ',
			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 .. ' ',
				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\'レンジャー\'') .. ' ',
						stat.time.hcog_ranged.zoom_out,
						zoomoutConfig,
						secondOpts)
					renderRow(
						tbl,
						iu.scope('2~4倍可変式AOG') .. ' ',
						stat.time.aog_variable.zoom_out,
						zoomoutConfig,
						secondOpts)
				else
					renderRow(
						tbl,
						iu.scope('3倍HCOG\'レンジャー\'') .. iu.scope('2~4倍可変式AOG') .. ' ',
						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\'レンジャー\'') .. ' ',
					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倍スナイパー') .. ' ',
				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倍可変式スナイパー') .. ' ',
				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倍デジタルスレット') .. ' ',
				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)
	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