void user_choice_manager::pull() { // there might be speak or similar commands in the replay before the user input. do_replay_handle(); synced_context::pull_remote_user_input(); do_replay_handle(); update_local_choice(); search_in_replay(); }
user_choice_manager::user_choice_manager(const std::string &name, const mp_sync::user_choice &uch, std::set<int> sides) : required_(sides) , res_() , local_choice_(0) , wait_message_() , oos_(false) , uch_(uch) , tagname_(name) , current_side_(resources::controller->current_side()) , changed_event_("user_choice_update") { update_local_choice(); const int max_side = static_cast<int>(resources::gameboard->teams().size()); for(int side : required_) { assert(1 <= side && side <= max_side); const team& t = resources::gameboard->teams()[side-1]; assert(!t.is_empty()); if(side != current_side_) { synced_context::set_is_simultaneously(); } } do_replay_handle(); search_in_replay(); }
void user_choice_manager::search_in_replay() { while(!finished() && !oos_) { do_replay_handle(); if(resources::recorder->at_end()) { return; } DBG_REPLAY << "MP synchronization: extracting choice from replay with has_local_side=" << has_local_choice() << "\n"; const config *action = resources::recorder->get_next_action(); assert(action); //action cannot be null because resources::recorder->at_end() returned false. if( !action->has_child(tagname_) || !(*action)["dependent"].to_bool()) { replay::process_error("[" + tagname_ + "] expected but none found\n. found instead:\n" + action->debug()); //We save this action for later resources::recorder->revert_action(); // execute this local choice locally oos_ = true; changed_event_.notify_observers(); return; } int from_side = (*action)["from_side"].to_int(0); if((*action)["side_invalid"].to_bool(false) == true) { //since this 'cheat' can have a quite heavy effect especialy in umc content we give an oos error . replay::process_error("MP synchronization: side_invalid in replay data, this could mean someone wants to cheat.\n"); } if(required_.find(from_side) == required_.end()) { replay::process_error("MP synchronization: we got an answer from side " + std::to_string(from_side) + "for [" + tagname_ + "] which is not was we expected\n"); } if(res_.find(from_side) != res_.end()) { replay::process_error("MP synchronization: we got already our answer from side " + std::to_string(from_side) + "for [" + tagname_ + "] now we have it twice.\n"); } res_[from_side] = action->child(tagname_); changed_event_.notify_observers(); } }
config synced_context::ask_server_choice(const server_choice& sch) { if (!is_synced()) { ERR_REPLAY << "Trying to ask the server for a '" << sch.name() << "' choice in a unsynced context, doing the choice locally. This can cause OOS.\n"; return sch.local_choice(); } set_is_simultaneously(); resources::controller->increase_server_request_number(); const bool is_mp_game = resources::controller->is_networked_mp(); bool did_require = false; DBG_REPLAY << "ask_server for random_seed\n"; /* as soon as random or similar is involved, undoing is impossible. */ resources::undo_stack->clear(); /* there might be speak or similar commands in the replay before the user input. */ while(true) { do_replay_handle(); bool is_replay_end = resources::recorder->at_end(); if (is_replay_end && !is_mp_game) { /* The decision is ours, and it will be inserted into the replay. */ DBG_REPLAY << "MP synchronization: local server choice\n"; leave_synced_context sync; config cfg = sch.local_choice(); //-1 for "server" todo: change that. resources::recorder->user_input(sch.name(), cfg, -1); return cfg; } else if(is_replay_end && is_mp_game) { DBG_REPLAY << "MP synchronization: remote server choice\n"; //here we can get into the situation that the decision has already been made but not received yet. synced_context::pull_remote_user_input(); //FIXME: we should call play_controller::play_silce or the application will freeze while waiting for a remote choice. resources::controller->play_slice(); /* we don't want to send multiple "require_random" to the server. */ if(!did_require) { sch.send_request(); did_require = true; } SDL_Delay(10); continue; } else if (!is_replay_end) { /* The decision has already been made, and must be extracted from the replay. */ DBG_REPLAY << "MP synchronization: replay server choice\n"; do_replay_handle(); const config *action = resources::recorder->get_next_action(); if (!action) { replay::process_error("[" + std::string(sch.name()) + "] expected but none found\n"); resources::recorder->revert_action(); return sch.local_choice(); } if (!action->has_child(sch.name())) { replay::process_error("[" + std::string(sch.name()) + "] expected but none found, found instead:\n " + action->debug() + "\n"); resources::recorder->revert_action(); return sch.local_choice(); } if((*action)["from_side"].str() != "server" || (*action)["side_invalid"].to_bool(false) ) { //we can proceed without getting OOS in this case, but allowing this would allow a "player chan choose their attack results in mp" cheat replay::process_error("wrong from_side or side_invalid this could mean someone wants to cheat\n"); } return action->child(sch.name()); } } }