All pastes #445378 Raw Edit

Stuff

public text v1 · immutable
·published 2007-04-17 20:48 UTC
rendered paste body
--[[ *** ESCAPE_DIALOG.SCRIPT ***

function run_tutorial_pda(first_speaker, second_speaker)
	printf("!-!-!")
	first_speaker:stop_talk()
	second_speaker:stop_talk()

	local dlg = ui_repair_items.uiRepairDialog( first_speaker, (2/3) )
	dlg.Open()
	wait( 10000 )

	game.start_tutorial("part_1_pda")
end

]]--

-- db.actor is the player character

-- ui_repair_items.uiRepairDialog( repairer, cost_modifier )
class "uiRepairDialog" (CUIScriptWnd)

function logf(fmt,...)

	local console = get_console()
	local now = tostring( game.time() )
	
	local text = string.gsub( now ..'__'.. string.format(fmt, ... ), ' ', '_' )
	console:execute( text )

end

local color_valid = GetARGB( 255, 255, 255, 255 )
local color_invalid = GetARGB( 255, 255, 0, 0 )

-- find repairable items on someone
function find_repairable_items( owner, repair_table )
	logf( "find_repairable_items() called")

-- the to-be-filled table of repairable items from owner
	local items_repairable = {}

-- for find_repairable_items - what is iterated over each item
	function iterant_find_repairable_item( owner, item )
		logf( "iterant_find_repairable_item() called")
	    if repair_table.length == 0 then
	    	table.insert( items_repairable, item )
	    	return
	    end
		local item_section = item:section()
		local item_repairable = repair_table[item_section]
		if item_repairable ~= nil then
			local item_condition = item:condition()
			if item_repairable >= 0 then
				if item_condition < item_repairable then
					table.insert( items_repairable, item )
				end
			elseif item_repairable == -1 then
			    table.insert( items_repairable, item )
			end
		end
		logf( "iterant_find_repairable_item() complete")
	end

-- fill the items_repairable table
	owner:iterate_inventory( iterant_find_repairable_item, owner )
	logf( "find_repairable_items() complete")
	return items_repairable
end

function calculate_cost_at_condition( cost, condition )
	return math.ceil( cost - ( cost * condition ) )
end


-- returns 2 values: condition repaired to, and cost of repairs
function calculate_repairable( cost_modifier, condition_current, target_condition, budget )
	logf( "calculate_repairable() called")

	-- check current condition against target condition
	if condition_current > target_condition then
	logf( "calculate_repairable() complete")
		return condition_current, 0
	end

	-- calculate amount of repair needed
	local repair_needed = target_condition - condition_current

	-- calculate maximum repair cost for fractional cost
	local repair_max_cost = math.ceil( item:cost() * cost_modifier )

	-- calculate amount repairable under budget
	local repair_budgeted = budget / repair_max_cost

	-- repair to target condition can be made under budget
	if repair_budgeted > repair_needed then
		logf( "calculate_repairable() complete")
		return repair_needed, math.ceil( repair_needed * repair_max_cost )
	end

	-- repair can only be made to budget cap
	logf( "calculate_repairable() complete")
	return repair_budgeted, budget
end

function condition_as_color( condition )
	logf( "condition_as_color() called")
	local n
	local r, g
	-- to scale down means n*v, the opposite, scale up, means (1-n)*v
	if        condition >= 0.75   then -- lime   (255,   0) -> green  (128,   0)
	    n = ( condition -  0.75 ) * 4
		-- scale down green from full to half
		r, g =          0     , ((  n)*128)+127

	elseif    condition >= 0.50   then -- green  (128,   0) -> yellow (255, 255)
	    n = ( condition -  0.50 ) * 4
	    -- scale up green from half to full, scale up red from none to full
		r, g = ((1-n)*255)    , ((1-n)*128)+127

	elseif    condition >= 0.25   then -- yellow (255, 255) -> olive  (128, 128)
	    n = ( condition -  0.25 ) * 4
	    -- scale down green from full to half, scale down red from full to half
		r, g = ((  n)*128)+127, ((  n)*128)+127

	else -- ( condition >= 0.00 ) then -- olive  (128, 128) -> red    (255,   0)
	    n =   condition          * 4
	    -- scale down green from half to none, scale up red from half to full
		r, g = ((1-n)*128)+127, ((  n)*128)

	end

	logf( "condition_as_color() complete")
	return GetARGB( 255, math.floor( r ), math.ceil( g ), 0 );
