From 1a53c96e4ee9640ed8f2920df896a55529464791 Mon Sep 17 00:00:00 2001 From: Marko Lindqvist Date: Tue, 3 Oct 2023 19:36:35 +0300 Subject: [PATCH 55/55] Make playertile extras dynamic bitvector It was a static one, always reserving memory for freeciv's internal maximum number of extras. See osdn #48798 Signed-off-by: Marko Lindqvist --- server/maphand.c | 14 +- server/maphand.h | 2 +- server/savegame/savegame2.c | 300 ++++++++++++++++++++++++++++++++---- server/savegame/savegame3.c | 97 ++++++++++-- server/unittools.c | 19 ++- 5 files changed, 377 insertions(+), 55 deletions(-) diff --git a/server/maphand.c b/server/maphand.c index 26e0f8b67c..25ff995c3a 100644 --- a/server/maphand.c +++ b/server/maphand.c @@ -549,7 +549,7 @@ void send_tile_info(struct conn_list *dest, struct tile *ptile, : 0; if (pplayer != NULL) { - info.extras = map_get_player_tile(ptile, pplayer)->extras; + dbv_to_bv(info.extras.vec, &(map_get_player_tile(ptile, pplayer)->extras)); } else { info.extras = ptile->extras; } @@ -587,7 +587,7 @@ void send_tile_info(struct conn_list *dest, struct tile *ptile, info.placing = -1; info.place_turn = 0; - info.extras = plrtile->extras; + dbv_to_bv(info.extras.vec, &(plrtile->extras)); /* Labels never change, so they are not subject to fog of war */ if (ptile->label != NULL) { @@ -1320,7 +1320,7 @@ static void player_tile_init(struct tile *ptile, struct player *pplayer) plrtile->owner = NULL; plrtile->extras_owner = NULL; plrtile->site = NULL; - BV_CLR_ALL(plrtile->extras); + dbv_init(&(plrtile->extras), extra_count()); if (!game.server.last_updated_year) { plrtile->last_updated = game.info.turn; } else { @@ -1343,6 +1343,8 @@ static void player_tile_free(struct tile *ptile, struct player *pplayer) if (plrtile->site != NULL) { vision_site_destroy(plrtile->site); } + + dbv_free(&(plrtile->extras)); } /**********************************************************************//** @@ -1384,16 +1386,16 @@ bool update_player_tile_knowledge(struct player *pplayer, struct tile *ptile) struct player_tile *plrtile = map_get_player_tile(ptile, pplayer); if (plrtile->terrain != ptile->terrain - || !BV_ARE_EQUAL(plrtile->extras, ptile->extras) + || !bv_match_dbv(&(plrtile->extras), ptile->extras.vec) || plrtile->resource != ptile->resource || plrtile->owner != tile_owner(ptile) || plrtile->extras_owner != extra_owner(ptile)) { plrtile->terrain = ptile->terrain; extra_type_iterate(pextra) { if (player_knows_extra_exist(pplayer, pextra, ptile)) { - BV_SET(plrtile->extras, extra_number(pextra)); + dbv_set(&(plrtile->extras), extra_number(pextra)); } else { - BV_CLR(plrtile->extras, extra_number(pextra)); + dbv_clr(&(plrtile->extras), extra_number(pextra)); } } extra_type_iterate_end; plrtile->resource = ptile->resource; diff --git a/server/maphand.h b/server/maphand.h index 486751d781..00adf35ed1 100644 --- a/server/maphand.h +++ b/server/maphand.h @@ -33,7 +33,7 @@ struct player_tile { struct terrain *terrain; /* NULL for unknown tiles */ struct player *owner; /* NULL for unowned */ struct player *extras_owner; - bv_extras extras; + struct dbv extras; /* If you build a city with an unknown square within city radius the square stays unknown. However, we still have to keep count diff --git a/server/savegame/savegame2.c b/server/savegame/savegame2.c index 97cf65cd37..6978e89920 100644 --- a/server/savegame/savegame2.c +++ b/server/savegame/savegame2.c @@ -302,12 +302,20 @@ static void worklist_load(struct section_file *file, int wlist_max_length, struct worklist *pwl, const char *path, ...); static void unit_ordering_apply(void); -static void sg_extras_set(bv_extras *extras, char ch, struct extra_type **idx); -static void sg_special_set(struct tile *ptile, bv_extras *extras, char ch, - const enum tile_special_type *idx, - bool rivers_overlay); -static void sg_bases_set(bv_extras *extras, char ch, struct base_type **idx); -static void sg_roads_set(bv_extras *extras, char ch, struct road_type **idx); +static void sg_extras_set_dbv(struct dbv *extras, char ch, + struct extra_type **idx); +static void sg_extras_set_bv(bv_extras *extras, char ch, + struct extra_type **idx); +static void sg_special_set_dbv(struct tile *ptile, struct dbv *extras, char ch, + const enum tile_special_type *idx, + bool rivers_overlay); +static void sg_special_set_bv(struct tile *ptile, bv_extras *extras, char ch, + const enum tile_special_type *idx, + bool rivers_overlay); +static void sg_bases_set_dbv(struct dbv *extras, char ch, struct base_type **idx); +static void sg_bases_set_bv(bv_extras *extras, char ch, struct base_type **idx); +static void sg_roads_set_dbv(struct dbv *extras, char ch, struct road_type **idx); +static void sg_roads_set_bv(bv_extras *extras, char ch, struct road_type **idx); static struct extra_type *char2resource(char c); static struct terrain *char2terrain(char ch); static Tech_type_id technology_load(struct section_file *file, @@ -844,7 +852,41 @@ static void unit_ordering_apply(void) in four to a character in hex notation. 'index' is a mapping of savegame bit -> base bit. ****************************************************************************/ -static void sg_extras_set(bv_extras *extras, char ch, struct extra_type **idx) +static void sg_extras_set_dbv(struct dbv *extras, char ch, + struct extra_type **idx) +{ + int i, bin; + const char *pch = strchr(hex_chars, ch); + + if (!pch || ch == '\0') { + log_sg("Unknown hex value: '%c' (%d)", ch, ch); + bin = 0; + } else { + bin = pch - hex_chars; + } + + for (i = 0; i < 4; i++) { + struct extra_type *pextra = idx[i]; + + if (pextra == NULL) { + continue; + } + if ((bin & (1 << i)) + && (wld.map.server.have_huts || !is_extra_caused_by(pextra, EC_HUT))) { + dbv_set(extras, extra_index(pextra)); + } + } +} + +/************************************************************************//** + Helper function for loading extras from a savegame. + + 'ch' gives the character loaded from the savegame. Extras are packed + in four to a character in hex notation. 'index' is a mapping of + savegame bit -> base bit. +****************************************************************************/ +static void sg_extras_set_bv(bv_extras *extras, char ch, + struct extra_type **idx) { int i, bin; const char *pch = strchr(hex_chars, ch); @@ -876,9 +918,146 @@ static void sg_extras_set(bv_extras *extras, char ch, struct extra_type **idx) in four to a character in hex notation. 'index' is a mapping of savegame bit -> special bit. S_LAST is used to mark unused savegame bits. ****************************************************************************/ -static void sg_special_set(struct tile *ptile, bv_extras *extras, char ch, - const enum tile_special_type *idx, - bool rivers_overlay) +static void sg_special_set_dbv(struct tile *ptile, struct dbv *extras, char ch, + const enum tile_special_type *idx, + bool rivers_overlay) +{ + int i, bin; + const char *pch = strchr(hex_chars, ch); + + if (!pch || ch == '\0') { + log_sg("Unknown hex value: '%c' (%d)", ch, ch); + bin = 0; + } else { + bin = pch - hex_chars; + } + + for (i = 0; i < 4; i++) { + enum tile_special_type sp = idx[i]; + + if (sp == S_LAST) { + continue; + } + if (rivers_overlay && sp != S_OLD_RIVER) { + continue; + } + + if (sp == S_HUT && !wld.map.server.have_huts) { + /* It would be logical to have this in the saving side - + * really not saving the huts in the first place, BUT + * 1) They have been saved by older versions, so we + * have to deal with such savegames. + * 2) This makes scenario author less likely to lose + * one's work completely after carefully placing huts + * and then saving with 'have_huts' disabled. */ + continue; + } + + if (bin & (1 << i)) { + if (sp == S_OLD_ROAD) { + struct road_type *proad; + + proad = road_by_compat_special(ROCO_ROAD); + if (proad) { + BV_SET(*extras, extra_index(road_extra_get(proad))); + } + } else if (sp == S_OLD_RAILROAD) { + struct road_type *proad; + + proad = road_by_compat_special(ROCO_RAILROAD); + if (proad) { + BV_SET(*extras, extra_index(road_extra_get(proad))); + } + } else if (sp == S_OLD_RIVER) { + struct road_type *proad; + + proad = road_by_compat_special(ROCO_RIVER); + if (proad) { + BV_SET(*extras, extra_index(road_extra_get(proad))); + } + } else { + struct extra_type *pextra = NULL; + enum extra_cause cause = EC_COUNT; + + /* Converting from old hardcoded specials to as sensible extra as we can */ + switch (sp) { + case S_IRRIGATION: + case S_FARMLAND: + /* If old savegame has both irrigation and farmland, EC_IRRIGATION + * gets applied twice, which hopefully has the correct result. */ + cause = EC_IRRIGATION; + break; + case S_MINE: + cause = EC_MINE; + break; + case S_POLLUTION: + cause = EC_POLLUTION; + break; + case S_HUT: + cause = EC_HUT; + break; + case S_FALLOUT: + cause = EC_FALLOUT; + break; + default: + pextra = extra_type_by_rule_name(special_rule_name(sp)); + break; + } + + if (cause != EC_COUNT) { + struct tile *vtile = tile_virtual_new(ptile); + struct terrain *pterr = tile_terrain(vtile); + const struct req_context tile_ctxt = { .tile = vtile }; + + /* Do not let the extras already set to the real tile mess with setup + * of the player tiles if that's what we're doing. */ + dbv_to_bv(vtile->extras.vec, extras); + + /* It's ok not to know which player or which unit originally built the extra - + * in the rules used when specials were saved these could not have made any + * difference. */ + /* Can't use next_extra_for_tile() as it works for buildable extras only. */ + + if ((cause != EC_IRRIGATION || pterr->irrigation_time != 0) + && (cause != EC_MINE || pterr->mining_time != 0) + && (cause != EC_BASE || pterr->base_time != 0) + && (cause != EC_ROAD || pterr->road_time != 0)) { + extra_type_by_cause_iterate(cause, candidate) { + if (!tile_has_extra(vtile, candidate)) { + if ((!is_extra_caused_by(candidate, EC_BASE) + || tile_city(vtile) != NULL + || extra_base_get(candidate)->border_sq <= 0) + && are_reqs_active(&tile_ctxt, tile_owner(vtile), + &candidate->reqs, + RPT_POSSIBLE)) { + pextra = candidate; + break; + } + } + } extra_type_by_cause_iterate_end; + } + + tile_virtual_destroy(vtile); + } + + if (pextra) { + dbv_set(extras, extra_index(pextra)); + } + } + } + } +} + +/************************************************************************//** + Complicated helper function for loading specials from a savegame. + + 'ch' gives the character loaded from the savegame. Specials are packed + in four to a character in hex notation. 'index' is a mapping of + savegame bit -> special bit. S_LAST is used to mark unused savegame bits. +****************************************************************************/ +static void sg_special_set_bv(struct tile *ptile, bv_extras *extras, char ch, + const enum tile_special_type *idx, + bool rivers_overlay) { int i, bin; const char *pch = strchr(hex_chars, ch); @@ -1013,7 +1192,39 @@ static void sg_special_set(struct tile *ptile, bv_extras *extras, char ch, in four to a character in hex notation. 'index' is a mapping of savegame bit -> base bit. ****************************************************************************/ -static void sg_bases_set(bv_extras *extras, char ch, struct base_type **idx) +static void sg_bases_set_dbv(struct dbv *extras, char ch, + struct base_type **idx) +{ + int i, bin; + const char *pch = strchr(hex_chars, ch); + + if (!pch || ch == '\0') { + log_sg("Unknown hex value: '%c' (%d)", ch, ch); + bin = 0; + } else { + bin = pch - hex_chars; + } + + for (i = 0; i < 4; i++) { + struct base_type *pbase = idx[i]; + + if (pbase == NULL) { + continue; + } + if (bin & (1 << i)) { + dbv_set(extras, extra_index(base_extra_get(pbase))); + } + } +} + +/************************************************************************//** + Helper function for loading bases from a savegame. + + 'ch' gives the character loaded from the savegame. Bases are packed + in four to a character in hex notation. 'index' is a mapping of + savegame bit -> base bit. +****************************************************************************/ +static void sg_bases_set_bv(bv_extras *extras, char ch, struct base_type **idx) { int i, bin; const char *pch = strchr(hex_chars, ch); @@ -1044,7 +1255,38 @@ static void sg_bases_set(bv_extras *extras, char ch, struct base_type **idx) in four to a character in hex notation. 'index' is a mapping of savegame bit -> road bit. ****************************************************************************/ -static void sg_roads_set(bv_extras *extras, char ch, struct road_type **idx) +static void sg_roads_set_dbv(struct dbv *extras, char ch, struct road_type **idx) +{ + int i, bin; + const char *pch = strchr(hex_chars, ch); + + if (!pch || ch == '\0') { + log_sg("Unknown hex value: '%c' (%d)", ch, ch); + bin = 0; + } else { + bin = pch - hex_chars; + } + + for (i = 0; i < 4; i++) { + struct road_type *proad = idx[i]; + + if (proad == NULL) { + continue; + } + if (bin & (1 << i)) { + dbv_set(extras, extra_index(road_extra_get(proad))); + } + } +} + +/************************************************************************//** + Helper function for loading roads from a savegame. + + 'ch' gives the character loaded from the savegame. Roads are packed + in four to a character in hex notation. 'index' is a mapping of + savegame bit -> road bit. +****************************************************************************/ +static void sg_roads_set_bv(bv_extras *extras, char ch, struct road_type **idx) { int i, bin; const char *pch = strchr(hex_chars, ch); @@ -2050,7 +2292,8 @@ static void sg_load_map_tiles_extras(struct loaddata *loading) /* Load extras. */ halfbyte_iterate_extras(j, loading->extra.size) { - LOAD_MAP_CHAR(ch, ptile, sg_extras_set(&ptile->extras, ch, loading->extra.order + 4 * j), + LOAD_MAP_CHAR(ch, ptile, sg_extras_set_bv(&ptile->extras, + ch, loading->extra.order + 4 * j), loading->file, "map.e%02d_%04d", j); } halfbyte_iterate_extras_end; } @@ -2065,8 +2308,8 @@ static void sg_load_map_tiles_bases(struct loaddata *loading) /* Load bases. */ halfbyte_iterate_bases(j, loading->base.size) { - LOAD_MAP_CHAR(ch, ptile, sg_bases_set(&ptile->extras, ch, - loading->base.order + 4 * j), + LOAD_MAP_CHAR(ch, ptile, sg_bases_set_bv(&ptile->extras, ch, + loading->base.order + 4 * j), loading->file, "map.b%02d_%04d", j); } halfbyte_iterate_bases_end; } @@ -2081,8 +2324,8 @@ static void sg_load_map_tiles_roads(struct loaddata *loading) /* Load roads. */ halfbyte_iterate_roads(j, loading->road.size) { - LOAD_MAP_CHAR(ch, ptile, sg_roads_set(&ptile->extras, ch, - loading->road.order + 4 * j), + LOAD_MAP_CHAR(ch, ptile, sg_roads_set_bv(&ptile->extras, ch, + loading->road.order + 4 * j), loading->file, "map.r%02d_%04d", j); } halfbyte_iterate_roads_end; } @@ -2111,9 +2354,9 @@ static void sg_load_map_tiles_specials(struct loaddata *loading, * the rivers overlay but no other specials. Scenarios that encode things * this way should have the "riversoverlay" capability. */ halfbyte_iterate_special(j, loading->special.size) { - LOAD_MAP_CHAR(ch, ptile, sg_special_set(ptile, &ptile->extras, ch, - loading->special.order + 4 * j, - rivers_overlay), + LOAD_MAP_CHAR(ch, ptile, sg_special_set_bv(ptile, &ptile->extras, ch, + loading->special.order + 4 * j, + rivers_overlay), loading->file, "map.spe%02d_%04d", j); } halfbyte_iterate_special_end; } @@ -4750,24 +4993,25 @@ static void sg_load_player_vision(struct loaddata *loading, /* Load player map (extras). */ halfbyte_iterate_extras(j, loading->extra.size) { LOAD_MAP_CHAR(ch, ptile, - sg_extras_set(&map_get_player_tile(ptile, plr)->extras, - ch, loading->extra.order + 4 * j), + sg_extras_set_dbv(&(map_get_player_tile(ptile, plr)->extras), + ch, loading->extra.order + 4 * j), loading->file, "player%d.map_e%02d_%04d", plrno, j); } halfbyte_iterate_extras_end; } else { /* Load player map (specials). */ halfbyte_iterate_special(j, loading->special.size) { LOAD_MAP_CHAR(ch, ptile, - sg_special_set(ptile, &map_get_player_tile(ptile, plr)->extras, - ch, loading->special.order + 4 * j, FALSE), + sg_special_set_dbv(ptile, + &(map_get_player_tile(ptile, plr)->extras), + ch, loading->special.order + 4 * j, FALSE), loading->file, "player%d.map_spe%02d_%04d", plrno, j); } halfbyte_iterate_special_end; /* Load player map (bases). */ halfbyte_iterate_bases(j, loading->base.size) { LOAD_MAP_CHAR(ch, ptile, - sg_bases_set(&map_get_player_tile(ptile, plr)->extras, - ch, loading->base.order + 4 * j), + sg_bases_set_dbv(&(map_get_player_tile(ptile, plr)->extras), + ch, loading->base.order + 4 * j), loading->file, "player%d.map_b%02d_%04d", plrno, j); } halfbyte_iterate_bases_end; @@ -4776,8 +5020,8 @@ static void sg_load_player_vision(struct loaddata *loading, /* 2.5.0 or newer */ halfbyte_iterate_roads(j, loading->road.size) { LOAD_MAP_CHAR(ch, ptile, - sg_roads_set(&map_get_player_tile(ptile, plr)->extras, - ch, loading->road.order + 4 * j), + sg_roads_set_dbv(&(map_get_player_tile(ptile, plr)->extras), + ch, loading->road.order + 4 * j), loading->file, "player%d.map_r%02d_%04d", plrno, j); } halfbyte_iterate_roads_end; } diff --git a/server/savegame/savegame3.c b/server/savegame/savegame3.c index 78f217649e..c436dfc49f 100644 --- a/server/savegame/savegame3.c +++ b/server/savegame/savegame3.c @@ -308,9 +308,12 @@ static void worklist_save(struct section_file *file, int max_length, const char *path, ...); static void unit_ordering_calc(void); static void unit_ordering_apply(void); -static void sg_extras_set(bv_extras *extras, char ch, struct extra_type **idx); -static char sg_extras_get(bv_extras extras, struct extra_type *presource, - const int *idx); +static void sg_extras_set_dbv(struct dbv *extras, char ch, struct extra_type **idx); +static void sg_extras_set_bv(bv_extras *extras, char ch, struct extra_type **idx); +static char sg_extras_get_dbv(struct dbv *extras, struct extra_type *presource, + const int *idx); +static char sg_extras_get_bv(bv_extras extras, struct extra_type *presource, + const int *idx); static struct terrain *char2terrain(char ch); static char terrain2char(const struct terrain *pterrain); static Tech_type_id technology_load(struct section_file *file, @@ -1089,7 +1092,41 @@ static void unit_ordering_apply(void) in four to a character in hex notation. 'index' is a mapping of savegame bit -> base bit. ****************************************************************************/ -static void sg_extras_set(bv_extras *extras, char ch, struct extra_type **idx) +static void sg_extras_set_dbv(struct dbv *extras, char ch, + struct extra_type **idx) +{ + int i, bin; + const char *pch = strchr(hex_chars, ch); + + if (!pch || ch == '\0') { + log_sg("Unknown hex value: '%c' (%d)", ch, ch); + bin = 0; + } else { + bin = pch - hex_chars; + } + + for (i = 0; i < 4; i++) { + struct extra_type *pextra = idx[i]; + + if (pextra == NULL) { + continue; + } + if ((bin & (1 << i)) + && (wld.map.server.have_huts || !is_extra_caused_by(pextra, EC_HUT))) { + dbv_set(extras, extra_index(pextra)); + } + } +} + +/************************************************************************//** + Helper function for loading extras from a savegame. + + 'ch' gives the character loaded from the savegame. Extras are packed + in four to a character in hex notation. 'index' is a mapping of + savegame bit -> base bit. +****************************************************************************/ +static void sg_extras_set_bv(bv_extras *extras, char ch, + struct extra_type **idx) { int i, bin; const char *pch = strchr(hex_chars, ch); @@ -1120,8 +1157,41 @@ static void sg_extras_set(bv_extras *extras, char ch, struct extra_type **idx) Extras are packed in four to a character in hex notation. 'index' specifies which set of extras are included in this character. ****************************************************************************/ -static char sg_extras_get(bv_extras extras, struct extra_type *presource, - const int *idx) +static char sg_extras_get_dbv(struct dbv *extras, struct extra_type *presource, + const int *idx) +{ + int i, bin = 0; + int max = dbv_bits(extras); + + for (i = 0; i < 4; i++) { + int extra = idx[i]; + + if (extra < 0) { + break; + } + + if (extra < max + && (dbv_isset(extras, extra) + /* An invalid resource, a resource that can't exist at the tile's + * current terrain, isn't in the bit extra vector. Save it so it + * can return if the tile's terrain changes to something it can + * exist on. */ + || extra_by_number(extra) == presource)) { + bin |= (1 << i); + } + } + + return hex_chars[bin]; +} + +/************************************************************************//** + Helper function for saving extras into a savegame. + + Extras are packed in four to a character in hex notation. 'index' + specifies which set of extras are included in this character. +****************************************************************************/ +static char sg_extras_get_bv(bv_extras extras, struct extra_type *presource, + const int *idx) { int i, bin = 0; @@ -2905,7 +2975,8 @@ static void sg_load_map_tiles_extras(struct loaddata *loading) /* Load extras. */ halfbyte_iterate_extras(j, loading->extra.size) { - LOAD_MAP_CHAR(ch, ptile, sg_extras_set(&ptile->extras, ch, loading->extra.order + 4 * j), + LOAD_MAP_CHAR(ch, ptile, sg_extras_set_bv(&ptile->extras, ch, + loading->extra.order + 4 * j), loading->file, "map.e%02d_%04d", j); } halfbyte_iterate_extras_end; @@ -2946,7 +3017,7 @@ static void sg_save_map_tiles_extras(struct savedata *saving) mod[l] = 4 * j + l; } } - SAVE_MAP_CHAR(ptile, sg_extras_get(ptile->extras, ptile->resource, mod), + SAVE_MAP_CHAR(ptile, sg_extras_get_bv(ptile->extras, ptile->resource, mod), saving->file, "map.e%02d_%04d", j); } halfbyte_iterate_extras_end; } @@ -6975,8 +7046,8 @@ static void sg_load_player_vision(struct loaddata *loading, /* Load player map (extras). */ halfbyte_iterate_extras(j, loading->extra.size) { LOAD_MAP_CHAR(ch, ptile, - sg_extras_set(&map_get_player_tile(ptile, plr)->extras, - ch, loading->extra.order + 4 * j), + sg_extras_set_dbv(&(map_get_player_tile(ptile, plr)->extras), + ch, loading->extra.order + 4 * j), loading->file, "player%d.map_e%02d_%04d", plrno, j); } halfbyte_iterate_extras_end; @@ -7295,9 +7366,9 @@ static void sg_save_player_vision(struct savedata *saving, } SAVE_MAP_CHAR(ptile, - sg_extras_get(map_get_player_tile(ptile, plr)->extras, - map_get_player_tile(ptile, plr)->resource, - mod), + sg_extras_get_dbv(&(map_get_player_tile(ptile, plr)->extras), + map_get_player_tile(ptile, plr)->resource, + mod), saving->file, "player%d.map_e%02d_%04d", plrno, j); } halfbyte_iterate_extras_end; diff --git a/server/unittools.c b/server/unittools.c index 8a96dc6576..f43826d8ad 100644 --- a/server/unittools.c +++ b/server/unittools.c @@ -2971,13 +2971,18 @@ bool do_paradrop(struct unit *punit, struct tile *ptile, /* Only take in account values from player map. */ const struct player_tile *plrtile = map_get_player_tile(ptile, pplayer); - if (NULL == plrtile->site - && !is_native_to_class(unit_class_get(punit), plrtile->terrain, - &(plrtile->extras))) { - notify_player(pplayer, ptile, E_BAD_COMMAND, ftc_server, - _("This unit cannot paradrop into %s."), - terrain_name_translation(plrtile->terrain)); - return FALSE; + if (NULL == plrtile->site) { + bv_extras fbv; + + dbv_to_bv(fbv.vec, &(plrtile->extras)); + + if (!is_native_to_class(unit_class_get(punit), plrtile->terrain, + &fbv)) { + notify_player(pplayer, ptile, E_BAD_COMMAND, ftc_server, + _("This unit cannot paradrop into %s."), + terrain_name_translation(plrtile->terrain)); + return FALSE; + } } if (NULL != plrtile->site -- 2.40.1