Merge lp:~widelands-dev/widelands/unify-program-parsers into lp:widelands

Proposed by GunChleoc
Status: Work in progress
Proposed branch: lp:~widelands-dev/widelands/unify-program-parsers
Merge into: lp:widelands
Prerequisite: lp:~widelands-dev/widelands/list-directories-in-cpp
Diff against target: 4800 lines (+1466/-1837)
52 files modified
data/tribes/immovables/shipconstruction_atlanteans/init.lua (+1/-1)
data/tribes/immovables/shipconstruction_barbarians/init.lua (+1/-1)
data/tribes/immovables/shipconstruction_empire/init.lua (+1/-1)
data/tribes/immovables/shipconstruction_frisians/init.lua (+1/-1)
data/tribes/workers/empire/stonemason/init.lua (+1/-1)
src/CMakeLists.txt (+0/-10)
src/editor/editorinteractive.cc (+1/-2)
src/editor/ui_menus/main_menu_new_map.cc (+1/-2)
src/editor/ui_menus/main_menu_random_map.cc (+1/-2)
src/helper.cc (+0/-55)
src/helper.h (+0/-44)
src/logic/editor_game_base.cc (+49/-55)
src/logic/editor_game_base.h (+2/-1)
src/logic/map_objects/CMakeLists.txt (+3/-1)
src/logic/map_objects/bob.cc (+3/-3)
src/logic/map_objects/bob.h (+5/-15)
src/logic/map_objects/immovable.cc (+15/-474)
src/logic/map_objects/immovable_program.cc (+344/-0)
src/logic/map_objects/immovable_program.h (+27/-39)
src/logic/map_objects/map_object.h (+1/-0)
src/logic/map_objects/map_object_program.cc (+125/-0)
src/logic/map_objects/map_object_program.h (+98/-0)
src/logic/map_objects/tribes/production_program.cc (+503/-790)
src/logic/map_objects/tribes/production_program.h (+34/-40)
src/logic/map_objects/tribes/productionsite.cc (+20/-4)
src/logic/map_objects/tribes/productionsite.h (+9/-0)
src/logic/map_objects/tribes/soldier.cc (+0/-1)
src/logic/map_objects/tribes/trainingsite.cc (+0/-1)
src/logic/map_objects/tribes/tribes.cc (+35/-10)
src/logic/map_objects/tribes/tribes.h (+1/-4)
src/logic/map_objects/tribes/worker.cc (+1/-2)
src/logic/map_objects/tribes/worker.h (+1/-1)
src/logic/map_objects/tribes/worker_descr.cc (+9/-10)
src/logic/map_objects/tribes/worker_program.cc (+151/-210)
src/logic/map_objects/tribes/worker_program.h (+3/-11)
src/logic/map_objects/world/critter.cc (+9/-15)
src/logic/map_objects/world/critter.h (+6/-3)
src/logic/map_objects/world/critter_program.h (+4/-11)
src/logic/map_objects/world/resource_description.cc (+0/-1)
src/network/CMakeLists.txt (+0/-1)
src/network/gameclient.cc (+0/-1)
src/network/gamehost.cc (+0/-1)
src/scripting/CMakeLists.txt (+0/-1)
src/scripting/lua_path.cc (+0/-1)
src/sound/CMakeLists.txt (+0/-1)
src/sound/fxset.cc (+0/-1)
src/sound/songset.cc (+0/-1)
src/ui_fsmenu/CMakeLists.txt (+0/-2)
src/ui_fsmenu/launch_spg.cc (+0/-1)
src/ui_fsmenu/options.cc (+0/-1)
src/wui/CMakeLists.txt (+0/-1)
src/wui/load_or_save_game.cc (+0/-2)
To merge this branch: bzr merge lp:~widelands-dev/widelands/unify-program-parsers
Reviewer Review Type Date Requested Status
Notabilis Approve
Review via email: mp+367936@code.launchpad.net

Commit message

Refactor program parsers
- Pull out common code into new class MapObjectProgram
- Fix ware demand checks so that they only affect the correct tribes
- Get rid of extraneous calls to EditorGameBase::postload()
- Small performance tweak for trainingsite programs
- Get rid of now empty helper library

Description of the change

The goal of this branch is to make the code easier to read, and to get rid of code duplication. This also fixes a bug with the ware demand checks.

To post a comment you must log in.
9049. By GunChleoc

Cleanup and performance tweak for trainingsites.

9050. By GunChleoc

Some more code style tweaks.

Revision history for this message
bunnybot (widelandsofficial) wrote :

Continuous integration builds have changed state:

Travis build 5061. State: failed. Details: https://travis-ci.org/widelands/widelands/builds/537401669.
Appveyor build 4841. State: success. Details: https://ci.appveyor.com/project/widelands-dev/widelands/build/_widelands_dev_widelands_unify_program_parsers-4841.

9051. By GunChleoc

Merged trunk.

Revision history for this message
Notabilis (notabilis27) wrote :

Looking mostly good, two comments in the diff.

I went through it commit by commit while ignoring the merges of trunk and list-directories-in-cpp. I hope that is okay? I am a bit unsure about that since there seem to be changes in the full diff that I can't find in the single commits.

9052. By GunChleoc

Addressed code review.

9053. By GunChleoc

Merged trunk.

Revision history for this message
GunChleoc (gunchleoc) wrote :

Thanks for the review! It should be OK. There were some merge conflicts, which is why there might be some small additional stuff, but nothing serious.

I have pushed a commit to address your comments.

9054. By GunChleoc

Make clang happy.

Revision history for this message
Notabilis (notabilis27) wrote :

Looking good now, thanks.

review: Approve
9055. By GunChleoc

Merged trunk.

Revision history for this message
GunChleoc (gunchleoc) wrote :

Thanks for reviewing again :)

@bunnybot merge

Revision history for this message
bunnybot (widelandsofficial) wrote :

Refusing to merge, since Travis is not green. Use @bunnybot merge force for merging anyways.

Travis build 5191. State: failed. Details: https://travis-ci.org/widelands/widelands/builds/545623652.

9056. By GunChleoc

Merged trunk.

9057. By GunChleoc

Add debug log output for test suite on Travis.

9058. By GunChleoc

Fix variable shadowing.

9059. By GunChleoc

Merged trunk.

9060. By GunChleoc

More debug log output.

9061. By GunChleoc

Even more log output.

9062. By GunChleoc

More log output.

9063. By GunChleoc

log output.

9064. By GunChleoc

Merged trunk.

9065. By GunChleoc

Merged trunk.

9066. By GunChleoc

Check for iterator end in match_and_skip.

9067. By GunChleoc

Remove temp log output.

Revision history for this message
bunnybot (widelandsofficial) wrote :

Continuous integration builds have changed state:

Travis build 5242. State: errored. Details: https://travis-ci.org/widelands/widelands/builds/549900235.
Appveyor build 5021. State: success. Details: https://ci.appveyor.com/project/widelands-dev/widelands/build/_widelands_dev_widelands_unify_program_parsers-5021.

Revision history for this message
Toni Förster (stonerl) wrote :

Is it okay to force merge this branch?

test/maps/expedition.wmf/scripting/test_check_transportation_works_one_ship.lua ...

  Running Widelands ...

Seems to time out in some builds.

Revision history for this message
GunChleoc (gunchleoc) wrote :

No, I still need to find the bug that occurs with some compilers.

9068. By GunChleoc

Merged trunk.

Revision history for this message
GunChleoc (gunchleoc) wrote :

I can reproduce the problem now in an Ubuntu trusty VM.

Unmerged revisions

9068. By GunChleoc

Merged trunk.

9067. By GunChleoc

Remove temp log output.

9066. By GunChleoc

Check for iterator end in match_and_skip.

9065. By GunChleoc

Merged trunk.

9064. By GunChleoc

Merged trunk.

9063. By GunChleoc

log output.

9062. By GunChleoc

More log output.

9061. By GunChleoc

Even more log output.

9060. By GunChleoc

More debug log output.

9059. By GunChleoc