end

function translate( string )
	return game.translate_string( "ui_repair_items_" .. string )
end

--[[  set of registered in game fonts:

 GetFontArialN21Russian()
 GetFontGraffiti32Russian()
 GetFontDI()
 GetFontHeaderRussian()
 GetFontGraffiti19Russian()
 GetFontLetterica25()
 GetFontMedium()
 GetFontLetterica16Russian()
 GetFontSmall()
 GetFontGraffiti22Russian()
 GetFontLetterica18Russian()
 GetFontBigDigit()
 GetFontHeaderEurope()
 GetFontGraffiti50Russian()

--]]

--[[ take control by name, dependent on type

 GetCheckButton(string)
 GetFrameLineWnd(string)
 GetPropertiesBox(string)
 GetListWnd(string)
 GetStatic(string)
 GetEditBox(string)
 GetTabControl(string)
 GetRadioGroup(string)
 GetDialogWnd(string)
 GetButton(string)
 GetMessageBox(string)
 GetProgressBar(string)
 GetRadioButton(string)
 GetFrameWindow(string)

--]]

function main( repairer, cost_modifier )
	logf( "main() called")
	local dlg
	if repairer.repair_cost_modifier ~= nil and cost_modifier ~= nil then
		dlg = ui_repair_items.uiRepairDialog( repairer, repairer.repair_cost_modifier * cost_modifier )
	elseif repairer.repair_cost_modifier ~= nil then
		dlg = ui_repair_items.uiRepairDialog( repairer, repairer.repair_cost_modifier )
	elseif cost_modifier ~= nil then
	    dlg = ui_repair_items.uiRepairDialog( repairer, cost_modifier )
	else
		dlg = ui_repair_items.uiRepairDialog( repairer, (2/3) )
	end
	dlg.Open()
	while dlg.IsOpen do
		wait( 1000 )
	end
	logf( "main() complete")
end

function uiRepairDialog:__init( repairer, cost_modifier ) super()
	logf( "uiRepairDialog:__init() called")
	self.repairer = repairer
	self.cost_modifier = cost_modifier
	self.last_recalc_params = { nil, nil }
	self.valid_repair = false
	self.IsOpen = false
	-- set base font for dialog
	self:SetFont( GetFontMedium() )
	self:InitControls()
	self:InitCallBacks()
	logf( "uiRepairDialog:__init() complete")
end

function uiRepairDialog:__finalize()
	logf( "uiRepairDialog:__finalize() called")
	self:Close()
	logf( "uiRepairDialog:__finalize() complete")
end


function uiRepairDialog:SetClGame( game_cl )
	logf( "uiRepairDialog:SetClGame() called")
	self.m_game_cl = game_cl
	logf( "uiRepairDialog:SetClGame() complete")
end

