From 13529b3b9001aed7fadff7abc06f02342aa8f016 Mon Sep 17 00:00:00 2001 From: Marko Lindqvist Date: Fri, 6 Jan 2023 22:15:59 +0200 Subject: [PATCH 30/30] Send city original owner information to client This information is available in the full city package only. We don't have that information stored for vision sites, to send to players other than current owner. Even with the full package, only the original owner get the full knowledge. See osdn #46402 Signed-off-by: Marko Lindqvist --- ai/default/daieffects.c | 2 +- client/packhand.c | 22 ++++++++++++--------- common/city.c | 5 ++++- common/city.h | 2 +- common/networking/packets.def | 1 + common/player.c | 2 +- common/requirements.c | 14 ++++++++++++-- fc_version | 2 +- server/citytools.c | 36 ++++++++++++++++++++++++++++++++--- server/citytools.h | 5 ++++- server/cityturn.c | 4 ++-- server/diplomats.c | 1 + server/plrhand.c | 5 +++-- server/savegame/savegame3.c | 9 +++++++-- 14 files changed, 84 insertions(+), 26 deletions(-) diff --git a/ai/default/daieffects.c b/ai/default/daieffects.c index 56abaefd0b..4439e36932 100644 --- a/ai/default/daieffects.c +++ b/ai/default/daieffects.c @@ -766,7 +766,7 @@ bool dai_can_requirement_be_met_in_city(const struct requirement *preq, return nation_is_in_current_set(preq->source.value.nation); case VUT_CITYSTATUS: - if (pcity == NULL) { + if (pcity == NULL || pcity->original == NULL) { return preq->present; } if (preq->present) { diff --git a/client/packhand.c b/client/packhand.c index b53e203ea3..9342d3f208 100644 --- a/client/packhand.c +++ b/client/packhand.c @@ -684,17 +684,21 @@ void handle_city_info(const struct packet_city_info *packet) ptile = city_tile(pcity); if (NULL == ptile) { - /* invisible worked city */ + /* Invisible worked city */ city_list_remove(invisible.cities, pcity); city_is_new = TRUE; pcity->tile = pcenter; ptile = pcenter; pcity->owner = powner; - pcity->original = powner; + if (has_capability("city-original", client.conn.capability)) { + pcity->original = player_by_number(packet->original); + } else { + pcity->original = NULL; + } } else if (city_owner(pcity) != powner) { - /* Remember what were the worked tiles. The server won't - * send to us again. */ + /* Remember what were the worked tiles. The server won't + * send them to us again. */ city_tile_iterate_skip_free_worked(city_map_radius_sq_get(pcity), ptile, pworked, _index, _x, _y) { if (pcity == tile_worked(pworked)) { @@ -1178,7 +1182,7 @@ void handle_city_short_info(const struct packet_city_short_info *packet) pcity->tile = pcenter; pcity->owner = powner; - pcity->original = powner; + pcity->original = NULL; whole_map_iterate(&(wld.map), wtile) { if (wtile->worked == pcity) { @@ -3167,7 +3171,7 @@ void handle_tile_info(const struct packet_tile_info *packet) if (NULL == pwork) { char named[MAX_LEN_CITYNAME]; - /* new unseen ("invisible") city, or before city_info */ + /* New unseen ("invisible") city, or before city_info */ fc_snprintf(named, sizeof(named), "%06u", packet->worked); pwork = create_city_virtual(invisible.placeholder, NULL, named); @@ -3179,11 +3183,11 @@ void handle_tile_info(const struct packet_tile_info *packet) log_debug("(%d,%d) invisible city %d, %s", TILE_XY(ptile), pwork->id, city_name_get(pwork)); } else if (NULL == city_tile(pwork)) { - /* old unseen ("invisible") city, or before city_info */ + /* Old unseen ("invisible") city, or before city_info */ if (NULL != powner && city_owner(pwork) != powner) { - /* update placeholder with current owner */ + /* Update placeholder with current owner */ pwork->owner = powner; - pwork->original = powner; + pwork->original = NULL; } } else { /* We have a real (not invisible) city record for this ID, but diff --git a/common/city.c b/common/city.c index 7d8c5604e7..b627ba92f1 100644 --- a/common/city.c +++ b/common/city.c @@ -3305,7 +3305,10 @@ struct city *create_city_virtual(struct player *pplayer, pcity->tile = ptile; fc_assert_ret_val(NULL != pplayer, NULL); /* No unowned cities! */ pcity->owner = pplayer; - pcity->original = pplayer; + + if (is_server()) { + pcity->original = pplayer; + } /* City structure was allocated with fc_calloc(), so contents are initially * zero. There is no need to initialize it a second time. */ diff --git a/common/city.h b/common/city.h index ea0624c3b8..ba4dc2ed82 100644 --- a/common/city.h +++ b/common/city.h @@ -309,7 +309,7 @@ struct city { char *name; struct tile *tile; /* May be NULL, should check! */ struct player *owner; /* Cannot be NULL. */ - struct player *original; /* Cannot be NULL. */ + struct player *original; int id; int style; enum capital_type capital; diff --git a/common/networking/packets.def b/common/networking/packets.def index 3ee5d59f3d..a19573d98c 100644 --- a/common/networking/packets.def +++ b/common/networking/packets.def @@ -696,6 +696,7 @@ PACKET_CITY_INFO = 31; sc, lsend, is-game-info, force, cancel(PACKET_CITY_SHORT_ TILE tile; PLAYER owner; + PLAYER original; add-cap(city-original) CITIZENS size; UINT8 city_radius_sq; UINT8 style; diff --git a/common/player.c b/common/player.c index 7e39b50217..c7dc7930d5 100644 --- a/common/player.c +++ b/common/player.c @@ -803,7 +803,7 @@ int player_number(const struct player *pplayer) Return struct player pointer for the given player index. You can retrieve players that are not in the game (with IDs larger than - player_count). An out-of-range player request will return NULL. + player_count). An out-of-range player request will return NULL. ***********************************************************************/ struct player *player_by_number(const int player_id) { diff --git a/common/requirements.c b/common/requirements.c index 029c1ff379..138f16a818 100644 --- a/common/requirements.c +++ b/common/requirements.c @@ -3650,7 +3650,7 @@ is_age_req_active(const struct req_context *context, } break; case REQ_RANGE_CITY: - if (context->city == NULL) { + if (context->city == NULL || context->city->original == NULL) { return TRI_MAYBE; } else { return BOOL_TO_TRISTATE( @@ -3864,18 +3864,28 @@ is_citystatus_req_active(const struct req_context *context, if (citystatus == CITYS_OWNED_BY_ORIGINAL) { switch (req->range) { case REQ_RANGE_CITY: + if (context->city->original == NULL) { + return TRI_MAYBE; + } return BOOL_TO_TRISTATE(city_owner(context->city) == context->city->original); case REQ_RANGE_TRADEROUTE: { bool found = (city_owner(context->city) == context->city->original); + bool maybe = FALSE; trade_partners_iterate(context->city, trade_partner) { - if (city_owner(trade_partner) == trade_partner->original) { + if (trade_partner->original == NULL) { + maybe = TRUE; + } else if (city_owner(trade_partner) == trade_partner->original) { found = TRUE; break; } } trade_partners_iterate_end; + if (!found && maybe) { + return TRI_MAYBE; + } + return BOOL_TO_TRISTATE(found); } case REQ_RANGE_LOCAL: diff --git a/fc_version b/fc_version index 9cfc5ef920..4eb5df4ec4 100755 --- a/fc_version +++ b/fc_version @@ -70,7 +70,7 @@ DEFAULT_FOLLOW_TAG=S3_1 # - No new mandatory capabilities can be added to the release branch; doing # so would break network capability of supposedly "compatible" releases. # -NETWORK_CAPSTRING="+Freeciv-3.1-network" +NETWORK_CAPSTRING="+Freeciv-3.1-network city-original" FREECIV_DISTRIBUTOR="" if test "x$FREECIV_LABEL_FORCE" != "x" ; then diff --git a/server/citytools.c b/server/citytools.c index 6ac3536a5a..2d16d61610 100644 --- a/server/citytools.c +++ b/server/citytools.c @@ -984,7 +984,7 @@ static void reestablish_city_trade_routes(struct city *pcity) announce_trade_route_removal(pcity, partner, FALSE); } - /* refresh regardless; either it lost a trade route or the trade + /* Refresh regardless; either it lost a trade route or the trade * route revenue changed. */ city_refresh(partner); send_city_info(city_owner(partner), partner); @@ -2210,6 +2210,7 @@ void broadcast_city_info(struct city *pcity) if (!send_city_suppressed || pplayer != powner) { if (can_player_see_city_internals(pplayer, pcity)) { update_dumb_city(pplayer, pcity); + packet.original = city_original_owner(pcity, pplayer); lsend_packet_city_info(pplayer->connections, &packet, FALSE); lsend_packet_city_nationalities(pplayer->connections, &nat_packet, FALSE); lsend_packet_city_rally_point(pplayer->connections, &rally_packet, FALSE); @@ -2227,6 +2228,7 @@ void broadcast_city_info(struct city *pcity) } players_iterate_end; /* Send to global observers. */ + packet.original = city_original_owner(pcity, NULL); conn_list_iterate(game.est_connections, pconn) { if (conn_is_global_observer(pconn)) { send_packet_city_info(pconn, &packet, FALSE); @@ -2360,8 +2362,9 @@ void send_city_info_at_tile(struct player *pviewer, struct conn_list *dest, if (pcity) { powner = city_owner(pcity); } - if (powner && powner == pviewer) { - /* send info to owner */ + + if (powner != NULL && powner == pviewer) { + /* Send info to owner */ /* This case implies powner non-NULL which means pcity non-NULL */ if (!send_city_suppressed) { /* Wait that city has been rearranged, if it's currently @@ -2373,6 +2376,7 @@ void send_city_info_at_tile(struct player *pviewer, struct conn_list *dest, update_dumb_city(powner, pcity); package_city(pcity, &packet, &nat_packet, &rally_packet, &web_packet, routes, FALSE); + packet.original = city_original_owner(pcity, pviewer); lsend_packet_city_info(dest, &packet, FALSE); lsend_packet_city_nationalities(dest, &nat_packet, FALSE); lsend_packet_city_rally_point(dest, &rally_packet, FALSE); @@ -2382,6 +2386,7 @@ void send_city_info_at_tile(struct player *pviewer, struct conn_list *dest, } traderoute_packet_list_iterate_end; if (dest == powner->connections) { /* HACK: send also a copy to global observers. */ + packet.original = city_original_owner(pcity, NULL); conn_list_iterate(game.est_connections, pconn) { if (conn_is_global_observer(pconn)) { send_packet_city_info(pconn, &packet, FALSE); @@ -2436,6 +2441,8 @@ void send_city_info_at_tile(struct player *pviewer, struct conn_list *dest, /************************************************************************//** Fill city info packet with information about given city. + This can't set 'original', as it depends on who is about to receive + the package whether they know true value of that. ****************************************************************************/ void package_city(struct city *pcity, struct packet_city_info *packet, struct packet_city_nationalities *nat_packet, @@ -2451,6 +2458,7 @@ void package_city(struct city *pcity, struct packet_city_info *packet, packet->id = pcity->id; packet->owner = player_number(city_owner(pcity)); + packet->tile = tile_index(city_tile(pcity)); sz_strlcpy(packet->name, city_name_get(pcity)); @@ -3489,3 +3497,25 @@ int city_production_buy_gold_cost(const struct city *pcity) return FC_INFINITY; } + +/************************************************************************//** + Return city's original owner id, as known by specified player. + NULL known_for is expected to mean global observer. +****************************************************************************/ +int city_original_owner(const struct city *pcity, + const struct player *known_for) +{ + if (pcity->original == NULL) { + /* Nobody knows */ + return MAX_NUM_PLAYER_SLOTS; + } + + if (pcity->original != known_for + || known_for == NULL) { + /* Players know what they have built themselves, + * global observer knows everything. */ + return player_number(pcity->original); + } + + return MAX_NUM_PLAYER_SLOTS; +} diff --git a/server/citytools.h b/server/citytools.h index dbf7fb49a4..6d9ae0f8d1 100644 --- a/server/citytools.h +++ b/server/citytools.h @@ -127,4 +127,7 @@ void package_and_send_worker_tasks(struct city *pcity); int city_production_buy_gold_cost(const struct city *pcity); -#endif /* FC__CITYTOOLS_H */ +int city_original_owner(const struct city *pcity, + const struct player *known_for); + +#endif /* FC__CITYTOOLS_H */ diff --git a/server/cityturn.c b/server/cityturn.c index 774e4436cc..46ea74bac8 100644 --- a/server/cityturn.c +++ b/server/cityturn.c @@ -3239,9 +3239,9 @@ int city_incite_cost(struct player *pplayer, struct city *pcity) if (!game.info.citizen_nationality) { if (city_owner(pcity) != pcity->original) { if (pplayer == pcity->original) { - cost /= 2; /* buy back: 50% price reduction */ + cost /= 2; /* Buy back: 50% price reduction */ } else { - cost = cost * 2 / 3; /* buy conquered: 33% price reduction */ + cost = cost * 2 / 3; /* Buy conquered: 33% price reduction */ } } } diff --git a/server/diplomats.c b/server/diplomats.c index 33d9435ab8..1fc3e73f95 100644 --- a/server/diplomats.c +++ b/server/diplomats.c @@ -381,6 +381,7 @@ bool diplomat_investigate(struct player *pplayer, struct unit *pdiplomat, &web_packet, routes, TRUE); /* We need to force to send the packet to ensure the client will receive * something and popup the city dialog. */ + city_packet.original = city_original_owner(pcity, pplayer); lsend_packet_city_info(pplayer->connections, &city_packet, TRUE); lsend_packet_city_nationalities(pplayer->connections, &nat_packet, TRUE); lsend_packet_city_rally_point(pplayer->connections, &rally_packet, TRUE); diff --git a/server/plrhand.c b/server/plrhand.c index 191a193c89..bdca1cad11 100644 --- a/server/plrhand.c +++ b/server/plrhand.c @@ -169,9 +169,10 @@ void kill_player(struct player *pplayer) /* Transfer back all cities not originally owned by player to their rightful owners, if they are still around */ save_palace = game.server.savepalace; - game.server.savepalace = FALSE; /* moving it around is dumb */ + game.server.savepalace = FALSE; /* Moving it around is dumb */ city_list_iterate_safe(pplayer->cities, pcity) { - if (pcity->original != pplayer && pcity->original->is_alive) { + if (pcity->original != pplayer && pcity->original != NULL + && pcity->original->is_alive) { /* Transfer city to original owner, kill all its units outside of a radius of 3, give verbose messages of every unit transferred, and raze buildings according to raze chance (also removes palace) */ diff --git a/server/savegame/savegame3.c b/server/savegame/savegame3.c index 905153e5cf..76d9749126 100644 --- a/server/savegame/savegame3.c +++ b/server/savegame/savegame3.c @@ -5359,8 +5359,13 @@ static void sg_save_player_cities(struct savedata *saving, secfile_insert_int(saving->file, pcity->id, "%s.id", buf); - secfile_insert_int(saving->file, player_number(pcity->original), - "%s.original", buf); + if (pcity->original != NULL) { + secfile_insert_int(saving->file, player_number(pcity->original), + "%s.original", buf); + } else { + /* Should never be the case on server side */ + secfile_insert_int(saving->file, -1, "%s.original", buf); + } secfile_insert_int(saving->file, city_size_get(pcity), "%s.size", buf); j = 0; -- 2.39.0