1 /*
2  * Copyright (C) 2002-2020 by the Widelands Development Team
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License
6  * as published by the Free Software Foundation; either version 2
7  * of the License, or (at your option) any later version.
8  *
9  * This program 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 this program; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
17  *
18  */
19 
20 #include "logic/map_objects/tribes/production_program.h"
21 
22 #include <cassert>
23 #include <memory>
24 
25 #include "base/i18n.h"
26 #include "base/macros.h"
27 #include "base/wexception.h"
28 #include "config.h"
29 #include "economy/economy.h"
30 #include "economy/flag.h"
31 #include "economy/input_queue.h"
32 #include "economy/wares_queue.h"
33 #include "io/filesystem/layered_filesystem.h"
34 #include "logic/game.h"
35 #include "logic/game_data_error.h"
36 #include "logic/map_objects/checkstep.h"
37 #include "logic/map_objects/findimmovable.h"
38 #include "logic/map_objects/findnode.h"
39 #include "logic/map_objects/tribes/productionsite.h"
40 #include "logic/map_objects/tribes/soldier.h"
41 #include "logic/map_objects/tribes/soldiercontrol.h"
42 #include "logic/map_objects/tribes/trainingsite.h"
43 #include "logic/map_objects/tribes/tribe_descr.h"
44 #include "logic/map_objects/tribes/worker_program.h"
45 #include "logic/map_objects/world/resource_description.h"
46 #include "logic/map_objects/world/world.h"
47 #include "logic/mapregion.h"
48 #include "logic/player.h"
49 #include "sound/note_sound.h"
50 #include "sound/sound_handler.h"
51 
52 namespace Widelands {
53 
54 namespace {
55 /// If the iterator contents match the string, increment the iterator. Returns whether it matched.
match_and_skip(const std::vector<std::string> & args,std::vector<std::string>::const_iterator & it,const std::string & matchme)56 bool match_and_skip(const std::vector<std::string>& args,
57                     std::vector<std::string>::const_iterator& it,
58                     const std::string& matchme) {
59 	const bool result = (it != args.end()) && (*it == matchme);
60 	if (result) {
61 		++it;
62 	}
63 	return result;
64 }
65 
create_economy_condition(const std::string & item,const ProductionSiteDescr & descr,const Tribes & tribes)66 ProductionProgram::ActReturn::Condition* create_economy_condition(const std::string& item,
67                                                                   const ProductionSiteDescr& descr,
68                                                                   const Tribes& tribes) {
69 	DescriptionIndex index = tribes.ware_index(item);
70 	if (tribes.ware_exists(index)) {
71 		descr.ware_demand_checks()->insert(index);
72 		return new ProductionProgram::ActReturn::EconomyNeedsWare(index);
73 	} else if (tribes.worker_exists(tribes.worker_index(item))) {
74 		index = tribes.worker_index(item);
75 		descr.worker_demand_checks()->insert(index);
76 		return new ProductionProgram::ActReturn::EconomyNeedsWorker(index);
77 	} else {
78 		throw GameDataError("Expected ware or worker type but found '%s'", item.c_str());
79 	}
80 }
81 
parse_training_attribute(const std::string & argument)82 TrainingAttribute parse_training_attribute(const std::string& argument) {
83 	if (argument == "health") {
84 		return TrainingAttribute::kHealth;
85 	} else if (argument == "attack") {
86 		return TrainingAttribute::kAttack;
87 	} else if (argument == "defense") {
88 		return TrainingAttribute::kDefense;
89 	} else if (argument == "evade") {
90 		return TrainingAttribute::kEvade;
91 	} else {
92 		throw GameDataError(
93 		   "Expected health|attack|defense|evade after 'soldier' but found '%s'", argument.c_str());
94 	}
95 }
96 }  // namespace
97 
~Action()98 ProductionProgram::Action::~Action() {
99 }
100 
get_building_work(Game &,ProductionSite &,Worker &) const101 bool ProductionProgram::Action::get_building_work(Game&, ProductionSite&, Worker&) const {
102 	return false;
103 }
104 
building_work_failed(Game &,ProductionSite &,Worker &) const105 void ProductionProgram::Action::building_work_failed(Game&, ProductionSite&, Worker&) const {
106 }
107 
108 ProductionProgram::Groups
parse_ware_type_groups(std::vector<std::string>::const_iterator begin,std::vector<std::string>::const_iterator end,const ProductionSiteDescr & descr,const Tribes & tribes)109 ProductionProgram::parse_ware_type_groups(std::vector<std::string>::const_iterator begin,
110                                           std::vector<std::string>::const_iterator end,
111                                           const ProductionSiteDescr& descr,
112                                           const Tribes& tribes) {
113 	ProductionProgram::Groups result;
114 
115 	for (auto& it = begin; it != end; ++it) {
116 		const std::pair<std::string, std::string> names_to_amount =
117 		   read_key_value_pair(*it, ':', "1");
118 		const uint8_t amount = read_positive(names_to_amount.second);
119 		uint8_t max_amount = 0;
120 		std::set<std::pair<DescriptionIndex, WareWorker>> ware_worker_names;
121 		for (const std::string& item_name : split_string(names_to_amount.first, ",")) {
122 			// Try as ware
123 			WareWorker type = wwWARE;
124 			DescriptionIndex item_index = tribes.ware_index(item_name);
125 			if (!tribes.ware_exists(item_index)) {
126 				item_index = tribes.worker_index(item_name);
127 				if (tribes.worker_exists(item_index)) {
128 					// It is a worker
129 					type = wwWORKER;
130 				} else {
131 					throw GameDataError(
132 					   "Expected ware or worker type but found '%s'", item_name.c_str());
133 				}
134 			}
135 
136 			// Sanity checks
137 			bool found = false;
138 			const BillOfMaterials& inputs =
139 			   (type == wwWARE) ? descr.input_wares() : descr.input_workers();
140 			for (const WareAmount& input : inputs) {
141 				if (input.first == item_index) {
142 					max_amount += input.second;
143 					found = true;
144 					break;
145 				}
146 			}
147 			if (!found) {
148 				throw GameDataError(
149 				   "%s was not declared in the building's 'inputs' table", item_name.c_str());
150 			}
151 
152 			if (max_amount < amount) {
153 				throw GameDataError(
154 				   "Ware/worker count is %u but (total) input storage capacity of "
155 				   "the specified ware type(s) is only %u, so the ware/worker requirement can "
156 				   "never be fulfilled by the site",
157 				   static_cast<unsigned int>(amount), static_cast<unsigned int>(max_amount));
158 			}
159 			// Add item
160 			ware_worker_names.insert(std::make_pair(item_index, type));
161 		}
162 		// Add set
163 		result.push_back(std::make_pair(ware_worker_names, amount));
164 	}
165 	if (result.empty()) {
166 		throw GameDataError("No wares or workers found");
167 	}
168 	return result;
169 }
170 
171 BillOfMaterials
parse_bill_of_materials(const std::vector<std::string> & arguments,WareWorker ww,const ProductionSiteDescr & descr,const Tribes & tribes)172 ProductionProgram::parse_bill_of_materials(const std::vector<std::string>& arguments,
173                                            WareWorker ww,
174                                            const ProductionSiteDescr& descr,
175                                            const Tribes& tribes) {
176 	BillOfMaterials result;
177 	for (const std::string& argument : arguments) {
178 		const std::pair<std::string, std::string> produceme = read_key_value_pair(argument, ':', "1");
179 
180 		const DescriptionIndex index = ww == WareWorker::wwWARE ?
181 		                                  tribes.safe_ware_index(produceme.first) :
182 		                                  tribes.safe_worker_index(produceme.first);
183 
184 		// Verify the building outputs
185 		switch (ww) {
186 		case WareWorker::wwWARE:
187 			if (!descr.is_output_ware_type(index)) {
188 				throw GameDataError("Ware '%s' is not listed in the building's 'outputs' table",
189 				                    produceme.first.c_str());
190 			}
191 			break;
192 		case WareWorker::wwWORKER:
193 			if (!descr.is_output_worker_type(index)) {
194 				throw GameDataError("Worker '%s' is not listed in the building's 'outputs' table",
195 				                    produceme.first.c_str());
196 			}
197 			break;
198 		}
199 
200 		result.push_back(std::make_pair(index, read_positive(produceme.second)));
201 	}
202 	return result;
203 }
204 
~Condition()205 ProductionProgram::ActReturn::Condition::~Condition() {
206 }
207 
Negation(const std::vector<std::string> & arguments,std::vector<std::string>::const_iterator & begin,std::vector<std::string>::const_iterator & end,const ProductionSiteDescr & descr,const Tribes & tribes)208 ProductionProgram::ActReturn::Negation::Negation(const std::vector<std::string>& arguments,
209                                                  std::vector<std::string>::const_iterator& begin,
210                                                  std::vector<std::string>::const_iterator& end,
211                                                  const ProductionSiteDescr& descr,
212                                                  const Tribes& tribes)
213    : operand(create_condition(arguments, begin, end, descr, tribes)) {
214 }
215 
~Negation()216 ProductionProgram::ActReturn::Negation::~Negation() {
217 	delete operand;
218 }
evaluate(const ProductionSite & ps) const219 bool ProductionProgram::ActReturn::Negation::evaluate(const ProductionSite& ps) const {
220 	return !operand->evaluate(ps);
221 }
222 
223 // Just a dummy to satisfy the superclass interface. Returns an empty string.
description(const Tribes & t) const224 std::string ProductionProgram::ActReturn::Negation::description(const Tribes& t) const {
225 	return operand->description_negation(t);
226 }
227 
228 // Just a dummy to satisfy the superclass interface. Returns an empty string.
description_negation(const Tribes & t) const229 std::string ProductionProgram::ActReturn::Negation::description_negation(const Tribes& t) const {
230 	return operand->description(t);
231 }
232 
evaluate(const ProductionSite & ps) const233 bool ProductionProgram::ActReturn::EconomyNeedsWare::evaluate(const ProductionSite& ps) const {
234 	return ps.get_economy(wwWARE)->needs_ware_or_worker(ware_type);
235 }
236 std::string
description(const Tribes & tribes) const237 ProductionProgram::ActReturn::EconomyNeedsWare::description(const Tribes& tribes) const {
238 	/** TRANSLATORS: e.g. Completed/Skipped/Did not start ... because the economy needs the ware
239 	 * '%s' */
240 	std::string result = (boost::format(_("the economy needs the ware ‘%s’")) %
241 	                      tribes.get_ware_descr(ware_type)->descname())
242 	                        .str();
243 	return result;
244 }
245 std::string
description_negation(const Tribes & tribes) const246 ProductionProgram::ActReturn::EconomyNeedsWare::description_negation(const Tribes& tribes) const {
247 	/** TRANSLATORS: e.g. Completed/Skipped/Did not start ... because the economy doesn't need the
248 	 * ware '%s' */
249 	std::string result = (boost::format(_("the economy doesn’t need the ware ‘%s’")) %
250 	                      tribes.get_ware_descr(ware_type)->descname())
251 	                        .str();
252 	return result;
253 }
254 
evaluate(const ProductionSite & ps) const255 bool ProductionProgram::ActReturn::EconomyNeedsWorker::evaluate(const ProductionSite& ps) const {
256 	return ps.get_economy(wwWORKER)->needs_ware_or_worker(worker_type);
257 }
258 std::string
description(const Tribes & tribes) const259 ProductionProgram::ActReturn::EconomyNeedsWorker::description(const Tribes& tribes) const {
260 	/** TRANSLATORS: e.g. Completed/Skipped/Did not start ... because the economy needs the worker
261 	 * '%s' */
262 	std::string result = (boost::format(_("the economy needs the worker ‘%s’")) %
263 	                      tribes.get_worker_descr(worker_type)->descname())
264 	                        .str();
265 	return result;
266 }
267 
268 std::string
description_negation(const Tribes & tribes) const269 ProductionProgram::ActReturn::EconomyNeedsWorker::description_negation(const Tribes& tribes) const {
270 	/** TRANSLATORS: e.g. Completed/Skipped/Did not start ... */
271 	/** TRANSLATORS:      ... because the economy doesn’t need the worker '%s' */
272 	std::string result = (boost::format(_("the economy doesn’t need the worker ‘%s’")) %
273 	                      tribes.get_worker_descr(worker_type)->descname())
274 	                        .str();
275 	return result;
276 }
277 
SiteHas(std::vector<std::string>::const_iterator begin,std::vector<std::string>::const_iterator end,const ProductionSiteDescr & descr,const Tribes & tribes)278 ProductionProgram::ActReturn::SiteHas::SiteHas(std::vector<std::string>::const_iterator begin,
279                                                std::vector<std::string>::const_iterator end,
280                                                const ProductionSiteDescr& descr,
281                                                const Tribes& tribes) {
282 	try {
283 		group = parse_ware_type_groups(begin, end, descr, tribes).front();
284 	} catch (const GameDataError& e) {
285 		throw GameDataError("Expected <ware or worker>[,<ware or worker>[,...]][:<amount>] after "
286 		                    "'site has' but got %s",
287 		                    e.what());
288 	}
289 }
evaluate(const ProductionSite & ps) const290 bool ProductionProgram::ActReturn::SiteHas::evaluate(const ProductionSite& ps) const {
291 	uint8_t count = group.second;
292 	for (InputQueue* ip_queue : ps.inputqueues()) {
293 		for (const auto& input_type : group.first) {
294 			if (input_type.first == ip_queue->get_index() &&
295 			    input_type.second == ip_queue->get_type()) {
296 				uint8_t const filled = ip_queue->get_filled();
297 				if (count <= filled)
298 					return true;
299 				count -= filled;
300 				break;
301 			}
302 		}
303 	}
304 	return false;
305 }
306 
description(const Tribes & tribes) const307 std::string ProductionProgram::ActReturn::SiteHas::description(const Tribes& tribes) const {
308 	std::vector<std::string> condition_list;
309 	for (const auto& entry : group.first) {
310 		condition_list.push_back(entry.second == wwWARE ?
311 		                            tribes.get_ware_descr(entry.first)->descname() :
312 		                            tribes.get_worker_descr(entry.first)->descname());
313 	}
314 	std::string condition = i18n::localize_list(condition_list, i18n::ConcatenateWith::AND);
315 	if (1 < group.second) {
316 		condition =
317 		   /** TRANSLATORS: This is an item in a list of wares, e.g. "3x water": */
318 		   /** TRANSLATORS:    %1$i = "3" */
319 		   /** TRANSLATORS:    %2$s = "water" */
320 		   (boost::format(_("%1$ix %2$s")) % static_cast<unsigned int>(group.second) % condition)
321 		      .str();
322 	}
323 
324 	std::string result =
325 	   /** TRANSLATORS: %s is a list of wares*/
326 	   (boost::format(_("the building has the following wares: %s")) % condition).str();
327 	return result;
328 }
329 
330 std::string
description_negation(const Tribes & tribes) const331 ProductionProgram::ActReturn::SiteHas::description_negation(const Tribes& tribes) const {
332 	std::vector<std::string> condition_list;
333 	for (const auto& entry : group.first) {
334 		condition_list.push_back(entry.second == wwWARE ?
335 		                            tribes.get_ware_descr(entry.first)->descname() :
336 		                            tribes.get_worker_descr(entry.first)->descname());
337 	}
338 	std::string condition = i18n::localize_list(condition_list, i18n::ConcatenateWith::AND);
339 	if (1 < group.second) {
340 		condition =
341 		   /** TRANSLATORS: This is an item in a list of wares, e.g. "3x water": */
342 		   /** TRANSLATORS:    %1$i = "3" */
343 		   /** TRANSLATORS:    %2$s = "water" */
344 		   (boost::format(_("%1$ix %2$s")) % static_cast<unsigned int>(group.second) % condition)
345 		      .str();
346 	}
347 
348 	std::string result =
349 	   /** TRANSLATORS: %s is a list of wares*/
350 	   (boost::format(_("the building doesn’t have the following wares: %s")) % condition).str();
351 	return result;
352 }
353 
evaluate(const ProductionSite & ps) const354 bool ProductionProgram::ActReturn::WorkersNeedExperience::evaluate(const ProductionSite& ps) const {
355 	ProductionSite::WorkingPosition const* const wp = ps.working_positions_;
356 	for (uint32_t i = ps.descr().nr_working_positions(); i;)
357 		if (wp[--i].worker->needs_experience())
358 			return true;
359 	return false;
360 }
description(const Tribes &) const361 std::string ProductionProgram::ActReturn::WorkersNeedExperience::description(const Tribes&) const {
362 	/** TRANSLATORS: 'Completed/Skipped/Did not start ... because a worker needs experience'. */
363 	return _("a worker needs experience");
364 }
365 
366 std::string
description_negation(const Tribes &) const367 ProductionProgram::ActReturn::WorkersNeedExperience::description_negation(const Tribes&) const {
368 	/** TRANSLATORS: 'Completed/Skipped/Did not start ... because the workers need no experience'. */
369 	return _("the workers need no experience");
370 }
371 
372 ProductionProgram::ActReturn::Condition*
create_condition(const std::vector<std::string> & arguments,std::vector<std::string>::const_iterator & begin,std::vector<std::string>::const_iterator & end,const ProductionSiteDescr & descr,const Tribes & tribes)373 ProductionProgram::ActReturn::create_condition(const std::vector<std::string>& arguments,
374                                                std::vector<std::string>::const_iterator& begin,
375                                                std::vector<std::string>::const_iterator& end,
376                                                const ProductionSiteDescr& descr,
377                                                const Tribes& tribes) {
378 	if (begin == end) {
379 		throw GameDataError("Expected a condition after '%s'", (begin - 1)->c_str());
380 	}
381 	try {
382 		if (match_and_skip(arguments, begin, "not")) {
383 			return new ActReturn::Negation(arguments, begin, end, descr, tribes);
384 		} else if (match_and_skip(arguments, begin, "economy")) {
385 			if (!match_and_skip(arguments, begin, "needs")) {
386 				throw GameDataError("Expected 'needs' after 'economy' but found '%s'", begin->c_str());
387 			}
388 			return create_economy_condition(*begin, descr, tribes);
389 		} else if (match_and_skip(arguments, begin, "site")) {
390 			if (!match_and_skip(arguments, begin, "has")) {
391 				throw GameDataError("Expected 'has' after 'site' but found '%s'", begin->c_str());
392 			}
393 			return new ProductionProgram::ActReturn::SiteHas(begin, end, descr, tribes);
394 		} else if (match_and_skip(arguments, begin, "workers")) {
395 			if (!match_and_skip(arguments, begin, "need")) {
396 				throw GameDataError(
397 				   "Expected 'need experience' after 'workers' but found '%s'", begin->c_str());
398 			}
399 			if (!match_and_skip(arguments, begin, "experience")) {
400 				throw GameDataError(
401 				   "Expected 'experience' after 'workers need' but found '%s'", begin->c_str());
402 			}
403 			return new ProductionProgram::ActReturn::WorkersNeedExperience();
404 		} else {
405 			throw GameDataError("Expected not|economy|site|workers after '%s' but found '%s'",
406 			                    (begin - 1)->c_str(), begin->c_str());
407 		}
408 	} catch (const WException& e) {
409 		throw GameDataError("Invalid condition. %s", e.what());
410 	}
411 }
412 
ActReturn(const std::vector<std::string> & arguments,const ProductionSiteDescr & descr,const Tribes & tribes)413 ProductionProgram::ActReturn::ActReturn(const std::vector<std::string>& arguments,
414                                         const ProductionSiteDescr& descr,
415                                         const Tribes& tribes) {
416 	if (arguments.empty()) {
417 		throw GameDataError("Usage: return=failed|completed|skipped [when|unless <conditions>]");
418 	}
419 	auto begin = arguments.begin();
420 
421 	if (match_and_skip(arguments, begin, "failed")) {
422 		result_ = ProgramResult::kFailed;
423 	} else if (match_and_skip(arguments, begin, "completed")) {
424 		result_ = ProgramResult::kCompleted;
425 	} else if (match_and_skip(arguments, begin, "no_stats")) {
426 		// TODO(GunChleoc): Savegame cpmpatibility - remove no_stats after Build 21
427 		result_ = ProgramResult::kCompleted;
428 	} else if (match_and_skip(arguments, begin, "skipped")) {
429 		result_ = ProgramResult::kSkipped;
430 	} else {
431 		throw GameDataError("Usage: return=failed|completed|skipped [when|unless <conditions>]");
432 	}
433 
434 	// Parse all arguments starting from the given iterator into our 'conditions_', splitting
435 	// individual conditions by the given 'separator'
436 	auto parse_conditions = [this, &descr, &tribes](const std::vector<std::string>& args,
437 	                                                std::vector<std::string>::const_iterator it,
438 	                                                const std::string& separator) {
439 		while (it != args.end()) {
440 			auto end = it + 1;
441 			while (end != args.end() && *end != separator) {
442 				++end;
443 			}
444 			if (it == end) {
445 				throw GameDataError(
446 				   "Expected: [%s] <condition> after '%s'", separator.c_str(), (it - 1)->c_str());
447 			}
448 
449 			conditions_.push_back(create_condition(args, it, end, descr, tribes));
450 			match_and_skip(args, end, separator);
451 			it = end;
452 		}
453 	};
454 
455 	is_when_ = true;
456 	if (begin != arguments.end()) {
457 		if (match_and_skip(arguments, begin, "when")) {
458 			parse_conditions(arguments, begin, "and");
459 		} else if (match_and_skip(arguments, begin, "unless")) {
460 			is_when_ = false;
461 			parse_conditions(arguments, begin, "or");
462 		} else {
463 			throw GameDataError("Expected when|unless but found '%s'", begin->c_str());
464 		}
465 	}
466 }
467 
~ActReturn()468 ProductionProgram::ActReturn::~ActReturn() {
469 	for (Condition* condition : conditions_) {
470 		delete condition;
471 	}
472 }
473 
execute(Game & game,ProductionSite & ps) const474 void ProductionProgram::ActReturn::execute(Game& game, ProductionSite& ps) const {
475 	if (!conditions_.empty()) {
476 		std::vector<std::string> condition_list;
477 		if (is_when_) {  //  'when a and b and ...' (all conditions must be true)
478 			for (const Condition* condition : conditions_) {
479 				if (!condition->evaluate(ps)) {   //  A condition is false,
480 					return ps.program_step(game);  //  continue program.
481 				}
482 				condition_list.push_back(condition->description(game.tribes()));
483 			}
484 		} else {  //  "unless a or b or ..." (all conditions must be false)
485 			for (const Condition* condition : conditions_) {
486 				if (condition->evaluate(ps)) {    //  A condition is true,
487 					return ps.program_step(game);  //  continue program.
488 				}
489 				condition_list.push_back(condition->description_negation(game.tribes()));
490 			}
491 		}
492 		std::string condition_string =
493 		   i18n::localize_list(condition_list, i18n::ConcatenateWith::AND);
494 
495 		std::string result_string;
496 		switch (result_) {
497 		case ProgramResult::kFailed: {
498 			/** TRANSLATORS: "Did not start working because the economy needs the ware '%s'" */
499 			result_string = (boost::format(_("Did not start %1$s because %2$s")) %
500 			                 ps.top_state().program->descname() % condition_string)
501 			                   .str();
502 		} break;
503 		case ProgramResult::kCompleted: {
504 			/** TRANSLATORS: "Completed working because the economy needs the ware '%s'" */
505 			result_string = (boost::format(_("Completed %1$s because %2$s")) %
506 			                 ps.top_state().program->descname() % condition_string)
507 			                   .str();
508 		} break;
509 		case ProgramResult::kSkipped: {
510 			/** TRANSLATORS: "Skipped working because the economy needs the ware '%s'" */
511 			result_string = (boost::format(_("Skipped %1$s because %2$s")) %
512 			                 ps.top_state().program->descname() % condition_string)
513 			                   .str();
514 		} break;
515 		case ProgramResult::kNone: {
516 			// TODO(GunChleoc): Same as skipped - is this on purpose?
517 			result_string = (boost::format(_("Skipped %1$s because %2$s")) %
518 			                 ps.top_state().program->descname() % condition_string)
519 			                   .str();
520 		}
521 		}
522 		if (ps.production_result() != ps.descr().out_of_resource_heading() ||
523 		    ps.descr().out_of_resource_heading().empty()) {
524 			ps.set_production_result(result_string);
525 		}
526 	}
527 	return ps.program_end(game, result_);
528 }
529 
ActCall(const std::vector<std::string> & arguments,const ProductionSiteDescr & descr)530 ProductionProgram::ActCall::ActCall(const std::vector<std::string>& arguments,
531                                     const ProductionSiteDescr& descr) {
532 	if (arguments.size() < 1 || arguments.size() > 4) {
533 		throw GameDataError(
534 		   "Usage: call=<program name> [on failure|completion|skip fail|complete|skip|repeat]");
535 	}
536 
537 	//  Initialize with default handling methods.
538 	handling_methods_[program_result_index(ProgramResult::kFailed)] =
539 	   ProgramResultHandlingMethod::kContinue;
540 	handling_methods_[program_result_index(ProgramResult::kCompleted)] =
541 	   ProgramResultHandlingMethod::kContinue;
542 	handling_methods_[program_result_index(ProgramResult::kSkipped)] =
543 	   ProgramResultHandlingMethod::kContinue;
544 
545 	// Fetch program to call
546 	const std::string& program_name = arguments.front();
547 	const ProductionSiteDescr::Programs& programs = descr.programs();
548 	ProductionSiteDescr::Programs::const_iterator const it = programs.find(program_name);
549 	if (it == programs.end()) {
550 		throw GameDataError("The program '%s' has not (yet) been declared in %s "
551 		                    "(wrong declaration order?)",
552 		                    program_name.c_str(), descr.name().c_str());
553 	}
554 	program_ = it->second.get();
555 
556 	//  Override with specified handling methods.
557 	if (arguments.size() > 1) {
558 		if (arguments.at(1) != "on") {
559 			throw GameDataError("Expected 'on' keyword in second position");
560 		}
561 
562 		ProgramResult result_to_set_method_for;
563 		if (arguments.at(2) == "failure") {
564 			if (handling_methods_[program_result_index(ProgramResult::kFailed)] !=
565 			    ProgramResultHandlingMethod::kContinue) {
566 				throw GameDataError("%s handling method already defined", "failure");
567 			}
568 			result_to_set_method_for = ProgramResult::kFailed;
569 		} else if (arguments.at(2) == "completion") {
570 			if (handling_methods_[program_result_index(ProgramResult::kCompleted)] !=
571 			    ProgramResultHandlingMethod::kContinue) {
572 				throw GameDataError("%s handling method already defined", "completion");
573 			}
574 			result_to_set_method_for = ProgramResult::kCompleted;
575 		} else if (arguments.at(2) == "skip") {
576 			if (handling_methods_[program_result_index(ProgramResult::kSkipped)] !=
577 			    ProgramResultHandlingMethod::kContinue) {
578 				throw GameDataError("%s handling method already defined", "skip");
579 			}
580 			result_to_set_method_for = ProgramResult::kSkipped;
581 		} else {
582 			throw GameDataError(
583 			   "Expected failure|completion|skip after 'on' but found '%s'", arguments.at(2).c_str());
584 		}
585 
586 		ProgramResultHandlingMethod handling_method;
587 		if (arguments.at(3) == "fail") {
588 			handling_method = ProgramResultHandlingMethod::kFail;
589 		} else if (arguments.at(3) == "complete") {
590 			handling_method = ProgramResultHandlingMethod::kComplete;
591 		} else if (arguments.at(3) == "skip") {
592 			handling_method = ProgramResultHandlingMethod::kSkip;
593 		} else if (arguments.at(3) == "repeat") {
594 			handling_method = ProgramResultHandlingMethod::kRepeat;
595 		} else {
596 			throw GameDataError("Expected fail|complete|skip|repeat in final position but found '%s'",
597 			                    arguments.at(3).c_str());
598 		}
599 		handling_methods_[program_result_index(result_to_set_method_for)] = handling_method;
600 	}
601 }
602 
execute(Game & game,ProductionSite & ps) const603 void ProductionProgram::ActCall::execute(Game& game, ProductionSite& ps) const {
604 	ProgramResult const program_result = ps.top_state().phase;
605 
606 	if (program_result == ProgramResult::kNone) {  //  The program has not yet been called.
607 		return ps.program_start(game, program_->name());
608 	}
609 
610 	switch (handling_methods_[program_result_index(program_result)]) {
611 	case ProgramResultHandlingMethod::kFail:
612 	case ProgramResultHandlingMethod::kComplete:
613 	case ProgramResultHandlingMethod::kSkip:
614 		return ps.program_end(game, ProgramResult::kNone);
615 	case ProgramResultHandlingMethod::kContinue:
616 		return ps.program_step(game);
617 	case ProgramResultHandlingMethod::kRepeat:
618 		ps.top_state().phase = ProgramResult::kNone;
619 		ps.program_timer_ = true;
620 		ps.program_time_ = ps.schedule_act(game, 10);
621 		break;
622 	}
623 }
624 
ActCallWorker(const std::vector<std::string> & arguments,const std::string & production_program_name,ProductionSiteDescr * descr,const Tribes & tribes)625 ProductionProgram::ActCallWorker::ActCallWorker(const std::vector<std::string>& arguments,
626                                                 const std::string& production_program_name,
627                                                 ProductionSiteDescr* descr,
628                                                 const Tribes& tribes) {
629 	if (arguments.size() != 1) {
630 		throw GameDataError("Usage: callworker=<worker program name>");
631 	}
632 
633 	program_ = arguments.front();
634 
635 	//  Quote from "void ProductionSite::program_act(Game &)":
636 	//  "Always main worker is doing stuff"
637 	const WorkerDescr& main_worker_descr =
638 	   *tribes.get_worker_descr(descr->working_positions().front().first);
639 
640 	//  This will fail unless the main worker has a program with the given
641 	//  name, so it also validates the parameter.
642 	const WorkareaInfo& worker_workarea_info =
643 	   main_worker_descr.get_program(program_)->get_workarea_info();
644 
645 	for (const auto& area_info : worker_workarea_info) {
646 		std::set<std::string>& building_radius_infos = descr->workarea_info_[area_info.first];
647 
648 		for (const std::string& worker_name : area_info.second) {
649 			std::string description = descr->name();
650 			description += ' ';
651 			description += production_program_name;
652 			description += " worker ";
653 			description += main_worker_descr.name();
654 			description += worker_name;
655 			building_radius_infos.insert(description);
656 		}
657 	}
658 }
659 
execute(Game & game,ProductionSite & ps) const660 void ProductionProgram::ActCallWorker::execute(Game& game, ProductionSite& ps) const {
661 	// Always main worker is doing stuff
662 	ps.working_positions_[ps.main_worker_].worker->update_task_buildingwork(game);
663 }
664 
get_building_work(Game & game,ProductionSite & psite,Worker & worker) const665 bool ProductionProgram::ActCallWorker::get_building_work(Game& game,
666                                                          ProductionSite& psite,
667                                                          Worker& worker) const {
668 	ProductionSite::State& state = psite.top_state();
669 	if (state.phase == ProgramResult::kNone) {
670 		worker.start_task_program(game, program());
671 		state.phase = ProgramResult::kFailed;
672 		return true;
673 	} else {
674 		psite.program_step(game);
675 		return false;
676 	}
677 }
678 
building_work_failed(Game & game,ProductionSite & psite,Worker &) const679 void ProductionProgram::ActCallWorker::building_work_failed(Game& game,
680                                                             ProductionSite& psite,
681                                                             Worker&) const {
682 	psite.program_end(game, ProgramResult::kFailed);
683 }
684 
ActSleep(const std::vector<std::string> & arguments)685 ProductionProgram::ActSleep::ActSleep(const std::vector<std::string>& arguments) {
686 	if (arguments.size() != 1) {
687 		throw GameDataError("Usage: sleep=<duration>");
688 	}
689 	duration_ = read_positive(arguments.front());
690 }
691 
execute(Game & game,ProductionSite & ps) const692 void ProductionProgram::ActSleep::execute(Game& game, ProductionSite& ps) const {
693 	return ps.program_step(game, duration_ ? duration_ : 0, ps.top_state().phase);
694 }
695 
ActAnimate(const std::vector<std::string> & arguments,ProductionSiteDescr * descr)696 ProductionProgram::ActAnimate::ActAnimate(const std::vector<std::string>& arguments,
697                                           ProductionSiteDescr* descr) {
698 	parameters = MapObjectProgram::parse_act_animate(arguments, *descr, false);
699 }
700 
execute(Game & game,ProductionSite & ps) const701 void ProductionProgram::ActAnimate::execute(Game& game, ProductionSite& ps) const {
702 	ps.start_animation(game, parameters.animation);
703 	return ps.program_step(
704 	   game, parameters.duration ? parameters.duration : 0, ps.top_state().phase);
705 }
706 
ActConsume(const std::vector<std::string> & arguments,const ProductionSiteDescr & descr,const Tribes & tribes)707 ProductionProgram::ActConsume::ActConsume(const std::vector<std::string>& arguments,
708                                           const ProductionSiteDescr& descr,
709                                           const Tribes& tribes) {
710 	if (arguments.empty()) {
711 		throw GameDataError(
712 		   "Usage: consume=<ware or worker>[,<ware or worker>[,...]][:<amount>] ...");
713 	}
714 	consumed_wares_workers_ =
715 	   parse_ware_type_groups(arguments.begin(), arguments.end(), descr, tribes);
716 }
717 
execute(Game & game,ProductionSite & ps) const718 void ProductionProgram::ActConsume::execute(Game& game, ProductionSite& ps) const {
719 	std::vector<InputQueue*> const inputqueues = ps.inputqueues();
720 	std::vector<uint8_t> consumption_quantities(inputqueues.size(), 0);
721 
722 	Groups l_groups = consumed_wares_workers_;  //  make a copy for local modification
723 
724 	//  Iterate over all input queues and see how much we should consume from
725 	//  each of them.
726 	bool found;
727 	for (size_t i = 0; i < inputqueues.size(); ++i) {
728 		DescriptionIndex const input_index = inputqueues[i]->get_index();
729 		WareWorker const input_type = inputqueues[i]->get_type();
730 		uint8_t nr_available = inputqueues[i]->get_filled();
731 		consumption_quantities[i] = 0;
732 
733 		//  Iterate over all consume groups and see if they want us to consume
734 		//  any thing from the currently considered input queue.
735 		for (Groups::iterator it = l_groups.begin(); it != l_groups.end();) {
736 			found = false;
737 			for (auto input_it = it->first.begin(); input_it != it->first.end(); input_it++) {
738 				if (input_it->first == input_index && input_it->second == input_type) {
739 					found = true;
740 					if (it->second <= nr_available) {
741 						//  There are enough wares of the currently considered type
742 						//  to fulfill the requirements of the current group. We can
743 						//  therefore erase the group.
744 						consumption_quantities[i] += it->second;
745 						nr_available -= it->second;
746 						it = l_groups.erase(it);
747 						//  No increment here, erase moved next element to the position
748 						//  pointed to by it.
749 					} else {
750 						consumption_quantities[i] += nr_available;
751 						it->second -= nr_available;
752 						++it;  //  Now check if the next group includes this ware type.
753 					}
754 					break;
755 				}
756 			}
757 			// group does not request ware
758 			if (!found)
759 				++it;
760 		}
761 	}
762 
763 	// "Did not start working because .... is/are missing"
764 	if (uint8_t const nr_missing_groups = l_groups.size()) {
765 		const TribeDescr& tribe = ps.owner().tribe();
766 
767 		std::vector<std::string> group_list;
768 		for (const auto& group : l_groups) {
769 			assert(group.first.size());
770 
771 			std::vector<std::string> ware_list;
772 			for (const auto& entry : group.first) {
773 				ware_list.push_back(entry.second == wwWARE ?
774 				                       tribe.get_ware_descr(entry.first)->descname() :
775 				                       tribe.get_worker_descr(entry.first)->descname());
776 			}
777 			std::string ware_string = i18n::localize_list(ware_list, i18n::ConcatenateWith::OR);
778 
779 			uint8_t const count = group.second;
780 			if (1 < count) {
781 				ware_string =
782 				   /** TRANSLATORS: e.g. 'Did not start working because 3x water and 3x wheat are
783 				      missing' */
784 				   /** TRANSLATORS: For this example, this is what's in the place holders: */
785 				   /** TRANSLATORS:    %1$i = "3" */
786 				   /** TRANSLATORS:    %2$s = "water" */
787 				   (boost::format(_("%1$ix %2$s")) % static_cast<unsigned int>(count) % ware_string)
788 				      .str();
789 			}
790 			group_list.push_back(ware_string);
791 		}
792 
793 		const std::string is_missing_string =
794 		   /** TRANSLATORS: e.g. 'Did not start working because 3x water and 3x wheat are missing' */
795 		   /** TRANSLATORS: e.g. 'Did not start working because fish, meat or pitta bread is missing'
796 		    */
797 		   (boost::format(ngettext("%s is missing", "%s are missing", nr_missing_groups)) %
798 		    i18n::localize_list(group_list, i18n::ConcatenateWith::AND))
799 		      .str();
800 
801 		std::string result_string =
802 		   /** TRANSLATORS: e.g. 'Did not start working because 3x water and 3x wheat are missing' */
803 		   /** TRANSLATORS: For this example, this is what's in the place holders: */
804 		   /** TRANSLATORS:    %1$s = "working" */
805 		   /** TRANSLATORS:    %2$s = "3x water and 3x wheat are missing" */
806 		   /** TRANSLATORS: This appears in the hover text on buildings. Please test these in
807 		      context*/
808 		   /** TRANSLATORS: on a development build if you can, and let us know if there are any issues
809 		    */
810 		   /** TRANSLATORS: we need to address for your language. */
811 		   (boost::format(_("Did not start %1$s because %2$s")) % ps.top_state().program->descname() %
812 		    is_missing_string)
813 		      .str();
814 
815 		if (ps.production_result() != ps.descr().out_of_resource_heading() ||
816 		    ps.descr().out_of_resource_heading().empty()) {
817 			ps.set_production_result(result_string);
818 		}
819 		return ps.program_end(game, ProgramResult::kFailed);
820 	} else {  //  we fulfilled all consumption requirements
821 		for (size_t i = 0; i < inputqueues.size(); ++i) {
822 			if (uint8_t const q = consumption_quantities[i]) {
823 				assert(q <= inputqueues[i]->get_filled());
824 				inputqueues[i]->set_filled(inputqueues[i]->get_filled() - q);
825 
826 				// Update consumption statistics
827 				if (inputqueues[i]->get_type() == wwWARE) {
828 					ps.get_owner()->ware_consumed(inputqueues[i]->get_index(), q);
829 				}
830 			}
831 		}
832 		return ps.program_step(game);
833 	}
834 }
835 
ActProduce(const std::vector<std::string> & arguments,const ProductionSiteDescr & descr,const Tribes & tribes)836 ProductionProgram::ActProduce::ActProduce(const std::vector<std::string>& arguments,
837                                           const ProductionSiteDescr& descr,
838                                           const Tribes& tribes) {
839 	if (arguments.empty()) {
840 		throw GameDataError("Usage: produce=<ware name>[:<amount>] [<ware name>[:<amount>]...]");
841 	}
842 	produced_wares_ = parse_bill_of_materials(arguments, WareWorker::wwWARE, descr, tribes);
843 }
844 
execute(Game & game,ProductionSite & ps) const845 void ProductionProgram::ActProduce::execute(Game& game, ProductionSite& ps) const {
846 	assert(ps.produced_wares_.empty());
847 	ps.produced_wares_ = produced_wares_;
848 	ps.working_positions_[ps.main_worker_].worker->update_task_buildingwork(game);
849 
850 	const TribeDescr& tribe = ps.owner().tribe();
851 	assert(produced_wares_.size());
852 
853 	std::vector<std::string> ware_descnames;
854 	uint8_t count = 0;
855 	for (const auto& item_pair : produced_wares_) {
856 		count += item_pair.second;
857 		std::string ware_descname = tribe.get_ware_descr(item_pair.first)->descname();
858 		if (1 < item_pair.second || 1 < produced_wares_.size()) {
859 			/** TRANSLATORS: This is an item in a list of wares, e.g. "Produced 2x Coal": */
860 			/** TRANSLATORS:    %%1$i = "2" */
861 			/** TRANSLATORS:    %2$s = "Coal" */
862 			ware_descname = (boost::format(_("%1$ix %2$s")) %
863 			                 static_cast<unsigned int>(item_pair.second) % ware_descname)
864 			                   .str();
865 		}
866 		ware_descnames.push_back(ware_descname);
867 	}
868 	std::string ware_list = i18n::localize_list(ware_descnames, i18n::ConcatenateWith::AND);
869 
870 	const std::string result_string =
871 	   /** TRANSLATORS: %s is a list of wares. String is fetched according to total amount of
872 	      wares. */
873 	   (boost::format(ngettext("Produced %s", "Produced %s", count)) % ware_list).str();
874 	if (ps.production_result() != ps.descr().out_of_resource_heading() ||
875 	    ps.descr().out_of_resource_heading().empty()) {
876 		ps.set_production_result(result_string);
877 	}
878 }
879 
get_building_work(Game & game,ProductionSite & psite,Worker &) const880 bool ProductionProgram::ActProduce::get_building_work(Game& game,
881                                                       ProductionSite& psite,
882                                                       Worker& /* worker */) const {
883 	// We reach this point once all wares have been carried outside the building
884 	psite.program_step(game);
885 	return false;
886 }
887 
ActRecruit(const std::vector<std::string> & arguments,const ProductionSiteDescr & descr,const Tribes & tribes)888 ProductionProgram::ActRecruit::ActRecruit(const std::vector<std::string>& arguments,
889                                           const ProductionSiteDescr& descr,
890                                           const Tribes& tribes) {
891 	if (arguments.empty()) {
892 		throw GameDataError("Usage: recruit=<worker name>[:<amount>] [<worker name>[:<amount>]...]");
893 	}
894 	recruited_workers_ = parse_bill_of_materials(arguments, WareWorker::wwWORKER, descr, tribes);
895 }
896 
execute(Game & game,ProductionSite & ps) const897 void ProductionProgram::ActRecruit::execute(Game& game, ProductionSite& ps) const {
898 	assert(ps.recruited_workers_.empty());
899 	ps.recruited_workers_ = recruited_workers_;
900 	ps.working_positions_[ps.main_worker_].worker->update_task_buildingwork(game);
901 
902 	const TribeDescr& tribe = ps.owner().tribe();
903 	assert(recruited_workers_.size());
904 	std::vector<std::string> worker_descnames;
905 	uint8_t count = 0;
906 	for (const auto& item_pair : recruited_workers_) {
907 		count += item_pair.second;
908 		std::string worker_descname = tribe.get_worker_descr(item_pair.first)->descname();
909 		if (1 < item_pair.second || 1 < recruited_workers_.size()) {
910 			/** TRANSLATORS: This is an item in a list of workers, e.g. "Recruited 2x Ox": */
911 			/** TRANSLATORS:    %1$i = "2" */
912 			/** TRANSLATORS:    %2$s = "Ox" */
913 			worker_descname = (boost::format(_("%1$ix %2$s")) %
914 			                   static_cast<unsigned int>(item_pair.second) % worker_descname)
915 			                     .str();
916 		}
917 		worker_descnames.push_back(worker_descname);
918 	}
919 	std::string unit_string = i18n::localize_list(worker_descnames, i18n::ConcatenateWith::AND);
920 
921 	const std::string result_string =
922 	   /** TRANSLATORS: %s is a list of workers. String is fetched according to total amount of
923 	      workers. */
924 	   (boost::format(ngettext("Recruited %s", "Recruited %s", count)) % unit_string).str();
925 	ps.set_production_result(result_string);
926 }
927 
get_building_work(Game & game,ProductionSite & psite,Worker &) const928 bool ProductionProgram::ActRecruit::get_building_work(Game& game,
929                                                       ProductionSite& psite,
930                                                       Worker& /* worker */) const {
931 	// We reach this point once all recruits have been guided outside the building
932 	psite.program_step(game);
933 	return false;
934 }
935 
ActMine(const std::vector<std::string> & arguments,const World & world,const std::string & production_program_name,ProductionSiteDescr * descr)936 ProductionProgram::ActMine::ActMine(const std::vector<std::string>& arguments,
937                                     const World& world,
938                                     const std::string& production_program_name,
939                                     ProductionSiteDescr* descr) {
940 	if (arguments.size() != 5) {
941 		throw GameDataError(
942 		   "Usage: mine=resource <workarea radius> <max> <chance> <worker experience gained>");
943 	}
944 
945 	resource_ = world.safe_resource_index(arguments.front().c_str());
946 	distance_ = read_positive(arguments.at(1));
947 	max_ = read_positive(arguments.at(2));
948 	chance_ = read_positive(arguments.at(3));
949 	training_ = read_positive(arguments.at(4));
950 
951 	const std::string description = descr->name() + " " + production_program_name + " mine " +
952 	                                world.get_resource(resource_)->name();
953 	descr->workarea_info_[distance_].insert(description);
954 }
955 
execute(Game & game,ProductionSite & ps) const956 void ProductionProgram::ActMine::execute(Game& game, ProductionSite& ps) const {
957 	Map* map = game.mutable_map();
958 
959 	//  select one of the nodes randomly
960 	uint32_t totalres = 0;
961 	uint32_t totalchance = 0;
962 	uint32_t totalstart = 0;
963 
964 	{
965 		MapRegion<Area<FCoords>> mr(
966 		   *map, Area<FCoords>(map->get_fcoords(ps.get_position()), distance_));
967 		do {
968 			DescriptionIndex fres = mr.location().field->get_resources();
969 			ResourceAmount amount = mr.location().field->get_resources_amount();
970 			ResourceAmount start_amount = mr.location().field->get_initial_res_amount();
971 
972 			if (fres != resource_) {
973 				amount = 0;
974 				start_amount = 0;
975 			}
976 
977 			totalres += amount;
978 			totalstart += start_amount;
979 			totalchance += 8 * amount;
980 
981 			// Add penalty for fields that are running out
982 			// Except for totally depleted fields or wrong ressource fields
983 			// if we already know there is no ressource (left) we won't mine there
984 			if (amount == 0)
985 				totalchance += 0;
986 			else if (amount <= 2)
987 				totalchance += 6;
988 			else if (amount <= 4)
989 				totalchance += 4;
990 			else if (amount <= 6)
991 				totalchance += 2;
992 		} while (mr.advance(*map));
993 	}
994 
995 	//  how much is digged
996 	int32_t digged_percentage = 100;
997 	if (totalstart)
998 		digged_percentage = (totalstart - totalres) * 100 / totalstart;
999 	if (!totalres)
1000 		digged_percentage = 100;
1001 
1002 	if (digged_percentage < max_) {
1003 		//  mine can produce normally
1004 		if (totalres == 0)
1005 			return ps.program_end(game, ProgramResult::kFailed);
1006 
1007 		//  second pass through nodes
1008 		assert(totalchance);
1009 		int32_t pick = game.logic_rand() % totalchance;
1010 
1011 		{
1012 			MapRegion<Area<FCoords>> mr(
1013 			   *map, Area<FCoords>(map->get_fcoords(ps.get_position()), distance_));
1014 			do {
1015 				DescriptionIndex fres = mr.location().field->get_resources();
1016 				ResourceAmount amount = mr.location().field->get_resources_amount();
1017 
1018 				if (fres != resource_)
1019 					amount = 0;
1020 
1021 				pick -= 8 * amount;
1022 				if (pick < 0) {
1023 					assert(amount > 0);
1024 
1025 					--amount;
1026 					map->set_resources(mr.location(), amount);
1027 					break;
1028 				}
1029 			} while (mr.advance(*map));
1030 		}
1031 
1032 		if (pick >= 0) {
1033 			return ps.program_end(game, ProgramResult::kFailed);
1034 		}
1035 
1036 	} else {
1037 		//  Inform the player about an empty mine, unless
1038 		//  there is a sufficiently high chance, that the mine
1039 		//  will still produce enough.
1040 		//  e.g. mines have chance=5, wells have 65
1041 		if (chance_ <= 20) {
1042 			ps.notify_player(game, 60);
1043 			// and change the default animation
1044 			ps.set_default_anim("empty");
1045 		}
1046 
1047 		//  Mine has reached its limits, still try to produce something but
1048 		//  independent of sourrunding resources. Do not decrease resources
1049 		//  further.
1050 		if (chance_ <= game.logic_rand() % 100) {
1051 
1052 			// Gain experience
1053 			if (training_ >= game.logic_rand() % 100) {
1054 				ps.train_workers(game);
1055 			}
1056 			return ps.program_end(game, ProgramResult::kFailed);
1057 		}
1058 	}
1059 
1060 	//  done successful
1061 	//  TODO(unknown): Should pass the time it takes to mine in the phase parameter of
1062 	//  ProductionSite::program_step so that the following sleep/animate
1063 	//  command knows how long it should last.
1064 	return ps.program_step(game);
1065 }
1066 
ActCheckSoldier(const std::vector<std::string> & arguments)1067 ProductionProgram::ActCheckSoldier::ActCheckSoldier(const std::vector<std::string>& arguments) {
1068 	if (arguments.size() != 3) {
1069 		throw GameDataError("Usage: checksoldier=soldier <training attribute> <level>");
1070 	}
1071 
1072 	if (arguments.front() != "soldier") {
1073 		throw GameDataError("Expected 'soldier' but found '%s'", arguments.front().c_str());
1074 	}
1075 	attribute_ = parse_training_attribute(arguments.at(1));
1076 	level_ = read_int(arguments.at(2), 0);
1077 }
1078 
execute(Game & game,ProductionSite & ps) const1079 void ProductionProgram::ActCheckSoldier::execute(Game& game, ProductionSite& ps) const {
1080 	const SoldierControl* ctrl = ps.soldier_control();
1081 	assert(ctrl != nullptr);
1082 	const std::vector<Soldier*> soldiers = ctrl->present_soldiers();
1083 	if (soldiers.empty()) {
1084 		ps.set_production_result(_("No soldier to train!"));
1085 		return ps.program_end(game, ProgramResult::kSkipped);
1086 	}
1087 	ps.molog("  Checking soldier (%u) level %d)\n", static_cast<unsigned int>(attribute_),
1088 	         static_cast<unsigned int>(level_));
1089 
1090 	const std::vector<Soldier*>::const_iterator soldiers_end = soldiers.end();
1091 	for (std::vector<Soldier*>::const_iterator it = soldiers.begin();; ++it) {
1092 		if (it == soldiers_end) {
1093 			ps.set_production_result(_("No soldier found for this training level!"));
1094 			return ps.program_end(game, ProgramResult::kSkipped);
1095 		}
1096 
1097 		if (attribute_ == TrainingAttribute::kHealth) {
1098 			if ((*it)->get_health_level() == level_) {
1099 				break;
1100 			}
1101 		} else if (attribute_ == TrainingAttribute::kAttack) {
1102 			if ((*it)->get_attack_level() == level_) {
1103 				break;
1104 			}
1105 		} else if (attribute_ == TrainingAttribute::kDefense) {
1106 			if ((*it)->get_defense_level() == level_) {
1107 				break;
1108 			}
1109 		} else if (attribute_ == TrainingAttribute::kEvade) {
1110 			if ((*it)->get_evade_level() == level_) {
1111 				break;
1112 			}
1113 		}
1114 	}
1115 	ps.molog("    okay\n");  // okay, do nothing
1116 
1117 	upcast(TrainingSite, ts, &ps);
1118 	ts->training_attempted(attribute_, level_);
1119 
1120 	ps.molog("  Check done!\n");
1121 
1122 	return ps.program_step(game);
1123 }
1124 
ActTrain(const std::vector<std::string> & arguments)1125 ProductionProgram::ActTrain::ActTrain(const std::vector<std::string>& arguments) {
1126 	if (arguments.size() != 4) {
1127 		throw GameDataError(
1128 		   "Usage: checksoldier=soldier <training attribute> <level before> <level after>");
1129 	}
1130 
1131 	if (arguments.front() != "soldier") {
1132 		throw GameDataError("Expected 'soldier' but found '%s'", arguments.front().c_str());
1133 	}
1134 
1135 	attribute_ = parse_training_attribute(arguments.at(1));
1136 	level_ = read_int(arguments.at(2), 0);
1137 	target_level_ = read_positive(arguments.at(3));
1138 }
1139 
execute(Game & game,ProductionSite & ps) const1140 void ProductionProgram::ActTrain::execute(Game& game, ProductionSite& ps) const {
1141 	const SoldierControl* ctrl = ps.soldier_control();
1142 	const std::vector<Soldier*> soldiers = ctrl->present_soldiers();
1143 	const std::vector<Soldier*>::const_iterator soldiers_end = soldiers.end();
1144 
1145 	ps.molog("  Training soldier's %u (%d to %d)", static_cast<unsigned int>(attribute_),
1146 	         static_cast<unsigned int>(level_), static_cast<unsigned int>(target_level_));
1147 
1148 	bool training_done = false;
1149 	for (auto it = soldiers.begin(); !training_done; ++it) {
1150 		if (it == soldiers_end) {
1151 			ps.set_production_result(_("No soldier found for this training level!"));
1152 			return ps.program_end(game, ProgramResult::kSkipped);
1153 		}
1154 		try {
1155 			switch (attribute_) {
1156 			case TrainingAttribute::kHealth:
1157 				if ((*it)->get_health_level() == level_) {
1158 					(*it)->set_health_level(target_level_);
1159 					training_done = true;
1160 				}
1161 				break;
1162 			case TrainingAttribute::kAttack:
1163 				if ((*it)->get_attack_level() == level_) {
1164 					(*it)->set_attack_level(target_level_);
1165 					training_done = true;
1166 				}
1167 				break;
1168 			case TrainingAttribute::kDefense:
1169 				if ((*it)->get_defense_level() == level_) {
1170 					(*it)->set_defense_level(target_level_);
1171 					training_done = true;
1172 				}
1173 				break;
1174 			case TrainingAttribute::kEvade:
1175 				if ((*it)->get_evade_level() == level_) {
1176 					(*it)->set_evade_level(target_level_);
1177 					training_done = true;
1178 				}
1179 				break;
1180 			default:
1181 				throw wexception(
1182 				   "Unknown training attribute index %d", static_cast<unsigned int>(attribute_));
1183 			}
1184 		} catch (...) {
1185 			throw wexception("Fail training soldier!!");
1186 		}
1187 	}
1188 
1189 	ps.molog("  Training done!\n");
1190 	ps.set_production_result(
1191 	   /** TRANSLATORS: Success message of a trainingsite '%s' stands for the description of the
1192 	    * training program, e.g. Completed upgrading soldier evade from level 0 to level 1 */
1193 	   (boost::format(_("Completed %s")) % ps.top_state().program->descname()).str());
1194 
1195 	upcast(TrainingSite, ts, &ps);
1196 	ts->training_successful(attribute_, level_);
1197 
1198 	return ps.program_step(game);
1199 }
1200 
ActPlaySound(const std::vector<std::string> & arguments)1201 ProductionProgram::ActPlaySound::ActPlaySound(const std::vector<std::string>& arguments) {
1202 	parameters = MapObjectProgram::parse_act_play_sound(arguments, kFxPriorityAllowMultiple - 1);
1203 }
1204 
execute(Game & game,ProductionSite & ps) const1205 void ProductionProgram::ActPlaySound::execute(Game& game, ProductionSite& ps) const {
1206 	Notifications::publish(
1207 	   NoteSound(SoundType::kAmbient, parameters.fx, ps.position_, parameters.priority));
1208 	return ps.program_step(game);
1209 }
1210 
ActConstruct(const std::vector<std::string> & arguments,const std::string & production_program_name,ProductionSiteDescr * descr)1211 ProductionProgram::ActConstruct::ActConstruct(const std::vector<std::string>& arguments,
1212                                               const std::string& production_program_name,
1213                                               ProductionSiteDescr* descr) {
1214 	if (arguments.size() != 3) {
1215 		throw GameDataError("Usage: construct=<object name> <worker program> <workarea radius>");
1216 	}
1217 	objectname = arguments.at(0);
1218 	workerprogram = arguments.at(1);
1219 	radius = read_positive(arguments.at(2));
1220 
1221 	const std::string description =
1222 	   descr->name() + ' ' + production_program_name + " construct " + objectname;
1223 	descr->workarea_info_[radius].insert(description);
1224 }
1225 
1226 const ImmovableDescr&
get_construction_descr(const Tribes & tribes) const1227 ProductionProgram::ActConstruct::get_construction_descr(const Tribes& tribes) const {
1228 	const ImmovableDescr* descr = tribes.get_immovable_descr(tribes.immovable_index(objectname));
1229 	if (!descr)
1230 		throw wexception("ActConstruct: immovable '%s' does not exist", objectname.c_str());
1231 
1232 	return *descr;
1233 }
1234 
execute(Game & game,ProductionSite & psite) const1235 void ProductionProgram::ActConstruct::execute(Game& game, ProductionSite& psite) const {
1236 	ProductionSite::State& state = psite.top_state();
1237 	const ImmovableDescr& descr = get_construction_descr(game.tribes());
1238 
1239 	// Early check for no resources
1240 	const Buildcost& buildcost = descr.buildcost();
1241 	DescriptionIndex available_resource = INVALID_INDEX;
1242 
1243 	for (Buildcost::const_iterator it = buildcost.begin(); it != buildcost.end(); ++it) {
1244 		if (psite.inputqueue(it->first, wwWARE).get_filled() > 0) {
1245 			available_resource = it->first;
1246 			break;
1247 		}
1248 	}
1249 
1250 	if (available_resource == INVALID_INDEX) {
1251 		psite.program_end(game, ProgramResult::kFailed);
1252 		return;
1253 	}
1254 
1255 	// Look for an appropriate object in the given radius
1256 	const Map& map = game.map();
1257 	std::vector<ImmovableFound> immovables;
1258 	CheckStepWalkOn cstep(MOVECAPS_WALK, true);
1259 	Area<FCoords> area(map.get_fcoords(psite.base_flag().get_position()), radius);
1260 	if (map.find_reachable_immovables(game, area, &immovables, cstep, FindImmovableByDescr(descr))) {
1261 		state.objvar = immovables[0].object;
1262 
1263 		psite.working_positions_[psite.main_worker_].worker->update_task_buildingwork(game);
1264 		return;
1265 	}
1266 
1267 	// No object found, look for a field where we can build
1268 	std::vector<Coords> fields;
1269 	FindNodeAnd fna;
1270 	// 10 is custom value to make sure the "water" is at least 10 nodes big
1271 	fna.add(FindNodeShore(10));
1272 	fna.add(FindNodeImmovableSize(FindNodeImmovableSize::sizeNone));
1273 	if (map.find_reachable_fields(game, area, &fields, cstep, fna)) {
1274 		// Testing received fields to get one with less immovables nearby
1275 		Coords best_coords = fields.back();  // Just to initialize it
1276 		uint32_t best_score = std::numeric_limits<uint32_t>::max();
1277 		while (!fields.empty()) {
1278 			Coords coords = fields.back();
1279 
1280 			// Counting immovables nearby
1281 			std::vector<ImmovableFound> found_immovables;
1282 			const uint32_t imm_count =
1283 			   map.find_immovables(game, Area<FCoords>(map.get_fcoords(coords), 2), &found_immovables);
1284 			if (best_score > imm_count) {
1285 				best_score = imm_count;
1286 				best_coords = coords;
1287 			}
1288 
1289 			// No need to go on, it cannot be better
1290 			if (imm_count == 0) {
1291 				break;
1292 			}
1293 
1294 			fields.pop_back();
1295 		}
1296 
1297 		state.coord = best_coords;
1298 
1299 		psite.working_positions_[psite.main_worker_].worker->update_task_buildingwork(game);
1300 		return;
1301 	}
1302 
1303 	psite.molog("construct: no object or buildable field\n");
1304 	psite.program_end(game, ProgramResult::kFailed);
1305 }
1306 
get_building_work(Game & game,ProductionSite & psite,Worker & worker) const1307 bool ProductionProgram::ActConstruct::get_building_work(Game& game,
1308                                                         ProductionSite& psite,
1309                                                         Worker& worker) const {
1310 	ProductionSite::State& state = psite.top_state();
1311 	if (state.phase > ProgramResult::kNone) {
1312 		psite.program_step(game);
1313 		return false;
1314 	}
1315 
1316 	// First step: figure out which ware item to bring along
1317 	Buildcost remaining;
1318 	WaresQueue* wq = nullptr;
1319 
1320 	Immovable* construction = dynamic_cast<Immovable*>(state.objvar.get(game));
1321 	if (construction) {
1322 		if (!construction->construct_remaining_buildcost(game, &remaining)) {
1323 			psite.molog("construct: immovable %u not under construction", construction->serial());
1324 			psite.program_end(game, ProgramResult::kFailed);
1325 			return false;
1326 		}
1327 	} else {
1328 		const ImmovableDescr& descr = get_construction_descr(game.tribes());
1329 		remaining = descr.buildcost();
1330 	}
1331 
1332 	for (Buildcost::const_iterator it = remaining.begin(); it != remaining.end(); ++it) {
1333 		WaresQueue& thiswq = dynamic_cast<WaresQueue&>(psite.inputqueue(it->first, wwWARE));
1334 		if (thiswq.get_filled() > 0) {
1335 			wq = &thiswq;
1336 			break;
1337 		}
1338 	}
1339 
1340 	if (!wq) {
1341 		psite.program_end(game, ProgramResult::kFailed);
1342 		return false;
1343 	}
1344 
1345 	// Second step: give ware to worker
1346 	WareInstance* ware =
1347 	   new WareInstance(wq->get_index(), game.tribes().get_ware_descr(wq->get_index()));
1348 	ware->init(game);
1349 	worker.set_carried_ware(game, ware);
1350 	wq->set_filled(wq->get_filled() - 1);
1351 
1352 	// Third step: send worker on his merry way, giving the target object or coords
1353 	worker.start_task_program(game, workerprogram);
1354 	worker.top_state().objvar1 = construction;
1355 	worker.top_state().coords = state.coord;
1356 
1357 	state.phase = ProgramResult::kFailed;
1358 	return true;
1359 }
1360 
building_work_failed(Game & game,ProductionSite & psite,Worker &) const1361 void ProductionProgram::ActConstruct::building_work_failed(Game& game,
1362                                                            ProductionSite& psite,
1363                                                            Worker&) const {
1364 	psite.program_end(game, ProgramResult::kFailed);
1365 }
1366 
ProductionProgram(const std::string & init_name,const std::string & init_descname,std::unique_ptr<LuaTable> actions_table,const Tribes & tribes,const World & world,ProductionSiteDescr * building)1367 ProductionProgram::ProductionProgram(const std::string& init_name,
1368                                      const std::string& init_descname,
1369                                      std::unique_ptr<LuaTable> actions_table,
1370                                      const Tribes& tribes,
1371                                      const World& world,
1372                                      ProductionSiteDescr* building)
1373    : MapObjectProgram(init_name), descname_(init_descname) {
1374 
1375 	for (const std::string& line : actions_table->array_entries<std::string>()) {
1376 		if (line.empty()) {
1377 			throw GameDataError("Empty line");
1378 		}
1379 		try {
1380 			ProgramParseInput parseinput = parse_program_string(line);
1381 
1382 			if (parseinput.name == "return") {
1383 				actions_.push_back(std::unique_ptr<ProductionProgram::Action>(
1384 				   new ActReturn(parseinput.arguments, *building, tribes)));
1385 			} else if (parseinput.name == "call") {
1386 				actions_.push_back(std::unique_ptr<ProductionProgram::Action>(
1387 				   new ActCall(parseinput.arguments, *building)));
1388 			} else if (parseinput.name == "sleep") {
1389 				actions_.push_back(
1390 				   std::unique_ptr<ProductionProgram::Action>(new ActSleep(parseinput.arguments)));
1391 			} else if (parseinput.name == "animate") {
1392 				actions_.push_back(std::unique_ptr<ProductionProgram::Action>(
1393 				   new ActAnimate(parseinput.arguments, building)));
1394 			} else if (parseinput.name == "consume") {
1395 				actions_.push_back(std::unique_ptr<ProductionProgram::Action>(
1396 				   new ActConsume(parseinput.arguments, *building, tribes)));
1397 			} else if (parseinput.name == "produce") {
1398 				actions_.push_back(std::unique_ptr<ProductionProgram::Action>(
1399 				   new ActProduce(parseinput.arguments, *building, tribes)));
1400 			} else if (parseinput.name == "recruit") {
1401 				actions_.push_back(std::unique_ptr<ProductionProgram::Action>(
1402 				   new ActRecruit(parseinput.arguments, *building, tribes)));
1403 			} else if (parseinput.name == "callworker") {
1404 				actions_.push_back(std::unique_ptr<ProductionProgram::Action>(
1405 				   new ActCallWorker(parseinput.arguments, name(), building, tribes)));
1406 			} else if (parseinput.name == "mine") {
1407 				actions_.push_back(std::unique_ptr<ProductionProgram::Action>(
1408 				   new ActMine(parseinput.arguments, world, name(), building)));
1409 			} else if (parseinput.name == "checksoldier") {
1410 				actions_.push_back(std::unique_ptr<ProductionProgram::Action>(
1411 				   new ActCheckSoldier(parseinput.arguments)));
1412 			} else if (parseinput.name == "train") {
1413 				actions_.push_back(
1414 				   std::unique_ptr<ProductionProgram::Action>(new ActTrain(parseinput.arguments)));
1415 			} else if (parseinput.name == "playsound") {
1416 				actions_.push_back(
1417 				   std::unique_ptr<ProductionProgram::Action>(new ActPlaySound(parseinput.arguments)));
1418 			} else if (parseinput.name == "construct") {
1419 				actions_.push_back(std::unique_ptr<ProductionProgram::Action>(
1420 				   new ActConstruct(parseinput.arguments, name(), building)));
1421 			} else {
1422 				throw GameDataError(
1423 				   "Unknown command '%s' in line '%s'", parseinput.name.c_str(), line.c_str());
1424 			}
1425 
1426 			const ProductionProgram::Action& action = *actions_.back();
1427 			for (const auto& group : action.consumed_wares_workers()) {
1428 				consumed_wares_workers_.push_back(group);
1429 			}
1430 
1431 			// Add produced wares. If the ware already exists, increase the amount
1432 			for (const auto& ware : action.produced_wares()) {
1433 				if (produced_wares_.count(ware.first) == 1) {
1434 					produced_wares_.at(ware.first) += ware.second;
1435 				} else {
1436 					produced_wares_.insert(ware);
1437 				}
1438 			}
1439 
1440 			// Add recruited workers. If the worker already exists, increase the amount
1441 			for (const auto& worker : action.recruited_workers()) {
1442 				if (recruited_workers_.count(worker.first) == 1) {
1443 					recruited_workers_.at(worker.first) += worker.second;
1444 				} else {
1445 					recruited_workers_.insert(worker);
1446 				}
1447 			}
1448 		} catch (const std::exception& e) {
1449 			throw GameDataError("Error reading line '%s': %s", line.c_str(), e.what());
1450 		}
1451 	}
1452 
1453 	if (actions_.empty()) {
1454 		throw GameDataError("No actions found");
1455 	}
1456 }
1457 
descname() const1458 const std::string& ProductionProgram::descname() const {
1459 	return descname_;
1460 }
size() const1461 size_t ProductionProgram::size() const {
1462 	return actions_.size();
1463 }
1464 
operator [](size_t const idx) const1465 const ProductionProgram::Action& ProductionProgram::operator[](size_t const idx) const {
1466 	return *actions_.at(idx);
1467 }
1468 
consumed_wares_workers() const1469 const ProductionProgram::Groups& ProductionProgram::consumed_wares_workers() const {
1470 	return consumed_wares_workers_;
1471 }
produced_wares() const1472 const Buildcost& ProductionProgram::produced_wares() const {
1473 	return produced_wares_;
1474 }
recruited_workers() const1475 const Buildcost& ProductionProgram::recruited_workers() const {
1476 	return recruited_workers_;
1477 }
1478 }  // namespace Widelands
1479