function uiRepairDialog:InitControls()
	logf( "uiRepairDialog:InitControls() called")
	local money = db.actor:money()
	local dev = device()
	local screen_w, screen_h = dev.width, dev.height
	local size_w, size_h = screen_w-100, screen_h-100
	local pos_x, pos_y = (screen_w/2) - (size_w/2), (screen_h/2) - (size_h/2)

	local margin_size = 10
	local top_margin, left_margin = margin_size, margin_size
	local bottom_margin, right_margin = size_h-margin_size, size_w-margin_size
	
	-- init self position and size
	--  position & size: x (from left), y (from top), width, height
	self:Init( pos_x, pos_y, size_w, size_h )

	logf("pos_x: %s   pos_y: %s   $size_w: %s   $size_h: %s", tostring(pos_x),tostring(pos_y),tostring(size_w),tostring(size_h) )
	
    ---------------------------------------------------------------- BACKGROUND
	local bkg_repair_wnd = CUIStatic()
	local bkg_repair_wnd_r = {
		--[[ [0] left   ]] 0,
		--[[ [1] top    ]] 0,
		--[[ [2] width  ]] size_w,
		--[[ [3] height ]] size_h
	}
	bkg_repair_wnd:SetAutoDelete( true )
	bkg_repair_wnd:SetWindowName( "bkg_repair_wnd" )
	bkg_repair_wnd:Init( unpack( bkg_repair_wnd_r ) )
	bkg_repair_wnd:SetColor( GetARGB( 255, 0, 0, 0 ) )
	self:AttachChild( bkg_repair_wnd )
	
	-- Start creating controls from bottom of window up up

	logf("left_margin: %s   bottom_margin: %s", tostring(left_margin), tostring(bottom_margin) )
	
	------------------------------------------------------------- REPAIR BUTTON
	logf( "constructing btn_repair" )
	local btn_repair = CUIButton()
	local btn_repair_r = {
		--[[ [0] left   ]] left_margin,
		--[[ [1] top    ]] bottom_margin-40,
		--[[ [2] width  ]] 135,
		--[[ [3] height ]] 40
	}
	logf( "constructing btn_repair 1" )
	local btn_repair = CUIButton()
	logf( "constructing btn_repair 2" )
	btn_repair:SetAutoDelete( true )
	logf( "constructing btn_repair 3" )
	btn_repair:SetWindowName( "btn_repair" )
	logf( "constructing btn_repair 4" )
	btn_repair:Init( unpack( btn_repair_r ) )
	logf( "constructing btn_repair 5" )
	btn_repair:SetText( translate( "btn_repair" ) )
	logf( "constructing btn_repair 6" )
	btn_repair:SetTextAlign( CGameFont.alCenter )
	logf( "constructing btn_repair 7" )
	btn_repair:SetTextY( -1 )
	logf( "constructing btn_repair 8" )
	self:Register( btn_repair )
	logf( "constructing btn_repair 9" )
	bkg_repair_wnd:AttachChild( btn_repair )
	logf( "constructing btn_repair 10" )

	------------------------------------------------------------- CANCEL BUTTON
	logf( "constructing btn_cancel" )
	local btn_cancel = CUIButton()
	local btn_cancel_r = {
		--[[ [0] left   ]] right_margin-btn_repair_r[2],
		--[[ [1] top    ]] btn_repair_r[1],
		--[[ [2] width  ]] btn_repair_r[2],
		--[[ [3] height ]] btn_repair_r[3]
	}
	btn_cancel:SetAutoDelete( true )
	btn_cancel:SetWindowName( "btn_cancel" )
	btn_cancel:Init( unpack( btn_cancel_r ) )
	btn_cancel:SetText( translate( "btn_cancel" ) )
	btn_cancel:SetTextAlign( CGameFont.alCenter )
	btn_cancel:SetTextY( -1 )
	self:Register( btn_cancel )
	bkg_repair_wnd:AttachChild( btn_cancel )

	----------------------------------------------------------- TIME COST LABEL
	logf( "constructing lbl_repair_time" )
	local lbl_repair_time = CUIStatic()
	local lbl_repair_time_r = {
		--[[ [0] left   ]] left_margin,
		--[[ [1] top    ]] btn_repair_r[1]-5,
		--[[ [2] width  ]] size_w-(margin_size*2),
		--[[ [3] height ]] btn_repair_r[3]
	}
	logf( "constructing lbl_repair_time 1" )
	lbl_repair_time:SetAutoDelete( true )
	logf( "constructing lbl_repair_time 2" )
	lbl_repair_time:SetWindowName( "lbl_repairable_items" )
	logf( "constructing lbl_repair_time 3" )
	lbl_repair_time:Init( unpack( lbl_repair_time_r ) )
	logf( "constructing lbl_repair_time 4" )
	lbl_repair_time:SetTextColor( GetARGB(255,0,0,0) )
	logf( "constructing lbl_repair_time 5" )
	lbl_repair_time:SetText( translate( "time_unknown" ) )
	logf( "constructing lbl_repair_time 6" )
	lbl_repair_time:SetTextAlign( CGameFont.alCenter )
	logf( "constructing lbl_repair_time 7" )
	lbl_repair_time:SetTextY( -1 )
	logf( "constructing lbl_repair_time 8" )
	bkg_repair_wnd:AttachChild( lbl_repair_time )

	-------------------------------------------------- IMPROVED CONDITION LABEL
	logf( "constructing lbl_condition_result" )
	local lbl_condition_result = CUIStatic()
	local lbl_condition_result_r = {
		--[[ [0] left   ]] left_margin,
		--[[ [1] top    ]] lbl_repair_time_r[1]-5,
		--[[ [2] width  ]] 200,
		--[[ [3] height ]] btn_repair_r[3]
	}
	lbl_condition_result:SetAutoDelete( true )
	lbl_condition_result:SetWindowName( "lbl_condition_result" )
	lbl_condition_result:Init( unpack( lbl_condition_result_r ) )
	lbl_condition_result:SetText( translate( "lbl_condition_result" ) )

	lbl_condition_result:SetTextAlign( CGameFont.alRight )
	lbl_condition_result:SetTextY( -1 )
	bkg_repair_wnd:AttachChild( lbl_condition_result )


    logf( "uiRepairDialog:InitControls() complete" )
