From 98e5a68bdacea59173d49f750ad40a2d5cadedb6 Mon Sep 17 00:00:00 2001 From: Marko Lindqvist Date: Sat, 26 Nov 2022 15:58:03 +0200 Subject: [PATCH 23/23] lua: Check legality of unit_move() and unit_teleport() Reported by ihnatus See osdn #44769 Signed-off-by: Marko Lindqvist --- common/movement.c | 97 +++++++++++++++++++++++++++++- common/movement.h | 8 +++ server/scripting/api_server_edit.c | 14 +++++ 3 files changed, 118 insertions(+), 1 deletion(-) diff --git a/common/movement.c b/common/movement.c index 07ef95334b..f136b9740b 100644 --- a/common/movement.c +++ b/common/movement.c @@ -541,7 +541,7 @@ bool unit_can_move_to_tile(const struct unit *punit, /************************************************************************** Returns whether the unit can move from its current tile to the - destination tile. An enumerated value is returned indication the error + destination tile. An enumerated value is returned indication the error or success status. The unit can move if: @@ -680,6 +680,101 @@ unit_move_to_tile_test(const struct unit *punit, return MR_OK; } +/**************************************************************************** + Returns whether the unit can move from its current tile to the + destination tile. An enumerated value is returned indication the error + or success status. + + The unit can teleport if: + 1) There are no non-allied units on the target tile. + 2) Animals cannot move out from home terrains + 3) Unit can move to a tile where it can't survive on its own if there + is free transport capacity. + 4) There are no peaceful but non-allied units on the target tile. + 5) There is not a non-allied city on the target tile when + enter_enemy_city is false. When enter_enemy_city is true a non + peaceful city is also accepted. + 6) Triremes cannot move out of sight from land. + 7) It is not the territory of a player we are at peace with. +****************************************************************************/ +enum unit_move_result +unit_teleport_to_tile_test(const struct unit *punit, + enum unit_activity activity, + const struct tile *src_tile, + const struct tile *dst_tile, bool igzoc, + bool enter_transport, struct unit *embark_to, + bool enter_enemy_city) +{ + struct city *pcity; + const struct unit_type *punittype = unit_type_get(punit); + const struct player *puowner = unit_owner(punit); + + /* 1) */ + if (is_non_allied_unit_tile(dst_tile, puowner)) { + /* You can't move onto a tile with non-allied units on it (try + * attacking instead). */ + return MR_DESTINATION_OCCUPIED_BY_NON_ALLIED_UNIT; + } + + /* 2) */ + if (puowner->ai_common.barbarian_type == ANIMAL_BARBARIAN + && dst_tile->terrain->animal != punittype) { + return MR_ANIMAL_DISALLOWED; + } + + /* 3) */ + if (embark_to != NULL) { + if (!could_unit_load(punit, embark_to)) { + return MR_NO_TRANSPORTER_CAPACITY; + } + } else if (!(can_exist_at_tile(punittype, dst_tile) + || (enter_transport + && unit_could_load_at(punit, dst_tile)))) { + return MR_NO_TRANSPORTER_CAPACITY; + } + + /* 4) */ + if (is_non_attack_unit_tile(dst_tile, puowner)) { + /* You can't move into a non-allied tile. + * + * FIXME: this should never happen since it should be caught by check + * #1. */ + return MR_NO_WAR; + } + + /* 5) */ + if ((pcity = tile_city(dst_tile))) { + if (enter_enemy_city) { + if (pplayers_non_attack(city_owner(pcity), puowner)) { + /* You can't move into an empty city of a civilization you're at + * peace with - you must first either declare war or make an + * alliance. */ + return MR_NO_WAR; + } + } else { + if (!pplayers_allied(city_owner(pcity), puowner)) { + /* You can't move into an empty city of a civilization you're not + * allied to. Assume that the player tried to attack. */ + return MR_NO_WAR; + } + } + } + + /* 6) */ + if (utype_has_flag(punittype, UTYF_COAST_STRICT) + && !pcity && !is_safe_ocean(dst_tile)) { + return MR_TRIREME; + } + + /* 7) */ + if (!utype_has_flag(punittype, UTYF_CIVILIAN) + && !player_can_invade_tile(puowner, dst_tile)) { + return MR_PEACE; + } + + return MR_OK; +} + /************************************************************************** Return true iff transporter has ability to transport transported. **************************************************************************/ diff --git a/common/movement.h b/common/movement.h index fa42c01507..99d7e716e7 100644 --- a/common/movement.h +++ b/common/movement.h @@ -109,6 +109,14 @@ unit_move_to_tile_test(const struct unit *punit, bool igzoc, struct unit *embark_to, bool enter_enemy_city); +enum unit_move_result +unit_teleport_to_tile_test(const struct unit *punit, + enum unit_activity activity, + const struct tile *src_tile, + const struct tile *dst_tile, + bool igzoc, + bool enter_transport, struct unit *embark_to, + bool enter_enemy_city); bool can_unit_transport(const struct unit *transporter, const struct unit *transported); bool can_unit_type_transport(const struct unit_type *transporter, const struct unit_class *transported); diff --git a/server/scripting/api_server_edit.c b/server/scripting/api_server_edit.c index 608ec14814..b5163cc03e 100644 --- a/server/scripting/api_server_edit.c +++ b/server/scripting/api_server_edit.c @@ -169,6 +169,13 @@ bool api_edit_unit_teleport(lua_State *L, Unit *punit, Tile *dest) LUASCRIPT_CHECK_ARG_NIL(L, punit, 2, Unit, FALSE); LUASCRIPT_CHECK_ARG_NIL(L, dest, 3, Tile, FALSE); + if (unit_teleport_to_tile_test(punit, ACTIVITY_IDLE, + unit_tile(punit), dest, TRUE, + FALSE, NULL, TRUE) != MR_OK) { + /* Can't teleport to target. Return that unit is still alive. */ + return TRUE; + } + /* Teleport first so destination is revealed even if unit dies */ alive = unit_move(punit, dest, 0, NULL, /* The old call would result in occupation before the @@ -574,6 +581,13 @@ bool api_edit_unit_move(lua_State *L, Unit *punit, Tile *ptile, LUASCRIPT_CHECK_ARG_NIL(L, ptile, 3, Tile, FALSE); LUASCRIPT_CHECK_ARG(L, movecost >= 0, 4, "Negative move cost!", FALSE); + if (unit_move_to_tile_test(punit, ACTIVITY_IDLE, + unit_tile(punit), ptile, TRUE, + NULL, TRUE) != MR_OK) { + /* Can't move to target. Return that unit is still alive. */ + return TRUE; + } + return unit_move(punit, ptile, movecost, NULL, /* Backwards compatibility for old scripts in rulesets * and (scenario) savegames. */ -- 2.35.1