1 #include "../simsys.h"
2 #include "../simconst.h"
3 #include "../simtypes.h"
4 #include "../simdebug.h"
5 
6 #include "../player/simplay.h"
7 #include "../simworld.h"
8 #include "../simmesg.h"
9 #include "../simmem.h"
10 #include "../simmenu.h"
11 
12 #include "../dataobj/loadsave.h"
13 #include "../dataobj/translator.h"
14 #include "../dataobj/environment.h"
15 #include "../network/network.h"
16 #include "../network/network_cmd_scenario.h"
17 #include "../dataobj/schedule.h"
18 
19 #include "../utils/cbuffer_t.h"
20 
21 // error popup
22 #include "../gui/simwin.h"
23 #include "../gui/scenario_info.h"
24 
25 // scripting
26 #include "../script/script.h"
27 #include "../script/export_objs.h"
28 #include "../script/api/api.h"
29 #include "../script/api_param.h"
30 #include "../script/api_class.h"
31 
32 #include "../tpl/plainstringhashtable_tpl.h"
33 
34 #include "scenario.h"
35 
36 #include <stdarg.h>
37 
38 // cache the scenario text files
39 static plainstringhashtable_tpl<plainstring> cached_text_files;
40 
41 
scenario_t(karte_t * w)42 scenario_t::scenario_t(karte_t *w) :
43 	description_text("get_short_description"),
44 	info_text("get_info_text"),
45 	goal_text("get_goal_text"),
46 	rule_text("get_rule_text"),
47 	result_text("get_result_text"),
48 	about_text("get_about_text"),
49 	debug_text("get_debug_text")
50 {
51 	welt = w;
52 	what_scenario = 0;
53 
54 	script = NULL;
55 	won = false;
56 	lost = false;
57 	rdwr_error = false;
58 	need_toolbar_update = false;
59 
60 	cached_text_files.clear();
61 }
62 
63 
~scenario_t()64 scenario_t::~scenario_t()
65 {
66 	delete script;
67 	clear_ptr_vector(forbidden_tools);
68 	cached_text_files.clear();
69 }
70 
71 
init(const char * scenario_base,const char * scenario_name_,karte_t * welt)72 const char* scenario_t::init( const char *scenario_base, const char *scenario_name_, karte_t *welt )
73 {
74 	this->welt = welt;
75 	scenario_name = scenario_name_;
76 
77 	// path to scenario files
78 	cbuffer_t buf;
79 	buf.printf("%s%s/", scenario_base, scenario_name_);
80 	scenario_path = buf;
81 
82 	// scenario script file
83 	buf.append("scenario.nut");
84 	if (!load_script( buf )) {
85 		dbg->warning("scenario_t::init", "could not load script file %s", (const char*)buf);
86 		return "Loading scenario script failed";
87 	}
88 
89 	const char *err = NULL;
90 	plainstring mapfile;
91 
92 	// load savegame
93 	if ((err = script->call_function(script_vm_t::FORCE, "get_map_file", mapfile))) {
94 		dbg->warning("scenario_t::init", "error [%s] calling get_map_file", err);
95 		return "No scenario map specified";
96 	}
97 
98 	// if savegame-string == "<attach>" then do not load a savegame, just attach to running game.
99 	if ( strcmp(mapfile, "<attach>") ) {
100 		// savegame location
101 		buf.clear();
102 		buf.printf("%s%s/%s", scenario_base, scenario_name_, mapfile.c_str());
103 		if (!welt->load( buf )) {
104 			dbg->warning("scenario_t::init", "error loading savegame %s", err, (const char*)buf);
105 			return "Could not load scenario map!";
106 		}
107 		// set savegame name
108 		buf.clear();
109 		buf.printf("%s.sve", scenario_name.c_str());
110 		welt->get_settings().set_filename( strdup(buf) );
111 		// re-initialize coordinate and rotation handling
112 		script_api::coordinate_transform_t::initialize();
113 	}
114 
115 	load_compatibility_script();
116 
117 	// load translations
118 	translator::load_files_from_folder( scenario_path.c_str(), "scenario" );
119 	cached_text_files.clear();
120 
121 	what_scenario = SCRIPTED;
122 
123 	// callback
124 	script->register_callback(&scenario_t::set_completion, "scenario_t_set_completed");
125 
126 	// register ourselves
127 	welt->set_scenario(this);
128 	welt->get_message()->clear();
129 
130 	// set start time
131 	sint32 const time = welt->get_current_month();
132 	welt->get_settings().set_starting_year( time / 12);
133 	welt->get_settings().set_starting_month( time % 12);
134 
135 	// set my player number to PLAYER_UNOWNED
136 	script->set_my_player(PLAYER_UNOWNED);
137 
138 	// now call startup function
139 	if ((err = script->call_function(script_vm_t::QUEUE, "start"))) {
140 		dbg->warning("scenario_t::init", "error [%s] calling start", err);
141 	}
142 
143 	return NULL;
144 }
145 
146 
load_base_script(script_vm_t * script,const char * base)147 bool load_base_script(script_vm_t *script, const char* base)
148 {
149 	cbuffer_t buf;
150 	buf.printf("%sscript/%s", env_t::program_dir, base);
151 	if (const char* err = script->call_script(buf)) {
152 		// should not happen
153 		dbg->error("load_base_script", "error [%s] calling %s", err, (const char*)buf);
154 		return false;
155 	}
156 	return true;
157 }
158 
159 
load_script(const char * filename)160 bool scenario_t::load_script(const char* filename)
161 {
162 	script = new script_vm_t(scenario_path.c_str(), "script-scenario.log");
163 	// load global stuff
164 	// constants must be known compile time
165 	export_global_constants(script->get_vm());
166 	// load scripting base definitions
167 	if (!load_base_script(script, "script_base.nut")) {
168 		return false;
169 	}
170 	// load scenario base definitions
171 	if (!load_base_script(script, "scenario_base.nut")) {
172 		return false;
173 	}
174 
175 	// register api functions
176 	register_export_function(script->get_vm(), true);
177 	if (script->get_error()) {
178 		dbg->error("scenario_t::load_script", "error [%s] calling register_export_function", script->get_error());
179 		return false;
180 	}
181 
182 	// init strings
183 	dynamic_string::init(script);
184 	// register callback
185 	if (env_t::server) {
186 		nwc_scenario_t::init(script);
187 	}
188 
189 	// load scenario definition
190 	if (const char* err = script->call_script(filename)) {
191 		dbg->error("scenario_t::load_script", "error [%s] calling %s", err, filename);
192 		return false;
193 	}
194 	return true;
195 }
196 
197 
load_compatibility_script()198 void scenario_t::load_compatibility_script()
199 {
200 	// check api version
201 	plainstring api_version;
202 	if (const char* err = script->call_function(script_vm_t::FORCE, "get_api_version", api_version)) {
203 		dbg->warning("scenario_t::init", "error [%s] calling get_api_version", err);
204 		api_version = "120.1";
205 	}
206 	if (api_version != "*") {
207 		// load scenario compatibility script
208 		if (load_base_script(script, "script_compat.nut")) {
209 			plainstring dummy;
210 			// call compatibility function
211 			if (const char* err = script->call_function(script_vm_t::FORCE, "compat", dummy, api_version) ) {
212 				dbg->warning("scenario_t::init", "error [%s] calling compat", err);
213 			}
214 		}
215 	}
216 }
217 
218 
koord_sq2w(koord & k) const219 void scenario_t::koord_sq2w(koord &k) const
220 {
221 	script_api::coordinate_transform_t::koord_sq2w(k);
222 }
223 
224 
get_forbidden_text()225 const char* scenario_t::get_forbidden_text()
226 {
227 	static cbuffer_t buf;
228 	buf.clear();
229 	buf.append("<h1>Forbidden stuff:</h1><br>");
230 	for(uint32 i=0; i<forbidden_tools.get_count(); i++) {
231 		scenario_t::forbidden_t &f = *forbidden_tools[i];
232 		buf.printf("[%d] Player = %d, Tool = %d", i, f.player_nr, f.toolnr);
233 		if (f.waytype!=invalid_wt) {
234 			buf.printf(", Waytype = %d", f.waytype);
235 		}
236 		if (f.type == forbidden_t::forbid_tool_rect) {
237 			if (-128<f.hmin ||  f.hmax<127) {
238 				buf.printf(", Cube = (%s,%d) x ", f.pos_nw.get_str(), f.hmin);
239 				buf.printf("(%s,%d)", f.pos_se.get_str(), f.hmax);
240 			}
241 			else {
242 				buf.printf(", Rect = (%s) x ", f.pos_nw.get_str());
243 				buf.printf("(%s)", f.pos_se.get_str());
244 			}
245 		}
246 		buf.printf("<br>");
247 	}
248 	return buf;
249 }
250 
251 
operator <(const forbidden_t & other) const252 bool scenario_t::forbidden_t::operator <(const forbidden_t &other) const
253 {
254 	bool lt = type < other.type;
255 	if (!lt  &&  type == other.type) {
256 		sint32 diff = (sint32)player_nr - (sint32)other.player_nr;
257 		if (diff == 0) {
258 			diff = (sint32)toolnr - (sint32)other.toolnr;
259 		}
260 		if (diff == 0) {
261 			diff = (sint32)waytype - (sint32)other.waytype;
262 		}
263 		lt = diff < 0;
264 	}
265 	return lt;
266 }
267 
268 
operator ==(const forbidden_t & other) const269 bool scenario_t::forbidden_t::operator ==(const forbidden_t &other) const
270 {
271 	bool eq =  (type == other.type)  &&  (player_nr == other.player_nr)  &&  (waytype == other.waytype);
272 	if (eq) {
273 		switch(type) {
274 			case forbid_tool_rect:
275 				eq = eq  &&  (hmin == other.hmin)  &&  (hmax == other.hmax);
276 				eq = eq  &&  (pos_nw == other.pos_nw);
277 				eq = eq  &&  (pos_se == other.pos_se);
278 				/* FALLTHROUGH */
279 			case forbid_tool:
280 				eq = eq  &&  (toolnr == other.toolnr);
281 				break;
282 		}
283 	}
284 	return eq;
285 }
286 
287 
forbidden_t(const forbidden_t & other)288 scenario_t::forbidden_t::forbidden_t(const forbidden_t& other) :
289 	type(other.type), player_nr(other.player_nr), toolnr(other.player_nr),
290 	waytype(other.waytype), pos_nw(other.pos_nw), pos_se(other.pos_se),
291 	hmin(other.hmin), hmax(other.hmax), error(other.error)
292 {
293 }
294 
295 
rotate90(const sint16 y_size)296 void scenario_t::forbidden_t::rotate90(const sint16 y_size)
297 {
298 	switch(type) {
299 		case forbid_tool_rect: {
300 			pos_nw.rotate90(y_size);
301 			pos_se.rotate90(y_size);
302 			sint16 x = pos_nw.x; pos_nw.x = pos_se.x; pos_se.x = x;
303 		}
304 		default: ;
305 	}
306 }
307 
308 
find_first(const forbidden_t & other) const309 uint32 scenario_t::find_first(const forbidden_t &other) const
310 {
311 	if (forbidden_tools.empty()  ||  *forbidden_tools.back() < other) {
312 		// empty vector, or everything is smaller
313 		return forbidden_tools.get_count();
314 	}
315 	if (other < (*forbidden_tools[0])) {
316 		// everything is larger
317 		return forbidden_tools.get_count();
318 	}
319 	else if ( other <= *forbidden_tools[0] ) {
320 		return 0;
321 	}
322 	// now: low < other <= high
323 	uint32 low = 0, high = forbidden_tools.get_count()-1;
324 	while(low+1 < high) {
325 		uint32 mid = (low+high) / 2;
326 		if (*forbidden_tools[mid] < other) {
327 			low = mid;
328 			// now low < other
329 		}
330 		else {
331 			high = mid;
332 			// now other <= high
333 		}
334 	};
335 	// still: low < other <= high
336 	bool notok = other < *forbidden_tools[high];
337 	return notok   ?  forbidden_tools.get_count()  :  high;
338 }
339 
340 
intern_forbid(forbidden_t * test,bool forbid)341 void scenario_t::intern_forbid(forbidden_t *test, bool forbid)
342 {
343 	bool changed = false;
344 	forbidden_t::forbid_type type = test->type;
345 
346 	for(uint32 i = find_first(*test); i < forbidden_tools.get_count()  &&  *forbidden_tools[i] <= *test; i++) {
347 		if (*test == *forbidden_tools[i]) {
348 			// entry exists already
349 			delete test;
350 			if (!forbid) {
351 				delete forbidden_tools[i];
352 				forbidden_tools.remove_at(i);
353 				changed = true;
354 			}
355 			goto end;
356 		}
357 	}
358 	// entry does not exist
359 	if (forbid) {
360 		forbidden_tools.insert_ordered(test, scenario_t::forbidden_t::compare);
361 		changed = true;
362 	}
363 end:
364 	if (changed  &&  type==forbidden_t::forbid_tool) {
365 		need_toolbar_update = true;
366 	}
367 }
368 
call_forbid_tool(forbidden_t * test,bool forbid)369 void scenario_t::call_forbid_tool(forbidden_t *test, bool forbid)
370 {
371 	if (env_t::server) {
372 		// send information over network
373 		nwc_scenario_rules_t *nws = new nwc_scenario_rules_t(welt->get_sync_steps() + 1, welt->get_map_counter());
374 		nws->rule = test;
375 		nws->forbid = forbid;
376 		network_send_all(nws, false);
377 	}
378 	else {
379 		// directly apply
380 		intern_forbid(test, forbid);
381 	}
382 }
383 
forbid_tool(uint8 player_nr,uint16 tool_id)384 void scenario_t::forbid_tool(uint8 player_nr, uint16 tool_id)
385 {
386 	forbidden_t *test = new forbidden_t(forbidden_t::forbid_tool, player_nr, tool_id, invalid_wt);
387 	call_forbid_tool(test, true);
388 }
389 
390 
allow_tool(uint8 player_nr,uint16 tool_id)391 void scenario_t::allow_tool(uint8 player_nr, uint16 tool_id)
392 {
393 	forbidden_t *test = new forbidden_t(forbidden_t::forbid_tool, player_nr, tool_id, invalid_wt);
394 	call_forbid_tool(test, false);
395 }
396 
397 
forbid_way_tool(uint8 player_nr,uint16 tool_id,waytype_t wt)398 void scenario_t::forbid_way_tool(uint8 player_nr, uint16 tool_id, waytype_t wt)
399 {
400 	forbidden_t *test = new forbidden_t(forbidden_t::forbid_tool, player_nr, tool_id, wt);
401 	call_forbid_tool(test, true);
402 }
403 
404 
allow_way_tool(uint8 player_nr,uint16 tool_id,waytype_t wt)405 void scenario_t::allow_way_tool(uint8 player_nr, uint16 tool_id, waytype_t wt)
406 {
407 	forbidden_t *test = new forbidden_t(forbidden_t::forbid_tool, player_nr, tool_id, wt);
408 	call_forbid_tool(test, false);
409 }
410 
411 
forbid_way_tool_rect(uint8 player_nr,uint16 tool_id,waytype_t wt,koord pos_nw,koord pos_se,plainstring err)412 void scenario_t::forbid_way_tool_rect(uint8 player_nr, uint16 tool_id, waytype_t wt, koord pos_nw, koord pos_se, plainstring err)
413 {
414 	forbid_way_tool_cube(player_nr, tool_id, wt, koord3d(pos_nw, -128), koord3d(pos_se, 127), err);
415 }
416 
417 
allow_way_tool_rect(uint8 player_nr,uint16 tool_id,waytype_t wt,koord pos_nw,koord pos_se)418 void scenario_t::allow_way_tool_rect(uint8 player_nr, uint16 tool_id, waytype_t wt, koord pos_nw, koord pos_se)
419 {
420 	allow_way_tool_cube(player_nr, tool_id, wt, koord3d(pos_nw, -128), koord3d(pos_se, 127));
421 }
422 
423 
forbid_way_tool_cube(uint8 player_nr,uint16 tool_id,waytype_t wt,koord3d pos_nw_0,koord3d pos_se_0,plainstring err)424 void scenario_t::forbid_way_tool_cube(uint8 player_nr, uint16 tool_id, waytype_t wt, koord3d pos_nw_0, koord3d pos_se_0, plainstring err)
425 {
426 	koord pos_nw( min(pos_nw_0.x, pos_se_0.x), min(pos_nw_0.y, pos_se_0.y));
427 	koord pos_se( max(pos_nw_0.x, pos_se_0.x), max(pos_nw_0.y, pos_se_0.y));
428 	sint8 hmin( min(pos_nw_0.z, pos_se_0.z) );
429 	sint8 hmax( max(pos_nw_0.z, pos_se_0.z) );
430 
431 	forbidden_t *test = new forbidden_t(player_nr, tool_id, wt, pos_nw, pos_se, hmin, hmax);
432 	test->error = err;
433 	call_forbid_tool(test, true);
434 }
435 
436 
allow_way_tool_cube(uint8 player_nr,uint16 tool_id,waytype_t wt,koord3d pos_nw_0,koord3d pos_se_0)437 void scenario_t::allow_way_tool_cube(uint8 player_nr, uint16 tool_id, waytype_t wt, koord3d pos_nw_0, koord3d pos_se_0)
438 {
439 	koord pos_nw( min(pos_nw_0.x, pos_se_0.x), min(pos_nw_0.y, pos_se_0.y));
440 	koord pos_se( max(pos_nw_0.x, pos_se_0.x), max(pos_nw_0.y, pos_se_0.y));
441 	sint8 hmin( min(pos_nw_0.z, pos_se_0.z) );
442 	sint8 hmax( max(pos_nw_0.z, pos_se_0.z) );
443 
444 	forbidden_t *test = new forbidden_t(player_nr, tool_id, wt, pos_nw, pos_se, hmin, hmax);
445 	call_forbid_tool(test, false);
446 }
447 
448 
clear_rules()449 void scenario_t::clear_rules()
450 {
451 	clear_ptr_vector(forbidden_tools);
452 	need_toolbar_update = true;
453 }
454 
455 
is_tool_allowed(const player_t * player,uint16 tool_id,sint16 wt)456 bool scenario_t::is_tool_allowed(const player_t* player, uint16 tool_id, sint16 wt)
457 {
458 	if (what_scenario != SCRIPTED  &&  what_scenario != SCRIPTED_NETWORK) {
459 		return true;
460 	}
461 	// first test the list
462 	if (!forbidden_tools.empty()) {
463 		forbidden_t test(forbidden_t::forbid_tool, PLAYER_UNOWNED, tool_id, invalid_wt);
464 		uint8 player_nr = player  ?  player->get_player_nr() :  PLAYER_UNOWNED;
465 
466 		// first test waytype invalid_wt, then wt
467 		// .. and all players then specific player
468 		for(uint32 wti = 0; wti<4; wti++) {
469 			if (find_first(test) < forbidden_tools.get_count()) {
470 				// there is something, hence forbidden
471 				return false;
472 			}
473 			// logic to test all possible four cases
474 			sim::swap<sint16>( wt, test.waytype );
475 			if (test.waytype == invalid_wt) {
476 				sim::swap<uint8>( player_nr, test.player_nr );
477 				if (test.player_nr == PLAYER_UNOWNED) {
478 					break;
479 				}
480 			}
481 		}
482 	}
483 	// then call script if available
484 	if (what_scenario == SCRIPTED) {
485 		bool ok = true;
486 		const char* err = script->call_function(script_vm_t::FORCE, "is_tool_allowed", ok, (uint8)(player  ?  player->get_player_nr() : PLAYER_UNOWNED), tool_id, wt);
487 		return err != NULL  ||  ok;
488 	}
489 
490 	return true;
491 }
492 
is_work_allowed_here(const player_t * player,uint16 tool_id,sint16 wt,koord3d pos)493 const char* scenario_t::is_work_allowed_here(const player_t* player, uint16 tool_id, sint16 wt, koord3d pos)
494 {
495 	if (what_scenario != SCRIPTED  &&  what_scenario != SCRIPTED_NETWORK) {
496 		return NULL;
497 	}
498 
499 	// first test the list
500 	if (!forbidden_tools.empty()) {
501 		forbidden_t test(forbidden_t::forbid_tool_rect, PLAYER_UNOWNED, tool_id, invalid_wt);
502 		uint8 player_nr = player  ?  player->get_player_nr() :  PLAYER_UNOWNED;
503 
504 		// first test waytype invalid_wt, then wt
505 		// .. and all players then specific player
506 		for(uint32 wti = 0; wti<4; wti++) {
507 			for(uint32 i = find_first(test); i < forbidden_tools.get_count()  &&  *forbidden_tools[i] <= test; i++) {
508 				forbidden_t const& f = *forbidden_tools[i];
509 				// check rectangle
510 				if (f.pos_nw.x <= pos.x  &&  f.pos_nw.y <= pos.y  &&  pos.x <= f.pos_se.x  &&  pos.y <= f.pos_se.y) {
511 					// check height
512 					if (f.hmin <= pos.z  &&  pos.z <= f.hmax) {
513 						const char* err = f.error.c_str();
514 						if (err == NULL) {
515 							err = "";
516 						}
517 						return err;
518 					}
519 				}
520 			}
521 			// logic to test all possible four cases
522 			sim::swap<sint16>( wt, test.waytype );
523 			if (test.waytype == invalid_wt) {
524 				sim::swap<uint8>( player_nr, test.player_nr );
525 				if (test.player_nr == PLAYER_UNOWNED) {
526 					break;
527 				}
528 			}
529 		}
530 	}
531 
532 	// then call the script
533 	// cannot be done for two_click_tool_t's as they depend on routefinding,
534 	// which is done per client
535 	if (what_scenario == SCRIPTED) {
536 		static plainstring msg;
537 		const char *err = script->call_function(script_vm_t::FORCE, "is_work_allowed_here", msg, (uint8)(player ? player->get_player_nr() : PLAYER_UNOWNED), tool_id, pos);
538 
539 		return err == NULL ? msg.c_str() : NULL;
540 	}
541 	return NULL;
542 }
543 
544 
is_schedule_allowed(const player_t * player,const schedule_t * schedule)545 const char* scenario_t::is_schedule_allowed(const player_t* player, const schedule_t* schedule)
546 {
547 	// sanity checks
548 	if (schedule == NULL) {
549 		return "";
550 	}
551 	if (env_t::server) {
552 		// networkgame: allowed
553 		return NULL;
554 	}
555 	// call script
556 	if (what_scenario == SCRIPTED) {
557 		static plainstring msg;
558 		const char *err = script->call_function(script_vm_t::FORCE, "is_schedule_allowed", msg, (uint8)(player  ?  player->get_player_nr() : PLAYER_UNOWNED), schedule);
559 
560 		return err == NULL ? msg.c_str() : NULL;
561 	}
562 	return NULL;
563 }
564 
565 
is_convoy_allowed(const player_t * player,convoihandle_t cnv,depot_t * depot)566 const char* scenario_t::is_convoy_allowed(const player_t* player, convoihandle_t cnv, depot_t* depot)
567 {
568 	// sanity checks
569 	if (!cnv.is_bound()  ||  depot == NULL) {
570 		return "";
571 	}
572 	if (env_t::server) {
573 		// networkgame: allowed
574 		return NULL;
575 	}
576 	// call script
577 	if (what_scenario == SCRIPTED) {
578 		static plainstring msg;
579 		const char *err = script->call_function(script_vm_t::FORCE, "is_convoy_allowed", msg, (uint8)(player  ?  player->get_player_nr() : PLAYER_UNOWNED), cnv, (obj_t*)depot);
580 
581 		return err == NULL ? msg.c_str() : NULL;
582 	}
583 	return NULL;
584 
585 }
586 
587 
get_error_text()588 const char* scenario_t::get_error_text()
589 {
590 	if (script) {
591 		return script->get_error();
592 	}
593 	return NULL;
594 }
595 
596 
step()597 void scenario_t::step()
598 {
599 	if (!script) {
600 		// update texts at clients if info window open
601 		if (env_t::networkmode  &&  !env_t::server  &&  win_get_magic(magic_scenario_info)) {
602 			update_scenario_texts();
603 		}
604 		return;
605 	}
606 
607 	uint16 new_won = 0;
608 	uint16 new_lost = 0;
609 
610 	// first check, whether win/loss state of any player changed
611 	for(uint32 i=0; i<PLAYER_UNOWNED; i++) {
612 		player_t *player = welt->get_player(i);
613 		uint16 mask = 1 << i;
614 		// player exists and has not won/lost yet
615 		if (player  &&  (((won | lost) & mask)==0)) {
616 			sint32 percentage = 0;
617 			// callback
618 			script->prepare_callback("scenario_t_set_completed", 2, i, percentage );
619 			// call script
620 			const char *err = script->call_function(script_vm_t::QUEUE, "is_scenario_completed", percentage, i);
621 			// clear callback
622 			script->clear_pending_callback();
623 
624 			// script might have deleted the player
625 			player = welt->get_player(i);
626 			if (player == NULL) {
627 				continue;
628 			}
629 			// call completed?
630 			if (script_vm_t::is_call_suspended(err)) {
631 				continue;
632 			}
633 			player->set_scenario_completion(percentage);
634 			// won ?
635 			if (percentage >= 100) {
636 				new_won |= mask;
637 			}
638 			// lost ?
639 			else if (percentage < 0) {
640 				new_lost |= mask;
641 			}
642 		}
643 	}
644 
645 	update_won_lost(new_won, new_lost);
646 
647 	// update texts
648 	if (win_get_magic(magic_scenario_info) ) {
649 		update_scenario_texts();
650 	}
651 
652 	// update toolbars if necessary
653 	if (need_toolbar_update) {
654 		tool_t::update_toolbars();
655 		need_toolbar_update = false;
656 	}
657 }
658 
659 
new_month()660 void scenario_t::new_month()
661 {
662 	if (script) {
663 		script->call_function(script_vm_t::QUEUE, "new_month");
664 	}
665 }
666 
new_year()667 void scenario_t::new_year()
668 {
669 	if (script) {
670 		script->call_function(script_vm_t::QUEUE, "new_year");
671 	}
672 }
673 
674 
update_won_lost(uint16 new_won,uint16 new_lost)675 void scenario_t::update_won_lost(uint16 new_won, uint16 new_lost)
676 {
677 	// server sends the new state to the clients
678 	if (env_t::server  &&  (new_won | new_lost)) {
679 		nwc_scenario_t *nwc = new nwc_scenario_t();
680 		nwc->won = new_won;
681 		nwc->lost = new_lost;
682 		nwc->what = nwc_scenario_t::UPDATE_WON_LOST;
683 		network_send_all(nwc, true);
684 	}
685 
686 	// we are the champions
687 	if (new_won) {
688 		won |= new_won;
689 		// those are the losers
690 		new_lost = ~new_won;
691 	}
692 	if (new_lost) {
693 		lost |= new_lost;
694 	}
695 
696 	// notify active player
697 	if ( (new_won|new_lost) & (1<<welt->get_active_player_nr()) ) {
698 		// most likely result text has changed, force update
699 		result_text.update(script, welt->get_active_player(), true);
700 
701 		open_info_win();
702 	}
703 }
704 
705 
update_scenario_texts()706 void scenario_t::update_scenario_texts()
707 {
708 	player_t *player = welt->get_active_player();
709 	info_text.update(script, player);
710 	goal_text.update(script, player);
711 	rule_text.update(script, player);
712 	result_text.update(script, player);
713 	about_text.update(script, player);
714 	debug_text.update(script, player);
715 	description_text.update(script, player);
716 }
717 
718 
load_language_file(const char * filename)719 plainstring scenario_t::load_language_file(const char* filename)
720 {
721 	if (filename == NULL) {
722 		return "(null)";
723 	}
724 	std::string path = scenario_path.c_str();
725 	// try user language
726 	std::string wanted_file = path + translator::get_lang()->iso + PATH_SEPARATOR + filename;
727 
728 	const plainstring& cached = cached_text_files.get(wanted_file.c_str());
729 	if (cached != NULL) {
730 		// file already cached
731 		return cached;
732 	}
733 	// not cached: try to read file
734 	FILE* file = dr_fopen(wanted_file.c_str(), "rb");
735 	if (file == NULL) {
736 		// try English
737 		file = dr_fopen((path + "en" + PATH_SEPARATOR + filename).c_str(), "rb");
738 	}
739 	if (file == NULL) {
740 		// try scenario directory
741 		file = dr_fopen((path + filename).c_str(), "rb");
742 	}
743 
744 	plainstring text = "";
745 	if (file) {
746 		fseek(file,0,SEEK_END);
747 		long len = ftell(file);
748 		if(len>0) {
749 			char* const buf = MALLOCN(char, len + 1);
750 			fseek(file,0,SEEK_SET);
751 			fread(buf, 1, len, file);
752 			buf[len] = '\0';
753 			text = buf;
754 			free(buf);
755 		}
756 		fclose(file);
757 	}
758 	// store text to cache
759 	cached_text_files.put(wanted_file.c_str(), text);
760 
761 	return text;
762 }
763 
open_info_win(const char * tab) const764 bool scenario_t::open_info_win(const char* tab) const
765 {
766 	// pop up for the win
767 	scenario_info_t *si = (scenario_info_t*)win_get_magic(magic_scenario_info);
768 	if (si == NULL) {
769 		si = new scenario_info_t();
770 		if (create_win(si, w_info, magic_scenario_info) < 0) {
771 			// failed
772 			return false;
773 		}
774 	}
775 	si->open_tab(tab);
776 	return true; // dummy return value
777 }
778 
779 
rdwr(loadsave_t * file)780 void scenario_t::rdwr(loadsave_t *file)
781 {
782 	file->rdwr_short(what_scenario);
783 	if (file->is_version_less(111, 5)) {
784 		uint32 city_nr = 0;
785 		file->rdwr_long(city_nr);
786 		sint64 factor = 0;
787 		file->rdwr_longlong(factor);
788 		koord k(0,0);
789 		k.rdwr( file );
790 	}
791 
792 	if (what_scenario != SCRIPTED  &&  what_scenario != SCRIPTED_NETWORK) {
793 		if (file->is_loading()) {
794 			what_scenario = 0;
795 		}
796 		return;
797 	}
798 
799 	script_api::coordinate_transform_t::rdwr(file);
800 	file->rdwr_short(won);
801 	file->rdwr_short(lost);
802 	file->rdwr_str(scenario_name);
803 
804 	// load scripts and scenario files
805 	if (what_scenario == SCRIPTED) {
806 		if (file->is_loading()) {
807 			// load persistent scenario data
808 			plainstring str;
809 			file->rdwr_str(str);
810 			dbg->warning("scenario_t::rdwr", "loaded persistent scenario data: %s", str.c_str());
811 			if (env_t::networkmode   &&  !env_t::server) {
812 				// client playing network scenario game:
813 				// script files are not available
814 				what_scenario = SCRIPTED_NETWORK;
815 				script = NULL;
816 			}
817 			else {
818 				// load script
819 				cbuffer_t script_filename;
820 				// assume error
821 				rdwr_error = true;
822 				// try addon directory first
823 				if (env_t::default_settings.get_with_private_paks()) {
824 					scenario_path = ( std::string("addons/") + env_t::objfilename + "scenario/" + scenario_name.c_str() + "/").c_str();
825 					script_filename.printf("%sscenario.nut", scenario_path.c_str());
826 					rdwr_error = !load_script(script_filename);
827 				}
828 
829 				// failed, try scenario from pakset directory
830 				if (rdwr_error) {
831 					scenario_path = (env_t::program_dir + env_t::objfilename + "scenario/" + scenario_name.c_str() + "/").c_str();
832 					script_filename.clear();
833 					script_filename.printf("%sscenario.nut", scenario_path.c_str());
834 					rdwr_error = !load_script(script_filename);
835 				}
836 
837 				if (!rdwr_error) {
838 					load_compatibility_script();
839 					// restore persistent data
840 					const char* err = script->eval_string(str);
841 					if (err) {
842 						dbg->warning("scenario_t::rdwr", "error [%s] evaluating persistent scenario data", err);
843 						rdwr_error = true;
844 					}
845 					// load translations
846 					translator::load_files_from_folder( scenario_path.c_str(), "scenario" );
847 					// callback
848 					script->register_callback(&scenario_t::set_completion, "scenario_t_set_completed");
849 				}
850 				else {
851 					dbg->warning("scenario_t::rdwr", "could not load script file %s", (const char*)script_filename);
852 				}
853 			}
854 		}
855 		else {
856 			plainstring str;
857 			script->call_function(script_vm_t::FORCEX, "save", str);
858 			dbg->warning("scenario_t::rdwr", "write persistent scenario data: %s", str.c_str());
859 			file->rdwr_str(str);
860 		}
861 	}
862 
863 	// load forbidden tools
864 	if (file->is_loading()) {
865 		clear_ptr_vector(forbidden_tools);
866 	}
867 	uint32 count = forbidden_tools.get_count();
868 	file->rdwr_long(count);
869 
870 	for(uint32 i=0; i<count; i++) {
871 		if (file->is_loading()) {
872 			forbidden_tools.append(new forbidden_t());
873 		}
874 		forbidden_tools[i]->rdwr(file);
875 	}
876 
877 	// cached strings
878 	if (file->is_version_atleast(120, 3)) {
879 		dynamic_string::rdwr_cache(file);
880 	}
881 
882 	if (what_scenario == SCRIPTED  &&  file->is_loading()  &&  !rdwr_error) {
883 		const char* err = script->call_function(script_vm_t::FORCEX, "resume_game");
884 		if (err) {
885 			dbg->warning("scenario_t::rdwr", "error [%s] calling resume_game", err);
886 			rdwr_error = true;
887 		}
888 	}
889 }
890 
rotate90(const sint16 y_size)891 void scenario_t::rotate90(const sint16 y_size)
892 {
893 	for(uint32 i=0; i<forbidden_tools.get_count(); i++) {
894 		forbidden_tools[i]->rotate90(y_size);
895 	}
896 }
897 
898 
899 // return percentage completed
get_completion(int player_nr)900 sint32 scenario_t::get_completion(int player_nr)
901 {
902 	if ( what_scenario == 0  ||  player_nr < 0  ||  player_nr >= PLAYER_UNOWNED) {
903 		return 0;
904 	}
905 	// check if won / lost
906 	uint32 pl = player_nr;
907 	if (won & (1<<player_nr)) {
908 		return 100;
909 	}
910 	else if (lost & (1<<player_nr)) {
911 		return -1;
912 	}
913 
914 	sint32 percentage = 0;
915 	player_t *player = welt->get_player(player_nr);
916 
917 	if ( what_scenario == SCRIPTED ) {
918 		// take cached value
919 		if (player) {
920 			percentage = player->get_scenario_completion();
921 		}
922 	}
923 	else if ( what_scenario == SCRIPTED_NETWORK ) {
924 		cbuffer_t buf;
925 		buf.printf("is_scenario_completed(%d)", pl);
926 		const char *ret = dynamic_string::fetch_result((const char*)buf, NULL, NULL);
927 		percentage = ret ? atoi(ret) : 0;
928 		// cache value
929 		if (player) {
930 			player->set_scenario_completion(percentage);
931 		}
932 	}
933 	return min( 100, percentage);
934 }
935 
936 
set_completion(sint32 player_nr,sint32 percentage)937 bool scenario_t::set_completion(sint32 player_nr, sint32 percentage)
938 {
939 	if ( player_nr < 0  ||  player_nr >= PLAYER_UNOWNED) {
940 		return false;
941 	}
942 	player_t *player = welt->get_player(player_nr);
943 	if (player == NULL) {
944 		return false;
945 	}
946 	player->set_scenario_completion(percentage - player->get_scenario_completion());
947 
948 	uint16 mask = 1 << player_nr;
949 	// check won/lost
950 	if (percentage < 0  &&  (lost & mask)==0) {
951 		update_won_lost(0, mask);
952 	}
953 	else if (percentage >= 100  &&  (won & mask)==0) {
954 		update_won_lost(mask, 0);
955 	}
956 	return true;
957 }
958 
959 
eval_string(const char * squirrel_string) const960 const char* scenario_t::eval_string(const char* squirrel_string) const
961 {
962 	if (what_scenario == SCRIPTED) {
963 		return script->eval_string(squirrel_string);
964 	}
965 	return "";
966 }
967