end

function uiRepairDialog:register_callbacks( element, event_action_pairs )
    logf( "register_callbacks() called" )

	    self:AddCallback( element, ui_events.BUTTON_CLICKED, self, "on_btn_cancel_clicked" )

    logf( "register_callbacks() complete" )
end

function uiRepairDialog:InitCallBacks()
    logf( "uiRepairDialog:InitCallBacks() called" )
	--[[
	register_callbacks( "chb_repair_rush", {
			{ ui_events.CHECK_BUTTON_SET,   "on_chb_repair_rush_set"   },
			{ ui_events.CHECK_BUTTON_RESET, "on_chb_repair_rush_reset" }
		} )
	register_callbacks( "ebx_condition_improve_v", {
			{ ui_events.EDIT_TEXT_CHANGED, "on_ebx_condition_improve_v_changed" },
			{ ui_events.EDIT_TEXT_COMMIT,  "on_ebx_condition_improve_v_commit"  }
		} )
	register_callbacks( "ebx_condition_result_v", {
			{ ui_events.EDIT_TEXT_CHANGED, "on_ebx_condition_result_v_changed" },
			{ ui_events.EDIT_TEXT_COMMIT,  "on_ebx_condition_result_v_commit"  }
		} )
	register_callbacks( "ebx_money_spending_v", {
			{ ui_events.EDIT_TEXT_CHANGED, "on_ebx_money_spending_v_changed" },
			{ ui_events.EDIT_TEXT_COMMIT,  "on_ebx_money_spending_v_commit"  }
		} )
	register_callbacks( "ebx_money_remaining_v", {
			{ ui_events.EDIT_TEXT_CHANGED, "on_ebx_money_remaining_v_changed" },
			{ ui_events.EDIT_TEXT_COMMIT,  "on_ebx_money_remaining_v_commit"  }
		} )
	register_callbacks( "btn_repair", {
			{ ui_events.BUTTON_CLICKED, "on_btn_repair_clicked" }
		} )
		
	self:register_callbacks( "btn_cancel", {
			{ ui_events.BUTTON_CLICKED, "on_btn_cancel_clicked" }
		} )
		--]]
		
		--'self:AddCallback( "btn_cancel", ui_events.BUTTON_CLICKED,self, "on_btn_cancel_clicked" )
    logf( "uiRepairDialog:InitCallBacks() complete" )
end

function uiRepairDialog:on_btn_cancel_clicked()
	logf( "on_btn_cancel_clicked() called")
	self:Close()
	logf( "on_btn_cancel_clicked() complete")
end

function uiRepairDialog:Open()
	logf( "uiRepairDialog:Open() called")
	holder = self:GetHolder()
	self:Show( true )
	self.Enabled( true )
	if holder == nil then 
	    holder = level
	end
	if not self.isOpen then
	    holder.start_stop_menu( self, true )
	end
	self.IsOpen = true
	logf( "uiRepairDialog:Open() complete")
end

function uiRepairDialog:Close()
	logf( "uiRepairDialog:Close() called")
	--[[
	holder = self:GetHolder()
	self:Show( false )
	--'self:Enabled( false )
	if holder == nil then 
	    holder = level
	end
	if self.isOpen then
	    holder.start_stop_menu( self, true )
	end
	self.IsOpen = false
	--]]
	
	--'self.m_game_cl:StopMenu(self, true) --close dialog
	
	--'self:GetHolder():start_stop_menu(self, true)
	
	logf( "uiRepairDialog:Close() complete")
end

function uiRepairDialog:RecalcMoney( budget )
	logf( "uiRepairDialog:RecalcMoney() called")
	uiRepairDialog:Recalc( 1, budget )
	logf( "uiRepairDialog:RecalcMoney() complete")
