// SONIC ROBO BLAST 2 //----------------------------------------------------------------------------- // Copyright (C) 1998-2000 by DooM Legacy Team. // Copyright (C) 1999-2020 by Sonic Team Junior. // Copyright (C) 2020 by Nev3r. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. // See the 'LICENSE' file for more details. //----------------------------------------------------------------------------- /// \file taglist.c /// \brief Ingame sector/line/mapthing tagging. #include "taglist.h" #include "z_zone.h" #include "r_data.h" // Bit array of whether a tag exists for sectors/lines/things. bitarray_t tags_available[BIT_ARRAY_SIZE (MAXTAGS)]; size_t num_tags; // Taggroups are used to list elements of the same tag, for iteration. // Since elements can now have multiple tags, it means an element may appear // in several taggroups at the same time. These are built on level load. taggroup_t* tags_sectors[MAXTAGS + 1]; taggroup_t* tags_lines[MAXTAGS + 1]; taggroup_t* tags_mapthings[MAXTAGS + 1]; /// Adds a tag to a given element's taglist. /// \warning This does not rebuild the global taggroups, which are used for iteration. void Tag_Add (taglist_t* list, const mtag_t tag) { list->tags = Z_Realloc(list->tags, (list->count + 1) * sizeof(list->tags), PU_LEVEL, NULL); list->tags[list->count++] = tag; } /// Sets the first tag entry in a taglist. /// Replicates the old way of accessing element->tag. void Tag_FSet (taglist_t* list, const mtag_t tag) { if (!list->count) { Tag_Add(list, tag); return; } list->tags[0] = tag; } /// Gets the first tag entry in a taglist. /// Replicates the old way of accessing element->tag. mtag_t Tag_FGet (const taglist_t* list) { if (list->count) return list->tags[0]; return 0; } /// Returns true if the given tag exist inside the list. boolean Tag_Find (const taglist_t* list, const mtag_t tag) { size_t i; for (i = 0; i < list->count; i++) if (list->tags[i] == tag) return true; return false; } /// Returns true if at least one tag is shared between two given lists. boolean Tag_Share (const taglist_t* list1, const taglist_t* list2) { size_t i; for (i = 0; i < list1->count; i++) if (Tag_Find(list2, list1->tags[i])) return true; return false; } /// Returns true if both lists are identical. boolean Tag_Compare (const taglist_t* list1, const taglist_t* list2) { size_t i; if (list1->count != list2->count) return false; for (i = 0; i < list1->count; i++) if (list1->tags[i] != list2->tags[i]) return false; return true; } /// Search for an element inside a global taggroup. size_t Taggroup_Find (const taggroup_t *group, const size_t id) { size_t i; if (!group) return -1; for (i = 0; i < group->count; i++) if (group->elements[i] == id) return i; return -1; } /// group->count, but also checks for NULL size_t Taggroup_Count (const taggroup_t *group) { return group ? group->count : 0; } /// Iterate thru elements in a global taggroup. INT32 Taggroup_Iterate ( taggroup_t *garray[], const size_t max_elements, const mtag_t tag, const size_t p) { const taggroup_t *group; if (tag == MTAG_GLOBAL) { if (p < max_elements) return p; return -1; } group = garray[(UINT16)tag]; if (group) { if (p < group->count) return group->elements[p]; return -1; } return -1; } /// Add an element to a global taggroup. void Taggroup_Add (taggroup_t *garray[], const mtag_t tag, size_t id) { taggroup_t *group; size_t i; // Insert position. if (tag == MTAG_GLOBAL) return; group = garray[(UINT16)tag]; // Don't add duplicate entries. if (Taggroup_Find(group, id) != (size_t)-1) return; if (! in_bit_array(tags_available, tag)) { num_tags++; set_bit_array(tags_available, tag); } // Create group if empty. if (!group) { i = 0; group = garray[(UINT16)tag] = Z_Calloc(sizeof(taggroup_t), PU_LEVEL, NULL); } else { // Keep the group element ids in an ascending order. // Find the location to insert the element to. for (i = 0; i < group->count; i++) if (group->elements[i] > id) break; } group->elements = Z_Realloc(group->elements, (group->count + 1) * sizeof(size_t), PU_LEVEL, NULL); // Offset existing elements to make room for the new one. if (i < group->count) memmove(&group->elements[i + 1], &group->elements[i], group->count - i); group->count++; group->elements[i] = id; } static size_t total_elements_with_tag (const mtag_t tag) { return ( Taggroup_Count(tags_sectors[tag]) + Taggroup_Count(tags_lines[tag]) + Taggroup_Count(tags_mapthings[tag]) ); } /// Remove an element from a global taggroup. void Taggroup_Remove (taggroup_t *garray[], const mtag_t tag, size_t id) { taggroup_t *group; size_t rempos; size_t oldcount; if (tag == MTAG_GLOBAL) return; group = garray[(UINT16)tag]; if ((rempos = Taggroup_Find(group, id)) == (size_t)-1) return; if (group->count == 1 && total_elements_with_tag(tag) == 1) { num_tags--; unset_bit_array(tags_available, tag); } // Strip away taggroup if no elements left. if (!(oldcount = group->count--)) { Z_Free(group->elements); Z_Free(group); garray[(UINT16)tag] = NULL; } else { size_t *newelements = Z_Malloc(group->count * sizeof(size_t), PU_LEVEL, NULL); size_t i; // Copy the previous entries save for the one to remove. for (i = 0; i < rempos; i++) newelements[i] = group->elements[i]; for (i = rempos + 1; i < oldcount; i++) newelements[i - 1] = group->elements[i]; Z_Free(group->elements); group->elements = newelements; } } // Initialization. static void Taglist_AddToSectors (const mtag_t tag, const size_t itemid) { Taggroup_Add(tags_sectors, tag, itemid); } static void Taglist_AddToLines (const mtag_t tag, const size_t itemid) { Taggroup_Add(tags_lines, tag, itemid); } static void Taglist_AddToMapthings (const mtag_t tag, const size_t itemid) { Taggroup_Add(tags_mapthings, tag, itemid); } /// After all taglists have been built for each element (sectors, lines, things), /// the global taggroups, made for iteration, are built here. void Taglist_InitGlobalTables(void) { size_t i, j; memset(tags_available, 0, sizeof tags_available); num_tags = 0; for (i = 0; i < MAXTAGS; i++) { tags_sectors[i] = NULL; tags_lines[i] = NULL; tags_mapthings[i] = NULL; } for (i = 0; i < numsectors; i++) { for (j = 0; j < sectors[i].tags.count; j++) Taglist_AddToSectors(sectors[i].tags.tags[j], i); } for (i = 0; i < numlines; i++) { for (j = 0; j < lines[i].tags.count; j++) Taglist_AddToLines(lines[i].tags.tags[j], i); } for (i = 0; i < nummapthings; i++) { for (j = 0; j < mapthings[i].tags.count; j++) Taglist_AddToMapthings(mapthings[i].tags.tags[j], i); } } // Iteration, ingame search. INT32 Tag_Iterate_Sectors (const mtag_t tag, const size_t p) { return Taggroup_Iterate(tags_sectors, numsectors, tag, p); } INT32 Tag_Iterate_Lines (const mtag_t tag, const size_t p) { return Taggroup_Iterate(tags_lines, numlines, tag, p); } INT32 Tag_Iterate_Things (const mtag_t tag, const size_t p) { return Taggroup_Iterate(tags_mapthings, nummapthings, tag, p); } INT32 Tag_FindLineSpecial(const INT16 special, const mtag_t tag) { size_t i; if (tag == MTAG_GLOBAL) { for (i = 0; i < numlines; i++) if (lines[i].special == special) return i; } else if (tags_lines[(UINT16)tag]) { taggroup_t *tagged = tags_lines[(UINT16)tag]; for (i = 0; i < tagged->count; i++) if (lines[tagged->elements[i]].special == special) return tagged->elements[i]; } return -1; } /// Backwards compatibility iteration function for Lua scripts. INT32 P_FindSpecialLineFromTag(INT16 special, INT16 tag, INT32 start) { if (tag == -1) { start++; if (start >= (INT32)numlines) return -1; while (start < (INT32)numlines && lines[start].special != special) start++; return start; } else { size_t p = 0; INT32 id; // For backwards compatibility's sake, simulate the old linked taglist behavior: // Iterate through the taglist and find the "start" line's position in the list, // And start checking with the next one (if it exists). if (start != -1) { for (; (id = Tag_Iterate_Lines(tag, p)) >= 0; p++) if (id == start) { p++; break; } } for (; (id = Tag_Iterate_Lines(tag, p)) >= 0; p++) if (lines[id].special == special) return id; return -1; } } // Ingame list manipulation. /// Changes the first tag for a given sector, and updates the global taggroups. void Tag_SectorFSet (const size_t id, const mtag_t tag) { sector_t* sec = §ors[id]; mtag_t curtag = Tag_FGet(&sec->tags); if (curtag == tag) return; Taggroup_Remove(tags_sectors, curtag, id); Taggroup_Add(tags_sectors, tag, id); Tag_FSet(&sec->tags, tag); }