1 /*
2  * This file is part of EasyRPG Player.
3  *
4  * EasyRPG Player is free software: you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation, either version 3 of the License, or
7  * (at your option) any later version.
8  *
9  * EasyRPG Player is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with EasyRPG Player. If not, see <http://www.gnu.org/licenses/>.
16  */
17 
18 // Headers
19 #include <algorithm>
20 #include <sstream>
21 #include <vector>
22 #include "baseui.h"
23 #include "cache.h"
24 #include <lcf/data.h>
25 #include "game_system.h"
26 #include "game_party.h"
27 #include "input.h"
28 #include <lcf/lsd/reader.h>
29 #include "player.h"
30 #include "scene_file.h"
31 #include "bitmap.h"
32 #include <lcf/reader_util.h>
33 #include "output.h"
34 
35 constexpr int arrow_animation_frames = 20;
36 
Scene_File(std::string message)37 Scene_File::Scene_File(std::string message) :
38 	message(message) {
39 }
40 
MakeBorderSprite(int y)41 std::unique_ptr<Sprite> Scene_File::MakeBorderSprite(int y) {
42 	auto bitmap = Bitmap::Create(SCREEN_TARGET_WIDTH, 8, Cache::System()->GetBackgroundColor());
43 	auto sprite = std::unique_ptr<Sprite>(new Sprite());
44 	sprite->SetVisible(true);
45 	sprite->SetZ(Priority_Window + 1);
46 	sprite->SetBitmap(bitmap);
47 	sprite->SetX(0);
48 	sprite->SetY(y);
49 	return sprite;
50 }
51 
MakeArrowSprite(bool down)52 std::unique_ptr<Sprite> Scene_File::MakeArrowSprite(bool down) {
53 	Rect rect = Rect(40, (down ? 16 : 8), 16, 8);
54 	auto bitmap = Bitmap::Create(*(Cache::System()), rect);
55 	auto sprite = std::unique_ptr<Sprite>(new Sprite());
56 	sprite->SetVisible(false);
57 	sprite->SetZ(Priority_Window + 2);
58 	sprite->SetBitmap(bitmap);
59 	sprite->SetX(SCREEN_TARGET_WIDTH / 2 - 8);
60 	sprite->SetY(down ? 232 : 32);
61 	return sprite;
62 }
63 
CreateHelpWindow()64 void Scene_File::CreateHelpWindow() {
65 	help_window.reset(new Window_Help(0, 0, SCREEN_TARGET_WIDTH, 32));
66 	help_window->SetText(message);
67 	help_window->SetZ(Priority_Window + 1);
68 }
69 
PopulatePartyFaces(Window_SaveFile & win,int,lcf::rpg::Save & savegame)70 void Scene_File::PopulatePartyFaces(Window_SaveFile& win, int /* id */, lcf::rpg::Save& savegame) {
71 	win.SetParty(savegame.title);
72 	win.SetHasSave(true);
73 }
74 
UpdateLatestTimestamp(int id,lcf::rpg::Save & savegame)75 void Scene_File::UpdateLatestTimestamp(int id, lcf::rpg::Save& savegame) {
76 	if (savegame.title.timestamp > latest_time) {
77 		latest_time = savegame.title.timestamp;
78 		latest_slot = id;
79 	}
80 }
81 
PopulateSaveWindow(Window_SaveFile & win,int id)82 void Scene_File::PopulateSaveWindow(Window_SaveFile& win, int id) {
83 	// Try to access file
84 	std::stringstream ss;
85 	ss << "Save" << (id <= 8 ? "0" : "") << (id + 1) << ".lsd";
86 
87 	std::string file = fs.FindFile(ss.str());
88 
89 	if (!file.empty()) {
90 		// File found
91 		auto save_stream = FileFinder::Save().OpenInputStream(file);
92 		if (!save_stream) {
93 			Output::Debug("Save {} read error", file);
94 			win.SetCorrupted(true);
95 			return;
96 		}
97 
98 		std::unique_ptr<lcf::rpg::Save> savegame = lcf::LSD_Reader::Load(save_stream, Player::encoding);
99 
100 		if (savegame) {
101 			PopulatePartyFaces(win, id, *savegame);
102 			UpdateLatestTimestamp(id, *savegame);
103 		} else {
104 			Output::Debug("Save {} corrupted", file);
105 			win.SetCorrupted(true);
106 		}
107 	}
108 }
109 
Start()110 void Scene_File::Start() {
111 	CreateHelpWindow();
112 	border_top = Scene_File::MakeBorderSprite(32);
113 
114 	// Refresh File Finder Save Folder
115 	fs = FileFinder::Save();
116 
117 	for (int i = 0; i < Utils::Clamp<int32_t>(lcf::Data::system.easyrpg_max_savefiles, 3, 99); i++) {
118 		std::shared_ptr<Window_SaveFile>
119 			w(new Window_SaveFile(0, 40 + i * 64, SCREEN_TARGET_WIDTH, 64));
120 		w->SetIndex(i);
121 		w->SetZ(Priority_Window);
122 		PopulateSaveWindow(*w, i);
123 		w->Refresh();
124 
125 		file_windows.push_back(w);
126 	}
127 
128 	border_bottom = Scene_File::MakeBorderSprite(232);
129 
130 	up_arrow = Scene_File::MakeArrowSprite(false);
131 	down_arrow = Scene_File::MakeArrowSprite(true);
132 
133 	index = latest_slot;
134 	top_index = std::max(0, index - 2);
135 
136 	Refresh();
137 
138 	for (auto& fw: file_windows) {
139 		fw->Update();
140 	}
141 }
142 
Refresh()143 void Scene_File::Refresh() {
144 	for (int i = 0; i < (int)file_windows.size(); i++) {
145 		Window_SaveFile *w = file_windows[i].get();
146 		w->SetY(40 + (i - top_index) * 64);
147 		w->SetActive(i == index);
148 		w->Refresh();
149 	}
150 }
151 
Update()152 void Scene_File::Update() {
153 	UpdateArrows();
154 
155 	if (IsWindowMoving()) {
156 		for (auto& fw: file_windows) {
157 			fw->Update();
158 		}
159 		return;
160 	}
161 
162 	if (Input::IsTriggered(Input::CANCEL)) {
163 		Main_Data::game_system->SePlay(Main_Data::game_system->GetSystemSE(Main_Data::game_system->SFX_Cancel));
164 		Scene::Pop();
165 	} else if (Input::IsTriggered(Input::DECISION)) {
166 		if (IsSlotValid(index)) {
167 			Main_Data::game_system->SePlay(Main_Data::game_system->GetSystemSE(Main_Data::game_system->SFX_Decision));
168 			Action(index);
169 		}
170 		else {
171 			Main_Data::game_system->SePlay(Main_Data::game_system->GetSystemSE(Main_Data::game_system->SFX_Buzzer));
172 		}
173 	}
174 
175 	int old_top_index = top_index;
176 	int old_index = index;
177 	int max_index = static_cast<int>(file_windows.size()) - 1;
178 
179 	if (Input::IsRepeated(Input::DOWN) || Input::IsTriggered(Input::SCROLL_DOWN)) {
180 		if (Input::IsTriggered(Input::DOWN) || Input::IsTriggered(Input::SCROLL_DOWN)
181 			|| index < max_index) {
182 			Main_Data::game_system->SePlay(Main_Data::game_system->GetSystemSE(Main_Data::game_system->SFX_Cursor));
183 			index = (index + 1) % file_windows.size();
184 		}
185 
186 		//top_index = std::max(top_index, index - 3 + 1);
187 	}
188 	if (Input::IsRepeated(Input::UP) || Input::IsTriggered(Input::SCROLL_UP)) {
189 		if (Input::IsTriggered(Input::UP) || Input::IsTriggered(Input::SCROLL_UP)
190 			|| index >= 1) {
191 			Main_Data::game_system->SePlay(Main_Data::game_system->GetSystemSE(Main_Data::game_system->SFX_Cursor));
192 			index = (index + max_index) % file_windows.size();
193 		}
194 
195 		//top_index = std::min(top_index, index);
196 	}
197 
198 	if (Input::IsRepeated(Input::PAGE_DOWN) && index < max_index) {
199 		Main_Data::game_system->SePlay(Main_Data::game_system->GetSystemSE(Main_Data::game_system->SFX_Cursor));
200 		index = (index + 3 <= max_index) ? index + 3 : max_index;
201 	}
202 	if (Input::IsRepeated(Input::PAGE_UP) && index >= 1) {
203 		Main_Data::game_system->SePlay(Main_Data::game_system->GetSystemSE(Main_Data::game_system->SFX_Cursor));
204 		index = (index > 3) ? index - 3 : 0;
205 	}
206 
207 	if (index > top_index + 2) {
208 		MoveFileWindows((top_index + 2 - index) * 64, 7);
209 		top_index = std::max(top_index, index - 3 + 1);
210 	}
211 	else if (index < top_index) {
212 		MoveFileWindows((top_index - index) * 64, 7);
213 		top_index = std::min(top_index, index);
214 	}
215 
216 	//top_index = std::min(top_index, std::max(top_index, index - 3 + 1));
217 
218 	if (top_index != old_top_index || index != old_index)
219 		Refresh();
220 
221 	for (auto& fw: file_windows) {
222 		fw->Update();
223 	}
224 }
225 
226 
IsWindowMoving() const227 bool Scene_File::IsWindowMoving() const {
228 	for (auto& fw: file_windows) {
229 		if (fw->IsMovementActive()) {
230 			return true;
231 		}
232 	}
233 	return false;
234 }
235 
MoveFileWindows(int dy,int dt)236 void Scene_File::MoveFileWindows(int dy, int dt) {
237 	for (auto& fw: file_windows) {
238 		fw->InitMovement(fw->GetX(), fw->GetY(), fw->GetX(), fw->GetY() + dy, dt);
239 	}
240 }
241 
UpdateArrows()242 void Scene_File::UpdateArrows() {
243 	int max_index = static_cast<int>(file_windows.size()) - 1;
244 
245         bool show_up_arrow = (top_index > 0);
246         bool show_down_arrow = (top_index < max_index - 2);
247 
248         if (show_up_arrow || show_down_arrow) {
249                 arrow_frame = (arrow_frame + 1) % (arrow_animation_frames * 2);
250         }
251         bool arrow_visible = (arrow_frame < arrow_animation_frames);
252         up_arrow->SetVisible(show_up_arrow && arrow_visible);
253         down_arrow->SetVisible(show_down_arrow && arrow_visible);
254 }
255