#include #include "globalincs/linklist.h" #include "object/object.h" #include "object/waypoint.h" #include "network/multiutil.h" #include //********************GLOBALS******************** SCP_vector Waypoint_lists; // In order to restore ai_info to a plain-old-data struct, ai_info->wp_index // now uses size_t rather than iterator to index into the list. If ai_info // eventually becomes an actual class, we ought to go back to using iterators. // 2023 addendum - Perhaps not; iterators don't gain us much compared to // indexes. See commit b4cc5d72f4 for the historical context. const int INVALID_WAYPOINT_POSITION = INT_MAX; //********************CLASS MEMBERS******************** waypoint::waypoint() { this->m_parsed_position.xyz.x = 0.0f; this->m_parsed_position.xyz.y = 0.0f; this->m_parsed_position.xyz.z = 0.0f; this->m_objnum = -1; } waypoint::waypoint(const vec3d *position) { Assert(position != NULL); this->m_parsed_position.xyz.x = position->xyz.x; this->m_parsed_position.xyz.y = position->xyz.y; this->m_parsed_position.xyz.z = position->xyz.z; this->m_objnum = -1; } const vec3d *waypoint::get_pos() const { if (m_objnum >= 0) return &Objects[m_objnum].pos; return &m_parsed_position; } int waypoint::get_objnum() const { return m_objnum; } const waypoint_list *waypoint::get_parent_list() const { int list_index = get_parent_list_index(); if (list_index < 0 || list_index >= static_cast(Waypoint_lists.size())) return nullptr; return &Waypoint_lists[list_index]; } waypoint_list *waypoint::get_parent_list() { int list_index = get_parent_list_index(); if (list_index < 0 || list_index >= static_cast(Waypoint_lists.size())) return nullptr; return &Waypoint_lists[list_index]; } int waypoint::get_parent_list_index() const { if (m_objnum < 0) return -1; return calc_waypoint_list_index(Objects[m_objnum].instance); } int waypoint::get_index() const { if (m_objnum < 0) return -1; return calc_waypoint_index(Objects[m_objnum].instance); } void waypoint::set_pos(const vec3d *pos) { Assert(pos != NULL); if (m_objnum >= 0) Objects[m_objnum].pos = *pos; else this->m_parsed_position = *pos; } waypoint_list::waypoint_list() { this->m_name[0] = '\0'; this->m_no_draw_lines = false; this->m_has_custom_color = false; this->m_color_r = this->m_color_g = this->m_color_b = 255; } waypoint_list::waypoint_list(const char *name) { Assert(name != NULL); Assert(find_matching_waypoint_list(name) == NULL); strcpy_s(this->m_name, name); this->m_no_draw_lines = false; this->m_has_custom_color = false; this->m_color_r = this->m_color_g = this->m_color_b = 255; } const char *waypoint_list::get_name() const { return m_name; } const SCP_vector &waypoint_list::get_waypoints() const { return m_waypoints; } SCP_vector &waypoint_list::get_waypoints() { return m_waypoints; } void waypoint_list::set_name(const char *name) { Assertion(name != nullptr, "Waypoint name cannot be null!"); auto len = strlen(name); if (len >= NAME_LENGTH) len = NAME_LENGTH - 5; // leave room for a colon, three digits, and a null terminator for points that belong to this list strncpy(this->m_name, name, len); this->m_name[len] = '\0'; } void waypoint_list::set_no_draw_lines(bool val) { m_no_draw_lines = val; } void waypoint_list::set_color(int r, int g, int b) { m_color_r = r; m_color_g = g; m_color_b = b; m_has_custom_color = true; } void waypoint_list::clear_color() { m_has_custom_color = false; } bool waypoint_list::get_no_draw_lines() const { return m_no_draw_lines; } bool waypoint_list::get_has_custom_color() const { return m_has_custom_color; } int waypoint_list::get_color_r() const { return m_color_r; } int waypoint_list::get_color_g() const { return m_color_g; } int waypoint_list::get_color_b() const { return m_color_b; } //********************FUNCTIONS******************** void waypoint_level_close() { // Clear all waypoint lists and all their waypoints. Note that this can happen either before or after objects are cleaned up. Waypoint_lists.clear(); } int calc_waypoint_instance(int waypoint_list_index, int waypoint_index) { Assert(waypoint_list_index >= 0); Assert(waypoint_index >= 0); return waypoint_list_index * 0x10000 + waypoint_index; } void calc_waypoint_indexes(int waypoint_instance, int &waypoint_list_index, int &waypoint_index) { Assert(waypoint_instance >= 0); waypoint_list_index = calc_waypoint_list_index(waypoint_instance); waypoint_index = calc_waypoint_index(waypoint_instance); } int calc_waypoint_list_index(int waypoint_instance) { if (waypoint_instance < 0) return -1; return waypoint_instance / 0x10000; } int calc_waypoint_index(int waypoint_instance) { if (waypoint_instance < 0) return -1; return waypoint_instance & 0xffff; } void waypoint_create_game_object(waypoint *wpt, int list_index, int wpt_index) { Assert(wpt != NULL); Assert(list_index >= 0); Assert(wpt_index >= 0); flagset default_flags; default_flags.set(Object::Object_Flags::Renders); wpt->m_objnum = obj_create(OBJ_WAYPOINT, -1, calc_waypoint_instance(list_index, wpt_index), NULL, wpt->get_pos(), 0.0f, default_flags); Assert(wpt->m_objnum > -1); if (wpt->m_objnum < 0) { return; } Objects[wpt->m_objnum].net_signature = multi_assign_network_signature(MULTI_SIG_WAYPOINT); } // done immediately after mission load; originally found in aicode.cpp void waypoint_create_game_objects() { int list = 0; for (auto &ii: Waypoint_lists) { int wpt = 0; for (auto &jj: ii.get_waypoints()) { waypoint_create_game_object(&jj, list, wpt); wpt++; } list++; } } waypoint_list *find_matching_waypoint_list(const char *name) { Assert(name != nullptr); for (auto &ii: Waypoint_lists) { if (!stricmp(ii.get_name(), name)) return ⅈ } return nullptr; } int find_matching_waypoint_list_index(const char *name) { Assert(name != nullptr); int i = 0; for (auto &ii: Waypoint_lists) { if (!stricmp(ii.get_name(), name)) return i; i++; } return -1; } // NOTE: waypoint names are always in the format Name:index waypoint *find_matching_waypoint(const char *name) { Assert(name != NULL); for (auto &ii: Waypoint_lists) { auto len = strlen(ii.get_name()); // the first half (before the :) matches if (!strnicmp(ii.get_name(), name, len)) { // this is ok because it could be "Waypoint path 1" vs. "Waypoint path 10" if (*(name + len) != ':') continue; // skip over the : to inspect a new string holding only the index const char *index_str = name + len + 1; if (*index_str == '\0') { nprintf(("waypoints", "possible error with waypoint name '%s': no waypoint number after the colon\n", name)); continue; } // make sure it's actually a number bool valid = true; for (const char *ch = index_str; *ch != '\0'; ch++) { if (!isdigit(*ch)) { valid = false; break; } } if (!valid) { nprintf(("waypoints", "possible error with waypoint name '%s': string after the colon is not a number\n", name)); continue; } // get the number and make sure it's in range uint index = atoi(index_str); if (index < 1 || index > ii.get_waypoints().size()) { nprintf(("waypoints", "possible error with waypoint name '%s': waypoint number is out of range\n", name)); continue; } return find_waypoint_at_index(&ii, index - 1); } } return NULL; } waypoint *find_waypoint_with_objnum(int objnum) { if (objnum < 0 || objnum >= MAX_OBJECTS || Objects[objnum].type != OBJ_WAYPOINT) return nullptr; return find_waypoint_with_instance(Objects[objnum].instance); } waypoint_list *find_waypoint_list_with_instance(int waypoint_instance, int *waypoint_index) { if (waypoint_instance < 0) { if (waypoint_index != NULL) *waypoint_index = -1; return NULL; } waypoint_list *wp_list = find_waypoint_list_at_index(calc_waypoint_list_index(waypoint_instance)); if (wp_list == NULL) { if (waypoint_index != NULL) *waypoint_index = -1; return NULL; } if (waypoint_index != NULL) { *waypoint_index = calc_waypoint_index(waypoint_instance); Assert(*waypoint_index >= 0 && (uint) *waypoint_index < wp_list->get_waypoints().size()); } return wp_list; } waypoint *find_waypoint_with_instance(int waypoint_instance) { if (waypoint_instance < 0) return nullptr; return find_waypoint_at_indexes(calc_waypoint_list_index(waypoint_instance), calc_waypoint_index(waypoint_instance)); } waypoint_list *find_waypoint_list_at_index(int index) { if (SCP_vector_inbounds(Waypoint_lists, index)) return &Waypoint_lists[index]; return nullptr; } waypoint *find_waypoint_at_index(waypoint_list *list, int index) { if (list && SCP_vector_inbounds(list->get_waypoints(), index)) return &list->get_waypoints()[index]; return nullptr; } waypoint *find_waypoint_at_indexes(int list_index, int index) { return find_waypoint_at_index(find_waypoint_list_at_index(list_index), index); } int find_index_of_waypoint_list(const waypoint_list *wp_list) { Assert(wp_list != NULL); ptrdiff_t index = wp_list - Waypoint_lists.data(); if (index >= 0 && index < (ptrdiff_t)Waypoint_lists.size()) return static_cast(index); return -1; } int find_index_of_waypoint(const waypoint_list *wp_list, const waypoint *wpt) { Assert(wp_list != NULL); Assert(wpt != NULL); ptrdiff_t index = wpt - wp_list->get_waypoints().data(); if (index >= 0 && index < (ptrdiff_t)wp_list->get_waypoints().size()) return (int)index; return -1; } void waypoint_find_unique_name(char *dest_name, int start_index) { Assert(dest_name != NULL); Assert(start_index >= 0); int index = start_index; waypoint_list *collision; do { sprintf(dest_name, "Waypoint path %d", index); index++; // valid name if no collision collision = find_matching_waypoint_list(dest_name); } while (collision != NULL); } void waypoint_stuff_name(char *dest, const char *waypoint_list_name, int waypoint_num) { constexpr size_t name_max_len = NAME_LENGTH - 3 - 1 - 1; // a colon, three digits, and a null terminator if (waypoint_num < 1) { Error(LOCATION, "A waypoint number must be at least 1!"); *dest = 0; return; } if (waypoint_num >= 1000) { Error(LOCATION, "This waypoint number has more than three digits! If you actually need this, first convince a coder that you are sane and then ask for the limit to be increased."); *dest = 0; return; } auto name_len = std::min(strlen(waypoint_list_name), name_max_len); strncpy(dest, waypoint_list_name, name_len); sprintf(dest + name_len, ":%d", waypoint_num); } void waypoint_stuff_name(SCP_string &dest, const char *waypoint_list_name, int waypoint_num) { constexpr size_t name_max_len = NAME_LENGTH - 3 - 1 - 1; // a colon, three digits, and a null terminator if (waypoint_num < 1) { Error(LOCATION, "A waypoint number must be at least 1!"); dest = ""; return; } if (waypoint_num >= 1000) { Error(LOCATION, "This waypoint number has more than three digits! If you actually need this, first convince a coder that you are sane and then ask for the limit to be increased."); dest = ""; return; } dest.assign(waypoint_list_name, std::min(strlen(waypoint_list_name), name_max_len)); dest += ":"; dest.append(std::to_string(waypoint_num)); } void waypoint_add_list(const char *name, const SCP_vector &vec_list) { Assert(name != NULL); if (find_matching_waypoint_list(name) != NULL) { Warning(LOCATION, "Waypoint list '%s' already exists in this mission! Not adding the new list...", name); return; } Waypoint_lists.emplace_back(name); auto& wp_list = Waypoint_lists.back(); wp_list.get_waypoints().reserve(vec_list.size()); for (const auto &ii: vec_list) { wp_list.get_waypoints().emplace_back(&ii); } // so that masking in the other function works // though if you actually hit this Assert, you have other problems Assert(wp_list.get_waypoints().size() <= 0xffff); } int waypoint_add(const vec3d *pos, int waypoint_instance, bool first_waypoint_in_list) { Assert(pos != NULL); waypoint_list *wp_list; waypoint *wpt; int wp_list_index, wp_index; // find a new list to start if (waypoint_instance < 0) { // get a name for it char buf[NAME_LENGTH]; waypoint_find_unique_name(buf, static_cast(Waypoint_lists.size()) + 1); // add new list with that name Waypoint_lists.emplace_back(buf); wp_list = &Waypoint_lists.back(); // set up references wp_list_index = static_cast(Waypoint_lists.size()) - 1; wp_index = static_cast(wp_list->get_waypoints().size()); } // create the waypoint on the same list as, and immediately after, waypoint_instance (unless it's the first waypoint) else { calc_waypoint_indexes(waypoint_instance, wp_list_index, wp_index); wp_list = find_waypoint_list_at_index(wp_list_index); if (first_waypoint_in_list) { wp_index = 0; } else { // theoretically waypoint_instance points to a current waypoint, so advance past it wp_index++; } // it has to be on, or at the end of, an existing list Assert(wp_list != NULL); Assert(wp_index <= static_cast(wp_list->get_waypoints().size())); // iterate through all waypoints that are at this index or later, // and edit their instances so that they point to a waypoint one place higher for (object *objp = GET_FIRST(&obj_used_list); objp != END_OF_LIST(&obj_used_list); objp = GET_NEXT(objp)) { if ((objp->type == OBJ_WAYPOINT) && (calc_waypoint_list_index(objp->instance) == wp_list_index) && (calc_waypoint_index(objp->instance) >= wp_index)) objp->instance++; } } // so that masking in the other function works // (though if you actually hit this Assert, you have other problems) Assert(wp_index < 0x10000); // create the waypoint object waypoint new_waypoint(pos); // add it at its appropriate spot, which may be the end of the list wp_list->get_waypoints().insert(wp_list->get_waypoints().begin() + wp_index, new_waypoint); wpt = find_waypoint_at_index(wp_list, wp_index); // apparently we create it in-game too; this is called by both scripting and FRED waypoint_create_game_object(wpt, wp_list_index, wp_index); return wpt->get_objnum(); } void waypoint_remove(const waypoint *wpt) { int objnum = wpt->get_objnum(); int this_list = calc_waypoint_list_index(Objects[objnum].instance); int this_index = calc_waypoint_index(Objects[objnum].instance); waypoint_list *wp_list = find_waypoint_list_at_index(this_list); Assert(wp_list == wpt->get_parent_list()); // special case... this is the only waypoint on its list if (wp_list->get_waypoints().size() == 1) { wp_list->get_waypoints().clear(); // special special case... this is the only waypoint list! if (Waypoint_lists.size() == 1) { Waypoint_lists.clear(); } // shift the other waypoint lists down else { // remove this particular waypoint list Waypoint_lists.erase(Waypoint_lists.begin() + this_list); // iterate through all waypoints that are in lists later than this one, // and edit their instances so that they point to a list one place lower for (object *objp = GET_FIRST(&obj_used_list); objp != END_OF_LIST(&obj_used_list); objp = GET_NEXT(objp)) { if ((objp->type == OBJ_WAYPOINT) && (calc_waypoint_list_index(objp->instance) > this_list)) objp->instance -= 0x10000; } } } // shift the other waypoints down else { // remove this particular waypoint wp_list->get_waypoints().erase(wp_list->get_waypoints().begin() + this_index); // iterate through all waypoints that are later than this one, // and edit their instances so that they point to a waypoint one place lower for (object *objp = GET_FIRST(&obj_used_list); objp != END_OF_LIST(&obj_used_list); objp = GET_NEXT(objp)) { if ((objp->type == OBJ_WAYPOINT) && (calc_waypoint_list_index(objp->instance) == this_list) && (calc_waypoint_index(objp->instance) > this_index)) objp->instance--; } } // FRED has its own object removal logic if (!Fred_running) obj_delete(objnum); }