← Back to team overview

widelands-dev team mailing list archive

Re: [Merge] lp:~widelands-dev/widelands/campaign_data into lp:widelands

 

Review: Approve

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.

Diff comments:

> 
> === modified file 'src/scripting/lua_bases.cc'
> --- src/scripting/lua_bases.cc	2018-04-07 16:59:00 +0000
> +++ src/scripting/lua_bases.cc	2018-05-17 12:19:36 +0000
> @@ -319,6 +325,172 @@
>  	return to_lua<LuaMaps::LuaTerrainDescription>(L, new LuaMaps::LuaTerrainDescription(descr));
>  }
>  
> +void save_table_recursively(lua_State* L, std::string depth, std::map<std::string, const char*> *data,
> +std::map<std::string, const char*> *keys, std::map<std::string, const char*> *type, std::map<std::string, uint32_t> *size) {
> +	lua_pushnil(L);
> +	uint32_t i = 0;
> +	while (lua_next(L, -2) != 0) {
> +		std::string key_key = depth + "_" + std::to_string(i);
> +
> +		const char* type_name = lua_typename(L, lua_type(L, -1));
> +		std::string t = std::string(type_name);
> +
> +		(*type)[key_key] = type_name;
> +
> +		if (t == "number" || t == "string") {
> +			(*data)[key_key] = luaL_checkstring(L, -1);
> +		} else if (t == "boolean") {
> +			(*data)[key_key] = luaL_checkboolean(L, -1) ? "true" : "false";
> +		} else if (t == "table") {
> +			save_table_recursively(L, depth + "_" + std::to_string(i), data, keys, type, size);

Uhm, should we put some limit to this recursion?
OTOH if this happens some LUA-code is fataly broken.

> +		} else {
> +			report_error(L, "A campaign data value may be a string, integer, boolean, or table; but not a %s!", type_name);
> +		}
> +
> +		++i;
> +
> +		lua_pop(L, 1);
> +		if (lua_type(L, -1) == LUA_TSTRING) {
> +			(*keys)[key_key] = luaL_checkstring(L, -1);
> +		} else if (lua_type(L, -1) == LUA_TNUMBER) {
> +			if (i != luaL_checkuint32(L, -1)) {
> +				report_error(L, "A campaign data array entry must not be nil!");
> +			}
> +		} else {
> +			report_error(L, "A campaign data key may be a string or integer; but not a %s!",
> +					lua_typename(L, lua_type(L, -1)));
> +		}
> +	}
> +	(*size)[depth] = i;
> +}
> +
> +/* RST
> +   .. function:: save_campaign_data(campaign_name, scenario_name, data)
> +
> +      :arg campaign_name: the name of the current campaign, e.g. "empiretut" or "frisians"
> +      :arg scenario_name: the name of the current scenario, e.g. "emp04" or "fri03"
> +      :arg data: a table of key-value pairs to save
> +
> +      Saves information that can be read by other scenarios.
> +
> +      If an array is used, the data will be saved in the correct order. Arrays may not contain nil values.
> +      If the table is not an array, all keys have to be strings.
> +      Tables may contain subtables of any depth. Cyclic dependencies will cause Widelands to crash.
> +      Only tables/arrays, strings, integer numbers and booleans may be used as values.
> +*/
> +int LuaEditorGameBase::save_campaign_data(lua_State* L) {
> +
> +	const std::string campaign_name = luaL_checkstring(L, 2);
> +	const std::string scenario_name = luaL_checkstring(L, 3);
> +	luaL_checktype(L, 4, LUA_TTABLE);
> +
> +	std::string dir = kCampaignDataDir + g_fs->file_separator() + campaign_name;
> +	boost::trim(dir);
> +	g_fs->ensure_directory_exists(dir);
> +
> +	std::string complete_filename = dir + g_fs->file_separator() + scenario_name + kCampaignDataExtension;
> +	boost::trim(complete_filename);
> +
> +	std::map<std::string, const char*> data;
> +	std::map<std::string, const char*> keys;
> +	std::map<std::string, const char*> type;
> +	std::map<std::string, uint32_t> size;
> +
> +	save_table_recursively(L, "", &data, &keys, &type, &size);
> +
> +	Profile profile;
> +	Section& data_section = profile.create_section("data");
> +	for (const auto &p : data) {
> +		data_section.set_string(p.first.c_str(), p.second);
> +	}
> +	Section& keys_section = profile.create_section("keys");
> +	for (const auto &p : keys) {
> +		keys_section.set_string(p.first.c_str(), p.second);
> +	}
> +	Section& type_section = profile.create_section("type");
> +	for (const auto &p : type) {
> +		type_section.set_string(p.first.c_str(), p.second);
> +	}
> +	Section& size_section = profile.create_section("size");
> +	for (const auto &p : size) {
> +		size_section.set_natural(p.first.c_str(), p.second);
> +	}
> +
> +	profile.write(complete_filename.c_str(), false);
> +
> +	return 0;
> +}
> +
> +void push_table_recursively(lua_State* L, std::string depth, Section* data_section, Section* keys_section,
> +Section* type_section, Section* size_section) {
> +	const uint32_t size = size_section->get_natural(depth.c_str());
> +	lua_newtable(L);
> +	for (uint32_t i = 0; i < size; i++) {
> +		const char* key_key = (depth + "_" + std::to_string(i)).c_str();
> +
> +		if (keys_section->has_val(key_key)) {
> +			lua_pushstring(L, keys_section->get_string(key_key));
> +		}
> +		else {
> +			lua_pushinteger(L, i + 1);
> +		}
> +		const std::string type = type_section->get_string(key_key);
> +
> +		if (type == "boolean") {
> +			lua_pushboolean(L, data_section->get_bool(key_key));
> +		} else if (type == "number") {
> +			lua_pushinteger(L, data_section->get_int(key_key));
> +		} else if (type == "string") {
> +			lua_pushstring(L, data_section->get_string(key_key));
> +		} else if (type == "table") {
> +			push_table_recursively(L, depth + "_" + std::to_string(i),
> +					data_section, keys_section, type_section, size_section);
> +		} else {
> +			log("Illegal data type %s in campaign data file, interpreting key %s as nil\n",
> +					type.c_str(), luaL_checkstring(L, -1));
> +			lua_pushnil(L);
> +		}
> +		lua_settable(L, -3);
> +	}
> +}
> +
> +/* RST
> +   .. function:: read_campaign_data(campaign_name, scenario_name)
> +
> +      :arg campaign_name: the name of the campaign, e.g. "empiretut" or "frisians"
> +      :arg scenario_name: the name of the scenario that saved the data, e.g. "emp04" or "fri03"
> +
> +      Reads information that was saved by another scenario.
> +      The data is returned as a table of key-value pairs.
> +      The table is not guaranteed to be in any particular order, unless it is an array,
> +      in which case it will be returned in the same order as it was saved.
> +      This function returns :const:`nil` if the file cannot be opened for reading.
> +*/
> +int LuaEditorGameBase::read_campaign_data(lua_State* L) {
> +	const std::string campaign_name = luaL_checkstring(L, 2);
> +	const std::string scenario_name = luaL_checkstring(L, 3);
> +
> +	std::string complete_filename = kCampaignDataDir + g_fs->file_separator() + campaign_name +
> +			g_fs->file_separator() + scenario_name + kCampaignDataExtension;
> +	boost::trim(complete_filename);
> +
> +	Profile profile;
> +	profile.read(complete_filename.c_str());
> +	Section* data_section = profile.get_section("data");
> +	Section* keys_section = profile.get_section("keys");
> +	Section* type_section = profile.get_section("type");
> +	Section* size_section = profile.get_section("size");
> +	if (data_section == nullptr || keys_section == nullptr || type_section == nullptr || size_section == nullptr) {
> +		log("Unable to read campaign data file, returning nil\n");
> +		lua_pushnil(L);
> +	}
> +	else {
> +		push_table_recursively(L, "", data_section, keys_section, type_section, size_section);
> +	}
> +
> +	return 1;
> +}
> +
>  /*
>   ==========================================================
>   C METHODS


-- 
https://code.launchpad.net/~widelands-dev/widelands/campaign_data/+merge/343783
Your team Widelands Developers is subscribed to branch lp:~widelands-dev/widelands/campaign_data.


Follow ups

References