end

function uiRepairDialog:RecalcCondition( condition_target )
	logf( "uiRepairDialog:RecalcCondition() called")
	uiRepairDialog:Recalc( condition_target, nil )
	logf( "uiRepairDialog:RecalcCondition() complete")
end

function uiRepairDialog:Recalc( condition_target, budget )
	logf( "uiRepairDialog:Recalc() called")

	-- TODO: Factor in 'goodwill' towards db.actor

	local money = db.actor:money()

	if condition_target == nil or condition_target > 1 then
		condition_target = 1
	end

	self.valid_repair = true

	if budget == nil then
		budget = money
	elseif budget > money then
		budget = money
	end

	self.last_recalc_params = { condition_target, budget }

	local lst_repairable_items = self:GetListWnd( "lst_repairable_items" )
	local lst_repairable_items_index = list:GetSelectedItem()

	if lst_repairable_items_index == -1 then
		logf( "uiRepairDialog:Recalc() complete")
		return
	end

	local repairable_item = self.items[index]
	local chb_repair_rush = self:GetCheckButton( "chb_repair_rush" )
	local ebx_condition_current_v = self:GetEditBox( "ebx_condition_current_v" )
	local ebx_condition_improve_v = self:GetEditBox( "ebx_condition_improve_v" )
	local ebx_condition_result_v = self:GetEditBox( "ebx_condition_result_v" )
	local ebx_money_spending_v = self:GetEditBox( "ebx_money_spending_v" )
	local ebx_money_remaining_v = self:GetEditBox( "ebx_money_remaining_v" )
	
	if money <= 0 then
		self.valid_repair = false
		ebx_condition_current_v:SetTextColor( color_invalid )
	end


	local rush_state = chb_repair_rush:GetCheck()
	
	local condition = repairable_item:condition()

	local condition_value = math.floor( condition * 10000 ) / 100
	local condition_target_value

    local condition_amount, expense
	if condition > condition_target then
		condition_target = condition
		condition_target_value = condition_value
		condition_ammount = 0
		expense = 0
		self.valid_repair = false
		ebx_money_spending_v:SetTextColor( color_invalid )
		ebx_condition_improve_v:SetTextColor( color_invalid )
	else
		condition_target_value = math.floor( condition_target * 10000 ) / 100
		condition_amount, expense = calculate_repairable(
			self.cost_modifier, condition_value, condition_target, budget )
		ebx_money_spending_v:SetTextColor( color_valid )
		ebx_condition_improve_v:SetTextColor( color_valid )
	end


	local condition_amount_value = math.floor( condition_amount * 10000 ) / 100
	
	ebx_condition_current_v:SetText( condition_value )

	ebx_condition_result_v:SetText( condition_target_value )
	ebx_condition_improve_v:SetText( condition_amount_value )

	local balance = money - expense
    ebx_money_spending_v:SetText( expense )
    ebx_money_remaining_v:SetText( balance )

	if balance < 0 then
		self.valid_repair = false
		ebx_money_remaining_v:SetTextColor( color_invalid )
		else
		ebx_money_remaining_v:SetTextColor( color_valid )
	end

	ebx_condition_current_v:SetTextColor( condition_as_color( condition ) )
	ebx_condition_result_v:SetTextColor( condition_as_color( condition_target ) )

	logf( "uiRepairDialog:Recalc() complete")
end

function uiRepairDialog:on_chb_repair_rush_set()
	logf( "on_chb_repair_rush_set() called")
	self:Recalc( unpack( self.last_calc_value ) )
	logf( "on_chb_repair_rush_set() complete")
end

function uiRepairDialog:on_chb_repair_rush_reset()
	logf( "on_chb_repair_rush_reset() called")
	self:Recalc( unpack( self.last_calc_value ) )
	logf( "on_chb_repair_rush_reset() complete")
end

function uiRepairDialog:on_ebx_condition_improve_v_changed()
	logf( "on_ebx_condition_improve_v_changed() called")
	local ebx_condition_improve_v = self:GetEditBox( "ebx_condition_improve_v" )
	self:RecalcCondition( tonumber( ebx_condition_improve_v:GetText() ) )
	logf( "on_ebx_condition_improve_v_changed() complete")
end