Merged trunk.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'data/tribes/immovables/shipconstruction_atlanteans/init.lua'
2--- data/tribes/immovables/shipconstruction_atlanteans/init.lua 2019-05-28 19:37:11 +0000
3+++ data/tribes/immovables/shipconstruction_atlanteans/init.lua 2019-08-31 15:58:01 +0000
4@@ -12,7 +12,7 @@
5 programs = {
6 program = {
7 "construct=idle 5000 210000",
8- "transform=bob tribe:atlanteans_ship",
9+ "transform=bob atlanteans_ship",
10 }
11 },
12 buildcost = {
13
14=== modified file 'data/tribes/immovables/shipconstruction_barbarians/init.lua'
15--- data/tribes/immovables/shipconstruction_barbarians/init.lua 2019-05-28 19:37:11 +0000
16+++ data/tribes/immovables/shipconstruction_barbarians/init.lua 2019-08-31 15:58:01 +0000
17@@ -12,7 +12,7 @@
18 programs = {
19 program = {
20 "construct=idle 5000 210000",
21- "transform=bob tribe:barbarians_ship",
22+ "transform=bob barbarians_ship",
23 }
24 },
25 buildcost = {
26
27=== modified file 'data/tribes/immovables/shipconstruction_empire/init.lua'
28--- data/tribes/immovables/shipconstruction_empire/init.lua 2019-05-28 19:37:11 +0000
29+++ data/tribes/immovables/shipconstruction_empire/init.lua 2019-08-31 15:58:01 +0000
30@@ -12,7 +12,7 @@
31 programs = {
32 program = {
33 "construct=idle 5000 210000",
34- "transform=bob tribe:empire_ship",
35+ "transform=bob empire_ship",
36 }
37 },
38 buildcost = {
39
40=== modified file 'data/tribes/immovables/shipconstruction_frisians/init.lua'
41--- data/tribes/immovables/shipconstruction_frisians/init.lua 2019-05-28 19:37:11 +0000
42+++ data/tribes/immovables/shipconstruction_frisians/init.lua 2019-08-31 15:58:01 +0000
43@@ -12,7 +12,7 @@
44 programs = {
45 program = {
46 "construct=idle 5000 210000",
47- "transform=bob tribe:frisians_ship",
48+ "transform=bob frisians_ship",
49 }
50 },
51 buildcost = {
52
53=== modified file 'data/tribes/workers/empire/stonemason/init.lua'
54--- data/tribes/workers/empire/stonemason/init.lua 2019-06-02 14:45:28 +0000
55+++ data/tribes/workers/empire/stonemason/init.lua 2019-08-31 15:58:01 +0000
56@@ -41,7 +41,7 @@
57 "return"
58 },
59 cut_marble = {
60- "findobject= attrib:rocks radius:6",
61+ "findobject=attrib:rocks radius:6",
62 "walk=object",
63 "playsound=sound/stonecutting/stonecutter 220",
64 "animate=hacking 17500",
65
66=== modified file 'src/CMakeLists.txt'
67--- src/CMakeLists.txt 2019-07-20 14:32:57 +0000
68+++ src/CMakeLists.txt 2019-08-31 15:58:01 +0000
69@@ -132,16 +132,6 @@
70 wui
71 )
72
73-# TODO(sirver): Split into libs with useful names.
74-wl_library(helper
75- SRCS
76- helper.cc
77- helper.h
78- USES_SDL2
79- DEPENDS
80- base_exceptions
81-)
82-
83 if (CMAKE_SYSTEM_NAME MATCHES "FreeBSD" OR CMAKE_SYSTEM_NAME MATCHES "OpenBSD")
84 target_link_libraries(widelands_ball_of_mud ${EXECINFO_LIBRARY})
85 endif (CMAKE_SYSTEM_NAME MATCHES "FreeBSD" OR CMAKE_SYSTEM_NAME MATCHES "OpenBSD")
86
87=== modified file 'src/editor/editorinteractive.cc'
88--- src/editor/editorinteractive.cc 2019-08-28 06:12:07 +0000
89+++ src/editor/editorinteractive.cc 2019-08-31 15:58:01 +0000
90@@ -474,8 +474,7 @@
91 }
92
93 ml->load_map_complete(egbase(), Widelands::MapLoader::LoadType::kEditor);
94- egbase().postload();
95- egbase().load_graphics(loader_ui);
96+ egbase().create_tempfile_and_save_mapdata(FileSystem::ZIP);
97 map_changed(MapWas::kReplaced);
98 }
99
100
101=== modified file 'src/editor/ui_menus/main_menu_new_map.cc'
102--- src/editor/ui_menus/main_menu_new_map.cc 2019-08-28 06:12:07 +0000
103+++ src/editor/ui_menus/main_menu_new_map.cc 2019-08-31 15:58:01 +0000
104@@ -110,8 +110,7 @@
105 map_size_box_.selected_height(), list_.get_selected(), _("No Name"),
106 get_config_string("realname", pgettext("author_name", "Unknown")));
107
108- egbase.postload();
109- egbase.load_graphics(loader_ui);
110+ egbase.create_tempfile_and_save_mapdata(FileSystem::ZIP);
111
112 map->recalc_whole_map(egbase.world());
113 parent.map_changed(EditorInteractive::MapWas::kReplaced);
114
115=== modified file 'src/editor/ui_menus/main_menu_random_map.cc'
116--- src/editor/ui_menus/main_menu_random_map.cc 2019-08-28 06:12:07 +0000
117+++ src/editor/ui_menus/main_menu_random_map.cc 2019-08-31 15:58:01 +0000
118@@ -516,8 +516,7 @@
119
120 gen.create_random_map();
121
122- egbase.postload();
123- egbase.load_graphics(loader_ui);
124+ egbase.create_tempfile_and_save_mapdata(FileSystem::ZIP);
125
126 map->recalc_whole_map(egbase.world());
127 eia.map_changed(EditorInteractive::MapWas::kReplaced);
128
129=== removed file 'src/helper.cc'
130--- src/helper.cc 2019-02-23 11:00:49 +0000
131+++ src/helper.cc 1970-01-01 00:00:00 +0000
132@@ -1,55 +0,0 @@
133-/*
134- * Copyright (C) 2002-2019 by the Widelands Development Team
135- *
136- * This program is free software; you can redistribute it and/or
137- * modify it under the terms of the GNU General Public License
138- * as published by the Free Software Foundation; either version 2
139- * of the License, or (at your option) any later version.
140- *
141- * This program is distributed in the hope that it will be useful,
142- * but WITHOUT ANY WARRANTY; without even the implied warranty of
143- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
144- * GNU General Public License for more details.
145- *
146- * You should have received a copy of the GNU General Public License
147- * along with this program; if not, write to the Free Software
148- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
149- *
150- */
151-
152-#include "helper.h"
153-
154-#include <cstdarg>
155-#include <memory>
156-#include <string>
157-
158-#include <boost/algorithm/string/replace.hpp>
159-#include <boost/format.hpp>
160-#include <boost/lexical_cast.hpp>
161-
162-std::vector<std::string> split_string(const std::string& s, const char* const separators) {
163- std::vector<std::string> result;
164- for (std::string::size_type pos = 0, endpos;
165- (pos = s.find_first_not_of(separators, pos)) != std::string::npos; pos = endpos) {
166- endpos = s.find_first_of(separators, pos);
167- result.push_back(s.substr(pos, endpos - pos));
168- }
169- return result;
170-}
171-
172-char* next_word(char*& p, bool& reached_end, char const terminator) {
173- assert(terminator);
174- char* const result = p;
175- for (; *p != terminator; ++p)
176- if (*p == '\0') {
177- reached_end = true;
178- goto end;
179- }
180- reached_end = false;
181- *p = '\0'; // terminate the word
182- ++p; // move past the terminator
183-end:
184- if (result < p)
185- return result;
186- throw wexception("expected word");
187-}
188
189=== removed file 'src/helper.h'
190--- src/helper.h 2019-03-27 07:20:26 +0000
191+++ src/helper.h 1970-01-01 00:00:00 +0000
192@@ -1,44 +0,0 @@
193-/*
194- * Copyright (C) 2006-2019 by the Widelands Development Team
195- *
196- * This program is free software; you can redistribute it and/or
197- * modify it under the terms of the GNU General Public License
198- * as published by the Free Software Foundation; either version 2
199- * of the License, or (at your option) any later version.
200- *
201- * This program is distributed in the hope that it will be useful,
202- * but WITHOUT ANY WARRANTY; without even the implied warranty of
203- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
204- * GNU General Public License for more details.
205- *
206- * You should have received a copy of the GNU General Public License
207- * along with this program; if not, write to the Free Software
208- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
209- *
210- */
211-
212-#ifndef WL_HELPER_H
213-#define WL_HELPER_H
214-
215-#include <cassert>
216-#include <cstring>
217-#include <string>
218-#include <vector>
219-
220-#include <SDL_keyboard.h>
221-#include <boost/utility.hpp>
222-
223-#include "base/wexception.h"
224-
225-/// Returns the word starting at the character that p points to and ending
226-/// before the first terminator character. Replaces the terminator with null.
227-// TODO(sirver): move into a logic/strings lib or so.
228-char* next_word(char*& p, bool& reached_end, char terminator = ' ');
229-
230-/// Split a string by separators.
231-/// \note This ignores empty elements, so do not use this for example to split
232-/// a string with newline characters into lines, because it would ignore empty
233-/// lines.
234-std::vector<std::string> split_string(const std::string&, char const* separators);
235-
236-#endif // end of include guard: WL_HELPER_H
237
238=== modified file 'src/logic/editor_game_base.cc'
239--- src/logic/editor_game_base.cc 2019-06-23 11:41:17 +0000
240+++ src/logic/editor_game_base.cc 2019-08-31 15:58:01 +0000
241@@ -114,50 +114,53 @@
242 * throws an exception if something goes wrong
243 */
244 void EditorGameBase::create_tempfile_and_save_mapdata(FileSystem::Type const type) {
245- // should only be called when a map was already loaded
246- assert(map_.filesystem());
247-
248- g_fs->ensure_directory_exists(kTempFileDir);
249-
250- std::string filename = kTempFileDir + g_fs->file_separator() + timestring() + "_mapdata";
251- std::string complete_filename = filename + kTempFileExtension;
252-
253- // if a file with that name already exists, then try a few name modifications
254- if (g_fs->file_exists(complete_filename)) {
255- int suffix;
256- for (suffix = 0; suffix <= 9; suffix++) {
257- complete_filename = filename + "-" + std::to_string(suffix) + kTempFileExtension;
258- if (!g_fs->file_exists(complete_filename))
259- break;
260- }
261- if (suffix > 9) {
262- throw wexception("EditorGameBase::create_tempfile_and_save_mapdata(): for all considered "
263- "filenames a file already existed");
264- }
265+ // save map data to temporary file and reassign map fs
266+ try {
267+ g_fs->ensure_directory_exists(kTempFileDir);
268+
269+ std::string filename = kTempFileDir + g_fs->file_separator() + timestring() + "_mapdata";
270+ std::string complete_filename = filename + kTempFileExtension;
271+
272+ // if a file with that name already exists, then try a few name modifications
273+ if (g_fs->file_exists(complete_filename)) {
274+ int suffix;
275+ for (suffix = 0; suffix <= 9; suffix++) {
276+ complete_filename = filename + "-" + std::to_string(suffix) + kTempFileExtension;
277+ if (!g_fs->file_exists(complete_filename))
278+ break;
279+ }
280+ if (suffix > 9) {
281+ throw wexception("EditorGameBase::create_tempfile_and_save_mapdata(): for all considered "
282+ "filenames a file already existed");
283+ }
284+ }
285+
286+ // create tmp_fs_
287+ tmp_fs_.reset(g_fs->create_sub_file_system(complete_filename, type));
288+
289+ // save necessary map data (we actually save the whole map)
290+ std::unique_ptr<Widelands::MapSaver> wms(new Widelands::MapSaver(*tmp_fs_, *this));
291+ wms->save();
292+
293+ // swap map fs
294+ std::unique_ptr<FileSystem> mapfs(tmp_fs_->make_sub_file_system("."));
295+ map_.swap_filesystem(mapfs);
296+ mapfs.reset();
297+
298+ // This is just a convenience hack:
299+ // If tmp_fs_ is a zip filesystem then - because of the way zip filesystems are currently
300+ // implemented -
301+ // the file is still in zip mode right now, which means that the file isn't finalized yet, i.e.,
302+ // not even a valid zip file until zip mode ends. To force ending the zip mode (thus finalizing
303+ // the file)
304+ // we simply perform a (otherwise useless) filesystem request.
305+ // It's not strictly necessary, but this way we get a valid zip file immediately istead of
306+ // at some unkown later point (when an unzip operation happens or a filesystem object destructs).
307+ tmp_fs_->file_exists("binary");
308+ } catch (const WException& e) {
309+ log("EditorGameBase: saving map to temporary file failed: %s", e.what());
310+ throw;
311 }
312-
313- // create tmp_fs_
314- tmp_fs_.reset(g_fs->create_sub_file_system(complete_filename, type));
315-
316- // save necessary map data (we actually save the whole map)
317- std::unique_ptr<Widelands::MapSaver> wms(new Widelands::MapSaver(*tmp_fs_, *this));
318- wms->save();
319-
320- // swap map fs
321- std::unique_ptr<FileSystem> mapfs(tmp_fs_->make_sub_file_system("."));
322- map_.swap_filesystem(mapfs);
323- mapfs.reset();
324-
325- // This is just a convenience hack:
326- // If tmp_fs_ is a zip filesystem then - because of the way zip filesystems are currently
327- // implemented -
328- // the file is still in zip mode right now, which means that the file isn't finalized yet, i.e.,
329- // not even a valid zip file until zip mode ends. To force ending the zip mode (thus finalizing
330- // the file)
331- // we simply perform a (otherwise useless) filesystem request.
332- // It's not strictly necessary, but this way we get a valid zip file immediately istead of
333- // at some unkown later point (when an unzip operation happens or a filesystem object destructs).
334- tmp_fs_->file_exists("binary");
335 }
336
337 void EditorGameBase::think() {
338@@ -276,20 +279,11 @@
339 }
340
341 /**
342- * Load and prepare detailed game data.
343- * This happens once just after the host has started the game and before the
344- * graphics are loaded.
345+ * Load and prepare detailed game and map data.
346+ * This happens once just after the host has started the game / the editor has started and before the graphics are loaded.
347 */
348 void EditorGameBase::postload() {
349- if (map_.filesystem()) {
350- // save map data to temporary file and reassign map fs
351- try {
352- create_tempfile_and_save_mapdata(FileSystem::ZIP);
353- } catch (const WException& e) {
354- log("EditorGameBase::postload: saving map to temporary file failed: %s", e.what());
355- throw;
356- }
357- }
358+ create_tempfile_and_save_mapdata(FileSystem::ZIP);
359
360 // Postload tribes
361 assert(tribes_);
362
363=== modified file 'src/logic/editor_game_base.h'
364--- src/logic/editor_game_base.h 2019-05-14 16:25:57 +0000
365+++ src/logic/editor_game_base.h 2019-08-31 15:58:01 +0000
366@@ -200,6 +200,8 @@
367 // Returns the mutable tribes. Prefer tribes() whenever possible.
368 Tribes* mutable_tribes();
369
370+ void create_tempfile_and_save_mapdata(FileSystem::Type type);
371+
372 private:
373 /// Common function for create_critter and create_ship.
374 Bob& create_bob(Coords, const BobDescr&, Player* owner = nullptr);
375@@ -264,7 +266,6 @@
376 /// a temporary file (in a special dir) is created for such data.
377 std::unique_ptr<FileSystem> tmp_fs_;
378 void delete_tempfile();
379- void create_tempfile_and_save_mapdata(FileSystem::Type type);
380
381 DISALLOW_COPY_AND_ASSIGN(EditorGameBase);
382 };
383
384=== modified file 'src/logic/map_objects/CMakeLists.txt'
385--- src/logic/map_objects/CMakeLists.txt 2019-08-09 17:29:40 +0000
386+++ src/logic/map_objects/CMakeLists.txt 2019-08-31 15:58:01 +0000
387@@ -39,8 +39,11 @@
388 immovable.cc
389 immovable.h
390 immovable_program.h
391+ immovable_program.cc
392 map_object.cc
393 map_object.h
394+ map_object_program.cc
395+ map_object_program.h
396 terrain_affinity.cc
397 terrain_affinity.h
398 tribes/attack_target.h
399@@ -129,7 +132,6 @@
400 graphic_surface
401 graphic_text_layout
402 graphic_toolbar_imageset
403- helper
404 io_fileread
405 io_filesystem
406 logic # TODO(GunChleoc): Circular dependency
407
408=== modified file 'src/logic/map_objects/bob.cc'
409--- src/logic/map_objects/bob.cc 2019-05-11 13:48:12 +0000
410+++ src/logic/map_objects/bob.cc 2019-08-31 15:58:01 +0000
411@@ -1092,8 +1092,8 @@
412 throw GameDataError("unknown bob task '%s'", name.c_str());
413 }
414
415-const BobProgramBase* Bob::Loader::get_program(const std::string& name) {
416- throw GameDataError("unknown bob program '%s'", name.c_str());
417+const MapObjectProgram* Bob::Loader::get_program(const std::string& name) {
418+ throw GameDataError("unknown map object program '%s'", name.c_str());
419 }
420
421 void Bob::save(EditorGameBase& eg, MapObjectSaver& mos, FileWrite& fw) {
422@@ -1156,7 +1156,7 @@
423 fw.unsigned_8(0);
424 }
425
426- fw.c_string(state.program ? state.program->get_name() : "");
427+ fw.c_string(state.program ? state.program->name() : "");
428 }
429 }
430 } // namespace Widelands
431
432=== modified file 'src/logic/map_objects/bob.h'
433--- src/logic/map_objects/bob.h 2019-04-24 06:01:37 +0000
434+++ src/logic/map_objects/bob.h 2019-08-31 15:58:01 +0000
435@@ -27,29 +27,19 @@
436 #include "graphic/diranimations.h"
437 #include "logic/map_objects/draw_text.h"
438 #include "logic/map_objects/map_object.h"
439+#include "logic/map_objects/map_object_program.h"
440 #include "logic/map_objects/walkingdir.h"
441 #include "logic/widelands_geometry.h"
442
443 namespace Widelands {
444+
445+class Bob;
446 class Map;
447 struct Route;
448 struct Transfer;
449 class TribeDescr;
450
451 /**
452- * BobProgramBase is only used that
453- * get_name always works
454- */
455-
456-struct BobProgramBase {
457- virtual ~BobProgramBase() {
458- }
459- virtual std::string get_name() const = 0;
460-};
461-
462-class Bob;
463-
464-/**
465 * Implement MapObjectDescr for the following \ref Bob class.
466 */
467 class BobDescr : public MapObjectDescr {
468@@ -223,7 +213,7 @@
469 DirAnimations diranims;
470 Path* path;
471 Route* route;
472- const BobProgramBase* program; ///< pointer to current program
473+ const MapObjectProgram* program; ///< pointer to current program
474 };
475
476 MO_DESCR(BobDescr)
477@@ -419,7 +409,7 @@
478
479 protected:
480 virtual const Task* get_task(const std::string& name);
481- virtual const BobProgramBase* get_program(const std::string& name);
482+ virtual const MapObjectProgram* get_program(const std::string& name);
483
484 private:
485 struct LoadState {
486
487=== modified file 'src/logic/map_objects/immovable.cc'
488--- src/logic/map_objects/immovable.cc 2019-05-28 17:01:30 +0000
489+++ src/logic/map_objects/immovable.cc 2019-08-31 15:58:01 +0000
490@@ -19,41 +19,17 @@
491
492 #include "logic/map_objects/immovable.h"
493
494-#include <cstdio>
495-#include <cstring>
496 #include <memory>
497
498-#include <boost/algorithm/string.hpp>
499-#include <boost/format.hpp>
500-
501-#include "base/log.h"
502-#include "base/macros.h"
503-#include "base/wexception.h"
504-#include "config.h"
505-#include "graphic/graphic.h"
506-#include "graphic/rendertarget.h"
507-#include "helper.h"
508 #include "io/fileread.h"
509 #include "io/filewrite.h"
510-#include "logic/editor_game_base.h"
511-#include "logic/field.h"
512-#include "logic/game.h"
513 #include "logic/game_data_error.h"
514-#include "logic/map.h"
515 #include "logic/map_objects/immovable_program.h"
516 #include "logic/map_objects/terrain_affinity.h"
517-#include "logic/map_objects/tribes/tribe_descr.h"
518-#include "logic/map_objects/tribes/worker.h"
519 #include "logic/map_objects/world/world.h"
520-#include "logic/mapfringeregion.h"
521 #include "logic/player.h"
522 #include "logic/widelands_geometry_io.h"
523-#include "map_io/tribes_legacy_lookup_table.h"
524 #include "map_io/world_legacy_lookup_table.h"
525-#include "notifications/notifications.h"
526-#include "scripting/lua_table.h"
527-#include "sound/note_sound.h"
528-#include "sound/sound_handler.h"
529
530 namespace Widelands {
531
532@@ -140,52 +116,6 @@
533 /*
534 ==============================================================================
535
536-ImmovableProgram IMPLEMENTATION
537-
538-==============================================================================
539-*/
540-
541-ImmovableProgram::ImmovableProgram(const std::string& init_name,
542- const std::vector<std::string>& lines,
543- ImmovableDescr* immovable)
544- : name_(init_name) {
545- for (const std::string& line : lines) {
546- std::vector<std::string> parts;
547- boost::split(parts, line, boost::is_any_of("="));
548- if (parts.size() != 2) {
549- throw GameDataError("invalid line: %s.", line.c_str());
550- }
551- std::unique_ptr<char[]> arguments(new char[parts[1].size() + 1]);
552- strncpy(arguments.get(), parts[1].c_str(), parts[1].size() + 1);
553-
554- Action* action;
555- if (parts[0] == "animate") {
556- action = new ActAnimate(arguments.get(), *immovable);
557- } else if (parts[0] == "transform") {
558- action = new ActTransform(arguments.get(), *immovable);
559- } else if (parts[0] == "grow") {
560- action = new ActGrow(arguments.get(), *immovable);
561- } else if (parts[0] == "remove") {
562- action = new ActRemove(arguments.get(), *immovable);
563- } else if (parts[0] == "seed") {
564- action = new ActSeed(arguments.get(), *immovable);
565- } else if (parts[0] == "playsound") {
566- action = new ActPlaySound(arguments.get(), *immovable);
567- } else if (parts[0] == "construct") {
568- action = new ActConstruct(arguments.get(), *immovable);
569- } else {
570- throw GameDataError("unknown command type \"%s\" in immovable \"%s\"", parts[0].c_str(),
571- immovable->name().c_str());
572- }
573- actions_.push_back(action);
574- }
575- if (actions_.empty())
576- throw GameDataError("no actions");
577-}
578-
579-/*
580-==============================================================================
581-
582 ImmovableDescr IMPLEMENTATION
583
584 ==============================================================================
585@@ -249,12 +179,16 @@
586 }
587
588 std::unique_ptr<LuaTable> programs = table.get_table("programs");
589- for (const std::string& program_name : programs->keys<std::string>()) {
590+ for (std::string program_name : programs->keys<std::string>()) {
591+ std::transform(program_name.begin(), program_name.end(), program_name.begin(), tolower);
592+ if (programs_.count(program_name)) {
593+ throw GameDataError("Program '%s' has already been declared for immovable '%s'", program_name.c_str(), name().c_str());
594+ }
595 try {
596 programs_[program_name] = new ImmovableProgram(
597- program_name, programs->get_table(program_name)->array_entries<std::string>(), this);
598+ program_name, programs->get_table(program_name)->array_entries<std::string>(), *this);
599 } catch (const std::exception& e) {
600- throw wexception("Error in program %s: %s", program_name.c_str(), e.what());
601+ throw GameDataError("%s: Error in immovable program %s: %s", name().c_str(), program_name.c_str(), e.what());
602 }
603 }
604
605@@ -308,9 +242,9 @@
606 void ImmovableDescr::make_sure_default_program_is_there() {
607 if (!programs_.count("program")) { // default program
608 assert(is_animation_known("idle"));
609- char parameters[] = "idle";
610+ std::vector<std::string> arguments{"idle"};
611 programs_["program"] =
612- new ImmovableProgram("program", new ImmovableProgram::ActAnimate(parameters, *this));
613+ new ImmovableProgram("program", std::unique_ptr<ImmovableProgram::Action>(new ImmovableProgram::ActAnimate(arguments, *this)));
614 }
615 }
616
617@@ -418,10 +352,12 @@
618 ImmovableProgram const* prog = program_;
619 if (!prog) {
620 prog = descr().get_program("program");
621- assert(prog != nullptr);
622- }
623- if (upcast(ImmovableProgram::ActAnimate const, act_animate, &(*prog)[program_ptr_]))
624- start_animation(egbase, descr().get_animation(act_animate->animation(), this));
625+ }
626+ assert(prog != nullptr);
627+
628+ if (upcast(ImmovableProgram::ActAnimate const, act_animate, &(*prog)[program_ptr_])) {
629+ start_animation(egbase, act_animate->animation());
630+ }
631
632 if (upcast(Game, game, &egbase)) {
633 switch_program(*game, "program");
634@@ -761,391 +697,6 @@
635 return loader.release();
636 }
637
638-ImmovableProgram::Action::~Action() {
639-}
640-
641-ImmovableProgram::ActAnimate::ActAnimate(char* parameters, ImmovableDescr& descr) {
642- try {
643- bool reached_end;
644- animation_name_ = std::string(next_word(parameters, reached_end));
645- if (!descr.is_animation_known(animation_name_)) {
646- throw GameDataError("Unknown animation: %s.", animation_name_.c_str());
647- }
648-
649- if (!reached_end) { // The next parameter is the duration.
650- char* endp;
651- long int const value = strtol(parameters, &endp, 0);
652- if (*endp || value <= 0)
653- throw GameDataError("expected %s but found \"%s\"", "duration in ms", parameters);
654- duration_ = value;
655- } else {
656- duration_ = 0; // forever
657- }
658- } catch (const WException& e) {
659- throw GameDataError("animate: %s", e.what());
660- }
661-}
662-
663-/// Use convolutuion to make the animation time a random variable with binomial
664-/// distribution and the configured time as the expected value.
665-void ImmovableProgram::ActAnimate::execute(Game& game, Immovable& immovable) const {
666- immovable.start_animation(game, immovable.descr().get_animation(animation_name_, &immovable));
667- immovable.program_step(
668- game, duration_ ? 1 + game.logic_rand() % duration_ + game.logic_rand() % duration_ : 0);
669-}
670-
671-ImmovableProgram::ActPlaySound::ActPlaySound(char* parameters, const ImmovableDescr&) {
672- try {
673- bool reached_end;
674- std::string name = next_word(parameters, reached_end);
675-
676- if (!reached_end) {
677- char* endp;
678- unsigned long long int const value = strtoull(parameters, &endp, 0);
679- priority = value;
680- if (*endp || priority != value)
681- throw GameDataError("expected %s but found \"%s\"", "priority", parameters);
682- } else
683- priority = 127;
684-
685- fx = g_sh->register_fx(SoundType::kAmbient, name);
686- } catch (const WException& e) {
687- throw GameDataError("playsound: %s", e.what());
688- }
689-}
690-
691-/** Demand from the g_sound_handler to play a certain sound effect.
692- * Whether the effect actually gets played
693- * is decided only by the sound server*/
694-void ImmovableProgram::ActPlaySound::execute(Game& game, Immovable& immovable) const {
695- Notifications::publish(NoteSound(SoundType::kAmbient, fx, immovable.get_position(), priority));
696- immovable.program_step(game);
697-}
698-
699-ImmovableProgram::ActTransform::ActTransform(char* parameters, ImmovableDescr& descr) {
700- try {
701- tribe = true;
702- bob = false;
703- probability = 0;
704-
705- std::vector<std::string> params = split_string(parameters, " ");
706- for (uint32_t i = 0; i < params.size(); ++i) {
707- if (params[i] == "bob")
708- bob = true;
709- else if (params[i] == "immovable")
710- bob = false;
711- else if (params[i][0] >= '0' && params[i][0] <= '9') {
712- long int const value = atoi(params[i].c_str());
713- if (value < 1 || 254 < value)
714- throw GameDataError("expected %s but found \"%s\"", "probability in range [1, 254]",
715- params[i].c_str());
716- probability = value;
717- } else {
718- std::vector<std::string> segments = split_string(params[i], ":");
719-
720- if (segments.size() > 2)
721- throw GameDataError("object type has more than 2 segments");
722- if (segments.size() == 2) {
723- if (segments[0] == "world")
724- tribe = false;
725- else if (segments[0] == "tribe") {
726- if (descr.owner_type() != MapObjectDescr::OwnerType::kTribe)
727- throw GameDataError("scope \"tribe\" does not match the immovable type");
728- tribe = true;
729- } else
730- throw GameDataError("unknown scope \"%s\" given for target type (must be "
731- "\"world\" or \"tribe\")",
732- parameters);
733-
734- type_name = segments[1];
735- } else {
736- type_name = segments[0];
737- }
738- }
739- }
740- if (type_name == descr.name())
741- throw GameDataError("illegal transformation to the same type");
742- } catch (const WException& e) {
743- throw GameDataError("transform: %s", e.what());
744- }
745-}
746-
747-void ImmovableProgram::ActTransform::execute(Game& game, Immovable& immovable) const {
748- if (probability == 0 || game.logic_rand() % 256 < probability) {
749- Player* player = immovable.get_owner();
750- Coords const c = immovable.get_position();
751- MapObjectDescr::OwnerType owner_type = immovable.descr().owner_type();
752- immovable.remove(game); // Now immovable is a dangling reference!
753-
754- if (bob) {
755- game.create_ship(c, type_name, player);
756- } else {
757- game.create_immovable_with_name(
758- c, type_name, owner_type, player, nullptr /* former_building_descr */);
759- }
760- } else
761- immovable.program_step(game);
762-}
763-
764-ImmovableProgram::ActGrow::ActGrow(char* parameters, ImmovableDescr& descr) {
765- if (!descr.has_terrain_affinity()) {
766- throw GameDataError(
767- "Immovable %s can 'grow', but has no terrain_affinity entry.", descr.name().c_str());
768- }
769-
770- try {
771- tribe = true;
772- for (char* p = parameters;;)
773- switch (*p) {
774- case ':': {
775- *p = '\0';
776- ++p;
777- if (descr.owner_type() != MapObjectDescr::OwnerType::kTribe)
778- throw GameDataError("immovable type not in tribes but target type has scope "
779- "(\"%s\")",
780- parameters);
781- else if (strcmp(parameters, "world"))
782- throw GameDataError("scope \"%s\" given for target type (must be "
783- "\"world\")",
784- parameters);
785- tribe = false;
786- parameters = p;
787- break;
788- }
789- case '\0':
790- goto end;
791- default:
792- ++p;
793- break;
794- }
795- end:
796- type_name = parameters;
797- } catch (const WException& e) {
798- throw GameDataError("grow: %s", e.what());
799- }
800-}
801-
802-void ImmovableProgram::ActGrow::execute(Game& game, Immovable& immovable) const {
803- const Map& map = game.map();
804- FCoords const f = map.get_fcoords(immovable.get_position());
805- const ImmovableDescr& descr = immovable.descr();
806-
807- if ((game.logic_rand() % TerrainAffinity::kPrecisionFactor) <
808- probability_to_grow(descr.terrain_affinity(), f, map, game.world().terrains())) {
809- MapObjectDescr::OwnerType owner_type = descr.owner_type();
810- Player* owner = immovable.get_owner();
811- immovable.remove(game); // Now immovable is a dangling reference!
812- game.create_immovable_with_name(
813- f, type_name, owner_type, owner, nullptr /* former_building_descr */);
814- } else {
815- immovable.program_step(game);
816- }
817-}
818-
819-/**
820- * remove
821- */
822-ImmovableProgram::ActRemove::ActRemove(char* parameters, ImmovableDescr&) {
823- try {
824- if (*parameters) {
825- char* endp;
826- long int const value = strtol(parameters, &endp, 0);
827- if (*endp || value < 1 || 254 < value)
828- throw GameDataError(
829- "expected %s but found \"%s\"", "probability in range [1, 254]", parameters);
830- probability = value;
831- } else
832- probability = 0;
833- } catch (const WException& e) {
834- throw GameDataError("remove: %s", e.what());
835- }
836-}
837-
838-void ImmovableProgram::ActRemove::execute(Game& game, Immovable& immovable) const {
839- if (probability == 0 || game.logic_rand() % 256 < probability)
840- immovable.remove(game); // Now immovable is a dangling reference!
841- else
842- immovable.program_step(game);
843-}
844-
845-ImmovableProgram::ActSeed::ActSeed(char* parameters, ImmovableDescr& descr) {
846- try {
847- probability = 0;
848- for (char* p = parameters;;)
849- switch (*p) {
850- case ':': {
851- *p = '\0';
852- ++p;
853- if (descr.owner_type() != MapObjectDescr::OwnerType::kTribe)
854- throw GameDataError("immovable type not in tribes but target type has scope "
855- "(\"%s\")",
856- parameters);
857- else if (strcmp(parameters, "world"))
858- throw GameDataError("scope \"%s\" given for target type (must be "
859- "\"world\")",
860- parameters);
861- parameters = p;
862- break;
863- }
864- case ' ': {
865- *p = '\0';
866- ++p;
867- char* endp;
868- long int const value = strtol(p, &endp, 0);
869- if (*endp || value < 1 || 254 < value)
870- throw GameDataError(
871- "expected %s but found \"%s\"", "probability in range [1, 254]", p);
872- probability = value;
873- // fallthrough
874- }
875- FALLS_THROUGH;
876- case '\0':
877- goto end;
878- default:
879- ++p;
880- break;
881- }
882- end:
883- type_name = parameters;
884- } catch (const WException& e) {
885- throw GameDataError("seed: %s", e.what());
886- }
887-}
888-
889-void ImmovableProgram::ActSeed::execute(Game& game, Immovable& immovable) const {
890- const Map& map = game.map();
891- FCoords const f = map.get_fcoords(immovable.get_position());
892- const ImmovableDescr& descr = immovable.descr();
893-
894- if ((game.logic_rand() % TerrainAffinity::kPrecisionFactor) <
895- probability_to_grow(descr.terrain_affinity(), f, map, game.world().terrains())) {
896- // Seed a new tree.
897- MapFringeRegion<> mr(map, Area<>(f, 0));
898- uint32_t fringe_size = 0;
899- do {
900- mr.extend(map);
901- fringe_size += 6;
902- } while (game.logic_rand() % std::numeric_limits<uint8_t>::max() < probability);
903-
904- for (uint32_t n = game.logic_rand() % fringe_size; n; --n) {
905- mr.advance(map);
906- }
907-
908- const FCoords new_location = map.get_fcoords(mr.location());
909- if (!new_location.field->get_immovable() &&
910- (new_location.field->nodecaps() & MOVECAPS_WALK) &&
911- (game.logic_rand() % TerrainAffinity::kPrecisionFactor) <
912- probability_to_grow(
913- descr.terrain_affinity(), new_location, map, game.world().terrains())) {
914- game.create_immovable_with_name(mr.location(), type_name, descr.owner_type(),
915- nullptr /* owner */, nullptr /* former_building_descr */);
916- }
917- }
918-
919- immovable.program_step(game);
920-}
921-
922-ImmovableProgram::ActConstruct::ActConstruct(char* parameters, ImmovableDescr& descr) {
923- try {
924- if (descr.owner_type() != MapObjectDescr::OwnerType::kTribe)
925- throw GameDataError("only usable for tribe immovable");
926-
927- std::vector<std::string> params = split_string(parameters, " ");
928-
929- if (params.size() != 3)
930- throw GameDataError("usage: animation-name buildtime decaytime");
931-
932- buildtime_ = atoi(params[1].c_str());
933- decaytime_ = atoi(params[2].c_str());
934-
935- animation_name_ = params[0];
936- if (!descr.is_animation_known(animation_name_)) {
937- throw GameDataError("unknown animation \"%s\" in immovable program for immovable \"%s\"",
938- animation_name_.c_str(), descr.name().c_str());
939- }
940- } catch (const WException& e) {
941- throw GameDataError("construct: %s", e.what());
942- }
943-}
944-
945-constexpr uint8_t kCurrentPacketVersionConstructionData = 1;
946-
947-struct ActConstructData : ImmovableActionData {
948- const char* name() const override {
949- return "construct";
950- }
951- void save(FileWrite& fw, Immovable& imm) override {
952- fw.unsigned_8(kCurrentPacketVersionConstructionData);
953- delivered.save(fw, imm.get_owner()->tribe());
954- }
955-
956- static ActConstructData* load(FileRead& fr, Immovable& imm) {
957- ActConstructData* d = new ActConstructData;
958-
959- try {
960- uint8_t packet_version = fr.unsigned_8();
961- if (packet_version == kCurrentPacketVersionConstructionData) {
962- d->delivered.load(fr, imm.get_owner()->tribe());
963- } else {
964- throw UnhandledVersionError(
965- "ActConstructData", packet_version, kCurrentPacketVersionConstructionData);
966- }
967- } catch (const WException& e) {
968- delete d;
969- d = nullptr;
970- throw GameDataError("ActConstructData: %s", e.what());
971- }
972-
973- return d;
974- }
975-
976- Buildcost delivered;
977-};
978-
979-void ImmovableProgram::ActConstruct::execute(Game& g, Immovable& imm) const {
980- ActConstructData* d = imm.get_action_data<ActConstructData>();
981- if (!d) {
982- // First execution
983- d = new ActConstructData;
984- imm.set_action_data(d);
985-
986- imm.start_animation(g, imm.descr().get_animation(animation_name_, &imm));
987- imm.anim_construction_total_ = imm.descr().buildcost().total();
988- } else {
989- // Perhaps we are called due to the construction timeout of the last construction step
990- Buildcost remaining;
991- imm.construct_remaining_buildcost(g, &remaining);
992- if (remaining.empty()) {
993- imm.program_step(g);
994- return;
995- }
996-
997- // Otherwise, this is a decay timeout
998- uint32_t totaldelivered = 0;
999- for (Buildcost::const_iterator it = d->delivered.begin(); it != d->delivered.end(); ++it)
1000- totaldelivered += it->second;
1001-
1002- if (!totaldelivered) {
1003- imm.remove(g);
1004- return;
1005- }
1006-
1007- uint32_t randdecay = g.logic_rand() % totaldelivered;
1008- for (Buildcost::iterator it = d->delivered.begin(); it != d->delivered.end(); ++it) {
1009- if (randdecay < it->second) {
1010- it->second--;
1011- break;
1012- }
1013-
1014- randdecay -= it->second;
1015- }
1016-
1017- imm.anim_construction_done_ = d->delivered.total();
1018- }
1019-
1020- imm.program_step_ = imm.schedule_act(g, decaytime_);
1021-}
1022-
1023 /**
1024 * For an immovable that is currently in construction mode, return \c true and
1025 * compute the remaining buildcost.
1026@@ -1208,16 +759,6 @@
1027 return true;
1028 }
1029
1030-ImmovableActionData*
1031-ImmovableActionData::load(FileRead& fr, Immovable& imm, const std::string& name) {
1032- // TODO(GunChleoc): Use "construct" only after Build 20
1033- if (name == "construction" || name == "construct")
1034- return ActConstructData::load(fr, imm);
1035- else {
1036- log("ImmovableActionData::load: type %s not known", name.c_str());
1037- return nullptr;
1038- }
1039-}
1040
1041 /*
1042 ==============================================================================
1043
1044=== added file 'src/logic/map_objects/immovable_program.cc'
1045--- src/logic/map_objects/immovable_program.cc 1970-01-01 00:00:00 +0000
1046+++ src/logic/map_objects/immovable_program.cc 2019-08-31 15:58:01 +0000
1047@@ -0,0 +1,344 @@
1048+/*
1049+ * Copyright (C) 2002-2019 by the Widelands Development Team
1050+ *
1051+ * This program is free software; you can redistribute it and/or
1052+ * modify it under the terms of the GNU General Public License
1053+ * as published by the Free Software Foundation; either version 2
1054+ * of the License, or (at your option) any later version.
1055+ *
1056+ * This program is distributed in the hope that it will be useful,
1057+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1058+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1059+ * GNU General Public License for more details.
1060+ *
1061+ * You should have received a copy of the GNU General Public License
1062+ * along with this program; if not, write to the Free Software
1063+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
1064+ *
1065+ */
1066+
1067+#include "logic/map_objects/immovable_program.h"
1068+
1069+#include <memory>
1070+
1071+#include "logic/game.h"
1072+#include "logic/game_data_error.h"
1073+#include "logic/map_objects/terrain_affinity.h"
1074+#include "logic/map_objects/world/world.h"
1075+#include "logic/mapfringeregion.h"
1076+#include "logic/player.h"
1077+#include "sound/note_sound.h"
1078+
1079+namespace Widelands {
1080+
1081+ImmovableProgram::ImmovableProgram(const std::string& init_name, std::unique_ptr<Action> action) : MapObjectProgram(init_name) {
1082+ actions_.push_back(std::move(action));
1083+}
1084+
1085+ImmovableProgram::ImmovableProgram(const std::string& init_name,
1086+ const std::vector<std::string>& lines,
1087+ const ImmovableDescr& immovable)
1088+ : MapObjectProgram(init_name) {
1089+ for (const std::string& line : lines) {
1090+ if (line.empty()) {
1091+ throw GameDataError("Empty line");
1092+ }
1093+ try {
1094+ ProgramParseInput parseinput = parse_program_string(line);
1095+
1096+ if (parseinput.name == "animate") {
1097+ actions_.push_back(std::unique_ptr<Action>(new ActAnimate(parseinput.arguments, immovable)));
1098+ } else if (parseinput.name == "transform") {
1099+ actions_.push_back(std::unique_ptr<Action>(new ActTransform(parseinput.arguments, immovable)));
1100+ } else if (parseinput.name == "grow") {
1101+ actions_.push_back(std::unique_ptr<Action>(new ActGrow(parseinput.arguments, immovable)));
1102+ } else if (parseinput.name == "remove") {
1103+ actions_.push_back(std::unique_ptr<Action>(new ActRemove(parseinput.arguments)));
1104+ } else if (parseinput.name == "seed") {
1105+ actions_.push_back(std::unique_ptr<Action>(new ActSeed(parseinput.arguments, immovable)));
1106+ } else if (parseinput.name == "playsound") {
1107+ actions_.push_back(std::unique_ptr<Action>(new ActPlaySound(parseinput.arguments)));
1108+ } else if (parseinput.name == "construct") {
1109+ actions_.push_back(std::unique_ptr<Action>(new ActConstruct(parseinput.arguments, immovable)));
1110+ } else {
1111+ throw GameDataError("Unknown command '%s' in line '%s'", parseinput.name.c_str(), line.c_str());
1112+ }
1113+ } catch (const GameDataError& e) {
1114+ throw GameDataError("Error reading line '%s': %s", line.c_str(), e.what());
1115+ }
1116+ }
1117+ if (actions_.empty()) {
1118+ throw GameDataError("No actions found");
1119+ }
1120+}
1121+
1122+ImmovableProgram::Action::~Action() {
1123+}
1124+
1125+ImmovableProgram::ActAnimate::ActAnimate(const std::vector<std::string>& arguments, const ImmovableDescr& descr) {
1126+ parameters = MapObjectProgram::parse_act_animate(arguments, descr, true);
1127+}
1128+
1129+/// Use convolution to make the animation time a random variable with binomial
1130+/// distribution and the configured time as the expected value.
1131+void ImmovableProgram::ActAnimate::execute(Game& game, Immovable& immovable) const {
1132+ immovable.start_animation(game, parameters.animation);
1133+ immovable.program_step(
1134+ game, parameters.duration ? 1 + game.logic_rand() % parameters.duration + game.logic_rand() % parameters.duration : 0);
1135+}
1136+
1137+ImmovableProgram::ActPlaySound::ActPlaySound(const std::vector<std::string>& arguments) {
1138+ parameters = MapObjectProgram::parse_act_play_sound(arguments, kFxPriorityAllowMultiple - 1);
1139+}
1140+
1141+/**
1142+ * Send request to the g_sound_handler to play a certain sound effect.
1143+ * Whether the effect actually gets played is decided by the sound server itself.
1144+ */
1145+void ImmovableProgram::ActPlaySound::execute(Game& game, Immovable& immovable) const {
1146+ Notifications::publish(NoteSound(SoundType::kAmbient, parameters.fx, immovable.get_position(), parameters.priority));
1147+ immovable.program_step(game);
1148+}
1149+
1150+ImmovableProgram::ActTransform::ActTransform(std::vector<std::string>& arguments, const ImmovableDescr& descr) {
1151+ if (arguments.empty()) {
1152+ throw GameDataError("Usage: transform=[bob] <name> [<probability>]");
1153+ }
1154+ try {
1155+ bob = false;
1156+ probability = 0;
1157+
1158+ for (const std::string& argument : arguments) {
1159+ if (argument == "bob") {
1160+ bob = true;
1161+ } else if (argument[0] >= '0' && argument[0] <= '9') {
1162+ probability = read_positive(argument, 254);
1163+ } else {
1164+ // TODO(GunChleoc): If would be nice to check if target exists, but we can't guarantee the load order. Maybe in postload() one day.
1165+ type_name = argument;
1166+ }
1167+ }
1168+ if (type_name == descr.name()) {
1169+ throw GameDataError("illegal transformation to the same type");
1170+ }
1171+ } catch (const WException& e) {
1172+ throw GameDataError("transform: %s", e.what());
1173+ }
1174+}
1175+
1176+void ImmovableProgram::ActTransform::execute(Game& game, Immovable& immovable) const {
1177+ if (probability == 0 || game.logic_rand() % 256 < probability) {
1178+ Player* player = immovable.get_owner();
1179+ Coords const c = immovable.get_position();
1180+ MapObjectDescr::OwnerType owner_type = immovable.descr().owner_type();
1181+ immovable.remove(game); // Now immovable is a dangling reference!
1182+
1183+ if (bob) {
1184+ game.create_ship(c, type_name, player);
1185+ } else {
1186+ game.create_immovable_with_name(
1187+ c, type_name, owner_type, player, nullptr /* former_building_descr */);
1188+ }
1189+ } else
1190+ immovable.program_step(game);
1191+}
1192+
1193+ImmovableProgram::ActGrow::ActGrow(std::vector<std::string>& arguments, const ImmovableDescr& descr) {
1194+ if (arguments.size() != 1) {
1195+ throw GameDataError("Usage: grow=<immovable name>");
1196+ }
1197+ if (!descr.has_terrain_affinity()) {
1198+ throw GameDataError(
1199+ "Immovable %s can 'grow', but has no terrain_affinity entry.", descr.name().c_str());
1200+ }
1201+
1202+ // TODO(GunChleoc): If would be nice to check if target exists, but we can't guarantee the load order. Maybe in postload() one day.
1203+ type_name = arguments.front();
1204+}
1205+
1206+void ImmovableProgram::ActGrow::execute(Game& game, Immovable& immovable) const {
1207+ const Map& map = game.map();
1208+ FCoords const f = map.get_fcoords(immovable.get_position());
1209+ const ImmovableDescr& descr = immovable.descr();
1210+
1211+ if ((game.logic_rand() % TerrainAffinity::kPrecisionFactor) <
1212+ probability_to_grow(descr.terrain_affinity(), f, map, game.world().terrains())) {
1213+ MapObjectDescr::OwnerType owner_type = descr.owner_type();
1214+ Player* owner = immovable.get_owner();
1215+ immovable.remove(game); // Now immovable is a dangling reference!
1216+ game.create_immovable_with_name(
1217+ f, type_name, owner_type, owner, nullptr /* former_building_descr */);
1218+ } else {
1219+ immovable.program_step(game);
1220+ }
1221+}
1222+
1223+/**
1224+ * remove
1225+ */
1226+ImmovableProgram::ActRemove::ActRemove(std::vector<std::string>& arguments) {
1227+ if (arguments.size() > 1) {
1228+ throw GameDataError("Usage: remove=[<probability>]");
1229+ }
1230+ probability = arguments.empty() ? 0 : read_positive(arguments.front(), 254);
1231+}
1232+
1233+void ImmovableProgram::ActRemove::execute(Game& game, Immovable& immovable) const {
1234+ if (probability == 0 || game.logic_rand() % 256 < probability) {
1235+ immovable.remove(game); // Now immovable is a dangling reference!
1236+ } else {
1237+ immovable.program_step(game);
1238+ }
1239+}
1240+
1241+ImmovableProgram::ActSeed::ActSeed(std::vector<std::string>& arguments, const ImmovableDescr& descr) {
1242+ if (arguments.size() != 1) {
1243+ throw GameDataError("Usage: seed=<immovable name>");
1244+ }
1245+ if (!descr.has_terrain_affinity()) {
1246+ throw GameDataError(
1247+ "Immovable %s can 'seed', but has no terrain_affinity entry.", descr.name().c_str());
1248+ }
1249+
1250+ // TODO(GunChleoc): If would be nice to check if target exists, but we can't guarantee the load order. Maybe in postload() one day.
1251+ type_name = arguments.front();
1252+}
1253+
1254+void ImmovableProgram::ActSeed::execute(Game& game, Immovable& immovable) const {
1255+ const Map& map = game.map();
1256+ FCoords const f = map.get_fcoords(immovable.get_position());
1257+ const ImmovableDescr& descr = immovable.descr();
1258+
1259+ if ((game.logic_rand() % TerrainAffinity::kPrecisionFactor) <
1260+ probability_to_grow(descr.terrain_affinity(), f, map, game.world().terrains())) {
1261+ // Seed a new tree.
1262+ MapFringeRegion<> mr(map, Area<>(f, 0));
1263+ uint32_t fringe_size = 0;
1264+ do {
1265+ mr.extend(map);
1266+ fringe_size += 6;
1267+ } while (game.logic_rand() % std::numeric_limits<uint8_t>::max() < probability);
1268+
1269+ for (uint32_t n = game.logic_rand() % fringe_size; n; --n) {
1270+ mr.advance(map);
1271+ }
1272+
1273+ const FCoords new_location = map.get_fcoords(mr.location());
1274+ if (!new_location.field->get_immovable() &&
1275+ (new_location.field->nodecaps() & MOVECAPS_WALK) &&
1276+ (game.logic_rand() % TerrainAffinity::kPrecisionFactor) <
1277+ probability_to_grow(
1278+ descr.terrain_affinity(), new_location, map, game.world().terrains())) {
1279+ game.create_immovable_with_name(mr.location(), type_name, descr.owner_type(),
1280+ nullptr /* owner */, nullptr /* former_building_descr */);
1281+ }
1282+ }
1283+
1284+ immovable.program_step(game);
1285+}
1286+
1287+ImmovableProgram::ActConstruct::ActConstruct(std::vector<std::string>& arguments, const ImmovableDescr& descr) {
1288+ if (arguments.size() != 3) {
1289+ throw GameDataError("Usage: construct=<animation> <build duration> <decay duration>");
1290+ }
1291+ try {
1292+ animation_name_ = arguments[0];
1293+ if (!descr.is_animation_known(animation_name_)) {
1294+ throw GameDataError("Unknown animation '%s' in immovable program for immovable '%s'",
1295+ animation_name_.c_str(), descr.name().c_str());
1296+ }
1297+
1298+ buildtime_ = read_positive(arguments[1]);
1299+ decaytime_ = read_positive(arguments[2]);
1300+ } catch (const WException& e) {
1301+ throw GameDataError("construct: %s", e.what());
1302+ }
1303+}
1304+
1305+constexpr uint8_t kCurrentPacketVersionConstructionData = 1;
1306+
1307+
1308+const char* ActConstructData::name() const {
1309+ return "construct";
1310+}
1311+void ActConstructData::save(FileWrite& fw, Immovable& imm) const {
1312+ fw.unsigned_8(kCurrentPacketVersionConstructionData);
1313+ delivered.save(fw, imm.get_owner()->tribe());
1314+}
1315+
1316+ActConstructData* ActConstructData::load(FileRead& fr, Immovable& imm) {
1317+ ActConstructData* d = new ActConstructData;
1318+
1319+ try {
1320+ uint8_t packet_version = fr.unsigned_8();
1321+ if (packet_version == kCurrentPacketVersionConstructionData) {
1322+ d->delivered.load(fr, imm.get_owner()->tribe());
1323+ } else {
1324+ throw UnhandledVersionError(
1325+ "ActConstructData", packet_version, kCurrentPacketVersionConstructionData);
1326+ }
1327+ } catch (const WException& e) {
1328+ delete d;
1329+ d = nullptr;
1330+ throw GameDataError("ActConstructData: %s", e.what());
1331+ }
1332+
1333+ return d;
1334+}
1335+
1336+
1337+void ImmovableProgram::ActConstruct::execute(Game& g, Immovable& imm) const {
1338+ ActConstructData* d = imm.get_action_data<ActConstructData>();
1339+ if (!d) {
1340+ // First execution
1341+ d = new ActConstructData;
1342+ imm.set_action_data(d);
1343+
1344+ imm.start_animation(g, imm.descr().get_animation(animation_name_, &imm));
1345+ imm.anim_construction_total_ = imm.descr().buildcost().total();
1346+ } else {
1347+ // Perhaps we are called due to the construction timeout of the last construction step
1348+ Buildcost remaining;
1349+ imm.construct_remaining_buildcost(g, &remaining);
1350+ if (remaining.empty()) {
1351+ imm.program_step(g);
1352+ return;
1353+ }
1354+
1355+ // Otherwise, this is a decay timeout
1356+ uint32_t totaldelivered = 0;
1357+ for (const auto& addme : d->delivered) {
1358+ totaldelivered += addme.second;
1359+ }
1360+
1361+ if (!totaldelivered) {
1362+ imm.remove(g);
1363+ return;
1364+ }
1365+
1366+ uint32_t randdecay = g.logic_rand() % totaldelivered;
1367+ for (Buildcost::iterator it = d->delivered.begin(); it != d->delivered.end(); ++it) {
1368+ if (randdecay < it->second) {
1369+ it->second--;
1370+ break;
1371+ }
1372+
1373+ randdecay -= it->second;
1374+ }
1375+
1376+ imm.anim_construction_done_ = d->delivered.total();
1377+ }
1378+
1379+ imm.program_step_ = imm.schedule_act(g, decaytime_);
1380+}
1381+
1382+ImmovableActionData*
1383+ImmovableActionData::load(FileRead& fr, Immovable& imm, const std::string& name) {
1384+ if (name == "construct") {
1385+ return ActConstructData::load(fr, imm);
1386+ } else {
1387+ log("ImmovableActionData::load: type %s not known", name.c_str());
1388+ return nullptr;
1389+ }
1390+}
1391+} // namespace Widelands
1392
1393=== modified file 'src/logic/map_objects/immovable_program.h'
1394--- src/logic/map_objects/immovable_program.h 2019-04-26 12:46:40 +0000
1395+++ src/logic/map_objects/immovable_program.h 2019-08-31 15:58:01 +0000
1396@@ -21,21 +21,19 @@
1397 #define WL_LOGIC_MAP_OBJECTS_IMMOVABLE_PROGRAM_H
1398
1399 #include <cstring>
1400+#include <memory>
1401 #include <string>
1402
1403 #include "base/macros.h"
1404
1405-/*
1406- * Implementation is in immovable.cc
1407- */
1408-
1409 #include "logic/map_objects/buildcost.h"
1410 #include "logic/map_objects/immovable.h"
1411+#include "logic/map_objects/map_object_program.h"
1412
1413 namespace Widelands {
1414
1415 /// Ordered sequence of actions (at least 1). Has a name.
1416-struct ImmovableProgram {
1417+struct ImmovableProgram : public MapObjectProgram {
1418
1419 /// Can be executed on an Immovable.
1420 struct Action {
1421@@ -63,15 +61,14 @@
1422 /// will not be stopped by this command. It will run until another animation
1423 /// is started.)
1424 struct ActAnimate : public Action {
1425- ActAnimate(char* parameters, ImmovableDescr&);
1426+ ActAnimate(const std::vector<std::string>& arguments, const ImmovableDescr&);
1427 void execute(Game&, Immovable&) const override;
1428- const std::string& animation() const {
1429- return animation_name_;
1430+ uint32_t animation() const {
1431+ return parameters.animation;
1432 }
1433
1434 private:
1435- std::string animation_name_;
1436- Duration duration_;
1437+ AnimationParameters parameters;
1438 };
1439
1440 /// Transforms the immovable into another immovable or into a bob
1441@@ -91,28 +88,26 @@
1442 /// name:
1443 /// name of the replacement object
1444 struct ActTransform : public Action {
1445- ActTransform(char* parameters, ImmovableDescr&);
1446+ ActTransform(std::vector<std::string>& arguments, const ImmovableDescr&);
1447 void execute(Game&, Immovable&) const override;
1448
1449 private:
1450 std::string type_name;
1451 bool bob;
1452- bool tribe;
1453 uint8_t probability;
1454 };
1455
1456 /// Like ActTransform but the probability is determined by the suitability.
1457 struct ActGrow : public Action {
1458- ActGrow(char* parameters, ImmovableDescr&);
1459+ ActGrow(std::vector<std::string>& arguments, const ImmovableDescr&);
1460 void execute(Game&, Immovable&) const override;
1461
1462 private:
1463 std::string type_name;
1464- bool tribe;
1465 };
1466
1467 struct ActRemove : public Action {
1468- ActRemove(char* parameters, ImmovableDescr&);
1469+ ActRemove(std::vector<std::string>& arguments);
1470 void execute(Game&, Immovable&) const override;
1471
1472 private:
1473@@ -120,7 +115,7 @@
1474 };
1475
1476 struct ActSeed : public Action {
1477- ActSeed(char* parameters, ImmovableDescr&);
1478+ ActSeed(std::vector<std::string>& arguments, const ImmovableDescr&);
1479 void execute(Game&, Immovable&) const override;
1480
1481 private:
1482@@ -142,12 +137,11 @@
1483 /// Plays the specified sound effect with the specified priority. Whether the
1484 /// sound effect is actually played is determined by the sound handler.
1485 struct ActPlaySound : public Action {
1486- ActPlaySound(char* parameters, const ImmovableDescr&);
1487+ ActPlaySound(const std::vector<std::string>& arguments);
1488 void execute(Game&, Immovable&) const override;
1489
1490 private:
1491- FxId fx;
1492- uint8_t priority;
1493+ PlaySoundParameters parameters;
1494 };
1495
1496 /**
1497@@ -164,7 +158,7 @@
1498 * Time until construction decays one step if no progress has been made.
1499 */
1500 struct ActConstruct : public Action {
1501- ActConstruct(char* parameters, ImmovableDescr&);
1502+ ActConstruct(std::vector<std::string>& arguments, const ImmovableDescr&);
1503 void execute(Game&, Immovable&) const override;
1504
1505 Duration buildtime() const {
1506@@ -181,24 +175,16 @@
1507 };
1508
1509 /// Create a program with a single action.
1510- ImmovableProgram(char const* const init_name, Action* const action) : name_(init_name) {
1511- actions_.push_back(action);
1512- }
1513+ ImmovableProgram(const std::string& init_name, std::unique_ptr<Action> action);
1514
1515- // Create an immovable program from a number of lines.
1516+ /// Create an immovable program from a number of lines.
1517 ImmovableProgram(const std::string& init_name,
1518 const std::vector<std::string>& lines,
1519- ImmovableDescr* immovable);
1520+ const ImmovableDescr& immovable);
1521
1522 ~ImmovableProgram() {
1523- for (Action* action : actions_) {
1524- delete action;
1525- }
1526 }
1527
1528- const std::string& name() const {
1529- return name_;
1530- }
1531 size_t size() const {
1532 return actions_.size();
1533 }
1534@@ -207,14 +193,8 @@
1535 return *actions_[idx];
1536 }
1537
1538- using Actions = std::vector<Action*>;
1539- const Actions& actions() const {
1540- return actions_;
1541- }
1542-
1543 private:
1544- std::string name_;
1545- Actions actions_;
1546+ std::vector<std::unique_ptr<Action>> actions_;
1547 };
1548
1549 struct ImmovableActionData {
1550@@ -224,10 +204,18 @@
1551 }
1552
1553 virtual const char* name() const = 0;
1554- virtual void save(FileWrite& fw, Immovable& imm) = 0;
1555+ virtual void save(FileWrite& fw, Immovable& imm) const = 0;
1556
1557 static ImmovableActionData* load(FileRead& fr, Immovable& imm, const std::string& name);
1558 };
1559+
1560+struct ActConstructData : ImmovableActionData {
1561+ const char* name() const override;
1562+ void save(FileWrite& fw, Immovable& imm) const override;
1563+ static ActConstructData* load(FileRead& fr, Immovable& imm);
1564+
1565+ Buildcost delivered;
1566+};
1567 } // namespace Widelands
1568
1569 #endif // end of include guard: WL_LOGIC_MAP_OBJECTS_IMMOVABLE_PROGRAM_H
1570
1571=== modified file 'src/logic/map_objects/map_object.h'
1572--- src/logic/map_objects/map_object.h 2019-05-25 08:51:42 +0000
1573+++ src/logic/map_objects/map_object.h 2019-08-31 15:58:01 +0000
1574@@ -125,6 +125,7 @@
1575 }
1576
1577 virtual uint32_t get_animation(const std::string& animname, const MapObject* mo) const;
1578+
1579 uint32_t main_animation() const;
1580 std::string get_animation_name(uint32_t) const; ///< needed for save, debug
1581
1582
1583=== added file 'src/logic/map_objects/map_object_program.cc'
1584--- src/logic/map_objects/map_object_program.cc 1970-01-01 00:00:00 +0000
1585+++ src/logic/map_objects/map_object_program.cc 2019-08-31 15:58:01 +0000
1586@@ -0,0 +1,125 @@
1587+/*
1588+ * Copyright (C) 2002-2019 by the Widelands Development Team
1589+ *
1590+ * This program is free software; you can redistribute it and/or
1591+ * modify it under the terms of the GNU General Public License
1592+ * as published by the Free Software Foundation; either version 2
1593+ * of the License, or (at your option) any later version.
1594+ *
1595+ * This program is distributed in the hope that it will be useful,
1596+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1597+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1598+ * GNU General Public License for more details.
1599+ *
1600+ * You should have received a copy of the GNU General Public License
1601+ * along with this program; if not, write to the Free Software
1602+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
1603+ *
1604+ */
1605+
1606+#include "logic/map_objects/map_object_program.h"
1607+
1608+#include "io/filesystem/layered_filesystem.h"
1609+#include "logic/game_data_error.h"
1610+#include "logic/map_objects/map_object.h"
1611+#include "sound/sound_handler.h"
1612+
1613+namespace Widelands {
1614+
1615+MapObjectProgram::MapObjectProgram(const std::string& init_name) : name_ (init_name) {}
1616+
1617+const std::string& MapObjectProgram::name() const {
1618+ return name_;
1619+}
1620+
1621+std::vector<std::string> MapObjectProgram::split_string(const std::string& s, const char* const separators) {
1622+ std::vector<std::string> result;
1623+ for (std::string::size_type pos = 0, endpos;
1624+ (pos = s.find_first_not_of(separators, pos)) != std::string::npos; pos = endpos) {
1625+ endpos = s.find_first_of(separators, pos);
1626+ result.push_back(s.substr(pos, endpos - pos));
1627+ }
1628+ return result;
1629+}
1630+
1631+
1632+unsigned int MapObjectProgram::read_int(const std::string& input, int min_value, int max_value) {
1633+ unsigned int result = 0U;
1634+ char* endp;
1635+ long int const value = strtol(input.c_str(), &endp, 0);
1636+ result = value;
1637+ if (*endp || result != value) {
1638+ throw GameDataError("Expected a number but found \"%s\"", input.c_str());
1639+ }
1640+ if (value < min_value) {
1641+ throw GameDataError("Expected a number >= %d but found \"%s\"", min_value, input.c_str());
1642+ }
1643+ if (value > max_value) {
1644+ throw GameDataError("Expected a number <= %d but found \"%s\"", max_value, input.c_str());
1645+ }
1646+ return result;
1647+}
1648+
1649+unsigned int MapObjectProgram::read_positive(const std::string& input, int max_value) {
1650+ return read_int(input, 1, max_value);
1651+}
1652+
1653+MapObjectProgram::ProgramParseInput MapObjectProgram::parse_program_string(const std::string& line) {
1654+ const std::pair<std::string, std::string> key_values = MapObjectProgram::read_key_value_pair(line, '=');
1655+ return ProgramParseInput{key_values.first, split_string(key_values.second, " \t\n")};
1656+}
1657+
1658+const std::pair<std::string, std::string> MapObjectProgram::read_key_value_pair(const std::string& input, const char separator, const std::string& default_value, const std::string& expected_key) {
1659+ const size_t idx = input.find(separator);
1660+ const std::string key = input.substr(0, idx);
1661+
1662+ if (!expected_key.empty()) {
1663+ if (idx == input.npos) {
1664+ throw GameDataError("Empty value in '%s' for separator '%c'\n", input.c_str(), separator);
1665+ }
1666+ if (key != expected_key) {
1667+ throw GameDataError("Expected key '%s' but found '%s' in '%s'\n", expected_key.c_str(), key.c_str(), input.c_str());
1668+ }
1669+ }
1670+
1671+ return std::make_pair(key, idx == input.npos ? default_value : input.substr(idx + 1));
1672+}
1673+
1674+MapObjectProgram::AnimationParameters MapObjectProgram::parse_act_animate(const std::vector<std::string>& arguments, const MapObjectDescr& descr, bool is_idle_allowed) {
1675+ if (arguments.size() < 1 || arguments.size() > 2) {
1676+ throw GameDataError("Usage: animate=<name> [<duration>]");
1677+ }
1678+
1679+ AnimationParameters result;
1680+ const std::string& animation_name = arguments.at(0);
1681+
1682+ if (!is_idle_allowed && animation_name == "idle") {
1683+ throw GameDataError("'idle' animation is default; calling is not allowed");
1684+ }
1685+ if (!descr.is_animation_known(animation_name)) {
1686+ throw GameDataError("Unknown animation '%s'", animation_name.c_str());
1687+ }
1688+ result.animation = descr.get_animation(animation_name, nullptr);
1689+
1690+ if (arguments.size() == 2) {
1691+ result.duration = read_positive(arguments.at(1));
1692+ }
1693+ return result;
1694+}
1695+
1696+MapObjectProgram::PlaySoundParameters MapObjectProgram::parse_act_play_sound(const std::vector<std::string>& arguments, uint8_t default_priority) {
1697+ if (arguments.size() < 1 || arguments.size() > 2) {
1698+ throw GameDataError("Usage: playsound=<sound_dir/sound_name> [priority]");
1699+ }
1700+ PlaySoundParameters result;
1701+ result.fx = SoundHandler::register_fx(SoundType::kAmbient, arguments.at(0));
1702+
1703+ result.priority = arguments.size() == 2 ? read_positive(arguments.at(1)) : default_priority;
1704+ if (result.priority < kFxPriorityLowest) {
1705+ throw GameDataError("Minmum priority for sounds is %d, but only %d was specified for %s",
1706+ kFxPriorityLowest, result.priority, arguments.at(0).c_str());
1707+ }
1708+ return result;
1709+}
1710+
1711+} // namespace Widelands
1712
1713=== added file 'src/logic/map_objects/map_object_program.h'
1714--- src/logic/map_objects/map_object_program.h 1970-01-01 00:00:00 +0000
1715+++ src/logic/map_objects/map_object_program.h 2019-08-31 15:58:01 +0000
1716@@ -0,0 +1,98 @@
1717+/*
1718+ * Copyright (C) 2002-2019 by the Widelands Development Team
1719+ *
1720+ * This program is free software; you can redistribute it and/or
1721+ * modify it under the terms of the GNU General Public License
1722+ * as published by the Free Software Foundation; either version 2
1723+ * of the License, or (at your option) any later version.
1724+ *
1725+ * This program is distributed in the hope that it will be useful,
1726+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1727+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1728+ * GNU General Public License for more details.
1729+ *
1730+ * You should have received a copy of the GNU General Public License
1731+ * along with this program; if not, write to the Free Software
1732+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
1733+ *
1734+ */
1735+
1736+#ifndef WL_LOGIC_MAP_OBJECTS_MAP_OBJECT_PROGRAM_H
1737+#define WL_LOGIC_MAP_OBJECTS_MAP_OBJECT_PROGRAM_H
1738+
1739+#include <limits>
1740+#include <string>
1741+#include <vector>
1742+
1743+#include "logic/widelands.h"
1744+#include "sound/constants.h"
1745+
1746+namespace Widelands {
1747+
1748+struct MapObjectDescr;
1749+
1750+/// Superclass for Worker, Immovable and Productionsite programs. Includes a program name and diverse parsing convenience functions. The creation and execution of program actions is left to the sub-classes.
1751+struct MapObjectProgram {
1752+ const std::string& name() const;
1753+
1754+ explicit MapObjectProgram(const std::string& init_name);
1755+ virtual ~MapObjectProgram() = default;
1756+
1757+protected:
1758+ /// Splits a string by separators.
1759+ /// \note This ignores empty elements, so do not use this for example to split
1760+ /// a string with newline characters into lines, because it would ignore empty
1761+ /// lines.
1762+ static std::vector<std::string> split_string(const std::string&, char const* separators);
1763+
1764+ /// Reads an int value from a string. Throws a GameDataError if 'min_value' or 'max_value' are exceeded
1765+ static unsigned int read_int(const std::string& input, int min_value, int max_value = std::numeric_limits<int32_t>::max());
1766+ /// Same as 'read_int', with 'min_value' == 1
1767+ static unsigned int read_positive(const std::string& input, int max_value = std::numeric_limits<int32_t>::max());
1768+
1769+ /**
1770+ * @brief Reads a key-value pair from a string using the given separator, e.g. "attrib:tree", "meat:2", "return=skipped unless economy needs meal"
1771+ * @param input The string to parse
1772+ * @param separator The separator for splitting the string, e.g. ':' or '='
1773+ * @param default_value A default to assign to the right-hand value if the separator is not found
1774+ * @param expected_key If this is not empty, the left-hand key must match this string
1775+ * @return A key, value pair
1776+ */
1777+ static const std::pair<std::string, std::string> read_key_value_pair(const std::string& input, const char separator, const std::string& default_value = "", const std::string& expected_key = "");
1778+
1779+ /// Left-hand and right-hand elements of a line in a program, e.g. parsed from "return=skipped unless economy needs meal"
1780+ struct ProgramParseInput {
1781+ /// Program name, e.g. "return"
1782+ std::string name;
1783+ /// Program arguments, e.g. { "skipped", "unless", "economy", "needs", "meal" }
1784+ std::vector<std::string> arguments;
1785+ };
1786+ /// Reads the program name and arguments from a string
1787+ static ProgramParseInput parse_program_string(const std::string& line);
1788+
1789+ /// Animation information
1790+ struct AnimationParameters {
1791+ /// Animation ID
1792+ uint32_t animation = 0;
1793+ /// Animation duration. 0 will play the animation forever.
1794+ Duration duration = 0;
1795+ };
1796+ /// Parses the arguments for an animation action, e.g. { "working", "24000" }. If 'is_idle_allowed' == false, throws a GameDataError if the animation is called "idle".
1797+ static AnimationParameters parse_act_animate(const std::vector<std::string>& arguments, const MapObjectDescr& descr, bool is_idle_allowed);
1798+
1799+ /// Sound effect information
1800+ struct PlaySoundParameters {
1801+ /// Sound effect ID
1802+ FxId fx;
1803+ /// Sound effect priority
1804+ uint8_t priority = 0;
1805+ };
1806+ /// Parses the arguments for a play_sound action, e.g. { "sound/smiths/sharpening", "120" }
1807+ static PlaySoundParameters parse_act_play_sound(const std::vector<std::string>& arguments, uint8_t default_priority);
1808+
1809+private:
1810+ const std::string name_;
1811+};
1812+} // namespace Widelands
1813+
1814+#endif // end of include guard: WL_LOGIC_MAP_OBJECTS_MAP_OBJECT_PROGRAM_H
1815
1816=== modified file 'src/logic/map_objects/tribes/production_program.cc'
1817--- src/logic/map_objects/tribes/production_program.cc 2019-05-18 11:58:43 +0000
1818+++ src/logic/map_objects/tribes/production_program.cc 2019-08-31 15:58:01 +0000
1819@@ -20,10 +20,6 @@
1820 #include "logic/map_objects/tribes/production_program.h"
1821
1822 #include <memory>
1823-#include <sstream>
1824-
1825-#include <boost/algorithm/string.hpp>
1826-#include <boost/format.hpp>
1827
1828 #include "base/i18n.h"
1829 #include "base/macros.h"
1830@@ -32,7 +28,6 @@
1831 #include "economy/economy.h"
1832 #include "economy/flag.h"
1833 #include "economy/input_queue.h"
1834-#include "helper.h"
1835 #include "io/filesystem/layered_filesystem.h"
1836 #include "logic/game.h"
1837 #include "logic/game_data_error.h"
1838@@ -56,135 +51,42 @@
1839 namespace Widelands {
1840
1841 namespace {
1842-
1843-/// Matches the string that candidate points to against the string that
1844-/// template points to. Stops at when reaching a null character or the
1845-/// character terminator. If a match is found, candidate is moved beyond the
1846-/// matched part.
1847-///
1848-/// example:
1849-/// char const * candidate = "return 75";
1850-/// bool const result = match(candidate, "return");
1851-/// now candidate points to " 75" and result is true
1852-bool match(char*& candidate, const char* pattern) {
1853- for (char* p = candidate;; ++p, ++pattern)
1854- if (!*pattern) {
1855- candidate = p;
1856- return true;
1857- } else if (*p != *pattern)
1858- break;
1859- return false;
1860-}
1861-
1862-/// Skips a sequence of consecutive characters with the value c, starting at p.
1863-/// Throws WException if no characters were skipped.
1864-void force_skip(char*& p, char const c = ' ') {
1865- char* t = p;
1866- while (*t == c)
1867- ++t;
1868- if (p < t)
1869- p = t;
1870- else
1871- throw wexception("expected '%c' but found \"%s\"", c, p);
1872-}
1873-
1874-/// Skips a sequence of consecutive characters with the value c, starting at p.
1875-/// Returns whether any characters were skipped.
1876-bool skip(char*& p, char const c = ' ') {
1877- char* t = p;
1878- while (*t == c)
1879- ++t;
1880- if (p < t) {
1881- p = t;
1882- return true;
1883- } else
1884- return false;
1885-}
1886-
1887-/// Combines match and force_skip.
1888-///
1889-/// example:
1890-/// char const * candidate = "return 75";
1891-/// bool const result = match_force_skip(candidate, "return");
1892-/// now candidate points to "75" and result is true
1893-///
1894-/// example:
1895-/// char const * candidate = "return75";
1896-/// bool const result = match_force_skip(candidate, "return");
1897-/// throws WException
1898-bool match_force_skip(char*& candidate, const char* pattern) {
1899- for (char* p = candidate;; ++p, ++pattern)
1900- if (!*pattern) {
1901- force_skip(p);
1902- candidate = p;
1903- return true;
1904- } else if (*p != *pattern)
1905- return false;
1906-
1907- NEVER_HERE();
1908-}
1909-
1910-ProductionProgram::ActReturn::Condition* create_economy_condition(char*& parameters,
1911- const Tribes& tribes) {
1912- try {
1913- if (match_force_skip(parameters, "needs"))
1914- try {
1915- bool reached_end;
1916- char const* const type_name = next_word(parameters, reached_end);
1917- const DescriptionIndex& wareindex = tribes.ware_index(type_name);
1918- if (tribes.ware_exists(wareindex)) {
1919- for (size_t i = 0; i < tribes.nrtribes(); ++i) {
1920- const TribeDescr& tribe_descr = *tribes.get_tribe_descr(i);
1921- if (tribe_descr.has_ware(wareindex)) {
1922- tribes.set_ware_type_has_demand_check(wareindex, tribe_descr.name());
1923- }
1924- }
1925- return new ProductionProgram::ActReturn::EconomyNeedsWare(wareindex);
1926- } else if (tribes.worker_exists(tribes.worker_index(type_name))) {
1927- const DescriptionIndex& workerindex = tribes.worker_index(type_name);
1928- for (size_t i = 0; i < tribes.nrtribes(); ++i) {
1929- const TribeDescr* tribe_descr = tribes.get_tribe_descr(i);
1930- if (tribe_descr->has_worker(workerindex)) {
1931- tribes.set_worker_type_has_demand_check(workerindex);
1932- }
1933- }
1934- return new ProductionProgram::ActReturn::EconomyNeedsWorker(workerindex);
1935- } else
1936- throw GameDataError(
1937- "expected %s but found \"%s\"", "ware type or worker type", type_name);
1938- } catch (const WException& e) {
1939- throw GameDataError("needs: %s", e.what());
1940- }
1941- else
1942- throw GameDataError("expected %s but found \"%s\"", "\"needs\"", parameters);
1943- } catch (const WException& e) {
1944- throw GameDataError("economy: %s", e.what());
1945- }
1946-}
1947-
1948-ProductionProgram::ActReturn::Condition*
1949-create_site_condition(char*& parameters, const ProductionSiteDescr& descr, const Tribes& tribes) {
1950- try {
1951- if (match_force_skip(parameters, "has"))
1952- return new ProductionProgram::ActReturn::SiteHas(parameters, descr, tribes);
1953- else
1954- throw GameDataError("expected %s but found \"%s\"", "\"has\"", parameters);
1955- } catch (const WException& e) {
1956- throw GameDataError("site: %s", e.what());
1957- }
1958-}
1959-
1960-ProductionProgram::ActReturn::Condition* create_workers_condition(char*& parameters) {
1961- try {
1962- if (match(parameters, "need experience"))
1963- return new ProductionProgram::ActReturn::WorkersNeedExperience;
1964- else
1965- throw GameDataError("expected %s but found \"%s\"", "\"need experience\"", parameters);
1966- } catch (const WException& e) {
1967- throw GameDataError("workers: %s", e.what());
1968- }
1969-}
1970-
1971+/// If the iterator contents match the string, increment the iterator. Returns whether it matched.
1972+bool match_and_skip(const std::vector<std::string>& args, std::vector<std::string>::const_iterator& it, const std::string& matchme) {
1973+ const bool result = (it != args.end()) && (*it == matchme);
1974+ if (result) {
1975+ ++it;
1976+ }
1977+ return result;
1978+}
1979+
1980+ProductionProgram::ActReturn::Condition* create_economy_condition(const std::string& item, const ProductionSiteDescr& descr, const Tribes& tribes) {
1981+ DescriptionIndex index = tribes.ware_index(item);
1982+ if (tribes.ware_exists(index)) {
1983+ descr.ware_demand_checks()->insert(index);
1984+ return new ProductionProgram::ActReturn::EconomyNeedsWare(index);
1985+ } else if (tribes.worker_exists(tribes.worker_index(item))) {
1986+ index = tribes.worker_index(item);
1987+ descr.worker_demand_checks()->insert(index);
1988+ return new ProductionProgram::ActReturn::EconomyNeedsWorker(index);
1989+ } else {
1990+ throw GameDataError("Expected ware or worker type but found '%s'", item.c_str());
1991+ }
1992+}
1993+
1994+TrainingAttribute parse_training_attribute(const std::string& argument) {
1995+ if (argument == "health") {
1996+ return TrainingAttribute::kHealth;
1997+ } else if (argument == "attack") {
1998+ return TrainingAttribute::kAttack;
1999+ } else if (argument == "defense") {
2000+ return TrainingAttribute::kDefense;
2001+ } else if (argument == "evade") {
2002+ return TrainingAttribute::kEvade;
2003+ } else {
2004+ throw GameDataError("Expected health|attack|defense|evade after 'soldier' but found '%s'", argument.c_str());
2005+ }
2006+}
2007 } // namespace
2008
2009 ProductionProgram::Action::~Action() {
2010@@ -197,90 +99,99 @@
2011 void ProductionProgram::Action::building_work_failed(Game&, ProductionSite&, Worker&) const {
2012 }
2013
2014-void ProductionProgram::parse_ware_type_group(char*& parameters,
2015- WareTypeGroup& group,
2016- const Tribes& tribes,
2017- const BillOfMaterials& input_wares,
2018- const BillOfMaterials& input_workers) {
2019- std::set<std::pair<DescriptionIndex, WareWorker>>::iterator last_insert_pos = group.first.end();
2020- uint8_t count = 1;
2021- uint8_t count_max = 0;
2022- for (;;) {
2023- char const* ware = parameters;
2024- while (*parameters && *parameters != ',' && *parameters != ':' && *parameters != ' ')
2025- ++parameters;
2026- char const terminator = *parameters;
2027- *parameters = '\0';
2028-
2029- // Try as ware
2030- WareWorker type = wwWARE;
2031- const BillOfMaterials* input_list = &input_wares;
2032- DescriptionIndex ware_index = tribes.ware_index(ware);
2033- if (!tribes.ware_exists(ware_index)) {
2034- ware_index = tribes.worker_index(ware);
2035- if (tribes.worker_exists(ware_index)) {
2036- // It is a worker
2037- type = wwWORKER;
2038- input_list = &input_workers;
2039- } else {
2040- throw GameDataError("Unknown ware or worker type \"%s\"", ware);
2041- }
2042- }
2043-
2044- bool found = false;
2045- for (const WareAmount& input : *input_list) {
2046- if (input.first == ware_index) {
2047- count_max += input.second;
2048- found = true;
2049- break;
2050- }
2051- }
2052- if (!found) {
2053- throw GameDataError("%s is not declared as an input (\"%s=<count>\" was not "
2054- "found in the [inputs] section)",
2055- ware, ware);
2056- }
2057-
2058- if (group.first.size() && ware_index <= group.first.begin()->first)
2059- throw GameDataError("wrong order of ware types within group: ware type %s appears "
2060- "after ware type %s (fix order!)",
2061- ware,
2062- tribes.get_ware_descr(group.first.begin()->first)->name().c_str());
2063- last_insert_pos = group.first.insert(last_insert_pos, std::make_pair(ware_index, type));
2064- *parameters = terminator;
2065- switch (terminator) {
2066- case ':': {
2067- ++parameters;
2068- char* endp;
2069- unsigned long long int const value = strtoull(parameters, &endp, 0);
2070- count = value;
2071- if ((*endp && *endp != ' ') || value < 1 || count != value)
2072- throw GameDataError("expected %s but found \"%s\"", "count", parameters);
2073- parameters = endp;
2074- if (count_max < count)
2075- throw GameDataError("group count is %u but (total) input storage capacity of "
2076- "the specified ware type(s) is only %u, so the group can "
2077+ProductionProgram::Groups ProductionProgram::parse_ware_type_groups(std::vector<std::string>::const_iterator begin, std::vector<std::string>::const_iterator end, const ProductionSiteDescr& descr, const Tribes& tribes) {
2078+ ProductionProgram::Groups result;
2079+
2080+ for (auto& it = begin; it != end; ++it) {
2081+ const std::pair<std::string, std::string> names_to_amount = read_key_value_pair(*it, ':', "1");
2082+ const uint8_t amount = read_positive(names_to_amount.second);
2083+ uint8_t max_amount = 0;
2084+ std::set<std::pair<DescriptionIndex, WareWorker>> ware_worker_names;
2085+ for (const std::string& item_name : split_string(names_to_amount.first, ",")) {
2086+ // Try as ware
2087+ WareWorker type = wwWARE;
2088+ DescriptionIndex item_index = tribes.ware_index(item_name);
2089+ if (!tribes.ware_exists(item_index)) {
2090+ item_index = tribes.worker_index(item_name);
2091+ if (tribes.worker_exists(item_index)) {
2092+ // It is a worker
2093+ type = wwWORKER;
2094+ } else {
2095+ throw GameDataError("Expected ware or worker type but found '%s'", item_name.c_str());
2096+ }
2097+ }
2098+
2099+ // Sanity checks
2100+ bool found = false;
2101+ const BillOfMaterials& inputs = (type == wwWARE) ? descr.input_wares() : descr.input_workers();
2102+ for (const WareAmount& input : inputs) {
2103+ if (input.first == item_index) {
2104+ max_amount += input.second;
2105+ found = true;
2106+ break;
2107+ }
2108+ }
2109+ if (!found) {
2110+ throw GameDataError("%s was not declared in the building's 'inputs' table", item_name.c_str());
2111+ }
2112+
2113+ if (max_amount < amount) {
2114+ throw GameDataError("Ware/worker count is %u but (total) input storage capacity of "
2115+ "the specified ware type(s) is only %u, so the ware/worker requirement can "
2116 "never be fulfilled by the site",
2117- count, count_max);
2118- }
2119- FALLS_THROUGH;
2120- case '\0':
2121- case ' ':
2122- group.second = count;
2123- return;
2124- case ',':
2125- ++parameters;
2126- break;
2127- default:
2128- // scan for terminator should ensure that this cannot happen
2129- NEVER_HERE();
2130- }
2131- }
2132+ static_cast<unsigned int>(amount), static_cast<unsigned int>(max_amount));
2133+ }
2134+ // Add item
2135+ ware_worker_names.insert(std::make_pair(item_index, type));
2136+ }
2137+ // Add set
2138+ result.push_back(std::make_pair(ware_worker_names, amount));
2139+ }
2140+ if (result.empty()) {
2141+ throw GameDataError("No wares or workers found");
2142+ }
2143+ return result;
2144+}
2145+
2146+
2147+BillOfMaterials ProductionProgram::parse_bill_of_materials(const std::vector<std::string>& arguments,
2148+ WareWorker ww,
2149+ const ProductionSiteDescr& descr,
2150+ const Tribes& tribes) {
2151+ BillOfMaterials result;
2152+ for (const std::string& argument : arguments) {
2153+ const std::pair<std::string, std::string> produceme = read_key_value_pair(argument, ':', "1");
2154+
2155+ const DescriptionIndex index = ww == WareWorker::wwWARE ?
2156+ tribes.safe_ware_index(produceme.first) :
2157+ tribes.safe_worker_index(produceme.first);
2158+
2159+ // Verify the building outputs
2160+ switch (ww) {
2161+ case WareWorker::wwWARE:
2162+ if (!descr.is_output_ware_type(index)) {
2163+ throw GameDataError("Ware '%s' is not listed in the building's 'outputs' table",
2164+ produceme.first.c_str());
2165+ } break;
2166+ case WareWorker::wwWORKER:
2167+ if (!descr.is_output_worker_type(index)) {
2168+ throw GameDataError("Worker '%s' is not listed in the building's 'outputs' table",
2169+ produceme.first.c_str());
2170+ } break;
2171+ }
2172+
2173+ result.push_back(std::make_pair(index, read_positive(produceme.second)));
2174+ }
2175+ return result;
2176 }
2177
2178 ProductionProgram::ActReturn::Condition::~Condition() {
2179 }
2180
2181+ProductionProgram::ActReturn::Negation::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)
2182+ : operand(create_condition(arguments, begin, end, descr, tribes)) {
2183+}
2184+
2185 ProductionProgram::ActReturn::Negation::~Negation() {
2186 delete operand;
2187 }
2188@@ -343,13 +254,14 @@
2189 return result;
2190 }
2191
2192-ProductionProgram::ActReturn::SiteHas::SiteHas(char*& parameters,
2193+ProductionProgram::ActReturn::SiteHas::SiteHas(std::vector<std::string>::const_iterator begin,
2194+ std::vector<std::string>::const_iterator end,
2195 const ProductionSiteDescr& descr,
2196 const Tribes& tribes) {
2197 try {
2198- parse_ware_type_group(parameters, group, tribes, descr.input_wares(), descr.input_workers());
2199- } catch (const WException& e) {
2200- throw GameDataError("has ware_type1[,ware_type2[,...]][:N]: %s", e.what());
2201+ group = parse_ware_type_groups(begin, end, descr, tribes).front();
2202+ } catch (const GameDataError& e) {
2203+ throw GameDataError("Expected <ware or worker>[,<ware or worker>[,...]][:<amount>] after 'site has' but got %s", e.what());
2204 }
2205 }
2206 bool ProductionProgram::ActReturn::SiteHas::evaluate(const ProductionSite& ps) const {
2207@@ -438,76 +350,89 @@
2208 return _("the workers need no experience");
2209 }
2210
2211-ProductionProgram::ActReturn::Condition* ProductionProgram::ActReturn::create_condition(
2212- char*& parameters, const ProductionSiteDescr& descr, const Tribes& tribes) {
2213+ProductionProgram::ActReturn::Condition* ProductionProgram::ActReturn::create_condition(const std::vector<std::string>& arguments,
2214+ std::vector<std::string>::const_iterator& begin, std::vector<std::string>::const_iterator& end, const ProductionSiteDescr& descr, const Tribes& tribes) {
2215+ if (begin == end) {
2216+ throw GameDataError("Expected a condition after '%s'", (begin - 1)->c_str());
2217+ }
2218 try {
2219- if (match_force_skip(parameters, "not"))
2220- return new ActReturn::Negation(parameters, descr, tribes);
2221- else if (match_force_skip(parameters, "economy"))
2222- return create_economy_condition(parameters, tribes);
2223- else if (match_force_skip(parameters, "site"))
2224- return create_site_condition(parameters, descr, tribes);
2225- else if (match_force_skip(parameters, "workers"))
2226- return create_workers_condition(parameters);
2227- else
2228+ if (match_and_skip(arguments, begin, "not")) {
2229+ return new ActReturn::Negation(arguments, begin, end, descr, tribes);
2230+ } else if (match_and_skip(arguments, begin, "economy")) {
2231+ if (!match_and_skip(arguments, begin, "needs")) {
2232+ throw GameDataError("Expected 'needs' after 'economy' but found '%s'", begin->c_str());
2233+ }
2234+ return create_economy_condition(*begin, descr, tribes);
2235+ } else if (match_and_skip(arguments, begin, "site")) {
2236+ if (!match_and_skip(arguments, begin, "has")) {
2237+ throw GameDataError("Expected 'has' after 'site' but found '%s'", begin->c_str());
2238+ }
2239+ return new ProductionProgram::ActReturn::SiteHas(begin, end, descr, tribes);
2240+ } else if (match_and_skip(arguments, begin, "workers")) {
2241+ if (!match_and_skip(arguments, begin, "need")) {
2242+ throw GameDataError("Expected 'need experience' after 'workers' but found '%s'", begin->c_str());
2243+ }
2244+ if (!match_and_skip(arguments, begin, "experience")) {
2245+ throw GameDataError("Expected 'experience' after 'workers need' but found '%s'", begin->c_str());
2246+ }
2247+ return new ProductionProgram::ActReturn::WorkersNeedExperience();
2248+ } else {
2249 throw GameDataError(
2250- "expected %s but found \"%s\"", "{\"not\"|\"economy\"|\"workers\"}", parameters);
2251+ "Expected not|economy|site|workers after '%s' but found '%s'", (begin - 1)->c_str(), begin->c_str());
2252+ }
2253 } catch (const WException& e) {
2254- throw GameDataError("invalid condition: %s", e.what());
2255+ throw GameDataError("Invalid condition. %s", e.what());
2256 }
2257 }
2258
2259-ProductionProgram::ActReturn::ActReturn(char* parameters,
2260+ProductionProgram::ActReturn::ActReturn(const std::vector<std::string>& arguments,
2261 const ProductionSiteDescr& descr,
2262 const Tribes& tribes) {
2263- try {
2264- if (match(parameters, "failed"))
2265- result_ = ProgramResult::kFailed;
2266- else if (match(parameters, "completed"))
2267- result_ = ProgramResult::kCompleted;
2268- else if (match(parameters, "skipped"))
2269- result_ = ProgramResult::kSkipped;
2270- else if (match(parameters, "no_stats"))
2271- result_ = ProgramResult::kNone;
2272- else
2273- throw GameDataError("expected %s but found \"%s\"",
2274- "{\"failed\"|\"completed\"|\"skipped\"|\"no_stats\"}", parameters);
2275-
2276- if (skip(parameters)) {
2277- if (match_force_skip(parameters, "when")) {
2278- is_when_ = true;
2279- for (;;) {
2280- conditions_.push_back(create_condition(parameters, descr, tribes));
2281- if (*parameters) {
2282- skip(parameters);
2283- if (!match_force_skip(parameters, "and"))
2284- throw GameDataError("expected \"%s\" or end of input", "and");
2285- } else
2286- break;
2287- }
2288- } else if (match_force_skip(parameters, "unless")) {
2289- is_when_ = false;
2290- for (;;) {
2291- if (!*parameters)
2292- throw GameDataError("expected condition at end of input");
2293- conditions_.push_back(create_condition(parameters, descr, tribes));
2294- if (*parameters) {
2295- skip(parameters);
2296- if (!match_force_skip(parameters, "or"))
2297- throw GameDataError("expected \"%s\" or end of input", "or");
2298- } else
2299- break;
2300- }
2301- } else
2302- throw GameDataError(
2303- "expected %s but found \"%s\"", "{\"when\"|\"unless\"}", parameters);
2304- } else if (*parameters)
2305- throw GameDataError("expected %s but found \"%s\"", ("space or end of input"), parameters);
2306- else
2307- is_when_ = true;
2308-
2309- } catch (const WException& e) {
2310- throw GameDataError("return: %s", e.what());
2311+ if (arguments.empty()) {
2312+ throw GameDataError("Usage: return=failed|completed|skipped|no_stats [when|unless <conditions>]");
2313+ }
2314+ auto begin = arguments.begin();
2315+
2316+ if (match_and_skip(arguments, begin, "failed")) {
2317+ result_ = ProgramResult::kFailed;
2318+ } else if (match_and_skip(arguments, begin, "completed")) {
2319+ result_ = ProgramResult::kCompleted;
2320+ } else if (match_and_skip(arguments, begin, "skipped")) {
2321+ result_ = ProgramResult::kSkipped;
2322+ } else if (match_and_skip(arguments, begin, "no_stats")) {
2323+ result_ = ProgramResult::kNone;
2324+ } else {
2325+ throw GameDataError("Usage: return=failed|completed|skipped|no_stats [when|unless <conditions>]");
2326+ }
2327+
2328+
2329+ // Parse all arguments starting from the given iterator into our 'conditions_', splitting individual conditions by the given 'separator'
2330+ auto parse_conditions = [this, &descr, &tribes] (const std::vector<std::string>& args, std::vector<std::string>::const_iterator it, const std::string& separator) {
2331+ while (it != args.end()) {
2332+ auto end = it + 1;
2333+ while (end != args.end() && *end != separator) {
2334+ ++end;
2335+ }
2336+ if (it == end) {
2337+ throw GameDataError("Expected: [%s] <condition> after '%s'", separator.c_str(), (it - 1)->c_str());
2338+ }
2339+
2340+ conditions_.push_back(create_condition(args, it, end, descr, tribes));
2341+ match_and_skip(args, end, separator);
2342+ it = end;
2343+ }
2344+ };
2345+
2346+ is_when_ = true;
2347+ if (begin != arguments.end()) {
2348+ if (match_and_skip(arguments, begin, "when")) {
2349+ parse_conditions(arguments, begin, "and");
2350+ } else if (match_and_skip(arguments, begin, "unless")) {
2351+ is_when_ = false;
2352+ parse_conditions(arguments, begin, "or");
2353+ } else {
2354+ throw GameDataError("Expected when|unless but found '%s'", begin->c_str());
2355+ }
2356 }
2357 }
2358
2359@@ -573,7 +498,11 @@
2360 return ps.program_end(game, result_);
2361 }
2362
2363-ProductionProgram::ActCall::ActCall(char* parameters, const ProductionSiteDescr& descr) {
2364+ProductionProgram::ActCall::ActCall(const std::vector<std::string>& arguments, const ProductionSiteDescr& descr) {
2365+ if (arguments.size() < 1 || arguments.size() > 4) {
2366+ throw GameDataError("Usage: call=<program name> [on failure|completion|skip fail|complete|skip|repeat]");
2367+ }
2368+
2369 // Initialize with default handling methods.
2370 handling_methods_[program_result_index(ProgramResult::kFailed)] =
2371 ProgramResultHandlingMethod::kContinue;
2372@@ -582,61 +511,60 @@
2373 handling_methods_[program_result_index(ProgramResult::kSkipped)] =
2374 ProgramResultHandlingMethod::kContinue;
2375
2376- try {
2377- bool reached_end;
2378- {
2379- char const* const program_name = next_word(parameters, reached_end);
2380- const ProductionSiteDescr::Programs& programs = descr.programs();
2381- ProductionSiteDescr::Programs::const_iterator const it = programs.find(program_name);
2382- if (it == programs.end())
2383- throw GameDataError("the program \"%s\" has not (yet) been declared in %s "
2384- "(wrong declaration order?)",
2385- program_name, descr.name().c_str());
2386- program_ = it->second.get();
2387- }
2388-
2389- // Override with specified handling methods.
2390- while (!reached_end) {
2391- skip(parameters);
2392- match_force_skip(parameters, "on");
2393-
2394- ProgramResult result_to_set_method_for;
2395- if (match_force_skip(parameters, "failure")) {
2396- if (handling_methods_[program_result_index(ProgramResult::kFailed)] !=
2397- ProgramResultHandlingMethod::kContinue)
2398- throw GameDataError("%s handling method already defined", "failure");
2399- result_to_set_method_for = ProgramResult::kFailed;
2400- } else if (match_force_skip(parameters, "completion")) {
2401- if (handling_methods_[program_result_index(ProgramResult::kCompleted)] !=
2402- ProgramResultHandlingMethod::kContinue)
2403- throw GameDataError("%s handling method already defined", "completion");
2404- result_to_set_method_for = ProgramResult::kCompleted;
2405- } else if (match_force_skip(parameters, "skip")) {
2406- if (handling_methods_[program_result_index(ProgramResult::kSkipped)] !=
2407- ProgramResultHandlingMethod::kContinue)
2408- throw GameDataError("%s handling method already defined", "skip");
2409- result_to_set_method_for = ProgramResult::kSkipped;
2410- } else
2411- throw GameDataError(
2412- "expected %s but found \"%s\"", "{\"failure\"|\"completion\"|\"skip\"}", parameters);
2413-
2414- ProgramResultHandlingMethod handling_method;
2415- if (match(parameters, "fail"))
2416- handling_method = ProgramResultHandlingMethod::kFail;
2417- else if (match(parameters, "complete"))
2418- handling_method = ProgramResultHandlingMethod::kComplete;
2419- else if (match(parameters, "skip"))
2420- handling_method = ProgramResultHandlingMethod::kSkip;
2421- else if (match(parameters, "repeat"))
2422- handling_method = ProgramResultHandlingMethod::kRepeat;
2423- else
2424- throw GameDataError("expected %s but found \"%s\"",
2425- "{\"fail\"|\"complete\"|\"skip\"|\"repeat\"}", parameters);
2426- handling_methods_[program_result_index(result_to_set_method_for)] = handling_method;
2427- reached_end = !*parameters;
2428- }
2429- } catch (const WException& e) {
2430- throw GameDataError("call: %s", e.what());
2431+ // Fetch program to call
2432+ const std::string& program_name = arguments.front();
2433+ const ProductionSiteDescr::Programs& programs = descr.programs();
2434+ ProductionSiteDescr::Programs::const_iterator const it = programs.find(program_name);
2435+ if (it == programs.end()) {
2436+ throw GameDataError("The program '%s' has not (yet) been declared in %s "
2437+ "(wrong declaration order?)",
2438+ program_name.c_str(), descr.name().c_str());
2439+ }
2440+ program_ = it->second.get();
2441+
2442+ // Override with specified handling methods.
2443+ if (arguments.size() > 1) {
2444+ if (arguments.at(1) != "on") {
2445+ throw GameDataError("Expected 'on' keyword in second position");
2446+ }
2447+
2448+ ProgramResult result_to_set_method_for;
2449+ if (arguments.at(2) == "failure") {
2450+ if (handling_methods_[program_result_index(ProgramResult::kFailed)] !=
2451+ ProgramResultHandlingMethod::kContinue) {
2452+ throw GameDataError("%s handling method already defined", "failure");
2453+ }
2454+ result_to_set_method_for = ProgramResult::kFailed;
2455+ } else if (arguments.at(2) == "completion") {
2456+ if (handling_methods_[program_result_index(ProgramResult::kCompleted)] !=
2457+ ProgramResultHandlingMethod::kContinue) {
2458+ throw GameDataError("%s handling method already defined", "completion");
2459+ }
2460+ result_to_set_method_for = ProgramResult::kCompleted;
2461+ } else if (arguments.at(2) == "skip") {
2462+ if (handling_methods_[program_result_index(ProgramResult::kSkipped)] !=
2463+ ProgramResultHandlingMethod::kContinue) {
2464+ throw GameDataError("%s handling method already defined", "skip");
2465+ }
2466+ result_to_set_method_for = ProgramResult::kSkipped;
2467+ } else {
2468+ throw GameDataError(
2469+ "Expected failure|completion|skip after 'on' but found '%s'", arguments.at(2).c_str());
2470+ }
2471+
2472+ ProgramResultHandlingMethod handling_method;
2473+ if (arguments.at(3) == "fail") {
2474+ handling_method = ProgramResultHandlingMethod::kFail;
2475+ } else if (arguments.at(3) == "complete") {
2476+ handling_method = ProgramResultHandlingMethod::kComplete;
2477+ } else if (arguments.at(3) == "skip") {
2478+ handling_method = ProgramResultHandlingMethod::kSkip;
2479+ } else if (arguments.at(3) == "repeat") {
2480+ handling_method = ProgramResultHandlingMethod::kRepeat;
2481+ } else {
2482+ throw GameDataError("Expected fail|complete|skip|repeat in final position but found '%s'", arguments.at(3).c_str());
2483+ }
2484+ handling_methods_[program_result_index(result_to_set_method_for)] = handling_method;
2485 }
2486 }
2487
2488@@ -662,38 +590,38 @@
2489 }
2490 }
2491
2492-ProductionProgram::ActCallWorker::ActCallWorker(char* parameters,
2493+ProductionProgram::ActCallWorker::ActCallWorker(const std::vector<std::string>& arguments,
2494 const std::string& production_program_name,
2495 ProductionSiteDescr* descr,
2496 const Tribes& tribes) {
2497- try {
2498- program_ = parameters;
2499-
2500- // Quote form "void ProductionSite::program_act(Game &)":
2501- // "Always main worker is doing stuff"
2502- const WorkerDescr& main_worker_descr =
2503- *tribes.get_worker_descr(descr->working_positions()[0].first);
2504-
2505- // This will fail unless the main worker has a program with the given
2506- // name, so it also validates the parameter.
2507- const WorkareaInfo& worker_workarea_info =
2508- main_worker_descr.get_program(program_)->get_workarea_info();
2509-
2510- for (const auto& area_info : worker_workarea_info) {
2511- std::set<std::string>& building_radius_infos = descr->workarea_info_[area_info.first];
2512-
2513- for (const std::string& worker_name : area_info.second) {
2514- std::string description = descr->name();
2515- description += ' ';
2516- description += production_program_name;
2517- description += " worker ";
2518- description += main_worker_descr.name();
2519- description += worker_name;
2520- building_radius_infos.insert(description);
2521- }
2522+ if (arguments.size() != 1) {
2523+ throw GameDataError("Usage: callworker=<worker program name>");
2524+ }
2525+
2526+ program_ = arguments.front();
2527+
2528+ // Quote from "void ProductionSite::program_act(Game &)":
2529+ // "Always main worker is doing stuff"
2530+ const WorkerDescr& main_worker_descr =
2531+ *tribes.get_worker_descr(descr->working_positions().front().first);
2532+
2533+ // This will fail unless the main worker has a program with the given
2534+ // name, so it also validates the parameter.
2535+ const WorkareaInfo& worker_workarea_info =
2536+ main_worker_descr.get_program(program_)->get_workarea_info();
2537+
2538+ for (const auto& area_info : worker_workarea_info) {
2539+ std::set<std::string>& building_radius_infos = descr->workarea_info_[area_info.first];
2540+
2541+ for (const std::string& worker_name : area_info.second) {
2542+ std::string description = descr->name();
2543+ description += ' ';
2544+ description += production_program_name;
2545+ description += " worker ";
2546+ description += main_worker_descr.name();
2547+ description += worker_name;
2548+ building_radius_infos.insert(description);
2549 }
2550- } catch (const WException& e) {
2551- throw GameDataError("worker: %s", e.what());
2552 }
2553 }
2554
2555@@ -722,42 +650,27 @@
2556 psite.program_end(game, ProgramResult::kFailed);
2557 }
2558
2559-ProductionProgram::ActSleep::ActSleep(char* parameters) {
2560- try {
2561- if (*parameters) {
2562- char* endp;
2563- long long int const value = strtoll(parameters, &endp, 0);
2564- duration_ = value;
2565- if (*endp || value <= 0 || duration_ != value)
2566- throw GameDataError("expected %s but found \"%s\"", "duration in ms", parameters);
2567- } else
2568- duration_ = 0; // Get duration from the result of a previous action.
2569- } catch (const WException& e) {
2570- throw GameDataError("sleep: %s", e.what());
2571+ProductionProgram::ActSleep::ActSleep(const std::vector<std::string>& arguments) {
2572+ if (arguments.size() != 1) {
2573+ throw GameDataError("Usage: sleep=<duration>");
2574 }
2575+ duration_ = read_positive(arguments.front());
2576 }
2577
2578 void ProductionProgram::ActSleep::execute(Game& game, ProductionSite& ps) const {
2579 return ps.program_step(game, duration_ ? duration_ : 0, ps.top_state().phase);
2580 }
2581
2582-ProductionProgram::ActCheckMap::ActCheckMap(char* parameters) {
2583- try {
2584- if (*parameters) {
2585- if (!strcmp(parameters, "seafaring"))
2586- feature_ = SEAFARING;
2587- else
2588- throw GameDataError("Unknown parameter \"%s\"", parameters);
2589- } else
2590- throw GameDataError("No parameter given!");
2591- } catch (const WException& e) {
2592- throw GameDataError("sleep: %s", e.what());
2593+ProductionProgram::ActCheckMap::ActCheckMap(const std::vector<std::string>& arguments) {
2594+ if (arguments.size() != 1 || arguments.front() != "seafaring") {
2595+ throw GameDataError("Usage: checkmap=seafaring");
2596 }
2597+ feature_ = Feature::kSeafaring;
2598 }
2599
2600 void ProductionProgram::ActCheckMap::execute(Game& game, ProductionSite& ps) const {
2601 switch (feature_) {
2602- case SEAFARING: {
2603+ case Feature::kSeafaring: {
2604 if (game.map().allows_seafaring()) {
2605 return ps.program_step(game, 0);
2606 } else {
2607@@ -765,57 +678,26 @@
2608 return ps.program_end(game, ProgramResult::kFailed);
2609 }
2610 }
2611- default:
2612- NEVER_HERE();
2613 }
2614+ NEVER_HERE();
2615 }
2616
2617-ProductionProgram::ActAnimate::ActAnimate(char* parameters, ProductionSiteDescr* descr) {
2618- try {
2619- bool reached_end;
2620- animation_name_ = std::string(next_word(parameters, reached_end));
2621- if (animation_name_ == "idle") {
2622- throw GameDataError("idle animation is default; calling is not allowed");
2623- }
2624- if (!descr->is_animation_known(animation_name_)) {
2625- throw GameDataError("Unknown animation '%s'", animation_name_.c_str());
2626- }
2627- if (!reached_end) { // The next parameter is the duration.
2628- char* endp;
2629- long long int const value = strtoll(parameters, &endp, 0);
2630- duration_ = value;
2631- if (*endp || value <= 0 || duration_ != value)
2632- throw GameDataError("expected %s but found \"%s\"", "duration in ms", parameters);
2633- } else
2634- duration_ = 0; // Get duration from the result of a previous action.
2635- } catch (const WException& e) {
2636- throw GameDataError("animate: %s", e.what());
2637- }
2638+ProductionProgram::ActAnimate::ActAnimate(const std::vector<std::string>& arguments, ProductionSiteDescr* descr) {
2639+ parameters = MapObjectProgram::parse_act_animate(arguments, *descr, false);
2640 }
2641
2642 void ProductionProgram::ActAnimate::execute(Game& game, ProductionSite& ps) const {
2643- ps.start_animation(game, ps.descr().get_animation(animation_name_, &ps));
2644- return ps.program_step(game, duration_ ? duration_ : 0, ps.top_state().phase);
2645+ ps.start_animation(game, parameters.animation);
2646+ return ps.program_step(game, parameters.duration ? parameters.duration : 0, ps.top_state().phase);
2647 }
2648
2649-ProductionProgram::ActConsume::ActConsume(char* parameters,
2650+ProductionProgram::ActConsume::ActConsume(const std::vector<std::string>& arguments,
2651 const ProductionSiteDescr& descr,
2652 const Tribes& tribes) {
2653- try {
2654- for (;;) {
2655- consumed_wares_workers_.resize(consumed_wares_workers_.size() + 1);
2656- parse_ware_type_group(parameters, *consumed_wares_workers_.rbegin(), tribes,
2657- descr.input_wares(), descr.input_workers());
2658- if (!*parameters)
2659- break;
2660- force_skip(parameters);
2661- }
2662- if (consumed_wares_workers_.empty()) {
2663- throw GameDataError("expected ware_type1[,ware_type2[,...]][:N] ...");
2664- }
2665- } catch (const WException& e) {
2666- throw GameDataError("consume: %s", e.what());
2667+ if (arguments.empty()) {
2668+ throw GameDataError("Usage: consume=<ware or worker>[,<ware or worker>[,...]][:<amount>] ...");
2669 }
2670+ consumed_wares_workers_ = parse_ware_type_groups(arguments.begin(), arguments.end(), descr, tribes);
2671 }
2672
2673 void ProductionProgram::ActConsume::execute(Game& game, ProductionSite& ps) const {
2674@@ -938,47 +820,13 @@
2675 }
2676 }
2677
2678-ProductionProgram::ActProduce::ActProduce(char* parameters,
2679+ProductionProgram::ActProduce::ActProduce(const std::vector<std::string>& arguments,
2680 const ProductionSiteDescr& descr,
2681 const Tribes& tribes) {
2682- try {
2683- for (bool more = true; more; ++parameters) {
2684- produced_wares_.resize(produced_wares_.size() + 1);
2685- WareAmount& item = *produced_wares_.rbegin();
2686- skip(parameters);
2687- char const* ware = parameters;
2688- for (;; ++parameters) {
2689- switch (*parameters) {
2690- default:
2691- break;
2692- case '\0':
2693- case ' ':
2694- item.second = 1;
2695- goto item_end;
2696- case ':': {
2697- *parameters = '\0';
2698- ++parameters;
2699- char* endp;
2700- unsigned long long int const value = strtoull(parameters, &endp, 0);
2701- item.second = value;
2702- if ((*endp && *endp != ' ') || value < 1 || item.second != value)
2703- throw GameDataError("expected %s but found \"%s\"", "count", parameters);
2704- parameters = endp;
2705- goto item_end;
2706- }
2707- }
2708- }
2709- item_end:
2710- more = *parameters != '\0';
2711- *parameters = '\0';
2712- if (!descr.is_output_ware_type(item.first = tribes.safe_ware_index(ware)))
2713- throw GameDataError("%s is not declared as an output (\"%s\" was not "
2714- "found in the \"outputs\" table)",
2715- ware, ware);
2716- }
2717- } catch (const WException& e) {
2718- throw GameDataError("produce: %s", e.what());
2719+ if (arguments.empty()) {
2720+ throw GameDataError("Usage: produce=<ware name>[:<amount>] [<ware name>[:<amount>]...]");
2721 }
2722+ produced_wares_ = parse_bill_of_materials(arguments, WareWorker::wwWARE, descr, tribes);
2723 }
2724
2725 void ProductionProgram::ActProduce::execute(Game& game, ProductionSite& ps) const {
2726@@ -1024,47 +872,13 @@
2727 return false;
2728 }
2729
2730-ProductionProgram::ActRecruit::ActRecruit(char* parameters,
2731+ProductionProgram::ActRecruit::ActRecruit(const std::vector<std::string>& arguments,
2732 const ProductionSiteDescr& descr,
2733 const Tribes& tribes) {
2734- try {
2735- for (bool more = true; more; ++parameters) {
2736- recruited_workers_.resize(recruited_workers_.size() + 1);
2737- WareAmount& item = *recruited_workers_.rbegin();
2738- skip(parameters);
2739- char const* worker = parameters;
2740- for (;; ++parameters) {
2741- switch (*parameters) {
2742- default:
2743- break;
2744- case '\0':
2745- case ' ':
2746- item.second = 1;
2747- goto item_end;
2748- case ':': {
2749- *parameters = '\0';
2750- ++parameters;
2751- char* endp;
2752- unsigned long long int const value = strtoull(parameters, &endp, 0);
2753- item.second = value;
2754- if ((*endp && *endp != ' ') || value < 1 || item.second != value)
2755- throw GameDataError("expected %s but found \"%s\"", "count", parameters);
2756- parameters = endp;
2757- goto item_end;
2758- }
2759- }
2760- }
2761- item_end:
2762- more = *parameters != '\0';
2763- *parameters = '\0';
2764- if (!descr.is_output_worker_type(item.first = tribes.safe_worker_index(worker)))
2765- throw GameDataError("%s is not declared as an output (\"%s\" was not "
2766- "found in the \"outputs\" table)",
2767- worker, worker);
2768- }
2769- } catch (const WException& e) {
2770- throw GameDataError("recruit: %s", e.what());
2771+ if (arguments.empty()) {
2772+ throw GameDataError("Usage: recruit=<worker name>[:<amount>] [<worker name>[:<amount>]...]");
2773 }
2774+ recruited_workers_ = parse_bill_of_materials(arguments, WareWorker::wwWORKER, descr, tribes);
2775 }
2776
2777 void ProductionProgram::ActRecruit::execute(Game& game, ProductionSite& ps) const {
2778@@ -1106,55 +920,23 @@
2779 return false;
2780 }
2781
2782-ProductionProgram::ActMine::ActMine(char* parameters,
2783+ProductionProgram::ActMine::ActMine(const std::vector<std::string>& arguments,
2784 const World& world,
2785 const std::string& production_program_name,
2786 ProductionSiteDescr* descr) {
2787- try {
2788- bool reached_end;
2789- resource_ = world.safe_resource_index(next_word(parameters, reached_end));
2790-
2791- {
2792- char* endp;
2793- unsigned long long int const value = strtoull(parameters, &endp, 0);
2794- distance_ = value;
2795- if (*endp != ' ' || distance_ != value)
2796- throw GameDataError("expected %s but found \"%s\"", "distance", parameters);
2797- parameters = endp;
2798- }
2799-
2800- {
2801- char* endp;
2802- unsigned long long int const value = strtoull(parameters, &endp, 0);
2803- max_ = value;
2804- if (*endp != ' ' || value < 1 || 100 < value)
2805- throw GameDataError("expected %s but found \"%s\"", "percentage", parameters);
2806- parameters = endp;
2807- }
2808-
2809- {
2810- char* endp;
2811- unsigned long long int const value = strtoull(parameters, &endp, 0);
2812- chance_ = value;
2813- if (*endp != ' ' || value < 1 || 100 < value)
2814- throw GameDataError("expected %s but found \"%s\"", "percentage", parameters);
2815- parameters = endp;
2816- }
2817- {
2818- char* endp;
2819- unsigned long long int const value = strtoull(parameters, &endp, 0);
2820- training_ = value;
2821- if (*endp || value < 1 || 100 < value)
2822- throw GameDataError("expected %s but found \"%s\"", "percentage", parameters);
2823- }
2824- std::string description = (boost::format("%1$s %2$s mine %3$s") % descr->name() %
2825- production_program_name % world.get_resource(resource_)->name())
2826- .str();
2827-
2828- descr->workarea_info_[distance_].insert(description);
2829- } catch (const WException& e) {
2830- throw GameDataError("mine: %s", e.what());
2831+ if (arguments.size() != 5) {
2832+ throw GameDataError("Usage: mine=resource <workarea radius> <max> <chance> <worker experience gained>");
2833 }
2834+
2835+ resource_ = world.safe_resource_index(arguments.front().c_str());
2836+ distance_ = read_positive(arguments.at(1));
2837+ max_ = read_positive(arguments.at(2));
2838+ chance_ = read_positive(arguments.at(3));
2839+ training_ = read_positive(arguments.at(4));
2840+
2841+ const std::string description = descr->name() + " " +
2842+ production_program_name + " mine " + world.get_resource(resource_)->name();
2843+ descr->workarea_info_[distance_].insert(description);
2844 }
2845
2846 void ProductionProgram::ActMine::execute(Game& game, ProductionSite& ps) const {
2847@@ -1268,32 +1050,16 @@
2848 return ps.program_step(game);
2849 }
2850
2851-ProductionProgram::ActCheckSoldier::ActCheckSoldier(char* parameters) {
2852- // TODO(unknown): This is currently hardcoded for "soldier", but should allow any
2853- // soldier type name.
2854- if (!match_force_skip(parameters, "soldier"))
2855- throw GameDataError("expected %s but found \"%s\"", "soldier type", parameters);
2856- try {
2857- if (match_force_skip(parameters, "health"))
2858- attribute = TrainingAttribute::kHealth;
2859- else if (match_force_skip(parameters, "attack"))
2860- attribute = TrainingAttribute::kAttack;
2861- else if (match_force_skip(parameters, "defense"))
2862- attribute = TrainingAttribute::kDefense;
2863- else if (match_force_skip(parameters, "evade"))
2864- attribute = TrainingAttribute::kEvade;
2865- else
2866- throw GameDataError("expected %s but found \"%s\"",
2867- "{\"health\"|\"attack\"|\"defense\"|\"evade\"}", parameters);
2868+ProductionProgram::ActCheckSoldier::ActCheckSoldier(const std::vector<std::string>& arguments) {
2869+ if (arguments.size() != 3) {
2870+ throw GameDataError("Usage: checksoldier=soldier <training attribute> <level>");
2871+ }
2872
2873- char* endp;
2874- unsigned long long int const value = strtoull(parameters, &endp, 0);
2875- level = value;
2876- if (*endp || level != value)
2877- throw GameDataError("expected %s but found \"%s\"", "level", parameters);
2878- } catch (const WException& e) {
2879- throw GameDataError("checksoldier: %s", e.what());
2880+ if (arguments.front() != "soldier") {
2881+ throw GameDataError("Expected 'soldier' but found '%s'", arguments.front().c_str());
2882 }
2883+ attribute_ = parse_training_attribute(arguments.at(1));
2884+ level_ = read_int(arguments.at(2), 0);
2885 }
2886
2887 void ProductionProgram::ActCheckSoldier::execute(Game& game, ProductionSite& ps) const {
2888@@ -1304,8 +1070,8 @@
2889 ps.set_production_result(_("No soldier to train!"));
2890 return ps.program_end(game, ProgramResult::kSkipped);
2891 }
2892- ps.molog(" Checking soldier (%u) level %d)\n", static_cast<unsigned int>(attribute),
2893- static_cast<unsigned int>(level));
2894+ ps.molog(" Checking soldier (%u) level %d)\n", static_cast<unsigned int>(attribute_),
2895+ static_cast<unsigned int>(level_));
2896
2897 const std::vector<Soldier*>::const_iterator soldiers_end = soldiers.end();
2898 for (std::vector<Soldier*>::const_iterator it = soldiers.begin();; ++it) {
2899@@ -1313,114 +1079,93 @@
2900 ps.set_production_result(_("No soldier found for this training level!"));
2901 return ps.program_end(game, ProgramResult::kSkipped);
2902 }
2903- if (attribute == TrainingAttribute::kHealth) {
2904- if ((*it)->get_health_level() == level)
2905- break;
2906- } else if (attribute == TrainingAttribute::kAttack) {
2907- if ((*it)->get_attack_level() == level)
2908- break;
2909- } else if (attribute == TrainingAttribute::kDefense) {
2910- if ((*it)->get_defense_level() == level)
2911- break;
2912- } else if (attribute == TrainingAttribute::kEvade) {
2913- if ((*it)->get_evade_level() == level)
2914- break;
2915+
2916+ if (attribute_ == TrainingAttribute::kHealth) {
2917+ if ((*it)->get_health_level() == level_) {
2918+ break;
2919+ }
2920+ } else if (attribute_ == TrainingAttribute::kAttack) {
2921+ if ((*it)->get_attack_level() == level_) {
2922+ break;
2923+ }
2924+ } else if (attribute_ == TrainingAttribute::kDefense) {
2925+ if ((*it)->get_defense_level() == level_) {
2926+ break;
2927+ }
2928+ } else if (attribute_ == TrainingAttribute::kEvade) {
2929+ if ((*it)->get_evade_level() == level_) {
2930+ break;
2931+ }
2932 }
2933 }
2934 ps.molog(" okay\n"); // okay, do nothing
2935
2936 upcast(TrainingSite, ts, &ps);
2937- ts->training_attempted(attribute, level);
2938+ ts->training_attempted(attribute_, level_);
2939
2940 ps.molog(" Check done!\n");
2941
2942 return ps.program_step(game);
2943 }
2944
2945-ProductionProgram::ActTrain::ActTrain(char* parameters) {
2946- // TODO(unknown): This is currently hardcoded for "soldier", but should allow any
2947- // soldier type name.
2948- if (!match_force_skip(parameters, "soldier"))
2949- throw GameDataError("expected %s but found \"%s\"", "soldier type", parameters);
2950- try {
2951- if (match_force_skip(parameters, "health"))
2952- attribute = TrainingAttribute::kHealth;
2953- else if (match_force_skip(parameters, "attack"))
2954- attribute = TrainingAttribute::kAttack;
2955- else if (match_force_skip(parameters, "defense"))
2956- attribute = TrainingAttribute::kDefense;
2957- else if (match_force_skip(parameters, "evade"))
2958- attribute = TrainingAttribute::kEvade;
2959- else
2960- throw GameDataError("expected %s but found \"%s\"",
2961- "{\"health\"|\"attack\"|\"defense\"|\"evade\"}", parameters);
2962-
2963- {
2964- char* endp;
2965- unsigned long long int const value = strtoull(parameters, &endp, 0);
2966- level = value;
2967- if (*endp != ' ' || level != value)
2968- throw GameDataError("expected %s but found \"%s\"", "level", parameters);
2969- parameters = endp;
2970- }
2971-
2972- {
2973- char* endp;
2974- unsigned long long int const value = strtoull(parameters, &endp, 0);
2975- target_level = value;
2976- if (*endp || target_level != value || target_level <= level)
2977- throw GameDataError("expected level > %u but found \"%s\"", level, parameters);
2978- }
2979- } catch (const WException& e) {
2980- throw GameDataError("train: %s", e.what());
2981- }
2982+ProductionProgram::ActTrain::ActTrain(const std::vector<std::string>& arguments) {
2983+ if (arguments.size() != 4) {
2984+ throw GameDataError("Usage: checksoldier=soldier <training attribute> <level before> <level after>");
2985+ }
2986+
2987+ if (arguments.front() != "soldier") {
2988+ throw GameDataError("Expected 'soldier' but found '%s'", arguments.front().c_str());
2989+ }
2990+
2991+ attribute_ = parse_training_attribute(arguments.at(1));
2992+ level_ = read_int(arguments.at(2), 0);
2993+ target_level_ = read_positive(arguments.at(3));
2994 }
2995
2996 void ProductionProgram::ActTrain::execute(Game& game, ProductionSite& ps) const {
2997 const SoldierControl* ctrl = ps.soldier_control();
2998 const std::vector<Soldier*> soldiers = ctrl->present_soldiers();
2999 const std::vector<Soldier*>::const_iterator soldiers_end = soldiers.end();
3000- std::vector<Soldier*>::const_iterator it = soldiers.begin();
3001-
3002- ps.molog(" Training soldier's %u (%d to %d)", static_cast<unsigned int>(attribute),
3003- static_cast<unsigned int>(level), static_cast<unsigned int>(target_level));
3004-
3005- for (;; ++it) {
3006+
3007+ ps.molog(" Training soldier's %u (%d to %d)", static_cast<unsigned int>(attribute_),
3008+ static_cast<unsigned int>(level_), static_cast<unsigned int>(target_level_));
3009+
3010+ bool training_done = false;
3011+ for (auto it = soldiers.begin(); !training_done; ++it) {
3012 if (it == soldiers_end) {
3013 ps.set_production_result(_("No soldier found for this training level!"));
3014 return ps.program_end(game, ProgramResult::kSkipped);
3015 }
3016- if (attribute == TrainingAttribute::kHealth) {
3017- if ((*it)->get_health_level() == level)
3018- break;
3019- } else if (attribute == TrainingAttribute::kAttack) {
3020- if ((*it)->get_attack_level() == level)
3021- break;
3022- } else if (attribute == TrainingAttribute::kDefense) {
3023- if ((*it)->get_defense_level() == level)
3024- break;
3025- } else if (attribute == TrainingAttribute::kEvade) {
3026- if ((*it)->get_evade_level() == level)
3027- break;
3028+ try {
3029+ switch (attribute_) {
3030+ case TrainingAttribute::kHealth:
3031+ if ((*it)->get_health_level() == level_) {
3032+ (*it)->set_health_level(target_level_);
3033+ training_done = true;
3034+ } break;
3035+ case TrainingAttribute::kAttack:
3036+ if ((*it)->get_attack_level() == level_) {
3037+ (*it)->set_attack_level(target_level_);
3038+ training_done = true;
3039+ } break;
3040+ case TrainingAttribute::kDefense:
3041+ if ((*it)->get_defense_level() == level_) {
3042+ (*it)->set_defense_level(target_level_);
3043+ training_done = true;
3044+ } break;
3045+ case TrainingAttribute::kEvade:
3046+ if ((*it)->get_evade_level() == level_) {
3047+ (*it)->set_evade_level(target_level_);
3048+ training_done = true;
3049+ } break;
3050+ default:
3051+ throw wexception("Unknown training attribute index %d", static_cast<unsigned int>(attribute_));
3052+ }
3053+ } catch (...) {
3054+ throw wexception("Fail training soldier!!");
3055 }
3056 }
3057- ps.molog(" okay\n"); // okay, do nothing
3058-
3059- try {
3060- if (attribute == TrainingAttribute::kHealth)
3061- (*it)->set_health_level(target_level);
3062- else if (attribute == TrainingAttribute::kAttack)
3063- (*it)->set_attack_level(target_level);
3064-
3065- else if (attribute == TrainingAttribute::kDefense)
3066- (*it)->set_defense_level(target_level);
3067-
3068- else if (attribute == TrainingAttribute::kEvade)
3069- (*it)->set_evade_level(target_level);
3070-
3071- } catch (...) {
3072- throw wexception("Fail training soldier!!");
3073- }
3074+
3075 ps.molog(" Training done!\n");
3076 ps.set_production_result(
3077 /** TRANSLATORS: Success message of a trainingsite '%s' stands for the description of the
3078@@ -1428,61 +1173,32 @@
3079 (boost::format(_("Completed %s")) % ps.top_state().program->descname()).str());
3080
3081 upcast(TrainingSite, ts, &ps);
3082- ts->training_successful(attribute, level);
3083+ ts->training_successful(attribute_, level_);
3084
3085 return ps.program_step(game);
3086 }
3087
3088-ProductionProgram::ActPlaySound::ActPlaySound(char* parameters) {
3089- try {
3090- bool reached_end;
3091- const char* const name = next_word(parameters, reached_end);
3092- fx = SoundHandler::register_fx(SoundType::kAmbient, name);
3093-
3094- if (!reached_end) {
3095- char* endp;
3096- unsigned long long int const value = strtoull(parameters, &endp, 0);
3097- priority = value;
3098- if (*endp || priority != value)
3099- throw GameDataError("expected %s but found \"%s\"", "priority", parameters);
3100- } else {
3101- priority = kFxPriorityAllowMultiple - 1;
3102- }
3103- if (priority < kFxPriorityLowest) {
3104- throw GameDataError("Minmum priority for sounds is %d, but only %d was specified for %s",
3105- kFxPriorityLowest, priority, name);
3106- }
3107- } catch (const WException& e) {
3108- throw GameDataError("playsound: %s", e.what());
3109- }
3110+ProductionProgram::ActPlaySound::ActPlaySound(const std::vector<std::string>& arguments) {
3111+ parameters = MapObjectProgram::parse_act_play_sound(arguments, kFxPriorityAllowMultiple - 1);
3112 }
3113
3114 void ProductionProgram::ActPlaySound::execute(Game& game, ProductionSite& ps) const {
3115- Notifications::publish(NoteSound(SoundType::kAmbient, fx, ps.position_, priority));
3116+ Notifications::publish(NoteSound(SoundType::kAmbient, parameters.fx, ps.position_, parameters.priority));
3117 return ps.program_step(game);
3118 }
3119
3120-ProductionProgram::ActConstruct::ActConstruct(char* parameters,
3121+ProductionProgram::ActConstruct::ActConstruct(const std::vector<std::string>& arguments,
3122 const std::string& production_program_name,
3123 ProductionSiteDescr* descr) {
3124- try {
3125- std::vector<std::string> params = split_string(parameters, " ");
3126-
3127- if (params.size() != 3)
3128- throw GameDataError("usage: construct object-name worker-program radius:NN");
3129-
3130- objectname = params[0];
3131- workerprogram = params[1];
3132- radius = boost::lexical_cast<uint32_t>(params[2]);
3133-
3134- std::set<std::string>& building_radius_infos = descr->workarea_info_[radius];
3135- std::string description = descr->name() + ' ' + production_program_name;
3136- description += " construct ";
3137- description += objectname;
3138- building_radius_infos.insert(description);
3139- } catch (const WException& e) {
3140- throw GameDataError("construct: %s", e.what());
3141+ if (arguments.size() != 3) {
3142+ throw GameDataError("Usage: construct=<object name> <worker program> <workarea radius>");
3143 }
3144+ objectname = arguments.at(0);
3145+ workerprogram = arguments.at(1);
3146+ radius = read_positive(arguments.at(2));
3147+
3148+ const std::string description = descr->name() + ' ' + production_program_name + " construct " + objectname;
3149+ descr->workarea_info_[radius].insert(description);
3150 }
3151
3152 const ImmovableDescr&
3153@@ -1632,96 +1348,93 @@
3154 const Tribes& tribes,
3155 const World& world,
3156 ProductionSiteDescr* building)
3157- : name_(init_name), descname_(init_descname) {
3158-
3159- for (const std::string& action_string : actions_table->array_entries<std::string>()) {
3160- std::vector<std::string> parts;
3161- boost::split(parts, action_string, boost::is_any_of("="));
3162- if (parts.size() != 2) {
3163- throw GameDataError(
3164- "invalid line: \"%s\" in production program \"%s\" for building \"%s\"",
3165- action_string.c_str(), name().c_str(), building->name().c_str());
3166- }
3167- std::unique_ptr<char[]> arguments(new char[parts[1].size() + 1]);
3168- strncpy(arguments.get(), parts[1].c_str(), parts[1].size() + 1);
3169-
3170- if (boost::iequals(parts[0], "return")) {
3171- actions_.push_back(std::unique_ptr<ProductionProgram::Action>(
3172- new ActReturn(arguments.get(), *building, tribes)));
3173- } else if (boost::iequals(parts[0], "call")) {
3174- actions_.push_back(
3175- std::unique_ptr<ProductionProgram::Action>(new ActCall(arguments.get(), *building)));
3176- } else if (boost::iequals(parts[0], "sleep")) {
3177- actions_.push_back(
3178- std::unique_ptr<ProductionProgram::Action>(new ActSleep(arguments.get())));
3179- } else if (boost::iequals(parts[0], "animate")) {
3180- actions_.push_back(
3181- std::unique_ptr<ProductionProgram::Action>(new ActAnimate(arguments.get(), building)));
3182- } else if (boost::iequals(parts[0], "consume")) {
3183- actions_.push_back(std::unique_ptr<ProductionProgram::Action>(
3184- new ActConsume(arguments.get(), *building, tribes)));
3185- } else if (boost::iequals(parts[0], "produce")) {
3186- actions_.push_back(std::unique_ptr<ProductionProgram::Action>(
3187- new ActProduce(arguments.get(), *building, tribes)));
3188- } else if (boost::iequals(parts[0], "recruit")) {
3189- actions_.push_back(std::unique_ptr<ProductionProgram::Action>(
3190- new ActRecruit(arguments.get(), *building, tribes)));
3191- } else if (boost::iequals(parts[0], "callworker")) {
3192- actions_.push_back(std::unique_ptr<ProductionProgram::Action>(
3193- new ActCallWorker(arguments.get(), name(), building, tribes)));
3194- } else if (boost::iequals(parts[0], "mine")) {
3195- actions_.push_back(std::unique_ptr<ProductionProgram::Action>(
3196- new ActMine(arguments.get(), world, name(), building)));
3197- } else if (boost::iequals(parts[0], "checksoldier")) {
3198- actions_.push_back(
3199- std::unique_ptr<ProductionProgram::Action>(new ActCheckSoldier(arguments.get())));
3200- } else if (boost::iequals(parts[0], "train")) {
3201- actions_.push_back(
3202- std::unique_ptr<ProductionProgram::Action>(new ActTrain(arguments.get())));
3203- } else if (boost::iequals(parts[0], "playsound")) {
3204- actions_.push_back(
3205- std::unique_ptr<ProductionProgram::Action>(new ActPlaySound(arguments.get())));
3206- } else if (boost::iequals(parts[0], "construct")) {
3207- actions_.push_back(std::unique_ptr<ProductionProgram::Action>(
3208- new ActConstruct(arguments.get(), name(), building)));
3209- } else if (boost::iequals(parts[0], "checkmap")) {
3210- actions_.push_back(
3211- std::unique_ptr<ProductionProgram::Action>(new ActCheckMap(arguments.get())));
3212- } else {
3213- throw GameDataError(
3214- "unknown command type \"%s\" in production program \"%s\" for building \"%s\"",
3215- arguments.get(), name().c_str(), building->name().c_str());
3216- }
3217-
3218- const ProductionProgram::Action& action = *actions_.back();
3219- for (const auto& group : action.consumed_wares_workers()) {
3220- consumed_wares_workers_.push_back(group);
3221- }
3222- // Add produced wares. If the ware already exists, increase the amount
3223- for (const auto& ware : action.produced_wares()) {
3224- if (produced_wares_.count(ware.first) == 1) {
3225- produced_wares_.at(ware.first) += ware.second;
3226- } else {
3227- produced_wares_.insert(ware);
3228- }
3229- }
3230- // Add recruited workers. If the worker already exists, increase the amount
3231- for (const auto& worker : action.recruited_workers()) {
3232- if (recruited_workers_.count(worker.first) == 1) {
3233- recruited_workers_.at(worker.first) += worker.second;
3234- } else {
3235- recruited_workers_.insert(worker);
3236- }
3237- }
3238- }
3239- if (actions_.empty())
3240- throw GameDataError("no actions in production program \"%s\" for building \"%s\"",
3241- name().c_str(), building->name().c_str());
3242-}
3243-
3244-const std::string& ProductionProgram::name() const {
3245- return name_;
3246-}
3247+ : MapObjectProgram(init_name), descname_(init_descname) {
3248+
3249+ for (const std::string& line : actions_table->array_entries<std::string>()) {
3250+ if (line.empty()) {
3251+ throw GameDataError("Empty line");
3252+ }
3253+ try {
3254+ ProgramParseInput parseinput = parse_program_string(line);
3255+
3256+ if (parseinput.name == "return") {
3257+ actions_.push_back(std::unique_ptr<ProductionProgram::Action>(
3258+ new ActReturn(parseinput.arguments, *building, tribes)));
3259+ } else if (parseinput.name == "call") {
3260+ actions_.push_back(
3261+ std::unique_ptr<ProductionProgram::Action>(new ActCall(parseinput.arguments, *building)));
3262+ } else if (parseinput.name == "sleep") {
3263+ actions_.push_back(
3264+ std::unique_ptr<ProductionProgram::Action>(new ActSleep(parseinput.arguments)));
3265+ } else if (parseinput.name == "animate") {
3266+ actions_.push_back(
3267+ std::unique_ptr<ProductionProgram::Action>(new ActAnimate(parseinput.arguments, building)));
3268+ } else if (parseinput.name =="consume") {
3269+ actions_.push_back(std::unique_ptr<ProductionProgram::Action>(
3270+ new ActConsume(parseinput.arguments, *building, tribes)));
3271+ } else if (parseinput.name == "produce") {
3272+ actions_.push_back(std::unique_ptr<ProductionProgram::Action>(
3273+ new ActProduce(parseinput.arguments, *building, tribes)));
3274+ } else if (parseinput.name == "recruit") {
3275+ actions_.push_back(std::unique_ptr<ProductionProgram::Action>(
3276+ new ActRecruit(parseinput.arguments, *building, tribes)));
3277+ } else if (parseinput.name =="callworker") {
3278+ actions_.push_back(std::unique_ptr<ProductionProgram::Action>(
3279+ new ActCallWorker(parseinput.arguments, name(), building, tribes)));
3280+ } else if (parseinput.name == "mine") {
3281+ actions_.push_back(std::unique_ptr<ProductionProgram::Action>(
3282+ new ActMine(parseinput.arguments, world, name(), building)));
3283+ } else if (parseinput.name == "checksoldier") {
3284+ actions_.push_back(
3285+ std::unique_ptr<ProductionProgram::Action>(new ActCheckSoldier(parseinput.arguments)));
3286+ } else if (parseinput.name == "train") {
3287+ actions_.push_back(
3288+ std::unique_ptr<ProductionProgram::Action>(new ActTrain(parseinput.arguments)));
3289+ } else if (parseinput.name == "playsound") {
3290+ actions_.push_back(
3291+ std::unique_ptr<ProductionProgram::Action>(new ActPlaySound(parseinput.arguments)));
3292+ } else if (parseinput.name == "construct") {
3293+ actions_.push_back(std::unique_ptr<ProductionProgram::Action>(
3294+ new ActConstruct(parseinput.arguments, name(), building)));
3295+ } else if (parseinput.name == "checkmap") {
3296+ actions_.push_back(
3297+ std::unique_ptr<ProductionProgram::Action>(new ActCheckMap(parseinput.arguments)));
3298+ } else {
3299+ throw GameDataError("Unknown command '%s' in line '%s'", parseinput.name.c_str(), line.c_str());
3300+ }
3301+
3302+ const ProductionProgram::Action& action = *actions_.back();
3303+ for (const auto& group : action.consumed_wares_workers()) {
3304+ consumed_wares_workers_.push_back(group);
3305+ }
3306+
3307+ // Add produced wares. If the ware already exists, increase the amount
3308+ for (const auto& ware : action.produced_wares()) {
3309+ if (produced_wares_.count(ware.first) == 1) {
3310+ produced_wares_.at(ware.first) += ware.second;
3311+ } else {
3312+ produced_wares_.insert(ware);
3313+ }
3314+ }
3315+
3316+ // Add recruited workers. If the worker already exists, increase the amount
3317+ for (const auto& worker : action.recruited_workers()) {
3318+ if (recruited_workers_.count(worker.first) == 1) {
3319+ recruited_workers_.at(worker.first) += worker.second;
3320+ } else {
3321+ recruited_workers_.insert(worker);
3322+ }
3323+ }
3324+ } catch (const std::exception& e) {
3325+ throw GameDataError("Error reading line '%s': %s", line.c_str(), e.what());
3326+ }
3327+ }
3328+
3329+ if (actions_.empty()) {
3330+ throw GameDataError("No actions found");
3331+ }
3332+}
3333+
3334 const std::string& ProductionProgram::descname() const {
3335 return descname_;
3336 }
3337
3338=== modified file 'src/logic/map_objects/tribes/production_program.h'
3339--- src/logic/map_objects/tribes/production_program.h 2019-05-11 13:48:12 +0000
3340+++ src/logic/map_objects/tribes/production_program.h 2019-08-31 15:58:01 +0000
3341@@ -32,6 +32,7 @@
3342 #include "base/log.h"
3343 #include "base/macros.h"
3344 #include "logic/map_objects/buildcost.h"
3345+#include "logic/map_objects/map_object_program.h"
3346 #include "logic/map_objects/tribes/bill_of_materials.h"
3347 #include "logic/map_objects/tribes/program_result.h"
3348 #include "logic/map_objects/tribes/training_attribute.h"
3349@@ -51,7 +52,7 @@
3350 class World;
3351
3352 /// Ordered sequence of actions (at least 1). Has a name.
3353-struct ProductionProgram {
3354+struct ProductionProgram : public MapObjectProgram {
3355
3356 /// A group of ware types with a count.
3357 using WareTypeGroup = std::pair<std::set<std::pair<DescriptionIndex, WareWorker>>, uint8_t>;
3358@@ -97,13 +98,11 @@
3359 DISALLOW_COPY_AND_ASSIGN(Action);
3360 };
3361
3362- /// Parse a group of ware types followed by an optional count and terminated
3363- /// by a space or null. Example: "fish,meat:2".
3364- static void parse_ware_type_group(char*& parameters,
3365- WareTypeGroup& group,
3366- const Tribes& tribes,
3367- const BillOfMaterials& input_wares,
3368- const BillOfMaterials& input_workers);
3369+ /// Parse a group of ware types followed by an optional count within a vector range. Example: "fish,meat:2".
3370+ static 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);
3371+
3372+ /// Parse a ware or worker list with optional amounts and ensure that the building's outputs match. Example: "fish:2".
3373+ static BillOfMaterials parse_bill_of_materials(const std::vector<std::string>& arguments, WareWorker ww, const ProductionSiteDescr& descr, const Tribes& tribes);
3374
3375 /// Returns from the program.
3376 ///
3377@@ -156,7 +155,7 @@
3378 /// Note: If the execution reaches the end of the program. the return value
3379 /// is implicitly set to Completed.
3380 struct ActReturn : public Action {
3381- ActReturn(char* parameters, const ProductionSiteDescr&, const Tribes& tribes);
3382+ ActReturn(const std::vector<std::string>& arguments, const ProductionSiteDescr&, const Tribes& tribes);
3383 ~ActReturn() override;
3384 void execute(Game&, ProductionSite&) const override;
3385
3386@@ -167,11 +166,10 @@
3387 virtual std::string description_negation(const Tribes&) const = 0;
3388 };
3389 static Condition*
3390- create_condition(char*& parameters, const ProductionSiteDescr&, const Tribes& tribes);
3391+ create_condition(const std::vector<std::string>& arguments, std::vector<std::string>::const_iterator& begin, std::vector<std::string>::const_iterator& end, const ProductionSiteDescr&, const Tribes& tribes);
3392+
3393 struct Negation : public Condition {
3394- Negation(char*& parameters, const ProductionSiteDescr& descr, const Tribes& tribes)
3395- : operand(create_condition(parameters, descr, tribes)) {
3396- }
3397+ 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);
3398 ~Negation() override;
3399 bool evaluate(const ProductionSite&) const override;
3400 // Just a dummy to satisfy the superclass interface. Do not use.
3401@@ -211,7 +209,7 @@
3402 /// wares, combining from any of the types specified, in its input
3403 /// queues.
3404 struct SiteHas : public Condition {
3405- SiteHas(char*& parameters, const ProductionSiteDescr&, const Tribes& tribes);
3406+ SiteHas(std::vector<std::string>::const_iterator begin, std::vector<std::string>::const_iterator end, const ProductionSiteDescr& descr, const Tribes& tribes);
3407 bool evaluate(const ProductionSite&) const override;
3408 std::string description(const Tribes& tribes) const override;
3409 std::string description_negation(const Tribes& tribes) const override;
3410@@ -269,7 +267,7 @@
3411 /// but no statistics are calculated (with the same effect as
3412 /// executing "return=no_stats")
3413 struct ActCall : public Action {
3414- ActCall(char* parameters, const ProductionSiteDescr&);
3415+ ActCall(const std::vector<std::string>& arguments, const ProductionSiteDescr&);
3416 void execute(Game&, ProductionSite&) const override;
3417
3418 private:
3419@@ -285,7 +283,7 @@
3420 /// program:
3421 /// The name of a program defined in the productionsite's main worker.
3422 struct ActCallWorker : public Action {
3423- ActCallWorker(char* parameters,
3424+ ActCallWorker(const std::vector<std::string>& arguments,
3425 const std::string& production_program_name,
3426 ProductionSiteDescr*,
3427 const Tribes& tribes);
3428@@ -311,7 +309,7 @@
3429 ///
3430 /// Blocks the execution of the program for the specified duration.
3431 struct ActSleep : public Action {
3432- explicit ActSleep(char* parameters);
3433+ explicit ActSleep(const std::vector<std::string>& arguments);
3434 void execute(Game&, ProductionSite&) const override;
3435
3436 private:
3437@@ -329,12 +327,12 @@
3438 ///
3439 /// Ends the program if the feature is not enabled.
3440 struct ActCheckMap : public Action {
3441- explicit ActCheckMap(char* parameters);
3442+ explicit ActCheckMap(const std::vector<std::string>& arguments);
3443 void execute(Game&, ProductionSite&) const override;
3444
3445 private:
3446- enum { SEAFARING = 1 };
3447- uint8_t feature_;
3448+ enum class Feature { kSeafaring = 1 };
3449+ Feature feature_;
3450 };
3451
3452 /// Runs an animation.
3453@@ -354,12 +352,11 @@
3454 /// animation will not be stopped by this command. It will run until another
3455 /// animation is started.)
3456 struct ActAnimate : public Action {
3457- ActAnimate(char* parameters, ProductionSiteDescr*);
3458+ ActAnimate(const std::vector<std::string>& arguments, ProductionSiteDescr*);
3459 void execute(Game&, ProductionSite&) const override;
3460
3461 private:
3462- std::string animation_name_;
3463- Duration duration_;
3464+ AnimationParameters parameters;
3465 };
3466
3467 /// Consumes wares from the input storages.
3468@@ -406,7 +403,7 @@
3469 /// types of a group are sorted.
3470 // TODO(unknown): change this!
3471 struct ActConsume : public Action {
3472- ActConsume(char* parameters, const ProductionSiteDescr&, const Tribes& tribes);
3473+ ActConsume(const std::vector<std::string>& arguments, const ProductionSiteDescr& descr, const Tribes& tribes);
3474 void execute(Game&, ProductionSite&) const override;
3475 };
3476
3477@@ -426,7 +423,7 @@
3478 /// produced wares are of the type specified in the group. How the produced
3479 /// wares are handled is defined by the productionsite.
3480 struct ActProduce : public Action {
3481- ActProduce(char* parameters, const ProductionSiteDescr&, const Tribes& tribes);
3482+ ActProduce(const std::vector<std::string>& arguments, const ProductionSiteDescr&, const Tribes& tribes);
3483 void execute(Game&, ProductionSite&) const override;
3484 bool get_building_work(Game&, ProductionSite&, Worker&) const override;
3485 };
3486@@ -447,13 +444,13 @@
3487 /// The recruited workers are of the type specified in the group. How the
3488 /// recruited workers are handled is defined by the productionsite.
3489 struct ActRecruit : public Action {
3490- ActRecruit(char* parameters, const ProductionSiteDescr&, const Tribes& tribes);
3491+ ActRecruit(const std::vector<std::string>& arguments, const ProductionSiteDescr&, const Tribes& tribes);
3492 void execute(Game&, ProductionSite&) const override;
3493 bool get_building_work(Game&, ProductionSite&, Worker&) const override;
3494 };
3495
3496 struct ActMine : public Action {
3497- ActMine(char* parameters,
3498+ ActMine(const std::vector<std::string>& arguments,
3499 const World&,
3500 const std::string& production_program_name,
3501 ProductionSiteDescr*);
3502@@ -468,22 +465,22 @@
3503 };
3504
3505 struct ActCheckSoldier : public Action {
3506- explicit ActCheckSoldier(char* parameters);
3507+ explicit ActCheckSoldier(const std::vector<std::string>& arguments);
3508 void execute(Game&, ProductionSite&) const override;
3509
3510 private:
3511- TrainingAttribute attribute;
3512- uint8_t level;
3513+ TrainingAttribute attribute_;
3514+ uint8_t level_;
3515 };
3516
3517 struct ActTrain : public Action {
3518- explicit ActTrain(char* parameters);
3519+ explicit ActTrain(const std::vector<std::string>& arguments);
3520 void execute(Game&, ProductionSite&) const override;
3521
3522 private:
3523- TrainingAttribute attribute;
3524- uint8_t level;
3525- uint8_t target_level;
3526+ TrainingAttribute attribute_;
3527+ uint8_t level_;
3528+ uint8_t target_level_;
3529 };
3530
3531 /// Plays a sound effect.
3532@@ -501,12 +498,11 @@
3533 /// Plays the specified sound effect with the specified priority. Whether the
3534 /// sound effect is actually played is determined by the sound handler.
3535 struct ActPlaySound : public Action {
3536- explicit ActPlaySound(char* parameters);
3537+ explicit ActPlaySound(const std::vector<std::string>& arguments);
3538 void execute(Game&, ProductionSite&) const override;
3539
3540 private:
3541- FxId fx;
3542- uint8_t priority;
3543+ PlaySoundParameters parameters;
3544 };
3545
3546 /// Sends a building worker to construct at an immovable.
3547@@ -522,7 +518,7 @@
3548 /// radius
3549 /// Activity radius
3550 struct ActConstruct : public Action {
3551- ActConstruct(char* parameters,
3552+ ActConstruct(const std::vector<std::string>& arguments,
3553 const std::string& production_program_name,
3554 ProductionSiteDescr*);
3555 void execute(Game&, ProductionSite&) const override;
3556@@ -544,7 +540,6 @@
3557 const World& world,
3558 ProductionSiteDescr* building);
3559
3560- const std::string& name() const;
3561 const std::string& descname() const;
3562
3563 size_t size() const;
3564@@ -555,7 +550,6 @@
3565 const Buildcost& recruited_workers() const;
3566
3567 private:
3568- std::string name_;
3569 std::string descname_;
3570 std::vector<std::unique_ptr<Action>> actions_;
3571 ProductionProgram::Groups consumed_wares_workers_;
3572
3573=== modified file 'src/logic/map_objects/tribes/productionsite.cc'
3574--- src/logic/map_objects/tribes/productionsite.cc 2019-08-20 17:35:33 +0000
3575+++ src/logic/map_objects/tribes/productionsite.cc 2019-08-31 15:58:01 +0000
3576@@ -93,6 +93,8 @@
3577 const Tribes& tribes,
3578 const World& world)
3579 : BuildingDescr(init_descname, init_type, table, tribes),
3580+ ware_demand_checks_(new std::set<DescriptionIndex>()),
3581+ worker_demand_checks_(new std::set<DescriptionIndex>()),
3582 out_of_resource_productivity_threshold_(100) {
3583 if (msgctxt.empty()) {
3584 throw Widelands::GameDataError(
3585@@ -187,10 +189,11 @@
3586 items_table = table.get_table("programs");
3587 for (std::string program_name : items_table->keys<std::string>()) {
3588 std::transform(program_name.begin(), program_name.end(), program_name.begin(), tolower);
3589+ if (programs_.count(program_name)) {
3590+ throw GameDataError("Program '%s' has already been declared for productionsite '%s'",
3591+ program_name.c_str(), name().c_str());
3592+ }
3593 try {
3594- if (programs_.count(program_name)) {
3595- throw wexception("this program has already been declared");
3596- }
3597 std::unique_ptr<LuaTable> program_table = items_table->get_table(program_name);
3598
3599 // Allow use of both gettext and pgettext. This way, we can have a lower workload on
3600@@ -204,7 +207,7 @@
3601 new ProductionProgram(program_name, program_descname,
3602 program_table->get_table("actions"), tribes, world, this));
3603 } catch (const std::exception& e) {
3604- throw wexception("program %s: %s", program_name.c_str(), e.what());
3605+ throw GameDataError("%s: Error in productionsite program %s: %s", name().c_str(), program_name.c_str(), e.what());
3606 }
3607 }
3608
3609@@ -272,6 +275,19 @@
3610 return *new ProductionSite(*this);
3611 }
3612
3613+std::set<DescriptionIndex>* ProductionSiteDescr::ware_demand_checks() const {
3614+ return ware_demand_checks_.get();
3615+}
3616+std::set<DescriptionIndex>* ProductionSiteDescr::worker_demand_checks() const {
3617+ return worker_demand_checks_.get();
3618+}
3619+void ProductionSiteDescr::clear_demand_checks() {
3620+ ware_demand_checks_->clear();
3621+ ware_demand_checks_.reset(nullptr);
3622+ worker_demand_checks_->clear();
3623+ worker_demand_checks_.reset(nullptr);
3624+}
3625+
3626 /*
3627 ==============================
3628
3629
3630=== modified file 'src/logic/map_objects/tribes/productionsite.h'
3631--- src/logic/map_objects/tribes/productionsite.h 2019-06-23 12:45:29 +0000
3632+++ src/logic/map_objects/tribes/productionsite.h 2019-08-31 15:58:01 +0000
3633@@ -73,6 +73,13 @@
3634
3635 Building& create_object() const override;
3636
3637+ // List of wares to register having economy checks. Parsed by the tribes during postload and must be nullptr after loading has finished
3638+ std::set<DescriptionIndex>* ware_demand_checks() const;
3639+ // List of workers to register having economy checks. Parsed by the tribes during postload and must be nullptr after loading has finished
3640+ std::set<DescriptionIndex>* worker_demand_checks() const;
3641+ // Clear ware and worker demand check info
3642+ void clear_demand_checks();
3643+
3644 uint32_t nr_working_positions() const {
3645 uint32_t result = 0;
3646 for (const auto& working_pos : working_positions()) {
3647@@ -143,6 +150,8 @@
3648 }
3649
3650 private:
3651+ std::unique_ptr<std::set<DescriptionIndex>> ware_demand_checks_;
3652+ std::unique_ptr<std::set<DescriptionIndex>> worker_demand_checks_;
3653 BillOfMaterials working_positions_;
3654 BillOfMaterials input_wares_;
3655 BillOfMaterials input_workers_;
3656
3657=== modified file 'src/logic/map_objects/tribes/soldier.cc'
3658--- src/logic/map_objects/tribes/soldier.cc 2019-05-27 14:25:47 +0000
3659+++ src/logic/map_objects/tribes/soldier.cc 2019-08-31 15:58:01 +0000
3660@@ -32,7 +32,6 @@
3661 #include "economy/flag.h"
3662 #include "graphic/graphic.h"
3663 #include "graphic/rendertarget.h"
3664-#include "helper.h"
3665 #include "io/fileread.h"
3666 #include "io/filewrite.h"
3667 #include "logic/editor_game_base.h"
3668
3669=== modified file 'src/logic/map_objects/tribes/trainingsite.cc'
3670--- src/logic/map_objects/tribes/trainingsite.cc 2019-06-23 11:41:17 +0000
3671+++ src/logic/map_objects/tribes/trainingsite.cc 2019-08-31 15:58:01 +0000
3672@@ -28,7 +28,6 @@
3673 #include "base/macros.h"
3674 #include "base/wexception.h"
3675 #include "economy/request.h"
3676-#include "helper.h"
3677 #include "logic/editor_game_base.h"
3678 #include "logic/game.h"
3679 #include "logic/map_objects/tribes/production_program.h"
3680
3681=== modified file 'src/logic/map_objects/tribes/tribes.cc'
3682--- src/logic/map_objects/tribes/tribes.cc 2019-06-23 12:45:29 +0000
3683+++ src/logic/map_objects/tribes/tribes.cc 2019-08-31 15:58:01 +0000
3684@@ -293,15 +293,6 @@
3685 return tribes_->get_mutable(tribeindex);
3686 }
3687
3688-void Tribes::set_ware_type_has_demand_check(const DescriptionIndex& wareindex,
3689- const std::string& tribename) const {
3690- wares_->get_mutable(wareindex)->set_has_demand_check(tribename);
3691-}
3692-
3693-void Tribes::set_worker_type_has_demand_check(const DescriptionIndex& workerindex) const {
3694- workers_->get_mutable(workerindex)->set_has_demand_check();
3695-}
3696-
3697 void Tribes::load_graphics() {
3698 for (size_t tribeindex = 0; tribeindex < nrtribes(); ++tribeindex) {
3699 TribeDescr* tribe = tribes_->get_mutable(tribeindex);
3700@@ -366,6 +357,11 @@
3701 // Some final checks on the gamedata
3702 for (DescriptionIndex i = 0; i < tribes_->size(); ++i) {
3703 TribeDescr* tribe_descr = tribes_->get_mutable(i);
3704+
3705+ // Register which wares and workers have economy demand checks for each tribe
3706+ for (const DescriptionIndex bi : tribe_descr->buildings()) {
3707+ postload_register_economy_demand_checks(*buildings_->get_mutable(bi), *tribe_descr);
3708+ }
3709 // Verify that the preciousness has been set for all of the tribe's wares
3710 for (const DescriptionIndex wi : tribe_descr->wares()) {
3711 if (tribe_descr->get_ware_descr(wi)->ai_hints().preciousness(tribe_descr->name()) ==
3712@@ -378,7 +374,36 @@
3713 }
3714 }
3715
3716-// Set default trainingsites proportions for AI. Make sure that we get a sum of ca. 100
3717+/// Register wares and workers that have economy demand checks for a building
3718+void Tribes::postload_register_economy_demand_checks(BuildingDescr& building_descr, const TribeDescr& tribe_descr)
3719+{
3720+ if (upcast(ProductionSiteDescr, prodsite, &building_descr)) {
3721+ // This function can be called only once per loading of tribes
3722+ assert(prodsite->ware_demand_checks() != nullptr);
3723+
3724+ for (const DescriptionIndex wi : *prodsite->ware_demand_checks()) {
3725+ if (!tribe_descr.has_ware(wi)) {
3726+ throw GameDataError("Productionsite '%s' for tribe '%s' has an economy demand check for ware '%s', but the tribe does not use this ware",
3727+ prodsite->name().c_str(),
3728+ tribe_descr.name().c_str(),
3729+ get_ware_descr(wi)->name().c_str());
3730+ }
3731+ wares_->get_mutable(wi)->set_has_demand_check(tribe_descr.name());
3732+ }
3733+ for (const DescriptionIndex wi : *prodsite->worker_demand_checks()) {
3734+ if (!tribe_descr.has_worker(wi)) {
3735+ throw GameDataError("Productionsite '%s' for tribe '%s' has an economy demand check for worker '%s', but the tribe does not use this worker",
3736+ prodsite->name().c_str(),
3737+ tribe_descr.name().c_str(),
3738+ get_worker_descr(wi)->name().c_str());
3739+ }
3740+ workers_->get_mutable(wi)->set_has_demand_check();
3741+ }
3742+ prodsite->clear_demand_checks();
3743+ }
3744+}
3745+
3746+/// Set default trainingsites proportions for AI. Make sure that we get a sum of ca. 100
3747 void Tribes::postload_calculate_trainingsites_proportions() {
3748 for (DescriptionIndex i = 0; i < tribes_->size(); ++i) {
3749 TribeDescr* tribe_descr = tribes_->get_mutable(i);
3750
3751=== modified file 'src/logic/map_objects/tribes/tribes.h'
3752--- src/logic/map_objects/tribes/tribes.h 2019-05-16 09:15:03 +0000
3753+++ src/logic/map_objects/tribes/tribes.h 2019-08-31 15:58:01 +0000
3754@@ -133,10 +133,6 @@
3755 const WorkerDescr* get_worker_descr(DescriptionIndex worker_index) const;
3756 const TribeDescr* get_tribe_descr(DescriptionIndex tribe_index) const;
3757
3758- void set_ware_type_has_demand_check(const DescriptionIndex& ware_index,
3759- const std::string& tribename) const;
3760- void set_worker_type_has_demand_check(const DescriptionIndex& worker_index) const;
3761-
3762 /// Load tribes' graphics
3763 void load_graphics();
3764
3765@@ -147,6 +143,7 @@
3766
3767 private:
3768 void postload_calculate_trainingsites_proportions();
3769+ void postload_register_economy_demand_checks(BuildingDescr& building_descr, const TribeDescr& tribe_descr);
3770
3771 std::unique_ptr<DescriptionMaintainer<BuildingDescr>> buildings_;
3772 std::unique_ptr<DescriptionMaintainer<ImmovableDescr>> immovables_;
3773
3774=== modified file 'src/logic/map_objects/tribes/worker.cc'
3775--- src/logic/map_objects/tribes/worker.cc 2019-05-28 17:01:30 +0000
3776+++ src/logic/map_objects/tribes/worker.cc 2019-08-31 15:58:01 +0000
3777@@ -36,7 +36,6 @@
3778 #include "graphic/graphic.h"
3779 #include "graphic/rendertarget.h"
3780 #include "graphic/text_layout.h"
3781-#include "helper.h"
3782 #include "io/fileread.h"
3783 #include "io/filewrite.h"
3784 #include "logic/cmd_incorporate.h"
3785@@ -3146,7 +3145,7 @@
3786 return Bob::Loader::get_task(name);
3787 }
3788
3789-const BobProgramBase* Worker::Loader::get_program(const std::string& name) {
3790+const MapObjectProgram* Worker::Loader::get_program(const std::string& name) {
3791 Worker& worker = get<Worker>();
3792 return worker.descr().get_program(name);
3793 }
3794
3795=== modified file 'src/logic/map_objects/tribes/worker.h'
3796--- src/logic/map_objects/tribes/worker.h 2019-04-24 06:01:37 +0000
3797+++ src/logic/map_objects/tribes/worker.h 2019-08-31 15:58:01 +0000
3798@@ -303,7 +303,7 @@
3799
3800 protected:
3801 const Task* get_task(const std::string& name) override;
3802- const BobProgramBase* get_program(const std::string& name) override;
3803+ const MapObjectProgram* get_program(const std::string& name) override;
3804
3805 private:
3806 uint32_t location_;
3807
3808=== modified file 'src/logic/map_objects/tribes/worker_descr.cc'
3809--- src/logic/map_objects/tribes/worker_descr.cc 2019-05-25 08:51:42 +0000
3810+++ src/logic/map_objects/tribes/worker_descr.cc 2019-08-31 15:58:01 +0000
3811@@ -106,16 +106,14 @@
3812 std::unique_ptr<LuaTable> programs_table = table.get_table("programs");
3813 for (std::string program_name : programs_table->keys<std::string>()) {
3814 std::transform(program_name.begin(), program_name.end(), program_name.begin(), tolower);
3815-
3816+ if (programs_.count(program_name)) {
3817+ throw GameDataError("Program '%s' has already been declared for worker '%s'", program_name.c_str(), name().c_str());
3818+ }
3819 try {
3820- if (programs_.count(program_name))
3821- throw wexception("this program has already been declared");
3822-
3823- programs_[program_name] =
3824- std::unique_ptr<WorkerProgram>(new WorkerProgram(program_name, *this, tribes_));
3825- programs_[program_name]->parse(*programs_table->get_table(program_name));
3826+ programs_[program_name] = std::unique_ptr<WorkerProgram>(
3827+ new WorkerProgram(program_name, *programs_table->get_table(program_name), *this, tribes_));
3828 } catch (const std::exception& e) {
3829- throw wexception("program %s: %s", program_name.c_str(), e.what());
3830+ throw GameDataError("%s: Error in worker program %s: %s", name().c_str(), program_name.c_str(), e.what());
3831 }
3832 }
3833 }
3834@@ -136,8 +134,9 @@
3835 WorkerProgram const* WorkerDescr::get_program(const std::string& programname) const {
3836 Programs::const_iterator it = programs_.find(programname);
3837
3838- if (it == programs_.end())
3839- throw wexception("%s has no program '%s'", name().c_str(), programname.c_str());
3840+ if (it == programs_.end()) {
3841+ throw GameDataError("%s has no program '%s'", name().c_str(), programname.c_str());
3842+ }
3843
3844 return it->second.get();
3845 }
3846
3847=== modified file 'src/logic/map_objects/tribes/worker_program.cc'
3848--- src/logic/map_objects/tribes/worker_program.cc 2019-05-11 13:48:12 +0000
3849+++ src/logic/map_objects/tribes/worker_program.cc 2019-08-31 15:58:01 +0000
3850@@ -23,7 +23,6 @@
3851 #include <string>
3852
3853 #include "base/log.h"
3854-#include "helper.h"
3855 #include "logic/game_data_error.h"
3856 #include "logic/map_objects/findnode.h"
3857 #include "sound/sound_handler.h"
3858@@ -98,54 +97,45 @@
3859
3860 {nullptr, nullptr}};
3861
3862+
3863 /**
3864 * Parse a program
3865 */
3866-void WorkerProgram::parse(const LuaTable& table) {
3867- for (const std::string& string : table.array_entries<std::string>()) {
3868- if (string.empty()) {
3869- log("Worker program %s for worker %s contains empty string\n", name_.c_str(),
3870- worker_.name().c_str());
3871- break;
3872+WorkerProgram::WorkerProgram(const std::string& init_name, const LuaTable& actions_table, const WorkerDescr& worker, const Tribes& tribes)
3873+ : MapObjectProgram(init_name), worker_(worker), tribes_(tribes) {
3874+
3875+ for (const std::string& line : actions_table.array_entries<std::string>()) {
3876+ if (line.empty()) {
3877+ throw GameDataError("Empty line");
3878 }
3879 try {
3880- std::vector<std::string> cmd(split_string(string, "="));
3881- if (cmd.empty()) {
3882- continue;
3883- }
3884+
3885+ ProgramParseInput parseinput = parse_program_string(line);
3886
3887 // Find the appropriate parser
3888 Worker::Action act;
3889 uint32_t mapidx;
3890
3891 for (mapidx = 0; parsemap_[mapidx].name; ++mapidx) {
3892- if (cmd[0] == parsemap_[mapidx].name) {
3893+ if (parseinput.name == parsemap_[mapidx].name) {
3894 break;
3895 }
3896 }
3897
3898 if (!parsemap_[mapidx].name) {
3899- throw wexception("unknown command type \"%s\"", cmd[0].c_str());
3900- }
3901-
3902- // TODO(GunChleoc): Quick and dirty solution, don't do it like that when we unify the
3903- // program parsers
3904- if (cmd.size() == 2) {
3905- const std::vector<std::string> parameters(split_string(cmd[1], " \t\n"));
3906- cmd.pop_back();
3907- for (const std::string& parameter : parameters) {
3908- cmd.push_back(parameter);
3909- }
3910- }
3911-
3912- (this->*parsemap_[mapidx].function)(&act, cmd);
3913+ throw GameDataError("Unknown command '%s' in line '%s'", parseinput.name.c_str(), line.c_str());
3914+ }
3915+
3916+ (this->*parsemap_[mapidx].function)(&act, parseinput.arguments);
3917
3918 actions_.push_back(act);
3919 } catch (const std::exception& e) {
3920- throw wexception("Error reading line '%s' in worker program %s for worker %s: %s",
3921- string.c_str(), name_.c_str(), worker_.name().c_str(), e.what());
3922+ throw GameDataError("Error reading line '%s': %s", line.c_str(), e.what());
3923 }
3924 }
3925+ if (actions_.empty()) {
3926+ throw GameDataError("No actions found");
3927+ }
3928 }
3929
3930 /* RST
3931@@ -172,11 +162,12 @@
3932 * iparam1 = ware index
3933 */
3934 void WorkerProgram::parse_createware(Worker::Action* act, const std::vector<std::string>& cmd) {
3935- if (cmd.size() != 2)
3936+ if (cmd.size() != 1) {
3937 throw wexception("Usage: createware=<ware type>");
3938+ }
3939
3940 act->function = &Worker::run_createware;
3941- act->iparam1 = tribes_.safe_ware_index(cmd[1]);
3942+ act->iparam1 = tribes_.safe_ware_index(cmd[0]);
3943 }
3944
3945 /* RST
3946@@ -207,16 +198,13 @@
3947 * sparam1 = resource
3948 */
3949 void WorkerProgram::parse_mine(Worker::Action* act, const std::vector<std::string>& cmd) {
3950- if (cmd.size() != 3)
3951- throw wexception("Usage: mine=<ware type> <area>");
3952+ if (cmd.size() != 2) {
3953+ throw GameDataError("Usage: mine=<ware type> <workarea radius>");
3954+ }
3955
3956 act->function = &Worker::run_mine;
3957- act->sparam1 = cmd[1];
3958- char* endp;
3959- act->iparam1 = strtol(cmd[2].c_str(), &endp, 0);
3960-
3961- if (*endp)
3962- throw wexception("Bad area '%s'", cmd[2].c_str());
3963+ act->sparam1 = cmd[0];
3964+ act->iparam1 = read_positive(cmd[1]);
3965 }
3966
3967 /* RST
3968@@ -244,16 +232,13 @@
3969 * sparam1 = resource
3970 */
3971 void WorkerProgram::parse_breed(Worker::Action* act, const std::vector<std::string>& cmd) {
3972- if (cmd.size() != 3)
3973- throw wexception("Usage: breed=<ware type> <area>");
3974+ if (cmd.size() != 2) {
3975+ throw GameDataError("Usage: breed=<ware type> <workarea radius>");
3976+ }
3977
3978 act->function = &Worker::run_breed;
3979- act->sparam1 = cmd[1];
3980- char* endp;
3981- act->iparam1 = strtol(cmd[2].c_str(), &endp, 0);
3982-
3983- if (*endp)
3984- throw wexception("Bad area '%s'", cmd[2].c_str());
3985+ act->sparam1 = cmd[0];
3986+ act->iparam1 = read_positive(cmd[1]);
3987 }
3988
3989 /* RST
3990@@ -296,37 +281,26 @@
3991 * sparam1 = type
3992 */
3993 void WorkerProgram::parse_findobject(Worker::Action* act, const std::vector<std::string>& cmd) {
3994- uint32_t i;
3995-
3996 act->function = &Worker::run_findobject;
3997 act->iparam1 = -1;
3998 act->iparam2 = -1;
3999 act->sparam1 = "immovable";
4000
4001 // Parse predicates
4002- for (i = 1; i < cmd.size(); ++i) {
4003- uint32_t idx = cmd[i].find(':');
4004- std::string const key = cmd[i].substr(0, idx);
4005- std::string const value = cmd[i].substr(idx + 1);
4006-
4007- if (key == "radius") {
4008- char* endp;
4009-
4010- act->iparam1 = strtol(value.c_str(), &endp, 0);
4011- if (*endp)
4012- throw wexception("Bad findobject radius '%s'", value.c_str());
4013-
4014- } else if (key == "attrib") {
4015- act->iparam2 = MapObjectDescr::get_attribute_id(value);
4016- } else if (key == "type") {
4017- act->sparam1 = value;
4018- } else
4019- throw wexception("Bad findobject predicate %s:%s", key.c_str(), value.c_str());
4020+ for (const std::string& argument : cmd) {
4021+ const std::pair<std::string, std::string> item = read_key_value_pair(argument, ':');
4022+
4023+ if (item.first == "radius") {
4024+ act->iparam1 = read_positive(item.second);
4025+ } else if (item.first == "attrib") {
4026+ act->iparam2 = MapObjectDescr::get_attribute_id(item.second);
4027+ } else if (item.first == "type") {
4028+ act->sparam1 = item.second;
4029+ } else {
4030+ throw GameDataError("Unknown findobject predicate %s", argument.c_str());
4031+ }
4032 }
4033
4034- if (act->iparam1 <= 0)
4035- throw wexception("findobject: must specify radius");
4036-
4037 workarea_info_[act->iparam1].insert(" findobject");
4038 }
4039
4040@@ -408,8 +382,6 @@
4041 * sparam1 = Resource
4042 */
4043 void WorkerProgram::parse_findspace(Worker::Action* act, const std::vector<std::string>& cmd) {
4044- uint32_t i;
4045-
4046 act->function = &Worker::run_findspace;
4047 act->iparam1 = -1;
4048 act->iparam2 = -1;
4049@@ -420,61 +392,58 @@
4050 act->sparam1 = "";
4051
4052 // Parse predicates
4053- for (i = 1; i < cmd.size(); ++i) {
4054- uint32_t idx = cmd[i].find(':');
4055- std::string key = cmd[i].substr(0, idx);
4056- std::string value = cmd[i].substr(idx + 1);
4057-
4058- if (key == "radius") {
4059- char* endp;
4060-
4061- act->iparam1 = strtol(value.c_str(), &endp, 0);
4062- if (*endp)
4063- throw wexception("Bad findspace radius '%s'", value.c_str());
4064-
4065- } else if (key == "size") {
4066- static const struct {
4067- char const* name;
4068- int32_t val;
4069- } sizenames[] = {{"any", FindNodeSize::sizeAny}, {"build", FindNodeSize::sizeBuild},
4070- {"small", FindNodeSize::sizeSmall}, {"medium", FindNodeSize::sizeMedium},
4071- {"big", FindNodeSize::sizeBig}, {"mine", FindNodeSize::sizeMine},
4072- {"port", FindNodeSize::sizePort}, {nullptr, 0}};
4073-
4074- int32_t index;
4075-
4076- for (index = 0; sizenames[index].name; ++index)
4077- if (value == sizenames[index].name)
4078- break;
4079-
4080- if (!sizenames[index].name)
4081- throw wexception("Bad findspace size '%s'", value.c_str());
4082-
4083- act->iparam2 = sizenames[index].val;
4084- } else if (key == "breed") {
4085- act->iparam4 = 1;
4086- } else if (key == "resource") {
4087- act->sparam1 = value;
4088- } else if (key == "space") {
4089- act->iparam3 = 1;
4090- } else if (key == "avoid") {
4091- act->iparam5 = MapObjectDescr::get_attribute_id(value);
4092- } else if (key == "saplingsearches") {
4093- int ival = strtol(value.c_str(), nullptr, 10);
4094- if (1 != act->iparam6 || 1 > ival) {
4095- throw wexception("Findspace: Forester should consider at least one spot.%d %d %s",
4096- act->iparam6, ival, value.c_str());
4097+ for (const std::string& argument : cmd) {
4098+ try {
4099+ const std::pair<std::string, std::string> item = read_key_value_pair(argument, ':');
4100+
4101+ if (item.first == "radius") {
4102+ act->iparam1 = read_positive(item.second);
4103+ } else if (item.first == "size") {
4104+ static const struct {
4105+ char const* name;
4106+ int32_t val;
4107+ } sizenames[] = {{"any", FindNodeSize::sizeAny}, {"build", FindNodeSize::sizeBuild},
4108+ {"small", FindNodeSize::sizeSmall}, {"medium", FindNodeSize::sizeMedium},
4109+ {"big", FindNodeSize::sizeBig}, {"mine", FindNodeSize::sizeMine},
4110+ {"port", FindNodeSize::sizePort}, {nullptr, 0}};
4111+
4112+ int32_t index;
4113+
4114+ for (index = 0; sizenames[index].name; ++index) {
4115+ if (item.second == sizenames[index].name) {
4116+ break;
4117+ }
4118+ }
4119+
4120+ if (!sizenames[index].name) {
4121+ throw GameDataError("Bad findspace size '%s'", item.second.c_str());
4122+ }
4123+
4124+ act->iparam2 = sizenames[index].val;
4125+ } else if (item.first == "breed") {
4126+ act->iparam4 = 1;
4127+ } else if (item.first == "resource") {
4128+ act->sparam1 = item.second;
4129+ } else if (item.first == "space") {
4130+ act->iparam3 = 1;
4131+ } else if (item.first == "avoid") {
4132+ act->iparam5 = MapObjectDescr::get_attribute_id(item.second);
4133+ } else if (item.first == "saplingsearches") {
4134+ act->iparam6 = read_positive(item.second);
4135 } else {
4136- act->iparam6 = ival;
4137+ throw GameDataError("Unknown findspace predicate %s", item.first.c_str());
4138 }
4139- } else
4140- throw wexception("Bad findspace predicate %s:%s", key.c_str(), value.c_str());
4141+ } catch (const GameDataError& e) {
4142+ throw GameDataError("Malformed findspace argument %s: %s", argument.c_str(), e.what());
4143+ }
4144 }
4145
4146- if (act->iparam1 <= 0)
4147- throw wexception("findspace: must specify radius");
4148- if (act->iparam2 < 0)
4149- throw wexception("findspace: must specify size");
4150+ if (act->iparam1 <= 0) {
4151+ throw GameDataError("findspace: must specify radius");
4152+ }
4153+ if (act->iparam2 < 0) {
4154+ throw GameDataError("findspace: must specify size");
4155+ }
4156 workarea_info_[act->iparam1].insert(" findspace");
4157 }
4158
4159@@ -526,19 +495,21 @@
4160 * iparam1 = walkXXX
4161 */
4162 void WorkerProgram::parse_walk(Worker::Action* act, const std::vector<std::string>& cmd) {
4163- if (cmd.size() != 2)
4164- throw wexception("Usage: walk=<where>");
4165+ if (cmd.size() != 1) {
4166+ throw GameDataError("Usage: walk=object|coords|object-or-coords");
4167+ }
4168
4169 act->function = &Worker::run_walk;
4170
4171- if (cmd[1] == "object")
4172+ if (cmd[0] == "object") {
4173 act->iparam1 = Worker::Action::walkObject;
4174- else if (cmd[1] == "coords")
4175+ } else if (cmd[0] == "coords") {
4176 act->iparam1 = Worker::Action::walkCoords;
4177- else if (cmd[1] == "object-or-coords")
4178+ } else if (cmd[0] == "object-or-coords") {
4179 act->iparam1 = Worker::Action::walkObject | Worker::Action::walkCoords;
4180- else
4181- throw wexception("Bad walk destination '%s'", cmd[1].c_str());
4182+ } else {
4183+ throw GameDataError("Bad walk destination '%s'", cmd[0].c_str());
4184+ }
4185 }
4186
4187 /* RST
4188@@ -565,28 +536,14 @@
4189 * iparam2 = duration
4190 */
4191 void WorkerProgram::parse_animate(Worker::Action* act, const std::vector<std::string>& cmd) {
4192- char* endp;
4193-
4194- if (cmd.size() != 3)
4195- throw GameDataError("Usage: animate=<name> <time>");
4196-
4197- if (!worker_.is_animation_known(cmd[1])) {
4198- throw GameDataError("unknown animation \"%s\" in worker program for worker \"%s\"",
4199- cmd[1].c_str(), worker_.name().c_str());
4200- }
4201+ AnimationParameters parameters = MapObjectProgram::parse_act_animate(cmd, worker_, true);
4202
4203 act->function = &Worker::run_animate;
4204 // If the second parameter to MapObjectDescr::get_animation is ever used for anything other than
4205 // level-dependent soldier animations, or we want to write a worker program for a soldier,
4206- // we will need to store the animation name as a string in an iparam
4207- act->iparam1 = worker_.get_animation(cmd[1], nullptr);
4208-
4209- act->iparam2 = strtol(cmd[2].c_str(), &endp, 0);
4210- if (*endp)
4211- throw GameDataError("Bad duration '%s'", cmd[2].c_str());
4212-
4213- if (act->iparam2 <= 0)
4214- throw GameDataError("animation duration must be positive");
4215+ // we will need to store the animation name as a string in an sparam
4216+ act->iparam1 = parameters.animation;
4217+ act->iparam2 = parameters.duration;
4218 }
4219
4220 /* RST
4221@@ -604,7 +561,10 @@
4222 /**
4223 * iparam1 = 0: don't drop ware on flag, 1: do drop ware on flag
4224 */
4225-void WorkerProgram::parse_return(Worker::Action* act, const std::vector<std::string>&) {
4226+void WorkerProgram::parse_return(Worker::Action* act, const std::vector<std::string>& cmd) {
4227+ if (!cmd.empty()) {
4228+ throw GameDataError("Usage: return");
4229+ }
4230 act->function = &Worker::run_return;
4231 act->iparam1 = 1; // drop a ware on our owner's flag
4232 }
4233@@ -634,11 +594,12 @@
4234 * sparam1 = callobject command name
4235 */
4236 void WorkerProgram::parse_callobject(Worker::Action* act, const std::vector<std::string>& cmd) {
4237- if (cmd.size() != 2)
4238- throw wexception("Usage: callobject=<program name>");
4239+ if (cmd.size() != 1) {
4240+ throw GameDataError("Usage: callobject=<program name>");
4241+ }
4242
4243 act->function = &Worker::run_callobject;
4244- act->sparam1 = cmd[1];
4245+ act->sparam1 = cmd[0];
4246 }
4247
4248 /* RST
4249@@ -691,36 +652,28 @@
4250 * iparam1 one of plantXXX
4251 */
4252 void WorkerProgram::parse_plant(Worker::Action* act, const std::vector<std::string>& cmd) {
4253- if (cmd.size() < 2)
4254- throw wexception(
4255- "Usage: plant plant=attrib:<attribute> [attrib:<attribute> ...] [unless object]");
4256+ if (cmd.empty()) {
4257+ throw GameDataError(
4258+ "Usage: plant=attrib:<attribute> [attrib:<attribute> ...] [unless object]");
4259+ }
4260
4261 act->function = &Worker::run_plant;
4262 act->iparam1 = Worker::Action::plantAlways;
4263- for (uint32_t i = 1; i < cmd.size(); ++i) {
4264- if (i >= 2 && cmd[i] == "unless") {
4265+ for (uint32_t i = 0; i < cmd.size(); ++i) {
4266+ if (cmd[i] == "unless") {
4267 ++i;
4268- if (i >= cmd.size())
4269- throw GameDataError("plant: something expected after unless");
4270- if (cmd[i] == "object")
4271+ if (i >= cmd.size()) {
4272+ throw GameDataError("plant: something expected after 'unless'");
4273+ }
4274+ if (cmd[i] == "object") {
4275 act->iparam1 = Worker::Action::plantUnlessObject;
4276- else
4277+ } else {
4278 throw GameDataError("plant: 'unless %s' not understood", cmd[i].c_str());
4279-
4280+ }
4281 continue;
4282 }
4283
4284- // Check if immovable type exists
4285- std::vector<std::string> const list(split_string(cmd[i], ":"));
4286- if (list.size() != 2 || list.at(0) != "attrib") {
4287- throw GameDataError("plant takes a list of attrib:<attribute> that reference world and/or "
4288- "tribe immovables");
4289- }
4290-
4291- const std::string& attrib_name = list[1];
4292- if (attrib_name.empty()) {
4293- throw GameDataError("plant has an empty attrib:<attribute> at position %d", i);
4294- }
4295+ const std::string attrib_name = read_key_value_pair(cmd[i], ':', "", "attrib").second;
4296
4297 // This will throw a GameDataError if the attribute doesn't exist.
4298 ImmovableDescr::get_attribute_id(attrib_name);
4299@@ -750,14 +703,12 @@
4300 */
4301 // TODO(GunChleoc): attrib:eatable would be much better, then depend on terrain too
4302 void WorkerProgram::parse_createbob(Worker::Action* act, const std::vector<std::string>& cmd) {
4303- if (cmd.size() < 2)
4304- throw wexception("Usage: createbob=<bob name> <bob name> ...");
4305+ if (cmd.empty()) {
4306+ throw GameDataError("Usage: createbob=<bob name> <bob name> ...");
4307+ }
4308
4309 act->function = &Worker::run_createbob;
4310-
4311- for (uint32_t i = 1; i < cmd.size(); ++i) {
4312- act->sparamv.push_back(cmd[i]);
4313- }
4314+ act->sparamv = std::move(cmd);
4315 }
4316
4317 /* RST
4318@@ -804,22 +755,14 @@
4319 * sparam1 = subcommand
4320 */
4321 void WorkerProgram::parse_repeatsearch(Worker::Action* act, const std::vector<std::string>& cmd) {
4322- char* endp;
4323-
4324- if (cmd.size() != 4)
4325- throw wexception("Usage: repeatsearch=<repeat #> <radius> <subcommand>");
4326+ if (cmd.size() != 3) {
4327+ throw GameDataError("Usage: repeatsearch=<repeat #> <radius> <subcommand>");
4328+ }
4329
4330 act->function = &Worker::run_repeatsearch;
4331-
4332- act->iparam1 = strtol(cmd[1].c_str(), &endp, 0);
4333- if (*endp)
4334- throw wexception("Bad repeat count '%s'", cmd[1].c_str());
4335-
4336- act->iparam2 = strtol(cmd[2].c_str(), &endp, 0);
4337- if (*endp)
4338- throw wexception("Bad radius '%s'", cmd[2].c_str());
4339-
4340- act->sparam1 = cmd[3];
4341+ act->iparam1 = read_positive(cmd[0]);
4342+ act->iparam2 = read_positive(cmd[1]);
4343+ act->sparam1 = cmd[2];
4344 }
4345
4346 /* RST
4347@@ -840,8 +783,9 @@
4348 }
4349 */
4350 void WorkerProgram::parse_findresources(Worker::Action* act, const std::vector<std::string>& cmd) {
4351- if (cmd.size() != 1)
4352- throw wexception("Usage: findresources");
4353+ if (!cmd.empty()) {
4354+ throw GameDataError("Usage: findresources");
4355+ }
4356
4357 act->function = &Worker::run_findresources;
4358 }
4359@@ -867,11 +811,12 @@
4360 * iparam2 = time
4361 */
4362 void WorkerProgram::parse_scout(Worker::Action* act, const std::vector<std::string>& cmd) {
4363- if (cmd.size() != 3)
4364- throw wexception("Usage: scout=<radius> <time>");
4365+ if (cmd.size() != 2) {
4366+ throw GameDataError("Usage: scout=<radius> <time>");
4367+ }
4368
4369- act->iparam1 = atoi(cmd[1].c_str());
4370- act->iparam2 = atoi(cmd[2].c_str());
4371+ act->iparam1 = read_positive(cmd[0]);
4372+ act->iparam2 = read_positive(cmd[1]);
4373 act->function = &Worker::run_scout;
4374 }
4375
4376@@ -902,17 +847,12 @@
4377 }
4378 */
4379 void WorkerProgram::parse_playsound(Worker::Action* act, const std::vector<std::string>& cmd) {
4380- if (cmd.size() < 3 || cmd.size() > 4)
4381- throw wexception("Usage: playsound <sound_dir> <sound_name> [priority]");
4382-
4383- act->iparam2 = SoundHandler::register_fx(SoundType::kAmbient, cmd[1]);
4384-
4385+ // 50% chance to play, only one instance at a time
4386+ PlaySoundParameters parameters = MapObjectProgram::parse_act_play_sound(cmd, kFxPriorityMedium);
4387+
4388+ act->iparam1 = parameters.priority;
4389+ act->iparam2 = parameters.fx;
4390 act->function = &Worker::run_playsound;
4391- act->iparam1 = cmd.size() == 2 ? kFxPriorityMedium : atoi(cmd[2].c_str());
4392- if (act->iparam1 < kFxPriorityLowest) {
4393- throw GameDataError("Minmum priority for sounds is %d, but only %d was specified for %s",
4394- kFxPriorityLowest, act->iparam1, cmd[1].c_str());
4395- }
4396 }
4397
4398 /* RST
4399@@ -943,8 +883,9 @@
4400 * for construction. This is used in ship building.
4401 */
4402 void WorkerProgram::parse_construct(Worker::Action* act, const std::vector<std::string>& cmd) {
4403- if (cmd.size() != 1)
4404- throw wexception("Usage: construct");
4405+ if (!cmd.empty()) {
4406+ throw GameDataError("Usage: construct");
4407+ }
4408
4409 act->function = &Worker::run_construct;
4410 }
4411
4412=== modified file 'src/logic/map_objects/tribes/worker_program.h'
4413--- src/logic/map_objects/tribes/worker_program.h 2019-02-23 11:00:49 +0000
4414+++ src/logic/map_objects/tribes/worker_program.h 2019-08-31 15:58:01 +0000
4415@@ -24,6 +24,7 @@
4416
4417 #include "base/macros.h"
4418 #include "logic/map_objects/bob.h"
4419+#include "logic/map_objects/map_object_program.h"
4420 #include "logic/map_objects/tribes/tribes.h"
4421 #include "logic/map_objects/tribes/workarea_info.h"
4422 #include "logic/map_objects/tribes/worker.h"
4423@@ -35,20 +36,13 @@
4424 // declaration (Chicken-and-egg problem)
4425 class WorkerDescr;
4426
4427-struct WorkerProgram : public BobProgramBase {
4428+struct WorkerProgram : public MapObjectProgram {
4429
4430 using ParseWorkerProgramFn = void (WorkerProgram::*)(Worker::Action*,
4431 const std::vector<std::string>&);
4432
4433- WorkerProgram(const std::string& name, const WorkerDescr& worker, const Tribes& tribes)
4434- : name_(name), worker_(worker), tribes_(tribes) {
4435- }
4436- ~WorkerProgram() override {
4437- }
4438+ WorkerProgram(const std::string& init_name, const LuaTable& actions_table, const WorkerDescr& worker, const Tribes& tribes);
4439
4440- std::string get_name() const override {
4441- return name_;
4442- }
4443 using Actions = std::vector<Worker::Action>;
4444 Actions::size_type get_size() const {
4445 return actions_.size();
4446@@ -62,7 +56,6 @@
4447 return &actions_[idx];
4448 }
4449
4450- void parse(const LuaTable& table);
4451 const WorkareaInfo& get_workarea_info() const {
4452 return workarea_info_;
4453 }
4454@@ -92,7 +85,6 @@
4455 void parse_playsound(Worker::Action* act, const std::vector<std::string>& cmd);
4456 void parse_construct(Worker::Action* act, const std::vector<std::string>& cmd);
4457
4458- const std::string name_;
4459 const WorkerDescr& worker_;
4460 const Tribes& tribes_;
4461 Actions actions_;
4462
4463=== modified file 'src/logic/map_objects/world/critter.cc'
4464--- src/logic/map_objects/world/critter.cc 2019-06-23 10:30:26 +0000
4465+++ src/logic/map_objects/world/critter.cc 2019-08-31 15:58:01 +0000
4466@@ -27,22 +27,21 @@
4467 #include <stdint.h>
4468
4469 #include "base/wexception.h"
4470-#include "helper.h"
4471 #include "io/fileread.h"
4472 #include "io/filewrite.h"
4473 #include "logic/field.h"
4474 #include "logic/game.h"
4475 #include "logic/game_data_error.h"
4476+#include "logic/map_objects/map_object_program.h"
4477 #include "logic/map_objects/tribes/tribe_descr.h"
4478-#include "logic/map_objects/world/critter_program.h"
4479 #include "logic/map_objects/world/world.h"
4480 #include "map_io/world_legacy_lookup_table.h"
4481 #include "scripting/lua_table.h"
4482
4483 namespace Widelands {
4484
4485-void CritterProgram::parse(const std::vector<std::string>& lines) {
4486- for (const std::string& line : lines) {
4487+CritterProgram::CritterProgram(const std::string& program_name, const LuaTable& actions_table) : MapObjectProgram(program_name) {
4488+ for (const std::string& line : actions_table.array_entries<std::string>()) {
4489 try {
4490 const std::vector<std::string> cmd(split_string(line, " \t\r\n"));
4491 if (cmd.empty())
4492@@ -109,9 +108,7 @@
4493 std::unique_ptr<LuaTable> programs = table.get_table("programs");
4494 for (const std::string& program_name : programs->keys<std::string>()) {
4495 try {
4496- std::unique_ptr<CritterProgram> prog(new CritterProgram(program_name));
4497- prog->parse(programs->get_table(program_name)->array_entries<std::string>());
4498- programs_[program_name] = prog.release();
4499+ programs_[program_name] = std::unique_ptr<CritterProgram>(new CritterProgram(program_name, *programs->get_table(program_name).get()));
4500 } catch (const std::exception& e) {
4501 throw wexception("Parse error in program %s: %s", program_name.c_str(), e.what());
4502 }
4503@@ -126,9 +123,6 @@
4504 }
4505
4506 CritterDescr::~CritterDescr() {
4507- for (auto program : programs_) {
4508- delete program.second;
4509- }
4510 }
4511
4512 bool CritterDescr::is_swimming() const {
4513@@ -141,11 +135,11 @@
4514 Get a program from the workers description.
4515 ===============
4516 */
4517-CritterProgram const* CritterDescr::get_program(const std::string& programname) const {
4518- Programs::const_iterator const it = programs_.find(programname);
4519+CritterProgram const* CritterDescr::get_program(const std::string& program_name) const {
4520+ Programs::const_iterator const it = programs_.find(program_name);
4521 if (it == programs_.end())
4522- throw wexception("%s has no program '%s'", name().c_str(), programname.c_str());
4523- return it->second;
4524+ throw wexception("%s has no program '%s'", name().c_str(), program_name.c_str());
4525+ return it->second.get();
4526 }
4527
4528 uint32_t CritterDescr::movecaps() const {
4529@@ -283,7 +277,7 @@
4530 return Bob::Loader::get_task(name);
4531 }
4532
4533-const BobProgramBase* Critter::Loader::get_program(const std::string& name) {
4534+const MapObjectProgram* Critter::Loader::get_program(const std::string& name) {
4535 Critter& critter = get<Critter>();
4536 return critter.descr().get_program(name);
4537 }
4538
4539=== modified file 'src/logic/map_objects/world/critter.h'
4540--- src/logic/map_objects/world/critter.h 2019-02-23 11:00:49 +0000
4541+++ src/logic/map_objects/world/critter.h 2019-08-31 15:58:01 +0000
4542@@ -20,9 +20,12 @@
4543 #ifndef WL_LOGIC_MAP_OBJECTS_WORLD_CRITTER_H
4544 #define WL_LOGIC_MAP_OBJECTS_WORLD_CRITTER_H
4545
4546+#include <memory>
4547+
4548 #include "base/macros.h"
4549 #include "graphic/diranimations.h"
4550 #include "logic/map_objects/bob.h"
4551+#include "logic/map_objects/world/critter_program.h"
4552
4553 class LuaTable;
4554 class WorldLegacyLookupTable;
4555@@ -48,13 +51,13 @@
4556 return walk_anims_;
4557 }
4558
4559- CritterProgram const* get_program(const std::string&) const;
4560+ CritterProgram const* get_program(const std::string& program_name) const;
4561
4562 const EditorCategory* editor_category() const;
4563
4564 private:
4565 DirAnimations walk_anims_;
4566- using Programs = std::map<std::string, CritterProgram*>;
4567+ using Programs = std::map<std::string, std::unique_ptr<const CritterProgram>>;
4568 Programs programs_;
4569 EditorCategory* editor_category_; // not owned.
4570 DISALLOW_COPY_AND_ASSIGN(CritterDescr);
4571@@ -83,7 +86,7 @@
4572 Loader();
4573
4574 const Task* get_task(const std::string& name) override;
4575- const BobProgramBase* get_program(const std::string& name) override;
4576+ const MapObjectProgram* get_program(const std::string& name) override;
4577 };
4578
4579 private:
4580
4581=== modified file 'src/logic/map_objects/world/critter_program.h'
4582--- src/logic/map_objects/world/critter_program.h 2019-02-23 11:00:49 +0000
4583+++ src/logic/map_objects/world/critter_program.h 2019-08-31 15:58:01 +0000
4584@@ -21,8 +21,10 @@
4585 #define WL_LOGIC_MAP_OBJECTS_WORLD_CRITTER_PROGRAM_H
4586
4587 #include "logic/map_objects/bob.h"
4588+#include "logic/map_objects/map_object_program.h"
4589
4590 namespace Widelands {
4591+class Critter;
4592
4593 struct CritterAction {
4594 using CritterExecuteActionFn = bool (Critter::*)(Game&, Bob::State&, const CritterAction&);
4595@@ -40,15 +42,9 @@
4596 std::vector<std::string> sparamv;
4597 };
4598
4599-struct CritterProgram : public BobProgramBase {
4600- explicit CritterProgram(const std::string& name) : name_(name) {
4601- }
4602- ~CritterProgram() override {
4603- }
4604+struct CritterProgram : public MapObjectProgram {
4605+ explicit CritterProgram(const std::string& program_name, const LuaTable& actions_table);
4606
4607- std::string get_name() const override {
4608- return name_;
4609- }
4610 int32_t get_size() const {
4611 return actions_.size();
4612 }
4613@@ -57,10 +53,7 @@
4614 return actions_[idx];
4615 }
4616
4617- void parse(const std::vector<std::string>& lines);
4618-
4619 private:
4620- std::string name_;
4621 std::vector<CritterAction> actions_;
4622 };
4623 } // namespace Widelands
4624
4625=== modified file 'src/logic/map_objects/world/resource_description.cc'
4626--- src/logic/map_objects/world/resource_description.cc 2019-02-23 11:00:49 +0000
4627+++ src/logic/map_objects/world/resource_description.cc 2019-08-31 15:58:01 +0000
4628@@ -21,7 +21,6 @@
4629
4630 #include <memory>
4631
4632-#include "helper.h"
4633 #include "logic/game_data_error.h"
4634 #include "scripting/lua_table.h"
4635
4636
4637=== modified file 'src/network/CMakeLists.txt'
4638--- src/network/CMakeLists.txt 2019-08-27 19:00:30 +0000
4639+++ src/network/CMakeLists.txt 2019-08-31 15:58:01 +0000
4640@@ -44,7 +44,6 @@
4641 build_info
4642 chat
4643 game_io
4644- helper
4645 io_fileread
4646 io_filesystem
4647 io_stream
4648
4649=== modified file 'src/network/gameclient.cc'
4650--- src/network/gameclient.cc 2019-08-28 06:12:07 +0000
4651+++ src/network/gameclient.cc 2019-08-31 15:58:01 +0000
4652@@ -31,7 +31,6 @@
4653 #include "build_info.h"
4654 #include "config.h"
4655 #include "game_io/game_loader.h"
4656-#include "helper.h"
4657 #include "io/fileread.h"
4658 #include "io/filesystem/filesystem_exceptions.h"
4659 #include "io/filewrite.h"
4660
4661=== modified file 'src/network/gamehost.cc'
4662--- src/network/gamehost.cc 2019-08-28 06:12:07 +0000
4663+++ src/network/gamehost.cc 2019-08-31 15:58:01 +0000
4664@@ -40,7 +40,6 @@
4665 #include "chat/chat.h"
4666 #include "game_io/game_loader.h"
4667 #include "game_io/game_preload_packet.h"
4668-#include "helper.h"
4669 #include "io/fileread.h"
4670 #include "io/filesystem/layered_filesystem.h"
4671 #include "logic/filesystem_constants.h"
4672
4673=== modified file 'src/scripting/CMakeLists.txt'
4674--- src/scripting/CMakeLists.txt 2019-08-27 19:00:30 +0000
4675+++ src/scripting/CMakeLists.txt 2019-08-31 15:58:01 +0000
4676@@ -70,7 +70,6 @@
4677 base_i18n
4678 base_macros
4679 build_info
4680- helper
4681 io_filesystem
4682 scripting_base
4683 scripting_errors
4684
4685=== modified file 'src/scripting/lua_path.cc'
4686--- src/scripting/lua_path.cc 2019-04-28 10:14:42 +0000
4687+++ src/scripting/lua_path.cc 2019-08-31 15:58:01 +0000
4688@@ -24,7 +24,6 @@
4689 #include <boost/lexical_cast.hpp>
4690
4691 #include "base/macros.h"
4692-#include "helper.h"
4693 #include "io/filesystem/layered_filesystem.h"
4694
4695 namespace {
4696
4697=== modified file 'src/sound/CMakeLists.txt'
4698--- src/sound/CMakeLists.txt 2019-08-27 19:00:30 +0000
4699+++ src/sound/CMakeLists.txt 2019-08-31 15:58:01 +0000
4700@@ -28,7 +28,6 @@
4701 DEPENDS
4702 base_i18n
4703 base_log
4704- helper
4705 io_fileread
4706 io_filesystem
4707 logic_exceptions
4708
4709=== modified file 'src/sound/fxset.cc'
4710--- src/sound/fxset.cc 2019-05-25 08:52:09 +0000
4711+++ src/sound/fxset.cc 2019-08-31 15:58:01 +0000
4712@@ -25,7 +25,6 @@
4713 #include <boost/regex.hpp>
4714
4715 #include "base/log.h"
4716-#include "helper.h"
4717 #include "io/fileread.h"
4718 #include "io/filesystem/layered_filesystem.h"
4719 #include "logic/game_data_error.h"
4720
4721=== modified file 'src/sound/songset.cc'
4722--- src/sound/songset.cc 2019-04-28 09:14:59 +0000
4723+++ src/sound/songset.cc 2019-08-31 15:58:01 +0000
4724@@ -25,7 +25,6 @@
4725 #include <boost/regex.hpp>
4726
4727 #include "base/log.h"
4728-#include "helper.h"
4729 #include "io/fileread.h"
4730 #include "io/filesystem/layered_filesystem.h"
4731
4732
4733=== modified file 'src/ui_fsmenu/CMakeLists.txt'
4734--- src/ui_fsmenu/CMakeLists.txt 2019-08-27 19:00:30 +0000
4735+++ src/ui_fsmenu/CMakeLists.txt 2019-08-31 15:58:01 +0000
4736@@ -10,7 +10,6 @@
4737 graphic_fonthandler
4738 graphic_text
4739 graphic_text_layout
4740- helper
4741 io_filesystem
4742 logic_filesystem_constants
4743 scripting_lua_interface
4744@@ -101,7 +100,6 @@
4745 base_i18n
4746 graphic
4747 graphic_playercolor
4748- helper
4749 io_filesystem
4750 io_profile
4751 logic
4752
4753=== modified file 'src/ui_fsmenu/launch_spg.cc'
4754--- src/ui_fsmenu/launch_spg.cc 2019-06-01 14:16:25 +0000
4755+++ src/ui_fsmenu/launch_spg.cc 2019-08-31 15:58:01 +0000
4756@@ -26,7 +26,6 @@
4757 #include "base/i18n.h"
4758 #include "base/warning.h"
4759 #include "base/wexception.h"
4760-#include "helper.h"
4761 #include "io/filesystem/layered_filesystem.h"
4762 #include "logic/game.h"
4763 #include "logic/game_controller.h"
4764
4765=== modified file 'src/ui_fsmenu/options.cc'
4766--- src/ui_fsmenu/options.cc 2019-08-25 14:50:16 +0000
4767+++ src/ui_fsmenu/options.cc 2019-08-31 15:58:01 +0000
4768@@ -36,7 +36,6 @@
4769 #include "graphic/text/bidi.h"
4770 #include "graphic/text/font_set.h"
4771 #include "graphic/text_layout.h"
4772-#include "helper.h"
4773 #include "io/filesystem/disk_filesystem.h"
4774 #include "io/filesystem/layered_filesystem.h"
4775 #include "logic/filesystem_constants.h"
4776
4777=== modified file 'src/wui/CMakeLists.txt'
4778--- src/wui/CMakeLists.txt 2019-08-27 19:00:30 +0000
4779+++ src/wui/CMakeLists.txt 2019-08-31 15:58:01 +0000
4780@@ -88,7 +88,6 @@
4781 base_i18n
4782 base_log
4783 base_time_string
4784- helper
4785 game_io
4786 graphic
4787 graphic_fonthandler
4788
4789=== modified file 'src/wui/load_or_save_game.cc'
4790--- src/wui/load_or_save_game.cc 2019-05-27 14:28:34 +0000
4791+++ src/wui/load_or_save_game.cc 2019-08-31 15:58:01 +0000
4792@@ -31,8 +31,6 @@
4793 #include "game_io/game_loader.h"
4794 #include "game_io/game_preload_packet.h"
4795 #include "graphic/font_handler.h"
4796-#include "graphic/text_layout.h"
4797-#include "helper.h"
4798 #include "io/filesystem/filesystem_exceptions.h"
4799 #include "io/filesystem/layered_filesystem.h"
4800 #include "logic/filesystem_constants.h"

Subscribers

People subscribed via source and target branches

to status/vote changes: