--[[ Modified by Tronex 2020/3/25 Ability to move between savegames by arrow keys or W/S Modified by Orleon 2023/5/11 Added profile system --]] saved_game_extension = ".scop" profile_prefix = "profile_" local config = axr_main.config if not (config:section_exist("save_profiles")) then config:w_value("save_profiles","current_profile",1) config:w_value("save_profiles","max_profiles",5) config:save() end function get_current_profile() return config:r_value("save_profiles","current_profile",2) end function get_max_profiles() return config:r_value("save_profiles","max_profiles",2) end function set_profile(num) config:w_value("save_profiles","current_profile",num) config:save() end function get_profile_name() return profile_prefix .. get_current_profile() end function hide_profile_prefix(fname) local s,e = fname:find(get_profile_name().." ") fname = fname:sub(e+1) return fname end function get_full_save_name(fname) return get_profile_name() .. " " .. fname end function rename_save_files(old_fname, new_fname) if (file_exist(old_fname..saved_game_extension)) then local f = getFS() local first = f:update_path('$game_saves$', old_fname) local second = f:update_path('$game_saves$', new_fname) f:file_rename(first..saved_game_extension, second..saved_game_extension, true) if (file_exist(old_fname..".scoc")) then f:file_rename(first..".scoc", second..".scoc", true) end if (file_exist(old_fname..".dds")) then f:file_rename(first..".dds", second..".dds", true) end end end function rename_save(fname, comp_fname) local new_fname = comp_fname or get_profile_name() .. " " .. fname if (file_exist(new_fname..saved_game_extension)) then delete_save_game(new_fname) end rename_save_files(fname, new_fname) end function complete_rename_save(fname) local t = str_explode(fname, profile_prefix) local str = "" for i=1, #t do str = str .. t[i] end if (str == "") then str = "unknown_save" end str = get_profile_name() .. " " .. str rename_save(fname, str) end function check_save_files() local f = getFS() local flist = f:file_list_open_ex("$game_saves$",bit_or(FS.FS_ListFiles,FS.FS_RootOnly),"*"..saved_game_extension) local f_cnt = flist:Size() local start_pos = string.len(profile_prefix) local curr_profile = get_profile_name() local max_num = get_max_profiles() for it=0, f_cnt-1 do local file = flist:GetAt(it) local file_name = string.sub(file:NameFull(), 0, (string.len(file:NameFull()) - string.len(saved_game_extension))) if (not file_name:find(profile_prefix)) then rename_save(file_name) else local s = file_name:find(profile_prefix) local _,_,num1,str1 = file_name:find("(%d+)(%s)") local _,_,str2 = file_name:find(profile_prefix.."(%D+)") if (s and s > 1) or (file_name:find(profile_prefix, start_pos)) or (file_name == profile_prefix) or (file_name == curr_profile) or (not str1) or (str2) or (num1 and tonumber(num1) > max_num) then complete_rename_save(file_name) end end end end function file_exist(fname) local f = getFS(); local flist = f:file_list_open_ex("$game_saves$",bit_or(FS.FS_ListFiles,FS.FS_RootOnly) , fname) local f_cnt = flist:Size() if f_cnt > 0 then return true else return false end end function delete_save_game(filename) local save_file = filename .. saved_game_extension local dds_file = filename .. ".dds" local f = getFS() f:file_delete("$game_saves$",save_file) if file_exist(dds_file) then f:file_delete("$game_saves$", dds_file) end if (file_exist(filename..".scoc")) then f:file_delete("$game_saves$",filename..".scoc") end end function AddTimeDigit(str, dig) if (dig > 9) then str = str .. dig else str = str .. "0" .. dig end return str end function file_data(fname) local f = getFS(); local flist = f:file_list_open_ex("$game_saves$",bit_or(FS.FS_ListFiles,FS.FS_RootOnly) , fname .. saved_game_extension) local f_cnt = flist:Size() if f_cnt > 0 then local file = flist:GetAt(0) local sg = CSavedGameWrapper(fname) local y,m,d,h,min,sec,ms = 0,0,0,0,0,0,0 y,m,d,h,min,sec,ms = sg:game_time():get(y,m,d,h,min,sec,ms) local date_time = "" date_time = AddTimeDigit(date_time, h) date_time = date_time .. ":" date_time = AddTimeDigit(date_time, min) date_time = date_time .. " " date_time = AddTimeDigit(date_time, m) date_time = date_time .. "/" date_time = AddTimeDigit(date_time, d) date_time = date_time .. "/" date_time = date_time .. y --string.format("[%d/%d/%d %d]",m,d,h,min,y) local health = string.format("\\n%s %d%s", game.translate_string("st_ui_health_sensor"),sg:actor_health()*100,"%") return game.translate_string("st_level") .. ": " .. game.translate_string(sg:level_name()) .. "\\n" .. game.translate_string("ui_inv_time")..": " .. date_time .. health else return "no file data" end end ------------------------------------------------------------ class "load_item" (CUIListBoxItem) function load_item:__init(height) super(height) self.file_name = "filename" self:SetTextColor(GetARGB(255, 170, 170, 170)) self.fn = self:GetTextItem() self.fn:SetFont(GetFontLetterica18Russian()) self.fn:SetEllipsis(true) end function load_item:__finalize() end ------------------------------------------------------------ class "UILoadDialog" (CUIScriptWnd) function UILoadDialog:__init() super() self:InitControls() self:InitCallBacks() end function UILoadDialog:__finalize() end function UILoadDialog:FillList() check_save_files() self.list_box:RemoveAll() local flist = getFS():file_list_open_ex("$game_saves$",bit_or(FS.FS_ListFiles,FS.FS_RootOnly),"*"..saved_game_extension) local f_cnt = flist:Size() local profile_name = get_profile_name() flist:Sort(FS.FS_sort_by_modif_down) for it=0, f_cnt-1 do local file = flist:GetAt(it) local file_name = string.sub(file:NameFull(), 0, (string.len(file:NameFull()) - string.len(saved_game_extension))) if (file_name:find(profile_name)) then local date_time = "[" .. file:ModifDigitOnly() .. "]" self:AddItemToList(file_name, date_time) end end end function UILoadDialog:InitControls() self:SetWndRect (Frect():set(0,0,1024,768)) local xml = CScriptXmlInit() local ctrl xml:ParseFile ("ui_mm_load_dlg.xml") xml:InitStatic ("background",self) ctrl = CUIWindow() ctrl:SetAutoDelete(true) xml:InitWindow ("file_item:main",0,ctrl) self.file_item_main_sz = vector2():set(ctrl:GetWidth(),ctrl:GetHeight()) xml:InitWindow ("file_item:fn",0,ctrl) self.file_item_fn_sz = vector2():set(ctrl:GetWidth(),ctrl:GetHeight()) xml:InitWindow ("file_item:fd",0,ctrl) self.file_item_fd_sz = vector2():set(ctrl:GetWidth(),ctrl:GetHeight()) self.form = xml:InitStatic("form",self) xml:InitStatic ("form:caption",self.form) self.picture = xml:InitStatic("form:picture",self.form) -- xml:InitStatic ("form:file_info",self.form) self.file_caption = xml:InitTextWnd("form:file_caption",self.form) self.file_data = xml:InitTextWnd("form:file_data",self.form) xml:InitFrame ("form:list_frame",self.form) self.list_box = xml:InitListBox("form:list",self.form) self.list_box:ShowSelectedItem (true) self:Register (self.list_box, "list_window") ctrl = xml:Init3tButton("form:btn_load", self.form) self:Register (ctrl, "button_load") ctrl = xml:Init3tButton ("form:btn_delete", self.form) self:Register (ctrl, "button_del") ctrl = xml:Init3tButton ("form:btn_cancel", self.form) self:Register (ctrl, "button_back") self.message_box = CUIMessageBoxEx() self:Register (self.message_box,"message_box") local prof_xml = CScriptXmlInit() prof_xml:ParseFile ("ui_mm_save_profiles.xml") self.profile_list = prof_xml:InitComboBox("profile_list", self.form) self.profile_list:SetAutoDelete(true) self:Register(self.profile_list, "profile_list") for i=1, get_max_profiles() do local str = strformat(game.translate_string("ui_st_mm_save_profile"), i) self.profile_list:AddItem(str, i) end self.current_profile = get_current_profile() self.profile_list:enable_id(self.current_profile) self.profile_list:SetText(strformat(game.translate_string("ui_st_mm_save_profile"), self.current_profile)) end function UILoadDialog:InitCallBacks() self:AddCallback("button_load", ui_events.BUTTON_CLICKED, self.OnButton_load_clicked, self) self:AddCallback("button_back", ui_events.BUTTON_CLICKED, self.OnButton_back_clicked, self) self:AddCallback("button_del", ui_events.BUTTON_CLICKED, self.OnButton_del_clicked, self) self:AddCallback("message_box", ui_events.MESSAGE_BOX_YES_CLICKED, self.OnMsgYes, self) self:AddCallback("message_box", ui_events.MESSAGE_BOX_OK_CLICKED, self.OnMsgYes, self) self:AddCallback("list_window", ui_events.LIST_ITEM_CLICKED, self.OnListItemClicked, self) self:AddCallback("list_window", ui_events.WINDOW_LBUTTON_DB_CLICK, self.OnListItemDbClicked, self) self:AddCallback("profile_list", ui_events.LIST_ITEM_SELECT, self.On_ProfileSelect, self) end function UILoadDialog:OnListItemClicked() if self.list_box:GetSize()==0 then return end local item = self.list_box:GetSelectedItem() if item == nil then self.file_caption:SetText ("") self.file_data:SetText ("") local r = self.picture:GetTextureRect() self.picture:InitTexture ("ui\\ui_noise") self.picture:SetTextureRect(Frect():set(r.x1,r.y1,r.x2,r.y2)) return end local item_text = item.fn:GetText() local full_item_text = get_full_save_name(item_text) self.file_caption:SetText (item_text) self.file_caption:SetEllipsis(true) self.file_data:SetText (file_data(full_item_text)) if file_exist(full_item_text .. saved_game_extension) ~= true then self.list_box:RemoveItem(item) return end local r = self.picture:GetTextureRect() if file_exist(full_item_text .. ".dds") then self.picture:InitTexture(full_item_text) else self.picture:InitTexture("ui\\ui_noise") end self.picture:SetTextureRect(Frect():set(r.x1,r.y1,r.x2,r.y2)) if (__show_scoc_debug) then __show_scoc_debug = nil local fname = full_item_text .. ".scoc" if not file_exist(fname) then return end local path = getFS():update_path('$game_saves$', '') local f = io.open(path..fname,"rb") if not (f) then return end local data = f:read("*all") f:close() if (data) then local m_data = marshal.decode(data) local output = io.open(path..full_item_text..".lua","wb") if (output) then output:write(utils_data.print_table(m_data,nil,true) or "") output:close() local msg = strformat("%s.scoc has be converted to *.lua",full_item_text) ui_dyn_msg_box.msg_box_ui("message_box_ok",true,msg) end end end end function UILoadDialog:OnListItemDbClicked() self:OnButton_load_clicked() end function UILoadDialog:OnMsgYes() local index = self.list_box:GetSelectedIndex() if index == -1 then return end if self.msgbox_id == 1 then local item = self.list_box:GetItemByIndex(index) if not (item) then return end local fname = get_full_save_name(item.fn:GetText()) delete_save_game (fname) self.list_box:RemoveItem(item) self:OnListItemClicked() elseif self.msgbox_id == 2 then self:load_game_internal() end self.msgbox_id = 0 end function UILoadDialog:load_game_internal() if self.list_box:GetSize()==0 then return end local index = self.list_box:GetSelectedIndex() if index == -1 then return end local item = self.list_box:GetItemByIndex(index) local fname = get_full_save_name(item.fn:GetText()) if (alife() == nil) then exec_console_cmd("disconnect") exec_console_cmd("start server(" .. fname .. "/single/alife/load) client(localhost)") else exec_console_cmd("load " .. fname) end end function UILoadDialog:OnButton_load_clicked() if self.list_box:GetSize()==0 then return end local item = self.list_box:GetSelectedItem() if item == nil then return end local fname = get_full_save_name(item.fn:GetText()) if ( not valid_saved_game(fname) ) then self.msgbox_id = 0 self.message_box:InitMessageBox ("message_box_invalid_saved_game") self.message_box:ShowDialog(true) return end if (alife() == nil) then self:load_game_internal () return end if ( (db.actor ~= nil) and (db.actor:alive() == false) ) then self:load_game_internal () return end self.msgbox_id = 2 self.message_box:InitMessageBox ("message_box_confirm_load_save") self.message_box:ShowDialog(true) end function UILoadDialog:OnButton_back_clicked() self.owner:ShowDialog(true) self:HideDialog() self.owner:Show (true) end function UILoadDialog:OnButton_del_clicked() if self.list_box:GetSize()==0 then return end local index = self.list_box:GetSelectedIndex() if index == -1 then return end self.msgbox_id = 1 self.message_box:InitMessageBox("message_box_delete_file_name") self.message_box:ShowDialog(true) end function UILoadDialog:OnKeyboard(dik, keyboard_action) --virtual function CUIScriptWnd.OnKeyboard(self,dik,keyboard_action) if (DEV_DEBUG) then if (keyboard_action == ui_events.WINDOW_KEY_PRESSED and dik == DIK_keys.DIK_LCONTROL) then __show_scoc_debug = true elseif (keyboard_action == ui_events.WINDOW_KEY_RELEASED and dik == DIK_keys.DIK_LCONTROL) then __show_scoc_debug = nil end end local bind = dik_to_bind(dik) if bind == key_bindings.kQUIT then self:OnButton_back_clicked() else if (keyboard_action == ui_events.WINDOW_KEY_PRESSED) then if (dik == DIK_keys.DIK_RETURN) then self:OnButton_load_clicked() elseif (dik == DIK_keys.DIK_DELETE) then self:OnButton_del_clicked() elseif (dik == DIK_keys.DIK_W) or (dik == DIK_keys.DIK_UP) then self:SelectNextItem(false) elseif (dik == DIK_keys.DIK_S) or (dik == DIK_keys.DIK_DOWN) then self:SelectNextItem(true) end end end return true end function UILoadDialog:AddItemToList(file_name, date_time) file_name = hide_profile_prefix(file_name) local _itm = load_item(self.file_item_main_sz.y) _itm:SetWndSize (self.file_item_main_sz) _itm.fn:SetWndPos (vector2():set(0,0)) _itm.fn:SetWndSize (self.file_item_fn_sz) _itm.fn:SetText (file_name) _itm.fage = _itm:AddTextField(date_time, self.file_item_fd_sz.x) _itm.fage:SetFont (GetFontLetterica16Russian()) _itm.fage:SetWndPos (vector2():set(self.file_item_fn_sz.x+4, 0)) _itm.fage:SetWndSize(self.file_item_fd_sz) self.list_box:AddExistingItem(_itm) end function UILoadDialog:SelectNextItem(state) local tot_idx = self.list_box:GetSize() if (tot_idx < 1) then return end local idx = self.list_box:GetSelectedIndex() or -1 local next_idx = state and idx + 1 or idx - 1 if next_idx > tot_idx then next_idx = 0 elseif next_idx < 0 then next_idx = tot_idx - 1 end self.list_box:SetSelectedIndex(next_idx) self:OnListItemClicked() end function UILoadDialog:On_ProfileSelect() local id = self.profile_list:CurrentID() if (self.current_profile == id) then return end self.file_caption:SetText("") self.file_data:SetText("") local r = self.picture:GetTextureRect() self.picture:InitTexture("ui\\ui_noise") self.picture:SetTextureRect(Frect():set(r.x1,r.y1,r.x2,r.y2)) set_profile(id) self.current_profile = id self:FillList() end