1 /*
2 * readnpcs.cc - Read in NPC's from npc.dat & schedule.dat. Also writes npc.dat back out.
3 *
4 * Copyright (C) 1999 Jeffrey S. Freedman
5 * Copyright (C) 2000-2013 The Exult Team
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
20 */
21
22 #ifdef HAVE_CONFIG_H
23 # include <config.h>
24 #endif
25
26 #include <cstring>
27
28 #include "gamewin.h"
29 #include "game.h"
30 #include "monsters.h"
31 #include "ucmachine.h"
32 #include "utils.h"
33 #include "fnames.h"
34 #include "schedule.h"
35 #include "databuf.h"
36 #include "miscinf.h"
37 //#include "items.h" /* Debugging only */
38
39 using std::cerr;
40 using std::cout;
41 using std::endl;
42 using std::ifstream;
43 using std::ios;
44 using std::ofstream;
45 using std::vector;
46
47 /*
48 * Read in the NPC's, plus the monster info.
49 */
50
read_npcs()51 void Game_window::read_npcs(
52 ) {
53 npcs.resize(1); // Create main actor.
54 Main_actor_shared ava = std::make_shared<Main_actor>("", 0);
55 npcs[0] = ava;
56 camera_actor = main_actor = ava.get();
57 bool fix_unused = false; // Get set for old savegames.
58 {
59 int num_npcs;
60 IFileDataSource nfile(NPC_DAT);
61 if (nfile.good()) {
62 num_npcs1 = nfile.read2(); // Get counts.
63 num_npcs = num_npcs1 + nfile.read2();
64 main_actor->read(&nfile, 0, false, fix_unused);
65 } else {
66 if (!Game::is_editing())
67 throw file_read_exception(NPC_DAT);
68 num_npcs1 = num_npcs = 1;
69 if (Game::get_avname())
70 main_actor->set_npc_name(Game::get_avname());
71 main_actor->set_shape(Shapeinfo_lookup::GetMaleAvShape());
72 main_actor->set_invalid(); // Put in middle of world.
73 main_actor->move(c_num_tiles / 2, c_num_tiles / 2, 0);
74 }
75 npcs.resize(num_npcs);
76 bodies.resize(num_npcs);
77
78 // Don't like it... no i don't.
79 center_view(main_actor->get_tile());
80 for (int i = 1; i < num_npcs; i++) { // Create the rest.
81 npcs[i] = std::make_shared<Npc_actor>("", 0);
82 auto& npc = npcs[i];
83 npc->read(&nfile, i, i < num_npcs1, fix_unused);
84 if (npc->is_unused()) {
85 // Not part of the game.
86 Game_object_shared keep;
87 npc->remove_this(&keep);
88 npc->set_schedule_type(Schedule::wait);
89 } else
90 npc->restore_schedule();
91 cycle_load_palette();
92 }
93 }
94 main_actor->set_actor_shape();
95 {
96 IFileDataSource nfile(MONSNPCS); // Monsters.
97 if (nfile.good()) {
98 // (Won't exist the first time; in this case U7open throws
99 int cnt = nfile.read2();
100 nfile.skip(1);// Read 1 ahead to test.
101 bool okay = nfile.good();
102 nfile.skip(-1);
103 while (okay && cnt--) {
104 // Read ahead to get shape.
105 nfile.skip(2);
106 unsigned short shnum = nfile.read2() & 0x3ff;
107 okay = nfile.good();
108 nfile.skip(-4);
109 ShapeID sid(shnum, 0);
110 if (!okay || sid.get_num_frames() < 16)
111 break; // Watch for corrupted file.
112 Game_object_shared new_monster = Monster_actor::create(shnum);
113 auto *act = static_cast<Monster_actor*>(new_monster.get());
114 act->read(&nfile, -1, false, fix_unused);
115 act->set_schedule_loc(act->get_tile());
116 act->restore_schedule();
117 cycle_load_palette();
118 }
119 } else {
120 #ifdef DEBUG
121 cerr << "Error reading saved monsters. Clearing list." << endl;
122 #endif
123 Monster_actor::give_up();
124 }
125 }
126 if (moving_barge) { // Gather all NPC's on barge.
127 Barge_object *b = moving_barge;
128 moving_barge = nullptr;
129 set_moving_barge(b);
130 }
131 read_schedules(); // Now get their schedules.
132 center_view(main_actor->get_tile());
133 }
134
135 /*
136 * Write NPC (and monster) data back out.
137 *
138 * Output: false if error, already reported.
139 */
140
write_npcs()141 void Game_window::write_npcs(
142 ) {
143 int num_npcs = npcs.size();
144 {
145 OFileDataSource nfile(NPC_DAT);
146
147 nfile.write2(num_npcs1); // Start with counts.
148 nfile.write2(num_npcs - num_npcs1);
149 int i;
150 std::cout << "NPC write " << std::endl;
151 for (i = 0; i < num_npcs; i++)
152 get_npc(i)->write(&nfile);
153 nfile.flush();
154 if (!nfile.good())
155 throw file_write_exception(NPC_DAT);
156 }
157 write_schedules(); // Write schedules
158 {
159 // Now write out monsters in world.
160 OFileDataSource nfile(MONSNPCS);
161 int cnt = 0;
162 nfile.write2(0); // Write 0 as a place holder.
163 for (Monster_actor *mact = Monster_actor::get_first_in_world();
164 mact; mact = mact->get_next_in_world())
165 if (!mact->is_dead()) { // Alive?
166 mact->write(&nfile);
167 cnt++;
168 }
169 nfile.seek(0); // Back to start.
170 nfile.write2(cnt); // Write actual count.
171 nfile.flush();
172 if (!nfile.good())
173 throw file_write_exception(MONSNPCS);
174 }
175 }
176
177 /*
178 * Read in offsets. When done, file is set to start of script names (if
179 * there are any).
180 */
181
Set_to_read_schedules(IStreamDataSource & sfile,int & num_npcs,int & entsize,int & num_script_names)182 std::unique_ptr<short[]> Set_to_read_schedules(
183 IStreamDataSource &sfile,
184 int &num_npcs, // # npc's returnes.
185 int &entsize, // Entry size returned.
186 int &num_script_names // # of usecode script names ret'd.
187 ) {
188 entsize = 4; // 4 is U7's size.
189 num_script_names = 0;
190 num_npcs = sfile.read4(); // # of NPC's, not include Avatar.
191 if (num_npcs == -1) { // Exult format?
192 entsize = 8;
193 num_npcs = sfile.read4();
194 } else if (num_npcs == -2) {
195 entsize = 8;
196 num_npcs = sfile.read4();
197 num_script_names = sfile.read2();
198 }
199 auto offsets = std::make_unique<short[]>(num_npcs);
200 int i; // Read offsets with list of scheds.
201 for (i = 0; i < num_npcs; i++)
202 offsets[i] = sfile.read2();
203 return offsets;
204 }
205
206 /*
207 * Read one NPC's schedule.
208 */
209
Read_a_schedule(IStreamDataSource & sfile,int index,Actor * npc,int entsize,const short * offsets)210 void Read_a_schedule(
211 IStreamDataSource &sfile,
212 int index,
213 Actor *npc,
214 int entsize,
215 const short *offsets
216 ) {
217 int cnt = offsets[index] - offsets[index - 1];
218 // Read schedules into this array.
219 Schedule_change *schedules = cnt ? new Schedule_change[cnt] : nullptr;
220 unsigned char ent[10];
221 if (entsize == 4) { // U7 format?
222 for (int j = 0; j < cnt; j++) {
223 sfile.read(reinterpret_cast<char *>(ent), 4);
224 schedules[j].set4(ent);
225 }
226 } else { // Exult formats.
227 for (int j = 0; j < cnt; j++) {
228 sfile.read(reinterpret_cast<char *>(ent), 8);
229 schedules[j].set8(ent);
230 }
231 }
232 if (npc) // Store in NPC.
233 npc->set_schedules(schedules, cnt);
234 else
235 delete [] schedules;
236 }
237
238 /*
239 * Read NPC schedules.
240 */
241
read_schedules()242 void Game_window::read_schedules(
243 ) {
244 std::unique_ptr<IFileDataSource> sfile = std::make_unique<IFileDataSource>(GSCHEDULE);
245 if (!sfile->good()) {
246 #ifdef DEBUG
247 cerr << "Couldn't open " << GSCHEDULE << ". Falling back to "
248 << SCHEDULE_DAT << "." << endl;
249 #endif
250 sfile = std::make_unique<IFileDataSource>(SCHEDULE_DAT);
251 if (!sfile->good()) {
252 if (!Game::is_editing())
253 throw file_open_exception(get_system_path(SCHEDULE_DAT));
254 else
255 return;
256 }
257 }
258 int num_npcs = 0;
259 int entsize;
260 int num_script_names;
261 auto offsets = Set_to_read_schedules(*sfile, num_npcs, entsize, num_script_names);
262 Schedule_change::clear();
263 vector<std::string> &script_names = Schedule_change::get_script_names();
264 if (num_script_names) {
265 sfile->read2(); // Skip past total size.
266 script_names.reserve(num_script_names);
267 for (int i = 0; i < num_script_names; ++i) {
268 int sz = sfile->read2();
269 std::string nm;
270 sfile->read(nm, sz);
271 script_names.push_back(std::move(nm));
272 }
273 }
274
275 for (int i = 0; i < num_npcs - 1; i++) { // Do each NPC, except Avatar.
276 // Avatar isn't included here.
277 Actor *npc = get_npc(i + 1);
278 Read_a_schedule(*sfile, i + 1, npc, entsize, offsets.get());
279 cycle_load_palette();
280 }
281 cout.flush();
282 }
283
284 /*
285 * Write NPC schedules.
286 */
287
write_schedules()288 void Game_window::write_schedules() {
289 Schedule_change *schedules;
290 int cnt;
291 short offset = 0;
292 int i;
293 int num;
294
295 // So do I allow for all NPCs (type1 and type2) - Yes i will
296 num = npcs.size();
297
298 OFileDataSource sfile(GSCHEDULE);
299 vector<std::string> &script_names = Schedule_change::get_script_names();
300
301 sfile.write4(static_cast<unsigned int>(-2)); // Exult version #.
302 sfile.write4(num); // # of NPC's, not include Avatar.
303 sfile.write2(script_names.size());
304 sfile.write2(0); // First offset
305
306 for (i = 1; i < num; i++) { // write offsets with list of scheds.
307 get_npc(i)->get_schedules(schedules, cnt);
308 offset += cnt;
309 sfile.write2(offset);
310 }
311 if (!script_names.empty()) {
312 int total = 0; // Figure total size.
313 for (auto& elem : script_names)
314 total += 2 + elem.size();
315 sfile.write2(total);
316 for (auto& elem : script_names) {
317 sfile.write2(elem.size());
318 sfile.write(elem);
319 }
320 }
321 for (i = 1; i < num; i++) { // Do each NPC, except Avatar.
322 get_npc(i)->get_schedules(schedules, cnt);
323 for (int j = 0; j < cnt; j++) {
324 unsigned char ent[20];
325 schedules[j].write8(ent);
326 sfile.write(reinterpret_cast<char *>(ent), 8);
327 }
328 }
329 }
330
revert_schedules(Actor * npc)331 void Game_window::revert_schedules(Actor *npc) {
332 // Can't do this if <= 0
333 if (npc->get_npc_num() <= 0) return;
334
335 IFileDataSource sfile(SCHEDULE_DAT);
336 if (!sfile.good()) {
337 throw file_read_exception(SCHEDULE_DAT);
338 }
339
340 int num_npcs;
341 int entsize;
342 int num_script_names;
343 auto offsets = Set_to_read_schedules(sfile, num_npcs, entsize, num_script_names);
344 if (num_script_names) {
345 int sz = sfile.read2();
346 sfile.skip(sz);
347 }
348 // Seek to the right place
349 sfile.skip(offsets[npc->get_npc_num() - 1]*entsize);
350
351 Read_a_schedule(sfile, npc->get_npc_num(), npc, entsize, offsets.get());
352 }
353
354