Add a "Teleport Unit (Debug!)" option to debug menu

Fixes #5649
This commit is contained in:
Marcus Örnås 2024-04-16 11:52:13 -05:00 committed by Pentarctagon
parent 39987cd1ca
commit d3b3c8c07a
13 changed files with 232 additions and 3 deletions

View file

@ -151,6 +151,11 @@
key=k
shift=yes
[/hotkey]
[hotkey]
command=teleportunit
key=t
shift=yes
[/hotkey]
[hotkey]
command=labelteamterrain
key=l

View file

@ -139,7 +139,7 @@
# Zoom
"zoomin,zoomout,zoomdefault," +
# Debug commands
"createunit,changeside,killunit," +
"createunit,changeside,killunit,teleportunit," +
# Labels
"labelterrain,clearlabels," +
# Shroud updates

View file

@ -207,6 +207,8 @@ namespace { // Private helpers for move_unit()
void post_move(undo_list *undo_stack);
/** Shows the various on-screen messages, for use after movement. */
void feedback() const;
/** Attempts to teleport the unit to a map_location. */
void try_teleport(bool show);
/** After checking expected movement, this is the expected path. */
std::vector<map_location> expected_path() const
@ -273,6 +275,10 @@ namespace { // Private helpers for move_unit()
inline bool do_move(const route_iterator & step_from,
const route_iterator & step_to,
unit_display::unit_mover & animator);
/** Teleports the unit. */
inline bool do_teleport(unit_display::unit_mover& animator);
/** Clears fog/shroud and handles units being sighted. */
inline void handle_fog(const map_location & hex, bool new_animation);
inline bool is_reasonable_stop(const map_location & hex) const;
@ -538,6 +544,40 @@ namespace { // Private helpers for move_unit()
return success;
}
/**
* Teleports the unit to begin_ + 1.
* @a animator is the unit_display::unit_mover being used.
* @return whether or not we started a new animation.
*/
inline bool unit_mover::do_teleport(unit_display::unit_mover& animator)
{
game_display& disp = *game_display::get_singleton();
const route_iterator step_to = begin_ + 1;
// Invalidate before moving so we invalidate neighbor hexes if needed.
move_it_->anim_comp().invalidate(disp);
// Attempt actually moving. Fails if *step_to is occupied.
auto [unit_it, success] = resources::gameboard->units().move(*begin_, *step_to);
if(success) {
// Update the moving unit.
move_it_ = unit_it;
move_it_->set_facing(begin_->get_relative_dir(*step_to));
move_it_->anim_comp().set_standing(false);
disp.invalidate_unit_after_move(*begin_, *step_to);
disp.invalidate(*step_to);
move_loc_ = step_to;
// Show this move.
animator.proceed_to(move_it_.get_shared_ptr(), step_to - begin_, move_it_->appearance_changed(), false);
move_it_->set_appearance_changed(false);
disp.redraw_minimap();
}
return success;
}
/**
* Clears fog/shroud and raises events for units being sighted.
@ -1012,6 +1052,50 @@ namespace { // Private helpers for move_unit()
event_mutated_mid_move_ = wml_removed_unit_ || wml_move_aborted_;
}
/**
* Attempts to teleport the unit to a map_location.
*
* @param[in] show Set to false to suppress animations.
*/
void unit_mover::try_teleport(bool show)
{
const route_iterator step_from = real_end_ - 1;
std::vector<int> not_seeing = get_sides_not_seeing(*move_it_);
// Prepare to animate.
unit_display::unit_mover animator(route_, show);
animator.start(move_it_.get_shared_ptr());
fire_hex_event("exit hex", step_from, begin_);
bool new_animation = do_teleport(animator);
if(current_uses_fog_)
handle_fog(*(begin_ + 1), new_animation);
animator.wait_for_anims();
fire_hex_event("enter hex", begin_, step_from);
if(is_reasonable_stop(*step_from)) {
pump_sighted(step_from);
}
pump_sighted(step_from);
if(move_it_.valid()) {
// Finish animating.
animator.finish(move_it_.get_shared_ptr());
// Check for the moving unit being seen.
auto [wml_undo_blocked, wml_move_aborted] = actor_sighted(*move_it_, &not_seeing);
wml_move_aborted_ |= wml_move_aborted;
wml_undo_disabled_ |= wml_undo_blocked;
}
post_move(resources::undo_stack);
}
/**
* Does some bookkeeping and event firing, for use after movement.
@ -1205,6 +1289,46 @@ static std::size_t move_unit_internal(undo_list* undo_stack,
return mover.steps_travelled();
}
void teleport_unit_and_record(const map_location& teleport_from,
const map_location& teleport_to,
bool show_move,
move_unit_spectator* move_spectator)
{
const bool skip_ally_sighted = true;
const bool continued_move = false;
const std::vector<map_location>& route{teleport_from, teleport_to};
unit_mover mover(route, move_spectator, continued_move, skip_ally_sighted);
if(synced_context::get_synced_state() != synced_context::SYNCED) {
/*
enter the synced mode and do the actual movement.
*/
resources::recorder->add_synced_command("debug_teleport",
config{"teleport_from_x", teleport_from.wml_x(), "teleport_from_y", teleport_from.wml_y(), "teleport_to_x",
teleport_to.wml_x(), "teleport_to_y", teleport_to.wml_y()});
set_scontext_synced sync;
mover.try_teleport(show_move);
sync.do_final_checkup();
} else {
// we are already in synced mode and don't need to reenter it again.
mover.try_teleport(show_move);
}
}
void teleport_unit_from_replay(const std::vector<map_location> &steps,
bool continued_move, bool skip_ally_sighted, bool show_move)
{
unit_mover mover(steps, nullptr, continued_move, skip_ally_sighted);
if ( !mover.check_expected_movement() )
{
replay::process_error("found corrupt movement in replay.");
return;
}
mover.try_teleport(show_move);
}
/**
* Moves a unit across the board.
*

View file

@ -98,6 +98,24 @@ private:
*/
game_events::pump_result_t get_village(const map_location& loc, int side, bool *time_bonus = nullptr, bool fire_event = true);
/**
* Teleports a unit across the board and enters the synced context.
*/
void teleport_unit_and_record(const map_location& teleport_from,
const map_location& teleport_to,
bool show_move = false,
move_unit_spectator* move_spectator = nullptr);
/**
* Teleports a unit across the board.
* To be called from replay when we are already in the synced context.
*/
void teleport_unit_from_replay(
const std::vector<map_location> &steps,
bool continued_move,
bool skip_ally_sighted,
bool show_move);
/**
* Moves a unit across the board.
* And enters the synced context.

View file

@ -197,6 +197,9 @@ bool command_executor::do_execute_command(const hotkey::ui_command& cmd, bool pr
case HOTKEY_KILL_UNIT:
kill_unit();
break;
case HOTKEY_TELEPORT_UNIT:
select_teleport();
break;
case HOTKEY_PREFERENCES:
preferences();
break;

View file

@ -136,6 +136,7 @@ public:
virtual void deselect_hex() {}
virtual void move_action() {}
virtual void select_and_action() {}
virtual void select_teleport() {}
virtual void touch_hex() {}
virtual void left_mouse_click() {}
virtual void right_mouse_click() {}

View file

@ -122,6 +122,7 @@ constexpr std::array<hotkey_command_temp, HOTKEY_NULL - 1> master_hotkey_list {{
{ HOTKEY_CREATE_UNIT, "createunit", N_("Create Unit (Debug!)"), false, scope_game, HKCAT_DEBUG, "" },
{ HOTKEY_CHANGE_SIDE, "changeside", N_("Change Side (Debug!)"), false, scope_game, HKCAT_DEBUG, "" },
{ HOTKEY_KILL_UNIT, "killunit", N_("Kill Unit (Debug!)"), false, scope_game, HKCAT_DEBUG, "" },
{ HOTKEY_TELEPORT_UNIT, "teleportunit", N_("Teleport Unit (Debug!)"), false, scope_game, HKCAT_DEBUG, "" },
{ HOTKEY_PREFERENCES, "preferences", N_("Preferences"), false, scope_game | scope_editor | scope_main, HKCAT_GENERAL, "" },
{ HOTKEY_OBJECTIVES, "objectives", N_("Objectives"), false, scope_game, HKCAT_MAP, "" },
{ HOTKEY_UNIT_LIST, "unitlist", N_("Unit List"), false, scope_game | scope_editor, HKCAT_UNITS, "" },

View file

@ -56,7 +56,7 @@ enum HOTKEY_COMMAND {
HOTKEY_SAVE_GAME, HOTKEY_SAVE_REPLAY, HOTKEY_SAVE_MAP, HOTKEY_LOAD_GAME,
HOTKEY_RECRUIT, HOTKEY_REPEAT_RECRUIT, HOTKEY_RECALL, HOTKEY_ENDTURN,
HOTKEY_TOGGLE_ELLIPSES, HOTKEY_TOGGLE_GRID, HOTKEY_STATUS_TABLE, HOTKEY_MUTE, HOTKEY_MOUSE_SCROLL,
HOTKEY_SPEAK, HOTKEY_CREATE_UNIT, HOTKEY_CHANGE_SIDE, HOTKEY_KILL_UNIT, HOTKEY_PREFERENCES,
HOTKEY_SPEAK, HOTKEY_CREATE_UNIT, HOTKEY_CHANGE_SIDE, HOTKEY_KILL_UNIT, HOTKEY_PREFERENCES, HOTKEY_TELEPORT_UNIT,
HOTKEY_OBJECTIVES, HOTKEY_UNIT_LIST, HOTKEY_STATISTICS, HOTKEY_STOP_NETWORK, HOTKEY_START_NETWORK, HOTKEY_SURRENDER, HOTKEY_QUIT_GAME, HOTKEY_QUIT_TO_DESKTOP,
HOTKEY_LABEL_TEAM_TERRAIN, HOTKEY_LABEL_TERRAIN, HOTKEY_CLEAR_LABELS,HOTKEY_SHOW_ENEMY_MOVES, HOTKEY_BEST_ENEMY_MOVES,
HOTKEY_DELAY_SHROUD, HOTKEY_UPDATE_SHROUD, HOTKEY_CONTINUE_MOVE,

View file

@ -98,6 +98,10 @@ void playsingle_controller::hotkey_handler::kill_unit(){
menu_handler_.kill_unit(mouse_handler_);
}
void playsingle_controller::hotkey_handler::select_teleport(){
mouse_handler_.select_teleport();
}
void playsingle_controller::hotkey_handler::label_terrain(bool team_only){
menu_handler_.label_terrain(mouse_handler_, team_only);
}
@ -248,6 +252,7 @@ bool playsingle_controller::hotkey_handler::can_execute_command(const hotkey::ui
case hotkey::HOTKEY_CREATE_UNIT:
case hotkey::HOTKEY_CHANGE_SIDE:
case hotkey::HOTKEY_KILL_UNIT:
case hotkey::HOTKEY_TELEPORT_UNIT:
return !events::commands_disabled && game_config::debug && play_controller_.get_map().on_board(mouse_handler_.get_last_hex()) && play_controller_.current_team().is_local();
case hotkey::HOTKEY_CLEAR_LABELS:

View file

@ -48,6 +48,7 @@ public:
virtual void rename_unit() override;
virtual void create_unit() override;
virtual void change_side() override;
virtual void select_teleport() override;
virtual void kill_unit() override;
virtual void label_terrain(bool) override;
virtual void clear_labels() override;

View file

@ -73,6 +73,7 @@ mouse_handler::mouse_handler(game_display* gui, play_controller& pc)
, over_route_(false)
, reachmap_invalid_(false)
, show_partial_move_(false)
, teleport_selected_(false)
, preventing_units_highlight_(false)
{
singleton_ = this;
@ -821,6 +822,47 @@ bool mouse_handler::right_click_show_menu(int x, int y, const bool /*browse*/)
return gui().map_area().contains(x, y);
}
void mouse_handler::select_teleport()
{
// Load whiteboard partial moves
//wb::future_map_if_active planned_unit_map;
if(game_lua_kernel* lk = pc_.gamestate().lua_kernel_.get()) {
lk->select_hex_callback(last_hex_);
}
unit_map::iterator clicked_u = find_unit(last_hex_);
unit_map::iterator selected_u = find_unit(selected_hex_);
if(clicked_u && (!selected_u || selected_u->side() != side_num_ ||
(clicked_u->side() == side_num_ && clicked_u->id() != selected_u->id()))
) {
selected_hex_ = last_hex_;
teleport_selected_ = true;
gui().select_hex(selected_hex_);
gui().set_route(nullptr);
}
}
void mouse_handler::teleport_action()
{
// Set the teleport to active so that we can use existing functions
// for teleport
teleport_selected_ = false;
actions::teleport_unit_and_record(selected_hex_, last_hex_);
cursor::set(cursor::NORMAL);
gui().invalidate_game_status();
gui().invalidate_all();
gui().clear_attack_indicator();
gui().set_route(nullptr);
// Select and deselect the units hex to prompt updates for hover
select_hex(last_hex_, false);
deselect_hex();
current_route_.steps.clear();
}
void mouse_handler::select_or_action(bool browse)
{
if(!pc_.get_map().on_board(last_hex_)) {
@ -845,6 +887,7 @@ void mouse_handler::select_or_action(bool browse)
} else {
move_action(browse);
}
teleport_selected_ = false;
}
void mouse_handler::move_action(bool browse)
@ -889,8 +932,12 @@ void mouse_handler::move_action(bool browse)
attack_from = current_unit_attacks_from(hex);
} // end planned unit map scope
// See if the teleport option is toggled
if(teleport_selected_) {
teleport_action();
}
// see if we're trying to do a attack or move-and-attack
if((!browse || pc_.get_whiteboard()->is_active()) && attack_from.valid()) {
else if((!browse || pc_.get_whiteboard()->is_active()) && attack_from.valid()) {
// Ignore this command if commands are disabled.
if(commands_disabled) {
return;

View file

@ -83,10 +83,12 @@ public:
const bool fire_event = true);
void move_action(bool browse) override;
void teleport_action();
void touch_action(const map_location hex, bool browse) override;
void select_or_action(bool browse);
void select_teleport();
/**
* Uses SDL and @ref game_display::hex_clicked_on
@ -192,6 +194,7 @@ private:
bool over_route_;
bool reachmap_invalid_;
bool show_partial_move_;
bool teleport_selected_;
static mouse_handler * singleton_;

View file

@ -594,6 +594,27 @@ SYNCED_COMMAND_HANDLER_FUNCTION(debug_lua, child, use_undo, /*show*/, /*error_ha
return true;
}
SYNCED_COMMAND_HANDLER_FUNCTION(debug_teleport, child, use_undo, /*show*/, /*error_handler*/)
{
if(use_undo) {
resources::undo_stack->clear();
}
debug_cmd_notification("teleport");
const map_location teleport_from(child["teleport_from_x"].to_int(), child["teleport_from_y"].to_int(), wml_loc());
const map_location teleport_to(child["teleport_to_x"].to_int(), child["teleport_to_y"].to_int(), wml_loc());
const unit_map::iterator unit_iter = resources::gameboard->units().find(teleport_from);
if(unit_iter != resources::gameboard->units().end()) {
if(unit_iter.valid()) {
actions::teleport_unit_from_replay({teleport_from, teleport_to}, false, false, false);
}
display::get_singleton()->redraw_minimap();
}
return true;
}
SYNCED_COMMAND_HANDLER_FUNCTION(debug_kill, child, use_undo, /*show*/, /*error_handler*/)
{
if (use_undo) {