function uiRepairDialog:on_ebx_condition_improve_v_commit()
	logf( "on_ebx_condition_improve_v_commit() called")
	local ebx_condition_improve_v = self:GetEditBox( "ebx_condition_improve_v" )
	self:RecalcCondition( tonumber( ebx_condition_improve_v:GetText() ) )
	logf( "on_ebx_condition_improve_v_commit() complete")
end

function uiRepairDialog:on_ebx_condition_result_v_changed()
	logf( "on_ebx_condition_result_v_changed() called")
	local ebx_condition_result_v = self:GetEditBox( "ebx_condition_result_v" )
	self:RecalcCondition( tonumber( ebx_condition_result_v:GetText() ) )
	logf( "on_ebx_condition_result_v_changed() complete")
end

function uiRepairDialog:on_ebx_ebx_condition_result_v_commit()
	logf( "on_ebx_ebx_condition_result_v_commit() called")
	local ebx_condition_result_v = self:GetEditBox( "ebx_condition_result_v" )
	self:RecalcCondition( tonumber( ebx_condition_result_v:GetText() ) )
	logf( "on_ebx_ebx_condition_result_v_commit() complete")
end

function uiRepairDialog:on_ebx_money_spending_v_changed()
	logf( "on_ebx_money_spending_v_commit() called")
	local ebx_money_spending_v = self:GetEditBox( "ebx_money_spending_v" )
	self:RecalcMoney( tonumber( ebx_money_spending_v:GetText() ) )
	logf( "on_ebx_money_spending_v_commit() complete")
end

function uiRepairDialog:on_ebx_money_spending_v_commit()
	logf( "on_ebx_money_spending_v_commit() called")
	local ebx_money_spending_v = self:GetEditBox( "ebx_money_spending_v" )
	self:RecalcMoney( tonumber( ebx_money_spending_v:GetText() ) )
	logf( "on_ebx_money_spending_v_commit() complete")
end
function uiRepairDialog:on_ebx_money_remaining_v_changed()
	logf( "on_ebx_money_remaining_v_changed() called")
	local ebx_money_remaining_v = self:GetEditBox( "ebx_money_remaining_v" )
	self:RecalcMoney( tonumber( ebx_money_remaining_v:GetText() ) )
	logf( "on_ebx_money_remaining_v_changed() complete")
end

function uiRepairDialog:on_ebx_money_remaining_v_commit()
	logf( "on_ebx_money_remaining_v_commit() called")
	local ebx_money_remaining_v = self:GetEditBox( "ebx_money_remaining_v" )
	self:RecalcMoney( tonumber( ebx_money_remaining_v:GetText() ) )
	logf( "on_ebx_money_remaining_v_commit() complete")
end

function uiRepairDialog:on_btn_repair_clicked()
	logf( "on_btn_repair_clicked() called")
	local lst_repairable_items = self:GetListWnd( "lst_repairable_items" )
	local index = list:GetSelectedItem()
	if index == -1 then return end
	local item = self.items[index]

	local ebx_condition_result_v = self:GetEditBox( "ebx_condition_result_v" )
	local ebx_money_spending_v = self:GetEditBox( "ebx_money_spending_v" )

	-- db.actor:transfer_money( ebx_money_spending_v:GetText(), self.repairer )
	
	-- TODO: Hide weapon in inaccessible virtual stash with repair and date info
	
	-- the player will be able to retrieve his weapon by asking the repairer
	-- even if the repairs aren't complete, and still get a partial repair

	-- maybe partial to full refunds depending on goodwill and time spent repairing?

	self:Close()
	logf( "on_btn_repair_clicked() complete")
end

-- virtual function (overloaded?)
function uiRepairDialog:OnKeyboard( dik, keyboard_action )
	CUIScriptWnd.OnKeyboard( self, dik, keyboard_action )

	-- translate DirectInputKey to bound function (in user.ltx)
	local bind = dik_to_bind(dik)
	if keyboard_action == ui_events.WINDOW_KEY_PRESSED then
		logf("pressed dik=%d bind=%d", dik, bind )
	elseif keyboard_action == ui_events.WINDOW_KEY_RELEASED then
		logf("released dik=%d bind=%d", dik, bind )
	end
	
	if bind == key_bindings.kQUIT then
		self:Close()
	end
	return true -- catch all keys.
end