/* * Copyright (C) Volition, Inc. 1999. All rights reserved. * * All source code herein is the property of Volition, Inc. You may not sell * or otherwise commercially exploit the source or things you created based on the * source. * */ #include "stdafx.h" #include #include "FRED.h" #include "MainFrm.h" #include "FREDDoc.h" #include "FREDView.h" #include "FredRender.h" #include "ai/aigoals.h" #include "ship/ship.h" #include "globalincs/linklist.h" #include "globalincs/version.h" #include "globalincs/alphacolors.h" #include "math/curve.h" #include "mission/missiongrid.h" #include "mission/missionparse.h" #include "mission/missionmessage.h" #include "mission/missiongoals.h" #include "mission/missionbriefcommon.h" #include "missioneditor/common.h" #include "missioneditor/objectduplication.h" #include "missioneditor/sexp_annotation_model.h" #include "model/modelreplace.h" #include "Management.h" #include "cfile/cfile.h" #include "graphics/2d.h" #include "render/3d.h" #include "weapon/weapon.h" #include "io/key.h" #include "parse/parselo.h" #include "math/fvi.h" #include "starfield/starfield.h" #include "parse/sexp.h" #include "parse/sexp/sexp_lookup.h" #include "io/mouse.h" #include "mission/missioncampaign.h" #include "wing.h" #include "MessageEditorDlg.h" #include "EventEditor.h" #include "MissionGoalsDlg.h" #include "MissionCutscenesDlg.h" #include "ShieldSysDlg.h" #include "gamesnd/eventmusic.h" #include "DebriefingEditorDlg.h" #include "starfield/nebula.h" #include "asteroid/asteroid.h" #include "hud/hudsquadmsg.h" #include "jumpnode/jumpnode.h" #include "stats/medals.h" #include "localization/localize.h" #include "osapi/osregistry.h" #include "localization/fhash.h" #include "io/timer.h" #include "nebula/neb.h" #include "nebula/neblightning.h" #include "species_defs/species_defs.h" #include "lighting/lighting_profiles.h" #include "osapi/osapi.h" #include "graphics/font.h" #include "object/objectdock.h" #include "gamesnd/gamesnd.h" #include "iff_defs/iff_defs.h" #include "menuui/techmenu.h" #include "missionui/fictionviewer.h" #include "mod_table/mod_table.h" #include "libs/ffmpeg/FFmpeg.h" #include "scripting/scripting.h" #include "scripting/global_hooks.h" #include "utils/Random.h" #include "prop/prop.h" #include #include "cmdline/cmdline.h" #define MAX_DOCKS 1000 #define UNKNOWN_USER "Unknown" extern void ssm_init(); // Need this to populate Ssm_info so OPF_SSM_CLASS does something. -MageKing17 int cur_wing = -1; int cur_wing_index; int cur_object_index = -1; int cur_ship = -1; int cur_ship_type_combo_index = 0; waypoint *cur_waypoint = NULL; waypoint_list *cur_waypoint_list = NULL; int delete_flag; int bypass_update = 0; int Update_ship = 0; int Update_wing = 0; int Update_prop = 0; char Fred_exe_dir[512] = ""; char Fred_base_dir[512] = ""; char Fred_alt_names[MAX_SHIPS][NAME_LENGTH+1]; char Fred_callsigns[MAX_SHIPS][NAME_LENGTH+1]; extern void allocate_parse_text(size_t size); // object numbers for ships in a wing. int wing_objects[MAX_WINGS][MAX_SHIPS_PER_WING]; char *Docking_bay_list[MAX_DOCKS]; // Goober5000 SCP_vector Show_iff; CCriticalSection CS_cur_object_index; // Used in the FRED drop-down menu and in error_check_initial_orders // NOTE: Certain goals (Form On Wing, Rearm, Chase Weapon, Fly To Ship) aren't listed here. This may or may not be intentional, // but if they are added in the future, it will be necessary to verify correct functionality in the various FRED dialog functions. ai_goal_list Ai_goal_list[] = { { "Waypoints", AI_GOAL_WAYPOINTS, 0 }, { "Waypoints once", AI_GOAL_WAYPOINTS_ONCE, 0 }, { "Attack", AI_GOAL_CHASE, 0 }, { "Attack", AI_GOAL_CHASE_WING, 0 }, // duplicate needed because we can no longer use bitwise operators { "Attack any ship", AI_GOAL_CHASE_ANY, 0 }, { "Attack ship class", AI_GOAL_CHASE_SHIP_CLASS, 0 }, { "Attack ship type", AI_GOAL_CHASE_SHIP_TYPE, 0 }, { "Guard", AI_GOAL_GUARD, 0 }, { "Guard", AI_GOAL_GUARD_WING, 0 }, // duplicate needed because we can no longer use bitwise operators { "Disable ship", AI_GOAL_DISABLE_SHIP, 0 }, { "Disable ship (tactical)",AI_GOAL_DISABLE_SHIP_TACTICAL, 0 }, { "Disarm ship", AI_GOAL_DISARM_SHIP, 0 }, { "Disarm ship (tactical)", AI_GOAL_DISARM_SHIP_TACTICAL, 0 }, { "Destroy subsystem", AI_GOAL_DESTROY_SUBSYSTEM, 0 }, { "Dock", AI_GOAL_DOCK, 0 }, { "Undock", AI_GOAL_UNDOCK, 0 }, { "Warp", AI_GOAL_WARP, 0 }, { "Evade ship", AI_GOAL_EVADE_SHIP, 0 }, { "Ignore ship", AI_GOAL_IGNORE, 0 }, { "Ignore ship (new)", AI_GOAL_IGNORE_NEW, 0 }, { "Stay near ship", AI_GOAL_STAY_NEAR_SHIP, 0 }, { "Keep safe distance", AI_GOAL_KEEP_SAFE_DISTANCE, 0 }, { "Stay still", AI_GOAL_STAY_STILL, 0 }, { "Play dead", AI_GOAL_PLAY_DEAD, 0 }, { "Play dead (persistent)", AI_GOAL_PLAY_DEAD_PERSISTENT, 0 } }; int Ai_goal_list_size = sizeof(Ai_goal_list) / sizeof(ai_goal_list); // internal function prototypes void set_cur_indices(int obj); int common_object_delete(int obj); int create_waypoint(vec3d *pos, int waypoint_instance); int create_ship(matrix *orient, vec3d *pos, int ship_type); int query_ship_name_duplicate(int ship); int query_prop_name_duplicate(int prop); char* reg_read_string(char* section, char* name, char* default_value); // Note that the parameter here is max *length*, not max buffer size. Leave room for the null-terminator! void string_copy(char *dest, const CString &src, size_t max_len, bool modify) { if (modify) if (strcmp(src, dest)) set_modified(); auto len = strlen(src); if (len > max_len) len = max_len; strncpy(dest, src, len); dest[len] = 0; } void string_copy(SCP_string &dest, const CString &src, bool modify) { if (modify) if (strcmp(src, dest.c_str())) set_modified(); dest = src; } // converts a multiline string (one with newlines in it) into a windows format multiline // string (newlines changed to '\r\n'). void convert_multiline_string(CString &dest, const SCP_string &src) { dest = src.c_str(); dest.Replace("\n", "\r\n"); } // converts a multiline string (one with newlines in it) into a windows format multiline // string (newlines changed to '\r\n'). void convert_multiline_string(CString &dest, const char *src) { dest = src; dest.Replace("\n", "\r\n"); } // Converts a windows format multiline CString back into a normal multiline string. void deconvert_multiline_string(char *dest, const CString &str, size_t max_len) { // leave room for the null terminator memset(dest, 0, max_len + 1); strncpy(dest, (LPCTSTR) str, max_len); replace_all(dest, "\r\n", "\n", max_len); } // ditto for SCP_string void deconvert_multiline_string(SCP_string &dest, const CString &str) { dest = str; replace_all(dest, "\r\n", "\n"); } void strip_quotation_marks(CString& str) { str.Remove('\"'); } void pad_with_newline(CString& str, int max_size) { int len = str.GetLength(); if (!len) { len = 1; } if (str[len - 1] != '\n' && len < max_size) { str += _T("\n"); } } void lcl_fred_replace_stuff(CString &text) { // this should be kept in sync with the function in localize.cpp text.Replace("\"", "$quote"); text.Replace(";", "$semicolon"); text.Replace("/", "$slash"); text.Replace("\\", "$backslash"); } CString get_display_name_for_text_box(const char *orig_name) { auto p = get_pointer_to_first_hash_symbol(orig_name); if (p) { // use the same logic as in end_string_at_first_hash_symbol, but rewritten for CString CString display_name(orig_name, static_cast(p - orig_name)); display_name.TrimRight(); return display_name; } else return ""; } void fred_preload_all_briefing_icons() { for (SCP_vector::iterator ii = Briefing_icon_info.begin(); ii != Briefing_icon_info.end(); ++ii) { generic_anim_load(&ii->regular); hud_anim_load(&ii->fade); hud_anim_load(&ii->highlight); } } bool fred_init(std::unique_ptr&& graphicsOps) { int i; // Metadata must to be set as early as possible, before the first SDL_Init(). // This is global info and cannot be changed later (i.e., it can't be set per mod) SDL_SetAppMetadata("FRED2 Open", FS_VERSION_FULL, "us.indiegames.scp.FRED2Open"); SDL_SetAppMetadataProperty(SDL_PROP_APP_METADATA_TYPE_STRING, "application"); SDL_SetAppMetadataProperty(SDL_PROP_APP_METADATA_COPYRIGHT_STRING, "Copyright 1999 Volition, Inc. & Copyright 2002-2026 The Source Code Project."); Random::seed(static_cast(time(nullptr))); os_init(Osreg_class_name, Osreg_app_name); timer_init(); Assert(strlen(Fred_base_dir) > 0); //-V805 // sigh... this should enable proper reading of cmdline_fso.cfg - Goober5000 cfile_chdir(Fred_base_dir); // this should enable mods - Kazan if (!parse_cmdline(__argc, __argv)) { // Command line contained an option that terminates the program immediately exit(1); } #ifndef NDEBUG #if FS_VERSION_REVIS == 0 mprintf(("Fred2 Open version: %i.%i.%i\n", FS_VERSION_MAJOR, FS_VERSION_MINOR, FS_VERSION_BUILD)); #else mprintf(("Fred2 Open version: %i.%i.%i.%i\n", FS_VERSION_MAJOR, FS_VERSION_MINOR, FS_VERSION_BUILD, FS_VERSION_REVIS)); #endif extern void cmdline_debug_print_cmdline(); cmdline_debug_print_cmdline(); #endif // d'oh if(cfile_init(Fred_exe_dir)){ exit(1); } // Load game_settings.tbl mod_table_init(); // initialize localization module. Make sure this is done AFTER initializing OS. // NOTE: Fred should ALWAYS run without localization. Otherwise it might swap in another language // when saving - which would cause inconsistencies when externalizing to tstrings.tbl via Exstr // trust me on this :) lcl_init(LCL_UNTRANSLATED); // Goober5000 - force init XSTRs (so they work, but only work untranslated, based on above comment) extern bool Xstr_inited; Xstr_inited = true; #ifndef NDEBUG load_filter_info(); #endif //CFREDView *window = CFREDView::GetView(); //HWND hwndApp = window->GetSafeHwnd(); //os_set_window((uint) hwndApp); snd_init(); // Not ready for this yet // Cmdline_nospec = 1; // Cmdline_noglow = 1; Cmdline_window = 1; gr_init(std::move(graphicsOps), GraphicsAPI::OpenGL, 640, 480, 32); gr_set_gamma(3.0f); io::mouse::CursorManager::get()->showCursor(false); font::init(); // loads up all fonts curves_init(); particle::ParticleManager::init(); gamesnd_parse_soundstbl(true); iff_init(); // Goober5000 species_init(); // Kazan gamesnd_parse_soundstbl(false); brief_icons_init(); hud_init_comm_orders(); // Goober5000 // wookieejedi // load in the controls and defaults including the controlconfigdefault.tbl // this allows the sexp tree in key-pressed to actually match what the game will use // especially useful when a custom Controlconfigdefaults.tbl is used control_config_common_init(); rank_init(); traitor_init(); medals_init(); // get medal names for sexpression usage key_init(); mouse_init(); model_init(); virtual_pof_init(); event_music_init(); alpha_colors_init(); // get fireball IDs for sexpression usage // (we don't need to init the entire system via fireball_init, we just need the information) fireball_parse_tbl(); animation::ModelAnimationParseHelper::parseTables(); sexp_startup(); // Must happen before ship init for LuaAI obj_init(); armor_init(); ai_init(); ai_profiles_init(); weapon_init(); glowpoint_init(); ship_init(); prop_init(); techroom_intel_init(); hud_positions_init(); asteroid_init(); mission_brief_common_init(); neb2_init(); // fullneb stuff nebl_init(); // neb lightning stars_init(); ssm_init(); // The game calls this after stars_init(), and we need Ssm_info initialized for OPF_SSM_CLASS. -MageKing17 lighting_profiles::load_profiles(); // To avoid breaking current mods which do not support scripts in FRED we only initialize the scripting // system if a special mod_table option is set if (Enable_scripts_in_fred) { // Note: Avoid calling any non-script functions after this line and before OnGameInit->run(), lest they run before scripting has completely initialized. script_init(); //WMC } Script_system.RunInitFunctions(); Scripting_game_init_run = true; // set this immediately before OnGameInit so that OnGameInit *itself* will run if (scripting::hooks::OnGameInit->isActive()) { scripting::hooks::OnGameInit->run(); } //Technically after the splash screen, but the best we can do these days. Since the override is hard-deprecated, we don't need to check it. if (scripting::hooks::OnSplashScreen->isActive()) { scripting::hooks::OnSplashScreen->run(); } // A non-deprecated hook that runs after the splash screen has faded out. if (scripting::hooks::OnSplashEnd->isActive()) { scripting::hooks::OnSplashEnd->run(); } libs::ffmpeg::initialize(); // for fred specific replacement texture stuff Fred_texture_replacements.clear(); // Goober5000 Show_iff.clear(); for (i = 0; i < (int)Iff_info.size(); i++) Show_iff.push_back(true); // Goober5000 strcpy_s(Voice_abbrev_briefing, ""); strcpy_s(Voice_abbrev_campaign, ""); strcpy_s(Voice_abbrev_command_briefing, ""); strcpy_s(Voice_abbrev_debriefing, ""); strcpy_s(Voice_abbrev_message, ""); strcpy_s(Voice_abbrev_mission, ""); Voice_no_replace_filenames = false; strcpy_s(Voice_script_entry_format, Voice_script_default_string.c_str()); Voice_export_selection = 0; Show_waypoints = TRUE; // initialize and activate external string hash table // make sure to do here so that we don't parse the table files into the hash table - waste of space fhash_init(); fhash_activate(); fred_preload_all_briefing_icons(); //phreak. This needs to be done or else the briefing icons won't show up fiction_viewer_reset(); cmd_brief_reset(); // mission creation requires the existence of a timestamp snapshot timer_start_frame(); mission_campaign_clear(); create_new_mission(); gr_reset_clip(); g3_start_frame(0); g3_set_view_matrix(&eye_pos, &eye_orient, 0.5f); Fred_main_wnd -> init_tools(); return true; } // called when FRED closes, after the document has been closed void fred_shutdown() { editor_free_engine_data(); gr_close(); os_cleanup(); } void set_physics_controls() { physics_init(&view_physics); view_physics.max_vel.xyz.x *= physics_speed / 3.0f; view_physics.max_vel.xyz.y *= physics_speed / 3.0f; view_physics.max_vel.xyz.z *= physics_speed / 3.0f; view_physics.max_rear_vel *= physics_speed / 3.0f; view_physics.max_rotvel.xyz.x *= physics_rot / 30.0f; view_physics.max_rotvel.xyz.y *= physics_rot / 30.0f; view_physics.max_rotvel.xyz.z *= physics_rot / 30.0f; view_physics.flags |= PF_ACCELERATES | PF_SLIDE_ENABLED; theApp.write_ini_file(1); } int create_object_on_grid(int waypoint_instance, bool prop) { int obj = -1; float rval; vec3d dir,pos; g3_point_to_vec_delayed(&dir, marking_box.x2, marking_box.y2); rval = fvi_ray_plane(&pos, &The_grid->center, &The_grid->gmatrix.vec.uvec, &view_pos, &dir, 0.0f); if (rval>=0.0f) { unmark_all(); obj = create_object(&pos, waypoint_instance, prop); if (obj >= 0) { mark_object(obj); FREDDoc_ptr->autosave("object create"); } else if (obj == -1) { if (prop && Prop_info.empty()) { Fred_main_wnd->MessageBox("No props defined. Can't create prop object!"); } else { Fred_main_wnd->MessageBox("Maximum object limit reached. Can't add any more objects."); } } } return obj; } void fix_prop_name(int prop) { int i = 1; do { sprintf(prop_id_lookup(prop)->prop_name, "U.R.A. Dummy %d", i++); } while (query_prop_name_duplicate(prop)); } void fix_ship_name(int ship) { char old_name[NAME_LENGTH]; strcpy_s(old_name, Ships[ship].ship_name); int i = 1; do { sprintf(Ships[ship].ship_name, "U.R.A. Moron %d", i++); } while (query_ship_name_duplicate(ship)); // This function is called when a newly created ship duplicates the name of an existing ship. In // that situation, ship_create() will have overwritten the existing ship's registry entry to point // to the new ship, so point it back at the ship that legitimately holds the old name. auto ship_it = Ship_registry_map.find(old_name); if (ship_it != Ship_registry_map.end() && Ship_registry[ship_it->second].shipnum == ship) { int other_shipnum = ship_name_lookup(old_name, 1); if (other_shipnum >= 0) { auto old_entry = &Ship_registry[ship_it->second]; old_entry->objnum = Ships[other_shipnum].objnum; old_entry->shipnum = other_shipnum; } else Ship_registry_map.erase(ship_it); // don't erase the vector entry to avoid clobbering other indexes } // add a fresh registry entry for this ship under its new name ship_registry_entry entry(Ships[ship].ship_name); entry.status = ShipStatus::PRESENT; entry.objnum = Ships[ship].objnum; entry.shipnum = ship; Ship_registry.push_back(entry); Ship_registry_map[Ships[ship].ship_name] = sz2i(Ship_registry.size() - 1); } int create_ship(matrix *orient, vec3d *pos, int ship_type) { int obj, z1, z2; float temp_max_hull_strength; ship_info *sip; // Save the Current Working dir to restore in a minute - fred is being stupid char pwd[MAX_PATH_LEN]; getcwd(pwd, MAX_PATH_LEN); // get the present working dir - probably [/modpath]/data/missions/ // "pop" and cfile_chdirs off the stack chdir(Fred_base_dir); obj = ship_create(orient, pos, ship_type); if (obj == -1) return -1; // ok, done with file io, restore the pwd chdir(pwd); Objects[obj].phys_info.speed = 33.0f; ship *shipp = &Ships[Objects[obj].instance]; sip = &Ship_info[shipp->ship_info_index]; if (query_ship_name_duplicate(Objects[obj].instance)) fix_ship_name(Objects[obj].instance); // default stuff according to species and IFF shipp->team = Species_info[Ship_info[shipp->ship_info_index].species].default_iff; resolve_parse_flags(&Objects[obj], Iff_info[shipp->team].default_parse_flags); // default shield setting shipp->special_shield = -1; z1 = Shield_sys_teams[shipp->team]; z2 = Shield_sys_types.value_or(ship_type, 0); if (((z1 == 1) && z2) || (z2 == 1)) Objects[obj].flags.set(Object::Object_Flags::No_shields); // set orders according to whether the ship is on the player ship's team { object *temp_objp; ship *temp_shipp = NULL; // find the first player ship for (temp_objp = GET_FIRST(&obj_used_list); temp_objp != END_OF_LIST(&obj_used_list); temp_objp = GET_NEXT(temp_objp)) { if (temp_objp->type == OBJ_START) { temp_shipp = &Ships[temp_objp->instance]; break; } } // set orders if teams match, or if player couldn't be found if (temp_shipp == NULL || shipp->team == temp_shipp->team) { // if this ship is not a small ship, then make the orders be the default orders without // the depart item if (!(sip->is_small_ship())) { shipp->orders_accepted = ship_get_default_orders_accepted( sip ); shipp->orders_accepted.erase(DEPART_ITEM); } } else { shipp->orders_accepted.clear(); } } // calc kamikaze stuff if (shipp->use_special_explosion) { temp_max_hull_strength = (float)shipp->special_exp_blast; } else { temp_max_hull_strength = sip->max_hull_strength; } Ai_info[shipp->ai_index].kamikaze_damage = (int) std::min(1000.0f, 200.0f + (temp_max_hull_strength / 4.0f)); auto replacements = sip->replacement_textures; for (auto& tr : replacements) { if (!stricmp(tr.new_texture, "invisible")) { // invisible is a special case tr.new_texture_id = REPLACE_WITH_INVISIBLE; } else { // try to load texture or anim as normal tr.new_texture_id = bm_load_either(tr.new_texture); } } shipp->apply_replacement_textures(replacements); return obj; } int create_prop(matrix* orient, vec3d* pos, int prop_type) { // Save the Current Working dir to restore in a minute - fred is being stupid char pwd[MAX_PATH_LEN]; getcwd(pwd, MAX_PATH_LEN); // get the present working dir - probably [/modpath]/data/missions/ // "pop" and cfile_chdirs off the stack chdir(Fred_base_dir); int obj = prop_create(orient, pos, prop_type); if (obj == -1) return -1; // ok, done with file io, restore the pwd chdir(pwd); if (query_prop_name_duplicate(Objects[obj].instance)) fix_prop_name(Objects[obj].instance); return obj; } int query_prop_name_duplicate(int prop) { const auto& target = prop_id_lookup(prop)->prop_name; for (size_t i = 0; i < Props.size(); ++i) { if (i == static_cast(prop)) continue; const auto& other = Props[i]; if (other.has_value() && other->objnum != -1) { if (!stricmp(other->prop_name, target)) { return 1; } } } return 0; } int query_ship_name_duplicate(int ship) { int i; for (i=0; iinstance; if ((objp->type == OBJ_SHIP) || (objp->type == OBJ_START)) { bool clone_as_player_start = false; bool player_start_demoted = false; if (objp->type == OBJ_START && (The_mission.game_type & MISSION_TYPE_MULTI)) { if (Player_starts < MAX_PLAYERS) { clone_as_player_start = true; } else { player_start_demoted = true; } } if (clone_as_player_start) { obj = create_player(&objp->pos, &objp->orient, Ships[inst].ship_info_index); } else { obj = create_ship(&objp->orient, &objp->pos, Ships[inst].ship_info_index); } if (obj == -1) return -1; int n = Objects[obj].instance; // Copy every editable per-ship field. See clone_ship_instance_data() in // code/missioneditor/objectduplication.cpp for the canonical field list. clone_ship_instance_data(inst, n); // Reinforcement-list propagation: if the source ship is listed as a // reinforcement, add a new entry pointing at the duplicate. int i = find_item_with_string(Reinforcements, &reinforcements::name, Ships[inst].ship_name); if (i >= 0) { Reinforcements.push_back(Reinforcements[i]); strcpy_s(Reinforcements.back().name, Ships[n].ship_name); } if (player_start_demoted) { CString msg; msg.Format( "Cannot create another player start. This mission already has the maximum of %d. " "The duplicate has been created as a regular ship instead.", MAX_PLAYERS); AfxMessageBox(msg, MB_OK | MB_ICONINFORMATION); } } else if (objp->type == OBJ_WAYPOINT) { // waypoint_instance < 0 means waypoint_add() will create a fresh list; // copy editable per-list properties from the source list in that case. bool is_first_of_new_list = (waypoint_instance < 0); int src_list_idx = calc_waypoint_list_index(objp->instance); obj = create_waypoint(&objp->pos, waypoint_instance); if (obj == -1) return -1; waypoint_instance = Objects[obj].instance; if (is_first_of_new_list) { clone_waypoint_path_instance_data(src_list_idx, calc_waypoint_list_index(waypoint_instance)); } } else if (objp->type == OBJ_JUMP_NODE) { CJumpNode* src_jnp = jumpnode_get_by_objp(objp); CJumpNode jnp(&objp->pos); if (src_jnp) { clone_jump_node_instance_data(*src_jnp, jnp); } obj = jnp.GetSCPObjectNumber(); Jump_nodes.push_back(std::move(jnp)); } else if (objp->type == OBJ_PROP) { prop* propp = prop_id_lookup(inst); if (propp != nullptr) { obj = create_prop(&objp->orient, &objp->pos, propp->prop_info_index); if (obj != -1) { clone_prop_instance_data(inst, Objects[obj].instance); } } } if (obj == -1) return -1; Objects[obj].pos = objp->pos; Objects[obj].orient = objp->orient; Objects[obj].flags.set(Object::Object_Flags::Temp_marked); return obj; } int create_object(vec3d *pos, int waypoint_instance, bool prop) { int obj, n; if (prop) { int prop_class = m_new_prop_type_combo_box.GetPropClass(m_new_prop_type_combo_box.GetCurSel()); if (prop_class < 0 || prop_class >= prop_info_size()) return -1; obj = create_prop(nullptr, pos, prop_class); if (obj == -1) return -1; } else { if (cur_ship_type_combo_index == static_cast(Id_select_type_waypoint)) { obj = create_waypoint(pos, waypoint_instance); } else if (cur_ship_type_combo_index == static_cast(Id_select_type_jump_node)) { CJumpNode jnp(pos); obj = jnp.GetSCPObjectNumber(); Jump_nodes.push_back(std::move(jnp)); } else { // creating a ship int ship_class = m_new_ship_type_combo_box.GetShipClass(cur_ship_type_combo_index); if (ship_class < 0 || ship_class >= ship_info_size()) return -1; obj = create_ship(nullptr, pos, ship_class); if (obj == -1) return -1; n = Objects[obj].instance; Ships[n].arrival_cue = alloc_sexp("true", SEXP_ATOM, SEXP_ATOM_OPERATOR, -1, -1); Ships[n].departure_cue = alloc_sexp("false", SEXP_ATOM, SEXP_ATOM_OPERATOR, -1, -1); Ships[n].cargo1 = 0; } } if (obj < 0) return obj; obj_merge_created_list(); set_modified(); Update_window = 1; return obj; } int create_player(vec3d *pos, matrix *orient, int type) { int obj; if (type == -1){ type = get_default_player_ship_index(); } Assert(type >= 0); obj = create_ship(orient, pos, type); Objects[obj].type = OBJ_START; Assert(Player_starts < MAX_PLAYERS); Player_starts++; if (Player_start_shipnum < 0) { Player_start_shipnum = Objects[obj].instance; } // be sure arrival/departure cues are set Ships[Objects[obj].instance].arrival_cue = Locked_sexp_true; Ships[Objects[obj].instance].departure_cue = Locked_sexp_false; obj_merge_created_list(); set_modified(); return obj; } int create_waypoint(vec3d *pos, int waypoint_instance) { int obj = waypoint_add(pos, waypoint_instance); set_modified(); return obj; } void create_new_mission() { reset_mission(); *Mission_filename = 0; FREDDoc_ptr->autosave("nothing"); Undo_count = 0; } void reset_mission() { // Guard against reentrant ship-editor write-back while the mission is reset. Ship_editor_dialog.bypass_all++; clear_mission(); create_player(&vmd_zero_vector, &vmd_identity_matrix); stars_post_level_init(); Ship_editor_dialog.bypass_all--; } void clear_mission(bool fast_reload) { char *str; int i, j; CTime t; // clean up everything we need to before we reset back to defaults. clean_up_selections(); if (Briefing_dialog){ Briefing_dialog->reset_editor(); } allocate_parse_text( PARSE_TEXT_SIZE ); mission_init(&The_mission); obj_init(); if (!fast_reload) model_free_all(); // Free all existing models ai_init(); props_level_init(); asteroid_level_init(); ship_level_init(); nebula_init(Nebula_index, Nebula_pitch, Nebula_bank, Nebula_heading); Shield_sys_teams.clear(); Shield_sys_teams.resize(Iff_info.size(), 0); Shield_sys_types.clear(); set_cur_indices(-1); str = reg_read_string("SOFTWARE\\Microsoft\\Windows\\CurrentVersion", "RegisteredOwner", NULL); if (!str) { str = reg_read_string("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion", "RegisteredOwner", NULL); if (!str) { str = getenv("USERNAME"); if (!str){ str = UNKNOWN_USER; } } } time_t currentTime; time(¤tTime); auto timeinfo = localtime(¤tTime); The_mission.name = "Untitled"; The_mission.author = str; time_to_mission_info_string(timeinfo, The_mission.created, DATE_TIME_LENGTH - 1); strcpy_s(The_mission.modified, The_mission.created); strcpy_s(The_mission.notes, "This is a FRED2_OPEN created mission."); strcpy_s(The_mission.mission_desc, "Put mission description here"); apply_default_custom_data(&The_mission); // reset alternate name & callsign stuff for(i=0; i= MAX_OBJECTS || Objects[index].type == OBJ_NONE) return FALSE; ptr = GET_FIRST(&obj_used_list); while (ptr != END_OF_LIST(&obj_used_list)) { Assert(ptr->type != OBJ_NONE); if (OBJ_INDEX(ptr) == index) obj_found = TRUE; ptr = GET_NEXT(ptr); } Assert(obj_found); // just to make sure it's in the list like it should be. return TRUE; } int query_valid_ship(int index) { int obj_found = FALSE; object *ptr; if (index < 0 || index >= MAX_OBJECTS || Objects[index].type != OBJ_SHIP) return FALSE; ptr = GET_FIRST(&obj_used_list); while (ptr != END_OF_LIST(&obj_used_list)) { Assert(ptr->type != OBJ_NONE); if (OBJ_INDEX(ptr) == index) obj_found = TRUE; ptr = GET_NEXT(ptr); } Assert(obj_found); // just to make sure it's in the list like it should be. return TRUE; } int query_valid_waypoint(int index) { int obj_found = FALSE; object *ptr; if (index < 0 || index >= MAX_OBJECTS || Objects[index].type != OBJ_WAYPOINT) return FALSE; ptr = GET_FIRST(&obj_used_list); while (ptr != END_OF_LIST(&obj_used_list)) { Assert(ptr->type != OBJ_NONE); if (OBJ_INDEX(ptr) == index) obj_found = TRUE; ptr = GET_NEXT(ptr); } Assert(obj_found); // just to make sure it's in the list like it should be. return TRUE; } // Sets the current object to whatever is specified or advances to the next object // in the list if nothing is passed. void set_cur_object_index(int obj) { if (obj < 0) unmark_all(); else mark_object(obj); set_cur_indices(obj); // select the new object Update_ship = Update_wing = Update_prop = 1; Waypoint_editor_dialog.initialize_data(1); Jumpnode_editor_dialog.initialize_data(1); Update_window = 1; } // changes the currently selected wing. It is assumed that cur_wing == cur_ship's wing // number. Don't call this if this won't be true, or else you'll screw things up. void set_cur_wing(int wing) { cur_wing = wing; /* if (cur_ship != -1) Assert(cur_wing == Ships[cur_ship].wingnum); if ((cur_object_index != -1) && (Objects[cur_object_index].type == OBJ_SHIP)) Assert(cur_wing == Ships[Objects[cur_object_index].instance].wingnum);*/ Update_wing = 1; Update_window = 1; } // sets up the various cur_* global variables related to the selecting of an object. This // is an internal function that shouldn't typically get called directly. Use set_cur_object_index() instead. void set_cur_indices(int obj) { int i; object *ptr; CSingleLock sync(&CS_cur_object_index); sync.Lock(); // Don't modify until it's unlocked (if it's locked elsewhere). if (query_valid_object(obj)) { cur_object_index = obj; cur_ship = cur_wing = -1; cur_waypoint_list = NULL; cur_waypoint = NULL; if ((Objects[obj].type == OBJ_SHIP) || (Objects[obj].type == OBJ_START)) { cur_ship = Objects[obj].instance; cur_wing = Ships[cur_ship].wingnum; if (cur_wing >= 0) for (i=0; iget_parent_list(); } return; } if (obj == -1 || !Num_objects) { cur_object_index = cur_ship = cur_wing = -1; cur_waypoint_list = NULL; cur_waypoint = NULL; return; } if (query_valid_object(cur_object_index)) ptr = Objects[cur_object_index].next; else ptr = GET_FIRST(&obj_used_list); if (ptr == END_OF_LIST(&obj_used_list)) ptr = ptr->next; Assert(ptr != END_OF_LIST(&obj_used_list)); cur_object_index = OBJ_INDEX(ptr); Assert(ptr->type != OBJ_NONE); cur_ship = cur_wing = -1; cur_waypoint_list = NULL; cur_waypoint = NULL; if (ptr->type == OBJ_SHIP) { cur_ship = ptr->instance; cur_wing = Ships[cur_ship].wingnum; for (i=0; itype == OBJ_WAYPOINT) { cur_waypoint = find_waypoint_with_instance(ptr->instance); Assert(cur_waypoint != NULL); cur_waypoint_list = cur_waypoint->get_parent_list(); } } int update_dialog_boxes() { int z; nprintf(("Fred routing", "updating dialog boxes\n")); // check wing first, since ships are dependent on wings, but not the reverse z = Wing_editor_dialog.update_data(0); if (z) { nprintf(("Fred routing", "wing dialog save failed\n")); Wing_editor_dialog.SetWindowPos(&Fred_main_wnd->wndTop, 0, 0, 0, 0, SWP_SHOWWINDOW | SWP_NOMOVE | SWP_NOSIZE); return z; } z = Ship_editor_dialog.update_data(0); if (z) { nprintf(("Fred routing", "ship dialog save failed\n")); Ship_editor_dialog.SetWindowPos(&Fred_main_wnd->wndTop, 0, 0, 0, 0, SWP_SHOWWINDOW | SWP_NOMOVE | SWP_NOSIZE); return z; } z = Prop_editor_dialog.update_data(0); if (z) { nprintf(("Fred routing", "prop dialog save failed\n")); Prop_editor_dialog.SetWindowPos(&Fred_main_wnd->wndTop, 0, 0, 0, 0, SWP_SHOWWINDOW | SWP_NOMOVE | SWP_NOSIZE); return z; } z = Waypoint_editor_dialog.update_data(0); if (z) { nprintf(("Fred routing", "waypoint dialog save failed\n")); Waypoint_editor_dialog.SetWindowPos(&Fred_main_wnd->wndTop, 0, 0, 0, 0, SWP_SHOWWINDOW | SWP_NOMOVE | SWP_NOSIZE); return z; } z = Jumpnode_editor_dialog.update_data(0); if (z) { nprintf(("Fred routing", "jumpnode dialog save failed\n")); Jumpnode_editor_dialog .SetWindowPos(&Fred_main_wnd->wndTop, 0, 0, 0, 0, SWP_SHOWWINDOW | SWP_NOMOVE | SWP_NOSIZE); return z; } update_map_window(); return 0; } int delete_object(int obj) { int r; Ship_editor_dialog.bypass_all++; r = common_object_delete(obj); Ship_editor_dialog.bypass_all--; return r; } int delete_object(object *ptr) { int r; Ship_editor_dialog.bypass_all++; r = common_object_delete(OBJ_INDEX(ptr)); Ship_editor_dialog.bypass_all--; return r; } int delete_ship(int ship) { int r; Ship_editor_dialog.bypass_all++; r = common_object_delete(Ships[ship].objnum); Ship_editor_dialog.bypass_all--; return r; } int common_object_delete(int obj) { char msg[255]; const char *name; int i, z, r, type; auto jnp = Jump_nodes.end(); type = Objects[obj].type; if (type == OBJ_START) { i = Objects[obj].instance; if (Player_starts < 2) { // player 1 start Fred_main_wnd->MessageBox("Must have at least 1 player starting point!", NULL, MB_OK | MB_ICONEXCLAMATION); unmark_object(obj); return 1; } Assert((i >= 0) && (i < MAX_SHIPS)); sprintf(msg, "Player %d", i + 1); name = msg; r = reference_handler(name, sexp_ref_type::PLAYER, obj); if (r) return r; if (Ships[i].wingnum >= 0) { r = delete_ship_from_wing(i); if (r) return r; } Objects[obj].type = OBJ_SHIP; // was allocated as a ship originally, so remove as such. invalidate_references(name, sexp_ref_type::PLAYER); // check if any ship is docked with this ship and break dock if so while (object_is_docked(&Objects[obj])) { ai_do_objects_undocked_stuff(&Objects[obj], dock_get_first_docked_object(&Objects[obj])); } ensure_valid_player_start_shipnum(); // may need a new single player start Player_starts--; } else if (type == OBJ_WAYPOINT) { waypoint *wpt = find_waypoint_with_instance(Objects[obj].instance); Assert(wpt != NULL); waypoint_list *wp_list = wpt->get_parent_list(); auto count = wp_list->get_waypoints().size(); // we'll end up deleting the path, so check for path references if (count == 1) { name = wp_list->get_name(); r = reference_handler(name, sexp_ref_type::WAYPOINT_PATH, obj); if (r) return r; } // check for waypoint references waypoint_stuff_name(msg, *wpt); name = msg; r = reference_handler(name, sexp_ref_type::WAYPOINT, obj); if (r) return r; // at this point we've confirmed we want to delete it invalidate_references(name, sexp_ref_type::WAYPOINT); if (count == 1) { invalidate_references(wp_list->get_name(), sexp_ref_type::WAYPOINT_PATH); } // save info needed to update shifted waypoint references after removal int deleted_index = wpt->get_index(); char list_name[NAME_LENGTH]; strcpy_s(list_name, wp_list->get_name()); // the actual removal code has been moved to this function in waypoints.cpp waypoint_remove(wpt); // update SEXP and AI goal references for waypoints that shifted down for (int wi = deleted_index; wi < (int)count - 1; wi++) { char old_wpt_name[NAME_LENGTH]; char new_wpt_name[NAME_LENGTH]; waypoint_stuff_name(old_wpt_name, list_name, wi + 2); // old 1-based number waypoint_stuff_name(new_wpt_name, list_name, wi + 1); // new 1-based number update_sexp_references(old_wpt_name, new_wpt_name); ai_update_goal_references(sexp_ref_type::WAYPOINT, old_wpt_name, new_wpt_name); } } else if (type == OBJ_SHIP) { name = Ships[Objects[obj].instance].ship_name; r = reference_handler(name, sexp_ref_type::SHIP, obj); if (r) return r; z = Objects[obj].instance; if (Ships[z].wingnum >= 1) { invalidate_references(name, sexp_ref_type::SHIP); r = delete_ship_from_wing(z); if (r) return r; } else if (Ships[z].wingnum >= 0) { r = delete_ship_from_wing(z); if (r) return r; invalidate_references(name, sexp_ref_type::SHIP); } delete_reinforcement(name); // check if any ship is docked with this ship and break dock if so while (object_is_docked(&Objects[obj])) { ai_do_objects_undocked_stuff(&Objects[obj], dock_get_first_docked_object(&Objects[obj])); } } else if (type == OBJ_PROP) { // Delete briefing icons maybe? } else if (type == OBJ_POINT) { Assert(Briefing_dialog); Briefing_dialog->delete_icon(Objects[obj].instance); Update_window = 1; return 0; } else if (type == OBJ_JUMP_NODE) { // find the jump node while the object still exists; the defunct entry is removed below jnp = std::find_if(Jump_nodes.begin(), Jump_nodes.end(), [obj](const CJumpNode &jn) { return jn.GetSCPObjectNumber() == obj; }); } unmark_object(obj); //obj_delete() handles all type-specific cleanup and frees up the object slot where the object used to reside obj_delete(obj); //for jump nodes, obj_delete() frees the node's resources via jumpnode_delete(), but the //editor is responsible for removing the defunct entry from the Jump_nodes vector if (jnp != Jump_nodes.end()) Jump_nodes.erase(jnp); set_modified(); Update_window = 1; return 0; } void delete_marked() { object *ptr, *next; delete_flag = 0; ptr = GET_FIRST(&obj_used_list); while (ptr != END_OF_LIST(&obj_used_list)) { next = GET_NEXT(ptr); if (ptr->flags[Object::Object_Flags::Marked]) if (delete_object(ptr) == 2) // user went to a reference, so don't get in the way. break; ptr = next; } if (!delete_flag) set_cur_object_index(-1); Update_window = 1; } void delete_reinforcement(const char *name) { int i = find_item_with_string(Reinforcements, &reinforcements::name, name); if (i < 0) return; Reinforcements.erase(Reinforcements.begin() + i); set_modified(); } // delete ship, removing it from its wing if necessary. int delete_ship_from_wing(int ship) { char name[NAME_LENGTH]; int i, r, wing, end; wing = Ships[ship].wingnum; if (wing >= 0) { if (Wings[wing].wave_count == 1) { cur_wing = -1; Update_wing = 1; r = delete_wing(wing, 1); if (r) { if (r == 2){ delete_flag = 1; } return r; } } else { i = Wings[wing].wave_count; end = i - 1; while (i--){ if (wing_objects[wing][i] == Ships[ship].objnum){ break; } } Assert(i != -1); // Error, object should be in wing. if (Wings[wing].special_ship == i){ Wings[wing].special_ship = 0; } else if (Wings[wing].special_ship > i) { Wings[wing].special_ship--; } if (i != end) { wing_objects[wing][i] = wing_objects[wing][end]; Wings[wing].ship_index[i] = Wings[wing].ship_index[end]; 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); } } const auto max_threshold_before = MAX_SHIPS_PER_WING - Wings[wing].wave_count; if (Wings[wing].threshold > max_threshold_before){ Wings[wing].threshold = max_threshold_before; } Wings[wing].wave_count--; if (Wings[wing].wave_count){ const auto max_threshold_after = MAX_SHIPS_PER_WING - Wings[wing].wave_count; if (Wings[wing].threshold > max_threshold_after) { Wings[wing].threshold = max_threshold_after; } } } } set_modified(); return 0; } // Return true if current object is valid and is in a wing. // Else return false. int query_object_in_wing(int obj) { if (query_valid_object(obj)){ if (Ships[Objects[obj].instance].wingnum != -1){ return TRUE; } } return FALSE; } void mark_object(int obj) { Assert(query_valid_object(obj)); if (!(Objects[obj].flags[Object::Object_Flags::Marked])) { Objects[obj].flags.set(Object::Object_Flags::Marked); // set as marked Marked++; Update_window = 1; if (cur_object_index == -1){ set_cur_object_index(obj); } Update_ship = Update_wing = Update_prop = 1; Waypoint_editor_dialog.initialize_data(1); Jumpnode_editor_dialog.initialize_data(1); } } void unmark_object(int obj) { Assert(query_valid_object(obj)); if (Objects[obj].flags[Object::Object_Flags::Marked]) { Objects[obj].flags.remove(Object::Object_Flags::Marked); Marked--; Update_window = 1; if (obj == cur_object_index) { // need to find a new index object *ptr; ptr = GET_FIRST(&obj_used_list); while (ptr != END_OF_LIST(&obj_used_list)) { if (ptr->flags[Object::Object_Flags::Marked]) { set_cur_object_index(OBJ_INDEX(ptr)); // found one return; } ptr = GET_NEXT(ptr); } set_cur_object_index(-1); // can't find one; nothing is marked. } Update_ship = Update_wing = Update_prop = 1; Waypoint_editor_dialog.initialize_data(1); Jumpnode_editor_dialog.initialize_data(1); } } // clears the marked flag of all objects (so nothing is marked) void unmark_all() { int i; if (Marked) { for (i=0; iicon_select(-1); unmark_all(); } void clear_menu(CMenu *ptr) { int count; count = ptr->GetMenuItemCount(); while (count--){ ptr->DeleteMenu(count, MF_BYPOSITION); } } void generate_wing_popup_menu(CMenu *mptr, int first_id, int state) { int i, z, columns, rows, count; columns = 1; rows = Num_wings; while (rows > 25) { columns++; rows = Num_wings / columns; } count = rows + 1; for (i=0; iAppendMenu(z, first_id + i, Wings[i].name); } } mptr->DeleteMenu(ID_PLACEHOLDER, MF_BYCOMMAND); } void generate_ship_popup_menu(CMenu *mptr, int first_id, int state, int filter) { int z, ship, columns, rows, count, num_ships; object *ptr; columns = 1; num_ships = ship_get_num_ships(); rows = num_ships; while (rows > 25) { columns++; rows = num_ships / columns; } count = rows + 1; ptr = GET_FIRST(&obj_used_list); while (ptr != END_OF_LIST(&obj_used_list)) { if ((ptr->type == OBJ_SHIP) || ((ptr->type == OBJ_START) && (filter & SHIP_FILTER_PLAYERS))) { z = 1; if (filter & SHIP_FILTER_FLYABLE) { if (!Ship_info[Ships[get_ship_from_obj(ptr)].ship_info_index].is_flyable()){ z = 0; } } if (z) { z = state | MF_STRING; if (!count--) { count = rows; z |= MF_MENUBARBREAK; } ship = ptr->instance; mptr->AppendMenu(z, first_id + ship, Ships[ship].ship_name); } } ptr = GET_NEXT(ptr); } mptr->DeleteMenu(ID_PLACEHOLDER, MF_BYCOMMAND); } // Alternate string lookup function, taking a CString instead. The reason that it's here, // instead of parselo.cpp, is because the class CString require an include of windows.h, // which everyone wants to avoid including in any freespace header files. So.. int string_lookup(const CString &str1, char *strlist[], int max) { int i; for (i=0; iGetMenuItemCount(); while (i--) { if ((submenu = base->GetSubMenu(i))>0) { if (gray_menu_tree(submenu)) { count++; } else { base->EnableMenuItem(i, MF_GRAYED | MF_BYPOSITION); } } else { z = base->GetMenuState(i, MF_BYPOSITION); if (z == MF_ENABLED){ count++; } } } return count; } int query_initial_orders_conflict(int wing) { int i, z; Assert(wing != -1); if (wing == -1){ return 0; } if (query_initial_orders_empty(Wings[wing].ai_goals)){ return 0; } i = Wings[wing].wave_count; // wing has orders, now check ships. while (i--) { z = Ships[Objects[wing_objects[wing][i]].instance].ai_index; if (!query_initial_orders_empty(Ai_info[z].goals)){ // ship also has orders return 1; } } return 0; } int query_initial_orders_empty(ai_goal *ai_goals) { int i; for (i=0; in_docks <= MAX_DOCKS); for (i=0; in_docks; i++) Docking_bay_list[i] = pm->docking_bays[i].name; return pm->n_docks; } // DA 1/7/99 These ship names are not variables int rename_ship(int ship, const char *name, bool update_display_name) { Assert(ship >= 0); Assert(strlen(name) < NAME_LENGTH); // we may not need to rename it if (strcmp(Ships[ship].ship_name, name) == 0) return 0; update_sexp_references(Ships[ship].ship_name, name); ai_update_goal_references(sexp_ref_type::SHIP, Ships[ship].ship_name, name); update_texture_replacements(Ships[ship].ship_name, name); int i = find_item_with_string(Reinforcements, &reinforcements::name, Ships[ship].ship_name); if (i >= 0) strcpy_s(Reinforcements[i].name, name); // keep the ship registry in sync auto reg_it = Ship_registry_map.find(Ships[ship].ship_name); if (reg_it != Ship_registry_map.end()) ship_registry_rename(reg_it->second, name, true); strcpy_s(Ships[ship].ship_name, name); if (ship == cur_ship) Ship_editor_dialog.m_ship_name = _T(name); if (update_display_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; } int invalidate_references(const char *name, sexp_ref_type type) { char new_name[512]; sprintf(new_name, "<%s>", name); update_sexp_references(name, new_name); ai_update_goal_references(type, name, new_name); update_texture_replacements(name, new_name); int i = find_item_with_string(Reinforcements, &reinforcements::name, name); if (i >= 0) strcpy_s(Reinforcements[i].name, new_name); return 0; } int internal_integrity_check() { int i; for (i=0; i<(int)Mission_events.size(); i++) verify_sexp_tree(Mission_events[i].formula); for (i=0; i<(int)Mission_goals.size(); i++) verify_sexp_tree(Mission_goals[i].formula); for (i=0; i= 0) { verify_sexp_tree(Ships[i].arrival_cue); verify_sexp_tree(Ships[i].departure_cue); if (Ships[i].ai_index < 0) Assert(0); if (Ai_info[Ships[i].ai_index].shipnum != i) Int3(); } return 0; } void correct_marking() { object *ptr; ptr = GET_FIRST(&obj_used_list); while (ptr != END_OF_LIST(&obj_used_list)) { if (ptr->flags[Object::Object_Flags::Marked]) { if (ptr->flags[Object::Object_Flags::Hidden]) unmark_object(OBJ_INDEX(ptr)); else switch (ptr->type) { case OBJ_WAYPOINT: if (!Show_waypoints) unmark_object(OBJ_INDEX(ptr)); break; case OBJ_START: if (!Show_starts || !Show_ships) unmark_object(OBJ_INDEX(ptr)); break; case OBJ_SHIP: if (!Show_ships) unmark_object(OBJ_INDEX(ptr)); if (!Show_iff[Ships[ptr->instance].team]) unmark_object(OBJ_INDEX(ptr)); break; } } ptr = GET_NEXT(ptr); } } // Fills a combo box with a list of all docking points of type 'type' on ship 'ship'. // Item data is the actual docking point index. void set_valid_dock_points(int ship, int type, CComboBox *box) { int i, z, num, model; model = Ship_info[Ships[ship].ship_info_index].model_num; num = model_get_num_dock_points(model); for (i=0; iAddString(model_get_dock_name(model, i)); box->SetItemData(z, i); } Assert(box->GetCount()); } // Given an object index, find the ship index for that object. int get_ship_from_obj(int obj) { if ((Objects[obj].type == OBJ_SHIP) || (Objects[obj].type == OBJ_START)) return Objects[obj].instance; Int3(); return 0; } int get_ship_from_obj(object *objp) { if ((objp->type == OBJ_SHIP) || (objp->type == OBJ_START)) return objp->instance; Int3(); return 0; } void ai_update_goal_references(sexp_ref_type type, const char *old_name, const char *new_name) { int i; for (i=0; i query_referenced_in_ai_goals(sexp_ref_type type, const char *name) { int i; for (i=0; i= 0) // skip if unused if (query_referenced_in_ai_goals(Ai_info[i].goals, type, name)) return std::make_pair(Ai_info[i].shipnum, sexp_src::SHIP_ORDER); for (i=0; i= 0) unmark_object(obj); return 1; } if (r == 2) { delete_flag = 1; return 2; } } pair = query_referenced_in_ai_goals(type, name); n = pair.first; source = pair.second; if (source != sexp_src::NONE) { switch (source) { case sexp_src::SHIP_ORDER: sprintf(text, "ship \"%s\"", Ships[n].ship_name); break; case sexp_src::WING_ORDER: sprintf(text, "wing \"%s\"", Wings[n].name); break; default: // very bad. Someone added an sexp somewhere and didn't change this. Error(LOCATION, "\"%s\" referenced by an unknown initial orders source! " "Run for the hills and let Hoffoss know right now!", name); } sprintf(msg, "%s is referenced by the initial orders of %s (possibly \n" "more initial orders). Do you want to delete it anyway?\n\n" "(click Cancel to go to the reference)", type_name, text); r = orders_reference_handler(source, n, msg); if (r == 1) { if (obj >= 0) unmark_object(obj); return 1; } if (r == 2) { delete_flag = 1; return 2; } } if ((type != sexp_ref_type::SHIP) && (type != sexp_ref_type::WING)) return 0; n = find_item_with_string(Reinforcements, &reinforcements::name, name); if (n >= 0) { sprintf(msg, "Ship \"%s\" is a reinforcement unit.\n" "Do you want to delete it anyway?", name); r = Fred_main_wnd->MessageBox(msg, NULL, MB_YESNO | MB_ICONEXCLAMATION); if (r == IDNO) { if (obj >= 0) unmark_object(obj); return 1; } } return 0; } int orders_reference_handler(sexp_src source, int source_index, char *msg) { int r = Fred_main_wnd->MessageBox(msg, "Warning", MB_YESNOCANCEL | MB_ICONEXCLAMATION); if (r == IDNO) return 1; if (r == IDYES) return 0; ShipGoalsDlg dlg_goals; switch (source) { case sexp_src::SHIP_ORDER: unmark_all(); mark_object(Ships[source_index].objnum); dlg_goals.self_ship = source_index; dlg_goals.DoModal(); if (!query_initial_orders_empty(Ai_info[Ships[source_index].ai_index].goals)) if ((Ships[source_index].wingnum >= 0) && (query_initial_orders_conflict(Ships[source_index].wingnum))) Fred_main_wnd->MessageBox("This ship's wing also has initial orders", "Possible conflict"); break; case sexp_src::WING_ORDER: unmark_all(); mark_wing(source_index); dlg_goals.self_wing = source_index; dlg_goals.DoModal(); if (query_initial_orders_conflict(source_index)) Fred_main_wnd->MessageBox("One or more ships of this wing also has initial orders", "Possible conflict"); break; default: // very bad. Someone added an sexp somewhere and didn't change this. Error(LOCATION, "Unknown initial order reference source"); } delete_flag = 1; return 2; } int sexp_reference_handler(int node, sexp_src source, int source_index, char *msg) { int r, n; r = Fred_main_wnd->MessageBox(msg, "Warning", MB_YESNOCANCEL | MB_ICONEXCLAMATION); if (r == IDNO) return 1; if (r == IDYES) return 0; n = source_index; switch (source) { case sexp_src::SHIP_ARRIVAL: case sexp_src::SHIP_DEPARTURE: if (!Ship_editor_dialog.GetSafeHwnd()) Ship_editor_dialog.Create(); Ship_editor_dialog.SetWindowPos(&Fred_main_wnd->wndTop, 0, 0, 0, 0, SWP_SHOWWINDOW | SWP_NOMOVE | SWP_NOSIZE); Ship_editor_dialog.ShowWindow(SW_RESTORE); Ship_editor_dialog.select_sexp_node = node; unmark_all(); mark_object(Ships[n].objnum); break; case sexp_src::WING_ARRIVAL: case sexp_src::WING_DEPARTURE: if (!Wing_editor_dialog.GetSafeHwnd()) Wing_editor_dialog.Create(); Wing_editor_dialog.SetWindowPos(&Fred_main_wnd->wndTop, 0, 0, 0, 0, SWP_SHOWWINDOW | SWP_NOMOVE | SWP_NOSIZE); Wing_editor_dialog.ShowWindow(SW_RESTORE); Wing_editor_dialog.select_sexp_node = node; unmark_all(); mark_wing(n); break; case sexp_src::EVENT: if (Message_editor_dlg) { Fred_main_wnd->MessageBox("You must close the message editor before the event editor can be opened"); break; } if (!Event_editor_dlg) { Event_editor_dlg = new event_editor; Event_editor_dlg->select_sexp_node = node; Event_editor_dlg->Create(event_editor::IDD); } Event_editor_dlg->SetWindowPos(&CWnd::wndTop, 0, 0, 0, 0, SWP_SHOWWINDOW | SWP_NOMOVE | SWP_NOSIZE); Event_editor_dlg->ShowWindow(SW_RESTORE); break; case sexp_src::MISSION_GOAL: { CMissionGoalsDlg dlg; dlg.select_sexp_node = node; dlg.DoModal(); break; } case sexp_src::MISSION_CUTSCENE: { CMissionCutscenesDlg dlg; dlg.select_sexp_node = node; dlg.DoModal(); break; } case sexp_src::DEBRIEFING: { if (!Debriefing_dialog) { Debriefing_dialog = new debriefing_editor_dlg; Debriefing_dialog->create(); } Debriefing_dialog->SetWindowPos(&CWnd::wndTop, 0, 0, 0, 0, SWP_SHOWWINDOW | SWP_NOMOVE | SWP_NOSIZE); Debriefing_dialog->ShowWindow(SW_RESTORE); Debriefing_dialog->focus_sexp(node); break; } case sexp_src::BRIEFING: { if (!Briefing_dialog) { Briefing_dialog = new briefing_editor_dlg; Briefing_dialog->create(); } Briefing_dialog->SetWindowPos(&Briefing_dialog->wndTop, 0, 0, 0, 0, SWP_SHOWWINDOW | SWP_NOMOVE | SWP_NOSIZE); Briefing_dialog->ShowWindow(SW_RESTORE); Briefing_dialog->focus_sexp(node); break; } default: // very bad. Someone added an sexp somewhere and didn't change this. Error(LOCATION, "Unknown sexp reference source"); } delete_flag = 1; return 2; } char *object_name(int obj) { static char text[80]; if (!query_valid_object(obj)) return "*none*"; switch (Objects[obj].type) { case OBJ_SHIP: case OBJ_START: return Ships[Objects[obj].instance].ship_name; case OBJ_WAYPOINT: waypoint_stuff_name(text, Objects[obj].instance); return text; case OBJ_POINT: return "Briefing icon"; } return "*unknown*"; } const char *get_order_name(ai_goal_mode order) { int i; if (order == AI_GOAL_NONE) // special case return "None"; for (i=0; itype == OBJ_WAYPOINT) { waypoint *wpt = find_waypoint_with_instance(objp->instance); Assert(wpt != NULL); wpt->set_pos(&objp->pos); } if ((objp->type == OBJ_SHIP) || (objp->type == OBJ_START)) // do we have a ship? { // reset the already-handled flag (inefficient, but it's FRED, so who cares) for (int i = 0; i < MAX_OBJECTS; i++) Objects[i].flags.remove(Object::Object_Flags::Docked_already_handled); // move all docked objects docked to me dock_move_docked_objects(objp); } } // determine if all the ships in a given wing are all marked or not. int query_whole_wing_marked(int wing) { int count = 0; object *ptr; if (!Wings[wing].wave_count) return 0; ptr = GET_FIRST(&obj_used_list); while (ptr != END_OF_LIST(&obj_used_list)) { if (ptr->flags[Object::Object_Flags::Marked]) if ((ptr->type == OBJ_SHIP) || (ptr->type == OBJ_START)) if (Ships[get_ship_from_obj(ptr)].wingnum == wing) count++; ptr = GET_NEXT(ptr); } if (count == Wings[wing].wave_count) return 1; return 0; } CJumpNode *jumpnode_get_by_name(const CString& name) { CJumpNode *jnp = jumpnode_get_by_name((LPCTSTR) name); return jnp; } // function which adds all current ships in the Fred mission to the passed in combo box. useful for // building up ship lists for arrival/departure targets void management_add_ships_to_combo( CComboBox *box, int flags ) { object *objp; int id, i, restrict_to_players; box->ResetContent(); // add the "special" targets, i.e. any friendly, any hostile, etc. if (flags & SHIPS_2_COMBO_SPECIAL) { for (restrict_to_players = 0; restrict_to_players < 2; restrict_to_players++) { for (i = 0; i < (int)Iff_info.size(); i++) { char tmp[NAME_LENGTH + 15]; stuff_special_arrival_anchor_name(tmp, i, restrict_to_players, false); id = box->AddString(tmp); box->SetItemData(id, get_special_anchor(tmp).value()); } } } // either add all ships to the list, or only add ships with docking bays. if ( flags & SHIPS_2_COMBO_ALL_SHIPS ) { for ( objp = GET_FIRST(&obj_used_list); objp != END_OF_LIST(&obj_used_list); objp = GET_NEXT(objp) ) { if ( ((objp->type == OBJ_SHIP) || (objp->type == OBJ_START)) && !(objp->flags[Object::Object_Flags::Marked]) ) { id = box->AddString(Ships[get_ship_from_obj(objp)].ship_name); box->SetItemData(id, get_ship_from_obj(objp)); } } } else if ( flags & SHIPS_2_COMBO_DOCKING_BAY_ONLY ) { for ( objp = GET_FIRST(&obj_used_list); objp != END_OF_LIST(&obj_used_list); objp = GET_NEXT(objp) ) { if ( ((objp->type == OBJ_SHIP) || (objp->type == OBJ_START)) && !(objp->flags[Object::Object_Flags::Marked]) ) { polymodel *pm; // determine if this ship has a docking bay pm = model_get( Ship_info[Ships[objp->instance].ship_info_index].model_num ); Assert( pm ); if ( pm->ship_bay && (pm->ship_bay->num_paths > 0) ) { id = box->AddString(Ships[get_ship_from_obj(objp)].ship_name); box->SetItemData(id, get_ship_from_obj(objp)); } } } } } char *reg_read_string( char *section, char *name, char *default_value ) { HKEY hKey = NULL; DWORD dwType, dwLen; char keyname[1024]; static char tmp_string_data[1024]; LONG lResult; strcpy_s( keyname, section ); lResult = RegOpenKeyEx( HKEY_LOCAL_MACHINE, // Where it is keyname, // name of key NULL, // DWORD reserved KEY_QUERY_VALUE, // Allows all changes &hKey ); // Location to store key if ( lResult != ERROR_SUCCESS ) { mprintf(( "Error opening registry key '%s'\n", keyname )); goto Cleanup; } if ( !name ) { mprintf(( "No variable name passed\n" )); goto Cleanup; } dwLen = 1024; lResult = RegQueryValueEx( hKey, // Handle to key name, // The values name NULL, // DWORD reserved &dwType, // What kind it is (ubyte *)&tmp_string_data, // value to set &dwLen ); // How many bytes to set if ( lResult != ERROR_SUCCESS ) { mprintf(( "Error reading registry key '%s'\n", name )); goto Cleanup; } default_value = tmp_string_data; Cleanup: if ( hKey ) RegCloseKey(hKey); return default_value; } // Goober5000 int wing_is_player_wing(int wing) { int i; if (wing < 0) return 0; if (The_mission.game_type & MISSION_TYPE_MULTI_TEAMS) { for (i=0; i::iterator ii = Fred_texture_replacements.begin(); ii != Fred_texture_replacements.end(); ++ii) { if (!stricmp(ii->ship_name, old_name)) strcpy_s(ii->ship_name, new_name); } } HBITMAP load_btnface_mapped(UINT id) { std::array cmap; cmap[0].from = RGB(255, 0, 255); // magenta (transparent background) cmap[0].to = GetSysColor(COLOR_BTNFACE); cmap[1].from = RGB(192, 192, 192); // dark gray (traditional background) cmap[1].to = GetSysColor(COLOR_BTNFACE); cmap[2].from = RGB(128, 128, 128); cmap[2].to = GetSysColor(COLOR_BTNSHADOW); cmap[3].from = RGB(255, 255, 255); cmap[3].to = GetSysColor(COLOR_BTNHIGHLIGHT); cmap[4].from = RGB(0, 0, 0); cmap[4].to = GetSysColor(COLOR_BTNTEXT); return CreateMappedBitmap(AfxGetResourceHandle(), id, 0, cmap.data(), sz2i(cmap.size())); } HICON load_button_icon(UINT id, COLORREF transparent) { // Load the resource bitmap as a device-dependent bitmap. HBITMAP color = (HBITMAP)::LoadBitmap(AfxGetResourceHandle(), MAKEINTRESOURCE(id)); if (color == nullptr) return nullptr; BITMAP bm; ::GetObject(color, sizeof(bm), &bm); HDC screen_dc = ::GetDC(nullptr); HDC color_dc = ::CreateCompatibleDC(screen_dc); HDC mask_dc = ::CreateCompatibleDC(screen_dc); // Build a 1-bpp mask: pixels matching the transparent key become white (1), // everything else black (0). This is the AND mask of the icon. HBITMAP mask = ::CreateBitmap(bm.bmWidth, bm.bmHeight, 1, 1, nullptr); HBITMAP old_color = (HBITMAP)::SelectObject(color_dc, color); HBITMAP old_mask = (HBITMAP)::SelectObject(mask_dc, mask); ::SetBkColor(color_dc, transparent); ::BitBlt(mask_dc, 0, 0, bm.bmWidth, bm.bmHeight, color_dc, 0, 0, SRCCOPY); // Punch the transparent pixels in the color bitmap to black (D & ~S), so the // icon's XOR stage leaves the background untouched where the mask is set. ::BitBlt(color_dc, 0, 0, bm.bmWidth, bm.bmHeight, mask_dc, 0, 0, 0x00220326 /*DSna: D & ~S*/); ::SelectObject(color_dc, old_color); ::SelectObject(mask_dc, old_mask); ::DeleteDC(color_dc); ::DeleteDC(mask_dc); ::ReleaseDC(nullptr, screen_dc); ICONINFO ii = {}; ii.fIcon = TRUE; ii.hbmMask = mask; ii.hbmColor = color; HICON icon = ::CreateIconIndirect(&ii); // CreateIconIndirect copies the bitmaps, so the originals can be freed. ::DeleteObject(mask); ::DeleteObject(color); return icon; }