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