[[Game[lay fixes]]

- added randomization of temples in Muff Malal's peninsula
- made it so in MP games, players can receive labels and messages while it's their turn
- added patch to 'show possible enemy moves'
This commit is contained in:
Dave White 2004-04-05 19:29:23 +00:00
parent 20908a5087
commit 5238dcc75a
12 changed files with 303 additions and 130 deletions

View file

@ -125,12 +125,13 @@ Defeat:
[/message]
[/event]
#define MOREMIRMU_TRAP X Y
[event]
name=moveto
[filter]
side=1
x=5
y=7
x={X}
y={Y}
[/filter]
[message]
id=msg4_7
@ -141,8 +142,8 @@ Defeat:
description=Moremirmu
side=1
type=White Mage
x=5
y=7
x={X}
y={Y}
[/unit]
#set the variable to say the Moremirmu is alive
[set_variable]
@ -155,13 +156,15 @@ Defeat:
message="I was hiding in this holy place, planning how to defeat the evil undeads. Now with your help, I can destroy them."
[/message]
[/event]
#enddef
#define XAKAE_TRAP X Y
[event]
name=moveto
[filter]
side=1
x=10
y=10
x={X}
y={Y}
[/filter]
[message]
id=msg4_9
@ -172,27 +175,27 @@ Defeat:
description=Xakae
side=2
type=Revenant
x=10
y=10
x={X}
y={Y}
[/unit]
[unit]
side=2
type=Walking Corpse
x=10
y=10
x={X}
y={Y}
[/unit]
[unit]
side=2
type=Walking Corpse
x=10
y=10
x={X}
y={Y}
[/unit]
#ifdef HARD
[unit]
side=2
type=Walking Corpse
x=10
y=10
x={X}
y={Y}
[/unit]
#endif
[message]
@ -201,6 +204,28 @@ Defeat:
message="Surprise! Searching for Mages, and all I get is elves!"
[/message]
[/event]
#enddef
[event]
name=start
{RANDOM 1..2}
[if]
[variable]
name=random
numerical_equals=1
[/variable]
[then]
{MOREMIRMU_TRAP 5 7}
{XAKAE_TRAP 10 10}
[/then]
[else]
{MOREMIRMU_TRAP 10 10}
{XAKAE_TRAP 5 7}
[/else]
[/if]
[/event]
[event]
name=moveto
[filter]

View file

@ -39,7 +39,7 @@ height=600
[menu]
is_context_menu=true
items=undo,redo,cycle,describeunit,speak,recruit,recall,createunit,renameunit,labelterrain,endturn
items=undo,redo,cycle,describeunit,speak,recruit,recall,createunit,renameunit,labelterrain,endturn,showenemymoves,bestenemymoves
[/menu]
# top panel

View file

@ -114,6 +114,16 @@ language="English"
key=l
ctrl=yes
[/hotkey]
[hotkey]
command=showenemymoves
key=v
ctrl=yes
[/hotkey]
[hotkey]
command=bestenemymoves
key=b
ctrl=yes
[/hotkey]
game_title="The Battle for Wesnoth"
version="Version"
@ -426,6 +436,8 @@ action_recruit="Recruit"
action_repeatrecruit="Repeat Recruit"
action_mute="Mute"
action_labelterrain="Set Label"
action_showenemymoves="Show Enemy Moves"
action_bestenemymoves="Best Possible Enemy Moves"
save_hotkeys_button="Save Hotkeys"
change_hotkey_button="Change Hotkey"
hotkeys_dialog="Hotkey Settings"
@ -494,6 +506,34 @@ attacks="attacks"
damage="damage"
hexes="hexes"
# Weapon special effect descriptions
weapon_special_backstab_description="Backstab:
This attack deals double damage if a friendly unit is on the opposite side of the target."
weapon_special_charge_description="Charge:
This attack deals double damage to the target. It also causes this unit to take double damage from the target's counterattack."
weapon_special_drain_description="Drain:
This unit drains health from living units, healing itself for half the amount of damage it deals."
weapon_special_magical_description="Magical:
This attack always has a 70% chance to hit."
weapon_special_marksman_description="Marksman:
When used offensively, this attack always has at least a 60% chance to hit."
weapon_special_plague_description="Plague:
If this unit kills a living target and that unit was not stationed in a village, the dead target's corpse will rise up and fight for you."
weapon_special_poison_description="Poison:
This attack poisons the target. Poisoned units lose 8 HP every turn until they are cured or are reduced to 1 HP."
weapon_special_slow_description="Slow:
This attack slows the target. Slowed units move at half normal speed and receive one less attack than normal in combat."
weapon_special_stone_description="Stone:
This attack turns the target to stone. Units that have been turned to stone may not move or attack."
#Multiplayer lobby/dialogs
game_lobby="Game Lobby"
shroud="Shroud"

View file

@ -51,3 +51,14 @@ y={Y}
type=cross
[/dot]
#enddef
#macro to quickly pick a random value (in the $random variable, to avoid
#cluterring up savegames with such temporary variables)
#define RANDOM RANGE
[set_variable]
name=random
random={RANGE}
[/set_variable]
#enddef

View file

@ -65,6 +65,8 @@ HOTKEY_COMMAND string_to_command(const std::string& str)
m.insert(val("statistics",HOTKEY_STATISTICS));
m.insert(val("quit",HOTKEY_QUIT_GAME));
m.insert(val("labelterrain",HOTKEY_LABEL_TERRAIN));
m.insert(val("showenemymoves",HOTKEY_SHOW_ENEMY_MOVES));
m.insert(val("bestenemymoves",HOTKEY_BEST_ENEMY_MOVES));
}
const std::map<std::string,HOTKEY_COMMAND>::const_iterator i = m.find(str);
@ -358,6 +360,14 @@ void execute_command(display& disp, HOTKEY_COMMAND command, command_executor* ex
if(executor)
executor->label_terrain();
break;
case HOTKEY_SHOW_ENEMY_MOVES:
if(executor)
executor->show_enemy_moves(false);
break;
case HOTKEY_BEST_ENEMY_MOVES:
if(executor)
executor->show_enemy_moves(true);
break;
case HOTKEY_QUIT_GAME: {
const int res = gui::show_dialog(disp,NULL,"",string_table["quit_message"],gui::YES_NO);
if(res == 0) {

View file

@ -33,7 +33,8 @@ enum HOTKEY_COMMAND { HOTKEY_CYCLE_UNITS, HOTKEY_END_UNIT_TURN, HOTKEY_LEADER,
HOTKEY_TOGGLE_GRID, HOTKEY_STATUS_TABLE, HOTKEY_MUTE,
HOTKEY_SPEAK, HOTKEY_CREATE_UNIT, HOTKEY_PREFERENCES,
HOTKEY_OBJECTIVES, HOTKEY_UNIT_LIST, HOTKEY_STATISTICS, HOTKEY_QUIT_GAME,
HOTKEY_LABEL_TERRAIN, HOTKEY_NULL };
HOTKEY_LABEL_TERRAIN, HOTKEY_SHOW_ENEMY_MOVES, HOTKEY_BEST_ENEMY_MOVES,
HOTKEY_NULL };
struct hotkey_item {
explicit hotkey_item(const config& cfg);
@ -98,6 +99,7 @@ public:
virtual void unit_list() = 0;
virtual void show_statistics() = 0;
virtual void label_terrain() = 0;
virtual void show_enemy_moves(bool ignore_units) = 0;
virtual bool can_execute_command(HOTKEY_COMMAND command) const = 0;
};

View file

@ -67,8 +67,7 @@ void queue_disconnect(connection connection_num);
//received in cfg. Times out after timeout milliseconds. Returns
//the connection that data was received from, or 0 if timeout
//occurred. Throws error if an error occurred.
connection receive_data(config& cfg, connection connection_num=0,
int timeout=0);
connection receive_data(config& cfg, connection connection_num=0, int timeout=0);
//sets the default maximum number of bytes to send to a client at a time
void set_default_send_size(size_t send_size);

View file

@ -459,110 +459,26 @@ redo_turn:
} else if(!replaying && team_it->is_network()) {
std::cerr << "is networked...\n";
bool turn_end = false;
turn_info turn_data(gameinfo,state_of_game,status,
game_config,level,key,gui,
map,teams,player_number,units,true);
while(!turn_end) {
turn_info turn_data(gameinfo,state_of_game,status,
game_config,level,key,gui,
map,teams,player_number,units,true);
for(;;) {
config cfg;
for(;;) {
cfg = config();
network::connection res = network::receive_data(cfg);
const network::connection res = network::receive_data(cfg);
if(res) {
std::cerr << "received network data: '" << cfg.write() << "'\n";
}
if(res && cfg.child("observer") != NULL) {
const config::child_list& observers = cfg.get_children("observer");
for(config::child_list::const_iterator ob = observers.begin(); ob != observers.end(); ++ob) {
gui.add_observer((**ob)["name"]);
}
continue;
}
if(res && cfg.child("observer_quit") != NULL) {
const config::child_list& observers = cfg.get_children("observer_quit");
for(config::child_list::const_iterator ob = observers.begin(); ob != observers.end(); ++ob) {
gui.remove_observer((**ob)["name"]);
}
continue;
}
if(res && cfg.child("leave_game") != NULL) {
throw network::error("");
}
//if a side has dropped out of the game.
if(res && cfg["side_drop"] != "") {
const size_t side = atoi(cfg["side_drop"].c_str())-1;
if(side >= teams.size()) {
std::cerr << "unknown side " << side << " is dropping game\n";
throw network::error("");
}
int action = 0;
//see if the side still has a leader alive. If they have
//no leader, we assume they just want to be replaced by
//the AI.
const unit_map::const_iterator leader = find_leader(units,side+1);
if(leader != units.end()) {
std::vector<std::string> options;
options.push_back(string_table["replace_ai_message"]);
options.push_back(string_table["replace_local_message"]);
options.push_back(string_table["abort_game_message"]);
const std::string msg = leader->second.description() + " " + string_table["player_leave_message"];
action = gui::show_dialog(gui,NULL,"",msg,gui::OK_ONLY,&options);
}
//make the player an AI, and redo this turn, in case
//it was the current player's team who has just changed into
//an AI.
if(action == 0) {
teams[side].make_ai();
goto redo_turn;
} else if(action == 1) {
teams[side].make_human();
goto redo_turn;
} else {
throw network::error("");
}
}
if(res && cfg.child("turn") != NULL) {
//forward the data to other peers
network::send_data_all_except(cfg,res);
break;
}
const int ncommand = recorder.ncommands();
turn_data.turn_slice();
turn_data.send_data(ncommand);
gui.draw();
const turn_info::PROCESS_DATA_RESULT result = turn_data.process_network_data(cfg,res);
if(result == turn_info::PROCESS_RESTART_TURN) {
goto redo_turn;
} else if(result == turn_info::PROCESS_END_TURN) {
break;
}
replay replay_obj(*cfg.child("turn"));
replay_obj.start_replay();
try {
turn_end = do_replay(gui,map,gameinfo,units,teams,
player_number,status,state_of_game,&replay_obj);
} catch(replay::error&) {
turn_data.save_game(string_table["network_sync_error"]);
return QUIT;
}
recorder.add_config(*cfg.child("turn"));
gui.invalidate_all();
const int ncommand = recorder.ncommands();
turn_data.turn_slice();
turn_data.send_data(ncommand);
gui.draw();
}

View file

@ -130,6 +130,10 @@ void play_turn(game_data& gameinfo, game_state& state_of_game,
while(!turn_data.turn_over()) {
try {
config cfg;
const network::connection res = network::receive_data(cfg);
turn_data.process_network_data(cfg,res);
turn_data.turn_slice();
} catch(end_level_exception& e) {
turn_data.send_data(start_command);
@ -194,9 +198,28 @@ int turn_info::send_data(int first_command)
if(network::nconnections() > 0 && (undo_stack_.empty() || end_turn_) &&
first_command < recorder.ncommands()) {
config cfg;
cfg.add_child("turn",recorder.get_data_range(first_command,recorder.ncommands()));
network::send_data(cfg);
const config& obj = cfg.add_child("turn",recorder.get_data_range(first_command,recorder.ncommands()));
if(obj.empty() == false) {
network::send_data(cfg);
}
return recorder.ncommands();
} else if(network::nconnections() > 0 && first_command < recorder.ncommands()) {
std::cerr << "getting non-undo commands\n";
//we don't want to send any moves that haven't been committed, however if there
//are any non-undoable moves (speaking, labelling), we want to send it now.
config cfg;
const config& obj = cfg.add_child("turn",recorder.get_data_range(first_command,recorder.ncommands(),replay::NON_UNDO_DATA));
if(obj.empty() == false) {
std::cerr << "sending non-undo commands\n";
network::send_data(cfg);
}
return first_command;
} else {
return first_command;
}
@ -269,8 +292,9 @@ void turn_info::handle_event(const SDL_Event& event)
void turn_info::mouse_motion(const SDL_MouseMotionEvent& event)
{
if(commands_disabled)
if(commands_disabled) {
return;
}
if(minimap_scrolling_) {
//if the game is run in a window, we could miss a LMB up event
@ -820,6 +844,8 @@ bool turn_info::can_execute_command(hotkey::HOTKEY_COMMAND command) const
case hotkey::HOTKEY_UNIT_LIST:
case hotkey::HOTKEY_STATISTICS:
case hotkey::HOTKEY_QUIT_GAME:
case hotkey::HOTKEY_SHOW_ENEMY_MOVES:
case hotkey::HOTKEY_BEST_ENEMY_MOVES:
return true;
case hotkey::HOTKEY_SPEAK:
@ -1659,10 +1685,6 @@ void turn_info::speak()
recorder.speak(cfg);
dialogs::unit_speak(cfg,gui_,units_);
//speaking is an unretractable operation
undo_stack_.clear();
redo_stack_.clear();
}
}
@ -1854,6 +1876,33 @@ void turn_info::label_terrain()
}
}
// Highlights squares that an enemy could move to on their turn
void turn_info::show_enemy_moves(bool ignore_units)
{
team& current_team = teams_[team_num_-1];
all_paths_ = paths();
// Compute enemy movement positions
for(unit_map::const_iterator u = units_.begin(); u != units_.end(); ++u) {
if(current_team.is_enemy(u->second.side()) && !gui_.fogged(u->first.x,u->first.y)) {
const bool is_skirmisher = u->second.type().is_skirmisher();
const bool teleports = u->second.type().teleports();
unit_map units;
units.insert(*u);
const paths& path = paths(map_,status_,gameinfo_,ignore_units?units:units_,
u->first,teams_,is_skirmisher,teleports,1);
for (paths::routes_map::const_iterator route = path.routes.begin(); route != path.routes.end(); ++route) {
// map<...>::operator[](const key_type& key) inserts key into
// the map with a default instance of value_type
all_paths_.routes[route->first];
}
}
}
gui_.set_paths(&all_paths_);
}
unit_map::iterator turn_info::current_unit()
{
unit_map::iterator i = units_.end();
@ -1895,3 +1944,93 @@ unit_map::const_iterator turn_info::current_unit() const
return i;
}
turn_info::PROCESS_DATA_RESULT turn_info::process_network_data(const config& cfg, network::connection from)
{
if(from == 0) {
return PROCESS_CONTINUE;
}
if(cfg.child("observer") != NULL) {
const config::child_list& observers = cfg.get_children("observer");
for(config::child_list::const_iterator ob = observers.begin(); ob != observers.end(); ++ob) {
gui_.add_observer((**ob)["name"]);
}
return PROCESS_CONTINUE;
}
if(cfg.child("observer_quit") != NULL) {
const config::child_list& observers = cfg.get_children("observer_quit");
for(config::child_list::const_iterator ob = observers.begin(); ob != observers.end(); ++ob) {
gui_.remove_observer((**ob)["name"]);
}
return PROCESS_CONTINUE;
}
if(cfg.child("leave_game") != NULL) {
throw network::error("");
}
//if a side has dropped out of the game.
if(cfg["side_drop"] != "") {
const size_t side = atoi(cfg["side_drop"].c_str())-1;
if(side >= teams_.size()) {
std::cerr << "unknown side " << side << " is dropping game\n";
throw network::error("");
}
int action = 0;
//see if the side still has a leader alive. If they have
//no leader, we assume they just want to be replaced by
//the AI.
const unit_map::const_iterator leader = find_leader(units_,side+1);
if(leader != units_.end()) {
std::vector<std::string> options;
options.push_back(string_table["replace_ai_message"]);
options.push_back(string_table["replace_local_message"]);
options.push_back(string_table["abort_game_message"]);
const std::string msg = leader->second.description() + " " + string_table["player_leave_message"];
action = gui::show_dialog(gui_,NULL,"",msg,gui::OK_ONLY,&options);
}
//make the player an AI, and redo this turn, in case
//it was the current player's team who has just changed into
//an AI.
if(action == 0) {
teams_[side].make_ai();
return PROCESS_RESTART_TURN;
} else if(action == 1) {
teams_[side].make_human();
return PROCESS_RESTART_TURN;
} else {
throw network::error("");
}
}
bool turn_end = false;
if(cfg.child("turn") != NULL) {
//forward the data to other peers
network::send_data_all_except(cfg,from);
replay replay_obj(*cfg.child("turn"));
replay_obj.start_replay();
try {
turn_end = do_replay(gui_,map_,gameinfo_,units_,teams_,
team_num_,status_,state_of_game_,&replay_obj);
} catch(replay::error& e) {
save_game(string_table["network_sync_error"]);
throw e;
}
recorder.add_config(*cfg.child("turn"));
}
return turn_end ? PROCESS_END_TURN : PROCESS_CONTINUE;
}

View file

@ -74,6 +74,11 @@ public:
void save_game(const std::string& message);
enum PROCESS_DATA_RESULT { PROCESS_CONTINUE, PROCESS_RESTART_TURN, PROCESS_END_TURN };
//function which will process incoming network data, and act on it.
PROCESS_DATA_RESULT process_network_data(const config& cfg, network::connection from);
private:
void write_game_snapshot(config& cfg) const;
@ -101,6 +106,7 @@ private:
void unit_list();
void show_statistics();
void label_terrain();
void show_enemy_moves(bool ignore_units);
void do_recruit(const std::string& name);
@ -134,7 +140,7 @@ private:
bool left_button_, right_button_, middle_button_;
bool minimap_scrolling_;
gamemap::location next_unit_;
paths current_paths_;
paths current_paths_, all_paths_;
paths::route current_route_;
bool enemy_paths_;
gamemap::location last_hex_;

View file

@ -72,7 +72,7 @@ void set_random_results(const config& cfg)
replay::replay() : pos_(0), current_(NULL), skip_(0)
{}
replay::replay(config& cfg) : cfg_(cfg), pos_(0), current_(NULL), skip_(0)
replay::replay(const config& cfg) : cfg_(cfg), pos_(0), current_(NULL), skip_(0)
{}
config& replay::get_config()
@ -225,6 +225,8 @@ void replay::add_label(const std::string& text, const gamemap::location& loc)
{
config* const cmd = add_command();
(*cmd)["undo"] = "no";
config val;
loc.write(val);
@ -241,10 +243,14 @@ void replay::end_turn()
void replay::speak(const config& cfg)
{
add_command()->add_child("speak") = cfg;
config* const cmd = add_command();
if(cmd != NULL) {
cmd->add_child("speak",cfg);
(*cmd)["undo"] = "no";
}
}
config replay::get_data_range(int cmd_start, int cmd_end)
config replay::get_data_range(int cmd_start, int cmd_end, DATA_TYPE data_type)
{
log_scope("get_data_range\n");
@ -252,7 +258,14 @@ config replay::get_data_range(int cmd_start, int cmd_end)
const config::child_list& cmd = commands();
while(cmd_start < cmd_end) {
res.add_child("command",*cmd[cmd_start]);
if((data_type == ALL_DATA || (*cmd[cmd_start])["undo"] == "no") && (*cmd[cmd_start])["sent"] != "yes") {
res.add_child("command",*cmd[cmd_start]);
if(data_type == NON_UNDO_DATA) {
(*cmd[cmd_start])["sent"] = "yes";
}
}
++cmd_start;
}
@ -261,7 +274,11 @@ config replay::get_data_range(int cmd_start, int cmd_end)
void replay::undo()
{
const config::child_itors& cmd = cfg_.child_range("command");
config::child_itors cmd = cfg_.child_range("command");
while(cmd.first != cmd.second && (**(cmd.second-1))["undo"] == "no") {
--cmd.second;
}
if(cmd.first != cmd.second) {
delete *(cmd.second-1);
cfg_.remove_child("command",cmd.second - cmd.first - 1);

View file

@ -26,7 +26,7 @@ class replay
{
public:
replay();
replay(config& cfg);
explicit replay(const config& cfg);
config& get_config();
@ -50,7 +50,15 @@ public:
void speak(const config& cfg);
config get_data_range(int cmd_start, int cmd_end);
//get data range will get a range of moves from the replay system.
//if data_type is 'ALL_DATA' then it will return all data in this range
//except for undoable data that has already been sent. If data_type is
//NON_UNDO_DATA, then it will only retrieve undoable data, and will mark
//it as already sent.
//undoable data includes moves such as placing a label or speaking, which is
//ignored by the undo system.
enum DATA_TYPE { ALL_DATA, NON_UNDO_DATA };
config get_data_range(int cmd_start, int cmd_end, DATA_TYPE data_type=ALL_DATA);
config get_last_turn(int num_turns=1);
void undo();