diff --git a/code/hud/hudsquadmsg.cpp b/code/hud/hudsquadmsg.cpp index 71369307009..64d6df39931 100644 --- a/code/hud/hudsquadmsg.cpp +++ b/code/hud/hudsquadmsg.cpp @@ -432,8 +432,7 @@ int hud_squadmsg_count_wings( int add_to_menu ) count++; if ( add_to_menu ) { Assert ( Num_menu_items < MAX_MENU_ITEMS ); - MsgItems[Num_menu_items].text = Wings[wingnum].name; - end_string_at_first_hash_symbol(MsgItems[Num_menu_items].text); + MsgItems[Num_menu_items].text = Wings[wingnum].get_display_name(); MsgItems[Num_menu_items].instance = wingnum; MsgItems[Num_menu_items].active = 1; Num_menu_items++; @@ -454,8 +453,7 @@ int hud_squadmsg_count_wings( int add_to_menu ) count++; if ( add_to_menu ) { Assert ( Num_menu_items < MAX_MENU_ITEMS ); - MsgItems[Num_menu_items].text = Wings[i].name; - end_string_at_first_hash_symbol(MsgItems[Num_menu_items].text); + MsgItems[Num_menu_items].text = Wings[i].get_display_name(); MsgItems[Num_menu_items].instance = i; MsgItems[Num_menu_items].active = 1; Num_menu_items++; @@ -2059,7 +2057,6 @@ void hud_squadmsg_reinforcement_select() Num_menu_items = 0; for (i = 0; i < Num_reinforcements; i++) { rp = &Reinforcements[i]; - SCP_string rp_name = rp->name; // don't put reinforcements onto the list that have already been used up. if ( rp->num_uses >= rp->uses ){ @@ -2075,6 +2072,7 @@ void hud_squadmsg_reinforcement_select() // Goober5000 - if it can't arrive, it doesn't count. This should check // for SEXP_FALSE as well as SEXP_KNOWN_FALSE, otherwise you end up with // a reinforcement menu containing no valid selections. + SCP_string rp_name; if ( (wingnum = wing_name_lookup(rp->name, 1)) != -1 ) { Assert ( Wings[wingnum].arrival_cue >= 0 ); if ( Sexp_nodes[Wings[wingnum].arrival_cue].value == SEXP_FALSE @@ -2082,7 +2080,7 @@ void hud_squadmsg_reinforcement_select() continue; } - end_string_at_first_hash_symbol(rp_name); + rp_name = Wings[wingnum].get_display_name(); // this will handle getting rid of the hash if necessary } else { p_object *p_objp; diff --git a/code/hud/hudwingmanstatus.cpp b/code/hud/hudwingmanstatus.cpp index 8df61f0c717..54896136fb9 100644 --- a/code/hud/hudwingmanstatus.cpp +++ b/code/hud/hudwingmanstatus.cpp @@ -582,18 +582,19 @@ void HudGaugeWingmanStatus::renderDots(int wing_index, int screen_index, int num // check if using full names or default abbreviations before rendering text char wingstr[NAME_LENGTH]; + auto wing_name = config + ? HC_wingam_gauge_status_names[wing_index] + : (Squadron_wings[wing_index] >= 0) + ? Wings[Squadron_wings[wing_index]].get_display_name() + : Squadron_wing_names[wing_index]; if (use_full_wingnames) { // wookieejedi - use full wing name with unaltered capitalization - strcpy_s(wingstr, config ? HC_wingam_gauge_status_names[wing_index] : Squadron_wing_names[wing_index]); + strncpy_s(wingstr, wing_name, NAME_LENGTH-1); } else { // Goober5000 - get the lowercase abbreviation - char abbrev[4]; - abbrev[0] = SCP_tolower(config ? HC_wingam_gauge_status_names[wing_index][0] : Squadron_wing_names[wing_index][0]); - abbrev[1] = SCP_tolower(config ? HC_wingam_gauge_status_names[wing_index][1] : Squadron_wing_names[wing_index][1]); - abbrev[2] = SCP_tolower(config ? HC_wingam_gauge_status_names[wing_index][2] : Squadron_wing_names[wing_index][2]); - abbrev[3] = '\0'; - strncpy(wingstr, abbrev, 4); + strncpy_s(wingstr, wing_name, 3); + SCP_tolower(wingstr); } int wingstr_width; diff --git a/code/mission/missionhotkey.cpp b/code/mission/missionhotkey.cpp index 18a73de7889..3038b5ccf1a 100644 --- a/code/mission/missionhotkey.cpp +++ b/code/mission/missionhotkey.cpp @@ -635,7 +635,6 @@ int hotkey_build_team_listing(int enemy_team_mask, int y, bool list_enemies) for (int i=0; i= 0) { @@ -677,8 +676,7 @@ int hotkey_build_team_listing(int enemy_team_mask, int y, bool list_enemies) if ( j < Wings[i].current_count ) continue; - strcpy_s(wing_name, Wings[i].name); - end_string_at_first_hash_symbol(wing_name); + auto wing_name = Wings[i].get_display_name(); int z = hotkey_line_add_sorted(wing_name, HotkeyLineType::WING, i, start); if (Wings[i].flags[Ship::Wing_Flags::Expanded]) { diff --git a/code/mission/missionlog.cpp b/code/mission/missionlog.cpp index de017527f02..42d54ebb4de 100644 --- a/code/mission/missionlog.cpp +++ b/code/mission/missionlog.cpp @@ -172,6 +172,8 @@ void mission_log_add_entry(LogType type, const char *pname, const char *sname, i Assert(index != -1); Assert(info_index != -1); // this is the team value + entry.pname_display = Wings[index].get_display_name(); + // get the team value for this wing. Departed or destroyed wings will pass the team // value in info_index parameter. For arriving wings, get the team value from the // first ship in the list because the info_index contains the wave count diff --git a/code/mission/missionparse.cpp b/code/mission/missionparse.cpp index e89ec552e4d..1ca35e6a6d5 100644 --- a/code/mission/missionparse.cpp +++ b/code/mission/missionparse.cpp @@ -4685,18 +4685,7 @@ int parse_wing_create_ships( wing *wingp, int num_to_create, bool force_create, wingp->total_arrived_count++; if (wingp->num_waves > 1) { - bool needs_display_name; - wing_bash_ship_name(p_objp->name, wingp->name, wingp->total_arrived_count + wingp->red_alert_skipped_ships, &needs_display_name); - - // set up display name if we need to - // (In the unlikely edge case where the ship already has a display name for some reason, it will be overwritten. - // This is unavoidable, because if we didn't overwrite display names, all waves would have the display name from the first wave.) - if (needs_display_name) - { - p_objp->display_name = p_objp->name; - end_string_at_first_hash_symbol(p_objp->display_name); - p_objp->flags.set(Mission::Parse_Object_Flags::SF_Has_display_name); - } + wing_bash_ship_name(p_objp, wingp, wingp->total_arrived_count + wingp->red_alert_skipped_ships); // subsequent waves of ships will not be in the ship registry, so add them if (!ship_registry_exists(p_objp->name)) @@ -4880,6 +4869,20 @@ void parse_wing(mission *pm) error_display(0, NOX("Redundant wing name: %s\n"), wingp->name); wingnum = Num_wings; + // if this name has a hash, create a default display name + if (get_pointer_to_first_hash_symbol(wingp->name)) + { + wingp->display_name = wingp->name; + end_string_at_first_hash_symbol(wingp->display_name); + wingp->flags.set(Ship::Wing_Flags::Has_display_name); + } + + if (optional_string("$Display Name:")) + { + stuff_string(wingp->display_name, F_NAME); + wingp->flags.set(Ship::Wing_Flags::Has_display_name); + } + // squad logo - Goober5000 if (optional_string("+Squad Logo:")) { @@ -9637,8 +9640,17 @@ bool check_for_25_1_data() return true; } - return std::any_of(Jump_nodes.begin(), Jump_nodes.end(), [defaultLayer](const auto& jn) { + if (std::any_of(Jump_nodes.begin(), Jump_nodes.end(), [defaultLayer](const auto& jn) { const auto& layer = jn.GetFredLayer(); return !layer.empty() && !lcase_equal(layer, defaultLayer); - }); + })) + return true; + + for (int wingnum = 0; wingnum < Num_wings; wingnum++) + { + if (Wings[wingnum].has_display_name()) + return true; + } + + return false; } diff --git a/code/missioneditor/missionsave.cpp b/code/missioneditor/missionsave.cpp index d1cbcb99225..5a6d4002e3a 100644 --- a/code/missioneditor/missionsave.cpp +++ b/code/missioneditor/missionsave.cpp @@ -3458,7 +3458,8 @@ int Fred_mission_save::save_objects() count++; // Display name - // The display name is only written if there was one at the start to avoid introducing inconsistencies + // (If we are always saving display names, the "only/not written" comments do not apply) + // The display name is only written if it currently exists and the ship is not part of a wing, to avoid introducing inconsistencies if (save_config.save_format != MissionFormat::RETAIL && ((save_config.always_save_display_names && shipp->wingnum < 0) || shipp->has_display_name())) { char truncated_name[NAME_LENGTH]; strcpy_s(truncated_name, shipp->ship_name); @@ -3466,10 +3467,10 @@ int Fred_mission_save::save_objects() // Also, the display name is not written if it's just the truncation of the name at the hash if ((save_config.always_save_display_names && shipp->wingnum < 0) || strcmp(shipp->get_display_name(), truncated_name) != 0) { - if (optional_string_fred("$Display name:", "$Class:")) { + if (optional_string_fred("$Display Name:", "$Class:")) { parse_comments(); } else { - fout("\n$Display name:"); + fout("\n$Display Name:"); } fout_ext(" ", "%s", shipp->get_display_name()); } @@ -4807,7 +4808,8 @@ int Fred_mission_save::save_waypoints() if (save_config.save_format != MissionFormat::RETAIL) { - // The display name is only written if there was one at the start to avoid introducing inconsistencies + // (If we are always saving display names, the "only/not written" comments do not apply) + // The display name is only written if it currently exists, to avoid introducing inconsistencies if (save_config.always_save_display_names || jnp->HasDisplayName()) { char truncated_name[NAME_LENGTH]; strcpy_s(truncated_name, jnp->GetName()); @@ -4961,6 +4963,25 @@ int Fred_mission_save::save_wings() count++; + // Display name + // (If we are always saving display names, the "only/not written" comments do not apply) + // The display name is only written if it currently exists, to avoid introducing inconsistencies + if (save_config.save_format != MissionFormat::RETAIL && (save_config.always_save_display_names || w.has_display_name())) { + char truncated_name[NAME_LENGTH]; + strcpy_s(truncated_name, w.name); + end_string_at_first_hash_symbol(truncated_name); + + // Also, the display name is not written if it's just the truncation of the name at the hash + if (save_config.always_save_display_names || strcmp(w.get_display_name(), truncated_name) != 0) { + if (optional_string_fred("$Display Name:", "$Waves:")) { + parse_comments(); + } else { + fout("\n$Display Name:"); + } + fout_ext(" ", "%s", w.get_display_name()); + } + } + // squad logo - Goober5000 if (save_config.save_format != MissionFormat::RETAIL) { if (strlen(w.wing_squad_filename) > 0) //-V805 diff --git a/code/missionui/missionshipchoice.cpp b/code/missionui/missionshipchoice.cpp index b7051b043a7..0ae135caf5b 100644 --- a/code/missionui/missionshipchoice.cpp +++ b/code/missionui/missionshipchoice.cpp @@ -2173,14 +2173,12 @@ void draw_wing_block(int wb_num, int hot_slot, int selected_slot, int class_sele // print the wing name under the wing wp = &Wings[wb->wingnum]; - char name[NAME_LENGTH]; - strcpy_s(name, wp->name); - end_string_at_first_hash_symbol(name); - gr_get_string_size(&w, &h, name); + auto wing_name = wp->get_display_name(); + gr_get_string_size(&w, &h, wing_name); sx = Wing_icon_coords[gr_screen.res][wb_num*MAX_WING_SLOTS][0] + 16 - w/2; sy = Wing_icon_coords[gr_screen.res][wb_num*MAX_WING_SLOTS + 3][1] + 32 + h; gr_set_color_fast(&Color_normal); - gr_string(sx, sy, name, GR_RESIZE_MENU); + gr_string(sx, sy, wing_name, GR_RESIZE_MENU); for ( i = 0; i < MAX_WING_SLOTS; i++ ) { GR_DEBUG_SCOPE("Single ship"); diff --git a/code/missionui/redalert.cpp b/code/missionui/redalert.cpp index 261d7737ed3..702e52f8c6c 100644 --- a/code/missionui/redalert.cpp +++ b/code/missionui/redalert.cpp @@ -968,7 +968,7 @@ void red_alert_bash_ship_status() // give the ship its name from the latest wave // (this will make the ship match to the correct red-alert data) - wing_bash_ship_name(shipp->ship_name, wingp->name, ((rws->latest_wave - 1) * wingp->wave_count) + 1 + pos_in_wing); + wing_bash_ship_name(shipp, wingp, ((rws->latest_wave - 1) * wingp->wave_count) + 1 + pos_in_wing); // need to update the ship registry too strcpy_s(Ship_registry[ship_entry_index].name, shipp->ship_name); Ship_registry_map[shipp->ship_name] = ship_entry_index; diff --git a/code/network/multi_ingame.cpp b/code/network/multi_ingame.cpp index 2684556530b..4ca24dd5de4 100644 --- a/code/network/multi_ingame.cpp +++ b/code/network/multi_ingame.cpp @@ -1332,7 +1332,7 @@ void process_ingame_wings_packet( ubyte *data, header *hinfo ) // kind of stupid, but bash the name since it won't get recreated properly from // the parse_wing_create_ships call. shipp = &Ships[shipnum]; - wing_bash_ship_name(shipp->ship_name, wingp->name, which_one + 1); + wing_bash_ship_name(shipp, wingp, which_one + 1); nprintf(("Network", "Created %s\n", shipp->ship_name)); objp = &Objects[shipp->objnum]; diff --git a/code/network/multiteamselect.cpp b/code/network/multiteamselect.cpp index cdd3f673ce0..87a92a82656 100644 --- a/code/network/multiteamselect.cpp +++ b/code/network/multiteamselect.cpp @@ -814,7 +814,7 @@ void multi_ts_sync_interface() void multi_ts_assign_players_all() { int idx,team_index,slot_index,found,player_count,shipnum; - char name_lookup[100]; + char name_lookup[NAME_LENGTH]; object *objp; // set all player ship indices to -1 @@ -833,12 +833,10 @@ void multi_ts_assign_players_all() // always assign the host to the wing leader of one of the TVT wings // this is valid for coop games as well because the first starting wing // and the first tvt wing must have the same name - memset(name_lookup,0,100); if(Netgame.type_flags & NG_TYPE_TEAM) { - sprintf(name_lookup, "%s 1", TVT_wing_names[Netgame.host->p_info.team]); - } - else { + wing_bash_ship_name(name_lookup, TVT_wing_names[Netgame.host->p_info.team], 1); + } else { // To account for cases where 1 is not a player ship for (int i = 0; i < MAX_SHIPS_PER_WING; i++) { wing_bash_ship_name(name_lookup, TVT_wing_names[0], i + 1); diff --git a/code/scripting/api/objs/wing.cpp b/code/scripting/api/objs/wing.cpp index dcfa283875c..6735467c07f 100644 --- a/code/scripting/api/objs/wing.cpp +++ b/code/scripting/api/objs/wing.cpp @@ -47,7 +47,7 @@ ADE_FUNC(__len, l_Wing, NULL, "Gets the number of ships in the wing", "number", return ade_set_args(L, "i", Wings[wdx].current_count); } -ADE_VIRTVAR(Name, l_Wing, "string", "Name of Wing", "string", "Wing name, or empty string if handle is invalid") +ADE_VIRTVAR(Name, l_Wing, "string", "Wing name. This is the actual name of the wing. Use getDisplayString to get the string which should be displayed to the player.", "string", "Wing name, or empty string if handle is invalid") { int wdx; const char* s = nullptr; @@ -63,6 +63,29 @@ ADE_VIRTVAR(Name, l_Wing, "string", "Name of Wing", "string", "Wing name, or emp return ade_set_args(L, "s", Wings[wdx].name); } +ADE_VIRTVAR(DisplayName, l_Wing, "string", "Wing display name", "string", "The display name of the wing or empty if there is no display string") +{ + int wingnum = -1; + const char* s = nullptr; + if (!ade_get_args(L, "o|s", l_Wing.Get(&wingnum), &s)) + return ade_set_error(L, "s", ""); + + if (wingnum < 0 || wingnum >= Num_wings) + return ade_set_error(L, "s", ""); + + auto wingp = &Wings[wingnum]; + + if (ADE_SETTING_VAR && s != nullptr) + { + wingp->display_name = s; + + // for compatibility reasons, if we are setting this to the empty string, clear the flag + wingp->flags.set(Ship::Wing_Flags::Has_display_name, s[0] != 0); + } + + return ade_set_args(L, "s", wingp->display_name.c_str()); +} + ADE_FUNC(isValid, l_Wing, NULL, "Detects whether handle is valid", "boolean", "true if valid, false if handle is invalid, nil if a syntax/type error occurs") { int idx; @@ -374,6 +397,18 @@ ADE_VIRTVAR(WaveDelayMaximum, l_Wing, "number", "The wing's maximum wave delay", return wing_getset_helper(L, &wing::wave_delay_max, true); } +ADE_FUNC(getDisplayString, l_Wing, nullptr, "Returns the string which should be used when displaying the name of the wing to the player", "string", "The display string or empty if handle is invalid") +{ + int wingnum = -1; + if (!ade_get_args(L, "o", l_Wing.Get(&wingnum))) + return ade_set_error(L, "s", ""); + + if (wingnum < 0 || wingnum >= Num_wings) + return ade_set_error(L, "s", ""); + + return ade_set_args(L, "s", Wings[wingnum].get_display_name()); +} + } } diff --git a/code/ship/ship.cpp b/code/ship/ship.cpp index fb5f6107e62..9181de02185 100644 --- a/code/ship/ship.cpp +++ b/code/ship/ship.cpp @@ -148,7 +148,7 @@ int Starting_wings[MAX_STARTING_WINGS]; // wings player starts a mission with ( int Squadron_wings[MAX_SQUADRON_WINGS]; int TVT_wings[MAX_TVT_WINGS]; -// Goober5000 +// Goober5000 - note, these are the real names, not the display names char Starting_wing_names[MAX_STARTING_WINGS][NAME_LENGTH]; char Squadron_wing_names[MAX_SQUADRON_WINGS][NAME_LENGTH]; char TVT_wing_names[MAX_TVT_WINGS][NAME_LENGTH]; @@ -7574,6 +7574,7 @@ ship_weapon::ship_weapon() { void wing::clear() { name[0] = '\0'; + display_name.clear(); wing_squad_filename[0] = '\0'; reinforcement_index = -1; hotkey = -1; @@ -7633,6 +7634,19 @@ void wing::clear() formation_scale = 1.0f; } +const char *wing::get_display_name() const +{ + if (has_display_name()) + return display_name.c_str(); + else + return name; +} + +bool wing::has_display_name() const +{ + return flags[Ship::Wing_Flags::Has_display_name]; +} + // NOTE: Now that the clear() member function exists, this function only sets the stuff associated with the object and ship class. static void ship_set(int ship_index, int objnum, int ship_type) { @@ -15204,27 +15218,69 @@ int get_available_secondary_weapons(object *objp, int *outlist, int *outbanklist return count; } -void wing_bash_ship_name(char *ship_name, const char *wing_name, int index, bool *needs_display_name) +void wing_bash_ship_name(SCP_string &ship_name, const char *wing_name, int index) +{ + // always create the name this way; display names are handled in the p_object* and ship* functions + sprintf(ship_name, NOX("%s %d"), wing_name, index); +} + +void wing_bash_ship_name(char *ship_name, const char *wing_name, int index) { - if (needs_display_name) - *needs_display_name = false; + static_assert(MAX_SHIPS_PER_WING < 10, "If two-digit wingman indexes are possible, this function will need to be modified."); + constexpr size_t max_name_len = NAME_LENGTH - 3; - // if wing name has a hash symbol, create the ship name a particular way - // (but don't do this for names that have the hash as the first or last character) - const char *p = get_pointer_to_first_hash_symbol(wing_name); - if ((p != NULL) && (p != wing_name) && (*(p+1) != '\0')) + // truncate name if too long + if (strlen(wing_name) > max_name_len) { - size_t len = (p - wing_name); - strncpy(ship_name, wing_name, len); - sprintf(ship_name + len, NOX(" %d"), index); - strcat(ship_name, p); - - if (needs_display_name) - *needs_display_name = true; + Warning(LOCATION, "Wing name %s is too long; truncating name for ship", wing_name); + strncpy(ship_name, wing_name, max_name_len); + sprintf(ship_name + max_name_len, NOX("%d"), index); } - // most of the time we should create the name the standard retail way else + { + // always create the name this way; display names are handled in the p_object* and ship* functions sprintf(ship_name, NOX("%s %d"), wing_name, index); + } +} + +void wing_bash_ship_name(p_object *p_objp, const wing *wingp, int index, bool reset_display_name_if_normal) +{ + // always update the real name + wing_bash_ship_name(p_objp->name, wingp->name, index); + + // also set up the display name if we have one + // (In the unlikely edge case where the ship already has a display name for some reason, it will be overwritten. + // This is unavoidable, because if we didn't overwrite display names, all waves would have the display name from the first wave.) + if (wingp->has_display_name()) + { + wing_bash_ship_name(p_objp->display_name, wingp->get_display_name(), index); + p_objp->flags.set(Mission::Parse_Object_Flags::SF_Has_display_name); + } + else if (reset_display_name_if_normal) + { + p_objp->display_name = ""; + p_objp->flags.remove(Mission::Parse_Object_Flags::SF_Has_display_name); + } +} + +void wing_bash_ship_name(ship *shipp, const wing *wingp, int index, bool reset_display_name_if_normal) +{ + // always update the real name + wing_bash_ship_name(shipp->ship_name, wingp->name, index); + + // also set up the display name if we have one + // (In the unlikely edge case where the ship already has a display name for some reason, it will be overwritten. + // This is unavoidable, because if we didn't overwrite display names, all waves would have the display name from the first wave.) + if (wingp->has_display_name()) + { + wing_bash_ship_name(shipp->display_name, wingp->get_display_name(), index); + shipp->flags.set(Ship::Ship_Flags::Has_display_name); + } + else if (reset_display_name_if_normal) + { + shipp->display_name = ""; + shipp->flags.remove(Ship::Ship_Flags::Has_display_name); + } } /** diff --git a/code/ship/ship.h b/code/ship/ship.h index 788b8c86215..e02c6995ca8 100644 --- a/code/ship/ship.h +++ b/code/ship/ship.h @@ -1575,6 +1575,8 @@ extern SCP_vector Engine_wash_info; // Defines a wing of ships. typedef struct wing { char name[NAME_LENGTH]; + SCP_string display_name; + char wing_squad_filename[MAX_FILENAME_LEN]; // Goober5000 int reinforcement_index; // index in reinforcement struct or -1 int hotkey; @@ -1634,6 +1636,9 @@ typedef struct wing { // reset to a completely blank wing void clear(); + + const char *get_display_name() const; + bool has_display_name() const; } wing; extern wing Wings[MAX_WINGS]; @@ -1770,8 +1775,11 @@ extern int wing_name_lookup(const char *name, int ignore_count = 0); extern bool wing_has_yet_to_arrive(const wing *wingp); -// for generating a ship name for arbitrary waves/indexes of that wing... correctly handles the # character -extern void wing_bash_ship_name(char *ship_name, const char *wing_name, int index, bool *needs_display_name = nullptr); +// for generating a ship name for arbitrary waves/indexes of that wing +extern void wing_bash_ship_name(SCP_string &ship_name, const char *wing_name, int index); +extern void wing_bash_ship_name(char *ship_name, const char *wing_name, int index); +extern void wing_bash_ship_name(p_object *p_objp, const wing *wingp, int index, bool reset_display_name_if_normal = false); +extern void wing_bash_ship_name(ship *shipp, const wing *wingp, int index, bool reset_display_name_if_normal = false); extern int Player_ship_class; // Do the special effect for energy dissipating into the shield for a hit. diff --git a/code/ship/ship_flags.h b/code/ship/ship_flags.h index ee4d5c23e1c..aa84be85049 100644 --- a/code/ship/ship_flags.h +++ b/code/ship/ship_flags.h @@ -292,6 +292,7 @@ namespace Ship { Same_departure_warp_when_docked, // Goober5000 No_first_wave_message, // don't play arrival message for the first wave Waypoints_no_formation, // wing will not try to form up when running a waypoint together + Has_display_name, // Goober5000 NUM_VALUES }; diff --git a/fred2/fred.rc b/fred2/fred.rc index bbfefe7442b..a50483e6052 100644 --- a/fred2/fred.rc +++ b/fred2/fred.rc @@ -1231,7 +1231,7 @@ BEGIN LISTBOX IDC_IFF_LIST,128,40,68,100,LBS_NOINTEGRALHEIGHT | LBS_EXTENDEDSEL | WS_VSCROLL | WS_TABSTOP END -IDD_WING_EDITOR DIALOGEX 0, 0, 296, 473 +IDD_WING_EDITOR DIALOGEX 0, 0, 296, 489 STYLE DS_SETFONT | DS_MODALFRAME | DS_3DLOOK | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU EXSTYLE WS_EX_CLIENTEDGE CAPTION "Wing Edit" @@ -1241,75 +1241,77 @@ BEGIN PUSHBUTTON "Prev",IDC_PREV,240,7,24,14,0,WS_EX_STATICEDGE PUSHBUTTON "Next",IDC_NEXT,267,7,24,14,0,WS_EX_STATICEDGE EDITTEXT IDC_WING_NAME,65,7,73,14,ES_AUTOHSCROLL - COMBOBOX IDC_WING_SPECIAL_SHIP,65,23,73,161,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP - EDITTEXT IDC_WING_WAVES,65,37,61,14,ES_AUTOHSCROLL | ES_NUMBER - CONTROL "Spin2",IDC_SPIN_WAVES,"msctls_updown32",UDS_SETBUDDYINT | UDS_ALIGNRIGHT | UDS_AUTOBUDDY | UDS_ARROWKEYS,127,37,11,15 - EDITTEXT IDC_WING_WAVE_THRESHOLD,65,53,61,15,ES_AUTOHSCROLL | ES_NUMBER - CONTROL "Spin2",IDC_SPIN_WAVE_THRESHOLD,"msctls_updown32",UDS_SETBUDDYINT | UDS_ALIGNRIGHT | UDS_AUTOBUDDY | UDS_ARROWKEYS,127,53,11,15 - COMBOBOX IDC_HOTKEY,65,70,73,115,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP - PUSHBUTTON "Squad Logo",IDC_WING_SQUAD_LOGO_BUTTON,7,89,55,14 - EDITTEXT IDC_WING_SQUAD_LOGO,65,90,80,14,ES_AUTOHSCROLL | ES_READONLY + EDITTEXT IDC_WING_DISPLAY_NAME,65,23,73,14,ES_AUTOHSCROLL + COMBOBOX IDC_WING_SPECIAL_SHIP,65,39,73,161,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + EDITTEXT IDC_WING_WAVES,65,53,61,14,ES_AUTOHSCROLL | ES_NUMBER + CONTROL "Spin2",IDC_SPIN_WAVES,"msctls_updown32",UDS_SETBUDDYINT | UDS_ALIGNRIGHT | UDS_AUTOBUDDY | UDS_ARROWKEYS,127,53,11,15 + EDITTEXT IDC_WING_WAVE_THRESHOLD,65,69,61,15,ES_AUTOHSCROLL | ES_NUMBER + CONTROL "Spin2",IDC_SPIN_WAVE_THRESHOLD,"msctls_updown32",UDS_SETBUDDYINT | UDS_ALIGNRIGHT | UDS_AUTOBUDDY | UDS_ARROWKEYS,127,69,11,15 + COMBOBOX IDC_HOTKEY,65,86,73,115,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + PUSHBUTTON "Squad Logo",IDC_WING_SQUAD_LOGO_BUTTON,7,105,55,14 + EDITTEXT IDC_WING_SQUAD_LOGO,65,106,80,14,ES_AUTOHSCROLL | ES_READONLY CONTROL "Reinforcement Unit",IDC_REINFORCEMENT,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,143,10,77,10 CONTROL "Ignore for Counting Goals",IDC_IGNORE_COUNT,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,143,21,96,10 CONTROL "No Arrival Music",IDC_NO_ARRIVAL_MUSIC,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,143,32,67,10 CONTROL "No Arrival Message",IDC_NO_ARRIVAL_MESSAGE,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,143,43,77,10 CONTROL "No First Wave Message",IDC_NO_FIRST_WAVE_MESSAGE,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,143,54,77,10 CONTROL "No Dynamic Goals",IDC_NO_DYNAMIC,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,143,65,75,10 - COMBOBOX IDC_ARRIVAL_LOCATION,47,122,91,127,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP - EDITTEXT IDC_ARRIVAL_DELAY,47,138,40,14,ES_AUTOHSCROLL | ES_NUMBER - CONTROL "Spin3",IDC_ARRIVAL_DELAY_SPIN,"msctls_updown32",UDS_SETBUDDYINT | UDS_ALIGNRIGHT | UDS_AUTOBUDDY | UDS_ARROWKEYS,88,138,11,14 - EDITTEXT IDC_ARRIVAL_DELAY_MIN,39,164,28,14,ES_AUTOHSCROLL | ES_NUMBER - EDITTEXT IDC_ARRIVAL_DELAY_MAX,99,164,28,14,ES_AUTOHSCROLL | ES_NUMBER - COMBOBOX IDC_ARRIVAL_TARGET,46,189,91,188,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP - EDITTEXT IDC_ARRIVAL_DISTANCE,46,203,40,14,ES_AUTOHSCROLL | ES_NUMBER - CONTROL "Tree1",IDC_ARRIVAL_TREE,"SysTreeView32",TVS_HASBUTTONS | TVS_HASLINES | TVS_LINESATROOT | TVS_EDITLABELS | WS_BORDER | WS_HSCROLL | WS_TABSTOP,15,270,121,84,WS_EX_CLIENTEDGE - CONTROL "No Warp Effect",IDC_NO_ARRIVAL_WARP,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,15,356,91,10 + COMBOBOX IDC_ARRIVAL_LOCATION,47,138,91,127,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + EDITTEXT IDC_ARRIVAL_DELAY,47,154,40,14,ES_AUTOHSCROLL | ES_NUMBER + CONTROL "Spin3",IDC_ARRIVAL_DELAY_SPIN,"msctls_updown32",UDS_SETBUDDYINT | UDS_ALIGNRIGHT | UDS_AUTOBUDDY | UDS_ARROWKEYS,88,154,11,14 + EDITTEXT IDC_ARRIVAL_DELAY_MIN,39,180,28,14,ES_AUTOHSCROLL | ES_NUMBER + EDITTEXT IDC_ARRIVAL_DELAY_MAX,99,180,28,14,ES_AUTOHSCROLL | ES_NUMBER + COMBOBOX IDC_ARRIVAL_TARGET,46,205,91,188,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + EDITTEXT IDC_ARRIVAL_DISTANCE,46,219,40,14,ES_AUTOHSCROLL | ES_NUMBER + CONTROL "Tree1",IDC_ARRIVAL_TREE,"SysTreeView32",TVS_HASBUTTONS | TVS_HASLINES | TVS_LINESATROOT | TVS_EDITLABELS | WS_BORDER | WS_HSCROLL | WS_TABSTOP,15,286,121,84,WS_EX_CLIENTEDGE + CONTROL "No Warp Effect",IDC_NO_ARRIVAL_WARP,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,15,372,91,10 CONTROL "Don't Adjust Warp When Docked",IDC_SAME_ARRIVAL_WARP_WHEN_DOCKED, - "Button",BS_3STATE | WS_TABSTOP,15,366,120,10 - COMBOBOX IDC_DEPARTURE_LOCATION,192,122,91,127,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP - EDITTEXT IDC_DEPARTURE_DELAY,192,138,40,14,ES_AUTOHSCROLL | ES_NUMBER - CONTROL "Spin3",IDC_DEPARTURE_DELAY_SPIN,"msctls_updown32",UDS_SETBUDDYINT | UDS_ALIGNRIGHT | UDS_AUTOBUDDY | UDS_ARROWKEYS,234,138,11,14 - COMBOBOX IDC_DEPARTURE_TARGET,188,189,91,188,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP - CONTROL "Tree1",IDC_DEPARTURE_TREE,"SysTreeView32",TVS_HASBUTTONS | TVS_HASLINES | TVS_LINESATROOT | TVS_EDITLABELS | WS_BORDER | WS_HSCROLL | WS_TABSTOP,160,270,121,84,WS_EX_CLIENTEDGE - CONTROL "No Warp Effect",IDC_NO_DEPARTURE_WARP,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,160,356,92,10 + "Button",BS_3STATE | WS_TABSTOP,15,382,120,10 + COMBOBOX IDC_DEPARTURE_LOCATION,192,138,91,127,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + EDITTEXT IDC_DEPARTURE_DELAY,192,154,40,14,ES_AUTOHSCROLL | ES_NUMBER + CONTROL "Spin3",IDC_DEPARTURE_DELAY_SPIN,"msctls_updown32",UDS_SETBUDDYINT | UDS_ALIGNRIGHT | UDS_AUTOBUDDY | UDS_ARROWKEYS,234,154,11,14 + COMBOBOX IDC_DEPARTURE_TARGET,188,205,91,188,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + CONTROL "Tree1",IDC_DEPARTURE_TREE,"SysTreeView32",TVS_HASBUTTONS | TVS_HASLINES | TVS_LINESATROOT | TVS_EDITLABELS | WS_BORDER | WS_HSCROLL | WS_TABSTOP,160,286,121,84,WS_EX_CLIENTEDGE + CONTROL "No Warp Effect",IDC_NO_DEPARTURE_WARP,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,160,372,92,10 CONTROL "Don't Adjust Warp When Docked",IDC_SAME_DEPARTURE_WARP_WHEN_DOCKED, - "Button",BS_3STATE | WS_TABSTOP,160,366,120,10 - EDITTEXT IDC_HELP_BOX,7,396,284,73,ES_MULTILINE | ES_READONLY,WS_EX_TRANSPARENT - LTEXT "Seconds",IDC_STATIC,245,141,33,8 + "Button",BS_3STATE | WS_TABSTOP,160,382,120,10 + EDITTEXT IDC_HELP_BOX,7,412,284,73,ES_MULTILINE | ES_READONLY,WS_EX_TRANSPARENT + LTEXT "Seconds",IDC_STATIC,245,157,33,8 PUSHBUTTON "Delete Wing",IDC_DELETE_WING,241,23,50,14,0,WS_EX_STATICEDGE PUSHBUTTON "Disband Wing",IDC_DISBAND_WING,241,39,50,14,0,WS_EX_STATICEDGE PUSHBUTTON "Initial Orders",IDC_GOALS2,241,55,50,14,0,WS_EX_STATICEDGE CONTROL "Hide Cues",IDC_HIDE_CUES,"Button",BS_AUTOCHECKBOX | BS_PUSHLIKE | WS_TABSTOP,241,71,49,12 - GROUPBOX "Departure",IDC_STATIC,153,110,138,270 + GROUPBOX "Departure",IDC_STATIC,153,126,138,270 RTEXT "Wing Name",IDC_STATIC,19,10,42,8 - RTEXT "Wave Threshold",IDC_STATIC,7,56,54,8 - RTEXT "# of Waves",IDC_STATIC,16,40,45,8 - RTEXT "Leader",IDC_STATIC,38,25,23,8,NOT WS_GROUP - LTEXT "Location",IDC_STATIC,15,124,28,8 - LTEXT "Cue:",IDC_STATIC,15,259,16,8 - GROUPBOX "Arrival",IDC_CUE_FRAME,7,110,138,270 - LTEXT "Location",IDC_STATIC,160,124,28,8 - LTEXT "Cue:",IDC_STATIC,160,259,16,8 - LTEXT "Delay",IDC_STATIC,160,140,19,8 - LTEXT "Delay",IDC_STATIC,15,140,19,8 - LTEXT "Seconds",IDC_STATIC,102,141,45,8 - RTEXT "Hotkey",IDC_STATIC,37,72,24,8 - GROUPBOX "Delay Between Waves",IDC_STATIC,15,154,119,30 - LTEXT "Min",IDC_STATIC,21,167,12,8 - LTEXT "Max",IDC_STATIC,80,167,14,8 - LTEXT "Target",IDC_STATIC,15,191,22,8 - LTEXT "Distance",IDC_STATIC,15,206,29,8 - LTEXT "Target",IDC_STATIC,157,191,22,8 - PUSHBUTTON "Restrict Arrival Paths",IDC_RESTRICT_ARRIVAL,15,223,122,14,0,WS_EX_STATICEDGE - PUSHBUTTON "Restrict Departure Paths",IDC_RESTRICT_DEPARTURE,160,223,123,14,0,WS_EX_STATICEDGE - EDITTEXT IDC_MINI_HELP_BOX,7,384,284,14,ES_MULTILINE | ES_READONLY,WS_EX_DLGMODALFRAME | WS_EX_TRANSPARENT - PUSHBUTTON "Custom Warp-in Parameters",IDC_CUSTOM_WARPIN_PARAMS,15,240,122,14,0,WS_EX_STATICEDGE - PUSHBUTTON "Custom Warp-out Parameters",IDC_CUSTOM_WARPOUT_PARAMS,160,240,123,14,0,WS_EX_STATICEDGE - LTEXT "Formation",IDC_STATIC,154,92,34,8 - COMBOBOX IDC_WING_FORMATION,192,89,76,127,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP - LTEXT "x",IDC_STATIC,192,78,8,8 - EDITTEXT IDC_WING_FORMATION_SCALE,197,77,24,11,ES_AUTOHSCROLL - PUSHBUTTON "Align",IDC_WING_FORMATION_ALIGN,269,89,22,14,0,WS_EX_STATICEDGE + RTEXT "Display Name",IDC_STATIC,7,26,54,8 + RTEXT "Wave Threshold",IDC_STATIC,7,72,54,8 + RTEXT "# of Waves",IDC_STATIC,16,56,45,8 + RTEXT "Leader",IDC_STATIC,38,41,23,8,NOT WS_GROUP + LTEXT "Location",IDC_STATIC,15,140,28,8 + LTEXT "Cue:",IDC_STATIC,15,275,16,8 + GROUPBOX "Arrival",IDC_CUE_FRAME,7,126,138,270 + LTEXT "Location",IDC_STATIC,160,140,28,8 + LTEXT "Cue:",IDC_STATIC,160,275,16,8 + LTEXT "Delay",IDC_STATIC,160,156,19,8 + LTEXT "Delay",IDC_STATIC,15,156,19,8 + LTEXT "Seconds",IDC_STATIC,102,157,45,8 + RTEXT "Hotkey",IDC_STATIC,37,88,24,8 + GROUPBOX "Delay Between Waves",IDC_STATIC,15,170,119,30 + LTEXT "Min",IDC_STATIC,21,183,12,8 + LTEXT "Max",IDC_STATIC,80,183,14,8 + LTEXT "Target",IDC_STATIC,15,207,22,8 + LTEXT "Distance",IDC_STATIC,15,222,29,8 + LTEXT "Target",IDC_STATIC,157,207,22,8 + PUSHBUTTON "Restrict Arrival Paths",IDC_RESTRICT_ARRIVAL,15,239,122,14,0,WS_EX_STATICEDGE + PUSHBUTTON "Restrict Departure Paths",IDC_RESTRICT_DEPARTURE,160,239,123,14,0,WS_EX_STATICEDGE + EDITTEXT IDC_MINI_HELP_BOX,7,400,284,14,ES_MULTILINE | ES_READONLY,WS_EX_DLGMODALFRAME | WS_EX_TRANSPARENT + PUSHBUTTON "Custom Warp-in Parameters",IDC_CUSTOM_WARPIN_PARAMS,15,256,122,14,0,WS_EX_STATICEDGE + PUSHBUTTON "Custom Warp-out Parameters",IDC_CUSTOM_WARPOUT_PARAMS,160,256,123,14,0,WS_EX_STATICEDGE + LTEXT "Formation",IDC_STATIC,154,108,34,8 + COMBOBOX IDC_WING_FORMATION,192,105,76,127,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + LTEXT "x",IDC_STATIC,192,94,8,8 + EDITTEXT IDC_WING_FORMATION_SCALE,197,93,24,11,ES_AUTOHSCROLL + PUSHBUTTON "Align",IDC_WING_FORMATION_ALIGN,269,105,22,14,0,WS_EX_STATICEDGE END IDD_OPERATOR_ARGUMENT_TYPES DIALOG 0, 0, 235, 50 diff --git a/fred2/freddoc.cpp b/fred2/freddoc.cpp index 37bb25fe83f..2164c7b82e7 100644 --- a/fred2/freddoc.cpp +++ b/fred2/freddoc.cpp @@ -317,17 +317,19 @@ bool CFREDDoc::load_mission(const char *pathname, int flags) { if ((Objects[wing_objects[i][j]].type == OBJ_SHIP) || (Objects[wing_objects[i][j]].type == OBJ_START)) { // don't change player ship names wing_bash_ship_name(name, Wings[i].name, j + 1); old_name = Ships[Wings[i].ship_index[j]].ship_name; - if (stricmp(name, old_name)) { // need to fix name + if (stricmp(name, old_name) != 0) { // need to fix name update_sexp_references(old_name, name); ai_update_goal_references(sexp_ref_type::SHIP, old_name, name); update_texture_replacements(old_name, name); - for (k = 0; k < Num_reinforcements; k++) + for (k = 0; k < Num_reinforcements; k++) { if (!strcmp(old_name, Reinforcements[k].name)) { Assert(strlen(name) < NAME_LENGTH); strcpy_s(Reinforcements[k].name, name); } + } - strcpy_s(Ships[Wings[i].ship_index[j]].ship_name, name); + // bash it again so that we handle display names if needed + wing_bash_ship_name(&Ships[Wings[i].ship_index[j]], &Wings[i], j + 1, true); } } } diff --git a/fred2/fredrender.h b/fred2/fredrender.h index ffabc5d6e3c..6c83dd4ddae 100644 --- a/fred2/fredrender.h +++ b/fred2/fredrender.h @@ -24,7 +24,7 @@ extern bool Draw_outlines_on_selected_ships; // If a ship is selected, draw mesh extern bool Draw_outline_at_warpin_position; // Project an outline at the place where the ship will arrive after warping in extern bool Always_save_display_names; // When saving a mission, always write display names to the mission file even if the display name is not set. // But ships in wings are excepted, because a display name will cause a ship to have the same name in every wave. - // In the future, a display name feature could be added to the wing dialog to handle this case. + // Wings now support display names, or the ship-change-display-name SEXP can be used, to handle that case. extern bool Error_checker_checks_potential_issues; // Error checker checks not only outright errors but also potential issues extern bool Error_checker_checks_potential_issues_once; // Same as above, but only once, and independent of the selected option extern int Show_stars; //!< Bool. If nonzero, draw the starfield, nebulas, and suns. Might also handle skyboxes diff --git a/fred2/fredview.cpp b/fred2/fredview.cpp index 56945ec5a41..a5de0275797 100644 --- a/fred2/fredview.cpp +++ b/fred2/fredview.cpp @@ -1141,13 +1141,11 @@ void CFREDView::OnLButtonUp(UINT nFlags, CPoint point) break; } -// Can't do player starts, since only player 1 is currently allowed to be in a wing - + // Can't do player starts, since only player 1 is currently allowed to be in a wing Assert(objp->type == OBJ_SHIP); ship = objp->instance; Assert(Ships[ship].wingnum == -1); - wing_bash_ship_name(Ships[ship].ship_name, Wings[Duped_wing].name, - Wings[Duped_wing].wave_count + 1); + wing_bash_ship_name(&Ships[ship], &Wings[Duped_wing], Wings[Duped_wing].wave_count + 1, true); Wings[Duped_wing].ship_index[Wings[Duped_wing].wave_count] = ship; Ships[ship].wingnum = Duped_wing; diff --git a/fred2/management.cpp b/fred2/management.cpp index b6187d3ad4f..02a29ee6702 100644 --- a/fred2/management.cpp +++ b/fred2/management.cpp @@ -1469,6 +1469,8 @@ int delete_ship_from_wing(int ship) if (Objects[wing_objects[wing][i]].type == OBJ_SHIP) { wing_bash_ship_name(name, Wings[wing].name, i + 1); rename_ship(Wings[wing].ship_index[i], name); + // bash it again for the display name + wing_bash_ship_name(&Ships[Wings[wing].ship_index[i]], &Wings[wing], i + 1, true); } } @@ -1847,6 +1849,26 @@ int rename_ship(int ship, const char *name) if (ship == cur_ship) Ship_editor_dialog.m_ship_name = _T(name); + // if this name has a hash, create a default display name + if (get_pointer_to_first_hash_symbol(Ships[ship].ship_name)) + { + Ships[ship].display_name = Ships[ship].ship_name; + end_string_at_first_hash_symbol(Ships[ship].display_name); + Ships[ship].flags.set(Ship::Ship_Flags::Has_display_name); + + if (ship == cur_ship) + Ship_editor_dialog.m_ship_display_name = _T(Ships[ship].display_name.c_str()); + } + // otherwise reset the display name + else + { + Ships[ship].display_name = ""; + Ships[ship].flags.remove(Ship::Ship_Flags::Has_display_name); + + if (ship == cur_ship) + Ship_editor_dialog.m_ship_display_name = _T(""); + } + return 0; } diff --git a/fred2/resource.h b/fred2/resource.h index 0ba948f0bd9..80075a0f10c 100644 --- a/fred2/resource.h +++ b/fred2/resource.h @@ -715,6 +715,7 @@ #define IDC_MAIN_HALL 1323 #define IDC_DEBRIEFING_PERSONA 1324 #define IDC_DISPLAY_NAME 1325 +#define IDC_WING_DISPLAY_NAME 1326 #define IDC_DOCK1 1327 #define IDC_INNER_MIN_X 1327 #define IDC_DOCK2 1328 diff --git a/fred2/shipeditordlg.cpp b/fred2/shipeditordlg.cpp index c2be434cb0e..8298aa7ef50 100644 --- a/fred2/shipeditordlg.cpp +++ b/fred2/shipeditordlg.cpp @@ -1304,7 +1304,7 @@ int CShipEditorDlg::update_ship(int ship) } else { - if (!Ships[ship].has_display_name()) + if (!Ships[ship].has_display_name() || Ships[ship].display_name != (LPCSTR)m_ship_display_name) set_modified(); Ships[ship].display_name = m_ship_display_name; Ships[ship].flags.set(Ship::Ship_Flags::Has_display_name); diff --git a/fred2/wing.cpp b/fred2/wing.cpp index f1c06dcc027..ef4ad8490d0 100644 --- a/fred2/wing.cpp +++ b/fred2/wing.cpp @@ -177,6 +177,20 @@ int create_wing() { dlg.m_name.TrimLeft(); dlg.m_name.TrimRight(); string_copy(Wings[wing].name, dlg.m_name, NAME_LENGTH - 1); + + // if this name has a hash, create a default display name + if (get_pointer_to_first_hash_symbol(Wings[wing].name)) + { + Wings[wing].display_name = Wings[wing].name; + end_string_at_first_hash_symbol(Wings[wing].display_name); + Wings[wing].flags.set(Ship::Wing_Flags::Has_display_name); + } + // otherwise reset the display name + else + { + Wings[wing].display_name = ""; + Wings[wing].flags.remove(Ship::Wing_Flags::Has_display_name); + } } set_cur_indices(-1); @@ -235,6 +249,8 @@ int create_wing() { wing_bash_ship_name(msg, Wings[wing].name, i + 1); rename_ship(ship, msg); + // bash it again for the display name + wing_bash_ship_name(&Ships[ship], &Wings[wing], i + 1, true); Wings[wing].ship_index[i] = ship; Ships[ship].wingnum = wing; @@ -407,6 +423,8 @@ void remove_ship_from_wing(int ship, int min) { if (Objects[obj].type == OBJ_SHIP) { wing_bash_ship_name(buf, Wings[wing].name, i + 1); rename_ship(Wings[wing].ship_index[i], buf); + // bash it again for the display name + wing_bash_ship_name(&Ships[Wings[wing].ship_index[i]], &Wings[wing], i + 1, true); } } diff --git a/fred2/wing_editor.cpp b/fred2/wing_editor.cpp index e9df5847710..252b886d16d 100644 --- a/fred2/wing_editor.cpp +++ b/fred2/wing_editor.cpp @@ -45,6 +45,7 @@ wing_editor::wing_editor(CWnd* pParent /*=NULL*/) { //{{AFX_DATA_INIT(wing_editor) m_wing_name = _T(""); + m_wing_display_name = _T(""); m_wing_squad_filename = _T(""); m_special_ship = -1; m_waves = 0; @@ -90,6 +91,7 @@ void wing_editor::DoDataExchange(CDataExchange* pDX) DDX_Control(pDX, IDC_SPIN_WAVE_THRESHOLD, m_threshold_spin); DDX_Control(pDX, IDC_SPIN_WAVES, m_waves_spin); DDX_Text(pDX, IDC_WING_NAME, m_wing_name); + DDX_Text(pDX, IDC_WING_DISPLAY_NAME, m_wing_display_name); DDX_Text(pDX, IDC_WING_SQUAD_LOGO, m_wing_squad_filename); DDX_CBIndex(pDX, IDC_WING_SPECIAL_SHIP, m_special_ship); DDX_CBIndex(pDX, IDC_WING_FORMATION, m_formation); @@ -182,6 +184,7 @@ BEGIN_MESSAGE_MAP(wing_editor, CDialog) ON_BN_CLICKED(IDC_CUSTOM_WARPIN_PARAMS, OnBnClickedCustomWarpinParams) ON_BN_CLICKED(IDC_CUSTOM_WARPOUT_PARAMS, OnBnClickedCustomWarpoutParams) ON_BN_CLICKED(IDC_WING_FORMATION_ALIGN, OnWingFormationAlign) + ON_EN_CHANGE(IDC_WING_NAME, OnChangeWingName) //}}AFX_MSG_MAP END_MESSAGE_MAP() @@ -287,6 +290,7 @@ void wing_editor::initialize_data_safe(int full_update) m_ignore_count = 0; if (cur_wing < 0) { m_wing_squad_filename = _T(""); + m_wing_display_name = _T(""); m_special_ship = -1; m_formation = 0; m_formation_scale = _T("1.0"); @@ -349,6 +353,7 @@ void wing_editor::initialize_data_safe(int full_update) player_wing = 1; m_wing_squad_filename = _T(Wings[cur_wing].wing_squad_filename); + m_wing_display_name = Wings[cur_wing].has_display_name() ? Wings[cur_wing].get_display_name() : ""; m_special_ship = Wings[cur_wing].special_ship; m_waves = Wings[cur_wing].num_waves; m_threshold = Wings[cur_wing].threshold; @@ -697,6 +702,25 @@ int wing_editor::update_data(int redraw) strcpy_s(old_name, Wings[cur_wing].name); string_copy(Wings[cur_wing].name, m_wing_name, NAME_LENGTH, 1); + + lcl_fred_replace_stuff(m_wing_display_name); + + // the display name was precalculated, so now just assign it + if (m_wing_display_name == m_wing_name || m_wing_display_name.CompareNoCase("") == 0) + { + if (Wings[cur_wing].has_display_name()) + set_modified(); + Wings[cur_wing].display_name = ""; + Wings[cur_wing].flags.remove(Ship::Wing_Flags::Has_display_name); + } + else + { + if (!Wings[cur_wing].has_display_name() || Wings[cur_wing].display_name != (LPCSTR)m_wing_display_name) + set_modified(); + Wings[cur_wing].display_name = m_wing_display_name; + Wings[cur_wing].flags.set(Ship::Wing_Flags::Has_display_name); + } + update_data_safe(); update_custom_wing_indexes(); @@ -718,8 +742,8 @@ int wing_editor::update_data(int redraw) if ((Objects[wing_objects[cur_wing][i]].type == OBJ_SHIP) || (Objects[wing_objects[cur_wing][i]].type == OBJ_START)) { wing_bash_ship_name(buf, str, i + 1); rename_ship(Wings[cur_wing].ship_index[i], buf); - // clear display name if we have one hanging around - Ships[Wings[cur_wing].ship_index[i]].flags.remove(Ship::Ship_Flags::Has_display_name); + // bash it again for the display name + wing_bash_ship_name(&Ships[Wings[cur_wing].ship_index[i]], &Wings[cur_wing], i + 1, true); } } @@ -1480,3 +1504,15 @@ void wing_editor::OnWingFormationAlign() wingp->formation = old_formation; wingp->formation_scale = old_formation_scale; } + +void wing_editor::OnChangeWingName() +{ + // sync the edit box to the variable + UpdateData(TRUE); + + // automatically determine or reset the display name + m_wing_display_name = get_display_name_for_text_box(m_wing_name); + + // sync the variable to the edit box + UpdateData(FALSE); +} diff --git a/fred2/wing_editor.h b/fred2/wing_editor.h index 71a231abdc2..708bd2cdcf4 100644 --- a/fred2/wing_editor.h +++ b/fred2/wing_editor.h @@ -47,6 +47,7 @@ class wing_editor : public CDialog CSpinButtonCtrl m_threshold_spin; CSpinButtonCtrl m_waves_spin; CString m_wing_name; + CString m_wing_display_name; int m_special_ship; int m_waves; int m_threshold; @@ -115,6 +116,7 @@ class wing_editor : public CDialog afx_msg void OnBnClickedCustomWarpinParams(); afx_msg void OnBnClickedCustomWarpoutParams(); afx_msg void OnWingFormationAlign(); + afx_msg void OnChangeWingName(); //}}AFX_MSG DECLARE_MESSAGE_MAP() diff --git a/qtfred/help-src/doc/dialogs/WingEditorDialog.html b/qtfred/help-src/doc/dialogs/WingEditorDialog.html index 29108ca0336..56ff20ad73c 100644 --- a/qtfred/help-src/doc/dialogs/WingEditorDialog.html +++ b/qtfred/help-src/doc/dialogs/WingEditorDialog.html @@ -18,6 +18,10 @@

General fields

FieldDescription NameWing name shown on the HUD and used in SEXPs (e.g. Alpha, Gamma). + Display NameOptional name shown to the player in place of the + internal name. Useful for localisation — the display name can be translated + while the internal name stays stable for SEXPs and scripting. Leave blank to + use the internal name. Wing leaderWhich ship in the wing acts as leader. Other members form up on the leader. Number of wavesHow many waves of this wing will arrive over the diff --git a/qtfred/src/mission/Editor.cpp b/qtfred/src/mission/Editor.cpp index 87aa811a3a2..90b7115fc5c 100644 --- a/qtfred/src/mission/Editor.cpp +++ b/qtfred/src/mission/Editor.cpp @@ -382,8 +382,7 @@ bool Editor::loadMission(const std::string& mission_name, int flags) { // fix old ship names for ships in wings if needed while (j--) { - if ((Objects[wing_objects[i][j]].type == OBJ_SHIP) - || (Objects[wing_objects[i][j]].type == OBJ_START)) { // don't change player ship names + if ((Objects[wing_objects[i][j]].type == OBJ_SHIP) || (Objects[wing_objects[i][j]].type == OBJ_START)) { // don't change player ship names wing_bash_ship_name(name, Wings[i].name, j + 1); old_name = Ships[Wings[i].ship_index[j]].ship_name; if (stricmp(name, old_name) != 0) { // need to fix name @@ -397,7 +396,8 @@ bool Editor::loadMission(const std::string& mission_name, int flags) { } } - strcpy_s(Ships[Wings[i].ship_index[j]].ship_name, name); + // bash it again so that we handle display names if needed + wing_bash_ship_name(&Ships[Wings[i].ship_index[j]], &Wings[i], j + 1, true); } } } @@ -1599,6 +1599,8 @@ int Editor::delete_ship_from_wing(int ship) { if (Objects[wing_objects[wing][i]].type == OBJ_SHIP) { wing_bash_ship_name(name, Wings[wing].name, i + 1); rename_ship(Wings[wing].ship_index[i], name); + // bash it again for the display name + wing_bash_ship_name(&Ships[Wings[wing].ship_index[i]], &Wings[wing], i + 1, true); } } @@ -1679,6 +1681,20 @@ int Editor::rename_ship(int ship, const char* name) { strcpy_s(Ships[ship].ship_name, name); + // if this name has a hash, create a default display name + if (get_pointer_to_first_hash_symbol(Ships[ship].ship_name)) + { + Ships[ship].display_name = Ships[ship].ship_name; + end_string_at_first_hash_symbol(Ships[ship].display_name); + Ships[ship].flags.set(Ship::Ship_Flags::Has_display_name); + } + // otherwise reset the display name + else + { + Ships[ship].display_name = ""; + Ships[ship].flags.remove(Ship::Ship_Flags::Has_display_name); + } + missionChanged(); return 0; diff --git a/qtfred/src/mission/EditorWing.cpp b/qtfred/src/mission/EditorWing.cpp index 38865c51adf..c5d81a6388c 100644 --- a/qtfred/src/mission/EditorWing.cpp +++ b/qtfred/src/mission/EditorWing.cpp @@ -194,6 +194,20 @@ int Editor::create_wing() } strcpy_s(Wings[wing].name, dlg->getModel()->getName().c_str()); + + // if this name has a hash, create a default display name + if (get_pointer_to_first_hash_symbol(Wings[wing].name)) + { + Wings[wing].display_name = Wings[wing].name; + end_string_at_first_hash_symbol(Wings[wing].display_name); + Wings[wing].flags.set(Ship::Wing_Flags::Has_display_name); + } + // otherwise reset the display name + else + { + Wings[wing].display_name = ""; + Wings[wing].flags.remove(Ship::Wing_Flags::Has_display_name); + } } setupCurrentObjectIndices(-1); @@ -255,6 +269,8 @@ int Editor::create_wing() wing_bash_ship_name(msg, Wings[wing].name, i + 1); rename_ship(ship, msg); + // bash it again for the display name + wing_bash_ship_name(&Ships[ship], &Wings[wing], i + 1, true); Wings[wing].ship_index[i] = ship; Ships[ship].wingnum = wing; @@ -359,6 +375,8 @@ void Editor::remove_ship_from_wing(int ship, int min) if (Objects[obj].type == OBJ_SHIP) { wing_bash_ship_name(buf, Wings[wing].name, i + 1); rename_ship(Wings[wing].ship_index[i], buf); + // bash it again for the display name + wing_bash_ship_name(&Ships[Wings[wing].ship_index[i]], &Wings[wing], i + 1, true); } } @@ -558,6 +576,20 @@ bool Editor::rename_wing(int wing, const SCP_string& new_name, bool rename_membe strncpy(Wings[wing].name, new_name.c_str(), NAME_LENGTH - 1); Wings[wing].name[NAME_LENGTH - 1] = '\0'; + // if this name has a hash, create a default display name + if (get_pointer_to_first_hash_symbol(Wings[wing].name)) + { + Wings[wing].display_name = Wings[wing].name; + end_string_at_first_hash_symbol(Wings[wing].display_name); + Wings[wing].flags.set(Ship::Wing_Flags::Has_display_name); + } + // otherwise reset the display name + else + { + Wings[wing].display_name = ""; + Wings[wing].flags.remove(Ship::Wing_Flags::Has_display_name); + } + if (rename_members) { for (int i = 0; i < Wings[wing].wave_count; ++i) { const int ship_idx = Wings[wing].ship_index[i]; @@ -566,6 +598,8 @@ bool Editor::rename_wing(int wing, const SCP_string& new_name, bool rename_membe char buf[NAME_LENGTH]; wing_bash_ship_name(buf, Wings[wing].name, i + 1); rename_ship(ship_idx, buf); + // bash it again for the display name + wing_bash_ship_name(&Ships[ship_idx], &Wings[wing], i + 1, true); } } diff --git a/qtfred/src/mission/dialogs/WingEditorDialogModel.cpp b/qtfred/src/mission/dialogs/WingEditorDialogModel.cpp index 80d29e2f799..61e1b3234db 100644 --- a/qtfred/src/mission/dialogs/WingEditorDialogModel.cpp +++ b/qtfred/src/mission/dialogs/WingEditorDialogModel.cpp @@ -425,6 +425,35 @@ void WingEditorDialogModel::setWingName(const SCP_string& name) } } +SCP_string WingEditorDialogModel::getWingDisplayName() const +{ + if (!wingIsValid()) + return ""; + const auto* w = getCurrentWing(); + return w->has_display_name() ? w->get_display_name() : ""; +} + +void WingEditorDialogModel::setWingDisplayName(const SCP_string& displayName) +{ + if (!wingIsValid()) + return; + + SCP_string display = displayName; + lcl_fred_replace_stuff(display); + + auto* w = getCurrentWing(); + if (display == _currentWingName || stricmp(display.c_str(), "") == 0) { + w->display_name = ""; + w->flags.remove(Ship::Wing_Flags::Has_display_name); + } else { + w->display_name = display; + w->flags.set(Ship::Wing_Flags::Has_display_name); + } + set_modified(); + _editor->missionChanged(); + modelChanged(); +} + int WingEditorDialogModel::getWingLeaderIndex() const { if (!wingIsValid()) diff --git a/qtfred/src/mission/dialogs/WingEditorDialogModel.h b/qtfred/src/mission/dialogs/WingEditorDialogModel.h index b34f4d36c1f..349048f6ccc 100644 --- a/qtfred/src/mission/dialogs/WingEditorDialogModel.h +++ b/qtfred/src/mission/dialogs/WingEditorDialogModel.h @@ -55,6 +55,8 @@ class WingEditorDialogModel : public AbstractDialogModel { // Top section, first column SCP_string getWingName() const; void setWingName(const SCP_string& name); + SCP_string getWingDisplayName() const; + void setWingDisplayName(const SCP_string& displayName); int getWingLeaderIndex() const; void setWingLeaderIndex(int newLeaderIndex); int getNumberOfWaves() const; diff --git a/qtfred/src/ui/dialogs/WingEditorDialog.cpp b/qtfred/src/ui/dialogs/WingEditorDialog.cpp index c831c755f41..ab82b21537c 100644 --- a/qtfred/src/ui/dialogs/WingEditorDialog.cpp +++ b/qtfred/src/ui/dialogs/WingEditorDialog.cpp @@ -24,6 +24,7 @@ WingEditorDialog::WingEditorDialog(FredView* parent, EditorViewport* viewport) ui->helpText->setVisible(viewport->Show_sexp_help_wing_editor); ui->wingNameEdit->setMaxLength(NAME_LENGTH - 1); + ui->wingDisplayNameEdit->setMaxLength(NAME_LENGTH - 1); setWindowTitle(tr("Wing Editor")); @@ -58,6 +59,7 @@ void WingEditorDialog::updateUi() // Top section, first column ui->wingNameEdit->setText(_model->getWingName().c_str()); + ui->wingDisplayNameEdit->setText(_model->getWingDisplayName().c_str()); ui->wingLeaderCombo->setCurrentIndex(_model->getWingLeaderIndex()); ui->numWavesSpinBox->setValue(_model->getNumberOfWaves()); ui->waveThresholdSpinBox->setValue(_model->getWaveThreshold()); @@ -122,6 +124,7 @@ void WingEditorDialog::enableOrDisableControls() auto enableAll = [&](bool on) { // Top section, first column ui->wingNameEdit->setEnabled(on); + ui->wingDisplayNameEdit->setEnabled(on); ui->wingLeaderCombo->setEnabled(on); ui->numWavesSpinBox->setEnabled(on); ui->waveThresholdSpinBox->setEnabled(on); @@ -228,6 +231,7 @@ void WingEditorDialog::clearGeneralFields() util::SignalBlockers blockers(this); ui->wingNameEdit->clear(); + ui->wingDisplayNameEdit->clear(); ui->wingLeaderCombo->setCurrentIndex(-1); ui->hotkeyCombo->setCurrentIndex(-1); @@ -357,6 +361,17 @@ void WingEditorDialog::on_wingNameEdit_editingFinished() { const auto newName = ui->wingNameEdit->text().toStdString(); _model->setWingName(newName); + + // rename_wing already auto-sets the display name from the hash; just sync the edit box + ui->wingDisplayNameEdit->setText(Editor::get_display_name_for_text_box(_model->getWingName()).c_str()); +} + +void WingEditorDialog::on_wingDisplayNameEdit_editingFinished() +{ + const auto newDisplayName = ui->wingDisplayNameEdit->text().toStdString(); + if (newDisplayName != _model->getWingDisplayName()) { + _model->setWingDisplayName(newDisplayName); + } } void WingEditorDialog::on_wingLeaderCombo_currentIndexChanged(int index) diff --git a/qtfred/src/ui/dialogs/WingEditorDialog.h b/qtfred/src/ui/dialogs/WingEditorDialog.h index fe7735d5a12..d8cc6bc60f1 100644 --- a/qtfred/src/ui/dialogs/WingEditorDialog.h +++ b/qtfred/src/ui/dialogs/WingEditorDialog.h @@ -23,6 +23,7 @@ class WingEditorDialog : public QDialog, public SexpTreeEditorInterface { // Top section, first column void on_wingNameEdit_editingFinished(); + void on_wingDisplayNameEdit_editingFinished(); void on_wingLeaderCombo_currentIndexChanged(int index); void on_numWavesSpinBox_valueChanged(int value); void on_waveThresholdSpinBox_valueChanged(int value); diff --git a/qtfred/src/ui/widgets/renderwidget.cpp b/qtfred/src/ui/widgets/renderwidget.cpp index 481993bb91f..6190e9f710b 100644 --- a/qtfred/src/ui/widgets/renderwidget.cpp +++ b/qtfred/src/ui/widgets/renderwidget.cpp @@ -372,9 +372,7 @@ void RenderWidget::mouseReleaseEvent(QMouseEvent* event) { Assert(objp->type == OBJ_SHIP); ship = objp->instance; Assert(Ships[ship].wingnum == -1); - wing_bash_ship_name(Ships[ship].ship_name, - Wings[_viewport->Duped_wing].name, - Wings[_viewport->Duped_wing].wave_count + 1); + wing_bash_ship_name(&Ships[ship], &Wings[_viewport->Duped_wing], Wings[_viewport->Duped_wing].wave_count + 1, true); Wings[_viewport->Duped_wing].ship_index[Wings[_viewport->Duped_wing].wave_count] = ship; Ships[ship].wingnum = _viewport->Duped_wing; diff --git a/qtfred/ui/WingEditorDialog.ui b/qtfred/ui/WingEditorDialog.ui index a73c7f3ae9e..39c2a5f8298 100644 --- a/qtfred/ui/WingEditorDialog.ui +++ b/qtfred/ui/WingEditorDialog.ui @@ -43,37 +43,44 @@ - - + + - Hotkey + Wing Name Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - + + + + <html><head/><body><p>Sets the ship's name</p></body></html> + + + + + - # of Waves + Display Name Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - + + - <html><head/><body><p>Sets the ship's name</p></body></html> + <html><head/><body><p>Sets the wing's display name</p></body></html> - - + + - Wave Threshold + Wing Leader Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter @@ -81,6 +88,19 @@ + + + + + + # of Waves + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + 1 @@ -90,33 +110,30 @@ - - - - - - - - + + - Wing Name + Wave Threshold Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - + + + + + - Wing Leader + Hotkey Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - + true