Merge lp:~widelands-dev/widelands/unify-program-parsers into lp:widelands
- unify-program-parsers
- Merge into trunk
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 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Notabilis | Approve | ||
Review via email:
|
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:
- 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.

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-directorie

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.

Notabilis (notabilis27) wrote : | # |
Looking good now, thanks.

GunChleoc (gunchleoc) wrote : | # |
Thanks for reviewing again :)
@bunnybot merge

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:/
- 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.

bunnybot (widelandsofficial) wrote : | # |
Continuous integration builds have changed state:
Travis build 5242. State: errored. Details: https:/
Appveyor build 5021. State: success. Details: https:/

Toni Förster (stonerl) wrote : | # |
Is it okay to force merge this branch?
test/maps/
Running Widelands ...
Seems to time out in some builds.

GunChleoc (gunchleoc) wrote : | # |
No, I still need to find the bug that occurs with some compilers.

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
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" |
Continuous integration builds have changed state:
Travis build 5061. State: failed. Details: https:/ /travis- ci.org/ widelands/ widelands/ builds/ 537401669. /ci.appveyor. com/project/ widelands- dev/widelands/ build/_ widelands_ dev_widelands_ unify_program_ parsers- 4841.
Appveyor build 4841. State: success. Details: https:/