Merge lp:~widelands-dev/widelands/campaign_data into lp:widelands
- campaign_data
- Merge into trunk
Status: | Merged |
---|---|
Merged at revision: | 8724 |
Proposed branch: | lp:~widelands-dev/widelands/campaign_data |
Merge into: | lp:widelands |
Diff against target: |
373 lines (+297/-0) 5 files modified
src/logic/filesystem_constants.h (+4/-0) src/scripting/CMakeLists.txt (+2/-0) src/scripting/lua_bases.cc (+212/-0) src/scripting/lua_bases.h (+2/-0) test/maps/plain.wmf/scripting/test_campaign_data.lua (+77/-0) |
To merge this branch: | bzr merge lp:~widelands-dev/widelands/campaign_data |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Klaus Halfmann | Approve | ||
Review via email: mp+343783@code.launchpad.net |
Commit message
Description of the change
Adds two Lua functions save_campaign_
These functions allow a campaign to store information in a file at the end of one scenario, and read it again in another mission.
Campaign data is stored in ".widelands/
A script can call these functions like this:
wl.Game(
local text = wl.Game(
local text = wl.Game(
GunChleoc (gunchleoc) wrote : | # |
bunnybot (widelandsofficial) wrote : | # |
Continuous integration builds have changed state:
Travis build 3403. State: failed. Details: https:/
Appveyor build 3209. State: success. Details: https:/
hessenfarmer (stephan-lutz) wrote : | # |
Thinking as scenario designer I would prefer to have the string solution. Because if there is a fixed format and I Need just one single value in the next Scenario I would have to write a lot of Zeros to the file, additionally there is more information in a Scenario than is contained in the lua table.
For example you could take the decisions contained in empire 4 (How to deal with the monastry) or fri02 (peace Option or war Option) you could easily use these decisions in follow on scenarios as well and they are just local variables.
Benedikt Straub (nordfriese) wrote : | # |
I could try to implement the table solution without fixed formats. So you pass a table to the save function, but it is up to you what keys to specify, and the .wcd file will contain only the keys you chose to use, with the values you assigned to them.
For example, you could call
local decision = ...
wl.Game(
conquered_
})
and then you´d get in the next scenario
local cdtable = wl.Game(
--> value of cdtable is {conquered_
local decision = cdtable.
So you´d save and get only the information you really want.
GunChleoc (gunchleoc) wrote : | # |
> I would have to write a lot of Zeros to the file
Why do you think that? If you want 1 value, write 1 value.
> For example you could take the decisions contained in empire 4 (How to deal with the monastry) or fri02 (peace Option or war Option) you could easily use these decisions in follow on scenarios as well and they are just local variables.
We can have a "general" section here like Nordfriese suggested, with as many string_key = string_value pairs as you want. Or maybe even better, a section "booleans" or "decisions" for decisions, and section "strings" for general strings? At the moment, I don't wee he need for strings though.
Using just 1 long string that you then need to parse in Lua sounds pretty hacky to me. Since this is a new design, we should take the time to design it properly. This way, we won't have to change it later and write compatibility code or break savegame compatibility. It will take longer now, but experience with this project has shown me that we will benefit greatly from a proper data structure.
hessenfarmer (stephan-lutz) wrote : | # |
Ok now I understand the thing better.
Of course Organising the file in different tables containing variables by their type, would make sense.
However as I can't imagine yet what things could be useful to save in the future, the Format should be able to cope with this. So it should provide some flexibility as well. Probably this will be given if there would be a table for every Kind of variable available.
bunnybot (widelandsofficial) wrote : | # |
Continuous integration builds have changed state:
Travis build 3411. State: failed. Details: https:/
Appveyor build 3217. State: failed. Details: https:/
bunnybot (widelandsofficial) wrote : | # |
Continuous integration builds have changed state:
Travis build 3413. State: failed. Details: https:/
Appveyor build 3219. State: success. Details: https:/
bunnybot (widelandsofficial) wrote : | # |
Continuous integration builds have changed state:
Travis build 3415. State: passed. Details: https:/
Appveyor build 3221. State: success. Details: https:/
bunnybot (widelandsofficial) wrote : | # |
Continuous integration builds have changed state:
Travis build 3439. State: errored. Details: https:/
Appveyor build 3244. State: success. Details: https:/
bunnybot (widelandsofficial) wrote : | # |
Continuous integration builds have changed state:
Travis build 3526. State: errored. Details: https:/
Appveyor build 3331. State: failed. Details: https:/
bunnybot (widelandsofficial) wrote : | # |
Continuous integration builds have changed state:
Travis build 3527. State: passed. Details: https:/
Appveyor build 3332. State: success. Details: https:/
Klaus Halfmann (klaus-halfmann) wrote : | # |
Looks we lost trac on this one.
But as it should not break anything on the road
to R20 we can include it and start using in with R21.
If I play a scenario twice with different outcome,
the data will still be inside the different save-files, ok?
One comment inline.
If this compiles and the test pass I will tell bunnybot to merge.
Klaus Halfmann (klaus-halfmann) wrote : | # |
Oops this crashes on OSX when running the regression tests:
test/maps/
Running Widelands ... FAIL
READ of size 3 at 0x7ffeeb29c1c1 thread T0
#0 0x109307c34 in wrap_strlen (libclang_
#1 0x1049e3d04 in boost::
#2 0x1049e3bf9 in boost::
#3 0x106e5794e in boost::
#4 0x106e58028 in bool boost::
#5 0x106e49ab6 in bool boost::
#6 0x106e496da in Section:
#7 0x106a5a3d2 in LuaBases:
#8 0x106a56d06 in LuaBases:
#9 0x106bf85ca in int method_
..
This frame has 7 object(s):
[32, 56) 'ref.tmp' (line 429) <== Memory access at offset 33 is inside this variable
[96, 120) 'ref.tmp2' (line 429)
[160, 184) 'ref.tmp3' (line 429)
...
SUMMARY: AddressSanitizer: stack-use-
Benedikt, can you confirm / fix it with this information?
Otherwise I will have to setup a longer debugger session.
GunChleoc (gunchleoc) wrote : | # |
I get an error too, from ASAN. There is some illegal data passed to Profile by that line.
=======
==10374==ERROR: AddressSanitizer: stack-use-
READ of size 3 at 0x7ffe6d2c9300 thread T0
#0 0x7f9ba515b66d (/usr/lib/
#1 0x55b57ce0c51d in boost::
#2 0x55b57ce2093e in boost::
#3 0x55b57cee0d81 in boost::
#4 0x55b57dd78425 in bool boost::
#5 0x55b57dd77345 in bool boost::
#6 0x55b57dd71a84 in Section:
#7 0x55b57db5209d in LuaBases:
#8 0x55b57db5330f in LuaBases:
Benedikt Straub (nordfriese) wrote : | # |
Diff comment: I don´t think a recursion depth limit is needed, as the documentation explicitly forbids cyclic dependencies. It would be easy to implement though – what would be a sensible limit here?
> If I play a scenario twice with different outcome,
> the data will still be inside the different save-files, ok?
Every time you play the scenario, the old .wcd file is overwritten. So, the read method always returns the last data that was saved.
Regarding the ASAN error – I have no idea why this happens. It puzzles me that it works correctly despite ASAN´s complaint. Any ideas how to fix this?
GunChleoc (gunchleoc) wrote : | # |
I would surround the offending line with some log outputs to try to find out which value is causing this.
Klaus Halfmann (klaus-halfmann) wrote : | # |
Trying to debug this is not easy, I still do not understand that code:
I run:
> ./regression_
I checked the outout in
> /var/folders/
I had to tweak the file to replace \n by real newline to make it readable.
'b'Trying to run: map:scripting/
'b'Forcing flag at (23, 26)
'b'Message: adding warehouse for player 1 at (22, 25)
'b'done
'b'Trying to run: test/maps/
'b'Writing campaign data...
'b'Done.
'b'Reading campaign data...
'b'====
'b'==17300==ERROR: AddressSanitizer: stack-use-
'b'READ of size 3 at 0x7ffee08c4261 thread T0
...
'b' #6 0x11184300a in Section:
'b' #7 0x11141d4e2 in LuaBases:
'b' #8 0x111419e16 in LuaBases:
So that code reads some variable from a stack that has already been freed.
Benedikt:
* Please add more comments to push_table_
* I could not locate the file campaigns/
where should I find this?
I assume profile.
which is always a bad idea. I found this function with more then one section
at a time is used only by your code. I had expected that the compiler will give
a warning, but Clang does not?
Can anybody give me some more hints how this Profile class should be used?
Klaus Halfmann (klaus-halfmann) wrote : | # |
Mhh, that Profile/Section concept is used more often then I first found it,
but its not broken. I think I now undestood the idea how you want to make
the recursive table into a flat .ini like file.
What ist the best way to make a quick and dirty test for this particular function?
Klaus Halfmann (klaus-halfmann) wrote : | # |
This is _not_ UseAfterReturn as I assumed before but UseAfterScope:
Here the examples from the ASAN Docs:
https:/
I did not dind a direkt scopes with brakets { .... } but an expression may be a scope, too.
What about this one?
const char* key_key = (depth + "_" + std::to_
http://
The pointer returned may be invalidated by further calls to
other member functions that modify the object.
Bu the temporary std:string you build here, is destructed right after that call,
as it goes out of scope. Will try o fix this now ... done.
Benedikt/Gun pleas take a look at my commit, (give me a few minutes)
Benedikt Straub (nordfriese) wrote : | # |
I added some comments and logs.
Normally, the campaign data file should be in "~/.widelands/
I believe the error may be caused by Section::has_val(). The Profile class is used in many places but the has_val() function was unused until now.
I´m unable to run with ASAN for some reason, it compiles fine but I get this error on startup:
==25087==ASan runtime does not come first in initial library list; you should either link runtime to your application or manually preload it with LD_PRELOAD.
Can you please trigger the error again with the additional logs and provide the output, and possibly the contents of the .wcd file (if it exists)?
The easiest way to test is to put the following code in the scripting/init.lua for any map, and load it as a singleplayer scenario:
include "scripting/
function thread()
game = wl.Game ()
save()
read()
end
function save()
print("Starting to WRITE...")
game:save_
x = 5,
y = "Hello",
z = {
nil,
10,
12,
14,
16,
"A",
"B",
"C",
{
a = 1,
b = 2,
},
"D",
}
})
print("Done.")
end
function print_table(t, depth)
print(
for k,v in pairs(t) do
local string = string.rep(" ", depth) .. k .. "="
if v == nil then
print ( string .. "nil" )
elseif type(v) == "table" then
print ( string .. "{" )
print (string.rep(" ", depth) .. "}")
elseif type(v) == "boolean" then
if v then print ( string .. "true" ) else print ( string .. "false" ) end
else
print ( string .. v )
end
end
end
function read()
print("Starting to READ...")
local result = game:read_
print("Returned:")
if result == nil then
print (" <nil>")
else
print_
end
print("Done.")
end
run(thread)
This saves a campaign data file, then reads it and prints the result to stdout. You can then compare the init.lua, the wcd file and the output. Without ASAN, this works fine for me.
Klaus Halfmann (klaus-halfmann) wrote : | # |
Commited, feel free to merge
bunnybot (widelandsofficial) wrote : | # |
Continuous integration builds have changed state:
Travis build 3557. State: failed. Details: https:/
Appveyor build 3361. State: success. Details: https:/
GunChleoc (gunchleoc) wrote : | # |
We still have a codecheck error:
src/scripting/
> I had to tweak the file to replace \n by real newline to make it readable.
That's because you're on Windows and your editor can't handle Unix line endings. I recommend using Notepad++ or Geany.
Klaus Halfmann (klaus-halfmann) wrote : | # |
Fixed that codecheck error and compiler warnings about thos table_recursive functions.
bunnybot (widelandsofficial) wrote : | # |
Continuous integration builds have changed state:
Travis build 3560. State: errored. Details: https:/
Appveyor build 3363. State: success. Details: https:/
Klaus Halfmann (klaus-halfmann) wrote : | # |
Looks trike travis had a _lot_ of Problems with http://
Gun: can this still go in?
GunChleoc (gunchleoc) wrote : | # |
We didn't get an debug builds on GCC out of this one, so best merge trunk to trigger Travis again.
bunnybot (widelandsofficial) wrote : | # |
Continuous integration builds have changed state:
Travis build 3566. State: passed. Details: https:/
Appveyor build 3369. State: success. Details: https:/
Klaus Halfmann (klaus-halfmann) wrote : | # |
@bunnybot merge
finally....
Preview Diff
1 | === modified file 'src/logic/filesystem_constants.h' |
2 | --- src/logic/filesystem_constants.h 2018-04-07 16:59:00 +0000 |
3 | +++ src/logic/filesystem_constants.h 2018-05-31 18:15:43 +0000 |
4 | @@ -54,6 +54,10 @@ |
5 | // Default autosave interval in minutes |
6 | constexpr int kDefaultAutosaveInterval = 15; |
7 | |
8 | +// Filesystem names for campaign data |
9 | +const std::string kCampaignDataDir = "campaigns"; |
10 | +const std::string kCampaignDataExtension = ".wcd"; |
11 | + |
12 | /// Filesystem names for screenshots |
13 | const std::string kScreenshotsDir = "screenshots"; |
14 | |
15 | |
16 | === modified file 'src/scripting/CMakeLists.txt' |
17 | --- src/scripting/CMakeLists.txt 2018-02-13 10:14:35 +0000 |
18 | +++ src/scripting/CMakeLists.txt 2018-05-31 18:15:43 +0000 |
19 | @@ -109,11 +109,13 @@ |
20 | logic |
21 | logic_campaign_visibility |
22 | logic_constants |
23 | + logic_filesystem_constants |
24 | logic_game_controller |
25 | logic_game_settings |
26 | logic_tribe_basic_info |
27 | logic_widelands_geometry |
28 | map_io |
29 | + profile |
30 | scripting_base |
31 | scripting_coroutine |
32 | scripting_errors |
33 | |
34 | === modified file 'src/scripting/lua_bases.cc' |
35 | --- src/scripting/lua_bases.cc 2018-04-07 16:59:00 +0000 |
36 | +++ src/scripting/lua_bases.cc 2018-05-31 18:15:43 +0000 |
37 | @@ -19,15 +19,19 @@ |
38 | |
39 | #include "scripting/lua_bases.h" |
40 | |
41 | +#include <boost/algorithm/string.hpp> |
42 | #include <boost/format.hpp> |
43 | |
44 | #include "economy/economy.h" |
45 | +#include "io/filesystem/layered_filesystem.h" |
46 | +#include "logic/filesystem_constants.h" |
47 | #include "logic/map_objects/checkstep.h" |
48 | #include "logic/map_objects/tribes/tribe_descr.h" |
49 | #include "logic/map_objects/tribes/tribes.h" |
50 | #include "logic/map_objects/tribes/ware_descr.h" |
51 | #include "logic/map_objects/world/world.h" |
52 | #include "logic/player.h" |
53 | +#include "profile/profile.h" |
54 | #include "scripting/factory.h" |
55 | #include "scripting/globals.h" |
56 | #include "scripting/lua_map.h" |
57 | @@ -83,6 +87,8 @@ |
58 | METHOD(LuaEditorGameBase, get_worker_description), |
59 | METHOD(LuaEditorGameBase, get_resource_description), |
60 | METHOD(LuaEditorGameBase, get_terrain_description), |
61 | + METHOD(LuaEditorGameBase, save_campaign_data), |
62 | + METHOD(LuaEditorGameBase, read_campaign_data), |
63 | {nullptr, nullptr}, |
64 | }; |
65 | const PropertyType<LuaEditorGameBase> LuaEditorGameBase::Properties[] = { |
66 | @@ -319,6 +325,212 @@ |
67 | return to_lua<LuaMaps::LuaTerrainDescription>(L, new LuaMaps::LuaTerrainDescription(descr)); |
68 | } |
69 | |
70 | +/* Helper function for save_campaign_data() |
71 | + |
72 | + This function reads the lua table from the stack and saves information about its |
73 | + keys, values and data types and its size to the provided maps. |
74 | + This function is recursive so subtables to any depth can be saved. |
75 | + Each value in the table (including all subtables) is uniquely identified by a key_key. |
76 | + The key_key is used as key in all the map. |
77 | + For the topmost table of size x, the key_keys are called '_0' through '_x-1'. |
78 | + For a subtable of size z at key_key '_y', the subtable's key_keys are called '_y_0' through '_y_z-1'. |
79 | + If a table is an array, the map 'keys' will contain no mappings for the array's key_keys. |
80 | +*/ |
81 | +static void save_table_recursively(lua_State* L, std::string depth, std::map<std::string, const char*> *data, |
82 | +std::map<std::string, const char*> *keys, std::map<std::string, const char*> *type, std::map<std::string, uint32_t> *size) { |
83 | + lua_pushnil(L); |
84 | + uint32_t i = 0; |
85 | + while (lua_next(L, -2) != 0) { |
86 | + std::string key_key = depth + "_" + std::to_string(i); |
87 | + |
88 | + // check the value's type |
89 | + const char* type_name = lua_typename(L, lua_type(L, -1)); |
90 | + std::string t = std::string(type_name); |
91 | + |
92 | + (*type)[key_key] = type_name; |
93 | + |
94 | + if (t == "number" || t == "string") { |
95 | + // numbers may be treated like strings here |
96 | + (*data)[key_key] = luaL_checkstring(L, -1); |
97 | + } else if (t == "boolean") { |
98 | + (*data)[key_key] = luaL_checkboolean(L, -1) ? "true" : "false"; |
99 | + } else if (t == "table") { |
100 | + save_table_recursively(L, depth + "_" + std::to_string(i), data, keys, type, size); |
101 | + } else { |
102 | + report_error(L, "A campaign data value may be a string, integer, boolean, or table; but not a %s!", type_name); |
103 | + } |
104 | + |
105 | + ++i; |
106 | + |
107 | + // put the key on the stack top |
108 | + lua_pop(L, 1); |
109 | + if (lua_type(L, -1) == LUA_TSTRING) { |
110 | + // this is a table |
111 | + (*keys)[key_key] = luaL_checkstring(L, -1); |
112 | + } else if (lua_type(L, -1) == LUA_TNUMBER) { |
113 | + // this is an array |
114 | + if (i != luaL_checkuint32(L, -1)) { |
115 | + // If we get here, the scripter must have set some array values to nil. |
116 | + // This is forbidden because it causes problems when trying to read the data later. |
117 | + report_error(L, "A campaign data array entry must not be nil!"); |
118 | + } |
119 | + // otherwise, this is a normal array, so all is well |
120 | + } else { |
121 | + report_error(L, "A campaign data key may be a string or integer; but not a %s!", |
122 | + lua_typename(L, lua_type(L, -1))); |
123 | + } |
124 | + } |
125 | + (*size)[depth] = i; |
126 | +} |
127 | + |
128 | +/* RST |
129 | + .. function:: save_campaign_data(campaign_name, scenario_name, data) |
130 | + |
131 | + :arg campaign_name: the name of the current campaign, e.g. "empiretut" or "frisians" |
132 | + :arg scenario_name: the name of the current scenario, e.g. "emp04" or "fri03" |
133 | + :arg data: a table of key-value pairs to save |
134 | + |
135 | + Saves information that can be read by other scenarios. |
136 | + |
137 | + If an array is used, the data will be saved in the correct order. Arrays may not contain nil values. |
138 | + If the table is not an array, all keys have to be strings. |
139 | + Tables may contain subtables of any depth. Cyclic dependencies will cause Widelands to crash. |
140 | + Only tables/arrays, strings, integer numbers and booleans may be used as values. |
141 | +*/ |
142 | +int LuaEditorGameBase::save_campaign_data(lua_State* L) { |
143 | + |
144 | + const std::string campaign_name = luaL_checkstring(L, 2); |
145 | + const std::string scenario_name = luaL_checkstring(L, 3); |
146 | + luaL_checktype(L, 4, LUA_TTABLE); |
147 | + |
148 | + std::string dir = kCampaignDataDir + g_fs->file_separator() + campaign_name; |
149 | + boost::trim(dir); |
150 | + g_fs->ensure_directory_exists(dir); |
151 | + |
152 | + std::string complete_filename = dir + g_fs->file_separator() + scenario_name + kCampaignDataExtension; |
153 | + boost::trim(complete_filename); |
154 | + |
155 | + std::map<std::string, const char*> data; |
156 | + std::map<std::string, const char*> keys; |
157 | + std::map<std::string, const char*> type; |
158 | + std::map<std::string, uint32_t> size; |
159 | + |
160 | + save_table_recursively(L, "", &data, &keys, &type, &size); |
161 | + |
162 | + Profile profile; |
163 | + Section& data_section = profile.create_section("data"); |
164 | + for (const auto &p : data) { |
165 | + data_section.set_string(p.first.c_str(), p.second); |
166 | + } |
167 | + Section& keys_section = profile.create_section("keys"); |
168 | + for (const auto &p : keys) { |
169 | + keys_section.set_string(p.first.c_str(), p.second); |
170 | + } |
171 | + Section& type_section = profile.create_section("type"); |
172 | + for (const auto &p : type) { |
173 | + type_section.set_string(p.first.c_str(), p.second); |
174 | + } |
175 | + Section& size_section = profile.create_section("size"); |
176 | + for (const auto &p : size) { |
177 | + size_section.set_natural(p.first.c_str(), p.second); |
178 | + } |
179 | + |
180 | + profile.write(complete_filename.c_str(), false); |
181 | + |
182 | + return 0; |
183 | +} |
184 | + |
185 | +/* Helper function for read_campaign_data() |
186 | + |
187 | + This function reads the campaign data file and re-creates the table the data was created from. |
188 | + This function is recursive so subtables to any depth can be created. |
189 | + For information on section structure and key_keys, see the comment for save_table_recursively(). |
190 | + This function first newly creates the table to write data to, and the number of items in the table is read. |
191 | + For each item, the unique key_key is created. If the 'keys' section doesn't contain an entry for that key_key, |
192 | + it must be because this table is supposed to be an array. Then the data type is checked |
193 | + and the key-value pair is written to the table as the correct type. |
194 | +*/ |
195 | +static void push_table_recursively(lua_State* L, std::string depth, Section* data_section, Section* keys_section, |
196 | +Section* type_section, Section* size_section) { |
197 | + const uint32_t size = size_section->get_natural(depth.c_str()); |
198 | + lua_newtable(L); |
199 | + for (uint32_t i = 0; i < size; i++) { |
200 | + std::string key_key_str(depth + '_' + std::to_string(i)); |
201 | + const char* key_key = key_key_str.c_str(); |
202 | + |
203 | + log("Checking whether a key for '%s' (data type is %s) exists ... ", |
204 | + key_key, type_section->get_string(key_key)); // NOCOM remove this log |
205 | + if (keys_section->has_val(key_key)) { |
206 | + // this is a table |
207 | + log("YES, the key is called '%s'.\n", keys_section->get_string(key_key)); // NOCOM remove this log |
208 | + lua_pushstring(L, keys_section->get_string(key_key)); |
209 | + } |
210 | + else { |
211 | + // this must be an array |
212 | + log("NO, [%i] will be used as key.\n", i + 1); // NOCOM remove this log |
213 | + lua_pushinteger(L, i + 1); |
214 | + } |
215 | + |
216 | + // check the data type and push the value |
217 | + const std::string type = type_section->get_string(key_key); |
218 | + |
219 | + if (type == "boolean") { |
220 | + lua_pushboolean(L, data_section->get_bool(key_key)); |
221 | + } else if (type == "number") { |
222 | + lua_pushinteger(L, data_section->get_int(key_key)); |
223 | + } else if (type == "string") { |
224 | + lua_pushstring(L, data_section->get_string(key_key)); |
225 | + } else if (type == "table") { |
226 | + // creates a new (sub-)table at the stacktop, populated with its own key-value-pairs |
227 | + push_table_recursively(L, depth + "_" + std::to_string(i), |
228 | + data_section, keys_section, type_section, size_section); |
229 | + } else { |
230 | + // this code should not be reached unless the user manually edited the .wcd file |
231 | + log("Illegal data type %s in campaign data file, setting key %s to nil\n", |
232 | + type.c_str(), luaL_checkstring(L, -1)); |
233 | + lua_pushnil(L); |
234 | + } |
235 | + lua_settable(L, -3); |
236 | + } |
237 | +} |
238 | + |
239 | +/* RST |
240 | + .. function:: read_campaign_data(campaign_name, scenario_name) |
241 | + |
242 | + :arg campaign_name: the name of the campaign, e.g. "empiretut" or "frisians" |
243 | + :arg scenario_name: the name of the scenario that saved the data, e.g. "emp04" or "fri03" |
244 | + |
245 | + Reads information that was saved by another scenario. |
246 | + The data is returned as a table of key-value pairs. |
247 | + The table is not guaranteed to be in any particular order, unless it is an array, |
248 | + in which case it will be returned in the same order as it was saved. |
249 | + This function returns :const:`nil` if the file cannot be opened for reading. |
250 | +*/ |
251 | +int LuaEditorGameBase::read_campaign_data(lua_State* L) { |
252 | + const std::string campaign_name = luaL_checkstring(L, 2); |
253 | + const std::string scenario_name = luaL_checkstring(L, 3); |
254 | + |
255 | + std::string complete_filename = kCampaignDataDir + g_fs->file_separator() + campaign_name + |
256 | + g_fs->file_separator() + scenario_name + kCampaignDataExtension; |
257 | + boost::trim(complete_filename); |
258 | + |
259 | + Profile profile; |
260 | + profile.read(complete_filename.c_str()); |
261 | + Section* data_section = profile.get_section("data"); |
262 | + Section* keys_section = profile.get_section("keys"); |
263 | + Section* type_section = profile.get_section("type"); |
264 | + Section* size_section = profile.get_section("size"); |
265 | + if (data_section == nullptr || keys_section == nullptr || type_section == nullptr || size_section == nullptr) { |
266 | + log("Unable to read campaign data file, returning nil\n"); |
267 | + lua_pushnil(L); |
268 | + } |
269 | + else { |
270 | + push_table_recursively(L, "", data_section, keys_section, type_section, size_section); |
271 | + } |
272 | + |
273 | + return 1; |
274 | +} |
275 | + |
276 | /* |
277 | ========================================================== |
278 | C METHODS |
279 | |
280 | === modified file 'src/scripting/lua_bases.h' |
281 | --- src/scripting/lua_bases.h 2018-04-07 16:59:00 +0000 |
282 | +++ src/scripting/lua_bases.h 2018-05-31 18:15:43 +0000 |
283 | @@ -68,6 +68,8 @@ |
284 | int get_worker_description(lua_State* L); |
285 | int get_resource_description(lua_State* L); |
286 | int get_terrain_description(lua_State* L); |
287 | + int save_campaign_data(lua_State* L); |
288 | + int read_campaign_data(lua_State* L); |
289 | |
290 | /* |
291 | * C methods |
292 | |
293 | === added file 'test/maps/plain.wmf/scripting/test_campaign_data.lua' |
294 | --- test/maps/plain.wmf/scripting/test_campaign_data.lua 1970-01-01 00:00:00 +0000 |
295 | +++ test/maps/plain.wmf/scripting/test_campaign_data.lua 2018-05-31 18:15:43 +0000 |
296 | @@ -0,0 +1,77 @@ |
297 | +-- Test writing and reading of campaign data |
298 | + |
299 | +run(function() |
300 | + sleep(5000) |
301 | + |
302 | + local game = wl.Game() |
303 | + |
304 | + print("Writing campaign data...") |
305 | + game:save_campaign_data("test_suite_campaign_data", "test_suite_campaign_data", { |
306 | + abc = "Hello", |
307 | + d = 8, |
308 | + efg = "World", |
309 | + h = -999, |
310 | + i = true, |
311 | + jklmno = 0, |
312 | + pq = "Hello", |
313 | + rst = { |
314 | + false, |
315 | + "abc", |
316 | + -1, |
317 | + 20, |
318 | + { hello = "World" } |
319 | + }, |
320 | + uv = 999, |
321 | + wxyz = false |
322 | + }) |
323 | + print("Done.") |
324 | + |
325 | + print("Reading campaign data...") |
326 | + local result = game:read_campaign_data("test_suite_campaign_data", "test_suite_campaign_data") |
327 | + assert_equal("string", type(result.abc)) |
328 | + assert_equal("Hello", result.abc) |
329 | + |
330 | + assert_equal("number", type(result.d)) |
331 | + assert_equal(8, result.d) |
332 | + |
333 | + assert_equal("string", type(result.efg)) |
334 | + assert_equal("World", result.efg) |
335 | + |
336 | + assert_equal("number", type(result.h)) |
337 | + assert_equal(-999, result.h) |
338 | + |
339 | + assert_equal("boolean", type(result.i)) |
340 | + assert_equal(true, result.i) |
341 | + |
342 | + assert_equal("number", type(result.jklmno)) |
343 | + assert_equal(0, result.jklmno) |
344 | + |
345 | + assert_equal("string", type(result.pq)) |
346 | + assert_equal("Hello", result.pq) |
347 | + |
348 | + assert_equal("number", type(result.uv)) |
349 | + assert_equal(999, result.uv) |
350 | + |
351 | + assert_equal("boolean", type(result.wxyz)) |
352 | + assert_equal(false, result.wxyz) |
353 | + |
354 | + assert_equal("boolean", type(result.rst[1])) |
355 | + assert_equal(false, result.rst[1]) |
356 | + |
357 | + assert_equal("string", type(result.rst[2])) |
358 | + assert_equal("abc", result.rst[2]) |
359 | + |
360 | + assert_equal("number", type(result.rst[3])) |
361 | + assert_equal(-1, result.rst[3]) |
362 | + |
363 | + assert_equal("number", type(result.rst[4])) |
364 | + assert_equal(20, result.rst[4]) |
365 | + |
366 | + assert_equal("string", type(result.rst[5].hello)) |
367 | + assert_equal("World", result.rst[5].hello) |
368 | + |
369 | + print("Done.") |
370 | + |
371 | + print("# All Tests passed.") |
372 | + wl.ui.MapView():close() |
373 | +end) |
I think this would benefit greatly from having proper data structures rather than using a string
https:/ /wl.widelands. org/forum/ topic/4228/ ?page=2# post-24594
Let me know if you need any help with the Lua tables in the C++ backend - they are a bit tricky and I usually find a similar function and copy/paste myself.