Added rand= for [set_variable] which can be used in MP,
the user has to make sure it stays in sync.
This commit is contained in:
parent
e171ebfa40
commit
06d19f9a4f
9 changed files with 229 additions and 18 deletions
|
@ -39,6 +39,8 @@ Version 1.3.12+svn:
|
|||
* updated {GENERIC_UNIT ...} macro to take advantage of random_gender
|
||||
* fixed [filter_radius] to support [and][or][not] and radius=
|
||||
* undoing recruits is no longer allowed
|
||||
* added rand= for [set_variable] which can be used in MP, the user has to
|
||||
make sure it stays in sync
|
||||
* miscellaneous and bug fixes:
|
||||
* various bug fixes and code cleanups
|
||||
* added gzip and gunzip command line parameters
|
||||
|
|
13
src/game.cpp
13
src/game.cpp
|
@ -755,6 +755,16 @@ bool game_controller::load_game()
|
|||
}
|
||||
}
|
||||
|
||||
// Get the status of the random in the snapshot.
|
||||
// For a replay we need to restore the start only, the replaying gets at
|
||||
// proper location.
|
||||
// For normal loading also restore the call count.
|
||||
const int seed = lexical_cast_default<int>
|
||||
(cfg["random_seed"], 42);
|
||||
const unsigned calls = show_replay ? 0 :
|
||||
lexical_cast_default<unsigned> (state_.snapshot["random_calls"]);
|
||||
state_.seed_random(seed, calls);
|
||||
|
||||
} catch(game::error& e) {
|
||||
gui::show_error_message(disp(), _("The file you have tried to load is corrupt: '") + e.message + '\'');
|
||||
return false;
|
||||
|
@ -819,6 +829,7 @@ void game_controller::set_tutorial()
|
|||
state_.campaign_define = "TUTORIAL";
|
||||
reset_defines_map();
|
||||
defines_map_["TUTORIAL"] = preproc_define();
|
||||
|
||||
}
|
||||
|
||||
bool game_controller::new_campaign()
|
||||
|
@ -2041,7 +2052,7 @@ static int play_game(int argc, char** argv)
|
|||
return remove(input_file.c_str());
|
||||
|
||||
} catch(io_exception& e) {
|
||||
std::cerr << "IO error: " << e.what() << "\n";
|
||||
std::cerr << "IO error: " << e.what() << "\n";
|
||||
}
|
||||
|
||||
} else if(val == "--gunzip") {
|
||||
|
|
|
@ -1080,6 +1080,12 @@ bool event_handler::handle_event_command(const queued_event& event_info,
|
|||
// range (i.e. -1..-10, 0..100, -10..10, etc).
|
||||
const std::string random = cfg["random"];
|
||||
if(random.empty() == false) {
|
||||
// random is deprecated but will be available in the 1.4 branch
|
||||
// so enable the message after forking
|
||||
//! @todo Enable after branching and once rand works fully in MP
|
||||
//! including synchronizing.
|
||||
//lg::wml_error << "Usage of 'random' is deprecated use 'rand' instead, "
|
||||
// "support will be removed in 1.5.2.\n";
|
||||
std::string random_value;
|
||||
// If we're not replaying, create a random number
|
||||
if(get_replay_source().at_end()) {
|
||||
|
@ -1144,7 +1150,6 @@ bool event_handler::handle_event_command(const queued_event& event_info,
|
|||
break;
|
||||
}
|
||||
}
|
||||
|
||||
recorder.set_random_value(random_value.c_str());
|
||||
}
|
||||
|
||||
|
@ -1160,6 +1165,79 @@ bool event_handler::handle_event_command(const queued_event& event_info,
|
|||
}
|
||||
var = random_value;
|
||||
}
|
||||
|
||||
// The new random generator, the logic is a copy paste of the old random.
|
||||
const std::string rand = cfg["rand"];
|
||||
if(rand.empty() == false) {
|
||||
assert(state_of_game);
|
||||
|
||||
std::string random_value;
|
||||
|
||||
std::string word;
|
||||
std::vector<std::string> words;
|
||||
std::vector<std::pair<long,long> > ranges;
|
||||
int num_choices = 0;
|
||||
std::string::size_type pos = 0, pos2 = std::string::npos;
|
||||
std::stringstream ss(std::stringstream::in|std::stringstream::out);
|
||||
while (pos2 != rand.length()) {
|
||||
pos = pos2+1;
|
||||
pos2 = rand.find(",", pos);
|
||||
|
||||
if (pos2 == std::string::npos)
|
||||
pos2 = rand.length();
|
||||
|
||||
word = rand.substr(pos, pos2-pos);
|
||||
words.push_back(word);
|
||||
std::string::size_type tmp = word.find("..");
|
||||
|
||||
|
||||
if (tmp == std::string::npos) {
|
||||
// Treat this element as a string
|
||||
ranges.push_back(std::pair<int, int>(0,0));
|
||||
num_choices += 1;
|
||||
}
|
||||
else {
|
||||
// Treat as a numerical range
|
||||
const std::string first = word.substr(0, tmp);
|
||||
const std::string second = word.substr(tmp+2,
|
||||
rand.length());
|
||||
|
||||
long low, high;
|
||||
ss << first + " " + second;
|
||||
ss >> low;
|
||||
ss >> high;
|
||||
ss.clear();
|
||||
|
||||
if (low > high) {
|
||||
tmp = low;
|
||||
low = high;
|
||||
high = tmp;
|
||||
}
|
||||
ranges.push_back(std::pair<long, long>(low,high));
|
||||
num_choices += (high - low) + 1;
|
||||
}
|
||||
}
|
||||
|
||||
int choice = state_of_game->get_random() % num_choices;
|
||||
int tmp = 0;
|
||||
for(size_t i = 0; i < ranges.size(); i++) {
|
||||
tmp += (ranges[i].second - ranges[i].first) + 1;
|
||||
if (tmp > choice) {
|
||||
if (ranges[i].first == 0 && ranges[i].second == 0) {
|
||||
random_value = words[i];
|
||||
}
|
||||
else {
|
||||
tmp = (ranges[i].second - (tmp - choice)) + 1;
|
||||
ss << tmp;
|
||||
ss >> random_value;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var = random_value;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Conditional statements
|
||||
|
|
|
@ -447,30 +447,36 @@ game_state::game_state(const game_data& data, const config& cfg) :
|
|||
snapshot(),
|
||||
last_selected(gamemap::location::null_location),
|
||||
variables(),
|
||||
temporaries()
|
||||
temporaries(),
|
||||
random_seed_(lexical_cast<int>(cfg["random_seed"])),
|
||||
random_pool_(random_seed_),
|
||||
random_calls_(0)
|
||||
{
|
||||
log_scope("read_game");
|
||||
|
||||
const config* snapshot = cfg.child("snapshot");
|
||||
|
||||
if (snapshot != NULL){
|
||||
if (snapshot != NULL){
|
||||
|
||||
this->snapshot = *snapshot;
|
||||
|
||||
const config::child_list& players = snapshot->get_children("player");
|
||||
seed_random(random_seed_, lexical_cast_default<unsigned>((*snapshot)["random_calls"]));
|
||||
|
||||
const config::child_list& players = snapshot->get_children("player");
|
||||
|
||||
if(!players.empty()) {
|
||||
for(config::child_list::const_iterator i = players.begin(); i != players.end(); ++i) {
|
||||
std::string save_id = (**i)["save_id"];
|
||||
if(!players.empty()) {
|
||||
for(config::child_list::const_iterator i = players.begin(); i != players.end(); ++i) {
|
||||
std::string save_id = (**i)["save_id"];
|
||||
|
||||
if(save_id.empty()) {
|
||||
std::cerr << "Corrupted player entry: NULL save_id" << std::endl;
|
||||
} else {
|
||||
player_info player = read_player(data, *i);
|
||||
this->players.insert(std::pair<std::string, player_info>(save_id,player));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if(save_id.empty()) {
|
||||
std::cerr << "Corrupted player entry: NULL save_id" << std::endl;
|
||||
} else {
|
||||
player_info player = read_player(data, *i);
|
||||
this->players.insert(std::pair<std::string, player_info>(save_id,player));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::cerr << "scenario: '" << scenario << "'\n";
|
||||
std::cerr << "next_scenario: '" << next_scenario << "'\n";
|
||||
|
@ -588,6 +594,9 @@ void write_game(const game_state& gamestate, config& cfg, WRITE_GAME_MODE mode)
|
|||
cfg["campaign_define"] = gamestate.campaign_define;
|
||||
cfg["campaign_extra_defines"] = utils::join(gamestate.campaign_xtra_defines);
|
||||
|
||||
cfg["random_seed"] = lexical_cast<std::string>(gamestate.get_random_seed());
|
||||
cfg["random_calls"] = lexical_cast<std::string>(gamestate.get_random_calls());
|
||||
|
||||
cfg.add_child("variables",gamestate.get_variables());
|
||||
|
||||
for(std::map<std::string, wml_menu_item *>::const_iterator j=gamestate.wml_menu_items.begin();
|
||||
|
@ -640,7 +649,10 @@ void write_game(config_writer &out, const game_state& gamestate, WRITE_GAME_MODE
|
|||
out.write_key_val("difficulty", gamestate.difficulty);
|
||||
out.write_key_val("campaign_define", gamestate.campaign_define);
|
||||
out.write_key_val("campaign_extra_defines", utils::join(gamestate.campaign_xtra_defines));
|
||||
out.write_key_val("random_seed", lexical_cast<std::string>(gamestate.get_random_seed()));
|
||||
out.write_key_val("random_calls", lexical_cast<std::string>(gamestate.get_random_calls()));
|
||||
out.write_child("variables", gamestate.get_variables());
|
||||
|
||||
for(std::map<std::string, wml_menu_item *>::const_iterator j=gamestate.wml_menu_items.begin();
|
||||
j!=gamestate.wml_menu_items.end(); ++j) {
|
||||
out.open_child("menu_item");
|
||||
|
@ -1095,6 +1107,44 @@ void game_state::clear_variable(const std::string& varname)
|
|||
}
|
||||
}
|
||||
|
||||
//! Get a new random number.
|
||||
int game_state::get_random()
|
||||
{
|
||||
random_next();
|
||||
++random_calls_;
|
||||
DBG_NG << "pulled user random " << random_pool_
|
||||
<< " for call " << random_calls_ << '\n';
|
||||
|
||||
return (static_cast<unsigned>(random_pool_ / 65536) % 32768);
|
||||
}
|
||||
|
||||
//! Seeds the random pool.
|
||||
//!
|
||||
//! @param seed The initial value for the random engine.
|
||||
//! @param call_count Upon loading we need to restore the state at saving
|
||||
//! so set the number of times a random number is generated
|
||||
//! for replays the orginal value is required.
|
||||
void game_state::seed_random(const int seed, const unsigned call_count)
|
||||
{
|
||||
random_pool_ = seed;
|
||||
random_seed_ = seed;
|
||||
for(random_calls_ = 0; random_calls_ < call_count; ++random_calls_) {
|
||||
random_next();
|
||||
}
|
||||
DBG_NG << "Seeded random with " << random_seed_ << " with "
|
||||
<< random_calls_ << " calls, pool is now at "
|
||||
<< random_pool_ << '\n';
|
||||
}
|
||||
|
||||
//! Sets the next random number in the pool.
|
||||
void game_state::random_next()
|
||||
{
|
||||
// Use the simple random generator as shown in man rand(3).
|
||||
// The division is done separately since we also want to
|
||||
// quickly go the the wanted index in the random list.
|
||||
random_pool_ = random_pool_ * 1103515245 + 12345;
|
||||
}
|
||||
|
||||
static void clear_wmi(std::map<std::string, wml_menu_item*>& gs_wmi) {
|
||||
std::map<std::string, wml_menu_item*>::iterator itor = gs_wmi.begin();
|
||||
for(itor = gs_wmi.begin(); itor != gs_wmi.end(); ++itor) {
|
||||
|
@ -1123,6 +1173,9 @@ game_state& game_state::operator=(const game_state& state)
|
|||
campaign = state.campaign;
|
||||
scenario = state.scenario;
|
||||
completion = state.completion;
|
||||
random_seed_ = state.random_seed_;
|
||||
random_pool_ = state.random_pool_;
|
||||
random_calls_ = state.random_calls_;
|
||||
players = state.players;
|
||||
scoped_variables = state.scoped_variables;
|
||||
|
||||
|
|
|
@ -113,7 +113,10 @@ public:
|
|||
snapshot(),
|
||||
last_selected(gamemap::location::null_location),
|
||||
variables(),
|
||||
temporaries()
|
||||
temporaries(),
|
||||
random_seed_(rand()),
|
||||
random_pool_(random_seed_),
|
||||
random_calls_(0)
|
||||
{}
|
||||
|
||||
game_state(const game_state& state);
|
||||
|
@ -182,10 +185,39 @@ public:
|
|||
|
||||
//! the last location where a select event fired.
|
||||
gamemap::location last_selected;
|
||||
|
||||
//! Get a new random number.
|
||||
int get_random();
|
||||
|
||||
//! Seeds the random pool.
|
||||
void seed_random(const int seed, const unsigned call_count = 0);
|
||||
|
||||
//! Resets the random to the 0 calls and the seed to the random
|
||||
//! this way we stay in the same sequence but don't have a lot
|
||||
//! calls. Used when moving to the next scenario.
|
||||
void rotate_random()
|
||||
{ random_seed_ = random_pool_; random_calls_ = 0; }
|
||||
|
||||
|
||||
int get_random_seed() const { return random_seed_; }
|
||||
int get_random_calls() const { return random_calls_; }
|
||||
|
||||
private:
|
||||
config variables;
|
||||
mutable config temporaries; // lengths of arrays, etc.
|
||||
friend struct variable_info;
|
||||
|
||||
//! Initial seed for the pool.
|
||||
int random_seed_;
|
||||
|
||||
//! State for the random pool.
|
||||
int random_pool_;
|
||||
|
||||
//! Number of time a random number is generated.
|
||||
unsigned random_calls_;
|
||||
|
||||
//! Sets the next random number in the pool.
|
||||
void random_next();
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -1447,6 +1447,8 @@ void connect::load_game()
|
|||
level_["experience_modifier"] = state_.snapshot["experience_modifier"];
|
||||
level_["observer"] = state_.snapshot["observer"];
|
||||
level_.add_child("snapshot") = state_.snapshot;
|
||||
level_["random_seed"] = state_.snapshot["random_seed"];
|
||||
level_["random_calls"] = state_.snapshot["random_calls"];
|
||||
|
||||
// Adds the replay data, and the replay start, to the level,
|
||||
// so clients can receive it.
|
||||
|
@ -1481,6 +1483,7 @@ void connect::load_game()
|
|||
|
||||
level_["scenario"] = params_.name;
|
||||
level_["experience_modifier"] = lexical_cast<std::string>(params_.xp_modifier);
|
||||
level_["random_seed"] = lexical_cast<std::string>(state_.get_random_seed());
|
||||
}
|
||||
|
||||
const std::string& era = params_.era;
|
||||
|
|
|
@ -31,6 +31,7 @@
|
|||
#include "wml_separators.hpp"
|
||||
|
||||
#define LOG_NG LOG_STREAM(info, engine)
|
||||
#define ERR_NG LOG_STREAM(err, engine)
|
||||
#define ERR_CF LOG_STREAM(err, config)
|
||||
#define DBG_NW LOG_STREAM(debug, network)
|
||||
#define LOG_NW LOG_STREAM(info, network)
|
||||
|
@ -94,6 +95,16 @@ void level_to_gamestate(config& level, game_state& state, bool saved_game)
|
|||
}
|
||||
}
|
||||
|
||||
//set random
|
||||
const std::string seed = level["random_seed"];
|
||||
if(! seed.empty()) {
|
||||
const unsigned calls = lexical_cast_default<unsigned>(level["random_calls"]);
|
||||
state.seed_random(lexical_cast<int>(seed), calls);
|
||||
} else {
|
||||
ERR_NG << "No random seed found, random "
|
||||
"events will probably be out of sync.\n";
|
||||
}
|
||||
|
||||
//adds the starting pos to the level
|
||||
if(level.child("replay_start") == NULL){
|
||||
level.add_child("replay_start", level);
|
||||
|
|
|
@ -449,6 +449,7 @@ LEVEL_RESULT play_game(display& disp, game_state& gamestate, const config& game_
|
|||
|
||||
// Switch to the next scenario.
|
||||
gamestate.scenario = gamestate.next_scenario;
|
||||
gamestate.rotate_random();
|
||||
|
||||
if(io_type == IO_CLIENT) {
|
||||
if (!gamestate.next_scenario.empty()){
|
||||
|
|
|
@ -15,6 +15,26 @@
|
|||
|
||||
//! @file random.cpp
|
||||
//! Generate random numbers.
|
||||
//!
|
||||
//! There are various ways to get a random number.
|
||||
//! rand() This can be used for things that never are send over the
|
||||
//! network e.g. generate a random map (the final result the
|
||||
//! map is send, but the other players don't need to generate
|
||||
//! the map.
|
||||
//!
|
||||
//! get_random() A random generator which is syncronized over the network
|
||||
//! this only seems to work when it's used by 1 player at the
|
||||
//! same time. It's syncronized after an event so if an event
|
||||
//! runs at two clients at the same time it gets out of sync
|
||||
//! and sets the entire game out of sync.
|
||||
//!
|
||||
//! game_state::get_random()
|
||||
//! A random generator which is seeded by the host of an MP
|
||||
//! game. This generator is (not yet) synchronized over the
|
||||
//! network. It's only used by [set_variable]rand=. The map
|
||||
//! designer has to make sure it stays in sync. This
|
||||
//! generator can be used at the same time at multiple client
|
||||
//! since the generators are always in sync.
|
||||
|
||||
#include "global.hpp"
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue